diff options
Diffstat (limited to 'storage')
592 files changed, 358847 insertions, 890 deletions
diff --git a/storage/archive/azio.c b/storage/archive/azio.c index 916dd8ba59d..b6d66fc0739 100644 --- a/storage/archive/azio.c +++ b/storage/archive/azio.c @@ -16,6 +16,8 @@ #include <stdio.h> #include <string.h> +#include "my_sys.h" + static int const gz_magic[2] = {0x1f, 0x8b}; /* gzip magic header */ static int const az_magic[3] = {0xfe, 0x03, 0x01}; /* az magic header */ @@ -52,8 +54,8 @@ int az_open (azio_stream *s, const char *path, int Flags, File fd) int level = Z_DEFAULT_COMPRESSION; /* compression level */ int strategy = Z_DEFAULT_STRATEGY; /* compression strategy */ - s->stream.zalloc = (alloc_func)0; - s->stream.zfree = (free_func)0; + s->stream.zalloc = my_az_allocator; + s->stream.zfree = my_az_free; s->stream.opaque = (voidpf)0; memset(s->inbuf, 0, AZ_BUFSIZE_READ); memset(s->outbuf, 0, AZ_BUFSIZE_WRITE); diff --git a/storage/archive/ha_archive.cc b/storage/archive/ha_archive.cc index 1146b2eb73a..86dcbf8d732 100644 --- a/storage/archive/ha_archive.cc +++ b/storage/archive/ha_archive.cc @@ -1513,7 +1513,7 @@ void ha_archive::start_bulk_insert(ha_rows rows) Other side of start_bulk_insert, is end_bulk_insert. Here we turn off the bulk insert flag, and set the share dirty so that the next select will call sync for us. */ -int ha_archive::end_bulk_insert() +int ha_archive::end_bulk_insert(bool table_will_be_deleted) { DBUG_ENTER("ha_archive::end_bulk_insert"); bulk_insert= FALSE; diff --git a/storage/archive/ha_archive.h b/storage/archive/ha_archive.h index ab630ed22fd..22f8302982d 100644 --- a/storage/archive/ha_archive.h +++ b/storage/archive/ha_archive.h @@ -134,7 +134,7 @@ public: int optimize(THD* thd, HA_CHECK_OPT* check_opt); int repair(THD* thd, HA_CHECK_OPT* check_opt); void start_bulk_insert(ha_rows rows); - int end_bulk_insert(); + int end_bulk_insert(bool table_will_be_deleted); enum row_type get_row_type() const { return ROW_TYPE_COMPRESSED; diff --git a/storage/csv/ha_tina.cc b/storage/csv/ha_tina.cc index e18d4025777..ad536d40002 100644 --- a/storage/csv/ha_tina.cc +++ b/storage/csv/ha_tina.cc @@ -441,7 +441,7 @@ ha_tina::ha_tina(handlerton *hton, TABLE_SHARE *table_arg) */ current_position(0), next_position(0), local_saved_data_file_length(0), file_buff(0), chain_alloced(0), chain_size(DEFAULT_CHAIN_LENGTH), - local_data_file_version(0), records_is_known(0) + local_data_file_version(0), records_is_known(0), curr_lock_type(F_UNLCK) { /* Set our original buffers from pre-allocated memory */ buffer.set((char*)byte_buffer, IO_SIZE, &my_charset_bin); @@ -729,7 +729,7 @@ const char **ha_tina::bas_ext() const for CSV engine. For more details see mysys/thr_lock.c */ -void tina_get_status(void* param, int concurrent_insert) +void tina_get_status(void* param, my_bool concurrent_insert) { ha_tina *tina= (ha_tina*) param; tina->get_status(); @@ -1496,6 +1496,14 @@ int ha_tina::delete_all_rows() DBUG_RETURN(rc); } +int ha_tina::external_lock(THD *thd __attribute__((unused)), int lock_type) +{ + if (lock_type==F_UNLCK && curr_lock_type == F_WRLCK) + update_status(); + curr_lock_type= lock_type; + return 0; +} + /* Called by the database to lock the table. Keep in mind that this is an internal lock. @@ -1510,7 +1518,7 @@ THR_LOCK_DATA **ha_tina::store_lock(THD *thd, return to; } -/* +/* Create a table. You do not want to leave the table open after a call to this (the database will call ::open() if it needs to). */ diff --git a/storage/csv/ha_tina.h b/storage/csv/ha_tina.h index 02e0700a825..06f23b029b4 100644 --- a/storage/csv/ha_tina.h +++ b/storage/csv/ha_tina.h @@ -85,6 +85,8 @@ class ha_tina: public handler MEM_ROOT blobroot; private: + int curr_lock_type; + bool get_write_pos(my_off_t *end_pos, tina_set *closest_hole); int open_update_temp_file_if_needed(); int init_tina_writer(); @@ -155,6 +157,8 @@ public: bool check_if_incompatible_data(HA_CREATE_INFO *info, uint table_changes); + int external_lock(THD *thd, int lock_type); + THR_LOCK_DATA **store_lock(THD *thd, THR_LOCK_DATA **to, enum thr_lock_type lock_type); diff --git a/storage/federated/ha_federated.cc b/storage/federated/ha_federated.cc index 8deb8693960..d9b1f1ffeeb 100644 --- a/storage/federated/ha_federated.cc +++ b/storage/federated/ha_federated.cc @@ -1988,12 +1988,12 @@ void ha_federated::start_bulk_insert(ha_rows rows) @retval != 0 Error occured at remote server. Also sets my_errno. */ -int ha_federated::end_bulk_insert() +int ha_federated::end_bulk_insert(bool abort) { int error= 0; DBUG_ENTER("ha_federated::end_bulk_insert"); - if (bulk_insert.str && bulk_insert.length) + if (!abort && bulk_insert.str && bulk_insert.length) { if (real_query(bulk_insert.str, bulk_insert.length)) error= stash_remote_error(); diff --git a/storage/federated/ha_federated.h b/storage/federated/ha_federated.h index 2f1c62decca..c0e8ff3bc31 100644 --- a/storage/federated/ha_federated.h +++ b/storage/federated/ha_federated.h @@ -205,7 +205,7 @@ public: int close(void); // required void start_bulk_insert(ha_rows rows); - int end_bulk_insert(); + int end_bulk_insert(bool abort); int write_row(uchar *buf); int update_row(const uchar *old_data, uchar *new_data); int delete_row(const uchar *buf); diff --git a/storage/heap/hp_write.c b/storage/heap/hp_write.c index c3dff67d3d5..29701f8ecbe 100644 --- a/storage/heap/hp_write.c +++ b/storage/heap/hp_write.c @@ -109,7 +109,7 @@ int hp_rb_write_key(HP_INFO *info, HP_KEYDEF *keyinfo, const uchar *record, custom_arg.key_length= hp_rb_make_key(keyinfo, info->recbuf, record, recpos); if (keyinfo->flag & HA_NOSAME) { - custom_arg.search_flag= SEARCH_FIND | SEARCH_UPDATE; + custom_arg.search_flag= SEARCH_FIND | SEARCH_UPDATE | SEARCH_INSERT; keyinfo->rb_tree.flag= TREE_NO_DUPS; } else diff --git a/storage/innobase/buf/buf0buf.c b/storage/innobase/buf/buf0buf.c index 901ce8e0fef..16ed9c9da3c 100644 --- a/storage/innobase/buf/buf0buf.c +++ b/storage/innobase/buf/buf0buf.c @@ -756,7 +756,7 @@ buf_pool_init( /* Wipe contents of frame to eliminate a Purify warning */ -#ifdef HAVE_purify +#ifdef HAVE_valgrind memset(block->frame, '\0', UNIV_PAGE_SIZE); #endif if (srv_use_awe) { diff --git a/storage/innobase/handler/ha_innodb.cc b/storage/innobase/handler/ha_innodb.cc index b35f83cce58..a73932e15dc 100644 --- a/storage/innobase/handler/ha_innodb.cc +++ b/storage/innobase/handler/ha_innodb.cc @@ -1630,6 +1630,9 @@ innobase_init( int err; bool ret; char *default_path; +#ifdef SAFE_MUTEX + my_bool old_safe_mutex_deadlock_detector; +#endif DBUG_ENTER("innobase_init"); handlerton *innobase_hton= (handlerton *)p; @@ -1884,8 +1887,15 @@ innobase_init( srv_sizeof_trx_t_in_ha_innodb_cc = sizeof(trx_t); +#ifdef SAFE_MUTEX + /* Disable deadlock detection as it's very slow for the buffer pool */ + old_safe_mutex_deadlock_detector= safe_mutex_deadlock_detector; + safe_mutex_deadlock_detector= 0; +#endif err = innobase_start_or_create_for_mysql(); - +#ifdef SAFE_MUTEX + safe_mutex_deadlock_detector= old_safe_mutex_deadlock_detector; +#endif if (err != DB_SUCCESS) { my_free(internal_innobase_data_file_path, MYF(MY_ALLOW_ZERO_PTR)); diff --git a/storage/innobase/include/univ.i b/storage/innobase/include/univ.i index 8ab62e655ff..e5e3577b4e6 100644 --- a/storage/innobase/include/univ.i +++ b/storage/innobase/include/univ.i @@ -55,10 +55,10 @@ of the 32-bit x86 assembler in mutex operations. */ # define UNIV_CAN_USE_X86_ASSEMBLER # endif -/* We only try to do explicit inlining of functions with gcc and -Microsoft Visual C++ */ +/* Enable explicit inlining of functions only for compilers known to +support it. */ -# if !defined(__GNUC__) +# if !defined(__GNUC__) && !defined(__SUNPRO_C) # undef UNIV_MUST_NOT_INLINE /* Remove compiler warning */ # define UNIV_MUST_NOT_INLINE # endif @@ -107,7 +107,7 @@ operations (very slow); also UNIV_DEBUG must be defined */ #define UNIV_BTR_DEBUG /* check B-tree links */ #define UNIV_LIGHT_MEM_DEBUG /* light memory debugging */ -#ifdef HAVE_purify +#ifdef HAVE_valgrind /* The following sets all new allocated memory to zero before use: this can be used to eliminate unnecessary Purify warnings, but note that it also masks many bugs Purify could detect. For detailed Purify analysis it diff --git a/storage/innobase/plug.in b/storage/innobase/plug.in.disabled index f7d2abed751..f7d2abed751 100644 --- a/storage/innobase/plug.in +++ b/storage/innobase/plug.in.disabled diff --git a/storage/innobase/srv/srv0start.c b/storage/innobase/srv/srv0start.c index ea88039f3dd..4f261d1171b 100644 --- a/storage/innobase/srv/srv0start.c +++ b/storage/innobase/srv/srv0start.c @@ -104,7 +104,7 @@ static char* srv_monitor_file_name; /* Avoid warnings when using purify */ -#ifdef HAVE_purify +#ifdef HAVE_valgrind static int inno_bcmp(register const char *s1, register const char *s2, register uint len) { diff --git a/storage/innodb_plugin/CMakeLists.txt b/storage/innodb_plugin/CMakeLists.txt index 7762ece9bcd..a4de7fd1a2a 100644 --- a/storage/innodb_plugin/CMakeLists.txt +++ b/storage/innodb_plugin/CMakeLists.txt @@ -75,4 +75,6 @@ SET(INNODB_PLUGIN_SOURCES btr/btr0btr.c btr/btr0cur.c btr/btr0pcur.c btr/btr0sea ut/ut0byte.c ut/ut0dbg.c ut/ut0mem.c ut/ut0rnd.c ut/ut0ut.c ut/ut0vec.c ut/ut0list.c ut/ut0wqueue.c) ADD_DEFINITIONS(-DHAVE_WINDOWS_ATOMICS -DINNODB_RW_LOCKS_USE_ATOMICS -DIB_HAVE_PAUSE_INSTRUCTION) -MYSQL_STORAGE_ENGINE(INNODB_PLUGIN)
\ No newline at end of file + +#Disable storage engine, as we are using XtraDB +#MYSQL_STORAGE_ENGINE(INNODB_PLUGIN) diff --git a/storage/maria/CMakeLists.txt b/storage/maria/CMakeLists.txt new file mode 100644 index 00000000000..cf01a6f1be1 --- /dev/null +++ b/storage/maria/CMakeLists.txt @@ -0,0 +1,91 @@ +# Copyright (C) 2007 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +INCLUDE("${PROJECT_SOURCE_DIR}/win/mysql_manifest.cmake") + +SET(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -DSAFEMALLOC -DSAFE_MUTEX") +SET(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -DSAFEMALLOC -DSAFE_MUTEX") + +INCLUDE_DIRECTORIES(${CMAKE_SOURCE_DIR}/include ${CMAKE_SOURCE_DIR}/zlib + ${CMAKE_SOURCE_DIR}/sql + ${CMAKE_SOURCE_DIR}/regex + ${CMAKE_SOURCE_DIR}/extra/yassl/include) +SET(MARIA_SOURCES ma_init.c ma_open.c ma_extra.c ma_info.c ma_rkey.c + ma_rnext.c ma_rnext_same.c + ma_search.c ma_page.c ma_key_recover.c ma_key.c + ma_locking.c ma_state.c + ma_rrnd.c ma_scan.c ma_cache.c + ma_statrec.c ma_packrec.c ma_dynrec.c + ma_blockrec.c ma_bitmap.c + ma_update.c ma_write.c ma_unique.c + ma_delete.c + ma_rprev.c ma_rfirst.c ma_rlast.c ma_rsame.c + ma_rsamepos.c ma_panic.c ma_close.c ma_create.c + ma_range.c ma_dbug.c ma_checksum.c + ma_changed.c ma_static.c ma_delete_all.c + ma_delete_table.c ma_rename.c ma_check.c + ma_keycache.c ma_preload.c ma_ft_parser.c + ma_ft_update.c ma_ft_boolean_search.c + ma_ft_nlq_search.c ft_maria.c ma_sort.c + ha_maria.cc trnman.c lockman.c tablockman.c + ma_rt_index.c ma_rt_key.c ma_rt_mbr.c ma_rt_split.c + ma_sp_key.c ma_control_file.c ma_loghandler.c + ma_pagecache.c ma_pagecaches.c + ma_checkpoint.c ma_recovery.c ma_commit.c ma_pagecrc.c + ha_maria.h maria_def.h ma_recovery_util.c +) + +IF(NOT SOURCE_SUBLIBS) + + ADD_LIBRARY(maria ${MARIA_SOURCES}) + +ADD_EXECUTABLE(maria_ftdump maria_ftdump.c) +TARGET_LINK_LIBRARIES(maria_ftdump maria myisam mysys dbug strings zlib wsock32) + +ADD_EXECUTABLE(maria_chk maria_chk.c) +TARGET_LINK_LIBRARIES(maria_chk maria myisam mysys dbug strings zlib wsock32) + +ADD_EXECUTABLE(maria_read_log maria_read_log.c) +TARGET_LINK_LIBRARIES(maria_read_log maria myisam mysys dbug strings zlib wsock32) + +ADD_EXECUTABLE(maria_pack maria_pack.c) +TARGET_LINK_LIBRARIES(maria_pack maria myisam mysys dbug strings zlib wsock32) + +ADD_EXECUTABLE(maria_dump_log ma_loghandler.c unittest/ma_loghandler_examples.c) +TARGET_LINK_LIBRARIES(maria_dump_log maria myisam mysys dbug strings zlib wsock32) +SET_TARGET_PROPERTIES(maria_dump_log PROPERTIES COMPILE_FLAGS "-DMARIA_DUMP_LOG") + +ADD_EXECUTABLE(ma_test1 ma_test1.c) +TARGET_LINK_LIBRARIES(ma_test1 maria myisam mysys dbug strings zlib wsock32) + +ADD_EXECUTABLE(ma_test2 ma_test2.c) +TARGET_LINK_LIBRARIES(ma_test2 maria myisam mysys dbug strings zlib wsock32) + +ADD_EXECUTABLE(ma_test3 ma_test3.c) +TARGET_LINK_LIBRARIES(ma_test3 maria myisam mysys dbug strings zlib wsock32) + +ADD_EXECUTABLE(ma_rt_test ma_rt_test.c) +TARGET_LINK_LIBRARIES(ma_rt_test maria myisam mysys dbug strings zlib wsock32) + +ADD_EXECUTABLE(ma_sp_test ma_sp_test.c) +TARGET_LINK_LIBRARIES(ma_sp_test maria myisam mysys dbug strings zlib wsock32) + +IF(EMBED_MANIFESTS) + MYSQL_EMBED_MANIFEST("maria_ftdump" "asInvoker") + MYSQL_EMBED_MANIFEST("maria_chk" "asInvoker") + MYSQL_EMBED_MANIFEST("maria_read_log" "asInvoker") + MYSQL_EMBED_MANIFEST("maria_pack" "asInvoker") +ENDIF(EMBED_MANIFESTS) + +ENDIF(NOT SOURCE_SUBLIBS) diff --git a/storage/maria/Makefile.am b/storage/maria/Makefile.am new file mode 100644 index 00000000000..0f1e482c3a1 --- /dev/null +++ b/storage/maria/Makefile.am @@ -0,0 +1,197 @@ +# Copyright (C) 2000-2008 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 + +MYSQLDATAdir = $(localstatedir) +MYSQLSHAREdir = $(pkgdatadir) +MYSQLBASEdir= $(prefix) +MYSQLLIBdir= $(pkglibdir) +INCLUDES = -I$(top_srcdir)/include -I$(top_builddir)/include \ + -I$(top_srcdir)/regex \ + -I$(top_srcdir)/sql \ + -I$(srcdir) +WRAPLIBS= + +LDADD = + +DEFS = @DEFS@ + +# "." is needed first because tests in unittest need libmaria +SUBDIRS = . unittest + +EXTRA_DIST = ma_test_all.sh ma_test_all.res ma_test_big.sh \ + ma_ft_stem.c CMakeLists.txt plug.in ma_test_recovery +pkgdata_DATA = ma_test_all ma_test_all.res ma_test_recovery +pkglib_LIBRARIES = libmaria.a +bin_PROGRAMS = maria_chk maria_pack maria_ftdump maria_read_log \ + maria_dump_log +maria_chk_DEPENDENCIES= $(LIBRARIES) +# Only reason to link with libmyisam.a here is that it's where some fulltext +# pieces are (but soon we'll remove fulltext dependencies from Maria). +# For now, it imposes that storage/myisam be built before storage/maria. +maria_chk_LDADD= @CLIENT_EXTRA_LDFLAGS@ libmaria.a \ + $(top_builddir)/storage/myisam/libmyisam.a \ + $(top_builddir)/mysys/libmysys.a \ + $(top_builddir)/dbug/libdbug.a \ + $(top_builddir)/strings/libmystrings.a @ZLIB_LIBS@ +maria_pack_DEPENDENCIES=$(LIBRARIES) +maria_pack_LDADD= @CLIENT_EXTRA_LDFLAGS@ libmaria.a \ + $(top_builddir)/storage/myisam/libmyisam.a \ + $(top_builddir)/mysys/libmysys.a \ + $(top_builddir)/dbug/libdbug.a \ + $(top_builddir)/strings/libmystrings.a @ZLIB_LIBS@ +maria_read_log_DEPENDENCIES=$(LIBRARIES) +maria_read_log_LDADD= @CLIENT_EXTRA_LDFLAGS@ libmaria.a \ + $(top_builddir)/storage/myisam/libmyisam.a \ + $(top_builddir)/mysys/libmysys.a \ + $(top_builddir)/dbug/libdbug.a \ + $(top_builddir)/strings/libmystrings.a @ZLIB_LIBS@ +maria_dump_log_DEPENDENCIES=$(LIBRARIES) ma_loghandler.c +maria_dump_log_LDADD= @CLIENT_EXTRA_LDFLAGS@ libmaria.a \ + $(top_builddir)/storage/myisam/libmyisam.a \ + $(top_builddir)/mysys/libmysys.a \ + $(top_builddir)/dbug/libdbug.a \ + $(top_builddir)/strings/libmystrings.a @ZLIB_LIBS@ +maria_dump_log_SOURCES= ma_loghandler.c unittest/ma_loghandler_examples.c +maria_dump_log_CPPFLAGS= -DMARIA_DUMP_LOG +noinst_PROGRAMS = ma_test1 ma_test2 ma_test3 ma_rt_test ma_sp_test +noinst_HEADERS = maria_def.h ma_rt_index.h ma_rt_key.h ma_rt_mbr.h \ + ma_sp_defs.h ma_fulltext.h ma_ftdefs.h ma_ft_test1.h \ + ma_ft_eval.h trnman.h lockman.h tablockman.h \ + ma_control_file.h ha_maria.h ma_blockrec.h \ + ma_loghandler.h ma_loghandler_lsn.h ma_pagecache.h \ + ma_checkpoint.h ma_recovery.h ma_commit.h ma_state.h \ + trnman_public.h ma_check_standalone.h \ + ma_key_recover.h ma_recovery_util.h +ma_test1_DEPENDENCIES= $(LIBRARIES) +ma_test1_LDADD= @CLIENT_EXTRA_LDFLAGS@ libmaria.a \ + $(top_builddir)/storage/myisam/libmyisam.a \ + $(top_builddir)/mysys/libmysys.a \ + $(top_builddir)/dbug/libdbug.a \ + $(top_builddir)/strings/libmystrings.a @ZLIB_LIBS@ +ma_test2_DEPENDENCIES= $(LIBRARIES) +ma_test2_LDADD= @CLIENT_EXTRA_LDFLAGS@ libmaria.a \ + $(top_builddir)/storage/myisam/libmyisam.a \ + $(top_builddir)/mysys/libmysys.a \ + $(top_builddir)/dbug/libdbug.a \ + $(top_builddir)/strings/libmystrings.a @ZLIB_LIBS@ +ma_test3_DEPENDENCIES= $(LIBRARIES) +ma_test3_LDADD= @CLIENT_EXTRA_LDFLAGS@ libmaria.a \ + $(top_builddir)/storage/myisam/libmyisam.a \ + $(top_builddir)/mysys/libmysys.a \ + $(top_builddir)/dbug/libdbug.a \ + $(top_builddir)/strings/libmystrings.a @ZLIB_LIBS@ +#ma_ft_test1_DEPENDENCIES= $(LIBRARIES) +#ma_ft_eval_DEPENDENCIES= $(LIBRARIES) +maria_ftdump_DEPENDENCIES= $(LIBRARIES) +maria_ftdump_LDADD= @CLIENT_EXTRA_LDFLAGS@ libmaria.a \ + $(top_builddir)/storage/myisam/libmyisam.a \ + $(top_builddir)/mysys/libmysys.a \ + $(top_builddir)/dbug/libdbug.a \ + $(top_builddir)/strings/libmystrings.a @ZLIB_LIBS@ +ma_rt_test_DEPENDENCIES= $(LIBRARIES) +ma_rt_test_LDADD= @CLIENT_EXTRA_LDFLAGS@ libmaria.a \ + $(top_builddir)/storage/myisam/libmyisam.a \ + $(top_builddir)/mysys/libmysys.a \ + $(top_builddir)/dbug/libdbug.a \ + $(top_builddir)/strings/libmystrings.a @ZLIB_LIBS@ +ma_sp_test_DEPENDENCIES= $(LIBRARIES) +ma_sp_test_LDADD= @CLIENT_EXTRA_LDFLAGS@ libmaria.a \ + $(top_builddir)/storage/myisam/libmyisam.a \ + $(top_builddir)/mysys/libmysys.a \ + $(top_builddir)/dbug/libdbug.a \ + $(top_builddir)/strings/libmystrings.a @ZLIB_LIBS@ +libmaria_a_SOURCES = ma_init.c ma_open.c ma_extra.c ma_info.c ma_rkey.c \ + ma_rnext.c ma_rnext_same.c \ + ma_search.c ma_page.c ma_key_recover.c ma_key.c \ + ma_locking.c ma_state.c \ + ma_rrnd.c ma_scan.c ma_cache.c \ + ma_statrec.c ma_packrec.c ma_dynrec.c \ + ma_blockrec.c ma_bitmap.c \ + ma_update.c ma_write.c ma_unique.c \ + ma_delete.c \ + ma_rprev.c ma_rfirst.c ma_rlast.c ma_rsame.c \ + ma_rsamepos.c ma_panic.c ma_close.c ma_create.c\ + ma_range.c ma_dbug.c ma_checksum.c \ + ma_changed.c ma_static.c ma_delete_all.c \ + ma_delete_table.c ma_rename.c ma_check.c \ + ma_keycache.c ma_preload.c ma_ft_parser.c \ + ma_ft_update.c ma_ft_boolean_search.c \ + ma_ft_nlq_search.c ft_maria.c ma_sort.c \ + trnman.c lockman.c tablockman.c \ + ma_rt_index.c ma_rt_key.c ma_rt_mbr.c ma_rt_split.c \ + ma_sp_key.c ma_control_file.c ma_loghandler.c \ + ma_pagecache.c ma_pagecaches.c \ + ma_checkpoint.c ma_recovery.c ma_commit.c \ + ma_pagecrc.c ma_recovery_util.c \ + ha_maria.cc +CLEANFILES = test?.MA? FT?.MA? isam.log ma_test_all ma_rt_test.MA? sp_test.MA? maria_log_control maria_log.0000* + +SUFFIXES = .sh + +.sh: + @RM@ -f $@ $@-t + @SED@ \ + -e 's!@''bindir''@!$(bindir)!g' \ + -e 's!@''scriptdir''@!$(bindir)!g' \ + -e 's!@''prefix''@!$(prefix)!g' \ + -e 's!@''datadir''@!$(datadir)!g' \ + -e 's!@''localstatedir''@!$(localstatedir)!g' \ + -e 's!@''libexecdir''@!$(libexecdir)!g' \ + -e 's!@''CC''@!@CC@!'\ + -e 's!@''CXX''@!@CXX@!'\ + -e 's!@''GXX''@!@GXX@!'\ + -e 's!@''PERL''@!@PERL@!' \ + -e 's!@''CFLAGS''@!@SAVE_CFLAGS@!'\ + -e 's!@''CXXFLAGS''@!@SAVE_CXXFLAGS@!'\ + -e 's!@''LDFLAGS''@!@SAVE_LDFLAGS@!'\ + -e 's!@''VERSION''@!@VERSION@!' \ + -e 's!@''MYSQL_SERVER_SUFFIX''@!@MYSQL_SERVER_SUFFIX@!' \ + -e 's!@''COMPILATION_COMMENT''@!@COMPILATION_COMMENT@!' \ + -e 's!@''MACHINE_TYPE''@!@MACHINE_TYPE@!' \ + -e 's!@''HOSTNAME''@!@HOSTNAME@!' \ + -e 's!@''SYSTEM_TYPE''@!@SYSTEM_TYPE@!' \ + -e 's!@''CHECK_PID''@!@CHECK_PID@!' \ + -e 's!@''FIND_PROC''@!@FIND_PROC@!' \ + -e 's!@''MYSQLD_DEFAULT_SWITCHES''@!@MYSQLD_DEFAULT_SWITCHES@!' \ + -e 's!@''MYSQL_UNIX_ADDR''@!@MYSQL_UNIX_ADDR@!' \ + -e 's!@''TARGET_LINUX''@!@TARGET_LINUX@!' \ + -e "s!@""CONF_COMMAND""@!@CONF_COMMAND@!" \ + -e 's!@''MYSQLD_USER''@!@MYSQLD_USER@!' \ + -e 's!@''sysconfdir''@!@sysconfdir@!' \ + -e 's!@''SHORT_MYSQL_INTRO''@!@SHORT_MYSQL_INTRO@!' \ + -e 's!@''SHARED_LIB_VERSION''@!@SHARED_LIB_VERSION@!' \ + -e 's!@''MYSQL_BASE_VERSION''@!@MYSQL_BASE_VERSION@!' \ + -e 's!@''MYSQL_NO_DASH_VERSION''@!@MYSQL_NO_DASH_VERSION@!' \ + -e 's!@''MYSQL_TCP_PORT''@!@MYSQL_TCP_PORT@!' \ + -e 's!@''PERL_DBI_VERSION''@!@PERL_DBI_VERSION@!' \ + -e 's!@''PERL_DBD_VERSION''@!@PERL_DBD_VERSION@!' \ + -e 's!@''PERL_DATA_DUMPER''@!@PERL_DATA_DUMPER@!' \ + $< > $@-t + @CHMOD@ +x $@-t + @MV@ $@-t $@ + +tags: + etags *.h *.c *.cc + +unittests = unittest + +test: + perl $(top_srcdir)/unittest/unit.pl run $(unittests) + +test-verbose: + HARNESS_VERBOSE=1 perl $(top_srcdir)/unittest/unit.pl run $(unittests) + +# Don't update the files from bitkeeper +%::SCCS/s.% diff --git a/storage/maria/file_formats.txt b/storage/maria/file_formats.txt new file mode 100644 index 00000000000..927e8ad985e --- /dev/null +++ b/storage/maria/file_formats.txt @@ -0,0 +1,71 @@ +# +# This should contain a description of the file format for most Maria files +# + +# Description of the header in the index file + +Header, 24 bytes + +Pos Length + +0 4 file_version +4 2 options +6 2 header_length +8 2 state_info_length +10 2 base_info_length +12 2 base_pos +14 2 key_parts +16 2 unique_key_parts +18 1 keys +19 1 uniques +20 1 language +21 1 fulltext_keys +22 1 data_file_type +23 1 org_data_file_type + + +Status part + +24 2 open_count +26 2 state_changed +28 7 create_rename_lsn + 7 is_of_horizon + 7 skip_redo_lsn + 8 state.records + 8 state->state.del + 8 state->split + 8 state->dellink + 8 state->first_bitmap_with_space + 8 state->state.key_file_length + 8 state->state.data_file_length + 8 state->state.empty + 8 state->state.key_empty + 8 state->auto_increment + 8 state->state.checksum + 4 state->process + 4 state->unique + 4 state->status + 4 state->update_count + + 1 state->sortkey + 1 reserved + +for each key + 8 state->key_root[i] + + 8 state->key_del + 4 state->sec_index_changed + 4 state->sec_index_used + 4 state->version + 8 state->key_map + 8 state->create_time + 8 state->recover_time + 8 state->check_time + 8 state->records_at_analyze + +for each key + 4 reserved + +for each key part + 8 state->rec_per_key_part[i] + 4 state->nulls_per_key_part[i] diff --git a/storage/maria/ft_maria.c b/storage/maria/ft_maria.c new file mode 100644 index 00000000000..1b082f904d0 --- /dev/null +++ b/storage/maria/ft_maria.c @@ -0,0 +1,48 @@ +/* Copyright (C) 2006 MySQL AB & MySQL Finland AB & TCX DataKonsult 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 */ + +/* Written by Sergei A. Golubchik, who has a shared copyright to this code */ + +/* + This function is for interface functions between fulltext and maria +*/ + +#include "ma_ftdefs.h" + +FT_INFO *maria_ft_init_search(uint flags, void *info, uint keynr, + uchar *query, uint query_len, CHARSET_INFO *cs, + uchar *record) +{ + FT_INFO *res; + if (flags & FT_BOOL) + res= maria_ft_init_boolean_search((MARIA_HA *) info, keynr, query, + query_len, cs); + else + res= maria_ft_init_nlq_search((MARIA_HA *) info, keynr, query, query_len, + flags, record); + return res; +} + +const struct _ft_vft _ma_ft_vft_nlq = { + maria_ft_nlq_read_next, maria_ft_nlq_find_relevance, + maria_ft_nlq_close_search, maria_ft_nlq_get_relevance, + maria_ft_nlq_reinit_search +}; +const struct _ft_vft _ma_ft_vft_boolean = { + maria_ft_boolean_read_next, maria_ft_boolean_find_relevance, + maria_ft_boolean_close_search, maria_ft_boolean_get_relevance, + maria_ft_boolean_reinit_search +}; + diff --git a/storage/maria/ha_maria.cc b/storage/maria/ha_maria.cc new file mode 100644 index 00000000000..baa40cc0037 --- /dev/null +++ b/storage/maria/ha_maria.cc @@ -0,0 +1,3348 @@ +/* Copyright (C) 2004-2008 MySQL AB & MySQL Finland AB & TCX DataKonsult AB + Copyright (C) 2008-2009 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + + +#ifdef USE_PRAGMA_IMPLEMENTATION +#pragma implementation // gcc: Class implementation +#endif + +#define MYSQL_SERVER 1 +#include "mysql_priv.h" +#include <mysql/plugin.h> +#include <m_ctype.h> +#include <my_dir.h> +#include <myisampack.h> +#include <my_bit.h> +#include "ha_maria.h" +#include "trnman_public.h" + +C_MODE_START +#include "maria_def.h" +#include "ma_rt_index.h" +#include "ma_blockrec.h" +#include "ma_checkpoint.h" +#include "ma_recovery.h" +C_MODE_END + +/* + Note that in future versions, only *transactional* Maria tables can + rollback, so this flag should be up or down conditionally. +*/ +#ifdef MARIA_CANNOT_ROLLBACK +#define CANNOT_ROLLBACK_FLAG HA_NO_TRANSACTIONS +#define trans_register_ha(A, B, C) do { /* nothing */ } while(0) +#else +#define CANNOT_ROLLBACK_FLAG 0 +#endif +#define THD_TRN (*(TRN **)thd_ha_data(thd, maria_hton)) + +ulong pagecache_division_limit, pagecache_age_threshold; +ulonglong pagecache_buffer_size; + +/** + As the auto-repair is initiated when opened from the SQL layer + (open_unireg_entry(), check_and_repair()), it does not happen when Maria's + Recovery internally opens the table to apply log records to it, which is + good. It would happen only after Recovery, if the table is still + corrupted. +*/ +ulong maria_recover_options= HA_RECOVER_NONE; +handlerton *maria_hton; + +/* bits in maria_recover_options */ +const char *maria_recover_names[]= +{ + /* + Compared to MyISAM, "default" was renamed to "normal" as it collided with + SET var=default which sets to the var's default i.e. what happens when the + var is not set i.e. HA_RECOVER_NONE. + Another change is that OFF is used to disable, not ""; this is to have OFF + display in SHOW VARIABLES which is better than "". + */ + "OFF", "NORMAL", "BACKUP", "FORCE", "QUICK", NullS +}; +TYPELIB maria_recover_typelib= +{ + array_elements(maria_recover_names) - 1, "", + maria_recover_names, NULL +}; + +const char *maria_stats_method_names[]= +{ + "nulls_unequal", "nulls_equal", + "nulls_ignored", NullS +}; +TYPELIB maria_stats_method_typelib= +{ + array_elements(maria_stats_method_names) - 1, "", + maria_stats_method_names, NULL +}; + +/* transactions log purge mode */ +const char *maria_translog_purge_type_names[]= +{ + "immediate", "external", "at_flush", NullS +}; +TYPELIB maria_translog_purge_type_typelib= +{ + array_elements(maria_translog_purge_type_names) - 1, "", + maria_translog_purge_type_names, NULL +}; +const char *maria_sync_log_dir_names[]= +{ + "NEVER", "NEWFILE", "ALWAYS", NullS +}; + +TYPELIB maria_sync_log_dir_typelib= +{ + array_elements(maria_sync_log_dir_names) - 1, "", + maria_sync_log_dir_names, NULL +}; + +/** Interval between background checkpoints in seconds */ +static ulong checkpoint_interval; +static void update_checkpoint_interval(MYSQL_THD thd, + struct st_mysql_sys_var *var, + void *var_ptr, const void *save); +/** After that many consecutive recovery failures, remove logs */ +static ulong force_start_after_recovery_failures; +static void update_log_file_size(MYSQL_THD thd, + struct st_mysql_sys_var *var, + void *var_ptr, const void *save); + +static MYSQL_SYSVAR_ULONG(block_size, maria_block_size, + PLUGIN_VAR_RQCMDARG | PLUGIN_VAR_READONLY, + "Block size to be used for MARIA index pages.", 0, 0, + MARIA_KEY_BLOCK_LENGTH, MARIA_MIN_KEY_BLOCK_LENGTH, + MARIA_MAX_KEY_BLOCK_LENGTH, MARIA_MIN_KEY_BLOCK_LENGTH); + +static MYSQL_SYSVAR_ULONG(checkpoint_interval, checkpoint_interval, + PLUGIN_VAR_RQCMDARG, + "Interval between automatic checkpoints, in seconds; 0 means" + " 'no automatic checkpoints' which makes sense only for testing.", + NULL, update_checkpoint_interval, 30, 0, UINT_MAX, 1); + +static MYSQL_SYSVAR_ULONG(force_start_after_recovery_failures, + force_start_after_recovery_failures, + /* + Read-only because setting it on the fly has no useful effect, + should be set on command-line. + */ + PLUGIN_VAR_RQCMDARG | PLUGIN_VAR_READONLY, + "Number of consecutive log recovery failures after which logs will be" + " automatically deleted to cure the problem; 0 (the default) disables" + " the feature.", NULL, NULL, 0, 0, UINT_MAX8, 1); + +static MYSQL_SYSVAR_BOOL(page_checksum, maria_page_checksums, 0, + "Maintain page checksums (can be overridden per table " + "with PAGE_CHECKSUM clause in CREATE TABLE)", 0, 0, 1); + +/* It is only command line argument */ +static MYSQL_SYSVAR_STR(log_dir_path, maria_data_root, + PLUGIN_VAR_NOSYSVAR | PLUGIN_VAR_RQCMDARG | PLUGIN_VAR_READONLY, + "Path to the directory where to store transactional log", + NULL, NULL, mysql_real_data_home); + + +static MYSQL_SYSVAR_ULONG(log_file_size, log_file_size, + PLUGIN_VAR_RQCMDARG, + "Limit for transaction log size", + NULL, update_log_file_size, TRANSLOG_FILE_SIZE, + TRANSLOG_MIN_FILE_SIZE, 0xffffffffL, TRANSLOG_PAGE_SIZE); + +static MYSQL_SYSVAR_ENUM(log_purge_type, log_purge_type, + PLUGIN_VAR_RQCMDARG, + "Specifies how maria transactional log will be purged. " + "Possible values of name are \"immediate\", \"external\" " + "and \"at_flush\"", + NULL, NULL, TRANSLOG_PURGE_IMMIDIATE, + &maria_translog_purge_type_typelib); + +static MYSQL_SYSVAR_ULONGLONG(max_sort_file_size, + maria_max_temp_length, PLUGIN_VAR_RQCMDARG, + "Don't use the fast sort index method to created index if the " + "temporary file would get bigger than this.", + 0, 0, MAX_FILE_SIZE, 0, MAX_FILE_SIZE, 1024*1024); + +static MYSQL_SYSVAR_ULONG(pagecache_age_threshold, + pagecache_age_threshold, PLUGIN_VAR_RQCMDARG, + "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 the page cache.", 0, 0, + 300, 100, ~0L, 100); + +static MYSQL_SYSVAR_ULONGLONG(pagecache_buffer_size, pagecache_buffer_size, + PLUGIN_VAR_RQCMDARG | PLUGIN_VAR_READONLY, + "The size of the buffer used for index blocks for Maria tables. " + "Increase this to get better index handling (for all reads and " + "multiple writes) to as much as you can afford.", 0, 0, + KEY_CACHE_SIZE, MALLOC_OVERHEAD, ~(ulong) 0, IO_SIZE); + +static MYSQL_SYSVAR_ULONG(pagecache_division_limit, pagecache_division_limit, + PLUGIN_VAR_RQCMDARG, + "The minimum percentage of warm blocks in key cache", 0, 0, + 100, 1, 100, 1); + +static MYSQL_SYSVAR_ENUM(recover, maria_recover_options, PLUGIN_VAR_OPCMDARG, + "Specifies how corrupted tables should be automatically repaired." + " Possible values are \"NORMAL\" (the default), \"BACKUP\", \"FORCE\"," + " \"QUICK\", or \"OFF\" which is like not using the option.", + NULL, NULL, HA_RECOVER_NONE, &maria_recover_typelib); + +static MYSQL_THDVAR_ULONG(repair_threads, PLUGIN_VAR_RQCMDARG, + "Number of threads to use when repairing maria tables. The value of 1 " + "disables parallel repair.", + 0, 0, 1, 1, ~0L, 1); + +static MYSQL_THDVAR_ULONG(sort_buffer_size, PLUGIN_VAR_RQCMDARG, + "The buffer that is allocated when sorting the index when doing a " + "REPAIR or when creating indexes with CREATE INDEX or ALTER TABLE.", + 0, 0, 8192*1024, 4, ~0L, 1); + +static MYSQL_THDVAR_ENUM(stats_method, PLUGIN_VAR_RQCMDARG, + "Specifies how maria index statistics collection code should treat " + "NULLs. Possible values are \"nulls_unequal\", \"nulls_equal\", " + "and \"nulls_ignored\".", 0, 0, 0, &maria_stats_method_typelib); + +static MYSQL_SYSVAR_ENUM(sync_log_dir, sync_log_dir, PLUGIN_VAR_RQCMDARG, + "Controls syncing directory after log file growth and new file " + "creation. Possible values are \"never\", \"newfile\" and " + "\"always\").", NULL, NULL, TRANSLOG_SYNC_DIR_NEWFILE, + &maria_sync_log_dir_typelib); + +#ifdef USE_MARIA_FOR_TMP_TABLES +static my_bool use_maria_for_temp_tables= 1; +#else +static my_bool use_maria_for_temp_tables= 0; +#endif + +static MYSQL_SYSVAR_BOOL(used_for_temp_tables, + use_maria_for_temp_tables, PLUGIN_VAR_READONLY | PLUGIN_VAR_NOCMDOPT, + "Whether temporary tables should be MyISAM or Maria", 0, 0, + 1); + +/***************************************************************************** +** MARIA tables +*****************************************************************************/ + +static handler *maria_create_handler(handlerton *hton, + TABLE_SHARE * table, + MEM_ROOT *mem_root) +{ + return new (mem_root) ha_maria(hton, table); +} + + +// collect errors printed by maria_check routines + +static void _ma_check_print_msg(HA_CHECK *param, const char *msg_type, + const char *fmt, va_list args) +{ + THD *thd= (THD *) param->thd; + Protocol *protocol= thd->protocol; + uint length, msg_length; + char msgbuf[HA_MAX_MSG_BUF]; + char name[NAME_LEN * 2 + 2]; + + msg_length= my_vsnprintf(msgbuf, sizeof(msgbuf), fmt, args); + msgbuf[sizeof(msgbuf) - 1]= 0; // healthy paranoia + + DBUG_PRINT(msg_type, ("message: %s", msgbuf)); + + if (!thd->vio_ok()) + { + sql_print_error(msgbuf); + return; + } + + if (param->testflag & + (T_CREATE_MISSING_KEYS | T_SAFE_REPAIR | T_AUTO_REPAIR)) + { + my_message(ER_NOT_KEYFILE, msgbuf, MYF(MY_WME)); + return; + } + length= (uint) (strxmov(name, param->db_name, ".", param->table_name, + 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: + ma_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). + */ + protocol->prepare_for_resend(); + protocol->store(name, length, system_charset_info); + protocol->store(param->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); + return; +} + + +/* + Convert TABLE object to Maria key and column definition + + SYNOPSIS + table2maria() + table_arg in TABLE object. + keydef_out out Maria key definition. + recinfo_out out Maria column definition. + records_out out Number of fields. + + DESCRIPTION + This function will allocate and initialize Maria key and column + definition for further use in ma_create or for a check for underlying + table conformance in merge engine. + + The caller needs to free *recinfo_out after use. Since *recinfo_out + and *keydef_out are allocated with a my_multi_malloc, *keydef_out + is freed automatically when *recinfo_out is freed. + + RETURN VALUE + 0 OK + # error code +*/ + +static int table2maria(TABLE *table_arg, data_file_type row_type, + MARIA_KEYDEF **keydef_out, + MARIA_COLUMNDEF **recinfo_out, uint *records_out, + MARIA_CREATE_INFO *create_info) +{ + uint i, j, recpos, minpos, fieldpos, temp_length, length; + enum ha_base_keytype type= HA_KEYTYPE_BINARY; + uchar *record; + KEY *pos; + MARIA_KEYDEF *keydef; + MARIA_COLUMNDEF *recinfo, *recinfo_pos; + HA_KEYSEG *keyseg; + TABLE_SHARE *share= table_arg->s; + uint options= share->db_options_in_use; + DBUG_ENTER("table2maria"); + + if (row_type == BLOCK_RECORD) + options|= HA_OPTION_PACK_RECORD; + + if (!(my_multi_malloc(MYF(MY_WME), + recinfo_out, (share->fields * 2 + 2) * sizeof(MARIA_COLUMNDEF), + keydef_out, share->keys * sizeof(MARIA_KEYDEF), + &keyseg, + (share->key_parts + share->keys) * sizeof(HA_KEYSEG), + NullS))) + DBUG_RETURN(HA_ERR_OUT_OF_MEM); /* purecov: inspected */ + keydef= *keydef_out; + recinfo= *recinfo_out; + pos= table_arg->key_info; + for (i= 0; i < share->keys; i++, pos++) + { + keydef[i].flag= (uint16) (pos->flags & (HA_NOSAME | HA_FULLTEXT | + HA_SPATIAL)); + keydef[i].key_alg= pos->algorithm == HA_KEY_ALG_UNDEF ? + (pos->flags & HA_SPATIAL ? HA_KEY_ALG_RTREE : HA_KEY_ALG_BTREE) : + pos->algorithm; + keydef[i].block_length= pos->block_size; + keydef[i].seg= keyseg; + keydef[i].keysegs= pos->key_parts; + for (j= 0; j < pos->key_parts; j++) + { + Field *field= pos->key_part[j].field; + type= field->key_type(); + keydef[i].seg[j].flag= pos->key_part[j].key_part_flag; + + if (options & HA_OPTION_PACK_KEYS || + (pos->flags & (HA_PACK_KEY | HA_BINARY_PACK_KEY | + HA_SPACE_PACK_USED))) + { + if (pos->key_part[j].length > 8 && + (type == HA_KEYTYPE_TEXT || + type == HA_KEYTYPE_NUM || + (type == HA_KEYTYPE_BINARY && !field->zero_pack()))) + { + /* No blobs here */ + if (j == 0) + keydef[i].flag|= HA_PACK_KEY; + if (!(field->flags & ZEROFILL_FLAG) && + (field->type() == MYSQL_TYPE_STRING || + field->type() == MYSQL_TYPE_VAR_STRING || + ((int) (pos->key_part[j].length - field->decimals())) >= 4)) + keydef[i].seg[j].flag|= HA_SPACE_PACK; + } + else if (j == 0 && (!(pos->flags & HA_NOSAME) || pos->key_length > 16)) + keydef[i].flag|= HA_BINARY_PACK_KEY; + } + keydef[i].seg[j].type= (int) type; + keydef[i].seg[j].start= pos->key_part[j].offset; + keydef[i].seg[j].length= pos->key_part[j].length; + keydef[i].seg[j].bit_start= keydef[i].seg[j].bit_end= + keydef[i].seg[j].bit_length= 0; + keydef[i].seg[j].bit_pos= 0; + keydef[i].seg[j].language= field->charset()->number; + + if (field->null_ptr) + { + keydef[i].seg[j].null_bit= field->null_bit; + keydef[i].seg[j].null_pos= (uint) (field->null_ptr- + (uchar*) table_arg->record[0]); + } + else + { + keydef[i].seg[j].null_bit= 0; + keydef[i].seg[j].null_pos= 0; + } + if (field->type() == MYSQL_TYPE_BLOB || + field->type() == MYSQL_TYPE_GEOMETRY) + { + keydef[i].seg[j].flag|= HA_BLOB_PART; + /* save number of bytes used to pack length */ + keydef[i].seg[j].bit_start= (uint) (field->pack_length() - + share->blob_ptr_size); + } + else if (field->type() == MYSQL_TYPE_BIT) + { + keydef[i].seg[j].bit_length= ((Field_bit *) field)->bit_len; + keydef[i].seg[j].bit_start= ((Field_bit *) field)->bit_ofs; + keydef[i].seg[j].bit_pos= (uint) (((Field_bit *) field)->bit_ptr - + (uchar*) table_arg->record[0]); + } + } + keyseg+= pos->key_parts; + } + if (table_arg->found_next_number_field) + keydef[share->next_number_index].flag|= HA_AUTO_KEY; + record= table_arg->record[0]; + recpos= 0; + recinfo_pos= recinfo; + create_info->null_bytes= table_arg->s->null_bytes; + + while (recpos < (uint) share->reclength) + { + Field **field, *found= 0; + minpos= share->reclength; + length= 0; + + for (field= table_arg->field; *field; field++) + { + if ((fieldpos= (*field)->offset(record)) >= recpos && + fieldpos <= minpos) + { + /* skip null fields */ + if (!(temp_length= (*field)->pack_length_in_rec())) + continue; /* Skip null-fields */ + if (! found || fieldpos < minpos || + (fieldpos == minpos && temp_length < length)) + { + minpos= fieldpos; + found= *field; + length= temp_length; + } + } + } + DBUG_PRINT("loop", ("found: 0x%lx recpos: %d minpos: %d length: %d", + (long) found, recpos, minpos, length)); + if (!found) + break; + + if (found->flags & BLOB_FLAG) + recinfo_pos->type= FIELD_BLOB; + else if (found->type() == MYSQL_TYPE_VARCHAR) + recinfo_pos->type= FIELD_VARCHAR; + else if (!(options & HA_OPTION_PACK_RECORD) || + (found->zero_pack() && (found->flags & PRI_KEY_FLAG))) + recinfo_pos->type= FIELD_NORMAL; + else if (found->zero_pack()) + recinfo_pos->type= FIELD_SKIP_ZERO; + else + recinfo_pos->type= ((length <= 3 || + (found->flags & ZEROFILL_FLAG)) ? + FIELD_NORMAL : + found->type() == MYSQL_TYPE_STRING || + found->type() == MYSQL_TYPE_VAR_STRING ? + FIELD_SKIP_ENDSPACE : + FIELD_SKIP_PRESPACE); + if (found->null_ptr) + { + recinfo_pos->null_bit= found->null_bit; + recinfo_pos->null_pos= (uint) (found->null_ptr - + (uchar*) table_arg->record[0]); + } + else + { + recinfo_pos->null_bit= 0; + recinfo_pos->null_pos= 0; + } + (recinfo_pos++)->length= (uint16) length; + recpos= minpos + length; + DBUG_PRINT("loop", ("length: %d type: %d", + recinfo_pos[-1].length,recinfo_pos[-1].type)); + } + *records_out= (uint) (recinfo_pos - recinfo); + DBUG_RETURN(0); +} + + +/* + Check for underlying table conformance + + SYNOPSIS + maria_check_definition() + t1_keyinfo in First table key definition + t1_recinfo in First table record definition + t1_keys in Number of keys in first table + t1_recs in Number of records in first table + t2_keyinfo in Second table key definition + t2_recinfo in Second table record definition + t2_keys in Number of keys in second table + t2_recs in Number of records in second table + strict in Strict check switch + + DESCRIPTION + This function compares two Maria definitions. By intention it was done + to compare merge table definition against underlying table definition. + It may also be used to compare dot-frm and MAI definitions of Maria + table as well to compare different Maria table definitions. + + For merge table it is not required that number of keys in merge table + must exactly match number of keys in underlying table. When calling this + function for underlying table conformance check, 'strict' flag must be + set to false, and converted merge definition must be passed as t1_*. + + Otherwise 'strict' flag must be set to 1 and it is not required to pass + converted dot-frm definition as t1_*. + + RETURN VALUE + 0 - Equal definitions. + 1 - Different definitions. + + TODO + - compare FULLTEXT keys; + - compare SPATIAL keys; + - compare FIELD_SKIP_ZERO which is converted to FIELD_NORMAL correctly + (should be correctly detected in table2maria). +*/ + +int maria_check_definition(MARIA_KEYDEF *t1_keyinfo, + MARIA_COLUMNDEF *t1_recinfo, + uint t1_keys, uint t1_recs, + MARIA_KEYDEF *t2_keyinfo, + MARIA_COLUMNDEF *t2_recinfo, + uint t2_keys, uint t2_recs, bool strict) +{ + uint i, j; + DBUG_ENTER("maria_check_definition"); + if ((strict ? t1_keys != t2_keys : t1_keys > t2_keys)) + { + DBUG_PRINT("error", ("Number of keys differs: t1_keys=%u, t2_keys=%u", + t1_keys, t2_keys)); + DBUG_RETURN(1); + } + if (t1_recs != t2_recs) + { + DBUG_PRINT("error", ("Number of recs differs: t1_recs=%u, t2_recs=%u", + t1_recs, t2_recs)); + DBUG_RETURN(1); + } + for (i= 0; i < t1_keys; i++) + { + HA_KEYSEG *t1_keysegs= t1_keyinfo[i].seg; + HA_KEYSEG *t2_keysegs= t2_keyinfo[i].seg; + if (t1_keyinfo[i].flag & HA_FULLTEXT && t2_keyinfo[i].flag & HA_FULLTEXT) + continue; + else if (t1_keyinfo[i].flag & HA_FULLTEXT || + t2_keyinfo[i].flag & HA_FULLTEXT) + { + DBUG_PRINT("error", ("Key %d has different definition", i)); + DBUG_PRINT("error", ("t1_fulltext= %d, t2_fulltext=%d", + test(t1_keyinfo[i].flag & HA_FULLTEXT), + test(t2_keyinfo[i].flag & HA_FULLTEXT))); + DBUG_RETURN(1); + } + if (t1_keyinfo[i].flag & HA_SPATIAL && t2_keyinfo[i].flag & HA_SPATIAL) + continue; + else if (t1_keyinfo[i].flag & HA_SPATIAL || + t2_keyinfo[i].flag & HA_SPATIAL) + { + DBUG_PRINT("error", ("Key %d has different definition", i)); + DBUG_PRINT("error", ("t1_spatial= %d, t2_spatial=%d", + test(t1_keyinfo[i].flag & HA_SPATIAL), + test(t2_keyinfo[i].flag & HA_SPATIAL))); + DBUG_RETURN(1); + } + if (t1_keyinfo[i].keysegs != t2_keyinfo[i].keysegs || + t1_keyinfo[i].key_alg != t2_keyinfo[i].key_alg) + { + DBUG_PRINT("error", ("Key %d has different definition", i)); + DBUG_PRINT("error", ("t1_keysegs=%d, t1_key_alg=%d", + t1_keyinfo[i].keysegs, t1_keyinfo[i].key_alg)); + DBUG_PRINT("error", ("t2_keysegs=%d, t2_key_alg=%d", + t2_keyinfo[i].keysegs, t2_keyinfo[i].key_alg)); + DBUG_RETURN(1); + } + for (j= t1_keyinfo[i].keysegs; j--;) + { + uint8 t1_keysegs_j__type= t1_keysegs[j].type; + /* + Table migration from 4.1 to 5.1. In 5.1 a *TEXT key part is + always HA_KEYTYPE_VARTEXT2. In 4.1 we had only the equivalent of + HA_KEYTYPE_VARTEXT1. Since we treat both the same on MyISAM + level, we can ignore a mismatch between these types. + */ + if ((t1_keysegs[j].flag & HA_BLOB_PART) && + (t2_keysegs[j].flag & HA_BLOB_PART)) + { + if ((t1_keysegs_j__type == HA_KEYTYPE_VARTEXT2) && + (t2_keysegs[j].type == HA_KEYTYPE_VARTEXT1)) + t1_keysegs_j__type= HA_KEYTYPE_VARTEXT1; /* purecov: tested */ + else if ((t1_keysegs_j__type == HA_KEYTYPE_VARBINARY2) && + (t2_keysegs[j].type == HA_KEYTYPE_VARBINARY1)) + t1_keysegs_j__type= HA_KEYTYPE_VARBINARY1; /* purecov: inspected */ + } + + if (t1_keysegs_j__type != t2_keysegs[j].type || + t1_keysegs[j].language != t2_keysegs[j].language || + t1_keysegs[j].null_bit != t2_keysegs[j].null_bit || + t1_keysegs[j].length != t2_keysegs[j].length) + { + DBUG_PRINT("error", ("Key segment %d (key %d) has different " + "definition", j, i)); + DBUG_PRINT("error", ("t1_type=%d, t1_language=%d, t1_null_bit=%d, " + "t1_length=%d", + t1_keysegs[j].type, t1_keysegs[j].language, + t1_keysegs[j].null_bit, t1_keysegs[j].length)); + DBUG_PRINT("error", ("t2_type=%d, t2_language=%d, t2_null_bit=%d, " + "t2_length=%d", + t2_keysegs[j].type, t2_keysegs[j].language, + t2_keysegs[j].null_bit, t2_keysegs[j].length)); + + DBUG_RETURN(1); + } + } + } + + for (i= 0; i < t1_recs; i++) + { + MARIA_COLUMNDEF *t1_rec= &t1_recinfo[i]; + MARIA_COLUMNDEF *t2_rec= &t2_recinfo[i]; + /* + FIELD_SKIP_ZERO can be changed to FIELD_NORMAL in maria_create, + see NOTE1 in ma_create.c + */ + if ((t1_rec->type != t2_rec->type && + !(t1_rec->type == (int) FIELD_SKIP_ZERO && + t1_rec->length == 1 && + t2_rec->type == (int) FIELD_NORMAL)) || + t1_rec->length != t2_rec->length || + t1_rec->null_bit != t2_rec->null_bit) + { + DBUG_PRINT("error", ("Field %d has different definition", i)); + DBUG_PRINT("error", ("t1_type=%d, t1_length=%d, t1_null_bit=%d", + t1_rec->type, t1_rec->length, t1_rec->null_bit)); + DBUG_PRINT("error", ("t2_type=%d, t2_length=%d, t2_null_bit=%d", + t2_rec->type, t2_rec->length, t2_rec->null_bit)); + DBUG_RETURN(1); + } + } + DBUG_RETURN(0); +} + + +extern "C" { + +volatile int *_ma_killed_ptr(HA_CHECK *param) +{ + /* In theory Unsafe conversion, but should be ok for now */ + return (int*) &(((THD *) (param->thd))->killed); +} + + +void _ma_check_print_error(HA_CHECK *param, const char *fmt, ...) +{ + va_list args; + DBUG_ENTER("_ma_check_print_error"); + param->error_printed |= 1; + param->out_flag |= O_DATA_LOST; + va_start(args, fmt); + _ma_check_print_msg(param, "error", fmt, args); + va_end(args); + DBUG_VOID_RETURN; +} + + +void _ma_check_print_info(HA_CHECK *param, const char *fmt, ...) +{ + va_list args; + DBUG_ENTER("_ma_check_print_info"); + va_start(args, fmt); + _ma_check_print_msg(param, "info", fmt, args); + va_end(args); + DBUG_VOID_RETURN; +} + + +void _ma_check_print_warning(HA_CHECK *param, const char *fmt, ...) +{ + va_list args; + DBUG_ENTER("_ma_check_print_warning"); + param->warning_printed= 1; + param->out_flag |= O_DATA_LOST; + va_start(args, fmt); + _ma_check_print_msg(param, "warning", fmt, args); + va_end(args); + DBUG_VOID_RETURN; +} + +} + +/** + Transactional table doing bulk insert with one single UNDO + (UNDO_BULK_INSERT) and with repair. +*/ +#define BULK_INSERT_SINGLE_UNDO_AND_REPAIR 1 +/** + Transactional table doing bulk insert with one single UNDO + (UNDO_BULK_INSERT) and without repair. +*/ +#define BULK_INSERT_SINGLE_UNDO_AND_NO_REPAIR 2 +/** + None of BULK_INSERT_SINGLE_UNDO_AND_REPAIR and + BULK_INSERT_SINGLE_UNDO_AND_NO_REPAIR. +*/ +#define BULK_INSERT_NONE 0 + +ha_maria::ha_maria(handlerton *hton, TABLE_SHARE *table_arg): +handler(hton, table_arg), file(0), +int_table_flags(HA_NULL_IN_KEY | HA_CAN_FULLTEXT | HA_CAN_SQL_HANDLER | + HA_BINLOG_ROW_CAPABLE | HA_BINLOG_STMT_CAPABLE | + HA_DUPLICATE_POS | HA_CAN_INDEX_BLOBS | HA_AUTO_PART_KEY | + HA_FILE_BASED | HA_CAN_GEOMETRY | CANNOT_ROLLBACK_FLAG | + HA_CAN_BIT_FIELD | HA_CAN_RTREEKEYS | + HA_HAS_RECORDS | HA_STATS_RECORDS_IS_EXACT), +can_enable_indexes(1), bulk_insert_single_undo(BULK_INSERT_NONE) +{} + + +handler *ha_maria::clone(MEM_ROOT *mem_root) +{ + ha_maria *new_handler= static_cast <ha_maria *>(handler::clone(mem_root)); + if (new_handler) + new_handler->file->state= file->state; + return new_handler; +} + + +static const char *ha_maria_exts[]= +{ + MARIA_NAME_IEXT, + MARIA_NAME_DEXT, + NullS +}; + + +const char **ha_maria::bas_ext() const +{ + return ha_maria_exts; +} + + +const char *ha_maria::index_type(uint key_number) +{ + return ((table->key_info[key_number].flags & HA_FULLTEXT) ? + "FULLTEXT" : + (table->key_info[key_number].flags & HA_SPATIAL) ? + "SPATIAL" : + (table->key_info[key_number].algorithm == HA_KEY_ALG_RTREE) ? + "RTREE" : "BTREE"); +} + + +double ha_maria::scan_time() +{ + if (file->s->data_file_type == BLOCK_RECORD) + return ulonglong2double(stats.data_file_length - file->s->block_size) / max(file->s->block_size / 2, IO_SIZE) + 2; + return handler::scan_time(); +} + +/* + We need to be able to store at least two keys on an index page as the + splitting algorithms depends on this. (With only one key on a page + we also can't use any compression, which may make the index file much + larger) + We use HA_MAX_KEY_BUFF as this is a stack restriction imposed by the + handler interface. + + We also need to reserve place for a record pointer (8) and 3 bytes + per key segment to store the length of the segment + possible null bytes. + These extra bytes are required here so that maria_create() will surely + accept any keys created which the returned key data storage length. +*/ + +uint ha_maria::max_supported_key_length() const +{ + uint tmp= (maria_max_key_length() - 8 - HA_MAX_KEY_SEG*3); + return min(HA_MAX_KEY_BUFF, tmp); +} + + +#ifdef HAVE_REPLICATION +int ha_maria::net_read_dump(NET * net) +{ + int data_fd= file->dfile.file; + int error= 0; + + my_seek(data_fd, 0L, MY_SEEK_SET, MYF(MY_WME)); + for (;;) + { + ulong packet_len= my_net_read(net); + if (!packet_len) + break; // end of file + if (packet_len == packet_error) + { + sql_print_error("ha_maria::net_read_dump - read error "); + error= -1; + goto err; + } + if (my_write(data_fd, (uchar *) net->read_pos, (uint) packet_len, + MYF(MY_WME | MY_FNABP))) + { + error= errno; + goto err; + } + } +err: + return error; +} + + +int ha_maria::dump(THD * thd, int fd) +{ + MARIA_SHARE *share= file->s; + NET *net= &thd->net; + uint block_size= share->block_size; + my_off_t bytes_to_read= share->state.state.data_file_length; + int data_fd= file->dfile.file; + uchar *buf= (uchar *) my_malloc(block_size, MYF(MY_WME)); + if (!buf) + return ENOMEM; + + int error= 0; + my_seek(data_fd, 0L, MY_SEEK_SET, MYF(MY_WME)); + for (; bytes_to_read > 0;) + { + size_t bytes= my_read(data_fd, buf, block_size, MYF(MY_WME)); + if (bytes == MY_FILE_ERROR) + { + error= errno; + goto err; + } + + if (fd >= 0) + { + if (my_write(fd, buf, bytes, MYF(MY_WME | MY_FNABP))) + { + error= errno ? errno : EPIPE; + goto err; + } + } + else + { + if (my_net_write(net, buf, bytes)) + { + error= errno ? errno : EPIPE; + goto err; + } + } + bytes_to_read -= bytes; + } + + if (fd < 0) + { + if (my_net_write(net, (uchar*) "", 0)) + error= errno ? errno : EPIPE; + net_flush(net); + } + +err: + my_free((uchar*) buf, MYF(0)); + return error; +} +#endif /* HAVE_REPLICATION */ + + /* Name is here without an extension */ + +int ha_maria::open(const char *name, int mode, uint test_if_locked) +{ + uint i; + +#ifdef NOT_USED + /* + If the user wants to have memory mapped data files, add an + open_flag. Do not memory map temporary tables because they are + expected to be inserted and thus extended a lot. Memory mapping is + efficient for files that keep their size, but very inefficient for + growing files. Using an open_flag instead of calling ma_extra(... + HA_EXTRA_MMAP ...) after maxs_open() has the advantage that the + mapping is not repeated for every open, but just done on the initial + open, when the MyISAM share is created. Every time the server + requires to open a new instance of a table it calls this method. We + will always supply HA_OPEN_MMAP for a permanent table. However, the + Maria storage engine will ignore this flag if this is a secondary + open of a table that is in use by other threads already (if the + Maria share exists already). + */ + if (!(test_if_locked & HA_OPEN_TMP_TABLE) && opt_maria_use_mmap) + test_if_locked|= HA_OPEN_MMAP; +#endif + + if (unlikely(maria_recover_options != HA_RECOVER_NONE)) + { + /* user asked to trigger a repair if table was not properly closed */ + test_if_locked|= HA_OPEN_ABORT_IF_CRASHED; + } + + if (!(file= maria_open(name, mode, test_if_locked | HA_OPEN_FROM_SQL_LAYER))) + return (my_errno ? my_errno : -1); + + if (test_if_locked & (HA_OPEN_IGNORE_IF_LOCKED | HA_OPEN_TMP_TABLE)) + VOID(maria_extra(file, HA_EXTRA_NO_WAIT_LOCK, 0)); + + info(HA_STATUS_NO_LOCK | HA_STATUS_VARIABLE | HA_STATUS_CONST); + if (!(test_if_locked & HA_OPEN_WAIT_IF_LOCKED)) + VOID(maria_extra(file, HA_EXTRA_WAIT_LOCK, 0)); + if ((data_file_type= file->s->data_file_type) != STATIC_RECORD) + int_table_flags |= HA_REC_NOT_IN_SEQ; + if (!file->s->base.born_transactional) + { + /* + INSERT DELAYED cannot work with transactional tables (because it cannot + stand up to "when client gets ok the data is safe on disk": the record + may not even be inserted). In the future, we could enable it back (as a + client doing INSERT DELAYED knows the specificities; but we then should + make sure to regularly commit in the delayed_insert thread). + */ + int_table_flags|= HA_CAN_INSERT_DELAYED; + } + if (file->s->options & (HA_OPTION_CHECKSUM | HA_OPTION_COMPRESS_RECORD)) + int_table_flags |= HA_HAS_NEW_CHECKSUM; + + for (i= 0; i < table->s->keys; i++) + { + plugin_ref parser= table->key_info[i].parser; + if (table->key_info[i].flags & HA_USES_PARSER) + file->s->keyinfo[i].parser= + (struct st_mysql_ftparser *)plugin_decl(parser)->info; + table->key_info[i].block_size= file->s->keyinfo[i].block_length; + } + my_errno= 0; + return my_errno; +} + + +int ha_maria::close(void) +{ + MARIA_HA *tmp= file; + file= 0; + return maria_close(tmp); +} + + +int ha_maria::write_row(uchar * buf) +{ + ha_statistic_increment(&SSV::ha_write_count); + + /* If we have a timestamp column, update it to the current time */ + if (table->timestamp_field_type & TIMESTAMP_AUTO_SET_ON_INSERT) + table->timestamp_field->set_time(); + + /* + 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 (table->next_number_field && buf == table->record[0]) + { + int error; + if ((error= update_auto_increment())) + return error; + } + return maria_write(file, buf); +} + + +int ha_maria::check(THD * thd, HA_CHECK_OPT * check_opt) +{ + if (!file) + return HA_ADMIN_INTERNAL_ERROR; + int error; + HA_CHECK param; + MARIA_SHARE *share= file->s; + const char *old_proc_info= thd_proc_info(thd, "Checking table"); + + maria_chk_init(¶m); + param.thd= thd; + param.op_name= "check"; + param.db_name= table->s->db.str; + param.table_name= table->alias; + param.testflag= check_opt->flags | T_CHECK | T_SILENT; + param.stats_method= (enum_handler_stats_method)THDVAR(thd,stats_method); + + if (!(table->db_stat & HA_READ_ONLY)) + param.testflag |= T_STATISTICS; + param.using_global_keycache= 1; + + if (!maria_is_crashed(file) && + (((param.testflag & T_CHECK_ONLY_CHANGED) && + !(share->state.changed & (STATE_CHANGED | STATE_CRASHED | + STATE_CRASHED_ON_REPAIR)) && + share->state.open_count == 0) || + ((param.testflag & T_FAST) && (share->state.open_count == + (uint) (share->global_changed ? 1 : + 0))))) + return HA_ADMIN_ALREADY_DONE; + + maria_chk_init_for_check(¶m, file); + (void) maria_chk_status(¶m, file); // Not fatal + error= maria_chk_size(¶m, file); + if (!error) + error|= maria_chk_del(¶m, file, param.testflag); + if (!error) + error= maria_chk_key(¶m, file); + if (!error) + { + if ((!(param.testflag & T_QUICK) && + ((share->options & + (HA_OPTION_PACK_RECORD | HA_OPTION_COMPRESS_RECORD)) || + (param.testflag & (T_EXTEND | T_MEDIUM)))) || maria_is_crashed(file)) + { + ulonglong old_testflag= param.testflag; + param.testflag |= T_MEDIUM; + if (!(error= init_io_cache(¶m.read_cache, file->dfile.file, + my_default_record_cache_size, READ_CACHE, + share->pack.header_length, 1, MYF(MY_WME)))) + { + error= maria_chk_data_link(¶m, file, + test(param.testflag & T_EXTEND)); + end_io_cache(&(param.read_cache)); + } + param.testflag= old_testflag; + } + } + if (!error) + { + if ((share->state.changed & (STATE_CHANGED | + STATE_CRASHED_ON_REPAIR | + STATE_CRASHED | STATE_NOT_ANALYZED)) || + (param.testflag & T_STATISTICS) || maria_is_crashed(file)) + { + file->update |= HA_STATE_CHANGED | HA_STATE_ROW_CHANGED; + pthread_mutex_lock(&share->intern_lock); + share->state.changed &= ~(STATE_CHANGED | STATE_CRASHED | + STATE_CRASHED_ON_REPAIR); + if (!(table->db_stat & HA_READ_ONLY)) + error= maria_update_state_info(¶m, file, + UPDATE_TIME | UPDATE_OPEN_COUNT | + UPDATE_STAT); + pthread_mutex_unlock(&share->intern_lock); + info(HA_STATUS_NO_LOCK | HA_STATUS_TIME | HA_STATUS_VARIABLE | + HA_STATUS_CONST); + } + } + else if (!maria_is_crashed(file) && !thd->killed) + { + maria_mark_crashed(file); + file->update |= HA_STATE_CHANGED | HA_STATE_ROW_CHANGED; + } + + thd_proc_info(thd, old_proc_info); + return error ? HA_ADMIN_CORRUPT : HA_ADMIN_OK; +} + + +/* + Analyze the key distribution in the table + As the table may be only locked for read, we have to take into account that + two threads may do an analyze at the same time! +*/ + +int ha_maria::analyze(THD *thd, HA_CHECK_OPT * check_opt) +{ + int error= 0; + HA_CHECK param; + MARIA_SHARE *share= file->s; + + maria_chk_init(¶m); + param.thd= thd; + param.op_name= "analyze"; + param.db_name= table->s->db.str; + param.table_name= table->alias; + param.testflag= (T_FAST | T_CHECK | T_SILENT | T_STATISTICS | + T_DONT_CHECK_CHECKSUM); + param.using_global_keycache= 1; + param.stats_method= (enum_handler_stats_method)THDVAR(thd,stats_method); + + if (!(share->state.changed & STATE_NOT_ANALYZED)) + return HA_ADMIN_ALREADY_DONE; + + error= maria_chk_key(¶m, file); + if (!error) + { + pthread_mutex_lock(&share->intern_lock); + error= maria_update_state_info(¶m, file, UPDATE_STAT); + pthread_mutex_unlock(&share->intern_lock); + } + else if (!maria_is_crashed(file) && !thd->killed) + maria_mark_crashed(file); + return error ? HA_ADMIN_CORRUPT : HA_ADMIN_OK; +} + + +int ha_maria::restore(THD * thd, HA_CHECK_OPT *check_opt) +{ + HA_CHECK_OPT tmp_check_opt; + char *backup_dir= thd->lex->backup_dir; + char src_path[FN_REFLEN], dst_path[FN_REFLEN]; + char table_name[FN_REFLEN]; + int error; + const char *errmsg; + DBUG_ENTER("restore"); + + VOID(tablename_to_filename(table->s->table_name.str, table_name, + sizeof(table_name))); + + if (fn_format_relative_to_data_home(src_path, table_name, backup_dir, + MARIA_NAME_DEXT)) + DBUG_RETURN(HA_ADMIN_INVALID); + + strxmov(dst_path, table->s->normalized_path.str, MARIA_NAME_DEXT, NullS); + if (my_copy(src_path, dst_path, MYF(MY_WME))) + { + error= HA_ADMIN_FAILED; + errmsg= "Failed in my_copy (Error %d)"; + goto err; + } + + tmp_check_opt.init(); + tmp_check_opt.flags |= T_VERY_SILENT | T_CALC_CHECKSUM | T_QUICK; + DBUG_RETURN(repair(thd, &tmp_check_opt)); + +err: + { + /* + Don't allocate param on stack here as this may be huge and it's + also allocated by repair() + */ + HA_CHECK *param; + if (!(param= (HA_CHECK*) my_malloc(sizeof(*param), MYF(MY_WME | MY_FAE)))) + DBUG_RETURN(error); + maria_chk_init(param); + param->thd= thd; + param->op_name= "restore"; + param->db_name= table->s->db.str; + param->table_name= table->s->table_name.str; + param->testflag= 0; + _ma_check_print_error(param, errmsg, my_errno); + my_free(param, MYF(0)); + DBUG_RETURN(error); + } +} + + +int ha_maria::backup(THD * thd, HA_CHECK_OPT *check_opt) +{ + char *backup_dir= thd->lex->backup_dir; + char src_path[FN_REFLEN], dst_path[FN_REFLEN]; + char table_name[FN_REFLEN]; + int error; + const char *errmsg; + DBUG_ENTER("ha_maria::backup"); + + VOID(tablename_to_filename(table->s->table_name.str, table_name, + sizeof(table_name))); + + if (fn_format_relative_to_data_home(dst_path, table_name, backup_dir, + reg_ext)) + { + errmsg= "Failed in fn_format() for .frm file (errno: %d)"; + error= HA_ADMIN_INVALID; + goto err; + } + + strxmov(src_path, table->s->normalized_path.str, reg_ext, NullS); + if (my_copy(src_path, dst_path, + MYF(MY_WME | MY_HOLD_ORIGINAL_MODES | MY_DONT_OVERWRITE_FILE))) + { + error= HA_ADMIN_FAILED; + errmsg= "Failed copying .frm file (errno: %d)"; + goto err; + } + + /* Change extension */ + if (fn_format_relative_to_data_home(dst_path, table_name, backup_dir, + MARIA_NAME_DEXT)) + { + errmsg= "Failed in fn_format() for .MYD file (errno: %d)"; + error= HA_ADMIN_INVALID; + goto err; + } + + strxmov(src_path, table->s->normalized_path.str, MARIA_NAME_DEXT, NullS); + if (_ma_flush_table_files(file, MARIA_FLUSH_DATA, FLUSH_FORCE_WRITE, + FLUSH_KEEP)) + { + error= HA_ADMIN_FAILED; + errmsg= "Failed in flush (Error %d)"; + goto err; + } + if (my_copy(src_path, dst_path, + MYF(MY_WME | MY_HOLD_ORIGINAL_MODES | MY_DONT_OVERWRITE_FILE))) + { + errmsg= "Failed copying .MYD file (errno: %d)"; + error= HA_ADMIN_FAILED; + goto err; + } + DBUG_RETURN(HA_ADMIN_OK); + +err: + { + HA_CHECK param; + maria_chk_init(¶m); + param.thd= thd; + param.op_name= "backup"; + param.db_name= table->s->db.str; + param.table_name= table->s->table_name.str; + param.testflag= 0; + _ma_check_print_error(¶m, errmsg, my_errno); + DBUG_RETURN(error); + } +} + + +int ha_maria::repair(THD * thd, HA_CHECK_OPT *check_opt) +{ + int error; + HA_CHECK param; + ha_rows start_records; + + if (!file) + return HA_ADMIN_INTERNAL_ERROR; + + maria_chk_init(¶m); + param.thd= thd; + param.op_name= "repair"; + param.testflag= ((check_opt->flags & ~(T_EXTEND)) | + T_SILENT | T_FORCE_CREATE | T_CALC_CHECKSUM | + (check_opt->flags & T_EXTEND ? T_REP : T_REP_BY_SORT)); + param.sort_buffer_length= THDVAR(thd, sort_buffer_size); + start_records= file->state->records; + while ((error= repair(thd, ¶m, 0)) && param.retry_repair) + { + param.retry_repair= 0; + if (test_all_bits(param.testflag, + (uint) (T_RETRY_WITHOUT_QUICK | T_QUICK))) + { + param.testflag&= ~(T_RETRY_WITHOUT_QUICK | T_QUICK); + /* Ensure we don't loose any rows when retrying without quick */ + param.testflag|= T_SAFE_REPAIR; + if (thd->vio_ok()) + _ma_check_print_info(¶m, "Retrying repair without quick"); + else + sql_print_information("Retrying repair of: '%s' without quick", + table->s->path.str); + continue; + } + param.testflag &= ~T_QUICK; + if ((param.testflag & T_REP_BY_SORT)) + { + param.testflag= (param.testflag & ~T_REP_BY_SORT) | T_REP; + sql_print_information("Retrying repair of: '%s' with keycache", + table->s->path.str); + continue; + } + break; + } + if (!error && start_records != file->state->records && + !(check_opt->flags & T_VERY_SILENT)) + { + char llbuff[22], llbuff2[22]; + sql_print_information("Found %s of %s rows when repairing '%s'", + llstr(file->state->records, llbuff), + llstr(start_records, llbuff2), + table->s->path.str); + } + return error; +} + +int ha_maria::zerofill(THD * thd, HA_CHECK_OPT *check_opt) +{ + int error; + HA_CHECK param; + MARIA_SHARE *share= file->s; + + if (!file) + return HA_ADMIN_INTERNAL_ERROR; + + maria_chk_init(¶m); + param.thd= thd; + param.op_name= "zerofill"; + param.testflag= check_opt->flags | T_SILENT | T_ZEROFILL; + param.sort_buffer_length= THDVAR(thd, sort_buffer_size); + error=maria_zerofill(¶m, file, share->open_file_name.str); + + if (!error) + { + pthread_mutex_lock(&share->intern_lock); + maria_update_state_info(¶m, file, UPDATE_TIME | UPDATE_OPEN_COUNT); + pthread_mutex_unlock(&share->intern_lock); + } + return error; +} + +int ha_maria::optimize(THD * thd, HA_CHECK_OPT *check_opt) +{ + int error; + HA_CHECK param; + if (!file) + return HA_ADMIN_INTERNAL_ERROR; + + maria_chk_init(¶m); + param.thd= thd; + param.op_name= "optimize"; + param.testflag= (check_opt->flags | T_SILENT | T_FORCE_CREATE | + T_REP_BY_SORT | T_STATISTICS | T_SORT_INDEX); + param.sort_buffer_length= THDVAR(thd, sort_buffer_size); + if ((error= repair(thd, ¶m, 1)) && param.retry_repair) + { + sql_print_warning("Warning: Optimize table got errno %d on %s.%s, retrying", + my_errno, param.db_name, param.table_name); + param.testflag &= ~T_REP_BY_SORT; + error= repair(thd, ¶m, 1); + } + return error; +} + + +int ha_maria::repair(THD *thd, HA_CHECK *param, bool do_optimize) +{ + int error= 0; + ulonglong local_testflag= param->testflag; + bool optimize_done= !do_optimize, statistics_done= 0; + const char *old_proc_info= thd->proc_info; + char fixed_name[FN_REFLEN]; + MARIA_SHARE *share= file->s; + ha_rows rows= file->state->records; + DBUG_ENTER("ha_maria::repair"); + + /* + Normally this method is entered with a properly opened table. If the + repair fails, it can be repeated with more elaborate options. Under + special circumstances it can happen that a repair fails so that it + closed the data file and cannot re-open it. In this case file->dfile + is set to -1. We must not try another repair without an open data + file. (Bug #25289) + */ + if (file->dfile.file == -1) + { + sql_print_information("Retrying repair of: '%s' failed. " + "Please try REPAIR EXTENDED or maria_chk", + table->s->path.str); + DBUG_RETURN(HA_ADMIN_FAILED); + } + + /* + If transactions was not enabled for a transactional table then + file->s->status is not up to date. This is needed for repair_by_sort + to work + */ + if (share->base.born_transactional && !share->now_transactional) + _ma_copy_nontrans_state_information(file); + + param->db_name= table->s->db.str; + param->table_name= table->alias; + param->tmpfile_createflag= O_RDWR | O_TRUNC; + param->using_global_keycache= 1; + param->thd= thd; + param->tmpdir= &mysql_tmpdir_list; + param->out_flag= 0; + strmov(fixed_name, share->open_file_name.str); + + // Don't lock tables if we have used LOCK TABLE + if (!thd->locked_tables && + maria_lock_database(file, table->s->tmp_table ? F_EXTRA_LCK : F_WRLCK)) + { + _ma_check_print_error(param, ER(ER_CANT_LOCK), my_errno); + DBUG_RETURN(HA_ADMIN_FAILED); + } + + if (!do_optimize || + (((share->data_file_type == BLOCK_RECORD) ? + (share->state.changed & STATE_NOT_OPTIMIZED_ROWS) : + (file->state->del || + share->state.split != file->state->records)) && + (!(param->testflag & T_QUICK) || + (share->state.changed & (STATE_NOT_OPTIMIZED_KEYS | + STATE_NOT_OPTIMIZED_ROWS))))) + { + ulonglong key_map= ((local_testflag & T_CREATE_MISSING_KEYS) ? + maria_get_mask_all_keys_active(share->base.keys) : + share->state.key_map); + ulonglong save_testflag= param->testflag; + if (maria_test_if_sort_rep(file, file->state->records, key_map, 0) && + (local_testflag & T_REP_BY_SORT)) + { + local_testflag |= T_STATISTICS; + param->testflag |= T_STATISTICS; // We get this for free + statistics_done= 1; + /* TODO: Remove BLOCK_RECORD test when parallel works with blocks */ + if (THDVAR(thd,repair_threads) > 1 && + share->data_file_type != BLOCK_RECORD) + { + char buf[40]; + /* TODO: respect maria_repair_threads variable */ + my_snprintf(buf, 40, "Repair with %d threads", my_count_bits(key_map)); + thd_proc_info(thd, buf); + param->testflag|= T_REP_PARALLEL; + error= maria_repair_parallel(param, file, fixed_name, + test(param->testflag & T_QUICK)); + /* to reset proc_info, as it was pointing to local buffer */ + thd_proc_info(thd, "Repair done"); + } + else + { + thd_proc_info(thd, "Repair by sorting"); + param->testflag|= T_REP_BY_SORT; + error= maria_repair_by_sort(param, file, fixed_name, + test(param->testflag & T_QUICK)); + } + } + else + { + thd_proc_info(thd, "Repair with keycache"); + param->testflag &= ~(T_REP_BY_SORT | T_REP_PARALLEL); + error= maria_repair(param, file, fixed_name, + test(param->testflag & T_QUICK)); + } + param->testflag= save_testflag | (param->testflag & T_RETRY_WITHOUT_QUICK); + optimize_done= 1; + } + if (!error) + { + if ((local_testflag & T_SORT_INDEX) && + (share->state.changed & STATE_NOT_SORTED_PAGES)) + { + optimize_done= 1; + thd_proc_info(thd, "Sorting index"); + error= maria_sort_index(param, file, fixed_name); + } + if (!statistics_done && (local_testflag & T_STATISTICS)) + { + if (share->state.changed & STATE_NOT_ANALYZED) + { + optimize_done= 1; + thd_proc_info(thd, "Analyzing"); + error= maria_chk_key(param, file); + } + else + local_testflag &= ~T_STATISTICS; // Don't update statistics + } + } + thd_proc_info(thd, "Saving state"); + pthread_mutex_lock(&share->intern_lock); + if (!error) + { + if ((share->state.changed & STATE_CHANGED) || maria_is_crashed(file)) + { + share->state.changed &= ~(STATE_CHANGED | STATE_CRASHED | + STATE_CRASHED_ON_REPAIR); + file->update |= HA_STATE_CHANGED | HA_STATE_ROW_CHANGED; + } + /* + repair updates share->state.state. Ensure that file->state is up to date + */ + if (file->state != &share->state.state) + *file->state= share->state.state; + if (share->base.auto_key) + _ma_update_auto_increment_key(param, file, 1); + if (optimize_done) + error= maria_update_state_info(param, file, + UPDATE_TIME | UPDATE_OPEN_COUNT | + (local_testflag & + T_STATISTICS ? UPDATE_STAT : 0)); + info(HA_STATUS_NO_LOCK | HA_STATUS_TIME | HA_STATUS_VARIABLE | + HA_STATUS_CONST, 0); + if (rows != file->state->records && !(param->testflag & T_VERY_SILENT)) + { + char llbuff[22], llbuff2[22]; + _ma_check_print_warning(param, "Number of rows changed from %s to %s", + llstr(rows, llbuff), + llstr(file->state->records, llbuff2)); + /* Abort if warning was converted to error */ + if (current_thd->is_error()) + error= 1; + } + } + else + { + maria_mark_crashed_on_repair(file); + file->update |= HA_STATE_CHANGED | HA_STATE_ROW_CHANGED; + maria_update_state_info(param, file, 0); + } + pthread_mutex_unlock(&share->intern_lock); + thd_proc_info(thd, old_proc_info); + if (!thd->locked_tables) + maria_lock_database(file, F_UNLCK); + error= error ? HA_ADMIN_FAILED : + (optimize_done ? + (write_log_record_for_repair(param, file) ? HA_ADMIN_FAILED : + HA_ADMIN_OK) : HA_ADMIN_ALREADY_DONE); + DBUG_RETURN(error); +} + + +/* + Assign table indexes to a specific key cache. +*/ + +int ha_maria::assign_to_keycache(THD * thd, HA_CHECK_OPT *check_opt) +{ +#if 0 && NOT_IMPLEMENTED + PAGECACHE *new_pagecache= check_opt->pagecache; + const char *errmsg= 0; + int error= HA_ADMIN_OK; + ulonglong map; + TABLE_LIST *table_list= table->pos_in_table_list; + DBUG_ENTER("ha_maria::assign_to_keycache"); + + + table->keys_in_use_for_query.clear_all(); + + if (table_list->process_index_hints(table)) + DBUG_RETURN(HA_ADMIN_FAILED); + map= ~(ulonglong) 0; + if (!table->keys_in_use_for_query.is_clear_all()) + /* use all keys if there's no list specified by the user through hints */ + map= table->keys_in_use_for_query.to_ulonglong(); + + if ((error= maria_assign_to_pagecache(file, map, new_pagecache))) + { + char buf[STRING_BUFFER_USUAL_SIZE]; + my_snprintf(buf, sizeof(buf), + "Failed to flush to index file (errno: %d)", error); + errmsg= buf; + error= HA_ADMIN_CORRUPT; + } + + if (error != HA_ADMIN_OK) + { + /* Send error to user */ + HA_CHECK param; + maria_chk_init(¶m); + param.thd= thd; + param.op_name= "assign_to_keycache"; + param.db_name= table->s->db.str; + param.table_name= table->s->table_name.str; + param.testflag= 0; + _ma_check_print_error(¶m, errmsg); + } + DBUG_RETURN(error); +#else + return HA_ADMIN_NOT_IMPLEMENTED; +#endif +} + + +/* + Preload pages of the index file for a table into the key cache. +*/ + +int ha_maria::preload_keys(THD * thd, HA_CHECK_OPT *check_opt) +{ + ulonglong map; + TABLE_LIST *table_list= table->pos_in_table_list; + + DBUG_ENTER("ha_maria::preload_keys"); + + table->keys_in_use_for_query.clear_all(); + + if (table_list->process_index_hints(table)) + DBUG_RETURN(HA_ADMIN_FAILED); + + map= ~(ulonglong) 0; + /* Check validity of the index references */ + if (!table->keys_in_use_for_query.is_clear_all()) + /* use all keys if there's no list specified by the user through hints */ + map= table->keys_in_use_for_query.to_ulonglong(); + + maria_extra(file, HA_EXTRA_PRELOAD_BUFFER_SIZE, + (void*) &thd->variables.preload_buff_size); + + int error; + + if ((error= maria_preload(file, map, table_list->ignore_leaves))) + { + char buf[MYSQL_ERRMSG_SIZE+20]; + const char *errmsg; + + switch (error) { + case HA_ERR_NON_UNIQUE_BLOCK_SIZE: + errmsg= "Indexes use different block sizes"; + break; + case HA_ERR_OUT_OF_MEM: + errmsg= "Failed to allocate buffer"; + break; + default: + my_snprintf(buf, sizeof(buf), + "Failed to read from index file (errno: %d)", my_errno); + errmsg= buf; + } + + HA_CHECK param; + maria_chk_init(¶m); + param.thd= thd; + param.op_name= "preload_keys"; + param.db_name= table->s->db.str; + param.table_name= table->s->table_name.str; + param.testflag= 0; + _ma_check_print_error(¶m, errmsg); + DBUG_RETURN(HA_ADMIN_FAILED); + } + DBUG_RETURN(HA_ADMIN_OK); +} + + +/* + Disable indexes, making it persistent if requested. + + SYNOPSIS + disable_indexes() + mode mode of operation: + HA_KEY_SWITCH_NONUNIQ disable all non-unique keys + HA_KEY_SWITCH_ALL disable all keys + HA_KEY_SWITCH_NONUNIQ_SAVE dis. non-uni. and make persistent + HA_KEY_SWITCH_ALL_SAVE dis. all keys and make persistent + + IMPLEMENTATION + HA_KEY_SWITCH_NONUNIQ is not implemented. + HA_KEY_SWITCH_ALL_SAVE is not implemented. + + RETURN + 0 ok + HA_ERR_WRONG_COMMAND mode not implemented. +*/ + +int ha_maria::disable_indexes(uint mode) +{ + int error; + + if (mode == HA_KEY_SWITCH_ALL) + { + /* call a storage engine function to switch the key map */ + error= maria_disable_indexes(file); + } + else if (mode == HA_KEY_SWITCH_NONUNIQ_SAVE) + { + maria_extra(file, HA_EXTRA_NO_KEYS, 0); + info(HA_STATUS_CONST); // Read new key info + error= 0; + } + else + { + /* mode not implemented */ + error= HA_ERR_WRONG_COMMAND; + } + return error; +} + + +/* + Enable indexes, making it persistent if requested. + + SYNOPSIS + enable_indexes() + mode mode of operation: + HA_KEY_SWITCH_NONUNIQ enable all non-unique keys + HA_KEY_SWITCH_ALL enable all keys + HA_KEY_SWITCH_NONUNIQ_SAVE en. non-uni. and make persistent + HA_KEY_SWITCH_ALL_SAVE en. all keys and make persistent + + DESCRIPTION + Enable indexes, which might have been disabled by disable_index() before. + The modes without _SAVE work only if both data and indexes are empty, + since the MARIA repair would enable them persistently. + To be sure in these cases, call handler::delete_all_rows() before. + + IMPLEMENTATION + HA_KEY_SWITCH_NONUNIQ is not implemented. + HA_KEY_SWITCH_ALL_SAVE is not implemented. + + RETURN + 0 ok + !=0 Error, among others: + HA_ERR_CRASHED data or index is non-empty. Delete all rows and retry. + HA_ERR_WRONG_COMMAND mode not implemented. +*/ + +int ha_maria::enable_indexes(uint mode) +{ + int error; + DBUG_PRINT("info", ("ha_maria::enable_indexes mode: %d", mode)); + if (maria_is_all_keys_active(file->s->state.key_map, file->s->base.keys)) + { + /* All indexes are enabled already. */ + return 0; + } + + if (mode == HA_KEY_SWITCH_ALL) + { + error= maria_enable_indexes(file); + /* + Do not try to repair on error, + as this could make the enabled state persistent, + but mode==HA_KEY_SWITCH_ALL forbids it. + */ + } + else if (mode == HA_KEY_SWITCH_NONUNIQ_SAVE) + { + THD *thd= current_thd; + HA_CHECK param; + const char *save_proc_info= thd_proc_info(thd, "Creating index"); + maria_chk_init(¶m); + param.op_name= "recreating_index"; + param.testflag= (T_SILENT | T_REP_BY_SORT | T_QUICK | + T_CREATE_MISSING_KEYS | T_SAFE_REPAIR); + if (bulk_insert_single_undo == BULK_INSERT_SINGLE_UNDO_AND_NO_REPAIR) + { + bulk_insert_single_undo= BULK_INSERT_SINGLE_UNDO_AND_REPAIR; + /* + Don't bump create_rename_lsn, because UNDO_BULK_INSERT + should not be skipped in case of crash during repair. + */ + param.testflag|= T_NO_CREATE_RENAME_LSN; + } + param.myf_rw &= ~MY_WAIT_IF_FULL; + param.sort_buffer_length= THDVAR(thd,sort_buffer_size); + param.stats_method= (enum_handler_stats_method)THDVAR(thd,stats_method); + param.tmpdir= &mysql_tmpdir_list; + if ((error= (repair(thd, ¶m, 0) != HA_ADMIN_OK)) && param.retry_repair) + { + sql_print_warning("Warning: Enabling keys got errno %d on %s.%s, " + "retrying", + my_errno, param.db_name, param.table_name); + /* This should never fail normally */ + DBUG_ASSERT(0); + /* Repairing by sort failed. Now try standard repair method. */ + param.testflag &= ~T_REP_BY_SORT; + error= (repair(thd, ¶m, 0) != HA_ADMIN_OK); + /* + If the standard repair succeeded, clear all error messages which + might have been set by the first repair. They can still be seen + with SHOW WARNINGS then. + */ + if (!error) + thd->clear_error(); + } + info(HA_STATUS_CONST); + thd_proc_info(thd, save_proc_info); + } + else + { + /* mode not implemented */ + error= HA_ERR_WRONG_COMMAND; + } + DBUG_EXECUTE_IF("maria_flush_whole_log", + { + DBUG_PRINT("maria_flush_whole_log", ("now")); + translog_flush(translog_get_horizon()); + }); + DBUG_EXECUTE_IF("maria_crash_enable_index", + { + DBUG_PRINT("maria_crash_enable_index", ("now")); + DBUG_ABORT(); + }); + return error; +} + + +/* + Test if indexes are disabled. + + + SYNOPSIS + indexes_are_disabled() + no parameters + + + RETURN + 0 indexes are not disabled + 1 all indexes are disabled + [2 non-unique indexes are disabled - NOT YET IMPLEMENTED] +*/ + +int ha_maria::indexes_are_disabled(void) +{ + return maria_indexes_are_disabled(file); +} + + +/* + prepare for a many-rows insert operation + e.g. - disable indexes (if they can be recreated fast) or + activate special bulk-insert optimizations + + SYNOPSIS + start_bulk_insert(rows) + rows Rows to be inserted + 0 if we don't know + + NOTICE + Do not forget to call end_bulk_insert() later! +*/ + +void ha_maria::start_bulk_insert(ha_rows rows) +{ + DBUG_ENTER("ha_maria::start_bulk_insert"); + THD *thd= current_thd; + ulong size= min(thd->variables.read_buff_size, + (ulong) (table->s->avg_row_length * rows)); + MARIA_SHARE *share= file->s; + DBUG_PRINT("info", ("start_bulk_insert: rows %lu size %lu", + (ulong) rows, size)); + + /* don't enable row cache if too few rows */ + if (!rows || (rows > MARIA_MIN_ROWS_TO_USE_WRITE_CACHE)) + maria_extra(file, HA_EXTRA_WRITE_CACHE, (void*) &size); + + can_enable_indexes= (maria_is_all_keys_active(share->state.key_map, + share->base.keys)); + bulk_insert_single_undo= BULK_INSERT_NONE; + + if (!(specialflag & SPECIAL_SAFE_MODE)) + { + /* + Only disable old index if the table was empty and we are inserting + a lot of rows. + We should not do this for only a few rows as this is slower and + we don't want to update the key statistics based of only a few rows. + Index file rebuild requires an exclusive lock, so if versioning is on + don't do it (see how ha_maria::store_lock() tries to predict repair). + We can repair index only if we have an exclusive (TL_WRITE) lock. To + see if table is empty, we shouldn't rely on the old records' count from + our transaction's start (if that old count is 0 but now there are + records in the table, we would wrongly destroy them). + So we need to look at share->state.state.records. + As a safety net for now, we don't remove the test of + file->state->records, because there is uncertainty on what will happen + during repair if the two states disagree. + */ + if ((file->state->records == 0) && + (share->state.state.records == 0) && can_enable_indexes && + (!rows || rows >= MARIA_MIN_ROWS_TO_DISABLE_INDEXES) && + (file->lock.type == TL_WRITE)) + { + /** + @todo for a single-row INSERT SELECT, we will go into repair, which + is more costly (flushes, syncs) than a row write. + */ + maria_disable_non_unique_index(file, rows); + if (share->now_transactional) + { + bulk_insert_single_undo= BULK_INSERT_SINGLE_UNDO_AND_NO_REPAIR; + write_log_record_for_bulk_insert(file); + _ma_tmp_disable_logging_for_table(file, TRUE); + /* + Pages currently in the page cache have type PAGECACHE_LSN_PAGE, we + are not allowed to overwrite them with PAGECACHE_PLAIN_PAGE, so + throw them away. It is not losing data, because we just wrote and + forced an UNDO which will for sure empty the table if we crash. The + upcoming unique-key insertions however need a proper index, so we + cannot leave the corrupted on-disk index file, thus we truncate it. + */ + maria_delete_all_rows(file); + } + } + else if (!file->bulk_insert && + (!rows || rows >= MARIA_MIN_ROWS_TO_USE_BULK_INSERT)) + { + maria_init_bulk_insert(file, thd->variables.bulk_insert_buff_size, rows); + } + } + DBUG_VOID_RETURN; +} + + +/* + end special bulk-insert optimizations, + which have been activated by start_bulk_insert(). + + SYNOPSIS + end_bulk_insert() + no arguments + + RETURN + 0 OK + != 0 Error +*/ + +int ha_maria::end_bulk_insert(bool table_will_be_deleted) +{ + int err; + DBUG_ENTER("ha_maria::end_bulk_insert"); + maria_end_bulk_insert(file, table_will_be_deleted); + if ((err= maria_extra(file, HA_EXTRA_NO_CACHE, 0))) + goto end; + if (can_enable_indexes && !table_will_be_deleted) + err= enable_indexes(HA_KEY_SWITCH_NONUNIQ_SAVE); +end: + if (bulk_insert_single_undo != BULK_INSERT_NONE) + { + DBUG_ASSERT(can_enable_indexes); + /* + Table was transactional just before start_bulk_insert(). + No need to flush pages if we did a repair (which already flushed). + */ + err|= + _ma_reenable_logging_for_table(file, + bulk_insert_single_undo == + BULK_INSERT_SINGLE_UNDO_AND_NO_REPAIR); + } + DBUG_RETURN(err); +} + + +bool ha_maria::check_and_repair(THD *thd) +{ + int error, crashed; + char *old_query; + uint old_query_length; + HA_CHECK_OPT check_opt; + DBUG_ENTER("ha_maria::check_and_repair"); + + check_opt.init(); + + if (file->s->state.changed & STATE_MOVED) + { + sql_print_information("Zerofilling table: '%s'", table->s->path.str); + if (!(error= zerofill(thd, &check_opt))) + DBUG_RETURN(0); + } + else + error= 1; + + /* + if we got this far - the table is crashed. + but don't auto-repair if maria_recover_options is not set + */ + if (!maria_recover_options) + DBUG_RETURN(error); + + error= 0; + check_opt.flags= T_MEDIUM | T_AUTO_REPAIR; + // Don't use quick if deleted rows + if (!file->state->del && (maria_recover_options & HA_RECOVER_QUICK)) + check_opt.flags |= T_QUICK; + + old_query= thd->query; + old_query_length= thd->query_length; + pthread_mutex_lock(&LOCK_thread_count); + thd->query= table->s->table_name.str; + thd->query_length= table->s->table_name.length; + pthread_mutex_unlock(&LOCK_thread_count); + + if (!(crashed= maria_is_crashed(file))) + { + sql_print_warning("Checking table: '%s'", table->s->path.str); + crashed= check(thd, &check_opt); + } + + if (crashed) + { + sql_print_warning("Recovering table: '%s'", table->s->path.str); + check_opt.flags= + ((maria_recover_options & HA_RECOVER_BACKUP ? T_BACKUP_DATA : 0) | + (maria_recover_options & HA_RECOVER_FORCE ? 0 : T_SAFE_REPAIR) | + T_AUTO_REPAIR); + if (repair(thd, &check_opt)) + error= 1; + } + pthread_mutex_lock(&LOCK_thread_count); + thd->query= old_query; + thd->query_length= old_query_length; + pthread_mutex_unlock(&LOCK_thread_count); + DBUG_RETURN(error); +} + + +bool ha_maria::is_crashed() const +{ + return (file->s->state.changed & (STATE_CRASHED | STATE_MOVED) || + (my_disable_locking && file->s->state.open_count)); +} + +#define CHECK_UNTIL_WE_FULLY_IMPLEMENTED_VERSIONING(msg) \ + do { \ + if (file->lock.type == TL_WRITE_CONCURRENT_INSERT) \ + { \ + my_error(ER_CHECK_NOT_IMPLEMENTED, MYF(0), msg); \ + return 1; \ + } \ + } while(0) + +int ha_maria::update_row(const uchar * old_data, uchar * new_data) +{ + CHECK_UNTIL_WE_FULLY_IMPLEMENTED_VERSIONING("UPDATE in WRITE CONCURRENT"); + ha_statistic_increment(&SSV::ha_update_count); + if (table->timestamp_field_type & TIMESTAMP_AUTO_SET_ON_UPDATE) + table->timestamp_field->set_time(); + return maria_update(file, old_data, new_data); +} + + +int ha_maria::delete_row(const uchar * buf) +{ + CHECK_UNTIL_WE_FULLY_IMPLEMENTED_VERSIONING("DELETE in WRITE CONCURRENT"); + ha_statistic_increment(&SSV::ha_delete_count); + return maria_delete(file, buf); +} + + +int ha_maria::index_read_map(uchar * buf, const uchar * key, + key_part_map keypart_map, + enum ha_rkey_function find_flag) +{ + DBUG_ASSERT(inited == INDEX); + ha_statistic_increment(&SSV::ha_read_key_count); + int error= maria_rkey(file, buf, active_index, key, keypart_map, find_flag); + table->status= error ? STATUS_NOT_FOUND : 0; + return error; +} + + +int ha_maria::index_read_idx_map(uchar * buf, uint index, const uchar * key, + key_part_map keypart_map, + enum ha_rkey_function find_flag) +{ + ha_statistic_increment(&SSV::ha_read_key_count); + int error= maria_rkey(file, buf, index, key, keypart_map, find_flag); + table->status= error ? STATUS_NOT_FOUND : 0; + return error; +} + + +int ha_maria::index_read_last_map(uchar * buf, const uchar * key, + key_part_map keypart_map) +{ + DBUG_ENTER("ha_maria::index_read_last_map"); + DBUG_ASSERT(inited == INDEX); + ha_statistic_increment(&SSV::ha_read_key_count); + int error= maria_rkey(file, buf, active_index, key, keypart_map, + HA_READ_PREFIX_LAST); + table->status= error ? STATUS_NOT_FOUND : 0; + DBUG_RETURN(error); +} + + +int ha_maria::index_next(uchar * buf) +{ + DBUG_ASSERT(inited == INDEX); + ha_statistic_increment(&SSV::ha_read_next_count); + int error= maria_rnext(file, buf, active_index); + table->status= error ? STATUS_NOT_FOUND : 0; + return error; +} + + +int ha_maria::index_prev(uchar * buf) +{ + DBUG_ASSERT(inited == INDEX); + ha_statistic_increment(&SSV::ha_read_prev_count); + int error= maria_rprev(file, buf, active_index); + table->status= error ? STATUS_NOT_FOUND : 0; + return error; +} + + +int ha_maria::index_first(uchar * buf) +{ + DBUG_ASSERT(inited == INDEX); + ha_statistic_increment(&SSV::ha_read_first_count); + int error= maria_rfirst(file, buf, active_index); + table->status= error ? STATUS_NOT_FOUND : 0; + return error; +} + + +int ha_maria::index_last(uchar * buf) +{ + DBUG_ASSERT(inited == INDEX); + ha_statistic_increment(&SSV::ha_read_last_count); + int error= maria_rlast(file, buf, active_index); + table->status= error ? STATUS_NOT_FOUND : 0; + return error; +} + + +int ha_maria::index_next_same(uchar * buf, + const uchar *key __attribute__ ((unused)), + uint length __attribute__ ((unused))) +{ + int error; + DBUG_ASSERT(inited == INDEX); + ha_statistic_increment(&SSV::ha_read_next_count); + /* + TODO: Delete this loop in Maria 1.5 as versioning will ensure this never + happens + */ + do + { + error= maria_rnext_same(file,buf); + } while (error == HA_ERR_RECORD_DELETED); + table->status= error ? STATUS_NOT_FOUND : 0; + return error; +} + + +int ha_maria::rnd_init(bool scan) +{ + if (scan) + return maria_scan_init(file); + return maria_reset(file); // Free buffers +} + + +int ha_maria::rnd_end() +{ + /* Safe to call even if we don't have started a scan */ + maria_scan_end(file); + return 0; +} + + +int ha_maria::rnd_next(uchar *buf) +{ + ha_statistic_increment(&SSV::ha_read_rnd_next_count); + int error= maria_scan(file, buf); + table->status= error ? STATUS_NOT_FOUND : 0; + return error; +} + + +int ha_maria::remember_rnd_pos() +{ + return (*file->s->scan_remember_pos)(file, &remember_pos); +} + + +int ha_maria::restart_rnd_next(uchar *buf) +{ + (*file->s->scan_restore_pos)(file, remember_pos); + return rnd_next(buf); +} + + +int ha_maria::rnd_pos(uchar *buf, uchar *pos) +{ + ha_statistic_increment(&SSV::ha_read_rnd_count); + int error= maria_rrnd(file, buf, my_get_ptr(pos, ref_length)); + table->status= error ? STATUS_NOT_FOUND : 0; + return error; +} + + +void ha_maria::position(const uchar *record) +{ + my_off_t row_position= maria_position(file); + my_store_ptr(ref, ref_length, row_position); +} + + +int ha_maria::info(uint flag) +{ + return info(flag, table->s->tmp_table == NO_TMP_TABLE); +} + +int ha_maria::info(uint flag, my_bool lock_table_share) +{ + MARIA_INFO maria_info; + char name_buff[FN_REFLEN]; + + (void) maria_status(file, &maria_info, flag); + if (flag & HA_STATUS_VARIABLE) + { + stats.records= maria_info.records; + stats.deleted= maria_info.deleted; + stats.data_file_length= maria_info.data_file_length; + stats.index_file_length= maria_info.index_file_length; + stats.delete_length= maria_info.delete_length; + stats.check_time= maria_info.check_time; + stats.mean_rec_length= maria_info.mean_reclength; + } + if (flag & HA_STATUS_CONST) + { + TABLE_SHARE *share= table->s; + stats.max_data_file_length= maria_info.max_data_file_length; + stats.max_index_file_length= maria_info.max_index_file_length; + stats.create_time= maria_info.create_time; + ref_length= maria_info.reflength; + share->db_options_in_use= maria_info.options; + stats.block_size= maria_block_size; + + /* Update share */ + if (lock_table_share) + pthread_mutex_lock(&share->mutex); + share->keys_in_use.set_prefix(share->keys); + share->keys_in_use.intersect_extended(maria_info.key_map); + share->keys_for_keyread.intersect(share->keys_in_use); + share->db_record_offset= maria_info.record_offset; + if (share->key_parts) + { + ulong *to= table->key_info[0].rec_per_key, *end; + double *from= maria_info.rec_per_key; + for (end= to+ share->key_parts ; to < end ; to++, from++) + *to= (ulong) (*from + 0.5); + } + if (lock_table_share) + pthread_mutex_unlock(&share->mutex); + + /* + Set data_file_name and index_file_name to point at the symlink value + if table is symlinked (Ie; Real name is not same as generated name) + */ + data_file_name= index_file_name= 0; + fn_format(name_buff, file->s->open_file_name.str, "", MARIA_NAME_DEXT, + MY_APPEND_EXT | MY_UNPACK_FILENAME); + if (strcmp(name_buff, maria_info.data_file_name)) + data_file_name =maria_info.data_file_name; + fn_format(name_buff, file->s->open_file_name.str, "", MARIA_NAME_IEXT, + MY_APPEND_EXT | MY_UNPACK_FILENAME); + if (strcmp(name_buff, maria_info.index_file_name)) + index_file_name=maria_info.index_file_name; + } + if (flag & HA_STATUS_ERRKEY) + { + errkey= maria_info.errkey; + my_store_ptr(dup_ref, ref_length, maria_info.dup_key_pos); + } + /* Faster to always update, than to do it based on flag */ + stats.update_time= maria_info.update_time; + stats.auto_increment_value= maria_info.auto_increment; + + return 0; +} + + +int ha_maria::extra(enum ha_extra_function operation) +{ + if ((specialflag & SPECIAL_SAFE_MODE) && operation == HA_EXTRA_KEYREAD) + return 0; +#ifdef NOT_USED + if (operation == HA_EXTRA_MMAP && !opt_maria_use_mmap) + return 0; +#endif + + /* + We have to set file->trn here because in some cases we call + extern_lock(F_UNLOCK) (which resets file->trn) followed by maria_close() + without calling commit/rollback in between. If file->trn is not set + we can't remove file->share from the transaction list in the extra() call. + */ + + if (!file->trn && + (operation == HA_EXTRA_PREPARE_FOR_DROP || + operation == HA_EXTRA_PREPARE_FOR_RENAME)) + { + THD *thd= table->in_use; + TRN *trn= THD_TRN; + _ma_set_trn_for_table(file, trn); + } + return maria_extra(file, operation, 0); +} + +int ha_maria::reset(void) +{ + return maria_reset(file); +} + +/* To be used with WRITE_CACHE and EXTRA_CACHE */ + +int ha_maria::extra_opt(enum ha_extra_function operation, ulong cache_size) +{ + if ((specialflag & SPECIAL_SAFE_MODE) && operation == HA_EXTRA_WRITE_CACHE) + return 0; + return maria_extra(file, operation, (void*) &cache_size); +} + + +int ha_maria::delete_all_rows() +{ + THD *thd= current_thd; + (void) translog_log_debug_info(file->trn, LOGREC_DEBUG_INFO_QUERY, + (uchar*) thd->query, thd->query_length); + if (file->s->now_transactional && + ((table->in_use->options & (OPTION_NOT_AUTOCOMMIT | OPTION_BEGIN)) || + table->in_use->locked_tables)) + { + /* + We are not in autocommit mode or user have done LOCK TABLES. + We must do the delete row by row to be able to rollback the command + */ + return HA_ERR_WRONG_COMMAND; + } + return maria_delete_all_rows(file); +} + + +int ha_maria::delete_table(const char *name) +{ + THD *thd= current_thd; + (void) translog_log_debug_info(0, LOGREC_DEBUG_INFO_QUERY, + (uchar*) thd->query, thd->query_length); + return maria_delete_table(name); +} + +int ha_maria::external_lock(THD *thd, int lock_type) +{ + TRN *trn= THD_TRN; + DBUG_ENTER("ha_maria::external_lock"); + /* + We don't test now_transactional because it may vary between lock/unlock + and thus confuse our reference counting. + It is critical to skip non-transactional tables: user-visible temporary + tables get an external_lock() when read/written for the first time, but no + corresponding unlock (they just stay locked and are later dropped while + locked); if a tmp table was transactional, "SELECT FROM non_tmp, tmp" + would never commit as its "locked_tables" count would stay 1. + When Maria has has_transactions()==TRUE, open_temporary_table() + (sql_base.cc) will use TRANSACTIONAL_TMP_TABLE and thus the + external_lock(F_UNLCK) will happen and we can then allow the user to + create transactional temporary tables. + */ + if (file->s->base.born_transactional) + { + /* Transactional table */ + if (lock_type != F_UNLCK) + { + /* Start of new statement */ + if (!trn) /* no transaction yet - open it now */ + { + trn= trnman_new_trn(& thd->transaction.wt); + if (unlikely(!trn)) + DBUG_RETURN(HA_ERR_OUT_OF_MEM); + THD_TRN= trn; + if (thd->options & (OPTION_NOT_AUTOCOMMIT | OPTION_BEGIN)) + trans_register_ha(thd, TRUE, maria_hton); + } + _ma_set_trn_for_table(file, trn); + if (!trnman_increment_locked_tables(trn)) + { + trans_register_ha(thd, FALSE, maria_hton); + trnman_new_statement(trn); + } + + if (!file->s->lock_key_trees) // If we don't use versioning + { + /* + We come here in the following cases: + - The table is a temporary table + - It's a table which is crash safe but not yet versioned, for + example a table with fulltext or rtree keys + + Set the current state to point to save_state so that the + block_format code don't count the same record twice. + Copy also the current state. This may have been wrong if the + same file was used several times in the last statement + */ + file->state= file->state_start; + *file->state= file->s->state.state; + } + + if (!thd->transaction.on) + { + /* + No need to log REDOs/UNDOs. If this is an internal temporary table + which will be renamed to a permanent table (like in ALTER TABLE), + the rename happens after unlocking so will be durable (and the table + will get its create_rename_lsn). + Note: if we wanted to enable users to have an old backup and apply + tons of archived logs to roll-forward, we could then not disable + REDOs/UNDOs in this case. + */ + DBUG_PRINT("info", ("Disabling logging for table")); + _ma_tmp_disable_logging_for_table(file, TRUE); + } +#ifdef EXTRA_DEBUG + if (lock_type == F_WRLCK && + ! (trnman_get_flags(trn) & TRN_STATE_INFO_LOGGED)) + { + trnman_set_flags(trn, trnman_get_flags(trn) | TRN_STATE_INFO_LOGGED | + TRN_STATE_TABLES_CAN_CHANGE); + (void) translog_log_debug_info(trn, LOGREC_DEBUG_INFO_QUERY, + (uchar*) thd->query, thd->query_length); + } +#endif + } + else + { + /* End of transaction */ + + /* + We always re-enable, don't rely on thd->transaction.on as it is + sometimes reset to true after unlocking (see mysql_truncate() for a + partitioned table based on Maria). + Note that we can come here without having an exclusive lock on the + table, for example in this case: + external_lock(F_(WR|RD)LCK); thr_lock() which fails due to lock + abortion; external_lock(F_UNLCK). Fortunately, the re-enabling happens + only if we were the thread which disabled logging. + */ + if (_ma_reenable_logging_for_table(file, TRUE)) + DBUG_RETURN(1); + /** @todo zero file->trn also in commit and rollback */ + _ma_set_trn_for_table(file, NULL); // Safety + /* + Ensure that file->state points to the current number of rows. This + is needed if someone calls maria_info() without first doing an + external lock of the table + */ + file->state= &file->s->state.state; + if (trn) + { + if (trnman_has_locked_tables(trn) && + !trnman_decrement_locked_tables(trn)) + { + /* + OK should not have been sent to client yet (ACID). + This is a bit excessive, ACID requires this only if there are some + changes to commit (rollback shouldn't be tested). + */ + DBUG_ASSERT(!thd->main_da.is_sent || + thd->killed == THD::KILL_CONNECTION); + /* autocommit ? rollback a transaction */ +#ifdef MARIA_CANNOT_ROLLBACK + if (ma_commit(trn)) + DBUG_RETURN(1); + THD_TRN= 0; +#else + if (!(thd->options & (OPTION_NOT_AUTOCOMMIT | OPTION_BEGIN))) + { + trnman_rollback_trn(trn); + DBUG_PRINT("info", ("THD_TRN set to 0x0")); + THD_TRN= 0; + } +#endif + } + trnman_set_flags(trn, trnman_get_flags(trn) & ~ TRN_STATE_INFO_LOGGED); + } + } + } /* if transactional table */ + DBUG_RETURN(maria_lock_database(file, !table->s->tmp_table ? + lock_type : ((lock_type == F_UNLCK) ? + F_UNLCK : F_EXTRA_LCK))); +} + +int ha_maria::start_stmt(THD *thd, thr_lock_type lock_type) +{ + TRN *trn; + if (file->s->base.born_transactional) + { + trn= THD_TRN; + DBUG_ASSERT(trn); // this may be called only after external_lock() + DBUG_ASSERT(trnman_has_locked_tables(trn)); + DBUG_ASSERT(lock_type != TL_UNLOCK); + DBUG_ASSERT(file->trn == trn); + + /* + If there was an implicit commit under this LOCK TABLES by a previous + statement (like a DDL), at least if that previous statement was about a + different ha_maria than 'this' then this->file->trn is a stale + pointer. We fix it: + */ + _ma_set_trn_for_table(file, trn); + /* + As external_lock() was already called, don't increment locked_tables. + Note that we call the function below possibly several times when + statement starts (once per table). This is ok as long as that function + does cheap operations. Otherwise, we will need to do it only on first + call to start_stmt(). + */ + trnman_new_statement(trn); + +#ifdef EXTRA_DEBUG + if (!(trnman_get_flags(trn) & TRN_STATE_INFO_LOGGED) && + trnman_get_flags(trn) & TRN_STATE_TABLES_CAN_CHANGE) + { + trnman_set_flags(trn, trnman_get_flags(trn) | TRN_STATE_INFO_LOGGED); + (void) translog_log_debug_info(trn, LOGREC_DEBUG_INFO_QUERY, + (uchar*) thd->query, thd->query_length); + } +#endif + } + return 0; +} + + +/** + Performs an implicit commit of the Maria transaction and creates a new + one. + + This can be considered a hack. When Maria loses HA_NO_TRANSACTIONS it will + be participant in the connection's transaction and so the implicit commits + (ha_commit()) (like in end_active_trans()) will do the implicit commit + without need to call this function which can then be removed. + + @param thd THD object + @param new_trn if a new transaction should be created; a new + transaction is not needed when we know that the + tables will be unlocked very soon. +*/ + +int ha_maria::implicit_commit(THD *thd, bool new_trn) +{ +#ifndef MARIA_CANNOT_ROLLBACK +#error this method should be removed +#endif + TRN *trn; + int error= 0; + TABLE *table; + DBUG_ENTER("ha_maria::implicit_commit"); + if (!new_trn && thd->locked_tables) + { + /* + "we are under LOCK TABLES" <=> "we shouldn't commit". + As thd->locked_tables is true, we are either under LOCK TABLES, or in + prelocking; prelocking can be under LOCK TABLES, or not (and in this + latter case only we should commit). + Note that we come here only at the end of the top statement + (dispatch_command()), we are never committing inside a sub-statement./ + */ + enum prelocked_mode_type prelocked_mode= thd->prelocked_mode; + if ((prelocked_mode == NON_PRELOCKED) || + (prelocked_mode == PRELOCKED_UNDER_LOCK_TABLES)) + { + DBUG_PRINT("info", ("locked_tables, skipping")); + DBUG_RETURN(0); + } + } + if ((trn= THD_TRN) != NULL) + { + uint locked_tables= trnman_has_locked_tables(trn); + if (unlikely(ma_commit(trn))) + error= 1; + if (!new_trn) + { + THD_TRN= NULL; + goto end; + } + /* + We need to create a new transaction and put it in THD_TRN. Indeed, + tables may be under LOCK TABLES, and so they will start the next + statement assuming they have a trn (see ha_maria::start_stmt()). + */ + trn= trnman_new_trn(& thd->transaction.wt); + /* This is just a commit, tables stay locked if they were: */ + trnman_reset_locked_tables(trn, locked_tables); + THD_TRN= trn; + if (unlikely(trn == NULL)) + error= HA_ERR_OUT_OF_MEM; + + /* + Move all locked tables to the new transaction + We must do it here as otherwise file->thd and file->state may be + stale pointers. We can't do this in start_stmt() as we don't know + when we should call _ma_setup_live_state() and in some cases, like + in check table, we use the table without calling start_stmt(). + */ + for (table=thd->open_tables; table ; table=table->next) + { + if (table->db_stat && table->file->ht == maria_hton) + { + MARIA_HA *handler= ((ha_maria*) table->file)->file; + if (handler->s->base.born_transactional) + { + _ma_set_trn_for_table(handler, trn); + /* If handler uses versioning */ + if (handler->s->lock_key_trees) + { + if (_ma_setup_live_state(handler)) + error= HA_ERR_OUT_OF_MEM; + } + } + } + } + } +end: + DBUG_RETURN(error); +} + + +THR_LOCK_DATA **ha_maria::store_lock(THD *thd, + THR_LOCK_DATA **to, + enum thr_lock_type lock_type) +{ + /* Test if we can fix test below */ + DBUG_ASSERT(lock_type != TL_UNLOCK && + (lock_type == TL_IGNORE || file->lock.type == TL_UNLOCK)); + if (lock_type != TL_IGNORE && file->lock.type == TL_UNLOCK) + { + const enum enum_sql_command sql_command= thd->lex->sql_command; + /* + We have to disable concurrent inserts for INSERT ... SELECT or + INSERT/UPDATE/DELETE with sub queries if we are using statement based + logging. We take the safe route here and disable this for all commands + that only does reading that are not SELECT. + */ + if (lock_type <= TL_READ_HIGH_PRIORITY && + !thd->current_stmt_binlog_row_based && + (sql_command != SQLCOM_SELECT && + sql_command != SQLCOM_LOCK_TABLES) && + (thd->options & OPTION_BIN_LOG) && + mysql_bin_log.is_open()) + lock_type= TL_READ_NO_INSERT; + else if (lock_type == TL_WRITE_CONCURRENT_INSERT) + { + const enum enum_duplicates duplicates= thd->lex->duplicates; + /* + Explanation for the 3 conditions below, in order: + + - Bulk insert may use repair, which will cause problems if other + threads try to read/insert to the table: disable versioning. + Note that our read of file->state->records is incorrect, as such + variable may have changed when we come to start_bulk_insert() (worse + case: we see != 0 so allow versioning, start_bulk_insert() sees 0 and + uses repair). This is prevented because start_bulk_insert() will not + try repair if we enabled versioning. + - INSERT SELECT ON DUPLICATE KEY UPDATE comes here with + TL_WRITE_CONCURRENT_INSERT but shouldn't because it can do + update/delete of a row and versioning doesn't support that + - same for LOAD DATA CONCURRENT REPLACE. + */ + if ((file->state->records == 0) || + (sql_command == SQLCOM_INSERT_SELECT && duplicates == DUP_UPDATE) || + (sql_command == SQLCOM_LOAD && duplicates == DUP_REPLACE)) + lock_type= TL_WRITE; + } + file->lock.type= lock_type; + } + *to++= &file->lock; + return to; +} + + +void ha_maria::update_create_info(HA_CREATE_INFO *create_info) +{ + ha_maria::info(HA_STATUS_AUTO | HA_STATUS_CONST); + if (!(create_info->used_fields & HA_CREATE_USED_AUTO)) + { + create_info->auto_increment_value= stats.auto_increment_value; + } + create_info->data_file_name= data_file_name; + create_info->index_file_name= index_file_name; + /* We need to restore the row type as Maria can change it */ + if (create_info->row_type != ROW_TYPE_DEFAULT && + !(create_info->used_fields & HA_CREATE_USED_ROW_FORMAT)) + create_info->row_type= get_row_type(); + /* + Show always page checksums, as this can be forced with + maria_page_checksums variable + */ + if (create_info->page_checksum == HA_CHOICE_UNDEF) + create_info->page_checksum= + (file->s->options & HA_OPTION_PAGE_CHECKSUM) ? HA_CHOICE_YES : + HA_CHOICE_NO; +} + + +enum row_type ha_maria::get_row_type() const +{ + switch (file->s->data_file_type) { + case STATIC_RECORD: return ROW_TYPE_FIXED; + case DYNAMIC_RECORD: return ROW_TYPE_DYNAMIC; + case BLOCK_RECORD: return ROW_TYPE_PAGE; + case COMPRESSED_RECORD: return ROW_TYPE_COMPRESSED; + default: return ROW_TYPE_NOT_USED; + } +} + + +static enum data_file_type maria_row_type(HA_CREATE_INFO *info) +{ + if (info->transactional == HA_CHOICE_YES) + return BLOCK_RECORD; + switch (info->row_type) { + case ROW_TYPE_FIXED: return STATIC_RECORD; + case ROW_TYPE_DYNAMIC: return DYNAMIC_RECORD; + default: return BLOCK_RECORD; + } +} + + +int ha_maria::create(const char *name, register TABLE *table_arg, + HA_CREATE_INFO *ha_create_info) +{ + int error; + uint create_flags= 0, record_count, i; + char buff[FN_REFLEN]; + MARIA_KEYDEF *keydef; + MARIA_COLUMNDEF *recinfo; + MARIA_CREATE_INFO create_info; + TABLE_SHARE *share= table_arg->s; + uint options= share->db_options_in_use; + enum data_file_type row_type; + THD *thd= current_thd; + DBUG_ENTER("ha_maria::create"); + + for (i= 0; i < share->keys; i++) + { + if (table_arg->key_info[i].flags & HA_USES_PARSER) + { + create_flags|= HA_CREATE_RELIES_ON_SQL_LAYER; + break; + } + } + /* Note: BLOCK_RECORD is used if table is transactional */ + row_type= maria_row_type(ha_create_info); + if (ha_create_info->transactional == HA_CHOICE_YES && + ha_create_info->row_type != ROW_TYPE_PAGE && + ha_create_info->row_type != ROW_TYPE_NOT_USED && + ha_create_info->row_type != ROW_TYPE_DEFAULT) + push_warning(current_thd, MYSQL_ERROR::WARN_LEVEL_NOTE, + ER_ILLEGAL_HA_CREATE_OPTION, + "Row format set to PAGE because of TRANSACTIONAL=1 option"); + + bzero((char*) &create_info, sizeof(create_info)); + if ((error= table2maria(table_arg, row_type, &keydef, &recinfo, + &record_count, &create_info))) + DBUG_RETURN(error); /* purecov: inspected */ + create_info.max_rows= share->max_rows; + create_info.reloc_rows= share->min_rows; + create_info.with_auto_increment= share->next_number_key_offset == 0; + create_info.auto_increment= (ha_create_info->auto_increment_value ? + ha_create_info->auto_increment_value -1 : + (ulonglong) 0); + create_info.data_file_length= ((ulonglong) share->max_rows * + share->avg_row_length); + create_info.data_file_name= ha_create_info->data_file_name; + create_info.index_file_name= ha_create_info->index_file_name; + create_info.language= share->table_charset->number; + + /* + Table is transactional: + - If the user specify that table is transactional (in this case + row type is forced to BLOCK_RECORD) + - If they specify BLOCK_RECORD without specifying transactional behaviour + + Shouldn't this test be pushed down to maria_create()? Because currently, + ma_test1 -T crashes: it creates a table with DYNAMIC_RECORD but has + born_transactional==1, which confuses some recovery-related code. + */ + create_info.transactional= (row_type == BLOCK_RECORD && + ha_create_info->transactional != HA_CHOICE_NO); + + if (ha_create_info->options & HA_LEX_CREATE_TMP_TABLE) + create_flags|= HA_CREATE_TMP_TABLE; + if (ha_create_info->options & HA_CREATE_KEEP_FILES) + create_flags|= HA_CREATE_KEEP_FILES; + if (options & HA_OPTION_PACK_RECORD) + create_flags|= HA_PACK_RECORD; + if (options & HA_OPTION_CHECKSUM) + create_flags|= HA_CREATE_CHECKSUM; + if (options & HA_OPTION_DELAY_KEY_WRITE) + create_flags|= HA_CREATE_DELAY_KEY_WRITE; + if ((ha_create_info->page_checksum == HA_CHOICE_UNDEF && + maria_page_checksums) || + ha_create_info->page_checksum == HA_CHOICE_YES) + create_flags|= HA_CREATE_PAGE_CHECKSUM; + + (void) translog_log_debug_info(0, LOGREC_DEBUG_INFO_QUERY, + (uchar*) thd->query, thd->query_length); + + /* TODO: Check that the following fn_format is really needed */ + error= + maria_create(fn_format(buff, name, "", "", + MY_UNPACK_FILENAME | MY_APPEND_EXT), + row_type, share->keys, keydef, + record_count, recinfo, + 0, (MARIA_UNIQUEDEF *) 0, + &create_info, create_flags); + + my_free((uchar*) recinfo, MYF(0)); + DBUG_RETURN(error); +} + + +int ha_maria::rename_table(const char *from, const char *to) +{ + THD *thd= current_thd; + (void) translog_log_debug_info(0, LOGREC_DEBUG_INFO_QUERY, + (uchar*) thd->query, thd->query_length); + return maria_rename(from, to); +} + + +void ha_maria::get_auto_increment(ulonglong offset, ulonglong increment, + ulonglong nb_desired_values, + ulonglong *first_value, + ulonglong *nb_reserved_values) +{ + ulonglong nr; + int error; + uchar key[HA_MAX_KEY_LENGTH]; + + if (!table->s->next_number_key_offset) + { // Autoincrement at key-start + ha_maria::info(HA_STATUS_AUTO); + *first_value= stats.auto_increment_value; + /* Maria has only table-level lock for now, so reserves to +inf */ + *nb_reserved_values= ULONGLONG_MAX; + return; + } + + /* it's safe to call the following if bulk_insert isn't on */ + maria_flush_bulk_insert(file, table->s->next_number_index); + + (void) extra(HA_EXTRA_KEYREAD); + key_copy(key, table->record[0], + table->key_info + table->s->next_number_index, + table->s->next_number_key_offset); + error= maria_rkey(file, table->record[1], (int) table->s->next_number_index, + key, make_prev_keypart_map(table->s->next_number_keypart), + HA_READ_PREFIX_LAST); + if (error) + nr= 1; + else + { + /* Get data from record[1] */ + nr= ((ulonglong) table->next_number_field-> + val_int_offset(table->s->rec_buff_length) + 1); + } + extra(HA_EXTRA_NO_KEYREAD); + *first_value= nr; + /* + 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; +} + + +/* + Find out how many rows there is in the given range + + SYNOPSIS + records_in_range() + inx Index to use + min_key Start of range. Null pointer if from first key + max_key End of range. Null pointer if to last key + + NOTES + min_key.flag can have one of the following values: + HA_READ_KEY_EXACT Include the key in the range + HA_READ_AFTER_KEY Don't include key in range + + max_key.flag can have one of the following values: + HA_READ_BEFORE_KEY Don't include key in range + HA_READ_AFTER_KEY Include all 'end_key' values in the range + + RETURN + HA_POS_ERROR Something is wrong with the index tree. + 0 There is no matching keys in the given range + number > 0 There is approximately 'number' matching rows in + the range. +*/ + +ha_rows ha_maria::records_in_range(uint inx, key_range *min_key, + key_range *max_key) +{ + return (ha_rows) maria_records_in_range(file, (int) inx, min_key, max_key); +} + + +int ha_maria::ft_read(uchar * buf) +{ + int error; + + if (!ft_handler) + return -1; + + thread_safe_increment(table->in_use->status_var.ha_read_next_count, + &LOCK_status); // why ? + + error= ft_handler->please->read_next(ft_handler, (char*) buf); + + table->status= error ? STATUS_NOT_FOUND : 0; + return error; +} + + +uint ha_maria::checksum() const +{ + return (uint) file->state->checksum; +} + + +bool ha_maria::check_if_incompatible_data(HA_CREATE_INFO *create_info, + uint table_changes) +{ + DBUG_ENTER("check_if_incompatible_data"); + uint options= table->s->db_options_in_use; + + if (create_info->auto_increment_value != stats.auto_increment_value || + create_info->data_file_name != data_file_name || + create_info->index_file_name != index_file_name || + (maria_row_type(create_info) != data_file_type && + create_info->row_type != ROW_TYPE_DEFAULT) || + table_changes == IS_EQUAL_NO || + (table_changes & IS_EQUAL_PACK_LENGTH)) // Not implemented yet + DBUG_RETURN(COMPATIBLE_DATA_NO); + + if ((options & (HA_OPTION_CHECKSUM | + HA_OPTION_DELAY_KEY_WRITE)) != + (create_info->table_options & (HA_OPTION_CHECKSUM | + HA_OPTION_DELAY_KEY_WRITE))) + DBUG_RETURN(COMPATIBLE_DATA_NO); + DBUG_RETURN(COMPATIBLE_DATA_YES); +} + + +static int maria_hton_panic(handlerton *hton, ha_panic_function flag) +{ + /* If no background checkpoints, we need to do one now */ + return ((checkpoint_interval == 0) ? + ma_checkpoint_execute(CHECKPOINT_FULL, FALSE) : 0) | maria_panic(flag); +} + + +static int maria_commit(handlerton *hton __attribute__ ((unused)), + THD *thd, bool all) +{ + TRN *trn= THD_TRN; + DBUG_ENTER("maria_commit"); + trnman_reset_locked_tables(trn, 0); + trnman_set_flags(trn, trnman_get_flags(trn) & ~TRN_STATE_INFO_LOGGED); + + /* statement or transaction ? */ + if ((thd->options & (OPTION_NOT_AUTOCOMMIT | OPTION_BEGIN)) && !all) + DBUG_RETURN(0); // end of statement + DBUG_PRINT("info", ("THD_TRN set to 0x0")); + THD_TRN= 0; + DBUG_RETURN(ma_commit(trn)); // end of transaction +} + + +static int maria_rollback(handlerton *hton __attribute__ ((unused)), + THD *thd, bool all) +{ + TRN *trn= THD_TRN; + DBUG_ENTER("maria_rollback"); + trnman_reset_locked_tables(trn, 0); + /* statement or transaction ? */ + if ((thd->options & (OPTION_NOT_AUTOCOMMIT | OPTION_BEGIN)) && !all) + { + trnman_rollback_statement(trn); + DBUG_RETURN(0); // end of statement + } + DBUG_PRINT("info", ("THD_TRN set to 0x0")); + THD_TRN= 0; + DBUG_RETURN(trnman_rollback_trn(trn) ? + HA_ERR_OUT_OF_MEM : 0); // end of transaction +} + + + +/** + @brief flush log handler + + @param hton maria handlerton (unused) + + @retval FALSE OK + @retval TRUE Error +*/ + +bool maria_flush_logs(handlerton *hton) +{ + return test(translog_purge_at_flush()); +} + + +#define SHOW_MSG_LEN (FN_REFLEN + 20) +/** + @brief show status handler + + @param hton maria handlerton + @param thd thread handler + @param print print function + @param stat type of status +*/ + +bool maria_show_status(handlerton *hton, + THD *thd, + stat_print_fn *print, + enum ha_stat_type stat) +{ + const LEX_STRING *engine_name= hton_name(hton); + switch (stat) { + case HA_ENGINE_LOGS: + { + TRANSLOG_ADDRESS horizon= translog_get_horizon(); + uint32 last_file= LSN_FILE_NO(horizon); + uint32 first_needed= translog_get_first_needed_file(); + uint32 first_file= translog_get_first_file(horizon); + uint32 i; + const char unknown[]= "unknown"; + const char needed[]= "in use"; + const char unneeded[]= "free"; + char path[FN_REFLEN]; + + if (first_file == 0) + { + const char error[]= "error"; + print(thd, engine_name->str, engine_name->length, + STRING_WITH_LEN(""), error, sizeof(error) - 1); + break; + } + + for (i= first_file; i <= last_file; i++) + { + char *file; + const char *status; + uint length, status_len; + MY_STAT stat_buff, *stat; + const char error[]= "can't stat"; + char object[SHOW_MSG_LEN]; + file= translog_filename_by_fileno(i, path); + if (!(stat= my_stat(file, &stat_buff, MYF(0)))) + { + status= error; + status_len= sizeof(error) - 1; + length= my_snprintf(object, SHOW_MSG_LEN, "Size unknown ; %s", file); + } + else + { + if (first_needed == 0) + { + status= unknown; + status_len= sizeof(unknown) - 1; + } + else if (i < first_needed) + { + status= unneeded; + status_len= sizeof(unneeded) - 1; + } + else + { + status= needed; + status_len= sizeof(needed) - 1; + } + length= my_snprintf(object, SHOW_MSG_LEN, "Size %12lu ; %s", + (ulong) stat->st_size, file); + } + + print(thd, engine_name->str, engine_name->length, + object, length, status, status_len); + } + break; + } + case HA_ENGINE_STATUS: + case HA_ENGINE_MUTEX: + default: + break; + } + return 0; +} + + +/** + Callback to delete all logs in directory. This is lower-level than other + functions in ma_loghandler.c which delete logs, as it does not rely on + translog_init() having been called first. + + @param directory directory where file is + @param filename base name of the file to delete +*/ + +static my_bool translog_callback_delete_all(const char *directory, + const char *filename) +{ + char complete_name[FN_REFLEN]; + fn_format(complete_name, filename, directory, "", MYF(MY_UNPACK_FILENAME)); + return my_delete(complete_name, MYF(MY_WME)); +} + + +/** + Helper function for option maria-force-start-after-recovery-failures. + Deletes logs if too many failures. Otherwise, increments the counter of + failures in the control file. + Notice how this has to be called _before_ translog_init() (if log is + corrupted, translog_init() might crash the server, so we need to remove logs + before). + + @param log_dir directory where logs to be deleted are +*/ + +static int mark_recovery_start(const char* log_dir) +{ + int res; + DBUG_ENTER("mark_recovery_start"); + if (unlikely(maria_recover_options == HA_RECOVER_NONE)) + ma_message_no_user(ME_JUST_WARNING, "Please consider using option" + " --maria-recover[=...] to automatically check and" + " repair tables when logs are removed by option" + " --maria-force-start-after-recovery-failures=#"); + if (recovery_failures >= force_start_after_recovery_failures) + { + /* + Remove logs which cause the problem; keep control file which has + critical info like uuid, max_trid (removing control file may make + correct tables look corrupted!). + */ + char msg[100]; + res= translog_walk_filenames(log_dir, &translog_callback_delete_all); + my_snprintf(msg, sizeof(msg), + "%s logs after %u consecutive failures of" + " recovery from logs", + (res ? "failed to remove some" : "removed all"), + recovery_failures); + ma_message_no_user((res ? 0 : ME_JUST_WARNING), msg); + } + else + res= ma_control_file_write_and_force(last_checkpoint_lsn, last_logno, + max_trid_in_control_file, + recovery_failures + 1); + DBUG_RETURN(res); +} + + +/** + Helper function for option maria-force-start-after-recovery-failures. + Records in the control file that recovery was a success, so that it's not + counted for maria-force-start-after-recovery-failures. +*/ + +static int mark_recovery_success(void) +{ + /* success of recovery, reset recovery_failures: */ + int res; + DBUG_ENTER("mark_recovery_success"); + res= ma_control_file_write_and_force(last_checkpoint_lsn, last_logno, + max_trid_in_control_file, 0); + DBUG_RETURN(res); +} + + +/* + Return 1 if table has changed during the current transaction +*/ + +bool ha_maria::is_changed() const +{ + return file->state->changed; +} + + +static int ha_maria_init(void *p) +{ + int res; + const char *log_dir= maria_data_root; + maria_hton= (handlerton *)p; + maria_hton->state= SHOW_OPTION_YES; + maria_hton->db_type= DB_TYPE_UNKNOWN; + maria_hton->create= maria_create_handler; + maria_hton->panic= maria_hton_panic; + maria_hton->commit= maria_commit; + maria_hton->rollback= maria_rollback; + maria_hton->flush_logs= maria_flush_logs; + maria_hton->show_status= maria_show_status; + /* TODO: decide if we support Maria being used for log tables */ + maria_hton->flags= HTON_CAN_RECREATE | HTON_SUPPORT_LOG_TABLES; + bzero(maria_log_pagecache, sizeof(*maria_log_pagecache)); + maria_tmpdir= &mysql_tmpdir_list; /* For REDO */ + res= maria_init() || ma_control_file_open(TRUE, TRUE) || + ((force_start_after_recovery_failures != 0) && + mark_recovery_start(log_dir)) || + !init_pagecache(maria_pagecache, + (size_t) pagecache_buffer_size, pagecache_division_limit, + pagecache_age_threshold, maria_block_size, 0) || + !init_pagecache(maria_log_pagecache, + TRANSLOG_PAGECACHE_SIZE, 0, 0, + TRANSLOG_PAGE_SIZE, 0) || + translog_init(maria_data_root, log_file_size, + MYSQL_VERSION_ID, server_id, maria_log_pagecache, + TRANSLOG_DEFAULT_FLAGS, 0) || + maria_recovery_from_log() || + ((force_start_after_recovery_failures != 0) && mark_recovery_success()) || + ma_checkpoint_init(checkpoint_interval); + maria_multi_threaded= maria_in_ha_maria= TRUE; + +#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 */ + maria_test_invalid_symlink= test_if_data_home_dir; +#endif + return res ? HA_ERR_INITIALIZATION : 0; +} + + +#ifdef HAVE_QUERY_CACHE +/** + @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, currently 0 + @param[out] engine_data Engine data will be set to 0. + + @note Despite the name of this function, it is used to check each statement + before it is cached and not to register a table or callback function. + + @see handler::register_query_cache_table + + @return The error code. The engine_data and engine_callback will be set to 0. + @retval TRUE Success + @retval FALSE An error occurred +*/ + +my_bool ha_maria::register_query_cache_table(THD *thd, char *table_name, + uint table_name_len, + qc_engine_callback + *engine_callback, + ulonglong *engine_data) +{ + ulonglong actual_data_file_length; + ulonglong current_data_file_length; + DBUG_ENTER("ha_maria::register_query_cache_table"); + + /* + No call back function is needed to determine if a cached statement + is valid or not. + */ + *engine_callback= 0; + + /* + No engine data is needed. + */ + *engine_data= 0; + + /* + If a concurrent INSERT has happened just before the currently processed + SELECT statement, the total size of the table is unknown. + + To determine if the table size is known, the current thread's snap shot of + the table size with the actual table size are compared. + + If the table size is unknown the SELECT statement can't be cached. + */ + + /* + POSIX visibility rules specify that "2. Whatever memory values a + thread can see when it unlocks a mutex <...> can also be seen by any + thread that later locks the same mutex". In this particular case, + concurrent insert thread had modified the data_file_length in + MYISAM_SHARE before it has unlocked (or even locked) + structure_guard_mutex. So, here we're guaranteed to see at least that + value after we've locked the same mutex. We can see a later value + (modified by some other thread) though, but it's ok, as we only want + to know if the variable was changed, the actual new value doesn't matter + */ + actual_data_file_length= file->s->state.state.data_file_length; + current_data_file_length= file->state->data_file_length; + + /* Return whether is ok to try to cache current statement. */ + DBUG_RETURN(!(file->s->non_transactional_concurrent_insert && + current_data_file_length != actual_data_file_length)); +} +#endif + +static struct st_mysql_sys_var* system_variables[]= { + MYSQL_SYSVAR(block_size), + MYSQL_SYSVAR(checkpoint_interval), + MYSQL_SYSVAR(force_start_after_recovery_failures), + MYSQL_SYSVAR(page_checksum), + MYSQL_SYSVAR(log_dir_path), + MYSQL_SYSVAR(log_file_size), + MYSQL_SYSVAR(log_purge_type), + MYSQL_SYSVAR(max_sort_file_size), + MYSQL_SYSVAR(pagecache_age_threshold), + MYSQL_SYSVAR(pagecache_buffer_size), + MYSQL_SYSVAR(pagecache_division_limit), + MYSQL_SYSVAR(recover), + MYSQL_SYSVAR(repair_threads), + MYSQL_SYSVAR(sort_buffer_size), + MYSQL_SYSVAR(stats_method), + MYSQL_SYSVAR(sync_log_dir), + MYSQL_SYSVAR(used_for_temp_tables), + NULL +}; + + +/** + @brief Updates the checkpoint interval and restarts the background thread. +*/ + +static void update_checkpoint_interval(MYSQL_THD thd, + struct st_mysql_sys_var *var, + void *var_ptr, const void *save) +{ + ma_checkpoint_end(); + ma_checkpoint_init(*(ulong *)var_ptr= (ulong)(*(long *)save)); +} + +/** + @brief Updates the transaction log file limit. +*/ + +static void update_log_file_size(MYSQL_THD thd, + struct st_mysql_sys_var *var, + void *var_ptr, const void *save) +{ + uint32 size= (uint32)((ulong)(*(long *)save)); + translog_set_file_size(size); + *(ulong *)var_ptr= size; +} + + +static SHOW_VAR status_variables[]= { + {"Maria_pagecache_blocks_not_flushed", (char*) &maria_pagecache_var.global_blocks_changed, SHOW_LONG_NOFLUSH}, + {"Maria_pagecache_blocks_unused", (char*) &maria_pagecache_var.blocks_unused, SHOW_LONG_NOFLUSH}, + {"Maria_pagecache_blocks_used", (char*) &maria_pagecache_var.blocks_used, SHOW_LONG_NOFLUSH}, + {"Maria_pagecache_read_requests", (char*) &maria_pagecache_var.global_cache_r_requests, SHOW_LONGLONG}, + {"Maria_pagecache_reads", (char*) &maria_pagecache_var.global_cache_read, SHOW_LONGLONG}, + {"Maria_pagecache_write_requests", (char*) &maria_pagecache_var.global_cache_w_requests, SHOW_LONGLONG}, + {"Maria_pagecache_writes", (char*) &maria_pagecache_var.global_cache_write, SHOW_LONGLONG}, + {NullS, NullS, SHOW_LONG} +}; + +struct st_mysql_storage_engine maria_storage_engine= +{ MYSQL_HANDLERTON_INTERFACE_VERSION }; + +mysql_declare_plugin(maria) +{ + MYSQL_STORAGE_ENGINE_PLUGIN, + &maria_storage_engine, + "MARIA", + "MySQL AB", + "Crash-safe tables with MyISAM heritage", + PLUGIN_LICENSE_GPL, + ha_maria_init, /* Plugin Init */ + NULL, /* Plugin Deinit */ + 0x0100, /* 1.0 */ + status_variables, /* status variables */ + system_variables, /* system variables */ + NULL +} +mysql_declare_plugin_end; diff --git a/storage/maria/ha_maria.h b/storage/maria/ha_maria.h new file mode 100644 index 00000000000..23fb74f1c27 --- /dev/null +++ b/storage/maria/ha_maria.h @@ -0,0 +1,167 @@ +/* Copyright (C) 2006,2004 MySQL AB & MySQL Finland AB & TCX DataKonsult 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_INTERFACE +#pragma interface /* gcc class implementation */ +#endif + +/* class for the maria handler */ + +#include <maria.h> + +#define HA_RECOVER_NONE 0 /* No automatic recover */ +#define HA_RECOVER_DEFAULT 1 /* Automatic recover active */ +#define HA_RECOVER_BACKUP 2 /* Make a backupfile on recover */ +#define HA_RECOVER_FORCE 4 /* Recover even if we loose rows */ +#define HA_RECOVER_QUICK 8 /* Don't check rows in data file */ + +extern ulong maria_sort_buffer_size; +extern TYPELIB maria_recover_typelib; +extern ulong maria_recover_options; + +class ha_maria :public handler +{ + MARIA_HA *file; + ulonglong int_table_flags; + MARIA_RECORD_POS remember_pos; + char *data_file_name, *index_file_name; + enum data_file_type data_file_type; + bool can_enable_indexes; + /** + If a transactional table is doing bulk insert with a single + UNDO_BULK_INSERT with/without repair. + */ + uint8 bulk_insert_single_undo; + int repair(THD * thd, HA_CHECK *param, bool optimize); + int zerofill(THD * thd, HA_CHECK_OPT *check_opt); + +public: + ha_maria(handlerton *hton, TABLE_SHARE * table_arg); + ~ha_maria() {} + handler *clone(MEM_ROOT *mem_root); + const char *table_type() const + { return "MARIA"; } + const char *index_type(uint key_number); + const char **bas_ext() const; + ulonglong table_flags() const + { return int_table_flags; } + ulong index_flags(uint inx, uint part, bool all_parts) const + { + return ((table_share->key_info[inx].algorithm == HA_KEY_ALG_FULLTEXT) ? + 0 : HA_READ_NEXT | HA_READ_PREV | HA_READ_RANGE | + HA_READ_ORDER | HA_KEYREAD_ONLY); + } + uint max_supported_keys() const + { return MARIA_MAX_KEY; } + uint max_supported_key_length() const; + uint max_supported_key_part_length() const + { return max_supported_key_length(); } + enum row_type get_row_type() const; + uint checksum() const; + virtual double scan_time(); + + 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_read_map(uchar * buf, const uchar * key, key_part_map keypart_map, + enum ha_rkey_function find_flag); + int index_read_idx_map(uchar * buf, uint idx, const uchar * key, + key_part_map keypart_map, + enum ha_rkey_function find_flag); + int index_read_last_map(uchar * buf, const uchar * key, + key_part_map keypart_map); + int index_next(uchar * buf); + int index_prev(uchar * buf); + int index_first(uchar * buf); + int index_last(uchar * buf); + int index_next_same(uchar * buf, const uchar * key, uint keylen); + int ft_init() + { + if (!ft_handler) + return 1; + ft_handler->please->reinit_search(ft_handler); + return 0; + } + FT_INFO *ft_init_ext(uint flags, uint inx, String * key) + { + return maria_ft_init_search(flags, file, inx, + (uchar *) key->ptr(), key->length(), + key->charset(), table->record[0]); + } + int ft_read(uchar * buf); + int rnd_init(bool scan); + int rnd_end(void); + int rnd_next(uchar * buf); + int rnd_pos(uchar * buf, uchar * pos); + int remember_rnd_pos(); + int restart_rnd_next(uchar * buf); + void position(const uchar * record); + int info(uint); + int info(uint, my_bool); + int extra(enum ha_extra_function operation); + int extra_opt(enum ha_extra_function operation, ulong cache_size); + int reset(void); + int external_lock(THD * thd, int lock_type); + int start_stmt(THD *thd, thr_lock_type lock_type); + int delete_all_rows(void); + int disable_indexes(uint mode); + int enable_indexes(uint mode); + int indexes_are_disabled(void); + void start_bulk_insert(ha_rows rows); + int end_bulk_insert(bool abort); + ha_rows records_in_range(uint inx, key_range * min_key, key_range * max_key); + void update_create_info(HA_CREATE_INFO * create_info); + int create(const char *name, TABLE * form, HA_CREATE_INFO * create_info); + THR_LOCK_DATA **store_lock(THD * thd, THR_LOCK_DATA ** to, + enum thr_lock_type lock_type); + virtual void get_auto_increment(ulonglong offset, ulonglong increment, + ulonglong nb_desired_values, + ulonglong *first_value, + ulonglong *nb_reserved_values); + int rename_table(const char *from, const char *to); + int delete_table(const char *name); + int check(THD * thd, HA_CHECK_OPT * check_opt); + int analyze(THD * thd, HA_CHECK_OPT * check_opt); + int repair(THD * thd, HA_CHECK_OPT * check_opt); + bool check_and_repair(THD * thd); + bool is_crashed() const; + bool is_changed() const; + bool auto_repair() const { return 1; } + int optimize(THD * thd, HA_CHECK_OPT * check_opt); + int restore(THD * thd, HA_CHECK_OPT * check_opt); + int backup(THD * thd, HA_CHECK_OPT * check_opt); + int assign_to_keycache(THD * thd, HA_CHECK_OPT * check_opt); + int preload_keys(THD * thd, HA_CHECK_OPT * check_opt); + bool check_if_incompatible_data(HA_CREATE_INFO * info, uint table_changes); +#ifdef HAVE_REPLICATION + int dump(THD * thd, int fd); + int net_read_dump(NET * net); +#endif +#ifdef HAVE_QUERY_CACHE + my_bool register_query_cache_table(THD *thd, char *table_key, + uint key_length, + qc_engine_callback + *engine_callback, + ulonglong *engine_data); +#endif + MARIA_HA *file_ptr(void) + { + return file; + } + static int implicit_commit(THD *thd, bool new_trn); +}; diff --git a/storage/maria/lockman.c b/storage/maria/lockman.c new file mode 100644 index 00000000000..e7f3c81b0fd --- /dev/null +++ b/storage/maria/lockman.c @@ -0,0 +1,785 @@ +/* QQ: TODO - allocate everything from dynarrays !!! (benchmark) */ +/* QQ: TODO instant duration locks */ +/* QQ: #warning automatically place S instead of LS if possible */ + +/* Copyright (C) 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 */ + +/* + Generic Lock Manager + + Lock manager handles locks on "resources", a resource must be uniquely + identified by a 64-bit number. Lock manager itself does not imply + anything about the nature of a resource - it can be a row, a table, a + database, or just anything. + + Locks belong to "lock owners". A Lock owner is uniquely identified by a + 16-bit number. A function loid2lo must be provided by the application + that takes such a number as an argument and returns a LOCK_OWNER + structure. + + Lock levels are completely defined by three tables. Lock compatibility + matrix specifies which locks can be held at the same time on a resource. + Lock combining matrix specifies what lock level has the same behaviour as + a pair of two locks of given levels. getlock_result matrix simplifies + intention locking and lock escalation for an application, basically it + defines which locks are intention locks and which locks are "loose" + locks. It is only used to provide better diagnostics for the + application, lock manager itself does not differentiate between normal, + intention, and loose locks. + + Internally lock manager is based on a lock-free hash, see lf_hash.c for + details. All locks are stored in a hash, with a resource id as a search + key, so all locks for the same resource will be considered collisions and + will be put in a one (lock-free) linked list. The main lock-handling + logic is in the inner loop that searches for a lock in such a linked + list - lockfind(). + + This works as follows. Locks generally are added to the end of the list + (with one exception, see below). When scanning the list it is always + possible to determine what locks are granted (active) and what locks are + waiting - first lock is obviously active, the second is active if it's + compatible with the first, and so on, a lock is active if it's compatible + with all previous locks and all locks before it are also active. + To calculate the "compatible with all previous locks" all locks are + accumulated in prev_lock variable using lock_combining_matrix. + + Lock upgrades: when a thread that has a lock on a given resource, + requests a new lock on the same resource and the old lock is not enough + to satisfy new lock requirements (which is defined by + lock_combining_matrix[old_lock][new_lock] != old_lock), a new lock is + placed in the list. Depending on other locks it is immediately active or + it will wait for other locks. Here's an exception to "locks are added + to the end" rule - upgraded locks are added after the last active lock + but before all waiting locks. Old lock (the one we upgraded from) is + not removed from the list, indeed it may be needed if the new lock was + in a savepoint that gets rolled back. So old lock is marked as "ignored" + (IGNORE_ME flag). New lock gets an UPGRADED flag. + + Loose locks add an important exception to the above. Loose locks do not + always commute with other locks. In the list IX-LS both locks are active, + while in the LS-IX list only the first lock is active. This creates a + problem in lock upgrades. If the list was IX-LS and the owner of the + first lock wants to place LS lock (which can be immediately granted), the + IX lock is upgraded to LSIX and the list becomes IX-LS-LSIX, which, + according to the lock compatibility matrix means that the last lock is + waiting - of course it all happened because IX and LS were swapped and + they don't commute. To work around this there's ACTIVE flag which is set + in every lock that never waited (was placed active), and this flag + overrides "compatible with all previous locks" rule. + + When a lock is placed to the end of the list it's either compatible with + all locks and all locks are active - new lock becomes active at once, or + it conflicts with some of the locks, in this case in the 'blocker' + variable a conflicting lock is returned and the calling thread waits on a + pthread condition in the LOCK_OWNER structure of the owner of the + conflicting lock. Or a new lock is compatible with all locks, but some + existing locks are not compatible with each other (example: request IS, + when the list is S-IX) - that is not all locks are active. In this case a + first waiting lock is returned in the 'blocker' variable, lockman_getlock() + notices that a "blocker" does not conflict with the requested lock, and + "dereferences" it, to find the lock that it's waiting on. The calling + thread than begins to wait on the same lock. + + To better support table-row relations where one needs to lock the table + with an intention lock before locking the row, extended diagnostics is + provided. When an intention lock (presumably on a table) is granted, + lockman_getlock() returns one of GOT_THE_LOCK (no need to lock the row, + perhaps the thread already has a normal lock on this table), + GOT_THE_LOCK_NEED_TO_LOCK_A_SUBRESOURCE (need to lock the row, as usual), + GOT_THE_LOCK_NEED_TO_INSTANT_LOCK_A_SUBRESOURCE (only need to check + whether it's possible to lock the row, but no need to lock it - perhaps + the thread has a loose lock on this table). This is defined by + getlock_result[] table. +*/ + +#include <my_global.h> +#include <my_sys.h> +#include <my_bit.h> +#include <lf.h> +#include "lockman.h" + +/* + Lock compatibility matrix. + + It's asymmetric. Read it as "Somebody has the lock <value in the row + label>, can I set the lock <value in the column label> ?" + + ') Though you can take LS lock while somebody has S lock, it makes no + sense - it's simpler to take S lock too. + + 1 - compatible + 0 - incompatible + -1 - "impossible", so that we can assert the impossibility. +*/ +static int lock_compatibility_matrix[10][10]= +{ /* N S X IS IX SIX LS LX SLX LSIX */ + { -1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }, /* N */ + { -1, 1, 0, 1, 0, 0, 1, 0, 0, 0 }, /* S */ + { -1, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, /* X */ + { -1, 1, 0, 1, 1, 1, 1, 1, 1, 1 }, /* IS */ + { -1, 0, 0, 1, 1, 0, 1, 1, 0, 1 }, /* IX */ + { -1, 0, 0, 1, 0, 0, 1, 0, 0, 0 }, /* SIX */ + { -1, 1, 0, 1, 0, 0, 1, 0, 0, 0 }, /* LS */ + { -1, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, /* LX */ + { -1, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, /* SLX */ + { -1, 0, 0, 1, 0, 0, 1, 0, 0, 0 } /* LSIX */ +}; + +/* + Lock combining matrix. + + It's symmetric. Read it as "what lock level L is identical to the + set of two locks A and B" + + One should never get N from it, we assert the impossibility +*/ +static enum lockman_lock_type lock_combining_matrix[10][10]= +{/* N S X IS IX SIX LS LX SLX LSIX */ + { N, S, X, IS, IX, SIX, S, SLX, SLX, SIX}, /* N */ + { S, S, X, S, SIX, SIX, S, SLX, SLX, SIX}, /* S */ + { X, X, X, X, X, X, X, X, X, X}, /* X */ + { IS, S, X, IS, IX, SIX, LS, LX, SLX, LSIX}, /* IS */ + { IX, SIX, X, IX, IX, SIX, LSIX, LX, SLX, LSIX}, /* IX */ + { SIX, SIX, X, SIX, SIX, SIX, SIX, SLX, SLX, SIX}, /* SIX */ + { LS, S, X, LS, LSIX, SIX, LS, LX, SLX, LSIX}, /* LS */ + { LX, SLX, X, LX, LX, SLX, LX, LX, SLX, LX}, /* LX */ + { SLX, SLX, X, SLX, SLX, SLX, SLX, SLX, SLX, SLX}, /* SLX */ + { LSIX, SIX, X, LSIX, LSIX, SIX, LSIX, LX, SLX, LSIX} /* LSIX */ +}; + +#define REPEAT_ONCE_MORE 0 +#define OK_TO_PLACE_THE_LOCK 1 +#define OK_TO_PLACE_THE_REQUEST 2 +#define ALREADY_HAVE_THE_LOCK 4 +#define ALREADY_HAVE_THE_REQUEST 8 +#define PLACE_NEW_DISABLE_OLD 16 +#define REQUEST_NEW_DISABLE_OLD 32 +#define RESOURCE_WAS_UNLOCKED 64 + +#define NEED_TO_WAIT (OK_TO_PLACE_THE_REQUEST | ALREADY_HAVE_THE_REQUEST |\ + REQUEST_NEW_DISABLE_OLD) +#define ALREADY_HAVE (ALREADY_HAVE_THE_LOCK | ALREADY_HAVE_THE_REQUEST) +#define LOCK_UPGRADE (PLACE_NEW_DISABLE_OLD | REQUEST_NEW_DISABLE_OLD) + + +/* + the return codes for lockman_getlock + + It's asymmetric. Read it as "I have the lock <value in the row label>, + what value should be returned for <value in the column label> ?" + + 0 means impossible combination (assert!) + + Defines below help to preserve the table structure. + I/L/A values are self explanatory + x means the combination is possible (assert should not crash) + but it cannot happen in row locks, only in table locks (S,X), + or lock escalations (LS,LX) +*/ +#define I GOT_THE_LOCK_NEED_TO_LOCK_A_SUBRESOURCE +#define L GOT_THE_LOCK_NEED_TO_INSTANT_LOCK_A_SUBRESOURCE +#define A GOT_THE_LOCK +#define x GOT_THE_LOCK +static enum lockman_getlock_result getlock_result[10][10]= +{/* N S X IS IX SIX LS LX SLX LSIX */ + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, /* N */ + { 0, x, 0, A, 0, 0, x, 0, 0, 0}, /* S */ + { 0, x, x, A, A, 0, x, x, 0, 0}, /* X */ + { 0, 0, 0, I, 0, 0, 0, 0, 0, 0}, /* IS */ + { 0, 0, 0, I, I, 0, 0, 0, 0, 0}, /* IX */ + { 0, x, 0, A, I, 0, x, 0, 0, 0}, /* SIX */ + { 0, 0, 0, L, 0, 0, x, 0, 0, 0}, /* LS */ + { 0, 0, 0, L, L, 0, x, x, 0, 0}, /* LX */ + { 0, x, 0, A, L, 0, x, x, 0, 0}, /* SLX */ + { 0, 0, 0, L, I, 0, x, 0, 0, 0} /* LSIX */ +}; +#undef I +#undef L +#undef A +#undef x + +LF_REQUIRE_PINS(4) + +typedef struct lockman_lock { + uint64 resource; + struct lockman_lock *lonext; + intptr volatile link; + uint32 hashnr; + /* QQ: TODO - remove hashnr from LOCK */ + uint16 loid; + uchar lock; /* sizeof(uchar) <= sizeof(enum) */ + uchar flags; +} LOCK; + +#define IGNORE_ME 1 +#define UPGRADED 2 +#define ACTIVE 4 + +typedef struct { + intptr volatile *prev; + LOCK *curr, *next; + LOCK *blocker, *upgrade_from; +} CURSOR; + +#define PTR(V) (LOCK *)((V) & (~(intptr)1)) +#define DELETED(V) ((V) & 1) + +/* + NOTE + cursor is positioned in either case + pins[0..3] are used, they are NOT removed on return +*/ +static int lockfind(LOCK * volatile *head, LOCK *node, + CURSOR *cursor, LF_PINS *pins) +{ + uint32 hashnr, cur_hashnr; + uint64 resource, cur_resource; + intptr cur_link; + my_bool cur_active, compatible, upgrading, prev_active; + enum lockman_lock_type lock, prev_lock, cur_lock; + uint16 loid, cur_loid; + int cur_flags, flags; + + hashnr= node->hashnr; + resource= node->resource; + lock= node->lock; + loid= node->loid; + flags= node->flags; + +retry: + cursor->prev= (intptr *)head; + prev_lock= N; + cur_active= TRUE; + compatible= TRUE; + upgrading= FALSE; + cursor->blocker= cursor->upgrade_from= 0; + _lf_unpin(pins, 3); + do { + cursor->curr= PTR(*cursor->prev); + _lf_pin(pins, 1, cursor->curr); + } while(*cursor->prev != (intptr)cursor->curr && LF_BACKOFF); + for (;;) + { + if (!cursor->curr) + break; + do { + cur_link= cursor->curr->link; + cursor->next= PTR(cur_link); + _lf_pin(pins, 0, cursor->next); + } while (cur_link != cursor->curr->link && LF_BACKOFF); + cur_hashnr= cursor->curr->hashnr; + cur_resource= cursor->curr->resource; + cur_lock= cursor->curr->lock; + cur_loid= cursor->curr->loid; + cur_flags= cursor->curr->flags; + if (*cursor->prev != (intptr)cursor->curr) + { + (void)LF_BACKOFF; + goto retry; + } + if (!DELETED(cur_link)) + { + if (cur_hashnr > hashnr || + (cur_hashnr == hashnr && cur_resource >= resource)) + { + if (cur_hashnr > hashnr || cur_resource > resource) + break; + /* ok, we have a lock for this resource */ + DBUG_ASSERT(lock_compatibility_matrix[prev_lock][cur_lock] >= 0); + DBUG_ASSERT(lock_compatibility_matrix[cur_lock][lock] >= 0); + if ((cur_flags & IGNORE_ME) && ! (flags & IGNORE_ME)) + { + DBUG_ASSERT(cur_active); + if (cur_loid == loid) + cursor->upgrade_from= cursor->curr; + } + else + { + prev_active= cur_active; + if (cur_flags & ACTIVE) + DBUG_ASSERT(prev_active == TRUE); + else + cur_active&= lock_compatibility_matrix[prev_lock][cur_lock]; + if (upgrading && !cur_active /*&& !(cur_flags & UPGRADED)*/) + break; + if (prev_active && !cur_active) + { + cursor->blocker= cursor->curr; + _lf_pin(pins, 3, cursor->curr); + } + if (cur_loid == loid) + { + /* we already have a lock on this resource */ + DBUG_ASSERT(lock_combining_matrix[cur_lock][lock] != N); + DBUG_ASSERT(!upgrading || (flags & IGNORE_ME)); + if (lock_combining_matrix[cur_lock][lock] == cur_lock) + { + /* new lock is compatible */ + if (cur_active) + { + cursor->blocker= cursor->curr; /* loose-locks! */ + _lf_unpin(pins, 3); /* loose-locks! */ + return ALREADY_HAVE_THE_LOCK; + } + else + return ALREADY_HAVE_THE_REQUEST; + } + /* not compatible, upgrading */ + upgrading= TRUE; + cursor->upgrade_from= cursor->curr; + } + else + { + if (!lock_compatibility_matrix[cur_lock][lock]) + { + compatible= FALSE; + cursor->blocker= cursor->curr; + _lf_pin(pins, 3, cursor->curr); + } + } + prev_lock= lock_combining_matrix[prev_lock][cur_lock]; + DBUG_ASSERT(prev_lock != N); + } + } + cursor->prev= &(cursor->curr->link); + _lf_pin(pins, 2, cursor->curr); + } + else + { + if (my_atomic_casptr((void **)cursor->prev, + (void **)&cursor->curr, cursor->next)) + _lf_alloc_free(pins, cursor->curr); + else + { + (void)LF_BACKOFF; + goto retry; + } + } + cursor->curr= cursor->next; + _lf_pin(pins, 1, cursor->curr); + } + /* + either the end of lock list - no more locks for this resource, + or upgrading and the end of active lock list + */ + if (upgrading) + { + if (compatible /*&& prev_active*/) + return PLACE_NEW_DISABLE_OLD; + else + return REQUEST_NEW_DISABLE_OLD; + } + if (cur_active && compatible) + { + /* + either no locks for this resource or all are compatible. + ok to place the lock in any case. + */ + return prev_lock == N ? RESOURCE_WAS_UNLOCKED + : OK_TO_PLACE_THE_LOCK; + } + /* we have a lock conflict. ok to place a lock request. And wait */ + return OK_TO_PLACE_THE_REQUEST; +} + +/* + NOTE + it uses pins[0..3], on return pins 0..2 are removed, pin 3 (blocker) stays +*/ +static int lockinsert(LOCK * volatile *head, LOCK *node, LF_PINS *pins, + LOCK **blocker) +{ + CURSOR cursor; + int res; + + do + { + res= lockfind(head, node, &cursor, pins); + DBUG_ASSERT(res != ALREADY_HAVE_THE_REQUEST); + if (!(res & ALREADY_HAVE)) + { + if (res & LOCK_UPGRADE) + { + node->flags|= UPGRADED; + node->lock= lock_combining_matrix[cursor.upgrade_from->lock][node->lock]; + } + if (!(res & NEED_TO_WAIT)) + node->flags|= ACTIVE; + node->link= (intptr)cursor.curr; + DBUG_ASSERT(node->link != (intptr)node); + DBUG_ASSERT(cursor.prev != &node->link); + if (!my_atomic_casptr((void **)cursor.prev, (void **)&cursor.curr, node)) + { + res= REPEAT_ONCE_MORE; + node->flags&= ~ACTIVE; + } + if (res & LOCK_UPGRADE) + cursor.upgrade_from->flags|= IGNORE_ME; + /* + QQ: is this OK ? if a reader has already read upgrade_from, + it may find it conflicting with node :( + - see the last test from test_lockman_simple() + */ + } + + } while (res == REPEAT_ONCE_MORE); + _lf_unpin(pins, 0); + _lf_unpin(pins, 1); + _lf_unpin(pins, 2); + /* + note that blocker is not necessarily pinned here (when it's == curr). + this is ok as in such a case it's either a dummy node for + initialize_bucket() and dummy nodes don't need pinning, + or it's a lock of the same transaction for lockman_getlock, + and it cannot be removed by another thread + */ + *blocker= cursor.blocker; + return res; +} + +/* + NOTE + it uses pins[0..3], on return pins 0..2 are removed, pin 3 (blocker) stays +*/ +static int lockpeek(LOCK * volatile *head, LOCK *node, LF_PINS *pins, + LOCK **blocker) +{ + CURSOR cursor; + int res; + + res= lockfind(head, node, &cursor, pins); + + _lf_unpin(pins, 0); + _lf_unpin(pins, 1); + _lf_unpin(pins, 2); + if (blocker) + *blocker= cursor.blocker; + return res; +} + +/* + NOTE + it uses pins[0..3], on return all pins are removed. + + One _must_ have the lock (or request) to call this +*/ +static int lockdelete(LOCK * volatile *head, LOCK *node, LF_PINS *pins) +{ + CURSOR cursor; + int res; + + do + { + res= lockfind(head, node, &cursor, pins); + DBUG_ASSERT(res & ALREADY_HAVE); + + if (cursor.upgrade_from) + cursor.upgrade_from->flags&= ~IGNORE_ME; + + /* + XXX this does not work with savepoints, as old lock is left ignored. + It cannot be unignored, as would basically mean moving the lock back + in the lock chain (from upgraded). And the latter is not allowed - + because it breaks list scanning. So old ignored lock must be deleted, + new - same - lock must be installed right after the lock we're deleting, + then we can delete. Good news is - this is only required when rolling + back a savepoint. + */ + if (my_atomic_casptr((void **)&(cursor.curr->link), + (void **)&cursor.next, 1+(char *)cursor.next)) + { + if (my_atomic_casptr((void **)cursor.prev, + (void **)&cursor.curr, cursor.next)) + _lf_alloc_free(pins, cursor.curr); + else + lockfind(head, node, &cursor, pins); + } + else + { + res= REPEAT_ONCE_MORE; + if (cursor.upgrade_from) + cursor.upgrade_from->flags|= IGNORE_ME; + } + } while (res == REPEAT_ONCE_MORE); + _lf_unpin(pins, 0); + _lf_unpin(pins, 1); + _lf_unpin(pins, 2); + _lf_unpin(pins, 3); + return res; +} + +void lockman_init(LOCKMAN *lm, loid_to_lo_func *func, uint timeout) +{ + lf_alloc_init(&lm->alloc, sizeof(LOCK), offsetof(LOCK, lonext)); + lf_dynarray_init(&lm->array, sizeof(LOCK **)); + lm->size= 1; + lm->count= 0; + lm->loid_to_lo= func; + lm->lock_timeout= timeout; +} + +void lockman_destroy(LOCKMAN *lm) +{ + LOCK *el= *(LOCK **)_lf_dynarray_lvalue(&lm->array, 0); + while (el) + { + intptr next= el->link; + if (el->hashnr & 1) + lf_alloc_direct_free(&lm->alloc, el); + else + my_free((void *)el, MYF(0)); + el= (LOCK *)next; + } + lf_alloc_destroy(&lm->alloc); + lf_dynarray_destroy(&lm->array); +} + +/* TODO: optimize it */ +#define MAX_LOAD 1 + +static void initialize_bucket(LOCKMAN *lm, LOCK * volatile *node, + uint bucket, LF_PINS *pins) +{ + int res; + uint parent= my_clear_highest_bit(bucket); + LOCK *dummy= (LOCK *)my_malloc(sizeof(LOCK), MYF(MY_WME)); + LOCK **tmp= 0, *cur; + LOCK * volatile *el= _lf_dynarray_lvalue(&lm->array, parent); + + if (*el == NULL && bucket) + initialize_bucket(lm, el, parent, pins); + dummy->hashnr= my_reverse_bits(bucket); + dummy->loid= 0; + dummy->lock= X; /* doesn't matter, in fact */ + dummy->resource= 0; + dummy->flags= 0; + res= lockinsert(el, dummy, pins, &cur); + DBUG_ASSERT(res & (ALREADY_HAVE_THE_LOCK | RESOURCE_WAS_UNLOCKED)); + if (res & ALREADY_HAVE_THE_LOCK) + { + my_free((void *)dummy, MYF(0)); + dummy= cur; + } + my_atomic_casptr((void **)node, (void **)&tmp, dummy); +} + +static inline uint calc_hash(uint64 resource) +{ + const uchar *pos= (uchar *)&resource; + ulong nr1= 1, nr2= 4, i; + for (i= 0; i < sizeof(resource) ; i++, pos++) + { + nr1^= (ulong) ((((uint) nr1 & 63)+nr2) * ((uint)*pos)) + (nr1 << 8); + nr2+= 3; + } + return nr1 & INT_MAX32; +} + +/* + RETURN + see enum lockman_getlock_result + NOTE + uses pins[0..3], they're removed on return +*/ +enum lockman_getlock_result lockman_getlock(LOCKMAN *lm, LOCK_OWNER *lo, + uint64 resource, + enum lockman_lock_type lock) +{ + int res; + uint csize, bucket, hashnr; + LOCK *node, * volatile *el, *blocker; + LF_PINS *pins= lo->pins; + enum lockman_lock_type old_lock; + + DBUG_ASSERT(lo->loid); + lf_rwlock_by_pins(pins); + node= (LOCK *)_lf_alloc_new(pins); + node->flags= 0; + node->lock= lock; + node->loid= lo->loid; + node->resource= resource; + hashnr= calc_hash(resource); + bucket= hashnr % lm->size; + el= _lf_dynarray_lvalue(&lm->array, bucket); + if (*el == NULL) + initialize_bucket(lm, el, bucket, pins); + node->hashnr= my_reverse_bits(hashnr) | 1; + res= lockinsert(el, node, pins, &blocker); + if (res & ALREADY_HAVE) + { + int r; + old_lock= blocker->lock; + _lf_alloc_free(pins, node); + lf_rwunlock_by_pins(pins); + r= getlock_result[old_lock][lock]; + DBUG_ASSERT(r); + return r; + } + /* a new value was added to the hash */ + csize= lm->size; + if ((my_atomic_add32(&lm->count, 1)+1.0) / csize > MAX_LOAD) + my_atomic_cas32(&lm->size, (int*) &csize, csize*2); + node->lonext= lo->all_locks; + lo->all_locks= node; + for ( ; res & NEED_TO_WAIT; res= lockpeek(el, node, pins, &blocker)) + { + LOCK_OWNER *wait_for_lo; + ulonglong deadline; + struct timespec timeout; + + _lf_assert_pin(pins, 3); /* blocker must be pinned here */ + wait_for_lo= lm->loid_to_lo(blocker->loid); + + /* + now, this is tricky. blocker is not necessarily a LOCK + we're waiting for. If it's compatible with what we want, + then we're waiting for a lock that blocker is waiting for + (see two places where blocker is set in lockfind) + In the latter case, let's "dereference" it + */ + if (lock_compatibility_matrix[blocker->lock][lock]) + { + blocker= wait_for_lo->all_locks; + _lf_pin(pins, 3, blocker); + if (blocker != wait_for_lo->all_locks) + continue; + wait_for_lo= wait_for_lo->waiting_for; + } + + /* + note that the blocker transaction may have ended by now, + its LOCK_OWNER and short id were reused, so 'wait_for_lo' may point + to an unrelated - albeit valid - LOCK_OWNER + */ + if (!wait_for_lo) + continue; + + lo->waiting_for= wait_for_lo; + lf_rwunlock_by_pins(pins); + + /* + We lock a mutex - it may belong to a wrong LOCK_OWNER, but it must + belong to _some_ LOCK_OWNER. It means, we can never free() a LOCK_OWNER, + if there're other active LOCK_OWNERs. + */ + /* QQ: race condition here */ + pthread_mutex_lock(wait_for_lo->mutex); + if (DELETED(blocker->link)) + { + /* + blocker transaction was ended, or a savepoint that owned + the lock was rolled back. Either way - the lock was removed + */ + pthread_mutex_unlock(wait_for_lo->mutex); + lf_rwlock_by_pins(pins); + continue; + } + + /* yuck. waiting */ + deadline= my_getsystime() + lm->lock_timeout * 10000; + set_timespec_nsec(timeout,lm->lock_timeout * 1000000); + do + { + pthread_cond_timedwait(wait_for_lo->cond, wait_for_lo->mutex, &timeout); + } while (!DELETED(blocker->link) && my_getsystime() < deadline); + pthread_mutex_unlock(wait_for_lo->mutex); + lf_rwlock_by_pins(pins); + if (!DELETED(blocker->link)) + { + /* + timeout. + note that we _don't_ release the lock request here. + Instead we're relying on the caller to abort the transaction, + and release all locks at once - see lockman_release_locks() + */ + _lf_unpin(pins, 3); + lf_rwunlock_by_pins(pins); + return DIDNT_GET_THE_LOCK; + } + } + lo->waiting_for= 0; + _lf_assert_unpin(pins, 3); /* unpin should not be needed */ + lf_rwunlock_by_pins(pins); + return getlock_result[lock][lock]; +} + +/* + RETURN + 0 - deleted + 1 - didn't (not found) + NOTE + see lockdelete() for pin usage notes +*/ +int lockman_release_locks(LOCKMAN *lm, LOCK_OWNER *lo) +{ + LOCK * volatile *el, *node, *next; + uint bucket; + LF_PINS *pins= lo->pins; + + pthread_mutex_lock(lo->mutex); + lf_rwlock_by_pins(pins); + for (node= lo->all_locks; node; node= next) + { + next= node->lonext; + bucket= calc_hash(node->resource) % lm->size; + el= _lf_dynarray_lvalue(&lm->array, bucket); + if (*el == NULL) + initialize_bucket(lm, el, bucket, pins); + lockdelete(el, node, pins); + my_atomic_add32(&lm->count, -1); + } + lf_rwunlock_by_pins(pins); + lo->all_locks= 0; + /* now signal all waiters */ + pthread_cond_broadcast(lo->cond); + pthread_mutex_unlock(lo->mutex); + return 0; +} + +#ifdef MY_LF_EXTRA_DEBUG +static const char *lock2str[]= +{ "N", "S", "X", "IS", "IX", "SIX", "LS", "LX", "SLX", "LSIX" }; +/* + NOTE + the function below is NOT thread-safe !!! +*/ +void print_lockhash(LOCKMAN *lm) +{ + LOCK *el= *(LOCK **)_lf_dynarray_lvalue(&lm->array, 0); + printf("hash: size %u count %u\n", lm->size, lm->count); + while (el) + { + intptr next= el->link; + if (el->hashnr & 1) + { + printf("0x%08lx { resource %lu, loid %u, lock %s", + (long) el->hashnr, (ulong) el->resource, el->loid, + lock2str[el->lock]); + if (el->flags & IGNORE_ME) printf(" IGNORE_ME"); + if (el->flags & UPGRADED) printf(" UPGRADED"); + if (el->flags & ACTIVE) printf(" ACTIVE"); + if (DELETED(next)) printf(" ***DELETED***"); + printf("}\n"); + } + else + { + /*printf("0x%08x { dummy }\n", el->hashnr);*/ + DBUG_ASSERT(el->resource == 0 && el->loid == 0 && el->lock == X); + } + el= PTR(next); + } +} +#endif diff --git a/storage/maria/lockman.h b/storage/maria/lockman.h new file mode 100644 index 00000000000..82ab483896f --- /dev/null +++ b/storage/maria/lockman.h @@ -0,0 +1,76 @@ +/* Copyright (C) 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 */ + +#ifndef _lockman_h +#define _lockman_h + +/* + Lock levels: + ^^^^^^^^^^^ + + N - "no lock", not a lock, used sometimes internally to simplify the code + S - Shared + X - eXclusive + IS - Intention Shared + IX - Intention eXclusive + SIX - Shared + Intention eXclusive + LS - Loose Shared + LX - Loose eXclusive + SLX - Shared + Loose eXclusive + LSIX - Loose Shared + Intention eXclusive +*/ +enum lockman_lock_type { N, S, X, IS, IX, SIX, LS, LX, SLX, LSIX, LOCK_TYPE_LAST }; + +struct lockman_lock; + +typedef struct st_lock_owner LOCK_OWNER; +struct st_lock_owner { + LF_PINS *pins; /* must be allocated from lockman's pinbox */ + struct lockman_lock *all_locks; /* a LIFO */ + LOCK_OWNER *waiting_for; + pthread_cond_t *cond; /* transactions waiting for this, wait on 'cond' */ + pthread_mutex_t *mutex; /* mutex is required to use 'cond' */ + uint16 loid; +}; + +typedef LOCK_OWNER *loid_to_lo_func(uint16); +typedef struct { + LF_DYNARRAY array; /* hash itself */ + LF_ALLOCATOR alloc; /* allocator for elements */ + int32 volatile size; /* size of array */ + int32 volatile count; /* number of elements in the hash */ + uint lock_timeout; + loid_to_lo_func *loid_to_lo; +} LOCKMAN; +#define DIDNT_GET_THE_LOCK 0 +enum lockman_getlock_result { + NO_MEMORY_FOR_LOCK=1, DEADLOCK, LOCK_TIMEOUT, + GOT_THE_LOCK, + GOT_THE_LOCK_NEED_TO_LOCK_A_SUBRESOURCE, + GOT_THE_LOCK_NEED_TO_INSTANT_LOCK_A_SUBRESOURCE +}; + +void lockman_init(LOCKMAN *, loid_to_lo_func *, uint); +void lockman_destroy(LOCKMAN *); +enum lockman_getlock_result lockman_getlock(LOCKMAN *lm, LOCK_OWNER *lo, + uint64 resource, + enum lockman_lock_type lock); +int lockman_release_locks(LOCKMAN *, LOCK_OWNER *); + +#ifdef EXTRA_DEBUG +void print_lockhash(LOCKMAN *lm); +#endif + +#endif diff --git a/storage/maria/ma_bitmap.c b/storage/maria/ma_bitmap.c new file mode 100644 index 00000000000..882c6e4cd06 --- /dev/null +++ b/storage/maria/ma_bitmap.c @@ -0,0 +1,2807 @@ +/* Copyright (C) 2007 Michael Widenius + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You 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 */ + +/* + Bitmap handling (for records in block) + + The data file starts with a bitmap page, followed by as many data + pages as the bitmap can cover. After this there is a new bitmap page + and more data pages etc. + + The bitmap code assumes there is always an active bitmap page and thus + that there is at least one bitmap page in the file + + Structure of bitmap page: + + Fixed size records (to be implemented later): + + 2 bits are used to indicate: + + 0 Empty + 1 0-75 % full (at least room for 2 records) + 2 75-100 % full (at least room for one record) + 3 100 % full (no more room for records) + + Assuming 8K pages, this will allow us to map: + 8192 (bytes per page) * 4 (pages mapped per byte) * 8192 (page size)= 256M + + (For Maria this will be 7*4 * 8192 = 224K smaller because of LSN) + + Note that for fixed size rows, we can't add more columns without doing + a full reorganization of the table. The user can always force a dynamic + size row format by specifying ROW_FORMAT=dynamic. + + + Dynamic size records: + + 3 bits are used to indicate Bytes free in 8K page + + 0 Empty page 8176 (head or tail) + 1 0-30 % full (at least room for 3 records) 5724 + 2 30-60 % full (at least room for 2 records) 3271 + 3 60-90 % full (at least room for one record) 818 + 4 100 % full (no more room for records) 0 + 5 Tail page, 0-40 % full 4906 + 6 Tail page, 40-80 % full 1636 + 7 Full tail page or full blob page 0 + + Assuming 8K pages, this will allow us to map: + 8192 (bytes per page) * 8 bits/byte / 3 bits/page * 8192 (page size)= 170.7M + + Note that values 1-3 may be adjust for each individual table based on + 'min record length'. Tail pages are for overflow data which can be of + any size and thus doesn't have to be adjusted for different tables. + If we add more columns to the table, some of the originally calculated + 'cut off' points may not be optimal, but they shouldn't be 'drasticly + wrong'. + + When allocating data from the bitmap, we are trying to do it in a + 'best fit' manner. Blobs and varchar blocks are given out in large + continuous extents to allow fast access to these. Before allowing a + row to 'flow over' to other blocks, we will compact the page and use + all space on it. If there is many rows in the page, we will ensure + there is *LEFT_TO_GROW_ON_SPLIT* bytes left on the page to allow other + rows to grow. + + The bitmap format allows us to extend the row file in big chunks, if needed. + + When calculating the size for a packed row, we will calculate the following + things separately: + - Row header + null_bits + empty_bits fixed size segments etc. + - Size of all char/varchar fields + - Size of each blob field + + The bitmap handler will get all the above information and return + either one page or a set of pages to put the different parts. + + Bitmaps are read on demand in response to insert/delete/update operations. + The following bitmap pointers will be cached and stored on disk on close: + - Current insert_bitmap; When inserting new data we will first try to + fill this one. + - First bitmap which is not completely full. This is updated when we + free data with an update or delete. + + While flushing out bitmaps, we will cache the status of the bitmap in memory + to avoid having to read a bitmap for insert of new data that will not + be of any use + - Total empty space + - Largest number of continuous pages + + Bitmap ONLY goes to disk in the following scenarios + - The file is closed (and we flush all changes to disk) + - On checkpoint + (Ie: When we do a checkpoint, we have to ensure that all bitmaps are + put on disk even if they are not in the page cache). + - When explicitely requested (for example on backup or after recvoery, + to simplify things) + + The flow of writing a row is that: + - Lock the bitmap + - Decide which data pages we will write to + - Mark them full in the bitmap page so that other threads do not try to + use the same data pages as us + - We unlock the bitmap + - Write the data pages + - Lock the bitmap + - Correct the bitmap page with the true final occupation of the data + pages (that is, we marked pages full but when we are done we realize + we didn't fill them) + - Unlock the bitmap. +*/ + +#include "maria_def.h" +#include "ma_blockrec.h" + +#define FULL_HEAD_PAGE 4 +#define FULL_TAIL_PAGE 7 + +/*#define WRONG_BITMAP_FLUSH 1*/ /*define only for provoking bugs*/ +#undef WRONG_BITMAP_FLUSH + +static my_bool _ma_read_bitmap_page(MARIA_HA *info, + MARIA_FILE_BITMAP *bitmap, + pgcache_page_no_t page); +static my_bool _ma_bitmap_create_missing(MARIA_HA *info, + MARIA_FILE_BITMAP *bitmap, + pgcache_page_no_t page); + +/* Write bitmap page to key cache */ + +static inline my_bool write_changed_bitmap(MARIA_SHARE *share, + MARIA_FILE_BITMAP *bitmap) +{ + DBUG_ENTER("write_changed_bitmap"); + DBUG_ASSERT(share->pagecache->block_size == bitmap->block_size); + DBUG_ASSERT(bitmap->file.write_callback != 0); + DBUG_PRINT("info", ("bitmap->non_flushable: %u", bitmap->non_flushable)); + + if ((bitmap->non_flushable == 0) +#ifdef WRONG_BITMAP_FLUSH + || 1 +#endif + ) + { + my_bool res= pagecache_write(share->pagecache, + &bitmap->file, bitmap->page, 0, + bitmap->map, PAGECACHE_PLAIN_PAGE, + PAGECACHE_LOCK_LEFT_UNLOCKED, + PAGECACHE_PIN_LEFT_UNPINNED, + PAGECACHE_WRITE_DELAY, 0, LSN_IMPOSSIBLE); + DBUG_RETURN(res); + } + else + { + MARIA_PINNED_PAGE page_link; + int res= pagecache_write(share->pagecache, + &bitmap->file, bitmap->page, 0, + bitmap->map, PAGECACHE_PLAIN_PAGE, + PAGECACHE_LOCK_LEFT_UNLOCKED, PAGECACHE_PIN, + PAGECACHE_WRITE_DELAY, &page_link.link, + LSN_IMPOSSIBLE); + page_link.unlock= PAGECACHE_LOCK_LEFT_UNLOCKED; + page_link.changed= 1; + push_dynamic(&bitmap->pinned_pages, (void*) &page_link); + DBUG_RETURN(res); + } +} + +/* + Initialize bitmap variables in share + + SYNOPSIS + _ma_bitmap_init() + share Share handler + file data file handler + + NOTES + This is called the first time a file is opened. + + RETURN + 0 ok + 1 error +*/ + +my_bool _ma_bitmap_init(MARIA_SHARE *share, File file) +{ + uint aligned_bit_blocks; + uint max_page_size; + MARIA_FILE_BITMAP *bitmap= &share->bitmap; + uint size= share->block_size; +#ifndef DBUG_OFF + /* We want to have a copy of the bitmap to be able to print differences */ + size*= 2; +#endif + + if (((bitmap->map= (uchar*) my_malloc(size, MYF(MY_WME))) == NULL) || + my_init_dynamic_array(&bitmap->pinned_pages, + sizeof(MARIA_PINNED_PAGE), 1, 1)) + return 1; + + bitmap->block_size= share->block_size; + bitmap->file.file= file; + _ma_bitmap_set_pagecache_callbacks(&bitmap->file, share); + + /* Size needs to be aligned on 6 */ + aligned_bit_blocks= (share->block_size - PAGE_SUFFIX_SIZE) / 6; + bitmap->total_size= aligned_bit_blocks * 6; + /* + In each 6 bytes, we have 6*8/3 = 16 pages covered + The +1 is to add the bitmap page, as this doesn't have to be covered + */ + bitmap->pages_covered= aligned_bit_blocks * 16 + 1; + bitmap->flush_all_requested= bitmap->non_flushable= 0; + + /* Update size for bits */ + /* TODO; Make this dependent of the row size */ + max_page_size= share->block_size - PAGE_OVERHEAD_SIZE + DIR_ENTRY_SIZE; + bitmap->sizes[0]= max_page_size; /* Empty page */ + bitmap->sizes[1]= max_page_size - max_page_size * 30 / 100; + bitmap->sizes[2]= max_page_size - max_page_size * 60 / 100; + bitmap->sizes[3]= max_page_size - max_page_size * 90 / 100; + bitmap->sizes[4]= 0; /* Full page */ + bitmap->sizes[5]= max_page_size - max_page_size * 40 / 100; + bitmap->sizes[6]= max_page_size - max_page_size * 80 / 100; + bitmap->sizes[7]= 0; + + pthread_mutex_init(&share->bitmap.bitmap_lock, MY_MUTEX_INIT_SLOW); + pthread_cond_init(&share->bitmap.bitmap_cond, 0); + + _ma_bitmap_reset_cache(share); + + if (share->state.first_bitmap_with_space == ~(pgcache_page_no_t) 0) + { + /* Start scanning for free space from start of file */ + share->state.first_bitmap_with_space = 0; + } + return 0; +} + + +/* + Free data allocated by _ma_bitmap_init + + SYNOPSIS + _ma_bitmap_end() + share Share handler +*/ + +my_bool _ma_bitmap_end(MARIA_SHARE *share) +{ + my_bool res= _ma_bitmap_flush(share); + safe_mutex_assert_owner(&share->close_lock); + pthread_mutex_destroy(&share->bitmap.bitmap_lock); + pthread_cond_destroy(&share->bitmap.bitmap_cond); + delete_dynamic(&share->bitmap.pinned_pages); + my_free(share->bitmap.map, MYF(MY_ALLOW_ZERO_PTR)); + share->bitmap.map= 0; + return res; +} + + +/* + Send updated bitmap to the page cache + + SYNOPSIS + _ma_bitmap_flush() + share Share handler + + NOTES + In the future, _ma_bitmap_flush() will be called to flush changes don't + by this thread (ie, checking the changed flag is ok). The reason we + check it again in the mutex is that if someone else did a flush at the + same time, we don't have to do the write. + This is also ok for _ma_scan_init_block_record() which does not want to + miss rows: it cares only for committed rows, that is, rows for which there + was a commit before our transaction started; as commit and transaction's + start are protected by the same LOCK_trn_list mutex, we see memory at + least as new as at other transaction's commit time, so if the committed + rows caused bitmap->changed to be true, we see it; if we see 0 it really + means a flush happened since then. So, it's ok to read without bitmap's + mutex. + + RETURN + 0 ok + 1 error +*/ + +my_bool _ma_bitmap_flush(MARIA_SHARE *share) +{ + my_bool res= 0; + DBUG_ENTER("_ma_bitmap_flush"); + if (share->bitmap.changed) + { + pthread_mutex_lock(&share->bitmap.bitmap_lock); + if (share->bitmap.changed) + { + share->bitmap.changed= 0; + res= write_changed_bitmap(share, &share->bitmap); + } + pthread_mutex_unlock(&share->bitmap.bitmap_lock); + } + DBUG_RETURN(res); +} + + +/** + Dirty-page filtering criteria for bitmap pages + + @param type Page's type + @param pageno Page's number + @param rec_lsn Page's rec_lsn + @param arg pages_covered of bitmap +*/ + +static enum pagecache_flush_filter_result +filter_flush_bitmap_pages(enum pagecache_page_type type + __attribute__ ((unused)), + pgcache_page_no_t pageno, + LSN rec_lsn __attribute__ ((unused)), + void *arg) +{ + return ((pageno % (*(ulong*)arg)) == 0); +} + + +/** + Flushes current bitmap page to the pagecache, and then all bitmap pages + from pagecache to the file. Used by Checkpoint. + + @param share Table's share +*/ + +my_bool _ma_bitmap_flush_all(MARIA_SHARE *share) +{ + my_bool res= 0; + MARIA_FILE_BITMAP *bitmap= &share->bitmap; + DBUG_ENTER("_ma_bitmap_flush_all"); + pthread_mutex_lock(&bitmap->bitmap_lock); + if (bitmap->changed) + { + bitmap->flush_all_requested= TRUE; +#ifndef WRONG_BITMAP_FLUSH + while (bitmap->non_flushable > 0) + { + DBUG_PRINT("info", ("waiting for bitmap to be flushable")); + pthread_cond_wait(&bitmap->bitmap_cond, &bitmap->bitmap_lock); + } +#endif + /* + Bitmap is in a flushable state: its contents in memory are reflected by + log records (complete REDO-UNDO groups) and all bitmap pages are + unpinned. We keep the mutex to preserve this situation, and flush to the + file. + */ + if (bitmap->changed) + { + res= write_changed_bitmap(share, bitmap); + bitmap->changed= FALSE; + } + /* + We do NOT use FLUSH_KEEP_LAZY because we must be sure that bitmap + pages have been flushed. That's a condition of correctness of + Recovery: data pages may have been all flushed, if we write the + checkpoint record Recovery will start from after their REDOs. If + bitmap page was not flushed, as the REDOs about it will be skipped, it + will wrongly not be recovered. If bitmap pages had a rec_lsn it would + be different. + There should be no pinned pages as bitmap->non_flushable==0. + */ + if (flush_pagecache_blocks_with_filter(share->pagecache, + &bitmap->file, FLUSH_KEEP, + filter_flush_bitmap_pages, + &bitmap->pages_covered) & + PCFLUSH_PINNED_AND_ERROR) + res= TRUE; + bitmap->flush_all_requested= FALSE; + /* + Some well-behaved threads may be waiting for flush_all_requested to + become false, wake them up. + */ + DBUG_PRINT("info", ("bitmap flusher waking up others")); + pthread_cond_broadcast(&bitmap->bitmap_cond); + } + pthread_mutex_unlock(&bitmap->bitmap_lock); + DBUG_RETURN(res); +} + + +/** + @brief Unpin all pinned bitmap pages + + @param share Table's share + + @return Operation status + @retval 0 ok + + @note This unpins pages pinned by other threads. +*/ + +static void _ma_bitmap_unpin_all(MARIA_SHARE *share) +{ + MARIA_FILE_BITMAP *bitmap= &share->bitmap; + MARIA_PINNED_PAGE *page_link= ((MARIA_PINNED_PAGE*) + dynamic_array_ptr(&bitmap->pinned_pages, 0)); + MARIA_PINNED_PAGE *pinned_page= page_link + bitmap->pinned_pages.elements; + DBUG_ENTER("_ma_bitmap_unpin_all"); + DBUG_PRINT("info", ("pinned: %u", bitmap->pinned_pages.elements)); + while (pinned_page-- != page_link) + pagecache_unlock_by_link(share->pagecache, pinned_page->link, + pinned_page->unlock, PAGECACHE_UNPIN, + LSN_IMPOSSIBLE, LSN_IMPOSSIBLE, TRUE, TRUE); + bitmap->pinned_pages.elements= 0; + DBUG_VOID_RETURN; +} + + +/* + Intialize bitmap in memory to a zero bitmap + + SYNOPSIS + _ma_bitmap_delete_all() + share Share handler + + NOTES + This is called on maria_delete_all_rows (truncate data file). +*/ + +void _ma_bitmap_delete_all(MARIA_SHARE *share) +{ + MARIA_FILE_BITMAP *bitmap= &share->bitmap; + DBUG_ENTER("_ma_bitmap_delete_all"); + if (bitmap->map) /* Not in create */ + { + bzero(bitmap->map, bitmap->block_size); + bitmap->changed= 1; + bitmap->page= 0; + bitmap->used_size= bitmap->total_size; + } + DBUG_VOID_RETURN; +} + + +/** + @brief Reset bitmap caches + + @fn _ma_bitmap_reset_cache() + @param share Maria share + + @notes + This is called after we have swapped file descriptors and we want + bitmap to forget all cached information +*/ + +void _ma_bitmap_reset_cache(MARIA_SHARE *share) +{ + MARIA_FILE_BITMAP *bitmap= &share->bitmap; + + if (bitmap->map) /* If using bitmap */ + { + /* Forget changes in current bitmap page */ + bitmap->changed= 0; + + /* + We can't read a page yet, as in some case we don't have an active + page cache yet. + Pretend we have a dummy, full and not changed bitmap page in memory. + */ + bitmap->page= ~(ulonglong) 0; + bitmap->used_size= bitmap->total_size; + bfill(bitmap->map, share->block_size, 255); +#ifndef DBUG_OFF + memcpy(bitmap->map + bitmap->block_size, bitmap->map, bitmap->block_size); +#endif + } +} + + +/* + Return bitmap pattern for the smallest head block that can hold 'size' + + SYNOPSIS + size_to_head_pattern() + bitmap Bitmap + size Requested size + + RETURN + 0-3 For a description of the bitmap sizes, see the header +*/ + +static uint size_to_head_pattern(MARIA_FILE_BITMAP *bitmap, uint size) +{ + if (size <= bitmap->sizes[3]) + return 3; + if (size <= bitmap->sizes[2]) + return 2; + if (size <= bitmap->sizes[1]) + return 1; + DBUG_ASSERT(size <= bitmap->sizes[0]); + return 0; +} + + +/* + Return bitmap pattern for head block where there is size bytes free + + SYNOPSIS + _ma_free_size_to_head_pattern() + bitmap Bitmap + size Requested size + + RETURN + 0-4 (Possible bitmap patterns for head block) +*/ + +uint _ma_free_size_to_head_pattern(MARIA_FILE_BITMAP *bitmap, uint size) +{ + if (size < bitmap->sizes[3]) + return 4; + if (size < bitmap->sizes[2]) + return 3; + if (size < bitmap->sizes[1]) + return 2; + return (size < bitmap->sizes[0]) ? 1 : 0; +} + + +/* + Return bitmap pattern for the smallest tail block that can hold 'size' + + SYNOPSIS + size_to_tail_pattern() + bitmap Bitmap + size Requested size + + RETURN + 0, 5 or 6 For a description of the bitmap sizes, see the header +*/ + +static uint size_to_tail_pattern(MARIA_FILE_BITMAP *bitmap, uint size) +{ + if (size <= bitmap->sizes[6]) + return 6; + if (size <= bitmap->sizes[5]) + return 5; + DBUG_ASSERT(size <= bitmap->sizes[0]); + return 0; +} + + +/* + Return bitmap pattern for tail block where there is size bytes free + + SYNOPSIS + free_size_to_tail_pattern() + bitmap Bitmap + size Requested size + + RETURN + 0, 5, 6, 7 For a description of the bitmap sizes, see the header +*/ + +static uint free_size_to_tail_pattern(MARIA_FILE_BITMAP *bitmap, uint size) +{ + if (size >= bitmap->sizes[0]) + return 0; /* Revert to empty page */ + if (size < bitmap->sizes[6]) + return 7; + if (size < bitmap->sizes[5]) + return 6; + return 5; +} + + +/* + Return size guranteed to be available on a page + + SYNOPSIS + pattern_to_head_size() + bitmap Bitmap + pattern Pattern (0-7) + + RETURN + 0 - block_size +*/ + +static inline uint pattern_to_size(MARIA_FILE_BITMAP *bitmap, uint pattern) +{ + DBUG_ASSERT(pattern <= 7); + return bitmap->sizes[pattern]; +} + + +/* + Print bitmap for debugging + + SYNOPSIS + _ma_print_bitmap() + bitmap Bitmap to print + + IMPLEMENTATION + Prints all changed bits since last call to _ma_print_bitmap(). + This is done by having a copy of the last bitmap in + bitmap->map+bitmap->block_size. +*/ + +#ifndef DBUG_OFF + +const char *bits_to_txt[]= +{ + "empty", "00-30% full", "30-60% full", "60-90% full", "full", + "tail 00-40 % full", "tail 40-80 % full", "tail/blob full" +}; + +static void _ma_print_bitmap_changes(MARIA_FILE_BITMAP *bitmap) +{ + uchar *pos, *end, *org_pos; + ulong page; + + end= bitmap->map + bitmap->used_size; + DBUG_LOCK_FILE; + fprintf(DBUG_FILE,"\nBitmap page changes at page %lu\n", + (ulong) bitmap->page); + + page= (ulong) bitmap->page+1; + for (pos= bitmap->map, org_pos= bitmap->map + bitmap->block_size ; + pos < end ; + pos+= 6, org_pos+= 6) + { + ulonglong bits= uint6korr(pos); /* 6 bytes = 6*8/3= 16 patterns */ + ulonglong org_bits= uint6korr(org_pos); + uint i; + + /* + Test if there is any changes in the next 16 bitmaps (to not have to + loop through all bits if we know they are the same) + */ + if (bits != org_bits) + { + for (i= 0; i < 16 ; i++, bits>>= 3, org_bits>>= 3) + { + if ((bits & 7) != (org_bits & 7)) + fprintf(DBUG_FILE, "Page: %8lu %s -> %s\n", page+i, + bits_to_txt[org_bits & 7], bits_to_txt[bits & 7]); + } + } + page+= 16; + } + fputc('\n', DBUG_FILE); + DBUG_UNLOCK_FILE; + memcpy(bitmap->map + bitmap->block_size, bitmap->map, bitmap->block_size); +} + + +/* Print content of bitmap for debugging */ + +void _ma_print_bitmap(MARIA_FILE_BITMAP *bitmap, uchar *data, + pgcache_page_no_t page) +{ + uchar *pos, *end; + char llbuff[22]; + + end= bitmap->map + bitmap->used_size; + DBUG_LOCK_FILE; + fprintf(DBUG_FILE,"\nDump of bitmap page at %s\n", llstr(page, llbuff)); + + page++; /* Skip bitmap page */ + for (pos= data, end= pos + bitmap->total_size; + pos < end ; + pos+= 6) + { + ulonglong bits= uint6korr(pos); /* 6 bytes = 6*8/3= 16 patterns */ + + /* + Test if there is any changes in the next 16 bitmaps (to not have to + loop through all bits if we know they are the same) + */ + if (bits) + { + uint i; + for (i= 0; i < 16 ; i++, bits>>= 3) + { + if (bits & 7) + fprintf(DBUG_FILE, "Page: %8s %s\n", llstr(page+i, llbuff), + bits_to_txt[bits & 7]); + } + } + page+= 16; + } + fputc('\n', DBUG_FILE); + DBUG_UNLOCK_FILE; +} + +#endif /* DBUG_OFF */ + + +/*************************************************************************** + Reading & writing bitmap pages +***************************************************************************/ + +/* + Read a given bitmap page + + SYNOPSIS + _ma_read_bitmap_page() + info Maria handler + bitmap Bitmap handler + page Page to read + + TODO + Update 'bitmap->used_size' to real size of used bitmap + + NOTE + We don't always have share->bitmap.bitmap_lock here + (when called from_ma_check_bitmap_data() for example). + + RETURN + 0 ok + 1 error (Error writing old bitmap or reading bitmap page) +*/ + +static my_bool _ma_read_bitmap_page(MARIA_HA *info, + MARIA_FILE_BITMAP *bitmap, + pgcache_page_no_t page) +{ + MARIA_SHARE *share= info->s; + my_bool res; + DBUG_ENTER("_ma_read_bitmap_page"); + DBUG_ASSERT(page % bitmap->pages_covered == 0); + DBUG_ASSERT(!bitmap->changed); + + bitmap->page= page; + if (((page + 1) * bitmap->block_size) > share->state.state.data_file_length) + { + /* Inexistent or half-created page */ + res= _ma_bitmap_create_missing(info, bitmap, page); + DBUG_RETURN(res); + } + bitmap->used_size= bitmap->total_size; + DBUG_ASSERT(share->pagecache->block_size == bitmap->block_size); + res= pagecache_read(share->pagecache, + &bitmap->file, page, 0, + bitmap->map, PAGECACHE_PLAIN_PAGE, + PAGECACHE_LOCK_LEFT_UNLOCKED, 0) == NULL; + + /* + We can't check maria_bitmap_marker here as if the bitmap page + previously had a true checksum and the user switched mode to not checksum + this may have any value, except maria_normal_page_marker. + + Using maria_normal_page_marker gives us a protection against bugs + when running without any checksums. + */ + +#ifndef DBUG_OFF + if (!res) + memcpy(bitmap->map + bitmap->block_size, bitmap->map, bitmap->block_size); +#endif + DBUG_RETURN(res); +} + + +/* + Change to another bitmap page + + SYNOPSIS + _ma_change_bitmap_page() + info Maria handler + bitmap Bitmap handler + page Bitmap page to read + + NOTES + If old bitmap was changed, write it out before reading new one + We return empty bitmap if page is outside of file size + + RETURN + 0 ok + 1 error (Error writing old bitmap or reading bitmap page) +*/ + +static my_bool _ma_change_bitmap_page(MARIA_HA *info, + MARIA_FILE_BITMAP *bitmap, + pgcache_page_no_t page) +{ + DBUG_ENTER("_ma_change_bitmap_page"); + + if (bitmap->changed) + { + if (write_changed_bitmap(info->s, bitmap)) + DBUG_RETURN(1); + bitmap->changed= 0; + } + DBUG_RETURN(_ma_read_bitmap_page(info, bitmap, page)); +} + + +/* + Read next suitable bitmap + + SYNOPSIS + move_to_next_bitmap() + bitmap Bitmap handle + + NOTES + The found bitmap may be full, so calling function may need to call this + repeatedly until it finds enough space. + + TODO + Add cache of bitmaps to not read something that is not usable + + RETURN + 0 ok + 1 error (either couldn't save old bitmap or read new one) +*/ + +static my_bool move_to_next_bitmap(MARIA_HA *info, MARIA_FILE_BITMAP *bitmap) +{ + pgcache_page_no_t page= bitmap->page; + MARIA_STATE_INFO *state= &info->s->state; + DBUG_ENTER("move_to_next_bitmap"); + + if (state->first_bitmap_with_space != ~(ulonglong) 0 && + state->first_bitmap_with_space != page) + { + page= state->first_bitmap_with_space; + state->first_bitmap_with_space= ~(ulonglong) 0; + } + else + page+= bitmap->pages_covered; + DBUG_RETURN(_ma_change_bitmap_page(info, bitmap, page)); +} + + +/**************************************************************************** + Allocate data in bitmaps +****************************************************************************/ + +/* + Store data in 'block' and mark the place used in the bitmap + + SYNOPSIS + fill_block() + bitmap Bitmap handle + block Store data about what we found + best_data Pointer to best 6 uchar aligned area in bitmap->map + best_pos Which bit in *best_data the area starts + 0 = first bit pattern, 1 second bit pattern etc + best_bits The original value of the bits at best_pos + fill_pattern Bitmap pattern to store in best_data[best_pos] + + NOTES + We mark all pages to be 'TAIL's, which means that + block->page_count is really a row position inside the page. +*/ + +static void fill_block(MARIA_FILE_BITMAP *bitmap, + MARIA_BITMAP_BLOCK *block, + uchar *best_data, uint best_pos, uint best_bits, + uint fill_pattern) +{ + uint page, offset, tmp; + uchar *data; + + /* For each 6 bytes we have 6*8/3= 16 patterns */ + page= ((uint) (best_data - bitmap->map)) / 6 * 16 + best_pos; + DBUG_ASSERT(page + 1 < bitmap->pages_covered); + block->page= bitmap->page + 1 + page; + block->page_count= TAIL_PAGE_COUNT_MARKER; + block->empty_space= pattern_to_size(bitmap, best_bits); + block->sub_blocks= 0; + block->org_bitmap_value= best_bits; + block->used= BLOCKUSED_TAIL; /* See _ma_bitmap_release_unused() */ + + /* + Mark place used by reading/writing 2 bytes at a time to handle + bitmaps in overlapping bytes + */ + best_pos*= 3; + data= best_data+ best_pos / 8; + offset= best_pos & 7; + tmp= uint2korr(data); + + /* we turn off the 3 bits and replace them with fill_pattern */ + tmp= (tmp & ~(7 << offset)) | (fill_pattern << offset); + int2store(data, tmp); + bitmap->changed= 1; + DBUG_EXECUTE("bitmap", _ma_print_bitmap_changes(bitmap);); +} + + +/* + Allocate data for head block + + SYNOPSIS + allocate_head() + bitmap bitmap + size Size of data region we need to store + block Store found information here + + IMPLEMENTATION + Find the best-fit page to put a region of 'size' + This is defined as the first page of the set of pages + with the smallest free space that can hold 'size'. + + RETURN + 0 ok (block is updated) + 1 error (no space in bitmap; block is not touched) +*/ + + +static my_bool allocate_head(MARIA_FILE_BITMAP *bitmap, uint size, + MARIA_BITMAP_BLOCK *block) +{ + uint min_bits= size_to_head_pattern(bitmap, size); + uchar *data= bitmap->map, *end= data + bitmap->used_size; + uchar *best_data= 0; + uint best_bits= (uint) -1, best_pos; + DBUG_ENTER("allocate_head"); + + LINT_INIT(best_pos); + DBUG_ASSERT(size <= FULL_PAGE_SIZE(bitmap->block_size)); + + for (; data < end; data+= 6) + { + ulonglong bits= uint6korr(data); /* 6 bytes = 6*8/3= 16 patterns */ + uint i; + + /* + Skip common patterns + We can skip empty pages (if we already found a match) or + anything matching the following pattern as this will be either + a full page or a tail page + */ + if ((!bits && best_data) || + ((bits & LL(04444444444444444)) == LL(04444444444444444))) + continue; + for (i= 0; i < 16 ; i++, bits >>= 3) + { + uint pattern= (uint) (bits & 7); + if (pattern <= min_bits) + { + /* There is enough space here */ + if ((int) pattern > (int) best_bits) + { + /* + There is more than enough space here and it's better than what + we have found so far. Remember it, as we will choose it if we + don't find anything in this bitmap page. + */ + best_bits= pattern; + best_data= data; + best_pos= i; + if (pattern == min_bits) + goto found; /* Best possible match */ + } + } + } + } + if (!best_data) /* Found no place */ + { + if (data >= bitmap->map + bitmap->total_size) + DBUG_RETURN(1); /* No space in bitmap */ + /* Allocate data at end of bitmap */ + bitmap->used_size+= 6; + set_if_smaller(bitmap->used_size, bitmap->total_size); + best_data= data; + best_pos= best_bits= 0; + } + +found: + fill_block(bitmap, block, best_data, best_pos, best_bits, FULL_HEAD_PAGE); + DBUG_RETURN(0); +} + + +/* + Allocate data for tail block + + SYNOPSIS + allocate_tail() + bitmap bitmap + size Size of block we need to find + block Store found information here + + RETURN + 0 ok (block is updated) + 1 error (no space in bitmap; block is not touched) +*/ + + +static my_bool allocate_tail(MARIA_FILE_BITMAP *bitmap, uint size, + MARIA_BITMAP_BLOCK *block) +{ + uint min_bits= size_to_tail_pattern(bitmap, size); + uchar *data= bitmap->map, *end= data + bitmap->used_size; + uchar *best_data= 0; + uint best_bits= (uint) -1, best_pos; + DBUG_ENTER("allocate_tail"); + DBUG_PRINT("enter", ("size: %u", size)); + + LINT_INIT(best_pos); + DBUG_ASSERT(size <= FULL_PAGE_SIZE(bitmap->block_size)); + + for (; data < end; data += 6) + { + ulonglong bits= uint6korr(data); /* 6 bytes = 6*8/3= 16 patterns */ + uint i; + + /* + Skip common patterns + We can skip empty pages (if we already found a match) or + the following patterns: 1-4 (head pages, not suitable for tail) or + 7 (full tail page). See 'Dynamic size records' comment at start of file. + + At the moment we only skip full head and tail pages (ie, all bits are + set) as this is easy to detect with one simple test and is a + quite common case if we have blobs. + */ + + if ((!bits && best_data) || bits == LL(0xffffffffffff) || + bits == LL(04444444444444444)) + continue; + for (i= 0; i < 16; i++, bits >>= 3) + { + uint pattern= (uint) (bits & 7); + if (pattern <= min_bits && (!pattern || pattern >= 5)) + { + if ((int) pattern > (int) best_bits) + { + best_bits= pattern; + best_data= data; + best_pos= i; + if (pattern == min_bits) + goto found; /* Can't be better */ + } + } + } + } + if (!best_data) + { + if (data >= bitmap->map + bitmap->total_size) + DBUG_RETURN(1); + /* Allocate data at end of bitmap */ + best_data= data; + bitmap->used_size+= 6; + set_if_smaller(bitmap->used_size, bitmap->total_size); + best_pos= best_bits= 0; + } + +found: + fill_block(bitmap, block, best_data, best_pos, best_bits, FULL_TAIL_PAGE); + DBUG_RETURN(0); +} + + +/* + Allocate data for full blocks + + SYNOPSIS + allocate_full_pages() + bitmap bitmap + pages_needed Total size in pages (bitmap->total_size) we would like to have + block Store found information here + full_page 1 if we are not allowed to split extent + + IMPLEMENTATION + We will return the smallest area >= size. If there is no such + block, we will return the biggest area that satisfies + area_size >= min(BLOB_SEGMENT_MIN_SIZE*full_page_size, size) + + To speed up searches, we will only consider areas that has at least 16 free + pages starting on an even boundary. When finding such an area, we will + extend it with all previous and following free pages. This will ensure + we don't get holes between areas + + RETURN + # Blocks used + 0 error (no space in bitmap; block is not touched) +*/ + +static ulong allocate_full_pages(MARIA_FILE_BITMAP *bitmap, + ulong pages_needed, + MARIA_BITMAP_BLOCK *block, my_bool full_page) +{ + uchar *data= bitmap->map, *data_end= data + bitmap->used_size; + uchar *page_end= data + bitmap->total_size; + uchar *best_data= 0; + uint min_size; + uint best_area_size, best_prefix_area_size, best_suffix_area_size; + uint page, size; + ulonglong best_prefix_bits; + DBUG_ENTER("allocate_full_pages"); + DBUG_PRINT("enter", ("pages_needed: %lu", pages_needed)); + + /* Following variables are only used if best_data is set */ + LINT_INIT(best_prefix_bits); + LINT_INIT(best_prefix_area_size); + LINT_INIT(best_suffix_area_size); + + min_size= pages_needed; + if (!full_page && min_size > BLOB_SEGMENT_MIN_SIZE) + min_size= BLOB_SEGMENT_MIN_SIZE; + best_area_size= ~(uint) 0; + + for (; data < page_end; data+= 6) + { + ulonglong bits= uint6korr(data); /* 6 bytes = 6*8/3= 16 patterns */ + uchar *data_start; + ulonglong prefix_bits= 0; + uint area_size, prefix_area_size, suffix_area_size; + + /* Find area with at least 16 free pages */ + if (bits) + continue; + data_start= data; + /* Find size of area */ + for (data+=6 ; data < data_end ; data+= 6) + { + if ((bits= uint6korr(data))) + break; + } + area_size= (uint) (data - data_start) / 6 * 16; + if (area_size >= best_area_size) + continue; + prefix_area_size= suffix_area_size= 0; + if (!bits) + { + /* + End of page; All the rest of the bits on page are part of area + This is needed because bitmap->used_size only covers the set bits + in the bitmap. + */ + area_size+= (uint) (page_end - data) / 6 * 16; + if (area_size >= best_area_size) + break; + data= page_end; + } + else + { + /* Add bits at end of page */ + for (; !(bits & 7); bits >>= 3) + suffix_area_size++; + area_size+= suffix_area_size; + } + if (data_start != bitmap->map) + { + /* Add bits before page */ + bits= prefix_bits= uint6korr(data_start - 6); + DBUG_ASSERT(bits != 0); + /* 111 000 000 000 000 000 000 000 000 000 000 000 000 000 000 000 */ + if (!(bits & LL(07000000000000000))) + { + data_start-= 6; + do + { + prefix_area_size++; + bits<<= 3; + } while (!(bits & LL(07000000000000000))); + area_size+= prefix_area_size; + /* Calculate offset to page from data_start */ + prefix_area_size= 16 - prefix_area_size; + } + } + if (area_size >= min_size && area_size <= best_area_size) + { + best_data= data_start; + best_area_size= area_size; + best_prefix_bits= prefix_bits; + best_prefix_area_size= prefix_area_size; + best_suffix_area_size= suffix_area_size; + + /* Prefer to put data in biggest possible area */ + if (area_size <= pages_needed) + min_size= area_size; + else + min_size= pages_needed; + } + } + if (!best_data) + DBUG_RETURN(0); /* No room on page */ + + /* + Now allocate min(pages_needed, area_size), starting from + best_start + best_prefix_area_size + */ + if (best_area_size > pages_needed) + best_area_size= pages_needed; + + /* For each 6 bytes we have 6*8/3= 16 patterns */ + page= ((uint) (best_data - bitmap->map) * 8) / 3 + best_prefix_area_size; + block->page= bitmap->page + 1 + page; + block->page_count= best_area_size; + block->empty_space= 0; + block->sub_blocks= 0; + block->org_bitmap_value= 0; + block->used= 0; + DBUG_ASSERT(page + best_area_size < bitmap->pages_covered); + DBUG_PRINT("info", ("page: %lu page_count: %u", + (ulong) block->page, block->page_count)); + + if (best_prefix_area_size) + { + ulonglong tmp; + /* Convert offset back to bits */ + best_prefix_area_size= 16 - best_prefix_area_size; + if (best_area_size < best_prefix_area_size) + { + tmp= (LL(1) << best_area_size*3) - 1; + best_area_size= best_prefix_area_size; /* for easy end test */ + } + else + tmp= (LL(1) << best_prefix_area_size*3) - 1; + tmp<<= (16 - best_prefix_area_size) * 3; + DBUG_ASSERT((best_prefix_bits & tmp) == 0); + best_prefix_bits|= tmp; + int6store(best_data, best_prefix_bits); + if (!(best_area_size-= best_prefix_area_size)) + { + DBUG_EXECUTE("bitmap", _ma_print_bitmap_changes(bitmap);); + DBUG_RETURN(block->page_count); + } + best_data+= 6; + } + best_area_size*= 3; /* Bits to set */ + size= best_area_size/8; /* Bytes to set */ + bfill(best_data, size, 255); + best_data+= size; + if ((best_area_size-= size * 8)) + { + /* fill last uchar */ + *best_data|= (uchar) ((1 << best_area_size) -1); + best_data++; + } + if (data_end < best_data) + { + bitmap->used_size= (uint) (best_data - bitmap->map); + DBUG_ASSERT(bitmap->used_size <= bitmap->total_size); + } + bitmap->changed= 1; + DBUG_EXECUTE("bitmap", _ma_print_bitmap_changes(bitmap);); + DBUG_RETURN(block->page_count); +} + + +/**************************************************************************** + Find right bitmaps where to store data +****************************************************************************/ + +/* + Find right bitmap and position for head block + + SYNOPSIS + find_head() + info Maria handler + length Size of data region we need store + position Position in bitmap_blocks where to store the + information for the head block. + + RETURN + 0 ok + 1 error +*/ + +static my_bool find_head(MARIA_HA *info, uint length, uint position) +{ + MARIA_FILE_BITMAP *bitmap= &info->s->bitmap; + MARIA_BITMAP_BLOCK *block; + /* + There is always place for the head block in bitmap_blocks as these are + preallocated at _ma_init_block_record(). + */ + block= dynamic_element(&info->bitmap_blocks, position, MARIA_BITMAP_BLOCK *); + + /* + We need to have DIRENTRY_SIZE here to take into account that we may + need an extra directory entry for the row + */ + while (allocate_head(bitmap, length + DIR_ENTRY_SIZE, block)) + if (move_to_next_bitmap(info, bitmap)) + return 1; + return 0; +} + + +/* + Find right bitmap and position for tail + + SYNOPSIS + find_tail() + info Maria handler + length Size of data region we need store + position Position in bitmap_blocks where to store the + information for the head block. + + RETURN + 0 ok + 1 error +*/ + +static my_bool find_tail(MARIA_HA *info, uint length, uint position) +{ + MARIA_FILE_BITMAP *bitmap= &info->s->bitmap; + MARIA_BITMAP_BLOCK *block; + DBUG_ENTER("find_tail"); + DBUG_ASSERT(length <= info->s->block_size - PAGE_OVERHEAD_SIZE); + + /* Needed, as there is no error checking in dynamic_element */ + if (allocate_dynamic(&info->bitmap_blocks, position)) + DBUG_RETURN(1); + block= dynamic_element(&info->bitmap_blocks, position, MARIA_BITMAP_BLOCK *); + + /* + We have to add DIR_ENTRY_SIZE to ensure we have space for the tail and + it's directroy entry on the page + */ + while (allocate_tail(bitmap, length + DIR_ENTRY_SIZE, block)) + if (move_to_next_bitmap(info, bitmap)) + DBUG_RETURN(1); + DBUG_RETURN(0); +} + + +/* + Find right bitmap and position for full blocks in one extent + + SYNOPSIS + find_mid() + info Maria handler. + pages How many pages to allocate. + position Position in bitmap_blocks where to store the + information for the head block. + NOTES + This is used to allocate the main extent after the 'head' block + (Ie, the middle part of the head-middle-tail entry) + + RETURN + 0 ok + 1 error +*/ + +static my_bool find_mid(MARIA_HA *info, ulong pages, uint position) +{ + MARIA_FILE_BITMAP *bitmap= &info->s->bitmap; + MARIA_BITMAP_BLOCK *block; + block= dynamic_element(&info->bitmap_blocks, position, MARIA_BITMAP_BLOCK *); + + while (!allocate_full_pages(bitmap, pages, block, 1)) + { + if (move_to_next_bitmap(info, bitmap)) + return 1; + } + return 0; +} + + +/* + Find right bitmap and position for putting a blob + + SYNOPSIS + find_blob() + info Maria handler. + length Length of the blob + + NOTES + The extents are stored last in info->bitmap_blocks + + IMPLEMENTATION + Allocate all full pages for the block + optionally one tail + + RETURN + 0 ok + 1 error +*/ + +static my_bool find_blob(MARIA_HA *info, ulong length) +{ + MARIA_FILE_BITMAP *bitmap= &info->s->bitmap; + uint full_page_size= FULL_PAGE_SIZE(info->s->block_size); + ulong pages; + uint rest_length, used; + uint first_block_pos; + MARIA_BITMAP_BLOCK *first_block= 0; + DBUG_ENTER("find_blob"); + DBUG_PRINT("enter", ("length: %lu", length)); + LINT_INIT(first_block_pos); + + pages= length / full_page_size; + rest_length= (uint) (length - pages * full_page_size); + if (rest_length >= MAX_TAIL_SIZE(info->s->block_size)) + { + pages++; + rest_length= 0; + } + + first_block_pos= info->bitmap_blocks.elements; + if (pages) + { + MARIA_BITMAP_BLOCK *block; + if (allocate_dynamic(&info->bitmap_blocks, + info->bitmap_blocks.elements + + pages / BLOB_SEGMENT_MIN_SIZE + 2)) + DBUG_RETURN(1); + block= dynamic_element(&info->bitmap_blocks, info->bitmap_blocks.elements, + MARIA_BITMAP_BLOCK*); + do + { + /* + We use 0x3fff here as the two upmost bits are reserved for + TAIL_BIT and START_EXTENT_BIT + */ + used= allocate_full_pages(bitmap, + (pages >= 0x3fff ? 0x3fff : (uint) pages), + block, 0); + if (!used) + { + if (move_to_next_bitmap(info, bitmap)) + DBUG_RETURN(1); + } + else + { + pages-= used; + info->bitmap_blocks.elements++; + block++; + } + } while (pages != 0); + } + if (rest_length && find_tail(info, rest_length, + info->bitmap_blocks.elements++)) + DBUG_RETURN(1); + first_block= dynamic_element(&info->bitmap_blocks, first_block_pos, + MARIA_BITMAP_BLOCK*); + first_block->sub_blocks= info->bitmap_blocks.elements - first_block_pos; + DBUG_RETURN(0); +} + + +/* + Find pages to put ALL blobs + + SYNOPSIS + allocate_blobs() + info Maria handler + row Information of what is in the row (from calc_record_size()) + + RETURN + 0 ok + 1 error +*/ + +static my_bool allocate_blobs(MARIA_HA *info, MARIA_ROW *row) +{ + ulong *length, *end; + uint elements; + /* + Reserve size for: + head block + one extent + tail block + */ + elements= info->bitmap_blocks.elements; + for (length= row->blob_lengths, end= length + info->s->base.blobs; + length < end; length++) + { + if (*length && find_blob(info, *length)) + return 1; + } + row->extents_count= (info->bitmap_blocks.elements - elements); + return 0; +} + + +/* + Store in the bitmap the new size for a head page + + SYNOPSIS + use_head() + info Maria handler + page Page number to update + (Note that caller guarantees this is in the active + bitmap) + size How much free space is left on the page + block_position In which info->bitmap_block we have the + information about the head block. + + NOTES + This is used on update where we are updating an existing head page +*/ + +static void use_head(MARIA_HA *info, pgcache_page_no_t page, uint size, + uint block_position) +{ + MARIA_FILE_BITMAP *bitmap= &info->s->bitmap; + MARIA_BITMAP_BLOCK *block; + uchar *data; + uint offset, tmp, offset_page; + DBUG_ASSERT(page % bitmap->pages_covered); + + block= dynamic_element(&info->bitmap_blocks, block_position, + MARIA_BITMAP_BLOCK*); + block->page= page; + block->page_count= 1 + TAIL_BIT; + block->empty_space= size; + block->used= BLOCKUSED_TAIL; + + /* + Mark place used by reading/writing 2 bytes at a time to handle + bitmaps in overlapping bytes + */ + offset_page= (uint) (page - bitmap->page - 1) * 3; + offset= offset_page & 7; + data= bitmap->map + offset_page / 8; + tmp= uint2korr(data); + block->org_bitmap_value= (tmp >> offset) & 7; + tmp= (tmp & ~(7 << offset)) | (FULL_HEAD_PAGE << offset); + int2store(data, tmp); + bitmap->changed= 1; + DBUG_EXECUTE("bitmap", _ma_print_bitmap_changes(bitmap);); +} + + +/* + Find out where to split the row (ie, what goes in head, middle, tail etc) + + SYNOPSIS + find_where_to_split_row() + share Maria share + row Information of what is in the row (from calc_record_size()) + extents_length Number of bytes needed to store all extents + split_size Free size on the page (The head length must be less + than this) + + RETURN + row_length for the head block. +*/ + +static uint find_where_to_split_row(MARIA_SHARE *share, MARIA_ROW *row, + uint extents_length, uint split_size) +{ + uint *lengths, *lengths_end; + /* + Ensure we have the minimum required space on head page: + - Header + length of field lengths (row->min_length) + - Number of extents + - One extent + */ + uint row_length= (row->min_length + + size_to_store_key_length(extents_length) + + ROW_EXTENT_SIZE); + DBUG_ASSERT(row_length < split_size); + /* + Store first in all_field_lengths the different parts that are written + to the row. This needs to be in same order as in + ma_block_rec.c::write_block_record() + */ + row->null_field_lengths[-3]= extents_length; + row->null_field_lengths[-2]= share->base.fixed_not_null_fields_length; + row->null_field_lengths[-1]= row->field_lengths_length; + for (lengths= row->null_field_lengths - EXTRA_LENGTH_FIELDS, + lengths_end= (lengths + share->base.pack_fields - share->base.blobs + + EXTRA_LENGTH_FIELDS); lengths < lengths_end; lengths++) + { + if (row_length + *lengths > split_size) + break; + row_length+= *lengths; + } + return row_length; +} + + +/* + Find where to write the middle parts of the row and the tail + + SYNOPSIS + write_rest_of_head() + info Maria handler + position Position in bitmap_blocks. Is 0 for rows that needs + full blocks (ie, has a head, middle part and optional tail) + rest_length How much left of the head block to write. + + RETURN + 0 ok + 1 error +*/ + +static my_bool write_rest_of_head(MARIA_HA *info, uint position, + ulong rest_length) +{ + MARIA_SHARE *share= info->s; + uint full_page_size= FULL_PAGE_SIZE(share->block_size); + MARIA_BITMAP_BLOCK *block; + DBUG_ENTER("write_rest_of_head"); + DBUG_PRINT("enter", ("position: %u rest_length: %lu", position, + rest_length)); + + if (position == 0) + { + /* Write out full pages */ + uint pages= rest_length / full_page_size; + + rest_length%= full_page_size; + if (rest_length >= MAX_TAIL_SIZE(share->block_size)) + { + /* Put tail on a full page */ + pages++; + rest_length= 0; + } + if (find_mid(info, pages, 1)) + DBUG_RETURN(1); + /* + Insert empty block after full pages, to allow write_block_record() to + split segment into used + free page + */ + block= dynamic_element(&info->bitmap_blocks, 2, MARIA_BITMAP_BLOCK*); + block->page_count= 0; + block->used= 0; + } + if (rest_length) + { + if (find_tail(info, rest_length, ELEMENTS_RESERVED_FOR_MAIN_PART - 1)) + DBUG_RETURN(1); + } + else + { + /* Empty tail block */ + block= dynamic_element(&info->bitmap_blocks, + ELEMENTS_RESERVED_FOR_MAIN_PART - 1, + MARIA_BITMAP_BLOCK *); + block->page_count= 0; + block->used= 0; + } + DBUG_RETURN(0); +} + + +/* + Find where to store one row + + SYNPOSIS + _ma_bitmap_find_place() + info Maria handler + row Information about row to write + blocks Store data about allocated places here + + RETURN + 0 ok + row->space_on_head_page contains minimum number of bytes we + expect to put on the head page. + 1 error + my_errno is set to error +*/ + +my_bool _ma_bitmap_find_place(MARIA_HA *info, MARIA_ROW *row, + MARIA_BITMAP_BLOCKS *blocks) +{ + MARIA_SHARE *share= info->s; + my_bool res= 1; + uint full_page_size, position, max_page_size; + uint head_length, row_length, rest_length, extents_length; + DBUG_ENTER("_ma_bitmap_find_place"); + + blocks->count= 0; + blocks->tail_page_skipped= blocks->page_skipped= 0; + row->extents_count= 0; + + /* + Reserve place for the following blocks: + - Head block + - Full page block + - Marker block to allow write_block_record() to split full page blocks + into full and free part + - Tail block + */ + + info->bitmap_blocks.elements= ELEMENTS_RESERVED_FOR_MAIN_PART; + max_page_size= (share->block_size - PAGE_OVERHEAD_SIZE); + + pthread_mutex_lock(&share->bitmap.bitmap_lock); + + if (row->total_length <= max_page_size) + { + /* Row fits in one page */ + position= ELEMENTS_RESERVED_FOR_MAIN_PART - 1; + if (find_head(info, (uint) row->total_length, position)) + goto abort; + row->space_on_head_page= row->total_length; + goto end; + } + + /* + First allocate all blobs so that we can find out the needed size for + the main block. + */ + if (row->blob_length && allocate_blobs(info, row)) + goto abort; + + extents_length= row->extents_count * ROW_EXTENT_SIZE; + /* + The + 3 is reserved for storing the number of segments in the row header. + */ + if ((head_length= (row->head_length + extents_length + 3)) <= + max_page_size) + { + /* Main row part fits into one page */ + position= ELEMENTS_RESERVED_FOR_MAIN_PART - 1; + if (find_head(info, head_length, position)) + goto abort; + row->space_on_head_page= head_length; + goto end; + } + + /* Allocate enough space */ + head_length+= ELEMENTS_RESERVED_FOR_MAIN_PART * ROW_EXTENT_SIZE; + + /* The first segment size is stored in 'row_length' */ + row_length= find_where_to_split_row(share, row, extents_length, + max_page_size); + + full_page_size= FULL_PAGE_SIZE(share->block_size); + position= 0; + if (head_length - row_length <= full_page_size) + position= ELEMENTS_RESERVED_FOR_MAIN_PART -2; /* Only head and tail */ + if (find_head(info, row_length, position)) + goto abort; + row->space_on_head_page= row_length; + + rest_length= head_length - row_length; + if (write_rest_of_head(info, position, rest_length)) + goto abort; + +end: + blocks->block= dynamic_element(&info->bitmap_blocks, position, + MARIA_BITMAP_BLOCK*); + blocks->block->sub_blocks= ELEMENTS_RESERVED_FOR_MAIN_PART - position; + /* First block's page_count is for all blocks */ + blocks->count= info->bitmap_blocks.elements - position; + res= 0; + +abort: + pthread_mutex_unlock(&share->bitmap.bitmap_lock); + DBUG_RETURN(res); +} + + +/* + Find where to put row on update (when head page is already defined) + + SYNPOSIS + _ma_bitmap_find_new_place() + info Maria handler + row Information about row to write + page On which page original row was stored + free_size Free size on head page + blocks Store data about allocated places here + + NOTES + This function is only called when the new row can't fit in the space of + the old row in the head page. + + This is essently same as _ma_bitmap_find_place() except that + we don't call find_head() to search in bitmaps where to put the page. + + RETURN + 0 ok + 1 error +*/ + +my_bool _ma_bitmap_find_new_place(MARIA_HA *info, MARIA_ROW *row, + pgcache_page_no_t page, uint free_size, + MARIA_BITMAP_BLOCKS *blocks) +{ + MARIA_SHARE *share= info->s; + my_bool res= 1; + uint position; + uint head_length, row_length, rest_length, extents_length; + ulonglong bitmap_page; + DBUG_ENTER("_ma_bitmap_find_new_place"); + + blocks->count= 0; + blocks->tail_page_skipped= blocks->page_skipped= 0; + row->extents_count= 0; + info->bitmap_blocks.elements= ELEMENTS_RESERVED_FOR_MAIN_PART; + + pthread_mutex_lock(&share->bitmap.bitmap_lock); + + /* + First allocate all blobs (so that we can find out the needed size for + the main block. + */ + if (row->blob_length && allocate_blobs(info, row)) + goto abort; + + /* Switch bitmap to current head page */ + bitmap_page= page / share->bitmap.pages_covered; + bitmap_page*= share->bitmap.pages_covered; + + if (share->bitmap.page != bitmap_page && + _ma_change_bitmap_page(info, &share->bitmap, bitmap_page)) + goto abort; + + extents_length= row->extents_count * ROW_EXTENT_SIZE; + if ((head_length= (row->head_length + extents_length + 3)) <= free_size) + { + /* Main row part fits into one page */ + position= ELEMENTS_RESERVED_FOR_MAIN_PART - 1; + use_head(info, page, head_length, position); + row->space_on_head_page= head_length; + goto end; + } + + /* Allocate enough space */ + head_length+= ELEMENTS_RESERVED_FOR_MAIN_PART * ROW_EXTENT_SIZE; + + /* The first segment size is stored in 'row_length' */ + row_length= find_where_to_split_row(share, row, extents_length, free_size); + + position= 0; + if (head_length - row_length < MAX_TAIL_SIZE(share->block_size)) + position= ELEMENTS_RESERVED_FOR_MAIN_PART -2; /* Only head and tail */ + use_head(info, page, row_length, position); + row->space_on_head_page= row_length; + + rest_length= head_length - row_length; + if (write_rest_of_head(info, position, rest_length)) + goto abort; + +end: + blocks->block= dynamic_element(&info->bitmap_blocks, position, + MARIA_BITMAP_BLOCK*); + blocks->block->sub_blocks= ELEMENTS_RESERVED_FOR_MAIN_PART - position; + /* First block's page_count is for all blocks */ + blocks->count= info->bitmap_blocks.elements - position; + res= 0; + +abort: + pthread_mutex_unlock(&share->bitmap.bitmap_lock); + DBUG_RETURN(res); +} + + +/**************************************************************************** + Clear and reset bits +****************************************************************************/ + +/* + Set fill pattern for a page + + set_page_bits() + info Maria handler + bitmap Bitmap handler + page Adress to page + fill_pattern Pattern (not size) for page + + NOTES + Page may not be part of active bitmap + + RETURN + 0 ok + 1 error +*/ + +static my_bool set_page_bits(MARIA_HA *info, MARIA_FILE_BITMAP *bitmap, + pgcache_page_no_t page, uint fill_pattern) +{ + pgcache_page_no_t bitmap_page; + uint offset_page, offset, tmp, org_tmp; + uchar *data; + DBUG_ENTER("set_page_bits"); + + bitmap_page= page - page % bitmap->pages_covered; + if (bitmap_page != bitmap->page && + _ma_change_bitmap_page(info, bitmap, bitmap_page)) + DBUG_RETURN(1); + + /* Find page number from start of bitmap */ + offset_page= (uint) (page - bitmap->page - 1); + /* + Mark place used by reading/writing 2 bytes at a time to handle + bitmaps in overlapping bytes + */ + offset_page*= 3; + offset= offset_page & 7; + data= bitmap->map + offset_page / 8; + org_tmp= tmp= uint2korr(data); + tmp= (tmp & ~(7 << offset)) | (fill_pattern << offset); + if (tmp == org_tmp) + DBUG_RETURN(0); /* No changes */ + int2store(data, tmp); + + bitmap->changed= 1; + DBUG_EXECUTE("bitmap", _ma_print_bitmap_changes(bitmap);); + if (fill_pattern != 3 && fill_pattern != 7) + set_if_smaller(info->s->state.first_bitmap_with_space, bitmap_page); + /* + Note that if the condition above is false (page is full), and all pages of + this bitmap are now full, and that bitmap page was + first_bitmap_with_space, we don't modify first_bitmap_with_space, indeed + its value still tells us where to start our search for a bitmap with space + (which is for sure after this full one). + That does mean that first_bitmap_with_space is only a lower bound. + */ + DBUG_RETURN(0); +} + + +/* + Get bitmap pattern for a given page + + SYNOPSIS + get_page_bits() + info Maria handler + bitmap Bitmap handler + page Page number + + RETURN + 0-7 Bitmap pattern + ~0 Error (couldn't read page) +*/ + +uint _ma_bitmap_get_page_bits(MARIA_HA *info, MARIA_FILE_BITMAP *bitmap, + pgcache_page_no_t page) +{ + pgcache_page_no_t bitmap_page; + uint offset_page, offset, tmp; + uchar *data; + DBUG_ENTER("_ma_bitmap_get_page_bits"); + + bitmap_page= page - page % bitmap->pages_covered; + if (bitmap_page != bitmap->page && + _ma_change_bitmap_page(info, bitmap, bitmap_page)) + DBUG_RETURN(~ (uint) 0); + + /* Find page number from start of bitmap */ + offset_page= (uint) (page - bitmap->page - 1); + /* + Mark place used by reading/writing 2 bytes at a time to handle + bitmaps in overlapping bytes + */ + offset_page*= 3; + offset= offset_page & 7; + data= bitmap->map + offset_page / 8; + tmp= uint2korr(data); + DBUG_RETURN((tmp >> offset) & 7); +} + + +/* + Mark all pages in a region as free + + SYNOPSIS + _ma_bitmap_reset_full_page_bits() + info Maria handler + bitmap Bitmap handler + page Start page + page_count Number of pages + + NOTES + We assume that all pages in region is covered by same bitmap + One must have a lock on info->s->bitmap.bitmap_lock + + RETURN + 0 ok + 1 Error (when reading bitmap) +*/ + +my_bool _ma_bitmap_reset_full_page_bits(MARIA_HA *info, + MARIA_FILE_BITMAP *bitmap, + pgcache_page_no_t page, + uint page_count) +{ + ulonglong bitmap_page; + uint offset, bit_start, bit_count, tmp; + uchar *data; + DBUG_ENTER("_ma_bitmap_reset_full_page_bits"); + DBUG_PRINT("enter", ("page: %lu page_count: %u", (ulong) page, page_count)); + safe_mutex_assert_owner(&info->s->bitmap.bitmap_lock); + + bitmap_page= page - page % bitmap->pages_covered; + DBUG_ASSERT(page != bitmap_page); + + if (bitmap_page != bitmap->page && + _ma_change_bitmap_page(info, bitmap, bitmap_page)) + DBUG_RETURN(1); + + /* Find page number from start of bitmap */ + offset= (uint) (page - bitmap->page - 1); + + /* Clear bits from 'page * 3' -> '(page + page_count) * 3' */ + bit_start= offset * 3; + bit_count= page_count * 3; + + data= bitmap->map + bit_start / 8; + offset= bit_start & 7; + + tmp= (255 << offset); /* Bits to keep */ + if (bit_count + offset < 8) + { + /* Only clear bits between 'offset' and 'offset+bit_count-1' */ + tmp^= (255 << (offset + bit_count)); + } + *data&= ~tmp; + + if ((int) (bit_count-= (8 - offset)) > 0) + { + uint fill; + data++; + /* + -1 is here to avoid one 'if' statement and to let the following code + handle the last byte + */ + if ((fill= (bit_count - 1) / 8)) + { + bzero(data, fill); + data+= fill; + } + bit_count-= fill * 8; /* Bits left to clear */ + tmp= (1 << bit_count) - 1; + *data&= ~tmp; + } + set_if_smaller(info->s->state.first_bitmap_with_space, bitmap_page); + bitmap->changed= 1; + DBUG_EXECUTE("bitmap", _ma_print_bitmap_changes(bitmap);); + DBUG_RETURN(0); +} + +/* + Set all pages in a region as used + + SYNOPSIS + _ma_bitmap_set_full_page_bits() + info Maria handler + bitmap Bitmap handler + page Start page + page_count Number of pages + + NOTES + We assume that all pages in region is covered by same bitmap + One must have a lock on info->s->bitmap.bitmap_lock + + RETURN + 0 ok + 1 Error (when reading bitmap) +*/ + +my_bool _ma_bitmap_set_full_page_bits(MARIA_HA *info, + MARIA_FILE_BITMAP *bitmap, + pgcache_page_no_t page, uint page_count) +{ + ulonglong bitmap_page; + uint offset, bit_start, bit_count, tmp; + uchar *data; + DBUG_ENTER("_ma_bitmap_set_full_page_bits"); + DBUG_PRINT("enter", ("page: %lu page_count: %u", (ulong) page, page_count)); + safe_mutex_assert_owner(&info->s->bitmap.bitmap_lock); + + bitmap_page= page - page % bitmap->pages_covered; + if (bitmap_page != bitmap->page && + _ma_change_bitmap_page(info, bitmap, bitmap_page)) + DBUG_RETURN(1); + + /* Find page number from start of bitmap */ + offset= (uint) (page - bitmap->page - 1); + + /* Set bits from 'page * 3' -> '(page + page_count) * 3' */ + bit_start= offset * 3; + bit_count= page_count * 3; + + data= bitmap->map + bit_start / 8; + offset= bit_start & 7; + + tmp= (255 << offset); /* Bits to keep */ + if (bit_count + offset < 8) + { + /* Only set bits between 'offset' and 'offset+bit_count-1' */ + tmp^= (255 << (offset + bit_count)); + } + *data|= tmp; + + if ((int) (bit_count-= (8 - offset)) > 0) + { + uint fill; + data++; + /* + -1 is here to avoid one 'if' statement and to let the following code + handle the last byte + */ + if ((fill= (bit_count - 1) / 8)) + { + bfill(data, fill, 255); + data+= fill; + } + bit_count-= fill * 8; /* Bits left to set */ + tmp= (1 << bit_count) - 1; + *data|= tmp; + } + bitmap->changed= 1; + DBUG_EXECUTE("bitmap", _ma_print_bitmap_changes(bitmap);); + DBUG_RETURN(0); +} + + +/** + @brief + Make a transition of MARIA_FILE_BITMAP::non_flushable. + If the bitmap becomes flushable, which requires that REDO-UNDO has been + logged and all bitmap pages touched by the thread have a correct + allocation, it unpins all bitmap pages, and if _ma_bitmap_flush_all() is + waiting (in practice it is a checkpoint), it wakes it up. + If the bitmap becomes or stays unflushable, the function merely records it + unless a concurrent _ma_bitmap_flush_all() is happening, in which case the + function first waits for the flush to be done. + + @note + this sets info->non_flushable_state to 1 if we have incremented + bitmap->non_flushable and not yet decremented it. + + @param share Table's share + @param non_flushable_inc Increment of MARIA_FILE_BITMAP::non_flushable + (-1 or +1). +*/ + +void _ma_bitmap_flushable(MARIA_HA *info, int non_flushable_inc) +{ + MARIA_SHARE *share= info->s; + MARIA_FILE_BITMAP *bitmap; + DBUG_ENTER("_ma_bitmap_flushable"); + + /* + Not transactional tables are never automaticly flushed and needs no + protection + */ + if (!share->now_transactional) + DBUG_VOID_RETURN; + + bitmap= &share->bitmap; + if (non_flushable_inc == -1) + { + pthread_mutex_lock(&bitmap->bitmap_lock); + DBUG_ASSERT((int) bitmap->non_flushable > 0); + DBUG_ASSERT(info->non_flushable_state == 1); + info->non_flushable_state= 0; + if (--bitmap->non_flushable == 0) + { + /* + We unlock and unpin pages locked and pinned by other threads. It does + not seem to be an issue as all bitmap changes are serialized with + the bitmap's mutex. + */ + _ma_bitmap_unpin_all(share); + if (unlikely(bitmap->flush_all_requested)) + { + DBUG_PRINT("info", ("bitmap flushable waking up flusher")); + pthread_cond_broadcast(&bitmap->bitmap_cond); + } + } + DBUG_PRINT("info", ("bitmap->non_flushable: %u", bitmap->non_flushable)); + pthread_mutex_unlock(&bitmap->bitmap_lock); + DBUG_VOID_RETURN; + } + DBUG_ASSERT(non_flushable_inc == 1); + DBUG_ASSERT(info->non_flushable_state == 0); + pthread_mutex_lock(&bitmap->bitmap_lock); + while (unlikely(bitmap->flush_all_requested)) + { + /* + Some other thread is waiting for the bitmap to become + flushable. Not the moment to make the bitmap unflushable or more + unflushable; let's rather back off and wait. If we didn't do this, with + multiple writers, there may always be one thread causing the bitmap to + be unflushable and _ma_bitmap_flush_all() would wait for long. + There should not be a deadlock because if our thread increased + non_flushable (and thus _ma_bitmap_flush_all() is waiting for at least + our thread), it is not going to increase it more so is not going to come + here. + */ + DBUG_PRINT("info", ("waiting for bitmap flusher")); + pthread_cond_wait(&bitmap->bitmap_cond, &bitmap->bitmap_lock); + } + bitmap->non_flushable++; + info->non_flushable_state= 1; + DBUG_PRINT("info", ("bitmap->non_flushable: %u", bitmap->non_flushable)); + pthread_mutex_unlock(&bitmap->bitmap_lock); + DBUG_VOID_RETURN; +} + + +/* + Correct bitmap pages to reflect the true allocation + + SYNOPSIS + _ma_bitmap_release_unused() + info Maria handle + blocks Bitmap blocks + + IMPLEMENTATION + If block->used & BLOCKUSED_TAIL is set: + If block->used & BLOCKUSED_USED is set, then the bits for the + corresponding page is set according to block->empty_space + If block->used & BLOCKUSED_USED is not set, then the bits for + the corresponding page is set to org_bitmap_value; + + If block->used & BLOCKUSED_TAIL is not set: + if block->used is not set, the bits for the corresponding page are + cleared + + For the first block (head block) the logic is same as for a tail block + + Note that we may have 'filler blocks' that are used to split a block + in half; These can be recognized by that they have page_count == 0. + + RETURN + 0 ok + 1 error (Couldn't write or read bitmap page) +*/ + +my_bool _ma_bitmap_release_unused(MARIA_HA *info, MARIA_BITMAP_BLOCKS *blocks) +{ + MARIA_BITMAP_BLOCK *block= blocks->block, *end= block + blocks->count; + MARIA_FILE_BITMAP *bitmap= &info->s->bitmap; + uint bits, current_bitmap_value; + DBUG_ENTER("_ma_bitmap_release_unused"); + + /* + We can skip FULL_HEAD_PAGE (4) as the page was marked as 'full' + when we allocated space in the page + */ + current_bitmap_value= FULL_HEAD_PAGE; + + pthread_mutex_lock(&bitmap->bitmap_lock); + + /* First handle head block */ + if (block->used & BLOCKUSED_USED) + { + DBUG_PRINT("info", ("head page: %lu empty_space: %u", + (ulong) block->page, block->empty_space)); + bits= _ma_free_size_to_head_pattern(bitmap, block->empty_space); + if (block->used & BLOCKUSED_USE_ORG_BITMAP) + current_bitmap_value= block->org_bitmap_value; + } + else + bits= block->org_bitmap_value; + if (bits != current_bitmap_value) + { + if (set_page_bits(info, bitmap, block->page, bits)) + goto err; + } + else + { + DBUG_ASSERT(current_bitmap_value == + _ma_bitmap_get_page_bits(info, bitmap, block->page)); + } + + /* Handle all full pages and tail pages (for head page and blob) */ + for (block++; block < end; block++) + { + uint page_count; + if (!block->page_count) + continue; /* Skip 'filler blocks' */ + + page_count= block->page_count; + if (block->used & BLOCKUSED_TAIL) + { + current_bitmap_value= FULL_TAIL_PAGE; + /* The bitmap page is only one page */ + page_count= 1; + if (block->used & BLOCKUSED_USED) + { + DBUG_PRINT("info", ("tail page: %lu empty_space: %u", + (ulong) block->page, block->empty_space)); + bits= free_size_to_tail_pattern(bitmap, block->empty_space); + if (block->used & BLOCKUSED_USE_ORG_BITMAP) + current_bitmap_value= block->org_bitmap_value; + } + else + bits= block->org_bitmap_value; + + /* + The page has all bits set; The following test is an optimization + to not set the bits to the same value as before. + */ + if (bits != current_bitmap_value && + set_page_bits(info, bitmap, block->page, bits)) + goto err; + } + else if (!(block->used & BLOCKUSED_USED) && + _ma_bitmap_reset_full_page_bits(info, bitmap, + block->page, page_count)) + goto err; + } + + /* This duplicates ma_bitmap_flushable(-1) except it already has mutex */ + if (info->non_flushable_state) + { + DBUG_ASSERT(((int) (bitmap->non_flushable)) > 0); + info->non_flushable_state= 0; + if (--bitmap->non_flushable == 0) + { + _ma_bitmap_unpin_all(info->s); + if (unlikely(bitmap->flush_all_requested)) + { + DBUG_PRINT("info", ("bitmap flushable waking up flusher")); + pthread_cond_broadcast(&bitmap->bitmap_cond); + } + } + } + DBUG_PRINT("info", ("bitmap->non_flushable: %u", bitmap->non_flushable)); + + pthread_mutex_unlock(&bitmap->bitmap_lock); + DBUG_RETURN(0); + +err: + pthread_mutex_unlock(&bitmap->bitmap_lock); + DBUG_RETURN(1); +} + + +/* + Free full pages from bitmap and pagecache + + SYNOPSIS + _ma_bitmap_free_full_pages() + info Maria handle + extents Extents (as stored on disk) + count Number of extents + + IMPLEMENTATION + Mark all full pages (not tails) from extents as free, both in bitmap + and page cache. + + RETURN + 0 ok + 1 error (Couldn't write or read bitmap page) +*/ + +my_bool _ma_bitmap_free_full_pages(MARIA_HA *info, const uchar *extents, + uint count) +{ + MARIA_FILE_BITMAP *bitmap= &info->s->bitmap; + DBUG_ENTER("_ma_bitmap_free_full_pages"); + + pthread_mutex_lock(&bitmap->bitmap_lock); + for (; count--; extents+= ROW_EXTENT_SIZE) + { + pgcache_page_no_t page= uint5korr(extents); + uint page_count= (uint2korr(extents + ROW_EXTENT_PAGE_SIZE) & + ~START_EXTENT_BIT); + if (!(page_count & TAIL_BIT)) + { + if (page == 0 && page_count == 0) + continue; /* Not used extent */ + if (pagecache_delete_pages(info->s->pagecache, &info->dfile, page, + page_count, PAGECACHE_LOCK_WRITE, 1) || + _ma_bitmap_reset_full_page_bits(info, bitmap, page, page_count)) + { + pthread_mutex_unlock(&bitmap->bitmap_lock); + DBUG_RETURN(1); + } + } + } + pthread_mutex_unlock(&bitmap->bitmap_lock); + DBUG_RETURN(0); +} + + +/* + Mark in the bitmap how much free space there is on a page + + SYNOPSIS + _ma_bitmap_set() + info Maria handler + page Adress to page + head 1 if page is a head page, 0 if tail page + empty_space How much empty space there is on page + + RETURN + 0 ok + 1 error +*/ + +my_bool _ma_bitmap_set(MARIA_HA *info, pgcache_page_no_t page, my_bool head, + uint empty_space) +{ + MARIA_FILE_BITMAP *bitmap= &info->s->bitmap; + uint bits; + my_bool res; + DBUG_ENTER("_ma_bitmap_set"); + + pthread_mutex_lock(&info->s->bitmap.bitmap_lock); + bits= (head ? + _ma_free_size_to_head_pattern(bitmap, empty_space) : + free_size_to_tail_pattern(bitmap, empty_space)); + res= set_page_bits(info, bitmap, page, bits); + pthread_mutex_unlock(&info->s->bitmap.bitmap_lock); + DBUG_RETURN(res); +} + + +/* + Check that bitmap pattern is correct for a page + + NOTES + Used in maria_chk + + SYNOPSIS + _ma_check_bitmap_data() + info Maria handler + page_type What kind of page this is + page Adress to page + empty_space Empty space on page + bitmap_pattern Store here the pattern that was in the bitmap for the + page. This is always updated. + + RETURN + 0 ok + 1 error +*/ + +my_bool _ma_check_bitmap_data(MARIA_HA *info, + enum en_page_type page_type, pgcache_page_no_t page, + uint empty_space, uint *bitmap_pattern) +{ + uint bits; + switch (page_type) { + case UNALLOCATED_PAGE: + case MAX_PAGE_TYPE: + bits= 0; + break; + case HEAD_PAGE: + bits= _ma_free_size_to_head_pattern(&info->s->bitmap, empty_space); + break; + case TAIL_PAGE: + bits= free_size_to_tail_pattern(&info->s->bitmap, empty_space); + break; + case BLOB_PAGE: + bits= FULL_TAIL_PAGE; + break; + default: + bits= 0; /* to satisfy compiler */ + DBUG_ASSERT(0); + } + return ((*bitmap_pattern= _ma_bitmap_get_page_bits(info, &info->s->bitmap, + page)) != bits); +} + + +/* + Check if the page type matches the one that we have in the bitmap + + SYNOPSIS + _ma_check_if_right_bitmap_type() + info Maria handler + page_type What kind of page this is + page Adress to page + bitmap_pattern Store here the pattern that was in the bitmap for the + page. This is always updated. + + NOTES + Used in maria_chk + + RETURN + 0 ok + 1 error +*/ + +my_bool _ma_check_if_right_bitmap_type(MARIA_HA *info, + enum en_page_type page_type, + pgcache_page_no_t page, + uint *bitmap_pattern) +{ + if ((*bitmap_pattern= _ma_bitmap_get_page_bits(info, &info->s->bitmap, + page)) > 7) + return 1; /* Couldn't read page */ + switch (page_type) { + case HEAD_PAGE: + return *bitmap_pattern < 1 || *bitmap_pattern > 4; + case TAIL_PAGE: + return *bitmap_pattern < 5; + case BLOB_PAGE: + return *bitmap_pattern != 7; + default: + break; + } + DBUG_ASSERT(0); + return 1; +} + + +/** + @brief create the first bitmap page of a freshly created data file + + @param share table's share + + @return Operation status + @retval 0 OK + @retval !=0 Error +*/ + +int _ma_bitmap_create_first(MARIA_SHARE *share) +{ + uint block_size= share->bitmap.block_size; + File file= share->bitmap.file.file; + uchar marker[CRC_SIZE]; + + /* + Next write operation of the page will write correct CRC + if it is needed + */ + int4store(marker, MARIA_NO_CRC_BITMAP_PAGE); + + if (my_chsize(file, block_size - sizeof(marker), + 0, MYF(MY_WME)) || + my_pwrite(file, marker, sizeof(marker), + block_size - sizeof(marker), + MYF(MY_NABP | MY_WME))) + return 1; + share->state.state.data_file_length= block_size; + _ma_bitmap_delete_all(share); + return 0; +} + + +/** + @brief Pagecache callback to get the TRANSLOG_ADDRESS to flush up to, when a + bitmap page needs to be flushed. + + @param page Page's content + @param page_no Page's number (<offset>/<page length>) + @param data_ptr Callback data pointer (pointer to MARIA_SHARE) + + @retval TRANSLOG_ADDRESS to flush up to. +*/ + +static my_bool +flush_log_for_bitmap(uchar *page __attribute__((unused)), + pgcache_page_no_t page_no __attribute__((unused)), + uchar *data_ptr __attribute__((unused))) +{ +#ifndef DBUG_OFF + const MARIA_SHARE *share= (MARIA_SHARE*)data_ptr; +#endif + DBUG_ENTER("flush_log_for_bitmap"); + DBUG_ASSERT(share->now_transactional); + /* + WAL imposes that UNDOs reach disk before bitmap is flushed. We don't know + the LSN of the last UNDO about this bitmap page, so we flush whole log. + */ + DBUG_RETURN(translog_flush(translog_get_horizon())); +} + + +/** + @brief Set callbacks for bitmap pages + + @note + We don't use pagecache_file_init here, as we want to keep the + code readable +*/ + +void _ma_bitmap_set_pagecache_callbacks(PAGECACHE_FILE *file, + MARIA_SHARE *share) +{ + file->callback_data= (uchar*) share; + file->flush_log_callback= maria_flush_log_for_page_none; + file->write_fail= maria_page_write_failure; + + if (share->temporary) + { + file->read_callback= &maria_page_crc_check_none; + file->write_callback= &maria_page_filler_set_none; + } + else + { + file->read_callback= &maria_page_crc_check_bitmap; + if (share->options & HA_OPTION_PAGE_CHECKSUM) + file->write_callback= &maria_page_crc_set_normal; + else + file->write_callback= &maria_page_filler_set_bitmap; + if (share->now_transactional) + file->flush_log_callback= flush_log_for_bitmap; + } +} + + +/** + Extends data file with zeroes and creates new bitmap pages into page cache. + + Writes all bitmap pages in [from, to]. + + Non-bitmap pages of zeroes are correct as they are marked empty in + bitmaps. Bitmap pages will not be zeroes: they will get their CRC fixed when + flushed. And if there is a crash before flush (so they are zeroes at + restart), a REDO will re-create them in page cache. +*/ + +static my_bool +_ma_bitmap_create_missing_into_pagecache(MARIA_SHARE *share, + MARIA_FILE_BITMAP *bitmap, + pgcache_page_no_t from, + pgcache_page_no_t to, + uchar *zeroes) +{ + pgcache_page_no_t i; + /* + We do not use my_chsize() because there can be a race between when it + reads the physical size and when it writes (assume data_file_length is 10, + physical length is 8 and two data pages are in cache, and here we do a + my_chsize: my_chsize sees physical length is 8, then the two data pages go + to disk then my_chsize writes from page 8 and so overwrites the two data + pages, wrongly). + We instead rely on the filesystem filling gaps with zeroes. + */ + for (i= from; i <= to; i+= bitmap->pages_covered) + { + /** + No need to keep them pinned, they are new so flushable. + @todo but we may want to keep them pinned, as an optimization: if they + are not pinned they may go to disk before the data pages go (so, the + physical pages would be in non-ascending "sparse" order on disk), or the + filesystem may fill gaps with zeroes physically which is a waste of + time. + */ + if (pagecache_write(share->pagecache, + &bitmap->file, i, 0, + zeroes, PAGECACHE_PLAIN_PAGE, + PAGECACHE_LOCK_LEFT_UNLOCKED, + PAGECACHE_PIN_LEFT_UNPINNED, + PAGECACHE_WRITE_DELAY, 0, LSN_IMPOSSIBLE)) + goto err; + } + /* + Data pages after data_file_length are full of zeroes but that is allowed + as they are marked empty in the bitmap. + */ + return FALSE; +err: + return TRUE; +} + + +/** + Creates missing bitmaps when we extend the data file. + + At run-time, when we need a new bitmap page we come here; and only one bitmap + page at a time is created. + + In some recovery cases we insert at a large offset in the data file, way + beyond state.data_file_length, so can need to create more than one bitmap + page in one go. Known case is: + Start a transaction in Maria; + delete last row of very large table (with delete_row) + do a bulk insert + crash + Then UNDO_BULK_INSERT will truncate table files, and + UNDO_ROW_DELETE will want to put the row back to its original position, + extending the data file a lot: bitmap page*s* in the hole must be created, + or he table would look corrupted. + + We need to log REDOs for bitmap creation, consider: we apply a REDO for a + data page, which creates the first data page covered by a new bitmap + not yet created. If the data page is flushed but the bitmap page is not and + there is a crash, re-execution of the REDO will complain about the zeroed + bitmap page (see it as corruption). Thus a REDO is needed to re-create the + bitmap. + + @param info Maria handler + @param bitmap Bitmap handler + @param page Last bitmap page to create + + @note When this function is called this must be true: + ((page + 1) * bitmap->block_size > info->s->state.state.data_file_length) + +*/ + +static my_bool _ma_bitmap_create_missing(MARIA_HA *info, + MARIA_FILE_BITMAP *bitmap, + pgcache_page_no_t page) +{ + MARIA_SHARE *share= info->s; + uint block_size= bitmap->block_size; + pgcache_page_no_t from, to; + my_off_t data_file_length= share->state.state.data_file_length; + DBUG_ENTER("_ma_bitmap_create_missing"); + + /* First (in offset order) bitmap page to create */ + if (data_file_length < block_size) + goto err; /* corrupted, should have first bitmap page */ + + from= (data_file_length / block_size - 1) / bitmap->pages_covered + 1; + from*= bitmap->pages_covered; + /* + page>=from because: + (page + 1) * bs > dfl, and page == k * pc so: + (k * pc + 1) * bs > dfl; k * pc + 1 > dfl / bs; k * pc > dfl / bs - 1 + k > (dfl / bs - 1) / pc; k >= (dfl / bs - 1) / pc + 1 + k * pc >= ((dfl / bs - 1) / pc + 1) * pc == from. + */ + DBUG_ASSERT(page >= from); + + if (share->now_transactional) + { + LSN lsn; + uchar log_data[FILEID_STORE_SIZE + PAGE_STORE_SIZE * 2]; + LEX_CUSTRING log_array[TRANSLOG_INTERNAL_PARTS + 1]; + page_store(log_data + FILEID_STORE_SIZE, from); + page_store(log_data + FILEID_STORE_SIZE + PAGE_STORE_SIZE, page); + log_array[TRANSLOG_INTERNAL_PARTS + 0].str= log_data; + log_array[TRANSLOG_INTERNAL_PARTS + 0].length= sizeof(log_data); + /* + We don't use info->trn so that this REDO is always executed even though + the UNDO does not reach disk due to crash. This is also consistent with + the fact that the new bitmap pages are not pinned. + */ + if (translog_write_record(&lsn, LOGREC_REDO_BITMAP_NEW_PAGE, + &dummy_transaction_object, info, + (translog_size_t)sizeof(log_data), + TRANSLOG_INTERNAL_PARTS + 1, log_array, + log_data, NULL)) + goto err; + /* + No need to flush the log: the bitmap pages we are going to create will + flush it when they go to disk. + */ + } + + /* + Last bitmap page. It has special creation: will go to the page cache + only later as we are going to modify it very soon. + */ + bzero(bitmap->map, bitmap->block_size); + bitmap->used_size= 0; +#ifndef DBUG_OFF + memcpy(bitmap->map + bitmap->block_size, bitmap->map, bitmap->block_size); +#endif + + /* Last bitmap page to create before 'page' */ + DBUG_ASSERT(page >= bitmap->pages_covered); + to= page - bitmap->pages_covered; + /* + In run-time situations, from>=to is always false, i.e. we always create + one bitmap at a time ('page'). + */ + if ((from <= to) && + _ma_bitmap_create_missing_into_pagecache(share, bitmap, from, to, + bitmap->map)) + goto err; + + share->state.state.data_file_length= (page + 1) * bitmap->block_size; + + DBUG_RETURN(FALSE); +err: + DBUG_RETURN(TRUE); +} + + +my_bool _ma_apply_redo_bitmap_new_page(MARIA_HA *info, + LSN lsn __attribute__ ((unused)), + const uchar *header) +{ + MARIA_SHARE *share= info->s; + MARIA_FILE_BITMAP *bitmap= &share->bitmap; + my_bool error; + pgcache_page_no_t from, to, min_from; + DBUG_ENTER("_ma_apply_redo_bitmap_new_page"); + + from= page_korr(header); + to= page_korr(header + PAGE_STORE_SIZE); + DBUG_PRINT("info", ("from: %lu to: %lu", (ulong)from, (ulong)to)); + if ((from > to) || + (from % bitmap->pages_covered) != 0 || + (to % bitmap->pages_covered) != 0) + { + error= TRUE; /* corrupted log record */ + goto err; + } + + min_from= (share->state.state.data_file_length / bitmap->block_size - 1) / + bitmap->pages_covered + 1; + min_from*= bitmap->pages_covered; + if (from < min_from) + { + DBUG_PRINT("info", ("overwrite bitmap pages from %lu", (ulong)min_from)); + /* + We have to overwrite. It could be that there was a bitmap page in + memory, covering a data page which went to disk, then crash: the + bitmap page is now full of zeros and is ==min_from, we have to overwrite + it with correct checksum. + */ + } + share->state.changed|= STATE_CHANGED; + bzero(info->buff, bitmap->block_size); + if (!(error= + _ma_bitmap_create_missing_into_pagecache(share, bitmap, from, to, + info->buff))) + share->state.state.data_file_length= (to + 1) * bitmap->block_size; + +err: + DBUG_RETURN(error); +} diff --git a/storage/maria/ma_blockrec.c b/storage/maria/ma_blockrec.c new file mode 100644 index 00000000000..0ac73f89776 --- /dev/null +++ b/storage/maria/ma_blockrec.c @@ -0,0 +1,7173 @@ +/* Copyright (C) 2007-2008 Michael Widenius + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You 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 */ + +/* + Storage of records in block + + Some clarifications about the abbrev used: + + NULL fields -> Fields that may have contain a NULL value. + Not null fields -> Fields that may not contain a NULL value. + Critical fields -> Fields that can't be null and can't be dropped without + causing a table reorganization. + + + Maria will have a LSN at start of each page (excluding the bitmap pages) + + The different page types that are in a data file are: + + Bitmap pages Map of free pages in the next extent (8192 page size + gives us 256M of mapped pages / bitmap) + Head page Start of rows are stored on this page. + A rowid always points to a head page + Blob page This page is totally filled with data from one blob or by + a set of long VARCHAR/CHAR fields + Tail page This contains the last part from different rows, blobs + or varchar fields. + + The data file starts with a bitmap page, followed by as many data + pages as the bitmap can cover. After this there is a new bitmap page + and more data pages etc. + + For information about the bitmap page, see ma_bitmap.c + + Structure of data and tail page: + + The page has a row directory at end of page to allow us to do deletes + without having to reorganize the page. It also allows us to later store + some more bytes after each row to allow them to grow without having to move + around other rows. + + Page header: + + LSN 7 bytes Log position for last page change + PAGE_TYPE 1 uchar 1 for head / 2 for tail / 3 for blob + DIR_COUNT 1 uchar Number of row/tail entries on page + FREE_DIR_LINK 1 uchar Pointer to first free director entry or 255 if no + empty space 2 bytes Empty space on page + + The most significant bit in PAGE_TYPE is set to 1 if the data on the page + can be compacted to get more space. (PAGE_CAN_BE_COMPACTED) + + Row data + + Row directory of NO entries, that consist of the following for each row + (in reverse order; i.e., first record is stored last): + + Position 2 bytes Position of row on page + Length 2 bytes Length of entry + + For Position and Length, the 1 most significant bit of the position and + the 1 most significant bit of the length could be used for some states of + the row (in other words, we should try to keep these reserved) + + Position is 0 if the entry is not used. In this case length[0] points + to a previous free entry (255 if no previous entry) and length[1] + to the next free entry (or 255 if last free entry). This works because + the directory entry 255 can never be marked free (if the first directory + entry is freed, the directory is shrinked). + + checksum 4 bytes Reserved for full page read testing and live backup. + + ---------------- + + Structure of blob pages: + + LSN 7 bytes Log position for last page change + PAGE_TYPE 1 uchar 3 + + data + + ----------------- + + Row data structure: + + Flag 1 uchar Marker of which header field exists + TRANSID 6 bytes TRANSID of changing transaction + (optional, added on insert and first + update/delete) + VER_PTR 7 bytes Pointer to older version in log + (undo record) + (optional, added after first + update/delete) + DELETE_TRANSID 6 bytes (optional). TRANSID of original row. + Added on delete. + Nulls_extended 1 uchar To allow us to add new DEFAULT NULL + fields (optional, added after first + change of row after alter table) + Number of ROW_EXTENT's 1-3 uchar Length encoded, optional + This is the number of extents the + row is split into + First row_extent 7 uchar Pointer to first row extent (optional) + + Total length of length array 1-3 uchar Only used if we have + char/varchar/blob fields. + Row checksum 1 uchar Only if table created with checksums + Null_bits .. One bit for each NULL field (a field that may + have the value NULL) + Empty_bits .. One bit for each field that may be 'empty'. + (Both for null and not null fields). + This bit is 1 if the value for the field is + 0 or empty string. + + field_offsets 2 byte/offset + For each 32'th field, there is one offset + that points to where the field information + starts in the block. This is to provide + fast access to later field in the row + when we only need to return a small + set of fields. + TODO: Implement this. + + Things marked above as 'optional' will only be present if the + corresponding bit is set in 'Flag' field. Flag gives us a way to + get more space on a page when doing page compaction as we don't need + to store TRANSID that have committed before the smallest running + transaction we have in memory. + + Data in the following order: + (Field order is precalculated when table is created) + + Critical fixed length, not null, fields. (Note, these can't be dropped) + Fixed length, null fields + + Length array, 1-4 uchar per field for all CHAR/VARCHAR/BLOB fields. + Number of bytes used in length array per entry is depending on max length + for field. + + ROW_EXTENT's + CHAR data (space stripped) + VARCHAR data + BLOB data + + Fields marked in null_bits or empty_bits are not stored in data part or + length array. + + If row doesn't fit into the given block, then the first EXTENT will be + stored last on the row. This is done so that we don't break any field + data in the middle. + + We first try to store the full row into one block. If that's not possible + we move out each big blob into their own extents. If this is not enough we + move out a concatenation of all varchars to their own extent. + + Each blob and the concatenated char/varchar fields are stored the following + way: + - Store the parts in as many full-contiguous pages as possible. + - The last part, that doesn't fill a full page, is stored in tail page. + + When doing an insert of a new row, we don't have to have + VER_PTR in the row. This will make rows that are not changed stored + efficiently. On update and delete we would add TRANSID (if it was an old + committed row) and VER_PTR to + the row. On row page compaction we can easily detect rows where + TRANSID was committed before the longest running transaction + started and we can then delete TRANSID and VER_PTR from the row to + gain more space. + + If a row is deleted in Maria, we change TRANSID to the deleting + transaction's id, change VER_PTR to point to the undo record for the delete, + and add DELETE_TRANSID (the id of the transaction which last + inserted/updated the row before its deletion). DELETE_TRANSID allows an old + transaction to avoid reading the log to know if it can see the last version + before delete (in other words it reduces the probability of having to follow + VER_PTR). TODO: depending on a compilation option, evaluate the performance + impact of not storing DELETE_TRANSID (which would make the row smaller). + + Description of the different parts: + + Flag is coded as: + + Description bit + TRANS_ID_exists 0 + VER_PTR_exists 1 + Row is deleted 2 (Means that DELETE_TRANSID exists) + Nulls_extended_exists 3 + Row is split 7 This means that 'Number_of_row_extents' exists + + Nulls_extended is the number of new DEFAULT NULL fields in the row + compared to the number of DEFAULT NULL fields when the first version + of the table was created. If Nulls_extended doesn't exist in the row, + we know it's 0 as this must be one of the original rows from when the + table was created first time. This coding allows us to add 255*8 = + 2048 new fields without requiring a full alter table. + + Empty_bits is used to allow us to store 0, 0.0, empty string, empty + varstring and empty blob efficiently. (This is very good for data + warehousing where NULL's are often regarded as evil). Having this + bitmap also allows us to drop information of a field during a future + delete if field was deleted with ALTER TABLE DROP COLUMN. To be able + to handle DROP COLUMN, we must store in the index header the fields + that has been dropped. When unpacking a row we will ignore dropped + fields. When storing a row, we will mark a dropped field either with a + null in the null bit map or in the empty_bits and not store any data + for it. + TODO: Add code for handling dropped fields. + + + A ROW EXTENT is range of pages. One ROW_EXTENT is coded as: + + START_PAGE 5 bytes + PAGE_COUNT 2 bytes. Bit 16 is set if this is a tail page. + Bit 15 is to set if this is start of a new + blob extent. + + With 8K pages, we can cover 256M in one extent. This coding gives us a + maximum file size of 2^40*8192 = 8192 tera + + As an example of ROW_EXTENT handling, assume a row with one integer + field (value 5), two big VARCHAR fields (size 250 and 8192*3), and 2 + big BLOB fields that we have updated. + + The record format for storing this into an empty file would be: + + Page 1: + + 00 00 00 00 00 00 00 LSN + 01 Only one row in page + FF No free dir entry + xx xx Empty space on page + + 10 Flag: row split, VER_PTR exists + 01 00 00 00 00 00 TRANSID 1 + 00 00 00 00 00 01 00 VER_PTR to first block in LOG file 1 + 5 Number of row extents + 02 00 00 00 00 03 00 VARCHAR's are stored in full pages 2,3,4 + 0 No null fields + 0 No empty fields + 05 00 00 00 00 00 80 Tail page for VARCHAR, rowid 0 + 06 00 00 00 00 80 00 First blob, stored at page 6-133 + 05 00 00 00 00 01 80 Tail of first blob (896 bytes) at page 5 + 86 00 00 00 00 80 00 Second blob, stored at page 134-262 + 05 00 00 00 00 02 80 Tail of second blob (896 bytes) at page 5 + 05 00 5 integer + FA Length of first varchar field (size 250) + 00 60 Length of second varchar field (size 8192*3) + 00 60 10 First medium BLOB, 1M + 01 00 10 00 Second BLOB, 1M + xx xx xx xx xx xx Varchars are stored here until end of page + + ..... until end of page + + 09 00 F4 1F Start position 9, length 8180 + xx xx xx xx Checksum + + A data page is allowed to have a wrong CRC and header as long as it is + marked empty in the bitmap and its directory's count is 0. +*/ + +#include "maria_def.h" +#include "ma_blockrec.h" +#include "trnman.h" +#include "ma_key_recover.h" +#include "ma_recovery_util.h" +#include <lf.h> + +/* + Struct for having a cursor over a set of extent. + This is used to loop over all extents for a row when reading + the row data. It's also used to store the tail positions for + a read row to be used by a later update/delete command. +*/ + +typedef struct st_maria_extent_cursor +{ + /* + Pointer to packed uchar array of extents for the row. + Format is described above in the header + */ + uchar *extent; + /* Where data starts on page; Only for debugging */ + uchar *data_start; + /* Position to all tails in the row. Updated when reading a row */ + MARIA_RECORD_POS *tail_positions; + /* Current page */ + pgcache_page_no_t page; + /* How many pages in the page region */ + uint page_count; + /* What kind of lock to use for tail pages */ + enum pagecache_page_lock lock_for_tail_pages; + /* Total number of extents (i.e., entries in the 'extent' slot) */ + uint extent_count; + /* <> 0 if current extent is a tail page; Set while using cursor */ + uint tail; + /* Position for tail on tail page */ + uint tail_row_nr; + /* + == 1 if we are working on the first extent (i.e., the one that is stored in + the row header, not an extent that is stored as part of the row data). + */ + my_bool first_extent; +} MARIA_EXTENT_CURSOR; + + +/** + @brief Structure for passing down info to write_hook_for_clr_end(). + This hooks needs to know the variation of the live checksum caused by the + current operation to update state.checksum under log's mutex, + needs to know the transaction's previous undo_lsn to set + trn->undo_lsn under log mutex, and needs to know the type of UNDO being + undone now to modify state.records under log mutex. +*/ + +/** S:share,D:checksum_delta,E:expression,P:pointer_into_record,L:length */ +#define store_checksum_in_rec(S,D,E,P,L) do \ + { \ + D= 0; \ + if ((S)->calc_checksum != NULL) \ + { \ + D= (E); \ + ha_checksum_store(P, D); \ + L+= HA_CHECKSUM_STORE_SIZE; \ + } \ + } while (0) + +static my_bool delete_tails(MARIA_HA *info, MARIA_RECORD_POS *tails); +static my_bool delete_head_or_tail(MARIA_HA *info, + pgcache_page_no_t page, uint record_number, + my_bool head, my_bool from_update); +#ifndef DBUG_OFF +static void _ma_print_directory(uchar *buff, uint block_size); +#endif +static uchar *store_page_range(uchar *to, MARIA_BITMAP_BLOCK *block, + uint block_size, ulong length, + uint *tot_ranges); +static size_t fill_insert_undo_parts(MARIA_HA *info, const uchar *record, + LEX_CUSTRING *log_parts, + uint *log_parts_count); +static size_t fill_update_undo_parts(MARIA_HA *info, const uchar *oldrec, + const uchar *newrec, + LEX_CUSTRING *log_parts, + uint *log_parts_count); + +/**************************************************************************** + Initialization +****************************************************************************/ + +/* + Initialize data needed for block structures +*/ + + +/* Size of the different header elements for a row */ + +static uchar header_sizes[]= +{ + TRANSID_SIZE, + VERPTR_SIZE, + TRANSID_SIZE, /* Delete transid */ + 1 /* Null extends */ +}; + +/* + Calculate array of all used headers + + Used to speed up: + + size= 1; + if (flag & 1) + size+= TRANSID_SIZE; + if (flag & 2) + size+= VERPTR_SIZE; + if (flag & 4) + size+= TRANSID_SIZE + if (flag & 8) + size+= 1; + + NOTES + This is called only once at startup of Maria +*/ + +static uchar total_header_size[1 << array_elements(header_sizes)]; +#define PRECALC_HEADER_BITMASK (array_elements(total_header_size) -1) + +void _ma_init_block_record_data(void) +{ + uint i; + bzero(total_header_size, sizeof(total_header_size)); + total_header_size[0]= FLAG_SIZE; /* Flag uchar */ + for (i= 1; i < array_elements(total_header_size); i++) + { + uint size= FLAG_SIZE, j, bit; + for (j= 0; (bit= (1 << j)) <= i; j++) + { + if (i & bit) + size+= header_sizes[j]; + } + total_header_size[i]= size; + } +} + + +my_bool _ma_once_init_block_record(MARIA_SHARE *share, File data_file) +{ + + share->base.max_data_file_length= + (((ulonglong) 1 << ((share->base.rec_reflength-1)*8))-1) * + share->block_size; +#if SIZEOF_OFF_T == 4 + set_if_smaller(share->base.max_data_file_length, INT_MAX32); +#endif + return _ma_bitmap_init(share, data_file); +} + + +my_bool _ma_once_end_block_record(MARIA_SHARE *share) +{ + int res= _ma_bitmap_end(share); + if (share->bitmap.file.file >= 0) + { + if (flush_pagecache_blocks(share->pagecache, &share->bitmap.file, + share->temporary ? FLUSH_IGNORE_CHANGED : + FLUSH_RELEASE)) + res= 1; + /* + File must be synced as it is going out of the maria_open_list and so + becoming unknown to Checkpoint. + */ + if (share->now_transactional && + my_sync(share->bitmap.file.file, MYF(MY_WME))) + res= 1; + if (my_close(share->bitmap.file.file, MYF(MY_WME))) + res= 1; + /* + Trivial assignment to guard against multiple invocations + (May happen if file are closed but we want to keep the maria object + around a bit longer) + */ + share->bitmap.file.file= -1; + } + if (share->id != 0) + { + /* + We de-assign the id even though index has not been flushed, this is ok + as close_lock serializes us with a Checkpoint looking at our share. + */ + translog_deassign_id_from_share(share); + } + return res; +} + + +/* Init info->cur_row structure */ + +my_bool _ma_init_block_record(MARIA_HA *info) +{ + MARIA_ROW *row= &info->cur_row, *new_row= &info->new_row; + MARIA_SHARE *share= info->s; + uint default_extents; + DBUG_ENTER("_ma_init_block_record"); + + if (!my_multi_malloc(MY_WME, + &row->empty_bits, share->base.pack_bytes, + &row->field_lengths, + share->base.max_field_lengths + 2, + &row->blob_lengths, sizeof(ulong) * share->base.blobs, + &row->null_field_lengths, (sizeof(uint) * + (share->base.fields - + share->base.blobs + + EXTRA_LENGTH_FIELDS)), + &row->tail_positions, (sizeof(MARIA_RECORD_POS) * + (share->base.blobs + 2)), + &new_row->empty_bits, share->base.pack_bytes, + &new_row->field_lengths, + share->base.max_field_lengths + 2, + &new_row->blob_lengths, + sizeof(ulong) * share->base.blobs, + &new_row->null_field_lengths, (sizeof(uint) * + (share->base.fields - + share->base.blobs + + EXTRA_LENGTH_FIELDS)), + &info->log_row_parts, + sizeof(*info->log_row_parts) * + (TRANSLOG_INTERNAL_PARTS + 3 + + share->base.fields + 3), + &info->update_field_data, + (share->base.fields * 4 + + share->base.max_field_lengths + 1 + 4), + NullS, 0)) + DBUG_RETURN(1); + /* Skip over bytes used to store length of field length for logging */ + row->field_lengths+= 2; + new_row->field_lengths+= 2; + + /* Reserve some initial space to avoid mallocs during execution */ + default_extents= (ELEMENTS_RESERVED_FOR_MAIN_PART + 1 + + (AVERAGE_BLOB_SIZE / + FULL_PAGE_SIZE(share->block_size) / + BLOB_SEGMENT_MIN_SIZE)); + + if (my_init_dynamic_array(&info->bitmap_blocks, + sizeof(MARIA_BITMAP_BLOCK), default_extents, + 64)) + goto err; + info->cur_row.extents_buffer_length= default_extents * ROW_EXTENT_SIZE; + if (!(info->cur_row.extents= my_malloc(info->cur_row.extents_buffer_length, + MYF(MY_WME)))) + goto err; + + info->row_base_length= share->base_length; + info->row_flag= share->base.default_row_flag; + + /* + We need to reserve 'EXTRA_LENGTH_FIELDS' number of parts in + null_field_lengths to allow splitting of rows in 'find_where_to_split_row' + */ + row->null_field_lengths+= EXTRA_LENGTH_FIELDS; + new_row->null_field_lengths+= EXTRA_LENGTH_FIELDS; + + DBUG_RETURN(0); + +err: + _ma_end_block_record(info); + DBUG_RETURN(1); +} + + +void _ma_end_block_record(MARIA_HA *info) +{ + DBUG_ENTER("_ma_end_block_record"); + my_free(info->cur_row.empty_bits, MYF(MY_ALLOW_ZERO_PTR)); + delete_dynamic(&info->bitmap_blocks); + my_free(info->cur_row.extents, MYF(MY_ALLOW_ZERO_PTR)); + my_free(info->blob_buff, MYF(MY_ALLOW_ZERO_PTR)); + /* + The data file is closed, when needed, in ma_once_end_block_record(). + The following protects us from doing an extra, not allowed, close + in maria_close() + */ + info->dfile.file= -1; + DBUG_VOID_RETURN; +} + + +/**************************************************************************** + Helper functions +****************************************************************************/ + +/* + Return the next unused postion on the page after a directory entry. + + SYNOPSIS + start_of_next_entry() + dir Directory entry to be used. This can not be the + the last entry on the page! + + RETURN + # Position in page where next entry starts. + Everything between the '*dir' and this are free to be used. +*/ + +static inline uint start_of_next_entry(uchar *dir) +{ + uchar *prev; + /* + Find previous used entry. (There is always a previous entry as + the directory never starts with a deleted entry) + */ + for (prev= dir - DIR_ENTRY_SIZE ; + prev[0] == 0 && prev[1] == 0 ; + prev-= DIR_ENTRY_SIZE) + {} + return (uint) uint2korr(prev); +} + + +/* + Return the offset where the previous entry ends (before on page) + + SYNOPSIS + end_of_previous_entry() + dir Address for current directory entry + end Address to last directory entry + + RETURN + # Position where previous entry ends (smallest address on page) + Everything between # and current entry are free to be used. +*/ + + +static inline uint end_of_previous_entry(uchar *dir, uchar *end) +{ + uchar *pos; + for (pos= dir + DIR_ENTRY_SIZE ; pos < end ; pos+= DIR_ENTRY_SIZE) + { + uint offset; + if ((offset= uint2korr(pos))) + return offset + uint2korr(pos+2); + } + return PAGE_HEADER_SIZE; +} + + +#ifndef DBUG_OFF + +static void _ma_print_directory(uchar *buff, uint block_size) +{ + uint max_entry= (uint) ((uchar *) buff)[DIR_COUNT_OFFSET], row= 0; + uint end_of_prev_row= PAGE_HEADER_SIZE; + uchar *dir, *end; + + dir= dir_entry_pos(buff, block_size, max_entry-1); + end= dir_entry_pos(buff, block_size, 0); + + DBUG_LOCK_FILE; + fprintf(DBUG_FILE,"Directory dump (pos:length):\n"); + + for (row= 1; dir <= end ; end-= DIR_ENTRY_SIZE, row++) + { + uint offset= uint2korr(end); + uint length= uint2korr(end+2); + fprintf(DBUG_FILE, " %4u:%4u", offset, offset ? length : 0); + if (!(row % (80/12))) + fputc('\n', DBUG_FILE); + if (offset) + { + DBUG_ASSERT(offset >= end_of_prev_row); + end_of_prev_row= offset + length; + } + } + fputc('\n', DBUG_FILE); + fflush(DBUG_FILE); + DBUG_UNLOCK_FILE; +} + + +static void check_directory(uchar *buff, uint block_size, uint min_row_length) +{ + uchar *dir, *end; + uint max_entry= (uint) buff[DIR_COUNT_OFFSET]; + uint start_of_dir, deleted; + uchar free_entry, prev_free_entry; + uint end_of_prev_row= PAGE_HEADER_SIZE; + + dir= dir_entry_pos(buff, block_size, max_entry-1); + start_of_dir= (uint) (dir - buff); + end= dir_entry_pos(buff, block_size, 0); + deleted= 0; + + /* Ensure that all rows are in increasing order and no overlaps */ + for (; dir <= end ; end-= DIR_ENTRY_SIZE) + { + uint offset= uint2korr(end); + uint length= uint2korr(end+2); + if (offset) + { + DBUG_ASSERT(offset >= end_of_prev_row); + DBUG_ASSERT(!length || length >= min_row_length); + end_of_prev_row= offset + length; + } + else + deleted++; + } + DBUG_ASSERT(end_of_prev_row <= start_of_dir); + + /* check free links */ + free_entry= buff[DIR_FREE_OFFSET]; + prev_free_entry= END_OF_DIR_FREE_LIST; + while (free_entry != END_OF_DIR_FREE_LIST) + { + uchar *dir= dir_entry_pos(buff, block_size, free_entry); + DBUG_ASSERT(dir[0] == 0 && dir[1] == 0); + DBUG_ASSERT(dir[2] == prev_free_entry); + prev_free_entry= free_entry; + free_entry= dir[3]; + deleted--; + } + DBUG_ASSERT(deleted == 0); +} +#else +#define check_directory(A,B,C) +#endif /* DBUG_OFF */ + + +/** + @brief Calculate if there is enough entries on the page +*/ + +my_bool enough_free_entries(uchar *buff, uint block_size, uint wanted_entries) +{ + uint entries= (uint) buff[DIR_COUNT_OFFSET]; + uint needed_free_entries, free_entry; + + if (entries + wanted_entries <= MAX_ROWS_PER_PAGE) + return 1; + + /* Check if enough free entries in free list */ + needed_free_entries= entries + wanted_entries - MAX_ROWS_PER_PAGE; + + free_entry= (uint) buff[DIR_FREE_OFFSET]; + while (free_entry != END_OF_DIR_FREE_LIST) + { + uchar *dir; + if (!--needed_free_entries) + return 1; + dir= dir_entry_pos(buff, block_size, free_entry); + free_entry= dir[3]; + } + return 0; /* Not enough entries */ +} + + +/** + @brief Extend a record area to fit a given size block + + @fn extend_area_on_page() + @param info Handler if head page and 0 if tail page + @param buff Page buffer + @param dir Pointer to dir entry in buffer + @param rownr Row number we working on + @param block_size Block size of buffer + @param request_length How much data we want to put at [dir] + @param empty_space Total empty space in buffer + This is updated with length after dir + is allocated and current block freed + + @implementation + The logic is as follows (same as in _ma_update_block_record()) + - If new data fits in old block, use old block. + - Extend block with empty space before block. If enough, use it. + - Extend block with empty space after block. If enough, use it. + - Use _ma_compact_block_page() to get all empty space at dir. + + @note + The given directory entry is set to rec length. + empty_space doesn't include the new directory entry + + + @return + @retval 0 ok + @retval ret_offset Pointer to store offset to found area + @retval ret_length Pointer to store length of found area + @retval [dir] rec_offset is store here too + + @retval 1 error (wrong info in block) +*/ + +static my_bool extend_area_on_page(MARIA_HA *info, + uchar *buff, uchar *dir, + uint rownr, uint block_size, + uint request_length, + uint *empty_space, uint *ret_offset, + uint *ret_length) +{ + uint rec_offset, length; + uint max_entry= (uint) buff[DIR_COUNT_OFFSET]; + DBUG_ENTER("extend_area_on_page"); + + rec_offset= uint2korr(dir); + if (rec_offset) + { + /* Extending old row; Mark current space as 'free' */ + length= uint2korr(dir + 2); + DBUG_PRINT("info", ("rec_offset: %u length: %u request_length: %u " + "empty_space: %u", + rec_offset, length, request_length, *empty_space)); + + *empty_space+= length; + } + else + { + /* Reusing free directory entry; Free it from the directory list */ + if (dir[2] == END_OF_DIR_FREE_LIST) + buff[DIR_FREE_OFFSET]= dir[3]; + else + { + uchar *prev_dir= dir_entry_pos(buff, block_size, (uint) dir[2]); + DBUG_ASSERT(uint2korr(prev_dir) == 0 && prev_dir[3] == (uchar) rownr); + prev_dir[3]= dir[3]; + } + if (dir[3] != END_OF_DIR_FREE_LIST) + { + uchar *next_dir= dir_entry_pos(buff, block_size, (uint) dir[3]); + DBUG_ASSERT(uint2korr(next_dir) == 0 && next_dir[2] == (uchar) rownr); + next_dir[2]= dir[2]; + } + rec_offset= start_of_next_entry(dir); + length= 0; + } + if (length < request_length) + { + uint old_rec_offset; + /* + New data did not fit in old position. + Find first possible position where to put new data. + */ + old_rec_offset= rec_offset; + rec_offset= end_of_previous_entry(dir, buff + block_size - + PAGE_SUFFIX_SIZE); + length+= (uint) (old_rec_offset - rec_offset); + DBUG_ASSERT(old_rec_offset); + /* + 'length' is 0 if we are doing an insert into a not allocated block. + This can only happen during "REDO of INSERT" or "UNDO of DELETE." + */ + if (length < request_length) + { + /* + Did not fit in current block + empty space. Extend with + empty space after block. + */ + if (rownr == max_entry - 1) + { + /* Last entry; Everything is free between this and directory */ + length= ((block_size - PAGE_SUFFIX_SIZE - DIR_ENTRY_SIZE * max_entry) - + rec_offset); + } + else + length= start_of_next_entry(dir) - rec_offset; + DBUG_ASSERT((int) length >= 0); + if (length < request_length) + { + /* Not enough continuous space, compact page to get more */ + int2store(dir, rec_offset); + /* Reset length, as this may be a deleted block */ + int2store(dir+2, 0); + _ma_compact_block_page(buff, block_size, rownr, 1, + info ? info->trn->min_read_from: 0, + info ? info->s->base.min_block_length : 0); + rec_offset= uint2korr(dir); + length= uint2korr(dir+2); + if (length < request_length) + { + DBUG_PRINT("error", ("Not enough space: " + "length: %u request_length: %u", + length, request_length)); + my_errno= HA_ERR_WRONG_IN_RECORD; /* File crashed */ + DBUG_RETURN(1); /* Error in block */ + } + *empty_space= length; /* All space is here */ + } + } + } + int2store(dir, rec_offset); + int2store(dir + 2, length); + *ret_offset= rec_offset; + *ret_length= length; + check_directory(buff, block_size, info ? info->s->base.min_block_length : 0); + DBUG_RETURN(0); +} + + +/** + @brief Copy not changed fields from 'from' to 'to' + + @notes + Assumption is that most fields are not changed! + (Which is why we don't test if all bits are set for some bytes in bitmap) +*/ + +void copy_not_changed_fields(MARIA_HA *info, MY_BITMAP *changed_fields, + uchar *to, uchar *from) +{ + MARIA_COLUMNDEF *column, *end_column; + uchar *bitmap= (uchar*) changed_fields->bitmap; + MARIA_SHARE *share= info->s; + uint bit= 1; + + for (column= share->columndef, end_column= column+ share->base.fields; + column < end_column; column++) + { + if (!(*bitmap & bit)) + { + uint field_length= column->length; + if (column->type == FIELD_VARCHAR) + { + if (column->fill_length == 1) + field_length= (uint) from[column->offset] + 1; + else + field_length= uint2korr(from + column->offset) + 2; + } + memcpy(to + column->offset, from + column->offset, field_length); + } + if ((bit= (bit << 1)) == 256) + { + bitmap++; + bit= 1; + } + } +} + +#ifdef NOT_YET_NEEDED +/* Calculate empty space on a page */ + +static uint empty_space_on_page(uchar *buff, uint block_size) +{ + enum en_page_type; + page_type= (enum en_page_type) (buff[PAGE_TYPE_OFFSET] & + ~(uchar) PAGE_CAN_BE_COMPACTED); + if (page_type == UNALLOCATED_PAGE) + return block_size; + if ((uint) page_type <= TAIL_PAGE) + return uint2korr(buff+EMPTY_SPACE_OFFSET); + return 0; /* Blob page */ +} +#endif + + +/* + @brief Ensure we have space for new directory entries + + @fn make_space_for_directory() + @param buff Page buffer + @param block_size Block size for pages + @param max_entry Number of current entries in directory + @param count Number of new entries to be added to directory + @param first_dir First directory entry on page + @param empty_space Total empty space in buffer. It's updated + to reflect the new empty space + @param first_pos Store position to last data byte on page here + + @note + This function is inline as the argument passing is the biggest + part of the function + + @return + @retval 0 ok + @retval 1 error (No data on page, fatal error) +*/ + +static inline my_bool +make_space_for_directory(MARIA_HA *info, + uchar *buff, uint block_size, uint max_entry, + uint count, uchar *first_dir, uint *empty_space, + uint *first_pos) +{ + uint length_needed= DIR_ENTRY_SIZE * count; + + /* + The following is not true only in the case and UNDO is used to reinsert + a row on a previously not used page + */ + if (likely(max_entry)) + { + /* Check if there is place for the directory entry on the page */ + *first_pos= uint2korr(first_dir) + uint2korr(first_dir + 2); + + if ((uint) (first_dir - buff) < *first_pos + length_needed) + { + /* Create place for directory */ + _ma_compact_block_page(buff, block_size, max_entry - 1, 0, + info ? info->trn->min_read_from : 0, + info ? info->s->base.min_block_length : 0); + *first_pos= (uint2korr(first_dir) + uint2korr(first_dir + 2)); + *empty_space= uint2korr(buff + EMPTY_SPACE_OFFSET); + if (*empty_space < length_needed) + { + /* + We should always have space, as we only come here for + UNDO of DELETE (in which case we know the row was on the + page before) or if the bitmap told us there was space on page + */ + DBUG_ASSERT(0); + return(1); + } + } + } + else + *first_pos= PAGE_HEADER_SIZE; + + /* Reduce directory entry size from free space size */ + (*empty_space)-= length_needed; + buff[DIR_COUNT_OFFSET]= (uchar) (max_entry + count); + return(0); +} + + +/* + Find free position in directory + + SYNOPSIS + find_free_position() + info Handler if head page and 0 otherwise + buff Page + block_size Size of page + res_rownr Store index to free position here + res_length Store length of found segment here + empty_space Store length of empty space on disk here. This is + all empty space, including the found block. + + NOTES + If there is a free directory entry (entry with position == 0), + then use it and change it to be the size of the empty block + after the previous entry. This guarantees that all row entries + are stored on disk in inverse directory order, which makes life easier for + '_ma_compact_block_page()' and to know if there is free space after any + block. + + If there is no free entry (entry with position == 0), then we create + a new one. If there is not space for the directory entry (because + the last block overlapps with the directory), we compact the page. + + We will update the offset and the length of the found dir entry to + match the position and empty space found. + + buff[EMPTY_SPACE_OFFSET] is NOT updated but left up to the caller + + See start of file for description of how free directory entires are linked + + RETURN + 0 Error (directory full or last block goes over directory) + # Pointer to directory entry on page +*/ + +static uchar *find_free_position(MARIA_HA *info, + uchar *buff, uint block_size, uint *res_rownr, + uint *res_length, uint *empty_space) +{ + uint max_entry, free_entry; + uint length, first_pos; + uchar *dir, *first_dir; + DBUG_ENTER("find_free_position"); + + max_entry= (uint) buff[DIR_COUNT_OFFSET]; + free_entry= (uint) buff[DIR_FREE_OFFSET]; + *empty_space= uint2korr(buff + EMPTY_SPACE_OFFSET); + + DBUG_PRINT("info", ("max_entry: %u free_entry: %u", max_entry, free_entry)); + + first_dir= dir_entry_pos(buff, block_size, max_entry - 1); + + /* Search after first free position */ + if (free_entry != END_OF_DIR_FREE_LIST) + { + if (free_entry >= max_entry) + DBUG_RETURN(0); /* Consistency error */ + dir= dir_entry_pos(buff, block_size, free_entry); + DBUG_ASSERT(uint2korr(dir) == 0 && dir[2] == END_OF_DIR_FREE_LIST); + /* Relink free list */ + if ((buff[DIR_FREE_OFFSET]= dir[3]) != END_OF_DIR_FREE_LIST) + { + uchar *next_entry= dir_entry_pos(buff, block_size, (uint) dir[3]); + DBUG_ASSERT((uint) next_entry[2] == free_entry && + uint2korr(next_entry) == 0); + next_entry[2]= END_OF_DIR_FREE_LIST; /* Backlink */ + } + + first_pos= end_of_previous_entry(dir, buff + block_size - + PAGE_SUFFIX_SIZE); + length= start_of_next_entry(dir) - first_pos; + int2store(dir, first_pos); /* Update dir entry */ + int2store(dir + 2, 0); + *res_rownr= free_entry; + *res_length= length; + + check_directory(buff, block_size, + info ? info->s->base.min_block_length : 0); + DBUG_RETURN(dir); + } + /* No free places in dir; create a new one */ + + /* Check if there is place for the directory entry */ + if (max_entry == MAX_ROWS_PER_PAGE) + DBUG_RETURN(0); + + if (make_space_for_directory(info, buff, block_size, max_entry, 1, + first_dir, empty_space, &first_pos)) + DBUG_RETURN(0); + + dir= first_dir - DIR_ENTRY_SIZE; + length= (uint) (dir - buff - first_pos); + DBUG_ASSERT(length <= *empty_space); + int2store(dir, first_pos); + int2store(dir + 2, 0); /* Max length of region */ + *res_rownr= max_entry; + *res_length= length; + + check_directory(buff, block_size, info ? info->s->base.min_block_length : 0); + DBUG_RETURN(dir); +} + + +/** + @brief Enlarge page directory to hold more entries + + @fn extend_directory() + @param info Handler if head page and 0 otherwise + @param buff Page buffer + @param block_size Block size + @param max_entry Number of directory entries on page + @param new_entry Position for new entry + @param empty_space Total empty space in buffer. It's updated + to reflect the new empty space + + @note + This is only called on UNDO when we want to expand the directory + to be able to re-insert row in a given position + + The new directory entry will be set to cover the maximum possible space + + @return + @retval 0 ok + @retval 1 error (No data on page, fatal error) +*/ + +static my_bool extend_directory(MARIA_HA *info, uchar *buff, uint block_size, + uint max_entry, uint new_entry, + uint *empty_space) +{ + uint length, first_pos; + uchar *dir, *first_dir; + DBUG_ENTER("extend_directory"); + + /* + Note that in if max_entry is 0, then first_dir will point to + an illegal directory entry. This is ok, as in this case we will + not access anything through first_dir. + */ + first_dir= dir_entry_pos(buff, block_size, max_entry) + DIR_ENTRY_SIZE; + + if (make_space_for_directory(info, buff, block_size, max_entry, + new_entry - max_entry + 1, + first_dir, empty_space, &first_pos)) + DBUG_RETURN(1); + + /* Set the new directory entry to cover the max possible length */ + dir= first_dir - DIR_ENTRY_SIZE * (new_entry - max_entry + 1); + length= (uint) (dir - buff - first_pos); + int2store(dir, first_pos); + int2store(dir+2, length); + *empty_space-= length; + + if (new_entry-- > max_entry) + { + /* Link all row entries between new_entry and max_entry into free list */ + uint free_entry= (uint) buff[DIR_FREE_OFFSET]; + uint prev_entry= END_OF_DIR_FREE_LIST; + buff[DIR_FREE_OFFSET]= new_entry; + do + { + dir+= DIR_ENTRY_SIZE; + dir[0]= dir[1]= 0; + dir[2]= (uchar) prev_entry; + dir[3]= (uchar) new_entry-1; + prev_entry= new_entry; + } while (new_entry-- > max_entry); + if ((dir[3]= free_entry) != END_OF_DIR_FREE_LIST) + { + /* Relink next entry to point to newly freed entry */ + uchar *next_entry= dir_entry_pos(buff, block_size, (uint) dir[3]); + DBUG_ASSERT(uint2korr(next_entry) == 0 && + next_entry[2] == END_OF_DIR_FREE_LIST); + next_entry[2]= max_entry; + } + } + + check_directory(buff, block_size, + info ? min(info->s->base.min_block_length, length) : 0); + DBUG_RETURN(0); +} + + +/**************************************************************************** + Updating records +****************************************************************************/ + +/* + Calculate length of all the different field parts + + SYNOPSIS + calc_record_size() + info Maria handler + record Row to store + row Store statistics about row here + + NOTES + The statistics is used to find out how much space a row will need + and also where we can split a row when we need to split it into several + extents. +*/ + +static void calc_record_size(MARIA_HA *info, const uchar *record, + MARIA_ROW *row) +{ + MARIA_SHARE *share= info->s; + uchar *field_length_data; + MARIA_COLUMNDEF *column, *end_column; + uint *null_field_lengths= row->null_field_lengths; + ulong *blob_lengths= row->blob_lengths; + DBUG_ENTER("calc_record_size"); + + row->normal_length= row->char_length= row->varchar_length= + row->blob_length= row->extents_count= 0; + + /* Create empty bitmap and calculate length of each varlength/char field */ + bzero(row->empty_bits, share->base.pack_bytes); + field_length_data= row->field_lengths; + for (column= share->columndef + share->base.fixed_not_null_fields, + end_column= share->columndef + share->base.fields; + column < end_column; column++, null_field_lengths++) + { + if ((record[column->null_pos] & column->null_bit)) + { + if (column->type != FIELD_BLOB) + *null_field_lengths= 0; + else + *blob_lengths++= 0; + continue; + } + switch (column->type) { + case FIELD_CHECK: + case FIELD_NORMAL: /* Fixed length field */ + case FIELD_ZERO: + DBUG_ASSERT(column->empty_bit == 0); + /* fall through */ + case FIELD_SKIP_PRESPACE: /* Not packed */ + row->normal_length+= column->length; + *null_field_lengths= column->length; + break; + case FIELD_SKIP_ZERO: /* Fixed length field */ + if (memcmp(record+ column->offset, maria_zero_string, + column->length) == 0) + { + row->empty_bits[column->empty_pos] |= column->empty_bit; + *null_field_lengths= 0; + } + else + { + row->normal_length+= column->length; + *null_field_lengths= column->length; + } + break; + case FIELD_SKIP_ENDSPACE: /* CHAR */ + { + const uchar *pos, *end; + for (pos= record + column->offset, end= pos + column->length; + end > pos && end[-1] == ' '; end--) + ; + if (pos == end) /* If empty string */ + { + row->empty_bits[column->empty_pos]|= column->empty_bit; + *null_field_lengths= 0; + } + else + { + uint length= (uint) (end - pos); + if (column->length <= 255) + *field_length_data++= (uchar) length; + else + { + int2store(field_length_data, length); + field_length_data+= 2; + } + row->char_length+= length; + *null_field_lengths= length; + } + break; + } + case FIELD_VARCHAR: + { + uint length, field_length_data_length; + const uchar *field_pos= record + column->offset; + + /* 256 is correct as this includes the length uchar */ + field_length_data[0]= field_pos[0]; + if (column->length <= 256) + { + length= (uint) (uchar) *field_pos; + field_length_data_length= 1; + } + else + { + length= uint2korr(field_pos); + field_length_data[1]= field_pos[1]; + field_length_data_length= 2; + } + *null_field_lengths= length; + if (!length) + { + row->empty_bits[column->empty_pos]|= column->empty_bit; + break; + } + row->varchar_length+= length; + *null_field_lengths= length; + field_length_data+= field_length_data_length; + break; + } + case FIELD_BLOB: + { + const uchar *field_pos= record + column->offset; + uint size_length= column->length - portable_sizeof_char_ptr; + ulong blob_length= _ma_calc_blob_length(size_length, field_pos); + + *blob_lengths++= blob_length; + if (!blob_length) + row->empty_bits[column->empty_pos]|= column->empty_bit; + else + { + row->blob_length+= blob_length; + memcpy(field_length_data, field_pos, size_length); + field_length_data+= size_length; + } + break; + } + default: + DBUG_ASSERT(0); + } + } + row->field_lengths_length= (uint) (field_length_data - row->field_lengths); + /* + - info->row_base_length is base information we must have on a page in first + extent: + - flag byte (1) + is_nulls_extended (0 | 1) + null_bytes + pack_bytes + + table_checksum (0 | 1) + - row->min_length is minimum amount of data we must store on + a page. bitmap code will ensure we get at list this much + + total number of extents and one extent information + - fixed_not_null_fields_length is length of fixed length fields that can't + be compacted + - head_length is the amount of data for the head page + (ie, all fields except blobs) + */ + row->min_length= (info->row_base_length + + (share->base.max_field_lengths ? + size_to_store_key_length(row->field_lengths_length) : + 0)); + row->head_length= (row->min_length + + share->base.fixed_not_null_fields_length + + row->field_lengths_length + + row->normal_length + + row->char_length + row->varchar_length); + row->total_length= (row->head_length + row->blob_length); + if (row->total_length < share->base.min_block_length) + row->total_length= share->base.min_block_length; + DBUG_PRINT("exit", ("head_length: %lu total_length: %lu", + (ulong) row->head_length, (ulong) row->total_length)); + DBUG_VOID_RETURN; +} + + +/** + Compact page by removing all space between rows + + Moves up all rows to start of page. Moves blocks that are directly after + each other with one memmove. + + @note if rownr is the last row in the page, and extend_block is false, + caller has to make sure to update bitmap page afterwards to reflect freed + space. + + @param buff Page to compact + @param block_size Size of page + @param rownr Put empty data after this row + @param extend_block If 1, extend the block at 'rownr' to cover the + whole block. + @param min_read_from If <> 0, remove all trid's that are less than this +*/ + +void _ma_compact_block_page(uchar *buff, uint block_size, uint rownr, + my_bool extend_block, TrID min_read_from, + uint min_row_length) +{ + uint max_entry= (uint) buff[DIR_COUNT_OFFSET]; + uint page_pos, next_free_pos, start_of_found_block, diff, end_of_found_block; + uint freed_size= 0; + uchar *dir, *end; + DBUG_ENTER("_ma_compact_block_page"); + DBUG_PRINT("enter", ("rownr: %u", rownr)); + DBUG_ASSERT(max_entry > 0 && + max_entry < (block_size - PAGE_HEADER_SIZE - + PAGE_SUFFIX_SIZE) / DIR_ENTRY_SIZE); + + /* Move all entries before and including rownr up to start of page */ + dir= dir_entry_pos(buff, block_size, rownr); + end= dir_entry_pos(buff, block_size, 0); + page_pos= next_free_pos= start_of_found_block= PAGE_HEADER_SIZE; + diff= 0; + for (; dir <= end ; end-= DIR_ENTRY_SIZE) + { + uint offset= uint2korr(end); + + if (offset) + { + uint row_length= uint2korr(end + 2); + DBUG_ASSERT(offset >= page_pos); + DBUG_ASSERT(buff + offset + row_length <= dir); + DBUG_ASSERT(row_length >= min_row_length || row_length == 0); + + /* Row length can be zero if row is to be deleted */ + if (min_read_from && row_length && (buff[offset] & ROW_FLAG_TRANSID)) + { + TrID transid= transid_korr(buff+offset+1); + if (transid < min_read_from) + { + /* Remove transid from row by moving the start point of the row up */ + buff[offset + TRANSID_SIZE]= buff[offset] & ~ROW_FLAG_TRANSID; + offset+= TRANSID_SIZE; + freed_size+= TRANSID_SIZE; + row_length-= TRANSID_SIZE; + int2store(end+2, row_length); + } + } + + if (offset != next_free_pos) + { + uint length= (next_free_pos - start_of_found_block); + /* + There was empty space before this and prev block + Check if we have to move previous block up to page start + */ + if (page_pos != start_of_found_block) + { + /* move up previous block */ + memmove(buff + page_pos, buff + start_of_found_block, length); + } + page_pos+= length; + /* next continuous block starts here */ + start_of_found_block= offset; + diff= offset - page_pos; + } + int2store(end, offset - diff); /* correct current pos */ + next_free_pos= offset + row_length; + + if (unlikely(row_length < min_row_length) && row_length) + { + /* + This can only happen in the case we compacted transid and + the row become 'too short' + + Move the current row down to it's right place and extend it + with 0. + */ + uint row_diff= min_row_length - row_length; + uint length= (next_free_pos - start_of_found_block); + + DBUG_ASSERT(page_pos != start_of_found_block); + bmove(buff + page_pos, buff + start_of_found_block, length); + bzero(buff+ page_pos + length, row_diff); + page_pos+= min_row_length; + int2store(end+2, min_row_length); + freed_size-= row_diff; + next_free_pos= start_of_found_block= page_pos; + diff= 0; + } + } + } + if (page_pos != start_of_found_block) + { + uint length= (next_free_pos - start_of_found_block); + memmove(buff + page_pos, buff + start_of_found_block, length); + } + start_of_found_block= uint2korr(dir); + + if (rownr != max_entry - 1) + { + /* Move all entries after rownr to end of page */ + uint rownr_length; + next_free_pos= end_of_found_block= page_pos= + block_size - DIR_ENTRY_SIZE * max_entry - PAGE_SUFFIX_SIZE; + diff= 0; + /* End points to entry before 'rownr' */ + for (dir= buff + end_of_found_block ; dir <= end ; dir+= DIR_ENTRY_SIZE) + { + uint offset= uint2korr(dir); + uint row_length; + uint row_end; + if (!offset) + continue; + row_length= uint2korr(dir + 2); + row_end= offset + row_length; + DBUG_ASSERT(offset >= start_of_found_block && + row_end <= next_free_pos && row_length >= min_row_length); + + if (min_read_from && (buff[offset] & ROW_FLAG_TRANSID)) + { + TrID transid= transid_korr(buff + offset+1); + if (transid < min_read_from) + { + /* Remove transid from row */ + buff[offset + TRANSID_SIZE]= buff[offset] & ~ROW_FLAG_TRANSID; + offset+= TRANSID_SIZE; + row_length-= TRANSID_SIZE; + int2store(dir+2, row_length); + } + if (unlikely(row_length < min_row_length)) + { + /* + This can only happen in the case we compacted transid and + the row become 'too short' + */ + uint row_diff= min_row_length - row_length; + if (next_free_pos < row_end + row_diff) + { + /* + Not enough space for extending next block with enough + end 0's. Move current data down to get place for them + */ + uint move_down= row_diff - (next_free_pos - row_end); + bmove(buff + offset - move_down, buff + offset, row_length); + offset-= move_down; + } + /* + Extend the next block with 0, which will be part of current + row when the blocks are joined together later + */ + bzero(buff + next_free_pos - row_diff, row_diff); + next_free_pos-= row_diff; + int2store(dir+2, min_row_length); + } + row_end= offset + row_length; + } + + if (row_end != next_free_pos) + { + uint length= (end_of_found_block - next_free_pos); + if (page_pos != end_of_found_block) + { + /* move next block down */ + memmove(buff + page_pos - length, buff + next_free_pos, length); + } + page_pos-= length; + /* next continuous block starts here */ + end_of_found_block= row_end; + diff= page_pos - row_end; + } + int2store(dir, offset + diff); /* correct current pos */ + next_free_pos= offset; + } + + if (page_pos != end_of_found_block) + { + uint length= (end_of_found_block - next_free_pos); + memmove(buff + page_pos - length, buff + next_free_pos, length); + next_free_pos= page_pos- length; + } + /* Extend rownr block to cover hole */ + rownr_length= next_free_pos - start_of_found_block; + int2store(dir+2, rownr_length); + DBUG_ASSERT(rownr_length >= min_row_length); + } + else + { + if (extend_block) + { + /* Extend last block to cover whole page */ + uint length= ((uint) (dir - buff) - start_of_found_block); + int2store(dir+2, length); + DBUG_ASSERT(length >= min_row_length); + } + else + { + /* Add length gained from freed transaction id's to this page */ + uint length= uint2korr(buff+ EMPTY_SPACE_OFFSET) + freed_size; + int2store(buff + EMPTY_SPACE_OFFSET, length); + } + buff[PAGE_TYPE_OFFSET]&= ~(uchar) PAGE_CAN_BE_COMPACTED; + } + check_directory(buff, block_size, min_row_length); + DBUG_EXECUTE("directory", _ma_print_directory(buff, block_size);); + DBUG_VOID_RETURN; +} + + +/* + Create an empty tail or head page + + SYNOPSIS + make_empty_page() + buff Page buffer + block_size Block size + page_type HEAD_PAGE or TAIL_PAGE + create_dir_entry TRUE of we should create a directory entry + + NOTES + EMPTY_SPACE is not updated +*/ + +static void make_empty_page(MARIA_HA *info, uchar *buff, uint page_type, + my_bool create_dir_entry) +{ + uint block_size= info->s->block_size; + DBUG_ENTER("make_empty_page"); + + bzero(buff, PAGE_HEADER_SIZE); + +#if !defined(DONT_ZERO_PAGE_BLOCKS) || defined(HAVE_valgrind) + /* + We zero the rest of the block to avoid getting old memory information + to disk and to allow the file to be compressed better if archived. + The code does not assume the block is zeroed. + */ + if (page_type != BLOB_PAGE) + bzero(buff+ PAGE_HEADER_SIZE, block_size - PAGE_HEADER_SIZE); +#endif + buff[PAGE_TYPE_OFFSET]= (uchar) page_type; + buff[DIR_COUNT_OFFSET]= (int) create_dir_entry; + buff[DIR_FREE_OFFSET]= END_OF_DIR_FREE_LIST; + if (create_dir_entry) + { + /* Create directory entry to point to start of page with size 0 */ + buff+= block_size - PAGE_SUFFIX_SIZE - DIR_ENTRY_SIZE; + int2store(buff, PAGE_HEADER_SIZE); + int2store(buff+2, 0); + } + DBUG_VOID_RETURN; +} + + +/* + Read or initialize new head or tail page + + SYNOPSIS + get_head_or_tail_page() + info Maria handler + block Block to read + buff Suggest this buffer to key cache + length Minimum space needed + page_type HEAD_PAGE || TAIL_PAGE + res Store result position here + + NOTES + We don't decremented buff[EMPTY_SPACE_OFFSET] with the allocated data + as we don't know how much data the caller will actually use. + + res->empty_space is set to length of empty space + + RETURN + 0 ok All slots in 'res' are updated + 1 error my_errno is set +*/ + +struct st_row_pos_info +{ + uchar *buff; /* page buffer */ + uchar *data; /* Place for data */ + uchar *dir; /* Directory */ + uint length; /* Length for data */ + uint rownr; /* Offset in directory */ + uint empty_space; /* Space left on page */ +}; + + +static my_bool get_head_or_tail_page(MARIA_HA *info, + MARIA_BITMAP_BLOCK *block, + uchar *buff, uint length, uint page_type, + enum pagecache_page_lock lock, + struct st_row_pos_info *res) +{ + uint block_size; + MARIA_PINNED_PAGE page_link; + MARIA_SHARE *share= info->s; + DBUG_ENTER("get_head_or_tail_page"); + DBUG_PRINT("enter", ("length: %u", length)); + + block_size= share->block_size; + if (block->org_bitmap_value == 0) /* Empty block */ + { + /* New page */ + make_empty_page(info, buff, page_type, 1); + res->buff= buff; + res->empty_space= res->length= (block_size - PAGE_OVERHEAD_SIZE); + res->data= (buff + PAGE_HEADER_SIZE); + res->dir= res->data + res->length; + res->rownr= 0; + DBUG_ASSERT(length <= res->length); + } + else + { + uchar *dir; + /* Read old page */ + page_link.unlock= PAGECACHE_LOCK_WRITE_UNLOCK; + res->buff= pagecache_read(share->pagecache, &info->dfile, + block->page, 0, 0, share->page_type, + lock, &page_link.link); + page_link.changed= res->buff != 0; + push_dynamic(&info->pinned_pages, (void*) &page_link); + if (!page_link.changed) + goto crashed; + + DBUG_ASSERT((res->buff[PAGE_TYPE_OFFSET] & PAGE_TYPE_MASK) == page_type); + if (!(dir= find_free_position(page_type == HEAD_PAGE ? info : 0, + res->buff, block_size, &res->rownr, + &res->length, &res->empty_space))) + goto crashed; + + if (res->length < length) + { + if (res->empty_space + res->length >= length) + { + _ma_compact_block_page(res->buff, block_size, res->rownr, 1, + (page_type == HEAD_PAGE ? + info->trn->min_read_from : 0), + (page_type == HEAD_PAGE ? + share->base.min_block_length : + 0)); + /* All empty space are now after current position */ + dir= dir_entry_pos(res->buff, block_size, res->rownr); + res->length= res->empty_space= uint2korr(dir+2); + } + if (res->length < length) + { + DBUG_PRINT("error", ("length: %u res->length: %u empty_space: %u", + length, res->length, res->empty_space)); + goto crashed; /* Wrong bitmap information */ + } + } + res->dir= dir; + res->data= res->buff + uint2korr(dir); + } + DBUG_RETURN(0); + +crashed: + my_errno= HA_ERR_WRONG_IN_RECORD; /* File crashed */ + DBUG_RETURN(1); +} + + +/* + @brief Create room for a head or tail row on a given page at given position + + @fn get_rowpos_in_head_or_tail_page() + @param info Maria handler + @param block Block to read + @param buff Suggest this buffer to key cache + @param length Minimum space needed + @param page_type HEAD_PAGE || TAIL_PAGE + @param rownr Rownr to use + @param res Store result position here + + @note + This is essential same as get_head_or_tail_page, with the difference + that the caller species at what position the row should be put. + This is used when restoring a row to it's original position as + part of UNDO DELETE or UNDO UPDATE + + @return + @retval 0 ok All slots in 'res' are updated + @retval 1 error my_errno is set +*/ + +static my_bool get_rowpos_in_head_or_tail_page(MARIA_HA *info, + MARIA_BITMAP_BLOCK *block, + uchar *buff, uint length, + uint page_type, + enum pagecache_page_lock lock, + uint rownr, + struct st_row_pos_info *res) +{ + MARIA_PINNED_PAGE page_link; + MARIA_SHARE *share= info->s; + uchar *dir; + uint block_size= share->block_size; + uint max_entry, max_length, rec_offset; + DBUG_ENTER("get_rowpos_in_head_or_tail_page"); + + if (block->org_bitmap_value == 0) /* Empty block */ + { + /* New page */ + make_empty_page(info, buff, page_type, 0); + res->empty_space= block_size - PAGE_HEADER_SIZE - PAGE_SUFFIX_SIZE; + } + else + { + page_link.unlock= PAGECACHE_LOCK_WRITE_UNLOCK; + buff= pagecache_read(share->pagecache, &info->dfile, + block->page, 0, 0, share->page_type, + lock, &page_link.link); + page_link.changed= buff != 0; + push_dynamic(&info->pinned_pages, (void*) &page_link); + if (!page_link.changed) /* Read error */ + goto err; + DBUG_ASSERT((buff[PAGE_TYPE_OFFSET] & PAGE_TYPE_MASK) == + (uchar) page_type); + if ((buff[PAGE_TYPE_OFFSET] & PAGE_TYPE_MASK) != (uchar) page_type) + goto err; + res->empty_space= uint2korr(buff + EMPTY_SPACE_OFFSET); + } + + max_entry= (uint) buff[DIR_COUNT_OFFSET]; + if (max_entry <= rownr) + { + if (extend_directory(page_type == HEAD_PAGE ? info : 0, buff, block_size, + max_entry, rownr, &res->empty_space)) + goto err; + } + + dir= dir_entry_pos(buff, block_size, rownr); +#ifdef SANITY_CHECKS + /* Tail's should always be unused */ + if (page_type == TAIL_PAGE && max_entry > rownr && + (dir[0] != 0 || dir[1] != 0)) + { + DBUG_ASSERT(0); + goto err; + } +#endif + + if (extend_area_on_page(page_type == HEAD_PAGE ? info : 0, buff, dir, + rownr, block_size, length, + &res->empty_space, &rec_offset, &max_length)) + goto err; + + res->buff= buff; + res->rownr= rownr; + res->dir= dir; + res->data= buff + rec_offset; + res->length= length; + DBUG_RETURN(0); + +err: + my_errno= HA_ERR_WRONG_IN_RECORD; /* File crashed */ + DBUG_RETURN(1); +} + + +/* + Write tail for head data or blob + + SYNOPSIS + write_tail() + info Maria handler + block Block to tail page + row_part Data to write to page + length Length of data + + NOTES + block->page_count is updated to the directory offset for the tail + so that we can store the position in the row extent information + + RETURN + 0 ok + block->page_count is set to point (dir entry + TAIL_BIT) + + 1 error; In this case my_errno is set to the error +*/ + +static my_bool write_tail(MARIA_HA *info, + MARIA_BITMAP_BLOCK *block, + uchar *row_part, uint org_length) +{ + MARIA_SHARE *share= info->s; + MARIA_PINNED_PAGE page_link; + uint block_size= share->block_size, empty_space, length= org_length; + struct st_row_pos_info row_pos; + my_off_t position; + my_bool res, block_is_read; + DBUG_ENTER("write_tail"); + DBUG_PRINT("enter", ("page: %lu length: %u", + (ulong) block->page, length)); + + info->keyread_buff_used= 1; + /* + Don't allocate smaller block than MIN_TAIL_SIZE (we want to give rows + some place to grow in the future) + */ + if (length < MIN_TAIL_SIZE) + length= MIN_TAIL_SIZE; + + if (block->page_count == TAIL_PAGE_COUNT_MARKER) + { + /* + Create new tail + page will be pinned & locked by get_head_or_tail_page + */ + if (get_head_or_tail_page(info, block, info->keyread_buff, length, + TAIL_PAGE, PAGECACHE_LOCK_WRITE, + &row_pos)) + DBUG_RETURN(1); + } + else + { + /* Write tail on predefined row position */ + if (get_rowpos_in_head_or_tail_page(info, block, info->keyread_buff, + length, TAIL_PAGE, + PAGECACHE_LOCK_WRITE, + block->page_count & ~TAIL_BIT, + &row_pos)) + DBUG_RETURN(1); + } + DBUG_PRINT("info", ("tailid: %lu (%lu:%u)", + (ulong) ma_recordpos(block->page, row_pos.rownr), + (ulong) block->page, row_pos.rownr)); + + block_is_read= block->org_bitmap_value != 0; + + memcpy(row_pos.data, row_part, org_length); + + if (share->now_transactional) + { + /* Log changes in tail block */ + uchar log_data[FILEID_STORE_SIZE + PAGE_STORE_SIZE + DIRPOS_STORE_SIZE]; + LEX_CUSTRING log_array[TRANSLOG_INTERNAL_PARTS + 2]; + LSN lsn; + + /* + Log REDO changes of tail page + Note that we have to log length, not org_length, to be sure that + REDO, which doesn't use write_tail, also creates a block of at least + MIN_TAIL_SIZE + */ + page_store(log_data + FILEID_STORE_SIZE, block->page); + dirpos_store(log_data + FILEID_STORE_SIZE + PAGE_STORE_SIZE, + row_pos.rownr); + log_array[TRANSLOG_INTERNAL_PARTS + 0].str= log_data; + log_array[TRANSLOG_INTERNAL_PARTS + 0].length= sizeof(log_data); + log_array[TRANSLOG_INTERNAL_PARTS + 1].str= row_pos.data; + log_array[TRANSLOG_INTERNAL_PARTS + 1].length= length; + if (translog_write_record(&lsn, + (block_is_read ? LOGREC_REDO_INSERT_ROW_TAIL : + LOGREC_REDO_NEW_ROW_TAIL), + info->trn, info, + (translog_size_t) (sizeof(log_data) + length), + TRANSLOG_INTERNAL_PARTS + 2, log_array, + log_data, NULL)) + DBUG_RETURN(1); + } + + int2store(row_pos.dir + 2, length); + empty_space= row_pos.empty_space - length; + int2store(row_pos.buff + EMPTY_SPACE_OFFSET, empty_space); + block->page_count= row_pos.rownr + TAIL_BIT; + /* + If there is less directory entries free than number of possible tails + we can write for a row, we mark the page full to ensure that we don't + during _ma_bitmap_find_place() allocate more entries on the tail page + than it can hold + */ + block->empty_space= (enough_free_entries(row_pos.buff, share->block_size, + 1 + share->base.blobs) ? + empty_space : 0); + block->used= BLOCKUSED_USED | BLOCKUSED_TAIL; + + /* Increase data file size, if extended */ + position= (my_off_t) block->page * block_size; + if (share->state.state.data_file_length <= position) + { + /* + We are modifying a state member before writing the UNDO; this is a WAL + violation. But for data_file_length this is ok, as long as we change + data_file_length after writing any log record (FILE_ID/REDO/UNDO) (see + collect_tables()). + */ + _ma_set_share_data_file_length(share, position + block_size); + } + + if (block_is_read) + { + /* Current page link is last element in pinned_pages */ + MARIA_PINNED_PAGE *page_link; + page_link= dynamic_element(&info->pinned_pages, + info->pinned_pages.elements-1, + MARIA_PINNED_PAGE*); + pagecache_unlock_by_link(share->pagecache, page_link->link, + PAGECACHE_LOCK_WRITE_TO_READ, + PAGECACHE_PIN_LEFT_PINNED, LSN_IMPOSSIBLE, + LSN_IMPOSSIBLE, 1, FALSE); + DBUG_ASSERT(page_link->changed); + page_link->unlock= PAGECACHE_LOCK_READ_UNLOCK; + res= 0; + } + else if (!(res= pagecache_write(share->pagecache, + &info->dfile, block->page, 0, + row_pos.buff,share->page_type, + PAGECACHE_LOCK_READ, + PAGECACHE_PIN, + PAGECACHE_WRITE_DELAY, &page_link.link, + LSN_IMPOSSIBLE))) + { + page_link.unlock= PAGECACHE_LOCK_READ_UNLOCK; + page_link.changed= 1; + push_dynamic(&info->pinned_pages, (void*) &page_link); + } + DBUG_RETURN(res); +} + + +/* + Write full pages + + SYNOPSIS + write_full_pages() + info Maria handler + lsn LSN for the undo record + block Where to write data + data Data to write + length Length of data + + NOTES + Logging of the changes to the full pages are done in the caller + write_block_record(). + + RETURN + 0 ok + 1 error on write +*/ + +static my_bool write_full_pages(MARIA_HA *info, + LSN lsn, + MARIA_BITMAP_BLOCK *block, + uchar *data, ulong length) +{ + pgcache_page_no_t page; + MARIA_SHARE *share= info->s; + uint block_size= share->block_size; + uint data_size= FULL_PAGE_SIZE(block_size); + uchar *buff= info->keyread_buff; + uint page_count, sub_blocks; + my_off_t position; + DBUG_ENTER("write_full_pages"); + DBUG_PRINT("enter", ("length: %lu page: %lu page_count: %lu", + (ulong) length, (ulong) block->page, + (ulong) block->page_count)); + DBUG_ASSERT((block->page_count & TAIL_BIT) == 0); + + info->keyread_buff_used= 1; + page= block->page; + page_count= block->page_count; + sub_blocks= block->sub_blocks; + + position= (my_off_t) (page + page_count) * block_size; + if (share->state.state.data_file_length < position) + _ma_set_share_data_file_length(share, position); + + /* Increase data file size, if extended */ + + for (; length; data+= data_size) + { + uint copy_length; + if (!page_count--) + { + if (!--sub_blocks) + { + DBUG_ASSERT(0); /* Wrong in bitmap or UNDO */ + my_errno= HA_ERR_WRONG_IN_RECORD; /* File crashed */ + DBUG_RETURN(1); + } + + block++; + page= block->page; + page_count= block->page_count - 1; + DBUG_PRINT("info", ("page: %lu page_count: %lu", + (ulong) block->page, (ulong) block->page_count)); + + position= (page + page_count + 1) * block_size; + if (share->state.state.data_file_length < position) + _ma_set_share_data_file_length(share, position); + } + lsn_store(buff, lsn); + buff[PAGE_TYPE_OFFSET]= (uchar) BLOB_PAGE; + copy_length= min(data_size, length); + memcpy(buff + LSN_SIZE + PAGE_TYPE_SIZE, data, copy_length); + length-= copy_length; + + /* + Zero out old information from the block. This removes possible + sensitive information from the block and also makes the file + easier to compress and easier to compare after recovery. + */ + if (copy_length != data_size) + bzero(buff + block_size - PAGE_SUFFIX_SIZE - (data_size - copy_length), + (data_size - copy_length) + PAGE_SUFFIX_SIZE); + + if (pagecache_write(share->pagecache, + &info->dfile, page, 0, + buff, share->page_type, + PAGECACHE_LOCK_LEFT_UNLOCKED, + PAGECACHE_PIN_LEFT_UNPINNED, + PAGECACHE_WRITE_DELAY, + 0, info->trn->rec_lsn)) + DBUG_RETURN(1); + page++; + DBUG_ASSERT(block->used & BLOCKUSED_USED); + } + DBUG_RETURN(0); +} + + +/* + Store ranges of full pages in compact format for logging + + SYNOPSIS + store_page_range() + to Store data here + block Where pages are to be written + block_size block size + length Length of data to be written + Normally this is full pages, except for the last + tail block that may only partly fit the last page. + tot_ranges Add here the number of ranges used + + NOTES + The format of one entry is: + + Ranges SUB_RANGE_SIZE + Empty bytes at end of last byte BLOCK_FILLER_SIZE + For each range + Page number PAGE_STORE_SIZE + Number of pages PAGERANGE_STORE_SIZE + + RETURN + # end position for 'to' +*/ + +static uchar *store_page_range(uchar *to, MARIA_BITMAP_BLOCK *block, + uint block_size, ulong length, + uint *tot_ranges) +{ + uint data_size= FULL_PAGE_SIZE(block_size); + ulong pages_left= (length + data_size -1) / data_size; + uint page_count, ranges, empty_space; + uchar *to_start; + DBUG_ENTER("store_page_range"); + + to_start= to; + to+= SUB_RANGE_SIZE; + + /* Store number of unused bytes at last page */ + empty_space= (uint) (pages_left * data_size - length); + int2store(to, empty_space); + to+= BLOCK_FILLER_SIZE; + + ranges= 0; + do + { + pgcache_page_no_t page; + page= block->page; + page_count= block->page_count; + block++; + if (page_count > pages_left) + page_count= pages_left; + + page_store(to, page); + to+= PAGE_STORE_SIZE; + pagerange_store(to, page_count); + to+= PAGERANGE_STORE_SIZE; + ranges++; + } while ((pages_left-= page_count)); + /* Store number of ranges for this block */ + int2store(to_start, ranges); + (*tot_ranges)+= ranges; + + DBUG_RETURN(to); +} + + +/* + Store packed extent data + + SYNOPSIS + store_extent_info() + to Store first packed data here + row_extents_second_part Store rest here + first_block First block to store + count Number of blocks + + NOTES + We don't have to store the position for the head block + + We have to set the START_EXTENT_BIT for every extent where the + blob will be stored on a page of it's own. We need this in the + UNDO phase to generate MARIA_BITMAP_BLOCK's for undo-delete and + undo-update. +*/ + +static void store_extent_info(uchar *to, + uchar *row_extents_second_part, + MARIA_BITMAP_BLOCK *first_block, + uint count) +{ + MARIA_BITMAP_BLOCK *block, *end_block; + uint copy_length; + my_bool first_found= 0; + + for (block= first_block, end_block= first_block+count ; + block < end_block; block++) + { + /* The following is only false for marker blocks */ + if (likely(block->used & BLOCKUSED_USED)) + { + uint page_count= block->page_count; + DBUG_ASSERT(page_count != 0); + page_store(to, block->page); + if (block->sub_blocks) + { + /* + Set a bit so that we later know that this was the first block + for a blob + */ + page_count|= START_EXTENT_BIT; + } + pagerange_store(to + PAGE_STORE_SIZE, page_count); + to+= ROW_EXTENT_SIZE; + if (!first_found) + { + first_found= 1; + to= row_extents_second_part; + } + } + } + copy_length= (count - 1) * ROW_EXTENT_SIZE; + /* + In some unlikely cases we have allocated to many blocks. Clear this + data. + */ + bzero(to, (size_t) (row_extents_second_part + copy_length - to)); +} + + +/** + @brief + Convert extent info read from file to MARIA_BITMAP_BLOCKS suitable + for write_block_record + + @note + In case of blobs, this function marks all the blob pages in the bitmap + as full pages. The bitmap bits for other pages will be marked + when write_block_record() calls _ma_bitmap_release_unused(). + + This function will be removed in Maria 2.0 when we instead of delete rows + mark them as deleted and only remove them after commit. + + @return + @retval 0 ok + @retval 1 Error (out of memory or disk error changing bitmap) +*/ + +static my_bool extent_to_bitmap_blocks(MARIA_HA *info, + MARIA_BITMAP_BLOCKS *blocks, + pgcache_page_no_t head_page, + uint extent_count, + const uchar *extent_info) +{ + MARIA_BITMAP_BLOCK *block, *start_block; + MARIA_SHARE *share= info->s; + uint i; + DBUG_ENTER("extent_to_bitmap_blocks"); + + if (allocate_dynamic(&info->bitmap_blocks, extent_count + 2)) + DBUG_RETURN(1); + block= blocks->block= dynamic_element(&info->bitmap_blocks, 0, + MARIA_BITMAP_BLOCK*); + blocks->count= extent_count + 1; + blocks->tail_page_skipped= blocks->page_skipped= 0; + block->page= head_page; + block->page_count= 1; + block->used= BLOCKUSED_USED | BLOCKUSED_USE_ORG_BITMAP; + /* Impossible value, will force storage of real value */ + block->org_bitmap_value= 255; + + start_block= block++; + for (i=0 ; + i++ < extent_count ; + block++, extent_info+= ROW_EXTENT_SIZE) + { + uint page_count= uint2korr(extent_info + ROW_EXTENT_PAGE_SIZE); + if (page_count & START_EXTENT_BIT) + { + page_count&= ~START_EXTENT_BIT; + start_block->sub_blocks= (uint) (block - start_block); + start_block= block; + + } + block->page= page_korr(extent_info); + block->page_count= page_count; + block->sub_blocks= 0; + + if (page_count & TAIL_BIT) + { + block->org_bitmap_value= _ma_bitmap_get_page_bits(info, &share->bitmap, + block->page); + block->used= (BLOCKUSED_TAIL | BLOCKUSED_USED | + BLOCKUSED_USE_ORG_BITMAP); + } + else + { + my_bool res; + pthread_mutex_lock(&share->bitmap.bitmap_lock); + res= _ma_bitmap_set_full_page_bits(info, &share->bitmap, + block->page, block->page_count); + pthread_mutex_unlock(&share->bitmap.bitmap_lock); + if (res) + DBUG_RETURN(1); + block->used= BLOCKUSED_USED; + } + } + start_block->sub_blocks= (uint) (block - start_block); + DBUG_RETURN(0); +} + + +/* + Free regions of pages with logging + + NOTES + We are removing filler events and tail page events from + row->extents to get smaller log. + + RETURN + 0 ok + 1 error +*/ + +static my_bool free_full_pages(MARIA_HA *info, MARIA_ROW *row) +{ + uchar log_data[FILEID_STORE_SIZE + PAGERANGE_STORE_SIZE]; + LEX_CUSTRING log_array[TRANSLOG_INTERNAL_PARTS + 2]; + LSN lsn; + size_t extents_length; + uchar *extents= row->extents; + DBUG_ENTER("free_full_pages"); + + if (info->s->now_transactional) + { + /* Compact events by removing filler and tail events */ + uchar *new_block= 0; + uchar *end, *to, *compact_extent_info; + my_bool res; + uint extents_count; + + if (!(compact_extent_info= my_alloca(row->extents_count * + ROW_EXTENT_SIZE))) + DBUG_RETURN(1); + + to= compact_extent_info; + for (end= extents + row->extents_count * ROW_EXTENT_SIZE ; + extents < end ; + extents+= ROW_EXTENT_SIZE) + { + uint page_count= uint2korr(extents + ROW_EXTENT_PAGE_SIZE); + page_count&= ~START_EXTENT_BIT; + if (! (page_count & TAIL_BIT) && page_count != 0) + { + /* Found correct extent */ + if (!new_block) + new_block= extents; /* First extent in range */ + continue; + } + /* Found extent to remove, copy everything found so far */ + if (new_block) + { + size_t length= (size_t) (extents - new_block); + memcpy(to, new_block, length); + to+= length; + new_block= 0; + } + } + if (new_block) + { + size_t length= (size_t) (extents - new_block); + memcpy(to, new_block, length); + to+= length; + } + + if (!unlikely(extents_length= (uint) (to - compact_extent_info))) + { + /* + No ranges. This happens in the rear case when we have a allocated + place for a blob on a tail page but it did fit into the main page. + */ + my_afree(compact_extent_info); + DBUG_RETURN(0); + } + extents_count= (uint) (extents_length / ROW_EXTENT_SIZE); + pagerange_store(log_data + FILEID_STORE_SIZE, extents_count); + log_array[TRANSLOG_INTERNAL_PARTS + 0].str= log_data; + log_array[TRANSLOG_INTERNAL_PARTS + 0].length= sizeof(log_data); + log_array[TRANSLOG_INTERNAL_PARTS + 1].str= compact_extent_info; + log_array[TRANSLOG_INTERNAL_PARTS + 1].length= extents_length; + res= translog_write_record(&lsn, LOGREC_REDO_FREE_BLOCKS, info->trn, + info, + (translog_size_t) (sizeof(log_data) + + extents_length), + TRANSLOG_INTERNAL_PARTS + 2, log_array, + log_data, NULL); + my_afree(compact_extent_info); + if (res) + DBUG_RETURN(1); + } + + DBUG_RETURN(_ma_bitmap_free_full_pages(info, row->extents, + row->extents_count)); +} + + +/* + Free one page range + + NOTES + This is very similar to free_full_pages() + + RETURN + 0 ok + 1 error +*/ + +static my_bool free_full_page_range(MARIA_HA *info, pgcache_page_no_t page, + uint count) +{ + my_bool res= 0; + uint delete_count; + MARIA_SHARE *share= info->s; + DBUG_ENTER("free_full_page_range"); + + delete_count= count; + if (share->state.state.data_file_length == + (page + count) * share->block_size) + { + /* + Don't delete last page from pagecache as this will make the file + shorter than expected if the last operation extended the file + */ + delete_count--; + } + if (delete_count && + pagecache_delete_pages(share->pagecache, &info->dfile, + page, delete_count, PAGECACHE_LOCK_WRITE, 0)) + res= 1; + + if (share->now_transactional) + { + LSN lsn; + /** @todo unify log_data's shape with delete_head_or_tail() */ + uchar log_data[FILEID_STORE_SIZE + PAGERANGE_STORE_SIZE + + ROW_EXTENT_SIZE]; + LEX_CUSTRING log_array[TRANSLOG_INTERNAL_PARTS + 1]; + DBUG_ASSERT(info->trn->rec_lsn); + pagerange_store(log_data + FILEID_STORE_SIZE, 1); + page_store(log_data + FILEID_STORE_SIZE + PAGERANGE_STORE_SIZE, + page); + int2store(log_data + FILEID_STORE_SIZE + PAGERANGE_STORE_SIZE + + PAGE_STORE_SIZE, count); + log_array[TRANSLOG_INTERNAL_PARTS + 0].str= log_data; + log_array[TRANSLOG_INTERNAL_PARTS + 0].length= sizeof(log_data); + + if (translog_write_record(&lsn, LOGREC_REDO_FREE_BLOCKS, + info->trn, info, + (translog_size_t) sizeof(log_data), + TRANSLOG_INTERNAL_PARTS + 1, log_array, + log_data, NULL)) + res= 1; + } + pthread_mutex_lock(&share->bitmap.bitmap_lock); + if (_ma_bitmap_reset_full_page_bits(info, &share->bitmap, page, count)) + res= 1; + pthread_mutex_unlock(&share->bitmap.bitmap_lock); + DBUG_RETURN(res); +} + + +/** + @brief Write a record to a (set of) pages + + @fn write_block_record() + @param info Maria handler + @param old_record Original record in case of update; NULL in case of + insert + @param record Record we should write + @param row Statistics about record (calculated by + calc_record_size()) + @param map_blocks On which pages the record should be stored + @param row_pos Position on head page where to put head part of + record + @param undo_lsn <> LSN_ERROR if we are executing an UNDO + @param old_record_checksum Checksum of old_record: ignored if table does + not have live checksum; otherwise if + old_record==NULL it must be 0. + + @note + On return all pinned pages are released. + + [page_buff + EMPTY_SPACE_OFFSET] is set to + row_pos->empty_space - head_length + + @return Operation status + @retval 0 OK + @retval 1 Error +*/ + +static my_bool write_block_record(MARIA_HA *info, + const uchar *old_record, + const uchar *record, + MARIA_ROW *row, + MARIA_BITMAP_BLOCKS *bitmap_blocks, + my_bool head_block_is_read, + struct st_row_pos_info *row_pos, + LSN undo_lsn, + ha_checksum old_record_checksum) +{ + uchar *data, *end_of_data, *tmp_data_used, *tmp_data; + uchar *row_extents_first_part, *row_extents_second_part; + uchar *field_length_data; + uchar *page_buff; + MARIA_BITMAP_BLOCK *block, *head_block; + MARIA_SHARE *share= info->s; + MARIA_COLUMNDEF *column, *end_column; + MARIA_PINNED_PAGE page_link; + uint block_size, flag, head_length; + ulong *blob_lengths; + my_bool row_extents_in_use, blob_full_pages_exists; + LSN lsn; + my_off_t position; + uint save_my_errno; + DBUG_ENTER("write_block_record"); + + LINT_INIT(row_extents_first_part); + LINT_INIT(row_extents_second_part); + + head_block= bitmap_blocks->block; + block_size= share->block_size; + + page_buff= row_pos->buff; + /* Position on head page where we should store the head part */ + data= row_pos->data; + end_of_data= data + row_pos->length; + + /* Write header */ + flag= info->row_flag; + row_extents_in_use= 0; + if (unlikely(row->total_length > row_pos->length)) + { + /* Need extent */ + DBUG_ASSERT(bitmap_blocks->count > 1); + if (bitmap_blocks->count <= 1) + goto crashed; /* Wrong in bitmap */ + flag|= ROW_FLAG_EXTENTS; + row_extents_in_use= 1; + } + /* For now we have only a minimum header */ + *data++= (uchar) flag; + if (flag & ROW_FLAG_TRANSID) + { + transid_store(data, info->trn->trid); + data+= TRANSID_SIZE; + } + + if (unlikely(flag & ROW_FLAG_NULLS_EXTENDED)) + *data++= (uchar) (share->base.null_bytes - + share->base.original_null_bytes); + if (row_extents_in_use) + { + /* Store first extent in header */ + store_key_length_inc(data, bitmap_blocks->count - 1); + row_extents_first_part= data; + data+= ROW_EXTENT_SIZE; + } + if (share->base.max_field_lengths) + store_key_length_inc(data, row->field_lengths_length); + if (share->calc_checksum) + { + *(data++)= (uchar) (row->checksum); /* store least significant byte */ + DBUG_ASSERT(!((old_record_checksum != 0) && (old_record == NULL))); + } + memcpy(data, record, share->base.null_bytes); + data+= share->base.null_bytes; + memcpy(data, row->empty_bits, share->base.pack_bytes); + data+= share->base.pack_bytes; + + DBUG_ASSERT(row_extents_in_use || undo_lsn != LSN_ERROR || + (uint) (data - row_pos->data) == row->min_length); + + /* + Allocate a buffer of rest of data (except blobs) + + To avoid double copying of data, we copy as many columns that fits into + the page. The rest goes into info->packed_row. + + Using an extra buffer, instead of doing continuous writes to different + pages, uses less code and we don't need to have to do a complex call + for every data segment we want to store. + */ + if (_ma_alloc_buffer(&info->rec_buff, &info->rec_buff_size, + row->head_length)) + DBUG_RETURN(1); + + tmp_data_used= 0; /* Either 0 or last used uchar in 'data' */ + tmp_data= data; + + if (row_extents_in_use) + { + uint copy_length= (bitmap_blocks->count - 2) * ROW_EXTENT_SIZE; + if (!tmp_data_used && tmp_data + copy_length > end_of_data) + { + tmp_data_used= tmp_data; + tmp_data= info->rec_buff; + } + row_extents_second_part= tmp_data; + /* + We will copy the extents here when we have figured out the tail + positions. + */ + tmp_data+= copy_length; + } + + /* Copy fields that has fixed lengths (primary key etc) */ + for (column= share->columndef, + end_column= column + share->base.fixed_not_null_fields; + column < end_column; column++) + { + if (!tmp_data_used && tmp_data + column->length > end_of_data) + { + tmp_data_used= tmp_data; + tmp_data= info->rec_buff; + } + memcpy(tmp_data, record + column->offset, column->length); + tmp_data+= column->length; + } + + /* Copy length of data for variable length fields */ + if (!tmp_data_used && tmp_data + row->field_lengths_length > end_of_data) + { + tmp_data_used= tmp_data; + tmp_data= info->rec_buff; + } + field_length_data= row->field_lengths; + memcpy(tmp_data, field_length_data, row->field_lengths_length); + tmp_data+= row->field_lengths_length; + + DBUG_ASSERT(row_extents_in_use || undo_lsn != LSN_ERROR || + (uint) (tmp_data - row_pos->data) == row->min_length + + share->base.fixed_not_null_fields_length + + row->field_lengths_length); + + /* Copy variable length fields and fields with null/zero */ + for (end_column= share->columndef + share->base.fields - share->base.blobs; + column < end_column ; + column++) + { + const uchar *field_pos; + ulong length; + if ((record[column->null_pos] & column->null_bit) || + (row->empty_bits[column->empty_pos] & column->empty_bit)) + continue; + + field_pos= record + column->offset; + switch (column->type) { + case FIELD_NORMAL: /* Fixed length field */ + case FIELD_SKIP_PRESPACE: + case FIELD_SKIP_ZERO: /* Fixed length field */ + length= column->length; + break; + case FIELD_SKIP_ENDSPACE: /* CHAR */ + /* Char that is space filled */ + if (column->length <= 255) + length= (uint) (uchar) *field_length_data++; + else + { + length= uint2korr(field_length_data); + field_length_data+= 2; + } + break; + case FIELD_VARCHAR: + if (column->length <= 256) + { + length= (uint) (uchar) *field_length_data++; + field_pos++; /* Skip length uchar */ + } + else + { + length= uint2korr(field_length_data); + field_length_data+= 2; + field_pos+= 2; + } + DBUG_ASSERT(length <= column->length); + break; + default: /* Wrong data */ + DBUG_ASSERT(0); + length=0; + break; + } + if (!tmp_data_used && tmp_data + length > end_of_data) + { + /* Data didn't fit in page; Change to use tmp buffer */ + tmp_data_used= tmp_data; + tmp_data= info->rec_buff; + } + memcpy((char*) tmp_data, field_pos, length); + tmp_data+= length; + } + + block= head_block + head_block->sub_blocks; /* Point to first blob data */ + + end_column= column + share->base.blobs; + blob_lengths= row->blob_lengths; + if (!tmp_data_used) + { + /* Still room on page; Copy as many blobs we can into this page */ + data= tmp_data; + for (; column < end_column && + *blob_lengths <= (ulong)(end_of_data - data); + column++, blob_lengths++) + { + uchar *tmp_pos; + uint length; + if (!*blob_lengths) /* Null or "" */ + continue; + length= column->length - portable_sizeof_char_ptr; + memcpy_fixed((uchar*) &tmp_pos, record + column->offset + length, + sizeof(char*)); + memcpy(data, tmp_pos, *blob_lengths); + data+= *blob_lengths; + /* Skip over tail page that was to be used to store blob */ + block++; + bitmap_blocks->tail_page_skipped= 1; + } + if (head_block->sub_blocks > 1) + { + /* We have allocated pages that where not used */ + bitmap_blocks->page_skipped= 1; + } + } + else + data= tmp_data_used; /* Get last used on page */ + + /* Update page directory */ + head_length= (uint) (data - row_pos->data); + DBUG_PRINT("info", ("Used head length on page: %u", head_length)); + DBUG_ASSERT(data <= end_of_data); + if (head_length < share->base.min_block_length) + { + /* Extend row to be of size min_block_length */ + uint diff_length= share->base.min_block_length - head_length; + bzero(data, diff_length); + data+= diff_length; + head_length= share->base.min_block_length; + } + int2store(row_pos->dir + 2, head_length); + /* update empty space at start of block */ + row_pos->empty_space-= head_length; + int2store(page_buff + EMPTY_SPACE_OFFSET, row_pos->empty_space); + /* Mark in bitmaps how the current page was actually used */ + head_block->empty_space= row_pos->empty_space; + if (page_buff[DIR_COUNT_OFFSET] == MAX_ROWS_PER_PAGE && + page_buff[DIR_FREE_OFFSET] == END_OF_DIR_FREE_LIST) + head_block->empty_space= 0; /* Page is full */ + head_block->used|= BLOCKUSED_USED; + + check_directory(page_buff, share->block_size, share->base.min_block_length); + + /* + Now we have to write tail pages, as we need to store the position + to them in the row extent header. + + We first write out all blob tails, to be able to store them in + the current page or 'tmp_data'. + + Then we write the tail of the non-blob fields (The position to the + tail page is stored either in row header, the extents in the head + page or in the first full page of the non-blob data. It's never in + the tail page of the non-blob data) + */ + + blob_full_pages_exists= 0; + if (row_extents_in_use) + { + if (column != end_column) /* If blob fields */ + { + MARIA_COLUMNDEF *save_column= column; + MARIA_BITMAP_BLOCK *save_block= block; + MARIA_BITMAP_BLOCK *end_block; + ulong *save_blob_lengths= blob_lengths; + + for (; column < end_column; column++, blob_lengths++) + { + uchar *blob_pos; + if (!*blob_lengths) /* Null or "" */ + continue; + if (block[block->sub_blocks - 1].used & BLOCKUSED_TAIL) + { + uint length; + length= column->length - portable_sizeof_char_ptr; + memcpy_fixed((uchar *) &blob_pos, record + column->offset + length, + sizeof(char*)); + length= *blob_lengths % FULL_PAGE_SIZE(block_size); /* tail size */ + if (length != *blob_lengths) + blob_full_pages_exists= 1; + if (write_tail(info, block + block->sub_blocks-1, + blob_pos + *blob_lengths - length, + length)) + goto disk_err; + } + else + blob_full_pages_exists= 1; + + for (end_block= block + block->sub_blocks; block < end_block; block++) + { + /* + Set only a bit, to not cause bitmap code to believe a block is full + when there is still a lot of entries in it + */ + block->used|= BLOCKUSED_USED; + } + } + column= save_column; + block= save_block; + blob_lengths= save_blob_lengths; + } + + if (tmp_data_used) /* non blob data overflows */ + { + MARIA_BITMAP_BLOCK *cur_block, *end_block, *last_head_block; + MARIA_BITMAP_BLOCK *head_tail_block= 0; + ulong length; + ulong data_length= (ulong) (tmp_data - info->rec_buff); + +#ifdef SANITY_CHECKS + DBUG_ASSERT(head_block->sub_blocks != 1); + if (head_block->sub_blocks == 1) + goto crashed; /* no reserved full or tails */ +#endif + /* + Find out where to write tail for non-blob fields. + + Problem here is that the bitmap code may have allocated more + space than we need. We have to handle the following cases: + + - Bitmap code allocated a tail page we don't need. + - The last full page allocated needs to be changed to a tail page + (Because we where able to put more data on the head page than + the bitmap allocation assumed) + + The reserved pages in bitmap_blocks for the main page has one of + the following allocations: + - Full pages, with following blocks: + # * full pages + empty page ; To be used if we change last full to tail page. This + has 'count' = 0. + tail page (optional, if last full page was part full) + - One tail page + */ + + cur_block= head_block + 1; + end_block= head_block + head_block->sub_blocks; + /* + Loop until we have find a block bigger than we need or + we find the empty page block. + */ + while (data_length >= (length= (cur_block->page_count * + FULL_PAGE_SIZE(block_size))) && + cur_block->page_count) + { +#ifdef SANITY_CHECKS + DBUG_ASSERT(!((cur_block == end_block) || + (cur_block->used & BLOCKUSED_USED))); + if ((cur_block == end_block) || (cur_block->used & BLOCKUSED_USED)) + goto crashed; +#endif + data_length-= length; + (cur_block++)->used|= BLOCKUSED_USED; + } + last_head_block= cur_block; + if (data_length) + { + if (cur_block->page_count == 0) + { + /* Skip empty filler block */ + cur_block++; + } +#ifdef SANITY_CHECKS + DBUG_ASSERT(!(cur_block >= end_block)); + if ((cur_block >= end_block)) + goto crashed; +#endif + if (cur_block->used & BLOCKUSED_TAIL) + { + DBUG_ASSERT(data_length < MAX_TAIL_SIZE(block_size)); + /* tail written to tail page */ + cur_block->used|= BLOCKUSED_USED; + head_tail_block= cur_block; + } + else if (data_length > length - MAX_TAIL_SIZE(block_size)) + { + /* tail written to full page */ + cur_block->used|= BLOCKUSED_USED; + if ((cur_block != end_block - 1) && + (end_block[-1].used & BLOCKUSED_TAIL)) + bitmap_blocks->tail_page_skipped= 1; + } + else + { + /* + cur_block is a full block, followed by an empty and optional + tail block. Change cur_block to a tail block or split it + into full blocks and tail blocks. + + TODO: + If there is enough space on the following tail block, use + this instead of creating a new tail block. + */ + DBUG_ASSERT(cur_block[1].page_count == 0); + if (cur_block->page_count == 1) + { + /* convert full block to tail block */ + cur_block->used|= BLOCKUSED_USED | BLOCKUSED_TAIL; + head_tail_block= cur_block; + } + else + { + DBUG_ASSERT(data_length < length - FULL_PAGE_SIZE(block_size)); + DBUG_PRINT("info", ("Splitting blocks into full and tail")); + cur_block[1].page= (cur_block->page + cur_block->page_count - 1); + cur_block[1].page_count= 1; /* Avoid DBUG_ASSERT */ + cur_block[1].used= BLOCKUSED_USED | BLOCKUSED_TAIL; + cur_block->page_count--; + cur_block->used|= BLOCKUSED_USED; + last_head_block= head_tail_block= cur_block+1; + } + if (end_block[-1].used & BLOCKUSED_TAIL) + bitmap_blocks->tail_page_skipped= 1; + } + } + else + { + /* Must be an empty or tail page */ + DBUG_ASSERT(cur_block->page_count == 0 || + cur_block->used & BLOCKUSED_TAIL); + if (end_block[-1].used & BLOCKUSED_TAIL) + bitmap_blocks->tail_page_skipped= 1; + } + + /* + Write all extents into page or tmp_data + + Note that we still don't have a correct position for the tail + of the non-blob fields. + */ + store_extent_info(row_extents_first_part, + row_extents_second_part, + head_block+1, bitmap_blocks->count - 1); + if (head_tail_block) + { + ulong block_length= (ulong) (tmp_data - info->rec_buff); + uchar *extent_data; + + length= (uint) (block_length % FULL_PAGE_SIZE(block_size)); + if (write_tail(info, head_tail_block, + info->rec_buff + block_length - length, + length)) + goto disk_err; + tmp_data-= length; /* Remove the tail */ + if (tmp_data == info->rec_buff) + { + /* We have no full blocks to write for the head part */ + tmp_data_used= 0; + } + + /* Store the tail position for the non-blob fields */ + if (head_tail_block == head_block + 1) + { + /* + We had a head block + tail block, which means that the + tail block is the first extent + */ + extent_data= row_extents_first_part; + } + else + { + /* + We have a head block + some full blocks + tail block + last_head_block is pointing after the last used extent + for the head block. + */ + extent_data= row_extents_second_part + + ((last_head_block - head_block) - 2) * ROW_EXTENT_SIZE; + } + DBUG_ASSERT(uint2korr(extent_data+5) & TAIL_BIT); + page_store(extent_data, head_tail_block->page); + int2store(extent_data + PAGE_STORE_SIZE, head_tail_block->page_count); + } + } + else + store_extent_info(row_extents_first_part, + row_extents_second_part, + head_block+1, bitmap_blocks->count - 1); + } + + if (share->now_transactional) + { + uchar log_data[FILEID_STORE_SIZE + PAGE_STORE_SIZE + DIRPOS_STORE_SIZE]; + LEX_CUSTRING log_array[TRANSLOG_INTERNAL_PARTS + 2]; + + /* Log REDO changes of head page */ + page_store(log_data + FILEID_STORE_SIZE, head_block->page); + dirpos_store(log_data + FILEID_STORE_SIZE + PAGE_STORE_SIZE, + row_pos->rownr); + log_array[TRANSLOG_INTERNAL_PARTS + 0].str= log_data; + log_array[TRANSLOG_INTERNAL_PARTS + 0].length= sizeof(log_data); + log_array[TRANSLOG_INTERNAL_PARTS + 1].str= row_pos->data; + log_array[TRANSLOG_INTERNAL_PARTS + 1].length= head_length; + if (translog_write_record(&lsn, + head_block_is_read ? + LOGREC_REDO_INSERT_ROW_HEAD : + LOGREC_REDO_NEW_ROW_HEAD, + info->trn, + info, + (translog_size_t) (sizeof(log_data) + + head_length), + TRANSLOG_INTERNAL_PARTS + 2, log_array, + log_data, NULL)) + goto disk_err; + } + +#ifdef RECOVERY_EXTRA_DEBUG + if (info->trn->undo_lsn != LSN_IMPOSSIBLE) + { + /* Stop right after the REDO; testing incomplete log record groups */ + DBUG_EXECUTE_IF("maria_flush_whole_log", + { + DBUG_PRINT("maria_flush_whole_log", ("now")); + translog_flush(translog_get_horizon()); + }); + DBUG_EXECUTE_IF("maria_crash", + { DBUG_PRINT("maria_crash", ("now")); DBUG_ABORT(); }); + } +#endif + + /* Increase data file size, if extended */ + position= (my_off_t) head_block->page * block_size; + if (share->state.state.data_file_length <= position) + _ma_set_share_data_file_length(share, position + block_size); + + if (head_block_is_read) + { + MARIA_PINNED_PAGE *page_link; + /* Head page is always the first pinned page */ + page_link= dynamic_element(&info->pinned_pages, 0, + MARIA_PINNED_PAGE*); + pagecache_unlock_by_link(share->pagecache, page_link->link, + PAGECACHE_LOCK_WRITE_TO_READ, + PAGECACHE_PIN_LEFT_PINNED, LSN_IMPOSSIBLE, + LSN_IMPOSSIBLE, 1, FALSE); + page_link->unlock= PAGECACHE_LOCK_READ_UNLOCK; + page_link->changed= 1; + } + else + { + if (pagecache_write(share->pagecache, + &info->dfile, head_block->page, 0, + page_buff, share->page_type, + head_block_is_read ? PAGECACHE_LOCK_WRITE_TO_READ : + PAGECACHE_LOCK_READ, + head_block_is_read ? PAGECACHE_PIN_LEFT_PINNED : + PAGECACHE_PIN, + PAGECACHE_WRITE_DELAY, &page_link.link, + LSN_IMPOSSIBLE)) + goto disk_err; + page_link.unlock= PAGECACHE_LOCK_READ_UNLOCK; + page_link.changed= 1; + push_dynamic(&info->pinned_pages, (void*) &page_link); + } + + if (share->now_transactional && (tmp_data_used || blob_full_pages_exists)) + { + /* + Log REDO writes for all full pages (head part and all blobs) + We write all here to be able to generate the UNDO record early + so that we can write the LSN for the UNDO record to all full pages. + */ + uchar tmp_log_data[FILEID_STORE_SIZE + PAGERANGE_STORE_SIZE + + (ROW_EXTENT_SIZE + BLOCK_FILLER_SIZE + SUB_RANGE_SIZE) * + ROW_EXTENTS_ON_STACK]; + uchar *log_data, *log_pos; + LEX_CUSTRING tmp_log_array[TRANSLOG_INTERNAL_PARTS + 2 + + ROW_EXTENTS_ON_STACK]; + LEX_CUSTRING *log_array_pos, *log_array; + int error; + translog_size_t log_entry_length= 0; + uint ext_length, extents= 0, sub_extents= 0; + + /* If few extents, then allocate things on stack to avoid a malloc call */ + if (bitmap_blocks->count < ROW_EXTENTS_ON_STACK) + { + log_array= tmp_log_array; + log_data= tmp_log_data; + } + else + { + if (!my_multi_malloc(MY_WME, &log_array, + (uint) ((bitmap_blocks->count + + TRANSLOG_INTERNAL_PARTS + 2) * + sizeof(*log_array)), + &log_data, FILEID_STORE_SIZE + PAGERANGE_STORE_SIZE + + bitmap_blocks->count * (ROW_EXTENT_SIZE + + BLOCK_FILLER_SIZE + + SUB_RANGE_SIZE), + NullS)) + goto disk_err; + } + log_pos= log_data + FILEID_STORE_SIZE + PAGERANGE_STORE_SIZE * 2; + log_array_pos= log_array+ TRANSLOG_INTERNAL_PARTS+1; + + if (tmp_data_used) + { + /* Full head page */ + translog_size_t block_length= (translog_size_t) (tmp_data - + info->rec_buff); + log_pos= store_page_range(log_pos, head_block+1, block_size, + (ulong) block_length, &extents); + log_array_pos->str= info->rec_buff; + log_array_pos->length= block_length; + log_entry_length+= block_length; + log_array_pos++; + sub_extents++; + } + if (blob_full_pages_exists) + { + MARIA_COLUMNDEF *tmp_column= column; + ulong *tmp_blob_lengths= blob_lengths; + MARIA_BITMAP_BLOCK *tmp_block= block; + + /* Full blob pages */ + for (; tmp_column < end_column; tmp_column++, tmp_blob_lengths++) + { + ulong blob_length; + uint length; + + if (!*tmp_blob_lengths) /* Null or "" */ + continue; + blob_length= *tmp_blob_lengths; + length= tmp_column->length - portable_sizeof_char_ptr; + /* + If last part of blog was on tail page, change blob_length to + reflect this + */ + if (tmp_block[tmp_block->sub_blocks - 1].used & BLOCKUSED_TAIL) + blob_length-= (blob_length % FULL_PAGE_SIZE(block_size)); + if (blob_length) + { + memcpy_fixed((uchar*) &log_array_pos->str, + record + tmp_column->offset + length, + sizeof(uchar*)); + log_array_pos->length= blob_length; + log_entry_length+= blob_length; + log_array_pos++; + sub_extents++; + + log_pos= store_page_range(log_pos, tmp_block, block_size, + blob_length, &extents); + } + tmp_block+= tmp_block->sub_blocks; + } + } + + log_array[TRANSLOG_INTERNAL_PARTS + 0].str= log_data; + ext_length= (uint) (log_pos - log_data); + log_array[TRANSLOG_INTERNAL_PARTS + 0].length= ext_length; + pagerange_store(log_data+ FILEID_STORE_SIZE, extents); + pagerange_store(log_data+ FILEID_STORE_SIZE + PAGERANGE_STORE_SIZE, + sub_extents); + + log_entry_length+= ext_length; + /* trn->rec_lsn is already set earlier in this function */ + error= translog_write_record(&lsn, LOGREC_REDO_INSERT_ROW_BLOBS, + info->trn, info, log_entry_length, + (uint) (log_array_pos - log_array), + log_array, log_data, NULL); + if (log_array != tmp_log_array) + my_free(log_array, MYF(0)); + if (error) + goto disk_err; + } + + /* Write UNDO or CLR record */ + lsn= LSN_IMPOSSIBLE; + if (share->now_transactional) + { + LEX_CUSTRING *log_array= info->log_row_parts; + + if (undo_lsn != LSN_ERROR) + { + /* + Store if this CLR is about UNDO_DELETE or UNDO_UPDATE; + in the first case, Recovery, when it sees the CLR_END in the + REDO phase, may decrement the records' count. + */ + if (_ma_write_clr(info, undo_lsn, + old_record ? LOGREC_UNDO_ROW_UPDATE : + LOGREC_UNDO_ROW_DELETE, + share->calc_checksum != 0, + row->checksum - old_record_checksum, + &lsn, (void*) 0)) + goto disk_err; + } + else + { + uchar log_data[LSN_STORE_SIZE + FILEID_STORE_SIZE + + PAGE_STORE_SIZE + DIRPOS_STORE_SIZE + + HA_CHECKSUM_STORE_SIZE + 2 + PAGERANGE_STORE_SIZE + + ROW_EXTENT_SIZE]; + ha_checksum checksum_delta; + + /* LOGREC_UNDO_ROW_INSERT & LOGREC_UNDO_ROW_UPDATE share same header */ + lsn_store(log_data, info->trn->undo_lsn); + page_store(log_data + LSN_STORE_SIZE + FILEID_STORE_SIZE, + head_block->page); + dirpos_store(log_data + LSN_STORE_SIZE + FILEID_STORE_SIZE + + PAGE_STORE_SIZE, + row_pos->rownr); + + log_array[TRANSLOG_INTERNAL_PARTS + 0].str= log_data; + log_array[TRANSLOG_INTERNAL_PARTS + 0].length= + (LSN_STORE_SIZE + FILEID_STORE_SIZE + PAGE_STORE_SIZE + + DIRPOS_STORE_SIZE); + store_checksum_in_rec(share, checksum_delta, + row->checksum - old_record_checksum, + log_data + LSN_STORE_SIZE + FILEID_STORE_SIZE + + PAGE_STORE_SIZE + DIRPOS_STORE_SIZE, + log_array[TRANSLOG_INTERNAL_PARTS + 0].length); + compile_time_assert(sizeof(ha_checksum) == HA_CHECKSUM_STORE_SIZE); + + if (!old_record) + { + /* Store undo_lsn in case we are aborting the insert */ + row->orig_undo_lsn= info->trn->undo_lsn; + /* Write UNDO log record for the INSERT */ + if (translog_write_record(&lsn, LOGREC_UNDO_ROW_INSERT, + info->trn, info, + (translog_size_t) + log_array[TRANSLOG_INTERNAL_PARTS + + 0].length, + TRANSLOG_INTERNAL_PARTS + 1, + log_array, + log_data + LSN_STORE_SIZE, &checksum_delta)) + goto disk_err; + } + else + { + /* Write UNDO log record for the UPDATE */ + uchar *log_pos= (log_data + + log_array[TRANSLOG_INTERNAL_PARTS + 0].length); + size_t row_length, extents_length; + uint row_parts_count; + + /* + Write head length and extents of the original row so that we + during UNDO can put it back in the original position + */ + int2store(log_pos, info->cur_row.head_length); + pagerange_store(log_pos + 2, info->cur_row.extents_count); + log_pos+= 2 + PAGERANGE_STORE_SIZE; + log_array[TRANSLOG_INTERNAL_PARTS + 0].length+= (2 + + PAGERANGE_STORE_SIZE); + info->log_row_parts[TRANSLOG_INTERNAL_PARTS+1].str= + info->cur_row.extents; + info->log_row_parts[TRANSLOG_INTERNAL_PARTS+1].length= + extents_length= info->cur_row.extents_count * ROW_EXTENT_SIZE; + + row_length= fill_update_undo_parts(info, old_record, record, + log_array + + TRANSLOG_INTERNAL_PARTS + 2, + &row_parts_count); + if (translog_write_record(&lsn, LOGREC_UNDO_ROW_UPDATE, info->trn, + info, + (translog_size_t) + (log_array[TRANSLOG_INTERNAL_PARTS + + 0].length + extents_length + + row_length), + TRANSLOG_INTERNAL_PARTS + 2 + + row_parts_count, + log_array, + log_data + LSN_STORE_SIZE, + &checksum_delta)) + goto disk_err; + } + } + } + /* Release not used space in used pages */ + if (_ma_bitmap_release_unused(info, bitmap_blocks)) + goto disk_err; + _ma_unpin_all_pages(info, lsn); + + if (tmp_data_used) + { + /* + Write data stored in info->rec_buff to pages + This is the char/varchar data that didn't fit into the head page. + */ + DBUG_ASSERT(bitmap_blocks->count != 0); + if (write_full_pages(info, lsn, head_block + 1, + info->rec_buff, (ulong) (tmp_data - info->rec_buff))) + goto disk_err; + } + + /* Write rest of blobs (data, but no tails as they are already written) */ + for (; column < end_column; column++, blob_lengths++) + { + uchar *blob_pos; + uint length; + ulong blob_length; + if (!*blob_lengths) /* Null or "" */ + continue; + length= column->length - portable_sizeof_char_ptr; + memcpy_fixed((uchar*) &blob_pos, record + column->offset + length, + sizeof(char*)); + /* remove tail part */ + blob_length= *blob_lengths; + if (block[block->sub_blocks - 1].used & BLOCKUSED_TAIL) + blob_length-= (blob_length % FULL_PAGE_SIZE(block_size)); + + if (blob_length && write_full_pages(info, lsn, block, + blob_pos, blob_length)) + goto disk_err; + block+= block->sub_blocks; + } + + _ma_finalize_row(info); + DBUG_RETURN(0); + +crashed: + /* Something was wrong with data on page */ + my_errno= HA_ERR_WRONG_IN_RECORD; + +disk_err: + /** + @todo RECOVERY we are going to let dirty pages go to disk while we have + logged UNDO, this violates WAL. We must mark the table corrupted! + + @todo RECOVERY we have written some REDOs without a closing UNDO, + it's possible that a next operation by this transaction succeeds and then + Recovery would glue the "orphan REDOs" to the succeeded operation and + execute the failed REDOs. We need some mark "abort this group" in the + log, or mark the table corrupted (then user will repair it and thus REDOs + will be skipped). + + @todo RECOVERY to not let write errors go unnoticed, pagecache_write() + should take a MARIA_HA* in argument, and it it + fails when flushing a page to disk it should call + (*the_maria_ha->write_error_func)(the_maria_ha) + and this hook will mark the table corrupted. + Maybe hook should be stored in the pagecache's block structure, or in a + hash "file->maria_ha*". + + @todo RECOVERY we should distinguish below between log write error and + table write error. The former should stop Maria immediately, the latter + should mark the table corrupted. + */ + /* + Unpin all pinned pages to not cause problems for disk cache. This is + safe to call even if we already called _ma_unpin_all_pages() above. + */ + save_my_errno= my_errno; + _ma_unpin_all_pages_and_finalize_row(info, LSN_IMPOSSIBLE); + my_errno= save_my_errno; + DBUG_RETURN(1); +} + + +/* + @brief Write a record + + @fn allocate_and_write_block_record() + @param info Maria handler + @param record Record to write + @param row Information about fields in 'record' + @param undo_lsn <> LSN_ERROR if we are executing an UNDO + + @return + @retval 0 ok + @retval 1 Error +*/ + +static my_bool allocate_and_write_block_record(MARIA_HA *info, + const uchar *record, + MARIA_ROW *row, + LSN undo_lsn) +{ + struct st_row_pos_info row_pos; + MARIA_BITMAP_BLOCKS *blocks= &row->insert_blocks; + int save_my_errno; + DBUG_ENTER("allocate_and_write_block_record"); + + _ma_bitmap_flushable(info, 1); + if (_ma_bitmap_find_place(info, row, blocks)) + goto err; /* Error reading bitmap */ + + /* + Sleep; a checkpoint will happen and should not send this over-allocated + bitmap to disk but rather wait. + */ + DBUG_EXECUTE_IF("maria_over_alloc_bitmap", sleep(10);); + + /* page will be pinned & locked by get_head_or_tail_page */ + if (get_head_or_tail_page(info, blocks->block, info->buff, + row->space_on_head_page, HEAD_PAGE, + PAGECACHE_LOCK_WRITE, &row_pos)) + goto err; + row->lastpos= ma_recordpos(blocks->block->page, row_pos.rownr); + if (info->s->calc_checksum) + { + if (undo_lsn == LSN_ERROR) + row->checksum= (info->s->calc_checksum)(info, record); + else + { + /* _ma_apply_undo_row_delete() already set row's checksum. Verify it. */ + DBUG_ASSERT(row->checksum == (info->s->calc_checksum)(info, record)); + } + } + if (write_block_record(info, (uchar*) 0, record, row, + blocks, blocks->block->org_bitmap_value != 0, + &row_pos, undo_lsn, 0)) + goto err; /* Error reading bitmap */ + DBUG_PRINT("exit", ("rowid: %lu (%lu:%u)", (ulong) row->lastpos, + (ulong) ma_recordpos_to_page(row->lastpos), + ma_recordpos_to_dir_entry(row->lastpos))); + /* Now let checkpoint happen but don't commit */ + DBUG_EXECUTE_IF("maria_over_alloc_bitmap", sleep(1000);); + DBUG_RETURN(0); + +err: + save_my_errno= my_errno; + if (info->non_flushable_state) + _ma_bitmap_flushable(info, -1); + _ma_unpin_all_pages_and_finalize_row(info, LSN_IMPOSSIBLE); + my_errno= save_my_errno; + DBUG_RETURN(1); +} + + +/* + Write a record and return rowid for it + + SYNOPSIS + _ma_write_init_block_record() + info Maria handler + record Record to write + + NOTES + This is done BEFORE we write the keys to the row! + + RETURN + HA_OFFSET_ERROR Something went wrong + # Rowid for row +*/ + +MARIA_RECORD_POS _ma_write_init_block_record(MARIA_HA *info, + const uchar *record) +{ + DBUG_ENTER("_ma_write_init_block_record"); + + calc_record_size(info, record, &info->cur_row); + if (allocate_and_write_block_record(info, record, + &info->cur_row, LSN_ERROR)) + DBUG_RETURN(HA_OFFSET_ERROR); + DBUG_RETURN(info->cur_row.lastpos); +} + + +/* + Dummy function for (*info->s->write_record)() + + Nothing to do here, as we already wrote the record in + _ma_write_init_block_record() +*/ + +my_bool _ma_write_block_record(MARIA_HA *info __attribute__ ((unused)), + const uchar *record __attribute__ ((unused))) +{ + return 0; /* Row already written */ +} + + +/** + @brief Remove row written by _ma_write_block_record() and log undo + + @param info Maria handler + + @note + This is called in case we got a duplicate unique key while + writing keys. + + @return Operation status + @retval 0 OK + @retval 1 Error +*/ + +my_bool _ma_write_abort_block_record(MARIA_HA *info) +{ + my_bool res= 0; + MARIA_BITMAP_BLOCKS *blocks= &info->cur_row.insert_blocks; + MARIA_BITMAP_BLOCK *block, *end; + LSN lsn= LSN_IMPOSSIBLE; + MARIA_SHARE *share= info->s; + DBUG_ENTER("_ma_write_abort_block_record"); + + _ma_bitmap_flushable(info, 1); + if (delete_head_or_tail(info, + ma_recordpos_to_page(info->cur_row.lastpos), + ma_recordpos_to_dir_entry(info->cur_row.lastpos), 1, + 0)) + res= 1; + for (block= blocks->block + 1, end= block + blocks->count - 1; block < end; + block++) + { + if (block->used & BLOCKUSED_USED) + { + if (block->used & BLOCKUSED_TAIL) + { + /* + block->page_count is set to the tail directory entry number in + write_block_record() + */ + if (delete_head_or_tail(info, block->page, + block->page_count & ~TAIL_BIT, + 0, 0)) + res= 1; + } + else + { + if (free_full_page_range(info, block->page, block->page_count)) + res= 1; + } + } + } + if (share->now_transactional) + { + if (_ma_write_clr(info, info->cur_row.orig_undo_lsn, + LOGREC_UNDO_ROW_INSERT, + share->calc_checksum != 0, + (ha_checksum) 0 - info->cur_row.checksum, + &lsn, (void*) 0)) + res= 1; + } + _ma_bitmap_flushable(info, -1); + _ma_unpin_all_pages_and_finalize_row(info, lsn); + DBUG_RETURN(res); +} + + +/* + Update a record + + NOTES + For the moment, we assume that info->curr_row.extents is always updated + when a row is read. In the future we may decide to read this on demand + for rows split into many extents. +*/ + +static my_bool _ma_update_block_record2(MARIA_HA *info, + MARIA_RECORD_POS record_pos, + const uchar *oldrec, + const uchar *record, + LSN undo_lsn) +{ + MARIA_BITMAP_BLOCKS *blocks= &info->cur_row.insert_blocks; + uchar *buff; + MARIA_ROW *cur_row= &info->cur_row, *new_row= &info->new_row; + MARIA_PINNED_PAGE page_link; + uint rownr, org_empty_size, head_length; + uint block_size= info->s->block_size; + uchar *dir; + pgcache_page_no_t page; + struct st_row_pos_info row_pos; + my_bool res; + ha_checksum old_checksum; + MARIA_SHARE *share= info->s; + DBUG_ENTER("_ma_update_block_record2"); + DBUG_PRINT("enter", ("rowid: %lu", (long) record_pos)); + +#ifdef ENABLE_IF_PROBLEM_WITH_UPDATE + DBUG_DUMP("oldrec", oldrec, share->base.reclength); + DBUG_DUMP("newrec", record, share->base.reclength); +#endif + + /* + Checksums of new and old rows were computed by callers already; new + row's was put into cur_row, old row's was put into new_row. + */ + old_checksum= new_row->checksum; + new_row->checksum= cur_row->checksum; + calc_record_size(info, record, new_row); + page= ma_recordpos_to_page(record_pos); + + _ma_bitmap_flushable(info, 1); + buff= pagecache_read(share->pagecache, + &info->dfile, (pgcache_page_no_t) page, 0, 0, + share->page_type, + PAGECACHE_LOCK_WRITE, &page_link.link); + page_link.unlock= PAGECACHE_LOCK_WRITE_UNLOCK; + page_link.changed= buff != 0; + push_dynamic(&info->pinned_pages, (void*) &page_link); + if (!buff) + goto err; + + org_empty_size= uint2korr(buff + EMPTY_SPACE_OFFSET); + rownr= ma_recordpos_to_dir_entry(record_pos); + dir= dir_entry_pos(buff, block_size, rownr); + + if ((org_empty_size + cur_row->head_length) >= new_row->total_length) + { + uint rec_offset, length; + MARIA_BITMAP_BLOCK block; + + /* + We can fit the new row in the same page as the original head part + of the row + */ + block.org_bitmap_value= _ma_free_size_to_head_pattern(&share->bitmap, + org_empty_size); + if (extend_area_on_page(info, buff, dir, rownr, block_size, + new_row->total_length, &org_empty_size, + &rec_offset, &length)) + goto err; + + row_pos.buff= buff; + row_pos.rownr= rownr; + row_pos.empty_space= org_empty_size; + row_pos.dir= dir; + row_pos.data= buff + rec_offset; + row_pos.length= length; + blocks->block= █ + blocks->count= 1; + block.page= page; + block.sub_blocks= 1; + block.used= BLOCKUSED_USED | BLOCKUSED_USE_ORG_BITMAP; + block.empty_space= row_pos.empty_space; + + if (*cur_row->tail_positions && + delete_tails(info, cur_row->tail_positions)) + goto err; + if (cur_row->extents_count && free_full_pages(info, cur_row)) + goto err; + res= write_block_record(info, oldrec, record, new_row, blocks, + 1, &row_pos, undo_lsn, old_checksum); + /* We can't update or delete this without re-reading it again */ + info->update&= ~HA_STATE_AKTIV; + DBUG_RETURN(res); + } + /* Delete old row */ + if (*cur_row->tail_positions && + delete_tails(info, cur_row->tail_positions)) + goto err; + if (cur_row->extents_count && free_full_pages(info, cur_row)) + goto err; + + head_length= uint2korr(dir + 2); + if (_ma_bitmap_find_new_place(info, new_row, page, head_length + + org_empty_size, blocks)) + goto err; + + /* + Allocate all size in block for record + TODO: + Need to improve this to do compact if we can fit one more blob into + the head page + */ + if ((head_length < new_row->space_on_head_page || + (new_row->total_length <= head_length && + org_empty_size + head_length >= new_row->total_length))) + { + _ma_compact_block_page(buff, block_size, rownr, 1, + info->trn->min_read_from, + share->base.min_block_length); + org_empty_size= 0; + head_length= uint2korr(dir + 2); + } + + row_pos.buff= buff; + row_pos.rownr= rownr; + row_pos.empty_space= org_empty_size + head_length; + row_pos.dir= dir; + row_pos.data= buff + uint2korr(dir); + row_pos.length= head_length; + if ((res= write_block_record(info, oldrec, record, new_row, blocks, 1, + &row_pos, undo_lsn, old_checksum))) + goto err; + DBUG_RETURN(0); + +err: + if (info->non_flushable_state) + _ma_bitmap_flushable(info, -1); + _ma_unpin_all_pages_and_finalize_row(info, LSN_IMPOSSIBLE); + DBUG_RETURN(1); +} + + +/* + @brief Store new row on it's original position + + @note + This is basicly a copy of _ma_update_block_record2 + When we have a purge thread for deleted row, we can remove this function + and use _ma_update_block_record2 instead. + + This is the main reason we don't make a lot of subfunctions that are + common between _ma_update_block_record2() and this function. +*/ + +static my_bool _ma_update_at_original_place(MARIA_HA *info, + pgcache_page_no_t page, + uint rownr, + uint length_on_head_page, + uint extent_count, + const uchar *extent_info, + const uchar *oldrec, + const uchar *record, + LSN undo_lsn) +{ + MARIA_BITMAP_BLOCKS *blocks; + MARIA_BITMAP_BLOCK *block; + MARIA_ROW *cur_row= &info->cur_row, *new_row= &info->new_row; + MARIA_PINNED_PAGE page_link; + MARIA_SHARE *share= info->s; + ha_checksum old_checksum; + uint org_empty_size, empty_size; + uint block_size= info->s->block_size; + uchar *dir, *buff; + struct st_row_pos_info row_pos; + my_bool res; + uint rec_offset, length; + DBUG_ENTER("_ma_update_at_original_place"); + +#ifdef ENABLE_IF_PROBLEM_WITH_UPDATE + DBUG_DUMP("oldrec", oldrec, share->base.reclength); + DBUG_DUMP("newrec", record, share->base.reclength); +#endif + + /* + Checksums of new and old rows were computed by callers already; new + row's was put into cur_row, old row's was put into new_row. + */ + old_checksum= new_row->checksum; + new_row->checksum= cur_row->checksum; + calc_record_size(info, record, new_row); + + _ma_bitmap_flushable(info, 1); + buff= pagecache_read(share->pagecache, + &info->dfile, (pgcache_page_no_t) page, 0, 0, + share->page_type, + PAGECACHE_LOCK_WRITE, &page_link.link); + page_link.unlock= PAGECACHE_LOCK_WRITE_UNLOCK; + page_link.changed= buff != 0; + push_dynamic(&info->pinned_pages, (void*) &page_link); + if (!buff) + goto err; + + org_empty_size= uint2korr(buff + EMPTY_SPACE_OFFSET); + dir= dir_entry_pos(buff, block_size, rownr); + + if ((org_empty_size + cur_row->head_length) < length_on_head_page) + { + my_errno= HA_ERR_WRONG_IN_RECORD; + goto err; + } + + /* + We can fit the new row in the same page as the original head part + of the row + */ + empty_size= org_empty_size; + if (extend_area_on_page(info, buff, dir, rownr, block_size, + length_on_head_page, &empty_size, + &rec_offset, &length)) + goto err; + + row_pos.buff= buff; + row_pos.rownr= rownr; + row_pos.empty_space= empty_size; + row_pos.dir= dir; + row_pos.data= buff + rec_offset; + row_pos.length= length_on_head_page; + + /* Delete old row */ + if (*cur_row->tail_positions && + delete_tails(info, cur_row->tail_positions)) + goto err; + if (cur_row->extents_count && free_full_pages(info, cur_row)) + goto err; + + /* Change extent information to be usable by write_block_record() */ + blocks= &cur_row->insert_blocks; + if (extent_to_bitmap_blocks(info, blocks, page, extent_count, extent_info)) + goto err; + block= blocks->block; + block->empty_space= row_pos.empty_space; + block->org_bitmap_value= _ma_free_size_to_head_pattern(&share->bitmap, + org_empty_size); + DBUG_ASSERT(block->org_bitmap_value == + _ma_bitmap_get_page_bits(info, &info->s->bitmap, page)); + block->used|= BLOCKUSED_USE_ORG_BITMAP; + + /* + We have to use <= below as the new_row may be smaller than the original + row as the new row doesn't have transaction id + */ + + DBUG_ASSERT(blocks->count > 1 || + max(new_row->total_length, share->base.min_block_length) <= + length_on_head_page); + + if ((res= write_block_record(info, oldrec, record, new_row, blocks, + 1, &row_pos, undo_lsn, old_checksum))) + goto err; + DBUG_RETURN(0); + +err: + if (info->non_flushable_state) + _ma_bitmap_flushable(info, -1); + _ma_unpin_all_pages_and_finalize_row(info, LSN_IMPOSSIBLE); + DBUG_RETURN(1); +} + + +/* Wrapper for _ma_update_block_record2() used by ma_update() */ + +my_bool _ma_update_block_record(MARIA_HA *info, MARIA_RECORD_POS record_pos, + const uchar *orig_rec, const uchar *new_rec) +{ + return _ma_update_block_record2(info, record_pos, orig_rec, new_rec, + LSN_ERROR); +} + + +/* + Delete a directory entry + + SYNOPSIS + delete_dir_entry() + buff Page buffer + block_size Block size + record_number Record number to delete + empty_space Empty space on page after delete + + RETURN + -1 Error on page + 0 ok + 1 Page is now empty +*/ + +static int delete_dir_entry(uchar *buff, uint block_size, uint record_number, + uint *empty_space_res) +{ + uint number_of_records= (uint) buff[DIR_COUNT_OFFSET]; + uint length, empty_space; + uchar *dir; + DBUG_ENTER("delete_dir_entry"); + +#ifdef SANITY_CHECKS + if (record_number >= number_of_records || + record_number > ((block_size - LSN_SIZE - PAGE_TYPE_SIZE - 1 - + PAGE_SUFFIX_SIZE) / DIR_ENTRY_SIZE)) + { + DBUG_PRINT("error", ("record_number: %u number_of_records: %u", + record_number, number_of_records)); + + DBUG_RETURN(-1); + } +#endif + + check_directory(buff, block_size, 0); + empty_space= uint2korr(buff + EMPTY_SPACE_OFFSET); + dir= dir_entry_pos(buff, block_size, record_number); + length= uint2korr(dir + 2); + + if (record_number == number_of_records - 1) + { + /* Delete this entry and all following free directory entries */ + uchar *end= buff + block_size - PAGE_SUFFIX_SIZE; + number_of_records--; + dir+= DIR_ENTRY_SIZE; + empty_space+= DIR_ENTRY_SIZE; + + /* Unlink and free the next empty ones */ + while (dir < end && dir[0] == 0 && dir[1] == 0) + { + number_of_records--; + if (dir[2] == END_OF_DIR_FREE_LIST) + buff[DIR_FREE_OFFSET]= dir[3]; + else + { + uchar *prev_entry= dir_entry_pos(buff, block_size, (uint) dir[2]); + DBUG_ASSERT(uint2korr(prev_entry) == 0 && prev_entry[3] == + number_of_records); + prev_entry[3]= dir[3]; + } + if (dir[3] != END_OF_DIR_FREE_LIST) + { + uchar *next_entry= dir_entry_pos(buff, block_size, (uint) dir[3]); + DBUG_ASSERT(uint2korr(next_entry) == 0 && next_entry[2] == + number_of_records); + next_entry[2]= dir[2]; + } + dir+= DIR_ENTRY_SIZE; + empty_space+= DIR_ENTRY_SIZE; + } + + if (number_of_records == 0) + { + /* All entries on page deleted */ + DBUG_PRINT("info", ("Page marked as unallocated")); + buff[PAGE_TYPE_OFFSET]= UNALLOCATED_PAGE; +#ifdef IDENTICAL_PAGES_AFTER_RECOVERY + { + dir= dir_entry_pos(buff, block_size, record_number); + bzero(dir, (record_number+1) * DIR_ENTRY_SIZE); + } +#endif + *empty_space_res= block_size; + DBUG_RETURN(1); + } + buff[DIR_COUNT_OFFSET]= (uchar) number_of_records; + } + else + { + /* Update directory */ + dir[0]= dir[1]= 0; + dir[2]= END_OF_DIR_FREE_LIST; + if ((dir[3]= buff[DIR_FREE_OFFSET]) != END_OF_DIR_FREE_LIST) + { + /* Relink next entry to point to newly freed entry */ + uchar *next_entry= dir_entry_pos(buff, block_size, (uint) dir[3]); + DBUG_ASSERT(uint2korr(next_entry) == 0 && + next_entry[2] == END_OF_DIR_FREE_LIST); + next_entry[2]= record_number; + } + buff[DIR_FREE_OFFSET]= record_number; + } + empty_space+= length; + + int2store(buff + EMPTY_SPACE_OFFSET, empty_space); + buff[PAGE_TYPE_OFFSET]|= (uchar) PAGE_CAN_BE_COMPACTED; + + *empty_space_res= empty_space; + + check_directory(buff, block_size, 0); + DBUG_RETURN(0); +} + + +/* + Delete a head a tail part + + SYNOPSIS + delete_head_or_tail() + info Maria handler + page Page (not file offset!) on which the row is + head 1 if this is a head page + from_update 1 if we are called from update. In this case we + leave the page as write locked as we may put + the new row into the old position. + + RETURN + 0 ok + 1 error +*/ + +static my_bool delete_head_or_tail(MARIA_HA *info, + pgcache_page_no_t page, uint record_number, + my_bool head, my_bool from_update) +{ + MARIA_SHARE *share= info->s; + uint empty_space; + uint block_size= share->block_size; + uchar *buff; + LSN lsn; + MARIA_PINNED_PAGE page_link; + int res; + enum pagecache_page_lock lock_at_write, lock_at_unpin; + DBUG_ENTER("delete_head_or_tail"); + DBUG_PRINT("enter", ("id: %lu (%lu:%u)", + (ulong) ma_recordpos(page, record_number), + (ulong) page, record_number)); + + buff= pagecache_read(share->pagecache, + &info->dfile, page, 0, 0, + share->page_type, + PAGECACHE_LOCK_WRITE, &page_link.link); + page_link.unlock= PAGECACHE_LOCK_WRITE_UNLOCK; + page_link.changed= buff != 0; + push_dynamic(&info->pinned_pages, (void*) &page_link); + if (!buff) + DBUG_RETURN(1); + DBUG_ASSERT((buff[PAGE_TYPE_OFFSET] & PAGE_TYPE_MASK) == + (head ? HEAD_PAGE : TAIL_PAGE)); + + if (from_update) + { + lock_at_write= PAGECACHE_LOCK_LEFT_WRITELOCKED; + lock_at_unpin= PAGECACHE_LOCK_WRITE_UNLOCK; + } + else + { + lock_at_write= PAGECACHE_LOCK_WRITE_TO_READ; + lock_at_unpin= PAGECACHE_LOCK_READ_UNLOCK; + } + + res= delete_dir_entry(buff, block_size, record_number, &empty_space); + if (res < 0) + DBUG_RETURN(1); + if (res == 0) /* after our deletion, page is still not empty */ + { + uchar log_data[FILEID_STORE_SIZE + PAGE_STORE_SIZE + DIRPOS_STORE_SIZE]; + LEX_CUSTRING log_array[TRANSLOG_INTERNAL_PARTS + 1]; + if (share->now_transactional) + { + /* Log REDO data */ + page_store(log_data + FILEID_STORE_SIZE, page); + dirpos_store(log_data + FILEID_STORE_SIZE + PAGE_STORE_SIZE, + record_number); + + log_array[TRANSLOG_INTERNAL_PARTS + 0].str= log_data; + log_array[TRANSLOG_INTERNAL_PARTS + 0].length= sizeof(log_data); + if (translog_write_record(&lsn, (head ? LOGREC_REDO_PURGE_ROW_HEAD : + LOGREC_REDO_PURGE_ROW_TAIL), + info->trn, info, + (translog_size_t) sizeof(log_data), + TRANSLOG_INTERNAL_PARTS + 1, log_array, + log_data, NULL)) + DBUG_RETURN(1); + } + } + else /* page is now empty */ + { + if (share->now_transactional) + { + uchar log_data[FILEID_STORE_SIZE + PAGE_STORE_SIZE]; + LEX_CUSTRING log_array[TRANSLOG_INTERNAL_PARTS + 1]; + page_store(log_data + FILEID_STORE_SIZE, page); + log_array[TRANSLOG_INTERNAL_PARTS + 0].str= log_data; + log_array[TRANSLOG_INTERNAL_PARTS + 0].length= sizeof(log_data); + if (translog_write_record(&lsn, LOGREC_REDO_FREE_HEAD_OR_TAIL, + info->trn, info, + (translog_size_t) sizeof(log_data), + TRANSLOG_INTERNAL_PARTS + 1, log_array, + log_data, NULL)) + DBUG_RETURN(1); + } + DBUG_ASSERT(empty_space >= share->bitmap.sizes[0]); + } + + pagecache_unlock_by_link(share->pagecache, page_link.link, + lock_at_write, + PAGECACHE_PIN_LEFT_PINNED, LSN_IMPOSSIBLE, + LSN_IMPOSSIBLE, 1, FALSE); + page_link.unlock= lock_at_unpin; + set_dynamic(&info->pinned_pages, (void*) &page_link, + info->pinned_pages.elements-1); + + DBUG_PRINT("info", ("empty_space: %u", empty_space)); + + /* + If there is not enough space for all possible tails, mark the + page full + */ + if (!head && !enough_free_entries(buff, share->block_size, + 1 + share->base.blobs)) + empty_space= 0; + + DBUG_RETURN(_ma_bitmap_set(info, page, head, empty_space)); +} + + +/* + delete all tails + + SYNOPSIS + delete_tails() + info Handler + tails Pointer to vector of tail positions, ending with 0 + + RETURN + 0 ok + 1 error +*/ + +static my_bool delete_tails(MARIA_HA *info, MARIA_RECORD_POS *tails) +{ + my_bool res= 0; + DBUG_ENTER("delete_tails"); + for (; *tails; tails++) + { + if (delete_head_or_tail(info, + ma_recordpos_to_page(*tails), + ma_recordpos_to_dir_entry(*tails), 0, 1)) + res= 1; + } + DBUG_RETURN(res); +} + + +/* + Delete a record + + NOTES + For the moment, we assume that info->cur_row.extents is always updated + when a row is read. In the future we may decide to read this on demand + for rows with many splits. +*/ + +my_bool _ma_delete_block_record(MARIA_HA *info, const uchar *record) +{ + pgcache_page_no_t page; + uint record_number; + MARIA_SHARE *share= info->s; + LSN lsn= LSN_IMPOSSIBLE; + DBUG_ENTER("_ma_delete_block_record"); + + page= ma_recordpos_to_page(info->cur_row.lastpos); + record_number= ma_recordpos_to_dir_entry(info->cur_row.lastpos); + DBUG_PRINT("enter", ("rowid: %lu (%lu:%u)", (ulong) info->cur_row.lastpos, + (ulong) page, record_number)); + + _ma_bitmap_flushable(info, 1); + if (delete_head_or_tail(info, page, record_number, 1, 0) || + delete_tails(info, info->cur_row.tail_positions)) + goto err; + + if (info->cur_row.extents_count && free_full_pages(info, &info->cur_row)) + goto err; + + if (share->now_transactional) + { + uchar log_data[LSN_STORE_SIZE + FILEID_STORE_SIZE + PAGE_STORE_SIZE + + DIRPOS_STORE_SIZE + 2 + PAGERANGE_STORE_SIZE + + HA_CHECKSUM_STORE_SIZE]; + uchar *log_pos; + size_t row_length; + uint row_parts_count, extents_length; + ha_checksum checksum_delta; + + /* Write UNDO record */ + lsn_store(log_data, info->trn->undo_lsn); + page_store(log_data + LSN_STORE_SIZE + FILEID_STORE_SIZE, page); + log_pos= log_data + LSN_STORE_SIZE + FILEID_STORE_SIZE + PAGE_STORE_SIZE; + dirpos_store(log_pos, record_number); + log_pos+= DIRPOS_STORE_SIZE; + int2store(log_pos, info->cur_row.head_length); + log_pos+= 2; + pagerange_store(log_pos, info->cur_row.extents_count); + log_pos+= PAGERANGE_STORE_SIZE; + + info->log_row_parts[TRANSLOG_INTERNAL_PARTS].str= log_data; + info->log_row_parts[TRANSLOG_INTERNAL_PARTS].length= + sizeof(log_data) - HA_CHECKSUM_STORE_SIZE; + store_checksum_in_rec(share, checksum_delta, + (ha_checksum) 0 - info->cur_row.checksum, log_pos, + info->log_row_parts[TRANSLOG_INTERNAL_PARTS + + 0].length); + info->log_row_parts[TRANSLOG_INTERNAL_PARTS+1].str= + info->cur_row.extents; + info->log_row_parts[TRANSLOG_INTERNAL_PARTS+1].length= + extents_length= info->cur_row.extents_count * ROW_EXTENT_SIZE; + + row_length= fill_insert_undo_parts(info, record, + (info->log_row_parts + + TRANSLOG_INTERNAL_PARTS + 2), + &row_parts_count); + + if (translog_write_record(&lsn, LOGREC_UNDO_ROW_DELETE, info->trn, + info, + (translog_size_t) + (info->log_row_parts[TRANSLOG_INTERNAL_PARTS + + 0].length + row_length + + extents_length), + TRANSLOG_INTERNAL_PARTS + 2 + row_parts_count, + info->log_row_parts, + log_data + LSN_STORE_SIZE, + &checksum_delta)) + goto err; + } + + _ma_bitmap_flushable(info, -1); + _ma_unpin_all_pages_and_finalize_row(info, lsn); + DBUG_RETURN(0); + +err: + _ma_bitmap_flushable(info, -1); + _ma_unpin_all_pages_and_finalize_row(info, LSN_IMPOSSIBLE); + DBUG_RETURN(1); +} + + +/**************************************************************************** + Reading of records +****************************************************************************/ + +/* + Read position to record from record directory at end of page + + SYNOPSIS + get_record_position() + buff page buffer + block_size block size for page + record_number Record number in index + end_of_data pointer to end of data for record + + RETURN + 0 Error in data + # Pointer to start of record. + In this case *end_of_data is set. +*/ + +static uchar *get_record_position(uchar *buff, uint block_size, + uint record_number, uchar **end_of_data) +{ + uint number_of_records= (uint) buff[DIR_COUNT_OFFSET]; + uchar *dir; + uchar *data; + uint offset, length; + +#ifdef SANITY_CHECKS + if (record_number >= number_of_records || + record_number > ((block_size - PAGE_HEADER_SIZE - PAGE_SUFFIX_SIZE) / + DIR_ENTRY_SIZE)) + { + DBUG_PRINT("error", + ("Wrong row number: record_number: %u number_of_records: %u", + record_number, number_of_records)); + return 0; + } +#endif + + dir= dir_entry_pos(buff, block_size, record_number); + offset= uint2korr(dir); + length= uint2korr(dir + 2); +#ifdef SANITY_CHECKS + if (offset < PAGE_HEADER_SIZE || + offset + length > (block_size - + number_of_records * DIR_ENTRY_SIZE - + PAGE_SUFFIX_SIZE)) + { + DBUG_PRINT("error", + ("Wrong row position: record_number: %u offset: %u " + "length: %u number_of_records: %u", + record_number, offset, length, number_of_records)); + return 0; + } +#endif + data= buff + offset; + *end_of_data= data + length; + return data; +} + + +/* + Init extent + + NOTES + extent is a cursor over which pages to read +*/ + +static void init_extent(MARIA_EXTENT_CURSOR *extent, uchar *extent_info, + uint extents, MARIA_RECORD_POS *tail_positions) +{ + uint page_count; + extent->extent= extent_info; + extent->extent_count= extents; + extent->page= page_korr(extent_info); /* First extent */ + page_count= (uint2korr(extent_info + ROW_EXTENT_PAGE_SIZE) & + ~START_EXTENT_BIT); + extent->tail= page_count & TAIL_BIT; + if (extent->tail) + { + extent->page_count= 1; + extent->tail_row_nr= page_count & ~TAIL_BIT; + } + else + extent->page_count= page_count; + extent->tail_positions= tail_positions; + extent->lock_for_tail_pages= PAGECACHE_LOCK_LEFT_UNLOCKED; +} + + +/* + Read next extent + + SYNOPSIS + read_next_extent() + info Maria handler + extent Pointer to current extent (this is updated to point + to next) + end_of_data Pointer to end of data in read block (out) + + NOTES + New block is read into info->buff + + RETURN + 0 Error; my_errno is set + # Pointer to start of data in read block + In this case end_of_data is updated to point to end of data. +*/ + +static uchar *read_next_extent(MARIA_HA *info, MARIA_EXTENT_CURSOR *extent, + uchar **end_of_data) +{ + MARIA_SHARE *share= info->s; + uchar *buff, *data; + MARIA_PINNED_PAGE page_link; + enum pagecache_page_lock lock; + DBUG_ENTER("read_next_extent"); + + if (!extent->page_count) + { + uint page_count; + if (!--extent->extent_count) + goto crashed; + extent->extent+= ROW_EXTENT_SIZE; + extent->page= page_korr(extent->extent); + page_count= (uint2korr(extent->extent+ROW_EXTENT_PAGE_SIZE) & + ~START_EXTENT_BIT); + if (!page_count) + goto crashed; + extent->tail= page_count & TAIL_BIT; + if (extent->tail) + extent->tail_row_nr= page_count & ~TAIL_BIT; + else + extent->page_count= page_count; + DBUG_PRINT("info",("New extent. Page: %lu page_count: %u tail_flag: %d", + (ulong) extent->page, extent->page_count, + extent->tail != 0)); + } + extent->first_extent= 0; + + lock= PAGECACHE_LOCK_LEFT_UNLOCKED; + if (extent->tail) + lock= extent->lock_for_tail_pages; + + buff= pagecache_read(share->pagecache, + &info->dfile, extent->page, 0, + info->buff, share->page_type, + lock, &page_link.link); + if (lock != PAGECACHE_LOCK_LEFT_UNLOCKED) + { + /* Read during UNDO */ + page_link.unlock= PAGECACHE_LOCK_WRITE_UNLOCK; + page_link.changed= buff != 0; + push_dynamic(&info->pinned_pages, (void*) &page_link); + } + if (!buff) + { + /* check if we tried to read over end of file (ie: bad data in record) */ + if ((extent->page + 1) * share->block_size > + share->state.state.data_file_length) + goto crashed; + DBUG_RETURN(0); + } + + if (!extent->tail) + { + /* Full data page */ + if ((buff[PAGE_TYPE_OFFSET] & PAGE_TYPE_MASK) != BLOB_PAGE) + goto crashed; + extent->page++; /* point to next page */ + extent->page_count--; + *end_of_data= buff + share->block_size - PAGE_SUFFIX_SIZE; + info->cur_row.full_page_count++; /* For maria_chk */ + DBUG_RETURN(extent->data_start= buff + LSN_SIZE + PAGE_TYPE_SIZE); + } + + /* Found tail */ + if ((buff[PAGE_TYPE_OFFSET] & PAGE_TYPE_MASK) != TAIL_PAGE) + goto crashed; + *(extent->tail_positions++)= ma_recordpos(extent->page, + extent->tail_row_nr); + info->cur_row.tail_count++; /* For maria_chk */ + + if (!(data= get_record_position(buff, share->block_size, + extent->tail_row_nr, + end_of_data))) + goto crashed; + extent->data_start= data; + extent->page_count= 0; /* No more data in extent */ + DBUG_RETURN(data); + + +crashed: + my_errno= HA_ERR_WRONG_IN_RECORD; /* File crashed */ + DBUG_PRINT("error", ("wrong extent information")); + DBUG_RETURN(0); +} + + +/* + Read data that may be split over many blocks + + SYNOPSIS + read_long_data() + info Maria handler + to Store result string here (this is allocated) + extent Pointer to current extent position + data Current position in buffer + end_of_data End of data in buffer + + NOTES + When we have to read a new buffer, it's read into info->buff + + This loop is implemented by goto's instead of a for() loop as + the code is notable smaller and faster this way (and it's not nice + to jump into a for loop() or into a 'then' clause) + + RETURN + 0 ok + 1 error +*/ + +static my_bool read_long_data(MARIA_HA *info, uchar *to, ulong length, + MARIA_EXTENT_CURSOR *extent, + uchar **data, uchar **end_of_data) +{ + DBUG_ENTER("read_long_data"); + DBUG_PRINT("enter", ("length: %lu left_length: %u", + length, (uint) (*end_of_data - *data))); + DBUG_ASSERT(*data <= *end_of_data); + + /* + Fields are never split in middle. This means that if length > rest-of-data + we should start reading from the next extent. The reason we may have + data left on the page is that if the fixed part of the row was less than + min_block_length the head block was extended to min_block_length. + + This may change in the future, which is why we have the loop written + the way it's written. + */ + if (extent->first_extent && length > (ulong) (*end_of_data - *data)) + *end_of_data= *data; + + for(;;) + { + uint left_length; + left_length= (uint) (*end_of_data - *data); + if (likely(left_length >= length)) + { + memcpy(to, *data, length); + (*data)+= length; + DBUG_PRINT("info", ("left_length: %u", left_length - (uint) length)); + DBUG_RETURN(0); + } + memcpy(to, *data, left_length); + to+= left_length; + length-= left_length; + if (!(*data= read_next_extent(info, extent, end_of_data))) + break; + } + DBUG_RETURN(1); +} + + +/* + Read a record from page (helper function for _ma_read_block_record()) + + SYNOPSIS + _ma_read_block_record2() + info Maria handler + record Store record here + data Start of head data for row + end_of_data End of data for row + + NOTES + The head page is already read by caller + Following data is update in info->cur_row: + + cur_row.head_length is set to size of entry in head block + cur_row.tail_positions is set to point to all tail blocks + cur_row.extents points to extents data + cur_row.extents_counts contains number of extents + cur_row.empty_bits is set to empty bits + cur_row.field_lengths contains packed length of all fields + cur_row.blob_length contains total length of all blobs + cur_row.checksum contains checksum of read record. + + RETURN + 0 ok + # Error code +*/ + +int _ma_read_block_record2(MARIA_HA *info, uchar *record, + uchar *data, uchar *end_of_data) +{ + MARIA_SHARE *share= info->s; + uchar *field_length_data, *blob_buffer, *start_of_data; + uint flag, null_bytes, cur_null_bytes, row_extents, field_lengths; + my_bool found_blob= 0; + MARIA_EXTENT_CURSOR extent; + MARIA_COLUMNDEF *column, *end_column; + MARIA_ROW *cur_row= &info->cur_row; + DBUG_ENTER("_ma_read_block_record2"); + + LINT_INIT(field_length_data); + LINT_INIT(blob_buffer); + + start_of_data= data; + flag= (uint) (uchar) data[0]; + cur_null_bytes= share->base.original_null_bytes; + null_bytes= share->base.null_bytes; + cur_row->head_length= (uint) (end_of_data - data); + cur_row->full_page_count= cur_row->tail_count= 0; + cur_row->blob_length= 0; + + if (flag & ROW_FLAG_TRANSID) + { + cur_row->trid= transid_korr(data+1); + if (!info->trn) + DBUG_RETURN(my_errno= HA_ERR_WRONG_IN_RECORD); /* File crashed */ + if (!trnman_can_read_from(info->trn, cur_row->trid)) + DBUG_RETURN(my_errno= HA_ERR_ROW_NOT_VISIBLE); + } + + /* Skip trans header (for now, until we have MVCC csupport) */ + data+= total_header_size[(flag & PRECALC_HEADER_BITMASK)]; + if (flag & ROW_FLAG_NULLS_EXTENDED) + cur_null_bytes+= data[-1]; + + row_extents= 0; + if (flag & ROW_FLAG_EXTENTS) + { + uint row_extent_size; + /* + Record is split over many data pages. + Get number of extents and first extent + */ + get_key_length(row_extents, data); + cur_row->extents_count= row_extents; + row_extent_size= row_extents * ROW_EXTENT_SIZE; + if (cur_row->extents_buffer_length < row_extent_size && + _ma_alloc_buffer(&cur_row->extents, + &cur_row->extents_buffer_length, + row_extent_size)) + DBUG_RETURN(my_errno); + memcpy(cur_row->extents, data, ROW_EXTENT_SIZE); + data+= ROW_EXTENT_SIZE; + init_extent(&extent, cur_row->extents, row_extents, + cur_row->tail_positions); + } + else + { + cur_row->extents_count= 0; + (*cur_row->tail_positions)= 0; + extent.page_count= 0; + extent.extent_count= 1; + } + extent.first_extent= 1; + + field_lengths= 0; + if (share->base.max_field_lengths) + { + get_key_length(field_lengths, data); + cur_row->field_lengths_length= field_lengths; +#ifdef SANITY_CHECKS + if (field_lengths > share->base.max_field_lengths) + goto err; +#endif + } + + if (share->calc_checksum) + cur_row->checksum= (uint) (uchar) *data++; + /* data now points on null bits */ + memcpy(record, data, cur_null_bytes); + if (unlikely(cur_null_bytes != null_bytes)) + { + /* + This only happens if we have added more NULL columns with + ALTER TABLE and are fetching an old, not yet modified old row + */ + bzero(record + cur_null_bytes, (uint) (null_bytes - cur_null_bytes)); + } + data+= null_bytes; + /* We copy the empty bits to be able to use them for delete/update */ + memcpy(cur_row->empty_bits, data, share->base.pack_bytes); + data+= share->base.pack_bytes; + + /* TODO: Use field offsets, instead of just skipping them */ + data+= share->base.field_offsets * FIELD_OFFSET_SIZE; + + /* + Read row extents (note that first extent was already read into + cur_row->extents above) + */ + if (row_extents > 1) + { + if (read_long_data(info, cur_row->extents + ROW_EXTENT_SIZE, + (row_extents - 1) * ROW_EXTENT_SIZE, + &extent, &data, &end_of_data)) + DBUG_RETURN(my_errno); + } + + /* + Data now points to start of fixed length field data that can't be null + or 'empty'. Note that these fields can't be split over blocks. + */ + for (column= share->columndef, + end_column= column + share->base.fixed_not_null_fields; + column < end_column; column++) + { + uint column_length= column->length; + if (data + column_length > end_of_data && + !(data= read_next_extent(info, &extent, &end_of_data))) + goto err; + memcpy(record + column->offset, data, column_length); + data+= column_length; + } + + /* Read array of field lengths. This may be stored in several extents */ + if (field_lengths) + { + field_length_data= cur_row->field_lengths; + if (read_long_data(info, field_length_data, field_lengths, &extent, + &data, &end_of_data)) + DBUG_RETURN(my_errno); + } + + /* Read variable length data. Each of these may be split over many extents */ + for (end_column= share->columndef + share->base.fields; + column < end_column; column++) + { + enum en_fieldtype type= column->type; + uchar *field_pos= record + column->offset; + /* First check if field is present in record */ + if ((record[column->null_pos] & column->null_bit) || + (cur_row->empty_bits[column->empty_pos] & column->empty_bit)) + { + bfill(record + column->offset, column->fill_length, + type == FIELD_SKIP_ENDSPACE ? ' ' : 0); + continue; + } + switch (type) { + case FIELD_NORMAL: /* Fixed length field */ + case FIELD_SKIP_PRESPACE: + case FIELD_SKIP_ZERO: /* Fixed length field */ + if (data + column->length > end_of_data && + !(data= read_next_extent(info, &extent, &end_of_data))) + goto err; + memcpy(field_pos, data, column->length); + data+= column->length; + break; + case FIELD_SKIP_ENDSPACE: /* CHAR */ + { + /* Char that is space filled */ + uint length; + if (column->length <= 255) + length= (uint) (uchar) *field_length_data++; + else + { + length= uint2korr(field_length_data); + field_length_data+= 2; + } +#ifdef SANITY_CHECKS + if (length > column->length) + goto err; +#endif + if (read_long_data(info, field_pos, length, &extent, &data, + &end_of_data)) + DBUG_RETURN(my_errno); + bfill(field_pos + length, column->length - length, ' '); + break; + } + case FIELD_VARCHAR: + { + ulong length; + if (column->length <= 256) + { + length= (uint) (uchar) (*field_pos++= *field_length_data++); + } + else + { + length= uint2korr(field_length_data); + field_pos[0]= field_length_data[0]; + field_pos[1]= field_length_data[1]; + field_pos+= 2; + field_length_data+= 2; + } +#ifdef SANITY_CHECKS + if (length > column->length) + goto err; +#endif + if (read_long_data(info, field_pos, length, &extent, &data, + &end_of_data)) + DBUG_RETURN(my_errno); + break; + } + case FIELD_BLOB: + { + uint column_size_length= column->length - portable_sizeof_char_ptr; + ulong blob_length= _ma_calc_blob_length(column_size_length, + field_length_data); + + if (!found_blob) + { + /* Calculate total length for all blobs */ + ulong blob_lengths= 0; + uchar *length_data= field_length_data; + MARIA_COLUMNDEF *blob_field= column; + + found_blob= 1; + for (; blob_field < end_column; blob_field++) + { + uint size_length; + if ((record[blob_field->null_pos] & blob_field->null_bit) || + (cur_row->empty_bits[blob_field->empty_pos] & + blob_field->empty_bit)) + continue; + size_length= blob_field->length - portable_sizeof_char_ptr; + blob_lengths+= _ma_calc_blob_length(size_length, length_data); + length_data+= size_length; + } + cur_row->blob_length= blob_lengths; + DBUG_PRINT("info", ("Total blob length: %lu", blob_lengths)); + if (_ma_alloc_buffer(&info->blob_buff, &info->blob_buff_size, + blob_lengths)) + DBUG_RETURN(my_errno); + blob_buffer= info->blob_buff; + } + + memcpy(field_pos, field_length_data, column_size_length); + memcpy_fixed(field_pos + column_size_length, (uchar *) &blob_buffer, + sizeof(char*)); + field_length_data+= column_size_length; + + /* + After we have read one extent, then each blob is in it's own extent + */ + if (!extent.first_extent || (ulong) (end_of_data - data) < blob_length) + end_of_data= data; /* Force read of next extent */ + + if (read_long_data(info, blob_buffer, blob_length, &extent, &data, + &end_of_data)) + DBUG_RETURN(my_errno); + blob_buffer+= blob_length; + break; + } + default: +#ifdef EXTRA_DEBUG + DBUG_ASSERT(0); /* purecov: deadcode */ +#endif + goto err; + } + continue; + } + + if (row_extents) + { + DBUG_PRINT("info", ("Row read: page_count: %u extent_count: %u", + extent.page_count, extent.extent_count)); + *extent.tail_positions= 0; /* End marker */ + if (extent.page_count) + goto err; + if (extent.extent_count > 1) + { + if (_ma_check_if_zero(extent.extent + ROW_EXTENT_SIZE, + (extent.extent_count-1) * ROW_EXTENT_SIZE)) + { + DBUG_PRINT("error", ("Data in extent is not zero")); + DBUG_DUMP("extent", extent.extent + ROW_EXTENT_SIZE, + (extent.extent_count-1) * ROW_EXTENT_SIZE); + goto err; + } + } + } + else + { + DBUG_PRINT("info", ("Row read")); + /* + data should normally point to end_of_date. The only exception is if + the row is very short in which case we allocated 'min_block_length' data + for allowing the row to expand. + */ + if (data != end_of_data && (uint) (end_of_data - start_of_data) > + share->base.min_block_length) + goto err; + } +#ifdef EXTRA_DEBUG + if (share->calc_checksum) + { + /* Esnure that row checksum is correct */ + DBUG_ASSERT(((share->calc_checksum)(info, record) & 255) == + cur_row->checksum); + } +#endif + info->update|= HA_STATE_AKTIV; /* We have an active record */ + DBUG_RETURN(0); + +err: + /* Something was wrong with data on record */ + DBUG_PRINT("error", ("Found record with wrong data")); + DBUG_RETURN((my_errno= HA_ERR_WRONG_IN_RECORD)); +} + + +/** @brief Read positions to tail blocks and full blocks + + @fn read_row_extent_info() + @param info Handler + + @notes + This function is a simpler version of _ma_read_block_record2() + The data about the used pages is stored in info->cur_row. + + @return Status + @retval 0 ok + @retval 1 Error. my_errno contains error number +*/ + +static my_bool read_row_extent_info(MARIA_HA *info, uchar *buff, + uint record_number) +{ + MARIA_SHARE *share= info->s; + MARIA_EXTENT_CURSOR extent; + MARIA_RECORD_POS *tail_pos; + uchar *data, *end_of_data; + uint flag, row_extents, row_extents_size, field_lengths; + uchar *extents, *end; + DBUG_ENTER("read_row_extent_info"); + + if (!(data= get_record_position(buff, share->block_size, + record_number, &end_of_data))) + DBUG_RETURN(1); /* Wrong in record */ + + flag= (uint) (uchar) data[0]; + /* Skip trans header */ + data+= total_header_size[(flag & PRECALC_HEADER_BITMASK)]; + + row_extents= 0; + row_extents_size= 0; + if (flag & ROW_FLAG_EXTENTS) + { + /* + Record is split over many data pages. + Get number of extents and first extent + */ + get_key_length(row_extents, data); + row_extents_size= row_extents * ROW_EXTENT_SIZE; + if (info->cur_row.extents_buffer_length < row_extents_size && + _ma_alloc_buffer(&info->cur_row.extents, + &info->cur_row.extents_buffer_length, + row_extents_size)) + DBUG_RETURN(1); + memcpy(info->cur_row.extents, data, ROW_EXTENT_SIZE); + data+= ROW_EXTENT_SIZE; + init_extent(&extent, info->cur_row.extents, row_extents, + info->cur_row.tail_positions); + extent.first_extent= 1; + } + info->cur_row.extents_count= row_extents; + + if (share->base.max_field_lengths) + get_key_length(field_lengths, data); + + if (share->calc_checksum) + info->cur_row.checksum= (uint) (uchar) *data++; + if (row_extents > 1) + { + data+= share->base.null_bytes; + data+= share->base.pack_bytes; + data+= share->base.field_offsets * FIELD_OFFSET_SIZE; + + /* + Read row extents (note that first extent was already read into + info->cur_row.extents above) + Lock tails with write lock as we will delete them later. + */ + extent.lock_for_tail_pages= PAGECACHE_LOCK_LEFT_WRITELOCKED; + if (read_long_data(info, info->cur_row.extents + ROW_EXTENT_SIZE, + row_extents_size - ROW_EXTENT_SIZE, + &extent, &data, &end_of_data)) + DBUG_RETURN(1); + } + + /* Update tail_positions with pointer to tails */ + tail_pos= info->cur_row.tail_positions; + for (extents= info->cur_row.extents, end= extents + row_extents_size; + extents < end; + extents+= ROW_EXTENT_SIZE) + { + pgcache_page_no_t page= uint5korr(extents); + uint page_count= uint2korr(extents + ROW_EXTENT_PAGE_SIZE); + if (page_count & TAIL_BIT) + *(tail_pos++)= ma_recordpos(page, (page_count & ~ (TAIL_BIT | + START_EXTENT_BIT))); + } + *tail_pos= 0; /* End marker */ + DBUG_RETURN(0); +} + + +/* + Read a record based on record position + + @fn _ma_read_block_record() + @param info Maria handler + @param record Store record here + @param record_pos Record position + + @return Status + @retval 0 ok + @retval # Error number +*/ + +int _ma_read_block_record(MARIA_HA *info, uchar *record, + MARIA_RECORD_POS record_pos) +{ + MARIA_SHARE *share= info->s; + uchar *data, *end_of_data, *buff; + uint offset; + uint block_size= share->block_size; + DBUG_ENTER("_ma_read_block_record"); + DBUG_PRINT("enter", ("rowid: %lu", (long) record_pos)); + + offset= ma_recordpos_to_dir_entry(record_pos); + + if (!(buff= pagecache_read(share->pagecache, + &info->dfile, ma_recordpos_to_page(record_pos), 0, + info->buff, share->page_type, + PAGECACHE_LOCK_LEFT_UNLOCKED, 0))) + DBUG_RETURN(my_errno); + DBUG_ASSERT((buff[PAGE_TYPE_OFFSET] & PAGE_TYPE_MASK) == HEAD_PAGE); + if (!(data= get_record_position(buff, block_size, offset, &end_of_data))) + { + DBUG_PRINT("error", ("Wrong directory entry in data block")); + my_errno= HA_ERR_RECORD_DELETED; /* File crashed */ + DBUG_RETURN(HA_ERR_RECORD_DELETED); + } + DBUG_RETURN(_ma_read_block_record2(info, record, data, end_of_data)); +} + + +/* compare unique constraint between stored rows */ + +my_bool _ma_cmp_block_unique(MARIA_HA *info, MARIA_UNIQUEDEF *def, + const uchar *record, MARIA_RECORD_POS pos) +{ + uchar *org_rec_buff, *old_record; + size_t org_rec_buff_size; + int error; + DBUG_ENTER("_ma_cmp_block_unique"); + + if (!(old_record= my_alloca(info->s->base.reclength))) + DBUG_RETURN(1); + + /* Don't let the compare destroy blobs that may be in use */ + org_rec_buff= info->rec_buff; + org_rec_buff_size= info->rec_buff_size; + if (info->s->base.blobs) + { + /* Force realloc of record buffer*/ + info->rec_buff= 0; + info->rec_buff_size= 0; + } + error= _ma_read_block_record(info, old_record, pos); + if (!error) + error= _ma_unique_comp(def, record, old_record, def->null_are_equal); + if (info->s->base.blobs) + { + my_free(info->rec_buff, MYF(MY_ALLOW_ZERO_PTR)); + info->rec_buff= org_rec_buff; + info->rec_buff_size= org_rec_buff_size; + } + DBUG_PRINT("exit", ("result: %d", error)); + my_afree(old_record); + DBUG_RETURN(error != 0); +} + + +/**************************************************************************** + Table scan +****************************************************************************/ + +/* + Allocate buffers for table scan + + SYNOPSIS + _ma_scan_init_block_record(MARIA_HA *info) + + IMPLEMENTATION + We allocate one buffer for the current bitmap and one buffer for the + current page + + RETURN + 0 ok + 1 error (couldn't allocate memory or disk error) +*/ + +my_bool _ma_scan_init_block_record(MARIA_HA *info) +{ + MARIA_SHARE *share= info->s; + DBUG_ENTER("_ma_scan_init_block_record"); + /* + bitmap_buff may already be allocated if this is the second call to + rnd_init() without a rnd_end() in between, see sql/handler.h + */ + if (!(info->scan.bitmap_buff || + ((info->scan.bitmap_buff= + (uchar *) my_malloc(share->block_size * 2, MYF(MY_WME)))))) + DBUG_RETURN(1); + info->scan.page_buff= info->scan.bitmap_buff + share->block_size; + info->scan.bitmap_end= info->scan.bitmap_buff + share->bitmap.total_size; + + /* Set scan variables to get _ma_scan_block() to start with reading bitmap */ + info->scan.number_of_rows= 0; + info->scan.bitmap_pos= info->scan.bitmap_end; + info->scan.bitmap_page= (pgcache_page_no_t) 0 - share->bitmap.pages_covered; + /* + We need to flush what's in memory (bitmap.map) to page cache otherwise, as + we are going to read bitmaps from page cache in table scan (see + _ma_scan_block_record()), we may miss recently inserted rows (bitmap page + in page cache would be too old). + */ + DBUG_RETURN(_ma_bitmap_flush(info->s)); +} + + +/* Free buffers allocated by _ma_scan_block_init() */ + +void _ma_scan_end_block_record(MARIA_HA *info) +{ + DBUG_ENTER("_ma_scan_end_block_record"); + my_free(info->scan.bitmap_buff, MYF(MY_ALLOW_ZERO_PTR)); + info->scan.bitmap_buff= 0; + if (info->scan_save) + { + my_free(info->scan_save, MYF(0)); + info->scan_save= 0; + } + DBUG_VOID_RETURN; +} + + +/** + @brief Save current scan position + + @note + For the moment we can only remember one position, but this is + good enough for MySQL usage + + @Warning + When this function is called, we assume that the thread is not deleting + or updating the current row before ma_scan_restore_block_record() + is called! + + @return + @retval 0 ok + @retval HA_ERR_WRONG_IN_RECORD Could not allocate memory to hold position +*/ + +int _ma_scan_remember_block_record(MARIA_HA *info, + MARIA_RECORD_POS *lastpos) +{ + uchar *bitmap_buff; + DBUG_ENTER("_ma_scan_remember_block_record"); + if (!(info->scan_save)) + { + if (!(info->scan_save= my_malloc(ALIGN_SIZE(sizeof(*info->scan_save)) + + info->s->block_size * 2, + MYF(MY_WME)))) + DBUG_RETURN(HA_ERR_OUT_OF_MEM); + info->scan_save->bitmap_buff= ((uchar*) info->scan_save + + ALIGN_SIZE(sizeof(*info->scan_save))); + } + /* Point to the last read row */ + *lastpos= info->cur_row.nextpos - 1; + info->scan.dir+= DIR_ENTRY_SIZE; + + /* Remember used bitmap and used head page */ + bitmap_buff= info->scan_save->bitmap_buff; + memcpy(info->scan_save, &info->scan, sizeof(*info->scan_save)); + info->scan_save->bitmap_buff= bitmap_buff; + memcpy(bitmap_buff, info->scan.bitmap_buff, info->s->block_size * 2); + DBUG_RETURN(0); +} + + +/** + @brief restore scan block it's original values + + @note + In theory we could swap bitmap buffers instead of copy them. + For the moment we don't do that because there are variables pointing + inside the buffers and it's a bit of hassle to either make them relative + or repoint them. +*/ + +void _ma_scan_restore_block_record(MARIA_HA *info, + MARIA_RECORD_POS lastpos) +{ + uchar *bitmap_buff; + DBUG_ENTER("_ma_scan_restore_block_record"); + + info->cur_row.nextpos= lastpos; + bitmap_buff= info->scan.bitmap_buff; + memcpy(&info->scan, info->scan_save, sizeof(*info->scan_save)); + info->scan.bitmap_buff= bitmap_buff; + memcpy(bitmap_buff, info->scan_save->bitmap_buff, info->s->block_size * 2); + + DBUG_VOID_RETURN; +} + + +/* + Read next record while scanning table + + SYNOPSIS + _ma_scan_block_record() + info Maria handler + record Store found here + record_pos Value stored in info->cur_row.next_pos after last call + skip_deleted + + NOTES + - One must have called mi_scan() before this + - In this version, we don't actually need record_pos, we as easily + use a variable in info->scan + + IMPLEMENTATION + Current code uses a lot of goto's to separate the different kind of + states we may be in. This gives us a minimum of executed if's for + the normal cases. I tried several different ways to code this, but + the current one was in the end the most readable and fastest. + + RETURN + 0 ok + # Error code +*/ + +int _ma_scan_block_record(MARIA_HA *info, uchar *record, + MARIA_RECORD_POS record_pos, + my_bool skip_deleted __attribute__ ((unused))) +{ + uint block_size; + my_off_t filepos; + MARIA_SHARE *share= info->s; + DBUG_ENTER("_ma_scan_block_record"); + +restart_record_read: + /* Find next row in current page */ + while (likely(record_pos < info->scan.number_of_rows)) + { + uint length, offset; + uchar *data, *end_of_data; + int error; + + while (!(offset= uint2korr(info->scan.dir))) + { + info->scan.dir-= DIR_ENTRY_SIZE; + record_pos++; +#ifdef SANITY_CHECKS + if (info->scan.dir < info->scan.dir_end) + { + DBUG_ASSERT(0); + goto err; + } +#endif + } + /* found row */ + info->cur_row.lastpos= info->scan.row_base_page + record_pos; + info->cur_row.nextpos= record_pos + 1; + data= info->scan.page_buff + offset; + length= uint2korr(info->scan.dir + 2); + end_of_data= data + length; + info->scan.dir-= DIR_ENTRY_SIZE; /* Point to previous row */ +#ifdef SANITY_CHECKS + if (end_of_data > info->scan.dir_end || + offset < PAGE_HEADER_SIZE || length < share->base.min_block_length) + { + DBUG_ASSERT(!(end_of_data > info->scan.dir_end)); + DBUG_ASSERT(!(offset < PAGE_HEADER_SIZE)); + DBUG_ASSERT(!(length < share->base.min_block_length)); + goto err; + } +#endif + DBUG_PRINT("info", ("rowid: %lu", (ulong) info->cur_row.lastpos)); + error= _ma_read_block_record2(info, record, data, end_of_data); + if (error != HA_ERR_ROW_NOT_VISIBLE) + DBUG_RETURN(error); + record_pos++; + } + + /* Find next head page in current bitmap */ +restart_bitmap_scan: + block_size= share->block_size; + if (likely(info->scan.bitmap_pos < info->scan.bitmap_end)) + { + uchar *data= info->scan.bitmap_pos; + longlong bits= info->scan.bits; + uint bit_pos= info->scan.bit_pos; + + do + { + while (likely(bits)) + { + uint pattern= (uint) (bits & 7); + bits >>= 3; + bit_pos++; + if (pattern > 0 && pattern <= 4) + { + /* Found head page; Read it */ + pgcache_page_no_t page; + info->scan.bitmap_pos= data; + info->scan.bits= bits; + info->scan.bit_pos= bit_pos; + page= (info->scan.bitmap_page + 1 + + (data - info->scan.bitmap_buff) / 6 * 16 + bit_pos - 1); + info->scan.row_base_page= ma_recordpos(page, 0); + if (!(pagecache_read(share->pagecache, + &info->dfile, + page, 0, info->scan.page_buff, + share->page_type, + PAGECACHE_LOCK_LEFT_UNLOCKED, 0))) + DBUG_RETURN(my_errno); + if (((info->scan.page_buff[PAGE_TYPE_OFFSET] & PAGE_TYPE_MASK) != + HEAD_PAGE)) + { + /* + This may happen if someone has been deleting all rows + from a page since we read the bitmap, so it may be ok. + Print warning in debug log and continue. + */ + DBUG_PRINT("warning", + ("Found page of type %d when expecting head page", + (info->scan.page_buff[PAGE_TYPE_OFFSET] & + PAGE_TYPE_MASK))); + continue; + } + if ((info->scan.number_of_rows= + (uint) (uchar) info->scan.page_buff[DIR_COUNT_OFFSET]) == 0) + { + DBUG_PRINT("error", ("Wrong page header")); + DBUG_RETURN((my_errno= HA_ERR_WRONG_IN_RECORD)); + } + DBUG_PRINT("info", ("Page %lu has %u rows", + (ulong) page, info->scan.number_of_rows)); + info->scan.dir= (info->scan.page_buff + block_size - + PAGE_SUFFIX_SIZE - DIR_ENTRY_SIZE); + info->scan.dir_end= (info->scan.dir - + (info->scan.number_of_rows - 1) * + DIR_ENTRY_SIZE); + record_pos= 0; + goto restart_record_read; + } + } + for (data+= 6; data < info->scan.bitmap_end; data+= 6) + { + bits= uint6korr(data); + /* Skip not allocated pages and blob / full tail pages */ + if (bits && bits != LL(07777777777777777)) + break; + } + bit_pos= 0; + } while (data < info->scan.bitmap_end); + } + + /* Read next bitmap */ + info->scan.bitmap_page+= share->bitmap.pages_covered; + filepos= (my_off_t) info->scan.bitmap_page * block_size; + if (unlikely(filepos >= share->state.state.data_file_length)) + { + DBUG_PRINT("info", ("Found end of file")); + DBUG_RETURN((my_errno= HA_ERR_END_OF_FILE)); + } + DBUG_PRINT("info", ("Reading bitmap at %lu", + (ulong) info->scan.bitmap_page)); + if (!(pagecache_read(share->pagecache, &info->s->bitmap.file, + info->scan.bitmap_page, + 0, info->scan.bitmap_buff, PAGECACHE_PLAIN_PAGE, + PAGECACHE_LOCK_LEFT_UNLOCKED, 0))) + DBUG_RETURN(my_errno); + /* Skip scanning 'bits' in bitmap scan code */ + info->scan.bitmap_pos= info->scan.bitmap_buff - 6; + info->scan.bits= 0; + goto restart_bitmap_scan; + +err: + DBUG_PRINT("error", ("Wrong data on page")); + DBUG_RETURN((my_errno= HA_ERR_WRONG_IN_RECORD)); +} + + +/* + Compare a row against a stored one + + NOTES + Not implemented, as block record is not supposed to be used in a shared + global environment +*/ + +my_bool _ma_compare_block_record(MARIA_HA *info __attribute__ ((unused)), + const uchar *record __attribute__ ((unused))) +{ + return 0; +} + + +/* + Store an integer with simple packing + + SYNOPSIS + ma_store_integer() + to Store the packed integer here + nr Integer to store + + NOTES + This is mostly used to store field numbers and lengths of strings. + We have to cast the result for the LL() becasue of a bug in Forte CC + compiler. + + Packing used is: + nr < 251 is stored as is (in 1 byte) + Numbers that require 1-4 bytes are stored as char(250+byte_length), data + Bigger numbers are stored as 255, data as ulonglong (not yet done). + + RETURN + Position in 'to' after the packed length +*/ + +uchar *ma_store_length(uchar *to, ulong nr) +{ + if (nr < 251) + { + *to=(uchar) nr; + return to+1; + } + if (nr < 65536) + { + if (nr <= 255) + { + to[0]= (uchar) 251; + to[1]= (uchar) nr; + return to+2; + } + to[0]= (uchar) 252; + int2store(to+1, nr); + return to+3; + } + if (nr < 16777216) + { + *to++= (uchar) 253; + int3store(to, nr); + return to+3; + } + *to++= (uchar) 254; + int4store(to, nr); + return to+4; +} + + +/* Calculate how many bytes needed to store a number */ + +uint ma_calc_length_for_store_length(ulong nr) +{ + if (nr < 251) + return 1; + if (nr < 65536) + { + if (nr <= 255) + return 2; + return 3; + } + if (nr < 16777216) + return 4; + return 5; +} + + +/* Retrive a stored number */ + +static ulong ma_get_length(const uchar **packet) +{ + reg1 const uchar *pos= *packet; + if (*pos < 251) + { + (*packet)++; + return (ulong) *pos; + } + if (*pos == 251) + { + (*packet)+= 2; + return (ulong) pos[1]; + } + if (*pos == 252) + { + (*packet)+= 3; + return (ulong) uint2korr(pos+1); + } + if (*pos == 253) + { + (*packet)+= 4; + return (ulong) uint3korr(pos+1); + } + DBUG_ASSERT(*pos == 254); + (*packet)+= 5; + return (ulong) uint4korr(pos+1); +} + + +/* + Fill array with pointers to field parts to be stored in log for insert + + SYNOPSIS + fill_insert_undo_parts() + info Maria handler + record Inserted row + log_parts Store pointers to changed memory areas here + log_parts_count See RETURN + + NOTES + We have information in info->cur_row about the read row. + + RETURN + length of data in log_parts. + log_parts_count contains number of used log_parts +*/ + +static size_t fill_insert_undo_parts(MARIA_HA *info, const uchar *record, + LEX_CUSTRING *log_parts, + uint *log_parts_count) +{ + MARIA_SHARE *share= info->s; + MARIA_COLUMNDEF *column, *end_column; + uchar *field_lengths= info->cur_row.field_lengths; + size_t row_length; + MARIA_ROW *cur_row= &info->cur_row; + LEX_CUSTRING *start_log_parts; + DBUG_ENTER("fill_insert_undo_parts"); + + start_log_parts= log_parts; + + /* Store null bits */ + log_parts->str= record; + log_parts->length= share->base.null_bytes; + row_length= log_parts->length; + log_parts++; + + /* Stored bitmap over packed (zero length or all-zero fields) */ + log_parts->str= info->cur_row.empty_bits; + log_parts->length= share->base.pack_bytes; + row_length+= log_parts->length; + log_parts++; + + if (share->base.max_field_lengths) + { + /* Store length of all not empty char, varchar and blob fields */ + log_parts->str= field_lengths - 2; + log_parts->length= info->cur_row.field_lengths_length+2; + int2store(log_parts->str, info->cur_row.field_lengths_length); + row_length+= log_parts->length; + log_parts++; + } + + if (share->base.blobs) + { + /* + Store total blob length to make buffer allocation easier during UNDO + */ + log_parts->str= info->length_buff; + log_parts->length= (uint) (ma_store_length((uchar *) log_parts->str, + info->cur_row.blob_length) - + (uchar*) log_parts->str); + row_length+= log_parts->length; + log_parts++; + } + + /* Handle constant length fields that are always present */ + for (column= share->columndef, + end_column= column+ share->base.fixed_not_null_fields; + column < end_column; + column++) + { + log_parts->str= record + column->offset; + log_parts->length= column->length; + row_length+= log_parts->length; + log_parts++; + } + + /* Handle NULL fields and CHAR/VARCHAR fields */ + for (end_column= share->columndef + share->base.fields - share->base.blobs; + column < end_column; + column++) + { + const uchar *column_pos; + size_t column_length; + if ((record[column->null_pos] & column->null_bit) || + cur_row->empty_bits[column->empty_pos] & column->empty_bit) + continue; + + column_pos= record+ column->offset; + column_length= column->length; + + switch (column->type) { + case FIELD_CHECK: + case FIELD_NORMAL: /* Fixed length field */ + case FIELD_ZERO: + case FIELD_SKIP_PRESPACE: /* Not packed */ + case FIELD_SKIP_ZERO: /* Fixed length field */ + break; + case FIELD_SKIP_ENDSPACE: /* CHAR */ + { + if (column->length <= 255) + column_length= *field_lengths++; + else + { + column_length= uint2korr(field_lengths); + field_lengths+= 2; + } + break; + } + case FIELD_VARCHAR: + { + if (column->fill_length == 1) + column_length= *field_lengths; + else + column_length= uint2korr(field_lengths); + field_lengths+= column->fill_length; + column_pos+= column->fill_length; + break; + } + default: + DBUG_ASSERT(0); + } + log_parts->str= column_pos; + log_parts->length= column_length; + row_length+= log_parts->length; + log_parts++; + } + + /* Add blobs */ + for (end_column+= share->base.blobs; column < end_column; column++) + { + const uchar *field_pos= record + column->offset; + uint size_length= column->length - portable_sizeof_char_ptr; + ulong blob_length= _ma_calc_blob_length(size_length, field_pos); + + /* + We don't have to check for null, as blob_length is guranteed to be 0 + if the blob is null + */ + if (blob_length) + { + uchar *blob_pos; + memcpy_fixed(&blob_pos, record + column->offset + size_length, + sizeof(blob_pos)); + log_parts->str= blob_pos; + log_parts->length= blob_length; + row_length+= log_parts->length; + log_parts++; + } + } + *log_parts_count= (uint) (log_parts - start_log_parts); + DBUG_RETURN(row_length); +} + + +/* + Fill array with pointers to field parts to be stored in log for update + + SYNOPSIS + fill_update_undo_parts() + info Maria handler + oldrec Original row + newrec New row + log_parts Store pointers to changed memory areas here + log_parts_count See RETURN + + IMPLEMENTATION + Format of undo record: + + Fields are stored in same order as the field array. + + Offset to changed field data (packed) + + For each changed field + Fieldnumber (packed) + Length, if variable length field (packed) + + For each changed field + Data + + Packing is using ma_store_integer() + + The reason we store field numbers & length separated from data (ie, not + after each other) is to get better cpu caching when we loop over + fields (as we probably don't have to access data for each field when we + want to read and old row through the undo log record). + + As a special case, we use '255' for the field number of the null bitmap. + + RETURN + length of data in log_parts. + log_parts_count contains number of used log_parts +*/ + +static size_t fill_update_undo_parts(MARIA_HA *info, const uchar *oldrec, + const uchar *newrec, + LEX_CUSTRING *log_parts, + uint *log_parts_count) +{ + MARIA_SHARE *share= info->s; + MARIA_COLUMNDEF *column, *end_column; + MARIA_ROW *old_row= &info->cur_row, *new_row= &info->new_row; + uchar *field_data, *start_field_data; + uchar *old_field_lengths= old_row->field_lengths; + uchar *new_field_lengths= new_row->field_lengths; + size_t row_length= 0; + uint field_lengths; + LEX_CUSTRING *start_log_parts; + my_bool new_column_is_empty; + DBUG_ENTER("fill_update_undo_parts"); + + start_log_parts= log_parts; + + /* + First log part is for number of fields, field numbers and lengths + The +4 is to reserve place for the number of changed fields. + */ + start_field_data= field_data= info->update_field_data + 4; + log_parts++; + + if (memcmp(oldrec, newrec, share->base.null_bytes)) + { + /* Store changed null bits */ + *field_data++= (uchar) 255; /* Special case */ + log_parts->str= oldrec; + log_parts->length= share->base.null_bytes; + row_length= log_parts->length; + log_parts++; + } + + /* Handle constant length fields */ + for (column= share->columndef, + end_column= column+ share->base.fixed_not_null_fields; + column < end_column; + column++) + { + if (memcmp(oldrec + column->offset, newrec + column->offset, + column->length)) + { + field_data= ma_store_length(field_data, + (uint) (column - share->columndef)); + log_parts->str= oldrec + column->offset; + log_parts->length= column->length; + row_length+= column->length; + log_parts++; + } + } + + /* Handle the rest: NULL fields and CHAR/VARCHAR fields and BLOB's */ + for (end_column= share->columndef + share->base.fields; + column < end_column; + column++) + { + const uchar *new_column_pos, *old_column_pos; + size_t new_column_length, old_column_length; + + /* First check if old column is null or empty */ + if (oldrec[column->null_pos] & column->null_bit) + { + /* + It's safe to skip this one as either the new column is also null + (no change) or the new_column is not null, in which case the null-bit + maps differed and we have already stored the null bitmap. + */ + continue; + } + if (old_row->empty_bits[column->empty_pos] & column->empty_bit) + { + if (new_row->empty_bits[column->empty_pos] & column->empty_bit) + continue; /* Both are empty; skip */ + + /* Store null length column */ + field_data= ma_store_length(field_data, + (uint) (column - share->columndef)); + field_data= ma_store_length(field_data, 0); + continue; + } + /* + Remember if the 'new' value is empty (as in this case we must always + log the original value + */ + new_column_is_empty= ((newrec[column->null_pos] & column->null_bit) || + (new_row->empty_bits[column->empty_pos] & + column->empty_bit)); + + old_column_pos= oldrec + column->offset; + new_column_pos= newrec + column->offset; + old_column_length= new_column_length= column->length; + + switch (column->type) { + case FIELD_CHECK: + case FIELD_NORMAL: /* Fixed length field */ + case FIELD_ZERO: + case FIELD_SKIP_PRESPACE: /* Not packed */ + case FIELD_SKIP_ZERO: /* Fixed length field */ + break; + case FIELD_VARCHAR: + new_column_length--; /* Skip length prefix */ + old_column_pos+= column->fill_length; + new_column_pos+= column->fill_length; + /* Fall through */ + case FIELD_SKIP_ENDSPACE: /* CHAR */ + { + if (new_column_length <= 255) + { + old_column_length= *old_field_lengths++; + if (!new_column_is_empty) + new_column_length= *new_field_lengths++; + } + else + { + old_column_length= uint2korr(old_field_lengths); + old_field_lengths+= 2; + if (!new_column_is_empty) + { + new_column_length= uint2korr(new_field_lengths); + new_field_lengths+= 2; + } + } + break; + } + case FIELD_BLOB: + { + uint size_length= column->length - portable_sizeof_char_ptr; + old_column_length= _ma_calc_blob_length(size_length, old_column_pos); + memcpy_fixed((uchar*) &old_column_pos, + oldrec + column->offset + size_length, + sizeof(old_column_pos)); + if (!new_column_is_empty) + { + new_column_length= _ma_calc_blob_length(size_length, new_column_pos); + memcpy_fixed((uchar*) &new_column_pos, + newrec + column->offset + size_length, + sizeof(old_column_pos)); + } + break; + } + default: + DBUG_ASSERT(0); + } + + if (new_column_is_empty || new_column_length != old_column_length || + memcmp(old_column_pos, new_column_pos, new_column_length)) + { + field_data= ma_store_length(field_data, + (ulong) (column - share->columndef)); + field_data= ma_store_length(field_data, (ulong) old_column_length); + + log_parts->str= old_column_pos; + log_parts->length= old_column_length; + row_length+= old_column_length; + log_parts++; + } + } + + *log_parts_count= (uint) (log_parts - start_log_parts); + + /* Store length of field length data before the field/field_lengths */ + field_lengths= (uint) (field_data - start_field_data); + start_log_parts->str= ((start_field_data - + ma_calc_length_for_store_length(field_lengths))); + ma_store_length((uchar*)start_log_parts->str, field_lengths); + start_log_parts->length= (size_t) (field_data - start_log_parts->str); + row_length+= start_log_parts->length; + DBUG_RETURN(row_length); +} + +/*************************************************************************** + In-write hooks called under log's lock when log record is written +***************************************************************************/ + +/** + @brief Sets transaction's rec_lsn if needed + + A transaction sometimes writes a REDO even before the page is in the + pagecache (example: brand new head or tail pages; full pages). So, if + Checkpoint happens just after the REDO write, it needs to know that the + REDO phase must start before this REDO. Scanning the pagecache cannot + tell that as the page is not in the cache. So, transaction sets its rec_lsn + to the REDO's LSN or somewhere before, and Checkpoint reads the + transaction's rec_lsn. + + @return Operation status, always 0 (success) +*/ + +my_bool write_hook_for_redo(enum translog_record_type type + __attribute__ ((unused)), + TRN *trn, MARIA_HA *tbl_info + __attribute__ ((unused)), + LSN *lsn, void *hook_arg + __attribute__ ((unused))) +{ + /* + Users of dummy_transaction_object must keep this TRN clean as it + is used by many threads (like those manipulating non-transactional + tables). It might be dangerous if one user sets rec_lsn or some other + member and it is picked up by another user (like putting this rec_lsn into + a page of a non-transactional table); it's safer if all members stay 0. So + non-transactional log records (REPAIR, CREATE, RENAME, DROP) should not + call this hook; we trust them but verify ;) + */ + DBUG_ASSERT(trn->trid != 0); + /* + If the hook stays so simple, it would be faster to pass + !trn->rec_lsn ? trn->rec_lsn : some_dummy_lsn + to translog_write_record(), like Monty did in his original code, and not + have a hook. For now we keep it like this. + */ + if (trn->rec_lsn == 0) + trn->rec_lsn= *lsn; + return 0; +} + + +/** + @brief Sets transaction's undo_lsn, first_undo_lsn if needed + + @return Operation status, always 0 (success) +*/ + +my_bool write_hook_for_undo(enum translog_record_type type + __attribute__ ((unused)), + TRN *trn, MARIA_HA *tbl_info + __attribute__ ((unused)), + LSN *lsn, void *hook_arg + __attribute__ ((unused))) +{ + DBUG_ASSERT(trn->trid != 0); + trn->undo_lsn= *lsn; + if (unlikely(LSN_WITH_FLAGS_TO_LSN(trn->first_undo_lsn) == 0)) + trn->first_undo_lsn= + trn->undo_lsn | LSN_WITH_FLAGS_TO_FLAGS(trn->first_undo_lsn); + return 0; + /* + when we implement purging, we will specialize this hook: UNDO_PURGE + records will additionally set trn->undo_purge_lsn + */ +} + + +/** + @brief Sets the table's records count and checksum and others to 0, then + calls the generic REDO hook. + + @return Operation status, always 0 (success) +*/ + +my_bool write_hook_for_redo_delete_all(enum translog_record_type type + __attribute__ ((unused)), + TRN *trn, MARIA_HA *tbl_info + __attribute__ ((unused)), + LSN *lsn, void *hook_arg) +{ + _ma_reset_status(tbl_info); + return write_hook_for_redo(type, trn, tbl_info, lsn, hook_arg); +} + + +/** + @brief Updates "records" and "checksum" and calls the generic UNDO hook + + @return Operation status, always 0 (success) +*/ + +my_bool write_hook_for_undo_row_insert(enum translog_record_type type + __attribute__ ((unused)), + TRN *trn, MARIA_HA *tbl_info, + LSN *lsn, void *hook_arg) +{ + MARIA_SHARE *share= tbl_info->s; + share->state.state.records++; + share->state.state.checksum+= *(ha_checksum *)hook_arg; + return write_hook_for_undo(type, trn, tbl_info, lsn, hook_arg); +} + + +/** + @brief Upates "records" and calls the generic UNDO hook + + @return Operation status, always 0 (success) +*/ + +my_bool write_hook_for_undo_row_delete(enum translog_record_type type + __attribute__ ((unused)), + TRN *trn, MARIA_HA *tbl_info, + LSN *lsn, void *hook_arg) +{ + MARIA_SHARE *share= tbl_info->s; + share->state.state.records--; + share->state.state.checksum+= *(ha_checksum *)hook_arg; + return write_hook_for_undo(type, trn, tbl_info, lsn, hook_arg); +} + + +/** + @brief Upates "records" and "checksum" and calls the generic UNDO hook + + @return Operation status, always 0 (success) +*/ + +my_bool write_hook_for_undo_row_update(enum translog_record_type type + __attribute__ ((unused)), + TRN *trn, MARIA_HA *tbl_info, + LSN *lsn, void *hook_arg) +{ + MARIA_SHARE *share= tbl_info->s; + share->state.state.checksum+= *(ha_checksum *)hook_arg; + return write_hook_for_undo(type, trn, tbl_info, lsn, hook_arg); +} + + +my_bool write_hook_for_undo_bulk_insert(enum translog_record_type type + __attribute__ ((unused)), + TRN *trn, MARIA_HA *tbl_info, + LSN *lsn, void *hook_arg) +{ + /* + We are going to call maria_delete_all_rows(), but without logging and + syncing, as an optimization (if we crash before commit, the UNDO will + empty; if we crash after commit, we have flushed and forced the files). + Status still needs to be reset under log mutex, in case of a concurrent + checkpoint. + */ + _ma_reset_status(tbl_info); + return write_hook_for_undo(type, trn, tbl_info, lsn, hook_arg); +} + + +/** + @brief Updates table's lsn_of_file_id. + + @return Operation status, always 0 (success) +*/ + +my_bool write_hook_for_file_id(enum translog_record_type type + __attribute__ ((unused)), + TRN *trn + __attribute__ ((unused)), + MARIA_HA *tbl_info, + LSN *lsn, + void *hook_arg + __attribute__ ((unused))) +{ + DBUG_ASSERT(cmp_translog_addr(tbl_info->s->lsn_of_file_id, *lsn) < 0); + tbl_info->s->lsn_of_file_id= *lsn; + return 0; +} + + +/** + Updates transaction's rec_lsn when committing. + + A transaction writes its commit record before being committed in trnman, so + if Checkpoint happens just between the COMMIT record log write and the + commit in trnman, it will record that transaction is not committed. Assume + the transaction (trn1) did an INSERT; after the checkpoint, a second + transaction (trn2) does a DELETE of what trn1 has inserted. Then crash, + Checkpoint record says that trn1 was not committed, and REDO phase starts + from Checkpoint record's LSN. So it will not find the COMMIT record of + trn1, will want to roll back trn1, which will fail because the row/key + which it wants to delete does not exist anymore. + To avoid this, Checkpoint needs to know that the REDO phase must start + before this COMMIT record, so transaction sets its rec_lsn to the COMMIT's + record LSN, and as Checkpoint reads the transaction's rec_lsn, Checkpoint + will know. + + @note so after commit trn->rec_lsn is a "commit LSN", which could be of + use later. + + @return Operation status, always 0 (success) +*/ + +my_bool write_hook_for_commit(enum translog_record_type type + __attribute__ ((unused)), + TRN *trn, + MARIA_HA *tbl_info + __attribute__ ((unused)), + LSN *lsn, + void *hook_arg + __attribute__ ((unused))) +{ + trn->rec_lsn= *lsn; + return 0; +} + + +/*************************************************************************** + Applying of REDO log records +***************************************************************************/ + +/* + Apply changes to head and tail pages + + SYNOPSIS + _ma_apply_redo_insert_row_head_or_tail() + info Maria handler + lsn LSN to put on page + page_type HEAD_PAGE or TAIL_PAGE + new_page True if this is first entry on page + header Header (without FILEID) + data Data to be put on page + data_length Length of data + + NOTE + Handles LOGREC_REDO_INSERT_ROW_HEAD, LOGREC_REDO_INSERT_ROW_TAIL + LOGREC_REDO_NEW_ROW_HEAD and LOGREC_REDO_NEW_ROW_TAIL + + RETURN + 0 ok + # Error number +*/ + +uint _ma_apply_redo_insert_row_head_or_tail(MARIA_HA *info, LSN lsn, + uint page_type, + my_bool new_page, + const uchar *header, + const uchar *data, + size_t data_length) +{ + MARIA_SHARE *share= info->s; + pgcache_page_no_t page; + uint rownr, empty_space; + uint block_size= share->block_size; + uint rec_offset; + uchar *buff, *dir; + uint result; + MARIA_PINNED_PAGE page_link; + enum pagecache_page_lock unlock_method; + enum pagecache_page_pin unpin_method; + my_off_t end_of_page; + uint error; + DBUG_ENTER("_ma_apply_redo_insert_row_head_or_tail"); + + page= page_korr(header); + rownr= dirpos_korr(header + PAGE_STORE_SIZE); + + DBUG_PRINT("enter", ("rowid: %lu page: %lu rownr: %u data_length: %u", + (ulong) ma_recordpos(page, rownr), + (ulong) page, rownr, (uint) data_length)); + + share->state.changed|= (STATE_CHANGED | STATE_NOT_ZEROFILLED | + STATE_NOT_MOVABLE); + + end_of_page= (page + 1) * share->block_size; + if (end_of_page > share->state.state.data_file_length) + { + DBUG_PRINT("info", ("Enlarging data file from %lu to %lu", + (ulong) share->state.state.data_file_length, + (ulong) end_of_page)); + /* + New page at end of file. Note that the test above is also positive if + data_file_length is not a multiple of block_size (system crashed while + writing the last page): in this case we just extend the last page and + fill it entirely with zeroes, then the REDO will put correct data on + it. + */ + unlock_method= PAGECACHE_LOCK_WRITE; + unpin_method= PAGECACHE_PIN; + + DBUG_ASSERT(rownr == 0 && new_page); + if (rownr != 0 || !new_page) + goto crashed_file; + + buff= info->keyread_buff; + info->keyread_buff_used= 1; + make_empty_page(info, buff, page_type, 1); + empty_space= (block_size - PAGE_OVERHEAD_SIZE); + rec_offset= PAGE_HEADER_SIZE; + dir= buff+ block_size - PAGE_SUFFIX_SIZE - DIR_ENTRY_SIZE; + } + else + { + unlock_method= PAGECACHE_LOCK_LEFT_WRITELOCKED; + unpin_method= PAGECACHE_PIN_LEFT_PINNED; + + share->pagecache->readwrite_flags&= ~MY_WME; + buff= pagecache_read(share->pagecache, &info->dfile, + page, 0, 0, + PAGECACHE_PLAIN_PAGE, PAGECACHE_LOCK_WRITE, + &page_link.link); + share->pagecache->readwrite_flags= share->pagecache->org_readwrite_flags; + if (!buff) + { + /* Skip errors when reading outside of file and uninitialized pages */ + if (!new_page || (my_errno != HA_ERR_FILE_TOO_SHORT && + my_errno != HA_ERR_WRONG_CRC)) + goto err; + /* Create new page */ + buff= pagecache_block_link_to_buffer(page_link.link); + buff[PAGE_TYPE_OFFSET]= UNALLOCATED_PAGE; + } + else if (lsn_korr(buff) >= lsn) /* Test if already applied */ + { + /* Fix bitmap, just in case */ + empty_space= uint2korr(buff + EMPTY_SPACE_OFFSET); + if (_ma_bitmap_set(info, page, page_type == HEAD_PAGE, empty_space)) + goto err; + pagecache_unlock_by_link(share->pagecache, page_link.link, + PAGECACHE_LOCK_WRITE_UNLOCK, + PAGECACHE_UNPIN, LSN_IMPOSSIBLE, + LSN_IMPOSSIBLE, 0, FALSE); + DBUG_RETURN(0); + } + + if (((buff[PAGE_TYPE_OFFSET] & PAGE_TYPE_MASK) != page_type)) + { + /* + This is a page that has been freed before and now should be + changed to new type. + */ + if (!new_page) + goto crashed_file; + make_empty_page(info, buff, page_type, 0); + empty_space= block_size - PAGE_HEADER_SIZE - PAGE_SUFFIX_SIZE; + (void) extend_directory(page_type == HEAD_PAGE ? info: 0, buff, + block_size, 0, rownr, &empty_space); + rec_offset= PAGE_HEADER_SIZE; + dir= dir_entry_pos(buff, block_size, rownr); + empty_space+= uint2korr(dir+2); + } + else + { + uint max_entry= (uint) buff[DIR_COUNT_OFFSET]; + uint length; + + DBUG_ASSERT(!new_page); + dir= dir_entry_pos(buff, block_size, rownr); + empty_space= uint2korr(buff + EMPTY_SPACE_OFFSET); + + if (max_entry <= rownr) + { + /* Add directory entry first in directory and data last on page */ + if (extend_directory(page_type == HEAD_PAGE ? info : 0, buff, + block_size, max_entry, rownr, &empty_space)) + goto crashed_file; + } + if (extend_area_on_page(page_type == HEAD_PAGE ? info : 0, buff, + dir, rownr, block_size, + (uint) data_length, &empty_space, + &rec_offset, &length)) + goto crashed_file; + } + } + /* Copy data */ + int2store(dir+2, data_length); + memcpy(buff + rec_offset, data, data_length); + empty_space-= (uint) data_length; + int2store(buff + EMPTY_SPACE_OFFSET, empty_space); + + /* + If page was not read before, write it but keep it pinned. + We don't update its LSN When we have processed all REDOs for this page + in the current REDO's group, we will stamp page with UNDO's LSN + (if we stamped it now, a next REDO, in + this group, for this page, would be skipped) and unpin then. + */ + result= 0; + if (unlock_method == PAGECACHE_LOCK_WRITE && + pagecache_write(share->pagecache, + &info->dfile, page, 0, + buff, PAGECACHE_PLAIN_PAGE, + unlock_method, unpin_method, + PAGECACHE_WRITE_DELAY, &page_link.link, + LSN_IMPOSSIBLE)) + result= my_errno; + + /* Fix bitmap */ + if (_ma_bitmap_set(info, page, page_type == HEAD_PAGE, empty_space)) + goto err; + + page_link.unlock= PAGECACHE_LOCK_WRITE_UNLOCK; + page_link.changed= 1; + push_dynamic(&info->pinned_pages, (void*) &page_link); + + /* + Data page and bitmap page are in place, we can update data_file_length in + case we extended the file. We could not do it earlier: bitmap code tests + data_file_length to know if it has to create a new page or not. + */ + set_if_bigger(share->state.state.data_file_length, end_of_page); + DBUG_RETURN(result); + +crashed_file: + my_errno= HA_ERR_WRONG_IN_RECORD; +err: + error= my_errno; + if (unlock_method == PAGECACHE_LOCK_LEFT_WRITELOCKED) + pagecache_unlock_by_link(share->pagecache, page_link.link, + PAGECACHE_LOCK_WRITE_UNLOCK, + PAGECACHE_UNPIN, LSN_IMPOSSIBLE, + LSN_IMPOSSIBLE, 0, FALSE); + _ma_mark_file_crashed(share); + DBUG_ASSERT(0); /* catch recovery errors early */ + DBUG_RETURN((my_errno= error)); +} + + +/* + Apply LOGREC_REDO_PURGE_ROW_HEAD & LOGREC_REDO_PURGE_ROW_TAIL + + SYNOPSIS + _ma_apply_redo_purge_row_head_or_tail() + info Maria handler + lsn LSN to put on page + page_type HEAD_PAGE or TAIL_PAGE + header Header (without FILEID) + + NOTES + This function is very similar to delete_head_or_tail() + + RETURN + 0 ok + # Error number +*/ + +uint _ma_apply_redo_purge_row_head_or_tail(MARIA_HA *info, LSN lsn, + uint page_type, + const uchar *header) +{ + MARIA_SHARE *share= info->s; + pgcache_page_no_t page; + uint rownr, empty_space; + uint block_size= share->block_size; + uchar *buff; + int result; + uint error; + MARIA_PINNED_PAGE page_link; + DBUG_ENTER("_ma_apply_redo_purge_row_head_or_tail"); + + page= page_korr(header); + rownr= dirpos_korr(header+PAGE_STORE_SIZE); + DBUG_PRINT("enter", ("rowid: %lu page: %lu rownr: %u", + (ulong) ma_recordpos(page, rownr), + (ulong) page, rownr)); + + share->state.changed|= (STATE_CHANGED | STATE_NOT_ZEROFILLED | + STATE_NOT_MOVABLE); + + if (!(buff= pagecache_read(share->pagecache, &info->dfile, + page, 0, 0, + PAGECACHE_PLAIN_PAGE, PAGECACHE_LOCK_WRITE, + &page_link.link))) + goto err; + + if (lsn_korr(buff) >= lsn) + { + /* + Already applied + Note that in case the page is not anymore a head or tail page + a future redo will fix the bitmap. + */ + if ((buff[PAGE_TYPE_OFFSET] & PAGE_TYPE_MASK) == page_type) + { + empty_space= uint2korr(buff+EMPTY_SPACE_OFFSET); + if (_ma_bitmap_set(info, page, page_type == HEAD_PAGE, + empty_space)) + goto err; + } + pagecache_unlock_by_link(share->pagecache, page_link.link, + PAGECACHE_LOCK_WRITE_UNLOCK, + PAGECACHE_UNPIN, LSN_IMPOSSIBLE, + LSN_IMPOSSIBLE, 0, FALSE); + DBUG_RETURN(0); + } + + DBUG_ASSERT((buff[PAGE_TYPE_OFFSET] & PAGE_TYPE_MASK) == (uchar) page_type); + + if (delete_dir_entry(buff, block_size, rownr, &empty_space) < 0) + { + my_errno= HA_ERR_WRONG_IN_RECORD; + goto err; + } + + page_link.unlock= PAGECACHE_LOCK_WRITE_UNLOCK; + page_link.changed= 1; + push_dynamic(&info->pinned_pages, (void*) &page_link); + + result= 0; + /* This will work even if the page was marked as UNALLOCATED_PAGE */ + if (_ma_bitmap_set(info, page, page_type == HEAD_PAGE, empty_space)) + result= my_errno; + + DBUG_RETURN(result); + +err: + error= my_errno; + pagecache_unlock_by_link(share->pagecache, page_link.link, + PAGECACHE_LOCK_WRITE_UNLOCK, + PAGECACHE_UNPIN, LSN_IMPOSSIBLE, + LSN_IMPOSSIBLE, 0, FALSE); + _ma_mark_file_crashed(share); + DBUG_ASSERT(0); + DBUG_RETURN((my_errno= error)); + +} + + +/** + @brief Apply LOGREC_REDO_FREE_BLOCKS + + @param info Maria handler + @param header Header (without FILEID) + + @note It marks the pages free in the bitmap + + @return Operation status + @retval 0 OK + @retval 1 Error +*/ + +uint _ma_apply_redo_free_blocks(MARIA_HA *info, + LSN lsn __attribute__((unused)), + const uchar *header) +{ + MARIA_SHARE *share= info->s; + uint ranges; + DBUG_ENTER("_ma_apply_redo_free_blocks"); + + share->state.changed|= (STATE_CHANGED | STATE_NOT_ZEROFILLED | + STATE_NOT_MOVABLE); + + ranges= pagerange_korr(header); + header+= PAGERANGE_STORE_SIZE; + DBUG_ASSERT(ranges > 0); + + while (ranges--) + { + my_bool res; + uint page_range; + pgcache_page_no_t page, start_page; + + start_page= page= page_korr(header); + header+= PAGE_STORE_SIZE; + /* Page range may have this bit set to indicate a tail page */ + page_range= pagerange_korr(header) & ~(TAIL_BIT | START_EXTENT_BIT); + DBUG_ASSERT(page_range > 0); + + header+= PAGERANGE_STORE_SIZE; + + DBUG_PRINT("info", ("page: %lu pages: %u", (long) page, page_range)); + + /** @todo leave bitmap lock to the bitmap code... */ + pthread_mutex_lock(&share->bitmap.bitmap_lock); + res= _ma_bitmap_reset_full_page_bits(info, &share->bitmap, start_page, + page_range); + pthread_mutex_unlock(&share->bitmap.bitmap_lock); + if (res) + { + _ma_mark_file_crashed(share); + DBUG_ASSERT(0); + DBUG_RETURN(res); + } + } + DBUG_RETURN(0); +} + + +/** + @brief Apply LOGREC_REDO_FREE_HEAD_OR_TAIL + + @param info Maria handler + @param header Header (without FILEID) + + @note It marks the page free in the bitmap, and sets the directory's count + to 0. + + @return Operation status + @retval 0 OK + @retval 1 Error +*/ + +uint _ma_apply_redo_free_head_or_tail(MARIA_HA *info, LSN lsn, + const uchar *header) +{ + MARIA_SHARE *share= info->s; + uchar *buff; + pgcache_page_no_t page; + MARIA_PINNED_PAGE page_link; + my_bool res; + DBUG_ENTER("_ma_apply_redo_free_head_or_tail"); + + share->state.changed|= (STATE_CHANGED | STATE_NOT_ZEROFILLED | + STATE_NOT_MOVABLE); + + page= page_korr(header); + + if (!(buff= pagecache_read(share->pagecache, + &info->dfile, + page, 0, 0, + PAGECACHE_PLAIN_PAGE, + PAGECACHE_LOCK_WRITE, &page_link.link))) + { + pagecache_unlock_by_link(share->pagecache, page_link.link, + PAGECACHE_LOCK_WRITE_UNLOCK, + PAGECACHE_UNPIN, LSN_IMPOSSIBLE, + LSN_IMPOSSIBLE, 0, FALSE); + goto err; + } + if (lsn_korr(buff) >= lsn) + { + /* Already applied */ + pagecache_unlock_by_link(share->pagecache, page_link.link, + PAGECACHE_LOCK_WRITE_UNLOCK, + PAGECACHE_UNPIN, LSN_IMPOSSIBLE, + LSN_IMPOSSIBLE, 0, FALSE); + } + else + { + buff[PAGE_TYPE_OFFSET]= UNALLOCATED_PAGE; +#ifdef IDENTICAL_PAGES_AFTER_RECOVERY + { + uint number_of_records= (uint) buff[DIR_COUNT_OFFSET]; + uchar *dir= dir_entry_pos(buff, share->block_size, + number_of_records-1); + buff[DIR_FREE_OFFSET]= END_OF_DIR_FREE_LIST; + bzero(dir, number_of_records * DIR_ENTRY_SIZE); + } +#endif + + page_link.unlock= PAGECACHE_LOCK_WRITE_UNLOCK; + page_link.changed= 1; + push_dynamic(&info->pinned_pages, (void*) &page_link); + } + /** @todo leave bitmap lock to the bitmap code... */ + pthread_mutex_lock(&share->bitmap.bitmap_lock); + res= _ma_bitmap_reset_full_page_bits(info, &share->bitmap, page, 1); + pthread_mutex_unlock(&share->bitmap.bitmap_lock); + if (res) + goto err; + DBUG_RETURN(0); + +err: + _ma_mark_file_crashed(share); + DBUG_ASSERT(0); + DBUG_RETURN(1); +} + + +/** + @brief Apply LOGREC_REDO_INSERT_ROW_BLOBS + + @param info Maria handler + @parma lsn LSN to put on pages + @param header Header (with FILEID) + @param redo_lsn REDO record's LSN + @param[out] number_of_blobs Number of blobs found in log record + @param[out] number_of_ranges Number of ranges found + @param[out] first_page First page touched + @param[out] last_page Last page touched + + @note Write full pages (full head & blob pages) + + @return Operation status + @retval 0 OK + @retval !=0 Error +*/ + +uint _ma_apply_redo_insert_row_blobs(MARIA_HA *info, + LSN lsn, const uchar *header, + LSN redo_lsn, + uint * const number_of_blobs, + uint * const number_of_ranges, + pgcache_page_no_t * const first_page, + pgcache_page_no_t * const last_page) +{ + MARIA_SHARE *share= info->s; + const uchar *data; + uint data_size= FULL_PAGE_SIZE(share->block_size); + uint blob_count, ranges; + uint16 sid; + pgcache_page_no_t first_page2= ULONGLONG_MAX, last_page2= 0; + DBUG_ENTER("_ma_apply_redo_insert_row_blobs"); + + share->state.changed|= (STATE_CHANGED | STATE_NOT_ZEROFILLED | + STATE_NOT_MOVABLE); + + sid= fileid_korr(header); + header+= FILEID_STORE_SIZE; + *number_of_ranges= ranges= pagerange_korr(header); + header+= PAGERANGE_STORE_SIZE; + *number_of_blobs= blob_count= pagerange_korr(header); + header+= PAGERANGE_STORE_SIZE; + DBUG_ASSERT(ranges >= blob_count); + + data= (header + ranges * ROW_EXTENT_SIZE + + blob_count * (SUB_RANGE_SIZE + BLOCK_FILLER_SIZE)); + + while (blob_count--) + { + uint sub_ranges, empty_space; + + sub_ranges= uint2korr(header); + header+= SUB_RANGE_SIZE; + empty_space= uint2korr(header); + header+= BLOCK_FILLER_SIZE; + DBUG_ASSERT(sub_ranges <= ranges && empty_space < data_size); + ranges-= sub_ranges; + + while (sub_ranges--) + { + uint i; + uint res; + uint page_range; + pgcache_page_no_t page, start_page; + uchar *buff; + + start_page= page= page_korr(header); + header+= PAGE_STORE_SIZE; + page_range= pagerange_korr(header); + header+= PAGERANGE_STORE_SIZE; + + for (i= page_range; i-- > 0 ; page++) + { + MARIA_PINNED_PAGE page_link; + enum pagecache_page_lock unlock_method; + enum pagecache_page_pin unpin_method; + uint length; + + set_if_smaller(first_page2, page); + set_if_bigger(last_page2, page); + if (_ma_redo_not_needed_for_page(sid, redo_lsn, page, FALSE)) + continue; + + if (((page + 1) * share->block_size) > + share->state.state.data_file_length) + { + /* New page or half written page at end of file */ + DBUG_PRINT("info", ("Enlarging data file from %lu to %lu", + (ulong) share->state.state.data_file_length, + (ulong) ((page + 1 ) * share->block_size))); + share->state.state.data_file_length= (page + 1) * share->block_size; + buff= info->keyread_buff; + info->keyread_buff_used= 1; + make_empty_page(info, buff, BLOB_PAGE, 0); + unlock_method= PAGECACHE_LOCK_LEFT_UNLOCKED; + unpin_method= PAGECACHE_PIN_LEFT_UNPINNED; + } + else + { + share->pagecache->readwrite_flags&= ~MY_WME; + buff= pagecache_read(share->pagecache, + &info->dfile, + page, 0, 0, + PAGECACHE_PLAIN_PAGE, + PAGECACHE_LOCK_WRITE, &page_link.link); + share->pagecache->readwrite_flags= share->pagecache-> + org_readwrite_flags; + if (!buff) + { + if (my_errno != HA_ERR_FILE_TOO_SHORT && + my_errno != HA_ERR_WRONG_CRC) + { + /* If not read outside of file */ + pagecache_unlock_by_link(share->pagecache, page_link.link, + PAGECACHE_LOCK_WRITE_UNLOCK, + PAGECACHE_UNPIN, LSN_IMPOSSIBLE, + LSN_IMPOSSIBLE, 0, FALSE); + goto err; + } + /* + Physical file was too short, create new page. It can be that + recovery started with a file with N pages, wrote page N+2 into + pagecache (increased data_file_length but not physical file + length), now reads page N+1: the read fails. + */ + buff= pagecache_block_link_to_buffer(page_link.link); + make_empty_page(info, buff, BLOB_PAGE, 0); + } + else + { +#ifndef DBUG_OFF + uchar found_page_type= (buff[PAGE_TYPE_OFFSET] & PAGE_TYPE_MASK); +#endif + if (lsn_korr(buff) >= lsn) + { + /* Already applied */ + DBUG_PRINT("info", ("already applied %llu >= %llu", + lsn_korr(buff), lsn)); + pagecache_unlock_by_link(share->pagecache, page_link.link, + PAGECACHE_LOCK_WRITE_UNLOCK, + PAGECACHE_UNPIN, LSN_IMPOSSIBLE, + LSN_IMPOSSIBLE, 0, FALSE); + continue; + } + DBUG_ASSERT((found_page_type == (uchar) BLOB_PAGE) || + (found_page_type == (uchar) UNALLOCATED_PAGE)); + } + unlock_method= PAGECACHE_LOCK_WRITE_UNLOCK; + unpin_method= PAGECACHE_UNPIN; + } + + /* + Blob pages are never updated twice in same redo-undo chain, so + it's safe to update lsn for them here + */ + lsn_store(buff, lsn); + buff[PAGE_TYPE_OFFSET]= BLOB_PAGE; + + length= data_size; + if (i == 0 && sub_ranges == 0) + { + /* + Last page may be only partly filled. We zero the rest, like + write_full_pages() does. + */ + length-= empty_space; + bzero(buff + share->block_size - PAGE_SUFFIX_SIZE - empty_space, + empty_space); + } + memcpy(buff+ PAGE_TYPE_OFFSET + 1, data, length); + data+= length; + if (pagecache_write(share->pagecache, + &info->dfile, page, 0, + buff, PAGECACHE_PLAIN_PAGE, + unlock_method, unpin_method, + PAGECACHE_WRITE_DELAY, 0, LSN_IMPOSSIBLE)) + goto err; + } + /** @todo leave bitmap lock to the bitmap code... */ + pthread_mutex_lock(&share->bitmap.bitmap_lock); + res= _ma_bitmap_set_full_page_bits(info, &share->bitmap, start_page, + page_range); + pthread_mutex_unlock(&share->bitmap.bitmap_lock); + if (res) + goto err; + } + } + *first_page= first_page2; + *last_page= last_page2; + DBUG_RETURN(0); + +err: + _ma_mark_file_crashed(share); + DBUG_ASSERT(0); + DBUG_RETURN(1); +} + + +/**************************************************************************** + Applying of UNDO entries +****************************************************************************/ + +/** Execute undo of a row insert (delete the inserted row) */ + +my_bool _ma_apply_undo_row_insert(MARIA_HA *info, LSN undo_lsn, + const uchar *header) +{ + pgcache_page_no_t page; + uint rownr; + uchar *buff; + my_bool res= 1; + MARIA_PINNED_PAGE page_link; + MARIA_SHARE *share= info->s; + ha_checksum checksum; + LSN lsn; + DBUG_ENTER("_ma_apply_undo_row_insert"); + + page= page_korr(header); + header+= PAGE_STORE_SIZE; + rownr= dirpos_korr(header); + header+= DIRPOS_STORE_SIZE; + DBUG_PRINT("enter", ("rowid: %lu page: %lu rownr: %u", + (ulong) ma_recordpos(page, rownr), + (ulong) page, rownr)); + + buff= pagecache_read(share->pagecache, + &info->dfile, page, 0, + 0, share->page_type, + PAGECACHE_LOCK_WRITE, + &page_link.link); + page_link.unlock= PAGECACHE_LOCK_WRITE_UNLOCK; + page_link.changed= buff != 0; + push_dynamic(&info->pinned_pages, (void*) &page_link); + if (!buff) + goto err; + + if (read_row_extent_info(info, buff, rownr)) + goto err; + + _ma_bitmap_flushable(info, 1); + if (delete_head_or_tail(info, page, rownr, 1, 1) || + delete_tails(info, info->cur_row.tail_positions)) + goto err; + + if (info->cur_row.extents_count && free_full_pages(info, &info->cur_row)) + goto err; + + checksum= 0; + if (share->calc_checksum) + checksum= (ha_checksum) 0 - ha_checksum_korr(header); + info->last_auto_increment= ~ (ulonglong) 0; + if (_ma_write_clr(info, undo_lsn, LOGREC_UNDO_ROW_INSERT, + share->calc_checksum != 0, checksum, &lsn, (void*) 0)) + goto err; + + res= 0; +err: + if (info->non_flushable_state) + _ma_bitmap_flushable(info, -1); + _ma_unpin_all_pages_and_finalize_row(info, lsn); + DBUG_RETURN(res); +} + + +/** Execute undo of a row delete (insert the row back where it was) */ + +my_bool _ma_apply_undo_row_delete(MARIA_HA *info, LSN undo_lsn, + const uchar *header, size_t header_length + __attribute__((unused))) +{ + MARIA_SHARE *share= info->s; + MARIA_ROW row; + MARIA_COLUMNDEF *column, *end_column; + MARIA_BITMAP_BLOCKS *blocks; + struct st_row_pos_info row_pos; + uchar *record; + const uchar *null_bits, *field_length_data, *extent_info; + pgcache_page_no_t page; + ulong *blob_lengths; + uint *null_field_lengths, extent_count, rownr, length_on_head_page; + DBUG_ENTER("_ma_apply_undo_row_delete"); + + /* + Use cur row as a base; We need to make a copy as we will change + some buffers to point directly to 'header' + */ + memcpy(&row, &info->cur_row, sizeof(row)); + + page= page_korr(header); + header+= PAGE_STORE_SIZE; + rownr= dirpos_korr(header); + header+= DIRPOS_STORE_SIZE; + length_on_head_page= uint2korr(header); + header+= 2; + extent_count= pagerange_korr(header); + header+= PAGERANGE_STORE_SIZE; + DBUG_PRINT("enter", ("rowid: %lu page: %lu rownr: %u", + (ulong) ma_recordpos(page, rownr), + (ulong) page, rownr)); + + if (share->calc_checksum) + { + /* + We extract the checksum delta here, saving a recomputation in + allocate_and_write_block_record(). It's only an optimization. + */ + row.checksum= (ha_checksum) 0 - ha_checksum_korr(header); + header+= HA_CHECKSUM_STORE_SIZE; + } + extent_info= header; + header+= extent_count * ROW_EXTENT_SIZE; + + null_field_lengths= row.null_field_lengths; + blob_lengths= row.blob_lengths; + + /* + Fill in info->cur_row with information about the row, like in + calc_record_size(), to be used by write_block_record() + */ + + row.normal_length= row.char_length= row.varchar_length= + row.blob_length= row.extents_count= row.field_lengths_length= 0; + + null_bits= header; + header+= share->base.null_bytes; + /* This will not be changed */ + row.empty_bits= (uchar*) header; + header+= share->base.pack_bytes; + if (share->base.max_field_lengths) + { + row.field_lengths_length= uint2korr(header); + row.field_lengths= (uchar*) header + 2 ; + header+= 2 + row.field_lengths_length; + } + if (share->base.blobs) + row.blob_length= ma_get_length(&header); + + /* We need to build up a record (without blobs) in rec_buff */ + if (!(record= my_malloc(share->base.reclength, MYF(MY_WME)))) + DBUG_RETURN(1); + + memcpy(record, null_bits, share->base.null_bytes); + + /* Copy field information from header to record */ + + /* Handle constant length fields that are always present */ + for (column= share->columndef, + end_column= column+ share->base.fixed_not_null_fields; + column < end_column; + column++) + { + memcpy(record + column->offset, header, column->length); + header+= column->length; + } + + /* Handle NULL fields and CHAR/VARCHAR fields */ + field_length_data= row.field_lengths; + for (end_column= share->columndef + share->base.fields; + column < end_column; + column++, null_field_lengths++) + { + if ((record[column->null_pos] & column->null_bit) || + row.empty_bits[column->empty_pos] & column->empty_bit) + { + if (column->type != FIELD_BLOB) + *null_field_lengths= 0; + else + *blob_lengths++= 0; + if (share->calc_checksum) + bfill(record + column->offset, column->fill_length, + column->type == FIELD_SKIP_ENDSPACE ? ' ' : 0); + continue; + } + switch (column->type) { + case FIELD_CHECK: + case FIELD_NORMAL: /* Fixed length field */ + case FIELD_ZERO: + case FIELD_SKIP_PRESPACE: /* Not packed */ + case FIELD_SKIP_ZERO: /* Fixed length field */ + row.normal_length+= column->length; + *null_field_lengths= column->length; + memcpy(record + column->offset, header, column->length); + header+= column->length; + break; + case FIELD_SKIP_ENDSPACE: /* CHAR */ + { + uint length; + if (column->length <= 255) + length= (uint) *field_length_data++; + else + { + length= uint2korr(field_length_data); + field_length_data+= 2; + } + row.char_length+= length; + *null_field_lengths= length; + memcpy(record + column->offset, header, length); + if (share->calc_checksum) + bfill(record + column->offset + length, (column->length - length), + ' '); + header+= length; + break; + } + case FIELD_VARCHAR: + { + uint length; + uchar *field_pos= record + column->offset; + + /* 256 is correct as this includes the length uchar */ + if (column->fill_length == 1) + { + field_pos[0]= *field_length_data; + length= (uint) *field_length_data; + } + else + { + field_pos[0]= field_length_data[0]; + field_pos[1]= field_length_data[1]; + length= uint2korr(field_length_data); + } + field_length_data+= column->fill_length; + field_pos+= column->fill_length; + row.varchar_length+= length; + *null_field_lengths= length; + memcpy(field_pos, header, length); + header+= length; + break; + } + case FIELD_BLOB: + { + /* Copy length of blob and pointer to blob data to record */ + uchar *field_pos= record + column->offset; + uint size_length= column->length - portable_sizeof_char_ptr; + ulong blob_length= _ma_calc_blob_length(size_length, field_length_data); + + memcpy(field_pos, field_length_data, size_length); + field_length_data+= size_length; + memcpy(field_pos + size_length, &header, sizeof(&header)); + header+= blob_length; + *blob_lengths++= blob_length; + break; + } + default: + DBUG_ASSERT(0); + } + } + row.head_length= (info->row_base_length + + share->base.fixed_not_null_fields_length + + row.field_lengths_length + + size_to_store_key_length(row.field_lengths_length) + + row.normal_length + + row.char_length + row.varchar_length); + row.total_length= (row.head_length + row.blob_length); + if (row.total_length < share->base.min_block_length) + row.total_length= share->base.min_block_length; + + /* + Row is now generated. Now we need to insert record on the original + pages with original size on each page. + */ + + _ma_bitmap_flushable(info, 1); + /* Change extent information to be usable by write_block_record() */ + blocks= &row.insert_blocks; + if (extent_to_bitmap_blocks(info, blocks, page, extent_count, extent_info)) + goto err; + blocks->block->org_bitmap_value= _ma_bitmap_get_page_bits(info, + &share->bitmap, + page); + blocks->block->used|= BLOCKUSED_USE_ORG_BITMAP; + + /* Read head page and allocate data for rowid */ + if (get_rowpos_in_head_or_tail_page(info, blocks->block, + info->buff, + length_on_head_page, + HEAD_PAGE, PAGECACHE_LOCK_WRITE, + rownr, &row_pos)) + goto err; + + if (share->calc_checksum) + { + DBUG_ASSERT(row.checksum == (share->calc_checksum)(info, record)); + } + if (write_block_record(info, (uchar*) 0, record, &row, + blocks, blocks->block->org_bitmap_value != 0, + &row_pos, undo_lsn, 0)) + goto err; + + my_free(record, MYF(0)); + DBUG_RETURN(0); + +err: + if (info->non_flushable_state) + _ma_bitmap_flushable(info, -1); + _ma_unpin_all_pages_and_finalize_row(info, LSN_IMPOSSIBLE); + my_free(record, MYF(0)); + DBUG_RETURN(1); +} + + +/** + Execute undo of a row update + + @fn _ma_apply_undo_row_update() + + @return Operation status + @retval 0 OK + @retval 1 Error +*/ + +my_bool _ma_apply_undo_row_update(MARIA_HA *info, LSN undo_lsn, + const uchar *header, + size_t header_length + __attribute__((unused))) +{ + MARIA_SHARE *share= info->s; + MARIA_RECORD_POS record_pos; + const uchar *field_length_data, *field_length_data_end, *extent_info; + uchar *current_record, *orig_record; + pgcache_page_no_t page; + ha_checksum checksum_delta; + uint rownr, field_length_header, extent_count, length_on_head_page; + int error= 1; + DBUG_ENTER("_ma_apply_undo_row_update"); + LINT_INIT(checksum_delta); + + page= page_korr(header); + header+= PAGE_STORE_SIZE; + rownr= dirpos_korr(header); + header+= DIRPOS_STORE_SIZE; + record_pos= ma_recordpos(page, rownr); + DBUG_PRINT("enter", ("rowid: %lu page: %lu rownr: %u", + (ulong) record_pos, (ulong) page, rownr)); + + if (share->calc_checksum) + { + checksum_delta= ha_checksum_korr(header); + header+= HA_CHECKSUM_STORE_SIZE; + } + length_on_head_page= uint2korr(header); + header+= 2; + extent_count= pagerange_korr(header); + header+= PAGERANGE_STORE_SIZE; + extent_info= header; + header+= extent_count * ROW_EXTENT_SIZE; + + /* + Set header to point to old field values, generated by + fill_update_undo_parts() + */ + field_length_header= ma_get_length(&header); + field_length_data= (uchar*) header; + header+= field_length_header; + field_length_data_end= header; + + /* Allocate buffer for current row & original row */ + if (!(current_record= my_malloc(share->base.reclength * 2, MYF(MY_WME)))) + DBUG_RETURN(1); + orig_record= current_record+ share->base.reclength; + + /* Read current record */ + if (_ma_read_block_record(info, current_record, record_pos)) + goto err; + + if (*field_length_data == 255) + { + /* Bitmap changed */ + field_length_data++; + memcpy(orig_record, header, share->base.null_bytes); + header+= share->base.null_bytes; + } + else + memcpy(orig_record, current_record, share->base.null_bytes); + bitmap_clear_all(&info->changed_fields); + + while (field_length_data < field_length_data_end) + { + uint field_nr= ma_get_length(&field_length_data), field_length; + MARIA_COLUMNDEF *column= share->columndef + field_nr; + uchar *orig_field_pos= orig_record + column->offset; + + bitmap_set_bit(&info->changed_fields, field_nr); + if (field_nr >= share->base.fixed_not_null_fields) + { + if (!(field_length= ma_get_length(&field_length_data))) + { + /* Null field or empty field */ + bfill(orig_field_pos, column->fill_length, + column->type == FIELD_SKIP_ENDSPACE ? ' ' : 0); + continue; + } + } + else + field_length= column->length; + + switch (column->type) { + case FIELD_CHECK: + case FIELD_NORMAL: /* Fixed length field */ + case FIELD_ZERO: + case FIELD_SKIP_PRESPACE: /* Not packed */ + memcpy(orig_field_pos, header, column->length); + header+= column->length; + break; + case FIELD_SKIP_ZERO: /* Number */ + case FIELD_SKIP_ENDSPACE: /* CHAR */ + { + uint diff; + memcpy(orig_field_pos, header, field_length); + if ((diff= (column->length - field_length))) + bfill(orig_field_pos + column->length - diff, diff, + column->type == FIELD_SKIP_ENDSPACE ? ' ' : 0); + header+= field_length; + } + break; + case FIELD_VARCHAR: + if (column->length <= 256) + { + *orig_field_pos++= (uchar) field_length; + } + else + { + int2store(orig_field_pos, field_length); + orig_field_pos+= 2; + } + memcpy(orig_field_pos, header, field_length); + header+= field_length; + break; + case FIELD_BLOB: + { + uint size_length= column->length - portable_sizeof_char_ptr; + _ma_store_blob_length(orig_field_pos, size_length, field_length); + memcpy_fixed(orig_field_pos + size_length, &header, sizeof(header)); + header+= field_length; + break; + } + default: + DBUG_ASSERT(0); + } + } + copy_not_changed_fields(info, &info->changed_fields, + orig_record, current_record); + + if (share->calc_checksum) + { + info->new_row.checksum= checksum_delta + + (info->cur_row.checksum= (*share->calc_checksum)(info, orig_record)); + /* verify that record's content is sane */ + DBUG_ASSERT(info->new_row.checksum == + (*share->calc_checksum)(info, current_record)); + } + + info->last_auto_increment= ~ (ulonglong) 0; + /* Now records are up to date, execute the update to original values */ + if (_ma_update_at_original_place(info, page, rownr, length_on_head_page, + extent_count, extent_info, + current_record, orig_record, undo_lsn)) + goto err; + + error= 0; +err: + my_free(current_record, MYF(0)); + DBUG_RETURN(error); +} + + +/** + Execute undo of a bulk insert which used repair + + @return Operation status + @retval 0 OK + @retval 1 Error +*/ + +my_bool _ma_apply_undo_bulk_insert(MARIA_HA *info, LSN undo_lsn) +{ + my_bool error; + LSN lsn; + DBUG_ENTER("_ma_apply_undo_bulk_insert"); + /* + We delete all rows, re-enable indices as bulk insert had disabled + non-unique ones. + */ + error= (maria_delete_all_rows(info) || + maria_enable_indexes(info) || + /* we enabled indices so need '2' below */ + _ma_state_info_write(info->s, + MA_STATE_INFO_WRITE_DONT_MOVE_OFFSET | + MA_STATE_INFO_WRITE_FULL_INFO | + MA_STATE_INFO_WRITE_LOCK) || + _ma_write_clr(info, undo_lsn, LOGREC_UNDO_BULK_INSERT, + FALSE, 0, &lsn, NULL)); + DBUG_RETURN(error); +} + + +/** + @brief Get the TRANSLOG_ADDRESS to flush up to + + @param page Page's content + @param page_no Page's number (<offset>/<page length>) + @param data_ptr Callback data pointer (pointer to MARIA_SHARE) + + @note + Usable for data (non-bitmap) and index pages + + @retval LSN to flush up to +*/ + +TRANSLOG_ADDRESS +maria_page_get_lsn(uchar *page, + pgcache_page_no_t page_no __attribute__((unused)), + uchar* data_ptr __attribute__((unused))) +{ +#ifndef DBUG_OFF + const MARIA_SHARE *share= (MARIA_SHARE*)data_ptr; + DBUG_ASSERT(share->page_type == PAGECACHE_LSN_PAGE && + share->now_transactional); +#endif + return lsn_korr(page); +} + + +/** + @brief Enable reading of all rows, ignoring versioning + + @note + This is mainly useful in single user applications, like maria_pack, + where we want to be able to read all rows without having to read the + transaction id from the control file +*/ + +void maria_ignore_trids(MARIA_HA *info) +{ + if (info->s->base.born_transactional) + { + if (!info->trn) + _ma_set_trn_for_table(info, &dummy_transaction_object); + /* Ignore transaction id when row is read */ + info->trn->min_read_from= ~(TrID) 0; + } +} diff --git a/storage/maria/ma_blockrec.h b/storage/maria/ma_blockrec.h new file mode 100644 index 00000000000..cb682eef701 --- /dev/null +++ b/storage/maria/ma_blockrec.h @@ -0,0 +1,285 @@ +/* Copyright (C) 2007 Michael Widenius + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You 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 */ + +/* + Storage of records in block +*/ + +#define LSN_SIZE 7 +#define DIR_COUNT_SIZE 1 /* Stores number of rows on page */ +#define DIR_FREE_SIZE 1 /* Pointer to first free dir entry */ +#define EMPTY_SPACE_SIZE 2 /* Stores empty space on page */ +#define PAGE_TYPE_SIZE 1 +#define PAGE_SUFFIX_SIZE 4 /* Bytes for checksum */ +#define PAGE_HEADER_SIZE (LSN_SIZE + DIR_COUNT_SIZE + DIR_FREE_SIZE +\ + EMPTY_SPACE_SIZE + PAGE_TYPE_SIZE) +#define PAGE_OVERHEAD_SIZE (PAGE_HEADER_SIZE + DIR_ENTRY_SIZE + \ + PAGE_SUFFIX_SIZE) +#define BLOCK_RECORD_POINTER_SIZE 6 + +#define FULL_PAGE_SIZE(block_size) ((block_size) - LSN_SIZE - \ + PAGE_TYPE_SIZE - PAGE_SUFFIX_SIZE) + +#define ROW_EXTENT_PAGE_SIZE 5 +#define ROW_EXTENT_COUNT_SIZE 2 +#define SUB_RANGE_SIZE 2 +#define BLOCK_FILLER_SIZE 2 +#define ROW_EXTENT_SIZE (ROW_EXTENT_PAGE_SIZE + ROW_EXTENT_COUNT_SIZE) +#define TAIL_BIT 0x8000 /* Bit in page_count to signify tail */ +#define START_EXTENT_BIT 0x4000 /* Bit in page_count to signify start*/ +/* page_count set by bitmap code for tail pages */ +#define TAIL_PAGE_COUNT_MARKER 0xffff +/* Number of extents reserved MARIA_BITMAP_BLOCKS to store head part */ +#define ELEMENTS_RESERVED_FOR_MAIN_PART 4 +/* This is just used to prealloc a dynamic array */ +#define AVERAGE_BLOB_SIZE 1024L*1024L +/* Number of pages to store continuous blob parts */ +#define BLOB_SEGMENT_MIN_SIZE 128 + +/* Fields before 'row->null_field_lengths' used by find_where_to_split_row */ +#define EXTRA_LENGTH_FIELDS 3 + +/* Size for the different parts in the row header (and head page) */ +#define FLAG_SIZE 1 +#define VERPTR_SIZE 7 +#define DIR_ENTRY_SIZE 4 +#define FIELD_OFFSET_SIZE 2 /* size of pointers to field starts */ + +/* Minimum header size needed for a new row */ +#define BASE_ROW_HEADER_SIZE FLAG_SIZE +#define TRANS_ROW_EXTRA_HEADER_SIZE TRANSID_SIZE + +#define PAGE_TYPE_MASK 7 +enum en_page_type { UNALLOCATED_PAGE, HEAD_PAGE, TAIL_PAGE, BLOB_PAGE, MAX_PAGE_TYPE }; +#define PAGE_CAN_BE_COMPACTED 128 /* Bit in PAGE_TYPE */ + +#define PAGE_TYPE_OFFSET LSN_SIZE +#define DIR_COUNT_OFFSET (LSN_SIZE+PAGE_TYPE_SIZE) +#define DIR_FREE_OFFSET (DIR_COUNT_OFFSET+DIR_COUNT_SIZE) +#define EMPTY_SPACE_OFFSET (DIR_FREE_OFFSET+DIR_FREE_SIZE) + +/* Bits used for flag uchar (one byte, first in record) */ +#define ROW_FLAG_TRANSID 1 +#define ROW_FLAG_VER_PTR 2 +#define ROW_FLAG_DELETE_TRANSID 4 +#define ROW_FLAG_NULLS_EXTENDED 8 +#define ROW_FLAG_EXTENTS 128 +#define ROW_FLAG_ALL (1+2+4+8+128) + +/******** Variables that affects how data pages are utilized ********/ + +/* Minium size of tail segment */ +#define MIN_TAIL_SIZE 32 + +/* + Fixed length part of Max possible header size; See row data structure + table in ma_blockrec.c. +*/ +#define MAX_FIXED_HEADER_SIZE (FLAG_SIZE + 3 + ROW_EXTENT_SIZE + 3) +#define TRANS_MAX_FIXED_HEADER_SIZE (MAX_FIXED_HEADER_SIZE + \ + TRANSID_SIZE + VERPTR_SIZE + \ + TRANSID_SIZE) + +/* We use 1 uchar in record header to store number of directory entries */ +#define MAX_ROWS_PER_PAGE 255 +#define END_OF_DIR_FREE_LIST ((uchar) 255) + +/* Bits for MARIA_BITMAP_BLOCKS->used */ +/* We stored data on disk in the block */ +#define BLOCKUSED_USED 1 +/* Bitmap on disk is block->org_bitmap_value ; Happens only on update */ +#define BLOCKUSED_USE_ORG_BITMAP 2 +/* We stored tail data on disk for the block */ +#define BLOCKUSED_TAIL 4 + +/******* defines that affects allocation (density) of data *******/ + +/* + If the tail part (from the main block or a blob) would use more than 75 % of + the size of page, store the tail on a full page instead of a shared + tail page. +*/ +#define MAX_TAIL_SIZE(block_size) ((block_size) *3 / 4) + +/* Don't allocate memory for too many row extents on the stack */ +#define ROW_EXTENTS_ON_STACK 32 + +/* Functions to convert MARIA_RECORD_POS to/from page:offset */ + +static inline MARIA_RECORD_POS ma_recordpos(pgcache_page_no_t page, + uint dir_entry) +{ + DBUG_ASSERT(dir_entry <= 255); + DBUG_ASSERT(page > 0); /* page 0 is bitmap, not data page */ + return (MARIA_RECORD_POS) (((ulonglong) page << 8) | dir_entry); +} + +static inline pgcache_page_no_t ma_recordpos_to_page(MARIA_RECORD_POS record_pos) +{ + return (pgcache_page_no_t) (record_pos >> 8); +} + +static inline uint ma_recordpos_to_dir_entry(MARIA_RECORD_POS record_pos) +{ + return (uint) (record_pos & 255); +} + +static inline uchar *dir_entry_pos(uchar *buff, uint block_size, uint pos) +{ + return (buff + block_size - DIR_ENTRY_SIZE * pos - PAGE_SUFFIX_SIZE - + DIR_ENTRY_SIZE); +} + +/* ma_blockrec.c */ +void _ma_init_block_record_data(void); +my_bool _ma_once_init_block_record(MARIA_SHARE *share, File dfile); +my_bool _ma_once_end_block_record(MARIA_SHARE *share); +my_bool _ma_init_block_record(MARIA_HA *info); +void _ma_end_block_record(MARIA_HA *info); + +my_bool _ma_update_block_record(MARIA_HA *info, MARIA_RECORD_POS pos, + const uchar *oldrec, const uchar *newrec); +my_bool _ma_delete_block_record(MARIA_HA *info, const uchar *record); +int _ma_read_block_record(MARIA_HA *info, uchar *record, + MARIA_RECORD_POS record_pos); +int _ma_read_block_record2(MARIA_HA *info, uchar *record, + uchar *data, uchar *end_of_data); +int _ma_scan_block_record(MARIA_HA *info, uchar *record, + MARIA_RECORD_POS, my_bool); +my_bool _ma_cmp_block_unique(MARIA_HA *info, MARIA_UNIQUEDEF *def, + const uchar *record, MARIA_RECORD_POS pos); +my_bool _ma_scan_init_block_record(MARIA_HA *info); +void _ma_scan_end_block_record(MARIA_HA *info); +int _ma_scan_remember_block_record(MARIA_HA *info, + MARIA_RECORD_POS *lastpos); +void _ma_scan_restore_block_record(MARIA_HA *info, + MARIA_RECORD_POS lastpos); + +MARIA_RECORD_POS _ma_write_init_block_record(MARIA_HA *info, + const uchar *record); +my_bool _ma_write_block_record(MARIA_HA *info, const uchar *record); +my_bool _ma_write_abort_block_record(MARIA_HA *info); +my_bool _ma_compare_block_record(register MARIA_HA *info, + register const uchar *record); +void _ma_compact_block_page(uchar *buff, uint block_size, uint rownr, + my_bool extend_block, TrID min_read_from, + uint min_row_length); +TRANSLOG_ADDRESS +maria_page_get_lsn(uchar *page, pgcache_page_no_t page_no, uchar* data_ptr); + +/* ma_bitmap.c */ +my_bool _ma_bitmap_init(MARIA_SHARE *share, File file); +my_bool _ma_bitmap_end(MARIA_SHARE *share); +my_bool _ma_bitmap_flush(MARIA_SHARE *share); +my_bool _ma_bitmap_flush_all(MARIA_SHARE *share); +void _ma_bitmap_reset_cache(MARIA_SHARE *share); +my_bool _ma_bitmap_find_place(MARIA_HA *info, MARIA_ROW *row, + MARIA_BITMAP_BLOCKS *result_blocks); +my_bool _ma_bitmap_release_unused(MARIA_HA *info, MARIA_BITMAP_BLOCKS *blocks); +my_bool _ma_bitmap_free_full_pages(MARIA_HA *info, const uchar *extents, + uint count); +my_bool _ma_bitmap_set(MARIA_HA *info, pgcache_page_no_t pos, my_bool head, + uint empty_space); +my_bool _ma_bitmap_reset_full_page_bits(MARIA_HA *info, + MARIA_FILE_BITMAP *bitmap, + pgcache_page_no_t page, + uint page_count); +my_bool _ma_bitmap_set_full_page_bits(MARIA_HA *info, + MARIA_FILE_BITMAP *bitmap, + pgcache_page_no_t page, uint page_count); +uint _ma_free_size_to_head_pattern(MARIA_FILE_BITMAP *bitmap, uint size); +my_bool _ma_bitmap_find_new_place(MARIA_HA *info, MARIA_ROW *new_row, + pgcache_page_no_t page, uint free_size, + MARIA_BITMAP_BLOCKS *result_blocks); +my_bool _ma_check_bitmap_data(MARIA_HA *info, + enum en_page_type page_type, + pgcache_page_no_t page, + uint empty_space, uint *bitmap_pattern); +my_bool _ma_check_if_right_bitmap_type(MARIA_HA *info, + enum en_page_type page_type, + pgcache_page_no_t page, + uint *bitmap_pattern); +uint _ma_bitmap_get_page_bits(MARIA_HA *info, MARIA_FILE_BITMAP *bitmap, + pgcache_page_no_t page); +void _ma_bitmap_delete_all(MARIA_SHARE *share); +int _ma_bitmap_create_first(MARIA_SHARE *share); +void _ma_bitmap_flushable(MARIA_HA *info, int non_flushable_inc); +void _ma_bitmap_set_pagecache_callbacks(PAGECACHE_FILE *file, + MARIA_SHARE *share); +#ifndef DBUG_OFF +void _ma_print_bitmap(MARIA_FILE_BITMAP *bitmap, uchar *data, + pgcache_page_no_t page); +#endif + +uint _ma_apply_redo_insert_row_head_or_tail(MARIA_HA *info, LSN lsn, + uint page_type, + my_bool new_page, + const uchar *header, + const uchar *data, + size_t data_length); +uint _ma_apply_redo_purge_row_head_or_tail(MARIA_HA *info, LSN lsn, + uint page_type, + const uchar *header); +uint _ma_apply_redo_free_blocks(MARIA_HA *info, LSN lsn, + const uchar *header); +uint _ma_apply_redo_free_head_or_tail(MARIA_HA *info, LSN lsn, + const uchar *header); +uint _ma_apply_redo_insert_row_blobs(MARIA_HA *info, LSN lsn, + const uchar *header, LSN redo_lsn, + uint * const number_of_blobs, + uint * const number_of_ranges, + pgcache_page_no_t * const first_page, + pgcache_page_no_t * const last_page); +my_bool _ma_apply_redo_bitmap_new_page(MARIA_HA *info, LSN lsn, + const uchar *header); +my_bool _ma_apply_undo_row_insert(MARIA_HA *info, LSN undo_lsn, + const uchar *header); +my_bool _ma_apply_undo_row_delete(MARIA_HA *info, LSN undo_lsn, + const uchar *header, size_t length); +my_bool _ma_apply_undo_row_update(MARIA_HA *info, LSN undo_lsn, + const uchar *header, size_t length); +my_bool _ma_apply_undo_bulk_insert(MARIA_HA *info, LSN undo_lsn); + +my_bool write_hook_for_redo(enum translog_record_type type, + TRN *trn, MARIA_HA *tbl_info, LSN *lsn, + void *hook_arg); +my_bool write_hook_for_undo(enum translog_record_type type, + TRN *trn, MARIA_HA *tbl_info, LSN *lsn, + void *hook_arg); +my_bool write_hook_for_redo_delete_all(enum translog_record_type type, + TRN *trn, MARIA_HA *tbl_info, + LSN *lsn, void *hook_arg); +my_bool write_hook_for_undo_row_insert(enum translog_record_type type, + TRN *trn, MARIA_HA *tbl_info, + LSN *lsn, void *hook_arg); +my_bool write_hook_for_undo_row_delete(enum translog_record_type type, + TRN *trn, MARIA_HA *tbl_info, + LSN *lsn, void *hook_arg); +my_bool write_hook_for_undo_row_update(enum translog_record_type type, + TRN *trn, MARIA_HA *tbl_info, + LSN *lsn, void *hook_arg); +my_bool write_hook_for_undo_bulk_insert(enum translog_record_type type, + TRN *trn, MARIA_HA *tbl_info, + LSN *lsn, void *hook_arg); +my_bool write_hook_for_file_id(enum translog_record_type type, + TRN *trn, MARIA_HA *tbl_info, LSN *lsn, + void *hook_arg); +my_bool write_hook_for_commit(enum translog_record_type type, + TRN *trn, MARIA_HA *tbl_info, LSN *lsn, + void *hook_arg); +void _ma_block_get_status(void* param, my_bool concurrent_insert); +void _ma_block_update_status(void *param); +void _ma_block_restore_status(void *param); +my_bool _ma_block_check_status(void *param); diff --git a/storage/maria/ma_cache.c b/storage/maria/ma_cache.c new file mode 100644 index 00000000000..82b5ddd8047 --- /dev/null +++ b/storage/maria/ma_cache.c @@ -0,0 +1,107 @@ +/* Copyright (C) 2006 MySQL AB & MySQL Finland AB & TCX DataKonsult 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 */ + +/* + Functions for read record cacheing with maria + Used for reading dynamic/compressed records from datafile. + + Can fetch data directly from file (outside cache), + if reading a small chunk straight before the cached part (with possible + overlap). + + Can be explicitly asked not to use cache (by not setting READING_NEXT in + flag) - useful for occasional out-of-cache reads, when the next read is + expected to hit the cache again. + + Allows "partial read" errors in the record header (when READING_HEADER flag + is set) - unread part is bzero'ed + + Note: out-of-cache reads are enabled for shared IO_CACHE's too, + as these reads will be cached by OS cache (and my_pread is always atomic) +*/ + + +#include "maria_def.h" + +my_bool _ma_read_cache(IO_CACHE *info, uchar *buff, my_off_t pos, + size_t length, uint flag) +{ + size_t read_length,in_buff_length; + my_off_t offset; + uchar *in_buff_pos; + DBUG_ENTER("_ma_read_cache"); + + if (pos < info->pos_in_file) + { + read_length=length; + if ((my_off_t) read_length > (my_off_t) (info->pos_in_file-pos)) + read_length=(uint) (info->pos_in_file-pos); + info->seek_not_done=1; + if (my_pread(info->file,buff,read_length,pos,MYF(MY_NABP))) + DBUG_RETURN(1); + if (!(length-=read_length)) + DBUG_RETURN(0); + pos+=read_length; + buff+=read_length; + } + if (pos >= info->pos_in_file && + (offset= (my_off_t) (pos - info->pos_in_file)) < + (my_off_t) (info->read_end - info->request_pos)) + { + in_buff_pos=info->request_pos+(uint) offset; + in_buff_length= min(length,(size_t) (info->read_end-in_buff_pos)); + memcpy(buff,info->request_pos+(uint) offset,(size_t) in_buff_length); + if (!(length-=in_buff_length)) + DBUG_RETURN(0); + pos+=in_buff_length; + buff+=in_buff_length; + } + else + in_buff_length=0; + if (flag & READING_NEXT) + { + if (pos != (info->pos_in_file + + (uint) (info->read_end - info->request_pos))) + { + info->pos_in_file=pos; /* Force start here */ + info->read_pos=info->read_end=info->request_pos; /* Everything used */ + info->seek_not_done=1; + } + else + info->read_pos=info->read_end; /* All block used */ + if (!(*info->read_function)(info,buff,length)) + DBUG_RETURN(0); + read_length=info->error; + } + else + { + info->seek_not_done=1; + if ((read_length=my_pread(info->file,buff,length,pos,MYF(0))) == length) + DBUG_RETURN(0); + } + if (!(flag & READING_HEADER) || (int) read_length == -1 || + read_length+in_buff_length < 3) + { + DBUG_PRINT("error", + ("Error %d reading next-multi-part block (Got %d bytes)", + my_errno, (int) read_length)); + if (!my_errno || my_errno == HA_ERR_FILE_TOO_SHORT) + my_errno= HA_ERR_WRONG_IN_RECORD; + DBUG_RETURN(1); + } + bzero(buff+read_length,MARIA_BLOCK_INFO_HEADER_LENGTH - in_buff_length - + read_length); + DBUG_RETURN(0); +} /* _ma_read_cache */ diff --git a/storage/maria/ma_changed.c b/storage/maria/ma_changed.c new file mode 100644 index 00000000000..4d0964581f6 --- /dev/null +++ b/storage/maria/ma_changed.c @@ -0,0 +1,33 @@ +/* Copyright (C) 2006 MySQL AB & MySQL Finland AB & TCX DataKonsult 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 */ + +/* Check if somebody has changed table since last check. */ + +#include "maria_def.h" + + /* Return 0 if table isn't changed */ + +int maria_is_changed(MARIA_HA *info) +{ + int result; + DBUG_ENTER("maria_is_changed"); + if (fast_ma_readinfo(info)) + DBUG_RETURN(-1); + VOID(_ma_writeinfo(info,0)); + result=(int) info->data_changed; + info->data_changed=0; + DBUG_PRINT("exit",("result: %d",result)); + DBUG_RETURN(result); +} diff --git a/storage/maria/ma_check.c b/storage/maria/ma_check.c new file mode 100644 index 00000000000..e33849bef04 --- /dev/null +++ b/storage/maria/ma_check.c @@ -0,0 +1,6752 @@ +/* Copyright (C) 2006 MySQL AB & MySQL Finland AB & TCX DataKonsult 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 */ + +/* Describe, check and repair of MARIA tables */ + +/* + About checksum calculation. + + There are two types of checksums. Table checksum and row checksum. + + Row checksum is an additional uchar at the end of dynamic length + records. It must be calculated if the table is configured for them. + Otherwise they must not be used. The variable + MYISAM_SHARE::calc_checksum determines if row checksums are used. + MI_INFO::checksum is used as temporary storage during row handling. + For parallel repair we must assure that only one thread can use this + variable. There is no problem on the write side as this is done by one + thread only. But when checking a record after read this could go + wrong. But since all threads read through a common read buffer, it is + sufficient if only one thread checks it. + + Table checksum is an eight uchar value in the header of the index file. + It can be calculated even if row checksums are not used. The variable + MI_CHECK::glob_crc is calculated over all records. + MI_SORT_PARAM::calc_checksum determines if this should be done. This + variable is not part of MI_CHECK because it must be set per thread for + parallel repair. The global glob_crc must be changed by one thread + only. And it is sufficient to calculate the checksum once only. +*/ + +#include "ma_ftdefs.h" +#include "ma_rt_index.h" +#include "ma_blockrec.h" +#include "trnman.h" +#include "ma_key_recover.h" + +#include <stdarg.h> +#include <my_getopt.h> +#ifdef HAVE_SYS_VADVISE_H +#include <sys/vadvise.h> +#endif +#ifdef HAVE_SYS_MMAN_H +#include <sys/mman.h> +#endif + +/* Functions defined in this file */ + +static int check_k_link(HA_CHECK *param, MARIA_HA *info, my_off_t next_link); +static int chk_index(HA_CHECK *param, MARIA_HA *info, MARIA_KEYDEF *keyinfo, + MARIA_PAGE *page, ha_rows *keys, + ha_checksum *key_checksum, uint level); +static uint isam_key_length(MARIA_HA *info,MARIA_KEYDEF *keyinfo); +static ha_checksum calc_checksum(ha_rows count); +static int writekeys(MARIA_SORT_PARAM *sort_param); +static int sort_one_index(HA_CHECK *param, MARIA_HA *info, + MARIA_KEYDEF *keyinfo, + my_off_t pagepos, File new_file); +static int sort_key_read(MARIA_SORT_PARAM *sort_param, uchar *key); +static int sort_maria_ft_key_read(MARIA_SORT_PARAM *sort_param, uchar *key); +static int sort_get_next_record(MARIA_SORT_PARAM *sort_param); +static int sort_key_cmp(MARIA_SORT_PARAM *sort_param, const void *a, + const void *b); +static int sort_maria_ft_key_write(MARIA_SORT_PARAM *sort_param, + const uchar *a); +static int sort_key_write(MARIA_SORT_PARAM *sort_param, const uchar *a); +static my_off_t get_record_for_key(MARIA_KEYDEF *keyinfo, const uchar *key); +static int sort_insert_key(MARIA_SORT_PARAM *sort_param, + reg1 SORT_KEY_BLOCKS *key_block, + const uchar *key, my_off_t prev_block); +static int sort_delete_record(MARIA_SORT_PARAM *sort_param); +/*static int _ma_flush_pending_blocks(HA_CHECK *param);*/ +static SORT_KEY_BLOCKS *alloc_key_blocks(HA_CHECK *param, uint blocks, + uint buffer_length); +static ha_checksum maria_byte_checksum(const uchar *buf, uint length); +static void set_data_file_type(MARIA_SORT_INFO *sort_info, MARIA_SHARE *share); +static void restore_data_file_type(MARIA_SHARE *share); +static void change_data_file_descriptor(MARIA_HA *info, File new_file); +static void unuse_data_file_descriptor(MARIA_HA *info); +static int _ma_safe_scan_block_record(MARIA_SORT_INFO *sort_info, + MARIA_HA *info, uchar *record); +static void copy_data_file_state(MARIA_STATE_INFO *to, + MARIA_STATE_INFO *from); +static void report_keypage_fault(HA_CHECK *param, MARIA_HA *info, + my_off_t position); +static my_bool create_new_data_handle(MARIA_SORT_PARAM *param, File new_file); +static my_bool _ma_flush_table_files_before_swap(HA_CHECK *param, + MARIA_HA *info); +static TrID max_trid_in_system(void); +static void _ma_check_print_not_visible_error(HA_CHECK *param, TrID used_trid); +void retry_if_quick(MARIA_SORT_PARAM *param, int error); + + +/* Initialize check param with default values */ + +void maria_chk_init(HA_CHECK *param) +{ + bzero((uchar*) param,sizeof(*param)); + param->opt_follow_links=1; + param->keys_in_use= ~(ulonglong) 0; + param->search_after_block=HA_OFFSET_ERROR; + param->auto_increment_value= 0; + param->use_buffers=USE_BUFFER_INIT; + param->read_buffer_length=READ_BUFFER_INIT; + param->write_buffer_length=READ_BUFFER_INIT; + param->sort_buffer_length=SORT_BUFFER_INIT; + param->sort_key_blocks=BUFFERS_WHEN_SORTING; + param->tmpfile_createflag=O_RDWR | O_TRUNC | O_EXCL; + param->myf_rw=MYF(MY_NABP | MY_WME | MY_WAIT_IF_FULL); + param->start_check_pos=0; + param->max_record_length= LONGLONG_MAX; + param->pagecache_block_size= KEY_CACHE_BLOCK_SIZE; + param->stats_method= MI_STATS_METHOD_NULLS_NOT_EQUAL; +} + + +/* Initialize check param and maria handler for check of table */ + +void maria_chk_init_for_check(HA_CHECK *param, MARIA_HA *info) +{ + param->not_visible_rows_found= 0; + param->max_found_trid= 0; + + /* + Set up transaction handler so that we can see all rows. When rows is read + we will check the found id against param->max_tried + */ + if (!ma_control_file_inited()) + param->max_trid= 0; /* Give warning for first trid found */ + else + param->max_trid= max_trid_in_system(); + + maria_ignore_trids(info); +} + + + /* Check the status flags for the table */ + +int maria_chk_status(HA_CHECK *param, MARIA_HA *info) +{ + MARIA_SHARE *share= info->s; + + if (maria_is_crashed_on_repair(info)) + _ma_check_print_warning(param, + "Table is marked as crashed and last repair failed"); + else if (maria_is_crashed(info)) + _ma_check_print_warning(param, + "Table is marked as crashed"); + if (share->state.open_count != (uint) (share->global_changed ? 1 : 0)) + { + /* Don't count this as a real warning, as check can correct this ! */ + uint save=param->warning_printed; + _ma_check_print_warning(param, + share->state.open_count==1 ? + "%d client is using or hasn't closed the table properly" : + "%d clients are using or haven't closed the table properly", + share->state.open_count); + /* If this will be fixed by the check, forget the warning */ + if (param->testflag & T_UPDATE_STATE) + param->warning_printed=save; + } + return 0; +} + +/* + Check delete links in row data +*/ + +int maria_chk_del(HA_CHECK *param, register MARIA_HA *info, + ulonglong test_flag) +{ + MARIA_SHARE *share= info->s; + reg2 ha_rows i; + uint delete_link_length; + my_off_t empty,next_link,old_link; + char buff[22],buff2[22]; + DBUG_ENTER("maria_chk_del"); + + LINT_INIT(old_link); + + param->record_checksum=0; + + if (share->data_file_type == BLOCK_RECORD) + DBUG_RETURN(0); /* No delete links here */ + + delete_link_length=((share->options & HA_OPTION_PACK_RECORD) ? 20 : + share->rec_reflength+1); + + if (!(test_flag & T_SILENT)) + puts("- check record delete-chain"); + + next_link=share->state.dellink; + if (share->state.state.del == 0) + { + if (test_flag & T_VERBOSE) + { + puts("No recordlinks"); + } + } + else + { + if (test_flag & T_VERBOSE) + printf("Recordlinks: "); + empty=0; + for (i= share->state.state.del ; i > 0L && next_link != HA_OFFSET_ERROR ; i--) + { + if (*_ma_killed_ptr(param)) + DBUG_RETURN(1); + if (test_flag & T_VERBOSE) + printf(" %9s",llstr(next_link,buff)); + if (next_link >= share->state.state.data_file_length) + goto wrong; + if (my_pread(info->dfile.file, (uchar*) buff, delete_link_length, + next_link,MYF(MY_NABP))) + { + if (test_flag & T_VERBOSE) puts(""); + _ma_check_print_error(param,"Can't read delete-link at filepos: %s", + llstr(next_link,buff)); + DBUG_RETURN(1); + } + if (*buff != '\0') + { + if (test_flag & T_VERBOSE) puts(""); + _ma_check_print_error(param,"Record at pos: %s is not remove-marked", + llstr(next_link,buff)); + goto wrong; + } + if (share->options & HA_OPTION_PACK_RECORD) + { + my_off_t prev_link=mi_sizekorr(buff+12); + if (empty && prev_link != old_link) + { + if (test_flag & T_VERBOSE) puts(""); + _ma_check_print_error(param,"Deleted block at %s doesn't point back at previous delete link",llstr(next_link,buff2)); + goto wrong; + } + old_link=next_link; + next_link=mi_sizekorr(buff+4); + empty+=mi_uint3korr(buff+1); + } + else + { + param->record_checksum+=(ha_checksum) next_link; + next_link= _ma_rec_pos(share, (uchar *) buff + 1); + empty+=share->base.pack_reclength; + } + } + if (share->state.state.del && (test_flag & T_VERBOSE)) + puts("\n"); + if (empty != share->state.state.empty) + { + _ma_check_print_warning(param, + "Found %s deleted space in delete link chain. Should be %s", + llstr(empty,buff2), + llstr(share->state.state.empty,buff)); + } + if (next_link != HA_OFFSET_ERROR) + { + _ma_check_print_error(param, + "Found more than the expected %s deleted rows in delete link chain", + llstr(share->state.state.del, buff)); + goto wrong; + } + if (i != 0) + { + _ma_check_print_error(param, + "Found %s deleted rows in delete link chain. Should be %s", + llstr(share->state.state.del - i, buff2), + llstr(share->state.state.del, buff)); + goto wrong; + } + } + DBUG_RETURN(0); + +wrong: + param->testflag|=T_RETRY_WITHOUT_QUICK; + if (test_flag & T_VERBOSE) + puts(""); + _ma_check_print_error(param,"record delete-link-chain corrupted"); + DBUG_RETURN(1); +} /* maria_chk_del */ + + +/* Check delete links in index file */ + +static int check_k_link(HA_CHECK *param, register MARIA_HA *info, + my_off_t next_link) +{ + MARIA_SHARE *share= info->s; + uint block_size= share->block_size; + ha_rows records; + char llbuff[21], llbuff2[21]; + uchar *buff; + DBUG_ENTER("check_k_link"); + + if (next_link == HA_OFFSET_ERROR) + DBUG_RETURN(0); /* Avoid printing empty line */ + + records= (ha_rows) (share->state.state.key_file_length / block_size); + while (next_link != HA_OFFSET_ERROR && records > 0) + { + if (*_ma_killed_ptr(param)) + DBUG_RETURN(1); + if (param->testflag & T_VERBOSE) + printf("%16s",llstr(next_link,llbuff)); + + /* Key blocks must lay within the key file length entirely. */ + if (next_link + block_size > share->state.state.key_file_length) + { + /* purecov: begin tested */ + _ma_check_print_error(param, "Invalid key block position: %s " + "key block size: %u file_length: %s", + llstr(next_link, llbuff), block_size, + llstr(share->state.state.key_file_length, llbuff2)); + DBUG_RETURN(1); + /* purecov: end */ + } + + /* Key blocks must be aligned at block_size */ + if (next_link & (block_size -1)) + { + /* purecov: begin tested */ + _ma_check_print_error(param, "Mis-aligned key block: %s " + "minimum key block length: %u", + llstr(next_link, llbuff), + block_size); + DBUG_RETURN(1); + /* purecov: end */ + } + + DBUG_ASSERT(share->pagecache->block_size == block_size); + if (!(buff= pagecache_read(share->pagecache, + &share->kfile, + (pgcache_page_no_t) (next_link / block_size), + DFLT_INIT_HITS, + info->buff, PAGECACHE_READ_UNKNOWN_PAGE, + PAGECACHE_LOCK_LEFT_UNLOCKED, 0))) + { + /* purecov: begin tested */ + _ma_check_print_error(param, "key cache read error for block: %s", + llstr(next_link,llbuff)); + DBUG_RETURN(1); + /* purecov: end */ + } + if (_ma_get_keynr(info->s, buff) != MARIA_DELETE_KEY_NR) + _ma_check_print_error(param, "Page at %s is not delete marked", + llstr(next_link, llbuff)); + + next_link= mi_sizekorr(buff + share->keypage_header); + records--; + param->key_file_blocks+=block_size; + } + if (param->testflag & T_VERBOSE) + { + if (next_link != HA_OFFSET_ERROR) + printf("%16s\n",llstr(next_link,llbuff)); + else + puts(""); + } + DBUG_RETURN (next_link != HA_OFFSET_ERROR); +} /* check_k_link */ + + + /* Check sizes of files */ + +int maria_chk_size(HA_CHECK *param, register MARIA_HA *info) +{ + MARIA_SHARE *share= info->s; + int error; + register my_off_t skr,size; + char buff[22],buff2[22]; + DBUG_ENTER("maria_chk_size"); + + if (!(param->testflag & T_SILENT)) + puts("- check file-size"); + + /* + The following is needed if called externally (not from maria_chk). + To get a correct physical size we need to flush them. + */ + if ((error= _ma_flush_table_files(info, + MARIA_FLUSH_DATA | MARIA_FLUSH_INDEX, + FLUSH_FORCE_WRITE, FLUSH_FORCE_WRITE))) + _ma_check_print_error(param, "Failed to flush data or index file"); + + size= my_seek(share->kfile.file, 0L, MY_SEEK_END, MYF(MY_THREADSAFE)); + if ((skr=(my_off_t) share->state.state.key_file_length) != size) + { + /* Don't give error if file generated by mariapack */ + if (skr > size && maria_is_any_key_active(share->state.key_map)) + { + error=1; + _ma_check_print_error(param, + "Size of indexfile is: %-8s Should be: %s", + llstr(size,buff), llstr(skr,buff2)); + } + else if (!(param->testflag & T_VERY_SILENT)) + _ma_check_print_warning(param, + "Size of indexfile is: %-8s Should be: %s", + llstr(size,buff), llstr(skr,buff2)); + } + if (!(param->testflag & T_VERY_SILENT) && + ! (share->options & HA_OPTION_COMPRESS_RECORD) && + ulonglong2double(share->state.state.key_file_length) > + ulonglong2double(share->base.margin_key_file_length)*0.9) + _ma_check_print_warning(param,"Keyfile is almost full, %10s of %10s used", + llstr(share->state.state.key_file_length,buff), + llstr(share->base.max_key_file_length-1,buff)); + + size= my_seek(info->dfile.file, 0L, MY_SEEK_END, MYF(0)); + skr=(my_off_t) share->state.state.data_file_length; + if (share->options & HA_OPTION_COMPRESS_RECORD) + skr+= MEMMAP_EXTRA_MARGIN; +#ifdef USE_RELOC + if (share->data_file_type == STATIC_RECORD && + skr < (my_off_t) share->base.reloc*share->base.min_pack_length) + skr=(my_off_t) share->base.reloc*share->base.min_pack_length; +#endif + if (skr != size) + { + if (skr > size && skr != size + MEMMAP_EXTRA_MARGIN) + { + share->state.state.data_file_length=size; /* Skip other errors */ + error=1; + _ma_check_print_error(param,"Size of datafile is: %-9s Should be: %s", + llstr(size,buff), llstr(skr,buff2)); + param->testflag|=T_RETRY_WITHOUT_QUICK; + } + else + { + _ma_check_print_warning(param, + "Size of datafile is: %-9s Should be: %s", + llstr(size,buff), llstr(skr,buff2)); + } + } + if (!(param->testflag & T_VERY_SILENT) && + !(share->options & HA_OPTION_COMPRESS_RECORD) && + ulonglong2double(share->state.state.data_file_length) > + (ulonglong2double(share->base.max_data_file_length)*0.9)) + _ma_check_print_warning(param, "Datafile is almost full, %10s of %10s used", + llstr(share->state.state.data_file_length,buff), + llstr(share->base.max_data_file_length-1,buff2)); + DBUG_RETURN(error); +} /* maria_chk_size */ + + +/* Check keys */ + +int maria_chk_key(HA_CHECK *param, register MARIA_HA *info) +{ + uint key,found_keys=0,full_text_keys=0,result=0; + ha_rows keys; + ha_checksum old_record_checksum,init_checksum; + my_off_t all_keydata,all_totaldata,key_totlength,length; + double *rec_per_key_part; + MARIA_SHARE *share= info->s; + MARIA_KEYDEF *keyinfo; + char buff[22],buff2[22]; + MARIA_PAGE page; + DBUG_ENTER("maria_chk_key"); + + if (!(param->testflag & T_SILENT)) + puts("- check key delete-chain"); + + param->key_file_blocks=share->base.keystart; + if (check_k_link(param, info, share->state.key_del)) + { + if (param->testflag & T_VERBOSE) puts(""); + _ma_check_print_error(param,"key delete-link-chain corrupted"); + DBUG_RETURN(-1); + } + + if (!(param->testflag & T_SILENT)) + puts("- check index reference"); + + all_keydata=all_totaldata=key_totlength=0; + init_checksum=param->record_checksum; + old_record_checksum=0; + if (share->data_file_type == STATIC_RECORD) + old_record_checksum= (calc_checksum(share->state.state.records + + share->state.state.del-1) * + share->base.pack_reclength); + rec_per_key_part= param->new_rec_per_key_part; + for (key= 0,keyinfo= &share->keyinfo[0]; key < share->base.keys ; + rec_per_key_part+=keyinfo->keysegs, key++, keyinfo++) + { + param->key_crc[key]=0; + if (! maria_is_key_active(share->state.key_map, key)) + { + /* Remember old statistics for key */ + memcpy((char*) rec_per_key_part, + (char*) (share->state.rec_per_key_part + + (uint) (rec_per_key_part - param->new_rec_per_key_part)), + keyinfo->keysegs*sizeof(*rec_per_key_part)); + continue; + } + found_keys++; + + param->record_checksum=init_checksum; + + bzero((char*) ¶m->unique_count,sizeof(param->unique_count)); + bzero((char*) ¶m->notnull_count,sizeof(param->notnull_count)); + + if ((!(param->testflag & T_SILENT))) + printf ("- check data record references index: %d\n",key+1); + if (keyinfo->flag & (HA_FULLTEXT | HA_SPATIAL)) + full_text_keys++; + if (share->state.key_root[key] == HA_OFFSET_ERROR) + { + if (share->state.state.records != 0 && !(keyinfo->flag & HA_FULLTEXT)) + _ma_check_print_error(param, "Key tree %u is empty", key + 1); + goto do_stat; + } + if (_ma_fetch_keypage(&page, info, keyinfo, share->state.key_root[key], + PAGECACHE_LOCK_LEFT_UNLOCKED, DFLT_INIT_HITS, + info->buff, 0)) + { + report_keypage_fault(param, info, share->state.key_root[key]); + if (!(param->testflag & T_INFO)) + DBUG_RETURN(-1); + result= -1; + continue; + } + param->key_file_blocks+=keyinfo->block_length; + keys=0; + param->keydata=param->totaldata=0; + param->key_blocks=0; + param->max_level=0; + if (chk_index(param, info,keyinfo, &page, &keys, param->key_crc+key,1)) + DBUG_RETURN(-1); + if (!(keyinfo->flag & (HA_FULLTEXT | HA_SPATIAL | HA_RTREE_INDEX))) + { + if (keys != share->state.state.records) + { + _ma_check_print_error(param,"Found %s keys of %s",llstr(keys,buff), + llstr(share->state.state.records,buff2)); + if (!(param->testflag & T_INFO)) + DBUG_RETURN(-1); + result= -1; + continue; + } + if ((found_keys - full_text_keys == 1 && + !(share->data_file_type == STATIC_RECORD)) || + (param->testflag & T_DONT_CHECK_CHECKSUM)) + old_record_checksum= param->record_checksum; + else if (old_record_checksum != param->record_checksum) + { + if (key) + _ma_check_print_error(param, + "Key %u doesn't point at same records as " + "key 1", + key+1); + else + _ma_check_print_error(param,"Key 1 doesn't point at all records"); + if (!(param->testflag & T_INFO)) + DBUG_RETURN(-1); + result= -1; + continue; + } + } + if ((uint) share->base.auto_key -1 == key) + { + /* Check that auto_increment key is bigger than max key value */ + ulonglong auto_increment; + const HA_KEYSEG *keyseg= share->keyinfo[share->base.auto_key-1].seg; + info->lastinx=key; + _ma_read_key_record(info, info->rec_buff, 0); + auto_increment= + ma_retrieve_auto_increment(info->rec_buff + keyseg->start, + keyseg->type); + if (auto_increment > share->state.auto_increment) + { + _ma_check_print_warning(param, "Auto-increment value: %s is smaller " + "than max used value: %s", + llstr(share->state.auto_increment,buff2), + llstr(auto_increment, buff)); + } + if (param->testflag & T_AUTO_INC) + { + set_if_bigger(share->state.auto_increment, + auto_increment); + set_if_bigger(share->state.auto_increment, + param->auto_increment_value); + } + + /* Check that there isn't a row with auto_increment = 0 in the table */ + maria_extra(info,HA_EXTRA_KEYREAD,0); + bzero(info->lastkey_buff, keyinfo->seg->length); + if (!maria_rkey(info, info->rec_buff, key, + info->lastkey_buff, + (key_part_map) 1, HA_READ_KEY_EXACT)) + { + /* Don't count this as a real warning, as maria_chk can't correct it */ + uint save=param->warning_printed; + _ma_check_print_warning(param, "Found row where the auto_increment " + "column has the value 0"); + param->warning_printed=save; + } + maria_extra(info,HA_EXTRA_NO_KEYREAD,0); + } + + length=(my_off_t) isam_key_length(info,keyinfo)*keys + param->key_blocks*2; + if (param->testflag & T_INFO && param->totaldata != 0L && keys != 0L) + printf("Key: %2d: Keyblocks used: %3d%% Packed: %4d%% Max levels: %2d\n", + key+1, + (int) (my_off_t2double(param->keydata)*100.0/my_off_t2double(param->totaldata)), + (int) ((my_off_t2double(length) - my_off_t2double(param->keydata))*100.0/ + my_off_t2double(length)), + param->max_level); + all_keydata+=param->keydata; all_totaldata+=param->totaldata; key_totlength+=length; + +do_stat: + if (param->testflag & T_STATISTICS) + maria_update_key_parts(keyinfo, rec_per_key_part, param->unique_count, + param->stats_method == MI_STATS_METHOD_IGNORE_NULLS? + param->notnull_count: NULL, + (ulonglong)share->state.state.records); + } + if (param->testflag & T_INFO) + { + if (all_totaldata != 0L && found_keys > 0) + printf("Total: Keyblocks used: %3d%% Packed: %4d%%\n\n", + (int) (my_off_t2double(all_keydata)*100.0/ + my_off_t2double(all_totaldata)), + (int) ((my_off_t2double(key_totlength) - + my_off_t2double(all_keydata))*100.0/ + my_off_t2double(key_totlength))); + else if (all_totaldata != 0L && maria_is_any_key_active(share->state.key_map)) + puts(""); + } + if (param->key_file_blocks != share->state.state.key_file_length && + share->state.key_map == ~(ulonglong) 0) + _ma_check_print_warning(param, "Some data are unreferenced in keyfile"); + if (found_keys != full_text_keys) + param->record_checksum=old_record_checksum-init_checksum; /* Remove delete links */ + else + param->record_checksum=0; + DBUG_RETURN(result); +} /* maria_chk_key */ + + + +static int chk_index_down(HA_CHECK *param, MARIA_HA *info, + MARIA_KEYDEF *keyinfo, + my_off_t page, uchar *buff, ha_rows *keys, + ha_checksum *key_checksum, uint level) +{ + char llbuff[22],llbuff2[22]; + MARIA_SHARE *share= info->s; + MARIA_PAGE ma_page; + DBUG_ENTER("chk_index_down"); + + /* Key blocks must lay within the key file length entirely. */ + if (page + keyinfo->block_length > share->state.state.key_file_length) + { + /* purecov: begin tested */ + /* Give it a chance to fit in the real file size. */ + my_off_t max_length= my_seek(info->s->kfile.file, 0L, MY_SEEK_END, + MYF(MY_THREADSAFE)); + _ma_check_print_error(param, "Invalid key block position: %s " + "key block size: %u file_length: %s", + llstr(page, llbuff), keyinfo->block_length, + llstr(share->state.state.key_file_length, llbuff2)); + if (page + keyinfo->block_length > max_length) + goto err; + /* Fix the remembered key file length. */ + share->state.state.key_file_length= (max_length & + ~ (my_off_t) (keyinfo->block_length - + 1)); + /* purecov: end */ + } + + /* Key blocks must be aligned at block length */ + if (page & (info->s->block_size -1)) + { + /* purecov: begin tested */ + _ma_check_print_error(param, "Mis-aligned key block: %s " + "key block length: %u", + llstr(page, llbuff), info->s->block_size); + goto err; + /* purecov: end */ + } + + if (_ma_fetch_keypage(&ma_page, info, keyinfo, page, + PAGECACHE_LOCK_LEFT_UNLOCKED, + DFLT_INIT_HITS, buff, 0)) + { + report_keypage_fault(param, info, page); + goto err; + } + param->key_file_blocks+=keyinfo->block_length; + if (chk_index(param, info, keyinfo, &ma_page, keys, key_checksum,level)) + goto err; + + DBUG_RETURN(0); + + /* purecov: begin tested */ +err: + DBUG_RETURN(1); + /* purecov: end */ +} + + +/* + "Ignore NULLs" statistics collection method: process first index tuple. + + SYNOPSIS + maria_collect_stats_nonulls_first() + keyseg IN Array of key part descriptions + notnull INOUT Array, notnull[i] = (number of {keypart1...keypart_i} + tuples that don't contain NULLs) + key IN Key values tuple + + DESCRIPTION + Process the first index tuple - find out which prefix tuples don't + contain NULLs, and update the array of notnull counters accordingly. +*/ + +static +void maria_collect_stats_nonulls_first(HA_KEYSEG *keyseg, ulonglong *notnull, + const uchar *key) +{ + uint first_null, kp; + first_null= ha_find_null(keyseg, key) - keyseg; + /* + All prefix tuples that don't include keypart_{first_null} are not-null + tuples (and all others aren't), increment counters for them. + */ + for (kp= 0; kp < first_null; kp++) + notnull[kp]++; +} + + +/* + "Ignore NULLs" statistics collection method: process next index tuple. + + SYNOPSIS + maria_collect_stats_nonulls_next() + keyseg IN Array of key part descriptions + notnull INOUT Array, notnull[i] = (number of {keypart1...keypart_i} + tuples that don't contain NULLs) + prev_key IN Previous key values tuple + last_key IN Next key values tuple + + DESCRIPTION + Process the next index tuple: + 1. Find out which prefix tuples of last_key don't contain NULLs, and + update the array of notnull counters accordingly. + 2. Find the first keypart number where the prev_key and last_key tuples + are different(A), or last_key has NULL value(B), and return it, so the + caller can count number of unique tuples for each key prefix. We don't + need (B) to be counted, and that is compensated back in + maria_update_key_parts(). + + RETURN + 1 + number of first keypart where values differ or last_key tuple has NULL +*/ + +static +int maria_collect_stats_nonulls_next(HA_KEYSEG *keyseg, ulonglong *notnull, + const uchar *prev_key, + const uchar *last_key) +{ + uint diffs[2]; + uint first_null_seg, kp; + HA_KEYSEG *seg; + + /* + Find the first keypart where values are different or either of them is + NULL. We get results in diffs array: + diffs[0]= 1 + number of first different keypart + diffs[1]=offset: (last_key + diffs[1]) points to first value in + last_key that is NULL or different from corresponding + value in prev_key. + */ + ha_key_cmp(keyseg, prev_key, last_key, USE_WHOLE_KEY, + SEARCH_FIND | SEARCH_NULL_ARE_NOT_EQUAL, diffs); + seg= keyseg + diffs[0] - 1; + + /* Find first NULL in last_key */ + first_null_seg= ha_find_null(seg, last_key + diffs[1]) - keyseg; + for (kp= 0; kp < first_null_seg; kp++) + notnull[kp]++; + + /* + Return 1+ number of first key part where values differ. Don't care if + these were NULLs and not .... We compensate for that in + maria_update_key_parts. + */ + return diffs[0]; +} + + +/* Check if index is ok */ + +static int chk_index(HA_CHECK *param, MARIA_HA *info, MARIA_KEYDEF *keyinfo, + MARIA_PAGE *anc_page, ha_rows *keys, + ha_checksum *key_checksum, uint level) +{ + int flag; + uint comp_flag, page_flag, nod_flag; + uchar *temp_buff, *keypos, *old_keypos, *endpos; + my_off_t next_page,record; + MARIA_SHARE *share= info->s; + char llbuff[22]; + uint diff_pos[2]; + uchar tmp_key_buff[MARIA_MAX_KEY_BUFF]; + MARIA_KEY tmp_key; + DBUG_ENTER("chk_index"); + DBUG_DUMP("buff", anc_page->buff, anc_page->size); + + /* TODO: implement appropriate check for RTree keys */ + if (keyinfo->flag & (HA_SPATIAL | HA_RTREE_INDEX)) + DBUG_RETURN(0); + + if (!(temp_buff=(uchar*) my_alloca((uint) keyinfo->block_length))) + { + _ma_check_print_error(param,"Not enough memory for keyblock"); + DBUG_RETURN(-1); + } + + if (keyinfo->flag & HA_NOSAME) + { + /* Not real duplicates */ + comp_flag=SEARCH_FIND | SEARCH_UPDATE | SEARCH_INSERT; + } + else + comp_flag=SEARCH_SAME; /* Keys in positionorder */ + + page_flag= anc_page->flag; + nod_flag= anc_page->node; + old_keypos= anc_page->buff + share->keypage_header; + keypos= old_keypos + nod_flag; + endpos= anc_page->buff + anc_page->size; + + param->keydata+= anc_page->size; + param->totaldata+= keyinfo->block_length; /* INFO */ + param->key_blocks++; + if (level > param->max_level) + param->max_level=level; + + if (_ma_get_keynr(share, anc_page->buff) != + (uint) (keyinfo - share->keyinfo)) + _ma_check_print_error(param, "Page at %s is not marked for index %u", + llstr(anc_page->pos, llbuff), + (uint) (keyinfo - share->keyinfo)); + if ((page_flag & KEYPAGE_FLAG_HAS_TRANSID) && + !share->base.born_transactional) + { + _ma_check_print_error(param, + "Page at %s is marked with HAS_TRANSID even if " + "table is not transactional", + llstr(anc_page->pos, llbuff)); + } + + if (anc_page->size > (uint) keyinfo->block_length - KEYPAGE_CHECKSUM_SIZE) + { + _ma_check_print_error(param, + "Page at %s has impossible (too big) pagelength", + llstr(anc_page->pos, llbuff)); + goto err; + } + + info->last_key.keyinfo= tmp_key.keyinfo= keyinfo; + tmp_key.data= tmp_key_buff; + for ( ;; ) + { + if (*_ma_killed_ptr(param)) + goto err; + if (nod_flag) + { + next_page= _ma_kpos(nod_flag,keypos); + if (chk_index_down(param,info,keyinfo,next_page, + temp_buff,keys,key_checksum,level+1)) + { + DBUG_DUMP("page_data", old_keypos, (uint) (keypos - old_keypos)); + goto err; + } + } + old_keypos=keypos; + if (keypos >= endpos || + !(*keyinfo->get_key)(&tmp_key, page_flag, nod_flag, &keypos)) + break; + if (keypos > endpos) + { + _ma_check_print_error(param, + "Page length and length of keys don't match at " + "page: %s", + llstr(anc_page->pos,llbuff)); + goto err; + } + if (share->data_file_type == BLOCK_RECORD && + !(page_flag & KEYPAGE_FLAG_HAS_TRANSID) && + key_has_transid(tmp_key.data + tmp_key.data_length + + share->rec_reflength-1)) + { + _ma_check_print_error(param, + "Found key marked for transid on page that is not " + "marked for transid at: %s", + llstr(anc_page->pos,llbuff)); + goto err; + } + + if ((*keys)++ && + (flag=ha_key_cmp(keyinfo->seg, info->last_key.data, tmp_key.data, + tmp_key.data_length + tmp_key.ref_length, + (comp_flag | SEARCH_INSERT | (tmp_key.flag >> 1) | + info->last_key.flag), diff_pos)) >=0) + { + DBUG_DUMP_KEY("old", &info->last_key); + DBUG_DUMP_KEY("new", &tmp_key); + DBUG_DUMP("new_in_page", old_keypos, (uint) (keypos-old_keypos)); + + if ((comp_flag & SEARCH_FIND) && flag == 0) + _ma_check_print_error(param,"Found duplicated key at page %s", + llstr(anc_page->pos,llbuff)); + else + _ma_check_print_error(param,"Key in wrong position at page %s", + llstr(anc_page->pos,llbuff)); + goto err; + } + + if (param->testflag & T_STATISTICS) + { + if (*keys != 1L) /* not first_key */ + { + if (param->stats_method == MI_STATS_METHOD_NULLS_NOT_EQUAL) + ha_key_cmp(keyinfo->seg, info->last_key.data, + tmp_key.data, tmp_key.data_length, + SEARCH_FIND | SEARCH_NULL_ARE_NOT_EQUAL, + diff_pos); + else if (param->stats_method == MI_STATS_METHOD_IGNORE_NULLS) + { + diff_pos[0]= maria_collect_stats_nonulls_next(keyinfo->seg, + param->notnull_count, + info->last_key.data, + tmp_key.data); + } + param->unique_count[diff_pos[0]-1]++; + } + else + { + if (param->stats_method == MI_STATS_METHOD_IGNORE_NULLS) + maria_collect_stats_nonulls_first(keyinfo->seg, param->notnull_count, + tmp_key.data); + } + } + _ma_copy_key(&info->last_key, &tmp_key); + (*key_checksum)+= maria_byte_checksum(tmp_key.data, tmp_key.data_length); + record= _ma_row_pos_from_key(&tmp_key); + + if (keyinfo->flag & HA_FULLTEXT) /* special handling for ft2 */ + { + uint off; + int subkeys; + get_key_full_length_rdonly(off, tmp_key.data); + subkeys= ft_sintXkorr(tmp_key.data + off); + if (subkeys < 0) + { + ha_rows tmp_keys=0; + if (chk_index_down(param,info,&share->ft2_keyinfo,record, + temp_buff,&tmp_keys,key_checksum,1)) + goto err; + if (tmp_keys + subkeys) + { + _ma_check_print_error(param, + "Number of words in the 2nd level tree " + "does not match the number in the header. " + "Parent word in on the page %s, offset %u", + llstr(anc_page->pos,llbuff), + (uint) (old_keypos - anc_page->buff)); + goto err; + } + (*keys)+=tmp_keys-1; + continue; + } + /* fall through */ + } + if ((share->data_file_type != BLOCK_RECORD && + record >= share->state.state.data_file_length) || + (share->data_file_type == BLOCK_RECORD && + ma_recordpos_to_page(record) * share->base.min_block_length >= + share->state.state.data_file_length)) + { +#ifndef DBUG_OFF + char llbuff2[22], llbuff3[22]; +#endif + _ma_check_print_error(param, + "Found key at page %s that points to record " + "outside datafile", + llstr(anc_page->pos,llbuff)); + DBUG_PRINT("test",("page: %s record: %s filelength: %s", + llstr(anc_page->pos,llbuff),llstr(record,llbuff2), + llstr(share->state.state.data_file_length,llbuff3))); + DBUG_DUMP_KEY("key", &tmp_key); + DBUG_DUMP("new_in_page", old_keypos, (uint) (keypos-old_keypos)); + goto err; + } + param->record_checksum+= (ha_checksum) record; + } + if (keypos != endpos) + { + _ma_check_print_error(param, + "Keyblock size at page %s is not correct. " + "Block length: %u key length: %u", + llstr(anc_page->pos, llbuff), anc_page->size, + (uint) (keypos - anc_page->buff)); + goto err; + } + my_afree(temp_buff); + DBUG_RETURN(0); + err: + my_afree(temp_buff); + DBUG_RETURN(1); +} /* chk_index */ + + + /* Calculate a checksum of 1+2+3+4...N = N*(N+1)/2 without overflow */ + +static ha_checksum calc_checksum(ha_rows count) +{ + ulonglong sum,a,b; + DBUG_ENTER("calc_checksum"); + + sum=0; + a=count; b=count+1; + if (a & 1) + b>>=1; + else + a>>=1; + while (b) + { + if (b & 1) + sum+=a; + a<<=1; b>>=1; + } + DBUG_PRINT("exit",("sum: %lx",(ulong) sum)); + DBUG_RETURN((ha_checksum) sum); +} /* calc_checksum */ + + + /* Calc length of key in normal isam */ + +static uint isam_key_length(MARIA_HA *info, register MARIA_KEYDEF *keyinfo) +{ + uint length; + HA_KEYSEG *keyseg; + DBUG_ENTER("isam_key_length"); + + length= info->s->rec_reflength; + for (keyseg=keyinfo->seg ; keyseg->type ; keyseg++) + length+= keyseg->length; + + DBUG_PRINT("exit",("length: %d",length)); + DBUG_RETURN(length); +} /* key_length */ + + + +static void record_pos_to_txt(MARIA_HA *info, my_off_t recpos, + char *buff) +{ + if (info->s->data_file_type != BLOCK_RECORD) + llstr(recpos, buff); + else + { + my_off_t page= ma_recordpos_to_page(recpos); + uint row= ma_recordpos_to_dir_entry(recpos); + char *end= longlong10_to_str(page, buff, 10); + *(end++)= ':'; + longlong10_to_str(row, end, 10); + } +} + + +/* + Check that keys in records exist in index tree + + SYNOPSIS + check_keys_in_record() + param Check paramenter + info Maria handler + extend Type of check (extended or normal) + start_recpos Position to row + record Record buffer + + NOTES + This function also calculates record checksum & number of rows +*/ + +static int check_keys_in_record(HA_CHECK *param, MARIA_HA *info, int extend, + my_off_t start_recpos, uchar *record) +{ + MARIA_SHARE *share= info->s; + MARIA_KEYDEF *keyinfo; + char llbuff[22+4]; + uint keynr; + + param->tmp_record_checksum+= (ha_checksum) start_recpos; + param->records++; + if (param->testflag & T_WRITE_LOOP && param->records % WRITE_COUNT == 0) + { + printf("%s\r", llstr(param->records, llbuff)); + VOID(fflush(stdout)); + } + + /* Check if keys match the record */ + for (keynr=0, keyinfo= share->keyinfo; keynr < share->base.keys; + keynr++, keyinfo++) + { + if (maria_is_key_active(share->state.key_map, keynr)) + { + MARIA_KEY key; + if (!(keyinfo->flag & HA_FULLTEXT)) + { + (*keyinfo->make_key)(info, &key, keynr, info->lastkey_buff, record, + start_recpos, 0); + if (extend) + { + /* We don't need to lock the key tree here as we don't allow + concurrent threads when running maria_chk + */ + int search_result= +#ifdef HAVE_RTREE_KEYS + (keyinfo->flag & (HA_SPATIAL | HA_RTREE_INDEX)) ? + maria_rtree_find_first(info, &key, MBR_EQUAL | MBR_DATA) : +#endif + _ma_search(info, &key, SEARCH_SAME, share->state.key_root[keynr]); + if (search_result) + { + record_pos_to_txt(info, start_recpos, llbuff); + _ma_check_print_error(param, + "Record at: %14s " + "Can't find key for index: %2d", + llbuff, keynr+1); + if (param->err_count++ > MAXERR || !(param->testflag & T_VERBOSE)) + return -1; + } + } + else + param->tmp_key_crc[keynr]+= + maria_byte_checksum(key.data, key.data_length); + } + } + } + return 0; +} + + +/* + Functions to loop through all rows and check if they are ok + + NOTES + One function for each record format + + RESULT + 0 ok + -1 Interrupted by user + 1 Error +*/ + +static int check_static_record(HA_CHECK *param, MARIA_HA *info, int extend, + uchar *record) +{ + MARIA_SHARE *share= info->s; + my_off_t start_recpos, pos; + char llbuff[22]; + + pos= 0; + while (pos < share->state.state.data_file_length) + { + if (*_ma_killed_ptr(param)) + return -1; + if (my_b_read(¶m->read_cache, record, + share->base.pack_reclength)) + { + _ma_check_print_error(param, + "got error: %d when reading datafile at position: " + "%s", + my_errno, llstr(pos, llbuff)); + return 1; + } + start_recpos= pos; + pos+= share->base.pack_reclength; + param->splits++; + if (*record == '\0') + { + param->del_blocks++; + param->del_length+= share->base.pack_reclength; + continue; /* Record removed */ + } + param->glob_crc+= _ma_static_checksum(info,record); + param->used+= share->base.pack_reclength; + if (check_keys_in_record(param, info, extend, start_recpos, record)) + return 1; + } + return 0; +} + + +static int check_dynamic_record(HA_CHECK *param, MARIA_HA *info, int extend, + uchar *record) +{ + MARIA_BLOCK_INFO block_info; + MARIA_SHARE *share= info->s; + my_off_t start_recpos, start_block, pos; + uchar *to; + ulong left_length; + uint b_type; + char llbuff[22],llbuff2[22],llbuff3[22]; + DBUG_ENTER("check_dynamic_record"); + + LINT_INIT(left_length); + LINT_INIT(start_recpos); + LINT_INIT(to); + + pos= 0; + while (pos < share->state.state.data_file_length) + { + my_bool got_error= 0; + int flag; + if (*_ma_killed_ptr(param)) + DBUG_RETURN(-1); + + flag= block_info.second_read=0; + block_info.next_filepos=pos; + do + { + if (_ma_read_cache(¶m->read_cache, block_info.header, + (start_block=block_info.next_filepos), + sizeof(block_info.header), + (flag ? 0 : READING_NEXT) | READING_HEADER)) + { + _ma_check_print_error(param, + "got error: %d when reading datafile at " + "position: %s", + my_errno, llstr(start_block, llbuff)); + DBUG_RETURN(1); + } + + if (start_block & (MARIA_DYN_ALIGN_SIZE-1)) + { + _ma_check_print_error(param,"Wrong aligned block at %s", + llstr(start_block,llbuff)); + DBUG_RETURN(1); + } + b_type= _ma_get_block_info(&block_info,-1,start_block); + if (b_type & (BLOCK_DELETED | BLOCK_ERROR | BLOCK_SYNC_ERROR | + BLOCK_FATAL_ERROR)) + { + if (b_type & BLOCK_SYNC_ERROR) + { + if (flag) + { + _ma_check_print_error(param,"Unexpected byte: %d at link: %s", + (int) block_info.header[0], + llstr(start_block,llbuff)); + DBUG_RETURN(1); + } + pos=block_info.filepos+block_info.block_len; + goto next; + } + if (b_type & BLOCK_DELETED) + { + if (block_info.block_len < share->base.min_block_length) + { + _ma_check_print_error(param, + "Deleted block with impossible length %lu " + "at %s", + block_info.block_len,llstr(pos,llbuff)); + DBUG_RETURN(1); + } + if ((block_info.next_filepos != HA_OFFSET_ERROR && + block_info.next_filepos >= share->state.state.data_file_length) || + (block_info.prev_filepos != HA_OFFSET_ERROR && + block_info.prev_filepos >= share->state.state.data_file_length)) + { + _ma_check_print_error(param,"Delete link points outside datafile " + "at %s", + llstr(pos,llbuff)); + DBUG_RETURN(1); + } + param->del_blocks++; + param->del_length+= block_info.block_len; + param->splits++; + pos= block_info.filepos+block_info.block_len; + goto next; + } + _ma_check_print_error(param,"Wrong bytesec: %d-%d-%d at linkstart: %s", + block_info.header[0],block_info.header[1], + block_info.header[2], + llstr(start_block,llbuff)); + DBUG_RETURN(1); + } + if (share->state.state.data_file_length < block_info.filepos+ + block_info.block_len) + { + _ma_check_print_error(param, + "Recordlink that points outside datafile at %s", + llstr(pos,llbuff)); + got_error=1; + break; + } + param->splits++; + if (!flag++) /* First block */ + { + start_recpos=pos; + pos=block_info.filepos+block_info.block_len; + if (block_info.rec_len > (uint) share->base.max_pack_length) + { + _ma_check_print_error(param,"Found too long record (%lu) at %s", + (ulong) block_info.rec_len, + llstr(start_recpos,llbuff)); + got_error=1; + break; + } + if (share->base.blobs) + { + if (_ma_alloc_buffer(&info->rec_buff, &info->rec_buff_size, + block_info.rec_len + + share->base.extra_rec_buff_size)) + + { + _ma_check_print_error(param, + "Not enough memory (%lu) for blob at %s", + (ulong) block_info.rec_len, + llstr(start_recpos,llbuff)); + got_error=1; + break; + } + } + to= info->rec_buff; + left_length= block_info.rec_len; + } + if (left_length < block_info.data_len) + { + _ma_check_print_error(param,"Found too long record (%lu) at %s", + (ulong) block_info.data_len, + llstr(start_recpos,llbuff)); + got_error=1; + break; + } + if (_ma_read_cache(¶m->read_cache, to, block_info.filepos, + (uint) block_info.data_len, + flag == 1 ? READING_NEXT : 0)) + { + _ma_check_print_error(param, + "got error: %d when reading datafile at " + "position: %s", my_errno, + llstr(block_info.filepos, llbuff)); + + DBUG_RETURN(1); + } + to+=block_info.data_len; + param->link_used+= block_info.filepos-start_block; + param->used+= block_info.filepos - start_block + block_info.data_len; + param->empty+= block_info.block_len-block_info.data_len; + left_length-= block_info.data_len; + if (left_length) + { + if (b_type & BLOCK_LAST) + { + _ma_check_print_error(param, + "Wrong record length %s of %s at %s", + llstr(block_info.rec_len-left_length,llbuff), + llstr(block_info.rec_len, llbuff2), + llstr(start_recpos,llbuff3)); + got_error=1; + break; + } + if (share->state.state.data_file_length < block_info.next_filepos) + { + _ma_check_print_error(param, + "Found next-recordlink that points outside " + "datafile at %s", + llstr(block_info.filepos,llbuff)); + got_error=1; + break; + } + } + } while (left_length); + + if (! got_error) + { + if (_ma_rec_unpack(info,record,info->rec_buff,block_info.rec_len) == + MY_FILE_ERROR) + { + _ma_check_print_error(param,"Found wrong record at %s", + llstr(start_recpos,llbuff)); + got_error=1; + } + else + { + ha_checksum checksum= 0; + if (share->calc_checksum) + checksum= (*share->calc_checksum)(info, record); + + if (param->testflag & (T_EXTEND | T_MEDIUM | T_VERBOSE)) + { + if (_ma_rec_check(info,record, info->rec_buff,block_info.rec_len, + test(share->calc_checksum), checksum)) + { + _ma_check_print_error(param,"Found wrong packed record at %s", + llstr(start_recpos,llbuff)); + got_error= 1; + } + } + param->glob_crc+= checksum; + } + + if (! got_error) + { + if (check_keys_in_record(param, info, extend, start_recpos, record)) + DBUG_RETURN(1); + } + else + { + if (param->err_count++ > MAXERR || !(param->testflag & T_VERBOSE)) + DBUG_RETURN(1); + } + } + else if (!flag) + pos= block_info.filepos+block_info.block_len; +next:; + } + DBUG_RETURN(0); +} + + +static int check_compressed_record(HA_CHECK *param, MARIA_HA *info, int extend, + uchar *record) +{ + MARIA_BLOCK_INFO block_info; + MARIA_SHARE *share= info->s; + my_off_t start_recpos, pos; + char llbuff[22]; + my_bool got_error= 0; + DBUG_ENTER("check_compressed_record"); + + pos= share->pack.header_length; /* Skip header */ + while (pos < share->state.state.data_file_length) + { + if (*_ma_killed_ptr(param)) + DBUG_RETURN(-1); + + if (_ma_read_cache(¶m->read_cache, block_info.header, pos, + share->pack.ref_length, READING_NEXT)) + { + _ma_check_print_error(param, + "got error: %d when reading datafile at position: " + "%s", + my_errno, llstr(pos, llbuff)); + DBUG_RETURN(1); + } + + start_recpos= pos; + param->splits++; + VOID(_ma_pack_get_block_info(info, &info->bit_buff, &block_info, + &info->rec_buff, &info->rec_buff_size, -1, + start_recpos)); + pos=block_info.filepos+block_info.rec_len; + if (block_info.rec_len < (uint) share->min_pack_length || + block_info.rec_len > (uint) share->max_pack_length) + { + _ma_check_print_error(param, + "Found block with wrong recordlength: %lu at %s", + block_info.rec_len, llstr(start_recpos,llbuff)); + got_error=1; + goto end; + } + if (_ma_read_cache(¶m->read_cache, info->rec_buff, + block_info.filepos, block_info.rec_len, READING_NEXT)) + { + _ma_check_print_error(param, + "got error: %d when reading datafile at position: " + "%s", + my_errno, llstr(block_info.filepos, llbuff)); + DBUG_RETURN(1); + } + if (_ma_pack_rec_unpack(info, &info->bit_buff, record, + info->rec_buff, block_info.rec_len)) + { + _ma_check_print_error(param,"Found wrong record at %s", + llstr(start_recpos,llbuff)); + got_error=1; + goto end; + } + param->glob_crc+= (*share->calc_checksum)(info,record); + param->link_used+= (block_info.filepos - start_recpos); + param->used+= (pos-start_recpos); + +end: + if (! got_error) + { + if (check_keys_in_record(param, info, extend, start_recpos, record)) + DBUG_RETURN(1); + } + else + { + got_error= 0; /* Reset for next loop */ + if (param->err_count++ > MAXERR || !(param->testflag & T_VERBOSE)) + DBUG_RETURN(1); + } + } + DBUG_RETURN(0); +} + + +/* + Check if layout on head or tail page is ok + + NOTES + This is for rows-in-block format. +*/ + +static int check_page_layout(HA_CHECK *param, MARIA_HA *info, + my_off_t page_pos, uchar *page, + uint row_count, uint head_empty, + uint *real_rows_found, uint *free_slots_found) +{ + uint empty, last_row_end, row, first_dir_entry, free_entry, block_size; + uint free_entries, prev_free_entry; + uchar *dir_entry; + char llbuff[22]; + my_bool error_in_free_list= 0; + DBUG_ENTER("check_page_layout"); + + block_size= info->s->block_size; + empty= 0; + last_row_end= PAGE_HEADER_SIZE; + *real_rows_found= 0; + + /* Check free directory list */ + free_entry= (uint) page[DIR_FREE_OFFSET]; + free_entries= 0; + prev_free_entry= END_OF_DIR_FREE_LIST; + while (free_entry != END_OF_DIR_FREE_LIST) + { + uchar *dir; + if (free_entry > row_count) + { + _ma_check_print_error(param, + "Page %9s: Directory free entry points outside " + "directory", + llstr(page_pos, llbuff)); + error_in_free_list= 1; + break; + } + dir= dir_entry_pos(page, block_size, free_entry); + if (uint2korr(dir) != 0) + { + _ma_check_print_error(param, + "Page %9s: Directory free entry points to " + "not deleted entry", + llstr(page_pos, llbuff)); + error_in_free_list= 1; + break; + } + if (dir[2] != prev_free_entry) + { + _ma_check_print_error(param, + "Page %9s: Directory free list back pointer " + "points to wrong entry", + llstr(page_pos, llbuff)); + error_in_free_list= 1; + break; + } + prev_free_entry= free_entry; + free_entry= dir[3]; + free_entries++; + } + *free_slots_found= free_entries; + + /* Check directry */ + dir_entry= page+ block_size - PAGE_SUFFIX_SIZE; + first_dir_entry= (block_size - row_count * DIR_ENTRY_SIZE - + PAGE_SUFFIX_SIZE); + for (row= 0 ; row < row_count ; row++) + { + uint pos, length; + dir_entry-= DIR_ENTRY_SIZE; + pos= uint2korr(dir_entry); + if (!pos) + { + free_entries--; + if (row == row_count -1) + { + _ma_check_print_error(param, + "Page %9s: First entry in directory is 0", + llstr(page_pos, llbuff)); + if (param->err_count++ > MAXERR || !(param->testflag & T_VERBOSE)) + DBUG_RETURN(1); + } + continue; /* Deleted row */ + } + (*real_rows_found)++; + length= uint2korr(dir_entry+2); + param->used+= length; + if (pos < last_row_end) + { + _ma_check_print_error(param, + "Page %9s: Row %3u overlapps with previous row", + llstr(page_pos, llbuff), row); + DBUG_RETURN(1); + } + empty+= (pos - last_row_end); + last_row_end= pos + length; + if (last_row_end > first_dir_entry) + { + _ma_check_print_error(param, + "Page %9s: Row %3u overlapps with directory", + llstr(page_pos, llbuff), row); + DBUG_RETURN(1); + } + } + empty+= (first_dir_entry - last_row_end); + + if (empty != head_empty) + { + _ma_check_print_error(param, + "Page %9s: Wrong empty size. Stored: %5u " + "Actual: %5u", + llstr(page_pos, llbuff), head_empty, empty); + param->err_count++; + } + if (free_entries != 0 && !error_in_free_list) + { + _ma_check_print_error(param, + "Page %9s: Directory free link don't include " + "all free entries", + llstr(page_pos, llbuff)); + param->err_count++; + } + DBUG_RETURN(param->err_count && + (param->err_count >= MAXERR || !(param->testflag & T_VERBOSE))); +} + + +/* + Check all rows on head page + + NOTES + This is for rows-in-block format. + + Before this, we have already called check_page_layout(), so + we know the block is logicaly correct (even if the rows may not be that) + + RETURN + 0 ok + 1 error +*/ + + +static my_bool check_head_page(HA_CHECK *param, MARIA_HA *info, uchar *record, + int extend, my_off_t page_pos, uchar *page_buff, + uint row_count) +{ + MARIA_SHARE *share= info->s; + uchar *dir_entry; + uint row; + char llbuff[22], llbuff2[22]; + ulonglong page= page_pos / share->block_size; + DBUG_ENTER("check_head_page"); + + dir_entry= page_buff+ share->block_size - PAGE_SUFFIX_SIZE; + for (row= 0 ; row < row_count ; row++) + { + uint pos, length, flag; + dir_entry-= DIR_ENTRY_SIZE; + pos= uint2korr(dir_entry); + if (!pos) + continue; + length= uint2korr(dir_entry+2); + if (length < share->base.min_block_length) + { + _ma_check_print_error(param, + "Page %9s: Row %3u is too short " + "(%d of min %d bytes)", + llstr(page, llbuff), row, length, + (uint) share->base.min_block_length); + DBUG_RETURN(1); + } + flag= (uint) (uchar) page_buff[pos]; + if (flag & ~(ROW_FLAG_ALL)) + _ma_check_print_error(param, + "Page %9s: Row %3u has wrong flag: %u", + llstr(page, llbuff), row, flag); + + DBUG_PRINT("info", ("rowid: %s page: %lu row: %u", + llstr(ma_recordpos(page, row), llbuff), + (ulong) page, row)); + info->cur_row.trid= 0; + if (_ma_read_block_record2(info, record, page_buff+pos, + page_buff+pos+length)) + { + _ma_check_print_error(param, + "Page %9s: Row %3d is crashed", + llstr(page, llbuff), row); + if (param->err_count++ > MAXERR || !(param->testflag & T_VERBOSE)) + DBUG_RETURN(1); + continue; + } + set_if_bigger(param->max_found_trid, info->cur_row.trid); + if (info->cur_row.trid > param->max_trid) + _ma_check_print_not_visible_error(param, info->cur_row.trid); + + if (share->calc_checksum) + { + ha_checksum checksum= (*share->calc_checksum)(info, record); + if (info->cur_row.checksum != (checksum & 255)) + _ma_check_print_error(param, "Page %9s: Row %3d has wrong checksum", + llstr(page, llbuff), row); + param->glob_crc+= checksum; + } + if (info->cur_row.extents_count) + { + uchar *extents= info->cur_row.extents; + uint i; + /* Check that bitmap has the right marker for the found extents */ + for (i= 0 ; i < info->cur_row.extents_count ; i++) + { + pgcache_page_no_t extent_page; + uint page_count, page_type; + extent_page= uint5korr(extents); + page_count= uint2korr(extents+5) & ~START_EXTENT_BIT; + extents+= ROW_EXTENT_SIZE; + page_type= BLOB_PAGE; + if (page_count & TAIL_BIT) + { + page_count= 1; + page_type= TAIL_PAGE; + } + /* + TODO OPTIMIZE: + Check the whole extent with one test and only do the loop if + something is wrong (for exact error reporting) + */ + for ( ; page_count--; extent_page++) + { + uint bitmap_pattern; + if (_ma_check_if_right_bitmap_type(info, page_type, extent_page, + &bitmap_pattern)) + { + _ma_check_print_error(param, + "Page %9s: Row: %3d has an extent with " + "wrong information in bitmap: " + "Page %9s Page_type: %d Bitmap: %d", + llstr(page, llbuff), row, + llstr(extent_page, llbuff2), + page_type, bitmap_pattern); + if (param->err_count++ > MAXERR || !(param->testflag & T_VERBOSE)) + DBUG_RETURN(1); + } + } + } + } + param->full_page_count+= info->cur_row.full_page_count; + param->tail_count+= info->cur_row.tail_count; + if (check_keys_in_record(param, info, extend, + ma_recordpos(page, row), record)) + DBUG_RETURN(1); + } + DBUG_RETURN(0); +} + + +/* + Check if rows-in-block data file is consistent +*/ + +static int check_block_record(HA_CHECK *param, MARIA_HA *info, int extend, + uchar *record) +{ + MARIA_SHARE *share= info->s; + my_off_t pos; + pgcache_page_no_t page; + uchar *page_buff, *bitmap_buff, *data; + char llbuff[22], llbuff2[22]; + uint block_size= share->block_size; + ha_rows full_page_count, tail_count; + my_bool full_dir; + uint offset_page, offset, free_count; + + LINT_INIT(full_dir); + + if (_ma_scan_init_block_record(info)) + { + _ma_check_print_error(param, "got error %d when initializing scan", + my_errno); + return 1; + } + bitmap_buff= info->scan.bitmap_buff; + page_buff= info->scan.page_buff; + full_page_count= tail_count= 0; + param->full_page_count= param->tail_count= 0; + param->used= param->link_used= 0; + param->splits= share->state.state.data_file_length / block_size; + + for (pos= 0, page= 0; + pos < share->state.state.data_file_length; + pos+= block_size, page++) + { + uint row_count, real_row_count, empty_space, page_type, bitmap_pattern; + LINT_INIT(row_count); + LINT_INIT(empty_space); + + if (*_ma_killed_ptr(param)) + { + _ma_scan_end_block_record(info); + return -1; + } + if ((page % share->bitmap.pages_covered) == 0) + { + /* Bitmap page */ + if (pagecache_read(share->pagecache, + &info->s->bitmap.file, + page, 1, + bitmap_buff, + PAGECACHE_PLAIN_PAGE, + PAGECACHE_LOCK_LEFT_UNLOCKED, 0) == 0) + { + _ma_check_print_error(param, + "Page %9s: Got error: %d when reading datafile", + llstr(page, llbuff), my_errno); + goto err; + } + param->used+= block_size; + param->link_used+= block_size; + continue; + } + /* Skip pages marked as empty in bitmap */ + offset_page= (uint) ((page % share->bitmap.pages_covered) -1) * 3; + offset= offset_page & 7; + data= bitmap_buff + offset_page / 8; + bitmap_pattern= uint2korr(data); + if (!((bitmap_pattern >> offset) & 7)) + { + param->empty+= block_size; + param->del_blocks++; + continue; + } + + if (pagecache_read(share->pagecache, + &info->dfile, + page, 1, + page_buff, + share->page_type, + PAGECACHE_LOCK_LEFT_UNLOCKED, 0) == 0) + { + _ma_check_print_error(param, + "Page %9s: Got error: %d when reading datafile", + llstr(page, llbuff), my_errno); + goto err; + } + page_type= page_buff[PAGE_TYPE_OFFSET] & PAGE_TYPE_MASK; + if (page_type == UNALLOCATED_PAGE || page_type >= MAX_PAGE_TYPE) + { + _ma_check_print_error(param, + "Page: %9s Found wrong page type %d", + llstr(page, llbuff), page_type); + if (param->err_count++ > MAXERR || !(param->testflag & T_VERBOSE)) + goto err; + continue; + } + switch ((enum en_page_type) page_type) { + case UNALLOCATED_PAGE: + case MAX_PAGE_TYPE: + default: + DBUG_ASSERT(0); /* Impossible */ + break; + case HEAD_PAGE: + row_count= page_buff[DIR_COUNT_OFFSET]; + empty_space= uint2korr(page_buff + EMPTY_SPACE_OFFSET); + param->used+= block_size - empty_space; + param->link_used+= (PAGE_HEADER_SIZE + PAGE_SUFFIX_SIZE + + row_count * DIR_ENTRY_SIZE); + if (empty_space < share->bitmap.sizes[3]) + param->lost+= empty_space; + if (check_page_layout(param, info, pos, page_buff, row_count, + empty_space, &real_row_count, &free_count)) + goto err; + full_dir= (row_count == MAX_ROWS_PER_PAGE && + page_buff[DIR_FREE_OFFSET] == END_OF_DIR_FREE_LIST); + break; + case TAIL_PAGE: + row_count= page_buff[DIR_COUNT_OFFSET]; + empty_space= uint2korr(page_buff + EMPTY_SPACE_OFFSET); + param->used+= block_size - empty_space; + param->link_used+= (PAGE_HEADER_SIZE + PAGE_SUFFIX_SIZE + + row_count * DIR_ENTRY_SIZE); + if (empty_space < share->bitmap.sizes[6]) + param->lost+= empty_space; + if (check_page_layout(param, info, pos, page_buff, row_count, + empty_space, &real_row_count, &free_count)) + goto err; + full_dir= (row_count - free_count >= MAX_ROWS_PER_PAGE - + share->base.blobs); + break; + case BLOB_PAGE: + full_page_count++; + full_dir= 0; + empty_space= block_size; /* for error reporting */ + param->link_used+= (LSN_SIZE + PAGE_TYPE_SIZE); + param->used+= block_size; + break; + } + if (_ma_check_bitmap_data(info, page_type, page, + full_dir ? 0 : empty_space, + &bitmap_pattern)) + { + if (bitmap_pattern == ~(uint) 0) + _ma_check_print_error(param, + "Page %9s: Wrong bitmap for data on page", + llstr(page, llbuff)); + else + _ma_check_print_error(param, + "Page %9s: Wrong data in bitmap. Page_type: " + "%d empty_space: %u Bitmap-bits: %d", + llstr(page, llbuff), page_type, + empty_space, bitmap_pattern); + if (param->err_count++ > MAXERR || !(param->testflag & T_VERBOSE)) + goto err; + } + if ((enum en_page_type) page_type == BLOB_PAGE) + continue; + param->empty+= empty_space; + if ((enum en_page_type) page_type == TAIL_PAGE) + { + tail_count+= real_row_count; + continue; + } + if (check_head_page(param, info, record, extend, pos, page_buff, + row_count)) + goto err; + } + + /* Verify that rest of bitmap is zero */ + + if (page % share->bitmap.pages_covered) + { + /* Not at end of bitmap */ + uint bitmap_pattern; + offset_page= (uint) ((page % share->bitmap.pages_covered) -1) * 3; + offset= offset_page & 7; + data= bitmap_buff + offset_page / 8; + bitmap_pattern= uint2korr(data); + if (((bitmap_pattern >> offset)) || + (data + 2 < bitmap_buff + share->bitmap.total_size && + _ma_check_if_zero(data+2, bitmap_buff + share->bitmap.total_size - + data - 2))) + { + ulonglong bitmap_page; + bitmap_page= page / share->bitmap.pages_covered; + bitmap_page*= share->bitmap.pages_covered; + + _ma_check_print_error(param, + "Bitmap at page %s has pages reserved outside of " + "data file length", + llstr(bitmap_page, llbuff)); + DBUG_EXECUTE("bitmap", _ma_print_bitmap(&share->bitmap, bitmap_buff, + bitmap_page);); + } + } + + _ma_scan_end_block_record(info); + + if (full_page_count != param->full_page_count) + _ma_check_print_error(param, "Full page count read through records was %s " + "but we found %s pages while scanning table", + llstr(param->full_page_count, llbuff), + llstr(full_page_count, llbuff2)); + if (tail_count != param->tail_count) + _ma_check_print_error(param, "Tail count read through records was %s but " + "we found %s tails while scanning table", + llstr(param->tail_count, llbuff), + llstr(tail_count, llbuff2)); + + return param->error_printed != 0; + +err: + _ma_scan_end_block_record(info); + return 1; +} + + +/* Check that record-link is ok */ + +int maria_chk_data_link(HA_CHECK *param, MARIA_HA *info, my_bool extend) +{ + MARIA_SHARE *share= info->s; + int error; + uchar *record; + char llbuff[22],llbuff2[22],llbuff3[22]; + DBUG_ENTER("maria_chk_data_link"); + + if (!(param->testflag & T_SILENT)) + { + if (extend) + puts("- check records and index references"); + else + puts("- check record links"); + } + + if (!(record= (uchar*) my_malloc(share->base.default_rec_buff_size, MYF(0)))) + { + _ma_check_print_error(param,"Not enough memory for record"); + DBUG_RETURN(-1); + } + param->records= param->del_blocks= 0; + param->used= param->link_used= param->splits= param->del_length= 0; + param->lost= 0; + param->tmp_record_checksum= param->glob_crc= 0; + param->err_count= 0; + + error= 0; + param->empty= share->pack.header_length; + + bzero((char*) param->tmp_key_crc, + share->base.keys * sizeof(param->tmp_key_crc[0])); + + switch (share->data_file_type) { + case BLOCK_RECORD: + error= check_block_record(param, info, extend, record); + break; + case STATIC_RECORD: + error= check_static_record(param, info, extend, record); + break; + case DYNAMIC_RECORD: + error= check_dynamic_record(param, info, extend, record); + break; + case COMPRESSED_RECORD: + error= check_compressed_record(param, info, extend, record); + break; + } /* switch */ + + if (error) + goto err; + + if (param->testflag & T_WRITE_LOOP) + { + VOID(fputs(" \r",stdout)); VOID(fflush(stdout)); + } + if (param->records != share->state.state.records) + { + _ma_check_print_error(param, + "Record-count is not ok; found %-10s Should be: %s", + llstr(param->records,llbuff), + llstr(share->state.state.records,llbuff2)); + error=1; + } + else if (param->record_checksum && + param->record_checksum != param->tmp_record_checksum) + { + _ma_check_print_error(param, + "Key pointers and record positions doesn't match"); + error=1; + } + else if (param->glob_crc != share->state.state.checksum && + (share->options & + (HA_OPTION_CHECKSUM | HA_OPTION_COMPRESS_RECORD))) + { + _ma_check_print_warning(param, + "Record checksum is not the same as checksum " + "stored in the index file"); + error=1; + } + else if (!extend) + { + uint key; + for (key=0 ; key < share->base.keys; key++) + { + if (param->tmp_key_crc[key] != param->key_crc[key] && + !(share->keyinfo[key].flag & + (HA_FULLTEXT | HA_SPATIAL | HA_RTREE_INDEX))) + { + _ma_check_print_error(param,"Checksum for key: %2d doesn't match " + "checksum for records", + key+1); + error=1; + } + } + } + + if (param->del_length != share->state.state.empty) + { + _ma_check_print_warning(param, + "Found %s deleted space. Should be %s", + llstr(param->del_length,llbuff2), + llstr(share->state.state.empty,llbuff)); + } + /* Skip following checks for BLOCK RECORD as they don't make any sence */ + if (share->data_file_type != BLOCK_RECORD) + { + if (param->used + param->empty + param->del_length != + share->state.state.data_file_length) + { + _ma_check_print_warning(param, + "Found %s record data and %s unused data and %s " + "deleted data", + llstr(param->used, llbuff), + llstr(param->empty,llbuff2), + llstr(param->del_length,llbuff3)); + _ma_check_print_warning(param, + "Total %s Should be: %s", + llstr((param->used+param->empty + + param->del_length), llbuff), + llstr(share->state.state.data_file_length, + llbuff2)); + } + if (param->del_blocks != share->state.state.del) + { + _ma_check_print_warning(param, + "Found %10s deleted blocks. Should be: %s", + llstr(param->del_blocks,llbuff), + llstr(share->state.state.del,llbuff2)); + } + if (param->splits != share->state.split) + { + _ma_check_print_warning(param, + "Found %10s parts. Should be: %s", + llstr(param->splits, llbuff), + llstr(share->state.split,llbuff2)); + } + } + if (param->testflag & T_INFO) + { + if (param->warning_printed || param->error_printed) + puts(""); + if (param->used != 0 && ! param->error_printed) + { + if (param->records) + { + printf("Records:%18s M.recordlength:%9lu Packed:%14.0f%%\n", + llstr(param->records,llbuff), + (long)((param->used - param->link_used)/param->records), + (share->base.blobs ? 0.0 : + (ulonglong2double((ulonglong) share->base.reclength * + param->records)- + my_off_t2double(param->used))/ + ulonglong2double((ulonglong) share->base.reclength * + param->records)*100.0)); + printf("Recordspace used:%9.0f%% Empty space:%12d%% " + "Blocks/Record: %6.2f\n", + (ulonglong2double(param->used - param->link_used)/ + ulonglong2double(param->used-param->link_used+param->empty) * + 100.0), + (!param->records ? 100 : + (int) (ulonglong2double(param->del_length+param->empty)/ + my_off_t2double(param->used)*100.0)), + ulonglong2double(param->splits - param->del_blocks) / + param->records); + } + else + printf("Records:%18s\n", "0"); + } + printf("Record blocks:%12s Delete blocks:%10s\n", + llstr(param->splits - param->del_blocks, llbuff), + llstr(param->del_blocks, llbuff2)); + printf("Record data: %12s Deleted data: %10s\n", + llstr(param->used - param->link_used,llbuff), + llstr(param->del_length, llbuff2)); + printf("Empty space: %12s Linkdata: %10s\n", + llstr(param->empty, llbuff),llstr(param->link_used, llbuff2)); + if (param->lost) + printf("Lost space: %12s", llstr(param->lost, llbuff)); + if (param->max_found_trid) + { + printf("Max trans. id: %11s\n", + llstr(param->max_found_trid, llbuff)); + } + } + my_free(record,MYF(0)); + DBUG_RETURN (error); + +err: + my_free(record,MYF(0)); + param->testflag|=T_RETRY_WITHOUT_QUICK; + DBUG_RETURN(1); +} /* maria_chk_data_link */ + + +/** + Prepares a table for a repair or index sort: flushes pages, records durably + in the table that it is undergoing the operation (if that op crashes, that + info will serve for Recovery and the user). + + If we start overwriting the index file, and crash then, old REDOs will + be tried and fail. To prevent that, we bump skip_redo_lsn, and thus we have + to flush and sync pages so that old REDOs can be skipped. + If this is not a bulk insert, which Recovery can handle gracefully (by + truncating files, see UNDO_BULK_INSERT) we also mark the table + crashed-on-repair, so that user knows it has to re-repair. If bulk insert we + shouldn't mark it crashed-on-repair, because if we did this, the UNDO phase + would skip the table (UNDO_BULK_INSERT would not be applied), + and maria_chk would not improve that. + If this is an OPTIMIZE which merely sorts index, we need to do the same + too: old REDOs should not apply to the new index file. + Only the flush is needed when in maria_chk which is not crash-safe. + + @param info table + @param param repair parameters + @param discard_index if index pages can be thrown away +*/ + +static my_bool protect_against_repair_crash(MARIA_HA *info, + const HA_CHECK *param, + my_bool discard_index) +{ + MARIA_SHARE *share= info->s; + + /* + There are other than recovery-related reasons to do the writes below: + - the physical size of the data file is sometimes used during repair: we + need to flush to have it exact + - we flush the state because maria_open(HA_OPEN_COPY) will want to read + it from disk. + */ + if (_ma_flush_table_files(info, MARIA_FLUSH_DATA | MARIA_FLUSH_INDEX, + FLUSH_FORCE_WRITE, + discard_index ? FLUSH_IGNORE_CHANGED : + FLUSH_FORCE_WRITE) || + (share->changed && + _ma_state_info_write(share, + MA_STATE_INFO_WRITE_DONT_MOVE_OFFSET | + MA_STATE_INFO_WRITE_FULL_INFO | + MA_STATE_INFO_WRITE_LOCK))) + return TRUE; + /* In maria_chk this is not needed: */ + if (maria_multi_threaded && share->base.born_transactional) + { + if ((param->testflag & T_NO_CREATE_RENAME_LSN) == 0) + { + /* this can be true only for a transactional table */ + maria_mark_crashed_on_repair(info); + if (_ma_state_info_write(share, + MA_STATE_INFO_WRITE_DONT_MOVE_OFFSET | + MA_STATE_INFO_WRITE_LOCK)) + return TRUE; + } + if (translog_status == TRANSLOG_OK && + _ma_update_state_lsns(share, translog_get_horizon(), + share->state.create_trid, FALSE, FALSE)) + return TRUE; + if (_ma_sync_table_files(info)) + return TRUE; + } + return FALSE; +} + + +/** + @brief Initialize variables for repair +*/ + +static int initialize_variables_for_repair(HA_CHECK *param, + MARIA_SORT_INFO *sort_info, + MARIA_SORT_PARAM *sort_param, + MARIA_HA *info, + my_bool rep_quick) +{ + MARIA_SHARE *share= info->s; + + /* Repair code relies on share->state.state so we have to update it here */ + if (share->lock.update_status) + (*share->lock.update_status)(info); + + bzero((char*) sort_info, sizeof(*sort_info)); + bzero((char*) sort_param, sizeof(*sort_param)); + + param->testflag|= T_REP; /* for easy checking */ + if (share->options & (HA_OPTION_CHECKSUM | HA_OPTION_COMPRESS_RECORD)) + param->testflag|= T_CALC_CHECKSUM; + param->glob_crc= 0; + if (rep_quick) + param->testflag|= T_QUICK; + else + param->testflag&= ~T_QUICK; + param->org_key_map= share->state.key_map; + + sort_param->sort_info= sort_info; + sort_param->fix_datafile= ! rep_quick; + sort_param->calc_checksum= test(param->testflag & T_CALC_CHECKSUM); + sort_info->info= sort_info->new_info= info; + sort_info->param= param; + set_data_file_type(sort_info, info->s); + sort_info->org_data_file_type= share->data_file_type; + + bzero(&info->rec_cache, sizeof(info->rec_cache)); + info->rec_cache.file= info->dfile.file; + info->update= (short) (HA_STATE_CHANGED | HA_STATE_ROW_CHANGED); + + if (protect_against_repair_crash(info, param, !test(param->testflag & + T_CREATE_MISSING_KEYS))) + return 1; + + /* calculate max_records */ + sort_info->filelength= my_seek(info->dfile.file, 0L, MY_SEEK_END, MYF(0)); + if ((param->testflag & T_CREATE_MISSING_KEYS) || + sort_info->org_data_file_type == COMPRESSED_RECORD) + sort_info->max_records= share->state.state.records; + else + { + ulong rec_length; + rec_length= max(share->base.min_pack_length, + share->base.min_block_length); + sort_info->max_records= (ha_rows) (sort_info->filelength / rec_length); + } + + /* Set up transaction handler so that we can see all rows */ + if (!ma_control_file_inited()) + param->max_trid= 0; /* Give warning for first trid found */ + else + param->max_trid= max_trid_in_system(); + + maria_ignore_trids(info); + /* Don't write transid's during repair */ + maria_versioning(info, 0); + return 0; +} + + +/** + @brief Drop all indexes + + @param[in] param check parameters + @param[in] info MARIA_HA handle + @param[in] force if to force drop all indexes + + @return status + @retval 0 OK + @retval != 0 Error + + @note + Once allocated, index blocks remain part of the key file forever. + When indexes are disabled, no block is freed. When enabling indexes, + no block is freed either. The new indexes are create from new + blocks. (Bug #4692) + + Before recreating formerly disabled indexes, the unused blocks + must be freed. There are two options to do this: + - Follow the tree of disabled indexes, add all blocks to the + deleted blocks chain. Would require a lot of random I/O. + - Drop all blocks by clearing all index root pointers and all + delete chain pointers and resetting key_file_length to the end + of the index file header. This requires to recreate all indexes, + even those that may still be intact. + The second method is probably faster in most cases. + + When disabling indexes, MySQL disables either all indexes or all + non-unique indexes. When MySQL [re-]enables disabled indexes + (T_CREATE_MISSING_KEYS), then we either have "lost" blocks in the + index file, or there are no non-unique indexes. In the latter case, + maria_repair*() would not be called as there would be no disabled + indexes. + + If there would be more unique indexes than disabled (non-unique) + indexes, we could do the first method. But this is not implemented + yet. By now we drop and recreate all indexes when repair is called. + + However, there is an exception. Sometimes MySQL disables non-unique + indexes when the table is empty (e.g. when copying a table in + mysql_alter_table()). When enabling the non-unique indexes, they + are still empty. So there is no index block that can be lost. This + optimization is implemented in this function. + + Note that in normal repair (T_CREATE_MISSING_KEYS not set) we + recreate all enabled indexes unconditonally. We do not change the + key_map. Otherwise we invert the key map temporarily (outside of + this function) and recreate the then "seemingly" enabled indexes. + When we cannot use the optimization, and drop all indexes, we + pretend that all indexes were disabled. By the inversion, we will + then recrate all indexes. +*/ + +static int maria_drop_all_indexes(HA_CHECK *param, MARIA_HA *info, + my_bool force) +{ + MARIA_SHARE *share= info->s; + MARIA_STATE_INFO *state= &share->state; + uint i; + DBUG_ENTER("maria_drop_all_indexes"); + + /* + If any of the disabled indexes has a key block assigned, we must + drop and recreate all indexes to avoid losing index blocks. + + If we want to recreate disabled indexes only _and_ all of these + indexes are empty, we don't need to recreate the existing indexes. + */ + if (!force && (param->testflag & T_CREATE_MISSING_KEYS)) + { + DBUG_PRINT("repair", ("creating missing indexes")); + for (i= 0; i < share->base.keys; i++) + { + DBUG_PRINT("repair", ("index #: %u key_root: 0x%lx active: %d", + i, (long) state->key_root[i], + maria_is_key_active(state->key_map, i))); + if ((state->key_root[i] != HA_OFFSET_ERROR) && + !maria_is_key_active(state->key_map, i)) + { + /* + This index has at least one key block and it is disabled. + We would lose its block(s) if would just recreate it. + So we need to drop and recreate all indexes. + */ + DBUG_PRINT("repair", ("nonempty and disabled: recreate all")); + break; + } + } + if (i >= share->base.keys) + goto end; + + /* + We do now drop all indexes and declare them disabled. With the + T_CREATE_MISSING_KEYS flag, maria_repair*() will recreate all + disabled indexes and enable them. + */ + maria_clear_all_keys_active(state->key_map); + DBUG_PRINT("repair", ("declared all indexes disabled")); + } + + /* Clear index root block pointers. */ + for (i= 0; i < share->base.keys; i++) + state->key_root[i]= HA_OFFSET_ERROR; + + /* Drop the delete chain. */ + share->state.key_del= HA_OFFSET_ERROR; + + /* Reset index file length to end of index file header. */ + share->state.state.key_file_length= share->base.keystart; + +end: + DBUG_RETURN(0); +} + + +/* + Recover old table by reading each record and writing all keys + + NOTES + Save new datafile-name in temp_filename. + We overwrite the index file as we go (writekeys() for example), so if we + crash during this the table is unusable and user (or Recovery in the + future) must repeat the REPAIR/OPTIMIZE operation. We could use a + temporary index file in the future (drawback: more disk space). + + IMPLEMENTATION (for hard repair with block format) + - Create new, unrelated MARIA_HA of the table + - Create new datafile and associate it with new handler + - Reset all statistic information in new handler + - Copy all data to new handler with normal write operations + - Move state of new handler to old handler + - Close new handler + - Close data file in old handler + - Rename old data file to new data file. + - Reopen data file in old handler +*/ + +int maria_repair(HA_CHECK *param, register MARIA_HA *info, + char *name, my_bool rep_quick) +{ + int error, got_error; + ha_rows start_records,new_header_length; + my_off_t del; + File new_file; + MARIA_SHARE *share= info->s; + char llbuff[22],llbuff2[22]; + MARIA_SORT_INFO sort_info; + MARIA_SORT_PARAM sort_param; + my_bool block_record, scan_inited= 0, + reenable_logging= share->now_transactional; + enum data_file_type org_data_file_type= share->data_file_type; + myf sync_dir= ((share->now_transactional && !share->temporary) ? + MY_SYNC_DIR : 0); + DBUG_ENTER("maria_repair"); + + got_error= 1; + new_file= -1; + start_records= share->state.state.records; + if (!(param->testflag & T_SILENT)) + { + printf("- recovering (with keycache) MARIA-table '%s'\n",name); + printf("Data records: %s\n", llstr(start_records, llbuff)); + } + + if (initialize_variables_for_repair(param, &sort_info, &sort_param, info, + rep_quick)) + goto err; + + if (reenable_logging) + _ma_tmp_disable_logging_for_table(info, 0); + + sort_param.current_filepos= sort_param.filepos= new_header_length= + ((param->testflag & T_UNPACK) ? 0L : share->pack.header_length); + + if (!rep_quick) + { + /* Get real path for data file */ + if ((new_file= my_create(fn_format(param->temp_filename, + share->data_file_name.str, "", + DATA_TMP_EXT, 2+4), + 0,param->tmpfile_createflag, + MYF(0))) < 0) + { + _ma_check_print_error(param,"Can't create new tempfile: '%s'", + param->temp_filename); + goto err; + } + if (new_header_length && + maria_filecopy(param, new_file, info->dfile.file, 0L, + new_header_length, "datafile-header")) + goto err; + share->state.dellink= HA_OFFSET_ERROR; + info->rec_cache.file= new_file; /* For sort_delete_record */ + if (share->data_file_type == BLOCK_RECORD || + (param->testflag & T_UNPACK)) + { + if (create_new_data_handle(&sort_param, new_file)) + goto err; + sort_info.new_info->rec_cache.file= new_file; + } + } + + block_record= sort_info.new_info->s->data_file_type == BLOCK_RECORD; + + if (org_data_file_type != BLOCK_RECORD) + { + /* We need a read buffer to read rows in big blocks */ + if (init_io_cache(¶m->read_cache, info->dfile.file, + (uint) param->read_buffer_length, + READ_CACHE, share->pack.header_length, 1, MYF(MY_WME))) + goto err; + } + if (sort_info.new_info->s->data_file_type != BLOCK_RECORD) + { + /* When writing to not block records, we need a write buffer */ + if (!rep_quick) + { + if (init_io_cache(&sort_info.new_info->rec_cache, new_file, + (uint) param->write_buffer_length, + WRITE_CACHE, new_header_length, 1, + MYF(MY_WME | MY_WAIT_IF_FULL) & param->myf_rw)) + goto err; + sort_info.new_info->opt_flag|=WRITE_CACHE_USED; + } + } + else if (block_record) + { + scan_inited= 1; + if (maria_scan_init(sort_info.info)) + goto err; + } + + if (!(sort_param.record= + (uchar *) my_malloc((uint) + share->base.default_rec_buff_size, MYF(0))) || + _ma_alloc_buffer(&sort_param.rec_buff, &sort_param.rec_buff_size, + share->base.default_rec_buff_size)) + { + _ma_check_print_error(param, "Not enough memory for extra record"); + goto err; + } + + sort_param.read_cache=param->read_cache; + sort_param.pos=sort_param.max_pos=share->pack.header_length; + param->read_cache.end_of_file= sort_info.filelength; + sort_param.master=1; + sort_info.max_records= ~(ha_rows) 0; + + del= share->state.state.del; + share->state.state.records= share->state.state.del= share->state.split= 0; + share->state.state.empty= 0; + + if (param->testflag & T_CREATE_MISSING_KEYS) + maria_set_all_keys_active(share->state.key_map, share->base.keys); + maria_drop_all_indexes(param, info, TRUE); + + maria_lock_memory(param); /* Everything is alloced */ + + /* Re-create all keys, which are set in key_map. */ + while (!(error=sort_get_next_record(&sort_param))) + { + if (block_record && _ma_sort_write_record(&sort_param)) + goto err; + + if (writekeys(&sort_param)) + { + if (my_errno != HA_ERR_FOUND_DUPP_KEY) + goto err; + DBUG_DUMP("record", sort_param.record, + share->base.default_rec_buff_size); + _ma_check_print_warning(param, + "Duplicate key %2d for record at %10s against " + "new record at %10s", + info->errkey+1, + llstr(sort_param.current_filepos, llbuff), + llstr(info->dup_key_pos,llbuff2)); + if (param->testflag & T_VERBOSE) + { + MARIA_KEY tmp_key; + MARIA_KEYDEF *keyinfo= share->keyinfo + info->errkey; + (*keyinfo->make_key)(info, &tmp_key, (uint) info->errkey, + info->lastkey_buff, + sort_param.record, 0L, 0); + _ma_print_key(stdout, &tmp_key); + } + sort_info.dupp++; + if ((param->testflag & (T_FORCE_UNIQUENESS|T_QUICK)) == T_QUICK) + { + param->testflag|=T_RETRY_WITHOUT_QUICK; + param->error_printed=1; + goto err; + } + /* purecov: begin tested */ + if (block_record) + { + sort_info.new_info->s->state.state.records--; + if ((*sort_info.new_info->s->write_record_abort)(sort_info.new_info)) + { + _ma_check_print_error(param,"Couldn't delete duplicate row"); + goto err; + } + } + /* purecov: end */ + continue; + } + if (!block_record) + { + if (_ma_sort_write_record(&sort_param)) + goto err; + /* Filepos is pointer to where next row will be stored */ + sort_param.current_filepos= sort_param.filepos; + } + } + if (error > 0 || maria_write_data_suffix(&sort_info, !rep_quick) || + flush_io_cache(&sort_info.new_info->rec_cache) || + param->read_cache.error < 0) + goto err; + + if (param->testflag & T_WRITE_LOOP) + { + VOID(fputs(" \r",stdout)); VOID(fflush(stdout)); + } + if (my_chsize(share->kfile.file, share->state.state.key_file_length, 0, MYF(0))) + { + _ma_check_print_warning(param, + "Can't change size of indexfile, error: %d", + my_errno); + goto err; + } + + if (rep_quick && del+sort_info.dupp != share->state.state.del) + { + _ma_check_print_error(param,"Couldn't fix table with quick recovery: " + "Found wrong number of deleted records"); + _ma_check_print_error(param,"Run recovery again without -q"); + param->retry_repair=1; + param->testflag|=T_RETRY_WITHOUT_QUICK; + goto err; + } + + if (param->testflag & T_SAFE_REPAIR) + { + /* Don't repair if we loosed more than one row */ + if (sort_info.new_info->s->state.state.records+1 < start_records) + { + share->state.state.records= start_records; + goto err; + } + } + + VOID(end_io_cache(&sort_info.new_info->rec_cache)); + info->opt_flag&= ~WRITE_CACHE_USED; + + /* + As we have read the data file (sort_get_next_record()) we may have + cached, non-changed blocks of it in the page cache. We must throw them + away as we are going to close their descriptor ('new_file'). We also want + to flush any index block, so that it is ready for the upcoming sync. + */ + if (_ma_flush_table_files_before_swap(param, info)) + goto err; + + if (!rep_quick) + { + sort_info.new_info->s->state.state.data_file_length= sort_param.filepos; + if (sort_info.new_info != sort_info.info) + { + MARIA_STATE_INFO save_state= sort_info.new_info->s->state; + if (maria_close(sort_info.new_info)) + { + _ma_check_print_error(param, "Got error %d on close", my_errno); + goto err; + } + copy_data_file_state(&share->state, &save_state); + new_file= -1; + sort_info.new_info= info; + } + share->state.version=(ulong) time((time_t*) 0); /* Force reopen */ + + /* Replace the actual file with the temporary file */ + if (new_file >= 0) + my_close(new_file, MYF(MY_WME)); + new_file= -1; + change_data_file_descriptor(info, -1); + if (maria_change_to_newfile(share->data_file_name.str, MARIA_NAME_DEXT, + DATA_TMP_EXT, + (param->testflag & T_BACKUP_DATA ? + MYF(MY_REDEL_MAKE_BACKUP): MYF(0)) | + sync_dir) || + _ma_open_datafile(info, share, NullS, -1)) + { + goto err; + } + } + else + { + share->state.state.data_file_length= sort_param.max_pos; + } + if (param->testflag & T_CALC_CHECKSUM) + share->state.state.checksum= param->glob_crc; + + if (!(param->testflag & T_SILENT)) + { + if (start_records != share->state.state.records) + printf("Data records: %s\n", llstr(share->state.state.records,llbuff)); + } + if (sort_info.dupp) + _ma_check_print_warning(param, + "%s records have been removed", + llstr(sort_info.dupp,llbuff)); + + got_error= 0; + /* If invoked by external program that uses thr_lock */ + if (&share->state.state != info->state) + *info->state= *info->state_start= share->state.state; + +err: + if (scan_inited) + maria_scan_end(sort_info.info); + _ma_reset_state(info); + + VOID(end_io_cache(¶m->read_cache)); + VOID(end_io_cache(&sort_info.new_info->rec_cache)); + info->opt_flag&= ~(READ_CACHE_USED | WRITE_CACHE_USED); + sort_info.new_info->opt_flag&= ~(READ_CACHE_USED | WRITE_CACHE_USED); + /* this below could fail, shouldn't we detect error? */ + if (got_error) + { + if (! param->error_printed) + _ma_check_print_error(param,"%d for record at pos %s",my_errno, + llstr(sort_param.start_recpos,llbuff)); + (void)_ma_flush_table_files_before_swap(param, info); + if (sort_info.new_info && sort_info.new_info != sort_info.info) + { + unuse_data_file_descriptor(sort_info.new_info); + maria_close(sort_info.new_info); + } + if (new_file >= 0) + { + VOID(my_close(new_file,MYF(0))); + VOID(my_delete(param->temp_filename, MYF(MY_WME))); + } + maria_mark_crashed_on_repair(info); + } + /* If caller had disabled logging it's not up to us to re-enable it */ + if (reenable_logging) + _ma_reenable_logging_for_table(info, FALSE); + + my_free(sort_param.rec_buff, MYF(MY_ALLOW_ZERO_PTR)); + my_free(sort_param.record,MYF(MY_ALLOW_ZERO_PTR)); + my_free(sort_info.buff,MYF(MY_ALLOW_ZERO_PTR)); + if (!got_error && (param->testflag & T_UNPACK)) + restore_data_file_type(share); + share->state.changed|= (STATE_NOT_OPTIMIZED_KEYS | STATE_NOT_SORTED_PAGES | + STATE_NOT_ANALYZED | STATE_NOT_ZEROFILLED); + if (!rep_quick) + share->state.changed&= ~(STATE_NOT_OPTIMIZED_ROWS | STATE_NOT_MOVABLE); + DBUG_RETURN(got_error); +} + + +/* Uppdate keyfile when doing repair */ + +static int writekeys(MARIA_SORT_PARAM *sort_param) +{ + uint i; + MARIA_HA *info= sort_param->sort_info->info; + MARIA_SHARE *share= info->s; + uchar *record= sort_param->record; + uchar *key_buff; + my_off_t filepos= sort_param->current_filepos; + MARIA_KEY key; + DBUG_ENTER("writekeys"); + + key_buff= info->lastkey_buff+share->base.max_key_length; + + for (i=0 ; i < share->base.keys ; i++) + { + if (maria_is_key_active(share->state.key_map, i)) + { + if (share->keyinfo[i].flag & HA_FULLTEXT ) + { + if (_ma_ft_add(info, i, key_buff, record, filepos)) + goto err; + } + else + { + if (!(*share->keyinfo[i].make_key)(info, &key, i, key_buff, record, + filepos, 0)) + goto err; + if ((*share->keyinfo[i].ck_insert)(info, &key)) + goto err; + } + } + } + DBUG_RETURN(0); + + err: + if (my_errno == HA_ERR_FOUND_DUPP_KEY) + { + info->errkey=(int) i; /* This key was found */ + while ( i-- > 0 ) + { + if (maria_is_key_active(share->state.key_map, i)) + { + if (share->keyinfo[i].flag & HA_FULLTEXT) + { + if (_ma_ft_del(info,i,key_buff,record,filepos)) + break; + } + else + { + (*share->keyinfo[i].make_key)(info, &key, i, key_buff, record, + filepos, 0); + if (_ma_ck_delete(info, &key)) + break; + } + } + } + } + /* Remove checksum that was added to glob_crc in sort_get_next_record */ + if (sort_param->calc_checksum) + sort_param->sort_info->param->glob_crc-= info->cur_row.checksum; + DBUG_PRINT("error",("errno: %d",my_errno)); + DBUG_RETURN(-1); +} /* writekeys */ + + + /* Change all key-pointers that points to a records */ + +int maria_movepoint(register MARIA_HA *info, uchar *record, + MARIA_RECORD_POS oldpos, MARIA_RECORD_POS newpos, + uint prot_key) +{ + uint i; + uchar *key_buff; + MARIA_SHARE *share= info->s; + MARIA_PAGE page; + DBUG_ENTER("maria_movepoint"); + + key_buff= info->lastkey_buff + share->base.max_key_length; + for (i=0 ; i < share->base.keys; i++) + { + if (i != prot_key && maria_is_key_active(share->state.key_map, i)) + { + MARIA_KEY key; + (*share->keyinfo[i].make_key)(info, &key, i, key_buff, record, oldpos, + 0); + if (key.keyinfo->flag & HA_NOSAME) + { /* Change pointer direct */ + MARIA_KEYDEF *keyinfo; + keyinfo=share->keyinfo+i; + if (_ma_search(info, &key, (uint32) (SEARCH_SAME | SEARCH_SAVE_BUFF), + share->state.key_root[i])) + DBUG_RETURN(-1); + _ma_page_setup(&page, info, keyinfo, info->last_keypage, + info->keyread_buff); + + _ma_dpointer(share, info->int_keypos - page.node - + share->rec_reflength,newpos); + + if (_ma_write_keypage(&page, PAGECACHE_LOCK_LEFT_UNLOCKED, + DFLT_INIT_HITS)) + DBUG_RETURN(-1); + } + else + { /* Change old key to new */ + if (_ma_ck_delete(info, &key)) + DBUG_RETURN(-1); + (*share->keyinfo[i].make_key)(info, &key, i, key_buff, record, newpos, + 0); + if (_ma_ck_write(info, &key)) + DBUG_RETURN(-1); + } + } + } + DBUG_RETURN(0); +} /* maria_movepoint */ + + + /* Tell system that we want all memory for our cache */ + +void maria_lock_memory(HA_CHECK *param __attribute__((unused))) +{ +#ifdef SUN_OS /* Key-cacheing thrases on sun 4.1 */ + if (param->opt_maria_lock_memory) + { + int success = mlockall(MCL_CURRENT); /* or plock(DATLOCK); */ + if (geteuid() == 0 && success != 0) + _ma_check_print_warning(param, + "Failed to lock memory. errno %d",my_errno); + } +#endif +} /* maria_lock_memory */ + + +/** + Flush all changed blocks to disk. + + We release blocks as it's unlikely that they would all be needed soon. + This function needs to be called before swapping data or index files or + syncing them. + + @param param description of the repair operation + @param info table +*/ + +static my_bool _ma_flush_table_files_before_swap(HA_CHECK *param, + MARIA_HA *info) +{ + DBUG_ENTER("_ma_flush_table_files_before_swap"); + if (_ma_flush_table_files(info, MARIA_FLUSH_DATA | MARIA_FLUSH_INDEX, + FLUSH_RELEASE, FLUSH_RELEASE)) + { + _ma_check_print_error(param, "%d when trying to write buffers", my_errno); + DBUG_RETURN(TRUE); + } + DBUG_RETURN(FALSE); +} + + + /* Sort index for more efficent reads */ + +int maria_sort_index(HA_CHECK *param, register MARIA_HA *info, char *name) +{ + reg2 uint key; + reg1 MARIA_KEYDEF *keyinfo; + File new_file; + my_off_t index_pos[HA_MAX_POSSIBLE_KEY]; + uint r_locks,w_locks; + int old_lock; + MARIA_SHARE *share= info->s; + MARIA_STATE_INFO old_state; + myf sync_dir= ((share->now_transactional && !share->temporary) ? + MY_SYNC_DIR : 0); + DBUG_ENTER("maria_sort_index"); + + /* cannot sort index files with R-tree indexes */ + for (key= 0,keyinfo= &share->keyinfo[0]; key < share->base.keys ; + key++,keyinfo++) + if (keyinfo->key_alg == HA_KEY_ALG_RTREE) + DBUG_RETURN(0); + + if (!(param->testflag & T_SILENT)) + printf("- Sorting index for MARIA-table '%s'\n",name); + + if (protect_against_repair_crash(info, param, FALSE)) + DBUG_RETURN(1); + + /* Get real path for index file */ + fn_format(param->temp_filename,name,"", MARIA_NAME_IEXT,2+4+32); + if ((new_file=my_create(fn_format(param->temp_filename,param->temp_filename, + "", INDEX_TMP_EXT,2+4), + 0,param->tmpfile_createflag,MYF(0))) <= 0) + { + _ma_check_print_error(param,"Can't create new tempfile: '%s'", + param->temp_filename); + DBUG_RETURN(-1); + } + if (maria_filecopy(param, new_file, share->kfile.file, 0L, + (ulong) share->base.keystart, "headerblock")) + goto err; + + param->new_file_pos=share->base.keystart; + for (key= 0,keyinfo= &share->keyinfo[0]; key < share->base.keys ; + key++,keyinfo++) + { + if (! maria_is_key_active(share->state.key_map, key)) + continue; + + if (share->state.key_root[key] != HA_OFFSET_ERROR) + { + index_pos[key]=param->new_file_pos; /* Write first block here */ + if (sort_one_index(param,info,keyinfo,share->state.key_root[key], + new_file)) + goto err; + } + else + index_pos[key]= HA_OFFSET_ERROR; /* No blocks */ + } + + /* Flush key cache for this file if we are calling this outside maria_chk */ + flush_pagecache_blocks(share->pagecache, &share->kfile, + FLUSH_IGNORE_CHANGED); + + share->state.version=(ulong) time((time_t*) 0); + old_state= share->state; /* save state if not stored */ + r_locks= share->r_locks; + w_locks= share->w_locks; + old_lock= info->lock_type; + + /* Put same locks as old file */ + share->r_locks= share->w_locks= share->tot_locks= 0; + (void) _ma_writeinfo(info,WRITEINFO_UPDATE_KEYFILE); + pthread_mutex_lock(&share->intern_lock); + VOID(my_close(share->kfile.file, MYF(MY_WME))); + share->kfile.file = -1; + pthread_mutex_unlock(&share->intern_lock); + VOID(my_close(new_file,MYF(MY_WME))); + if (maria_change_to_newfile(share->index_file_name.str, MARIA_NAME_IEXT, + INDEX_TMP_EXT, sync_dir) || + _ma_open_keyfile(share)) + goto err2; + info->lock_type= F_UNLCK; /* Force maria_readinfo to lock */ + _ma_readinfo(info,F_WRLCK,0); /* Will lock the table */ + info->lock_type= old_lock; + share->r_locks= r_locks; + share->w_locks= w_locks; + share->tot_locks= r_locks+w_locks; + share->state= old_state; /* Restore old state */ + + share->state.state.key_file_length=param->new_file_pos; + info->update= (short) (HA_STATE_CHANGED | HA_STATE_ROW_CHANGED); + for (key=0 ; key < share->base.keys ; key++) + share->state.key_root[key]=index_pos[key]; + share->state.key_del= HA_OFFSET_ERROR; + + share->state.changed&= ~STATE_NOT_SORTED_PAGES; + DBUG_EXECUTE_IF("maria_flush_whole_log", + { + DBUG_PRINT("maria_flush_whole_log", ("now")); + translog_flush(translog_get_horizon()); + }); + DBUG_EXECUTE_IF("maria_crash_sort_index", + { + DBUG_PRINT("maria_crash_sort_index", ("now")); + DBUG_ABORT(); + }); + DBUG_RETURN(0); + +err: + VOID(my_close(new_file,MYF(MY_WME))); +err2: + VOID(my_delete(param->temp_filename,MYF(MY_WME))); + DBUG_RETURN(-1); +} /* maria_sort_index */ + + +/** + @brief put CRC on the page + + @param buff reference on the page buffer. + @param pos position of the page in the file. + @param length length of the page +*/ + +static void put_crc(uchar *buff, my_off_t pos, MARIA_SHARE *share) +{ + maria_page_crc_set_index(buff, (pgcache_page_no_t) (pos / share->block_size), + (uchar*) share); +} + + +/* Sort index blocks recursive using one index */ + +static int sort_one_index(HA_CHECK *param, MARIA_HA *info, + MARIA_KEYDEF *keyinfo, + my_off_t pagepos, File new_file) +{ + uint length,nod_flag; + uchar *buff,*keypos,*endpos; + my_off_t new_page_pos,next_page; + MARIA_SHARE *share= info->s; + MARIA_KEY key; + MARIA_PAGE page; + DBUG_ENTER("sort_one_index"); + + /* cannot walk over R-tree indices */ + DBUG_ASSERT(keyinfo->key_alg != HA_KEY_ALG_RTREE); + new_page_pos=param->new_file_pos; + param->new_file_pos+=keyinfo->block_length; + key.keyinfo= keyinfo; + key.data= info->lastkey_buff; + + if (!(buff= (uchar*) my_alloca((uint) keyinfo->block_length))) + { + _ma_check_print_error(param,"Not enough memory for key block"); + DBUG_RETURN(-1); + } + if (_ma_fetch_keypage(&page, info, keyinfo, pagepos, + PAGECACHE_LOCK_LEFT_UNLOCKED, + DFLT_INIT_HITS, buff, 0)) + { + report_keypage_fault(param, info, pagepos); + goto err; + } + + if ((nod_flag= page.node) || keyinfo->flag & HA_FULLTEXT) + { + keypos= page.buff + share->keypage_header + nod_flag; + endpos= page.buff + page.size; + + for ( ;; ) + { + if (nod_flag) + { + next_page= _ma_kpos(nod_flag,keypos); + /* Save new pos */ + _ma_kpointer(info,keypos-nod_flag,param->new_file_pos); + if (sort_one_index(param,info,keyinfo,next_page,new_file)) + { + DBUG_PRINT("error", + ("From page: %ld, keyoffset: %lu used_length: %d", + (ulong) pagepos, (ulong) (keypos - buff), + (int) page.size)); + DBUG_DUMP("buff", page.buff, page.size); + goto err; + } + } + if (keypos >= endpos || + !(*keyinfo->get_key)(&key, page.flag, nod_flag, &keypos)) + break; + DBUG_ASSERT(keypos <= endpos); + if (keyinfo->flag & HA_FULLTEXT) + { + uint off; + int subkeys; + get_key_full_length_rdonly(off, key.data); + subkeys= ft_sintXkorr(key.data + off); + if (subkeys < 0) + { + next_page= _ma_row_pos_from_key(&key); + _ma_dpointer(share, keypos - nod_flag - share->rec_reflength, + param->new_file_pos); /* Save new pos */ + if (sort_one_index(param,info,&share->ft2_keyinfo, + next_page,new_file)) + goto err; + } + } + } + } + + /* Fill block with zero and write it to the new index file */ + length= page.size; + bzero(buff+length,keyinfo->block_length-length); + put_crc(buff, new_page_pos, share); + if (my_pwrite(new_file, buff,(uint) keyinfo->block_length, + new_page_pos,MYF(MY_NABP | MY_WAIT_IF_FULL))) + { + _ma_check_print_error(param,"Can't write indexblock, error: %d",my_errno); + goto err; + } + my_afree(buff); + DBUG_RETURN(0); +err: + my_afree(buff); + DBUG_RETURN(1); +} /* sort_one_index */ + + +/** + @brief Fill empty space in index file with zeroes + + @return + @retval 0 Ok + @retval 1 Error +*/ + +static my_bool maria_zerofill_index(HA_CHECK *param, MARIA_HA *info, + const char *name) +{ + MARIA_SHARE *share= info->s; + MARIA_PINNED_PAGE page_link; + char llbuff[21]; + uchar *buff; + pgcache_page_no_t page; + my_off_t pos; + my_off_t key_file_length= share->state.state.key_file_length; + uint block_size= share->block_size; + my_bool zero_lsn= (share->base.born_transactional && + !(param->testflag & T_ZEROFILL_KEEP_LSN)); + DBUG_ENTER("maria_zerofill_index"); + + if (!(param->testflag & T_SILENT)) + printf("- Zerofilling index for MARIA-table '%s'\n",name); + + /* Go through the index file */ + for (pos= share->base.keystart, page= (ulonglong) (pos / block_size); + pos < key_file_length; + pos+= block_size, page++) + { + uint length; + if (!(buff= pagecache_read(share->pagecache, + &share->kfile, page, + DFLT_INIT_HITS, 0, + PAGECACHE_PLAIN_PAGE, PAGECACHE_LOCK_WRITE, + &page_link.link))) + { + pagecache_unlock_by_link(share->pagecache, page_link.link, + PAGECACHE_LOCK_WRITE_UNLOCK, + PAGECACHE_UNPIN, LSN_IMPOSSIBLE, + LSN_IMPOSSIBLE, 0, FALSE); + _ma_check_print_error(param, + "Page %9s: Got error %d when reading index file", + llstr(pos, llbuff), my_errno); + DBUG_RETURN(1); + } + if (zero_lsn) + bzero(buff, LSN_SIZE); + + if (share->base.born_transactional) + { + uint keynr= _ma_get_keynr(share, buff); + if (keynr != MARIA_DELETE_KEY_NR) + { + MARIA_PAGE page; + DBUG_ASSERT(keynr < share->base.keys); + + _ma_page_setup(&page, info, share->keyinfo + keynr, pos, buff); + if (_ma_compact_keypage(&page, ~(TrID) 0)) + { + _ma_check_print_error(param, + "Page %9s: Got error %d when reading index " + "file", + llstr(pos, llbuff), my_errno); + DBUG_RETURN(1); + } + } + } + + length= _ma_get_page_used(share, buff); + DBUG_ASSERT(length <= block_size); + if (length < block_size) + bzero(buff + length, block_size - length); + pagecache_unlock_by_link(share->pagecache, page_link.link, + PAGECACHE_LOCK_WRITE_UNLOCK, + PAGECACHE_UNPIN, LSN_IMPOSSIBLE, + LSN_IMPOSSIBLE, 1, FALSE); + } + if (flush_pagecache_blocks(share->pagecache, &share->kfile, + FLUSH_FORCE_WRITE)) + DBUG_RETURN(1); + DBUG_RETURN(0); +} + + +/** + @brief Fill empty space in data file with zeroes + + @todo + Zerofill all pages marked in bitmap as empty and change them to + be of type UNALLOCATED_PAGE + + @return + @retval 0 Ok + @retval 1 Error +*/ + +static my_bool maria_zerofill_data(HA_CHECK *param, MARIA_HA *info, + const char *name) +{ + MARIA_SHARE *share= info->s; + MARIA_PINNED_PAGE page_link; + char llbuff[21]; + my_off_t pos; + pgcache_page_no_t page; + uint block_size= share->block_size; + MARIA_FILE_BITMAP *bitmap= &share->bitmap; + my_bool zero_lsn= !(param->testflag & T_ZEROFILL_KEEP_LSN), error; + DBUG_ENTER("maria_zerofill_data"); + + /* This works only with BLOCK_RECORD files */ + if (share->data_file_type != BLOCK_RECORD) + DBUG_RETURN(0); + + if (!(param->testflag & T_SILENT)) + printf("- Zerofilling data for MARIA-table '%s'\n",name); + + /* Go through the record file */ + for (page= 1, pos= block_size; + pos < share->state.state.data_file_length; + pos+= block_size, page++) + { + uchar *buff; + enum en_page_type page_type; + + /* Ignore bitmap pages */ + if ((page % share->bitmap.pages_covered) == 0) + continue; + if (!(buff= pagecache_read(share->pagecache, + &info->dfile, + page, 1, 0, + PAGECACHE_PLAIN_PAGE, PAGECACHE_LOCK_WRITE, + &page_link.link))) + { + _ma_check_print_error(param, + "Page %9s: Got error: %d when reading datafile", + llstr(pos, llbuff), my_errno); + goto err; + } + page_type= (enum en_page_type) (buff[PAGE_TYPE_OFFSET] & PAGE_TYPE_MASK); + switch (page_type) { + case UNALLOCATED_PAGE: + if (zero_lsn) + bzero(buff, block_size); + else + bzero(buff + LSN_SIZE, block_size - LSN_SIZE); + break; + case BLOB_PAGE: + if (_ma_bitmap_get_page_bits(info, bitmap, page) == 0) + { + /* Unallocated page */ + if (zero_lsn) + bzero(buff, block_size); + else + bzero(buff + LSN_SIZE, block_size - LSN_SIZE); + } + else + if (zero_lsn) + bzero(buff, LSN_SIZE); + break; + case HEAD_PAGE: + case TAIL_PAGE: + { + uint max_entry= (uint) buff[DIR_COUNT_OFFSET]; + uint offset, dir_start; + uchar *dir; + + if (zero_lsn) + bzero(buff, LSN_SIZE); + if (max_entry != 0) + { + my_bool is_head_page= (page_type == HEAD_PAGE); + dir= dir_entry_pos(buff, block_size, max_entry - 1); + _ma_compact_block_page(buff, block_size, max_entry -1, 0, + is_head_page ? ~(TrID) 0 : 0, + is_head_page ? + share->base.min_block_length : 0); + /* compactation may have increased free space */ + if (_ma_bitmap_set(info, page, is_head_page, + uint2korr(buff + EMPTY_SPACE_OFFSET))) + goto err; + + /* Zerofill the not used part */ + offset= uint2korr(dir) + uint2korr(dir+2); + dir_start= (uint) (dir - buff); + DBUG_ASSERT(dir_start >= offset); + if (dir_start > offset) + bzero(buff + offset, dir_start - offset); + } + break; + } + default: + _ma_check_print_error(param, + "Page %9s: Found unrecognizable block of type %d", + llstr(pos, llbuff), page_type); + goto err; + } + pagecache_unlock_by_link(share->pagecache, page_link.link, + PAGECACHE_LOCK_WRITE_UNLOCK, + PAGECACHE_UNPIN, LSN_IMPOSSIBLE, + LSN_IMPOSSIBLE, 1, FALSE); + } + error= _ma_bitmap_flush(share); + if (flush_pagecache_blocks(share->pagecache, &info->dfile, + FLUSH_FORCE_WRITE)) + error= 1; + DBUG_RETURN(error); + +err: + pagecache_unlock_by_link(share->pagecache, page_link.link, + PAGECACHE_LOCK_WRITE_UNLOCK, + PAGECACHE_UNPIN, LSN_IMPOSSIBLE, + LSN_IMPOSSIBLE, 0, FALSE); + /* flush what was changed so far */ + (void) _ma_bitmap_flush(share); + (void) flush_pagecache_blocks(share->pagecache, &info->dfile, + FLUSH_FORCE_WRITE); + + DBUG_RETURN(1); +} + + +/** + @brief Fill empty space in index and data files with zeroes + + @return + @retval 0 Ok + @retval 1 Error +*/ + +int maria_zerofill(HA_CHECK *param, MARIA_HA *info, const char *name) +{ + my_bool error, reenable_logging, + zero_lsn= !(param->testflag & T_ZEROFILL_KEEP_LSN); + MARIA_SHARE *share= info->s; + DBUG_ENTER("maria_zerofill"); + if ((reenable_logging= share->now_transactional)) + _ma_tmp_disable_logging_for_table(info, 0); + if (!(error= (maria_zerofill_index(param, info, name) || + maria_zerofill_data(param, info, name) || + _ma_set_uuid(info, 0)))) + { + /* + Mark that we have done zerofill of data and index. If we zeroed pages' + LSN, table is movable. + */ + share->state.changed&= ~STATE_NOT_ZEROFILLED; + if (zero_lsn) + { + share->state.changed&= ~(STATE_NOT_MOVABLE | STATE_MOVED); + /* Table should get new LSNs */ + share->state.create_rename_lsn= share->state.is_of_horizon= + share->state.skip_redo_lsn= LSN_NEEDS_NEW_STATE_LSNS; + } + /* Ensure state is later flushed to disk, if within maria_chk */ + info->update= (HA_STATE_CHANGED | HA_STATE_ROW_CHANGED); + + /* Reset create_trid to make file comparable */ + share->state.create_trid= 0; + } + if (reenable_logging) + _ma_reenable_logging_for_table(info, FALSE); + DBUG_RETURN(error); +} + + +/* + Let temporary file replace old file. + This assumes that the new file was created in the same + directory as given by realpath(filename). + This will ensure that any symlinks that are used will still work. + Copy stats from old file to new file, deletes orignal and + changes new file name to old file name +*/ + +int maria_change_to_newfile(const char * filename, const char * old_ext, + const char * new_ext, myf MyFlags) +{ + char old_filename[FN_REFLEN],new_filename[FN_REFLEN]; +#ifdef USE_RAID + if (raid_chunks) + return my_raid_redel(fn_format(old_filename,filename,"",old_ext,2+4), + fn_format(new_filename,filename,"",new_ext,2+4), + raid_chunks, + MYF(MY_WME | MY_LINK_WARNING | MyFlags)); +#endif + /* Get real path to filename */ + (void) fn_format(old_filename,filename,"",old_ext,2+4+32); + return my_redel(old_filename, + fn_format(new_filename,old_filename,"",new_ext,2+4), + MYF(MY_WME | MY_LINK_WARNING | MyFlags)); +} /* maria_change_to_newfile */ + + +/* Copy a block between two files */ + +int maria_filecopy(HA_CHECK *param, File to,File from,my_off_t start, + my_off_t length, const char *type) +{ + uchar tmp_buff[IO_SIZE], *buff; + ulong buff_length; + DBUG_ENTER("maria_filecopy"); + + buff_length=(ulong) min(param->write_buffer_length,length); + if (!(buff=my_malloc(buff_length,MYF(0)))) + { + buff=tmp_buff; buff_length=IO_SIZE; + } + + VOID(my_seek(from,start,MY_SEEK_SET,MYF(0))); + while (length > buff_length) + { + if (my_read(from, buff, buff_length, MYF(MY_NABP)) || + my_write(to, buff, buff_length, param->myf_rw)) + goto err; + length-= buff_length; + } + if (my_read(from, buff, (size_t) length,MYF(MY_NABP)) || + my_write(to, buff, (size_t) length,param->myf_rw)) + goto err; + if (buff != tmp_buff) + my_free(buff,MYF(0)); + DBUG_RETURN(0); +err: + if (buff != tmp_buff) + my_free(buff,MYF(0)); + _ma_check_print_error(param,"Can't copy %s to tempfile, error %d", + type,my_errno); + DBUG_RETURN(1); +} + + +/* + Repair table or given index using sorting + + SYNOPSIS + maria_repair_by_sort() + param Repair parameters + info MARIA handler to repair + name Name of table (for warnings) + rep_quick set to <> 0 if we should not change data file + + RESULT + 0 ok + <>0 Error +*/ + +int maria_repair_by_sort(HA_CHECK *param, register MARIA_HA *info, + const char * name, my_bool rep_quick) +{ + int got_error; + uint i; + ha_rows start_records; + my_off_t new_header_length, org_header_length, del; + File new_file; + MARIA_SORT_PARAM sort_param; + MARIA_SHARE *share= info->s; + HA_KEYSEG *keyseg; + double *rec_per_key_part; + char llbuff[22]; + MARIA_SORT_INFO sort_info; + ulonglong key_map; + myf sync_dir= ((share->now_transactional && !share->temporary) ? + MY_SYNC_DIR : 0); + my_bool scan_inited= 0; + DBUG_ENTER("maria_repair_by_sort"); + LINT_INIT(key_map); + + got_error= 1; + new_file= -1; + start_records= share->state.state.records; + if (!(param->testflag & T_SILENT)) + { + printf("- recovering (with sort) MARIA-table '%s'\n",name); + printf("Data records: %s\n", llstr(start_records,llbuff)); + } + + if (initialize_variables_for_repair(param, &sort_info, &sort_param, info, + rep_quick)) + goto err; + + org_header_length= share->pack.header_length; + new_header_length= (param->testflag & T_UNPACK) ? 0 : org_header_length; + sort_param.filepos= new_header_length; + + if (!rep_quick) + { + /* Get real path for data file */ + if ((new_file=my_create(fn_format(param->temp_filename, + share->data_file_name.str, "", + DATA_TMP_EXT, 2+4), + 0,param->tmpfile_createflag, + MYF(0))) < 0) + { + _ma_check_print_error(param,"Can't create new tempfile: '%s'", + param->temp_filename); + goto err; + } + if (new_header_length && + maria_filecopy(param, new_file, info->dfile.file, 0L, + new_header_length, "datafile-header")) + goto err; + + share->state.dellink= HA_OFFSET_ERROR; + info->rec_cache.file= new_file; /* For sort_delete_record */ + if (share->data_file_type == BLOCK_RECORD || + (param->testflag & T_UNPACK)) + { + if (create_new_data_handle(&sort_param, new_file)) + goto err; + sort_info.new_info->rec_cache.file= new_file; + } + } + + if (!(sort_info.key_block= + alloc_key_blocks(param, + (uint) param->sort_key_blocks, + share->base.max_key_block_length))) + goto err; + sort_info.key_block_end=sort_info.key_block+param->sort_key_blocks; + + if (share->data_file_type != BLOCK_RECORD) + { + /* We need a read buffer to read rows in big blocks */ + if (init_io_cache(¶m->read_cache, info->dfile.file, + (uint) param->read_buffer_length, + READ_CACHE, org_header_length, 1, MYF(MY_WME))) + goto err; + } + if (sort_info.new_info->s->data_file_type != BLOCK_RECORD) + { + /* When writing to not block records, we need a write buffer */ + if (!rep_quick) + { + if (init_io_cache(&sort_info.new_info->rec_cache, new_file, + (uint) param->write_buffer_length, + WRITE_CACHE, new_header_length, 1, + MYF(MY_WME | MY_WAIT_IF_FULL) & param->myf_rw)) + goto err; + sort_info.new_info->opt_flag|= WRITE_CACHE_USED; + } + } + + if (!(sort_param.record= + (uchar*) my_malloc((size_t) share->base.default_rec_buff_size, + MYF(0))) || + _ma_alloc_buffer(&sort_param.rec_buff, &sort_param.rec_buff_size, + share->base.default_rec_buff_size)) + { + _ma_check_print_error(param, "Not enough memory for extra record"); + goto err; + } + + /* Optionally drop indexes and optionally modify the key_map */ + maria_drop_all_indexes(param, info, FALSE); + key_map= share->state.key_map; + if (param->testflag & T_CREATE_MISSING_KEYS) + { + /* Invert the copied key_map to recreate all disabled indexes. */ + key_map= ~key_map; + } + + param->read_cache.end_of_file= sort_info.filelength; + sort_param.wordlist=NULL; + init_alloc_root(&sort_param.wordroot, FTPARSER_MEMROOT_ALLOC_SIZE, 0); + + sort_param.key_cmp=sort_key_cmp; + sort_param.lock_in_memory=maria_lock_memory; + sort_param.tmpdir=param->tmpdir; + sort_param.master =1; + + del=share->state.state.del; + + rec_per_key_part= param->new_rec_per_key_part; + for (sort_param.key=0 ; sort_param.key < share->base.keys ; + rec_per_key_part+=sort_param.keyinfo->keysegs, sort_param.key++) + { + sort_param.keyinfo=share->keyinfo+sort_param.key; + /* + Skip this index if it is marked disabled in the copied + (and possibly inverted) key_map. + */ + if (! maria_is_key_active(key_map, sort_param.key)) + { + /* Remember old statistics for key */ + memcpy((char*) rec_per_key_part, + (char*) (share->state.rec_per_key_part + + (uint) (rec_per_key_part - param->new_rec_per_key_part)), + sort_param.keyinfo->keysegs*sizeof(*rec_per_key_part)); + DBUG_PRINT("repair", ("skipping seemingly disabled index #: %u", + sort_param.key)); + continue; + } + + if ((!(param->testflag & T_SILENT))) + printf ("- Fixing index %d\n",sort_param.key+1); + + sort_param.read_cache=param->read_cache; + sort_param.seg=sort_param.keyinfo->seg; + sort_param.max_pos= sort_param.pos= org_header_length; + keyseg=sort_param.seg; + bzero((char*) sort_param.unique,sizeof(sort_param.unique)); + sort_param.key_length=share->rec_reflength; + for (i=0 ; keyseg[i].type != HA_KEYTYPE_END; i++) + { + sort_param.key_length+=keyseg[i].length; + if (keyseg[i].flag & HA_SPACE_PACK) + sort_param.key_length+=get_pack_length(keyseg[i].length); + if (keyseg[i].flag & (HA_BLOB_PART | HA_VAR_LENGTH_PART)) + sort_param.key_length+=2 + test(keyseg[i].length >= 127); + if (keyseg[i].flag & HA_NULL_PART) + sort_param.key_length++; + } + share->state.state.records=share->state.state.del=share->state.split=0; + share->state.state.empty=0; + + if (sort_param.keyinfo->flag & HA_FULLTEXT) + { + uint ft_max_word_len_for_sort=FT_MAX_WORD_LEN_FOR_SORT* + sort_param.keyinfo->seg->charset->mbmaxlen; + sort_param.key_length+=ft_max_word_len_for_sort-HA_FT_MAXBYTELEN; + /* + fulltext indexes may have much more entries than the + number of rows in the table. We estimate the number here. + + Note, built-in parser is always nr. 0 - see ftparser_call_initializer() + */ + if (sort_param.keyinfo->ftkey_nr == 0) + { + /* + for built-in parser the number of generated index entries + cannot be larger than the size of the data file divided + by the minimal word's length + */ + sort_info.max_records= + (ha_rows) (sort_info.filelength/ft_min_word_len+1); + } + else + { + /* + for external plugin parser we cannot tell anything at all :( + so, we'll use all the sort memory and start from ~10 buffpeks. + (see _ma_create_index_by_sort) + */ + sort_info.max_records= + 10*param->sort_buffer_length/sort_param.key_length; + } + + sort_param.key_read= sort_maria_ft_key_read; + sort_param.key_write= sort_maria_ft_key_write; + } + else + { + sort_param.key_read= sort_key_read; + sort_param.key_write= sort_key_write; + } + + if (sort_info.new_info->s->data_file_type == BLOCK_RECORD) + { + scan_inited= 1; + if (maria_scan_init(sort_info.info)) + goto err; + } + if (_ma_create_index_by_sort(&sort_param, + (my_bool) (!(param->testflag & T_VERBOSE)), + (size_t) param->sort_buffer_length)) + { + param->retry_repair=1; + _ma_check_print_error(param, "Create index by sort failed"); + goto err; + } + DBUG_EXECUTE_IF("maria_flush_whole_log", + { + DBUG_PRINT("maria_flush_whole_log", ("now")); + translog_flush(translog_get_horizon()); + }); + DBUG_EXECUTE_IF("maria_crash_create_index_by_sort", + { + DBUG_PRINT("maria_crash_create_index_by_sort", ("now")); + DBUG_ABORT(); + }); + if (scan_inited) + { + scan_inited= 0; + maria_scan_end(sort_info.info); + } + + /* No need to calculate checksum again. */ + sort_param.calc_checksum= 0; + free_root(&sort_param.wordroot, MYF(0)); + + /* Set for next loop */ + sort_info.max_records= (ha_rows) sort_info.new_info->s->state.state.records; + if (param->testflag & T_STATISTICS) + maria_update_key_parts(sort_param.keyinfo, rec_per_key_part, + sort_param.unique, + (param->stats_method == + MI_STATS_METHOD_IGNORE_NULLS ? + sort_param.notnull : NULL), + (ulonglong) share->state.state.records); + maria_set_key_active(share->state.key_map, sort_param.key); + DBUG_PRINT("repair", ("set enabled index #: %u", sort_param.key)); + + if (_ma_flush_table_files_before_swap(param, info)) + goto err; + + if (sort_param.fix_datafile) + { + param->read_cache.end_of_file=sort_param.filepos; + if (maria_write_data_suffix(&sort_info,1) || + end_io_cache(&sort_info.new_info->rec_cache)) + { + _ma_check_print_error(param, "Got error when flushing row cache"); + goto err; + } + sort_info.new_info->opt_flag&= ~WRITE_CACHE_USED; + + if (param->testflag & T_SAFE_REPAIR) + { + /* Don't repair if we loosed more than one row */ + if (share->state.state.records+1 < start_records) + { + _ma_check_print_error(param, + "Rows lost; Aborting because safe repair was " + "requested"); + share->state.state.records=start_records; + goto err; + } + } + + sort_info.new_info->s->state.state.data_file_length= sort_param.filepos; + if (sort_info.new_info != sort_info.info) + { + MARIA_STATE_INFO save_state= sort_info.new_info->s->state; + if (maria_close(sort_info.new_info)) + { + _ma_check_print_error(param, "Got error %d on close", my_errno); + goto err; + } + copy_data_file_state(&share->state, &save_state); + new_file= -1; + sort_info.new_info= info; + info->rec_cache.file= info->dfile.file; + } + + share->state.version=(ulong) time((time_t*) 0); /* Force reopen */ + + /* Replace the actual file with the temporary file */ + if (new_file >= 0) + { + my_close(new_file, MYF(MY_WME)); + new_file= -1; + } + change_data_file_descriptor(info, -1); + if (maria_change_to_newfile(share->data_file_name.str, MARIA_NAME_DEXT, + DATA_TMP_EXT, + (param->testflag & T_BACKUP_DATA ? + MYF(MY_REDEL_MAKE_BACKUP): MYF(0)) | + sync_dir) || + _ma_open_datafile(info, share, NullS, -1)) + { + _ma_check_print_error(param, "Couldn't change to new data file"); + goto err; + } + if (param->testflag & T_UNPACK) + restore_data_file_type(share); + + org_header_length= share->pack.header_length; + sort_info.org_data_file_type= share->data_file_type; + sort_info.filelength= share->state.state.data_file_length; + sort_param.fix_datafile=0; + } + else + share->state.state.data_file_length=sort_param.max_pos; + + param->read_cache.file= info->dfile.file; /* re-init read cache */ + reinit_io_cache(¶m->read_cache,READ_CACHE,share->pack.header_length, + 1,1); + } + + if (param->testflag & T_WRITE_LOOP) + { + VOID(fputs(" \r",stdout)); VOID(fflush(stdout)); + } + + if (rep_quick && del+sort_info.dupp != share->state.state.del) + { + _ma_check_print_error(param,"Couldn't fix table with quick recovery: " + "Found wrong number of deleted records"); + _ma_check_print_error(param,"Run recovery again without -q"); + got_error=1; + param->retry_repair=1; + param->testflag|=T_RETRY_WITHOUT_QUICK; + goto err; + } + + if (rep_quick && (param->testflag & T_FORCE_UNIQUENESS)) + { + my_off_t skr= (share->state.state.data_file_length + + (sort_info.org_data_file_type == COMPRESSED_RECORD) ? + MEMMAP_EXTRA_MARGIN : 0); +#ifdef USE_RELOC + if (sort_info.org_data_file_type == STATIC_RECORD && + skr < share->base.reloc*share->base.min_pack_length) + skr=share->base.reloc*share->base.min_pack_length; +#endif + if (skr != sort_info.filelength) + if (my_chsize(info->dfile.file, skr, 0, MYF(0))) + _ma_check_print_warning(param, + "Can't change size of datafile, error: %d", + my_errno); + } + + if (param->testflag & T_CALC_CHECKSUM) + share->state.state.checksum=param->glob_crc; + + if (my_chsize(share->kfile.file, share->state.state.key_file_length, 0, + MYF(0))) + _ma_check_print_warning(param, + "Can't change size of indexfile, error: %d", + my_errno); + + if (!(param->testflag & T_SILENT)) + { + if (start_records != share->state.state.records) + printf("Data records: %s\n", llstr(share->state.state.records,llbuff)); + } + if (sort_info.dupp) + _ma_check_print_warning(param, + "%s records have been removed", + llstr(sort_info.dupp,llbuff)); + got_error=0; + /* If invoked by external program that uses thr_lock */ + if (&share->state.state != info->state) + *info->state= *info->state_start= share->state.state; + +err: + if (scan_inited) + maria_scan_end(sort_info.info); + _ma_reset_state(info); + + VOID(end_io_cache(&sort_info.new_info->rec_cache)); + VOID(end_io_cache(¶m->read_cache)); + info->opt_flag&= ~(READ_CACHE_USED | WRITE_CACHE_USED); + sort_info.new_info->opt_flag&= ~(READ_CACHE_USED | WRITE_CACHE_USED); + if (got_error) + { + if (! param->error_printed) + _ma_check_print_error(param,"%d when fixing table",my_errno); + (void)_ma_flush_table_files_before_swap(param, info); + if (sort_info.new_info && sort_info.new_info != sort_info.info) + { + unuse_data_file_descriptor(sort_info.new_info); + maria_close(sort_info.new_info); + } + if (new_file >= 0) + { + VOID(my_close(new_file,MYF(0))); + VOID(my_delete(param->temp_filename, MYF(MY_WME))); + } + maria_mark_crashed_on_repair(info); + } + else + { + if (key_map == share->state.key_map) + share->state.changed&= ~STATE_NOT_OPTIMIZED_KEYS; + /* + Now that we have flushed and forced everything, we can bump + create_rename_lsn: + */ + DBUG_EXECUTE_IF("maria_flush_whole_log", + { + DBUG_PRINT("maria_flush_whole_log", ("now")); + translog_flush(translog_get_horizon()); + }); + DBUG_EXECUTE_IF("maria_crash_repair", + { + DBUG_PRINT("maria_crash_repair", ("now")); + DBUG_ABORT(); + }); + } + share->state.changed|= STATE_NOT_SORTED_PAGES; + if (!rep_quick) + share->state.changed&= ~(STATE_NOT_OPTIMIZED_ROWS | STATE_NOT_ZEROFILLED | + STATE_NOT_MOVABLE); + + my_free(sort_param.rec_buff, MYF(MY_ALLOW_ZERO_PTR)); + my_free(sort_param.record,MYF(MY_ALLOW_ZERO_PTR)); + my_free(sort_info.key_block, MYF(MY_ALLOW_ZERO_PTR)); + my_free(sort_info.ft_buf, MYF(MY_ALLOW_ZERO_PTR)); + my_free(sort_info.buff,MYF(MY_ALLOW_ZERO_PTR)); + DBUG_RETURN(got_error); +} + + +/* + Threaded repair of table using sorting + + SYNOPSIS + maria_repair_parallel() + param Repair parameters + info MARIA handler to repair + name Name of table (for warnings) + rep_quick set to <> 0 if we should not change data file + + DESCRIPTION + Same as maria_repair_by_sort but do it multithreaded + Each key is handled by a separate thread. + TODO: make a number of threads a parameter + + In parallel repair we use one thread per index. There are two modes: + + Quick + + Only the indexes are rebuilt. All threads share a read buffer. + Every thread that needs fresh data in the buffer enters the shared + cache lock. The last thread joining the lock reads the buffer from + the data file and wakes all other threads. + + Non-quick + + The data file is rebuilt and all indexes are rebuilt to point to + the new record positions. One thread is the master thread. It + reads from the old data file and writes to the new data file. It + also creates one of the indexes. The other threads read from a + buffer which is filled by the master. If they need fresh data, + they enter the shared cache lock. If the masters write buffer is + full, it flushes it to the new data file and enters the shared + cache lock too. When all threads joined in the lock, the master + copies its write buffer to the read buffer for the other threads + and wakes them. + + RESULT + 0 ok + <>0 Error +*/ + +int maria_repair_parallel(HA_CHECK *param, register MARIA_HA *info, + const char * name, my_bool rep_quick) +{ +#ifndef THREAD + return maria_repair_by_sort(param, info, name, rep_quick); +#else + int got_error; + uint i,key, total_key_length, istep; + ha_rows start_records; + my_off_t new_header_length,del; + File new_file; + MARIA_SORT_PARAM *sort_param=0, tmp_sort_param; + MARIA_SHARE *share= info->s; + double *rec_per_key_part; + HA_KEYSEG *keyseg; + char llbuff[22]; + IO_CACHE new_data_cache; /* For non-quick repair. */ + IO_CACHE_SHARE io_share; + MARIA_SORT_INFO sort_info; + ulonglong key_map; + pthread_attr_t thr_attr; + myf sync_dir= ((share->now_transactional && !share->temporary) ? + MY_SYNC_DIR : 0); + DBUG_ENTER("maria_repair_parallel"); + LINT_INIT(key_map); + + got_error= 1; + new_file= -1; + start_records= share->state.state.records; + if (!(param->testflag & T_SILENT)) + { + printf("- parallel recovering (with sort) MARIA-table '%s'\n",name); + printf("Data records: %s\n", llstr(start_records, llbuff)); + } + + if (initialize_variables_for_repair(param, &sort_info, &tmp_sort_param, info, + rep_quick)) + goto err; + + new_header_length= ((param->testflag & T_UNPACK) ? 0 : + share->pack.header_length); + + /* + Quick repair (not touching data file, rebuilding indexes): + { + Read cache is (HA_CHECK *param)->read_cache using info->dfile.file. + } + + Non-quick repair (rebuilding data file and indexes): + { + Master thread: + + Read cache is (HA_CHECK *param)->read_cache using info->dfile.file. + Write cache is (MARIA_INFO *info)->rec_cache using new_file. + + Slave threads: + + Read cache is new_data_cache synced to master rec_cache. + + The final assignment of the filedescriptor for rec_cache is done + after the cache creation. + + Don't check file size on new_data_cache, as the resulting file size + is not known yet. + + As rec_cache and new_data_cache are synced, write_buffer_length is + used for the read cache 'new_data_cache'. Both start at the same + position 'new_header_length'. + } + */ + DBUG_PRINT("info", ("is quick repair: %d", (int) rep_quick)); + + /* Initialize pthread structures before goto err. */ + pthread_mutex_init(&sort_info.mutex, MY_MUTEX_INIT_FAST); + pthread_cond_init(&sort_info.cond, 0); + + if (!(sort_info.key_block= + alloc_key_blocks(param, (uint) param->sort_key_blocks, + share->base.max_key_block_length)) || + init_io_cache(¶m->read_cache, info->dfile.file, + (uint) param->read_buffer_length, + READ_CACHE, share->pack.header_length, 1, MYF(MY_WME)) || + (!rep_quick && + (init_io_cache(&info->rec_cache, info->dfile.file, + (uint) param->write_buffer_length, + WRITE_CACHE, new_header_length, 1, + MYF(MY_WME | MY_WAIT_IF_FULL) & param->myf_rw) || + init_io_cache(&new_data_cache, -1, + (uint) param->write_buffer_length, + READ_CACHE, new_header_length, 1, + MYF(MY_WME | MY_DONT_CHECK_FILESIZE))))) + goto err; + sort_info.key_block_end=sort_info.key_block+param->sort_key_blocks; + info->opt_flag|=WRITE_CACHE_USED; + info->rec_cache.file= info->dfile.file; /* for sort_delete_record */ + + if (!rep_quick) + { + /* Get real path for data file */ + if ((new_file= my_create(fn_format(param->temp_filename, + share->data_file_name.str, "", + DATA_TMP_EXT, + 2+4), + 0,param->tmpfile_createflag, + MYF(0))) < 0) + { + _ma_check_print_error(param,"Can't create new tempfile: '%s'", + param->temp_filename); + goto err; + } + if (new_header_length && + maria_filecopy(param, new_file, info->dfile.file,0L,new_header_length, + "datafile-header")) + goto err; + if (param->testflag & T_UNPACK) + restore_data_file_type(share); + share->state.dellink= HA_OFFSET_ERROR; + info->rec_cache.file=new_file; + } + + /* Optionally drop indexes and optionally modify the key_map. */ + maria_drop_all_indexes(param, info, FALSE); + key_map= share->state.key_map; + if (param->testflag & T_CREATE_MISSING_KEYS) + { + /* Invert the copied key_map to recreate all disabled indexes. */ + key_map= ~key_map; + } + + param->read_cache.end_of_file= sort_info.filelength; + + /* + +1 below is required hack for parallel repair mode. + The share->state.state.records value, that is compared later + to sort_info.max_records and cannot exceed it, is + increased in sort_key_write. In maria_repair_by_sort, sort_key_write + is called after sort_key_read, where the comparison is performed, + but in parallel mode master thread can call sort_key_write + before some other repair thread calls sort_key_read. + Furthermore I'm not even sure +1 would be enough. + May be sort_info.max_records shold be always set to max value in + parallel mode. + */ + sort_info.max_records++; + + del=share->state.state.del; + + if (!(sort_param=(MARIA_SORT_PARAM *) + my_malloc((uint) share->base.keys * + (sizeof(MARIA_SORT_PARAM) + share->base.pack_reclength), + MYF(MY_ZEROFILL)))) + { + _ma_check_print_error(param,"Not enough memory for key!"); + goto err; + } + total_key_length=0; + rec_per_key_part= param->new_rec_per_key_part; + share->state.state.records=share->state.state.del=share->state.split=0; + share->state.state.empty=0; + + for (i=key=0, istep=1 ; key < share->base.keys ; + rec_per_key_part+=sort_param[i].keyinfo->keysegs, i+=istep, key++) + { + sort_param[i].key=key; + sort_param[i].keyinfo=share->keyinfo+key; + sort_param[i].seg=sort_param[i].keyinfo->seg; + /* + Skip this index if it is marked disabled in the copied + (and possibly inverted) key_map. + */ + if (! maria_is_key_active(key_map, key)) + { + /* Remember old statistics for key */ + memcpy((char*) rec_per_key_part, + (char*) (share->state.rec_per_key_part+ + (uint) (rec_per_key_part - param->new_rec_per_key_part)), + sort_param[i].keyinfo->keysegs*sizeof(*rec_per_key_part)); + istep=0; + continue; + } + istep=1; + if ((!(param->testflag & T_SILENT))) + printf ("- Fixing index %d\n",key+1); + if (sort_param[i].keyinfo->flag & HA_FULLTEXT) + { + sort_param[i].key_read=sort_maria_ft_key_read; + sort_param[i].key_write=sort_maria_ft_key_write; + } + else + { + sort_param[i].key_read=sort_key_read; + sort_param[i].key_write=sort_key_write; + } + sort_param[i].key_cmp=sort_key_cmp; + sort_param[i].lock_in_memory=maria_lock_memory; + sort_param[i].tmpdir=param->tmpdir; + sort_param[i].sort_info=&sort_info; + sort_param[i].master=0; + sort_param[i].fix_datafile=0; + sort_param[i].calc_checksum= 0; + + sort_param[i].filepos=new_header_length; + sort_param[i].max_pos=sort_param[i].pos=share->pack.header_length; + + sort_param[i].record= (((uchar *)(sort_param+share->base.keys))+ + (share->base.pack_reclength * i)); + if (_ma_alloc_buffer(&sort_param[i].rec_buff, &sort_param[i].rec_buff_size, + share->base.default_rec_buff_size)) + { + _ma_check_print_error(param,"Not enough memory!"); + goto err; + } + sort_param[i].key_length=share->rec_reflength; + for (keyseg=sort_param[i].seg; keyseg->type != HA_KEYTYPE_END; + keyseg++) + { + sort_param[i].key_length+=keyseg->length; + if (keyseg->flag & HA_SPACE_PACK) + sort_param[i].key_length+=get_pack_length(keyseg->length); + if (keyseg->flag & (HA_BLOB_PART | HA_VAR_LENGTH_PART)) + sort_param[i].key_length+=2 + test(keyseg->length >= 127); + if (keyseg->flag & HA_NULL_PART) + sort_param[i].key_length++; + } + total_key_length+=sort_param[i].key_length; + + if (sort_param[i].keyinfo->flag & HA_FULLTEXT) + { + uint ft_max_word_len_for_sort= + (FT_MAX_WORD_LEN_FOR_SORT * + sort_param[i].keyinfo->seg->charset->mbmaxlen); + sort_param[i].key_length+=ft_max_word_len_for_sort-HA_FT_MAXBYTELEN; + init_alloc_root(&sort_param[i].wordroot, FTPARSER_MEMROOT_ALLOC_SIZE, 0); + } + } + sort_info.total_keys=i; + sort_param[0].master= 1; + sort_param[0].fix_datafile= ! rep_quick; + sort_param[0].calc_checksum= test(param->testflag & T_CALC_CHECKSUM); + + if (!maria_ftparser_alloc_param(info)) + goto err; + + sort_info.got_error=0; + pthread_mutex_lock(&sort_info.mutex); + + /* + Initialize the I/O cache share for use with the read caches and, in + case of non-quick repair, the write cache. When all threads join on + the cache lock, the writer copies the write cache contents to the + read caches. + */ + if (i > 1) + { + if (rep_quick) + init_io_cache_share(¶m->read_cache, &io_share, NULL, i); + else + init_io_cache_share(&new_data_cache, &io_share, &info->rec_cache, i); + } + else + io_share.total_threads= 0; /* share not used */ + + (void) pthread_attr_init(&thr_attr); + (void) pthread_attr_setdetachstate(&thr_attr,PTHREAD_CREATE_DETACHED); + + for (i=0 ; i < sort_info.total_keys ; i++) + { + /* + Copy the properly initialized IO_CACHE structure so that every + thread has its own copy. In quick mode param->read_cache is shared + for use by all threads. In non-quick mode all threads but the + first copy the shared new_data_cache, which is synchronized to the + write cache of the first thread. The first thread copies + param->read_cache, which is not shared. + */ + sort_param[i].read_cache= ((rep_quick || !i) ? param->read_cache : + new_data_cache); + DBUG_PRINT("io_cache_share", ("thread: %u read_cache: 0x%lx", + i, (long) &sort_param[i].read_cache)); + + /* + two approaches: the same amount of memory for each thread + or the memory for the same number of keys for each thread... + In the second one all the threads will fill their sort_buffers + (and call write_keys) at the same time, putting more stress on i/o. + */ + sort_param[i].sortbuff_size= +#ifndef USING_SECOND_APPROACH + param->sort_buffer_length/sort_info.total_keys; +#else + param->sort_buffer_length*sort_param[i].key_length/total_key_length; +#endif + if (pthread_create(&sort_param[i].thr, &thr_attr, + _ma_thr_find_all_keys, + (void *) (sort_param+i))) + { + _ma_check_print_error(param,"Cannot start a repair thread"); + /* Cleanup: Detach from the share. Avoid others to be blocked. */ + if (io_share.total_threads) + remove_io_thread(&sort_param[i].read_cache); + DBUG_PRINT("error", ("Cannot start a repair thread")); + sort_info.got_error=1; + } + else + sort_info.threads_running++; + } + (void) pthread_attr_destroy(&thr_attr); + + /* waiting for all threads to finish */ + while (sort_info.threads_running) + pthread_cond_wait(&sort_info.cond, &sort_info.mutex); + pthread_mutex_unlock(&sort_info.mutex); + + if ((got_error= _ma_thr_write_keys(sort_param))) + { + param->retry_repair=1; + goto err; + } + got_error=1; /* Assume the following may go wrong */ + + if (_ma_flush_table_files_before_swap(param, info)) + goto err; + + if (sort_param[0].fix_datafile) + { + /* + Append some nulls to the end of a memory mapped file. Destroy the + write cache. The master thread did already detach from the share + by remove_io_thread() in sort.c:thr_find_all_keys(). + */ + if (maria_write_data_suffix(&sort_info,1) || + end_io_cache(&info->rec_cache)) + goto err; + if (param->testflag & T_SAFE_REPAIR) + { + /* Don't repair if we loosed more than one row */ + if (share->state.state.records+1 < start_records) + { + share->state.state.records=start_records; + goto err; + } + } + share->state.state.data_file_length= share->state.state.data_file_length= + sort_param->filepos; + /* Only whole records */ + share->state.version= (ulong) time((time_t*) 0); + /* + Exchange the data file descriptor of the table, so that we use the + new file from now on. + */ + my_close(info->dfile.file, MYF(0)); + info->dfile.file= new_file; + share->pack.header_length=(ulong) new_header_length; + } + else + share->state.state.data_file_length=sort_param->max_pos; + + if (rep_quick && del+sort_info.dupp != share->state.state.del) + { + _ma_check_print_error(param,"Couldn't fix table with quick recovery: " + "Found wrong number of deleted records"); + _ma_check_print_error(param,"Run recovery again without -q"); + param->retry_repair=1; + param->testflag|=T_RETRY_WITHOUT_QUICK; + goto err; + } + + if (rep_quick && (param->testflag & T_FORCE_UNIQUENESS)) + { + my_off_t skr= (share->state.state.data_file_length + + (sort_info.org_data_file_type == COMPRESSED_RECORD) ? + MEMMAP_EXTRA_MARGIN : 0); +#ifdef USE_RELOC + if (sort_info.org_data_file_type == STATIC_RECORD && + skr < share->base.reloc*share->base.min_pack_length) + skr=share->base.reloc*share->base.min_pack_length; +#endif + if (skr != sort_info.filelength) + if (my_chsize(info->dfile.file, skr, 0, MYF(0))) + _ma_check_print_warning(param, + "Can't change size of datafile, error: %d", + my_errno); + } + if (param->testflag & T_CALC_CHECKSUM) + share->state.state.checksum=param->glob_crc; + + if (my_chsize(share->kfile.file, share->state.state.key_file_length, 0, + MYF(0))) + _ma_check_print_warning(param, + "Can't change size of indexfile, error: %d", + my_errno); + + if (!(param->testflag & T_SILENT)) + { + if (start_records != share->state.state.records) + printf("Data records: %s\n", llstr(share->state.state.records,llbuff)); + } + if (sort_info.dupp) + _ma_check_print_warning(param, + "%s records have been removed", + llstr(sort_info.dupp,llbuff)); + got_error=0; + /* If invoked by external program that uses thr_lock */ + if (&share->state.state != info->state) + *info->state= *info->state_start= share->state.state; + +err: + _ma_reset_state(info); + + /* + Destroy the write cache. The master thread did already detach from + the share by remove_io_thread() or it was not yet started (if the + error happend before creating the thread). + */ + VOID(end_io_cache(&sort_info.new_info->rec_cache)); + VOID(end_io_cache(¶m->read_cache)); + info->opt_flag&= ~(READ_CACHE_USED | WRITE_CACHE_USED); + sort_info.new_info->opt_flag&= ~(READ_CACHE_USED | WRITE_CACHE_USED); + /* + Destroy the new data cache in case of non-quick repair. All slave + threads did either detach from the share by remove_io_thread() + already or they were not yet started (if the error happend before + creating the threads). + */ + if (!rep_quick) + VOID(end_io_cache(&new_data_cache)); + if (!got_error) + { + /* Replace the actual file with the temporary file */ + if (new_file >= 0) + { + my_close(new_file,MYF(0)); + info->dfile.file= new_file= -1; + if (maria_change_to_newfile(share->data_file_name.str, MARIA_NAME_DEXT, + DATA_TMP_EXT, + MYF((param->testflag & T_BACKUP_DATA ? + MY_REDEL_MAKE_BACKUP : 0) | + sync_dir)) || + _ma_open_datafile(info,share, NullS, -1)) + got_error=1; + } + } + if (got_error) + { + if (! param->error_printed) + _ma_check_print_error(param,"%d when fixing table",my_errno); + (void)_ma_flush_table_files_before_swap(param, info); + if (new_file >= 0) + { + VOID(my_close(new_file,MYF(0))); + VOID(my_delete(param->temp_filename, MYF(MY_WME))); + if (info->dfile.file == new_file) + info->dfile.file= -1; + } + maria_mark_crashed_on_repair(info); + } + else if (key_map == share->state.key_map) + share->state.changed&= ~STATE_NOT_OPTIMIZED_KEYS; + share->state.changed|= STATE_NOT_SORTED_PAGES; + if (!rep_quick) + share->state.changed&= ~(STATE_NOT_OPTIMIZED_ROWS | STATE_NOT_ZEROFILLED | + STATE_NOT_MOVABLE); + + pthread_cond_destroy (&sort_info.cond); + pthread_mutex_destroy(&sort_info.mutex); + + my_free(sort_info.ft_buf, MYF(MY_ALLOW_ZERO_PTR)); + my_free(sort_info.key_block,MYF(MY_ALLOW_ZERO_PTR)); + my_free(sort_param,MYF(MY_ALLOW_ZERO_PTR)); + my_free(sort_info.buff,MYF(MY_ALLOW_ZERO_PTR)); + if (!got_error && (param->testflag & T_UNPACK)) + restore_data_file_type(share); + DBUG_RETURN(got_error); +#endif /* THREAD */ +} + + /* Read next record and return next key */ + +static int sort_key_read(MARIA_SORT_PARAM *sort_param, uchar *key) +{ + int error; + MARIA_SORT_INFO *sort_info= sort_param->sort_info; + MARIA_HA *info= sort_info->info; + MARIA_KEY int_key; + DBUG_ENTER("sort_key_read"); + + if ((error=sort_get_next_record(sort_param))) + DBUG_RETURN(error); + if (info->s->state.state.records == sort_info->max_records) + { + _ma_check_print_error(sort_info->param, + "Key %d - Found too many records; Can't continue", + sort_param->key+1); + DBUG_RETURN(1); + } + if (_ma_sort_write_record(sort_param)) + DBUG_RETURN(1); + + (*info->s->keyinfo[sort_param->key].make_key)(info, &int_key, + sort_param->key, key, + sort_param->record, + sort_param->current_filepos, + 0); + sort_param->real_key_length= int_key.data_length + int_key.ref_length; +#ifdef HAVE_valgrind + bzero(key+sort_param->real_key_length, + (sort_param->key_length-sort_param->real_key_length)); +#endif + DBUG_RETURN(0); +} /* sort_key_read */ + + +static int sort_maria_ft_key_read(MARIA_SORT_PARAM *sort_param, uchar *key) +{ + int error; + MARIA_SORT_INFO *sort_info=sort_param->sort_info; + MARIA_HA *info=sort_info->info; + FT_WORD *wptr=0; + MARIA_KEY int_key; + DBUG_ENTER("sort_maria_ft_key_read"); + + if (!sort_param->wordlist) + { + for (;;) + { + free_root(&sort_param->wordroot, MYF(MY_MARK_BLOCKS_FREE)); + if ((error=sort_get_next_record(sort_param))) + DBUG_RETURN(error); + if ((error= _ma_sort_write_record(sort_param))) + DBUG_RETURN(error); + if (!(wptr= _ma_ft_parserecord(info,sort_param->key,sort_param->record, + &sort_param->wordroot))) + + DBUG_RETURN(1); + if (wptr->pos) + break; + } + sort_param->wordptr=sort_param->wordlist=wptr; + } + else + { + error=0; + wptr=(FT_WORD*)(sort_param->wordptr); + } + + _ma_ft_make_key(info, &int_key, sort_param->key, key, wptr++, + sort_param->current_filepos); + sort_param->real_key_length= int_key.data_length + int_key.ref_length; + +#ifdef HAVE_valgrind + if (sort_param->key_length > sort_param->real_key_length) + bzero(key+sort_param->real_key_length, + (sort_param->key_length-sort_param->real_key_length)); +#endif + if (!wptr->pos) + { + free_root(&sort_param->wordroot, MYF(MY_MARK_BLOCKS_FREE)); + sort_param->wordlist=0; + } + else + sort_param->wordptr=(void*)wptr; + + DBUG_RETURN(error); +} /* sort_maria_ft_key_read */ + + +/* + Read next record from file using parameters in sort_info. + + SYNOPSIS + sort_get_next_record() + sort_param Information about and for the sort process + + NOTES + Dynamic Records With Non-Quick Parallel Repair + + For non-quick parallel repair we use a synchronized read/write + cache. This means that one thread is the master who fixes the data + file by reading each record from the old data file and writing it + to the new data file. By doing this the records in the new data + file are written contiguously. Whenever the write buffer is full, + it is copied to the read buffer. The slaves read from the read + buffer, which is not associated with a file. Thus read_cache.file + is -1. When using _mi_read_cache(), the slaves must always set + flag to READING_NEXT so that the function never tries to read from + file. This is safe because the records are contiguous. There is no + need to read outside the cache. This condition is evaluated in the + variable 'parallel_flag' for quick reference. read_cache.file must + be >= 0 in every other case. + + RETURN + -1 end of file + 0 ok + sort_param->current_filepos points to record position. + sort_param->record contains record + sort_param->max_pos contains position to last byte read + > 0 error +*/ + +static int sort_get_next_record(MARIA_SORT_PARAM *sort_param) +{ + int searching; + int parallel_flag; + uint found_record,b_type,left_length; + my_off_t pos; + MARIA_BLOCK_INFO block_info; + MARIA_SORT_INFO *sort_info=sort_param->sort_info; + HA_CHECK *param=sort_info->param; + MARIA_HA *info=sort_info->info; + MARIA_SHARE *share= info->s; + char llbuff[22],llbuff2[22]; + DBUG_ENTER("sort_get_next_record"); + + if (*_ma_killed_ptr(param)) + DBUG_RETURN(1); + + switch (sort_info->org_data_file_type) { + case BLOCK_RECORD: + { + for (;;) + { + int flag; + /* + Assume table is transactional and it had LSN pages in the + cache. Repair has flushed them, left data pages stay in + cache, and disabled transactionality (so share's current page + type is PLAIN); page cache would assert if it finds a cached LSN page + while _ma_scan_block_record() requested a PLAIN page. So we use + UNKNOWN. + */ + enum pagecache_page_type save_page_type= share->page_type; + share->page_type= PAGECACHE_READ_UNKNOWN_PAGE; + if (info != sort_info->new_info) + { + /* Safe scanning */ + flag= _ma_safe_scan_block_record(sort_info, info, + sort_param->record); + } + else + { + /* + Scan on clean table. + It requires a reliable data_file_length so we set it. + */ + share->state.state.data_file_length= sort_info->filelength; + info->cur_row.trid= 0; + flag= _ma_scan_block_record(info, sort_param->record, + info->cur_row.nextpos, 1); + set_if_bigger(param->max_found_trid, info->cur_row.trid); + if (info->cur_row.trid > param->max_trid) + { + _ma_check_print_not_visible_error(param, info->cur_row.trid); + flag= HA_ERR_ROW_NOT_VISIBLE; + } + } + share->page_type= save_page_type; + if (!flag) + { + if (sort_param->calc_checksum) + { + ha_checksum checksum; + checksum= (*share->calc_check_checksum)(info, sort_param->record); + if (share->calc_checksum && + info->cur_row.checksum != (checksum & 255)) + { + if (param->testflag & T_VERBOSE) + { + record_pos_to_txt(info, info->cur_row.lastpos, llbuff); + _ma_check_print_info(param, + "Found record with wrong checksum at %s", + llbuff); + } + continue; + } + info->cur_row.checksum= checksum; + param->glob_crc+= checksum; + } + sort_param->start_recpos= sort_param->current_filepos= + info->cur_row.lastpos; + DBUG_RETURN(0); + } + if (flag == HA_ERR_END_OF_FILE) + { + sort_param->max_pos= share->state.state.data_file_length; + DBUG_RETURN(-1); + } + /* Retry only if wrong record, not if disk error */ + if (flag != HA_ERR_WRONG_IN_RECORD) + { + retry_if_quick(sort_param, flag); + DBUG_RETURN(flag); + } + } + break; /* Impossible */ + } + case STATIC_RECORD: + for (;;) + { + if (my_b_read(&sort_param->read_cache,sort_param->record, + share->base.pack_reclength)) + { + if (sort_param->read_cache.error) + param->out_flag |= O_DATA_LOST; + retry_if_quick(sort_param, my_errno); + DBUG_RETURN(-1); + } + sort_param->start_recpos=sort_param->pos; + if (!sort_param->fix_datafile) + { + sort_param->current_filepos= sort_param->pos; + if (sort_param->master) + share->state.split++; + } + sort_param->max_pos=(sort_param->pos+=share->base.pack_reclength); + if (*sort_param->record) + { + if (sort_param->calc_checksum) + param->glob_crc+= (info->cur_row.checksum= + _ma_static_checksum(info,sort_param->record)); + DBUG_RETURN(0); + } + if (!sort_param->fix_datafile && sort_param->master) + { + share->state.state.del++; + share->state.state.empty+=share->base.pack_reclength; + } + } + case DYNAMIC_RECORD: + { + uchar *to; + ha_checksum checksum= 0; + LINT_INIT(to); + + pos=sort_param->pos; + searching=(sort_param->fix_datafile && (param->testflag & T_EXTEND)); + parallel_flag= (sort_param->read_cache.file < 0) ? READING_NEXT : 0; + for (;;) + { + found_record=block_info.second_read= 0; + left_length=1; + if (searching) + { + pos=MY_ALIGN(pos,MARIA_DYN_ALIGN_SIZE); + param->testflag|=T_RETRY_WITHOUT_QUICK; + sort_param->start_recpos=pos; + } + do + { + if (pos > sort_param->max_pos) + sort_param->max_pos=pos; + if (pos & (MARIA_DYN_ALIGN_SIZE-1)) + { + if ((param->testflag & T_VERBOSE) || searching == 0) + _ma_check_print_info(param,"Wrong aligned block at %s", + llstr(pos,llbuff)); + if (searching) + goto try_next; + } + if (found_record && pos == param->search_after_block) + _ma_check_print_info(param,"Block: %s used by record at %s", + llstr(param->search_after_block,llbuff), + llstr(sort_param->start_recpos,llbuff2)); + if (_ma_read_cache(&sort_param->read_cache, + block_info.header, pos, + MARIA_BLOCK_INFO_HEADER_LENGTH, + (! found_record ? READING_NEXT : 0) | + parallel_flag | READING_HEADER)) + { + if (found_record) + { + _ma_check_print_info(param, + "Can't read whole record at %s (errno: %d)", + llstr(sort_param->start_recpos,llbuff),errno); + goto try_next; + } + DBUG_RETURN(-1); + } + if (searching && ! sort_param->fix_datafile) + { + param->error_printed=1; + param->retry_repair=1; + param->testflag|=T_RETRY_WITHOUT_QUICK; + DBUG_RETURN(1); /* Something wrong with data */ + } + b_type= _ma_get_block_info(&block_info,-1,pos); + if ((b_type & (BLOCK_ERROR | BLOCK_FATAL_ERROR)) || + ((b_type & BLOCK_FIRST) && + (block_info.rec_len < (uint) share->base.min_pack_length || + block_info.rec_len > (uint) share->base.max_pack_length))) + { + uint i; + if (param->testflag & T_VERBOSE || searching == 0) + _ma_check_print_info(param, + "Wrong bytesec: %3d-%3d-%3d at %10s; Skipped", + block_info.header[0],block_info.header[1], + block_info.header[2],llstr(pos,llbuff)); + if (found_record) + goto try_next; + block_info.second_read=0; + searching=1; + /* Search after block in read header string */ + for (i=MARIA_DYN_ALIGN_SIZE ; + i < MARIA_BLOCK_INFO_HEADER_LENGTH ; + i+= MARIA_DYN_ALIGN_SIZE) + if (block_info.header[i] >= 1 && + block_info.header[i] <= MARIA_MAX_DYN_HEADER_BYTE) + break; + pos+=(ulong) i; + sort_param->start_recpos=pos; + continue; + } + if (b_type & BLOCK_DELETED) + { + my_bool error=0; + if (block_info.block_len+ (uint) (block_info.filepos-pos) < + share->base.min_block_length) + { + if (!searching) + _ma_check_print_info(param, + "Deleted block with impossible length %lu " + "at %s", + block_info.block_len,llstr(pos,llbuff)); + error=1; + } + else + { + if ((block_info.next_filepos != HA_OFFSET_ERROR && + block_info.next_filepos >= + share->state.state.data_file_length) || + (block_info.prev_filepos != HA_OFFSET_ERROR && + block_info.prev_filepos >= + share->state.state.data_file_length)) + { + if (!searching) + _ma_check_print_info(param, + "Delete link points outside datafile at " + "%s", + llstr(pos,llbuff)); + error=1; + } + } + if (error) + { + if (found_record) + goto try_next; + searching=1; + pos+= MARIA_DYN_ALIGN_SIZE; + sort_param->start_recpos=pos; + block_info.second_read=0; + continue; + } + } + else + { + if (block_info.block_len+ (uint) (block_info.filepos-pos) < + share->base.min_block_length || + block_info.block_len > (uint) share->base.max_pack_length+ + MARIA_SPLIT_LENGTH) + { + if (!searching) + _ma_check_print_info(param, + "Found block with impossible length %lu " + "at %s; Skipped", + block_info.block_len+ + (uint) (block_info.filepos-pos), + llstr(pos,llbuff)); + if (found_record) + goto try_next; + searching=1; + pos+= MARIA_DYN_ALIGN_SIZE; + sort_param->start_recpos=pos; + block_info.second_read=0; + continue; + } + } + if (b_type & (BLOCK_DELETED | BLOCK_SYNC_ERROR)) + { + if (!sort_param->fix_datafile && sort_param->master && + (b_type & BLOCK_DELETED)) + { + share->state.state.empty+=block_info.block_len; + share->state.state.del++; + share->state.split++; + } + if (found_record) + goto try_next; + if (searching) + { + pos+=MARIA_DYN_ALIGN_SIZE; + sort_param->start_recpos=pos; + } + else + pos=block_info.filepos+block_info.block_len; + block_info.second_read=0; + continue; + } + + if (!sort_param->fix_datafile && sort_param->master) + share->state.split++; + if (! found_record++) + { + sort_param->find_length=left_length=block_info.rec_len; + sort_param->start_recpos=pos; + if (!sort_param->fix_datafile) + sort_param->current_filepos= sort_param->start_recpos; + if (sort_param->fix_datafile && (param->testflag & T_EXTEND)) + sort_param->pos=block_info.filepos+1; + else + sort_param->pos=block_info.filepos+block_info.block_len; + if (share->base.blobs) + { + if (_ma_alloc_buffer(&sort_param->rec_buff, + &sort_param->rec_buff_size, + block_info.rec_len + + share->base.extra_rec_buff_size)) + + { + if (param->max_record_length >= block_info.rec_len) + { + _ma_check_print_error(param,"Not enough memory for blob at %s " + "(need %lu)", + llstr(sort_param->start_recpos,llbuff), + (ulong) block_info.rec_len); + DBUG_RETURN(1); + } + else + { + _ma_check_print_info(param,"Not enough memory for blob at %s " + "(need %lu); Row skipped", + llstr(sort_param->start_recpos,llbuff), + (ulong) block_info.rec_len); + goto try_next; + } + } + } + to= sort_param->rec_buff; + } + if (left_length < block_info.data_len || ! block_info.data_len) + { + _ma_check_print_info(param, + "Found block with too small length at %s; " + "Skipped", + llstr(sort_param->start_recpos,llbuff)); + goto try_next; + } + if (block_info.filepos + block_info.data_len > + sort_param->read_cache.end_of_file) + { + _ma_check_print_info(param, + "Found block that points outside data file " + "at %s", + llstr(sort_param->start_recpos,llbuff)); + goto try_next; + } + /* + Copy information that is already read. Avoid accessing data + below the cache start. This could happen if the header + streched over the end of the previous buffer contents. + */ + { + uint header_len= (uint) (block_info.filepos - pos); + uint prefetch_len= (MARIA_BLOCK_INFO_HEADER_LENGTH - header_len); + + if (prefetch_len > block_info.data_len) + prefetch_len= block_info.data_len; + if (prefetch_len) + { + memcpy(to, block_info.header + header_len, prefetch_len); + block_info.filepos+= prefetch_len; + block_info.data_len-= prefetch_len; + left_length-= prefetch_len; + to+= prefetch_len; + } + } + if (block_info.data_len && + _ma_read_cache(&sort_param->read_cache,to,block_info.filepos, + block_info.data_len, + (found_record == 1 ? READING_NEXT : 0) | + parallel_flag)) + { + _ma_check_print_info(param, + "Read error for block at: %s (error: %d); " + "Skipped", + llstr(block_info.filepos,llbuff),my_errno); + goto try_next; + } + left_length-=block_info.data_len; + to+=block_info.data_len; + pos=block_info.next_filepos; + if (pos == HA_OFFSET_ERROR && left_length) + { + _ma_check_print_info(param, + "Wrong block with wrong total length " + "starting at %s", + llstr(sort_param->start_recpos,llbuff)); + goto try_next; + } + if (pos + MARIA_BLOCK_INFO_HEADER_LENGTH > + sort_param->read_cache.end_of_file) + { + _ma_check_print_info(param, + "Found link that points at %s (outside data " + "file) at %s", + llstr(pos,llbuff2), + llstr(sort_param->start_recpos,llbuff)); + goto try_next; + } + } while (left_length); + + if (_ma_rec_unpack(info,sort_param->record,sort_param->rec_buff, + sort_param->find_length) != MY_FILE_ERROR) + { + if (sort_param->read_cache.error < 0) + DBUG_RETURN(1); + if (sort_param->calc_checksum) + checksum= (share->calc_check_checksum)(info, sort_param->record); + if ((param->testflag & (T_EXTEND | T_REP)) || searching) + { + if (_ma_rec_check(info, sort_param->record, sort_param->rec_buff, + sort_param->find_length, + (param->testflag & T_QUICK) && + sort_param->calc_checksum && + test(share->calc_checksum), checksum)) + { + _ma_check_print_info(param,"Found wrong packed record at %s", + llstr(sort_param->start_recpos,llbuff)); + goto try_next; + } + } + if (sort_param->calc_checksum) + param->glob_crc+= checksum; + DBUG_RETURN(0); + } + if (!searching) + _ma_check_print_info(param,"Key %d - Found wrong stored record at %s", + sort_param->key+1, + llstr(sort_param->start_recpos,llbuff)); + try_next: + pos=(sort_param->start_recpos+=MARIA_DYN_ALIGN_SIZE); + searching=1; + } + } + case COMPRESSED_RECORD: + for (searching=0 ;; searching=1, sort_param->pos++) + { + if (_ma_read_cache(&sort_param->read_cache, block_info.header, + sort_param->pos, + share->pack.ref_length,READING_NEXT)) + DBUG_RETURN(-1); + if (searching && ! sort_param->fix_datafile) + { + param->error_printed=1; + param->retry_repair=1; + param->testflag|=T_RETRY_WITHOUT_QUICK; + DBUG_RETURN(1); /* Something wrong with data */ + } + sort_param->start_recpos=sort_param->pos; + if (_ma_pack_get_block_info(info, &sort_param->bit_buff, &block_info, + &sort_param->rec_buff, + &sort_param->rec_buff_size, -1, + sort_param->pos)) + DBUG_RETURN(-1); + if (!block_info.rec_len && + sort_param->pos + MEMMAP_EXTRA_MARGIN == + sort_param->read_cache.end_of_file) + DBUG_RETURN(-1); + if (block_info.rec_len < (uint) share->min_pack_length || + block_info.rec_len > (uint) share->max_pack_length) + { + if (! searching) + _ma_check_print_info(param, + "Found block with wrong recordlength: %lu " + "at %s\n", + block_info.rec_len, + llstr(sort_param->pos,llbuff)); + continue; + } + if (_ma_read_cache(&sort_param->read_cache, sort_param->rec_buff, + block_info.filepos, block_info.rec_len, + READING_NEXT)) + { + if (! searching) + _ma_check_print_info(param,"Couldn't read whole record from %s", + llstr(sort_param->pos,llbuff)); + continue; + } +#ifdef HAVE_valgrind + bzero(sort_param->rec_buff + block_info.rec_len, + share->base.extra_rec_buff_size); +#endif + if (_ma_pack_rec_unpack(info, &sort_param->bit_buff, sort_param->record, + sort_param->rec_buff, block_info.rec_len)) + { + if (! searching) + _ma_check_print_info(param,"Found wrong record at %s", + llstr(sort_param->pos,llbuff)); + continue; + } + if (!sort_param->fix_datafile) + { + sort_param->current_filepos= sort_param->pos; + if (sort_param->master) + share->state.split++; + } + sort_param->max_pos= (sort_param->pos=block_info.filepos+ + block_info.rec_len); + info->packed_length=block_info.rec_len; + + if (sort_param->calc_checksum) + { + info->cur_row.checksum= (*share->calc_check_checksum)(info, + sort_param-> + record); + param->glob_crc+= info->cur_row.checksum; + } + DBUG_RETURN(0); + } + } + DBUG_RETURN(1); /* Impossible */ +} + + +/** + @brief Write record to new file. + + @fn _ma_sort_write_record() + @param sort_param Sort parameters. + + @note + This is only called by a master thread if parallel repair is used. + + @return + @retval 0 OK + sort_param->current_filepos points to inserted record for + block_records and to the place for the next record for + other row types. + sort_param->filepos points to end of file + @retval 1 Error +*/ + +int _ma_sort_write_record(MARIA_SORT_PARAM *sort_param) +{ + int flag; + uint length; + ulong block_length,reclength; + uchar *from; + uchar block_buff[8]; + MARIA_SORT_INFO *sort_info=sort_param->sort_info; + HA_CHECK *param= sort_info->param; + MARIA_HA *info= sort_info->new_info; + MARIA_SHARE *share= info->s; + DBUG_ENTER("_ma_sort_write_record"); + + if (sort_param->fix_datafile) + { + sort_param->current_filepos= sort_param->filepos; + switch (sort_info->new_data_file_type) { + case BLOCK_RECORD: + if ((sort_param->current_filepos= + (*share->write_record_init)(info, sort_param->record)) == + HA_OFFSET_ERROR) + DBUG_RETURN(1); + /* Pointer to end of file */ + sort_param->filepos= share->state.state.data_file_length; + break; + case STATIC_RECORD: + if (my_b_write(&info->rec_cache,sort_param->record, + share->base.pack_reclength)) + { + _ma_check_print_error(param,"%d when writing to datafile",my_errno); + DBUG_RETURN(1); + } + sort_param->filepos+=share->base.pack_reclength; + share->state.split++; + break; + case DYNAMIC_RECORD: + if (! info->blobs) + from=sort_param->rec_buff; + else + { + /* must be sure that local buffer is big enough */ + reclength=share->base.pack_reclength+ + _ma_calc_total_blob_length(info,sort_param->record)+ + ALIGN_SIZE(MARIA_MAX_DYN_BLOCK_HEADER)+MARIA_SPLIT_LENGTH+ + MARIA_DYN_DELETE_BLOCK_HEADER; + if (sort_info->buff_length < reclength) + { + if (!(sort_info->buff=my_realloc(sort_info->buff, (uint) reclength, + MYF(MY_FREE_ON_ERROR | + MY_ALLOW_ZERO_PTR)))) + DBUG_RETURN(1); + sort_info->buff_length=reclength; + } + from= (uchar *) sort_info->buff+ALIGN_SIZE(MARIA_MAX_DYN_BLOCK_HEADER); + } + /* We can use info->checksum here as only one thread calls this */ + info->cur_row.checksum= (*share->calc_check_checksum)(info, + sort_param-> + record); + reclength= _ma_rec_pack(info,from,sort_param->record); + flag=0; + + do + { + block_length=reclength+ 3 + test(reclength >= (65520-3)); + if (block_length < share->base.min_block_length) + block_length=share->base.min_block_length; + info->update|=HA_STATE_WRITE_AT_END; + block_length=MY_ALIGN(block_length,MARIA_DYN_ALIGN_SIZE); + if (block_length > MARIA_MAX_BLOCK_LENGTH) + block_length=MARIA_MAX_BLOCK_LENGTH; + if (_ma_write_part_record(info,0L,block_length, + sort_param->filepos+block_length, + &from,&reclength,&flag)) + { + _ma_check_print_error(param,"%d when writing to datafile",my_errno); + DBUG_RETURN(1); + } + sort_param->filepos+=block_length; + share->state.split++; + } while (reclength); + break; + case COMPRESSED_RECORD: + reclength=info->packed_length; + length= _ma_save_pack_length((uint) share->pack.version, block_buff, + reclength); + if (share->base.blobs) + length+= _ma_save_pack_length((uint) share->pack.version, + block_buff + length, info->blob_length); + if (my_b_write(&info->rec_cache,block_buff,length) || + my_b_write(&info->rec_cache, sort_param->rec_buff, reclength)) + { + _ma_check_print_error(param,"%d when writing to datafile",my_errno); + DBUG_RETURN(1); + } + sort_param->filepos+=reclength+length; + share->state.split++; + break; + } + } + if (sort_param->master) + { + share->state.state.records++; + if ((param->testflag & T_WRITE_LOOP) && + (share->state.state.records % WRITE_COUNT) == 0) + { + char llbuff[22]; + printf("%s\r", llstr(share->state.state.records,llbuff)); + VOID(fflush(stdout)); + } + } + DBUG_RETURN(0); +} /* _ma_sort_write_record */ + + +/* Compare two keys from _ma_create_index_by_sort */ + +static int sort_key_cmp(MARIA_SORT_PARAM *sort_param, const void *a, + const void *b) +{ + uint not_used[2]; + return (ha_key_cmp(sort_param->seg, *((uchar* const *) a), + *((uchar* const *) b), + USE_WHOLE_KEY, SEARCH_SAME, not_used)); +} /* sort_key_cmp */ + + +static int sort_key_write(MARIA_SORT_PARAM *sort_param, const uchar *a) +{ + uint diff_pos[2]; + char llbuff[22],llbuff2[22]; + MARIA_SORT_INFO *sort_info=sort_param->sort_info; + HA_CHECK *param= sort_info->param; + int cmp; + + if (sort_info->key_block->inited) + { + cmp= ha_key_cmp(sort_param->seg, sort_info->key_block->lastkey, + a, USE_WHOLE_KEY, + SEARCH_FIND | SEARCH_UPDATE | SEARCH_INSERT, + diff_pos); + if (param->stats_method == MI_STATS_METHOD_NULLS_NOT_EQUAL) + ha_key_cmp(sort_param->seg, sort_info->key_block->lastkey, + a, USE_WHOLE_KEY, + SEARCH_FIND | SEARCH_NULL_ARE_NOT_EQUAL, diff_pos); + else if (param->stats_method == MI_STATS_METHOD_IGNORE_NULLS) + { + diff_pos[0]= maria_collect_stats_nonulls_next(sort_param->seg, + sort_param->notnull, + sort_info->key_block->lastkey, + a); + } + sort_param->unique[diff_pos[0]-1]++; + } + else + { + cmp= -1; + if (param->stats_method == MI_STATS_METHOD_IGNORE_NULLS) + maria_collect_stats_nonulls_first(sort_param->seg, sort_param->notnull, + a); + } + if ((sort_param->keyinfo->flag & HA_NOSAME) && cmp == 0) + { + sort_info->dupp++; + sort_info->info->cur_row.lastpos= get_record_for_key(sort_param->keyinfo, + a); + _ma_check_print_warning(param, + "Duplicate key %2u for record at %10s against " + "record at %10s", + sort_param->key + 1, + llstr(sort_info->info->cur_row.lastpos, llbuff), + llstr(get_record_for_key(sort_param->keyinfo, + sort_info->key_block-> + lastkey), + llbuff2)); + param->testflag|=T_RETRY_WITHOUT_QUICK; + if (sort_info->param->testflag & T_VERBOSE) + _ma_print_keydata(stdout,sort_param->seg, a, USE_WHOLE_KEY); + return (sort_delete_record(sort_param)); + } +#ifndef DBUG_OFF + if (cmp > 0) + { + _ma_check_print_error(param, + "Internal error: Keys are not in order from sort"); + return(1); + } +#endif + return (sort_insert_key(sort_param, sort_info->key_block, + a, HA_OFFSET_ERROR)); +} /* sort_key_write */ + + +int _ma_sort_ft_buf_flush(MARIA_SORT_PARAM *sort_param) +{ + MARIA_SORT_INFO *sort_info=sort_param->sort_info; + SORT_KEY_BLOCKS *key_block=sort_info->key_block; + MARIA_SHARE *share=sort_info->info->s; + uint val_off, val_len; + int error; + SORT_FT_BUF *maria_ft_buf=sort_info->ft_buf; + uchar *from, *to; + + val_len=share->ft2_keyinfo.keylength; + get_key_full_length_rdonly(val_off, maria_ft_buf->lastkey); + to= maria_ft_buf->lastkey+val_off; + + if (maria_ft_buf->buf) + { + /* flushing first-level tree */ + error= sort_insert_key(sort_param,key_block,maria_ft_buf->lastkey, + HA_OFFSET_ERROR); + for (from=to+val_len; + !error && from < maria_ft_buf->buf; + from+= val_len) + { + memcpy(to, from, val_len); + error= sort_insert_key(sort_param,key_block,maria_ft_buf->lastkey, + HA_OFFSET_ERROR); + } + return error; + } + /* flushing second-level tree keyblocks */ + error=_ma_flush_pending_blocks(sort_param); + /* updating lastkey with second-level tree info */ + ft_intXstore(maria_ft_buf->lastkey+val_off, -maria_ft_buf->count); + _ma_dpointer(sort_info->info->s, maria_ft_buf->lastkey+val_off+HA_FT_WLEN, + share->state.key_root[sort_param->key]); + /* restoring first level tree data in sort_info/sort_param */ + sort_info->key_block=sort_info->key_block_end- sort_info->param->sort_key_blocks; + sort_param->keyinfo=share->keyinfo+sort_param->key; + share->state.key_root[sort_param->key]=HA_OFFSET_ERROR; + /* writing lastkey in first-level tree */ + return error ? error : + sort_insert_key(sort_param,sort_info->key_block, + maria_ft_buf->lastkey,HA_OFFSET_ERROR); +} + + +static int sort_maria_ft_key_write(MARIA_SORT_PARAM *sort_param, + const uchar *a) +{ + uint a_len, val_off, val_len, error; + MARIA_SORT_INFO *sort_info= sort_param->sort_info; + SORT_FT_BUF *ft_buf= sort_info->ft_buf; + SORT_KEY_BLOCKS *key_block= sort_info->key_block; + MARIA_SHARE *share= sort_info->info->s; + + val_len=HA_FT_WLEN+share->base.rec_reflength; + get_key_full_length_rdonly(a_len, a); + + if (!ft_buf) + { + /* + use two-level tree only if key_reflength fits in rec_reflength place + and row format is NOT static - for _ma_dpointer not to garble offsets + */ + if ((share->base.key_reflength <= + share->base.rec_reflength) && + (share->options & + (HA_OPTION_PACK_RECORD | HA_OPTION_COMPRESS_RECORD))) + ft_buf= (SORT_FT_BUF *)my_malloc(sort_param->keyinfo->block_length + + sizeof(SORT_FT_BUF), MYF(MY_WME)); + + if (!ft_buf) + { + sort_param->key_write=sort_key_write; + return sort_key_write(sort_param, a); + } + sort_info->ft_buf= ft_buf; + goto word_init_ft_buf; /* no need to duplicate the code */ + } + get_key_full_length_rdonly(val_off, ft_buf->lastkey); + + if (ha_compare_text(sort_param->seg->charset, + a+1,a_len-1, + ft_buf->lastkey+1,val_off-1, 0, 0)==0) + { + uchar *p; + if (!ft_buf->buf) /* store in second-level tree */ + { + ft_buf->count++; + return sort_insert_key(sort_param,key_block, + a + a_len, HA_OFFSET_ERROR); + } + + /* storing the key in the buffer. */ + memcpy (ft_buf->buf, (const char *)a+a_len, val_len); + ft_buf->buf+=val_len; + if (ft_buf->buf < ft_buf->end) + return 0; + + /* converting to two-level tree */ + p=ft_buf->lastkey+val_off; + + while (key_block->inited) + key_block++; + sort_info->key_block=key_block; + sort_param->keyinfo= &share->ft2_keyinfo; + ft_buf->count=(ft_buf->buf - p)/val_len; + + /* flushing buffer to second-level tree */ + for (error=0; !error && p < ft_buf->buf; p+= val_len) + error=sort_insert_key(sort_param,key_block,p,HA_OFFSET_ERROR); + ft_buf->buf=0; + return error; + } + + /* flushing buffer */ + if ((error=_ma_sort_ft_buf_flush(sort_param))) + return error; + +word_init_ft_buf: + a_len+=val_len; + memcpy(ft_buf->lastkey, a, a_len); + ft_buf->buf=ft_buf->lastkey+a_len; + /* + 32 is just a safety margin here + (at least max(val_len, sizeof(nod_flag)) should be there). + May be better performance could be achieved if we'd put + (sort_info->keyinfo->block_length-32)/XXX + instead. + TODO: benchmark the best value for XXX. + */ + ft_buf->end= ft_buf->lastkey+ (sort_param->keyinfo->block_length-32); + return 0; +} /* sort_maria_ft_key_write */ + + +/* get pointer to record from a key */ + +static my_off_t get_record_for_key(MARIA_KEYDEF *keyinfo, + const uchar *key_data) +{ + MARIA_KEY key; + key.keyinfo= keyinfo; + key.data= (uchar*) key_data; + key.data_length= _ma_keylength(keyinfo, key_data); + return _ma_row_pos_from_key(&key); +} /* get_record_for_key */ + + +/* Insert a key in sort-key-blocks */ + +static int sort_insert_key(MARIA_SORT_PARAM *sort_param, + register SORT_KEY_BLOCKS *key_block, + const uchar *key, + my_off_t prev_block) +{ + uint a_length,t_length,nod_flag; + my_off_t filepos,key_file_length; + uchar *anc_buff,*lastkey; + MARIA_KEY_PARAM s_temp; + MARIA_KEYDEF *keyinfo=sort_param->keyinfo; + MARIA_SORT_INFO *sort_info= sort_param->sort_info; + HA_CHECK *param=sort_info->param; + MARIA_PINNED_PAGE tmp_page_link, *page_link= &tmp_page_link; + MARIA_KEY tmp_key; + MARIA_HA *info= sort_info->info; + MARIA_SHARE *share= info->s; + DBUG_ENTER("sort_insert_key"); + + anc_buff= key_block->buff; + lastkey=key_block->lastkey; + nod_flag= (key_block == sort_info->key_block ? 0 : + share->base.key_reflength); + + if (!key_block->inited) + { + key_block->inited=1; + if (key_block == sort_info->key_block_end) + { + _ma_check_print_error(param, + "To many key-block-levels; " + "Try increasing sort_key_blocks"); + DBUG_RETURN(1); + } + a_length= share->keypage_header + nod_flag; + key_block->end_pos= anc_buff + share->keypage_header; + bzero(anc_buff, share->keypage_header); + _ma_store_keynr(share, anc_buff, (uint) (sort_param->keyinfo - + share->keyinfo)); + lastkey=0; /* No previous key in block */ + } + else + a_length= _ma_get_page_used(share, anc_buff); + + /* Save pointer to previous block */ + if (nod_flag) + { + _ma_store_keypage_flag(share, anc_buff, KEYPAGE_FLAG_ISNOD); + _ma_kpointer(info,key_block->end_pos,prev_block); + } + + tmp_key.keyinfo= keyinfo; + tmp_key.data= (uchar*) key; + tmp_key.data_length= _ma_keylength(keyinfo, key) - share->base.rec_reflength; + tmp_key.ref_length= share->base.rec_reflength; + + t_length= (*keyinfo->pack_key)(&tmp_key, nod_flag, + (uchar*) 0, lastkey, lastkey, &s_temp); + (*keyinfo->store_key)(keyinfo, key_block->end_pos+nod_flag,&s_temp); + a_length+=t_length; + _ma_store_page_used(share, anc_buff, a_length); + key_block->end_pos+=t_length; + if (a_length <= (uint) (keyinfo->block_length - KEYPAGE_CHECKSUM_SIZE)) + { + MARIA_KEY tmp_key2; + tmp_key2.data= key_block->lastkey; + _ma_copy_key(&tmp_key2, &tmp_key); + key_block->last_length=a_length-t_length; + DBUG_RETURN(0); + } + + /* Fill block with end-zero and write filled block */ + _ma_store_page_used(share, anc_buff, key_block->last_length); + bzero(anc_buff+key_block->last_length, + keyinfo->block_length- key_block->last_length); + key_file_length=share->state.state.key_file_length; + if ((filepos= _ma_new(info, DFLT_INIT_HITS, &page_link)) == HA_OFFSET_ERROR) + DBUG_RETURN(1); + _ma_fast_unlock_key_del(info); + + /* If we read the page from the key cache, we have to write it back to it */ + if (page_link->changed) + { + MARIA_PAGE page; + pop_dynamic(&info->pinned_pages); + _ma_page_setup(&page, info, keyinfo, filepos, anc_buff); + if (_ma_write_keypage(&page, PAGECACHE_LOCK_WRITE_UNLOCK, DFLT_INIT_HITS)) + DBUG_RETURN(1); + } + else + { + put_crc(anc_buff, filepos, share); + if (my_pwrite(share->kfile.file, anc_buff, + (uint) keyinfo->block_length, filepos, param->myf_rw)) + DBUG_RETURN(1); + } + DBUG_DUMP("buff", anc_buff, _ma_get_page_used(share, anc_buff)); + + /* Write separator-key to block in next level */ + if (sort_insert_key(sort_param,key_block+1,key_block->lastkey,filepos)) + DBUG_RETURN(1); + + /* clear old block and write new key in it */ + key_block->inited=0; + DBUG_RETURN(sort_insert_key(sort_param, key_block,key,prev_block)); +} /* sort_insert_key */ + + +/* Delete record when we found a duplicated key */ + +static int sort_delete_record(MARIA_SORT_PARAM *sort_param) +{ + uint i; + int old_file,error; + uchar *key; + MARIA_SORT_INFO *sort_info=sort_param->sort_info; + HA_CHECK *param=sort_info->param; + MARIA_HA *row_info= sort_info->new_info, *key_info= sort_info->info; + DBUG_ENTER("sort_delete_record"); + + if ((param->testflag & (T_FORCE_UNIQUENESS|T_QUICK)) == T_QUICK) + { + _ma_check_print_error(param, + "Quick-recover aborted; Run recovery without switch " + "-q or with switch -qq"); + DBUG_RETURN(1); + } + if (key_info->s->options & HA_OPTION_COMPRESS_RECORD) + { + _ma_check_print_error(param, + "Recover aborted; Can't run standard recovery on " + "compressed tables with errors in data-file. " + "Use 'maria_chk --safe-recover' to fix it"); + DBUG_RETURN(1); + } + + old_file= row_info->dfile.file; + /* This only affects static and dynamic row formats */ + row_info->dfile.file= row_info->rec_cache.file; + if (flush_io_cache(&row_info->rec_cache)) + DBUG_RETURN(1); + + key= key_info->lastkey_buff + key_info->s->base.max_key_length; + if ((error=(*row_info->s->read_record)(row_info, sort_param->record, + key_info->cur_row.lastpos)) && + error != HA_ERR_RECORD_DELETED) + { + _ma_check_print_error(param,"Can't read record to be removed"); + row_info->dfile.file= old_file; + DBUG_RETURN(1); + } + row_info->cur_row.lastpos= key_info->cur_row.lastpos; + + for (i=0 ; i < sort_info->current_key ; i++) + { + MARIA_KEY tmp_key; + (*key_info->s->keyinfo[i].make_key)(key_info, &tmp_key, i, key, + sort_param->record, + key_info->cur_row.lastpos, 0); + if (_ma_ck_delete(key_info, &tmp_key)) + { + _ma_check_print_error(param, + "Can't delete key %d from record to be removed", + i+1); + row_info->dfile.file= old_file; + DBUG_RETURN(1); + } + } + if (sort_param->calc_checksum) + param->glob_crc-=(*key_info->s->calc_check_checksum)(key_info, + sort_param->record); + error= (*row_info->s->delete_record)(row_info, sort_param->record); + if (error) + _ma_check_print_error(param,"Got error %d when deleting record", + my_errno); + row_info->dfile.file= old_file; /* restore actual value */ + row_info->s->state.state.records--; + DBUG_RETURN(error); +} /* sort_delete_record */ + + +/* Fix all pending blocks and flush everything to disk */ + +int _ma_flush_pending_blocks(MARIA_SORT_PARAM *sort_param) +{ + uint nod_flag,length; + my_off_t filepos,key_file_length; + SORT_KEY_BLOCKS *key_block; + MARIA_SORT_INFO *sort_info= sort_param->sort_info; + myf myf_rw=sort_info->param->myf_rw; + MARIA_HA *info=sort_info->info; + MARIA_KEYDEF *keyinfo=sort_param->keyinfo; + MARIA_PINNED_PAGE tmp_page_link, *page_link= &tmp_page_link; + DBUG_ENTER("_ma_flush_pending_blocks"); + + filepos= HA_OFFSET_ERROR; /* if empty file */ + nod_flag=0; + for (key_block=sort_info->key_block ; key_block->inited ; key_block++) + { + key_block->inited=0; + length= _ma_get_page_used(info->s, key_block->buff); + if (nod_flag) + _ma_kpointer(info,key_block->end_pos,filepos); + key_file_length= info->s->state.state.key_file_length; + bzero(key_block->buff+length, keyinfo->block_length-length); + if ((filepos= _ma_new(info, DFLT_INIT_HITS, &page_link)) == + HA_OFFSET_ERROR) + goto err; + + /* If we read the page from the key cache, we have to write it back */ + if (page_link->changed) + { + MARIA_PAGE page; + pop_dynamic(&info->pinned_pages); + + _ma_page_setup(&page, info, keyinfo, filepos, key_block->buff); + if (_ma_write_keypage(&page, PAGECACHE_LOCK_WRITE_UNLOCK, + DFLT_INIT_HITS)) + goto err; + } + else + { + put_crc(key_block->buff, filepos, info->s); + if (my_pwrite(info->s->kfile.file, key_block->buff, + (uint) keyinfo->block_length,filepos, myf_rw)) + goto err; + } + DBUG_DUMP("buff",key_block->buff,length); + nod_flag=1; + } + info->s->state.key_root[sort_param->key]=filepos; /* Last is root for tree */ + _ma_fast_unlock_key_del(info); + DBUG_RETURN(0); + +err: + _ma_fast_unlock_key_del(info); + DBUG_RETURN(1); +} /* _ma_flush_pending_blocks */ + + /* alloc space and pointers for key_blocks */ + +static SORT_KEY_BLOCKS *alloc_key_blocks(HA_CHECK *param, uint blocks, + uint buffer_length) +{ + reg1 uint i; + SORT_KEY_BLOCKS *block; + DBUG_ENTER("alloc_key_blocks"); + + if (!(block= (SORT_KEY_BLOCKS*) my_malloc((sizeof(SORT_KEY_BLOCKS)+ + buffer_length+IO_SIZE)*blocks, + MYF(0)))) + { + _ma_check_print_error(param,"Not enough memory for sort-key-blocks"); + return(0); + } + for (i=0 ; i < blocks ; i++) + { + block[i].inited=0; + block[i].buff= (uchar*) (block+blocks)+(buffer_length+IO_SIZE)*i; + } + DBUG_RETURN(block); +} /* alloc_key_blocks */ + + + /* Check if file is almost full */ + +int maria_test_if_almost_full(MARIA_HA *info) +{ + MARIA_SHARE *share= info->s; + + if (share->options & HA_OPTION_COMPRESS_RECORD) + return 0; + return my_seek(share->kfile.file, 0L, MY_SEEK_END, + MYF(MY_THREADSAFE))/10*9 > + (my_off_t) share->base.max_key_file_length || + my_seek(info->dfile.file, 0L, MY_SEEK_END, MYF(0)) / 10 * 9 > + (my_off_t) share->base.max_data_file_length; +} + + +/* Recreate table with bigger more alloced record-data */ + +int maria_recreate_table(HA_CHECK *param, MARIA_HA **org_info, char *filename) +{ + int error; + MARIA_HA info; + MARIA_SHARE share; + MARIA_KEYDEF *keyinfo,*key,*key_end; + HA_KEYSEG *keysegs,*keyseg; + MARIA_COLUMNDEF *columndef,*column,*end; + MARIA_UNIQUEDEF *uniquedef,*u_ptr,*u_end; + MARIA_STATUS_INFO status_info; + uint unpack,key_parts; + ha_rows max_records; + ulonglong file_length,tmp_length; + MARIA_CREATE_INFO create_info; + DBUG_ENTER("maria_recreate_table"); + + error=1; /* Default error */ + info= **org_info; + status_info= (*org_info)->state[0]; + info.state= &status_info; + share= *(*org_info)->s; + unpack= ((share.data_file_type == COMPRESSED_RECORD) && + (param->testflag & T_UNPACK)); + if (!(keyinfo=(MARIA_KEYDEF*) my_alloca(sizeof(MARIA_KEYDEF) * + share.base.keys))) + DBUG_RETURN(0); + memcpy((uchar*) keyinfo,(uchar*) share.keyinfo, + (size_t) (sizeof(MARIA_KEYDEF)*share.base.keys)); + + key_parts= share.base.all_key_parts; + if (!(keysegs=(HA_KEYSEG*) my_alloca(sizeof(HA_KEYSEG)* + (key_parts+share.base.keys)))) + { + my_afree(keyinfo); + DBUG_RETURN(1); + } + if (!(columndef=(MARIA_COLUMNDEF*) + my_alloca(sizeof(MARIA_COLUMNDEF)*(share.base.fields+1)))) + { + my_afree(keyinfo); + my_afree(keysegs); + DBUG_RETURN(1); + } + if (!(uniquedef=(MARIA_UNIQUEDEF*) + my_alloca(sizeof(MARIA_UNIQUEDEF)*(share.state.header.uniques+1)))) + { + my_afree(columndef); + my_afree(keyinfo); + my_afree(keysegs); + DBUG_RETURN(1); + } + + /* Copy the column definitions in their original order */ + for (column= share.columndef, end= share.columndef+share.base.fields; + column != end ; + column++) + columndef[column->column_nr]= *column; + + /* Change the new key to point at the saved key segments */ + memcpy((uchar*) keysegs,(uchar*) share.keyparts, + (size_t) (sizeof(HA_KEYSEG)*(key_parts+share.base.keys+ + share.state.header.uniques))); + keyseg=keysegs; + for (key=keyinfo,key_end=keyinfo+share.base.keys; key != key_end ; key++) + { + key->seg=keyseg; + for (; keyseg->type ; keyseg++) + { + if (param->language) + keyseg->language=param->language; /* change language */ + } + keyseg++; /* Skip end pointer */ + } + + /* + Copy the unique definitions and change them to point at the new key + segments + */ + memcpy((uchar*) uniquedef,(uchar*) share.uniqueinfo, + (size_t) (sizeof(MARIA_UNIQUEDEF)*(share.state.header.uniques))); + for (u_ptr=uniquedef,u_end=uniquedef+share.state.header.uniques; + u_ptr != u_end ; u_ptr++) + { + u_ptr->seg=keyseg; + keyseg+=u_ptr->keysegs+1; + } + + file_length=(ulonglong) my_seek(info.dfile.file, 0L, MY_SEEK_END, MYF(0)); + if (share.options & HA_OPTION_COMPRESS_RECORD) + share.base.records=max_records=info.state->records; + else if (share.base.min_pack_length) + max_records=(ha_rows) (file_length / share.base.min_pack_length); + else + max_records=0; + share.options&= ~HA_OPTION_TEMP_COMPRESS_RECORD; + + tmp_length= file_length+file_length/10; + set_if_bigger(file_length,param->max_data_file_length); + set_if_bigger(file_length,tmp_length); + set_if_bigger(file_length,(ulonglong) share.base.max_data_file_length); + + VOID(maria_close(*org_info)); + + bzero((char*) &create_info,sizeof(create_info)); + create_info.max_rows=max(max_records,share.base.records); + create_info.reloc_rows=share.base.reloc; + create_info.old_options=(share.options | + (unpack ? HA_OPTION_TEMP_COMPRESS_RECORD : 0)); + + create_info.data_file_length=file_length; + create_info.auto_increment=share.state.auto_increment; + create_info.language = (param->language ? param->language : + share.state.header.language); + create_info.key_file_length= status_info.key_file_length; + create_info.org_data_file_type= ((enum data_file_type) + share.state.header.org_data_file_type); + + /* + Allow for creating an auto_increment key. This has an effect only if + an auto_increment key exists in the original table. + */ + create_info.with_auto_increment= TRUE; + create_info.null_bytes= share.base.null_bytes; + create_info.transactional= share.base.born_transactional; + + /* + We don't have to handle symlinks here because we are using + HA_DONT_TOUCH_DATA + */ + if (maria_create(filename, share.data_file_type, + share.base.keys - share.state.header.uniques, + keyinfo, share.base.fields, columndef, + share.state.header.uniques, uniquedef, + &create_info, + HA_DONT_TOUCH_DATA)) + { + _ma_check_print_error(param, + "Got error %d when trying to recreate indexfile", + my_errno); + goto end; + } + *org_info= maria_open(filename,O_RDWR, + (HA_OPEN_FOR_REPAIR | + ((param->testflag & T_WAIT_FOREVER) ? + HA_OPEN_WAIT_IF_LOCKED : + (param->testflag & T_DESCRIPT) ? + HA_OPEN_IGNORE_IF_LOCKED : + HA_OPEN_ABORT_IF_LOCKED))); + if (!*org_info) + { + _ma_check_print_error(param, + "Got error %d when trying to open re-created " + "indexfile", my_errno); + goto end; + } + /* We are modifing */ + (*org_info)->s->options&= ~HA_OPTION_READ_ONLY_DATA; + VOID(_ma_readinfo(*org_info,F_WRLCK,0)); + (*org_info)->s->state.state.records= info.state->records; + if (share.state.create_time) + (*org_info)->s->state.create_time=share.state.create_time; +#ifdef EXTERNAL_LOCKING + (*org_info)->s->state.unique= (*org_info)->this_unique= share.state.unique; +#endif + (*org_info)->s->state.state.checksum= info.state->checksum; + (*org_info)->s->state.state.del= info.state->del; + (*org_info)->s->state.dellink= share.state.dellink; + (*org_info)->s->state.state.empty= info.state->empty; + (*org_info)->s->state.state.data_file_length= info.state->data_file_length; + *(*org_info)->state= (*org_info)->s->state.state; + if (maria_update_state_info(param,*org_info,UPDATE_TIME | UPDATE_STAT | + UPDATE_OPEN_COUNT)) + goto end; + error=0; +end: + my_afree(uniquedef); + my_afree(keyinfo); + my_afree(columndef); + my_afree(keysegs); + DBUG_RETURN(error); +} + + + /* write suffix to data file if neaded */ + +int maria_write_data_suffix(MARIA_SORT_INFO *sort_info, my_bool fix_datafile) +{ + MARIA_HA *info=sort_info->new_info; + + if (info->s->data_file_type == COMPRESSED_RECORD && fix_datafile) + { + uchar buff[MEMMAP_EXTRA_MARGIN]; + bzero(buff,sizeof(buff)); + if (my_b_write(&info->rec_cache,buff,sizeof(buff))) + { + _ma_check_print_error(sort_info->param, + "%d when writing to datafile",my_errno); + return 1; + } + sort_info->param->read_cache.end_of_file+=sizeof(buff); + } + return 0; +} + + +/* Update state and maria_chk time of indexfile */ + +int maria_update_state_info(HA_CHECK *param, MARIA_HA *info,uint update) +{ + MARIA_SHARE *share= info->s; + DBUG_ENTER("maria_update_state_info"); + + if (update & UPDATE_OPEN_COUNT) + { + share->state.open_count=0; + share->global_changed=0; + } + if (update & UPDATE_STAT) + { + uint i, key_parts= mi_uint2korr(share->state.header.key_parts); + share->state.records_at_analyze= share->state.state.records; + share->state.changed&= ~STATE_NOT_ANALYZED; + if (share->state.state.records) + { + for (i=0; i<key_parts; i++) + { + if (!(share->state.rec_per_key_part[i]=param->new_rec_per_key_part[i])) + share->state.changed|= STATE_NOT_ANALYZED; + } + } + } + if (update & (UPDATE_STAT | UPDATE_SORT | UPDATE_TIME | UPDATE_AUTO_INC)) + { + if (update & UPDATE_TIME) + { + share->state.check_time= (long) time((time_t*) 0); + if (!share->state.create_time) + share->state.create_time= share->state.check_time; + } + if (_ma_state_info_write(share, + MA_STATE_INFO_WRITE_DONT_MOVE_OFFSET | + MA_STATE_INFO_WRITE_FULL_INFO)) + goto err; + share->changed=0; + } + { /* Force update of status */ + int error; + uint r_locks=share->r_locks,w_locks=share->w_locks; + share->r_locks= share->w_locks= share->tot_locks= 0; + error= _ma_writeinfo(info,WRITEINFO_NO_UNLOCK); + share->r_locks=r_locks; + share->w_locks=w_locks; + share->tot_locks=r_locks+w_locks; + if (!error) + DBUG_RETURN(0); + } +err: + _ma_check_print_error(param,"%d when updating keyfile",my_errno); + DBUG_RETURN(1); +} + +/* + Update auto increment value for a table + When setting the 'repair_only' flag we only want to change the + old auto_increment value if its wrong (smaller than some given key). + The reason is that we shouldn't change the auto_increment value + for a table without good reason when only doing a repair; If the + user have inserted and deleted rows, the auto_increment value + may be bigger than the biggest current row and this is ok. + + If repair_only is not set, we will update the flag to the value in + param->auto_increment is bigger than the biggest key. +*/ + +void _ma_update_auto_increment_key(HA_CHECK *param, MARIA_HA *info, + my_bool repair_only) +{ + MARIA_SHARE *share= info->s; + uchar *record; + DBUG_ENTER("update_auto_increment_key"); + + if (!share->base.auto_key || + ! maria_is_key_active(share->state.key_map, share->base.auto_key - 1)) + { + if (!(param->testflag & T_VERY_SILENT)) + _ma_check_print_info(param, + "Table: %s doesn't have an auto increment key\n", + param->isam_file_name); + DBUG_VOID_RETURN; + } + if (!(param->testflag & T_SILENT) && + !(param->testflag & T_REP)) + printf("Updating MARIA file: %s\n", param->isam_file_name); + /* + We have to use an allocated buffer instead of info->rec_buff as + _ma_put_key_in_record() may use info->rec_buff + */ + if (!(record= (uchar*) my_malloc((size_t) share->base.default_rec_buff_size, + MYF(0)))) + { + _ma_check_print_error(param,"Not enough memory for extra record"); + DBUG_VOID_RETURN; + } + + maria_extra(info,HA_EXTRA_KEYREAD,0); + if (maria_rlast(info, record, share->base.auto_key-1)) + { + if (my_errno != HA_ERR_END_OF_FILE) + { + maria_extra(info,HA_EXTRA_NO_KEYREAD,0); + my_free((char*) record, MYF(0)); + _ma_check_print_error(param,"%d when reading last record",my_errno); + DBUG_VOID_RETURN; + } + if (!repair_only) + share->state.auto_increment=param->auto_increment_value; + } + else + { + const HA_KEYSEG *keyseg= share->keyinfo[share->base.auto_key-1].seg; + ulonglong auto_increment= + ma_retrieve_auto_increment(record + keyseg->start, keyseg->type); + set_if_bigger(share->state.auto_increment,auto_increment); + if (!repair_only) + set_if_bigger(share->state.auto_increment, param->auto_increment_value); + } + maria_extra(info,HA_EXTRA_NO_KEYREAD,0); + my_free((char*) record, MYF(0)); + maria_update_state_info(param, info, UPDATE_AUTO_INC); + DBUG_VOID_RETURN; +} + + +/* + Update statistics for each part of an index + + SYNOPSIS + maria_update_key_parts() + keyinfo IN Index information (only key->keysegs used) + rec_per_key_part OUT Store statistics here + unique IN Array of (#distinct tuples) + notnull_tuples IN Array of (#tuples), or NULL + records Number of records in the table + + DESCRIPTION + This function is called produce index statistics values from unique and + notnull_tuples arrays after these arrays were produced with sequential + index scan (the scan is done in two places: chk_index() and + sort_key_write()). + + This function handles all 3 index statistics collection methods. + + Unique is an array: + unique[0]= (#different values of {keypart1}) - 1 + unique[1]= (#different values of {keypart1,keypart2} tuple)-unique[0]-1 + ... + + For MI_STATS_METHOD_IGNORE_NULLS method, notnull_tuples is an array too: + notnull_tuples[0]= (#of {keypart1} tuples such that keypart1 is not NULL) + notnull_tuples[1]= (#of {keypart1,keypart2} tuples such that all + keypart{i} are not NULL) + ... + For all other statistics collection methods notnull_tuples==NULL. + + Output is an array: + rec_per_key_part[k] = + = E(#records in the table such that keypart_1=c_1 AND ... AND + keypart_k=c_k for arbitrary constants c_1 ... c_k) + + = {assuming that values have uniform distribution and index contains all + tuples from the domain (or that {c_1, ..., c_k} tuple is choosen from + index tuples} + + = #tuples-in-the-index / #distinct-tuples-in-the-index. + + The #tuples-in-the-index and #distinct-tuples-in-the-index have different + meaning depending on which statistics collection method is used: + + MI_STATS_METHOD_* how are nulls compared? which tuples are counted? + NULLS_EQUAL NULL == NULL all tuples in table + NULLS_NOT_EQUAL NULL != NULL all tuples in table + IGNORE_NULLS n/a tuples that don't have NULLs +*/ + +void maria_update_key_parts(MARIA_KEYDEF *keyinfo, double *rec_per_key_part, + ulonglong *unique, ulonglong *notnull, + ulonglong records) +{ + ulonglong count=0, unique_tuples; + ulonglong tuples= records; + uint parts; + double tmp; + for (parts=0 ; parts < keyinfo->keysegs ; parts++) + { + count+=unique[parts]; + unique_tuples= count + 1; + if (notnull) + { + tuples= notnull[parts]; + /* + #(unique_tuples not counting tuples with NULLs) = + #(unique_tuples counting tuples with NULLs as different) - + #(tuples with NULLs) + */ + unique_tuples -= (records - notnull[parts]); + } + + if (unique_tuples == 0) + tmp= 1; + else if (count == 0) + tmp= ulonglong2double(tuples); /* 1 unique tuple */ + else + tmp= ulonglong2double(tuples) / ulonglong2double(unique_tuples); + + /* + for some weird keys (e.g. FULLTEXT) tmp can be <1 here. + let's ensure it is not + */ + set_if_bigger(tmp,1); + + *rec_per_key_part++= tmp; + } +} + + +static ha_checksum maria_byte_checksum(const uchar *buf, uint length) +{ + ha_checksum crc; + const uchar *end=buf+length; + for (crc=0; buf != end; buf++) + crc=((crc << 1) + *buf) + + test(crc & (((ha_checksum) 1) << (8*sizeof(ha_checksum)-1))); + return crc; +} + +static my_bool maria_too_big_key_for_sort(MARIA_KEYDEF *key, ha_rows rows) +{ + uint key_maxlength=key->maxlength; + if (key->flag & HA_FULLTEXT) + { + uint ft_max_word_len_for_sort=FT_MAX_WORD_LEN_FOR_SORT* + key->seg->charset->mbmaxlen; + key_maxlength+=ft_max_word_len_for_sort-HA_FT_MAXBYTELEN; + } + return (key->flag & HA_SPATIAL) || + (key->flag & (HA_BINARY_PACK_KEY | HA_VAR_LENGTH_KEY | HA_FULLTEXT) && + ((ulonglong) rows * key_maxlength > + (ulonglong) maria_max_temp_length)); +} + +/* + Deactivate all not unique index that can be recreated fast + These include packed keys on which sorting will use more temporary + space than the max allowed file length or for which the unpacked keys + will take much more space than packed keys. + Note that 'rows' may be zero for the case when we don't know how many + rows we will put into the file. + */ + +void maria_disable_non_unique_index(MARIA_HA *info, ha_rows rows) +{ + MARIA_SHARE *share= info->s; + MARIA_KEYDEF *key=share->keyinfo; + uint i; + + DBUG_ASSERT(share->state.state.records == 0 && + (!rows || rows >= MARIA_MIN_ROWS_TO_DISABLE_INDEXES)); + for (i=0 ; i < share->base.keys ; i++,key++) + { + if (!(key->flag & + (HA_NOSAME | HA_SPATIAL | HA_AUTO_KEY | HA_RTREE_INDEX)) && + ! maria_too_big_key_for_sort(key,rows) && share->base.auto_key != i+1) + { + maria_clear_key_active(share->state.key_map, i); + info->update|= HA_STATE_CHANGED; + } + } +} + + +/* + Return TRUE if we can use repair by sorting + One can set the force argument to force to use sorting + even if the temporary file would be quite big! +*/ + +my_bool maria_test_if_sort_rep(MARIA_HA *info, ha_rows rows, + ulonglong key_map, my_bool force) +{ + MARIA_SHARE *share= info->s; + MARIA_KEYDEF *key=share->keyinfo; + uint i; + + /* + maria_repair_by_sort only works if we have at least one key. If we don't + have any keys, we should use the normal repair. + */ + if (! maria_is_any_key_active(key_map)) + return FALSE; /* Can't use sort */ + for (i=0 ; i < share->base.keys ; i++,key++) + { + if (!force && maria_too_big_key_for_sort(key,rows)) + return FALSE; + } + return TRUE; +} + + +/** + @brief Create a new handle for manipulation the new record file + + @note + It's ok for Recovery to have two MARIA_SHARE on the same index file + because the one we create here is not transactional +*/ + +static my_bool create_new_data_handle(MARIA_SORT_PARAM *param, File new_file) +{ + + MARIA_SORT_INFO *sort_info= param->sort_info; + MARIA_HA *info= sort_info->info; + MARIA_HA *new_info; + DBUG_ENTER("create_new_data_handle"); + + if (!(sort_info->new_info= maria_open(info->s->open_file_name.str, O_RDWR, + HA_OPEN_COPY | HA_OPEN_FOR_REPAIR))) + DBUG_RETURN(1); + + new_info= sort_info->new_info; + _ma_bitmap_set_pagecache_callbacks(&new_info->s->bitmap.file, + new_info->s); + _ma_set_data_pagecache_callbacks(&new_info->dfile, new_info->s); + change_data_file_descriptor(new_info, new_file); + maria_lock_database(new_info, F_EXTRA_LCK); + if ((sort_info->param->testflag & T_UNPACK) && + info->s->data_file_type == COMPRESSED_RECORD) + { + (*new_info->s->once_end)(new_info->s); + (*new_info->s->end)(new_info); + restore_data_file_type(new_info->s); + _ma_setup_functions(new_info->s); + if ((*new_info->s->once_init)(new_info->s, new_file) || + (*new_info->s->init)(new_info)) + DBUG_RETURN(1); + } + _ma_reset_status(new_info); + if (_ma_initialize_data_file(new_info->s, new_file)) + DBUG_RETURN(1); + + /* Take into account any bitmap page created above: */ + param->filepos= new_info->s->state.state.data_file_length; + + /* Use new virtual functions for key generation */ + info->s->keypos_to_recpos= new_info->s->keypos_to_recpos; + info->s->recpos_to_keypos= new_info->s->recpos_to_keypos; + DBUG_RETURN(0); +} + + +static void +set_data_file_type(MARIA_SORT_INFO *sort_info, MARIA_SHARE *share) +{ + if ((sort_info->new_data_file_type=share->data_file_type) == + COMPRESSED_RECORD && sort_info->param->testflag & T_UNPACK) + { + MARIA_SHARE tmp; + sort_info->new_data_file_type= share->state.header.org_data_file_type; + /* Set delete_function for sort_delete_record() */ + tmp= *share; + tmp.state.header.data_file_type= tmp.state.header.org_data_file_type; + tmp.options= ~HA_OPTION_COMPRESS_RECORD; + _ma_setup_functions(&tmp); + share->delete_record=tmp.delete_record; + } +} + +static void restore_data_file_type(MARIA_SHARE *share) +{ + MARIA_SHARE tmp_share; + share->options&= ~HA_OPTION_COMPRESS_RECORD; + mi_int2store(share->state.header.options,share->options); + share->state.header.data_file_type= + share->state.header.org_data_file_type; + share->data_file_type= share->state.header.data_file_type; + share->pack.header_length= 0; + + /* Use new virtual functions for key generation */ + tmp_share= *share; + _ma_setup_functions(&tmp_share); + share->keypos_to_recpos= tmp_share.keypos_to_recpos; + share->recpos_to_keypos= tmp_share.recpos_to_keypos; +} + + +static void change_data_file_descriptor(MARIA_HA *info, File new_file) +{ + my_close(info->dfile.file, MYF(MY_WME)); + info->dfile.file= info->s->bitmap.file.file= new_file; + _ma_bitmap_reset_cache(info->s); +} + + +/** + @brief Mark the data file to not be used + + @note + This is used in repair when we want to ensure the handler will not + write anything to the data file anymore +*/ + +static void unuse_data_file_descriptor(MARIA_HA *info) +{ + info->dfile.file= info->s->bitmap.file.file= -1; + _ma_bitmap_reset_cache(info->s); +} + + +/* + Copy all states that has to do with the data file + + NOTES + This is done to copy the state from the data file generated from + repair to the original handler +*/ + +static void copy_data_file_state(MARIA_STATE_INFO *to, + MARIA_STATE_INFO *from) +{ + to->state.records= from->state.records; + to->state.del= from->state.del; + to->state.empty= from->state.empty; + to->state.data_file_length= from->state.data_file_length; + to->split= from->split; + to->dellink= from->dellink; + to->first_bitmap_with_space= from->first_bitmap_with_space; +} + + +/* + Read 'safely' next record while scanning table. + + SYNOPSIS + _ma_safe_scan_block_record() + info Maria handler + record Store found here + + NOTES + - One must have called mi_scan() before this + + Differences compared to _ma_scan_block_records() are: + - We read all blocks, not only blocks marked by the bitmap to be safe + - In case of errors, next read will read next record. + - More sanity checks + + RETURN + 0 ok + HA_ERR_END_OF_FILE End of file + # error number +*/ + + +static int _ma_safe_scan_block_record(MARIA_SORT_INFO *sort_info, + MARIA_HA *info, uchar *record) +{ + MARIA_SHARE *share= info->s; + MARIA_RECORD_POS record_pos= info->cur_row.nextpos; + pgcache_page_no_t page= sort_info->page; + DBUG_ENTER("_ma_safe_scan_block_record"); + + for (;;) + { + /* Find next row in current page */ + if (likely(record_pos < info->scan.number_of_rows)) + { + uint length, offset; + uchar *data, *end_of_data; + char llbuff[22]; + + while (!(offset= uint2korr(info->scan.dir))) + { + info->scan.dir-= DIR_ENTRY_SIZE; + record_pos++; + if (info->scan.dir < info->scan.dir_end) + { + _ma_check_print_info(sort_info->param, + "Wrong directory on page %s", + llstr(page, llbuff)); + goto read_next_page; + } + } + /* found row */ + info->cur_row.lastpos= info->scan.row_base_page + record_pos; + info->cur_row.nextpos= record_pos + 1; + data= info->scan.page_buff + offset; + length= uint2korr(info->scan.dir + 2); + end_of_data= data + length; + info->scan.dir-= DIR_ENTRY_SIZE; /* Point to previous row */ + + if (end_of_data > info->scan.dir_end || + offset < PAGE_HEADER_SIZE || length < share->base.min_block_length) + { + _ma_check_print_info(sort_info->param, + "Wrong directory entry %3u at page %s", + (uint) record_pos, llstr(page, llbuff)); + record_pos++; + continue; + } + else + { + DBUG_PRINT("info", ("rowid: %lu", (ulong) info->cur_row.lastpos)); + DBUG_RETURN(_ma_read_block_record2(info, record, data, end_of_data)); + } + } + +read_next_page: + /* Read until we find next head page */ + for (;;) + { + uint page_type; + char llbuff[22]; + + sort_info->page++; /* In case of errors */ + page++; + if (!(page % share->bitmap.pages_covered)) + { + /* Skip bitmap */ + page++; + sort_info->page++; + } + if ((my_off_t) (page + 1) * share->block_size > sort_info->filelength) + DBUG_RETURN(HA_ERR_END_OF_FILE); + if (!(pagecache_read(share->pagecache, + &info->dfile, + page, 0, info->scan.page_buff, + PAGECACHE_READ_UNKNOWN_PAGE, + PAGECACHE_LOCK_LEFT_UNLOCKED, 0))) + { + if (my_errno == HA_ERR_WRONG_CRC) + { + _ma_check_print_info(sort_info->param, + "Wrong CRC on datapage at %s", + llstr(page, llbuff)); + continue; + } + DBUG_RETURN(my_errno); + } + page_type= (info->scan.page_buff[PAGE_TYPE_OFFSET] & + PAGE_TYPE_MASK); + if (page_type == HEAD_PAGE) + { + if ((info->scan.number_of_rows= + (uint) (uchar) info->scan.page_buff[DIR_COUNT_OFFSET]) != 0) + break; + _ma_check_print_info(sort_info->param, + "Wrong head page at page %s", + llstr(page, llbuff)); + } + else if (page_type >= MAX_PAGE_TYPE) + { + _ma_check_print_info(sort_info->param, + "Found wrong page type: %d at page %s", + page_type, llstr(page, llbuff)); + } + } + + /* New head page */ + info->scan.dir= (info->scan.page_buff + share->block_size - + PAGE_SUFFIX_SIZE - DIR_ENTRY_SIZE); + info->scan.dir_end= (info->scan.dir - + (info->scan.number_of_rows - 1) * + DIR_ENTRY_SIZE); + info->scan.row_base_page= ma_recordpos(page, 0); + record_pos= 0; + } +} + + +/** + @brief Writes a LOGREC_REPAIR_TABLE record and updates create_rename_lsn + if needed (so that maria_read_log does not redo the repair). + + @param param description of the REPAIR operation + @param info table + + @return Operation status + @retval 0 ok + @retval 1 error (disk problem) +*/ + +my_bool write_log_record_for_repair(const HA_CHECK *param, MARIA_HA *info) +{ + MARIA_SHARE *share= info->s; + /* in case this is maria_chk or recovery... */ + if (translog_status == TRANSLOG_OK && !maria_in_recovery && + share->base.born_transactional) + { + my_bool save_now_transactional= share->now_transactional; + + /* + For now this record is only informative. It could serve when applying + logs to a backup, but that needs more thought. Assume table became + corrupted. It is repaired, then some writes happen to it. + Later we restore an old backup, and want to apply this REDO_REPAIR_TABLE + record. For it to give the same result as originally, the table should + be corrupted the same way, so applying previous REDOs should produce the + same corruption; that's really not guaranteed (different execution paths + in execution of REDOs vs runtime code so not same bugs hit, temporary + hardware issues not repeatable etc). Corruption may not be repeatable. + A reasonable solution is to execute the REDO_REPAIR_TABLE record and + check if the checksum of the resulting table matches what it was at the + end of the original repair (should be stored in log record); or execute + the REDO_REPAIR_TABLE if the checksum of the table-before-repair matches + was it was at the start of the original repair (should be stored in log + record). + */ + LEX_CUSTRING log_array[TRANSLOG_INTERNAL_PARTS + 1]; + uchar log_data[FILEID_STORE_SIZE + 8 + 8]; + LSN lsn; + + /* + testflag gives an idea of what REPAIR did (in particular T_QUICK + or not: did it touch the data file or not?). + */ + int8store(log_data + FILEID_STORE_SIZE, param->testflag); + /* org_key_map is used when recreating index after a load data infile */ + int8store(log_data + FILEID_STORE_SIZE + 8, param->org_key_map); + + log_array[TRANSLOG_INTERNAL_PARTS + 0].str= log_data; + log_array[TRANSLOG_INTERNAL_PARTS + 0].length= sizeof(log_data); + + share->now_transactional= 1; + if (unlikely(translog_write_record(&lsn, LOGREC_REDO_REPAIR_TABLE, + &dummy_transaction_object, info, + (translog_size_t) sizeof(log_data), + sizeof(log_array)/sizeof(log_array[0]), + log_array, log_data, NULL) || + translog_flush(lsn))) + return TRUE; + /* + The table's existence was made durable earlier (MY_SYNC_DIR passed to + maria_change_to_newfile()). All pages have been flushed, state too, we + need to force it to disk. Old REDOs should not be applied to the table, + which is already enforced as skip_redos_lsn was increased in + protect_against_repair_crash(). But if this is an explicit repair, + even UNDO phase should ignore this table: create_rename_lsn should be + increased, and this also serves for the REDO_REPAIR to be ignored by + maria_read_log. + The fully correct order would be: sync data and index file, remove crash + mark and update LSNs then write state and sync index file. But at this + point state (without crash mark) is already written. + */ + if ((!(param->testflag & T_NO_CREATE_RENAME_LSN) && + _ma_update_state_lsns(share, lsn, share->state.create_trid, FALSE, + FALSE)) || + _ma_sync_table_files(info)) + return TRUE; + share->now_transactional= save_now_transactional; + } + return FALSE; +} + + +/** + Writes an UNDO record which if executed in UNDO phase, will empty the + table. Such record is thus logged only in certain cases of bulk insert + (table needs to be empty etc). +*/ +my_bool write_log_record_for_bulk_insert(MARIA_HA *info) +{ + LEX_CUSTRING log_array[TRANSLOG_INTERNAL_PARTS + 1]; + uchar log_data[LSN_STORE_SIZE + FILEID_STORE_SIZE]; + LSN lsn; + lsn_store(log_data, info->trn->undo_lsn); + log_array[TRANSLOG_INTERNAL_PARTS + 0].str= log_data; + log_array[TRANSLOG_INTERNAL_PARTS + 0].length= sizeof(log_data); + return translog_write_record(&lsn, LOGREC_UNDO_BULK_INSERT, + info->trn, info, + (translog_size_t) + log_array[TRANSLOG_INTERNAL_PARTS + + 0].length, + TRANSLOG_INTERNAL_PARTS + 1, log_array, + log_data + LSN_STORE_SIZE, NULL) || + translog_flush(lsn); /* WAL */ +} + + +/* Give error message why reading of key page failed */ + +static void report_keypage_fault(HA_CHECK *param, MARIA_HA *info, + my_off_t position) +{ + char buff[11]; + uint32 block_size= info->s->block_size; + + if (my_errno == HA_ERR_CRASHED) + _ma_check_print_error(param, + "Wrong base information on indexpage at page: %s", + llstr(position / block_size, buff)); + else + _ma_check_print_error(param, + "Can't read indexpage from page: %s, " + "error: %d", + llstr(position / block_size, buff), my_errno); +} + + +/** + When we want to check a table, we verify that the transaction ids of rows + and keys are not bigger than the biggest id generated by Maria so far, which + is returned by the function below. + + @note If control file is not open, 0 may be returned; to not confuse + this with a valid max trid of 0, the caller should notice that it failed to + open the control file (ma_control_file_inited() can serve for that). +*/ + +static TrID max_trid_in_system(void) +{ + TrID id= trnman_get_max_trid(); /* 0 if transac manager not initialized */ + /* 'id' may be far bigger, if last shutdown is old */ + return max(id, max_trid_in_control_file); +} + + +static void _ma_check_print_not_visible_error(HA_CHECK *param, TrID used_trid) +{ + char buff[22], buff2[22]; + if (!param->not_visible_rows_found++) + { + if (!ma_control_file_inited()) + { + _ma_check_print_warning(param, + "Found row with transaction id %s but no " + "maria_control_file was specified. " + "The table may be corrupted", + llstr(used_trid, buff)); + } + else + { + _ma_check_print_error(param, + "Found row with transaction id %s when max " + "transaction id according to maria_control_file " + "is %s", + llstr(used_trid, buff), + llstr(param->max_trid, buff2)); + } + } +} + + +/** + Mark that we can retry normal repair if we used quick repair + + We shouldn't do this in case of disk error as in this case we are likely + to loose much more than expected. +*/ + +void retry_if_quick(MARIA_SORT_PARAM *sort_param, int error) +{ + HA_CHECK *param=sort_param->sort_info->param; + + if (!sort_param->fix_datafile && error >= HA_ERR_FIRST) + { + param->retry_repair=1; + param->testflag|=T_RETRY_WITHOUT_QUICK; + } +} diff --git a/storage/maria/ma_check_standalone.h b/storage/maria/ma_check_standalone.h new file mode 100644 index 00000000000..3874d722d6c --- /dev/null +++ b/storage/maria/ma_check_standalone.h @@ -0,0 +1,106 @@ +/* Copyright (C) 2007 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 */ + +/* + All standalone programs which need to use functions from ma_check.c + (like maria_repair()) must define their version of _ma_killed_ptr() + and _ma_check_print_info|warning|error(). Indeed, linking with ma_check.o + brings in the dependencies of ma_check.o which are definitions of the above + functions; if the program does not define them then the ones of + ha_maria.o are used i.e. ha_maria.o is linked into the program, and this + brings dependencies of ha_maria.o on mysqld.o into the program's linking + which thus fails, as the program is not linked with mysqld.o. + This file contains the versions of these functions used by maria_chk and + maria_read_log. +*/ + +/* + Check if check/repair operation was killed by a signal +*/ + +static int not_killed= 0; + +volatile int *_ma_killed_ptr(HA_CHECK *param __attribute__((unused))) +{ + return ¬_killed; /* always NULL */ +} + + /* print warnings and errors */ + /* VARARGS */ + +void _ma_check_print_info(HA_CHECK *param __attribute__((unused)), + const char *fmt,...) +{ + va_list args; + DBUG_ENTER("_ma_check_print_info"); + DBUG_PRINT("enter", ("format: %s", fmt)); + + va_start(args,fmt); + VOID(vfprintf(stdout, fmt, args)); + VOID(fputc('\n',stdout)); + va_end(args); + DBUG_VOID_RETURN; +} + +/* VARARGS */ + +void _ma_check_print_warning(HA_CHECK *param, const char *fmt,...) +{ + va_list args; + DBUG_ENTER("_ma_check_print_warning"); + DBUG_PRINT("enter", ("format: %s", fmt)); + + fflush(stdout); + if (!param->warning_printed && !param->error_printed) + { + if (param->testflag & T_SILENT) + fprintf(stderr,"%s: MARIA file %s\n",my_progname_short, + param->isam_file_name); + param->out_flag|= O_DATA_LOST; + } + param->warning_printed=1; + va_start(args,fmt); + fprintf(stderr,"%s: warning: ",my_progname_short); + VOID(vfprintf(stderr, fmt, args)); + VOID(fputc('\n',stderr)); + fflush(stderr); + va_end(args); + DBUG_VOID_RETURN; +} + +/* VARARGS */ + +void _ma_check_print_error(HA_CHECK *param, const char *fmt,...) +{ + va_list args; + DBUG_ENTER("_ma_check_print_error"); + DBUG_PRINT("enter", ("format: %s", fmt)); + + fflush(stdout); + if (!param->warning_printed && !param->error_printed) + { + if (param->testflag & T_SILENT) + fprintf(stderr,"%s: MARIA file %s\n",my_progname_short,param->isam_file_name); + param->out_flag|= O_DATA_LOST; + } + param->error_printed|=1; + va_start(args,fmt); + fprintf(stderr,"%s: error: ",my_progname_short); + VOID(vfprintf(stderr, fmt, args)); + VOID(fputc('\n',stderr)); + fflush(stderr); + va_end(args); + DBUG_VOID_RETURN; +} diff --git a/storage/maria/ma_checkpoint.c b/storage/maria/ma_checkpoint.c new file mode 100644 index 00000000000..6d1a6332c54 --- /dev/null +++ b/storage/maria/ma_checkpoint.c @@ -0,0 +1,1235 @@ +/* Copyright (C) 2006,2007 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 */ + +/* + WL#3071 Maria checkpoint + First version written by Guilhem Bichot on 2006-04-27. +*/ + +/* Here is the implementation of this module */ + +/** @todo RECOVERY BUG this is unreviewed code */ +/* + Summary: + checkpoints are done either by a background thread (checkpoint every Nth + second) or by a client. + In ha_maria, it's not made available to clients, and will soon be done by a + background thread (periodically taking checkpoints and flushing dirty + pages). +*/ + +#include "maria_def.h" +#include "ma_pagecache.h" +#include "ma_blockrec.h" +#include "ma_checkpoint.h" +#include "ma_loghandler_lsn.h" + + +/** @brief type of checkpoint currently running */ +static CHECKPOINT_LEVEL checkpoint_in_progress= CHECKPOINT_NONE; +/** @brief protects checkpoint_in_progress */ +static pthread_mutex_t LOCK_checkpoint; +/** @brief for killing the background checkpoint thread */ +static pthread_cond_t COND_checkpoint; +/** @brief if checkpoint module was inited or not */ +static my_bool checkpoint_inited= FALSE; +/** @brief 'kill' flag for the background checkpoint thread */ +static int checkpoint_thread_die; +/* is ulong like pagecache->blocks_changed */ +static ulong pages_to_flush_before_next_checkpoint; +static PAGECACHE_FILE *dfiles, /**< data files to flush in background */ + *dfiles_end; /**< list of data files ends here */ +static PAGECACHE_FILE *kfiles, /**< index files to flush in background */ + *kfiles_end; /**< list of index files ends here */ +/* those two statistics below could serve in SHOW GLOBAL STATUS */ +static uint checkpoints_total= 0, /**< all checkpoint requests made */ + checkpoints_ok_total= 0; /**< all checkpoints which succeeded */ + +struct st_filter_param +{ + LSN up_to_lsn; /**< only pages with rec_lsn < this LSN */ + uint max_pages; /**< stop after flushing this number pages */ +}; /**< information to determine which dirty pages should be flushed */ + +static enum pagecache_flush_filter_result +filter_flush_file_medium(enum pagecache_page_type type, + pgcache_page_no_t page, + LSN rec_lsn, void *arg); +static enum pagecache_flush_filter_result +filter_flush_file_full(enum pagecache_page_type type, + pgcache_page_no_t page, + LSN rec_lsn, void *arg); +static enum pagecache_flush_filter_result +filter_flush_file_evenly(enum pagecache_page_type type, + pgcache_page_no_t pageno, + LSN rec_lsn, void *arg); +static int really_execute_checkpoint(void); +pthread_handler_t ma_checkpoint_background(void *arg); +static int collect_tables(LEX_STRING *str, LSN checkpoint_start_log_horizon); + +/** + @brief Does a checkpoint + + @param level what level of checkpoint to do + @param no_wait if another checkpoint of same or stronger level + is already running, consider our job done + + @note In ha_maria, there can never be two threads trying a checkpoint at + the same time. + + @return Operation status + @retval 0 ok + @retval !=0 error +*/ + +int ma_checkpoint_execute(CHECKPOINT_LEVEL level, my_bool no_wait) +{ + int result= 0; + DBUG_ENTER("ma_checkpoint_execute"); + + if (!checkpoint_inited) + { + /* + If ha_maria failed to start, maria_panic_hton is called, we come here. + */ + DBUG_RETURN(0); + } + DBUG_ASSERT(level > CHECKPOINT_NONE); + + /* look for already running checkpoints */ + pthread_mutex_lock(&LOCK_checkpoint); + while (checkpoint_in_progress != CHECKPOINT_NONE) + { + if (no_wait && (checkpoint_in_progress >= level)) + { + /* + If we are the checkpoint background thread, we don't wait (it's + smarter to flush pages instead of waiting here while the other thread + finishes its checkpoint). + */ + pthread_mutex_unlock(&LOCK_checkpoint); + goto end; + } + pthread_cond_wait(&COND_checkpoint, &LOCK_checkpoint); + } + + checkpoint_in_progress= level; + pthread_mutex_unlock(&LOCK_checkpoint); + /* from then on, we are sure to be and stay the only checkpointer */ + + result= really_execute_checkpoint(); + pthread_cond_broadcast(&COND_checkpoint); +end: + DBUG_RETURN(result); +} + + +/** + @brief Does a checkpoint, really; expects no other checkpoints + running. + + Checkpoint level requested is read from checkpoint_in_progress. + + @return Operation status + @retval 0 ok + @retval !=0 error +*/ + +static int really_execute_checkpoint(void) +{ + uint i, error= 0; + /** @brief checkpoint_start_log_horizon will be stored there */ + char *ptr; + LEX_STRING record_pieces[4]; /**< only malloc-ed pieces */ + LSN min_page_rec_lsn, min_trn_rec_lsn, min_first_undo_lsn; + TRANSLOG_ADDRESS checkpoint_start_log_horizon; + char checkpoint_start_log_horizon_char[LSN_STORE_SIZE]; + DBUG_ENTER("really_execute_checkpoint"); + DBUG_PRINT("enter", ("level: %d", checkpoint_in_progress)); + bzero(&record_pieces, sizeof(record_pieces)); + + /* + STEP 1: record current end-of-log position using log's lock. It is + critical for the correctness of Checkpoint (related to memory visibility + rules, the log's lock is a mutex). + "Horizon" is a lower bound of the LSN of the next log record. + */ + checkpoint_start_log_horizon= translog_get_horizon(); + DBUG_PRINT("info",("checkpoint_start_log_horizon (%lu,0x%lx)", + LSN_IN_PARTS(checkpoint_start_log_horizon))); + lsn_store(checkpoint_start_log_horizon_char, checkpoint_start_log_horizon); + + /* + STEP 2: fetch information about transactions. + We must fetch transactions before dirty pages. Indeed, a transaction + first sets its rec_lsn then sets the page's rec_lsn then sets its rec_lsn + to 0. If we fetched pages first, we may see no dirty page yet, then we + fetch transactions but the transaction has already reset its rec_lsn to 0 + so we miss rec_lsn again. + For a similar reason (over-allocated bitmap pages) we have to fetch + transactions before flushing bitmap pages. + + min_trn_rec_lsn will serve to lower the starting point of the REDO phase + (down from checkpoint_start_log_horizon). + */ + if (unlikely(trnman_collect_transactions(&record_pieces[0], + &record_pieces[1], + &min_trn_rec_lsn, + &min_first_undo_lsn))) + goto err; + + + /* STEP 3: fetch information about table files */ + if (unlikely(collect_tables(&record_pieces[2], + checkpoint_start_log_horizon))) + goto err; + + + /* STEP 4: fetch information about dirty pages */ + /* + It's better to do it _after_ having flushed some data pages (which + collect_tables() may have done), because those are now non-dirty and so we + have a more up-to-date dirty pages list to put into the checkpoint record, + and thus we will have less work at Recovery. + */ + /* Using default pagecache for now */ + if (unlikely(pagecache_collect_changed_blocks_with_lsn(maria_pagecache, + &record_pieces[3], + &min_page_rec_lsn))) + goto err; + + + /* LAST STEP: now write the checkpoint log record */ + { + LSN lsn; + translog_size_t total_rec_length; + /* + the log handler is allowed to modify "str" and "length" (but not "*str") + of its argument, so we must not pass it record_pieces directly, + otherwise we would later not know what memory pieces to my_free(). + */ + LEX_CUSTRING log_array[TRANSLOG_INTERNAL_PARTS + 5]; + log_array[TRANSLOG_INTERNAL_PARTS + 0].str= + (uchar*) checkpoint_start_log_horizon_char; + log_array[TRANSLOG_INTERNAL_PARTS + 0].length= total_rec_length= + sizeof(checkpoint_start_log_horizon_char); + for (i= 0; i < (sizeof(record_pieces)/sizeof(record_pieces[0])); i++) + { + log_array[TRANSLOG_INTERNAL_PARTS + 1 + i]= + *(LEX_CUSTRING *)&record_pieces[i]; + total_rec_length+= (translog_size_t) record_pieces[i].length; + } + if (unlikely(translog_write_record(&lsn, LOGREC_CHECKPOINT, + &dummy_transaction_object, NULL, + total_rec_length, + sizeof(log_array)/sizeof(log_array[0]), + log_array, NULL, NULL) || + translog_flush(lsn))) + goto err; + translog_lock(); + /* + This cannot be done as a inwrite_rec_hook of LOGREC_CHECKPOINT, because + such hook would be called before translog_flush (and we must be sure + that log was flushed before we write to the control file). + */ + if (unlikely(ma_control_file_write_and_force(lsn, last_logno, + max_trid_in_control_file, + recovery_failures))) + { + translog_unlock(); + goto err; + } + translog_unlock(); + } + + /* + Note that we should not alter memory structures until we have successfully + written the checkpoint record and control file. + */ + /* checkpoint succeeded */ + ptr= record_pieces[3].str; + pages_to_flush_before_next_checkpoint= uint4korr(ptr); + DBUG_PRINT("checkpoint",("%u pages to flush before next checkpoint", + (uint)pages_to_flush_before_next_checkpoint)); + + /* compute log's low-water mark */ + { + TRANSLOG_ADDRESS log_low_water_mark= min_page_rec_lsn; + set_if_smaller(log_low_water_mark, min_trn_rec_lsn); + set_if_smaller(log_low_water_mark, min_first_undo_lsn); + set_if_smaller(log_low_water_mark, checkpoint_start_log_horizon); + /** + Now purge unneeded logs. + As some systems have an unreliable fsync (drive lying), we could try to + be robust against that: remember a few previous checkpoints in the + control file, and not purge logs immediately... Think about it. + */ + if (translog_purge(log_low_water_mark)) + ma_message_no_user(0, "log purging failed"); + } + + goto end; + +err: + error= 1; + ma_message_no_user(0, "checkpoint failed"); + /* we were possibly not able to determine what pages to flush */ + pages_to_flush_before_next_checkpoint= 0; + +end: + for (i= 0; i < (sizeof(record_pieces)/sizeof(record_pieces[0])); i++) + my_free(record_pieces[i].str, MYF(MY_ALLOW_ZERO_PTR)); + pthread_mutex_lock(&LOCK_checkpoint); + checkpoint_in_progress= CHECKPOINT_NONE; + checkpoints_total++; + checkpoints_ok_total+= !error; + pthread_mutex_unlock(&LOCK_checkpoint); + DBUG_RETURN(error); +} + + +/** + @brief Initializes the checkpoint module + + @param interval If one wants the module to create a + thread which will periodically do + checkpoints, and flush dirty pages, in the + background, it should specify a non-zero + interval in seconds. The thread will then be + created and will take checkpoints separated by + approximately 'interval' second. + + @note A checkpoint is taken only if there has been some significant + activity since the previous checkpoint. Between checkpoint N and N+1 the + thread flushes all dirty pages which were already dirty at the time of + checkpoint N. + + @return Operation status + @retval 0 ok + @retval !=0 error +*/ + +int ma_checkpoint_init(ulong interval) +{ + pthread_t th; + int res= 0; + DBUG_ENTER("ma_checkpoint_init"); + checkpoint_inited= TRUE; + checkpoint_thread_die= 2; /* not yet born == dead */ + if (pthread_mutex_init(&LOCK_checkpoint, MY_MUTEX_INIT_SLOW) || + pthread_cond_init(&COND_checkpoint, 0)) + res= 1; + else if (interval > 0) + { + compile_time_assert(sizeof(void *) >= sizeof(ulong)); + if (!(res= pthread_create(&th, NULL, ma_checkpoint_background, + (void *)interval))) + checkpoint_thread_die= 0; /* thread lives, will have to be killed */ + } + DBUG_RETURN(res); +} + + +#ifndef DBUG_OFF +/** + Function used to test recovery: flush some table pieces and then caller + crashes. + + @param what_to_flush 0: current bitmap and all data pages + 1: state + 2: all bitmap pages +*/ +static void flush_all_tables(int what_to_flush) +{ + int res= 0; + LIST *pos; /**< to iterate over open tables */ + pthread_mutex_lock(&THR_LOCK_maria); + for (pos= maria_open_list; pos; pos= pos->next) + { + MARIA_HA *info= (MARIA_HA*)pos->data; + if (info->s->now_transactional) + { + switch (what_to_flush) + { + case 0: + res= _ma_flush_table_files(info, MARIA_FLUSH_DATA | MARIA_FLUSH_INDEX, + FLUSH_KEEP, FLUSH_KEEP); + break; + case 1: + res= _ma_state_info_write(info->s, + MA_STATE_INFO_WRITE_DONT_MOVE_OFFSET| + MA_STATE_INFO_WRITE_LOCK); + DBUG_PRINT("maria_flush_states", + ("is_of_horizon: LSN (%lu,0x%lx)", + LSN_IN_PARTS(info->s->state.is_of_horizon))); + break; + case 2: + res= _ma_bitmap_flush_all(info->s); + break; + } + } + DBUG_ASSERT(res == 0); + } + pthread_mutex_unlock(&THR_LOCK_maria); +} +#endif + + +/** + @brief Destroys the checkpoint module +*/ + +void ma_checkpoint_end(void) +{ + DBUG_ENTER("ma_checkpoint_end"); + /* + Some intentional crash methods, usually triggered by + SET MARIA_CHECKPOINT_INTERVAL=X + */ + DBUG_EXECUTE_IF("maria_flush_bitmap", + { + DBUG_PRINT("maria_flush_bitmap", ("now")); + flush_all_tables(2); + }); + DBUG_EXECUTE_IF("maria_flush_whole_page_cache", + { + DBUG_PRINT("maria_flush_whole_page_cache", ("now")); + flush_all_tables(0); + }); + DBUG_EXECUTE_IF("maria_flush_whole_log", + { + DBUG_PRINT("maria_flush_whole_log", ("now")); + translog_flush(translog_get_horizon()); + }); + /* + Note that for WAL reasons, maria_flush_states requires + maria_flush_whole_log. + */ + DBUG_EXECUTE_IF("maria_flush_states", + { + DBUG_PRINT("maria_flush_states", ("now")); + flush_all_tables(1); + }); + DBUG_EXECUTE_IF("maria_crash", + { DBUG_PRINT("maria_crash", ("now")); DBUG_ABORT(); }); + + if (checkpoint_inited) + { + pthread_mutex_lock(&LOCK_checkpoint); + if (checkpoint_thread_die != 2) /* thread was started ok */ + { + DBUG_PRINT("info",("killing Maria background checkpoint thread")); + checkpoint_thread_die= 1; /* kill it */ + do /* and wait for it to be dead */ + { + /* wake it up if it was in a sleep */ + pthread_cond_broadcast(&COND_checkpoint); + DBUG_PRINT("info",("waiting for Maria background checkpoint thread" + " to die")); + pthread_cond_wait(&COND_checkpoint, &LOCK_checkpoint); + } + while (checkpoint_thread_die != 2); + } + pthread_mutex_unlock(&LOCK_checkpoint); + my_free((uchar *)dfiles, MYF(MY_ALLOW_ZERO_PTR)); + my_free((uchar *)kfiles, MYF(MY_ALLOW_ZERO_PTR)); + dfiles= kfiles= NULL; + pthread_mutex_destroy(&LOCK_checkpoint); + pthread_cond_destroy(&COND_checkpoint); + checkpoint_inited= FALSE; + } + DBUG_VOID_RETURN; +} + + +/** + @brief dirty-page filtering criteria for MEDIUM checkpoint. + + We flush data/index pages which have been dirty since the previous + checkpoint (this is the two-checkpoint rule: the REDO phase will not have + to start from earlier than the next-to-last checkpoint). + Bitmap pages are handled by _ma_bitmap_flush_all(). + + @param type Page's type + @param pageno Page's number + @param rec_lsn Page's rec_lsn + @param arg filter_param +*/ + +static enum pagecache_flush_filter_result +filter_flush_file_medium(enum pagecache_page_type type, + pgcache_page_no_t pageno __attribute__ ((unused)), + LSN rec_lsn, void *arg) +{ + struct st_filter_param *param= (struct st_filter_param *)arg; + return (type == PAGECACHE_LSN_PAGE) && + (cmp_translog_addr(rec_lsn, param->up_to_lsn) <= 0); +} + + +/** + @brief dirty-page filtering criteria for FULL checkpoint. + + We flush all dirty data/index pages. + Bitmap pages are handled by _ma_bitmap_flush_all(). + + @param type Page's type + @param pageno Page's number + @param rec_lsn Page's rec_lsn + @param arg filter_param +*/ + +static enum pagecache_flush_filter_result +filter_flush_file_full(enum pagecache_page_type type, + pgcache_page_no_t pageno __attribute__ ((unused)), + LSN rec_lsn __attribute__ ((unused)), + void *arg __attribute__ ((unused))) +{ + return (type == PAGECACHE_LSN_PAGE); +} + + +/** + @brief dirty-page filtering criteria for background flushing thread. + + We flush data/index pages which have been dirty since the previous + checkpoint (this is the two-checkpoint rule: the REDO phase will not have + to start from earlier than the next-to-last checkpoint), and no + bitmap pages. But we flush no more than a certain number of pages (to have + an even flushing, no write burst). + The reason to not flush bitmap pages is that they may not be in a flushable + state at this moment and we don't want to wait for them. + + @param type Page's type + @param pageno Page's number + @param rec_lsn Page's rec_lsn + @param arg filter_param +*/ + +static enum pagecache_flush_filter_result +filter_flush_file_evenly(enum pagecache_page_type type, + pgcache_page_no_t pageno __attribute__ ((unused)), + LSN rec_lsn, void *arg) +{ + struct st_filter_param *param= (struct st_filter_param *)arg; + if (unlikely(param->max_pages == 0)) /* all flushed already */ + return FLUSH_FILTER_SKIP_ALL; + if ((type == PAGECACHE_LSN_PAGE) && + (cmp_translog_addr(rec_lsn, param->up_to_lsn) <= 0)) + { + param->max_pages--; + return FLUSH_FILTER_OK; + } + return FLUSH_FILTER_SKIP_TRY_NEXT; +} + + +/** + @brief Background thread which does checkpoints and flushes periodically. + + Takes a checkpoint. After this, all pages dirty at the time of that + checkpoint are flushed evenly until it is time to take another checkpoint. + This ensures that the REDO phase starts at earliest (in LSN time) at the + next-to-last checkpoint record ("two-checkpoint rule"). + + @note MikaelR questioned why the same thread does two different jobs, the + risk could be that while a checkpoint happens no LRD flushing happens. +*/ + +pthread_handler_t ma_checkpoint_background(void *arg) +{ + /** @brief At least this of log/page bytes written between checkpoints */ + const uint checkpoint_min_activity= 2*1024*1024; + /* + If the interval could be changed by the user while we are in this thread, + it could be annoying: for example it could cause "case 2" to be executed + right after "case 0", thus having 'dfile' unset. So the thread cares only + about the interval's value when it started. + */ + const ulong interval= (ulong)arg; + uint sleeps, sleep_time; + TRANSLOG_ADDRESS log_horizon_at_last_checkpoint= + translog_get_horizon(); + ulonglong pagecache_flushes_at_last_checkpoint= + maria_pagecache->global_cache_write; + uint pages_bunch_size; + struct st_filter_param filter_param; + PAGECACHE_FILE *dfile; /**< data file currently being flushed */ + PAGECACHE_FILE *kfile; /**< index file currently being flushed */ + LINT_INIT(kfile); + LINT_INIT(dfile); + LINT_INIT(pages_bunch_size); + + my_thread_init(); + DBUG_PRINT("info",("Maria background checkpoint thread starts")); + DBUG_ASSERT(interval > 0); + + /* + Recovery ended with all tables closed and a checkpoint: no need to take + one immediately. + */ + sleeps= 1; + pages_to_flush_before_next_checkpoint= 0; + + for(;;) /* iterations of checkpoints and dirty page flushing */ + { +#if 0 /* good for testing, to do a lot of checkpoints, finds a lot of bugs */ + sleeps=0; +#endif + struct timespec abstime; + switch (sleeps % interval) + { + case 0: + /* + With background flushing evenly distributed over the time + between two checkpoints, we should have only little flushing to do + in the checkpoint. + */ + /* + No checkpoint if little work of interest for recovery was done + since last checkpoint. Such work includes log writing (lengthens + recovery, checkpoint would shorten it), page flushing (checkpoint + would decrease the amount of read pages in recovery). + In case of one short statement per minute (very low load), we don't + want to checkpoint every minute, hence the positive + checkpoint_min_activity. + */ + if (((translog_get_horizon() - log_horizon_at_last_checkpoint) + + (maria_pagecache->global_cache_write - + pagecache_flushes_at_last_checkpoint) * + maria_pagecache->block_size) < checkpoint_min_activity) + { + /* don't take checkpoint, so don't know what to flush */ + pages_to_flush_before_next_checkpoint= 0; + sleep_time= interval; + break; + } + sleep_time= 1; + ma_checkpoint_execute(CHECKPOINT_MEDIUM, TRUE); + /* + Snapshot this kind of "state" of the engine. Note that the value below + is possibly greater than last_checkpoint_lsn. + */ + log_horizon_at_last_checkpoint= translog_get_horizon(); + pagecache_flushes_at_last_checkpoint= + maria_pagecache->global_cache_write; + /* + If the checkpoint above succeeded it has set d|kfiles and + d|kfiles_end. If is has failed, it has set + pages_to_flush_before_next_checkpoint to 0 so we will skip flushing + and sleep until the next checkpoint. + */ + break; + case 1: + /* set up parameters for background page flushing */ + filter_param.up_to_lsn= last_checkpoint_lsn; + pages_bunch_size= pages_to_flush_before_next_checkpoint / interval; + dfile= dfiles; + kfile= kfiles; + /* fall through */ + default: + if (pages_bunch_size > 0) + { + DBUG_PRINT("checkpoint", + ("Maria background checkpoint thread: %u pages", + pages_bunch_size)); + /* flush a bunch of dirty pages */ + filter_param.max_pages= pages_bunch_size; + while (dfile != dfiles_end) + { + /* + We use FLUSH_KEEP_LAZY: if a file is already in flush, it's + smarter to move to the next file than wait for this one to be + completely flushed, which may take long. + StaleFilePointersInFlush: notice how below we use "dfile" which + is an OS file descriptor plus some function and MARIA_SHARE + pointers; this data dates from a previous checkpoint; since then, + the table may have been closed (so MARIA_SHARE* became stale), and + the file descriptor reassigned to another table which does not + have the same CRC-read-set callbacks: it is thus important that + flush_pagecache_blocks_with_filter() does not use the pointers, + only the OS file descriptor. + */ + int res= + flush_pagecache_blocks_with_filter(maria_pagecache, + dfile, FLUSH_KEEP_LAZY, + filter_flush_file_evenly, + &filter_param); + if (unlikely(res & PCFLUSH_ERROR)) + ma_message_no_user(0, "background data page flush failed"); + if (filter_param.max_pages == 0) /* bunch all flushed, sleep */ + break; /* and we will continue with the same file */ + dfile++; /* otherwise all this file is flushed, move to next file */ + /* + MikaelR noted that he observed that Linux's file cache may never + fsync to disk until this cache is full, at which point it decides + to empty the cache, making the machine very slow. A solution was + to fsync after writing 2 MB. So we might want to fsync() here if + we wrote enough pages. + */ + } + while (kfile != kfiles_end) + { + int res= + flush_pagecache_blocks_with_filter(maria_pagecache, + kfile, FLUSH_KEEP_LAZY, + filter_flush_file_evenly, + &filter_param); + if (unlikely(res & PCFLUSH_ERROR)) + ma_message_no_user(0, "background index page flush failed"); + if (filter_param.max_pages == 0) /* bunch all flushed, sleep */ + break; /* and we will continue with the same file */ + kfile++; /* otherwise all this file is flushed, move to next file */ + } + sleep_time= 1; + } + else + { + /* Can directly sleep until the next checkpoint moment */ + sleep_time= interval - (sleeps % interval); + } + } + pthread_mutex_lock(&LOCK_checkpoint); + if (checkpoint_thread_die == 1) + break; +#if 0 /* good for testing, to do a lot of checkpoints, finds a lot of bugs */ + pthread_mutex_unlock(&LOCK_checkpoint); + my_sleep(100000); /* a tenth of a second */ + pthread_mutex_lock(&LOCK_checkpoint); +#else + /* To have a killable sleep, we use timedwait like our SQL GET_LOCK() */ + DBUG_PRINT("info", ("sleeping %u seconds", sleep_time)); + set_timespec(abstime, sleep_time); + pthread_cond_timedwait(&COND_checkpoint, &LOCK_checkpoint, &abstime); +#endif + if (checkpoint_thread_die == 1) + break; + pthread_mutex_unlock(&LOCK_checkpoint); + sleeps+= sleep_time; + } + pthread_mutex_unlock(&LOCK_checkpoint); + DBUG_PRINT("info",("Maria background checkpoint thread ends")); + { + CHECKPOINT_LEVEL level= CHECKPOINT_FULL; + /* + That's the final one, which guarantees that a clean shutdown always ends + with a checkpoint. + */ + DBUG_EXECUTE_IF("maria_checkpoint_indirect", level= CHECKPOINT_INDIRECT;); + ma_checkpoint_execute(level, FALSE); + } + pthread_mutex_lock(&LOCK_checkpoint); + checkpoint_thread_die= 2; /* indicate that we are dead */ + /* wake up ma_checkpoint_end() which may be waiting for our death */ + pthread_cond_broadcast(&COND_checkpoint); + /* broadcast was inside unlock because ma_checkpoint_end() destroys mutex */ + pthread_mutex_unlock(&LOCK_checkpoint); + my_thread_end(); + return 0; +} + + +/** + @brief Allocates buffer and stores in it some info about open tables, + does some flushing on those. + + Does the allocation because the caller cannot know the size itself. + Memory freeing is to be done by the caller (if the "str" member of the + LEX_STRING is not NULL). + The caller is taking a checkpoint. + + @param[out] str pointer to where the allocated buffer, + and its size, will be put; buffer will be filled + with info about open tables + @param checkpoint_start_log_horizon Of the in-progress checkpoint + record. + + @return Operation status + @retval 0 OK + @retval 1 Error +*/ + +static int collect_tables(LEX_STRING *str, LSN checkpoint_start_log_horizon) +{ + MARIA_SHARE **distinct_shares= NULL; + char *ptr; + uint error= 1, sync_error= 0, nb, nb_stored, i; + my_bool unmark_tables= TRUE; + uint total_names_length; + LIST *pos; /**< to iterate over open tables */ + struct st_state_copy { + uint index; + MARIA_STATE_INFO state; + }; + struct st_state_copy *state_copies= NULL, /**< fixed-size cache of states */ + *state_copies_end, /**< cache ends here */ + *state_copy; /**< iterator in cache */ + TRANSLOG_ADDRESS state_copies_horizon; /**< horizon of states' _copies_ */ + struct st_filter_param filter_param; + PAGECACHE_FLUSH_FILTER filter; + DBUG_ENTER("collect_tables"); + + LINT_INIT(state_copies_horizon); + /* let's make a list of distinct shares */ + pthread_mutex_lock(&THR_LOCK_maria); + for (nb= 0, pos= maria_open_list; pos; pos= pos->next) + { + MARIA_HA *info= (MARIA_HA*)pos->data; + MARIA_SHARE *share= info->s; + /* the first three variables below can never change */ + if (share->base.born_transactional && !share->temporary && + share->mode != O_RDONLY && + !(share->in_checkpoint & MARIA_CHECKPOINT_SEEN_IN_LOOP)) + { + /* + Apart from us, only maria_close() reads/sets in_checkpoint but cannot + run now as we hold THR_LOCK_maria. + */ + /* + This table is relevant for checkpoint and not already seen. Mark it, + so that it is not seen again in the loop. + */ + nb++; + DBUG_ASSERT(share->in_checkpoint == 0); + /* This flag ensures that we count only _distinct_ shares. */ + share->in_checkpoint= MARIA_CHECKPOINT_SEEN_IN_LOOP; + } + } + if (unlikely((distinct_shares= + (MARIA_SHARE **)my_malloc(nb * sizeof(MARIA_SHARE *), + MYF(MY_WME))) == NULL)) + goto err; + for (total_names_length= 0, i= 0, pos= maria_open_list; pos; pos= pos->next) + { + MARIA_HA *info= (MARIA_HA*)pos->data; + MARIA_SHARE *share= info->s; + if (share->in_checkpoint & MARIA_CHECKPOINT_SEEN_IN_LOOP) + { + distinct_shares[i++]= share; + /* + With this we prevent the share from going away while we later flush + and force it without holding THR_LOCK_maria. For example if the share + could be my_free()d by maria_close() we would have a problem when we + access it to flush the table. We "pin" the share pointer. + And we also take down MARIA_CHECKPOINT_SEEN_IN_LOOP, so that it is + not seen again in the loop. + */ + share->in_checkpoint= MARIA_CHECKPOINT_LOOKS_AT_ME; + /** @todo avoid strlen() */ + total_names_length+= share->open_file_name.length; + } + } + + DBUG_ASSERT(i == nb); + pthread_mutex_unlock(&THR_LOCK_maria); + DBUG_PRINT("info",("found %u table shares", nb)); + + str->length= + 4 + /* number of tables */ + (2 + /* short id */ + LSN_STORE_SIZE + /* first_log_write_at_lsn */ + 1 /* end-of-name 0 */ + ) * nb + total_names_length; + if (unlikely((str->str= my_malloc(str->length, MYF(MY_WME))) == NULL)) + goto err; + + ptr= str->str; + ptr+= 4; /* real number of stored tables is not yet know */ + + /* only possible checkpointer, so can do the read below without mutex */ + filter_param.up_to_lsn= last_checkpoint_lsn; + switch(checkpoint_in_progress) + { + case CHECKPOINT_MEDIUM: + filter= &filter_flush_file_medium; + break; + case CHECKPOINT_FULL: + filter= &filter_flush_file_full; + break; + case CHECKPOINT_INDIRECT: + filter= NULL; + break; + default: + DBUG_ASSERT(0); + goto err; + } + + /* + The principle of reading/writing the state below is explained in + ma_recovery.c, look for "Recovery of the state". + */ +#define STATE_COPIES 1024 + state_copies= (struct st_state_copy *) + my_malloc(STATE_COPIES * sizeof(struct st_state_copy), MYF(MY_WME)); + dfiles= (PAGECACHE_FILE *)my_realloc((uchar *)dfiles, + /* avoid size of 0 for my_realloc */ + max(1, nb) * sizeof(PAGECACHE_FILE), + MYF(MY_WME | MY_ALLOW_ZERO_PTR)); + kfiles= (PAGECACHE_FILE *)my_realloc((uchar *)kfiles, + /* avoid size of 0 for my_realloc */ + max(1, nb) * sizeof(PAGECACHE_FILE), + MYF(MY_WME | MY_ALLOW_ZERO_PTR)); + if (unlikely((state_copies == NULL) || + (dfiles == NULL) || (kfiles == NULL))) + goto err; + state_copy= state_copies_end= NULL; + dfiles_end= dfiles; + kfiles_end= kfiles; + + for (nb_stored= 0, i= 0; i < nb; i++) + { + MARIA_SHARE *share= distinct_shares[i]; + PAGECACHE_FILE kfile, dfile; + my_bool ignore_share; + if (!(share->in_checkpoint & MARIA_CHECKPOINT_LOOKS_AT_ME)) + { + /* + No need for a mutex to read the above, only us can write *this* bit of + the in_checkpoint bitmap + */ + continue; + } + /** + @todo We should not look at tables which didn't change since last + checkpoint. + */ + DBUG_PRINT("info",("looking at table '%s'", share->open_file_name.str)); + if (state_copy == state_copies_end) /* we have no more cached states */ + { + /* + Collect and cache a bunch of states. We do this for many states at a + time, to not lock/unlock the log's lock too often. + */ + uint j, bound= min(nb, i + STATE_COPIES); + state_copy= state_copies; + /* part of the state is protected by log's lock */ + translog_lock(); + state_copies_horizon= translog_get_horizon_no_lock(); + for (j= i; j < bound; j++) + { + MARIA_SHARE *share2= distinct_shares[j]; + if (!(share2->in_checkpoint & MARIA_CHECKPOINT_LOOKS_AT_ME)) + continue; + state_copy->index= j; + state_copy->state= share2->state; /* we copy the state */ + state_copy++; + /* + data_file_length is not updated under log's lock by the bitmap + code, but writing a wrong data_file_length is ok: a next + maria_close() will correct it; if we crash before, Recovery will + set it to the true physical size. + */ + } + translog_unlock(); + /** + We are going to flush these states. + Before, all records describing how to undo such state must be + in the log (WAL). Usually this means UNDOs. In the special case of + data|key_file_length, recovery just needs to open the table to fix the + length, so any LOGREC_FILE_ID/REDO/UNDO allowing recovery to + understand it must open a table, is enough; so as long as + data|key_file_length is updated after writing any log record it's ok: + if we copied new value above, it means the record was before + state_copies_horizon and we flush such record below. + Apart from data|key_file_length which are easily recoverable from the + real file's size, all other state members must be updated only when + writing the UNDO; otherwise, if updated before, if their new value is + flushed by a checkpoint and there is a crash before UNDO is written, + their REDO group will be missing or at least incomplete and skipped + by recovery, so bad state value will stay. For example, setting + key_root before writing the UNDO: the table would have old index + pages (they were pinned at time of crash) and a new, thus wrong, + key_root. + @todo RECOVERY BUG check that all code honours that. + */ + if (translog_flush(state_copies_horizon)) + goto err; + /* now we have cached states and they are WAL-safe*/ + state_copies_end= state_copy; + state_copy= state_copies; + } + + /* locate our state among these cached ones */ + for ( ; state_copy->index != i; state_copy++) + DBUG_ASSERT(state_copy < state_copies_end); + + /* OS file descriptors are ints which we stored in 4 bytes */ + compile_time_assert(sizeof(int) <= 4); + /* + Protect against maria_close() (which does some memory freeing in + MARIA_FILE_BITMAP) with close_lock. intern_lock is not + sufficient as we, as well as maria_close(), are going to unlock + intern_lock in the middle of manipulating the table. Serializing us and + maria_close() should help avoid problems. + */ + pthread_mutex_lock(&share->close_lock); + pthread_mutex_lock(&share->intern_lock); + /* + Tables in a normal state have their two file descriptors open. + In some rare cases like REPAIR, some descriptor may be closed or even + -1. If that happened, the _ma_state_info_write() may fail. This is + prevented by enclosing all all places which close/change kfile.file with + intern_lock. + */ + kfile= share->kfile; + dfile= share->bitmap.file; + /* + Ignore table which has no logged writes (all its future log records will + be found naturally by Recovery). Ignore obsolete shares (_before_ + setting themselves to last_version=0 they already did all flush and + sync; if we flush their state now we may be flushing an obsolete state + onto a newer one (assuming the table has been reopened with a different + share but of course same physical index file). + */ + ignore_share= (share->id == 0) | (share->last_version == 0); + DBUG_PRINT("info", ("ignore_share: %d", ignore_share)); + if (!ignore_share) + { + uint open_file_name_len= share->open_file_name.length + 1; + /* remember the descriptors for background flush */ + *(dfiles_end++)= dfile; + *(kfiles_end++)= kfile; + /* we will store this table in the record */ + nb_stored++; + int2store(ptr, share->id); + ptr+= 2; + lsn_store(ptr, share->lsn_of_file_id); + ptr+= LSN_STORE_SIZE; + /* + first_bitmap_with_space is not updated under log's lock, and is + important. We would need the bitmap's lock to get it right. Recovery + of this is not clear, so we just play safe: write it out as + unknown: if crash, _ma_bitmap_init() at next open (for example in + Recovery) will convert it to 0 and thus the first insertion will + search for free space from the file's first bitmap (0) - + under-optimal but safe. + If no crash, maria_close() will write the exact value. + */ + state_copy->state.first_bitmap_with_space= ~(ulonglong)0; + memcpy(ptr, share->open_file_name.str, open_file_name_len); + ptr+= open_file_name_len; + if (cmp_translog_addr(share->state.is_of_horizon, + checkpoint_start_log_horizon) >= 0) + { + /* + State was flushed recently, it does not hold down the log's + low-water mark and will not give avoidable work to Recovery. So we + needn't flush it. Also, it is possible that while we copied the + state above (under log's lock, without intern_lock) it was being + modified in memory or flushed to disk (without log's lock, under + intern_lock, like in maria_extra()), so our copy may be incorrect + and we should not flush it. + It may also be a share which got last_version==0 since we checked + last_version; in this case, it flushed its state and the LSN test + above will catch it. + */ + } + else + { + /* + We could do the state flush only if share->changed, but it's + tricky. + Consider a maria_write() which has written REDO,UNDO, and before it + calls _ma_writeinfo() (setting share->changed=1), checkpoint + happens and sees share->changed=0, does not flush state. It is + possible that Recovery does not start from before the REDO and thus + the state is not recovered. A solution may be to set + share->changed=1 under log mutex when writing log records. + But as anyway we have another problem below, this optimization would + be of little use. + */ + /** @todo flush state only if changed since last checkpoint */ + DBUG_ASSERT(share->last_version != 0); + state_copy->state.is_of_horizon= share->state.is_of_horizon= + state_copies_horizon; + if (kfile.file >= 0) + sync_error|= + _ma_state_info_write_sub(kfile.file, &state_copy->state, + MA_STATE_INFO_WRITE_DONT_MOVE_OFFSET); + /* + We don't set share->changed=0 because it may interfere with a + concurrent _ma_writeinfo() doing share->changed=1 (cancel its + effect). The sad consequence is that we will flush the same state at + each checkpoint if the table was once written and then not anymore. + */ + } + } + /* + _ma_bitmap_flush_all() may wait, so don't keep intern_lock as + otherwise this would deadlock with allocate_and_write_block_record() + calling _ma_set_share_data_file_length() + */ + pthread_mutex_unlock(&share->intern_lock); + + if (!ignore_share) + { + /* + share->bitmap is valid because it's destroyed under close_lock which + we hold. + */ + if (_ma_bitmap_flush_all(share)) + { + sync_error= 1; + /** @todo all write failures should mark table corrupted */ + ma_message_no_user(0, "checkpoint bitmap page flush failed"); + } + DBUG_ASSERT(share->pagecache == maria_pagecache); + } + /* + Clean up any unused states. + TODO: Only do this call if there has been # (10?) ended transactions + since last call. + We had to release intern_lock to respect lock order with LOCK_trn_list. + */ + _ma_remove_not_visible_states_with_lock(share, FALSE); + + if (share->in_checkpoint & MARIA_CHECKPOINT_SHOULD_FREE_ME) + { + /* + maria_close() left us free the share. When it run it set share->id + to 0. As it run before we locked close_lock, we should have seen this + and so this assertion should be true: + */ + DBUG_ASSERT(ignore_share); + pthread_mutex_destroy(&share->intern_lock); + pthread_mutex_unlock(&share->close_lock); + pthread_mutex_destroy(&share->close_lock); + my_free((uchar *)share, MYF(0)); + } + else + { + /* share goes back to normal state */ + share->in_checkpoint= 0; + pthread_mutex_unlock(&share->close_lock); + } + + /* + We do the big disk writes out of intern_lock to not block other + users of this table (intern_lock is taken at the start and end of + every statement). This means that file descriptors may be invalid + (files may have been closed for example by HA_EXTRA_PREPARE_FOR_* + under Windows, or REPAIR). This should not be a problem as we use + MY_IGNORE_BADFD. Descriptors may even point to other files but then + the old blocks (of before the close) must have been flushed for sure, + so our flush will flush new blocks (of after the latest open) and that + should do no harm. + */ + /* + If CHECKPOINT_MEDIUM, this big flush below may result in a + serious write burst. Realize that all pages dirtied between the + last checkpoint and the one we are doing now, will be flushed at + next checkpoint, except those evicted by LRU eviction (depending on + the size of the page cache compared to the size of the working data + set, eviction may be rare or frequent). + We avoid that burst by anticipating: those pages are flushed + in bunches spanned regularly over the time interval between now and + the next checkpoint, by a background thread. Thus the next checkpoint + will have only little flushing to do (CHECKPOINT_MEDIUM should thus be + only a little slower than CHECKPOINT_INDIRECT). + */ + + /* + PageCacheFlushConcurrencyBugs + Inside the page cache, calls to flush_pagecache_blocks_int() on the same + file are serialized. Examples of concurrency bugs which happened when we + didn't have this serialization: + - maria_chk_size() (via CHECK TABLE) happens concurrently with + Checkpoint: Checkpoint is flushing a page: it pins the page and is + pre-empted, maria_chk_size() wants to flush this page too so gets an + error because Checkpoint pinned this page. Such error makes + maria_chk_size() mark the table as corrupted. + - maria_close() happens concurrently with Checkpoint: + Checkpoint is flushing a page: it registers a request on the page, is + pre-empted ; maria_close() flushes this page too with FLUSH_RELEASE: + FLUSH_RELEASE will cause a free_block() which assumes the page is in the + LRU, but it is not (as Checkpoint registered a request). Crash. + - one thread is evicting a page of the file out of the LRU: it marks it + iPC_BLOCK_IN_SWITCH and is pre-empted. Then two other threads do flushes + of the same file concurrently (like above). Then one flusher sees the + page is in switch, removes it from changed_blocks[] and puts it in its + first_in_switch, so the other flusher will not see the page at all and + return too early. If it's maria_close() which returns too early, then + maria_close() may close the file descriptor, and the other flusher, and + the evicter will fail to write their page: corruption. + */ + + if (!ignore_share) + { + if (filter != NULL) + { + if ((flush_pagecache_blocks_with_filter(maria_pagecache, + &dfile, FLUSH_KEEP_LAZY, + filter, &filter_param) & + PCFLUSH_ERROR)) + ma_message_no_user(0, "checkpoint data page flush failed"); + if ((flush_pagecache_blocks_with_filter(maria_pagecache, + &kfile, FLUSH_KEEP_LAZY, + filter, &filter_param) & + PCFLUSH_ERROR)) + ma_message_no_user(0, "checkpoint index page flush failed"); + } + /* + fsyncs the fd, that's the loooong operation (e.g. max 150 fsync + per second, so if you have touched 1000 files it's 7 seconds). + */ + sync_error|= + my_sync(dfile.file, MYF(MY_WME | MY_IGNORE_BADFD)) | + my_sync(kfile.file, MYF(MY_WME | MY_IGNORE_BADFD)); + /* + in case of error, we continue because writing other tables to disk is + still useful. + */ + } + } + + if (sync_error) + goto err; + /* We maybe over-estimated (due to share->id==0 or last_version==0) */ + DBUG_ASSERT(str->length >= (uint)(ptr - str->str)); + str->length= (uint)(ptr - str->str); + /* + As we support max 65k tables open at a time (2-byte short id), we + assume uint is enough for the cumulated length of table names; and + LEX_STRING::length is uint. + */ + int4store(str->str, nb_stored); + error= unmark_tables= 0; + +err: + if (unlikely(unmark_tables)) + { + /* maria_close() uses THR_LOCK_maria from start to end */ + pthread_mutex_lock(&THR_LOCK_maria); + for (i= 0; i < nb; i++) + { + MARIA_SHARE *share= distinct_shares[i]; + if (share->in_checkpoint & MARIA_CHECKPOINT_SHOULD_FREE_ME) + { + /* maria_close() left us to free the share */ + pthread_mutex_destroy(&share->intern_lock); + my_free((uchar *)share, MYF(0)); + } + else + { + /* share goes back to normal state */ + share->in_checkpoint= 0; + } + } + pthread_mutex_unlock(&THR_LOCK_maria); + } + my_free((uchar *)distinct_shares, MYF(MY_ALLOW_ZERO_PTR)); + my_free((uchar *)state_copies, MYF(MY_ALLOW_ZERO_PTR)); + DBUG_RETURN(error); +} diff --git a/storage/maria/ma_checkpoint.h b/storage/maria/ma_checkpoint.h new file mode 100644 index 00000000000..69645c6bcda --- /dev/null +++ b/storage/maria/ma_checkpoint.h @@ -0,0 +1,92 @@ +/* Copyright (C) 2006,2007 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 */ + +/* + WL#3071 Maria checkpoint + First version written by Guilhem Bichot on 2006-04-27. + Does not compile yet. +*/ + +/* This is the interface of this module. */ + +typedef enum enum_ma_checkpoint_level { + CHECKPOINT_NONE= 0, + /* just write dirty_pages, transactions table and sync files */ + CHECKPOINT_INDIRECT, + /* also flush all dirty pages which were already dirty at prev checkpoint */ + CHECKPOINT_MEDIUM, + /* also flush all dirty pages */ + CHECKPOINT_FULL +} CHECKPOINT_LEVEL; + +C_MODE_START +int ma_checkpoint_init(ulong interval); +void ma_checkpoint_end(void); +int ma_checkpoint_execute(CHECKPOINT_LEVEL level, my_bool no_wait); +C_MODE_END + +/** + @brief reads some LSNs with special trickery + + If a 64-bit variable transitions between both halves being zero to both + halves being non-zero, and back, this function can be used to do a read of + it (without mutex, without atomic load) which always produces a correct + (though maybe slightly old) value (even on 32-bit CPUs). The value is at + least as new as the latest mutex unlock done by the calling thread. + The assumption is that the system sets both 4-byte halves either at the + same time, or one after the other (in any order), but NOT some bytes of the + first half then some bytes of the second half then the rest of bytes of the + first half. With this assumption, the function can detect when it is + seeing an inconsistent value. + + @param LSN pointer to the LSN variable to read + + @return LSN part (most significant byte always 0) +*/ +#if ( SIZEOF_CHARP >= 8 ) +/* 64-bit CPU, 64-bit reads are atomic */ +#define lsn_read_non_atomic LSN_WITH_FLAGS_TO_LSN +#else +static inline LSN lsn_read_non_atomic_32(const volatile LSN *x) +{ + /* + 32-bit CPU, 64-bit reads may give a mixed of old half and new half (old + low bits and new high bits, or the contrary). + */ + for (;;) /* loop until no atomicity problems */ + { + /* + Remove most significant byte in case this is a LSN_WITH_FLAGS object. + Those flags in TRN::first_undo_lsn break the condition on transitions so + they must be removed below. + */ + LSN y= LSN_WITH_FLAGS_TO_LSN(*x); + if (likely((y == LSN_IMPOSSIBLE) || LSN_VALID(y))) + return y; + } +} +#define lsn_read_non_atomic(x) lsn_read_non_atomic_32(&x) +#endif + +/** + prints a message from a task not connected to any user (checkpoint + and recovery for example). + + @param level 0 if error, ME_JUST_WARNING if warning, + ME_JUST_INFO if info + @param sentence text to write +*/ +#define ma_message_no_user(level, sentence) \ + my_printf_error(HA_ERR_GENERIC, "Maria engine: %s", MYF(level), sentence) diff --git a/storage/maria/ma_checksum.c b/storage/maria/ma_checksum.c new file mode 100644 index 00000000000..61ec638053a --- /dev/null +++ b/storage/maria/ma_checksum.c @@ -0,0 +1,89 @@ +/* Copyright (C) 2006 MySQL AB & MySQL Finland AB & TCX DataKonsult 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 */ + +/* Calculate a checksum for a row */ + +#include "maria_def.h" + +/** + Calculate a checksum for the record + + _ma_checksum() + @param info Maria handler + @param record Record + + @note + To ensure that the checksum is independent of the row format + we need to always calculate the checksum in the original field order. + + @return checksum +*/ + +ha_checksum _ma_checksum(MARIA_HA *info, const uchar *record) +{ + ha_checksum crc=0; + uint i,end; + MARIA_COLUMNDEF *base_column= info->s->columndef; + uint16 *column_nr= info->s->column_nr; + + if (info->s->base.null_bytes) + crc= my_checksum(crc, record, info->s->base.null_bytes); + + for (i= 0, end= info->s->base.fields ; i < end ; i++) + { + MARIA_COLUMNDEF *column= base_column + column_nr[i]; + const uchar *pos; + ulong length; + + if (record[column->null_pos] & column->null_bit) + continue; /* Null field */ + + pos= record + column->offset; + switch (column->type) { + case FIELD_BLOB: + { + uint blob_size_length= column->length- portable_sizeof_char_ptr; + length= _ma_calc_blob_length(blob_size_length, pos); + if (length) + { + memcpy((char*) &pos, pos + blob_size_length, sizeof(char*)); + crc= my_checksum(crc, pos, length); + } + continue; + } + case FIELD_VARCHAR: + { + uint pack_length= column->fill_length; + if (pack_length == 1) + length= (ulong) *pos; + else + length= uint2korr(pos); + pos+= pack_length; /* Skip length information */ + break; + } + default: + length= column->length; + break; + } + crc= my_checksum(crc, pos, length); + } + return crc; +} + + +ha_checksum _ma_static_checksum(MARIA_HA *info, const uchar *pos) +{ + return my_checksum(0, pos, info->s->base.reclength); +} diff --git a/storage/maria/ma_close.c b/storage/maria/ma_close.c new file mode 100644 index 00000000000..235b37f7030 --- /dev/null +++ b/storage/maria/ma_close.c @@ -0,0 +1,207 @@ +/* Copyright (C) 2006 MySQL AB & MySQL Finland AB & TCX DataKonsult 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 */ + +/* close a isam-database */ +/* + TODO: + We need to have a separate mutex on the closed file to allow other threads + to open other files during the time we flush the cache and close this file +*/ + +#include "maria_def.h" + +int maria_close(register MARIA_HA *info) +{ + int error=0,flag; + my_bool share_can_be_freed= FALSE; + MARIA_SHARE *share= info->s; + DBUG_ENTER("maria_close"); + DBUG_PRINT("enter",("base: 0x%lx reopen: %u locks: %u", + (long) info, (uint) share->reopen, + (uint) share->tot_locks)); + + /* Check that we have unlocked key delete-links properly */ + DBUG_ASSERT(info->key_del_used == 0); + + pthread_mutex_lock(&THR_LOCK_maria); + if (info->lock_type == F_EXTRA_LCK) + info->lock_type=F_UNLCK; /* HA_EXTRA_NO_USER_CHANGE */ + + if (share->reopen == 1 && share->kfile.file >= 0) + _ma_decrement_open_count(info); + + if (info->lock_type != F_UNLCK) + { + if (maria_lock_database(info,F_UNLCK)) + error=my_errno; + } + pthread_mutex_lock(&share->close_lock); + pthread_mutex_lock(&share->intern_lock); + + if (share->options & HA_OPTION_READ_ONLY_DATA) + { + share->r_locks--; + share->tot_locks--; + } + if (info->opt_flag & (READ_CACHE_USED | WRITE_CACHE_USED)) + { + if (end_io_cache(&info->rec_cache)) + error=my_errno; + info->opt_flag&= ~(READ_CACHE_USED | WRITE_CACHE_USED); + } + flag= !--share->reopen; + maria_open_list=list_delete(maria_open_list,&info->open_list); + + my_free(info->rec_buff, MYF(MY_ALLOW_ZERO_PTR)); + (*share->end)(info); + + if (flag) + { + /* Last close of file; Flush everything */ + + /* Check that we don't have any dangling pointers from the transaction */ + DBUG_ASSERT(share->in_trans == 0); + + if (share->kfile.file >= 0) + { + if ((*share->once_end)(share)) + error= my_errno; + if (flush_pagecache_blocks(share->pagecache, &share->kfile, + (share->temporary ? + FLUSH_IGNORE_CHANGED : + FLUSH_RELEASE))) + error= my_errno; +#ifdef HAVE_MMAP + if (share->file_map) + _ma_unmap_file(info); +#endif + /* + If we are crashed, we can safely flush the current state as it will + not change the crashed state. + We can NOT write the state in other cases as other threads + may be using the file at this point + IF using --external-locking, which does not apply to Maria. + */ + if (((share->changed && share->base.born_transactional) || + maria_is_crashed(info))) + { + /* + State must be written to file as it was not done at table's + unlocking. + */ + if (_ma_state_info_write(share, MA_STATE_INFO_WRITE_DONT_MOVE_OFFSET)) + error= my_errno; + } + /* + File must be synced as it is going out of the maria_open_list and so + becoming unknown to future Checkpoints. + */ + if (share->now_transactional && my_sync(share->kfile.file, MYF(MY_WME))) + error= my_errno; + if (my_close(share->kfile.file, MYF(0))) + error= my_errno; + } +#ifdef THREAD + thr_lock_delete(&share->lock); + (void) pthread_mutex_destroy(&share->key_del_lock); + { + int i,keys; + keys = share->state.header.keys; + VOID(rwlock_destroy(&share->mmap_lock)); + for(i=0; i<keys; i++) { + VOID(rwlock_destroy(&share->keyinfo[i].root_lock)); + } + } +#endif + DBUG_ASSERT(share->now_transactional == share->base.born_transactional); + /* + We assign -1 because checkpoint does not need to flush (in case we + have concurrent checkpoint if no then we do not need it here also) + */ + share->kfile.file= -1; + + /* + Remember share->history for future opens + + We have to unlock share->intern_lock then lock it after + LOCK_trn_list (trnman_lock()) to avoid dead locks. + */ + pthread_mutex_unlock(&share->intern_lock); + _ma_remove_not_visible_states_with_lock(share, TRUE); + pthread_mutex_lock(&share->intern_lock); + + if (share->in_checkpoint & MARIA_CHECKPOINT_LOOKS_AT_ME) + { + /* we cannot my_free() the share, Checkpoint would see a bad pointer */ + share->in_checkpoint|= MARIA_CHECKPOINT_SHOULD_FREE_ME; + } + else + share_can_be_freed= TRUE; + + if (share->state_history) + { + MARIA_STATE_HISTORY_CLOSED *history; + /* + Here we ignore the unlikely case that we don't have memory to + store the state. In the worst case what happens is that any transaction + that tries to access this table will get a wrong status information. + */ + if ((history= (MARIA_STATE_HISTORY_CLOSED *) + my_malloc(sizeof(*history), MYF(MY_WME)))) + { + history->create_rename_lsn= share->state.create_rename_lsn; + history->state_history= share->state_history; + if (my_hash_insert(&maria_stored_state, (uchar*) history)) + my_free(history, MYF(0)); + } + /* Marker for concurrent checkpoint */ + share->state_history= 0; + } + } + pthread_mutex_unlock(&THR_LOCK_maria); + pthread_mutex_unlock(&share->intern_lock); + pthread_mutex_unlock(&share->close_lock); + if (share_can_be_freed) + { + (void) pthread_mutex_destroy(&share->intern_lock); + (void) pthread_mutex_destroy(&share->close_lock); + my_free((uchar *)share, MYF(0)); + /* + If share cannot be freed, it's because checkpoint has previously + recorded to include this share in the checkpoint and so is soon going to + look at some of its content (share->in_checkpoint/id/last_version). + */ + } + my_free(info->ftparser_param, MYF(MY_ALLOW_ZERO_PTR)); + if (info->dfile.file >= 0) + { + /* + This is outside of mutex so would confuse a concurrent + Checkpoint. Fortunately in BLOCK_RECORD we close earlier under mutex. + */ + if (my_close(info->dfile.file, MYF(0))) + error= my_errno; + } + + delete_dynamic(&info->pinned_pages); + my_free(info, MYF(0)); + + if (error) + { + DBUG_PRINT("error", ("Got error on close: %d", my_errno)); + DBUG_RETURN(my_errno= error); + } + DBUG_RETURN(0); +} /* maria_close */ diff --git a/storage/maria/ma_commit.c b/storage/maria/ma_commit.c new file mode 100644 index 00000000000..70bc668a220 --- /dev/null +++ b/storage/maria/ma_commit.c @@ -0,0 +1,129 @@ +/* Copyright (C) 2007-2008 MySQL AB, 2008-2009 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#include "maria_def.h" +#include "trnman.h" + +/** + writes a COMMIT record to log and commits transaction in memory + + @param trn transaction + + @return Operation status + @retval 0 ok + @retval 1 error (disk error or out of memory) +*/ + +int ma_commit(TRN *trn) +{ + int res; + LSN commit_lsn; + LEX_CUSTRING log_array[TRANSLOG_INTERNAL_PARTS]; + DBUG_ENTER("ma_commit"); + + DBUG_ASSERT(trn->rec_lsn == LSN_IMPOSSIBLE); + if (trn->undo_lsn == 0) /* no work done, rollback (cheaper than commit) */ + DBUG_RETURN(trnman_rollback_trn(trn)); + /* + - if COMMIT record is written before trnman_commit_trn(): + if Checkpoint comes in the middle it will see trn is not committed, + then if crash, Recovery might roll back trn (if min(rec_lsn) is after + COMMIT record) and this is not an issue as + * transaction's updates were not made visible to other transactions + * "commit ok" was not sent to client + Alternatively, Recovery might commit trn (if min(rec_lsn) is before COMMIT + record), which is ok too. All in all it means that "trn committed" is not + 100% equal to "COMMIT record written". + - if COMMIT record is written after trnman_commit_trn(): + if crash happens between the two, trn will be rolled back which is an + issue (transaction's updates were made visible to other transactions). + So we need to go the first way. + + Note that we have to use | here to ensure that all calls are made. + */ + + /* + We do not store "thd->transaction.xid_state.xid" for now, it will be + needed only when we support XA. + */ + res= (translog_write_record(&commit_lsn, LOGREC_COMMIT, + trn, NULL, 0, + sizeof(log_array)/sizeof(log_array[0]), + log_array, NULL, NULL) | + translog_flush(commit_lsn)); + + DBUG_EXECUTE_IF("maria_sleep_in_commit", + { + DBUG_PRINT("info", ("maria_sleep_in_commit")); + sleep(3); + }); + res|= trnman_commit_trn(trn); + + + /* + Note: if trnman_commit_trn() fails above, we have already + written the COMMIT record, so Checkpoint and Recovery will see the + transaction as committed. + */ + DBUG_RETURN(res); +} + + +/** + Writes a COMMIT record for a transaciton associated with a file + + @param info Maria handler + + @return Operation status + @retval 0 ok + @retval # error (disk error or out of memory) +*/ + +int maria_commit(MARIA_HA *info) +{ + return info->s->now_transactional ? ma_commit(info->trn) : 0; +} + + +/** + Starts a transaction on a file handle + + @param info Maria handler + + @return Operation status + @retval 0 ok + @retval # Error code. + + @note this can be used only in single-threaded programs (tests), + because we create a transaction (trnman_new_trn) with WT_THD=0. + XXX it needs to be fixed when we'll start using maria_begin from SQL. +*/ + +int maria_begin(MARIA_HA *info) +{ + DBUG_ENTER("maria_begin"); + + if (info->s->now_transactional) + { + TRN *trn= trnman_new_trn(0); + if (unlikely(!trn)) + DBUG_RETURN(HA_ERR_OUT_OF_MEM); + + DBUG_PRINT("info", ("TRN set to 0x%lx", (ulong) trn)); + _ma_set_trn_for_table(info, trn); + } + DBUG_RETURN(0); +} + diff --git a/storage/maria/ma_commit.h b/storage/maria/ma_commit.h new file mode 100644 index 00000000000..2c57c73fd7a --- /dev/null +++ b/storage/maria/ma_commit.h @@ -0,0 +1,18 @@ +/* Copyright (C) 2007 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 */ + +C_MODE_START +int ma_commit(TRN *trn); +C_MODE_END diff --git a/storage/maria/ma_control_file.c b/storage/maria/ma_control_file.c new file mode 100644 index 00000000000..1e1fc34c77e --- /dev/null +++ b/storage/maria/ma_control_file.c @@ -0,0 +1,606 @@ +/* Copyright (C) 2007 MySQL AB & Guilhem Bichot & Michael Widenius + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You 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 */ + +/* + WL#3234 Maria control file + First version written by Guilhem Bichot on 2006-04-27. +*/ + +#ifndef EXTRACT_DEFINITIONS +#include "maria_def.h" +#include "ma_checkpoint.h" +#endif + +/* + A control file contains the following objects: + +Start of create time variables (at start of file): + - Magic string (including version number of Maria control file) + - Uuid + - Size of create time part + - Size of dynamic part + - Maria block size +..... Here we can add new variables without changing format + - Checksum of create time part (last of block) + +Start of changeable part: + - Checksum of changeable part + - LSN of last checkpoint + - Number of last log file + - Max trid in control file (since Maria 1.5 May 2008) + - Number of consecutive recovery failures (since Maria 1.5 May 2008) +..... Here we can add new variables without changing format + +The idea is that one can add new variables to the control file and still +use it with old program versions. If one needs to do an incompatible change +one should increment the control file version number. +*/ + +/* Total size should be < sector size for atomic write operation */ +#define CF_MAX_SIZE 512 +#define CF_MIN_SIZE (CF_BLOCKSIZE_OFFSET + CF_BLOCKSIZE_SIZE + \ + CF_CHECKSUM_SIZE * 2 + CF_LSN_SIZE + CF_FILENO_SIZE) + +/* Create time variables */ +#define CF_MAGIC_STRING "\xfe\xfe\xc" +#define CF_MAGIC_STRING_OFFSET 0 +#define CF_MAGIC_STRING_SIZE (sizeof(CF_MAGIC_STRING)-1) +#define CF_VERSION_OFFSET (CF_MAGIC_STRING_OFFSET + CF_MAGIC_STRING_SIZE) +#define CF_VERSION_SIZE 1 +#define CF_UUID_OFFSET (CF_VERSION_OFFSET + CF_VERSION_SIZE) +#define CF_UUID_SIZE MY_UUID_SIZE +#define CF_CREATE_TIME_SIZE_OFFSET (CF_UUID_OFFSET + CF_UUID_SIZE) +#define CF_SIZE_SIZE 2 +#define CF_CHANGEABLE_SIZE_OFFSET (CF_CREATE_TIME_SIZE_OFFSET + CF_SIZE_SIZE) +#define CF_BLOCKSIZE_OFFSET (CF_CHANGEABLE_SIZE_OFFSET + CF_SIZE_SIZE) +#define CF_BLOCKSIZE_SIZE 2 + +#define CF_CREATE_TIME_TOTAL_SIZE (CF_BLOCKSIZE_OFFSET + CF_BLOCKSIZE_SIZE + \ + CF_CHECKSUM_SIZE) + +/* + Start of the part that changes during execution + This is stored at offset uint2korr(file[CF_CHANGEABLE_SIZE]) +*/ +#define CF_CHECKSUM_OFFSET 0 +#define CF_CHECKSUM_SIZE 4 +#define CF_LSN_OFFSET (CF_CHECKSUM_OFFSET + CF_CHECKSUM_SIZE) +#define CF_LSN_SIZE LSN_STORE_SIZE +#define CF_FILENO_OFFSET (CF_LSN_OFFSET + CF_LSN_SIZE) +#define CF_FILENO_SIZE 4 +#define CF_MAX_TRID_OFFSET (CF_FILENO_OFFSET + CF_FILENO_SIZE) +#define CF_MAX_TRID_SIZE TRANSID_SIZE +#define CF_RECOV_FAIL_OFFSET (CF_MAX_TRID_OFFSET + CF_MAX_TRID_SIZE) +#define CF_RECOV_FAIL_SIZE 1 +#define CF_CHANGEABLE_TOTAL_SIZE (CF_RECOV_FAIL_OFFSET + CF_RECOV_FAIL_SIZE) + +/* + The following values should not be changed, except when changing version + number of the maria control file. These are the minimum sizes of the + parts the code can handle. +*/ + +#define CF_MIN_CREATE_TIME_TOTAL_SIZE \ +(CF_BLOCKSIZE_OFFSET + CF_BLOCKSIZE_SIZE + CF_CHECKSUM_SIZE) +#define CF_MIN_CHANGEABLE_TOTAL_SIZE \ +(CF_FILENO_OFFSET + CF_FILENO_SIZE) + +#ifndef EXTRACT_DEFINITIONS + +/* This module owns these two vars. */ +/** + This LSN serves for the two-checkpoint rule, and also to find the + checkpoint record when doing a recovery. +*/ +LSN last_checkpoint_lsn= LSN_IMPOSSIBLE; +uint32 last_logno= FILENO_IMPOSSIBLE; +/** + The maximum transaction id given to a transaction. It is only updated at + clean shutdown (in case of crash, logs have better information). +*/ +TrID max_trid_in_control_file= 0; + +/** + Number of consecutive log or recovery failures. Reset to 0 after recovery's + success. +*/ +uint8 recovery_failures= 0; + +/** + @brief If log's lock should be asserted when writing to control file. + + Can be re-used by any function which needs to be thread-safe except when + it is called at startup. +*/ +my_bool maria_multi_threaded= FALSE; +/** @brief if currently doing a recovery */ +my_bool maria_in_recovery= FALSE; + +/** + Control file is less then 512 bytes (a disk sector), + to be as atomic as possible +*/ +static int control_file_fd= -1; + +static uint cf_create_time_size; +static uint cf_changeable_size; + +/** + @brief Create Maria control file +*/ + +static CONTROL_FILE_ERROR create_control_file(const char *name, + int open_flags) +{ + uint32 sum; + uchar buffer[CF_CREATE_TIME_TOTAL_SIZE]; + DBUG_ENTER("maria_create_control_file"); + + if ((control_file_fd= my_create(name, 0, + open_flags, + MYF(MY_SYNC_DIR | MY_WME))) < 0) + DBUG_RETURN(CONTROL_FILE_UNKNOWN_ERROR); + + /* Reset variables, as we are creating the file */ + cf_create_time_size= CF_CREATE_TIME_TOTAL_SIZE; + cf_changeable_size= CF_CHANGEABLE_TOTAL_SIZE; + + /* Create unique uuid for the control file */ + my_uuid_init((ulong) &buffer, (ulong) &maria_uuid); + my_uuid(maria_uuid); + + /* Prepare and write the file header */ + memcpy(buffer, CF_MAGIC_STRING, CF_MAGIC_STRING_SIZE); + buffer[CF_VERSION_OFFSET]= CONTROL_FILE_VERSION; + memcpy(buffer + CF_UUID_OFFSET, maria_uuid, CF_UUID_SIZE); + int2store(buffer + CF_CREATE_TIME_SIZE_OFFSET, cf_create_time_size); + int2store(buffer + CF_CHANGEABLE_SIZE_OFFSET, cf_changeable_size); + + /* Write create time variables */ + int2store(buffer + CF_BLOCKSIZE_OFFSET, maria_block_size); + + /* Store checksum for create time parts */ + sum= (uint32) my_checksum(0, buffer, cf_create_time_size - + CF_CHECKSUM_SIZE); + int4store(buffer + cf_create_time_size - CF_CHECKSUM_SIZE, sum); + + if (my_pwrite(control_file_fd, buffer, cf_create_time_size, + 0, MYF(MY_FNABP | MY_WME))) + DBUG_RETURN(CONTROL_FILE_UNKNOWN_ERROR); + + /* + To be safer we should make sure that there are no logs or data/index + files around (indeed it could be that the control file alone was deleted + or not restored, and we should not go on with life at this point). + + Things should still be relatively safe as if someone tries to use + an old table with a new control file the different uuid:s between + the files will cause ma_open() to generate an HA_ERR_OLD_FILE + error. When used from mysqld this will cause the table to be open + in repair mode which will remove all dependencies between the + table and the old control file. + + We could have a tool which can rebuild the control file, by reading the + directory of logs, finding the newest log, reading it to find last + checkpoint... Slow but can save your db. For this to be possible, we + must always write to the control file right after writing the checkpoint + log record, and do nothing in between (i.e. the checkpoint must be + usable as soon as it has been written to the log). + */ + + /* init the file with these "undefined" values */ + DBUG_RETURN(ma_control_file_write_and_force(LSN_IMPOSSIBLE, + FILENO_IMPOSSIBLE, 0, 0)); +} + + +/** + Locks control file exclusively. This is kept for the duration of the engine + process, to prevent another Maria instance to write to our logs or control + file. +*/ + +static int lock_control_file(const char *name) +{ + uint retry= 0; + /* + On Windows, my_lock() uses locking() which is mandatory locking and so + prevents maria-recovery.test from copying the control file. And in case of + crash, it may take a while for Windows to unlock file, causing downtime. + */ + /** + @todo BUG We should explore my_sopen(_SH_DENYWRD) to open or create the + file under Windows. + */ +#ifndef __WIN__ + /* + We can't here use the automatic wait in my_lock() as the alarm thread + may not yet exists. + */ + while (my_lock(control_file_fd, F_WRLCK, 0L, F_TO_EOF, + MYF(MY_SEEK_NOT_DONE | MY_FORCE_LOCK | MY_NO_WAIT))) + { + if (retry == 0) + my_printf_error(HA_ERR_INITIALIZATION, + "Can't lock maria control file '%s' for exclusive use, " + "error: %d. Will retry for %d seconds", 0, + name, my_errno, MARIA_MAX_CONTROL_FILE_LOCK_RETRY); + if (retry++ > MARIA_MAX_CONTROL_FILE_LOCK_RETRY) + return 1; + sleep(1); + } +#endif + return 0; +} + + +/* + @brief Initialize control file subsystem + + Looks for the control file. If none and creation is requested, creates file. + If present, reads it to find out last checkpoint's LSN and last log, updates + the last_checkpoint_lsn and last_logno global variables. + Called at engine's start. + + @note + The format of the control file is defined in the comments and defines + at the start of this file. + + @param create_if_missing create file if not found + + @return Operation status + @retval 0 OK + @retval 1 Error (in which case the file is left closed) +*/ + +CONTROL_FILE_ERROR ma_control_file_open(my_bool create_if_missing, + my_bool print_error) +{ + uchar buffer[CF_MAX_SIZE]; + char name[FN_REFLEN], errmsg_buff[256]; + const char *errmsg, *lock_failed_errmsg= "Could not get an exclusive lock;" + " file is probably in use by another process"; + uint new_cf_create_time_size, new_cf_changeable_size, new_block_size; + my_off_t file_size; + int open_flags= O_BINARY | /*O_DIRECT |*/ O_RDWR; + int error= CONTROL_FILE_UNKNOWN_ERROR; + DBUG_ENTER("ma_control_file_open"); + + /* + If you change sizes in the #defines, you at least have to change the + "*store" and "*korr" calls in this file, and can even create backward + compatibility problems. Beware! + */ + DBUG_ASSERT(CF_LSN_SIZE == (3+4)); + DBUG_ASSERT(CF_FILENO_SIZE == 4); + + if (control_file_fd >= 0) /* already open */ + DBUG_RETURN(0); + + if (fn_format(name, CONTROL_FILE_BASE_NAME, + maria_data_root, "", MYF(MY_WME)) == NullS) + DBUG_RETURN(CONTROL_FILE_UNKNOWN_ERROR); + + if (my_access(name,F_OK)) + { + CONTROL_FILE_ERROR create_error; + if (!create_if_missing) + { + error= CONTROL_FILE_MISSING; + errmsg= "Can't find file"; + goto err; + } + if ((create_error= create_control_file(name, open_flags))) + { + error= create_error; + errmsg= "Can't create file"; + goto err; + } + if (lock_control_file(name)) + { + errmsg= lock_failed_errmsg; + goto err; + } + goto ok; + } + + /* Otherwise, file exists */ + + if ((control_file_fd= my_open(name, open_flags, MYF(MY_WME))) < 0) + { + errmsg= "Can't open file"; + goto err; + } + + if (lock_control_file(name)) /* lock it before reading content */ + { + errmsg= lock_failed_errmsg; + goto err; + } + + file_size= my_seek(control_file_fd, 0, SEEK_END, MYF(MY_WME)); + if (file_size == MY_FILEPOS_ERROR) + { + errmsg= "Can't read size"; + goto err; + } + if (file_size < CF_MIN_SIZE) + { + /* + Given that normally we write only a sector and it's atomic, the only + possibility for a file to be of too short size is if we crashed at the + very first startup, between file creation and file write. Quite unlikely + (and can be made even more unlikely by doing this: create a temp file, + write it, and then rename it to be the control file). + What's more likely is if someone forgot to restore the control file, + just did a "touch control" to try to get Maria to start, or if the + disk/filesystem has a problem. + So let's be rigid. + */ + error= CONTROL_FILE_TOO_SMALL; + errmsg= "Size of control file is smaller than expected"; + goto err; + } + + /* Check if control file is unexpectedly big */ + if (file_size > CF_MAX_SIZE) + { + error= CONTROL_FILE_TOO_BIG; + errmsg= "File size bigger than expected"; + goto err; + } + + if (my_pread(control_file_fd, buffer, (size_t)file_size, 0, MYF(MY_FNABP))) + { + errmsg= "Can't read file"; + goto err; + } + + if (memcmp(buffer + CF_MAGIC_STRING_OFFSET, + CF_MAGIC_STRING, CF_MAGIC_STRING_SIZE)) + { + error= CONTROL_FILE_BAD_MAGIC_STRING; + errmsg= "Missing valid id at start of file. File is not a valid maria control file"; + goto err; + } + + if (buffer[CF_VERSION_OFFSET] > CONTROL_FILE_VERSION) + { + error= CONTROL_FILE_BAD_VERSION; + sprintf(errmsg_buff, "File is from a future maria system: %d. Current version is: %d", + (int) buffer[CF_VERSION_OFFSET], CONTROL_FILE_VERSION); + errmsg= errmsg_buff; + goto err; + } + + new_cf_create_time_size= uint2korr(buffer + CF_CREATE_TIME_SIZE_OFFSET); + new_cf_changeable_size= uint2korr(buffer + CF_CHANGEABLE_SIZE_OFFSET); + + if (new_cf_create_time_size < CF_MIN_CREATE_TIME_TOTAL_SIZE || + new_cf_changeable_size < CF_MIN_CHANGEABLE_TOTAL_SIZE || + new_cf_create_time_size + new_cf_changeable_size != file_size) + { + error= CONTROL_FILE_INCONSISTENT_INFORMATION; + errmsg= "Sizes stored in control file are inconsistent"; + goto err; + } + + new_block_size= uint2korr(buffer + CF_BLOCKSIZE_OFFSET); + if (new_block_size != maria_block_size) + { + error= CONTROL_FILE_WRONG_BLOCKSIZE; + sprintf(errmsg_buff, + "Block size in control file (%u) is different than given maria_block_size: %u", + new_block_size, (uint) maria_block_size); + errmsg= errmsg_buff; + goto err; + } + + if (my_checksum(0, buffer, new_cf_create_time_size - CF_CHECKSUM_SIZE) != + uint4korr(buffer + new_cf_create_time_size - CF_CHECKSUM_SIZE)) + { + error= CONTROL_FILE_BAD_HEAD_CHECKSUM; + errmsg= "Fixed part checksum mismatch"; + goto err; + } + + if (my_checksum(0, buffer + new_cf_create_time_size + CF_CHECKSUM_SIZE, + new_cf_changeable_size - CF_CHECKSUM_SIZE) != + uint4korr(buffer + new_cf_create_time_size)) + { + error= CONTROL_FILE_BAD_CHECKSUM; + errmsg= "Changeable part (end of control file) checksum mismatch"; + goto err; + } + + memcpy(maria_uuid, buffer + CF_UUID_OFFSET, CF_UUID_SIZE); + cf_create_time_size= new_cf_create_time_size; + cf_changeable_size= new_cf_changeable_size; + last_checkpoint_lsn= lsn_korr(buffer + new_cf_create_time_size + + CF_LSN_OFFSET); + last_logno= uint4korr(buffer + new_cf_create_time_size + CF_FILENO_OFFSET); + if (new_cf_changeable_size >= (CF_MAX_TRID_OFFSET + CF_MAX_TRID_SIZE)) + max_trid_in_control_file= + transid_korr(buffer + new_cf_create_time_size + CF_MAX_TRID_OFFSET); + if (new_cf_changeable_size >= (CF_RECOV_FAIL_OFFSET + CF_RECOV_FAIL_SIZE)) + recovery_failures= + (buffer + new_cf_create_time_size + CF_RECOV_FAIL_OFFSET)[0]; + +ok: + DBUG_RETURN(0); + +err: + if (print_error) + my_printf_error(HA_ERR_INITIALIZATION, + "Got error '%s' when trying to use maria control file " + "'%s'", 0, errmsg, name); + ma_control_file_end(); /* will unlock file if needed */ + DBUG_RETURN(error); +} + + +/* + Write information durably to the control file; stores this information into + the last_checkpoint_lsn, last_logno, max_trid_in_control_file, + recovery_failures global variables. + Called when we have created a new log (after syncing this log's creation), + when we have written a checkpoint (after syncing this log record), at + shutdown (for storing trid in case logs are soon removed by user), and + before and after recovery (to store recovery_failures). + Variables last_checkpoint_lsn and last_logno must be protected by caller + using log's lock, unless this function is called at startup. + + SYNOPSIS + ma_control_file_write_and_force() + last_checkpoint_lsn_arg LSN of last checkpoint + last_logno_arg last log file number + max_trid_arg maximum transaction longid + recovery_failures_arg consecutive recovery failures + + NOTE + We always want to do one single my_pwrite() here to be as atomic as + possible. + + RETURN + 0 - OK + 1 - Error +*/ + +int ma_control_file_write_and_force(LSN last_checkpoint_lsn_arg, + uint32 last_logno_arg, + TrID max_trid_arg, + uint8 recovery_failures_arg) +{ + uchar buffer[CF_MAX_SIZE]; + uint32 sum; + my_bool no_need_sync; + DBUG_ENTER("ma_control_file_write_and_force"); + + /* + We don't need to sync if this is just an increase of + recovery_failures: it's even good if that counter is not increased on disk + in case of power or hardware failure (less false positives when removing + logs). + */ + no_need_sync= ((last_checkpoint_lsn == last_checkpoint_lsn_arg) && + (last_logno == last_logno_arg) && + (max_trid_in_control_file == max_trid_arg) && + (recovery_failures_arg > 0)); + + if (control_file_fd < 0) + DBUG_RETURN(1); + +#ifndef DBUG_OFF + if (maria_multi_threaded) + translog_lock_handler_assert_owner(); +#endif + + lsn_store(buffer + CF_LSN_OFFSET, last_checkpoint_lsn_arg); + int4store(buffer + CF_FILENO_OFFSET, last_logno_arg); + transid_store(buffer + CF_MAX_TRID_OFFSET, max_trid_arg); + (buffer + CF_RECOV_FAIL_OFFSET)[0]= recovery_failures_arg; + + if (cf_changeable_size > CF_CHANGEABLE_TOTAL_SIZE) + { + /* + More room than needed for us. Must be a newer version. Clear part which + we cannot maintain, so that any future version notices we didn't + maintain its extra data. + */ + uint zeroed= cf_changeable_size - CF_CHANGEABLE_TOTAL_SIZE; + char msg[150]; + bzero(buffer + CF_CHANGEABLE_TOTAL_SIZE, zeroed); + my_snprintf(msg, sizeof(msg), + "Control file must be from a newer version; zero-ing out %u" + " unknown bytes in control file at offset %u", zeroed, + cf_changeable_size + cf_create_time_size); + ma_message_no_user(ME_JUST_WARNING, msg); + } + else + { + /* not enough room for what we need to store: enlarge */ + cf_changeable_size= CF_CHANGEABLE_TOTAL_SIZE; + } + /* Note that the create-time portion is not touched */ + + /* Checksum is stored first */ + compile_time_assert(CF_CHECKSUM_OFFSET == 0); + sum= my_checksum(0, buffer + CF_CHECKSUM_SIZE, + cf_changeable_size - CF_CHECKSUM_SIZE); + int4store(buffer, sum); + + if (my_pwrite(control_file_fd, buffer, cf_changeable_size, + cf_create_time_size, MYF(MY_FNABP | MY_WME)) || + (!no_need_sync && my_sync(control_file_fd, MYF(MY_WME)))) + DBUG_RETURN(1); + + last_checkpoint_lsn= last_checkpoint_lsn_arg; + last_logno= last_logno_arg; + max_trid_in_control_file= max_trid_arg; + recovery_failures= recovery_failures_arg; + + cf_changeable_size= CF_CHANGEABLE_TOTAL_SIZE; /* no more warning */ + DBUG_RETURN(0); +} + + +/* + Free resources taken by control file subsystem + + SYNOPSIS + ma_control_file_end() +*/ + +int ma_control_file_end(void) +{ + int close_error; + DBUG_ENTER("ma_control_file_end"); + + if (control_file_fd < 0) /* already closed */ + DBUG_RETURN(0); + +#ifndef __WIN__ + (void) my_lock(control_file_fd, F_UNLCK, 0L, F_TO_EOF, + MYF(MY_SEEK_NOT_DONE | MY_FORCE_LOCK)); +#endif + + close_error= my_close(control_file_fd, MYF(MY_WME)); + /* + As my_close() frees structures even if close() fails, we do the same, + i.e. we mark the file as closed in all cases. + */ + control_file_fd= -1; + /* + As this module owns these variables, closing the module forbids access to + them (just a safety): + */ + last_checkpoint_lsn= LSN_IMPOSSIBLE; + last_logno= FILENO_IMPOSSIBLE; + max_trid_in_control_file= recovery_failures= 0; + + DBUG_RETURN(close_error); +} + + +/** + Tells if control file is initialized. +*/ + +my_bool ma_control_file_inited(void) +{ + return (control_file_fd >= 0); +} + +#endif /* EXTRACT_DEFINITIONS */ diff --git a/storage/maria/ma_control_file.h b/storage/maria/ma_control_file.h new file mode 100644 index 00000000000..4cb5527620d --- /dev/null +++ b/storage/maria/ma_control_file.h @@ -0,0 +1,74 @@ +/* Copyright (C) 2006 MySQL AB & MySQL Finland AB & TCX DataKonsult 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 */ + +/* + WL#3234 Maria control file + First version written by Guilhem Bichot on 2006-04-27. +*/ + +#ifndef _ma_control_file_h +#define _ma_control_file_h + +#define CONTROL_FILE_BASE_NAME "maria_log_control" +/* + Major version for control file. Should only be changed when doing + big changes that made the new control file incompatible with all + older versions of Maria. +*/ +#define CONTROL_FILE_VERSION 1 + +/* Here is the interface of this module */ + +/* + LSN of the last checkoint + (if last_checkpoint_lsn == LSN_IMPOSSIBLE then there was never a checkpoint) +*/ +extern LSN last_checkpoint_lsn; +/* + Last log number (if last_logno == FILENO_IMPOSSIBLE then there is no log + file yet) +*/ +extern uint32 last_logno; + +extern TrID max_trid_in_control_file; + +extern uint8 recovery_failures; + +extern my_bool maria_multi_threaded, maria_in_recovery; + +typedef enum enum_control_file_error { + CONTROL_FILE_OK= 0, + CONTROL_FILE_TOO_SMALL, + CONTROL_FILE_TOO_BIG, + CONTROL_FILE_BAD_MAGIC_STRING, + CONTROL_FILE_BAD_VERSION, + CONTROL_FILE_BAD_CHECKSUM, + CONTROL_FILE_BAD_HEAD_CHECKSUM, + CONTROL_FILE_MISSING, + CONTROL_FILE_INCONSISTENT_INFORMATION, + CONTROL_FILE_WRONG_BLOCKSIZE, + CONTROL_FILE_UNKNOWN_ERROR /* any other error */ +} CONTROL_FILE_ERROR; + +C_MODE_START +CONTROL_FILE_ERROR ma_control_file_open(my_bool create_if_missing, + my_bool print_error); +int ma_control_file_write_and_force(LSN last_checkpoint_lsn_arg, + uint32 last_logno_arg, TrID max_trid_arg, + uint8 recovery_failures_arg); +int ma_control_file_end(void); +my_bool ma_control_file_inited(void); +C_MODE_END +#endif diff --git a/storage/maria/ma_create.c b/storage/maria/ma_create.c new file mode 100644 index 00000000000..05b11929d0a --- /dev/null +++ b/storage/maria/ma_create.c @@ -0,0 +1,1419 @@ +/* Copyright (C) 2006 MySQL AB & MySQL Finland AB & TCX DataKonsult 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 */ + +/* Create a MARIA table */ + +#include "ma_ftdefs.h" +#include "ma_sp_defs.h" +#include <my_bit.h> +#include "ma_blockrec.h" +#include "trnman_public.h" + +#if defined(MSDOS) || defined(__WIN__) +#ifdef __WIN__ +#include <fcntl.h> +#else +#include <process.h> /* Prototype for getpid */ +#endif +#endif +#include <m_ctype.h> + +static int compare_columns(MARIA_COLUMNDEF **a, MARIA_COLUMNDEF **b); + +/* + Old options is used when recreating database, from maria_chk +*/ + +int maria_create(const char *name, enum data_file_type datafile_type, + uint keys,MARIA_KEYDEF *keydefs, + uint columns, MARIA_COLUMNDEF *columndef, + uint uniques, MARIA_UNIQUEDEF *uniquedefs, + MARIA_CREATE_INFO *ci,uint flags) +{ + register uint i,j; + File dfile,file; + int errpos,save_errno, create_mode= O_RDWR | O_TRUNC, res; + myf create_flag; + uint length,max_key_length,packed,pack_bytes,pointer,real_length_diff, + key_length,info_length,key_segs,options,min_key_length, + base_pos,long_varchar_count,varchar_length, + unique_key_parts,fulltext_keys,offset, not_block_record_extra_length; + uint max_field_lengths, extra_header_size, column_nr; + ulong reclength, real_reclength,min_pack_length; + char filename[FN_REFLEN], linkname[FN_REFLEN], *linkname_ptr; + ulong pack_reclength; + ulonglong tot_length,max_rows, tmp; + enum en_fieldtype type; + enum data_file_type org_datafile_type= datafile_type; + MARIA_SHARE share; + MARIA_KEYDEF *keydef,tmp_keydef; + MARIA_UNIQUEDEF *uniquedef; + HA_KEYSEG *keyseg,tmp_keyseg; + MARIA_COLUMNDEF *column, *end_column; + double *rec_per_key_part; + ulong *nulls_per_key_part; + uint16 *column_array; + my_off_t key_root[HA_MAX_POSSIBLE_KEY], kfile_size_before_extension; + MARIA_CREATE_INFO tmp_create_info; + my_bool tmp_table= FALSE; /* cache for presence of HA_OPTION_TMP_TABLE */ + my_bool forced_packed; + myf sync_dir= 0; + uchar *log_data= NULL; + DBUG_ENTER("maria_create"); + DBUG_PRINT("enter", ("keys: %u columns: %u uniques: %u flags: %u", + keys, columns, uniques, flags)); + + DBUG_ASSERT(maria_inited); + LINT_INIT(dfile); + LINT_INIT(file); + + if (!ci) + { + bzero((char*) &tmp_create_info,sizeof(tmp_create_info)); + ci=&tmp_create_info; + } + + if (keys + uniques > MARIA_MAX_KEY) + { + DBUG_RETURN(my_errno=HA_WRONG_CREATE_OPTION); + } + errpos=0; + options=0; + bzero((uchar*) &share,sizeof(share)); + + if (flags & HA_DONT_TOUCH_DATA) + { + /* We come here from recreate table */ + org_datafile_type= ci->org_data_file_type; + if (!(ci->old_options & HA_OPTION_TEMP_COMPRESS_RECORD)) + options= (ci->old_options & + (HA_OPTION_COMPRESS_RECORD | HA_OPTION_PACK_RECORD | + HA_OPTION_READ_ONLY_DATA | HA_OPTION_CHECKSUM | + HA_OPTION_TMP_TABLE | HA_OPTION_DELAY_KEY_WRITE | + HA_OPTION_LONG_BLOB_PTR | HA_OPTION_PAGE_CHECKSUM)); + else + { + /* Uncompressing rows */ + options= (ci->old_options & + (HA_OPTION_CHECKSUM | HA_OPTION_TMP_TABLE | + HA_OPTION_DELAY_KEY_WRITE | HA_OPTION_LONG_BLOB_PTR | + HA_OPTION_PAGE_CHECKSUM)); + } + } + else + { + /* Transactional tables must be of type BLOCK_RECORD */ + if (ci->transactional) + datafile_type= BLOCK_RECORD; + } + + if (ci->reloc_rows > ci->max_rows) + ci->reloc_rows=ci->max_rows; /* Check if wrong parameter */ + + if (!(rec_per_key_part= + (double*) my_malloc((keys + uniques)*HA_MAX_KEY_SEG*sizeof(double) + + (keys + uniques)*HA_MAX_KEY_SEG*sizeof(ulong) + + sizeof(uint16) * columns, + MYF(MY_WME | MY_ZEROFILL)))) + DBUG_RETURN(my_errno); + nulls_per_key_part= (ulong*) (rec_per_key_part + + (keys + uniques) * HA_MAX_KEY_SEG); + column_array= (uint16*) (nulls_per_key_part + + (keys + uniques) * HA_MAX_KEY_SEG); + + + /* Start by checking fields and field-types used */ + varchar_length=long_varchar_count=packed= not_block_record_extra_length= + pack_reclength= max_field_lengths= 0; + reclength= min_pack_length= ci->null_bytes; + forced_packed= 0; + column_nr= 0; + + for (column= columndef, end_column= column + columns ; + column != end_column ; + column++) + { + /* Fill in not used struct parts */ + column->column_nr= column_nr++; + column->offset= reclength; + column->empty_pos= 0; + column->empty_bit= 0; + column->fill_length= column->length; + if (column->null_bit) + options|= HA_OPTION_NULL_FIELDS; + + reclength+= column->length; + type= column->type; + if (datafile_type == BLOCK_RECORD) + { + if (type == FIELD_SKIP_PRESPACE) + type= column->type= FIELD_NORMAL; /* SKIP_PRESPACE not supported */ + if (type == FIELD_NORMAL && + column->length > FULL_PAGE_SIZE(maria_block_size)) + { + /* FIELD_NORMAL can't be split over many blocks, convert to a CHAR */ + type= column->type= FIELD_SKIP_ENDSPACE; + } + } + + if (type != FIELD_NORMAL && type != FIELD_CHECK) + { + column->empty_pos= packed/8; + column->empty_bit= (1 << (packed & 7)); + if (type == FIELD_BLOB) + { + forced_packed= 1; + packed++; + share.base.blobs++; + if (pack_reclength != INT_MAX32) + { + if (column->length == 4+portable_sizeof_char_ptr) + pack_reclength= INT_MAX32; + else + { + /* Add max possible blob length */ + pack_reclength+= (1 << ((column->length- + portable_sizeof_char_ptr)*8)); + } + } + max_field_lengths+= (column->length - portable_sizeof_char_ptr); + } + else if (type == FIELD_SKIP_PRESPACE || + type == FIELD_SKIP_ENDSPACE) + { + forced_packed= 1; + max_field_lengths+= column->length > 255 ? 2 : 1; + not_block_record_extra_length++; + packed++; + } + else if (type == FIELD_VARCHAR) + { + varchar_length+= column->length-1; /* Used for min_pack_length */ + pack_reclength++; + not_block_record_extra_length++; + max_field_lengths++; + packed++; + column->fill_length= 1; + options|= HA_OPTION_NULL_FIELDS; /* Use ma_checksum() */ + + /* We must test for 257 as length includes pack-length */ + if (test(column->length >= 257)) + { + long_varchar_count++; + max_field_lengths++; + column->fill_length= 2; + } + } + else if (type == FIELD_SKIP_ZERO) + packed++; + else + { + if (!column->null_bit) + min_pack_length+= column->length; + else + { + /* Only BLOCK_RECORD skips NULL fields for all field values */ + not_block_record_extra_length+= column->length; + } + column->empty_pos= 0; + column->empty_bit= 0; + } + } + else /* FIELD_NORMAL */ + { + if (!column->null_bit) + { + min_pack_length+= column->length; + share.base.fixed_not_null_fields++; + share.base.fixed_not_null_fields_length+= column->length; + } + else + not_block_record_extra_length+= column->length; + } + } + + if (datafile_type == STATIC_RECORD && forced_packed) + { + /* Can't use fixed length records, revert to block records */ + datafile_type= BLOCK_RECORD; + } + + if (datafile_type == DYNAMIC_RECORD) + options|= HA_OPTION_PACK_RECORD; /* Must use packed records */ + + if (datafile_type == STATIC_RECORD) + { + /* We can't use checksum with static length rows */ + flags&= ~HA_CREATE_CHECKSUM; + options&= ~HA_OPTION_CHECKSUM; + min_pack_length= reclength; + packed= 0; + } + else if (datafile_type != BLOCK_RECORD) + min_pack_length+= not_block_record_extra_length; + else + min_pack_length+= 5; /* Min row overhead */ + + if (flags & HA_CREATE_TMP_TABLE) + { + options|= HA_OPTION_TMP_TABLE; + tmp_table= TRUE; + create_mode|= O_NOFOLLOW; + /* "CREATE TEMPORARY" tables are not crash-safe (dropped at restart) */ + ci->transactional= FALSE; + flags&= ~HA_CREATE_PAGE_CHECKSUM; + } + share.base.null_bytes= ci->null_bytes; + share.base.original_null_bytes= ci->null_bytes; + share.base.born_transactional= ci->transactional; + share.base.max_field_lengths= max_field_lengths; + share.base.field_offsets= 0; /* for future */ + + if (flags & HA_CREATE_CHECKSUM || (options & HA_OPTION_CHECKSUM)) + { + options|= HA_OPTION_CHECKSUM; + min_pack_length++; + pack_reclength++; + } + if (pack_reclength < INT_MAX32) + pack_reclength+= max_field_lengths + long_varchar_count; + else + pack_reclength= INT_MAX32; + + if (flags & HA_CREATE_DELAY_KEY_WRITE) + options|= HA_OPTION_DELAY_KEY_WRITE; + if (flags & HA_CREATE_RELIES_ON_SQL_LAYER) + options|= HA_OPTION_RELIES_ON_SQL_LAYER; + if (flags & HA_CREATE_PAGE_CHECKSUM) + options|= HA_OPTION_PAGE_CHECKSUM; + + pack_bytes= (packed + 7) / 8; + if (pack_reclength != INT_MAX32) + pack_reclength+= reclength+pack_bytes + + test(test_all_bits(options, HA_OPTION_CHECKSUM | HA_OPTION_PACK_RECORD)); + min_pack_length+= pack_bytes; + /* Calculate min possible row length for rows-in-block */ + extra_header_size= MAX_FIXED_HEADER_SIZE; + if (ci->transactional) + { + extra_header_size= TRANS_MAX_FIXED_HEADER_SIZE; + DBUG_PRINT("info",("creating a transactional table")); + } + share.base.min_block_length= (extra_header_size + share.base.null_bytes + + pack_bytes); + if (!ci->data_file_length && ci->max_rows) + { + if (pack_reclength == INT_MAX32 || + (~(ulonglong) 0)/ci->max_rows < (ulonglong) pack_reclength) + ci->data_file_length= ~(ulonglong) 0; + else + ci->data_file_length=(ulonglong) ci->max_rows*pack_reclength; + } + else if (!ci->max_rows) + { + if (datafile_type == BLOCK_RECORD) + { + uint rows_per_page= ((maria_block_size - PAGE_OVERHEAD_SIZE) / + (min_pack_length + extra_header_size + + DIR_ENTRY_SIZE)); + ulonglong data_file_length= ci->data_file_length; + if (!data_file_length) + data_file_length= ((((ulonglong) 1 << ((BLOCK_RECORD_POINTER_SIZE-1) * + 8)) -1) * maria_block_size); + if (rows_per_page > 0) + { + set_if_smaller(rows_per_page, MAX_ROWS_PER_PAGE); + ci->max_rows= data_file_length / maria_block_size * rows_per_page; + } + else + ci->max_rows= data_file_length / (min_pack_length + + extra_header_size + + DIR_ENTRY_SIZE); + } + else + ci->max_rows=(ha_rows) (ci->data_file_length/(min_pack_length + + ((options & + HA_OPTION_PACK_RECORD) ? + 3 : 0))); + } + max_rows= (ulonglong) ci->max_rows; + if (datafile_type == BLOCK_RECORD) + { + /* + The + 1 is for record position withing page + The / 2 is because we need one bit for knowing if there is transid's + after the row pointer + */ + pointer= maria_get_pointer_length((ci->data_file_length / + (maria_block_size * 2)), 3) + 1; + set_if_smaller(pointer, BLOCK_RECORD_POINTER_SIZE); + + if (!max_rows) + max_rows= (((((ulonglong) 1 << ((pointer-1)*8)) -1) * maria_block_size) / + min_pack_length / 2); + } + else + { + if (datafile_type != STATIC_RECORD) + pointer= maria_get_pointer_length(ci->data_file_length, + maria_data_pointer_size); + else + pointer= maria_get_pointer_length(ci->max_rows, maria_data_pointer_size); + if (!max_rows) + max_rows= ((((ulonglong) 1 << (pointer*8)) -1) / min_pack_length); + } + + real_reclength=reclength; + if (datafile_type == STATIC_RECORD) + { + if (reclength <= pointer) + reclength=pointer+1; /* reserve place for delete link */ + } + else + reclength+= long_varchar_count; /* We need space for varchar! */ + + max_key_length=0; tot_length=0 ; key_segs=0; + fulltext_keys=0; + share.state.rec_per_key_part= rec_per_key_part; + share.state.nulls_per_key_part= nulls_per_key_part; + share.state.key_root=key_root; + share.state.key_del= HA_OFFSET_ERROR; + if (uniques) + max_key_length= MARIA_UNIQUE_HASH_LENGTH + pointer; + + for (i=0, keydef=keydefs ; i < keys ; i++ , keydef++) + { + share.state.key_root[i]= HA_OFFSET_ERROR; + length= real_length_diff= 0; + min_key_length= key_length= pointer; + + if (keydef->key_alg == HA_KEY_ALG_RTREE) + keydef->flag|= HA_RTREE_INDEX; /* For easier tests */ + + if (keydef->flag & HA_SPATIAL) + { +#ifdef HAVE_SPATIAL + /* BAR TODO to support 3D and more dimensions in the future */ + uint sp_segs=SPDIMS*2; + keydef->flag=HA_SPATIAL; + + if (flags & HA_DONT_TOUCH_DATA) + { + /* + Called by maria_chk - i.e. table structure was taken from + MYI file and SPATIAL key *does have* additional sp_segs keysegs. + keydef->seg here points right at the GEOMETRY segment, + so we only need to decrease keydef->keysegs. + (see maria_recreate_table() in _ma_check.c) + */ + keydef->keysegs-=sp_segs-1; + } + + for (j=0, keyseg=keydef->seg ; (int) j < keydef->keysegs ; + j++, keyseg++) + { + if (keyseg->type != HA_KEYTYPE_BINARY && + keyseg->type != HA_KEYTYPE_VARBINARY1 && + keyseg->type != HA_KEYTYPE_VARBINARY2) + { + my_errno=HA_WRONG_CREATE_OPTION; + goto err_no_lock; + } + } + keydef->keysegs+=sp_segs; + key_length+=SPLEN*sp_segs; + length++; /* At least one length uchar */ + min_key_length++; +#else + my_errno= HA_ERR_UNSUPPORTED; + goto err_no_lock; +#endif /*HAVE_SPATIAL*/ + } + else if (keydef->flag & HA_FULLTEXT) + { + keydef->flag=HA_FULLTEXT | HA_PACK_KEY | HA_VAR_LENGTH_KEY; + options|=HA_OPTION_PACK_KEYS; /* Using packed keys */ + + for (j=0, keyseg=keydef->seg ; (int) j < keydef->keysegs ; + j++, keyseg++) + { + if (keyseg->type != HA_KEYTYPE_TEXT && + keyseg->type != HA_KEYTYPE_VARTEXT1 && + keyseg->type != HA_KEYTYPE_VARTEXT2) + { + my_errno=HA_WRONG_CREATE_OPTION; + goto err_no_lock; + } + if (!(keyseg->flag & HA_BLOB_PART) && + (keyseg->type == HA_KEYTYPE_VARTEXT1 || + keyseg->type == HA_KEYTYPE_VARTEXT2)) + { + /* Make a flag that this is a VARCHAR */ + keyseg->flag|= HA_VAR_LENGTH_PART; + /* Store in bit_start number of bytes used to pack the length */ + keyseg->bit_start= ((keyseg->type == HA_KEYTYPE_VARTEXT1)? + 1 : 2); + } + } + + fulltext_keys++; + key_length+= HA_FT_MAXBYTELEN+HA_FT_WLEN; + length++; /* At least one length uchar */ + min_key_length+= 1 + HA_FT_WLEN; + real_length_diff=HA_FT_MAXBYTELEN-FT_MAX_WORD_LEN_FOR_SORT; + } + else + { + /* Test if prefix compression */ + if (keydef->flag & HA_PACK_KEY) + { + /* Can't use space_compression on number keys */ + if ((keydef->seg[0].flag & HA_SPACE_PACK) && + keydef->seg[0].type == (int) HA_KEYTYPE_NUM) + keydef->seg[0].flag&= ~HA_SPACE_PACK; + + /* Only use HA_PACK_KEY when first segment is a variable length key */ + if (!(keydef->seg[0].flag & (HA_SPACE_PACK | HA_BLOB_PART | + HA_VAR_LENGTH_PART))) + { + /* pack relative to previous key */ + keydef->flag&= ~HA_PACK_KEY; + keydef->flag|= HA_BINARY_PACK_KEY | HA_VAR_LENGTH_KEY; + } + else + { + keydef->seg[0].flag|=HA_PACK_KEY; /* for easyer intern test */ + keydef->flag|=HA_VAR_LENGTH_KEY; + options|=HA_OPTION_PACK_KEYS; /* Using packed keys */ + } + } + if (keydef->flag & HA_BINARY_PACK_KEY) + options|=HA_OPTION_PACK_KEYS; /* Using packed keys */ + + if (keydef->flag & HA_AUTO_KEY && ci->with_auto_increment) + share.base.auto_key=i+1; + for (j=0, keyseg=keydef->seg ; j < keydef->keysegs ; j++, keyseg++) + { + /* numbers are stored with high by first to make compression easier */ + switch (keyseg->type) { + case HA_KEYTYPE_SHORT_INT: + case HA_KEYTYPE_LONG_INT: + case HA_KEYTYPE_FLOAT: + case HA_KEYTYPE_DOUBLE: + case HA_KEYTYPE_USHORT_INT: + case HA_KEYTYPE_ULONG_INT: + case HA_KEYTYPE_LONGLONG: + case HA_KEYTYPE_ULONGLONG: + case HA_KEYTYPE_INT24: + case HA_KEYTYPE_UINT24: + case HA_KEYTYPE_INT8: + keyseg->flag|= HA_SWAP_KEY; + break; + case HA_KEYTYPE_VARTEXT1: + case HA_KEYTYPE_VARTEXT2: + case HA_KEYTYPE_VARBINARY1: + case HA_KEYTYPE_VARBINARY2: + if (!(keyseg->flag & HA_BLOB_PART)) + { + /* Make a flag that this is a VARCHAR */ + keyseg->flag|= HA_VAR_LENGTH_PART; + /* Store in bit_start number of bytes used to pack the length */ + keyseg->bit_start= ((keyseg->type == HA_KEYTYPE_VARTEXT1 || + keyseg->type == HA_KEYTYPE_VARBINARY1) ? + 1 : 2); + } + break; + default: + break; + } + if (keyseg->flag & HA_SPACE_PACK) + { + DBUG_ASSERT(!(keyseg->flag & (HA_VAR_LENGTH_PART | HA_BLOB_PART))); + keydef->flag |= HA_SPACE_PACK_USED | HA_VAR_LENGTH_KEY; + options|=HA_OPTION_PACK_KEYS; /* Using packed keys */ + length++; /* At least one length uchar */ + if (!keyseg->null_bit) + min_key_length++; + key_length+= keyseg->length; + if (keyseg->length >= 255) + { + /* prefix may be 3 bytes */ + length+= 2; + } + } + else if (keyseg->flag & (HA_VAR_LENGTH_PART | HA_BLOB_PART)) + { + DBUG_ASSERT(!test_all_bits(keyseg->flag, + (HA_VAR_LENGTH_PART | HA_BLOB_PART))); + keydef->flag|=HA_VAR_LENGTH_KEY; + length++; /* At least one length uchar */ + if (!keyseg->null_bit) + min_key_length++; + options|=HA_OPTION_PACK_KEYS; /* Using packed keys */ + key_length+= keyseg->length; + if (keyseg->length >= 255) + { + /* prefix may be 3 bytes */ + length+= 2; + } + } + else + { + key_length+= keyseg->length; + if (!keyseg->null_bit) + min_key_length+= keyseg->length; + } + if (keyseg->null_bit) + { + key_length++; + /* min key part is 1 byte */ + min_key_length++; + options|=HA_OPTION_PACK_KEYS; + keyseg->flag|=HA_NULL_PART; + keydef->flag|=HA_VAR_LENGTH_KEY | HA_NULL_PART_KEY; + } + } + } /* if HA_FULLTEXT */ + key_segs+=keydef->keysegs; + if (keydef->keysegs > HA_MAX_KEY_SEG) + { + my_errno=HA_WRONG_CREATE_OPTION; + goto err_no_lock; + } + /* + key_segs may be 0 in the case when we only want to be able to + add on row into the table. This can happen with some DISTINCT queries + in MySQL + */ + if ((keydef->flag & (HA_NOSAME | HA_NULL_PART_KEY)) == HA_NOSAME && + key_segs) + share.state.rec_per_key_part[key_segs-1]=1L; + length+=key_length; + /* + A key can't be longer than than half a index block (as we have + to be able to put at least 2 keys on an index block for the key + algorithms to work). + */ + if (length > maria_max_key_length()) + { + my_errno=HA_WRONG_CREATE_OPTION; + goto err_no_lock; + } + keydef->block_length= (uint16) maria_block_size; + keydef->keylength= (uint16) key_length; + keydef->minlength= (uint16) min_key_length; + keydef->maxlength= (uint16) length; + + if (length > max_key_length) + max_key_length= length; + tot_length+= ((max_rows/(ulong) (((uint) maria_block_size - + MAX_KEYPAGE_HEADER_SIZE - + KEYPAGE_CHECKSUM_SIZE)/ + (length*2))) * + maria_block_size); + } + + unique_key_parts=0; + for (i=0, uniquedef=uniquedefs ; i < uniques ; i++ , uniquedef++) + { + uniquedef->key=keys+i; + unique_key_parts+=uniquedef->keysegs; + share.state.key_root[keys+i]= HA_OFFSET_ERROR; + tot_length+= (max_rows/(ulong) (((uint) maria_block_size - + MAX_KEYPAGE_HEADER_SIZE - + KEYPAGE_CHECKSUM_SIZE) / + ((MARIA_UNIQUE_HASH_LENGTH + pointer)*2)))* + (ulong) maria_block_size; + } + keys+=uniques; /* Each unique has 1 key */ + key_segs+=uniques; /* Each unique has 1 key seg */ + + base_pos=(MARIA_STATE_INFO_SIZE + keys * MARIA_STATE_KEY_SIZE + + key_segs * MARIA_STATE_KEYSEG_SIZE); + info_length= base_pos+(uint) (MARIA_BASE_INFO_SIZE+ + keys * MARIA_KEYDEF_SIZE+ + uniques * MARIA_UNIQUEDEF_SIZE + + (key_segs + unique_key_parts)*HA_KEYSEG_SIZE+ + columns*(MARIA_COLUMNDEF_SIZE + 2)); + + DBUG_PRINT("info", ("info_length: %u", info_length)); + /* There are only 16 bits for the total header length. */ + if (info_length > 65535) + { + my_printf_error(HA_WRONG_CREATE_OPTION, + "Maria table '%s' has too many columns and/or " + "indexes and/or unique constraints.", + MYF(0), name + dirname_length(name)); + my_errno= HA_WRONG_CREATE_OPTION; + goto err_no_lock; + } + + bmove(share.state.header.file_version, maria_file_magic, 4); + ci->old_options=options | (ci->old_options & HA_OPTION_TEMP_COMPRESS_RECORD ? + HA_OPTION_COMPRESS_RECORD | + HA_OPTION_TEMP_COMPRESS_RECORD: 0); + mi_int2store(share.state.header.options,ci->old_options); + mi_int2store(share.state.header.header_length,info_length); + mi_int2store(share.state.header.state_info_length,MARIA_STATE_INFO_SIZE); + mi_int2store(share.state.header.base_info_length,MARIA_BASE_INFO_SIZE); + mi_int2store(share.state.header.base_pos,base_pos); + share.state.header.data_file_type= share.data_file_type= datafile_type; + share.state.header.org_data_file_type= org_datafile_type; + share.state.header.language= (ci->language ? + ci->language : default_charset_info->number); + + share.state.dellink = HA_OFFSET_ERROR; + share.state.first_bitmap_with_space= 0; +#ifdef EXTERNAL_LOCKING + share.state.process= (ulong) getpid(); +#endif + share.state.version= (ulong) time((time_t*) 0); + share.state.sortkey= (ushort) ~0; + share.state.auto_increment=ci->auto_increment; + share.options=options; + share.base.rec_reflength=pointer; + share.base.block_size= maria_block_size; + + /* + Get estimate for index file length (this may be wrong for FT keys) + This is used for pointers to other key pages. + */ + tmp= (tot_length + maria_block_size * keys * + MARIA_INDEX_BLOCK_MARGIN) / maria_block_size; + + /* + use maximum of key_file_length we calculated and key_file_length value we + got from MAI file header (see also mariapack.c:save_state) + */ + share.base.key_reflength= + maria_get_pointer_length(max(ci->key_file_length,tmp),3); + share.base.keys= share.state.header.keys= keys; + share.state.header.uniques= uniques; + share.state.header.fulltext_keys= fulltext_keys; + mi_int2store(share.state.header.key_parts,key_segs); + mi_int2store(share.state.header.unique_key_parts,unique_key_parts); + + maria_set_all_keys_active(share.state.key_map, keys); + + share.base.keystart = share.state.state.key_file_length= + MY_ALIGN(info_length, maria_block_size); + share.base.max_key_block_length= maria_block_size; + share.base.max_key_length=ALIGN_SIZE(max_key_length+4); + share.base.records=ci->max_rows; + share.base.reloc= ci->reloc_rows; + share.base.reclength=real_reclength; + share.base.pack_reclength=reclength+ test(options & HA_OPTION_CHECKSUM); + share.base.max_pack_length=pack_reclength; + share.base.min_pack_length=min_pack_length; + share.base.pack_bytes= pack_bytes; + share.base.fields= columns; + share.base.pack_fields= packed; + + if (share.data_file_type == BLOCK_RECORD) + { + /* + we are going to create a first bitmap page, set data_file_length + to reflect this, before the state goes to disk + */ + share.state.state.data_file_length= maria_block_size; + /* Add length of packed fields + length */ + share.base.pack_reclength+= share.base.max_field_lengths+3; + + /* Adjust max_pack_length, to be used if we have short rows */ + if (share.base.max_pack_length < maria_block_size) + { + share.base.max_pack_length+= FLAG_SIZE; + if (ci->transactional) + share.base.max_pack_length+= TRANSID_SIZE * 2; + } + } + + /* max_data_file_length and max_key_file_length are recalculated on open */ + if (tmp_table) + share.base.max_data_file_length= (my_off_t) ci->data_file_length; + else if (ci->transactional && translog_status == TRANSLOG_OK && + !maria_in_recovery) + { + /* + we have checked translog_inited above, because maria_chk may call us + (via maria_recreate_table()) and it does not have a log. + */ + sync_dir= MY_SYNC_DIR; + /* + If crash between _ma_state_info_write_sub() and + _ma_update_state__lsns_sub(), table should be ignored by Recovery (or + old REDOs would fail), so we cannot let LSNs be 0: + */ + share.state.skip_redo_lsn= share.state.is_of_horizon= + share.state.create_rename_lsn= LSN_MAX; + } + + if (datafile_type == DYNAMIC_RECORD) + { + share.base.min_block_length= + (share.base.pack_reclength+3 < MARIA_EXTEND_BLOCK_LENGTH && + ! share.base.blobs) ? + max(share.base.pack_reclength,MARIA_MIN_BLOCK_LENGTH) : + MARIA_EXTEND_BLOCK_LENGTH; + } + else if (datafile_type == STATIC_RECORD) + share.base.min_block_length= share.base.pack_reclength; + + if (! (flags & HA_DONT_TOUCH_DATA)) + share.state.create_time= (long) time((time_t*) 0); + + pthread_mutex_lock(&THR_LOCK_maria); + + /* + NOTE: For test_if_reopen() we need a real path name. Hence we need + MY_RETURN_REAL_PATH for every fn_format(filename, ...). + */ + if (ci->index_file_name) + { + char *iext= strrchr(ci->index_file_name, '.'); + int have_iext= iext && !strcmp(iext, MARIA_NAME_IEXT); + if (tmp_table) + { + char *path; + /* chop off the table name, tempory tables use generated name */ + if ((path= strrchr(ci->index_file_name, FN_LIBCHAR))) + *path= '\0'; + fn_format(filename, name, ci->index_file_name, MARIA_NAME_IEXT, + MY_REPLACE_DIR | MY_UNPACK_FILENAME | + MY_RETURN_REAL_PATH | MY_APPEND_EXT); + } + else + { + fn_format(filename, ci->index_file_name, "", MARIA_NAME_IEXT, + MY_UNPACK_FILENAME | MY_RETURN_REAL_PATH | + (have_iext ? MY_REPLACE_EXT : MY_APPEND_EXT)); + } + fn_format(linkname, name, "", MARIA_NAME_IEXT, + MY_UNPACK_FILENAME|MY_APPEND_EXT); + linkname_ptr= linkname; + /* + Don't create the table if the link or file exists to ensure that one + doesn't accidently destroy another table. + Don't sync dir now if the data file has the same path. + */ + create_flag= + (ci->data_file_name && + !strcmp(ci->index_file_name, ci->data_file_name)) ? 0 : sync_dir; + } + else + { + char *iext= strrchr(name, '.'); + int have_iext= iext && !strcmp(iext, MARIA_NAME_IEXT); + fn_format(filename, name, "", MARIA_NAME_IEXT, + MY_UNPACK_FILENAME | MY_RETURN_REAL_PATH | + (have_iext ? MY_REPLACE_EXT : MY_APPEND_EXT)); + linkname_ptr= NullS; + /* + Replace the current file. + Don't sync dir now if the data file has the same path. + */ + create_flag= (flags & HA_CREATE_KEEP_FILES) ? 0 : MY_DELETE_OLD; + create_flag|= (!ci->data_file_name ? 0 : sync_dir); + } + + /* + If a MRG_MARIA table is in use, the mapped MARIA tables are open, + but no entry is made in the table cache for them. + A TRUNCATE command checks for the table in the cache only and could + be fooled to believe, the table is not open. + Pull the emergency brake in this situation. (Bug #8306) + + + NOTE: The filename is compared against unique_file_name of every + open table. Hence we need a real path here. + */ + if (_ma_test_if_reopen(filename)) + { + my_printf_error(0, "MARIA table '%s' is in use " + "(most likely by a MERGE table). Try FLUSH TABLES.", + MYF(0), name + dirname_length(name)); + my_errno= HA_ERR_TABLE_EXIST; + goto err; + } + + if ((file= my_create_with_symlink(linkname_ptr, filename, 0, create_mode, + MYF(MY_WME|create_flag))) < 0) + goto err; + errpos=1; + + DBUG_PRINT("info", ("write state info and base info")); + if (_ma_state_info_write_sub(file, &share.state, + MA_STATE_INFO_WRITE_FULL_INFO) || + _ma_base_info_write(file, &share.base)) + goto err; + DBUG_PRINT("info", ("base_pos: %d base_info_size: %d", + base_pos, MARIA_BASE_INFO_SIZE)); + DBUG_ASSERT(my_tell(file,MYF(0)) == base_pos+ MARIA_BASE_INFO_SIZE); + + /* Write key and keyseg definitions */ + DBUG_PRINT("info", ("write key and keyseg definitions")); + for (i=0 ; i < share.base.keys - uniques; i++) + { + uint sp_segs=(keydefs[i].flag & HA_SPATIAL) ? 2*SPDIMS : 0; + + if (_ma_keydef_write(file, &keydefs[i])) + goto err; + for (j=0 ; j < keydefs[i].keysegs-sp_segs ; j++) + if (_ma_keyseg_write(file, &keydefs[i].seg[j])) + goto err; +#ifdef HAVE_SPATIAL + for (j=0 ; j < sp_segs ; j++) + { + HA_KEYSEG sseg; + sseg.type=SPTYPE; + sseg.language= 7; /* Binary */ + sseg.null_bit=0; + sseg.bit_start=0; + sseg.bit_end=0; + sseg.bit_length= 0; + sseg.bit_pos= 0; + sseg.length=SPLEN; + sseg.null_pos=0; + sseg.start=j*SPLEN; + sseg.flag= HA_SWAP_KEY; + if (_ma_keyseg_write(file, &sseg)) + goto err; + } +#endif + } + /* Create extra keys for unique definitions */ + offset= real_reclength - uniques*MARIA_UNIQUE_HASH_LENGTH; + bzero((char*) &tmp_keydef,sizeof(tmp_keydef)); + bzero((char*) &tmp_keyseg,sizeof(tmp_keyseg)); + for (i=0; i < uniques ; i++) + { + tmp_keydef.keysegs=1; + tmp_keydef.flag= HA_UNIQUE_CHECK; + tmp_keydef.block_length= (uint16) maria_block_size; + tmp_keydef.keylength= MARIA_UNIQUE_HASH_LENGTH + pointer; + tmp_keydef.minlength=tmp_keydef.maxlength=tmp_keydef.keylength; + tmp_keyseg.type= MARIA_UNIQUE_HASH_TYPE; + tmp_keyseg.length= MARIA_UNIQUE_HASH_LENGTH; + tmp_keyseg.start= offset; + offset+= MARIA_UNIQUE_HASH_LENGTH; + if (_ma_keydef_write(file,&tmp_keydef) || + _ma_keyseg_write(file,(&tmp_keyseg))) + goto err; + } + + /* Save unique definition */ + DBUG_PRINT("info", ("write unique definitions")); + for (i=0 ; i < share.state.header.uniques ; i++) + { + HA_KEYSEG *keyseg_end; + keyseg= uniquedefs[i].seg; + if (_ma_uniquedef_write(file, &uniquedefs[i])) + goto err; + for (keyseg= uniquedefs[i].seg, keyseg_end= keyseg+ uniquedefs[i].keysegs; + keyseg < keyseg_end; + keyseg++) + { + switch (keyseg->type) { + case HA_KEYTYPE_VARTEXT1: + case HA_KEYTYPE_VARTEXT2: + case HA_KEYTYPE_VARBINARY1: + case HA_KEYTYPE_VARBINARY2: + if (!(keyseg->flag & HA_BLOB_PART)) + { + keyseg->flag|= HA_VAR_LENGTH_PART; + keyseg->bit_start= ((keyseg->type == HA_KEYTYPE_VARTEXT1 || + keyseg->type == HA_KEYTYPE_VARBINARY1) ? + 1 : 2); + } + break; + default: + DBUG_ASSERT((keyseg->flag & HA_VAR_LENGTH_PART) == 0); + break; + } + if (_ma_keyseg_write(file, keyseg)) + goto err; + } + } + DBUG_PRINT("info", ("write field definitions")); + if (datafile_type == BLOCK_RECORD) + { + /* Store columns in a more efficent order */ + MARIA_COLUMNDEF **col_order, **pos; + if (!(col_order= (MARIA_COLUMNDEF**) my_malloc(share.base.fields * + sizeof(MARIA_COLUMNDEF*), + MYF(MY_WME)))) + goto err; + for (column= columndef, pos= col_order ; + column != end_column ; + column++, pos++) + *pos= column; + qsort(col_order, share.base.fields, sizeof(*col_order), + (qsort_cmp) compare_columns); + for (i=0 ; i < share.base.fields ; i++) + { + column_array[col_order[i]->column_nr]= i; + if (_ma_columndef_write(file, col_order[i])) + { + my_free(col_order, MYF(0)); + goto err; + } + } + my_free(col_order, MYF(0)); + } + else + { + for (i=0 ; i < share.base.fields ; i++) + { + column_array[i]= (uint16) i; + if (_ma_columndef_write(file, &columndef[i])) + goto err; + } + } + if (_ma_column_nr_write(file, column_array, columns)) + goto err; + + if ((kfile_size_before_extension= my_tell(file,MYF(0))) == MY_FILEPOS_ERROR) + goto err; +#ifndef DBUG_OFF + if (kfile_size_before_extension != info_length) + DBUG_PRINT("warning",("info_length: %u != used_length: %u", + info_length, (uint)kfile_size_before_extension)); +#endif + + if (sync_dir) + { + /* + we log the first bytes and then the size to which we extend; this is + not log 1 KB of mostly zeroes if this is a small table. + */ + char empty_string[]= ""; + LEX_CUSTRING log_array[TRANSLOG_INTERNAL_PARTS + 4]; + translog_size_t total_rec_length= 0; + uint k; + LSN lsn; + log_array[TRANSLOG_INTERNAL_PARTS + 1].length= 1 + 2 + 2 + + (uint) kfile_size_before_extension; + /* we are needing maybe 64 kB, so don't use the stack */ + log_data= my_malloc(log_array[TRANSLOG_INTERNAL_PARTS + 1].length, MYF(0)); + if ((log_data == NULL) || + my_pread(file, 1 + 2 + 2 + log_data, + (size_t) kfile_size_before_extension, 0, MYF(MY_NABP))) + goto err; + /* + remember if the data file was created or not, to know if Recovery can + do it or not, in the future + */ + log_data[0]= test(flags & HA_DONT_TOUCH_DATA); + int2store(log_data + 1, kfile_size_before_extension); + int2store(log_data + 1 + 2, share.base.keystart); + log_array[TRANSLOG_INTERNAL_PARTS + 0].str= (uchar *)name; + /* we store the end-zero, for Recovery to just pass it to my_create() */ + log_array[TRANSLOG_INTERNAL_PARTS + 0].length= strlen(name) + 1; + log_array[TRANSLOG_INTERNAL_PARTS + 1].str= log_data; + /* symlink description is also needed for re-creation by Recovery: */ + { + const char *s= ci->data_file_name ? ci->data_file_name : empty_string; + log_array[TRANSLOG_INTERNAL_PARTS + 2].str= (uchar*)s; + log_array[TRANSLOG_INTERNAL_PARTS + 2].length= strlen(s) + 1; + s= ci->index_file_name ? ci->index_file_name : empty_string; + log_array[TRANSLOG_INTERNAL_PARTS + 3].str= (uchar*)s; + log_array[TRANSLOG_INTERNAL_PARTS + 3].length= strlen(s) + 1; + } + for (k= TRANSLOG_INTERNAL_PARTS; + k < (sizeof(log_array)/sizeof(log_array[0])); k++) + total_rec_length+= (translog_size_t) log_array[k].length; + /** + For this record to be of any use for Recovery, we need the upper + MySQL layer to be crash-safe, which it is not now (that would require + work using the ddl_log of sql/sql_table.cc); when it is, we should + reconsider the moment of writing this log record (before or after op, + under THR_LOCK_maria or not...), how to use it in Recovery. + For now this record can serve when we apply logs to a backup, + so we sync it. This happens before the data file is created. If the + data file was created before, and we crashed before writing the log + record, at restart the table may be used, so we would not have a + trustable history in the log (impossible to apply this log to a + backup). The way we do it, if we crash before writing the log record + then there is no data file and the table cannot be used. + @todo Note that in case of TRUNCATE TABLE we also come here; for + Recovery to be able to finish TRUNCATE TABLE, instead of leaving a + half-truncated table, we should log the record at start of + maria_create(); for that we shouldn't write to the index file but to a + buffer (DYNAMIC_STRING), put the buffer into the record, then put the + buffer into the index file (so, change _ma_keydef_write() etc). That + would also enable Recovery to finish a CREATE TABLE. The final result + would be that we would be able to finish what the SQL layer has asked + for: it would be atomic. + When in CREATE/TRUNCATE (or DROP or RENAME or REPAIR) we have not + called external_lock(), so have no TRN. It does not matter, as all + these operations are non-transactional and sync their files. + */ + if (unlikely(translog_write_record(&lsn, + LOGREC_REDO_CREATE_TABLE, + &dummy_transaction_object, NULL, + total_rec_length, + sizeof(log_array)/sizeof(log_array[0]), + log_array, NULL, NULL) || + translog_flush(lsn))) + goto err; + share.kfile.file= file; + DBUG_EXECUTE_IF("maria_flush_whole_log", + { + DBUG_PRINT("maria_flush_whole_log", ("now")); + translog_flush(translog_get_horizon()); + }); + DBUG_EXECUTE_IF("maria_crash_create_table", + { + DBUG_PRINT("maria_crash_create_table", ("now")); + DBUG_ABORT(); + }); + /* + store LSN into file, needed for Recovery to not be confused if a + DROP+CREATE happened (applying REDOs to the wrong table). + */ + if (_ma_update_state_lsns_sub(&share, lsn, trnman_get_min_safe_trid(), + FALSE, TRUE)) + goto err; + my_free(log_data, MYF(0)); + } + + if (!(flags & HA_DONT_TOUCH_DATA)) + { + if (ci->data_file_name) + { + char *dext= strrchr(ci->data_file_name, '.'); + int have_dext= dext && !strcmp(dext, MARIA_NAME_DEXT); + + if (tmp_table) + { + char *path; + /* chop off the table name, tempory tables use generated name */ + if ((path= strrchr(ci->data_file_name, FN_LIBCHAR))) + *path= '\0'; + fn_format(filename, name, ci->data_file_name, MARIA_NAME_DEXT, + MY_REPLACE_DIR | MY_UNPACK_FILENAME | MY_APPEND_EXT); + } + else + { + fn_format(filename, ci->data_file_name, "", MARIA_NAME_DEXT, + MY_UNPACK_FILENAME | + (have_dext ? MY_REPLACE_EXT : MY_APPEND_EXT)); + } + fn_format(linkname, name, "",MARIA_NAME_DEXT, + MY_UNPACK_FILENAME | MY_APPEND_EXT); + linkname_ptr= linkname; + create_flag=0; + } + else + { + fn_format(filename,name,"", MARIA_NAME_DEXT, + MY_UNPACK_FILENAME | MY_APPEND_EXT); + linkname_ptr= NullS; + create_flag= (flags & HA_CREATE_KEEP_FILES) ? 0 : MY_DELETE_OLD; + } + if ((dfile= + my_create_with_symlink(linkname_ptr, filename, 0, create_mode, + MYF(MY_WME | create_flag | sync_dir))) < 0) + goto err; + errpos=3; + + if (_ma_initialize_data_file(&share, dfile)) + goto err; + } + + /* Enlarge files */ + DBUG_PRINT("info", ("enlarge to keystart: %lu", + (ulong) share.base.keystart)); + if (my_chsize(file,(ulong) share.base.keystart,0,MYF(0))) + goto err; + + if (sync_dir && my_sync(file, MYF(0))) + goto err; + + if (! (flags & HA_DONT_TOUCH_DATA)) + { +#ifdef USE_RELOC + if (my_chsize(dfile,share.base.min_pack_length*ci->reloc_rows,0,MYF(0))) + goto err; +#endif + if (sync_dir && my_sync(dfile, MYF(0))) + goto err; + if (my_close(dfile,MYF(0))) + goto err; + } + pthread_mutex_unlock(&THR_LOCK_maria); + res= 0; + my_free((char*) rec_per_key_part,MYF(0)); + errpos=0; + if (my_close(file,MYF(0))) + res= my_errno; + DBUG_RETURN(res); + +err: + pthread_mutex_unlock(&THR_LOCK_maria); + +err_no_lock: + save_errno=my_errno; + switch (errpos) { + case 3: + VOID(my_close(dfile,MYF(0))); + /* fall through */ + case 2: + if (! (flags & HA_DONT_TOUCH_DATA)) + my_delete_with_symlink(fn_format(filename,name,"",MARIA_NAME_DEXT, + MY_UNPACK_FILENAME | MY_APPEND_EXT), + sync_dir); + /* fall through */ + case 1: + VOID(my_close(file,MYF(0))); + if (! (flags & HA_DONT_TOUCH_DATA)) + my_delete_with_symlink(fn_format(filename,name,"",MARIA_NAME_IEXT, + MY_UNPACK_FILENAME | MY_APPEND_EXT), + sync_dir); + } + my_free(log_data, MYF(MY_ALLOW_ZERO_PTR)); + my_free((char*) rec_per_key_part, MYF(0)); + DBUG_RETURN(my_errno=save_errno); /* return the fatal errno */ +} + + +uint maria_get_pointer_length(ulonglong file_length, uint def) +{ + DBUG_ASSERT(def >= 2 && def <= 7); + if (file_length) /* If not default */ + { +#ifdef NOT_YET_READY_FOR_8_BYTE_POINTERS + if (file_length >= (ULL(1) << 56)) + def=8; + else +#endif + if (file_length >= (ULL(1) << 48)) + def=7; + else if (file_length >= (ULL(1) << 40)) + def=6; + else if (file_length >= (ULL(1) << 32)) + def=5; + else if (file_length >= (ULL(1) << 24)) + def=4; + else if (file_length >= (ULL(1) << 16)) + def=3; + else + def=2; + } + return def; +} + + +/* + Sort columns for records-in-block + + IMPLEMENTATION + Sort columns in following order: + + Fixed size, not null columns + Fixed length, null fields + Numbers (zero fill fields) + Variable length fields (CHAR, VARCHAR) according to length + Blobs + + For same kind of fields, keep fields in original order +*/ + +static inline int sign(long a) +{ + return a < 0 ? -1 : (a > 0 ? 1 : 0); +} + + +static int compare_columns(MARIA_COLUMNDEF **a_ptr, MARIA_COLUMNDEF **b_ptr) +{ + MARIA_COLUMNDEF *a= *a_ptr, *b= *b_ptr; + enum en_fieldtype a_type, b_type; + + a_type= (a->type == FIELD_CHECK) ? FIELD_NORMAL : a->type; + b_type= (b->type == FIELD_CHECK) ? FIELD_NORMAL : b->type; + + if (a_type == FIELD_NORMAL && !a->null_bit) + { + if (b_type != FIELD_NORMAL || b->null_bit) + return -1; + return sign((long) a->offset - (long) b->offset); + } + if (b_type == FIELD_NORMAL && !b->null_bit) + return 1; + if (a_type == b_type) + return sign((long) a->offset - (long) b->offset); + if (a_type == FIELD_NORMAL) + return -1; + if (b_type == FIELD_NORMAL) + return 1; + if (a_type == FIELD_SKIP_ZERO) + return -1; + if (b_type == FIELD_SKIP_ZERO) + return 1; + if (a->type != FIELD_BLOB && b->type != FIELD_BLOB) + if (a->length != b->length) + return sign((long) a->length - (long) b->length); + if (a_type == FIELD_BLOB) + return 1; + if (b_type == FIELD_BLOB) + return -1; + return sign((long) a->offset - (long) b->offset); +} + + +/** + @brief Initialize data file + + @note + In BLOCK_RECORD, a freshly created datafile is one page long; while in + other formats it is 0-byte long. + */ + +int _ma_initialize_data_file(MARIA_SHARE *share, File dfile) +{ + if (share->data_file_type == BLOCK_RECORD) + { + share->bitmap.block_size= share->base.block_size; + share->bitmap.file.file = dfile; + return _ma_bitmap_create_first(share); + } + return 0; +} + + +/** + @brief Writes create_rename_lsn, skip_redo_lsn and is_of_horizon to disk, + can force. + + This is for special cases where: + - we don't want to write the full state to disk (so, not call + _ma_state_info_write()) because some parts of the state may be + currently inconsistent, or because it would be overkill + - we must sync these LSNs immediately for correctness. + It acquires intern_lock to protect the LSNs and state write. + + @param share table's share + @param lsn LSN to write to log files + @param create_trid Trid to be used as state.create_trid + @param do_sync if the write should be forced to disk + @param update_create_rename_lsn if this LSN should be updated or not + + @return Operation status + @retval 0 ok + @retval 1 error (disk problem) +*/ + +int _ma_update_state_lsns(MARIA_SHARE *share, LSN lsn, TrID create_trid, + my_bool do_sync, my_bool update_create_rename_lsn) +{ + int res; + pthread_mutex_lock(&share->intern_lock); + res= _ma_update_state_lsns_sub(share, lsn, create_trid, do_sync, + update_create_rename_lsn); + pthread_mutex_unlock(&share->intern_lock); + return res; +} + + +/** + @brief Writes create_rename_lsn, skip_redo_lsn and is_of_horizon to disk, + can force. + + Shortcut of _ma_update_state_lsns() when we know that intern_lock is not + needed (when creating a table or opening it for the first time). + + @param share table's share + @param lsn LSN to write to state; if LSN_IMPOSSIBLE, write + a LOGREC_IMPORTED_TABLE and use its LSN as lsn. + @param create_trid Trid to be used as state.create_trid + @param do_sync if the write should be forced to disk + @param update_create_rename_lsn if this LSN should be updated or not + + @return Operation status + @retval 0 ok + @retval 1 error (disk problem) +*/ + +#if (_MSC_VER == 1310) +/* + Visual Studio 2003 compiler produces internal compiler error + in this function. Disable optimizations to workaround. +*/ +#pragma optimize("",off) +#endif +int _ma_update_state_lsns_sub(MARIA_SHARE *share, LSN lsn, TrID create_trid, + my_bool do_sync, + my_bool update_create_rename_lsn) +{ + uchar buf[LSN_STORE_SIZE * 3], *ptr; + uchar trid_buff[8]; + File file= share->kfile.file; + DBUG_ASSERT(file >= 0); + + if (lsn == LSN_IMPOSSIBLE) + { + int res; + LEX_CUSTRING log_array[TRANSLOG_INTERNAL_PARTS + 1]; + /* table name is logged only for information */ + log_array[TRANSLOG_INTERNAL_PARTS + 0].str= + (uchar *)(share->open_file_name.str); + log_array[TRANSLOG_INTERNAL_PARTS + 0].length= + share->open_file_name.length + 1; + if ((res= translog_write_record(&lsn, LOGREC_IMPORTED_TABLE, + &dummy_transaction_object, NULL, + (translog_size_t) + log_array[TRANSLOG_INTERNAL_PARTS + + 0].length, + sizeof(log_array)/sizeof(log_array[0]), + log_array, NULL, NULL))) + return res; + } + + for (ptr= buf; ptr < (buf + sizeof(buf)); ptr+= LSN_STORE_SIZE) + lsn_store(ptr, lsn); + share->state.skip_redo_lsn= share->state.is_of_horizon= lsn; + share->state.create_trid= create_trid; + mi_int8store(trid_buff, create_trid); + if (update_create_rename_lsn) + { + share->state.create_rename_lsn= lsn; + if (share->id != 0) + { + /* + If OP is the operation which is calling us, if table is later written, + we could see in the log: + FILE_ID ... REDO_OP ... REDO_INSERT. + (that can happen in real life at least with OP=REPAIR). + As FILE_ID will be ignored by Recovery because it is < + create_rename_lsn, REDO_INSERT would be ignored too, wrongly. + To avoid that, we force a LOGREC_FILE_ID to be logged at next write: + */ + translog_deassign_id_from_share(share); + } + } + else + lsn_store(buf, share->state.create_rename_lsn); + return (my_pwrite(file, buf, sizeof(buf), + sizeof(share->state.header) + + MARIA_FILE_CREATE_RENAME_LSN_OFFSET, MYF(MY_NABP)) || + my_pwrite(file, trid_buff, sizeof(trid_buff), + sizeof(share->state.header) + + MARIA_FILE_CREATE_TRID_OFFSET, MYF(MY_NABP)) || + (do_sync && my_sync(file, MYF(0)))); +} +#if (_MSC_VER == 1310) +#pragma optimize("",on) +#endif /*VS2003 compiler bug workaround*/ diff --git a/storage/maria/ma_dbug.c b/storage/maria/ma_dbug.c new file mode 100644 index 00000000000..ea69975ad4b --- /dev/null +++ b/storage/maria/ma_dbug.c @@ -0,0 +1,202 @@ +/* Copyright (C) 2006 MySQL AB & MySQL Finland AB & TCX DataKonsult 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 */ + +/* Support rutiner with are using with dbug */ + +#include "maria_def.h" + +void _ma_print_key(FILE *stream, MARIA_KEY *key) +{ + _ma_print_keydata(stream, key->keyinfo->seg, key->data, key->data_length); +} + + +/* Print a key in a user understandable format */ + +void _ma_print_keydata(FILE *stream, register HA_KEYSEG *keyseg, + const uchar *key, uint length) +{ + int flag; + short int s_1; + long int l_1; + float f_1; + double d_1; + const uchar *end; + const uchar *key_end= key + length; + + VOID(fputs("Key: \"",stream)); + flag=0; + for (; keyseg->type && key < key_end ;keyseg++) + { + if (flag++) + VOID(putc('-',stream)); + end= key+ keyseg->length; + if (keyseg->flag & HA_NULL_PART) + { + /* A NULL value is encoded by a 1-byte flag. Zero means NULL. */ + if (! *(key++)) + { + fprintf(stream,"NULL"); + continue; + } + end++; + } + + switch (keyseg->type) { + case HA_KEYTYPE_BINARY: + if (!(keyseg->flag & HA_SPACE_PACK) && keyseg->length == 1) + { /* packed binary digit */ + VOID(fprintf(stream,"%d",(uint) *key++)); + break; + } + /* fall through */ + case HA_KEYTYPE_TEXT: + case HA_KEYTYPE_NUM: + if (keyseg->flag & HA_SPACE_PACK) + { + VOID(fprintf(stream,"%.*s",(int) *key,key+1)); + key+= (int) *key+1; + } + else + { + VOID(fprintf(stream,"%.*s",(int) keyseg->length,key)); + key=end; + } + break; + case HA_KEYTYPE_INT8: + VOID(fprintf(stream,"%d",(int) *((const signed char*) key))); + key=end; + break; + case HA_KEYTYPE_SHORT_INT: + s_1= mi_sint2korr(key); + VOID(fprintf(stream,"%d",(int) s_1)); + key=end; + break; + case HA_KEYTYPE_USHORT_INT: + { + ushort u_1; + u_1= mi_uint2korr(key); + VOID(fprintf(stream,"%u",(uint) u_1)); + key=end; + break; + } + case HA_KEYTYPE_LONG_INT: + l_1=mi_sint4korr(key); + VOID(fprintf(stream,"%ld",l_1)); + key=end; + break; + case HA_KEYTYPE_ULONG_INT: + l_1=mi_uint4korr(key); + VOID(fprintf(stream,"%lu",(ulong) l_1)); + key=end; + break; + case HA_KEYTYPE_INT24: + VOID(fprintf(stream,"%ld",(long) mi_sint3korr(key))); + key=end; + break; + case HA_KEYTYPE_UINT24: + VOID(fprintf(stream,"%lu",(ulong) mi_uint3korr(key))); + key=end; + break; + case HA_KEYTYPE_FLOAT: + mi_float4get(f_1,key); + VOID(fprintf(stream,"%g",(double) f_1)); + key=end; + break; + case HA_KEYTYPE_DOUBLE: + mi_float8get(d_1,key); + VOID(fprintf(stream,"%g",d_1)); + key=end; + break; +#ifdef HAVE_LONG_LONG + case HA_KEYTYPE_LONGLONG: + { + char buff[21]; + longlong2str(mi_sint8korr(key),buff,-10); + VOID(fprintf(stream,"%s",buff)); + key=end; + break; + } + case HA_KEYTYPE_ULONGLONG: + { + char buff[21]; + longlong2str(mi_sint8korr(key),buff,10); + VOID(fprintf(stream,"%s",buff)); + key=end; + break; + } + case HA_KEYTYPE_BIT: + { + uint i; + fputs("0x",stream); + for (i=0 ; i < keyseg->length ; i++) + fprintf(stream, "%02x", (uint) *key++); + key= end; + break; + } + +#endif + case HA_KEYTYPE_VARTEXT1: /* VARCHAR and TEXT */ + case HA_KEYTYPE_VARTEXT2: /* VARCHAR and TEXT */ + case HA_KEYTYPE_VARBINARY1: /* VARBINARY and BLOB */ + case HA_KEYTYPE_VARBINARY2: /* VARBINARY and BLOB */ + { + uint tmp_length; + get_key_length(tmp_length,key); + /* + The following command sometimes gives a warning from valgrind. + Not yet sure if the bug is in valgrind, glibc or mysqld + */ + VOID(fprintf(stream,"%.*s",(int) tmp_length,key)); + key+=tmp_length; + break; + } + default: break; /* This never happens */ + } + } + VOID(fputs("\"\n",stream)); + return; +} /* print_key */ + + +#ifdef EXTRA_DEBUG + +my_bool _ma_check_table_is_closed(const char *name, const char *where) +{ + char filename[FN_REFLEN]; + LIST *pos; + DBUG_ENTER("_ma_check_table_is_closed"); + + (void) fn_format(filename,name,"",MARIA_NAME_IEXT,4+16+32); + pthread_mutex_lock(&THR_LOCK_maria); + for (pos=maria_open_list ; pos ; pos=pos->next) + { + MARIA_HA *info=(MARIA_HA*) pos->data; + MARIA_SHARE *share= info->s; + if (!strcmp(share->unique_file_name.str, filename)) + { + if (share->last_version) + { + fprintf(stderr,"Warning: Table: %s is open on %s\n", name,where); + DBUG_PRINT("warning",("Table: %s is open on %s", name,where)); + pthread_mutex_unlock(&THR_LOCK_maria); + DBUG_RETURN(1); + } + } + } + pthread_mutex_unlock(&THR_LOCK_maria); + DBUG_RETURN(0); +} +#endif /* EXTRA_DEBUG */ diff --git a/storage/maria/ma_delete.c b/storage/maria/ma_delete.c new file mode 100644 index 00000000000..5b0a2ac8884 --- /dev/null +++ b/storage/maria/ma_delete.c @@ -0,0 +1,1531 @@ +/* Copyright (C) 2006 MySQL AB & MySQL Finland AB & TCX DataKonsult 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 "ma_fulltext.h" +#include "ma_rt_index.h" +#include "trnman.h" +#include "ma_key_recover.h" + +static int d_search(MARIA_HA *info, MARIA_KEY *key, uint32 comp_flag, + MARIA_PAGE *page); +static int del(MARIA_HA *info, MARIA_KEY *key, + MARIA_PAGE *anc_page, MARIA_PAGE *leaf_page, + uchar *keypos, my_off_t next_block, uchar *ret_key_buff); +static int underflow(MARIA_HA *info, MARIA_KEYDEF *keyinfo, + MARIA_PAGE *anc_page, MARIA_PAGE *leaf_page, + uchar *keypos); +static uint remove_key(MARIA_KEYDEF *keyinfo, uint page_flag, uint nod_flag, + uchar *keypos, uchar *lastkey, uchar *page_end, + my_off_t *next_block, MARIA_KEY_PARAM *s_temp); + +/* @breif Remove a row from a MARIA table */ + +int maria_delete(MARIA_HA *info,const uchar *record) +{ + uint i; + uchar *old_key; + int save_errno; + char lastpos[8]; + MARIA_SHARE *share= info->s; + MARIA_KEYDEF *keyinfo; + DBUG_ENTER("maria_delete"); + + /* Test if record is in datafile */ + DBUG_EXECUTE_IF("maria_pretend_crashed_table_on_usage", + maria_print_error(share, HA_ERR_CRASHED); + DBUG_RETURN(my_errno= HA_ERR_CRASHED);); + DBUG_EXECUTE_IF("my_error_test_undefined_error", + maria_print_error(share, INT_MAX); + DBUG_RETURN(my_errno= INT_MAX);); + if (!(info->update & HA_STATE_AKTIV)) + { + DBUG_RETURN(my_errno=HA_ERR_KEY_NOT_FOUND); /* No database read */ + } + if (share->options & HA_OPTION_READ_ONLY_DATA) + { + DBUG_RETURN(my_errno=EACCES); + } + if (_ma_readinfo(info,F_WRLCK,1)) + DBUG_RETURN(my_errno); + if ((*share->compare_record)(info,record)) + goto err; /* Error on read-check */ + + if (_ma_mark_file_changed(info)) + goto err; + + /* Ensure we don't change the autoincrement value */ + info->last_auto_increment= ~(ulonglong) 0; + /* Remove all keys from the index file */ + + old_key= info->lastkey_buff2; + + for (i=0, keyinfo= share->keyinfo ; i < share->base.keys ; i++, keyinfo++) + { + if (maria_is_key_active(share->state.key_map, i)) + { + keyinfo->version++; + if (keyinfo->flag & HA_FULLTEXT) + { + if (_ma_ft_del(info, i, old_key, record, info->cur_row.lastpos)) + goto err; + } + else + { + MARIA_KEY key; + if (keyinfo->ck_delete(info, + (*keyinfo->make_key)(info, &key, i, old_key, + record, + info->cur_row.lastpos, + info->cur_row.trid))) + goto err; + } + /* The above changed info->lastkey2. Inform maria_rnext_same(). */ + info->update&= ~HA_STATE_RNEXT_SAME; + } + } + + if (share->calc_checksum) + { + /* + We can't use the row based checksum as this doesn't have enough + precision. + */ + info->cur_row.checksum= (*share->calc_checksum)(info, record); + } + + if ((*share->delete_record)(info, record)) + goto err; /* Remove record from database */ + + info->state->checksum-= info->cur_row.checksum; + info->state->records--; + info->update= HA_STATE_CHANGED+HA_STATE_DELETED+HA_STATE_ROW_CHANGED; + share->state.changed|= (STATE_NOT_OPTIMIZED_ROWS | STATE_NOT_MOVABLE | + STATE_NOT_ZEROFILLED); + info->state->changed=1; + + mi_sizestore(lastpos, info->cur_row.lastpos); + VOID(_ma_writeinfo(info,WRITEINFO_UPDATE_KEYFILE)); + allow_break(); /* Allow SIGHUP & SIGINT */ + if (info->invalidator != 0) + { + DBUG_PRINT("info", ("invalidator... '%s' (delete)", + share->open_file_name.str)); + (*info->invalidator)(share->open_file_name.str); + info->invalidator=0; + } + DBUG_RETURN(0); + +err: + save_errno= my_errno; + DBUG_ASSERT(save_errno); + if (!save_errno) + save_errno= HA_ERR_INTERNAL_ERROR; /* Should never happen */ + + mi_sizestore(lastpos, info->cur_row.lastpos); + if (save_errno != HA_ERR_RECORD_CHANGED) + { + maria_print_error(share, HA_ERR_CRASHED); + maria_mark_crashed(info); /* mark table crashed */ + } + VOID(_ma_writeinfo(info,WRITEINFO_UPDATE_KEYFILE)); + info->update|=HA_STATE_WRITTEN; /* Buffer changed */ + allow_break(); /* Allow SIGHUP & SIGINT */ + if (save_errno == HA_ERR_KEY_NOT_FOUND) + { + maria_print_error(share, HA_ERR_CRASHED); + my_errno=HA_ERR_CRASHED; + } + DBUG_RETURN(my_errno= save_errno); +} /* maria_delete */ + + +/* + Remove a key from the btree index + + TODO: + Change ma_ck_real_delete() to use another buffer for changed keys instead + of key->data. This would allows us to remove the copying of the key here. +*/ + +my_bool _ma_ck_delete(MARIA_HA *info, MARIA_KEY *key) +{ + MARIA_SHARE *share= info->s; + int res; + LSN lsn= LSN_IMPOSSIBLE; + my_off_t new_root= share->state.key_root[key->keyinfo->key_nr]; + uchar key_buff[MARIA_MAX_KEY_BUFF], *save_key_data; + MARIA_KEY org_key; + DBUG_ENTER("_ma_ck_delete"); + + save_key_data= key->data; + if (share->now_transactional) + { + /* Save original value as the key may change */ + memcpy(key_buff, key->data, key->data_length + key->ref_length); + org_key= *key; + key->data= key_buff; + } + + res= _ma_ck_real_delete(info, key, &new_root); + + key->data= save_key_data; + if (!res && share->now_transactional) + res= _ma_write_undo_key_delete(info, &org_key, new_root, &lsn); + else + { + share->state.key_root[key->keyinfo->key_nr]= new_root; + _ma_fast_unlock_key_del(info); + } + _ma_unpin_all_pages_and_finalize_row(info, lsn); + DBUG_RETURN(res != 0); +} /* _ma_ck_delete */ + + +my_bool _ma_ck_real_delete(register MARIA_HA *info, MARIA_KEY *key, + my_off_t *root) +{ + int error; + my_bool result= 0; + my_off_t old_root; + uchar *root_buff; + MARIA_KEYDEF *keyinfo= key->keyinfo; + MARIA_PAGE page; + DBUG_ENTER("_ma_ck_real_delete"); + + if ((old_root=*root) == HA_OFFSET_ERROR) + { + my_errno=HA_ERR_CRASHED; + DBUG_RETURN(1); + } + if (!(root_buff= (uchar*) my_alloca((uint) keyinfo->block_length+ + MARIA_MAX_KEY_BUFF*2))) + { + DBUG_PRINT("error",("Couldn't allocate memory")); + my_errno=ENOMEM; + DBUG_RETURN(1); + } + DBUG_PRINT("info",("root_page: %ld", (long) old_root)); + if (_ma_fetch_keypage(&page, info, keyinfo, old_root, + PAGECACHE_LOCK_WRITE, DFLT_INIT_HITS, root_buff, 0)) + { + result= 1; + goto err; + } + if ((error= d_search(info, key, (keyinfo->flag & HA_FULLTEXT ? + SEARCH_FIND | SEARCH_UPDATE | SEARCH_INSERT: + SEARCH_SAME), + &page))) + { + if (error < 0) + result= 1; + else if (error == 2) + { + DBUG_PRINT("test",("Enlarging of root when deleting")); + if (_ma_enlarge_root(info, key, root)) + result= 1; + } + else /* error == 1 */ + { + MARIA_SHARE *share= info->s; + + page_mark_changed(info, &page); + + if (page.size <= page.node + share->keypage_header + 1) + { + if (page.node) + *root= _ma_kpos(page.node, root_buff +share->keypage_header + + page.node); + else + *root=HA_OFFSET_ERROR; + if (_ma_dispose(info, old_root, 0)) + result= 1; + } + else if (_ma_write_keypage(&page, PAGECACHE_LOCK_LEFT_WRITELOCKED, + DFLT_INIT_HITS)) + result= 1; + } + } +err: + my_afree(root_buff); + DBUG_PRINT("exit",("Return: %d",result)); + DBUG_RETURN(result); +} /* _ma_ck_real_delete */ + + +/** + @brief Remove key below key root + + @param key Key to delete. Will contain new key if block was enlarged + + @return + @retval 0 ok (anc_page is not changed) + @retval 1 If data on page is too small; In this case anc_buff is not saved + @retval 2 If data on page is too big + @retval -1 On errors +*/ + +static int d_search(MARIA_HA *info, MARIA_KEY *key, uint32 comp_flag, + MARIA_PAGE *anc_page) +{ + int flag,ret_value,save_flag; + uint nod_flag, page_flag; + my_bool last_key; + uchar *leaf_buff,*keypos; + uchar lastkey[MARIA_MAX_KEY_BUFF]; + MARIA_KEY_PARAM s_temp; + MARIA_SHARE *share= info->s; + MARIA_KEYDEF *keyinfo= key->keyinfo; + MARIA_PAGE leaf_page; + DBUG_ENTER("d_search"); + DBUG_DUMP("page", anc_page->buff, anc_page->size); + + flag=(*keyinfo->bin_search)(key, anc_page, comp_flag, &keypos, lastkey, + &last_key); + if (flag == MARIA_FOUND_WRONG_KEY) + { + DBUG_PRINT("error",("Found wrong key")); + DBUG_RETURN(-1); + } + page_flag= anc_page->flag; + nod_flag= anc_page->node; + + if (!flag && (keyinfo->flag & HA_FULLTEXT)) + { + uint off; + int subkeys; + + get_key_full_length_rdonly(off, lastkey); + subkeys=ft_sintXkorr(lastkey+off); + DBUG_ASSERT(info->ft1_to_ft2==0 || subkeys >=0); + comp_flag=SEARCH_SAME; + if (subkeys >= 0) + { + /* normal word, one-level tree structure */ + if (info->ft1_to_ft2) + { + /* we're in ft1->ft2 conversion mode. Saving key data */ + insert_dynamic(info->ft1_to_ft2, (lastkey+off)); + } + else + { + /* we need exact match only if not in ft1->ft2 conversion mode */ + flag=(*keyinfo->bin_search)(key, anc_page, comp_flag, &keypos, + lastkey, &last_key); + } + /* fall through to normal delete */ + } + else + { + /* popular word. two-level tree. going down */ + uint tmp_key_length; + my_off_t root; + uchar *kpos=keypos; + MARIA_KEY tmp_key; + + tmp_key.data= lastkey; + tmp_key.keyinfo= keyinfo; + + if (!(tmp_key_length=(*keyinfo->get_key)(&tmp_key, page_flag, nod_flag, + &kpos))) + { + my_errno= HA_ERR_CRASHED; + DBUG_RETURN(-1); + } + root= _ma_row_pos_from_key(&tmp_key); + if (subkeys == -1) + { + /* the last entry in sub-tree */ + if (_ma_dispose(info, root, 1)) + DBUG_RETURN(-1); + /* fall through to normal delete */ + } + else + { + MARIA_KEY word_key; + keyinfo=&share->ft2_keyinfo; + /* we'll modify key entry 'in vivo' */ + kpos-=keyinfo->keylength+nod_flag; + get_key_full_length_rdonly(off, key->data); + + word_key.data= key->data + off; + word_key.keyinfo= &share->ft2_keyinfo; + word_key.data_length= HA_FT_WLEN; + word_key.ref_length= 0; + word_key.flag= 0; + ret_value= _ma_ck_real_delete(info, &word_key, &root); + _ma_dpointer(share, kpos+HA_FT_WLEN, root); + subkeys++; + ft_intXstore(kpos, subkeys); + if (!ret_value) + { + page_mark_changed(info, anc_page); + ret_value= _ma_write_keypage(anc_page, + PAGECACHE_LOCK_LEFT_WRITELOCKED, + DFLT_INIT_HITS); + } + DBUG_PRINT("exit",("Return: %d",ret_value)); + DBUG_RETURN(ret_value); + } + } + } + leaf_buff=0; + if (nod_flag) + { + /* Read left child page */ + leaf_page.pos= _ma_kpos(nod_flag,keypos); + if (!(leaf_buff= (uchar*) my_alloca((uint) keyinfo->block_length+ + MARIA_MAX_KEY_BUFF*2))) + { + DBUG_PRINT("error", ("Couldn't allocate memory")); + my_errno=ENOMEM; + DBUG_RETURN(-1); + } + if (_ma_fetch_keypage(&leaf_page, info,keyinfo, leaf_page.pos, + PAGECACHE_LOCK_WRITE, DFLT_INIT_HITS, leaf_buff, + 0)) + goto err; + } + + if (flag != 0) + { + if (!nod_flag) + { + DBUG_PRINT("error",("Didn't find key")); + my_errno=HA_ERR_CRASHED; /* This should newer happend */ + goto err; + } + save_flag=0; + ret_value= d_search(info, key, comp_flag, &leaf_page); + } + else + { /* Found key */ + uint tmp; + uint anc_buff_length= anc_page->size; + uint anc_page_flag= anc_page->flag; + my_off_t next_block; + + if (!(tmp= remove_key(keyinfo, anc_page_flag, nod_flag, keypos, lastkey, + anc_page->buff + anc_buff_length, + &next_block, &s_temp))) + goto err; + + page_mark_changed(info, anc_page); + anc_buff_length-= tmp; + anc_page->size= anc_buff_length; + page_store_size(share, anc_page); + + /* + Log initial changes on pages + If there is an underflow, there will be more changes logged to the + page + */ + if (share->now_transactional && + _ma_log_delete(anc_page, s_temp.key_pos, + s_temp.changed_length, s_temp.move_length)) + DBUG_RETURN(-1); + + if (!nod_flag) + { /* On leaf page */ + if (anc_buff_length <= (info->quick_mode ? + MARIA_MIN_KEYBLOCK_LENGTH : + (uint) keyinfo->underflow_block_length)) + { + /* Page will be written by caller if we return 1 */ + DBUG_RETURN(1); + } + if (_ma_write_keypage(anc_page, + PAGECACHE_LOCK_LEFT_WRITELOCKED, DFLT_INIT_HITS)) + DBUG_RETURN(-1); + DBUG_RETURN(0); + } + save_flag=1; /* Mark that anc_buff is changed */ + ret_value= del(info, key, anc_page, &leaf_page, + keypos, next_block, lastkey); + } + if (ret_value >0) + { + save_flag=1; + if (ret_value == 1) + ret_value= underflow(info, keyinfo, anc_page, &leaf_page, keypos); + else + { + /* This can only happen with variable length keys */ + MARIA_KEY last_key; + DBUG_PRINT("test",("Enlarging of key when deleting")); + + last_key.data= lastkey; + last_key.keyinfo= keyinfo; + if (!_ma_get_last_key(&last_key, anc_page, keypos)) + goto err; + ret_value= _ma_insert(info, key, anc_page, keypos, + last_key.data, + (MARIA_PAGE*) 0, (uchar*) 0, (my_bool) 0); + } + } + if (ret_value == 0 && anc_page->size > + (uint) (keyinfo->block_length - KEYPAGE_CHECKSUM_SIZE)) + { + /* parent buffer got too big ; We have to split the page */ + save_flag=1; + ret_value= _ma_split_page(info, key, anc_page, + (uint) (keyinfo->block_length - + KEYPAGE_CHECKSUM_SIZE), + (uchar*) 0, 0, 0, lastkey, 0) | 2; + } + if (save_flag && ret_value != 1) + { + page_mark_changed(info, anc_page); + if (_ma_write_keypage(anc_page, PAGECACHE_LOCK_LEFT_WRITELOCKED, + DFLT_INIT_HITS)) + ret_value= -1; + } + else + { + DBUG_DUMP("page", anc_page->buff, anc_page->size); + } + my_afree(leaf_buff); + DBUG_PRINT("exit",("Return: %d",ret_value)); + DBUG_RETURN(ret_value); + +err: + my_afree(leaf_buff); + DBUG_PRINT("exit",("Error: %d",my_errno)); + DBUG_RETURN (-1); +} /* d_search */ + + +/** + @brief Remove a key that has a page-reference + + @param info Maria handler + @param key Buffer for key to be inserted at upper level + @param anc_page Page address for page where deleted key was + @param anc_buff Page buffer (nod) where deleted key was + @param leaf_page Page address for nod before the deleted key + @param leaf_buff Buffer for leaf_page + @param leaf_buff_link Pinned page link for leaf_buff + @param keypos Pos to where deleted key was on anc_buff + @param next_block Page adress for nod after deleted key + @param ret_key_buff Key before keypos in anc_buff + + @notes + leaf_page must be written to disk if retval > 0 + anc_page is not updated on disk. Caller should do this + + @return + @retval < 0 Error + @retval 0 OK. leaf_buff is written to disk + + @retval 1 key contains key to upper level (from balance page) + leaf_buff has underflow + @retval 2 key contains key to upper level (from split space) +*/ + +static int del(MARIA_HA *info, MARIA_KEY *key, + MARIA_PAGE *anc_page, MARIA_PAGE *leaf_page, + uchar *keypos, my_off_t next_block, uchar *ret_key_buff) +{ + int ret_value,length; + uint a_length, page_flag, nod_flag, leaf_length, new_leaf_length; + uchar keybuff[MARIA_MAX_KEY_BUFF],*endpos,*next_buff,*key_start, *prev_key; + uchar *anc_buff; + MARIA_KEY_PARAM s_temp; + MARIA_KEY tmp_key; + MARIA_SHARE *share= info->s; + MARIA_KEYDEF *keyinfo= key->keyinfo; + MARIA_KEY ret_key; + MARIA_PAGE next_page; + DBUG_ENTER("del"); + DBUG_PRINT("enter",("leaf_page: %ld keypos: 0x%lx", (long) leaf_page, + (ulong) keypos)); + DBUG_DUMP("leaf_buff", leaf_page->buff, leaf_page->size); + + page_flag= leaf_page->flag; + leaf_length= leaf_page->size; + nod_flag= leaf_page->node; + + endpos= leaf_page->buff + leaf_length; + tmp_key.keyinfo= keyinfo; + tmp_key.data= keybuff; + + if (!(key_start= _ma_get_last_key(&tmp_key, leaf_page, endpos))) + DBUG_RETURN(-1); + + if (nod_flag) + { + next_page.pos= _ma_kpos(nod_flag,endpos); + if (!(next_buff= (uchar*) my_alloca((uint) keyinfo->block_length+ + MARIA_MAX_KEY_BUFF*2))) + DBUG_RETURN(-1); + if (_ma_fetch_keypage(&next_page, info, keyinfo, next_page.pos, + PAGECACHE_LOCK_WRITE, DFLT_INIT_HITS, next_buff, 0)) + ret_value= -1; + else + { + DBUG_DUMP("next_page", next_page.buff, next_page.size); + if ((ret_value= del(info, key, anc_page, &next_page, + keypos, next_block, ret_key_buff)) >0) + { + /* Get new length after key was deleted */ + endpos= leaf_page->buff+ leaf_page->size; + if (ret_value == 1) + { + ret_value= underflow(info, keyinfo, leaf_page, &next_page, + endpos); + if (ret_value == 0 && leaf_page->size > + (uint) (keyinfo->block_length - KEYPAGE_CHECKSUM_SIZE)) + { + ret_value= (_ma_split_page(info, key, leaf_page, + (uint) (keyinfo->block_length - + KEYPAGE_CHECKSUM_SIZE), + (uchar*) 0, 0, 0, + ret_key_buff, 0) | 2); + } + } + else + { + DBUG_PRINT("test",("Inserting of key when deleting")); + if (!_ma_get_last_key(&tmp_key, leaf_page, endpos)) + goto err; + ret_value= _ma_insert(info, key, leaf_page, endpos, + tmp_key.data, (MARIA_PAGE *) 0, (uchar*) 0, + 0); + } + } + page_mark_changed(info, leaf_page); + /* + If ret_value <> 0, then leaf_page underflowed and caller will have + to handle underflow and write leaf_page to disk. + We can't write it here, as if leaf_page is empty we get an assert + in _ma_write_keypage. + */ + if (ret_value == 0 && _ma_write_keypage(leaf_page, + PAGECACHE_LOCK_LEFT_WRITELOCKED, + DFLT_INIT_HITS)) + goto err; + } + my_afree(next_buff); + DBUG_RETURN(ret_value); + } + + /* + Remove last key from leaf page + Note that leaf_page page may only have had one key (can normally only + happen in quick mode), in which ase it will now temporary have 0 keys + on it. This will be corrected by the caller as we will return 0. + */ + new_leaf_length= (uint) (key_start - leaf_page->buff); + leaf_page->size= new_leaf_length; + page_store_size(share, leaf_page); + + if (share->now_transactional && + _ma_log_suffix(leaf_page, leaf_length, new_leaf_length)) + goto err; + + page_mark_changed(info, leaf_page); /* Safety */ + if (new_leaf_length <= (info->quick_mode ? MARIA_MIN_KEYBLOCK_LENGTH : + (uint) keyinfo->underflow_block_length)) + { + /* Underflow, leaf_page will be written by caller */ + ret_value= 1; + } + else + { + ret_value= 0; + if (_ma_write_keypage(leaf_page, PAGECACHE_LOCK_LEFT_WRITELOCKED, + DFLT_INIT_HITS)) + goto err; + } + + /* Place last key in ancestor page on deleted key position */ + a_length= anc_page->size; + anc_buff= anc_page->buff; + endpos= anc_buff + a_length; + + ret_key.keyinfo= keyinfo; + ret_key.data= ret_key_buff; + + prev_key= 0; + if (keypos != anc_buff+share->keypage_header + share->base.key_reflength) + { + if (!_ma_get_last_key(&ret_key, anc_page, keypos)) + goto err; + prev_key= ret_key.data; + } + length= (*keyinfo->pack_key)(&tmp_key, share->base.key_reflength, + keypos == endpos ? (uchar*) 0 : keypos, + prev_key, prev_key, + &s_temp); + if (length > 0) + bmove_upp(endpos+length,endpos,(uint) (endpos-keypos)); + else + bmove(keypos,keypos-length, (int) (endpos-keypos)+length); + (*keyinfo->store_key)(keyinfo,keypos,&s_temp); + key_start= keypos; + if (tmp_key.flag & (SEARCH_USER_KEY_HAS_TRANSID | + SEARCH_PAGE_KEY_HAS_TRANSID)) + { + _ma_mark_page_with_transid(share, anc_page); + } + + /* Save pointer to next leaf on parent page */ + if (!(*keyinfo->get_key)(&ret_key, page_flag, share->base.key_reflength, + &keypos)) + goto err; + _ma_kpointer(info,keypos - share->base.key_reflength,next_block); + anc_page->size= a_length + length; + page_store_size(share, anc_page); + + if (share->now_transactional && + _ma_log_add(anc_page, a_length, + key_start, s_temp.changed_length, s_temp.move_length, 1)) + goto err; + + DBUG_RETURN(new_leaf_length <= + (info->quick_mode ? MARIA_MIN_KEYBLOCK_LENGTH : + (uint) keyinfo->underflow_block_length)); +err: + DBUG_RETURN(-1); +} /* del */ + + +/** + @brief Balances adjacent pages if underflow occours + + @fn underflow() + @param anc_buff Anchestor page data + @param leaf_page Page number of leaf page + @param leaf_buff Leaf page (page that underflowed) + @param leaf_page_link Pointer to pin information about leaf page + @param keypos Position after current key in anc_buff + + @note + This function writes redo entries for all changes + leaf_page is saved to disk + Caller must save anc_buff + + @return + @retval 0 ok + @retval 1 ok, but anc_buff did underflow + @retval -1 error + */ + +static int underflow(MARIA_HA *info, MARIA_KEYDEF *keyinfo, + MARIA_PAGE *anc_page, MARIA_PAGE *leaf_page, + uchar *keypos) +{ + int t_length; + uint anc_length,buff_length,leaf_length,p_length,s_length,nod_flag; + uint next_buff_length, new_buff_length, key_reflength; + uint unchanged_leaf_length, new_leaf_length, new_anc_length; + uint anc_page_flag, page_flag; + uchar anc_key_buff[MARIA_MAX_KEY_BUFF], leaf_key_buff[MARIA_MAX_KEY_BUFF]; + uchar *endpos, *next_keypos, *anc_pos, *half_pos, *prev_key; + uchar *anc_buff, *leaf_buff; + uchar *after_key, *anc_end_pos; + MARIA_KEY_PARAM key_deleted, key_inserted; + MARIA_SHARE *share= info->s; + my_bool first_key; + MARIA_KEY tmp_key, anc_key, leaf_key; + MARIA_PAGE next_page; + DBUG_ENTER("underflow"); + DBUG_PRINT("enter",("leaf_page: %ld keypos: 0x%lx",(long) leaf_page->pos, + (ulong) keypos)); + DBUG_DUMP("anc_buff", anc_page->buff, anc_page->size); + DBUG_DUMP("leaf_buff", leaf_page->buff, leaf_page->size); + + anc_page_flag= anc_page->flag; + anc_buff= anc_page->buff; + leaf_buff= leaf_page->buff; + info->keyread_buff_used=1; + next_keypos=keypos; + nod_flag= leaf_page->node; + p_length= nod_flag+share->keypage_header; + anc_length= anc_page->size; + leaf_length= leaf_page->size; + key_reflength= share->base.key_reflength; + if (share->keyinfo+info->lastinx == keyinfo) + info->page_changed=1; + first_key= keypos == anc_buff + share->keypage_header + key_reflength; + + tmp_key.data= info->buff; + anc_key.data= anc_key_buff; + leaf_key.data= leaf_key_buff; + tmp_key.keyinfo= leaf_key.keyinfo= anc_key.keyinfo= keyinfo; + + if ((keypos < anc_buff + anc_length && (info->state->records & 1)) || + first_key) + { + size_t tmp_length; + uint next_page_flag; + /* Use page right of anc-page */ + DBUG_PRINT("test",("use right page")); + + /* + Calculate position after the current key. Note that keydata itself is + not used + */ + if (keyinfo->flag & HA_BINARY_PACK_KEY) + { + if (!(next_keypos= _ma_get_key(&tmp_key, anc_page, keypos))) + goto err; + } + else + { + /* Avoid length error check if packed key */ + tmp_key.data[0]= tmp_key.data[1]= 0; + /* Got to end of found key */ + if (!(*keyinfo->get_key)(&tmp_key, anc_page_flag, key_reflength, + &next_keypos)) + goto err; + } + next_page.pos= _ma_kpos(key_reflength, next_keypos); + if (_ma_fetch_keypage(&next_page, info, keyinfo, next_page.pos, + PAGECACHE_LOCK_WRITE, DFLT_INIT_HITS, info->buff, 0)) + goto err; + next_buff_length= next_page.size; + next_page_flag= next_page.flag; + DBUG_DUMP("next", next_page.buff, next_page.size); + + /* find keys to make a big key-page */ + bmove(next_keypos-key_reflength, next_page.buff + share->keypage_header, + key_reflength); + + if (!_ma_get_last_key(&anc_key, anc_page, next_keypos) || + !_ma_get_last_key(&leaf_key, leaf_page, leaf_buff+leaf_length)) + goto err; + + /* merge pages and put parting key from anc_page between */ + prev_key= (leaf_length == p_length ? (uchar*) 0 : leaf_key.data); + t_length= (*keyinfo->pack_key)(&anc_key, nod_flag, next_page.buff+p_length, + prev_key, prev_key, &key_inserted); + tmp_length= next_buff_length - p_length; + endpos= next_page.buff + tmp_length + leaf_length + t_length; + /* next_page.buff will always be larger than before !*/ + bmove_upp(endpos, next_page.buff + next_buff_length, tmp_length); + memcpy(next_page.buff, leaf_buff,(size_t) leaf_length); + (*keyinfo->store_key)(keyinfo, next_page.buff+leaf_length, &key_inserted); + buff_length= (uint) (endpos - next_page.buff); + + /* Set page flag from combination of both key pages and parting key */ + page_flag= next_page_flag | leaf_page->flag; + if (anc_key.flag & (SEARCH_USER_KEY_HAS_TRANSID | + SEARCH_PAGE_KEY_HAS_TRANSID)) + page_flag|= KEYPAGE_FLAG_HAS_TRANSID; + + next_page.size= buff_length; + next_page.flag= page_flag; + page_store_info(share, &next_page); + + /* remove key from anc_page */ + if (!(s_length=remove_key(keyinfo, anc_page_flag, key_reflength, keypos, + anc_key_buff, anc_buff+anc_length, + (my_off_t *) 0, &key_deleted))) + goto err; + + new_anc_length= anc_length - s_length; + anc_page->size= new_anc_length; + page_store_size(share, anc_page); + + if (buff_length <= (uint) (keyinfo->block_length - KEYPAGE_CHECKSUM_SIZE)) + { + /* All keys fitted into one page */ + page_mark_changed(info, &next_page); + if (_ma_dispose(info, next_page.pos, 0)) + goto err; + + memcpy(leaf_buff, next_page.buff, (size_t) buff_length); + leaf_page->size= next_page.size; + leaf_page->flag= next_page.flag; + + if (share->now_transactional) + { + /* Log changes to parent page */ + if (_ma_log_delete(anc_page, key_deleted.key_pos, + key_deleted.changed_length, + key_deleted.move_length)) + goto err; + /* + Log changes to leaf page. Data for leaf page is in leaf_buff + which contains original leaf_buff, parting key and next_buff + */ + if (_ma_log_suffix(leaf_page, leaf_length, buff_length)) + goto err; + } + } + else + { + /* + Balancing didn't free a page, so we have to split 'buff' into two + pages: + - Find key in middle of buffer + - Store everything before key in 'leaf_page' + - Pack key into anc_page at position of deleted key + Note that anc_page may overflow! (is handled by caller) + - Store remaining keys in next_page (buff) + */ + MARIA_KEY_PARAM anc_key_inserted; + + anc_end_pos= anc_buff + new_anc_length; + + DBUG_PRINT("test",("anc_buff: 0x%lx anc_end_pos: 0x%lx", + (long) anc_buff, (long) anc_end_pos)); + + if (!first_key && !_ma_get_last_key(&anc_key, anc_page, keypos)) + goto err; + if (!(half_pos= _ma_find_half_pos(&leaf_key, &next_page, &after_key))) + goto err; + new_leaf_length= (uint) (half_pos - next_page.buff); + memcpy(leaf_buff, next_page.buff, (size_t) new_leaf_length); + + leaf_page->size= new_leaf_length; + leaf_page->flag= page_flag; + page_store_info(share, leaf_page); + + /* Correct new keypointer to leaf_page */ + half_pos=after_key; + _ma_kpointer(info, + leaf_key.data + leaf_key.data_length + leaf_key.ref_length, + next_page.pos); + + /* Save key in anc_page */ + prev_key= (first_key ? (uchar*) 0 : anc_key.data); + t_length= (*keyinfo->pack_key)(&leaf_key, key_reflength, + (keypos == anc_end_pos ? (uchar*) 0 : + keypos), + prev_key, prev_key, &anc_key_inserted); + if (t_length >= 0) + bmove_upp(anc_end_pos+t_length, anc_end_pos, + (uint) (anc_end_pos - keypos)); + else + bmove(keypos,keypos-t_length,(uint) (anc_end_pos-keypos)+t_length); + (*keyinfo->store_key)(keyinfo,keypos, &anc_key_inserted); + new_anc_length+= t_length; + anc_page->size= new_anc_length; + page_store_size(share, anc_page); + + if (leaf_key.flag & (SEARCH_USER_KEY_HAS_TRANSID | + SEARCH_PAGE_KEY_HAS_TRANSID)) + _ma_mark_page_with_transid(share, anc_page); + + /* Store key first in new page */ + if (nod_flag) + bmove(next_page.buff + share->keypage_header, half_pos-nod_flag, + (size_t) nod_flag); + if (!(*keyinfo->get_key)(&leaf_key, page_flag, nod_flag, &half_pos)) + goto err; + t_length=(int) (*keyinfo->pack_key)(&leaf_key, nod_flag, (uchar*) 0, + (uchar*) 0, (uchar*) 0, + &key_inserted); + /* t_length will always be > 0 for a new page !*/ + tmp_length= (size_t) ((next_page.buff + buff_length) - half_pos); + bmove(next_page.buff + p_length + t_length, half_pos, tmp_length); + (*keyinfo->store_key)(keyinfo, next_page.buff + p_length, &key_inserted); + new_buff_length= tmp_length + t_length + p_length; + next_page.size= new_buff_length; + page_store_size(share, &next_page); + /* keypage flag is already up to date */ + + if (share->now_transactional) + { + /* + Log changes to parent page + This has one key deleted from it and one key inserted to it at + keypos + + ma_log_add ensures that we don't log changes that is outside of + key block size, as the REDO code can't handle that + */ + if (_ma_log_add(anc_page, anc_length, keypos, + anc_key_inserted.move_length + + max(anc_key_inserted.changed_length - + anc_key_inserted.move_length, + key_deleted.changed_length), + anc_key_inserted.move_length - + key_deleted.move_length, 1)) + goto err; + + /* + Log changes to leaf page. + This contains original data with new data added at end + */ + DBUG_ASSERT(leaf_length <= new_leaf_length); + if (_ma_log_suffix(leaf_page, leaf_length, new_leaf_length)) + goto err; + /* + Log changes to next page + + This contains original data with some prefix data deleted and + some compressed data at start possible extended + + Data in buff was originally: + org_leaf_buff [leaf_length] + separator_key [buff_key_inserted.move_length] + next_key_changes [buff_key_inserted.changed_length -move_length] + next_page_data [next_buff_length - p_length - + (buff_key_inserted.changed_length -move_length)] + + After changes it's now: + unpacked_key [key_inserted.changed_length] + next_suffix [next_buff_length - key_inserted.changed_length] + + */ + DBUG_ASSERT(new_buff_length <= next_buff_length); + if (_ma_log_prefix(&next_page, key_inserted.changed_length, + (int) (new_buff_length - next_buff_length))) + goto err; + } + page_mark_changed(info, &next_page); + if (_ma_write_keypage(&next_page, + PAGECACHE_LOCK_LEFT_WRITELOCKED, DFLT_INIT_HITS)) + goto err; + } + + page_mark_changed(info, leaf_page); + if (_ma_write_keypage(leaf_page, + PAGECACHE_LOCK_LEFT_WRITELOCKED, DFLT_INIT_HITS)) + goto err; + DBUG_RETURN(new_anc_length <= + ((info->quick_mode ? MARIA_MIN_KEYBLOCK_LENGTH : + (uint) keyinfo->underflow_block_length))); + } + + DBUG_PRINT("test",("use left page")); + + keypos= _ma_get_last_key(&anc_key, anc_page, keypos); + if (!keypos) + goto err; + next_page.pos= _ma_kpos(key_reflength,keypos); + if (_ma_fetch_keypage(&next_page, info, keyinfo, next_page.pos, + PAGECACHE_LOCK_WRITE, DFLT_INIT_HITS, info->buff, 0)) + goto err; + buff_length= next_page.size; + endpos= next_page.buff + buff_length; + DBUG_DUMP("prev", next_page.buff, next_page.size); + + /* find keys to make a big key-page */ + bmove(next_keypos - key_reflength, leaf_buff + share->keypage_header, + key_reflength); + next_keypos=keypos; + if (!(*keyinfo->get_key)(&anc_key, anc_page_flag, key_reflength, + &next_keypos)) + goto err; + if (!_ma_get_last_key(&leaf_key, &next_page, endpos)) + goto err; + + /* merge pages and put parting key from anc_page between */ + prev_key= (leaf_length == p_length ? (uchar*) 0 : leaf_key.data); + t_length=(*keyinfo->pack_key)(&anc_key, nod_flag, + (leaf_length == p_length ? + (uchar*) 0 : leaf_buff+p_length), + prev_key, prev_key, + &key_inserted); + if (t_length >= 0) + bmove(endpos+t_length, leaf_buff+p_length, + (size_t) (leaf_length-p_length)); + else /* We gained space */ + bmove(endpos,leaf_buff+((int) p_length-t_length), + (size_t) (leaf_length-p_length+t_length)); + (*keyinfo->store_key)(keyinfo,endpos, &key_inserted); + + /* Remember for logging how many bytes of leaf_buff that are not changed */ + DBUG_ASSERT((int) key_inserted.changed_length >= key_inserted.move_length); + unchanged_leaf_length= leaf_length - (key_inserted.changed_length - + key_inserted.move_length); + + new_buff_length= buff_length + leaf_length - p_length + t_length; + + page_flag= next_page.flag | leaf_page->flag; + if (anc_key.flag & (SEARCH_USER_KEY_HAS_TRANSID | + SEARCH_PAGE_KEY_HAS_TRANSID)) + page_flag|= KEYPAGE_FLAG_HAS_TRANSID; + + next_page.size= new_buff_length; + next_page.flag= page_flag; + page_store_info(share, &next_page); + + /* remove key from anc_page */ + if (!(s_length= remove_key(keyinfo, anc_page_flag, key_reflength, keypos, + anc_key_buff, + anc_buff+anc_length, (my_off_t *) 0, + &key_deleted))) + goto err; + + new_anc_length= anc_length - s_length; + anc_page->size= new_anc_length; + page_store_size(share, anc_page); + + if (new_buff_length <= (uint) (keyinfo->block_length - + KEYPAGE_CHECKSUM_SIZE)) + { + /* All keys fitted into one page */ + page_mark_changed(info, leaf_page); + if (_ma_dispose(info, leaf_page->pos, 0)) + goto err; + + if (share->now_transactional) + { + /* Log changes to parent page */ + if (_ma_log_delete(anc_page, key_deleted.key_pos, + key_deleted.changed_length, key_deleted.move_length)) + + goto err; + /* + Log changes to next page. Data for leaf page is in buff + that contains original leaf_buff, parting key and next_buff + */ + if (_ma_log_suffix(&next_page, buff_length, new_buff_length)) + goto err; + } + } + else + { + /* + Balancing didn't free a page, so we have to split 'next_page' into two + pages + - Find key in middle of buffer (buff) + - Pack key at half_buff into anc_page at position of deleted key + Note that anc_page may overflow! (is handled by caller) + - Move everything after middlekey to 'leaf_buff' + - Shorten buff at 'endpos' + */ + MARIA_KEY_PARAM anc_key_inserted; + size_t tmp_length; + + if (keypos == anc_buff + share->keypage_header + key_reflength) + anc_pos= 0; /* First key */ + else + { + if (!_ma_get_last_key(&anc_key, anc_page, keypos)) + goto err; + anc_pos= anc_key.data; + } + if (!(endpos= _ma_find_half_pos(&leaf_key, &next_page, &half_pos))) + goto err; + + /* Correct new keypointer to leaf_page */ + _ma_kpointer(info,leaf_key.data + leaf_key.data_length + + leaf_key.ref_length, leaf_page->pos); + + /* Save key in anc_page */ + DBUG_DUMP("anc_buff", anc_buff, new_anc_length); + DBUG_DUMP_KEY("key_to_anc", &leaf_key); + anc_end_pos= anc_buff + new_anc_length; + t_length=(*keyinfo->pack_key)(&leaf_key, key_reflength, + keypos == anc_end_pos ? (uchar*) 0 + : keypos, + anc_pos, anc_pos, + &anc_key_inserted); + if (t_length >= 0) + bmove_upp(anc_end_pos+t_length, anc_end_pos, + (uint) (anc_end_pos-keypos)); + else + bmove(keypos,keypos-t_length,(uint) (anc_end_pos-keypos)+t_length); + (*keyinfo->store_key)(keyinfo,keypos, &anc_key_inserted); + new_anc_length+= t_length; + anc_page->size= new_anc_length; + page_store_size(share, anc_page); + + if (leaf_key.flag & (SEARCH_USER_KEY_HAS_TRANSID | + SEARCH_PAGE_KEY_HAS_TRANSID)) + _ma_mark_page_with_transid(share, anc_page); + + /* Store first key on new page */ + if (nod_flag) + bmove(leaf_buff + share->keypage_header, half_pos-nod_flag, + (size_t) nod_flag); + if (!(*keyinfo->get_key)(&leaf_key, page_flag, nod_flag, &half_pos)) + goto err; + DBUG_DUMP_KEY("key_to_leaf", &leaf_key); + t_length=(*keyinfo->pack_key)(&leaf_key, nod_flag, (uchar*) 0, + (uchar*) 0, (uchar*) 0, &key_inserted); + /* t_length will always be > 0 for a new page !*/ + tmp_length= (size_t) ((next_page.buff + new_buff_length) - half_pos); + DBUG_PRINT("info",("t_length: %d length: %d",t_length, (int) tmp_length)); + bmove(leaf_buff+p_length+t_length, half_pos, tmp_length); + (*keyinfo->store_key)(keyinfo,leaf_buff+p_length, &key_inserted); + new_leaf_length= tmp_length + t_length + p_length; + + leaf_page->size= new_leaf_length; + leaf_page->flag= page_flag; + page_store_info(share, leaf_page); + + new_buff_length= (uint) (endpos - next_page.buff); + next_page.size= new_buff_length; + page_store_size(share, &next_page); + + if (share->now_transactional) + { + /* + Log changes to parent page + This has one key deleted from it and one key inserted to it at + keypos + + ma_log_add() ensures that we don't log changes that is outside of + key block size, as the REDO code can't handle that + */ + if (_ma_log_add(anc_page, anc_length, keypos, + anc_key_inserted.move_length + + max(anc_key_inserted.changed_length - + anc_key_inserted.move_length, + key_deleted.changed_length), + anc_key_inserted.move_length - + key_deleted.move_length, 1)) + goto err; + + /* + Log changes to leaf page. + This contains original data with new data added first + */ + DBUG_ASSERT(leaf_length <= new_leaf_length); + if (_ma_log_prefix(leaf_page, new_leaf_length - unchanged_leaf_length, + (int) (new_leaf_length - leaf_length))) + goto err; + /* + Log changes to next page + This contains original data with some suffix data deleted + + */ + DBUG_ASSERT(new_buff_length <= buff_length); + if (_ma_log_suffix(&next_page, buff_length, new_buff_length)) + goto err; + } + + page_mark_changed(info, leaf_page); + if (_ma_write_keypage(leaf_page, + PAGECACHE_LOCK_LEFT_WRITELOCKED, DFLT_INIT_HITS)) + goto err; + } + page_mark_changed(info, &next_page); + if (_ma_write_keypage(&next_page, + PAGECACHE_LOCK_LEFT_WRITELOCKED, DFLT_INIT_HITS)) + goto err; + + DBUG_RETURN(new_anc_length <= + ((info->quick_mode ? MARIA_MIN_KEYBLOCK_LENGTH : + (uint) keyinfo->underflow_block_length))); + +err: + DBUG_RETURN(-1); +} /* underflow */ + + +/** + @brief Remove a key from page + + @fn remove_key() + keyinfo Key handle + nod_flag Length of node ptr + keypos Where on page key starts + lastkey Buffer for storing keys to be removed + page_end Pointer to end of page + next_block If <> 0 and node-page, this is set to address of + next page + s_temp Information about what changes was done one the page: + s_temp.key_pos Start of key + s_temp.move_length Number of bytes removed at keypos + s_temp.changed_length Number of bytes changed at keypos + + @todo + The current code doesn't handle the case that the next key may be + packed better against the previous key if there is a case difference + + @return + @retval 0 error + @retval # How many chars was removed +*/ + +static uint remove_key(MARIA_KEYDEF *keyinfo, uint page_flag, uint nod_flag, + uchar *keypos, uchar *lastkey, + uchar *page_end, my_off_t *next_block, + MARIA_KEY_PARAM *s_temp) +{ + int s_length; + uchar *start; + DBUG_ENTER("remove_key"); + DBUG_PRINT("enter", ("keypos: 0x%lx page_end: 0x%lx", + (long) keypos, (long) page_end)); + + start= s_temp->key_pos= keypos; + s_temp->changed_length= 0; + if (!(keyinfo->flag & + (HA_PACK_KEY | HA_SPACE_PACK_USED | HA_VAR_LENGTH_KEY | + HA_BINARY_PACK_KEY)) && + !(page_flag & KEYPAGE_FLAG_HAS_TRANSID)) + { + /* Static length key */ + s_length=(int) (keyinfo->keylength+nod_flag); + if (next_block && nod_flag) + *next_block= _ma_kpos(nod_flag,keypos+s_length); + } + else + { + /* Let keypos point at next key */ + MARIA_KEY key; + + /* Calculate length of key */ + key.keyinfo= keyinfo; + key.data= lastkey; + if (!(*keyinfo->get_key)(&key, page_flag, nod_flag, &keypos)) + DBUG_RETURN(0); /* Error */ + + if (next_block && nod_flag) + *next_block= _ma_kpos(nod_flag,keypos); + s_length=(int) (keypos-start); + if (keypos != page_end) + { + if (keyinfo->flag & HA_BINARY_PACK_KEY) + { + uchar *old_key= start; + uint next_length,prev_length,prev_pack_length; + + /* keypos points here on start of next key */ + get_key_length(next_length,keypos); + get_key_pack_length(prev_length,prev_pack_length,old_key); + if (next_length > prev_length) + { + uint diff= (next_length-prev_length); + /* We have to copy data from the current key to the next key */ + keypos-= diff + prev_pack_length; + store_key_length(keypos, prev_length); + bmove(keypos + prev_pack_length, lastkey + prev_length, diff); + s_length=(int) (keypos-start); + s_temp->changed_length= diff + prev_pack_length; + } + } + else + { + /* Check if a variable length first key part */ + if ((keyinfo->seg->flag & HA_PACK_KEY) && *keypos & 128) + { + /* Next key is packed against the current one */ + uint next_length,prev_length,prev_pack_length,lastkey_length, + rest_length; + if (keyinfo->seg[0].length >= 127) + { + if (!(prev_length=mi_uint2korr(start) & 32767)) + goto end; + next_length=mi_uint2korr(keypos) & 32767; + keypos+=2; + prev_pack_length=2; + } + else + { + if (!(prev_length= *start & 127)) + goto end; /* Same key as previous*/ + next_length= *keypos & 127; + keypos++; + prev_pack_length=1; + } + if (!(*start & 128)) + prev_length=0; /* prev key not packed */ + if (keyinfo->seg[0].flag & HA_NULL_PART) + lastkey++; /* Skip null marker */ + get_key_length(lastkey_length,lastkey); + if (!next_length) /* Same key after */ + { + next_length=lastkey_length; + rest_length=0; + } + else + get_key_length(rest_length,keypos); + + if (next_length >= prev_length) + { + /* Next key is based on deleted key */ + uint pack_length; + uint diff= (next_length-prev_length); + + /* keypos points to data of next key (after key length) */ + bmove(keypos - diff, lastkey + prev_length, diff); + rest_length+= diff; + pack_length= prev_length ? get_pack_length(rest_length): 0; + keypos-= diff + pack_length + prev_pack_length; + s_length=(int) (keypos-start); + if (prev_length) /* Pack against prev key */ + { + *keypos++= start[0]; + if (prev_pack_length == 2) + *keypos++= start[1]; + store_key_length(keypos,rest_length); + } + else + { + /* Next key is not packed anymore */ + if (keyinfo->seg[0].flag & HA_NULL_PART) + { + rest_length++; /* Mark not null */ + } + if (prev_pack_length == 2) + { + mi_int2store(keypos,rest_length); + } + else + *keypos= rest_length; + } + s_temp->changed_length= diff + pack_length + prev_pack_length; + } + } + } + } + } + end: + bmove(start, start+s_length, (uint) (page_end-start-s_length)); + s_temp->move_length= s_length; + DBUG_RETURN((uint) s_length); +} /* remove_key */ + + +/**************************************************************************** + Logging of redos +****************************************************************************/ + +/** + @brief log entry where some parts are deleted and some things are changed + + @fn _ma_log_delete() + @param info Maria handler + @param page Pageaddress for changed page + @param buff Page buffer + @param key_pos Start of change area + @param changed_length How many bytes where changed at key_pos + @param move_length How many bytes where deleted at key_pos + +*/ + +my_bool _ma_log_delete(MARIA_PAGE *ma_page, const uchar *key_pos, + uint changed_length, uint move_length) +{ + LSN lsn; + uchar log_data[FILEID_STORE_SIZE + PAGE_STORE_SIZE + 9 + 7], *log_pos; + LEX_CUSTRING log_array[TRANSLOG_INTERNAL_PARTS + 3]; + uint translog_parts; + uint offset= (uint) (key_pos - ma_page->buff); + MARIA_HA *info= ma_page->info; + MARIA_SHARE *share= info->s; + my_off_t page; + DBUG_ENTER("_ma_log_delete"); + DBUG_PRINT("enter", ("page: %lu changed_length: %u move_length: %d", + (ulong) ma_page->pos, changed_length, move_length)); + DBUG_ASSERT(share->now_transactional && move_length); + DBUG_ASSERT(offset + changed_length <= ma_page->size); + + /* Store address of new root page */ + page= ma_page->pos / share->block_size; + page_store(log_data + FILEID_STORE_SIZE, page); + log_pos= log_data+ FILEID_STORE_SIZE + PAGE_STORE_SIZE; + log_pos[0]= KEY_OP_OFFSET; + int2store(log_pos+1, offset); + log_pos[3]= KEY_OP_SHIFT; + int2store(log_pos+4, -(int) move_length); + log_pos+= 6; + translog_parts= 1; + if (changed_length) + { + log_pos[0]= KEY_OP_CHANGE; + int2store(log_pos+1, changed_length); + log_pos+= 3; + translog_parts= 2; + log_array[TRANSLOG_INTERNAL_PARTS + 1].str= ma_page->buff + offset; + log_array[TRANSLOG_INTERNAL_PARTS + 1].length= changed_length; + } + +#ifdef EXTRA_DEBUG_KEY_CHANGES + { + int page_length= ma_page->size; + ha_checksum crc; + crc= my_checksum(0, ma_page->buff + LSN_STORE_SIZE, + page_length - LSN_STORE_SIZE); + log_pos[0]= KEY_OP_CHECK; + int2store(log_pos+1, page_length); + int4store(log_pos+3, crc); + + log_array[TRANSLOG_INTERNAL_PARTS + translog_parts].str= log_pos; + log_array[TRANSLOG_INTERNAL_PARTS + translog_parts].length= 7; + changed_length+= 7; + translog_parts++; + } +#endif + + log_array[TRANSLOG_INTERNAL_PARTS + 0].str= log_data; + log_array[TRANSLOG_INTERNAL_PARTS + 0].length= (uint) (log_pos - log_data); + + if (translog_write_record(&lsn, LOGREC_REDO_INDEX, + info->trn, info, + (translog_size_t) + log_array[TRANSLOG_INTERNAL_PARTS + 0].length + + changed_length, + TRANSLOG_INTERNAL_PARTS + translog_parts, + log_array, log_data, NULL)) + DBUG_RETURN(1); + DBUG_RETURN(0); +} + + +/**************************************************************************** + Logging of undos +****************************************************************************/ + +my_bool _ma_write_undo_key_delete(MARIA_HA *info, const MARIA_KEY *key, + my_off_t new_root, LSN *res_lsn) +{ + MARIA_SHARE *share= info->s; + uchar log_data[LSN_STORE_SIZE + FILEID_STORE_SIZE + + KEY_NR_STORE_SIZE + PAGE_STORE_SIZE], *log_pos; + LEX_CUSTRING log_array[TRANSLOG_INTERNAL_PARTS + 2]; + struct st_msg_to_write_hook_for_undo_key msg; + enum translog_record_type log_type= LOGREC_UNDO_KEY_DELETE; + uint keynr= key->keyinfo->key_nr; + + lsn_store(log_data, info->trn->undo_lsn); + key_nr_store(log_data + LSN_STORE_SIZE + FILEID_STORE_SIZE, keynr); + log_pos= log_data + LSN_STORE_SIZE + FILEID_STORE_SIZE + KEY_NR_STORE_SIZE; + + /** + @todo BUG if we had concurrent insert/deletes, reading state's key_root + like this would be unsafe. + */ + if (new_root != share->state.key_root[keynr]) + { + my_off_t page; + page= ((new_root == HA_OFFSET_ERROR) ? IMPOSSIBLE_PAGE_NO : + new_root / share->block_size); + page_store(log_pos, page); + log_pos+= PAGE_STORE_SIZE; + log_type= LOGREC_UNDO_KEY_DELETE_WITH_ROOT; + } + + log_array[TRANSLOG_INTERNAL_PARTS + 0].str= log_data; + log_array[TRANSLOG_INTERNAL_PARTS + 0].length= (uint) (log_pos - log_data); + log_array[TRANSLOG_INTERNAL_PARTS + 1].str= key->data; + log_array[TRANSLOG_INTERNAL_PARTS + 1].length= (key->data_length + + key->ref_length); + + msg.root= &share->state.key_root[keynr]; + msg.value= new_root; + /* + set autoincrement to 1 if this is an auto_increment key + This is only used if we are now in a rollback of a duplicate key + */ + msg.auto_increment= share->base.auto_key == keynr + 1; + + return translog_write_record(res_lsn, log_type, + info->trn, info, + (translog_size_t) + (log_array[TRANSLOG_INTERNAL_PARTS + 0].length + + log_array[TRANSLOG_INTERNAL_PARTS + 1].length), + TRANSLOG_INTERNAL_PARTS + 2, log_array, + log_data + LSN_STORE_SIZE, &msg) ? -1 : 0; +} diff --git a/storage/maria/ma_delete_all.c b/storage/maria/ma_delete_all.c new file mode 100644 index 00000000000..4661ea0ab59 --- /dev/null +++ b/storage/maria/ma_delete_all.c @@ -0,0 +1,192 @@ +/* Copyright (C) 2006 MySQL AB & MySQL Finland AB & TCX DataKonsult 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 */ + +/* Remove all rows from a MARIA table */ +/* This clears the status information and truncates files */ + +#include "maria_def.h" +#include "trnman.h" + +/** + @brief deletes all rows from a table + + @param info Maria handler + + @note It is important that this function does not rely on the state + information, as it may be called by ma_apply_undo_bulk_insert() on an + inconsistent table left by a crash. + + @return Operation status + @retval 0 ok + @retval 1 error +*/ + +int maria_delete_all_rows(MARIA_HA *info) +{ + MARIA_SHARE *share= info->s; + my_bool log_record; + LSN lsn; + DBUG_ENTER("maria_delete_all_rows"); + + if (share->options & HA_OPTION_READ_ONLY_DATA) + { + DBUG_RETURN(my_errno=EACCES); + } + /** + @todo LOCK take X-lock on table here. + When we have versioning, if some other thread is looking at this table, + we cannot shrink the file like this. + */ + if (_ma_readinfo(info,F_WRLCK,1)) + DBUG_RETURN(my_errno); + log_record= share->now_transactional && !share->temporary; + if (_ma_mark_file_changed(info)) + goto err; + + if (log_record) + { + /* + This record will be used by Recovery to finish the deletion if it + crashed. We force it to have a complete history in the log. + */ + LEX_CUSTRING log_array[TRANSLOG_INTERNAL_PARTS + 1]; + uchar log_data[FILEID_STORE_SIZE]; + log_array[TRANSLOG_INTERNAL_PARTS + 0].str= log_data; + log_array[TRANSLOG_INTERNAL_PARTS + 0].length= sizeof(log_data); + if (unlikely(translog_write_record(&lsn, LOGREC_REDO_DELETE_ALL, + info->trn, info, 0, + sizeof(log_array)/sizeof(log_array[0]), + log_array, log_data, NULL) || + translog_flush(lsn))) + goto err; + /* + If we fail in this function after this point, log and table will be + inconsistent. + */ + } + else + { + /* Other branch called function below when writing log record, in hook */ + _ma_reset_status(info); + } + /* Remove old history as the table is now empty for everyone */ + _ma_reset_state(info); + + /* + If we are using delayed keys or if the user has done changes to the tables + since it was locked then there may be key blocks in the page cache. Or + there may be data blocks there. We need to throw them away or they may + re-enter the emptied table or another table later. + */ + +#ifdef HAVE_MMAP + if (share->file_map) + _ma_unmap_file(info); +#endif + + if (_ma_flush_table_files(info, MARIA_FLUSH_DATA|MARIA_FLUSH_INDEX, + FLUSH_IGNORE_CHANGED, FLUSH_IGNORE_CHANGED) || + my_chsize(info->dfile.file, 0, 0, MYF(MY_WME)) || + my_chsize(share->kfile.file, share->base.keystart, 0, MYF(MY_WME))) + goto err; + + if (_ma_initialize_data_file(share, info->dfile.file)) + goto err; + + if (log_record) + { + /* + Because LOGREC_REDO_DELETE_ALL does not operate on pages, it has the + following problem: + delete_all; inserts (redo_insert); all pages get flushed; checkpoint: + the dirty pages list will be empty. In recovery, delete_all is executed, + but redo_insert are skipped (dirty pages list is empty). + To avoid this, we need to set skip_redo_lsn now, and thus need to sync + files. + Also fixes the problem of: + bulk insert; insert; delete_all; crash: + "bulk insert" is skipped (no REDOs), so if "insert" would not be skipped + (if we didn't update skip_redo_lsn below) then "insert" would be tried + and fail, saying that it sees that the first page has to be created + though the inserted row has rownr>0. + */ + my_bool error= _ma_state_info_write(share, + MA_STATE_INFO_WRITE_DONT_MOVE_OFFSET | + MA_STATE_INFO_WRITE_LOCK) || + _ma_update_state_lsns(share, lsn, trnman_get_min_trid(), FALSE, FALSE) || + _ma_sync_table_files(info); + info->trn->rec_lsn= LSN_IMPOSSIBLE; + if (error) + goto err; + } + + VOID(_ma_writeinfo(info,WRITEINFO_UPDATE_KEYFILE)); +#ifdef HAVE_MMAP + /* Map again */ + if (share->file_map) + _ma_dynmap_file(info, (my_off_t) 0); +#endif + allow_break(); /* Allow SIGHUP & SIGINT */ + DBUG_RETURN(0); + +err: + { + int save_errno=my_errno; + VOID(_ma_writeinfo(info,WRITEINFO_UPDATE_KEYFILE)); + info->update|=HA_STATE_WRITTEN; /* Buffer changed */ + allow_break(); /* Allow SIGHUP & SIGINT */ + DBUG_RETURN(my_errno=save_errno); + } +} /* maria_delete_all_rows */ + + +/* + Reset status information + + SYNOPSIS + _ma_reset_status() + maria Maria handler + + DESCRIPTION + Resets data and index file information as if the file would be empty + Files are not touched. +*/ + +void _ma_reset_status(MARIA_HA *info) +{ + MARIA_SHARE *share= info->s; + MARIA_STATE_INFO *state= &share->state; + uint i; + DBUG_ENTER("_ma_reset_status"); + + state->split= 0; + state->state.records= state->state.del= 0; + state->changed= 0; /* File is optimized */ + state->dellink= HA_OFFSET_ERROR; + state->sortkey= (ushort) ~0; + state->state.key_file_length= share->base.keystart; + state->state.data_file_length= 0; + state->state.empty= state->state.key_empty= 0; + state->state.checksum= 0; + + *info->state= state->state; + + /* Drop the delete key chain. */ + state->key_del= HA_OFFSET_ERROR; + /* Clear all keys */ + for (i=0 ; i < share->base.keys ; i++) + state->key_root[i]= HA_OFFSET_ERROR; + DBUG_VOID_RETURN; +} diff --git a/storage/maria/ma_delete_table.c b/storage/maria/ma_delete_table.c new file mode 100644 index 00000000000..0237bb884c5 --- /dev/null +++ b/storage/maria/ma_delete_table.c @@ -0,0 +1,107 @@ +/* Copyright (C) 2006 MySQL AB & MySQL Finland AB & TCX DataKonsult 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 "ma_fulltext.h" +#include "trnman_public.h" + +/** + @brief drops (deletes) a table + + @param name table's name + + @return Operation status + @retval 0 ok + @retval 1 error +*/ + +int maria_delete_table(const char *name) +{ + char from[FN_REFLEN]; +#ifdef USE_RAID + uint raid_type=0,raid_chunks=0; +#endif + MARIA_HA *info; + myf sync_dir; + DBUG_ENTER("maria_delete_table"); + +#ifdef EXTRA_DEBUG + _ma_check_table_is_closed(name,"delete"); +#endif + /** @todo LOCK take X-lock on table */ + /* + We need to know if this table is transactional. + When built with RAID support, we also need to determine if this table + makes use of the raid feature. If yes, we need to remove all raid + chunks. This is done with my_raid_delete(). Unfortunately it is + necessary to open the table just to check this. We use + 'open_for_repair' to be able to open even a crashed table. If even + this open fails, we assume no raid configuration for this table + and try to remove the normal data file only. This may however + leave the raid chunks behind. + */ + if (!(info= maria_open(name, O_RDONLY, HA_OPEN_FOR_REPAIR))) + { +#ifdef USE_RAID + raid_type= 0; +#endif + sync_dir= 0; + } + else + { +#ifdef USE_RAID + raid_type= info->s->base.raid_type; + raid_chunks= info->s->base.raid_chunks; +#endif + sync_dir= (info->s->now_transactional && !info->s->temporary && + !maria_in_recovery) ? + MY_SYNC_DIR : 0; + maria_close(info); + } + + if (sync_dir) + { + /* + For this log record to be of any use for Recovery, we need the upper + MySQL layer to be crash-safe in DDLs. + For now this record can serve when we apply logs to a backup, so we sync + it. + */ + LSN lsn; + LEX_CUSTRING log_array[TRANSLOG_INTERNAL_PARTS + 1]; + log_array[TRANSLOG_INTERNAL_PARTS + 0].str= (uchar*)name; + log_array[TRANSLOG_INTERNAL_PARTS + 0].length= strlen(name) + 1; + if (unlikely(translog_write_record(&lsn, LOGREC_REDO_DROP_TABLE, + &dummy_transaction_object, NULL, + (translog_size_t) + log_array[TRANSLOG_INTERNAL_PARTS + + 0].length, + sizeof(log_array)/sizeof(log_array[0]), + log_array, NULL, NULL) || + translog_flush(lsn))) + DBUG_RETURN(1); + } + + fn_format(from,name,"",MARIA_NAME_IEXT,MY_UNPACK_FILENAME|MY_APPEND_EXT); + if (my_delete_with_symlink(from, MYF(MY_WME | sync_dir))) + DBUG_RETURN(my_errno); + fn_format(from,name,"",MARIA_NAME_DEXT,MY_UNPACK_FILENAME|MY_APPEND_EXT); +#ifdef USE_RAID + if (raid_type) + DBUG_RETURN(my_raid_delete(from, raid_chunks, MYF(MY_WME | sync_dir)) ? + my_errno : 0); +#endif + DBUG_RETURN(my_delete_with_symlink(from, MYF(MY_WME | sync_dir)) ? + my_errno : 0); +} diff --git a/storage/maria/ma_dynrec.c b/storage/maria/ma_dynrec.c new file mode 100644 index 00000000000..57b76b713f4 --- /dev/null +++ b/storage/maria/ma_dynrec.c @@ -0,0 +1,2042 @@ +/* Copyright (C) 2006 MySQL AB & MySQL Finland AB & TCX DataKonsult 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 */ + +/* + Functions to handle space-packed-records and blobs + + A row may be stored in one or more linked blocks. + The block size is between MARIA_MIN_BLOCK_LENGTH and MARIA_MAX_BLOCK_LENGTH. + Each block is aligned on MARIA_DYN_ALIGN_SIZE. + The reson for the max block size is to not have too many different types + of blocks. For the differnet block types, look at _ma_get_block_info() +*/ + +#include "maria_def.h" + +static my_bool write_dynamic_record(MARIA_HA *info,const uchar *record, + ulong reclength); +static int _ma_find_writepos(MARIA_HA *info,ulong reclength,my_off_t *filepos, + ulong *length); +static my_bool update_dynamic_record(MARIA_HA *info, MARIA_RECORD_POS filepos, + uchar *record, ulong reclength); +static my_bool delete_dynamic_record(MARIA_HA *info,MARIA_RECORD_POS filepos, + uint second_read); +static my_bool _ma_cmp_buffer(File file, const uchar *buff, my_off_t filepos, + uint length); + +#ifdef THREAD +/* Play it safe; We have a small stack when using threads */ +#undef my_alloca +#undef my_afree +#define my_alloca(A) my_malloc((A),MYF(0)) +#define my_afree(A) my_free((A),MYF(0)) +#endif + + /* Interface function from MARIA_HA */ + +#ifdef HAVE_MMAP + +/* + Create mmaped area for MARIA handler + + SYNOPSIS + _ma_dynmap_file() + info MARIA handler + + RETURN + 0 ok + 1 error. +*/ + +my_bool _ma_dynmap_file(MARIA_HA *info, my_off_t size) +{ + DBUG_ENTER("_ma_dynmap_file"); + if (size > (my_off_t) (~((size_t) 0)) - MEMMAP_EXTRA_MARGIN) + { + DBUG_PRINT("warning", ("File is too large for mmap")); + DBUG_RETURN(1); + } + /* + Ingo wonders if it is good to use MAP_NORESERVE. From the Linux man page: + MAP_NORESERVE + Do not reserve swap space for this mapping. When swap space is + reserved, one has the guarantee that it is possible to modify the + mapping. When swap space is not reserved one might get SIGSEGV + upon a write if no physical memory is available. + */ + info->s->file_map= (uchar*) + my_mmap(0, (size_t)(size + MEMMAP_EXTRA_MARGIN), + info->s->mode==O_RDONLY ? PROT_READ : + PROT_READ | PROT_WRITE, + MAP_SHARED | MAP_NORESERVE, + info->dfile.file, 0L); + if (info->s->file_map == (uchar*) MAP_FAILED) + { + info->s->file_map= NULL; + DBUG_RETURN(1); + } +#if defined(HAVE_MADVISE) + madvise((char*) info->s->file_map, size, MADV_RANDOM); +#endif + info->s->mmaped_length= size; + DBUG_RETURN(0); +} + + +/* + Resize mmaped area for MARIA handler + + SYNOPSIS + _ma_remap_file() + info MARIA handler + + RETURN +*/ + +void _ma_remap_file(MARIA_HA *info, my_off_t size) +{ + if (info->s->file_map) + { + VOID(my_munmap((char*) info->s->file_map, + (size_t) info->s->mmaped_length + MEMMAP_EXTRA_MARGIN)); + _ma_dynmap_file(info, size); + } +} +#endif + + +/* + Read bytes from MySAM handler, using mmap or pread + + SYNOPSIS + _ma_mmap_pread() + info MARIA handler + Buffer Input buffer + Count Count of bytes for read + offset Start position + MyFlags + + RETURN + 0 ok +*/ + +size_t _ma_mmap_pread(MARIA_HA *info, uchar *Buffer, + size_t Count, my_off_t offset, myf MyFlags) +{ + DBUG_PRINT("info", ("maria_read with mmap %d\n", info->dfile.file)); + if (info->s->lock_key_trees) + rw_rdlock(&info->s->mmap_lock); + + /* + The following test may fail in the following cases: + - We failed to remap a memory area (fragmented memory?) + - This thread has done some writes, but not yet extended the + memory mapped area. + */ + + if (info->s->mmaped_length >= offset + Count) + { + memcpy(Buffer, info->s->file_map + offset, Count); + if (info->s->lock_key_trees) + rw_unlock(&info->s->mmap_lock); + return 0; + } + else + { + if (info->s->lock_key_trees) + rw_unlock(&info->s->mmap_lock); + return my_pread(info->dfile.file, Buffer, Count, offset, MyFlags); + } +} + + + /* wrapper for my_pread in case if mmap isn't used */ + +size_t _ma_nommap_pread(MARIA_HA *info, uchar *Buffer, + size_t Count, my_off_t offset, myf MyFlags) +{ + return my_pread(info->dfile.file, Buffer, Count, offset, MyFlags); +} + + +/* + Write bytes to MySAM handler, using mmap or pwrite + + SYNOPSIS + _ma_mmap_pwrite() + info MARIA handler + Buffer Output buffer + Count Count of bytes for write + offset Start position + MyFlags + + RETURN + 0 ok + !=0 error. In this case return error from pwrite +*/ + +size_t _ma_mmap_pwrite(MARIA_HA *info, const uchar *Buffer, + size_t Count, my_off_t offset, myf MyFlags) +{ + DBUG_PRINT("info", ("maria_write with mmap %d\n", info->dfile.file)); + if (info->s->lock_key_trees) + rw_rdlock(&info->s->mmap_lock); + + /* + The following test may fail in the following cases: + - We failed to remap a memory area (fragmented memory?) + - This thread has done some writes, but not yet extended the + memory mapped area. + */ + + if (info->s->mmaped_length >= offset + Count) + { + memcpy(info->s->file_map + offset, Buffer, Count); + if (info->s->lock_key_trees) + rw_unlock(&info->s->mmap_lock); + return 0; + } + else + { + info->s->nonmmaped_inserts++; + if (info->s->lock_key_trees) + rw_unlock(&info->s->mmap_lock); + return my_pwrite(info->dfile.file, Buffer, Count, offset, MyFlags); + } + +} + + + /* wrapper for my_pwrite in case if mmap isn't used */ + +size_t _ma_nommap_pwrite(MARIA_HA *info, const uchar *Buffer, + size_t Count, my_off_t offset, myf MyFlags) +{ + return my_pwrite(info->dfile.file, Buffer, Count, offset, MyFlags); +} + + +my_bool _ma_write_dynamic_record(MARIA_HA *info, const uchar *record) +{ + ulong reclength= _ma_rec_pack(info,info->rec_buff + MARIA_REC_BUFF_OFFSET, + record); + return (write_dynamic_record(info,info->rec_buff + MARIA_REC_BUFF_OFFSET, + reclength)); +} + +my_bool _ma_update_dynamic_record(MARIA_HA *info, MARIA_RECORD_POS pos, + const uchar *oldrec __attribute__ ((unused)), + const uchar *record) +{ + uint length= _ma_rec_pack(info, info->rec_buff + MARIA_REC_BUFF_OFFSET, + record); + return (update_dynamic_record(info, pos, + info->rec_buff + MARIA_REC_BUFF_OFFSET, + length)); +} + + +my_bool _ma_write_blob_record(MARIA_HA *info, const uchar *record) +{ + uchar *rec_buff; + int error; + ulong reclength,reclength2,extra; + + extra= (ALIGN_SIZE(MARIA_MAX_DYN_BLOCK_HEADER)+MARIA_SPLIT_LENGTH+ + MARIA_DYN_DELETE_BLOCK_HEADER+1); + reclength= (info->s->base.pack_reclength + + _ma_calc_total_blob_length(info,record)+ extra); + if (!(rec_buff=(uchar*) my_alloca(reclength))) + { + my_errno= HA_ERR_OUT_OF_MEM; /* purecov: inspected */ + return(1); + } + reclength2= _ma_rec_pack(info, + rec_buff+ALIGN_SIZE(MARIA_MAX_DYN_BLOCK_HEADER), + record); + DBUG_PRINT("info",("reclength: %lu reclength2: %lu", + reclength, reclength2)); + DBUG_ASSERT(reclength2 <= reclength); + error= write_dynamic_record(info, + rec_buff+ALIGN_SIZE(MARIA_MAX_DYN_BLOCK_HEADER), + reclength2); + my_afree(rec_buff); + return(error != 0); +} + + +my_bool _ma_update_blob_record(MARIA_HA *info, MARIA_RECORD_POS pos, + const uchar *oldrec __attribute__ ((unused)), + const uchar *record) +{ + uchar *rec_buff; + int error; + ulong reclength,extra; + + extra= (ALIGN_SIZE(MARIA_MAX_DYN_BLOCK_HEADER)+MARIA_SPLIT_LENGTH+ + MARIA_DYN_DELETE_BLOCK_HEADER); + reclength= (info->s->base.pack_reclength+ + _ma_calc_total_blob_length(info,record)+ extra); +#ifdef NOT_USED /* We now support big rows */ + if (reclength > MARIA_DYN_MAX_ROW_LENGTH) + { + my_errno=HA_ERR_TO_BIG_ROW; + return 1; + } +#endif + if (!(rec_buff=(uchar*) my_alloca(reclength))) + { + my_errno= HA_ERR_OUT_OF_MEM; /* purecov: inspected */ + return(1); + } + reclength= _ma_rec_pack(info,rec_buff+ALIGN_SIZE(MARIA_MAX_DYN_BLOCK_HEADER), + record); + error=update_dynamic_record(info,pos, + rec_buff+ALIGN_SIZE(MARIA_MAX_DYN_BLOCK_HEADER), + reclength); + my_afree(rec_buff); + return(error != 0); +} + + +my_bool _ma_delete_dynamic_record(MARIA_HA *info, + const uchar *record __attribute__ ((unused))) +{ + return delete_dynamic_record(info, info->cur_row.lastpos, 0); +} + + +/** + Write record to data-file. + + @todo it's cheating: it casts "const uchar*" to uchar*. +*/ + +static my_bool write_dynamic_record(MARIA_HA *info, const uchar *record, + ulong reclength) +{ + int flag; + ulong length; + my_off_t filepos; + DBUG_ENTER("write_dynamic_record"); + + flag=0; + + /* + Check if we have enough room for the new record. + First we do simplified check to make usual case faster. + Then we do more precise check for the space left. + Though it still is not absolutely precise, as + we always use MARIA_MAX_DYN_BLOCK_HEADER while it can be + less in the most of the cases. + */ + + if (unlikely(info->s->base.max_data_file_length - + info->state->data_file_length < + reclength + MARIA_MAX_DYN_BLOCK_HEADER)) + { + if (info->s->base.max_data_file_length - info->state->data_file_length + + info->state->empty - info->state->del * MARIA_MAX_DYN_BLOCK_HEADER < + reclength + MARIA_MAX_DYN_BLOCK_HEADER) + { + my_errno=HA_ERR_RECORD_FILE_FULL; + DBUG_RETURN(1); + } + } + + do + { + if (_ma_find_writepos(info,reclength,&filepos,&length)) + goto err; + if (_ma_write_part_record(info,filepos,length, + (info->append_insert_at_end ? + HA_OFFSET_ERROR : info->s->state.dellink), + (uchar**) &record,&reclength,&flag)) + goto err; + } while (reclength); + + DBUG_RETURN(0); +err: + DBUG_RETURN(1); +} + + + /* Get a block for data ; The given data-area must be used !! */ + +static int _ma_find_writepos(MARIA_HA *info, + ulong reclength, /* record length */ + my_off_t *filepos, /* Return file pos */ + ulong *length) /* length of block at filepos */ +{ + MARIA_BLOCK_INFO block_info; + ulong tmp; + DBUG_ENTER("_ma_find_writepos"); + + if (info->s->state.dellink != HA_OFFSET_ERROR && + !info->append_insert_at_end) + { + /* Deleted blocks exists; Get last used block */ + *filepos=info->s->state.dellink; + block_info.second_read=0; + info->rec_cache.seek_not_done=1; + if (!(_ma_get_block_info(&block_info, info->dfile.file, + info->s->state.dellink) & + BLOCK_DELETED)) + { + DBUG_PRINT("error",("Delete link crashed")); + my_errno=HA_ERR_WRONG_IN_RECORD; + DBUG_RETURN(-1); + } + info->s->state.dellink=block_info.next_filepos; + info->state->del--; + info->state->empty-= block_info.block_len; + *length= block_info.block_len; + } + else + { + /* No deleted blocks; Allocate a new block */ + *filepos=info->state->data_file_length; + if ((tmp=reclength+3 + test(reclength >= (65520-3))) < + info->s->base.min_block_length) + tmp= info->s->base.min_block_length; + else + tmp= ((tmp+MARIA_DYN_ALIGN_SIZE-1) & + (~ (ulong) (MARIA_DYN_ALIGN_SIZE-1))); + if (info->state->data_file_length > + (info->s->base.max_data_file_length - tmp)) + { + my_errno=HA_ERR_RECORD_FILE_FULL; + DBUG_RETURN(-1); + } + if (tmp > MARIA_MAX_BLOCK_LENGTH) + tmp=MARIA_MAX_BLOCK_LENGTH; + *length= tmp; + info->state->data_file_length+= tmp; + info->s->state.split++; + info->update|=HA_STATE_WRITE_AT_END; + } + DBUG_RETURN(0); +} /* _ma_find_writepos */ + + + +/* + Unlink a deleted block from the deleted list. + This block will be combined with the preceding or next block to form + a big block. +*/ + +static my_bool unlink_deleted_block(MARIA_HA *info, + MARIA_BLOCK_INFO *block_info) +{ + DBUG_ENTER("unlink_deleted_block"); + if (block_info->filepos == info->s->state.dellink) + { + /* First deleted block; We can just use this ! */ + info->s->state.dellink=block_info->next_filepos; + } + else + { + MARIA_BLOCK_INFO tmp; + tmp.second_read=0; + /* Unlink block from the previous block */ + if (!(_ma_get_block_info(&tmp, info->dfile.file, block_info->prev_filepos) + & BLOCK_DELETED)) + DBUG_RETURN(1); /* Something is wrong */ + mi_sizestore(tmp.header+4,block_info->next_filepos); + if (info->s->file_write(info, tmp.header+4,8, + block_info->prev_filepos+4, MYF(MY_NABP))) + DBUG_RETURN(1); + /* Unlink block from next block */ + if (block_info->next_filepos != HA_OFFSET_ERROR) + { + if (!(_ma_get_block_info(&tmp, info->dfile.file, + block_info->next_filepos) + & BLOCK_DELETED)) + DBUG_RETURN(1); /* Something is wrong */ + mi_sizestore(tmp.header+12,block_info->prev_filepos); + if (info->s->file_write(info, tmp.header+12,8, + block_info->next_filepos+12, + MYF(MY_NABP))) + DBUG_RETURN(1); + } + } + /* We now have one less deleted block */ + info->state->del--; + info->state->empty-= block_info->block_len; + info->s->state.split--; + + /* + If this was a block that we where accessing through table scan + (maria_rrnd() or maria_scan(), then ensure that we skip over this block + when doing next maria_rrnd() or maria_scan(). + */ + if (info->cur_row.nextpos == block_info->filepos) + info->cur_row.nextpos+= block_info->block_len; + DBUG_RETURN(0); +} + + +/* + Add a backward link to delete block + + SYNOPSIS + update_backward_delete_link() + info MARIA handler + delete_block Position to delete block to update. + If this is 'HA_OFFSET_ERROR', nothing will be done + filepos Position to block that 'delete_block' should point to + + RETURN + 0 ok + 1 error. In this case my_error is set. +*/ + +static my_bool update_backward_delete_link(MARIA_HA *info, + my_off_t delete_block, + MARIA_RECORD_POS filepos) +{ + MARIA_BLOCK_INFO block_info; + DBUG_ENTER("update_backward_delete_link"); + + if (delete_block != HA_OFFSET_ERROR) + { + block_info.second_read=0; + if (_ma_get_block_info(&block_info, info->dfile.file, delete_block) + & BLOCK_DELETED) + { + uchar buff[8]; + mi_sizestore(buff,filepos); + if (info->s->file_write(info,buff, 8, delete_block+12, MYF(MY_NABP))) + DBUG_RETURN(1); /* Error on write */ + } + else + { + my_errno=HA_ERR_WRONG_IN_RECORD; + DBUG_RETURN(1); /* Wrong delete link */ + } + } + DBUG_RETURN(0); +} + +/* Delete datarecord from database */ +/* info->rec_cache.seek_not_done is updated in cmp_record */ + +static my_bool delete_dynamic_record(MARIA_HA *info, MARIA_RECORD_POS filepos, + uint second_read) +{ + uint length,b_type; + MARIA_BLOCK_INFO block_info,del_block; + int error; + my_bool remove_next_block; + DBUG_ENTER("delete_dynamic_record"); + + /* First add a link from the last block to the new one */ + error= update_backward_delete_link(info, info->s->state.dellink, filepos); + + block_info.second_read=second_read; + do + { + /* Remove block at 'filepos' */ + if ((b_type= _ma_get_block_info(&block_info, info->dfile.file, filepos)) + & (BLOCK_DELETED | BLOCK_ERROR | BLOCK_SYNC_ERROR | + BLOCK_FATAL_ERROR) || + (length=(uint) (block_info.filepos-filepos) +block_info.block_len) < + MARIA_MIN_BLOCK_LENGTH) + { + my_errno=HA_ERR_WRONG_IN_RECORD; + DBUG_RETURN(1); + } + /* Check if next block is a delete block */ + del_block.second_read=0; + remove_next_block=0; + if (_ma_get_block_info(&del_block, info->dfile.file, filepos + length) & + BLOCK_DELETED && del_block.block_len+length < + MARIA_DYN_MAX_BLOCK_LENGTH) + { + /* We can't remove this yet as this block may be the head block */ + remove_next_block=1; + length+=del_block.block_len; + } + + block_info.header[0]=0; + mi_int3store(block_info.header+1,length); + mi_sizestore(block_info.header+4,info->s->state.dellink); + if (b_type & BLOCK_LAST) + bfill(block_info.header+12,8,255); + else + mi_sizestore(block_info.header+12,block_info.next_filepos); + if (info->s->file_write(info, block_info.header, 20, filepos, + MYF(MY_NABP))) + DBUG_RETURN(1); + info->s->state.dellink = filepos; + info->state->del++; + info->state->empty+=length; + filepos=block_info.next_filepos; + + /* Now it's safe to unlink the deleted block directly after this one */ + if (remove_next_block && unlink_deleted_block(info,&del_block)) + error=1; + } while (!(b_type & BLOCK_LAST)); + + DBUG_RETURN(error); +} + + + /* Write a block to datafile */ + +int _ma_write_part_record(MARIA_HA *info, + my_off_t filepos, /* points at empty block */ + ulong length, /* length of block */ + my_off_t next_filepos,/* Next empty block */ + uchar **record, /* pointer to record ptr */ + ulong *reclength, /* length of *record */ + int *flag) /* *flag == 0 if header */ +{ + ulong head_length,res_length,extra_length,long_block,del_length; + uchar *pos,*record_end; + my_off_t next_delete_block; + uchar temp[MARIA_SPLIT_LENGTH+MARIA_DYN_DELETE_BLOCK_HEADER]; + DBUG_ENTER("_ma_write_part_record"); + + next_delete_block=HA_OFFSET_ERROR; + + res_length=extra_length=0; + if (length > *reclength + MARIA_SPLIT_LENGTH) + { /* Splitt big block */ + res_length=MY_ALIGN(length- *reclength - MARIA_EXTEND_BLOCK_LENGTH, + MARIA_DYN_ALIGN_SIZE); + length-= res_length; /* Use this for first part */ + } + long_block= (length < 65520L && *reclength < 65520L) ? 0 : 1; + if (length == *reclength+ 3 + long_block) + { + /* Block is exactly of the right length */ + temp[0]=(uchar) (1+ *flag)+(uchar) long_block; /* Flag is 0 or 6 */ + if (long_block) + { + mi_int3store(temp+1,*reclength); + head_length=4; + } + else + { + mi_int2store(temp+1,*reclength); + head_length=3; + } + } + else if (length-long_block < *reclength+4) + { /* To short block */ + if (next_filepos == HA_OFFSET_ERROR) + next_filepos= (info->s->state.dellink != HA_OFFSET_ERROR && + !info->append_insert_at_end ? + info->s->state.dellink : info->state->data_file_length); + if (*flag == 0) /* First block */ + { + if (*reclength > MARIA_MAX_BLOCK_LENGTH) + { + head_length= 16; + temp[0]=13; + mi_int4store(temp+1,*reclength); + mi_int3store(temp+5,length-head_length); + mi_sizestore(temp+8,next_filepos); + } + else + { + head_length=5+8+long_block*2; + temp[0]=5+(uchar) long_block; + if (long_block) + { + mi_int3store(temp+1,*reclength); + mi_int3store(temp+4,length-head_length); + mi_sizestore(temp+7,next_filepos); + } + else + { + mi_int2store(temp+1,*reclength); + mi_int2store(temp+3,length-head_length); + mi_sizestore(temp+5,next_filepos); + } + } + } + else + { + head_length=3+8+long_block; + temp[0]=11+(uchar) long_block; + if (long_block) + { + mi_int3store(temp+1,length-head_length); + mi_sizestore(temp+4,next_filepos); + } + else + { + mi_int2store(temp+1,length-head_length); + mi_sizestore(temp+3,next_filepos); + } + } + } + else + { /* Block with empty info last */ + head_length=4+long_block; + extra_length= length- *reclength-head_length; + temp[0]= (uchar) (3+ *flag)+(uchar) long_block; /* 3,4 or 9,10 */ + if (long_block) + { + mi_int3store(temp+1,*reclength); + temp[4]= (uchar) (extra_length); + } + else + { + mi_int2store(temp+1,*reclength); + temp[3]= (uchar) (extra_length); + } + length= *reclength+head_length; /* Write only what is needed */ + } + DBUG_DUMP("header", temp, head_length); + + /* Make a long block for one write */ + record_end= *record+length-head_length; + del_length=(res_length ? MARIA_DYN_DELETE_BLOCK_HEADER : 0); + bmove((*record-head_length), temp, head_length); + memcpy(temp,record_end,(size_t) (extra_length+del_length)); + bzero(record_end, extra_length); + + if (res_length) + { + /* Check first if we can join this block with the next one */ + MARIA_BLOCK_INFO del_block; + my_off_t next_block=filepos+length+extra_length+res_length; + + del_block.second_read=0; + if (next_block < info->state->data_file_length && + info->s->state.dellink != HA_OFFSET_ERROR) + { + if ((_ma_get_block_info(&del_block, info->dfile.file, next_block) + & BLOCK_DELETED) && + res_length + del_block.block_len < MARIA_DYN_MAX_BLOCK_LENGTH) + { + if (unlink_deleted_block(info,&del_block)) + goto err; + res_length+=del_block.block_len; + } + } + + /* Create a delete link of the last part of the block */ + pos=record_end+extra_length; + pos[0]= '\0'; + mi_int3store(pos+1,res_length); + mi_sizestore(pos+4,info->s->state.dellink); + bfill(pos+12,8,255); /* End link */ + next_delete_block=info->s->state.dellink; + info->s->state.dellink= filepos+length+extra_length; + info->state->del++; + info->state->empty+=res_length; + info->s->state.split++; + } + if (info->opt_flag & WRITE_CACHE_USED && + info->update & HA_STATE_WRITE_AT_END) + { + if (info->update & HA_STATE_EXTEND_BLOCK) + { + info->update&= ~HA_STATE_EXTEND_BLOCK; + if (my_block_write(&info->rec_cache, *record-head_length, + length+extra_length+del_length,filepos)) + goto err; + } + else if (my_b_write(&info->rec_cache, *record-head_length, + length+extra_length+del_length)) + goto err; + } + else + { + info->rec_cache.seek_not_done=1; + if (info->s->file_write(info, *record-head_length, + length+extra_length+ + del_length,filepos,info->s->write_flag)) + goto err; + } + memcpy(record_end,temp,(size_t) (extra_length+del_length)); + *record=record_end; + *reclength-=(length-head_length); + *flag=6; + + if (del_length) + { + /* link the next delete block to this */ + if (update_backward_delete_link(info, next_delete_block, + info->s->state.dellink)) + goto err; + } + + DBUG_RETURN(0); +err: + DBUG_PRINT("exit",("errno: %d",my_errno)); + DBUG_RETURN(1); +} /* _ma_write_part_record */ + + + /* update record from datafile */ + +static my_bool update_dynamic_record(MARIA_HA *info, MARIA_RECORD_POS filepos, + uchar *record, ulong reclength) +{ + int flag; + uint error; + ulong length; + MARIA_BLOCK_INFO block_info; + DBUG_ENTER("update_dynamic_record"); + + flag=block_info.second_read=0; + /* + Check if we have enough room for the record. + First we do simplified check to make usual case faster. + Then we do more precise check for the space left. + Though it still is not absolutely precise, as + we always use MARIA_MAX_DYN_BLOCK_HEADER while it can be + less in the most of the cases. + */ + + /* + compare with just the reclength as we're going + to get some space from the old replaced record + */ + if (unlikely(info->s->base.max_data_file_length - + info->state->data_file_length < reclength)) + { + /* If new record isn't longer, we can go on safely */ + if (info->cur_row.total_length < reclength) + { + if (info->s->base.max_data_file_length - info->state->data_file_length + + info->state->empty - info->state->del * MARIA_MAX_DYN_BLOCK_HEADER < + reclength - info->cur_row.total_length + MARIA_MAX_DYN_BLOCK_HEADER) + { + my_errno=HA_ERR_RECORD_FILE_FULL; + goto err; + } + } + } + /* Remember length for updated row if it's updated again */ + info->cur_row.total_length= reclength; + + while (reclength > 0) + { + if (filepos != info->s->state.dellink) + { + block_info.next_filepos= HA_OFFSET_ERROR; + if ((error= _ma_get_block_info(&block_info, info->dfile.file, filepos)) + & (BLOCK_DELETED | BLOCK_ERROR | BLOCK_SYNC_ERROR | + BLOCK_FATAL_ERROR)) + { + DBUG_PRINT("error",("Got wrong block info")); + if (!(error & BLOCK_FATAL_ERROR)) + my_errno=HA_ERR_WRONG_IN_RECORD; + goto err; + } + length=(ulong) (block_info.filepos-filepos) + block_info.block_len; + if (length < reclength) + { + uint tmp=MY_ALIGN(reclength - length + 3 + + test(reclength >= 65520L),MARIA_DYN_ALIGN_SIZE); + /* Don't create a block bigger than MARIA_MAX_BLOCK_LENGTH */ + tmp= min(length+tmp, MARIA_MAX_BLOCK_LENGTH)-length; + /* Check if we can extend this block */ + if (block_info.filepos + block_info.block_len == + info->state->data_file_length && + info->state->data_file_length < + info->s->base.max_data_file_length-tmp) + { + /* extend file */ + DBUG_PRINT("info",("Extending file with %d bytes",tmp)); + if (info->cur_row.nextpos == info->state->data_file_length) + info->cur_row.nextpos+= tmp; + info->state->data_file_length+= tmp; + info->update|= HA_STATE_WRITE_AT_END | HA_STATE_EXTEND_BLOCK; + length+=tmp; + } + else if (length < MARIA_MAX_BLOCK_LENGTH - MARIA_MIN_BLOCK_LENGTH) + { + /* + Check if next block is a deleted block + Above we have MARIA_MIN_BLOCK_LENGTH to avoid the problem where + the next block is so small it can't be splited which could + casue problems + */ + + MARIA_BLOCK_INFO del_block; + del_block.second_read=0; + if (_ma_get_block_info(&del_block, info->dfile.file, + block_info.filepos + block_info.block_len) & + BLOCK_DELETED) + { + /* Use; Unlink it and extend the current block */ + DBUG_PRINT("info",("Extending current block")); + if (unlink_deleted_block(info,&del_block)) + goto err; + if ((length+=del_block.block_len) > MARIA_MAX_BLOCK_LENGTH) + { + /* + New block was too big, link overflow part back to + delete list + */ + my_off_t next_pos; + ulong rest_length= length-MARIA_MAX_BLOCK_LENGTH; + set_if_bigger(rest_length, MARIA_MIN_BLOCK_LENGTH); + next_pos= del_block.filepos+ del_block.block_len - rest_length; + + if (update_backward_delete_link(info, info->s->state.dellink, + next_pos)) + DBUG_RETURN(1); + + /* create delete link for data that didn't fit into the page */ + del_block.header[0]=0; + mi_int3store(del_block.header+1, rest_length); + mi_sizestore(del_block.header+4,info->s->state.dellink); + bfill(del_block.header+12,8,255); + if (info->s->file_write(info, del_block.header, 20, + next_pos, MYF(MY_NABP))) + DBUG_RETURN(1); + info->s->state.dellink= next_pos; + info->s->state.split++; + info->state->del++; + info->state->empty+= rest_length; + length-= rest_length; + } + } + } + } + } + else + { + if (_ma_find_writepos(info,reclength,&filepos,&length)) + goto err; + } + if (_ma_write_part_record(info,filepos,length,block_info.next_filepos, + &record,&reclength,&flag)) + goto err; + if ((filepos=block_info.next_filepos) == HA_OFFSET_ERROR) + { + /* Start writing data on deleted blocks */ + filepos=info->s->state.dellink; + } + } + + if (block_info.next_filepos != HA_OFFSET_ERROR) + if (delete_dynamic_record(info,block_info.next_filepos,1)) + goto err; + + DBUG_RETURN(0); +err: + DBUG_RETURN(1); +} + + + /* Pack a record. Return new reclength */ + +uint _ma_rec_pack(MARIA_HA *info, register uchar *to, + register const uchar *from) +{ + uint length,new_length,flag,bit,i; + const uchar *pos,*end; + uchar *startpos,*packpos; + enum en_fieldtype type; + reg3 MARIA_COLUMNDEF *column; + MARIA_BLOB *blob; + DBUG_ENTER("_ma_rec_pack"); + + flag= 0; + bit= 1; + startpos= packpos=to; + to+= info->s->base.pack_bytes; + blob= info->blobs; + column= info->s->columndef; + if (info->s->base.null_bytes) + { + memcpy(to, from, info->s->base.null_bytes); + from+= info->s->base.null_bytes; + to+= info->s->base.null_bytes; + } + + for (i=info->s->base.fields ; i-- > 0; from+= length, column++) + { + length=(uint) column->length; + if ((type = (enum en_fieldtype) column->type) != FIELD_NORMAL) + { + if (type == FIELD_BLOB) + { + if (!blob->length) + flag|=bit; + else + { + char *temp_pos; + size_t tmp_length=length-portable_sizeof_char_ptr; + memcpy(to,from,tmp_length); + memcpy_fixed(&temp_pos,from+tmp_length,sizeof(char*)); + memcpy(to+tmp_length,temp_pos,(size_t) blob->length); + to+=tmp_length+blob->length; + } + blob++; + } + else if (type == FIELD_SKIP_ZERO) + { + if (memcmp(from, maria_zero_string, length) == 0) + flag|=bit; + else + { + memcpy(to, from, (size_t) length); + to+=length; + } + } + else if (type == FIELD_SKIP_ENDSPACE || + type == FIELD_SKIP_PRESPACE) + { + pos= from; end= from + length; + if (type == FIELD_SKIP_ENDSPACE) + { /* Pack trailing spaces */ + while (end > from && *(end-1) == ' ') + end--; + } + else + { /* Pack pref-spaces */ + while (pos < end && *pos == ' ') + pos++; + } + new_length=(uint) (end-pos); + if (new_length +1 + test(column->length > 255 && new_length > 127) + < length) + { + if (column->length > 255 && new_length > 127) + { + to[0]= (uchar) ((new_length & 127) + 128); + to[1]= (uchar) (new_length >> 7); + to+=2; + } + else + *to++= (uchar) new_length; + memcpy(to, pos, (size_t) new_length); to+=new_length; + flag|=bit; + } + else + { + memcpy(to,from,(size_t) length); to+=length; + } + } + else if (type == FIELD_VARCHAR) + { + uint pack_length= HA_VARCHAR_PACKLENGTH(column->length -1); + uint tmp_length; + if (pack_length == 1) + { + tmp_length= (uint) *from; + *to++= *from; + } + else + { + tmp_length= uint2korr(from); + store_key_length_inc(to,tmp_length); + } + memcpy(to, from+pack_length,tmp_length); + to+= tmp_length; + continue; + } + else + { + memcpy(to,from,(size_t) length); to+=length; + continue; /* Normal field */ + } + if ((bit= bit << 1) >= 256) + { + *packpos++ = (uchar) flag; + bit=1; flag=0; + } + } + else + { + memcpy(to,from,(size_t) length); to+=length; + } + } + if (bit != 1) + *packpos= (uchar) flag; + if (info->s->calc_checksum) + *to++= (uchar) info->cur_row.checksum; + DBUG_PRINT("exit",("packed length: %d",(int) (to-startpos))); + DBUG_RETURN((uint) (to-startpos)); +} /* _ma_rec_pack */ + + + +/* + Check if a record was correctly packed. Used only by maria_chk + Returns 0 if record is ok. +*/ + +my_bool _ma_rec_check(MARIA_HA *info,const uchar *record, uchar *rec_buff, + ulong packed_length, my_bool with_checksum, + ha_checksum checksum) +{ + uint length,new_length,flag,bit,i; + const uchar *pos,*end; + uchar *packpos,*to; + enum en_fieldtype type; + reg3 MARIA_COLUMNDEF *column; + DBUG_ENTER("_ma_rec_check"); + + packpos=rec_buff; to= rec_buff+info->s->base.pack_bytes; + column= info->s->columndef; + flag= *packpos; bit=1; + record+= info->s->base.null_bytes; + to+= info->s->base.null_bytes; + + for (i=info->s->base.fields ; i-- > 0; record+= length, column++) + { + length=(uint) column->length; + if ((type = (enum en_fieldtype) column->type) != FIELD_NORMAL) + { + if (type == FIELD_BLOB) + { + uint blob_length= + _ma_calc_blob_length(length-portable_sizeof_char_ptr,record); + if (!blob_length && !(flag & bit)) + goto err; + if (blob_length) + to+=length - portable_sizeof_char_ptr+ blob_length; + } + else if (type == FIELD_SKIP_ZERO) + { + if (memcmp(record, maria_zero_string, length) == 0) + { + if (!(flag & bit)) + goto err; + } + else + to+=length; + } + else if (type == FIELD_SKIP_ENDSPACE || + type == FIELD_SKIP_PRESPACE) + { + pos= record; end= record + length; + if (type == FIELD_SKIP_ENDSPACE) + { /* Pack trailing spaces */ + while (end > record && *(end-1) == ' ') + end--; + } + else + { /* Pack pre-spaces */ + while (pos < end && *pos == ' ') + pos++; + } + new_length=(uint) (end-pos); + if (new_length +1 + test(column->length > 255 && new_length > 127) + < length) + { + if (!(flag & bit)) + goto err; + if (column->length > 255 && new_length > 127) + { + /* purecov: begin inspected */ + if (to[0] != (uchar) ((new_length & 127) + 128) || + to[1] != (uchar) (new_length >> 7)) + goto err; + to+=2; + /* purecov: end */ + } + else if (*to++ != (uchar) new_length) + goto err; + to+=new_length; + } + else + to+=length; + } + else if (type == FIELD_VARCHAR) + { + uint pack_length= HA_VARCHAR_PACKLENGTH(column->length -1); + uint tmp_length; + if (pack_length == 1) + { + tmp_length= (uint) *record; + to+= 1+ tmp_length; + continue; + } + else + { + tmp_length= uint2korr(record); + to+= get_pack_length(tmp_length)+tmp_length; + } + continue; + } + else + { + to+=length; + continue; /* Normal field */ + } + if ((bit= bit << 1) >= 256) + { + flag= *++packpos; + bit=1; + } + } + else + to+= length; + } + if (packed_length != (uint) (to - rec_buff) + + test(info->s->calc_checksum) || (bit != 1 && (flag & ~(bit - 1)))) + goto err; + if (with_checksum && ((uchar) checksum != (uchar) *to)) + { + DBUG_PRINT("error",("wrong checksum for row")); + goto err; + } + DBUG_RETURN(0); + +err: + DBUG_RETURN(1); +} + + +/* + @brief Unpacks a record + + @return Recordlength + @retval >0 ok + @retval MY_FILE_ERROR (== -1) Error. + my_errno is set to HA_ERR_WRONG_IN_RECORD +*/ + +ulong _ma_rec_unpack(register MARIA_HA *info, register uchar *to, uchar *from, + ulong found_length) +{ + uint flag,bit,length,min_pack_length, column_length; + enum en_fieldtype type; + uchar *from_end,*to_end,*packpos; + reg3 MARIA_COLUMNDEF *column, *end_column; + DBUG_ENTER("_ma_rec_unpack"); + + to_end=to + info->s->base.reclength; + from_end=from+found_length; + flag= (uchar) *from; bit=1; packpos=from; + if (found_length < info->s->base.min_pack_length) + goto err; + from+= info->s->base.pack_bytes; + min_pack_length= info->s->base.min_pack_length - info->s->base.pack_bytes; + + if ((length= info->s->base.null_bytes)) + { + memcpy(to, from, length); + from+= length; + to+= length; + min_pack_length-= length; + } + + for (column= info->s->columndef, end_column= column + info->s->base.fields; + column < end_column ; to+= column_length, column++) + { + column_length= column->length; + if ((type = (enum en_fieldtype) column->type) != FIELD_NORMAL && + (type != FIELD_CHECK)) + { + if (type == FIELD_VARCHAR) + { + uint pack_length= HA_VARCHAR_PACKLENGTH(column_length-1); + if (pack_length == 1) + { + length= (uint) *(uchar*) from; + if (length > column_length-1) + goto err; + *to= *from++; + } + else + { + get_key_length(length, from); + if (length > column_length-2) + goto err; + int2store(to,length); + } + if (from+length > from_end) + goto err; + memcpy(to+pack_length, from, length); + from+= length; + min_pack_length--; + continue; + } + if (flag & bit) + { + if (type == FIELD_BLOB || type == FIELD_SKIP_ZERO) + bzero(to, column_length); + else if (type == FIELD_SKIP_ENDSPACE || + type == FIELD_SKIP_PRESPACE) + { + if (column->length > 255 && *from & 128) + { + if (from + 1 >= from_end) + goto err; + length= (*from & 127)+ ((uint) (uchar) *(from+1) << 7); from+=2; + } + else + { + if (from == from_end) + goto err; + length= (uchar) *from++; + } + min_pack_length--; + if (length >= column_length || + min_pack_length + length > (uint) (from_end - from)) + goto err; + if (type == FIELD_SKIP_ENDSPACE) + { + memcpy(to, from, (size_t) length); + bfill(to+length, column_length-length, ' '); + } + else + { + bfill(to, column_length-length, ' '); + memcpy(to+column_length-length, from, (size_t) length); + } + from+=length; + } + } + else if (type == FIELD_BLOB) + { + uint size_length=column_length- portable_sizeof_char_ptr; + ulong blob_length= _ma_calc_blob_length(size_length,from); + ulong from_left= (ulong) (from_end - from); + if (from_left < size_length || + from_left - size_length < blob_length || + from_left - size_length - blob_length < min_pack_length) + goto err; + memcpy(to, from, (size_t) size_length); + from+=size_length; + memcpy_fixed(to+size_length,(uchar*) &from,sizeof(char*)); + from+=blob_length; + } + else + { + if (type == FIELD_SKIP_ENDSPACE || type == FIELD_SKIP_PRESPACE) + min_pack_length--; + if (min_pack_length + column_length > (uint) (from_end - from)) + goto err; + memcpy(to, from, (size_t) column_length); from+=column_length; + } + if ((bit= bit << 1) >= 256) + { + flag= (uchar) *++packpos; bit=1; + } + } + else + { + if (min_pack_length > (uint) (from_end - from)) + goto err; + min_pack_length-=column_length; + memcpy(to, from, (size_t) column_length); + from+=column_length; + } + } + if (info->s->calc_checksum) + info->cur_row.checksum= (uint) (uchar) *from++; + if (to == to_end && from == from_end && (bit == 1 || !(flag & ~(bit-1)))) + DBUG_RETURN(found_length); + +err: + my_errno= HA_ERR_WRONG_IN_RECORD; + DBUG_PRINT("error",("to_end: 0x%lx -> 0x%lx from_end: 0x%lx -> 0x%lx", + (long) to, (long) to_end, (long) from, (long) from_end)); + DBUG_DUMP("from", info->rec_buff, info->s->base.min_pack_length); + DBUG_RETURN(MY_FILE_ERROR); +} /* _ma_rec_unpack */ + + + /* Calc length of blob. Update info in blobs->length */ + +ulong _ma_calc_total_blob_length(MARIA_HA *info, const uchar *record) +{ + ulong length; + MARIA_BLOB *blob,*end; + + for (length=0, blob= info->blobs, end=blob+info->s->base.blobs ; + blob != end; + blob++) + { + blob->length= _ma_calc_blob_length(blob->pack_length, + record + blob->offset); + length+=blob->length; + } + return length; +} + + +ulong _ma_calc_blob_length(uint length, const uchar *pos) +{ + switch (length) { + case 1: + return (uint) (uchar) *pos; + case 2: + return (uint) uint2korr(pos); + case 3: + return uint3korr(pos); + case 4: + return uint4korr(pos); + default: + break; + } + return 0; /* Impossible */ +} + + +void _ma_store_blob_length(uchar *pos,uint pack_length,uint length) +{ + switch (pack_length) { + case 1: + *pos= (uchar) length; + break; + case 2: + int2store(pos,length); + break; + case 3: + int3store(pos,length); + break; + case 4: + int4store(pos,length); + default: + break; + } + return; +} + + +/* + Read record from datafile. + + SYNOPSIS + _ma_read_dynamic_record() + info MARIA_HA pointer to table. + filepos From where to read the record. + buf Destination for record. + + NOTE + If a write buffer is active, it needs to be flushed if its contents + intersects with the record to read. We always check if the position + of the first uchar of the write buffer is lower than the position + past the last uchar to read. In theory this is also true if the write + buffer is completely below the read segment. That is, if there is no + intersection. But this case is unusual. We flush anyway. Only if the + first uchar in the write buffer is above the last uchar to read, we do + not flush. + + A dynamic record may need several reads. So this check must be done + before every read. Reading a dynamic record starts with reading the + block header. If the record does not fit into the free space of the + header, the block may be longer than the header. In this case a + second read is necessary. These one or two reads repeat for every + part of the record. + + RETURN + 0 OK + # Error number +*/ + +int _ma_read_dynamic_record(MARIA_HA *info, uchar *buf, + MARIA_RECORD_POS filepos) +{ + int block_of_record; + uint b_type; + MARIA_BLOCK_INFO block_info; + File file; + uchar *to; + uint left_length; + DBUG_ENTER("_ma_read_dynamic_record"); + + if (filepos == HA_OFFSET_ERROR) + goto err; + + LINT_INIT(to); + LINT_INIT(left_length); + file= info->dfile.file; + block_of_record= 0; /* First block of record is numbered as zero. */ + block_info.second_read= 0; + do + { + /* A corrupted table can have wrong pointers. (Bug# 19835) */ + if (filepos == HA_OFFSET_ERROR) + goto panic; + if (info->opt_flag & WRITE_CACHE_USED && + (info->rec_cache.pos_in_file < filepos + + MARIA_BLOCK_INFO_HEADER_LENGTH) && + flush_io_cache(&info->rec_cache)) + goto err; + info->rec_cache.seek_not_done=1; + if ((b_type= _ma_get_block_info(&block_info, file, filepos)) & + (BLOCK_DELETED | BLOCK_ERROR | BLOCK_SYNC_ERROR | + BLOCK_FATAL_ERROR)) + { + if (b_type & (BLOCK_SYNC_ERROR | BLOCK_DELETED)) + my_errno=HA_ERR_RECORD_DELETED; + goto err; + } + if (block_of_record++ == 0) /* First block */ + { + info->cur_row.total_length= block_info.rec_len; + if (block_info.rec_len > (uint) info->s->base.max_pack_length) + goto panic; + if (info->s->base.blobs) + { + if (_ma_alloc_buffer(&info->rec_buff, &info->rec_buff_size, + block_info.rec_len + + info->s->base.extra_rec_buff_size)) + goto err; + } + to= info->rec_buff; + left_length=block_info.rec_len; + } + if (left_length < block_info.data_len || ! block_info.data_len) + goto panic; /* Wrong linked record */ + /* copy information that is already read */ + { + uint offset= (uint) (block_info.filepos - filepos); + uint prefetch_len= (sizeof(block_info.header) - offset); + filepos+= sizeof(block_info.header); + + if (prefetch_len > block_info.data_len) + prefetch_len= block_info.data_len; + if (prefetch_len) + { + memcpy(to, block_info.header + offset, prefetch_len); + block_info.data_len-= prefetch_len; + left_length-= prefetch_len; + to+= prefetch_len; + } + } + /* read rest of record from file */ + if (block_info.data_len) + { + if (info->opt_flag & WRITE_CACHE_USED && + info->rec_cache.pos_in_file < filepos + block_info.data_len && + flush_io_cache(&info->rec_cache)) + goto err; + /* + What a pity that this method is not called 'file_pread' and that + there is no equivalent without seeking. We are at the right + position already. :( + */ + if (info->s->file_read(info, to, block_info.data_len, + filepos, MYF(MY_NABP))) + goto panic; + left_length-=block_info.data_len; + to+=block_info.data_len; + } + filepos= block_info.next_filepos; + } while (left_length); + + info->update|= HA_STATE_AKTIV; /* We have a aktive record */ + fast_ma_writeinfo(info); + DBUG_RETURN(_ma_rec_unpack(info,buf,info->rec_buff,block_info.rec_len) != + MY_FILE_ERROR ? 0 : my_errno); + +err: + fast_ma_writeinfo(info); + DBUG_RETURN(my_errno); + +panic: + my_errno=HA_ERR_WRONG_IN_RECORD; + goto err; +} + + /* compare unique constraint between stored rows */ + +my_bool _ma_cmp_dynamic_unique(MARIA_HA *info, MARIA_UNIQUEDEF *def, + const uchar *record, MARIA_RECORD_POS pos) +{ + uchar *old_rec_buff,*old_record; + size_t old_rec_buff_size; + my_bool error; + DBUG_ENTER("_ma_cmp_dynamic_unique"); + + if (!(old_record=my_alloca(info->s->base.reclength))) + DBUG_RETURN(1); + + /* Don't let the compare destroy blobs that may be in use */ + old_rec_buff= info->rec_buff; + old_rec_buff_size= info->rec_buff_size; + + if (info->s->base.blobs) + { + info->rec_buff= 0; + info->rec_buff_size= 0; + } + error= _ma_read_dynamic_record(info, old_record, pos) != 0; + if (!error) + error=_ma_unique_comp(def, record, old_record, def->null_are_equal) != 0; + if (info->s->base.blobs) + { + my_free(info->rec_buff, MYF(MY_ALLOW_ZERO_PTR)); + info->rec_buff= old_rec_buff; + info->rec_buff_size= old_rec_buff_size; + } + my_afree(old_record); + DBUG_RETURN(error); +} + + + /* Compare of record on disk with packed record in memory */ + +my_bool _ma_cmp_dynamic_record(register MARIA_HA *info, + register const uchar *record) +{ + uint flag, reclength, b_type,cmp_length; + my_off_t filepos; + uchar *buffer; + MARIA_BLOCK_INFO block_info; + my_bool error= 1; + DBUG_ENTER("_ma_cmp_dynamic_record"); + + /* We are going to do changes; dont let anybody disturb */ + dont_break(); /* Dont allow SIGHUP or SIGINT */ + + if (info->opt_flag & WRITE_CACHE_USED) + { + info->update&= ~(HA_STATE_WRITE_AT_END | HA_STATE_EXTEND_BLOCK); + if (flush_io_cache(&info->rec_cache)) + DBUG_RETURN(1); + } + info->rec_cache.seek_not_done=1; + + /* If nobody have touched the database we don't have to test rec */ + + buffer=info->rec_buff; + if ((info->opt_flag & READ_CHECK_USED)) + { /* If check isn't disabled */ + if (info->s->base.blobs) + { + if (!(buffer=(uchar*) my_alloca(info->s->base.pack_reclength+ + _ma_calc_total_blob_length(info,record)))) + DBUG_RETURN(1); + } + reclength= _ma_rec_pack(info,buffer,record); + record= buffer; + + filepos= info->cur_row.lastpos; + flag=block_info.second_read=0; + block_info.next_filepos=filepos; + while (reclength > 0) + { + if ((b_type= _ma_get_block_info(&block_info, info->dfile.file, + block_info.next_filepos)) + & (BLOCK_DELETED | BLOCK_ERROR | BLOCK_SYNC_ERROR | + BLOCK_FATAL_ERROR)) + { + if (b_type & (BLOCK_SYNC_ERROR | BLOCK_DELETED)) + my_errno=HA_ERR_RECORD_CHANGED; + goto err; + } + if (flag == 0) /* First block */ + { + flag=1; + if (reclength != block_info.rec_len) + { + my_errno=HA_ERR_RECORD_CHANGED; + goto err; + } + } else if (reclength < block_info.data_len) + { + my_errno=HA_ERR_WRONG_IN_RECORD; + goto err; + } + reclength-= block_info.data_len; + cmp_length= block_info.data_len; + if (!reclength && info->s->calc_checksum) + cmp_length--; /* 'record' may not contain checksum */ + + if (_ma_cmp_buffer(info->dfile.file, record, block_info.filepos, + cmp_length)) + { + my_errno=HA_ERR_RECORD_CHANGED; + goto err; + } + flag=1; + record+=block_info.data_len; + } + } + my_errno=0; + error= 0; +err: + if (buffer != info->rec_buff) + my_afree(buffer); + DBUG_PRINT("exit", ("result: %d", error)); + DBUG_RETURN(error); +} + + + /* Compare file to buffert */ + +static my_bool _ma_cmp_buffer(File file, const uchar *buff, my_off_t filepos, + uint length) +{ + uint next_length; + uchar temp_buff[IO_SIZE*2]; + DBUG_ENTER("_ma_cmp_buffer"); + + next_length= IO_SIZE*2 - (uint) (filepos & (IO_SIZE-1)); + + while (length > IO_SIZE*2) + { + if (my_pread(file,temp_buff,next_length,filepos, MYF(MY_NABP)) || + memcmp(buff, temp_buff, next_length)) + goto err; + filepos+=next_length; + buff+=next_length; + length-= next_length; + next_length=IO_SIZE*2; + } + if (my_pread(file,temp_buff,length,filepos,MYF(MY_NABP))) + goto err; + DBUG_RETURN(memcmp(buff, temp_buff, length) != 0); +err: + DBUG_RETURN(1); +} + + +/* + Read next record from datafile during table scan. + + SYNOPSIS + _ma_read_rnd_dynamic_record() + info MARIA_HA pointer to table. + buf Destination for record. + filepos From where to read the record. + skip_deleted_blocks If to repeat reading until a non-deleted + record is found. + + NOTE + This is identical to _ma_read_dynamic_record(), except the following + cases: + + - If there is no active row at 'filepos', continue scanning for + an active row. (This is becasue the previous + _ma_read_rnd_dynamic_record() call stored the next block position + in filepos, but this position may not be a start block for a row + - We may have READ_CACHING enabled, in which case we use the cache + to read rows. + + For other comments, check _ma_read_dynamic_record() + + RETURN + 0 OK + != 0 Error number +*/ + +int _ma_read_rnd_dynamic_record(MARIA_HA *info, + uchar *buf, + MARIA_RECORD_POS filepos, + my_bool skip_deleted_blocks) +{ + int block_of_record, info_read; + uint left_len,b_type; + uchar *to; + MARIA_BLOCK_INFO block_info; + MARIA_SHARE *share= info->s; + DBUG_ENTER("_ma_read_rnd_dynamic_record"); + + info_read=0; + LINT_INIT(to); + + if (info->lock_type == F_UNLCK) + { +#ifndef UNSAFE_LOCKING +#else + info->tmp_lock_type=F_RDLCK; +#endif + } + else + info_read=1; /* memory-keyinfoblock is ok */ + + block_of_record= 0; /* First block of record is numbered as zero. */ + block_info.second_read= 0; + left_len=1; + do + { + if (filepos >= info->state->data_file_length) + { + if (!info_read) + { /* Check if changed */ + info_read=1; + info->rec_cache.seek_not_done=1; + if (_ma_state_info_read_dsk(share->kfile.file, &share->state)) + goto panic; + } + if (filepos >= info->state->data_file_length) + { + my_errno= HA_ERR_END_OF_FILE; + goto err; + } + } + if (info->opt_flag & READ_CACHE_USED) + { + if (_ma_read_cache(&info->rec_cache, block_info.header, filepos, + sizeof(block_info.header), + (!block_of_record && skip_deleted_blocks ? + READING_NEXT : 0) | READING_HEADER)) + goto panic; + b_type= _ma_get_block_info(&block_info,-1,filepos); + } + else + { + if (info->opt_flag & WRITE_CACHE_USED && + info->rec_cache.pos_in_file < filepos + MARIA_BLOCK_INFO_HEADER_LENGTH && + flush_io_cache(&info->rec_cache)) + DBUG_RETURN(my_errno); + info->rec_cache.seek_not_done=1; + b_type= _ma_get_block_info(&block_info, info->dfile.file, filepos); + } + + if (b_type & (BLOCK_DELETED | BLOCK_ERROR | BLOCK_SYNC_ERROR | + BLOCK_FATAL_ERROR)) + { + if ((b_type & (BLOCK_DELETED | BLOCK_SYNC_ERROR)) + && skip_deleted_blocks) + { + filepos=block_info.filepos+block_info.block_len; + block_info.second_read=0; + continue; /* Search after next_record */ + } + if (b_type & (BLOCK_DELETED | BLOCK_SYNC_ERROR)) + { + my_errno= HA_ERR_RECORD_DELETED; + info->cur_row.lastpos= block_info.filepos; + info->cur_row.nextpos= block_info.filepos+block_info.block_len; + } + goto err; + } + if (block_of_record == 0) /* First block */ + { + info->cur_row.total_length= block_info.rec_len; + if (block_info.rec_len > (uint) share->base.max_pack_length) + goto panic; + info->cur_row.lastpos= filepos; + if (share->base.blobs) + { + if (_ma_alloc_buffer(&info->rec_buff, &info->rec_buff_size, + block_info.rec_len + + info->s->base.extra_rec_buff_size)) + goto err; + } + to= info->rec_buff; + left_len=block_info.rec_len; + } + if (left_len < block_info.data_len) + goto panic; /* Wrong linked record */ + + /* copy information that is already read */ + { + uint offset=(uint) (block_info.filepos - filepos); + uint tmp_length= (sizeof(block_info.header) - offset); + filepos=block_info.filepos; + + if (tmp_length > block_info.data_len) + tmp_length= block_info.data_len; + if (tmp_length) + { + memcpy(to, block_info.header+offset, tmp_length); + block_info.data_len-=tmp_length; + left_len-=tmp_length; + to+=tmp_length; + filepos+=tmp_length; + } + } + /* read rest of record from file */ + if (block_info.data_len) + { + if (info->opt_flag & READ_CACHE_USED) + { + if (_ma_read_cache(&info->rec_cache, to,filepos, + block_info.data_len, + (!block_of_record && skip_deleted_blocks) ? + READING_NEXT : 0)) + goto panic; + } + else + { + if (info->opt_flag & WRITE_CACHE_USED && + info->rec_cache.pos_in_file < + block_info.filepos + block_info.data_len && + flush_io_cache(&info->rec_cache)) + goto err; + /* VOID(my_seek(info->dfile.file, filepos, MY_SEEK_SET, MYF(0))); */ + if (my_read(info->dfile.file, to, block_info.data_len, MYF(MY_NABP))) + { + if (my_errno == HA_ERR_FILE_TOO_SHORT) + my_errno= HA_ERR_WRONG_IN_RECORD; /* Unexpected end of file */ + goto err; + } + } + } + /* + Increment block-of-record counter. If it was the first block, + remember the position behind the block for the next call. + */ + if (block_of_record++ == 0) + { + info->cur_row.nextpos= block_info.filepos+block_info.block_len; + skip_deleted_blocks=0; + } + left_len-=block_info.data_len; + to+=block_info.data_len; + filepos=block_info.next_filepos; + } while (left_len); + + info->update|= HA_STATE_AKTIV | HA_STATE_KEY_CHANGED; + fast_ma_writeinfo(info); + if (_ma_rec_unpack(info,buf,info->rec_buff,block_info.rec_len) != + MY_FILE_ERROR) + DBUG_RETURN(0); + DBUG_RETURN(my_errno); /* Wrong record */ + +panic: + my_errno=HA_ERR_WRONG_IN_RECORD; /* Something is fatal wrong */ +err: + fast_ma_writeinfo(info); + DBUG_RETURN(my_errno); +} + + + /* Read and process header from a dynamic-record-file */ + +uint _ma_get_block_info(MARIA_BLOCK_INFO *info, File file, my_off_t filepos) +{ + uint return_val=0; + uchar *header=info->header; + + if (file >= 0) + { + /* + We do not use my_pread() here because we want to have the file + pointer set to the end of the header after this function. + my_pread() may leave the file pointer untouched. + */ + VOID(my_seek(file,filepos,MY_SEEK_SET,MYF(0))); + if (my_read(file, header, sizeof(info->header),MYF(0)) != + sizeof(info->header)) + goto err; + } + DBUG_DUMP("header",header,MARIA_BLOCK_INFO_HEADER_LENGTH); + if (info->second_read) + { + if (info->header[0] <= 6 || info->header[0] == 13) + return_val=BLOCK_SYNC_ERROR; + } + else + { + if (info->header[0] > 6 && info->header[0] != 13) + return_val=BLOCK_SYNC_ERROR; + } + info->next_filepos= HA_OFFSET_ERROR; /* Dummy if no next block */ + + switch (info->header[0]) { + case 0: + if ((info->block_len=(uint) mi_uint3korr(header+1)) < + MARIA_MIN_BLOCK_LENGTH || + (info->block_len & (MARIA_DYN_ALIGN_SIZE -1))) + goto err; + info->filepos=filepos; + info->next_filepos=mi_sizekorr(header+4); + info->prev_filepos=mi_sizekorr(header+12); +#if SIZEOF_OFF_T == 4 + if ((mi_uint4korr(header+4) != 0 && + (mi_uint4korr(header+4) != (ulong) ~0 || + info->next_filepos != (ulong) ~0)) || + (mi_uint4korr(header+12) != 0 && + (mi_uint4korr(header+12) != (ulong) ~0 || + info->prev_filepos != (ulong) ~0))) + goto err; +#endif + return return_val | BLOCK_DELETED; /* Deleted block */ + + case 1: + info->rec_len=info->data_len=info->block_len=mi_uint2korr(header+1); + info->filepos=filepos+3; + return return_val | BLOCK_FIRST | BLOCK_LAST; + case 2: + info->rec_len=info->data_len=info->block_len=mi_uint3korr(header+1); + info->filepos=filepos+4; + return return_val | BLOCK_FIRST | BLOCK_LAST; + + case 13: + info->rec_len=mi_uint4korr(header+1); + info->block_len=info->data_len=mi_uint3korr(header+5); + info->next_filepos=mi_sizekorr(header+8); + info->second_read=1; + info->filepos=filepos+16; + return return_val | BLOCK_FIRST; + + case 3: + info->rec_len=info->data_len=mi_uint2korr(header+1); + info->block_len=info->rec_len+ (uint) header[3]; + info->filepos=filepos+4; + return return_val | BLOCK_FIRST | BLOCK_LAST; + case 4: + info->rec_len=info->data_len=mi_uint3korr(header+1); + info->block_len=info->rec_len+ (uint) header[4]; + info->filepos=filepos+5; + return return_val | BLOCK_FIRST | BLOCK_LAST; + + case 5: + info->rec_len=mi_uint2korr(header+1); + info->block_len=info->data_len=mi_uint2korr(header+3); + info->next_filepos=mi_sizekorr(header+5); + info->second_read=1; + info->filepos=filepos+13; + return return_val | BLOCK_FIRST; + case 6: + info->rec_len=mi_uint3korr(header+1); + info->block_len=info->data_len=mi_uint3korr(header+4); + info->next_filepos=mi_sizekorr(header+7); + info->second_read=1; + info->filepos=filepos+15; + return return_val | BLOCK_FIRST; + + /* The following blocks are identical to 1-6 without rec_len */ + case 7: + info->data_len=info->block_len=mi_uint2korr(header+1); + info->filepos=filepos+3; + return return_val | BLOCK_LAST; + case 8: + info->data_len=info->block_len=mi_uint3korr(header+1); + info->filepos=filepos+4; + return return_val | BLOCK_LAST; + + case 9: + info->data_len=mi_uint2korr(header+1); + info->block_len=info->data_len+ (uint) header[3]; + info->filepos=filepos+4; + return return_val | BLOCK_LAST; + case 10: + info->data_len=mi_uint3korr(header+1); + info->block_len=info->data_len+ (uint) header[4]; + info->filepos=filepos+5; + return return_val | BLOCK_LAST; + + case 11: + info->data_len=info->block_len=mi_uint2korr(header+1); + info->next_filepos=mi_sizekorr(header+3); + info->second_read=1; + info->filepos=filepos+11; + return return_val; + case 12: + info->data_len=info->block_len=mi_uint3korr(header+1); + info->next_filepos=mi_sizekorr(header+4); + info->second_read=1; + info->filepos=filepos+12; + return return_val; + } + +err: + my_errno=HA_ERR_WRONG_IN_RECORD; /* Garbage */ + return BLOCK_ERROR; +} diff --git a/storage/maria/ma_extra.c b/storage/maria/ma_extra.c new file mode 100644 index 00000000000..ef16cae9eef --- /dev/null +++ b/storage/maria/ma_extra.c @@ -0,0 +1,615 @@ +/* Copyright (C) 2006 MySQL AB & MySQL Finland AB & TCX DataKonsult 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 "maria_def.h" +#ifdef HAVE_SYS_MMAN_H +#include <sys/mman.h> +#endif +#include "ma_blockrec.h" + +static void maria_extra_keyflag(MARIA_HA *info, + enum ha_extra_function function); + +/** + @brief Set options and buffers to optimize table handling + + @param name table's name + @param info open table + @param function operation + @param extra_arg Pointer to extra argument (normally pointer to + ulong); used when function is one of: + HA_EXTRA_WRITE_CACHE + HA_EXTRA_CACHE + + @return Operation status + @retval 0 ok + @retval !=0 error +*/ + +int maria_extra(MARIA_HA *info, enum ha_extra_function function, + void *extra_arg) +{ + int error= 0; + ulong cache_size; + MARIA_SHARE *share= info->s; + my_bool block_records= share->data_file_type == BLOCK_RECORD; + DBUG_ENTER("maria_extra"); + DBUG_PRINT("enter",("function: %d",(int) function)); + + switch (function) { + case HA_EXTRA_RESET_STATE: /* Reset state (don't free buffers) */ + info->lastinx= 0; /* Use first index as def */ + info->last_search_keypage= info->cur_row.lastpos= HA_OFFSET_ERROR; + info->page_changed= 1; + /* Next/prev gives first/last */ + if (info->opt_flag & READ_CACHE_USED) + { + reinit_io_cache(&info->rec_cache,READ_CACHE,0, + (pbool) (info->lock_type != F_UNLCK), + (pbool) test(info->update & HA_STATE_ROW_CHANGED) + ); + } + info->update= ((info->update & HA_STATE_CHANGED) | HA_STATE_NEXT_FOUND | + HA_STATE_PREV_FOUND); + break; + case HA_EXTRA_CACHE: + if (block_records) + break; /* Not supported */ + + if (info->lock_type == F_UNLCK && + (share->options & HA_OPTION_PACK_RECORD)) + { + error= 1; /* Not possibly if not locked */ + my_errno= EACCES; + break; + } + if (info->s->file_map) /* Don't use cache if mmap */ + break; +#if defined(HAVE_MMAP) && defined(HAVE_MADVISE) + if ((share->options & HA_OPTION_COMPRESS_RECORD)) + { + pthread_mutex_lock(&share->intern_lock); + if (_ma_memmap_file(info)) + { + /* We don't nead MADV_SEQUENTIAL if small file */ + madvise((char*) share->file_map, share->state.state.data_file_length, + share->state.state.data_file_length <= RECORD_CACHE_SIZE*16 ? + MADV_RANDOM : MADV_SEQUENTIAL); + pthread_mutex_unlock(&share->intern_lock); + break; + } + pthread_mutex_unlock(&share->intern_lock); + } +#endif + if (info->opt_flag & WRITE_CACHE_USED) + { + info->opt_flag&= ~WRITE_CACHE_USED; + if ((error= end_io_cache(&info->rec_cache))) + break; + } + if (!(info->opt_flag & + (READ_CACHE_USED | WRITE_CACHE_USED | MEMMAP_USED))) + { + cache_size= (extra_arg ? *(ulong*) extra_arg : + my_default_record_cache_size); + if (!(init_io_cache(&info->rec_cache, info->dfile.file, + (uint) min(share->state.state.data_file_length+1, + cache_size), + READ_CACHE,0L,(pbool) (info->lock_type != F_UNLCK), + MYF(share->write_flag & MY_WAIT_IF_FULL)))) + { + info->opt_flag|= READ_CACHE_USED; + info->update&= ~HA_STATE_ROW_CHANGED; + } + if (share->non_transactional_concurrent_insert) + info->rec_cache.end_of_file= info->state->data_file_length; + } + break; + case HA_EXTRA_REINIT_CACHE: + if (info->opt_flag & READ_CACHE_USED) + { + reinit_io_cache(&info->rec_cache, READ_CACHE, info->cur_row.nextpos, + (pbool) (info->lock_type != F_UNLCK), + (pbool) test(info->update & HA_STATE_ROW_CHANGED)); + info->update&= ~HA_STATE_ROW_CHANGED; + if (share->non_transactional_concurrent_insert) + info->rec_cache.end_of_file= info->state->data_file_length; + } + break; + case HA_EXTRA_WRITE_CACHE: + if (info->lock_type == F_UNLCK) + { + error= 1; /* Not possibly if not locked */ + break; + } + if (block_records) + break; /* Not supported */ + + cache_size= (extra_arg ? *(ulong*) extra_arg : + my_default_record_cache_size); + if (!(info->opt_flag & + (READ_CACHE_USED | WRITE_CACHE_USED | OPT_NO_ROWS)) && + !share->state.header.uniques) + if (!(init_io_cache(&info->rec_cache, info->dfile.file, cache_size, + WRITE_CACHE,share->state.state.data_file_length, + (pbool) (info->lock_type != F_UNLCK), + MYF(share->write_flag & MY_WAIT_IF_FULL)))) + { + info->opt_flag|= WRITE_CACHE_USED; + info->update&= ~(HA_STATE_ROW_CHANGED | + HA_STATE_WRITE_AT_END | + HA_STATE_EXTEND_BLOCK); + } + break; + case HA_EXTRA_PREPARE_FOR_UPDATE: + if (info->s->data_file_type != DYNAMIC_RECORD) + break; + /* Remove read/write cache if dynamic rows */ + case HA_EXTRA_NO_CACHE: + if (info->opt_flag & (READ_CACHE_USED | WRITE_CACHE_USED)) + { + info->opt_flag&= ~(READ_CACHE_USED | WRITE_CACHE_USED); + error= end_io_cache(&info->rec_cache); + /* Sergei will insert full text index caching here */ + } +#if defined(HAVE_MMAP) && defined(HAVE_MADVISE) + if (info->opt_flag & MEMMAP_USED) + madvise((char*) share->file_map, share->state.state.data_file_length, + MADV_RANDOM); +#endif + break; + case HA_EXTRA_FLUSH_CACHE: + if (info->opt_flag & WRITE_CACHE_USED) + { + if ((error= flush_io_cache(&info->rec_cache))) + { + maria_print_error(info->s, HA_ERR_CRASHED); + maria_mark_crashed(info); /* Fatal error found */ + } + } + break; + case HA_EXTRA_NO_READCHECK: + info->opt_flag&= ~READ_CHECK_USED; /* No readcheck */ + break; + case HA_EXTRA_READCHECK: + info->opt_flag|= READ_CHECK_USED; + break; + case HA_EXTRA_KEYREAD: /* Read only keys to record */ + case HA_EXTRA_REMEMBER_POS: + info->opt_flag|= REMEMBER_OLD_POS; + bmove(info->last_key.data + share->base.max_key_length*2, + info->last_key.data, + info->last_key.data_length + info->last_key.ref_length); + info->save_update= info->update; + info->save_lastinx= info->lastinx; + info->save_lastpos= info->cur_row.lastpos; + info->save_lastkey_data_length= info->last_key.data_length; + info->save_lastkey_ref_length= info->last_key.ref_length; + if (function == HA_EXTRA_REMEMBER_POS) + break; + /* fall through */ + case HA_EXTRA_KEYREAD_CHANGE_POS: + info->opt_flag|= KEY_READ_USED; + info->read_record= _ma_read_key_record; + break; + case HA_EXTRA_NO_KEYREAD: + case HA_EXTRA_RESTORE_POS: + if (info->opt_flag & REMEMBER_OLD_POS) + { + bmove(info->last_key.data, + info->last_key.data + share->base.max_key_length*2, + info->save_lastkey_data_length + info->save_lastkey_ref_length); + info->update= info->save_update | HA_STATE_WRITTEN; + info->lastinx= info->save_lastinx; + info->cur_row.lastpos= info->save_lastpos; + info->last_key.data_length= info->save_lastkey_data_length; + info->last_key.ref_length= info->save_lastkey_ref_length; + info->last_key.flag= 0; + } + info->read_record= share->read_record; + info->opt_flag&= ~(KEY_READ_USED | REMEMBER_OLD_POS); + break; + case HA_EXTRA_NO_USER_CHANGE: /* Database is somehow locked agains changes */ + info->lock_type= F_EXTRA_LCK; /* Simulate as locked */ + break; + case HA_EXTRA_WAIT_LOCK: + info->lock_wait= 0; + break; + case HA_EXTRA_NO_WAIT_LOCK: + info->lock_wait= MY_SHORT_WAIT; + break; + case HA_EXTRA_NO_KEYS: + /* we're going to modify pieces of the state, stall Checkpoint */ + pthread_mutex_lock(&share->intern_lock); + if (info->lock_type == F_UNLCK) + { + pthread_mutex_unlock(&share->intern_lock); + error= 1; /* Not possibly if not lock */ + break; + } + if (maria_is_any_key_active(share->state.key_map)) + { + MARIA_KEYDEF *key= share->keyinfo; + uint i; + for (i =0 ; i < share->base.keys ; i++,key++) + { + if (!(key->flag & HA_NOSAME) && info->s->base.auto_key != i+1) + { + maria_clear_key_active(share->state.key_map, i); + info->update|= HA_STATE_CHANGED; + } + } + + if (!share->changed) + { + share->state.changed|= STATE_CHANGED | STATE_NOT_ANALYZED; + share->changed= 1; /* Update on close */ + if (!share->global_changed) + { + share->global_changed= 1; + share->state.open_count++; + } + } + if (!share->now_transactional) + share->state.state= *info->state; + /* + That state write to disk must be done, even for transactional tables; + indeed the table's share is going to be lost (there was a + HA_EXTRA_FORCE_REOPEN before, which set share->last_version to + 0), and so the only way it leaves information (share->state.key_map) + for the posterity is by writing it to disk. + */ + DBUG_ASSERT(!maria_in_recovery); + error= _ma_state_info_write(share, + MA_STATE_INFO_WRITE_DONT_MOVE_OFFSET | + MA_STATE_INFO_WRITE_FULL_INFO); + } + pthread_mutex_unlock(&share->intern_lock); + break; + case HA_EXTRA_FORCE_REOPEN: + /* + MySQL uses this case after it has closed all other instances + of this table. + We however do a flush here for additional safety. + */ + /** @todo consider porting these flush-es to MyISAM */ + DBUG_ASSERT(share->reopen == 1); + error= _ma_flush_table_files(info, MARIA_FLUSH_DATA | MARIA_FLUSH_INDEX, + FLUSH_FORCE_WRITE, FLUSH_FORCE_WRITE); + if (!error && share->changed) + { + pthread_mutex_lock(&share->intern_lock); + if (!(error= _ma_state_info_write(share, + MA_STATE_INFO_WRITE_DONT_MOVE_OFFSET| + MA_STATE_INFO_WRITE_FULL_INFO))) + share->changed= 0; + pthread_mutex_unlock(&share->intern_lock); + } + pthread_mutex_lock(&THR_LOCK_maria); + pthread_mutex_lock(&share->intern_lock); /* protect against Checkpoint */ + /* this makes the share not be re-used next time the table is opened */ + share->last_version= 0L; /* Impossible version */ + pthread_mutex_unlock(&share->intern_lock); + pthread_mutex_unlock(&THR_LOCK_maria); + break; + case HA_EXTRA_PREPARE_FOR_DROP: + case HA_EXTRA_PREPARE_FOR_RENAME: + { + my_bool do_flush= test(function != HA_EXTRA_PREPARE_FOR_DROP); + enum flush_type type; + pthread_mutex_lock(&THR_LOCK_maria); + /* + This share, to have last_version=0, needs to save all its data/index + blocks to disk if this is not for a DROP TABLE. Otherwise they would be + invisible to future openers; and they could even go to disk late and + cancel the work of future openers. + */ + if (info->lock_type != F_UNLCK && !info->was_locked) + { + info->was_locked= info->lock_type; + if (maria_lock_database(info, F_UNLCK)) + error= my_errno; + info->lock_type= F_UNLCK; + } + if (share->kfile.file >= 0) + _ma_decrement_open_count(info); + pthread_mutex_lock(&share->intern_lock); + if (info->trn) + { + _ma_remove_table_from_trnman(share, info->trn); + /* Ensure we don't point to the deleted data in trn */ + info->state= info->state_start= &share->state.state; + } + + type= do_flush ? FLUSH_RELEASE : FLUSH_IGNORE_CHANGED; + if (_ma_flush_table_files(info, MARIA_FLUSH_DATA | MARIA_FLUSH_INDEX, + type, type)) + { + error=my_errno; + share->changed= 1; + } + if (info->opt_flag & (READ_CACHE_USED | WRITE_CACHE_USED)) + { + info->opt_flag&= ~(READ_CACHE_USED | WRITE_CACHE_USED); + if (end_io_cache(&info->rec_cache)) + error= 1; + } + if (share->kfile.file >= 0) + { + if (do_flush) + { + /* Save the state so that others can find it from disk. */ + if ((share->changed && + _ma_state_info_write(share, + MA_STATE_INFO_WRITE_DONT_MOVE_OFFSET | + MA_STATE_INFO_WRITE_FULL_INFO)) || + my_sync(share->kfile.file, MYF(0))) + error= my_errno; + else + share->changed= 0; + } + else + { + /* be sure that state is not tried for write as file may be closed */ + share->changed= 0; + } + } + if (share->data_file_type == BLOCK_RECORD && + share->bitmap.file.file >= 0) + { + if (do_flush && my_sync(share->bitmap.file.file, MYF(0))) + error= my_errno; + } + /* For protection against Checkpoint, we set under intern_lock: */ + share->last_version= 0L; /* Impossible version */ + pthread_mutex_unlock(&share->intern_lock); + pthread_mutex_unlock(&THR_LOCK_maria); + break; + } + case HA_EXTRA_PREPARE_FOR_FORCED_CLOSE: + if (info->trn) + { + pthread_mutex_lock(&share->intern_lock); + _ma_remove_table_from_trnman(share, info->trn); + /* Ensure we don't point to the deleted data in trn */ + info->state= info->state_start= &share->state.state; + pthread_mutex_unlock(&share->intern_lock); + } + break; + case HA_EXTRA_FLUSH: + if (!share->temporary) + error= _ma_flush_table_files(info, MARIA_FLUSH_DATA | MARIA_FLUSH_INDEX, + FLUSH_KEEP, FLUSH_KEEP); +#ifdef HAVE_PWRITE + _ma_decrement_open_count(info); +#endif + if (share->not_flushed) + { + share->not_flushed= 0; + if (_ma_sync_table_files(info)) + error= my_errno; + if (error) + { + share->changed= 1; + maria_print_error(info->s, HA_ERR_CRASHED); + maria_mark_crashed(info); /* Fatal error found */ + } + } + break; + case HA_EXTRA_NORMAL: /* Theese isn't in use */ + info->quick_mode= 0; + break; + case HA_EXTRA_QUICK: + info->quick_mode= 1; + break; + case HA_EXTRA_NO_ROWS: + if (!share->state.header.uniques) + info->opt_flag|= OPT_NO_ROWS; + break; + case HA_EXTRA_PRELOAD_BUFFER_SIZE: + info->preload_buff_size= *((ulong *) extra_arg); + break; + case HA_EXTRA_CHANGE_KEY_TO_UNIQUE: + case HA_EXTRA_CHANGE_KEY_TO_DUP: + maria_extra_keyflag(info, function); + break; + case HA_EXTRA_MMAP: +#ifdef HAVE_MMAP + if (block_records) + break; /* Not supported */ + pthread_mutex_lock(&share->intern_lock); + /* + Memory map the data file if it is not already mapped. It is safe + to memory map a file while other threads are using file I/O on it. + Assigning a new address to a function pointer is an atomic + operation. intern_lock prevents that two or more mappings are done + at the same time. + */ + if (!share->file_map) + { + if (_ma_dynmap_file(info, share->state.state.data_file_length)) + { + DBUG_PRINT("warning",("mmap failed: errno: %d",errno)); + error= my_errno= errno; + } + else + { + share->file_read= _ma_mmap_pread; + share->file_write= _ma_mmap_pwrite; + } + } + pthread_mutex_unlock(&share->intern_lock); +#endif + break; + case HA_EXTRA_MARK_AS_LOG_TABLE: + pthread_mutex_lock(&share->intern_lock); + share->is_log_table= TRUE; + pthread_mutex_unlock(&share->intern_lock); + break; + case HA_EXTRA_KEY_CACHE: + case HA_EXTRA_NO_KEY_CACHE: + default: + break; + } + DBUG_RETURN(error); +} /* maria_extra */ + + +/* + Start/Stop Inserting Duplicates Into a Table, WL#1648. +*/ + +static void maria_extra_keyflag(MARIA_HA *info, + enum ha_extra_function function) +{ + uint idx; + + for (idx= 0; idx< info->s->base.keys; idx++) + { + switch (function) { + case HA_EXTRA_CHANGE_KEY_TO_UNIQUE: + info->s->keyinfo[idx].flag|= HA_NOSAME; + break; + case HA_EXTRA_CHANGE_KEY_TO_DUP: + info->s->keyinfo[idx].flag&= ~(HA_NOSAME); + break; + default: + break; + } + } +} + + +int maria_reset(MARIA_HA *info) +{ + int error= 0; + MARIA_SHARE *share= info->s; + DBUG_ENTER("maria_reset"); + /* + Free buffers and reset the following flags: + EXTRA_CACHE, EXTRA_WRITE_CACHE, EXTRA_KEYREAD, EXTRA_QUICK + + If the row buffer cache is large (for dynamic tables), reduce it + to save memory. + */ + if (info->opt_flag & (READ_CACHE_USED | WRITE_CACHE_USED)) + { + info->opt_flag&= ~(READ_CACHE_USED | WRITE_CACHE_USED); + error= end_io_cache(&info->rec_cache); + } + /* Free memory used for keeping blobs */ + if (share->base.blobs) + { + if (info->rec_buff_size > share->base.default_rec_buff_size) + { + info->rec_buff_size= 1; /* Force realloc */ + _ma_alloc_buffer(&info->rec_buff, &info->rec_buff_size, + share->base.default_rec_buff_size); + } + if (info->blob_buff_size > MARIA_SMALL_BLOB_BUFFER) + { + info->blob_buff_size= 1; /* Force realloc */ + _ma_alloc_buffer(&info->blob_buff, &info->blob_buff_size, + MARIA_SMALL_BLOB_BUFFER); + } + } +#if defined(HAVE_MMAP) && defined(HAVE_MADVISE) + if (info->opt_flag & MEMMAP_USED) + madvise((char*) share->file_map, share->state.state.data_file_length, + MADV_RANDOM); +#endif + info->opt_flag&= ~(KEY_READ_USED | REMEMBER_OLD_POS); + info->quick_mode= 0; + info->lastinx= 0; /* Use first index as def */ + info->last_search_keypage= info->cur_row.lastpos= HA_OFFSET_ERROR; + info->page_changed= 1; + info->update= ((info->update & HA_STATE_CHANGED) | HA_STATE_NEXT_FOUND | + HA_STATE_PREV_FOUND); + DBUG_RETURN(error); +} + + +int _ma_sync_table_files(const MARIA_HA *info) +{ + return (my_sync(info->dfile.file, MYF(MY_WME)) || + my_sync(info->s->kfile.file, MYF(MY_WME))); +} + + +/** + @brief flushes the data and/or index file of a table + + This is useful when one wants to read a table using OS syscalls (like + my_copy()) and first wants to be sure that MySQL-level caches go down to + the OS so that OS syscalls can see all data. It can flush rec_cache, + bitmap, pagecache of data file, pagecache of index file. + + @param info table + @param flush_data_or_index one or two of these flags: + MARIA_FLUSH_DATA, MARIA_FLUSH_INDEX + @param flush_type_for_data + @param flush_type_for_index + + @note does not sync files (@see _ma_sync_table_files()). + @note Progressively this function will be used in all places where we flush + the index but not the data file (probable bugs). + + @return Operation status + @retval 0 OK + @retval 1 Error +*/ + +int _ma_flush_table_files(MARIA_HA *info, uint flush_data_or_index, + enum flush_type flush_type_for_data, + enum flush_type flush_type_for_index) +{ + int error= 0; + MARIA_SHARE *share= info->s; + /* flush data file first because it's more critical */ + if (flush_data_or_index & MARIA_FLUSH_DATA) + { + if ((info->opt_flag & WRITE_CACHE_USED) && + flush_type_for_data != FLUSH_IGNORE_CHANGED && + flush_io_cache(&info->rec_cache)) + error= 1; + if (share->data_file_type == BLOCK_RECORD) + { + if (flush_type_for_data != FLUSH_IGNORE_CHANGED) + { + if (_ma_bitmap_flush(share)) + error= 1; + } + else + { + pthread_mutex_lock(&share->bitmap.bitmap_lock); + share->bitmap.changed= 0; + pthread_mutex_unlock(&share->bitmap.bitmap_lock); + } + if (flush_pagecache_blocks(share->pagecache, &info->dfile, + flush_type_for_data)) + error= 1; + } + } + if ((flush_data_or_index & MARIA_FLUSH_INDEX) && + flush_pagecache_blocks(share->pagecache, &share->kfile, + flush_type_for_index)) + error= 1; + if (!error) + return 0; + + maria_print_error(info->s, HA_ERR_CRASHED); + maria_mark_crashed(info); + return 1; +} diff --git a/storage/maria/ma_ft_boolean_search.c b/storage/maria/ma_ft_boolean_search.c new file mode 100644 index 00000000000..763b8827a6c --- /dev/null +++ b/storage/maria/ma_ft_boolean_search.c @@ -0,0 +1,1039 @@ +/* Copyright (C) 2006 MySQL AB & MySQL Finland AB & TCX DataKonsult 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 */ + +/* Written by Sergei A. Golubchik, who has a shared copyright to this code */ + +/* TODO: add caching - pre-read several index entries at once */ + +/* + Added optimization for full-text queries with plus-words. It was + implemented by sharing maximal document id (max_docid) variable + inside plus subtree. max_docid could be used by any word in plus + subtree, but it could be updated by plus-word only. + + Fulltext "smarter index merge" optimization assumes that rows + it gets are ordered by doc_id. That is not the case when we + search for a word with truncation operator. It may return + rows in random order. Thus we may not use "smarter index merge" + optimization with "trunc-words". + + The idea is: there is no need to search for docid smaller than + biggest docid inside current plus subtree or any upper plus subtree. + + Examples: + +word1 word2 + share same max_docid + max_docid updated by word1 + +word1 +(word2 word3) + share same max_docid + max_docid updated by word1 + +(word1 -word2) +(+word3 word4) + share same max_docid + max_docid updated by word3 + +word1 word2 (+word3 word4 (+word5 word6)) + three subexpressions (including the top-level one), + every one has its own max_docid, updated by its plus word. + but for the search word6 uses + max(word1.max_docid, word3.max_docid, word5.max_docid), + while word4 uses, accordingly, + max(word1.max_docid, word3.max_docid). +*/ + +#define FT_CORE +#include "ma_ftdefs.h" + +/* search with boolean queries */ + +static double _wghts[11]= +{ + 0.131687242798354, + 0.197530864197531, + 0.296296296296296, + 0.444444444444444, + 0.666666666666667, + 1.000000000000000, + 1.500000000000000, + 2.250000000000000, + 3.375000000000000, + 5.062500000000000, + 7.593750000000000}; +static double *wghts=_wghts+5; /* wghts[i] = 1.5**i */ + +static double _nwghts[11]= +{ + -0.065843621399177, + -0.098765432098766, + -0.148148148148148, + -0.222222222222222, + -0.333333333333334, + -0.500000000000000, + -0.750000000000000, + -1.125000000000000, + -1.687500000000000, + -2.531250000000000, + -3.796875000000000}; +static double *nwghts=_nwghts+5; /* nwghts[i] = -0.5*1.5**i */ + +#define FTB_FLAG_TRUNC 1 +/* At most one of the following flags can be set */ +#define FTB_FLAG_YES 2 +#define FTB_FLAG_NO 4 +#define FTB_FLAG_WONLY 8 + +typedef struct st_ftb_expr FTB_EXPR; +struct st_ftb_expr +{ + FTB_EXPR *up; + uint flags; +/* ^^^^^^^^^^^^^^^^^^ FTB_{EXPR,WORD} common section */ + my_off_t docid[2]; + my_off_t max_docid; + float weight; + float cur_weight; + LIST *phrase; /* phrase words */ + LIST *document; /* for phrase search */ + uint yesses; /* number of "yes" words matched */ + uint nos; /* number of "no" words matched */ + uint ythresh; /* number of "yes" words in expr */ + uint yweaks; /* number of "yes" words for scan only */ +}; + +typedef struct st_ftb_word +{ + FTB_EXPR *up; + uint flags; +/* ^^^^^^^^^^^^^^^^^^ FTB_{EXPR,WORD} common section */ + my_off_t docid[2]; /* for index search and for scan */ + my_off_t key_root; + FTB_EXPR *max_docid_expr; + MARIA_KEYDEF *keyinfo; + struct st_ftb_word *prev; + float weight; + uint ndepth; + uint len; + uchar off; + uchar word[1]; +} FTB_WORD; + +typedef struct st_ft_info +{ + struct _ft_vft *please; + MARIA_HA *info; + CHARSET_INFO *charset; + FTB_EXPR *root; + FTB_WORD **list; + FTB_WORD *last_word; + MEM_ROOT mem_root; + QUEUE queue; + TREE no_dupes; + my_off_t lastpos; + uint keynr; + uchar with_scan; + enum { UNINITIALIZED, READY, INDEX_SEARCH, INDEX_DONE } state; +} FTB; + +static int FTB_WORD_cmp(my_off_t *v, FTB_WORD *a, FTB_WORD *b) +{ + int i; + + /* if a==curdoc, take it as a < b */ + if (v && a->docid[0] == *v) + return -1; + + /* ORDER BY docid, ndepth DESC */ + i=CMP_NUM(a->docid[0], b->docid[0]); + if (!i) + i=CMP_NUM(b->ndepth,a->ndepth); + return i; +} + +static int FTB_WORD_cmp_list(CHARSET_INFO *cs, FTB_WORD **a, FTB_WORD **b) +{ + /* ORDER BY word, ndepth */ + int i= ha_compare_text(cs, (uchar*) (*a)->word + 1,(*a)->len - 1, + (uchar*) (*b)->word + 1,(*b)->len - 1, 0, 0); + if (!i) + i=CMP_NUM((*a)->ndepth, (*b)->ndepth); + return i; +} + + +typedef struct st_my_ftb_param +{ + FTB *ftb; + FTB_EXPR *ftbe; + uchar *up_quot; + uint depth; +} MY_FTB_PARAM; + + +static int ftb_query_add_word(MYSQL_FTPARSER_PARAM *param, + char *word, int word_len, + MYSQL_FTPARSER_BOOLEAN_INFO *info) +{ + MY_FTB_PARAM *ftb_param= param->mysql_ftparam; + FTB_WORD *ftbw; + FTB_EXPR *ftbe, *tmp_expr; + FT_WORD *phrase_word; + LIST *tmp_element; + int r= info->weight_adjust; + float weight= (float) + (info->wasign ? nwghts : wghts)[(r>5)?5:((r<-5)?-5:r)]; + + switch (info->type) { + case FT_TOKEN_WORD: + ftbw= (FTB_WORD *)alloc_root(&ftb_param->ftb->mem_root, + sizeof(FTB_WORD) + + (info->trunc ? MARIA_MAX_KEY_BUFF : + word_len * ftb_param->ftb->charset->mbmaxlen + + HA_FT_WLEN + + ftb_param->ftb->info->s->rec_reflength)); + ftbw->len= word_len + 1; + ftbw->flags= 0; + ftbw->off= 0; + if (info->yesno > 0) ftbw->flags|= FTB_FLAG_YES; + if (info->yesno < 0) ftbw->flags|= FTB_FLAG_NO; + if (info->trunc) ftbw->flags|= FTB_FLAG_TRUNC; + ftbw->weight= weight; + ftbw->up= ftb_param->ftbe; + ftbw->docid[0]= ftbw->docid[1]= HA_OFFSET_ERROR; + ftbw->ndepth= (info->yesno < 0) + ftb_param->depth; + ftbw->key_root= HA_OFFSET_ERROR; + memcpy(ftbw->word + 1, word, word_len); + ftbw->word[0]= word_len; + if (info->yesno > 0) ftbw->up->ythresh++; + ftb_param->ftb->queue.max_elements++; + ftbw->prev= ftb_param->ftb->last_word; + ftb_param->ftb->last_word= ftbw; + ftb_param->ftb->with_scan|= (info->trunc & FTB_FLAG_TRUNC); + for (tmp_expr= ftb_param->ftbe; tmp_expr->up; tmp_expr= tmp_expr->up) + if (! (tmp_expr->flags & FTB_FLAG_YES)) + break; + ftbw->max_docid_expr= tmp_expr; + /* fall through */ + case FT_TOKEN_STOPWORD: + if (! ftb_param->up_quot) break; + phrase_word= (FT_WORD *)alloc_root(&ftb_param->ftb->mem_root, sizeof(FT_WORD)); + tmp_element= (LIST *)alloc_root(&ftb_param->ftb->mem_root, sizeof(LIST)); + phrase_word->pos= (uchar*) word; + phrase_word->len= word_len; + tmp_element->data= (void *)phrase_word; + ftb_param->ftbe->phrase= list_add(ftb_param->ftbe->phrase, tmp_element); + /* Allocate document list at this point. + It allows to avoid huge amount of allocs/frees for each row.*/ + tmp_element= (LIST *)alloc_root(&ftb_param->ftb->mem_root, sizeof(LIST)); + tmp_element->data= alloc_root(&ftb_param->ftb->mem_root, sizeof(FT_WORD)); + ftb_param->ftbe->document= + list_add(ftb_param->ftbe->document, tmp_element); + break; + case FT_TOKEN_LEFT_PAREN: + ftbe=(FTB_EXPR *)alloc_root(&ftb_param->ftb->mem_root, sizeof(FTB_EXPR)); + ftbe->flags= 0; + if (info->yesno > 0) ftbe->flags|= FTB_FLAG_YES; + if (info->yesno < 0) ftbe->flags|= FTB_FLAG_NO; + ftbe->weight= weight; + ftbe->up= ftb_param->ftbe; + ftbe->max_docid= ftbe->ythresh= ftbe->yweaks= 0; + ftbe->docid[0]= ftbe->docid[1]= HA_OFFSET_ERROR; + ftbe->phrase= NULL; + ftbe->document= 0; + if (info->quot) ftb_param->ftb->with_scan|= 2; + if (info->yesno > 0) ftbe->up->ythresh++; + ftb_param->ftbe= ftbe; + ftb_param->depth++; + ftb_param->up_quot= (uchar*) info->quot; + break; + case FT_TOKEN_RIGHT_PAREN: + if (ftb_param->ftbe->document) + { + /* Circuit document list */ + for (tmp_element= ftb_param->ftbe->document; + tmp_element->next; tmp_element= tmp_element->next) /* no-op */; + tmp_element->next= ftb_param->ftbe->document; + ftb_param->ftbe->document->prev= tmp_element; + } + info->quot= 0; + if (ftb_param->ftbe->up) + { + DBUG_ASSERT(ftb_param->depth); + ftb_param->ftbe= ftb_param->ftbe->up; + ftb_param->depth--; + ftb_param->up_quot= 0; + } + break; + case FT_TOKEN_EOF: + default: + break; + } + return(0); +} + + +static int ftb_parse_query_internal(MYSQL_FTPARSER_PARAM *param, + char *query, int len) +{ + MY_FTB_PARAM *ftb_param= param->mysql_ftparam; + MYSQL_FTPARSER_BOOLEAN_INFO info; + CHARSET_INFO *cs= ftb_param->ftb->charset; + uchar **start= (uchar**) &query; + uchar *end= (uchar*) query + len; + FT_WORD w; + + info.prev= ' '; + info.quot= 0; + while (maria_ft_get_word(cs, start, end, &w, &info)) + param->mysql_add_word(param, (char *) w.pos, w.len, &info); + return(0); +} + + +static int _ftb_parse_query(FTB *ftb, uchar *query, uint len, + struct st_mysql_ftparser *parser) +{ + MYSQL_FTPARSER_PARAM *param; + MY_FTB_PARAM ftb_param; + DBUG_ENTER("_ftb_parse_query"); + DBUG_ASSERT(parser); + + if (ftb->state != UNINITIALIZED) + DBUG_RETURN(0); + if (! (param= maria_ftparser_call_initializer(ftb->info, ftb->keynr, 0))) + DBUG_RETURN(1); + + ftb_param.ftb= ftb; + ftb_param.depth= 0; + ftb_param.ftbe= ftb->root; + ftb_param.up_quot= 0; + + param->mysql_parse= ftb_parse_query_internal; + param->mysql_add_word= ftb_query_add_word; + param->mysql_ftparam= (void *)&ftb_param; + param->cs= ftb->charset; + param->doc= (char*) query; + param->length= len; + param->flags= 0; + param->mode= MYSQL_FTPARSER_FULL_BOOLEAN_INFO; + DBUG_RETURN(parser->parse(param)); +} + + +static int _ftb_no_dupes_cmp(void* not_used __attribute__((unused)), + const void *a,const void *b) +{ + return CMP_NUM((*((my_off_t*)a)), (*((my_off_t*)b))); +} + + +/* returns 1 if the search was finished (must-word wasn't found) */ + +static int _ft2_search(FTB *ftb, FTB_WORD *ftbw, my_bool init_search) +{ + int r; + int subkeys=1; + my_bool can_go_down; + MARIA_HA *info=ftb->info; + uint off, extra=HA_FT_WLEN+info->s->base.rec_reflength; + uchar *lastkey_buf= ftbw->word+ftbw->off; + MARIA_KEY key; + LINT_INIT(off); + + if (ftbw->flags & FTB_FLAG_TRUNC) + lastkey_buf+=ftbw->len; + + if (init_search) + { + ftbw->key_root=info->s->state.key_root[ftb->keynr]; + ftbw->keyinfo=info->s->keyinfo+ftb->keynr; + key.keyinfo= ftbw->keyinfo; + key.data= ftbw->word; + key.data_length= ftbw->len; + key.ref_length= 0; + key.flag= 0; + + r= _ma_search(info, &key, SEARCH_FIND | SEARCH_BIGGER, ftbw->key_root); + } + else + { + uint sflag= SEARCH_BIGGER; + my_off_t max_docid=0; + FTB_EXPR *tmp; + + for (tmp= ftbw->max_docid_expr; tmp; tmp= tmp->up) + set_if_bigger(max_docid, tmp->max_docid); + + if (ftbw->docid[0] < max_docid) + { + sflag|= SEARCH_SAME; + _ma_dpointer(info->s, (uchar*) (ftbw->word + ftbw->len + HA_FT_WLEN), + max_docid); + } + + key.keyinfo= ftbw->keyinfo; + key.data= lastkey_buf; + key.data_length= USE_WHOLE_KEY; + key.ref_length= 0; + key.flag= 0; + + r= _ma_search(info, &key, sflag, ftbw->key_root); + } + + can_go_down=(!ftbw->off && (init_search || (ftbw->flags & FTB_FLAG_TRUNC))); + /* Skip rows inserted by concurrent insert */ + while (!r) + { + if (can_go_down) + { + /* going down ? */ + off= info->last_key.data_length + info->last_key.ref_length - extra; + subkeys=ft_sintXkorr(info->last_key.data + off); + } + if (subkeys<0 || info->cur_row.lastpos < info->state->data_file_length) + break; + r= _ma_search_next(info, &info->last_key, SEARCH_BIGGER, ftbw->key_root); + } + + if (!r && !ftbw->off) + { + r= ha_compare_text(ftb->charset, + info->last_key.data+1, + info->last_key.data_length + info->last_key.ref_length- + extra-1, + (uchar*) ftbw->word+1, + ftbw->len-1, + (my_bool) (ftbw->flags & FTB_FLAG_TRUNC), 0); + } + + if (r) /* not found */ + { + if (!ftbw->off || !(ftbw->flags & FTB_FLAG_TRUNC)) + { + ftbw->docid[0]=HA_OFFSET_ERROR; + if ((ftbw->flags & FTB_FLAG_YES) && ftbw->up->up==0) + { + /* + This word MUST BE present in every document returned, + so we can stop the search right now + */ + ftb->state=INDEX_DONE; + return 1; /* search is done */ + } + else + return 0; + } + + /* going up to the first-level tree to continue search there */ + _ma_dpointer(info->s, (lastkey_buf+HA_FT_WLEN), ftbw->key_root); + ftbw->key_root=info->s->state.key_root[ftb->keynr]; + ftbw->keyinfo=info->s->keyinfo+ftb->keynr; + ftbw->off=0; + return _ft2_search(ftb, ftbw, 0); + } + + /* matching key found */ + memcpy(lastkey_buf, info->last_key.data, + info->last_key.data_length + info->last_key.ref_length); + if (lastkey_buf == ftbw->word) + ftbw->len= info->last_key.data_length + info->last_key.ref_length - extra; + + /* going down ? */ + if (subkeys<0) + { + /* + yep, going down, to the second-level tree + TODO here: subkey-based optimization + */ + ftbw->off=off; + ftbw->key_root= info->cur_row.lastpos; + ftbw->keyinfo=& info->s->ft2_keyinfo; + r= _ma_search_first(info, ftbw->keyinfo, ftbw->key_root); + DBUG_ASSERT(r==0); /* found something */ + memcpy(lastkey_buf+off, info->last_key.data, + info->last_key.data_length + info->last_key.ref_length); + } + ftbw->docid[0]= info->cur_row.lastpos; + if (ftbw->flags & FTB_FLAG_YES && !(ftbw->flags & FTB_FLAG_TRUNC)) + ftbw->max_docid_expr->max_docid= info->cur_row.lastpos; + return 0; +} + +static void _ftb_init_index_search(FT_INFO *ftb) +{ + int i; + FTB_WORD *ftbw; + + if ((ftb->state != READY && ftb->state !=INDEX_DONE) || + ftb->keynr == NO_SUCH_KEY) + return; + ftb->state=INDEX_SEARCH; + + for (i=ftb->queue.elements; i; i--) + { + ftbw=(FTB_WORD *)(ftb->queue.root[i]); + + if (ftbw->flags & FTB_FLAG_TRUNC) + { + /* + special treatment for truncation operator + 1. there are some (besides this) +words + | no need to search in the index, it can never ADD new rows + | to the result, and to remove half-matched rows we do scan anyway + 2. -trunc* + | same as 1. + 3. in 1 and 2, +/- need not be on the same expr. level, + but can be on any upper level, as in +word +(trunc1* trunc2*) + 4. otherwise + | We have to index-search for this prefix. + | It may cause duplicates, as in the index (sorted by <word,docid>) + | <aaaa,row1> + | <aabb,row2> + | <aacc,row1> + | Searching for "aa*" will find row1 twice... + */ + FTB_EXPR *ftbe; + for (ftbe=(FTB_EXPR*)ftbw; + ftbe->up && !(ftbe->up->flags & FTB_FLAG_TRUNC); + ftbe->up->flags|= FTB_FLAG_TRUNC, ftbe=ftbe->up) + { + if (ftbe->flags & FTB_FLAG_NO || /* 2 */ + ftbe->up->ythresh - ftbe->up->yweaks > + (uint) test(ftbe->flags & FTB_FLAG_YES)) /* 1 */ + { + FTB_EXPR *top_ftbe=ftbe->up; + ftbw->docid[0]=HA_OFFSET_ERROR; + for (ftbe=(FTB_EXPR *)ftbw; + ftbe != top_ftbe && !(ftbe->flags & FTB_FLAG_NO); + ftbe=ftbe->up) + ftbe->up->yweaks++; + ftbe=0; + break; + } + } + if (!ftbe) + continue; + /* 4 */ + if (!is_tree_inited(& ftb->no_dupes)) + init_tree(& ftb->no_dupes,0,0,sizeof(my_off_t), + _ftb_no_dupes_cmp,0,0,0); + else + reset_tree(& ftb->no_dupes); + } + + ftbw->off=0; /* in case of reinit */ + if (_ft2_search(ftb, ftbw, 1)) + return; + } + queue_fix(& ftb->queue); +} + + +FT_INFO * maria_ft_init_boolean_search(MARIA_HA *info, uint keynr, + uchar *query, + uint query_len, CHARSET_INFO *cs) +{ + FTB *ftb; + FTB_EXPR *ftbe; + FTB_WORD *ftbw; + + if (!(ftb=(FTB *)my_malloc(sizeof(FTB), MYF(MY_WME)))) + return 0; + ftb->please= (struct _ft_vft *) & _ma_ft_vft_boolean; + ftb->state=UNINITIALIZED; + ftb->info=info; + ftb->keynr=keynr; + ftb->charset=cs; + DBUG_ASSERT(keynr==NO_SUCH_KEY || cs == info->s->keyinfo[keynr].seg->charset); + ftb->with_scan=0; + ftb->lastpos=HA_OFFSET_ERROR; + bzero(& ftb->no_dupes, sizeof(TREE)); + ftb->last_word= 0; + + init_alloc_root(&ftb->mem_root, 1024, 1024); + ftb->queue.max_elements= 0; + if (!(ftbe=(FTB_EXPR *)alloc_root(&ftb->mem_root, sizeof(FTB_EXPR)))) + goto err; + ftbe->weight=1; + ftbe->flags=FTB_FLAG_YES; + ftbe->nos=1; + ftbe->up=0; + ftbe->max_docid= ftbe->ythresh= ftbe->yweaks= 0; + ftbe->docid[0]=ftbe->docid[1]=HA_OFFSET_ERROR; + ftbe->phrase= NULL; + ftbe->document= 0; + ftb->root=ftbe; + if (unlikely(_ftb_parse_query(ftb, query, query_len, + keynr == NO_SUCH_KEY ? &ft_default_parser : + info->s->keyinfo[keynr].parser))) + goto err; + /* + Hack: instead of init_queue, we'll use reinit queue to be able + to alloc queue with alloc_root() + */ + if (! (ftb->queue.root= (uchar **)alloc_root(&ftb->mem_root, + (ftb->queue.max_elements + 1) * + sizeof(void *)))) + goto err; + reinit_queue(&ftb->queue, ftb->queue.max_elements, 0, 0, + (int (*)(void*, uchar*, uchar*))FTB_WORD_cmp, 0); + for (ftbw= ftb->last_word; ftbw; ftbw= ftbw->prev) + queue_insert(&ftb->queue, (uchar *)ftbw); + ftb->list=(FTB_WORD **)alloc_root(&ftb->mem_root, + sizeof(FTB_WORD *)*ftb->queue.elements); + memcpy(ftb->list, ftb->queue.root+1, sizeof(FTB_WORD *)*ftb->queue.elements); + my_qsort2(ftb->list, ftb->queue.elements, sizeof(FTB_WORD *), + (qsort2_cmp)FTB_WORD_cmp_list, ftb->charset); + if (ftb->queue.elements<2) ftb->with_scan &= ~FTB_FLAG_TRUNC; + ftb->state=READY; + return ftb; +err: + free_root(& ftb->mem_root, MYF(0)); + my_free(ftb, MYF(0)); + return 0; +} + + +typedef struct st_my_ftb_phrase_param +{ + LIST *phrase; + LIST *document; + CHARSET_INFO *cs; + uint phrase_length; + uint document_length; + uint match; +} MY_FTB_PHRASE_PARAM; + + +static int ftb_phrase_add_word(MYSQL_FTPARSER_PARAM *param, + char *word, int word_len, + MYSQL_FTPARSER_BOOLEAN_INFO *boolean_info __attribute__((unused))) +{ + MY_FTB_PHRASE_PARAM *phrase_param= param->mysql_ftparam; + FT_WORD *w= (FT_WORD *)phrase_param->document->data; + LIST *phrase, *document; + w->pos= (uchar*) word; + w->len= word_len; + phrase_param->document= phrase_param->document->prev; + if (phrase_param->phrase_length > phrase_param->document_length) + { + phrase_param->document_length++; + return 0; + } + /* TODO: rewrite phrase search to avoid + comparing the same word twice. */ + for (phrase= phrase_param->phrase, document= phrase_param->document->next; + phrase; phrase= phrase->next, document= document->next) + { + FT_WORD *phrase_word= (FT_WORD *)phrase->data; + FT_WORD *document_word= (FT_WORD *)document->data; + if (my_strnncoll(phrase_param->cs, (uchar*) phrase_word->pos, + phrase_word->len, + (uchar*) document_word->pos, document_word->len)) + return 0; + } + phrase_param->match++; + return 0; +} + + +static int ftb_check_phrase_internal(MYSQL_FTPARSER_PARAM *param, + char *document, int len) +{ + FT_WORD word; + MY_FTB_PHRASE_PARAM *phrase_param= param->mysql_ftparam; + const uchar *docend= (uchar*) document + len; + while (maria_ft_simple_get_word(phrase_param->cs, (uchar**) &document, + docend, &word, FALSE)) + { + param->mysql_add_word(param, (char*) word.pos, word.len, 0); + if (phrase_param->match) + break; + } + return 0; +} + + +/* + Checks if given buffer matches phrase list. + + SYNOPSIS + _ftb_check_phrase() + s0 start of buffer + e0 end of buffer + phrase broken into list phrase + cs charset info + + RETURN VALUE + 1 is returned if phrase found, 0 else. + -1 is returned if error occurs. +*/ + +static int _ftb_check_phrase(FTB *ftb, const uchar *document, uint len, + FTB_EXPR *ftbe, struct st_mysql_ftparser *parser) +{ + MY_FTB_PHRASE_PARAM ftb_param; + MYSQL_FTPARSER_PARAM *param; + DBUG_ENTER("_ftb_check_phrase"); + DBUG_ASSERT(parser); + + if (! (param= maria_ftparser_call_initializer(ftb->info, ftb->keynr, 1))) + DBUG_RETURN(0); + ftb_param.phrase= ftbe->phrase; + ftb_param.document= ftbe->document; + ftb_param.cs= ftb->charset; + ftb_param.phrase_length= list_length(ftbe->phrase); + ftb_param.document_length= 1; + ftb_param.match= 0; + + param->mysql_parse= ftb_check_phrase_internal; + param->mysql_add_word= ftb_phrase_add_word; + param->mysql_ftparam= (void *)&ftb_param; + param->cs= ftb->charset; + param->doc= (char *) document; + param->length= len; + param->flags= 0; + param->mode= MYSQL_FTPARSER_WITH_STOPWORDS; + if (unlikely(parser->parse(param))) + return -1; + DBUG_RETURN(ftb_param.match ? 1 : 0); +} + + +static int _ftb_climb_the_tree(FTB *ftb, FTB_WORD *ftbw, FT_SEG_ITERATOR *ftsi_orig) +{ + FT_SEG_ITERATOR ftsi; + FTB_EXPR *ftbe; + float weight=ftbw->weight; + int yn_flag= ftbw->flags, ythresh, mode=(ftsi_orig != 0); + my_off_t curdoc=ftbw->docid[mode]; + struct st_mysql_ftparser *parser= ftb->keynr == NO_SUCH_KEY ? + &ft_default_parser : + ftb->info->s->keyinfo[ftb->keynr].parser; + + for (ftbe=ftbw->up; ftbe; ftbe=ftbe->up) + { + ythresh = ftbe->ythresh - (mode ? 0 : ftbe->yweaks); + if (ftbe->docid[mode] != curdoc) + { + ftbe->cur_weight=0; + ftbe->yesses=ftbe->nos=0; + ftbe->docid[mode]=curdoc; + } + if (ftbe->nos) + break; + if (yn_flag & FTB_FLAG_YES) + { + weight /= ftbe->ythresh; + ftbe->cur_weight += weight; + if ((int) ++ftbe->yesses == ythresh) + { + yn_flag=ftbe->flags; + weight=ftbe->cur_weight*ftbe->weight; + if (mode && ftbe->phrase) + { + int found= 0; + + memcpy(&ftsi, ftsi_orig, sizeof(ftsi)); + while (_ma_ft_segiterator(&ftsi) && !found) + { + if (!ftsi.pos) + continue; + found= _ftb_check_phrase(ftb, ftsi.pos, ftsi.len, ftbe, parser); + if (unlikely(found < 0)) + return 1; + } + if (!found) + break; + } /* ftbe->quot */ + } + else + break; + } + else + if (yn_flag & FTB_FLAG_NO) + { + /* + NOTE: special sort function of queue assures that all + (yn_flag & FTB_FLAG_NO) != 0 + events for every particular subexpression will + "auto-magically" happen BEFORE all the + (yn_flag & FTB_FLAG_YES) != 0 events. So no + already matched expression can become not-matched again. + */ + ++ftbe->nos; + break; + } + else + { + if (ftbe->ythresh) + weight/=3; + ftbe->cur_weight += weight; + if ((int) ftbe->yesses < ythresh) + break; + if (!(yn_flag & FTB_FLAG_WONLY)) + yn_flag= ((int) ftbe->yesses++ == ythresh) ? ftbe->flags : FTB_FLAG_WONLY ; + weight*= ftbe->weight; + } + } + return 0; +} + + +int maria_ft_boolean_read_next(FT_INFO *ftb, char *record) +{ + FTB_EXPR *ftbe; + FTB_WORD *ftbw; + MARIA_HA *info=ftb->info; + my_off_t curdoc; + + if (ftb->state != INDEX_SEARCH && ftb->state != INDEX_DONE) + return -1; + + /* black magic ON */ + if ((int) _ma_check_index(info, ftb->keynr) < 0) + return my_errno; + if (_ma_readinfo(info, F_RDLCK, 1)) + return my_errno; + /* black magic OFF */ + + if (!ftb->queue.elements) + return my_errno=HA_ERR_END_OF_FILE; + + /* Attention!!! Address of a local variable is used here! See err: label */ + ftb->queue.first_cmp_arg=(void *)&curdoc; + + while (ftb->state == INDEX_SEARCH && + (curdoc=((FTB_WORD *)queue_top(& ftb->queue))->docid[0]) != + HA_OFFSET_ERROR) + { + while (curdoc == (ftbw=(FTB_WORD *)queue_top(& ftb->queue))->docid[0]) + { + if (unlikely(_ftb_climb_the_tree(ftb, ftbw, 0))) + { + my_errno= HA_ERR_OUT_OF_MEM; + goto err; + } + + /* update queue */ + _ft2_search(ftb, ftbw, 0); + queue_replaced(& ftb->queue); + } + + ftbe=ftb->root; + if (ftbe->docid[0]==curdoc && ftbe->cur_weight>0 && + ftbe->yesses>=(ftbe->ythresh-ftbe->yweaks) && !ftbe->nos) + { + /* curdoc matched ! */ + if (is_tree_inited(&ftb->no_dupes) && + tree_insert(&ftb->no_dupes, &curdoc, 0, + ftb->no_dupes.custom_arg)->count >1) + /* but it managed already to get past this line once */ + continue; + + info->cur_row.lastpos= curdoc; + /* Clear all states, except that the table was updated */ + info->update&= (HA_STATE_CHANGED | HA_STATE_ROW_CHANGED); + + if (!(*info->read_record)(info, (uchar *) record, curdoc)) + { + info->update|= HA_STATE_AKTIV; /* Record is read */ + if (ftb->with_scan && + maria_ft_boolean_find_relevance(ftb, (uchar *) record, 0)==0) + continue; /* no match */ + my_errno=0; + goto err; + } + goto err; + } + } + ftb->state=INDEX_DONE; + my_errno=HA_ERR_END_OF_FILE; +err: + ftb->queue.first_cmp_arg=(void *)0; + return my_errno; +} + + +typedef struct st_my_ftb_find_param +{ + FT_INFO *ftb; + FT_SEG_ITERATOR *ftsi; +} MY_FTB_FIND_PARAM; + + +static int ftb_find_relevance_add_word(MYSQL_FTPARSER_PARAM *param, + char *word, int len, + MYSQL_FTPARSER_BOOLEAN_INFO *boolean_info __attribute__((unused))) +{ + MY_FTB_FIND_PARAM *ftb_param= param->mysql_ftparam; + FT_INFO *ftb= ftb_param->ftb; + FTB_WORD *ftbw; + int a, b, c; + /* + Find right-most element in the array of query words matching this + word from a document. + */ + for (a= 0, b= ftb->queue.elements, c= (a+b)/2; b-a>1; c= (a+b)/2) + { + ftbw= ftb->list[c]; + if (ha_compare_text(ftb->charset, (uchar*)word, len, + (uchar*)ftbw->word+1, ftbw->len-1, + (my_bool)(ftbw->flags&FTB_FLAG_TRUNC), 0) < 0) + b= c; + else + a= c; + } + /* + If there were no words with truncation operator, we iterate to the + beginning of an array until array element is equal to the word from + a document. This is done mainly because the same word may be + mentioned twice (or more) in the query. + + In case query has words with truncation operator we must iterate + to the beginning of the array. There may be non-matching query words + between matching word with truncation operator and the right-most + matching element. E.g., if we're looking for 'aaa15' in an array of + 'aaa1* aaa14 aaa15 aaa16'. + + Worse of that there still may be match even if the binary search + above didn't find matching element. E.g., if we're looking for + 'aaa15' in an array of 'aaa1* aaa14 aaa16'. The binary search will + stop at 'aaa16'. + */ + for (; c >= 0; c--) + { + ftbw= ftb->list[c]; + if (ha_compare_text(ftb->charset, (uchar*)word, len, + (uchar*)ftbw->word + 1,ftbw->len - 1, + (my_bool)(ftbw->flags & FTB_FLAG_TRUNC), 0)) + { + if (ftb->with_scan & FTB_FLAG_TRUNC) + continue; + else + break; + } + if (ftbw->docid[1] == ftb->info->cur_row.lastpos) + continue; + ftbw->docid[1]= ftb->info->cur_row.lastpos; + if (unlikely(_ftb_climb_the_tree(ftb, ftbw, ftb_param->ftsi))) + return 1; + } + return(0); +} + + +static int ftb_find_relevance_parse(MYSQL_FTPARSER_PARAM *param, + char *doc, int len) +{ + MY_FTB_FIND_PARAM *ftb_param= param->mysql_ftparam; + FT_INFO *ftb= ftb_param->ftb; + uchar *end= (uchar*) doc + len; + FT_WORD w; + while (maria_ft_simple_get_word(ftb->charset, (uchar**) &doc, + end, &w, TRUE)) + param->mysql_add_word(param, (char *) w.pos, w.len, 0); + return(0); +} + + +float maria_ft_boolean_find_relevance(FT_INFO *ftb, uchar *record, uint length) +{ + FTB_EXPR *ftbe; + FT_SEG_ITERATOR ftsi, ftsi2; + MARIA_RECORD_POS docid= ftb->info->cur_row.lastpos; + MY_FTB_FIND_PARAM ftb_param; + MYSQL_FTPARSER_PARAM *param; + struct st_mysql_ftparser *parser= ftb->keynr == NO_SUCH_KEY ? + &ft_default_parser : + ftb->info->s->keyinfo[ftb->keynr].parser; + + if (docid == HA_OFFSET_ERROR) + return -2.0; + if (!ftb->queue.elements) + return 0; + if (! (param= maria_ftparser_call_initializer(ftb->info, ftb->keynr, 0))) + return 0; + + if (ftb->state != INDEX_SEARCH && docid <= ftb->lastpos) + { + FTB_EXPR *x; + uint i; + + for (i=0; i < ftb->queue.elements; i++) + { + ftb->list[i]->docid[1]=HA_OFFSET_ERROR; + for (x=ftb->list[i]->up; x; x=x->up) + x->docid[1]=HA_OFFSET_ERROR; + } + } + + ftb->lastpos=docid; + + if (ftb->keynr==NO_SUCH_KEY) + _ma_ft_segiterator_dummy_init(record, length, &ftsi); + else + _ma_ft_segiterator_init(ftb->info, ftb->keynr, record, &ftsi); + memcpy(&ftsi2, &ftsi, sizeof(ftsi)); + + ftb_param.ftb= ftb; + ftb_param.ftsi= &ftsi2; + param->mysql_parse= ftb_find_relevance_parse; + param->mysql_add_word= ftb_find_relevance_add_word; + param->mysql_ftparam= (void *)&ftb_param; + param->flags= 0; + param->cs= ftb->charset; + param->mode= MYSQL_FTPARSER_SIMPLE_MODE; + + while (_ma_ft_segiterator(&ftsi)) + { + if (!ftsi.pos) + continue; + param->doc= (char *)ftsi.pos; + param->length= ftsi.len; + if (unlikely(parser->parse(param))) + return 0; + } + ftbe=ftb->root; + if (ftbe->docid[1]==docid && ftbe->cur_weight>0 && + ftbe->yesses>=ftbe->ythresh && !ftbe->nos) + { /* row matched ! */ + return ftbe->cur_weight; + } + else + { /* match failed ! */ + return 0.0; + } +} + + +void maria_ft_boolean_close_search(FT_INFO *ftb) +{ + if (is_tree_inited(& ftb->no_dupes)) + { + delete_tree(& ftb->no_dupes); + } + free_root(& ftb->mem_root, MYF(0)); + my_free(ftb, MYF(0)); +} + + +float maria_ft_boolean_get_relevance(FT_INFO *ftb) +{ + return ftb->root->cur_weight; +} + + +void maria_ft_boolean_reinit_search(FT_INFO *ftb) +{ + _ftb_init_index_search(ftb); +} diff --git a/storage/maria/ma_ft_eval.c b/storage/maria/ma_ft_eval.c new file mode 100644 index 00000000000..5fc67c6c664 --- /dev/null +++ b/storage/maria/ma_ft_eval.c @@ -0,0 +1,254 @@ +/* Copyright (C) 2006 MySQL AB & MySQL Finland AB & TCX DataKonsult 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 */ + +/* Written by Sergei A. Golubchik, who has a shared copyright to this code + added support for long options (my_getopt) 22.5.2002 by Jani Tolonen */ + +#include "ma_ftdefs.h" +#include "maria_ft_eval.h" +#include <stdarg.h> +#include <my_getopt.h> + +static void print_error(int exit_code, const char *fmt,...); +static void get_options(int argc, char *argv[]); +static int create_record(char *pos, FILE *file); +static void usage(); + +static struct my_option my_long_options[] = +{ + {"", 's', "", 0, 0, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + {"", 'q', "", 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"", 'S', "", 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"", '#', "", 0, 0, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + {"", 'V', "", 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"", '?', "", 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"", 'h', "", 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} +}; + +int main(int argc, char *argv[]) +{ + MARIA_HA *file; + int i,j; + + MY_INIT(argv[0]); + get_options(argc,argv); + bzero((char*)recinfo,sizeof(recinfo)); + + maria_init(); + /* First define 2 columns */ + recinfo[0].type=FIELD_SKIP_ENDSPACE; + recinfo[0].length=docid_length; + recinfo[1].type=FIELD_BLOB; + recinfo[1].length= 4+portable_sizeof_char_ptr; + + /* Define a key over the first column */ + keyinfo[0].seg=keyseg; + keyinfo[0].keysegs=1; + keyinfo[0].block_length= 0; /* Default block length */ + keyinfo[0].seg[0].type= HA_KEYTYPE_TEXT; + keyinfo[0].seg[0].flag= HA_BLOB_PART; + keyinfo[0].seg[0].start=recinfo[0].length; + keyinfo[0].seg[0].length=key_length; + keyinfo[0].seg[0].null_bit=0; + keyinfo[0].seg[0].null_pos=0; + keyinfo[0].seg[0].bit_start=4; + keyinfo[0].seg[0].language=MY_CHARSET_CURRENT; + keyinfo[0].flag = HA_FULLTEXT; + + if (!silent) + printf("- Creating isam-file\n"); + if (maria_create(filename,1,keyinfo,2,recinfo,0,NULL,(MARIA_CREATE_INFO*) 0,0)) + goto err; + if (!(file=maria_open(filename,2,0))) + goto err; + if (!silent) + printf("Initializing stopwords\n"); + maria_ft_init_stopwords(stopwordlist); + + if (!silent) + printf("- Writing key:s\n"); + + my_errno=0; + i=0; + while (create_record(record,df)) + { + error=maria_write(file,record); + if (error) + printf("I= %2d maria_write: %d errno: %d\n",i,error,my_errno); + i++; + } + fclose(df); + + if (maria_close(file)) goto err; + if (!silent) + printf("- Reopening file\n"); + if (!(file=maria_open(filename,2,0))) goto err; + if (!silent) + printf("- Reading rows with key\n"); + for (i=1;create_record(record,qf);i++) + { + FT_DOCLIST *result; + double w; + int t, err; + + result=maria_ft_nlq_init_search(file,0,blob_record,(uint) strlen(blob_record),1); + if (!result) + { + printf("Query %d failed with errno %3d\n",i,my_errno); + goto err; + } + if (!silent) + printf("Query %d. Found: %d.\n",i,result->ndocs); + for (j=0;(err=maria_ft_nlq_read_next(result, read_record))==0;j++) + { + t=uint2korr(read_record); + w=maria_ft_nlq_get_relevance(result); + printf("%d %.*s %f\n",i,t,read_record+2,w); + } + if (err != HA_ERR_END_OF_FILE) + { + printf("maria_ft_read_next %d failed with errno %3d\n",j,my_errno); + goto err; + } + maria_ft_nlq_close_search(result); + } + + if (maria_close(file)) goto err; + maria_end(); + my_end(MY_CHECK_ERROR); + + return (0); + + err: + printf("got error: %3d when using maria-database\n",my_errno); + return 1; /* skip warning */ + +} + + +static my_bool +get_one_option(int optid, const struct my_option *opt __attribute__((unused)), + char *argument) +{ + switch (optid) { + case 's': + if (stopwordlist && stopwordlist != maria_ft_precompiled_stopwords) + break; + { + FILE *f; char s[HA_FT_MAXLEN]; int i=0,n=SWL_INIT; + + if (!(stopwordlist=(const char**) malloc(n*sizeof(char *)))) + print_error(1,"malloc(%d)",n*sizeof(char *)); + if (!(f=fopen(argument,"r"))) + print_error(1,"fopen(%s)",argument); + while (!feof(f)) + { + if (!(fgets(s,HA_FT_MAXLEN,f))) + print_error(1,"fgets(s,%d,%s)",HA_FT_MAXLEN,argument); + if (!(stopwordlist[i++]=strdup(s))) + print_error(1,"strdup(%s)",s); + if (i >= n) + { + n+=SWL_PLUS; + if (!(stopwordlist=(const char**) realloc((char*) stopwordlist, + n*sizeof(char *)))) + print_error(1,"realloc(%d)",n*sizeof(char *)); + } + } + fclose(f); + stopwordlist[i]=NULL; + break; + } + case 'q': silent=1; break; + case 'S': if (stopwordlist==maria_ft_precompiled_stopwords) stopwordlist=NULL; break; + case '#': + DBUG_PUSH (argument); + break; + case 'V': + case '?': + case 'h': + usage(); + exit(1); + } + return 0; +} + + +static void get_options(int argc, char *argv[]) +{ + int ho_error; + + if ((ho_error=handle_options(&argc, &argv, my_long_options, get_one_option))) + exit(ho_error); + + if (!(d_file=argv[optind])) print_error(1,"No d_file"); + if (!(df=fopen(d_file,"r"))) + print_error(1,"fopen(%s)",d_file); + if (!(q_file=argv[optind+1])) print_error(1,"No q_file"); + if (!(qf=fopen(q_file,"r"))) + print_error(1,"fopen(%s)",q_file); + return; +} /* get options */ + + +static int create_record(char *pos, FILE *file) +{ + uint tmp; char *ptr; + + bzero((char *)pos,MAX_REC_LENGTH); + + /* column 1 - VARCHAR */ + if (!(fgets(pos+2,MAX_REC_LENGTH-32,file))) + { + if (feof(file)) + return 0; + else + print_error(1,"fgets(docid) - 1"); + } + tmp=(uint) strlen(pos+2)-1; + int2store(pos,tmp); + pos+=recinfo[0].length; + + /* column 2 - BLOB */ + + if (!(fgets(blob_record,MAX_BLOB_LENGTH,file))) + print_error(1,"fgets(docid) - 2"); + tmp=(uint) strlen(blob_record); + int4store(pos,tmp); + ptr=blob_record; + memcpy_fixed(pos+4,&ptr,sizeof(char*)); + return 1; +} + +/* VARARGS */ + +static void print_error(int exit_code, const char *fmt,...) +{ + va_list args; + + va_start(args,fmt); + fprintf(stderr,"%s: error: ",my_progname); + VOID(vfprintf(stderr, fmt, args)); + VOID(fputc('\n',stderr)); + fflush(stderr); + va_end(args); + exit(exit_code); +} + + +static void usage() +{ + printf("%s [options]\n", my_progname); + my_print_help(my_long_options); + my_print_variables(my_long_options); +} diff --git a/storage/maria/ma_ft_eval.h b/storage/maria/ma_ft_eval.h new file mode 100644 index 00000000000..481943dfb0b --- /dev/null +++ b/storage/maria/ma_ft_eval.h @@ -0,0 +1,41 @@ +/* Copyright (C) 2006 MySQL AB & Sergei A. Golubchik + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You 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 */ + +/* Written by Sergei A. Golubchik, who has a shared copyright to this code */ + +const char **stopwordlist=maria_ft_precompiled_stopwords; + +#define MAX_REC_LENGTH 128 +#define MAX_BLOB_LENGTH 60000 +char record[MAX_REC_LENGTH], read_record[MAX_REC_LENGTH+MAX_BLOB_LENGTH]; +char blob_record[MAX_BLOB_LENGTH+20*20]; + +char *filename= (char*) "EVAL"; + +int silent=0, error=0; + +uint key_length=MAX_BLOB_LENGTH,docid_length=32; +char *d_file, *q_file; +FILE *df,*qf; + +MARIA_COLUMNDEF recinfo[3]; +MARIA_KEYDEF keyinfo[2]; +HA_KEYSEG keyseg[10]; + +#define SWL_INIT 500 +#define SWL_PLUS 50 + +#define MAX_LINE_LENGTH 128 +char line[MAX_LINE_LENGTH]; diff --git a/storage/maria/ma_ft_nlq_search.c b/storage/maria/ma_ft_nlq_search.c new file mode 100644 index 00000000000..1a85c50174e --- /dev/null +++ b/storage/maria/ma_ft_nlq_search.c @@ -0,0 +1,377 @@ +/* Copyright (C) 2006 MySQL AB & MySQL Finland AB & TCX DataKonsult 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 */ + +/* Written by Sergei A. Golubchik, who has a shared copyright to this code */ + +#define FT_CORE +#include "ma_ftdefs.h" + +/* search with natural language queries */ + +typedef struct ft_doc_rec +{ + my_off_t dpos; + double weight; +} FT_DOC; + +struct st_ft_info +{ + struct _ft_vft *please; + MARIA_HA *info; + int ndocs; + int curdoc; + FT_DOC doc[1]; +}; + +typedef struct st_all_in_one +{ + MARIA_HA *info; + uint keynr; + CHARSET_INFO *charset; + uchar *keybuff; + TREE dtree; +} ALL_IN_ONE; + +typedef struct st_ft_superdoc +{ + FT_DOC doc; + FT_WORD *word_ptr; + double tmp_weight; +} FT_SUPERDOC; + +static int FT_SUPERDOC_cmp(void* cmp_arg __attribute__((unused)), + FT_SUPERDOC *p1, FT_SUPERDOC *p2) +{ + if (p1->doc.dpos < p2->doc.dpos) + return -1; + if (p1->doc.dpos == p2->doc.dpos) + return 0; + return 1; +} + +static int walk_and_match(FT_WORD *word, uint32 count, ALL_IN_ONE *aio) +{ + int subkeys, r; + uint doc_cnt; + FT_SUPERDOC sdoc, *sptr; + TREE_ELEMENT *selem; + double gweight=1; + MARIA_HA *info= aio->info; + uchar *keybuff= aio->keybuff; + MARIA_KEYDEF *keyinfo= info->s->keyinfo+aio->keynr; + my_off_t key_root=info->s->state.key_root[aio->keynr]; + uint extra=HA_FT_WLEN+info->s->base.rec_reflength; + MARIA_KEY key; +#if HA_FT_WTYPE == HA_KEYTYPE_FLOAT + float tmp_weight; +#else +#error +#endif + DBUG_ENTER("walk_and_match"); + + word->weight=LWS_FOR_QUERY; + + _ma_ft_make_key(info, &key, aio->keynr, keybuff, word, 0); + key.data_length-= HA_FT_WLEN; + doc_cnt=0; + + /* Skip rows inserted by current inserted */ + for (r= _ma_search(info, &key, SEARCH_FIND, key_root) ; + !r && + (subkeys=ft_sintXkorr(info->last_key.data + + info->last_key.data_length + + info->last_key.ref_length - extra)) > 0 && + info->cur_row.lastpos >= info->state->data_file_length ; + r= _ma_search_next(info, &info->last_key, SEARCH_BIGGER, key_root)) + ; + + info->update|= HA_STATE_AKTIV; /* for _ma_test_if_changed() */ + + /* The following should be safe, even if we compare doubles */ + while (!r && gweight) + { + + if (key.data_length && + ha_compare_text(aio->charset, + info->last_key.data+1, + info->last_key.data_length + + info->last_key.ref_length - extra - 1, + key.data+1, key.data_length-1, 0, 0)) + break; + + if (subkeys<0) + { + if (doc_cnt) + DBUG_RETURN(1); /* index is corrupted */ + /* + TODO here: unsafe optimization, should this word + be skipped (based on subkeys) ? + */ + keybuff+= key.data_length; + keyinfo= &info->s->ft2_keyinfo; + key_root= info->cur_row.lastpos; + key.data_length= 0; + r= _ma_search_first(info, keyinfo, key_root); + goto do_skip; + } +#if HA_FT_WTYPE == HA_KEYTYPE_FLOAT + tmp_weight=*(float*)&subkeys; +#else +#error +#endif + /* The following should be safe, even if we compare doubles */ + if (tmp_weight==0) + DBUG_RETURN(doc_cnt); /* stopword, doc_cnt should be 0 */ + + sdoc.doc.dpos= info->cur_row.lastpos; + + /* saving document matched into dtree */ + if (!(selem=tree_insert(&aio->dtree, &sdoc, 0, aio->dtree.custom_arg))) + DBUG_RETURN(1); + + sptr=(FT_SUPERDOC *)ELEMENT_KEY((&aio->dtree), selem); + + if (selem->count==1) /* document's first match */ + sptr->doc.weight=0; + else + sptr->doc.weight+=sptr->tmp_weight*sptr->word_ptr->weight; + + sptr->word_ptr=word; + sptr->tmp_weight=tmp_weight; + + doc_cnt++; + + gweight=word->weight*GWS_IN_USE; + if (gweight < 0 || doc_cnt > 2000000) + gweight=0; + + if (_ma_test_if_changed(info) == 0) + r= _ma_search_next(info, &info->last_key, SEARCH_BIGGER, key_root); + else + r= _ma_search(info, &info->last_key, SEARCH_BIGGER, key_root); +do_skip: + while ((subkeys=ft_sintXkorr(info->last_key.data + + info->last_key.data_length + + info->last_key.ref_length - extra)) > 0 && + !r && info->cur_row.lastpos >= info->state->data_file_length) + r= _ma_search_next(info, &info->last_key, SEARCH_BIGGER, key_root); + + } + word->weight=gweight; + + DBUG_RETURN(0); +} + + +static int walk_and_copy(FT_SUPERDOC *from, + uint32 count __attribute__((unused)), FT_DOC **to) +{ + DBUG_ENTER("walk_and_copy"); + from->doc.weight+=from->tmp_weight*from->word_ptr->weight; + (*to)->dpos=from->doc.dpos; + (*to)->weight=from->doc.weight; + (*to)++; + DBUG_RETURN(0); +} + +static int walk_and_push(FT_SUPERDOC *from, + uint32 count __attribute__((unused)), QUEUE *best) +{ + DBUG_ENTER("walk_and_copy"); + from->doc.weight+=from->tmp_weight*from->word_ptr->weight; + set_if_smaller(best->elements, ft_query_expansion_limit-1); + queue_insert(best, (uchar *)& from->doc); + DBUG_RETURN(0); +} + + +static int FT_DOC_cmp(void *unused __attribute__((unused)), + FT_DOC *a, FT_DOC *b) +{ + return sgn(b->weight - a->weight); +} + + +FT_INFO *maria_ft_init_nlq_search(MARIA_HA *info, uint keynr, uchar *query, + uint query_len, uint flags, uchar *record) +{ + TREE wtree; + ALL_IN_ONE aio; + FT_DOC *dptr; + FT_INFO *dlist=NULL; + MARIA_RECORD_POS saved_lastpos= info->cur_row.lastpos; + struct st_mysql_ftparser *parser; + MYSQL_FTPARSER_PARAM *ftparser_param; + DBUG_ENTER("maria_ft_init_nlq_search"); + + /* black magic ON */ + if ((int) (keynr = _ma_check_index(info,keynr)) < 0) + DBUG_RETURN(NULL); + if (_ma_readinfo(info,F_RDLCK,1)) + DBUG_RETURN(NULL); + /* black magic OFF */ + + aio.info=info; + aio.keynr=keynr; + aio.charset=info->s->keyinfo[keynr].seg->charset; + aio.keybuff= info->lastkey_buff2; + parser= info->s->keyinfo[keynr].parser; + if (! (ftparser_param= maria_ftparser_call_initializer(info, keynr, 0))) + goto err; + + bzero(&wtree,sizeof(wtree)); + + init_tree(&aio.dtree,0,0,sizeof(FT_SUPERDOC),(qsort_cmp2)&FT_SUPERDOC_cmp,0, + NULL, NULL); + + maria_ft_parse_init(&wtree, aio.charset); + ftparser_param->flags= 0; + if (maria_ft_parse(&wtree, query, query_len, parser, ftparser_param, + &wtree.mem_root)) + goto err; + + if (tree_walk(&wtree, (tree_walk_action)&walk_and_match, &aio, + left_root_right)) + goto err; + + if (flags & FT_EXPAND && ft_query_expansion_limit) + { + QUEUE best; + init_queue(&best,ft_query_expansion_limit,0,0, (queue_compare) &FT_DOC_cmp, + 0); + tree_walk(&aio.dtree, (tree_walk_action) &walk_and_push, + &best, left_root_right); + while (best.elements) + { + my_off_t docid=((FT_DOC *)queue_remove(& best, 0))->dpos; + if (!(*info->read_record)(info, record, docid)) + { + info->update|= HA_STATE_AKTIV; + ftparser_param->flags= MYSQL_FTFLAGS_NEED_COPY; + if (unlikely(_ma_ft_parse(&wtree, info, keynr, record, ftparser_param, + &wtree.mem_root))) + { + delete_queue(&best); + goto err; + } + } + } + delete_queue(&best); + reset_tree(&aio.dtree); + if (tree_walk(&wtree, (tree_walk_action)&walk_and_match, &aio, + left_root_right)) + goto err; + + } + + /* + If ndocs == 0, this will not allocate RAM for FT_INFO.doc[], + so if ndocs == 0, FT_INFO.doc[] must not be accessed. + */ + dlist=(FT_INFO *)my_malloc(sizeof(FT_INFO)+ + sizeof(FT_DOC)* + (int)(aio.dtree.elements_in_tree-1), + MYF(0)); + if (!dlist) + goto err; + + dlist->please= (struct _ft_vft *) & _ma_ft_vft_nlq; + dlist->ndocs=aio.dtree.elements_in_tree; + dlist->curdoc=-1; + dlist->info=aio.info; + dptr=dlist->doc; + + tree_walk(&aio.dtree, (tree_walk_action) &walk_and_copy, + &dptr, left_root_right); + + if (flags & FT_SORTED) + my_qsort2(dlist->doc, dlist->ndocs, sizeof(FT_DOC), + (qsort2_cmp)&FT_DOC_cmp, 0); + +err: + delete_tree(&aio.dtree); + delete_tree(&wtree); + info->cur_row.lastpos= saved_lastpos; + DBUG_RETURN(dlist); +} + + +int maria_ft_nlq_read_next(FT_INFO *handler, char *record) +{ + MARIA_HA *info= (MARIA_HA *) handler->info; + + if (++handler->curdoc >= handler->ndocs) + { + --handler->curdoc; + return HA_ERR_END_OF_FILE; + } + + info->update&= (HA_STATE_CHANGED | HA_STATE_ROW_CHANGED); + + info->cur_row.lastpos= handler->doc[handler->curdoc].dpos; + if (!(*info->read_record)(info, (uchar *) record, info->cur_row.lastpos)) + { + info->update|= HA_STATE_AKTIV; /* Record is read */ + return 0; + } + return my_errno; +} + + +float maria_ft_nlq_find_relevance(FT_INFO *handler, + uchar *record __attribute__((unused)), + uint length __attribute__((unused))) +{ + int a,b,c; + FT_DOC *docs=handler->doc; + MARIA_RECORD_POS docid= handler->info->cur_row.lastpos; + + if (docid == HA_POS_ERROR) + return -5.0; + + /* Assuming docs[] is sorted by dpos... */ + + for (a=0, b=handler->ndocs, c=(a+b)/2; b-a>1; c=(a+b)/2) + { + if (docs[c].dpos > docid) + b=c; + else + a=c; + } + /* bounds check to avoid accessing unallocated handler->doc */ + if (a < handler->ndocs && docs[a].dpos == docid) + return (float) docs[a].weight; + else + return 0.0; +} + + +void maria_ft_nlq_close_search(FT_INFO *handler) +{ + my_free(handler, MYF(0)); +} + + +float maria_ft_nlq_get_relevance(FT_INFO *handler) +{ + return (float) handler->doc[handler->curdoc].weight; +} + + +void maria_ft_nlq_reinit_search(FT_INFO *handler) +{ + handler->curdoc=-1; +} + diff --git a/storage/maria/ma_ft_parser.c b/storage/maria/ma_ft_parser.c new file mode 100644 index 00000000000..bdfbbb936ce --- /dev/null +++ b/storage/maria/ma_ft_parser.c @@ -0,0 +1,413 @@ +/* Copyright (C) 2006 MySQL AB & MySQL Finland AB & TCX DataKonsult 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 */ + +/* Written by Sergei A. Golubchik, who has a shared copyright to this code */ + +#include "ma_ftdefs.h" + +typedef struct st_maria_ft_docstat { + FT_WORD *list; + uint uniq; + double sum; +} FT_DOCSTAT; + + +typedef struct st_my_maria_ft_parser_param +{ + TREE *wtree; + MEM_ROOT *mem_root; +} MY_FT_PARSER_PARAM; + + +static int FT_WORD_cmp(CHARSET_INFO* cs, FT_WORD *w1, FT_WORD *w2) +{ + return ha_compare_text(cs, (uchar*) w1->pos, w1->len, + (uchar*) w2->pos, w2->len, 0, 0); +} + +static int walk_and_copy(FT_WORD *word,uint32 count,FT_DOCSTAT *docstat) +{ + word->weight=LWS_IN_USE; + docstat->sum+=word->weight; + memcpy_fixed((docstat->list)++,word,sizeof(FT_WORD)); + return 0; +} + +/* transforms tree of words into the array, applying normalization */ + +FT_WORD * maria_ft_linearize(TREE *wtree, MEM_ROOT *mem_root) +{ + FT_WORD *wlist,*p; + FT_DOCSTAT docstat; + DBUG_ENTER("maria_ft_linearize"); + + if ((wlist=(FT_WORD *) alloc_root(mem_root, sizeof(FT_WORD)* + (1+wtree->elements_in_tree)))) + { + docstat.list=wlist; + docstat.uniq=wtree->elements_in_tree; + docstat.sum=0; + tree_walk(wtree,(tree_walk_action)&walk_and_copy,&docstat,left_root_right); + } + delete_tree(wtree); + if (!wlist) + DBUG_RETURN(NULL); + + docstat.list->pos=NULL; + + for (p=wlist;p->pos;p++) + { + p->weight=PRENORM_IN_USE; + } + + for (p=wlist;p->pos;p++) + { + p->weight/=NORM_IN_USE; + } + + DBUG_RETURN(wlist); +} + +my_bool maria_ft_boolean_check_syntax_string(const uchar *str) +{ + uint i, j; + + if (!str || + (strlen((const char *) str) + 1 != sizeof(ft_boolean_syntax)) || + (str[0] != ' ' && str[1] != ' ')) + return 1; + for (i=0; i<sizeof(ft_boolean_syntax); i++) + { + /* limiting to 7-bit ascii only */ + if ((unsigned char)(str[i]) > 127 || + my_isalnum(default_charset_info, str[i])) + return 1; + for (j=0; j<i; j++) + if (str[i] == str[j] && (i != 11 || j != 10)) + return 1; + } + return 0; +} + +/* + RETURN VALUE + 0 - eof + 1 - word found + 2 - left bracket + 3 - right bracket + 4 - stopword found +*/ +uchar maria_ft_get_word(CHARSET_INFO *cs, uchar **start, uchar *end, + FT_WORD *word, MYSQL_FTPARSER_BOOLEAN_INFO *param) +{ + uchar *doc=*start; + int ctype; + uint mwc, length; + int mbl; + + param->yesno=(FTB_YES==' ') ? 1 : (param->quot != 0); + param->weight_adjust= param->wasign= 0; + param->type= FT_TOKEN_EOF; + + while (doc<end) + { + for (; doc < end; doc+= (mbl > 0 ? mbl : (mbl < 0 ? -mbl : 1))) + { + mbl= cs->cset->ctype(cs, &ctype, doc, end); + if (true_word_char(ctype, *doc)) + break; + if (*doc == FTB_RQUOT && param->quot) + { + param->quot= (char *) doc; + *start=doc+1; + param->type= FT_TOKEN_RIGHT_PAREN; + goto ret; + } + if (!param->quot) + { + if (*doc == FTB_LBR || *doc == FTB_RBR || *doc == FTB_LQUOT) + { + /* param->prev=' '; */ + *start=doc+1; + if (*doc == FTB_LQUOT) + param->quot= (char *) *start; + param->type= (*doc == FTB_RBR ? FT_TOKEN_RIGHT_PAREN : FT_TOKEN_LEFT_PAREN); + goto ret; + } + if (param->prev == ' ') + { + if (*doc == FTB_YES ) { param->yesno=+1; continue; } else + if (*doc == FTB_EGAL) { param->yesno= 0; continue; } else + if (*doc == FTB_NO ) { param->yesno=-1; continue; } else + if (*doc == FTB_INC ) { param->weight_adjust++; continue; } else + if (*doc == FTB_DEC ) { param->weight_adjust--; continue; } else + if (*doc == FTB_NEG ) { param->wasign= !param->wasign; continue; } + } + } + param->prev=*doc; + param->yesno=(FTB_YES==' ') ? 1 : (param->quot != 0); + param->weight_adjust= param->wasign= 0; + } + + mwc=length=0; + for (word->pos= doc; doc < end; length++, + doc+= (mbl > 0 ? mbl : (mbl < 0 ? -mbl : 1))) + { + mbl= cs->cset->ctype(cs, &ctype, doc, end); + if (true_word_char(ctype, *doc)) + mwc=0; + else if (!misc_word_char(*doc) || mwc) + break; + else + mwc++; + } + param->prev='A'; /* be sure *prev is true_word_char */ + word->len= (uint)(doc-word->pos) - mwc; + if ((param->trunc=(doc<end && *doc == FTB_TRUNC))) + doc++; + + if (((length >= ft_min_word_len && !is_stopword((char *) word->pos, + word->len)) + || param->trunc) && length < ft_max_word_len) + { + *start=doc; + param->type= FT_TOKEN_WORD; + goto ret; + } + else if (length) /* make sure length > 0 (if start contains spaces only) */ + { + *start= doc; + param->type= FT_TOKEN_STOPWORD; + goto ret; + } + } + if (param->quot) + { + param->quot= (char *)(*start= doc); + param->type= 3; /* FT_RBR */ + goto ret; + } +ret: + return param->type; +} + +uchar maria_ft_simple_get_word(CHARSET_INFO *cs, uchar **start, + const uchar *end, FT_WORD *word, + my_bool skip_stopwords) +{ + uchar *doc= *start; + uint mwc, length; + int ctype, mbl; + DBUG_ENTER("maria_ft_simple_get_word"); + + do + { + for (;; doc+= (mbl > 0 ? mbl : (mbl < 0 ? -mbl : 1))) + { + if (doc >= end) + DBUG_RETURN(0); + mbl= cs->cset->ctype(cs, &ctype, doc, end); + if (true_word_char(ctype, *doc)) + break; + } + + mwc= length= 0; + for (word->pos= doc; doc < end; length++, + doc+= (mbl > 0 ? mbl : (mbl < 0 ? -mbl : 1))) + { + mbl= cs->cset->ctype(cs, &ctype, doc, end); + if (true_word_char(ctype, *doc)) + mwc= 0; + else if (!misc_word_char(*doc) || mwc) + break; + else + mwc++; + } + + word->len= (uint)(doc-word->pos) - mwc; + + if (skip_stopwords == FALSE || + (length >= ft_min_word_len && length < ft_max_word_len && + !is_stopword((char *) word->pos, word->len))) + { + *start= doc; + DBUG_RETURN(1); + } + } while (doc < end); + DBUG_RETURN(0); +} + +void maria_ft_parse_init(TREE *wtree, CHARSET_INFO *cs) +{ + DBUG_ENTER("maria_ft_parse_init"); + if (!is_tree_inited(wtree)) + init_tree(wtree,0,0,sizeof(FT_WORD),(qsort_cmp2)&FT_WORD_cmp,0,NULL, cs); + DBUG_VOID_RETURN; +} + + +static int maria_ft_add_word(MYSQL_FTPARSER_PARAM *param, + char *word, int word_len, + MYSQL_FTPARSER_BOOLEAN_INFO *boolean_info __attribute__((unused))) +{ + TREE *wtree; + FT_WORD w; + MY_FT_PARSER_PARAM *ft_param=param->mysql_ftparam; + DBUG_ENTER("maria_ft_add_word"); + wtree= ft_param->wtree; + if (param->flags & MYSQL_FTFLAGS_NEED_COPY) + { + uchar *ptr; + DBUG_ASSERT(wtree->with_delete == 0); + ptr= (uchar *)alloc_root(ft_param->mem_root, word_len); + memcpy(ptr, word, word_len); + w.pos= ptr; + } + else + w.pos= (uchar *) word; + w.len= word_len; + if (!tree_insert(wtree, &w, 0, wtree->custom_arg)) + { + delete_tree(wtree); + DBUG_RETURN(1); + } + DBUG_RETURN(0); +} + + +static int maria_ft_parse_internal(MYSQL_FTPARSER_PARAM *param, + char *doc_arg, int doc_len) +{ + uchar *doc= (uchar*) doc_arg; + uchar *end= doc + doc_len; + MY_FT_PARSER_PARAM *ft_param=param->mysql_ftparam; + TREE *wtree= ft_param->wtree; + FT_WORD w; + DBUG_ENTER("maria_ft_parse_internal"); + + while (maria_ft_simple_get_word(wtree->custom_arg, &doc, end, &w, TRUE)) + if (param->mysql_add_word(param, (char *) w.pos, w.len, 0)) + DBUG_RETURN(1); + DBUG_RETURN(0); +} + + +int maria_ft_parse(TREE *wtree, uchar *doc, int doclen, + struct st_mysql_ftparser *parser, + MYSQL_FTPARSER_PARAM *param, MEM_ROOT *mem_root) +{ + MY_FT_PARSER_PARAM my_param; + DBUG_ENTER("maria_ft_parse"); + DBUG_ASSERT(parser); + my_param.wtree= wtree; + my_param.mem_root= mem_root; + + param->mysql_parse= maria_ft_parse_internal; + param->mysql_add_word= maria_ft_add_word; + param->mysql_ftparam= &my_param; + param->cs= wtree->custom_arg; + param->doc= (char *) doc; + param->length= doclen; + param->mode= MYSQL_FTPARSER_SIMPLE_MODE; + DBUG_RETURN(parser->parse(param)); +} + + +#define MAX_PARAM_NR 2 + +MYSQL_FTPARSER_PARAM* maria_ftparser_alloc_param(MARIA_HA *info) +{ + if (!info->ftparser_param) + { + /* +. info->ftparser_param can not be zero after the initialization, + because it always includes built-in fulltext parser. And built-in + parser can be called even if the table has no fulltext indexes and + no varchar/text fields. + + ftb_find_relevance... parser (ftb_find_relevance_parse, + ftb_find_relevance_add_word) calls ftb_check_phrase... parser + (ftb_check_phrase_internal, ftb_phrase_add_word). Thus MAX_PARAM_NR=2. + */ + info->ftparser_param= (MYSQL_FTPARSER_PARAM *) + my_malloc(MAX_PARAM_NR * sizeof(MYSQL_FTPARSER_PARAM) * + info->s->ftkeys, MYF(MY_WME | MY_ZEROFILL)); + init_alloc_root(&info->ft_memroot, FTPARSER_MEMROOT_ALLOC_SIZE, 0); + } + return info->ftparser_param; +} + + +MYSQL_FTPARSER_PARAM *maria_ftparser_call_initializer(MARIA_HA *info, + uint keynr, uint paramnr) +{ + uint32 ftparser_nr; + struct st_mysql_ftparser *parser; + + if (!maria_ftparser_alloc_param(info)) + return 0; + + if (keynr == NO_SUCH_KEY) + { + ftparser_nr= 0; + parser= &ft_default_parser; + } + else + { + ftparser_nr= info->s->keyinfo[keynr].ftkey_nr; + parser= info->s->keyinfo[keynr].parser; + } + DBUG_ASSERT(paramnr < MAX_PARAM_NR); + ftparser_nr= ftparser_nr*MAX_PARAM_NR + paramnr; + if (! info->ftparser_param[ftparser_nr].mysql_add_word) + { + /* Note, that mysql_add_word is used here as a flag: + mysql_add_word == 0 - parser is not initialized + mysql_add_word != 0 - parser is initialized, or no + initialization needed. */ + info->ftparser_param[ftparser_nr].mysql_add_word= + (int (*)(struct st_mysql_ftparser_param *, char *, int, + MYSQL_FTPARSER_BOOLEAN_INFO *)) 1; + if (parser->init && parser->init(&info->ftparser_param[ftparser_nr])) + return 0; + } + return &info->ftparser_param[ftparser_nr]; +} + + +void maria_ftparser_call_deinitializer(MARIA_HA *info) +{ + uint i, j, keys= info->s->state.header.keys; + free_root(&info->ft_memroot, MYF(0)); + if (! info->ftparser_param) + return; + for (i= 0; i < keys; i++) + { + MARIA_KEYDEF *keyinfo= &info->s->keyinfo[i]; + for (j=0; j < MAX_PARAM_NR; j++) + { + MYSQL_FTPARSER_PARAM *ftparser_param= + &info->ftparser_param[keyinfo->ftkey_nr*MAX_PARAM_NR + j]; + if (keyinfo->flag & HA_FULLTEXT && ftparser_param->mysql_add_word) + { + if (keyinfo->parser->deinit) + keyinfo->parser->deinit(ftparser_param); + ftparser_param->mysql_add_word= 0; + } + else + break; + } + } +} diff --git a/storage/maria/ma_ft_stem.c b/storage/maria/ma_ft_stem.c new file mode 100644 index 00000000000..06fc0b2df6c --- /dev/null +++ b/storage/maria/ma_ft_stem.c @@ -0,0 +1,18 @@ +/* Copyright (C) 2006 MySQL AB & MySQL Finland AB & TCX DataKonsult 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 */ + +/* Written by Sergei A. Golubchik, who has a shared copyright to this code */ + +/* mulitingual stem */ diff --git a/storage/maria/ma_ft_test1.c b/storage/maria/ma_ft_test1.c new file mode 100644 index 00000000000..4c98e766234 --- /dev/null +++ b/storage/maria/ma_ft_test1.c @@ -0,0 +1,317 @@ +/* Copyright (C) 2006 MySQL AB & MySQL Finland AB & TCX DataKonsult 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 */ + +/* Written by Sergei A. Golubchik, who has a shared copyright to this code + added support for long options (my_getopt) 22.5.2002 by Jani Tolonen */ + +#include "ma_ftdefs.h" +#include "maria_ft_test1.h" +#include <my_getopt.h> + +static int key_field=FIELD_VARCHAR,extra_field=FIELD_SKIP_ENDSPACE; +static uint key_length=200,extra_length=50; +static int key_type=HA_KEYTYPE_TEXT; +static int verbose=0,silent=0,skip_update=0, + no_keys=0,no_stopwords=0,no_search=0,no_fulltext=0; +static int create_flag=0,error=0; + +#define MAX_REC_LENGTH 300 +static char record[MAX_REC_LENGTH],read_record[MAX_REC_LENGTH]; + +static int run_test(const char *filename); +static void get_options(int argc, char *argv[]); +static void create_record(char *, int); +static void usage(); + +static struct my_option my_long_options[] = +{ + {"", 'v', "", 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"", '?', "", 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"", 'h', "", 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"", 'V', "", 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"", 'v', "", 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"", 's', "", 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"", 'N', "", 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"", 'S', "", 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"", 'K', "", 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"", 'F', "", 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"", 'U', "", 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"", '#', "", 0, 0, 0, GET_STR, OPT_ARG, 0, 0, 0, 0, 0, 0}, + { 0, 0, 0, 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0} +}; + +int main(int argc, char *argv[]) +{ + MY_INIT(argv[0]); + + get_options(argc,argv); + maria_init(); + + exit(run_test("FT1")); +} + +static MARIA_COLUMNDEF recinfo[3]; +static MARIA_KEYDEF keyinfo[2]; +static HA_KEYSEG keyseg[10]; + +static int run_test(const char *filename) +{ + MARIA_HA *file; + int i,j; + my_off_t pos; + + bzero((char*) recinfo,sizeof(recinfo)); + + /* First define 2 columns */ + recinfo[0].type=extra_field; + recinfo[0].length= (extra_field == FIELD_BLOB ? 4 + portable_sizeof_char_ptr : + extra_length); + if (extra_field == FIELD_VARCHAR) + recinfo[0].length+= HA_VARCHAR_PACKLENGTH(extra_length); + recinfo[1].type=key_field; + recinfo[1].length= (key_field == FIELD_BLOB ? 4+portable_sizeof_char_ptr : + key_length); + if (key_field == FIELD_VARCHAR) + recinfo[1].length+= HA_VARCHAR_PACKLENGTH(key_length); + + /* Define a key over the first column */ + keyinfo[0].seg=keyseg; + keyinfo[0].keysegs=1; + keyinfo[0].block_length= 0; /* Default block length */ + keyinfo[0].seg[0].type= key_type; + keyinfo[0].seg[0].flag= (key_field == FIELD_BLOB) ? HA_BLOB_PART: + (key_field == FIELD_VARCHAR) ? HA_VAR_LENGTH_PART:0; + keyinfo[0].seg[0].start=recinfo[0].length; + keyinfo[0].seg[0].length=key_length; + keyinfo[0].seg[0].null_bit= 0; + keyinfo[0].seg[0].null_pos=0; + keyinfo[0].seg[0].language= default_charset_info->number; + keyinfo[0].flag = (no_fulltext?HA_PACK_KEY:HA_FULLTEXT); + + if (!silent) + printf("- Creating isam-file\n"); + if (maria_create(filename,(no_keys?0:1),keyinfo,2,recinfo,0,NULL, + (MARIA_CREATE_INFO*) 0, create_flag)) + goto err; + if (!(file=maria_open(filename,2,0))) + goto err; + + if (!silent) + printf("- %s stopwords\n",no_stopwords?"Skipping":"Initializing"); + maria_ft_init_stopwords(no_stopwords?NULL:maria_ft_precompiled_stopwords); + + if (!silent) + printf("- Writing key:s\n"); + + my_errno=0; + for (i=NUPD ; i<NDATAS; i++ ) + { + create_record(record,i); + error=maria_write(file,record); + if (verbose || error) + printf("I= %2d maria_write: %d errno: %d, record: %s\n", + i,error,my_errno,data[i].f0); + } + + if (!skip_update) + { + if (!silent) + printf("- Updating rows\n"); + + /* Read through all rows and update them */ + pos=(ha_rows) 0; + i=0; + while ((error=maria_rrnd(file,read_record,pos)) == 0) + { + create_record(record,NUPD-i-1); + if (maria_update(file,read_record,record)) + { + printf("Can't update row: %.*s, error: %d\n", + keyinfo[0].seg[0].length,record,my_errno); + } + if(++i == NUPD) break; + pos=HA_OFFSET_ERROR; + } + if (i != NUPD) + printf("Found %d of %d rows\n", i,NUPD); + } + + if (maria_close(file)) goto err; + if(no_search) return 0; + if (!silent) + printf("- Reopening file\n"); + if (!(file=maria_open(filename,2,0))) goto err; + if (!silent) + printf("- Reading rows with key\n"); + for (i=0 ; i < NQUERIES ; i++) + { + FT_DOCLIST *result; + result=maria_ft_nlq_init_search(file,0,(char*) query[i],strlen(query[i]),1); + if(!result) + { + printf("Query %d: `%s' failed with errno %3d\n",i,query[i],my_errno); + continue; + } + printf("Query %d: `%s'. Found: %d. Top five documents:\n", + i,query[i],result->ndocs); + for (j=0;j<5;j++) + { + double w; int err; + err= maria_ft_nlq_read_next(result, read_record); + if (err==HA_ERR_END_OF_FILE) + { + printf("No more matches!\n"); + break; + } + else if (err) + { + printf("maria_ft_read_next %d failed with errno %3d\n",j,my_errno); + break; + } + w=maria_ft_nlq_get_relevance(result); + if (key_field == FIELD_VARCHAR) + { + uint l; + char *p; + p=recinfo[0].length+read_record; + l=uint2korr(p); + printf("%10.7f: %.*s\n",w,(int) l,p+2); + } + else + printf("%10.7f: %.*s\n",w,recinfo[1].length, + recinfo[0].length+read_record); + } + maria_ft_nlq_close_search(result); + } + + if (maria_close(file)) goto err; + maria_end(); + my_end(MY_CHECK_ERROR); + + return (0); +err: + printf("got error: %3d when using maria-database\n",my_errno); + return 1; /* skip warning */ +} + +static char blob_key[MAX_REC_LENGTH]; +/* static char blob_record[MAX_REC_LENGTH+20*20]; */ + +void create_record(char *pos, int n) +{ + bzero((char*) pos,MAX_REC_LENGTH); + if (recinfo[0].type == FIELD_BLOB) + { + uint tmp; + char *ptr; + strnmov(blob_key,data[n].f0,keyinfo[0].seg[0].length); + tmp=strlen(blob_key); + int4store(pos,tmp); + ptr=blob_key; + memcpy_fixed(pos+4,&ptr,sizeof(char*)); + pos+=recinfo[0].length; + } + else if (recinfo[0].type == FIELD_VARCHAR) + { + uint tmp; + /* -1 is here because pack_length is stored in seg->length */ + uint pack_length= HA_VARCHAR_PACKLENGTH(keyinfo[0].seg[0].length-1); + strnmov(pos+pack_length,data[n].f0,keyinfo[0].seg[0].length); + tmp=strlen(pos+pack_length); + if (pack_length == 1) + *pos= (char) tmp; + else + int2store(pos,tmp); + pos+=recinfo[0].length; + } + else + { + strnmov(pos,data[n].f0,keyinfo[0].seg[0].length); + pos+=recinfo[0].length; + } + if (recinfo[1].type == FIELD_BLOB) + { + uint tmp; + char *ptr; + strnmov(blob_key,data[n].f2,keyinfo[0].seg[0].length); + tmp=strlen(blob_key); + int4store(pos,tmp); + ptr=blob_key; + memcpy_fixed(pos+4,&ptr,sizeof(char*)); + pos+=recinfo[1].length; + } + else if (recinfo[1].type == FIELD_VARCHAR) + { + uint tmp; + /* -1 is here because pack_length is stored in seg->length */ + uint pack_length= HA_VARCHAR_PACKLENGTH(keyinfo[0].seg[0].length-1); + strnmov(pos+pack_length,data[n].f2,keyinfo[0].seg[0].length); + tmp=strlen(pos+1); + if (pack_length == 1) + *pos= (char) tmp; + else + int2store(pos,tmp); + pos+=recinfo[1].length; + } + else + { + strnmov(pos,data[n].f2,keyinfo[0].seg[0].length); + pos+=recinfo[1].length; + } +} + + +static my_bool +get_one_option(int optid, const struct my_option *opt __attribute__((unused)), + char *argument) +{ + switch(optid) { + case 'v': verbose=1; break; + case 's': silent=1; break; + case 'F': no_fulltext=1; no_search=1; + case 'U': skip_update=1; break; + case 'K': no_keys=no_search=1; break; + case 'N': no_search=1; break; + case 'S': no_stopwords=1; break; + case '#': + DBUG_PUSH (argument); + break; + case 'V': + case '?': + case 'h': + usage(); + exit(1); + } + return 0; +} + +/* Read options */ + +static void get_options(int argc,char *argv[]) +{ + int ho_error; + + if ((ho_error=handle_options(&argc, &argv, my_long_options, get_one_option))) + exit(ho_error); + return; +} /* get options */ + + +static void usage() +{ + printf("%s [options]\n", my_progname); + my_print_help(my_long_options); + my_print_variables(my_long_options); +} diff --git a/storage/maria/ma_ft_test1.h b/storage/maria/ma_ft_test1.h new file mode 100644 index 00000000000..5883c42f5c5 --- /dev/null +++ b/storage/maria/ma_ft_test1.h @@ -0,0 +1,420 @@ +/* Copyright (C) 2006 MySQL AB & MySQL Finland AB & TCX DataKonsult 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 */ + +/* Written by Sergei A. Golubchik, who has a shared copyright to this code */ + +#define NUPD 20 +#define NDATAS 389 +struct { const char *f0, *f2; } data[NDATAS] = { + {"1", "General Information about MySQL"}, + {"1.1", "What is MySQL?"}, + {"1.2", "About this manual"}, + {"1.3", "History of MySQL"}, + {"1.4", "The main features of MySQL"}, + {"1.5", "General SQL information and tutorials"}, + {"1.6", "Useful MySQL-related links"}, + {"1.7", "What are stored procedures and triggers and so on?"}, + {"2", "MySQL mailing lists and how to ask questions/give error (bug) reports"}, + {"2.1", "Subscribing to/un-subscribing from the MySQL mailing list"}, + {"2.2", "Asking questions or reporting bugs"}, + {"2.3", "I think I have found a bug. What information do you need to help me?"}, + {"2.3.1", "MySQL keeps crashing"}, + {"2.4", "Guidelines for answering questions on the mailing list"}, + {"3", "Licensing or When do I have/want to pay for MySQL?"}, + {"3.1", "How much does MySQL cost?"}, + {"3.2", "How do I get commercial support?"}, + {"3.2.1", "Types of commercial support"}, + {"3.2.1.1", "Basic email support"}, + {"3.2.1.2", "Extended email support"}, +/*------------------------------- NUPD=20 -------------------------------*/ + {"3.2.1.3", "Asking: Login support"}, + {"3.2.1.4", "Extended login support"}, + {"3.3", "How do I pay for licenses/support?"}, + {"3.4", "Who do I contact when I want more information about licensing/support?"}, + {"3.5", "What Copyright does MySQL use?"}, + {"3.6", "When may I distribute MySQL commercially without a fee?"}, + {"3.7", "I want to sell a product that can be configured to use MySQL"}, + {"3.8", "I am running a commercial web server using MySQL"}, + {"3.9", "Do I need a license to sell commercial Perl/tcl/PHP/Web+ etc applications?"}, + {"3.10", "Possible future changes in the licensing"}, + {"4", "Compiling and installing MySQL"}, + {"4.1", "How do I get MySQL?"}, + {"4.2", "Which MySQL version should I use?"}, + {"4.3", "How/when will you release updates?"}, + {"4.4", "What operating systems does MySQL support?"}, + {"4.5", "Compiling MySQL from source code"}, + {"4.5.1", "Quick installation overview"}, + {"4.5.2", "Usual configure switches"}, + {"4.5.3", "Applying a patch"}, + {"4.6", "Problems compiling?"}, + {"4.7", "General compilation notes"}, + {"4.8", "MIT-pthreads notes (FreeBSD)"}, + {"4.9", "Perl installation comments"}, + {"4.10", "Special things to consider for some machine/OS combinations"}, + {"4.10.1", "Solaris notes"}, + {"4.10.2", "SunOS 4 notes"}, + {"4.10.3", "Linux notes for all versions"}, + {"4.10.3.1", "Linux-x86 notes"}, + {"4.10.3.2", "RedHat 5.0"}, + {"4.10.3.3", "RedHat 5.1"}, + {"4.10.3.4", "Linux-Sparc notes"}, + {"4.10.3.5", "Linux-Alpha notes"}, + {"4.10.3.6", "MkLinux notes"}, + {"4.10.4", "Alpha-DEC-Unix notes"}, + {"4.10.5", "Alpha-DEC-OSF1 notes"}, + {"4.10.6", "SGI-IRIX notes"}, + {"4.10.7", "FreeBSD notes"}, + {"4.10.7.1", "FreeBSD-3.0 notes"}, + {"4.10.8", "BSD/OS 2.# notes"}, + {"4.10.8.1", "BSD/OS 3.# notes"}, + {"4.10.9", "SCO notes"}, + {"4.10.10", "SCO Unixware 7.0 notes"}, + {"4.10.11", "IBM-AIX notes"}, + {"4.10.12", "HP-UX notes"}, + {"4.11", "TcX binaries"}, + {"4.12", "Win32 notes"}, + {"4.13", "Installation instructions for MySQL binary releases"}, + {"4.13.1", "How to get MySQL Perl support working"}, + {"4.13.2", "Linux notes"}, + {"4.13.3", "HP-UX notes"}, + {"4.13.4", "Linking client libraries"}, + {"4.14", "Problems running mysql_install_db"}, + {"4.15", "Problems starting MySQL"}, + {"4.16", "Automatic start/stop of MySQL"}, + {"4.17", "Option files"}, + {"5", "How standards-compatible is MySQL?"}, + {"5.1", "What extensions has MySQL to ANSI SQL92?"}, + {"5.2", "What functionality is missing in MySQL?"}, + {"5.2.1", "Sub-selects"}, + {"5.2.2", "SELECT INTO TABLE"}, + {"5.2.3", "Transactions"}, + {"5.2.4", "Triggers"}, + {"5.2.5", "Foreign Keys"}, + {"5.2.5.1", "Some reasons NOT to use FOREIGN KEYS"}, + {"5.2.6", "Views"}, + {"5.2.7", "-- as start of a comment"}, + {"5.3", "What standards does MySQL follow?"}, + {"5.4", "What functions exist only for compatibility?"}, + {"5.5", "Limitations of BLOB and TEXT types"}, + {"5.6", "How to cope without COMMIT-ROLLBACK"}, + {"6", "The MySQL access privilege system"}, + {"6.1", "What the privilege system does"}, + {"6.2", "Connecting to the MySQL server"}, + {"6.2.1", "Keeping your password secure"}, + {"6.3", "Privileges provided by MySQL"}, + {"6.4", "How the privilege system works"}, + {"6.5", "The privilege tables"}, + {"6.6", "Setting up the initial MySQL privileges"}, + {"6.7", "Adding new user privileges to MySQL"}, + {"6.8", "An example permission setup"}, + {"6.9", "Causes of Access denied errors"}, + {"6.10", "How to make MySQL secure against crackers"}, + {"7", "MySQL language reference"}, + {"7.1", "Literals: how to write strings and numbers"}, + {"7.1.1", "Strings"}, + {"7.1.2", "Numbers"}, + {"7.1.3", "NULL values"}, + {"7.1.4", "Database, table, index, column and alias names"}, + {"7.1.4.1", "Case sensitivity in names"}, + {"7.2", "Column types"}, + {"7.2.1", "Column type storage requirements"}, + {"7.2.5", "Numeric types"}, + {"7.2.6", "Date and time types"}, + {"7.2.6.1", "The DATE type"}, + {"7.2.6.2", "The TIME type"}, + {"7.2.6.3", "The DATETIME type"}, + {"7.2.6.4", "The TIMESTAMP type"}, + {"7.2.6.5", "The YEAR type"}, + {"7.2.6.6", "Miscellaneous date and time properties"}, + {"7.2.7", "String types"}, + {"7.2.7.1", "The CHAR and VARCHAR types"}, + {"7.2.7.2", "The BLOB and TEXT types"}, + {"7.2.7.3", "The ENUM type"}, + {"7.2.7.4", "The SET type"}, + {"7.2.8", "Choosing the right type for a column"}, + {"7.2.9", "Column indexes"}, + {"7.2.10", "Multiple-column indexes"}, + {"7.2.11", "Using column types from other database engines"}, + {"7.3", "Functions for use in SELECT and WHERE clauses"}, + {"7.3.1", "Grouping functions"}, + {"7.3.2", "Normal arithmetic operations"}, + {"7.3.3", "Bit functions"}, + {"7.3.4", "Logical operations"}, + {"7.3.5", "Comparison operators"}, + {"7.3.6", "String comparison functions"}, + {"7.3.7", "Control flow functions"}, + {"7.3.8", "Mathematical functions"}, + {"7.3.9", "String functions"}, + {"7.3.10", "Date and time functions"}, + {"7.3.11", "Miscellaneous functions"}, + {"7.3.12", "Functions for use with GROUP BY clauses"}, + {"7.4", "CREATE DATABASE syntax"}, + {"7.5", "DROP DATABASE syntax"}, + {"7.6", "CREATE TABLE syntax"}, + {"7.7", "ALTER TABLE syntax"}, + {"7.8", "OPTIMIZE TABLE syntax"}, + {"7.9", "DROP TABLE syntax"}, + {"7.10", "DELETE syntax"}, + {"7.11", "SELECT syntax"}, + {"7.12", "JOIN syntax"}, + {"7.13", "INSERT syntax"}, + {"7.14", "REPLACE syntax"}, + {"7.15", "LOAD DATA INFILE syntax"}, + {"7.16", "UPDATE syntax"}, + {"7.17", "USE syntax"}, + {"7.18", "SHOW syntax (Get information about tables, columns...)"}, + {"7.19", "EXPLAIN syntax (Get information about a SELECT)"}, + {"7.20", "DESCRIBE syntax (Get information about columns)"}, + {"7.21", "LOCK TABLES/UNLOCK TABLES syntax"}, + {"7.22", "SET OPTION syntax"}, + {"7.23", "GRANT syntax (Compatibility function)"}, + {"7.24", "CREATE INDEX syntax (Compatibility function)"}, + {"7.25", "DROP INDEX syntax (Compatibility function)"}, + {"7.26", "Comment syntax"}, + {"7.27", "CREATE FUNCTION/DROP FUNCTION syntax"}, + {"7.28", "Is MySQL picky about reserved words?"}, + {"8", "Example SQL queries"}, + {"8.1", "Queries from twin project"}, + {"8.1.1", "Find all non-distributed twins"}, + {"8.1.2", "Show a table on twin pair status"}, + {"9", "How safe/stable is MySQL?"}, + {"9.1", "How stable is MySQL?"}, + {"9.2", "Why are there is so many releases of MySQL?"}, + {"9.3", "Checking a table for errors"}, + {"9.4", "How to repair tables"}, + {"9.5", "Is there anything special to do when upgrading/downgrading MySQL?"}, + {"9.5.1", "Upgrading from a 3.21 version to 3.22"}, + {"9.5.2", "Upgrading from a 3.20 version to 3.21"}, + {"9.5.3", "Upgrading to another architecture"}, + {"9.6", "Year 2000 compliance"}, + {"10", "MySQL Server functions"}, + {"10.1", "What languages are supported by MySQL?"}, + {"10.1.1", "Character set used for data & sorting"}, + {"10.2", "The update log"}, + {"10.3", "How big can MySQL tables be?"}, + {"11", "Getting maximum performance from MySQL"}, + {"11.1", "How does one change the size of MySQL buffers?"}, + {"11.2", "How compiling and linking affects the speed of MySQL"}, + {"11.3", "How does MySQL use memory?"}, + {"11.4", "How does MySQL use indexes?"}, + {"11.5", "What optimizations are done on WHERE clauses?"}, + {"11.6", "How does MySQL open & close tables?"}, + {"11.6.0.1", "What are the drawbacks of creating possibly thousands of tables in a database?"}, + {"11.7", "How does MySQL lock tables?"}, + {"11.8", "How should I arrange my table to be as fast/small as possible?"}, + {"11.9", "What affects the speed of INSERT statements?"}, + {"11.10", "What affects the speed DELETE statements?"}, + {"11.11", "How do I get MySQL to run at full speed?"}, + {"11.12", "What are the different row formats? Or, when should VARCHAR/CHAR be used?"}, + {"11.13", "Why so many open tables?"}, + {"12", "MySQL benchmark suite"}, + {"13", "MySQL Utilites"}, + {"13.1", "Overview of the different MySQL programs"}, + {"13.2", "The MySQL table check, optimize and repair program"}, + {"13.2.1", "isamchk memory use"}, + {"13.2.2", "Getting low-level table information"}, + {"13.3", "The MySQL compressed read-only table generator"}, + {"14", "Adding new functions to MySQL"}, + {"15", "MySQL ODBC Support"}, + {"15.1", "Operating systems supported by MyODBC"}, + {"15.2", "How to report problems with MyODBC"}, + {"15.3", "Programs known to work with MyODBC"}, + {"15.4", "How to fill in the various fields in the ODBC administrator program"}, + {"15.5", "How to get the value of an AUTO_INCREMENT column in ODBC"}, + {"16", "Problems and common errors"}, + {"16.1", "Some common errors when using MySQL"}, + {"16.1.1", "MySQL server has gone away error"}, + {"16.1.2", "Can't connect to local MySQL server error"}, + {"16.1.3", "Out of memory error"}, + {"16.1.4", "Packet too large error"}, + {"16.1.5", "The table is full error"}, + {"16.1.6", "Commands out of sync error in client"}, + {"16.1.7", "Removing user error"}, + {"16.2", "How MySQL handles a full disk"}, + {"16.3", "How to run SQL commands from a text file"}, + {"16.4", "Where MySQL stores temporary files"}, + {"16.5", "Access denied error"}, + {"16.6", "How to run MySQL as a normal user"}, + {"16.7", "Problems with file permissions"}, + {"16.8", "File not found"}, + {"16.9", "Problems using DATE columns"}, + {"16.10", "Case sensitivity in searches"}, + {"16.11", "Problems with NULL values"}, + {"17", "Solving some common problems with MySQL"}, + {"17.1", "Database replication"}, + {"17.2", "Database backups"}, + {"18", "MySQL client tools and API's"}, + {"18.1", "MySQL C API"}, + {"18.2", "C API datatypes"}, + {"18.3", "C API function overview"}, + {"18.4", "C API function descriptions"}, + {"18.4.1", "mysql_affected_rows()"}, + {"18.4.2", "mysql_close()"}, + {"18.4.3", "mysql_connect()"}, + {"18.4.4", "mysql_create_db()"}, + {"18.4.5", "mysql_data_seek()"}, + {"18.4.6", "mysql_debug()"}, + {"18.4.7", "mysql_drop_db()"}, + {"18.4.8", "mysql_dump_debug_info()"}, + {"18.4.9", "mysql_eof()"}, + {"18.4.10", "mysql_errno()"}, + {"18.4.11", "mysql_error()"}, + {"18.4.12", "mysql_escape_string()"}, + {"18.4.13", "mysql_fetch_field()"}, + {"18.4.14", "mysql_fetch_fields()"}, + {"18.4.15", "mysql_fetch_field_direct()"}, + {"18.4.16", "mysql_fetch_lengths()"}, + {"18.4.17", "mysql_fetch_row()"}, + {"18.4.18", "mysql_field_seek()"}, + {"18.4.19", "mysql_field_tell()"}, + {"18.4.20", "mysql_free_result()"}, + {"18.4.21", "mysql_get_client_info()"}, + {"18.4.22", "mysql_get_host_info()"}, + {"18.4.23", "mysql_get_proto_info()"}, + {"18.4.24", "mysql_get_server_info()"}, + {"18.4.25", "mysql_info()"}, + {"18.4.26", "mysql_init()"}, + {"18.4.27", "mysql_insert_id()"}, + {"18.4.28", "mysql_kill()"}, + {"18.4.29", "mysql_list_dbs()"}, + {"18.4.30", "mysql_list_fields()"}, + {"18.4.31", "mysql_list_processes()"}, + {"18.4.32", "mysql_list_tables()"}, + {"18.4.33", "mysql_num_fields()"}, + {"18.4.34", "mysql_num_rows()"}, + {"18.4.35", "mysql_query()"}, + {"18.4.36", "mysql_real_connect()"}, + {"18.4.37", "mysql_real_query()"}, + {"18.4.38", "mysql_reload()"}, + {"18.4.39", "mysql_row_tell()"}, + {"18.4.40", "mysql_select_db()"}, + {"18.4.41", "mysql_shutdown()"}, + {"18.4.42", "mysql_stat()"}, + {"18.4.43", "mysql_store_result()"}, + {"18.4.44", "mysql_thread_id()"}, + {"18.4.45", "mysql_use_result()"}, + {"18.4.46", "Why is it that after mysql_query() returns success, mysql_store_result() sometimes returns NULL?"}, + {"18.4.47", "What results can I get from a query?"}, + {"18.4.48", "How can I get the unique ID for the last inserted row?"}, + {"18.4.49", "Problems linking with the C API"}, + {"18.4.50", "How to make a thread-safe client"}, + {"18.5", "MySQL Perl API's"}, + {"18.5.1", "DBI with DBD::mysql"}, + {"18.5.1.1", "The DBI interface"}, + {"18.5.1.2", "More DBI/DBD information"}, + {"18.6", "MySQL Java connectivity (JDBC)"}, + {"18.7", "MySQL PHP API's"}, + {"18.8", "MySQL C++ API's"}, + {"18.9", "MySQL Python API's"}, + {"18.10", "MySQL TCL API's"}, + {"19", "How MySQL compares to other databases"}, + {"19.1", "How MySQL compares to mSQL"}, + {"19.1.1", "How to convert mSQL tools for MySQL"}, + {"19.1.2", "How mSQL and MySQL client/server communications protocols differ"}, + {"19.1.3", "How mSQL 2.0 SQL syntax differs from MySQL"}, + {"19.2", "How MySQL compares to PostgreSQL"}, + {"A", "Some users of MySQL"}, + {"B", "Contributed programs"}, + {"C", "Contributors to MySQL"}, + {"D", "MySQL change history"}, + {"19.3", "Changes in release 3.22.x (Alpha version)"}, + {"19.3.1", "Changes in release 3.22.7"}, + {"19.3.2", "Changes in release 3.22.6"}, + {"19.3.3", "Changes in release 3.22.5"}, + {"19.3.4", "Changes in release 3.22.4"}, + {"19.3.5", "Changes in release 3.22.3"}, + {"19.3.6", "Changes in release 3.22.2"}, + {"19.3.7", "Changes in release 3.22.1"}, + {"19.3.8", "Changes in release 3.22.0"}, + {"19.4", "Changes in release 3.21.x"}, + {"19.4.1", "Changes in release 3.21.33"}, + {"19.4.2", "Changes in release 3.21.32"}, + {"19.4.3", "Changes in release 3.21.31"}, + {"19.4.4", "Changes in release 3.21.30"}, + {"19.4.5", "Changes in release 3.21.29"}, + {"19.4.6", "Changes in release 3.21.28"}, + {"19.4.7", "Changes in release 3.21.27"}, + {"19.4.8", "Changes in release 3.21.26"}, + {"19.4.9", "Changes in release 3.21.25"}, + {"19.4.10", "Changes in release 3.21.24"}, + {"19.4.11", "Changes in release 3.21.23"}, + {"19.4.12", "Changes in release 3.21.22"}, + {"19.4.13", "Changes in release 3.21.21a"}, + {"19.4.14", "Changes in release 3.21.21"}, + {"19.4.15", "Changes in release 3.21.20"}, + {"19.4.16", "Changes in release 3.21.19"}, + {"19.4.17", "Changes in release 3.21.18"}, + {"19.4.18", "Changes in release 3.21.17"}, + {"19.4.19", "Changes in release 3.21.16"}, + {"19.4.20", "Changes in release 3.21.15"}, + {"19.4.21", "Changes in release 3.21.14b"}, + {"19.4.22", "Changes in release 3.21.14a"}, + {"19.4.23", "Changes in release 3.21.13"}, + {"19.4.24", "Changes in release 3.21.12"}, + {"19.4.25", "Changes in release 3.21.11"}, + {"19.4.26", "Changes in release 3.21.10"}, + {"19.4.27", "Changes in release 3.21.9"}, + {"19.4.28", "Changes in release 3.21.8"}, + {"19.4.29", "Changes in release 3.21.7"}, + {"19.4.30", "Changes in release 3.21.6"}, + {"19.4.31", "Changes in release 3.21.5"}, + {"19.4.32", "Changes in release 3.21.4"}, + {"19.4.33", "Changes in release 3.21.3"}, + {"19.4.34", "Changes in release 3.21.2"}, + {"19.4.35", "Changes in release 3.21.0"}, + {"19.5", "Changes in release 3.20.x"}, + {"19.5.1", "Changes in release 3.20.18"}, + {"19.5.2", "Changes in release 3.20.17"}, + {"19.5.3", "Changes in release 3.20.16"}, + {"19.5.4", "Changes in release 3.20.15"}, + {"19.5.5", "Changes in release 3.20.14"}, + {"19.5.6", "Changes in release 3.20.13"}, + {"19.5.7", "Changes in release 3.20.11"}, + {"19.5.8", "Changes in release 3.20.10"}, + {"19.5.9", "Changes in release 3.20.9"}, + {"19.5.10", "Changes in release 3.20.8"}, + {"19.5.11", "Changes in release 3.20.7"}, + {"19.5.12", "Changes in release 3.20.6"}, + {"19.5.13", "Changes in release 3.20.3"}, + {"19.5.14", "Changes in release 3.20.0"}, + {"19.6", "Changes in release 3.19.x"}, + {"19.6.1", "Changes in release 3.19.5"}, + {"19.6.2", "Changes in release 3.19.4"}, + {"19.6.3", "Changes in release 3.19.3"}, + {"E", "Known errors and design deficiencies in MySQL"}, + {"F", "List of things we want to add to MySQL in the future (The TODO)"}, + {"19.7", "Things that must done in the real near future"}, + {"19.8", "Things that have to be done sometime"}, + {"19.9", "Some things we don't have any plans to do"}, + {"G", "Comments on porting to other systems"}, + {"19.10", "Debugging MySQL"}, + {"19.11", "Comments about RTS threads"}, + {"19.12", "What is the difference between different thread packages?"}, + {"H", "Description of MySQL regular expression syntax"}, + {"I", "What is Unireg?"}, + {"J", "The MySQL server license"}, + {"K", "The MySQL license for Microsoft operating systems"}, + {"*", "SQL command, type and function index"}, + {"*", "Concept Index"} +}; + +#define NQUERIES 5 +const char *query[NQUERIES]={ + "mysql information and manual", + "upgrading from previous version", + "column indexes", + "against about after more right the with/without", /* stopwords test */ + "mysql license and copyright" +}; diff --git a/storage/maria/ma_ft_update.c b/storage/maria/ma_ft_update.c new file mode 100644 index 00000000000..f38990efab9 --- /dev/null +++ b/storage/maria/ma_ft_update.c @@ -0,0 +1,379 @@ +/* Copyright (C) 2006 MySQL AB & MySQL Finland AB & TCX DataKonsult 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 */ + +/* Written by Sergei A. Golubchik, who has a shared copyright to this code */ + +/* functions to work with full-text indices */ + +#include "ma_ftdefs.h" +#include <math.h> + +void _ma_ft_segiterator_init(MARIA_HA *info, uint keynr, const uchar *record, + FT_SEG_ITERATOR *ftsi) +{ + DBUG_ENTER("_ma_ft_segiterator_init"); + + ftsi->num=info->s->keyinfo[keynr].keysegs; + ftsi->seg=info->s->keyinfo[keynr].seg; + ftsi->rec=record; + DBUG_VOID_RETURN; +} + +void _ma_ft_segiterator_dummy_init(const uchar *record, uint len, + FT_SEG_ITERATOR *ftsi) +{ + DBUG_ENTER("_ma_ft_segiterator_dummy_init"); + + ftsi->num=1; + ftsi->seg=0; + ftsi->pos=record; + ftsi->len=len; + DBUG_VOID_RETURN; +} + +/* + This function breaks convention "return 0 in success" + but it's easier to use like this + + while(_ma_ft_segiterator()) + + so "1" means "OK", "0" means "EOF" +*/ + +uint _ma_ft_segiterator(register FT_SEG_ITERATOR *ftsi) +{ + DBUG_ENTER("_ma_ft_segiterator"); + + if (!ftsi->num) + DBUG_RETURN(0); + + ftsi->num--; + if (!ftsi->seg) + DBUG_RETURN(1); + + ftsi->seg--; + + if (ftsi->seg->null_bit && + (ftsi->rec[ftsi->seg->null_pos] & ftsi->seg->null_bit)) + { + ftsi->pos=0; + DBUG_RETURN(1); + } + ftsi->pos= ftsi->rec+ftsi->seg->start; + if (ftsi->seg->flag & HA_VAR_LENGTH_PART) + { + uint pack_length= (ftsi->seg->bit_start); + ftsi->len= (pack_length == 1 ? (uint) * ftsi->pos : + uint2korr(ftsi->pos)); + ftsi->pos+= pack_length; /* Skip VARCHAR length */ + DBUG_RETURN(1); + } + if (ftsi->seg->flag & HA_BLOB_PART) + { + ftsi->len= _ma_calc_blob_length(ftsi->seg->bit_start,ftsi->pos); + memcpy_fixed((char*) &ftsi->pos, ftsi->pos+ftsi->seg->bit_start, + sizeof(char*)); + DBUG_RETURN(1); + } + ftsi->len=ftsi->seg->length; + DBUG_RETURN(1); +} + + +/* parses a document i.e. calls maria_ft_parse for every keyseg */ + +uint _ma_ft_parse(TREE *parsed, MARIA_HA *info, uint keynr, const uchar *record, + MYSQL_FTPARSER_PARAM *param, MEM_ROOT *mem_root) +{ + FT_SEG_ITERATOR ftsi; + struct st_mysql_ftparser *parser; + DBUG_ENTER("_ma_ft_parse"); + + _ma_ft_segiterator_init(info, keynr, record, &ftsi); + + maria_ft_parse_init(parsed, info->s->keyinfo[keynr].seg->charset); + parser= info->s->keyinfo[keynr].parser; + while (_ma_ft_segiterator(&ftsi)) + { + /** @todo this casts ftsi.pos (const) to non-const */ + if (ftsi.pos) + if (maria_ft_parse(parsed, (uchar *)ftsi.pos, ftsi.len, parser, param, + mem_root)) + DBUG_RETURN(1); + } + DBUG_RETURN(0); +} + +FT_WORD * _ma_ft_parserecord(MARIA_HA *info, uint keynr, const uchar *record, + MEM_ROOT *mem_root) +{ + TREE ptree; + MYSQL_FTPARSER_PARAM *param; + DBUG_ENTER("_ma_ft_parserecord"); + if (! (param= maria_ftparser_call_initializer(info, keynr, 0))) + DBUG_RETURN(NULL); + bzero((char*) &ptree, sizeof(ptree)); + param->flags= 0; + if (_ma_ft_parse(&ptree, info, keynr, record, param, mem_root)) + DBUG_RETURN(NULL); + + DBUG_RETURN(maria_ft_linearize(&ptree, mem_root)); +} + +static int _ma_ft_store(MARIA_HA *info, uint keynr, uchar *keybuf, + FT_WORD *wlist, my_off_t filepos) +{ + DBUG_ENTER("_ma_ft_store"); + + for (; wlist->pos; wlist++) + { + MARIA_KEY key; + _ma_ft_make_key(info, &key, keynr, keybuf, wlist, filepos); + if (_ma_ck_write(info, &key)) + DBUG_RETURN(1); + } + DBUG_RETURN(0); +} + +static int _ma_ft_erase(MARIA_HA *info, uint keynr, uchar *keybuf, + FT_WORD *wlist, my_off_t filepos) +{ + uint err=0; + DBUG_ENTER("_ma_ft_erase"); + + for (; wlist->pos; wlist++) + { + MARIA_KEY key; + _ma_ft_make_key(info, &key, keynr, keybuf, wlist, filepos); + if (_ma_ck_delete(info, &key)) + err=1; + } + DBUG_RETURN(err); +} + +/* + Compares an appropriate parts of two WORD_KEY keys directly out of records + returns 1 if they are different +*/ + +#define THOSE_TWO_DAMN_KEYS_ARE_REALLY_DIFFERENT 1 +#define GEE_THEY_ARE_ABSOLUTELY_IDENTICAL 0 + +int _ma_ft_cmp(MARIA_HA *info, uint keynr, const uchar *rec1, const uchar *rec2) +{ + FT_SEG_ITERATOR ftsi1, ftsi2; + CHARSET_INFO *cs=info->s->keyinfo[keynr].seg->charset; + DBUG_ENTER("_ma_ft_cmp"); + + _ma_ft_segiterator_init(info, keynr, rec1, &ftsi1); + _ma_ft_segiterator_init(info, keynr, rec2, &ftsi2); + + while (_ma_ft_segiterator(&ftsi1) && _ma_ft_segiterator(&ftsi2)) + { + if ((ftsi1.pos != ftsi2.pos) && + (!ftsi1.pos || !ftsi2.pos || + ha_compare_text(cs, ftsi1.pos,ftsi1.len, + ftsi2.pos,ftsi2.len,0,0))) + DBUG_RETURN(THOSE_TWO_DAMN_KEYS_ARE_REALLY_DIFFERENT); + } + DBUG_RETURN(GEE_THEY_ARE_ABSOLUTELY_IDENTICAL); +} + + +/* update a document entry */ + +int _ma_ft_update(MARIA_HA *info, uint keynr, uchar *keybuf, + const uchar *oldrec, const uchar *newrec, my_off_t pos) +{ + int error= -1; + FT_WORD *oldlist,*newlist, *old_word, *new_word; + CHARSET_INFO *cs=info->s->keyinfo[keynr].seg->charset; + int cmp, cmp2; + DBUG_ENTER("_ma_ft_update"); + + if (!(old_word=oldlist=_ma_ft_parserecord(info, keynr, oldrec, + &info->ft_memroot)) || + !(new_word=newlist=_ma_ft_parserecord(info, keynr, newrec, + &info->ft_memroot))) + goto err; + + error=0; + while(old_word->pos && new_word->pos) + { + cmp= ha_compare_text(cs, (uchar*) old_word->pos,old_word->len, + (uchar*) new_word->pos,new_word->len,0,0); + cmp2= cmp ? 0 : (fabs(old_word->weight - new_word->weight) > 1.e-5); + + if (cmp < 0 || cmp2) + { + MARIA_KEY key; + _ma_ft_make_key(info, &key, keynr, keybuf, old_word, pos); + if (_ma_ck_delete(info, &key)) + { + error= -1; + goto err; + } + } + if (cmp > 0 || cmp2) + { + MARIA_KEY key; + _ma_ft_make_key(info, &key, keynr, keybuf, new_word,pos); + if ((error= _ma_ck_write(info, &key))) + goto err; + } + if (cmp<=0) old_word++; + if (cmp>=0) new_word++; + } + if (old_word->pos) + error= _ma_ft_erase(info,keynr,keybuf,old_word,pos); + else if (new_word->pos) + error= _ma_ft_store(info,keynr,keybuf,new_word,pos); + +err: + free_root(&info->ft_memroot, MYF(MY_MARK_BLOCKS_FREE)); + DBUG_RETURN(error); +} + + +/* adds a document to the collection */ + +int _ma_ft_add(MARIA_HA *info, uint keynr, uchar *keybuf, const uchar *record, + my_off_t pos) +{ + int error= -1; + FT_WORD *wlist; + DBUG_ENTER("_ma_ft_add"); + DBUG_PRINT("enter",("keynr: %d",keynr)); + + if ((wlist= _ma_ft_parserecord(info, keynr, record, &info->ft_memroot))) + error= _ma_ft_store(info,keynr,keybuf,wlist,pos); + free_root(&info->ft_memroot, MYF(MY_MARK_BLOCKS_FREE)); + DBUG_PRINT("exit",("Return: %d",error)); + DBUG_RETURN(error); +} + + +/* removes a document from the collection */ + +int _ma_ft_del(MARIA_HA *info, uint keynr, uchar *keybuf, const uchar *record, + my_off_t pos) +{ + int error= -1; + FT_WORD *wlist; + DBUG_ENTER("_ma_ft_del"); + DBUG_PRINT("enter",("keynr: %d",keynr)); + + if ((wlist= _ma_ft_parserecord(info, keynr, record, &info->ft_memroot))) + error= _ma_ft_erase(info,keynr,keybuf,wlist,pos); + free_root(&info->ft_memroot, MYF(MY_MARK_BLOCKS_FREE)); + DBUG_PRINT("exit",("Return: %d",error)); + DBUG_RETURN(error); +} + + +MARIA_KEY *_ma_ft_make_key(MARIA_HA *info, MARIA_KEY *key, uint keynr, + uchar *keybuf, + FT_WORD *wptr, my_off_t filepos) +{ + uchar buf[HA_FT_MAXBYTELEN+16]; + DBUG_ENTER("_ma_ft_make_key"); + +#if HA_FT_WTYPE == HA_KEYTYPE_FLOAT + { + float weight=(float) ((filepos==HA_OFFSET_ERROR) ? 0 : wptr->weight); + mi_float4store(buf,weight); + } +#else +#error +#endif + + int2store(buf+HA_FT_WLEN,wptr->len); + memcpy(buf+HA_FT_WLEN+2,wptr->pos,wptr->len); + /* Can't be spatial so it's ok to call _ma_make_key directly here */ + DBUG_RETURN(_ma_make_key(info, key, keynr, keybuf, buf, filepos, 0)); +} + + +/* + convert key value to ft2 +*/ + +my_bool _ma_ft_convert_to_ft2(MARIA_HA *info, MARIA_KEY *key) +{ + MARIA_SHARE *share= info->s; + my_off_t root; + DYNAMIC_ARRAY *da=info->ft1_to_ft2; + MARIA_KEYDEF *keyinfo=&share->ft2_keyinfo; + uchar *key_ptr= (uchar*) dynamic_array_ptr(da, 0), *end; + uint length, key_length; + MARIA_PINNED_PAGE tmp_page_link, *page_link= &tmp_page_link; + MARIA_KEY tmp_key; + MARIA_PAGE page; + DBUG_ENTER("_ma_ft_convert_to_ft2"); + + /* we'll generate one pageful at once, and insert the rest one-by-one */ + /* calculating the length of this page ...*/ + length=(keyinfo->block_length-2) / keyinfo->keylength; + set_if_smaller(length, da->elements); + length=length * keyinfo->keylength; + + get_key_full_length_rdonly(key_length, key->data); + while (_ma_ck_delete(info, key) == 0) + { + /* + nothing to do here. + _ma_ck_delete() will populate info->ft1_to_ft2 with deleted keys + */ + } + + /* creating pageful of keys */ + bzero(info->buff, share->keypage_header); + _ma_store_keynr(share, info->buff, keyinfo->key_nr); + _ma_store_page_used(share, info->buff, length + share->keypage_header); + memcpy(info->buff + share->keypage_header, key_ptr, length); + info->keyread_buff_used= info->page_changed=1; /* info->buff is used */ + /** + @todo RECOVERY BUG this is not logged yet. Ok as this code is never + called, but soon it will be. + */ + if ((root= _ma_new(info, DFLT_INIT_HITS, &page_link)) == HA_OFFSET_ERROR) + DBUG_RETURN(1); + + _ma_page_setup(&page, info, keyinfo, root, info->buff); + if (_ma_write_keypage(&page, page_link->write_lock, DFLT_INIT_HITS)) + DBUG_RETURN(1); + + /* inserting the rest of key values */ + end= (uchar*) dynamic_array_ptr(da, da->elements); + tmp_key.keyinfo= keyinfo; + tmp_key.data_length= keyinfo->keylength; + tmp_key.ref_length= 0; + tmp_key.flag= 0; + for (key_ptr+=length; key_ptr < end; key_ptr+=keyinfo->keylength) + { + tmp_key.data= key_ptr; + if (_ma_ck_real_write_btree(info, key, &root, SEARCH_SAME)) + DBUG_RETURN(1); + } + + /* now, writing the word key entry */ + ft_intXstore(key->data + key_length, - (int) da->elements); + _ma_dpointer(share, key->data + key_length + HA_FT_WLEN, root); + + DBUG_RETURN(_ma_ck_real_write_btree(info, key, + &share->state.key_root[key->keyinfo-> + key_nr], + SEARCH_SAME)); +} diff --git a/storage/maria/ma_ftdefs.h b/storage/maria/ma_ftdefs.h new file mode 100644 index 00000000000..7e83d774aed --- /dev/null +++ b/storage/maria/ma_ftdefs.h @@ -0,0 +1,154 @@ +/* Copyright (C) 2006 MySQL AB & MySQL Finland AB & TCX DataKonsult 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 */ + +/* Written by Sergei A. Golubchik, who has a shared copyright to this code */ + +/* some definitions for full-text indices */ + +#include "ma_fulltext.h" +#include <m_ctype.h> +#include <my_tree.h> +#include <queues.h> +#include <mysql/plugin.h> + +#define true_word_char(ctype, character) \ + ((ctype) & (_MY_U | _MY_L | _MY_NMR) || \ + (character) == '_') +#define misc_word_char(X) 0 + +#define FT_MAX_WORD_LEN_FOR_SORT 31 + +#define FTPARSER_MEMROOT_ALLOC_SIZE 65536 + +#define COMPILE_STOPWORDS_IN + +/* Interested readers may consult SMART + (ftp://ftp.cs.cornell.edu/pub/smart/smart.11.0.tar.Z) + for an excellent implementation of vector space model we use. + It also demonstrate the usage of different weghting techniques. + This code, though, is completely original and is not based on the + SMART code but was in some cases inspired by it. + + NORM_PIVOT was taken from the article + A.Singhal, C.Buckley, M.Mitra, "Pivoted Document Length Normalization", + ACM SIGIR'96, 21-29, 1996 + */ + +#define LWS_FOR_QUERY LWS_TF +#define LWS_IN_USE LWS_LOG +#define PRENORM_IN_USE PRENORM_AVG +#define NORM_IN_USE NORM_PIVOT +#define GWS_IN_USE GWS_PROB +/*==============================================================*/ +#define LWS_TF (count) +#define LWS_BINARY (count>0) +#define LWS_SQUARE (count*count) +#define LWS_LOG (count?(log( (double) count)+1):0) +/*--------------------------------------------------------------*/ +#define PRENORM_NONE (p->weight) +#define PRENORM_MAX (p->weight/docstat.max) +#define PRENORM_AUG (0.4+0.6*p->weight/docstat.max) +#define PRENORM_AVG (p->weight/docstat.sum*docstat.uniq) +#define PRENORM_AVGLOG ((1+log(p->weight))/(1+log(docstat.sum/docstat.uniq))) +/*--------------------------------------------------------------*/ +#define NORM_NONE (1) +#define NORM_SUM (docstat.nsum) +#define NORM_COS (sqrt(docstat.nsum2)) + +#define PIVOT_VAL (0.0115) +#define NORM_PIVOT (1+PIVOT_VAL*docstat.uniq) +/*---------------------------------------------------------------*/ +#define GWS_NORM (1/sqrt(sum2)) +#define GWS_GFIDF (sum/doc_cnt) +/* Mysterious, but w/o (double) GWS_IDF performs better :-o */ +#define GWS_IDF log(aio->info->state->records/doc_cnt) +#define GWS_IDF1 log((double)aio->info->state->records/doc_cnt) +#define GWS_PROB ((aio->info->state->records > doc_cnt) ? log(((double)(aio->info->state->records-doc_cnt))/doc_cnt) : 0 ) +#define GWS_FREQ (1.0/doc_cnt) +#define GWS_SQUARED pow(log((double)aio->info->state->records/doc_cnt),2) +#define GWS_CUBIC pow(log((double)aio->info->state->records/doc_cnt),3) +#define GWS_ENTROPY (1-(suml/sum-log(sum))/log(aio->info->state->records)) +/*=================================================================*/ + +/* Boolean search operators */ +#define FTB_YES (ft_boolean_syntax[0]) +#define FTB_EGAL (ft_boolean_syntax[1]) +#define FTB_NO (ft_boolean_syntax[2]) +#define FTB_INC (ft_boolean_syntax[3]) +#define FTB_DEC (ft_boolean_syntax[4]) +#define FTB_LBR (ft_boolean_syntax[5]) +#define FTB_RBR (ft_boolean_syntax[6]) +#define FTB_NEG (ft_boolean_syntax[7]) +#define FTB_TRUNC (ft_boolean_syntax[8]) +#define FTB_LQUOT (ft_boolean_syntax[10]) +#define FTB_RQUOT (ft_boolean_syntax[11]) + +typedef struct st_maria_ft_word { + uchar * pos; + uint len; + double weight; +} FT_WORD; + +int is_stopword(char *word, uint len); + +MARIA_KEY *_ma_ft_make_key(MARIA_HA *, MARIA_KEY *, uint , uchar *, FT_WORD *, + my_off_t); + +uchar maria_ft_get_word(CHARSET_INFO *, uchar **, uchar *, FT_WORD *, + MYSQL_FTPARSER_BOOLEAN_INFO *); +uchar maria_ft_simple_get_word(CHARSET_INFO *, uchar **, const uchar *, + FT_WORD *, my_bool); + +typedef struct _st_maria_ft_seg_iterator { + uint num, len; + HA_KEYSEG *seg; + const uchar *rec, *pos; +} FT_SEG_ITERATOR; + +void _ma_ft_segiterator_init(MARIA_HA *, uint, const uchar *, FT_SEG_ITERATOR *); +void _ma_ft_segiterator_dummy_init(const uchar *, uint, FT_SEG_ITERATOR *); +uint _ma_ft_segiterator(FT_SEG_ITERATOR *); + +void maria_ft_parse_init(TREE *, CHARSET_INFO *); +int maria_ft_parse(TREE *, uchar *, int, struct st_mysql_ftparser *parser, + MYSQL_FTPARSER_PARAM *, MEM_ROOT *); +FT_WORD * maria_ft_linearize(TREE *, MEM_ROOT *); +FT_WORD * _ma_ft_parserecord(MARIA_HA *, uint, const uchar *, MEM_ROOT *); +uint _ma_ft_parse(TREE *, MARIA_HA *, uint, const uchar *, + MYSQL_FTPARSER_PARAM *, MEM_ROOT *); + +FT_INFO *maria_ft_init_nlq_search(MARIA_HA *, uint, uchar *, uint, uint, uchar *); +FT_INFO *maria_ft_init_boolean_search(MARIA_HA *, uint, uchar *, uint, CHARSET_INFO *); + +extern const struct _ft_vft _ma_ft_vft_nlq; +int maria_ft_nlq_read_next(FT_INFO *, char *); +float maria_ft_nlq_find_relevance(FT_INFO *, uchar *, uint); +void maria_ft_nlq_close_search(FT_INFO *); +float maria_ft_nlq_get_relevance(FT_INFO *); +my_off_t maria_ft_nlq_get_docid(FT_INFO *); +void maria_ft_nlq_reinit_search(FT_INFO *); + +extern const struct _ft_vft _ma_ft_vft_boolean; +int maria_ft_boolean_read_next(FT_INFO *, char *); +float maria_ft_boolean_find_relevance(FT_INFO *, uchar *, uint); +void maria_ft_boolean_close_search(FT_INFO *); +float maria_ft_boolean_get_relevance(FT_INFO *); +my_off_t maria_ft_boolean_get_docid(FT_INFO *); +void maria_ft_boolean_reinit_search(FT_INFO *); +MYSQL_FTPARSER_PARAM* maria_ftparser_alloc_param(MARIA_HA *info); +extern MYSQL_FTPARSER_PARAM *maria_ftparser_call_initializer(MARIA_HA *info, + uint keynr, + uint paramnr); +extern void maria_ftparser_call_deinitializer(MARIA_HA *info); diff --git a/storage/maria/ma_fulltext.h b/storage/maria/ma_fulltext.h new file mode 100644 index 00000000000..6e087990bd2 --- /dev/null +++ b/storage/maria/ma_fulltext.h @@ -0,0 +1,27 @@ +/* Copyright (C) 2006 MySQL AB & MySQL Finland AB & TCX DataKonsult 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 */ + +/* Written by Sergei A. Golubchik, who has a shared copyright to this code */ + +/* some definitions for full-text indices */ + +#include "maria_def.h" +#include "ft_global.h" + +int _ma_ft_cmp(MARIA_HA *, uint, const uchar *, const uchar *); +int _ma_ft_add(MARIA_HA *, uint, uchar *, const uchar *, my_off_t); +int _ma_ft_del(MARIA_HA *, uint, uchar *, const uchar *, my_off_t); + +my_bool _ma_ft_convert_to_ft2(MARIA_HA *, MARIA_KEY *); diff --git a/storage/maria/ma_info.c b/storage/maria/ma_info.c new file mode 100644 index 00000000000..1bbfa3cbf7e --- /dev/null +++ b/storage/maria/ma_info.c @@ -0,0 +1,142 @@ +/* Copyright (C) 2006 MySQL AB & MySQL Finland AB & TCX DataKonsult 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 */ + +/* Return useful base information for an open table */ + +#include "maria_def.h" +#ifdef __WIN__ +#include <sys/stat.h> +#endif + + /* Get position to last record */ + +MARIA_RECORD_POS maria_position(MARIA_HA *info) +{ + return info->cur_row.lastpos; +} + + +/* Get information about the table */ +/* if flag == 2 one get current info (no sync from database */ + +int maria_status(MARIA_HA *info, register MARIA_INFO *x, uint flag) +{ + MY_STAT state; + MARIA_SHARE *share= info->s; + DBUG_ENTER("maria_status"); + + x->recpos= info->cur_row.lastpos; + if (flag == HA_STATUS_POS) + DBUG_RETURN(0); /* Compatible with ISAM */ + if (!(flag & HA_STATUS_NO_LOCK)) + { + pthread_mutex_lock(&share->intern_lock); + VOID(_ma_readinfo(info,F_RDLCK,0)); + fast_ma_writeinfo(info); + pthread_mutex_unlock(&share->intern_lock); + } + if (flag & HA_STATUS_VARIABLE) + { + x->records = info->state->records; + x->deleted = share->state.state.del; + x->delete_length = share->state.state.empty; + x->data_file_length = share->state.state.data_file_length; + x->index_file_length= share->state.state.key_file_length; + + x->keys = share->state.header.keys; + x->check_time = share->state.check_time; + x->mean_reclength = x->records ? + (ulong) ((x->data_file_length - x->delete_length) /x->records) : + (ulong) share->min_pack_length; + } + if (flag & HA_STATUS_ERRKEY) + { + x->errkey= info->errkey; + x->dup_key_pos= info->dup_key_pos; + } + if (flag & HA_STATUS_CONST) + { + x->reclength = share->base.reclength; + x->max_data_file_length=share->base.max_data_file_length; + x->max_index_file_length=info->s->base.max_key_file_length; + x->filenr = info->dfile.file; + x->options = share->options; + x->create_time=share->state.create_time; + x->reflength= maria_get_pointer_length(share->base.max_data_file_length, + maria_data_pointer_size); + x->record_offset= (info->s->data_file_type == STATIC_RECORD ? + share->base.pack_reclength: 0); + x->sortkey= -1; /* No clustering */ + x->rec_per_key = share->state.rec_per_key_part; + x->key_map = share->state.key_map; + x->data_file_name = share->data_file_name.str; + x->index_file_name = share->index_file_name.str; + x->data_file_type = share->data_file_type; + } + if ((flag & HA_STATUS_TIME) && !my_fstat(info->dfile.file, &state, MYF(0))) + x->update_time=state.st_mtime; + else + x->update_time=0; + if (flag & HA_STATUS_AUTO) + { + x->auto_increment= share->state.auto_increment+1; + if (!x->auto_increment) /* This shouldn't happen */ + x->auto_increment= ~(ulonglong) 0; + } + DBUG_RETURN(0); +} + + +/* + Write a message to the error log. + + SYNOPSIS + _ma_report_error() + file_name Name of table file (e.g. index_file_name). + errcode Error number. + + DESCRIPTION + This function supplies my_error() with a table name. Most error + messages need one. Since string arguments in error messages are limited + to 64 characters by convention, we ensure that in case of truncation, + that the end of the index file path is in the message. This contains + the most valuable information (the table name and the database name). + + RETURN + void +*/ + +void _ma_report_error(int errcode, const LEX_STRING *name) +{ + size_t length; + const char *file_name= name->str; + DBUG_ENTER("_ma_report_error"); + DBUG_PRINT("enter",("errcode %d, table '%s'", errcode, file_name)); + + if ((length= name->length) > 64) + { + /* we first remove the directory */ + size_t dir_length= dirname_length(file_name); + file_name+= dir_length; + if ((length-= dir_length) > 64) + { + /* still too long, chop start of table name */ + file_name+= length - 64; + } + } + + my_error(errcode, MYF(ME_NOREFRESH), file_name); + DBUG_VOID_RETURN; +} diff --git a/storage/maria/ma_init.c b/storage/maria/ma_init.c new file mode 100644 index 00000000000..1f2eddd7e30 --- /dev/null +++ b/storage/maria/ma_init.c @@ -0,0 +1,104 @@ +/* Copyright (C) 2006 MySQL AB & MySQL Finland AB & TCX DataKonsult 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 */ + +/* Initialize an maria-database */ + +#include "maria_def.h" +#include <ft_global.h> +#include "ma_blockrec.h" +#include "trnman_public.h" +#include "ma_checkpoint.h" +#include <hash.h> + +void history_state_free(MARIA_STATE_HISTORY_CLOSED *closed_history) +{ + MARIA_STATE_HISTORY *history, *next; + + /* + Free all active history + In case of maria_open() this list should be empty as the history is moved + to handler->share. + */ + for (history= closed_history->state_history; history ; history= next) + { + next= history->next; + my_free(history, MYF(0)); + } + my_free(closed_history, MYF(0)); +} + + +/* + Initialize maria + + SYNOPSIS + maria_init() + + TODO + Open log files and do recovery if need + + RETURN + 0 ok + # error number +*/ + +int maria_init(void) +{ + DBUG_ASSERT(maria_block_size && + maria_block_size % MARIA_MIN_KEY_BLOCK_LENGTH == 0); + if (!maria_inited) + { + maria_inited= TRUE; + pthread_mutex_init(&THR_LOCK_maria,MY_MUTEX_INIT_SLOW); + _ma_init_block_record_data(); + trnman_end_trans_hook= _ma_trnman_end_trans_hook; + my_handler_error_register(); + } + hash_init(&maria_stored_state, &my_charset_bin, 32, + 0, sizeof(LSN), 0, (hash_free_key) history_state_free, 0); + DBUG_PRINT("info",("dummy_transaction_object: %p", + &dummy_transaction_object)); + return 0; +} + + +void maria_end(void) +{ + if (maria_inited) + { + TrID trid; + maria_inited= maria_multi_threaded= FALSE; + ft_free_stopwords(); + ma_checkpoint_end(); + if ((trid= trnman_get_max_trid()) > max_trid_in_control_file) + { + /* + Store max transaction id into control file, in case logs are removed + by user, or maria_chk wants to check tables (it cannot access max trid + from the log, as it cannot process REDOs). + */ + (void)ma_control_file_write_and_force(last_checkpoint_lsn, last_logno, + trid, recovery_failures); + } + trnman_destroy(); + if (translog_status == TRANSLOG_OK) + translog_destroy(); + end_pagecache(maria_log_pagecache, TRUE); + end_pagecache(maria_pagecache, TRUE); + ma_control_file_end(); + pthread_mutex_destroy(&THR_LOCK_maria); + hash_free(&maria_stored_state); + } +} diff --git a/storage/maria/ma_key.c b/storage/maria/ma_key.c new file mode 100644 index 00000000000..08702a9109e --- /dev/null +++ b/storage/maria/ma_key.c @@ -0,0 +1,733 @@ +/* Copyright (C) 2006 MySQL AB & MySQL Finland AB & TCX DataKonsult 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 */ + +/* Functions to handle keys */ + +#include "maria_def.h" +#include "m_ctype.h" +#include "ma_sp_defs.h" +#include "ma_blockrec.h" /* For ROW_FLAG_TRANSID */ +#include "trnman.h" +#ifdef HAVE_IEEEFP_H +#include <ieeefp.h> +#endif + +#define CHECK_KEYS /* Enable safety checks */ + +static int _ma_put_key_in_record(MARIA_HA *info,uint keynr,uchar *record); + +#define FIX_LENGTH(cs, pos, length, char_length) \ + do { \ + if (length > char_length) \ + char_length= (uint) my_charpos(cs, pos, pos+length, char_length); \ + set_if_smaller(char_length,length); \ + } while(0) + + +/** + Store trid in a packed format as part of a key + + @fn transid_store_packed + @param info Maria handler + @param to End of key to which we should store a packed transid + @param trid Trid to be stored + + @notes + + Keys that have a transid has the lowest bit set for the last byte of the key + This function sets this bit for the key. + + Trid is max 6 bytes long + + First Trid it's converted to a smaller number by using + trid= trid - create_trid. + Then trid is then shifted up one bit so that we can use the + lowest bit as a marker if it's followed by another trid. + + Trid is then stored as follows: + + if trid < 256-12 + one byte + else + one byte prefix length_of_trid_in_bytes + 249 followed by data + in high-byte-first order + + Prefix bytes 244 to 249 are reserved for negative transid, that can be used + when we pack transid relative to each other on a key block. + + We have to store transid in high-byte-first order so that we can compare + them unpacked byte per byte and as soon we find a difference we know + which is smaller. + + For example, assuming we the following data: + + key_data: 1 (4 byte integer) + pointer_to_row: 2 << 8 + 3 = 515 (page 2, row 3) + table_create_transid 1000 Defined at create table time and + stored in table definition + transid 1010 Transaction that created row + delete_transid 2011 Transaction that deleted row + + In addition we assume the table is created with a data pointer length + of 4 bytes (this is automatically calculated based on the medium + length of rows and the given max number of rows) + + The binary data for the key would then look like this in hex: + + 00 00 00 01 Key data (1 stored high byte first) + 00 00 00 47 (515 << 1) + 1 ; The last 1 is marker that key cont. + 15 ((1010-1000) << 1) + 1 ; The last 1 is marker that key cont. + FB 07 E6 Length byte (FE = 249 + 2 means 2 bytes) and + ((2011 - 1000) << 1) = 07 E6 +*/ + +uint transid_store_packed(MARIA_HA *info, uchar *to, ulonglong trid) +{ + uchar *start; + uint length; + uchar buff[8]; + DBUG_ASSERT(trid < (LL(1) << (MARIA_MAX_PACK_TRANSID_SIZE*8))); + DBUG_ASSERT(trid >= info->s->state.create_trid); + + trid= (trid - info->s->state.create_trid) << 1; + + /* Mark that key contains transid */ + to[-1]|= 1; + + if (trid < MARIA_MIN_TRANSID_PACK_OFFSET) + { + to[0]= (uchar) trid; + return 1; + } + start= to; + + /* store things in low-byte-first-order in buff */ + to= buff; + do + { + *to++= (uchar) trid; + trid= trid>>8; + } while (trid); + + length= (uint) (to - buff); + /* Store length prefix */ + start[0]= (uchar) (length + MARIA_TRANSID_PACK_OFFSET); + start++; + /* Copy things in high-byte-first order to output buffer */ + do + { + *start++= *--to; + } while (to != buff); + return length+1; +} + + +/** + Read packed transid + + @fn transid_get_packed + @param info Maria handler + @param from Transid is stored here + + See transid_store_packed() for how transid is packed + +*/ + +ulonglong transid_get_packed(MARIA_SHARE *share, const uchar *from) +{ + ulonglong value; + uint length; + + if (from[0] < MARIA_MIN_TRANSID_PACK_OFFSET) + value= (ulonglong) from[0]; + else + { + value= 0; + for (length= (uint) (from[0] - MARIA_TRANSID_PACK_OFFSET), + value= (ulonglong) from[1], from+=2; + --length ; + from++) + value= (value << 8) + ((ulonglong) *from); + } + return (value >> 1) + share->state.create_trid; +} + + +/* + Make a normal (not spatial or fulltext) intern key from a record + + SYNOPSIS + _ma_make_key() + info MyiSAM handler + int_key Store created key here + keynr key number + key Buffer used to store key data + record Record + filepos Position to record in the data file + + NOTES + This is used to generate keys from the record on insert, update and delete + + RETURN + key +*/ + +MARIA_KEY *_ma_make_key(MARIA_HA *info, MARIA_KEY *int_key, uint keynr, + uchar *key, const uchar *record, + MARIA_RECORD_POS filepos, ulonglong trid) +{ + const uchar *pos; + reg1 HA_KEYSEG *keyseg; + my_bool is_ft; + DBUG_ENTER("_ma_make_key"); + + int_key->data= key; + int_key->flag= 0; /* Always return full key */ + int_key->keyinfo= info->s->keyinfo + keynr; + + is_ft= int_key->keyinfo->flag & HA_FULLTEXT; + for (keyseg= int_key->keyinfo->seg ; keyseg->type ;keyseg++) + { + enum ha_base_keytype type=(enum ha_base_keytype) keyseg->type; + uint length=keyseg->length; + uint char_length; + CHARSET_INFO *cs=keyseg->charset; + + if (keyseg->null_bit) + { + if (record[keyseg->null_pos] & keyseg->null_bit) + { + *key++= 0; /* NULL in key */ + continue; + } + *key++=1; /* Not NULL */ + } + + char_length= ((!is_ft && cs && cs->mbmaxlen > 1) ? length/cs->mbmaxlen : + length); + + pos= record+keyseg->start; + if (type == HA_KEYTYPE_BIT) + { + if (keyseg->bit_length) + { + uchar bits= get_rec_bits(record + keyseg->bit_pos, + keyseg->bit_start, keyseg->bit_length); + *key++= (char) bits; + length--; + } + memcpy(key, pos, length); + key+= length; + continue; + } + if (keyseg->flag & HA_SPACE_PACK) + { + if (type != HA_KEYTYPE_NUM) + { + length= (uint) cs->cset->lengthsp(cs, (const char*)pos, length); + } + else + { + const uchar *end= pos + length; + while (pos < end && pos[0] == ' ') + pos++; + length= (uint) (end-pos); + } + FIX_LENGTH(cs, pos, length, char_length); + store_key_length_inc(key,char_length); + memcpy(key, pos, (size_t) char_length); + key+=char_length; + continue; + } + if (keyseg->flag & HA_VAR_LENGTH_PART) + { + uint pack_length= (keyseg->bit_start == 1 ? 1 : 2); + uint tmp_length= (pack_length == 1 ? (uint) *pos : + uint2korr(pos)); + pos+= pack_length; /* Skip VARCHAR length */ + set_if_smaller(length,tmp_length); + FIX_LENGTH(cs, pos, length, char_length); + store_key_length_inc(key,char_length); + memcpy(key,pos,(size_t) char_length); + key+= char_length; + continue; + } + else if (keyseg->flag & HA_BLOB_PART) + { + uint tmp_length= _ma_calc_blob_length(keyseg->bit_start,pos); + uchar *blob_pos; + memcpy_fixed(&blob_pos, pos+keyseg->bit_start,sizeof(char*)); + set_if_smaller(length,tmp_length); + FIX_LENGTH(cs, blob_pos, length, char_length); + store_key_length_inc(key,char_length); + memcpy(key, blob_pos, (size_t) char_length); + key+= char_length; + continue; + } + else if (keyseg->flag & HA_SWAP_KEY) + { /* Numerical column */ +#ifdef HAVE_ISNAN + if (type == HA_KEYTYPE_FLOAT) + { + float nr; + float4get(nr,pos); + if (isnan(nr)) + { + /* Replace NAN with zero */ + bzero(key,length); + key+=length; + continue; + } + } + else if (type == HA_KEYTYPE_DOUBLE) + { + double nr; + float8get(nr,pos); + if (isnan(nr)) + { + bzero(key,length); + key+=length; + continue; + } + } +#endif + pos+=length; + while (length--) + { + *key++ = *--pos; + } + continue; + } + FIX_LENGTH(cs, pos, length, char_length); + memcpy(key, pos, char_length); + if (length > char_length) + cs->cset->fill(cs, (char*) key+char_length, length-char_length, ' '); + key+= length; + } + _ma_dpointer(info->s, key, filepos); + int_key->data_length= (key - int_key->data); + int_key->ref_length= info->s->rec_reflength; + int_key->flag= 0; + if (_ma_have_versioning(info) && trid) + { + int_key->ref_length+= transid_store_packed(info, + key + int_key->ref_length, + (TrID) trid); + int_key->flag|= SEARCH_USER_KEY_HAS_TRANSID; + } + + DBUG_PRINT("exit",("keynr: %d",keynr)); + DBUG_DUMP_KEY("key", int_key); + DBUG_EXECUTE("key", _ma_print_key(DBUG_FILE, int_key);); + DBUG_RETURN(int_key); +} /* _ma_make_key */ + + +/* + Pack a key to intern format from given format (c_rkey) + + SYNOPSIS + _ma_pack_key() + info MARIA handler + int_key Store key here + keynr key number + key Buffer for key data + old Original not packed key + keypart_map bitmap of used keyparts + last_used_keyseg out parameter. May be NULL + + RETURN + int_key + + last_use_keyseg Store pointer to the keyseg after the last used one +*/ + +MARIA_KEY *_ma_pack_key(register MARIA_HA *info, MARIA_KEY *int_key, + uint keynr, uchar *key, + const uchar *old, key_part_map keypart_map, + HA_KEYSEG **last_used_keyseg) +{ + HA_KEYSEG *keyseg; + my_bool is_ft; + DBUG_ENTER("_ma_pack_key"); + + int_key->data= key; + int_key->keyinfo= info->s->keyinfo + keynr; + + /* "one part" rtree key is 2*SPDIMS part key in Maria */ + if (int_key->keyinfo->key_alg == HA_KEY_ALG_RTREE) + keypart_map= (((key_part_map)1) << (2*SPDIMS)) - 1; + + /* only key prefixes are supported */ + DBUG_ASSERT(((keypart_map+1) & keypart_map) == 0); + + is_ft= int_key->keyinfo->flag & HA_FULLTEXT; + for (keyseg=int_key->keyinfo->seg ; keyseg->type && keypart_map; + old+= keyseg->length, keyseg++) + { + enum ha_base_keytype type= (enum ha_base_keytype) keyseg->type; + uint length= keyseg->length; + uint char_length; + const uchar *pos; + CHARSET_INFO *cs=keyseg->charset; + + keypart_map>>= 1; + if (keyseg->null_bit) + { + if (!(*key++= (char) 1-*old++)) /* Copy null marker */ + { + if (keyseg->flag & (HA_VAR_LENGTH_PART | HA_BLOB_PART)) + old+= 2; + continue; /* Found NULL */ + } + } + char_length= ((!is_ft && cs && cs->mbmaxlen > 1) ? length/cs->mbmaxlen : + length); + pos= old; + if (keyseg->flag & HA_SPACE_PACK) + { + const uchar *end= pos + length; + if (type == HA_KEYTYPE_NUM) + { + while (pos < end && pos[0] == ' ') + pos++; + } + else if (type != HA_KEYTYPE_BINARY) + { + while (end > pos && end[-1] == ' ') + end--; + } + length=(uint) (end-pos); + FIX_LENGTH(cs, pos, length, char_length); + store_key_length_inc(key,char_length); + memcpy(key,pos,(size_t) char_length); + key+= char_length; + continue; + } + else if (keyseg->flag & (HA_VAR_LENGTH_PART | HA_BLOB_PART)) + { + /* Length of key-part used with maria_rkey() always 2 */ + uint tmp_length=uint2korr(pos); + pos+=2; + set_if_smaller(length,tmp_length); /* Safety */ + FIX_LENGTH(cs, pos, length, char_length); + store_key_length_inc(key,char_length); + old+=2; /* Skip length */ + memcpy(key, pos,(size_t) char_length); + key+= char_length; + continue; + } + else if (keyseg->flag & HA_SWAP_KEY) + { /* Numerical column */ + pos+=length; + while (length--) + *key++ = *--pos; + continue; + } + FIX_LENGTH(cs, pos, length, char_length); + memcpy(key, pos, char_length); + if (length > char_length) + cs->cset->fill(cs, (char*) key+char_length, length-char_length, ' '); + key+= length; + } + if (last_used_keyseg) + *last_used_keyseg= keyseg; + + /* set flag to SEARCH_PART_KEY if we are not using all key parts */ + int_key->flag= keyseg->type ? SEARCH_PART_KEY : 0; + int_key->ref_length= 0; + int_key->data_length= (key - int_key->data); + + DBUG_PRINT("exit", ("length: %u", int_key->data_length)); + DBUG_RETURN(int_key); +} /* _ma_pack_key */ + + +/** + Copy a key +*/ + +void _ma_copy_key(MARIA_KEY *to, const MARIA_KEY *from) +{ + memcpy(to->data, from->data, from->data_length + from->ref_length); + to->keyinfo= from->keyinfo; + to->data_length= from->data_length; + to->ref_length= from->ref_length; + to->flag= from->flag; +} + + +/* + Store found key in record + + SYNOPSIS + _ma_put_key_in_record() + info MARIA handler + keynr Key number that was used + record Store key here + + Last read key is in info->lastkey + + NOTES + Used when only-keyread is wanted + + RETURN + 0 ok + 1 error +*/ + +static int _ma_put_key_in_record(register MARIA_HA *info, uint keynr, + uchar *record) +{ + reg2 uchar *key; + uchar *pos,*key_end; + reg1 HA_KEYSEG *keyseg; + uchar *blob_ptr; + DBUG_ENTER("_ma_put_key_in_record"); + + blob_ptr= info->lastkey_buff2; /* Place to put blob parts */ + key= info->last_key.data; /* Key that was read */ + key_end= key + info->last_key.data_length; + for (keyseg=info->s->keyinfo[keynr].seg ; keyseg->type ;keyseg++) + { + if (keyseg->null_bit) + { + if (!*key++) + { + record[keyseg->null_pos]|= keyseg->null_bit; + continue; + } + record[keyseg->null_pos]&= ~keyseg->null_bit; + } + if (keyseg->type == HA_KEYTYPE_BIT) + { + uint length= keyseg->length; + + if (keyseg->bit_length) + { + uchar bits= *key++; + set_rec_bits(bits, record + keyseg->bit_pos, keyseg->bit_start, + keyseg->bit_length); + length--; + } + else + { + clr_rec_bits(record + keyseg->bit_pos, keyseg->bit_start, + keyseg->bit_length); + } + memcpy(record + keyseg->start, key, length); + key+= length; + continue; + } + if (keyseg->flag & HA_SPACE_PACK) + { + uint length; + get_key_length(length,key); +#ifdef CHECK_KEYS + if (length > keyseg->length || key+length > key_end) + goto err; +#endif + pos= record+keyseg->start; + if (keyseg->type != (int) HA_KEYTYPE_NUM) + { + memcpy(pos,key,(size_t) length); + keyseg->charset->cset->fill(keyseg->charset, + (char*) pos + length, + keyseg->length - length, + ' '); + } + else + { + bfill(pos,keyseg->length-length,' '); + memcpy(pos+keyseg->length-length,key,(size_t) length); + } + key+=length; + continue; + } + + if (keyseg->flag & HA_VAR_LENGTH_PART) + { + uint length; + get_key_length(length,key); +#ifdef CHECK_KEYS + if (length > keyseg->length || key+length > key_end) + goto err; +#endif + /* Store key length */ + if (keyseg->bit_start == 1) + *(uchar*) (record+keyseg->start)= (uchar) length; + else + int2store(record+keyseg->start, length); + /* And key data */ + memcpy(record+keyseg->start + keyseg->bit_start, key, length); + key+= length; + } + else if (keyseg->flag & HA_BLOB_PART) + { + uint length; + get_key_length(length,key); +#ifdef CHECK_KEYS + if (length > keyseg->length || key+length > key_end) + goto err; +#endif + memcpy(record+keyseg->start+keyseg->bit_start, + (char*) &blob_ptr,sizeof(char*)); + memcpy(blob_ptr,key,length); + blob_ptr+=length; + + /* The above changed info->lastkey2. Inform maria_rnext_same(). */ + info->update&= ~HA_STATE_RNEXT_SAME; + + _ma_store_blob_length(record+keyseg->start, + (uint) keyseg->bit_start,length); + key+=length; + } + else if (keyseg->flag & HA_SWAP_KEY) + { + uchar *to= record+keyseg->start+keyseg->length; + uchar *end= key+keyseg->length; +#ifdef CHECK_KEYS + if (end > key_end) + goto err; +#endif + do + { + *--to= *key++; + } while (key != end); + continue; + } + else + { +#ifdef CHECK_KEYS + if (key+keyseg->length > key_end) + goto err; +#endif + memcpy(record+keyseg->start, key, (size_t) keyseg->length); + key+= keyseg->length; + } + } + DBUG_RETURN(0); + +err: + DBUG_RETURN(1); /* Crashed row */ +} /* _ma_put_key_in_record */ + + + /* Here when key reads are used */ + +int _ma_read_key_record(MARIA_HA *info, uchar *buf, MARIA_RECORD_POS filepos) +{ + fast_ma_writeinfo(info); + if (filepos != HA_OFFSET_ERROR) + { + if (info->lastinx >= 0) + { /* Read only key */ + if (_ma_put_key_in_record(info,(uint) info->lastinx,buf)) + { + maria_print_error(info->s, HA_ERR_CRASHED); + my_errno=HA_ERR_CRASHED; + return -1; + } + info->update|= HA_STATE_AKTIV; /* We should find a record */ + return 0; + } + my_errno=HA_ERR_WRONG_INDEX; + } + return(-1); /* Wrong data to read */ +} + + +/* + Retrieve auto_increment info + + SYNOPSIS + retrieve_auto_increment() + key Auto-increment key + key_type Key's type + + NOTE + 'key' should in "record" format, that is, how it is packed in a record + (this matters with HA_SWAP_KEY). + + IMPLEMENTATION + For signed columns we don't retrieve the auto increment value if it's + less than zero. +*/ + +ulonglong ma_retrieve_auto_increment(const uchar *key, uint8 key_type) +{ + ulonglong value= 0; /* Store unsigned values here */ + longlong s_value= 0; /* Store signed values here */ + + switch (key_type) { + case HA_KEYTYPE_INT8: + s_value= (longlong) *(const char*)key; + break; + case HA_KEYTYPE_BINARY: + value=(ulonglong) *key; + break; + case HA_KEYTYPE_SHORT_INT: + s_value= (longlong) sint2korr(key); + break; + case HA_KEYTYPE_USHORT_INT: + value=(ulonglong) uint2korr(key); + break; + case HA_KEYTYPE_LONG_INT: + s_value= (longlong) sint4korr(key); + break; + case HA_KEYTYPE_ULONG_INT: + value=(ulonglong) uint4korr(key); + break; + case HA_KEYTYPE_INT24: + s_value= (longlong) sint3korr(key); + break; + case HA_KEYTYPE_UINT24: + value=(ulonglong) uint3korr(key); + break; + case HA_KEYTYPE_FLOAT: /* This shouldn't be used */ + { + float f_1; + float4get(f_1,key); + /* Ignore negative values */ + value = (f_1 < (float) 0.0) ? 0 : (ulonglong) f_1; + break; + } + case HA_KEYTYPE_DOUBLE: /* This shouldn't be used */ + { + double f_1; + float8get(f_1,key); + /* Ignore negative values */ + value = (f_1 < 0.0) ? 0 : (ulonglong) f_1; + break; + } + case HA_KEYTYPE_LONGLONG: + s_value= sint8korr(key); + break; + case HA_KEYTYPE_ULONGLONG: + value= uint8korr(key); + break; + default: + DBUG_ASSERT(0); + value=0; /* Error */ + break; + } + + /* + The following code works becasue if s_value < 0 then value is 0 + and if s_value == 0 then value will contain either s_value or the + correct value. + */ + return (s_value > 0) ? (ulonglong) s_value : value; +} diff --git a/storage/maria/ma_key_recover.c b/storage/maria/ma_key_recover.c new file mode 100644 index 00000000000..38317cd61db --- /dev/null +++ b/storage/maria/ma_key_recover.c @@ -0,0 +1,1310 @@ +/* Copyright (C) 2007 Michael Widenius + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You 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 */ + +/* Redo of index */ + +#include "maria_def.h" +#include "ma_blockrec.h" +#include "trnman.h" +#include "ma_key_recover.h" +#include "ma_rt_index.h" + +/**************************************************************************** + Some helper functions used both by key page loggin and block page loggin +****************************************************************************/ + +/** + @brief Unpin all pinned pages + + @fn _ma_unpin_all_pages() + @param info Maria handler + @param undo_lsn LSN for undo pages. LSN_IMPOSSIBLE if we shouldn't write + undo (like on duplicate key errors) + + info->pinned_pages is the list of pages to unpin. Each member of the list + must have its 'changed' saying if the page was changed or not. + + @note + We unpin pages in the reverse order as they where pinned; This is not + necessary now, but may simplify things in the future. + + @return + @retval 0 ok + @retval 1 error (fatal disk error) +*/ + +void _ma_unpin_all_pages(MARIA_HA *info, LSN undo_lsn) +{ + MARIA_PINNED_PAGE *page_link= ((MARIA_PINNED_PAGE*) + dynamic_array_ptr(&info->pinned_pages, 0)); + MARIA_PINNED_PAGE *pinned_page= page_link + info->pinned_pages.elements; + DBUG_ENTER("_ma_unpin_all_pages"); + DBUG_PRINT("info", ("undo_lsn: %lu", (ulong) undo_lsn)); + + if (!info->s->now_transactional) + DBUG_ASSERT(undo_lsn == LSN_IMPOSSIBLE || maria_in_recovery); + + while (pinned_page-- != page_link) + { + /* + Note this assert fails if we got a disk error or the record file + is corrupted, which means we should have this enabled only in debug + builds. + */ +#ifdef EXTRA_DEBUG + DBUG_ASSERT(!pinned_page->changed || + undo_lsn != LSN_IMPOSSIBLE || !info->s->now_transactional); +#endif + pagecache_unlock_by_link(info->s->pagecache, pinned_page->link, + pinned_page->unlock, PAGECACHE_UNPIN, + info->trn->rec_lsn, undo_lsn, + pinned_page->changed, FALSE); + } + + info->pinned_pages.elements= 0; + DBUG_VOID_RETURN; +} + + +my_bool _ma_write_clr(MARIA_HA *info, LSN undo_lsn, + enum translog_record_type undo_type, + my_bool store_checksum, ha_checksum checksum, + LSN *res_lsn, void *extra_msg) +{ + uchar log_data[LSN_STORE_SIZE + FILEID_STORE_SIZE + CLR_TYPE_STORE_SIZE + + HA_CHECKSUM_STORE_SIZE+ KEY_NR_STORE_SIZE + PAGE_STORE_SIZE]; + uchar *log_pos; + LEX_CUSTRING log_array[TRANSLOG_INTERNAL_PARTS + 1]; + struct st_msg_to_write_hook_for_clr_end msg; + my_bool res; + DBUG_ENTER("_ma_write_clr"); + + /* undo_lsn must be first for compression to work */ + lsn_store(log_data, undo_lsn); + clr_type_store(log_data + LSN_STORE_SIZE + FILEID_STORE_SIZE, undo_type); + log_pos= log_data + LSN_STORE_SIZE + FILEID_STORE_SIZE + CLR_TYPE_STORE_SIZE; + + /* Extra_msg is handled in write_hook_for_clr_end() */ + msg.undone_record_type= undo_type; + msg.previous_undo_lsn= undo_lsn; + msg.extra_msg= extra_msg; + msg.checksum_delta= 0; + + if (store_checksum) + { + msg.checksum_delta= checksum; + ha_checksum_store(log_pos, checksum); + log_pos+= HA_CHECKSUM_STORE_SIZE; + } + else if (undo_type == LOGREC_UNDO_KEY_INSERT_WITH_ROOT || + undo_type == LOGREC_UNDO_KEY_DELETE_WITH_ROOT) + { + /* Key root changed. Store new key root */ + struct st_msg_to_write_hook_for_undo_key *undo_msg= extra_msg; + pgcache_page_no_t page; + key_nr_store(log_pos, undo_msg->keynr); + page= (undo_msg->value == HA_OFFSET_ERROR ? IMPOSSIBLE_PAGE_NO : + undo_msg->value / info->s->block_size); + page_store(log_pos + KEY_NR_STORE_SIZE, page); + log_pos+= KEY_NR_STORE_SIZE + PAGE_STORE_SIZE; + } + log_array[TRANSLOG_INTERNAL_PARTS + 0].str= log_data; + log_array[TRANSLOG_INTERNAL_PARTS + 0].length= (uint) (log_pos - log_data); + + + /* + We need intern_lock mutex for calling _ma_state_info_write in the trigger. + We do it here to have the same sequence of mutexes locking everywhere + (first intern_lock then transactional log buffer lock) + */ + if (undo_type == LOGREC_UNDO_BULK_INSERT) + pthread_mutex_lock(&info->s->intern_lock); + + res= translog_write_record(res_lsn, LOGREC_CLR_END, + info->trn, info, + (translog_size_t) + log_array[TRANSLOG_INTERNAL_PARTS + 0].length, + TRANSLOG_INTERNAL_PARTS + 1, log_array, + log_data + LSN_STORE_SIZE, &msg); + if (undo_type == LOGREC_UNDO_BULK_INSERT) + pthread_mutex_unlock(&info->s->intern_lock); + DBUG_RETURN(res); +} + + +/** + @brief Sets transaction's undo_lsn, first_undo_lsn if needed + + @return Operation status, always 0 (success) +*/ + +my_bool write_hook_for_clr_end(enum translog_record_type type + __attribute__ ((unused)), + TRN *trn, MARIA_HA *tbl_info, + LSN *lsn __attribute__ ((unused)), + void *hook_arg) +{ + MARIA_SHARE *share= tbl_info->s; + struct st_msg_to_write_hook_for_clr_end *msg= + (struct st_msg_to_write_hook_for_clr_end *)hook_arg; + my_bool error= FALSE; + DBUG_ENTER("write_hook_for_clr_end"); + DBUG_ASSERT(trn->trid != 0); + trn->undo_lsn= msg->previous_undo_lsn; + + switch (msg->undone_record_type) { + case LOGREC_UNDO_ROW_DELETE: + share->state.state.records++; + share->state.state.checksum+= msg->checksum_delta; + break; + case LOGREC_UNDO_ROW_INSERT: + share->state.state.records--; + share->state.state.checksum+= msg->checksum_delta; + break; + case LOGREC_UNDO_ROW_UPDATE: + share->state.state.checksum+= msg->checksum_delta; + break; + case LOGREC_UNDO_KEY_INSERT_WITH_ROOT: + case LOGREC_UNDO_KEY_DELETE_WITH_ROOT: + { + /* Update key root */ + struct st_msg_to_write_hook_for_undo_key *extra_msg= + (struct st_msg_to_write_hook_for_undo_key *) msg->extra_msg; + *extra_msg->root= extra_msg->value; + break; + } + case LOGREC_UNDO_KEY_INSERT: + case LOGREC_UNDO_KEY_DELETE: + break; + case LOGREC_UNDO_BULK_INSERT: + safe_mutex_assert_owner(&share->intern_lock); + error= (maria_enable_indexes(tbl_info) || + /* we enabled indices, need '2' below */ + _ma_state_info_write(share, + MA_STATE_INFO_WRITE_DONT_MOVE_OFFSET | + MA_STATE_INFO_WRITE_FULL_INFO)); + /* no need for _ma_reset_status(): REDO_DELETE_ALL is just before us */ + break; + default: + DBUG_ASSERT(0); + } + if (trn->undo_lsn == LSN_IMPOSSIBLE) /* has fully rolled back */ + trn->first_undo_lsn= LSN_WITH_FLAGS_TO_FLAGS(trn->first_undo_lsn); + DBUG_RETURN(error); +} + + +/** + @brief write hook for undo key +*/ + +my_bool write_hook_for_undo_key(enum translog_record_type type, + TRN *trn, MARIA_HA *tbl_info, + LSN *lsn, void *hook_arg) +{ + struct st_msg_to_write_hook_for_undo_key *msg= + (struct st_msg_to_write_hook_for_undo_key *) hook_arg; + + *msg->root= msg->value; + _ma_fast_unlock_key_del(tbl_info); + return write_hook_for_undo(type, trn, tbl_info, lsn, 0); +} + + +/** + Updates "auto_increment" and calls the generic UNDO_KEY hook + + @return Operation status, always 0 (success) +*/ + +my_bool write_hook_for_undo_key_insert(enum translog_record_type type, + TRN *trn, MARIA_HA *tbl_info, + LSN *lsn, void *hook_arg) +{ + struct st_msg_to_write_hook_for_undo_key *msg= + (struct st_msg_to_write_hook_for_undo_key *) hook_arg; + MARIA_SHARE *share= tbl_info->s; + if (msg->auto_increment > 0) + { + /* + Only reason to set it here is to have a mutex protect from checkpoint + reading at the same time (would see a corrupted value). + + The purpose of the following code is to set auto_increment if the row + has a with auto_increment value higher than the current one. We also + want to be able to restore the old value, in case of rollback, + if no one else has tried to set the value. + + The logic used is that we only restore the auto_increment value if + tbl_info->last_auto_increment == share->last_auto_increment + when it's time to do the rollback. + */ + DBUG_PRINT("info",("auto_inc: %lu new auto_inc: %lu", + (ulong)share->state.auto_increment, + (ulong)msg->auto_increment)); + if (share->state.auto_increment < msg->auto_increment) + { + /* Remember the original value, in case of rollback */ + tbl_info->last_auto_increment= share->last_auto_increment= + share->state.auto_increment; + share->state.auto_increment= msg->auto_increment; + } + else + { + /* + If the current value would have affected the original auto_increment + value, set it to an impossible value so that it's not restored on + rollback + */ + if (msg->auto_increment > share->last_auto_increment) + share->last_auto_increment= ~(ulonglong) 0; + } + } + return write_hook_for_undo_key(type, trn, tbl_info, lsn, hook_arg); +} + + +/** + @brief Updates "share->auto_increment" in case of abort and calls + generic UNDO_KEY hook + + @return Operation status, always 0 (success) +*/ + +my_bool write_hook_for_undo_key_delete(enum translog_record_type type, + TRN *trn, MARIA_HA *tbl_info, + LSN *lsn, void *hook_arg) +{ + struct st_msg_to_write_hook_for_undo_key *msg= + (struct st_msg_to_write_hook_for_undo_key *) hook_arg; + MARIA_SHARE *share= tbl_info->s; + if (msg->auto_increment > 0) /* If auto increment key */ + { + /* Restore auto increment if no one has changed it in between */ + if (share->last_auto_increment == tbl_info->last_auto_increment && + tbl_info->last_auto_increment != ~(ulonglong) 0) + share->state.auto_increment= tbl_info->last_auto_increment; + } + return write_hook_for_undo_key(type, trn, tbl_info, lsn, hook_arg); +} + + +/***************************************************************************** + Functions for logging of key page changes +*****************************************************************************/ + +/** + @brief + Write log entry for page that has got data added or deleted at start of page +*/ + +my_bool _ma_log_prefix(MARIA_PAGE *ma_page, uint changed_length, + int move_length) +{ + uint translog_parts; + LSN lsn; + uchar log_data[FILEID_STORE_SIZE + PAGE_STORE_SIZE + 7 + 7 + 2], *log_pos; + uchar *buff= ma_page->buff; + LEX_CUSTRING log_array[TRANSLOG_INTERNAL_PARTS + 3]; + pgcache_page_no_t page; + MARIA_HA *info= ma_page->info; + DBUG_ENTER("_ma_log_prefix"); + DBUG_PRINT("enter", ("page: %lu changed_length: %u move_length: %d", + (ulong) ma_page->pos, changed_length, move_length)); + + page= ma_page->pos / info->s->block_size; + log_pos= log_data + FILEID_STORE_SIZE; + page_store(log_pos, page); + log_pos+= PAGE_STORE_SIZE; + + /* Store keypage_flag */ + *log_pos++= KEY_OP_SET_PAGEFLAG; + *log_pos++= buff[KEYPAGE_TRANSFLAG_OFFSET]; + + if (move_length < 0) + { + /* Delete prefix */ + log_pos[0]= KEY_OP_DEL_PREFIX; + int2store(log_pos+1, -move_length); + log_pos+= 3; + if (changed_length) + { + /* + We don't need a KEY_OP_OFFSET as KEY_OP_DEL_PREFIX has an implicit + offset + */ + log_pos[0]= KEY_OP_CHANGE; + int2store(log_pos+1, changed_length); + log_pos+= 3; + } + } + else + { + /* Add prefix */ + DBUG_ASSERT(changed_length >0 && (int) changed_length >= move_length); + log_pos[0]= KEY_OP_ADD_PREFIX; + int2store(log_pos+1, move_length); + int2store(log_pos+3, changed_length); + log_pos+= 5; + } + + translog_parts= 1; + log_array[TRANSLOG_INTERNAL_PARTS + 0].str= log_data; + log_array[TRANSLOG_INTERNAL_PARTS + 0].length= (uint) (log_pos - + log_data); + if (changed_length) + { + log_array[TRANSLOG_INTERNAL_PARTS + 1].str= (buff + + info->s->keypage_header); + log_array[TRANSLOG_INTERNAL_PARTS + 1].length= changed_length; + translog_parts= 2; + } + +#ifdef EXTRA_DEBUG_KEY_CHANGES + { + int page_length= ma_page->size; + ha_checksum crc; + crc= my_checksum(0, buff + LSN_STORE_SIZE, page_length - LSN_STORE_SIZE); + log_pos[0]= KEY_OP_CHECK; + int2store(log_pos+1, page_length); + int4store(log_pos+3, crc); + + log_array[TRANSLOG_INTERNAL_PARTS + translog_parts].str= log_pos; + log_array[TRANSLOG_INTERNAL_PARTS + translog_parts].length= 7; + changed_length+= 7; + translog_parts++; + } +#endif + + DBUG_RETURN(translog_write_record(&lsn, LOGREC_REDO_INDEX, + info->trn, info, + (translog_size_t) + log_array[TRANSLOG_INTERNAL_PARTS + + 0].length + changed_length, + TRANSLOG_INTERNAL_PARTS + translog_parts, + log_array, log_data, NULL)); +} + + +/** + @brief + Write log entry for page that has got data added or deleted at end of page +*/ + +my_bool _ma_log_suffix(MARIA_PAGE *ma_page, uint org_length, uint new_length) +{ + LSN lsn; + LEX_CUSTRING log_array[TRANSLOG_INTERNAL_PARTS + 3]; + uchar log_data[FILEID_STORE_SIZE + PAGE_STORE_SIZE + 10 + 7 + 2], *log_pos; + uchar *buff= ma_page->buff; + int diff; + uint translog_parts, extra_length; + MARIA_HA *info= ma_page->info; + pgcache_page_no_t page; + DBUG_ENTER("_ma_log_suffix"); + DBUG_PRINT("enter", ("page: %lu org_length: %u new_length: %u", + (ulong) ma_page->pos, org_length, new_length)); + + page= ma_page->pos / info->s->block_size; + + log_pos= log_data + FILEID_STORE_SIZE; + page_store(log_pos, page); + log_pos+= PAGE_STORE_SIZE; + + /* Store keypage_flag */ + *log_pos++= KEY_OP_SET_PAGEFLAG; + *log_pos++= buff[KEYPAGE_TRANSFLAG_OFFSET]; + + if ((diff= (int) (new_length - org_length)) < 0) + { + log_pos[0]= KEY_OP_DEL_SUFFIX; + int2store(log_pos+1, -diff); + log_pos+= 3; + translog_parts= 1; + extra_length= 0; + } + else + { + log_pos[0]= KEY_OP_ADD_SUFFIX; + int2store(log_pos+1, diff); + log_pos+= 3; + log_array[TRANSLOG_INTERNAL_PARTS + 1].str= buff + org_length; + log_array[TRANSLOG_INTERNAL_PARTS + 1].length= (uint) diff; + translog_parts= 2; + extra_length= (uint) diff; + } + + log_array[TRANSLOG_INTERNAL_PARTS + 0].str= log_data; + log_array[TRANSLOG_INTERNAL_PARTS + 0].length= (uint) (log_pos - + log_data); + +#ifdef EXTRA_DEBUG_KEY_CHANGES + { + ha_checksum crc; + crc= my_checksum(0, buff + LSN_STORE_SIZE, new_length - LSN_STORE_SIZE); + log_pos[0]= KEY_OP_CHECK; + int2store(log_pos+1, new_length); + int4store(log_pos+3, crc); + + log_array[TRANSLOG_INTERNAL_PARTS + translog_parts].str= log_pos; + log_array[TRANSLOG_INTERNAL_PARTS + translog_parts].length= 7; + extra_length+= 7; + translog_parts++; + } +#endif + + DBUG_RETURN(translog_write_record(&lsn, LOGREC_REDO_INDEX, + info->trn, info, + (translog_size_t) + log_array[TRANSLOG_INTERNAL_PARTS + + 0].length + extra_length, + TRANSLOG_INTERNAL_PARTS + translog_parts, + log_array, log_data, NULL)); +} + + +/** + @brief Log that a key was added to the page + + @param ma_page Changed page + @param org_page_length Length of data in page before key was added + + @note + If handle_overflow is set, then we have to protect against + logging changes that is outside of the page. + This may happen during underflow() handling where the buffer + in memory temporary contains more data than block_size +*/ + +my_bool _ma_log_add(MARIA_PAGE *ma_page, + uint org_page_length, uchar *key_pos, + uint changed_length, int move_length, + my_bool handle_overflow __attribute__ ((unused))) +{ + LSN lsn; + uchar log_data[FILEID_STORE_SIZE + PAGE_STORE_SIZE + 3 + 3 + 3 + 3 + 7 + 2]; + uchar *log_pos; + uchar *buff= ma_page->buff; + LEX_CUSTRING log_array[TRANSLOG_INTERNAL_PARTS + 3]; + MARIA_HA *info= ma_page->info; + uint offset= (uint) (key_pos - buff); + uint page_length= info->s->block_size - KEYPAGE_CHECKSUM_SIZE; + uint translog_parts; + pgcache_page_no_t page_pos; + DBUG_ENTER("_ma_log_add"); + DBUG_PRINT("enter", ("page: %lu org_page_length: %u changed_length: %u " + "move_length: %d", + (ulong) ma_page->pos, org_page_length, changed_length, + move_length)); + DBUG_ASSERT(info->s->now_transactional); + + /* + Write REDO entry that contains the logical operations we need + to do the page + */ + log_pos= log_data + FILEID_STORE_SIZE; + page_pos= ma_page->pos / info->s->block_size; + page_store(log_pos, page_pos); + log_pos+= PAGE_STORE_SIZE; + + /* Store keypage_flag */ + *log_pos++= KEY_OP_SET_PAGEFLAG; + *log_pos++= buff[KEYPAGE_TRANSFLAG_OFFSET]; + + if (org_page_length + move_length > page_length) + { + /* + Overflow. Cut either key or data from page end so that key fits + The code that splits the too big page will ignore logging any + data over org_page_length + */ + DBUG_ASSERT(handle_overflow); + if (offset + changed_length > page_length) + { + changed_length= page_length - offset; + move_length= 0; + } + else + { + uint diff= org_page_length + move_length - page_length; + log_pos[0]= KEY_OP_DEL_SUFFIX; + int2store(log_pos+1, diff); + log_pos+= 3; + org_page_length= page_length - move_length; + } + } + + if (offset == org_page_length) + log_pos[0]= KEY_OP_ADD_SUFFIX; + else + { + log_pos[0]= KEY_OP_OFFSET; + int2store(log_pos+1, offset); + log_pos+= 3; + if (move_length) + { + log_pos[0]= KEY_OP_SHIFT; + int2store(log_pos+1, move_length); + log_pos+= 3; + } + log_pos[0]= KEY_OP_CHANGE; + } + int2store(log_pos+1, changed_length); + log_pos+= 3; + translog_parts= 2; + + log_array[TRANSLOG_INTERNAL_PARTS + 0].str= log_data; + log_array[TRANSLOG_INTERNAL_PARTS + 0].length= (uint) (log_pos - + log_data); + log_array[TRANSLOG_INTERNAL_PARTS + 1].str= key_pos; + log_array[TRANSLOG_INTERNAL_PARTS + 1].length= changed_length; + +#ifdef EXTRA_DEBUG_KEY_CHANGES + { + MARIA_SHARE *share= info->s; + ha_checksum crc; + uint save_page_length= ma_page->size; + uint new_length= org_page_length + move_length; + _ma_store_page_used(share, buff, new_length); + crc= my_checksum(0, buff + LSN_STORE_SIZE, new_length - LSN_STORE_SIZE); + log_pos[0]= KEY_OP_CHECK; + int2store(log_pos+1, new_length); + int4store(log_pos+3, crc); + + log_array[TRANSLOG_INTERNAL_PARTS + translog_parts].str= log_pos; + log_array[TRANSLOG_INTERNAL_PARTS + translog_parts].length= 7; + changed_length+= 7; + translog_parts++; + _ma_store_page_used(share, buff, save_page_length); + } +#endif + + if (translog_write_record(&lsn, LOGREC_REDO_INDEX, + info->trn, info, + (translog_size_t) + log_array[TRANSLOG_INTERNAL_PARTS + 0].length + + changed_length, + TRANSLOG_INTERNAL_PARTS + translog_parts, + log_array, log_data, NULL)) + DBUG_RETURN(-1); + DBUG_RETURN(0); +} + + +/**************************************************************************** + Redo of key pages +****************************************************************************/ + +/** + @brief Apply LOGREC_REDO_INDEX_NEW_PAGE + + @param info Maria handler + @param header Header (without FILEID) + + @return Operation status + @retval 0 OK + @retval 1 Error +*/ + +uint _ma_apply_redo_index_new_page(MARIA_HA *info, LSN lsn, + const uchar *header, uint length) +{ + pgcache_page_no_t root_page= page_korr(header); + pgcache_page_no_t free_page= page_korr(header + PAGE_STORE_SIZE); + uint key_nr= key_nr_korr(header + PAGE_STORE_SIZE * 2); + my_bool page_type_flag= header[PAGE_STORE_SIZE * 2 + KEY_NR_STORE_SIZE]; + enum pagecache_page_lock unlock_method; + enum pagecache_page_pin unpin_method; + MARIA_PINNED_PAGE page_link; + my_off_t file_size; + uchar *buff; + uint result; + MARIA_SHARE *share= info->s; + DBUG_ENTER("_ma_apply_redo_index_new_page"); + DBUG_PRINT("enter", ("root_page: %lu free_page: %lu", + (ulong) root_page, (ulong) free_page)); + + /* Set header to point at key data */ + + share->state.changed|= (STATE_CHANGED | STATE_NOT_OPTIMIZED_KEYS | + STATE_NOT_SORTED_PAGES | STATE_NOT_ZEROFILLED | + STATE_NOT_MOVABLE); + + header+= PAGE_STORE_SIZE * 2 + KEY_NR_STORE_SIZE + 1; + length-= PAGE_STORE_SIZE * 2 + KEY_NR_STORE_SIZE + 1; + + file_size= (my_off_t) (root_page + 1) * share->block_size; + if (cmp_translog_addr(lsn, share->state.is_of_horizon) >= 0) + { + /* free_page is 0 if we shouldn't set key_del */ + if (free_page) + { + if (free_page != IMPOSSIBLE_PAGE_NO) + share->state.key_del= (my_off_t) free_page * share->block_size; + else + share->state.key_del= HA_OFFSET_ERROR; + } + if (page_type_flag) /* root page */ + share->state.key_root[key_nr]= file_size - share->block_size; + } + + if (file_size > share->state.state.key_file_length) + { + share->state.state.key_file_length= file_size; + buff= info->keyread_buff; + info->keyread_buff_used= 1; + unlock_method= PAGECACHE_LOCK_WRITE; + unpin_method= PAGECACHE_PIN; + } + else + { + if (!(buff= pagecache_read(share->pagecache, &share->kfile, + root_page, 0, 0, + PAGECACHE_PLAIN_PAGE, PAGECACHE_LOCK_WRITE, + &page_link.link))) + { + if (my_errno != HA_ERR_FILE_TOO_SHORT && + my_errno != HA_ERR_WRONG_CRC) + { + result= 1; + goto err; + } + buff= pagecache_block_link_to_buffer(page_link.link); + } + else if (lsn_korr(buff) >= lsn) + { + /* Already applied */ + DBUG_PRINT("info", ("Page is up to date, skipping redo")); + result= 0; + goto err; + } + unlock_method= PAGECACHE_LOCK_LEFT_WRITELOCKED; + unpin_method= PAGECACHE_PIN_LEFT_PINNED; + } + + /* Write modified page */ + bzero(buff, LSN_STORE_SIZE); + memcpy(buff + LSN_STORE_SIZE, header, length); + bzero(buff + LSN_STORE_SIZE + length, + share->block_size - LSN_STORE_SIZE - KEYPAGE_CHECKSUM_SIZE - length); + bfill(buff + share->block_size - KEYPAGE_CHECKSUM_SIZE, + KEYPAGE_CHECKSUM_SIZE, (uchar) 255); + + result= 0; + if (unlock_method == PAGECACHE_LOCK_WRITE && + pagecache_write(share->pagecache, + &share->kfile, root_page, 0, + buff, PAGECACHE_PLAIN_PAGE, + unlock_method, unpin_method, + PAGECACHE_WRITE_DELAY, &page_link.link, + LSN_IMPOSSIBLE)) + result= 1; + + /* Mark page to be unlocked and written at _ma_unpin_all_pages() */ + page_link.unlock= PAGECACHE_LOCK_WRITE_UNLOCK; + page_link.changed= 1; + push_dynamic(&info->pinned_pages, (void*) &page_link); + DBUG_RETURN(result); + +err: + pagecache_unlock_by_link(share->pagecache, page_link.link, + PAGECACHE_LOCK_WRITE_UNLOCK, + PAGECACHE_UNPIN, LSN_IMPOSSIBLE, + LSN_IMPOSSIBLE, 0, FALSE); + DBUG_RETURN(result); +} + + +/** + @brief Apply LOGREC_REDO_INDEX_FREE_PAGE + + @param info Maria handler + @param header Header (without FILEID) + + @return Operation status + @retval 0 OK + @retval 1 Error +*/ + +uint _ma_apply_redo_index_free_page(MARIA_HA *info, + LSN lsn, + const uchar *header) +{ + pgcache_page_no_t page= page_korr(header); + pgcache_page_no_t free_page= page_korr(header + PAGE_STORE_SIZE); + my_off_t old_link; + MARIA_PINNED_PAGE page_link; + MARIA_SHARE *share= info->s; + uchar *buff; + int result; + DBUG_ENTER("_ma_apply_redo_index_free_page"); + DBUG_PRINT("enter", ("page: %lu free_page: %lu", + (ulong) page, (ulong) free_page)); + + share->state.changed|= (STATE_CHANGED | STATE_NOT_OPTIMIZED_KEYS | + STATE_NOT_SORTED_PAGES | STATE_NOT_ZEROFILLED | + STATE_NOT_MOVABLE); + + if (cmp_translog_addr(lsn, share->state.is_of_horizon) >= 0) + share->state.key_del= (my_off_t) page * share->block_size; + + old_link= ((free_page != IMPOSSIBLE_PAGE_NO) ? + (my_off_t) free_page * share->block_size : + HA_OFFSET_ERROR); + if (!(buff= pagecache_read(share->pagecache, &share->kfile, + page, 0, 0, + PAGECACHE_PLAIN_PAGE, PAGECACHE_LOCK_WRITE, + &page_link.link))) + { + result= (uint) my_errno; + goto err; + } + if (lsn_korr(buff) >= lsn) + { + /* Already applied */ + result= 0; + goto err; + } + /* Free page */ + bzero(buff + LSN_STORE_SIZE, share->keypage_header - LSN_STORE_SIZE); + _ma_store_keynr(share, buff, (uchar) MARIA_DELETE_KEY_NR); + _ma_store_page_used(share, buff, share->keypage_header + 8); + mi_sizestore(buff + share->keypage_header, old_link); + +#ifdef IDENTICAL_PAGES_AFTER_RECOVERY + { + bzero(buff + share->keypage_header + 8, + share->block_size - share->keypage_header - 8 - + KEYPAGE_CHECKSUM_SIZE); + } +#endif + + /* Mark page to be unlocked and written at _ma_unpin_all_pages() */ + page_link.unlock= PAGECACHE_LOCK_WRITE_UNLOCK; + page_link.changed= 1; + push_dynamic(&info->pinned_pages, (void*) &page_link); + DBUG_RETURN(0); + +err: + pagecache_unlock_by_link(share->pagecache, page_link.link, + PAGECACHE_LOCK_WRITE_UNLOCK, + PAGECACHE_UNPIN, LSN_IMPOSSIBLE, + LSN_IMPOSSIBLE, 0, FALSE); + DBUG_RETURN(result); +} + + +/** + @brief Apply LOGREC_REDO_INDEX + + @fn ma_apply_redo_index() + @param info Maria handler + @param header Header (without FILEID) + + @notes + Data for this part is a set of logical instructions of how to + construct the key page. + + Information of the layout of the components for REDO_INDEX: + + Name Parameters (in byte) Information + KEY_OP_OFFSET 2 Set position for next operations + KEY_OP_SHIFT 2 (signed int) How much to shift down or up + KEY_OP_CHANGE 2 length, data Data to replace at 'pos' + KEY_OP_ADD_PREFIX 2 move-length How much data should be moved up + 2 change-length Data to be replaced at page start + KEY_OP_DEL_PREFIX 2 length Bytes to be deleted at page start + KEY_OP_ADD_SUFFIX 2 length, data Add data to end of page + KEY_OP_DEL_SUFFIX 2 length Reduce page length with this + Sets position to start of page + KEY_OP_CHECK 6 page_length[2},CRC Used only when debugging + KEY_OP_COMPACT_PAGE 6 transid + KEY_OP_SET_PAGEFLAG 1 flag for page + + @return Operation status + @retval 0 OK + @retval 1 Error +*/ + +long my_counter= 0; + +uint _ma_apply_redo_index(MARIA_HA *info, + LSN lsn, const uchar *header, uint head_length) +{ + MARIA_SHARE *share= info->s; + pgcache_page_no_t page_pos= page_korr(header); + MARIA_PINNED_PAGE page_link; + uchar *buff; + const uchar *header_end= header + head_length; + uint page_offset= 0, org_page_length; + uint nod_flag, page_length, keypage_header, keynr; + int result; + MARIA_PAGE page; + DBUG_ENTER("_ma_apply_redo_index"); + DBUG_PRINT("enter", ("page: %lu", (ulong) page_pos)); + + /* Set header to point at key data */ + header+= PAGE_STORE_SIZE; + + if (!(buff= pagecache_read(share->pagecache, &share->kfile, + page_pos, 0, 0, + PAGECACHE_PLAIN_PAGE, PAGECACHE_LOCK_WRITE, + &page_link.link))) + { + result= 1; + goto err; + } + if (lsn_korr(buff) >= lsn) + { + /* Already applied */ + DBUG_PRINT("info", ("Page is up to date, skipping redo")); + result= 0; + goto err; + } + + keynr= _ma_get_keynr(share, buff); + _ma_page_setup(&page, info, share->keyinfo + keynr, page_pos, buff); + nod_flag= page.node; + org_page_length= page_length= page.size; + + keypage_header= share->keypage_header; + DBUG_PRINT("redo", ("page_length: %u", page_length)); + + /* Apply modifications to page */ + do + { + switch ((enum en_key_op) (*header++)) { + case KEY_OP_OFFSET: /* 1 */ + page_offset= uint2korr(header); + header+= 2; + DBUG_PRINT("redo", ("key_op_offset: %u", page_offset)); + DBUG_ASSERT(page_offset >= keypage_header && page_offset <= page_length); + break; + case KEY_OP_SHIFT: /* 2 */ + { + int length= sint2korr(header); + header+= 2; + DBUG_PRINT("redo", ("key_op_shift: %d", length)); + DBUG_ASSERT(page_offset != 0 && page_offset <= page_length && + page_length + length < share->block_size); + + if (length < 0) + bmove(buff + page_offset, buff + page_offset - length, + page_length - page_offset + length); + else + bmove_upp(buff + page_length + length, buff + page_length, + page_length - page_offset); + page_length+= length; + break; + } + case KEY_OP_CHANGE: /* 3 */ + { + uint length= uint2korr(header); + DBUG_PRINT("redo", ("key_op_change: %u", length)); + DBUG_ASSERT(page_offset != 0 && page_offset + length <= page_length); + + memcpy(buff + page_offset, header + 2 , length); + header+= 2 + length; + break; + } + case KEY_OP_ADD_PREFIX: /* 4 */ + { + uint insert_length= uint2korr(header); + uint changed_length= uint2korr(header+2); + DBUG_PRINT("redo", ("key_op_add_prefix: %u %u", + insert_length, changed_length)); + + DBUG_ASSERT(insert_length <= changed_length && + page_length + changed_length <= share->block_size); + + bmove_upp(buff + page_length + insert_length, buff + page_length, + page_length - keypage_header); + memcpy(buff + keypage_header, header + 4 , changed_length); + header+= 4 + changed_length; + page_length+= insert_length; + break; + } + case KEY_OP_DEL_PREFIX: /* 5 */ + { + uint length= uint2korr(header); + header+= 2; + DBUG_PRINT("redo", ("key_op_del_prefix: %u", length)); + DBUG_ASSERT(length <= page_length - keypage_header); + + bmove(buff + keypage_header, buff + keypage_header + + length, page_length - keypage_header - length); + page_length-= length; + + page_offset= keypage_header; /* Prepare for change */ + break; + } + case KEY_OP_ADD_SUFFIX: /* 6 */ + { + uint insert_length= uint2korr(header); + DBUG_PRINT("redo", ("key_op_add_prefix: %u", insert_length)); + DBUG_ASSERT(page_length + insert_length <= share->block_size); + memcpy(buff + page_length, header+2, insert_length); + + page_length+= insert_length; + header+= 2 + insert_length; + break; + } + case KEY_OP_DEL_SUFFIX: /* 7 */ + { + uint del_length= uint2korr(header); + header+= 2; + DBUG_PRINT("redo", ("key_op_del_suffix: %u", del_length)); + DBUG_ASSERT(page_length - del_length >= keypage_header); + page_length-= del_length; + break; + } + case KEY_OP_CHECK: /* 8 */ + { +#ifdef EXTRA_DEBUG_KEY_CHANGES + uint check_page_length; + ha_checksum crc; + check_page_length= uint2korr(header); + crc= uint4korr(header+2); + _ma_store_page_used(share, buff, page_length); + DBUG_ASSERT(check_page_length == page_length); + if (crc != (uint32) my_checksum(0, buff + LSN_STORE_SIZE, + page_length - LSN_STORE_SIZE)) + { + DBUG_PRINT("error", ("page_length %u",page_length)); + DBUG_DUMP("KEY_OP_CHECK bad page", buff, share->block_size); + DBUG_ASSERT("crc" == "failure in REDO_INDEX"); + } +#endif + DBUG_PRINT("redo", ("key_op_check")); + header+= 6; + break; + } + case KEY_OP_MULTI_COPY: /* 9 */ + { + /* + List of fixed-len memcpy() operations with their source located inside + the page. The log record's piece looks like: + first the length 'full_length' to be used by memcpy() + then the number of bytes used by the list of (to,from) pairs + then the (to,from) pairs, so we do: + for (t,f) in [list of (to,from) pairs]: + memcpy(t, f, full_length). + */ + uint full_length, log_memcpy_length; + const uchar *log_memcpy_end; + + DBUG_PRINT("redo", ("key_op_multi_copy")); + full_length= uint2korr(header); + header+= 2; + log_memcpy_length= uint2korr(header); + header+= 2; + log_memcpy_end= header + log_memcpy_length; + DBUG_ASSERT(full_length < share->block_size); + while (header < log_memcpy_end) + { + uint to, from; + to= uint2korr(header); + header+= 2; + from= uint2korr(header); + header+= 2; + /* "from" is a place in the existing page */ + DBUG_ASSERT(max(from, to) < share->block_size); + memcpy(buff + to, buff + from, full_length); + } + break; + } + case KEY_OP_SET_PAGEFLAG: + DBUG_PRINT("redo", ("key_op_set_pageflag")); + buff[KEYPAGE_TRANSFLAG_OFFSET]= *header++; + break; + case KEY_OP_COMPACT_PAGE: + { + TrID transid= transid_korr(header); + + DBUG_PRINT("redo", ("key_op_compact_page")); + header+= TRANSID_SIZE; + if (_ma_compact_keypage(&page, transid)) + { + result= 1; + goto err; + } + page_length= page.size; + } + case KEY_OP_NONE: + default: + DBUG_ASSERT(0); + result= 1; + goto err; + } + } while (header < header_end); + DBUG_ASSERT(header == header_end); + + /* Write modified page */ + page.size= page_length; + _ma_store_page_used(share, buff, page_length); + + /* + Clean old stuff up. Gives us better compression of we archive things + and makes things easer to debug + */ + if (page_length < org_page_length) + bzero(buff + page_length, org_page_length-page_length); + + /* Mark page to be unlocked and written at _ma_unpin_all_pages() */ + page_link.unlock= PAGECACHE_LOCK_WRITE_UNLOCK; + page_link.changed= 1; + push_dynamic(&info->pinned_pages, (void*) &page_link); + DBUG_RETURN(0); + +err: + pagecache_unlock_by_link(share->pagecache, page_link.link, + PAGECACHE_LOCK_WRITE_UNLOCK, + PAGECACHE_UNPIN, LSN_IMPOSSIBLE, + LSN_IMPOSSIBLE, 0, FALSE); + if (result) + _ma_mark_file_crashed(share); + DBUG_RETURN(result); +} + + +/**************************************************************************** + Undo of key block changes +****************************************************************************/ + +/** + @brief Undo of insert of key (ie, delete the inserted key) +*/ + +my_bool _ma_apply_undo_key_insert(MARIA_HA *info, LSN undo_lsn, + const uchar *header, uint length) +{ + LSN lsn; + my_bool res; + uint keynr; + uchar key_buff[MARIA_MAX_KEY_BUFF]; + MARIA_SHARE *share= info->s; + MARIA_KEY key; + my_off_t new_root; + struct st_msg_to_write_hook_for_undo_key msg; + DBUG_ENTER("_ma_apply_undo_key_insert"); + + share->state.changed|= (STATE_CHANGED | STATE_NOT_OPTIMIZED_KEYS | + STATE_NOT_SORTED_PAGES | STATE_NOT_ZEROFILLED | + STATE_NOT_MOVABLE); + keynr= key_nr_korr(header); + length-= KEY_NR_STORE_SIZE; + + /* We have to copy key as _ma_ck_real_delete() may change it */ + memcpy(key_buff, header + KEY_NR_STORE_SIZE, length); + DBUG_DUMP("key_buff", key_buff, length); + + new_root= share->state.key_root[keynr]; + /* + Change the key to an internal structure. + It's safe to have SEARCH_USER_KEY_HAS_TRANSID even if there isn't + a transaction id, as ha_key_cmp() will stop comparison when key length + is reached. + For index with transid flag, the ref_length of the key is not correct. + This should however be safe as long as this key is only used for + comparsion against other keys (not for packing or for read-next etc as + in this case we use data_length + ref_length, which is correct. + */ + key.keyinfo= share->keyinfo + keynr; + key.data= key_buff; + key.data_length= length - share->rec_reflength; + key.ref_length= share->rec_reflength; + key.flag= SEARCH_USER_KEY_HAS_TRANSID; + + res= ((share->keyinfo[keynr].key_alg == HA_KEY_ALG_RTREE) ? + maria_rtree_real_delete(info, &key, &new_root) : + _ma_ck_real_delete(info, &key, &new_root)); + if (res) + _ma_mark_file_crashed(share); + msg.root= &share->state.key_root[keynr]; + msg.value= new_root; + msg.keynr= keynr; + + if (_ma_write_clr(info, undo_lsn, *msg.root == msg.value ? + LOGREC_UNDO_KEY_INSERT : LOGREC_UNDO_KEY_INSERT_WITH_ROOT, + 0, 0, &lsn, (void*) &msg)) + res= 1; + + _ma_fast_unlock_key_del(info); + _ma_unpin_all_pages_and_finalize_row(info, lsn); + DBUG_RETURN(res); +} + + +/** + @brief Undo of delete of key (ie, insert the deleted key) + + @param with_root If the UNDO is UNDO_KEY_DELETE_WITH_ROOT +*/ + +my_bool _ma_apply_undo_key_delete(MARIA_HA *info, LSN undo_lsn, + const uchar *header, uint length, + my_bool with_root) +{ + LSN lsn; + my_bool res; + uint keynr, skip_bytes; + uchar key_buff[MARIA_MAX_KEY_BUFF]; + MARIA_SHARE *share= info->s; + my_off_t new_root; + struct st_msg_to_write_hook_for_undo_key msg; + MARIA_KEY key; + DBUG_ENTER("_ma_apply_undo_key_delete"); + + share->state.changed|= (STATE_CHANGED | STATE_NOT_OPTIMIZED_KEYS | + STATE_NOT_SORTED_PAGES | STATE_NOT_ZEROFILLED | + STATE_NOT_MOVABLE); + keynr= key_nr_korr(header); + skip_bytes= KEY_NR_STORE_SIZE + (with_root ? PAGE_STORE_SIZE : 0); + header+= skip_bytes; + length-= skip_bytes; + + /* We have to copy key as _ma_ck_real_write_btree() may change it */ + memcpy(key_buff, header, length); + DBUG_DUMP("key", key_buff, length); + + key.keyinfo= share->keyinfo + keynr; + key.data= key_buff; + key.data_length= length - share->rec_reflength; + key.ref_length= share->rec_reflength; + key.flag= SEARCH_USER_KEY_HAS_TRANSID; + + new_root= share->state.key_root[keynr]; + res= (share->keyinfo[keynr].key_alg == HA_KEY_ALG_RTREE) ? + maria_rtree_insert_level(info, &key, -1, &new_root) : + _ma_ck_real_write_btree(info, &key, &new_root, + share->keyinfo[keynr].write_comp_flag | + key.flag); + if (res) + _ma_mark_file_crashed(share); + + msg.root= &share->state.key_root[keynr]; + msg.value= new_root; + msg.keynr= keynr; + if (_ma_write_clr(info, undo_lsn, + *msg.root == msg.value ? + LOGREC_UNDO_KEY_DELETE : LOGREC_UNDO_KEY_DELETE_WITH_ROOT, + 0, 0, &lsn, + (void*) &msg)) + res= 1; + + _ma_fast_unlock_key_del(info); + _ma_unpin_all_pages_and_finalize_row(info, lsn); + DBUG_RETURN(res); +} + + +/**************************************************************************** + Handle some local variables +****************************************************************************/ + +/** + @brief lock key_del for other threads usage + + @fn _ma_lock_key_del() + @param info Maria handler + @param insert_at_end Set to 1 if we are doing an insert + + @note + To allow higher concurrency in the common case where we do inserts + and we don't have any linked blocks we do the following: + - Mark in info->key_del_used that we are not using key_del + - Return at once (without marking key_del as used) + + This is safe as we in this case don't write key_del_current into + the redo log and during recover we are not updating key_del. + + @retval 1 Use page at end of file + @retval 0 Use page at share->key_del_current +*/ + +my_bool _ma_lock_key_del(MARIA_HA *info, my_bool insert_at_end) +{ + MARIA_SHARE *share= info->s; + + /* + info->key_del_used is 0 initially. + If the caller needs a block (_ma_new()), we look at the free list: + - looks empty? then caller will create a new block at end of file and + remember (through info->key_del_used==2) that it will not change + state.key_del and does not need to wake up waiters as nobody will wait for + it. + - non-empty? then we wait for other users of the state.key_del list to + have finished, then we lock this list (through share->key_del_used==1) + because we need to prevent some other thread to also read state.key_del + and use the same page as ours. We remember through info->key_del_used==1 + that we will have to set state.key_del at unlock time and wake up + waiters. + If the caller wants to free a block (_ma_dispose()), "empty" and + "non-empty" are treated as "non-empty" is treated above. + When we are ready to unlock, we copy share->key_del_current into + state.key_del. Unlocking happens when writing the UNDO log record, that + can make a long lock time. + Why we wrote "*looks* empty": because we are looking at state.key_del + which may be slightly old (share->key_del_current may be more recent and + exact): when we want a new page, we tolerate to treat "there was no free + page 1 millisecond ago" as "there is no free page". It's ok to non-pop + (_ma_new(), page will be found later anyway) but it's not ok to non-push + (_ma_dispose(), page would be lost). + When we leave this function, info->key_del_used is always 1 or 2. + */ + if (info->key_del_used != 1) + { + pthread_mutex_lock(&share->key_del_lock); + if (share->state.key_del == HA_OFFSET_ERROR && insert_at_end) + { + pthread_mutex_unlock(&share->key_del_lock); + info->key_del_used= 2; /* insert-with-append */ + return 1; + } +#ifdef THREAD + while (share->key_del_used) + pthread_cond_wait(&share->key_del_cond, &share->key_del_lock); +#endif + info->key_del_used= 1; + share->key_del_used= 1; + share->key_del_current= share->state.key_del; + pthread_mutex_unlock(&share->key_del_lock); + } + return share->key_del_current == HA_OFFSET_ERROR; +} + + +/** + @brief copy changes to key_del and unlock it + + @notes + In case of many threads using the maria table, we always have a lock + on the translog when comming here. +*/ + +void _ma_unlock_key_del(MARIA_HA *info) +{ + DBUG_ASSERT(info->key_del_used); + if (info->key_del_used == 1) /* Ignore insert-with-append */ + { + MARIA_SHARE *share= info->s; + pthread_mutex_lock(&share->key_del_lock); + share->key_del_used= 0; + share->state.key_del= share->key_del_current; + pthread_mutex_unlock(&share->key_del_lock); + pthread_cond_signal(&share->key_del_cond); + } + info->key_del_used= 0; +} diff --git a/storage/maria/ma_key_recover.h b/storage/maria/ma_key_recover.h new file mode 100644 index 00000000000..4b4198f3008 --- /dev/null +++ b/storage/maria/ma_key_recover.h @@ -0,0 +1,112 @@ +/* Copyright (C) 2007 Michael Widenius + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You 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 */ + +/* + When we have finished the write/update/delete of a row, we have cleanups to + do. For now it is signalling to Checkpoint that all dirtied pages have + their rec_lsn set and page LSN set (_ma_unpin_all_pages() has been called), + and that bitmap pages are correct (_ma_bitmap_release_unused() has been + called). +*/ + +/* Struct for clr_end */ + +struct st_msg_to_write_hook_for_clr_end +{ + LSN previous_undo_lsn; + enum translog_record_type undone_record_type; + ha_checksum checksum_delta; + void *extra_msg; +}; + +struct st_msg_to_write_hook_for_undo_key +{ + my_off_t *root; + my_off_t value; + uint keynr; + ulonglong auto_increment; +}; + + +/* Function definitions for some redo functions */ + +my_bool _ma_write_clr(MARIA_HA *info, LSN undo_lsn, + enum translog_record_type undo_type, + my_bool store_checksum, ha_checksum checksum, + LSN *res_lsn, void *extra_msg); +int _ma_write_undo_key_insert(MARIA_HA *info, const MARIA_KEY *key, + my_off_t *root, my_off_t new_root, + LSN *res_lsn); +my_bool _ma_write_undo_key_delete(MARIA_HA *info, const MARIA_KEY *key, + my_off_t new_root, LSN *res_lsn); +my_bool write_hook_for_clr_end(enum translog_record_type type, + TRN *trn, MARIA_HA *tbl_info, LSN *lsn, + void *hook_arg); +extern my_bool write_hook_for_undo_key(enum translog_record_type type, + TRN *trn, MARIA_HA *tbl_info, + LSN *lsn, void *hook_arg); +extern my_bool write_hook_for_undo_key_insert(enum translog_record_type type, + TRN *trn, MARIA_HA *tbl_info, + LSN *lsn, void *hook_arg); +extern my_bool write_hook_for_undo_key_delete(enum translog_record_type type, + TRN *trn, MARIA_HA *tbl_info, + LSN *lsn, void *hook_arg); +void _ma_unpin_all_pages(MARIA_HA *info, LSN undo_lsn); + +my_bool _ma_log_prefix(MARIA_PAGE *page, uint changed_length, int move_length); +my_bool _ma_log_suffix(MARIA_PAGE *page, uint org_length, + uint new_length); +my_bool _ma_log_add(MARIA_PAGE *page, uint buff_length, uchar *key_pos, + uint changed_length, int move_length, + my_bool handle_overflow); +my_bool _ma_log_delete(MARIA_PAGE *page, const uchar *key_pos, + uint changed_length, uint move_length); +my_bool _ma_log_change(MARIA_PAGE *page, const uchar *key_pos, uint length); +my_bool _ma_log_new(MARIA_PAGE *page, my_bool root_page); + +uint _ma_apply_redo_index_new_page(MARIA_HA *info, LSN lsn, + const uchar *header, uint length); +uint _ma_apply_redo_index_free_page(MARIA_HA *info, LSN lsn, + const uchar *header); +uint _ma_apply_redo_index(MARIA_HA *info, + LSN lsn, const uchar *header, uint length); + +my_bool _ma_apply_undo_key_insert(MARIA_HA *info, LSN undo_lsn, + const uchar *header, uint length); +my_bool _ma_apply_undo_key_delete(MARIA_HA *info, LSN undo_lsn, + const uchar *header, uint length, + my_bool with_root); + +static inline void _ma_finalize_row(MARIA_HA *info) +{ + info->trn->rec_lsn= LSN_IMPOSSIBLE; +} + +/* unpinning is often the last operation before finalizing */ + +static inline void _ma_unpin_all_pages_and_finalize_row(MARIA_HA *info, + LSN undo_lsn) +{ + _ma_unpin_all_pages(info, undo_lsn); + _ma_finalize_row(info); +} + +extern my_bool _ma_lock_key_del(MARIA_HA *info, my_bool insert_at_end); +extern void _ma_unlock_key_del(MARIA_HA *info); +static inline void _ma_fast_unlock_key_del(MARIA_HA *info) +{ + if (info->key_del_used) + _ma_unlock_key_del(info); +} diff --git a/storage/maria/ma_keycache.c b/storage/maria/ma_keycache.c new file mode 100644 index 00000000000..39fc7d421ae --- /dev/null +++ b/storage/maria/ma_keycache.c @@ -0,0 +1,164 @@ +/* Copyright (C) 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 */ + +/* + Key cache assignments +*/ + +#include "maria_def.h" + +/* + Assign pages of the index file for a table to a key cache + + SYNOPSIS + maria_assign_to_pagecache() + info open table + key_map map of indexes to assign to the key cache + pagecache_ptr pointer to the key cache handle + assign_lock Mutex to lock during assignment + + PREREQUESTS + One must have a READ lock or a WRITE lock on the table when calling + the function to ensure that there is no other writers to it. + + The caller must also ensure that one doesn't call this function from + two different threads with the same table. + + NOTES + At present pages for all indexes must be assigned to the same key cache. + In future only pages for indexes specified in the key_map parameter + of the table will be assigned to the specified key cache. + + RETURN VALUE + 0 If a success + # Error code +*/ + +int maria_assign_to_pagecache(MARIA_HA *info, + ulonglong key_map __attribute__((unused)), + PAGECACHE *pagecache) +{ + int error= 0; + MARIA_SHARE* share= info->s; + DBUG_ENTER("maria_assign_to_pagecache"); + DBUG_PRINT("enter", + ("old_pagecache_handle: 0x%lx new_pagecache_handle: 0x%lx", + (long) share->pagecache, (long) pagecache)); + + /* + Skip operation if we didn't change key cache. This can happen if we + call this for all open instances of the same table + */ + if (share->pagecache == pagecache) + DBUG_RETURN(0); + + /* + First flush all blocks for the table in the old key cache. + This is to ensure that the disk is consistent with the data pages + in memory (which may not be the case if the table uses delayed_key_write) + + Note that some other read thread may still fill in the key cache with + new blocks during this call and after, but this doesn't matter as + all threads will start using the new key cache for their next call to + maria library and we know that there will not be any changed blocks + in the old key cache. + */ + + if (flush_pagecache_blocks(share->pagecache, &share->kfile, FLUSH_RELEASE)) + { + error= my_errno; + maria_print_error(info->s, HA_ERR_CRASHED); + maria_mark_crashed(info); /* Mark that table must be checked */ + } + + /* + Flush the new key cache for this file. This is needed to ensure + that there is no old blocks (with outdated data) left in the new key + cache from an earlier assign_to_keycache operation + + (This can never fail as there is never any not written data in the + new key cache) + */ + (void) flush_pagecache_blocks(pagecache, &share->kfile, FLUSH_RELEASE); + + /* + ensure that setting the key cache and changing the multi_pagecache + is done atomicly + */ + pthread_mutex_lock(&share->intern_lock); + /* + Tell all threads to use the new key cache + This should be seen at the lastes for the next call to an maria function. + */ + share->pagecache= pagecache; + + /* store the key cache in the global hash structure for future opens */ + if (multi_pagecache_set((uchar*) share->unique_file_name.str, + share->unique_file_name.length, + share->pagecache)) + error= my_errno; + pthread_mutex_unlock(&share->intern_lock); + DBUG_RETURN(error); +} + + +/* + Change all MARIA entries that uses one key cache to another key cache + + SYNOPSIS + maria_change_pagecache() + old_pagecache Old key cache + new_pagecache New key cache + + NOTES + This is used when we delete one key cache. + + To handle the case where some other threads tries to open an MARIA + table associated with the to-be-deleted key cache while this operation + is running, we have to call 'multi_pagecache_change()' from this + function while we have a lock on the MARIA table list structure. + + This is safe as long as it's only MARIA that is using this specific + key cache. +*/ + + +void maria_change_pagecache(PAGECACHE *old_pagecache, + PAGECACHE *new_pagecache) +{ + LIST *pos; + DBUG_ENTER("maria_change_pagecache"); + + /* + Lock list to ensure that no one can close the table while we manipulate it + */ + pthread_mutex_lock(&THR_LOCK_maria); + for (pos=maria_open_list ; pos ; pos=pos->next) + { + MARIA_HA *info= (MARIA_HA*) pos->data; + MARIA_SHARE *share= info->s; + if (share->pagecache == old_pagecache) + maria_assign_to_pagecache(info, (ulonglong) ~0, new_pagecache); + } + + /* + We have to do the following call while we have the lock on the + MARIA list structure to ensure that another thread is not trying to + open a new table that will be associted with the old key cache + */ + multi_pagecache_change(old_pagecache, new_pagecache); + pthread_mutex_unlock(&THR_LOCK_maria); + DBUG_VOID_RETURN; +} diff --git a/storage/maria/ma_locking.c b/storage/maria/ma_locking.c new file mode 100644 index 00000000000..9d57cd1cba3 --- /dev/null +++ b/storage/maria/ma_locking.c @@ -0,0 +1,553 @@ +/* Copyright (C) 2006 MySQL AB & MySQL Finland AB & TCX DataKonsult 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 */ + +/* + Locking of Maria-tables. + Must be first request before doing any furter calls to any Maria function. + Is used to allow many process use the same non transactional Maria table +*/ + +#include "ma_ftdefs.h" + + /* lock table by F_UNLCK, F_RDLCK or F_WRLCK */ + +int maria_lock_database(MARIA_HA *info, int lock_type) +{ + int error; + uint count; + MARIA_SHARE *share= info->s; + DBUG_ENTER("maria_lock_database"); + DBUG_PRINT("enter",("lock_type: %d old lock %d r_locks: %u w_locks: %u " + "global_changed: %d open_count: %u name: '%s'", + lock_type, info->lock_type, share->r_locks, + share->w_locks, + share->global_changed, share->state.open_count, + share->index_file_name.str)); + if (share->options & HA_OPTION_READ_ONLY_DATA || + info->lock_type == lock_type) + DBUG_RETURN(0); + if (lock_type == F_EXTRA_LCK) /* Used by TMP tables */ + { + ++share->w_locks; + ++share->tot_locks; + info->lock_type= lock_type; + DBUG_RETURN(0); + } + + error=0; + pthread_mutex_lock(&share->intern_lock); + if (share->kfile.file >= 0) /* May only be false on windows */ + { + switch (lock_type) { + case F_UNLCK: + maria_ftparser_call_deinitializer(info); + if (info->lock_type == F_RDLCK) + { + count= --share->r_locks; + if (share->lock_restore_status) + (*share->lock_restore_status)(info); + } + else + { + count= --share->w_locks; + if (share->lock.update_status) + (*share->lock.update_status)(info); + } + --share->tot_locks; + if (info->lock_type == F_WRLCK && !share->w_locks) + { + /* pages of transactional tables get flushed at Checkpoint */ + if (!share->base.born_transactional && !share->temporary && + _ma_flush_table_files(info, + share->delay_key_write ? MARIA_FLUSH_DATA : + MARIA_FLUSH_DATA | MARIA_FLUSH_INDEX, + FLUSH_KEEP, FLUSH_KEEP)) + error= my_errno; + } + if (info->opt_flag & (READ_CACHE_USED | WRITE_CACHE_USED)) + { + if (end_io_cache(&info->rec_cache)) + { + error=my_errno; + maria_print_error(info->s, HA_ERR_CRASHED); + maria_mark_crashed(info); + } + } + if (!count) + { + DBUG_PRINT("info",("changed: %u w_locks: %u", + (uint) share->changed, share->w_locks)); + if (share->changed && !share->w_locks) + { +#ifdef HAVE_MMAP + if ((share->mmaped_length != + share->state.state.data_file_length) && + (share->nonmmaped_inserts > MAX_NONMAPPED_INSERTS)) + { + if (share->lock_key_trees) + rw_wrlock(&share->mmap_lock); + _ma_remap_file(info, share->state.state.data_file_length); + share->nonmmaped_inserts= 0; + if (share->lock_key_trees) + rw_unlock(&share->mmap_lock); + } +#endif +#ifdef EXTERNAL_LOCKING + share->state.process= share->last_process=share->this_process; + share->state.unique= info->last_unique= info->this_unique; + share->state.update_count= info->last_loop= ++info->this_loop; +#endif + /* transactional tables rather flush their state at Checkpoint */ + if (!share->base.born_transactional) + { + if (_ma_state_info_write_sub(share->kfile.file, &share->state, + MA_STATE_INFO_WRITE_DONT_MOVE_OFFSET)) + error= my_errno; + else + { + /* A value of 0 means below means "state flushed" */ + share->changed= 0; + } + } + if (maria_flush) + { + if (_ma_sync_table_files(info)) + error= my_errno; + } + else + share->not_flushed=1; + if (error) + { + maria_print_error(info->s, HA_ERR_CRASHED); + maria_mark_crashed(info); + } + } + } + info->opt_flag&= ~(READ_CACHE_USED | WRITE_CACHE_USED); + info->lock_type= F_UNLCK; + break; + case F_RDLCK: + if (info->lock_type == F_WRLCK) + { + /* + Change RW to READONLY + + mysqld does not turn write locks to read locks, + so we're never here in mysqld. + */ + share->w_locks--; + share->r_locks++; + info->lock_type=lock_type; + break; + } +#ifdef MARIA_EXTERNAL_LOCKING + if (!share->r_locks && !share->w_locks) + { + /* note that a transactional table should not do this */ + if (_ma_state_info_read_dsk(share->kfile.file, &share->state)) + { + error=my_errno; + break; + } + } +#endif + VOID(_ma_test_if_changed(info)); + share->r_locks++; + share->tot_locks++; + info->lock_type=lock_type; + break; + case F_WRLCK: + if (info->lock_type == F_RDLCK) + { /* Change READONLY to RW */ + if (share->r_locks == 1) + { + share->r_locks--; + share->w_locks++; + info->lock_type=lock_type; + break; + } + } +#ifdef MARIA_EXTERNAL_LOCKING + if (!(share->options & HA_OPTION_READ_ONLY_DATA)) + { + if (!share->w_locks) + { + if (!share->r_locks) + { + /* + Note that transactional tables should not do this. + If we enabled this code, we should make sure to skip it if + born_transactional is true. We should not test + now_transactional to decide if we can call + _ma_state_info_read_dsk(), because it can temporarily be 0 + (TRUNCATE on a partitioned table) and thus it would make a state + modification below without mutex, confusing a concurrent + checkpoint running. + Even if this code was enabled only for non-transactional tables: + in scenario LOCK TABLE t1 WRITE; INSERT INTO t1; DELETE FROM t1; + state on disk read by DELETE is obsolete as it was not flushed + at the end of INSERT. MyISAM same. It however causes no issue as + maria_delete_all_rows() calls _ma_reset_status() thus is not + influenced by the obsolete read values. + */ + if (_ma_state_info_read_dsk(share->kfile.file, &share->state)) + { + error=my_errno; + break; + } + } + } + } +#endif /* defined(MARIA_EXTERNAL_LOCKING) */ + VOID(_ma_test_if_changed(info)); + + info->lock_type=lock_type; + info->invalidator=share->invalidator; + share->w_locks++; + share->tot_locks++; + break; + default: + DBUG_ASSERT(0); + break; /* Impossible */ + } + } +#ifdef __WIN__ + else + { + /* + Check for bad file descriptors if this table is part + of a merge union. Failing to capture this may cause + a crash on windows if the table is renamed and + later on referenced by the merge table. + */ + if( info->owned_by_merge && (info->s)->kfile.file < 0 ) + { + error = HA_ERR_NO_SUCH_TABLE; + } + } +#endif + pthread_mutex_unlock(&share->intern_lock); + DBUG_RETURN(error); +} /* maria_lock_database */ + + +/**************************************************************************** + ** functions to read / write the state +****************************************************************************/ + +int _ma_readinfo(register MARIA_HA *info __attribute__ ((unused)), + int lock_type __attribute__ ((unused)), + int check_keybuffer __attribute__ ((unused))) +{ +#ifdef MARIA_EXTERNAL_LOCKING + DBUG_ENTER("_ma_readinfo"); + + if (info->lock_type == F_UNLCK) + { + MARIA_SHARE *share= info->s; + if (!share->tot_locks) + { + /* should not be done for transactional tables */ + if (_ma_state_info_read_dsk(share->kfile.file, &share->state)) + { + if (!my_errno) + my_errno= HA_ERR_FILE_TOO_SHORT; + DBUG_RETURN(1); + } + } + if (check_keybuffer) + VOID(_ma_test_if_changed(info)); + info->invalidator=share->invalidator; + } + else if (lock_type == F_WRLCK && info->lock_type == F_RDLCK) + { + my_errno=EACCES; /* Not allowed to change */ + DBUG_RETURN(-1); /* when have read_lock() */ + } + DBUG_RETURN(0); +#else + return 0; +#endif /* defined(MARIA_EXTERNAL_LOCKING) */ +} /* _ma_readinfo */ + + +/* + Every isam-function that uppdates the isam-database MUST end with this + request + + NOTES + my_errno is not changed if this succeeds! +*/ + +int _ma_writeinfo(register MARIA_HA *info, uint operation) +{ + int error,olderror; + MARIA_SHARE *share= info->s; + DBUG_ENTER("_ma_writeinfo"); + DBUG_PRINT("info",("operation: %u tot_locks: %u", operation, + share->tot_locks)); + + error=0; + if (share->tot_locks == 0 && !share->base.born_transactional) + { + /* transactional tables flush their state at Checkpoint */ + if (operation) + { /* Two threads can't be here */ + olderror= my_errno; /* Remember last error */ + +#ifdef EXTERNAL_LOCKING + /* + The following only makes sense if we want to be allow two different + processes access the same table at the same time + */ + share->state.process= share->last_process= share->this_process; + share->state.unique= info->last_unique= info->this_unique; + share->state.update_count= info->last_loop= ++info->this_loop; +#endif + + if ((error= + _ma_state_info_write_sub(share->kfile.file, + &share->state, + MA_STATE_INFO_WRITE_DONT_MOVE_OFFSET))) + olderror=my_errno; +#ifdef __WIN__ + if (maria_flush) + { + _commit(share->kfile.file); + _commit(info->dfile.file); + } +#endif + my_errno=olderror; + } + } + else if (operation) + share->changed= 1; /* Mark keyfile changed */ + DBUG_RETURN(error); +} /* _ma_writeinfo */ + + +/* + Test if an external process has changed the database + (Should be called after readinfo) +*/ + +int _ma_test_if_changed(register MARIA_HA *info) +{ +#ifdef EXTERNAL_LOCKING + MARIA_SHARE *share= info->s; + if (share->state.process != share->last_process || + share->state.unique != info->last_unique || + share->state.update_count != info->last_loop) + { /* Keyfile has changed */ + DBUG_PRINT("info",("index file changed")); + if (share->state.process != share->this_process) + VOID(flush_pagecache_blocks(share->pagecache, &share->kfile, + FLUSH_RELEASE)); + share->last_process=share->state.process; + info->last_unique= share->state.unique; + info->last_loop= share->state.update_count; + info->update|= HA_STATE_WRITTEN; /* Must use file on next */ + info->data_changed= 1; /* For maria_is_changed */ + return 1; + } +#endif + return (!(info->update & HA_STATE_AKTIV) || + (info->update & (HA_STATE_WRITTEN | HA_STATE_DELETED | + HA_STATE_KEY_CHANGED))); +} /* _ma_test_if_changed */ + + +/* + Put a mark in the .MAI file that someone is updating the table + + DOCUMENTATION + state.open_count in the .MAI file is used the following way: + - For the first change of the .MYI file in this process open_count is + incremented by _ma_mark_file_changed(). (We have a write lock on the file + when this happens) + - In maria_close() it's decremented by _ma_decrement_open_count() if it + was incremented in the same process. + + This mean that if we are the only process using the file, the open_count + tells us if the MARIA file wasn't properly closed. (This is true if + my_disable_locking is set). + + open_count is not maintained on disk for temporary tables. +*/ + +int _ma_mark_file_changed(MARIA_HA *info) +{ + uchar buff[3]; + register MARIA_SHARE *share= info->s; + int error= 1; + DBUG_ENTER("_ma_mark_file_changed"); + +#define _MA_ALREADY_MARKED_FILE_CHANGED \ + ((share->state.changed & STATE_CHANGED) && share->global_changed) + if (_MA_ALREADY_MARKED_FILE_CHANGED) + DBUG_RETURN(0); + pthread_mutex_lock(&share->intern_lock); /* recheck under mutex */ + if (! _MA_ALREADY_MARKED_FILE_CHANGED) + { + share->state.changed|=(STATE_CHANGED | STATE_NOT_ANALYZED | + STATE_NOT_OPTIMIZED_KEYS); + if (!share->global_changed) + { + share->global_changed=1; + share->state.open_count++; + } + /* + Temp tables don't need an open_count as they are removed on crash. + In theory transactional tables are fixed by log-based recovery, so don't + need an open_count either, but if recovery has failed and logs have been + removed (by maria-force-start-after-recovery-failures), we still need to + detect dubious tables. + If we didn't maintain open_count on disk for a table, after a crash + we wouldn't know if it was closed at crash time (thus does not need a + check) or not. So we would have to check all tables: overkill. + */ + if (!share->temporary) + { + mi_int2store(buff,share->state.open_count); + buff[2]=1; /* Mark that it's changed */ + if (my_pwrite(share->kfile.file, buff, sizeof(buff), + sizeof(share->state.header) + + MARIA_FILE_OPEN_COUNT_OFFSET, + MYF(MY_NABP))) + goto err; + } + /* Set uuid of file if not yet set (zerofilled file) */ + if (share->base.born_transactional && + !(share->state.changed & STATE_NOT_MOVABLE)) + { + /* Lock table to current installation */ + if (_ma_set_uuid(info, 0) || + (share->state.create_rename_lsn == LSN_NEEDS_NEW_STATE_LSNS && + _ma_update_state_lsns_sub(share, LSN_IMPOSSIBLE, + trnman_get_min_trid(), + TRUE, TRUE))) + goto err; + share->state.changed|= STATE_NOT_MOVABLE; + } + } + error= 0; +err: + pthread_mutex_unlock(&share->intern_lock); + DBUG_RETURN(error); +#undef _MA_ALREADY_MARKED_FILE_CHANGED +} + +/* + Check that a region is all zero + + SYNOPSIS + check_if_zero() + pos Start of memory to check + length length of memory region + + NOTES + Used mainly to detect rows with wrong extent information +*/ + +my_bool _ma_check_if_zero(uchar *pos, size_t length) +{ + uchar *end; + for (end= pos+ length; pos != end ; pos++) + if (pos[0] != 0) + return 1; + return 0; +} + +/* + This is only called by close or by extra(HA_FLUSH) if the OS has the pwrite() + call. In these context the following code should be safe! + */ + +int _ma_decrement_open_count(MARIA_HA *info) +{ + uchar buff[2]; + register MARIA_SHARE *share= info->s; + int lock_error=0,write_error=0; + if (share->global_changed) + { + uint old_lock=info->lock_type; + share->global_changed=0; + lock_error=maria_lock_database(info,F_WRLCK); + /* Its not fatal even if we couldn't get the lock ! */ + if (share->state.open_count > 0) + { + share->state.open_count--; + share->changed= 1; /* We have to update state */ + if (!share->temporary) + { + mi_int2store(buff,share->state.open_count); + write_error= (int) my_pwrite(share->kfile.file, buff, sizeof(buff), + sizeof(share->state.header) + + MARIA_FILE_OPEN_COUNT_OFFSET, + MYF(MY_NABP)); + } + } + if (!lock_error) + lock_error=maria_lock_database(info,old_lock); + } + return test(lock_error || write_error); +} + + +/** @brief mark file as crashed */ + +void _ma_mark_file_crashed(MARIA_SHARE *share) +{ + uchar buff[2]; + DBUG_ENTER("_ma_mark_file_crashed"); + + share->state.changed|= STATE_CRASHED; + mi_int2store(buff, share->state.changed); + /* + We can ignore the errors, as if the mark failed, there isn't anything + else we can do; The user should already have got an error that the + table was crashed. + */ + (void) my_pwrite(share->kfile.file, buff, sizeof(buff), + sizeof(share->state.header) + + MARIA_FILE_CHANGED_OFFSET, + MYF(MY_NABP)); + DBUG_VOID_RETURN; +} + + +/** + @brief Set uuid of for a Maria file + + @fn _ma_set_uuid() + @param info Maria handler + @param reset_uuid Instead of setting file to maria_uuid, set it to + 0 to mark it as movable +*/ + +my_bool _ma_set_uuid(MARIA_HA *info, my_bool reset_uuid) +{ + uchar buff[MY_UUID_SIZE], *uuid; + + uuid= maria_uuid; + if (reset_uuid) + { + bzero(buff, sizeof(buff)); + uuid= buff; + } + return (my_bool) my_pwrite(info->s->kfile.file, uuid, MY_UUID_SIZE, + mi_uint2korr(info->s->state.header.base_pos), + MYF(MY_NABP)); +} diff --git a/storage/maria/ma_loghandler.c b/storage/maria/ma_loghandler.c new file mode 100644 index 00000000000..c5efab506f1 --- /dev/null +++ b/storage/maria/ma_loghandler.c @@ -0,0 +1,8810 @@ +/* Copyright (C) 2007 MySQL AB & Sanja Belkin + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You 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 "maria_def.h" +#include "trnman.h" +#include "ma_blockrec.h" /* for some constants and in-write hooks */ +#include "ma_key_recover.h" /* For some in-write hooks */ +#include "ma_checkpoint.h" + +/* + On Windows, neither my_open() nor my_sync() work for directories. + Also there is no need to flush filesystem changes ,i.e to sync() + directories. +*/ +#ifdef __WIN__ +#define sync_dir(A,B) 0 +#else +#define sync_dir(A,B) my_sync(A,B) +#endif + +/** + @file + @brief Module which writes and reads to a transaction log +*/ + +/* 0xFF can never be valid first byte of a chunk */ +#define TRANSLOG_FILLER 0xFF + +/* number of opened log files in the pagecache (should be at least 2) */ +#define OPENED_FILES_NUM 3 +#define CACHED_FILES_NUM 5 +#define CACHED_FILES_NUM_DIRECT_SEARCH_LIMIT 7 +#if CACHED_FILES_NUM > CACHED_FILES_NUM_DIRECT_SEARCH_LIMIT +#include <hash.h> +#include <m_ctype.h> +#endif + +/* transaction log file descriptor */ +typedef struct st_translog_file +{ + uint32 number; + PAGECACHE_FILE handler; + my_bool was_recovered; + my_bool is_sync; +} TRANSLOG_FILE; + +/* records buffer size (should be TRANSLOG_PAGE_SIZE * n) */ +#define TRANSLOG_WRITE_BUFFER (1024*1024) +/* + pagecache_read/write/inject() use bmove512() on their buffers so those must + be long-aligned, which we guarantee by using the type below: +*/ +typedef union +{ + ulonglong dummy; + uchar buffer[TRANSLOG_PAGE_SIZE]; +} TRANSLOG_PAGE_SIZE_BUFF; + +/* min chunk length */ +#define TRANSLOG_MIN_CHUNK 3 +/* + Number of buffers used by loghandler + + Should be at least 4, because one thread can block up to 2 buffers in + normal circumstances (less then half of one and full other, or just + switched one and other), But if we met end of the file in the middle and + have to switch buffer it will be 3. + 1 buffer for flushing/writing. + We have a bigger number here for higher concurrency and to make division + faster. + + The number should be power of 2 to be fast. +*/ +#define TRANSLOG_BUFFERS_NO 8 +/* number of bytes (+ header) which can be unused on first page in sequence */ +#define TRANSLOG_MINCHUNK_CONTENT 1 +/* version of log file */ +#define TRANSLOG_VERSION_ID 10000 /* 1.00.00 */ + +#define TRANSLOG_PAGE_FLAGS 6 /* transaction log page flags offset */ + +/* Maximum length of compressed LSNs (the worst case of whole LSN storing) */ +#define COMPRESSED_LSN_MAX_STORE_SIZE (2 + LSN_STORE_SIZE) +#define MAX_NUMBER_OF_LSNS_PER_RECORD 2 + + +/* max lsn calculation for buffer */ +#define BUFFER_MAX_LSN(B) \ + ((B)->last_lsn == LSN_IMPOSSIBLE ? (B)->prev_last_lsn : (B)->last_lsn) + +/* log write buffer descriptor */ +struct st_translog_buffer +{ + /* + Cache for current log. Comes first to be aligned for bmove512() in + pagecache_inject() + */ + uchar buffer[TRANSLOG_WRITE_BUFFER]; + /* + Maximum LSN of records which ends in this buffer (or IMPOSSIBLE_LSN + if no LSNs ends here) + */ + LSN last_lsn; + /* last_lsn of previous buffer or IMPOSSIBLE_LSN if it is very first one */ + LSN prev_last_lsn; + /* This buffer offset in the file */ + TRANSLOG_ADDRESS offset; + /* + Next buffer offset in the file (it is not always offset + size, + in case of flush by LSN it can be offset + size - TRANSLOG_PAGE_SIZE) + */ + TRANSLOG_ADDRESS next_buffer_offset; + /* Previous buffer offset to detect it flush finish */ + TRANSLOG_ADDRESS prev_buffer_offset; + /* + How much is written (or will be written when copy_to_buffer_in_progress + become 0) to this buffer + */ + translog_size_t size; + /* File handler for this buffer */ + TRANSLOG_FILE *file; + /* Threads which are waiting for buffer filling/freeing */ + pthread_cond_t waiting_filling_buffer; + /* Number of records which are in copy progress */ + uint copy_to_buffer_in_progress; + /* list of waiting buffer ready threads */ + struct st_my_thread_var *waiting_flush; + /* + If true then previous buffer overlap with this one (due to flush of + loghandler, the last page of that buffer is the same as the first page + of this buffer) and have to be written first (because contain old + content of page which present in both buffers) + */ + my_bool overlay; + uint buffer_no; + /* + Lock for the buffer. + + Current buffer also lock the whole handler (if one want lock the handler + one should lock the current buffer). + + Buffers are locked only in one direction (with overflow and beginning + from the first buffer). If we keep lock on buffer N we can lock only + buffer N+1 (never N-1). + + One thread do not lock more then 2 buffer in a time, so to make dead + lock it should be N thread (where N equal number of buffers) takes one + buffer and try to lock next. But it is impossible because there is only + 2 cases when thread take 2 buffers: 1) one thread finishes current + buffer (where horizon is) and start next (to which horizon moves). 2) + flush start from buffer after current (oldest) and go till the current + crabbing by buffer sequence. And there is only one flush in a moment + (they are serialised). + + Because of above and number of buffers equal 5 we can't get dead lock (it is + impossible to get all 5 buffers locked simultaneously). + */ + pthread_mutex_t mutex; + /* + Some thread is going to close the buffer and it should be + done only by that thread + */ + my_bool is_closing_buffer; + /* + Version of the buffer increases every time buffer the buffer flushed. + With file and offset it allow detect buffer changes + */ + uint8 ver; + + /* + When previous buffer sent to disk it set its address here to allow + to detect when it is done + (we have to keep it in this buffer to lock buffers only in one direction). + */ + TRANSLOG_ADDRESS prev_sent_to_disk; + pthread_cond_t prev_sent_to_disk_cond; +}; + + +struct st_buffer_cursor +{ + /* pointer into the buffer */ + uchar *ptr; + /* current buffer */ + struct st_translog_buffer *buffer; + /* How many bytes we wrote on the current page */ + uint16 current_page_fill; + /* + How many times we write the page on the disk during flushing process + (for sector protection). + */ + uint16 write_counter; + /* previous write offset */ + uint16 previous_offset; + /* Number of current buffer */ + uint8 buffer_no; + /* + True if it is just filling buffer after advancing the pointer to + the horizon. + */ + my_bool chaser; + /* + Is current page of the cursor already finished (sector protection + should be applied if it is needed) + */ + my_bool protected; +}; + + +typedef uint8 dirty_buffer_mask_t; + +struct st_translog_descriptor +{ + /* *** Parameters of the log handler *** */ + + /* Page cache for the log reads */ + PAGECACHE *pagecache; + uint flags; + /* File open flags */ + uint open_flags; + /* max size of one log size (for new logs creation) */ + uint32 log_file_max_size; + uint32 server_version; + /* server ID (used for replication) */ + uint32 server_id; + /* Loghandler's buffer capacity in case of chunk 2 filling */ + uint32 buffer_capacity_chunk_2; + /* + Half of the buffer capacity in case of chunk 2 filling, + used to decide will we write a record in one group or many. + It is written to the variable just to avoid devision every + time we need it. + */ + uint32 half_buffer_capacity_chunk_2; + /* Page overhead calculated by flags (whether CRC is enabled, etc) */ + uint16 page_overhead; + /* + Page capacity ("useful load") calculated by flags + (TRANSLOG_PAGE_SIZE - page_overhead-1) + */ + uint16 page_capacity_chunk_2; + /* Path to the directory where we store log store files */ + char directory[FN_REFLEN]; + + /* *** Current state of the log handler *** */ + /* list of opened files */ + DYNAMIC_ARRAY open_files; + /* min/max number of file in the array */ + uint32 max_file, min_file; + /* the opened files list guard */ + rw_lock_t open_files_lock; + + /* + File descriptor of the directory where we store log files for syncing + it. + */ + File directory_fd; + /* buffers for log writing */ + struct st_translog_buffer buffers[TRANSLOG_BUFFERS_NO]; + /* Mask where 1 in position N mean that buffer N is not flushed */ + dirty_buffer_mask_t dirty_buffer_mask; + /* The above variable protection */ + pthread_mutex_t dirty_buffer_mask_lock; + /* + horizon - visible end of the log (here is absolute end of the log: + position where next chunk can start + */ + TRANSLOG_ADDRESS horizon; + /* horizon buffer cursor */ + struct st_buffer_cursor bc; + /* maximum LSN of the current (not finished) file */ + LSN max_lsn; + + /* + Last flushed LSN (protected by log_flush_lock). + Pointers in the log ordered like this: + last_lsn_checked <= flushed <= sent_to_disk <= in_buffers_only <= + max_lsn <= horizon + */ + LSN flushed; + /* Last LSN sent to the disk (but maybe not written yet) */ + LSN sent_to_disk; + /* Horizon from which log started after initialization */ + TRANSLOG_ADDRESS log_start; + TRANSLOG_ADDRESS previous_flush_horizon; + /* All what is after this address is not sent to disk yet */ + TRANSLOG_ADDRESS in_buffers_only; + /* protection of sent_to_disk and in_buffers_only */ + pthread_mutex_t sent_to_disk_lock; + /* + Protect flushed (see above) and for flush serialization (will + be removed in v1.5 + */ + pthread_mutex_t log_flush_lock; + pthread_cond_t log_flush_cond; + + /* Protects changing of headers of finished files (max_lsn) */ + pthread_mutex_t file_header_lock; + + /* + Sorted array (with protection) of files where we started writing process + and so we can't give last LSN yet + */ + pthread_mutex_t unfinished_files_lock; + DYNAMIC_ARRAY unfinished_files; + + /* + minimum number of still need file calculeted during last + translog_purge call + */ + uint32 min_need_file; + /* Purger data: minimum file in the log (or 0 if unknown) */ + uint32 min_file_number; + /* Protect purger from many calls and it's data */ + pthread_mutex_t purger_lock; + /* last low water mark checked */ + LSN last_lsn_checked; + /** + Must be set to 0 under loghandler lock every time a new LSN + is generated. + */ + my_bool is_everything_flushed; + /* True when flush pass is in progress */ + my_bool flush_in_progress; + /* The flush number (used to distinguish two flushes goes one by one) */ + volatile int flush_no; + /* Next flush pass variables */ + TRANSLOG_ADDRESS next_pass_max_lsn; + pthread_t max_lsn_requester; +}; + +static struct st_translog_descriptor log_descriptor; + +ulong log_purge_type= TRANSLOG_PURGE_IMMIDIATE; +ulong log_file_size= TRANSLOG_FILE_SIZE; +ulong sync_log_dir= TRANSLOG_SYNC_DIR_NEWFILE; + +/* Marker for end of log */ +static uchar end_of_log= 0; +#define END_OF_LOG &end_of_log + +enum enum_translog_status translog_status= TRANSLOG_UNINITED; + +/* chunk types */ +#define TRANSLOG_CHUNK_LSN 0x00 /* 0 chunk refer as LSN (head or tail */ +#define TRANSLOG_CHUNK_FIXED (1 << 6) /* 1 (pseudo)fixed record (also LSN) */ +#define TRANSLOG_CHUNK_NOHDR (2 << 6) /* 2 no head chunk (till page end) */ +#define TRANSLOG_CHUNK_LNGTH (3 << 6) /* 3 chunk with chunk length */ +#define TRANSLOG_CHUNK_TYPE (3 << 6) /* Mask to get chunk type */ +#define TRANSLOG_REC_TYPE 0x3F /* Mask to get record type */ +#define TRANSLOG_CHUNK_0_CONT 0x3F /* the type to mark chunk 0 continue */ + +/* compressed (relative) LSN constants */ +#define TRANSLOG_CLSN_LEN_BITS 0xC0 /* Mask to get compressed LSN length */ + + +#include <my_atomic.h> +/* an array that maps id of a MARIA_SHARE to this MARIA_SHARE */ +static MARIA_SHARE **id_to_share= NULL; +/* lock for id_to_share */ +static my_atomic_rwlock_t LOCK_id_to_share; + +static my_bool translog_dummy_callback(uchar *page, + pgcache_page_no_t page_no, + uchar* data_ptr); +static my_bool translog_page_validator(uchar *page, + pgcache_page_no_t page_no, + uchar* data_ptr); + +static my_bool translog_get_next_chunk(TRANSLOG_SCANNER_DATA *scanner); +static uint32 translog_first_file(TRANSLOG_ADDRESS horizon, int is_protected); +LSN translog_next_LSN(TRANSLOG_ADDRESS addr, TRANSLOG_ADDRESS horizon); + + +/* + Initialize log_record_type_descriptors +*/ + +LOG_DESC log_record_type_descriptor[LOGREC_NUMBER_OF_TYPES]; + + +#ifndef DBUG_OFF + +#define translog_buffer_lock_assert_owner(B) \ + safe_mutex_assert_owner(&(B)->mutex) +#define translog_lock_assert_owner() \ + safe_mutex_assert_owner(&log_descriptor.bc.buffer->mutex) +void translog_lock_handler_assert_owner() +{ + translog_lock_assert_owner(); +} + +/** + @brief check the description table validity + + @param num how many records should be filled +*/ + +static void check_translog_description_table(int num) +{ + int i; + DBUG_ENTER("check_translog_description_table"); + DBUG_PRINT("enter", ("last record: %d", num)); + DBUG_ASSERT(num > 0); + /* last is reserved for extending the table */ + DBUG_ASSERT(num < LOGREC_NUMBER_OF_TYPES - 1); + DBUG_ASSERT(log_record_type_descriptor[0].rclass == LOGRECTYPE_NOT_ALLOWED); + + for (i= 0; i <= num; i++) + { + DBUG_PRINT("info", + ("record type: %d class: %d fixed: %u header: %u LSNs: %u " + "name: %s", + i, log_record_type_descriptor[i].rclass, + (uint)log_record_type_descriptor[i].fixed_length, + (uint)log_record_type_descriptor[i].read_header_len, + (uint)log_record_type_descriptor[i].compressed_LSN, + log_record_type_descriptor[i].name)); + switch (log_record_type_descriptor[i].rclass) { + case LOGRECTYPE_NOT_ALLOWED: + DBUG_ASSERT(i == 0); + break; + case LOGRECTYPE_VARIABLE_LENGTH: + DBUG_ASSERT(log_record_type_descriptor[i].fixed_length == 0); + DBUG_ASSERT((log_record_type_descriptor[i].compressed_LSN == 0) || + ((log_record_type_descriptor[i].compressed_LSN == 1) && + (log_record_type_descriptor[i].read_header_len >= + LSN_STORE_SIZE)) || + ((log_record_type_descriptor[i].compressed_LSN == 2) && + (log_record_type_descriptor[i].read_header_len >= + LSN_STORE_SIZE * 2))); + break; + case LOGRECTYPE_PSEUDOFIXEDLENGTH: + DBUG_ASSERT(log_record_type_descriptor[i].fixed_length == + log_record_type_descriptor[i].read_header_len); + DBUG_ASSERT(log_record_type_descriptor[i].compressed_LSN > 0); + DBUG_ASSERT(log_record_type_descriptor[i].compressed_LSN <= 2); + break; + case LOGRECTYPE_FIXEDLENGTH: + DBUG_ASSERT(log_record_type_descriptor[i].fixed_length == + log_record_type_descriptor[i].read_header_len); + DBUG_ASSERT(log_record_type_descriptor[i].compressed_LSN == 0); + break; + default: + DBUG_ASSERT(0); + } + } + for (i= num + 1; i < LOGREC_NUMBER_OF_TYPES; i++) + { + DBUG_ASSERT(log_record_type_descriptor[i].rclass == + LOGRECTYPE_NOT_ALLOWED); + } + DBUG_VOID_RETURN; +} +#else +#define translog_buffer_lock_assert_owner(B) {} +#define translog_lock_assert_owner() {} +#endif + +static LOG_DESC INIT_LOGREC_RESERVED_FOR_CHUNKS23= +{LOGRECTYPE_NOT_ALLOWED, 0, 0, NULL, NULL, NULL, 0, + "reserved", LOGREC_NOT_LAST_IN_GROUP, NULL, NULL }; + +static LOG_DESC INIT_LOGREC_REDO_INSERT_ROW_HEAD= +{LOGRECTYPE_VARIABLE_LENGTH, 0, + FILEID_STORE_SIZE + PAGE_STORE_SIZE + DIRPOS_STORE_SIZE, NULL, + write_hook_for_redo, NULL, 0, + "redo_insert_row_head", LOGREC_NOT_LAST_IN_GROUP, NULL, NULL}; + +static LOG_DESC INIT_LOGREC_REDO_INSERT_ROW_TAIL= +{LOGRECTYPE_VARIABLE_LENGTH, 0, + FILEID_STORE_SIZE + PAGE_STORE_SIZE + DIRPOS_STORE_SIZE, NULL, + write_hook_for_redo, NULL, 0, + "redo_insert_row_tail", LOGREC_NOT_LAST_IN_GROUP, NULL, NULL}; + +static LOG_DESC INIT_LOGREC_REDO_NEW_ROW_HEAD= +{LOGRECTYPE_VARIABLE_LENGTH, 0, + FILEID_STORE_SIZE + PAGE_STORE_SIZE + DIRPOS_STORE_SIZE, NULL, + write_hook_for_redo, NULL, 0, + "redo_new_row_head", LOGREC_NOT_LAST_IN_GROUP, NULL, NULL}; + +static LOG_DESC INIT_LOGREC_REDO_NEW_ROW_TAIL= +{LOGRECTYPE_VARIABLE_LENGTH, 0, + FILEID_STORE_SIZE + PAGE_STORE_SIZE + DIRPOS_STORE_SIZE, NULL, + write_hook_for_redo, NULL, 0, + "redo_new_row_tail", LOGREC_NOT_LAST_IN_GROUP, NULL, NULL}; + +static LOG_DESC INIT_LOGREC_REDO_INSERT_ROW_BLOBS= +{LOGRECTYPE_VARIABLE_LENGTH, 0, FILEID_STORE_SIZE, NULL, + write_hook_for_redo, NULL, 0, + "redo_insert_row_blobs", LOGREC_NOT_LAST_IN_GROUP, NULL, NULL}; + +static LOG_DESC INIT_LOGREC_REDO_PURGE_ROW_HEAD= +{LOGRECTYPE_FIXEDLENGTH, + FILEID_STORE_SIZE + PAGE_STORE_SIZE + DIRPOS_STORE_SIZE, + FILEID_STORE_SIZE + PAGE_STORE_SIZE + DIRPOS_STORE_SIZE, + NULL, write_hook_for_redo, NULL, 0, + "redo_purge_row_head", LOGREC_NOT_LAST_IN_GROUP, NULL, NULL}; + +static LOG_DESC INIT_LOGREC_REDO_PURGE_ROW_TAIL= +{LOGRECTYPE_FIXEDLENGTH, + FILEID_STORE_SIZE + PAGE_STORE_SIZE + DIRPOS_STORE_SIZE, + FILEID_STORE_SIZE + PAGE_STORE_SIZE + DIRPOS_STORE_SIZE, + NULL, write_hook_for_redo, NULL, 0, + "redo_purge_row_tail", LOGREC_NOT_LAST_IN_GROUP, NULL, NULL}; + +static LOG_DESC INIT_LOGREC_REDO_FREE_BLOCKS= +{LOGRECTYPE_VARIABLE_LENGTH, 0, + FILEID_STORE_SIZE + PAGERANGE_STORE_SIZE, + NULL, write_hook_for_redo, NULL, 0, + "redo_free_blocks", LOGREC_NOT_LAST_IN_GROUP, NULL, NULL}; + +static LOG_DESC INIT_LOGREC_REDO_FREE_HEAD_OR_TAIL= +{LOGRECTYPE_FIXEDLENGTH, + FILEID_STORE_SIZE + PAGE_STORE_SIZE, + FILEID_STORE_SIZE + PAGE_STORE_SIZE, + NULL, write_hook_for_redo, NULL, 0, + "redo_free_head_or_tail", LOGREC_NOT_LAST_IN_GROUP, NULL, NULL}; + +/* not yet used; for when we have versioning */ +static LOG_DESC INIT_LOGREC_REDO_DELETE_ROW= +{LOGRECTYPE_FIXEDLENGTH, 16, 16, NULL, write_hook_for_redo, NULL, 0, + "redo_delete_row", LOGREC_NOT_LAST_IN_GROUP, NULL, NULL}; + +/** @todo RECOVERY BUG unused, remove? */ +static LOG_DESC INIT_LOGREC_REDO_UPDATE_ROW_HEAD= +{LOGRECTYPE_VARIABLE_LENGTH, 0, 9, NULL, write_hook_for_redo, NULL, 0, + "redo_update_row_head", LOGREC_NOT_LAST_IN_GROUP, NULL, NULL}; + +static LOG_DESC INIT_LOGREC_REDO_INDEX= +{LOGRECTYPE_VARIABLE_LENGTH, 0, 9, NULL, write_hook_for_redo, NULL, 0, + "redo_index", LOGREC_NOT_LAST_IN_GROUP, NULL, NULL}; + +static LOG_DESC INIT_LOGREC_REDO_INDEX_NEW_PAGE= +{LOGRECTYPE_VARIABLE_LENGTH, 0, + FILEID_STORE_SIZE + PAGE_STORE_SIZE * 2 + KEY_NR_STORE_SIZE + 1, + NULL, write_hook_for_redo, NULL, 0, + "redo_index_new_page", LOGREC_NOT_LAST_IN_GROUP, NULL, NULL}; + +static LOG_DESC INIT_LOGREC_REDO_INDEX_FREE_PAGE= +{LOGRECTYPE_FIXEDLENGTH, FILEID_STORE_SIZE + PAGE_STORE_SIZE * 2, + FILEID_STORE_SIZE + PAGE_STORE_SIZE * 2, + NULL, write_hook_for_redo, NULL, 0, + "redo_index_free_page", LOGREC_NOT_LAST_IN_GROUP, NULL, NULL}; + +static LOG_DESC INIT_LOGREC_REDO_UNDELETE_ROW= +{LOGRECTYPE_FIXEDLENGTH, 16, 16, NULL, write_hook_for_redo, NULL, 0, + "redo_undelete_row", LOGREC_NOT_LAST_IN_GROUP, NULL, NULL}; + +static LOG_DESC INIT_LOGREC_CLR_END= +{LOGRECTYPE_VARIABLE_LENGTH, 0, LSN_STORE_SIZE + FILEID_STORE_SIZE + + CLR_TYPE_STORE_SIZE, NULL, write_hook_for_clr_end, NULL, 1, + "clr_end", LOGREC_LAST_IN_GROUP, NULL, NULL}; + +static LOG_DESC INIT_LOGREC_PURGE_END= +{LOGRECTYPE_PSEUDOFIXEDLENGTH, 5, 5, NULL, NULL, NULL, 1, + "purge_end", LOGREC_LAST_IN_GROUP, NULL, NULL}; + +static LOG_DESC INIT_LOGREC_UNDO_ROW_INSERT= +{LOGRECTYPE_VARIABLE_LENGTH, 0, + LSN_STORE_SIZE + FILEID_STORE_SIZE + PAGE_STORE_SIZE + DIRPOS_STORE_SIZE, + NULL, write_hook_for_undo_row_insert, NULL, 1, + "undo_row_insert", LOGREC_LAST_IN_GROUP, NULL, NULL}; + +static LOG_DESC INIT_LOGREC_UNDO_ROW_DELETE= +{LOGRECTYPE_VARIABLE_LENGTH, 0, + LSN_STORE_SIZE + FILEID_STORE_SIZE + PAGE_STORE_SIZE + DIRPOS_STORE_SIZE, + NULL, write_hook_for_undo_row_delete, NULL, 1, + "undo_row_delete", LOGREC_LAST_IN_GROUP, NULL, NULL}; + +static LOG_DESC INIT_LOGREC_UNDO_ROW_UPDATE= +{LOGRECTYPE_VARIABLE_LENGTH, 0, + LSN_STORE_SIZE + FILEID_STORE_SIZE + PAGE_STORE_SIZE + DIRPOS_STORE_SIZE, + NULL, write_hook_for_undo_row_update, NULL, 1, + "undo_row_update", LOGREC_LAST_IN_GROUP, NULL, NULL}; + +static LOG_DESC INIT_LOGREC_UNDO_KEY_INSERT= +{LOGRECTYPE_VARIABLE_LENGTH, 0, + LSN_STORE_SIZE + FILEID_STORE_SIZE + KEY_NR_STORE_SIZE, + NULL, write_hook_for_undo_key_insert, NULL, 1, + "undo_key_insert", LOGREC_LAST_IN_GROUP, NULL, NULL}; + +/* This will never be in the log, only in the clr */ +static LOG_DESC INIT_LOGREC_UNDO_KEY_INSERT_WITH_ROOT= +{LOGRECTYPE_VARIABLE_LENGTH, 0, + LSN_STORE_SIZE + FILEID_STORE_SIZE + KEY_NR_STORE_SIZE + PAGE_STORE_SIZE, + NULL, write_hook_for_undo_key, NULL, 1, + "undo_key_insert_with_root", LOGREC_LAST_IN_GROUP, NULL, NULL}; + +static LOG_DESC INIT_LOGREC_UNDO_KEY_DELETE= +{LOGRECTYPE_VARIABLE_LENGTH, 0, + LSN_STORE_SIZE + FILEID_STORE_SIZE + KEY_NR_STORE_SIZE, + NULL, write_hook_for_undo_key_delete, NULL, 1, + "undo_key_delete", LOGREC_LAST_IN_GROUP, NULL, NULL}; + +static LOG_DESC INIT_LOGREC_UNDO_KEY_DELETE_WITH_ROOT= +{LOGRECTYPE_VARIABLE_LENGTH, 0, + LSN_STORE_SIZE + FILEID_STORE_SIZE + KEY_NR_STORE_SIZE + PAGE_STORE_SIZE, + NULL, write_hook_for_undo_key_delete, NULL, 1, + "undo_key_delete_with_root", LOGREC_LAST_IN_GROUP, NULL, NULL}; + +static LOG_DESC INIT_LOGREC_PREPARE= +{LOGRECTYPE_VARIABLE_LENGTH, 0, 0, NULL, NULL, NULL, 0, + "prepare", LOGREC_IS_GROUP_ITSELF, NULL, NULL}; + +static LOG_DESC INIT_LOGREC_PREPARE_WITH_UNDO_PURGE= +{LOGRECTYPE_VARIABLE_LENGTH, 0, LSN_STORE_SIZE, NULL, NULL, NULL, 1, + "prepare_with_undo_purge", LOGREC_IS_GROUP_ITSELF, NULL, NULL}; + +static LOG_DESC INIT_LOGREC_COMMIT= +{LOGRECTYPE_FIXEDLENGTH, 0, 0, NULL, + write_hook_for_commit, NULL, 0, "commit", LOGREC_IS_GROUP_ITSELF, NULL, + NULL}; + +static LOG_DESC INIT_LOGREC_COMMIT_WITH_UNDO_PURGE= +{LOGRECTYPE_PSEUDOFIXEDLENGTH, 5, 5, NULL, write_hook_for_commit, NULL, 1, + "commit_with_undo_purge", LOGREC_IS_GROUP_ITSELF, NULL, NULL}; + +static LOG_DESC INIT_LOGREC_CHECKPOINT= +{LOGRECTYPE_VARIABLE_LENGTH, 0, 0, NULL, NULL, NULL, 0, + "checkpoint", LOGREC_IS_GROUP_ITSELF, NULL, NULL}; + +static LOG_DESC INIT_LOGREC_REDO_CREATE_TABLE= +{LOGRECTYPE_VARIABLE_LENGTH, 0, 1 + 2, NULL, NULL, NULL, 0, +"redo_create_table", LOGREC_IS_GROUP_ITSELF, NULL, NULL}; + +static LOG_DESC INIT_LOGREC_REDO_RENAME_TABLE= +{LOGRECTYPE_VARIABLE_LENGTH, 0, 0, NULL, NULL, NULL, 0, + "redo_rename_table", LOGREC_IS_GROUP_ITSELF, NULL, NULL}; + +static LOG_DESC INIT_LOGREC_REDO_DROP_TABLE= +{LOGRECTYPE_VARIABLE_LENGTH, 0, 0, NULL, NULL, NULL, 0, + "redo_drop_table", LOGREC_IS_GROUP_ITSELF, NULL, NULL}; + +static LOG_DESC INIT_LOGREC_REDO_DELETE_ALL= +{LOGRECTYPE_FIXEDLENGTH, FILEID_STORE_SIZE, FILEID_STORE_SIZE, + NULL, write_hook_for_redo_delete_all, NULL, 0, + "redo_delete_all", LOGREC_IS_GROUP_ITSELF, NULL, NULL}; + +static LOG_DESC INIT_LOGREC_REDO_REPAIR_TABLE= +{LOGRECTYPE_FIXEDLENGTH, FILEID_STORE_SIZE + 8 + 8, FILEID_STORE_SIZE + 8 + 8, + NULL, NULL, NULL, 0, + "redo_repair_table", LOGREC_IS_GROUP_ITSELF, NULL, NULL}; + +static LOG_DESC INIT_LOGREC_FILE_ID= +{LOGRECTYPE_VARIABLE_LENGTH, 0, 2, NULL, write_hook_for_file_id, NULL, 0, + "file_id", LOGREC_IS_GROUP_ITSELF, NULL, NULL}; + +static LOG_DESC INIT_LOGREC_LONG_TRANSACTION_ID= +{LOGRECTYPE_FIXEDLENGTH, 6, 6, NULL, NULL, NULL, 0, + "long_transaction_id", LOGREC_IS_GROUP_ITSELF, NULL, NULL}; + +static LOG_DESC INIT_LOGREC_INCOMPLETE_LOG= +{LOGRECTYPE_FIXEDLENGTH, FILEID_STORE_SIZE, FILEID_STORE_SIZE, + NULL, NULL, NULL, 0, + "incomplete_log", LOGREC_IS_GROUP_ITSELF, NULL, NULL}; + +static LOG_DESC INIT_LOGREC_INCOMPLETE_GROUP= +{LOGRECTYPE_FIXEDLENGTH, 0, 0, + NULL, NULL, NULL, 0, + "incomplete_group", LOGREC_IS_GROUP_ITSELF, NULL, NULL}; + +static LOG_DESC INIT_LOGREC_UNDO_BULK_INSERT= +{LOGRECTYPE_VARIABLE_LENGTH, 0, + LSN_STORE_SIZE + FILEID_STORE_SIZE, + NULL, write_hook_for_undo_bulk_insert, NULL, 1, + "undo_bulk_insert", LOGREC_LAST_IN_GROUP, NULL, NULL}; + +static LOG_DESC INIT_LOGREC_REDO_BITMAP_NEW_PAGE= +{LOGRECTYPE_FIXEDLENGTH, FILEID_STORE_SIZE + PAGE_STORE_SIZE * 2, + FILEID_STORE_SIZE + PAGE_STORE_SIZE * 2, + NULL, NULL, NULL, 0, + "redo_create_bitmap", LOGREC_IS_GROUP_ITSELF, NULL, NULL}; + +static LOG_DESC INIT_LOGREC_IMPORTED_TABLE= +{LOGRECTYPE_VARIABLE_LENGTH, 0, 0, NULL, NULL, NULL, 0, + "imported_table", LOGREC_IS_GROUP_ITSELF, NULL, NULL}; + +static LOG_DESC INIT_LOGREC_DEBUG_INFO= +{LOGRECTYPE_VARIABLE_LENGTH, 0, 0, NULL, NULL, NULL, 0, + "info", LOGREC_IS_GROUP_ITSELF, NULL, NULL}; + +const myf log_write_flags= MY_WME | MY_NABP | MY_WAIT_IF_FULL; + +void translog_table_init() +{ + int i; + log_record_type_descriptor[LOGREC_RESERVED_FOR_CHUNKS23]= + INIT_LOGREC_RESERVED_FOR_CHUNKS23; + log_record_type_descriptor[LOGREC_REDO_INSERT_ROW_HEAD]= + INIT_LOGREC_REDO_INSERT_ROW_HEAD; + log_record_type_descriptor[LOGREC_REDO_INSERT_ROW_TAIL]= + INIT_LOGREC_REDO_INSERT_ROW_TAIL; + log_record_type_descriptor[LOGREC_REDO_NEW_ROW_HEAD]= + INIT_LOGREC_REDO_NEW_ROW_HEAD; + log_record_type_descriptor[LOGREC_REDO_NEW_ROW_TAIL]= + INIT_LOGREC_REDO_NEW_ROW_TAIL; + log_record_type_descriptor[LOGREC_REDO_INSERT_ROW_BLOBS]= + INIT_LOGREC_REDO_INSERT_ROW_BLOBS; + log_record_type_descriptor[LOGREC_REDO_PURGE_ROW_HEAD]= + INIT_LOGREC_REDO_PURGE_ROW_HEAD; + log_record_type_descriptor[LOGREC_REDO_PURGE_ROW_TAIL]= + INIT_LOGREC_REDO_PURGE_ROW_TAIL; + log_record_type_descriptor[LOGREC_REDO_FREE_BLOCKS]= + INIT_LOGREC_REDO_FREE_BLOCKS; + log_record_type_descriptor[LOGREC_REDO_FREE_HEAD_OR_TAIL]= + INIT_LOGREC_REDO_FREE_HEAD_OR_TAIL; + log_record_type_descriptor[LOGREC_REDO_DELETE_ROW]= + INIT_LOGREC_REDO_DELETE_ROW; + log_record_type_descriptor[LOGREC_REDO_UPDATE_ROW_HEAD]= + INIT_LOGREC_REDO_UPDATE_ROW_HEAD; + log_record_type_descriptor[LOGREC_REDO_INDEX]= + INIT_LOGREC_REDO_INDEX; + log_record_type_descriptor[LOGREC_REDO_INDEX_NEW_PAGE]= + INIT_LOGREC_REDO_INDEX_NEW_PAGE; + log_record_type_descriptor[LOGREC_REDO_INDEX_FREE_PAGE]= + INIT_LOGREC_REDO_INDEX_FREE_PAGE; + log_record_type_descriptor[LOGREC_REDO_UNDELETE_ROW]= + INIT_LOGREC_REDO_UNDELETE_ROW; + log_record_type_descriptor[LOGREC_CLR_END]= + INIT_LOGREC_CLR_END; + log_record_type_descriptor[LOGREC_PURGE_END]= + INIT_LOGREC_PURGE_END; + log_record_type_descriptor[LOGREC_UNDO_ROW_INSERT]= + INIT_LOGREC_UNDO_ROW_INSERT; + log_record_type_descriptor[LOGREC_UNDO_ROW_DELETE]= + INIT_LOGREC_UNDO_ROW_DELETE; + log_record_type_descriptor[LOGREC_UNDO_ROW_UPDATE]= + INIT_LOGREC_UNDO_ROW_UPDATE; + log_record_type_descriptor[LOGREC_UNDO_KEY_INSERT]= + INIT_LOGREC_UNDO_KEY_INSERT; + log_record_type_descriptor[LOGREC_UNDO_KEY_INSERT_WITH_ROOT]= + INIT_LOGREC_UNDO_KEY_INSERT_WITH_ROOT; + log_record_type_descriptor[LOGREC_UNDO_KEY_DELETE]= + INIT_LOGREC_UNDO_KEY_DELETE; + log_record_type_descriptor[LOGREC_UNDO_KEY_DELETE_WITH_ROOT]= + INIT_LOGREC_UNDO_KEY_DELETE_WITH_ROOT; + log_record_type_descriptor[LOGREC_PREPARE]= + INIT_LOGREC_PREPARE; + log_record_type_descriptor[LOGREC_PREPARE_WITH_UNDO_PURGE]= + INIT_LOGREC_PREPARE_WITH_UNDO_PURGE; + log_record_type_descriptor[LOGREC_COMMIT]= + INIT_LOGREC_COMMIT; + log_record_type_descriptor[LOGREC_COMMIT_WITH_UNDO_PURGE]= + INIT_LOGREC_COMMIT_WITH_UNDO_PURGE; + log_record_type_descriptor[LOGREC_CHECKPOINT]= + INIT_LOGREC_CHECKPOINT; + log_record_type_descriptor[LOGREC_REDO_CREATE_TABLE]= + INIT_LOGREC_REDO_CREATE_TABLE; + log_record_type_descriptor[LOGREC_REDO_RENAME_TABLE]= + INIT_LOGREC_REDO_RENAME_TABLE; + log_record_type_descriptor[LOGREC_REDO_DROP_TABLE]= + INIT_LOGREC_REDO_DROP_TABLE; + log_record_type_descriptor[LOGREC_REDO_DELETE_ALL]= + INIT_LOGREC_REDO_DELETE_ALL; + log_record_type_descriptor[LOGREC_REDO_REPAIR_TABLE]= + INIT_LOGREC_REDO_REPAIR_TABLE; + log_record_type_descriptor[LOGREC_FILE_ID]= + INIT_LOGREC_FILE_ID; + log_record_type_descriptor[LOGREC_LONG_TRANSACTION_ID]= + INIT_LOGREC_LONG_TRANSACTION_ID; + log_record_type_descriptor[LOGREC_INCOMPLETE_LOG]= + INIT_LOGREC_INCOMPLETE_LOG; + log_record_type_descriptor[LOGREC_INCOMPLETE_GROUP]= + INIT_LOGREC_INCOMPLETE_GROUP; + log_record_type_descriptor[LOGREC_UNDO_BULK_INSERT]= + INIT_LOGREC_UNDO_BULK_INSERT; + log_record_type_descriptor[LOGREC_REDO_BITMAP_NEW_PAGE]= + INIT_LOGREC_REDO_BITMAP_NEW_PAGE; + log_record_type_descriptor[LOGREC_IMPORTED_TABLE]= + INIT_LOGREC_IMPORTED_TABLE; + log_record_type_descriptor[LOGREC_DEBUG_INFO]= + INIT_LOGREC_DEBUG_INFO; + + for (i= LOGREC_FIRST_FREE; i < LOGREC_NUMBER_OF_TYPES; i++) + log_record_type_descriptor[i].rclass= LOGRECTYPE_NOT_ALLOWED; +#ifndef DBUG_OFF + check_translog_description_table(LOGREC_FIRST_FREE -1); +#endif +} + + +/* all possible flags page overheads */ +static uint page_overhead[TRANSLOG_FLAGS_NUM]; + +typedef struct st_translog_validator_data +{ + TRANSLOG_ADDRESS *addr; + my_bool was_recovered; +} TRANSLOG_VALIDATOR_DATA; + + +/* + Check cursor/buffer consistence + + SYNOPSIS + translog_check_cursor + cursor cursor which will be checked +*/ + +static void translog_check_cursor(struct st_buffer_cursor *cursor + __attribute__((unused))) +{ + DBUG_ASSERT(cursor->chaser || + ((ulong) (cursor->ptr - cursor->buffer->buffer) == + cursor->buffer->size)); + DBUG_ASSERT(cursor->buffer->buffer_no == cursor->buffer_no); + DBUG_ASSERT((cursor->ptr -cursor->buffer->buffer) %TRANSLOG_PAGE_SIZE == + cursor->current_page_fill % TRANSLOG_PAGE_SIZE); + DBUG_ASSERT(cursor->current_page_fill <= TRANSLOG_PAGE_SIZE); +} + + +/** + @brief switch the loghandler in read only mode in case of write error +*/ + +void translog_stop_writing() +{ + DBUG_ENTER("translog_stop_writing"); + DBUG_PRINT("error", ("errno: %d my_errno: %d", errno, my_errno)); + translog_status= (translog_status == TRANSLOG_SHUTDOWN ? + TRANSLOG_UNINITED : + TRANSLOG_READONLY); + log_descriptor.is_everything_flushed= 1; + log_descriptor.open_flags= O_BINARY | O_RDONLY; + DBUG_ASSERT(0); + DBUG_VOID_RETURN; +} + + +/* + @brief Get file name of the log by log number + + @param file_no Number of the log we want to open + @param path Pointer to buffer where file name will be + stored (must be FN_REFLEN bytes at least) + + @return pointer to path +*/ + +char *translog_filename_by_fileno(uint32 file_no, char *path) +{ + char buff[11], *end; + uint length; + DBUG_ENTER("translog_filename_by_fileno"); + DBUG_ASSERT(file_no <= 0xfffffff); + + /* log_descriptor.directory is already formated */ + end= strxmov(path, log_descriptor.directory, "maria_log.0000000", NullS); + length= (uint) (int10_to_str(file_no, buff, 10) - buff); + strmov(end - length +1, buff); + + DBUG_PRINT("info", ("Path: '%s' path: 0x%lx", path, (ulong) path)); + DBUG_RETURN(path); +} + + +/** + @brief Create log file with given number without cache + + @param file_no Number of the log we want to open + + retval -1 error + retval # file descriptor number +*/ + +static File create_logfile_by_number_no_cache(uint32 file_no) +{ + File file; + char path[FN_REFLEN]; + DBUG_ENTER("create_logfile_by_number_no_cache"); + + if (translog_status != TRANSLOG_OK) + DBUG_RETURN(-1); + + /* TODO: add O_DIRECT to open flags (when buffer is aligned) */ + if ((file= my_create(translog_filename_by_fileno(file_no, path), + 0, O_BINARY | O_RDWR, MYF(MY_WME))) < 0) + { + DBUG_PRINT("error", ("Error %d during creating file '%s'", errno, path)); + translog_stop_writing(); + DBUG_RETURN(-1); + } + if (sync_log_dir >= TRANSLOG_SYNC_DIR_NEWFILE && + sync_dir(log_descriptor.directory_fd, MYF(MY_WME | MY_IGNORE_BADFD))) + { + DBUG_PRINT("error", ("Error %d during syncing directory '%s'", + errno, log_descriptor.directory)); + translog_stop_writing(); + DBUG_RETURN(-1); + } + DBUG_PRINT("info", ("File: '%s' handler: %d", path, file)); + DBUG_RETURN(file); +} + +/** + @brief Open (not create) log file with given number without cache + + @param file_no Number of the log we want to open + + retval -1 error + retval # file descriptor number +*/ + +static File open_logfile_by_number_no_cache(uint32 file_no) +{ + File file; + char path[FN_REFLEN]; + DBUG_ENTER("open_logfile_by_number_no_cache"); + + /* TODO: add O_DIRECT to open flags (when buffer is aligned) */ + /* TODO: use my_create() */ + if ((file= my_open(translog_filename_by_fileno(file_no, path), + log_descriptor.open_flags, + MYF(MY_WME))) < 0) + { + DBUG_PRINT("error", ("Error %d during opening file '%s'", errno, path)); + DBUG_RETURN(-1); + } + DBUG_PRINT("info", ("File: '%s' handler: %d", path, file)); + DBUG_RETURN(file); +} + + +/** + @brief get file descriptor by given number using cache + + @param file_no Number of the log we want to open + + retval # file descriptor + retval NULL file is not opened +*/ + +static TRANSLOG_FILE *get_logfile_by_number(uint32 file_no) +{ + TRANSLOG_FILE *file; + DBUG_ENTER("get_logfile_by_number"); + rw_rdlock(&log_descriptor.open_files_lock); + if (log_descriptor.max_file - file_no >= + log_descriptor.open_files.elements) + { + DBUG_PRINT("info", ("File #%u is not opened", file_no)); + rw_unlock(&log_descriptor.open_files_lock); + DBUG_RETURN(NULL); + } + DBUG_ASSERT(log_descriptor.max_file - log_descriptor.min_file + 1 == + log_descriptor.open_files.elements); + DBUG_ASSERT(log_descriptor.max_file >= file_no); + DBUG_ASSERT(log_descriptor.min_file <= file_no); + + file= *dynamic_element(&log_descriptor.open_files, + log_descriptor.max_file - file_no, TRANSLOG_FILE **); + rw_unlock(&log_descriptor.open_files_lock); + DBUG_PRINT("info", ("File 0x%lx File no: %lu, File handler: %d", + (ulong)file, (ulong)file_no, + (file ? file->handler.file : -1))); + DBUG_ASSERT(!file || file->number == file_no); + DBUG_RETURN(file); +} + + +/** + @brief get current file descriptor + + retval # file descriptor +*/ + +static TRANSLOG_FILE *get_current_logfile() +{ + TRANSLOG_FILE *file; + rw_rdlock(&log_descriptor.open_files_lock); + DBUG_ASSERT(log_descriptor.max_file - log_descriptor.min_file + 1 == + log_descriptor.open_files.elements); + file= *dynamic_element(&log_descriptor.open_files, 0, TRANSLOG_FILE **); + rw_unlock(&log_descriptor.open_files_lock); + return (file); +} + +uchar NEAR maria_trans_file_magic[]= +{ (uchar) 254, (uchar) 254, (uchar) 11, '\001', 'M', 'A', 'R', 'I', 'A', + 'L', 'O', 'G' }; +#define LOG_HEADER_DATA_SIZE (sizeof(maria_trans_file_magic) + \ + 8 + 4 + 4 + 4 + 2 + 3 + \ + LSN_STORE_SIZE) + + +/* + Write log file page header in the just opened new log file + + SYNOPSIS + translog_write_file_header(); + + NOTES + First page is just a marker page; We don't store any real log data in it. + + RETURN + 0 OK + 1 ERROR +*/ + +static my_bool translog_write_file_header() +{ + TRANSLOG_FILE *file; + ulonglong timestamp; + uchar page_buff[TRANSLOG_PAGE_SIZE], *page= page_buff; + my_bool rc; + DBUG_ENTER("translog_write_file_header"); + + /* file tag */ + memcpy(page, maria_trans_file_magic, sizeof(maria_trans_file_magic)); + page+= sizeof(maria_trans_file_magic); + /* timestamp */ + timestamp= my_getsystime(); + int8store(page, timestamp); + page+= 8; + /* maria version */ + int4store(page, TRANSLOG_VERSION_ID); + page+= 4; + /* mysql version (MYSQL_VERSION_ID) */ + int4store(page, log_descriptor.server_version); + page+= 4; + /* server ID */ + int4store(page, log_descriptor.server_id); + page+= 4; + /* loghandler page_size */ + int2store(page, TRANSLOG_PAGE_SIZE - 1); + page+= 2; + /* file number */ + int3store(page, LSN_FILE_NO(log_descriptor.horizon)); + page+= 3; + lsn_store(page, LSN_IMPOSSIBLE); + page+= LSN_STORE_SIZE; + memset(page, TRANSLOG_FILLER, sizeof(page_buff) - (page- page_buff)); + + file= get_current_logfile(); + rc= my_pwrite(file->handler.file, page_buff, sizeof(page_buff), 0, + log_write_flags) != 0; + /* + Dropping the flag in such way can make false alarm: signalling than the + file in not sync when it is sync, but the situation is quite rare and + protections with mutexes give much more overhead to the whole engine + */ + file->is_sync= 0; + DBUG_RETURN(rc); +} + +/* + @brief write the new LSN on the given file header + + @param file The file descriptor + @param lsn That LSN which should be written + + @retval 0 OK + @retval 1 Error +*/ + +static my_bool translog_max_lsn_to_header(File file, LSN lsn) +{ + uchar lsn_buff[LSN_STORE_SIZE]; + DBUG_ENTER("translog_max_lsn_to_header"); + DBUG_PRINT("enter", ("File descriptor: %ld " + "lsn: (%lu,0x%lx)", + (long) file, + LSN_IN_PARTS(lsn))); + + lsn_store(lsn_buff, lsn); + + DBUG_RETURN(my_pwrite(file, lsn_buff, + LSN_STORE_SIZE, + (LOG_HEADER_DATA_SIZE - LSN_STORE_SIZE), + log_write_flags) != 0 || + my_sync(file, MYF(MY_WME)) != 0); +} + + +/* + Information from transaction log file header +*/ + +typedef struct st_loghandler_file_info +{ + /* + LSN_IMPOSSIBLE for current file (not finished file). + Maximum LSN of the record which parts stored in the + file. + */ + LSN max_lsn; + ulonglong timestamp; /* Time stamp */ + ulong maria_version; /* Version of maria loghandler */ + ulong mysql_version; /* Version of mysql server */ + ulong server_id; /* Server ID */ + ulong page_size; /* Loghandler page size */ + ulong file_number; /* Number of the file (from the file header) */ +} LOGHANDLER_FILE_INFO; + +/* + @brief Extract hander file information from loghandler file page + + @param desc header information descriptor to be filled with information + @param page_buff buffer with the page content +*/ + +static void translog_interpret_file_header(LOGHANDLER_FILE_INFO *desc, + uchar *page_buff) +{ + uchar *ptr; + + ptr= page_buff + sizeof(maria_trans_file_magic); + desc->timestamp= uint8korr(ptr); + ptr+= 8; + desc->maria_version= uint4korr(ptr); + ptr+= 4; + desc->mysql_version= uint4korr(ptr); + ptr+= 4; + desc->server_id= uint4korr(ptr + 4); + ptr+= 4; + desc->page_size= uint2korr(ptr) + 1; + ptr+= 2; + desc->file_number= uint3korr(ptr); + ptr+=3; + desc->max_lsn= lsn_korr(ptr); +} + + +/* + @brief Read hander file information from loghandler file + + @param desc header information descriptor to be filled with information + @param file file descriptor to read + + @retval 0 OK + @retval 1 Error +*/ + +my_bool translog_read_file_header(LOGHANDLER_FILE_INFO *desc, File file) +{ + uchar page_buff[LOG_HEADER_DATA_SIZE]; + DBUG_ENTER("translog_read_file_header"); + + if (my_pread(file, page_buff, + sizeof(page_buff), 0, MYF(MY_FNABP | MY_WME))) + { + DBUG_PRINT("info", ("log read fail error: %d", my_errno)); + DBUG_RETURN(1); + } + translog_interpret_file_header(desc, page_buff); + DBUG_PRINT("info", ("timestamp: %llu maria ver: %lu mysql ver: %lu " + "server id %lu page size %lu file number %lu " + "max lsn: (%lu,0x%lx)", + (ulonglong) desc->timestamp, + (ulong) desc->maria_version, + (ulong) desc->mysql_version, + (ulong) desc->server_id, + desc->page_size, (ulong) desc->file_number, + LSN_IN_PARTS(desc->max_lsn))); + DBUG_RETURN(0); +} + + +/* + @brief set the lsn to the files from_file - to_file if it is greater + then written in the file + + @param from_file first file number (min) + @param to_file last file number (max) + @param lsn the lsn for writing + @param is_locked true if current thread locked the log handler + + @retval 0 OK + @retval 1 Error +*/ + +static my_bool translog_set_lsn_for_files(uint32 from_file, uint32 to_file, + LSN lsn, my_bool is_locked) +{ + uint32 file; + DBUG_ENTER("translog_set_lsn_for_files"); + DBUG_PRINT("enter", ("From: %lu to: %lu lsn: (%lu,0x%lx) locked: %d", + (ulong) from_file, (ulong) to_file, + LSN_IN_PARTS(lsn), + is_locked)); + DBUG_ASSERT(from_file <= to_file); + DBUG_ASSERT(from_file > 0); /* we have not file 0 */ + + /* Checks the current file (not finished yet file) */ + if (!is_locked) + translog_lock(); + if (to_file == (uint32) LSN_FILE_NO(log_descriptor.horizon)) + { + if (likely(cmp_translog_addr(lsn, log_descriptor.max_lsn) > 0)) + log_descriptor.max_lsn= lsn; + to_file--; + } + if (!is_locked) + translog_unlock(); + + /* Checks finished files if they are */ + pthread_mutex_lock(&log_descriptor.file_header_lock); + for (file= from_file; file <= to_file; file++) + { + LOGHANDLER_FILE_INFO info; + File fd= open_logfile_by_number_no_cache(file); + if ((fd < 0) || + ((translog_read_file_header(&info, fd) || + (cmp_translog_addr(lsn, info.max_lsn) > 0 && + translog_max_lsn_to_header(fd, lsn))) | + my_close(fd, MYF(MY_WME)))) + { + translog_stop_writing(); + DBUG_RETURN(1); + } + } + pthread_mutex_unlock(&log_descriptor.file_header_lock); + + DBUG_RETURN(0); +} + + +/* descriptor of file in unfinished_files */ +struct st_file_counter +{ + uint32 file; /* file number */ + uint32 counter; /* counter for started writes */ +}; + + +/* + @brief mark file "in progress" (for multi-group records) + + @param file log file number +*/ + +static void translog_mark_file_unfinished(uint32 file) +{ + int place, i; + struct st_file_counter fc, *fc_ptr; + + DBUG_ENTER("translog_mark_file_unfinished"); + DBUG_PRINT("enter", ("file: %lu", (ulong) file)); + + fc.file= file; fc.counter= 1; + pthread_mutex_lock(&log_descriptor.unfinished_files_lock); + + if (log_descriptor.unfinished_files.elements == 0) + { + insert_dynamic(&log_descriptor.unfinished_files, (uchar*) &fc); + DBUG_PRINT("info", ("The first element inserted")); + goto end; + } + + for (place= log_descriptor.unfinished_files.elements - 1; + place >= 0; + place--) + { + fc_ptr= dynamic_element(&log_descriptor.unfinished_files, + place, struct st_file_counter *); + if (fc_ptr->file <= file) + break; + } + + if (place >= 0 && fc_ptr->file == file) + { + fc_ptr->counter++; + DBUG_PRINT("info", ("counter increased")); + goto end; + } + + if (place == (int)log_descriptor.unfinished_files.elements) + { + insert_dynamic(&log_descriptor.unfinished_files, (uchar*) &fc); + DBUG_PRINT("info", ("The last element inserted")); + goto end; + } + /* shift and assign new element */ + insert_dynamic(&log_descriptor.unfinished_files, + (uchar*) + dynamic_element(&log_descriptor.unfinished_files, + log_descriptor.unfinished_files.elements- 1, + struct st_file_counter *)); + for(i= log_descriptor.unfinished_files.elements - 1; i > place; i--) + { + /* we do not use set_dynamic() to avoid unneeded checks */ + memcpy(dynamic_element(&log_descriptor.unfinished_files, + i, struct st_file_counter *), + dynamic_element(&log_descriptor.unfinished_files, + i + 1, struct st_file_counter *), + sizeof(struct st_file_counter)); + } + memcpy(dynamic_element(&log_descriptor.unfinished_files, + place + 1, struct st_file_counter *), + &fc, sizeof(struct st_file_counter)); +end: + pthread_mutex_unlock(&log_descriptor.unfinished_files_lock); + DBUG_VOID_RETURN; +} + + +/* + @brief remove file mark "in progress" (for multi-group records) + + @param file log file number +*/ + +static void translog_mark_file_finished(uint32 file) +{ + int i; + struct st_file_counter *fc_ptr; + DBUG_ENTER("translog_mark_file_finished"); + DBUG_PRINT("enter", ("file: %lu", (ulong) file)); + + LINT_INIT(fc_ptr); + + pthread_mutex_lock(&log_descriptor.unfinished_files_lock); + + DBUG_ASSERT(log_descriptor.unfinished_files.elements > 0); + for (i= 0; + i < (int) log_descriptor.unfinished_files.elements; + i++) + { + fc_ptr= dynamic_element(&log_descriptor.unfinished_files, + i, struct st_file_counter *); + if (fc_ptr->file == file) + { + break; + } + } + DBUG_ASSERT(i < (int) log_descriptor.unfinished_files.elements); + + if (! --fc_ptr->counter) + delete_dynamic_element(&log_descriptor.unfinished_files, i); + pthread_mutex_unlock(&log_descriptor.unfinished_files_lock); + DBUG_VOID_RETURN; +} + + +/* + @brief get max LSN of the record which parts stored in this file + + @param file file number + + @return requested LSN or LSN_IMPOSSIBLE/LSN_ERROR + @retval LSN_IMPOSSIBLE File is still not finished + @retval LSN_ERROR Error opening file + @retval # LSN of the record which parts stored in this file +*/ + +LSN translog_get_file_max_lsn_stored(uint32 file) +{ + uint32 limit= FILENO_IMPOSSIBLE; + DBUG_ENTER("translog_get_file_max_lsn_stored"); + DBUG_PRINT("enter", ("file: %lu", (ulong)file)); + DBUG_ASSERT(translog_status == TRANSLOG_OK || + translog_status == TRANSLOG_READONLY); + + pthread_mutex_lock(&log_descriptor.unfinished_files_lock); + + /* find file with minimum file number "in progress" */ + if (log_descriptor.unfinished_files.elements > 0) + { + struct st_file_counter *fc_ptr; + fc_ptr= dynamic_element(&log_descriptor.unfinished_files, + 0, struct st_file_counter *); + limit= fc_ptr->file; /* minimal file number "in progress" */ + } + pthread_mutex_unlock(&log_descriptor.unfinished_files_lock); + + /* + if there is no "in progress file" then unfinished file is in progress + for sure + */ + if (limit == FILENO_IMPOSSIBLE) + { + TRANSLOG_ADDRESS horizon= translog_get_horizon(); + limit= LSN_FILE_NO(horizon); + } + + if (file >= limit) + { + DBUG_PRINT("info", ("The file in in progress")); + DBUG_RETURN(LSN_IMPOSSIBLE); + } + + { + LOGHANDLER_FILE_INFO info; + File fd= open_logfile_by_number_no_cache(file); + if ((fd < 0) || + (translog_read_file_header(&info, fd) | my_close(fd, MYF(MY_WME)))) + { + DBUG_PRINT("error", ("Can't read file header")); + DBUG_RETURN(LSN_ERROR); + } + DBUG_PRINT("info", ("Max lsn: (%lu,0x%lx)", + LSN_IN_PARTS(info.max_lsn))); + DBUG_RETURN(info.max_lsn); + } +} + +/* + Initialize transaction log file buffer + + SYNOPSIS + translog_buffer_init() + buffer The buffer to initialize + num Number of this buffer + + RETURN + 0 OK + 1 Error +*/ + +static my_bool translog_buffer_init(struct st_translog_buffer *buffer, int num) +{ + DBUG_ENTER("translog_buffer_init"); + buffer->prev_last_lsn= buffer->last_lsn= LSN_IMPOSSIBLE; + DBUG_PRINT("info", ("last_lsn and prev_last_lsn set to 0 buffer: 0x%lx", + (ulong) buffer)); + + buffer->buffer_no= (uint8) num; + /* This Buffer File */ + buffer->file= NULL; + buffer->overlay= 0; + /* cache for current log */ + memset(buffer->buffer, TRANSLOG_FILLER, TRANSLOG_WRITE_BUFFER); + /* Buffer size */ + buffer->size= 0; + /* cond of thread which is waiting for buffer filling */ + if (pthread_cond_init(&buffer->waiting_filling_buffer, 0)) + DBUG_RETURN(1); + /* Number of records which are in copy progress */ + buffer->copy_to_buffer_in_progress= 0; + /* list of waiting buffer ready threads */ + buffer->waiting_flush= 0; + /* + Buffers locked by fallowing mutex. As far as buffers create logical + circle (after last buffer goes first) it trigger false alarm of deadlock + detect system, so we remove check of deadlock for this buffers. In deed + all mutex locks concentrated around current buffer except flushing + thread (but it is only one thread). One thread can't take more then + 2 buffer locks at once. So deadlock is impossible here. + + To prevent false alarm of dead lock detection we switch dead lock + detection for one buffer in the middle of the buffers chain. Excluding + only one of eight buffers from deadlock detection hardly can hide other + possible problems which include this mutexes. + */ + if (my_pthread_mutex_init(&buffer->mutex, MY_MUTEX_INIT_FAST, + "translog_buffer->mutex", + (num == TRANSLOG_BUFFERS_NO - 2 ? + MYF_NO_DEADLOCK_DETECTION : 0)) || + pthread_cond_init(&buffer->prev_sent_to_disk_cond, 0)) + DBUG_RETURN(1); + buffer->is_closing_buffer= 0; + buffer->prev_sent_to_disk= LSN_IMPOSSIBLE; + buffer->prev_buffer_offset= LSN_IMPOSSIBLE; + buffer->ver= 0; + DBUG_RETURN(0); +} + + +/* + @brief close transaction log file by descriptor + + @param file pagegecache file descriptor reference + + @return Operation status + @retval 0 OK + @retval 1 Error +*/ + +static my_bool translog_close_log_file(TRANSLOG_FILE *file) +{ + int rc= 0; + flush_pagecache_blocks(log_descriptor.pagecache, &file->handler, + FLUSH_RELEASE); + /* + Sync file when we close it + TODO: sync only we have changed the log + */ + if (!file->is_sync) + rc= my_sync(file->handler.file, MYF(MY_WME)); + rc|= my_close(file->handler.file, MYF(MY_WME)); + my_free(file, MYF(0)); + return test(rc); +} + + +/** + @brief Dummy function for write failure (the log to not use + pagecache writing) +*/ + +void translog_dummy_write_failure(uchar *data __attribute__((unused))) +{ + return; +} + + +/** + @brief Initializes TRANSLOG_FILE structure + + @param file reference on the file to initialize + @param number file number + @param is_sync is file synced on disk +*/ + +static void translog_file_init(TRANSLOG_FILE *file, uint32 number, + my_bool is_sync) +{ + pagecache_file_init(file->handler, &translog_page_validator, + &translog_dummy_callback, + &translog_dummy_write_failure, + maria_flush_log_for_page_none, file); + file->number= number; + file->was_recovered= 0; + file->is_sync= is_sync; +} + + +/** + @brief Create and fill header of new file. + + @note the caller must call it right after it has increased + log_descriptor.horizon to the new file + (log_descriptor.horizon+= LSN_ONE_FILE) + + + @retval 0 OK + @retval 1 Error +*/ + +static my_bool translog_create_new_file() +{ + TRANSLOG_FILE *file= (TRANSLOG_FILE*)my_malloc(sizeof(TRANSLOG_FILE), + MYF(0)); + + TRANSLOG_FILE *old= get_current_logfile(); + uint32 file_no= LSN_FILE_NO(log_descriptor.horizon); + DBUG_ENTER("translog_create_new_file"); + + if (file == NULL) + goto error; + + /* + Writes max_lsn to the file header before finishing it (there is no need + to lock file header buffer because it is still unfinished file, so only + one thread can finish the file and nobody interested of LSN of current + (unfinished) file, because no one can purge it). + */ + if (translog_max_lsn_to_header(old->handler.file, log_descriptor.max_lsn)) + goto error; + + rw_wrlock(&log_descriptor.open_files_lock); + DBUG_ASSERT(log_descriptor.max_file - log_descriptor.min_file + 1 == + log_descriptor.open_files.elements); + DBUG_ASSERT(file_no == log_descriptor.max_file + 1); + if (allocate_dynamic(&log_descriptor.open_files, + log_descriptor.max_file - log_descriptor.min_file + 2)) + goto error_lock; + if ((file->handler.file= + create_logfile_by_number_no_cache(file_no)) == -1) + goto error_lock; + translog_file_init(file, file_no, 0); + + /* this call just expand the array */ + insert_dynamic(&log_descriptor.open_files, (uchar*)&file); + log_descriptor.max_file++; + { + char *start= (char*) dynamic_element(&log_descriptor.open_files, 0, + TRANSLOG_FILE**); + memmove(start + sizeof(TRANSLOG_FILE*), start, + sizeof(TRANSLOG_FILE*) * + (log_descriptor.max_file - log_descriptor.min_file + 1 - 1)); + } + /* can't fail we because we expanded array */ + set_dynamic(&log_descriptor.open_files, (uchar*)&file, 0); + DBUG_ASSERT(log_descriptor.max_file - log_descriptor.min_file + 1 == + log_descriptor.open_files.elements); + rw_unlock(&log_descriptor.open_files_lock); + + DBUG_PRINT("info", ("file_no: %lu", (ulong)file_no)); + + if (translog_write_file_header()) + DBUG_RETURN(1); + + if (ma_control_file_write_and_force(last_checkpoint_lsn, file_no, + max_trid_in_control_file, + recovery_failures)) + { + translog_stop_writing(); + DBUG_RETURN(1); + } + + DBUG_RETURN(0); + +error_lock: + rw_unlock(&log_descriptor.open_files_lock); +error: + translog_stop_writing(); + DBUG_RETURN(1); +} + + +/** + @brief Locks the loghandler buffer. + + @param buffer This buffer which should be locked + + @note See comment before buffer 'mutex' variable. + + @retval 0 OK + @retval 1 Error +*/ + +static void translog_buffer_lock(struct st_translog_buffer *buffer) +{ + DBUG_ENTER("translog_buffer_lock"); + DBUG_PRINT("enter", + ("Lock buffer #%u: (0x%lx)", (uint) buffer->buffer_no, + (ulong) buffer)); + pthread_mutex_lock(&buffer->mutex); + DBUG_VOID_RETURN; +} + + +/* + Unlock the loghandler buffer + + SYNOPSIS + translog_buffer_unlock() + buffer This buffer which should be unlocked + + RETURN + 0 OK + 1 Error +*/ + +static void translog_buffer_unlock(struct st_translog_buffer *buffer) +{ + DBUG_ENTER("translog_buffer_unlock"); + DBUG_PRINT("enter", ("Unlock buffer... #%u (0x%lx)", + (uint) buffer->buffer_no, (ulong) buffer)); + + pthread_mutex_unlock(&buffer->mutex); + DBUG_VOID_RETURN; +} + + +/* + Write a header on the page + + SYNOPSIS + translog_new_page_header() + horizon Where to write the page + cursor Where to write the page + + NOTE + - space for page header should be checked before +*/ + +static uchar translog_sector_random; + +static void translog_new_page_header(TRANSLOG_ADDRESS *horizon, + struct st_buffer_cursor *cursor) +{ + uchar *ptr; + + DBUG_ENTER("translog_new_page_header"); + DBUG_ASSERT(cursor->ptr); + + cursor->protected= 0; + + ptr= cursor->ptr; + /* Page number */ + int3store(ptr, LSN_OFFSET(*horizon) / TRANSLOG_PAGE_SIZE); + ptr+= 3; + /* File number */ + int3store(ptr, LSN_FILE_NO(*horizon)); + ptr+= 3; + DBUG_ASSERT(TRANSLOG_PAGE_FLAGS == (ptr - cursor->ptr)); + cursor->ptr[TRANSLOG_PAGE_FLAGS]= (uchar) log_descriptor.flags; + ptr++; + if (log_descriptor.flags & TRANSLOG_PAGE_CRC) + { +#ifndef DBUG_OFF + DBUG_PRINT("info", ("write 0x11223344 CRC to (%lu,0x%lx)", + LSN_IN_PARTS(*horizon))); + /* This will be overwritten by real CRC; This is just for debugging */ + int4store(ptr, 0x11223344); +#endif + /* CRC will be put when page is finished */ + ptr+= CRC_SIZE; + } + if (log_descriptor.flags & TRANSLOG_SECTOR_PROTECTION) + { + /* + translog_sector_randmo works like "random" values producer because + it is enough to have such "random" for this purpose and it will + not interfere with higher level pseudo random value generator + */ + ptr[0]= translog_sector_random++; + ptr+= TRANSLOG_PAGE_SIZE / DISK_DRIVE_SECTOR_SIZE; + } + { + uint len= (ptr - cursor->ptr); + (*horizon)+= len; /* increasing the offset part of the address */ + cursor->current_page_fill= len; + if (!cursor->chaser) + cursor->buffer->size+= len; + } + cursor->ptr= ptr; + DBUG_PRINT("info", ("NewP buffer #%u: 0x%lx chaser: %d Size: %lu (%lu) " + "Horizon: (%lu,0x%lx)", + (uint) cursor->buffer->buffer_no, (ulong) cursor->buffer, + cursor->chaser, (ulong) cursor->buffer->size, + (ulong) (cursor->ptr - cursor->buffer->buffer), + LSN_IN_PARTS(*horizon))); + translog_check_cursor(cursor); + DBUG_VOID_RETURN; +} + + +/* + Put sector protection on the page image + + SYNOPSIS + translog_put_sector_protection() + page reference on the page content + cursor cursor of the buffer + + NOTES + We put a sector protection on all following sectors on the page, + except the first sector that is protected by page header. +*/ + +static void translog_put_sector_protection(uchar *page, + struct st_buffer_cursor *cursor) +{ + uchar *table= page + log_descriptor.page_overhead - + TRANSLOG_PAGE_SIZE / DISK_DRIVE_SECTOR_SIZE; + uint i, offset; + uint16 last_protected_sector= ((cursor->previous_offset - 1) / + DISK_DRIVE_SECTOR_SIZE); + uint16 start_sector= cursor->previous_offset / DISK_DRIVE_SECTOR_SIZE; + uint8 value= table[0] + cursor->write_counter; + DBUG_ENTER("translog_put_sector_protection"); + + if (start_sector == 0) + { + /* First sector is protected by file & page numbers in the page header. */ + start_sector= 1; + } + + DBUG_PRINT("enter", ("Write counter:%u value:%u offset:%u, " + "last protected:%u start sector:%u", + (uint) cursor->write_counter, + (uint) value, + (uint) cursor->previous_offset, + (uint) last_protected_sector, (uint) start_sector)); + if (last_protected_sector == start_sector) + { + i= last_protected_sector; + offset= last_protected_sector * DISK_DRIVE_SECTOR_SIZE; + /* restore data, because we modified sector which was protected */ + if (offset < cursor->previous_offset) + page[offset]= table[i]; + } + for (i= start_sector, offset= start_sector * DISK_DRIVE_SECTOR_SIZE; + i < TRANSLOG_PAGE_SIZE / DISK_DRIVE_SECTOR_SIZE; + i++, (offset+= DISK_DRIVE_SECTOR_SIZE)) + { + DBUG_PRINT("info", ("sector:%u offset:%u data 0x%x", + i, offset, (uint) page[offset])); + table[i]= page[offset]; + page[offset]= value; + DBUG_PRINT("info", ("sector:%u offset:%u data 0x%x", + i, offset, (uint) page[offset])); + } + DBUG_VOID_RETURN; +} + + +/* + Calculate CRC32 of given area + + SYNOPSIS + translog_crc() + area Pointer of the area beginning + length The Area length + + RETURN + CRC32 +*/ + +static uint32 translog_crc(uchar *area, uint length) +{ + DBUG_ENTER("translog_crc"); + DBUG_RETURN(crc32(0L, (unsigned char*) area, length)); +} + + +/* + Finish current page with zeros + + SYNOPSIS + translog_finish_page() + horizon \ horizon & buffer pointers + cursor / +*/ + +static void translog_finish_page(TRANSLOG_ADDRESS *horizon, + struct st_buffer_cursor *cursor) +{ + uint16 left= TRANSLOG_PAGE_SIZE - cursor->current_page_fill; + uchar *page= cursor->ptr - cursor->current_page_fill; + DBUG_ENTER("translog_finish_page"); + DBUG_PRINT("enter", ("Buffer: #%u 0x%lx " + "Buffer addr: (%lu,0x%lx) " + "Page addr: (%lu,0x%lx) " + "size:%lu (%lu) Pg:%u left:%u", + (uint) cursor->buffer_no, (ulong) cursor->buffer, + LSN_IN_PARTS(cursor->buffer->offset), + (ulong) LSN_FILE_NO(*horizon), + (ulong) (LSN_OFFSET(*horizon) - + cursor->current_page_fill), + (ulong) cursor->buffer->size, + (ulong) (cursor->ptr -cursor->buffer->buffer), + (uint) cursor->current_page_fill, (uint) left)); + DBUG_ASSERT(LSN_FILE_NO(*horizon) == LSN_FILE_NO(cursor->buffer->offset)); + translog_check_cursor(cursor); + if (cursor->protected) + { + DBUG_PRINT("info", ("Already protected and finished")); + DBUG_VOID_RETURN; + } + cursor->protected= 1; + + DBUG_ASSERT(left < TRANSLOG_PAGE_SIZE); + if (left != 0) + { + DBUG_PRINT("info", ("left: %u", (uint) left)); + memset(cursor->ptr, TRANSLOG_FILLER, left); + cursor->ptr+= left; + (*horizon)+= left; /* offset increasing */ + if (!cursor->chaser) + cursor->buffer->size+= left; + /* We are finishing the page so reset the counter */ + cursor->current_page_fill= 0; + DBUG_PRINT("info", ("Finish Page buffer #%u: 0x%lx " + "chaser: %d Size: %lu (%lu)", + (uint) cursor->buffer->buffer_no, + (ulong) cursor->buffer, cursor->chaser, + (ulong) cursor->buffer->size, + (ulong) (cursor->ptr - cursor->buffer->buffer))); + translog_check_cursor(cursor); + } + /* + When we are finishing the page other thread might not finish the page + header yet (in case if we started from the middle of the page) so we + have to read log_descriptor.flags but not the flags from the page. + */ + if (log_descriptor.flags & TRANSLOG_SECTOR_PROTECTION) + { + translog_put_sector_protection(page, cursor); + DBUG_PRINT("info", ("drop write_counter")); + cursor->write_counter= 0; + cursor->previous_offset= 0; + } + if (log_descriptor.flags & TRANSLOG_PAGE_CRC) + { + uint32 crc= translog_crc(page + log_descriptor.page_overhead, + TRANSLOG_PAGE_SIZE - + log_descriptor.page_overhead); + DBUG_PRINT("info", ("CRC: %lx", (ulong) crc)); + /* We have page number, file number and flag before crc */ + int4store(page + 3 + 3 + 1, crc); + } + DBUG_VOID_RETURN; +} + + +/* + @brief Wait until all threads have finished closing this buffer. + + @param buffer This buffer should be check +*/ + +static void translog_wait_for_closing(struct st_translog_buffer *buffer) +{ + DBUG_ENTER("translog_wait_for_closing"); + DBUG_PRINT("enter", ("Buffer #%u 0x%lx copies in progress: %u " + "is closing %u File: %d size: %lu", + (uint) buffer->buffer_no, (ulong) buffer, + (uint) buffer->copy_to_buffer_in_progress, + (uint) buffer->is_closing_buffer, + (buffer->file ? buffer->file->handler.file : -1), + (ulong) buffer->size)); + translog_buffer_lock_assert_owner(buffer); + + while (buffer->is_closing_buffer) + { + DBUG_PRINT("info", ("wait for writers... buffer: #%u 0x%lx", + (uint) buffer->buffer_no, (ulong) buffer)); + DBUG_ASSERT(buffer->file != NULL); + pthread_cond_wait(&buffer->waiting_filling_buffer, &buffer->mutex); + DBUG_PRINT("info", ("wait for writers done buffer: #%u 0x%lx", + (uint) buffer->buffer_no, (ulong) buffer)); + } + + DBUG_VOID_RETURN; +} + + +/* + @brief Wait until all threads have finished filling this buffer. + + @param buffer This buffer should be check +*/ + +static void translog_wait_for_writers(struct st_translog_buffer *buffer) +{ + DBUG_ENTER("translog_wait_for_writers"); + DBUG_PRINT("enter", ("Buffer #%u 0x%lx copies in progress: %u " + "is closing %u File: %d size: %lu", + (uint) buffer->buffer_no, (ulong) buffer, + (uint) buffer->copy_to_buffer_in_progress, + (uint) buffer->is_closing_buffer, + (buffer->file ? buffer->file->handler.file : -1), + (ulong) buffer->size)); + translog_buffer_lock_assert_owner(buffer); + + while (buffer->copy_to_buffer_in_progress) + { + DBUG_PRINT("info", ("wait for writers... buffer: #%u 0x%lx", + (uint) buffer->buffer_no, (ulong) buffer)); + DBUG_ASSERT(buffer->file != NULL); + pthread_cond_wait(&buffer->waiting_filling_buffer, &buffer->mutex); + DBUG_PRINT("info", ("wait for writers done buffer: #%u 0x%lx", + (uint) buffer->buffer_no, (ulong) buffer)); + } + + DBUG_VOID_RETURN; +} + + +/* + + Wait for buffer to become free + + SYNOPSIS + translog_wait_for_buffer_free() + buffer The buffer we are waiting for + + NOTE + - this buffer should be locked +*/ + +static void translog_wait_for_buffer_free(struct st_translog_buffer *buffer) +{ + TRANSLOG_ADDRESS offset= buffer->offset; + TRANSLOG_FILE *file= buffer->file; + uint8 ver= buffer->ver; + DBUG_ENTER("translog_wait_for_buffer_free"); + DBUG_PRINT("enter", ("Buffer #%u 0x%lx copies in progress: %u " + "is closing %u File: %d size: %lu", + (uint) buffer->buffer_no, (ulong) buffer, + (uint) buffer->copy_to_buffer_in_progress, + (uint) buffer->is_closing_buffer, + (buffer->file ? buffer->file->handler.file : -1), + (ulong) buffer->size)); + + translog_wait_for_writers(buffer); + + if (offset != buffer->offset || file != buffer->file || ver != buffer->ver) + DBUG_VOID_RETURN; /* the buffer if already freed */ + + while (buffer->file != NULL) + { + DBUG_PRINT("info", ("wait for writers... buffer: #%u 0x%lx", + (uint) buffer->buffer_no, (ulong) buffer)); + pthread_cond_wait(&buffer->waiting_filling_buffer, &buffer->mutex); + DBUG_PRINT("info", ("wait for writers done. buffer: #%u 0x%lx", + (uint) buffer->buffer_no, (ulong) buffer)); + } + DBUG_ASSERT(buffer->copy_to_buffer_in_progress == 0); + DBUG_VOID_RETURN; +} + + +/* + Initialize the cursor for a buffer + + SYNOPSIS + translog_cursor_init() + buffer The buffer + cursor It's cursor + buffer_no Number of buffer +*/ + +static void translog_cursor_init(struct st_buffer_cursor *cursor, + struct st_translog_buffer *buffer, + uint8 buffer_no) +{ + DBUG_ENTER("translog_cursor_init"); + cursor->ptr= buffer->buffer; + cursor->buffer= buffer; + cursor->buffer_no= buffer_no; + cursor->current_page_fill= 0; + cursor->chaser= (cursor != &log_descriptor.bc); + cursor->write_counter= 0; + cursor->previous_offset= 0; + cursor->protected= 0; + DBUG_VOID_RETURN; +} + + +/* + @brief Initialize buffer for the current file, and a cursor for this buffer. + + @param buffer The buffer + @param cursor It's cursor + @param buffer_no Number of buffer +*/ + +static void translog_start_buffer(struct st_translog_buffer *buffer, + struct st_buffer_cursor *cursor, + uint buffer_no) +{ + DBUG_ENTER("translog_start_buffer"); + DBUG_PRINT("enter", + ("Assign buffer: #%u (0x%lx) offset: 0x%lx(%lu)", + (uint) buffer->buffer_no, (ulong) buffer, + (ulong) LSN_OFFSET(log_descriptor.horizon), + (ulong) LSN_OFFSET(log_descriptor.horizon))); + DBUG_ASSERT(buffer_no == buffer->buffer_no); + buffer->prev_last_lsn= buffer->last_lsn= LSN_IMPOSSIBLE; + DBUG_PRINT("info", ("last_lsn and prev_last_lsn set to 0 buffer: 0x%lx", + (ulong) buffer)); + buffer->offset= log_descriptor.horizon; + buffer->next_buffer_offset= LSN_IMPOSSIBLE; + buffer->file= get_current_logfile(); + buffer->overlay= 0; + buffer->size= 0; + translog_cursor_init(cursor, buffer, buffer_no); + DBUG_PRINT("info", ("file: #%ld (%d) init cursor #%u: 0x%lx " + "chaser: %d Size: %lu (%lu)", + (long) (buffer->file ? buffer->file->number : 0), + (buffer->file ? buffer->file->handler.file : -1), + (uint) cursor->buffer->buffer_no, (ulong) cursor->buffer, + cursor->chaser, (ulong) cursor->buffer->size, + (ulong) (cursor->ptr - cursor->buffer->buffer))); + translog_check_cursor(cursor); + pthread_mutex_lock(&log_descriptor.dirty_buffer_mask_lock); + log_descriptor.dirty_buffer_mask|= (1 << buffer->buffer_no); + pthread_mutex_unlock(&log_descriptor.dirty_buffer_mask_lock); + + DBUG_VOID_RETURN; +} + + +/* + @brief Switch to the next buffer in a chain. + + @param horizon \ Pointers on current position in file and buffer + @param cursor / + @param new_file Also start new file + + @note + - loghandler should be locked + - after return new and old buffer still are locked + + @retval 0 OK + @retval 1 Error +*/ + +static my_bool translog_buffer_next(TRANSLOG_ADDRESS *horizon, + struct st_buffer_cursor *cursor, + my_bool new_file) +{ + uint old_buffer_no= cursor->buffer_no; + uint new_buffer_no= (old_buffer_no + 1) % TRANSLOG_BUFFERS_NO; + struct st_translog_buffer *new_buffer= log_descriptor.buffers + new_buffer_no; + my_bool chasing= cursor->chaser; + DBUG_ENTER("translog_buffer_next"); + + DBUG_PRINT("info", ("horizon: (%lu,0x%lx) chasing: %d", + LSN_IN_PARTS(log_descriptor.horizon), chasing)); + + DBUG_ASSERT(cmp_translog_addr(log_descriptor.horizon, *horizon) >= 0); + + translog_finish_page(horizon, cursor); + + if (!chasing) + { + translog_buffer_lock(new_buffer); +#ifndef DBUG_OFF + { + TRANSLOG_ADDRESS offset= new_buffer->offset; + TRANSLOG_FILE *file= new_buffer->file; + uint8 ver= new_buffer->ver; + translog_lock_assert_owner(); +#endif + translog_wait_for_buffer_free(new_buffer); +#ifndef DBUG_OFF + /* We keep the handler locked so nobody can start this new buffer */ + DBUG_ASSERT(offset == new_buffer->offset && new_buffer->file == NULL && + (file == NULL ? ver : (uint8)(ver + 1)) == new_buffer->ver); + } +#endif + } + else + DBUG_ASSERT(new_buffer->file != NULL); + + if (new_file) + { + /* move the horizon to the next file and its header page */ + (*horizon)+= LSN_ONE_FILE; + (*horizon)= LSN_REPLACE_OFFSET(*horizon, TRANSLOG_PAGE_SIZE); + if (!chasing && translog_create_new_file()) + { + DBUG_RETURN(1); + } + } + + /* prepare next page */ + if (chasing) + translog_cursor_init(cursor, new_buffer, new_buffer_no); + else + { + translog_lock_assert_owner(); + translog_start_buffer(new_buffer, cursor, new_buffer_no); + new_buffer->prev_buffer_offset= + log_descriptor.buffers[old_buffer_no].offset; + new_buffer->prev_last_lsn= + BUFFER_MAX_LSN(log_descriptor.buffers + old_buffer_no); + } + log_descriptor.buffers[old_buffer_no].next_buffer_offset= new_buffer->offset; + DBUG_PRINT("info", ("prev_last_lsn set to (%lu,0x%lx) buffer: 0x%lx", + LSN_IN_PARTS(new_buffer->prev_last_lsn), + (ulong) new_buffer)); + translog_new_page_header(horizon, cursor); + DBUG_RETURN(0); +} + + +/* + Sets max LSN sent to file, and address from which data is only in the buffer + + SYNOPSIS + translog_set_sent_to_disk() + buffer buffer which we have sent to disk + + TODO: use atomic operations if possible (64bit architectures?) +*/ + +static void translog_set_sent_to_disk(struct st_translog_buffer *buffer) +{ + LSN lsn= buffer->last_lsn; + TRANSLOG_ADDRESS in_buffers= buffer->next_buffer_offset; + + DBUG_ENTER("translog_set_sent_to_disk"); + pthread_mutex_lock(&log_descriptor.sent_to_disk_lock); + DBUG_PRINT("enter", ("lsn: (%lu,0x%lx) in_buffers: (%lu,0x%lx) " + "in_buffers_only: (%lu,0x%lx) start: (%lu,0x%lx) " + "sent_to_disk: (%lu,0x%lx)", + LSN_IN_PARTS(lsn), + LSN_IN_PARTS(in_buffers), + LSN_IN_PARTS(log_descriptor.log_start), + LSN_IN_PARTS(log_descriptor.in_buffers_only), + LSN_IN_PARTS(log_descriptor.sent_to_disk))); + /* + We write sequentially (first part of following assert) but we rewrite + the same page in case we started mysql and shut it down immediately + (second part of the following assert) + */ + DBUG_ASSERT(cmp_translog_addr(lsn, log_descriptor.sent_to_disk) >= 0 || + cmp_translog_addr(lsn, log_descriptor.log_start) < 0); + log_descriptor.sent_to_disk= lsn; + /* LSN_IMPOSSIBLE == 0 => it will work for very first time */ + if (cmp_translog_addr(in_buffers, log_descriptor.in_buffers_only) > 0) + { + log_descriptor.in_buffers_only= in_buffers; + DBUG_PRINT("info", ("set new in_buffers_only")); + } + pthread_mutex_unlock(&log_descriptor.sent_to_disk_lock); + DBUG_VOID_RETURN; +} + + +/* + Sets address from which data is only in the buffer + + SYNOPSIS + translog_set_only_in_buffers() + lsn LSN to assign + in_buffers to assign to in_buffers_only +*/ + +static void translog_set_only_in_buffers(TRANSLOG_ADDRESS in_buffers) +{ + DBUG_ENTER("translog_set_only_in_buffers"); + pthread_mutex_lock(&log_descriptor.sent_to_disk_lock); + DBUG_PRINT("enter", ("in_buffers: (%lu,0x%lx) " + "in_buffers_only: (%lu,0x%lx)", + LSN_IN_PARTS(in_buffers), + LSN_IN_PARTS(log_descriptor.in_buffers_only))); + /* LSN_IMPOSSIBLE == 0 => it will work for very first time */ + if (cmp_translog_addr(in_buffers, log_descriptor.in_buffers_only) > 0) + { + if (translog_status != TRANSLOG_OK) + DBUG_VOID_RETURN; + log_descriptor.in_buffers_only= in_buffers; + DBUG_PRINT("info", ("set new in_buffers_only")); + } + pthread_mutex_unlock(&log_descriptor.sent_to_disk_lock); + DBUG_VOID_RETURN; +} + + +/* + Gets address from which data is only in the buffer + + SYNOPSIS + translog_only_in_buffers() + + RETURN + address from which data is only in the buffer +*/ + +static TRANSLOG_ADDRESS translog_only_in_buffers() +{ + register TRANSLOG_ADDRESS addr; + DBUG_ENTER("translog_only_in_buffers"); + pthread_mutex_lock(&log_descriptor.sent_to_disk_lock); + addr= log_descriptor.in_buffers_only; + pthread_mutex_unlock(&log_descriptor.sent_to_disk_lock); + DBUG_RETURN(addr); +} + + +/* + Get max LSN sent to file + + SYNOPSIS + translog_get_sent_to_disk() + + RETURN + max LSN send to file +*/ + +static LSN translog_get_sent_to_disk() +{ + register LSN lsn; + DBUG_ENTER("translog_get_sent_to_disk"); + pthread_mutex_lock(&log_descriptor.sent_to_disk_lock); + lsn= log_descriptor.sent_to_disk; + DBUG_PRINT("info", ("sent to disk up to (%lu,0x%lx)", LSN_IN_PARTS(lsn))); + pthread_mutex_unlock(&log_descriptor.sent_to_disk_lock); + DBUG_RETURN(lsn); +} + + +/* + Get first chunk address on the given page + + SYNOPSIS + translog_get_first_chunk_offset() + page The page where to find first chunk + + RETURN + first chunk offset +*/ + +static my_bool translog_get_first_chunk_offset(uchar *page) +{ + DBUG_ENTER("translog_get_first_chunk_offset"); + DBUG_ASSERT(page[TRANSLOG_PAGE_FLAGS] < TRANSLOG_FLAGS_NUM); + DBUG_RETURN(page_overhead[page[TRANSLOG_PAGE_FLAGS]]); +} + + +/* + Write coded length of record + + SYNOPSIS + translog_write_variable_record_1group_code_len + dst Destination buffer pointer + length Length which should be coded + header_len Calculated total header length +*/ + +static void +translog_write_variable_record_1group_code_len(uchar *dst, + translog_size_t length, + uint16 header_len) +{ + switch (header_len) { + case 6: /* (5 + 1) */ + DBUG_ASSERT(length <= 250); + *dst= (uint8) length; + return; + case 8: /* (5 + 3) */ + DBUG_ASSERT(length <= 0xFFFF); + *dst= 251; + int2store(dst + 1, length); + return; + case 9: /* (5 + 4) */ + DBUG_ASSERT(length <= (ulong) 0xFFFFFF); + *dst= 252; + int3store(dst + 1, length); + return; + case 10: /* (5 + 5) */ + *dst= 253; + int4store(dst + 1, length); + return; + default: + DBUG_ASSERT(0); + } + return; +} + + +/* + Decode record data length and advance given pointer to the next field + + SYNOPSIS + translog_variable_record_1group_decode_len() + src The pointer to the pointer to the length beginning + + RETURN + decoded length +*/ + +static translog_size_t translog_variable_record_1group_decode_len(uchar **src) +{ + uint8 first= (uint8) (**src); + switch (first) { + case 251: + (*src)+= 3; + return (uint2korr((*src) - 2)); + case 252: + (*src)+= 4; + return (uint3korr((*src) - 3)); + case 253: + (*src)+= 5; + return (uint4korr((*src) - 4)); + case 254: + case 255: + DBUG_ASSERT(0); /* reserved for future use */ + return (0); + default: + (*src)++; + return (first); + } +} + + +/* + Get total length of this chunk (not only body) + + SYNOPSIS + translog_get_total_chunk_length() + page The page where chunk placed + offset Offset of the chunk on this place + + RETURN + total length of the chunk +*/ + +static uint16 translog_get_total_chunk_length(uchar *page, uint16 offset) +{ + DBUG_ENTER("translog_get_total_chunk_length"); + switch (page[offset] & TRANSLOG_CHUNK_TYPE) { + case TRANSLOG_CHUNK_LSN: + { + /* 0 chunk referred as LSN (head or tail) */ + translog_size_t rec_len; + uchar *start= page + offset; + uchar *ptr= start + 1 + 2; /* chunk type and short trid */ + uint16 chunk_len, header_len, page_rest; + DBUG_PRINT("info", ("TRANSLOG_CHUNK_LSN")); + rec_len= translog_variable_record_1group_decode_len(&ptr); + chunk_len= uint2korr(ptr); + header_len= (uint16) (ptr -start) + 2; + DBUG_PRINT("info", ("rec len: %lu chunk len: %u header len: %u", + (ulong) rec_len, (uint) chunk_len, (uint) header_len)); + if (chunk_len) + { + DBUG_PRINT("info", ("chunk len: %u + %u = %u", + (uint) header_len, (uint) chunk_len, + (uint) (chunk_len + header_len))); + DBUG_RETURN(chunk_len + header_len); + } + page_rest= TRANSLOG_PAGE_SIZE - offset; + DBUG_PRINT("info", ("page_rest %u", (uint) page_rest)); + if (rec_len + header_len < page_rest) + DBUG_RETURN(rec_len + header_len); + DBUG_RETURN(page_rest); + } + case TRANSLOG_CHUNK_FIXED: + { + uchar *ptr; + uint type= page[offset] & TRANSLOG_REC_TYPE; + uint length; + int i; + /* 1 (pseudo)fixed record (also LSN) */ + DBUG_PRINT("info", ("TRANSLOG_CHUNK_FIXED")); + DBUG_ASSERT(log_record_type_descriptor[type].rclass == + LOGRECTYPE_FIXEDLENGTH || + log_record_type_descriptor[type].rclass == + LOGRECTYPE_PSEUDOFIXEDLENGTH); + if (log_record_type_descriptor[type].rclass == LOGRECTYPE_FIXEDLENGTH) + { + DBUG_PRINT("info", + ("Fixed length: %u", + (uint) (log_record_type_descriptor[type].fixed_length + 3))); + DBUG_RETURN(log_record_type_descriptor[type].fixed_length + 3); + } + + ptr= page + offset + 3; /* first compressed LSN */ + length= log_record_type_descriptor[type].fixed_length + 3; + for (i= 0; i < log_record_type_descriptor[type].compressed_LSN; i++) + { + /* first 2 bits is length - 2 */ + uint len= (((uint8) (*ptr)) >> 6) + 2; + if (ptr[0] == 0 && ((uint8) ptr[1]) == 1) + len+= LSN_STORE_SIZE; /* case of full LSN storing */ + ptr+= len; + /* subtract saved bytes */ + length-= (LSN_STORE_SIZE - len); + } + DBUG_PRINT("info", ("Pseudo-fixed length: %u", length)); + DBUG_RETURN(length); + } + case TRANSLOG_CHUNK_NOHDR: + /* 2 no header chunk (till page end) */ + DBUG_PRINT("info", ("TRANSLOG_CHUNK_NOHDR length: %u", + (uint) (TRANSLOG_PAGE_SIZE - offset))); + DBUG_RETURN(TRANSLOG_PAGE_SIZE - offset); + case TRANSLOG_CHUNK_LNGTH: /* 3 chunk with chunk length */ + DBUG_PRINT("info", ("TRANSLOG_CHUNK_LNGTH")); + DBUG_ASSERT(TRANSLOG_PAGE_SIZE - offset >= 3); + DBUG_PRINT("info", ("length: %u", uint2korr(page + offset + 1) + 3)); + DBUG_RETURN(uint2korr(page + offset + 1) + 3); + default: + DBUG_ASSERT(0); + DBUG_RETURN(0); + } +} + +/* + @brief Waits previous buffer flush finish + + @param buffer buffer for check + + @retval 0 previous buffer flushed and this thread have to flush this one + @retval 1 previous buffer flushed and this buffer flushed by other thread too +*/ + +my_bool translog_prev_buffer_flush_wait(struct st_translog_buffer *buffer) +{ + TRANSLOG_ADDRESS offset= buffer->offset; + TRANSLOG_FILE *file= buffer->file; + uint8 ver= buffer->ver; + DBUG_ENTER("translog_prev_buffer_flush_wait"); + DBUG_PRINT("enter", ("buffer: 0x%lx #%u offset: (%lu,0x%lx) " + "prev sent: (%lu,0x%lx) prev offset: (%lu,0x%lx)", + (ulong) buffer, (uint) buffer->buffer_no, + LSN_IN_PARTS(buffer->offset), + LSN_IN_PARTS(buffer->prev_sent_to_disk), + LSN_IN_PARTS(buffer->prev_buffer_offset))); + translog_buffer_lock_assert_owner(buffer); + /* + if prev_sent_to_disk == LSN_IMPOSSIBLE then + prev_buffer_offset should be LSN_IMPOSSIBLE + because it means that this buffer was never used + */ + DBUG_ASSERT((buffer->prev_sent_to_disk == LSN_IMPOSSIBLE && + buffer->prev_buffer_offset == LSN_IMPOSSIBLE) || + buffer->prev_sent_to_disk != LSN_IMPOSSIBLE); + if (buffer->prev_buffer_offset != buffer->prev_sent_to_disk) + { + do { + pthread_cond_wait(&buffer->prev_sent_to_disk_cond, &buffer->mutex); + if (buffer->file != file || buffer->offset != offset || + buffer->ver != ver) + { + translog_buffer_unlock(buffer); + DBUG_RETURN(1); /* some the thread flushed the buffer already */ + } + } while(buffer->prev_buffer_offset != buffer->prev_sent_to_disk); + } + DBUG_RETURN(0); +} + + +/* + Flush given buffer + + SYNOPSIS + translog_buffer_flush() + buffer This buffer should be flushed + + RETURN + 0 OK + 1 Error +*/ + +static my_bool translog_buffer_flush(struct st_translog_buffer *buffer) +{ + uint32 i, pg; + TRANSLOG_ADDRESS offset= buffer->offset; + TRANSLOG_FILE *file= buffer->file; + uint8 ver= buffer->ver; + DBUG_ENTER("translog_buffer_flush"); + DBUG_PRINT("enter", + ("Buffer: #%u 0x%lx file: %d offset: (%lu,0x%lx) size: %lu", + (uint) buffer->buffer_no, (ulong) buffer, + buffer->file->handler.file, + LSN_IN_PARTS(buffer->offset), + (ulong) buffer->size)); + translog_buffer_lock_assert_owner(buffer); + + if (buffer->file == NULL) + DBUG_RETURN(0); + + translog_wait_for_writers(buffer); + + if (buffer->file != file || buffer->offset != offset || buffer->ver != ver) + DBUG_RETURN(0); /* some the thread flushed the buffer already */ + + if (buffer->is_closing_buffer) + { + /* some other flush in progress */ + translog_wait_for_closing(buffer); + } + + if (buffer->file != file || buffer->offset != offset || buffer->ver != ver) + DBUG_RETURN(0); /* some the thread flushed the buffer already */ + + if (buffer->overlay && translog_prev_buffer_flush_wait(buffer)) + DBUG_RETURN(0); /* some the thread flushed the buffer already */ + + /* + Send page by page in the pagecache what we are going to write on the + disk + */ + file= buffer->file; + for (i= 0, pg= LSN_OFFSET(buffer->offset) / TRANSLOG_PAGE_SIZE; + i < buffer->size; + i+= TRANSLOG_PAGE_SIZE, pg++) + { + TRANSLOG_ADDRESS addr= (buffer->offset + i); + TRANSLOG_VALIDATOR_DATA data; + DBUG_PRINT("info", ("send log form %lu till %lu address: (%lu,0x%lx) " + "page #: %lu buffer size: %lu buffer: 0x%lx", + (ulong) i, (ulong) (i + TRANSLOG_PAGE_SIZE), + LSN_IN_PARTS(addr), (ulong) pg, (ulong) buffer->size, + (ulong) buffer)); + data.addr= &addr; + DBUG_ASSERT(log_descriptor.pagecache->block_size == TRANSLOG_PAGE_SIZE); + DBUG_ASSERT(i + TRANSLOG_PAGE_SIZE <= buffer->size); + if (translog_status != TRANSLOG_OK && translog_status != TRANSLOG_SHUTDOWN) + DBUG_RETURN(1); + if (pagecache_inject(log_descriptor.pagecache, + &file->handler, pg, 3, + buffer->buffer + i, + PAGECACHE_PLAIN_PAGE, + PAGECACHE_LOCK_LEFT_UNLOCKED, + PAGECACHE_PIN_LEFT_UNPINNED, 0, + LSN_IMPOSSIBLE)) + { + DBUG_PRINT("error", + ("Can't write page (%lu,0x%lx) to pagecache, error: %d", + (ulong) buffer->file->number, + (ulong) (LSN_OFFSET(buffer->offset)+ i), + my_errno)); + translog_stop_writing(); + DBUG_RETURN(1); + } + } + file->is_sync= 0; + if (my_pwrite(file->handler.file, buffer->buffer, + buffer->size, LSN_OFFSET(buffer->offset), + log_write_flags)) + { + DBUG_PRINT("error", ("Can't write buffer (%lu,0x%lx) size %lu " + "to the disk (%d)", + (ulong) file->handler.file, + (ulong) LSN_OFFSET(buffer->offset), + (ulong) buffer->size, errno)); + translog_stop_writing(); + DBUG_RETURN(1); + } + /* + Dropping the flag in such way can make false alarm: signalling than the + file in not sync when it is sync, but the situation is quite rare and + protections with mutexes give much more overhead to the whole engine + */ + file->is_sync= 0; + + if (LSN_OFFSET(buffer->last_lsn) != 0) /* if buffer->last_lsn is set */ + { + if (translog_prev_buffer_flush_wait(buffer)) + DBUG_RETURN(0); /* some the thread flushed the buffer already */ + translog_set_sent_to_disk(buffer); + } + else + translog_set_only_in_buffers(buffer->next_buffer_offset); + + /* say to next buffer that we are finished */ + { + struct st_translog_buffer *next_buffer= + log_descriptor.buffers + ((buffer->buffer_no + 1) % TRANSLOG_BUFFERS_NO); + if (likely(translog_status == TRANSLOG_OK)){ + translog_buffer_lock(next_buffer); + next_buffer->prev_sent_to_disk= buffer->offset; + translog_buffer_unlock(next_buffer); + pthread_cond_broadcast(&next_buffer->prev_sent_to_disk_cond); + } + else + { + /* + It is shutdown => + 1) there is only one thread + 2) mutexes of other buffers can be destroyed => we can't use them + */ + next_buffer->prev_sent_to_disk= buffer->offset; + } + } + /* Free buffer */ + buffer->file= NULL; + buffer->overlay= 0; + buffer->ver++; + pthread_mutex_lock(&log_descriptor.dirty_buffer_mask_lock); + log_descriptor.dirty_buffer_mask&= ~(1 << buffer->buffer_no); + pthread_mutex_unlock(&log_descriptor.dirty_buffer_mask_lock); + pthread_cond_broadcast(&buffer->waiting_filling_buffer); + DBUG_RETURN(0); +} + + +/* + Recover page with sector protection (wipe out failed chunks) + + SYNOPSYS + translog_recover_page_up_to_sector() + page reference on the page + offset offset of failed sector + + RETURN + 0 OK + 1 Error +*/ + +static my_bool translog_recover_page_up_to_sector(uchar *page, uint16 offset) +{ + uint16 chunk_offset= translog_get_first_chunk_offset(page), valid_chunk_end; + DBUG_ENTER("translog_recover_page_up_to_sector"); + DBUG_PRINT("enter", ("offset: %u first chunk: %u", + (uint) offset, (uint) chunk_offset)); + + while (page[chunk_offset] != TRANSLOG_FILLER && chunk_offset < offset) + { + uint16 chunk_length; + if ((chunk_length= + translog_get_total_chunk_length(page, chunk_offset)) == 0) + { + DBUG_PRINT("error", ("cant get chunk length (offset %u)", + (uint) chunk_offset)); + DBUG_RETURN(1); + } + DBUG_PRINT("info", ("chunk: offset: %u length %u", + (uint) chunk_offset, (uint) chunk_length)); + if (((ulong) chunk_offset) + ((ulong) chunk_length) > TRANSLOG_PAGE_SIZE) + { + DBUG_PRINT("error", ("damaged chunk (offset %u) in trusted area", + (uint) chunk_offset)); + DBUG_RETURN(1); + } + chunk_offset+= chunk_length; + } + + valid_chunk_end= chunk_offset; + /* end of trusted area - sector parsing */ + while (page[chunk_offset] != TRANSLOG_FILLER) + { + uint16 chunk_length; + if ((chunk_length= + translog_get_total_chunk_length(page, chunk_offset)) == 0) + break; + + DBUG_PRINT("info", ("chunk: offset: %u length %u", + (uint) chunk_offset, (uint) chunk_length)); + if (((ulong) chunk_offset) + ((ulong) chunk_length) > + (uint) (offset + DISK_DRIVE_SECTOR_SIZE)) + break; + + chunk_offset+= chunk_length; + valid_chunk_end= chunk_offset; + } + DBUG_PRINT("info", ("valid chunk end offset: %u", (uint) valid_chunk_end)); + + memset(page + valid_chunk_end, TRANSLOG_FILLER, + TRANSLOG_PAGE_SIZE - valid_chunk_end); + + DBUG_RETURN(0); +} + + +/** + @brief Dummy write callback. +*/ + +static my_bool +translog_dummy_callback(uchar *page __attribute__((unused)), + pgcache_page_no_t page_no __attribute__((unused)), + uchar* data_ptr __attribute__((unused))) +{ + return 0; +} + + +/** + @brief Checks and removes sector protection. + + @param page reference on the page content. + @param file transaction log descriptor. + + @retvat 0 OK + @retval 1 Error +*/ + +static my_bool +translog_check_sector_protection(uchar *page, TRANSLOG_FILE *file) +{ + uint i, offset; + uchar *table= page + page_overhead[page[TRANSLOG_PAGE_FLAGS]] - + TRANSLOG_PAGE_SIZE / DISK_DRIVE_SECTOR_SIZE; + uint8 current= table[0]; + DBUG_ENTER("translog_check_sector_protection"); + + for (i= 1, offset= DISK_DRIVE_SECTOR_SIZE; + i < TRANSLOG_PAGE_SIZE / DISK_DRIVE_SECTOR_SIZE; + i++, offset+= DISK_DRIVE_SECTOR_SIZE) + { + /* + TODO: add chunk counting for "suspecting" sectors (difference is + more than 1-2), if difference more then present chunks then it is + the problem. + */ + uint8 test= page[offset]; + DBUG_PRINT("info", ("sector: #%u offset: %u current: %lx " + "read: 0x%x stored: 0x%x%x", + i, offset, (ulong) current, + (uint) uint2korr(page + offset), (uint) table[i], + (uint) table[i + 1])); + /* + 3 is minimal possible record length. So we can have "distance" + between 2 sectors value more then DISK_DRIVE_SECTOR_SIZE / 3 + only if it is old value, i.e. the sector was not written. + */ + if (((test < current) && + ((uint)(0xFFL - current + test) > DISK_DRIVE_SECTOR_SIZE / 3)) || + ((test >= current) && + ((uint)(test - current) > DISK_DRIVE_SECTOR_SIZE / 3))) + { + if (translog_recover_page_up_to_sector(page, offset)) + DBUG_RETURN(1); + file->was_recovered= 1; + DBUG_RETURN(0); + } + + /* Restore value on the page */ + page[offset]= table[i]; + current= test; + DBUG_PRINT("info", ("sector: #%u offset: %u current: %lx " + "read: 0x%x stored: 0x%x", + i, offset, (ulong) current, + (uint) page[offset], (uint) table[i])); + } + DBUG_RETURN(0); +} + + +/** + @brief Log page validator (read callback) + + @param page The page data to check + @param page_no The page number (<offset>/<page length>) + @param data_ptr Read callback data pointer (pointer to TRANSLOG_FILE) + + @todo: add turning loghandler to read-only mode after merging with + that patch. + + @retval 0 OK + @retval 1 Error +*/ + +static my_bool translog_page_validator(uchar *page, + pgcache_page_no_t page_no, + uchar* data_ptr) +{ + uint this_page_page_overhead; + uint flags; + uchar *page_pos; + TRANSLOG_FILE *data= (TRANSLOG_FILE *) data_ptr; +#ifndef DBUG_OFF + pgcache_page_no_t offset= page_no * TRANSLOG_PAGE_SIZE; +#endif + DBUG_ENTER("translog_page_validator"); + + data->was_recovered= 0; + + if (uint3korr(page) != page_no || + uint3korr(page + 3) != data->number) + { + DBUG_PRINT("error", ("Page (%lu,0x%lx): " + "page address written in the page is incorrect: " + "File %lu instead of %lu or page %lu instead of %lu", + (ulong) data->number, (ulong) offset, + (ulong) uint3korr(page + 3), (ulong) data->number, + (ulong) uint3korr(page), + (ulong) page_no)); + DBUG_RETURN(1); + } + flags= (uint)(page[TRANSLOG_PAGE_FLAGS]); + this_page_page_overhead= page_overhead[flags]; + if (flags & ~(TRANSLOG_PAGE_CRC | TRANSLOG_SECTOR_PROTECTION | + TRANSLOG_RECORD_CRC)) + { + DBUG_PRINT("error", ("Page (%lu,0x%lx): " + "Garbage in the page flags field detected : %x", + (ulong) data->number, (ulong) offset, + (uint) flags)); + DBUG_RETURN(1); + } + page_pos= page + (3 + 3 + 1); + if (flags & TRANSLOG_PAGE_CRC) + { + uint32 crc= translog_crc(page + this_page_page_overhead, + TRANSLOG_PAGE_SIZE - + this_page_page_overhead); + if (crc != uint4korr(page_pos)) + { + DBUG_PRINT("error", ("Page (%lu,0x%lx): " + "CRC mismatch: calculated: %lx on the page %lx", + (ulong) data->number, (ulong) offset, + (ulong) crc, (ulong) uint4korr(page_pos))); + DBUG_RETURN(1); + } + page_pos+= CRC_SIZE; /* Skip crc */ + } + if (flags & TRANSLOG_SECTOR_PROTECTION && + translog_check_sector_protection(page, data)) + { + DBUG_RETURN(1); + } + DBUG_RETURN(0); +} + + +/** + @brief Locks the loghandler. +*/ + +void translog_lock() +{ + uint8 current_buffer; + DBUG_ENTER("translog_lock"); + + /* + Locking the loghandler mean locking current buffer, but it can change + during locking, so we should check it + */ + for (;;) + { + /* + log_descriptor.bc.buffer_no is only one byte so its reading is + an atomic operation + */ + current_buffer= log_descriptor.bc.buffer_no; + translog_buffer_lock(log_descriptor.buffers + current_buffer); + if (log_descriptor.bc.buffer_no == current_buffer) + break; + translog_buffer_unlock(log_descriptor.buffers + current_buffer); + } + DBUG_VOID_RETURN; +} + + +/* + Unlock the loghandler + + SYNOPSIS + translog_unlock() + + RETURN + 0 OK + 1 Error +*/ + +void translog_unlock() +{ + translog_buffer_unlock(log_descriptor.bc.buffer); +} + + +/** + @brief Get log page by file number and offset of the beginning of the page + + @param data validator data, which contains the page address + @param buffer buffer for page placing + (might not be used in some cache implementations) + @param direct_link if it is not NULL then caller can accept direct + link to the page cache + + @retval NULL Error + @retval # pointer to the page cache which should be used to read this page +*/ + +static uchar *translog_get_page(TRANSLOG_VALIDATOR_DATA *data, uchar *buffer, + PAGECACHE_BLOCK_LINK **direct_link) +{ + TRANSLOG_ADDRESS addr= *(data->addr), in_buffers; + uint32 file_no= LSN_FILE_NO(addr); + TRANSLOG_FILE *file; + DBUG_ENTER("translog_get_page"); + DBUG_PRINT("enter", ("File: %lu Offset: %lu(0x%lx)", + (ulong) file_no, + (ulong) LSN_OFFSET(addr), + (ulong) LSN_OFFSET(addr))); + + /* it is really page address */ + DBUG_ASSERT(LSN_OFFSET(addr) % TRANSLOG_PAGE_SIZE == 0); + if (direct_link) + *direct_link= NULL; + +restart: + + in_buffers= translog_only_in_buffers(); + DBUG_PRINT("info", ("in_buffers: (%lu,0x%lx)", + LSN_IN_PARTS(in_buffers))); + if (in_buffers != LSN_IMPOSSIBLE && + cmp_translog_addr(addr, in_buffers) >= 0) + { + translog_lock(); + DBUG_ASSERT(cmp_translog_addr(addr, log_descriptor.horizon) < 0); + /* recheck with locked loghandler */ + in_buffers= translog_only_in_buffers(); + if (cmp_translog_addr(addr, in_buffers) >= 0) + { + uint16 buffer_no= log_descriptor.bc.buffer_no; +#ifndef DBUG_OFF + uint16 buffer_start= buffer_no; +#endif + struct st_translog_buffer *buffer_unlock= log_descriptor.bc.buffer; + struct st_translog_buffer *curr_buffer= log_descriptor.bc.buffer; + for (;;) + { + /* + if the page is in the buffer and it is the last version of the + page (in case of division the page by buffer flush) + */ + if (curr_buffer->file != NULL && + cmp_translog_addr(addr, curr_buffer->offset) >= 0 && + cmp_translog_addr(addr, + (curr_buffer->next_buffer_offset ? + curr_buffer->next_buffer_offset: + curr_buffer->offset + curr_buffer->size)) < 0) + { + TRANSLOG_ADDRESS offset= curr_buffer->offset; + TRANSLOG_FILE *fl= curr_buffer->file; + uchar *from, *table= NULL; + int is_last_unfinished_page; + uint last_protected_sector= 0; + TRANSLOG_FILE file_copy; + uint8 ver= curr_buffer->ver; + translog_wait_for_writers(curr_buffer); + if (offset != curr_buffer->offset || fl != curr_buffer->file || + ver != curr_buffer->ver) + { + DBUG_ASSERT(buffer_unlock == curr_buffer); + translog_buffer_unlock(buffer_unlock); + goto restart; + } + DBUG_ASSERT(LSN_FILE_NO(addr) == LSN_FILE_NO(curr_buffer->offset)); + from= curr_buffer->buffer + (addr - curr_buffer->offset); + memcpy(buffer, from, TRANSLOG_PAGE_SIZE); + /* + We can use copy then in translog_page_validator() because it + do not put it permanently somewhere. + We have to use copy because after releasing log lock we can't + guaranty that the file still be present (in real life it will be + present but theoretically possible that it will be released + already from last files cache); + */ + file_copy= *(curr_buffer->file); + file_copy.handler.callback_data= (uchar*) &file_copy; + is_last_unfinished_page= ((log_descriptor.bc.buffer == + curr_buffer) && + (log_descriptor.bc.ptr >= from) && + (log_descriptor.bc.ptr < + from + TRANSLOG_PAGE_SIZE)); + if (is_last_unfinished_page && + (buffer[TRANSLOG_PAGE_FLAGS] & TRANSLOG_SECTOR_PROTECTION)) + { + last_protected_sector= ((log_descriptor.bc.previous_offset - 1) / + DISK_DRIVE_SECTOR_SIZE); + table= buffer + log_descriptor.page_overhead - + TRANSLOG_PAGE_SIZE / DISK_DRIVE_SECTOR_SIZE; + } + + DBUG_ASSERT(buffer_unlock == curr_buffer); + translog_buffer_unlock(buffer_unlock); + if (is_last_unfinished_page) + { + uint i; + /* + This is last unfinished page => we should not check CRC and + remove only that protection which already installed (no need + to check it) + + We do not check the flag of sector protection, because if + (buffer[TRANSLOG_PAGE_FLAGS] & TRANSLOG_SECTOR_PROTECTION) is + not set then last_protected_sector will be 0 so following loop + will be never executed + */ + DBUG_PRINT("info", ("This is last unfinished page, " + "last protected sector %u", + last_protected_sector)); + for (i= 1; i <= last_protected_sector; i++) + { + uint offset= i * DISK_DRIVE_SECTOR_SIZE; + DBUG_PRINT("info", ("Sector %u: 0x%02x <- 0x%02x", + i, buffer[offset], + table[i])); + buffer[offset]= table[i]; + } + } + else + { + /* + This IF should be true because we use in-memory data which + supposed to be correct. + */ + if (translog_page_validator(buffer, + LSN_OFFSET(addr) / TRANSLOG_PAGE_SIZE, + (uchar*) &file_copy)) + { + DBUG_ASSERT(0); + buffer= NULL; + } + } + DBUG_RETURN(buffer); + } + buffer_no= (buffer_no + 1) % TRANSLOG_BUFFERS_NO; + curr_buffer= log_descriptor.buffers + buffer_no; + translog_buffer_lock(curr_buffer); + translog_buffer_unlock(buffer_unlock); + buffer_unlock= curr_buffer; + /* we can't make a full circle */ + DBUG_ASSERT(buffer_start != buffer_no); + } + } + translog_unlock(); + } + file= get_logfile_by_number(file_no); + DBUG_ASSERT(file != NULL); + buffer= pagecache_read(log_descriptor.pagecache, &file->handler, + LSN_OFFSET(addr) / TRANSLOG_PAGE_SIZE, + 3, (direct_link ? NULL : buffer), + PAGECACHE_PLAIN_PAGE, + (direct_link ? + PAGECACHE_LOCK_READ : + PAGECACHE_LOCK_LEFT_UNLOCKED), + direct_link); + DBUG_PRINT("info", ("Direct link is assigned to : 0x%lx * 0x%lx", + (ulong) direct_link, + (ulong)(direct_link ? *direct_link : NULL))); + data->was_recovered= file->was_recovered; + DBUG_RETURN(buffer); +} + + +/** + @brief free direct log page link + + @param direct_link the direct log page link to be freed + +*/ + +static void translog_free_link(PAGECACHE_BLOCK_LINK *direct_link) +{ + DBUG_ENTER("translog_free_link"); + DBUG_PRINT("info", ("Direct link: 0x%lx", + (ulong) direct_link)); + if (direct_link) + pagecache_unlock_by_link(log_descriptor.pagecache, direct_link, + PAGECACHE_LOCK_READ_UNLOCK, PAGECACHE_UNPIN, + LSN_IMPOSSIBLE, LSN_IMPOSSIBLE, 0, FALSE); + DBUG_VOID_RETURN; +} + + +/** + @brief Finds last full page of the given log file. + + @param addr address structure to fill with data, which contain + file number of the log file + @param last_page_ok Result of the check whether last page OK. + (for now only we check only that file length + divisible on page length). + @param no_errors suppress messages about non-critical errors + + @retval 0 OK + @retval 1 Error +*/ + +static my_bool translog_get_last_page_addr(TRANSLOG_ADDRESS *addr, + my_bool *last_page_ok, + my_bool no_errors) +{ + char path[FN_REFLEN]; + uint32 rec_offset; + my_off_t file_size; + uint32 file_no= LSN_FILE_NO(*addr); + TRANSLOG_FILE *file; +#ifndef DBUG_OFF + char buff[21]; +#endif + DBUG_ENTER("translog_get_last_page_addr"); + + if (likely((file= get_logfile_by_number(file_no)) != NULL)) + { + /* + This function used only during initialization of loghandler or in + scanner (which mean we need read that part of the log), so the + requested log file have to be opened and can't be freed after + returning pointer on it (file_size). + */ + file_size= my_seek(file->handler.file, 0, SEEK_END, MYF(0)); + } + else + { + /* + This branch is used only during very early initialization + when files are not opened. + */ + File fd; + if ((fd= my_open(translog_filename_by_fileno(file_no, path), + O_RDONLY, (no_errors ? MYF(0) : MYF(MY_WME)))) < 0) + { + my_errno= errno; + DBUG_PRINT("error", ("Error %d during opening file #%d", + errno, file_no)); + DBUG_RETURN(1); + } + file_size= my_seek(fd, 0, SEEK_END, MYF(0)); + my_close(fd, MYF(0)); + } + DBUG_PRINT("info", ("File size: %s", llstr(file_size, buff))); + if (file_size == MY_FILEPOS_ERROR) + DBUG_RETURN(1); + DBUG_ASSERT(file_size < ULL(0xffffffff)); + if (((uint32)file_size) > TRANSLOG_PAGE_SIZE) + { + rec_offset= (((((uint32)file_size) / TRANSLOG_PAGE_SIZE) - 1) * + TRANSLOG_PAGE_SIZE); + *last_page_ok= (((uint32)file_size) == rec_offset + TRANSLOG_PAGE_SIZE); + } + else + { + *last_page_ok= 0; + rec_offset= 0; + } + *addr= MAKE_LSN(file_no, rec_offset); + DBUG_PRINT("info", ("Last page: 0x%lx ok: %d", (ulong) rec_offset, + *last_page_ok)); + DBUG_RETURN(0); +} + + +/** + @brief Get number bytes for record length storing + + @param length Record length which will be encoded + + @return 1,3,4,5 - number of bytes to store given length +*/ + +static uint translog_variable_record_length_bytes(translog_size_t length) +{ + if (length < 250) + return 1; + if (length < 0xFFFF) + return 3; + if (length < (ulong) 0xFFFFFF) + return 4; + return 5; +} + + +/** + @brief Gets header of this chunk. + + @param chunk The pointer to the chunk beginning + + @retval # total length of the chunk + @retval 0 Error +*/ + +static uint16 translog_get_chunk_header_length(uchar *chunk) +{ + DBUG_ENTER("translog_get_chunk_header_length"); + switch (*chunk & TRANSLOG_CHUNK_TYPE) { + case TRANSLOG_CHUNK_LSN: + { + /* 0 chunk referred as LSN (head or tail) */ + translog_size_t rec_len; + uchar *start= chunk; + uchar *ptr= start + 1 + 2; + uint16 chunk_len, header_len; + DBUG_PRINT("info", ("TRANSLOG_CHUNK_LSN")); + rec_len= translog_variable_record_1group_decode_len(&ptr); + chunk_len= uint2korr(ptr); + header_len= (uint16) (ptr - start) +2; + DBUG_PRINT("info", ("rec len: %lu chunk len: %u header len: %u", + (ulong) rec_len, (uint) chunk_len, (uint) header_len)); + if (chunk_len) + { + /* TODO: fine header end */ + /* + The last chunk of multi-group record can be base for it header + calculation (we skip to the first group to read the header) so if we + stuck here something is wrong. + */ + DBUG_ASSERT(0); + DBUG_RETURN(0); /* Keep compiler happy */ + } + DBUG_RETURN(header_len); + } + case TRANSLOG_CHUNK_FIXED: + { + /* 1 (pseudo)fixed record (also LSN) */ + DBUG_PRINT("info", ("TRANSLOG_CHUNK_FIXED = 3")); + DBUG_RETURN(3); + } + case TRANSLOG_CHUNK_NOHDR: + /* 2 no header chunk (till page end) */ + DBUG_PRINT("info", ("TRANSLOG_CHUNK_NOHDR = 1")); + DBUG_RETURN(1); + break; + case TRANSLOG_CHUNK_LNGTH: + /* 3 chunk with chunk length */ + DBUG_PRINT("info", ("TRANSLOG_CHUNK_LNGTH = 3")); + DBUG_RETURN(3); + break; + default: + DBUG_ASSERT(0); + DBUG_RETURN(0); /* Keep compiler happy */ + } +} + + +/** + @brief Truncate the log to the given address. Used during the startup if the + end of log if corrupted. + + @param addr new horizon + + @retval 0 OK + @retval 1 Error +*/ + +static my_bool translog_truncate_log(TRANSLOG_ADDRESS addr) +{ + uchar *page; + TRANSLOG_ADDRESS current_page; + uint32 next_page_offset, page_rest; + uint32 i; + File fd; + TRANSLOG_VALIDATOR_DATA data; + char path[FN_REFLEN]; + uchar page_buff[TRANSLOG_PAGE_SIZE]; + DBUG_ENTER("translog_truncate_log"); + /* TODO: write warning to the client */ + DBUG_PRINT("warning", ("removing all records from (%lu,0x%lx) " + "till (%lu,0x%lx)", + LSN_IN_PARTS(addr), + LSN_IN_PARTS(log_descriptor.horizon))); + DBUG_ASSERT(cmp_translog_addr(addr, log_descriptor.horizon) < 0); + /* remove files between the address and horizon */ + for (i= LSN_FILE_NO(addr) + 1; i <= LSN_FILE_NO(log_descriptor.horizon); i++) + if (my_delete(translog_filename_by_fileno(i, path), MYF(MY_WME))) + { + translog_unlock(); + DBUG_RETURN(1); + } + + /* truncate the last file up to the last page */ + next_page_offset= LSN_OFFSET(addr); + next_page_offset= (next_page_offset - + ((next_page_offset - 1) % TRANSLOG_PAGE_SIZE + 1) + + TRANSLOG_PAGE_SIZE); + page_rest= next_page_offset - LSN_OFFSET(addr); + memset(page_buff, TRANSLOG_FILLER, page_rest); + if ((fd= open_logfile_by_number_no_cache(LSN_FILE_NO(addr))) < 0 || + ((my_chsize(fd, next_page_offset, TRANSLOG_FILLER, MYF(MY_WME)) || + (page_rest && my_pwrite(fd, page_buff, page_rest, LSN_OFFSET(addr), + log_write_flags)) || + my_sync(fd, MYF(MY_WME))) | + my_close(fd, MYF(MY_WME))) || + (sync_log_dir >= TRANSLOG_SYNC_DIR_ALWAYS && + sync_dir(log_descriptor.directory_fd, MYF(MY_WME | MY_IGNORE_BADFD)))) + DBUG_RETURN(1); + + /* fix the horizon */ + log_descriptor.horizon= addr; + /* fix the buffer data */ + current_page= MAKE_LSN(LSN_FILE_NO(addr), (next_page_offset - + TRANSLOG_PAGE_SIZE)); + data.addr= ¤t_page; + if ((page= translog_get_page(&data, log_descriptor.buffers->buffer, NULL)) == + NULL) + DBUG_RETURN(1); + if (page != log_descriptor.buffers->buffer) + memcpy(log_descriptor.buffers->buffer, page, TRANSLOG_PAGE_SIZE); + log_descriptor.bc.buffer->offset= current_page; + log_descriptor.bc.buffer->size= LSN_OFFSET(addr) - LSN_OFFSET(current_page); + log_descriptor.bc.ptr= + log_descriptor.buffers->buffer + log_descriptor.bc.buffer->size; + log_descriptor.bc.current_page_fill= log_descriptor.bc.buffer->size; + DBUG_RETURN(0); +} + + +/** + Applies function 'callback' to all files (in a directory) which + name looks like a log's name (maria_log.[0-9]{7}). + If 'callback' returns TRUE this interrupts the walk and returns + TRUE. Otherwise FALSE is returned after processing all log files. + It cannot just use log_descriptor.directory because that may not yet have + been initialized. + + @param directory directory to scan + @param callback function to apply; is passed directory and base + name of found file +*/ + +my_bool translog_walk_filenames(const char *directory, + my_bool (*callback)(const char *, + const char *)) +{ + MY_DIR *dirp; + uint i; + my_bool rc= FALSE; + + /* Finds and removes transaction log files */ + if (!(dirp = my_dir(directory, MYF(MY_DONT_SORT)))) + return FALSE; + + for (i= 0; i < dirp->number_off_files; i++) + { + char *file= dirp->dir_entry[i].name; + if (strncmp(file, "maria_log.", 10) == 0 && + file[10] >= '0' && file[10] <= '9' && + file[11] >= '0' && file[11] <= '9' && + file[12] >= '0' && file[12] <= '9' && + file[13] >= '0' && file[13] <= '9' && + file[14] >= '0' && file[14] <= '9' && + file[15] >= '0' && file[15] <= '9' && + file[16] >= '0' && file[16] <= '9' && + file[17] >= '0' && file[17] <= '9' && + file[18] == '\0' && (*callback)(directory, file)) + { + rc= TRUE; + break; + } + } + my_dirend(dirp); + return rc; +} + + +/** + @brief Fills table of dependence length of page header from page flags +*/ + +static void translog_fill_overhead_table() +{ + uint i; + for (i= 0; i < TRANSLOG_FLAGS_NUM; i++) + { + page_overhead[i]= 7; + if (i & TRANSLOG_PAGE_CRC) + page_overhead[i]+= CRC_SIZE; + if (i & TRANSLOG_SECTOR_PROTECTION) + page_overhead[i]+= TRANSLOG_PAGE_SIZE / + DISK_DRIVE_SECTOR_SIZE; + } +} + + +/** + Callback to find first log in directory. +*/ + +static my_bool translog_callback_search_first(const char *directory + __attribute__((unused)), + const char *filename + __attribute__((unused))) +{ + return TRUE; +} + + +/** + @brief Checks that chunk is LSN one + + @param type type of the chunk + + @retval 1 the chunk is LNS + @retval 0 the chunk is not LSN +*/ + +static my_bool translog_is_LSN_chunk(uchar type) +{ + DBUG_ENTER("translog_is_LSN_chunk"); + DBUG_PRINT("info", ("byte: %x chunk type: %u record type: %u", + type, type >> 6, type & TRANSLOG_REC_TYPE)); + DBUG_RETURN(((type & TRANSLOG_CHUNK_TYPE) == TRANSLOG_CHUNK_FIXED) || + (((type & TRANSLOG_CHUNK_TYPE) == TRANSLOG_CHUNK_LSN) && + ((type & TRANSLOG_REC_TYPE)) != TRANSLOG_CHUNK_0_CONT)); +} + + +/** + @brief Initialize transaction log + + @param directory Directory where log files are put + @param log_file_max_size max size of one log size (for new logs creation) + @param server_version version of MySQL server (MYSQL_VERSION_ID) + @param server_id server ID (replication & Co) + @param pagecache Page cache for the log reads + @param flags flags (TRANSLOG_PAGE_CRC, TRANSLOG_SECTOR_PROTECTION + TRANSLOG_RECORD_CRC) + @param read_only Put transaction log in read-only mode + @param init_table_func function to initialize record descriptors table + @param no_errors suppress messages about non-critical errors + + @todo + Free used resources in case of error. + + @retval 0 OK + @retval 1 Error +*/ + +my_bool translog_init_with_table(const char *directory, + uint32 log_file_max_size, + uint32 server_version, + uint32 server_id, PAGECACHE *pagecache, + uint flags, my_bool readonly, + void (*init_table_func)(), + my_bool no_errors) +{ + int i; + int old_log_was_recovered= 0, logs_found= 0; + uint old_flags= flags; + uint32 start_file_num= 1; + TRANSLOG_ADDRESS sure_page, last_page, last_valid_page, checkpoint_lsn; + my_bool version_changed= 0; + DBUG_ENTER("translog_init_with_table"); + + id_to_share= NULL; + log_descriptor.directory_fd= -1; + log_descriptor.is_everything_flushed= 1; + log_descriptor.flush_in_progress= 0; + log_descriptor.flush_no= 0; + log_descriptor.next_pass_max_lsn= LSN_IMPOSSIBLE; + + (*init_table_func)(); + compile_time_assert(sizeof(log_descriptor.dirty_buffer_mask) * 8 >= + TRANSLOG_BUFFERS_NO); + log_descriptor.dirty_buffer_mask= 0; + if (readonly) + log_descriptor.open_flags= O_BINARY | O_RDONLY; + else + log_descriptor.open_flags= O_BINARY | O_RDWR; + if (pthread_mutex_init(&log_descriptor.sent_to_disk_lock, + MY_MUTEX_INIT_FAST) || + pthread_mutex_init(&log_descriptor.file_header_lock, + MY_MUTEX_INIT_FAST) || + pthread_mutex_init(&log_descriptor.unfinished_files_lock, + MY_MUTEX_INIT_FAST) || + pthread_mutex_init(&log_descriptor.purger_lock, + MY_MUTEX_INIT_FAST) || + pthread_mutex_init(&log_descriptor.log_flush_lock, + MY_MUTEX_INIT_FAST) || + pthread_mutex_init(&log_descriptor.dirty_buffer_mask_lock, + MY_MUTEX_INIT_FAST) || + pthread_cond_init(&log_descriptor.log_flush_cond, 0) || + my_rwlock_init(&log_descriptor.open_files_lock, + NULL) || + my_init_dynamic_array(&log_descriptor.open_files, + sizeof(TRANSLOG_FILE*), 10, 10) || + my_init_dynamic_array(&log_descriptor.unfinished_files, + sizeof(struct st_file_counter), + 10, 10)) + goto err; + log_descriptor.min_need_file= 0; + log_descriptor.min_file_number= 0; + log_descriptor.last_lsn_checked= LSN_IMPOSSIBLE; + + /* Directory to store files */ + unpack_dirname(log_descriptor.directory, directory); +#ifndef __WIN__ + if ((log_descriptor.directory_fd= my_open(log_descriptor.directory, + O_RDONLY, MYF(MY_WME))) < 0) + { + my_errno= errno; + DBUG_PRINT("error", ("Error %d during opening directory '%s'", + errno, log_descriptor.directory)); + goto err; + } +#endif + log_descriptor.in_buffers_only= LSN_IMPOSSIBLE; + DBUG_ASSERT(log_file_max_size % TRANSLOG_PAGE_SIZE == 0 && + log_file_max_size >= TRANSLOG_MIN_FILE_SIZE); + /* max size of one log size (for new logs creation) */ + log_file_size= log_descriptor.log_file_max_size= + log_file_max_size; + /* server version */ + log_descriptor.server_version= server_version; + /* server ID */ + log_descriptor.server_id= server_id; + /* Page cache for the log reads */ + log_descriptor.pagecache= pagecache; + /* Flags */ + DBUG_ASSERT((flags & + ~(TRANSLOG_PAGE_CRC | TRANSLOG_SECTOR_PROTECTION | + TRANSLOG_RECORD_CRC)) == 0); + log_descriptor.flags= flags; + translog_fill_overhead_table(); + log_descriptor.page_overhead= page_overhead[flags]; + log_descriptor.page_capacity_chunk_2= + TRANSLOG_PAGE_SIZE - log_descriptor.page_overhead - 1; + compile_time_assert(TRANSLOG_WRITE_BUFFER % TRANSLOG_PAGE_SIZE == 0); + log_descriptor.buffer_capacity_chunk_2= + (TRANSLOG_WRITE_BUFFER / TRANSLOG_PAGE_SIZE) * + log_descriptor.page_capacity_chunk_2; + log_descriptor.half_buffer_capacity_chunk_2= + log_descriptor.buffer_capacity_chunk_2 / 2; + DBUG_PRINT("info", + ("Overhead: %u pc2: %u bc2: %u, bc2/2: %u", + log_descriptor.page_overhead, + log_descriptor.page_capacity_chunk_2, + log_descriptor.buffer_capacity_chunk_2, + log_descriptor.half_buffer_capacity_chunk_2)); + + /* Just to init it somehow (hack for bootstrap)*/ + { + TRANSLOG_FILE *file= 0; + log_descriptor.min_file = log_descriptor.max_file= 1; + insert_dynamic(&log_descriptor.open_files, (uchar *)&file); + translog_start_buffer(log_descriptor.buffers, &log_descriptor.bc, 0); + pop_dynamic(&log_descriptor.open_files); + } + + /* Buffers for log writing */ + for (i= 0; i < TRANSLOG_BUFFERS_NO; i++) + { + if (translog_buffer_init(log_descriptor.buffers + i, i)) + goto err; + DBUG_PRINT("info", ("translog_buffer buffer #%u: 0x%lx", + i, (ulong) log_descriptor.buffers + i)); + } + + /* + last_logno and last_checkpoint_lsn were set in + ma_control_file_create_or_open() + */ + logs_found= (last_logno != FILENO_IMPOSSIBLE); + + translog_status= (readonly ? TRANSLOG_READONLY : TRANSLOG_OK); + checkpoint_lsn= last_checkpoint_lsn; + + if (logs_found) + { + my_bool pageok; + DBUG_PRINT("info", ("log found...")); + /* + TODO: scan directory for maria_log.XXXXXXXX files and find + highest XXXXXXXX & set logs_found + TODO: check that last checkpoint within present log addresses space + + find the log end + */ + if (LSN_FILE_NO(last_checkpoint_lsn) == FILENO_IMPOSSIBLE) + { + DBUG_ASSERT(LSN_OFFSET(last_checkpoint_lsn) == 0); + /* only last log needs to be checked */ + sure_page= MAKE_LSN(last_logno, TRANSLOG_PAGE_SIZE); + } + else + { + sure_page= last_checkpoint_lsn; + DBUG_ASSERT(LSN_OFFSET(sure_page) % TRANSLOG_PAGE_SIZE != 0); + sure_page-= LSN_OFFSET(sure_page) % TRANSLOG_PAGE_SIZE; + } + /* Set horizon to the beginning of the last file first */ + log_descriptor.horizon= last_page= MAKE_LSN(last_logno, 0); + if (translog_get_last_page_addr(&last_page, &pageok, no_errors)) + { + if (!translog_walk_filenames(log_descriptor.directory, + &translog_callback_search_first)) + { + /* + Files was deleted, just start from the next log number, so that + existing tables are in the past. + */ + start_file_num= last_logno + 1; + checkpoint_lsn= LSN_IMPOSSIBLE; /* no log so no checkpoint */ + logs_found= 0; + } + else + goto err; + } + else if (LSN_OFFSET(last_page) == 0) + { + if (LSN_FILE_NO(last_page) == 1) + { + logs_found= 0; /* file #1 has no pages */ + DBUG_PRINT("info", ("log found. But is is empty => no log assumed")); + } + else + { + last_page-= LSN_ONE_FILE; + if (translog_get_last_page_addr(&last_page, &pageok, 0)) + goto err; + } + } + if (logs_found) + { + uint32 i; + log_descriptor.min_file= translog_first_file(log_descriptor.horizon, 1); + log_descriptor.max_file= last_logno; + /* Open all files */ + if (allocate_dynamic(&log_descriptor.open_files, + log_descriptor.max_file - + log_descriptor.min_file + 1)) + goto err; + for (i = log_descriptor.max_file; i >= log_descriptor.min_file; i--) + { + /* + We can't allocate all file together because they will be freed + one by one + */ + TRANSLOG_FILE *file= (TRANSLOG_FILE *)my_malloc(sizeof(TRANSLOG_FILE), + MYF(0)); + + compile_time_assert(MY_FILEPOS_ERROR > ULL(0xffffffff)); + if (file == NULL || + (file->handler.file= + open_logfile_by_number_no_cache(i)) < 0 || + my_seek(file->handler.file, 0, SEEK_END, MYF(0)) >= + ULL(0xffffffff)) + { + int j; + for (j= i - log_descriptor.min_file - 1; j > 0; j--) + { + TRANSLOG_FILE *el= + *dynamic_element(&log_descriptor.open_files, j, + TRANSLOG_FILE **); + my_close(el->handler.file, MYF(MY_WME)); + my_free(el, MYF(0)); + } + if (file) + { + free(file); + goto err; + } + else + goto err; + } + translog_file_init(file, i, 1); + /* we allocated space so it can't fail */ + insert_dynamic(&log_descriptor.open_files, (uchar *)&file); + } + DBUG_ASSERT(log_descriptor.max_file - log_descriptor.min_file + 1 == + log_descriptor.open_files.elements); + } + } + else if (readonly) + { + /* There is no logs and there is read-only mode => nothing to read */ + DBUG_PRINT("error", ("No logs and read-only mode")); + goto err; + } + + if (logs_found) + { + TRANSLOG_ADDRESS current_page= sure_page; + my_bool pageok; + + DBUG_PRINT("info", ("The log is really present")); + DBUG_ASSERT(sure_page <= last_page); + + /* TODO: check page size */ + + last_valid_page= LSN_IMPOSSIBLE; + /* + Scans and validate pages. We need it to show "outside" only for sure + valid part of the log. If the log was damaged then fixed we have to + cut off damaged part before some other process start write something + in the log. + */ + do + { + TRANSLOG_ADDRESS current_file_last_page; + current_file_last_page= current_page; + if (translog_get_last_page_addr(¤t_file_last_page, &pageok, 0)) + goto err; + if (!pageok) + { + DBUG_PRINT("error", ("File %lu have no complete last page", + (ulong) LSN_FILE_NO(current_file_last_page))); + old_log_was_recovered= 1; + /* This file is not written till the end so it should be last */ + last_page= current_file_last_page; + /* TODO: issue warning */ + } + do + { + TRANSLOG_VALIDATOR_DATA data; + TRANSLOG_PAGE_SIZE_BUFF psize_buff; + uchar *page; + data.addr= ¤t_page; + if ((page= translog_get_page(&data, psize_buff.buffer, NULL)) == NULL) + goto err; + if (data.was_recovered) + { + DBUG_PRINT("error", ("file no: %lu (%d) " + "rec_offset: 0x%lx (%lu) (%d)", + (ulong) LSN_FILE_NO(current_page), + (uint3korr(page + 3) != + LSN_FILE_NO(current_page)), + (ulong) LSN_OFFSET(current_page), + (ulong) (LSN_OFFSET(current_page) / + TRANSLOG_PAGE_SIZE), + (uint3korr(page) != + LSN_OFFSET(current_page) / + TRANSLOG_PAGE_SIZE))); + old_log_was_recovered= 1; + break; + } + old_flags= page[TRANSLOG_PAGE_FLAGS]; + last_valid_page= current_page; + current_page+= TRANSLOG_PAGE_SIZE; /* increase offset */ + } while (current_page <= current_file_last_page); + current_page+= LSN_ONE_FILE; + current_page= LSN_REPLACE_OFFSET(current_page, TRANSLOG_PAGE_SIZE); + } while (LSN_FILE_NO(current_page) <= LSN_FILE_NO(last_page) && + !old_log_was_recovered); + if (last_valid_page == LSN_IMPOSSIBLE) + { + /* Panic!!! Even page which should be valid is invalid */ + /* TODO: issue error */ + goto err; + } + DBUG_PRINT("info", ("Last valid page is in file: %lu " + "offset: %lu (0x%lx) " + "Logs found: %d was recovered: %d " + "flags match: %d", + (ulong) LSN_FILE_NO(last_valid_page), + (ulong) LSN_OFFSET(last_valid_page), + (ulong) LSN_OFFSET(last_valid_page), + logs_found, old_log_was_recovered, + (old_flags == flags))); + + /* TODO: check server ID */ + if (logs_found && !old_log_was_recovered && old_flags == flags) + { + TRANSLOG_VALIDATOR_DATA data; + TRANSLOG_PAGE_SIZE_BUFF psize_buff; + uchar *page; + uint16 chunk_offset; + data.addr= &last_valid_page; + /* continue old log */ + DBUG_ASSERT(LSN_FILE_NO(last_valid_page)== + LSN_FILE_NO(log_descriptor.horizon)); + if ((page= translog_get_page(&data, psize_buff.buffer, NULL)) == NULL || + (chunk_offset= translog_get_first_chunk_offset(page)) == 0) + goto err; + + /* Puts filled part of old page in the buffer */ + log_descriptor.horizon= last_valid_page; + translog_start_buffer(log_descriptor.buffers, &log_descriptor.bc, 0); + /* + Free space if filled with TRANSLOG_FILLER and first uchar of + real chunk can't be TRANSLOG_FILLER + */ + while (chunk_offset < TRANSLOG_PAGE_SIZE && + page[chunk_offset] != TRANSLOG_FILLER) + { + uint16 chunk_length; + if ((chunk_length= + translog_get_total_chunk_length(page, chunk_offset)) == 0) + goto err; + DBUG_PRINT("info", ("chunk: offset: %u length: %u", + (uint) chunk_offset, (uint) chunk_length)); + chunk_offset+= chunk_length; + + /* chunk can't cross the page border */ + DBUG_ASSERT(chunk_offset <= TRANSLOG_PAGE_SIZE); + } + memcpy(log_descriptor.buffers->buffer, page, chunk_offset); + log_descriptor.bc.buffer->size+= chunk_offset; + log_descriptor.bc.ptr+= chunk_offset; + log_descriptor.bc.current_page_fill= chunk_offset; + log_descriptor.horizon= LSN_REPLACE_OFFSET(log_descriptor.horizon, + (chunk_offset + + LSN_OFFSET(last_valid_page))); + DBUG_PRINT("info", ("Move Page #%u: 0x%lx chaser: %d Size: %lu (%lu)", + (uint) log_descriptor.bc.buffer_no, + (ulong) log_descriptor.bc.buffer, + log_descriptor.bc.chaser, + (ulong) log_descriptor.bc.buffer->size, + (ulong) (log_descriptor.bc.ptr - log_descriptor.bc. + buffer->buffer))); + translog_check_cursor(&log_descriptor.bc); + } + if (!old_log_was_recovered && old_flags == flags) + { + LOGHANDLER_FILE_INFO info; + /* + Accessing &log_descriptor.open_files without mutex is safe + because it is initialization + */ + if (translog_read_file_header(&info, + (*dynamic_element(&log_descriptor. + open_files, + 0, TRANSLOG_FILE **))-> + handler.file)) + goto err; + version_changed= (info.maria_version != TRANSLOG_VERSION_ID); + } + } + DBUG_PRINT("info", ("Logs found: %d was recovered: %d", + logs_found, old_log_was_recovered)); + if (!logs_found) + { + TRANSLOG_FILE *file= (TRANSLOG_FILE*)my_malloc(sizeof(TRANSLOG_FILE), + MYF(0)); + DBUG_PRINT("info", ("The log is not found => we will create new log")); + if (file == NULL) + goto err; + /* Start new log system from scratch */ + log_descriptor.horizon= MAKE_LSN(start_file_num, + TRANSLOG_PAGE_SIZE); /* header page */ + if ((file->handler.file= + create_logfile_by_number_no_cache(start_file_num)) == -1) + goto err; + translog_file_init(file, start_file_num, 0); + if (insert_dynamic(&log_descriptor.open_files, (uchar*)&file)) + goto err; + log_descriptor.min_file= log_descriptor.max_file= start_file_num; + if (translog_write_file_header()) + goto err; + DBUG_ASSERT(log_descriptor.max_file - log_descriptor.min_file + 1 == + log_descriptor.open_files.elements); + + if (ma_control_file_write_and_force(checkpoint_lsn, start_file_num, + max_trid_in_control_file, + recovery_failures)) + goto err; + /* assign buffer 0 */ + translog_start_buffer(log_descriptor.buffers, &log_descriptor.bc, 0); + translog_new_page_header(&log_descriptor.horizon, &log_descriptor.bc); + } + else if ((old_log_was_recovered || old_flags != flags || version_changed) && + !readonly) + { + /* leave the damaged file untouched */ + log_descriptor.horizon+= LSN_ONE_FILE; + /* header page */ + log_descriptor.horizon= LSN_REPLACE_OFFSET(log_descriptor.horizon, + TRANSLOG_PAGE_SIZE); + if (translog_create_new_file()) + goto err; + /* + Buffer system left untouched after recovery => we should init it + (starting from buffer 0) + */ + translog_start_buffer(log_descriptor.buffers, &log_descriptor.bc, 0); + translog_new_page_header(&log_descriptor.horizon, &log_descriptor.bc); + } + + /* all LSNs that are on disk are flushed */ + log_descriptor.log_start= log_descriptor.sent_to_disk= + log_descriptor.flushed= log_descriptor.horizon; + log_descriptor.in_buffers_only= log_descriptor.bc.buffer->offset; + log_descriptor.max_lsn= LSN_IMPOSSIBLE; /* set to 0 */ + log_descriptor.previous_flush_horizon= log_descriptor.horizon; + /* + Now 'flushed' is set to 'horizon' value, but 'horizon' is (potentially) + address of the next LSN and we want indicate that all LSNs that are + already on the disk are flushed so we need decrease horizon on 1 (we are + sure that there is no LSN on the disk which is greater then 'flushed' + and there will not be LSN created that is equal or less then the value + of the 'flushed'). + */ + log_descriptor.flushed--; /* offset decreased */ + log_descriptor.sent_to_disk--; /* offset decreased */ + /* + Log records will refer to a MARIA_SHARE by a unique 2-byte id; set up + structures for generating 2-byte ids: + */ + my_atomic_rwlock_init(&LOCK_id_to_share); + id_to_share= (MARIA_SHARE **) my_malloc(SHARE_ID_MAX * sizeof(MARIA_SHARE*), + MYF(MY_WME | MY_ZEROFILL)); + if (unlikely(!id_to_share)) + goto err; + id_to_share--; /* min id is 1 */ + + /* Check the last LSN record integrity */ + if (logs_found) + { + TRANSLOG_SCANNER_DATA scanner; + TRANSLOG_ADDRESS page_addr; + LSN last_lsn= LSN_IMPOSSIBLE; + /* + take very last page address and try to find LSN record on it + if it fail take address of previous page and so on + */ + page_addr= (log_descriptor.horizon - + ((log_descriptor.horizon - 1) % TRANSLOG_PAGE_SIZE + 1)); + if (translog_scanner_init(page_addr, 1, &scanner, 1)) + goto err; + scanner.page_offset= page_overhead[scanner.page[TRANSLOG_PAGE_FLAGS]]; + for (;;) + { + uint chunk_1byte; + chunk_1byte= scanner.page[scanner.page_offset]; + while (!translog_is_LSN_chunk(chunk_1byte) && + scanner.page != END_OF_LOG && + scanner.page[scanner.page_offset] != TRANSLOG_FILLER && + scanner.page_addr == page_addr) + { + if (translog_get_next_chunk(&scanner)) + { + translog_destroy_scanner(&scanner); + goto err; + } + if (scanner.page != END_OF_LOG) + chunk_1byte= scanner.page[scanner.page_offset]; + } + if (translog_is_LSN_chunk(chunk_1byte)) + { + last_lsn= scanner.page_addr + scanner.page_offset; + if (translog_get_next_chunk(&scanner)) + { + translog_destroy_scanner(&scanner); + goto err; + } + if (scanner.page == END_OF_LOG) + break; /* it was the last record */ + chunk_1byte= scanner.page[scanner.page_offset]; + continue; /* try to find other record on this page */ + } + + if (last_lsn != LSN_IMPOSSIBLE) + break; /* there is no more records on the page */ + + /* We have to make step back */ + if (unlikely(LSN_OFFSET(page_addr) == TRANSLOG_PAGE_SIZE)) + { + uint32 file_no= LSN_FILE_NO(page_addr); + my_bool last_page_ok; + /* it is beginning of the current file */ + if (unlikely(file_no == 1)) + { + /* + It is beginning of the log => there is no LSNs in the log => + There is no harm in leaving it "as-is". + */ + DBUG_RETURN(0); + } + file_no--; + page_addr= MAKE_LSN(file_no, TRANSLOG_PAGE_SIZE); + translog_get_last_page_addr(&page_addr, &last_page_ok, 0); + /* page should be OK as it is not the last file */ + DBUG_ASSERT(last_page_ok); + } + else + { + page_addr-= TRANSLOG_PAGE_SIZE; + } + translog_destroy_scanner(&scanner); + if (translog_scanner_init(page_addr, 1, &scanner, 1)) + goto err; + scanner.page_offset= page_overhead[scanner.page[TRANSLOG_PAGE_FLAGS]]; + } + translog_destroy_scanner(&scanner); + + /* Now scanner points to the last LSN chunk, lets check it */ + { + TRANSLOG_HEADER_BUFFER rec; + translog_size_t rec_len; + int len; + uchar buffer[1]; + DBUG_PRINT("info", ("going to check the last found record (%lu,0x%lx)", + LSN_IN_PARTS(last_lsn))); + + len= + translog_read_record_header(last_lsn, &rec); + if (unlikely (len == RECHEADER_READ_ERROR || + len == RECHEADER_READ_EOF)) + { + DBUG_PRINT("error", ("unexpected end of log or record during " + "reading record header: (%lu,0x%lx) len: %d", + LSN_IN_PARTS(last_lsn), len)); + if (readonly) + log_descriptor.log_start= log_descriptor.horizon= last_lsn; + else if (translog_truncate_log(last_lsn)) + { + translog_free_record_header(&rec); + goto err; + } + } + else + { + DBUG_ASSERT(last_lsn == rec.lsn); + if (likely(rec.record_length != 0)) + { + /* + Reading the last byte of record will trigger scanning all + record chunks for now + */ + rec_len= translog_read_record(rec.lsn, rec.record_length - 1, 1, + buffer, NULL); + if (rec_len != 1) + { + DBUG_PRINT("error", ("unexpected end of log or record during " + "reading record body: (%lu,0x%lx) len: %d", + LSN_IN_PARTS(rec.lsn), + len)); + if (readonly) + log_descriptor.log_start= log_descriptor.horizon= last_lsn; + + else if (translog_truncate_log(last_lsn)) + { + translog_free_record_header(&rec); + goto err; + } + } + } + } + translog_free_record_header(&rec); + } + } + DBUG_RETURN(0); +err: + ma_message_no_user(0, "log initialization failed"); + DBUG_RETURN(1); +} + + +/* + @brief Free transaction log file buffer. + + @param buffer_no The buffer to free +*/ + +static void translog_buffer_destroy(struct st_translog_buffer *buffer) +{ + DBUG_ENTER("translog_buffer_destroy"); + DBUG_PRINT("enter", + ("Buffer #%u: 0x%lx file: %d offset: (%lu,0x%lx) size: %lu", + (uint) buffer->buffer_no, (ulong) buffer, + (buffer->file ? buffer->file->handler.file : -1), + LSN_IN_PARTS(buffer->offset), + (ulong) buffer->size)); + if (buffer->file != NULL) + { + /* + We ignore errors here, because we can't do something about it + (it is shutting down) + + We also have to take the locks even if there can't be any other + threads running, because translog_buffer_flush() + requires that we have the buffer locked. + */ + translog_buffer_lock(buffer); + translog_buffer_flush(buffer); + translog_buffer_unlock(buffer); + } + DBUG_PRINT("info", ("Destroy mutex: 0x%lx", (ulong) &buffer->mutex)); + pthread_mutex_destroy(&buffer->mutex); + pthread_cond_destroy(&buffer->waiting_filling_buffer); + DBUG_VOID_RETURN; +} + + +/* + Free log handler resources + + SYNOPSIS + translog_destroy() +*/ + +void translog_destroy() +{ + TRANSLOG_FILE **file; + uint i; + uint8 current_buffer; + DBUG_ENTER("translog_destroy"); + + DBUG_ASSERT(translog_status == TRANSLOG_OK || + translog_status == TRANSLOG_READONLY); + translog_lock(); + current_buffer= log_descriptor.bc.buffer_no; + translog_status= (translog_status == TRANSLOG_READONLY ? + TRANSLOG_UNINITED : + TRANSLOG_SHUTDOWN); + if (log_descriptor.bc.buffer->file != NULL) + translog_finish_page(&log_descriptor.horizon, &log_descriptor.bc); + translog_unlock(); + + for (i= 0; i < TRANSLOG_BUFFERS_NO; i++) + { + struct st_translog_buffer *buffer= (log_descriptor.buffers + + ((i + current_buffer + 1) % + TRANSLOG_BUFFERS_NO)); + translog_buffer_destroy(buffer); + } + translog_status= TRANSLOG_UNINITED; + + /* close files */ + while ((file= (TRANSLOG_FILE **)pop_dynamic(&log_descriptor.open_files))) + translog_close_log_file(*file); + pthread_mutex_destroy(&log_descriptor.sent_to_disk_lock); + pthread_mutex_destroy(&log_descriptor.file_header_lock); + pthread_mutex_destroy(&log_descriptor.unfinished_files_lock); + pthread_mutex_destroy(&log_descriptor.purger_lock); + pthread_mutex_destroy(&log_descriptor.log_flush_lock); + pthread_mutex_destroy(&log_descriptor.dirty_buffer_mask_lock); + pthread_cond_destroy(&log_descriptor.log_flush_cond); + rwlock_destroy(&log_descriptor.open_files_lock); + delete_dynamic(&log_descriptor.open_files); + delete_dynamic(&log_descriptor.unfinished_files); + + if (log_descriptor.directory_fd >= 0) + my_close(log_descriptor.directory_fd, MYF(MY_WME)); + my_atomic_rwlock_destroy(&LOCK_id_to_share); + if (id_to_share != NULL) + my_free((id_to_share + 1), MYF(MY_WME)); + DBUG_VOID_RETURN; +} + + +/* + @brief Starts new page. + + @param horizon \ Position in file and buffer where we are + @param cursor / + @param prev_buffer Buffer which should be flushed will be assigned here. + This is always set (to NULL if nothing to flush). + + @note We do not want to flush the buffer immediately because we want to + let caller of this function first advance 'horizon' pointer and unlock the + loghandler and only then flush the log which can take some time. + + @retval 0 OK + @retval 1 Error +*/ + +static my_bool translog_page_next(TRANSLOG_ADDRESS *horizon, + struct st_buffer_cursor *cursor, + struct st_translog_buffer **prev_buffer) +{ + struct st_translog_buffer *buffer= cursor->buffer; + DBUG_ENTER("translog_page_next"); + + *prev_buffer= NULL; + if ((cursor->ptr + TRANSLOG_PAGE_SIZE > + cursor->buffer->buffer + TRANSLOG_WRITE_BUFFER) || + (LSN_OFFSET(*horizon) > + log_descriptor.log_file_max_size - TRANSLOG_PAGE_SIZE)) + { + DBUG_PRINT("info", ("Switch to next buffer Buffer Size: %lu (%lu) => %d " + "File size: %lu max: %lu => %d", + (ulong) cursor->buffer->size, + (ulong) (cursor->ptr - cursor->buffer->buffer), + (cursor->ptr + TRANSLOG_PAGE_SIZE > + cursor->buffer->buffer + TRANSLOG_WRITE_BUFFER), + (ulong) LSN_OFFSET(*horizon), + (ulong) log_descriptor.log_file_max_size, + (LSN_OFFSET(*horizon) > + (log_descriptor.log_file_max_size - + TRANSLOG_PAGE_SIZE)))); + if (translog_buffer_next(horizon, cursor, + LSN_OFFSET(*horizon) > + (log_descriptor.log_file_max_size - + TRANSLOG_PAGE_SIZE))) + DBUG_RETURN(1); + *prev_buffer= buffer; + DBUG_PRINT("info", ("Buffer #%u (0x%lu): have to be flushed", + (uint) buffer->buffer_no, (ulong) buffer)); + } + else + { + DBUG_PRINT("info", ("Use the same buffer #%u (0x%lu): " + "Buffer Size: %lu (%lu)", + (uint) buffer->buffer_no, + (ulong) buffer, + (ulong) cursor->buffer->size, + (ulong) (cursor->ptr - cursor->buffer->buffer))); + translog_finish_page(horizon, cursor); + translog_new_page_header(horizon, cursor); + } + DBUG_RETURN(0); +} + + +/* + Write data of given length to the current page + + SYNOPSIS + translog_write_data_on_page() + horizon \ Pointers on file and buffer + cursor / + length IN length of the chunk + buffer buffer with data + + RETURN + 0 OK + 1 Error +*/ + +static my_bool translog_write_data_on_page(TRANSLOG_ADDRESS *horizon, + struct st_buffer_cursor *cursor, + translog_size_t length, + uchar *buffer) +{ + DBUG_ENTER("translog_write_data_on_page"); + DBUG_PRINT("enter", ("Chunk length: %lu Page size %u", + (ulong) length, (uint) cursor->current_page_fill)); + DBUG_ASSERT(length > 0); + DBUG_ASSERT(length + cursor->current_page_fill <= TRANSLOG_PAGE_SIZE); + DBUG_ASSERT(length + cursor->ptr <= cursor->buffer->buffer + + TRANSLOG_WRITE_BUFFER); + + memcpy(cursor->ptr, buffer, length); + cursor->ptr+= length; + (*horizon)+= length; /* adds offset */ + cursor->current_page_fill+= length; + if (!cursor->chaser) + cursor->buffer->size+= length; + DBUG_PRINT("info", ("Write data buffer #%u: 0x%lx " + "chaser: %d Size: %lu (%lu)", + (uint) cursor->buffer->buffer_no, (ulong) cursor->buffer, + cursor->chaser, (ulong) cursor->buffer->size, + (ulong) (cursor->ptr - cursor->buffer->buffer))); + translog_check_cursor(cursor); + + DBUG_RETURN(0); +} + + +/* + Write data from parts of given length to the current page + + SYNOPSIS + translog_write_parts_on_page() + horizon \ Pointers on file and buffer + cursor / + length IN length of the chunk + parts IN/OUT chunk source + + RETURN + 0 OK + 1 Error +*/ + +static my_bool translog_write_parts_on_page(TRANSLOG_ADDRESS *horizon, + struct st_buffer_cursor *cursor, + translog_size_t length, + struct st_translog_parts *parts) +{ + translog_size_t left= length; + uint cur= (uint) parts->current; + DBUG_ENTER("translog_write_parts_on_page"); + DBUG_PRINT("enter", ("Chunk length: %lu parts: %u of %u. Page size: %u " + "Buffer size: %lu (%lu)", + (ulong) length, + (uint) (cur + 1), (uint) parts->elements, + (uint) cursor->current_page_fill, + (ulong) cursor->buffer->size, + (ulong) (cursor->ptr - cursor->buffer->buffer))); + DBUG_ASSERT(length > 0); + DBUG_ASSERT(length + cursor->current_page_fill <= TRANSLOG_PAGE_SIZE); + DBUG_ASSERT(length + cursor->ptr <= cursor->buffer->buffer + + TRANSLOG_WRITE_BUFFER); + + do + { + translog_size_t len; + LEX_CUSTRING *part; + const uchar *buff; + + DBUG_ASSERT(cur < parts->elements); + part= parts->parts + cur; + buff= part->str; + DBUG_PRINT("info", ("Part: %u Length: %lu left: %lu buff: 0x%lx", + (uint) (cur + 1), (ulong) part->length, (ulong) left, + (ulong) buff)); + + if (part->length > left) + { + /* we should write less then the current part */ + len= left; + part->length-= len; + part->str+= len; + DBUG_PRINT("info", ("Set new part: %u Length: %lu", + (uint) (cur + 1), (ulong) part->length)); + } + else + { + len= (translog_size_t) part->length; + cur++; + DBUG_PRINT("info", ("moved to next part (len: %lu)", (ulong) len)); + } + DBUG_PRINT("info", ("copy: 0x%lx <- 0x%lx %u", + (ulong) cursor->ptr, (ulong)buff, (uint)len)); + if (likely(len)) + { + memcpy(cursor->ptr, buff, len); + left-= len; + cursor->ptr+= len; + } + } while (left); + + DBUG_PRINT("info", ("Horizon: (%lu,0x%lx) Length %lu(0x%lx)", + LSN_IN_PARTS(*horizon), + (ulong) length, (ulong) length)); + parts->current= cur; + (*horizon)+= length; /* offset increasing */ + cursor->current_page_fill+= length; + if (!cursor->chaser) + cursor->buffer->size+= length; + /* + We do not not updating parts->total_record_length here because it is + need only before writing record to have total length + */ + DBUG_PRINT("info", ("Write parts buffer #%u: 0x%lx " + "chaser: %d Size: %lu (%lu) " + "Horizon: (%lu,0x%lx) buff offset: 0x%lx", + (uint) cursor->buffer->buffer_no, (ulong) cursor->buffer, + cursor->chaser, (ulong) cursor->buffer->size, + (ulong) (cursor->ptr - cursor->buffer->buffer), + LSN_IN_PARTS(*horizon), + (ulong) (LSN_OFFSET(cursor->buffer->offset) + + cursor->buffer->size))); + translog_check_cursor(cursor); + + DBUG_RETURN(0); +} + + +/* + Put 1 group chunk type 0 header into parts array + + SYNOPSIS + translog_write_variable_record_1group_header() + parts Descriptor of record source parts + type The log record type + short_trid Short transaction ID or 0 if it has no sense + header_length Calculated header length of chunk type 0 + chunk0_header Buffer for the chunk header writing +*/ + +static void +translog_write_variable_record_1group_header(struct st_translog_parts *parts, + enum translog_record_type type, + SHORT_TRANSACTION_ID short_trid, + uint16 header_length, + uchar *chunk0_header) +{ + LEX_CUSTRING *part; + DBUG_ASSERT(parts->current != 0); /* first part is left for header */ + part= parts->parts + (--parts->current); + parts->total_record_length+= (translog_size_t) (part->length= header_length); + part->str= chunk0_header; + /* puts chunk type */ + *chunk0_header= (uchar) (type | TRANSLOG_CHUNK_LSN); + int2store(chunk0_header + 1, short_trid); + /* puts record length */ + translog_write_variable_record_1group_code_len(chunk0_header + 3, + parts->record_length, + header_length); + /* puts 0 as chunk length which indicate 1 group record */ + int2store(chunk0_header + header_length - 2, 0); +} + + +/* + Increase number of writers for this buffer + + SYNOPSIS + translog_buffer_increase_writers() + buffer target buffer +*/ + +static inline void +translog_buffer_increase_writers(struct st_translog_buffer *buffer) +{ + DBUG_ENTER("translog_buffer_increase_writers"); + translog_buffer_lock_assert_owner(buffer); + buffer->copy_to_buffer_in_progress++; + DBUG_PRINT("info", ("copy_to_buffer_in_progress. Buffer #%u 0x%lx progress: %d", + (uint) buffer->buffer_no, (ulong) buffer, + buffer->copy_to_buffer_in_progress)); + DBUG_VOID_RETURN; +} + + +/* + Decrease number of writers for this buffer + + SYNOPSIS + translog_buffer_decrease_writers() + buffer target buffer +*/ + +static void translog_buffer_decrease_writers(struct st_translog_buffer *buffer) +{ + DBUG_ENTER("translog_buffer_decrease_writers"); + translog_buffer_lock_assert_owner(buffer); + buffer->copy_to_buffer_in_progress--; + DBUG_PRINT("info", + ("copy_to_buffer_in_progress. Buffer #%u 0x%lx progress: %d", + (uint) buffer->buffer_no, (ulong) buffer, + buffer->copy_to_buffer_in_progress)); + if (buffer->copy_to_buffer_in_progress == 0) + pthread_cond_broadcast(&buffer->waiting_filling_buffer); + DBUG_VOID_RETURN; +} + + +/** + @brief Skip to the next page for chaser (thread which advanced horizon + pointer and now feeling the buffer) + + @param horizon \ Pointers on file position and buffer + @param cursor / + + @retval 1 OK + @retval 0 Error +*/ + +static my_bool translog_chaser_page_next(TRANSLOG_ADDRESS *horizon, + struct st_buffer_cursor *cursor) +{ + struct st_translog_buffer *buffer_to_flush; + my_bool rc; + DBUG_ENTER("translog_chaser_page_next"); + DBUG_ASSERT(cursor->chaser); + rc= translog_page_next(horizon, cursor, &buffer_to_flush); + if (buffer_to_flush != NULL) + { + translog_buffer_lock(buffer_to_flush); + translog_buffer_decrease_writers(buffer_to_flush); + if (!rc) + rc= translog_buffer_flush(buffer_to_flush); + translog_buffer_unlock(buffer_to_flush); + } + DBUG_RETURN(rc); +} + +/* + Put chunk 2 from new page beginning + + SYNOPSIS + translog_write_variable_record_chunk2_page() + parts Descriptor of record source parts + horizon \ Pointers on file position and buffer + cursor / + + RETURN + 0 OK + 1 Error +*/ + +static my_bool +translog_write_variable_record_chunk2_page(struct st_translog_parts *parts, + TRANSLOG_ADDRESS *horizon, + struct st_buffer_cursor *cursor) +{ + uchar chunk2_header[1]; + DBUG_ENTER("translog_write_variable_record_chunk2_page"); + chunk2_header[0]= TRANSLOG_CHUNK_NOHDR; + + if (translog_chaser_page_next(horizon, cursor)) + DBUG_RETURN(1); + + /* Puts chunk type */ + translog_write_data_on_page(horizon, cursor, 1, chunk2_header); + /* Puts chunk body */ + translog_write_parts_on_page(horizon, cursor, + log_descriptor.page_capacity_chunk_2, parts); + DBUG_RETURN(0); +} + + +/* + Put chunk 3 of requested length in the buffer from new page beginning + + SYNOPSIS + translog_write_variable_record_chunk3_page() + parts Descriptor of record source parts + length Length of this chunk + horizon \ Pointers on file position and buffer + cursor / + + RETURN + 0 OK + 1 Error +*/ + +static my_bool +translog_write_variable_record_chunk3_page(struct st_translog_parts *parts, + uint16 length, + TRANSLOG_ADDRESS *horizon, + struct st_buffer_cursor *cursor) +{ + LEX_CUSTRING *part; + uchar chunk3_header[1 + 2]; + DBUG_ENTER("translog_write_variable_record_chunk3_page"); + + if (translog_chaser_page_next(horizon, cursor)) + DBUG_RETURN(1); + + if (length == 0) + { + /* It was call to write page header only (no data for chunk 3) */ + DBUG_PRINT("info", ("It is a call to make page header only")); + DBUG_RETURN(0); + } + + DBUG_ASSERT(parts->current != 0); /* first part is left for header */ + part= parts->parts + (--parts->current); + parts->total_record_length+= (translog_size_t) (part->length= 1 + 2); + part->str= chunk3_header; + /* Puts chunk type */ + *chunk3_header= (uchar) (TRANSLOG_CHUNK_LNGTH); + /* Puts chunk length */ + int2store(chunk3_header + 1, length); + + translog_write_parts_on_page(horizon, cursor, length + 1 + 2, parts); + DBUG_RETURN(0); +} + +/* + Move log pointer (horizon) on given number pages starting from next page, + and given offset on the last page + + SYNOPSIS + translog_advance_pointer() + pages Number of full pages starting from the next one + last_page_data Plus this data on the last page + + RETURN + 0 OK + 1 Error +*/ + +static my_bool translog_advance_pointer(int pages, uint16 last_page_data) +{ + translog_size_t last_page_offset= (log_descriptor.page_overhead + + last_page_data); + translog_size_t offset= (TRANSLOG_PAGE_SIZE - + log_descriptor.bc.current_page_fill + + pages * TRANSLOG_PAGE_SIZE + last_page_offset); + translog_size_t buffer_end_offset, file_end_offset, min_offset; + DBUG_ENTER("translog_advance_pointer"); + DBUG_PRINT("enter", ("Pointer: (%lu, 0x%lx) + %u + %u pages + %u + %u", + LSN_IN_PARTS(log_descriptor.horizon), + (uint) (TRANSLOG_PAGE_SIZE - + log_descriptor.bc.current_page_fill), + pages, (uint) log_descriptor.page_overhead, + (uint) last_page_data)); + translog_lock_assert_owner(); + + if (pages == -1) + { + /* + It is special case when we advance the pointer on the same page. + It can happened when we write last part of multi-group record. + */ + DBUG_ASSERT(last_page_data + log_descriptor.bc.current_page_fill <= + TRANSLOG_PAGE_SIZE); + offset= last_page_data; + last_page_offset= log_descriptor.bc.current_page_fill + last_page_data; + goto end; + } + DBUG_PRINT("info", ("last_page_offset %lu", (ulong) last_page_offset)); + DBUG_ASSERT(last_page_offset <= TRANSLOG_PAGE_SIZE); + + /* + The loop will be executed 1-3 times. Usually we advance the + pointer to fill only the current buffer (if we have more then 1/2 of + buffer free or 2 buffers (rest of current and all next). In case of + really huge record end where we write last group with "table of + content" of all groups and ignore buffer borders we can occupy + 3 buffers. + */ + for (;;) + { + uint8 new_buffer_no; + struct st_translog_buffer *new_buffer; + struct st_translog_buffer *old_buffer; + buffer_end_offset= TRANSLOG_WRITE_BUFFER - log_descriptor.bc.buffer->size; + if (likely(log_descriptor.log_file_max_size >= + LSN_OFFSET(log_descriptor.horizon))) + file_end_offset= (log_descriptor.log_file_max_size - + LSN_OFFSET(log_descriptor.horizon)); + else + { + /* + We already have written more then current file limit allow, + So we will finish this page and start new file + */ + file_end_offset= (TRANSLOG_PAGE_SIZE - + log_descriptor.bc.current_page_fill); + } + DBUG_PRINT("info", ("offset: %lu buffer_end_offs: %lu, " + "file_end_offs: %lu", + (ulong) offset, (ulong) buffer_end_offset, + (ulong) file_end_offset)); + DBUG_PRINT("info", ("Buff #%u %u (0x%lx) offset 0x%lx + size 0x%lx = " + "0x%lx (0x%lx)", + (uint) log_descriptor.bc.buffer->buffer_no, + (uint) log_descriptor.bc.buffer_no, + (ulong) log_descriptor.bc.buffer, + (ulong) LSN_OFFSET(log_descriptor.bc.buffer->offset), + (ulong) log_descriptor.bc.buffer->size, + (ulong) (LSN_OFFSET(log_descriptor.bc.buffer->offset) + + log_descriptor.bc.buffer->size), + (ulong) LSN_OFFSET(log_descriptor.horizon))); + DBUG_ASSERT(LSN_OFFSET(log_descriptor.bc.buffer->offset) + + log_descriptor.bc.buffer->size == + LSN_OFFSET(log_descriptor.horizon)); + + if (offset <= buffer_end_offset && offset <= file_end_offset) + break; + old_buffer= log_descriptor.bc.buffer; + new_buffer_no= (log_descriptor.bc.buffer_no + 1) % TRANSLOG_BUFFERS_NO; + new_buffer= log_descriptor.buffers + new_buffer_no; + + translog_buffer_lock(new_buffer); +#ifndef DBUG_OFF + { + TRANSLOG_ADDRESS offset= new_buffer->offset; + TRANSLOG_FILE *file= new_buffer->file; + uint8 ver= new_buffer->ver; + translog_lock_assert_owner(); +#endif + translog_wait_for_buffer_free(new_buffer); +#ifndef DBUG_OFF + /* We keep the handler locked so nobody can start this new buffer */ + DBUG_ASSERT(offset == new_buffer->offset && new_buffer->file == NULL && + (file == NULL ? ver : (uint8)(ver + 1)) == new_buffer->ver); + } +#endif + + min_offset= min(buffer_end_offset, file_end_offset); + /* TODO: check is it ptr or size enough */ + log_descriptor.bc.buffer->size+= min_offset; + log_descriptor.bc.ptr+= min_offset; + DBUG_PRINT("info", ("NewP buffer #%u: 0x%lx chaser: %d Size: %lu (%lu)", + (uint) log_descriptor.bc.buffer->buffer_no, + (ulong) log_descriptor.bc.buffer, + log_descriptor.bc.chaser, + (ulong) log_descriptor.bc.buffer->size, + (ulong) (log_descriptor.bc.ptr -log_descriptor.bc. + buffer->buffer))); + DBUG_ASSERT((ulong) (log_descriptor.bc.ptr - + log_descriptor.bc.buffer->buffer) == + log_descriptor.bc.buffer->size); + DBUG_ASSERT(log_descriptor.bc.buffer->buffer_no == + log_descriptor.bc.buffer_no); + translog_buffer_increase_writers(log_descriptor.bc.buffer); + + if (file_end_offset <= buffer_end_offset) + { + log_descriptor.horizon+= LSN_ONE_FILE; + log_descriptor.horizon= LSN_REPLACE_OFFSET(log_descriptor.horizon, + TRANSLOG_PAGE_SIZE); + DBUG_PRINT("info", ("New file: %lu", + (ulong) LSN_FILE_NO(log_descriptor.horizon))); + if (translog_create_new_file()) + { + DBUG_RETURN(1); + } + } + else + { + DBUG_PRINT("info", ("The same file")); + log_descriptor.horizon+= min_offset; /* offset increasing */ + } + translog_start_buffer(new_buffer, &log_descriptor.bc, new_buffer_no); + old_buffer->next_buffer_offset= new_buffer->offset; + new_buffer->prev_buffer_offset= old_buffer->offset; + translog_buffer_unlock(old_buffer); + offset-= min_offset; + } + DBUG_PRINT("info", ("drop write_counter")); + log_descriptor.bc.write_counter= 0; + log_descriptor.bc.previous_offset= 0; +end: + log_descriptor.bc.ptr+= offset; + log_descriptor.bc.buffer->size+= offset; + translog_buffer_increase_writers(log_descriptor.bc.buffer); + log_descriptor.horizon+= offset; /* offset increasing */ + log_descriptor.bc.current_page_fill= last_page_offset; + DBUG_PRINT("info", ("NewP buffer #%u: 0x%lx chaser: %d Size: %lu (%lu) " + "offset: %u last page: %u", + (uint) log_descriptor.bc.buffer->buffer_no, + (ulong) log_descriptor.bc.buffer, + log_descriptor.bc.chaser, + (ulong) log_descriptor.bc.buffer->size, + (ulong) (log_descriptor.bc.ptr - + log_descriptor.bc.buffer-> + buffer), (uint) offset, + (uint) last_page_offset)); + DBUG_PRINT("info", + ("pointer moved to: (%lu, 0x%lx)", + LSN_IN_PARTS(log_descriptor.horizon))); + translog_check_cursor(&log_descriptor.bc); + log_descriptor.bc.protected= 0; + DBUG_RETURN(0); +} + + +/* + Get page rest + + SYNOPSIS + translog_get_current_page_rest() + + NOTE loghandler should be locked + + RETURN + number of bytes left on the current page +*/ + +static uint translog_get_current_page_rest() +{ + return (TRANSLOG_PAGE_SIZE - log_descriptor.bc.current_page_fill); +} + + +/* + Get buffer rest in full pages + + SYNOPSIS + translog_get_current_buffer_rest() + + NOTE loghandler should be locked + + RETURN + number of full pages left on the current buffer +*/ + +static uint translog_get_current_buffer_rest() +{ + return ((log_descriptor.bc.buffer->buffer + TRANSLOG_WRITE_BUFFER - + log_descriptor.bc.ptr) / + TRANSLOG_PAGE_SIZE); +} + +/* + Calculate possible group size without first (current) page + + SYNOPSIS + translog_get_current_group_size() + + NOTE loghandler should be locked + + RETURN + group size without first (current) page +*/ + +static translog_size_t translog_get_current_group_size() +{ + /* buffer rest in full pages */ + translog_size_t buffer_rest= translog_get_current_buffer_rest(); + DBUG_ENTER("translog_get_current_group_size"); + DBUG_PRINT("info", ("buffer_rest in pages: %u", buffer_rest)); + + buffer_rest*= log_descriptor.page_capacity_chunk_2; + /* in case of only half of buffer free we can write this and next buffer */ + if (buffer_rest < log_descriptor.half_buffer_capacity_chunk_2) + { + DBUG_PRINT("info", ("buffer_rest: %lu -> add %lu", + (ulong) buffer_rest, + (ulong) log_descriptor.buffer_capacity_chunk_2)); + buffer_rest+= log_descriptor.buffer_capacity_chunk_2; + } + + DBUG_PRINT("info", ("buffer_rest: %lu", (ulong) buffer_rest)); + + DBUG_RETURN(buffer_rest); +} + + +static inline void set_lsn(LSN *lsn, LSN value) +{ + DBUG_ENTER("set_lsn"); + translog_lock_assert_owner(); + *lsn= value; + /* we generate LSN so something is not flushed in log */ + log_descriptor.is_everything_flushed= 0; + DBUG_PRINT("info", ("new LSN appeared: (%lu,0x%lx)", LSN_IN_PARTS(value))); + DBUG_VOID_RETURN; +} + + +/** + @brief Write variable record in 1 group. + + @param lsn LSN of the record will be written here + @param type the log record type + @param short_trid Short transaction ID or 0 if it has no sense + @param parts Descriptor of record source parts + @param buffer_to_flush Buffer which have to be flushed if it is not 0 + @param header_length Calculated header length of chunk type 0 + @param trn Transaction structure pointer for hooks by + record log type, for short_id + @param hook_arg Argument which will be passed to pre-write and + in-write hooks of this record. + + @note + We must have a translog_lock() when entering this function + We must have buffer_to_flush locked (if not null) + + @return Operation status + @retval 0 OK + @retval 1 Error +*/ + +static my_bool +translog_write_variable_record_1group(LSN *lsn, + enum translog_record_type type, + MARIA_HA *tbl_info, + SHORT_TRANSACTION_ID short_trid, + struct st_translog_parts *parts, + struct st_translog_buffer + *buffer_to_flush, uint16 header_length, + TRN *trn, void *hook_arg) +{ + TRANSLOG_ADDRESS horizon; + struct st_buffer_cursor cursor; + int rc= 0; + uint i; + translog_size_t record_rest, full_pages, first_page; + uint additional_chunk3_page= 0; + uchar chunk0_header[1 + 2 + 5 + 2]; + DBUG_ENTER("translog_write_variable_record_1group"); + translog_lock_assert_owner(); + if (buffer_to_flush) + translog_buffer_lock_assert_owner(buffer_to_flush); + + set_lsn(lsn, horizon= log_descriptor.horizon); + if (translog_set_lsn_for_files(LSN_FILE_NO(*lsn), LSN_FILE_NO(*lsn), + *lsn, TRUE) || + (log_record_type_descriptor[type].inwrite_hook && + (*log_record_type_descriptor[type].inwrite_hook)(type, trn, tbl_info, + lsn, hook_arg))) + { + translog_unlock(); + DBUG_RETURN(1); + } + cursor= log_descriptor.bc; + cursor.chaser= 1; + + /* Advance pointer to be able unlock the loghandler */ + first_page= translog_get_current_page_rest(); + record_rest= parts->record_length - (first_page - header_length); + full_pages= record_rest / log_descriptor.page_capacity_chunk_2; + record_rest= (record_rest % log_descriptor.page_capacity_chunk_2); + + if (record_rest + 1 == log_descriptor.page_capacity_chunk_2) + { + DBUG_PRINT("info", ("2 chunks type 3 is needed")); + /* We will write 2 chunks type 3 at the end of this group */ + additional_chunk3_page= 1; + record_rest= 1; + } + + DBUG_PRINT("info", ("first_page: %u (%u) full_pages: %u (%lu) " + "additional: %u (%u) rest %u = %u", + first_page, first_page - header_length, + full_pages, + (ulong) full_pages * + log_descriptor.page_capacity_chunk_2, + additional_chunk3_page, + additional_chunk3_page * + (log_descriptor.page_capacity_chunk_2 - 1), + record_rest, parts->record_length)); + /* record_rest + 3 is chunk type 3 overhead + record_rest */ + rc|= translog_advance_pointer((int)(full_pages + additional_chunk3_page), + (record_rest ? record_rest + 3 : 0)); + log_descriptor.bc.buffer->last_lsn= *lsn; + DBUG_PRINT("info", ("last_lsn set to (%lu,0x%lx) buffer: 0x%lx", + LSN_IN_PARTS(log_descriptor.bc.buffer->last_lsn), + (ulong) log_descriptor.bc.buffer)); + + translog_unlock(); + + /* + Check if we switched buffer and need process it (current buffer is + unlocked already => we will not delay other threads + */ + if (buffer_to_flush != NULL) + { + if (!rc) + rc= translog_buffer_flush(buffer_to_flush); + translog_buffer_unlock(buffer_to_flush); + } + if (rc) + DBUG_RETURN(1); + + translog_write_variable_record_1group_header(parts, type, short_trid, + header_length, chunk0_header); + + /* fill the pages */ + translog_write_parts_on_page(&horizon, &cursor, first_page, parts); + + DBUG_PRINT("info", ("absolute horizon: (%lu,0x%lx) local: (%lu,0x%lx)", + LSN_IN_PARTS(log_descriptor.horizon), + LSN_IN_PARTS(horizon))); + + for (i= 0; i < full_pages; i++) + { + if (translog_write_variable_record_chunk2_page(parts, &horizon, &cursor)) + DBUG_RETURN(1); + + DBUG_PRINT("info", ("absolute horizon: (%lu,0x%lx) local: (%lu,0x%lx)", + LSN_IN_PARTS(log_descriptor.horizon), + LSN_IN_PARTS(horizon))); + } + + if (additional_chunk3_page) + { + if (translog_write_variable_record_chunk3_page(parts, + log_descriptor. + page_capacity_chunk_2 - 2, + &horizon, &cursor)) + DBUG_RETURN(1); + DBUG_PRINT("info", ("absolute horizon: (%lu,0x%lx) local: (%lu,0x%lx)", + LSN_IN_PARTS(log_descriptor.horizon), + LSN_IN_PARTS(horizon))); + DBUG_ASSERT(cursor.current_page_fill == TRANSLOG_PAGE_SIZE); + } + + if (translog_write_variable_record_chunk3_page(parts, + record_rest, + &horizon, &cursor)) + DBUG_RETURN(1); + DBUG_PRINT("info", ("absolute horizon: (%lu,0x%lx) local: (%lu,0x%lx)", + (ulong) LSN_FILE_NO(log_descriptor.horizon), + (ulong) LSN_OFFSET(log_descriptor.horizon), + (ulong) LSN_FILE_NO(horizon), + (ulong) LSN_OFFSET(horizon))); + + translog_buffer_lock(cursor.buffer); + translog_buffer_decrease_writers(cursor.buffer); + translog_buffer_unlock(cursor.buffer); + DBUG_RETURN(rc); +} + + +/** + @brief Write variable record in 1 chunk. + + @param lsn LSN of the record will be written here + @param type the log record type + @param short_trid Short transaction ID or 0 if it has no sense + @param parts Descriptor of record source parts + @param buffer_to_flush Buffer which have to be flushed if it is not 0 + @param header_length Calculated header length of chunk type 0 + @param trn Transaction structure pointer for hooks by + record log type, for short_id + @param hook_arg Argument which will be passed to pre-write and + in-write hooks of this record. + + @note + We must have a translog_lock() when entering this function + We must have buffer_to_flush locked (if not null) + + @return Operation status + @retval 0 OK + @retval 1 Error +*/ + +static my_bool +translog_write_variable_record_1chunk(LSN *lsn, + enum translog_record_type type, + MARIA_HA *tbl_info, + SHORT_TRANSACTION_ID short_trid, + struct st_translog_parts *parts, + struct st_translog_buffer + *buffer_to_flush, uint16 header_length, + TRN *trn, void *hook_arg) +{ + int rc; + uchar chunk0_header[1 + 2 + 5 + 2]; + DBUG_ENTER("translog_write_variable_record_1chunk"); + translog_lock_assert_owner(); + if (buffer_to_flush) + translog_buffer_lock_assert_owner(buffer_to_flush); + + translog_write_variable_record_1group_header(parts, type, short_trid, + header_length, chunk0_header); + set_lsn(lsn, log_descriptor.horizon); + if (translog_set_lsn_for_files(LSN_FILE_NO(*lsn), LSN_FILE_NO(*lsn), + *lsn, TRUE) || + (log_record_type_descriptor[type].inwrite_hook && + (*log_record_type_descriptor[type].inwrite_hook)(type, trn, tbl_info, + lsn, hook_arg))) + { + translog_unlock(); + DBUG_RETURN(1); + } + + rc= translog_write_parts_on_page(&log_descriptor.horizon, + &log_descriptor.bc, + parts->total_record_length, parts); + log_descriptor.bc.buffer->last_lsn= *lsn; + DBUG_PRINT("info", ("last_lsn set to (%lu,0x%lx) buffer: 0x%lx", + LSN_IN_PARTS(log_descriptor.bc.buffer->last_lsn), + (ulong) log_descriptor.bc.buffer)); + translog_unlock(); + + /* + check if we switched buffer and need process it (current buffer is + unlocked already => we will not delay other threads + */ + if (buffer_to_flush != NULL) + { + if (!rc) + rc= translog_buffer_flush(buffer_to_flush); + translog_buffer_unlock(buffer_to_flush); + } + + DBUG_RETURN(rc); +} + + +/* + @brief Calculates and write LSN difference (compressed LSN). + + @param base_lsn LSN from which we calculate difference + @param lsn LSN for codding + @param dst Result will be written to dst[-pack_length] .. dst[-1] + + @note To store an LSN in a compact way we will use the following compression: + If a log record has LSN1, and it contains the LSN2 as a back reference, + Instead of LSN2 we write LSN1-LSN2, encoded as: + two bits the number N (see below) + 14 bits + N bytes + That is, LSN is encoded in 2..5 bytes, and the number of bytes minus 2 + is stored in the first two bits. + + @note function made to write the result in backward direction with no + special sense or tricks both directions are equal in complicity + + @retval # pointer on coded LSN +*/ + +static uchar *translog_put_LSN_diff(LSN base_lsn, LSN lsn, uchar *dst) +{ + uint64 diff; + DBUG_ENTER("translog_put_LSN_diff"); + DBUG_PRINT("enter", ("Base: (%lu,0x%lx) val: (%lu,0x%lx) dst: 0x%lx", + LSN_IN_PARTS(base_lsn), LSN_IN_PARTS(lsn), + (ulong) dst)); + DBUG_ASSERT(base_lsn > lsn); + diff= base_lsn - lsn; + DBUG_PRINT("info", ("Diff: 0x%llx", (ulonglong) diff)); + if (diff <= 0x3FFF) + { + dst-= 2; + /* + Note we store this high uchar first to ensure that first uchar has + 0 in the 3 upper bits. + */ + dst[0]= (uchar)(diff >> 8); + dst[1]= (uchar)(diff & 0xFF); + } + else if (diff <= 0x3FFFFFL) + { + dst-= 3; + dst[0]= (uchar)(0x40 | (diff >> 16)); + int2store(dst + 1, diff & 0xFFFF); + } + else if (diff <= 0x3FFFFFFFL) + { + dst-= 4; + dst[0]= (uchar)(0x80 | (diff >> 24)); + int3store(dst + 1, diff & 0xFFFFFFL); + } + else if (diff <= LL(0x3FFFFFFFFF)) + + { + dst-= 5; + dst[0]= (uchar)(0xC0 | (diff >> 32)); + int4store(dst + 1, diff & 0xFFFFFFFFL); + } + else + { + /* + It is full LSN after special 1 diff (which is impossible + in real life) + */ + dst-= 2 + LSN_STORE_SIZE; + dst[0]= 0; + dst[1]= 1; + lsn_store(dst + 2, lsn); + } + DBUG_PRINT("info", ("new dst: 0x%lx", (ulong) dst)); + DBUG_RETURN(dst); +} + + +/* + Get LSN from LSN-difference (compressed LSN) + + SYNOPSIS + translog_get_LSN_from_diff() + base_lsn LSN from which we calculate difference + src pointer to coded lsn + dst pointer to buffer where to write 7byte LSN + + NOTE: + To store an LSN in a compact way we will use the following compression: + + If a log record has LSN1, and it contains the lSN2 as a back reference, + Instead of LSN2 we write LSN1-LSN2, encoded as: + + two bits the number N (see below) + 14 bits + N bytes + + That is, LSN is encoded in 2..5 bytes, and the number of bytes minus 2 + is stored in the first two bits. + + RETURN + pointer to buffer after decoded LSN +*/ + +static uchar *translog_get_LSN_from_diff(LSN base_lsn, uchar *src, uchar *dst) +{ + LSN lsn; + uint32 diff; + uint32 first_byte; + uint32 file_no, rec_offset; + uint8 code; + DBUG_ENTER("translog_get_LSN_from_diff"); + DBUG_PRINT("enter", ("Base: (%lu,0x%lx) src: 0x%lx dst 0x%lx", + LSN_IN_PARTS(base_lsn), (ulong) src, (ulong) dst)); + first_byte= *((uint8*) src); + code= first_byte >> 6; /* Length is in 2 most significant bits */ + first_byte&= 0x3F; + src++; /* Skip length + encode */ + file_no= LSN_FILE_NO(base_lsn); /* Assume relative */ + DBUG_PRINT("info", ("code: %u first byte: %lu", + (uint) code, (ulong) first_byte)); + switch (code) { + case 0: + if (first_byte == 0 && *((uint8*)src) == 1) + { + /* + It is full LSN after special 1 diff (which is impossible + in real life) + */ + memcpy(dst, src + 1, LSN_STORE_SIZE); + DBUG_PRINT("info", ("Special case of full LSN, new src: 0x%lx", + (ulong) (src + 1 + LSN_STORE_SIZE))); + DBUG_RETURN(src + 1 + LSN_STORE_SIZE); + } + rec_offset= LSN_OFFSET(base_lsn) - ((first_byte << 8) + *((uint8*)src)); + break; + case 1: + diff= uint2korr(src); + rec_offset= LSN_OFFSET(base_lsn) - ((first_byte << 16) + diff); + break; + case 2: + diff= uint3korr(src); + rec_offset= LSN_OFFSET(base_lsn) - ((first_byte << 24) + diff); + break; + case 3: + { + ulonglong base_offset= LSN_OFFSET(base_lsn); + diff= uint4korr(src); + if (diff > LSN_OFFSET(base_lsn)) + { + /* take 1 from file offset */ + first_byte++; + base_offset+= LL(0x100000000); + } + file_no= LSN_FILE_NO(base_lsn) - first_byte; + DBUG_ASSERT(base_offset - diff <= UINT_MAX); + rec_offset= (uint32)(base_offset - diff); + break; + } + default: + DBUG_ASSERT(0); + DBUG_RETURN(NULL); + } + lsn= MAKE_LSN(file_no, rec_offset); + src+= code + 1; + lsn_store(dst, lsn); + DBUG_PRINT("info", ("new src: 0x%lx", (ulong) src)); + DBUG_RETURN(src); +} + + +/** + @brief Encodes relative LSNs listed in the parameters. + + @param parts Parts list with encoded LSN(s) + @param base_lsn LSN which is base for encoding + @param lsns number of LSN(s) to encode + @param compressed_LSNs buffer which can be used for storing compressed LSN(s) +*/ + +static void translog_relative_LSN_encode(struct st_translog_parts *parts, + LSN base_lsn, + uint lsns, uchar *compressed_LSNs) +{ + LEX_CUSTRING *part; + uint lsns_len= lsns * LSN_STORE_SIZE; + uchar buffer_src[MAX_NUMBER_OF_LSNS_PER_RECORD * LSN_STORE_SIZE]; + uchar *buffer= buffer_src; + const uchar *cbuffer; + + DBUG_ENTER("translog_relative_LSN_encode"); + + DBUG_ASSERT(parts->current != 0); + part= parts->parts + parts->current; + + /* collect all LSN(s) in one chunk if it (they) is (are) divided */ + if (part->length < lsns_len) + { + uint copied= part->length; + LEX_CUSTRING *next_part; + DBUG_PRINT("info", ("Using buffer: 0x%lx", (ulong) compressed_LSNs)); + memcpy(buffer, part->str, part->length); + next_part= parts->parts + parts->current + 1; + do + { + DBUG_ASSERT(next_part < parts->parts + parts->elements); + if ((next_part->length + copied) < lsns_len) + { + memcpy(buffer + copied, next_part->str, + next_part->length); + copied+= next_part->length; + next_part->length= 0; next_part->str= 0; + /* delete_dynamic_element(&parts->parts, parts->current + 1); */ + next_part++; + parts->current++; + part= parts->parts + parts->current; + } + else + { + uint len= lsns_len - copied; + memcpy(buffer + copied, next_part->str, len); + copied= lsns_len; + next_part->str+= len; + next_part->length-= len; + } + } while (copied < lsns_len); + cbuffer= buffer; + } + else + { + cbuffer= part->str; + part->str+= lsns_len; + part->length-= lsns_len; + parts->current--; + part= parts->parts + parts->current; + } + + { + /* Compress */ + LSN ref; + int economy; + const uchar *src_ptr; + uchar *dst_ptr= compressed_LSNs + (MAX_NUMBER_OF_LSNS_PER_RECORD * + COMPRESSED_LSN_MAX_STORE_SIZE); + /* + We write the result in backward direction with no special sense or + tricks both directions are equal in complicity + */ + for (src_ptr= cbuffer + lsns_len - LSN_STORE_SIZE; + src_ptr >= (const uchar*)cbuffer; + src_ptr-= LSN_STORE_SIZE) + { + ref= lsn_korr(src_ptr); + dst_ptr= translog_put_LSN_diff(base_lsn, ref, dst_ptr); + } + part->length= (uint)((compressed_LSNs + + (MAX_NUMBER_OF_LSNS_PER_RECORD * + COMPRESSED_LSN_MAX_STORE_SIZE)) - + dst_ptr); + parts->record_length-= (economy= lsns_len - part->length); + DBUG_PRINT("info", ("new length of LSNs: %lu economy: %d", + (ulong)part->length, economy)); + parts->total_record_length-= economy; + part->str= dst_ptr; + } + DBUG_VOID_RETURN; +} + + +/** + @brief Write multi-group variable-size record. + + @param lsn LSN of the record will be written here + @param type the log record type + @param short_trid Short transaction ID or 0 if it has no sense + @param parts Descriptor of record source parts + @param buffer_to_flush Buffer which have to be flushed if it is not 0 + @param header_length Header length calculated for 1 group + @param buffer_rest Beginning from which we plan to write in full pages + @param trn Transaction structure pointer for hooks by + record log type, for short_id + @param hook_arg Argument which will be passed to pre-write and + in-write hooks of this record. + + @note + We must have a translog_lock() when entering this function + + We must have buffer_to_flush locked (if not null) + buffer_to_flush should *NOT* be locked when calling this function. + (This is note is here as this is different from most other + translog_write...() functions which require the buffer to be locked) + + @return Operation status + @retval 0 OK + @retval 1 Error +*/ + +static my_bool +translog_write_variable_record_mgroup(LSN *lsn, + enum translog_record_type type, + MARIA_HA *tbl_info, + SHORT_TRANSACTION_ID short_trid, + struct st_translog_parts *parts, + struct st_translog_buffer + *buffer_to_flush, + uint16 header_length, + translog_size_t buffer_rest, + TRN *trn, void *hook_arg) +{ + TRANSLOG_ADDRESS horizon; + struct st_buffer_cursor cursor; + int rc= 0; + uint i, chunk2_page, full_pages; + uint curr_group= 0; + translog_size_t record_rest, first_page, chunk3_pages, chunk0_pages= 1; + translog_size_t done= 0; + struct st_translog_group_descriptor group; + DYNAMIC_ARRAY groups; + uint16 chunk3_size; + uint16 page_capacity= log_descriptor.page_capacity_chunk_2 + 1; + uint16 last_page_capacity; + my_bool new_page_before_chunk0= 1, first_chunk0= 1; + uchar chunk0_header[1 + 2 + 5 + 2 + 2], group_desc[7 + 1]; + uchar chunk2_header[1]; + uint header_fixed_part= header_length + 2; + uint groups_per_page= (page_capacity - header_fixed_part) / (7 + 1); + uint file_of_the_first_group; + int pages_to_skip; + struct st_translog_buffer *buffer_of_last_lsn; + DBUG_ENTER("translog_write_variable_record_mgroup"); + translog_lock_assert_owner(); + + chunk2_header[0]= TRANSLOG_CHUNK_NOHDR; + + if (my_init_dynamic_array(&groups, + sizeof(struct st_translog_group_descriptor), + 10, 10)) + { + translog_unlock(); + DBUG_PRINT("error", ("init array failed")); + DBUG_RETURN(1); + } + + first_page= translog_get_current_page_rest(); + record_rest= parts->record_length - (first_page - 1); + DBUG_PRINT("info", ("Record Rest: %lu", (ulong) record_rest)); + + if (record_rest < buffer_rest) + { + /* + The record (group 1 type) is larger than the free space on the page + - we need to split it in two. But when we split it in two, the first + part is big enough to hold all the data of the record (because the + header of the first part of the split is smaller than the header of + the record as a whole when it takes only one chunk) + */ + DBUG_PRINT("info", ("too many free space because changing header")); + buffer_rest-= log_descriptor.page_capacity_chunk_2; + DBUG_ASSERT(record_rest >= buffer_rest); + } + + file_of_the_first_group= LSN_FILE_NO(log_descriptor.horizon); + translog_mark_file_unfinished(file_of_the_first_group); + do + { + group.addr= horizon= log_descriptor.horizon; + cursor= log_descriptor.bc; + cursor.chaser= 1; + if ((full_pages= buffer_rest / log_descriptor.page_capacity_chunk_2) > 255) + { + /* sizeof(uint8) == 256 is max number of chunk in multi-chunks group */ + full_pages= 255; + buffer_rest= full_pages * log_descriptor.page_capacity_chunk_2; + } + /* + group chunks = + full pages + first page (which actually can be full, too). + But here we assign number of chunks - 1 + */ + group.num= full_pages; + if (insert_dynamic(&groups, (uchar*) &group)) + { + DBUG_PRINT("error", ("insert into array failed")); + goto err_unlock; + } + + DBUG_PRINT("info", ("chunk: #%u first_page: %u (%u) " + "full_pages: %lu (%lu) " + "Left %lu", + groups.elements, + first_page, first_page - 1, + (ulong) full_pages, + (ulong) (full_pages * + log_descriptor.page_capacity_chunk_2), + (ulong)(parts->record_length - (first_page - 1 + + buffer_rest) - + done))); + rc|= translog_advance_pointer((int)full_pages, 0); + + translog_unlock(); + + if (buffer_to_flush != NULL) + { + translog_buffer_decrease_writers(buffer_to_flush); + if (!rc) + rc= translog_buffer_flush(buffer_to_flush); + translog_buffer_unlock(buffer_to_flush); + buffer_to_flush= NULL; + } + if (rc) + { + DBUG_PRINT("error", ("flush of unlock buffer failed")); + goto err; + } + + translog_write_data_on_page(&horizon, &cursor, 1, chunk2_header); + translog_write_parts_on_page(&horizon, &cursor, first_page - 1, parts); + DBUG_PRINT("info", ("absolute horizon: (%lu,0x%lx) local: (%lu,0x%lx) " + "Left %lu", + LSN_IN_PARTS(log_descriptor.horizon), + LSN_IN_PARTS(horizon), + (ulong) (parts->record_length - (first_page - 1) - + done))); + + for (i= 0; i < full_pages; i++) + { + if (translog_write_variable_record_chunk2_page(parts, &horizon, &cursor)) + goto err; + + DBUG_PRINT("info", ("absolute horizon: (%lu,0x%lx) " + "local: (%lu,0x%lx) " + "Left: %lu", + LSN_IN_PARTS(log_descriptor.horizon), + LSN_IN_PARTS(horizon), + (ulong) (parts->record_length - (first_page - 1) - + i * log_descriptor.page_capacity_chunk_2 - + done))); + } + + done+= (first_page - 1 + buffer_rest); + + if (translog_chaser_page_next(&horizon, &cursor)) + { + DBUG_PRINT("error", ("flush of unlock buffer failed")); + goto err; + } + translog_buffer_lock(cursor.buffer); + translog_buffer_decrease_writers(cursor.buffer); + translog_buffer_unlock(cursor.buffer); + + translog_lock(); + + /* Check that we have place for chunk type 2 */ + first_page= translog_get_current_page_rest(); + if (first_page <= 1) + { + if (translog_page_next(&log_descriptor.horizon, &log_descriptor.bc, + &buffer_to_flush)) + goto err_unlock; + first_page= translog_get_current_page_rest(); + } + buffer_rest= translog_get_current_group_size(); + } while ((translog_size_t)(first_page + buffer_rest) < + (translog_size_t)(parts->record_length - done)); + + group.addr= horizon= log_descriptor.horizon; + cursor= log_descriptor.bc; + cursor.chaser= 1; + group.num= 0; /* 0 because it does not matter */ + if (insert_dynamic(&groups, (uchar*) &group)) + { + DBUG_PRINT("error", ("insert into array failed")); + goto err_unlock; + } + record_rest= parts->record_length - done; + DBUG_PRINT("info", ("Record rest: %lu", (ulong) record_rest)); + if (first_page > record_rest + 1) + { + /* + We have not so much data to fill all first page + (no speaking about full pages) + so it will be: + <chunk0 <data>> + or + <chunk0>...<chunk0><chunk0 <data>> + or + <chunk3 <data>><chunk0>...<chunk0><chunk0 <possible data of 1 byte>> + */ + chunk2_page= full_pages= 0; + last_page_capacity= first_page; + pages_to_skip= -1; + } + else + { + /* + We will have: + <chunk2 <data>>...<chunk2 <data>><chunk0 <data>> + or + <chunk2 <data>>...<chunk2 <data>><chunk0>...<chunk0><chunk0 <data>> + or + <chunk3 <data>><chunk0>...<chunk0><chunk0 <possible data of 1 byte>> + */ + chunk2_page= 1; + record_rest-= (first_page - 1); + pages_to_skip= full_pages= + record_rest / log_descriptor.page_capacity_chunk_2; + record_rest= (record_rest % log_descriptor.page_capacity_chunk_2); + last_page_capacity= page_capacity; + } + chunk3_size= 0; + chunk3_pages= 0; + if (last_page_capacity > record_rest + 1 && record_rest != 0) + { + if (last_page_capacity > + record_rest + header_fixed_part + groups.elements * (7 + 1)) + { + /* 1 record of type 0 */ + chunk3_pages= 0; + } + else + { + pages_to_skip++; + chunk3_pages= 1; + if (record_rest + 2 == last_page_capacity) + { + chunk3_size= record_rest - 1; + record_rest= 1; + } + else + { + chunk3_size= record_rest; + record_rest= 0; + } + } + } + /* + A first non-full page will hold type 0 chunk only if it fit in it with + all its headers + */ + while (page_capacity < + record_rest + header_fixed_part + + (groups.elements - groups_per_page * (chunk0_pages - 1)) * (7 + 1)) + chunk0_pages++; + DBUG_PRINT("info", ("chunk0_pages: %u groups %u groups per full page: %u " + "Group on last page: %u", + chunk0_pages, groups.elements, + groups_per_page, + (groups.elements - + ((page_capacity - header_fixed_part) / (7 + 1)) * + (chunk0_pages - 1)))); + DBUG_PRINT("info", ("first_page: %u chunk2: %u full_pages: %u (%lu) " + "chunk3: %u (%u) rest: %u", + first_page, + chunk2_page, full_pages, + (ulong) full_pages * + log_descriptor.page_capacity_chunk_2, + chunk3_pages, (uint) chunk3_size, (uint) record_rest)); + rc= translog_advance_pointer(pages_to_skip + (int)(chunk0_pages - 1), + record_rest + header_fixed_part + + (groups.elements - + ((page_capacity - + header_fixed_part) / (7 + 1)) * + (chunk0_pages - 1)) * (7 + 1)); + buffer_of_last_lsn= log_descriptor.bc.buffer; + translog_unlock(); + + if (buffer_to_flush != NULL) + { + translog_buffer_decrease_writers(buffer_to_flush); + if (!rc) + rc= translog_buffer_flush(buffer_to_flush); + translog_buffer_unlock(buffer_to_flush); + buffer_to_flush= NULL; + } + if (rc) + { + DBUG_PRINT("error", ("flush of unlock buffer failed")); + goto err; + } + + if (rc) + goto err; + + if (chunk2_page) + { + DBUG_PRINT("info", ("chunk 2 to finish first page")); + translog_write_data_on_page(&horizon, &cursor, 1, chunk2_header); + translog_write_parts_on_page(&horizon, &cursor, first_page - 1, parts); + DBUG_PRINT("info", ("absolute horizon: (%lu,0x%lx) local: (%lu,0x%lx) " + "Left: %lu", + LSN_IN_PARTS(log_descriptor.horizon), + LSN_IN_PARTS(horizon), + (ulong) (parts->record_length - (first_page - 1) - + done))); + } + else if (chunk3_pages) + { + uchar chunk3_header[3]; + DBUG_PRINT("info", ("chunk 3")); + DBUG_ASSERT(full_pages == 0); + chunk3_pages= 0; + chunk3_header[0]= TRANSLOG_CHUNK_LNGTH; + int2store(chunk3_header + 1, chunk3_size); + translog_write_data_on_page(&horizon, &cursor, 3, chunk3_header); + translog_write_parts_on_page(&horizon, &cursor, chunk3_size, parts); + DBUG_PRINT("info", ("absolute horizon: (%lu,0x%lx) local: (%lu,0x%lx) " + "Left: %lu", + LSN_IN_PARTS(log_descriptor.horizon), + LSN_IN_PARTS(horizon), + (ulong) (parts->record_length - chunk3_size - done))); + } + else + { + DBUG_PRINT("info", ("no new_page_before_chunk0")); + new_page_before_chunk0= 0; + } + + for (i= 0; i < full_pages; i++) + { + DBUG_ASSERT(chunk2_page != 0); + if (translog_write_variable_record_chunk2_page(parts, &horizon, &cursor)) + goto err; + + DBUG_PRINT("info", ("absolute horizon: (%lu,0x%lx) local: (%lu,0x%lx) " + "Left: %lu", + LSN_IN_PARTS(log_descriptor.horizon), + LSN_IN_PARTS(horizon), + (ulong) (parts->record_length - (first_page - 1) - + i * log_descriptor.page_capacity_chunk_2 - + done))); + } + + if (chunk3_pages && + translog_write_variable_record_chunk3_page(parts, + chunk3_size, + &horizon, &cursor)) + goto err; + DBUG_PRINT("info", ("absolute horizon: (%lu,0x%lx) local: (%lu,0x%lx)", + LSN_IN_PARTS(log_descriptor.horizon), + LSN_IN_PARTS(horizon))); + + *chunk0_header= (uchar) (type | TRANSLOG_CHUNK_LSN); + int2store(chunk0_header + 1, short_trid); + translog_write_variable_record_1group_code_len(chunk0_header + 3, + parts->record_length, + header_length); + do + { + int limit; + if (new_page_before_chunk0 && + translog_chaser_page_next(&horizon, &cursor)) + { + DBUG_PRINT("error", ("flush of unlock buffer failed")); + goto err; + } + new_page_before_chunk0= 1; + + if (first_chunk0) + { + first_chunk0= 0; + + /* + We can drop "log_descriptor.is_everything_flushed" earlier when have + lock on loghandler and assign initial value of "horizon" variable or + before unlocking loghandler (because we will increase writers + counter on the buffer and every thread which wanted flush the buffer + will wait till we finish with it). But IMHO better here take short + lock and do not bother other threads with waiting. + */ + translog_lock(); + set_lsn(lsn, horizon); + buffer_of_last_lsn->last_lsn= *lsn; + DBUG_PRINT("info", ("last_lsn set to (%lu,0x%lx) buffer: 0x%lx", + LSN_IN_PARTS(buffer_of_last_lsn->last_lsn), + (ulong) buffer_of_last_lsn)); + if (log_record_type_descriptor[type].inwrite_hook && + (*log_record_type_descriptor[type].inwrite_hook) (type, trn, + tbl_info, + lsn, hook_arg)) + goto err_unlock; + translog_unlock(); + } + + /* + A first non-full page will hold type 0 chunk only if it fit in it with + all its headers => the fist page is full or number of groups less then + possible number of full page. + */ + limit= (groups_per_page < groups.elements - curr_group ? + groups_per_page : groups.elements - curr_group); + DBUG_PRINT("info", ("Groups: %u curr: %u limit: %u", + (uint) groups.elements, (uint) curr_group, + (uint) limit)); + + if (chunk0_pages == 1) + { + DBUG_PRINT("info", ("chunk_len: 2 + %u * (7+1) + %u = %u", + (uint) limit, (uint) record_rest, + (uint) (2 + limit * (7 + 1) + record_rest))); + int2store(chunk0_header + header_length - 2, + 2 + limit * (7 + 1) + record_rest); + } + else + { + DBUG_PRINT("info", ("chunk_len: 2 + %u * (7+1) = %u", + (uint) limit, (uint) (2 + limit * (7 + 1)))); + int2store(chunk0_header + header_length - 2, 2 + limit * (7 + 1)); + } + int2store(chunk0_header + header_length, groups.elements - curr_group); + translog_write_data_on_page(&horizon, &cursor, header_fixed_part, + chunk0_header); + for (i= curr_group; i < limit + curr_group; i++) + { + struct st_translog_group_descriptor *grp_ptr; + grp_ptr= dynamic_element(&groups, i, + struct st_translog_group_descriptor *); + lsn_store(group_desc, grp_ptr->addr); + group_desc[7]= grp_ptr->num; + translog_write_data_on_page(&horizon, &cursor, (7 + 1), group_desc); + } + + if (chunk0_pages == 1 && record_rest != 0) + translog_write_parts_on_page(&horizon, &cursor, record_rest, parts); + + chunk0_pages--; + curr_group+= limit; + /* put special type to indicate that it is not LSN chunk */ + *chunk0_header= (uchar) (TRANSLOG_CHUNK_LSN | TRANSLOG_CHUNK_0_CONT); + } while (chunk0_pages != 0); + translog_buffer_lock(cursor.buffer); + translog_buffer_decrease_writers(cursor.buffer); + translog_buffer_unlock(cursor.buffer); + rc= 0; + + if (translog_set_lsn_for_files(file_of_the_first_group, LSN_FILE_NO(*lsn), + *lsn, FALSE)) + goto err; + + translog_mark_file_finished(file_of_the_first_group); + + delete_dynamic(&groups); + DBUG_RETURN(rc); + +err_unlock: + + translog_unlock(); + +err: + if (buffer_to_flush != NULL) + { + /* This is to prevent locking buffer forever in case of error */ + translog_buffer_decrease_writers(buffer_to_flush); + if (!rc) + rc= translog_buffer_flush(buffer_to_flush); + translog_buffer_unlock(buffer_to_flush); + buffer_to_flush= NULL; + } + + + translog_mark_file_finished(file_of_the_first_group); + + delete_dynamic(&groups); + DBUG_RETURN(1); +} + + +/** + @brief Write the variable length log record. + + @param lsn LSN of the record will be written here + @param type the log record type + @param short_trid Short transaction ID or 0 if it has no sense + @param parts Descriptor of record source parts + @param trn Transaction structure pointer for hooks by + record log type, for short_id + @param hook_arg Argument which will be passed to pre-write and + in-write hooks of this record. + + @return Operation status + @retval 0 OK + @retval 1 Error +*/ + +static my_bool translog_write_variable_record(LSN *lsn, + enum translog_record_type type, + MARIA_HA *tbl_info, + SHORT_TRANSACTION_ID short_trid, + struct st_translog_parts *parts, + TRN *trn, void *hook_arg) +{ + struct st_translog_buffer *buffer_to_flush= NULL; + uint header_length1= 1 + 2 + 2 + + translog_variable_record_length_bytes(parts->record_length); + ulong buffer_rest; + uint page_rest; + /* Max number of such LSNs per record is 2 */ + uchar compressed_LSNs[MAX_NUMBER_OF_LSNS_PER_RECORD * + COMPRESSED_LSN_MAX_STORE_SIZE]; + my_bool res; + DBUG_ENTER("translog_write_variable_record"); + + translog_lock(); + DBUG_PRINT("info", ("horizon: (%lu,0x%lx)", + LSN_IN_PARTS(log_descriptor.horizon))); + page_rest= TRANSLOG_PAGE_SIZE - log_descriptor.bc.current_page_fill; + DBUG_PRINT("info", ("header length: %u page_rest: %u", + header_length1, page_rest)); + + /* + header and part which we should read have to fit in one chunk + TODO: allow to divide readable header + */ + if (page_rest < + (header_length1 + log_record_type_descriptor[type].read_header_len)) + { + DBUG_PRINT("info", + ("Next page, size: %u header: %u + %u", + log_descriptor.bc.current_page_fill, + header_length1, + log_record_type_descriptor[type].read_header_len)); + translog_page_next(&log_descriptor.horizon, &log_descriptor.bc, + &buffer_to_flush); + /* Chunk 2 header is 1 byte, so full page capacity will be one uchar more */ + page_rest= log_descriptor.page_capacity_chunk_2 + 1; + DBUG_PRINT("info", ("page_rest: %u", page_rest)); + } + + /* + To minimize compressed size we will compress always relative to + very first chunk address (log_descriptor.horizon for now) + */ + if (log_record_type_descriptor[type].compressed_LSN > 0) + { + translog_relative_LSN_encode(parts, log_descriptor.horizon, + log_record_type_descriptor[type]. + compressed_LSN, compressed_LSNs); + /* recalculate header length after compression */ + header_length1= 1 + 2 + 2 + + translog_variable_record_length_bytes(parts->record_length); + DBUG_PRINT("info", ("after compressing LSN(s) header length: %u " + "record length: %lu", + header_length1, (ulong)parts->record_length)); + } + + /* TODO: check space on current page for header + few bytes */ + if (page_rest >= parts->record_length + header_length1) + { + /* following function makes translog_unlock(); */ + res= translog_write_variable_record_1chunk(lsn, type, tbl_info, + short_trid, + parts, buffer_to_flush, + header_length1, trn, hook_arg); + DBUG_RETURN(res); + } + + buffer_rest= translog_get_current_group_size(); + + if (buffer_rest >= parts->record_length + header_length1 - page_rest) + { + /* following function makes translog_unlock(); */ + res= translog_write_variable_record_1group(lsn, type, tbl_info, + short_trid, + parts, buffer_to_flush, + header_length1, trn, hook_arg); + DBUG_RETURN(res); + } + /* following function makes translog_unlock(); */ + res= translog_write_variable_record_mgroup(lsn, type, tbl_info, + short_trid, + parts, buffer_to_flush, + header_length1, + buffer_rest, trn, hook_arg); + DBUG_RETURN(res); +} + + +/** + @brief Write the fixed and pseudo-fixed log record. + + @param lsn LSN of the record will be written here + @param type the log record type + @param short_trid Short transaction ID or 0 if it has no sense + @param parts Descriptor of record source parts + @param trn Transaction structure pointer for hooks by + record log type, for short_id + @param hook_arg Argument which will be passed to pre-write and + in-write hooks of this record. + + @return Operation status + @retval 0 OK + @retval 1 Error +*/ + +static my_bool translog_write_fixed_record(LSN *lsn, + enum translog_record_type type, + MARIA_HA *tbl_info, + SHORT_TRANSACTION_ID short_trid, + struct st_translog_parts *parts, + TRN *trn, void *hook_arg) +{ + struct st_translog_buffer *buffer_to_flush= NULL; + uchar chunk1_header[1 + 2]; + /* Max number of such LSNs per record is 2 */ + uchar compressed_LSNs[MAX_NUMBER_OF_LSNS_PER_RECORD * + COMPRESSED_LSN_MAX_STORE_SIZE]; + LEX_CUSTRING *part; + int rc= 1; + DBUG_ENTER("translog_write_fixed_record"); + DBUG_ASSERT((log_record_type_descriptor[type].rclass == + LOGRECTYPE_FIXEDLENGTH && + parts->record_length == + log_record_type_descriptor[type].fixed_length) || + (log_record_type_descriptor[type].rclass == + LOGRECTYPE_PSEUDOFIXEDLENGTH && + parts->record_length == + log_record_type_descriptor[type].fixed_length)); + + translog_lock(); + DBUG_PRINT("info", ("horizon: (%lu,0x%lx)", + LSN_IN_PARTS(log_descriptor.horizon))); + + DBUG_ASSERT(log_descriptor.bc.current_page_fill <= TRANSLOG_PAGE_SIZE); + DBUG_PRINT("info", + ("Page size: %u record: %u next cond: %d", + log_descriptor.bc.current_page_fill, + (parts->record_length + + log_record_type_descriptor[type].compressed_LSN * 2 + 3), + ((((uint) log_descriptor.bc.current_page_fill) + + (parts->record_length + + log_record_type_descriptor[type].compressed_LSN * 2 + 3)) > + TRANSLOG_PAGE_SIZE))); + /* + check that there is enough place on current page. + NOTE: compressing may increase page LSN size on two bytes for every LSN + */ + if ((((uint) log_descriptor.bc.current_page_fill) + + (parts->record_length + + log_record_type_descriptor[type].compressed_LSN * 2 + 3)) > + TRANSLOG_PAGE_SIZE) + { + DBUG_PRINT("info", ("Next page")); + if (translog_page_next(&log_descriptor.horizon, &log_descriptor.bc, + &buffer_to_flush)) + goto err; /* rc == 1 */ + if (buffer_to_flush) + translog_buffer_lock_assert_owner(buffer_to_flush); + } + + set_lsn(lsn, log_descriptor.horizon); + if (translog_set_lsn_for_files(LSN_FILE_NO(*lsn), LSN_FILE_NO(*lsn), + *lsn, TRUE) || + (log_record_type_descriptor[type].inwrite_hook && + (*log_record_type_descriptor[type].inwrite_hook)(type, trn, tbl_info, + lsn, hook_arg))) + goto err; + + /* compress LSNs */ + if (log_record_type_descriptor[type].rclass == + LOGRECTYPE_PSEUDOFIXEDLENGTH) + { + DBUG_ASSERT(log_record_type_descriptor[type].compressed_LSN > 0); + translog_relative_LSN_encode(parts, *lsn, + log_record_type_descriptor[type]. + compressed_LSN, compressed_LSNs); + } + + /* + Write the whole record at once (we know that there is enough place on + the destination page) + */ + DBUG_ASSERT(parts->current != 0); /* first part is left for header */ + part= parts->parts + (--parts->current); + parts->total_record_length+= (translog_size_t) (part->length= 1 + 2); + part->str= chunk1_header; + *chunk1_header= (uchar) (type | TRANSLOG_CHUNK_FIXED); + int2store(chunk1_header + 1, short_trid); + + rc= translog_write_parts_on_page(&log_descriptor.horizon, + &log_descriptor.bc, + parts->total_record_length, parts); + + log_descriptor.bc.buffer->last_lsn= *lsn; + DBUG_PRINT("info", ("last_lsn set to (%lu,0x%lx) buffer: 0x%lx", + LSN_IN_PARTS(log_descriptor.bc.buffer->last_lsn), + (ulong) log_descriptor.bc.buffer)); + +err: + translog_unlock(); + + /* + check if we switched buffer and need process it (current buffer is + unlocked already => we will not delay other threads + */ + if (buffer_to_flush != NULL) + { + if (!rc) + rc= translog_buffer_flush(buffer_to_flush); + translog_buffer_unlock(buffer_to_flush); + } + + DBUG_RETURN(rc); +} + + +/** + @brief Writes the log record + + If share has no 2-byte-id yet, gives an id to the share and logs + LOGREC_FILE_ID. If transaction has not logged LOGREC_LONG_TRANSACTION_ID + yet, logs it. + + @param lsn LSN of the record will be written here + @param type the log record type + @param trn Transaction structure pointer for hooks by + record log type, for short_id + @param tbl_info MARIA_HA of table or NULL + @param rec_len record length or 0 (count it) + @param part_no number of parts or 0 (count it) + @param parts_data zero ended (in case of number of parts is 0) + array of LEX_STRINGs (parts), first + TRANSLOG_INTERNAL_PARTS positions in the log + should be unused (need for loghandler) + @param store_share_id if tbl_info!=NULL then share's id will + automatically be stored in the two first bytes + pointed (so pointer is assumed to be !=NULL) + @param hook_arg argument which will be passed to pre-write and + in-write hooks of this record. + + @return Operation status + @retval 0 OK + @retval 1 Error +*/ + +my_bool translog_write_record(LSN *lsn, + enum translog_record_type type, + TRN *trn, MARIA_HA *tbl_info, + translog_size_t rec_len, + uint part_no, + LEX_CUSTRING *parts_data, + uchar *store_share_id, + void *hook_arg) +{ + struct st_translog_parts parts; + LEX_CUSTRING *part; + int rc; + uint short_trid= trn->short_id; + DBUG_ENTER("translog_write_record"); + DBUG_PRINT("enter", ("type: %u (%s) ShortTrID: %u rec_len: %lu", + (uint) type, log_record_type_descriptor[type].name, + (uint) short_trid, (ulong) rec_len)); + DBUG_ASSERT(translog_status == TRANSLOG_OK || + translog_status == TRANSLOG_READONLY); + if (unlikely(translog_status != TRANSLOG_OK)) + { + DBUG_PRINT("error", ("Transaction log is write protected")); + DBUG_RETURN(1); + } + + if (tbl_info) + { + MARIA_SHARE *share= tbl_info->s; + DBUG_ASSERT(share->now_transactional); + if (unlikely(share->id == 0)) + { + /* + First log write for this MARIA_SHARE; give it a short id. + When the lock manager is enabled and needs a short id, it should be + assigned in the lock manager (because row locks will be taken before + log records are written; for example SELECT FOR UPDATE takes locks but + writes no log record. + */ + if (unlikely(translog_assign_id_to_share(tbl_info, trn))) + DBUG_RETURN(1); + } + fileid_store(store_share_id, share->id); + } + if (unlikely(!(trn->first_undo_lsn & TRANSACTION_LOGGED_LONG_ID))) + { + LSN dummy_lsn; + LEX_CUSTRING log_array[TRANSLOG_INTERNAL_PARTS + 1]; + uchar log_data[6]; + DBUG_ASSERT(trn->undo_lsn == LSN_IMPOSSIBLE); + int6store(log_data, trn->trid); + log_array[TRANSLOG_INTERNAL_PARTS + 0].str= log_data; + log_array[TRANSLOG_INTERNAL_PARTS + 0].length= sizeof(log_data); + trn->first_undo_lsn|= TRANSACTION_LOGGED_LONG_ID; /* no recursion */ + if (unlikely(translog_write_record(&dummy_lsn, LOGREC_LONG_TRANSACTION_ID, + trn, NULL, sizeof(log_data), + sizeof(log_array)/sizeof(log_array[0]), + log_array, NULL, NULL))) + DBUG_RETURN(1); + } + + parts.parts= parts_data; + + /* count parts if they are not counted by upper level */ + if (part_no == 0) + { + for (part_no= TRANSLOG_INTERNAL_PARTS; + parts_data[part_no].length != 0; + part_no++); + } + parts.elements= part_no; + parts.current= TRANSLOG_INTERNAL_PARTS; + + /* clear TRANSLOG_INTERNAL_PARTS */ + compile_time_assert(TRANSLOG_INTERNAL_PARTS != 0); + parts_data[0].str= 0; + parts_data[0].length= 0; + + /* count length of the record */ + if (rec_len == 0) + { + for(part= parts_data + TRANSLOG_INTERNAL_PARTS;\ + part < parts_data + part_no; + part++) + { + rec_len+= (translog_size_t) part->length; + } + } + parts.record_length= rec_len; + +#ifndef DBUG_OFF + { + uint i; + uint len= 0; +#ifdef HAVE_valgrind + ha_checksum checksum= 0; +#endif + for (i= TRANSLOG_INTERNAL_PARTS; i < part_no; i++) + { +#ifdef HAVE_valgrind + /* Find unitialized bytes early */ + checksum+= my_checksum(checksum, parts_data[i].str, + parts_data[i].length); +#endif + len+= parts_data[i].length; + } + DBUG_ASSERT(len == rec_len); + } +#endif + /* + Start total_record_length from record_length then overhead will + be add + */ + parts.total_record_length= parts.record_length; + DBUG_PRINT("info", ("record length: %lu", (ulong) parts.record_length)); + + /* process this parts */ + if (!(rc= (log_record_type_descriptor[type].prewrite_hook && + (*log_record_type_descriptor[type].prewrite_hook) (type, trn, + tbl_info, + hook_arg)))) + { + switch (log_record_type_descriptor[type].rclass) { + case LOGRECTYPE_VARIABLE_LENGTH: + rc= translog_write_variable_record(lsn, type, tbl_info, + short_trid, &parts, trn, hook_arg); + break; + case LOGRECTYPE_PSEUDOFIXEDLENGTH: + case LOGRECTYPE_FIXEDLENGTH: + rc= translog_write_fixed_record(lsn, type, tbl_info, + short_trid, &parts, trn, hook_arg); + break; + case LOGRECTYPE_NOT_ALLOWED: + default: + DBUG_ASSERT(0); + rc= 1; + } + } + + DBUG_PRINT("info", ("LSN: (%lu,0x%lx)", LSN_IN_PARTS(*lsn))); + DBUG_RETURN(rc); +} + + +/* + Decode compressed (relative) LSN(s) + + SYNOPSIS + translog_relative_lsn_decode() + base_lsn LSN for encoding + src Decode LSN(s) from here + dst Put decoded LSNs here + lsns number of LSN(s) + + RETURN + position in sources after decoded LSN(s) +*/ + +static uchar *translog_relative_LSN_decode(LSN base_lsn, + uchar *src, uchar *dst, uint lsns) +{ + uint i; + for (i= 0; i < lsns; i++, dst+= LSN_STORE_SIZE) + { + src= translog_get_LSN_from_diff(base_lsn, src, dst); + } + return src; +} + +/** + @brief Get header of fixed/pseudo length record and call hook for + it processing + + @param page Pointer to the buffer with page where LSN chunk is + placed + @param page_offset Offset of the first chunk in the page + @param buff Buffer to be filled with header data + + @return Length of header or operation status + @retval # number of bytes in TRANSLOG_HEADER_BUFFER::header where + stored decoded part of the header +*/ + +static int translog_fixed_length_header(uchar *page, + translog_size_t page_offset, + TRANSLOG_HEADER_BUFFER *buff) +{ + struct st_log_record_type_descriptor *desc= + log_record_type_descriptor + buff->type; + uchar *src= page + page_offset + 3; + uchar *dst= buff->header; + uchar *start= src; + int lsns= desc->compressed_LSN; + uint length= desc->fixed_length; + DBUG_ENTER("translog_fixed_length_header"); + + buff->record_length= length; + + if (desc->rclass == LOGRECTYPE_PSEUDOFIXEDLENGTH) + { + DBUG_ASSERT(lsns > 0); + src= translog_relative_LSN_decode(buff->lsn, src, dst, lsns); + lsns*= LSN_STORE_SIZE; + dst+= lsns; + length-= lsns; + buff->compressed_LSN_economy= (lsns - (int) (src - start)); + } + else + buff->compressed_LSN_economy= 0; + + memcpy(dst, src, length); + buff->non_header_data_start_offset= (uint16) (page_offset + + ((src + length) - + (page + page_offset))); + buff->non_header_data_len= 0; + DBUG_RETURN(buff->record_length); +} + + +/* + Free resources used by TRANSLOG_HEADER_BUFFER + + SYNOPSIS + translog_free_record_header(); +*/ + +void translog_free_record_header(TRANSLOG_HEADER_BUFFER *buff) +{ + DBUG_ENTER("translog_free_record_header"); + if (buff->groups_no != 0) + { + my_free(buff->groups, MYF(0)); + buff->groups_no= 0; + } + DBUG_VOID_RETURN; +} + + +/** + @brief Returns the current horizon at the end of the current log + + @return Horizon + @retval LSN_ERROR error + @retvar # Horizon +*/ + +TRANSLOG_ADDRESS translog_get_horizon() +{ + TRANSLOG_ADDRESS res; + DBUG_ASSERT(translog_status == TRANSLOG_OK || + translog_status == TRANSLOG_READONLY); + translog_lock(); + res= log_descriptor.horizon; + translog_unlock(); + return res; +} + + +/** + @brief Returns the current horizon at the end of the current log, caller is + assumed to already hold the lock + + @return Horizon + @retval LSN_ERROR error + @retvar # Horizon +*/ + +TRANSLOG_ADDRESS translog_get_horizon_no_lock() +{ + DBUG_ASSERT(translog_status == TRANSLOG_OK || + translog_status == TRANSLOG_READONLY); + translog_lock_assert_owner(); + return log_descriptor.horizon; +} + + +/* + Set last page in the scanner data structure + + SYNOPSIS + translog_scanner_set_last_page() + scanner Information about current chunk during scanning + + RETURN + 0 OK + 1 Error +*/ + +static my_bool translog_scanner_set_last_page(TRANSLOG_SCANNER_DATA *scanner) +{ + my_bool page_ok; + if (LSN_FILE_NO(scanner->page_addr) == LSN_FILE_NO(scanner->horizon)) + { + /* It is last file => we can easy find last page address by horizon */ + uint pagegrest= LSN_OFFSET(scanner->horizon) % TRANSLOG_PAGE_SIZE; + scanner->last_file_page= (scanner->horizon - + (pagegrest ? pagegrest : TRANSLOG_PAGE_SIZE)); + return (0); + } + scanner->last_file_page= scanner->page_addr; + return (translog_get_last_page_addr(&scanner->last_file_page, &page_ok, 0)); +} + + +/** + @brief Get page from page cache according to requested method + + @param scanner The scanner data + + @return operation status + @retval 0 OK + @retval 1 Error +*/ + +static my_bool +translog_scanner_get_page(TRANSLOG_SCANNER_DATA *scanner) +{ + TRANSLOG_VALIDATOR_DATA data; + DBUG_ENTER("translog_scanner_get_page"); + data.addr= &scanner->page_addr; + data.was_recovered= 0; + DBUG_RETURN((scanner->page= + translog_get_page(&data, scanner->buffer, + (scanner->use_direct_link ? + &scanner->direct_link : + NULL))) == + NULL); +} + + +/** + @brief Initialize reader scanner. + + @param lsn LSN with which it have to be inited + @param fixed_horizon true if it is OK do not read records which was written + after scanning beginning + @param scanner scanner which have to be inited + @param use_direct prefer using direct lings from page handler + where it is possible. + + @note If direct link was used translog_destroy_scanner should be + called after it using + + @return status of the operation + @retval 0 OK + @retval 1 Error +*/ + +my_bool translog_scanner_init(LSN lsn, + my_bool fixed_horizon, + TRANSLOG_SCANNER_DATA *scanner, + my_bool use_direct) +{ + TRANSLOG_VALIDATOR_DATA data; + DBUG_ENTER("translog_scanner_init"); + DBUG_PRINT("enter", ("Scanner: 0x%lx LSN: (%lu,0x%lx)", + (ulong) scanner, LSN_IN_PARTS(lsn))); + DBUG_ASSERT(translog_status == TRANSLOG_OK || + translog_status == TRANSLOG_READONLY); + + data.addr= &scanner->page_addr; + data.was_recovered= 0; + + scanner->page_offset= LSN_OFFSET(lsn) % TRANSLOG_PAGE_SIZE; + + scanner->fixed_horizon= fixed_horizon; + scanner->use_direct_link= use_direct; + scanner->direct_link= NULL; + + scanner->horizon= translog_get_horizon(); + DBUG_PRINT("info", ("horizon: (%lu,0x%lx)", LSN_IN_PARTS(scanner->horizon))); + + /* lsn < horizon */ + DBUG_ASSERT(lsn <= scanner->horizon); + + scanner->page_addr= lsn; + scanner->page_addr-= scanner->page_offset; /*decrease offset */ + + if (translog_scanner_set_last_page(scanner)) + DBUG_RETURN(1); + + if (translog_scanner_get_page(scanner)) + DBUG_RETURN(1); + DBUG_RETURN(0); +} + + +/** + @brief Destroy scanner object; + + @param scanner The scanner object to destroy +*/ + +void translog_destroy_scanner(TRANSLOG_SCANNER_DATA *scanner) +{ + DBUG_ENTER("translog_destroy_scanner"); + DBUG_PRINT("enter", ("Scanner: 0x%lx", (ulong)scanner)); + translog_free_link(scanner->direct_link); + DBUG_VOID_RETURN; +} + + +/* + Checks End of the Log + + SYNOPSIS + translog_scanner_eol() + scanner Information about current chunk during scanning + + RETURN + 1 End of the Log + 0 OK +*/ + +static my_bool translog_scanner_eol(TRANSLOG_SCANNER_DATA *scanner) +{ + DBUG_ENTER("translog_scanner_eol"); + DBUG_PRINT("enter", + ("Horizon: (%lu, 0x%lx) Current: (%lu, 0x%lx+0x%x=0x%lx)", + LSN_IN_PARTS(scanner->horizon), + LSN_IN_PARTS(scanner->page_addr), + (uint) scanner->page_offset, + (ulong) (LSN_OFFSET(scanner->page_addr) + scanner->page_offset))); + if (scanner->horizon > (scanner->page_addr + + scanner->page_offset)) + { + DBUG_PRINT("info", ("Horizon is not reached")); + DBUG_RETURN(0); + } + if (scanner->fixed_horizon) + { + DBUG_PRINT("info", ("Horizon is fixed and reached")); + DBUG_RETURN(1); + } + scanner->horizon= translog_get_horizon(); + DBUG_PRINT("info", + ("Horizon is re-read, EOL: %d", + scanner->horizon <= (scanner->page_addr + + scanner->page_offset))); + DBUG_RETURN(scanner->horizon <= (scanner->page_addr + + scanner->page_offset)); +} + + +/** + @brief Cheks End of the Page + + @param scanner Information about current chunk during scanning + + @retval 1 End of the Page + @retval 0 OK +*/ + +static my_bool translog_scanner_eop(TRANSLOG_SCANNER_DATA *scanner) +{ + DBUG_ENTER("translog_scanner_eop"); + DBUG_RETURN(scanner->page_offset >= TRANSLOG_PAGE_SIZE || + scanner->page[scanner->page_offset] == TRANSLOG_FILLER); +} + + +/** + @brief Checks End of the File (i.e. we are scanning last page, which do not + mean end of this page) + + @param scanner Information about current chunk during scanning + + @retval 1 End of the File + @retval 0 OK +*/ + +static my_bool translog_scanner_eof(TRANSLOG_SCANNER_DATA *scanner) +{ + DBUG_ENTER("translog_scanner_eof"); + DBUG_ASSERT(LSN_FILE_NO(scanner->page_addr) == + LSN_FILE_NO(scanner->last_file_page)); + DBUG_PRINT("enter", ("curr Page: 0x%lx last page: 0x%lx " + "normal EOF: %d", + (ulong) LSN_OFFSET(scanner->page_addr), + (ulong) LSN_OFFSET(scanner->last_file_page), + LSN_OFFSET(scanner->page_addr) == + LSN_OFFSET(scanner->last_file_page))); + /* + TODO: detect damaged file EOF, + TODO: issue warning if damaged file EOF detected + */ + DBUG_RETURN(scanner->page_addr == + scanner->last_file_page); +} + +/* + Move scanner to the next chunk + + SYNOPSIS + translog_get_next_chunk() + scanner Information about current chunk during scanning + + RETURN + 0 OK + 1 Error +*/ + +static my_bool +translog_get_next_chunk(TRANSLOG_SCANNER_DATA *scanner) +{ + uint16 len; + DBUG_ENTER("translog_get_next_chunk"); + + if (translog_scanner_eop(scanner)) + len= TRANSLOG_PAGE_SIZE - scanner->page_offset; + else if ((len= translog_get_total_chunk_length(scanner->page, + scanner->page_offset)) == 0) + DBUG_RETURN(1); + scanner->page_offset+= len; + + if (translog_scanner_eol(scanner)) + { + scanner->page= END_OF_LOG; + scanner->page_offset= 0; + DBUG_RETURN(0); + } + if (translog_scanner_eop(scanner)) + { + /* before reading next page we should unpin current one if it was pinned */ + translog_free_link(scanner->direct_link); + if (translog_scanner_eof(scanner)) + { + DBUG_PRINT("info", ("horizon: (%lu,0x%lx) pageaddr: (%lu,0x%lx)", + LSN_IN_PARTS(scanner->horizon), + LSN_IN_PARTS(scanner->page_addr))); + /* if it is log end it have to be caught before */ + DBUG_ASSERT(LSN_FILE_NO(scanner->horizon) > + LSN_FILE_NO(scanner->page_addr)); + scanner->page_addr+= LSN_ONE_FILE; + scanner->page_addr= LSN_REPLACE_OFFSET(scanner->page_addr, + TRANSLOG_PAGE_SIZE); + if (translog_scanner_set_last_page(scanner)) + DBUG_RETURN(1); + } + else + { + scanner->page_addr+= TRANSLOG_PAGE_SIZE; /* offset increased */ + } + + if (translog_scanner_get_page(scanner)) + DBUG_RETURN(1); + + scanner->page_offset= translog_get_first_chunk_offset(scanner->page); + if (translog_scanner_eol(scanner)) + { + scanner->page= END_OF_LOG; + scanner->page_offset= 0; + DBUG_RETURN(0); + } + DBUG_ASSERT(scanner->page[scanner->page_offset] != TRANSLOG_FILLER); + } + DBUG_RETURN(0); +} + + +/** + @brief Get header of variable length record and call hook for it processing + + @param page Pointer to the buffer with page where LSN chunk is + placed + @param page_offset Offset of the first chunk in the page + @param buff Buffer to be filled with header data + @param scanner If present should be moved to the header page if + it differ from LSN page + + @return Length of header or operation status + @retval RECHEADER_READ_ERROR error + @retval RECHEADER_READ_EOF End of the log reached during the read + @retval # number of bytes in + TRANSLOG_HEADER_BUFFER::header where + stored decoded part of the header +*/ + +static int +translog_variable_length_header(uchar *page, translog_size_t page_offset, + TRANSLOG_HEADER_BUFFER *buff, + TRANSLOG_SCANNER_DATA *scanner) +{ + struct st_log_record_type_descriptor *desc= (log_record_type_descriptor + + buff->type); + uchar *src= page + page_offset + 1 + 2; + uchar *dst= buff->header; + LSN base_lsn; + uint lsns= desc->compressed_LSN; + uint16 chunk_len; + uint16 length= desc->read_header_len; + uint16 buffer_length= length; + uint16 body_len; + int rc; + TRANSLOG_SCANNER_DATA internal_scanner; + DBUG_ENTER("translog_variable_length_header"); + + buff->record_length= translog_variable_record_1group_decode_len(&src); + chunk_len= uint2korr(src); + DBUG_PRINT("info", ("rec len: %lu chunk len: %u length: %u bufflen: %u", + (ulong) buff->record_length, (uint) chunk_len, + (uint) length, (uint) buffer_length)); + if (chunk_len == 0) + { + uint16 page_rest; + DBUG_PRINT("info", ("1 group")); + src+= 2; + page_rest= (uint16) (TRANSLOG_PAGE_SIZE - (src - page)); + + base_lsn= buff->lsn; + body_len= min(page_rest, buff->record_length); + } + else + { + uint grp_no, curr; + uint header_to_skip; + uint16 page_rest; + + DBUG_PRINT("info", ("multi-group")); + grp_no= buff->groups_no= uint2korr(src + 2); + if (!(buff->groups= + (TRANSLOG_GROUP*) my_malloc(sizeof(TRANSLOG_GROUP) * grp_no, + MYF(0)))) + DBUG_RETURN(RECHEADER_READ_ERROR); + DBUG_PRINT("info", ("Groups: %u", (uint) grp_no)); + src+= (2 + 2); + page_rest= (uint16) (TRANSLOG_PAGE_SIZE - (src - page)); + curr= 0; + header_to_skip= src - (page + page_offset); + buff->chunk0_pages= 0; + + for (;;) + { + uint i, read_length= grp_no; + + buff->chunk0_pages++; + if (page_rest < grp_no * (7 + 1)) + read_length= page_rest / (7 + 1); + DBUG_PRINT("info", ("Read chunk0 page#%u read: %u left: %u " + "start from: %u", + buff->chunk0_pages, read_length, grp_no, curr)); + for (i= 0; i < read_length; i++, curr++) + { + DBUG_ASSERT(curr < buff->groups_no); + buff->groups[curr].addr= lsn_korr(src + i * (7 + 1)); + buff->groups[curr].num= src[i * (7 + 1) + 7]; + DBUG_PRINT("info", ("group #%u (%lu,0x%lx) chunks: %u", + curr, + LSN_IN_PARTS(buff->groups[curr].addr), + (uint) buff->groups[curr].num)); + } + grp_no-= read_length; + if (grp_no == 0) + { + if (scanner) + { + buff->chunk0_data_addr= scanner->page_addr; + /* offset increased */ + buff->chunk0_data_addr+= (page_offset + header_to_skip + + read_length * (7 + 1)); + } + else + { + buff->chunk0_data_addr= buff->lsn; + /* offset increased */ + buff->chunk0_data_addr+= (header_to_skip + read_length * (7 + 1)); + } + buff->chunk0_data_len= chunk_len - 2 - read_length * (7 + 1); + DBUG_PRINT("info", ("Data address: (%lu,0x%lx) len: %u", + LSN_IN_PARTS(buff->chunk0_data_addr), + buff->chunk0_data_len)); + break; + } + if (scanner == NULL) + { + DBUG_PRINT("info", ("use internal scanner for header reading")); + scanner= &internal_scanner; + if (translog_scanner_init(buff->lsn, 1, scanner, 0)) + { + rc= RECHEADER_READ_ERROR; + goto exit_and_free; + } + } + if (translog_get_next_chunk(scanner)) + { + if (scanner == &internal_scanner) + translog_destroy_scanner(scanner); + rc= RECHEADER_READ_ERROR; + goto exit_and_free; + } + if (scanner->page == END_OF_LOG) + { + if (scanner == &internal_scanner) + translog_destroy_scanner(scanner); + rc= RECHEADER_READ_EOF; + goto exit_and_free; + } + page= scanner->page; + page_offset= scanner->page_offset; + src= page + page_offset + header_to_skip; + chunk_len= uint2korr(src - 2 - 2); + DBUG_PRINT("info", ("Chunk len: %u", (uint) chunk_len)); + page_rest= (uint16) (TRANSLOG_PAGE_SIZE - (src - page)); + } + + if (scanner == NULL) + { + DBUG_PRINT("info", ("use internal scanner")); + scanner= &internal_scanner; + } + else + { + translog_destroy_scanner(scanner); + } + base_lsn= buff->groups[0].addr; + translog_scanner_init(base_lsn, 1, scanner, scanner == &internal_scanner); + /* first group chunk is always chunk type 2 */ + page= scanner->page; + page_offset= scanner->page_offset; + src= page + page_offset + 1; + page_rest= (uint16) (TRANSLOG_PAGE_SIZE - (src - page)); + body_len= page_rest; + if (scanner == &internal_scanner) + translog_destroy_scanner(scanner); + } + if (lsns) + { + uchar *start= src; + src= translog_relative_LSN_decode(base_lsn, src, dst, lsns); + lsns*= LSN_STORE_SIZE; + dst+= lsns; + length-= lsns; + buff->record_length+= (buff->compressed_LSN_economy= + (int) (lsns - (src - start))); + DBUG_PRINT("info", ("lsns: %u length: %u economy: %d new length: %lu", + lsns / LSN_STORE_SIZE, (uint) length, + (int) buff->compressed_LSN_economy, + (ulong) buff->record_length)); + body_len-= (uint16) (src - start); + } + else + buff->compressed_LSN_economy= 0; + + DBUG_ASSERT(body_len >= length); + body_len-= length; + memcpy(dst, src, length); + buff->non_header_data_start_offset= (uint16) (src + length - page); + buff->non_header_data_len= body_len; + DBUG_PRINT("info", ("non_header_data_start_offset: %u len: %u buffer: %u", + buff->non_header_data_start_offset, + buff->non_header_data_len, buffer_length)); + DBUG_RETURN(buffer_length); + +exit_and_free: + my_free(buff->groups, MYF(0)); + buff->groups_no= 0; /* prevent try to use of buff->groups */ + DBUG_RETURN(rc); +} + + +/** + @brief Read record header from the given buffer + + @param page page content buffer + @param page_offset offset of the chunk in the page + @param buff destination buffer + @param scanner If this is set the scanner will be moved to the + record header page (differ from LSN page in case of + multi-group records) + + @return Length of header or operation status + @retval RECHEADER_READ_ERROR error + @retval # number of bytes in + TRANSLOG_HEADER_BUFFER::header where + stored decoded part of the header +*/ + +int translog_read_record_header_from_buffer(uchar *page, + uint16 page_offset, + TRANSLOG_HEADER_BUFFER *buff, + TRANSLOG_SCANNER_DATA *scanner) +{ + translog_size_t res; + DBUG_ENTER("translog_read_record_header_from_buffer"); + DBUG_ASSERT(translog_is_LSN_chunk(page[page_offset])); + DBUG_ASSERT(translog_status == TRANSLOG_OK || + translog_status == TRANSLOG_READONLY); + DBUG_PRINT("info", ("page byte: 0x%x offset: %u", + (uint) page[page_offset], (uint) page_offset)); + buff->type= (page[page_offset] & TRANSLOG_REC_TYPE); + buff->short_trid= uint2korr(page + page_offset + 1); + DBUG_PRINT("info", ("Type %u, Short TrID %u, LSN (%lu,0x%lx)", + (uint) buff->type, (uint)buff->short_trid, + LSN_IN_PARTS(buff->lsn))); + /* Read required bytes from the header and call hook */ + switch (log_record_type_descriptor[buff->type].rclass) { + case LOGRECTYPE_VARIABLE_LENGTH: + res= translog_variable_length_header(page, page_offset, buff, + scanner); + break; + case LOGRECTYPE_PSEUDOFIXEDLENGTH: + case LOGRECTYPE_FIXEDLENGTH: + res= translog_fixed_length_header(page, page_offset, buff); + break; + default: + DBUG_ASSERT(0); /* we read some junk (got no LSN) */ + res= RECHEADER_READ_ERROR; + } + DBUG_RETURN(res); +} + + +/** + @brief Read record header and some fixed part of a record (the part depend + on record type). + + @param lsn log record serial number (address of the record) + @param buff log record header buffer + + @note Some type of record can be read completely by this call + @note "Decoded" header stored in TRANSLOG_HEADER_BUFFER::header (relative + LSN can be translated to absolute one), some fields can be added (like + actual header length in the record if the header has variable length) + + @return Length of header or operation status + @retval RECHEADER_READ_ERROR error + @retval # number of bytes in + TRANSLOG_HEADER_BUFFER::header where + stored decoded part of the header +*/ + +int translog_read_record_header(LSN lsn, TRANSLOG_HEADER_BUFFER *buff) +{ + TRANSLOG_PAGE_SIZE_BUFF psize_buff; + uchar *page; + translog_size_t res, page_offset= LSN_OFFSET(lsn) % TRANSLOG_PAGE_SIZE; + PAGECACHE_BLOCK_LINK *direct_link; + TRANSLOG_ADDRESS addr; + TRANSLOG_VALIDATOR_DATA data; + DBUG_ENTER("translog_read_record_header"); + DBUG_PRINT("enter", ("LSN: (%lu,0x%lx)", LSN_IN_PARTS(lsn))); + DBUG_ASSERT(LSN_OFFSET(lsn) % TRANSLOG_PAGE_SIZE != 0); + DBUG_ASSERT(translog_status == TRANSLOG_OK || + translog_status == TRANSLOG_READONLY); + + buff->lsn= lsn; + buff->groups_no= 0; + data.addr= &addr; + data.was_recovered= 0; + addr= lsn; + addr-= page_offset; /* offset decreasing */ + res= (!(page= translog_get_page(&data, psize_buff.buffer, &direct_link))) ? + RECHEADER_READ_ERROR : + translog_read_record_header_from_buffer(page, page_offset, buff, 0); + translog_free_link(direct_link); + DBUG_RETURN(res); +} + + +/** + @brief Read record header and some fixed part of a record (the part depend + on record type). + + @param scan scanner position to read + @param buff log record header buffer + @param move_scanner request to move scanner to the header position + + @note Some type of record can be read completely by this call + @note "Decoded" header stored in TRANSLOG_HEADER_BUFFER::header (relative + LSN can be translated to absolute one), some fields can be added (like + actual header length in the record if the header has variable length) + + @return Length of header or operation status + @retval RECHEADER_READ_ERROR error + @retval # number of bytes in + TRANSLOG_HEADER_BUFFER::header where stored + decoded part of the header +*/ + +int translog_read_record_header_scan(TRANSLOG_SCANNER_DATA *scanner, + TRANSLOG_HEADER_BUFFER *buff, + my_bool move_scanner) +{ + translog_size_t res; + DBUG_ENTER("translog_read_record_header_scan"); + DBUG_PRINT("enter", ("Scanner: Cur: (%lu,0x%lx) Hrz: (%lu,0x%lx) " + "Lst: (%lu,0x%lx) Offset: %u(%x) fixed %d", + LSN_IN_PARTS(scanner->page_addr), + LSN_IN_PARTS(scanner->horizon), + LSN_IN_PARTS(scanner->last_file_page), + (uint) scanner->page_offset, + (uint) scanner->page_offset, scanner->fixed_horizon)); + DBUG_ASSERT(translog_status == TRANSLOG_OK || + translog_status == TRANSLOG_READONLY); + buff->groups_no= 0; + buff->lsn= scanner->page_addr; + buff->lsn+= scanner->page_offset; /* offset increasing */ + res= translog_read_record_header_from_buffer(scanner->page, + scanner->page_offset, + buff, + (move_scanner ? + scanner : 0)); + DBUG_RETURN(res); +} + + +/** + @brief Read record header and some fixed part of the next record (the part + depend on record type). + + @param scanner data for scanning if lsn is NULL scanner data + will be used for continue scanning. + The scanner can be NULL. + + @param buff log record header buffer + + @return Length of header or operation status + @retval RECHEADER_READ_ERROR error + @retval RECHEADER_READ_EOF EOF + @retval # number of bytes in + TRANSLOG_HEADER_BUFFER::header where + stored decoded part of the header +*/ + +int translog_read_next_record_header(TRANSLOG_SCANNER_DATA *scanner, + TRANSLOG_HEADER_BUFFER *buff) +{ + translog_size_t res; + + DBUG_ENTER("translog_read_next_record_header"); + buff->groups_no= 0; /* to be sure that we will free it right */ + DBUG_PRINT("enter", ("scanner: 0x%lx", (ulong) scanner)); + DBUG_PRINT("info", ("Scanner: Cur: (%lu,0x%lx) Hrz: (%lu,0x%lx) " + "Lst: (%lu,0x%lx) Offset: %u(%x) fixed: %d", + LSN_IN_PARTS(scanner->page_addr), + LSN_IN_PARTS(scanner->horizon), + LSN_IN_PARTS(scanner->last_file_page), + (uint) scanner->page_offset, + (uint) scanner->page_offset, scanner->fixed_horizon)); + DBUG_ASSERT(translog_status == TRANSLOG_OK || + translog_status == TRANSLOG_READONLY); + + do + { + if (translog_get_next_chunk(scanner)) + DBUG_RETURN(RECHEADER_READ_ERROR); + if (scanner->page == END_OF_LOG) + { + DBUG_PRINT("info", ("End of file from the scanner")); + /* Last record was read */ + buff->lsn= LSN_IMPOSSIBLE; + DBUG_RETURN(RECHEADER_READ_EOF); + } + DBUG_PRINT("info", ("Page: (%lu,0x%lx) offset: %lu byte: %x", + LSN_IN_PARTS(scanner->page_addr), + (ulong) scanner->page_offset, + (uint) scanner->page[scanner->page_offset])); + } while (!translog_is_LSN_chunk(scanner->page[scanner->page_offset]) && + scanner->page[scanner->page_offset] != TRANSLOG_FILLER); + + if (scanner->page[scanner->page_offset] == TRANSLOG_FILLER) + { + DBUG_PRINT("info", ("End of file")); + /* Last record was read */ + buff->lsn= LSN_IMPOSSIBLE; + /* Return 'end of log' marker */ + res= RECHEADER_READ_EOF; + } + else + res= translog_read_record_header_scan(scanner, buff, 0); + DBUG_RETURN(res); +} + + +/* + Moves record data reader to the next chunk and fill the data reader + information about that chunk. + + SYNOPSIS + translog_record_read_next_chunk() + data data cursor + + RETURN + 0 OK + 1 Error +*/ + +static my_bool translog_record_read_next_chunk(TRANSLOG_READER_DATA *data) +{ + translog_size_t new_current_offset= data->current_offset + data->chunk_size; + uint16 chunk_header_len, chunk_len; + uint8 type; + DBUG_ENTER("translog_record_read_next_chunk"); + + if (data->eor) + { + DBUG_PRINT("info", ("end of the record flag set")); + DBUG_RETURN(1); + } + + if (data->header.groups_no && + data->header.groups_no - 1 != data->current_group && + data->header.groups[data->current_group].num == data->current_chunk) + { + /* Goto next group */ + data->current_group++; + data->current_chunk= 0; + DBUG_PRINT("info", ("skip to group: #%u", data->current_group)); + translog_destroy_scanner(&data->scanner); + translog_scanner_init(data->header.groups[data->current_group].addr, + 1, &data->scanner, 1); + } + else + { + data->current_chunk++; + if (translog_get_next_chunk(&data->scanner)) + DBUG_RETURN(1); + if (data->scanner.page == END_OF_LOG) + { + /* + Actually it should not happened, but we want to quit nicely in case + of a truncated log + */ + DBUG_RETURN(1); + } + } + type= data->scanner.page[data->scanner.page_offset] & TRANSLOG_CHUNK_TYPE; + + if (type == TRANSLOG_CHUNK_LSN && data->header.groups_no) + { + DBUG_PRINT("info", + ("Last chunk: data len: %u offset: %u group: %u of %u", + data->header.chunk0_data_len, data->scanner.page_offset, + data->current_group, data->header.groups_no - 1)); + DBUG_ASSERT(data->header.groups_no - 1 == data->current_group); + DBUG_ASSERT(data->header.lsn == + data->scanner.page_addr + data->scanner.page_offset); + translog_destroy_scanner(&data->scanner); + translog_scanner_init(data->header.chunk0_data_addr, 1, &data->scanner, 1); + data->chunk_size= data->header.chunk0_data_len; + data->body_offset= data->scanner.page_offset; + data->current_offset= new_current_offset; + data->eor= 1; + DBUG_RETURN(0); + } + + if (type == TRANSLOG_CHUNK_LSN || type == TRANSLOG_CHUNK_FIXED) + { + data->eor= 1; + DBUG_RETURN(1); /* End of record */ + } + + chunk_header_len= + translog_get_chunk_header_length(data->scanner.page + + data->scanner.page_offset); + chunk_len= translog_get_total_chunk_length(data->scanner.page, + data->scanner.page_offset); + data->chunk_size= chunk_len - chunk_header_len; + data->body_offset= data->scanner.page_offset + chunk_header_len; + data->current_offset= new_current_offset; + DBUG_PRINT("info", ("grp: %u chunk: %u body_offset: %u chunk_size: %u " + "current_offset: %lu", + (uint) data->current_group, + (uint) data->current_chunk, + (uint) data->body_offset, + (uint) data->chunk_size, (ulong) data->current_offset)); + DBUG_RETURN(0); +} + + +/* + Initialize record reader data from LSN + + SYNOPSIS + translog_init_reader_data() + lsn reference to LSN we should start from + data reader data to initialize + + RETURN + 0 OK + 1 Error +*/ + +static my_bool translog_init_reader_data(LSN lsn, + TRANSLOG_READER_DATA *data) +{ + int read_header; + DBUG_ENTER("translog_init_reader_data"); + if (translog_scanner_init(lsn, 1, &data->scanner, 1) || + ((read_header= + translog_read_record_header_scan(&data->scanner, &data->header, 1)) + == RECHEADER_READ_ERROR)) + DBUG_RETURN(1); + data->read_header= read_header; + data->body_offset= data->header.non_header_data_start_offset; + data->chunk_size= data->header.non_header_data_len; + data->current_offset= data->read_header; + data->current_group= 0; + data->current_chunk= 0; + data->eor= 0; + DBUG_PRINT("info", ("read_header: %u " + "body_offset: %u chunk_size: %u current_offset: %lu", + (uint) data->read_header, + (uint) data->body_offset, + (uint) data->chunk_size, (ulong) data->current_offset)); + DBUG_RETURN(0); +} + + +/** + @brief Destroy reader data object +*/ + +static void translog_destroy_reader_data(TRANSLOG_READER_DATA *data) +{ + translog_destroy_scanner(&data->scanner); + translog_free_record_header(&data->header); +} + + +/* + Read a part of the record. + + SYNOPSIS + translog_read_record_header() + lsn log record serial number (address of the record) + offset From the beginning of the record beginning (read + by translog_read_record_header). + length Length of record part which have to be read. + buffer Buffer where to read the record part (have to be at + least 'length' bytes length) + + RETURN + length of data actually read +*/ + +translog_size_t translog_read_record(LSN lsn, + translog_size_t offset, + translog_size_t length, + uchar *buffer, + TRANSLOG_READER_DATA *data) +{ + translog_size_t requested_length= length; + translog_size_t end= offset + length; + TRANSLOG_READER_DATA internal_data; + DBUG_ENTER("translog_read_record"); + DBUG_ASSERT(translog_status == TRANSLOG_OK || + translog_status == TRANSLOG_READONLY); + + if (data == NULL) + { + DBUG_ASSERT(lsn != LSN_IMPOSSIBLE); + data= &internal_data; + } + if (lsn || + (offset < data->current_offset && + !(offset < data->read_header && offset + length < data->read_header))) + { + if (translog_init_reader_data(lsn, data)) + DBUG_RETURN(0); + } + DBUG_PRINT("info", ("Offset: %lu length: %lu " + "Scanner: Cur: (%lu,0x%lx) Hrz: (%lu,0x%lx) " + "Lst: (%lu,0x%lx) Offset: %u(%x) fixed: %d", + (ulong) offset, (ulong) length, + LSN_IN_PARTS(data->scanner.page_addr), + LSN_IN_PARTS(data->scanner.horizon), + LSN_IN_PARTS(data->scanner.last_file_page), + (uint) data->scanner.page_offset, + (uint) data->scanner.page_offset, + data->scanner.fixed_horizon)); + if (offset < data->read_header) + { + uint16 len= min(data->read_header, end) - offset; + DBUG_PRINT("info", + ("enter header offset: %lu length: %lu", + (ulong) offset, (ulong) length)); + memcpy(buffer, data->header.header + offset, len); + length-= len; + if (length == 0) + { + translog_destroy_reader_data(data); + DBUG_RETURN(requested_length); + } + offset+= len; + buffer+= len; + DBUG_PRINT("info", + ("len: %u offset: %lu curr: %lu length: %lu", + len, (ulong) offset, (ulong) data->current_offset, + (ulong) length)); + } + /* TODO: find first page which we should read by offset */ + + /* read the record chunk by chunk */ + for(;;) + { + uint page_end= data->current_offset + data->chunk_size; + DBUG_PRINT("info", + ("enter body offset: %lu curr: %lu " + "length: %lu page_end: %lu", + (ulong) offset, (ulong) data->current_offset, (ulong) length, + (ulong) page_end)); + if (offset < page_end) + { + uint len= page_end - offset; + set_if_smaller(len, length); /* in case we read beyond record's end */ + DBUG_ASSERT(offset >= data->current_offset); + memcpy(buffer, + data->scanner.page + data->body_offset + + (offset - data->current_offset), len); + length-= len; + if (length == 0) + { + translog_destroy_reader_data(data); + DBUG_RETURN(requested_length); + } + offset+= len; + buffer+= len; + DBUG_PRINT("info", + ("len: %u offset: %lu curr: %lu length: %lu", + len, (ulong) offset, (ulong) data->current_offset, + (ulong) length)); + } + if (translog_record_read_next_chunk(data)) + { + translog_destroy_reader_data(data); + DBUG_RETURN(requested_length - length); + } + } +} + + +/* + @brief Force skipping to the next buffer + + @todo Do not copy old page content if all page protections are switched off + (because we do not need calculate something or change old parts of the page) +*/ + +static void translog_force_current_buffer_to_finish() +{ + TRANSLOG_ADDRESS new_buff_beginning; + uint16 old_buffer_no= log_descriptor.bc.buffer_no; + uint16 new_buffer_no= (old_buffer_no + 1) % TRANSLOG_BUFFERS_NO; + struct st_translog_buffer *new_buffer= (log_descriptor.buffers + + new_buffer_no); + struct st_translog_buffer *old_buffer= log_descriptor.bc.buffer; + uchar *data= log_descriptor.bc.ptr - log_descriptor.bc.current_page_fill; + uint16 left= TRANSLOG_PAGE_SIZE - log_descriptor.bc.current_page_fill; + uint16 current_page_fill, write_counter, previous_offset; + DBUG_ENTER("translog_force_current_buffer_to_finish"); + DBUG_PRINT("enter", ("Buffer #%u 0x%lx " + "Buffer addr: (%lu,0x%lx) " + "Page addr: (%lu,0x%lx) " + "size: %lu (%lu) Pg: %u left: %u in progress %u", + (uint) log_descriptor.bc.buffer_no, + (ulong) log_descriptor.bc.buffer, + LSN_IN_PARTS(log_descriptor.bc.buffer->offset), + (ulong) LSN_FILE_NO(log_descriptor.horizon), + (ulong) (LSN_OFFSET(log_descriptor.horizon) - + log_descriptor.bc.current_page_fill), + (ulong) log_descriptor.bc.buffer->size, + (ulong) (log_descriptor.bc.ptr -log_descriptor.bc. + buffer->buffer), + (uint) log_descriptor.bc.current_page_fill, + (uint) left, + (uint) log_descriptor.bc.buffer-> + copy_to_buffer_in_progress)); + translog_lock_assert_owner(); + LINT_INIT(current_page_fill); + new_buff_beginning= log_descriptor.bc.buffer->offset; + new_buff_beginning+= log_descriptor.bc.buffer->size; /* increase offset */ + + DBUG_ASSERT(log_descriptor.bc.ptr !=NULL); + DBUG_ASSERT(LSN_FILE_NO(log_descriptor.horizon) == + LSN_FILE_NO(log_descriptor.bc.buffer->offset)); + translog_check_cursor(&log_descriptor.bc); + DBUG_ASSERT(left < TRANSLOG_PAGE_SIZE); + if (left) + { + /* + TODO: if 'left' is so small that can't hold any other record + then do not move the page + */ + DBUG_PRINT("info", ("left: %u", (uint) left)); + + /* decrease offset */ + new_buff_beginning-= log_descriptor.bc.current_page_fill; + current_page_fill= log_descriptor.bc.current_page_fill; + + memset(log_descriptor.bc.ptr, TRANSLOG_FILLER, left); + log_descriptor.bc.buffer->size+= left; + DBUG_PRINT("info", ("Finish Page buffer #%u: 0x%lx " + "Size: %lu", + (uint) log_descriptor.bc.buffer->buffer_no, + (ulong) log_descriptor.bc.buffer, + (ulong) log_descriptor.bc.buffer->size)); + DBUG_ASSERT(log_descriptor.bc.buffer->buffer_no == + log_descriptor.bc.buffer_no); + } + else + { + log_descriptor.bc.current_page_fill= 0; + } + + translog_buffer_lock(new_buffer); +#ifndef DBUG_OFF + { + TRANSLOG_ADDRESS offset= new_buffer->offset; + TRANSLOG_FILE *file= new_buffer->file; + uint8 ver= new_buffer->ver; + translog_lock_assert_owner(); +#endif + translog_wait_for_buffer_free(new_buffer); +#ifndef DBUG_OFF + /* We keep the handler locked so nobody can start this new buffer */ + DBUG_ASSERT(offset == new_buffer->offset && new_buffer->file == NULL && + (file == NULL ? ver : (uint8)(ver + 1)) == new_buffer->ver); + } +#endif + + write_counter= log_descriptor.bc.write_counter; + previous_offset= log_descriptor.bc.previous_offset; + translog_start_buffer(new_buffer, &log_descriptor.bc, new_buffer_no); + /* Fix buffer offset (which was incorrectly set to horizon) */ + log_descriptor.bc.buffer->offset= new_buff_beginning; + log_descriptor.bc.write_counter= write_counter; + log_descriptor.bc.previous_offset= previous_offset; + new_buffer->prev_last_lsn= BUFFER_MAX_LSN(old_buffer); + DBUG_PRINT("info", ("prev_last_lsn set to (%lu,0x%lx) buffer: 0x%lx", + LSN_IN_PARTS(new_buffer->prev_last_lsn), + (ulong) new_buffer)); + + /* + Advances this log pointer, increases writers and let other threads to + write to the log while we process old page content + */ + if (left) + { + log_descriptor.bc.ptr+= current_page_fill; + log_descriptor.bc.buffer->size= log_descriptor.bc.current_page_fill= + current_page_fill; + new_buffer->overlay= 1; + } + else + translog_new_page_header(&log_descriptor.horizon, &log_descriptor.bc); + translog_buffer_increase_writers(new_buffer); + translog_buffer_unlock(new_buffer); + + /* + We have to wait until all writers finish before start changing the + pages by applying protection and copying the page content in the + new buffer. + */ +#ifndef DBUG_OFF + { + TRANSLOG_ADDRESS offset= old_buffer->offset; + TRANSLOG_FILE *file= old_buffer->file; + uint8 ver= old_buffer->ver; +#endif + /* + Now only one thread can flush log (buffer can flush many threads but + log flush log flush where this function is used can do only one thread) + so no other thread can set is_closing_buffer. + */ + DBUG_ASSERT(!old_buffer->is_closing_buffer); + old_buffer->is_closing_buffer= 1; /* Other flushes will wait */ + DBUG_PRINT("enter", ("Buffer #%u 0x%lx is_closing_buffer set", + (uint) old_buffer->buffer_no, (ulong) old_buffer)); + translog_wait_for_writers(old_buffer); +#ifndef DBUG_OFF + /* We blocked flushing this buffer so the buffer should not changed */ + DBUG_ASSERT(offset == old_buffer->offset && file == old_buffer->file && + ver == old_buffer->ver); + } +#endif + + if (log_descriptor.flags & TRANSLOG_SECTOR_PROTECTION) + { + translog_put_sector_protection(data, &log_descriptor.bc); + if (left) + { + log_descriptor.bc.write_counter++; + log_descriptor.bc.previous_offset= current_page_fill; + } + else + { + DBUG_PRINT("info", ("drop write_counter")); + log_descriptor.bc.write_counter= 0; + log_descriptor.bc.previous_offset= 0; + } + } + + if (log_descriptor.flags & TRANSLOG_PAGE_CRC) + { + uint32 crc= translog_crc(data + log_descriptor.page_overhead, + TRANSLOG_PAGE_SIZE - + log_descriptor.page_overhead); + DBUG_PRINT("info", ("CRC: 0x%lx", (ulong) crc)); + int4store(data + 3 + 3 + 1, crc); + } + old_buffer->is_closing_buffer= 0; + DBUG_PRINT("enter", ("Buffer #%u 0x%lx is_closing_buffer cleared", + (uint) old_buffer->buffer_no, (ulong) old_buffer)); + pthread_cond_broadcast(&old_buffer->waiting_filling_buffer); + + if (left) + { + /* + TODO: do not copy beginning of the page if we have no CRC or sector + checks on + */ + memcpy(new_buffer->buffer, data, current_page_fill); + } + old_buffer->next_buffer_offset= new_buffer->offset; + translog_buffer_lock(new_buffer); + new_buffer->prev_buffer_offset= old_buffer->offset; + translog_buffer_decrease_writers(new_buffer); + translog_buffer_unlock(new_buffer); + + DBUG_VOID_RETURN; +} + + +/** + @brief Waits while given lsn will be flushed + + @param lsn log record serial number up to which (inclusive) + the log has to be flushed +*/ + +void translog_flush_wait_for_end(LSN lsn) +{ + DBUG_ENTER("translog_flush_wait_for_end"); + DBUG_PRINT("enter", ("LSN: (%lu,0x%lx)", LSN_IN_PARTS(lsn))); + safe_mutex_assert_owner(&log_descriptor.log_flush_lock); + while (cmp_translog_addr(log_descriptor.flushed, lsn) < 0) + pthread_cond_wait(&log_descriptor.log_flush_cond, + &log_descriptor.log_flush_lock); + DBUG_VOID_RETURN; +} + + +/** + @brief Sets goal for the next flush pass and waits for this pass end. + + @param lsn log record serial number up to which (inclusive) + the log has to be flushed +*/ + +void translog_flush_set_new_goal_and_wait(TRANSLOG_ADDRESS lsn) +{ + int flush_no= log_descriptor.flush_no; + DBUG_ENTER("translog_flush_set_new_goal_and_wait"); + DBUG_PRINT("enter", ("LSN: (%lu,0x%lx)", LSN_IN_PARTS(lsn))); + safe_mutex_assert_owner(&log_descriptor.log_flush_lock); + if (cmp_translog_addr(lsn, log_descriptor.next_pass_max_lsn) > 0) + { + log_descriptor.next_pass_max_lsn= lsn; + log_descriptor.max_lsn_requester= pthread_self(); + } + while (flush_no == log_descriptor.flush_no) + { + pthread_cond_wait(&log_descriptor.log_flush_cond, + &log_descriptor.log_flush_lock); + } + DBUG_VOID_RETURN; +} + + +/** + @brief Flush the log up to given LSN (included) + + @param lsn log record serial number up to which (inclusive) + the log has to be flushed + + @return Operation status + @retval 0 OK + @retval 1 Error + +*/ + +my_bool translog_flush(TRANSLOG_ADDRESS lsn) +{ + LSN sent_to_disk= LSN_IMPOSSIBLE; + TRANSLOG_ADDRESS flush_horizon; + uint fn, i; + dirty_buffer_mask_t dirty_buffer_mask; + uint8 last_buffer_no, start_buffer_no; + my_bool rc= 0; + DBUG_ENTER("translog_flush"); + DBUG_PRINT("enter", ("Flush up to LSN: (%lu,0x%lx)", LSN_IN_PARTS(lsn))); + DBUG_ASSERT(translog_status == TRANSLOG_OK || + translog_status == TRANSLOG_READONLY); + LINT_INIT(sent_to_disk); + + pthread_mutex_lock(&log_descriptor.log_flush_lock); + DBUG_PRINT("info", ("Everything is flushed up to (%lu,0x%lx)", + LSN_IN_PARTS(log_descriptor.flushed))); + if (cmp_translog_addr(log_descriptor.flushed, lsn) >= 0) + { + pthread_mutex_unlock(&log_descriptor.log_flush_lock); + DBUG_RETURN(0); + } + if (log_descriptor.flush_in_progress) + { + translog_flush_set_new_goal_and_wait(lsn); + if (!pthread_equal(log_descriptor.max_lsn_requester, pthread_self())) + { + /* fix lsn if it was horizon */ + if (cmp_translog_addr(lsn, log_descriptor.bc.buffer->last_lsn) > 0) + lsn= BUFFER_MAX_LSN(log_descriptor.bc.buffer); + translog_flush_wait_for_end(lsn); + pthread_mutex_unlock(&log_descriptor.log_flush_lock); + DBUG_RETURN(0); + } + log_descriptor.next_pass_max_lsn= LSN_IMPOSSIBLE; + } + log_descriptor.flush_in_progress= 1; + flush_horizon= log_descriptor.previous_flush_horizon; + DBUG_PRINT("info", ("flush_in_progress is set")); + pthread_mutex_unlock(&log_descriptor.log_flush_lock); + + translog_lock(); + if (log_descriptor.is_everything_flushed) + { + DBUG_PRINT("info", ("everything is flushed")); + rc= (translog_status == TRANSLOG_READONLY); + translog_unlock(); + goto out; + } + + /* + We will recheck information when will lock buffers one by + one so we can use unprotected read here (this is just for + speed up buffers processing) + */ + dirty_buffer_mask= log_descriptor.dirty_buffer_mask; + DBUG_PRINT("info", ("Dirty buffer mask: %lx current buffer: %u", + (ulong) dirty_buffer_mask, + (uint) log_descriptor.bc.buffer_no)); + for (i= (log_descriptor.bc.buffer_no + 1) % TRANSLOG_BUFFERS_NO; + i != log_descriptor.bc.buffer_no && !(dirty_buffer_mask & (1 << i)); + i= (i + 1) % TRANSLOG_BUFFERS_NO) {} + start_buffer_no= i; + + DBUG_PRINT("info", + ("start from: %u current: %u prev last lsn: (%lu,0x%lx)", + (uint) start_buffer_no, (uint) log_descriptor.bc.buffer_no, + LSN_IN_PARTS(log_descriptor.bc.buffer->prev_last_lsn))); + + + /* + if LSN up to which we have to flush bigger then maximum LSN of previous + buffer and at least one LSN was saved in the current buffer (last_lsn != + LSN_IMPOSSIBLE) then we better finish the current buffer. + */ + if (cmp_translog_addr(lsn, log_descriptor.bc.buffer->prev_last_lsn) > 0 && + log_descriptor.bc.buffer->last_lsn != LSN_IMPOSSIBLE) + { + struct st_translog_buffer *buffer= log_descriptor.bc.buffer; + lsn= log_descriptor.bc.buffer->last_lsn; /* fix lsn if it was horizon */ + DBUG_PRINT("info", ("LSN to flush fixed to last lsn: (%lu,0x%lx)", + LSN_IN_PARTS(log_descriptor.bc.buffer->last_lsn))); + last_buffer_no= log_descriptor.bc.buffer_no; + log_descriptor.is_everything_flushed= 1; + translog_force_current_buffer_to_finish(); + translog_buffer_unlock(buffer); + } + else + { + last_buffer_no= ((log_descriptor.bc.buffer_no + TRANSLOG_BUFFERS_NO -1) % + TRANSLOG_BUFFERS_NO); + translog_unlock(); + } + sent_to_disk= translog_get_sent_to_disk(); + if (cmp_translog_addr(lsn, sent_to_disk) > 0) + { + + DBUG_PRINT("info", ("Start buffer #: %u last buffer #: %u", + (uint) start_buffer_no, (uint) last_buffer_no)); + last_buffer_no= (last_buffer_no + 1) % TRANSLOG_BUFFERS_NO; + i= start_buffer_no; + do + { + struct st_translog_buffer *buffer= log_descriptor.buffers + i; + translog_buffer_lock(buffer); + DBUG_PRINT("info", ("Check buffer: 0x%lx #: %u " + "prev last LSN: (%lu,0x%lx) " + "last LSN: (%lu,0x%lx) status: %s", + (ulong)(buffer), + (uint) i, + LSN_IN_PARTS(buffer->prev_last_lsn), + LSN_IN_PARTS(buffer->last_lsn), + (buffer->file ? + "dirty" : "closed"))); + if (buffer->prev_last_lsn <= lsn && + buffer->file != NULL) + { + DBUG_ASSERT(flush_horizon <= buffer->offset + buffer->size); + flush_horizon= buffer->offset + buffer->size; + translog_buffer_flush(buffer); + } + translog_buffer_unlock(buffer); + i= (i + 1) % TRANSLOG_BUFFERS_NO; + } while (i != last_buffer_no); + sent_to_disk= translog_get_sent_to_disk(); + } + + /* sync files from previous flush till current one */ + for (fn= LSN_FILE_NO(log_descriptor.flushed); fn <= LSN_FILE_NO(lsn); fn++) + { + TRANSLOG_FILE *file= get_logfile_by_number(fn); + DBUG_ASSERT(file != NULL); + if (!file->is_sync) + { + if (my_sync(file->handler.file, MYF(MY_WME))) + { + rc= 1; + translog_stop_writing(); + sent_to_disk= LSN_IMPOSSIBLE; + goto out; + } + file->is_sync= 1; + } + } + + if (sync_log_dir >= TRANSLOG_SYNC_DIR_ALWAYS && + (LSN_FILE_NO(log_descriptor.previous_flush_horizon) != + LSN_FILE_NO(flush_horizon) || + ((LSN_OFFSET(log_descriptor.previous_flush_horizon) - 1) / + TRANSLOG_PAGE_SIZE) != + ((LSN_OFFSET(flush_horizon) - 1) / TRANSLOG_PAGE_SIZE))) + rc|= sync_dir(log_descriptor.directory_fd, MYF(MY_WME | MY_IGNORE_BADFD)); + log_descriptor.previous_flush_horizon= flush_horizon; +out: + pthread_mutex_lock(&log_descriptor.log_flush_lock); + if (sent_to_disk != LSN_IMPOSSIBLE) + log_descriptor.flushed= sent_to_disk; + log_descriptor.flush_in_progress= 0; + log_descriptor.flush_no++; + DBUG_PRINT("info", ("flush_in_progress is dropped")); + pthread_mutex_unlock(&log_descriptor.log_flush_lock);\ + pthread_cond_broadcast(&log_descriptor.log_flush_cond); + DBUG_RETURN(rc); +} + + +/** + @brief Gives a 2-byte-id to MARIA_SHARE and logs this fact + + If a MARIA_SHARE does not yet have a 2-byte-id (unique over all currently + open MARIA_SHAREs), give it one and record this assignment in the log + (LOGREC_FILE_ID log record). + + @param tbl_info table + @param trn calling transaction + + @return Operation status + @retval 0 OK + @retval 1 Error + + @note Can be called even if share already has an id (then will do nothing) +*/ + +int translog_assign_id_to_share(MARIA_HA *tbl_info, TRN *trn) +{ + MARIA_SHARE *share= tbl_info->s; + /* + If you give an id to a non-BLOCK_RECORD table, you also need to release + this id somewhere. Then you can change the assertion. + */ + DBUG_ASSERT(share->data_file_type == BLOCK_RECORD); + /* re-check under mutex to avoid having 2 ids for the same share */ + pthread_mutex_lock(&share->intern_lock); + if (unlikely(share->id == 0)) + { + LSN lsn; + LEX_CUSTRING log_array[TRANSLOG_INTERNAL_PARTS + 2]; + uchar log_data[FILEID_STORE_SIZE]; + /* Inspired by set_short_trid() of trnman.c */ + uint i= share->kfile.file % SHARE_ID_MAX + 1; + do + { + my_atomic_rwlock_wrlock(&LOCK_id_to_share); + for ( ; i <= SHARE_ID_MAX ; i++) /* the range is [1..SHARE_ID_MAX] */ + { + void *tmp= NULL; + if (id_to_share[i] == NULL && + my_atomic_casptr((void **)&id_to_share[i], &tmp, share)) + { + share->id= (uint16)i; + break; + } + } + my_atomic_rwlock_wrunlock(&LOCK_id_to_share); + i= 1; /* scan the whole array */ + } while (share->id == 0); + DBUG_PRINT("info", ("id_to_share: 0x%lx -> %u", (ulong)share, share->id)); + log_array[TRANSLOG_INTERNAL_PARTS + 0].str= log_data; + log_array[TRANSLOG_INTERNAL_PARTS + 0].length= sizeof(log_data); + /* + open_file_name is an unresolved name (symlinks are not resolved, datadir + is not realpath-ed, etc) which is good: the log can be moved to another + directory and continue working. + */ + log_array[TRANSLOG_INTERNAL_PARTS + 1].str= + (uchar *)share->open_file_name.str; + log_array[TRANSLOG_INTERNAL_PARTS + 1].length= + share->open_file_name.length + 1; + /* + We can't unlock share->intern_lock before the log entry is written to + ensure no one uses the id before it's logged. + */ + if (unlikely(translog_write_record(&lsn, LOGREC_FILE_ID, trn, tbl_info, + (translog_size_t) + (sizeof(log_data) + + log_array[TRANSLOG_INTERNAL_PARTS + + 1].length), + sizeof(log_array)/sizeof(log_array[0]), + log_array, log_data, NULL))) + { + pthread_mutex_unlock(&share->intern_lock); + return 1; + } + } + pthread_mutex_unlock(&share->intern_lock); + return 0; +} + + +/** + @brief Recycles a MARIA_SHARE's short id. + + @param share table + + @note Must be called only if share has an id (i.e. id != 0) +*/ + +void translog_deassign_id_from_share(MARIA_SHARE *share) +{ + DBUG_PRINT("info", ("id_to_share: 0x%lx id %u -> 0", + (ulong)share, share->id)); + /* + We don't need any mutex as we are called only when closing the last + instance of the table or at the end of REPAIR: no writes can be + happening. But a Checkpoint may be reading share->id, so we require this + mutex: + */ + safe_mutex_assert_owner(&share->intern_lock); + my_atomic_rwlock_rdlock(&LOCK_id_to_share); + my_atomic_storeptr((void **)&id_to_share[share->id], 0); + my_atomic_rwlock_rdunlock(&LOCK_id_to_share); + share->id= 0; + /* useless but safety: */ + share->lsn_of_file_id= LSN_IMPOSSIBLE; +} + + +void translog_assign_id_to_share_from_recovery(MARIA_SHARE *share, + uint16 id) +{ + DBUG_ASSERT(maria_in_recovery && !maria_multi_threaded); + DBUG_ASSERT(share->data_file_type == BLOCK_RECORD); + DBUG_ASSERT(share->id == 0); + DBUG_ASSERT(id_to_share[id] == NULL); + id_to_share[share->id= id]= share; +} + + +/** + @brief check if such log file exists + + @param file_no number of the file to test + + @retval 0 no such file + @retval 1 there is file with such number +*/ + +my_bool translog_is_file(uint file_no) +{ + MY_STAT stat_buff; + char path[FN_REFLEN]; + return (test(my_stat(translog_filename_by_fileno(file_no, path), + &stat_buff, MYF(0)))); +} + + +/** + @brief returns minimum log file number + + @param horizon the end of the log + @param is_protected true if it is under purge_log protection + + @retval minimum file number + @retval 0 no files found +*/ + +static uint32 translog_first_file(TRANSLOG_ADDRESS horizon, int is_protected) +{ + uint min_file= 0, max_file; + DBUG_ENTER("translog_first_file"); + if (!is_protected) + pthread_mutex_lock(&log_descriptor.purger_lock); + if (log_descriptor.min_file_number && + translog_is_file(log_descriptor.min_file_number)) + { + DBUG_PRINT("info", ("cached %lu", + (ulong) log_descriptor.min_file_number)); + if (!is_protected) + pthread_mutex_unlock(&log_descriptor.purger_lock); + DBUG_RETURN(log_descriptor.min_file_number); + } + + max_file= LSN_FILE_NO(horizon); + + /* binary search for last file */ + while (min_file != max_file && min_file != (max_file - 1)) + { + uint test= (min_file + max_file) / 2; + DBUG_PRINT("info", ("min_file: %u test: %u max_file: %u", + min_file, test, max_file)); + if (test == max_file) + test--; + if (translog_is_file(test)) + max_file= test; + else + min_file= test; + } + log_descriptor.min_file_number= max_file; + if (!is_protected) + pthread_mutex_unlock(&log_descriptor.purger_lock); + DBUG_PRINT("info", ("first file :%lu", (ulong) max_file)); + DBUG_ASSERT(max_file >= 1); + DBUG_RETURN(max_file); +} + + +/** + @brief returns the most close LSN higher the given chunk address + + @param addr the chunk address to start from + @param horizon the horizon if it is known or LSN_IMPOSSIBLE + + @retval LSN_ERROR Error + @retval LSN_IMPOSSIBLE no LSNs after the address + @retval # LSN of the most close LSN higher the given chunk address +*/ + +LSN translog_next_LSN(TRANSLOG_ADDRESS addr, TRANSLOG_ADDRESS horizon) +{ + TRANSLOG_SCANNER_DATA scanner; + LSN result; + DBUG_ENTER("translog_next_LSN"); + + if (horizon == LSN_IMPOSSIBLE) + horizon= translog_get_horizon(); + + if (addr == horizon) + DBUG_RETURN(LSN_IMPOSSIBLE); + + translog_scanner_init(addr, 0, &scanner, 1); + /* + addr can point not to a chunk beginning but page end so next + page beginning. + */ + if (addr % TRANSLOG_PAGE_SIZE == 0) + { + /* + We are emulating the page end which cased such horizon value to + trigger translog_scanner_eop(). + + We can't just increase addr on page header overhead because it + can be file end so we allow translog_get_next_chunk() to skip + to the next page in correct way + */ + scanner.page_addr-= TRANSLOG_PAGE_SIZE; + scanner.page_offset= TRANSLOG_PAGE_SIZE; +#ifndef DBUG_OFF + scanner.page= NULL; /* prevent using incorrect page content */ +#endif + } + /* addr can point not to a chunk beginning but to a page end */ + if (translog_scanner_eop(&scanner)) + { + if (translog_get_next_chunk(&scanner)) + { + result= LSN_ERROR; + goto out; + } + if (scanner.page == END_OF_LOG) + { + result= LSN_IMPOSSIBLE; + goto out; + } + } + + while (!translog_is_LSN_chunk(scanner.page[scanner.page_offset]) && + scanner.page[scanner.page_offset] != TRANSLOG_FILLER) + { + if (translog_get_next_chunk(&scanner)) + { + result= LSN_ERROR; + goto out; + } + if (scanner.page == END_OF_LOG) + { + result= LSN_IMPOSSIBLE; + goto out; + } + } + + if (scanner.page[scanner.page_offset] == TRANSLOG_FILLER) + result= LSN_IMPOSSIBLE; /* reached page filler */ + else + result= scanner.page_addr + scanner.page_offset; +out: + translog_destroy_scanner(&scanner); + DBUG_RETURN(result); +} + + +/** + @brief returns the LSN of the first record starting in this log + + @retval LSN_ERROR Error + @retval LSN_IMPOSSIBLE no log or the log is empty + @retval # LSN of the first record +*/ + +LSN translog_first_lsn_in_log() +{ + TRANSLOG_ADDRESS addr, horizon= translog_get_horizon(); + TRANSLOG_VALIDATOR_DATA data; + uint file; + uint16 chunk_offset; + uchar *page; + DBUG_ENTER("translog_first_lsn_in_log"); + DBUG_PRINT("info", ("Horizon: (%lu,0x%lx)", LSN_IN_PARTS(horizon))); + DBUG_ASSERT(translog_status == TRANSLOG_OK || + translog_status == TRANSLOG_READONLY); + + if (!(file= translog_first_file(horizon, 0))) + { + /* log has no records yet */ + DBUG_RETURN(LSN_IMPOSSIBLE); + } + + addr= MAKE_LSN(file, TRANSLOG_PAGE_SIZE); /* the first page of the file */ + data.addr= &addr; + { + TRANSLOG_PAGE_SIZE_BUFF psize_buff; + if ((page= translog_get_page(&data, psize_buff.buffer, NULL)) == NULL || + (chunk_offset= translog_get_first_chunk_offset(page)) == 0) + DBUG_RETURN(LSN_ERROR); + } + addr+= chunk_offset; + + DBUG_RETURN(translog_next_LSN(addr, horizon)); +} + + +/** + @brief Returns theoretical first LSN if first log is present + + @retval LSN_ERROR Error + @retval LSN_IMPOSSIBLE no log + @retval # LSN of the first record +*/ + +LSN translog_first_theoretical_lsn() +{ + TRANSLOG_ADDRESS addr= translog_get_horizon(); + TRANSLOG_PAGE_SIZE_BUFF psize_buff; + uchar *page; + TRANSLOG_VALIDATOR_DATA data; + DBUG_ENTER("translog_first_theoretical_lsn"); + DBUG_PRINT("info", ("Horizon: (%lu,0x%lx)", LSN_IN_PARTS(addr))); + DBUG_ASSERT(translog_status == TRANSLOG_OK || + translog_status == TRANSLOG_READONLY); + + if (!translog_is_file(1)) + DBUG_RETURN(LSN_IMPOSSIBLE); + if (addr == MAKE_LSN(1, TRANSLOG_PAGE_SIZE)) + { + /* log has no records yet */ + DBUG_RETURN(MAKE_LSN(1, TRANSLOG_PAGE_SIZE + + log_descriptor.page_overhead)); + } + + addr= MAKE_LSN(1, TRANSLOG_PAGE_SIZE); /* the first page of the file */ + data.addr= &addr; + if ((page= translog_get_page(&data, psize_buff.buffer, NULL)) == NULL) + DBUG_RETURN(LSN_ERROR); + + DBUG_RETURN(MAKE_LSN(1, TRANSLOG_PAGE_SIZE + + page_overhead[page[TRANSLOG_PAGE_FLAGS]])); +} + + +/** + @brief Checks given low water mark and purge files if it is need + + @param low the last (minimum) address which is need + + @retval 0 OK + @retval 1 Error +*/ + +my_bool translog_purge(TRANSLOG_ADDRESS low) +{ + uint32 last_need_file= LSN_FILE_NO(low); + TRANSLOG_ADDRESS horizon= translog_get_horizon(); + int rc= 0; + DBUG_ENTER("translog_purge"); + DBUG_PRINT("enter", ("low: (%lu,0x%lx)", LSN_IN_PARTS(low))); + DBUG_ASSERT(translog_status == TRANSLOG_OK || + translog_status == TRANSLOG_READONLY); + + pthread_mutex_lock(&log_descriptor.purger_lock); + if (LSN_FILE_NO(log_descriptor.last_lsn_checked) < last_need_file) + { + uint32 i; + uint32 min_file= translog_first_file(horizon, 1); + DBUG_ASSERT(min_file != 0); /* log is already started */ + for(i= min_file; i < last_need_file && rc == 0; i++) + { + LSN lsn= translog_get_file_max_lsn_stored(i); + if (lsn == LSN_IMPOSSIBLE) + break; /* files are still in writing */ + if (lsn == LSN_ERROR) + { + rc= 1; + break; + } + if (cmp_translog_addr(lsn, low) >= 0) + break; + + DBUG_PRINT("info", ("purge file %lu", (ulong) i)); + + /* remove file descriptor from the cache */ + /* + log_descriptor.min_file can be changed only here during execution + and the function is serialized, so we can access it without problems + */ + if (i >= log_descriptor.min_file) + { + TRANSLOG_FILE *file; + rw_wrlock(&log_descriptor.open_files_lock); + DBUG_ASSERT(log_descriptor.max_file - log_descriptor.min_file + 1 == + log_descriptor.open_files.elements); + DBUG_ASSERT(log_descriptor.min_file == i); + file= *((TRANSLOG_FILE **)pop_dynamic(&log_descriptor.open_files)); + DBUG_PRINT("info", ("Files : %d", log_descriptor.open_files.elements)); + DBUG_ASSERT(i == file->number); + log_descriptor.min_file++; + DBUG_ASSERT(log_descriptor.max_file - log_descriptor.min_file + 1 == + log_descriptor.open_files.elements); + rw_unlock(&log_descriptor.open_files_lock); + translog_close_log_file(file); + } + if (log_purge_type == TRANSLOG_PURGE_IMMIDIATE) + { + char path[FN_REFLEN], *file_name; + file_name= translog_filename_by_fileno(i, path); + rc= test(my_delete(file_name, MYF(MY_WME))); + } + } + if (unlikely(rc == 1)) + log_descriptor.min_need_file= 0; /* impossible value */ + else + log_descriptor.min_need_file= i; + } + + pthread_mutex_unlock(&log_descriptor.purger_lock); + DBUG_RETURN(rc); +} + + +/** + @brief Purges files by stored min need file in case of + "ondemend" purge type + + @note This function do real work only if it is "ondemend" purge type + and translog_purge() was called at least once and last time without + errors + + @retval 0 OK + @retval 1 Error +*/ + +my_bool translog_purge_at_flush() +{ + uint32 i, min_file; + int rc= 0; + DBUG_ENTER("translog_purge_at_flush"); + DBUG_ASSERT(translog_status == TRANSLOG_OK || + translog_status == TRANSLOG_READONLY); + + if (unlikely(translog_status == TRANSLOG_READONLY)) + { + DBUG_PRINT("info", ("The log is read only => exit")); + DBUG_RETURN(0); + } + + if (log_purge_type != TRANSLOG_PURGE_ONDEMAND) + { + DBUG_PRINT("info", ("It is not \"at_flush\" => exit")); + DBUG_RETURN(0); + } + + pthread_mutex_lock(&log_descriptor.purger_lock); + + if (unlikely(log_descriptor.min_need_file == 0)) + { + DBUG_PRINT("info", ("No info about min need file => exit")); + pthread_mutex_unlock(&log_descriptor.purger_lock); + DBUG_RETURN(0); + } + + min_file= translog_first_file(translog_get_horizon(), 1); + DBUG_ASSERT(min_file != 0); /* log is already started */ + for(i= min_file; i < log_descriptor.min_need_file && rc == 0; i++) + { + char path[FN_REFLEN], *file_name; + DBUG_PRINT("info", ("purge file %lu\n", (ulong) i)); + file_name= translog_filename_by_fileno(i, path); + rc= test(my_delete(file_name, MYF(MY_WME))); + } + + pthread_mutex_unlock(&log_descriptor.purger_lock); + DBUG_RETURN(rc); +} + + +/** + @brief Gets min file number + + @param horizon the end of the log + + @retval minimum file number + @retval 0 no files found +*/ + +uint32 translog_get_first_file(TRANSLOG_ADDRESS horizon) +{ + return translog_first_file(horizon, 0); +} + + +/** + @brief Gets min file number which is needed + + @retval minimum file number + @retval 0 unknown +*/ + +uint32 translog_get_first_needed_file() +{ + uint32 file_no; + pthread_mutex_lock(&log_descriptor.purger_lock); + file_no= log_descriptor.min_need_file; + pthread_mutex_unlock(&log_descriptor.purger_lock); + return file_no; +} + + +/** + @brief Gets transaction log file size + + @return transaction log file size +*/ + +uint32 translog_get_file_size() +{ + uint32 res; + translog_lock(); + res= log_descriptor.log_file_max_size; + translog_unlock(); + return (res); +} + + +/** + @brief Sets transaction log file size + + @return Returns actually set transaction log size +*/ + +void translog_set_file_size(uint32 size) +{ + struct st_translog_buffer *old_buffer= NULL; + DBUG_ENTER("translog_set_file_size"); + translog_lock(); + DBUG_PRINT("enter", ("Size: %lu", (ulong) size)); + DBUG_ASSERT(size % TRANSLOG_PAGE_SIZE == 0 && + size >= TRANSLOG_MIN_FILE_SIZE); + log_descriptor.log_file_max_size= size; + /* if current file longer then finish it*/ + if (LSN_OFFSET(log_descriptor.horizon) >= log_descriptor.log_file_max_size) + { + old_buffer= log_descriptor.bc.buffer; + translog_buffer_next(&log_descriptor.horizon, &log_descriptor.bc, 1); + translog_buffer_unlock(old_buffer); + } + translog_unlock(); + if (old_buffer) + { + translog_buffer_lock(old_buffer); + translog_buffer_flush(old_buffer); + translog_buffer_unlock(old_buffer); + } + DBUG_VOID_RETURN; +} + + +/** + Write debug information to log if we EXTRA_DEBUG is enabled +*/ + +my_bool translog_log_debug_info(TRN *trn __attribute__((unused)), + enum translog_debug_info_type type + __attribute__((unused)), + uchar *info __attribute__((unused)), + size_t length __attribute__((unused))) +{ +#ifdef EXTRA_DEBUG + LEX_CUSTRING log_array[TRANSLOG_INTERNAL_PARTS + 2]; + uchar debug_type; + LSN lsn; + + if (!trn) + { + /* + We can't log the current transaction because we don't have + an active transaction. Use a temporary transaction object instead + */ + trn= &dummy_transaction_object; + } + debug_type= (uchar) type; + log_array[TRANSLOG_INTERNAL_PARTS + 0].str= &debug_type; + log_array[TRANSLOG_INTERNAL_PARTS + 0].length= 1; + log_array[TRANSLOG_INTERNAL_PARTS + 1].str= info; + log_array[TRANSLOG_INTERNAL_PARTS + 1].length= length; + return translog_write_record(&lsn, LOGREC_DEBUG_INFO, + trn, NULL, + (translog_size_t) (1+ length), + sizeof(log_array)/sizeof(log_array[0]), + log_array, NULL, NULL); +#else + return 0; +#endif +} + + +#ifdef MARIA_DUMP_LOG +#include <my_getopt.h> +extern void translog_example_table_init(); +static const char *load_default_groups[]= { "maria_dump_log",0 }; +static void get_options(int *argc,char * * *argv); +#ifndef DBUG_OFF +#if defined(__WIN__) +const char *default_dbug_option= "d:t:i:O,\\maria_dump_log.trace"; +#else +const char *default_dbug_option= "d:t:i:o,/tmp/maria_dump_log.trace"; +#endif +#endif +static ulonglong opt_offset; +static ulong opt_pages; +static const char *opt_file= NULL; +static File handler= -1; +static my_bool opt_unit= 0; +static struct my_option my_long_options[] = +{ +#ifdef IMPLTMENTED + {"body", 'b', + "Print chunk body dump", + (uchar **) &opt_body, (uchar **) &opt_body, 0, + GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, +#endif +#ifndef DBUG_OFF + {"debug", '#', "Output debug log. Often the argument is 'd:t:o,filename'.", + 0, 0, 0, GET_STR, OPT_ARG, 0, 0, 0, 0, 0, 0}, +#endif + {"file", 'f', "Path to file which will be read", + (uchar**) &opt_file, 0, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + {"help", '?', "Display this help and exit.", + 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0}, + { "offset", 'o', "Start reading log from this offset", + (uchar**) &opt_offset, (uchar**) &opt_offset, + 0, GET_ULL, REQUIRED_ARG, 0, 0, ~(longlong) 0, 0, 0, 0 }, + { "pages", 'n', "Number of pages to read", + (uchar**) &opt_pages, (uchar**) &opt_pages, 0, + GET_ULONG, REQUIRED_ARG, (long) ~(ulong) 0, + (long) 1, (long) ~(ulong) 0, (long) 0, + (long) 1, 0}, + {"unit-test", 'U', + "Use unit test record table (for logs created by unittests", + (uchar **) &opt_unit, (uchar **) &opt_unit, 0, + GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"version", 'V', "Print version 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} +}; + + +static void print_version(void) +{ + VOID(printf("%s Ver 1.0 for %s on %s\n", + my_progname_short, SYSTEM_TYPE, MACHINE_TYPE)); + NETWARE_SET_SCREEN_MODE(1); +} + + +static void usage(void) +{ + print_version(); + puts("Copyright (C) 2008 MySQL AB"); + puts("This software comes with ABSOLUTELY NO WARRANTY. This is free software,"); + puts("and you are welcome to modify and redistribute it under the GPL license\n"); + + puts("Dump content of maria log pages."); + VOID(printf("\nUsage: %s -f file OPTIONS\n", my_progname_short)); + my_print_help(my_long_options); + print_defaults("my", load_default_groups); + my_print_variables(my_long_options); +} + + +static my_bool +get_one_option(int optid __attribute__((unused)), + const struct my_option *opt __attribute__((unused)), + char *argument __attribute__((unused))) +{ + switch (optid) { + case '?': + usage(); + exit(0); + case 'V': + print_version(); + exit(0); +#ifndef DBUG_OFF + case '#': + DBUG_SET_INITIAL(argument ? argument : default_dbug_option); + break; +#endif + } + return 0; +} + + +static void get_options(int *argc,char ***argv) +{ + int ho_error; + + if ((ho_error=handle_options(argc, argv, my_long_options, get_one_option))) + exit(ho_error); + + if (opt_file == NULL) + { + usage(); + exit(1); + } +} + + +/** + @brief Dump information about file header page. +*/ + +static void dump_header_page(uchar *buff) +{ + LOGHANDLER_FILE_INFO desc; + char strbuff[21]; + translog_interpret_file_header(&desc, buff); + printf(" This can be header page:\n" + " Timestamp: %s\n" + " Maria log version: %lu\n" + " Server version: %lu\n" + " Server id %lu\n" + " Page size %lu\n", + llstr(desc.timestamp, strbuff), + desc.maria_version, + desc.mysql_version, + desc.server_id, + desc.page_size); + if (desc.page_size != TRANSLOG_PAGE_SIZE) + printf(" WARNING: page size is not equal compiled in one %lu!!!\n", + (ulong) TRANSLOG_PAGE_SIZE); + printf(" File number %lu\n" + " Max lsn: (%lu,0x%lx)\n", + desc.file_number, + LSN_IN_PARTS(desc.max_lsn)); +} + +static const char *record_class_string[]= +{ + "LOGRECTYPE_NOT_ALLOWED", + "LOGRECTYPE_VARIABLE_LENGTH", + "LOGRECTYPE_PSEUDOFIXEDLENGTH", + "LOGRECTYPE_FIXEDLENGTH" +}; + + +/** + @brief dump information about transaction log chunk + + @param buffer reference to the whole page + @param ptr pointer to the chunk + + @reval # reference to the next chunk + @retval NULL can't interpret data +*/ + +static uchar *dump_chunk(uchar *buffer, uchar *ptr) +{ + uint length; + if (*ptr == TRANSLOG_FILLER) + { + printf(" Filler till the page end\n"); + for (; ptr < buffer + TRANSLOG_PAGE_SIZE; ptr++) + { + if (*ptr != TRANSLOG_FILLER) + { + printf(" WARNING: non filler character met before page end " + "(page + 0x%04x: 0x%02x) (stop interpretation)!!!", + (uint) (ptr - buffer), (uint) ptr[0]); + return NULL; + } + } + return ptr; + } + if (*ptr == 0 || *ptr == 0xFF) + { + printf(" WARNING: chunk can't start from 0x0 " + "(stop interpretation)!!!\n"); + return NULL; + } + switch (ptr[0] & TRANSLOG_CHUNK_TYPE) { + case TRANSLOG_CHUNK_LSN: + printf(" LSN chunk type 0 (variable length)\n"); + if (likely((ptr[0] & TRANSLOG_REC_TYPE) != TRANSLOG_CHUNK_0_CONT)) + { + printf(" Record type %u: %s record class %s compressed LSNs: %u\n", + ptr[0] & TRANSLOG_REC_TYPE, + (log_record_type_descriptor[ptr[0] & TRANSLOG_REC_TYPE].name ? + log_record_type_descriptor[ptr[0] & TRANSLOG_REC_TYPE].name : + "NULL"), + record_class_string[log_record_type_descriptor[ptr[0] & + TRANSLOG_REC_TYPE]. + rclass], + log_record_type_descriptor[ptr[0] & TRANSLOG_REC_TYPE]. + compressed_LSN); + if (log_record_type_descriptor[ptr[0] & TRANSLOG_REC_TYPE].rclass != + LOGRECTYPE_VARIABLE_LENGTH) + { + printf(" WARNING: this record class here can't be used " + "(stop interpretation)!!!\n"); + break; + } + } + else + printf(" Continuation of previous chunk 0 header \n"); + printf(" Short transaction id: %u\n", (uint) uint2korr(ptr + 1)); + { + uchar *hdr_ptr= ptr + 1 + 2; /* chunk type and short trid */ + uint16 chunk_len; + printf (" Record length: %lu\n", + (ulong) translog_variable_record_1group_decode_len(&hdr_ptr)); + chunk_len= uint2korr(hdr_ptr); + if (chunk_len == 0) + printf (" It is 1 group record (chunk length == 0)\n"); + else + { + uint16 groups, i; + + printf (" Chunk length %u\n", (uint) chunk_len); + groups= uint2korr(hdr_ptr + 2); + hdr_ptr+= 4; + printf (" Number of groups left to the end %u:\n", (uint) groups); + for(i= 0; + i < groups && hdr_ptr < buffer + TRANSLOG_PAGE_SIZE; + i++, hdr_ptr+= LSN_STORE_SIZE + 1) + { + TRANSLOG_ADDRESS gpr_addr= lsn_korr(hdr_ptr); + uint pages= hdr_ptr[LSN_STORE_SIZE]; + printf (" Group +#%u: (%lu,0x%lx) pages: %u\n", + (uint) i, LSN_IN_PARTS(gpr_addr), pages); + } + } + } + break; + case TRANSLOG_CHUNK_FIXED: + printf(" LSN chunk type 1 (fixed size)\n"); + printf(" Record type %u: %s record class %s compressed LSNs: %u\n", + ptr[0] & TRANSLOG_REC_TYPE, + (log_record_type_descriptor[ptr[0] & TRANSLOG_REC_TYPE].name ? + log_record_type_descriptor[ptr[0] & TRANSLOG_REC_TYPE].name : + "NULL"), + record_class_string[log_record_type_descriptor[ptr[0] & + TRANSLOG_REC_TYPE]. + rclass], + log_record_type_descriptor[ptr[0] & TRANSLOG_REC_TYPE]. + compressed_LSN); + if (log_record_type_descriptor[ptr[0] & TRANSLOG_REC_TYPE].rclass != + LOGRECTYPE_PSEUDOFIXEDLENGTH && + log_record_type_descriptor[ptr[0] & TRANSLOG_REC_TYPE].rclass != + LOGRECTYPE_FIXEDLENGTH) + { + printf(" WARNING: this record class here can't be used " + "(stop interpretation)!!!\n"); + } + printf(" Short transaction id: %u\n", (uint) uint2korr(ptr + 1)); + break; + case TRANSLOG_CHUNK_NOHDR: + printf(" No header chunk type 2(till the end of the page)\n"); + if (ptr[0] & TRANSLOG_REC_TYPE) + { + printf(" WARNING: chunk header content record type: 0x%02x " + "(dtop interpretation)!!!", + (uint) ptr[0]); + return NULL; + } + break; + case TRANSLOG_CHUNK_LNGTH: + printf(" Chunk with length type 3\n"); + if (ptr[0] & TRANSLOG_REC_TYPE) + { + printf(" WARNING: chunk header content record type: 0x%02x " + "(dtop interpretation)!!!", + (uint) ptr[0]); + return NULL; + } + break; + } + { + intptr offset= ptr - buffer; + DBUG_ASSERT(offset >= 0 && offset <= UINT_MAX16); + length= translog_get_total_chunk_length(buffer, (uint16)offset); + } + printf(" Length %u\n", length); + ptr+= length; + return ptr; +} + + +/** + @brief Dump information about page with data. +*/ + +static void dump_datapage(uchar *buffer) +{ + uchar *ptr; + ulong offset; + uint32 page, file; + uint header_len; + printf(" Page: %ld File number: %ld\n", + (ulong) (page= uint3korr(buffer)), + (ulong) (file= uint3korr(buffer + 3))); + if (page == 0) + printf(" WARNING: page == 0!!!\n"); + if (file == 0) + printf(" WARNING: file == 0!!!\n"); + offset= page * TRANSLOG_PAGE_SIZE; + printf(" Flags (0x%x):\n", (uint) buffer[TRANSLOG_PAGE_FLAGS]); + if (buffer[TRANSLOG_PAGE_FLAGS]) + { + if (buffer[TRANSLOG_PAGE_FLAGS] & TRANSLOG_PAGE_CRC) + printf(" Page CRC\n"); + if (buffer[TRANSLOG_PAGE_FLAGS] & TRANSLOG_SECTOR_PROTECTION) + printf(" Sector protection\n"); + if (buffer[TRANSLOG_PAGE_FLAGS] & TRANSLOG_RECORD_CRC) + printf(" Record CRC (WARNING: not yet implemented!!!)\n"); + if (buffer[TRANSLOG_PAGE_FLAGS] & ~(TRANSLOG_PAGE_CRC | + TRANSLOG_SECTOR_PROTECTION | + TRANSLOG_RECORD_CRC)) + { + printf(" WARNING: unknown flags (stop interpretation)!!!\n"); + return; + } + } + else + printf(" No flags\n"); + printf(" Page header length: %u\n", + (header_len= page_overhead[buffer[TRANSLOG_PAGE_FLAGS]])); + if (buffer[TRANSLOG_PAGE_FLAGS] & TRANSLOG_RECORD_CRC) + { + uint32 crc= uint4korr(buffer + TRANSLOG_PAGE_FLAGS + 1); + uint32 ccrc; + printf (" Page CRC 0x%04lx\n", (ulong) crc); + ccrc= translog_crc(buffer + header_len, TRANSLOG_PAGE_SIZE - header_len); + if (crc != ccrc) + printf(" WARNING: calculated CRC: 0x%04lx!!!\n", (ulong) ccrc); + } + if (buffer[TRANSLOG_PAGE_FLAGS] & TRANSLOG_SECTOR_PROTECTION) + { + TRANSLOG_FILE tfile; + { + uchar *table= buffer + header_len - + TRANSLOG_PAGE_SIZE / DISK_DRIVE_SECTOR_SIZE; + uint i; + printf(" Sector protection current value: 0x%02x\n", (uint) table[0]); + for (i= 1; i < TRANSLOG_PAGE_SIZE / DISK_DRIVE_SECTOR_SIZE; i++) + { + printf(" Sector protection in sector: 0x%02x saved value 0x%02x\n", + (uint)buffer[i * DISK_DRIVE_SECTOR_SIZE], + (uint)table[i]); + } + } + tfile.number= file; + tfile.handler.file= handler; + pagecache_file_init(tfile.handler, NULL, NULL, NULL, NULL, NULL); + tfile.was_recovered= 0; + tfile.is_sync= 1; + if (translog_check_sector_protection(buffer, &tfile)) + printf(" WARNING: sector protection found problems!!!\n"); + } + ptr= buffer + header_len; + while (ptr && ptr < buffer + TRANSLOG_PAGE_SIZE) + { + printf(" Chunk (%lu,0x%lx):\n", + (ulong)file, (ulong) offset + (ptr - buffer)); + ptr= dump_chunk(buffer, ptr); + } +} + + +/** + @brief Dump information about page. +*/ + +static void dump_page(uchar *buffer) +{ + printf("Page by offset %llu (0x%llx)\n", opt_offset, opt_offset); + if (strncmp((char*)maria_trans_file_magic, (char*)buffer, + sizeof(maria_trans_file_magic)) == 0) + { + dump_header_page(buffer); + } + dump_datapage(buffer); +} + + +/** + @brief maria_dump_log main function. +*/ + +int main(int argc, char **argv) +{ + char **default_argv; + uchar buffer[TRANSLOG_PAGE_SIZE]; + MY_INIT(argv[0]); + + load_defaults("my", load_default_groups, &argc, &argv); + default_argv= argv; + get_options(&argc, &argv); + + if (opt_unit) + translog_example_table_init(); + else + translog_table_init(); + translog_fill_overhead_table(); + + maria_data_root= (char *)"."; + + if ((handler= my_open(opt_file, O_RDONLY, MYF(MY_WME))) < 0) + { + fprintf(stderr, "Can't open file: '%s' errno: %d\n", + opt_file, my_errno); + goto err; + } + if (my_seek(handler, opt_offset, SEEK_SET, MYF(MY_WME)) != + opt_offset) + { + fprintf(stderr, "Can't set position %lld file: '%s' errno: %d\n", + opt_offset, opt_file, my_errno); + goto err; + } + for (; + opt_pages; + opt_offset+= TRANSLOG_PAGE_SIZE, opt_pages--) + { + if (my_pread(handler, buffer, TRANSLOG_PAGE_SIZE, opt_offset, + MYF(MY_NABP))) + { + if (my_errno == HA_ERR_FILE_TOO_SHORT) + goto end; + fprintf(stderr, "Can't read page at position %lld file: '%s' " + "errno: %d\n", opt_offset, opt_file, my_errno); + goto err; + } + dump_page(buffer); + } + +end: + my_close(handler, MYF(0)); + free_defaults(default_argv); + exit(0); + return 0; /* No compiler warning */ + +err: + my_close(handler, MYF(0)); + fprintf(stderr, "%s: FAILED\n", my_progname_short); + free_defaults(default_argv); + exit(1); +} +#endif diff --git a/storage/maria/ma_loghandler.h b/storage/maria/ma_loghandler.h new file mode 100644 index 00000000000..dba6283e303 --- /dev/null +++ b/storage/maria/ma_loghandler.h @@ -0,0 +1,460 @@ +/* Copyright (C) 2007 MySQL AB & Sanja Belkin + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You 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 _ma_loghandler_h +#define _ma_loghandler_h + +/* transaction log default cache size (TODO: make it global variable) */ +#define TRANSLOG_PAGECACHE_SIZE (1024U*1024*2) +/* transaction log default file size */ +#define TRANSLOG_FILE_SIZE (1024U*1024*1024) +/* minimum possible transaction log size */ +#define TRANSLOG_MIN_FILE_SIZE (1024U*1024*8) +/* transaction log default flags (TODO: make it global variable) */ +#define TRANSLOG_DEFAULT_FLAGS 0 + +/* + Transaction log flags. + + We allow all kind protections to be switched on together for people who + really unsure in their hardware/OS. +*/ +#define TRANSLOG_PAGE_CRC 1 +#define TRANSLOG_SECTOR_PROTECTION (1<<1) +#define TRANSLOG_RECORD_CRC (1<<2) +#define TRANSLOG_FLAGS_NUM ((TRANSLOG_PAGE_CRC | TRANSLOG_SECTOR_PROTECTION | \ + TRANSLOG_RECORD_CRC) + 1) + +#define RECHEADER_READ_ERROR -1 +#define RECHEADER_READ_EOF -2 + +/* + Page size in transaction log + It should be Power of 2 and multiple of DISK_DRIVE_SECTOR_SIZE + (DISK_DRIVE_SECTOR_SIZE * 2^N) +*/ +#define TRANSLOG_PAGE_SIZE (8U*1024) + +#include "ma_loghandler_lsn.h" +#include "trnman_public.h" + +/* short transaction ID type */ +typedef uint16 SHORT_TRANSACTION_ID; + +struct st_maria_handler; + +/* Changing one of the "SIZE" below will break backward-compatibility! */ +/* Length of CRC at end of pages */ +#define ROW_EXTENT_PAGE_SIZE 5 +#define ROW_EXTENT_COUNT_SIZE 2 +/* Size of file id in logs */ +#define FILEID_STORE_SIZE 2 +/* Size of page reference in log */ +#define PAGE_STORE_SIZE ROW_EXTENT_PAGE_SIZE +/* Size of page ranges in log */ +#define PAGERANGE_STORE_SIZE ROW_EXTENT_COUNT_SIZE +#define DIRPOS_STORE_SIZE 1 +#define CLR_TYPE_STORE_SIZE 1 +/* If table has live checksum we store its changes in UNDOs */ +#define HA_CHECKSUM_STORE_SIZE 4 +#define KEY_NR_STORE_SIZE 1 +#define PAGE_LENGTH_STORE_SIZE 2 + +/* Store methods to match the above sizes */ +#define fileid_store(T,A) int2store(T,A) +#define page_store(T,A) int5store(T,((ulonglong)(A))) +#define dirpos_store(T,A) ((*(uchar*) (T)) = A) +#define pagerange_store(T,A) int2store(T,A) +#define clr_type_store(T,A) ((*(uchar*) (T)) = A) +#define key_nr_store(T, A) ((*(uchar*) (T)) = A) +#define ha_checksum_store(T,A) int4store(T,A) +#define fileid_korr(P) uint2korr(P) +#define page_korr(P) uint5korr(P) +#define dirpos_korr(P) (*(const uchar *) (P)) +#define pagerange_korr(P) uint2korr(P) +#define clr_type_korr(P) (*(const uchar *) (P)) +#define key_nr_korr(P) (*(const uchar *) (P)) +#define ha_checksum_korr(P) uint4korr(P) + +/* + Length of disk drive sector size (we assume that writing it + to disk is an atomic operation) +*/ +#define DISK_DRIVE_SECTOR_SIZE 512U + +/* position reserved in an array of parts of a log record */ +#define TRANSLOG_INTERNAL_PARTS 2 + +/* types of records in the transaction log */ +/* TODO: Set numbers for these when we have all entries figured out */ + +enum translog_record_type +{ + LOGREC_RESERVED_FOR_CHUNKS23= 0, + LOGREC_REDO_INSERT_ROW_HEAD, + LOGREC_REDO_INSERT_ROW_TAIL, + LOGREC_REDO_NEW_ROW_HEAD, + LOGREC_REDO_NEW_ROW_TAIL, + LOGREC_REDO_INSERT_ROW_BLOBS, + LOGREC_REDO_PURGE_ROW_HEAD, + LOGREC_REDO_PURGE_ROW_TAIL, + LOGREC_REDO_FREE_BLOCKS, + LOGREC_REDO_FREE_HEAD_OR_TAIL, + LOGREC_REDO_DELETE_ROW, /* unused */ + LOGREC_REDO_UPDATE_ROW_HEAD, /* unused */ + LOGREC_REDO_INDEX, + LOGREC_REDO_INDEX_NEW_PAGE, + LOGREC_REDO_INDEX_FREE_PAGE, + LOGREC_REDO_UNDELETE_ROW, + LOGREC_CLR_END, + LOGREC_PURGE_END, + LOGREC_UNDO_ROW_INSERT, + LOGREC_UNDO_ROW_DELETE, + LOGREC_UNDO_ROW_UPDATE, + LOGREC_UNDO_KEY_INSERT, + LOGREC_UNDO_KEY_INSERT_WITH_ROOT, + LOGREC_UNDO_KEY_DELETE, + LOGREC_UNDO_KEY_DELETE_WITH_ROOT, + LOGREC_PREPARE, + LOGREC_PREPARE_WITH_UNDO_PURGE, + LOGREC_COMMIT, + LOGREC_COMMIT_WITH_UNDO_PURGE, + LOGREC_CHECKPOINT, + LOGREC_REDO_CREATE_TABLE, + LOGREC_REDO_RENAME_TABLE, + LOGREC_REDO_DROP_TABLE, + LOGREC_REDO_DELETE_ALL, + LOGREC_REDO_REPAIR_TABLE, + LOGREC_FILE_ID, + LOGREC_LONG_TRANSACTION_ID, + LOGREC_INCOMPLETE_LOG, + LOGREC_INCOMPLETE_GROUP, + LOGREC_UNDO_BULK_INSERT, + LOGREC_REDO_BITMAP_NEW_PAGE, + LOGREC_IMPORTED_TABLE, + LOGREC_DEBUG_INFO, + LOGREC_FIRST_FREE, + LOGREC_RESERVED_FUTURE_EXTENSION= 63 +}; +#define LOGREC_NUMBER_OF_TYPES 64 /* Maximum, can't be extended */ + +/* Type of operations in LOGREC_REDO_INDEX */ + +enum en_key_op +{ + KEY_OP_NONE, /* Not used */ + KEY_OP_OFFSET, /* Set current position */ + KEY_OP_SHIFT, /* Shift up/or down at current position */ + KEY_OP_CHANGE, /* Change data at current position */ + KEY_OP_ADD_PREFIX, /* Insert data at start of page */ + KEY_OP_DEL_PREFIX, /* Delete data at start of page */ + KEY_OP_ADD_SUFFIX, /* Insert data at end of page */ + KEY_OP_DEL_SUFFIX, /* Delete data at end of page */ + KEY_OP_CHECK, /* For debugging; CRC of used part of page */ + KEY_OP_MULTI_COPY, /* List of memcpy()s with fixed-len sources in page */ + KEY_OP_SET_PAGEFLAG, /* Set pageflag from next byte */ + KEY_OP_COMPACT_PAGE /* Compact key page */ +}; + + +enum translog_debug_info_type +{ + LOGREC_DEBUG_INFO_QUERY +}; + +/* Size of log file; One log file is restricted to 4G */ +typedef uint32 translog_size_t; + +#define TRANSLOG_RECORD_HEADER_MAX_SIZE 1024U + +typedef struct st_translog_group_descriptor +{ + TRANSLOG_ADDRESS addr; + uint8 num; +} TRANSLOG_GROUP; + + +typedef struct st_translog_header_buffer +{ + /* LSN of the read record */ + LSN lsn; + /* array of groups descriptors, can be used only if groups_no > 0 */ + TRANSLOG_GROUP *groups; + /* short transaction ID or 0 if it has no sense for the record */ + SHORT_TRANSACTION_ID short_trid; + /* + The Record length in buffer (including read header, but excluding + hidden part of record (type, short TrID, length) + */ + translog_size_t record_length; + /* + Buffer for write decoded header of the record (depend on the record + type) + */ + uchar header[TRANSLOG_RECORD_HEADER_MAX_SIZE]; + /* number of groups listed in */ + uint groups_no; + /* in multi-group number of chunk0 pages (valid only if groups_no > 0) */ + uint chunk0_pages; + /* type of the read record */ + enum translog_record_type type; + /* chunk 0 data address (valid only if groups_no > 0) */ + TRANSLOG_ADDRESS chunk0_data_addr; + /* + Real compressed LSN(s) size economy (<number of LSN(s)>*7 - <real_size>) + */ + int16 compressed_LSN_economy; + /* short transaction ID or 0 if it has no sense for the record */ + uint16 non_header_data_start_offset; + /* non read body data length in this first chunk */ + uint16 non_header_data_len; + /* chunk 0 data size (valid only if groups_no > 0) */ + uint16 chunk0_data_len; +} TRANSLOG_HEADER_BUFFER; + + +typedef struct st_translog_scanner_data +{ + uchar buffer[TRANSLOG_PAGE_SIZE]; /* buffer for page content */ + TRANSLOG_ADDRESS page_addr; /* current page address */ + /* end of the log which we saw last time */ + TRANSLOG_ADDRESS horizon; + TRANSLOG_ADDRESS last_file_page; /* Last page on in this file */ + uchar *page; /* page content pointer */ + /* direct link on the current page or NULL if not supported/requested */ + PAGECACHE_BLOCK_LINK *direct_link; + /* offset of the chunk in the page */ + translog_size_t page_offset; + /* set horizon only once at init */ + my_bool fixed_horizon; + /* try to get direct link on the page if it is possible */ + my_bool use_direct_link; +} TRANSLOG_SCANNER_DATA; + + +typedef struct st_translog_reader_data +{ + TRANSLOG_HEADER_BUFFER header; /* Header */ + TRANSLOG_SCANNER_DATA scanner; /* chunks scanner */ + translog_size_t body_offset; /* current chunk body offset */ + /* data offset from the record beginning */ + translog_size_t current_offset; + /* number of bytes read in header */ + uint16 read_header; + uint16 chunk_size; /* current chunk size */ + uint current_group; /* current group */ + uint current_chunk; /* current chunk in the group */ + my_bool eor; /* end of the record */ +} TRANSLOG_READER_DATA; + +C_MODE_START + +/* Records types for unittests */ +#define LOGREC_FIXED_RECORD_0LSN_EXAMPLE 1 +#define LOGREC_VARIABLE_RECORD_0LSN_EXAMPLE 2 +#define LOGREC_FIXED_RECORD_1LSN_EXAMPLE 3 +#define LOGREC_VARIABLE_RECORD_1LSN_EXAMPLE 4 +#define LOGREC_FIXED_RECORD_2LSN_EXAMPLE 5 +#define LOGREC_VARIABLE_RECORD_2LSN_EXAMPLE 6 + +extern void translog_example_table_init(); +extern void translog_table_init(); +#define translog_init(D,M,V,I,C,F,R) \ + translog_init_with_table(D,M,V,I,C,F,R,&translog_table_init,0) +extern my_bool translog_init_with_table(const char *directory, + uint32 log_file_max_size, + uint32 server_version, + uint32 server_id, + PAGECACHE *pagecache, + uint flags, + my_bool readonly, + void (*init_table_func)(), + my_bool no_error); + +extern my_bool +translog_write_record(LSN *lsn, enum translog_record_type type, TRN *trn, + MARIA_HA *tbl_info, + translog_size_t rec_len, uint part_no, + LEX_CUSTRING *parts_data, uchar *store_share_id, + void *hook_arg); + +extern void translog_destroy(); + +extern int translog_read_record_header(LSN lsn, TRANSLOG_HEADER_BUFFER *buff); + +extern void translog_free_record_header(TRANSLOG_HEADER_BUFFER *buff); + +extern translog_size_t translog_read_record(LSN lsn, + translog_size_t offset, + translog_size_t length, + uchar *buffer, + struct st_translog_reader_data + *data); + +extern my_bool translog_flush(TRANSLOG_ADDRESS lsn); + +extern my_bool translog_scanner_init(LSN lsn, + my_bool fixed_horizon, + struct st_translog_scanner_data *scanner, + my_bool use_direct_link); +extern void translog_destroy_scanner(TRANSLOG_SCANNER_DATA *scanner); + +extern int translog_read_next_record_header(TRANSLOG_SCANNER_DATA *scanner, + TRANSLOG_HEADER_BUFFER *buff); +extern LSN translog_get_file_max_lsn_stored(uint32 file); +extern my_bool translog_purge(TRANSLOG_ADDRESS low); +extern my_bool translog_is_file(uint file_no); +extern void translog_lock(); +extern void translog_unlock(); +extern void translog_lock_handler_assert_owner(); +extern TRANSLOG_ADDRESS translog_get_horizon(); +extern TRANSLOG_ADDRESS translog_get_horizon_no_lock(); +extern int translog_assign_id_to_share(struct st_maria_handler *tbl_info, + TRN *trn); +extern void translog_deassign_id_from_share(struct st_maria_share *share); +extern void +translog_assign_id_to_share_from_recovery(struct st_maria_share *share, + uint16 id); +extern my_bool translog_walk_filenames(const char *directory, + my_bool (*callback)(const char *, + const char *)); +extern my_bool translog_log_debug_info(TRN *trn, + enum translog_debug_info_type type, + uchar *info, size_t length); + +enum enum_translog_status +{ + TRANSLOG_UNINITED, /* no initialization done or error during initialization */ + TRANSLOG_OK, /* transaction log is functioning */ + TRANSLOG_READONLY, /* read only mode due to write errors */ + TRANSLOG_SHUTDOWN /* going to shutdown the loghandler */ +}; +extern enum enum_translog_status translog_status; + +/* + all the rest added because of recovery; should we make + ma_loghandler_for_recovery.h ? +*/ + +#define SHARE_ID_MAX 65535 /* array's size */ + +extern LSN translog_first_lsn_in_log(); +extern LSN translog_first_theoretical_lsn(); +extern LSN translog_next_LSN(TRANSLOG_ADDRESS addr, TRANSLOG_ADDRESS horizon); +extern my_bool translog_purge_at_flush(); +extern uint32 translog_get_first_file(TRANSLOG_ADDRESS horizon); +extern uint32 translog_get_first_needed_file(); +extern char *translog_filename_by_fileno(uint32 file_no, char *path); +extern void translog_set_file_size(uint32 size); + +/* record parts descriptor */ +struct st_translog_parts +{ + /* full record length */ + translog_size_t record_length; + /* full record length with chunk headers */ + translog_size_t total_record_length; + /* current part index */ + uint current; + /* total number of elements in parts */ + uint elements; + /* array of parts */ + LEX_CUSTRING *parts; +}; + +typedef my_bool(*prewrite_rec_hook) (enum translog_record_type type, + TRN *trn, + struct st_maria_handler *tbl_info, + void *hook_arg); + +typedef my_bool(*inwrite_rec_hook) (enum translog_record_type type, + TRN *trn, + struct st_maria_handler *tbl_info, + LSN *lsn, void *hook_arg); + +typedef uint16(*read_rec_hook) (enum translog_record_type type, + uint16 read_length, uchar *read_buff, + uchar *decoded_buff); + + +/* record classes */ +enum record_class +{ + LOGRECTYPE_NOT_ALLOWED, + LOGRECTYPE_VARIABLE_LENGTH, + LOGRECTYPE_PSEUDOFIXEDLENGTH, + LOGRECTYPE_FIXEDLENGTH +}; + +enum enum_record_in_group { + LOGREC_NOT_LAST_IN_GROUP= 0, LOGREC_LAST_IN_GROUP, LOGREC_IS_GROUP_ITSELF +}; + +/* + Descriptor of log record type +*/ +typedef struct st_log_record_type_descriptor +{ + /* internal class of the record */ + enum record_class rclass; + /* + length for fixed-size record, pseudo-fixed record + length with uncompressed LSNs + */ + uint16 fixed_length; + /* how much record body (belonged to headers too) read with headers */ + uint16 read_header_len; + /* HOOK for writing the record called before lock */ + prewrite_rec_hook prewrite_hook; + /* HOOK for writing the record called when LSN is known, inside lock */ + inwrite_rec_hook inwrite_hook; + /* HOOK for reading headers */ + read_rec_hook read_hook; + /* + For pseudo fixed records number of compressed LSNs followed by + system header + */ + int16 compressed_LSN; + /* the rest is for maria_read_log & Recovery */ + /** @brief for debug error messages or "maria_read_log" command-line tool */ + const char *name; + enum enum_record_in_group record_in_group; + /* a function to execute when we see the record during the REDO phase */ + int (*record_execute_in_redo_phase)(const TRANSLOG_HEADER_BUFFER *); + /* a function to execute when we see the record during the UNDO phase */ + int (*record_execute_in_undo_phase)(const TRANSLOG_HEADER_BUFFER *, TRN *); +} LOG_DESC; + +extern LOG_DESC log_record_type_descriptor[LOGREC_NUMBER_OF_TYPES]; + +typedef enum +{ + TRANSLOG_PURGE_IMMIDIATE, + TRANSLOG_PURGE_EXTERNAL, + TRANSLOG_PURGE_ONDEMAND +} enum_maria_translog_purge_type; +extern ulong log_purge_type; +extern ulong log_file_size; + +typedef enum +{ + TRANSLOG_SYNC_DIR_NEVER, + TRANSLOG_SYNC_DIR_NEWFILE, + TRANSLOG_SYNC_DIR_ALWAYS +} enum_maria_sync_log_dir; +extern ulong sync_log_dir; + +C_MODE_END +#endif diff --git a/storage/maria/ma_loghandler_lsn.h b/storage/maria/ma_loghandler_lsn.h new file mode 100644 index 00000000000..7fa53bc0a50 --- /dev/null +++ b/storage/maria/ma_loghandler_lsn.h @@ -0,0 +1,111 @@ +/* Copyright (C) 2007 MySQL AB & Sanja Belkin + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You 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 _ma_loghandler_lsn_h +#define _ma_loghandler_lsn_h + +/* + Transaction log record address: + file_no << 32 | offset + file_no is only 3 bytes so we can use signed integer to make + comparison simpler. +*/ +typedef int64 TRANSLOG_ADDRESS; + +/* + Compare addresses + A1 > A2 -> result > 0 + A1 == A2 -> 0 + A1 < A2 -> result < 0 +*/ +#define cmp_translog_addr(A1,A2) ((A1) - (A2)) + +/* + TRANSLOG_ADDRESS is just address of some byte in the log (usually some + chunk) + LSN used where address of some record in the log needed (not just any + address) +*/ +typedef TRANSLOG_ADDRESS LSN; + +/* Gets file number part of a LSN/log address */ +#define LSN_FILE_NO(L) (uint32) ((L) >> 32) + +/* Gets raw file number part of a LSN/log address */ +#define LSN_FILE_NO_PART(L) ((L) & ((int64)0xFFFFFF00000000LL)) + +/* Parts of LSN for printing */ +#define LSN_IN_PARTS(L) (ulong)LSN_FILE_NO(L),(ulong)LSN_OFFSET(L) + +/* Gets record offset of a LSN/log address */ +#define LSN_OFFSET(L) (ulong) ((L) & 0xFFFFFFFFL) + +/* Makes lsn/log address from file number and record offset */ +#define MAKE_LSN(F,S) ((LSN) ((((uint64)(F)) << 32) | (S))) + +/* checks LSN */ +#define LSN_VALID(L) \ + ((LSN_FILE_NO_PART(L) != FILENO_IMPOSSIBLE) && \ + (LSN_OFFSET(L) != LOG_OFFSET_IMPOSSIBLE)) + +/* size of stored LSN on a disk, don't change it! */ +#define LSN_STORE_SIZE 7 + +/* Puts LSN into buffer (dst) */ +#define lsn_store(dst, lsn) \ + do { \ + int3store((dst), LSN_FILE_NO(lsn)); \ + int4store((char*)(dst) + 3, LSN_OFFSET(lsn)); \ + } while (0) + +/* Unpacks LSN from the buffer (P) */ +#define lsn_korr(P) MAKE_LSN(uint3korr(P), uint4korr((const char*)(P) + 3)) + +/* what we need to add to LSN to increase it on one file */ +#define LSN_ONE_FILE ((int64)0x100000000LL) + +#define LSN_REPLACE_OFFSET(L, S) (LSN_FILE_NO_PART(L) | (S)) + +/* + an 8-byte type whose most significant uchar is used for "flags"; 7 + other bytes are a LSN. +*/ +typedef LSN LSN_WITH_FLAGS; +#define LSN_WITH_FLAGS_TO_LSN(x) (x & ULL(0x00FFFFFFFFFFFFFF)) +#define LSN_WITH_FLAGS_TO_FLAGS(x) (x & ULL(0xFF00000000000000)) + +#define FILENO_IMPOSSIBLE 0 /**< log file's numbering starts at 1 */ +#define LOG_OFFSET_IMPOSSIBLE 0 /**< log always has a header */ +#define LSN_IMPOSSIBLE ((LSN)0) +/* following LSN also is impossible */ +#define LSN_ERROR ((LSN)1) + +/** @brief some impossible LSN serve as markers */ + +/** + When table is modified by maria_chk, or auto-zerofilled, old REDOs don't + apply, table is freshly born again somehow: its state's LSNs need to be + updated to the new instance which receives this table. +*/ +#define LSN_NEEDS_NEW_STATE_LSNS ((LSN)2) + +/** + @brief the maximum valid LSN. + Unlike ULONGLONG_MAX, it can be safely used in comparison with valid LSNs + (ULONGLONG_MAX is too big for correctness of cmp_translog_addr()). +*/ +#define LSN_MAX (LSN)ULL(0x00FFFFFFFFFFFFFF) + +#endif diff --git a/storage/maria/ma_open.c b/storage/maria/ma_open.c new file mode 100644 index 00000000000..c86c439a702 --- /dev/null +++ b/storage/maria/ma_open.c @@ -0,0 +1,1927 @@ +/* Copyright (C) 2006 MySQL AB & MySQL Finland AB & TCX DataKonsult 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 */ + +/* open a isam-database */ + +#include "ma_fulltext.h" +#include "ma_sp_defs.h" +#include "ma_rt_index.h" +#include "ma_blockrec.h" +#include <m_ctype.h> + +#if defined(MSDOS) || defined(__WIN__) +#ifdef __WIN__ +#include <fcntl.h> +#else +#include <process.h> /* Prototype for getpid */ +#endif +#endif + +static void setup_key_functions(MARIA_KEYDEF *keyinfo); +static my_bool maria_scan_init_dummy(MARIA_HA *info); +static void maria_scan_end_dummy(MARIA_HA *info); +static my_bool maria_once_init_dummy(MARIA_SHARE *, File); +static my_bool maria_once_end_dummy(MARIA_SHARE *); +static uchar *_ma_base_info_read(uchar *ptr, MARIA_BASE_INFO *base); +static uchar *_ma_state_info_read(uchar *ptr, MARIA_STATE_INFO *state); + +#define get_next_element(to,pos,size) { memcpy((char*) to,pos,(size_t) size); \ + pos+=size;} + + +#define disk_pos_assert(pos, end_pos) \ +if (pos > end_pos) \ +{ \ + my_errno=HA_ERR_CRASHED; \ + goto err; \ +} + + +/****************************************************************************** +** Return the shared struct if the table is already open. +** In MySQL the server will handle version issues. +******************************************************************************/ + +MARIA_HA *_ma_test_if_reopen(const char *filename) +{ + LIST *pos; + + for (pos=maria_open_list ; pos ; pos=pos->next) + { + MARIA_HA *info=(MARIA_HA*) pos->data; + MARIA_SHARE *share= info->s; + if (!strcmp(share->unique_file_name.str,filename) && share->last_version) + return info; + } + return 0; +} + + +/* + Open a new instance of an already opened Maria table + + SYNOPSIS + maria_clone_internal() + share Share of already open table + mode Mode of table (O_RDONLY | O_RDWR) + data_file Filedescriptor of data file to use < 0 if one should open + open it. + + RETURN + # Maria handler + 0 Error +*/ + + +static MARIA_HA *maria_clone_internal(MARIA_SHARE *share, const char *name, + int mode, File data_file) +{ + int save_errno; + uint errpos; + MARIA_HA info,*m_info; + my_bitmap_map *changed_fields_bitmap; + DBUG_ENTER("maria_clone_internal"); + + errpos= 0; + bzero((uchar*) &info,sizeof(info)); + + if (mode == O_RDWR && share->mode == O_RDONLY) + { + my_errno=EACCES; /* Can't open in write mode */ + goto err; + } + if (data_file >= 0) + info.dfile.file= data_file; + else if (_ma_open_datafile(&info, share, name, -1)) + goto err; + errpos= 5; + + /* alloc and set up private structure parts */ + if (!my_multi_malloc(MY_WME, + &m_info,sizeof(MARIA_HA), + &info.blobs,sizeof(MARIA_BLOB)*share->base.blobs, + &info.buff,(share->base.max_key_block_length*2+ + share->base.max_key_length), + &info.lastkey_buff,share->base.max_key_length*2+1, + &info.first_mbr_key, share->base.max_key_length, + &info.maria_rtree_recursion_state, + share->have_rtree ? 1024 : 0, + &changed_fields_bitmap, + bitmap_buffer_size(share->base.fields), + NullS)) + goto err; + errpos= 6; + + memcpy(info.blobs,share->blobs,sizeof(MARIA_BLOB)*share->base.blobs); + info.lastkey_buff2= info.lastkey_buff + share->base.max_key_length; + info.last_key.data= info.lastkey_buff; + + info.s=share; + info.cur_row.lastpos= HA_OFFSET_ERROR; + info.update= (short) (HA_STATE_NEXT_FOUND+HA_STATE_PREV_FOUND); + info.opt_flag=READ_CHECK_USED; + info.this_unique= (ulong) info.dfile.file; /* Uniq number in process */ +#ifdef EXTERNAL_LOCKING + if (share->data_file_type == COMPRESSED_RECORD) + info.this_unique= share->state.unique; + info.this_loop=0; /* Update counter */ + info.last_unique= share->state.unique; + info.last_loop= share->state.update_count; +#endif + info.errkey= -1; + info.page_changed=1; + info.keyread_buff= info.buff + share->base.max_key_block_length; + + info.lock_type= F_UNLCK; + if (share->options & HA_OPTION_TMP_TABLE) + info.lock_type= F_WRLCK; + + _ma_set_data_pagecache_callbacks(&info.dfile, share); + bitmap_init(&info.changed_fields, changed_fields_bitmap, + share->base.fields, 0); + if ((*share->init)(&info)) + goto err; + + /* The following should be big enough for all pinning purposes */ + if (my_init_dynamic_array(&info.pinned_pages, + sizeof(MARIA_PINNED_PAGE), + max(share->base.blobs*2 + 4, + MARIA_MAX_TREE_LEVELS*3), 16)) + goto err; + + + pthread_mutex_lock(&share->intern_lock); + info.read_record= share->read_record; + share->reopen++; + share->write_flag=MYF(MY_NABP | MY_WAIT_IF_FULL); + if (share->options & HA_OPTION_READ_ONLY_DATA) + { + info.lock_type=F_RDLCK; + share->r_locks++; + share->tot_locks++; + } + if ((share->options & HA_OPTION_DELAY_KEY_WRITE) && + maria_delay_key_write) + share->delay_key_write=1; + + if (!share->base.born_transactional) /* For transactional ones ... */ + { + /* ... force crash if no trn given */ + _ma_set_trn_for_table(&info, &dummy_transaction_object); + info.state= &share->state.state; /* Change global values by default */ + } + else + { + info.state= &share->state.common; + *info.state= share->state.state; /* Initial values */ + } + info.state_start= info.state; /* Initial values */ + + pthread_mutex_unlock(&share->intern_lock); + + /* Allocate buffer for one record */ + /* prerequisites: info->rec_buffer == 0 && info->rec_buff_size == 0 */ + if (_ma_alloc_buffer(&info.rec_buff, &info.rec_buff_size, + share->base.default_rec_buff_size)) + goto err; + + bzero(info.rec_buff, share->base.default_rec_buff_size); + + *m_info=info; +#ifdef THREAD + thr_lock_data_init(&share->lock,&m_info->lock,(void*) m_info); +#endif + m_info->open_list.data=(void*) m_info; + maria_open_list=list_add(maria_open_list,&m_info->open_list); + + DBUG_RETURN(m_info); + +err: + save_errno=my_errno ? my_errno : HA_ERR_END_OF_FILE; + if ((save_errno == HA_ERR_CRASHED) || + (save_errno == HA_ERR_CRASHED_ON_USAGE) || + (save_errno == HA_ERR_CRASHED_ON_REPAIR)) + _ma_report_error(save_errno, &share->open_file_name); + switch (errpos) { + case 6: + (*share->end)(&info); + delete_dynamic(&info.pinned_pages); + my_free(m_info, MYF(0)); + /* fall through */ + case 5: + if (data_file < 0) + VOID(my_close(info.dfile.file, MYF(0))); + break; + } + my_errno=save_errno; + DBUG_RETURN (NULL); +} /* maria_clone_internal */ + + +/* Make a clone of a maria table */ + +MARIA_HA *maria_clone(MARIA_SHARE *share, int mode) +{ + MARIA_HA *new_info; + pthread_mutex_lock(&THR_LOCK_maria); + new_info= maria_clone_internal(share, NullS, mode, + share->data_file_type == BLOCK_RECORD ? + share->bitmap.file.file : -1); + pthread_mutex_unlock(&THR_LOCK_maria); + return new_info; +} + + +/****************************************************************************** + open a MARIA table + + See my_base.h for the handle_locking argument + if handle_locking and HA_OPEN_ABORT_IF_CRASHED then abort if the table + is marked crashed or if we are not using locking and the table doesn't + have an open count of 0. +******************************************************************************/ + +MARIA_HA *maria_open(const char *name, int mode, uint open_flags) +{ + int kfile,open_mode,save_errno; + uint i,j,len,errpos,head_length,base_pos,keys, realpath_err, + key_parts,unique_key_parts,fulltext_keys,uniques; + size_t info_length; + char name_buff[FN_REFLEN], org_name[FN_REFLEN], index_name[FN_REFLEN], + data_name[FN_REFLEN]; + uchar *disk_cache, *disk_pos, *end_pos; + MARIA_HA info,*m_info,*old_info; + MARIA_SHARE share_buff,*share; + double *rec_per_key_part; + ulong *nulls_per_key_part; + my_off_t key_root[HA_MAX_POSSIBLE_KEY]; + ulonglong max_key_file_length, max_data_file_length; + my_bool versioning= 1; + File data_file= -1; + DBUG_ENTER("maria_open"); + + LINT_INIT(m_info); + kfile= -1; + errpos= 0; + head_length=sizeof(share_buff.state.header); + bzero((uchar*) &info,sizeof(info)); + + realpath_err= my_realpath(name_buff, fn_format(org_name, name, "", + MARIA_NAME_IEXT, + MY_UNPACK_FILENAME),MYF(0)); + if (my_is_symlink(org_name) && + (realpath_err || (*maria_test_invalid_symlink)(name_buff))) + { + my_errno= HA_WRONG_CREATE_OPTION; + DBUG_RETURN(0); + } + + pthread_mutex_lock(&THR_LOCK_maria); + old_info= 0; + if ((open_flags & HA_OPEN_COPY) || + !(old_info=_ma_test_if_reopen(name_buff))) + { + share= &share_buff; + bzero((uchar*) &share_buff,sizeof(share_buff)); + share_buff.state.key_root=key_root; + share_buff.pagecache= multi_pagecache_search((uchar*) name_buff, + (uint) strlen(name_buff), + maria_pagecache); + + DBUG_EXECUTE_IF("maria_pretend_crashed_table_on_open", + if (strstr(name, "/t1")) + { + my_errno= HA_ERR_CRASHED; + goto err; + }); + if ((kfile=my_open(name_buff,(open_mode=O_RDWR) | O_SHARE,MYF(0))) < 0) + { + if ((errno != EROFS && errno != EACCES) || + mode != O_RDONLY || + (kfile=my_open(name_buff,(open_mode=O_RDONLY) | O_SHARE,MYF(0))) < 0) + goto err; + } + share->mode=open_mode; + errpos= 1; + if (my_pread(kfile,share->state.header.file_version, head_length, 0, + MYF(MY_NABP))) + { + my_errno= HA_ERR_NOT_A_TABLE; + goto err; + } + if (memcmp(share->state.header.file_version, maria_file_magic, 4)) + { + DBUG_PRINT("error",("Wrong header in %s",name_buff)); + DBUG_DUMP("error_dump", share->state.header.file_version, + head_length); + my_errno=HA_ERR_NOT_A_TABLE; + goto err; + } + share->options= mi_uint2korr(share->state.header.options); + if (share->options & + ~(HA_OPTION_PACK_RECORD | HA_OPTION_PACK_KEYS | + HA_OPTION_COMPRESS_RECORD | HA_OPTION_READ_ONLY_DATA | + HA_OPTION_TEMP_COMPRESS_RECORD | HA_OPTION_CHECKSUM | + HA_OPTION_TMP_TABLE | HA_OPTION_DELAY_KEY_WRITE | + HA_OPTION_RELIES_ON_SQL_LAYER | HA_OPTION_NULL_FIELDS | + HA_OPTION_PAGE_CHECKSUM)) + { + DBUG_PRINT("error",("wrong options: 0x%lx", share->options)); + my_errno=HA_ERR_NEW_FILE; + goto err; + } + if ((share->options & HA_OPTION_RELIES_ON_SQL_LAYER) && + ! (open_flags & HA_OPEN_FROM_SQL_LAYER)) + { + DBUG_PRINT("error", ("table cannot be opened from non-sql layer")); + my_errno= HA_ERR_UNSUPPORTED; + goto err; + } + /* Don't call realpath() if the name can't be a link */ + if (!strcmp(name_buff, org_name) || + my_readlink(index_name, org_name, MYF(0)) == -1) + (void) strmov(index_name, org_name); + *strrchr(org_name, FN_EXTCHAR)= '\0'; + (void) fn_format(data_name,org_name,"",MARIA_NAME_DEXT, + MY_APPEND_EXT|MY_UNPACK_FILENAME|MY_RESOLVE_SYMLINKS); + + info_length=mi_uint2korr(share->state.header.header_length); + base_pos= mi_uint2korr(share->state.header.base_pos); + + /* + Allocate space for header information and for data that is too + big to keep on stack + */ + if (!my_multi_malloc(MY_WME, + &disk_cache, info_length+128, + &rec_per_key_part, + (sizeof(*rec_per_key_part) * HA_MAX_POSSIBLE_KEY * + HA_MAX_KEY_SEG), + &nulls_per_key_part, + (sizeof(*nulls_per_key_part) * HA_MAX_POSSIBLE_KEY * + HA_MAX_KEY_SEG), + NullS)) + { + my_errno=ENOMEM; + goto err; + } + share_buff.state.rec_per_key_part= rec_per_key_part; + share_buff.state.nulls_per_key_part= nulls_per_key_part; + + end_pos=disk_cache+info_length; + errpos= 3; + if (my_pread(kfile, disk_cache, info_length, 0L, MYF(MY_NABP))) + { + my_errno=HA_ERR_CRASHED; + goto err; + } + len=mi_uint2korr(share->state.header.state_info_length); + keys= (uint) share->state.header.keys; + uniques= (uint) share->state.header.uniques; + fulltext_keys= (uint) share->state.header.fulltext_keys; + key_parts= mi_uint2korr(share->state.header.key_parts); + unique_key_parts= mi_uint2korr(share->state.header.unique_key_parts); + if (len != MARIA_STATE_INFO_SIZE) + { + DBUG_PRINT("warning", + ("saved_state_info_length: %d state_info_length: %d", + len,MARIA_STATE_INFO_SIZE)); + } + share->state_diff_length=len-MARIA_STATE_INFO_SIZE; + + _ma_state_info_read(disk_cache, &share->state); + len= mi_uint2korr(share->state.header.base_info_length); + if (len != MARIA_BASE_INFO_SIZE) + { + DBUG_PRINT("warning",("saved_base_info_length: %d base_info_length: %d", + len,MARIA_BASE_INFO_SIZE)); + } + disk_pos= _ma_base_info_read(disk_cache + base_pos, &share->base); + share->state.state_length=base_pos; + + if (!(open_flags & HA_OPEN_FOR_REPAIR) && + ((share->state.changed & STATE_CRASHED) || + ((open_flags & HA_OPEN_ABORT_IF_CRASHED) && + (my_disable_locking && share->state.open_count)))) + { + DBUG_PRINT("error",("Table is marked as crashed. open_flags: %u " + "changed: %u open_count: %u !locking: %d", + open_flags, share->state.changed, + share->state.open_count, my_disable_locking)); + my_errno=((share->state.changed & STATE_CRASHED_ON_REPAIR) ? + HA_ERR_CRASHED_ON_REPAIR : HA_ERR_CRASHED_ON_USAGE); + goto err; + } + + /* + We can ignore testing uuid if STATE_NOT_MOVABLE is set, as in this + case the uuid will be set in _ma_mark_file_changed() + */ + if ((share->state.changed & STATE_NOT_MOVABLE) && + share->base.born_transactional && + ((!(open_flags & HA_OPEN_IGNORE_MOVED_STATE) && + memcmp(share->base.uuid, maria_uuid, MY_UUID_SIZE)) || + share->state.create_trid > trnman_get_max_trid())) + { + if (open_flags & HA_OPEN_FOR_REPAIR) + share->state.changed|= STATE_MOVED; + else + { + my_errno= HA_ERR_OLD_FILE; + goto err; + } + } + + /* sanity check */ + if (share->base.keystart > 65535 || share->base.rec_reflength > 8) + { + my_errno=HA_ERR_CRASHED; + goto err; + } + + key_parts+=fulltext_keys*FT_SEGS; + if (share->base.max_key_length > maria_max_key_length() || + keys > MARIA_MAX_KEY || key_parts > MARIA_MAX_KEY * HA_MAX_KEY_SEG) + { + DBUG_PRINT("error",("Wrong key info: Max_key_length: %d keys: %d key_parts: %d", share->base.max_key_length, keys, key_parts)); + my_errno=HA_ERR_UNSUPPORTED; + goto err; + } + + /* Ensure we have space in the key buffer for transaction id's */ + if (share->base.born_transactional) + share->base.max_key_length= ALIGN_SIZE(share->base.max_key_length + + MARIA_MAX_PACK_TRANSID_SIZE); + + /* + If page cache is not initialized, then assume we will create the + page_cache after the table is opened! + This is only used by maria_check to allow it to check/repair tables + with different block sizes. + */ + if (share->base.block_size != maria_block_size && + share_buff.pagecache->inited != 0) + { + DBUG_PRINT("error", ("Wrong block size %u; Expected %u", + (uint) share->base.block_size, + (uint) maria_block_size)); + my_errno=HA_ERR_UNSUPPORTED; + goto err; + } + + /* Correct max_file_length based on length of sizeof(off_t) */ + max_data_file_length= + (share->options & (HA_OPTION_PACK_RECORD | HA_OPTION_COMPRESS_RECORD)) ? + (((ulonglong) 1 << (share->base.rec_reflength*8))-1) : + (_ma_safe_mul(share->base.pack_reclength, + (ulonglong) 1 << (share->base.rec_reflength*8))-1); + + max_key_file_length= + _ma_safe_mul(maria_block_size, + ((ulonglong) 1 << (share->base.key_reflength*8))-1); +#if SIZEOF_OFF_T == 4 + set_if_smaller(max_data_file_length, INT_MAX32); + set_if_smaller(max_key_file_length, INT_MAX32); +#endif + share->base.max_data_file_length=(my_off_t) max_data_file_length; + share->base.max_key_file_length=(my_off_t) max_key_file_length; + + if (share->options & HA_OPTION_COMPRESS_RECORD) + share->base.max_key_length+=2; /* For safety */ + /* Add space for node pointer */ + share->base.max_key_length+= share->base.key_reflength; + + share->unique_file_name.length= strlen(name_buff); + share->index_file_name.length= strlen(index_name); + share->data_file_name.length= strlen(data_name); + share->open_file_name.length= strlen(name); + if (!my_multi_malloc(MY_WME, + &share,sizeof(*share), + &share->state.rec_per_key_part, + sizeof(double) * key_parts, + &share->state.nulls_per_key_part, + sizeof(long)* key_parts, + &share->keyinfo,keys*sizeof(MARIA_KEYDEF), + &share->uniqueinfo,uniques*sizeof(MARIA_UNIQUEDEF), + &share->keyparts, + (key_parts+unique_key_parts+keys+uniques) * + sizeof(HA_KEYSEG), + &share->columndef, + (share->base.fields+1)*sizeof(MARIA_COLUMNDEF), + &share->column_nr, share->base.fields*sizeof(uint16), + &share->blobs,sizeof(MARIA_BLOB)*share->base.blobs, + &share->unique_file_name.str, + share->unique_file_name.length+1, + &share->index_file_name.str, + share->index_file_name.length+1, + &share->data_file_name.str, + share->data_file_name.length+1, + &share->open_file_name.str, + share->open_file_name.length+1, + &share->state.key_root,keys*sizeof(my_off_t), + &share->mmap_lock,sizeof(rw_lock_t), + NullS)) + goto err; + errpos= 4; + + *share=share_buff; + memcpy((char*) share->state.rec_per_key_part, + (char*) rec_per_key_part, sizeof(double)*key_parts); + memcpy((char*) share->state.nulls_per_key_part, + (char*) nulls_per_key_part, sizeof(long)*key_parts); + memcpy((char*) share->state.key_root, + (char*) key_root, sizeof(my_off_t)*keys); + strmov(share->unique_file_name.str, name_buff); + strmov(share->index_file_name.str, index_name); + strmov(share->data_file_name.str, data_name); + strmov(share->open_file_name.str, name); + + share->block_size= share->base.block_size; /* Convenience */ + { + HA_KEYSEG *pos=share->keyparts; + uint32 ftkey_nr= 1; + for (i=0 ; i < keys ; i++) + { + share->keyinfo[i].share= share; + disk_pos=_ma_keydef_read(disk_pos, &share->keyinfo[i]); + share->keyinfo[i].key_nr= i; + disk_pos_assert(disk_pos + share->keyinfo[i].keysegs * HA_KEYSEG_SIZE, + end_pos); + if (share->keyinfo[i].key_alg == HA_KEY_ALG_RTREE) + share->have_rtree= 1; + share->keyinfo[i].seg=pos; + for (j=0 ; j < share->keyinfo[i].keysegs; j++,pos++) + { + disk_pos=_ma_keyseg_read(disk_pos, pos); + if (pos->type == HA_KEYTYPE_TEXT || + pos->type == HA_KEYTYPE_VARTEXT1 || + pos->type == HA_KEYTYPE_VARTEXT2) + { + if (!pos->language) + pos->charset=default_charset_info; + else if (!(pos->charset= get_charset(pos->language, MYF(MY_WME)))) + { + my_errno=HA_ERR_UNKNOWN_CHARSET; + goto err; + } + } + else if (pos->type == HA_KEYTYPE_BINARY) + pos->charset= &my_charset_bin; + } + if (share->keyinfo[i].flag & HA_SPATIAL) + { +#ifdef HAVE_SPATIAL + uint sp_segs=SPDIMS*2; + share->keyinfo[i].seg=pos-sp_segs; + share->keyinfo[i].keysegs--; + versioning= 0; +#else + my_errno=HA_ERR_UNSUPPORTED; + goto err; +#endif + } + else if (share->keyinfo[i].flag & HA_FULLTEXT) + { + versioning= 0; + DBUG_ASSERT(fulltext_keys); + { + uint k; + share->keyinfo[i].seg=pos; + for (k=0; k < FT_SEGS; k++) + { + *pos= ft_keysegs[k]; + pos[0].language= pos[-1].language; + if (!(pos[0].charset= pos[-1].charset)) + { + my_errno=HA_ERR_CRASHED; + goto err; + } + pos++; + } + } + if (!share->ft2_keyinfo.seg) + { + memcpy(&share->ft2_keyinfo, &share->keyinfo[i], + sizeof(MARIA_KEYDEF)); + share->ft2_keyinfo.keysegs=1; + share->ft2_keyinfo.flag=0; + share->ft2_keyinfo.keylength= + share->ft2_keyinfo.minlength= + share->ft2_keyinfo.maxlength=HA_FT_WLEN+share->base.rec_reflength; + share->ft2_keyinfo.seg=pos-1; + share->ft2_keyinfo.end=pos; + setup_key_functions(& share->ft2_keyinfo); + } + share->keyinfo[i].ftkey_nr= ftkey_nr++; + } + setup_key_functions(share->keyinfo+i); + share->keyinfo[i].end=pos; + pos->type=HA_KEYTYPE_END; /* End */ + pos->length=share->base.rec_reflength; + pos->null_bit=0; + pos->flag=0; /* For purify */ + pos++; + } + for (i=0 ; i < uniques ; i++) + { + disk_pos=_ma_uniquedef_read(disk_pos, &share->uniqueinfo[i]); + disk_pos_assert(disk_pos + share->uniqueinfo[i].keysegs * + HA_KEYSEG_SIZE, end_pos); + share->uniqueinfo[i].seg=pos; + for (j=0 ; j < share->uniqueinfo[i].keysegs; j++,pos++) + { + disk_pos=_ma_keyseg_read(disk_pos, pos); + if (pos->type == HA_KEYTYPE_TEXT || + pos->type == HA_KEYTYPE_VARTEXT1 || + pos->type == HA_KEYTYPE_VARTEXT2) + { + if (!pos->language) + pos->charset=default_charset_info; + else if (!(pos->charset= get_charset(pos->language, MYF(MY_WME)))) + { + my_errno=HA_ERR_UNKNOWN_CHARSET; + goto err; + } + } + } + share->uniqueinfo[i].end=pos; + pos->type=HA_KEYTYPE_END; /* End */ + pos->null_bit=0; + pos->flag=0; + pos++; + } + share->ftkeys= ftkey_nr; + } + share->data_file_type= share->state.header.data_file_type; + share->base_length= (BASE_ROW_HEADER_SIZE + + share->base.is_nulls_extended + + share->base.null_bytes + + share->base.pack_bytes + + test(share->options & HA_OPTION_CHECKSUM)); + share->keypage_header= ((share->base.born_transactional ? + LSN_STORE_SIZE + TRANSID_SIZE : + 0) + KEYPAGE_KEYID_SIZE + KEYPAGE_FLAG_SIZE + + KEYPAGE_USED_SIZE); + share->kfile.file= kfile; + + if (open_flags & HA_OPEN_COPY) + { + /* + this instance will be a temporary one used just to create a data + file for REPAIR. Don't do logging. This base information will not go + to disk. + */ + share->base.born_transactional= FALSE; + } + if (share->base.born_transactional) + { + share->page_type= PAGECACHE_LSN_PAGE; + if (share->state.create_rename_lsn == LSN_NEEDS_NEW_STATE_LSNS) + { + /* + Was repaired with maria_chk, maybe later maria_pack-ed. Some sort of + import into the server. It starts its existence (from the point of + view of the server, including server's recovery) now. + */ + if (((open_flags & HA_OPEN_FROM_SQL_LAYER) && + (share->state.changed & STATE_NOT_MOVABLE)) || maria_in_recovery) + _ma_update_state_lsns_sub(share, LSN_IMPOSSIBLE, + trnman_get_min_safe_trid(), TRUE, TRUE); + } + else if ((!LSN_VALID(share->state.create_rename_lsn) || + !LSN_VALID(share->state.is_of_horizon) || + (cmp_translog_addr(share->state.create_rename_lsn, + share->state.is_of_horizon) > 0) || + !LSN_VALID(share->state.skip_redo_lsn) || + (cmp_translog_addr(share->state.create_rename_lsn, + share->state.skip_redo_lsn) > 0)) && + !(open_flags & HA_OPEN_FOR_REPAIR)) + { + /* + If in Recovery, it will not work. If LSN is invalid and not + LSN_NEEDS_NEW_STATE_LSNS, header must be corrupted. + In both cases, must repair. + */ + my_errno=((share->state.changed & STATE_CRASHED_ON_REPAIR) ? + HA_ERR_CRASHED_ON_REPAIR : HA_ERR_CRASHED_ON_USAGE); + goto err; + } + } + else + share->page_type= PAGECACHE_PLAIN_PAGE; + share->now_transactional= share->base.born_transactional; + + /* Use pack_reclength as we don't want to modify base.pack_recklength */ + if (share->state.header.org_data_file_type == DYNAMIC_RECORD) + { + /* add bits used to pack data to pack_reclength for faster allocation */ + share->base.pack_reclength+= share->base.pack_bytes; + share->base.extra_rec_buff_size= + (ALIGN_SIZE(MARIA_MAX_DYN_BLOCK_HEADER) + MARIA_SPLIT_LENGTH + + MARIA_REC_BUFF_OFFSET); + } + if (share->data_file_type == COMPRESSED_RECORD) + { + /* Need some extra bytes for decode_bytes */ + share->base.extra_rec_buff_size+= 7; + } + share->base.default_rec_buff_size= max(share->base.pack_reclength + + share->base.extra_rec_buff_size, + share->base.max_key_length); + + disk_pos_assert(disk_pos + share->base.fields *MARIA_COLUMNDEF_SIZE, + end_pos); + for (i= j= 0 ; i < share->base.fields ; i++) + { + disk_pos=_ma_columndef_read(disk_pos,&share->columndef[i]); + share->columndef[i].pack_type=0; + share->columndef[i].huff_tree=0; + if (share->columndef[i].type == FIELD_BLOB) + { + share->blobs[j].pack_length= + share->columndef[i].length-portable_sizeof_char_ptr; + share->blobs[j].offset= share->columndef[i].offset; + j++; + } + } + share->columndef[i].type= FIELD_LAST; /* End marker */ + disk_pos= _ma_column_nr_read(disk_pos, share->column_nr, + share->base.fields); + + if ((share->data_file_type == BLOCK_RECORD || + share->data_file_type == COMPRESSED_RECORD)) + { + if (_ma_open_datafile(&info, share, name, -1)) + goto err; + data_file= info.dfile.file; + } + errpos= 5; + + if (open_flags & HA_OPEN_DELAY_KEY_WRITE) + share->options|= HA_OPTION_DELAY_KEY_WRITE; + if (mode == O_RDONLY) + share->options|= HA_OPTION_READ_ONLY_DATA; + share->is_log_table= FALSE; + + if (open_flags & HA_OPEN_TMP_TABLE) + { + share->options|= HA_OPTION_TMP_TABLE; + share->temporary= share->delay_key_write= 1; + share->write_flag=MYF(MY_NABP); + share->w_locks++; /* We don't have to update status */ + share->tot_locks++; + } + + _ma_set_index_pagecache_callbacks(&share->kfile, share); + share->this_process=(ulong) getpid(); +#ifdef EXTERNAL_LOCKING + share->last_process= share->state.process; +#endif + share->base.key_parts=key_parts; + share->base.all_key_parts=key_parts+unique_key_parts; + if (!(share->last_version=share->state.version)) + share->last_version=1; /* Safety */ + share->rec_reflength=share->base.rec_reflength; /* May be changed */ + share->base.margin_key_file_length=(share->base.max_key_file_length - + (keys ? MARIA_INDEX_BLOCK_MARGIN * + share->block_size * keys : 0)); + share->block_size= share->base.block_size; + my_free(disk_cache, MYF(0)); + _ma_setup_functions(share); + if ((*share->once_init)(share, info.dfile.file)) + goto err; + if (share->now_transactional) + { + /* Setup initial state that is visible for all */ + MARIA_STATE_HISTORY_CLOSED *history; + if ((history= (MARIA_STATE_HISTORY_CLOSED *) + hash_search(&maria_stored_state, + (uchar*) &share->state.create_rename_lsn, 0))) + { + /* + Move history from hash to share. This is safe to do as we + don't have a lock on share->intern_lock. + */ + share->state_history= + _ma_remove_not_visible_states(history->state_history, 0, 0); + history->state_history= 0; + (void) hash_delete(&maria_stored_state, (uchar*) history); + } + else + { + /* Table is not part of any active transaction; Create new history */ + if (!(share->state_history= (MARIA_STATE_HISTORY *) + my_malloc(sizeof(*share->state_history), MYF(MY_WME)))) + goto err; + share->state_history->trid= 0; /* Visible by all */ + share->state_history->state= share->state.state; + share->state_history->next= 0; + } + } +#ifdef THREAD + thr_lock_init(&share->lock); + pthread_mutex_init(&share->intern_lock, MY_MUTEX_INIT_FAST); + pthread_mutex_init(&share->key_del_lock, MY_MUTEX_INIT_FAST); + pthread_cond_init(&share->key_del_cond, 0); + pthread_mutex_init(&share->close_lock, MY_MUTEX_INIT_FAST); + for (i=0; i<keys; i++) + VOID(my_rwlock_init(&share->keyinfo[i].root_lock, NULL)); + VOID(my_rwlock_init(&share->mmap_lock, NULL)); + + share->row_is_visible= _ma_row_visible_always; + share->lock.get_status= _ma_reset_update_flag; + if (!thr_lock_inited) + { + /* Probably a single threaded program; Don't use concurrent inserts */ + maria_concurrent_insert=0; + } + else if (maria_concurrent_insert) + { + share->non_transactional_concurrent_insert= + ((share->options & (HA_OPTION_READ_ONLY_DATA | HA_OPTION_TMP_TABLE | + HA_OPTION_COMPRESS_RECORD | + HA_OPTION_TEMP_COMPRESS_RECORD)) || + (open_flags & HA_OPEN_TMP_TABLE) || + share->data_file_type == BLOCK_RECORD || + share->have_rtree) ? 0 : 1; + if (share->non_transactional_concurrent_insert || + (!share->temporary && share->now_transactional && versioning)) + { + share->lock_key_trees= 1; + if (share->data_file_type == BLOCK_RECORD) + { + DBUG_ASSERT(share->now_transactional); + share->have_versioning= 1; + share->row_is_visible= _ma_row_visible_transactional_table; + share->lock.get_status= _ma_block_get_status; + share->lock.update_status= _ma_block_update_status; + share->lock.check_status= _ma_block_check_status; + /* + We can for the moment only allow multiple concurrent inserts + only if there is no auto-increment key. To lift this restriction + we have to: + - Extend statement base replication to support auto-increment + intervalls. + - Fix that we allocate auto-increment in intervals and that + it's properly reset if the interval was not used + */ + share->lock.allow_multiple_concurrent_insert= + share->base.auto_key == 0; + share->lock_restore_status= 0; + } + else + { + share->row_is_visible= _ma_row_visible_non_transactional_table; + share->lock.get_status= _ma_get_status; + share->lock.copy_status= _ma_copy_status; + share->lock.update_status= _ma_update_status; + share->lock.restore_status= _ma_restore_status; + share->lock.check_status= _ma_check_status; + share->lock_restore_status= _ma_restore_status; + } + } + } +#endif + /* + Memory mapping can only be requested after initializing intern_lock. + */ + if (open_flags & HA_OPEN_MMAP) + { + info.s= share; + maria_extra(&info, HA_EXTRA_MMAP, 0); + } + } + else + { + share= old_info->s; + if (share->data_file_type == BLOCK_RECORD) + data_file= share->bitmap.file.file; /* Only opened once */ + } + + if (!(m_info= maria_clone_internal(share, name, mode, data_file))) + goto err; + + pthread_mutex_unlock(&THR_LOCK_maria); + DBUG_RETURN(m_info); + +err: + save_errno=my_errno ? my_errno : HA_ERR_END_OF_FILE; + if ((save_errno == HA_ERR_CRASHED) || + (save_errno == HA_ERR_CRASHED_ON_USAGE) || + (save_errno == HA_ERR_CRASHED_ON_REPAIR)) + { + LEX_STRING tmp_name; + tmp_name.str= (char*) name; + tmp_name.length= strlen(name); + _ma_report_error(save_errno, &tmp_name); + } + if (save_errno == HA_ERR_OLD_FILE) /* uuid is different ? */ + save_errno= HA_ERR_CRASHED_ON_USAGE; /* the code to trigger auto-repair */ + switch (errpos) { + case 5: + if (data_file >= 0) + VOID(my_close(data_file, MYF(0))); + if (old_info) + break; /* Don't remove open table */ + (*share->once_end)(share); + /* fall through */ + case 4: + my_free(share,MYF(0)); + /* fall through */ + case 3: + my_free(disk_cache, MYF(0)); + /* fall through */ + case 1: + VOID(my_close(kfile,MYF(0))); + /* fall through */ + case 0: + default: + break; + } + pthread_mutex_unlock(&THR_LOCK_maria); + my_errno= save_errno; + DBUG_RETURN (NULL); +} /* maria_open */ + + +/* + Reallocate a buffer, if the current buffer is not large enough +*/ + +my_bool _ma_alloc_buffer(uchar **old_addr, size_t *old_size, + size_t new_size) +{ + if (*old_size < new_size) + { + uchar *addr; + if (!(addr= (uchar*) my_realloc(*old_addr, new_size, + MYF(MY_ALLOW_ZERO_PTR)))) + return 1; + *old_addr= addr; + *old_size= new_size; + } + return 0; +} + + +ulonglong _ma_safe_mul(ulonglong a, ulonglong b) +{ + ulonglong max_val= ~ (ulonglong) 0; /* my_off_t is unsigned */ + + if (!a || max_val / a < b) + return max_val; + return a*b; +} + + /* Set up functions in structs */ + +void _ma_setup_functions(register MARIA_SHARE *share) +{ + share->once_init= maria_once_init_dummy; + share->once_end= maria_once_end_dummy; + share->init= maria_scan_init_dummy; + share->end= maria_scan_end_dummy; + share->scan_init= maria_scan_init_dummy;/* Compat. dummy function */ + share->scan_end= maria_scan_end_dummy;/* Compat. dummy function */ + share->scan_remember_pos= _ma_def_scan_remember_pos; + share->scan_restore_pos= _ma_def_scan_restore_pos; + + share->write_record_init= _ma_write_init_default; + share->write_record_abort= _ma_write_abort_default; + share->keypos_to_recpos= _ma_transparent_recpos; + share->recpos_to_keypos= _ma_transparent_recpos; + + switch (share->data_file_type) { + case COMPRESSED_RECORD: + share->read_record= _ma_read_pack_record; + share->scan= _ma_read_rnd_pack_record; + share->once_init= _ma_once_init_pack_row; + share->once_end= _ma_once_end_pack_row; + /* + Calculate checksum according to data in the original, not compressed, + row. + */ + if (share->state.header.org_data_file_type == STATIC_RECORD && + ! (share->options & HA_OPTION_NULL_FIELDS)) + share->calc_checksum= _ma_static_checksum; + else + share->calc_checksum= _ma_checksum; + share->calc_write_checksum= share->calc_checksum; + break; + case DYNAMIC_RECORD: + share->read_record= _ma_read_dynamic_record; + share->scan= _ma_read_rnd_dynamic_record; + share->delete_record= _ma_delete_dynamic_record; + share->compare_record= _ma_cmp_dynamic_record; + share->compare_unique= _ma_cmp_dynamic_unique; + share->calc_checksum= share->calc_write_checksum= _ma_checksum; + if (share->base.blobs) + { + share->update_record= _ma_update_blob_record; + share->write_record= _ma_write_blob_record; + } + else + { + share->write_record= _ma_write_dynamic_record; + share->update_record= _ma_update_dynamic_record; + } + break; + case STATIC_RECORD: + share->read_record= _ma_read_static_record; + share->scan= _ma_read_rnd_static_record; + share->delete_record= _ma_delete_static_record; + share->compare_record= _ma_cmp_static_record; + share->update_record= _ma_update_static_record; + share->write_record= _ma_write_static_record; + share->compare_unique= _ma_cmp_static_unique; + share->keypos_to_recpos= _ma_static_keypos_to_recpos; + share->recpos_to_keypos= _ma_static_recpos_to_keypos; + if (share->state.header.org_data_file_type == STATIC_RECORD && + ! (share->options & HA_OPTION_NULL_FIELDS)) + share->calc_checksum= _ma_static_checksum; + else + share->calc_checksum= _ma_checksum; + break; + case BLOCK_RECORD: + share->once_init= _ma_once_init_block_record; + share->once_end= _ma_once_end_block_record; + share->init= _ma_init_block_record; + share->end= _ma_end_block_record; + share->write_record_init= _ma_write_init_block_record; + share->write_record_abort= _ma_write_abort_block_record; + share->scan_init= _ma_scan_init_block_record; + share->scan_end= _ma_scan_end_block_record; + share->scan= _ma_scan_block_record; + share->scan_remember_pos= _ma_scan_remember_block_record; + share->scan_restore_pos= _ma_scan_restore_block_record; + share->read_record= _ma_read_block_record; + share->delete_record= _ma_delete_block_record; + share->compare_record= _ma_compare_block_record; + share->update_record= _ma_update_block_record; + share->write_record= _ma_write_block_record; + share->compare_unique= _ma_cmp_block_unique; + share->calc_checksum= _ma_checksum; + share->keypos_to_recpos= _ma_transaction_keypos_to_recpos; + share->recpos_to_keypos= _ma_transaction_recpos_to_keypos; + + /* + write_block_record() will calculate the checksum; Tell maria_write() + that it doesn't have to do this. + */ + share->calc_write_checksum= 0; + break; + } + share->file_read= _ma_nommap_pread; + share->file_write= _ma_nommap_pwrite; + share->calc_check_checksum= share->calc_checksum; + + if (!(share->options & HA_OPTION_CHECKSUM) && + share->data_file_type != COMPRESSED_RECORD) + share->calc_checksum= share->calc_write_checksum= 0; + return; +} + + +static void setup_key_functions(register MARIA_KEYDEF *keyinfo) +{ + if (keyinfo->key_alg == HA_KEY_ALG_RTREE) + { +#ifdef HAVE_RTREE_KEYS + keyinfo->ck_insert = maria_rtree_insert; + keyinfo->ck_delete = maria_rtree_delete; +#else + DBUG_ASSERT(0); /* maria_open should check it never happens */ +#endif + } + else + { + keyinfo->ck_insert = _ma_ck_write; + keyinfo->ck_delete = _ma_ck_delete; + } + if (keyinfo->flag & HA_SPATIAL) + keyinfo->make_key= _ma_sp_make_key; + else + keyinfo->make_key= _ma_make_key; + + if (keyinfo->flag & HA_BINARY_PACK_KEY) + { /* Simple prefix compression */ + keyinfo->bin_search= _ma_seq_search; + keyinfo->get_key= _ma_get_binary_pack_key; + keyinfo->skip_key= _ma_skip_binary_pack_key; + keyinfo->pack_key= _ma_calc_bin_pack_key_length; + keyinfo->store_key= _ma_store_bin_pack_key; + } + else if (keyinfo->flag & HA_VAR_LENGTH_KEY) + { + keyinfo->get_key= _ma_get_pack_key; + keyinfo->skip_key= _ma_skip_pack_key; + if (keyinfo->seg[0].flag & HA_PACK_KEY) + { /* Prefix compression */ + /* + _ma_prefix_search() compares end-space against ASCII blank (' '). + It cannot be used for character sets, that do not encode the + blank character like ASCII does. UCS2 is an example. All + character sets with a fixed width > 1 or a mimimum width > 1 + cannot represent blank like ASCII does. In these cases we have + to use _ma_seq_search() for the search. + */ + if (!keyinfo->seg->charset || use_strnxfrm(keyinfo->seg->charset) || + (keyinfo->seg->flag & HA_NULL_PART) || + keyinfo->seg->charset->mbminlen > 1) + keyinfo->bin_search= _ma_seq_search; + else + keyinfo->bin_search= _ma_prefix_search; + keyinfo->pack_key= _ma_calc_var_pack_key_length; + keyinfo->store_key= _ma_store_var_pack_key; + } + else + { + keyinfo->bin_search= _ma_seq_search; + keyinfo->pack_key= _ma_calc_var_key_length; /* Variable length key */ + keyinfo->store_key= _ma_store_static_key; + } + } + else + { + keyinfo->bin_search= _ma_bin_search; + keyinfo->get_key= _ma_get_static_key; + keyinfo->skip_key= _ma_skip_static_key; + keyinfo->pack_key= _ma_calc_static_key_length; + keyinfo->store_key= _ma_store_static_key; + } + + /* set keyinfo->write_comp_flag */ + if (keyinfo->flag & HA_SORT_ALLOWS_SAME) + keyinfo->write_comp_flag=SEARCH_BIGGER; /* Put after same key */ + else if (keyinfo->flag & ( HA_NOSAME | HA_FULLTEXT)) + { + keyinfo->write_comp_flag= SEARCH_FIND | SEARCH_UPDATE; /* No duplicates */ + if (keyinfo->flag & HA_NULL_ARE_EQUAL) + keyinfo->write_comp_flag|= SEARCH_NULL_ARE_EQUAL; + } + else + keyinfo->write_comp_flag= SEARCH_SAME; /* Keys in rec-pos order */ + keyinfo->write_comp_flag|= SEARCH_INSERT; + return; +} + + +/** + @brief Function to save and store the header in the index file (.MYI) + + Operates under MARIA_SHARE::intern_lock if requested. + Sets MARIA_SHARE::MARIA_STATE_INFO::is_of_horizon if transactional table. + Then calls _ma_state_info_write_sub(). + + @param share table + @param pWrite bitmap: if 1 (MA_STATE_INFO_WRITE_DONT_MOVE_OFFSET) + is set my_pwrite() is used otherwise my_write(); + if 2 (MA_STATE_INFO_WRITE_FULL_INFO) is set, info + about keys is written (should only be needed + after ALTER TABLE ENABLE/DISABLE KEYS, and + REPAIR/OPTIMIZE); if 4 (MA_STATE_INFO_WRITE_LOCK) + is set, MARIA_SHARE::intern_lock is taken. + + @return Operation status + @retval 0 OK + @retval 1 Error +*/ + +uint _ma_state_info_write(MARIA_SHARE *share, uint pWrite) +{ + uint res; + if (share->options & HA_OPTION_READ_ONLY_DATA) + return 0; + + if (pWrite & MA_STATE_INFO_WRITE_LOCK) + pthread_mutex_lock(&share->intern_lock); + else if (maria_multi_threaded) + { + safe_mutex_assert_owner(&share->intern_lock); + } + if (share->base.born_transactional && translog_status == TRANSLOG_OK && + !maria_in_recovery) + { + /* + In a recovery, we want to set is_of_horizon to the LSN of the last + record executed by Recovery, not the current EOF of the log (which + is too new). Recovery does it by itself. + */ + share->state.is_of_horizon= translog_get_horizon(); + DBUG_PRINT("info", ("is_of_horizon set to LSN (%lu,0x%lx)", + LSN_IN_PARTS(share->state.is_of_horizon))); + } + res= _ma_state_info_write_sub(share->kfile.file, &share->state, pWrite); + if (pWrite & MA_STATE_INFO_WRITE_LOCK) + pthread_mutex_unlock(&share->intern_lock); + share->changed= 0; + return res; +} + + +/** + @brief Function to save and store the header in the index file (.MYI). + + Shortcut to use instead of _ma_state_info_write() when appropriate. + + @param file descriptor of the index file to write + @param state state information to write to the file + @param pWrite bitmap: if 1 (MA_STATE_INFO_WRITE_DONT_MOVE_OFFSET) + is set my_pwrite() is used otherwise my_write(); + if 2 (MA_STATE_INFO_WRITE_FULL_INFO) is set, info + about keys is written (should only be needed + after ALTER TABLE ENABLE/DISABLE KEYS, and + REPAIR/OPTIMIZE). + + @notes + For transactional multiuser tables, this function is called + with intern_lock & translog_lock or when the last thread who + is using the table is closing it. + Because of the translog_lock we don't need to have a lock on + key_del_lock. + + @return Operation status + @retval 0 OK + @retval 1 Error +*/ + +uint _ma_state_info_write_sub(File file, MARIA_STATE_INFO *state, uint pWrite) +{ + uchar buff[MARIA_STATE_INFO_SIZE + MARIA_STATE_EXTRA_SIZE]; + uchar *ptr=buff; + uint i, keys= (uint) state->header.keys; + size_t res; + DBUG_ENTER("_ma_state_info_write_sub"); + + memcpy_fixed(ptr,&state->header,sizeof(state->header)); + ptr+=sizeof(state->header); + + /* open_count must be first because of _ma_mark_file_changed ! */ + mi_int2store(ptr,state->open_count); ptr+= 2; + /* changed must be second, because of _ma_mark_file_crashed */ + mi_int2store(ptr,state->changed); ptr+= 2; + + /* + If you change the offset of these LSNs, note that some functions do a + direct write of them without going through this function. + */ + lsn_store(ptr, state->create_rename_lsn); ptr+= LSN_STORE_SIZE; + lsn_store(ptr, state->is_of_horizon); ptr+= LSN_STORE_SIZE; + lsn_store(ptr, state->skip_redo_lsn); ptr+= LSN_STORE_SIZE; + mi_rowstore(ptr,state->state.records); ptr+= 8; + mi_rowstore(ptr,state->state.del); ptr+= 8; + mi_rowstore(ptr,state->split); ptr+= 8; + mi_sizestore(ptr,state->dellink); ptr+= 8; + mi_sizestore(ptr,state->first_bitmap_with_space); ptr+= 8; + mi_sizestore(ptr,state->state.key_file_length); ptr+= 8; + mi_sizestore(ptr,state->state.data_file_length); ptr+= 8; + mi_sizestore(ptr,state->state.empty); ptr+= 8; + mi_sizestore(ptr,state->state.key_empty); ptr+= 8; + mi_int8store(ptr,state->auto_increment); ptr+= 8; + mi_int8store(ptr,(ulonglong) state->state.checksum); ptr+= 8; + mi_int8store(ptr,state->create_trid); ptr+= 8; + mi_int4store(ptr,state->status); ptr+= 4; + mi_int4store(ptr,state->update_count); ptr+= 4; + *ptr++= state->sortkey; + *ptr++= 0; /* Reserved */ + ptr+= state->state_diff_length; + + for (i=0; i < keys; i++) + { + mi_sizestore(ptr,state->key_root[i]); ptr+= 8; + } + mi_sizestore(ptr,state->key_del); ptr+= 8; + if (pWrite & MA_STATE_INFO_WRITE_FULL_INFO) /* From maria_chk */ + { + uint key_parts= mi_uint2korr(state->header.key_parts); + mi_int4store(ptr,state->sec_index_changed); ptr+= 4; + mi_int4store(ptr,state->sec_index_used); ptr+= 4; + mi_int4store(ptr,state->version); ptr+= 4; + mi_int8store(ptr,state->key_map); ptr+= 8; + mi_int8store(ptr,(ulonglong) state->create_time); ptr+= 8; + mi_int8store(ptr,(ulonglong) state->recover_time); ptr+= 8; + mi_int8store(ptr,(ulonglong) state->check_time); ptr+= 8; + mi_sizestore(ptr, state->records_at_analyze); ptr+= 8; + /* reserve place for some information per key */ + bzero(ptr, keys*4); ptr+= keys*4; + for (i=0 ; i < key_parts ; i++) + { + float8store(ptr, state->rec_per_key_part[i]); ptr+= 8; + mi_int4store(ptr, state->nulls_per_key_part[i]); ptr+= 4; + } + } + + res= (pWrite & MA_STATE_INFO_WRITE_DONT_MOVE_OFFSET) ? + my_pwrite(file, buff, (size_t) (ptr-buff), 0L, + MYF(MY_NABP | MY_THREADSAFE)) : + my_write(file, buff, (size_t) (ptr-buff), + MYF(MY_NABP)); + DBUG_RETURN(res != 0); +} + + +static uchar *_ma_state_info_read(uchar *ptr, MARIA_STATE_INFO *state) +{ + uint i,keys,key_parts; + memcpy_fixed(&state->header,ptr, sizeof(state->header)); + ptr+= sizeof(state->header); + keys= (uint) state->header.keys; + key_parts= mi_uint2korr(state->header.key_parts); + + state->open_count = mi_uint2korr(ptr); ptr+= 2; + state->changed= mi_uint2korr(ptr); ptr+= 2; + state->create_rename_lsn= lsn_korr(ptr); ptr+= LSN_STORE_SIZE; + state->is_of_horizon= lsn_korr(ptr); ptr+= LSN_STORE_SIZE; + state->skip_redo_lsn= lsn_korr(ptr); ptr+= LSN_STORE_SIZE; + state->state.records= mi_rowkorr(ptr); ptr+= 8; + state->state.del = mi_rowkorr(ptr); ptr+= 8; + state->split = mi_rowkorr(ptr); ptr+= 8; + state->dellink= mi_sizekorr(ptr); ptr+= 8; + state->first_bitmap_with_space= mi_sizekorr(ptr); ptr+= 8; + state->state.key_file_length = mi_sizekorr(ptr); ptr+= 8; + state->state.data_file_length= mi_sizekorr(ptr); ptr+= 8; + state->state.empty = mi_sizekorr(ptr); ptr+= 8; + state->state.key_empty= mi_sizekorr(ptr); ptr+= 8; + state->auto_increment=mi_uint8korr(ptr); ptr+= 8; + state->state.checksum=(ha_checksum) mi_uint8korr(ptr);ptr+= 8; + state->create_trid= mi_uint8korr(ptr); ptr+= 8; + state->status = mi_uint4korr(ptr); ptr+= 4; + state->update_count=mi_uint4korr(ptr); ptr+= 4; + state->sortkey= (uint) *ptr++; + ptr++; /* reserved */ + + ptr+= state->state_diff_length; + + for (i=0; i < keys; i++) + { + state->key_root[i]= mi_sizekorr(ptr); ptr+= 8; + } + state->key_del= mi_sizekorr(ptr); ptr+= 8; + state->sec_index_changed = mi_uint4korr(ptr); ptr+= 4; + state->sec_index_used = mi_uint4korr(ptr); ptr+= 4; + state->version = mi_uint4korr(ptr); ptr+= 4; + state->key_map = mi_uint8korr(ptr); ptr+= 8; + state->create_time = (time_t) mi_sizekorr(ptr); ptr+= 8; + state->recover_time =(time_t) mi_sizekorr(ptr); ptr+= 8; + state->check_time = (time_t) mi_sizekorr(ptr); ptr+= 8; + state->records_at_analyze= mi_sizekorr(ptr); ptr+= 8; + ptr+= keys * 4; /* Skip reserved bytes */ + for (i=0 ; i < key_parts ; i++) + { + float8get(state->rec_per_key_part[i], ptr); ptr+= 8; + state->nulls_per_key_part[i]= mi_uint4korr(ptr); ptr+= 4; + } + return ptr; +} + + +/** + @brief Fills the state by reading its copy on disk. + + Should not be called for transactional tables, as their state on disk is + rarely current and so is often misleading for a reader. + Does nothing in single user mode. + + @param file file to read from + @param state state which will be filled +*/ + +uint _ma_state_info_read_dsk(File file __attribute__((unused)), + MARIA_STATE_INFO *state __attribute__((unused))) +{ +#ifdef EXTERNAL_LOCKING + uchar buff[MARIA_STATE_INFO_SIZE + MARIA_STATE_EXTRA_SIZE]; + + /* trick to detect transactional tables */ + DBUG_ASSERT(state->create_rename_lsn == LSN_IMPOSSIBLE); + if (!maria_single_user) + { + if (my_pread(file, buff, state->state_length, 0L, MYF(MY_NABP))) + return 1; + _ma_state_info_read(buff, state); + } +#endif + return 0; +} + + +/**************************************************************************** +** store and read of MARIA_BASE_INFO +****************************************************************************/ + +uint _ma_base_info_write(File file, MARIA_BASE_INFO *base) +{ + uchar buff[MARIA_BASE_INFO_SIZE], *ptr=buff; + + bmove(ptr, maria_uuid, MY_UUID_SIZE); + ptr+= MY_UUID_SIZE; + mi_sizestore(ptr,base->keystart); ptr+= 8; + mi_sizestore(ptr,base->max_data_file_length); ptr+= 8; + mi_sizestore(ptr,base->max_key_file_length); ptr+= 8; + mi_rowstore(ptr,base->records); ptr+= 8; + mi_rowstore(ptr,base->reloc); ptr+= 8; + mi_int4store(ptr,base->mean_row_length); ptr+= 4; + mi_int4store(ptr,base->reclength); ptr+= 4; + mi_int4store(ptr,base->pack_reclength); ptr+= 4; + mi_int4store(ptr,base->min_pack_length); ptr+= 4; + mi_int4store(ptr,base->max_pack_length); ptr+= 4; + mi_int4store(ptr,base->min_block_length); ptr+= 4; + mi_int2store(ptr,base->fields); ptr+= 2; + mi_int2store(ptr,base->fixed_not_null_fields); ptr+= 2; + mi_int2store(ptr,base->fixed_not_null_fields_length); ptr+= 2; + mi_int2store(ptr,base->max_field_lengths); ptr+= 2; + mi_int2store(ptr,base->pack_fields); ptr+= 2; + mi_int2store(ptr,base->extra_options) ptr+= 2; + mi_int2store(ptr,base->null_bytes); ptr+= 2; + mi_int2store(ptr,base->original_null_bytes); ptr+= 2; + mi_int2store(ptr,base->field_offsets); ptr+= 2; + mi_int2store(ptr,0); ptr+= 2; /* reserved */ + mi_int2store(ptr,base->block_size); ptr+= 2; + *ptr++= base->rec_reflength; + *ptr++= base->key_reflength; + *ptr++= base->keys; + *ptr++= base->auto_key; + *ptr++= base->born_transactional; + *ptr++= 0; /* Reserved */ + mi_int2store(ptr,base->pack_bytes); ptr+= 2; + mi_int2store(ptr,base->blobs); ptr+= 2; + mi_int2store(ptr,base->max_key_block_length); ptr+= 2; + mi_int2store(ptr,base->max_key_length); ptr+= 2; + mi_int2store(ptr,base->extra_alloc_bytes); ptr+= 2; + *ptr++= base->extra_alloc_procent; + bzero(ptr,16); ptr+= 16; /* extra */ + DBUG_ASSERT((ptr - buff) == MARIA_BASE_INFO_SIZE); + return my_write(file, buff, (size_t) (ptr-buff), MYF(MY_NABP)) != 0; +} + + +static uchar *_ma_base_info_read(uchar *ptr, MARIA_BASE_INFO *base) +{ + bmove(base->uuid, ptr, MY_UUID_SIZE); ptr+= MY_UUID_SIZE; + base->keystart= mi_sizekorr(ptr); ptr+= 8; + base->max_data_file_length= mi_sizekorr(ptr); ptr+= 8; + base->max_key_file_length= mi_sizekorr(ptr); ptr+= 8; + base->records= (ha_rows) mi_sizekorr(ptr); ptr+= 8; + base->reloc= (ha_rows) mi_sizekorr(ptr); ptr+= 8; + base->mean_row_length= mi_uint4korr(ptr); ptr+= 4; + base->reclength= mi_uint4korr(ptr); ptr+= 4; + base->pack_reclength= mi_uint4korr(ptr); ptr+= 4; + base->min_pack_length= mi_uint4korr(ptr); ptr+= 4; + base->max_pack_length= mi_uint4korr(ptr); ptr+= 4; + base->min_block_length= mi_uint4korr(ptr); ptr+= 4; + base->fields= mi_uint2korr(ptr); ptr+= 2; + base->fixed_not_null_fields= mi_uint2korr(ptr); ptr+= 2; + base->fixed_not_null_fields_length= mi_uint2korr(ptr);ptr+= 2; + base->max_field_lengths= mi_uint2korr(ptr); ptr+= 2; + base->pack_fields= mi_uint2korr(ptr); ptr+= 2; + base->extra_options= mi_uint2korr(ptr); ptr+= 2; + base->null_bytes= mi_uint2korr(ptr); ptr+= 2; + base->original_null_bytes= mi_uint2korr(ptr); ptr+= 2; + base->field_offsets= mi_uint2korr(ptr); ptr+= 2; + ptr+= 2; + base->block_size= mi_uint2korr(ptr); ptr+= 2; + + base->rec_reflength= *ptr++; + base->key_reflength= *ptr++; + base->keys= *ptr++; + base->auto_key= *ptr++; + base->born_transactional= *ptr++; + ptr++; + base->pack_bytes= mi_uint2korr(ptr); ptr+= 2; + base->blobs= mi_uint2korr(ptr); ptr+= 2; + base->max_key_block_length= mi_uint2korr(ptr); ptr+= 2; + base->max_key_length= mi_uint2korr(ptr); ptr+= 2; + base->extra_alloc_bytes= mi_uint2korr(ptr); ptr+= 2; + base->extra_alloc_procent= *ptr++; + ptr+= 16; + return ptr; +} + +/*-------------------------------------------------------------------------- + maria_keydef +---------------------------------------------------------------------------*/ + +my_bool _ma_keydef_write(File file, MARIA_KEYDEF *keydef) +{ + uchar buff[MARIA_KEYDEF_SIZE]; + uchar *ptr=buff; + + *ptr++= (uchar) keydef->keysegs; + *ptr++= keydef->key_alg; /* Rtree or Btree */ + mi_int2store(ptr,keydef->flag); ptr+= 2; + mi_int2store(ptr,keydef->block_length); ptr+= 2; + mi_int2store(ptr,keydef->keylength); ptr+= 2; + mi_int2store(ptr,keydef->minlength); ptr+= 2; + mi_int2store(ptr,keydef->maxlength); ptr+= 2; + return my_write(file, buff, (size_t) (ptr-buff), MYF(MY_NABP)) != 0; +} + +uchar *_ma_keydef_read(uchar *ptr, MARIA_KEYDEF *keydef) +{ + keydef->keysegs = (uint) *ptr++; + keydef->key_alg = *ptr++; /* Rtree or Btree */ + + keydef->flag = mi_uint2korr(ptr); ptr+= 2; + keydef->block_length = mi_uint2korr(ptr); ptr+= 2; + keydef->keylength = mi_uint2korr(ptr); ptr+= 2; + keydef->minlength = mi_uint2korr(ptr); ptr+= 2; + keydef->maxlength = mi_uint2korr(ptr); ptr+= 2; + keydef->underflow_block_length=keydef->block_length/3; + keydef->version = 0; /* Not saved */ + keydef->parser = &ft_default_parser; + keydef->ftkey_nr = 0; + return ptr; +} + +/*************************************************************************** +** maria_keyseg +***************************************************************************/ + +my_bool _ma_keyseg_write(File file, const HA_KEYSEG *keyseg) +{ + uchar buff[HA_KEYSEG_SIZE]; + uchar *ptr=buff; + ulong pos; + + *ptr++= keyseg->type; + *ptr++= keyseg->language; + *ptr++= keyseg->null_bit; + *ptr++= keyseg->bit_start; + *ptr++= keyseg->bit_end; + *ptr++= keyseg->bit_length; + mi_int2store(ptr,keyseg->flag); ptr+= 2; + mi_int2store(ptr,keyseg->length); ptr+= 2; + mi_int4store(ptr,keyseg->start); ptr+= 4; + pos= keyseg->null_bit ? keyseg->null_pos : keyseg->bit_pos; + mi_int4store(ptr, pos); + ptr+=4; + + return my_write(file, buff, (size_t) (ptr-buff), MYF(MY_NABP)) != 0; +} + + +uchar *_ma_keyseg_read(uchar *ptr, HA_KEYSEG *keyseg) +{ + keyseg->type = *ptr++; + keyseg->language = *ptr++; + keyseg->null_bit = *ptr++; + keyseg->bit_start = *ptr++; + keyseg->bit_end = *ptr++; + keyseg->bit_length = *ptr++; + keyseg->flag = mi_uint2korr(ptr); ptr+= 2; + keyseg->length = mi_uint2korr(ptr); ptr+= 2; + keyseg->start = mi_uint4korr(ptr); ptr+= 4; + keyseg->null_pos = mi_uint4korr(ptr); ptr+= 4; + keyseg->charset=0; /* Will be filled in later */ + if (keyseg->null_bit) + keyseg->bit_pos= (uint16)(keyseg->null_pos + (keyseg->null_bit == 7)); + else + { + keyseg->bit_pos= (uint16)keyseg->null_pos; + keyseg->null_pos= 0; + } + return ptr; +} + +/*-------------------------------------------------------------------------- + maria_uniquedef +---------------------------------------------------------------------------*/ + +my_bool _ma_uniquedef_write(File file, MARIA_UNIQUEDEF *def) +{ + uchar buff[MARIA_UNIQUEDEF_SIZE]; + uchar *ptr=buff; + + mi_int2store(ptr,def->keysegs); ptr+=2; + *ptr++= (uchar) def->key; + *ptr++ = (uchar) def->null_are_equal; + + return my_write(file, buff, (size_t) (ptr-buff), MYF(MY_NABP)) != 0; +} + +uchar *_ma_uniquedef_read(uchar *ptr, MARIA_UNIQUEDEF *def) +{ + def->keysegs = mi_uint2korr(ptr); + def->key = ptr[2]; + def->null_are_equal=ptr[3]; + return ptr+4; /* 1 extra uchar */ +} + +/*************************************************************************** +** MARIA_COLUMNDEF +***************************************************************************/ + +my_bool _ma_columndef_write(File file, MARIA_COLUMNDEF *columndef) +{ + uchar buff[MARIA_COLUMNDEF_SIZE]; + uchar *ptr=buff; + + mi_int2store(ptr,(ulong) columndef->column_nr); ptr+= 2; + mi_int2store(ptr,(ulong) columndef->offset); ptr+= 2; + mi_int2store(ptr,columndef->type); ptr+= 2; + mi_int2store(ptr,columndef->length); ptr+= 2; + mi_int2store(ptr,columndef->fill_length); ptr+= 2; + mi_int2store(ptr,columndef->null_pos); ptr+= 2; + mi_int2store(ptr,columndef->empty_pos); ptr+= 2; + + (*ptr++)= columndef->null_bit; + (*ptr++)= columndef->empty_bit; + ptr[0]= ptr[1]= ptr[2]= ptr[3]= 0; ptr+= 4; /* For future */ + return my_write(file, buff, (size_t) (ptr-buff), MYF(MY_NABP)) != 0; +} + +uchar *_ma_columndef_read(uchar *ptr, MARIA_COLUMNDEF *columndef) +{ + columndef->column_nr= mi_uint2korr(ptr); ptr+= 2; + columndef->offset= mi_uint2korr(ptr); ptr+= 2; + columndef->type= mi_sint2korr(ptr); ptr+= 2; + columndef->length= mi_uint2korr(ptr); ptr+= 2; + columndef->fill_length= mi_uint2korr(ptr); ptr+= 2; + columndef->null_pos= mi_uint2korr(ptr); ptr+= 2; + columndef->empty_pos= mi_uint2korr(ptr); ptr+= 2; + columndef->null_bit= (uint8) *ptr++; + columndef->empty_bit= (uint8) *ptr++; + ptr+= 4; + return ptr; +} + +my_bool _ma_column_nr_write(File file, uint16 *offsets, uint columns) +{ + uchar *buff, *ptr, *end; + size_t size= columns*2; + my_bool res; + + if (!(buff= (uchar*) my_alloca(size))) + return 1; + for (ptr= buff, end= ptr + size; ptr < end ; ptr+= 2, offsets++) + int2store(ptr, *offsets); + res= my_write(file, buff, size, MYF(MY_NABP)) != 0; + my_afree(buff); + return res; +} + + +uchar *_ma_column_nr_read(uchar *ptr, uint16 *offsets, uint columns) +{ + uchar *end; + size_t size= columns*2; + for (end= ptr + size; ptr < end ; ptr+=2, offsets++) + *offsets= uint2korr(ptr); + return ptr; +} + +/** + @brief Set callbacks for data pages + + @note + We don't use pagecache_file_init here, as we want to keep the + code readable +*/ + +void _ma_set_data_pagecache_callbacks(PAGECACHE_FILE *file, + MARIA_SHARE *share) +{ + file->callback_data= (uchar*) share; + file->flush_log_callback= &maria_flush_log_for_page_none; /* Do nothing */ + + if (share->temporary) + { + file->read_callback= &maria_page_crc_check_none; + file->write_callback= &maria_page_filler_set_none; + } + else + { + file->read_callback= &maria_page_crc_check_data; + if (share->options & HA_OPTION_PAGE_CHECKSUM) + file->write_callback= &maria_page_crc_set_normal; + else + file->write_callback= &maria_page_filler_set_normal; + if (share->now_transactional) + file->flush_log_callback= maria_flush_log_for_page; + } +} + + +/** + @brief Set callbacks for index pages + + @note + We don't use pagecache_file_init here, as we want to keep the + code readable +*/ + +void _ma_set_index_pagecache_callbacks(PAGECACHE_FILE *file, + MARIA_SHARE *share) +{ + file->callback_data= (uchar*) share; + file->flush_log_callback= &maria_flush_log_for_page_none; /* Do nothing */ + file->write_fail= maria_page_write_failure; + + if (share->temporary) + { + file->read_callback= &maria_page_crc_check_none; + file->write_callback= &maria_page_filler_set_none; + } + else + { + file->read_callback= &maria_page_crc_check_index; + if (share->options & HA_OPTION_PAGE_CHECKSUM) + file->write_callback= &maria_page_crc_set_index; + else + file->write_callback= &maria_page_filler_set_normal; + + if (share->now_transactional) + file->flush_log_callback= maria_flush_log_for_page; + } +} + + +/************************************************************************** + Open data file + We can't use dup() here as the data file descriptors need to have different + active seek-positions. + + The argument file_to_dup is here for the future if there would on some OS + exist a dup()-like call that would give us two different file descriptors. +*************************************************************************/ + +int _ma_open_datafile(MARIA_HA *info, MARIA_SHARE *share, const char *org_name, + File file_to_dup __attribute__((unused))) +{ + char *data_name= share->data_file_name.str; + char real_data_name[FN_REFLEN]; + + if (org_name) + { + fn_format(real_data_name, org_name, "", MARIA_NAME_DEXT, 4); + if (my_is_symlink(real_data_name)) + { + if (my_realpath(real_data_name, real_data_name, MYF(0)) || + (*maria_test_invalid_symlink)(real_data_name)) + { + my_errno= HA_WRONG_CREATE_OPTION; + return 1; + } + data_name= real_data_name; + } + } + + info->dfile.file= share->bitmap.file.file= + my_open(share->data_file_name.str, share->mode | O_SHARE, + MYF(MY_WME)); + return info->dfile.file >= 0 ? 0 : 1; +} + + +int _ma_open_keyfile(MARIA_SHARE *share) +{ + /* + Modifications to share->kfile should be under intern_lock to protect + against a concurrent checkpoint. + */ + pthread_mutex_lock(&share->intern_lock); + share->kfile.file= my_open(share->unique_file_name.str, + share->mode | O_SHARE, + MYF(MY_WME)); + pthread_mutex_unlock(&share->intern_lock); + return (share->kfile.file < 0); +} + + +/* + Disable all indexes. + + SYNOPSIS + maria_disable_indexes() + info A pointer to the MARIA storage engine MARIA_HA struct. + + DESCRIPTION + Disable all indexes. + + RETURN + 0 ok +*/ + +int maria_disable_indexes(MARIA_HA *info) +{ + MARIA_SHARE *share= info->s; + + maria_clear_all_keys_active(share->state.key_map); + return 0; +} + + +/* + Enable all indexes + + SYNOPSIS + maria_enable_indexes() + info A pointer to the MARIA storage engine MARIA_HA struct. + + DESCRIPTION + Enable all indexes. The indexes might have been disabled + by maria_disable_index() before. + The function works only if both data and indexes are empty, + otherwise a repair is required. + To be sure, call handler::delete_all_rows() before. + + RETURN + 0 ok + HA_ERR_CRASHED data or index is non-empty. +*/ + +int maria_enable_indexes(MARIA_HA *info) +{ + int error= 0; + MARIA_SHARE *share= info->s; + DBUG_ENTER("maria_enable_indexes"); + + if ((share->state.state.data_file_length != + (share->data_file_type == BLOCK_RECORD ? share->block_size : 0)) || + (share->state.state.key_file_length != share->base.keystart)) + { + DBUG_PRINT("error", ("data_file_length: %lu key_file_length: %lu", + (ulong) share->state.state.data_file_length, + (ulong) share->state.state.key_file_length)); + maria_print_error(info->s, HA_ERR_CRASHED); + error= HA_ERR_CRASHED; + } + else + maria_set_all_keys_active(share->state.key_map, share->base.keys); + DBUG_RETURN(error); +} + + +/* + Test if indexes are disabled. + + SYNOPSIS + maria_indexes_are_disabled() + info A pointer to the MARIA storage engine MARIA_HA struct. + + DESCRIPTION + Test if indexes are disabled. + + RETURN + 0 indexes are not disabled + 1 all indexes are disabled + 2 non-unique indexes are disabled +*/ + +int maria_indexes_are_disabled(MARIA_HA *info) +{ + MARIA_SHARE *share= info->s; + + /* + No keys or all are enabled. keys is the number of keys. Left shifted + gives us only one bit set. When decreased by one, gives us all all bits + up to this one set and it gets unset. + */ + if (!share->base.keys || + (maria_is_all_keys_active(share->state.key_map, share->base.keys))) + return 0; + + /* All are disabled */ + if (maria_is_any_key_active(share->state.key_map)) + return 1; + + /* + We have keys. Some enabled, some disabled. + Don't check for any non-unique disabled but return directly 2 + */ + return 2; +} + + +static my_bool maria_scan_init_dummy(MARIA_HA *info __attribute__((unused))) +{ + return 0; +} + +static void maria_scan_end_dummy(MARIA_HA *info __attribute__((unused))) +{ +} + +static my_bool maria_once_init_dummy(MARIA_SHARE *share + __attribute__((unused)), + File dfile __attribute__((unused))) +{ + return 0; +} + +static my_bool maria_once_end_dummy(MARIA_SHARE *share __attribute__((unused))) +{ + return 0; +} diff --git a/storage/maria/ma_packrec.c b/storage/maria/ma_packrec.c new file mode 100644 index 00000000000..4df00d9bb88 --- /dev/null +++ b/storage/maria/ma_packrec.c @@ -0,0 +1,1723 @@ +/* Copyright (C) 2006 MySQL AB & MySQL Finland AB & TCX DataKonsult 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 */ + + /* Functions to compressed records */ + +#include "maria_def.h" + +#define IS_CHAR ((uint) 32768) /* Bit if char (not offset) in tree */ + +/* Some definitions to keep in sync with maria_pack.c */ +#define HEAD_LENGTH 32 /* Length of fixed header */ + +#if INT_MAX > 32767 +#define BITS_SAVED 32 +#define MAX_QUICK_TABLE_BITS 9 /* Because we may shift in 24 bits */ +#else +#define BITS_SAVED 16 +#define MAX_QUICK_TABLE_BITS 6 +#endif + +#define get_bit(BU) ((BU)->bits ? \ + (BU)->current_byte & ((maria_bit_type) 1 << --(BU)->bits) :\ + (fill_buffer(BU), (BU)->bits= BITS_SAVED-1,\ + (BU)->current_byte & ((maria_bit_type) 1 << (BITS_SAVED-1)))) +#define skip_to_next_byte(BU) ((BU)->bits&=~7) +#define get_bits(BU,count) (((BU)->bits >= count) ? (((BU)->current_byte >> ((BU)->bits-=count)) & mask[count]) : fill_and_get_bits(BU,count)) + +#define decode_bytes_test_bit(bit) \ + if (low_byte & (1 << (7-bit))) \ + pos++; \ + if (*pos & IS_CHAR) \ + { bits-=(bit+1); break; } \ + pos+= *pos + +/* + Size in uint16 of a Huffman tree for uchar compression of 256 uchar values +*/ +#define OFFSET_TABLE_SIZE 512 + +static my_bool _ma_read_pack_info(MARIA_SHARE *share, File file, + pbool fix_keys); +static uint read_huff_table(MARIA_BIT_BUFF *bit_buff, + MARIA_DECODE_TREE *decode_tree, + uint16 **decode_table,uchar **intervall_buff, + uint16 *tmp_buff); +static void make_quick_table(uint16 *to_table,uint16 *decode_table, + uint *next_free,uint value,uint bits, + uint max_bits); +static void fill_quick_table(uint16 *table,uint bits, uint max_bits, + uint value); +static uint copy_decode_table(uint16 *to_pos,uint offset, + uint16 *decode_table); +static uint find_longest_bitstream(uint16 *table, uint16 *end); +static void (*get_unpack_function(MARIA_COLUMNDEF *rec))(MARIA_COLUMNDEF *field, + MARIA_BIT_BUFF *buff, + uchar *to, + uchar *end); +static void uf_zerofill_skip_zero(MARIA_COLUMNDEF *rec, + MARIA_BIT_BUFF *bit_buff, + uchar *to,uchar *end); +static void uf_skip_zero(MARIA_COLUMNDEF *rec,MARIA_BIT_BUFF *bit_buff, + uchar *to,uchar *end); +static void uf_space_normal(MARIA_COLUMNDEF *rec,MARIA_BIT_BUFF *bit_buff, + uchar *to,uchar *end); +static void uf_space_endspace_selected(MARIA_COLUMNDEF *rec, + MARIA_BIT_BUFF *bit_buff, + uchar *to, uchar *end); +static void uf_endspace_selected(MARIA_COLUMNDEF *rec,MARIA_BIT_BUFF *bit_buff, + uchar *to,uchar *end); +static void uf_space_endspace(MARIA_COLUMNDEF *rec,MARIA_BIT_BUFF *bit_buff, + uchar *to,uchar *end); +static void uf_endspace(MARIA_COLUMNDEF *rec,MARIA_BIT_BUFF *bit_buff, + uchar *to,uchar *end); +static void uf_space_prespace_selected(MARIA_COLUMNDEF *rec, + MARIA_BIT_BUFF *bit_buff, + uchar *to, uchar *end); +static void uf_prespace_selected(MARIA_COLUMNDEF *rec,MARIA_BIT_BUFF *bit_buff, + uchar *to,uchar *end); +static void uf_space_prespace(MARIA_COLUMNDEF *rec,MARIA_BIT_BUFF *bit_buff, + uchar *to,uchar *end); +static void uf_prespace(MARIA_COLUMNDEF *rec,MARIA_BIT_BUFF *bit_buff, + uchar *to,uchar *end); +static void uf_zerofill_normal(MARIA_COLUMNDEF *rec,MARIA_BIT_BUFF *bit_buff, + uchar *to,uchar *end); +static void uf_constant(MARIA_COLUMNDEF *rec,MARIA_BIT_BUFF *bit_buff, + uchar *to,uchar *end); +static void uf_intervall(MARIA_COLUMNDEF *rec,MARIA_BIT_BUFF *bit_buff, + uchar *to,uchar *end); +static void uf_zero(MARIA_COLUMNDEF *rec,MARIA_BIT_BUFF *bit_buff, + uchar *to,uchar *end); +static void uf_blob(MARIA_COLUMNDEF *rec, MARIA_BIT_BUFF *bit_buff, + uchar *to, uchar *end); +static void uf_varchar1(MARIA_COLUMNDEF *rec, MARIA_BIT_BUFF *bit_buff, + uchar *to, uchar *end); +static void uf_varchar2(MARIA_COLUMNDEF *rec, MARIA_BIT_BUFF *bit_buff, + uchar *to, uchar *end); +static void decode_bytes(MARIA_COLUMNDEF *rec,MARIA_BIT_BUFF *bit_buff, + uchar *to,uchar *end); +static uint decode_pos(MARIA_BIT_BUFF *bit_buff, + MARIA_DECODE_TREE *decode_tree); +static void init_bit_buffer(MARIA_BIT_BUFF *bit_buff,uchar *buffer, + uint length); +static uint fill_and_get_bits(MARIA_BIT_BUFF *bit_buff,uint count); +static void fill_buffer(MARIA_BIT_BUFF *bit_buff); +static uint max_bit(uint value); +static uint read_pack_length(uint version, const uchar *buf, ulong *length); +#ifdef HAVE_MMAP +static uchar *_ma_mempack_get_block_info(MARIA_HA *maria, + MARIA_BIT_BUFF *bit_buff, + MARIA_BLOCK_INFO *info, + uchar **rec_buff_p, + size_t *rec_buff_size_p, + uchar *header); +#endif + +static maria_bit_type mask[]= +{ + 0x00000000, + 0x00000001, 0x00000003, 0x00000007, 0x0000000f, + 0x0000001f, 0x0000003f, 0x0000007f, 0x000000ff, + 0x000001ff, 0x000003ff, 0x000007ff, 0x00000fff, + 0x00001fff, 0x00003fff, 0x00007fff, 0x0000ffff, +#if BITS_SAVED > 16 + 0x0001ffff, 0x0003ffff, 0x0007ffff, 0x000fffff, + 0x001fffff, 0x003fffff, 0x007fffff, 0x00ffffff, + 0x01ffffff, 0x03ffffff, 0x07ffffff, 0x0fffffff, + 0x1fffffff, 0x3fffffff, 0x7fffffff, 0xffffffff, +#endif +}; + + +my_bool _ma_once_init_pack_row(MARIA_SHARE *share, File dfile) +{ + share->options|= HA_OPTION_READ_ONLY_DATA; + return (_ma_read_pack_info(share, dfile, + (pbool) + test(!(share->options & + (HA_OPTION_PACK_RECORD | + HA_OPTION_TEMP_COMPRESS_RECORD))))); +} + + +my_bool _ma_once_end_pack_row(MARIA_SHARE *share) +{ + if (share->decode_trees) + { + my_free(share->decode_trees,MYF(0)); + my_free(share->decode_tables,MYF(0)); + } + return 0; +} + + +/* Read all packed info, allocate memory and fix field structs */ + +static my_bool _ma_read_pack_info(MARIA_SHARE *share, File file, + pbool fix_keys) +{ + int diff_length; + uint i,trees,huff_tree_bits,rec_reflength,length; + uint16 *decode_table,*tmp_buff; + ulong elements,intervall_length; + uchar *disk_cache; + uchar *intervall_buff; + uchar header[HEAD_LENGTH]; + MARIA_BIT_BUFF bit_buff; + DBUG_ENTER("_ma_read_pack_info"); + + if (maria_quick_table_bits < 4) + maria_quick_table_bits=4; + else if (maria_quick_table_bits > MAX_QUICK_TABLE_BITS) + maria_quick_table_bits=MAX_QUICK_TABLE_BITS; + + my_errno=0; + if (my_read(file, header, sizeof(header), MYF(MY_NABP))) + { + if (!my_errno) + my_errno=HA_ERR_END_OF_FILE; + goto err0; + } + /* Only the first three bytes of magic number are independent of version. */ + if (memcmp(header, maria_pack_file_magic, 3)) + { + my_errno=HA_ERR_WRONG_IN_RECORD; + goto err0; + } + share->pack.version= header[3]; /* fourth uchar of magic number */ + share->pack.header_length= uint4korr(header+4); + share->min_pack_length=(uint) uint4korr(header+8); + share->max_pack_length=(uint) uint4korr(header+12); + set_if_bigger(share->base.default_rec_buff_size, + share->max_pack_length + 7); + elements=uint4korr(header+16); + intervall_length=uint4korr(header+20); + trees=uint2korr(header+24); + share->pack.ref_length=header[26]; + rec_reflength=header[27]; + diff_length=(int) rec_reflength - (int) share->base.rec_reflength; + if (fix_keys) + share->rec_reflength=rec_reflength; + DBUG_PRINT("info", ("fixed header length: %u", HEAD_LENGTH)); + DBUG_PRINT("info", ("total header length: %lu", share->pack.header_length)); + DBUG_PRINT("info", ("pack file version: %u", share->pack.version)); + DBUG_PRINT("info", ("min pack length: %lu", share->min_pack_length)); + DBUG_PRINT("info", ("max pack length: %lu", share->max_pack_length)); + DBUG_PRINT("info", ("elements of all trees: %lu", elements)); + DBUG_PRINT("info", ("distinct values bytes: %lu", intervall_length)); + DBUG_PRINT("info", ("number of code trees: %u", trees)); + DBUG_PRINT("info", ("bytes for record lgt: %u", share->pack.ref_length)); + DBUG_PRINT("info", ("record pointer length: %u", rec_reflength)); + + + /* + Memory segment #1: + - Decode tree heads + - Distinct column values + */ + if (!(share->decode_trees=(MARIA_DECODE_TREE*) + my_malloc((uint) (trees*sizeof(MARIA_DECODE_TREE)+ + intervall_length*sizeof(uchar)), + MYF(MY_WME)))) + goto err0; + intervall_buff=(uchar*) (share->decode_trees+trees); + + /* + Memory segment #2: + - Decode tables + - Quick decode tables + - Temporary decode table + - Compressed data file header cache + This segment will be reallocated after construction of the tables. + */ + length=(uint) (elements*2+trees*(1 << maria_quick_table_bits)); + if (!(share->decode_tables=(uint16*) + my_malloc((length+OFFSET_TABLE_SIZE)*sizeof(uint16)+ + (uint) (share->pack.header_length - sizeof(header)) + + share->base.extra_rec_buff_size, + MYF(MY_WME | MY_ZEROFILL)))) + goto err1; + tmp_buff=share->decode_tables+length; + disk_cache=(uchar*) (tmp_buff+OFFSET_TABLE_SIZE); + + if (my_read(file,disk_cache, + (uint) (share->pack.header_length-sizeof(header)), + MYF(MY_NABP))) + goto err2; +#ifdef HAVE_valgrind + /* Zero bytes accessed by fill_buffer */ + bzero(disk_cache + (share->pack.header_length-sizeof(header)), + share->base.extra_rec_buff_size); +#endif + + huff_tree_bits=max_bit(trees ? trees-1 : 0); + init_bit_buffer(&bit_buff, disk_cache, + (uint) (share->pack.header_length-sizeof(header))); + /* Read new info for each field */ + for (i=0 ; i < share->base.fields ; i++) + { + share->columndef[i].base_type=(enum en_fieldtype) get_bits(&bit_buff,5); + share->columndef[i].pack_type=(uint) get_bits(&bit_buff,6); + share->columndef[i].space_length_bits=get_bits(&bit_buff,5); + share->columndef[i].huff_tree=share->decode_trees+(uint) get_bits(&bit_buff, + huff_tree_bits); + share->columndef[i].unpack= get_unpack_function(share->columndef + i); + DBUG_PRINT("info", ("col: %2u type: %2u pack: %u slbits: %2u", + i, share->columndef[i].base_type, + share->columndef[i].pack_type, + share->columndef[i].space_length_bits)); + } + skip_to_next_byte(&bit_buff); + /* + Construct the decoding tables from the file header. Keep track of + the used memory. + */ + decode_table=share->decode_tables; + for (i=0 ; i < trees ; i++) + if (read_huff_table(&bit_buff,share->decode_trees+i,&decode_table, + &intervall_buff,tmp_buff)) + goto err3; + /* Reallocate the decoding tables to the used size. */ + decode_table=(uint16*) + my_realloc((uchar*) share->decode_tables, + (uint) ((uchar*) decode_table - (uchar*) share->decode_tables), + MYF(MY_HOLD_ON_ERROR)); + /* Fix the table addresses in the tree heads. */ + { + my_ptrdiff_t diff= PTR_BYTE_DIFF(decode_table,share->decode_tables); + share->decode_tables=decode_table; + for (i=0 ; i < trees ; i++) + share->decode_trees[i].table=ADD_TO_PTR(share->decode_trees[i].table, + diff, uint16*); + } + + /* Fix record-ref-length for keys */ + if (fix_keys) + { + for (i=0 ; i < share->base.keys ; i++) + { + MARIA_KEYDEF *keyinfo= &share->keyinfo[i]; + keyinfo->keylength+= (uint16) diff_length; + keyinfo->minlength+= (uint16) diff_length; + keyinfo->maxlength+= (uint16) diff_length; + keyinfo->seg[keyinfo->flag & HA_FULLTEXT ? + FT_SEGS : keyinfo->keysegs].length= (uint16) rec_reflength; + } + if (share->ft2_keyinfo.seg) + { + MARIA_KEYDEF *ft2_keyinfo= &share->ft2_keyinfo; + ft2_keyinfo->keylength+= (uint16) diff_length; + ft2_keyinfo->minlength+= (uint16) diff_length; + ft2_keyinfo->maxlength+= (uint16) diff_length; + } + } + + if (bit_buff.error || bit_buff.pos < bit_buff.end) + goto err3; + + DBUG_RETURN(0); + +err3: + my_errno=HA_ERR_WRONG_IN_RECORD; +err2: + my_free(share->decode_tables, MYF(0)); +err1: + my_free(share->decode_trees, MYF(0)); +err0: + DBUG_RETURN(1); +} + + +/* + Read a huff-code-table from datafile. + + SYNOPSIS + read_huff_table() + bit_buff Bit buffer pointing at start of the + decoding table in the file header cache. + decode_tree Pointer to the decode tree head. + decode_table IN/OUT Address of a pointer to the next free space. + intervall_buff IN/OUT Address of a pointer to the next unused values. + tmp_buff Buffer for temporary extraction of a full + decoding table as read from bit_buff. + + RETURN + 0 OK. + 1 Error. +*/ +static uint read_huff_table(MARIA_BIT_BUFF *bit_buff, + MARIA_DECODE_TREE *decode_tree, + uint16 **decode_table, uchar **intervall_buff, + uint16 *tmp_buff) +{ + uint min_chr,elements,char_bits,offset_bits,size,intervall_length,table_bits, + next_free_offset; + uint16 *ptr,*end; + DBUG_ENTER("read_huff_table"); + + if (!get_bits(bit_buff,1)) + { + /* Byte value compression. */ + min_chr=get_bits(bit_buff,8); + elements=get_bits(bit_buff,9); + char_bits=get_bits(bit_buff,5); + offset_bits=get_bits(bit_buff,5); + intervall_length=0; + ptr=tmp_buff; + ptr=tmp_buff; + DBUG_PRINT("info", ("byte value compression")); + DBUG_PRINT("info", ("minimum uchar value: %u", min_chr)); + DBUG_PRINT("info", ("number of tree nodes: %u", elements)); + DBUG_PRINT("info", ("bits for values: %u", char_bits)); + DBUG_PRINT("info", ("bits for tree offsets: %u", offset_bits)); + if (elements > 256) + { + DBUG_PRINT("error", ("ERROR: illegal number of tree elements: %u", + elements)); + DBUG_RETURN(1); + } + } + else + { + /* Distinct column value compression. */ + min_chr=0; + elements=get_bits(bit_buff,15); + intervall_length=get_bits(bit_buff,16); + char_bits=get_bits(bit_buff,5); + offset_bits=get_bits(bit_buff,5); + decode_tree->quick_table_bits=0; + ptr= *decode_table; + DBUG_PRINT("info", ("distinct column value compression")); + DBUG_PRINT("info", ("number of tree nodes: %u", elements)); + DBUG_PRINT("info", ("value buffer length: %u", intervall_length)); + DBUG_PRINT("info", ("bits for value index: %u", char_bits)); + DBUG_PRINT("info", ("bits for tree offsets: %u", offset_bits)); + } + size=elements*2-2; + DBUG_PRINT("info", ("tree size in uint16: %u", size)); + DBUG_PRINT("info", ("tree size in bytes: %u", + size * (uint) sizeof(uint16))); + + for (end=ptr+size ; ptr < end ; ptr++) + { + if (get_bit(bit_buff)) + { + *ptr= (uint16) get_bits(bit_buff,offset_bits); + if ((ptr + *ptr >= end) || !*ptr) + { + DBUG_PRINT("error", ("ERROR: illegal pointer in decode tree")); + DBUG_RETURN(1); + } + } + else + *ptr= (uint16) (IS_CHAR + (get_bits(bit_buff,char_bits) + min_chr)); + } + skip_to_next_byte(bit_buff); + + decode_tree->table= *decode_table; + decode_tree->intervalls= *intervall_buff; + if (! intervall_length) + { + /* Byte value compression. ptr started from tmp_buff. */ + /* Find longest Huffman code from begin to end of tree in bits. */ + table_bits= find_longest_bitstream(tmp_buff, ptr); + if (table_bits >= OFFSET_TABLE_SIZE) + DBUG_RETURN(1); + if (table_bits > maria_quick_table_bits) + table_bits=maria_quick_table_bits; + DBUG_PRINT("info", ("table bits: %u", table_bits)); + + next_free_offset= (1 << table_bits); + make_quick_table(*decode_table,tmp_buff,&next_free_offset,0,table_bits, + table_bits); + (*decode_table)+= next_free_offset; + decode_tree->quick_table_bits=table_bits; + } + else + { + /* Distinct column value compression. ptr started from *decode_table */ + (*decode_table)=end; + /* + get_bits() moves some bytes to a cache buffer in advance. May need + to step back. + */ + bit_buff->pos-= bit_buff->bits/8; + /* Copy the distinct column values from the buffer. */ + memcpy(*intervall_buff,bit_buff->pos,(size_t) intervall_length); + (*intervall_buff)+=intervall_length; + bit_buff->pos+=intervall_length; + bit_buff->bits=0; + } + DBUG_RETURN(0); +} + + +/* + Make a quick_table for faster decoding. + + SYNOPSIS + make_quick_table() + to_table Target quick_table and remaining decode table. + decode_table Source Huffman (sub-)tree within tmp_buff. + next_free_offset IN/OUT Next free offset from to_table. + Starts behind quick_table on the top-level. + value Huffman bits found so far. + bits Remaining bits to be collected. + max_bits Total number of bits to collect (table_bits). + + DESCRIPTION + + The quick table is an array of 16-bit values. There exists one value + for each possible code representable by max_bits (table_bits) bits. + In most cases table_bits is 9. So there are 512 16-bit values. + + If the high-order bit (16) is set (IS_CHAR) then the array slot for + this value is a valid Huffman code for a resulting uchar value. + + The low-order 8 bits (1..8) are the resulting uchar value. + + Bits 9..14 are the length of the Huffman code for this uchar value. + This means so many bits from the input stream were needed to + represent this uchar value. The remaining bits belong to later + Huffman codes. This also means that for every Huffman code shorter + than table_bits there are multiple entires in the array, which + differ just in the unused bits. + + If the high-order bit (16) is clear (0) then the remaining bits are + the position of the remaining Huffman decode tree segment behind the + quick table. + + RETURN + void +*/ + +static void make_quick_table(uint16 *to_table, uint16 *decode_table, + uint *next_free_offset, uint value, uint bits, + uint max_bits) +{ + DBUG_ENTER("make_quick_table"); + + /* + When down the table to the requested maximum, copy the rest of the + Huffman table. + */ + if (!bits--) + { + /* + Remaining left Huffman tree segment starts behind quick table. + Remaining right Huffman tree segment starts behind left segment. + */ + to_table[value]= (uint16) *next_free_offset; + /* + Re-construct the remaining Huffman tree segment at + next_free_offset in to_table. + */ + *next_free_offset=copy_decode_table(to_table, *next_free_offset, + decode_table); + DBUG_VOID_RETURN; + } + + /* Descent on the left side. Left side bits are clear (0). */ + if (!(*decode_table & IS_CHAR)) + { + /* Not a leaf. Follow the pointer. */ + make_quick_table(to_table,decode_table+ *decode_table, + next_free_offset,value,bits,max_bits); + } + else + { + /* + A leaf. A Huffman code is complete. Fill the quick_table + array for all possible bit strings starting with this Huffman + code. + */ + fill_quick_table(to_table+value,bits,max_bits,(uint) *decode_table); + } + + /* Descent on the right side. Right side bits are set (1). */ + decode_table++; + value|= (1 << bits); + if (!(*decode_table & IS_CHAR)) + { + /* Not a leaf. Follow the pointer. */ + make_quick_table(to_table,decode_table+ *decode_table, + next_free_offset,value,bits,max_bits); + } + else + { + /* + A leaf. A Huffman code is complete. Fill the quick_table + array for all possible bit strings starting with this Huffman + code. + */ + fill_quick_table(to_table+value,bits,max_bits,(uint) *decode_table); + } + + DBUG_VOID_RETURN; +} + + +/* + Fill quick_table for all possible values starting with this Huffman code. + + SYNOPSIS + fill_quick_table() + table Target quick_table position. + bits Unused bits from max_bits. + max_bits Total number of bits to collect (table_bits). + value The uchar encoded by the found Huffman code. + + DESCRIPTION + + Fill the segment (all slots) of the quick_table array with the + resulting value for the found Huffman code. There are as many slots + as there are combinations representable by the unused bits. + + In most cases we use 9 table bits. Assume a 3-bit Huffman code. Then + there are 6 unused bits. Hence we fill 2**6 = 64 slots with the + value. + + RETURN + void +*/ + +static void fill_quick_table(uint16 *table, uint bits, uint max_bits, + uint value) +{ + uint16 *end; + DBUG_ENTER("fill_quick_table"); + + /* + Bits 1..8 of value represent the decoded uchar value. + Bits 9..14 become the length of the Huffman code for this uchar value. + Bit 16 flags a valid code (IS_CHAR). + */ + value|= (max_bits - bits) << 8 | IS_CHAR; + + for (end= table + ((my_ptrdiff_t) 1 << bits); table < end; table++) + { + *table= (uint16) value; + } + DBUG_VOID_RETURN; +} + + +/* + Reconstruct a decode subtree at the target position. + + SYNOPSIS + copy_decode_table() + to_pos Target quick_table and remaining decode table. + offset Next free offset from to_pos. + decode_table Source Huffman subtree within tmp_buff. + + NOTE + Pointers in the decode tree are relative to the pointers position. + + RETURN + next free offset from to_pos. +*/ + +static uint copy_decode_table(uint16 *to_pos, uint offset, + uint16 *decode_table) +{ + uint prev_offset= offset; + DBUG_ENTER("copy_decode_table"); + + /* Descent on the left side. */ + if (!(*decode_table & IS_CHAR)) + { + /* Set a pointer to the next target node. */ + to_pos[offset]=2; + /* Copy the left hand subtree there. */ + offset=copy_decode_table(to_pos,offset+2,decode_table+ *decode_table); + } + else + { + /* Copy the uchar value. */ + to_pos[offset]= *decode_table; + /* Step behind this node. */ + offset+=2; + } + + /* Descent on the right side. */ + decode_table++; + if (!(*decode_table & IS_CHAR)) + { + /* Set a pointer to the next free target node. */ + to_pos[prev_offset+1]=(uint16) (offset-prev_offset-1); + /* Copy the right hand subtree to the entry of that node. */ + offset=copy_decode_table(to_pos,offset,decode_table+ *decode_table); + } + else + { + /* Copy the uchar value. */ + to_pos[prev_offset+1]= *decode_table; + } + DBUG_RETURN(offset); +} + + +/* + Find the length of the longest Huffman code in this table in bits. + + SYNOPSIS + find_longest_bitstream() + table Code (sub-)table start. + end End of code table. + + IMPLEMENTATION + + Recursively follow the branch(es) of the code pair on every level of + the tree until two uchar values (and no branch) are found. Add one to + each level when returning back from each recursion stage. + + 'end' is used for error checking only. A clean tree terminates + before reaching 'end'. Hence the exact value of 'end' is not too + important. However having it higher than necessary could lead to + misbehaviour should 'next' jump into the dirty area. + + RETURN + length Length of longest Huffman code in bits. + >= OFFSET_TABLE_SIZE Error, broken tree. It does not end before 'end'. +*/ + +static uint find_longest_bitstream(uint16 *table, uint16 *end) +{ + uint length=1; + uint length2; + if (!(*table & IS_CHAR)) + { + uint16 *next= table + *table; + if (next > end || next == table) + { + DBUG_PRINT("error", ("ERROR: illegal pointer in decode tree")); + return OFFSET_TABLE_SIZE; + } + length=find_longest_bitstream(next, end)+1; + } + table++; + if (!(*table & IS_CHAR)) + { + uint16 *next= table + *table; + if (next > end || next == table) + { + DBUG_PRINT("error", ("ERROR: illegal pointer in decode tree")); + return OFFSET_TABLE_SIZE; + } + length2= find_longest_bitstream(next, end) + 1; + length=max(length,length2); + } + return length; +} + + +/* + Read record from datafile. + + SYNOPSIS + _ma_read_pack_record() + info A pointer to MARIA_HA. + filepos File offset of the record. + buf RETURN The buffer to receive the record. + + RETURN + 0 On success + # Error number +*/ + +int _ma_read_pack_record(MARIA_HA *info, uchar *buf, MARIA_RECORD_POS filepos) +{ + MARIA_BLOCK_INFO block_info; + File file; + DBUG_ENTER("maria_read_pack_record"); + + if (filepos == HA_OFFSET_ERROR) + DBUG_RETURN(my_errno); /* _search() didn't find record */ + + file= info->dfile.file; + if (_ma_pack_get_block_info(info, &info->bit_buff, &block_info, + &info->rec_buff, &info->rec_buff_size, file, + filepos)) + goto err; + if (my_read(file, info->rec_buff + block_info.offset , + block_info.rec_len - block_info.offset, MYF(MY_NABP))) + goto panic; + info->update|= HA_STATE_AKTIV; + DBUG_RETURN(_ma_pack_rec_unpack(info,&info->bit_buff, buf, + info->rec_buff, block_info.rec_len)); +panic: + my_errno=HA_ERR_WRONG_IN_RECORD; +err: + DBUG_RETURN(my_errno); +} + + + +int _ma_pack_rec_unpack(register MARIA_HA *info, MARIA_BIT_BUFF *bit_buff, + register uchar *to, uchar *from, ulong reclength) +{ + uchar *end_field; + reg3 MARIA_COLUMNDEF *end; + MARIA_COLUMNDEF *current_field; + MARIA_SHARE *share= info->s; + DBUG_ENTER("_ma_pack_rec_unpack"); + + if (info->s->base.null_bytes) + { + memcpy(to, from, info->s->base.null_bytes); + to+= info->s->base.null_bytes; + from+= info->s->base.null_bytes; + reclength-= info->s->base.null_bytes; + } + init_bit_buffer(bit_buff, from, reclength); + for (current_field=share->columndef, end=current_field+share->base.fields ; + current_field < end ; + current_field++,to=end_field) + { + end_field=to+current_field->length; + (*current_field->unpack)(current_field, bit_buff, to, end_field); + } + if (!bit_buff->error && + bit_buff->pos - bit_buff->bits / 8 == bit_buff->end) + DBUG_RETURN(0); + info->update&= ~HA_STATE_AKTIV; + DBUG_RETURN(my_errno=HA_ERR_WRONG_IN_RECORD); +} /* _ma_pack_rec_unpack */ + + + /* Return function to unpack field */ + +static void (*get_unpack_function(MARIA_COLUMNDEF *rec)) + (MARIA_COLUMNDEF *, MARIA_BIT_BUFF *, uchar *, uchar *) +{ + switch (rec->base_type) { + case FIELD_SKIP_ZERO: + if (rec->pack_type & PACK_TYPE_ZERO_FILL) + return &uf_zerofill_skip_zero; + return &uf_skip_zero; + case FIELD_NORMAL: + if (rec->pack_type & PACK_TYPE_SPACE_FIELDS) + return &uf_space_normal; + if (rec->pack_type & PACK_TYPE_ZERO_FILL) + return &uf_zerofill_normal; + return &decode_bytes; + case FIELD_SKIP_ENDSPACE: + if (rec->pack_type & PACK_TYPE_SPACE_FIELDS) + { + if (rec->pack_type & PACK_TYPE_SELECTED) + return &uf_space_endspace_selected; + return &uf_space_endspace; + } + if (rec->pack_type & PACK_TYPE_SELECTED) + return &uf_endspace_selected; + return &uf_endspace; + case FIELD_SKIP_PRESPACE: + if (rec->pack_type & PACK_TYPE_SPACE_FIELDS) + { + if (rec->pack_type & PACK_TYPE_SELECTED) + return &uf_space_prespace_selected; + return &uf_space_prespace; + } + if (rec->pack_type & PACK_TYPE_SELECTED) + return &uf_prespace_selected; + return &uf_prespace; + case FIELD_CONSTANT: + return &uf_constant; + case FIELD_INTERVALL: + return &uf_intervall; + case FIELD_ZERO: + case FIELD_CHECK: + return &uf_zero; + case FIELD_BLOB: + return &uf_blob; + case FIELD_VARCHAR: + if (rec->length <= 256) /* 255 + 1 uchar length */ + return &uf_varchar1; + return &uf_varchar2; + case FIELD_LAST: + default: + return 0; /* This should never happend */ + } +} + + /* The different functions to unpack a field */ + +static void uf_zerofill_skip_zero(MARIA_COLUMNDEF *rec, + MARIA_BIT_BUFF *bit_buff, + uchar *to, uchar *end) +{ + if (get_bit(bit_buff)) + bzero((char*) to,(uint) (end-to)); + else + { + end-=rec->space_length_bits; + decode_bytes(rec,bit_buff,to,end); + bzero((char*) end,rec->space_length_bits); + } +} + +static void uf_skip_zero(MARIA_COLUMNDEF *rec, MARIA_BIT_BUFF *bit_buff, + uchar *to, uchar *end) +{ + if (get_bit(bit_buff)) + bzero((char*) to,(uint) (end-to)); + else + decode_bytes(rec,bit_buff,to,end); +} + +static void uf_space_normal(MARIA_COLUMNDEF *rec, MARIA_BIT_BUFF *bit_buff, + uchar *to, uchar *end) +{ + if (get_bit(bit_buff)) + bfill(to, (end-to), ' '); + else + decode_bytes(rec,bit_buff,to,end); +} + +static void uf_space_endspace_selected(MARIA_COLUMNDEF *rec, + MARIA_BIT_BUFF *bit_buff, + uchar *to, uchar *end) +{ + uint spaces; + if (get_bit(bit_buff)) + bfill(to, (end-to), ' '); + else + { + if (get_bit(bit_buff)) + { + if ((spaces=get_bits(bit_buff,rec->space_length_bits))+to > end) + { + bit_buff->error=1; + return; + } + if (to+spaces != end) + decode_bytes(rec,bit_buff,to,end-spaces); + bfill(end - spaces, spaces, ' '); + } + else + decode_bytes(rec,bit_buff,to,end); + } +} + +static void uf_endspace_selected(MARIA_COLUMNDEF *rec, + MARIA_BIT_BUFF *bit_buff, + uchar *to, uchar *end) +{ + uint spaces; + if (get_bit(bit_buff)) + { + if ((spaces=get_bits(bit_buff,rec->space_length_bits))+to > end) + { + bit_buff->error=1; + return; + } + if (to+spaces != end) + decode_bytes(rec,bit_buff,to,end-spaces); + bfill(end - spaces, spaces, ' '); + } + else + decode_bytes(rec,bit_buff,to,end); +} + +static void uf_space_endspace(MARIA_COLUMNDEF *rec, MARIA_BIT_BUFF *bit_buff, + uchar *to, uchar *end) +{ + uint spaces; + if (get_bit(bit_buff)) + bfill(to, (end-to), ' '); + else + { + if ((spaces=get_bits(bit_buff,rec->space_length_bits))+to > end) + { + bit_buff->error=1; + return; + } + if (to+spaces != end) + decode_bytes(rec,bit_buff,to,end-spaces); + bfill(end - spaces, spaces, ' '); + } +} + +static void uf_endspace(MARIA_COLUMNDEF *rec, MARIA_BIT_BUFF *bit_buff, + uchar *to, uchar *end) +{ + uint spaces; + if ((spaces=get_bits(bit_buff,rec->space_length_bits))+to > end) + { + bit_buff->error=1; + return; + } + if (to+spaces != end) + decode_bytes(rec,bit_buff,to,end-spaces); + bfill(end - spaces, spaces, ' '); +} + +static void uf_space_prespace_selected(MARIA_COLUMNDEF *rec, + MARIA_BIT_BUFF *bit_buff, + uchar *to, uchar *end) +{ + uint spaces; + if (get_bit(bit_buff)) + bfill(to, (end-to), ' '); + else + { + if (get_bit(bit_buff)) + { + if ((spaces=get_bits(bit_buff,rec->space_length_bits))+to > end) + { + bit_buff->error=1; + return; + } + bfill(to, spaces, ' '); + if (to+spaces != end) + decode_bytes(rec,bit_buff,to+spaces,end); + } + else + decode_bytes(rec,bit_buff,to,end); + } +} + + +static void uf_prespace_selected(MARIA_COLUMNDEF *rec, + MARIA_BIT_BUFF *bit_buff, + uchar *to, uchar *end) +{ + uint spaces; + if (get_bit(bit_buff)) + { + if ((spaces=get_bits(bit_buff,rec->space_length_bits))+to > end) + { + bit_buff->error=1; + return; + } + bfill(to, spaces, ' '); + if (to+spaces != end) + decode_bytes(rec,bit_buff,to+spaces,end); + } + else + decode_bytes(rec,bit_buff,to,end); +} + + +static void uf_space_prespace(MARIA_COLUMNDEF *rec, MARIA_BIT_BUFF *bit_buff, + uchar *to, uchar *end) +{ + uint spaces; + if (get_bit(bit_buff)) + bfill(to, (end-to), ' '); + else + { + if ((spaces=get_bits(bit_buff,rec->space_length_bits))+to > end) + { + bit_buff->error=1; + return; + } + bfill(to, spaces, ' '); + if (to+spaces != end) + decode_bytes(rec,bit_buff,to+spaces,end); + } +} + +static void uf_prespace(MARIA_COLUMNDEF *rec, MARIA_BIT_BUFF *bit_buff, + uchar *to, uchar *end) +{ + uint spaces; + if ((spaces=get_bits(bit_buff,rec->space_length_bits))+to > end) + { + bit_buff->error=1; + return; + } + bfill(to, spaces, ' '); + if (to+spaces != end) + decode_bytes(rec,bit_buff,to+spaces,end); +} + +static void uf_zerofill_normal(MARIA_COLUMNDEF *rec, MARIA_BIT_BUFF *bit_buff, + uchar *to, uchar *end) +{ + end-=rec->space_length_bits; + decode_bytes(rec,bit_buff, to, end); + bzero((char*) end,rec->space_length_bits); +} + +static void uf_constant(MARIA_COLUMNDEF *rec, + MARIA_BIT_BUFF *bit_buff __attribute__((unused)), + uchar *to, uchar *end) +{ + memcpy(to,rec->huff_tree->intervalls,(size_t) (end-to)); +} + +static void uf_intervall(MARIA_COLUMNDEF *rec, MARIA_BIT_BUFF *bit_buff, + uchar *to, + uchar *end) +{ + reg1 uint field_length=(uint) (end-to); + memcpy(to,rec->huff_tree->intervalls+field_length*decode_pos(bit_buff, + rec->huff_tree), + (size_t) field_length); +} + + +/*ARGSUSED*/ +static void uf_zero(MARIA_COLUMNDEF *rec __attribute__((unused)), + MARIA_BIT_BUFF *bit_buff __attribute__((unused)), + uchar *to, uchar *end) +{ + bzero(to, (uint) (end-to)); +} + +static void uf_blob(MARIA_COLUMNDEF *rec, MARIA_BIT_BUFF *bit_buff, + uchar *to, uchar *end) +{ + if (get_bit(bit_buff)) + bzero(to, (uint) (end-to)); + else + { + ulong length=get_bits(bit_buff,rec->space_length_bits); + uint pack_length=(uint) (end-to)-portable_sizeof_char_ptr; + if (bit_buff->blob_pos+length > bit_buff->blob_end) + { + bit_buff->error=1; + bzero(to, (end-to)); + return; + } + decode_bytes(rec, bit_buff, bit_buff->blob_pos, + bit_buff->blob_pos + length); + _ma_store_blob_length(to, pack_length, length); + memcpy_fixed((uchar*) to+pack_length,(uchar*) &bit_buff->blob_pos, + sizeof(uchar*)); + bit_buff->blob_pos+=length; + } +} + + +static void uf_varchar1(MARIA_COLUMNDEF *rec, MARIA_BIT_BUFF *bit_buff, + uchar *to, uchar *end __attribute__((unused))) +{ + if (get_bit(bit_buff)) + to[0]= 0; /* Zero lengths */ + else + { + ulong length=get_bits(bit_buff,rec->space_length_bits); + *to= (char) length; + decode_bytes(rec,bit_buff,to+1,to+1+length); + } +} + + +static void uf_varchar2(MARIA_COLUMNDEF *rec, MARIA_BIT_BUFF *bit_buff, + uchar *to, uchar *end __attribute__((unused))) +{ + if (get_bit(bit_buff)) + to[0]=to[1]=0; /* Zero lengths */ + else + { + ulong length=get_bits(bit_buff,rec->space_length_bits); + int2store(to,length); + decode_bytes(rec,bit_buff,to+2,to+2+length); + } +} + + /* Functions to decode of buffer of bits */ + +#if BITS_SAVED == 64 + +static void decode_bytes(MARIA_COLUMNDEF *rec,MARIA_BIT_BUFF *bit_buff, + uchar *to, uchar *end) +{ + reg1 uint bits,low_byte; + reg3 uint16 *pos; + reg4 uint table_bits,table_and; + MARIA_DECODE_TREE *decode_tree; + + decode_tree=rec->decode_tree; + bits=bit_buff->bits; /* Save in reg for quicker access */ + table_bits=decode_tree->quick_table_bits; + table_and= (1 << table_bits)-1; + + do + { + if (bits <= 32) + { + if (bit_buff->pos > bit_buff->end+4) + { + bit_buff->error=1; + return; /* Can't be right */ + } + bit_buff->current_byte= (bit_buff->current_byte << 32) + + ((((uint) bit_buff->pos[3])) + + (((uint) bit_buff->pos[2]) << 8) + + (((uint) bit_buff->pos[1]) << 16) + + (((uint) bit_buff->pos[0]) << 24)); + bit_buff->pos+=4; + bits+=32; + } + /* + First use info in quick_table. + + The quick table is an array of 16-bit values. There exists one + value for each possible code representable by table_bits bits. + In most cases table_bits is 9. So there are 512 16-bit values. + + If the high-order bit (16) is set (IS_CHAR) then the array slot + for this value is a valid Huffman code for a resulting uchar value. + + The low-order 8 bits (1..8) are the resulting uchar value. + + Bits 9..14 are the length of the Huffman code for this uchar value. + This means so many bits from the input stream were needed to + represent this uchar value. The remaining bits belong to later + Huffman codes. This also means that for every Huffman code shorter + than table_bits there are multiple entires in the array, which + differ just in the unused bits. + + If the high-order bit (16) is clear (0) then the remaining bits are + the position of the remaining Huffman decode tree segment behind the + quick table. + */ + low_byte=(uint) (bit_buff->current_byte >> (bits - table_bits)) & table_and; + low_byte=decode_tree->table[low_byte]; + if (low_byte & IS_CHAR) + { + /* + All Huffman codes of less or equal table_bits length are in the + quick table. This is one of them. + */ + *to++ = (char) (low_byte & 255); /* Found char in quick table */ + bits-= ((low_byte >> 8) & 31); /* Remove bits used */ + } + else + { /* Map through rest of decode-table */ + /* This means that the Huffman code must be longer than table_bits. */ + pos=decode_tree->table+low_byte; + bits-=table_bits; + /* NOTE: decode_bytes_test_bit() is a macro wich contains a break !!! */ + for (;;) + { + low_byte=(uint) (bit_buff->current_byte >> (bits-8)); + decode_bytes_test_bit(0); + decode_bytes_test_bit(1); + decode_bytes_test_bit(2); + decode_bytes_test_bit(3); + decode_bytes_test_bit(4); + decode_bytes_test_bit(5); + decode_bytes_test_bit(6); + decode_bytes_test_bit(7); + bits-=8; + } + *to++ = (char) *pos; + } + } while (to != end); + + bit_buff->bits=bits; + return; +} + +#else + +static void decode_bytes(MARIA_COLUMNDEF *rec, MARIA_BIT_BUFF *bit_buff, + uchar *to, uchar *end) +{ + reg1 uint bits,low_byte; + reg3 uint16 *pos; + reg4 uint table_bits,table_and; + MARIA_DECODE_TREE *decode_tree; + + decode_tree=rec->huff_tree; + bits=bit_buff->bits; /* Save in reg for quicker access */ + table_bits=decode_tree->quick_table_bits; + table_and= (1 << table_bits)-1; + + do + { + if (bits < table_bits) + { + if (bit_buff->pos > bit_buff->end+1) + { + bit_buff->error=1; + return; /* Can't be right */ + } +#if BITS_SAVED == 32 + bit_buff->current_byte= (bit_buff->current_byte << 24) + + (((uint) ((uchar) bit_buff->pos[2]))) + + (((uint) ((uchar) bit_buff->pos[1])) << 8) + + (((uint) ((uchar) bit_buff->pos[0])) << 16); + bit_buff->pos+=3; + bits+=24; +#else + if (bits) /* We must have at leasts 9 bits */ + { + bit_buff->current_byte= (bit_buff->current_byte << 8) + + (uint) ((uchar) bit_buff->pos[0]); + bit_buff->pos++; + bits+=8; + } + else + { + bit_buff->current_byte= ((uint) ((uchar) bit_buff->pos[0]) << 8) + + ((uint) ((uchar) bit_buff->pos[1])); + bit_buff->pos+=2; + bits+=16; + } +#endif + } + /* First use info in quick_table */ + low_byte=(bit_buff->current_byte >> (bits - table_bits)) & table_and; + low_byte=decode_tree->table[low_byte]; + if (low_byte & IS_CHAR) + { + *to++ = (low_byte & 255); /* Found char in quick table */ + bits-= ((low_byte >> 8) & 31); /* Remove bits used */ + } + else + { /* Map through rest of decode-table */ + pos=decode_tree->table+low_byte; + bits-=table_bits; + for (;;) + { + if (bits < 8) + { /* We don't need to check end */ +#if BITS_SAVED == 32 + bit_buff->current_byte= (bit_buff->current_byte << 24) + + (((uint) ((uchar) bit_buff->pos[2]))) + + (((uint) ((uchar) bit_buff->pos[1])) << 8) + + (((uint) ((uchar) bit_buff->pos[0])) << 16); + bit_buff->pos+=3; + bits+=24; +#else + bit_buff->current_byte= (bit_buff->current_byte << 8) + + (uint) ((uchar) bit_buff->pos[0]); + bit_buff->pos+=1; + bits+=8; +#endif + } + low_byte=(uint) (bit_buff->current_byte >> (bits-8)); + decode_bytes_test_bit(0); + decode_bytes_test_bit(1); + decode_bytes_test_bit(2); + decode_bytes_test_bit(3); + decode_bytes_test_bit(4); + decode_bytes_test_bit(5); + decode_bytes_test_bit(6); + decode_bytes_test_bit(7); + bits-=8; + } + *to++ = (char) *pos; + } + } while (to != end); + + bit_buff->bits=bits; + return; +} +#endif /* BIT_SAVED == 64 */ + + +static uint decode_pos(MARIA_BIT_BUFF *bit_buff, + MARIA_DECODE_TREE *decode_tree) +{ + uint16 *pos=decode_tree->table; + for (;;) + { + if (get_bit(bit_buff)) + pos++; + if (*pos & IS_CHAR) + return (uint) (*pos & ~IS_CHAR); + pos+= *pos; + } +} + + +int _ma_read_rnd_pack_record(MARIA_HA *info, + uchar *buf, + register MARIA_RECORD_POS filepos, + my_bool skip_deleted_blocks) +{ + File file; + MARIA_BLOCK_INFO block_info; + MARIA_SHARE *share= info->s; + DBUG_ENTER("_ma_read_rnd_pack_record"); + + if (filepos >= info->state->data_file_length) + { + my_errno= HA_ERR_END_OF_FILE; + goto err; + } + + file= info->dfile.file; + if (info->opt_flag & READ_CACHE_USED) + { + if (_ma_read_cache(&info->rec_cache, block_info.header, + filepos, share->pack.ref_length, + skip_deleted_blocks ? READING_NEXT : 0)) + goto err; + file= -1; + } + if (_ma_pack_get_block_info(info, &info->bit_buff, &block_info, + &info->rec_buff, &info->rec_buff_size, + file, filepos)) + goto err; /* Error code is already set */ +#ifndef DBUG_OFF + if (block_info.rec_len > share->max_pack_length) + { + my_errno=HA_ERR_WRONG_IN_RECORD; + goto err; + } +#endif + + if (info->opt_flag & READ_CACHE_USED) + { + if (_ma_read_cache(&info->rec_cache, info->rec_buff, + block_info.filepos, block_info.rec_len, + skip_deleted_blocks ? READING_NEXT : 0)) + goto err; + } + else + { + if (my_read(info->dfile.file, info->rec_buff + block_info.offset, + block_info.rec_len-block_info.offset, + MYF(MY_NABP))) + goto err; + } + info->packed_length= block_info.rec_len; + info->cur_row.lastpos= filepos; + info->cur_row.nextpos= block_info.filepos+block_info.rec_len; + info->update|= HA_STATE_AKTIV | HA_STATE_KEY_CHANGED; + + DBUG_RETURN (_ma_pack_rec_unpack(info, &info->bit_buff, buf, + info->rec_buff, block_info.rec_len)); + err: + DBUG_RETURN(my_errno); +} + + + /* Read and process header from a huff-record-file */ + +uint _ma_pack_get_block_info(MARIA_HA *maria, MARIA_BIT_BUFF *bit_buff, + MARIA_BLOCK_INFO *info, + uchar **rec_buff_p, size_t *rec_buff_size_p, + File file, my_off_t filepos) +{ + uchar *header= info->header; + uint head_length,ref_length; + LINT_INIT(ref_length); + + if (file >= 0) + { + ref_length=maria->s->pack.ref_length; + /* + We can't use my_pread() here because _ma_read_rnd_pack_record assumes + position is ok + */ + VOID(my_seek(file,filepos,MY_SEEK_SET,MYF(0))); + if (my_read(file, header,ref_length,MYF(MY_NABP))) + return BLOCK_FATAL_ERROR; + DBUG_DUMP("header", header, ref_length); + } + head_length= read_pack_length((uint) maria->s->pack.version, header, + &info->rec_len); + if (maria->s->base.blobs) + { + head_length+= read_pack_length((uint) maria->s->pack.version, + header + head_length, &info->blob_len); + /* + Ensure that the record buffer is big enough for the compressed + record plus all expanded blobs. [We do not have an extra buffer + for the resulting blobs. Sigh.] + */ + if (_ma_alloc_buffer(rec_buff_p, rec_buff_size_p, + info->rec_len + info->blob_len + + maria->s->base.extra_rec_buff_size)) + return BLOCK_FATAL_ERROR; /* not enough memory */ + bit_buff->blob_pos= *rec_buff_p + info->rec_len; + bit_buff->blob_end= bit_buff->blob_pos + info->blob_len; + maria->blob_length=info->blob_len; + } + info->filepos=filepos+head_length; + if (file > 0) + { + info->offset=min(info->rec_len, ref_length - head_length); + memcpy(*rec_buff_p, header + head_length, info->offset); + } + return 0; +} + + + /* rutines for bit buffer */ + /* Note buffer must be 6 uchar bigger than longest row */ + +static void init_bit_buffer(MARIA_BIT_BUFF *bit_buff, uchar *buffer, + uint length) +{ + bit_buff->pos=buffer; + bit_buff->end=buffer+length; + bit_buff->bits=bit_buff->error=0; + bit_buff->current_byte=0; /* Avoid purify errors */ +} + +static uint fill_and_get_bits(MARIA_BIT_BUFF *bit_buff, uint count) +{ + uint tmp; + count-=bit_buff->bits; + tmp=(bit_buff->current_byte & mask[bit_buff->bits]) << count; + fill_buffer(bit_buff); + bit_buff->bits=BITS_SAVED - count; + return tmp+(bit_buff->current_byte >> (BITS_SAVED - count)); +} + + /* Fill in empty bit_buff->current_byte from buffer */ + /* Sets bit_buff->error if buffer is exhausted */ + +static void fill_buffer(MARIA_BIT_BUFF *bit_buff) +{ + if (bit_buff->pos >= bit_buff->end) + { + bit_buff->error= 1; + bit_buff->current_byte=0; + return; + } +#if BITS_SAVED == 64 + bit_buff->current_byte= ((((uint) ((uchar) bit_buff->pos[7]))) + + (((uint) ((uchar) bit_buff->pos[6])) << 8) + + (((uint) ((uchar) bit_buff->pos[5])) << 16) + + (((uint) ((uchar) bit_buff->pos[4])) << 24) + + ((ulonglong) + ((((uint) ((uchar) bit_buff->pos[3]))) + + (((uint) ((uchar) bit_buff->pos[2])) << 8) + + (((uint) ((uchar) bit_buff->pos[1])) << 16) + + (((uint) ((uchar) bit_buff->pos[0])) << 24)) << 32)); + bit_buff->pos+=8; +#else +#if BITS_SAVED == 32 + bit_buff->current_byte= (((uint) ((uchar) bit_buff->pos[3])) + + (((uint) ((uchar) bit_buff->pos[2])) << 8) + + (((uint) ((uchar) bit_buff->pos[1])) << 16) + + (((uint) ((uchar) bit_buff->pos[0])) << 24)); + bit_buff->pos+=4; +#else + bit_buff->current_byte= (uint) (((uint) ((uchar) bit_buff->pos[1]))+ + (((uint) ((uchar) bit_buff->pos[0])) << 8)); + bit_buff->pos+=2; +#endif +#endif +} + + /* Get number of bits neaded to represent value */ + +static uint max_bit(register uint value) +{ + reg2 uint power=1; + + while ((value>>=1)) + power++; + return (power); +} + + +/***************************************************************************** + Some redefined functions to handle files when we are using memmap +*****************************************************************************/ +#ifdef HAVE_SYS_MMAN_H +#include <sys/mman.h> +#endif + +#ifdef HAVE_MMAP + +static int _ma_read_mempack_record(MARIA_HA *info, uchar *buf, + MARIA_RECORD_POS filepos); +static int _ma_read_rnd_mempack_record(MARIA_HA*, uchar *, MARIA_RECORD_POS, + my_bool); + +my_bool _ma_memmap_file(MARIA_HA *info) +{ + MARIA_SHARE *share= info->s; + DBUG_ENTER("maria_memmap_file"); + + if (!info->s->file_map) + { + if (my_seek(info->dfile.file, 0L, MY_SEEK_END, MYF(0)) < + share->state.state.data_file_length+MEMMAP_EXTRA_MARGIN) + { + DBUG_PRINT("warning",("File isn't extended for memmap")); + DBUG_RETURN(0); + } + if (_ma_dynmap_file(info, share->state.state.data_file_length)) + DBUG_RETURN(0); + } + info->opt_flag|= MEMMAP_USED; + info->read_record= share->read_record= _ma_read_mempack_record; + share->scan= _ma_read_rnd_mempack_record; + DBUG_RETURN(1); +} + + +void _ma_unmap_file(MARIA_HA *info) +{ + VOID(my_munmap((char*) info->s->file_map, + (size_t) info->s->mmaped_length + MEMMAP_EXTRA_MARGIN)); +} + + +static uchar * +_ma_mempack_get_block_info(MARIA_HA *maria, + MARIA_BIT_BUFF *bit_buff, + MARIA_BLOCK_INFO *info, + uchar **rec_buff_p, + size_t *rec_buff_size_p, + uchar *header) +{ + header+= read_pack_length((uint) maria->s->pack.version, header, + &info->rec_len); + if (maria->s->base.blobs) + { + header+= read_pack_length((uint) maria->s->pack.version, header, + &info->blob_len); + /* _ma_alloc_rec_buff sets my_errno on error */ + if (_ma_alloc_buffer(rec_buff_p, rec_buff_size_p, + info->blob_len + maria->s->base.extra_rec_buff_size)) + return 0; /* not enough memory */ + bit_buff->blob_pos= *rec_buff_p; + bit_buff->blob_end= *rec_buff_p + info->blob_len; + } + return header; +} + + +static int _ma_read_mempack_record(MARIA_HA *info, uchar *buf, + MARIA_RECORD_POS filepos) +{ + MARIA_BLOCK_INFO block_info; + MARIA_SHARE *share= info->s; + uchar *pos; + DBUG_ENTER("maria_read_mempack_record"); + + if (filepos == HA_OFFSET_ERROR) + DBUG_RETURN(my_errno); /* _search() didn't find record */ + + if (!(pos= (uchar*) _ma_mempack_get_block_info(info, &info->bit_buff, + &block_info, &info->rec_buff, + &info->rec_buff_size, + (uchar*) share->file_map+ + filepos))) + DBUG_RETURN(my_errno); + DBUG_RETURN(_ma_pack_rec_unpack(info, &info->bit_buff, buf, + pos, block_info.rec_len)); +} + + +/*ARGSUSED*/ +static int _ma_read_rnd_mempack_record(MARIA_HA *info, + uchar *buf, + register MARIA_RECORD_POS filepos, + my_bool skip_deleted_blocks + __attribute__((unused))) +{ + MARIA_BLOCK_INFO block_info; + MARIA_SHARE *share= info->s; + uchar *pos,*start; + DBUG_ENTER("_ma_read_rnd_mempack_record"); + + if (filepos >= share->state.state.data_file_length) + { + my_errno=HA_ERR_END_OF_FILE; + goto err; + } + if (!(pos= (uchar*) _ma_mempack_get_block_info(info, &info->bit_buff, + &block_info, + &info->rec_buff, + &info->rec_buff_size, + (uchar*) + (start= share->file_map + + filepos)))) + goto err; +#ifndef DBUG_OFF + if (block_info.rec_len > info->s->max_pack_length) + { + my_errno=HA_ERR_WRONG_IN_RECORD; + goto err; + } +#endif + info->packed_length=block_info.rec_len; + info->cur_row.lastpos= filepos; + info->cur_row.nextpos= filepos+(uint) (pos-start)+block_info.rec_len; + info->update|= HA_STATE_AKTIV | HA_STATE_KEY_CHANGED; + + DBUG_RETURN (_ma_pack_rec_unpack(info, &info->bit_buff, buf, + pos, block_info.rec_len)); + err: + DBUG_RETURN(my_errno); +} + +#endif /* HAVE_MMAP */ + + /* Save length of row */ + +uint _ma_save_pack_length(uint version, uchar *block_buff, ulong length) +{ + if (length < 254) + { + *(uchar*) block_buff= (uchar) length; + return 1; + } + if (length <= 65535) + { + *(uchar*) block_buff=254; + int2store(block_buff+1,(uint) length); + return 3; + } + *(uchar*) block_buff=255; + if (version == 1) /* old format */ + { + DBUG_ASSERT(length <= 0xFFFFFF); + int3store(block_buff + 1, (ulong) length); + return 4; + } + else + { + int4store(block_buff + 1, (ulong) length); + return 5; + } +} + + +static uint read_pack_length(uint version, const uchar *buf, ulong *length) +{ + if (buf[0] < 254) + { + *length= buf[0]; + return 1; + } + else if (buf[0] == 254) + { + *length= uint2korr(buf + 1); + return 3; + } + if (version == 1) /* old format */ + { + *length= uint3korr(buf + 1); + return 4; + } + else + { + *length= uint4korr(buf + 1); + return 5; + } +} + + +uint _ma_calc_pack_length(uint version, ulong length) +{ + return (length < 254) ? 1 : (length < 65536) ? 3 : (version == 1) ? 4 : 5; +} diff --git a/storage/maria/ma_page.c b/storage/maria/ma_page.c new file mode 100644 index 00000000000..54361b8ee3b --- /dev/null +++ b/storage/maria/ma_page.c @@ -0,0 +1,591 @@ +/* Copyright (C) 2006 MySQL AB & MySQL Finland AB & TCX DataKonsult 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 */ + +/* + Read and write key blocks + + The basic structure of a key block is as follows: + + LSN 7 (LSN_STORE_SIZE); Log number for last change; + Only for transactional pages + PACK_TRANSID 6 (TRANSID_SIZE); Relative transid to pack page transid's + Only for transactional pages + KEYNR 1 (KEYPAGE_KEYID_SIZE) Which index this page belongs to + FLAG 1 (KEYPAGE_FLAG_SIZE) Flags for page + PAGE_SIZE 2 (KEYPAGE_USED_SIZE) How much of the page is used. + high-byte-first + + The flag is a combination of the following values: + + KEYPAGE_FLAG_ISNOD Page is a node + KEYPAGE_FLAG_HAS_TRANSID There may be a transid on the page. + + After this we store key data, either packed or not packed, directly + after each other. If the page is a node flag, there is a pointer to + the next key page at page start and after each key. + + At end of page the last KEYPAGE_CHECKSUM_SIZE bytes are reserved for a + page checksum. +*/ + +#include "maria_def.h" +#include "trnman.h" +#include "ma_key_recover.h" + +/** + Fill MARIA_PAGE structure for usage with _ma_write_keypage +*/ + +void _ma_page_setup(MARIA_PAGE *page, MARIA_HA *info, + const MARIA_KEYDEF *keyinfo, my_off_t pos, + uchar *buff) +{ + MARIA_SHARE *share= info->s; + + page->info= info; + page->keyinfo= keyinfo; + page->buff= buff; + page->pos= pos; + page->size= _ma_get_page_used(share, buff); + page->flag= _ma_get_keypage_flag(share, buff); + page->node= ((page->flag & KEYPAGE_FLAG_ISNOD) ? + share->base.key_reflength : 0); +} + + +/** + Fetch a key-page in memory + + @fn _ma_fetch_keypage() + @param page Fill this struct with information about read page + @param info Maria handler + @param keyinfo Key definition for used key + @param pos Position for page (in bytes) + @param lock Lock type for page + @param level Importance of page; Priority for page cache + @param buff Buffer to use for page + @param return_buffer Set to 1 if we want to force useage of buff + + @return + @retval 0 ok + @retval 1 error +*/ + +my_bool _ma_fetch_keypage(MARIA_PAGE *page, MARIA_HA *info, + const MARIA_KEYDEF *keyinfo, + my_off_t pos, enum pagecache_page_lock lock, + int level, uchar *buff, + my_bool return_buffer __attribute__ ((unused))) +{ + uchar *tmp; + MARIA_PINNED_PAGE page_link; + MARIA_SHARE *share= info->s; + uint block_size= share->block_size; + DBUG_ENTER("_ma_fetch_keypage"); + DBUG_PRINT("enter",("pos: %ld", (long) pos)); + + tmp= pagecache_read(share->pagecache, &share->kfile, + (pgcache_page_no_t) (pos / block_size), level, buff, + share->page_type, lock, &page_link.link); + + if (lock != PAGECACHE_LOCK_LEFT_UNLOCKED) + { + DBUG_ASSERT(lock == PAGECACHE_LOCK_WRITE); + page_link.unlock= PAGECACHE_LOCK_WRITE_UNLOCK; + page_link.changed= 0; + push_dynamic(&info->pinned_pages, (void*) &page_link); + page->link_offset= info->pinned_pages.elements-1; + } + + if (tmp == info->buff) + info->keyread_buff_used=1; + else if (!tmp) + { + DBUG_PRINT("error",("Got errno: %d from pagecache_read",my_errno)); + info->last_keypage=HA_OFFSET_ERROR; + maria_print_error(share, HA_ERR_CRASHED); + my_errno=HA_ERR_CRASHED; + DBUG_RETURN(1); + } + info->last_keypage= pos; + + /* + Setup page structure to make pages easy to use + This is same as page_fill_info, but here inlined as this si used + so often. + */ + page->info= info; + page->keyinfo= keyinfo; + page->buff= tmp; + page->pos= pos; + page->size= _ma_get_page_used(share, tmp); + page->flag= _ma_get_keypage_flag(share, tmp); + page->node= ((page->flag & KEYPAGE_FLAG_ISNOD) ? + share->base.key_reflength : 0); + +#ifdef EXTRA_DEBUG + { + uint page_size= page->size; + if (page_size < 4 || page_size > block_size || + _ma_get_keynr(share, tmp) != keyinfo->key_nr) + { + DBUG_PRINT("error",("page %lu had wrong page length: %u keynr: %u", + (ulong) (pos / block_size), page_size, + _ma_get_keynr(share, tmp))); + DBUG_DUMP("page", tmp, page_size); + info->last_keypage = HA_OFFSET_ERROR; + maria_print_error(share, HA_ERR_CRASHED); + my_errno= HA_ERR_CRASHED; + tmp= 0; + } + } +#endif + DBUG_RETURN(0); +} /* _ma_fetch_keypage */ + + +/* Write a key-page on disk */ + +my_bool _ma_write_keypage(MARIA_PAGE *page, enum pagecache_page_lock lock, + int level) +{ + MARIA_SHARE *share= page->info->s; + uint block_size= share->block_size; + uchar *buff= page->buff; + my_bool res; + MARIA_PINNED_PAGE page_link; + DBUG_ENTER("_ma_write_keypage"); + +#ifdef EXTRA_DEBUG /* Safety check */ + { + uint page_length, nod_flag; + page_length= _ma_get_page_used(share, buff); + nod_flag= _ma_test_if_nod(share, buff); + + DBUG_ASSERT(page->size == page_length); + DBUG_ASSERT(page->flag == _ma_get_keypage_flag(share, buff)); + + if (page->pos < share->base.keystart || + page->pos+block_size > share->state.state.key_file_length || + (page->pos & (maria_block_size-1))) + { + DBUG_PRINT("error",("Trying to write inside key status region: " + "key_start: %lu length: %lu page: %lu", + (long) share->base.keystart, + (long) share->state.state.key_file_length, + (long) page->pos)); + my_errno=EINVAL; + DBUG_ASSERT(0); + DBUG_RETURN(1); + } + DBUG_PRINT("page",("write page at: %lu",(long) page->pos)); + DBUG_DUMP("buff", buff, page_length); + DBUG_ASSERT(page_length >= share->keypage_header + nod_flag + + page->keyinfo->minlength || maria_in_recovery); + } +#endif + + /* Verify that keynr is correct */ + DBUG_ASSERT(_ma_get_keynr(share, buff) == page->keyinfo->key_nr); + +#if defined(EXTRA_DEBUG) && defined(HAVE_valgrind) && defined(NOT_ANYMORE) + { + /* This is here to catch uninitialized bytes */ + uint length= page->size; + ulong crc= my_checksum(0, buff, length); + int4store(buff + block_size - KEYPAGE_CHECKSUM_SIZE, crc); + } +#endif + +#ifdef IDENTICAL_PAGES_AFTER_RECOVERY + { + uint length= page->size; + DBUG_ASSERT(length <= block_size - KEYPAGE_CHECKSUM_SIZE); + bzero(buff + length, block_size - length); + } +#endif + + res= pagecache_write(share->pagecache, + &share->kfile, + (pgcache_page_no_t) (page->pos / block_size), + level, buff, share->page_type, + lock, + lock == PAGECACHE_LOCK_LEFT_WRITELOCKED ? + PAGECACHE_PIN_LEFT_PINNED : + (lock == PAGECACHE_LOCK_WRITE_UNLOCK ? + PAGECACHE_UNPIN : PAGECACHE_PIN), + PAGECACHE_WRITE_DELAY, &page_link.link, + LSN_IMPOSSIBLE); + + if (lock == PAGECACHE_LOCK_WRITE) + { + /* It was not locked before, we have to unlock it when we unpin pages */ + page_link.unlock= PAGECACHE_LOCK_WRITE_UNLOCK; + page_link.changed= 1; + push_dynamic(&page->info->pinned_pages, (void*) &page_link); + } + DBUG_RETURN(res); +} + + +/** + @brief Put page in free list + + @fn _ma_dispose() + @param info Maria handle + @param pos Address to page + @param page_not_read 1 if page has not yet been read + + @note + The page at 'pos' must have been read with a write lock. + This function does logging (unlike _ma_new()). + + @return + @retval 0 ok + @retval 1 error + +*/ + +int _ma_dispose(register MARIA_HA *info, my_off_t pos, my_bool page_not_read) +{ + my_off_t old_link; + uchar buff[MAX_KEYPAGE_HEADER_SIZE+ 8 + 2]; + ulonglong page_no; + MARIA_SHARE *share= info->s; + MARIA_PINNED_PAGE page_link; + uint block_size= share->block_size; + int result= 0; + enum pagecache_page_lock lock_method; + enum pagecache_page_pin pin_method; + DBUG_ENTER("_ma_dispose"); + DBUG_PRINT("enter",("pos: %ld", (long) pos)); + DBUG_ASSERT(pos % block_size == 0); + + (void) _ma_lock_key_del(info, 0); + + old_link= share->key_del_current; + share->key_del_current= pos; + page_no= pos / block_size; + bzero(buff, share->keypage_header); + _ma_store_keynr(share, buff, (uchar) MARIA_DELETE_KEY_NR); + _ma_store_page_used(share, buff, share->keypage_header + 8); + mi_sizestore(buff + share->keypage_header, old_link); + share->state.changed|= STATE_NOT_SORTED_PAGES; + + if (share->now_transactional) + { + LSN lsn; + uchar log_data[FILEID_STORE_SIZE + PAGE_STORE_SIZE * 2]; + LEX_CUSTRING log_array[TRANSLOG_INTERNAL_PARTS + 1]; + my_off_t page; + + /* Store address of deleted page */ + page_store(log_data + FILEID_STORE_SIZE, page_no); + + /* Store link to next unused page (the link that is written to page) */ + page= (old_link == HA_OFFSET_ERROR ? IMPOSSIBLE_PAGE_NO : + old_link / block_size); + page_store(log_data + FILEID_STORE_SIZE + PAGE_STORE_SIZE, page); + + log_array[TRANSLOG_INTERNAL_PARTS + 0].str= log_data; + log_array[TRANSLOG_INTERNAL_PARTS + 0].length= sizeof(log_data); + + if (translog_write_record(&lsn, LOGREC_REDO_INDEX_FREE_PAGE, + info->trn, info, + (translog_size_t) sizeof(log_data), + TRANSLOG_INTERNAL_PARTS + 1, log_array, + log_data, NULL)) + result= 1; + } + + if (page_not_read) + { + lock_method= PAGECACHE_LOCK_WRITE; + pin_method= PAGECACHE_PIN; + } + else + { + lock_method= PAGECACHE_LOCK_LEFT_WRITELOCKED; + pin_method= PAGECACHE_PIN_LEFT_PINNED; + } + + if (pagecache_write_part(share->pagecache, + &share->kfile, (pgcache_page_no_t) page_no, + PAGECACHE_PRIORITY_LOW, buff, + share->page_type, + lock_method, pin_method, + PAGECACHE_WRITE_DELAY, &page_link.link, + LSN_IMPOSSIBLE, + 0, share->keypage_header + 8)) + result= 1; + +#ifdef IDENTICAL_PAGES_AFTER_RECOVERY + { + uchar *page_buff= pagecache_block_link_to_buffer(page_link.link); + bzero(page_buff + share->keypage_header + 8, + block_size - share->keypage_header - 8 - KEYPAGE_CHECKSUM_SIZE); + } +#endif + + if (page_not_read) + { + /* It was not locked before, we have to unlock it when we unpin pages */ + page_link.unlock= PAGECACHE_LOCK_WRITE_UNLOCK; + page_link.changed= 1; + push_dynamic(&info->pinned_pages, (void*) &page_link); + } + + DBUG_RETURN(result); +} /* _ma_dispose */ + + +/** + @brief Get address for free page to use + + @fn _ma_new() + @param info Maria handle + @param level Type of key block (caching priority for pagecache) + @param page_link Pointer to page in page cache if read. One can + check if this is used by checking if + page_link->changed != 0 + + @note Logging of this is left to the caller (so that the "new"ing and the + first changes done to this new page can be logged as one single entry - one + single _ma_log_new()) call). + + @return + HA_OFFSET_ERROR File is full or page read error + # Page address to use +*/ + +my_off_t _ma_new(register MARIA_HA *info, int level, + MARIA_PINNED_PAGE **page_link) + +{ + my_off_t pos; + MARIA_SHARE *share= info->s; + uint block_size= share->block_size; + DBUG_ENTER("_ma_new"); + + if (_ma_lock_key_del(info, 1)) + { + pthread_mutex_lock(&share->intern_lock); + pos= share->state.state.key_file_length; + if (pos >= share->base.max_key_file_length - block_size) + { + my_errno=HA_ERR_INDEX_FILE_FULL; + pthread_mutex_unlock(&share->intern_lock); + DBUG_RETURN(HA_OFFSET_ERROR); + } + share->state.state.key_file_length+= block_size; + /* Following is for not transactional tables */ + info->state->key_file_length= share->state.state.key_file_length; + pthread_mutex_unlock(&share->intern_lock); + (*page_link)->changed= 0; + (*page_link)->write_lock= PAGECACHE_LOCK_WRITE; + } + else + { + uchar *buff; + pos= share->key_del_current; /* Protected */ + DBUG_ASSERT(share->pagecache->block_size == block_size); + if (!(buff= pagecache_read(share->pagecache, + &share->kfile, + (pgcache_page_no_t) (pos / block_size), level, + 0, share->page_type, + PAGECACHE_LOCK_WRITE, &(*page_link)->link))) + pos= HA_OFFSET_ERROR; + else + { + /* + Next deleted page's number is in the header of the present page + (single linked list): + */ +#ifndef DBUG_OFF + my_off_t key_del_current; +#endif + share->key_del_current= mi_sizekorr(buff+share->keypage_header); +#ifndef DBUG_OFF + key_del_current= share->key_del_current; + DBUG_ASSERT(key_del_current != share->state.key_del && + (key_del_current != 0) && + ((key_del_current == HA_OFFSET_ERROR) || + (key_del_current <= + (share->state.state.key_file_length - block_size)))); +#endif + } + + (*page_link)->unlock= PAGECACHE_LOCK_WRITE_UNLOCK; + (*page_link)->write_lock= PAGECACHE_LOCK_WRITE; + /* + We have to mark it changed as _ma_flush_pending_blocks() uses + 'changed' to know if we used the page cache or not + */ + (*page_link)->changed= 1; + push_dynamic(&info->pinned_pages, (void*) *page_link); + *page_link= dynamic_element(&info->pinned_pages, + info->pinned_pages.elements-1, + MARIA_PINNED_PAGE *); + } + share->state.changed|= STATE_NOT_SORTED_PAGES; + DBUG_PRINT("exit",("Pos: %ld",(long) pos)); + DBUG_RETURN(pos); +} /* _ma_new */ + + +/** + Log compactation of a index page +*/ + +static my_bool _ma_log_compact_keypage(MARIA_HA *info, my_off_t page, + TrID min_read_from) +{ + LSN lsn; + uchar log_data[FILEID_STORE_SIZE + PAGE_STORE_SIZE + 1 + TRANSID_SIZE]; + LEX_CUSTRING log_array[TRANSLOG_INTERNAL_PARTS + 1]; + MARIA_SHARE *share= info->s; + DBUG_ENTER("_ma_log_compact_keypage"); + DBUG_PRINT("enter", ("page: %lu", (ulong) page)); + + /* Store address of new root page */ + page/= share->block_size; + page_store(log_data + FILEID_STORE_SIZE, page); + + log_data[FILEID_STORE_SIZE + PAGE_STORE_SIZE]= KEY_OP_COMPACT_PAGE; + transid_store(log_data + FILEID_STORE_SIZE + PAGE_STORE_SIZE +1, + min_read_from); + + log_array[TRANSLOG_INTERNAL_PARTS + 0].str= log_data; + log_array[TRANSLOG_INTERNAL_PARTS + 0].length= sizeof(log_data); + + if (translog_write_record(&lsn, LOGREC_REDO_INDEX, + info->trn, info, + (translog_size_t) sizeof(log_data), + TRANSLOG_INTERNAL_PARTS + 1, log_array, + log_data, NULL)) + DBUG_RETURN(1); + DBUG_RETURN(0); +} + + +/** + Remove all transaction id's less than given one from a key page + + @fn _ma_compact_keypage() + @param keyinfo Key handler + @param page_pos Page position on disk + @param page Buffer for page + @param min_read_from Remove all trids from page less than this + + @retval 0 Ok + ®retval 1 Error; my_errno contains the error +*/ + +my_bool _ma_compact_keypage(MARIA_PAGE *ma_page, TrID min_read_from) +{ + MARIA_HA *info= ma_page->info; + MARIA_SHARE *share= info->s; + MARIA_KEY key; + uchar *page, *endpos, *start_of_empty_space; + uint page_flag, nod_flag, saved_space; + my_bool page_has_transid; + DBUG_ENTER("_ma_compact_keypage"); + + page_flag= ma_page->flag; + if (!(page_flag & KEYPAGE_FLAG_HAS_TRANSID)) + DBUG_RETURN(0); /* No transaction id on page */ + + nod_flag= ma_page->node; + page= ma_page->buff; + endpos= page + ma_page->size; + key.data= info->lastkey_buff; + key.keyinfo= (MARIA_KEYDEF*) ma_page->keyinfo; + + page_has_transid= 0; + page+= share->keypage_header + nod_flag; + key.data[0]= 0; /* safety */ + start_of_empty_space= 0; + saved_space= 0; + do + { + if (!(page= (*ma_page->keyinfo->skip_key)(&key, 0, 0, page))) + { + DBUG_PRINT("error",("Couldn't find last key: page: 0x%lx", + (long) page)); + maria_print_error(share, HA_ERR_CRASHED); + my_errno=HA_ERR_CRASHED; + DBUG_RETURN(1); + } + if (key_has_transid(page-1)) + { + uint transid_length; + transid_length= transid_packed_length(page); + + if (min_read_from == ~(TrID) 0 || + min_read_from < transid_get_packed(share, page)) + { + page[-1]&= 254; /* Remove transid marker */ + transid_length= transid_packed_length(page); + if (start_of_empty_space) + { + /* Move block before the transid up in page */ + uint copy_length= (uint) (page - start_of_empty_space) - saved_space; + memmove(start_of_empty_space, start_of_empty_space + saved_space, + copy_length); + start_of_empty_space+= copy_length; + } + else + start_of_empty_space= page; + saved_space+= transid_length; + } + else + page_has_transid= 1; /* At least one id left */ + page+= transid_length; + } + page+= nod_flag; + } while (page < endpos); + + DBUG_ASSERT(page == endpos); + + if (start_of_empty_space) + { + /* + Move last block down + This is always true if any transid was removed + */ + uint copy_length= (uint) (endpos - start_of_empty_space) - saved_space; + + if (copy_length) + memmove(start_of_empty_space, start_of_empty_space + saved_space, + copy_length); + ma_page->size= (uint) (start_of_empty_space + copy_length - ma_page->buff); + page_store_size(share, ma_page); + } + + if (!page_has_transid) + { + ma_page->flag&= ~KEYPAGE_FLAG_HAS_TRANSID; + _ma_store_keypage_flag(share, ma_page->buff, ma_page->flag); + /* Clear packed transid (in case of zerofill) */ + bzero(ma_page->buff + LSN_STORE_SIZE, TRANSID_SIZE); + } + + if (share->now_transactional) + { + if (_ma_log_compact_keypage(info, ma_page->pos, min_read_from)) + DBUG_RETURN(1); + } + DBUG_RETURN(0); +} diff --git a/storage/maria/ma_pagecache.c b/storage/maria/ma_pagecache.c new file mode 100644 index 00000000000..dd676028f05 --- /dev/null +++ b/storage/maria/ma_pagecache.c @@ -0,0 +1,5100 @@ +/* Copyright (C) 2000-2008 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 */ + +/* + These functions handle page caching for Maria tables. + + One cache can handle many files. + It must contain buffers of the same blocksize. + init_pagecache() should be used to init cache handler. + + The free list (free_block_list) is a stack like structure. + When a block is freed by free_block(), it is pushed onto the stack. + When a new block is required it is first tried to pop one from the stack. + If the stack is empty, it is tried to get a never-used block from the pool. + If this is empty too, then a block is taken from the LRU ring, flushing it + to disk, if necessary. This is handled in find_block(). + With the new free list, the blocks can have three temperatures: + hot, warm and cold (which is free). This is remembered in the block header + by the enum PCBLOCK_TEMPERATURE temperature variable. Remembering the + temperature is necessary to correctly count the number of warm blocks, + which is required to decide when blocks are allowed to become hot. Whenever + a block is inserted to another (sub-)chain, we take the old and new + temperature into account to decide if we got one more or less warm block. + blocks_unused is the sum of never used blocks in the pool and of currently + free blocks. blocks_used is the number of blocks fetched from the pool and + as such gives the maximum number of in-use blocks at any time. + + TODO: Write operation locks whole cache till the end of the operation. + Should be fixed. +*/ + +#include "maria_def.h" +#include <m_string.h> +#include "ma_pagecache.h" +#include "ma_blockrec.h" +#include <my_bit.h> +#include <errno.h> + +/* + Some compilation flags have been added specifically for this module + to control the following: + - not to let a thread to yield the control when reading directly + from page cache, which might improve performance in many cases; + to enable this add: + #define SERIALIZED_READ_FROM_CACHE + - to set an upper bound for number of threads simultaneously + using the page cache; this setting helps to determine an optimal + size for hash table and improve performance when the number of + blocks in the page cache much less than the number of threads + accessing it; + to set this number equal to <N> add + #define MAX_THREADS <N> + - to substitute calls of pthread_cond_wait for calls of + pthread_cond_timedwait (wait with timeout set up); + this setting should be used only when you want to trap a deadlock + situation, which theoretically should not happen; + to set timeout equal to <T> seconds add + #define PAGECACHE_TIMEOUT <T> + - to enable the module traps and to send debug information from + page cache module to a special debug log add: + #define PAGECACHE_DEBUG + the name of this debug log file <LOG NAME> can be set through: + #define PAGECACHE_DEBUG_LOG <LOG NAME> + if the name is not defined, it's set by default; + if the PAGECACHE_DEBUG flag is not set up and we are in a debug + mode, i.e. when ! defined(DBUG_OFF), the debug information from the + module is sent to the regular debug log. + + Example of the settings: + #define SERIALIZED_READ_FROM_CACHE + #define MAX_THREADS 100 + #define PAGECACHE_TIMEOUT 1 + #define PAGECACHE_DEBUG + #define PAGECACHE_DEBUG_LOG "my_pagecache_debug.log" +*/ + +/* + In key cache we have external raw locking here we use + SERIALIZED_READ_FROM_CACHE to avoid problem of reading + not consistent data from the page. + (keycache functions (key_cache_read(), key_cache_insert() and + key_cache_write()) rely on external MyISAM lock, we don't) +*/ +#define SERIALIZED_READ_FROM_CACHE yes + +#define PCBLOCK_INFO(B) \ + DBUG_PRINT("info", \ + ("block: 0x%lx fd: %lu page: %lu s: %0x hshL: " \ + " 0x%lx req: %u/%u wrlocks: %u rdlocks %u " \ + "rdlocks_q: %u pins: %u status: %u type: %s", \ + (ulong)(B), \ + (ulong)((B)->hash_link ? \ + (B)->hash_link->file.file : \ + 0), \ + (ulong)((B)->hash_link ? \ + (B)->hash_link->pageno : \ + 0), \ + (B)->status, \ + (ulong)(B)->hash_link, \ + (uint) (B)->requests, \ + (uint)((B)->hash_link ? \ + (B)->hash_link->requests : \ + 0), \ + block->wlocks, block->rlocks, block->rlocks_queue, \ + (uint)(B)->pins, (uint)(B)->status, \ + page_cache_page_type_str[(B)->type])) + +/* TODO: put it to my_static.c */ +my_bool my_disable_flush_pagecache_blocks= 0; + +#define STRUCT_PTR(TYPE, MEMBER, a) \ + (TYPE *) ((char *) (a) - offsetof(TYPE, MEMBER)) + +/* types of condition variables */ +#define COND_FOR_REQUESTED 0 /* queue of thread waiting for read operation */ +#define COND_FOR_SAVED 1 /* queue of thread waiting for flush */ +#define COND_FOR_WRLOCK 2 /* queue of write lock */ +#define COND_SIZE 3 /* number of COND_* queues */ + +typedef pthread_cond_t KEYCACHE_CONDVAR; + +/* descriptor of the page in the page cache block buffer */ +struct st_pagecache_page +{ + PAGECACHE_FILE file; /* file to which the page belongs to */ + pgcache_page_no_t pageno; /* number of the page in the file */ +}; + +/* element in the chain of a hash table bucket */ +struct st_pagecache_hash_link +{ + struct st_pagecache_hash_link + *next, **prev; /* to connect links in the same bucket */ + struct st_pagecache_block_link + *block; /* reference to the block for the page: */ + PAGECACHE_FILE file; /* from such a file */ + pgcache_page_no_t pageno; /* this page */ + uint requests; /* number of requests for the page */ +}; + +/* simple states of a block */ +#define PCBLOCK_ERROR 1 /* an error occurred when performing disk i/o */ +#define PCBLOCK_READ 2 /* the is page in the block buffer */ +#define PCBLOCK_IN_SWITCH 4 /* block is preparing to read new page */ +#define PCBLOCK_REASSIGNED 8 /* block does not accept requests for old page */ +#define PCBLOCK_IN_FLUSH 16 /* block is in flush operation */ +#define PCBLOCK_CHANGED 32 /* block buffer contains a dirty page */ +#define PCBLOCK_DIRECT_W 64 /* possible direct write to the block */ + +/* page status, returned by find_block */ +#define PAGE_READ 0 +#define PAGE_TO_BE_READ 1 +#define PAGE_WAIT_TO_BE_READ 2 + +/* block temperature determines in which (sub-)chain the block currently is */ +enum PCBLOCK_TEMPERATURE { PCBLOCK_COLD /*free*/ , PCBLOCK_WARM , PCBLOCK_HOT }; + +/* debug info */ +#ifndef DBUG_OFF +static const char *page_cache_page_type_str[]= +{ + /* used only for control page type changing during debugging */ + "EMPTY", + "PLAIN", + "LSN", + "READ_UNKNOWN" +}; + +static const char *page_cache_page_write_mode_str[]= +{ + "DELAY", + "DONE" +}; + +static const char *page_cache_page_lock_str[]= +{ + "free -> free", + "read -> read", + "write -> write", + "free -> read", + "free -> write", + "read -> free", + "write -> free", + "write -> read" +}; + +static const char *page_cache_page_pin_str[]= +{ + "pinned -> pinned", + "unpinned -> unpinned", + "unpinned -> pinned", + "pinned -> unpinned" +}; + + +typedef struct st_pagecache_pin_info +{ + struct st_pagecache_pin_info *next, **prev; + struct st_my_thread_var *thread; +} PAGECACHE_PIN_INFO; + +/* + st_pagecache_lock_info structure should be kept in next, prev, thread part + compatible with st_pagecache_pin_info to be compatible in functions. +*/ + +typedef struct st_pagecache_lock_info +{ + struct st_pagecache_lock_info *next, **prev; + struct st_my_thread_var *thread; + my_bool write_lock; +} PAGECACHE_LOCK_INFO; + + +/* service functions maintain debugging info about pin & lock */ + + +/* + Links information about thread pinned/locked the block to the list + + SYNOPSIS + info_link() + list the list to link in + node the node which should be linked +*/ + +static void info_link(PAGECACHE_PIN_INFO **list, PAGECACHE_PIN_INFO *node) +{ + if ((node->next= *list)) + node->next->prev= &(node->next); + *list= node; + node->prev= list; +} + + +/* + Unlinks information about thread pinned/locked the block from the list + + SYNOPSIS + info_unlink() + node the node which should be unlinked +*/ + +static void info_unlink(PAGECACHE_PIN_INFO *node) +{ + if ((*node->prev= node->next)) + node->next->prev= node->prev; +} + + +/* + Finds information about given thread in the list of threads which + pinned/locked this block. + + SYNOPSIS + info_find() + list the list where to find the thread + thread thread ID (reference to the st_my_thread_var + of the thread) + any return any thread of the list + + RETURN + 0 - the thread was not found + pointer to the information node of the thread in the list, or, if 'any', + to any thread of the list. +*/ + +static PAGECACHE_PIN_INFO *info_find(PAGECACHE_PIN_INFO *list, + struct st_my_thread_var *thread, + my_bool any) +{ + register PAGECACHE_PIN_INFO *i= list; + if (any) + return i; + for(; i != 0; i= i->next) + if (i->thread == thread) + return i; + return 0; +} + +#endif /* !DBUG_OFF */ + +/* page cache block */ +struct st_pagecache_block_link +{ + struct st_pagecache_block_link + *next_used, **prev_used; /* to connect links in the LRU chain (ring) */ + struct st_pagecache_block_link + *next_changed, **prev_changed; /* for lists of file dirty/clean blocks */ + struct st_pagecache_hash_link + *hash_link; /* backward ptr to referring hash_link */ +#ifndef DBUG_OFF + PAGECACHE_PIN_INFO *pin_list; + PAGECACHE_LOCK_INFO *lock_list; +#endif + KEYCACHE_CONDVAR *condvar; /* condition variable for 'no readers' event */ + uchar *buffer; /* buffer for the block page */ + pthread_t write_locker; + + ulonglong last_hit_time; /* timestamp of the last hit */ + WQUEUE + wqueue[COND_SIZE]; /* queues on waiting requests for new/old pages */ + uint32 requests; /* number of requests for the block */ + uint32 pins; /* pin counter */ + uint32 wlocks; /* write locks counter */ + uint32 rlocks; /* read locks counter */ + uint32 rlocks_queue; /* rd. locks waiting wr. lock of this thread */ + uint16 status; /* state of the block */ + int16 error; /* error code for block in case of error */ + enum PCBLOCK_TEMPERATURE temperature; /* block temperature: cold, warm, hot*/ + enum pagecache_page_type type; /* type of the block */ + uint hits_left; /* number of hits left until promotion */ + /** @brief LSN when first became dirty; LSN_MAX means "not yet set" */ + LSN rec_lsn; +}; + +/** @brief information describing a run of flush_pagecache_blocks_int() */ +struct st_file_in_flush +{ + File file; + /** + @brief threads waiting for the thread currently flushing this file to be + done + */ + WQUEUE flush_queue; + /** + @brief if the thread currently flushing the file has a non-empty + first_in_switch list. + */ + my_bool first_in_switch; +}; + +#ifndef DBUG_OFF +/* debug checks */ + +#ifdef NOT_USED +static my_bool info_check_pin(PAGECACHE_BLOCK_LINK *block, + enum pagecache_page_pin mode + __attribute__((unused))) +{ + struct st_my_thread_var *thread= my_thread_var; + PAGECACHE_PIN_INFO *info= info_find(block->pin_list, thread); + DBUG_ENTER("info_check_pin"); + DBUG_PRINT("enter", ("thread: 0x%lx pin: %s", + (ulong) thread, page_cache_page_pin_str[mode])); + if (info) + { + if (mode == PAGECACHE_PIN_LEFT_UNPINNED) + { + DBUG_PRINT("info", + ("info_check_pin: thread: 0x%lx block: 0x%lx ; LEFT_UNPINNED!!!", + (ulong)thread, (ulong)block)); + DBUG_RETURN(1); + } + else if (mode == PAGECACHE_PIN) + { + DBUG_PRINT("info", + ("info_check_pin: thread: 0x%lx block: 0x%lx ; PIN!!!", + (ulong)thread, (ulong)block)); + DBUG_RETURN(1); + } + } + else + { + if (mode == PAGECACHE_PIN_LEFT_PINNED) + { + DBUG_PRINT("info", + ("info_check_pin: thread: 0x%lx block: 0x%lx ; LEFT_PINNED!!!", + (ulong)thread, (ulong)block)); + DBUG_RETURN(1); + } + else if (mode == PAGECACHE_UNPIN) + { + DBUG_PRINT("info", + ("info_check_pin: thread: 0x%lx block: 0x%lx ; UNPIN!!!", + (ulong)thread, (ulong)block)); + DBUG_RETURN(1); + } + } + DBUG_RETURN(0); +} + + +/* + Debug function which checks current lock/pin state and requested changes + + SYNOPSIS + info_check_lock() + lock requested lock changes + pin requested pin changes + + RETURN + 0 - OK + 1 - Error +*/ + +static my_bool info_check_lock(PAGECACHE_BLOCK_LINK *block, + enum pagecache_page_lock lock, + enum pagecache_page_pin pin) +{ + struct st_my_thread_var *thread= my_thread_var; + PAGECACHE_LOCK_INFO *info= + (PAGECACHE_LOCK_INFO *) info_find((PAGECACHE_PIN_INFO *) block->lock_list, + thread); + DBUG_ENTER("info_check_lock"); + switch(lock) { + case PAGECACHE_LOCK_LEFT_UNLOCKED: + if (pin != PAGECACHE_PIN_LEFT_UNPINNED || + info) + goto error; + break; + case PAGECACHE_LOCK_LEFT_READLOCKED: + if ((pin != PAGECACHE_PIN_LEFT_UNPINNED && + pin != PAGECACHE_PIN_LEFT_PINNED) || + info == 0 || info->write_lock) + goto error; + break; + case PAGECACHE_LOCK_LEFT_WRITELOCKED: + if (pin != PAGECACHE_PIN_LEFT_PINNED || + info == 0 || !info->write_lock) + goto error; + break; + case PAGECACHE_LOCK_READ: + if ((pin != PAGECACHE_PIN_LEFT_UNPINNED && + pin != PAGECACHE_PIN) || + info != 0) + goto error; + break; + case PAGECACHE_LOCK_WRITE: + if (pin != PAGECACHE_PIN || + info != 0) + goto error; + break; + case PAGECACHE_LOCK_READ_UNLOCK: + if ((pin != PAGECACHE_PIN_LEFT_UNPINNED && + pin != PAGECACHE_UNPIN) || + info == 0 || info->write_lock) + goto error; + break; + case PAGECACHE_LOCK_WRITE_UNLOCK: + if (pin != PAGECACHE_UNPIN || + info == 0 || !info->write_lock) + goto error; + break; + case PAGECACHE_LOCK_WRITE_TO_READ: + if ((pin != PAGECACHE_PIN_LEFT_PINNED && + pin != PAGECACHE_UNPIN) || + info == 0 || !info->write_lock) + goto error; + break; + } + DBUG_RETURN(0); +error: + DBUG_PRINT("info", + ("info_check_lock: thread: 0x%lx block 0x%lx: info: %d wrt: %d," + "to lock: %s, to pin: %s", + (ulong)thread, (ulong)block, test(info), + (info ? info->write_lock : 0), + page_cache_page_lock_str[lock], + page_cache_page_pin_str[pin])); + DBUG_RETURN(1); +} +#endif /* NOT_USED */ +#endif /* !DBUG_OFF */ + +#define FLUSH_CACHE 2000 /* sort this many blocks at once */ + +static void free_block(PAGECACHE *pagecache, PAGECACHE_BLOCK_LINK *block); +#ifndef DBUG_OFF +static void test_key_cache(PAGECACHE *pagecache, + const char *where, my_bool lock); +#endif + +#define PAGECACHE_HASH(p, f, pos) (((ulong) (pos) + \ + (ulong) (f).file) & (p->hash_entries-1)) +#define FILE_HASH(f) ((uint) (f).file & (PAGECACHE_CHANGED_BLOCKS_HASH - 1)) + +#define DEFAULT_PAGECACHE_DEBUG_LOG "pagecache_debug.log" + +#if defined(PAGECACHE_DEBUG) && ! defined(PAGECACHE_DEBUG_LOG) +#define PAGECACHE_DEBUG_LOG DEFAULT_PAGECACHE_DEBUG_LOG +#endif + +#if defined(PAGECACHE_DEBUG_LOG) +static FILE *pagecache_debug_log= NULL; +static void pagecache_debug_print _VARARGS((const char *fmt, ...)); +#define PAGECACHE_DEBUG_OPEN \ + if (!pagecache_debug_log) \ + { \ + pagecache_debug_log= fopen(PAGECACHE_DEBUG_LOG, "w"); \ + (void) setvbuf(pagecache_debug_log, NULL, _IOLBF, BUFSIZ); \ + } + +#define PAGECACHE_DEBUG_CLOSE \ + if (pagecache_debug_log) \ + { \ + fclose(pagecache_debug_log); \ + pagecache_debug_log= 0; \ + } +#else +#define PAGECACHE_DEBUG_OPEN +#define PAGECACHE_DEBUG_CLOSE +#endif /* defined(PAGECACHE_DEBUG_LOG) */ + +#if defined(PAGECACHE_DEBUG_LOG) && defined(PAGECACHE_DEBUG) +#define KEYCACHE_DBUG_PRINT(l, m) \ + { if (pagecache_debug_log) \ + fprintf(pagecache_debug_log, "%s: ", l); \ + pagecache_debug_print m; } + +#define KEYCACHE_DBUG_ASSERT(a) \ + { if (! (a) && pagecache_debug_log) \ + fclose(pagecache_debug_log); \ + assert(a); } +#else +#define KEYCACHE_DBUG_PRINT(l, m) DBUG_PRINT(l, m) +#define KEYCACHE_DBUG_ASSERT(a) DBUG_ASSERT(a) +#endif /* defined(PAGECACHE_DEBUG_LOG) && defined(PAGECACHE_DEBUG) */ + +#if defined(PAGECACHE_DEBUG) || !defined(DBUG_OFF) +#ifdef THREAD +static long pagecache_thread_id; +#define KEYCACHE_THREAD_TRACE(l) \ + KEYCACHE_DBUG_PRINT(l,("|thread %ld",pagecache_thread_id)) + +#define KEYCACHE_THREAD_TRACE_BEGIN(l) \ + { struct st_my_thread_var *thread_var= my_thread_var; \ + pagecache_thread_id= thread_var->id; \ + KEYCACHE_DBUG_PRINT(l,("[thread %ld",pagecache_thread_id)) } + +#define KEYCACHE_THREAD_TRACE_END(l) \ + KEYCACHE_DBUG_PRINT(l,("]thread %ld",pagecache_thread_id)) +#else /* THREAD */ +#define KEYCACHE_THREAD_TRACE(l) KEYCACHE_DBUG_PRINT(l,("")) +#define KEYCACHE_THREAD_TRACE_BEGIN(l) KEYCACHE_DBUG_PRINT(l,("")) +#define KEYCACHE_THREAD_TRACE_END(l) KEYCACHE_DBUG_PRINT(l,("")) +#endif /* THREAD */ +#else +#define KEYCACHE_THREAD_TRACE_BEGIN(l) +#define KEYCACHE_THREAD_TRACE_END(l) +#define KEYCACHE_THREAD_TRACE(l) +#endif /* defined(PAGECACHE_DEBUG) || !defined(DBUG_OFF) */ + +#define PCBLOCK_NUMBER(p, b) \ + ((uint) (((char*)(b)-(char *) p->block_root)/sizeof(PAGECACHE_BLOCK_LINK))) +#define PAGECACHE_HASH_LINK_NUMBER(p, h) \ + ((uint) (((char*)(h)-(char *) p->hash_link_root)/ \ + sizeof(PAGECACHE_HASH_LINK))) + +#if (defined(PAGECACHE_TIMEOUT) && !defined(__WIN__)) || defined(PAGECACHE_DEBUG) +static int pagecache_pthread_cond_wait(pthread_cond_t *cond, + pthread_mutex_t *mutex); +#else +#define pagecache_pthread_cond_wait pthread_cond_wait +#endif + +#if defined(PAGECACHE_DEBUG) +static int ___pagecache_pthread_mutex_lock(pthread_mutex_t *mutex); +static void ___pagecache_pthread_mutex_unlock(pthread_mutex_t *mutex); +static int ___pagecache_pthread_cond_signal(pthread_cond_t *cond); +#define pagecache_pthread_mutex_lock(M) \ +{ DBUG_PRINT("lock", ("mutex lock 0x%lx %u", (ulong)(M), __LINE__)); \ + ___pagecache_pthread_mutex_lock(M);} +#define pagecache_pthread_mutex_unlock(M) \ +{ DBUG_PRINT("lock", ("mutex unlock 0x%lx %u", (ulong)(M), __LINE__)); \ + ___pagecache_pthread_mutex_unlock(M);} +#define pagecache_pthread_cond_signal(M) \ +{ DBUG_PRINT("lock", ("signal 0x%lx %u", (ulong)(M), __LINE__)); \ + ___pagecache_pthread_cond_signal(M);} +#else +#define pagecache_pthread_mutex_lock pthread_mutex_lock +#define pagecache_pthread_mutex_unlock pthread_mutex_unlock +#define pagecache_pthread_cond_signal pthread_cond_signal +#endif /* defined(PAGECACHE_DEBUG) */ + +extern my_bool translog_flush(TRANSLOG_ADDRESS lsn); + +/* + Write page to the disk + + SYNOPSIS + pagecache_fwrite() + pagecache - page cache pointer + filedesc - pagecache file descriptor structure + buffer - buffer which we will write + type - page type (plain or with LSN) + flags - MYF() flags + + RETURN + 0 - OK + 1 - Error +*/ + +static my_bool pagecache_fwrite(PAGECACHE *pagecache, + PAGECACHE_FILE *filedesc, + uchar *buffer, + pgcache_page_no_t pageno, + enum pagecache_page_type type + __attribute__((unused)), + myf flags) +{ + DBUG_ENTER("pagecache_fwrite"); + DBUG_ASSERT(type != PAGECACHE_READ_UNKNOWN_PAGE); + + /* Todo: Integrate this with write_callback so we have only one callback */ + if ((*filedesc->flush_log_callback)(buffer, pageno, filedesc->callback_data)) + DBUG_RETURN(1); + DBUG_PRINT("info", ("write_callback: 0x%lx data: 0x%lx", + (ulong) filedesc->write_callback, + (ulong) filedesc->callback_data)); + if ((*filedesc->write_callback)(buffer, pageno, filedesc->callback_data)) + { + DBUG_PRINT("error", ("write callback problem")); + DBUG_RETURN(1); + } + if (my_pwrite(filedesc->file, buffer, pagecache->block_size, + ((my_off_t) pageno << pagecache->shift), flags)) + { + (*filedesc->write_fail)(filedesc->callback_data); + DBUG_RETURN(1); + } + DBUG_RETURN(0); +} + + +/* + Read page from the disk + + SYNOPSIS + pagecache_fread() + pagecache - page cache pointer + filedesc - pagecache file descriptor structure + buffer - buffer in which we will read + pageno - page number + flags - MYF() flags +*/ +#define pagecache_fread(pagecache, filedesc, buffer, pageno, flags) \ + my_pread((filedesc)->file, buffer, pagecache->block_size, \ + ((my_off_t) pageno << pagecache->shift), flags) + + +/** + @brief set rec_lsn of pagecache block (if it is needed) + + @param block block where to set rec_lsn + @param first_REDO_LSN_for_page the LSN to set +*/ + +static inline void pagecache_set_block_rec_lsn(PAGECACHE_BLOCK_LINK *block, + LSN first_REDO_LSN_for_page) +{ + if (block->rec_lsn == LSN_MAX) + block->rec_lsn= first_REDO_LSN_for_page; + else + DBUG_ASSERT(cmp_translog_addr(block->rec_lsn, + first_REDO_LSN_for_page) <= 0); +} + + +/* + next_power(value) is 2 at the power of (1+floor(log2(value))); + e.g. next_power(2)=4, next_power(3)=4. +*/ +static inline uint next_power(uint value) +{ + return (uint) my_round_up_to_next_power((uint32) value) << 1; +} + + +/* + Initialize a page cache + + SYNOPSIS + init_pagecache() + pagecache pointer to a page cache data structure + key_cache_block_size size of blocks to keep cached data + use_mem total memory to use for the key cache + division_limit division limit (may be zero) + age_threshold age threshold (may be zero) + block_size size of block (should be power of 2) + my_read_flags Flags used for all pread/pwrite calls + Usually MY_WME in case of recovery + + RETURN VALUE + number of blocks in the key cache, if successful, + 0 - otherwise. + + NOTES. + if pagecache->inited != 0 we assume that the key cache + is already initialized. This is for now used by myisamchk, but shouldn't + be something that a program should rely on! + + It's assumed that no two threads call this function simultaneously + referring to the same key cache handle. + +*/ + +ulong init_pagecache(PAGECACHE *pagecache, size_t use_mem, + uint division_limit, uint age_threshold, + uint block_size, myf my_readwrite_flags) +{ + ulong blocks, hash_links, length; + int error; + DBUG_ENTER("init_pagecache"); + DBUG_ASSERT(block_size >= 512); + + PAGECACHE_DEBUG_OPEN; + if (pagecache->inited && pagecache->disk_blocks > 0) + { + DBUG_PRINT("warning",("key cache already in use")); + DBUG_RETURN(0); + } + + pagecache->global_cache_w_requests= pagecache->global_cache_r_requests= 0; + pagecache->global_cache_read= pagecache->global_cache_write= 0; + pagecache->disk_blocks= -1; + if (! pagecache->inited) + { + if (pthread_mutex_init(&pagecache->cache_lock, MY_MUTEX_INIT_FAST) || + hash_init(&pagecache->files_in_flush, &my_charset_bin, 32, + offsetof(struct st_file_in_flush, file), + sizeof(((struct st_file_in_flush *)NULL)->file), + NULL, NULL, 0)) + goto err; + pagecache->inited= 1; + pagecache->in_init= 0; + pagecache->resize_queue.last_thread= NULL; + } + + pagecache->mem_size= use_mem; + pagecache->block_size= block_size; + pagecache->shift= my_bit_log2(block_size); + pagecache->readwrite_flags= my_readwrite_flags | MY_NABP | MY_WAIT_IF_FULL; + pagecache->org_readwrite_flags= pagecache->readwrite_flags; + DBUG_PRINT("info", ("block_size: %u", block_size)); + DBUG_ASSERT(((uint)(1 << pagecache->shift)) == block_size); + + blocks= (ulong) (use_mem / (sizeof(PAGECACHE_BLOCK_LINK) + + 2 * sizeof(PAGECACHE_HASH_LINK) + + sizeof(PAGECACHE_HASH_LINK*) * + 5/4 + block_size)); + /* + We need to support page cache with just one block to be able to do + scanning of rows-in-block files + */ + for ( ; ; ) + { + if (blocks < 8) + { + my_errno= ENOMEM; + goto err; + } + /* Set my_hash_entries to the next bigger 2 power */ + if ((pagecache->hash_entries= next_power(blocks)) < + (blocks) * 5/4) + pagecache->hash_entries<<= 1; + hash_links= 2 * blocks; +#if defined(MAX_THREADS) + if (hash_links < MAX_THREADS + blocks - 1) + hash_links= MAX_THREADS + blocks - 1; +#endif + while ((length= (ALIGN_SIZE(blocks * sizeof(PAGECACHE_BLOCK_LINK)) + + ALIGN_SIZE(hash_links * sizeof(PAGECACHE_HASH_LINK)) + + ALIGN_SIZE(sizeof(PAGECACHE_HASH_LINK*) * + pagecache->hash_entries))) + + (blocks << pagecache->shift) > use_mem) + blocks--; + /* Allocate memory for cache page buffers */ + if ((pagecache->block_mem= + my_large_malloc((ulong) blocks * pagecache->block_size, + MYF(MY_WME)))) + { + /* + Allocate memory for blocks, hash_links and hash entries; + For each block 2 hash links are allocated + */ + if ((pagecache->block_root= + (PAGECACHE_BLOCK_LINK*) my_malloc((size_t) length, MYF(0)))) + break; + my_large_free(pagecache->block_mem, MYF(0)); + pagecache->block_mem= 0; + } + blocks= blocks / 4*3; + } + pagecache->blocks_unused= blocks; + pagecache->disk_blocks= (long) blocks; + pagecache->hash_links= hash_links; + pagecache->hash_root= + (PAGECACHE_HASH_LINK**) ((char*) pagecache->block_root + + ALIGN_SIZE(blocks*sizeof(PAGECACHE_BLOCK_LINK))); + pagecache->hash_link_root= + (PAGECACHE_HASH_LINK*) ((char*) pagecache->hash_root + + ALIGN_SIZE((sizeof(PAGECACHE_HASH_LINK*) * + pagecache->hash_entries))); + bzero((uchar*) pagecache->block_root, + pagecache->disk_blocks * sizeof(PAGECACHE_BLOCK_LINK)); + bzero((uchar*) pagecache->hash_root, + pagecache->hash_entries * sizeof(PAGECACHE_HASH_LINK*)); + bzero((uchar*) pagecache->hash_link_root, + pagecache->hash_links * sizeof(PAGECACHE_HASH_LINK)); + pagecache->hash_links_used= 0; + pagecache->free_hash_list= NULL; + pagecache->blocks_used= pagecache->blocks_changed= 0; + + pagecache->global_blocks_changed= 0; + pagecache->blocks_available=0; /* For debugging */ + + /* The LRU chain is empty after initialization */ + pagecache->used_last= NULL; + pagecache->used_ins= NULL; + pagecache->free_block_list= NULL; + pagecache->time= 0; + pagecache->warm_blocks= 0; + pagecache->min_warm_blocks= (division_limit ? + blocks * division_limit / 100 + 1 : + blocks); + pagecache->age_threshold= (age_threshold ? + blocks * age_threshold / 100 : + blocks); + + pagecache->cnt_for_resize_op= 0; + pagecache->resize_in_flush= 0; + pagecache->can_be_used= 1; + + pagecache->waiting_for_hash_link.last_thread= NULL; + pagecache->waiting_for_block.last_thread= NULL; + DBUG_PRINT("exit", + ("disk_blocks: %ld block_root: 0x%lx hash_entries: %ld\ + hash_root: 0x%lx hash_links: %ld hash_link_root: 0x%lx", + pagecache->disk_blocks, (long) pagecache->block_root, + pagecache->hash_entries, (long) pagecache->hash_root, + pagecache->hash_links, (long) pagecache->hash_link_root)); + bzero((uchar*) pagecache->changed_blocks, + sizeof(pagecache->changed_blocks[0]) * + PAGECACHE_CHANGED_BLOCKS_HASH); + bzero((uchar*) pagecache->file_blocks, + sizeof(pagecache->file_blocks[0]) * + PAGECACHE_CHANGED_BLOCKS_HASH); + + pagecache->blocks= pagecache->disk_blocks > 0 ? pagecache->disk_blocks : 0; + DBUG_RETURN((ulong) pagecache->disk_blocks); + +err: + error= my_errno; + pagecache->disk_blocks= 0; + pagecache->blocks= 0; + if (pagecache->block_mem) + { + my_large_free(pagecache->block_mem, MYF(0)); + pagecache->block_mem= NULL; + } + if (pagecache->block_root) + { + my_free(pagecache->block_root, MYF(0)); + pagecache->block_root= NULL; + } + my_errno= error; + pagecache->can_be_used= 0; + DBUG_RETURN(0); +} + + +/* + Flush all blocks in the key cache to disk +*/ + +#ifdef NOT_USED +static int flush_all_key_blocks(PAGECACHE *pagecache) +{ +#if defined(PAGECACHE_DEBUG) + uint cnt=0; +#endif + while (pagecache->blocks_changed > 0) + { + PAGECACHE_BLOCK_LINK *block; + for (block= pagecache->used_last->next_used ; ; block=block->next_used) + { + if (block->hash_link) + { +#if defined(PAGECACHE_DEBUG) + cnt++; + KEYCACHE_DBUG_ASSERT(cnt <= pagecache->blocks_used); +#endif + if (flush_pagecache_blocks_int(pagecache, &block->hash_link->file, + FLUSH_RELEASE, NULL, NULL)) + return 1; + break; + } + if (block == pagecache->used_last) + break; + } + } + return 0; +} +#endif /* NOT_USED */ + +/* + Resize a key cache + + SYNOPSIS + resize_pagecache() + pagecache pointer to a page cache data structure + use_mem total memory to use for the new key cache + division_limit new division limit (if not zero) + age_threshold new age threshold (if not zero) + + RETURN VALUE + number of blocks in the key cache, if successful, + 0 - otherwise. + + NOTES. + The function first compares the memory size parameter + with the key cache value. + + If they differ the function free the the memory allocated for the + old key cache blocks by calling the end_pagecache function and + then rebuilds the key cache with new blocks by calling + init_key_cache. + + The function starts the operation only when all other threads + performing operations with the key cache let her to proceed + (when cnt_for_resize=0). + + Before being usable, this function needs: + - to receive fixes for BUG#17332 "changing key_buffer_size on a running + server can crash under load" similar to those done to the key cache + - to have us (Sanja) look at the additional constraints placed on + resizing, due to the page locking specific to this page cache. + So we disable it for now. +*/ +#if NOT_USED /* keep disabled until code is fixed see above !! */ +ulong resize_pagecache(PAGECACHE *pagecache, + size_t use_mem, uint division_limit, + uint age_threshold) +{ + ulong blocks; +#ifdef THREAD + struct st_my_thread_var *thread; + WQUEUE *wqueue; + +#endif + DBUG_ENTER("resize_pagecache"); + + if (!pagecache->inited) + DBUG_RETURN(pagecache->disk_blocks); + + if(use_mem == pagecache->mem_size) + { + change_pagecache_param(pagecache, division_limit, age_threshold); + DBUG_RETURN(pagecache->disk_blocks); + } + + pagecache_pthread_mutex_lock(&pagecache->cache_lock); + +#ifdef THREAD + wqueue= &pagecache->resize_queue; + thread= my_thread_var; + wqueue_link_into_queue(wqueue, thread); + + while (wqueue->last_thread->next != thread) + { + pagecache_pthread_cond_wait(&thread->suspend, &pagecache->cache_lock); + } +#endif + + pagecache->resize_in_flush= 1; + if (flush_all_key_blocks(pagecache)) + { + /* TODO: if this happens, we should write a warning in the log file ! */ + pagecache->resize_in_flush= 0; + blocks= 0; + pagecache->can_be_used= 0; + goto finish; + } + pagecache->resize_in_flush= 0; + pagecache->can_be_used= 0; +#ifdef THREAD + while (pagecache->cnt_for_resize_op) + { + KEYCACHE_DBUG_PRINT("resize_pagecache: wait", + ("suspend thread %ld", thread->id)); + pagecache_pthread_cond_wait(&thread->suspend, &pagecache->cache_lock); + } +#else + KEYCACHE_DBUG_ASSERT(pagecache->cnt_for_resize_op == 0); +#endif + + end_pagecache(pagecache, 0); /* Don't free mutex */ + /* The following will work even if use_mem is 0 */ + blocks= init_pagecache(pagecache, pagecache->block_size, use_mem, + division_limit, age_threshold, + pagecache->readwrite_flags); + +finish: +#ifdef THREAD + wqueue_unlink_from_queue(wqueue, thread); + /* Signal for the next resize request to proceeed if any */ + if (wqueue->last_thread) + { + KEYCACHE_DBUG_PRINT("resize_pagecache: signal", + ("thread %ld", wqueue->last_thread->next->id)); + pagecache_pthread_cond_signal(&wqueue->last_thread->next->suspend); + } +#endif + pagecache_pthread_mutex_unlock(&pagecache->cache_lock); + DBUG_RETURN(blocks); +} +#endif /* 0 */ + + +/* + Increment counter blocking resize key cache operation +*/ +static inline void inc_counter_for_resize_op(PAGECACHE *pagecache) +{ + pagecache->cnt_for_resize_op++; +} + + +/* + Decrement counter blocking resize key cache operation; + Signal the operation to proceed when counter becomes equal zero +*/ +static inline void dec_counter_for_resize_op(PAGECACHE *pagecache) +{ +#ifdef THREAD + struct st_my_thread_var *last_thread; + if (!--pagecache->cnt_for_resize_op && + (last_thread= pagecache->resize_queue.last_thread)) + { + KEYCACHE_DBUG_PRINT("dec_counter_for_resize_op: signal", + ("thread %ld", last_thread->next->id)); + pagecache_pthread_cond_signal(&last_thread->next->suspend); + } +#else + pagecache->cnt_for_resize_op--; +#endif +} + +/* + Change the page cache parameters + + SYNOPSIS + change_pagecache_param() + pagecache pointer to a page cache data structure + division_limit new division limit (if not zero) + age_threshold new age threshold (if not zero) + + RETURN VALUE + none + + NOTES. + Presently the function resets the key cache parameters + concerning midpoint insertion strategy - division_limit and + age_threshold. +*/ + +void change_pagecache_param(PAGECACHE *pagecache, uint division_limit, + uint age_threshold) +{ + DBUG_ENTER("change_pagecache_param"); + + pagecache_pthread_mutex_lock(&pagecache->cache_lock); + if (division_limit) + pagecache->min_warm_blocks= (pagecache->disk_blocks * + division_limit / 100 + 1); + if (age_threshold) + pagecache->age_threshold= (pagecache->disk_blocks * + age_threshold / 100); + pagecache_pthread_mutex_unlock(&pagecache->cache_lock); + DBUG_VOID_RETURN; +} + + +/* + Removes page cache from memory. Does NOT flush pages to disk. + + SYNOPSIS + end_pagecache() + pagecache page cache handle + cleanup Complete free (Free also mutex for key cache) + + RETURN VALUE + none +*/ + +void end_pagecache(PAGECACHE *pagecache, my_bool cleanup) +{ + DBUG_ENTER("end_pagecache"); + DBUG_PRINT("enter", ("key_cache: 0x%lx", (long) pagecache)); + + if (!pagecache->inited) + DBUG_VOID_RETURN; + + if (pagecache->disk_blocks > 0) + { + if (pagecache->block_mem) + { + my_large_free(pagecache->block_mem, MYF(0)); + pagecache->block_mem= NULL; + my_free(pagecache->block_root, MYF(0)); + pagecache->block_root= NULL; + } + pagecache->disk_blocks= -1; + /* Reset blocks_changed to be safe if flush_all_key_blocks is called */ + pagecache->blocks_changed= 0; + } + + DBUG_PRINT("status", ("used: %lu changed: %lu w_requests: %lu " + "writes: %lu r_requests: %lu reads: %lu", + pagecache->blocks_used, + pagecache->global_blocks_changed, + (ulong) pagecache->global_cache_w_requests, + (ulong) pagecache->global_cache_write, + (ulong) pagecache->global_cache_r_requests, + (ulong) pagecache->global_cache_read)); + + if (cleanup) + { + hash_free(&pagecache->files_in_flush); + pthread_mutex_destroy(&pagecache->cache_lock); + pagecache->inited= pagecache->can_be_used= 0; + PAGECACHE_DEBUG_CLOSE; + } + DBUG_VOID_RETURN; +} /* end_pagecache */ + + +/* + Unlink a block from the chain of dirty/clean blocks +*/ + +static inline void unlink_changed(PAGECACHE_BLOCK_LINK *block) +{ + if (block->next_changed) + block->next_changed->prev_changed= block->prev_changed; + *block->prev_changed= block->next_changed; +} + + +/* + Link a block into the chain of dirty/clean blocks +*/ + +static inline void link_changed(PAGECACHE_BLOCK_LINK *block, + PAGECACHE_BLOCK_LINK **phead) +{ + block->prev_changed= phead; + if ((block->next_changed= *phead)) + (*phead)->prev_changed= &block->next_changed; + *phead= block; +} + + +/* + Unlink a block from the chain of dirty/clean blocks, if it's asked for, + and link it to the chain of clean blocks for the specified file +*/ + +static void link_to_file_list(PAGECACHE *pagecache, + PAGECACHE_BLOCK_LINK *block, + PAGECACHE_FILE *file, my_bool unlink_flag) +{ + if (unlink_flag) + unlink_changed(block); + link_changed(block, &pagecache->file_blocks[FILE_HASH(*file)]); + if (block->status & PCBLOCK_CHANGED) + { + block->status&= ~PCBLOCK_CHANGED; + block->rec_lsn= LSN_MAX; + pagecache->blocks_changed--; + pagecache->global_blocks_changed--; + } +} + + +/* + Unlink a block from the chain of clean blocks for the specified + file and link it to the chain of dirty blocks for this file +*/ + +static inline void link_to_changed_list(PAGECACHE *pagecache, + PAGECACHE_BLOCK_LINK *block) +{ + unlink_changed(block); + link_changed(block, + &pagecache->changed_blocks[FILE_HASH(block->hash_link->file)]); + block->status|=PCBLOCK_CHANGED; + pagecache->blocks_changed++; + pagecache->global_blocks_changed++; +} + + +/* + Link a block to the LRU chain at the beginning or at the end of + one of two parts. + + SYNOPSIS + link_block() + pagecache pointer to a page cache data structure + block pointer to the block to link to the LRU chain + hot <-> to link the block into the hot subchain + at_end <-> to link the block at the end of the subchain + + RETURN VALUE + none + + NOTES. + The LRU chain is represented by a circular list of block structures. + The list is double-linked of the type (**prev,*next) type. + The LRU chain is divided into two parts - hot and warm. + There are two pointers to access the last blocks of these two + parts. The beginning of the warm part follows right after the + end of the hot part. + Only blocks of the warm part can be used for replacement. + The first block from the beginning of this subchain is always + taken for eviction (pagecache->last_used->next) + + LRU chain: +------+ H O T +------+ + +----| end |----...<----| beg |----+ + | +------+last +------+ | + v<-link in latest hot (new end) | + | link in latest warm (new end)->^ + | +------+ W A R M +------+ | + +----| beg |---->...----| end |----+ + +------+ +------+ins + first for eviction +*/ + +static void link_block(PAGECACHE *pagecache, PAGECACHE_BLOCK_LINK *block, + my_bool hot, my_bool at_end) +{ + PAGECACHE_BLOCK_LINK *ins; + PAGECACHE_BLOCK_LINK **ptr_ins; + + PCBLOCK_INFO(block); + KEYCACHE_DBUG_ASSERT(! (block->hash_link && block->hash_link->requests)); +#ifdef THREAD + if (!hot && pagecache->waiting_for_block.last_thread) + { + /* Signal that in the LRU warm sub-chain an available block has appeared */ + struct st_my_thread_var *last_thread= + pagecache->waiting_for_block.last_thread; + struct st_my_thread_var *first_thread= last_thread->next; + struct st_my_thread_var *next_thread= first_thread; + PAGECACHE_HASH_LINK *hash_link= + (PAGECACHE_HASH_LINK *) first_thread->opt_info; + struct st_my_thread_var *thread; + do + { + thread= next_thread; + next_thread= thread->next; + /* + We notify about the event all threads that ask + for the same page as the first thread in the queue + */ + if ((PAGECACHE_HASH_LINK *) thread->opt_info == hash_link) + { + KEYCACHE_DBUG_PRINT("link_block: signal", ("thread: %ld", thread->id)); + pagecache_pthread_cond_signal(&thread->suspend); + wqueue_unlink_from_queue(&pagecache->waiting_for_block, thread); + block->requests++; + } + } + while (thread != last_thread); + hash_link->block= block; + KEYCACHE_THREAD_TRACE("link_block: after signaling"); +#if defined(PAGECACHE_DEBUG) + KEYCACHE_DBUG_PRINT("link_block", + ("linked,unlinked block: %u status: %x #requests: %u #available: %u", + PCBLOCK_NUMBER(pagecache, block), block->status, + block->requests, pagecache->blocks_available)); +#endif + return; + } +#else /* THREAD */ + KEYCACHE_DBUG_ASSERT(! (!hot && pagecache->waiting_for_block.last_thread)); + /* Condition not transformed using DeMorgan, to keep the text identical */ +#endif /* THREAD */ + ptr_ins= hot ? &pagecache->used_ins : &pagecache->used_last; + ins= *ptr_ins; + if (ins) + { + ins->next_used->prev_used= &block->next_used; + block->next_used= ins->next_used; + block->prev_used= &ins->next_used; + ins->next_used= block; + if (at_end) + *ptr_ins= block; + } + else + { + /* The LRU chain is empty */ + pagecache->used_last= pagecache->used_ins= block->next_used= block; + block->prev_used= &block->next_used; + } + KEYCACHE_THREAD_TRACE("link_block"); +#if defined(PAGECACHE_DEBUG) + pagecache->blocks_available++; + KEYCACHE_DBUG_PRINT("link_block", + ("linked block: %u:%1u status: %x #requests: %u #available: %u", + PCBLOCK_NUMBER(pagecache, block), at_end, block->status, + block->requests, pagecache->blocks_available)); + KEYCACHE_DBUG_ASSERT((ulong) pagecache->blocks_available <= + pagecache->blocks_used); +#endif +} + + +/* + Unlink a block from the LRU chain + + SYNOPSIS + unlink_block() + pagecache pointer to a page cache data structure + block pointer to the block to unlink from the LRU chain + + RETURN VALUE + none + + NOTES. + See NOTES for link_block +*/ + +static void unlink_block(PAGECACHE *pagecache, PAGECACHE_BLOCK_LINK *block) +{ + DBUG_ENTER("unlink_block"); + DBUG_PRINT("unlink_block", ("unlink 0x%lx", (ulong)block)); + DBUG_ASSERT(block->next_used != NULL); + if (block->next_used == block) + { + /* The list contains only one member */ + pagecache->used_last= pagecache->used_ins= NULL; + } + else + { + block->next_used->prev_used= block->prev_used; + *block->prev_used= block->next_used; + if (pagecache->used_last == block) + pagecache->used_last= STRUCT_PTR(PAGECACHE_BLOCK_LINK, + next_used, block->prev_used); + if (pagecache->used_ins == block) + pagecache->used_ins= STRUCT_PTR(PAGECACHE_BLOCK_LINK, + next_used, block->prev_used); + } + block->next_used= NULL; + + KEYCACHE_THREAD_TRACE("unlink_block"); +#if defined(PAGECACHE_DEBUG) + KEYCACHE_DBUG_ASSERT(pagecache->blocks_available != 0); + pagecache->blocks_available--; + KEYCACHE_DBUG_PRINT("unlink_block", + ("unlinked block: 0x%lx (%u) status: %x #requests: %u #available: %u", + (ulong)block, PCBLOCK_NUMBER(pagecache, block), + block->status, + block->requests, pagecache->blocks_available)); + PCBLOCK_INFO(block); +#endif + DBUG_VOID_RETURN; +} + + +/* + Register requests for a block + + SYNOPSIS + reg_requests() + pagecache this page cache reference + block the block we request reference + count how many requests we register (it is 1 everywhere) + + NOTE + Registration of request means we are going to use this block so we exclude + it from the LRU if it is first request +*/ +static void reg_requests(PAGECACHE *pagecache, PAGECACHE_BLOCK_LINK *block, + int count) +{ + DBUG_ENTER("reg_requests"); + DBUG_PRINT("enter", ("block: 0x%lx (%u) status: %x reqs: %u", + (ulong)block, PCBLOCK_NUMBER(pagecache, block), + block->status, block->requests)); + PCBLOCK_INFO(block); + if (! block->requests) + /* First request for the block unlinks it */ + unlink_block(pagecache, block); + block->requests+= count; + DBUG_VOID_RETURN; +} + + +/* + Unregister request for a block + linking it to the LRU chain if it's the last request + + SYNOPSIS + unreg_request() + pagecache pointer to a page cache data structure + block pointer to the block to link to the LRU chain + at_end <-> to link the block at the end of the LRU chain + + RETURN VALUE + none + + NOTES. + Every linking to the LRU chain decrements by one a special block + counter (if it's positive). If the at_end parameter is TRUE the block is + added either at the end of warm sub-chain or at the end of hot sub-chain. + It is added to the hot subchain if its counter is zero and number of + blocks in warm sub-chain is not less than some low limit (determined by + the division_limit parameter). Otherwise the block is added to the warm + sub-chain. If the at_end parameter is FALSE the block is always added + at beginning of the warm sub-chain. + Thus a warm block can be promoted to the hot sub-chain when its counter + becomes zero for the first time. + At the same time the block at the very beginning of the hot subchain + might be moved to the beginning of the warm subchain if it stays untouched + for a too long time (this time is determined by parameter age_threshold). +*/ + +static void unreg_request(PAGECACHE *pagecache, + PAGECACHE_BLOCK_LINK *block, int at_end) +{ + DBUG_ENTER("unreg_request"); + DBUG_PRINT("enter", ("block 0x%lx (%u) status: %x reqs: %u", + (ulong)block, PCBLOCK_NUMBER(pagecache, block), + block->status, block->requests)); + PCBLOCK_INFO(block); + DBUG_ASSERT(block->requests > 0); + if (! --block->requests) + { + my_bool hot; + if (block->hits_left) + block->hits_left--; + hot= !block->hits_left && at_end && + pagecache->warm_blocks > pagecache->min_warm_blocks; + if (hot) + { + if (block->temperature == PCBLOCK_WARM) + pagecache->warm_blocks--; + block->temperature= PCBLOCK_HOT; + KEYCACHE_DBUG_PRINT("unreg_request", ("#warm_blocks: %lu", + pagecache->warm_blocks)); + } + link_block(pagecache, block, hot, (my_bool)at_end); + block->last_hit_time= pagecache->time; + pagecache->time++; + + block= pagecache->used_ins; + /* Check if we should link a hot block to the warm block */ + if (block && pagecache->time - block->last_hit_time > + pagecache->age_threshold) + { + unlink_block(pagecache, block); + link_block(pagecache, block, 0, 0); + if (block->temperature != PCBLOCK_WARM) + { + pagecache->warm_blocks++; + block->temperature= PCBLOCK_WARM; + } + KEYCACHE_DBUG_PRINT("unreg_request", ("#warm_blocks: %lu", + pagecache->warm_blocks)); + } + } + DBUG_VOID_RETURN; +} + +/* + Remove a reader of the page in block +*/ + +static inline void remove_reader(PAGECACHE_BLOCK_LINK *block) +{ + DBUG_ENTER("remove_reader"); + PCBLOCK_INFO(block); + DBUG_ASSERT(block->hash_link->requests > 0); +#ifdef THREAD + if (! --block->hash_link->requests && block->condvar) + pagecache_pthread_cond_signal(block->condvar); +#else + --block->hash_link->requests; +#endif + DBUG_VOID_RETURN; +} + + +/* + Wait until the last reader of the page in block + signals on its termination +*/ + +static inline void wait_for_readers(PAGECACHE *pagecache + __attribute__((unused)), + PAGECACHE_BLOCK_LINK *block) +{ +#ifdef THREAD + struct st_my_thread_var *thread= my_thread_var; + while (block->hash_link->requests) + { + KEYCACHE_DBUG_PRINT("wait_for_readers: wait", + ("suspend thread: %ld block: %u", + thread->id, PCBLOCK_NUMBER(pagecache, block))); + block->condvar= &thread->suspend; + pagecache_pthread_cond_wait(&thread->suspend, &pagecache->cache_lock); + block->condvar= NULL; + } +#else + KEYCACHE_DBUG_ASSERT(block->hash_link->requests == 0); +#endif +} + + +/* + Add a hash link to a bucket in the hash_table +*/ + +static inline void link_hash(PAGECACHE_HASH_LINK **start, + PAGECACHE_HASH_LINK *hash_link) +{ + if (*start) + (*start)->prev= &hash_link->next; + hash_link->next= *start; + hash_link->prev= start; + *start= hash_link; +} + + +/* + Remove a hash link from the hash table +*/ + +static void unlink_hash(PAGECACHE *pagecache, PAGECACHE_HASH_LINK *hash_link) +{ + KEYCACHE_DBUG_PRINT("unlink_hash", ("fd: %u pos_ %lu #requests=%u", + (uint) hash_link->file.file, (ulong) hash_link->pageno, + hash_link->requests)); + KEYCACHE_DBUG_ASSERT(hash_link->requests == 0); + if ((*hash_link->prev= hash_link->next)) + hash_link->next->prev= hash_link->prev; + hash_link->block= NULL; +#ifdef THREAD + if (pagecache->waiting_for_hash_link.last_thread) + { + /* Signal that a free hash link has appeared */ + struct st_my_thread_var *last_thread= + pagecache->waiting_for_hash_link.last_thread; + struct st_my_thread_var *first_thread= last_thread->next; + struct st_my_thread_var *next_thread= first_thread; + PAGECACHE_PAGE *first_page= (PAGECACHE_PAGE *) (first_thread->opt_info); + struct st_my_thread_var *thread; + + hash_link->file= first_page->file; + DBUG_ASSERT(first_page->pageno < ((ULL(1)) << 40)); + hash_link->pageno= first_page->pageno; + do + { + PAGECACHE_PAGE *page; + thread= next_thread; + page= (PAGECACHE_PAGE *) thread->opt_info; + next_thread= thread->next; + /* + We notify about the event all threads that ask + for the same page as the first thread in the queue + */ + if (page->file.file == hash_link->file.file && + page->pageno == hash_link->pageno) + { + KEYCACHE_DBUG_PRINT("unlink_hash: signal", ("thread %ld", thread->id)); + pagecache_pthread_cond_signal(&thread->suspend); + wqueue_unlink_from_queue(&pagecache->waiting_for_hash_link, thread); + } + } + while (thread != last_thread); + link_hash(&pagecache->hash_root[PAGECACHE_HASH(pagecache, + hash_link->file, + hash_link->pageno)], + hash_link); + return; + } +#else /* THREAD */ + KEYCACHE_DBUG_ASSERT(! (pagecache->waiting_for_hash_link.last_thread)); +#endif /* THREAD */ + hash_link->next= pagecache->free_hash_list; + pagecache->free_hash_list= hash_link; +} + + +/* + Get the hash link for the page if it is in the cache (do not put the + page in the cache if it is absent there) + + SYNOPSIS + get_present_hash_link() + pagecache Pagecache reference + file file ID + pageno page number in the file + start where to put pointer to found hash bucket (for + direct referring it) + + RETURN + found hashlink pointer +*/ + +static PAGECACHE_HASH_LINK *get_present_hash_link(PAGECACHE *pagecache, + PAGECACHE_FILE *file, + pgcache_page_no_t pageno, + PAGECACHE_HASH_LINK ***start) +{ + reg1 PAGECACHE_HASH_LINK *hash_link; +#if defined(PAGECACHE_DEBUG) + int cnt; +#endif + DBUG_ENTER("get_present_hash_link"); + + KEYCACHE_DBUG_PRINT("get_present_hash_link", ("fd: %u pos: %lu", + (uint) file->file, (ulong) pageno)); + + /* + Find the bucket in the hash table for the pair (file, pageno); + start contains the head of the bucket list, + hash_link points to the first member of the list + */ + hash_link= *(*start= &pagecache->hash_root[PAGECACHE_HASH(pagecache, + *file, pageno)]); +#if defined(PAGECACHE_DEBUG) + cnt= 0; +#endif + /* Look for an element for the pair (file, pageno) in the bucket chain */ + while (hash_link && + (hash_link->pageno != pageno || + hash_link->file.file != file->file)) + { + hash_link= hash_link->next; +#if defined(PAGECACHE_DEBUG) + cnt++; + if (! (cnt <= pagecache->hash_links_used)) + { + int i; + for (i=0, hash_link= **start ; + i < cnt ; i++, hash_link= hash_link->next) + { + KEYCACHE_DBUG_PRINT("get_present_hash_link", ("fd: %u pos: %lu", + (uint) hash_link->file.file, (ulong) hash_link->pageno)); + } + } + KEYCACHE_DBUG_ASSERT(cnt <= pagecache->hash_links_used); +#endif + } + if (hash_link) + { + /* Register the request for the page */ + hash_link->requests++; + } + /* + As soon as the caller will release the page cache's lock, "hash_link" + will be potentially obsolete (unusable) information. + */ + DBUG_RETURN(hash_link); +} + + +/* + Get the hash link for a page +*/ + +static PAGECACHE_HASH_LINK *get_hash_link(PAGECACHE *pagecache, + PAGECACHE_FILE *file, + pgcache_page_no_t pageno) +{ + reg1 PAGECACHE_HASH_LINK *hash_link; + PAGECACHE_HASH_LINK **start; + + KEYCACHE_DBUG_PRINT("get_hash_link", ("fd: %u pos: %lu", + (uint) file->file, (ulong) pageno)); + +restart: + /* try to find the page in the cache */ + hash_link= get_present_hash_link(pagecache, file, pageno, + &start); + if (!hash_link) + { + /* There is no hash link in the hash table for the pair (file, pageno) */ + if (pagecache->free_hash_list) + { + hash_link= pagecache->free_hash_list; + pagecache->free_hash_list= hash_link->next; + } + else if (pagecache->hash_links_used < pagecache->hash_links) + { + hash_link= &pagecache->hash_link_root[pagecache->hash_links_used++]; + } + else + { +#ifdef THREAD + /* Wait for a free hash link */ + struct st_my_thread_var *thread= my_thread_var; + PAGECACHE_PAGE page; + KEYCACHE_DBUG_PRINT("get_hash_link", ("waiting")); + page.file= *file; + page.pageno= pageno; + thread->opt_info= (void *) &page; + wqueue_link_into_queue(&pagecache->waiting_for_hash_link, thread); + KEYCACHE_DBUG_PRINT("get_hash_link: wait", + ("suspend thread %ld", thread->id)); + pagecache_pthread_cond_wait(&thread->suspend, + &pagecache->cache_lock); + thread->opt_info= NULL; +#else + KEYCACHE_DBUG_ASSERT(0); +#endif + DBUG_PRINT("info", ("restarting...")); + goto restart; + } + hash_link->file= *file; + DBUG_ASSERT(pageno < ((ULL(1)) << 40)); + hash_link->pageno= pageno; + link_hash(start, hash_link); + /* Register the request for the page */ + hash_link->requests++; + } + + return hash_link; +} + + +/* + Get a block for the file page requested by a pagecache read/write operation; + If the page is not in the cache return a free block, if there is none + return the lru block after saving its buffer if the page is dirty. + + SYNOPSIS + + find_block() + pagecache pointer to a page cache data structure + file handler for the file to read page from + pageno number of the page in the file + init_hits_left how initialize the block counter for the page + wrmode <-> get for writing + reg_req Register request to thye page + page_st out {PAGE_READ,PAGE_TO_BE_READ,PAGE_WAIT_TO_BE_READ} + + RETURN VALUE + Pointer to the found block if successful, 0 - otherwise + + NOTES. + For the page from file positioned at pageno the function checks whether + the page is in the key cache specified by the first parameter. + If this is the case it immediately returns the block. + If not, the function first chooses a block for this page. If there is + no not used blocks in the key cache yet, the function takes the block + at the very beginning of the warm sub-chain. It saves the page in that + block if it's dirty before returning the pointer to it. + The function returns in the page_st parameter the following values: + PAGE_READ - if page already in the block, + PAGE_TO_BE_READ - if it is to be read yet by the current thread + WAIT_TO_BE_READ - if it is to be read by another thread + If an error occurs THE PCBLOCK_ERROR bit is set in the block status. + It might happen that there are no blocks in LRU chain (in warm part) - + all blocks are unlinked for some read/write operations. Then the function + waits until first of this operations links any block back. +*/ + +static PAGECACHE_BLOCK_LINK *find_block(PAGECACHE *pagecache, + PAGECACHE_FILE *file, + pgcache_page_no_t pageno, + int init_hits_left, + my_bool wrmode, + my_bool reg_req, + int *page_st) +{ + PAGECACHE_HASH_LINK *hash_link; + PAGECACHE_BLOCK_LINK *block; + int error= 0; + int page_status; + + DBUG_ENTER("find_block"); + KEYCACHE_THREAD_TRACE("find_block:begin"); + DBUG_PRINT("enter", ("fd: %d pos: %lu wrmode: %d", + file->file, (ulong) pageno, wrmode)); + KEYCACHE_DBUG_PRINT("find_block", ("fd: %d pos: %lu wrmode: %d", + file->file, (ulong) pageno, + wrmode)); +#if !defined(DBUG_OFF) && defined(EXTRA_DEBUG) + DBUG_EXECUTE("check_pagecache", + test_key_cache(pagecache, "start of find_block", 0);); +#endif + +restart: + /* Find the hash link for the requested page (file, pageno) */ + hash_link= get_hash_link(pagecache, file, pageno); + + page_status= -1; + if ((block= hash_link->block) && + block->hash_link == hash_link && (block->status & PCBLOCK_READ)) + page_status= PAGE_READ; + + if (wrmode && pagecache->resize_in_flush) + { + /* This is a write request during the flush phase of a resize operation */ + + if (page_status != PAGE_READ) + { + /* We don't need the page in the cache: we are going to write on disk */ + DBUG_ASSERT(hash_link->requests > 0); + hash_link->requests--; + unlink_hash(pagecache, hash_link); + return 0; + } + if (!(block->status & PCBLOCK_IN_FLUSH)) + { + DBUG_ASSERT(hash_link->requests > 0); + hash_link->requests--; + /* + Remove block to invalidate the page in the block buffer + as we are going to write directly on disk. + Although we have an exclusive lock for the updated key part + the control can be yielded by the current thread as we might + have unfinished readers of other key parts in the block + buffer. Still we are guaranteed not to have any readers + of the key part we are writing into until the block is + removed from the cache as we set the PCBLOCK_REASSIGNED + flag (see the code below that handles reading requests). + */ + free_block(pagecache, block); + return 0; + } + /* Wait until the page is flushed on disk */ + DBUG_ASSERT(hash_link->requests > 0); + hash_link->requests--; + { +#ifdef THREAD + struct st_my_thread_var *thread= my_thread_var; + wqueue_add_to_queue(&block->wqueue[COND_FOR_SAVED], thread); + do + { + KEYCACHE_DBUG_PRINT("find_block: wait", + ("suspend thread %ld", thread->id)); + pagecache_pthread_cond_wait(&thread->suspend, + &pagecache->cache_lock); + } + while(thread->next); +#else + KEYCACHE_DBUG_ASSERT(0); + /* + Given the use of "resize_in_flush", it seems impossible + that this whole branch is ever entered in single-threaded case + because "(wrmode && pagecache->resize_in_flush)" cannot be true. + TODO: Check this, and then put the whole branch into the + "#ifdef THREAD" guard. + */ +#endif + } + /* Invalidate page in the block if it has not been done yet */ + if (block->status) + free_block(pagecache, block); + return 0; + } + + if (page_status == PAGE_READ && + (block->status & (PCBLOCK_IN_SWITCH | PCBLOCK_REASSIGNED))) + { + /* This is a request for a page to be removed from cache */ + + KEYCACHE_DBUG_PRINT("find_block", + ("request for old page in block: %u " + "wrmode: %d block->status: %d", + PCBLOCK_NUMBER(pagecache, block), wrmode, + block->status)); + /* + Only reading requests can proceed until the old dirty page is flushed, + all others are to be suspended, then resubmitted + */ + if (!wrmode && !(block->status & PCBLOCK_REASSIGNED)) + { + if (reg_req) + reg_requests(pagecache, block, 1); + } + else + { + DBUG_ASSERT(hash_link->requests > 0); + hash_link->requests--; + KEYCACHE_DBUG_PRINT("find_block", + ("request waiting for old page to be saved")); + { +#ifdef THREAD + struct st_my_thread_var *thread= my_thread_var; + /* Put the request into the queue of those waiting for the old page */ + wqueue_add_to_queue(&block->wqueue[COND_FOR_SAVED], thread); + /* Wait until the request can be resubmitted */ + do + { + KEYCACHE_DBUG_PRINT("find_block: wait", + ("suspend thread %ld", thread->id)); + pagecache_pthread_cond_wait(&thread->suspend, + &pagecache->cache_lock); + } + while(thread->next); +#else + KEYCACHE_DBUG_ASSERT(0); + /* No parallel requests in single-threaded case */ +#endif + } + KEYCACHE_DBUG_PRINT("find_block", + ("request for old page resubmitted")); + DBUG_PRINT("info", ("restarting...")); + /* Resubmit the request */ + goto restart; + } + } + else + { + /* This is a request for a new page or for a page not to be removed */ + if (! block) + { + /* No block is assigned for the page yet */ + if (pagecache->blocks_unused) + { + if (pagecache->free_block_list) + { + /* There is a block in the free list. */ + block= pagecache->free_block_list; + pagecache->free_block_list= block->next_used; + block->next_used= NULL; + } + else + { + /* There are some never used blocks, take first of them */ + block= &pagecache->block_root[pagecache->blocks_used]; + block->buffer= ADD_TO_PTR(pagecache->block_mem, + ((ulong) pagecache->blocks_used* + pagecache->block_size), + uchar*); + pagecache->blocks_used++; + } + pagecache->blocks_unused--; + DBUG_ASSERT(block->wlocks == 0); + DBUG_ASSERT(block->rlocks == 0); + DBUG_ASSERT(block->rlocks_queue == 0); + DBUG_ASSERT(block->pins == 0); + block->status= 0; +#ifndef DBUG_OFF + block->type= PAGECACHE_EMPTY_PAGE; +#endif + block->requests= 1; + block->temperature= PCBLOCK_COLD; + block->hits_left= init_hits_left; + block->last_hit_time= 0; + block->rec_lsn= LSN_MAX; + link_to_file_list(pagecache, block, file, 0); + block->hash_link= hash_link; + hash_link->block= block; + page_status= PAGE_TO_BE_READ; + DBUG_PRINT("info", ("page to be read set for page 0x%lx", + (ulong)block)); + KEYCACHE_DBUG_PRINT("find_block", + ("got free or never used block %u", + PCBLOCK_NUMBER(pagecache, block))); + } + else + { + /* There are no never used blocks, use a block from the LRU chain */ + + /* + Wait until a new block is added to the LRU chain; + several threads might wait here for the same page, + all of them must get the same block + */ + +#ifdef THREAD + if (! pagecache->used_last) + { + struct st_my_thread_var *thread= my_thread_var; + thread->opt_info= (void *) hash_link; + wqueue_link_into_queue(&pagecache->waiting_for_block, thread); + do + { + KEYCACHE_DBUG_PRINT("find_block: wait", + ("suspend thread %ld", thread->id)); + pagecache_pthread_cond_wait(&thread->suspend, + &pagecache->cache_lock); + } + while (thread->next); + thread->opt_info= NULL; + } +#else + KEYCACHE_DBUG_ASSERT(pagecache->used_last); +#endif + block= hash_link->block; + if (! block) + { + /* + Take the first block from the LRU chain + unlinking it from the chain + */ + block= pagecache->used_last->next_used; + block->hits_left= init_hits_left; + block->last_hit_time= 0; + if (reg_req) + reg_requests(pagecache, block, 1); + hash_link->block= block; + } + PCBLOCK_INFO(block); + DBUG_ASSERT(block->wlocks == 0); + DBUG_ASSERT(block->rlocks == 0); + DBUG_ASSERT(block->rlocks_queue == 0); + DBUG_ASSERT(block->pins == 0); + + if (block->hash_link != hash_link && + ! (block->status & PCBLOCK_IN_SWITCH) ) + { + /* this is a primary request for a new page */ + DBUG_ASSERT(block->wlocks == 0); + DBUG_ASSERT(block->rlocks == 0); + DBUG_ASSERT(block->rlocks_queue == 0); + DBUG_ASSERT(block->pins == 0); + block->status|= PCBLOCK_IN_SWITCH; + + KEYCACHE_DBUG_PRINT("find_block", + ("got block %u for new page", + PCBLOCK_NUMBER(pagecache, block))); + + if (block->status & PCBLOCK_CHANGED) + { + /* The block contains a dirty page - push it out of the cache */ + + KEYCACHE_DBUG_PRINT("find_block", ("block is dirty")); + + pagecache_pthread_mutex_unlock(&pagecache->cache_lock); + /* + The call is thread safe because only the current + thread might change the block->hash_link value + */ + DBUG_ASSERT(block->pins == 0); + error= pagecache_fwrite(pagecache, + &block->hash_link->file, + block->buffer, + block->hash_link->pageno, + block->type, + pagecache->readwrite_flags); + pagecache_pthread_mutex_lock(&pagecache->cache_lock); + pagecache->global_cache_write++; + } + + block->status|= PCBLOCK_REASSIGNED; + if (block->hash_link) + { + /* + Wait until all pending read requests + for this page are executed + (we could have avoided this waiting, if we had read + a page in the cache in a sweep, without yielding control) + */ + wait_for_readers(pagecache, block); + + /* Remove the hash link for this page from the hash table */ + unlink_hash(pagecache, block->hash_link); + /* All pending requests for this page must be resubmitted */ +#ifdef THREAD + if (block->wqueue[COND_FOR_SAVED].last_thread) + wqueue_release_queue(&block->wqueue[COND_FOR_SAVED]); +#endif + } + link_to_file_list(pagecache, block, file, + (my_bool)(block->hash_link ? 1 : 0)); + PCBLOCK_INFO(block); + block->status= error ? PCBLOCK_ERROR : 0; + block->error= (int16) my_errno; +#ifndef DBUG_OFF + block->type= PAGECACHE_EMPTY_PAGE; + if (error) + my_debug_put_break_here(); +#endif + block->hash_link= hash_link; + page_status= PAGE_TO_BE_READ; + DBUG_PRINT("info", ("page to be read set for page 0x%lx", + (ulong)block)); + + KEYCACHE_DBUG_ASSERT(block->hash_link->block == block); + KEYCACHE_DBUG_ASSERT(hash_link->block->hash_link == hash_link); + } + else + { + /* This is for secondary requests for a new page only */ + KEYCACHE_DBUG_PRINT("find_block", + ("block->hash_link: %p hash_link: %p " + "block->status: %u", block->hash_link, + hash_link, block->status )); + page_status= (((block->hash_link == hash_link) && + (block->status & PCBLOCK_READ)) ? + PAGE_READ : PAGE_WAIT_TO_BE_READ); + } + } + } + else + { + if (reg_req) + reg_requests(pagecache, block, 1); + KEYCACHE_DBUG_PRINT("find_block", + ("block->hash_link: %p hash_link: %p " + "block->status: %u", block->hash_link, + hash_link, block->status )); + page_status= (((block->hash_link == hash_link) && + (block->status & PCBLOCK_READ)) ? + PAGE_READ : PAGE_WAIT_TO_BE_READ); + } + } + + KEYCACHE_DBUG_ASSERT(page_status != -1); + *page_st= page_status; + DBUG_PRINT("info", + ("block: 0x%lx fd: %u pos: %lu block->status: %u page_status: %u", + (ulong) block, (uint) file->file, + (ulong) pageno, block->status, (uint) page_status)); + KEYCACHE_DBUG_PRINT("find_block", + ("block: 0x%lx fd: %d pos: %lu block->status: %u page_status: %d", + (ulong) block, + file->file, (ulong) pageno, block->status, + page_status)); + +#if !defined(DBUG_OFF) && defined(EXTRA_DEBUG) + DBUG_EXECUTE("check_pagecache", + test_key_cache(pagecache, "end of find_block",0);); +#endif + KEYCACHE_THREAD_TRACE("find_block:end"); + DBUG_RETURN(block); +} + + +static void add_pin(PAGECACHE_BLOCK_LINK *block) +{ + DBUG_ENTER("add_pin"); + DBUG_PRINT("enter", ("block: 0x%lx pins: %u", + (ulong) block, + block->pins)); + PCBLOCK_INFO(block); + block->pins++; +#ifndef DBUG_OFF + { + PAGECACHE_PIN_INFO *info= + (PAGECACHE_PIN_INFO *)my_malloc(sizeof(PAGECACHE_PIN_INFO), MYF(0)); + info->thread= my_thread_var; + info_link(&block->pin_list, info); + } +#endif + DBUG_VOID_RETURN; +} + +static void remove_pin(PAGECACHE_BLOCK_LINK *block, my_bool any +#ifdef DBUG_OFF + __attribute__((unused)) +#endif + ) +{ + DBUG_ENTER("remove_pin"); + DBUG_PRINT("enter", ("block: 0x%lx pins: %u any: %d", + (ulong) block, + block->pins, (int)any)); + PCBLOCK_INFO(block); + DBUG_ASSERT(block->pins > 0); + block->pins--; +#ifndef DBUG_OFF + { + PAGECACHE_PIN_INFO *info= info_find(block->pin_list, my_thread_var, any); + DBUG_ASSERT(info != 0); + info_unlink(info); + my_free(info, MYF(0)); + } +#endif + DBUG_VOID_RETURN; +} +#ifndef DBUG_OFF +static void info_add_lock(PAGECACHE_BLOCK_LINK *block, my_bool wl) +{ + PAGECACHE_LOCK_INFO *info= + (PAGECACHE_LOCK_INFO *)my_malloc(sizeof(PAGECACHE_LOCK_INFO), MYF(0)); + info->thread= my_thread_var; + info->write_lock= wl; + info_link((PAGECACHE_PIN_INFO **)&block->lock_list, + (PAGECACHE_PIN_INFO *)info); +} +static void info_remove_lock(PAGECACHE_BLOCK_LINK *block) +{ + PAGECACHE_LOCK_INFO *info= + (PAGECACHE_LOCK_INFO *)info_find((PAGECACHE_PIN_INFO *)block->lock_list, + my_thread_var, FALSE); + DBUG_ASSERT(info != 0); + info_unlink((PAGECACHE_PIN_INFO *)info); + my_free(info, MYF(0)); +} +static void info_change_lock(PAGECACHE_BLOCK_LINK *block, my_bool wl) +{ + PAGECACHE_LOCK_INFO *info= + (PAGECACHE_LOCK_INFO *)info_find((PAGECACHE_PIN_INFO *)block->lock_list, + my_thread_var, FALSE); + DBUG_ASSERT(info != 0); + DBUG_ASSERT(info->write_lock != wl); + info->write_lock= wl; +} +#else +#define info_add_lock(B,W) +#define info_remove_lock(B) +#define info_change_lock(B,W) +#endif + + +/** + @brief waiting for lock for read and write lock + + @parem pagecache pointer to a page cache data structure + @parem block the block to work with + @param file file of the block when it was locked + @param pageno page number of the block when it was locked + @param lock_type MY_PTHREAD_LOCK_READ or MY_PTHREAD_LOCK_WRITE + + @retval 0 OK + @retval 1 Can't lock this block, need retry +*/ + +static my_bool pagecache_wait_lock(PAGECACHE *pagecache, + PAGECACHE_BLOCK_LINK *block, + PAGECACHE_FILE file, + pgcache_page_no_t pageno, + uint lock_type) +{ + /* Lock failed we will wait */ +#ifdef THREAD + struct st_my_thread_var *thread= my_thread_var; + DBUG_ENTER("pagecache_wait_lock"); + DBUG_PRINT("info", ("fail to lock, waiting... 0x%lx", (ulong)block)); + thread->lock_type= lock_type; + wqueue_add_to_queue(&block->wqueue[COND_FOR_WRLOCK], thread); + dec_counter_for_resize_op(pagecache); + do + { + KEYCACHE_DBUG_PRINT("get_wrlock: wait", + ("suspend thread %ld", thread->id)); + pagecache_pthread_cond_wait(&thread->suspend, + &pagecache->cache_lock); + } + while(thread->next); +#else + DBUG_ASSERT(0); +#endif + PCBLOCK_INFO(block); + if ((block->status & (PCBLOCK_REASSIGNED | PCBLOCK_IN_SWITCH)) || + file.file != block->hash_link->file.file || + pageno != block->hash_link->pageno) + { + DBUG_PRINT("info", ("the block 0x%lx changed => need retry " + "status: %x files %d != %d or pages %lu != %lu", + (ulong)block, block->status, + file.file, block->hash_link->file.file, + (ulong) pageno, (ulong) block->hash_link->pageno)); + DBUG_RETURN(1); + } + DBUG_RETURN(0); +} + +/** + @brief Put on the block write lock + + @parem pagecache pointer to a page cache data structure + @parem block the block to work with + + @note We have loose scheme for locking by the same thread: + * Downgrade to read lock if no other locks are taken + * Our scheme of locking allow for the same thread + - the same kind of lock + - taking read lock if write lock present + - downgrading to read lock if still other place the same + thread keep write lock + * But unlock operation number should be the same to lock operation. + * If we try to get read lock having active write locks we put read + locks to queue, and as soon as write lock(s) gone the read locks + from queue came in force. + * If read lock is unlocked earlier then it came to force it + just removed from the queue + + @retval 0 OK + @retval 1 Can't lock this block, need retry +*/ + +static my_bool get_wrlock(PAGECACHE *pagecache, + PAGECACHE_BLOCK_LINK *block) +{ + PAGECACHE_FILE file= block->hash_link->file; + pgcache_page_no_t pageno= block->hash_link->pageno; + pthread_t locker= pthread_self(); + DBUG_ENTER("get_wrlock"); + DBUG_PRINT("info", ("the block 0x%lx " + "files %d(%d) pages %lu(%lu)", + (ulong) block, + file.file, block->hash_link->file.file, + (ulong) pageno, (ulong) block->hash_link->pageno)); + PCBLOCK_INFO(block); + /* + We assume that the same thread will try write lock on block on which it + has already read lock. + */ + while ((block->wlocks && !pthread_equal(block->write_locker, locker)) || + block->rlocks) + { + /* Lock failed we will wait */ + if (pagecache_wait_lock(pagecache, block, file, pageno, + MY_PTHREAD_LOCK_WRITE)) + DBUG_RETURN(1); + } + /* we are doing it by global cache mutex protection, so it is OK */ + block->wlocks++; + block->write_locker= locker; + DBUG_PRINT("info", ("WR lock set, block 0x%lx", (ulong)block)); + DBUG_RETURN(0); +} + + +/* + @brief Put on the block read lock + + @param pagecache pointer to a page cache data structure + @param block the block to work with + @param user_file Unique handler per handler file. Used to check if + we request many write locks withing the same + statement + + @note see note for get_wrlock(). + + @retvalue 0 OK + @retvalue 1 Can't lock this block, need retry +*/ + +static my_bool get_rdlock(PAGECACHE *pagecache, + PAGECACHE_BLOCK_LINK *block) +{ + PAGECACHE_FILE file= block->hash_link->file; + pgcache_page_no_t pageno= block->hash_link->pageno; + pthread_t locker= pthread_self(); + DBUG_ENTER("get_rdlock"); + DBUG_PRINT("info", ("the block 0x%lx " + "files %d(%d) pages %lu(%lu)", + (ulong) block, + file.file, block->hash_link->file.file, + (ulong) pageno, (ulong) block->hash_link->pageno)); + PCBLOCK_INFO(block); + while (block->wlocks && !pthread_equal(block->write_locker, locker)) + { + /* Lock failed we will wait */ + if (pagecache_wait_lock(pagecache, block, file, pageno, + MY_PTHREAD_LOCK_READ)) + DBUG_RETURN(1); + } + /* we are doing it by global cache mutex protection, so it is OK */ + if (block->wlocks) + { + DBUG_ASSERT(pthread_equal(block->write_locker, locker)); + block->rlocks_queue++; + DBUG_PRINT("info", ("RD lock put into queue, block 0x%lx", (ulong)block)); + } + else + { + block->rlocks++; + DBUG_PRINT("info", ("RD lock set, block 0x%lx", (ulong)block)); + } + DBUG_RETURN(0); +} + + +/* + @brief Remove write lock from the block + + @param pagecache pointer to a page cache data structure + @param block the block to work with + @param read_lock downgrade to read lock + + @note see note for get_wrlock(). +*/ + +static void release_wrlock(PAGECACHE_BLOCK_LINK *block, my_bool read_lock) +{ + DBUG_ENTER("release_wrlock"); + PCBLOCK_INFO(block); + DBUG_ASSERT(block->wlocks > 0); + DBUG_ASSERT(block->rlocks == 0); + DBUG_ASSERT(block->pins > 0); + if (read_lock) + block->rlocks_queue++; + if (block->wlocks == 1) + { + block->rlocks= block->rlocks_queue; + block->rlocks_queue= 0; + } + block->wlocks--; + if (block->wlocks > 0) + DBUG_VOID_RETURN; /* Multiple write locked */ + DBUG_PRINT("info", ("WR lock reset, block 0x%lx", (ulong)block)); +#ifdef THREAD + /* release all threads waiting for read lock or one waiting for write */ + if (block->wqueue[COND_FOR_WRLOCK].last_thread) + wqueue_release_one_locktype_from_queue(&block->wqueue[COND_FOR_WRLOCK]); +#endif + PCBLOCK_INFO(block); + DBUG_VOID_RETURN; +} + +/* + @brief Remove read lock from the block + + @param pagecache pointer to a page cache data structure + @param block the block to work with + + @note see note for get_wrlock(). +*/ + +static void release_rdlock(PAGECACHE_BLOCK_LINK *block) +{ + DBUG_ENTER("release_wrlock"); + PCBLOCK_INFO(block); + if (block->wlocks) + { + DBUG_ASSERT(pthread_equal(block->write_locker, pthread_self())); + DBUG_ASSERT(block->rlocks == 0); + DBUG_ASSERT(block->rlocks_queue > 0); + block->rlocks_queue--; + DBUG_PRINT("info", ("RD lock queue decreased, block 0x%lx", (ulong)block)); + DBUG_VOID_RETURN; + } + DBUG_ASSERT(block->rlocks > 0); + DBUG_ASSERT(block->rlocks_queue == 0); + block->rlocks--; + DBUG_PRINT("info", ("RD lock decreased, block 0x%lx", (ulong)block)); + if (block->rlocks > 0) + DBUG_VOID_RETURN; /* Multiple write locked */ + DBUG_PRINT("info", ("RD lock reset, block 0x%lx", (ulong)block)); +#ifdef THREAD + /* release all threads waiting for read lock or one waiting for write */ + if (block->wqueue[COND_FOR_WRLOCK].last_thread) + wqueue_release_one_locktype_from_queue(&block->wqueue[COND_FOR_WRLOCK]); +#endif + PCBLOCK_INFO(block); + DBUG_VOID_RETURN; +} + +/** + @brief Try to lock/unlock and pin/unpin the block + + @param pagecache pointer to a page cache data structure + @param block the block to work with + @param lock lock change mode + @param pin pinchange mode + @param file File handler requesting pin + @param any allow unpinning block pinned by any thread; possible + only if not locked, see pagecache_unlock_by_link() + + @retval 0 OK + @retval 1 Try to lock the block failed +*/ + +static my_bool make_lock_and_pin(PAGECACHE *pagecache, + PAGECACHE_BLOCK_LINK *block, + enum pagecache_page_lock lock, + enum pagecache_page_pin pin, + my_bool any) +{ + DBUG_ENTER("make_lock_and_pin"); + + DBUG_PRINT("enter", ("block: 0x%lx", (ulong)block)); +#ifndef DBUG_OFF + if (block) + { + DBUG_PRINT("enter", ("block: 0x%lx (%u) wrlocks: %u rdlocks: %u " + "rdlocks_q: %u pins: %u lock: %s pin: %s any %d", + (ulong)block, PCBLOCK_NUMBER(pagecache, block), + block->wlocks, block->rlocks, block->rlocks_queue, + block->pins, + page_cache_page_lock_str[lock], + page_cache_page_pin_str[pin], (int)any)); + PCBLOCK_INFO(block); + } +#endif + + DBUG_ASSERT(!any || + ((lock == PAGECACHE_LOCK_LEFT_UNLOCKED) && + (pin == PAGECACHE_UNPIN))); + + switch (lock) { + case PAGECACHE_LOCK_WRITE: /* free -> write */ + /* Writelock and pin the buffer */ + if (get_wrlock(pagecache, block)) + { + /* Couldn't lock because block changed status => need retry */ + goto retry; + } + + /* The cache is locked so nothing afraid of */ + add_pin(block); + info_add_lock(block, 1); + break; + case PAGECACHE_LOCK_WRITE_TO_READ: /* write -> read */ + case PAGECACHE_LOCK_WRITE_UNLOCK: /* write -> free */ + /* Removes write lock and puts read lock */ + release_wrlock(block, lock == PAGECACHE_LOCK_WRITE_TO_READ); + /* fall through */ + case PAGECACHE_LOCK_READ_UNLOCK: /* read -> free */ + if (lock == PAGECACHE_LOCK_READ_UNLOCK) + release_rdlock(block); + /* fall through */ + case PAGECACHE_LOCK_LEFT_READLOCKED: /* read -> read */ + if (pin == PAGECACHE_UNPIN) + { + remove_pin(block, FALSE); + } + if (lock == PAGECACHE_LOCK_WRITE_TO_READ) + { + info_change_lock(block, 0); + } + else if (lock == PAGECACHE_LOCK_WRITE_UNLOCK || + lock == PAGECACHE_LOCK_READ_UNLOCK) + { + info_remove_lock(block); + } + break; + case PAGECACHE_LOCK_READ: /* free -> read */ + if (get_rdlock(pagecache, block)) + { + /* Couldn't lock because block changed status => need retry */ + goto retry; + } + + if (pin == PAGECACHE_PIN) + { + /* The cache is locked so nothing afraid off */ + add_pin(block); + } + info_add_lock(block, 0); + break; + case PAGECACHE_LOCK_LEFT_UNLOCKED: /* free -> free */ + if (pin == PAGECACHE_UNPIN) + { + remove_pin(block, any); + } + /* fall through */ + case PAGECACHE_LOCK_LEFT_WRITELOCKED: /* write -> write */ + break; /* do nothing */ + default: + DBUG_ASSERT(0); /* Never should happened */ + } + +#ifndef DBUG_OFF + if (block) + PCBLOCK_INFO(block); +#endif + DBUG_RETURN(0); +retry: + DBUG_PRINT("INFO", ("Retry block 0x%lx", (ulong)block)); + PCBLOCK_INFO(block); + DBUG_ASSERT(block->hash_link->requests > 0); + block->hash_link->requests--; + PCBLOCK_INFO(block); + DBUG_RETURN(1); + +} + + +/* + Read into a key cache block buffer from disk. + + SYNOPSIS + + read_block() + pagecache pointer to a page cache data structure + block block to which buffer the data is to be read + primary <-> the current thread will read the data + + RETURN VALUE + None + + NOTES. + The function either reads a page data from file to the block buffer, + or waits until another thread reads it. What page to read is determined + by a block parameter - reference to a hash link for this page. + If an error occurs THE PCBLOCK_ERROR bit is set in the block status. + + On entry cache_lock is locked +*/ + +static void read_block(PAGECACHE *pagecache, + PAGECACHE_BLOCK_LINK *block, + my_bool primary) +{ + + DBUG_ENTER("read_block"); + DBUG_PRINT("enter", ("read block: 0x%lx primary: %d", + (ulong)block, primary)); + if (primary) + { + size_t error; + /* + This code is executed only by threads + that submitted primary requests + */ + + pagecache->global_cache_read++; + /* Page is not in buffer yet, is to be read from disk */ + pagecache_pthread_mutex_unlock(&pagecache->cache_lock); + /* + Here other threads may step in and register as secondary readers. + They will register in block->wqueue[COND_FOR_REQUESTED]. + */ + error= pagecache_fread(pagecache, &block->hash_link->file, + block->buffer, + block->hash_link->pageno, + pagecache->readwrite_flags); + pagecache_pthread_mutex_lock(&pagecache->cache_lock); + if (error) + { + block->status|= PCBLOCK_ERROR; + block->error= (int16) my_errno; + my_debug_put_break_here(); + } + else + { + block->status|= PCBLOCK_READ; + if ((*block->hash_link->file.read_callback)(block->buffer, + block->hash_link->pageno, + block->hash_link-> + file.callback_data)) + { + DBUG_PRINT("error", ("read callback problem")); + block->status|= PCBLOCK_ERROR; + block->error= (int16) my_errno; + my_debug_put_break_here(); + } + } + DBUG_PRINT("read_block", + ("primary request: new page in cache")); + /* Signal that all pending requests for this page now can be processed */ +#ifdef THREAD + if (block->wqueue[COND_FOR_REQUESTED].last_thread) + wqueue_release_queue(&block->wqueue[COND_FOR_REQUESTED]); +#endif + } + else + { + /* + This code is executed only by threads + that submitted secondary requests + */ + +#ifdef THREAD + struct st_my_thread_var *thread= my_thread_var; + /* Put the request into a queue and wait until it can be processed */ + wqueue_add_to_queue(&block->wqueue[COND_FOR_REQUESTED], thread); + do + { + DBUG_PRINT("read_block: wait", + ("suspend thread %ld", thread->id)); + pagecache_pthread_cond_wait(&thread->suspend, + &pagecache->cache_lock); + } + while (thread->next); +#else + KEYCACHE_DBUG_ASSERT(0); + /* No parallel requests in single-threaded case */ +#endif + DBUG_PRINT("read_block", + ("secondary request: new page in cache")); + } + DBUG_VOID_RETURN; +} + + +/** + @brief Set LSN on the page to the given one if the given LSN is bigger + + @param pagecache pointer to a page cache data structure + @param lsn LSN to set + @param block block to check and set +*/ + +static void check_and_set_lsn(PAGECACHE *pagecache, + LSN lsn, PAGECACHE_BLOCK_LINK *block) +{ + LSN old; + DBUG_ENTER("check_and_set_lsn"); + /* + In recovery, we can _ma_unpin_all_pages() to put a LSN on page, though + page would be PAGECACHE_PLAIN_PAGE (transactionality temporarily disabled + to not log REDOs). + */ + DBUG_ASSERT((block->type == PAGECACHE_LSN_PAGE) || maria_in_recovery); + old= lsn_korr(block->buffer); + DBUG_PRINT("info", ("old lsn: (%lu, 0x%lx) new lsn: (%lu, 0x%lx)", + LSN_IN_PARTS(old), LSN_IN_PARTS(lsn))); + if (cmp_translog_addr(lsn, old) > 0) + { + + DBUG_ASSERT(block->type != PAGECACHE_READ_UNKNOWN_PAGE); + lsn_store(block->buffer, lsn); + /* we stored LSN in page so we dirtied it */ + if (!(block->status & PCBLOCK_CHANGED)) + link_to_changed_list(pagecache, block); + } + DBUG_VOID_RETURN; +} + + +/** + @brief Unlock/unpin page and put LSN stamp if it need + + @param pagecache pointer to a page cache data structure + @pagam file handler for the file for the block of data to be read + @param pageno number of the block of data in the file + @param lock lock change + @param pin pin page + @param first_REDO_LSN_for_page do not set it if it is zero + @param lsn if it is not LSN_IMPOSSIBLE (0) and it + is bigger then LSN on the page it will be written on + the page + @param was_changed should be true if the page was write locked with + direct link giving and the page was changed + + @note + Pininig uses requests registration mechanism it works following way: + | beginnig | ending | + | of func. | of func. | + ----------------------------+-------------+---------------+ + PAGECACHE_PIN_LEFT_PINNED | - | - | + PAGECACHE_PIN_LEFT_UNPINNED | reg request | unreg request | + PAGECACHE_PIN | reg request | - | + PAGECACHE_UNPIN | - | unreg request | + + +*/ + +void pagecache_unlock(PAGECACHE *pagecache, + PAGECACHE_FILE *file, + pgcache_page_no_t pageno, + enum pagecache_page_lock lock, + enum pagecache_page_pin pin, + LSN first_REDO_LSN_for_page, + LSN lsn, my_bool was_changed) +{ + PAGECACHE_BLOCK_LINK *block; + int page_st; + DBUG_ENTER("pagecache_unlock"); + DBUG_PRINT("enter", ("fd: %u page: %lu %s %s", + (uint) file->file, (ulong) pageno, + page_cache_page_lock_str[lock], + page_cache_page_pin_str[pin])); + /* we do not allow any lock/pin increasing here */ + DBUG_ASSERT(pin != PAGECACHE_PIN); + DBUG_ASSERT(lock != PAGECACHE_LOCK_READ); + DBUG_ASSERT(lock != PAGECACHE_LOCK_WRITE); + + pagecache_pthread_mutex_lock(&pagecache->cache_lock); + /* + As soon as we keep lock cache can be used, and we have lock because want + to unlock. + */ + DBUG_ASSERT(pagecache->can_be_used); + + inc_counter_for_resize_op(pagecache); + /* See NOTE for pagecache_unlock about registering requests */ + block= find_block(pagecache, file, pageno, 0, 0, + pin == PAGECACHE_PIN_LEFT_UNPINNED, &page_st); + PCBLOCK_INFO(block); + DBUG_ASSERT(block != 0 && page_st == PAGE_READ); + if (first_REDO_LSN_for_page) + { + DBUG_ASSERT(lock == PAGECACHE_LOCK_WRITE_UNLOCK); + DBUG_ASSERT(pin == PAGECACHE_UNPIN); + pagecache_set_block_rec_lsn(block, first_REDO_LSN_for_page); + } + if (lsn != LSN_IMPOSSIBLE) + check_and_set_lsn(pagecache, lsn, block); + + /* if we lock for write we must link the block to changed blocks */ + DBUG_ASSERT((block->status & PCBLOCK_DIRECT_W) == 0 || + (lock == PAGECACHE_LOCK_WRITE_UNLOCK || + lock == PAGECACHE_LOCK_WRITE_TO_READ || + lock == PAGECACHE_LOCK_LEFT_WRITELOCKED)); + /* + if was_changed then status should be PCBLOCK_DIRECT_W or marked + as dirty + */ + DBUG_ASSERT(!was_changed || (block->status & PCBLOCK_DIRECT_W) || + (block->status & PCBLOCK_CHANGED)); + if ((block->status & PCBLOCK_DIRECT_W) && + (lock == PAGECACHE_LOCK_WRITE_UNLOCK || + lock == PAGECACHE_LOCK_WRITE_TO_READ)) + { + if (!(block->status & PCBLOCK_CHANGED) && was_changed) + link_to_changed_list(pagecache, block); + block->status&= ~PCBLOCK_DIRECT_W; + DBUG_PRINT("info", ("Drop PCBLOCK_DIRECT_W for block: 0x%lx", + (ulong) block)); + } + + if (make_lock_and_pin(pagecache, block, lock, pin, FALSE)) + { + DBUG_ASSERT(0); /* should not happend */ + } + + remove_reader(block); + /* + Link the block into the LRU chain if it's the last submitted request + for the block and block will not be pinned. + See NOTE for pagecache_unlock about registering requests. + */ + if (pin != PAGECACHE_PIN_LEFT_PINNED) + unreg_request(pagecache, block, 1); + + dec_counter_for_resize_op(pagecache); + + pagecache_pthread_mutex_unlock(&pagecache->cache_lock); + + DBUG_VOID_RETURN; +} + + +/* + Unpin page + + SYNOPSIS + pagecache_unpin() + pagecache pointer to a page cache data structure + file handler for the file for the block of data to be read + pageno number of the block of data in the file + lsn if it is not LSN_IMPOSSIBLE (0) and it + is bigger then LSN on the page it will be written on + the page +*/ + +void pagecache_unpin(PAGECACHE *pagecache, + PAGECACHE_FILE *file, + pgcache_page_no_t pageno, + LSN lsn) +{ + PAGECACHE_BLOCK_LINK *block; + int page_st; + DBUG_ENTER("pagecache_unpin"); + DBUG_PRINT("enter", ("fd: %u page: %lu", + (uint) file->file, (ulong) pageno)); + pagecache_pthread_mutex_lock(&pagecache->cache_lock); + /* + As soon as we keep lock cache can be used, and we have lock bacause want + aunlock. + */ + DBUG_ASSERT(pagecache->can_be_used); + + inc_counter_for_resize_op(pagecache); + /* See NOTE for pagecache_unlock about registering requests */ + block= find_block(pagecache, file, pageno, 0, 0, 0, &page_st); + DBUG_ASSERT(block != 0); + DBUG_ASSERT(page_st == PAGE_READ); + /* we can't unpin such page without unlock */ + DBUG_ASSERT((block->status & PCBLOCK_DIRECT_W) == 0); + + if (lsn != LSN_IMPOSSIBLE) + check_and_set_lsn(pagecache, lsn, block); + + /* + we can just unpin only with keeping read lock because: + a) we can't pin without any lock + b) we can't unpin keeping write lock + */ + if (make_lock_and_pin(pagecache, block, + PAGECACHE_LOCK_LEFT_READLOCKED, + PAGECACHE_UNPIN, FALSE)) + DBUG_ASSERT(0); /* should not happend */ + + remove_reader(block); + /* + Link the block into the LRU chain if it's the last submitted request + for the block and block will not be pinned. + See NOTE for pagecache_unlock about registering requests + */ + unreg_request(pagecache, block, 1); + + dec_counter_for_resize_op(pagecache); + + pagecache_pthread_mutex_unlock(&pagecache->cache_lock); + + DBUG_VOID_RETURN; +} + + +/** + @brief Unlock/unpin page and put LSN stamp if it need + (uses direct block/page pointer) + + @param pagecache pointer to a page cache data structure + @param link direct link to page (returned by read or write) + @param lock lock change + @param pin pin page + @param first_REDO_LSN_for_page do not set it if it is LSN_IMPOSSIBLE (0) + @param lsn if it is not LSN_IMPOSSIBLE and it is bigger then + LSN on the page it will be written on the page + @param was_changed should be true if the page was write locked with + direct link giving and the page was changed + @param any allow unpinning block pinned by any thread; possible + only if not locked + + @note 'any' is a hack so that _ma_bitmap_unpin_all() is allowed to unpin + non-locked bitmap pages pinned by other threads. Because it always uses + PAGECACHE_LOCK_LEFT_UNLOCKED and PAGECACHE_UNPIN + (see write_changed_bitmap()), the hack is limited to these conditions. +*/ + +void pagecache_unlock_by_link(PAGECACHE *pagecache, + PAGECACHE_BLOCK_LINK *block, + enum pagecache_page_lock lock, + enum pagecache_page_pin pin, + LSN first_REDO_LSN_for_page, + LSN lsn, my_bool was_changed, + my_bool any) +{ + DBUG_ENTER("pagecache_unlock_by_link"); + DBUG_PRINT("enter", ("block: 0x%lx fd: %u page: %lu changed: %d %s %s", + (ulong) block, + (uint) block->hash_link->file.file, + (ulong) block->hash_link->pageno, was_changed, + page_cache_page_lock_str[lock], + page_cache_page_pin_str[pin])); + /* + We do not allow any lock/pin increasing here and page can't be + unpinned because we use direct link. + */ + DBUG_ASSERT(pin != PAGECACHE_PIN); + DBUG_ASSERT(pin != PAGECACHE_PIN_LEFT_UNPINNED); + DBUG_ASSERT(lock != PAGECACHE_LOCK_READ); + DBUG_ASSERT(lock != PAGECACHE_LOCK_WRITE); + pagecache_pthread_mutex_lock(&pagecache->cache_lock); + if (pin == PAGECACHE_PIN_LEFT_UNPINNED && + lock == PAGECACHE_LOCK_READ_UNLOCK) + { + if (make_lock_and_pin(pagecache, block, lock, pin, FALSE)) + DBUG_ASSERT(0); /* should not happend */ + pagecache_pthread_mutex_unlock(&pagecache->cache_lock); + DBUG_VOID_RETURN; + } + + /* + As soon as we keep lock cache can be used, and we have lock because want + unlock. + */ + DBUG_ASSERT(pagecache->can_be_used); + + inc_counter_for_resize_op(pagecache); + if (was_changed) + { + if (first_REDO_LSN_for_page != LSN_IMPOSSIBLE) + { + /* + LOCK_READ_UNLOCK is ok here as the page may have first locked + with WRITE lock that was temporarly converted to READ lock before + it's unpinned + */ + DBUG_ASSERT(lock == PAGECACHE_LOCK_WRITE_UNLOCK || + lock == PAGECACHE_LOCK_READ_UNLOCK); + DBUG_ASSERT(pin == PAGECACHE_UNPIN); + pagecache_set_block_rec_lsn(block, first_REDO_LSN_for_page); + } + if (lsn != LSN_IMPOSSIBLE) + check_and_set_lsn(pagecache, lsn, block); + /* + Reset error flag. Mark also that page is active; This may not have + been the case if there was an error reading the page + */ + block->status= (block->status & ~PCBLOCK_ERROR) | PCBLOCK_READ; + } + + /* if we lock for write we must link the block to changed blocks */ + DBUG_ASSERT((block->status & PCBLOCK_DIRECT_W) == 0 || + (lock == PAGECACHE_LOCK_WRITE_UNLOCK || + lock == PAGECACHE_LOCK_WRITE_TO_READ || + lock == PAGECACHE_LOCK_LEFT_WRITELOCKED)); + /* + If was_changed then status should be PCBLOCK_DIRECT_W or marked + as dirty + */ + DBUG_ASSERT(!was_changed || (block->status & PCBLOCK_DIRECT_W) || + (block->status & PCBLOCK_CHANGED)); + if ((block->status & PCBLOCK_DIRECT_W) && + (lock == PAGECACHE_LOCK_WRITE_UNLOCK || + lock == PAGECACHE_LOCK_WRITE_TO_READ)) + { + if (!(block->status & PCBLOCK_CHANGED) && was_changed) + link_to_changed_list(pagecache, block); + block->status&= ~PCBLOCK_DIRECT_W; + DBUG_PRINT("info", ("Drop PCBLOCK_DIRECT_W for block: 0x%lx", + (ulong) block)); + } + + if (make_lock_and_pin(pagecache, block, lock, pin, any)) + DBUG_ASSERT(0); /* should not happend */ + + /* + Link the block into the LRU chain if it's the last submitted request + for the block and block will not be pinned. + See NOTE for pagecache_unlock about registering requests. + */ + if (pin != PAGECACHE_PIN_LEFT_PINNED) + unreg_request(pagecache, block, 1); + + dec_counter_for_resize_op(pagecache); + + pagecache_pthread_mutex_unlock(&pagecache->cache_lock); + + DBUG_VOID_RETURN; +} + + +/* + Unpin page + (uses direct block/page pointer) + + SYNOPSIS + pagecache_unpin_by_link() + pagecache pointer to a page cache data structure + link direct link to page (returned by read or write) + lsn if it is not LSN_IMPOSSIBLE (0) and it + is bigger then LSN on the page it will be written on + the page +*/ + +void pagecache_unpin_by_link(PAGECACHE *pagecache, + PAGECACHE_BLOCK_LINK *block, + LSN lsn) +{ + DBUG_ENTER("pagecache_unpin_by_link"); + DBUG_PRINT("enter", ("block: 0x%lx fd: %u page: %lu", + (ulong) block, + (uint) block->hash_link->file.file, + (ulong) block->hash_link->pageno)); + + pagecache_pthread_mutex_lock(&pagecache->cache_lock); + /* + As soon as we keep lock cache can be used, and we have lock because want + unlock. + */ + DBUG_ASSERT(pagecache->can_be_used); + /* we can't unpin such page without unlock */ + DBUG_ASSERT((block->status & PCBLOCK_DIRECT_W) == 0); + + inc_counter_for_resize_op(pagecache); + + if (lsn != LSN_IMPOSSIBLE) + check_and_set_lsn(pagecache, lsn, block); + + /* + We can just unpin only with keeping read lock because: + a) we can't pin without any lock + b) we can't unpin keeping write lock + */ + if (make_lock_and_pin(pagecache, block, + PAGECACHE_LOCK_LEFT_READLOCKED, + PAGECACHE_UNPIN, FALSE)) + DBUG_ASSERT(0); /* should not happend */ + + /* + Link the block into the LRU chain if it's the last submitted request + for the block and block will not be pinned. + See NOTE for pagecache_unlock about registering requests. + */ + unreg_request(pagecache, block, 1); + + dec_counter_for_resize_op(pagecache); + + pagecache_pthread_mutex_unlock(&pagecache->cache_lock); + + DBUG_VOID_RETURN; +} + +/* description of how to change lock before and after read/write */ +struct rw_lock_change +{ + my_bool need_lock_change; /* need changing of lock at the end */ + enum pagecache_page_lock new_lock; /* lock at the beginning */ + enum pagecache_page_lock unlock_lock; /* lock at the end */ +}; + +/* description of how to change pin before and after read/write */ +struct rw_pin_change +{ + enum pagecache_page_pin new_pin; /* pin status at the beginning */ + enum pagecache_page_pin unlock_pin; /* pin status at the end */ +}; + +/** + Depending on the lock which the user wants in pagecache_read(), we + need to acquire a first type of lock at start of pagecache_read(), and + downgrade it to a second type of lock at end. For example, if user + asked for no lock (PAGECACHE_LOCK_LEFT_UNLOCKED) this translates into + taking first a read lock PAGECACHE_LOCK_READ (to rightfully block on + existing write locks) then read then unlock the lock i.e. change lock + to PAGECACHE_LOCK_READ_UNLOCK (the "1" below tells that a change is + needed). +*/ + +static struct rw_lock_change lock_to_read[8]= +{ + { /*PAGECACHE_LOCK_LEFT_UNLOCKED*/ + 1, + PAGECACHE_LOCK_READ, PAGECACHE_LOCK_READ_UNLOCK + }, + { /*PAGECACHE_LOCK_LEFT_READLOCKED*/ + 0, + PAGECACHE_LOCK_LEFT_READLOCKED, PAGECACHE_LOCK_LEFT_READLOCKED + }, + { /*PAGECACHE_LOCK_LEFT_WRITELOCKED*/ + 0, + PAGECACHE_LOCK_LEFT_WRITELOCKED, PAGECACHE_LOCK_LEFT_WRITELOCKED + }, + { /*PAGECACHE_LOCK_READ*/ + 1, + PAGECACHE_LOCK_READ, PAGECACHE_LOCK_LEFT_READLOCKED + }, + { /*PAGECACHE_LOCK_WRITE*/ + 1, + PAGECACHE_LOCK_WRITE, PAGECACHE_LOCK_LEFT_WRITELOCKED + }, + { /*PAGECACHE_LOCK_READ_UNLOCK*/ + 1, + PAGECACHE_LOCK_LEFT_READLOCKED, PAGECACHE_LOCK_READ_UNLOCK + }, + { /*PAGECACHE_LOCK_WRITE_UNLOCK*/ + 1, + PAGECACHE_LOCK_LEFT_WRITELOCKED, PAGECACHE_LOCK_WRITE_UNLOCK + }, + { /*PAGECACHE_LOCK_WRITE_TO_READ*/ + 1, + PAGECACHE_LOCK_LEFT_WRITELOCKED, PAGECACHE_LOCK_WRITE_TO_READ + } +}; + +/** + Two sets of pin modes (every as for lock upper but for pinning). The + difference between sets if whether we are going to provide caller with + reference on the block or not +*/ + +static struct rw_pin_change lock_to_pin[2][8]= +{ + { + { /*PAGECACHE_LOCK_LEFT_UNLOCKED*/ + PAGECACHE_PIN_LEFT_UNPINNED, + PAGECACHE_PIN_LEFT_UNPINNED + }, + { /*PAGECACHE_LOCK_LEFT_READLOCKED*/ + PAGECACHE_PIN_LEFT_UNPINNED, + PAGECACHE_PIN_LEFT_UNPINNED, + }, + { /*PAGECACHE_LOCK_LEFT_WRITELOCKED*/ + PAGECACHE_PIN_LEFT_PINNED, + PAGECACHE_PIN_LEFT_PINNED + }, + { /*PAGECACHE_LOCK_READ*/ + PAGECACHE_PIN_LEFT_UNPINNED, + PAGECACHE_PIN_LEFT_UNPINNED + }, + { /*PAGECACHE_LOCK_WRITE*/ + PAGECACHE_PIN, + PAGECACHE_PIN_LEFT_PINNED + }, + { /*PAGECACHE_LOCK_READ_UNLOCK*/ + PAGECACHE_PIN_LEFT_UNPINNED, + PAGECACHE_PIN_LEFT_UNPINNED + }, + { /*PAGECACHE_LOCK_WRITE_UNLOCK*/ + PAGECACHE_PIN_LEFT_PINNED, + PAGECACHE_UNPIN + }, + { /*PAGECACHE_LOCK_WRITE_TO_READ*/ + PAGECACHE_PIN_LEFT_PINNED, + PAGECACHE_UNPIN + } + }, + { + { /*PAGECACHE_LOCK_LEFT_UNLOCKED*/ + PAGECACHE_PIN_LEFT_UNPINNED, + PAGECACHE_PIN_LEFT_UNPINNED + }, + { /*PAGECACHE_LOCK_LEFT_READLOCKED*/ + PAGECACHE_PIN_LEFT_UNPINNED, + PAGECACHE_PIN_LEFT_UNPINNED, + }, + { /*PAGECACHE_LOCK_LEFT_WRITELOCKED*/ + PAGECACHE_PIN_LEFT_PINNED, + PAGECACHE_PIN_LEFT_PINNED + }, + { /*PAGECACHE_LOCK_READ*/ + PAGECACHE_PIN, + PAGECACHE_PIN_LEFT_PINNED + }, + { /*PAGECACHE_LOCK_WRITE*/ + PAGECACHE_PIN, + PAGECACHE_PIN_LEFT_PINNED + }, + { /*PAGECACHE_LOCK_READ_UNLOCK*/ + PAGECACHE_PIN_LEFT_UNPINNED, + PAGECACHE_PIN_LEFT_UNPINNED + }, + { /*PAGECACHE_LOCK_WRITE_UNLOCK*/ + PAGECACHE_PIN_LEFT_PINNED, + PAGECACHE_UNPIN + }, + { /*PAGECACHE_LOCK_WRITE_TO_READ*/ + PAGECACHE_PIN_LEFT_PINNED, + PAGECACHE_PIN_LEFT_PINNED, + } + } +}; + + +/* + @brief Read a block of data from a cached file into a buffer; + + @param pagecache pointer to a page cache data structure + @param file handler for the file for the block of data to be read + @param pageno number of the block of data in the file + @param level determines the weight of the data + @param buff buffer to where the data must be placed + @param type type of the page + @param lock lock change + @param link link to the page if we pin it + + @return address from where the data is placed if successful, 0 - otherwise. + + @note Pin will be chosen according to lock parameter (see lock_to_pin) + + @note 'buff', if not NULL, must be long-aligned. + + @note If buff==0 then we provide reference on the page so should keep the + page pinned. +*/ + +uchar *pagecache_read(PAGECACHE *pagecache, + PAGECACHE_FILE *file, + pgcache_page_no_t pageno, + uint level, + uchar *buff, + enum pagecache_page_type type, + enum pagecache_page_lock lock, + PAGECACHE_BLOCK_LINK **page_link) +{ + my_bool error= 0; + enum pagecache_page_pin + new_pin= lock_to_pin[buff==0][lock].new_pin, + unlock_pin= lock_to_pin[buff==0][lock].unlock_pin; + PAGECACHE_BLOCK_LINK *fake_link; + my_bool reg_request; +#ifndef DBUG_OFF + char llbuf[22]; + DBUG_ENTER("pagecache_read"); + DBUG_PRINT("enter", ("fd: %u page: %s buffer: 0x%lx level: %u " + "t:%s (%d)%s->%s %s->%s", + (uint) file->file, ullstr(pageno, llbuf), + (ulong) buff, level, + page_cache_page_type_str[type], + lock_to_read[lock].need_lock_change, + page_cache_page_lock_str[lock_to_read[lock].new_lock], + page_cache_page_lock_str[lock_to_read[lock].unlock_lock], + page_cache_page_pin_str[new_pin], + page_cache_page_pin_str[unlock_pin])); + DBUG_ASSERT(buff != 0 || (buff == 0 && (unlock_pin == PAGECACHE_PIN || + unlock_pin == PAGECACHE_PIN_LEFT_PINNED))); + DBUG_ASSERT(pageno < ((ULL(1)) << 40)); +#endif + + if (!page_link) + page_link= &fake_link; + *page_link= 0; /* Catch errors */ + +restart: + + if (pagecache->can_be_used) + { + /* Key cache is used */ + PAGECACHE_BLOCK_LINK *block; + uint status; + int page_st; + + pagecache_pthread_mutex_lock(&pagecache->cache_lock); + if (!pagecache->can_be_used) + { + pagecache_pthread_mutex_unlock(&pagecache->cache_lock); + goto no_key_cache; + } + + inc_counter_for_resize_op(pagecache); + pagecache->global_cache_r_requests++; + /* See NOTE for pagecache_unlock about registering requests. */ + reg_request= ((new_pin == PAGECACHE_PIN_LEFT_UNPINNED) || + (new_pin == PAGECACHE_PIN)); + block= find_block(pagecache, file, pageno, level, + lock == PAGECACHE_LOCK_WRITE, + reg_request, &page_st); + DBUG_PRINT("info", ("Block type: %s current type %s", + page_cache_page_type_str[block->type], + page_cache_page_type_str[type])); + if (((block->status & PCBLOCK_ERROR) == 0) && (page_st != PAGE_READ)) + { + /* The requested page is to be read into the block buffer */ + read_block(pagecache, block, + (my_bool)(page_st == PAGE_TO_BE_READ)); + DBUG_PRINT("info", ("read is done")); + } + /* + Assert after block is read. Imagine two concurrent SELECTs on same + table (thread1 and 2), which want to pagecache_read() the same + pageno/fileno. Thread1 calls find_block(), decides to evict a dirty + page from LRU; while it's writing this dirty page to disk, it is + pre-empted and thread2 runs its find_block(), gets the block (in + PAGE_TO_BE_READ state). This block is still containing the in-eviction + dirty page so has an its type, which cannot be tested. + So thread2 has to wait for read_block() to finish (when it wakes up in + read_block(), it's woken up by read_block() of thread1, which implies + that block's type was set to EMPTY by thread1 as part of find_block()). + */ + DBUG_ASSERT(block->type == PAGECACHE_EMPTY_PAGE || + block->type == type || + type == PAGECACHE_LSN_PAGE || + type == PAGECACHE_READ_UNKNOWN_PAGE || + block->type == PAGECACHE_READ_UNKNOWN_PAGE); + if (type != PAGECACHE_READ_UNKNOWN_PAGE || + block->type == PAGECACHE_EMPTY_PAGE) + block->type= type; + + if (make_lock_and_pin(pagecache, block, lock_to_read[lock].new_lock, + new_pin, FALSE)) + { + /* + We failed to write lock the block, cache is unlocked, + we will try to get the block again. + */ + if (reg_request) + unreg_request(pagecache, block, 1); + pagecache_pthread_mutex_unlock(&pagecache->cache_lock); + DBUG_PRINT("info", ("restarting...")); + goto restart; + } + + status= block->status; + if (!buff) + { + buff= block->buffer; + /* possibly we will write here (resolved on unlock) */ + if ((lock == PAGECACHE_LOCK_WRITE || + lock == PAGECACHE_LOCK_LEFT_WRITELOCKED) && + !(block->status & PCBLOCK_CHANGED)) + { + block->status|= PCBLOCK_DIRECT_W; + DBUG_PRINT("info", ("Set PCBLOCK_DIRECT_W for block: 0x%lx", + (ulong) block)); + } + } + else + { + if (!(status & PCBLOCK_ERROR)) + { +#if !defined(SERIALIZED_READ_FROM_CACHE) + pagecache_pthread_mutex_unlock(&pagecache->cache_lock); +#endif + + DBUG_ASSERT((pagecache->block_size & 511) == 0); + /* Copy data from the cache buffer */ + bmove512(buff, block->buffer, pagecache->block_size); + +#if !defined(SERIALIZED_READ_FROM_CACHE) + pagecache_pthread_mutex_lock(&pagecache->cache_lock); +#endif + } + else + my_errno= block->error; + } + + remove_reader(block); + if (lock_to_read[lock].need_lock_change) + { + if (make_lock_and_pin(pagecache, block, + lock_to_read[lock].unlock_lock, + unlock_pin, FALSE)) + DBUG_ASSERT(0); + } + /* + Link the block into the LRU chain if it's the last submitted request + for the block and block will not be pinned. + See NOTE for pagecache_unlock about registering requests. + */ + if (unlock_pin == PAGECACHE_PIN_LEFT_UNPINNED || + unlock_pin == PAGECACHE_UNPIN) + unreg_request(pagecache, block, 1); + else + *page_link= block; + + dec_counter_for_resize_op(pagecache); + + pagecache_pthread_mutex_unlock(&pagecache->cache_lock); + + if (status & PCBLOCK_ERROR) + { + DBUG_ASSERT(my_errno != 0); + DBUG_PRINT("error", ("Got error %d when doing page read", my_errno)); + DBUG_RETURN((uchar *) 0); + } + + DBUG_RETURN(buff); + } + +no_key_cache: /* Key cache is not used */ + + /* We can't use mutex here as the key cache may not be initialized */ + pagecache->global_cache_r_requests++; + pagecache->global_cache_read++; + if (pagecache_fread(pagecache, file, buff, pageno, + pagecache->readwrite_flags)) + error= 1; + DBUG_RETURN(error ? (uchar*) 0 : buff); +} + + +/* + @brief Delete page from the buffer (common part for link and file/page) + + @param pagecache pointer to a page cache data structure + @param block direct link to page (returned by read or write) + @param page_link hash link of the block + @param flush flush page if it is dirty + + @retval 0 deleted or was not present at all + @retval 1 error + +*/ + +static my_bool pagecache_delete_internal(PAGECACHE *pagecache, + PAGECACHE_BLOCK_LINK *block, + PAGECACHE_HASH_LINK *page_link, + my_bool flush) +{ + my_bool error= 0; + if (block->status & PCBLOCK_CHANGED) + { + if (flush) + { + /* The block contains a dirty page - push it out of the cache */ + + KEYCACHE_DBUG_PRINT("find_block", ("block is dirty")); + + pagecache_pthread_mutex_unlock(&pagecache->cache_lock); + /* + The call is thread safe because only the current + thread might change the block->hash_link value + */ + DBUG_ASSERT(block->pins == 1); + error= pagecache_fwrite(pagecache, + &block->hash_link->file, + block->buffer, + block->hash_link->pageno, + block->type, + pagecache->readwrite_flags); + pagecache_pthread_mutex_lock(&pagecache->cache_lock); + pagecache->global_cache_write++; + + if (error) + { + block->status|= PCBLOCK_ERROR; + block->error= (int16) my_errno; + my_debug_put_break_here(); + goto err; + } + } + pagecache->blocks_changed--; + pagecache->global_blocks_changed--; + /* + free_block() will change the status and rec_lsn of the block so no + need to change them here. + */ + } + /* Cache is locked, so we can relese page before freeing it */ + if (make_lock_and_pin(pagecache, block, + PAGECACHE_LOCK_WRITE_UNLOCK, + PAGECACHE_UNPIN, FALSE)) + DBUG_ASSERT(0); + DBUG_ASSERT(block->hash_link->requests > 0); + page_link->requests--; + /* See NOTE for pagecache_unlock about registering requests. */ + free_block(pagecache, block); + +err: + dec_counter_for_resize_op(pagecache); + return error; +} + + +/* + @brief Delete page from the buffer by link + + @param pagecache pointer to a page cache data structure + @param link direct link to page (returned by read or write) + @param lock lock change + @param flush flush page if it is dirty + + @retval 0 deleted or was not present at all + @retval 1 error + + @note lock can be only PAGECACHE_LOCK_LEFT_WRITELOCKED (page was + write locked before) or PAGECACHE_LOCK_WRITE (delete will write + lock page before delete) +*/ + +my_bool pagecache_delete_by_link(PAGECACHE *pagecache, + PAGECACHE_BLOCK_LINK *block, + enum pagecache_page_lock lock, + my_bool flush) +{ + my_bool error= 0; + enum pagecache_page_pin pin= PAGECACHE_PIN_LEFT_PINNED; + DBUG_ENTER("pagecache_delete_by_link"); + DBUG_PRINT("enter", ("fd: %d block 0x%lx %s %s", + block->hash_link->file.file, + (ulong) block, + page_cache_page_lock_str[lock], + page_cache_page_pin_str[pin])); + DBUG_ASSERT(lock == PAGECACHE_LOCK_WRITE || + lock == PAGECACHE_LOCK_LEFT_WRITELOCKED); + DBUG_ASSERT(block->pins != 0); /* should be pinned */ + + if (pagecache->can_be_used) + { + pagecache_pthread_mutex_lock(&pagecache->cache_lock); + if (!pagecache->can_be_used) + goto end; + + /* + This block should be pinned (i.e. has not zero request counter) => + Such block can't be chosen for eviction. + */ + DBUG_ASSERT((block->status & + (PCBLOCK_IN_SWITCH | PCBLOCK_REASSIGNED)) == 0); + /* + make_lock_and_pin() can't fail here, because we are keeping pin on the + block and it can't be evicted (which is cause of lock fail and retry) + */ + if (make_lock_and_pin(pagecache, block, lock, pin, FALSE)) + DBUG_ASSERT(0); + + /* + get_present_hash_link() side effect emulation before call + pagecache_delete_internal() + */ + block->hash_link->requests++; + + error= pagecache_delete_internal(pagecache, block, block->hash_link, + flush); +end: + pagecache_pthread_mutex_unlock(&pagecache->cache_lock); + } + + DBUG_RETURN(error); +} + + +/** + @brief Returns "hits" for promotion + + @return "hits" for promotion +*/ + +uint pagecache_pagelevel(PAGECACHE_BLOCK_LINK *block) +{ + return block->hits_left; +} + +/* + @brief Adds "hits" to the page + + @param link direct link to page (returned by read or write) + @param level number of "hits" which we add to the page +*/ + +void pagecache_add_level_by_link(PAGECACHE_BLOCK_LINK *block, + uint level) +{ + DBUG_ASSERT(block->pins != 0); /* should be pinned */ + /* + Operation is just for statistics so it is not really important + if it interfere with other hit increasing => we are doing it without + locking the pagecache. + */ + block->hits_left+= level; +} + +/* + @brief Delete page from the buffer + + @param pagecache pointer to a page cache data structure + @param file handler for the file for the block of data to be read + @param pageno number of the block of data in the file + @param lock lock change + @param flush flush page if it is dirty + + @retval 0 deleted or was not present at all + @retval 1 error + + @note lock can be only PAGECACHE_LOCK_LEFT_WRITELOCKED (page was + write locked before) or PAGECACHE_LOCK_WRITE (delete will write + lock page before delete) +*/ +static enum pagecache_page_pin lock_to_pin_one_phase[8]= +{ + PAGECACHE_PIN_LEFT_UNPINNED /*PAGECACHE_LOCK_LEFT_UNLOCKED*/, + PAGECACHE_PIN_LEFT_UNPINNED /*PAGECACHE_LOCK_LEFT_READLOCKED*/, + PAGECACHE_PIN_LEFT_PINNED /*PAGECACHE_LOCK_LEFT_WRITELOCKED*/, + PAGECACHE_PIN_LEFT_UNPINNED /*PAGECACHE_LOCK_READ*/, + PAGECACHE_PIN /*PAGECACHE_LOCK_WRITE*/, + PAGECACHE_PIN_LEFT_UNPINNED /*PAGECACHE_LOCK_READ_UNLOCK*/, + PAGECACHE_UNPIN /*PAGECACHE_LOCK_WRITE_UNLOCK*/, + PAGECACHE_UNPIN /*PAGECACHE_LOCK_WRITE_TO_READ*/ +}; + +my_bool pagecache_delete(PAGECACHE *pagecache, + PAGECACHE_FILE *file, + pgcache_page_no_t pageno, + enum pagecache_page_lock lock, + my_bool flush) +{ + my_bool error= 0; + enum pagecache_page_pin pin= lock_to_pin_one_phase[lock]; + DBUG_ENTER("pagecache_delete"); + DBUG_PRINT("enter", ("fd: %u page: %lu %s %s", + (uint) file->file, (ulong) pageno, + page_cache_page_lock_str[lock], + page_cache_page_pin_str[pin])); + DBUG_ASSERT(lock == PAGECACHE_LOCK_WRITE || + lock == PAGECACHE_LOCK_LEFT_WRITELOCKED); + DBUG_ASSERT(pin == PAGECACHE_PIN || + pin == PAGECACHE_PIN_LEFT_PINNED); +restart: + + DBUG_ASSERT(pageno < ((ULL(1)) << 40)); + if (pagecache->can_be_used) + { + /* Key cache is used */ + reg1 PAGECACHE_BLOCK_LINK *block; + PAGECACHE_HASH_LINK **unused_start, *page_link; + + pagecache_pthread_mutex_lock(&pagecache->cache_lock); + if (!pagecache->can_be_used) + goto end; + + inc_counter_for_resize_op(pagecache); + page_link= get_present_hash_link(pagecache, file, pageno, &unused_start); + if (!page_link) + { + DBUG_PRINT("info", ("There is no such page in the cache")); + pagecache_pthread_mutex_unlock(&pagecache->cache_lock); + DBUG_RETURN(0); + } + block= page_link->block; + if (block->status & (PCBLOCK_REASSIGNED | PCBLOCK_IN_SWITCH)) + { + DBUG_PRINT("info", ("Block 0x%0lx already is %s", + (ulong) block, + ((block->status & PCBLOCK_REASSIGNED) ? + "reassigned" : "in switch"))); + PCBLOCK_INFO(block); + page_link->requests--; + goto end; + } + /* See NOTE for pagecache_unlock about registering requests. */ + if (pin == PAGECACHE_PIN) + reg_requests(pagecache, block, 1); + DBUG_ASSERT(block != 0); + if (make_lock_and_pin(pagecache, block, lock, pin, FALSE)) + { + /* + We failed to writelock the block, cache is unlocked, and last write + lock is released, we will try to get the block again. + */ + if (pin == PAGECACHE_PIN) + unreg_request(pagecache, block, 1); + pagecache_pthread_mutex_unlock(&pagecache->cache_lock); + DBUG_PRINT("info", ("restarting...")); + goto restart; + } + + /* we can't delete with opened direct link for write */ + DBUG_ASSERT((block->status & PCBLOCK_DIRECT_W) == 0); + + error= pagecache_delete_internal(pagecache, block, page_link, flush); +end: + pagecache_pthread_mutex_unlock(&pagecache->cache_lock); + } + + DBUG_RETURN(error); +} + + +my_bool pagecache_delete_pages(PAGECACHE *pagecache, + PAGECACHE_FILE *file, + pgcache_page_no_t pageno, + uint page_count, + enum pagecache_page_lock lock, + my_bool flush) +{ + pgcache_page_no_t page_end; + DBUG_ENTER("pagecache_delete_pages"); + DBUG_ASSERT(page_count > 0); + + page_end= pageno + page_count; + do + { + if (pagecache_delete(pagecache, file, pageno, + lock, flush)) + DBUG_RETURN(1); + } while (++pageno != page_end); + DBUG_RETURN(0); +} + + +/** + @brief Writes a buffer into a cached file. + + @param pagecache pointer to a page cache data structure + @param file handler for the file to write data to + @param pageno number of the block of data in the file + @param level determines the weight of the data + @param buff buffer with the data + @param type type of the page + @param lock lock change + @param pin pin page + @param write_mode how to write page + @param link link to the page if we pin it + @param first_REDO_LSN_for_page the lsn to set rec_lsn + @param offset offset in the page + @param size size of data + @param validator read page validator + @param validator_data the validator data + + @retval 0 if a success. + @retval 1 Error. +*/ + +static struct rw_lock_change write_lock_change_table[]= +{ + {1, + PAGECACHE_LOCK_WRITE, + PAGECACHE_LOCK_WRITE_UNLOCK} /*PAGECACHE_LOCK_LEFT_UNLOCKED*/, + {0, /*unsupported (we can't write having the block read locked) */ + PAGECACHE_LOCK_LEFT_UNLOCKED, + PAGECACHE_LOCK_LEFT_UNLOCKED} /*PAGECACHE_LOCK_LEFT_READLOCKED*/, + {0, PAGECACHE_LOCK_LEFT_WRITELOCKED, 0} /*PAGECACHE_LOCK_LEFT_WRITELOCKED*/, + {1, + PAGECACHE_LOCK_WRITE, + PAGECACHE_LOCK_WRITE_TO_READ} /*PAGECACHE_LOCK_READ*/, + {0, PAGECACHE_LOCK_WRITE, 0} /*PAGECACHE_LOCK_WRITE*/, + {0, /*unsupported (we can't write having the block read locked) */ + PAGECACHE_LOCK_LEFT_UNLOCKED, + PAGECACHE_LOCK_LEFT_UNLOCKED} /*PAGECACHE_LOCK_READ_UNLOCK*/, + {1, + PAGECACHE_LOCK_LEFT_WRITELOCKED, + PAGECACHE_LOCK_WRITE_UNLOCK } /*PAGECACHE_LOCK_WRITE_UNLOCK*/, + {1, + PAGECACHE_LOCK_LEFT_WRITELOCKED, + PAGECACHE_LOCK_WRITE_TO_READ} /*PAGECACHE_LOCK_WRITE_TO_READ*/ +}; + + +static struct rw_pin_change write_pin_change_table[]= +{ + {PAGECACHE_PIN_LEFT_PINNED, + PAGECACHE_PIN_LEFT_PINNED} /*PAGECACHE_PIN_LEFT_PINNED*/, + {PAGECACHE_PIN, + PAGECACHE_UNPIN} /*PAGECACHE_PIN_LEFT_UNPINNED*/, + {PAGECACHE_PIN, + PAGECACHE_PIN_LEFT_PINNED} /*PAGECACHE_PIN*/, + {PAGECACHE_PIN_LEFT_PINNED, + PAGECACHE_UNPIN} /*PAGECACHE_UNPIN*/ +}; + + +/** + @note 'buff', if not NULL, must be long-aligned. +*/ + +my_bool pagecache_write_part(PAGECACHE *pagecache, + PAGECACHE_FILE *file, + pgcache_page_no_t pageno, + uint level, + uchar *buff, + enum pagecache_page_type type, + enum pagecache_page_lock lock, + enum pagecache_page_pin pin, + enum pagecache_write_mode write_mode, + PAGECACHE_BLOCK_LINK **page_link, + LSN first_REDO_LSN_for_page, + uint offset, uint size) +{ + PAGECACHE_BLOCK_LINK *block= NULL; + PAGECACHE_BLOCK_LINK *fake_link; + my_bool error= 0; + int need_lock_change= write_lock_change_table[lock].need_lock_change; + my_bool reg_request; +#ifndef DBUG_OFF + char llbuf[22]; + DBUG_ENTER("pagecache_write_part"); + DBUG_PRINT("enter", ("fd: %u page: %s level: %u type: %s lock: %s " + "pin: %s mode: %s offset: %u size %u", + (uint) file->file, ullstr(pageno, llbuf), level, + page_cache_page_type_str[type], + page_cache_page_lock_str[lock], + page_cache_page_pin_str[pin], + page_cache_page_write_mode_str[write_mode], + offset, size)); + DBUG_ASSERT(type != PAGECACHE_READ_UNKNOWN_PAGE); + DBUG_ASSERT(lock != PAGECACHE_LOCK_LEFT_READLOCKED); + DBUG_ASSERT(lock != PAGECACHE_LOCK_READ_UNLOCK); + DBUG_ASSERT(offset + size <= pagecache->block_size); + DBUG_ASSERT(pageno < ((ULL(1)) << 40)); +#endif + + if (!page_link) + page_link= &fake_link; + *page_link= 0; + +restart: + +#if !defined(DBUG_OFF) && defined(EXTRA_DEBUG) + DBUG_EXECUTE("check_pagecache", + test_key_cache(pagecache, "start of key_cache_write", 1);); +#endif + + if (pagecache->can_be_used) + { + /* Key cache is used */ + int page_st; + my_bool need_page_ready_signal= FALSE; + + pagecache_pthread_mutex_lock(&pagecache->cache_lock); + if (!pagecache->can_be_used) + { + pagecache_pthread_mutex_unlock(&pagecache->cache_lock); + goto no_key_cache; + } + + inc_counter_for_resize_op(pagecache); + pagecache->global_cache_w_requests++; + /* See NOTE for pagecache_unlock about registering requests. */ + reg_request= ((pin == PAGECACHE_PIN_LEFT_UNPINNED) || + (pin == PAGECACHE_PIN)); + block= find_block(pagecache, file, pageno, level, + TRUE, + reg_request, &page_st); + if (!block) + { + DBUG_ASSERT(write_mode != PAGECACHE_WRITE_DONE); + /* It happens only for requests submitted during resize operation */ + dec_counter_for_resize_op(pagecache); + pagecache_pthread_mutex_unlock(&pagecache->cache_lock); + /* Write to the disk key cache is in resize at the moment*/ + goto no_key_cache; + } + DBUG_PRINT("info", ("page status: %d", page_st)); + if (!(block->status & PCBLOCK_ERROR) && + ((page_st == PAGE_TO_BE_READ && + (offset || size < pagecache->block_size)) || + (page_st == PAGE_WAIT_TO_BE_READ))) + { + /* The requested page is to be read into the block buffer */ + read_block(pagecache, block, + (my_bool)(page_st == PAGE_TO_BE_READ)); + DBUG_PRINT("info", ("read is done")); + } + else if (page_st == PAGE_TO_BE_READ) + { + need_page_ready_signal= TRUE; + } + + DBUG_ASSERT(block->type == PAGECACHE_EMPTY_PAGE || + block->type == PAGECACHE_READ_UNKNOWN_PAGE || + block->type == type || + /* this is for when going to non-trans to trans */ + (block->type == PAGECACHE_PLAIN_PAGE && + type == PAGECACHE_LSN_PAGE)); + block->type= type; + /* we write to the page so it has no sense to keep the flag */ + block->status&= ~PCBLOCK_DIRECT_W; + DBUG_PRINT("info", ("Drop PCBLOCK_DIRECT_W for block: 0x%lx", + (ulong) block)); + + if (make_lock_and_pin(pagecache, block, + write_lock_change_table[lock].new_lock, + (need_lock_change ? + write_pin_change_table[pin].new_pin : + pin), FALSE)) + { + /* + We failed to writelock the block, cache is unlocked, and last write + lock is released, we will try to get the block again. + */ + if (reg_request) + unreg_request(pagecache, block, 1); + pagecache_pthread_mutex_unlock(&pagecache->cache_lock); + DBUG_PRINT("info", ("restarting...")); + goto restart; + } + + if (write_mode == PAGECACHE_WRITE_DONE) + { + if (block->status & PCBLOCK_ERROR) + { + my_debug_put_break_here(); + DBUG_PRINT("warning", ("Writing on page with error")); + } + else + { + /* Copy data from buff */ + if (!(size & 511)) + bmove512(block->buffer + offset, buff, size); + else + memcpy(block->buffer + offset, buff, size); + block->status= PCBLOCK_READ; + /* + The read_callback can change the page content (removing page + protection) so it have to be called + */ + DBUG_PRINT("info", ("read_callback: 0x%lx data: 0x%lx", + (ulong) block->hash_link->file.read_callback, + (ulong) block->hash_link->file.callback_data)); + if ((*block->hash_link->file.read_callback)(block->buffer, + block->hash_link->pageno, + block->hash_link-> + file.callback_data)) + { + DBUG_PRINT("error", ("read callback problem")); + block->status|= PCBLOCK_ERROR; + block->error= (int16) my_errno; + my_debug_put_break_here(); + } + KEYCACHE_DBUG_PRINT("key_cache_insert", + ("Page injection")); +#ifdef THREAD + /* Signal that all pending requests for this now can be processed. */ + if (block->wqueue[COND_FOR_REQUESTED].last_thread) + wqueue_release_queue(&block->wqueue[COND_FOR_REQUESTED]); +#endif + } + } + else + { + if (! (block->status & PCBLOCK_CHANGED)) + link_to_changed_list(pagecache, block); + + if (!(size & 511)) + bmove512(block->buffer + offset, buff, size); + else + memcpy(block->buffer + offset, buff, size); + block->status|= PCBLOCK_READ; + /* Page is correct again if we made a full write in it */ + if (size == pagecache->block_size) + block->status&= ~PCBLOCK_ERROR; + } + +#ifdef THREAD + if (need_page_ready_signal && + block->wqueue[COND_FOR_REQUESTED].last_thread) + wqueue_release_queue(&block->wqueue[COND_FOR_REQUESTED]); +#endif + + if (first_REDO_LSN_for_page) + { + /* single write action of the last write action */ + DBUG_ASSERT(lock == PAGECACHE_LOCK_WRITE_UNLOCK || + lock == PAGECACHE_LOCK_LEFT_UNLOCKED); + DBUG_ASSERT(pin == PAGECACHE_UNPIN || + pin == PAGECACHE_PIN_LEFT_UNPINNED); + pagecache_set_block_rec_lsn(block, first_REDO_LSN_for_page); + } + + if (need_lock_change) + { + /* + We don't set rec_lsn of the block; this is ok as for the + Maria-block-record's pages, we always keep pages pinned here. + */ + if (make_lock_and_pin(pagecache, block, + write_lock_change_table[lock].unlock_lock, + write_pin_change_table[pin].unlock_pin, FALSE)) + DBUG_ASSERT(0); + } + + /* Unregister the request */ + DBUG_ASSERT(block->hash_link->requests > 0); + block->hash_link->requests--; + /* See NOTE for pagecache_unlock about registering requests. */ + if (pin == PAGECACHE_PIN_LEFT_UNPINNED || pin == PAGECACHE_UNPIN) + unreg_request(pagecache, block, 1); + else + *page_link= block; + + if (block->status & PCBLOCK_ERROR) + { + error= 1; + my_debug_put_break_here(); + } + + dec_counter_for_resize_op(pagecache); + + pagecache_pthread_mutex_unlock(&pagecache->cache_lock); + + goto end; + } + +no_key_cache: + /* + We can't by pass the normal page cache operations because need + whole page for calling callbacks & so on. + This branch should not be used for now (but it is fixed as it + should be just to avoid confusing) + */ + DBUG_ASSERT(0); + /* Key cache is not used */ + if (write_mode == PAGECACHE_WRITE_DELAY) + { + /* We can't use mutex here as the key cache may not be initialized */ + pagecache->global_cache_w_requests++; + pagecache->global_cache_write++; + if (offset != 0 || size != pagecache->block_size) + { + uchar *page_buffer= (uchar *) alloca(pagecache->block_size); + + pagecache->global_cache_read++; + if ((error= (pagecache_fread(pagecache, file, + page_buffer, + pageno, + pagecache->readwrite_flags) != 0))) + goto end; + if ((file->read_callback)(page_buffer, pageno, file->callback_data)) + { + DBUG_PRINT("error", ("read callback problem")); + error= 1; + goto end; + } + memcpy((char *)page_buffer + offset, buff, size); + buff= page_buffer; + } + if (pagecache_fwrite(pagecache, file, buff, pageno, type, + pagecache->readwrite_flags)) + error= 1; + } + +end: +#if !defined(DBUG_OFF) && defined(EXTRA_DEBUG) + DBUG_EXECUTE("exec", + test_key_cache(pagecache, "end of key_cache_write", 1);); +#endif + if (block) + PCBLOCK_INFO(block); + else + DBUG_PRINT("info", ("No block")); + DBUG_RETURN(error); +} + + +/* + Free block: remove reference to it from hash table, + remove it from the chain file of dirty/clean blocks + and add it to the free list. +*/ + +static void free_block(PAGECACHE *pagecache, PAGECACHE_BLOCK_LINK *block) +{ + KEYCACHE_THREAD_TRACE("free block"); + KEYCACHE_DBUG_PRINT("free_block", + ("block: %u hash_link 0x%lx", + PCBLOCK_NUMBER(pagecache, block), + (long) block->hash_link)); + if (block->hash_link) + { + /* + While waiting for readers to finish, new readers might request the + block. But since we set block->status|= PCBLOCK_REASSIGNED, they + will wait on block->wqueue[COND_FOR_SAVED]. They must be signalled + later. + */ + block->status|= PCBLOCK_REASSIGNED; + wait_for_readers(pagecache, block); + unlink_hash(pagecache, block->hash_link); + } + + unlink_changed(block); + DBUG_ASSERT(block->wlocks == 0); + DBUG_ASSERT(block->rlocks == 0); + DBUG_ASSERT(block->rlocks_queue == 0); + DBUG_ASSERT(block->pins == 0); + block->status= 0; +#ifndef DBUG_OFF + block->type= PAGECACHE_EMPTY_PAGE; +#endif + block->rec_lsn= LSN_MAX; + KEYCACHE_THREAD_TRACE("free block"); + KEYCACHE_DBUG_PRINT("free_block", + ("block is freed")); + unreg_request(pagecache, block, 0); + block->hash_link= NULL; + + /* Remove the free block from the LRU ring. */ + unlink_block(pagecache, block); + if (block->temperature == PCBLOCK_WARM) + pagecache->warm_blocks--; + block->temperature= PCBLOCK_COLD; + /* Insert the free block in the free list. */ + block->next_used= pagecache->free_block_list; + pagecache->free_block_list= block; + /* Keep track of the number of currently unused blocks. */ + pagecache->blocks_unused++; + +#ifdef THREAD + /* All pending requests for this page must be resubmitted. */ + if (block->wqueue[COND_FOR_SAVED].last_thread) + wqueue_release_queue(&block->wqueue[COND_FOR_SAVED]); +#endif +} + + +static int cmp_sec_link(PAGECACHE_BLOCK_LINK **a, PAGECACHE_BLOCK_LINK **b) +{ + return (((*a)->hash_link->pageno < (*b)->hash_link->pageno) ? -1 : + ((*a)->hash_link->pageno > (*b)->hash_link->pageno) ? 1 : 0); +} + + +/** + @brief Flush a portion of changed blocks to disk, free used blocks + if requested + + @param pagecache This page cache reference. + @param file File which should be flushed + @param cache Beginning of array of the block. + @param end Reference to the block after last in the array. + @param flush_type Type of the flush. + @param first_errno Where to store first errno of the flush. + + + @return Operation status + @retval PCFLUSH_OK OK + @retval PCFLUSH_ERROR There was errors during the flush process. + @retval PCFLUSH_PINNED Pinned blocks was met and skipped. + @retval PCFLUSH_PINNED_AND_ERROR PCFLUSH_ERROR and PCFLUSH_PINNED. +*/ + +static int flush_cached_blocks(PAGECACHE *pagecache, + PAGECACHE_FILE *file, + PAGECACHE_BLOCK_LINK **cache, + PAGECACHE_BLOCK_LINK **end, + enum flush_type type, + int *first_errno) +{ + int rc= PCFLUSH_OK; + my_bool error; + uint count= (uint) (end-cache); + DBUG_ENTER("flush_cached_blocks"); + *first_errno= 0; + + /* Don't lock the cache during the flush */ + pagecache_pthread_mutex_unlock(&pagecache->cache_lock); + /* + As all blocks referred in 'cache' are marked by PCBLOCK_IN_FLUSH + we are guaranteed that no thread will change them + */ + qsort((uchar*) cache, count, sizeof(*cache), (qsort_cmp) cmp_sec_link); + + pagecache_pthread_mutex_lock(&pagecache->cache_lock); + for (; cache != end; cache++) + { + PAGECACHE_BLOCK_LINK *block= *cache; + + if (block->pins) + { + KEYCACHE_DBUG_PRINT("flush_cached_blocks", + ("block: %u (0x%lx) pinned", + PCBLOCK_NUMBER(pagecache, block), (ulong)block)); + DBUG_PRINT("info", ("block: %u (0x%lx) pinned", + PCBLOCK_NUMBER(pagecache, block), (ulong)block)); + PCBLOCK_INFO(block); + /* undo the mark put by flush_pagecache_blocks_int(): */ + block->status&= ~PCBLOCK_IN_FLUSH; + rc|= PCFLUSH_PINNED; + DBUG_PRINT("warning", ("Page pinned")); + unreg_request(pagecache, block, 1); + if (!*first_errno) + *first_errno= HA_ERR_INTERNAL_ERROR; + continue; + } + /* if the block is not pinned then it is not write locked */ + DBUG_ASSERT(block->wlocks == 0); + DBUG_ASSERT(block->pins == 0); + if (make_lock_and_pin(pagecache, block, + PAGECACHE_LOCK_WRITE, PAGECACHE_PIN, FALSE)) + DBUG_ASSERT(0); + DBUG_ASSERT(block->pins == 1); + + KEYCACHE_DBUG_PRINT("flush_cached_blocks", + ("block: %u (0x%lx) to be flushed", + PCBLOCK_NUMBER(pagecache, block), (ulong)block)); + DBUG_PRINT("info", ("block: %u (0x%lx) to be flushed", + PCBLOCK_NUMBER(pagecache, block), (ulong)block)); + PCBLOCK_INFO(block); + pagecache_pthread_mutex_unlock(&pagecache->cache_lock); + DBUG_PRINT("info", ("block: %u (0x%lx) pins: %u", + PCBLOCK_NUMBER(pagecache, block), (ulong)block, + block->pins)); + DBUG_ASSERT(block->pins == 1); + /** + @todo IO If page is contiguous with next page to flush, group flushes + in one single my_pwrite(). + */ + /** + It is important to use block->hash_link->file below and not 'file', as + the first one is right and the second may have different out-of-date + content (see StaleFilePointersInFlush in ma_checkpoint.c). + @todo change argument of functions to be File. + */ + error= pagecache_fwrite(pagecache, &block->hash_link->file, + block->buffer, + block->hash_link->pageno, + block->type, + pagecache->readwrite_flags); + pagecache_pthread_mutex_lock(&pagecache->cache_lock); + + if (make_lock_and_pin(pagecache, block, + PAGECACHE_LOCK_WRITE_UNLOCK, + PAGECACHE_UNPIN, FALSE)) + DBUG_ASSERT(0); + + pagecache->global_cache_write++; + if (error) + { + block->status|= PCBLOCK_ERROR; + block->error= (int16) my_errno; + my_debug_put_break_here(); + if (!*first_errno) + *first_errno= my_errno ? my_errno : -1; + rc|= PCFLUSH_ERROR; + } +#ifdef THREAD + /* + Let to proceed for possible waiting requests to write to the block page. + It might happen only during an operation to resize the key cache. + */ + if (block->wqueue[COND_FOR_SAVED].last_thread) + wqueue_release_queue(&block->wqueue[COND_FOR_SAVED]); +#endif + /* type will never be FLUSH_IGNORE_CHANGED here */ + if (! (type == FLUSH_KEEP || type == FLUSH_KEEP_LAZY || + type == FLUSH_FORCE_WRITE)) + { + pagecache->blocks_changed--; + pagecache->global_blocks_changed--; + free_block(pagecache, block); + } + else + { + block->status&= ~PCBLOCK_IN_FLUSH; + link_to_file_list(pagecache, block, file, 1); + unreg_request(pagecache, block, 1); + } + } + DBUG_RETURN(rc); +} + + +/** + @brief flush all blocks for a file to disk but don't do any mutex locks + + @param pagecache pointer to a pagecache data structure + @param file handler for the file to flush to + @param flush_type type of the flush + @param filter optional function which tells what blocks to flush; + can be non-NULL only if FLUSH_KEEP, FLUSH_KEEP_LAZY + or FLUSH_FORCE_WRITE. + @param filter_arg an argument to pass to 'filter'. Information about + the block will be passed too. + + @note + Flushes all blocks having the same OS file descriptor as 'file->file', so + can flush blocks having '*block->hash_link->file' != '*file'. + + @note + This function doesn't do any mutex locks because it needs to be called + both from flush_pagecache_blocks and flush_all_key_blocks (the later one + does the mutex lock in the resize_pagecache() function). + + @note + This function can cause problems if two threads call it + concurrently on the same file (look for "PageCacheFlushConcurrencyBugs" + in ma_checkpoint.c); to avoid them, it has internal logic to serialize in + this situation. + + @return Operation status + @retval PCFLUSH_OK OK + @retval PCFLUSH_ERROR There was errors during the flush process. + @retval PCFLUSH_PINNED Pinned blocks was met and skipped. + @retval PCFLUSH_PINNED_AND_ERROR PCFLUSH_ERROR and PCFLUSH_PINNED. +*/ + +static int flush_pagecache_blocks_int(PAGECACHE *pagecache, + PAGECACHE_FILE *file, + enum flush_type type, + PAGECACHE_FLUSH_FILTER filter, + void *filter_arg) +{ + PAGECACHE_BLOCK_LINK *cache_buff[FLUSH_CACHE],**cache; + int last_errno= 0; + int rc= PCFLUSH_OK; + DBUG_ENTER("flush_pagecache_blocks_int"); + DBUG_PRINT("enter", + ("fd: %d blocks_used: %lu blocks_changed: %lu type: %d", + file->file, pagecache->blocks_used, pagecache->blocks_changed, + type)); + +#if !defined(DBUG_OFF) && defined(EXTRA_DEBUG) + DBUG_EXECUTE("check_pagecache", + test_key_cache(pagecache, + "start of flush_pagecache_blocks", 0);); +#endif + + cache= cache_buff; + if (pagecache->disk_blocks > 0 && + (!my_disable_flush_pagecache_blocks || + (type != FLUSH_KEEP && type != FLUSH_KEEP_LAZY))) + { + /* + Key cache exists. If my_disable_flush_pagecache_blocks is true it + disables the operation but only FLUSH_KEEP[_LAZY]: other flushes still + need to be allowed: FLUSH_RELEASE has to free blocks, and + FLUSH_FORCE_WRITE is to overrule my_disable_flush_pagecache_blocks. + */ + int error= 0; + uint count= 0; + PAGECACHE_BLOCK_LINK **pos, **end; + PAGECACHE_BLOCK_LINK *first_in_switch= NULL; + PAGECACHE_BLOCK_LINK *block, *next; +#if defined(PAGECACHE_DEBUG) + uint cnt= 0; +#endif + +#ifdef THREAD + struct st_file_in_flush us_flusher, *other_flusher; + us_flusher.file= file->file; + us_flusher.flush_queue.last_thread= NULL; + us_flusher.first_in_switch= FALSE; + while ((other_flusher= (struct st_file_in_flush *) + hash_search(&pagecache->files_in_flush, (uchar *)&file->file, + sizeof(file->file)))) + { + /* + File is in flush already: wait, unless FLUSH_KEEP_LAZY. "Flusher" + means "who can mark PCBLOCK_IN_FLUSH", i.e. caller of + flush_pagecache_blocks_int(). + */ + struct st_my_thread_var *thread; + if (type == FLUSH_KEEP_LAZY) + { + DBUG_PRINT("info",("FLUSH_KEEP_LAZY skips")); + DBUG_RETURN(0); + } + thread= my_thread_var; + wqueue_add_to_queue(&other_flusher->flush_queue, thread); + do + { + KEYCACHE_DBUG_PRINT("flush_pagecache_blocks_int: wait1", + ("suspend thread %ld", thread->id)); + pagecache_pthread_cond_wait(&thread->suspend, + &pagecache->cache_lock); + } + while (thread->next); + } + /* we are the only flusher of this file now */ + while (my_hash_insert(&pagecache->files_in_flush, (uchar *)&us_flusher)) + { + /* + Out of memory, wait for flushers to empty the hash and retry; should + rarely happen. Other threads are flushing the file; when done, they + are going to remove themselves from the hash, and thus memory will + appear again. However, this memory may be stolen by yet another thread + (for a purpose unrelated to page cache), before we retry + hash_insert(). So the loop may run for long. Only if the thread was + killed do we abort the loop, returning 1 (error) which can cause the + table to be marked as corrupted (cf maria_chk_size(), maria_close()) + and thus require a table check. + */ + DBUG_ASSERT(0); + pagecache_pthread_mutex_unlock(&pagecache->cache_lock); + if (my_thread_var->abort) + DBUG_RETURN(1); /* End if aborted by user */ + sleep(10); + pagecache_pthread_mutex_lock(&pagecache->cache_lock); + } +#endif + + if (type != FLUSH_IGNORE_CHANGED) + { + /* + Count how many key blocks we have to cache to be able + to flush all dirty pages with minimum seek moves. + */ + for (block= pagecache->changed_blocks[FILE_HASH(*file)] ; + block; + block= block->next_changed) + { + if (block->hash_link->file.file == file->file) + { + count++; + KEYCACHE_DBUG_ASSERT(count<= pagecache->blocks_used); + } + } + /* Allocate a new buffer only if its bigger than the one we have */ + if (count > FLUSH_CACHE && + !(cache= + (PAGECACHE_BLOCK_LINK**) + my_malloc(sizeof(PAGECACHE_BLOCK_LINK*)*count, MYF(0)))) + { + cache= cache_buff; + count= FLUSH_CACHE; + } + } + + /* Retrieve the blocks and write them to a buffer to be flushed */ +restart: + end= (pos= cache)+count; + for (block= pagecache->changed_blocks[FILE_HASH(*file)] ; + block; + block= next) + { +#if defined(PAGECACHE_DEBUG) + cnt++; + KEYCACHE_DBUG_ASSERT(cnt <= pagecache->blocks_used); +#endif + next= block->next_changed; + if (block->hash_link->file.file != file->file) + continue; + if (filter != NULL) + { + int filter_res= (*filter)(block->type, block->hash_link->pageno, + block->rec_lsn, filter_arg); + DBUG_PRINT("info",("filter returned %d", filter_res)); + if (filter_res == FLUSH_FILTER_SKIP_TRY_NEXT) + continue; + if (filter_res == FLUSH_FILTER_SKIP_ALL) + break; + DBUG_ASSERT(filter_res == FLUSH_FILTER_OK); + } + { + /* + Mark the block with BLOCK_IN_FLUSH in order not to let + other threads to use it for new pages and interfere with + our sequence of flushing dirty file pages + */ + block->status|= PCBLOCK_IN_FLUSH; + + if (! (block->status & PCBLOCK_IN_SWITCH)) + { + /* + We care only for the blocks for which flushing was not + initiated by other threads as a result of page swapping + */ + reg_requests(pagecache, block, 1); + if (type != FLUSH_IGNORE_CHANGED) + { + /* It's not a temporary file */ + if (pos == end) + { + /* + This happens only if there is not enough + memory for the big block + */ + if ((rc|= flush_cached_blocks(pagecache, file, cache, + end, type, &error)) & + (PCFLUSH_ERROR | PCFLUSH_PINNED)) + last_errno=error; + DBUG_PRINT("info", ("restarting...")); + /* + Restart the scan as some other thread might have changed + the changed blocks chain: the blocks that were in switch + state before the flush started have to be excluded + */ + goto restart; + } + *pos++= block; + } + else + { + /* It's a temporary file */ + pagecache->blocks_changed--; + pagecache->global_blocks_changed--; + free_block(pagecache, block); + } + } + else if (type != FLUSH_KEEP_LAZY) + { + /* + Link the block into a list of blocks 'in switch', and then we will + wait for this list to be empty, which means they have been flushed + */ + unlink_changed(block); + link_changed(block, &first_in_switch); + us_flusher.first_in_switch= TRUE; + } + } + } + if (pos != cache) + { + if ((rc|= flush_cached_blocks(pagecache, file, cache, pos, type, + &error)) & + (PCFLUSH_ERROR | PCFLUSH_PINNED)) + last_errno= error; + } + /* Wait until list of blocks in switch is empty */ + while (first_in_switch) + { +#if defined(PAGECACHE_DEBUG) + cnt= 0; +#endif + block= first_in_switch; + { +#ifdef THREAD + struct st_my_thread_var *thread= my_thread_var; + wqueue_add_to_queue(&block->wqueue[COND_FOR_SAVED], thread); + do + { + KEYCACHE_DBUG_PRINT("flush_pagecache_blocks_int: wait2", + ("suspend thread %ld", thread->id)); + pagecache_pthread_cond_wait(&thread->suspend, + &pagecache->cache_lock); + } + while (thread->next); +#else + KEYCACHE_DBUG_ASSERT(0); + /* No parallel requests in single-threaded case */ +#endif + } +#if defined(PAGECACHE_DEBUG) + cnt++; + KEYCACHE_DBUG_ASSERT(cnt <= pagecache->blocks_used); +#endif + } + us_flusher.first_in_switch= FALSE; + /* The following happens very seldom */ + if (! (type == FLUSH_KEEP || type == FLUSH_KEEP_LAZY || + type == FLUSH_FORCE_WRITE)) + { + /* + this code would free all blocks while filter maybe handled only a + few, that is not possible. + */ + DBUG_ASSERT(filter == NULL); +#if defined(PAGECACHE_DEBUG) + cnt=0; +#endif + for (block= pagecache->file_blocks[FILE_HASH(*file)] ; + block; + block= next) + { +#if defined(PAGECACHE_DEBUG) + cnt++; + KEYCACHE_DBUG_ASSERT(cnt <= pagecache->blocks_used); +#endif + next= block->next_changed; + if (block->hash_link->file.file == file->file && + (! (block->status & PCBLOCK_CHANGED) + || type == FLUSH_IGNORE_CHANGED)) + { + reg_requests(pagecache, block, 1); + free_block(pagecache, block); + } + } + } +#ifdef THREAD + /* wake up others waiting to flush this file */ + hash_delete(&pagecache->files_in_flush, (uchar *)&us_flusher); + if (us_flusher.flush_queue.last_thread) + wqueue_release_queue(&us_flusher.flush_queue); +#endif + } + +#ifndef DBUG_OFF + DBUG_EXECUTE("check_pagecache", + test_key_cache(pagecache, "end of flush_pagecache_blocks", 0);); +#endif + if (cache != cache_buff) + my_free(cache, MYF(0)); + if (rc != 0) + { + if (last_errno) + my_errno= last_errno; /* Return first error */ + DBUG_PRINT("error", ("Got error: %d", my_errno)); + } + DBUG_RETURN(rc); +} + + +/** + @brief flush all blocks for a file to disk + + @param pagecache pointer to a pagecache data structure + @param file handler for the file to flush to + @param flush_type type of the flush + @param filter optional function which tells what blocks to flush; + can be non-NULL only if FLUSH_KEEP, FLUSH_KEEP_LAZY + or FLUSH_FORCE_WRITE. + @param filter_arg an argument to pass to 'filter'. Information about + the block will be passed too. + + @return Operation status + @retval PCFLUSH_OK OK + @retval PCFLUSH_ERROR There was errors during the flush process. + @retval PCFLUSH_PINNED Pinned blocks was met and skipped. + @retval PCFLUSH_PINNED_AND_ERROR PCFLUSH_ERROR and PCFLUSH_PINNED. +*/ + +int flush_pagecache_blocks_with_filter(PAGECACHE *pagecache, + PAGECACHE_FILE *file, + enum flush_type type, + PAGECACHE_FLUSH_FILTER filter, + void *filter_arg) +{ + int res; + DBUG_ENTER("flush_pagecache_blocks_with_filter"); + DBUG_PRINT("enter", ("pagecache: 0x%lx", (long) pagecache)); + + if (pagecache->disk_blocks <= 0) + DBUG_RETURN(0); + pagecache_pthread_mutex_lock(&pagecache->cache_lock); + inc_counter_for_resize_op(pagecache); + res= flush_pagecache_blocks_int(pagecache, file, type, filter, filter_arg); + dec_counter_for_resize_op(pagecache); + pagecache_pthread_mutex_unlock(&pagecache->cache_lock); + DBUG_RETURN(res); +} + + +/* + Reset the counters of a key cache. + + SYNOPSIS + reset_pagecache_counters() + name the name of a key cache + pagecache pointer to the pagecache to be reset + + DESCRIPTION + This procedure is used to reset the counters of all currently used key + caches, both the default one and the named ones. + + RETURN + 0 on success (always because it can't fail) +*/ + +int reset_pagecache_counters(const char *name __attribute__((unused)), + PAGECACHE *pagecache) +{ + DBUG_ENTER("reset_pagecache_counters"); + if (!pagecache->inited) + { + DBUG_PRINT("info", ("Key cache %s not initialized.", name)); + DBUG_RETURN(0); + } + DBUG_PRINT("info", ("Resetting counters for key cache %s.", name)); + + pagecache->global_blocks_changed= 0; /* Key_blocks_not_flushed */ + pagecache->global_cache_r_requests= 0; /* Key_read_requests */ + pagecache->global_cache_read= 0; /* Key_reads */ + pagecache->global_cache_w_requests= 0; /* Key_write_requests */ + pagecache->global_cache_write= 0; /* Key_writes */ + DBUG_RETURN(0); +} + + +/** + @brief Allocates a buffer and stores in it some info about all dirty pages + + Does the allocation because the caller cannot know the size itself. + Memory freeing is to be done by the caller (if the "str" member of the + LEX_STRING is not NULL). + Ignores all pages of another type than PAGECACHE_LSN_PAGE, because they + are not interesting for a checkpoint record. + The caller has the intention of doing checkpoints. + + @param pagecache pointer to the page cache + @param[out] str pointer to where the allocated buffer, and + its size, will be put + @param[out] min_rec_lsn pointer to where the minimum rec_lsn of all + relevant dirty pages will be put + @return Operation status + @retval 0 OK + @retval 1 Error +*/ + +my_bool pagecache_collect_changed_blocks_with_lsn(PAGECACHE *pagecache, + LEX_STRING *str, + LSN *min_rec_lsn) +{ + my_bool error= 0; + ulong stored_list_size= 0; + uint file_hash; + char *ptr; + LSN minimum_rec_lsn= LSN_MAX; + DBUG_ENTER("pagecache_collect_changed_blocks_with_LSN"); + + DBUG_ASSERT(NULL == str->str); + /* + We lock the entire cache but will be quick, just reading/writing a few MBs + of memory at most. + */ + pagecache_pthread_mutex_lock(&pagecache->cache_lock); +#ifdef THREAD + for (;;) + { + struct st_file_in_flush *other_flusher; + for (file_hash= 0; + (other_flusher= (struct st_file_in_flush *) + hash_element(&pagecache->files_in_flush, file_hash)) != NULL && + !other_flusher->first_in_switch; + file_hash++) + {} + if (other_flusher == NULL) + break; + /* + other_flusher.first_in_switch is true: some thread is flushing a file + and has removed dirty blocks from changed_blocks[] while they were still + dirty (they were being evicted (=>flushed) by yet another thread, which + may not have flushed the block yet so it may still be dirty). + If Checkpoint proceeds now, it will not see the page. If there is a + crash right after writing the checkpoint record, before the page is + flushed, at recovery the page will be wrongly ignored because it won't + be in the dirty pages list in the checkpoint record. So wait. + */ + { + struct st_my_thread_var *thread= my_thread_var; + wqueue_add_to_queue(&other_flusher->flush_queue, thread); + do + { + KEYCACHE_DBUG_PRINT("pagecache_collect_changed_blocks_with_lsn: wait", + ("suspend thread %ld", thread->id)); + pagecache_pthread_cond_wait(&thread->suspend, + &pagecache->cache_lock); + } + while (thread->next); + } + } +#endif + + /* Count how many dirty pages are interesting */ + for (file_hash= 0; file_hash < PAGECACHE_CHANGED_BLOCKS_HASH; file_hash++) + { + PAGECACHE_BLOCK_LINK *block; + for (block= pagecache->changed_blocks[file_hash] ; + block; + block= block->next_changed) + { + /* + Q: is there something subtle with block->hash_link: can it be NULL? + does it have to be == hash_link->block... ? + */ + DBUG_ASSERT(block->hash_link != NULL); + DBUG_ASSERT(block->status & PCBLOCK_CHANGED); + /* + Note that we don't store bitmap pages, or pages from non-transactional + (like temporary) tables. Don't checkpoint during Recovery which uses + PAGECACHE_PLAIN_PAGE. + */ + if (block->type != PAGECACHE_LSN_PAGE) + continue; /* no need to store it */ + stored_list_size++; + } + } + + compile_time_assert(sizeof(pagecache->blocks) <= 8); + str->length= 8 + /* number of dirty pages */ + (2 + /* table id */ + 1 + /* data or index file */ + 5 + /* pageno */ + LSN_STORE_SIZE /* rec_lsn */ + ) * stored_list_size; + if (NULL == (str->str= my_malloc(str->length, MYF(MY_WME)))) + goto err; + ptr= str->str; + int8store(ptr, (ulonglong)stored_list_size); + ptr+= 8; + DBUG_PRINT("info", ("found %lu dirty pages", stored_list_size)); + if (stored_list_size == 0) + goto end; + for (file_hash= 0; file_hash < PAGECACHE_CHANGED_BLOCKS_HASH; file_hash++) + { + PAGECACHE_BLOCK_LINK *block; + for (block= pagecache->changed_blocks[file_hash] ; + block; + block= block->next_changed) + { + uint16 table_id; + MARIA_SHARE *share; + if (block->type != PAGECACHE_LSN_PAGE) + continue; /* no need to store it in the checkpoint record */ + share= (MARIA_SHARE *)(block->hash_link->file.callback_data); + table_id= share->id; + int2store(ptr, table_id); + ptr+= 2; + ptr[0]= (share->kfile.file == block->hash_link->file.file); + ptr++; + DBUG_ASSERT(block->hash_link->pageno < ((ULL(1)) << 40)); + page_store(ptr, block->hash_link->pageno); + ptr+= PAGE_STORE_SIZE; + lsn_store(ptr, block->rec_lsn); + ptr+= LSN_STORE_SIZE; + if (block->rec_lsn != LSN_MAX) + { + DBUG_ASSERT(LSN_VALID(block->rec_lsn)); + if (cmp_translog_addr(block->rec_lsn, minimum_rec_lsn) < 0) + minimum_rec_lsn= block->rec_lsn; + } /* otherwise, some trn->rec_lsn should hold the correct info */ + } + } +end: + pagecache_pthread_mutex_unlock(&pagecache->cache_lock); + *min_rec_lsn= minimum_rec_lsn; + DBUG_RETURN(error); + +err: + error= 1; + goto end; +} + + +#ifndef DBUG_OFF + +/** + Verifies that a file has no dirty pages. +*/ + +void pagecache_file_no_dirty_page(PAGECACHE *pagecache, PAGECACHE_FILE *file) +{ + File fd= file->file; + PAGECACHE_BLOCK_LINK *block; + for (block= pagecache->changed_blocks[FILE_HASH(*file)]; + block != NULL; + block= block->next_changed) + if (block->hash_link->file.file == fd) + { + DBUG_PRINT("info", ("pagecache_file_not_in error")); + PCBLOCK_INFO(block); + DBUG_ASSERT(0); + } +} + + +/* + Test if disk-cache is ok +*/ +static void test_key_cache(PAGECACHE *pagecache __attribute__((unused)), + const char *where __attribute__((unused)), + my_bool lock __attribute__((unused))) +{ + /* TODO */ +} +#endif + +uchar *pagecache_block_link_to_buffer(PAGECACHE_BLOCK_LINK *block) +{ + return block->buffer; +} + +#if defined(PAGECACHE_TIMEOUT) + +#define KEYCACHE_DUMP_FILE "pagecache_dump.txt" +#define MAX_QUEUE_LEN 100 + + +static void pagecache_dump(PAGECACHE *pagecache) +{ + FILE *pagecache_dump_file=fopen(KEYCACHE_DUMP_FILE, "w"); + struct st_my_thread_var *last; + struct st_my_thread_var *thread; + PAGECACHE_BLOCK_LINK *block; + PAGECACHE_HASH_LINK *hash_link; + PAGECACHE_PAGE *page; + uint i; + + fprintf(pagecache_dump_file, "thread:%u\n", thread->id); + + i=0; + thread=last=waiting_for_hash_link.last_thread; + fprintf(pagecache_dump_file, "queue of threads waiting for hash link\n"); + if (thread) + do + { + thread= thread->next; + page= (PAGECACHE_PAGE *) thread->opt_info; + fprintf(pagecache_dump_file, + "thread:%u, (file,pageno)=(%u,%lu)\n", + thread->id,(uint) page->file.file,(ulong) page->pageno); + if (++i == MAX_QUEUE_LEN) + break; + } + while (thread != last); + + i=0; + thread=last=waiting_for_block.last_thread; + fprintf(pagecache_dump_file, "queue of threads waiting for block\n"); + if (thread) + do + { + thread=thread->next; + hash_link= (PAGECACHE_HASH_LINK *) thread->opt_info; + fprintf(pagecache_dump_file, + "thread:%u hash_link:%u (file,pageno)=(%u,%lu)\n", + thread->id, (uint) PAGECACHE_HASH_LINK_NUMBER(pagecache, hash_link), + (uint) hash_link->file.file,(ulong) hash_link->pageno); + if (++i == MAX_QUEUE_LEN) + break; + } + while (thread != last); + + for (i=0 ; i < pagecache->blocks_used ; i++) + { + int j; + block= &pagecache->block_root[i]; + hash_link= block->hash_link; + fprintf(pagecache_dump_file, + "block:%u hash_link:%d status:%x #requests=%u waiting_for_readers:%d\n", + i, (int) (hash_link ? + PAGECACHE_HASH_LINK_NUMBER(pagecache, hash_link) : + -1), + block->status, block->requests, block->condvar ? 1 : 0); + for (j=0 ; j < COND_SIZE; j++) + { + PAGECACHE_WQUEUE *wqueue=&block->wqueue[j]; + thread= last= wqueue->last_thread; + fprintf(pagecache_dump_file, "queue #%d\n", j); + if (thread) + { + do + { + thread=thread->next; + fprintf(pagecache_dump_file, + "thread:%u\n", thread->id); + if (++i == MAX_QUEUE_LEN) + break; + } + while (thread != last); + } + } + } + fprintf(pagecache_dump_file, "LRU chain:"); + block= pagecache= used_last; + if (block) + { + do + { + block= block->next_used; + fprintf(pagecache_dump_file, + "block:%u, ", PCBLOCK_NUMBER(pagecache, block)); + } + while (block != pagecache->used_last); + } + fprintf(pagecache_dump_file, "\n"); + + fclose(pagecache_dump_file); +} + +#endif /* defined(PAGECACHE_TIMEOUT) */ + +#if defined(PAGECACHE_TIMEOUT) && !defined(__WIN__) + + +static int pagecache_pthread_cond_wait(pthread_cond_t *cond, + pthread_mutex_t *mutex) +{ + int rc; + struct timeval now; /* time when we started waiting */ + struct timespec timeout; /* timeout value for the wait function */ + struct timezone tz; +#if defined(PAGECACHE_DEBUG) + int cnt=0; +#endif + + /* Get current time */ + gettimeofday(&now, &tz); + /* Prepare timeout value */ + timeout.tv_sec= now.tv_sec + PAGECACHE_TIMEOUT; + /* + timeval uses microseconds. + timespec uses nanoseconds. + 1 nanosecond = 1000 micro seconds + */ + timeout.tv_nsec= now.tv_usec * 1000; + KEYCACHE_THREAD_TRACE_END("started waiting"); +#if defined(PAGECACHE_DEBUG) + cnt++; + if (cnt % 100 == 0) + fprintf(pagecache_debug_log, "waiting...\n"); + fflush(pagecache_debug_log); +#endif + rc= pthread_cond_timedwait(cond, mutex, &timeout); + KEYCACHE_THREAD_TRACE_BEGIN("finished waiting"); + if (rc == ETIMEDOUT || rc == ETIME) + { +#if defined(PAGECACHE_DEBUG) + fprintf(pagecache_debug_log,"aborted by pagecache timeout\n"); + fclose(pagecache_debug_log); + abort(); +#endif + pagecache_dump(); + } + +#if defined(PAGECACHE_DEBUG) + KEYCACHE_DBUG_ASSERT(rc != ETIMEDOUT); +#else + assert(rc != ETIMEDOUT); +#endif + return rc; +} +#else +#if defined(PAGECACHE_DEBUG) +static int pagecache_pthread_cond_wait(pthread_cond_t *cond, + pthread_mutex_t *mutex) +{ + int rc; + KEYCACHE_THREAD_TRACE_END("started waiting"); + rc= pthread_cond_wait(cond, mutex); + KEYCACHE_THREAD_TRACE_BEGIN("finished waiting"); + return rc; +} +#endif +#endif /* defined(PAGECACHE_TIMEOUT) && !defined(__WIN__) */ + +#if defined(PAGECACHE_DEBUG) +static int ___pagecache_pthread_mutex_lock(pthread_mutex_t *mutex) +{ + int rc; + rc= pthread_mutex_lock(mutex); + KEYCACHE_THREAD_TRACE_BEGIN(""); + return rc; +} + + +static void ___pagecache_pthread_mutex_unlock(pthread_mutex_t *mutex) +{ + KEYCACHE_THREAD_TRACE_END(""); + pthread_mutex_unlock(mutex); +} + + +static int ___pagecache_pthread_cond_signal(pthread_cond_t *cond) +{ + int rc; + KEYCACHE_THREAD_TRACE("signal"); + rc= pthread_cond_signal(cond); + return rc; +} + + +#if defined(PAGECACHE_DEBUG_LOG) + + +static void pagecache_debug_print(const char * fmt, ...) +{ + va_list args; + va_start(args,fmt); + if (pagecache_debug_log) + { + VOID(vfprintf(pagecache_debug_log, fmt, args)); + VOID(fputc('\n',pagecache_debug_log)); + } + va_end(args); +} +#endif /* defined(PAGECACHE_DEBUG_LOG) */ + +#if defined(PAGECACHE_DEBUG_LOG) + + +void pagecache_debug_log_close(void) +{ + if (pagecache_debug_log) + fclose(pagecache_debug_log); +} +#endif /* defined(PAGECACHE_DEBUG_LOG) */ + +#endif /* defined(PAGECACHE_DEBUG) */ diff --git a/storage/maria/ma_pagecache.h b/storage/maria/ma_pagecache.h new file mode 100644 index 00000000000..821728ef374 --- /dev/null +++ b/storage/maria/ma_pagecache.h @@ -0,0 +1,325 @@ +/* Copyright (C) 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 */ + +/* Page cache variable structures */ + +#ifndef _ma_pagecache_h +#define _ma_pagecache_h +C_MODE_START + +#include "ma_loghandler_lsn.h" +#include <m_string.h> +#include <hash.h> + +/* Type of the page */ +enum pagecache_page_type +{ + /* + Used only for control page type changing during debugging. This define + should only be using when using DBUG. + */ + PAGECACHE_EMPTY_PAGE, + /* the page does not contain LSN */ + PAGECACHE_PLAIN_PAGE, + /* the page contain LSN (maria tablespace page) */ + PAGECACHE_LSN_PAGE, + /* Page type used when scanning file and we don't care about the type */ + PAGECACHE_READ_UNKNOWN_PAGE +}; + +/* + This enum describe lock status changing. every type of page cache will + interpret WRITE/READ lock as it need. +*/ +enum pagecache_page_lock +{ + PAGECACHE_LOCK_LEFT_UNLOCKED, /* free -> free */ + PAGECACHE_LOCK_LEFT_READLOCKED, /* read -> read */ + PAGECACHE_LOCK_LEFT_WRITELOCKED, /* write -> write */ + PAGECACHE_LOCK_READ, /* free -> read */ + PAGECACHE_LOCK_WRITE, /* free -> write */ + PAGECACHE_LOCK_READ_UNLOCK, /* read -> free */ + PAGECACHE_LOCK_WRITE_UNLOCK, /* write -> free */ + PAGECACHE_LOCK_WRITE_TO_READ /* write -> read */ +}; +/* + This enum describe pin status changing +*/ +enum pagecache_page_pin +{ + PAGECACHE_PIN_LEFT_PINNED, /* pinned -> pinned */ + PAGECACHE_PIN_LEFT_UNPINNED, /* unpinned -> unpinned */ + PAGECACHE_PIN, /* unpinned -> pinned */ + PAGECACHE_UNPIN /* pinned -> unpinned */ +}; +/* How to write the page */ +enum pagecache_write_mode +{ + /* do not write immediately, i.e. it will be dirty page */ + PAGECACHE_WRITE_DELAY, + /* page already is in the file. (key cache insert analogue) */ + PAGECACHE_WRITE_DONE +}; + +/* page number for maria */ +typedef ulonglong pgcache_page_no_t; + +/* file descriptor for Maria */ +typedef struct st_pagecache_file +{ + File file; + /** Cannot be NULL */ + my_bool (*read_callback)(uchar *page, pgcache_page_no_t offset, + uchar *data); + /** Cannot be NULL */ + my_bool (*write_callback)(uchar *page, pgcache_page_no_t offset, + uchar *data); + void (*write_fail)(uchar *data); + /** Cannot be NULL */ + my_bool (*flush_log_callback)(uchar *page, pgcache_page_no_t offset, + uchar *data); + uchar *callback_data; +} PAGECACHE_FILE; + +/* declare structures that is used by st_pagecache */ + +struct st_pagecache_block_link; +typedef struct st_pagecache_block_link PAGECACHE_BLOCK_LINK; +struct st_pagecache_page; +typedef struct st_pagecache_page PAGECACHE_PAGE; +struct st_pagecache_hash_link; +typedef struct st_pagecache_hash_link PAGECACHE_HASH_LINK; + +#include <wqueue.h> + +#define PAGECACHE_CHANGED_BLOCKS_HASH 128 /* must be power of 2 */ +#define PAGECACHE_PRIORITY_LOW 0 +#define PAGECACHE_PRIORITY_DEFAULT 3 +#define PAGECACHE_PRIORITY_HIGH 6 + +/* + The page cache structure + It also contains read-only statistics parameters. +*/ + +typedef struct st_pagecache +{ + size_t mem_size; /* specified size of the cache memory */ + ulong min_warm_blocks; /* min number of warm blocks; */ + ulong age_threshold; /* age threshold for hot blocks */ + ulonglong time; /* total number of block link operations */ + ulong hash_entries; /* max number of entries in the hash table */ + long hash_links; /* max number of hash links */ + long hash_links_used; /* number of hash links taken from free links pool */ + long disk_blocks; /* max number of blocks in the cache */ + ulong blocks_used; /* maximum number of concurrently used blocks */ + ulong blocks_unused; /* number of currently unused blocks */ + ulong blocks_changed; /* number of currently dirty blocks */ + ulong warm_blocks; /* number of blocks in warm sub-chain */ + ulong cnt_for_resize_op; /* counter to block resize operation */ + ulong blocks_available; /* number of blocks available in the LRU chain */ + long blocks; /* max number of blocks in the cache */ + uint32 block_size; /* size of the page buffer of a cache block */ + PAGECACHE_HASH_LINK **hash_root;/* arr. of entries into hash table buckets */ + PAGECACHE_HASH_LINK *hash_link_root;/* memory for hash table links */ + PAGECACHE_HASH_LINK *free_hash_list;/* list of free hash links */ + PAGECACHE_BLOCK_LINK *free_block_list;/* list of free blocks */ + PAGECACHE_BLOCK_LINK *block_root;/* memory for block links */ + uchar HUGE_PTR *block_mem; /* memory for block buffers */ + PAGECACHE_BLOCK_LINK *used_last;/* ptr to the last block of the LRU chain */ + PAGECACHE_BLOCK_LINK *used_ins;/* ptr to the insertion block in LRU chain */ + pthread_mutex_t cache_lock; /* to lock access to the cache structure */ + WQUEUE resize_queue; /* threads waiting during resize operation */ + WQUEUE waiting_for_hash_link;/* waiting for a free hash link */ + WQUEUE waiting_for_block; /* requests waiting for a free block */ + /* hash for dirty file bl.*/ + PAGECACHE_BLOCK_LINK *changed_blocks[PAGECACHE_CHANGED_BLOCKS_HASH]; + /* hash for other file bl.*/ + PAGECACHE_BLOCK_LINK *file_blocks[PAGECACHE_CHANGED_BLOCKS_HASH]; + + /* + The following variables are and variables used to hold parameters for + initializing the key cache. + */ + + ulonglong param_buff_size; /* size the memory allocated for the cache */ + ulong param_block_size; /* size of the blocks in the key cache */ + ulong param_division_limit; /* min. percentage of warm blocks */ + ulong param_age_threshold; /* determines when hot block is downgraded */ + + /* Statistics variables. These are reset in reset_pagecache_counters(). */ + ulong global_blocks_changed; /* number of currently dirty blocks */ + ulonglong global_cache_w_requests;/* number of write requests (write hits) */ + ulonglong global_cache_write; /* number of writes from cache to files */ + ulonglong global_cache_r_requests;/* number of read requests (read hits) */ + ulonglong global_cache_read; /* number of reads from files to cache */ + + uint shift; /* block size = 2 ^ shift */ + myf readwrite_flags; /* Flags to pread/pwrite() */ + myf org_readwrite_flags; /* Flags to pread/pwrite() at init */ + my_bool inited; + my_bool resize_in_flush; /* true during flush of resize operation */ + my_bool can_be_used; /* usage of cache for read/write is allowed */ + my_bool in_init; /* Set to 1 in MySQL during init/resize */ + HASH files_in_flush; /**< files in flush_pagecache_blocks_int() */ +} PAGECACHE; + +/** @brief Return values for PAGECACHE_FLUSH_FILTER */ +enum pagecache_flush_filter_result +{ + FLUSH_FILTER_SKIP_TRY_NEXT= 0,/**< skip page and move on to next one */ + FLUSH_FILTER_OK, /**< flush page and move on to next one */ + FLUSH_FILTER_SKIP_ALL /**< skip page and all next ones */ +}; +/** @brief a filter function type for flush_pagecache_blocks_with_filter() */ +typedef enum pagecache_flush_filter_result +(*PAGECACHE_FLUSH_FILTER)(enum pagecache_page_type type, + pgcache_page_no_t page, + LSN rec_lsn, void *arg); + +/* The default key cache */ +extern PAGECACHE dflt_pagecache_var, *dflt_pagecache; + +extern ulong init_pagecache(PAGECACHE *pagecache, size_t use_mem, + uint division_limit, uint age_threshold, + uint block_size, myf my_read_flags); +extern ulong resize_pagecache(PAGECACHE *pagecache, + size_t use_mem, uint division_limit, + uint age_threshold); +extern void change_pagecache_param(PAGECACHE *pagecache, uint division_limit, + uint age_threshold); + +extern uchar *pagecache_read(PAGECACHE *pagecache, + PAGECACHE_FILE *file, + pgcache_page_no_t pageno, + uint level, + uchar *buff, + enum pagecache_page_type type, + enum pagecache_page_lock lock, + PAGECACHE_BLOCK_LINK **link); + +#define pagecache_write(P,F,N,L,B,T,O,I,M,K,R) \ + pagecache_write_part(P,F,N,L,B,T,O,I,M,K,R,0,(P)->block_size) + +#define pagecache_inject(P,F,N,L,B,T,O,I,K,R) \ + pagecache_write_part(P,F,N,L,B,T,O,I,PAGECACHE_WRITE_DONE, \ + K,R,0,(P)->block_size) + +extern my_bool pagecache_write_part(PAGECACHE *pagecache, + PAGECACHE_FILE *file, + pgcache_page_no_t pageno, + uint level, + uchar *buff, + enum pagecache_page_type type, + enum pagecache_page_lock lock, + enum pagecache_page_pin pin, + enum pagecache_write_mode write_mode, + PAGECACHE_BLOCK_LINK **link, + LSN first_REDO_LSN_for_page, + uint offset, + uint size); +extern void pagecache_unlock(PAGECACHE *pagecache, + PAGECACHE_FILE *file, + pgcache_page_no_t pageno, + enum pagecache_page_lock lock, + enum pagecache_page_pin pin, + LSN first_REDO_LSN_for_page, + LSN lsn, my_bool was_changed); +extern void pagecache_unlock_by_link(PAGECACHE *pagecache, + PAGECACHE_BLOCK_LINK *block, + enum pagecache_page_lock lock, + enum pagecache_page_pin pin, + LSN first_REDO_LSN_for_page, + LSN lsn, my_bool was_changed, + my_bool any); +extern void pagecache_unpin(PAGECACHE *pagecache, + PAGECACHE_FILE *file, + pgcache_page_no_t pageno, + LSN lsn); +extern void pagecache_unpin_by_link(PAGECACHE *pagecache, + PAGECACHE_BLOCK_LINK *link, + LSN lsn); + + +/* Results of flush operation (bit field in fact) */ + +/* The flush is done. */ +#define PCFLUSH_OK 0 +/* There was errors during the flush process. */ +#define PCFLUSH_ERROR 1 +/* Pinned blocks was met and skipped. */ +#define PCFLUSH_PINNED 2 +/* PCFLUSH_ERROR and PCFLUSH_PINNED. */ +#define PCFLUSH_PINNED_AND_ERROR (PCFLUSH_ERROR|PCFLUSH_PINNED) + +#define pagecache_file_init(F,RC,WC,WF,GLC,D) \ + do{ \ + (F).read_callback= (RC); (F).write_callback= (WC); \ + (F).write_fail= (WF); \ + (F).flush_log_callback= (GLC); (F).callback_data= (uchar*)(D); \ + } while(0) + +#define flush_pagecache_blocks(A,B,C) \ + flush_pagecache_blocks_with_filter(A,B,C,NULL,NULL) +extern int flush_pagecache_blocks_with_filter(PAGECACHE *keycache, + PAGECACHE_FILE *file, + enum flush_type type, + PAGECACHE_FLUSH_FILTER filter, + void *filter_arg); +extern my_bool pagecache_delete(PAGECACHE *pagecache, + PAGECACHE_FILE *file, + pgcache_page_no_t pageno, + enum pagecache_page_lock lock, + my_bool flush); +extern my_bool pagecache_delete_by_link(PAGECACHE *pagecache, + PAGECACHE_BLOCK_LINK *link, + enum pagecache_page_lock lock, + my_bool flush); +extern my_bool pagecache_delete_pages(PAGECACHE *pagecache, + PAGECACHE_FILE *file, + pgcache_page_no_t pageno, + uint page_count, + enum pagecache_page_lock lock, + my_bool flush); +extern void end_pagecache(PAGECACHE *keycache, my_bool cleanup); +extern my_bool pagecache_collect_changed_blocks_with_lsn(PAGECACHE *pagecache, + LEX_STRING *str, + LSN *min_lsn); +extern int reset_pagecache_counters(const char *name, PAGECACHE *pagecache); +extern uchar *pagecache_block_link_to_buffer(PAGECACHE_BLOCK_LINK *block); + +extern uint pagecache_pagelevel(PAGECACHE_BLOCK_LINK *block); +extern void pagecache_add_level_by_link(PAGECACHE_BLOCK_LINK *block, + uint level); + +/* Functions to handle multiple key caches */ +extern my_bool multi_pagecache_init(void); +extern void multi_pagecache_free(void); +extern PAGECACHE *multi_pagecache_search(uchar *key, uint length, + PAGECACHE *def); +extern my_bool multi_pagecache_set(const uchar *key, uint length, + PAGECACHE *pagecache); +extern void multi_pagecache_change(PAGECACHE *old_data, + PAGECACHE *new_data); +extern int reset_pagecache_counters(const char *name, + PAGECACHE *pagecache); +#ifndef DBUG_OFF +void pagecache_file_no_dirty_page(PAGECACHE *pagecache, PAGECACHE_FILE *file); +#else +#define pagecache_file_no_dirty_page(A,B) {} +#endif + +C_MODE_END +#endif /* _keycache_h */ diff --git a/storage/maria/ma_pagecaches.c b/storage/maria/ma_pagecaches.c new file mode 100644 index 00000000000..8a1423ee0d7 --- /dev/null +++ b/storage/maria/ma_pagecaches.c @@ -0,0 +1,104 @@ +/* Copyright (C) 2003-2007 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 */ + +/* + Handling of multiple key caches + + The idea is to have a thread safe hash on the table name, + with a default key cache value that is returned if the table name is not in + the cache. +*/ + +#include "maria_def.h" +#include "ma_pagecache.h" +#include <hash.h> +#include <m_string.h> +#include "../../mysys/my_safehash.h" + +/***************************************************************************** + Functions to handle the pagecache objects +*****************************************************************************/ + +/* Variable to store all key cache objects */ +static SAFE_HASH pagecache_hash; + + +my_bool multi_pagecache_init(void) +{ + return safe_hash_init(&pagecache_hash, 16, (uchar*) maria_pagecache); +} + + +void multi_pagecache_free(void) +{ + safe_hash_free(&pagecache_hash); +} + +/* + Get a key cache to be used for a specific table. + + SYNOPSIS + multi_pagecache_search() + key key to find (usually table path) + uint length Length of key. + def Default value if no key cache + + NOTES + This function is coded in such a way that we will return the + default key cache even if one never called multi_pagecache_init. + This will ensure that it works with old MyISAM clients. + + RETURN + key cache to use +*/ + +PAGECACHE *multi_pagecache_search(uchar *key, uint length, + PAGECACHE *def) +{ + if (!pagecache_hash.hash.records) + return def; + return (PAGECACHE*) safe_hash_search(&pagecache_hash, key, length, + (void*) def); +} + + +/* + Assosiate a key cache with a key + + + SYONOPSIS + multi_pagecache_set() + key key (path to table etc..) + length Length of key + pagecache cache to assococite with the table + + NOTES + This can be used both to insert a new entry and change an existing + entry +*/ + + +my_bool multi_pagecache_set(const uchar *key, uint length, + PAGECACHE *pagecache) +{ + return safe_hash_set(&pagecache_hash, key, length, (uchar*) pagecache); +} + + +void multi_pagecache_change(PAGECACHE *old_data, + PAGECACHE *new_data) +{ + safe_hash_change(&pagecache_hash, (uchar*) old_data, (uchar*) new_data); +} diff --git a/storage/maria/ma_pagecrc.c b/storage/maria/ma_pagecrc.c new file mode 100644 index 00000000000..640bb8880f4 --- /dev/null +++ b/storage/maria/ma_pagecrc.c @@ -0,0 +1,378 @@ +/* Copyright (C) 2007-2008 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 */ + +#include "maria_def.h" + + +/** + @brief calculate crc of the page avoiding special values + + @param start The value to start CRC (we use page number here) + @param data data pointer + @param length length of the data + + @return crc of the page without special values +*/ + +static uint32 maria_page_crc(uint32 start, uchar *data, uint length) +{ + uint32 crc= crc32(start, data, length); + + /* we need this assert to get following comparison working */ + compile_time_assert(MARIA_NO_CRC_BITMAP_PAGE == + MARIA_NO_CRC_NORMAL_PAGE - 1 && + MARIA_NO_CRC_NORMAL_PAGE == 0xffffffff); + if (crc >= MARIA_NO_CRC_BITMAP_PAGE) + crc= MARIA_NO_CRC_BITMAP_PAGE - 1; + + return(crc); +} + +/** + @brief Maria pages read callback (checks the page CRC) + + @param page The page data to check + @param page_no The page number (<offset>/<page length>) + @param data_ptr pointer to MARIA_SHARE + @param no_crc_val Value which means CRC absence + (MARIA_NO_CRC_NORMAL_PAGE or MARIA_NO_CRC_BITMAP_PAGE) + @param data_length length of data to calculate CRC + + @retval 0 OK + @retval 1 Error +*/ + +static my_bool maria_page_crc_check(uchar *page, + pgcache_page_no_t page_no, + MARIA_SHARE *share, + uint32 no_crc_val, + int data_length) +{ + uint32 crc= uint4korr(page + share->block_size - CRC_SIZE), new_crc; + my_bool res; + DBUG_ENTER("maria_page_crc_check"); + + DBUG_ASSERT((uint)data_length <= share->block_size - CRC_SIZE); + + /* we need this assert to get following comparison working */ + compile_time_assert(MARIA_NO_CRC_BITMAP_PAGE == + MARIA_NO_CRC_NORMAL_PAGE - 1 && + MARIA_NO_CRC_NORMAL_PAGE == 0xffffffff); + /* + If crc is no_crc_val then + the page has no crc, so there is nothing to check. + */ + if (crc >= MARIA_NO_CRC_BITMAP_PAGE) + { + DBUG_PRINT("info", ("No crc: %lu crc: %lu page: %lu ", + (ulong) no_crc_val, (ulong) crc, (ulong) page_no)); + if (crc != no_crc_val) + { + my_errno= HA_ERR_WRONG_CRC; + DBUG_PRINT("error", ("Wrong no CRC value")); + DBUG_RETURN(1); + } + DBUG_RETURN(0); + } + new_crc= maria_page_crc((uint32) page_no, page, data_length); + DBUG_ASSERT(new_crc != no_crc_val); + res= test(new_crc != crc); + if (res) + { + /* + Bitmap pages may be totally zero filled in some cases. + This happens when we get a crash after the pagecache has written + out a page that is on a newly created bitmap page and we get + a crash before the bitmap page is written out. + + We handle this case with the following logic: + When reading, approve of bitmap pages where all bytes are zero + (This is after all a bitmap pages where no data is reserved and + the CRC will be corrected at next write) + */ + if (no_crc_val == MARIA_NO_CRC_BITMAP_PAGE && + crc == 0 && _ma_check_if_zero(page, data_length)) + { + DBUG_PRINT("warning", ("Found bitmap page that was not initialized")); + DBUG_RETURN(0); + } + + DBUG_PRINT("error", ("Page: %lu crc: %lu calculated crc: %lu", + (ulong) page_no, (ulong) crc, (ulong) new_crc)); + my_errno= HA_ERR_WRONG_CRC; + } + DBUG_RETURN(res); +} + + +/** + @brief Maria pages write callback (sets the page CRC for data and index + files) + + @param page The page data to set + @param page_no The page number (<offset>/<page length>) + @param data_ptr Write callback data pointer (pointer to MARIA_SHARE) + + @retval 0 OK +*/ + +my_bool maria_page_crc_set_normal(uchar *page, + pgcache_page_no_t page_no, + uchar *data_ptr) +{ + MARIA_SHARE *share= (MARIA_SHARE *)data_ptr; + int data_length= share->block_size - CRC_SIZE; + uint32 crc= maria_page_crc((uint32) page_no, page, data_length); + DBUG_ENTER("maria_page_crc_set_normal"); + DBUG_PRINT("info", ("Page %lu crc: %lu", (ulong) page_no, (ulong)crc)); + + /* crc is on the stack so it is aligned, pagecache buffer is aligned, too */ + int4store_aligned(page + data_length, crc); + DBUG_RETURN(0); +} + + +/** + @brief Maria pages write callback (sets the page CRC for keys) + + @param page The page data to set + @param page_no The page number (<offset>/<page length>) + @param data_ptr Write callback data pointer (pointer to MARIA_SHARE) + + @retval 0 OK +*/ + +my_bool maria_page_crc_set_index(uchar *page, + pgcache_page_no_t page_no, + uchar *data_ptr) +{ + MARIA_SHARE *share= (MARIA_SHARE *)data_ptr; + int data_length= _ma_get_page_used(share, page); + uint32 crc= maria_page_crc((uint32) page_no, page, data_length); + DBUG_ENTER("maria_page_crc_set_index"); + DBUG_PRINT("info", ("Page %lu crc: %lu", + (ulong) page_no, (ulong) crc)); + DBUG_ASSERT((uint)data_length <= share->block_size - CRC_SIZE); + /* crc is on the stack so it is aligned, pagecache buffer is aligned, too */ + int4store_aligned(page + share->block_size - CRC_SIZE, crc); + DBUG_RETURN(0); +} + + +/* interface functions */ + + +/** + @brief Maria pages read callback (checks the page CRC) for index/data pages + + @param page The page data to check + @param page_no The page number (<offset>/<page length>) + @param data_ptr Read callback data pointer (pointer to MARIA_SHARE) + + @retval 0 OK + @retval 1 Error +*/ + +my_bool maria_page_crc_check_data(uchar *page, + pgcache_page_no_t page_no, + uchar *data_ptr) +{ + MARIA_SHARE *share= (MARIA_SHARE *)data_ptr; + return (maria_page_crc_check(page, (uint32) page_no, share, + MARIA_NO_CRC_NORMAL_PAGE, + share->block_size - CRC_SIZE)); +} + + +/** + @brief Maria pages read callback (checks the page CRC) for bitmap pages + + @param page The page data to check + @param page_no The page number (<offset>/<page length>) + @param data_ptr Read callback data pointer (pointer to MARIA_SHARE) + + @retval 0 OK + @retval 1 Error +*/ + +my_bool maria_page_crc_check_bitmap(uchar *page, + pgcache_page_no_t page_no, + uchar *data_ptr) +{ + MARIA_SHARE *share= (MARIA_SHARE *)data_ptr; + return (maria_page_crc_check(page, (uint32) page_no, share, + MARIA_NO_CRC_BITMAP_PAGE, + share->block_size - CRC_SIZE)); +} + + +/** + @brief Maria pages read callback (checks the page CRC) for index pages + + @param page The page data to check + @param page_no The page number (<offset>/<page length>) + @param data_ptr Read callback data pointer (pointer to MARIA_SHARE) + + @retval 0 OK + @retval 1 Error +*/ + +my_bool maria_page_crc_check_index(uchar *page, + pgcache_page_no_t page_no, + uchar *data_ptr) +{ + MARIA_SHARE *share= (MARIA_SHARE *)data_ptr; + uint length= _ma_get_page_used(share, page); + if (length > share->block_size - CRC_SIZE) + { + DBUG_PRINT("error", ("Wrong page length: %u", length)); + return (my_errno= HA_ERR_WRONG_CRC); + } + return maria_page_crc_check(page, (uint32) page_no, share, + MARIA_NO_CRC_NORMAL_PAGE, + length); +} + + +/** + @brief Maria pages dumme read callback for temporary tables + + @retval 0 OK + @retval 1 Error +*/ + +my_bool maria_page_crc_check_none(uchar *page __attribute__((unused)), + pgcache_page_no_t page_no + __attribute__((unused)), + uchar *data_ptr __attribute__((unused))) +{ + return 0; +} + + +/** + @brief Maria pages write callback (sets the page filler for index/data) + + @param page The page data to set + @param page_no The page number (<offset>/<page length>) + @param data_ptr Write callback data pointer (pointer to MARIA_SHARE) + + @retval 0 OK +*/ + +my_bool maria_page_filler_set_normal(uchar *page, + pgcache_page_no_t page_no + __attribute__((unused)), + uchar *data_ptr) +{ + DBUG_ENTER("maria_page_filler_set_normal"); + DBUG_ASSERT(page_no != 0); /* Catches some simple bugs */ + int4store_aligned(page + ((MARIA_SHARE *)data_ptr)->block_size - CRC_SIZE, + MARIA_NO_CRC_NORMAL_PAGE); + DBUG_RETURN(0); +} + + +/** + @brief Maria pages write callback (sets the page filler for bitmap) + + @param page The page data to set + @param page_no The page number (<offset>/<page length>) + @param data_ptr Write callback data pointer (pointer to MARIA_SHARE) + + @retval 0 OK +*/ + +my_bool maria_page_filler_set_bitmap(uchar *page, + pgcache_page_no_t page_no + __attribute__((unused)), + uchar *data_ptr) +{ + DBUG_ENTER("maria_page_filler_set_bitmap"); + int4store_aligned(page + ((MARIA_SHARE *)data_ptr)->block_size - CRC_SIZE, + MARIA_NO_CRC_BITMAP_PAGE); + DBUG_RETURN(0); +} + + +/** + @brief Maria pages dummy write callback for temporary tables + + @retval 0 OK +*/ + +my_bool maria_page_filler_set_none(uchar *page __attribute__((unused)), + pgcache_page_no_t page_no + __attribute__((unused)), + uchar *data_ptr __attribute__((unused))) +{ +#ifdef HAVE_valgrind + int4store_aligned(page + ((MARIA_SHARE *)data_ptr)->block_size - CRC_SIZE, + 0); +#endif + return 0; +} + + +/** + @brief Write failure callback (mark table as corrupted) + + @param data_ptr Write callback data pointer (pointer to MARIA_SHARE) +*/ + +void maria_page_write_failure(uchar* data_ptr) +{ + maria_mark_crashed_share((MARIA_SHARE *)data_ptr); +} + + +/** + @brief Maria flush log log if needed + + @param page The page data to set + @param page_no The page number (<offset>/<page length>) + @param data_ptr Write callback data pointer (pointer to MARIA_SHARE) + + @retval 0 OK + @retval 1 error +*/ + +my_bool maria_flush_log_for_page(uchar *page, + pgcache_page_no_t page_no + __attribute__((unused)), + uchar *data_ptr __attribute__((unused))) +{ + LSN lsn; +#ifndef DBUG_OFF + const MARIA_SHARE *share= (MARIA_SHARE*) data_ptr; +#endif + DBUG_ENTER("maria_flush_log_for_page"); + /* share is 0 here only in unittest */ + DBUG_ASSERT(!share || (share->page_type == PAGECACHE_LSN_PAGE && + share->now_transactional)); + lsn= lsn_korr(page); + if (translog_flush(lsn)) + DBUG_RETURN(1); + DBUG_RETURN(0); +} + + +my_bool maria_flush_log_for_page_none(uchar *page __attribute__((unused)), + pgcache_page_no_t page_no + __attribute__((unused)), + uchar *data_ptr __attribute__((unused))) +{ + return 0; +} diff --git a/storage/maria/ma_panic.c b/storage/maria/ma_panic.c new file mode 100644 index 00000000000..a86563f31fb --- /dev/null +++ b/storage/maria/ma_panic.c @@ -0,0 +1,140 @@ +/* Copyright (C) 2006 MySQL AB & MySQL Finland AB & TCX DataKonsult 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 "ma_fulltext.h" + +/* + Stop usage of Maria + + SYNOPSIS + maria_panic() + flag HA_PANIC_CLOSE: All maria files (tables and log) are closed. + maria_end() is called. + HA_PANIC_WRITE: All misam files are unlocked and + all changed data in single user maria is + written to file + HA_PANIC_READ All maria files that was locked when + maria_panic(HA_PANIC_WRITE) was done is + locked. A maria_readinfo() is done for + all single user files to get changes + in database + + RETURN + 0 ok + # error number in case of error +*/ + +int maria_panic(enum ha_panic_function flag) +{ + int error=0; + LIST *list_element,*next_open; + MARIA_HA *info; + DBUG_ENTER("maria_panic"); + + if (!maria_inited) + DBUG_RETURN(0); + pthread_mutex_lock(&THR_LOCK_maria); + for (list_element=maria_open_list ; list_element ; list_element=next_open) + { + next_open=list_element->next; /* Save if close */ + info=(MARIA_HA*) list_element->data; + switch (flag) { + case HA_PANIC_CLOSE: + /* + If bad luck (if some tables would be used now, which normally does not + happen in MySQL), as we release the mutex, the list may change and so + we may crash. + */ + pthread_mutex_unlock(&THR_LOCK_maria); + if (maria_close(info)) + error=my_errno; + pthread_mutex_lock(&THR_LOCK_maria); + break; + case HA_PANIC_WRITE: /* Do this to free databases */ +#ifdef CANT_OPEN_FILES_TWICE + if (info->s->options & HA_OPTION_READ_ONLY_DATA) + break; +#endif + if (flush_pagecache_blocks(info->s->pagecache, &info->s->kfile, + FLUSH_RELEASE)) + error=my_errno; + if (info->opt_flag & WRITE_CACHE_USED) + if (flush_io_cache(&info->rec_cache)) + error=my_errno; + if (info->opt_flag & READ_CACHE_USED) + { + if (flush_io_cache(&info->rec_cache)) + error=my_errno; + reinit_io_cache(&info->rec_cache,READ_CACHE,0, + (pbool) (info->lock_type != F_UNLCK),1); + } + if (info->lock_type != F_UNLCK && ! info->was_locked) + { + info->was_locked=info->lock_type; + if (maria_lock_database(info,F_UNLCK)) + error=my_errno; + } +#ifdef CANT_OPEN_FILES_TWICE + if (info->s->kfile.file >= 0 && my_close(info->s->kfile.file, MYF(0))) + error = my_errno; + if (info->dfile.file >= 0 && my_close(info->dfile.file, MYF(0))) + error = my_errno; + info->s->kfile.file= info->dfile.file= -1;/* Files aren't open anymore */ + break; +#endif + case HA_PANIC_READ: /* Restore to before WRITE */ +#ifdef CANT_OPEN_FILES_TWICE + { /* Open closed files */ + char name_buff[FN_REFLEN]; + MARIA_SHARE *share= info->s; + if (share->kfile.file < 0) + { + + if ((share->kfile.file= my_open(fn_format(name_buff, + info->filename, "", + N_NAME_IEXT,4), + info->mode, + MYF(MY_WME))) < 0) + error = my_errno; + } + if (info->dfile.file < 0) + { + if ((info->dfile.file= my_open(fn_format(name_buff, info->filename, + "", N_NAME_DEXT, 4), + info->mode, + MYF(MY_WME))) < 0) + error = my_errno; + info->rec_cache.file= info->dfile.file; + } + if (share->bitmap.file.file < 0) + share->bitmap.file.file= info->dfile.file; + } +#endif + if (info->was_locked) + { + if (maria_lock_database(info, info->was_locked)) + error=my_errno; + info->was_locked=0; + } + break; + } + } + pthread_mutex_unlock(&THR_LOCK_maria); + if (flag == HA_PANIC_CLOSE) + maria_end(); + if (!error) + DBUG_RETURN(0); + DBUG_RETURN(my_errno=error); +} /* maria_panic */ diff --git a/storage/maria/ma_preload.c b/storage/maria/ma_preload.c new file mode 100644 index 00000000000..6dfb4e437b6 --- /dev/null +++ b/storage/maria/ma_preload.c @@ -0,0 +1,116 @@ +/* Copyright (C) 2006 MySQL AB & MySQL Finland AB & TCX DataKonsult 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 */ + +/* + Preload indexes into key cache +*/ + +#include "maria_def.h" + + +/* + Preload pages of the index file for a table into the key cache + + SYNOPSIS + maria_preload() + info open table + map map of indexes to preload into key cache + ignore_leaves only non-leaves pages are to be preloaded + + RETURN VALUE + 0 if a success. error code - otherwise. + + NOTES. + At present pages for all indexes are preloaded. + In future only pages for indexes specified in the key_map parameter + of the table will be preloaded. + We don't yet use preload_buff_size (we read page after page). +*/ + +int maria_preload(MARIA_HA *info, ulonglong key_map, my_bool ignore_leaves) +{ + ulong block_length= 0; + uchar *buff; + MARIA_SHARE* share= info->s; + uint keynr; + my_off_t key_file_length= share->state.state.key_file_length; + pgcache_page_no_t page_no, page_no_max; + PAGECACHE_BLOCK_LINK *page_link; + DBUG_ENTER("maria_preload"); + + if (!share->state.header.keys || !maria_is_any_key_active(key_map) || + (key_file_length == share->base.keystart)) + DBUG_RETURN(0); + + block_length= share->pagecache->block_size; + + if (!(buff= (uchar *) my_malloc(block_length, MYF(MY_WME)))) + DBUG_RETURN(my_errno= HA_ERR_OUT_OF_MEM); + + if (flush_pagecache_blocks(share->pagecache, &share->kfile, FLUSH_RELEASE)) + goto err; + + /* + Currently when we come here all other open instances of the table have + been closed, and we flushed all pages of our own instance, so there + cannot be any page of this table in the cache. Thus my_pread() would be + safe. But in the future, we will allow more concurrency during + preloading, so we use pagecache_read() instead of my_pread() because we + observed that on some Linux, concurrent pread() and pwrite() (which + could be from a page eviction by another thread) to the same page can + make pread() see an half-written page. + In this future, we should find a way to read state.key_file_length + reliably, handle concurrent shrinks (delete_all_rows()) etc. + */ + for ((page_no= share->base.keystart / block_length), + (page_no_max= key_file_length / block_length); + page_no < page_no_max; page_no++) + { + /** + @todo instead of reading pages one by one we could have a call + pagecache_read_several_pages() which does a single my_pread() for many + consecutive pages (like the my_pread() in mi_preload()). + */ + if (pagecache_read(share->pagecache, &share->kfile, page_no, + DFLT_INIT_HITS, buff, share->page_type, + PAGECACHE_LOCK_WRITE, &page_link) == NULL) + goto err; + keynr= _ma_get_keynr(share, buff); + if (((ignore_leaves && !_ma_test_if_nod(share, buff)) || + keynr == MARIA_DELETE_KEY_NR || + !(key_map & ((ulonglong) 1 << keynr))) && + (pagecache_pagelevel(page_link) == DFLT_INIT_HITS)) + { + /* + This page is not interesting, and (last condition above) we are the + ones who put it in the cache, so nobody else is interested in it. + */ + if (pagecache_delete_by_link(share->pagecache, page_link, + PAGECACHE_LOCK_LEFT_WRITELOCKED, FALSE)) + goto err; + } + else /* otherwise it stays in cache: */ + pagecache_unlock_by_link(share->pagecache, page_link, + PAGECACHE_LOCK_WRITE_UNLOCK, PAGECACHE_UNPIN, + LSN_IMPOSSIBLE, LSN_IMPOSSIBLE, FALSE, FALSE); + } + + my_free(buff, MYF(0)); + DBUG_RETURN(0); + +err: + my_free(buff, MYF(MY_ALLOW_ZERO_PTR)); + DBUG_RETURN(my_errno= errno); +} diff --git a/storage/maria/ma_range.c b/storage/maria/ma_range.c new file mode 100644 index 00000000000..5dc4e3a9959 --- /dev/null +++ b/storage/maria/ma_range.c @@ -0,0 +1,312 @@ +/* Copyright (C) 2006 MySQL AB & MySQL Finland AB & TCX DataKonsult 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 */ + +/* + Gives a approximated number of how many records there is between two keys. + Used when optimizing querries. + */ + +#include "maria_def.h" +#include "ma_rt_index.h" + +static ha_rows _ma_record_pos(MARIA_HA *,const uchar *, key_part_map, + enum ha_rkey_function); +static double _ma_search_pos(MARIA_HA *, MARIA_KEY *, uint32, my_off_t); +static uint _ma_keynr(MARIA_PAGE *page, uchar *keypos, uint *ret_max_key); + + +/** + @brief Estimate how many records there is in a given range + + @param info MARIA handler + @param inx Index to use + @param min_key Min key. Is = 0 if no min range + @param max_key Max key. Is = 0 if no max range + + @note + We should ONLY return 0 if there is no rows in range + + @return Estimated number of rows or error + @retval HA_POS_ERROR error (or we can't estimate number of rows) + @retval number Estimated number of rows +*/ + +ha_rows maria_records_in_range(MARIA_HA *info, int inx, key_range *min_key, + key_range *max_key) +{ + ha_rows start_pos,end_pos,res; + MARIA_SHARE *share= info->s; + MARIA_KEY key; + MARIA_KEYDEF *keyinfo; + DBUG_ENTER("maria_records_in_range"); + + if ((inx = _ma_check_index(info,inx)) < 0) + DBUG_RETURN(HA_POS_ERROR); + + if (fast_ma_readinfo(info)) + DBUG_RETURN(HA_POS_ERROR); + info->update&= (HA_STATE_CHANGED+HA_STATE_ROW_CHANGED); + keyinfo= share->keyinfo + inx; + if (share->lock_key_trees) + rw_rdlock(&keyinfo->root_lock); + + switch (keyinfo->key_alg) { +#ifdef HAVE_RTREE_KEYS + case HA_KEY_ALG_RTREE: + { + uchar *key_buff; + + /* + The problem is that the optimizer doesn't support + RTree keys properly at the moment. + Hope this will be fixed some day. + But now NULL in the min_key means that we + didn't make the task for the RTree key + and expect BTree functionality from it. + As it's not able to handle such request + we return the error. + */ + if (!min_key) + { + res= HA_POS_ERROR; + break; + } + key_buff= info->last_key.data + share->base.max_key_length; + _ma_pack_key(info, &key, inx, key_buff, + min_key->key, min_key->keypart_map, + (HA_KEYSEG**) 0); + res= maria_rtree_estimate(info, &key, maria_read_vec[min_key->flag]); + res= res ? res : 1; /* Don't return 0 */ + break; + } +#endif + case HA_KEY_ALG_BTREE: + default: + start_pos= (min_key ? + _ma_record_pos(info, min_key->key, min_key->keypart_map, + min_key->flag) : + (ha_rows) 0); + end_pos= (max_key ? + _ma_record_pos(info, max_key->key, max_key->keypart_map, + max_key->flag) : + info->state->records + (ha_rows) 1); + res= (end_pos < start_pos ? (ha_rows) 0 : + (end_pos == start_pos ? (ha_rows) 1 : end_pos-start_pos)); + if (start_pos == HA_POS_ERROR || end_pos == HA_POS_ERROR) + res=HA_POS_ERROR; + } + + if (share->lock_key_trees) + rw_unlock(&keyinfo->root_lock); + fast_ma_writeinfo(info); + + /** + @todo LOCK + If res==0 (no rows), if we need to guarantee repeatability of the search, + we will need to set a next-key lock in this statement. + Also SELECT COUNT(*)... + */ + + DBUG_PRINT("info",("records: %ld",(ulong) (res))); + DBUG_RETURN(res); +} + + + /* Find relative position (in records) for key in index-tree */ + +static ha_rows _ma_record_pos(MARIA_HA *info, const uchar *key_data, + key_part_map keypart_map, + enum ha_rkey_function search_flag) +{ + uint inx= (uint) info->lastinx; + uint32 nextflag; + uchar *key_buff; + double pos; + MARIA_KEY key; + DBUG_ENTER("_ma_record_pos"); + DBUG_PRINT("enter",("search_flag: %d",search_flag)); + DBUG_ASSERT(keypart_map); + + key_buff= info->lastkey_buff+info->s->base.max_key_length; + _ma_pack_key(info, &key, inx, key_buff, key_data, keypart_map, + (HA_KEYSEG**) 0); + DBUG_EXECUTE("key", _ma_print_key(DBUG_FILE, &key);); + nextflag=maria_read_vec[search_flag]; + + /* + my_handler.c:ha_compare_text() has a flag 'skip_end_space'. + This is set in my_handler.c:ha_key_cmp() in dependence on the + compare flags 'nextflag' and the column type. + + TEXT columns are of type HA_KEYTYPE_VARTEXT. In this case the + condition is skip_end_space= ((nextflag & (SEARCH_FIND | + SEARCH_UPDATE)) == SEARCH_FIND). + + SEARCH_FIND is used for an exact key search. The combination + SEARCH_FIND | SEARCH_UPDATE is used in write/update/delete + operations with a comment like "Not real duplicates", whatever this + means. From the condition above we can see that 'skip_end_space' is + always false for these operations. The result is that trailing space + counts in key comparison and hence, emtpy strings ('', string length + zero, but not NULL) compare less that strings starting with control + characters and these in turn compare less than strings starting with + blanks. + + When estimating the number of records in a key range, we request an + exact search for the minimum key. This translates into a plain + SEARCH_FIND flag. Using this alone would lead to a 'skip_end_space' + compare. Empty strings would be expected above control characters. + Their keys would not be found because they are located below control + characters. + + This is the reason that we add the SEARCH_UPDATE flag here. It makes + the key estimation compare in the same way like key write operations + do. Olny so we will find the keys where they have been inserted. + + Adding the flag unconditionally does not hurt as it is used in the + above mentioned condition only. So it can safely be used together + with other flags. + */ + pos= _ma_search_pos(info, &key, + nextflag | SEARCH_SAVE_BUFF | SEARCH_UPDATE, + info->s->state.key_root[inx]); + if (pos >= 0.0) + { + DBUG_PRINT("exit",("pos: %ld",(ulong) (pos*info->state->records))); + DBUG_RETURN((ulong) (pos*info->state->records+0.5)); + } + DBUG_RETURN(HA_POS_ERROR); +} + + +/** + Find offset for key on index page + + @notes + Modified version of _ma_search() + + @return + @retval 0.0 <= x <= 1.0 +*/ + +static double _ma_search_pos(MARIA_HA *info, MARIA_KEY *key, + uint32 nextflag, my_off_t pos) +{ + int flag; + uint keynr, max_keynr; + my_bool after_key; + uchar *keypos; + double offset; + MARIA_KEYDEF *keyinfo= key->keyinfo; + MARIA_PAGE page; + DBUG_ENTER("_ma_search_pos"); + LINT_INIT(max_keynr); + + if (pos == HA_OFFSET_ERROR) + DBUG_RETURN(0.5); + + if (_ma_fetch_keypage(&page, info, keyinfo, pos, + PAGECACHE_LOCK_LEFT_UNLOCKED, DFLT_INIT_HITS, + info->buff, 1)) + goto err; + flag= (*keyinfo->bin_search)(key, &page, nextflag, &keypos, + info->lastkey_buff, &after_key); + keynr= _ma_keynr(&page, keypos, &max_keynr); + + if (flag) + { + if (flag == MARIA_FOUND_WRONG_KEY) + DBUG_RETURN(-1); /* error */ + /* + Didn't found match. keypos points at next (bigger) key + Try to find a smaller, better matching key. + Matches keynr + [0-1] + */ + if (flag > 0 && ! page.node) + offset= 1.0; + else if ((offset= _ma_search_pos(info, key, nextflag, + _ma_kpos(page.node,keypos))) < 0) + DBUG_RETURN(offset); + } + else + { + /* + Found match. Keypos points at the start of the found key + Matches keynr+1 + */ + offset=1.0; /* Matches keynr+1 */ + if ((nextflag & SEARCH_FIND) && page.node && + ((keyinfo->flag & (HA_NOSAME | HA_NULL_PART)) != HA_NOSAME || + (nextflag & (SEARCH_PREFIX | SEARCH_NO_FIND | SEARCH_LAST | + SEARCH_PART_KEY)))) + { + /* + There may be identical keys in the tree. Try to match on of those. + Matches keynr + [0-1] + */ + if ((offset= _ma_search_pos(info, key, SEARCH_FIND, + _ma_kpos(page.node,keypos))) < 0) + DBUG_RETURN(offset); /* Read error */ + } + } + DBUG_PRINT("info",("keynr: %d offset: %g max_keynr: %d nod: %d flag: %d", + keynr,offset,max_keynr,page.node,flag)); + DBUG_RETURN((keynr+offset)/(max_keynr+1)); +err: + DBUG_PRINT("exit",("Error: %d",my_errno)); + DBUG_RETURN (-1.0); +} + + +/* Get keynummer of current key and max number of keys in nod */ + +static uint _ma_keynr(MARIA_PAGE *page, uchar *keypos, uint *ret_max_key) +{ + uint page_flag, nod_flag, keynr, max_key; + uchar t_buff[MARIA_MAX_KEY_BUFF], *pos, *end; + const MARIA_KEYDEF *keyinfo= page->keyinfo; + MARIA_KEY key; + + page_flag= page->flag; + nod_flag= page->node; + pos= page->buff + page->info->s->keypage_header + nod_flag; + end= page->buff + page->size; + + if (!(keyinfo->flag & (HA_VAR_LENGTH_KEY | HA_BINARY_PACK_KEY)) && + ! (page_flag & KEYPAGE_FLAG_HAS_TRANSID)) + { + *ret_max_key= (uint) (end - pos)/(keyinfo->keylength+nod_flag); + return (uint) (keypos - pos)/(keyinfo->keylength+nod_flag); + } + + max_key=keynr=0; + t_buff[0]=0; /* Safety */ + key.data= t_buff; + key.keyinfo= (MARIA_KEYDEF*) keyinfo; + + while (pos < end) + { + if (!(pos= (*keyinfo->skip_key)(&key, page_flag, nod_flag, pos))) + { + DBUG_ASSERT(0); + return 0; /* Error */ + } + max_key++; + if (pos == keypos) + keynr= max_key; + } + *ret_max_key=max_key; + return(keynr); +} diff --git a/storage/maria/ma_recovery.c b/storage/maria/ma_recovery.c new file mode 100644 index 00000000000..54639b596d9 --- /dev/null +++ b/storage/maria/ma_recovery.c @@ -0,0 +1,3592 @@ +/* Copyright (C) 2006, 2007 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 */ + +/* + WL#3072 Maria recovery + First version written by Guilhem Bichot on 2006-04-27. +*/ + +/* Here is the implementation of this module */ + +#include "maria_def.h" +#include "ma_recovery.h" +#include "ma_blockrec.h" +#include "ma_checkpoint.h" +#include "trnman.h" +#include "ma_key_recover.h" +#include "ma_recovery_util.h" + +struct st_trn_for_recovery /* used only in the REDO phase */ +{ + LSN group_start_lsn, undo_lsn, first_undo_lsn; + TrID long_trid; +}; +struct st_table_for_recovery /* used in the REDO and UNDO phase */ +{ + MARIA_HA *info; +}; +/* Variables used by all functions of this module. Ok as single-threaded */ +static struct st_trn_for_recovery *all_active_trans; +static struct st_table_for_recovery *all_tables; +static struct st_dirty_page *dirty_pages_pool; +static LSN current_group_end_lsn; +#ifndef DBUG_OFF +/** Current group of REDOs is about this table and only this one */ +static MARIA_HA *current_group_table; +#endif +static TrID max_long_trid= 0; /**< max long trid seen by REDO phase */ +static my_bool skip_DDLs; /**< if REDO phase should skip DDL records */ +/** @brief to avoid writing a checkpoint if recovery did nothing. */ +static my_bool checkpoint_useful; +static my_bool in_redo_phase; +static my_bool trns_created; +static ulong skipped_undo_phase; +static ulonglong now; /**< for tracking execution time of phases */ +static int (*save_error_handler_hook)(uint, const char *,myf); +static uint recovery_warnings; /**< count of warnings */ + +#define prototype_redo_exec_hook(R) \ + static int exec_REDO_LOGREC_ ## R(const TRANSLOG_HEADER_BUFFER *rec) + +#define prototype_redo_exec_hook_dummy(R) \ + static int exec_REDO_LOGREC_ ## R(const TRANSLOG_HEADER_BUFFER *rec \ + __attribute__ ((unused))) + +#define prototype_undo_exec_hook(R) \ + static int exec_UNDO_LOGREC_ ## R(const TRANSLOG_HEADER_BUFFER *rec, TRN *trn) + +prototype_redo_exec_hook(LONG_TRANSACTION_ID); +prototype_redo_exec_hook_dummy(CHECKPOINT); +prototype_redo_exec_hook(REDO_CREATE_TABLE); +prototype_redo_exec_hook(REDO_RENAME_TABLE); +prototype_redo_exec_hook(REDO_REPAIR_TABLE); +prototype_redo_exec_hook(REDO_DROP_TABLE); +prototype_redo_exec_hook(FILE_ID); +prototype_redo_exec_hook(INCOMPLETE_LOG); +prototype_redo_exec_hook_dummy(INCOMPLETE_GROUP); +prototype_redo_exec_hook(UNDO_BULK_INSERT); +prototype_redo_exec_hook(IMPORTED_TABLE); +prototype_redo_exec_hook(REDO_INSERT_ROW_HEAD); +prototype_redo_exec_hook(REDO_INSERT_ROW_TAIL); +prototype_redo_exec_hook(REDO_INSERT_ROW_HEAD); +prototype_redo_exec_hook(REDO_PURGE_ROW_HEAD); +prototype_redo_exec_hook(REDO_PURGE_ROW_TAIL); +prototype_redo_exec_hook(REDO_FREE_HEAD_OR_TAIL); +prototype_redo_exec_hook(REDO_FREE_BLOCKS); +prototype_redo_exec_hook(REDO_DELETE_ALL); +prototype_redo_exec_hook(REDO_INDEX); +prototype_redo_exec_hook(REDO_INDEX_NEW_PAGE); +prototype_redo_exec_hook(REDO_INDEX_FREE_PAGE); +prototype_redo_exec_hook(REDO_BITMAP_NEW_PAGE); +prototype_redo_exec_hook(UNDO_ROW_INSERT); +prototype_redo_exec_hook(UNDO_ROW_DELETE); +prototype_redo_exec_hook(UNDO_ROW_UPDATE); +prototype_redo_exec_hook(UNDO_KEY_INSERT); +prototype_redo_exec_hook(UNDO_KEY_DELETE); +prototype_redo_exec_hook(UNDO_KEY_DELETE_WITH_ROOT); +prototype_redo_exec_hook(COMMIT); +prototype_redo_exec_hook(CLR_END); +prototype_redo_exec_hook(DEBUG_INFO); +prototype_undo_exec_hook(UNDO_ROW_INSERT); +prototype_undo_exec_hook(UNDO_ROW_DELETE); +prototype_undo_exec_hook(UNDO_ROW_UPDATE); +prototype_undo_exec_hook(UNDO_KEY_INSERT); +prototype_undo_exec_hook(UNDO_KEY_DELETE); +prototype_undo_exec_hook(UNDO_KEY_DELETE_WITH_ROOT); +prototype_undo_exec_hook(UNDO_BULK_INSERT); + +static int run_redo_phase(LSN lsn, enum maria_apply_log_way apply); +static uint end_of_redo_phase(my_bool prepare_for_undo_phase); +static int run_undo_phase(uint uncommitted); +static void display_record_position(const LOG_DESC *log_desc, + const TRANSLOG_HEADER_BUFFER *rec, + uint number); +static int display_and_apply_record(const LOG_DESC *log_desc, + const TRANSLOG_HEADER_BUFFER *rec); +static MARIA_HA *get_MARIA_HA_from_REDO_record(const + TRANSLOG_HEADER_BUFFER *rec); +static MARIA_HA *get_MARIA_HA_from_UNDO_record(const + TRANSLOG_HEADER_BUFFER *rec); +static void prepare_table_for_close(MARIA_HA *info, TRANSLOG_ADDRESS horizon); +static LSN parse_checkpoint_record(LSN lsn); +static void new_transaction(uint16 sid, TrID long_id, LSN undo_lsn, + LSN first_undo_lsn); +static int new_table(uint16 sid, const char *name, LSN lsn_of_file_id); +static int new_page(uint32 fileid, pgcache_page_no_t pageid, LSN rec_lsn, + struct st_dirty_page *dirty_page); +static int close_all_tables(void); +static my_bool close_one_table(const char *name, TRANSLOG_ADDRESS addr); +static void print_redo_phase_progress(TRANSLOG_ADDRESS addr); +static void delete_all_transactions(); + +/** @brief global [out] buffer for translog_read_record(); never shrinks */ +static struct +{ + /* + uchar* is more adapted (less casts) than char*, thus we don't use + LEX_STRING. + */ + uchar *str; + size_t length; +} log_record_buffer; +static void enlarge_buffer(const TRANSLOG_HEADER_BUFFER *rec) +{ + if (log_record_buffer.length < rec->record_length) + { + log_record_buffer.length= rec->record_length; + log_record_buffer.str= my_realloc(log_record_buffer.str, + rec->record_length, + MYF(MY_WME | MY_ALLOW_ZERO_PTR)); + } +} +/** @brief Tells what kind of progress message was printed to the error log */ +static enum recovery_message_type +{ + REC_MSG_NONE= 0, REC_MSG_REDO, REC_MSG_UNDO, REC_MSG_FLUSH +} recovery_message_printed; + + +/* Hook to ensure we get nicer output if we get an error */ + +int maria_recover_error_handler_hook(uint error, const char *str, + myf flags) +{ + if (procent_printed) + { + procent_printed= 0; + fputc('\n', stderr); + fflush(stderr); + } + return (*save_error_handler_hook)(error, str, flags); +} + +/* Define this if you want gdb to break in some interesting situations */ +#define ALERT_USER() + +static void print_preamble() +{ + ma_message_no_user(ME_JUST_INFO, "starting recovery"); +} + + +/** + @brief Recovers from the last checkpoint. + + Runs the REDO phase using special structures, then sets up the playground + of runtime: recreates transactions inside trnman, open tables with their + two-byte-id mapping; takes a checkpoint and runs the UNDO phase. Closes all + tables. + + @return Operation status + @retval 0 OK + @retval !=0 Error +*/ + +int maria_recovery_from_log(void) +{ + int res= 1; + FILE *trace_file; + uint warnings_count; + DBUG_ENTER("maria_recovery_from_log"); + + DBUG_ASSERT(!maria_in_recovery); + maria_in_recovery= TRUE; + +#ifdef EXTRA_DEBUG + trace_file= fopen("maria_recovery.trace", "a+"); +#else + trace_file= NULL; /* no trace file for being fast */ +#endif + tprint(trace_file, "TRACE of the last MARIA recovery from mysqld\n"); + DBUG_ASSERT(maria_pagecache->inited); + res= maria_apply_log(LSN_IMPOSSIBLE, MARIA_LOG_APPLY, trace_file, + TRUE, TRUE, TRUE, &warnings_count); + if (!res) + { + if (warnings_count == 0) + tprint(trace_file, "SUCCESS\n"); + else + tprint(trace_file, "DOUBTFUL (%u warnings, check previous output)\n", + warnings_count); + } + if (trace_file) + fclose(trace_file); + maria_in_recovery= FALSE; + DBUG_RETURN(res); +} + + +/** + @brief Displays and/or applies the log + + @param from_lsn LSN from which log reading/applying should start; + LSN_IMPOSSIBLE means "use last checkpoint" + @param apply how log records should be applied or not + @param trace_file trace file where progress/debug messages will go + @param skip_DDLs_arg Should DDL records (CREATE/RENAME/DROP/REPAIR) + be skipped by the REDO phase or not + @param take_checkpoints Should we take checkpoints or not. + @param[out] warnings_count Count of warnings will be put there + + @todo This trace_file thing is primitive; soon we will make it similar to + ma_check_print_warning() etc, and a successful recovery does not need to + create a trace file. But for debugging now it is useful. + + @return Operation status + @retval 0 OK + @retval !=0 Error +*/ + +int maria_apply_log(LSN from_lsn, enum maria_apply_log_way apply, + FILE *trace_file, + my_bool should_run_undo_phase, my_bool skip_DDLs_arg, + my_bool take_checkpoints, uint *warnings_count) +{ + int error= 0; + uint uncommitted_trans; + ulonglong old_now; + DBUG_ENTER("maria_apply_log"); + + DBUG_ASSERT(apply == MARIA_LOG_APPLY || !should_run_undo_phase); + DBUG_ASSERT(!maria_multi_threaded); + recovery_warnings= 0; + /* checkpoints can happen only if TRNs have been built */ + DBUG_ASSERT(should_run_undo_phase || !take_checkpoints); + all_active_trans= (struct st_trn_for_recovery *) + my_malloc((SHORT_TRID_MAX + 1) * sizeof(struct st_trn_for_recovery), + MYF(MY_ZEROFILL)); + all_tables= (struct st_table_for_recovery *) + my_malloc((SHARE_ID_MAX + 1) * sizeof(struct st_table_for_recovery), + MYF(MY_ZEROFILL)); + + save_error_handler_hook= error_handler_hook; + error_handler_hook= maria_recover_error_handler_hook; + + if (!all_active_trans || !all_tables) + goto err; + + if (take_checkpoints && ma_checkpoint_init(0)) + goto err; + + recovery_message_printed= REC_MSG_NONE; + checkpoint_useful= trns_created= FALSE; + tracef= trace_file; +#ifdef INSTANT_FLUSH_OF_MESSAGES + /* enable this for instant flush of messages to trace file */ + setbuf(tracef, NULL); +#endif + skip_DDLs= skip_DDLs_arg; + skipped_undo_phase= 0; + + if (from_lsn == LSN_IMPOSSIBLE) + { + if (last_checkpoint_lsn == LSN_IMPOSSIBLE) + { + from_lsn= translog_first_lsn_in_log(); + if (unlikely(from_lsn == LSN_ERROR)) + goto err; + } + else + { + from_lsn= parse_checkpoint_record(last_checkpoint_lsn); + if (from_lsn == LSN_ERROR) + goto err; + } + } + + now= my_getsystime(); + in_redo_phase= TRUE; + if (run_redo_phase(from_lsn, apply)) + { + ma_message_no_user(0, "Redo phase failed"); + goto err; + } + + if ((uncommitted_trans= + end_of_redo_phase(should_run_undo_phase)) == (uint)-1) + { + ma_message_no_user(0, "End of redo phase failed"); + goto err; + } + in_redo_phase= FALSE; + + old_now= now; + now= my_getsystime(); + if (recovery_message_printed == REC_MSG_REDO) + { + double phase_took= (now - old_now)/10000000.0; + /* + Detailed progress info goes to stderr, because ma_message_no_user() + cannot put several messages on one line. + */ + procent_printed= 1; + fprintf(stderr, " (%.1f seconds); ", phase_took); + fflush(stderr); + } + + /** + REDO phase does not fill blocks' rec_lsn, so a checkpoint now would be + wrong: if a future recovery used it, the REDO phase would always + start from the checkpoint and never from before, wrongly skipping REDOs + (tested). Another problem is that the REDO phase uses + PAGECACHE_PLAIN_PAGE, while Checkpoint only collects PAGECACHE_LSN_PAGE. + + @todo fix this. pagecache_write() now can have a rec_lsn argument. And we + could make a function which goes through pages at end of REDO phase and + changes their type. + */ +#ifdef FIX_AND_ENABLE_LATER + if (take_checkpoints && checkpoint_useful) + { + /* + We take a checkpoint as it can save future recovery work if we crash + during the UNDO phase. But we don't flush pages, as UNDOs will change + them again probably. + If we wanted to take checkpoints in the middle of the REDO phase, at a + moment when we haven't reached the end of log so don't have exact data + about transactions, we could write a special checkpoint: containing only + the list of dirty pages, otherwise to be treated as if it was at the + same LSN as the last checkpoint. + */ + if (ma_checkpoint_execute(CHECKPOINT_INDIRECT, FALSE)) + goto err; + } +#endif + + if (should_run_undo_phase) + { + if (run_undo_phase(uncommitted_trans)) + { + ma_message_no_user(0, "Undo phase failed"); + goto err; + } + } + else if (uncommitted_trans > 0) + { + eprint(tracef, "***WARNING: %u uncommitted transactions; some tables may" + " be left inconsistent!***", uncommitted_trans); + recovery_warnings++; + } + + if (skipped_undo_phase) + { + /* + We could want to print a list of tables for which UNDOs were skipped, + but not one line per skipped UNDO. + */ + eprint(tracef, "***WARNING: %lu UNDO records skipped in UNDO phase; some" + " tables may be left inconsistent!***", skipped_undo_phase); + recovery_warnings++; + } + + old_now= now; + now= my_getsystime(); + if (recovery_message_printed == REC_MSG_UNDO) + { + double phase_took= (now - old_now)/10000000.0; + procent_printed= 1; + fprintf(stderr, " (%.1f seconds); ", phase_took); + fflush(stderr); + } + + /* + we don't use maria_panic() because it would maria_end(), and Recovery does + not want that (we want to keep some modules initialized for runtime). + */ + if (close_all_tables()) + { + ma_message_no_user(0, "closing of tables failed"); + goto err; + } + + old_now= now; + now= my_getsystime(); + if (recovery_message_printed == REC_MSG_FLUSH) + { + double phase_took= (now - old_now)/10000000.0; + procent_printed= 1; + fprintf(stderr, " (%.1f seconds); ", phase_took); + fflush(stderr); + } + + if (take_checkpoints && checkpoint_useful) + { + /* No dirty pages, all tables are closed, no active transactions, save: */ + if (ma_checkpoint_execute(CHECKPOINT_FULL, FALSE)) + goto err; + } + + goto end; +err: + error= 1; + tprint(tracef, "\nRecovery of tables with transaction logs FAILED\n"); + if (trns_created) + delete_all_transactions(); +end: + error_handler_hook= save_error_handler_hook; + hash_free(&all_dirty_pages); + bzero(&all_dirty_pages, sizeof(all_dirty_pages)); + my_free(dirty_pages_pool, MYF(MY_ALLOW_ZERO_PTR)); + dirty_pages_pool= NULL; + my_free(all_tables, MYF(MY_ALLOW_ZERO_PTR)); + all_tables= NULL; + my_free(all_active_trans, MYF(MY_ALLOW_ZERO_PTR)); + all_active_trans= NULL; + my_free(log_record_buffer.str, MYF(MY_ALLOW_ZERO_PTR)); + log_record_buffer.str= NULL; + log_record_buffer.length= 0; + ma_checkpoint_end(); + *warnings_count= recovery_warnings; + if (recovery_message_printed != REC_MSG_NONE) + { + if (procent_printed) + { + procent_printed= 0; + fprintf(stderr, "\n"); + fflush(stderr); + } + if (!error) + ma_message_no_user(ME_JUST_INFO, "recovery done"); + } + if (error) + my_message(HA_ERR_INITIALIZATION, + "Maria recovery failed. Please run maria_chk -r on all maria " + "tables and delete all maria_log.######## files", MYF(0)); + procent_printed= 0; + /* + We don't cleanly close tables if we hit some error (may corrupt them by + flushing some wrong blocks made from wrong REDOs). It also leaves their + open_count>0, which ensures that --maria-recover, if used, will try to + repair them. + */ + DBUG_RETURN(error); +} + + +/* very basic info about the record's header */ +static void display_record_position(const LOG_DESC *log_desc, + const TRANSLOG_HEADER_BUFFER *rec, + uint number) +{ + /* + if number==0, we're going over records which we had already seen and which + form a group, so we indent below the group's end record + */ + tprint(tracef, + "%sRec#%u LSN (%lu,0x%lx) short_trid %u %s(num_type:%u) len %lu\n", + number ? "" : " ", number, LSN_IN_PARTS(rec->lsn), + rec->short_trid, log_desc->name, rec->type, + (ulong)rec->record_length); + if (rec->type == LOGREC_DEBUG_INFO) + { + /* Print some extra information */ + (*log_desc->record_execute_in_redo_phase)(rec); + } +} + + +static int display_and_apply_record(const LOG_DESC *log_desc, + const TRANSLOG_HEADER_BUFFER *rec) +{ + int error; + if (log_desc->record_execute_in_redo_phase == NULL) + { + /* die on all not-yet-handled records :) */ + DBUG_ASSERT("one more hook" == "to write"); + return 1; + } + if ((error= (*log_desc->record_execute_in_redo_phase)(rec))) + eprint(tracef, "Got error %d when executing record %s", + my_errno, log_desc->name); + return error; +} + + +prototype_redo_exec_hook(LONG_TRANSACTION_ID) +{ + uint16 sid= rec->short_trid; + TrID long_trid= all_active_trans[sid].long_trid; + /* + Any incomplete group should be of an old crash which already had a + recovery and thus has logged INCOMPLETE_GROUP which we must have seen. + */ + DBUG_ASSERT(all_active_trans[sid].group_start_lsn == LSN_IMPOSSIBLE); + if (long_trid != 0) + { + LSN ulsn= all_active_trans[sid].undo_lsn; + /* + If the first record of that transaction is after 'rec', it's probably + because that transaction was found in the checkpoint record, and then + it's ok, we can forget about that transaction (we'll meet it later + again in the REDO phase) and replace it with the one in 'rec'. + */ + if ((ulsn != LSN_IMPOSSIBLE) && + (cmp_translog_addr(ulsn, rec->lsn) < 0)) + { + char llbuf[22]; + llstr(long_trid, llbuf); + eprint(tracef, "Found an old transaction long_trid %s short_trid %u" + " with same short id as this new transaction, and has neither" + " committed nor rollback (undo_lsn: (%lu,0x%lx))", + llbuf, sid, LSN_IN_PARTS(ulsn)); + goto err; + } + } + long_trid= uint6korr(rec->header); + new_transaction(sid, long_trid, LSN_IMPOSSIBLE, LSN_IMPOSSIBLE); + goto end; +err: + ALERT_USER(); + return 1; +end: + return 0; +} + + +static void new_transaction(uint16 sid, TrID long_id, LSN undo_lsn, + LSN first_undo_lsn) +{ + char llbuf[22]; + all_active_trans[sid].long_trid= long_id; + llstr(long_id, llbuf); + tprint(tracef, "Transaction long_trid %s short_trid %u starts," + " undo_lsn (%lu,0x%lx) first_undo_lsn (%lu,0x%lx)\n", + llbuf, sid, LSN_IN_PARTS(undo_lsn), LSN_IN_PARTS(first_undo_lsn)); + all_active_trans[sid].undo_lsn= undo_lsn; + all_active_trans[sid].first_undo_lsn= first_undo_lsn; + set_if_bigger(max_long_trid, long_id); +} + + +prototype_redo_exec_hook_dummy(CHECKPOINT) +{ + /* the only checkpoint we care about was found via control file, ignore */ + return 0; +} + + +prototype_redo_exec_hook_dummy(INCOMPLETE_GROUP) +{ + /* abortion was already made */ + return 0; +} + + +prototype_redo_exec_hook(INCOMPLETE_LOG) +{ + MARIA_HA *info; + if (skip_DDLs) + { + tprint(tracef, "we skip DDLs\n"); + return 0; + } + if ((info= get_MARIA_HA_from_REDO_record(rec)) == NULL) + { + /* no such table, don't need to warn */ + return 0; + } + /* + Example of what can go wrong when replaying DDLs: + CREATE TABLE t (logged); INSERT INTO t VALUES(1) (logged); + ALTER TABLE t ... which does + CREATE a temporary table #sql... (logged) + INSERT data from t into #sql... (not logged) + RENAME #sql TO t (logged) + Removing tables by hand and replaying the log will leave in the + end an empty table "t": missing records. If after the RENAME an INSERT + into t was done, that row had number 1 in its page, executing the + REDO_INSERT_ROW_HEAD on the recreated empty t will fail (assertion + failure in _ma_apply_redo_insert_row_head_or_tail(): new data page is + created whereas rownr is not 0). + So when the server disables logging for ALTER TABLE or CREATE SELECT, it + logs LOGREC_INCOMPLETE_LOG to warn maria_read_log and then the user. + + Another issue is that replaying of DDLs is not correct enough to work if + there was a crash during a DDL (see comment in execution of + REDO_RENAME_TABLE ). + */ + tprint(tracef, "***WARNING: MySQL server currently logs no records" + " about insertion of data by ALTER TABLE and CREATE SELECT," + " as they are not necessary for recovery;" + " present applying of log records may well not work.***\n"); + recovery_warnings++; + return 0; +} + + +prototype_redo_exec_hook(REDO_CREATE_TABLE) +{ + File dfile= -1, kfile= -1; + char *linkname_ptr, filename[FN_REFLEN], *name, *ptr, *ptr2, + *data_file_name, *index_file_name; + uchar *kfile_header; + myf create_flag; + uint flags; + int error= 1, create_mode= O_RDWR | O_TRUNC, i; + MARIA_HA *info= NULL; + uint kfile_size_before_extension, keystart; + + if (skip_DDLs) + { + tprint(tracef, "we skip DDLs\n"); + return 0; + } + enlarge_buffer(rec); + if (log_record_buffer.str == NULL || + translog_read_record(rec->lsn, 0, rec->record_length, + log_record_buffer.str, NULL) != + rec->record_length) + { + eprint(tracef, "Failed to read record"); + goto end; + } + name= (char *)log_record_buffer.str; + /* + TRUNCATE TABLE and REPAIR USE_FRM call maria_create(), so below we can + find a REDO_CREATE_TABLE for a table which we have open, that's why we + need to look for any open instances and close them first. + */ + if (close_one_table(name, rec->lsn)) + { + eprint(tracef, "Table '%s' got error %d on close", name, my_errno); + ALERT_USER(); + goto end; + } + /* we try hard to get create_rename_lsn, to avoid mistakes if possible */ + info= maria_open(name, O_RDONLY, HA_OPEN_FOR_REPAIR); + if (info) + { + MARIA_SHARE *share= info->s; + /* check that we're not already using it */ + if (share->reopen != 1) + { + eprint(tracef, "Table '%s is already open (reopen=%u)", + name, share->reopen); + ALERT_USER(); + goto end; + } + DBUG_ASSERT(share->now_transactional == share->base.born_transactional); + if (!share->base.born_transactional) + { + /* + could be that transactional table was later dropped, and a non-trans + one was renamed to its name, thus create_rename_lsn is 0 and should + not be trusted. + */ + tprint(tracef, "Table '%s' is not transactional, ignoring creation\n", + name); + ALERT_USER(); + error= 0; + goto end; + } + if (cmp_translog_addr(share->state.create_rename_lsn, rec->lsn) >= 0) + { + tprint(tracef, "Table '%s' has create_rename_lsn (%lu,0x%lx) more " + "recent than record, ignoring creation", + name, LSN_IN_PARTS(share->state.create_rename_lsn)); + error= 0; + goto end; + } + if (maria_is_crashed(info)) + { + eprint(tracef, "Table '%s' is crashed, can't recreate it", name); + ALERT_USER(); + goto end; + } + maria_close(info); + info= NULL; + } + else /* one or two files absent, or header corrupted... */ + tprint(tracef, "Table '%s' can't be opened, probably does not exist\n", + name); + /* if does not exist, or is older, overwrite it */ + ptr= name + strlen(name) + 1; + if ((flags= ptr[0] ? HA_DONT_TOUCH_DATA : 0)) + tprint(tracef, ", we will only touch index file"); + ptr++; + kfile_size_before_extension= uint2korr(ptr); + ptr+= 2; + keystart= uint2korr(ptr); + ptr+= 2; + kfile_header= (uchar *)ptr; + ptr+= kfile_size_before_extension; + /* set header lsns */ + ptr2= (char *) kfile_header + sizeof(info->s->state.header) + + MARIA_FILE_CREATE_RENAME_LSN_OFFSET; + for (i= 0; i<3; i++) + { + lsn_store(ptr2, rec->lsn); + ptr2+= LSN_STORE_SIZE; + } + data_file_name= ptr; + ptr+= strlen(data_file_name) + 1; + index_file_name= ptr; + ptr+= strlen(index_file_name) + 1; + /** @todo handle symlinks */ + if (data_file_name[0] || index_file_name[0]) + { + eprint(tracef, "Table '%s' DATA|INDEX DIRECTORY clauses are not handled", + name); + goto end; + } + fn_format(filename, name, "", MARIA_NAME_IEXT, + (MY_UNPACK_FILENAME | + (flags & HA_DONT_TOUCH_DATA) ? MY_RETURN_REAL_PATH : 0) | + MY_APPEND_EXT); + linkname_ptr= NULL; + create_flag= MY_DELETE_OLD; + tprint(tracef, "Table '%s' creating as '%s'\n", name, filename); + if ((kfile= my_create_with_symlink(linkname_ptr, filename, 0, create_mode, + MYF(MY_WME|create_flag))) < 0) + { + eprint(tracef, "Failed to create index file"); + goto end; + } + if (my_pwrite(kfile, kfile_header, + kfile_size_before_extension, 0, MYF(MY_NABP|MY_WME)) || + my_chsize(kfile, keystart, 0, MYF(MY_WME))) + { + eprint(tracef, "Failed to write to index file"); + goto end; + } + if (!(flags & HA_DONT_TOUCH_DATA)) + { + fn_format(filename,name,"", MARIA_NAME_DEXT, + MY_UNPACK_FILENAME | MY_APPEND_EXT); + linkname_ptr= NULL; + create_flag=MY_DELETE_OLD; + if (((dfile= + my_create_with_symlink(linkname_ptr, filename, 0, create_mode, + MYF(MY_WME | create_flag))) < 0) || + my_close(dfile, MYF(MY_WME))) + { + eprint(tracef, "Failed to create data file"); + goto end; + } + /* + we now have an empty data file. To be able to + _ma_initialize_data_file() we need some pieces of the share to be + correctly filled. So we just open the table (fortunately, an empty + data file does not preclude this). + */ + if (((info= maria_open(name, O_RDONLY, 0)) == NULL) || + _ma_initialize_data_file(info->s, info->dfile.file)) + { + eprint(tracef, "Failed to open new table or write to data file"); + goto end; + } + } + error= 0; +end: + if (kfile >= 0) + error|= my_close(kfile, MYF(MY_WME)); + if (info != NULL) + error|= maria_close(info); + return error; +} + + +prototype_redo_exec_hook(REDO_RENAME_TABLE) +{ + char *old_name, *new_name; + int error= 1; + MARIA_HA *info= NULL; + if (skip_DDLs) + { + tprint(tracef, "we skip DDLs\n"); + return 0; + } + enlarge_buffer(rec); + if (log_record_buffer.str == NULL || + translog_read_record(rec->lsn, 0, rec->record_length, + log_record_buffer.str, NULL) != + rec->record_length) + { + eprint(tracef, "Failed to read record"); + goto end; + } + old_name= (char *)log_record_buffer.str; + new_name= old_name + strlen(old_name) + 1; + tprint(tracef, "Table '%s' to rename to '%s'; old-name table ", old_name, + new_name); + /* + Here is why we skip CREATE/DROP/RENAME when doing a recovery from + ha_maria (whereas we do when called from maria_read_log). Consider: + CREATE TABLE t; + RENAME TABLE t to u; + DROP TABLE u; + RENAME TABLE v to u; # crash between index rename and data rename. + And do a Recovery (not removing tables beforehand). + Recovery replays CREATE, then RENAME: the maria_open("t") works, + maria_open("u") does not (no data file) so table "u" is considered + inexistent and so maria_rename() is done which overwrites u's index file, + which is lost. Ok, the data file (v.MAD) is still available, but only a + REPAIR USE_FRM can rebuild the index, which is unsafe and downtime. + So it is preferrable to not execute RENAME, and leave the "mess" of files, + rather than possibly destroy a file. DBA will manually rename files. + A safe recovery method would probably require checking the existence of + the index file and of the data file separately (not via maria_open()), and + maybe also to store a create_rename_lsn in the data file too + For now, all we risk is to leave the mess (half-renamed files) left by the + crash. We however sync files and directories at each file rename. The SQL + layer is anyway not crash-safe for DDLs (except the repartioning-related + ones). + We replay DDLs in maria_read_log to be able to recreate tables from + scratch. It means that "maria_read_log -a" should not be used on a + database which just crashed during a DDL. And also ALTER TABLE does not + log insertions of records into the temporary table, so replaying may + fail (grep for INCOMPLETE_LOG in files). + */ + info= maria_open(old_name, O_RDONLY, HA_OPEN_FOR_REPAIR); + if (info) + { + MARIA_SHARE *share= info->s; + if (!share->base.born_transactional) + { + tprint(tracef, ", is not transactional, ignoring renaming\n"); + ALERT_USER(); + error= 0; + goto end; + } + if (cmp_translog_addr(share->state.create_rename_lsn, rec->lsn) >= 0) + { + tprint(tracef, ", has create_rename_lsn (%lu,0x%lx) more recent than" + " record, ignoring renaming", + LSN_IN_PARTS(share->state.create_rename_lsn)); + error= 0; + goto end; + } + if (maria_is_crashed(info)) + { + tprint(tracef, ", is crashed, can't rename it"); + ALERT_USER(); + goto end; + } + if (close_one_table(info->s->open_file_name.str, rec->lsn) || + maria_close(info)) + goto end; + info= NULL; + tprint(tracef, ", is ok for renaming; new-name table "); + } + else /* one or two files absent, or header corrupted... */ + { + tprint(tracef, ", can't be opened, probably does not exist"); + error= 0; + goto end; + } + /* + We must also check the create_rename_lsn of the 'new_name' table if it + exists: otherwise we may, with our rename which overwrites, destroy + another table. For example: + CREATE TABLE t; + RENAME t to u; + DROP TABLE u; + RENAME v to u; # v is an old table, its creation/insertions not in log + And start executing the log (without removing tables beforehand): creates + t, renames it to u (if not testing create_rename_lsn) thus overwriting + old-named v, drops u, and we are stuck, we have lost data. + */ + info= maria_open(new_name, O_RDONLY, HA_OPEN_FOR_REPAIR); + if (info) + { + MARIA_SHARE *share= info->s; + /* We should not have open instances on this table. */ + if (share->reopen != 1) + { + tprint(tracef, ", is already open (reopen=%u)\n", share->reopen); + ALERT_USER(); + goto end; + } + if (!share->base.born_transactional) + { + tprint(tracef, ", is not transactional, ignoring renaming\n"); + ALERT_USER(); + goto drop; + } + if (cmp_translog_addr(share->state.create_rename_lsn, rec->lsn) >= 0) + { + tprint(tracef, ", has create_rename_lsn (%lu,0x%lx) more recent than" + " record, ignoring renaming", + LSN_IN_PARTS(share->state.create_rename_lsn)); + /* + We have to drop the old_name table. Consider: + CREATE TABLE t; + CREATE TABLE v; + RENAME TABLE t to u; + DROP TABLE u; + RENAME TABLE v to u; + and apply the log without removing tables beforehand. t will be + created, v too; in REDO_RENAME u will be more recent, but we still + have to drop t otherwise it stays. + */ + goto drop; + } + if (maria_is_crashed(info)) + { + tprint(tracef, ", is crashed, can't rename it"); + ALERT_USER(); + goto end; + } + if (maria_close(info)) + goto end; + info= NULL; + /* abnormal situation */ + tprint(tracef, ", exists but is older than record, can't rename it"); + goto end; + } + else /* one or two files absent, or header corrupted... */ + tprint(tracef, ", can't be opened, probably does not exist"); + tprint(tracef, ", renaming '%s'", old_name); + if (maria_rename(old_name, new_name)) + { + eprint(tracef, "Failed to rename table"); + goto end; + } + info= maria_open(new_name, O_RDONLY, 0); + if (info == NULL) + { + eprint(tracef, "Failed to open renamed table"); + goto end; + } + if (_ma_update_state_lsns(info->s, rec->lsn, info->s->state.create_trid, + TRUE, TRUE)) + goto end; + if (maria_close(info)) + goto end; + info= NULL; + error= 0; + goto end; +drop: + tprint(tracef, ", only dropping '%s'", old_name); + if (maria_delete_table(old_name)) + { + eprint(tracef, "Failed to drop table"); + goto end; + } + error= 0; + goto end; +end: + tprint(tracef, "\n"); + if (info != NULL) + error|= maria_close(info); + return error; +} + + +/* + The record may come from REPAIR, ALTER TABLE ENABLE KEYS, OPTIMIZE. +*/ +prototype_redo_exec_hook(REDO_REPAIR_TABLE) +{ + int error= 1; + MARIA_HA *info; + HA_CHECK param; + char *name; + my_bool quick_repair; + DBUG_ENTER("exec_REDO_LOGREC_REDO_REPAIR_TABLE"); + + if (skip_DDLs) + { + /* + REPAIR is not exactly a DDL, but it manipulates files without logging + insertions into them. + */ + tprint(tracef, "we skip DDLs\n"); + DBUG_RETURN(0); + } + if ((info= get_MARIA_HA_from_REDO_record(rec)) == NULL) + DBUG_RETURN(0); + + /* + Otherwise, the mapping is newer than the table, and our record is newer + than the mapping, so we can repair. + */ + tprint(tracef, " repairing...\n"); + + maria_chk_init(¶m); + param.isam_file_name= name= info->s->open_file_name.str; + param.testflag= uint8korr(rec->header + FILEID_STORE_SIZE); + param.tmpdir= maria_tmpdir; + DBUG_ASSERT(maria_tmpdir); + + info->s->state.key_map= uint8korr(rec->header + FILEID_STORE_SIZE + 8); + quick_repair= test(param.testflag & T_QUICK); + + if (param.testflag & T_REP_PARALLEL) + { + if (maria_repair_parallel(¶m, info, name, quick_repair)) + goto end; + } + else if (param.testflag & T_REP_BY_SORT) + { + if (maria_repair_by_sort(¶m, info, name, quick_repair)) + goto end; + } + else if (maria_repair(¶m, info, name, quick_repair)) + goto end; + + if (_ma_update_state_lsns(info->s, rec->lsn, trnman_get_min_safe_trid(), + TRUE, !(param.testflag & T_NO_CREATE_RENAME_LSN))) + goto end; + error= 0; + +end: + DBUG_RETURN(error); +} + + +prototype_redo_exec_hook(REDO_DROP_TABLE) +{ + char *name; + int error= 1; + MARIA_HA *info; + if (skip_DDLs) + { + tprint(tracef, "we skip DDLs\n"); + return 0; + } + enlarge_buffer(rec); + if (log_record_buffer.str == NULL || + translog_read_record(rec->lsn, 0, rec->record_length, + log_record_buffer.str, NULL) != + rec->record_length) + { + eprint(tracef, "Failed to read record"); + return 1; + } + name= (char *)log_record_buffer.str; + tprint(tracef, "Table '%s'", name); + info= maria_open(name, O_RDONLY, HA_OPEN_FOR_REPAIR); + if (info) + { + MARIA_SHARE *share= info->s; + if (!share->base.born_transactional) + { + tprint(tracef, ", is not transactional, ignoring removal\n"); + ALERT_USER(); + error= 0; + goto end; + } + if (cmp_translog_addr(share->state.create_rename_lsn, rec->lsn) >= 0) + { + tprint(tracef, ", has create_rename_lsn (%lu,0x%lx) more recent than" + " record, ignoring removal", + LSN_IN_PARTS(share->state.create_rename_lsn)); + error= 0; + goto end; + } + if (maria_is_crashed(info)) + { + tprint(tracef, ", is crashed, can't drop it"); + ALERT_USER(); + goto end; + } + if (close_one_table(info->s->open_file_name.str, rec->lsn) || + maria_close(info)) + goto end; + info= NULL; + /* if it is older, or its header is corrupted, drop it */ + tprint(tracef, ", dropping '%s'", name); + if (maria_delete_table(name)) + { + eprint(tracef, "Failed to drop table"); + goto end; + } + } + else /* one or two files absent, or header corrupted... */ + tprint(tracef,", can't be opened, probably does not exist"); + error= 0; +end: + tprint(tracef, "\n"); + if (info != NULL) + error|= maria_close(info); + return error; +} + + +prototype_redo_exec_hook(FILE_ID) +{ + uint16 sid; + int error= 1; + const char *name; + MARIA_HA *info; + DBUG_ENTER("exec_REDO_LOGREC_FILE_ID"); + + if (cmp_translog_addr(rec->lsn, checkpoint_start) < 0) + { + /* + If that mapping was still true at checkpoint time, it was found in + checkpoint record, no need to recreate it. If that mapping had ended at + checkpoint time (table was closed or repaired), a flush and force + happened and so mapping is not needed. + */ + tprint(tracef, "ignoring because before checkpoint\n"); + DBUG_RETURN(0); + } + + enlarge_buffer(rec); + if (log_record_buffer.str == NULL || + translog_read_record(rec->lsn, 0, rec->record_length, + log_record_buffer.str, NULL) != + rec->record_length) + { + eprint(tracef, "Failed to read record"); + goto end; + } + sid= fileid_korr(log_record_buffer.str); + info= all_tables[sid].info; + if (info != NULL) + { + tprint(tracef, " Closing table '%s'\n", info->s->open_file_name.str); + prepare_table_for_close(info, rec->lsn); + if (maria_close(info)) + { + eprint(tracef, "Failed to close table"); + goto end; + } + all_tables[sid].info= NULL; + } + name= (char *)log_record_buffer.str + FILEID_STORE_SIZE; + if (new_table(sid, name, rec->lsn)) + goto end; + error= 0; +end: + DBUG_RETURN(error); +} + + +static int new_table(uint16 sid, const char *name, LSN lsn_of_file_id) +{ + /* + -1 (skip table): close table and return 0; + 1 (error): close table and return 1; + 0 (success): leave table open and return 0. + */ + int error= 1; + MARIA_HA *info; + MARIA_SHARE *share; + my_off_t dfile_len, kfile_len; + + checkpoint_useful= TRUE; + if ((name == NULL) || (name[0] == 0)) + { + /* + we didn't use DBUG_ASSERT() because such record corruption could + silently pass in the "info == NULL" test below. + */ + tprint(tracef, ", record is corrupted"); + info= NULL; + goto end; + } + tprint(tracef, "Table '%s', id %u", name, sid); + info= maria_open(name, O_RDWR, HA_OPEN_FOR_REPAIR); + if (info == NULL) + { + tprint(tracef, ", is absent (must have been dropped later?)" + " or its header is so corrupted that we cannot open it;" + " we skip it"); + error= 0; + goto end; + } + share= info->s; + /* check that we're not already using it */ + if (share->reopen != 1) + { + tprint(tracef, ", is already open (reopen=%u)\n", share->reopen); + /* + It could be that we have in the log + FILE_ID(t1,10) ... (t1 was flushed) ... FILE_ID(t1,12); + */ + if (close_one_table(share->open_file_name.str, lsn_of_file_id)) + goto end; + } + if (!share->base.born_transactional) + { + /* + This can happen if one converts a transactional table to a + not transactional table + */ + tprint(tracef, ", is not transactional. Ignoring open request"); + error= -1; + goto end; + } + if (cmp_translog_addr(lsn_of_file_id, share->state.create_rename_lsn) <= 0) + { + tprint(tracef, ", has create_rename_lsn (%lu,0x%lx) more recent than" + " LOGREC_FILE_ID's LSN (%lu,0x%lx), ignoring open request", + LSN_IN_PARTS(share->state.create_rename_lsn), + LSN_IN_PARTS(lsn_of_file_id)); + error= -1; + goto end; + /* + Note that we tested that before testing corruption; a recent corrupted + table is not a blocker for the present log record. + */ + } + if (maria_is_crashed(info)) + { + eprint(tracef, "Table '%s' is crashed, skipping it. Please repair it with" + " maria_chk -r", share->open_file_name.str); + error= -1; /* not fatal, try with other tables */ + goto end; + /* + Note that if a first recovery fails to apply a REDO, it marks the table + corrupted and stops the entire recovery. A second recovery will find the + table is marked corrupted and skip it (and thus possibly handle other + tables). + */ + } + /* don't log any records for this work */ + _ma_tmp_disable_logging_for_table(info, FALSE); + /* execution of some REDO records relies on data_file_length */ + dfile_len= my_seek(info->dfile.file, 0, SEEK_END, MYF(MY_WME)); + kfile_len= my_seek(info->s->kfile.file, 0, SEEK_END, MYF(MY_WME)); + if ((dfile_len == MY_FILEPOS_ERROR) || + (kfile_len == MY_FILEPOS_ERROR)) + { + tprint(tracef, ", length unknown\n"); + goto end; + } + if (share->state.state.data_file_length != dfile_len) + { + tprint(tracef, ", has wrong state.data_file_length (fixing it)"); + share->state.state.data_file_length= dfile_len; + } + if (share->state.state.key_file_length != kfile_len) + { + tprint(tracef, ", has wrong state.key_file_length (fixing it)"); + share->state.state.key_file_length= kfile_len; + } + if ((dfile_len % share->block_size) || (kfile_len % share->block_size)) + { + tprint(tracef, ", has too short last page\n"); + /* Recovery will fix this, no error */ + ALERT_USER(); + } + /* + This LSN serves in this situation; assume log is: + FILE_ID(6->"t2") REDO_INSERT(6) FILE_ID(6->"t1") CHECKPOINT(6->"t1") + then crash, checkpoint record is parsed and opens "t1" with id 6; assume + REDO phase starts from the REDO_INSERT above: it will wrongly try to + update a page of "t1". With this LSN below, REDO_INSERT can realize the + mapping is newer than itself, and not execute. + Same example is possible with UNDO_INSERT (update of the state). + */ + info->s->lsn_of_file_id= lsn_of_file_id; + all_tables[sid].info= info; + /* + We don't set info->s->id, it would be useless (no logging in REDO phase); + if you change that, know that some records in REDO phase call + _ma_update_state_lsns() which resets info->s->id. + */ + tprint(tracef, ", opened"); + error= 0; +end: + tprint(tracef, "\n"); + if (error) + { + if (info != NULL) + maria_close(info); + if (error == -1) + error= 0; + } + return error; +} + +/* + NOTE + This is called for REDO_INSERT_ROW_HEAD and READ_NEW_ROW_HEAD +*/ + +prototype_redo_exec_hook(REDO_INSERT_ROW_HEAD) +{ + int error= 1; + uchar *buff= NULL; + MARIA_HA *info= get_MARIA_HA_from_REDO_record(rec); + if (info == NULL) + { + /* + Table was skipped at open time (because later dropped/renamed, not + transactional, or create_rename_lsn newer than LOGREC_FILE_ID), or + record was skipped due to skip_redo_lsn; it is not an error. + */ + return 0; + } + /* + Note that REDO is per page, we still consider it if its transaction + committed long ago and is unknown. + */ + /* + If REDO's LSN is > page's LSN (read from disk), we are going to modify the + page and change its LSN. The normal runtime code stores the UNDO's LSN + into the page. Here storing the REDO's LSN (rec->lsn) would work + (we are not writing to the log here, so don't have to "flush up to UNDO's + LSN"). But in a test scenario where we do updates at runtime, then remove + tables, apply the log and check that this results in the same table as at + runtime, putting the same LSN as runtime had done will decrease + differences. So we use the UNDO's LSN which is current_group_end_lsn. + */ + enlarge_buffer(rec); + if (log_record_buffer.str == NULL) + { + eprint(tracef, "Failed to read allocate buffer for record"); + goto end; + } + if (translog_read_record(rec->lsn, 0, rec->record_length, + log_record_buffer.str, NULL) != + rec->record_length) + { + eprint(tracef, "Failed to read record"); + goto end; + } + buff= log_record_buffer.str; + if (_ma_apply_redo_insert_row_head_or_tail(info, current_group_end_lsn, + HEAD_PAGE, + (rec->type == + LOGREC_REDO_NEW_ROW_HEAD), + buff + FILEID_STORE_SIZE, + buff + + FILEID_STORE_SIZE + + PAGE_STORE_SIZE + + DIRPOS_STORE_SIZE, + rec->record_length - + (FILEID_STORE_SIZE + + PAGE_STORE_SIZE + + DIRPOS_STORE_SIZE))) + goto end; + error= 0; +end: + return error; +} + +/* + NOTE + This is called for REDO_INSERT_ROW_TAIL and READ_NEW_ROW_TAIL +*/ + +prototype_redo_exec_hook(REDO_INSERT_ROW_TAIL) +{ + int error= 1; + uchar *buff; + MARIA_HA *info= get_MARIA_HA_from_REDO_record(rec); + if (info == NULL) + return 0; + enlarge_buffer(rec); + if (log_record_buffer.str == NULL || + translog_read_record(rec->lsn, 0, rec->record_length, + log_record_buffer.str, NULL) != + rec->record_length) + { + eprint(tracef, "Failed to read record"); + goto end; + } + buff= log_record_buffer.str; + if (_ma_apply_redo_insert_row_head_or_tail(info, current_group_end_lsn, + TAIL_PAGE, + (rec->type == + LOGREC_REDO_NEW_ROW_TAIL), + buff + FILEID_STORE_SIZE, + buff + + FILEID_STORE_SIZE + + PAGE_STORE_SIZE + + DIRPOS_STORE_SIZE, + rec->record_length - + (FILEID_STORE_SIZE + + PAGE_STORE_SIZE + + DIRPOS_STORE_SIZE))) + goto end; + error= 0; + +end: + return error; +} + + +prototype_redo_exec_hook(REDO_INSERT_ROW_BLOBS) +{ + int error= 1; + uchar *buff; + uint number_of_blobs, number_of_ranges; + pgcache_page_no_t first_page, last_page; + char llbuf1[22], llbuf2[22]; + MARIA_HA *info= get_MARIA_HA_from_REDO_record(rec); + if (info == NULL) + return 0; + enlarge_buffer(rec); + if (log_record_buffer.str == NULL || + translog_read_record(rec->lsn, 0, rec->record_length, + log_record_buffer.str, NULL) != + rec->record_length) + { + eprint(tracef, "Failed to read record"); + goto end; + } + buff= log_record_buffer.str; + if (_ma_apply_redo_insert_row_blobs(info, current_group_end_lsn, + buff, rec->lsn, &number_of_blobs, + &number_of_ranges, + &first_page, &last_page)) + goto end; + llstr(first_page, llbuf1); + llstr(last_page, llbuf2); + tprint(tracef, " %u blobs %u ranges, first page %s last %s", + number_of_blobs, number_of_ranges, llbuf1, llbuf2); + + error= 0; + +end: + tprint(tracef, " \n"); + return error; +} + + +prototype_redo_exec_hook(REDO_PURGE_ROW_HEAD) +{ + int error= 1; + MARIA_HA *info= get_MARIA_HA_from_REDO_record(rec); + if (info == NULL) + return 0; + if (_ma_apply_redo_purge_row_head_or_tail(info, current_group_end_lsn, + HEAD_PAGE, + rec->header + FILEID_STORE_SIZE)) + goto end; + error= 0; +end: + return error; +} + + +prototype_redo_exec_hook(REDO_PURGE_ROW_TAIL) +{ + int error= 1; + MARIA_HA *info= get_MARIA_HA_from_REDO_record(rec); + if (info == NULL) + return 0; + if (_ma_apply_redo_purge_row_head_or_tail(info, current_group_end_lsn, + TAIL_PAGE, + rec->header + FILEID_STORE_SIZE)) + goto end; + error= 0; +end: + return error; +} + + +prototype_redo_exec_hook(REDO_FREE_BLOCKS) +{ + int error= 1; + uchar *buff; + MARIA_HA *info= get_MARIA_HA_from_REDO_record(rec); + if (info == NULL) + return 0; + enlarge_buffer(rec); + + if (log_record_buffer.str == NULL || + translog_read_record(rec->lsn, 0, rec->record_length, + log_record_buffer.str, NULL) != + rec->record_length) + { + eprint(tracef, "Failed to read record"); + goto end; + } + + buff= log_record_buffer.str; + if (_ma_apply_redo_free_blocks(info, current_group_end_lsn, + buff + FILEID_STORE_SIZE)) + goto end; + error= 0; +end: + return error; +} + + +prototype_redo_exec_hook(REDO_FREE_HEAD_OR_TAIL) +{ + int error= 1; + MARIA_HA *info= get_MARIA_HA_from_REDO_record(rec); + if (info == NULL) + return 0; + + if (_ma_apply_redo_free_head_or_tail(info, current_group_end_lsn, + rec->header + FILEID_STORE_SIZE)) + goto end; + error= 0; +end: + return error; +} + + +prototype_redo_exec_hook(REDO_DELETE_ALL) +{ + int error= 1; + MARIA_HA *info= get_MARIA_HA_from_REDO_record(rec); + if (info == NULL) + return 0; + tprint(tracef, " deleting all %lu rows\n", + (ulong)info->s->state.state.records); + if (maria_delete_all_rows(info)) + goto end; + error= 0; +end: + return error; +} + + +prototype_redo_exec_hook(REDO_INDEX) +{ + int error= 1; + MARIA_HA *info= get_MARIA_HA_from_REDO_record(rec); + if (info == NULL) + return 0; + enlarge_buffer(rec); + + if (log_record_buffer.str == NULL || + translog_read_record(rec->lsn, 0, rec->record_length, + log_record_buffer.str, NULL) != + rec->record_length) + { + eprint(tracef, "Failed to read record"); + goto end; + } + + if (_ma_apply_redo_index(info, current_group_end_lsn, + log_record_buffer.str + FILEID_STORE_SIZE, + rec->record_length - FILEID_STORE_SIZE)) + goto end; + error= 0; +end: + return error; +} + +prototype_redo_exec_hook(REDO_INDEX_NEW_PAGE) +{ + int error= 1; + MARIA_HA *info= get_MARIA_HA_from_REDO_record(rec); + if (info == NULL) + return 0; + enlarge_buffer(rec); + + if (log_record_buffer.str == NULL || + translog_read_record(rec->lsn, 0, rec->record_length, + log_record_buffer.str, NULL) != + rec->record_length) + { + eprint(tracef, "Failed to read record"); + goto end; + } + + if (_ma_apply_redo_index_new_page(info, current_group_end_lsn, + log_record_buffer.str + FILEID_STORE_SIZE, + rec->record_length - FILEID_STORE_SIZE)) + goto end; + error= 0; +end: + return error; +} + + +prototype_redo_exec_hook(REDO_INDEX_FREE_PAGE) +{ + int error= 1; + MARIA_HA *info= get_MARIA_HA_from_REDO_record(rec); + if (info == NULL) + return 0; + + if (_ma_apply_redo_index_free_page(info, current_group_end_lsn, + rec->header + FILEID_STORE_SIZE)) + goto end; + error= 0; +end: + return error; +} + + +prototype_redo_exec_hook(REDO_BITMAP_NEW_PAGE) +{ + int error= 1; + MARIA_HA *info= get_MARIA_HA_from_REDO_record(rec); + if (info == NULL) + return 0; + enlarge_buffer(rec); + + if (log_record_buffer.str == NULL || + translog_read_record(rec->lsn, 0, rec->record_length, + log_record_buffer.str, NULL) != + rec->record_length) + { + eprint(tracef, "Failed to read record"); + goto end; + } + + if (cmp_translog_addr(rec->lsn, checkpoint_start) >= 0) + { + /* + Record is potentially after the bitmap flush made by Checkpoint, so has + to be replayed. It may overwrite a more recent state but that will be + corrected by all upcoming REDOs for data pages. + If the condition is false, we must not apply the record: it is unneeded + and nocive (may not be corrected as REDOs can be skipped due to + dirty-pages list). + */ + if (_ma_apply_redo_bitmap_new_page(info, current_group_end_lsn, + log_record_buffer.str + + FILEID_STORE_SIZE)) + goto end; + } + error= 0; +end: + return error; +} + + +static inline void set_undo_lsn_for_active_trans(uint16 short_trid, LSN lsn) +{ + if (all_active_trans[short_trid].long_trid == 0) + { + /* transaction unknown, so has committed or fully rolled back long ago */ + return; + } + all_active_trans[short_trid].undo_lsn= lsn; + if (all_active_trans[short_trid].first_undo_lsn == LSN_IMPOSSIBLE) + all_active_trans[short_trid].first_undo_lsn= lsn; +} + + +prototype_redo_exec_hook(UNDO_ROW_INSERT) +{ + MARIA_HA *info= get_MARIA_HA_from_UNDO_record(rec); + MARIA_SHARE *share; + + set_undo_lsn_for_active_trans(rec->short_trid, rec->lsn); + if (info == NULL) + { + /* + Note that we set undo_lsn anyway. So that if the transaction is later + rolled back, this UNDO is tried for execution and we get a warning (as + it would then be abnormal that info==NULL). + */ + return 0; + } + share= info->s; + if (cmp_translog_addr(rec->lsn, share->state.is_of_horizon) >= 0) + { + tprint(tracef, " state has LSN (%lu,0x%lx) older than record, updating" + " rows' count\n", LSN_IN_PARTS(share->state.is_of_horizon)); + share->state.state.records++; + if (share->calc_checksum) + { + uchar buff[HA_CHECKSUM_STORE_SIZE]; + if (translog_read_record(rec->lsn, LSN_STORE_SIZE + FILEID_STORE_SIZE + + PAGE_STORE_SIZE + DIRPOS_STORE_SIZE, + HA_CHECKSUM_STORE_SIZE, buff, NULL) != + HA_CHECKSUM_STORE_SIZE) + { + eprint(tracef, "Failed to read record"); + return 1; + } + share->state.state.checksum+= ha_checksum_korr(buff); + } + info->s->state.changed|= (STATE_CHANGED | STATE_NOT_ANALYZED | + STATE_NOT_ZEROFILLED | STATE_NOT_MOVABLE); + } + tprint(tracef, " rows' count %lu\n", (ulong)info->s->state.state.records); + /* Unpin all pages, stamp them with UNDO's LSN */ + _ma_unpin_all_pages(info, rec->lsn); + return 0; +} + + +prototype_redo_exec_hook(UNDO_ROW_DELETE) +{ + MARIA_HA *info= get_MARIA_HA_from_UNDO_record(rec); + MARIA_SHARE *share; + + set_undo_lsn_for_active_trans(rec->short_trid, rec->lsn); + if (info == NULL) + return 0; + share= info->s; + if (cmp_translog_addr(rec->lsn, share->state.is_of_horizon) >= 0) + { + tprint(tracef, " state older than record\n"); + share->state.state.records--; + if (share->calc_checksum) + { + uchar buff[HA_CHECKSUM_STORE_SIZE]; + if (translog_read_record(rec->lsn, LSN_STORE_SIZE + FILEID_STORE_SIZE + + PAGE_STORE_SIZE + DIRPOS_STORE_SIZE + 2 + + PAGERANGE_STORE_SIZE, + HA_CHECKSUM_STORE_SIZE, buff, NULL) != + HA_CHECKSUM_STORE_SIZE) + { + eprint(tracef, "Failed to read record"); + return 1; + } + share->state.state.checksum+= ha_checksum_korr(buff); + } + share->state.changed|= (STATE_CHANGED | STATE_NOT_ANALYZED | + STATE_NOT_OPTIMIZED_ROWS | STATE_NOT_ZEROFILLED | + STATE_NOT_MOVABLE); + } + tprint(tracef, " rows' count %lu\n", (ulong)share->state.state.records); + _ma_unpin_all_pages(info, rec->lsn); + return 0; +} + + +prototype_redo_exec_hook(UNDO_ROW_UPDATE) +{ + MARIA_HA *info= get_MARIA_HA_from_UNDO_record(rec); + MARIA_SHARE *share; + + set_undo_lsn_for_active_trans(rec->short_trid, rec->lsn); + if (info == NULL) + return 0; + share= info->s; + if (cmp_translog_addr(rec->lsn, share->state.is_of_horizon) >= 0) + { + if (share->calc_checksum) + { + uchar buff[HA_CHECKSUM_STORE_SIZE]; + if (translog_read_record(rec->lsn, LSN_STORE_SIZE + FILEID_STORE_SIZE + + PAGE_STORE_SIZE + DIRPOS_STORE_SIZE, + HA_CHECKSUM_STORE_SIZE, buff, NULL) != + HA_CHECKSUM_STORE_SIZE) + { + eprint(tracef, "Failed to read record"); + return 1; + } + share->state.state.checksum+= ha_checksum_korr(buff); + } + share->state.changed|= (STATE_CHANGED | STATE_NOT_ANALYZED | + STATE_NOT_ZEROFILLED | STATE_NOT_MOVABLE); + } + _ma_unpin_all_pages(info, rec->lsn); + return 0; +} + + +prototype_redo_exec_hook(UNDO_KEY_INSERT) +{ + MARIA_HA *info; + MARIA_SHARE *share; + + set_undo_lsn_for_active_trans(rec->short_trid, rec->lsn); + if (!(info= get_MARIA_HA_from_UNDO_record(rec))) + return 0; + share= info->s; + if (cmp_translog_addr(rec->lsn, share->state.is_of_horizon) >= 0) + { + const uchar *ptr= rec->header + LSN_STORE_SIZE + FILEID_STORE_SIZE; + uint keynr= key_nr_korr(ptr); + if (share->base.auto_key == (keynr + 1)) /* it's auto-increment */ + { + const HA_KEYSEG *keyseg= info->s->keyinfo[keynr].seg; + ulonglong value; + char llbuf[22]; + uchar *to; + tprint(tracef, " state older than record\n"); + /* we read the record to find the auto_increment value */ + enlarge_buffer(rec); + if (log_record_buffer.str == NULL || + translog_read_record(rec->lsn, 0, rec->record_length, + log_record_buffer.str, NULL) != + rec->record_length) + { + eprint(tracef, "Failed to read record"); + return 1; + } + to= log_record_buffer.str + LSN_STORE_SIZE + FILEID_STORE_SIZE + + KEY_NR_STORE_SIZE; + if (keyseg->flag & HA_SWAP_KEY) + { + /* We put key from log record to "data record" packing format... */ + uchar reversed[MARIA_MAX_KEY_BUFF]; + uchar *key_ptr= to; + uchar *key_end= key_ptr + keyseg->length; + to= reversed + keyseg->length; + do + { + *--to= *key_ptr++; + } while (key_ptr != key_end); + /* ... so that we can read it with: */ + } + value= ma_retrieve_auto_increment(to, keyseg->type); + set_if_bigger(share->state.auto_increment, value); + llstr(share->state.auto_increment, llbuf); + tprint(tracef, " auto-inc %s\n", llbuf); + } + } + _ma_unpin_all_pages(info, rec->lsn); + return 0; +} + + +prototype_redo_exec_hook(UNDO_KEY_DELETE) +{ + MARIA_HA *info; + + set_undo_lsn_for_active_trans(rec->short_trid, rec->lsn); + if (!(info= get_MARIA_HA_from_UNDO_record(rec))) + return 0; + _ma_unpin_all_pages(info, rec->lsn); + return 0; +} + + +prototype_redo_exec_hook(UNDO_KEY_DELETE_WITH_ROOT) +{ + MARIA_HA *info= get_MARIA_HA_from_UNDO_record(rec); + MARIA_SHARE *share; + + set_undo_lsn_for_active_trans(rec->short_trid, rec->lsn); + if (info == NULL) + return 0; + share= info->s; + if (cmp_translog_addr(rec->lsn, share->state.is_of_horizon) >= 0) + { + uint key_nr; + my_off_t page; + key_nr= key_nr_korr(rec->header + LSN_STORE_SIZE + FILEID_STORE_SIZE); + page= page_korr(rec->header + LSN_STORE_SIZE + FILEID_STORE_SIZE + + KEY_NR_STORE_SIZE); + share->state.key_root[key_nr]= (page == IMPOSSIBLE_PAGE_NO ? + HA_OFFSET_ERROR : + page * share->block_size); + } + _ma_unpin_all_pages(info, rec->lsn); + return 0; +} + + +prototype_redo_exec_hook(UNDO_BULK_INSERT) +{ + /* + If the repair finished it wrote and sync the state. If it didn't finish, + we are going to empty the table and that will fix the state. + */ + set_undo_lsn_for_active_trans(rec->short_trid, rec->lsn); + return 0; +} + + +prototype_redo_exec_hook(IMPORTED_TABLE) +{ + char *name; + enlarge_buffer(rec); + if (log_record_buffer.str == NULL || + translog_read_record(rec->lsn, 0, rec->record_length, + log_record_buffer.str, NULL) != + rec->record_length) + { + eprint(tracef, "Failed to read record"); + return 1; + } + name= (char *)log_record_buffer.str; + tprint(tracef, "Table '%s' was imported (auto-zerofilled) in this Maria instance\n", name); + return 0; +} + + +prototype_redo_exec_hook(COMMIT) +{ + uint16 sid= rec->short_trid; + TrID long_trid= all_active_trans[sid].long_trid; + char llbuf[22]; + if (long_trid == 0) + { + tprint(tracef, "We don't know about transaction with short_trid %u;" + "it probably committed long ago, forget it\n", sid); + bzero(&all_active_trans[sid], sizeof(all_active_trans[sid])); + return 0; + } + llstr(long_trid, llbuf); + tprint(tracef, "Transaction long_trid %s short_trid %u committed\n", + llbuf, sid); + bzero(&all_active_trans[sid], sizeof(all_active_trans[sid])); +#ifdef MARIA_VERSIONING + /* + if real recovery: + transaction was committed, move it to some separate list for later + purging (but don't purge now! purging may have been started before, we + may find REDO_PURGE records soon). + */ +#endif + return 0; +} + +prototype_redo_exec_hook(CLR_END) +{ + MARIA_HA *info= get_MARIA_HA_from_UNDO_record(rec); + MARIA_SHARE *share; + LSN previous_undo_lsn; + enum translog_record_type undone_record_type; + const LOG_DESC *log_desc; + my_bool row_entry= 0; + uchar *logpos; + DBUG_ENTER("exec_REDO_LOGREC_CLR_END"); + + previous_undo_lsn= lsn_korr(rec->header); + undone_record_type= + clr_type_korr(rec->header + LSN_STORE_SIZE + FILEID_STORE_SIZE); + log_desc= &log_record_type_descriptor[undone_record_type]; + + set_undo_lsn_for_active_trans(rec->short_trid, previous_undo_lsn); + if (info == NULL) + DBUG_RETURN(0); + share= info->s; + tprint(tracef, " CLR_END was about %s, undo_lsn now LSN (%lu,0x%lx)\n", + log_desc->name, LSN_IN_PARTS(previous_undo_lsn)); + + enlarge_buffer(rec); + if (log_record_buffer.str == NULL || + translog_read_record(rec->lsn, 0, rec->record_length, + log_record_buffer.str, NULL) != + rec->record_length) + { + eprint(tracef, "Failed to read record"); + return 1; + } + logpos= (log_record_buffer.str + LSN_STORE_SIZE + FILEID_STORE_SIZE + + CLR_TYPE_STORE_SIZE); + + if (cmp_translog_addr(rec->lsn, share->state.is_of_horizon) >= 0) + { + tprint(tracef, " state older than record\n"); + switch (undone_record_type) { + case LOGREC_UNDO_ROW_DELETE: + row_entry= 1; + share->state.state.records++; + break; + case LOGREC_UNDO_ROW_INSERT: + share->state.state.records--; + share->state.changed|= STATE_NOT_OPTIMIZED_ROWS; + row_entry= 1; + break; + case LOGREC_UNDO_ROW_UPDATE: + row_entry= 1; + break; + case LOGREC_UNDO_KEY_INSERT: + case LOGREC_UNDO_KEY_DELETE: + break; + case LOGREC_UNDO_KEY_INSERT_WITH_ROOT: + case LOGREC_UNDO_KEY_DELETE_WITH_ROOT: + { + uint key_nr; + my_off_t page; + key_nr= key_nr_korr(logpos); + page= page_korr(logpos + KEY_NR_STORE_SIZE); + share->state.key_root[key_nr]= (page == IMPOSSIBLE_PAGE_NO ? + HA_OFFSET_ERROR : + page * share->block_size); + break; + } + case LOGREC_UNDO_BULK_INSERT: + break; + default: + DBUG_ASSERT(0); + } + if (row_entry && share->calc_checksum) + share->state.state.checksum+= ha_checksum_korr(logpos); + share->state.changed|= (STATE_CHANGED | STATE_NOT_ANALYZED | + STATE_NOT_ZEROFILLED | STATE_NOT_MOVABLE); + } + if (row_entry) + tprint(tracef, " rows' count %lu\n", (ulong)share->state.state.records); + _ma_unpin_all_pages(info, rec->lsn); + DBUG_RETURN(0); +} + + +/** + Hock to print debug information (like MySQL query) +*/ + +prototype_redo_exec_hook(DEBUG_INFO) +{ + uchar *data; + enum translog_debug_info_type debug_info; + + enlarge_buffer(rec); + if (log_record_buffer.str == NULL || + translog_read_record(rec->lsn, 0, rec->record_length, + log_record_buffer.str, NULL) != + rec->record_length) + { + eprint(tracef, "Failed to read record debug record"); + return 1; + } + debug_info= (enum translog_debug_info_type) log_record_buffer.str[0]; + data= log_record_buffer.str + 1; + switch (debug_info) { + case LOGREC_DEBUG_INFO_QUERY: + tprint(tracef, "Query: %.*s\n", rec->record_length - 1, + (char*) data); + break; + default: + DBUG_ASSERT(0); + } + return 0; +} + + +/** + In some cases we have to skip execution of an UNDO record during the UNDO + phase. +*/ + +static void skip_undo_record(LSN previous_undo_lsn, TRN *trn) +{ + trn->undo_lsn= previous_undo_lsn; + if (previous_undo_lsn == LSN_IMPOSSIBLE) /* has fully rolled back */ + trn->first_undo_lsn= LSN_WITH_FLAGS_TO_FLAGS(trn->first_undo_lsn); + skipped_undo_phase++; +} + + +prototype_undo_exec_hook(UNDO_ROW_INSERT) +{ + my_bool error; + MARIA_HA *info= get_MARIA_HA_from_UNDO_record(rec); + LSN previous_undo_lsn= lsn_korr(rec->header); + MARIA_SHARE *share; + const uchar *record_ptr; + + if (info == NULL) + { + /* + Unlike for REDOs, if the table was skipped it is abnormal; we have a + transaction to rollback which used this table, as it is not rolled back + it was supposed to hold this table and so the table should still be + there. Skip it (user may have repaired the table with maria_chk because + it was so badly corrupted that a previous recovery failed) but warn. + */ + skip_undo_record(previous_undo_lsn, trn); + return 0; + } + share= info->s; + share->state.changed|= (STATE_CHANGED | STATE_NOT_ANALYZED | + STATE_NOT_OPTIMIZED_ROWS | STATE_NOT_ZEROFILLED | + STATE_NOT_MOVABLE); + record_ptr= rec->header; + if (share->calc_checksum) + { + /* + We need to read more of the record to put the checksum into the record + buffer used by _ma_apply_undo_row_insert(). + If the table has no live checksum, rec->header will be enough. + */ + enlarge_buffer(rec); + if (log_record_buffer.str == NULL || + translog_read_record(rec->lsn, 0, rec->record_length, + log_record_buffer.str, NULL) != + rec->record_length) + { + eprint(tracef, "Failed to read record"); + return 1; + } + record_ptr= log_record_buffer.str; + } + + info->trn= trn; + error= _ma_apply_undo_row_insert(info, previous_undo_lsn, + record_ptr + LSN_STORE_SIZE + + FILEID_STORE_SIZE); + info->trn= 0; + /* trn->undo_lsn is updated in an inwrite_hook when writing the CLR_END */ + tprint(tracef, " rows' count %lu\n", (ulong)info->s->state.state.records); + tprint(tracef, " undo_lsn now LSN (%lu,0x%lx)\n", + LSN_IN_PARTS(trn->undo_lsn)); + return error; +} + + +prototype_undo_exec_hook(UNDO_ROW_DELETE) +{ + my_bool error; + MARIA_HA *info= get_MARIA_HA_from_UNDO_record(rec); + LSN previous_undo_lsn= lsn_korr(rec->header); + MARIA_SHARE *share; + + if (info == NULL) + { + skip_undo_record(previous_undo_lsn, trn); + return 0; + } + + share= info->s; + share->state.changed|= (STATE_CHANGED | STATE_NOT_ANALYZED | + STATE_NOT_ZEROFILLED | STATE_NOT_MOVABLE); + enlarge_buffer(rec); + if (log_record_buffer.str == NULL || + translog_read_record(rec->lsn, 0, rec->record_length, + log_record_buffer.str, NULL) != + rec->record_length) + { + eprint(tracef, "Failed to read record"); + return 1; + } + + info->trn= trn; + error= _ma_apply_undo_row_delete(info, previous_undo_lsn, + log_record_buffer.str + LSN_STORE_SIZE + + FILEID_STORE_SIZE, + rec->record_length - + (LSN_STORE_SIZE + FILEID_STORE_SIZE)); + info->trn= 0; + tprint(tracef, " rows' count %lu\n undo_lsn now LSN (%lu,0x%lx)\n", + (ulong)share->state.state.records, LSN_IN_PARTS(trn->undo_lsn)); + return error; +} + + +prototype_undo_exec_hook(UNDO_ROW_UPDATE) +{ + my_bool error; + MARIA_HA *info= get_MARIA_HA_from_UNDO_record(rec); + LSN previous_undo_lsn= lsn_korr(rec->header); + MARIA_SHARE *share; + + if (info == NULL) + { + skip_undo_record(previous_undo_lsn, trn); + return 0; + } + + share= info->s; + share->state.changed|= (STATE_CHANGED | STATE_NOT_ANALYZED | + STATE_NOT_ZEROFILLED | STATE_NOT_MOVABLE); + enlarge_buffer(rec); + if (log_record_buffer.str == NULL || + translog_read_record(rec->lsn, 0, rec->record_length, + log_record_buffer.str, NULL) != + rec->record_length) + { + eprint(tracef, "Failed to read record"); + return 1; + } + + info->trn= trn; + error= _ma_apply_undo_row_update(info, previous_undo_lsn, + log_record_buffer.str + LSN_STORE_SIZE + + FILEID_STORE_SIZE, + rec->record_length - + (LSN_STORE_SIZE + FILEID_STORE_SIZE)); + info->trn= 0; + tprint(tracef, " undo_lsn now LSN (%lu,0x%lx)\n", + LSN_IN_PARTS(trn->undo_lsn)); + return error; +} + + +prototype_undo_exec_hook(UNDO_KEY_INSERT) +{ + my_bool error; + MARIA_HA *info= get_MARIA_HA_from_UNDO_record(rec); + LSN previous_undo_lsn= lsn_korr(rec->header); + MARIA_SHARE *share; + + if (info == NULL) + { + skip_undo_record(previous_undo_lsn, trn); + return 0; + } + + share= info->s; + share->state.changed|= (STATE_CHANGED | STATE_NOT_ANALYZED | + STATE_NOT_ZEROFILLED | STATE_NOT_MOVABLE); + + enlarge_buffer(rec); + if (log_record_buffer.str == NULL || + translog_read_record(rec->lsn, 0, rec->record_length, + log_record_buffer.str, NULL) != + rec->record_length) + { + eprint(tracef, "Failed to read record"); + return 1; + } + + info->trn= trn; + error= _ma_apply_undo_key_insert(info, previous_undo_lsn, + log_record_buffer.str + LSN_STORE_SIZE + + FILEID_STORE_SIZE, + rec->record_length - LSN_STORE_SIZE - + FILEID_STORE_SIZE); + info->trn= 0; + /* trn->undo_lsn is updated in an inwrite_hook when writing the CLR_END */ + tprint(tracef, " undo_lsn now LSN (%lu,0x%lx)\n", + LSN_IN_PARTS(trn->undo_lsn)); + return error; +} + + +prototype_undo_exec_hook(UNDO_KEY_DELETE) +{ + my_bool error; + MARIA_HA *info= get_MARIA_HA_from_UNDO_record(rec); + LSN previous_undo_lsn= lsn_korr(rec->header); + MARIA_SHARE *share; + + if (info == NULL) + { + skip_undo_record(previous_undo_lsn, trn); + return 0; + } + + share= info->s; + share->state.changed|= (STATE_CHANGED | STATE_NOT_ANALYZED | + STATE_NOT_ZEROFILLED | STATE_NOT_MOVABLE); + + enlarge_buffer(rec); + if (log_record_buffer.str == NULL || + translog_read_record(rec->lsn, 0, rec->record_length, + log_record_buffer.str, NULL) != + rec->record_length) + { + eprint(tracef, "Failed to read record"); + return 1; + } + + info->trn= trn; + error= _ma_apply_undo_key_delete(info, previous_undo_lsn, + log_record_buffer.str + LSN_STORE_SIZE + + FILEID_STORE_SIZE, + rec->record_length - LSN_STORE_SIZE - + FILEID_STORE_SIZE, FALSE); + info->trn= 0; + /* trn->undo_lsn is updated in an inwrite_hook when writing the CLR_END */ + tprint(tracef, " undo_lsn now LSN (%lu,0x%lx)\n", + LSN_IN_PARTS(trn->undo_lsn)); + return error; +} + + +prototype_undo_exec_hook(UNDO_KEY_DELETE_WITH_ROOT) +{ + my_bool error; + MARIA_HA *info= get_MARIA_HA_from_UNDO_record(rec); + LSN previous_undo_lsn= lsn_korr(rec->header); + MARIA_SHARE *share; + + if (info == NULL) + { + skip_undo_record(previous_undo_lsn, trn); + return 0; + } + + share= info->s; + share->state.changed|= (STATE_CHANGED | STATE_NOT_ANALYZED | + STATE_NOT_ZEROFILLED | STATE_NOT_MOVABLE); + + enlarge_buffer(rec); + if (log_record_buffer.str == NULL || + translog_read_record(rec->lsn, 0, rec->record_length, + log_record_buffer.str, NULL) != + rec->record_length) + { + eprint(tracef, "Failed to read record"); + return 1; + } + + info->trn= trn; + error= _ma_apply_undo_key_delete(info, previous_undo_lsn, + log_record_buffer.str + LSN_STORE_SIZE + + FILEID_STORE_SIZE, + rec->record_length - LSN_STORE_SIZE - + FILEID_STORE_SIZE, TRUE); + info->trn= 0; + /* trn->undo_lsn is updated in an inwrite_hook when writing the CLR_END */ + tprint(tracef, " undo_lsn now LSN (%lu,0x%lx)\n", + LSN_IN_PARTS(trn->undo_lsn)); + return error; +} + + +prototype_undo_exec_hook(UNDO_BULK_INSERT) +{ + my_bool error; + MARIA_HA *info= get_MARIA_HA_from_UNDO_record(rec); + LSN previous_undo_lsn= lsn_korr(rec->header); + MARIA_SHARE *share; + + if (info == NULL) + { + skip_undo_record(previous_undo_lsn, trn); + return 0; + } + + share= info->s; + share->state.changed|= (STATE_CHANGED | STATE_NOT_ANALYZED | + STATE_NOT_ZEROFILLED | STATE_NOT_MOVABLE); + + info->trn= trn; + error= _ma_apply_undo_bulk_insert(info, previous_undo_lsn); + info->trn= 0; + /* trn->undo_lsn is updated in an inwrite_hook when writing the CLR_END */ + tprint(tracef, " undo_lsn now LSN (%lu,0x%lx)\n", + LSN_IN_PARTS(trn->undo_lsn)); + return error; +} + + +static int run_redo_phase(LSN lsn, enum maria_apply_log_way apply) +{ + TRANSLOG_HEADER_BUFFER rec; + struct st_translog_scanner_data scanner; + int len; + uint i; + + /* install hooks for execution */ +#define install_redo_exec_hook(R) \ + log_record_type_descriptor[LOGREC_ ## R].record_execute_in_redo_phase= \ + exec_REDO_LOGREC_ ## R; +#define install_redo_exec_hook_shared(R,S) \ + log_record_type_descriptor[LOGREC_ ## R].record_execute_in_redo_phase= \ + exec_REDO_LOGREC_ ## S; +#define install_undo_exec_hook(R) \ + log_record_type_descriptor[LOGREC_ ## R].record_execute_in_undo_phase= \ + exec_UNDO_LOGREC_ ## R; + install_redo_exec_hook(LONG_TRANSACTION_ID); + install_redo_exec_hook(CHECKPOINT); + install_redo_exec_hook(REDO_CREATE_TABLE); + install_redo_exec_hook(REDO_RENAME_TABLE); + install_redo_exec_hook(REDO_REPAIR_TABLE); + install_redo_exec_hook(REDO_DROP_TABLE); + install_redo_exec_hook(FILE_ID); + install_redo_exec_hook(INCOMPLETE_LOG); + install_redo_exec_hook(INCOMPLETE_GROUP); + install_redo_exec_hook(REDO_INSERT_ROW_HEAD); + install_redo_exec_hook(REDO_INSERT_ROW_TAIL); + install_redo_exec_hook(REDO_INSERT_ROW_BLOBS); + install_redo_exec_hook(REDO_PURGE_ROW_HEAD); + install_redo_exec_hook(REDO_PURGE_ROW_TAIL); + install_redo_exec_hook(REDO_FREE_HEAD_OR_TAIL); + install_redo_exec_hook(REDO_FREE_BLOCKS); + install_redo_exec_hook(REDO_DELETE_ALL); + install_redo_exec_hook(REDO_INDEX); + install_redo_exec_hook(REDO_INDEX_NEW_PAGE); + install_redo_exec_hook(REDO_INDEX_FREE_PAGE); + install_redo_exec_hook(REDO_BITMAP_NEW_PAGE); + install_redo_exec_hook(UNDO_ROW_INSERT); + install_redo_exec_hook(UNDO_ROW_DELETE); + install_redo_exec_hook(UNDO_ROW_UPDATE); + install_redo_exec_hook(UNDO_KEY_INSERT); + install_redo_exec_hook(UNDO_KEY_DELETE); + install_redo_exec_hook(UNDO_KEY_DELETE_WITH_ROOT); + install_redo_exec_hook(COMMIT); + install_redo_exec_hook(CLR_END); + install_undo_exec_hook(UNDO_ROW_INSERT); + install_undo_exec_hook(UNDO_ROW_DELETE); + install_undo_exec_hook(UNDO_ROW_UPDATE); + install_undo_exec_hook(UNDO_KEY_INSERT); + install_undo_exec_hook(UNDO_KEY_DELETE); + install_undo_exec_hook(UNDO_KEY_DELETE_WITH_ROOT); + /* REDO_NEW_ROW_HEAD shares entry with REDO_INSERT_ROW_HEAD */ + install_redo_exec_hook_shared(REDO_NEW_ROW_HEAD, REDO_INSERT_ROW_HEAD); + /* REDO_NEW_ROW_TAIL shares entry with REDO_INSERT_ROW_TAIL */ + install_redo_exec_hook_shared(REDO_NEW_ROW_TAIL, REDO_INSERT_ROW_TAIL); + install_redo_exec_hook(UNDO_BULK_INSERT); + install_undo_exec_hook(UNDO_BULK_INSERT); + install_redo_exec_hook(IMPORTED_TABLE); + install_redo_exec_hook(DEBUG_INFO); + + current_group_end_lsn= LSN_IMPOSSIBLE; +#ifndef DBUG_OFF + current_group_table= NULL; +#endif + + if (unlikely(lsn == LSN_IMPOSSIBLE || lsn == translog_get_horizon())) + { + tprint(tracef, "checkpoint address refers to the log end log or " + "log is empty, nothing to do.\n"); + return 0; + } + + len= translog_read_record_header(lsn, &rec); + + if (len == RECHEADER_READ_ERROR) + { + eprint(tracef, "Failed to read header of the first record."); + return 1; + } + if (translog_scanner_init(lsn, 1, &scanner, 1)) + { + tprint(tracef, "Scanner init failed\n"); + return 1; + } + for (i= 1;;i++) + { + uint16 sid= rec.short_trid; + const LOG_DESC *log_desc= &log_record_type_descriptor[rec.type]; + display_record_position(log_desc, &rec, i); + /* + A complete group is a set of log records with an "end mark" record + (e.g. a set of REDOs for an operation, terminated by an UNDO for this + operation); if there is no "end mark" record the group is incomplete and + won't be executed. + */ + if ((log_desc->record_in_group == LOGREC_IS_GROUP_ITSELF) || + (log_desc->record_in_group == LOGREC_LAST_IN_GROUP)) + { + if (all_active_trans[sid].group_start_lsn != LSN_IMPOSSIBLE) + { + if (log_desc->record_in_group == LOGREC_IS_GROUP_ITSELF) + { + /* + Can happen if the transaction got a table write error, then + unlocked tables thus wrote a COMMIT record. Or can be an + INCOMPLETE_GROUP record written by a previous recovery. + */ + tprint(tracef, "\nDiscarding incomplete group before this record\n"); + all_active_trans[sid].group_start_lsn= LSN_IMPOSSIBLE; + } + else + { + struct st_translog_scanner_data scanner2; + TRANSLOG_HEADER_BUFFER rec2; + /* + There is a complete group for this transaction, containing more + than this event. + */ + tprint(tracef, " ends a group:\n"); + len= + translog_read_record_header(all_active_trans[sid].group_start_lsn, + &rec2); + if (len < 0) /* EOF or error */ + { + tprint(tracef, "Cannot find record where it should be\n"); + goto err; + } + if (translog_scanner_init(rec2.lsn, 1, &scanner2, 1)) + { + tprint(tracef, "Scanner2 init failed\n"); + goto err; + } + current_group_end_lsn= rec.lsn; + do + { + if (rec2.short_trid == sid) /* it's in our group */ + { + const LOG_DESC *log_desc2= &log_record_type_descriptor[rec2.type]; + display_record_position(log_desc2, &rec2, 0); + if (apply == MARIA_LOG_CHECK) + { + translog_size_t read_len; + enlarge_buffer(&rec2); + read_len= + translog_read_record(rec2.lsn, 0, rec2.record_length, + log_record_buffer.str, NULL); + if (read_len != rec2.record_length) + { + tprint(tracef, "Cannot read record's body: read %u of" + " %u bytes\n", read_len, rec2.record_length); + translog_destroy_scanner(&scanner2); + translog_free_record_header(&rec2); + goto err; + } + } + if (apply == MARIA_LOG_APPLY && + display_and_apply_record(log_desc2, &rec2)) + { + translog_destroy_scanner(&scanner2); + translog_free_record_header(&rec2); + goto err; + } + } + translog_free_record_header(&rec2); + len= translog_read_next_record_header(&scanner2, &rec2); + if (len < 0) /* EOF or error */ + { + tprint(tracef, "Cannot find record where it should be\n"); + translog_destroy_scanner(&scanner2); + translog_free_record_header(&rec2); + goto err; + } + } + while (rec2.lsn < rec.lsn); + /* group finished */ + all_active_trans[sid].group_start_lsn= LSN_IMPOSSIBLE; + current_group_end_lsn= LSN_IMPOSSIBLE; /* for debugging */ + display_record_position(log_desc, &rec, 0); + translog_destroy_scanner(&scanner2); + translog_free_record_header(&rec2); + } + } + if (apply == MARIA_LOG_APPLY && + display_and_apply_record(log_desc, &rec)) + goto err; +#ifndef DBUG_OFF + current_group_table= NULL; +#endif + } + else /* record does not end group */ + { + /* just record the fact, can't know if can execute yet */ + if (all_active_trans[sid].group_start_lsn == LSN_IMPOSSIBLE) + { + /* group not yet started */ + all_active_trans[sid].group_start_lsn= rec.lsn; + } + } + translog_free_record_header(&rec); + len= translog_read_next_record_header(&scanner, &rec); + if (len < 0) + { + switch (len) + { + case RECHEADER_READ_EOF: + tprint(tracef, "EOF on the log\n"); + break; + case RECHEADER_READ_ERROR: + tprint(tracef, "Error reading log\n"); + goto err; + } + break; + } + } + translog_destroy_scanner(&scanner); + translog_free_record_header(&rec); + if (recovery_message_printed == REC_MSG_REDO) + { + fprintf(stderr, " 100%%"); + fflush(stderr); + procent_printed= 1; + } + return 0; + +err: + translog_destroy_scanner(&scanner); + translog_free_record_header(&rec); + return 1; +} + + +/** + @brief Informs about any aborted groups or uncommitted transactions, + prepares for the UNDO phase if needed. + + @note Observe that it may init trnman. +*/ +static uint end_of_redo_phase(my_bool prepare_for_undo_phase) +{ + uint sid, uncommitted= 0; + char llbuf[22]; + LSN addr; + + hash_free(&all_dirty_pages); + /* + hash_free() can be called multiple times probably, but be safe if that + changes + */ + bzero(&all_dirty_pages, sizeof(all_dirty_pages)); + my_free(dirty_pages_pool, MYF(MY_ALLOW_ZERO_PTR)); + dirty_pages_pool= NULL; + + llstr(max_long_trid, llbuf); + tprint(tracef, "Maximum transaction long id seen: %s\n", llbuf); + llstr(max_trid_in_control_file, llbuf); + tprint(tracef, "Maximum transaction long id seen in control file: %s\n", + llbuf); + /* + If logs were deleted, or lost, trid in control file is needed to set + trnman's generator: + */ + set_if_bigger(max_long_trid, max_trid_in_control_file); + if (prepare_for_undo_phase && trnman_init(max_long_trid)) + return -1; + + trns_created= TRUE; + + for (sid= 0; sid <= SHORT_TRID_MAX; sid++) + { + TrID long_trid= all_active_trans[sid].long_trid; + LSN gslsn= all_active_trans[sid].group_start_lsn; + TRN *trn; + if (gslsn != LSN_IMPOSSIBLE) + { + tprint(tracef, "Group at LSN (%lu,0x%lx) short_trid %u incomplete\n", + LSN_IN_PARTS(gslsn), sid); + all_active_trans[sid].group_start_lsn= LSN_IMPOSSIBLE; + } + if (all_active_trans[sid].undo_lsn != LSN_IMPOSSIBLE) + { + llstr(long_trid, llbuf); + tprint(tracef, "Transaction long_trid %s short_trid %u uncommitted\n", + llbuf, sid); + /* + dummy_transaction_object serves only for DDLs, where there is never a + rollback or incomplete group. And unknown transactions (which have + long_trid==0) should have undo_lsn==LSN_IMPOSSIBLE. + */ + if (long_trid ==0) + { + eprint(tracef, "Transaction with long_trid 0 should not roll back"); + ALERT_USER(); + return -1; + } + if (prepare_for_undo_phase) + { + if ((trn= trnman_recreate_trn_from_recovery(sid, long_trid)) == NULL) + return -1; + trn->undo_lsn= all_active_trans[sid].undo_lsn; + trn->first_undo_lsn= all_active_trans[sid].first_undo_lsn | + TRANSACTION_LOGGED_LONG_ID; /* because trn is known in log */ + if (gslsn != LSN_IMPOSSIBLE) + { + /* + UNDO phase will log some records. So, a future recovery may see: + REDO(from incomplete group) - REDO(from rollback) - CLR_END + and thus execute the first REDO (finding it in "a complete + group"). To prevent that: + */ + LEX_CUSTRING log_array[TRANSLOG_INTERNAL_PARTS]; + LSN lsn; + if (translog_write_record(&lsn, LOGREC_INCOMPLETE_GROUP, + trn, NULL, 0, + TRANSLOG_INTERNAL_PARTS, log_array, + NULL, NULL)) + return -1; + } + } + uncommitted++; + } +#ifdef MARIA_VERSIONING + /* + If real recovery: if transaction was committed, move it to some separate + list for soon purging. + */ +#endif + } + + my_free(all_active_trans, MYF(MY_ALLOW_ZERO_PTR)); + all_active_trans= NULL; + + /* + The UNDO phase uses some normal run-time code of ROLLBACK: generates log + records, etc; prepare tables for that + */ + addr= translog_get_horizon(); + for (sid= 0; sid <= SHARE_ID_MAX; sid++) + { + MARIA_HA *info= all_tables[sid].info; + if (info != NULL) + { + prepare_table_for_close(info, addr); + /* + But we don't close it; we leave it available for the UNDO phase; + it's likely that the UNDO phase will need it. + */ + if (prepare_for_undo_phase) + translog_assign_id_to_share_from_recovery(info->s, sid); + } + } + return uncommitted; +} + + +static int run_undo_phase(uint uncommitted) +{ + LSN last_undo; + DBUG_ENTER("run_undo_phase"); + + if (uncommitted > 0) + { + checkpoint_useful= TRUE; + if (tracef != stdout) + { + if (recovery_message_printed == REC_MSG_NONE) + print_preamble(); + fprintf(stderr, "transactions to roll back:"); + recovery_message_printed= REC_MSG_UNDO; + } + tprint(tracef, "%u transactions will be rolled back\n", uncommitted); + procent_printed= 1; + for( ; ; ) + { + char llbuf[22]; + TRN *trn; + if (recovery_message_printed == REC_MSG_UNDO) + { + fprintf(stderr, " %u", uncommitted); + fflush(stderr); + } + if ((uncommitted--) == 0) + break; + trn= trnman_get_any_trn(); + DBUG_ASSERT(trn != NULL); + llstr(trn->trid, llbuf); + tprint(tracef, "Rolling back transaction of long id %s\n", llbuf); + last_undo= trn->undo_lsn + 1; + + /* Execute all undo entries */ + while (trn->undo_lsn) + { + TRANSLOG_HEADER_BUFFER rec; + LOG_DESC *log_desc; + DBUG_ASSERT(trn->undo_lsn < last_undo); + last_undo= trn->undo_lsn; + + if (translog_read_record_header(trn->undo_lsn, &rec) == + RECHEADER_READ_ERROR) + DBUG_RETURN(1); + log_desc= &log_record_type_descriptor[rec.type]; + display_record_position(log_desc, &rec, 0); + if (log_desc->record_execute_in_undo_phase(&rec, trn)) + { + eprint(tracef, "Got error %d when executing undo %s", my_errno, + log_desc->name); + translog_free_record_header(&rec); + DBUG_RETURN(1); + } + translog_free_record_header(&rec); + } + + if (trnman_rollback_trn(trn)) + DBUG_RETURN(1); + /* We could want to span a few threads (4?) instead of 1 */ + /* In the future, we want to have this phase *online* */ + } + } + procent_printed= 0; + DBUG_RETURN(0); +} + + +/** + In case of error in recovery, deletes all transactions from the transaction + manager so that this module does not assert. + + @note no checkpoint should be taken as those transactions matter for the + next recovery (they still haven't been properly dealt with). +*/ + +static void delete_all_transactions() +{ + for( ; ; ) + { + TRN *trn= trnman_get_any_trn(); + if (trn == NULL) + break; + trn->undo_lsn= trn->first_undo_lsn= LSN_IMPOSSIBLE; + trnman_rollback_trn(trn); /* ignore error */ + } +} + + +/** + @brief re-enables transactionality, updates is_of_horizon + + @param info table + @param horizon address to set is_of_horizon +*/ + +static void prepare_table_for_close(MARIA_HA *info, TRANSLOG_ADDRESS horizon) +{ + MARIA_SHARE *share= info->s; + /* + In a fully-forward REDO phase (no checkpoint record), + state is now at least as new as the LSN of the current record. It may be + newer, in case we are seeing a LOGREC_FILE_ID which tells us to close a + table, but that table was later modified further in the log. + But if we parsed a checkpoint record, it may be this way in the log: + FILE_ID(6->t2)... FILE_ID(6->t1)... CHECKPOINT(6->t1) + Checkpoint parsing opened t1 with id 6; first FILE_ID above is going to + make t1 close; the first condition below is however false (when checkpoint + was taken it increased is_of_horizon) and so it works. For safety we + add the second condition. + */ + if (cmp_translog_addr(share->state.is_of_horizon, horizon) < 0 && + cmp_translog_addr(share->lsn_of_file_id, horizon) < 0) + { + share->state.is_of_horizon= horizon; + _ma_state_info_write_sub(share->kfile.file, &share->state, + MA_STATE_INFO_WRITE_DONT_MOVE_OFFSET); + } + + /* + Ensure that info->state is up to date as + _ma_renable_logging_for_table() is depending on this + */ + *info->state= info->s->state.state; + + /* + This leaves PAGECACHE_PLAIN_PAGE pages into the cache, while the table is + going to switch back to transactional. So the table will be a mix of + pages, which is ok as long as we don't take any checkpoints until all + tables get closed at the end of the UNDO phase. + */ + _ma_reenable_logging_for_table(info, FALSE); + info->trn= NULL; /* safety */ +} + + +static MARIA_HA *get_MARIA_HA_from_REDO_record(const + TRANSLOG_HEADER_BUFFER *rec) +{ + uint16 sid; + pgcache_page_no_t page; + MARIA_HA *info; + MARIA_SHARE *share; + char llbuf[22]; + my_bool index_page_redo_entry= FALSE, page_redo_entry= FALSE; + LINT_INIT(page); + + print_redo_phase_progress(rec->lsn); + sid= fileid_korr(rec->header); + switch (rec->type) { + /* not all REDO records have a page: */ + case LOGREC_REDO_INDEX_NEW_PAGE: + case LOGREC_REDO_INDEX: + case LOGREC_REDO_INDEX_FREE_PAGE: + index_page_redo_entry= 1; + /* Fall trough*/ + case LOGREC_REDO_INSERT_ROW_HEAD: + case LOGREC_REDO_INSERT_ROW_TAIL: + case LOGREC_REDO_PURGE_ROW_HEAD: + case LOGREC_REDO_PURGE_ROW_TAIL: + case LOGREC_REDO_NEW_ROW_HEAD: + case LOGREC_REDO_NEW_ROW_TAIL: + case LOGREC_REDO_FREE_HEAD_OR_TAIL: + page_redo_entry= TRUE; + page= page_korr(rec->header + FILEID_STORE_SIZE); + llstr(page, llbuf); + break; + /* + For REDO_FREE_BLOCKS, no need to look at dirty pages list: it does not + read data pages, only reads/modifies bitmap page(s) which is cheap. + */ + default: + break; + } + tprint(tracef, " For table of short id %u", sid); + info= all_tables[sid].info; +#ifndef DBUG_OFF + DBUG_ASSERT(current_group_table == NULL || current_group_table == info); + current_group_table= info; +#endif + if (info == NULL) + { + tprint(tracef, ", table skipped, so skipping record\n"); + return NULL; + } + share= info->s; + tprint(tracef, ", '%s'", share->open_file_name.str); + DBUG_ASSERT(in_redo_phase); + if (cmp_translog_addr(rec->lsn, share->lsn_of_file_id) <= 0) + { + /* + This can happen only if processing a record before the checkpoint + record. + id->name mapping is newer than REDO record: for sure the table subject + of the REDO has been flushed and forced (id re-assignment implies this); + REDO can be ignored (and must be, as we don't know what this subject + table was). + */ + DBUG_ASSERT(cmp_translog_addr(rec->lsn, checkpoint_start) < 0); + tprint(tracef, ", table's LOGREC_FILE_ID has LSN (%lu,0x%lx) more recent" + " than record, skipping record", + LSN_IN_PARTS(share->lsn_of_file_id)); + return NULL; + } + if (cmp_translog_addr(rec->lsn, share->state.skip_redo_lsn) <= 0) + { + /* probably a bulk insert repair */ + tprint(tracef, ", has skip_redo_lsn (%lu,0x%lx) more recent than" + " record, skipping record\n", + LSN_IN_PARTS(share->state.skip_redo_lsn)); + return NULL; + } + /* detect if an open instance of a dropped table (internal bug) */ + DBUG_ASSERT(share->last_version != 0); + if (page_redo_entry) + { + /* + Consult dirty pages list. + REDO_INSERT_ROW_BLOBS will consult list by itself, as it covers several + pages. + */ + tprint(tracef, " page %s", llbuf); + if (_ma_redo_not_needed_for_page(sid, rec->lsn, page, + index_page_redo_entry)) + return NULL; + } + /* + So we are going to read the page, and if its LSN is older than the + record's we will modify the page + */ + tprint(tracef, ", applying record\n"); + _ma_writeinfo(info, WRITEINFO_UPDATE_KEYFILE); /* to flush state on close */ + return info; +} + + +static MARIA_HA *get_MARIA_HA_from_UNDO_record(const + TRANSLOG_HEADER_BUFFER *rec) +{ + uint16 sid; + MARIA_HA *info; + MARIA_SHARE *share; + + sid= fileid_korr(rec->header + LSN_STORE_SIZE); + tprint(tracef, " For table of short id %u", sid); + info= all_tables[sid].info; +#ifndef DBUG_OFF + DBUG_ASSERT(!in_redo_phase || + current_group_table == NULL || current_group_table == info); + current_group_table= info; +#endif + if (info == NULL) + { + tprint(tracef, ", table skipped, so skipping record\n"); + return NULL; + } + share= info->s; + tprint(tracef, ", '%s'", share->open_file_name.str); + if (cmp_translog_addr(rec->lsn, share->lsn_of_file_id) <= 0) + { + tprint(tracef, ", table's LOGREC_FILE_ID has LSN (%lu,0x%lx) more recent" + " than record, skipping record", + LSN_IN_PARTS(share->lsn_of_file_id)); + return NULL; + } + if (in_redo_phase && + cmp_translog_addr(rec->lsn, share->state.skip_redo_lsn) <= 0) + { + /* probably a bulk insert repair */ + tprint(tracef, ", has skip_redo_lsn (%lu,0x%lx) more recent than" + " record, skipping record\n", + LSN_IN_PARTS(share->state.skip_redo_lsn)); + return NULL; + } + DBUG_ASSERT(share->last_version != 0); + _ma_writeinfo(info, WRITEINFO_UPDATE_KEYFILE); /* to flush state on close */ + tprint(tracef, ", applying record\n"); + return info; +} + + +/** + @brief Parses checkpoint record. + + Builds from it the dirty_pages list (a hash), opens tables and maps them to + their 2-byte IDs, recreates transactions (not real TRNs though). + + @return LSN from where in the log the REDO phase should start + @retval LSN_ERROR error + @retval other ok +*/ + +static LSN parse_checkpoint_record(LSN lsn) +{ + ulong i; + ulonglong nb_dirty_pages; + TRANSLOG_HEADER_BUFFER rec; + TRANSLOG_ADDRESS start_address; + int len; + uint nb_active_transactions, nb_committed_transactions, nb_tables; + uchar *ptr; + LSN minimum_rec_lsn_of_active_transactions, minimum_rec_lsn_of_dirty_pages; + struct st_dirty_page *next_dirty_page_in_pool; + + tprint(tracef, "Loading data from checkpoint record at LSN (%lu,0x%lx)\n", + LSN_IN_PARTS(lsn)); + if ((len= translog_read_record_header(lsn, &rec)) == RECHEADER_READ_ERROR) + { + tprint(tracef, "Cannot find checkpoint record where it should be\n"); + return LSN_ERROR; + } + + enlarge_buffer(&rec); + if (log_record_buffer.str == NULL || + translog_read_record(rec.lsn, 0, rec.record_length, + log_record_buffer.str, NULL) != + rec.record_length) + { + eprint(tracef, "Failed to read record"); + return LSN_ERROR; + } + + ptr= log_record_buffer.str; + start_address= lsn_korr(ptr); + ptr+= LSN_STORE_SIZE; + tprint(tracef, "Checkpoint record has start_horizon at (%lu,0x%lx)\n", + LSN_IN_PARTS(start_address)); + + /* transactions */ + nb_active_transactions= uint2korr(ptr); + ptr+= 2; + tprint(tracef, "%u active transactions\n", nb_active_transactions); + minimum_rec_lsn_of_active_transactions= lsn_korr(ptr); + ptr+= LSN_STORE_SIZE; + max_long_trid= transid_korr(ptr); + ptr+= TRANSID_SIZE; + + /* + how much brain juice and discussions there was to come to writing this + line. It may make start_address slightly decrease (only by the time it + takes to write one or a few rows, roughly). + */ + tprint(tracef, "Checkpoint record has min_rec_lsn of active transactions" + " at (%lu,0x%lx)\n", + LSN_IN_PARTS(minimum_rec_lsn_of_active_transactions)); + set_if_smaller(start_address, minimum_rec_lsn_of_active_transactions); + + for (i= 0; i < nb_active_transactions; i++) + { + uint16 sid= uint2korr(ptr); + TrID long_id; + LSN undo_lsn, first_undo_lsn; + ptr+= 2; + long_id= uint6korr(ptr); + ptr+= 6; + DBUG_ASSERT(sid > 0 && long_id > 0); + undo_lsn= lsn_korr(ptr); + ptr+= LSN_STORE_SIZE; + first_undo_lsn= lsn_korr(ptr); + ptr+= LSN_STORE_SIZE; + new_transaction(sid, long_id, undo_lsn, first_undo_lsn); + } + nb_committed_transactions= uint4korr(ptr); + ptr+= 4; + tprint(tracef, "%lu committed transactions\n", + (ulong)nb_committed_transactions); + /* no purging => committed transactions are not important */ + ptr+= (6 + LSN_STORE_SIZE) * nb_committed_transactions; + + /* tables */ + nb_tables= uint4korr(ptr); + ptr+= 4; + tprint(tracef, "%u open tables\n", nb_tables); + for (i= 0; i< nb_tables; i++) + { + char name[FN_REFLEN]; + LSN first_log_write_lsn; + uint name_len; + uint16 sid= uint2korr(ptr); + ptr+= 2; + DBUG_ASSERT(sid > 0); + first_log_write_lsn= lsn_korr(ptr); + ptr+= LSN_STORE_SIZE; + name_len= strlen((char *)ptr) + 1; + strmake(name, (char *)ptr, sizeof(name)-1); + ptr+= name_len; + if (new_table(sid, name, first_log_write_lsn)) + return LSN_ERROR; + } + + /* dirty pages */ + nb_dirty_pages= uint8korr(ptr); + + /* Ensure casts later will not loose significant bits. */ + DBUG_ASSERT((nb_dirty_pages <= SIZE_T_MAX/sizeof(struct st_dirty_page)) && + (nb_dirty_pages <= ULONG_MAX)); + + ptr+= 8; + tprint(tracef, "%lu dirty pages\n", (ulong) nb_dirty_pages); + if (hash_init(&all_dirty_pages, &my_charset_bin, (ulong)nb_dirty_pages, + offsetof(struct st_dirty_page, file_and_page_id), + sizeof(((struct st_dirty_page *)NULL)->file_and_page_id), + NULL, NULL, 0)) + return LSN_ERROR; + dirty_pages_pool= + (struct st_dirty_page *)my_malloc((size_t)nb_dirty_pages * + sizeof(struct st_dirty_page), + MYF(MY_WME)); + if (unlikely(dirty_pages_pool == NULL)) + return LSN_ERROR; + next_dirty_page_in_pool= dirty_pages_pool; + minimum_rec_lsn_of_dirty_pages= LSN_MAX; + for (i= 0; i < nb_dirty_pages ; i++) + { + pgcache_page_no_t page_id; + LSN rec_lsn; + uint32 is_index; + uint16 table_id= uint2korr(ptr); + ptr+= 2; + is_index= ptr[0]; + ptr++; + page_id= page_korr(ptr); + ptr+= PAGE_STORE_SIZE; + rec_lsn= lsn_korr(ptr); + ptr+= LSN_STORE_SIZE; + if (new_page((is_index << 16) | table_id, + page_id, rec_lsn, next_dirty_page_in_pool++)) + return LSN_ERROR; + set_if_smaller(minimum_rec_lsn_of_dirty_pages, rec_lsn); + } + /* after that, there will be no insert/delete into the hash */ + /* + sanity check on record (did we screw up with all those "ptr+=", did the + checkpoint write code and checkpoint read code go out of sync?). + */ + if (ptr != (log_record_buffer.str + log_record_buffer.length)) + { + eprint(tracef, "checkpoint record corrupted\n"); + return LSN_ERROR; + } + + /* + start_address is now from where the dirty pages list can be ignored. + Find LSN higher or equal to this TRANSLOG_ADDRESS, suitable for + translog_read_record() functions. + */ + start_address= checkpoint_start= + translog_next_LSN(start_address, LSN_IMPOSSIBLE); + tprint(tracef, "Checkpoint record start_horizon now adjusted to" + " LSN (%lu,0x%lx)\n", LSN_IN_PARTS(start_address)); + if (checkpoint_start == LSN_IMPOSSIBLE) + { + /* + There must be a problem, as our checkpoint record exists and is >= the + address which is stored in its first bytes, which is >= start_address. + */ + return LSN_ERROR; + } + /* now, where the REDO phase should start reading log: */ + tprint(tracef, "Checkpoint has min_rec_lsn of dirty pages at" + " LSN (%lu,0x%lx)\n", LSN_IN_PARTS(minimum_rec_lsn_of_dirty_pages)); + set_if_smaller(start_address, minimum_rec_lsn_of_dirty_pages); + DBUG_PRINT("info", + ("checkpoint_start: (%lu,0x%lx) start_address: (%lu,0x%lx)", + LSN_IN_PARTS(checkpoint_start), LSN_IN_PARTS(start_address))); + return start_address; +} + + +static int new_page(uint32 fileid, pgcache_page_no_t pageid, LSN rec_lsn, + struct st_dirty_page *dirty_page) +{ + /* serves as hash key */ + dirty_page->file_and_page_id= (((uint64)fileid) << 40) | pageid; + dirty_page->rec_lsn= rec_lsn; + return my_hash_insert(&all_dirty_pages, (uchar *)dirty_page); +} + + +static int close_all_tables(void) +{ + int error= 0; + uint count= 0; + LIST *list_element, *next_open; + MARIA_HA *info; + TRANSLOG_ADDRESS addr; + DBUG_ENTER("close_all_tables"); + + pthread_mutex_lock(&THR_LOCK_maria); + if (maria_open_list == NULL) + goto end; + tprint(tracef, "Closing all tables\n"); + if (tracef != stdout) + { + if (recovery_message_printed == REC_MSG_NONE) + print_preamble(); + for (count= 0, list_element= maria_open_list ; + list_element ; count++, (list_element= list_element->next)) + ; + fprintf(stderr, "tables to flush:"); + recovery_message_printed= REC_MSG_FLUSH; + } + /* + Since the end of end_of_redo_phase(), we may have written new records + (if UNDO phase ran) and thus the state is newer than at + end_of_redo_phase(), we need to bump is_of_horizon again. + */ + addr= translog_get_horizon(); + for (list_element= maria_open_list ; ; list_element= next_open) + { + if (recovery_message_printed == REC_MSG_FLUSH) + { + fprintf(stderr, " %u", count--); + fflush(stderr); + } + if (list_element == NULL) + break; + next_open= list_element->next; + info= (MARIA_HA*)list_element->data; + pthread_mutex_unlock(&THR_LOCK_maria); /* ok, UNDO phase not online yet */ + /* + Tables which we see here are exactly those which were open at time of + crash. They might have open_count>0 as Checkpoint maybe flushed their + state while they were used. As Recovery corrected them, don't alarm the + user, don't ask for a table check: + */ + info->s->state.open_count= 0; + prepare_table_for_close(info, addr); + error|= maria_close(info); + pthread_mutex_lock(&THR_LOCK_maria); + } +end: + pthread_mutex_unlock(&THR_LOCK_maria); + DBUG_RETURN(error); +} + + +/** + @brief Close all table instances with a certain name which are present in + all_tables. + + @param name Name of table + @param addr Log address passed to prepare_table_for_close() +*/ + +static my_bool close_one_table(const char *name, TRANSLOG_ADDRESS addr) +{ + my_bool res= 0; + /* There are no other threads using the tables, so we don't need any locks */ + struct st_table_for_recovery *internal_table, *end; + for (internal_table= all_tables, end= internal_table + SHARE_ID_MAX + 1; + internal_table < end ; + internal_table++) + { + MARIA_HA *info= internal_table->info; + if ((info != NULL) && !strcmp(info->s->open_file_name.str, name)) + { + prepare_table_for_close(info, addr); + if (maria_close(info)) + res= 1; + internal_table->info= NULL; + } + } + return res; +} + + +/** + Temporarily disables logging for this table. + + If that makes the log incomplete, writes a LOGREC_INCOMPLETE_LOG to the log + to warn log readers. + + @param info table + @param log_incomplete if that disabling makes the log incomplete + + @note for example in the REDO phase we disable logging but that does not + make the log incomplete. +*/ + +void _ma_tmp_disable_logging_for_table(MARIA_HA *info, + my_bool log_incomplete) +{ + MARIA_SHARE *share= info->s; + DBUG_ENTER("_ma_tmp_disable_logging_for_table"); + if (log_incomplete) + { + uchar log_data[FILEID_STORE_SIZE]; + LEX_CUSTRING log_array[TRANSLOG_INTERNAL_PARTS + 1]; + LSN lsn; + log_array[TRANSLOG_INTERNAL_PARTS + 0].str= log_data; + log_array[TRANSLOG_INTERNAL_PARTS + 0].length= sizeof(log_data); + translog_write_record(&lsn, LOGREC_INCOMPLETE_LOG, + &dummy_transaction_object, info, + (translog_size_t) sizeof(log_data), + TRANSLOG_INTERNAL_PARTS + 1, log_array, + log_data, NULL); + } + + /* if we disabled before writing the record, record wouldn't reach log */ + share->now_transactional= FALSE; + + /* + Reset state pointers. This is needed as in ALTER table we may do + commit fllowed by _ma_renable_logging_for_table and then + info->state may point to a state that was deleted by + _ma_trnman_end_trans_hook() + */ + share->state.common= *info->state; + info->state= &share->state.common; + info->switched_transactional= TRUE; + + /* + Some code in ma_blockrec.c assumes a trn even if !now_transactional but in + this case it only reads trn->rec_lsn, which has to be LSN_IMPOSSIBLE and + should be now. info->trn may be NULL in maria_chk. + */ + if (info->trn == NULL) + info->trn= &dummy_transaction_object; + DBUG_ASSERT(info->trn->rec_lsn == LSN_IMPOSSIBLE); + share->page_type= PAGECACHE_PLAIN_PAGE; + /* Functions below will pick up now_transactional and change callbacks */ + _ma_set_data_pagecache_callbacks(&info->dfile, share); + _ma_set_index_pagecache_callbacks(&share->kfile, share); + _ma_bitmap_set_pagecache_callbacks(&share->bitmap.file, share); + DBUG_VOID_RETURN; +} + + +/** + Re-enables logging for a table which had it temporarily disabled. + + Only the thread which disabled logging is allowed to reenable it. Indeed, + re-enabling logging affects all open instances, one must have exclusive + access to the table to do that. In practice, the one which disables has + such access. + + @param info table + @param flush_pages if function needs to flush pages first +*/ + +my_bool _ma_reenable_logging_for_table(MARIA_HA *info, my_bool flush_pages) +{ + MARIA_SHARE *share= info->s; + DBUG_ENTER("_ma_reenable_logging_for_table"); + + if (share->now_transactional == share->base.born_transactional || + !info->switched_transactional) + DBUG_RETURN(0); + info->switched_transactional= FALSE; + + if ((share->now_transactional= share->base.born_transactional)) + { + share->page_type= PAGECACHE_LSN_PAGE; + + /* + Copy state information that where updated while the table was used + in not transactional mode + */ + _ma_copy_nontrans_state_information(info); + _ma_reset_history(info->s); + + if (flush_pages) + { + /* + We are going to change callbacks; if a page is flushed at this moment + this can cause race conditions, that's one reason to flush pages + now. Other reasons: a checkpoint could be running and miss pages; the + pages have type PAGECACHE_PLAIN_PAGE which should not remain. As + there are no REDOs for pages, them, bitmaps and the state also have to + be flushed and synced. + */ + if (_ma_flush_table_files(info, MARIA_FLUSH_DATA | MARIA_FLUSH_INDEX, + FLUSH_RELEASE, FLUSH_RELEASE) || + _ma_state_info_write(share, + MA_STATE_INFO_WRITE_DONT_MOVE_OFFSET | + MA_STATE_INFO_WRITE_LOCK) || + _ma_sync_table_files(info)) + DBUG_RETURN(1); + } + else if (!maria_in_recovery) + { + /* + Except in Recovery, we mustn't leave dirty pages (see comments above). + Note that this does not verify that the state was flushed, but hey. + */ + pagecache_file_no_dirty_page(share->pagecache, &info->dfile); + pagecache_file_no_dirty_page(share->pagecache, &share->kfile); + } + _ma_set_data_pagecache_callbacks(&info->dfile, share); + _ma_set_index_pagecache_callbacks(&share->kfile, share); + _ma_bitmap_set_pagecache_callbacks(&share->bitmap.file, share); + /* + info->trn was not changed in the disable/enable combo, so that it's + still usable in this kind of combination: + external_lock; + start_bulk_insert; # table is empty, disables logging + end_bulk_insert; # enables logging + start_bulk_insert; # table is not empty, logging stays + # so rows insertion needs the real trn. + as happens during row-based replication on the slave. + */ + } + DBUG_RETURN(0); +} + + +static void print_redo_phase_progress(TRANSLOG_ADDRESS addr) +{ + static uint end_logno= FILENO_IMPOSSIBLE, percentage_printed= 0; + static ulong end_offset; + static ulonglong initial_remainder= ~(ulonglong) 0; + + uint cur_logno; + ulong cur_offset; + ulonglong local_remainder; + uint percentage_done; + + if (tracef == stdout) + return; + if (recovery_message_printed == REC_MSG_NONE) + { + print_preamble(); + fprintf(stderr, "recovered pages: 0%%"); + fflush(stderr); + procent_printed= 1; + recovery_message_printed= REC_MSG_REDO; + } + if (end_logno == FILENO_IMPOSSIBLE) + { + LSN end_addr= translog_get_horizon(); + end_logno= LSN_FILE_NO(end_addr); + end_offset= LSN_OFFSET(end_addr); + } + cur_logno= LSN_FILE_NO(addr); + cur_offset= LSN_OFFSET(addr); + local_remainder= (cur_logno == end_logno) ? (end_offset - cur_offset) : + (((longlong)log_file_size) - cur_offset + + max(end_logno - cur_logno - 1, 0) * ((longlong)log_file_size) + + end_offset); + if (initial_remainder == (ulonglong)(-1)) + initial_remainder= local_remainder; + percentage_done= (uint) ((initial_remainder - local_remainder) * ULL(100) / + initial_remainder); + if ((percentage_done - percentage_printed) >= 10) + { + percentage_printed= percentage_done; + fprintf(stderr, " %u%%", percentage_done); + fflush(stderr); + procent_printed= 1; + } +} + + +#ifdef MARIA_EXTERNAL_LOCKING +#error Marias Checkpoint and Recovery are really not ready for it +#endif + +/* +Recovery of the state : how it works +===================================== + +Here we ignore Checkpoints for a start. + +The state (MARIA_HA::MARIA_SHARE::MARIA_STATE_INFO) is updated in +memory frequently (at least at every row write/update/delete) but goes +to disk at few moments: maria_close() when closing the last open +instance, and a few rare places like CHECK/REPAIR/ALTER +(non-transactional tables also do it at maria_lock_database() but we +needn't cover them here). + +In case of crash, state on disk is likely to be older than what it was +in memory, the REDO phase needs to recreate the state as it was in +memory at the time of crash. When we say Recovery here we will always +mean "REDO phase". + +For example MARIA_STATUS_INFO::records (count of records). It is updated at +the end of every row write/update/delete/delete_all. When Recovery sees the +sign of such row operation (UNDO or REDO), it may need to update the records' +count if that count does not reflect that operation (is older). How to know +the age of the state compared to the log record: every time the state +goes to disk at runtime, its member "is_of_horizon" is updated to the +current end-of-log horizon. So Recovery just needs to compare is_of_horizon +and the record's LSN to know if it should modify "records". + +Other operations like ALTER TABLE DISABLE KEYS update the state but +don't write log records, thus the REDO phase cannot repeat their +effect on the state in case of crash. But we make them sync the state +as soon as they have finished. This reduces the window for a problem. + +It looks like only one thread at a time updates the state in memory or +on disk. We assume that the upper level (normally MySQL) has protection +against issuing HA_EXTRA_(FORCE_REOPEN|PREPARE_FOR_RENAME) so that these +are not issued while there are any running transactions on the given table. +If this is not done, we may write a corrupted state to disk. + +With checkpoints +================ + +Checkpoint module needs to read the state in memory and write it to +disk. This may happen while some other thread is modifying the state +in memory or on disk. Checkpoint thus may be reading changing data, it +needs a mutex to not have it corrupted, and concurrent modifiers of +the state need that mutex too for the same reason. +"records" is modified for every row write/update/delete, we don't want +to add a mutex lock/unlock there. So we re-use the mutex lock/unlock +which is already present in these moments, namely the log's mutex which is +taken when UNDO_ROW_INSERT|UPDATE|DELETE is written: we update "records" in +under-log-mutex hooks when writing these records (thus "records" is +not updated at the end of maria_write/update/delete() anymore). +Thus Checkpoint takes the log's lock and can read "records" from +memory an write it to disk and release log's lock. +We however want to avoid having the disk write under the log's +lock. So it has to be under another mutex, natural choice is +intern_lock (as Checkpoint needs it anyway to read MARIA_SHARE::kfile, +and as maria_close() takes it too). All state writes to disk are +changed to be protected with intern_lock. +So Checkpoint takes intern_lock, log's lock, reads "records" from +memory, releases log's lock, updates is_of_horizon and writes "records" to +disk, release intern_lock. +In practice, not only "records" needs to be written but the full +state. So, Checkpoint reads the full state from memory. Some other +thread may at this moment be modifying in memory some pieces of the +state which are not protected by the lock's log (see ma_extra.c +HA_EXTRA_NO_KEYS), and Checkpoint would be reading a corrupted state +from memory; to guard against that we extend the intern_lock-zone to +changes done to the state in memory by HA_EXTRA_NO_KEYS et al, and +also any change made in memory to create_rename_lsn/state_is_of_horizon. +Last, we don't want in Checkpoint to do + log lock; read state from memory; release log lock; +for each table, it may hold the log's lock too much in total. +So, we instead do + log lock; read N states from memory; release log lock; +Thus, the sequence above happens outside of any intern_lock. +But this re-introduces the problem that some other thread may be changing the +state in memory and on disk under intern_lock, without log's lock, like +HA_EXTRA_NO_KEYS, while we read the N states. However, when Checkpoint later +comes to handling the table under intern_lock, which is serialized with +HA_EXTRA_NO_KEYS, it can see that is_of_horizon is higher then when the state +was read from memory under log's lock, and thus can decide to not flush the +obsolete state it has, knowing that the other thread flushed a more recent +state already. If on the other hand is_of_horizon is not higher, the read +state is current and can be flushed. So we have a per-table sequence: + lock intern_lock; test if is_of_horizon is higher than when we read the state + under log's lock; if no then flush the read state to disk. +*/ + +/* some comments and pseudo-code which we keep for later */ +#if 0 + /* + MikaelR suggests: support checkpoints during REDO phase too: do checkpoint + after a certain amount of log records have been executed. This helps + against repeated crashes. Those checkpoints could not be user-requested + (as engine is not communicating during the REDO phase), so they would be + automatic: this changes the original assumption that we don't write to the + log while in the REDO phase, but why not. How often should we checkpoint? + */ + + /* + We want to have two steps: + engine->recover_with_max_memory(); + next_engine->recover_with_max_memory(); + engine->init_with_normal_memory(); + next_engine->init_with_normal_memory(); + So: in recover_with_max_memory() allocate a giant page cache, do REDO + phase, then all page cache is flushed and emptied and freed (only retain + small structures like TM): take full checkpoint, which is useful if + next engine crashes in its recovery the next second. + Destroy all shares (maria_close()), then at init_with_normal_memory() we + do this: + */ + + /**** UNDO PHASE *****/ + + /* + Launch one or more threads to do the background rollback. Don't wait for + them to complete their rollback (background rollback; for debugging, we + can have an option which waits). Set a counter (total_of_rollback_threads) + to the number of threads to lauch. + + Note that InnoDB's rollback-in-background works as long as InnoDB is the + last engine to recover, otherwise MySQL will refuse new connections until + the last engine has recovered so it's not "background" from the user's + point of view. InnoDB is near top of sys_table_types so all others + (e.g. BDB) recover after it... So it's really "online rollback" only if + InnoDB is the only engine. + */ + + /* wake up delete/update handler */ + /* tell the TM that it can now accept new transactions */ + + /* + mark that checkpoint requests are now allowed. + */ +#endif diff --git a/storage/maria/ma_recovery.h b/storage/maria/ma_recovery.h new file mode 100644 index 00000000000..aa8fa7ecae9 --- /dev/null +++ b/storage/maria/ma_recovery.h @@ -0,0 +1,33 @@ +/* Copyright (C) 2006,2007 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 */ + +/* + WL#3072 Maria recovery + First version written by Guilhem Bichot on 2006-04-27. +*/ + +/* This is the interface of this module. */ + +/* Performs recovery of the engine at start */ + +C_MODE_START +enum maria_apply_log_way +{ MARIA_LOG_APPLY, MARIA_LOG_DISPLAY_HEADER, MARIA_LOG_CHECK }; +int maria_recovery_from_log(void); +int maria_apply_log(LSN lsn, enum maria_apply_log_way apply, + FILE *trace_file, + my_bool execute_undo_phase, my_bool skip_DDLs, + my_bool take_checkpoints, uint *warnings_count); +C_MODE_END diff --git a/storage/maria/ma_recovery_util.c b/storage/maria/ma_recovery_util.c new file mode 100644 index 00000000000..a45a990472e --- /dev/null +++ b/storage/maria/ma_recovery_util.c @@ -0,0 +1,138 @@ +/* Copyright (C) 2006,2007,2008 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 */ + +/* + Q: Why isn't ma_recovery_util.c simply moved to ma_recovery.c ? + + A: ma_recovery.c, because it invokes objects from ma_check.c (like + maria_chk_init()) causes the following problem: + if a source file a.c of a program invokes a function defined in + ma_recovery.c, then a.o depends on ma_recovery.o which depends on + ma_check.o: linker thus brings in ma_check.o. That brings in the + dependencies of ma_check.o which are definitions of _ma_check_print_info() + etc; if a.o does not define them then the ones of ha_maria.o are used + i.e. ha_maria.o is linked into the program, and this brings in dependencies + of ha_maria.o on mysqld.o into the program's linking which thus fails, as + the program is not linked with mysqld.o. + Thus, while several functions defined in ma_recovery.c could be useful to + other files, they cannot be used by them. + So we are going to gradually move a great share of ma_recovery.c's exported + functions into the present file, to isolate the problematic components and + avoid the problem. +*/ + +#include "maria_def.h" + +HASH all_dirty_pages; +struct st_dirty_page /* used only in the REDO phase */ +{ + uint64 file_and_page_id; + LSN rec_lsn; +}; +/* + LSN after which dirty pages list does not apply. Can be slightly before + when ma_checkpoint_execute() started. +*/ +LSN checkpoint_start= LSN_IMPOSSIBLE; + +/** @todo looks like duplicate of recovery_message_printed */ +my_bool procent_printed; +FILE *tracef; /**< trace file for debugging */ + + +/** @brief Prints to a trace file if it is not NULL */ +void tprint(FILE *trace_file __attribute__ ((unused)), + const char *format __attribute__ ((unused)), ...) +{ + va_list args; + va_start(args, format); + DBUG_PRINT("info", ("%s", format)); + if (trace_file != NULL) + { + if (procent_printed) + { + procent_printed= 0; + fputc('\n', trace_file); + } + vfprintf(trace_file, format, args); + } + va_end(args); +} + + +void eprint(FILE *trace_file __attribute__ ((unused)), + const char *format __attribute__ ((unused)), ...) +{ + va_list args; + va_start(args, format); + DBUG_PRINT("error", ("%s", format)); + if (!trace_file) + trace_file= stderr; + + if (procent_printed) + { + /* In silent mode, print on another line than the 0% 10% 20% line */ + procent_printed= 0; + fputc('\n', trace_file); + } + vfprintf(trace_file , format, args); + fputc('\n', trace_file); + if (trace_file != stderr) + { + va_start(args, format); + my_printv_error(HA_ERR_INITIALIZATION, format, MYF(0), args); + } + va_end(args); + fflush(trace_file); +} + + +/** + Tells if the dirty pages list found in checkpoint record allows to ignore a + REDO for a certain page. + + @param shortid short id of the table + @param lsn REDO record's LSN + @param page page number + @param index TRUE if index page, FALSE if data page +*/ + +my_bool _ma_redo_not_needed_for_page(uint16 shortid, LSN lsn, + pgcache_page_no_t page, + my_bool index) +{ + if (cmp_translog_addr(lsn, checkpoint_start) < 0) + { + /* + 64-bit key is formed like this: + Most significant byte: 0 if data page, 1 if index page + Next 2 bytes: table's short id + Next 5 bytes: page number + */ + uint64 file_and_page_id= + (((uint64)((index << 16) | shortid)) << 40) | page; + struct st_dirty_page *dirty_page= (struct st_dirty_page *) + hash_search(&all_dirty_pages, + (uchar *)&file_and_page_id, sizeof(file_and_page_id)); + DBUG_PRINT("info", ("in dirty pages list: %d", dirty_page != NULL)); + if ((dirty_page == NULL) || + cmp_translog_addr(lsn, dirty_page->rec_lsn) < 0) + { + tprint(tracef, ", ignoring because of dirty_pages list\n"); + return TRUE; + } + } + return FALSE; +} diff --git a/storage/maria/ma_recovery_util.h b/storage/maria/ma_recovery_util.h new file mode 100644 index 00000000000..a35fea84fe9 --- /dev/null +++ b/storage/maria/ma_recovery_util.h @@ -0,0 +1,37 @@ +/* Copyright (C) 2006,2007,2008 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 */ + +struct st_dirty_page /* used only in the REDO phase */ +{ + uint64 file_and_page_id; + LSN rec_lsn; +}; +extern HASH all_dirty_pages; +/* + LSN after which dirty pages list does not apply. Can be slightly before + when ma_checkpoint_execute() started. +*/ +extern LSN checkpoint_start; +extern my_bool procent_printed; +extern FILE *tracef; + + +my_bool _ma_redo_not_needed_for_page(uint16 shortid, LSN lsn, + pgcache_page_no_t page, + my_bool index); +void tprint(FILE *trace_file, const char *format, ...) + ATTRIBUTE_FORMAT(printf, 2, 3); +void eprint(FILE *trace_file, const char *format, ...) + ATTRIBUTE_FORMAT(printf, 2, 3); diff --git a/storage/maria/ma_rename.c b/storage/maria/ma_rename.c new file mode 100644 index 00000000000..380f3da3c46 --- /dev/null +++ b/storage/maria/ma_rename.c @@ -0,0 +1,135 @@ +/* Copyright (C) 2006 MySQL AB & MySQL Finland AB & TCX DataKonsult 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 */ + +/* + Rename a table +*/ + +#include "ma_fulltext.h" +#include "trnman_public.h" + +/** + @brief renames a table + + @param old_name current name of table + @param new_name table should be renamed to this name + + @return Operation status + @retval 0 OK + @retval !=0 Error +*/ + +int maria_rename(const char *old_name, const char *new_name) +{ + char from[FN_REFLEN],to[FN_REFLEN]; + int data_file_rename_error; +#ifdef USE_RAID + uint raid_type=0,raid_chunks=0; +#endif + MARIA_HA *info; + MARIA_SHARE *share; + myf sync_dir; + DBUG_ENTER("maria_rename"); + +#ifdef EXTRA_DEBUG + _ma_check_table_is_closed(old_name,"rename old_table"); + _ma_check_table_is_closed(new_name,"rename new table2"); +#endif + /** @todo LOCK take X-lock on table */ + if (!(info= maria_open(old_name, O_RDWR, HA_OPEN_FOR_REPAIR))) + DBUG_RETURN(my_errno); + share= info->s; +#ifdef USE_RAID + raid_type = share->base.raid_type; + raid_chunks = share->base.raid_chunks; +#endif + + /* + the renaming of an internal table to the final table (like in ALTER TABLE) + is the moment when this table receives its correct create_rename_lsn and + this is important; make sure transactionality has been re-enabled. + */ + DBUG_ASSERT(share->now_transactional == share->base.born_transactional); + sync_dir= (share->now_transactional && !share->temporary && + !maria_in_recovery) ? MY_SYNC_DIR : 0; + if (sync_dir) + { + LSN lsn; + LEX_CUSTRING log_array[TRANSLOG_INTERNAL_PARTS + 2]; + uint old_name_len= strlen(old_name)+1, new_name_len= strlen(new_name)+1; + log_array[TRANSLOG_INTERNAL_PARTS + 0].str= (uchar*)old_name; + log_array[TRANSLOG_INTERNAL_PARTS + 0].length= old_name_len; + log_array[TRANSLOG_INTERNAL_PARTS + 1].str= (uchar*)new_name; + log_array[TRANSLOG_INTERNAL_PARTS + 1].length= new_name_len; + /* + For this record to be of any use for Recovery, we need the upper + MySQL layer to be crash-safe, which it is not now (that would require + work using the ddl_log of sql/sql_table.cc); when it is, we should + reconsider the moment of writing this log record (before or after op, + under THR_LOCK_maria or not...), how to use it in Recovery. + For now it can serve to apply logs to a backup so we sync it. + */ + if (unlikely(translog_write_record(&lsn, LOGREC_REDO_RENAME_TABLE, + &dummy_transaction_object, NULL, + old_name_len + new_name_len, + sizeof(log_array)/sizeof(log_array[0]), + log_array, NULL, NULL) || + translog_flush(lsn))) + { + maria_close(info); + DBUG_RETURN(1); + } + /* + store LSN into file, needed for Recovery to not be confused if a + RENAME happened (applying REDOs to the wrong table). + */ + if (_ma_update_state_lsns(share, lsn, share->state.create_trid, TRUE, + TRUE)) + { + maria_close(info); + DBUG_RETURN(1); + } + } + + maria_close(info); + + fn_format(from,old_name,"",MARIA_NAME_IEXT,MY_UNPACK_FILENAME|MY_APPEND_EXT); + fn_format(to,new_name,"",MARIA_NAME_IEXT,MY_UNPACK_FILENAME|MY_APPEND_EXT); + if (my_rename_with_symlink(from, to, MYF(MY_WME | sync_dir))) + DBUG_RETURN(my_errno); + fn_format(from,old_name,"",MARIA_NAME_DEXT,MY_UNPACK_FILENAME|MY_APPEND_EXT); + fn_format(to,new_name,"",MARIA_NAME_DEXT,MY_UNPACK_FILENAME|MY_APPEND_EXT); +#ifdef USE_RAID + if (raid_type) + data_file_rename_error= my_raid_rename(from, to, raid_chunks, + MYF(MY_WME | sync_dir)); + else +#endif + data_file_rename_error= + my_rename_with_symlink(from, to, MYF(MY_WME | sync_dir)); + if (data_file_rename_error) + { + /* + now we have a renamed index file and a non-renamed data file, try to + undo the rename of the index file. + */ + data_file_rename_error= my_errno; + fn_format(from, old_name, "", MARIA_NAME_IEXT, MYF(MY_UNPACK_FILENAME|MY_APPEND_EXT)); + fn_format(to, new_name, "", MARIA_NAME_IEXT, MYF(MY_UNPACK_FILENAME|MY_APPEND_EXT)); + my_rename_with_symlink(to, from, MYF(MY_WME | sync_dir)); + } + DBUG_RETURN(data_file_rename_error); + +} diff --git a/storage/maria/ma_rfirst.c b/storage/maria/ma_rfirst.c new file mode 100644 index 00000000000..226aaa551f0 --- /dev/null +++ b/storage/maria/ma_rfirst.c @@ -0,0 +1,26 @@ +/* Copyright (C) 2006 MySQL AB & MySQL Finland AB & TCX DataKonsult 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 "maria_def.h" + + /* Read first row through a specfic key */ + +int maria_rfirst(MARIA_HA *info, uchar *buf, int inx) +{ + DBUG_ENTER("maria_rfirst"); + info->cur_row.lastpos= HA_OFFSET_ERROR; + info->update|= HA_STATE_PREV_FOUND; + DBUG_RETURN(maria_rnext(info,buf,inx)); +} /* maria_rfirst */ diff --git a/storage/maria/ma_rkey.c b/storage/maria/ma_rkey.c new file mode 100644 index 00000000000..9e529aca666 --- /dev/null +++ b/storage/maria/ma_rkey.c @@ -0,0 +1,200 @@ +/* Copyright (C) 2006 MySQL AB & MySQL Finland AB & TCX DataKonsult 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 */ + +/* Read record based on a key */ + +#include "maria_def.h" +#include "ma_rt_index.h" + +/** + Read a record using key + + @note + Ordinary search_flag is 0 ; Give error if no record with key +*/ + +int maria_rkey(MARIA_HA *info, uchar *buf, int inx, const uchar *key_data, + key_part_map keypart_map, enum ha_rkey_function search_flag) +{ + uchar *key_buff; + MARIA_SHARE *share= info->s; + MARIA_KEYDEF *keyinfo; + HA_KEYSEG *last_used_keyseg; + uint32 nextflag; + MARIA_KEY key; + DBUG_ENTER("maria_rkey"); + DBUG_PRINT("enter", ("base: 0x%lx buf: 0x%lx inx: %d search_flag: %d", + (long) info, (long) buf, inx, search_flag)); + + if ((inx = _ma_check_index(info,inx)) < 0) + DBUG_RETURN(my_errno); + + info->update&= (HA_STATE_CHANGED | HA_STATE_ROW_CHANGED); + info->last_key_func= search_flag; + keyinfo= share->keyinfo + inx; + + key_buff= info->lastkey_buff+info->s->base.max_key_length; + + if (info->once_flags & USE_PACKED_KEYS) + { + info->once_flags&= ~USE_PACKED_KEYS; /* Reset flag */ + /* + key is already packed!; This happens when we are using a MERGE TABLE + In this key 'key_part_map' is the length of the key ! + */ + bmove(key_buff, key_data, keypart_map); + key.data= key_buff; + key.keyinfo= keyinfo; + key.data_length= keypart_map; + key.ref_length= 0; + key.flag= 0; + + last_used_keyseg= keyinfo->seg + info->last_used_keyseg; + } + else + { + DBUG_ASSERT(keypart_map); + /* Save the packed key for later use in the second buffer of lastkey. */ + _ma_pack_key(info, &key, inx, key_buff, key_data, + keypart_map, &last_used_keyseg); + /* Save packed_key_length for use by the MERGE engine. */ + info->pack_key_length= key.data_length; + info->last_used_keyseg= (uint16) (last_used_keyseg - + keyinfo->seg); + DBUG_EXECUTE("key", _ma_print_key(DBUG_FILE, &key);); + } + + if (fast_ma_readinfo(info)) + goto err; + if (share->lock_key_trees) + rw_rdlock(&keyinfo->root_lock); + + nextflag= maria_read_vec[search_flag] | key.flag; + + switch (keyinfo->key_alg) { +#ifdef HAVE_RTREE_KEYS + case HA_KEY_ALG_RTREE: + if (maria_rtree_find_first(info, &key, nextflag) < 0) + { + maria_print_error(info->s, HA_ERR_CRASHED); + my_errno= HA_ERR_CRASHED; + info->cur_row.lastpos= HA_OFFSET_ERROR; + } + break; +#endif + case HA_KEY_ALG_BTREE: + default: + if (!_ma_search(info, &key, nextflag, info->s->state.key_root[inx])) + { + MARIA_KEY lastkey; + lastkey.keyinfo= keyinfo; + lastkey.data= info->lastkey_buff; + /* + Found a key, but it might not be usable. We cannot use rows that + are inserted by other threads after we got our table lock + ("concurrent inserts"). The record may not even be present yet. + Keys are inserted into the index(es) before the record is + inserted into the data file. + */ + if ((*share->row_is_visible)(info)) + break; + + /* The key references a concurrently inserted record. */ + if (search_flag == HA_READ_KEY_EXACT && + last_used_keyseg == keyinfo->seg + keyinfo->keysegs) + { + /* Simply ignore the key if it matches exactly. (Bug #29838) */ + my_errno= HA_ERR_KEY_NOT_FOUND; + info->cur_row.lastpos= HA_OFFSET_ERROR; + break; + } + + do + { + uint not_used[2]; + /* + Skip rows that are inserted by other threads since we got + a lock. Note that this can only happen if we are not + searching after a full length exact key, because the keys + are sorted according to position. + */ + lastkey.data_length= info->last_key.data_length; + lastkey.ref_length= info->last_key.ref_length; + lastkey.flag= info->last_key.flag; + if (_ma_search_next(info, &lastkey, maria_readnext_vec[search_flag], + info->s->state.key_root[inx])) + break; /* purecov: inspected */ + /* + Check that the found key does still match the search. + _ma_search_next() delivers the next key regardless of its + value. + */ + if (!(nextflag & (SEARCH_BIGGER | SEARCH_SMALLER)) && + ha_key_cmp(keyinfo->seg, info->last_key.data, key.data, + key.data_length, SEARCH_FIND, not_used)) + { + /* purecov: begin inspected */ + my_errno= HA_ERR_KEY_NOT_FOUND; + info->cur_row.lastpos= HA_OFFSET_ERROR; + break; + /* purecov: end */ + } + } while (!(*share->row_is_visible)(info)); + } + } + if (share->lock_key_trees) + rw_unlock(&keyinfo->root_lock); + + if (info->cur_row.lastpos == HA_OFFSET_ERROR) + { + fast_ma_writeinfo(info); + goto err; + } + + /* Calculate length of the found key; Used by maria_rnext_same */ + if ((keyinfo->flag & HA_VAR_LENGTH_KEY)) + info->last_rkey_length= _ma_keylength_part(keyinfo, info->lastkey_buff, + last_used_keyseg); + else + info->last_rkey_length= key.data_length; + + /* Check if we don't want to have record back, only error message */ + if (!buf) + { + fast_ma_writeinfo(info); + DBUG_RETURN(0); + } + if (!(*info->read_record)(info, buf, info->cur_row.lastpos)) + { + info->update|= HA_STATE_AKTIV; /* Record is read */ + DBUG_RETURN(0); + } + + info->cur_row.lastpos= HA_OFFSET_ERROR; /* Didn't find row */ + +err: + /* Store last used key as a base for read next */ + memcpy(info->last_key.data, key_buff, key.data_length); + info->last_key.data_length= key.data_length; + info->last_key.ref_length= info->s->base.rec_reflength; + info->last_key.flag= 0; + /* Create key with rowid 0 */ + bzero((char*) info->last_key.data + info->last_key.data_length, + info->s->base.rec_reflength); + + if (search_flag == HA_READ_AFTER_KEY) + info->update|=HA_STATE_NEXT_FOUND; /* Previous gives last row */ + DBUG_RETURN(my_errno); +} /* _ma_rkey */ diff --git a/storage/maria/ma_rlast.c b/storage/maria/ma_rlast.c new file mode 100644 index 00000000000..a9a470d37d9 --- /dev/null +++ b/storage/maria/ma_rlast.c @@ -0,0 +1,26 @@ +/* Copyright (C) 2006 MySQL AB & MySQL Finland AB & TCX DataKonsult 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 "maria_def.h" + + /* Read last row with the same key as the previous read. */ + +int maria_rlast(MARIA_HA *info, uchar *buf, int inx) +{ + DBUG_ENTER("maria_rlast"); + info->cur_row.lastpos= HA_OFFSET_ERROR; + info->update|= HA_STATE_NEXT_FOUND; + DBUG_RETURN(maria_rprev(info,buf,inx)); +} /* maria_rlast */ diff --git a/storage/maria/ma_rnext.c b/storage/maria/ma_rnext.c new file mode 100644 index 00000000000..be960eccfe0 --- /dev/null +++ b/storage/maria/ma_rnext.c @@ -0,0 +1,125 @@ +/* Copyright (C) 2006 MySQL AB & MySQL Finland AB & TCX DataKonsult 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 "maria_def.h" + +#include "ma_rt_index.h" + + /* + Read next row with the same key as previous read + One may have done a write, update or delete of the previous row. + NOTE! Even if one changes the previous row, the next read is done + based on the position of the last used key! + */ + +int maria_rnext(MARIA_HA *info, uchar *buf, int inx) +{ + int error,changed; + uint flag; + MARIA_SHARE *share= info->s; + MARIA_KEYDEF *keyinfo; + DBUG_ENTER("maria_rnext"); + + if ((inx = _ma_check_index(info,inx)) < 0) + DBUG_RETURN(my_errno); + flag=SEARCH_BIGGER; /* Read next */ + if (info->cur_row.lastpos == HA_OFFSET_ERROR && + info->update & HA_STATE_PREV_FOUND) + flag=0; /* Read first */ + + if (fast_ma_readinfo(info)) + DBUG_RETURN(my_errno); + keyinfo= share->keyinfo + inx; + if (share->lock_key_trees) + rw_rdlock(&keyinfo->root_lock); + changed= _ma_test_if_changed(info); + if (!flag) + { + switch (keyinfo->key_alg){ +#ifdef HAVE_RTREE_KEYS + case HA_KEY_ALG_RTREE: + error=maria_rtree_get_first(info, inx, + info->last_key.data_length + + info->last_key.ref_length); + + break; +#endif + case HA_KEY_ALG_BTREE: + default: + error= _ma_search_first(info, keyinfo, share->state.key_root[inx]); + break; + } + } + else + { + switch (keyinfo->key_alg) { +#ifdef HAVE_RTREE_KEYS + case HA_KEY_ALG_RTREE: + /* + Note that rtree doesn't support that the table + may be changed since last call, so we do need + to skip rows inserted by other threads like in btree + */ + error= maria_rtree_get_next(info, inx, info->last_key.data_length + + info->last_key.ref_length); + break; +#endif + case HA_KEY_ALG_BTREE: + default: + if (!changed) + error= _ma_search_next(info, &info->last_key, + flag | info->last_key.flag, + share->state.key_root[inx]); + else + error= _ma_search(info, &info->last_key, flag | info->last_key.flag, + share->state.key_root[inx]); + } + } + + if (!error) + { + while (!(*share->row_is_visible)(info)) + { + /* Skip rows inserted by other threads since we got a lock */ + if ((error= _ma_search_next(info, &info->last_key, + SEARCH_BIGGER, + share->state.key_root[inx]))) + break; + } + } + if (share->lock_key_trees) + rw_unlock(&keyinfo->root_lock); + + /* Don't clear if database-changed */ + info->update&= (HA_STATE_CHANGED | HA_STATE_ROW_CHANGED); + info->update|= HA_STATE_NEXT_FOUND; + + if (error) + { + if (my_errno == HA_ERR_KEY_NOT_FOUND) + my_errno=HA_ERR_END_OF_FILE; + } + else if (!buf) + { + DBUG_RETURN(info->cur_row.lastpos == HA_OFFSET_ERROR ? my_errno : 0); + } + else if (!(*info->read_record)(info, buf, info->cur_row.lastpos)) + { + info->update|= HA_STATE_AKTIV; /* Record is read */ + DBUG_RETURN(0); + } + DBUG_PRINT("error",("Got error: %d, errno: %d",error, my_errno)); + DBUG_RETURN(my_errno); +} /* maria_rnext */ diff --git a/storage/maria/ma_rnext_same.c b/storage/maria/ma_rnext_same.c new file mode 100644 index 00000000000..cbd81d20816 --- /dev/null +++ b/storage/maria/ma_rnext_same.c @@ -0,0 +1,108 @@ +/* Copyright (C) 2006 MySQL AB & MySQL Finland AB & TCX DataKonsult 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 "maria_def.h" +#include "ma_rt_index.h" + +/* + Read next row with the same key as previous read, but abort if + the key changes. + One may have done a write, update or delete of the previous row. + + NOTE! Even if one changes the previous row, the next read is done + based on the position of the last used key! +*/ + +int maria_rnext_same(MARIA_HA *info, uchar *buf) +{ + int error; + uint inx,not_used[2]; + MARIA_KEYDEF *keyinfo; + DBUG_ENTER("maria_rnext_same"); + + if ((int) (inx= info->lastinx) < 0 || + info->cur_row.lastpos == HA_OFFSET_ERROR) + DBUG_RETURN(my_errno=HA_ERR_WRONG_INDEX); + if (fast_ma_readinfo(info)) + DBUG_RETURN(my_errno); + + keyinfo= info->s->keyinfo+inx; + if (info->s->lock_key_trees) + rw_rdlock(&keyinfo->root_lock); + + switch (keyinfo->key_alg) { +#ifdef HAVE_RTREE_KEYS + case HA_KEY_ALG_RTREE: + if ((error=maria_rtree_find_next(info,inx, + maria_read_vec[info->last_key_func]))) + { + error=1; + my_errno=HA_ERR_END_OF_FILE; + info->cur_row.lastpos= HA_OFFSET_ERROR; + break; + } + break; +#endif + case HA_KEY_ALG_BTREE: + default: + if (!(info->update & HA_STATE_RNEXT_SAME)) + { + /* First rnext_same; Store old key */ + memcpy(info->lastkey_buff2, info->last_key.data, + info->last_rkey_length); + } + for (;;) + { + if ((error= _ma_search_next(info, &info->last_key, + SEARCH_BIGGER, + info->s->state.key_root[inx]))) + break; + if (ha_key_cmp(keyinfo->seg, info->last_key.data, + info->lastkey_buff2, + info->last_rkey_length, SEARCH_FIND, + not_used)) + { + error=1; + my_errno=HA_ERR_END_OF_FILE; + info->cur_row.lastpos= HA_OFFSET_ERROR; + break; + } + /* Skip rows that are inserted by other threads since we got a lock */ + if ((info->s->row_is_visible)(info)) + break; + } + } + if (info->s->lock_key_trees) + rw_unlock(&keyinfo->root_lock); + /* Don't clear if database-changed */ + info->update&= (HA_STATE_CHANGED | HA_STATE_ROW_CHANGED); + info->update|= HA_STATE_NEXT_FOUND | HA_STATE_RNEXT_SAME; + + if (error) + { + if (my_errno == HA_ERR_KEY_NOT_FOUND) + my_errno=HA_ERR_END_OF_FILE; + } + else if (!buf) + { + DBUG_RETURN(info->cur_row.lastpos == HA_OFFSET_ERROR ? my_errno : 0); + } + else if (!(*info->read_record)(info, buf, info->cur_row.lastpos)) + { + info->update|= HA_STATE_AKTIV; /* Record is read */ + DBUG_RETURN(0); + } + DBUG_RETURN(my_errno); +} /* maria_rnext_same */ diff --git a/storage/maria/ma_rprev.c b/storage/maria/ma_rprev.c new file mode 100644 index 00000000000..b9f46d7c405 --- /dev/null +++ b/storage/maria/ma_rprev.c @@ -0,0 +1,86 @@ +/* Copyright (C) 2006 MySQL AB & MySQL Finland AB & TCX DataKonsult 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 "maria_def.h" + + /* + Read previous row with the same key as previous read + One may have done a write, update or delete of the previous row. + NOTE! Even if one changes the previous row, the next read is done + based on the position of the last used key! + */ + +int maria_rprev(MARIA_HA *info, uchar *buf, int inx) +{ + int error,changed; + register uint flag; + MARIA_SHARE *share= info->s; + MARIA_KEYDEF *keyinfo; + DBUG_ENTER("maria_rprev"); + + if ((inx = _ma_check_index(info,inx)) < 0) + DBUG_RETURN(my_errno); + flag=SEARCH_SMALLER; /* Read previous */ + if (info->cur_row.lastpos == HA_OFFSET_ERROR && + info->update & HA_STATE_NEXT_FOUND) + flag=0; /* Read last */ + + if (fast_ma_readinfo(info)) + DBUG_RETURN(my_errno); + keyinfo= share->keyinfo + inx; + changed= _ma_test_if_changed(info); + if (share->lock_key_trees) + rw_rdlock(&keyinfo->root_lock); + if (!flag) + error= _ma_search_last(info, keyinfo, share->state.key_root[inx]); + else if (!changed) + error= _ma_search_next(info, &info->last_key, + flag | info->last_key.flag, + share->state.key_root[inx]); + else + error= _ma_search(info, &info->last_key, flag | info->last_key.flag, + share->state.key_root[inx]); + + if (!error) + { + while (!(*share->row_is_visible)(info)) + { + /* Skip rows that are inserted by other threads since we got a lock */ + if ((error= _ma_search_next(info, &info->last_key, + SEARCH_SMALLER, + share->state.key_root[inx]))) + break; + } + } + if (share->lock_key_trees) + rw_unlock(&keyinfo->root_lock); + info->update&= (HA_STATE_CHANGED | HA_STATE_ROW_CHANGED); + info->update|= HA_STATE_PREV_FOUND; + if (error) + { + if (my_errno == HA_ERR_KEY_NOT_FOUND) + my_errno=HA_ERR_END_OF_FILE; + } + else if (!buf) + { + DBUG_RETURN(info->cur_row.lastpos == HA_OFFSET_ERROR ? my_errno : 0); + } + else if (!(*info->read_record)(info, buf, info->cur_row.lastpos)) + { + info->update|= HA_STATE_AKTIV; /* Record is read */ + DBUG_RETURN(0); + } + DBUG_RETURN(my_errno); +} /* maria_rprev */ diff --git a/storage/maria/ma_rrnd.c b/storage/maria/ma_rrnd.c new file mode 100644 index 00000000000..24c4bfdd467 --- /dev/null +++ b/storage/maria/ma_rrnd.c @@ -0,0 +1,44 @@ +/* Copyright (C) 2006 MySQL AB & MySQL Finland AB & TCX DataKonsult 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 */ + +/* Read a record with random-access. The position to the record must + get by MARIA_HA. The next record can be read with pos= MARIA_POS_ERROR */ + + +#include "maria_def.h" + +/* + Read a row based on position. + + RETURN + 0 Ok. + HA_ERR_RECORD_DELETED Record is deleted. + HA_ERR_END_OF_FILE EOF. +*/ + +int maria_rrnd(MARIA_HA *info, uchar *buf, MARIA_RECORD_POS filepos) +{ + DBUG_ENTER("maria_rrnd"); + + DBUG_ASSERT(filepos != HA_OFFSET_ERROR); + + /* Init all but update-flag */ + info->update&= (HA_STATE_CHANGED | HA_STATE_ROW_CHANGED); + if (info->opt_flag & WRITE_CACHE_USED && flush_io_cache(&info->rec_cache)) + DBUG_RETURN(my_errno); + + info->cur_row.lastpos= filepos; /* Remember for update */ + DBUG_RETURN((*info->s->read_record)(info, buf, filepos)); +} diff --git a/storage/maria/ma_rsame.c b/storage/maria/ma_rsame.c new file mode 100644 index 00000000000..4bdbfd526ba --- /dev/null +++ b/storage/maria/ma_rsame.c @@ -0,0 +1,78 @@ +/* Copyright (C) 2006 MySQL AB & MySQL Finland AB & TCX DataKonsult 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 "maria_def.h" + +/** + Find current row with read on position or read on key + + @notes + If inx >= 0 find record using key + + @warning + This function is not row version safe. + This is not crtical as this function is not used by MySQL + + @return + @retval 0 Ok + @retval HA_ERR_KEY_NOT_FOUND Row is deleted + @retval HA_ERR_END_OF_FILE End of file +*/ + + +int maria_rsame(MARIA_HA *info, uchar *record, int inx) +{ + DBUG_ENTER("maria_rsame"); + + if (inx != -1 && ! maria_is_key_active(info->s->state.key_map, inx)) + { + DBUG_PRINT("error", ("wrong index usage")); + DBUG_RETURN(my_errno=HA_ERR_WRONG_INDEX); + } + if (info->cur_row.lastpos == HA_OFFSET_ERROR || + info->update & HA_STATE_DELETED) + { + DBUG_PRINT("error", ("no current record")); + DBUG_RETURN(my_errno=HA_ERR_KEY_NOT_FOUND); /* No current record */ + } + info->update&= (HA_STATE_CHANGED | HA_STATE_ROW_CHANGED); + + /* Read row from data file */ + if (fast_ma_readinfo(info)) + DBUG_RETURN(my_errno); + + if (inx >= 0) + { + MARIA_KEYDEF *keyinfo= info->s->keyinfo + inx; + info->lastinx= inx; + (*keyinfo->make_key)(info, &info->last_key, (uint) inx, + info->lastkey_buff, record, + info->cur_row.lastpos, + info->cur_row.trid); + if (info->s->lock_key_trees) + rw_rdlock(&keyinfo->root_lock); + VOID(_ma_search(info, &info->last_key, SEARCH_SAME, + info->s->state.key_root[inx])); + if (info->s->lock_key_trees) + rw_unlock(&keyinfo->root_lock); + } + + if (!(*info->read_record)(info, record, info->cur_row.lastpos)) + DBUG_RETURN(0); + if (my_errno == HA_ERR_RECORD_DELETED) + my_errno=HA_ERR_KEY_NOT_FOUND; + DBUG_PRINT("error", ("my_errno: %d", my_errno)); + DBUG_RETURN(my_errno); +} /* maria_rsame */ diff --git a/storage/maria/ma_rsamepos.c b/storage/maria/ma_rsamepos.c new file mode 100644 index 00000000000..d2099e7b116 --- /dev/null +++ b/storage/maria/ma_rsamepos.c @@ -0,0 +1,63 @@ +/* Copyright (C) 2006 MySQL AB & MySQL Finland AB & TCX DataKonsult 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 */ + +/* read record through position and fix key-position */ +/* As maria_rsame but supply a position */ + +#include "maria_def.h" + + +/* + Read row based on postion + + @param inx If inx >= 0 postion the given index on found row + + @return + @retval 0 Ok + @retval HA_ERR_KEY_NOT_FOUND Row is deleted + @retval HA_ERR_END_OF_FILE End of file +*/ + +int maria_rsame_with_pos(MARIA_HA *info, uchar *record, int inx, + MARIA_RECORD_POS filepos) +{ + DBUG_ENTER("maria_rsame_with_pos"); + DBUG_PRINT("enter",("index: %d filepos: %ld", inx, (long) filepos)); + + if (inx < -1 || + (inx >= 0 && ! maria_is_key_active(info->s->state.key_map, inx))) + { + DBUG_RETURN(my_errno=HA_ERR_WRONG_INDEX); + } + + info->update&= (HA_STATE_CHANGED | HA_STATE_ROW_CHANGED); + if ((*info->s->read_record)(info, record, filepos)) + { + if (my_errno == HA_ERR_RECORD_DELETED) + my_errno=HA_ERR_KEY_NOT_FOUND; + DBUG_RETURN(my_errno); + } + info->cur_row.lastpos= filepos; + info->lastinx= inx; + if (inx >= 0) + { + (*info->s->keyinfo[inx].make_key)(info, &info->last_key, (uint) inx, + info->lastkey_buff, + record, info->cur_row.lastpos, + info->cur_row.trid); + info->update|=HA_STATE_KEY_CHANGED; /* Don't use indexposition */ + } + DBUG_RETURN(0); +} /* maria_rsame_pos */ diff --git a/storage/maria/ma_rt_index.c b/storage/maria/ma_rt_index.c new file mode 100644 index 00000000000..134514091bb --- /dev/null +++ b/storage/maria/ma_rt_index.c @@ -0,0 +1,1340 @@ +/* Copyright (C) 2006 MySQL AB & Ramil Kalimullin & MySQL Finland AB + & TCX DataKonsult 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 "maria_def.h" +#include "trnman.h" +#include "ma_key_recover.h" + +#ifdef HAVE_RTREE_KEYS + +#include "ma_rt_index.h" +#include "ma_rt_key.h" +#include "ma_rt_mbr.h" + +#define REINSERT_BUFFER_INC 10 +#define PICK_BY_AREA +/*#define PICK_BY_PERIMETER*/ + +typedef struct st_page_level +{ + uint level; + my_off_t offs; +} stPageLevel; + +typedef struct st_page_list +{ + uint n_pages; + uint m_pages; + stPageLevel *pages; +} stPageList; + + +/* + Find next key in r-tree according to search_flag recursively + + NOTES + Used in maria_rtree_find_first() and maria_rtree_find_next() + + RETURN + -1 Error + 0 Found + 1 Not found +*/ + +static int maria_rtree_find_req(MARIA_HA *info, MARIA_KEYDEF *keyinfo, + uint32 search_flag, + uint nod_cmp_flag, my_off_t page_pos, + int level) +{ + MARIA_SHARE *share= info->s; + uint nod_flag; + int res; + uchar *page_buf, *k, *last; + int key_data_length; + uint *saved_key= (uint*) (info->maria_rtree_recursion_state) + level; + MARIA_PAGE page; + + if (!(page_buf= (uchar*) my_alloca((uint) keyinfo->block_length))) + { + my_errno= HA_ERR_OUT_OF_MEM; + return -1; + } + if (_ma_fetch_keypage(&page, info, keyinfo, page_pos, + PAGECACHE_LOCK_LEFT_UNLOCKED, + DFLT_INIT_HITS, page_buf, 0)) + goto err; + nod_flag= page.node; + + key_data_length= keyinfo->keylength - share->base.rec_reflength; + + if (info->maria_rtree_recursion_depth >= level) + { + k= page_buf + *saved_key; + } + else + { + k= rt_PAGE_FIRST_KEY(share, page_buf, nod_flag); + } + last= rt_PAGE_END(&page); + + for (; k < last; k= rt_PAGE_NEXT_KEY(share, k, key_data_length, nod_flag)) + { + if (nod_flag) + { + /* this is an internal node in the tree */ + if (!(res= maria_rtree_key_cmp(keyinfo->seg, + info->first_mbr_key, k, + info->last_rkey_length, nod_cmp_flag))) + { + switch ((res= maria_rtree_find_req(info, keyinfo, search_flag, + nod_cmp_flag, + _ma_kpos(nod_flag, k), + level + 1))) + { + case 0: /* found - exit from recursion */ + *saved_key= k - page_buf; + goto ok; + case 1: /* not found - continue searching */ + info->maria_rtree_recursion_depth= level; + break; + default: /* error */ + case -1: + goto err; + } + } + } + else + { + /* this is a leaf */ + if (!maria_rtree_key_cmp(keyinfo->seg, info->first_mbr_key, + k, info->last_rkey_length, search_flag)) + { + uchar *after_key= rt_PAGE_NEXT_KEY(share, k, key_data_length, 0); + MARIA_KEY tmp_key; + + /* + We don't need to set all MARIA_KEY elements here as + _ma_row_pos_from_key() only uses a few of them. + */ + tmp_key.keyinfo= keyinfo; + tmp_key.data= k; + tmp_key.data_length= key_data_length; + + info->cur_row.lastpos= _ma_row_pos_from_key(&tmp_key); + info->last_key.keyinfo= keyinfo; + info->last_key.data_length= key_data_length; + info->last_key.ref_length= share->base.rec_reflength; + info->last_key.flag= 0; + memcpy(info->last_key.data, k, + info->last_key.data_length + info->last_key.ref_length); + info->maria_rtree_recursion_depth= level; + *saved_key= last - page_buf; + + if (after_key < last) + { + uchar *keyread_buff= info->keyread_buff; + info->int_keypos= keyread_buff; + info->int_maxpos= keyread_buff + (last - after_key); + memcpy(keyread_buff, after_key, last - after_key); + info->keyread_buff_used= 0; + } + else + { + info->keyread_buff_used= 1; + } + + res= 0; + goto ok; + } + } + } + info->cur_row.lastpos= HA_OFFSET_ERROR; + my_errno= HA_ERR_KEY_NOT_FOUND; + res= 1; + +ok: + my_afree(page_buf); + return res; + +err: + my_afree(page_buf); + info->cur_row.lastpos= HA_OFFSET_ERROR; + return -1; +} + + +/* + Find first key in r-tree according to search_flag condition + + SYNOPSIS + maria_rtree_find_first() + info Handler to MARIA file + key Key to search for + search_flag Bitmap of flags how to do the search + + RETURN + -1 Error + 0 Found + 1 Not found +*/ + +int maria_rtree_find_first(MARIA_HA *info, MARIA_KEY *key, uint32 search_flag) +{ + my_off_t root; + uint nod_cmp_flag; + MARIA_KEYDEF *keyinfo= key->keyinfo; + + if ((root= info->s->state.key_root[keyinfo->key_nr]) == HA_OFFSET_ERROR) + { + my_errno= HA_ERR_END_OF_FILE; + return -1; + } + + /* + Save searched key, include data pointer. + The data pointer is required if the search_flag contains MBR_DATA. + (minimum bounding rectangle) + */ + memcpy(info->first_mbr_key, key->data, key->data_length + key->ref_length); + info->last_rkey_length= key->data_length; + + info->maria_rtree_recursion_depth= -1; + info->keyread_buff_used= 1; + + nod_cmp_flag= ((search_flag & (MBR_EQUAL | MBR_WITHIN)) ? + MBR_WITHIN : MBR_INTERSECT); + return maria_rtree_find_req(info, keyinfo, search_flag, nod_cmp_flag, root, + 0); +} + + +/* + Find next key in r-tree according to search_flag condition + + SYNOPSIS + maria_rtree_find_next() + info Handler to MARIA file + uint keynr Key number to use + search_flag Bitmap of flags how to do the search + + RETURN + -1 Error + 0 Found + 1 Not found +*/ + +int maria_rtree_find_next(MARIA_HA *info, uint keynr, uint32 search_flag) +{ + my_off_t root; + uint32 nod_cmp_flag; + MARIA_KEYDEF *keyinfo= info->s->keyinfo + keynr; + DBUG_ASSERT(info->last_key.keyinfo == keyinfo); + + if (info->update & HA_STATE_DELETED) + return maria_rtree_find_first(info, &info->last_key, search_flag); + + if (!info->keyread_buff_used) + { + uchar *key= info->int_keypos; + + while (key < info->int_maxpos) + { + if (!maria_rtree_key_cmp(keyinfo->seg, + info->first_mbr_key, key, + info->last_rkey_length, search_flag)) + { + uchar *after_key= key + keyinfo->keylength; + MARIA_KEY tmp_key; + + /* + We don't need to set all MARIA_KEY elements here as + _ma_row_pos_from_key only uses a few of them. + */ + tmp_key.keyinfo= keyinfo; + tmp_key.data= key; + tmp_key.data_length= keyinfo->keylength - info->s->base.rec_reflength; + + info->cur_row.lastpos= _ma_row_pos_from_key(&tmp_key); + memcpy(info->last_key.data, key, info->last_key.data_length); + + if (after_key < info->int_maxpos) + info->int_keypos= after_key; + else + info->keyread_buff_used= 1; + return 0; + } + key+= keyinfo->keylength; + } + } + if ((root= info->s->state.key_root[keynr]) == HA_OFFSET_ERROR) + { + my_errno= HA_ERR_END_OF_FILE; + return -1; + } + + nod_cmp_flag= (((search_flag & (MBR_EQUAL | MBR_WITHIN)) ? + MBR_WITHIN : MBR_INTERSECT)); + return maria_rtree_find_req(info, keyinfo, search_flag, nod_cmp_flag, root, + 0); +} + + +/* + Get next key in r-tree recursively + + NOTES + Used in maria_rtree_get_first() and maria_rtree_get_next() + + RETURN + -1 Error + 0 Found + 1 Not found +*/ + +static int maria_rtree_get_req(MARIA_HA *info, MARIA_KEYDEF *keyinfo, + uint key_length, my_off_t page_pos, int level) +{ + MARIA_SHARE *share= info->s; + uchar *page_buf, *last, *k; + uint nod_flag, key_data_length; + int res; + uint *saved_key= (uint*) (info->maria_rtree_recursion_state) + level; + MARIA_PAGE page; + + if (!(page_buf= (uchar*) my_alloca((uint) keyinfo->block_length))) + return -1; + if (_ma_fetch_keypage(&page, info, keyinfo, page_pos, + PAGECACHE_LOCK_LEFT_UNLOCKED, + DFLT_INIT_HITS, page_buf, 0)) + goto err; + nod_flag= page.node; + + key_data_length= keyinfo->keylength - share->base.rec_reflength; + + if (info->maria_rtree_recursion_depth >= level) + { + k= page.buff + *saved_key; + if (!nod_flag) + { + /* Only leaf pages contain data references. */ + /* Need to check next key with data reference. */ + k= rt_PAGE_NEXT_KEY(share, k, key_data_length, nod_flag); + } + } + else + { + k= rt_PAGE_FIRST_KEY(share, page.buff, nod_flag); + } + last= rt_PAGE_END(&page); + + for (; k < last; k= rt_PAGE_NEXT_KEY(share, k, key_data_length, nod_flag)) + { + if (nod_flag) + { + /* this is an internal node in the tree */ + switch ((res= maria_rtree_get_req(info, keyinfo, key_length, + _ma_kpos(nod_flag, k), level + 1))) + { + case 0: /* found - exit from recursion */ + *saved_key= k - page.buff; + goto ok; + case 1: /* not found - continue searching */ + info->maria_rtree_recursion_depth= level; + break; + default: + case -1: /* error */ + goto err; + } + } + else + { + /* this is a leaf */ + uchar *after_key= rt_PAGE_NEXT_KEY(share, k, key_data_length, 0); + MARIA_KEY tmp_key; + + /* + We don't need to set all MARIA_KEY elements here as + _ma_row_pos_from_key() only uses a few of them. + */ + tmp_key.keyinfo= keyinfo; + tmp_key.data= k; + tmp_key.data_length= key_data_length; + + info->cur_row.lastpos= _ma_row_pos_from_key(&tmp_key); + info->last_key.data_length= key_data_length; + info->last_key.ref_length= share->base.rec_reflength; + + memcpy(info->last_key.data, k, + info->last_key.data_length + info->last_key.ref_length); + + info->maria_rtree_recursion_depth= level; + *saved_key= k - page.buff; + + if (after_key < last) + { + uchar *keyread_buff= info->keyread_buff; + info->last_rtree_keypos= saved_key; + memcpy(keyread_buff, page.buff, page.size); + info->int_maxpos= keyread_buff + page.size; + info->keyread_buff_used= 0; + } + else + { + info->keyread_buff_used= 1; + } + + res= 0; + goto ok; + } + } + info->cur_row.lastpos= HA_OFFSET_ERROR; + my_errno= HA_ERR_KEY_NOT_FOUND; + res= 1; + +ok: + my_afree(page_buf); + return res; + +err: + my_afree(page_buf); + info->cur_row.lastpos= HA_OFFSET_ERROR; + return -1; +} + + +/* + Get first key in r-tree + + RETURN + -1 Error + 0 Found + 1 Not found +*/ + +int maria_rtree_get_first(MARIA_HA *info, uint keynr, uint key_length) +{ + my_off_t root; + MARIA_KEYDEF *keyinfo= info->s->keyinfo + keynr; + + if ((root= info->s->state.key_root[keynr]) == HA_OFFSET_ERROR) + { + my_errno= HA_ERR_END_OF_FILE; + return -1; + } + + info->maria_rtree_recursion_depth= -1; + info->keyread_buff_used= 1; + + return maria_rtree_get_req(info, keyinfo, key_length, root, 0); +} + + +/* + Get next key in r-tree + + RETURN + -1 Error + 0 Found + 1 Not found +*/ + +int maria_rtree_get_next(MARIA_HA *info, uint keynr, uint key_length) +{ + my_off_t root; + MARIA_KEYDEF *keyinfo= info->s->keyinfo + keynr; + uchar *keyread_buff= info->keyread_buff; + + if (!info->keyread_buff_used) + { + uint key_data_length= keyinfo->keylength - info->s->base.rec_reflength; + /* rt_PAGE_NEXT_KEY(*info->last_rtree_keypos) */ + uchar *key= keyread_buff + *info->last_rtree_keypos + keyinfo->keylength; + /* rt_PAGE_NEXT_KEY(key) */ + uchar *after_key= key + keyinfo->keylength; + MARIA_KEY tmp_key; + + tmp_key.keyinfo= keyinfo; + tmp_key.data= key; + tmp_key.data_length= key_data_length; + tmp_key.ref_length= info->s->base.rec_reflength; + tmp_key.flag= 0; + + info->cur_row.lastpos= _ma_row_pos_from_key(&tmp_key); + _ma_copy_key(&info->last_key, &tmp_key); + + *info->last_rtree_keypos= (uint) (key - keyread_buff); + if (after_key >= info->int_maxpos) + { + info->keyread_buff_used= 1; + } + + return 0; + } + else + { + if ((root= info->s->state.key_root[keynr]) == HA_OFFSET_ERROR) + { + my_errno= HA_ERR_END_OF_FILE; + return -1; + } + + return maria_rtree_get_req(info, &keyinfo[keynr], key_length, root, 0); + } +} + + +/* + Choose non-leaf better key for insertion + + Returns a pointer inside the page_buf buffer. +*/ +#ifdef PICK_BY_PERIMETER +static const uchar *maria_rtree_pick_key(const MARIA_KEY *key, + const MARIA_PAGE *page) +{ + double increase; + double best_incr; + double perimeter; + double best_perimeter; + uchar *best_key= NULL; + const MARIA_HA *info= page->info; + + uchar *k= rt_PAGE_FIRST_KEY(info->s, page->buf, page->node); + uchar *last= rt_PAGE_END(info, page); + + LINT_INIT(best_perimeter); + LINT_INIT(best_key); + LINT_INIT(best_incr); + + for (; k < last; k= rt_PAGE_NEXT_KEY(k, key->data_length, nod_flag)) + { + if ((increase= maria_rtree_perimeter_increase(keyinfo->seg, k, key, + &perimeter)) == -1) + return NULL; + if ((increase < best_incr)|| + (increase == best_incr && perimeter < best_perimeter)) + { + best_key= k; + best_perimeter= perimeter; + best_incr= increase; + } + } + return best_key; +} + +#endif /*PICK_BY_PERIMETER*/ + +#ifdef PICK_BY_AREA +static const uchar *maria_rtree_pick_key(const MARIA_KEY *key, + const MARIA_PAGE *page) +{ + const MARIA_HA *info= page->info; + MARIA_SHARE *share= info->s; + double increase; + double best_incr= DBL_MAX; + double area; + double best_area; + const uchar *best_key= NULL; + const uchar *k= rt_PAGE_FIRST_KEY(share, page->buff, page->node); + const uchar *last= rt_PAGE_END(page); + + LINT_INIT(best_area); + + for (; k < last; + k= rt_PAGE_NEXT_KEY(share, k, key->data_length, page->node)) + { + /* The following is safe as -1.0 is an exact number */ + if ((increase= maria_rtree_area_increase(key->keyinfo->seg, k, key->data, + key->data_length + + key->ref_length, + &area)) == -1.0) + return NULL; + /* The following should be safe, even if we compare doubles */ + if (!best_key || increase < best_incr || + ((increase == best_incr) && (area < best_area))) + { + best_key= k; + best_area= area; + best_incr= increase; + } + } + return best_key; +} + +#endif /*PICK_BY_AREA*/ + +/* + Go down and insert key into tree + + RETURN + -1 Error + 0 Child was not split + 1 Child was split +*/ + +static int maria_rtree_insert_req(MARIA_HA *info, MARIA_KEY *key, + my_off_t page_pos, my_off_t *new_page, + int ins_level, int level) +{ + uint nod_flag; + uint key_length= key->data_length; + int res; + uchar *page_buf, *k; + MARIA_SHARE *share= info->s; + MARIA_KEYDEF *keyinfo= key->keyinfo; + MARIA_PAGE page; + DBUG_ENTER("maria_rtree_insert_req"); + + if (!(page_buf= (uchar*) my_alloca((uint) keyinfo->block_length + + MARIA_MAX_KEY_BUFF))) + { + my_errno= HA_ERR_OUT_OF_MEM; + DBUG_RETURN(-1); /* purecov: inspected */ + } + if (_ma_fetch_keypage(&page, info, keyinfo, page_pos, PAGECACHE_LOCK_WRITE, + DFLT_INIT_HITS, page_buf, 0)) + goto err; + nod_flag= page.node; + DBUG_PRINT("rtree", ("page: %lu level: %d ins_level: %d nod_flag: %u", + (ulong) page.pos, level, ins_level, nod_flag)); + + if ((ins_level == -1 && nod_flag) || /* key: go down to leaf */ + (ins_level > -1 && ins_level > level)) /* branch: go down to ins_level */ + { + if (!(k= (uchar *)maria_rtree_pick_key(key, &page))) + goto err; + /* k is now a pointer inside the page_buf buffer */ + switch ((res= maria_rtree_insert_req(info, key, + _ma_kpos(nod_flag, k), new_page, + ins_level, level + 1))) + { + case 0: /* child was not split, most common case */ + { + maria_rtree_combine_rect(keyinfo->seg, k, key->data, k, key_length); + if (share->now_transactional && + _ma_log_change(&page, k, key_length)) + goto err; + page_mark_changed(info, &page); + if (_ma_write_keypage(&page, PAGECACHE_LOCK_LEFT_WRITELOCKED, + DFLT_INIT_HITS)) + goto err; + goto ok; + } + case 1: /* child was split */ + { + /* Set new_key to point to a free buffer area */ + uchar *new_key_buff= page_buf + keyinfo->block_length + nod_flag; + MARIA_KEY new_key; + MARIA_KEY k_key; + + DBUG_ASSERT(nod_flag); + k_key.keyinfo= new_key.keyinfo= keyinfo; + new_key.data= new_key_buff; + k_key.data= k; + k_key.data_length= new_key.data_length= key->data_length; + k_key.ref_length= new_key.ref_length= key->ref_length; + k_key.flag= new_key.flag= 0; /* Safety */ + + /* set proper MBR for key */ + if (maria_rtree_set_key_mbr(info, &k_key, _ma_kpos(nod_flag, k))) + goto err; + if (share->now_transactional && + _ma_log_change(&page, k, key_length)) + goto err; + /* add new key for new page */ + _ma_kpointer(info, new_key_buff - nod_flag, *new_page); + if (maria_rtree_set_key_mbr(info, &new_key, *new_page)) + goto err; + res= maria_rtree_add_key(&new_key, &page, new_page); + page_mark_changed(info, &page); + if (_ma_write_keypage(&page, PAGECACHE_LOCK_LEFT_WRITELOCKED, + DFLT_INIT_HITS)) + goto err; + goto ok; + } + default: + case -1: /* error */ + { + goto err; + } + } + } + else + { + res= maria_rtree_add_key(key, &page, new_page); + page_mark_changed(info, &page); + if (_ma_write_keypage(&page, PAGECACHE_LOCK_LEFT_WRITELOCKED, + DFLT_INIT_HITS)) + goto err; + } + +ok: + my_afree(page_buf); + DBUG_RETURN(res); + +err: + res= -1; /* purecov: inspected */ + goto ok; /* purecov: inspected */ +} + + +/** + Insert key into the tree + + @param info table + @param key KEY to insert + @param ins_level at which level key insertion should start + @param root put new key_root there + + @return Operation result + @retval -1 Error + @retval 0 Root was not split + @retval 1 Root was split +*/ + +int maria_rtree_insert_level(MARIA_HA *info, MARIA_KEY *key, int ins_level, + my_off_t *root) +{ + my_off_t old_root; + MARIA_SHARE *share= info->s; + MARIA_KEYDEF *keyinfo= key->keyinfo; + int res; + my_off_t new_page; + enum pagecache_page_lock write_lock; + DBUG_ENTER("maria_rtree_insert_level"); + + if ((old_root= share->state.key_root[keyinfo->key_nr]) == HA_OFFSET_ERROR) + { + MARIA_PINNED_PAGE tmp_page_link, *page_link; + MARIA_PAGE page; + + page_link= &tmp_page_link; + if ((old_root= _ma_new(info, DFLT_INIT_HITS, &page_link)) == + HA_OFFSET_ERROR) + DBUG_RETURN(-1); + write_lock= page_link->write_lock; + info->keyread_buff_used= 1; + bzero(info->buff, share->block_size); + _ma_store_keynr(share, info->buff, keyinfo->key_nr); + _ma_store_page_used(share, info->buff, share->keypage_header); + _ma_page_setup(&page, info, keyinfo, old_root, info->buff); + + if (share->now_transactional && _ma_log_new(&page, 1)) + DBUG_RETURN(1); + + res= maria_rtree_add_key(key, &page, NULL); + if (_ma_write_keypage(&page, write_lock, DFLT_INIT_HITS)) + DBUG_RETURN(1); + *root= old_root; + DBUG_RETURN(res); + } + + switch ((res= maria_rtree_insert_req(info, key, old_root, &new_page, + ins_level, 0))) + { + case 0: /* root was not split */ + { + break; + } + case 1: /* root was split, grow a new root; very rare */ + { + uchar *new_root_buf, *new_key_buff; + my_off_t new_root; + uint nod_flag= share->base.key_reflength; + MARIA_PINNED_PAGE tmp_page_link, *page_link; + MARIA_KEY new_key; + MARIA_PAGE page; + page_link= &tmp_page_link; + + DBUG_PRINT("rtree", ("root was split, grow a new root")); + if (!(new_root_buf= (uchar*) my_alloca((uint) keyinfo->block_length + + MARIA_MAX_KEY_BUFF))) + { + my_errno= HA_ERR_OUT_OF_MEM; + DBUG_RETURN(-1); /* purecov: inspected */ + } + + bzero(new_root_buf, share->block_size); + _ma_store_keypage_flag(share, new_root_buf, KEYPAGE_FLAG_ISNOD); + _ma_store_keynr(share, new_root_buf, keyinfo->key_nr); + _ma_store_page_used(share, new_root_buf, share->keypage_header); + if ((new_root= _ma_new(info, DFLT_INIT_HITS, &page_link)) == + HA_OFFSET_ERROR) + goto err; + write_lock= page_link->write_lock; + + _ma_page_setup(&page, info, keyinfo, new_root, new_root_buf); + + if (share->now_transactional && _ma_log_new(&page, 1)) + goto err; + + /* Point to some free space */ + new_key_buff= new_root_buf + keyinfo->block_length + nod_flag; + new_key.keyinfo= keyinfo; + new_key.data= new_key_buff; + new_key.data_length= key->data_length; + new_key.ref_length= key->ref_length; + new_key.flag= 0; + + _ma_kpointer(info, new_key_buff - nod_flag, old_root); + if (maria_rtree_set_key_mbr(info, &new_key, old_root)) + goto err; + if (maria_rtree_add_key(&new_key, &page, NULL) + == -1) + goto err; + _ma_kpointer(info, new_key_buff - nod_flag, new_page); + if (maria_rtree_set_key_mbr(info, &new_key, new_page)) + goto err; + if (maria_rtree_add_key(&new_key, &page, NULL) + == -1) + goto err; + if (_ma_write_keypage(&page, write_lock, DFLT_INIT_HITS)) + goto err; + *root= new_root; + DBUG_PRINT("rtree", ("new root page: %lu level: %d nod_flag: %u", + (ulong) new_root, 0, page.node)); + + my_afree(new_root_buf); + break; +err: + my_afree(new_root_buf); + DBUG_RETURN(-1); /* purecov: inspected */ + } + default: + case -1: /* error */ + { + DBUG_ASSERT(0); + break; + } + } + DBUG_RETURN(res); +} + + +/* + Insert key into the tree - interface function + + RETURN + 1 Error + 0 OK +*/ + +my_bool maria_rtree_insert(MARIA_HA *info, MARIA_KEY *key) +{ + int res; + MARIA_SHARE *share= info->s; + my_off_t *root, new_root; + LSN lsn= LSN_IMPOSSIBLE; + DBUG_ENTER("maria_rtree_insert"); + + if (!key) + DBUG_RETURN(1); /* _ma_sp_make_key failed */ + + root= &share->state.key_root[key->keyinfo->key_nr]; + new_root= *root; + + if ((res= (maria_rtree_insert_level(info, key, -1, &new_root) == -1))) + goto err; + if (share->now_transactional) + res= _ma_write_undo_key_insert(info, key, root, new_root, &lsn); + else + { + *root= new_root; + _ma_fast_unlock_key_del(info); + } + _ma_unpin_all_pages_and_finalize_row(info, lsn); +err: + DBUG_RETURN(res != 0); +} + + +/* + Fill reinsert page buffer + + RETURN + 1 Error + 0 OK +*/ + +static my_bool maria_rtree_fill_reinsert_list(stPageList *ReinsertList, + my_off_t page, int level) +{ + DBUG_ENTER("maria_rtree_fill_reinsert_list"); + DBUG_PRINT("rtree", ("page: %lu level: %d", (ulong) page, level)); + if (ReinsertList->n_pages == ReinsertList->m_pages) + { + ReinsertList->m_pages += REINSERT_BUFFER_INC; + if (!(ReinsertList->pages= (stPageLevel*)my_realloc((uchar*)ReinsertList->pages, + ReinsertList->m_pages * sizeof(stPageLevel), MYF(MY_ALLOW_ZERO_PTR)))) + goto err; + } + /* save page to ReinsertList */ + ReinsertList->pages[ReinsertList->n_pages].offs= page; + ReinsertList->pages[ReinsertList->n_pages].level= level; + ReinsertList->n_pages++; + DBUG_RETURN(0); + +err: + DBUG_RETURN(1); /* purecov: inspected */ +} + + +/* + Go down and delete key from the tree + + RETURN + -1 Error + 0 Deleted + 1 Not found + 2 Empty leaf +*/ + +static int maria_rtree_delete_req(MARIA_HA *info, const MARIA_KEY *key, + my_off_t page_pos, uint *page_size, + stPageList *ReinsertList, int level) +{ + ulong i; + uint nod_flag; + int res; + uchar *page_buf, *last, *k; + MARIA_SHARE *share= info->s; + MARIA_KEYDEF *keyinfo= key->keyinfo; + MARIA_PAGE page; + DBUG_ENTER("maria_rtree_delete_req"); + + if (!(page_buf= (uchar*) my_alloca((uint) keyinfo->block_length))) + { + my_errno= HA_ERR_OUT_OF_MEM; + DBUG_RETURN(-1); /* purecov: inspected */ + } + if (_ma_fetch_keypage(&page, info, keyinfo, page_pos, PAGECACHE_LOCK_WRITE, + DFLT_INIT_HITS, page_buf, 0)) + goto err; + nod_flag= page.node; + DBUG_PRINT("rtree", ("page: %lu level: %d nod_flag: %u", + (ulong) page_pos, level, nod_flag)); + + k= rt_PAGE_FIRST_KEY(share, page_buf, nod_flag); + last= rt_PAGE_END(&page); + + for (i= 0; + k < last; + k= rt_PAGE_NEXT_KEY(share, k, key->data_length, nod_flag), i++) + { + if (nod_flag) + { + /* not leaf */ + if (!maria_rtree_key_cmp(keyinfo->seg, key->data, k, key->data_length, + MBR_WITHIN)) + { + switch ((res= maria_rtree_delete_req(info, key, + _ma_kpos(nod_flag, k), + page_size, ReinsertList, + level + 1))) + { + case 0: /* deleted */ + { + /* test page filling */ + if (*page_size + key->data_length >= + rt_PAGE_MIN_SIZE(keyinfo->block_length)) + { + /* OK */ + /* Calculate a new key value (MBR) for the shrinked block. */ + MARIA_KEY tmp_key; + tmp_key.keyinfo= keyinfo; + tmp_key.data= k; + tmp_key.data_length= key->data_length; + tmp_key.ref_length= key->ref_length; + tmp_key.flag= 0; /* Safety */ + + if (maria_rtree_set_key_mbr(info, &tmp_key, + _ma_kpos(nod_flag, k))) + goto err; + if (share->now_transactional && + _ma_log_change(&page, k, key->data_length)) + goto err; + page_mark_changed(info, &page) + if (_ma_write_keypage(&page, + PAGECACHE_LOCK_LEFT_WRITELOCKED, + DFLT_INIT_HITS)) + goto err; + } + else + { + /* + Too small: delete key & add it descendant to reinsert list. + Store position and level of the block so that it can be + accessed later for inserting the remaining keys. + */ + DBUG_PRINT("rtree", ("too small. move block to reinsert list")); + if (maria_rtree_fill_reinsert_list(ReinsertList, + _ma_kpos(nod_flag, k), + level + 1)) + goto err; + /* + Delete the key that references the block. This makes the + block disappear from the index. Hence we need to insert + its remaining keys later. Note: if the block is a branch + block, we do not only remove this block, but the whole + subtree. So we need to re-insert its keys on the same + level later to reintegrate the subtrees. + */ + if (maria_rtree_delete_key(&page, k, key->data_length)) + goto err; + page_mark_changed(info, &page); + if (_ma_write_keypage(&page, PAGECACHE_LOCK_LEFT_WRITELOCKED, + DFLT_INIT_HITS)) + goto err; + *page_size= page.size; + } + + goto ok; + } + case 1: /* not found - continue searching */ + { + break; + } + case 2: /* vacuous case: last key in the leaf */ + { + if (maria_rtree_delete_key(&page, k, key->data_length)) + goto err; + page_mark_changed(info, &page); + if (_ma_write_keypage(&page, PAGECACHE_LOCK_LEFT_WRITELOCKED, + DFLT_INIT_HITS)) + goto err; + *page_size= page.size; + res= 0; + goto ok; + } + default: /* error */ + case -1: + { + goto err; + } + } + } + } + else + { + /* leaf */ + if (!maria_rtree_key_cmp(keyinfo->seg, key->data, k, key->data_length, + MBR_EQUAL | MBR_DATA)) + { + page_mark_changed(info, &page); + if (maria_rtree_delete_key(&page, k, key->data_length)) + goto err; + *page_size= page.size; + if (*page_size == info->s->keypage_header) + { + /* last key in the leaf */ + res= 2; + if (_ma_dispose(info, page.pos, 0)) + goto err; + } + else + { + res= 0; + if (_ma_write_keypage(&page, PAGECACHE_LOCK_LEFT_WRITELOCKED, + DFLT_INIT_HITS)) + goto err; + } + goto ok; + } + } + } + res= 1; + +ok: + my_afree(page_buf); + DBUG_RETURN(res); + +err: + my_afree(page_buf); + DBUG_RETURN(-1); /* purecov: inspected */ +} + + +/* + Delete key - interface function + + RETURN + 1 Error + 0 Deleted +*/ + +my_bool maria_rtree_delete(MARIA_HA *info, MARIA_KEY *key) +{ + MARIA_SHARE *share= info->s; + my_off_t new_root= share->state.key_root[key->keyinfo->key_nr]; + int res; + LSN lsn= LSN_IMPOSSIBLE; + DBUG_ENTER("maria_rtree_delete"); + + if ((res= maria_rtree_real_delete(info, key, &new_root))) + goto err; + + if (share->now_transactional) + res= _ma_write_undo_key_delete(info, key, new_root, &lsn); + else + share->state.key_root[key->keyinfo->key_nr]= new_root; + +err: + _ma_fast_unlock_key_del(info); + _ma_unpin_all_pages_and_finalize_row(info, lsn); + DBUG_RETURN(res != 0); +} + + +my_bool maria_rtree_real_delete(MARIA_HA *info, MARIA_KEY *key, + my_off_t *root) +{ + uint page_size; + stPageList ReinsertList; + my_off_t old_root; + MARIA_SHARE *share= info->s; + MARIA_KEYDEF *keyinfo= key->keyinfo; + uint key_data_length= key->data_length; + DBUG_ENTER("maria_rtree_real_delete"); + + if ((old_root= share->state.key_root[keyinfo->key_nr]) == + HA_OFFSET_ERROR) + { + my_errno= HA_ERR_END_OF_FILE; + DBUG_RETURN(1); /* purecov: inspected */ + } + DBUG_PRINT("rtree", ("starting deletion at root page: %lu", + (ulong) old_root)); + + ReinsertList.pages= NULL; + ReinsertList.n_pages= 0; + ReinsertList.m_pages= 0; + + switch (maria_rtree_delete_req(info, key, old_root, &page_size, + &ReinsertList, 0)) { + case 2: /* empty */ + { + *root= HA_OFFSET_ERROR; + break; + } + case 0: /* deleted */ + { + uint nod_flag; + ulong i; + MARIA_KEY tmp_key; + tmp_key.keyinfo= key->keyinfo; + tmp_key.data_length= key->data_length; + tmp_key.ref_length= key->ref_length; + tmp_key.flag= 0; /* Safety */ + uchar *page_buf; + MARIA_PAGE page; + + if (ReinsertList.n_pages) + { + if (!(page_buf= (uchar*) my_alloca((uint) keyinfo->block_length))) + { + my_errno= HA_ERR_OUT_OF_MEM; + goto err; + } + + for (i= 0; i < ReinsertList.n_pages; ++i) + { + uchar *k, *last; + if (_ma_fetch_keypage(&page, info, keyinfo, ReinsertList.pages[i].offs, + PAGECACHE_LOCK_WRITE, + DFLT_INIT_HITS, page_buf, 0)) + goto err; + nod_flag= page.node; + DBUG_PRINT("rtree", ("reinserting keys from " + "page: %lu level: %d nod_flag: %u", + (ulong) ReinsertList.pages[i].offs, + ReinsertList.pages[i].level, nod_flag)); + + k= rt_PAGE_FIRST_KEY(share, page.buff, nod_flag); + last= rt_PAGE_END(&page); + for (; k < last; k= rt_PAGE_NEXT_KEY(share, k, key_data_length, + nod_flag)) + { + int res; + tmp_key.data= k; + if ((res= maria_rtree_insert_level(info, &tmp_key, + ReinsertList.pages[i].level, + root)) == -1) + { + my_afree(page_buf); + goto err; + } + if (res) + { + uint j; + DBUG_PRINT("rtree", ("root has been split, adjust levels")); + for (j= i; j < ReinsertList.n_pages; j++) + { + ReinsertList.pages[j].level++; + DBUG_PRINT("rtree", ("keys from page: %lu now level: %d", + (ulong) ReinsertList.pages[i].offs, + ReinsertList.pages[i].level)); + } + } + } + page_mark_changed(info, &page); + if (_ma_dispose(info, page.pos, 0)) + { + my_afree(page_buf); + goto err; + } + } + my_afree(page_buf); + my_free(ReinsertList.pages, MYF(0)); + } + + /* check for redundant root (not leaf, 1 child) and eliminate */ + if ((old_root= *root) == HA_OFFSET_ERROR) + goto err; + if (_ma_fetch_keypage(&page, info, keyinfo, old_root, + PAGECACHE_LOCK_WRITE, + DFLT_INIT_HITS, info->buff, 0)) + goto err; + nod_flag= page.node; + if (nod_flag && (page.size == share->keypage_header + key_data_length + + nod_flag)) + { + *root= _ma_kpos(nod_flag, + rt_PAGE_FIRST_KEY(share, info->buff, nod_flag)); + page_mark_changed(info, &page); + if (_ma_dispose(info, page.pos, 0)) + goto err; + } + info->update= HA_STATE_DELETED; + break; + } + case 1: /* not found */ + { + my_errno= HA_ERR_KEY_NOT_FOUND; + goto err; + } + case -1: /* error */ + default: + goto err; /* purecov: inspected */ + } + DBUG_RETURN(0); + +err: + DBUG_RETURN(1); +} + + +/* + Estimate number of suitable keys in the tree + + RETURN + estimated value +*/ + +ha_rows maria_rtree_estimate(MARIA_HA *info, MARIA_KEY *key, uint32 flag) +{ + my_off_t root; + uint i= 0; + uint nod_flag, key_data_length; + uchar *page_buf, *k, *last; + double area= 0; + ha_rows res= 0; + MARIA_SHARE *share= info->s; + MARIA_KEYDEF *keyinfo= key->keyinfo; + MARIA_PAGE page; + + if (flag & MBR_DISJOINT) + return info->state->records; + + if ((root= share->state.key_root[key->keyinfo->key_nr]) == HA_OFFSET_ERROR) + return HA_POS_ERROR; + if (!(page_buf= (uchar*) my_alloca((uint) keyinfo->block_length))) + return HA_POS_ERROR; + if (_ma_fetch_keypage(&page, info, keyinfo, root, + PAGECACHE_LOCK_LEFT_UNLOCKED, DFLT_INIT_HITS, page_buf, + 0)) + goto err; + nod_flag= page.node; + + key_data_length= key->data_length; + + k= rt_PAGE_FIRST_KEY(share, page.buff, nod_flag); + last= rt_PAGE_END(&page); + + for (; k < last; + k= rt_PAGE_NEXT_KEY(share, k, key_data_length, nod_flag), i++) + { + if (nod_flag) + { + double k_area= maria_rtree_rect_volume(keyinfo->seg, k, key_data_length); + + /* The following should be safe, even if we compare doubles */ + if (k_area == 0) + { + if (flag & (MBR_CONTAIN | MBR_INTERSECT)) + { + area+= 1; + } + else if (flag & (MBR_WITHIN | MBR_EQUAL)) + { + if (!maria_rtree_key_cmp(keyinfo->seg, key->data, k, key_data_length, + MBR_WITHIN)) + area+= 1; + } + else + goto err; + } + else + { + if (flag & (MBR_CONTAIN | MBR_INTERSECT)) + { + area+= maria_rtree_overlapping_area(keyinfo->seg, key->data, k, + key_data_length) / k_area; + } + else if (flag & (MBR_WITHIN | MBR_EQUAL)) + { + if (!maria_rtree_key_cmp(keyinfo->seg, key->data, k, key_data_length, + MBR_WITHIN)) + area+= (maria_rtree_rect_volume(keyinfo->seg, key->data, + key_data_length) / k_area); + } + else + goto err; + } + } + else + { + if (!maria_rtree_key_cmp(keyinfo->seg, key->data, k, key_data_length, + flag)) + ++res; + } + } + if (nod_flag) + { + if (i) + res= (ha_rows) (area / i * info->state->records); + else + res= HA_POS_ERROR; + } + + my_afree(page_buf); + return res; + +err: + my_afree(page_buf); + return HA_POS_ERROR; +} + +#endif /*HAVE_RTREE_KEYS*/ diff --git a/storage/maria/ma_rt_index.h b/storage/maria/ma_rt_index.h new file mode 100644 index 00000000000..dacaa4389b7 --- /dev/null +++ b/storage/maria/ma_rt_index.h @@ -0,0 +1,46 @@ +/* Copyright (C) 2006 MySQL AB & Ramil Kalimullin & MySQL Finland AB + & TCX DataKonsult 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 _rt_index_h +#define _rt_index_h + +#ifdef HAVE_RTREE_KEYS + +#define rt_PAGE_FIRST_KEY(share, page, nod_flag) (page + share->keypage_header + nod_flag) +#define rt_PAGE_NEXT_KEY(share, key, key_length, nod_flag) (key + key_length +\ + (nod_flag ? nod_flag : share->base.rec_reflength)) +#define rt_PAGE_END(page) ((page)->buff + (page)->size) + +#define rt_PAGE_MIN_SIZE(block_length) ((uint)(block_length - KEYPAGE_CHECKSUM_SIZE) / 3) + +my_bool maria_rtree_insert(MARIA_HA *info, MARIA_KEY *key); +my_bool maria_rtree_delete(MARIA_HA *info, MARIA_KEY *key); +int maria_rtree_insert_level(MARIA_HA *info, MARIA_KEY *key, + int ins_level, my_off_t *root); +my_bool maria_rtree_real_delete(MARIA_HA *info, MARIA_KEY *key, + my_off_t *root); +int maria_rtree_find_first(MARIA_HA *info, MARIA_KEY *key, uint search_flag); +int maria_rtree_find_next(MARIA_HA *info, uint keynr, uint32 search_flag); + +int maria_rtree_get_first(MARIA_HA *info, uint keynr, uint key_length); +int maria_rtree_get_next(MARIA_HA *info, uint keynr, uint key_length); + +ha_rows maria_rtree_estimate(MARIA_HA *info, MARIA_KEY *key, uint32 flag); + +int maria_rtree_split_page(const MARIA_KEY *key, MARIA_PAGE *page, + my_off_t *new_page_offs); +#endif /*HAVE_RTREE_KEYS*/ +#endif /* _rt_index_h */ diff --git a/storage/maria/ma_rt_key.c b/storage/maria/ma_rt_key.c new file mode 100644 index 00000000000..bc2c8f71f5d --- /dev/null +++ b/storage/maria/ma_rt_key.c @@ -0,0 +1,118 @@ +/* Copyright (C) 2006 MySQL AB & Ramil Kalimullin + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You 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 "maria_def.h" +#include "trnman.h" +#include "ma_key_recover.h" + +#ifdef HAVE_RTREE_KEYS +#include "ma_rt_index.h" +#include "ma_rt_key.h" +#include "ma_rt_mbr.h" + +/* + Add key to the page + + RESULT VALUES + -1 Error + 0 Not split + 1 Split +*/ + +int maria_rtree_add_key(const MARIA_KEY *key, MARIA_PAGE *page, + my_off_t *new_page) +{ + MARIA_HA *info= page->info; + MARIA_SHARE *share= info->s; + uint page_size= page->size; + uint nod_flag= page->node; + uchar *key_pos= rt_PAGE_END(page); + uint tot_key_length= key->data_length + key->ref_length + nod_flag; + DBUG_ENTER("maria_rtree_add_key"); + + if (page_size + tot_key_length <= + (uint)(key->keyinfo->block_length - KEYPAGE_CHECKSUM_SIZE)) + { + /* split won't be necessary */ + if (nod_flag) + { + DBUG_ASSERT(_ma_kpos(nod_flag, key->data) < + info->state->key_file_length); + /* We don't store reference to row on nod pages for rtree index */ + tot_key_length-= key->ref_length; + } + /* save key */ + memcpy(key_pos, key->data - nod_flag, tot_key_length); + page->size+= tot_key_length; + page_store_size(share, page); + if (share->now_transactional && + _ma_log_add(page, key_pos - page->buff, + key_pos, tot_key_length, tot_key_length, 0)) + DBUG_RETURN(-1); + DBUG_RETURN(0); + } + DBUG_RETURN(maria_rtree_split_page(key, page, new_page) ? -1 : 1); +} + + +/* + Delete key from the page + + Notes + key_length is only the data part of the key +*/ + +int maria_rtree_delete_key(MARIA_PAGE *page, uchar *key, uint key_length) +{ + MARIA_HA *info= page->info; + MARIA_SHARE *share= info->s; + uint key_length_with_nod_flag; + uchar *key_start; + + key_start= key - page->node; + if (!page->node) + key_length+= share->base.rec_reflength; + + memmove(key_start, key + key_length, page->size - key_length - + (key - page->buff)); + key_length_with_nod_flag= key_length + page->node; + page->size-= key_length_with_nod_flag; + page_store_size(share, page); + if (share->now_transactional && + _ma_log_delete(page, key_start, 0, key_length_with_nod_flag)) + return -1; + return 0; +} + + +/* + Calculate and store key MBR into *key. +*/ + +int maria_rtree_set_key_mbr(MARIA_HA *info, MARIA_KEY *key, + my_off_t child_page) +{ + MARIA_PAGE page; + DBUG_ENTER("maria_rtree_set_key_mbr"); + if (_ma_fetch_keypage(&page, info, key->keyinfo, child_page, + PAGECACHE_LOCK_LEFT_UNLOCKED, + DFLT_INIT_HITS, info->buff, 0)) + DBUG_RETURN(-1); + + DBUG_RETURN(maria_rtree_page_mbr(key->keyinfo->seg, + &page, key->data, key->data_length)); +} + +#endif /*HAVE_RTREE_KEYS*/ diff --git a/storage/maria/ma_rt_key.h b/storage/maria/ma_rt_key.h new file mode 100644 index 00000000000..948809f3d38 --- /dev/null +++ b/storage/maria/ma_rt_key.h @@ -0,0 +1,31 @@ +/* Copyright (C) 2006 MySQL AB & Ramil Kalimullin & MySQL Finland AB + & TCX DataKonsult 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 */ + +/* Written by Ramil Kalimullin, who has a shared copyright to this code */ + +#ifndef _rt_key_h +#define _rt_key_h + +#ifdef HAVE_RTREE_KEYS + +int maria_rtree_add_key(const MARIA_KEY *key, MARIA_PAGE *page, + my_off_t *new_page); +int maria_rtree_delete_key(MARIA_PAGE *page, uchar *key, uint key_length); +int maria_rtree_set_key_mbr(MARIA_HA *info, MARIA_KEY *key, + my_off_t child_page); + +#endif /*HAVE_RTREE_KEYS*/ +#endif /* _rt_key_h */ diff --git a/storage/maria/ma_rt_mbr.c b/storage/maria/ma_rt_mbr.c new file mode 100644 index 00000000000..b3e2b0ceab8 --- /dev/null +++ b/storage/maria/ma_rt_mbr.c @@ -0,0 +1,818 @@ +/* Copyright (C) 2006 MySQL AB & Ramil Kalimullin & MySQL Finland AB + & TCX DataKonsult 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 "maria_def.h" + +#ifdef HAVE_RTREE_KEYS + +#include "ma_rt_index.h" +#include "ma_rt_mbr.h" + +#define INTERSECT_CMP(amin, amax, bmin, bmax) ((amin > bmax) || (bmin > amax)) +#define CONTAIN_CMP(amin, amax, bmin, bmax) ((bmin > amin) || (bmax < amax)) +#define WITHIN_CMP(amin, amax, bmin, bmax) ((amin > bmin) || (amax < bmax)) +#define DISJOINT_CMP(amin, amax, bmin, bmax) ((amin <= bmax) && (bmin <= amax)) +#define EQUAL_CMP(amin, amax, bmin, bmax) ((amin != bmin) || (amax != bmax)) + +#define FCMP(A, B) ((int)(A) - (int)(B)) +#define p_inc(A, B, X) {A += X; B += X;} + +#define RT_CMP(nextflag) \ + if (nextflag & MBR_INTERSECT) \ + { \ + if (INTERSECT_CMP(amin, amax, bmin, bmax)) \ + return 1; \ + } \ + else if (nextflag & MBR_CONTAIN) \ + { \ + if (CONTAIN_CMP(amin, amax, bmin, bmax)) \ + return 1; \ + } \ + else if (nextflag & MBR_WITHIN) \ + { \ + if (WITHIN_CMP(amin, amax, bmin, bmax)) \ + return 1; \ + } \ + else if (nextflag & MBR_EQUAL) \ + { \ + if (EQUAL_CMP(amin, amax, bmin, bmax)) \ + return 1; \ + } \ + else if (nextflag & MBR_DISJOINT) \ + { \ + if (DISJOINT_CMP(amin, amax, bmin, bmax)) \ + return 1; \ + }\ + else /* if unknown comparison operator */ \ + { \ + DBUG_ASSERT(0); \ + } + +#define RT_CMP_KORR(type, korr_func, len, nextflag) \ +{ \ + type amin, amax, bmin, bmax; \ + amin= korr_func(a); \ + bmin= korr_func(b); \ + amax= korr_func(a+len); \ + bmax= korr_func(b+len); \ + RT_CMP(nextflag); \ +} + +#define RT_CMP_GET(type, get_func, len, nextflag) \ +{ \ + type amin, amax, bmin, bmax; \ + get_func(amin, a); \ + get_func(bmin, b); \ + get_func(amax, a+len); \ + get_func(bmax, b+len); \ + RT_CMP(nextflag); \ +} + +/* + Compares two keys a and b depending on nextflag + nextflag can contain these flags: + MBR_INTERSECT(a,b) a overlaps b + MBR_CONTAIN(a,b) a contains b + MBR_DISJOINT(a,b) a disjoint b + MBR_WITHIN(a,b) a within b + MBR_EQUAL(a,b) All coordinates of MBRs are equal + MBR_DATA(a,b) Data reference is the same + Returns 0 on success. +*/ + +int maria_rtree_key_cmp(HA_KEYSEG *keyseg, const uchar *b, const uchar *a, + uint key_length, uint32 nextflag) +{ + for (; (int) key_length > 0; keyseg += 2 ) + { + uint32 keyseg_length; + switch ((enum ha_base_keytype) keyseg->type) { + case HA_KEYTYPE_INT8: + RT_CMP_KORR(int8, mi_sint1korr, 1, nextflag); + break; + case HA_KEYTYPE_BINARY: + RT_CMP_KORR(uint8, mi_uint1korr, 1, nextflag); + break; + case HA_KEYTYPE_SHORT_INT: + RT_CMP_KORR(int16, mi_sint2korr, 2, nextflag); + break; + case HA_KEYTYPE_USHORT_INT: + RT_CMP_KORR(uint16, mi_uint2korr, 2, nextflag); + break; + case HA_KEYTYPE_INT24: + RT_CMP_KORR(int32, mi_sint3korr, 3, nextflag); + break; + case HA_KEYTYPE_UINT24: + RT_CMP_KORR(uint32, mi_uint3korr, 3, nextflag); + break; + case HA_KEYTYPE_LONG_INT: + RT_CMP_KORR(int32, mi_sint4korr, 4, nextflag); + break; + case HA_KEYTYPE_ULONG_INT: + RT_CMP_KORR(uint32, mi_uint4korr, 4, nextflag); + break; +#ifdef HAVE_LONG_LONG + case HA_KEYTYPE_LONGLONG: + RT_CMP_KORR(longlong, mi_sint8korr, 8, nextflag) + break; + case HA_KEYTYPE_ULONGLONG: + RT_CMP_KORR(ulonglong, mi_uint8korr, 8, nextflag) + break; +#endif + case HA_KEYTYPE_FLOAT: + /* The following should be safe, even if we compare doubles */ + RT_CMP_GET(float, mi_float4get, 4, nextflag); + break; + case HA_KEYTYPE_DOUBLE: + RT_CMP_GET(double, mi_float8get, 8, nextflag); + break; + case HA_KEYTYPE_END: + goto end; + default: + return 1; + } + keyseg_length= keyseg->length * 2; + key_length-= keyseg_length; + a+= keyseg_length; + b+= keyseg_length; + } + +end: + if (nextflag & MBR_DATA) + { + const uchar *end= a + keyseg->length; + do + { + if (*a++ != *b++) + return FCMP(a[-1], b[-1]); + } while (a != end); + } + return 0; +} + +#define RT_VOL_KORR(type, korr_func, len, cast) \ +{ \ + type amin, amax; \ + amin= korr_func(a); \ + amax= korr_func(a+len); \ + res *= (cast(amax) - cast(amin)); \ +} + +#define RT_VOL_GET(type, get_func, len, cast) \ +{ \ + type amin, amax; \ + get_func(amin, a); \ + get_func(amax, a+len); \ + res *= (cast(amax) - cast(amin)); \ +} + +/* + Calculates rectangle volume +*/ +double maria_rtree_rect_volume(HA_KEYSEG *keyseg, uchar *a, uint key_length) +{ + double res= 1; + for (; (int)key_length > 0; keyseg += 2) + { + uint32 keyseg_length; + switch ((enum ha_base_keytype) keyseg->type) { + case HA_KEYTYPE_INT8: + RT_VOL_KORR(int8, mi_sint1korr, 1, (double)); + break; + case HA_KEYTYPE_BINARY: + RT_VOL_KORR(uint8, mi_uint1korr, 1, (double)); + break; + case HA_KEYTYPE_SHORT_INT: + RT_VOL_KORR(int16, mi_sint2korr, 2, (double)); + break; + case HA_KEYTYPE_USHORT_INT: + RT_VOL_KORR(uint16, mi_uint2korr, 2, (double)); + break; + case HA_KEYTYPE_INT24: + RT_VOL_KORR(int32, mi_sint3korr, 3, (double)); + break; + case HA_KEYTYPE_UINT24: + RT_VOL_KORR(uint32, mi_uint3korr, 3, (double)); + break; + case HA_KEYTYPE_LONG_INT: + RT_VOL_KORR(int32, mi_sint4korr, 4, (double)); + break; + case HA_KEYTYPE_ULONG_INT: + RT_VOL_KORR(uint32, mi_uint4korr, 4, (double)); + break; +#ifdef HAVE_LONG_LONG + case HA_KEYTYPE_LONGLONG: + RT_VOL_KORR(longlong, mi_sint8korr, 8, (double)); + break; + case HA_KEYTYPE_ULONGLONG: + RT_VOL_KORR(longlong, mi_sint8korr, 8, ulonglong2double); + break; +#endif + case HA_KEYTYPE_FLOAT: + RT_VOL_GET(float, mi_float4get, 4, (double)); + break; + case HA_KEYTYPE_DOUBLE: + RT_VOL_GET(double, mi_float8get, 8, (double)); + break; + case HA_KEYTYPE_END: + key_length= 0; + break; + default: + return -1; + } + keyseg_length= keyseg->length * 2; + key_length-= keyseg_length; + a+= keyseg_length; + } + return res; +} + +#define RT_D_MBR_KORR(type, korr_func, len, cast) \ +{ \ + type amin, amax; \ + amin= korr_func(a); \ + amax= korr_func(a+len); \ + *res++= cast(amin); \ + *res++= cast(amax); \ +} + +#define RT_D_MBR_GET(type, get_func, len, cast) \ +{ \ + type amin, amax; \ + get_func(amin, a); \ + get_func(amax, a+len); \ + *res++= cast(amin); \ + *res++= cast(amax); \ +} + + +/* + Creates an MBR as an array of doubles. + Fills *res. +*/ + +int maria_rtree_d_mbr(const HA_KEYSEG *keyseg, const uchar *a, + uint key_length, double *res) +{ + for (; (int)key_length > 0; keyseg += 2) + { + uint32 keyseg_length; + switch ((enum ha_base_keytype) keyseg->type) { + case HA_KEYTYPE_INT8: + RT_D_MBR_KORR(int8, mi_sint1korr, 1, (double)); + break; + case HA_KEYTYPE_BINARY: + RT_D_MBR_KORR(uint8, mi_uint1korr, 1, (double)); + break; + case HA_KEYTYPE_SHORT_INT: + RT_D_MBR_KORR(int16, mi_sint2korr, 2, (double)); + break; + case HA_KEYTYPE_USHORT_INT: + RT_D_MBR_KORR(uint16, mi_uint2korr, 2, (double)); + break; + case HA_KEYTYPE_INT24: + RT_D_MBR_KORR(int32, mi_sint3korr, 3, (double)); + break; + case HA_KEYTYPE_UINT24: + RT_D_MBR_KORR(uint32, mi_uint3korr, 3, (double)); + break; + case HA_KEYTYPE_LONG_INT: + RT_D_MBR_KORR(int32, mi_sint4korr, 4, (double)); + break; + case HA_KEYTYPE_ULONG_INT: + RT_D_MBR_KORR(uint32, mi_uint4korr, 4, (double)); + break; +#ifdef HAVE_LONG_LONG + case HA_KEYTYPE_LONGLONG: + RT_D_MBR_KORR(longlong, mi_sint8korr, 8, (double)); + break; + case HA_KEYTYPE_ULONGLONG: + RT_D_MBR_KORR(longlong, mi_sint8korr, 8, ulonglong2double); + break; +#endif + case HA_KEYTYPE_FLOAT: + RT_D_MBR_GET(float, mi_float4get, 4, (double)); + break; + case HA_KEYTYPE_DOUBLE: + RT_D_MBR_GET(double, mi_float8get, 8, (double)); + break; + case HA_KEYTYPE_END: + key_length= 0; + break; + default: + return 1; + } + keyseg_length= keyseg->length * 2; + key_length-= keyseg_length; + a+= keyseg_length; + } + return 0; +} + +#define RT_COMB_KORR(type, korr_func, store_func, len) \ +{ \ + type amin, amax, bmin, bmax; \ + amin= korr_func(a); \ + bmin= korr_func(b); \ + amax= korr_func(a+len); \ + bmax= korr_func(b+len); \ + amin= min(amin, bmin); \ + amax= max(amax, bmax); \ + store_func(c, amin); \ + store_func(c+len, amax); \ +} + +#define RT_COMB_GET(type, get_func, store_func, len) \ +{ \ + type amin, amax, bmin, bmax; \ + get_func(amin, a); \ + get_func(bmin, b); \ + get_func(amax, a+len); \ + get_func(bmax, b+len); \ + amin= min(amin, bmin); \ + amax= max(amax, bmax); \ + store_func(c, amin); \ + store_func(c+len, amax); \ +} + +/* + Creates common minimal bounding rectungle + for two input rectagnles a and b + Result is written to c +*/ + +int maria_rtree_combine_rect(const HA_KEYSEG *keyseg, const uchar* a, + const uchar* b, uchar* c, + uint key_length) +{ + for ( ; (int) key_length > 0 ; keyseg += 2) + { + uint32 keyseg_length; + switch ((enum ha_base_keytype) keyseg->type) { + case HA_KEYTYPE_INT8: + RT_COMB_KORR(int8, mi_sint1korr, mi_int1store, 1); + break; + case HA_KEYTYPE_BINARY: + RT_COMB_KORR(uint8, mi_uint1korr, mi_int1store, 1); + break; + case HA_KEYTYPE_SHORT_INT: + RT_COMB_KORR(int16, mi_sint2korr, mi_int2store, 2); + break; + case HA_KEYTYPE_USHORT_INT: + RT_COMB_KORR(uint16, mi_uint2korr, mi_int2store, 2); + break; + case HA_KEYTYPE_INT24: + RT_COMB_KORR(int32, mi_sint3korr, mi_int3store, 3); + break; + case HA_KEYTYPE_UINT24: + RT_COMB_KORR(uint32, mi_uint3korr, mi_int3store, 3); + break; + case HA_KEYTYPE_LONG_INT: + RT_COMB_KORR(int32, mi_sint4korr, mi_int4store, 4); + break; + case HA_KEYTYPE_ULONG_INT: + RT_COMB_KORR(uint32, mi_uint4korr, mi_int4store, 4); + break; +#ifdef HAVE_LONG_LONG + case HA_KEYTYPE_LONGLONG: + RT_COMB_KORR(longlong, mi_sint8korr, mi_int8store, 8); + break; + case HA_KEYTYPE_ULONGLONG: + RT_COMB_KORR(ulonglong, mi_uint8korr, mi_int8store, 8); + break; +#endif + case HA_KEYTYPE_FLOAT: + RT_COMB_GET(float, mi_float4get, mi_float4store, 4); + break; + case HA_KEYTYPE_DOUBLE: + RT_COMB_GET(double, mi_float8get, mi_float8store, 8); + break; + case HA_KEYTYPE_END: + return 0; + default: + return 1; + } + keyseg_length= keyseg->length * 2; + key_length-= keyseg_length; + a+= keyseg_length; + b+= keyseg_length; + c+= keyseg_length; + } + return 0; +} + + +#define RT_OVL_AREA_KORR(type, korr_func, len) \ +{ \ + type amin, amax, bmin, bmax; \ + amin= korr_func(a); \ + bmin= korr_func(b); \ + amax= korr_func(a+len); \ + bmax= korr_func(b+len); \ + amin= max(amin, bmin); \ + amax= min(amax, bmax); \ + if (amin >= amax) \ + return 0; \ + res *= amax - amin; \ +} + +#define RT_OVL_AREA_GET(type, get_func, len) \ +{ \ + type amin, amax, bmin, bmax; \ + get_func(amin, a); \ + get_func(bmin, b); \ + get_func(amax, a+len); \ + get_func(bmax, b+len); \ + amin= max(amin, bmin); \ + amax= min(amax, bmax); \ + if (amin >= amax) \ + return 0; \ + res *= amax - amin; \ +} + +/* +Calculates overlapping area of two MBRs a & b +*/ +double maria_rtree_overlapping_area(HA_KEYSEG *keyseg, uchar* a, uchar* b, + uint key_length) +{ + double res= 1; + for (; (int) key_length > 0 ; keyseg += 2) + { + uint32 keyseg_length; + switch ((enum ha_base_keytype) keyseg->type) { + case HA_KEYTYPE_INT8: + RT_OVL_AREA_KORR(int8, mi_sint1korr, 1); + break; + case HA_KEYTYPE_BINARY: + RT_OVL_AREA_KORR(uint8, mi_uint1korr, 1); + break; + case HA_KEYTYPE_SHORT_INT: + RT_OVL_AREA_KORR(int16, mi_sint2korr, 2); + break; + case HA_KEYTYPE_USHORT_INT: + RT_OVL_AREA_KORR(uint16, mi_uint2korr, 2); + break; + case HA_KEYTYPE_INT24: + RT_OVL_AREA_KORR(int32, mi_sint3korr, 3); + break; + case HA_KEYTYPE_UINT24: + RT_OVL_AREA_KORR(uint32, mi_uint3korr, 3); + break; + case HA_KEYTYPE_LONG_INT: + RT_OVL_AREA_KORR(int32, mi_sint4korr, 4); + break; + case HA_KEYTYPE_ULONG_INT: + RT_OVL_AREA_KORR(uint32, mi_uint4korr, 4); + break; +#ifdef HAVE_LONG_LONG + case HA_KEYTYPE_LONGLONG: + RT_OVL_AREA_KORR(longlong, mi_sint8korr, 8); + break; + case HA_KEYTYPE_ULONGLONG: + RT_OVL_AREA_KORR(longlong, mi_sint8korr, 8); + break; +#endif + case HA_KEYTYPE_FLOAT: + RT_OVL_AREA_GET(float, mi_float4get, 4); + break; + case HA_KEYTYPE_DOUBLE: + RT_OVL_AREA_GET(double, mi_float8get, 8); + break; + case HA_KEYTYPE_END: + return res; + default: + return -1; + } + keyseg_length= keyseg->length * 2; + key_length-= keyseg_length; + a+= keyseg_length; + b+= keyseg_length; + } + return res; +} + +#define RT_AREA_INC_KORR(type, korr_func, len) \ +{ \ + type amin, amax, bmin, bmax; \ + amin= korr_func(a); \ + bmin= korr_func(b); \ + amax= korr_func(a+len); \ + bmax= korr_func(b+len); \ + a_area *= (((double)amax) - ((double)amin)); \ + loc_ab_area *= ((double)max(amax, bmax) - (double)min(amin, bmin)); \ +} + +#define RT_AREA_INC_GET(type, get_func, len)\ +{\ + type amin, amax, bmin, bmax; \ + get_func(amin, a); \ + get_func(bmin, b); \ + get_func(amax, a+len); \ + get_func(bmax, b+len); \ + a_area *= (((double)amax) - ((double)amin)); \ + loc_ab_area *= ((double)max(amax, bmax) - (double)min(amin, bmin)); \ +} + +/* + Calculates MBR_AREA(a+b) - MBR_AREA(a) + Fills *ab_area. + Note: when 'a' and 'b' objects are far from each other, + the area increase can be really big, so this function + can return 'inf' as a result. +*/ + +double maria_rtree_area_increase(const HA_KEYSEG *keyseg, const uchar *a, + const uchar *b, + uint key_length, double *ab_area) +{ + double a_area= 1.0; + double loc_ab_area= 1.0; + + *ab_area= 1.0; + for (; (int)key_length > 0; keyseg += 2) + { + uint32 keyseg_length; + + if (keyseg->null_bit) /* Handle NULL part */ + return -1; + + switch ((enum ha_base_keytype) keyseg->type) { + case HA_KEYTYPE_INT8: + RT_AREA_INC_KORR(int8, mi_sint1korr, 1); + break; + case HA_KEYTYPE_BINARY: + RT_AREA_INC_KORR(uint8, mi_uint1korr, 1); + break; + case HA_KEYTYPE_SHORT_INT: + RT_AREA_INC_KORR(int16, mi_sint2korr, 2); + break; + case HA_KEYTYPE_USHORT_INT: + RT_AREA_INC_KORR(uint16, mi_uint2korr, 2); + break; + case HA_KEYTYPE_INT24: + RT_AREA_INC_KORR(int32, mi_sint3korr, 3); + break; + case HA_KEYTYPE_UINT24: + RT_AREA_INC_KORR(int32, mi_uint3korr, 3); + break; + case HA_KEYTYPE_LONG_INT: + RT_AREA_INC_KORR(int32, mi_sint4korr, 4); + break; + case HA_KEYTYPE_ULONG_INT: + RT_AREA_INC_KORR(uint32, mi_uint4korr, 4); + break; +#ifdef HAVE_LONG_LONG + case HA_KEYTYPE_LONGLONG: + RT_AREA_INC_KORR(longlong, mi_sint8korr, 8); + break; + case HA_KEYTYPE_ULONGLONG: + RT_AREA_INC_KORR(longlong, mi_sint8korr, 8); + break; +#endif + case HA_KEYTYPE_FLOAT: + RT_AREA_INC_GET(float, mi_float4get, 4); + break; + case HA_KEYTYPE_DOUBLE: + RT_AREA_INC_GET(double, mi_float8get, 8); + break; + case HA_KEYTYPE_END: + goto safe_end; + default: + return -1; + } + keyseg_length= keyseg->length * 2; + key_length-= keyseg_length; + a+= keyseg_length; + b+= keyseg_length; + } +safe_end: + *ab_area= loc_ab_area; + return loc_ab_area - a_area; +} + +#define RT_PERIM_INC_KORR(type, korr_func, len) \ +{ \ + type amin, amax, bmin, bmax; \ + amin= korr_func(a); \ + bmin= korr_func(b); \ + amax= korr_func(a+len); \ + bmax= korr_func(b+len); \ + a_perim+= (((double)amax) - ((double)amin)); \ + *ab_perim+= ((double)max(amax, bmax) - (double)min(amin, bmin)); \ +} + +#define RT_PERIM_INC_GET(type, get_func, len)\ +{\ + type amin, amax, bmin, bmax; \ + get_func(amin, a); \ + get_func(bmin, b); \ + get_func(amax, a+len); \ + get_func(bmax, b+len); \ + a_perim+= (((double)amax) - ((double)amin)); \ + *ab_perim+= ((double)max(amax, bmax) - (double)min(amin, bmin)); \ +} + +/* +Calculates MBR_PERIMETER(a+b) - MBR_PERIMETER(a) +*/ +double maria_rtree_perimeter_increase(HA_KEYSEG *keyseg, uchar* a, uchar* b, + uint key_length, double *ab_perim) +{ + double a_perim= 0.0; + + *ab_perim= 0.0; + for (; (int)key_length > 0; keyseg += 2) + { + uint32 keyseg_length; + + if (keyseg->null_bit) /* Handle NULL part */ + return -1; + + switch ((enum ha_base_keytype) keyseg->type) { + case HA_KEYTYPE_INT8: + RT_PERIM_INC_KORR(int8, mi_sint1korr, 1); + break; + case HA_KEYTYPE_BINARY: + RT_PERIM_INC_KORR(uint8, mi_uint1korr, 1); + break; + case HA_KEYTYPE_SHORT_INT: + RT_PERIM_INC_KORR(int16, mi_sint2korr, 2); + break; + case HA_KEYTYPE_USHORT_INT: + RT_PERIM_INC_KORR(uint16, mi_uint2korr, 2); + break; + case HA_KEYTYPE_INT24: + RT_PERIM_INC_KORR(int32, mi_sint3korr, 3); + break; + case HA_KEYTYPE_UINT24: + RT_PERIM_INC_KORR(int32, mi_uint3korr, 3); + break; + case HA_KEYTYPE_LONG_INT: + RT_PERIM_INC_KORR(int32, mi_sint4korr, 4); + break; + case HA_KEYTYPE_ULONG_INT: + RT_PERIM_INC_KORR(uint32, mi_uint4korr, 4); + break; +#ifdef HAVE_LONG_LONG + case HA_KEYTYPE_LONGLONG: + RT_PERIM_INC_KORR(longlong, mi_sint8korr, 8); + break; + case HA_KEYTYPE_ULONGLONG: + RT_PERIM_INC_KORR(longlong, mi_sint8korr, 8); + break; +#endif + case HA_KEYTYPE_FLOAT: + RT_PERIM_INC_GET(float, mi_float4get, 4); + break; + case HA_KEYTYPE_DOUBLE: + RT_PERIM_INC_GET(double, mi_float8get, 8); + break; + case HA_KEYTYPE_END: + return *ab_perim - a_perim; + default: + return -1; + } + keyseg_length= keyseg->length * 2; + key_length-= keyseg_length; + a+= keyseg_length; + b+= keyseg_length; + } + return *ab_perim - a_perim; +} + + +#define RT_PAGE_MBR_KORR(share, type, korr_func, store_func, len, to) \ +{ \ + type amin, amax, bmin, bmax; \ + amin= korr_func(k + inc); \ + amax= korr_func(k + inc + len); \ + k= rt_PAGE_NEXT_KEY(share, k, k_len, nod_flag); \ + for (; k < last; k= rt_PAGE_NEXT_KEY(share, k, k_len, nod_flag)) \ +{ \ + bmin= korr_func(k + inc); \ + bmax= korr_func(k + inc + len); \ + if (amin > bmin) \ + amin= bmin; \ + if (amax < bmax) \ + amax= bmax; \ +} \ + store_func(to, amin); \ + to+= len; \ + store_func(to, amax); \ + to += len; \ + inc += 2 * len; \ +} + +#define RT_PAGE_MBR_GET(share, type, get_func, store_func, len, to) \ +{ \ + type amin, amax, bmin, bmax; \ + get_func(amin, k + inc); \ + get_func(amax, k + inc + len); \ + k= rt_PAGE_NEXT_KEY(share, k, k_len, nod_flag); \ + for (; k < last; k= rt_PAGE_NEXT_KEY(share, k, k_len, nod_flag)) \ +{ \ + get_func(bmin, k + inc); \ + get_func(bmax, k + inc + len); \ + if (amin > bmin) \ + amin= bmin; \ + if (amax < bmax) \ + amax= bmax; \ +} \ + store_func(to, amin); \ + to+= len; \ + store_func(to, amax); \ + to+= len; \ + inc += 2 * len; \ +} + +/* + Calculates key page total MBR= MBR(key1) + MBR(key2) + ... + Stores into *to. +*/ +int maria_rtree_page_mbr(const HA_KEYSEG *keyseg, + MARIA_PAGE *page, + uchar *to, uint key_length) +{ + MARIA_HA *info= page->info; + MARIA_SHARE *share= info->s; + uint inc= 0; + uint k_len= key_length; + uint nod_flag= page->node; + const uchar *k; + const uchar *last= rt_PAGE_END(page); + + for (; (int)key_length > 0; keyseg += 2) + { + key_length -= keyseg->length * 2; + + /* Handle NULL part */ + if (keyseg->null_bit) + { + return 1; + } + + k= rt_PAGE_FIRST_KEY(share, page->buff, nod_flag); + + switch ((enum ha_base_keytype) keyseg->type) { + case HA_KEYTYPE_INT8: + RT_PAGE_MBR_KORR(share, int8, mi_sint1korr, mi_int1store, 1, to); + break; + case HA_KEYTYPE_BINARY: + RT_PAGE_MBR_KORR(share, uint8, mi_uint1korr, mi_int1store, 1, to); + break; + case HA_KEYTYPE_SHORT_INT: + RT_PAGE_MBR_KORR(share, int16, mi_sint2korr, mi_int2store, 2, to); + break; + case HA_KEYTYPE_USHORT_INT: + RT_PAGE_MBR_KORR(share, uint16, mi_uint2korr, mi_int2store, 2, to); + break; + case HA_KEYTYPE_INT24: + RT_PAGE_MBR_KORR(share, int32, mi_sint3korr, mi_int3store, 3, to); + break; + case HA_KEYTYPE_UINT24: + RT_PAGE_MBR_KORR(share, uint32, mi_uint3korr, mi_int3store, 3, to); + break; + case HA_KEYTYPE_LONG_INT: + RT_PAGE_MBR_KORR(share, int32, mi_sint4korr, mi_int4store, 4, to); + break; + case HA_KEYTYPE_ULONG_INT: + RT_PAGE_MBR_KORR(share, uint32, mi_uint4korr, mi_int4store, 4, to); + break; +#ifdef HAVE_LONG_LONG + case HA_KEYTYPE_LONGLONG: + RT_PAGE_MBR_KORR(share, longlong, mi_sint8korr, mi_int8store, 8, to); + break; + case HA_KEYTYPE_ULONGLONG: + RT_PAGE_MBR_KORR(share, ulonglong, mi_uint8korr, mi_int8store, 8, to); + break; +#endif + case HA_KEYTYPE_FLOAT: + RT_PAGE_MBR_GET(share, float, mi_float4get, mi_float4store, 4, to); + break; + case HA_KEYTYPE_DOUBLE: + RT_PAGE_MBR_GET(share, double, mi_float8get, mi_float8store, 8, to); + break; + case HA_KEYTYPE_END: + return 0; + default: + return 1; + } + } + return 0; +} + +#endif /*HAVE_RTREE_KEYS*/ diff --git a/storage/maria/ma_rt_mbr.h b/storage/maria/ma_rt_mbr.h new file mode 100644 index 00000000000..8fcd3d37b99 --- /dev/null +++ b/storage/maria/ma_rt_mbr.h @@ -0,0 +1,40 @@ +/* Copyright (C) 2006 MySQL AB & Ramil Kalimullin & MySQL Finland AB + & TCX DataKonsult 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 _rt_mbr_h +#define _rt_mbr_h + +#ifdef HAVE_RTREE_KEYS + +int maria_rtree_key_cmp(HA_KEYSEG *keyseg, const uchar *a, const uchar *b, + uint key_length, uint32 nextflag); +int maria_rtree_combine_rect(const HA_KEYSEG *keyseg, + const uchar *, const uchar *, uchar*, + uint key_length); +double maria_rtree_rect_volume(HA_KEYSEG *keyseg, uchar*, uint key_length); +int maria_rtree_d_mbr(const HA_KEYSEG *keyseg, const uchar *a, + uint key_length, double *res); +double maria_rtree_overlapping_area(HA_KEYSEG *keyseg, uchar *a, uchar *b, + uint key_length); +double maria_rtree_area_increase(const HA_KEYSEG *keyseg, const uchar *a, + const uchar *b, + uint key_length, double *ab_area); +double maria_rtree_perimeter_increase(HA_KEYSEG *keyseg, uchar* a, uchar* b, + uint key_length, double *ab_perim); +int maria_rtree_page_mbr(const HA_KEYSEG *keyseg, MARIA_PAGE *page, + uchar *key, uint key_length); +#endif /*HAVE_RTREE_KEYS*/ +#endif /* _rt_mbr_h */ diff --git a/storage/maria/ma_rt_split.c b/storage/maria/ma_rt_split.c new file mode 100644 index 00000000000..8f137c2e0cf --- /dev/null +++ b/storage/maria/ma_rt_split.c @@ -0,0 +1,567 @@ +/* Copyright (C) 2006 MySQL AB & Alexey Botchkov & MySQL Finland AB + & TCX DataKonsult 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 "maria_def.h" +#include "trnman.h" +#include "ma_key_recover.h" + +#ifdef HAVE_RTREE_KEYS + +#include "ma_rt_index.h" +#include "ma_rt_key.h" +#include "ma_rt_mbr.h" + +typedef struct +{ + double square; + int n_node; + const uchar *key; + double *coords; +} SplitStruct; + +inline static double *reserve_coords(double **d_buffer, int n_dim) +{ + double *coords= *d_buffer; + (*d_buffer)+= n_dim * 2; + return coords; +} + +static void mbr_join(double *a, const double *b, int n_dim) +{ + double *end= a + n_dim * 2; + do + { + if (a[0] > b[0]) + a[0]= b[0]; + + if (a[1] < b[1]) + a[1]= b[1]; + + a+= 2; + b+= 2; + } while (a != end); +} + +/* +Counts the square of mbr which is a join of a and b +*/ +static double mbr_join_square(const double *a, const double *b, int n_dim) +{ + const double *end= a + n_dim * 2; + double square= 1.0; + do + { + square *= + ((a[1] < b[1]) ? b[1] : a[1]) - ((a[0] > b[0]) ? b[0] : a[0]); + + a+= 2; + b+= 2; + } while (a != end); + + return square; +} + +static double count_square(const double *a, int n_dim) +{ + const double *end= a + n_dim * 2; + double square= 1.0; + do + { + square *= a[1] - a[0]; + a+= 2; + } while (a != end); + return square; +} + +inline static void copy_coords(double *dst, const double *src, int n_dim) +{ + memcpy(dst, src, sizeof(double) * (n_dim * 2)); +} + +/** + Select two nodes to collect group upon. + + Note that such function uses 'double' arithmetic so may behave differently + on different platforms/builds. There are others in this file. +*/ +static void pick_seeds(SplitStruct *node, int n_entries, + SplitStruct **seed_a, SplitStruct **seed_b, int n_dim) +{ + SplitStruct *cur1; + SplitStruct *lim1= node + (n_entries - 1); + SplitStruct *cur2; + SplitStruct *lim2= node + n_entries; + + double max_d= -DBL_MAX; + double d; + + for (cur1= node; cur1 < lim1; cur1++) + { + for (cur2=cur1 + 1; cur2 < lim2; cur2++) + { + + d= mbr_join_square(cur1->coords, cur2->coords, n_dim) - cur1->square - + cur2->square; + if (d > max_d) + { + max_d= d; + *seed_a= cur1; + *seed_b= cur2; + } + } + } +} + +/* +Select next node and group where to add +*/ +static void pick_next(SplitStruct *node, int n_entries, double *g1, double *g2, + SplitStruct **choice, int *n_group, int n_dim) +{ + SplitStruct *cur= node; + SplitStruct *end= node + n_entries; + + double max_diff= -DBL_MAX; + + for (; cur < end; cur++) + { + double diff; + double abs_diff; + + if (cur->n_node) + { + continue; + } + + diff= mbr_join_square(g1, cur->coords, n_dim) - + mbr_join_square(g2, cur->coords, n_dim); + + abs_diff= fabs(diff); + if (abs_diff > max_diff) + { + max_diff= abs_diff; + *n_group= 1 + (diff > 0); + *choice= cur; + } + } +} + +/* +Mark not-in-group entries as n_group +*/ +static void mark_all_entries(SplitStruct *node, int n_entries, int n_group) +{ + SplitStruct *cur= node; + SplitStruct *end= node + n_entries; + + for (; cur < end; cur++) + { + if (cur->n_node) + { + continue; + } + cur->n_node= n_group; + } +} + +static int split_maria_rtree_node(SplitStruct *node, int n_entries, + int all_size, /* Total key's size */ + int key_size, + int min_size, /* Minimal group size */ + int size1, int size2 /* initial group sizes */, + double **d_buffer, int n_dim) +{ + SplitStruct *cur; + SplitStruct *a; + SplitStruct *b; + double *g1= reserve_coords(d_buffer, n_dim); + double *g2= reserve_coords(d_buffer, n_dim); + SplitStruct *next; + int next_node; + int i; + SplitStruct *end= node + n_entries; + LINT_INIT(a); + LINT_INIT(b); + LINT_INIT(next); + LINT_INIT(next_node); + + if (all_size < min_size * 2) + { + return 1; + } + + cur= node; + for (; cur < end; cur++) + { + cur->square= count_square(cur->coords, n_dim); + cur->n_node= 0; + } + + pick_seeds(node, n_entries, &a, &b, n_dim); + a->n_node= 1; + b->n_node= 2; + + + copy_coords(g1, a->coords, n_dim); + size1+= key_size; + copy_coords(g2, b->coords, n_dim); + size2+= key_size; + + + for (i=n_entries - 2; i>0; --i) + { + if (all_size - (size2 + key_size) < min_size) /* Can't write into group 2 */ + { + mark_all_entries(node, n_entries, 1); + break; + } + + if (all_size - (size1 + key_size) < min_size) /* Can't write into group 1 */ + { + mark_all_entries(node, n_entries, 2); + break; + } + + pick_next(node, n_entries, g1, g2, &next, &next_node, n_dim); + if (next_node == 1) + { + size1+= key_size; + mbr_join(g1, next->coords, n_dim); + } + else + { + size2+= key_size; + mbr_join(g2, next->coords, n_dim); + } + next->n_node= next_node; + } + + return 0; +} + + +/** + Logs key reorganization done in a split page (new page is logged elsewhere). + + The effect of a split on the split page is three changes: + - some piece of the page move to different places inside this page (we are + not interested here in the pieces which move to the new page) + - the key is inserted into the page or not (could be in the new page) + - page is shrunk + All this is uniquely determined by a few parameters: + - the key (starting at 'key-nod_flag', for 'full_length' bytes + (maria_rtree_split_page() seems to depend on its parameters key&key_length + but in fact it reads more (to the left: nod_flag, and to the right: + full_length) + - the binary content of the page + - some variables in the share + - double arithmetic, which is unpredictable from machine to machine and + from build to build (see pick_seeds() above: it has a comparison between + double-s 'if (d > max_d)' so the comparison can go differently from machine + to machine or build to build, it has happened in real life). + If one day we use precision-math instead of double-math, in GIS, then the + last parameter would become constant accross machines and builds and we + could some cheap logging: just log the few parameters above. + Until then, we log the list of memcpy() operations (fortunately, we often do + not have to log the source bytes, as they can be found in the page before + applying the REDO; the only source bytes to log are the key), the key if it + was inserted into this page, and the shrinking. + + @param info table + @param page page's offset in the file + @param buff content of the page (post-split) + @param key_with_nod_flag pointer to key-nod_flag + @param full_length length of (key + (nod_flag (if node) or rowid (if + leaf))) + @param log_internal_copy encoded list of mempcy() operations done on + split page, having their source in the page + @param log_internal_copy_length length of above list, in bytes + @param log_key_copy operation describing the key's copy, or NULL if the + inserted key was not put into the page (was put in + new page, so does not have to be logged here) + @param length_diff by how much the page has shrunk during split +*/ + +static my_bool _ma_log_rt_split(MARIA_PAGE *page, + const uchar *key_with_nod_flag, + uint full_length, + const uchar *log_internal_copy, + uint log_internal_copy_length, + const uchar *log_key_copy, + uint length_diff) +{ + MARIA_HA *info= page->info; + MARIA_SHARE *share= info->s; + LSN lsn; + uchar log_data[FILEID_STORE_SIZE + PAGE_STORE_SIZE + 1 + 2 + 1 + 2 + 2 + 7], + *log_pos; + LEX_CUSTRING log_array[TRANSLOG_INTERNAL_PARTS + 5]; + uint translog_parts, extra_length= 0; + my_off_t page_pos; + DBUG_ENTER("_ma_log_rt_split"); + DBUG_PRINT("enter", ("page: %lu", (ulong) page)); + + DBUG_ASSERT(share->now_transactional); + page_pos= page->pos / share->block_size; + page_store(log_data + FILEID_STORE_SIZE, page_pos); + log_pos= log_data+ FILEID_STORE_SIZE + PAGE_STORE_SIZE; + log_pos[0]= KEY_OP_DEL_SUFFIX; + log_pos++; + DBUG_ASSERT((int)length_diff > 0); + int2store(log_pos, length_diff); + log_pos+= 2; + log_pos[0]= KEY_OP_MULTI_COPY; + log_pos++; + int2store(log_pos, full_length); + log_pos+= 2; + int2store(log_pos, log_internal_copy_length); + log_pos+= 2; + log_array[TRANSLOG_INTERNAL_PARTS + 0].str= log_data; + log_array[TRANSLOG_INTERNAL_PARTS + 0].length= sizeof(log_data) - 7; + log_array[TRANSLOG_INTERNAL_PARTS + 1].str= log_internal_copy; + log_array[TRANSLOG_INTERNAL_PARTS + 1].length= log_internal_copy_length; + translog_parts= 2; + if (log_key_copy != NULL) /* need to store key into record */ + { + log_array[TRANSLOG_INTERNAL_PARTS + 2].str= log_key_copy; + log_array[TRANSLOG_INTERNAL_PARTS + 2].length= 1 + 2 + 1 + 2; + log_array[TRANSLOG_INTERNAL_PARTS + 3].str= key_with_nod_flag; + log_array[TRANSLOG_INTERNAL_PARTS + 3].length= full_length; + extra_length= 1 + 2 + 1 + 2 + full_length; + translog_parts+= 2; + } + +#ifdef EXTRA_DEBUG_KEY_CHANGES + { + int page_length= page->size; + ha_checksum crc; + uchar *check_start= log_pos; + crc= my_checksum(0, page->buff + LSN_STORE_SIZE, + page_length - LSN_STORE_SIZE); + log_pos[0]= KEY_OP_CHECK; + log_pos++; + int2store(log_pos, page_length); + log_pos+= 2; + int4store(log_pos, crc); + log_pos+= 4; + log_array[TRANSLOG_INTERNAL_PARTS + translog_parts].str= check_start; + log_array[TRANSLOG_INTERNAL_PARTS + translog_parts].length= 7; + translog_parts++; + } +#endif + + if (translog_write_record(&lsn, LOGREC_REDO_INDEX, + info->trn, info, + (translog_size_t) ((log_pos - log_data) + + log_internal_copy_length + + extra_length), + TRANSLOG_INTERNAL_PARTS + translog_parts, + log_array, log_data, NULL)) + DBUG_RETURN(1); + DBUG_RETURN(0); +} + +/** + 0 ok; the created page is put into page cache; the shortened one is not (up + to the caller to do it) + 1 or -1: error. + If new_page_offs==NULL, won't create new page (for redo phase). +*/ + +int maria_rtree_split_page(const MARIA_KEY *key, MARIA_PAGE *page, + my_off_t *new_page_offs) +{ + MARIA_HA *info= page->info; + MARIA_SHARE *share= info->s; + const my_bool transactional= share->now_transactional; + int n1, n2; /* Number of items in groups */ + SplitStruct *task; + SplitStruct *cur; + SplitStruct *stop; + double *coord_buf; + double *next_coord; + double *old_coord; + int n_dim; + uchar *source_cur, *cur1, *cur2; + uchar *new_page_buff, *log_internal_copy, *log_internal_copy_ptr, + *log_key_copy= NULL; + int err_code= 0; + uint new_page_length; + uint nod_flag= page->node; + uint org_length= page->size; + uint full_length= key->data_length + (nod_flag ? nod_flag : + key->ref_length); + uint key_data_length= key->data_length; + int max_keys= ((org_length - share->keypage_header) / (full_length)); + MARIA_PINNED_PAGE tmp_page_link, *page_link= &tmp_page_link; + MARIA_KEYDEF *keyinfo= key->keyinfo; + DBUG_ENTER("maria_rtree_split_page"); + DBUG_PRINT("rtree", ("splitting block")); + + n_dim= keyinfo->keysegs / 2; + + if (!(coord_buf= (double*) my_alloca(n_dim * 2 * sizeof(double) * + (max_keys + 1 + 4) + + sizeof(SplitStruct) * (max_keys + 1)))) + DBUG_RETURN(-1); /* purecov: inspected */ + + task= (SplitStruct *)(coord_buf + n_dim * 2 * (max_keys + 1 + 4)); + + next_coord= coord_buf; + + stop= task + max_keys; + source_cur= rt_PAGE_FIRST_KEY(share, page->buff, nod_flag); + + for (cur= task; + cur < stop; + cur++, source_cur= rt_PAGE_NEXT_KEY(share, source_cur, key_data_length, + nod_flag)) + { + cur->coords= reserve_coords(&next_coord, n_dim); + cur->key= source_cur; + maria_rtree_d_mbr(keyinfo->seg, source_cur, key_data_length, cur->coords); + } + + cur->coords= reserve_coords(&next_coord, n_dim); + maria_rtree_d_mbr(keyinfo->seg, key->data, key_data_length, cur->coords); + cur->key= key->data; + + old_coord= next_coord; + + if (split_maria_rtree_node(task, max_keys + 1, + page->size + full_length + 2, + full_length, + rt_PAGE_MIN_SIZE(keyinfo->block_length), + 2, 2, &next_coord, n_dim)) + { + err_code= 1; + goto split_err; + } + + /* Allocate buffer for new page and piece of log record */ + if (!(new_page_buff= (uchar*) my_alloca((uint)keyinfo->block_length + + (transactional ? + (max_keys * (2 + 2) + + 1 + 2 + 1 + 2) : 0)))) + { + err_code= -1; + goto split_err; + } + log_internal_copy= log_internal_copy_ptr= new_page_buff + + keyinfo->block_length; + bzero(new_page_buff, share->block_size); + + stop= task + (max_keys + 1); + cur1= rt_PAGE_FIRST_KEY(share, page->buff, nod_flag); + cur2= rt_PAGE_FIRST_KEY(share, new_page_buff, nod_flag); + + n1= n2= 0; + for (cur= task; cur < stop; cur++) + { + uchar *to; + const uchar *cur_key= cur->key; + my_bool log_this_change; + DBUG_ASSERT(log_key_copy == NULL); + if (cur->n_node == 1) + { + to= cur1; + cur1= rt_PAGE_NEXT_KEY(share, cur1, key_data_length, nod_flag); + n1++; + log_this_change= transactional; + } + else + { + to= cur2; + cur2= rt_PAGE_NEXT_KEY(share, cur2, key_data_length, nod_flag); + n2++; + log_this_change= FALSE; + } + if (to != cur_key) + { + uchar *to_with_nod_flag= to - nod_flag; + const uchar *cur_key_with_nod_flag= cur_key - nod_flag; + memcpy(to_with_nod_flag, cur_key_with_nod_flag, full_length); + if (log_this_change) + { + uint to_with_nod_flag_offs= to_with_nod_flag - page->buff; + if (likely(cur_key != key->data)) + { + /* this memcpy() is internal to the page (source in the page) */ + uint cur_key_with_nod_flag_offs= cur_key_with_nod_flag - page->buff; + int2store(log_internal_copy_ptr, to_with_nod_flag_offs); + log_internal_copy_ptr+= 2; + int2store(log_internal_copy_ptr, cur_key_with_nod_flag_offs); + log_internal_copy_ptr+= 2; + } + else + { + /* last iteration, and this involves *key: source is external */ + log_key_copy= log_internal_copy_ptr; + log_key_copy[0]= KEY_OP_OFFSET; + int2store(log_key_copy + 1, to_with_nod_flag_offs); + log_key_copy[3]= KEY_OP_CHANGE; + int2store(log_key_copy + 4, full_length); + /* _ma_log_rt_split() will store *key, right after */ + } + } + } + } + { /* verify that above loop didn't touch header bytes */ + uint i; + for (i= 0; i < share->keypage_header; i++) + DBUG_ASSERT(new_page_buff[i]==0); + } + + if (nod_flag) + _ma_store_keypage_flag(share, new_page_buff, KEYPAGE_FLAG_ISNOD); + _ma_store_keynr(share, new_page_buff, keyinfo->key_nr); + new_page_length= share->keypage_header + n2 * full_length; + _ma_store_page_used(share, new_page_buff, new_page_length); + page->size= share->keypage_header + n1 * full_length; + page_store_size(share, page); + + if ((*new_page_offs= _ma_new(info, DFLT_INIT_HITS, &page_link)) == + HA_OFFSET_ERROR) + err_code= -1; + else + { + MARIA_PAGE new_page; + _ma_page_setup(&new_page, info, keyinfo, *new_page_offs, new_page_buff); + + if (transactional && + ( /* log change to split page */ + _ma_log_rt_split(page, key->data - nod_flag, + full_length, log_internal_copy, + log_internal_copy_ptr - log_internal_copy, + log_key_copy, org_length - page->size) || + /* and to new page */ + _ma_log_new(&new_page, 0))) + err_code= -1; + + if (_ma_write_keypage(&new_page, page_link->write_lock, + DFLT_INIT_HITS)) + err_code= -1; + } + DBUG_PRINT("rtree", ("split new block: %lu", (ulong) *new_page_offs)); + + my_afree(new_page); + +split_err: + my_afree(coord_buf); + DBUG_RETURN(err_code); +} + +#endif /*HAVE_RTREE_KEYS*/ diff --git a/storage/maria/ma_rt_test.c b/storage/maria/ma_rt_test.c new file mode 100644 index 00000000000..af54e6b27be --- /dev/null +++ b/storage/maria/ma_rt_test.c @@ -0,0 +1,692 @@ +/* Copyright (C) 2006 MySQL AB & MySQL Finland AB & TCX DataKonsult 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 */ + +/* Testing of the basic functions of a MARIA rtree table */ +/* Written by Alex Barkov who has a shared copyright to this code */ + + +#include "maria_def.h" +#include "ma_control_file.h" +#include "ma_loghandler.h" +#include "ma_checkpoint.h" +#include "trnman.h" +#include <my_getopt.h> + +#ifdef HAVE_RTREE_KEYS + +#include "ma_rt_index.h" + +#define MAX_REC_LENGTH 1024 +#define ndims 2 +#define KEYALG HA_KEY_ALG_RTREE + +static int read_with_pos(MARIA_HA * file); +static void create_record(uchar *record,uint rownr); +static void create_record1(uchar *record,uint rownr); +static void print_record(uchar * record,my_off_t offs,const char * tail); +static int run_test(const char *filename); +static void get_options(int argc, char *argv[]); +static void usage(); + +static double rt_data[]= +{ + /*1*/ 0,10,0,10, + /*2*/ 5,15,0,10, + /*3*/ 0,10,5,15, + /*4*/ 10,20,10,20, + /*5*/ 0,10,0,10, + /*6*/ 5,15,0,10, + /*7*/ 0,10,5,15, + /*8*/ 10,20,10,20, + /*9*/ 0,10,0,10, + /*10*/ 5,15,0,10, + /*11*/ 0,10,5,15, + /*12*/ 10,20,10,20, + /*13*/ 0,10,0,10, + /*14*/ 5,15,0,10, + /*15*/ 0,10,5,15, + /*16*/ 10,20,10,20, + /*17*/ 5,15,0,10, + /*18*/ 0,10,5,15, + /*19*/ 10,20,10,20, + /*20*/ 0,10,0,10, + + /*1*/ 100,110,0,10, + /*2*/ 105,115,0,10, + /*3*/ 100,110,5,15, + /*4*/ 110,120,10,20, + /*5*/ 100,110,0,10, + /*6*/ 105,115,0,10, + /*7*/ 100,110,5,15, + /*8*/ 110,120,10,20, + /*9*/ 100,110,0,10, + /*10*/ 105,115,0,10, + /*11*/ 100,110,5,15, + /*12*/ 110,120,10,20, + /*13*/ 100,110,0,10, + /*14*/ 105,115,0,10, + /*15*/ 100,110,5,15, + /*16*/ 110,120,10,20, + /*17*/ 105,115,0,10, + /*18*/ 100,110,5,15, + /*19*/ 110,120,10,20, + /*20*/ 100,110,0,10, + -1 +}; + +static int testflag, checkpoint, create_flag; +static my_bool silent, transactional, die_in_middle_of_transaction, + opt_versioning; +static enum data_file_type record_type= DYNAMIC_RECORD; + +int main(int argc, char *argv[]) +{ + MY_INIT(argv[0]); + get_options(argc, argv); + maria_data_root= (char *)"."; + /* Maria requires that we always have a page cache */ + if (maria_init() || + (init_pagecache(maria_pagecache, maria_block_size * 16, 0, 0, + maria_block_size, MY_WME) == 0) || + ma_control_file_open(TRUE, TRUE) || + (init_pagecache(maria_log_pagecache, + TRANSLOG_PAGECACHE_SIZE, 0, 0, + TRANSLOG_PAGE_SIZE, MY_WME) == 0) || + translog_init(maria_data_root, TRANSLOG_FILE_SIZE, + 0, 0, maria_log_pagecache, + TRANSLOG_DEFAULT_FLAGS, 0) || + (transactional && (trnman_init(0) || ma_checkpoint_init(0)))) + { + fprintf(stderr, "Error in initialization\n"); + exit(1); + } + + exit(run_test("rt_test")); +} + + +static int run_test(const char *filename) +{ + MARIA_HA *file; + MARIA_UNIQUEDEF uniquedef; + MARIA_CREATE_INFO create_info; + MARIA_COLUMNDEF recinfo[20]; + MARIA_KEYDEF keyinfo[20]; + HA_KEYSEG keyseg[20]; + key_range range; + + int opt_unique=0; + int key_type=HA_KEYTYPE_DOUBLE; + int key_length=8; + int null_fields=0; + int nrecords=sizeof(rt_data)/(sizeof(double)*4);/* 40 */ + int rec_length=0; + int uniques=0; + int i, max_i; + int error; + int row_count=0; + uchar record[MAX_REC_LENGTH]; + uchar read_record[MAX_REC_LENGTH]; + int upd= 10; + ha_rows hrows; + + bzero(&uniquedef, sizeof(uniquedef)); + bzero(&create_info, sizeof(create_info)); + bzero(recinfo, sizeof(recinfo)); + bzero(keyinfo, sizeof(keyinfo)); + bzero(keyseg, sizeof(keyseg)); + + /* Define a column for NULLs and DEL markers*/ + + recinfo[0].type=FIELD_NORMAL; + recinfo[0].length=1; /* For NULL bits */ + rec_length=1; + + /* Define 2*ndims columns for coordinates*/ + + for (i=1; i<=2*ndims ;i++) + { + recinfo[i].type=FIELD_NORMAL; + recinfo[i].length=key_length; + rec_length+=key_length; + } + + /* Define a key with 2*ndims segments */ + + keyinfo[0].seg=keyseg; + keyinfo[0].keysegs=2*ndims; + keyinfo[0].flag=0; + keyinfo[0].key_alg=KEYALG; + + for (i=0; i<2*ndims; i++) + { + keyinfo[0].seg[i].type= key_type; + keyinfo[0].seg[i].flag=0; /* Things like HA_REVERSE_SORT */ + keyinfo[0].seg[i].start= (key_length*i)+1; + keyinfo[0].seg[i].length=key_length; + keyinfo[0].seg[i].null_bit= null_fields ? 2 : 0; + keyinfo[0].seg[i].null_pos=0; + keyinfo[0].seg[i].language=default_charset_info->number; + } + + if (!silent) + printf("- Creating isam-file\n"); + + create_info.max_rows=10000000; + create_info.transactional= transactional; + + if (maria_create(filename, + record_type, + 1, /* keys */ + keyinfo, + 1+2*ndims+opt_unique, /* columns */ + recinfo,uniques,&uniquedef,&create_info,create_flag)) + goto err; + + if (!silent) + printf("- Open isam-file\n"); + + if (!(file=maria_open(filename,2,HA_OPEN_ABORT_IF_LOCKED))) + goto err; + maria_begin(file); + if (opt_versioning) + maria_versioning(file, 1); + if (testflag == 1) + goto end; + if (checkpoint == 1 && ma_checkpoint_execute(CHECKPOINT_MEDIUM, FALSE)) + goto err; + if (!silent) + printf("- Writing key:s\n"); + + for (i=0; i<nrecords; i++ ) + { + create_record(record,i); + error=maria_write(file,record); + print_record(record,maria_position(file),"\n"); + if (!error) + { + row_count++; + } + else + { + fprintf(stderr, "maria_write: %d\n", error); + goto err; + } + } + + if (maria_scan_init(file)) + { + fprintf(stderr, "maria_scan_init failed\n"); + goto err; + } + if ((error=read_with_pos(file))) + goto err; + maria_scan_end(file); + + if (!silent) + printf("- Reading rows with key\n"); + + for (i=0 ; i < nrecords ; i++) + { + my_errno=0; + create_record(record,i); + + bzero((char*) read_record,MAX_REC_LENGTH); + error=maria_rkey(file,read_record,0,record+1,HA_WHOLE_KEY,HA_READ_MBR_EQUAL); + + if (error && error!=HA_ERR_KEY_NOT_FOUND) + { + fprintf(stderr," maria_rkey: %3d errno: %3d\n",error,my_errno); + goto err; + } + if (error == HA_ERR_KEY_NOT_FOUND) + { + print_record(record,maria_position(file)," NOT FOUND\n"); + continue; + } + print_record(read_record,maria_position(file),"\n"); + } + + if (checkpoint == 2 && ma_checkpoint_execute(CHECKPOINT_MEDIUM, FALSE)) + goto err; + + if (testflag == 2) + goto end; + + if (!silent) + printf("- Deleting rows\n"); + if (maria_scan_init(file)) + { + fprintf(stderr, "maria_scan_init failed\n"); + goto err; + } + + for (i=0; i < nrecords/4; i++) + { + my_errno=0; + bzero((char*) read_record,MAX_REC_LENGTH); + error=maria_scan(file,read_record); + if (error) + { + fprintf(stderr, "pos: %2d maria_rrnd: %3d errno: %3d\n", i, error, + my_errno); + goto err; + } + print_record(read_record,maria_position(file),"\n"); + + error=maria_delete(file,read_record); + if (error) + { + fprintf(stderr, "pos: %2d maria_delete: %3d errno: %3d\n", i, error, + my_errno); + goto err; + } + } + maria_scan_end(file); + + if (testflag == 3) + goto end; + if (checkpoint == 3 && ma_checkpoint_execute(CHECKPOINT_MEDIUM, FALSE)) + goto err; + + if (!silent) + printf("- Updating rows with position\n"); + if (maria_scan_init(file)) + { + fprintf(stderr, "maria_scan_init failed\n"); + goto err; + } + + /* We are looking for nrecords-necords/2 non-deleted records */ + for (i=0, max_i= nrecords - nrecords/2; i < max_i ; i++) + { + my_errno=0; + bzero((char*) read_record,MAX_REC_LENGTH); + error=maria_scan(file,read_record); + if (error) + { + if (error==HA_ERR_RECORD_DELETED) + { + if (!silent) + printf("found deleted record\n"); + /* + In BLOCK_RECORD format, maria_scan() never returns deleted records, + while in DYNAMIC format it can. Don't count such record: + */ + max_i++; + continue; + } + fprintf(stderr, "pos: %2d maria_rrnd: %3d errno: %3d\n",i , error, + my_errno); + goto err; + } + print_record(read_record,maria_position(file),""); + create_record1(record,i+nrecords*upd); + if (!silent) + printf("\t-> "); + print_record(record,maria_position(file),"\n"); + error=maria_update(file,read_record,record); + if (error) + { + fprintf(stderr, "pos: %2d maria_update: %3d errno: %3d\n",i, error, + my_errno); + goto err; + } + } + + if (testflag == 4) + goto end; + if (checkpoint == 4 && ma_checkpoint_execute(CHECKPOINT_MEDIUM, FALSE)) + goto err; + + if (maria_scan_init(file)) + { + fprintf(stderr, "maria_scan_init failed\n"); + goto err; + } + if ((error=read_with_pos(file))) + goto err; + maria_scan_end(file); + + if (!silent) + printf("- Test maria_rkey then a sequence of maria_rnext_same\n"); + + create_record(record, nrecords*4/5); + print_record(record,0," search for\n"); + + if ((error=maria_rkey(file,read_record,0,record+1,HA_WHOLE_KEY, + HA_READ_MBR_INTERSECT))) + { + fprintf(stderr, "maria_rkey: %3d errno: %3d\n",error,my_errno); + goto err; + } + print_record(read_record,maria_position(file)," maria_rkey\n"); + row_count=1; + + for (;;) + { + if ((error=maria_rnext_same(file,read_record))) + { + if (error==HA_ERR_END_OF_FILE) + break; + fprintf(stderr, "maria_next: %3d errno: %3d\n",error,my_errno); + goto err; + } + print_record(read_record,maria_position(file)," maria_rnext_same\n"); + row_count++; + } + if (!silent) + printf(" %d rows\n",row_count); + + if (!silent) + printf("- Test maria_rfirst then a sequence of maria_rnext\n"); + + error=maria_rfirst(file,read_record,0); + if (error) + { + fprintf(stderr, "maria_rfirst: %3d errno: %3d\n",error,my_errno); + goto err; + } + row_count=1; + print_record(read_record,maria_position(file)," maria_frirst\n"); + + for (i=0;i<nrecords;i++) + { + if ((error=maria_rnext(file,read_record,0))) + { + if (error==HA_ERR_END_OF_FILE) + break; + fprintf(stderr, "maria_next: %3d errno: %3d\n",error,my_errno); + goto err; + } + print_record(read_record,maria_position(file)," maria_rnext\n"); + row_count++; + } + if (!silent) + printf(" %d rows\n",row_count); + + if (!silent) + printf("- Test maria_records_in_range()\n"); + + create_record1(record, nrecords*4/5); + print_record(record,0,"\n"); + + range.key= record+1; + range.length= 1000; /* Big enough */ + range.flag= HA_READ_MBR_INTERSECT; + hrows= maria_records_in_range(file,0, &range, (key_range*) 0); + if (!silent) + printf(" %ld rows\n", (long) hrows); + +end: + maria_scan_end(file); + if (die_in_middle_of_transaction) + { + /* see similar code in ma_test2.c for comments */ + switch (die_in_middle_of_transaction) { + case 1: + _ma_flush_table_files(file, MARIA_FLUSH_DATA | MARIA_FLUSH_INDEX, + FLUSH_RELEASE, FLUSH_RELEASE); + break; + case 2: + if (translog_flush(file->trn->undo_lsn)) + goto err; + break; + case 3: + break; + case 4: + _ma_flush_table_files(file, MARIA_FLUSH_DATA, FLUSH_RELEASE, + FLUSH_RELEASE); + if (translog_flush(file->trn->undo_lsn)) + goto err; + break; + } + if (!silent) + printf("Dying on request without maria_commit()/maria_close()\n"); + exit(0); + } + if (maria_commit(file)) + goto err; + if (maria_close(file)) goto err; + maria_end(); + my_end(MY_CHECK_ERROR); + + return 0; + +err: + fprintf(stderr, "got error: %3d when using maria-database\n",my_errno); + return 1; /* skip warning */ +} + + + +static int read_with_pos (MARIA_HA * file) +{ + int error; + int i; + uchar read_record[MAX_REC_LENGTH]; + + if (!silent) + printf("- Reading rows with position\n"); + for (i=0;;i++) + { + my_errno=0; + bzero((char*) read_record,MAX_REC_LENGTH); + error=maria_scan(file,read_record); + if (error) + { + if (error==HA_ERR_END_OF_FILE) + break; + if (error==HA_ERR_RECORD_DELETED) + continue; + fprintf(stderr, "pos: %2d maria_rrnd: %3d errno: %3d\n", i, error, + my_errno); + return error; + } + print_record(read_record,maria_position(file),"\n"); + } + return 0; +} + + +#ifdef NOT_USED +static void bprint_record(char * record, + my_off_t offs __attribute__((unused)), + const char * tail) +{ + int i; + char * pos; + if (silent) + return; + i=(unsigned char)record[0]; + printf("%02X ",i); + + for( pos=record+1, i=0; i<32; i++,pos++){ + int b=(unsigned char)*pos; + printf("%02X",b); + } + printf("%s",tail); +} +#endif + + +static void print_record(uchar *record, + my_off_t offs __attribute__((unused)), + const char * tail) +{ + int i; + uchar *pos; + double c; + + if (silent) + return; + printf(" rec=(%d)",(unsigned char)record[0]); + for ( pos=record+1, i=0; i<2*ndims; i++) + { + memcpy(&c,pos,sizeof(c)); + float8get(c,pos); + printf(" %.14g ",c); + pos+=sizeof(c); + } + printf("pos=%ld",(long int)offs); + printf("%s",tail); +} + + + +static void create_record1(uchar *record, uint rownr) +{ + int i; + uchar *pos; + double c=rownr+10; + + bzero((char*) record,MAX_REC_LENGTH); + record[0]=0x01; /* DEL marker */ + + for ( pos=record+1, i=0; i<2*ndims; i++) + { + memcpy(pos,&c,sizeof(c)); + float8store(pos,c); + pos+=sizeof(c); + } +} + +#ifdef NOT_USED + +static void create_record0(char *record,uint rownr) +{ + int i; + char * pos; + double c=rownr+10; + double c0=0; + + bzero((char*) record,MAX_REC_LENGTH); + record[0]=0x01; /* DEL marker */ + + for ( pos=record+1, i=0; i<ndims; i++) + { + memcpy(pos,&c0,sizeof(c0)); + float8store(pos,c0); + pos+=sizeof(c0); + memcpy(pos,&c,sizeof(c)); + float8store(pos,c); + pos+=sizeof(c); + } +} + +#endif + +static void create_record(uchar *record, uint rownr) +{ + int i; + uchar *pos; + double *data= rt_data+rownr*4; + record[0]=0x01; /* DEL marker */ + for ( pos=record+1, i=0; i<ndims*2; i++) + { + float8store(pos,data[i]); + pos+=8; + } +} + + +static struct my_option my_long_options[] = +{ + {"checkpoint", 'H', "Checkpoint at specified stage", (uchar**) &checkpoint, + (uchar**) &checkpoint, 0, GET_INT, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + {"checksum", 'c', "Undocumented", + 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0}, +#ifndef DBUG_OFF + {"debug", '#', "Undocumented", + 0, 0, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, +#endif + {"help", '?', "Display help and exit", + 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"row-fixed-size", 'S', "Fixed size records", + 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"rows-in-block", 'M', "Store rows in block format", + 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"silent", 's', "Undocumented", + (uchar**) &silent, (uchar**) &silent, 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, + 0, 0}, + {"testflag", 't', "Stop test at specified stage", (uchar**) &testflag, + (uchar**) &testflag, 0, GET_INT, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + {"test-undo", 'A', + "Abort hard. Used for testing recovery with undo", + (uchar**) &die_in_middle_of_transaction, + (uchar**) &die_in_middle_of_transaction, + 0, GET_INT, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + {"transactional", 'T', + "Test in transactional mode. (Only works with block format)", + (uchar**) &transactional, (uchar**) &transactional, 0, GET_BOOL, NO_ARG, + 0, 0, 0, 0, 0, 0}, + {"versioning", 'C', "Use row versioning (only works with block format)", + (uchar**) &opt_versioning, (uchar**) &opt_versioning, 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))) +{ + switch(optid) { + case 'c': + create_flag|= HA_CREATE_CHECKSUM | HA_CREATE_PAGE_CHECKSUM; + break; + case 'M': + record_type= BLOCK_RECORD; + break; + case 'S': + record_type= STATIC_RECORD; + break; + case '#': + DBUG_PUSH(argument); + break; + case '?': + usage(); + exit(1); + } + return 0; +} + + +/* Read options */ + +static void get_options(int argc, char *argv[]) +{ + int ho_error; + + if ((ho_error=handle_options(&argc, &argv, my_long_options, get_one_option))) + exit(ho_error); + + return; +} /* get options */ + + +static void usage() +{ + printf("Usage: %s [options]\n\n", my_progname); + my_print_help(my_long_options); + my_print_variables(my_long_options); +} + +#else +int main(int argc __attribute__((unused)),char *argv[] __attribute__((unused))) +{ + exit(0); +} +#endif /*HAVE_RTREE_KEYS*/ diff --git a/storage/maria/ma_scan.c b/storage/maria/ma_scan.c new file mode 100644 index 00000000000..cbac463a2c8 --- /dev/null +++ b/storage/maria/ma_scan.c @@ -0,0 +1,74 @@ +/* Copyright (C) 2006 MySQL AB & MySQL Finland AB & TCX DataKonsult 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 */ + +/* Read through all rows sequntially */ + +#include "maria_def.h" + +int maria_scan_init(register MARIA_HA *info) +{ + DBUG_ENTER("maria_scan_init"); + + info->cur_row.nextpos= info->s->pack.header_length; /* Read first record */ + info->lastinx= -1; /* Can't forward or backward */ + if (info->opt_flag & WRITE_CACHE_USED && flush_io_cache(&info->rec_cache)) + DBUG_RETURN(my_errno); + + if ((*info->s->scan_init)(info)) + DBUG_RETURN(my_errno); + DBUG_RETURN(0); +} + +/* + Read a row based on position. + + SYNOPSIS + maria_scan() + info Maria handler + record Read data here + + RETURN + 0 ok + HA_ERR_END_OF_FILE End of file + HA_ERR_RECORD_DELETED Record was deleted (can only happen for static rec) + # Error code +*/ + +int maria_scan(MARIA_HA *info, uchar *record) +{ + DBUG_ENTER("maria_scan"); + /* Init all but update-flag */ + info->update&= (HA_STATE_CHANGED | HA_STATE_ROW_CHANGED); + DBUG_RETURN((*info->s->scan)(info, record, info->cur_row.nextpos, 1)); +} + + +void maria_scan_end(MARIA_HA *info) +{ + (*info->s->scan_end)(info); +} + + +int _ma_def_scan_remember_pos(MARIA_HA *info, MARIA_RECORD_POS *lastpos) +{ + *lastpos= info->cur_row.lastpos; + return 0; +} + + +void _ma_def_scan_restore_pos(MARIA_HA *info, MARIA_RECORD_POS lastpos) +{ + info->cur_row.nextpos= lastpos; +} diff --git a/storage/maria/ma_search.c b/storage/maria/ma_search.c new file mode 100644 index 00000000000..a50f3c49a26 --- /dev/null +++ b/storage/maria/ma_search.c @@ -0,0 +1,2357 @@ +/* Copyright (C) 2006 MySQL AB & MySQL Finland AB & TCX DataKonsult 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 */ + +/* key handling functions */ + +#include "ma_fulltext.h" +#include "m_ctype.h" + +static my_bool _ma_get_prev_key(MARIA_KEY *key, MARIA_PAGE *ma_page, + uchar *keypos); + + +/* Check that new index is ok */ + +int _ma_check_index(MARIA_HA *info, int inx) +{ + if (inx < 0 || ! maria_is_key_active(info->s->state.key_map, inx)) + { + my_errno=HA_ERR_WRONG_INDEX; + return -1; + } + if (info->lastinx != inx) /* Index changed */ + { + info->lastinx = inx; + info->page_changed=1; + info->update= ((info->update & (HA_STATE_CHANGED | HA_STATE_ROW_CHANGED)) | + HA_STATE_NEXT_FOUND | HA_STATE_PREV_FOUND); + } + if (info->opt_flag & WRITE_CACHE_USED && flush_io_cache(&info->rec_cache)) + return(-1); + return(inx); +} /* _ma_check_index */ + + +/** + @breif Search after row by a key + + @note + Position to row is stored in info->lastpos + + @return + @retval 0 ok (key found) + @retval -1 Not found + @retval 1 If one should continue search on higher level +*/ + +int _ma_search(register MARIA_HA *info, MARIA_KEY *key, uint32 nextflag, + register my_off_t pos) +{ + my_bool last_key_not_used; + int error,flag; + uint page_flag, nod_flag, used_length; + uchar *keypos,*maxpos; + uchar lastkey[MARIA_MAX_KEY_BUFF]; + MARIA_KEYDEF *keyinfo= key->keyinfo; + MARIA_PAGE page; + DBUG_ENTER("_ma_search"); + DBUG_PRINT("enter",("pos: %lu nextflag: %u lastpos: %lu", + (ulong) pos, nextflag, (ulong) info->cur_row.lastpos)); + DBUG_EXECUTE("key", _ma_print_key(DBUG_FILE, key);); + + if (pos == HA_OFFSET_ERROR) + { + my_errno=HA_ERR_KEY_NOT_FOUND; /* Didn't find key */ + info->cur_row.lastpos= HA_OFFSET_ERROR; + if (!(nextflag & (SEARCH_SMALLER | SEARCH_BIGGER | SEARCH_LAST))) + DBUG_RETURN(-1); /* Not found ; return error */ + DBUG_RETURN(1); /* Search at upper levels */ + } + + if (_ma_fetch_keypage(&page, info, keyinfo, pos, + PAGECACHE_LOCK_LEFT_UNLOCKED, + DFLT_INIT_HITS, info->keyread_buff, + test(!(nextflag & SEARCH_SAVE_BUFF)))) + goto err; + DBUG_DUMP("page", page.buff, page.size); + + flag= (*keyinfo->bin_search)(key, &page, nextflag, &keypos, lastkey, + &last_key_not_used); + if (flag == MARIA_FOUND_WRONG_KEY) + DBUG_RETURN(-1); + page_flag= page.flag; + used_length= page.size; + nod_flag= page.node; + maxpos= page.buff + used_length -1; + + if (flag) + { + if ((error= _ma_search(info, key, nextflag, + _ma_kpos(nod_flag,keypos))) <= 0) + DBUG_RETURN(error); + + if (flag >0) + { + if (nextflag & (SEARCH_SMALLER | SEARCH_LAST) && + keypos == page.buff + info->s->keypage_header + nod_flag) + DBUG_RETURN(1); /* Bigger than key */ + } + else if (nextflag & SEARCH_BIGGER && keypos >= maxpos) + DBUG_RETURN(1); /* Smaller than key */ + } + else + { + /* Found matching key */ + if ((nextflag & SEARCH_FIND) && nod_flag && + ((keyinfo->flag & (HA_NOSAME | HA_NULL_PART)) != HA_NOSAME || + (key->flag & SEARCH_PART_KEY) || info->s->base.born_transactional)) + { + if ((error= _ma_search(info, key, (nextflag | SEARCH_FIND) & + ~(SEARCH_BIGGER | SEARCH_SMALLER | SEARCH_LAST), + _ma_kpos(nod_flag,keypos))) >= 0 || + my_errno != HA_ERR_KEY_NOT_FOUND) + DBUG_RETURN(error); + info->last_keypage= HA_OFFSET_ERROR; /* Buffer not in mem */ + } + } + if (pos != info->last_keypage) + { + uchar *old_buff= page.buff; + if (_ma_fetch_keypage(&page, info, keyinfo, pos, + PAGECACHE_LOCK_LEFT_UNLOCKED,DFLT_INIT_HITS, + info->keyread_buff, + test(!(nextflag & SEARCH_SAVE_BUFF)))) + goto err; + /* Restore position if page buffer moved */ + keypos= page.buff + (keypos - old_buff); + maxpos= page.buff + (maxpos - old_buff); + } + + info->last_key.keyinfo= keyinfo; + if ((nextflag & (SEARCH_SMALLER | SEARCH_LAST)) && flag != 0) + { + uint not_used[2]; + if (_ma_get_prev_key(&info->last_key, &page, keypos)) + goto err; + /* + We have to use key->flag >> 1 here to transform + SEARCH_PAGE_KEY_HAS_TRANSID to SEARCH_USER_KEY_HAS_TRANSID + */ + if (!(nextflag & SEARCH_SMALLER) && + ha_key_cmp(keyinfo->seg, info->last_key.data, key->data, + key->data_length + key->ref_length, + SEARCH_FIND | (key->flag >> 1) | info->last_key.flag, + not_used)) + { + my_errno=HA_ERR_KEY_NOT_FOUND; /* Didn't find key */ + goto err; + } + } + else + { + /* Set info->last_key to temporarily point to last key value */ + info->last_key.data= lastkey; + /* Get key value (if not packed key) and position after key */ + if (!(*keyinfo->get_key)(&info->last_key, page_flag, nod_flag, &keypos)) + goto err; + memcpy(info->lastkey_buff, lastkey, + info->last_key.data_length + info->last_key.ref_length); + info->last_key.data= info->lastkey_buff; + } + info->cur_row.lastpos= _ma_row_pos_from_key(&info->last_key); + info->cur_row.trid= _ma_trid_from_key(&info->last_key); + /* Save position for a possible read next / previous */ + info->int_keypos= info->keyread_buff + (keypos - page.buff); + info->int_maxpos= info->keyread_buff + (maxpos - page.buff); + info->int_nod_flag=nod_flag; + info->int_keytree_version=keyinfo->version; + info->last_search_keypage=info->last_keypage; + info->page_changed=0; + /* Set marker that buffer was used (Marker for mi_search_next()) */ + info->keyread_buff_used= (info->keyread_buff != page.buff); + + DBUG_PRINT("exit",("found key at %lu",(ulong) info->cur_row.lastpos)); + DBUG_RETURN(0); + +err: + DBUG_PRINT("exit",("Error: %d",my_errno)); + info->cur_row.lastpos= HA_OFFSET_ERROR; + info->page_changed=1; + DBUG_RETURN (-1); +} /* _ma_search */ + + +/* + Search after key in page-block + + @fn _ma_bin_search + @param key Search after this key + @param page Start of data page + @param comp_flag How key should be compared + @param ret_pos + @param buff Buffer for holding a key (not used here) + @param last_key + + @note + If keys are packed, then smaller or identical key is stored in buff + + @return + @retval <0, 0 , >0 depending on if if found is smaller, equal or bigger than + 'key' + @retval ret_pos Points to where the identical or bigger key starts + @retval last_key Set to 1 if key is the last key in the page. +*/ + +int _ma_bin_search(const MARIA_KEY *key, const MARIA_PAGE *ma_page, + uint32 comp_flag, uchar **ret_pos, + uchar *buff __attribute__((unused)), my_bool *last_key) +{ + int flag; + uint page_flag; + uint start, mid, end, save_end, totlength, nod_flag; + uint not_used[2]; + MARIA_KEYDEF *keyinfo= key->keyinfo; + MARIA_SHARE *share= keyinfo->share; + uchar *page; + DBUG_ENTER("_ma_bin_search"); + + LINT_INIT(flag); + + page_flag= ma_page->flag; + if (page_flag & KEYPAGE_FLAG_HAS_TRANSID) + { + /* Keys have varying length, can't use binary search */ + DBUG_RETURN(_ma_seq_search(key, ma_page, comp_flag, ret_pos, buff, + last_key)); + } + + nod_flag= ma_page->node; + totlength= keyinfo->keylength + nod_flag; + DBUG_ASSERT(ma_page->size >= share->keypage_header + nod_flag + totlength); + + start=0; + mid=1; + save_end= end= ((ma_page->size - nod_flag - share->keypage_header) / + totlength-1); + DBUG_PRINT("test",("page_length: %u end: %u", ma_page->size, end)); + page= ma_page->buff + share->keypage_header + nod_flag; + + while (start != end) + { + mid= (start+end)/2; + if ((flag=ha_key_cmp(keyinfo->seg, page + (uint) mid * totlength, + key->data, key->data_length + key->ref_length, + comp_flag, not_used)) + >= 0) + end=mid; + else + start=mid+1; + } + if (mid != start) + flag=ha_key_cmp(keyinfo->seg, page + (uint) start * totlength, + key->data, key->data_length + key->ref_length, comp_flag, + not_used); + if (flag < 0) + start++; /* point at next, bigger key */ + *ret_pos= (page + (uint) start * totlength); + *last_key= end == save_end; + DBUG_PRINT("exit",("flag: %d keypos: %d",flag,start)); + DBUG_RETURN(flag); +} /* _ma_bin_search */ + + +/** + Locate a packed key in a key page. + + @fn _ma_seq_search() + @param key Search key. + @param page Key page (beginning). + @param comp_flag Search flags like SEARCH_SAME etc. + @param ret_pos + @param buff Buffer for holding temp keys + @param last_key + + @description + Used instead of _ma_bin_search() when key is packed. + Puts smaller or identical key in buff. + Key is searched sequentially. + + @todo + Don't copy key to buffer if we are not using key with prefix packing + + @return + @retval > 0 Key in 'buff' is smaller than search key. + @retval 0 Key in 'buff' is identical to search key. + @retval < 0 Not found. + + @retval ret_pos Points to where the identical or bigger key starts + @retval last_key Set to 1 if key is the last key in the page + @retval buff Copy of previous or identical unpacked key +*/ + +int _ma_seq_search(const MARIA_KEY *key, const MARIA_PAGE *ma_page, + uint32 comp_flag, uchar **ret_pos, + uchar *buff, my_bool *last_key) +{ + int flag; + uint page_flag, nod_flag, length, not_used[2]; + uchar t_buff[MARIA_MAX_KEY_BUFF], *end; + uchar *page; + MARIA_KEYDEF *keyinfo= key->keyinfo; + MARIA_SHARE *share= keyinfo->share; + MARIA_KEY tmp_key; + DBUG_ENTER("_ma_seq_search"); + + LINT_INIT(flag); + LINT_INIT(length); + + page_flag= ma_page->flag; + nod_flag= ma_page->node; + page= ma_page->buff; + end= page + ma_page->size; + page+= share->keypage_header + nod_flag; + *ret_pos= page; + t_buff[0]=0; /* Avoid bugs */ + + tmp_key.data= t_buff; + tmp_key.keyinfo= keyinfo; + while (page < end) + { + length=(*keyinfo->get_key)(&tmp_key, page_flag, nod_flag, &page); + if (length == 0 || page > end) + { + maria_print_error(share, HA_ERR_CRASHED); + my_errno=HA_ERR_CRASHED; + DBUG_PRINT("error", + ("Found wrong key: length: %u page: 0x%lx end: 0x%lx", + length, (long) page, (long) end)); + DBUG_RETURN(MARIA_FOUND_WRONG_KEY); + } + if ((flag= ha_key_cmp(keyinfo->seg, t_buff, key->data, + key->data_length + key->ref_length, + comp_flag | tmp_key.flag, + not_used)) >= 0) + break; + DBUG_PRINT("loop_extra",("page: 0x%lx key: '%s' flag: %d", + (long) page, t_buff, flag)); + memcpy(buff,t_buff,length); + *ret_pos=page; + } + if (flag == 0) + memcpy(buff,t_buff,length); /* Result is first key */ + *last_key= page == end; + DBUG_PRINT("exit",("flag: %d ret_pos: 0x%lx", flag, (long) *ret_pos)); + DBUG_RETURN(flag); +} /* _ma_seq_search */ + + +/** + Search for key on key page with string prefix compression + + @notes + This is an optimized function compared to calling _ma_get_pack_key() + for each key in the buffer + + Same interface as for _ma_seq_search() +*/ + +int _ma_prefix_search(const MARIA_KEY *key, const MARIA_PAGE *ma_page, + uint32 nextflag, uchar **ret_pos, uchar *buff, + my_bool *last_key) +{ + /* + my_flag is raw comparison result to be changed according to + SEARCH_NO_FIND,SEARCH_LAST and HA_REVERSE_SORT flags. + flag is the value returned by ha_key_cmp and as treated as final + */ + int flag=0, my_flag=-1; + uint nod_flag, length, len, matched, cmplen, kseg_len; + uint page_flag, prefix_len,suffix_len; + int key_len_skip, seg_len_pack, key_len_left; + uchar *end, *vseg, *saved_vseg, *saved_from; + uchar *page; + uchar tt_buff[MARIA_MAX_KEY_BUFF+2], *t_buff=tt_buff+2; + uchar *saved_to; + const uchar *kseg; + uint saved_length=0, saved_prefix_len=0; + uint length_pack; + MARIA_KEYDEF *keyinfo= key->keyinfo; + MARIA_SHARE *share= keyinfo->share; + uchar *sort_order= keyinfo->seg->charset->sort_order; + DBUG_ENTER("_ma_prefix_search"); + + LINT_INIT(length); + LINT_INIT(prefix_len); + LINT_INIT(seg_len_pack); + LINT_INIT(saved_from); + LINT_INIT(saved_to); + LINT_INIT(saved_vseg); + + t_buff[0]=0; /* Avoid bugs */ + page_flag= ma_page->flag; + nod_flag= ma_page->node; + page_flag&= KEYPAGE_FLAG_HAS_TRANSID; /* For faster test in loop */ + page= ma_page->buff; + end= page + ma_page->size; + page+= share->keypage_header + nod_flag; + *ret_pos= page; + kseg= key->data; + + get_key_pack_length(kseg_len, length_pack, kseg); + key_len_skip=length_pack+kseg_len; + key_len_left=(int) (key->data_length + key->ref_length) - (int) key_len_skip; + /* If key_len is 0, then length_pack is 1, then key_len_left is -1. */ + cmplen= ((key_len_left>=0) ? kseg_len : + (key->data_length + key->ref_length - length_pack)); + DBUG_PRINT("info",("key: '%.*s'",kseg_len,kseg)); + + /* + Keys are compressed the following way: + + If the max length of first key segment <= 127 bytes the prefix is + 1 uchar else it's 2 byte + + (prefix) length The high bit is set if this is a prefix for the prev key. + [suffix length] Packed length of suffix if the previous was a prefix. + (suffix) data Key data bytes (past the common prefix or whole segment). + [next-key-seg] Next key segments (([packed length], data), ...) + pointer Reference to the data file (last_keyseg->length). + */ + + matched=0; /* how many char's from prefix were alredy matched */ + len=0; /* length of previous key unpacked */ + + while (page < end) + { + uint packed= *page & 128; + uint key_flag; + + vseg= page; + if (keyinfo->seg->length >= 127) + { + suffix_len=mi_uint2korr(vseg) & 32767; + vseg+=2; + } + else + suffix_len= *vseg++ & 127; + + if (packed) + { + if (suffix_len == 0) + { + /* == 0x80 or 0x8000, same key, prefix length == old key length. */ + prefix_len=len; + } + else + { + /* > 0x80 or 0x8000, this is prefix lgt, packed suffix lgt follows. */ + prefix_len=suffix_len; + get_key_length(suffix_len,vseg); + } + } + else + { + /* Not packed. No prefix used from last key. */ + prefix_len=0; + } + + len=prefix_len+suffix_len; + seg_len_pack=get_pack_length(len); + t_buff=tt_buff+3-seg_len_pack; + store_key_length(t_buff,len); + + if (prefix_len > saved_prefix_len) + memcpy(t_buff+seg_len_pack+saved_prefix_len,saved_vseg, + prefix_len-saved_prefix_len); + saved_vseg=vseg; + saved_prefix_len=prefix_len; + + DBUG_PRINT("loop",("page: '%.*s%.*s'",prefix_len,t_buff+seg_len_pack, + suffix_len,vseg)); + { + /* Calculate length of one key */ + uchar *from= vseg+suffix_len; + HA_KEYSEG *keyseg; + + for (keyseg=keyinfo->seg+1 ; keyseg->type ; keyseg++ ) + { + if (keyseg->flag & HA_NULL_PART) + { + if (!(*from++)) + continue; + } + if (keyseg->flag & (HA_VAR_LENGTH_PART | HA_BLOB_PART | HA_SPACE_PACK)) + { + uint key_part_length; + get_key_length(key_part_length,from); + from+= key_part_length; + } + else + from+= keyseg->length; + } + from+= keyseg->length; + key_flag=0; + + if (page_flag && key_has_transid(from-1)) + { + from+= transid_packed_length(from); + key_flag= SEARCH_PAGE_KEY_HAS_TRANSID; + } + page= from + nod_flag; + length= (uint) (from-vseg); + } + + if (page > end) + { + maria_print_error(share, HA_ERR_CRASHED); + my_errno=HA_ERR_CRASHED; + DBUG_PRINT("error", + ("Found wrong key: length: %u page: 0x%lx end: %lx", + length, (long) page, (long) end)); + DBUG_RETURN(MARIA_FOUND_WRONG_KEY); + } + + if (matched >= prefix_len) + { + /* We have to compare. But we can still skip part of the key */ + uint left; + const uchar *k= kseg+prefix_len; + + /* + If prefix_len > cmplen then we are in the end-space comparison + phase. Do not try to acces the key any more ==> left= 0. + */ + left= ((len <= cmplen) ? suffix_len : + ((prefix_len < cmplen) ? cmplen - prefix_len : 0)); + + matched=prefix_len+left; + + if (sort_order) + { + for (my_flag=0;left;left--) + if ((my_flag= (int) sort_order[*vseg++] - (int) sort_order[*k++])) + break; + } + else + { + for (my_flag=0;left;left--) + if ((my_flag= (int) *vseg++ - (int) *k++)) + break; + } + + if (my_flag>0) /* mismatch */ + break; + if (my_flag==0) /* match */ + { + /* + ** len cmplen seg_left_len more_segs + ** < matched=len; continue search + ** > = prefix ? found : (matched=len; + * continue search) + ** > < - ok, found + ** = < - ok, found + ** = = - ok, found + ** = = + next seg + */ + if (len < cmplen) + { + if ((keyinfo->seg->type != HA_KEYTYPE_TEXT && + keyinfo->seg->type != HA_KEYTYPE_VARTEXT1 && + keyinfo->seg->type != HA_KEYTYPE_VARTEXT2)) + my_flag= -1; + else + { + /* We have to compare k and vseg as if they were space extended */ + const uchar *k_end= k+ (cmplen - len); + for ( ; k < k_end && *k == ' '; k++) ; + if (k == k_end) + goto cmp_rest; /* should never happen */ + if ((uchar) *k < (uchar) ' ') + { + my_flag= 1; /* Compared string is smaller */ + break; + } + my_flag= -1; /* Continue searching */ + } + } + else if (len > cmplen) + { + uchar *vseg_end; + if ((nextflag & SEARCH_PREFIX) && key_len_left == 0) + goto fix_flag; + + /* We have to compare k and vseg as if they were space extended */ + for (vseg_end= vseg + (len-cmplen) ; + vseg < vseg_end && *vseg == (uchar) ' '; + vseg++, matched++) ; + DBUG_ASSERT(vseg < vseg_end); + + if ((uchar) *vseg > (uchar) ' ') + { + my_flag= 1; /* Compared string is smaller */ + break; + } + my_flag= -1; /* Continue searching */ + } + else + { + cmp_rest: + if (key_len_left>0) + { + uint not_used[2]; + if ((flag = ha_key_cmp(keyinfo->seg+1,vseg, + k, key_len_left, nextflag | key_flag, + not_used)) >= 0) + break; + } + else + { + /* + at this line flag==-1 if the following lines were already + visited and 0 otherwise, i.e. flag <=0 here always !!! + */ + fix_flag: + DBUG_ASSERT(flag <= 0); + if (nextflag & (SEARCH_NO_FIND | SEARCH_LAST)) + flag=(nextflag & (SEARCH_BIGGER | SEARCH_LAST)) ? -1 : 1; + if (flag>=0) + break; + } + } + } + matched-=left; + } + /* else (matched < prefix_len) ---> do nothing. */ + + memcpy(buff,t_buff,saved_length=seg_len_pack+prefix_len); + saved_to= buff+saved_length; + saved_from= saved_vseg; + saved_length=length; + *ret_pos=page; + } + if (my_flag) + flag=(keyinfo->seg->flag & HA_REVERSE_SORT) ? -my_flag : my_flag; + if (flag == 0) + { + memcpy(buff,t_buff,saved_length=seg_len_pack+prefix_len); + saved_to= buff+saved_length; + saved_from= saved_vseg; + saved_length=length; + } + if (saved_length) + memcpy(saved_to, saved_from, saved_length); + + *last_key= page == end; + + DBUG_PRINT("exit",("flag: %d ret_pos: 0x%lx", flag, (long) *ret_pos)); + DBUG_RETURN(flag); +} /* _ma_prefix_search */ + + +/* Get pos to a key_block */ + +my_off_t _ma_kpos(uint nod_flag, const uchar *after_key) +{ + after_key-=nod_flag; + switch (nod_flag) { +#if SIZEOF_OFF_T > 4 + case 7: + return mi_uint7korr(after_key)*maria_block_size; + case 6: + return mi_uint6korr(after_key)*maria_block_size; + case 5: + return mi_uint5korr(after_key)*maria_block_size; +#else + case 7: + after_key++; + case 6: + after_key++; + case 5: + after_key++; +#endif + case 4: + return ((my_off_t) mi_uint4korr(after_key))*maria_block_size; + case 3: + return ((my_off_t) mi_uint3korr(after_key))*maria_block_size; + case 2: + return (my_off_t) (mi_uint2korr(after_key)*maria_block_size); + case 1: + return (uint) (*after_key)*maria_block_size; + case 0: /* At leaf page */ + default: /* Impossible */ + return(HA_OFFSET_ERROR); + } +} /* _kpos */ + + +/* Save pos to a key_block */ + +void _ma_kpointer(register MARIA_HA *info, register uchar *buff, my_off_t pos) +{ + pos/=maria_block_size; + switch (info->s->base.key_reflength) { +#if SIZEOF_OFF_T > 4 + case 7: mi_int7store(buff,pos); break; + case 6: mi_int6store(buff,pos); break; + case 5: mi_int5store(buff,pos); break; +#else + case 7: *buff++=0; + /* fall trough */ + case 6: *buff++=0; + /* fall trough */ + case 5: *buff++=0; + /* fall trough */ +#endif + case 4: mi_int4store(buff,pos); break; + case 3: mi_int3store(buff,pos); break; + case 2: mi_int2store(buff,(uint) pos); break; + case 1: buff[0]= (uchar) pos; break; + default: abort(); /* impossible */ + } +} /* _ma_kpointer */ + + +/* Calc pos to a data-record from a key */ + +MARIA_RECORD_POS _ma_row_pos_from_key(const MARIA_KEY *key) +{ + my_off_t pos; + const uchar *after_key= key->data + key->data_length; + MARIA_SHARE *share= key->keyinfo->share; + switch (share->rec_reflength) { +#if SIZEOF_OFF_T > 4 + case 8: pos= (my_off_t) mi_uint8korr(after_key); break; + case 7: pos= (my_off_t) mi_uint7korr(after_key); break; + case 6: pos= (my_off_t) mi_uint6korr(after_key); break; + case 5: pos= (my_off_t) mi_uint5korr(after_key); break; +#else + case 8: pos= (my_off_t) mi_uint4korr(after_key+4); break; + case 7: pos= (my_off_t) mi_uint4korr(after_key+3); break; + case 6: pos= (my_off_t) mi_uint4korr(after_key+2); break; + case 5: pos= (my_off_t) mi_uint4korr(after_key+1); break; +#endif + case 4: pos= (my_off_t) mi_uint4korr(after_key); break; + case 3: pos= (my_off_t) mi_uint3korr(after_key); break; + case 2: pos= (my_off_t) mi_uint2korr(after_key); break; + default: + pos=0L; /* Shut compiler up */ + } + return (*share->keypos_to_recpos)(share, pos); +} + + +/** + Get trid from a key + + @param key Maria key read from a page + + @retval 0 If key doesn't have a trid + @retval trid +*/ + +TrID _ma_trid_from_key(const MARIA_KEY *key) +{ + if (!(key->flag & (SEARCH_PAGE_KEY_HAS_TRANSID | + SEARCH_USER_KEY_HAS_TRANSID))) + return 0; + return transid_get_packed(key->keyinfo->share, + key->data + key->data_length + + key->keyinfo->share->rec_reflength); +} + + +/* Calc position from a record pointer ( in delete link chain ) */ + +MARIA_RECORD_POS _ma_rec_pos(MARIA_SHARE *share, uchar *ptr) +{ + my_off_t pos; + switch (share->rec_reflength) { +#if SIZEOF_OFF_T > 4 + case 8: + pos= (my_off_t) mi_uint8korr(ptr); + if (pos == HA_OFFSET_ERROR) + return HA_OFFSET_ERROR; /* end of list */ + break; + case 7: + pos= (my_off_t) mi_uint7korr(ptr); + if (pos == (((my_off_t) 1) << 56) -1) + return HA_OFFSET_ERROR; /* end of list */ + break; + case 6: + pos= (my_off_t) mi_uint6korr(ptr); + if (pos == (((my_off_t) 1) << 48) -1) + return HA_OFFSET_ERROR; /* end of list */ + break; + case 5: + pos= (my_off_t) mi_uint5korr(ptr); + if (pos == (((my_off_t) 1) << 40) -1) + return HA_OFFSET_ERROR; /* end of list */ + break; +#else + case 8: + case 7: + case 6: + case 5: + ptr+= (share->rec_reflength-4); + /* fall through */ +#endif + case 4: + pos= (my_off_t) mi_uint4korr(ptr); + if (pos == (my_off_t) (uint32) ~0L) + return HA_OFFSET_ERROR; + break; + case 3: + pos= (my_off_t) mi_uint3korr(ptr); + if (pos == (my_off_t) (1 << 24) -1) + return HA_OFFSET_ERROR; + break; + case 2: + pos= (my_off_t) mi_uint2korr(ptr); + if (pos == (my_off_t) (1 << 16) -1) + return HA_OFFSET_ERROR; + break; + default: abort(); /* Impossible */ + } + return (*share->keypos_to_recpos)(share, pos); +} + + +/* save position to record */ + +void _ma_dpointer(MARIA_SHARE *share, uchar *buff, my_off_t pos) +{ + if (pos != HA_OFFSET_ERROR) + pos= (*share->recpos_to_keypos)(share, pos); + + switch (share->rec_reflength) { +#if SIZEOF_OFF_T > 4 + case 8: mi_int8store(buff,pos); break; + case 7: mi_int7store(buff,pos); break; + case 6: mi_int6store(buff,pos); break; + case 5: mi_int5store(buff,pos); break; +#else + case 8: *buff++=0; + /* fall trough */ + case 7: *buff++=0; + /* fall trough */ + case 6: *buff++=0; + /* fall trough */ + case 5: *buff++=0; + /* fall trough */ +#endif + case 4: mi_int4store(buff,pos); break; + case 3: mi_int3store(buff,pos); break; + case 2: mi_int2store(buff,(uint) pos); break; + default: abort(); /* Impossible */ + } +} /* _ma_dpointer */ + + +my_off_t _ma_static_keypos_to_recpos(MARIA_SHARE *share, my_off_t pos) +{ + return pos * share->base.pack_reclength; +} + + +my_off_t _ma_static_recpos_to_keypos(MARIA_SHARE *share, my_off_t pos) +{ + return pos / share->base.pack_reclength; +} + +my_off_t _ma_transparent_recpos(MARIA_SHARE *share __attribute__((unused)), + my_off_t pos) +{ + return pos; +} + +my_off_t _ma_transaction_keypos_to_recpos(MARIA_SHARE *share + __attribute__((unused)), + my_off_t pos) +{ + /* We need one bit to store if there is transid's after position */ + return pos >> 1; +} + +my_off_t _ma_transaction_recpos_to_keypos(MARIA_SHARE *share + __attribute__((unused)), + my_off_t pos) +{ + return pos << 1; +} + +/* + @brief Get key from key-block + + @param key Should contain previous key. Will contain new key + @param page_flag Flag on page block + @param nod_flag Is set to nod length if we on nod + @param page Points at previous key; Its advanced to point at next key + + @notes + Same as _ma_get_key but used with fixed length keys + + @return + @retval key_length + length of data pointer (without nod length) + */ + +uint _ma_get_static_key(MARIA_KEY *key, uint page_flag, uint nod_flag, + register uchar **page) +{ + register MARIA_KEYDEF *keyinfo= key->keyinfo; + size_t key_length= keyinfo->keylength; + + key->ref_length= keyinfo->share->rec_reflength; + key->data_length= key_length - key->ref_length; + key->flag= 0; + if (page_flag & KEYPAGE_FLAG_HAS_TRANSID) + { + uchar *end= *page + keyinfo->keylength; + if (key_has_transid(end-1)) + { + uint trans_length= transid_packed_length(end); + key->ref_length+= trans_length; + key_length+= trans_length; + key->flag= SEARCH_PAGE_KEY_HAS_TRANSID; + } + } + key_length+= nod_flag; + memcpy(key->data, *page, key_length); + *page+= key_length; + return key_length - nod_flag; +} /* _ma_get_static_key */ + + +/** + Skip over static length key from key-block + + @fn _ma_skip_static_key() + @param key Keyinfo and buffer that can be used + @param nod_flag If nod: Length of node pointer, else zero. + @param key Points at key + + @retval pointer to next key +*/ + +uchar *_ma_skip_static_key(MARIA_KEY *key, uint page_flag, + uint nod_flag, uchar *page) +{ + page+= key->keyinfo->keylength; + if ((page_flag & KEYPAGE_FLAG_HAS_TRANSID) && key_has_transid(page-1)) + page+= transid_packed_length(page); + return page+ nod_flag; +} + + +/* + get key which is packed against previous key or key with a NULL column. + + SYNOPSIS + _ma_get_pack_key() + @param int_key Should contain previous key. Will contain new key + @param page_flag page_flag from page + @param nod_flag If nod: Length of node pointer, else zero. + @param page_pos Points at previous key; Its advanced to point at next key + + @return + @retval key_length + length of data pointer +*/ + +uint _ma_get_pack_key(MARIA_KEY *int_key, uint page_flag, + uint nod_flag, uchar **page_pos) +{ + reg1 HA_KEYSEG *keyseg; + uchar *page= *page_pos; + uint length; + uchar *key= int_key->data; + MARIA_KEYDEF *keyinfo= int_key->keyinfo; + + for (keyseg=keyinfo->seg ; keyseg->type ;keyseg++) + { + if (keyseg->flag & HA_PACK_KEY) + { + /* key with length, packed to previous key */ + uchar *start= key; + uint packed= *page & 128,tot_length,rest_length; + if (keyseg->length >= 127) + { + length=mi_uint2korr(page) & 32767; + page+=2; + } + else + length= *page++ & 127; + + if (packed) + { + if (length > (uint) keyseg->length) + { + maria_print_error(keyinfo->share, HA_ERR_CRASHED); + my_errno=HA_ERR_CRASHED; + return 0; /* Error */ + } + if (length == 0) /* Same key */ + { + if (keyseg->flag & HA_NULL_PART) + *key++=1; /* Can't be NULL */ + get_key_length(length,key); + key+= length; /* Same diff_key as prev */ + if (length > keyseg->length) + { + DBUG_PRINT("error", + ("Found too long null packed key: %u of %u at 0x%lx", + length, keyseg->length, (long) *page_pos)); + DBUG_DUMP("key", *page_pos, 16); + maria_print_error(keyinfo->share, HA_ERR_CRASHED); + my_errno=HA_ERR_CRASHED; + return 0; + } + continue; + } + if (keyseg->flag & HA_NULL_PART) + { + key++; /* Skip null marker*/ + start++; + } + + get_key_length(rest_length,page); + tot_length=rest_length+length; + + /* If the stored length has changed, we must move the key */ + if (tot_length >= 255 && *start != 255) + { + /* length prefix changed from a length of one to a length of 3 */ + bmove_upp(key+length+3, key+length+1, length); + *key=255; + mi_int2store(key+1,tot_length); + key+=3+length; + } + else if (tot_length < 255 && *start == 255) + { + bmove(key+1,key+3,length); + *key=tot_length; + key+=1+length; + } + else + { + store_key_length_inc(key,tot_length); + key+=length; + } + memcpy(key,page,rest_length); + page+=rest_length; + key+=rest_length; + continue; + } + else + { + /* Key that is not packed against previous key */ + if (keyseg->flag & HA_NULL_PART) + { + if (!length--) /* Null part */ + { + *key++=0; + continue; + } + *key++=1; /* Not null */ + } + } + if (length > (uint) keyseg->length) + { + DBUG_PRINT("error",("Found too long packed key: %u of %u at 0x%lx", + length, keyseg->length, (long) *page_pos)); + DBUG_DUMP("key", *page_pos, 16); + maria_print_error(keyinfo->share, HA_ERR_CRASHED); + my_errno=HA_ERR_CRASHED; + return 0; /* Error */ + } + store_key_length_inc(key,length); + } + else + { + if (keyseg->flag & HA_NULL_PART) + { + if (!(*key++ = *page++)) + continue; + } + if (keyseg->flag & + (HA_VAR_LENGTH_PART | HA_BLOB_PART | HA_SPACE_PACK)) + { + uchar *tmp=page; + get_key_length(length,tmp); + length+=(uint) (tmp-page); + } + else + length=keyseg->length; + } + memcpy(key, page,(size_t) length); + key+=length; + page+=length; + } + + int_key->data_length= (key - int_key->data); + int_key->flag= 0; + length= keyseg->length; + if (page_flag & KEYPAGE_FLAG_HAS_TRANSID) + { + uchar *end= page + length; + if (key_has_transid(end-1)) + { + length+= transid_packed_length(end); + int_key->flag= SEARCH_PAGE_KEY_HAS_TRANSID; + } + } + int_key->ref_length= length; + length+= nod_flag; + bmove(key, page, length); + *page_pos= page+length; + + return (int_key->data_length + int_key->ref_length); +} /* _ma_get_pack_key */ + + +/** + skip key which is packed against previous key or key with a NULL column. + + @fn _ma_skip_pack_key() + @param key Keyinfo and buffer that can be used + @param nod_flag If nod: Length of node pointer, else zero. + @param key Points at key + + @note + This is in principle a simpler version of _ma_get_pack_key() + + @retval pointer to next key +*/ + +uchar *_ma_skip_pack_key(MARIA_KEY *key, uint page_flag, + uint nod_flag, uchar *page) +{ + reg1 HA_KEYSEG *keyseg; + for (keyseg= key->keyinfo->seg ; keyseg->type ; keyseg++) + { + if (keyseg->flag & HA_PACK_KEY) + { + /* key with length, packed to previous key */ + uint packed= *page & 128, length; + if (keyseg->length >= 127) + { + length= mi_uint2korr(page) & 32767; + page+= 2; + } + else + length= *page++ & 127; + + if (packed) + { + if (length == 0) /* Same key */ + continue; + get_key_length(length,page); + page+= length; + continue; + } + if ((keyseg->flag & HA_NULL_PART) && length) + { + /* + Keys that can have null use length+1 as the length for date as the + number 0 is reserved for keys that have a NULL value + */ + length--; + } + page+= length; + } + else + { + if (keyseg->flag & HA_NULL_PART) + if (!*page++) + continue; + if (keyseg->flag & (HA_SPACE_PACK | HA_BLOB_PART | HA_VAR_LENGTH_PART)) + { + uint length; + get_key_length(length,page); + page+=length; + } + else + page+= keyseg->length; + } + } + page+= keyseg->length; + if ((page_flag & KEYPAGE_FLAG_HAS_TRANSID) && key_has_transid(page-1)) + page+= transid_packed_length(page); + return page + nod_flag; +} + + +/* Read key that is packed relatively to previous */ + +uint _ma_get_binary_pack_key(MARIA_KEY *int_key, uint page_flag, uint nod_flag, + register uchar **page_pos) +{ + reg1 HA_KEYSEG *keyseg; + uchar *page, *page_end, *from, *from_end, *key; + uint length,tmp; + MARIA_KEYDEF *keyinfo= int_key->keyinfo; + DBUG_ENTER("_ma_get_binary_pack_key"); + + page= *page_pos; + page_end=page + MARIA_MAX_KEY_BUFF + 1; + key= int_key->data; + + /* + Keys are compressed the following way: + + prefix length Packed length of prefix common with prev key. + (1 or 3 bytes) + for each key segment: + [is null] Null indicator if can be null (1 byte, zero means null) + [length] Packed length if varlength (1 or 3 bytes) + key segment 'length' bytes of key segment value + pointer Reference to the data file (last_keyseg->length). + + get_key_length() is a macro. It gets the prefix length from 'page' + and puts it into 'length'. It increments 'page' by 1 or 3, depending + on the packed length of the prefix length. + */ + get_key_length(length,page); + if (length) + { + if (length > keyinfo->maxlength) + { + DBUG_PRINT("error", + ("Found too long binary packed key: %u of %u at 0x%lx", + length, keyinfo->maxlength, (long) *page_pos)); + DBUG_DUMP("key", *page_pos, 16); + maria_print_error(keyinfo->share, HA_ERR_CRASHED); + my_errno=HA_ERR_CRASHED; + DBUG_RETURN(0); /* Wrong key */ + } + /* Key is packed against prev key, take prefix from prev key. */ + from= key; + from_end= key + length; + } + else + { + /* Key is not packed against prev key, take all from page buffer. */ + from= page; + from_end= page_end; + } + + /* + The trouble is that key can be split in two parts: + The first part (prefix) is in from .. from_end - 1. + The second part starts at page. + The split can be at every byte position. So we need to check for + the end of the first part before using every byte. + */ + for (keyseg=keyinfo->seg ; keyseg->type ;keyseg++) + { + if (keyseg->flag & HA_NULL_PART) + { + /* If prefix is used up, switch to rest. */ + if (from == from_end) + { + from=page; + from_end=page_end; + } + if (!(*key++ = *from++)) + continue; /* Null part */ + } + if (keyseg->flag & (HA_VAR_LENGTH_PART | HA_BLOB_PART | HA_SPACE_PACK)) + { + /* If prefix is used up, switch to rest. */ + if (from == from_end) { from=page; from_end=page_end; } + /* Get length of dynamic length key part */ + if ((length= (uint) (uchar) (*key++ = *from++)) == 255) + { + /* If prefix is used up, switch to rest. */ + if (from == from_end) { from=page; from_end=page_end; } + length= ((uint) (uchar) ((*key++ = *from++))) << 8; + /* If prefix is used up, switch to rest. */ + if (from == from_end) { from=page; from_end=page_end; } + length+= (uint) (uchar) ((*key++ = *from++)); + } + } + else + length=keyseg->length; + + if ((tmp=(uint) (from_end-from)) <= length) + { + key+=tmp; /* Use old key */ + length-=tmp; + from=page; from_end=page_end; + } + DBUG_ASSERT((int) length >= 0); + DBUG_PRINT("info",("key: 0x%lx from: 0x%lx length: %u", + (long) key, (long) from, length)); + memmove(key, from, (size_t) length); + key+=length; + from+=length; + } + /* + Last segment (type == 0) contains length of data pointer. + If we have mixed key blocks with data pointer and key block pointer, + we have to copy both. + */ + int_key->data_length= (key - int_key->data); + int_key->ref_length= length= keyseg->length; + int_key->flag= 0; + if ((tmp=(uint) (from_end-from)) <= length) + { + /* Skip over the last common part of the data */ + key+= tmp; + length-= tmp; + from= page; + } + else + { + /* + Remaining length is greater than max possible length. + This can happen only if we switched to the new key bytes already. + 'page_end' is calculated with MARIA_MAX_KEY_BUFF. So it can be far + behind the real end of the key. + */ + if (from_end != page_end) + { + DBUG_PRINT("error",("Error when unpacking key")); + maria_print_error(keyinfo->share, HA_ERR_CRASHED); + my_errno=HA_ERR_CRASHED; + DBUG_RETURN(0); /* Error */ + } + } + if (page_flag & KEYPAGE_FLAG_HAS_TRANSID) + { + uchar *end= from + length; + if (key_has_transid(end-1)) + { + uint trans_length= transid_packed_length(end); + length+= trans_length; + int_key->ref_length+= trans_length; + int_key->flag= SEARCH_PAGE_KEY_HAS_TRANSID; + } + } + + /* Copy rest of data ptr and, if appropriate, trans_id and node_ptr */ + memcpy(key, from, length + nod_flag); + *page_pos= from + length + nod_flag; + + DBUG_RETURN(int_key->data_length + int_key->ref_length); +} + +/** + skip key which is ptefix packed against previous key + + @fn _ma_skip_binary_key() + @param key Keyinfo and buffer that can be used + @param nod_flag If nod: Length of node pointer, else zero. + @param key Points at key + + @note + We have to copy the key as otherwise we don't know how much left + data there is of the key. + + @todo + Implement more efficient version of this. We can ignore to copy any rest + key parts that are not null or not packed. We also don't have to copy + rowid or transid. + + @retval pointer to next key +*/ + +uchar *_ma_skip_binary_pack_key(MARIA_KEY *key, uint page_flag, + uint nod_flag, uchar *page) +{ + if (!_ma_get_binary_pack_key(key, page_flag, nod_flag, &page)) + return 0; + return page; +} + + +/** + @brief Get key at position without knowledge of previous key + + @return pointer to next key +*/ + +uchar *_ma_get_key(MARIA_KEY *key, MARIA_PAGE *ma_page, uchar *keypos) +{ + uint page_flag, nod_flag; + MARIA_KEYDEF *keyinfo= key->keyinfo; + uchar *page; + DBUG_ENTER("_ma_get_key"); + + page= ma_page->buff; + page_flag= ma_page->flag; + nod_flag= ma_page->node; + + if (! (keyinfo->flag & (HA_VAR_LENGTH_KEY | HA_BINARY_PACK_KEY)) && + ! (page_flag & KEYPAGE_FLAG_HAS_TRANSID)) + { + bmove(key->data, keypos, keyinfo->keylength+nod_flag); + key->ref_length= keyinfo->share->rec_reflength; + key->data_length= keyinfo->keylength - key->ref_length; + key->flag= 0; + DBUG_RETURN(keypos+keyinfo->keylength+nod_flag); + } + else + { + page+= keyinfo->share->keypage_header + nod_flag; + key->data[0]= 0; /* safety */ + while (page <= keypos) + { + if (!(*keyinfo->get_key)(key, page_flag, nod_flag, &page)) + { + maria_print_error(keyinfo->share, HA_ERR_CRASHED); + my_errno=HA_ERR_CRASHED; + DBUG_RETURN(0); + } + } + } + DBUG_PRINT("exit",("page: 0x%lx length: %u", (long) page, + key->data_length + key->ref_length)); + DBUG_RETURN(page); +} /* _ma_get_key */ + + +/* + @brief Get key at position without knowledge of previous key + + @return + @retval 0 ok + @retval 1 error +*/ + +static my_bool _ma_get_prev_key(MARIA_KEY *key, MARIA_PAGE *ma_page, + uchar *keypos) +{ + uint page_flag, nod_flag; + MARIA_KEYDEF *keyinfo= key->keyinfo; + DBUG_ENTER("_ma_get_prev_key"); + + page_flag= ma_page->flag; + nod_flag= ma_page->node; + + if (! (keyinfo->flag & (HA_VAR_LENGTH_KEY | HA_BINARY_PACK_KEY)) && + ! (page_flag & KEYPAGE_FLAG_HAS_TRANSID)) + { + bmove(key->data, keypos - keyinfo->keylength - nod_flag, + keyinfo->keylength); + key->ref_length= keyinfo->share->rec_reflength; + key->data_length= keyinfo->keylength - key->ref_length; + key->flag= 0; + DBUG_RETURN(0); + } + else + { + uchar *page; + + page= ma_page->buff + keyinfo->share->keypage_header + nod_flag; + key->data[0]= 0; /* safety */ + DBUG_ASSERT(page != keypos); + while (page < keypos) + { + if (! (*keyinfo->get_key)(key, page_flag, nod_flag, &page)) + { + maria_print_error(keyinfo->share, HA_ERR_CRASHED); + my_errno=HA_ERR_CRASHED; + DBUG_RETURN(1); + } + } + } + DBUG_RETURN(0); +} /* _ma_get_prev_key */ + + +/* + @brief Get last key from key-page before 'endpos' + + @note + endpos may be either end of buffer or start of a key + + @return + @retval pointer to where key starts +*/ + +uchar *_ma_get_last_key(MARIA_KEY *key, MARIA_PAGE *ma_page, uchar *endpos) +{ + uint page_flag,nod_flag; + uchar *lastpos, *page; + MARIA_KEYDEF *keyinfo= key->keyinfo; + DBUG_ENTER("_ma_get_last_key"); + DBUG_PRINT("enter",("page: 0x%lx endpos: 0x%lx", (long) ma_page->buff, + (long) endpos)); + + page_flag= ma_page->flag; + nod_flag= ma_page->node; + page= ma_page->buff + keyinfo->share->keypage_header + nod_flag; + + if (! (keyinfo->flag & (HA_VAR_LENGTH_KEY | HA_BINARY_PACK_KEY)) && + ! (page_flag & KEYPAGE_FLAG_HAS_TRANSID)) + { + lastpos= endpos-keyinfo->keylength-nod_flag; + key->ref_length= keyinfo->share->rec_reflength; + key->data_length= keyinfo->keylength - key->ref_length; + key->flag= 0; + if (lastpos >= page) + bmove(key->data, lastpos, keyinfo->keylength + nod_flag); + } + else + { + lastpos= page; + key->data[0]=0; /* safety */ + while (page < endpos) + { + lastpos= page; + if (!(*keyinfo->get_key)(key, page_flag, nod_flag, &page)) + { + DBUG_PRINT("error",("Couldn't find last key: page: 0x%lx", + (long) page)); + maria_print_error(keyinfo->share, HA_ERR_CRASHED); + my_errno=HA_ERR_CRASHED; + DBUG_RETURN(0); + } + } + } + DBUG_PRINT("exit",("lastpos: 0x%lx length: %u", (ulong) lastpos, + key->data_length + key->ref_length)); + DBUG_RETURN(lastpos); +} /* _ma_get_last_key */ + + +/** + Calculate length of unpacked key + + @param info Maria handler + @param keyinfo key handler + @param key data for key + + @notes + This function is very seldom used. It's mainly used for debugging + or when calculating a key length from a stored key in batch insert. + + This function does *NOT* calculate length of transid size! + This function can't be used against a prefix packed key on a page + + @return + @retval total length for key +*/ + +uint _ma_keylength(MARIA_KEYDEF *keyinfo, const uchar *key) +{ + reg1 HA_KEYSEG *keyseg; + const uchar *start; + + if (! (keyinfo->flag & (HA_VAR_LENGTH_KEY | HA_BINARY_PACK_KEY))) + return (keyinfo->keylength); + + start= key; + for (keyseg=keyinfo->seg ; keyseg->type ; keyseg++) + { + if (keyseg->flag & HA_NULL_PART) + if (!*key++) + continue; + if (keyseg->flag & (HA_SPACE_PACK | HA_BLOB_PART | HA_VAR_LENGTH_PART)) + { + uint length; + get_key_length(length,key); + key+=length; + } + else + key+= keyseg->length; + } + return((uint) (key-start)+keyseg->length); +} /* _ma_keylength */ + + +/* + Calculate length of part key. + + Used in maria_rkey() to find the key found for the key-part that was used. + This is needed in case of multi-byte character sets where we may search + after '0xDF' but find 'ss' +*/ + +uint _ma_keylength_part(MARIA_KEYDEF *keyinfo, register const uchar *key, + HA_KEYSEG *end) +{ + reg1 HA_KEYSEG *keyseg; + const uchar *start= key; + + for (keyseg=keyinfo->seg ; keyseg != end ; keyseg++) + { + if (keyseg->flag & HA_NULL_PART) + if (!*key++) + continue; + if (keyseg->flag & (HA_SPACE_PACK | HA_BLOB_PART | HA_VAR_LENGTH_PART)) + { + uint length; + get_key_length(length,key); + key+=length; + } + else + key+= keyseg->length; + } + return (uint) (key-start); +} + + +/* + Find next/previous record with same key + + WARNING + This can't be used when database is touched after last read +*/ + +int _ma_search_next(register MARIA_HA *info, MARIA_KEY *key, + uint32 nextflag, my_off_t pos) +{ + int error; + uchar lastkey[MARIA_MAX_KEY_BUFF]; + MARIA_KEYDEF *keyinfo= key->keyinfo; + MARIA_KEY tmp_key; + MARIA_PAGE page; + DBUG_ENTER("_ma_search_next"); + DBUG_PRINT("enter",("nextflag: %u lastpos: %lu int_keypos: 0x%lx page_changed %d keyread_buff_used: %d", + nextflag, (ulong) info->cur_row.lastpos, + (ulong) info->int_keypos, + info->page_changed, info->keyread_buff_used)); + DBUG_EXECUTE("key", _ma_print_key(DBUG_FILE, key);); + + /* + Force full read if we are at last key or if we are not on a leaf + and the key tree has changed since we used it last time + Note that even if the key tree has changed since last read, we can use + the last read data from the leaf if we haven't used the buffer for + something else. + */ + + if (((nextflag & SEARCH_BIGGER) && info->int_keypos >= info->int_maxpos) || + info->page_changed || + (info->int_keytree_version != keyinfo->version && + (info->int_nod_flag || info->keyread_buff_used))) + DBUG_RETURN(_ma_search(info, key, nextflag | SEARCH_SAVE_BUFF, + pos)); + + if (info->keyread_buff_used) + { + if (_ma_fetch_keypage(&page, info, keyinfo, info->last_search_keypage, + PAGECACHE_LOCK_LEFT_UNLOCKED, + DFLT_INIT_HITS, info->keyread_buff, 0)) + DBUG_RETURN(-1); + info->keyread_buff_used=0; + } + else + { + /* Last used buffer is in info->keyread_buff */ + /* Todo: Add info->keyread_page to keep track of this */ + _ma_page_setup(&page, info, keyinfo, 0, info->keyread_buff); + } + + tmp_key.data= lastkey; + info->last_key.keyinfo= tmp_key.keyinfo= keyinfo; + + if (nextflag & SEARCH_BIGGER) /* Next key */ + { + if (page.node) + { + my_off_t tmp_pos= _ma_kpos(page.node, info->int_keypos); + + if ((error= _ma_search(info, key, nextflag | SEARCH_SAVE_BUFF, + tmp_pos)) <=0) + DBUG_RETURN(error); + } + if (keyinfo->flag & (HA_PACK_KEY | HA_BINARY_PACK_KEY) && + info->last_key.data != key->data) + memcpy(info->last_key.data, key->data, + key->data_length + key->ref_length); + if (!(*keyinfo->get_key)(&info->last_key, page.flag, page.node, + &info->int_keypos)) + DBUG_RETURN(-1); + } + else /* Previous key */ + { + /* Find start of previous key */ + info->int_keypos= _ma_get_last_key(&tmp_key, &page, info->int_keypos); + if (!info->int_keypos) + DBUG_RETURN(-1); + if (info->int_keypos == info->keyread_buff + info->s->keypage_header) + { + /* Previous key was first key, read key before this one */ + DBUG_RETURN(_ma_search(info, key, nextflag | SEARCH_SAVE_BUFF, + pos)); + } + if (page.node && + (error= _ma_search(info, key, nextflag | SEARCH_SAVE_BUFF, + _ma_kpos(page.node,info->int_keypos))) <= 0) + DBUG_RETURN(error); + + /* QQ: We should be able to optimize away the following call */ + if (! _ma_get_last_key(&info->last_key, &page, info->int_keypos)) + DBUG_RETURN(-1); + } + info->cur_row.lastpos= _ma_row_pos_from_key(&info->last_key); + info->cur_row.trid= _ma_trid_from_key(&info->last_key); + DBUG_PRINT("exit",("found key at %lu",(ulong) info->cur_row.lastpos)); + DBUG_RETURN(0); +} /* _ma_search_next */ + + +/** + Search after position for the first row in an index + + @return + Found row is stored in info->cur_row.lastpos +*/ + +int _ma_search_first(MARIA_HA *info, MARIA_KEYDEF *keyinfo, + my_off_t pos) +{ + uchar *first_pos; + MARIA_PAGE page; + MARIA_SHARE *share= info->s; + DBUG_ENTER("_ma_search_first"); + + if (pos == HA_OFFSET_ERROR) + { + my_errno=HA_ERR_KEY_NOT_FOUND; + info->cur_row.lastpos= HA_OFFSET_ERROR; + DBUG_RETURN(-1); + } + + do + { + if (_ma_fetch_keypage(&page, info, keyinfo, pos, + PAGECACHE_LOCK_LEFT_UNLOCKED, + DFLT_INIT_HITS, info->keyread_buff, 0)) + { + info->cur_row.lastpos= HA_OFFSET_ERROR; + DBUG_RETURN(-1); + } + first_pos= page.buff + share->keypage_header + page.node; + } while ((pos= _ma_kpos(page.node, first_pos)) != HA_OFFSET_ERROR); + + info->last_key.keyinfo= keyinfo; + + if (!(*keyinfo->get_key)(&info->last_key, page.flag, page.node, &first_pos)) + DBUG_RETURN(-1); /* Crashed */ + + info->int_keypos= first_pos; + info->int_maxpos= (page.buff + page.size -1); + info->int_nod_flag= page.node; + info->int_keytree_version= keyinfo->version; + info->last_search_keypage= info->last_keypage; + info->page_changed=info->keyread_buff_used=0; + info->cur_row.lastpos= _ma_row_pos_from_key(&info->last_key); + info->cur_row.trid= _ma_trid_from_key(&info->last_key); + + DBUG_PRINT("exit",("found key at %lu", (ulong) info->cur_row.lastpos)); + DBUG_RETURN(0); +} /* _ma_search_first */ + + +/** + Search after position for the last row in an index + + @return + Found row is stored in info->cur_row.lastpos +*/ + +int _ma_search_last(MARIA_HA *info, MARIA_KEYDEF *keyinfo, + my_off_t pos) +{ + uchar *end_of_page; + MARIA_PAGE page; + DBUG_ENTER("_ma_search_last"); + + if (pos == HA_OFFSET_ERROR) + { + my_errno=HA_ERR_KEY_NOT_FOUND; /* Didn't find key */ + info->cur_row.lastpos= HA_OFFSET_ERROR; + DBUG_RETURN(-1); + } + + do + { + if (_ma_fetch_keypage(&page, info, keyinfo, pos, + PAGECACHE_LOCK_LEFT_UNLOCKED, + DFLT_INIT_HITS, info->keyread_buff, 0)) + { + info->cur_row.lastpos= HA_OFFSET_ERROR; + DBUG_RETURN(-1); + } + end_of_page= page.buff + page.size; + } while ((pos= _ma_kpos(page.node, end_of_page)) != HA_OFFSET_ERROR); + + info->last_key.keyinfo= keyinfo; + + if (!_ma_get_last_key(&info->last_key, &page, end_of_page)) + DBUG_RETURN(-1); + info->cur_row.lastpos= _ma_row_pos_from_key(&info->last_key); + info->cur_row.trid= _ma_trid_from_key(&info->last_key); + info->int_keypos= info->int_maxpos= end_of_page; + info->int_nod_flag= page.node; + info->int_keytree_version= keyinfo->version; + info->last_search_keypage= info->last_keypage; + info->page_changed=info->keyread_buff_used=0; + + DBUG_PRINT("exit",("found key at %lu",(ulong) info->cur_row.lastpos)); + DBUG_RETURN(0); +} /* _ma_search_last */ + + + +/**************************************************************************** +** +** Functions to store and pack a key in a page +** +** maria_calc_xx_key_length takes the following arguments: +** nod_flag If nod: Length of nod-pointer +** next_key Position to pos after the new key in buffer +** org_key Key that was before the next key in buffer +** prev_key Last key before current key +** key Key that will be stored +** s_temp Information how next key will be packed +****************************************************************************/ + +/* Static length key */ + +int +_ma_calc_static_key_length(const MARIA_KEY *key, uint nod_flag, + uchar *next_pos __attribute__((unused)), + uchar *org_key __attribute__((unused)), + uchar *prev_key __attribute__((unused)), + MARIA_KEY_PARAM *s_temp) +{ + s_temp->key= key->data; + return (int) (s_temp->move_length= key->data_length + key->ref_length + + nod_flag); +} + +/* Variable length key */ + +int +_ma_calc_var_key_length(const MARIA_KEY *key, uint nod_flag, + uchar *next_pos __attribute__((unused)), + uchar *org_key __attribute__((unused)), + uchar *prev_key __attribute__((unused)), + MARIA_KEY_PARAM *s_temp) +{ + s_temp->key= key->data; + return (int) (s_temp->move_length= key->data_length + key->ref_length + + nod_flag); +} + +/** + @brief Calc length needed to store prefixed compressed keys + + @info + Variable length first segment which is prefix compressed + (maria_chk reports 'packed + stripped') + + Keys are compressed the following way: + + If the max length of first key segment <= 127 bytes the prefix is + 1 uchar else it's 2 byte + + prefix byte(s) The high bit is set if this is a prefix for the prev key + length Packed length if the previous was a prefix byte + [data_length] data bytes ('length' bytes) + next-key-seg Next key segments + + If the first segment can have NULL: + If key was packed + data_length is length of rest of key + If key was not packed + The data_length is 0 for NULLS and 1+data_length for not null columns +*/ + +int +_ma_calc_var_pack_key_length(const MARIA_KEY *int_key, uint nod_flag, + uchar *next_key, uchar *org_key, uchar *prev_key, + MARIA_KEY_PARAM *s_temp) +{ + reg1 HA_KEYSEG *keyseg; + int length; + uint key_length,ref_length,org_key_length=0, + length_pack,new_key_length,diff_flag,pack_marker; + const uchar *key, *start, *end, *key_end; + uchar *sort_order; + my_bool same_length; + MARIA_KEYDEF *keyinfo= int_key->keyinfo; + + key= int_key->data; + length_pack=s_temp->ref_length=s_temp->n_ref_length=s_temp->n_length=0; + same_length=0; keyseg=keyinfo->seg; + key_length= int_key->data_length + int_key->ref_length + nod_flag; + + sort_order=0; + if ((keyinfo->flag & HA_FULLTEXT) && + ((keyseg->type == HA_KEYTYPE_TEXT) || + (keyseg->type == HA_KEYTYPE_VARTEXT1) || + (keyseg->type == HA_KEYTYPE_VARTEXT2)) && + !use_strnxfrm(keyseg->charset)) + sort_order= keyseg->charset->sort_order; + + /* diff flag contains how many bytes is needed to pack key */ + if (keyseg->length >= 127) + { + diff_flag=2; + pack_marker=32768; + } + else + { + diff_flag= 1; + pack_marker=128; + } + s_temp->pack_marker=pack_marker; + + /* Handle the case that the first part have NULL values */ + if (keyseg->flag & HA_NULL_PART) + { + if (!*key++) + { + s_temp->key= key; + s_temp->key_length= 0; + s_temp->totlength= key_length-1+diff_flag; + s_temp->next_key_pos= 0; /* No next key */ + return (s_temp->move_length= s_temp->totlength); + } + s_temp->store_not_null=1; + key_length--; /* We don't store NULL */ + if (prev_key && !*prev_key++) + org_key=prev_key=0; /* Can't pack against prev */ + else if (org_key) + org_key++; /* Skip NULL */ + } + else + s_temp->store_not_null=0; + s_temp->prev_key= org_key; + + /* The key part will start with a packed length */ + + get_key_pack_length(new_key_length,length_pack,key); + end= key_end= key+ new_key_length; + start= key; + + /* Calc how many characters are identical between this and the prev. key */ + if (prev_key) + { + get_key_length(org_key_length,prev_key); + s_temp->prev_key=prev_key; /* Pointer at data */ + /* Don't use key-pack if length == 0 */ + if (new_key_length && new_key_length == org_key_length) + same_length=1; + else if (new_key_length > org_key_length) + end= key + org_key_length; + + if (sort_order) /* SerG */ + { + while (key < end && + sort_order[*key] == sort_order[*prev_key]) + { + key++; prev_key++; + } + } + else + { + while (key < end && *key == *prev_key) + { + key++; prev_key++; + } + } + } + + s_temp->key=key; + s_temp->key_length= (uint) (key_end-key); + + if (same_length && key == key_end) + { + /* identical variable length key */ + s_temp->ref_length= pack_marker; + length=(int) key_length-(int) (key_end-start)-length_pack; + length+= diff_flag; + if (next_key) + { /* Can't combine with next */ + s_temp->n_length= *next_key; /* Needed by _ma_store_key */ + next_key=0; + } + } + else + { + if (start != key) + { /* Starts as prev key */ + ref_length= (uint) (key-start); + s_temp->ref_length= ref_length + pack_marker; + length= (int) (key_length - ref_length); + + length-= length_pack; + length+= diff_flag; + length+= ((new_key_length-ref_length) >= 255) ? 3 : 1;/* Rest_of_key */ + } + else + { + s_temp->key_length+=s_temp->store_not_null; /* If null */ + length= key_length - length_pack+ diff_flag; + } + } + s_temp->totlength=(uint) length; + s_temp->prev_length=0; + DBUG_PRINT("test",("tot_length: %u length: %d uniq_key_length: %u", + key_length, length, s_temp->key_length)); + + /* If something after that hasn't length=0, test if we can combine */ + if ((s_temp->next_key_pos=next_key)) + { + uint packed,n_length; + + packed = *next_key & 128; + if (diff_flag == 2) + { + n_length= mi_uint2korr(next_key) & 32767; /* Length of next key */ + next_key+=2; + } + else + n_length= *next_key++ & 127; + if (!packed) + n_length-= s_temp->store_not_null; + + if (n_length || packed) /* Don't pack 0 length keys */ + { + uint next_length_pack, new_ref_length=s_temp->ref_length; + + if (packed) + { + /* If first key and next key is packed (only on delete) */ + if (!prev_key && org_key) + { + get_key_length(org_key_length,org_key); + key=start; + if (sort_order) /* SerG */ + { + while (key < end && + sort_order[*key] == sort_order[*org_key]) + { + key++; org_key++; + } + } + else + { + while (key < end && *key == *org_key) + { + key++; org_key++; + } + } + if ((new_ref_length= (uint) (key - start))) + new_ref_length+=pack_marker; + } + + if (!n_length) + { + /* + We put a different key between two identical variable length keys + Extend next key to have same prefix as this key + */ + if (new_ref_length) /* prefix of previus key */ + { /* make next key longer */ + s_temp->part_of_prev_key= new_ref_length; + s_temp->prev_length= org_key_length - + (new_ref_length-pack_marker); + s_temp->n_ref_length= s_temp->part_of_prev_key; + s_temp->n_length= s_temp->prev_length; + n_length= get_pack_length(s_temp->prev_length); + s_temp->prev_key+= (new_ref_length - pack_marker); + length+= s_temp->prev_length + n_length; + } + else + { /* Can't use prev key */ + s_temp->part_of_prev_key=0; + s_temp->prev_length= org_key_length; + s_temp->n_ref_length=s_temp->n_length= org_key_length; + length+= org_key_length; + } + return (s_temp->move_length= (int) length); + } + + ref_length=n_length; + /* Get information about not packed key suffix */ + get_key_pack_length(n_length,next_length_pack,next_key); + + /* Test if new keys has fewer characters that match the previous key */ + if (!new_ref_length) + { /* Can't use prev key */ + s_temp->part_of_prev_key= 0; + s_temp->prev_length= ref_length; + s_temp->n_ref_length= s_temp->n_length= n_length+ref_length; + return s_temp->move_length= ((int) length+ref_length- + next_length_pack); + } + if (ref_length+pack_marker > new_ref_length) + { + uint new_pack_length=new_ref_length-pack_marker; + /* We must copy characters from the original key to the next key */ + s_temp->part_of_prev_key= new_ref_length; + s_temp->prev_length= ref_length - new_pack_length; + s_temp->n_ref_length=s_temp->n_length=n_length + s_temp->prev_length; + s_temp->prev_key+= new_pack_length; + length-= (next_length_pack - get_pack_length(s_temp->n_length)); + return s_temp->move_length= ((int) length + s_temp->prev_length); + } + } + else + { + /* Next key wasn't a prefix of previous key */ + ref_length=0; + next_length_pack=0; + } + DBUG_PRINT("test",("length: %d next_key: 0x%lx", length, + (long) next_key)); + + { + uint tmp_length; + key=(start+=ref_length); + if (key+n_length < key_end) /* Normalize length based */ + key_end= key+n_length; + if (sort_order) /* SerG */ + { + while (key < key_end && + sort_order[*key] == sort_order[*next_key]) + { + key++; next_key++; + } + } + else + { + while (key < key_end && *key == *next_key) + { + key++; next_key++; + } + } + if (!(tmp_length=(uint) (key-start))) + { /* Key can't be re-packed */ + s_temp->next_key_pos=0; + return (s_temp->move_length= length); + } + ref_length+=tmp_length; + n_length-=tmp_length; + length-=tmp_length+next_length_pack; /* We gained these chars */ + } + if (n_length == 0 && ref_length == new_key_length) + { + s_temp->n_ref_length=pack_marker; /* Same as prev key */ + } + else + { + s_temp->n_ref_length=ref_length | pack_marker; + length+= get_pack_length(n_length); + s_temp->n_length=n_length; + } + } + } + return (s_temp->move_length= length); +} + + +/* Length of key which is prefix compressed */ + +int _ma_calc_bin_pack_key_length(const MARIA_KEY *int_key, + uint nod_flag, + uchar *next_key, + uchar *org_key, uchar *prev_key, + MARIA_KEY_PARAM *s_temp) +{ + uint length,key_length,ref_length; + const uchar *key= int_key->data; + + s_temp->totlength= key_length= (int_key->data_length + int_key->ref_length+ + nod_flag); +#ifdef HAVE_valgrind + s_temp->n_length= s_temp->n_ref_length=0; /* For valgrind */ +#endif + s_temp->key=key; + s_temp->prev_key=org_key; + if (prev_key) /* If not first key in block */ + { + /* pack key against previous key */ + /* + As keys may be identical when running a sort in maria_chk, we + have to guard against the case where keys may be identical + */ + const uchar *end; + end=key+key_length; + for ( ; *key == *prev_key && key < end; key++,prev_key++) ; + s_temp->ref_length= ref_length=(uint) (key-s_temp->key); + length=key_length - ref_length + get_pack_length(ref_length); + } + else + { + /* No previous key */ + s_temp->ref_length=ref_length=0; + length=key_length+1; + } + if ((s_temp->next_key_pos=next_key)) /* If another key after */ + { + /* pack key against next key */ + uint next_length,next_length_pack; + get_key_pack_length(next_length,next_length_pack,next_key); + + /* If first key and next key is packed (only on delete) */ + if (!prev_key && org_key && next_length) + { + const uchar *end; + for (key= s_temp->key, end=key+next_length ; + *key == *org_key && key < end; + key++,org_key++) ; + ref_length= (uint) (key - s_temp->key); + } + + if (next_length > ref_length) + { + /* + We put a key with different case between two keys with the same prefix + Extend next key to have same prefix as this key + */ + s_temp->n_ref_length= ref_length; + s_temp->prev_length= next_length-ref_length; + s_temp->prev_key+= ref_length; + return s_temp->move_length= ((int) (length+ s_temp->prev_length - + next_length_pack + + get_pack_length(ref_length))); + } + /* Check how many characters are identical to next key */ + key= s_temp->key+next_length; + s_temp->prev_length= 0; + while (*key++ == *next_key++) ; + if ((ref_length= (uint) (key - s_temp->key)-1) == next_length) + { + s_temp->next_key_pos=0; + return (s_temp->move_length= length); /* Can't pack next key */ + } + s_temp->n_ref_length=ref_length; + return s_temp->move_length= (int) (length-(ref_length - next_length) - + next_length_pack + + get_pack_length(ref_length)); + } + return (s_temp->move_length= (int) length); +} + + +/* +** store a key packed with _ma_calc_xxx_key_length in page-buffert +*/ + +/* store key without compression */ + +void _ma_store_static_key(MARIA_KEYDEF *keyinfo __attribute__((unused)), + register uchar *key_pos, + register MARIA_KEY_PARAM *s_temp) +{ + memcpy(key_pos, s_temp->key,(size_t) s_temp->move_length); + s_temp->changed_length= s_temp->move_length; +} + + +/* store variable length key with prefix compression */ + +#define store_pack_length(test,pos,length) { \ + if (test) { *((pos)++) = (uchar) (length); } else \ + { *((pos)++) = (uchar) ((length) >> 8); *((pos)++) = (uchar) (length); } } + + +void _ma_store_var_pack_key(MARIA_KEYDEF *keyinfo __attribute__((unused)), + register uchar *key_pos, + register MARIA_KEY_PARAM *s_temp) +{ + uint length; + uchar *org_key_pos= key_pos; + + if (s_temp->ref_length) + { + /* Packed against previous key */ + store_pack_length(s_temp->pack_marker == 128,key_pos,s_temp->ref_length); + /* If not same key after */ + if (s_temp->ref_length != s_temp->pack_marker) + store_key_length_inc(key_pos,s_temp->key_length); + } + else + { + /* Not packed against previous key */ + store_pack_length(s_temp->pack_marker == 128,key_pos,s_temp->key_length); + } + bmove(key_pos, s_temp->key, + (length= s_temp->totlength - (uint) (key_pos-org_key_pos))); + + key_pos+= length; + + if (!s_temp->next_key_pos) /* No following key */ + goto end; + + if (s_temp->prev_length) + { + /* Extend next key because new key didn't have same prefix as prev key */ + if (s_temp->part_of_prev_key) + { + store_pack_length(s_temp->pack_marker == 128,key_pos, + s_temp->part_of_prev_key); + store_key_length_inc(key_pos,s_temp->n_length); + } + else + { + s_temp->n_length+= s_temp->store_not_null; + store_pack_length(s_temp->pack_marker == 128,key_pos, + s_temp->n_length); + } + memcpy(key_pos, s_temp->prev_key, s_temp->prev_length); + key_pos+= s_temp->prev_length; + } + else if (s_temp->n_ref_length) + { + store_pack_length(s_temp->pack_marker == 128,key_pos,s_temp->n_ref_length); + if (s_temp->n_ref_length != s_temp->pack_marker) + { + /* Not identical key */ + store_key_length_inc(key_pos,s_temp->n_length); + } + } + else + { + s_temp->n_length+= s_temp->store_not_null; + store_pack_length(s_temp->pack_marker == 128,key_pos,s_temp->n_length); + } + +end: + s_temp->changed_length= (uint) (key_pos - org_key_pos); +} + + +/* variable length key with prefix compression */ + +void _ma_store_bin_pack_key(MARIA_KEYDEF *keyinfo __attribute__((unused)), + register uchar *key_pos, + register MARIA_KEY_PARAM *s_temp) +{ + uchar *org_key_pos= key_pos; + size_t length= s_temp->totlength - s_temp->ref_length; + + store_key_length_inc(key_pos,s_temp->ref_length); + memcpy(key_pos, s_temp->key+s_temp->ref_length, length); + key_pos+= length; + + if (s_temp->next_key_pos) + { + store_key_length_inc(key_pos,s_temp->n_ref_length); + if (s_temp->prev_length) /* If we must extend key */ + { + memcpy(key_pos,s_temp->prev_key,s_temp->prev_length); + key_pos+= s_temp->prev_length; + } + } + s_temp->changed_length= (uint) (key_pos - org_key_pos); +} diff --git a/storage/maria/ma_sort.c b/storage/maria/ma_sort.c new file mode 100644 index 00000000000..fa2cbab995a --- /dev/null +++ b/storage/maria/ma_sort.c @@ -0,0 +1,1076 @@ +/* Copyright (C) 2006 MySQL AB & MySQL Finland AB & TCX DataKonsult 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 */ + +/* + Creates a index for a database by reading keys, sorting them and outputing + them in sorted order through MARIA_SORT_INFO functions. +*/ + +#include "ma_fulltext.h" +#if defined(MSDOS) || defined(__WIN__) +#include <fcntl.h> +#else +#include <stddef.h> +#endif +#include <queues.h> + +/* static variables */ + +#undef MIN_SORT_MEMORY +#undef MYF_RW +#undef DISK_BUFFER_SIZE + +#define MERGEBUFF 15 +#define MERGEBUFF2 31 +#define MIN_SORT_MEMORY (4096-MALLOC_OVERHEAD) +#define MYF_RW MYF(MY_NABP | MY_WME | MY_WAIT_IF_FULL) +#define DISK_BUFFER_SIZE (IO_SIZE*16) + + +/* + Pointers of functions for store and read keys from temp file +*/ + +extern void print_error _VARARGS((const char *fmt,...)); + +/* Functions defined in this file */ + +static ha_rows find_all_keys(MARIA_SORT_PARAM *info,uint keys, + uchar **sort_keys, + DYNAMIC_ARRAY *buffpek,int *maxbuffer, + IO_CACHE *tempfile, + IO_CACHE *tempfile_for_exceptions); +static int write_keys(MARIA_SORT_PARAM *info, uchar **sort_keys, + uint count, BUFFPEK *buffpek,IO_CACHE *tempfile); +static int write_key(MARIA_SORT_PARAM *info, uchar *key, + IO_CACHE *tempfile); +static int write_index(MARIA_SORT_PARAM *info, uchar **sort_keys, + uint count); +static int merge_many_buff(MARIA_SORT_PARAM *info,uint keys, + uchar **sort_keys, + BUFFPEK *buffpek,int *maxbuffer, + IO_CACHE *t_file); +static uint read_to_buffer(IO_CACHE *fromfile,BUFFPEK *buffpek, + uint sort_length); +static int merge_buffers(MARIA_SORT_PARAM *info,uint keys, + IO_CACHE *from_file, IO_CACHE *to_file, + uchar **sort_keys, BUFFPEK *lastbuff, + BUFFPEK *Fb, BUFFPEK *Tb); +static int merge_index(MARIA_SORT_PARAM *,uint, uchar **,BUFFPEK *, int, + IO_CACHE *); +static int flush_maria_ft_buf(MARIA_SORT_PARAM *info); + +static int write_keys_varlen(MARIA_SORT_PARAM *info, uchar **sort_keys, + uint count, BUFFPEK *buffpek, + IO_CACHE *tempfile); +static uint read_to_buffer_varlen(IO_CACHE *fromfile,BUFFPEK *buffpek, + uint sort_length); +static int write_merge_key(MARIA_SORT_PARAM *info, IO_CACHE *to_file, + uchar *key, uint sort_length, uint count); +static int write_merge_key_varlen(MARIA_SORT_PARAM *info, + IO_CACHE *to_file, uchar *key, + uint sort_length, uint count); +static inline int +my_var_write(MARIA_SORT_PARAM *info, IO_CACHE *to_file, uchar *bufs); + +/* + Creates a index of sorted keys + + SYNOPSIS + _ma_create_index_by_sort() + info Sort parameters + no_messages Set to 1 if no output + sortbuff_size Size of sortbuffer to allocate + + RESULT + 0 ok + <> 0 Error +*/ + +int _ma_create_index_by_sort(MARIA_SORT_PARAM *info, my_bool no_messages, + size_t sortbuff_size) +{ + int error,maxbuffer,skr; + size_t memavl,old_memavl; + uint keys,sort_length; + DYNAMIC_ARRAY buffpek; + ha_rows records; + uchar **sort_keys; + IO_CACHE tempfile, tempfile_for_exceptions; + DBUG_ENTER("_ma_create_index_by_sort"); + DBUG_PRINT("enter",("sort_buff_size: %lu sort_length: %d max_records: %lu", + (ulong) sortbuff_size, info->key_length, + (ulong) info->sort_info->max_records)); + + if (info->keyinfo->flag & HA_VAR_LENGTH_KEY) + { + info->write_keys= write_keys_varlen; + info->read_to_buffer=read_to_buffer_varlen; + info->write_key=write_merge_key_varlen; + } + else + { + info->write_keys= write_keys; + info->read_to_buffer=read_to_buffer; + info->write_key=write_merge_key; + } + + my_b_clear(&tempfile); + my_b_clear(&tempfile_for_exceptions); + bzero((char*) &buffpek,sizeof(buffpek)); + sort_keys= (uchar **) NULL; error= 1; + maxbuffer=1; + + memavl=max(sortbuff_size,MIN_SORT_MEMORY); + records= info->sort_info->max_records; + sort_length= info->key_length; + LINT_INIT(keys); + + while (memavl >= MIN_SORT_MEMORY) + { + if ((records < UINT_MAX32) && + ((my_off_t) (records + 1) * + (sort_length + sizeof(char*)) <= (my_off_t) memavl)) + keys= (uint)records+1; + else + do + { + skr=maxbuffer; + if (memavl < sizeof(BUFFPEK)*(uint) maxbuffer || + (keys=(memavl-sizeof(BUFFPEK)*(uint) maxbuffer)/ + (sort_length+sizeof(char*))) <= 1 || + keys < (uint) maxbuffer) + { + _ma_check_print_error(info->sort_info->param, + "maria_sort_buffer_size is too small"); + goto err; + } + } + while ((maxbuffer= (int) (records/(keys-1)+1)) != skr); + + if ((sort_keys=(uchar**) my_malloc(keys*(sort_length+sizeof(char*))+ + HA_FT_MAXBYTELEN, MYF(0)))) + { + if (my_init_dynamic_array(&buffpek, sizeof(BUFFPEK), maxbuffer, + maxbuffer/2)) + { + my_free(sort_keys,MYF(0)); + sort_keys= 0; + } + else + break; + } + old_memavl=memavl; + if ((memavl=memavl/4*3) < MIN_SORT_MEMORY && old_memavl > MIN_SORT_MEMORY) + memavl=MIN_SORT_MEMORY; + } + if (memavl < MIN_SORT_MEMORY) + { + _ma_check_print_error(info->sort_info->param, "Maria sort buffer" + " too small"); /* purecov: tested */ + goto err; /* purecov: tested */ + } + (*info->lock_in_memory)(info->sort_info->param);/* Everything is allocated */ + + if (!no_messages) + printf(" - Searching for keys, allocating buffer for %d keys\n",keys); + + if ((records=find_all_keys(info,keys,sort_keys,&buffpek,&maxbuffer, + &tempfile,&tempfile_for_exceptions)) + == HA_POS_ERROR) + goto err; /* purecov: tested */ + if (maxbuffer == 0) + { + if (!no_messages) + printf(" - Dumping %lu keys\n", (ulong) records); + if (write_index(info,sort_keys, (uint) records)) + goto err; /* purecov: inspected */ + } + else + { + keys=(keys*(sort_length+sizeof(char*)))/sort_length; + if (maxbuffer >= MERGEBUFF2) + { + if (!no_messages) + printf(" - Merging %lu keys\n", (ulong) records); /* purecov: tested */ + if (merge_many_buff(info,keys,sort_keys, + dynamic_element(&buffpek,0,BUFFPEK *),&maxbuffer,&tempfile)) + goto err; /* purecov: inspected */ + } + if (flush_io_cache(&tempfile) || + reinit_io_cache(&tempfile,READ_CACHE,0L,0,0)) + goto err; /* purecov: inspected */ + if (!no_messages) + printf(" - Last merge and dumping keys\n"); /* purecov: tested */ + if (merge_index(info,keys,sort_keys,dynamic_element(&buffpek,0,BUFFPEK *), + maxbuffer,&tempfile)) + goto err; /* purecov: inspected */ + } + + if (flush_maria_ft_buf(info) || _ma_flush_pending_blocks(info)) + goto err; + + if (my_b_inited(&tempfile_for_exceptions)) + { + MARIA_HA *idx=info->sort_info->info; + uint16 key_length; + MARIA_KEY key; + key.keyinfo= idx->s->keyinfo + info->key; + + if (!no_messages) + printf(" - Adding exceptions\n"); /* purecov: tested */ + if (flush_io_cache(&tempfile_for_exceptions) || + reinit_io_cache(&tempfile_for_exceptions,READ_CACHE,0L,0,0)) + goto err; + + while (!my_b_read(&tempfile_for_exceptions,(uchar*)&key_length, + sizeof(key_length)) + && !my_b_read(&tempfile_for_exceptions,(uchar*)sort_keys, + (uint) key_length)) + { + key.data= (uchar*) sort_keys; + key.ref_length= idx->s->rec_reflength; + key.data_length= key_length - key.ref_length; + key.flag= 0; + if (_ma_ck_write(idx, &key)) + goto err; + } + } + + error =0; + +err: + my_free(sort_keys, MYF(MY_ALLOW_ZERO_PTR)); + delete_dynamic(&buffpek); + close_cached_file(&tempfile); + close_cached_file(&tempfile_for_exceptions); + + DBUG_RETURN(error ? -1 : 0); +} /* _ma_create_index_by_sort */ + + +/* Search after all keys and place them in a temp. file */ + +static ha_rows find_all_keys(MARIA_SORT_PARAM *info, uint keys, + uchar **sort_keys, DYNAMIC_ARRAY *buffpek, + int *maxbuffer, IO_CACHE *tempfile, + IO_CACHE *tempfile_for_exceptions) +{ + int error; + uint idx; + DBUG_ENTER("find_all_keys"); + + idx=error=0; + sort_keys[0]= (uchar*) (sort_keys+keys); + + while (!(error=(*info->key_read)(info,sort_keys[idx]))) + { + if (info->real_key_length > info->key_length) + { + if (write_key(info,sort_keys[idx],tempfile_for_exceptions)) + DBUG_RETURN(HA_POS_ERROR); /* purecov: inspected */ + continue; + } + + if (++idx == keys) + { + if (info->write_keys(info,sort_keys,idx-1, + (BUFFPEK *)alloc_dynamic(buffpek), + tempfile)) + DBUG_RETURN(HA_POS_ERROR); /* purecov: inspected */ + + sort_keys[0]=(uchar*) (sort_keys+keys); + memcpy(sort_keys[0],sort_keys[idx-1],(size_t) info->key_length); + idx=1; + } + sort_keys[idx]=sort_keys[idx-1]+info->key_length; + } + if (error > 0) + DBUG_RETURN(HA_POS_ERROR); /* Aborted by get_key */ /* purecov: inspected */ + if (buffpek->elements) + { + if (info->write_keys(info,sort_keys,idx,(BUFFPEK *)alloc_dynamic(buffpek), + tempfile)) + DBUG_RETURN(HA_POS_ERROR); /* purecov: inspected */ + *maxbuffer=buffpek->elements-1; + } + else + *maxbuffer=0; + + DBUG_RETURN((*maxbuffer)*(keys-1)+idx); +} /* find_all_keys */ + + +#ifdef THREAD +/* Search after all keys and place them in a temp. file */ + +pthread_handler_t _ma_thr_find_all_keys(void *arg) +{ + MARIA_SORT_PARAM *sort_param= (MARIA_SORT_PARAM*) arg; + int error; + size_t memavl,old_memavl; + uint sort_length; + ulong idx, maxbuffer, keys; + uchar **sort_keys=0; + + LINT_INIT(keys); + + error=1; + + if (my_thread_init()) + goto err; + + { /* Add extra block since DBUG_ENTER declare variables */ + DBUG_ENTER("_ma_thr_find_all_keys"); + DBUG_PRINT("enter", ("master: %d", sort_param->master)); + if (sort_param->sort_info->got_error) + goto err; + + if (sort_param->keyinfo->flag & HA_VAR_LENGTH_KEY) + { + sort_param->write_keys= write_keys_varlen; + sort_param->read_to_buffer= read_to_buffer_varlen; + sort_param->write_key= write_merge_key_varlen; + } + else + { + sort_param->write_keys= write_keys; + sort_param->read_to_buffer= read_to_buffer; + sort_param->write_key= write_merge_key; + } + + my_b_clear(&sort_param->tempfile); + my_b_clear(&sort_param->tempfile_for_exceptions); + bzero((char*) &sort_param->buffpek,sizeof(sort_param->buffpek)); + bzero((char*) &sort_param->unique, sizeof(sort_param->unique)); + + memavl= max(sort_param->sortbuff_size, MIN_SORT_MEMORY); + idx= (uint)sort_param->sort_info->max_records; + sort_length= sort_param->key_length; + maxbuffer= 1; + + while (memavl >= MIN_SORT_MEMORY) + { + if ((my_off_t) (idx+1)*(sort_length+sizeof(char*)) <= (my_off_t) memavl) + keys= idx+1; + else + { + ulong skr; + do + { + skr= maxbuffer; + if (memavl < sizeof(BUFFPEK)*maxbuffer || + (keys=(memavl-sizeof(BUFFPEK)*maxbuffer)/ + (sort_length+sizeof(char*))) <= 1 || + keys < maxbuffer) + { + _ma_check_print_error(sort_param->sort_info->param, + "maria_sort_buffer_size is too small"); + goto err; + } + } + while ((maxbuffer= (int) (idx/(keys-1)+1)) != skr); + } + if ((sort_keys= (uchar **) + my_malloc(keys*(sort_length+sizeof(char*))+ + ((sort_param->keyinfo->flag & HA_FULLTEXT) ? + HA_FT_MAXBYTELEN : 0), MYF(0)))) + { + if (my_init_dynamic_array(&sort_param->buffpek, sizeof(BUFFPEK), + maxbuffer, maxbuffer/2)) + { + my_free(sort_keys, MYF(0)); + sort_keys= (uchar **) NULL; /* for err: label */ + } + else + break; + } + old_memavl= memavl; + if ((memavl= memavl/4*3) < MIN_SORT_MEMORY && + old_memavl > MIN_SORT_MEMORY) + memavl= MIN_SORT_MEMORY; + } + if (memavl < MIN_SORT_MEMORY) + { + _ma_check_print_error(sort_param->sort_info->param, + "Maria sort buffer too small"); + goto err; /* purecov: tested */ + } + + if (sort_param->sort_info->param->testflag & T_VERBOSE) + printf("Key %d - Allocating buffer for %lu keys\n", + sort_param->key+1, (ulong) keys); + sort_param->sort_keys= sort_keys; + + idx= error= 0; + sort_keys[0]= (uchar*) (sort_keys+keys); + + DBUG_PRINT("info", ("reading keys")); + while (!(error= sort_param->sort_info->got_error) && + !(error= (*sort_param->key_read)(sort_param, sort_keys[idx]))) + { + if (sort_param->real_key_length > sort_param->key_length) + { + if (write_key(sort_param,sort_keys[idx], + &sort_param->tempfile_for_exceptions)) + goto err; + continue; + } + + if (++idx == keys) + { + if (sort_param->write_keys(sort_param, sort_keys, idx - 1, + (BUFFPEK *)alloc_dynamic(&sort_param-> + buffpek), + &sort_param->tempfile)) + goto err; + sort_keys[0]= (uchar*) (sort_keys+keys); + memcpy(sort_keys[0], sort_keys[idx - 1], + (size_t) sort_param->key_length); + idx= 1; + } + sort_keys[idx]=sort_keys[idx - 1] + sort_param->key_length; + } + if (error > 0) + goto err; + if (sort_param->buffpek.elements) + { + if (sort_param->write_keys(sort_param,sort_keys, idx, + (BUFFPEK *) alloc_dynamic(&sort_param-> + buffpek), + &sort_param->tempfile)) + goto err; + sort_param->keys= (sort_param->buffpek.elements - 1) * (keys - 1) + idx; + } + else + sort_param->keys= idx; + + sort_param->sort_keys_length= keys; + goto ok; + +err: + DBUG_PRINT("error", ("got some error")); + sort_param->sort_info->got_error= 1; /* no need to protect with a mutex */ + my_free(sort_keys,MYF(MY_ALLOW_ZERO_PTR)); + sort_param->sort_keys=0; + delete_dynamic(& sort_param->buffpek); + close_cached_file(&sort_param->tempfile); + close_cached_file(&sort_param->tempfile_for_exceptions); + +ok: + free_root(&sort_param->wordroot, MYF(0)); + /* + Detach from the share if the writer is involved. Avoid others to + be blocked. This includes a flush of the write buffer. This will + also indicate EOF to the readers. + */ + if (sort_param->sort_info->info->rec_cache.share) + remove_io_thread(&sort_param->sort_info->info->rec_cache); + + /* Readers detach from the share if any. Avoid others to be blocked. */ + if (sort_param->read_cache.share) + remove_io_thread(&sort_param->read_cache); + + pthread_mutex_lock(&sort_param->sort_info->mutex); + if (!--sort_param->sort_info->threads_running) + pthread_cond_signal(&sort_param->sort_info->cond); + pthread_mutex_unlock(&sort_param->sort_info->mutex); + DBUG_PRINT("exit", ("======== ending thread ========")); + } + my_thread_end(); + return NULL; +} + + +int _ma_thr_write_keys(MARIA_SORT_PARAM *sort_param) +{ + MARIA_SORT_INFO *sort_info=sort_param->sort_info; + HA_CHECK *param=sort_info->param; + ulong length, keys; + double *rec_per_key_part= param->new_rec_per_key_part; + int got_error=sort_info->got_error; + uint i; + MARIA_HA *info=sort_info->info; + MARIA_SHARE *share= info->s; + MARIA_SORT_PARAM *sinfo; + uchar *mergebuf=0; + DBUG_ENTER("_ma_thr_write_keys"); + LINT_INIT(length); + + for (i= 0, sinfo= sort_param ; + i < sort_info->total_keys ; + i++, rec_per_key_part+=sinfo->keyinfo->keysegs, sinfo++) + { + if (!sinfo->sort_keys) + { + got_error=1; + my_free(sinfo->rec_buff, MYF(MY_ALLOW_ZERO_PTR)); + continue; + } + if (!got_error) + { + maria_set_key_active(share->state.key_map, sinfo->key); + + if (!sinfo->buffpek.elements) + { + if (param->testflag & T_VERBOSE) + { + printf("Key %d - Dumping %u keys\n",sinfo->key+1, sinfo->keys); + fflush(stdout); + } + if (write_index(sinfo, sinfo->sort_keys, sinfo->keys) || + flush_maria_ft_buf(sinfo) || _ma_flush_pending_blocks(sinfo)) + got_error=1; + } + if (!got_error && param->testflag & T_STATISTICS) + maria_update_key_parts(sinfo->keyinfo, rec_per_key_part, sinfo->unique, + param->stats_method == + MI_STATS_METHOD_IGNORE_NULLS ? + sinfo->notnull : NULL, + (ulonglong) share->state.state.records); + } + my_free(sinfo->sort_keys,MYF(0)); + my_free(sinfo->rec_buff, MYF(MY_ALLOW_ZERO_PTR)); + sinfo->sort_keys=0; + } + + for (i= 0, sinfo= sort_param ; + i < sort_info->total_keys ; + i++, + delete_dynamic(&sinfo->buffpek), + close_cached_file(&sinfo->tempfile), + close_cached_file(&sinfo->tempfile_for_exceptions), + sinfo++) + { + if (got_error) + continue; + if (sinfo->keyinfo->flag & HA_VAR_LENGTH_KEY) + { + sinfo->write_keys=write_keys_varlen; + sinfo->read_to_buffer=read_to_buffer_varlen; + sinfo->write_key=write_merge_key_varlen; + } + else + { + sinfo->write_keys=write_keys; + sinfo->read_to_buffer=read_to_buffer; + sinfo->write_key=write_merge_key; + } + if (sinfo->buffpek.elements) + { + uint maxbuffer=sinfo->buffpek.elements-1; + if (!mergebuf) + { + length=param->sort_buffer_length; + while (length >= MIN_SORT_MEMORY) + { + if ((mergebuf= my_malloc(length, MYF(0)))) + break; + length=length*3/4; + } + if (!mergebuf) + { + got_error=1; + continue; + } + } + keys=length/sinfo->key_length; + if (maxbuffer >= MERGEBUFF2) + { + if (param->testflag & T_VERBOSE) + printf("Key %d - Merging %u keys\n",sinfo->key+1, sinfo->keys); + if (merge_many_buff(sinfo, keys, (uchar **) mergebuf, + dynamic_element(&sinfo->buffpek, 0, BUFFPEK *), + (int*) &maxbuffer, &sinfo->tempfile)) + { + got_error=1; + continue; + } + } + if (flush_io_cache(&sinfo->tempfile) || + reinit_io_cache(&sinfo->tempfile,READ_CACHE,0L,0,0)) + { + got_error=1; + continue; + } + if (param->testflag & T_VERBOSE) + printf("Key %d - Last merge and dumping keys\n", sinfo->key+1); + if (merge_index(sinfo, keys, (uchar**) mergebuf, + dynamic_element(&sinfo->buffpek,0,BUFFPEK *), + maxbuffer,&sinfo->tempfile) || + flush_maria_ft_buf(sinfo) || + _ma_flush_pending_blocks(sinfo)) + { + got_error=1; + continue; + } + } + if (my_b_inited(&sinfo->tempfile_for_exceptions)) + { + uint16 key_length; + + if (param->testflag & T_VERBOSE) + printf("Key %d - Dumping 'long' keys\n", sinfo->key+1); + + if (flush_io_cache(&sinfo->tempfile_for_exceptions) || + reinit_io_cache(&sinfo->tempfile_for_exceptions,READ_CACHE,0L,0,0)) + { + got_error=1; + continue; + } + + while (!got_error && + !my_b_read(&sinfo->tempfile_for_exceptions,(uchar*)&key_length, + sizeof(key_length))) + { + uchar maria_ft_buf[HA_FT_MAXBYTELEN + HA_FT_WLEN + 10]; + if (key_length > sizeof(maria_ft_buf) || + my_b_read(&sinfo->tempfile_for_exceptions, (uchar*)maria_ft_buf, + (uint) key_length)) + got_error= 1; + else + { + MARIA_KEY tmp_key; + tmp_key.keyinfo= info->s->keyinfo + sinfo->key; + tmp_key.data= maria_ft_buf; + tmp_key.ref_length= info->s->rec_reflength; + tmp_key.data_length= key_length - info->s->rec_reflength; + tmp_key.flag= 0; + if (_ma_ck_write(info, &tmp_key)) + got_error=1; + } + } + } + } + my_free(mergebuf,MYF(MY_ALLOW_ZERO_PTR)); + DBUG_RETURN(got_error); +} +#endif /* THREAD */ + + +/* Write all keys in memory to file for later merge */ + +static int write_keys(MARIA_SORT_PARAM *info, register uchar **sort_keys, + uint count, BUFFPEK *buffpek, IO_CACHE *tempfile) +{ + uchar **end; + uint sort_length=info->key_length; + DBUG_ENTER("write_keys"); + + my_qsort2((uchar*) sort_keys,count,sizeof(uchar*),(qsort2_cmp) info->key_cmp, + info); + if (!my_b_inited(tempfile) && + open_cached_file(tempfile, my_tmpdir(info->tmpdir), "ST", + DISK_BUFFER_SIZE, info->sort_info->param->myf_rw)) + DBUG_RETURN(1); /* purecov: inspected */ + + buffpek->file_pos=my_b_tell(tempfile); + buffpek->count=count; + + for (end=sort_keys+count ; sort_keys != end ; sort_keys++) + { + if (my_b_write(tempfile, *sort_keys, (uint) sort_length)) + DBUG_RETURN(1); /* purecov: inspected */ + } + DBUG_RETURN(0); +} /* write_keys */ + + +static inline int +my_var_write(MARIA_SORT_PARAM *info, IO_CACHE *to_file, uchar *bufs) +{ + int err; + uint16 len= _ma_keylength(info->keyinfo, bufs); + + /* The following is safe as this is a local file */ + if ((err= my_b_write(to_file, (uchar*)&len, sizeof(len)))) + return (err); + if ((err= my_b_write(to_file,bufs, (uint) len))) + return (err); + return (0); +} + + +static int write_keys_varlen(MARIA_SORT_PARAM *info, + register uchar **sort_keys, + uint count, BUFFPEK *buffpek, + IO_CACHE *tempfile) +{ + uchar **end; + int err; + DBUG_ENTER("write_keys_varlen"); + + my_qsort2((uchar*) sort_keys,count,sizeof(uchar*),(qsort2_cmp) info->key_cmp, + info); + if (!my_b_inited(tempfile) && + open_cached_file(tempfile, my_tmpdir(info->tmpdir), "ST", + DISK_BUFFER_SIZE, info->sort_info->param->myf_rw)) + DBUG_RETURN(1); /* purecov: inspected */ + + buffpek->file_pos=my_b_tell(tempfile); + buffpek->count=count; + for (end=sort_keys+count ; sort_keys != end ; sort_keys++) + { + if ((err= my_var_write(info,tempfile, *sort_keys))) + DBUG_RETURN(err); + } + DBUG_RETURN(0); +} /* write_keys_varlen */ + + +static int write_key(MARIA_SORT_PARAM *info, uchar *key, + IO_CACHE *tempfile) +{ + uint16 key_length=info->real_key_length; + DBUG_ENTER("write_key"); + + if (!my_b_inited(tempfile) && + open_cached_file(tempfile, my_tmpdir(info->tmpdir), "ST", + DISK_BUFFER_SIZE, info->sort_info->param->myf_rw)) + DBUG_RETURN(1); + + if (my_b_write(tempfile, (uchar*)&key_length,sizeof(key_length)) || + my_b_write(tempfile, key, (uint) key_length)) + DBUG_RETURN(1); + DBUG_RETURN(0); +} /* write_key */ + + +/* Write index */ + +static int write_index(MARIA_SORT_PARAM *info, + register uchar **sort_keys, + register uint count) +{ + DBUG_ENTER("write_index"); + + my_qsort2((uchar*) sort_keys,(size_t) count,sizeof(uchar*), + (qsort2_cmp) info->key_cmp,info); + while (count--) + { + if ((*info->key_write)(info, *sort_keys++)) + DBUG_RETURN(-1); /* purecov: inspected */ + } + DBUG_RETURN(0); +} /* write_index */ + + + /* Merge buffers to make < MERGEBUFF2 buffers */ + +static int merge_many_buff(MARIA_SORT_PARAM *info, uint keys, + uchar **sort_keys, BUFFPEK *buffpek, + int *maxbuffer, IO_CACHE *t_file) +{ + register int 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,my_tmpdir(info->tmpdir),"ST", + DISK_BUFFER_SIZE, info->sort_info->param->myf_rw)) + DBUG_RETURN(1); /* purecov: inspected */ + + from_file= t_file ; to_file= &t_file2; + while (*maxbuffer >= MERGEBUFF2) + { + reinit_io_cache(from_file,READ_CACHE,0L,0,0); + reinit_io_cache(to_file,WRITE_CACHE,0L,0,0); + lastbuff=buffpek; + for (i=0 ; i <= *maxbuffer-MERGEBUFF*3/2 ; i+=MERGEBUFF) + { + if (merge_buffers(info,keys,from_file,to_file,sort_keys,lastbuff++, + buffpek+i,buffpek+i+MERGEBUFF-1)) + goto cleanup; + } + if (merge_buffers(info,keys,from_file,to_file,sort_keys,lastbuff++, + buffpek+i,buffpek+ *maxbuffer)) + break; /* purecov: inspected */ + if (flush_io_cache(to_file)) + break; /* purecov: inspected */ + temp=from_file; from_file=to_file; to_file=temp; + *maxbuffer= (int) (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 */ + + DBUG_RETURN(*maxbuffer >= MERGEBUFF2); /* Return 1 if interrupted */ +} /* merge_many_buff */ + + +/* + Read data to buffer + + SYNOPSIS + read_to_buffer() + fromfile File to read from + buffpek Where to read from + sort_length max length to read + RESULT + > 0 Ammount of bytes read + -1 Error +*/ + +static uint read_to_buffer(IO_CACHE *fromfile, BUFFPEK *buffpek, + uint sort_length) +{ + register uint count; + uint length; + + if ((count=(uint) min((ha_rows) buffpek->max_keys,buffpek->count))) + { + if (my_pread(fromfile->file, buffpek->base, + (length= sort_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*sort_length); +} /* read_to_buffer */ + +static uint read_to_buffer_varlen(IO_CACHE *fromfile, BUFFPEK *buffpek, + uint sort_length) +{ + register uint count; + uint idx; + uchar *buffp; + + if ((count=(uint) min((ha_rows) buffpek->max_keys,buffpek->count))) + { + buffp= buffpek->base; + + for (idx=1;idx<=count;idx++) + { + uint16 length_of_key; + if (my_pread(fromfile->file,(uchar*)&length_of_key,sizeof(length_of_key), + buffpek->file_pos,MYF_RW)) + return((uint) -1); + buffpek->file_pos+=sizeof(length_of_key); + if (my_pread(fromfile->file, buffp, length_of_key, + buffpek->file_pos,MYF_RW)) + return((uint) -1); + buffpek->file_pos+=length_of_key; + buffp = buffp + sort_length; + } + buffpek->key=buffpek->base; + buffpek->count-= count; + buffpek->mem_count= count; + } + return (count*sort_length); +} /* read_to_buffer_varlen */ + + +static int write_merge_key_varlen(MARIA_SORT_PARAM *info, + IO_CACHE *to_file, uchar* key, + uint sort_length, uint count) +{ + uint idx; + uchar *bufs = key; + + for (idx=1;idx<=count;idx++) + { + int err; + if ((err= my_var_write(info, to_file, bufs))) + return (err); + bufs=bufs+sort_length; + } + return(0); +} + + +static int write_merge_key(MARIA_SORT_PARAM *info __attribute__((unused)), + IO_CACHE *to_file, uchar *key, + uint sort_length, uint count) +{ + return my_b_write(to_file, key, (size_t) sort_length*count); +} + +/* + Merge buffers to one buffer + If to_file == 0 then use info->key_write +*/ + +static int NEAR_F +merge_buffers(MARIA_SORT_PARAM *info, uint keys, IO_CACHE *from_file, + IO_CACHE *to_file, uchar **sort_keys, BUFFPEK *lastbuff, + BUFFPEK *Fb, BUFFPEK *Tb) +{ + int error; + uint sort_length,maxcount; + ha_rows count; + my_off_t to_start_filepos; + uchar *strpos; + BUFFPEK *buffpek,**refpek; + QUEUE queue; + volatile int *killed= _ma_killed_ptr(info->sort_info->param); + DBUG_ENTER("merge_buffers"); + + count=error=0; + maxcount=keys/((uint) (Tb-Fb) +1); + DBUG_ASSERT(maxcount > 0); + LINT_INIT(to_start_filepos); + if (to_file) + to_start_filepos=my_b_tell(to_file); + strpos= (uchar*) sort_keys; + sort_length=info->key_length; + + if (init_queue(&queue,(uint) (Tb-Fb)+1,offsetof(BUFFPEK,key),0, + (int (*)(void*, uchar *,uchar*)) info->key_cmp, + (void*) info)) + DBUG_RETURN(1); /* purecov: inspected */ + + for (buffpek= Fb ; buffpek <= Tb ; buffpek++) + { + count+= buffpek->count; + buffpek->base= strpos; + buffpek->max_keys=maxcount; + strpos+= (uint) (error=(int) info->read_to_buffer(from_file,buffpek, + sort_length)); + if (error == -1) + goto err; /* purecov: inspected */ + queue_insert(&queue,(uchar*) buffpek); + } + + while (queue.elements > 1) + { + for (;;) + { + if (*killed) + { + error=1; goto err; + } + buffpek=(BUFFPEK*) queue_top(&queue); + if (to_file) + { + if (info->write_key(info,to_file, buffpek->key, + (uint) sort_length,1)) + { + error=1; goto err; /* purecov: inspected */ + } + } + else + { + if ((*info->key_write)(info,(void*) buffpek->key)) + { + error=1; goto err; /* purecov: inspected */ + } + } + buffpek->key+=sort_length; + if (! --buffpek->mem_count) + { + if (!(error=(int) info->read_to_buffer(from_file,buffpek,sort_length))) + { + uchar *base= buffpek->base; + uint max_keys=buffpek->max_keys; + + VOID(queue_remove(&queue,0)); + + /* Put room used by buffer to use in other buffer */ + for (refpek= (BUFFPEK**) &queue_top(&queue); + refpek <= (BUFFPEK**) &queue_end(&queue); + refpek++) + { + buffpek= *refpek; + if (buffpek->base+buffpek->max_keys*sort_length == base) + { + buffpek->max_keys+=max_keys; + break; + } + else if (base+max_keys*sort_length == buffpek->base) + { + buffpek->base=base; + buffpek->max_keys+=max_keys; + break; + } + } + break; /* One buffer have been removed */ + } + } + else if (error == -1) + goto err; /* purecov: inspected */ + queue_replaced(&queue); /* Top element has been replaced */ + } + } + buffpek=(BUFFPEK*) queue_top(&queue); + buffpek->base= (uchar*) sort_keys; + buffpek->max_keys=keys; + do + { + if (to_file) + { + if (info->write_key(info, to_file, buffpek->key, + sort_length,buffpek->mem_count)) + { + error=1; goto err; /* purecov: inspected */ + } + } + else + { + register uchar *end; + strpos= buffpek->key; + for (end= strpos+buffpek->mem_count*sort_length; + strpos != end ; + strpos+=sort_length) + { + if ((*info->key_write)(info, strpos)) + { + error=1; goto err; /* purecov: inspected */ + } + } + } + } + while ((error=(int) info->read_to_buffer(from_file,buffpek,sort_length)) != + -1 && error != 0); + + lastbuff->count=count; + if (to_file) + lastbuff->file_pos=to_start_filepos; +err: + delete_queue(&queue); + DBUG_RETURN(error); +} /* merge_buffers */ + + + /* Do a merge to output-file (save only positions) */ + +static int NEAR_F +merge_index(MARIA_SORT_PARAM *info, uint keys, uchar **sort_keys, + BUFFPEK *buffpek, int maxbuffer, IO_CACHE *tempfile) +{ + DBUG_ENTER("merge_index"); + if (merge_buffers(info,keys,tempfile,(IO_CACHE*) 0,sort_keys,buffpek,buffpek, + buffpek+maxbuffer)) + DBUG_RETURN(1); /* purecov: inspected */ + DBUG_RETURN(0); +} /* merge_index */ + + +static int flush_maria_ft_buf(MARIA_SORT_PARAM *info) +{ + int err=0; + if (info->sort_info->ft_buf) + { + err=_ma_sort_ft_buf_flush(info); + my_free(info->sort_info->ft_buf, MYF(0)); + info->sort_info->ft_buf=0; + } + return err; +} diff --git a/storage/maria/ma_sp_defs.h b/storage/maria/ma_sp_defs.h new file mode 100644 index 00000000000..398bf99c52e --- /dev/null +++ b/storage/maria/ma_sp_defs.h @@ -0,0 +1,48 @@ +/* Copyright (C) 2006 MySQL AB & Ramil Kalimullin & MySQL Finland AB + & TCX DataKonsult 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 _SP_DEFS_H +#define _SP_DEFS_H + +#define SPDIMS 2 +#define SPTYPE HA_KEYTYPE_DOUBLE +#define SPLEN 8 + +#ifdef HAVE_SPATIAL + +enum wkbType +{ + wkbPoint = 1, + wkbLineString = 2, + wkbPolygon = 3, + wkbMultiPoint = 4, + wkbMultiLineString = 5, + wkbMultiPolygon = 6, + wkbGeometryCollection = 7 +}; + +enum wkbByteOrder +{ + wkbXDR = 0, /* Big Endian */ + wkbNDR = 1 /* Little Endian */ +}; + +MARIA_KEY *_ma_sp_make_key(MARIA_HA *info, MARIA_KEY *ret_key, uint keynr, + uchar *key, const uchar *record, my_off_t filepos, + ulonglong trid); + +#endif /*HAVE_SPATIAL*/ +#endif /* _SP_DEFS_H */ diff --git a/storage/maria/ma_sp_key.c b/storage/maria/ma_sp_key.c new file mode 100644 index 00000000000..22944a5db0a --- /dev/null +++ b/storage/maria/ma_sp_key.c @@ -0,0 +1,305 @@ +/* Copyright (C) 2006 MySQL AB & Ramil Kalimullin + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You 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 "maria_def.h" +#include "ma_blockrec.h" /* For ROW_FLAG_TRANSID */ +#include "trnman.h" + +#ifdef HAVE_SPATIAL + +#include "ma_sp_defs.h" + +static int sp_add_point_to_mbr(uchar *(*wkb), uchar *end, uint n_dims, + uchar byte_order, double *mbr); +static int sp_get_point_mbr(uchar *(*wkb), uchar *end, uint n_dims, + uchar byte_order, double *mbr); +static int sp_get_linestring_mbr(uchar *(*wkb), uchar *end, uint n_dims, + uchar byte_order, double *mbr); +static int sp_get_polygon_mbr(uchar *(*wkb), uchar *end, uint n_dims, + uchar byte_order, double *mbr); +static int sp_get_geometry_mbr(uchar *(*wkb), uchar *end, uint n_dims, + double *mbr, int top); +static int sp_mbr_from_wkb(uchar (*wkb), uint size, uint n_dims, double *mbr); + + +/** + Create spactial key +*/ + +MARIA_KEY *_ma_sp_make_key(MARIA_HA *info, MARIA_KEY *ret_key, uint keynr, + uchar *key, const uchar *record, my_off_t filepos, + ulonglong trid) +{ + HA_KEYSEG *keyseg; + MARIA_KEYDEF *keyinfo = &info->s->keyinfo[keynr]; + uint len = 0; + const uchar *pos; + uint dlen; + uchar *dptr; + double mbr[SPDIMS * 2]; + uint i; + DBUG_ENTER("_ma_sp_make_key"); + + keyseg = &keyinfo->seg[-1]; + pos = record + keyseg->start; + ret_key->data= key; + + dlen = _ma_calc_blob_length(keyseg->bit_start, pos); + memcpy_fixed(&dptr, pos + keyseg->bit_start, sizeof(char*)); + if (!dptr) + { + my_errno= HA_ERR_NULL_IN_SPATIAL; + DBUG_RETURN(0); + } + + sp_mbr_from_wkb(dptr + 4, dlen - 4, SPDIMS, mbr); /* SRID */ + + for (i = 0, keyseg = keyinfo->seg; keyseg->type; keyseg++, i++) + { + uint length = keyseg->length, start= keyseg->start; + double val; + + DBUG_ASSERT(length == 8); + DBUG_ASSERT(!(start % 8)); + DBUG_ASSERT(start < sizeof(mbr)); + DBUG_ASSERT(keyseg->type == HA_KEYTYPE_DOUBLE); + + val= mbr[start / sizeof (double)]; +#ifdef HAVE_ISNAN + if (isnan(val)) + { + bzero(key, length); + key+= length; + len+= length; + continue; + } +#endif + + if (keyseg->flag & HA_SWAP_KEY) + { + mi_float8store(key, val); + } + else + { + float8store((uchar *)key, val); + } + key += length; + len+= length; + } + _ma_dpointer(info->s, key, filepos); + ret_key->keyinfo= keyinfo; + ret_key->data_length= len; + ret_key->ref_length= info->s->rec_reflength; + ret_key->flag= 0; + if (_ma_have_versioning(info) && trid) + { + ret_key->ref_length+= transid_store_packed(info, + key + ret_key->ref_length, + trid); + } + DBUG_EXECUTE("key", _ma_print_key(DBUG_FILE, ret_key);); + DBUG_RETURN(ret_key); +} + + +/* + Calculate minimal bounding rectangle (mbr) of the spatial object + stored in "well-known binary representation" (wkb) format. +*/ + +static int sp_mbr_from_wkb(uchar *wkb, uint size, uint n_dims, double *mbr) +{ + uint i; + + for (i=0; i < n_dims; ++i) + { + mbr[i * 2] = DBL_MAX; + mbr[i * 2 + 1] = -DBL_MAX; + } + + return sp_get_geometry_mbr(&wkb, wkb + size, n_dims, mbr, 1); +} + +/* + Add one point stored in wkb to mbr +*/ + +static int sp_add_point_to_mbr(uchar *(*wkb), uchar *end, uint n_dims, + uchar byte_order __attribute__((unused)), + double *mbr) +{ + double ord; + double *mbr_end= mbr + n_dims * 2; + + while (mbr < mbr_end) + { + if ((*wkb) > end - 8) + return -1; + float8get(ord, (const uchar*) *wkb); + (*wkb)+= 8; + if (ord < *mbr) + *mbr= ord; + mbr++; + if (ord > *mbr) + *mbr= ord; + mbr++; + } + return 0; +} + + +static int sp_get_point_mbr(uchar *(*wkb), uchar *end, uint n_dims, + uchar byte_order, double *mbr) +{ + return sp_add_point_to_mbr(wkb, end, n_dims, byte_order, mbr); +} + + +static int sp_get_linestring_mbr(uchar *(*wkb), uchar *end, uint n_dims, + uchar byte_order, double *mbr) +{ + uint n_points; + + n_points = uint4korr(*wkb); + (*wkb) += 4; + for (; n_points > 0; --n_points) + { + /* Add next point to mbr */ + if (sp_add_point_to_mbr(wkb, end, n_dims, byte_order, mbr)) + return -1; + } + return 0; +} + + +static int sp_get_polygon_mbr(uchar *(*wkb), uchar *end, uint n_dims, + uchar byte_order, double *mbr) +{ + uint n_linear_rings; + uint n_points; + + n_linear_rings = uint4korr((*wkb)); + (*wkb) += 4; + + for (; n_linear_rings > 0; --n_linear_rings) + { + n_points = uint4korr((*wkb)); + (*wkb) += 4; + for (; n_points > 0; --n_points) + { + /* Add next point to mbr */ + if (sp_add_point_to_mbr(wkb, end, n_dims, byte_order, mbr)) + return -1; + } + } + return 0; +} + +static int sp_get_geometry_mbr(uchar *(*wkb), uchar *end, uint n_dims, + double *mbr, int top) +{ + int res; + uchar byte_order; + uint wkb_type; + + byte_order = *(*wkb); + ++(*wkb); + + wkb_type = uint4korr((*wkb)); + (*wkb) += 4; + + switch ((enum wkbType) wkb_type) + { + case wkbPoint: + res = sp_get_point_mbr(wkb, end, n_dims, byte_order, mbr); + break; + case wkbLineString: + res = sp_get_linestring_mbr(wkb, end, n_dims, byte_order, mbr); + break; + case wkbPolygon: + res = sp_get_polygon_mbr(wkb, end, n_dims, byte_order, mbr); + break; + case wkbMultiPoint: + { + uint n_items; + n_items = uint4korr((*wkb)); + (*wkb) += 4; + for (; n_items > 0; --n_items) + { + byte_order = *(*wkb); + ++(*wkb); + (*wkb) += 4; + if (sp_get_point_mbr(wkb, end, n_dims, byte_order, mbr)) + return -1; + } + res = 0; + break; + } + case wkbMultiLineString: + { + uint n_items; + n_items = uint4korr((*wkb)); + (*wkb) += 4; + for (; n_items > 0; --n_items) + { + byte_order = *(*wkb); + ++(*wkb); + (*wkb) += 4; + if (sp_get_linestring_mbr(wkb, end, n_dims, byte_order, mbr)) + return -1; + } + res = 0; + break; + } + case wkbMultiPolygon: + { + uint n_items; + n_items = uint4korr((*wkb)); + (*wkb) += 4; + for (; n_items > 0; --n_items) + { + byte_order = *(*wkb); + ++(*wkb); + (*wkb) += 4; + if (sp_get_polygon_mbr(wkb, end, n_dims, byte_order, mbr)) + return -1; + } + res = 0; + break; + } + case wkbGeometryCollection: + { + uint n_items; + + if (!top) + return -1; + + n_items = uint4korr((*wkb)); + (*wkb) += 4; + for (; n_items > 0; --n_items) + { + if (sp_get_geometry_mbr(wkb, end, n_dims, mbr, 0)) + return -1; + } + res = 0; + break; + } + default: + res = -1; + } + return res; +} + +#endif /*HAVE_SPATIAL*/ diff --git a/storage/maria/ma_sp_test.c b/storage/maria/ma_sp_test.c new file mode 100644 index 00000000000..b8c00753acb --- /dev/null +++ b/storage/maria/ma_sp_test.c @@ -0,0 +1,568 @@ +/* Copyright (C) 2006 MySQL AB & MySQL Finland AB & TCX DataKonsult 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 */ + +/* Testing of the basic functions of a MARIA spatial table */ +/* Written by Alex Barkov, who has a shared copyright to this code */ + +#include "maria.h" + +#ifdef HAVE_SPATIAL +#include "ma_sp_defs.h" + +#define MAX_REC_LENGTH 1024 +#define KEYALG HA_KEY_ALG_RTREE + +static void create_linestring(uchar *record,uint rownr); +static void print_record(uchar * record,my_off_t offs,const char * tail); + +static void create_key(uchar *key,uint rownr); +static void print_key(const uchar *key,const char * tail); + +static int run_test(const char *filename); +static int read_with_pos(MARIA_HA * file, int silent); + +static int maria_rtree_CreateLineStringWKB(double *ords, uint n_dims, uint n_points, + uchar *wkb); +static void maria_rtree_PrintWKB(uchar *wkb, uint n_dims); + +static char blob_key[MAX_REC_LENGTH]; + + +int main(int argc __attribute__((unused)),char *argv[]) +{ + MY_INIT(argv[0]); + maria_init(); + exit(run_test("sp_test")); +} + + +int run_test(const char *filename) +{ + MARIA_HA *file; + MARIA_UNIQUEDEF uniquedef; + MARIA_CREATE_INFO create_info; + MARIA_COLUMNDEF recinfo[20]; + MARIA_KEYDEF keyinfo[20]; + HA_KEYSEG keyseg[20]; + key_range min_range, max_range; + int silent=0; + int create_flag=0; + int null_fields=0; + int nrecords=30; + int uniques=0; + int i; + int error; + int row_count=0; + uchar record[MAX_REC_LENGTH]; + uchar key[MAX_REC_LENGTH]; + uchar read_record[MAX_REC_LENGTH]; + int upd=10; + ha_rows hrows; + + /* Define a column for NULLs and DEL markers*/ + + recinfo[0].type=FIELD_NORMAL; + recinfo[0].length=1; /* For NULL bits */ + + + /* Define spatial column */ + + recinfo[1].type=FIELD_BLOB; + recinfo[1].length=4 + portable_sizeof_char_ptr; + + + + /* Define a key with 1 spatial segment */ + + keyinfo[0].seg=keyseg; + keyinfo[0].keysegs=1; + keyinfo[0].flag=HA_SPATIAL; + keyinfo[0].key_alg=KEYALG; + + keyinfo[0].seg[0].type= HA_KEYTYPE_BINARY; + keyinfo[0].seg[0].flag=0; + keyinfo[0].seg[0].start= 1; + keyinfo[0].seg[0].length=1; /* Spatial ignores it anyway */ + keyinfo[0].seg[0].null_bit= null_fields ? 2 : 0; + keyinfo[0].seg[0].null_pos=0; + keyinfo[0].seg[0].language=default_charset_info->number; + keyinfo[0].seg[0].bit_start=4; /* Long BLOB */ + + + if (!silent) + printf("- Creating isam-file\n"); + + bzero((char*) &create_info,sizeof(create_info)); + create_info.max_rows=10000000; + + if (maria_create(filename, + DYNAMIC_RECORD, + 1, /* keys */ + keyinfo, + 2, /* columns */ + recinfo,uniques,&uniquedef,&create_info,create_flag)) + goto err; + + if (!silent) + printf("- Open isam-file\n"); + + if (!(file=maria_open(filename,2,HA_OPEN_ABORT_IF_LOCKED))) + goto err; + + if (!silent) + printf("- Writing key:s\n"); + + for (i=0; i<nrecords; i++ ) + { + create_linestring(record,i); + error=maria_write(file,record); + print_record(record,maria_position(file),"\n"); + if (!error) + { + row_count++; + } + else + { + printf("maria_write: %d\n", error); + goto err; + } + } + + if ((error=read_with_pos(file,silent))) + goto err; + + if (!silent) + printf("- Deleting rows with position\n"); + for (i=0; i < nrecords/4; i++) + { + my_errno=0; + bzero((char*) read_record,MAX_REC_LENGTH); + error=maria_rrnd(file,read_record,i == 0 ? 0L : HA_OFFSET_ERROR); + if (error) + { + printf("pos: %2d maria_rrnd: %3d errno: %3d\n",i,error,my_errno); + goto err; + } + print_record(read_record,maria_position(file),"\n"); + error=maria_delete(file,read_record); + if (error) + { + printf("pos: %2d maria_delete: %3d errno: %3d\n",i,error,my_errno); + goto err; + } + } + + if (!silent) + printf("- Updating rows with position\n"); + for (i=0; i < nrecords/2 ; i++) + { + my_errno=0; + bzero((char*) read_record,MAX_REC_LENGTH); + error=maria_rrnd(file,read_record,i == 0 ? 0L : HA_OFFSET_ERROR); + if (error) + { + if (error==HA_ERR_RECORD_DELETED) + continue; + printf("pos: %2d maria_rrnd: %3d errno: %3d\n",i,error,my_errno); + goto err; + } + print_record(read_record,maria_position(file),""); + create_linestring(record,i+nrecords*upd); + printf("\t-> "); + print_record(record,maria_position(file),"\n"); + error=maria_update(file,read_record,record); + if (error) + { + printf("pos: %2d maria_update: %3d errno: %3d\n",i,error,my_errno); + goto err; + } + } + + if ((error=read_with_pos(file,silent))) + goto err; + + if (!silent) + printf("- Test maria_rkey then a sequence of maria_rnext_same\n"); + + create_key(key, nrecords*4/5); + print_key(key," search for INTERSECT\n"); + + if ((error=maria_rkey(file,read_record,0,key,0,HA_READ_MBR_INTERSECT))) + { + printf("maria_rkey: %3d errno: %3d\n",error,my_errno); + goto err; + } + print_record(read_record,maria_position(file)," maria_rkey\n"); + row_count=1; + + for (;;) + { + if ((error=maria_rnext_same(file,read_record))) + { + if (error==HA_ERR_END_OF_FILE) + break; + printf("maria_next: %3d errno: %3d\n",error,my_errno); + goto err; + } + print_record(read_record,maria_position(file)," maria_rnext_same\n"); + row_count++; + } + printf(" %d rows\n",row_count); + + if (!silent) + printf("- Test maria_rfirst then a sequence of maria_rnext\n"); + + error=maria_rfirst(file,read_record,0); + if (error) + { + printf("maria_rfirst: %3d errno: %3d\n",error,my_errno); + goto err; + } + row_count=1; + print_record(read_record,maria_position(file)," maria_frirst\n"); + + for(i=0;i<nrecords;i++) { + if ((error=maria_rnext(file,read_record,0))) + { + if (error==HA_ERR_END_OF_FILE) + break; + printf("maria_next: %3d errno: %3d\n",error,my_errno); + goto err; + } + print_record(read_record,maria_position(file)," maria_rnext\n"); + row_count++; + } + printf(" %d rows\n",row_count); + + if (!silent) + printf("- Test maria_records_in_range()\n"); + + create_key(key, nrecords*upd); + print_key(key," INTERSECT\n"); + min_range.key= key; + min_range.length= 1000; /* Big enough */ + min_range.flag= HA_READ_MBR_INTERSECT; + max_range.key= record+1; + max_range.length= 1000; /* Big enough */ + max_range.flag= HA_READ_KEY_EXACT; + hrows= maria_records_in_range(file,0, &min_range, &max_range); + printf(" %ld rows\n", (long) hrows); + + if (maria_close(file)) goto err; + maria_end(); + my_end(MY_CHECK_ERROR); + + return 0; + +err: + printf("got error: %3d when using maria-database\n",my_errno); + maria_end(); + return 1; /* skip warning */ +} + + +static int read_with_pos (MARIA_HA * file,int silent) +{ + int error; + int i; + uchar read_record[MAX_REC_LENGTH]; + int rows=0; + + if (!silent) + printf("- Reading rows with position\n"); + for (i=0;;i++) + { + my_errno=0; + bzero((char*) read_record,MAX_REC_LENGTH); + error=maria_rrnd(file,read_record,i == 0 ? 0L : HA_OFFSET_ERROR); + if (error) + { + if (error==HA_ERR_END_OF_FILE) + break; + if (error==HA_ERR_RECORD_DELETED) + continue; + printf("pos: %2d maria_rrnd: %3d errno: %3d\n",i,error,my_errno); + return error; + } + rows++; + print_record(read_record,maria_position(file),"\n"); + } + printf(" %d rows\n",rows); + return 0; +} + + +#ifdef NOT_USED +static void bprint_record(uchar * record, + my_off_t offs __attribute__((unused)), + const char * tail) +{ + int i; + char * pos; + i=(unsigned char)record[0]; + printf("%02X ",i); + + for( pos=record+1, i=0; i<32; i++,pos++) + { + int b=(unsigned char)*pos; + printf("%02X",b); + } + printf("%s",tail); +} +#endif + + +static void print_record(uchar * record, my_off_t offs,const char * tail) +{ + uchar *pos; + char *ptr; + uint len; + + printf(" rec=(%d)",(unsigned char)record[0]); + pos=record+1; + len=sint4korr(pos); + pos+=4; + printf(" len=%d ",len); + memcpy_fixed(&ptr,pos,sizeof(char*)); + if (ptr) + maria_rtree_PrintWKB((uchar*) ptr,SPDIMS); + else + printf("<NULL> "); + printf(" offs=%ld ",(long int)offs); + printf("%s",tail); +} + + +#ifdef NOT_USED +static void create_point(uchar *record,uint rownr) +{ + uint tmp; + char *ptr; + char *pos=record; + double x[200]; + int i; + + for(i=0;i<SPDIMS;i++) + x[i]=rownr; + + bzero((char*) record,MAX_REC_LENGTH); + *pos=0x01; /* DEL marker */ + pos++; + + memset(blob_key,0,sizeof(blob_key)); + tmp=maria_rtree_CreatePointWKB(x,SPDIMS,blob_key); + + int4store(pos,tmp); + pos+=4; + + ptr=blob_key; + memcpy_fixed(pos,&ptr,sizeof(char*)); +} +#endif + + +static void create_linestring(uchar *record,uint rownr) +{ + uint tmp; + char *ptr; + uchar *pos= record; + double x[200]; + int i,j; + int npoints=2; + + for(j=0;j<npoints;j++) + for(i=0;i<SPDIMS;i++) + x[i+j*SPDIMS]=rownr*j; + + bzero((char*) record,MAX_REC_LENGTH); + *pos=0x01; /* DEL marker */ + pos++; + + memset(blob_key,0,sizeof(blob_key)); + tmp=maria_rtree_CreateLineStringWKB(x,SPDIMS,npoints, (uchar*) blob_key); + + int4store(pos,tmp); + pos+=4; + + ptr=blob_key; + memcpy_fixed(pos,&ptr,sizeof(char*)); +} + + +static void create_key(uchar *key,uint rownr) +{ + double c=rownr; + uchar *pos; + uint i; + + bzero(key,MAX_REC_LENGTH); + for ( pos=key, i=0; i<2*SPDIMS; i++) + { + float8store(pos,c); + pos+=sizeof(c); + } +} + +static void print_key(const uchar *key,const char * tail) +{ + double c; + uint i; + + printf(" key="); + for (i=0; i<2*SPDIMS; i++) + { + float8get(c,key); + key+=sizeof(c); + printf("%.14g ",c); + } + printf("%s",tail); +} + + +#ifdef NOT_USED + +static int maria_rtree_CreatePointWKB(double *ords, uint n_dims, uchar *wkb) +{ + uint i; + + *wkb = wkbXDR; + ++wkb; + int4store(wkb, wkbPoint); + wkb += 4; + + for (i=0; i < n_dims; ++i) + { + float8store(wkb, ords[i]); + wkb += 8; + } + return 5 + n_dims * 8; +} +#endif + + +static int maria_rtree_CreateLineStringWKB(double *ords, uint n_dims, uint n_points, + uchar *wkb) +{ + uint i; + uint n_ords = n_dims * n_points; + + *wkb = wkbXDR; + ++wkb; + int4store(wkb, wkbLineString); + wkb += 4; + int4store(wkb, n_points); + wkb += 4; + for (i=0; i < n_ords; ++i) + { + float8store(wkb, ords[i]); + wkb += 8; + } + return 9 + n_points * n_dims * 8; +} + + +static void maria_rtree_PrintWKB(uchar *wkb, uint n_dims) +{ + uint wkb_type; + + ++wkb; + wkb_type = uint4korr(wkb); + wkb += 4; + + switch ((enum wkbType)wkb_type) + { + case wkbPoint: + { + uint i; + double ord; + + printf("POINT("); + for (i=0; i < n_dims; ++i) + { + float8get(ord, wkb); + wkb += 8; + printf("%.14g", ord); + if (i < n_dims - 1) + printf(" "); + else + printf(")"); + } + break; + } + case wkbLineString: + { + uint p, i; + uint n_points; + double ord; + + printf("LineString("); + n_points = uint4korr(wkb); + wkb += 4; + for (p=0; p < n_points; ++p) + { + for (i=0; i < n_dims; ++i) + { + float8get(ord, wkb); + wkb += 8; + printf("%.14g", ord); + if (i < n_dims - 1) + printf(" "); + } + if (p < n_points - 1) + printf(", "); + else + printf(")"); + } + break; + } + case wkbPolygon: + { + printf("POLYGON(...)"); + break; + } + case wkbMultiPoint: + { + printf("MULTIPOINT(...)"); + break; + } + case wkbMultiLineString: + { + printf("MULTILINESTRING(...)"); + break; + } + case wkbMultiPolygon: + { + printf("MULTIPOLYGON(...)"); + break; + } + case wkbGeometryCollection: + { + printf("GEOMETRYCOLLECTION(...)"); + break; + } + default: + { + printf("UNKNOWN GEOMETRY TYPE"); + break; + } + } +} + +#else +int main(int argc __attribute__((unused)),char *argv[] __attribute__((unused))) +{ + exit(0); +} +#endif /*HAVE_SPATIAL*/ diff --git a/storage/maria/ma_state.c b/storage/maria/ma_state.c new file mode 100644 index 00000000000..a7e3b49e284 --- /dev/null +++ b/storage/maria/ma_state.c @@ -0,0 +1,720 @@ +/* Copyright (C) 2008 Sun AB and Michael Widenius + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You 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 */ + +/* + Functions to maintain live statistics for Maria transactional tables + and versioning for not transactional tables + + See WL#3138; Maria - fast "SELECT COUNT(*) FROM t;" and "CHECKSUM TABLE t" + for details about live number of rows and live checksums + + TODO + - Allocate MA_USED_TABLES and MA_HISTORY_STATE from a global pool (to + avoid calls to malloc() + - In trnamn_end_trans_hook(), don't call _ma_remove_not_visible_states() + every time. One could for example call it if there has been more than + 10 ended transactions since last time it was called. +*/ + +#include "maria_def.h" +#include "trnman.h" +#include "ma_blockrec.h" + +/** + @brief Setup initial start-of-transaction state for a table + + @fn _ma_setup_live_state + @param info Maria handler + + @notes + This function ensures that trn->used_tables contains a list of + start and live states for tables that are part of the transaction + and that info->state points to the current live state for the table. + + @TODO + Change trn->table_list to a hash and share->state_history to a binary tree + + @return + @retval 0 ok + @retval 1 error (out of memory) +*/ + +my_bool _ma_setup_live_state(MARIA_HA *info) +{ + TRN *trn= info->trn; + MARIA_SHARE *share= info->s; + MARIA_USED_TABLES *tables; + MARIA_STATE_HISTORY *history; + DBUG_ENTER("_ma_setup_live_state"); + + for (tables= (MARIA_USED_TABLES*) info->trn->used_tables; + tables; + tables= tables->next) + { + if (tables->share == share) + { + /* Table is already used by transaction */ + goto end; + } + } + /* Table was not used before, create new table state entry */ + if (!(tables= (MARIA_USED_TABLES*) my_malloc(sizeof(*tables), + MYF(MY_WME | MY_ZEROFILL)))) + DBUG_RETURN(1); + tables->next= trn->used_tables; + trn->used_tables= tables; + tables->share= share; + + pthread_mutex_lock(&share->intern_lock); + share->in_trans++; + DBUG_PRINT("info", ("share: 0x%lx in_trans: %d", + (ulong) share, share->in_trans)); + + history= share->state_history; + + /* + We must keep share locked to ensure that we don't access a history + link that is deleted by concurrently running checkpoint. + + It's enough to compare trids here (instead of calling + tranman_can_read_from) as history->trid is a commit_trid + */ + while (trn->trid <= history->trid) + history= history->next; + pthread_mutex_unlock(&share->intern_lock); + /* The current item can't be deleted as it's the first one visible for us */ + tables->state_start= tables->state_current= history->state; + tables->state_current.changed= tables->state_current.no_transid= 0; + + DBUG_PRINT("info", ("records: %ld", (ulong) tables->state_start.records)); + +end: + info->state_start= &tables->state_start; + info->state= &tables->state_current; + + /* + Mark in transaction state if we are not using transid (versioning) + on rows. If not, then we will in _ma_trnman_end_trans_hook() + ensure that the state is visible for all at end of transaction + */ + tables->state_current.no_transid|= !(info->row_flag & ROW_FLAG_TRANSID); + + DBUG_RETURN(0); +} + + +/** + @brief Remove states that are not visible by anyone + + @fn _ma_remove_not_visible_states() + @param org_history List to history + @param all 1 if we should delete the first state if it's + visible for all. For the moment this is only used + on close() of table. + @param trnman_is_locked Set to 1 if we have already a lock on trnman. + + @notes + The assumption is that items in the history list is ordered by + commit_trid. + + A state is not visible anymore if there is no new transaction + that has been started between the commit_trid's of two states + + As long as some states exists, we keep the newest = (last commit) + state as first state in the history. This is to allow us to just move + the history from the global list to the share when we open the table. + + Note that if 'all' is set trnman_is_locked must be 0, becasue + trnman_get_min_trid() will take a lock on trnman. + + @return + @retval Pointer to new history list +*/ + +MARIA_STATE_HISTORY +*_ma_remove_not_visible_states(MARIA_STATE_HISTORY *org_history, + my_bool all, + my_bool trnman_is_locked) +{ + TrID last_trid; + MARIA_STATE_HISTORY *history, **parent, *next; + DBUG_ENTER("_ma_remove_not_visible_states"); + + if (!org_history) + DBUG_RETURN(0); /* Not versioned table */ + + last_trid= org_history->trid; + parent= &org_history->next; + for (history= org_history->next; history; history= next) + { + next= history->next; + if (!trnman_exists_active_transactions(history->trid, last_trid, + trnman_is_locked)) + { + DBUG_PRINT("info", ("removing history->trid: %lu next: %lu", + (ulong) history->trid, (ulong) last_trid)); + my_free(history, MYF(0)); + continue; + } + *parent= history; + parent= &history->next; + last_trid= history->trid; + } + *parent= 0; + + if (all && parent == &org_history->next) + { + /* There is only one state left. Delete this if it's visible for all */ + if (last_trid < trnman_get_min_trid()) + { + my_free(org_history, MYF(0)); + org_history= 0; + } + } + DBUG_RETURN(org_history); +} + + +/** + @brief Remove not used state history + + @param share Maria table information + @param all 1 if we should delete the first state if it's + visible for all. For the moment this is only used + on close() of table. + + @notes + share and trnman are not locked. + + We must first lock trnman and then share->intern_lock. This is becasue + _ma_trnman_end_trans_hook() has a lock on trnman and then + takes share->intern_lock. +*/ + +void _ma_remove_not_visible_states_with_lock(MARIA_SHARE *share, + my_bool all) +{ + my_bool is_lock_trman; + if ((is_lock_trman= trman_is_inited())) + trnman_lock(); + + pthread_mutex_lock(&share->intern_lock); + share->state_history= _ma_remove_not_visible_states(share->state_history, + all, 1); + pthread_mutex_unlock(&share->intern_lock); + if (is_lock_trman) + trnman_unlock(); +} + + +/* + Free state history information from share->history and reset information + to current state. + + @notes + Used after repair as then all rows are visible for everyone +*/ + +void _ma_reset_state(MARIA_HA *info) +{ + MARIA_SHARE *share= info->s; + MARIA_STATE_HISTORY *history= share->state_history; + + if (history) + { + MARIA_STATE_HISTORY *next; + + /* Set the current history to current state */ + share->state_history->state= share->state.state; + /* Set current table handler to point to new history state */ + info->state= info->state_start= &share->state_history->state; + for (history= history->next ; history ; history= next) + { + next= history->next; + my_free(history, MYF(0)); + } + share->state_history->next= 0; + share->state_history->trid= 0; /* Visibile for all */ + } +} + + +/**************************************************************************** + The following functions are called by thr_lock() in threaded applications + for not transactional tables +****************************************************************************/ + +/* + Create a copy of the current status for the table + + SYNOPSIS + _ma_get_status() + param Pointer to Myisam handler + concurrent_insert Set to 1 if we are going to do concurrent inserts + (THR_WRITE_CONCURRENT_INSERT was used) +*/ + +void _ma_get_status(void* param, my_bool concurrent_insert) +{ + MARIA_HA *info=(MARIA_HA*) param; + DBUG_ENTER("_ma_get_status"); + DBUG_PRINT("info",("key_file: %ld data_file: %ld concurrent_insert: %d", + (long) info->s->state.state.key_file_length, + (long) info->s->state.state.data_file_length, + concurrent_insert)); +#ifndef DBUG_OFF + if (info->state->key_file_length > info->s->state.state.key_file_length || + info->state->data_file_length > info->s->state.state.data_file_length) + DBUG_PRINT("warning",("old info: key_file: %ld data_file: %ld", + (long) info->state->key_file_length, + (long) info->state->data_file_length)); +#endif + info->state_save= info->s->state.state; + info->state= &info->state_save; + info->state->changed= 0; + info->append_insert_at_end= concurrent_insert; + DBUG_VOID_RETURN; +} + + +void _ma_update_status(void* param) +{ + MARIA_HA *info=(MARIA_HA*) param; + /* + Because someone may have closed the table we point at, we only + update the state if its our own state. This isn't a problem as + we are always pointing at our own lock or at a read lock. + (This is enforced by thr_multi_lock.c) + */ + if (info->state == &info->state_save) + { + MARIA_SHARE *share= info->s; +#ifndef DBUG_OFF + DBUG_PRINT("info",("updating status: key_file: %ld data_file: %ld", + (long) info->state->key_file_length, + (long) info->state->data_file_length)); + if (info->state->key_file_length < share->state.state.key_file_length || + info->state->data_file_length < share->state.state.data_file_length) + DBUG_PRINT("warning",("old info: key_file: %ld data_file: %ld", + (long) share->state.state.key_file_length, + (long) share->state.state.data_file_length)); +#endif + /* + we are going to modify the state without lock's log, this would break + recovery if done with a transactional table. + */ + DBUG_ASSERT(!info->s->base.born_transactional); + share->state.state= *info->state; + info->state= &share->state.state; + } + info->append_insert_at_end= 0; +} + + +void _ma_restore_status(void *param) +{ + MARIA_HA *info= (MARIA_HA*) param; + info->state= &info->s->state.state; + info->append_insert_at_end= 0; +} + + +void _ma_copy_status(void* to, void *from) +{ + ((MARIA_HA*) to)->state= &((MARIA_HA*) from)->state_save; +} + + +void _ma_reset_update_flag(void *param, + my_bool concurrent_insert __attribute__((unused))) +{ + MARIA_HA *info=(MARIA_HA*) param; + info->state->changed= 0; +} + + +/** + @brief Check if should allow concurrent inserts + + @implementation + Allow concurrent inserts if we don't have a hole in the table or + if there is no active write lock and there is active read locks and + maria_concurrent_insert == 2. In this last case the new + row('s) are inserted at end of file instead of filling up the hole. + + The last case is to allow one to inserts into a heavily read-used table + even if there is holes. + + @notes + If there is a an rtree indexes in the table, concurrent inserts are + disabled in maria_open() + + @return + @retval 0 ok to use concurrent inserts + @retval 1 not ok +*/ + +my_bool _ma_check_status(void *param) +{ + MARIA_HA *info=(MARIA_HA*) param; + /* + The test for w_locks == 1 is here because this thread has already done an + external lock (in other words: w_locks == 1 means no other threads has + a write lock) + */ + DBUG_PRINT("info",("dellink: %ld r_locks: %u w_locks: %u", + (long) info->s->state.dellink, (uint) info->s->r_locks, + (uint) info->s->w_locks)); + return (my_bool) !(info->s->state.dellink == HA_OFFSET_ERROR || + (maria_concurrent_insert == 2 && info->s->r_locks && + info->s->w_locks == 1)); +} + + +/** + @brief write hook at end of trans to store status for all used table + + @Notes + This function must be called under trnman_lock in trnman_end_trn() + because of the following reasons: + - After trnman_end_trn() is called, the current transaction will be + regarded as committed and all used tables state_history will be + visible to other transactions. To do this, we loop over all used + tables and create/update a history entries that contains the correct + state_history for them. +*/ + +my_bool _ma_trnman_end_trans_hook(TRN *trn, my_bool commit, + my_bool active_transactions) +{ + my_bool error= 0; + MARIA_USED_TABLES *tables, *next; + DBUG_ENTER("_ma_trnman_end_trans_hook"); + + for (tables= (MARIA_USED_TABLES*) trn->used_tables; + tables; + tables= next) + { + MARIA_SHARE *share= tables->share; + next= tables->next; + if (commit) + { + MARIA_STATE_HISTORY *history; + + pthread_mutex_lock(&share->intern_lock); + + /* We only have to update history state if something changed */ + if (tables->state_current.changed) + { + if (tables->state_current.no_transid) + { + /* + The change was done without using transid on rows (like in + bulk insert). In this case this thread is the only one + that is using the table and all rows will be visble + for all transactions. + */ + _ma_reset_history(share); + } + else + { + if (active_transactions && share->now_transactional && + trnman_exists_active_transactions(share->state_history->trid, + trn->commit_trid, 1)) + { + /* + There exist transactions that are still using the current + share->state_history. Create a new history item for this + commit and add it first in the state_history list. This + ensures that all history items are stored in the list in + decresing trid order. + */ + if (!(history= my_malloc(sizeof(*history), MYF(MY_WME)))) + { + /* purecov: begin inspected */ + error= 1; + pthread_mutex_unlock(&share->intern_lock); + my_free(tables, MYF(0)); + continue; + /* purecov: end */ + } + history->state= share->state_history->state; + history->next= share->state_history; + share->state_history= history; + } + else + { + /* Previous history can't be seen by anyone, reuse old memory */ + history= share->state_history; + DBUG_PRINT("info", ("removing history->trid: %lu new: %lu", + (ulong) history->trid, + (ulong) trn->commit_trid)); + } + + history->state.records+= (tables->state_current.records - + tables->state_start.records); + history->state.checksum+= (tables->state_current.checksum - + tables->state_start.checksum); + history->trid= trn->commit_trid; + + if (history->next) + { + /* Remove not visible states */ + share->state_history= _ma_remove_not_visible_states(history, 0, 1); + } + DBUG_PRINT("info", ("share: 0x%lx in_trans: %d", + (ulong) share, share->in_trans)); + } + } + share->in_trans--; + pthread_mutex_unlock(&share->intern_lock); + } + else + { +#ifndef DBUG_OFF + /* + We need to keep share->in_trans correct in the debug library + because of the assert in maria_close() + */ + pthread_mutex_lock(&share->intern_lock); + share->in_trans--; + pthread_mutex_unlock(&share->intern_lock); +#endif + } + my_free(tables, MYF(0)); + } + trn->used_tables= 0; + DBUG_RETURN(error); +} + + +/** + Remove table from trnman_list + + @notes + This is used when we unlock a table from a group of locked tables + just before doing a rename or drop table. + + share->internal_lock must be locked when function is called +*/ + +void _ma_remove_table_from_trnman(MARIA_SHARE *share, TRN *trn) +{ + MARIA_USED_TABLES *tables, **prev; + DBUG_ENTER("_ma_remove_table_from_trnman"); + DBUG_PRINT("enter", ("share: 0x%lx in_trans: %d", + (ulong) share, share->in_trans)); + + safe_mutex_assert_owner(&share->intern_lock); + + for (prev= (MARIA_USED_TABLES**) &trn->used_tables, tables= *prev; + tables; + tables= *prev) + { + if (tables->share == share) + { + *prev= tables->next; + share->in_trans--; + DBUG_PRINT("info", ("in_trans: %d", share->in_trans)); + my_free(tables, MYF(0)); + break; + } + prev= &tables->next; + } + DBUG_VOID_RETURN; +} + + + +/**************************************************************************** + The following functions are called by thr_lock() in threaded applications + for transactional tables. +****************************************************************************/ + +/* + Create a copy of the current status for the table + + SYNOPSIS + _ma_get_status() + param Pointer to Myisam handler + concurrent_insert Set to 1 if we are going to do concurrent inserts + (THR_WRITE_CONCURRENT_INSERT was used) +*/ + +void _ma_block_get_status(void* param, my_bool concurrent_insert) +{ + MARIA_HA *info=(MARIA_HA*) param; + DBUG_ENTER("_ma_block_get_status"); + DBUG_PRINT("info", ("concurrent_insert %d", concurrent_insert)); + info->row_base_length= info->s->base_length; + info->row_flag= info->s->base.default_row_flag; + if (concurrent_insert) + { + DBUG_ASSERT(info->lock.type == TL_WRITE_CONCURRENT_INSERT); + info->row_flag|= ROW_FLAG_TRANSID; + info->row_base_length+= TRANSID_SIZE; + } + else + { + DBUG_ASSERT(info->lock.type != TL_WRITE_CONCURRENT_INSERT); + } + + if (info->s->lock_key_trees) + { + /* + Assume for now that this doesn't fail (It can only fail in + out of memory conditions) + TODO: Fix this by having one extra state pre-allocated + */ + (void) _ma_setup_live_state(info); + } + DBUG_VOID_RETURN; +} + + +void _ma_block_update_status(void *param __attribute__((unused))) +{ +} + +void _ma_block_restore_status(void *param __attribute__((unused))) +{ +} + + +/** + Check if should allow concurrent inserts + + @return + @retval 0 ok to use concurrent inserts + @retval 1 not ok +*/ + +my_bool _ma_block_check_status(void *param __attribute__((unused))) +{ + return (my_bool) 0; +} + + +/** + Enable/disable versioning +*/ + +void maria_versioning(MARIA_HA *info, my_bool versioning) +{ + /* For now, this is a hack */ + if (info->s->have_versioning) + { + enum thr_lock_type save_lock_type; + /* Assume is a non threaded application (for now) */ + info->s->lock_key_trees= 0; + /* Set up info->lock.type temporary for _ma_block_get_status() */ + save_lock_type= info->lock.type; + info->lock.type= versioning ? TL_WRITE_CONCURRENT_INSERT : TL_WRITE; + _ma_block_get_status((void*) info, versioning); + info->lock.type= save_lock_type; + } +} + + +/** + Update data_file_length to new length + + NOTES + Only used by block records +*/ + +void _ma_set_share_data_file_length(MARIA_SHARE *share, ulonglong new_length) +{ + pthread_mutex_lock(&share->intern_lock); + if (share->state.state.data_file_length < new_length) + share->state.state.data_file_length= new_length; + pthread_mutex_unlock(&share->intern_lock); +} + + +/** + Copy state information that where updated while the table was used + in not transactional mode +*/ + +void _ma_copy_nontrans_state_information(MARIA_HA *info) +{ + info->s->state.state.records= info->state->records; + info->s->state.state.checksum= info->state->checksum; +} + + +void _ma_reset_history(MARIA_SHARE *share) +{ + MARIA_STATE_HISTORY *history, *next; + DBUG_ENTER("_ma_reset_history"); + + share->state_history->trid= 0; /* Visibly by all */ + share->state_history->state= share->state.state; + history= share->state_history->next; + share->state_history->next= 0; + + for (; history; history= next) + { + next= history->next; + my_free(history, MYF(0)); + } + DBUG_VOID_RETURN; +} + + +/**************************************************************************** + Virtual functions to check if row is visible +****************************************************************************/ + +/** + Row is always visible + This is for tables without concurrent insert +*/ + +my_bool _ma_row_visible_always(MARIA_HA *info __attribute__((unused))) +{ + return 1; +} + + +/** + Row visibility for non transactional tables with concurrent insert + + @implementation + When we got our table lock, we saved the current + data_file_length. Concurrent inserts always go to the end of the + file. So we can test if the found key references a new record. +*/ + +my_bool _ma_row_visible_non_transactional_table(MARIA_HA *info) +{ + return info->cur_row.lastpos < info->state->data_file_length; +} + + +/** + Row visibility for transactional tables with versioning + + + @TODO + Add test if found key was marked deleted and it was deleted by + us. In that case we should return 0 +*/ + +my_bool _ma_row_visible_transactional_table(MARIA_HA *info) +{ + return trnman_can_read_from(info->trn, info->cur_row.trid); +} diff --git a/storage/maria/ma_state.h b/storage/maria/ma_state.h new file mode 100644 index 00000000000..f3317f0084a --- /dev/null +++ b/storage/maria/ma_state.h @@ -0,0 +1,85 @@ +/* Copyright (C) 2008 Sun AB & Michael Widenius + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You 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 */ + +/* Struct to store tables in use by one transaction */ + +typedef struct st_maria_status_info +{ + ha_rows records; /* Rows in table */ + ha_rows del; /* Removed rows */ + my_off_t empty; /* lost space in datafile */ + my_off_t key_empty; /* lost space in indexfile */ + my_off_t key_file_length; + my_off_t data_file_length; + ha_checksum checksum; + uint32 changed:1, /* Set if table was changed */ + no_transid:1; /* Set if no transid was set on rows */ +} MARIA_STATUS_INFO; + + +typedef struct st_used_tables { + struct st_used_tables *next; + struct st_maria_share *share; + MARIA_STATUS_INFO state_current; + MARIA_STATUS_INFO state_start; +} MARIA_USED_TABLES; + + +/* Struct to store commit state at different times */ + +typedef struct st_state_history { + struct st_state_history *next; + TrID trid; + MARIA_STATUS_INFO state; +} MARIA_STATE_HISTORY; + + +/* struct to remember history for closed tables */ + +typedef struct st_state_history_closed { + LSN create_rename_lsn; + MARIA_STATE_HISTORY *state_history; +} MARIA_STATE_HISTORY_CLOSED; + + +my_bool _ma_setup_live_state(MARIA_HA *info); +MARIA_STATE_HISTORY *_ma_remove_not_visible_states(MARIA_STATE_HISTORY + *org_history, + my_bool all, + my_bool trman_is_locked); +void _ma_reset_state(MARIA_HA *info); +void _ma_get_status(void* param, my_bool concurrent_insert); +void _ma_update_status(void* param); +void _ma_restore_status(void *param); +void _ma_copy_status(void* to, void *from); +void _ma_reset_update_flag(void *param, my_bool concurrent_insert); +my_bool _ma_check_status(void *param); +void _ma_block_get_status(void* param, my_bool concurrent_insert); +void _ma_block_update_status(void *param); +void _ma_block_restore_status(void *param); +my_bool _ma_block_check_status(void *param); +void maria_versioning(MARIA_HA *info, my_bool versioning); +void _ma_set_share_data_file_length(struct st_maria_share *share, + ulonglong new_length); +void _ma_copy_nontrans_state_information(MARIA_HA *info); +my_bool _ma_trnman_end_trans_hook(TRN *trn, my_bool commit, + my_bool active_transactions); +my_bool _ma_row_visible_always(MARIA_HA *info); +my_bool _ma_row_visible_non_transactional_table(MARIA_HA *info); +my_bool _ma_row_visible_transactional_table(MARIA_HA *info); +void _ma_remove_not_visible_states_with_lock(struct st_maria_share *share, + my_bool all); +void _ma_remove_table_from_trnman(struct st_maria_share *share, TRN *trn); +void _ma_reset_history(struct st_maria_share *share); diff --git a/storage/maria/ma_static.c b/storage/maria/ma_static.c new file mode 100644 index 00000000000..b4589469caf --- /dev/null +++ b/storage/maria/ma_static.c @@ -0,0 +1,107 @@ +/* Copyright (C) 2006 MySQL AB & MySQL Finland AB & TCX DataKonsult 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 */ + + +/* + Static variables for MARIA library. All definied here for easy making of + a shared library +*/ + +#ifndef _global_h +#include "maria_def.h" +#include "trnman.h" +#endif + +LIST *maria_open_list=0; +uchar maria_file_magic[]= +{ (uchar) 254, (uchar) 254, (uchar) 9, '\003', }; +uchar maria_pack_file_magic[]= +{ (uchar) 254, (uchar) 254, (uchar) 10, '\001', }; +/* Unique number for this maria instance */ +uchar maria_uuid[MY_UUID_SIZE]; +uint maria_quick_table_bits=9; +ulong maria_block_size= MARIA_KEY_BLOCK_LENGTH; +my_bool maria_flush= 0, maria_single_user= 0; +my_bool maria_delay_key_write= 0, maria_page_checksums= 1; +my_bool maria_inited= FALSE; +my_bool maria_in_ha_maria= FALSE; /* If used from ha_maria or not */ +pthread_mutex_t THR_LOCK_maria; +#if defined(THREAD) && !defined(DONT_USE_RW_LOCKS) +ulong maria_concurrent_insert= 2; +#else +ulong maria_concurrent_insert= 0; +#endif +my_off_t maria_max_temp_length= MAX_FILE_SIZE; +ulong maria_bulk_insert_tree_size=8192*1024; +ulong maria_data_pointer_size= 4; + +PAGECACHE maria_pagecache_var; +PAGECACHE *maria_pagecache= &maria_pagecache_var; + +PAGECACHE maria_log_pagecache_var; +PAGECACHE *maria_log_pagecache= &maria_log_pagecache_var; +MY_TMPDIR *maria_tmpdir; /* Tempdir for redo */ +char *maria_data_root; +HASH maria_stored_state; + +/** + @brief when transactionality does not matter we can use this transaction + + Used in external programs like ma_test*, and also internally inside + libmaria when there is no transaction around and the operation isn't + transactional (CREATE/DROP/RENAME/OPTIMIZE/REPAIR). +*/ +TRN dummy_transaction_object; + +/* a WT_RESOURCE_TYPE for transactions waiting on a unique key conflict */ +WT_RESOURCE_TYPE ma_rc_dup_unique={ wt_resource_id_memcmp, 0}; + +/* Enough for comparing if number is zero */ +uchar maria_zero_string[]= {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}; + +/* + read_vec[] is used for converting between P_READ_KEY.. and SEARCH_ + Position is , == , >= , <= , > , < +*/ + +uint32 maria_read_vec[]= +{ + SEARCH_FIND, /* HA_READ_KEY_EXACT */ + SEARCH_FIND | SEARCH_BIGGER, /* HA_READ_KEY_OR_NEXT */ + SEARCH_FIND | SEARCH_SMALLER, /* HA_READ_KEY_OR_PREV */ + SEARCH_NO_FIND | SEARCH_BIGGER, /* HA_READ_AFTER_KEY */ + SEARCH_NO_FIND | SEARCH_SMALLER, /* HA_READ_BEFORE_KEY */ + SEARCH_FIND | SEARCH_PART_KEY, /* HA_READ_PREFIX */ + SEARCH_LAST, /* HA_READ_PREFIX_LAST */ + SEARCH_LAST | SEARCH_SMALLER, /* HA_READ_PREFIX_LAST_OR_PREV */ + MBR_CONTAIN, /* HA_READ_MBR_CONTAIN */ + MBR_INTERSECT, /* HA_READ_MBR_INTERSECT */ + MBR_WITHIN, /* HA_READ_MBR_WITHIN */ + MBR_DISJOINT, /* HA_READ_MBR_DISJOINT */ + MBR_EQUAL /* HA_READ_MBR_EQUAL */ +}; + +uint32 maria_readnext_vec[]= +{ + SEARCH_BIGGER, SEARCH_BIGGER, SEARCH_SMALLER, SEARCH_BIGGER, SEARCH_SMALLER, + SEARCH_BIGGER, SEARCH_SMALLER, SEARCH_SMALLER +}; + +static int always_valid(const char *filename __attribute__((unused))) +{ + return 0; +} + +int (*maria_test_invalid_symlink)(const char *filename)= always_valid; diff --git a/storage/maria/ma_statrec.c b/storage/maria/ma_statrec.c new file mode 100644 index 00000000000..0aa3a3acbc1 --- /dev/null +++ b/storage/maria/ma_statrec.c @@ -0,0 +1,302 @@ +/* Copyright (C) 2006 MySQL AB & MySQL Finland AB & TCX DataKonsult 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 */ + + /* Functions to handle fixed-length-records */ + +#include "maria_def.h" + + +my_bool _ma_write_static_record(MARIA_HA *info, const uchar *record) +{ + uchar temp[8]; /* max pointer length */ + if (info->s->state.dellink != HA_OFFSET_ERROR && + !info->append_insert_at_end) + { + my_off_t filepos=info->s->state.dellink; + info->rec_cache.seek_not_done=1; /* We have done a seek */ + if (info->s->file_read(info, &temp[0],info->s->base.rec_reflength, + info->s->state.dellink+1, + MYF(MY_NABP))) + goto err; + info->s->state.dellink= _ma_rec_pos(info->s, temp); + info->state->del--; + info->state->empty-=info->s->base.pack_reclength; + if (info->s->file_write(info, record, info->s->base.reclength, + filepos, MYF(MY_NABP))) + goto err; + } + else + { + if (info->state->data_file_length > info->s->base.max_data_file_length- + info->s->base.pack_reclength) + { + my_errno=HA_ERR_RECORD_FILE_FULL; + return(2); + } + if (info->opt_flag & WRITE_CACHE_USED) + { /* Cash in use */ + if (my_b_write(&info->rec_cache, record, + info->s->base.reclength)) + goto err; + if (info->s->base.pack_reclength != info->s->base.reclength) + { + uint length=info->s->base.pack_reclength - info->s->base.reclength; + bzero(temp,length); + if (my_b_write(&info->rec_cache, temp,length)) + goto err; + } + } + else + { + info->rec_cache.seek_not_done=1; /* We have done a seek */ + if (info->s->file_write(info, record, info->s->base.reclength, + info->state->data_file_length, + info->s->write_flag)) + goto err; + if (info->s->base.pack_reclength != info->s->base.reclength) + { + uint length=info->s->base.pack_reclength - info->s->base.reclength; + bzero(temp,length); + if (info->s->file_write(info, temp,length, + info->state->data_file_length+ + info->s->base.reclength, + info->s->write_flag)) + goto err; + } + } + info->state->data_file_length+=info->s->base.pack_reclength; + info->s->state.split++; + } + return 0; + err: + return 1; +} + +my_bool _ma_update_static_record(MARIA_HA *info, MARIA_RECORD_POS pos, + const uchar *oldrec __attribute__ ((unused)), + const uchar *record) +{ + info->rec_cache.seek_not_done=1; /* We have done a seek */ + return (info->s->file_write(info, + record, info->s->base.reclength, + pos, + MYF(MY_NABP)) != 0); +} + + +my_bool _ma_delete_static_record(MARIA_HA *info, + const uchar *record __attribute__ ((unused))) +{ + uchar temp[9]; /* 1+sizeof(uint32) */ + info->state->del++; + info->state->empty+=info->s->base.pack_reclength; + temp[0]= '\0'; /* Mark that record is deleted */ + _ma_dpointer(info->s, temp+1, info->s->state.dellink); + info->s->state.dellink= info->cur_row.lastpos; + info->rec_cache.seek_not_done=1; + return (info->s->file_write(info, temp, 1+info->s->rec_reflength, + info->cur_row.lastpos, MYF(MY_NABP)) != 0); +} + + +my_bool _ma_cmp_static_record(register MARIA_HA *info, + register const uchar *old) +{ + DBUG_ENTER("_ma_cmp_static_record"); + + /* We are going to do changes; dont let anybody disturb */ + dont_break(); /* Dont allow SIGHUP or SIGINT */ + + if (info->opt_flag & WRITE_CACHE_USED) + { + if (flush_io_cache(&info->rec_cache)) + { + DBUG_RETURN(1); + } + info->rec_cache.seek_not_done=1; /* We have done a seek */ + } + + if ((info->opt_flag & READ_CHECK_USED)) + { /* If check isn't disabled */ + info->rec_cache.seek_not_done=1; /* We have done a seek */ + if (info->s->file_read(info, info->rec_buff, info->s->base.reclength, + info->cur_row.lastpos, MYF(MY_NABP))) + DBUG_RETURN(1); + if (memcmp(info->rec_buff, old, (uint) info->s->base.reclength)) + { + DBUG_DUMP("read",old,info->s->base.reclength); + DBUG_DUMP("disk",info->rec_buff,info->s->base.reclength); + my_errno=HA_ERR_RECORD_CHANGED; /* Record have changed */ + DBUG_RETURN(1); + } + } + DBUG_RETURN(0); +} + + +my_bool _ma_cmp_static_unique(MARIA_HA *info, MARIA_UNIQUEDEF *def, + const uchar *record, MARIA_RECORD_POS pos) +{ + DBUG_ENTER("_ma_cmp_static_unique"); + + info->rec_cache.seek_not_done=1; /* We have done a seek */ + if (info->s->file_read(info, info->rec_buff, info->s->base.reclength, + pos, MYF(MY_NABP))) + DBUG_RETURN(1); + DBUG_RETURN(_ma_unique_comp(def, record, info->rec_buff, + def->null_are_equal)); +} + + +/* + Read a fixed-length-record + + RETURN + 0 Ok + 1 record delete + -1 on read-error or locking-error +*/ + +int _ma_read_static_record(register MARIA_HA *info, register uchar *record, + MARIA_RECORD_POS pos) +{ + int error; + DBUG_ENTER("_ma_read_static_record"); + + if (pos != HA_OFFSET_ERROR) + { + if (info->opt_flag & WRITE_CACHE_USED && + info->rec_cache.pos_in_file <= pos && + flush_io_cache(&info->rec_cache)) + DBUG_RETURN(my_errno); + info->rec_cache.seek_not_done=1; /* We have done a seek */ + + error= (int) info->s->file_read(info, record,info->s->base.reclength, + pos, MYF(MY_NABP)); + if (! error) + { + fast_ma_writeinfo(info); + if (!*record) + { + /* Record is deleted */ + DBUG_PRINT("warning", ("Record is deleted")); + DBUG_RETURN((my_errno=HA_ERR_RECORD_DELETED)); + } + info->update|= HA_STATE_AKTIV; /* Record is read */ + DBUG_RETURN(0); + } + } + fast_ma_writeinfo(info); /* No such record */ + DBUG_RETURN(my_errno); +} + + +/** + @brief Read record from given position or next record + + @note + When scanning, this function will return HA_ERR_RECORD_DELETED + for deleted rows even if skip_deleted_blocks is set. + The reason for this is to allow the caller to calculate the record + position without having to do call maria_position() for each record. +*/ + +int _ma_read_rnd_static_record(MARIA_HA *info, uchar *buf, + MARIA_RECORD_POS filepos, + my_bool skip_deleted_blocks) +{ + int locked,error,cache_read; + uint cache_length; + MARIA_SHARE *share= info->s; + DBUG_ENTER("_ma_read_rnd_static_record"); + + cache_read=0; + cache_length=0; + if (info->opt_flag & READ_CACHE_USED) + { /* Cache in use */ + if (filepos == my_b_tell(&info->rec_cache) && + (skip_deleted_blocks || !filepos)) + { + cache_read=1; /* Read record using cache */ + cache_length= (uint) (info->rec_cache.read_end - + info->rec_cache.read_pos); + } + else + info->rec_cache.seek_not_done=1; /* Filepos is changed */ + } + locked=0; + if (info->lock_type == F_UNLCK) + { + if (filepos >= info->state->data_file_length) + { /* Test if new records */ + if (_ma_readinfo(info,F_RDLCK,0)) + DBUG_RETURN(my_errno); + locked=1; + } + else + { /* We don't nead new info */ +#ifndef UNSAFE_LOCKING + if ((! cache_read || share->base.reclength > cache_length) && + share->tot_locks == 0) + { /* record not in cache */ + locked=1; + } +#else + info->tmp_lock_type=F_RDLCK; +#endif + } + } + if (filepos >= info->state->data_file_length) + { + DBUG_PRINT("test",("filepos: %ld (%ld) records: %ld del: %ld", + (long) filepos/share->base.reclength, (long) filepos, + (long) info->state->records, (long) info->state->del)); + fast_ma_writeinfo(info); + DBUG_RETURN(my_errno=HA_ERR_END_OF_FILE); + } + info->cur_row.lastpos= filepos; + info->cur_row.nextpos= filepos+share->base.pack_reclength; + + if (! cache_read) /* No cacheing */ + { + error= _ma_read_static_record(info, buf, filepos); + DBUG_RETURN(error); + } + + /* Read record with cacheing */ + error=my_b_read(&info->rec_cache, buf, share->base.reclength); + if (info->s->base.pack_reclength != info->s->base.reclength && !error) + { + uchar tmp[8]; /* Skill fill bytes */ + error=my_b_read(&info->rec_cache, tmp, + info->s->base.pack_reclength - info->s->base.reclength); + } + if (locked) + VOID(_ma_writeinfo(info,0)); /* Unlock keyfile */ + if (!error) + { + if (!buf[0]) + { /* Record is removed */ + DBUG_RETURN(my_errno=HA_ERR_RECORD_DELETED); + } + /* Found and may be updated */ + info->update|= HA_STATE_AKTIV | HA_STATE_KEY_CHANGED; + DBUG_RETURN(0); + } + /* my_errno should be set if rec_cache.error == -1 */ + if (info->rec_cache.error != -1 || my_errno == 0) + my_errno=HA_ERR_WRONG_IN_RECORD; + DBUG_RETURN(my_errno); /* Something wrong (EOF?) */ +} diff --git a/storage/maria/ma_test1.c b/storage/maria/ma_test1.c new file mode 100644 index 00000000000..affa3a71634 --- /dev/null +++ b/storage/maria/ma_test1.c @@ -0,0 +1,899 @@ +/* Copyright (C) 2006 MySQL AB & MySQL Finland AB & TCX DataKonsult 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 */ + +/* Testing of the basic functions of a MARIA table */ + +#include "maria_def.h" +#include <my_getopt.h> +#include <m_string.h> +#include "ma_control_file.h" +#include "ma_loghandler.h" +#include "ma_checkpoint.h" +#include "trnman.h" + +extern PAGECACHE *maria_log_pagecache; +extern char *maria_data_root; + +#define MAX_REC_LENGTH 1024 + +static void usage(); + +static int rec_pointer_size=0, flags[50], testflag, checkpoint; +static int key_field=FIELD_SKIP_PRESPACE,extra_field=FIELD_SKIP_ENDSPACE; +static int key_type=HA_KEYTYPE_NUM; +static int create_flag=0; +static ulong blob_length; +static enum data_file_type record_type= DYNAMIC_RECORD; + +static uint insert_count, update_count, remove_count; +static uint pack_keys=0, pack_seg=0, key_length; +static uint unique_key=HA_NOSAME; +static uint die_in_middle_of_transaction; +static my_bool pagecacheing, null_fields, silent, skip_update, opt_unique; +static my_bool verbose, skip_delete, transactional; +static my_bool opt_versioning= 0; +static MARIA_COLUMNDEF recinfo[4]; +static MARIA_KEYDEF keyinfo[10]; +static HA_KEYSEG keyseg[10]; +static HA_KEYSEG uniqueseg[10]; + +static int run_test(const char *filename); +static void get_options(int argc, char *argv[]); +static void create_key(uchar *key,uint rownr); +static void create_record(uchar *record,uint rownr); +static void update_record(uchar *record); + + +/* + These are here only for testing of recovery with undo. We are not + including maria_def.h here as this test is also to be an example of + how to use maria outside of the maria directory +*/ + +extern int _ma_flush_table_files(MARIA_HA *info, uint flush_data_or_index, + enum flush_type flush_type_for_data, + enum flush_type flush_type_for_index); +#define MARIA_FLUSH_DATA 1 + + +int main(int argc,char *argv[]) +{ +#if defined(SAFE_MUTEX) && defined(THREAD) + safe_mutex_deadlock_detector= 1; +#endif + MY_INIT(argv[0]); + get_options(argc,argv); + maria_data_root= (char *)"."; + /* Maria requires that we always have a page cache */ + if (maria_init() || + (init_pagecache(maria_pagecache, maria_block_size * 16, 0, 0, + maria_block_size, MY_WME) == 0) || + ma_control_file_open(TRUE, TRUE) || + (init_pagecache(maria_log_pagecache, + TRANSLOG_PAGECACHE_SIZE, 0, 0, + TRANSLOG_PAGE_SIZE, MY_WME) == 0) || + translog_init(maria_data_root, TRANSLOG_FILE_SIZE, + 0, 0, maria_log_pagecache, + TRANSLOG_DEFAULT_FLAGS, 0) || + (transactional && (trnman_init(0) || ma_checkpoint_init(0)))) + { + fprintf(stderr, "Error in initialization\n"); + exit(1); + } + if (opt_versioning) + init_thr_lock(); + + exit(run_test("test1")); +} + + +static int run_test(const char *filename) +{ + MARIA_HA *file; + int i,j= 0,error,deleted,rec_length,uniques=0; + uint offset_to_key; + ha_rows found,row_count; + uchar record[MAX_REC_LENGTH],key[MAX_REC_LENGTH],read_record[MAX_REC_LENGTH]; + MARIA_UNIQUEDEF uniquedef; + MARIA_CREATE_INFO create_info; + + if (die_in_middle_of_transaction) + null_fields= 1; + + bzero((char*) recinfo,sizeof(recinfo)); + bzero((char*) &create_info,sizeof(create_info)); + + /* First define 2 columns */ + create_info.null_bytes= 1; + recinfo[0].type= key_field; + recinfo[0].length= (key_field == FIELD_BLOB ? 4+portable_sizeof_char_ptr : + key_length); + if (key_field == FIELD_VARCHAR) + recinfo[0].length+= HA_VARCHAR_PACKLENGTH(key_length); + recinfo[1].type=extra_field; + recinfo[1].length= (extra_field == FIELD_BLOB ? 4 + portable_sizeof_char_ptr : 24); + if (extra_field == FIELD_VARCHAR) + recinfo[1].length+= HA_VARCHAR_PACKLENGTH(recinfo[1].length); + recinfo[1].null_bit= null_fields ? 2 : 0; + + if (opt_unique) + { + recinfo[2].type=FIELD_CHECK; + recinfo[2].length=MARIA_UNIQUE_HASH_LENGTH; + } + rec_length= recinfo[0].length + recinfo[1].length + recinfo[2].length + + create_info.null_bytes; + + if (key_type == HA_KEYTYPE_VARTEXT1 && + key_length > 255) + key_type= HA_KEYTYPE_VARTEXT2; + + /* Define a key over the first column */ + keyinfo[0].seg=keyseg; + keyinfo[0].keysegs=1; + keyinfo[0].block_length= 0; /* Default block length */ + keyinfo[0].key_alg=HA_KEY_ALG_BTREE; + keyinfo[0].seg[0].type= key_type; + keyinfo[0].seg[0].flag= pack_seg; + keyinfo[0].seg[0].start=1; + keyinfo[0].seg[0].length=key_length; + keyinfo[0].seg[0].null_bit= null_fields ? 2 : 0; + keyinfo[0].seg[0].null_pos=0; + keyinfo[0].seg[0].language= default_charset_info->number; + if (pack_seg & HA_BLOB_PART) + { + keyinfo[0].seg[0].bit_start=4; /* Length of blob length */ + } + keyinfo[0].flag = (uint8) (pack_keys | unique_key); + + bzero((uchar*) flags,sizeof(flags)); + if (opt_unique) + { + uint start; + uniques=1; + bzero((char*) &uniquedef,sizeof(uniquedef)); + bzero((char*) uniqueseg,sizeof(uniqueseg)); + uniquedef.seg=uniqueseg; + uniquedef.keysegs=2; + + /* Make a unique over all columns (except first NULL fields) */ + for (i=0, start=1 ; i < 2 ; i++) + { + uniqueseg[i].start=start; + start+=recinfo[i].length; + uniqueseg[i].length=recinfo[i].length; + uniqueseg[i].language= default_charset_info->number; + } + uniqueseg[0].type= key_type; + uniqueseg[0].null_bit= null_fields ? 2 : 0; + uniqueseg[1].type= HA_KEYTYPE_TEXT; + if (extra_field == FIELD_BLOB) + { + uniqueseg[1].length=0; /* The whole blob */ + uniqueseg[1].bit_start=4; /* long blob */ + uniqueseg[1].flag|= HA_BLOB_PART; + } + else if (extra_field == FIELD_VARCHAR) + { + uniqueseg[1].flag|= HA_VAR_LENGTH_PART; + uniqueseg[1].type= (HA_VARCHAR_PACKLENGTH(recinfo[1].length-1) == 1 ? + HA_KEYTYPE_VARTEXT1 : HA_KEYTYPE_VARTEXT2); + } + } + else + uniques=0; + + offset_to_key= test(null_fields); + if (key_field == FIELD_BLOB || key_field == FIELD_VARCHAR) + offset_to_key+= 2; + + if (!silent) + printf("- Creating maria file\n"); + create_info.max_rows=(ulong) (rec_pointer_size ? + (1L << (rec_pointer_size*8))/40 : + 0); + create_info.transactional= transactional; + if (maria_create(filename, record_type, 1, keyinfo,2+opt_unique,recinfo, + uniques, &uniquedef, &create_info, + create_flag)) + goto err; + if (!(file=maria_open(filename,2,HA_OPEN_ABORT_IF_LOCKED))) + goto err; + if (!silent) + printf("- Writing key:s\n"); + + if (maria_begin(file)) + goto err; + if (opt_versioning) + maria_versioning(file, 1); + my_errno=0; + row_count=deleted=0; + for (i=49 ; i>=1 ; i-=2 ) + { + if (insert_count-- == 0) + { + if (testflag) + break; + VOID(maria_close(file)); + exit(0); + } + j=i%25 +1; + create_record(record,j); + error=maria_write(file,record); + if (!error) + row_count++; + flags[j]=1; + if (verbose || error) + printf("J= %2d maria_write: %d errno: %d\n", j,error,my_errno); + } + + if (maria_commit(file) || maria_begin(file)) + goto err; + + if (checkpoint == 1 && ma_checkpoint_execute(CHECKPOINT_MEDIUM, FALSE)) + goto err; + + if (testflag == 1) + goto end; + + /* Insert 2 rows with null values */ + if (null_fields) + { + create_record(record,0); + error=maria_write(file,record); + if (!error) + row_count++; + if (verbose || error) + printf("J= NULL maria_write: %d errno: %d\n", error,my_errno); + error=maria_write(file,record); + if (!error) + row_count++; + if (verbose || error) + printf("J= NULL maria_write: %d errno: %d\n", error,my_errno); + flags[0]=2; + } + + if (checkpoint == 2 && ma_checkpoint_execute(CHECKPOINT_MEDIUM, FALSE)) + goto err; + + if (testflag == 2) + { + printf("Terminating after inserts\n"); + goto end; + } + + if (maria_commit(file) || maria_begin(file)) + goto err; + + if (!skip_update) + { + if (opt_unique) + { + if (!silent) + printf("- Checking unique constraint\n"); + create_record(record,j); /* Check last created row */ + if (!maria_write(file,record) || my_errno != HA_ERR_FOUND_DUPP_UNIQUE) + { + printf("unique check failed\n"); + } + } + if (!silent) + printf("- Updating rows\n"); + + /* Update first last row to force extend of file */ + if (maria_rsame(file,read_record,-1)) + { + printf("Can't find last row with maria_rsame\n"); + } + else + { + memcpy(record,read_record,rec_length); + update_record(record); + if (maria_update(file,read_record,record)) + { + printf("Can't update last row: %.*s\n", + keyinfo[0].seg[0].length,read_record+1); + } + } + + /* Read through all rows and update them */ + assert(maria_scan_init(file) == 0); + + found=0; + while ((error= maria_scan(file,read_record)) == 0) + { + if (--update_count == 0) { VOID(maria_close(file)) ; exit(0) ; } + memcpy(record,read_record,rec_length); + update_record(record); + if (maria_update(file,read_record,record)) + { + printf("Can't update row: %.*s, error: %d\n", + keyinfo[0].seg[0].length,record+1,my_errno); + } + found++; + } + if (found != row_count) + printf("Found %ld of %ld rows\n", (ulong) found, (ulong) row_count); + maria_scan_end(file); + } + + if (checkpoint == 3 && ma_checkpoint_execute(CHECKPOINT_MEDIUM, FALSE)) + goto err; + + if (testflag == 3) + { + printf("Terminating after updates\n"); + goto end; + } + if (!silent) + printf("- Reopening file\n"); + if (maria_commit(file)) + goto err; + if (maria_close(file)) + goto err; + if (!(file=maria_open(filename,2,HA_OPEN_ABORT_IF_LOCKED))) + goto err; + if (maria_begin(file)) + goto err; + if (opt_versioning) + maria_versioning(file, 1); + if (!skip_delete) + { + if (!silent) + printf("- Removing keys\n"); + + for (i=0 ; i <= 10 ; i++) + { + /* + If you want to debug the problem in ma_test_recovery with BLOBs + (see @todo there), you can break out of the loop after just one + delete, it is enough, like this: + if (i==1) break; + */ + /* testing */ + if (remove_count-- == 0) + { + fprintf(stderr, + "delete-rows number of rows deleted; Going down hard!\n"); + goto end; + } + j=i*2; + if (!flags[j]) + continue; + create_key(key,j); + my_errno=0; + if ((error = maria_rkey(file, read_record, 0, key, + HA_WHOLE_KEY, HA_READ_KEY_EXACT))) + { + if (verbose || (flags[j] >= 1 || + (error && my_errno != HA_ERR_KEY_NOT_FOUND))) + printf("key: '%.*s' maria_rkey: %3d errno: %3d\n", + (int) key_length,key+offset_to_key,error,my_errno); + } + else + { + error=maria_delete(file,read_record); + if (verbose || error) + printf("key: '%.*s' maria_delete: %3d errno: %3d\n", + (int) key_length, key+offset_to_key, error, my_errno); + if (! error) + { + deleted++; + flags[j]--; + } + } + } + } + + if (checkpoint == 4 && ma_checkpoint_execute(CHECKPOINT_MEDIUM, FALSE)) + goto err; + + if (testflag == 4) + { + printf("Terminating after deletes\n"); + goto end; + } + + if (!silent) + printf("- Reading rows with key\n"); + record[1]= 0; /* For nicer printf */ + for (i=0 ; i <= 25 ; i++) + { + create_key(key,i); + my_errno=0; + error=maria_rkey(file,read_record,0,key,HA_WHOLE_KEY,HA_READ_KEY_EXACT); + if (verbose || + (error == 0 && flags[i] == 0 && unique_key) || + (error && (flags[i] != 0 || my_errno != HA_ERR_KEY_NOT_FOUND))) + { + printf("key: '%.*s' maria_rkey: %3d errno: %3d record: %s\n", + (int) key_length,key+offset_to_key,error,my_errno,record+1); + } + } + + if (!silent) + printf("- Reading rows with position\n"); + if (maria_scan_init(file)) + { + fprintf(stderr, "maria_scan_init failed\n"); + goto err; + } + + for (i=1,found=0 ; i <= 30 ; i++) + { + my_errno=0; + if ((error= maria_scan(file, read_record)) == HA_ERR_END_OF_FILE) + { + if (found != row_count-deleted) + printf("Found only %ld of %ld rows\n", (ulong) found, + (ulong) (row_count - deleted)); + break; + } + if (!error) + found++; + if (verbose || (error != 0 && error != HA_ERR_RECORD_DELETED && + error != HA_ERR_END_OF_FILE)) + { + printf("pos: %2d maria_rrnd: %3d errno: %3d record: %s\n", + i-1,error,my_errno,read_record+1); + } + } + maria_scan_end(file); + +end: + if (die_in_middle_of_transaction) + { + /* As commit record is not done, UNDO entries needs to be rolled back */ + switch (die_in_middle_of_transaction) { + case 1: + /* + Flush changed pages go to disk. That will also flush log. Recovery + will skip REDOs and apply UNDOs. + */ + _ma_flush_table_files(file, MARIA_FLUSH_DATA | MARIA_FLUSH_INDEX, + FLUSH_RELEASE, FLUSH_RELEASE); + break; + case 2: + /* + Just flush log. Pages are likely to not be on disk. Recovery will + then execute REDOs and UNDOs. + */ + if (translog_flush(file->trn->undo_lsn)) + goto err; + break; + case 3: + /* + Flush nothing. Pages and log are likely to not be on disk. Recovery + will then do nothing. + */ + break; + case 4: + /* + Flush changed data pages go to disk. Changed index pages are not + flushed. Recovery will skip some REDOs and apply UNDOs. + */ + _ma_flush_table_files(file, MARIA_FLUSH_DATA, FLUSH_RELEASE, + FLUSH_RELEASE); + /* + We have to flush log separately as the redo for the last key page + may not be flushed + */ + if (translog_flush(file->trn->undo_lsn)) + goto err; + break; + } + printf("Dying on request without maria_commit()/maria_close()\n"); + exit(0); + } + + if (maria_commit(file)) + goto err; + if (maria_close(file)) + goto err; + maria_end(); + my_end(MY_CHECK_ERROR); + + return (0); +err: + printf("got error: %3d when using maria-database\n",my_errno); + return 1; /* skip warning */ +} + + +static void create_key_part(uchar *key,uint rownr) +{ + if (!unique_key) + rownr&=7; /* Some identical keys */ + if (keyinfo[0].seg[0].type == HA_KEYTYPE_NUM) + { + sprintf((char*) key,"%*d",keyinfo[0].seg[0].length,rownr); + } + else if (keyinfo[0].seg[0].type == HA_KEYTYPE_VARTEXT1 || + keyinfo[0].seg[0].type == HA_KEYTYPE_VARTEXT2) + { /* Alpha record */ + /* Create a key that may be easily packed */ + bfill(key,keyinfo[0].seg[0].length,rownr < 10 ? 'A' : 'B'); + sprintf((char*) key+keyinfo[0].seg[0].length-2,"%-2d",rownr); + if ((rownr & 7) == 0) + { + /* Change the key to force a unpack of the next key */ + bfill(key+3,keyinfo[0].seg[0].length-5,rownr < 10 ? 'a' : 'b'); + } + } + else + { /* Alpha record */ + if (keyinfo[0].seg[0].flag & HA_SPACE_PACK) + sprintf((char*) key,"%-*d",keyinfo[0].seg[0].length,rownr); + else + { + /* Create a key that may be easily packed */ + bfill(key,keyinfo[0].seg[0].length,rownr < 10 ? 'A' : 'B'); + sprintf((char*) key+keyinfo[0].seg[0].length-2,"%-2d",rownr); + if ((rownr & 7) == 0) + { + /* Change the key to force a unpack of the next key */ + key[1]= (rownr < 10 ? 'a' : 'b'); + } + } + } +} + + +static void create_key(uchar *key,uint rownr) +{ + if (keyinfo[0].seg[0].null_bit) + { + if (rownr == 0) + { + key[0]=1; /* null key */ + key[1]=0; /* For easy print of key */ + return; + } + *key++=0; + } + if (keyinfo[0].seg[0].flag & (HA_BLOB_PART | HA_VAR_LENGTH_PART)) + { + uint tmp; + create_key_part(key+2,rownr); + tmp=strlen((char*) key+2); + int2store(key,tmp); + } + else + create_key_part(key,rownr); +} + + +static uchar blob_key[MAX_REC_LENGTH]; +static uchar blob_record[MAX_REC_LENGTH+20*20]; + + +static void create_record(uchar *record,uint rownr) +{ + uchar *pos; + bzero((char*) record,MAX_REC_LENGTH); + record[0]=1; /* delete marker */ + if (rownr == 0 && keyinfo[0].seg[0].null_bit) + record[0]|=keyinfo[0].seg[0].null_bit; /* Null key */ + + pos=record+1; + if (recinfo[0].type == FIELD_BLOB) + { + uint tmp; + uchar *ptr; + create_key_part(blob_key,rownr); + tmp=strlen((char*) blob_key); + int4store(pos,tmp); + ptr=blob_key; + memcpy_fixed(pos+4,&ptr,sizeof(char*)); + pos+=recinfo[0].length; + } + else if (recinfo[0].type == FIELD_VARCHAR) + { + uint tmp, pack_length= HA_VARCHAR_PACKLENGTH(recinfo[0].length-1); + create_key_part(pos+pack_length,rownr); + tmp= strlen((char*) pos+pack_length); + if (pack_length == 1) + *(uchar*) pos= (uchar) tmp; + else + int2store(pos,tmp); + pos+= recinfo[0].length; + } + else + { + create_key_part(pos,rownr); + pos+=recinfo[0].length; + } + if (recinfo[1].type == FIELD_BLOB) + { + uint tmp; + uchar *ptr;; + sprintf((char*) blob_record,"... row: %d", rownr); + strappend((char*) blob_record,max(MAX_REC_LENGTH-rownr,10),' '); + tmp=strlen((char*) blob_record); + int4store(pos,tmp); + ptr=blob_record; + memcpy_fixed(pos+4,&ptr,sizeof(char*)); + } + else if (recinfo[1].type == FIELD_VARCHAR) + { + uint tmp, pack_length= HA_VARCHAR_PACKLENGTH(recinfo[1].length-1); + sprintf((char*) pos+pack_length, "... row: %d", rownr); + tmp= strlen((char*) pos+pack_length); + if (pack_length == 1) + *pos= (uchar) tmp; + else + int2store(pos,tmp); + } + else + { + sprintf((char*) pos,"... row: %d", rownr); + strappend((char*) pos,recinfo[1].length,' '); + } +} + +/* change row to test re-packing of rows and reallocation of keys */ + +static void update_record(uchar *record) +{ + uchar *pos=record+1; + if (recinfo[0].type == FIELD_BLOB) + { + uchar *column,*ptr; + int length; + length=uint4korr(pos); /* Long blob */ + memcpy_fixed(&column,pos+4,sizeof(char*)); + memcpy(blob_key,column,length); /* Move old key */ + ptr=blob_key; + memcpy_fixed(pos+4,&ptr,sizeof(char*)); /* Store pointer to new key */ + if (keyinfo[0].seg[0].type != HA_KEYTYPE_NUM) + default_charset_info->cset->casedn(default_charset_info, + (char*) blob_key, length, + (char*) blob_key, length); + pos+=recinfo[0].length; + } + else if (recinfo[0].type == FIELD_VARCHAR) + { + uint pack_length= HA_VARCHAR_PACKLENGTH(recinfo[0].length-1); + uint length= pack_length == 1 ? (uint) *(uchar*) pos : uint2korr(pos); + default_charset_info->cset->casedn(default_charset_info, + (char*) pos + pack_length, length, + (char*) pos + pack_length, length); + pos+=recinfo[0].length; + } + else + { + if (keyinfo[0].seg[0].type != HA_KEYTYPE_NUM) + default_charset_info->cset->casedn(default_charset_info, + (char*) pos, keyinfo[0].seg[0].length, + (char*) pos, keyinfo[0].seg[0].length); + pos+=recinfo[0].length; + } + + if (recinfo[1].type == FIELD_BLOB) + { + uchar *column; + int length; + length=uint4korr(pos); + memcpy_fixed(&column,pos+4,sizeof(char*)); + memcpy(blob_record,column,length); + bfill(blob_record+length,20,'.'); /* Make it larger */ + length+=20; + int4store(pos,length); + column=blob_record; + memcpy_fixed(pos+4,&column,sizeof(char*)); + } + else if (recinfo[1].type == FIELD_VARCHAR) + { + /* Second field is longer than 10 characters */ + uint pack_length= HA_VARCHAR_PACKLENGTH(recinfo[1].length-1); + uint length= pack_length == 1 ? (uint) *(uchar*) pos : uint2korr(pos); + pos= record+ recinfo[1].offset; + bfill(pos+pack_length+length,recinfo[1].length-length-pack_length,'.'); + length=recinfo[1].length-pack_length; + if (pack_length == 1) + *(uchar*) pos= (uchar) length; + else + int2store(pos,length); + } + else + { + bfill(pos+recinfo[1].length-10,10,'.'); + } +} + + +static struct my_option my_long_options[] = +{ + {"checkpoint", 'H', "Checkpoint at specified stage", (uchar**) &checkpoint, + (uchar**) &checkpoint, 0, GET_INT, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + {"checksum", 'c', "Undocumented", + 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0}, +#ifndef DBUG_OFF + {"debug", '#', "Undocumented", + 0, 0, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, +#endif + {"delete-rows", 'd', "Abort after this many rows has been deleted", + (uchar**) &remove_count, (uchar**) &remove_count, 0, GET_UINT, REQUIRED_ARG, + 1000, 0, 0, 0, 0, 0}, + {"help", '?', "Display help and exit", + 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"insert-rows", 'i', "Undocumented", (uchar**) &insert_count, + (uchar**) &insert_count, 0, GET_UINT, REQUIRED_ARG, 1000, 0, 0, 0, 0, 0}, + {"key-alpha", 'a', "Use a key of type HA_KEYTYPE_TEXT", + 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"key-binary-pack", 'B', "Undocumented", + 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"key-blob", 'b', "Undocumented", + (uchar**) &blob_length, (uchar**) &blob_length, + 0, GET_ULONG, OPT_ARG, 0, 0, 0, 0, 0, 0}, + {"key-cache", 'K', "Undocumented", (uchar**) &pagecacheing, + (uchar**) &pagecacheing, 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"key-length", 'k', "Undocumented", (uchar**) &key_length, + (uchar**) &key_length, 0, GET_UINT, REQUIRED_ARG, 6, 0, 0, 0, 0, 0}, + {"key-multiple", 'm', "Don't use unique keys", + 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"key-prefix_pack", 'P', "Undocumented", + 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"key-space_pack", 'p', "Undocumented", + 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"key-varchar", 'w', "Test VARCHAR keys", + 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"null-fields", 'N', "Define fields with NULL", + (uchar**) &null_fields, (uchar**) &null_fields, 0, GET_BOOL, NO_ARG, + 0, 0, 0, 0, 0, 0}, + {"row-fixed-size", 'S', "Fixed size records", + 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"rows-in-block", 'M', "Store rows in block format", + 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"row-pointer-size", 'R', "Undocumented", (uchar**) &rec_pointer_size, + (uchar**) &rec_pointer_size, 0, GET_INT, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + {"silent", 's', "Undocumented", + (uchar**) &silent, (uchar**) &silent, 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, + 0, 0}, + {"skip-delete", 'D', "Don't test deletes", (uchar**) &skip_delete, + (uchar**) &skip_delete, 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"skip-update", 'U', "Don't test updates", (uchar**) &skip_update, + (uchar**) &skip_update, 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"testflag", 't', "Stop test at specified stage", (uchar**) &testflag, + (uchar**) &testflag, 0, GET_INT, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + {"test-undo", 'A', + "Abort hard. Used for testing recovery with undo", + (uchar**) &die_in_middle_of_transaction, + (uchar**) &die_in_middle_of_transaction, + 0, GET_INT, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + {"transactional", 'T', + "Test in transactional mode. (Only works with block format)", + (uchar**) &transactional, (uchar**) &transactional, 0, GET_BOOL, NO_ARG, + 0, 0, 0, 0, 0, 0}, + {"unique", 'E', "Check unique handling", (uchar**) &opt_unique, + (uchar**) &opt_unique, 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"update-rows", 'u', "Max number of rows to update", (uchar**) &update_count, + (uchar**) &update_count, 0, GET_UINT, REQUIRED_ARG, 1000, 0, 0, 0, 0, 0}, + {"verbose", 'v', "Be more verbose", (uchar**) &verbose, + (uchar**) &verbose, 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"version", 'V', "Print version number and exit", + 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"versioning", 'C', "Use row versioning (only works with block format)", + (uchar**) &opt_versioning, (uchar**) &opt_versioning, 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))) +{ + switch(optid) { + case 'a': + key_type= HA_KEYTYPE_TEXT; + break; + case 'c': + create_flag|= HA_CREATE_CHECKSUM | HA_CREATE_PAGE_CHECKSUM; + break; + case 'R': /* Length of record pointer */ + if (rec_pointer_size > 3) + rec_pointer_size=0; + break; + case 'P': + pack_keys= HA_PACK_KEY; /* Use prefix compression */ + break; + case 'B': + pack_keys= HA_BINARY_PACK_KEY; /* Use binary compression */ + break; + case 'M': + record_type= BLOCK_RECORD; + break; + case 'S': + if (key_field == FIELD_VARCHAR) + { + create_flag=0; /* Static sized varchar */ + record_type= STATIC_RECORD; + } + else if (key_field != FIELD_BLOB) + { + key_field=FIELD_NORMAL; /* static-size record */ + extra_field=FIELD_NORMAL; + record_type= STATIC_RECORD; + } + break; + case 'p': + pack_keys=HA_PACK_KEY; /* Use prefix + space packing */ + pack_seg=HA_SPACE_PACK; + key_type=HA_KEYTYPE_TEXT; + break; + case 'm': + unique_key=0; + break; + case 'b': + key_field=FIELD_BLOB; /* blob key */ + extra_field= FIELD_BLOB; + pack_seg|= HA_BLOB_PART; + key_type= HA_KEYTYPE_VARTEXT1; + if (record_type == STATIC_RECORD) + record_type= DYNAMIC_RECORD; + break; + case 'k': + if (key_length < 4 || key_length > HA_MAX_KEY_LENGTH) + { + fprintf(stderr,"Wrong key length\n"); + exit(1); + } + break; + case 'w': + key_field=FIELD_VARCHAR; /* varchar keys */ + extra_field= FIELD_VARCHAR; + key_type= HA_KEYTYPE_VARTEXT1; + pack_seg|= HA_VAR_LENGTH_PART; + if (record_type == STATIC_RECORD) + record_type= DYNAMIC_RECORD; + break; + case 'K': /* Use key cacheing */ + pagecacheing=1; + break; + case 'V': + printf("test1 Ver 1.2 \n"); + exit(0); + case '#': + DBUG_PUSH(argument); + break; + case '?': + usage(); + exit(1); + } + return 0; +} + + +/* Read options */ + +static void get_options(int argc, char *argv[]) +{ + int ho_error; + + if ((ho_error=handle_options(&argc, &argv, my_long_options, get_one_option))) + exit(ho_error); + if (transactional) + record_type= BLOCK_RECORD; + return; +} /* get options */ + + +static void usage() +{ + printf("Usage: %s [options]\n\n", my_progname); + my_print_help(my_long_options); + my_print_variables(my_long_options); +} diff --git a/storage/maria/ma_test2.c b/storage/maria/ma_test2.c new file mode 100644 index 00000000000..081cac8a2aa --- /dev/null +++ b/storage/maria/ma_test2.c @@ -0,0 +1,1240 @@ +/* Copyright (C) 2006 MySQL AB & MySQL Finland AB & TCX DataKonsult 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 */ + +/* Test av isam-databas: stor test */ + +#ifndef USE_MY_FUNC /* We want to be able to dbug this !! */ +#define USE_MY_FUNC +#endif +#include "maria_def.h" +#include "trnman.h" +#include <m_ctype.h> +#include <my_bit.h> +#include "ma_checkpoint.h" + +#define STANDARD_LENGTH 37 +#define MARIA_KEYS 6 +#define MAX_PARTS 4 +#if !defined(MSDOS) && !defined(labs) +#define labs(a) abs(a) +#endif + +static void get_options(int argc, char *argv[]); +static uint rnd(uint max_value); +static void fix_length(uchar *record,uint length); +static void put_blob_in_record(uchar *blob_pos,char **blob_buffer, + ulong *length); +static void copy_key(MARIA_HA *info, uint inx, uchar *record, uchar *key); + +static int verbose= 0, testflag= 0, first_key= 0, async_io= 0, pagecacheing= 0; +static int write_cacheing= 0, do_locking= 0, rec_pointer_size= 0; +static int silent= 0, opt_quick_mode= 0, transactional= 0, skip_update= 0; +static int die_in_middle_of_transaction= 0, pack_fields= 1; +static int pack_seg= HA_SPACE_PACK, pack_type= HA_PACK_KEY, remove_count= -1; +static int create_flag= 0, srand_arg= 0, checkpoint= 0; +static my_bool opt_versioning= 0; +static uint use_blob= 0, update_count= 0; +static ulong pagecache_size=8192*32; +static enum data_file_type record_type= DYNAMIC_RECORD; + +static uint keys=MARIA_KEYS,recant=1000; +static uint16 key1[1001],key3[5000]; +static uchar record[300],record2[300],key[100],key2[100]; +static uchar read_record[300],read_record2[300],read_record3[300]; +static HA_KEYSEG glob_keyseg[MARIA_KEYS][MAX_PARTS]; + + /* Test program */ + +int main(int argc, char *argv[]) +{ + uint i; + int j,n1,n2,n3,error,k; + uint write_count,update,dupp_keys,opt_delete,start,length,blob_pos, + reclength,ant,found_parts; + my_off_t lastpos; + ha_rows range_records,records; + MARIA_HA *file; + MARIA_KEYDEF keyinfo[10]; + MARIA_COLUMNDEF recinfo[10]; + MARIA_INFO info; + const char *filename; + char *blob_buffer; + MARIA_CREATE_INFO create_info; + +#if defined(SAFE_MUTEX) && defined(THREAD) + safe_mutex_deadlock_detector= 1; +#endif + MY_INIT(argv[0]); + + filename= "test2"; + get_options(argc,argv); + if (! async_io) + my_disable_async_io=1; + + maria_data_root= (char *)"."; + /* Maria requires that we always have a page cache */ + if (maria_init() || + (init_pagecache(maria_pagecache, pagecache_size, 0, 0, + maria_block_size, MY_WME) == 0) || + ma_control_file_open(TRUE, TRUE) || + (init_pagecache(maria_log_pagecache, + TRANSLOG_PAGECACHE_SIZE, 0, 0, + TRANSLOG_PAGE_SIZE, MY_WME) == 0) || + translog_init(maria_data_root, TRANSLOG_FILE_SIZE, + 0, 0, maria_log_pagecache, + TRANSLOG_DEFAULT_FLAGS, 0) || + (transactional && (trnman_init(0) || ma_checkpoint_init(0)))) + { + fprintf(stderr, "Error in initialization"); + exit(1); + } + if (opt_versioning) + init_thr_lock(); + + reclength=STANDARD_LENGTH+60+(use_blob ? 8 : 0); + blob_pos=STANDARD_LENGTH+60; + keyinfo[0].seg= &glob_keyseg[0][0]; + keyinfo[0].seg[0].start=0; + keyinfo[0].seg[0].length=6; + keyinfo[0].seg[0].type=HA_KEYTYPE_TEXT; + keyinfo[0].seg[0].language= default_charset_info->number; + keyinfo[0].seg[0].flag=(uint8) pack_seg; + keyinfo[0].seg[0].null_bit=0; + keyinfo[0].seg[0].null_pos=0; + keyinfo[0].key_alg=HA_KEY_ALG_BTREE; + keyinfo[0].keysegs=1; + keyinfo[0].flag = pack_type; + keyinfo[0].block_length= 0; /* Default block length */ + keyinfo[1].seg= &glob_keyseg[1][0]; + keyinfo[1].seg[0].start=7; + keyinfo[1].seg[0].length=6; + keyinfo[1].seg[0].type=HA_KEYTYPE_BINARY; + keyinfo[1].seg[0].flag=0; + keyinfo[1].seg[0].null_bit=0; + keyinfo[1].seg[0].null_pos=0; + keyinfo[1].seg[1].start=0; /* two part key */ + keyinfo[1].seg[1].length=6; + keyinfo[1].seg[1].type=HA_KEYTYPE_NUM; + keyinfo[1].seg[1].flag=HA_REVERSE_SORT; + keyinfo[1].seg[1].null_bit=0; + keyinfo[1].seg[1].null_pos=0; + keyinfo[1].key_alg=HA_KEY_ALG_BTREE; + keyinfo[1].keysegs=2; + keyinfo[1].flag =0; + keyinfo[1].block_length= MARIA_MIN_KEY_BLOCK_LENGTH; /* Diff blocklength */ + keyinfo[2].seg= &glob_keyseg[2][0]; + keyinfo[2].seg[0].start=12; + keyinfo[2].seg[0].length=8; + keyinfo[2].seg[0].type=HA_KEYTYPE_BINARY; + keyinfo[2].seg[0].flag=HA_REVERSE_SORT; + keyinfo[2].seg[0].null_bit=0; + keyinfo[2].seg[0].null_pos=0; + keyinfo[2].key_alg=HA_KEY_ALG_BTREE; + keyinfo[2].keysegs=1; + keyinfo[2].flag =HA_NOSAME; + keyinfo[2].block_length= 0; /* Default block length */ + keyinfo[3].seg= &glob_keyseg[3][0]; + keyinfo[3].seg[0].start=0; + keyinfo[3].seg[0].length=reclength-(use_blob ? 8 : 0); + keyinfo[3].seg[0].type=HA_KEYTYPE_TEXT; + keyinfo[3].seg[0].language=default_charset_info->number; + keyinfo[3].seg[0].flag=(uint8) pack_seg; + keyinfo[3].seg[0].null_bit=0; + keyinfo[3].seg[0].null_pos=0; + keyinfo[3].key_alg=HA_KEY_ALG_BTREE; + keyinfo[3].keysegs=1; + keyinfo[3].flag = pack_type; + keyinfo[3].block_length= 0; /* Default block length */ + keyinfo[4].seg= &glob_keyseg[4][0]; + keyinfo[4].seg[0].start=0; + keyinfo[4].seg[0].length=5; + keyinfo[4].seg[0].type=HA_KEYTYPE_TEXT; + keyinfo[4].seg[0].language=default_charset_info->number; + keyinfo[4].seg[0].flag=0; + keyinfo[4].seg[0].null_bit=0; + keyinfo[4].seg[0].null_pos=0; + keyinfo[4].key_alg=HA_KEY_ALG_BTREE; + keyinfo[4].keysegs=1; + keyinfo[4].flag = pack_type; + keyinfo[4].block_length= 0; /* Default block length */ + keyinfo[5].seg= &glob_keyseg[5][0]; + keyinfo[5].seg[0].start=0; + keyinfo[5].seg[0].length=4; + keyinfo[5].seg[0].type=HA_KEYTYPE_TEXT; + keyinfo[5].seg[0].language=default_charset_info->number; + keyinfo[5].seg[0].flag=pack_seg; + keyinfo[5].seg[0].null_bit=0; + keyinfo[5].seg[0].null_pos=0; + keyinfo[5].key_alg=HA_KEY_ALG_BTREE; + keyinfo[5].keysegs=1; + keyinfo[5].flag = pack_type; + keyinfo[5].block_length= 0; /* Default block length */ + + recinfo[0].type=pack_fields ? FIELD_SKIP_PRESPACE : 0; + recinfo[0].length=7; + recinfo[0].null_bit=0; + recinfo[0].null_pos=0; + recinfo[1].type=pack_fields ? FIELD_SKIP_PRESPACE : 0; + recinfo[1].length=5; + recinfo[1].null_bit=0; + recinfo[1].null_pos=0; + recinfo[2].type=pack_fields ? FIELD_SKIP_PRESPACE : 0; + recinfo[2].length=9; + recinfo[2].null_bit=0; + recinfo[2].null_pos=0; + recinfo[3].type=FIELD_NORMAL; + recinfo[3].length=STANDARD_LENGTH-7-5-9-4; + recinfo[3].null_bit=0; + recinfo[3].null_pos=0; + recinfo[4].type=pack_fields ? FIELD_SKIP_ZERO : 0; + recinfo[4].length=4; + recinfo[4].null_bit=0; + recinfo[4].null_pos=0; + recinfo[5].type=pack_fields ? FIELD_SKIP_ENDSPACE : 0; + recinfo[5].length=60; + recinfo[5].null_bit=0; + recinfo[5].null_pos=0; + if (use_blob) + { + recinfo[6].type=FIELD_BLOB; + recinfo[6].length=4+portable_sizeof_char_ptr; + recinfo[6].null_bit=0; + recinfo[6].null_pos=0; + } + + write_count=update=dupp_keys=opt_delete=0; + blob_buffer=0; + + for (i=1000 ; i>0 ; i--) key1[i]=0; + for (i=4999 ; i>0 ; i--) key3[i]=0; + + if (!silent) + printf("- Creating maria-file\n"); + file= 0; + bzero((char*) &create_info,sizeof(create_info)); + create_info.max_rows=(ha_rows) (rec_pointer_size ? + (1L << (rec_pointer_size*8))/ + reclength : 0); + create_info.reloc_rows=(ha_rows) 100; + create_info.transactional= transactional; + if (maria_create(filename, record_type, keys,&keyinfo[first_key], + use_blob ? 7 : 6, &recinfo[0], + 0,(MARIA_UNIQUEDEF*) 0, + &create_info,create_flag)) + goto err; + if (!(file=maria_open(filename,2,HA_OPEN_ABORT_IF_LOCKED))) + goto err; + maria_begin(file); + if (opt_versioning) + maria_versioning(file, 1); + if (testflag == 1) + goto end; + if (checkpoint == 1 && ma_checkpoint_execute(CHECKPOINT_MEDIUM, FALSE)) + goto err; + if (!silent) + printf("- Writing key:s\n"); + if (do_locking) + maria_lock_database(file,F_WRLCK); + if (write_cacheing) + maria_extra(file,HA_EXTRA_WRITE_CACHE,0); + if (opt_quick_mode) + maria_extra(file,HA_EXTRA_QUICK,0); + + for (i=0 ; i < recant ; i++) + { + ulong blob_length; + n1=rnd(1000); n2=rnd(100); n3=rnd(5000); + sprintf((char*) record,"%6d:%4d:%8d:Pos: %4d ",n1,n2,n3,write_count); + int4store(record+STANDARD_LENGTH-4,(long) i); + fix_length(record,(uint) STANDARD_LENGTH+rnd(60)); + put_blob_in_record(record+blob_pos,&blob_buffer, &blob_length); + DBUG_PRINT("test",("record: %d blob_length: %lu", i, blob_length)); + + if (maria_write(file,record)) + { + if (my_errno != HA_ERR_FOUND_DUPP_KEY || key3[n3] == 0) + { + printf("Error: %d in write at record: %d\n",my_errno,i); + goto err; + } + if (verbose) printf(" Double key: %d at record# %d\n", n3, i); + } + else + { + if (key3[n3] == 1 && first_key <3 && first_key+keys >= 3) + { + printf("Error: Didn't get error when writing second key: '%8d'\n",n3); + goto err; + } + write_count++; key1[n1]++; key3[n3]=1; + } + + /* Check if we can find key without flushing database */ + if (i % 10 == 0) + { + for (j=rnd(1000)+1 ; j>0 && key1[j] == 0 ; j--) ; + if (!j) + for (j=999 ; j>0 && key1[j] == 0 ; j--) ; + sprintf((char*) key,"%6d",j); + if (maria_rkey(file,read_record,0,key,HA_WHOLE_KEY,HA_READ_KEY_EXACT)) + { + printf("Test in loop: Can't find key: \"%s\"\n",key); + goto err; + } + } + } + if (checkpoint == 2 && ma_checkpoint_execute(CHECKPOINT_MEDIUM, FALSE)) + goto err; + + if (write_cacheing) + { + if (maria_extra(file,HA_EXTRA_NO_CACHE,0)) + { + puts("got error from maria_extra(HA_EXTRA_NO_CACHE)"); + goto err; + } + } + + if (testflag == 2) + goto end; + +#ifdef REMOVE_WHEN_WE_HAVE_RESIZE + if (pagecacheing) + resize_pagecache(maria_pagecache, maria_block_size, + pagecache_size * 2, 0, 0); +#endif + if (!silent) + printf("- Delete\n"); + if (srand_arg) + srand(srand_arg); + if (!update_count) + update_count= recant/10; + + for (i=0 ; i < update_count ; i++) + { + for (j=rnd(1000)+1 ; j>0 && key1[j] == 0 ; j--) ; + if (j != 0) + { + sprintf((char*) key,"%6d",j); + if (maria_rkey(file,read_record,0,key,HA_WHOLE_KEY,HA_READ_KEY_EXACT)) + { + printf("can't find key1: \"%s\"\n",key); + goto err; + } + if (bcmp(read_record+keyinfo[0].seg[0].start, + key, keyinfo[0].seg[0].length)) + { + printf("Found wrong record when searching for key: \"%s\"\n",key); + goto err; + } + if (opt_delete == (uint) remove_count) /* While testing */ + goto end; + if (maria_delete(file,read_record)) + { + printf("error: %d; can't delete record: \"%s\"\n", my_errno,read_record); + goto err; + } + opt_delete++; + key1[atoi((char*) read_record+keyinfo[0].seg[0].start)]--; + key3[atoi((char*) read_record+keyinfo[2].seg[0].start)]=0; + } + else + puts("Warning: Skipping delete test because no dupplicate keys"); + } + if (testflag == 3) + goto end; + if (checkpoint == 3 && ma_checkpoint_execute(CHECKPOINT_MEDIUM, FALSE)) + goto err; + + if (!silent) + printf("- Update\n"); + if (srand_arg) + srand(srand_arg); + if (!update_count) + update_count= recant/10; + + for (i=0 ; i < update_count ; i++) + { + n1=rnd(1000); n2=rnd(100); n3=rnd(5000); + sprintf((char*) record2,"%6d:%4d:%8d:XXX: %4d ",n1,n2,n3,update); + int4store(record2+STANDARD_LENGTH-4,(long) i); + fix_length(record2,(uint) STANDARD_LENGTH+rnd(60)); + + for (j=rnd(1000)+1 ; j>0 && key1[j] == 0 ; j--) ; + if (j != 0) + { + sprintf((char*) key,"%6d",j); + if (maria_rkey(file,read_record,0,key,HA_WHOLE_KEY,HA_READ_KEY_EXACT)) + { + printf("can't find key1: \"%s\"\n", (char*) key); + goto err; + } + if (bcmp(read_record+keyinfo[0].seg[0].start, + key, keyinfo[0].seg[0].length)) + { + printf("Found wrong record when searching for key: \"%s\"; Found \"%.*s\"\n", + key, keyinfo[0].seg[0].length, + read_record+keyinfo[0].seg[0].start); + goto err; + } + if (use_blob) + { + ulong blob_length; + if (i & 1) + put_blob_in_record(record2+blob_pos,&blob_buffer, &blob_length); + else + bmove(record2+blob_pos, read_record+blob_pos, 4 + sizeof(char*)); + } + if (skip_update) + continue; + if (maria_update(file,read_record,record2)) + { + if (my_errno != HA_ERR_FOUND_DUPP_KEY || key3[n3] == 0) + { + printf("error: %d; can't update:\nFrom: \"%s\"\nTo: \"%s\"\n", + my_errno,read_record,record2); + goto err; + } + if (verbose) + printf("Double key when tried to update:\nFrom: \"%s\"\nTo: \"%s\"\n",record,record2); + } + else + { + key1[atoi((char*) read_record+keyinfo[0].seg[0].start)]--; + key3[atoi((char*) read_record+keyinfo[2].seg[0].start)]=0; + key1[n1]++; key3[n3]=1; + update++; + } + } + } + if (testflag == 4) + goto end; + if (checkpoint == 4 && ma_checkpoint_execute(CHECKPOINT_MEDIUM, FALSE)) + goto err; + + for (i=999, dupp_keys=j=0 ; i>0 ; i--) + { + if (key1[i] > dupp_keys) + { + dupp_keys=key1[i]; j=i; + } + } + sprintf((char*) key,"%6d",j); + start=keyinfo[0].seg[0].start; + length=keyinfo[0].seg[0].length; + if (dupp_keys) + { + if (!silent) + printf("- Same key: first - next -> last - prev -> first\n"); + DBUG_PRINT("progpos",("first - next -> last - prev -> first")); + if (verbose) printf(" Using key: \"%s\" Keys: %d\n",key,dupp_keys); + + if (maria_rkey(file,read_record,0,key,HA_WHOLE_KEY,HA_READ_KEY_EXACT)) + goto err; + if (maria_rsame(file,read_record2,-1)) + goto err; + if (memcmp(read_record,read_record2,reclength) != 0) + { + printf("maria_rsame didn't find same record\n"); + goto err; + } + info.recpos=maria_position(file); + if (maria_rfirst(file,read_record2,0) || + maria_rsame_with_pos(file,read_record2,0,info.recpos) || + memcmp(read_record,read_record2,reclength) != 0) + { + printf("maria_rsame_with_pos didn't find same record\n"); + goto err; + } + { + int skr; + info.recpos= maria_position(file); + skr= maria_rnext(file,read_record2,0); + if ((skr && my_errno != HA_ERR_END_OF_FILE) || + maria_rprev(file,read_record2,0) || + memcmp(read_record,read_record2,reclength) != 0 || + info.recpos != maria_position(file)) + { + printf("maria_rsame_with_pos lost position\n"); + goto err; + } + } + ant=1; + while (maria_rnext(file,read_record2,0) == 0 && + memcmp(read_record2+start,key,length) == 0) ant++; + if (ant != dupp_keys) + { + printf("next: Found: %d keys of %d\n",ant,dupp_keys); + goto err; + } + ant=0; + while (maria_rprev(file,read_record3,0) == 0 && + bcmp(read_record3+start,key,length) == 0) ant++; + if (ant != dupp_keys) + { + printf("prev: Found: %d records of %d\n",ant,dupp_keys); + goto err; + } + + /* Check of maria_rnext_same */ + if (maria_rkey(file,read_record,0,key,HA_WHOLE_KEY,HA_READ_KEY_EXACT)) + goto err; + ant=1; + while (!maria_rnext_same(file,read_record3) && ant < dupp_keys+10) + ant++; + if (ant != dupp_keys || my_errno != HA_ERR_END_OF_FILE) + { + printf("maria_rnext_same: Found: %d records of %d\n",ant,dupp_keys); + goto err; + } + } + + if (!silent) + printf("- All keys: first - next -> last - prev -> first\n"); + DBUG_PRINT("progpos",("All keys: first - next -> last - prev -> first")); + ant=1; + if (maria_rfirst(file,read_record,0)) + { + printf("Can't find first record\n"); + goto err; + } + while ((error=maria_rnext(file,read_record3,0)) == 0 && ant < write_count+10) + ant++; + if (ant != write_count - opt_delete || error != HA_ERR_END_OF_FILE) + { + printf("next: I found: %d records of %d (error: %d)\n", + ant, write_count - opt_delete, error); + goto err; + } + if (maria_rlast(file,read_record2,0) || + bcmp(read_record2,read_record3,reclength)) + { + printf("Can't find last record\n"); + DBUG_DUMP("record2", read_record2, reclength); + DBUG_DUMP("record3", read_record3, reclength); + goto err; + } + ant=1; + while (maria_rprev(file,read_record3,0) == 0 && ant < write_count+10) + ant++; + if (ant != write_count - opt_delete) + { + printf("prev: I found: %d records of %d\n",ant,write_count); + goto err; + } + if (bcmp(read_record,read_record3,reclength)) + { + printf("Can't find first record\n"); + goto err; + } + + if (!silent) + printf("- Test if: Read first - next - prev - prev - next == first\n"); + DBUG_PRINT("progpos",("- Read first - next - prev - prev - next == first")); + if (maria_rfirst(file,read_record,0) || + maria_rnext(file,read_record3,0) || + maria_rprev(file,read_record3,0) || + maria_rprev(file,read_record3,0) == 0 || + maria_rnext(file,read_record3,0)) + goto err; + if (bcmp(read_record,read_record3,reclength) != 0) + printf("Can't find first record\n"); + + if (!silent) + printf("- Test if: Read last - prev - next - next - prev == last\n"); + DBUG_PRINT("progpos",("Read last - prev - next - next - prev == last")); + if (maria_rlast(file,read_record2,0) || + maria_rprev(file,read_record3,0) || + maria_rnext(file,read_record3,0) || + maria_rnext(file,read_record3,0) == 0 || + maria_rprev(file,read_record3,0)) + goto err; + if (bcmp(read_record2,read_record3,reclength)) + printf("Can't find last record\n"); +#ifdef NOT_ANYMORE + if (!silent) + puts("- Test read key-part"); + strmov(key2,key); + for(i=strlen(key2) ; i-- > 1 ;) + { + key2[i]=0; + + /* The following row is just to catch some bugs in the key code */ + bzero((char*) file->lastkey,file->s->base.max_key_length*2); + if (maria_rkey(file,read_record,0,key2,(uint) i,HA_READ_PREFIX)) + goto err; + if (bcmp(read_record+start,key,(uint) i)) + { + puts("Didn't find right record"); + goto err; + } + } +#endif + if (dupp_keys > 2) + { + if (!silent) + printf("- Read key (first) - next - delete - next -> last\n"); + DBUG_PRINT("progpos",("first - next - delete - next -> last")); + if (maria_rkey(file,read_record,0,key,HA_WHOLE_KEY,HA_READ_KEY_EXACT)) + goto err; + if (maria_rnext(file,read_record3,0)) goto err; + if (maria_delete(file,read_record3)) goto err; + opt_delete++; + ant=1; + while (maria_rnext(file,read_record3,0) == 0 && + bcmp(read_record3+start,key,length) == 0) ant++; + if (ant != dupp_keys-1) + { + printf("next: I can only find: %d keys of %d\n",ant,dupp_keys-1); + goto err; + } + } + if (dupp_keys>4) + { + if (!silent) + printf("- Read last of key - prev - delete - prev -> first\n"); + DBUG_PRINT("progpos",("last - prev - delete - prev -> first")); + if (maria_rprev(file,read_record3,0)) goto err; + if (maria_rprev(file,read_record3,0)) goto err; + if (maria_delete(file,read_record3)) goto err; + opt_delete++; + ant=1; + while (maria_rprev(file,read_record3,0) == 0 && + bcmp(read_record3+start,key,length) == 0) ant++; + if (ant != dupp_keys-2) + { + printf("next: I can only find: %d keys of %d\n",ant,dupp_keys-2); + goto err; + } + } + if (dupp_keys > 6) + { + if (!silent) + printf("- Read first - delete - next -> last\n"); + DBUG_PRINT("progpos",("first - delete - next -> last")); + if (maria_rkey(file,read_record3,0,key,HA_WHOLE_KEY,HA_READ_KEY_EXACT)) + goto err; + if (maria_delete(file,read_record3)) goto err; + opt_delete++; + ant=1; + if (maria_rnext(file,read_record,0)) + goto err; /* Skall finnas poster */ + while (maria_rnext(file,read_record3,0) == 0 && + bcmp(read_record3+start,key,length) == 0) ant++; + if (ant != dupp_keys-3) + { + printf("next: I can only find: %d keys of %d\n",ant,dupp_keys-3); + goto err; + } + + if (!silent) + printf("- Read last - delete - prev -> first\n"); + DBUG_PRINT("progpos",("last - delete - prev -> first")); + if (maria_rprev(file,read_record3,0)) goto err; + if (maria_delete(file,read_record3)) goto err; + opt_delete++; + ant=0; + while (maria_rprev(file,read_record3,0) == 0 && + bcmp(read_record3+start,key,length) == 0) ant++; + if (ant != dupp_keys-4) + { + printf("next: I can only find: %d keys of %d\n",ant,dupp_keys-4); + goto err; + } + } + + if (!silent) + puts("- Test if: Read rrnd - same"); + DBUG_PRINT("progpos",("Read rrnd - same")); + assert(maria_scan_init(file) == 0); + for (i=0 ; i < write_count ; i++) + { + int tmp; + if ((tmp= maria_scan(file,read_record)) && + tmp != HA_ERR_END_OF_FILE && + tmp != HA_ERR_RECORD_DELETED) + { + printf("Got error %d when scanning table\n", tmp); + break; + } + if (!tmp) + { + /* Remember position to last found row */ + info.recpos= maria_position(file); + bmove(read_record2,read_record,reclength); + } + } + maria_scan_end(file); + if (i != write_count && i != write_count - opt_delete) + { + printf("Found wrong number of rows while scanning table\n"); + goto err; + } + + if (maria_rsame_with_pos(file,read_record,0,info.recpos)) + goto err; + if (bcmp(read_record,read_record2,reclength) != 0) + { + printf("maria_rsame_with_pos didn't find same record\n"); + goto err; + } + + for (i=min(2,keys) ; i-- > 0 ;) + { + if (maria_rsame(file,read_record2,(int) i)) goto err; + if (bcmp(read_record,read_record2,reclength) != 0) + { + printf("maria_rsame didn't find same record\n"); + goto err; + } + } + if (!silent) + puts("- Test maria_records_in_range"); + maria_status(file,&info,HA_STATUS_VARIABLE); + for (i=0 ; i < info.keys ; i++) + { + key_range min_key, max_key; + if (maria_rfirst(file,read_record,(int) i) || + maria_rlast(file,read_record2,(int) i)) + goto err; + copy_key(file,(uint) i, read_record, key); + copy_key(file,(uint) i, read_record2, key2); + min_key.key= key; + min_key.keypart_map= HA_WHOLE_KEY; + min_key.flag= HA_READ_KEY_EXACT; + max_key.key= key2; + max_key.keypart_map= HA_WHOLE_KEY; + max_key.flag= HA_READ_AFTER_KEY; + + range_records= maria_records_in_range(file,(int) i, &min_key, &max_key); + if (range_records < info.records*8/10 || + range_records > info.records*12/10) + { + printf("maria_records_range returned %ld; Should be about %ld\n", + (long) range_records,(long) info.records); + goto err; + } + if (verbose) + { + printf("maria_records_range returned %ld; Exact is %ld (diff: %4.2g %%)\n", + (long) range_records, (long) info.records, + labs((long) range_records - (long) info.records)*100.0/ + info.records); + } + } + for (i=0 ; i < 5 ; i++) + { + for (j=rnd(1000)+1 ; j>0 && key1[j] == 0 ; j--) ; + for (k=rnd(1000)+1 ; k>0 && key1[k] == 0 ; k--) ; + if (j != 0 && k != 0) + { + key_range min_key, max_key; + if (j > k) + swap_variables(int, j, k); + sprintf((char*) key,"%6d",j); + sprintf((char*) key2,"%6d",k); + + min_key.key= key; + min_key.keypart_map= HA_WHOLE_KEY; + min_key.flag= HA_READ_AFTER_KEY; + max_key.key= key2; + max_key.keypart_map= HA_WHOLE_KEY; + max_key.flag= HA_READ_BEFORE_KEY; + range_records= maria_records_in_range(file, 0, &min_key, &max_key); + records=0; + for (j++ ; j < k ; j++) + records+=key1[j]; + if ((long) range_records < (long) records*7/10-2 || + (long) range_records > (long) records*14/10+2) + { + printf("maria_records_range for key: %d returned %lu; Should be about %lu\n", + i, (ulong) range_records, (ulong) records); + goto err; + } + if (verbose && records) + { + printf("maria_records_range returned %lu; Exact is %lu (diff: %4.2g %%)\n", + (ulong) range_records, (ulong) records, + labs((long) range_records-(long) records)*100.0/records); + + } + } + } + + if (!silent) + printf("- maria_info\n"); + maria_status(file,&info,HA_STATUS_VARIABLE | HA_STATUS_CONST); + if (info.records != write_count-opt_delete || info.deleted > opt_delete + update + || info.keys != keys) + { + puts("Wrong info from maria_info"); + printf("Got: records: %lu delete: %lu i_keys: %d\n", + (ulong) info.records, (ulong) info.deleted, info.keys); + goto err; + } + if (verbose) + { + char buff[80]; + get_date(buff,3,info.create_time); + printf("info: Created %s\n",buff); + get_date(buff,3,info.check_time); + printf("info: checked %s\n",buff); + get_date(buff,3,info.update_time); + printf("info: Modified %s\n",buff); + } + + maria_panic(HA_PANIC_WRITE); + maria_panic(HA_PANIC_READ); + if (maria_is_changed(file)) + puts("Warning: maria_is_changed reported that datafile was changed"); + + if (!silent) + printf("- maria_extra(CACHE) + maria_rrnd.... + maria_extra(NO_CACHE)\n"); + if (maria_reset(file) || maria_extra(file,HA_EXTRA_CACHE,0)) + { + if (do_locking || (!use_blob && !pack_fields)) + { + puts("got error from maria_extra(HA_EXTRA_CACHE)"); + goto err; + } + } + ant=0; + assert(maria_scan_init(file) == 0); + while ((error= maria_scan(file,record)) != HA_ERR_END_OF_FILE && + ant < write_count + 10) + ant+= error ? 0 : 1; + maria_scan_end(file); + if (ant != write_count-opt_delete) + { + printf("scan with cache: I can only find: %d records of %d\n", + ant,write_count-opt_delete); + maria_scan_end(file); + goto err; + } + if (maria_extra(file,HA_EXTRA_NO_CACHE,0)) + { + puts("got error from maria_extra(HA_EXTRA_NO_CACHE)"); + maria_scan_end(file); + goto err; + } + maria_scan_end(file); + + ant=0; + maria_scan_init(file); + while ((error=maria_scan(file,record)) != HA_ERR_END_OF_FILE && + ant < write_count + 10) + ant+= error ? 0 : 1; + if (ant != write_count-opt_delete) + { + printf("scan with cache: I can only find: %d records of %d\n", + ant,write_count-opt_delete); + maria_scan_end(file); + goto err; + } + maria_scan_end(file); + + if (testflag == 5) + goto end; + if (checkpoint == 5 && ma_checkpoint_execute(CHECKPOINT_MEDIUM, FALSE)) + goto err; + + if (!silent) + printf("- Removing keys\n"); + DBUG_PRINT("progpos",("Removing keys")); + lastpos = HA_OFFSET_ERROR; + /* DBUG_POP(); */ + maria_reset(file); + found_parts=0; + maria_scan_init(file); + while ((error= maria_scan(file,read_record)) != HA_ERR_END_OF_FILE) + { + info.recpos=maria_position(file); + if (lastpos >= info.recpos && lastpos != HA_OFFSET_ERROR) + { + printf("maria_rrnd didn't advance filepointer; old: %ld, new: %ld\n", + (long) lastpos, (long) info.recpos); + goto err; + } + lastpos=info.recpos; + if (error == 0) + { + if (opt_delete == (uint) remove_count) /* While testing */ + goto end; + if (rnd(2) == 1 && maria_rsame(file,read_record,-1)) + { + printf("can't find record %lx\n",(long) info.recpos); + goto err; + } + if (use_blob) + { + ulong blob_length,pos; + uchar *ptr; + memcpy_fixed(&ptr, read_record+blob_pos+4, sizeof(ptr)); + blob_length= uint4korr(read_record+blob_pos); + for (pos=0 ; pos < blob_length ; pos++) + { + if (ptr[pos] != (uchar) (blob_length+pos)) + { + printf("Found blob with wrong info at %ld\n",(long) lastpos); + maria_scan_end(file); + my_errno= 0; + goto err; + } + } + } + if (maria_delete(file,read_record)) + { + printf("can't delete record: %6.6s, delete_count: %d\n", + read_record, opt_delete); + maria_scan_end(file); + goto err; + } + opt_delete++; + } + else + found_parts++; + } + if (my_errno != HA_ERR_END_OF_FILE && my_errno != HA_ERR_RECORD_DELETED) + printf("error: %d from maria_rrnd\n",my_errno); + if (write_count != opt_delete) + { + printf("Deleted only %d of %d records (%d parts)\n",opt_delete,write_count, + found_parts); + maria_scan_end(file); + goto err; + } + if (testflag == 6) + goto end; + if (checkpoint == 6 && ma_checkpoint_execute(CHECKPOINT_MEDIUM, FALSE)) + goto err; + +end: + maria_scan_end(file); + if (die_in_middle_of_transaction) + { + /* As commit record is not done, UNDO entries needs to be rolled back */ + switch (die_in_middle_of_transaction) { + case 1: + /* + Flush changed data and index pages go to disk + That will also flush log. Recovery will skip REDOs and apply UNDOs. + */ + _ma_flush_table_files(file, MARIA_FLUSH_DATA | MARIA_FLUSH_INDEX, + FLUSH_RELEASE, FLUSH_RELEASE); + break; + case 2: + /* + Just flush log. Pages are likely to not be on disk. Recovery will + then execute REDOs and UNDOs. + */ + if (translog_flush(file->trn->undo_lsn)) + goto err; + break; + case 3: + /* + Flush nothing. Pages and log are likely to not be on disk. Recovery + will then do nothing. + */ + break; + case 4: + /* + Flush changed data pages go to disk. Changed index pages are not + flushed. Recovery will skip some REDOs and apply UNDOs. + */ + _ma_flush_table_files(file, MARIA_FLUSH_DATA, FLUSH_RELEASE, + FLUSH_RELEASE); + /* + We have to flush log separately as the redo for the last key page + may not be flushed + */ + if (translog_flush(file->trn->undo_lsn)) + goto err; + break; + } + printf("Dying on request without maria_commit()/maria_close()\n"); + exit(0); + } + if (maria_commit(file)) + goto err; + if (maria_close(file)) + { + file= 0; + goto err; + } + file= 0; + maria_panic(HA_PANIC_CLOSE); /* Should close log */ + if (!silent) + { + printf("\nFollowing test have been made:\n"); + printf("Write records: %d\nUpdate records: %d\nSame-key-read: %d\nDelete records: %d\n", write_count,update,dupp_keys,opt_delete); + if (rec_pointer_size) + printf("Record pointer size: %d\n",rec_pointer_size); + printf("maria_block_size: %lu\n", maria_block_size); + if (write_cacheing) + puts("Key cache resized"); + if (write_cacheing) + puts("Write cacheing used"); + if (write_cacheing) + puts("quick mode"); + if (async_io && do_locking) + puts("Asyncron io with locking used"); + else if (do_locking) + puts("Locking used"); + if (use_blob) + puts("blobs used"); + printf("key cache status: \n\ +blocks used:%10lu\n\ +not flushed:%10lu\n\ +w_requests: %10lu\n\ +writes: %10lu\n\ +r_requests: %10lu\n\ +reads: %10lu\n", + maria_pagecache->blocks_used, + maria_pagecache->global_blocks_changed, + (ulong) maria_pagecache->global_cache_w_requests, + (ulong) maria_pagecache->global_cache_write, + (ulong) maria_pagecache->global_cache_r_requests, + (ulong) maria_pagecache->global_cache_read); + } + maria_end(); + my_free(blob_buffer, MYF(MY_ALLOW_ZERO_PTR)); + my_end(silent ? MY_CHECK_ERROR : MY_CHECK_ERROR | MY_GIVE_INFO); + return(0); +err: + printf("got error: %d when using MARIA-database\n",my_errno); + if (file) + { + if (maria_commit(file)) + goto err; + VOID(maria_close(file)); + } + maria_end(); + return(1); +} /* main */ + + +/* Read options */ + +static void get_options(int argc, char **argv) +{ + char *pos,*progname; + + progname= argv[0]; + + while (--argc >0 && *(pos = *(++argv)) == '-' ) { + switch(*++pos) { + case 'B': + pack_type= HA_BINARY_PACK_KEY; + break; + case 'b': + use_blob= 1000; + if (*++pos) + use_blob= atol(pos); + break; + case 'K': /* Use key cacheing */ + pagecacheing=1; + if (*++pos) + pagecache_size=atol(pos); + break; + case 'W': /* Use write cacheing */ + write_cacheing=1; + if (*++pos) + my_default_record_cache_size=atoi(pos); + break; + case 'd': + remove_count= atoi(++pos); + break; + case 'i': + if (*++pos) + srand(srand_arg= atoi(pos)); + break; + case 'L': + do_locking=1; + break; + case 'a': /* use asyncron io */ + async_io=1; + if (*++pos) + my_default_record_cache_size=atoi(pos); + break; + case 'v': /* verbose */ + verbose=1; + break; + case 'm': /* records */ + if ((recant=atoi(++pos)) < 10 && testflag > 2) + { + fprintf(stderr,"record count must be >= 10 (if testflag > 2)\n"); + exit(1); + } + break; + case 'e': /* maria_block_length */ + case 'E': + if ((maria_block_size= atoi(++pos)) < MARIA_MIN_KEY_BLOCK_LENGTH || + maria_block_size > MARIA_MAX_KEY_BLOCK_LENGTH) + { + fprintf(stderr,"Wrong maria_block_length\n"); + exit(1); + } + maria_block_size= my_round_up_to_next_power(maria_block_size); + break; + case 'f': + if ((first_key=atoi(++pos)) < 0 || first_key >= MARIA_KEYS) + first_key=0; + break; + case 'H': + checkpoint= atoi(++pos); + break; + case 'k': + if ((keys=(uint) atoi(++pos)) < 1 || + keys > (uint) (MARIA_KEYS-first_key)) + keys=MARIA_KEYS-first_key; + break; + case 'M': + record_type= BLOCK_RECORD; + break; + case 'P': + pack_type=0; /* Don't use DIFF_LENGTH */ + pack_seg=0; + break; + case 'R': /* Length of record pointer */ + rec_pointer_size=atoi(++pos); + if (rec_pointer_size > 7) + rec_pointer_size=0; + break; + case 'S': + pack_fields=0; /* Static-length-records */ + record_type= STATIC_RECORD; + break; + case 's': + silent=1; + break; + case 't': + testflag=atoi(++pos); /* testmod */ + break; + case 'T': + transactional= 1; + break; + case 'A': + die_in_middle_of_transaction= atoi(++pos); + break; + case 'u': + update_count=atoi(++pos); + if (!update_count) + skip_update= 1; + break; + case 'q': + opt_quick_mode=1; + break; + case 'c': + create_flag|= HA_CREATE_CHECKSUM | HA_CREATE_PAGE_CHECKSUM; + break; + case 'D': + create_flag|=HA_CREATE_DELAY_KEY_WRITE; + break; + case 'g': + skip_update= TRUE; + break; + case 'C': + opt_versioning= 1; + break; + case '?': + case 'I': + case 'V': + printf("%s Ver 1.2 for %s at %s\n",progname,SYSTEM_TYPE,MACHINE_TYPE); + puts("By Monty, for testing Maria\n"); + printf("Usage: %s [-?AbBcCDIKLPRqSsTVWltv] [-k#] [-f#] [-m#] [-e#] [-E#] [-t#]\n", + progname); + exit(0); + case '#': + DBUG_PUSH (++pos); + break; + default: + printf("Illegal option: '%c'\n",*pos); + break; + } + } + return; +} /* get options */ + + /* Get a random value 0 <= x <= n */ + +static uint rnd(uint max_value) +{ + return (uint) ((rand() & 32767)/32767.0*max_value); +} /* rnd */ + + + /* Create a variable length record */ + +static void fix_length(uchar *rec, uint length) +{ + bmove(rec+STANDARD_LENGTH, + "0123456789012345678901234567890123456789012345678901234567890", + length-STANDARD_LENGTH); + strfill((char*) rec+length,STANDARD_LENGTH+60-length,' '); +} /* fix_length */ + + +/* Put maybe a blob in record */ + +static int first_entry; + +static void put_blob_in_record(uchar *blob_pos, char **blob_buffer, + ulong *blob_length) +{ + ulong i,length; + *blob_length= 0; + if (use_blob) + { + if (! *blob_buffer && + !(*blob_buffer=my_malloc((uint) use_blob,MYF(MY_WME)))) + { + use_blob= 0; + return; + } + if (rnd(10) == 0) + { + if (first_entry++ == 0) + { + /* Ensure we have at least one blob of max length in file */ + length= use_blob; + } + else + length=rnd(use_blob); + for (i=0 ; i < length ; i++) + (*blob_buffer)[i]=(char) (length+i); + int4store(blob_pos,length); + memcpy_fixed(blob_pos+4,(char*) blob_buffer,sizeof(char*)); + *blob_length= length; + } + else + { + int4store(blob_pos,0); + } + } + return; +} + + +static void copy_key(MARIA_HA *info,uint inx,uchar *rec,uchar *key_buff) +{ + HA_KEYSEG *keyseg; + + for (keyseg=info->s->keyinfo[inx].seg ; keyseg->type ; keyseg++) + { + memcpy(key_buff,rec+keyseg->start,(size_t) keyseg->length); + key_buff+=keyseg->length; + } + return; +} diff --git a/storage/maria/ma_test3.c b/storage/maria/ma_test3.c new file mode 100644 index 00000000000..8dd631380a0 --- /dev/null +++ b/storage/maria/ma_test3.c @@ -0,0 +1,501 @@ +/* Copyright (C) 2006 MySQL AB & MySQL Finland AB & TCX DataKonsult 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 */ + +/* Test av locking */ + +#if !(defined (__NETWARE_) || defined (_WIN32)) /*no fork() in Windows*/ + +#include "maria.h" +#include <sys/types.h> +#ifdef HAVE_SYS_WAIT_H +# include <sys/wait.h> +#endif +#ifndef WEXITSTATUS +# define WEXITSTATUS(stat_val) ((unsigned)(stat_val) >> 8) +#endif +#ifndef WIFEXITED +# define WIFEXITED(stat_val) (((stat_val) & 255) == 0) +#endif + + +#if defined(HAVE_LRAND48) +#define rnd(X) (lrand48() % X) +#define rnd_init(X) srand48(X) +#else +#define rnd(X) (random() % X) +#define rnd_init(X) srandom(X) +#endif + + +const char *filename= "test3"; +uint tests=10,forks=10,pagecacheing=0; + +static void get_options(int argc, char *argv[]); +void start_test(int id); +int test_read(MARIA_HA *,int),test_write(MARIA_HA *,int,int), + test_update(MARIA_HA *,int,int),test_rrnd(MARIA_HA *,int); + +struct record { + uchar id[8]; + uchar nr[4]; + uchar text[10]; +} record; + + +int main(int argc,char **argv) +{ + int status,wait_ret; + uint i=0; + MARIA_KEYDEF keyinfo[10]; + MARIA_COLUMNDEF recinfo[10]; + HA_KEYSEG keyseg[10][2]; + MY_INIT(argv[0]); + get_options(argc,argv); + + fprintf(stderr, "WARNING! this program is to test 'external locking'" + " (when several processes share a table through file locking)" + " which is not supported by Maria at all; expect errors." + " We may soon remove this program.\n"); + maria_init(); + bzero((char*) keyinfo,sizeof(keyinfo)); + bzero((char*) recinfo,sizeof(recinfo)); + bzero((char*) keyseg,sizeof(keyseg)); + keyinfo[0].seg= &keyseg[0][0]; + keyinfo[0].seg[0].start=0; + keyinfo[0].seg[0].length=8; + keyinfo[0].seg[0].type=HA_KEYTYPE_TEXT; + keyinfo[0].seg[0].flag=HA_SPACE_PACK; + keyinfo[0].key_alg=HA_KEY_ALG_BTREE; + keyinfo[0].keysegs=1; + keyinfo[0].flag = (uint8) HA_PACK_KEY; + keyinfo[0].block_length= 0; /* Default block length */ + keyinfo[1].seg= &keyseg[1][0]; + keyinfo[1].seg[0].start=8; + keyinfo[1].seg[0].length=4; /* Long is always 4 in maria */ + keyinfo[1].seg[0].type=HA_KEYTYPE_LONG_INT; + keyinfo[1].seg[0].flag=0; + keyinfo[1].key_alg=HA_KEY_ALG_BTREE; + keyinfo[1].keysegs=1; + keyinfo[1].flag =HA_NOSAME; + keyinfo[1].block_length= 0; /* Default block length */ + + recinfo[0].type=0; + recinfo[0].length=sizeof(record.id); + recinfo[1].type=0; + recinfo[1].length=sizeof(record.nr); + recinfo[2].type=0; + recinfo[2].length=sizeof(record.text); + + puts("- Creating maria-file"); + my_delete(filename,MYF(0)); /* Remove old locks under gdb */ + if (maria_create(filename,BLOCK_RECORD, 2, &keyinfo[0],2,&recinfo[0],0, + (MARIA_UNIQUEDEF*) 0, (MARIA_CREATE_INFO*) 0,0)) + exit(1); + + rnd_init(0); + printf("- Starting %d processes\n",forks); fflush(stdout); + for (i=0 ; i < forks; i++) + { + if (!fork()) + { + start_test(i+1); + sleep(1); + return 0; + } + VOID(rnd(1)); + } + + for (i=0 ; i < forks ; i++) + while ((wait_ret=wait(&status)) && wait_ret == -1); + maria_end(); + return 0; +} + + +static void get_options(int argc, char **argv) +{ + char *pos,*progname; + + progname= argv[0]; + + while (--argc >0 && *(pos = *(++argv)) == '-' ) { + switch(*++pos) { + case 'f': + forks=atoi(++pos); + break; + case 't': + tests=atoi(++pos); + break; + case 'K': /* Use key cacheing */ + pagecacheing=1; + break; + case 'A': /* All flags */ + pagecacheing=1; + break; + case '?': + case 'I': + case 'V': + printf("%s Ver 1.0 for %s at %s\n",progname,SYSTEM_TYPE,MACHINE_TYPE); + puts("By Monty, for your professional use\n"); + puts("Test av locking with threads\n"); + printf("Usage: %s [-?lKA] [-f#] [-t#]\n",progname); + exit(0); + case '#': + DBUG_PUSH (++pos); + break; + default: + printf("Illegal option: '%c'\n",*pos); + break; + } + } + return; +} + + +void start_test(int id) +{ + uint i; + int error,lock_type; + MARIA_INFO isam_info; + MARIA_HA *file,*file1,*file2=0,*lock; + + if (!(file1=maria_open(filename,O_RDWR,HA_OPEN_WAIT_IF_LOCKED)) || + !(file2=maria_open(filename,O_RDWR,HA_OPEN_WAIT_IF_LOCKED))) + { + fprintf(stderr,"Can't open isam-file: %s\n",filename); + exit(1); + } + if (pagecacheing && rnd(2) == 0) + init_pagecache(maria_pagecache, 65536L, 0, 0, MARIA_KEY_BLOCK_LENGTH, + MY_WME); + printf("Process %d, pid: %d\n",id,getpid()); fflush(stdout); + + for (error=i=0 ; i < tests && !error; i++) + { + file= (rnd(2) == 1) ? file1 : file2; + lock=0 ; lock_type=0; + if (rnd(10) == 0) + { + if (maria_lock_database(lock=(rnd(2) ? file1 : file2), + lock_type=(rnd(2) == 0 ? F_RDLCK : F_WRLCK))) + { + fprintf(stderr,"%2d: start: Can't lock table %d\n",id,my_errno); + error=1; + break; + } + } + switch (rnd(4)) { + case 0: error=test_read(file,id); break; + case 1: error=test_rrnd(file,id); break; + case 2: error=test_write(file,id,lock_type); break; + case 3: error=test_update(file,id,lock_type); break; + } + if (lock) + maria_lock_database(lock,F_UNLCK); + } + if (!error) + { + maria_status(file1,&isam_info,HA_STATUS_VARIABLE); + printf("%2d: End of test. Records: %ld Deleted: %ld\n", + id,(long) isam_info.records, (long) isam_info.deleted); + fflush(stdout); + } + + maria_close(file1); + maria_close(file2); + if (error) + { + printf("%2d: Aborted\n",id); fflush(stdout); + exit(1); + } +} + + +int test_read(MARIA_HA *file,int id) +{ + uint i,lock,found,next,prev; + ulong find; + + lock=0; + if (rnd(2) == 0) + { + lock=1; + if (maria_lock_database(file,F_RDLCK)) + { + fprintf(stderr,"%2d: Can't lock table %d\n",id,my_errno); + return 1; + } + } + + found=next=prev=0; + for (i=0 ; i < 100 ; i++) + { + find=rnd(100000); + if (!maria_rkey(file,record.id,1,(uchar*) &find, HA_WHOLE_KEY, + HA_READ_KEY_EXACT)) + found++; + else + { + if (my_errno != HA_ERR_KEY_NOT_FOUND) + { + fprintf(stderr,"%2d: Got error %d from read in read\n",id,my_errno); + return 1; + } + else if (!maria_rnext(file,record.id,1)) + next++; + else + { + if (my_errno != HA_ERR_END_OF_FILE) + { + fprintf(stderr,"%2d: Got error %d from rnext in read\n",id,my_errno); + return 1; + } + else if (!maria_rprev(file,record.id,1)) + prev++; + else + { + if (my_errno != HA_ERR_END_OF_FILE) + { + fprintf(stderr,"%2d: Got error %d from rnext in read\n", + id,my_errno); + return 1; + } + } + } + } + } + if (lock) + { + if (maria_lock_database(file,F_UNLCK)) + { + fprintf(stderr,"%2d: Can't unlock table\n",id); + return 1; + } + } + printf("%2d: read: found: %5d next: %5d prev: %5d\n", + id,found,next,prev); + fflush(stdout); + return 0; +} + + +int test_rrnd(MARIA_HA *file,int id) +{ + uint count,lock; + + lock=0; + if (rnd(2) == 0) + { + lock=1; + if (maria_lock_database(file,F_RDLCK)) + { + fprintf(stderr,"%2d: Can't lock table (%d)\n",id,my_errno); + maria_close(file); + return 1; + } + if (rnd(2) == 0) + maria_extra(file,HA_EXTRA_CACHE,0); + } + + count=0; + if (maria_rrnd(file,record.id,0L)) + { + if (my_errno == HA_ERR_END_OF_FILE) + goto end; + fprintf(stderr,"%2d: Can't read first record (%d)\n",id,my_errno); + return 1; + } + for (count=1 ; !maria_rrnd(file,record.id,HA_OFFSET_ERROR) ;count++) ; + if (my_errno != HA_ERR_END_OF_FILE) + { + fprintf(stderr,"%2d: Got error %d from rrnd\n",id,my_errno); + return 1; + } + +end: + if (lock) + { + maria_extra(file,HA_EXTRA_NO_CACHE,0); + if (maria_lock_database(file,F_UNLCK)) + { + fprintf(stderr,"%2d: Can't unlock table\n",id); + exit(0); + } + } + printf("%2d: rrnd: %5d\n",id,count); fflush(stdout); + return 0; +} + + +int test_write(MARIA_HA *file,int id,int lock_type) +{ + uint i,tries,count,lock; + + lock=0; + if (rnd(2) == 0 || lock_type == F_RDLCK) + { + lock=1; + if (maria_lock_database(file,F_WRLCK)) + { + if (lock_type == F_RDLCK && my_errno == EDEADLK) + { + printf("%2d: write: deadlock\n",id); fflush(stdout); + return 0; + } + fprintf(stderr,"%2d: Can't lock table (%d)\n",id,my_errno); + maria_close(file); + return 1; + } + if (rnd(2) == 0) + maria_extra(file,HA_EXTRA_WRITE_CACHE,0); + } + + sprintf((char*) record.id,"%7d",getpid()); + strnmov((char*) record.text,"Testing...", sizeof(record.text)); + + tries=(uint) rnd(100)+10; + for (i=count=0 ; i < tries ; i++) + { + uint32 tmp=rnd(80000)+20000; + int4store(record.nr,tmp); + if (!maria_write(file,record.id)) + count++; + else + { + if (my_errno != HA_ERR_FOUND_DUPP_KEY) + { + fprintf(stderr,"%2d: Got error %d (errno %d) from write\n",id,my_errno, + errno); + return 1; + } + } + } + if (lock) + { + maria_extra(file,HA_EXTRA_NO_CACHE,0); + if (maria_lock_database(file,F_UNLCK)) + { + fprintf(stderr,"%2d: Can't unlock table\n",id); + exit(0); + } + } + printf("%2d: write: %5d\n",id,count); fflush(stdout); + return 0; +} + + +int test_update(MARIA_HA *file,int id,int lock_type) +{ + uint i,lock,found,next,prev,update; + uint32 tmp; + char find[4]; + struct record new_record; + + lock=0; + if (rnd(2) == 0 || lock_type == F_RDLCK) + { + lock=1; + if (maria_lock_database(file,F_WRLCK)) + { + if (lock_type == F_RDLCK && my_errno == EDEADLK) + { + printf("%2d: write: deadlock\n",id); fflush(stdout); + return 0; + } + fprintf(stderr,"%2d: Can't lock table (%d)\n",id,my_errno); + return 1; + } + } + bzero((char*) &new_record,sizeof(new_record)); + strmov((char*) new_record.text,"Updated"); + + found=next=prev=update=0; + for (i=0 ; i < 100 ; i++) + { + tmp=rnd(100000); + int4store(find,tmp); + if (!maria_rkey(file,record.id,1,(uchar*) find, HA_WHOLE_KEY, + HA_READ_KEY_EXACT)) + found++; + else + { + if (my_errno != HA_ERR_KEY_NOT_FOUND) + { + fprintf(stderr,"%2d: Got error %d from read in update\n",id,my_errno); + return 1; + } + else if (!maria_rnext(file,record.id,1)) + next++; + else + { + if (my_errno != HA_ERR_END_OF_FILE) + { + fprintf(stderr,"%2d: Got error %d from rnext in update\n", + id,my_errno); + return 1; + } + else if (!maria_rprev(file,record.id,1)) + prev++; + else + { + if (my_errno != HA_ERR_END_OF_FILE) + { + fprintf(stderr,"%2d: Got error %d from rnext in update\n", + id,my_errno); + return 1; + } + continue; + } + } + } + memcpy_fixed(new_record.id,record.id,sizeof(record.id)); + tmp=rnd(20000)+40000; + int4store(new_record.nr,tmp); + if (!maria_update(file,record.id,new_record.id)) + update++; + else + { + if (my_errno != HA_ERR_RECORD_CHANGED && + my_errno != HA_ERR_RECORD_DELETED && + my_errno != HA_ERR_FOUND_DUPP_KEY) + { + fprintf(stderr,"%2d: Got error %d from update\n",id,my_errno); + return 1; + } + } + } + if (lock) + { + if (maria_lock_database(file,F_UNLCK)) + { + fprintf(stderr,"Can't unlock table,id, error%d\n",my_errno); + return 1; + } + } + printf("%2d: update: %5d\n",id,update); fflush(stdout); + return 0; +} + +#else /* __NETWARE__ || __WIN__ */ + +#include <stdio.h> + +int main() +{ + fprintf(stderr,"this test has not been ported to Netware or Windows\n"); + return 0; +} + +#endif /* __NETWARE__|| __WIN__ */ diff --git a/storage/maria/ma_test_all.res b/storage/maria/ma_test_all.res new file mode 100644 index 00000000000..586aaf68020 --- /dev/null +++ b/storage/maria/ma_test_all.res @@ -0,0 +1,14 @@ +Running tests with dynamic row format +Running tests with static row format +Running tests with block row format +Running tests with block row format and transactions +ma_test2 -s -L -K -R1 -m2000 ; Should give error 135 +Error: 135 in write at record: 1099 +got error: 135 when using MARIA-database +./maria_chk -sm test2 will warn that 'Datafile is almost full' +maria_chk: MARIA file test2 +maria_chk: warning: Datafile is almost full, 65516 of 65534 used +MARIA-table 'test2' is usable but should be fixed +MARIA RECOVERY TESTS +ALL RECOVERY TESTS OK +!!!!!!!! BUT REMEMBER to FIX this BLOB issue !!!!!!! diff --git a/storage/maria/ma_test_all.sh b/storage/maria/ma_test_all.sh new file mode 100755 index 00000000000..041fbf3abe6 --- /dev/null +++ b/storage/maria/ma_test_all.sh @@ -0,0 +1,19 @@ +#!/bin/sh +# +# This file is now deprecated and has been replaced by +# unittest/ma_test_all-t +# +# +# +# + +if test -n "$1"; then + + # unit.pl can't pass options to ma_test_all-t, so if anything + # was passed as an argument, assume the purpose was to pass + # them to ma_test_all-t and call it directly + + unittest/ma_test_all-t $@ +else + perl ../../unittest/unit.pl run unittest/ma_test_all-t +fi diff --git a/storage/maria/ma_test_big.sh b/storage/maria/ma_test_big.sh new file mode 100644 index 00000000000..6419d05e3a4 --- /dev/null +++ b/storage/maria/ma_test_big.sh @@ -0,0 +1,22 @@ +#!/bin/sh +# +# This tests is good to find bugs in the redo/undo handling and in +# finding bugs in blob handling +# + +set -e +a=15 +while test $a -le 5000 +do + echo $a + rm -f maria_log* + ma_test2 -s -L -K -W -P -M -T -c -b32768 -t4 -A1 -m$a > /dev/null + maria_read_log -a -s >& /dev/null + maria_chk -es test2 + maria_read_log -a -s >& /dev/null + maria_chk -es test2 + rm test2.MA? + maria_read_log -a -s >& /dev/null + maria_chk -es test2 + a=$((a+1)) +done diff --git a/storage/maria/ma_test_force_start.pl b/storage/maria/ma_test_force_start.pl new file mode 100755 index 00000000000..7ab8190a738 --- /dev/null +++ b/storage/maria/ma_test_force_start.pl @@ -0,0 +1,238 @@ +#!/usr/bin/env perl + + +use strict; +use warnings; + +my $usage= <<EOF; +This program tests that the options +--maria-force-start-after-recovery-failures --maria-recover work as +expected. +It has to be run from directory mysql-test, and works with non-debug +and debug binaries. +Pass it option -d or -i (to test corruption of data or index file). +EOF + +# -d currently exhibits BUG#36578 +# "Maria: maria-recover may fail to autorepair a table" + +die($usage) if (@ARGV == 0); + +my $corrupt_index; + +if ($ARGV[0] eq '-d') + { + $corrupt_index= 0; + } +elsif ($ARGV[0] eq '-i') + { + $corrupt_index= 1; + } +else + { + die($usage); + } + +my $force_after= 3; +my $corrupt_file= $corrupt_index ? "MAI" : "MAD"; +my $corrupt_message= + "\\[ERROR\\] mysqld(.exe)*: Table '..test.t1' is marked as crashed and should be repaired"; + +my $sql_name= "./var/tmp/create_table.sql"; +my $error_log_name= "./var/log/master.err"; +my @cmd_output; +my $whatever; # garbage data +$ENV{MTR_VERSION} = 1; # MTR2 does not have --start-and-exit +my $base_server_cmd= "perl mysql-test-run.pl --mysqld=--maria-force-start-after-recovery-failures=$force_after --suite=maria maria.maria-recover ";
+if ($^O =~ /^mswin/i) + { + print <<EOF; +WARNING: with Activestate Perl, mysql-test-run.pl --start-and-exit has a bug: +it does not exit; cygwin perl recommended +EOF + } +my $iswindows= ( $^O =~ /win/i && $^O !~ /darwin/i ); +$base_server_cmd.= ($iswindows ? "--mysqld=--console" : "--mem"); +my $server_cmd; +my $server_pid_name="./var/run/master.pid"; +my $server_pid; +my $i; # count of server restarts +sub kill_server; + +my $suffix= ($iswindows ? ".exe" : ""); +my $client_exe_path= "../client/release"; +# we use -f, sometimes -x is unexpectedly false in Cygwin +if ( ! -f "$client_exe_path/mysql$suffix" ) + { + $client_exe_path= "../client/relwithdebinfo"; + if ( ! -f "$client_exe_path/mysql$suffix" ) + { + $client_exe_path= "../client/debug"; + if ( ! -f "$client_exe_path/mysql$suffix" ) + { + $client_exe_path= "../client"; + if ( ! -f "$client_exe_path/mysql$suffix" ) + { + die("Cannot find 'mysql' executable\n"); + } + } + } + } + +print "starting mysqld\n"; +$server_cmd= $base_server_cmd . " --start-and-exit 2>&1"; +@cmd_output=`$server_cmd`; +die if $?; +my $master_port= (grep (/Using MASTER_MYPORT .*= (\d+)$/, @cmd_output))[0]; +$master_port =~ s/.*= //; +chomp $master_port; +die unless $master_port > 0; + +my $client_cmd= "$client_exe_path/mysql -u root -h 127.0.0.1 -P $master_port test < $sql_name"; + +open(FILE, ">", $sql_name) or die; + +# To exhibit BUG#36578 with -d, we don't create an index if -d. This is +# because the presence of an index will cause repair-by-sort to be used, +# where sort_get_next_record() is only called inside +#_ma_create_index_by_sort(), so the latter function fails and in this +# case retry_repair is set, so bug does not happen. Whereas without +# an index, repair-with-key-cache is called, which calls +# sort_get_next_record() whose failure itself does not cause a retry. + +print FILE "create table t1 (a varchar(1000)". + ($corrupt_index ? ", index(a)" : "") .") engine=maria;\n"; +print FILE <<EOF; +insert into t1 values("ThursdayMorningsMarket"); +# If Recovery executes REDO_INDEX_NEW_PAGE it will overwrite our +# intentional corruption; we make Recovery skip this record by bumping +# create_rename_lsn using OPTIMIZE TABLE. This also makes sure to put +# the pages on disk, so that we can corrupt them. +optimize table t1; +# mark table open, so that --maria-recover repairs it +insert into t1 select concat(a,'b') from t1 limit 1; +EOF +close FILE; + +print "creating table\n"; +`$client_cmd`; +die if $?; + +print "killing mysqld hard\n"; +kill_server(9); + +print "ruining " . + ($corrupt_index ? "first page of keys" : "bitmap page") . + " in table to test maria-recover\n"; +open(FILE, "+<", "./var/master-data/test/t1.$corrupt_file") or die; +$whatever= ("\xAB" x 100); +sysseek (FILE, $corrupt_index ? 8192 : (8192-100-100), 0) or die; +syswrite (FILE, $whatever) or die; +close FILE; + +print "ruining log to make recovery fail; mysqld should fail the $force_after first restarts\n"; +open(FILE, "+<", "./var/tmp/maria_log.00000001") or die; +$whatever= ("\xAB" x 8192); +sysseek (FILE, 99, 0) or die; +syswrite (FILE, $whatever) or die; +close FILE; + +$server_cmd= $base_server_cmd . " --start-dirty 2>&1"; +for($i= 1; $i <= $force_after; $i= $i + 1) + { + print "mysqld restart number $i... "; + unlink($error_log_name) or die; + `$server_cmd`; + # mysqld should return 1 when can't read log + die unless (($? >> 8) == 1); + open(FILE, "<", $error_log_name) or die; + @cmd_output= <FILE>; + close FILE; + die unless grep(/\[ERROR\] mysqld(.exe)*: Maria engine: log initialization failed/, @cmd_output); + die unless grep(/\[ERROR\] Plugin 'MARIA' init function returned error./, @cmd_output); + print "failed - ok\n"; + } + +print "mysqld restart number $i... "; +unlink($error_log_name) or die; +@cmd_output=`$server_cmd`; +die if $?; +open(FILE, "<", $error_log_name) or die; +@cmd_output= <FILE>; +close FILE; +die unless grep(/\[Warning\] mysqld(.exe)*: Maria engine: removed all logs after [\d]+ consecutive failures of recovery from logs/, @cmd_output); +die unless grep(/\[ERROR\] mysqld(.exe)*: File '.*tmp.maria_log.00000001' not found \(Errcode: 2\)/, @cmd_output); +print "success - ok\n"; + +open(FILE, ">", $sql_name) or die; +print FILE <<EOF; +set global maria_recover=normal; +insert into t1 values('aaa'); +EOF +close FILE; + +# verify corruption has not yet been noticed +open(FILE, "<", $error_log_name) or die; +@cmd_output= <FILE>; +close FILE; +die if grep(/$corrupt_message/, @cmd_output); + +print "inserting in table\n"; +`$client_cmd`; +die if $?; +print "table is usable - ok\n"; + +open(FILE, "<", $error_log_name) or die; +@cmd_output= <FILE>; +close FILE; +die unless grep(/$corrupt_message/, @cmd_output); +die unless grep(/\[Warning\] Recovering table: '..test.t1'/, @cmd_output); +print "was corrupted and automatically repaired - ok\n"; + +# remove our traces +kill_server(15); + +print "TEST ALL OK\n"; + +# kills mysqld with signal given in parameter +sub kill_server + { + my ($sig)= @_; + my $wait_count= 0; + my $kill_cmd; + my @kill_output; + open(FILE, "<", $server_pid_name) or die; + @cmd_output= <FILE>; + close FILE; + $server_pid= $cmd_output[0]; + chomp $server_pid; + die unless $server_pid > 0; + if ($iswindows) + { + # On Windows, server_pid_name is not the "main" process id + # so perl's kill() does not see this process id. + # But taskkill works, though only with /F ("-9"-style kill). + $kill_cmd= "taskkill /F /PID $server_pid 2>&1"; + @kill_output= `$kill_cmd`; + die unless grep(/has been terminated/, @kill_output); + } + else + { + kill($sig, $server_pid) or die; + } + while (1) # wait until mysqld process gone + { + if ($iswindows) + { + @kill_output= `$kill_cmd`; + last if grep(/not found/, @kill_output); + } + else + { + kill (0, $server_pid) or last; + } + print "waiting for mysqld to die\n" if ($wait_count > 30); + $wait_count= $wait_count + 1; + select(undef, undef, undef, 0.1); + } + } diff --git a/storage/maria/ma_test_recovery b/storage/maria/ma_test_recovery new file mode 100755 index 00000000000..0b20264c434 --- /dev/null +++ b/storage/maria/ma_test_recovery @@ -0,0 +1,8 @@ +#!/bin/sh + +# Remove comment from next line if this script fails and you need more +# information of what's going on + +# This file is deprecated and has been replaced with ma_test_recovery.pl + +unittest/ma_test_recovery.pl $@ diff --git a/storage/maria/ma_unique.c b/storage/maria/ma_unique.c new file mode 100644 index 00000000000..bae58fd70cd --- /dev/null +++ b/storage/maria/ma_unique.c @@ -0,0 +1,245 @@ +/* Copyright (C) 2006 MySQL AB & MySQL Finland AB & TCX DataKonsult 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 */ + +/* Functions to check if a row is unique */ + +#include "maria_def.h" +#include <m_ctype.h> + +/** + Check if there exist a row with the same hash + + @notes + This function is not versioning safe. For the moment this is not a problem + as it's only used for internal temporary tables in MySQL for which there + isn't any versioning information. +*/ + +my_bool _ma_check_unique(MARIA_HA *info, MARIA_UNIQUEDEF *def, uchar *record, + ha_checksum unique_hash, my_off_t disk_pos) +{ + my_off_t lastpos=info->cur_row.lastpos; + MARIA_KEYDEF *keyinfo= &info->s->keyinfo[def->key]; + uchar *key_buff= info->lastkey_buff2; + MARIA_KEY key; + DBUG_ENTER("_ma_check_unique"); + DBUG_PRINT("enter",("unique_hash: %lu", (ulong) unique_hash)); + + maria_unique_store(record+keyinfo->seg->start, unique_hash); + /* Can't be spatial so it's ok to call _ma_make_key directly here */ + _ma_make_key(info, &key, def->key, key_buff, record, 0, 0); + + /* The above changed info->lastkey_buff2. Inform maria_rnext_same(). */ + info->update&= ~HA_STATE_RNEXT_SAME; + + DBUG_ASSERT(key.data_length == MARIA_UNIQUE_HASH_LENGTH); + if (_ma_search(info, &key, SEARCH_FIND, info->s->state.key_root[def->key])) + { + info->page_changed=1; /* Can't optimize read next */ + info->cur_row.lastpos= lastpos; + DBUG_RETURN(0); /* No matching rows */ + } + + for (;;) + { + if (info->cur_row.lastpos != disk_pos && + !(*info->s->compare_unique)(info,def,record,info->cur_row.lastpos)) + { + my_errno=HA_ERR_FOUND_DUPP_UNIQUE; + info->errkey= (int) def->key; + info->dup_key_pos= info->cur_row.lastpos; + info->page_changed= 1; /* Can't optimize read next */ + info->cur_row.lastpos= lastpos; + DBUG_PRINT("info",("Found duplicate")); + DBUG_RETURN(1); /* Found identical */ + } + DBUG_ASSERT(info->last_key.data_length == MARIA_UNIQUE_HASH_LENGTH); + if (_ma_search_next(info, &info->last_key, SEARCH_BIGGER, + info->s->state.key_root[def->key]) || + bcmp((char*) info->last_key.data, (char*) key_buff, + MARIA_UNIQUE_HASH_LENGTH)) + { + info->page_changed= 1; /* Can't optimize read next */ + info->cur_row.lastpos= lastpos; + DBUG_RETURN(0); /* end of tree */ + } + } +} + + +/* + Calculate a hash for a row + + TODO + Add support for bit fields +*/ + +ha_checksum _ma_unique_hash(MARIA_UNIQUEDEF *def, const uchar *record) +{ + const uchar *pos, *end; + ha_checksum crc= 0; + ulong seed1=0, seed2= 4; + HA_KEYSEG *keyseg; + + for (keyseg=def->seg ; keyseg < def->end ; keyseg++) + { + enum ha_base_keytype type=(enum ha_base_keytype) keyseg->type; + uint length=keyseg->length; + + if (keyseg->null_bit) + { + if (record[keyseg->null_pos] & keyseg->null_bit) + { + /* + Change crc in a way different from an empty string or 0. + (This is an optimisation; The code will work even if this isn't + done) + */ + crc=((crc << 8) + 511+ + (crc >> (8*sizeof(ha_checksum)-8))); + continue; + } + } + pos= record+keyseg->start; + if (keyseg->flag & HA_VAR_LENGTH_PART) + { + uint pack_length= keyseg->bit_start; + uint tmp_length= (pack_length == 1 ? (uint) *pos : + uint2korr(pos)); + pos+= pack_length; /* Skip VARCHAR length */ + set_if_smaller(length,tmp_length); + } + else if (keyseg->flag & HA_BLOB_PART) + { + uint tmp_length= _ma_calc_blob_length(keyseg->bit_start,pos); + memcpy_fixed((uchar*) &pos,pos+keyseg->bit_start,sizeof(char*)); + if (!length || length > tmp_length) + length=tmp_length; /* The whole blob */ + } + end= pos+length; + if (type == HA_KEYTYPE_TEXT || type == HA_KEYTYPE_VARTEXT1 || + type == HA_KEYTYPE_VARTEXT2) + { + keyseg->charset->coll->hash_sort(keyseg->charset, + (const uchar*) pos, length, &seed1, + &seed2); + crc^= seed1; + } + else + while (pos != end) + crc=((crc << 8) + + (((uchar) *pos++))) + + (crc >> (8*sizeof(ha_checksum)-8)); + } + return crc; +} + + +/* + compare unique key for two rows + + TODO + Add support for bit fields + + RETURN + 0 if both rows have equal unique value + 1 Rows are different +*/ + +my_bool _ma_unique_comp(MARIA_UNIQUEDEF *def, const uchar *a, const uchar *b, + my_bool null_are_equal) +{ + const uchar *pos_a, *pos_b, *end; + HA_KEYSEG *keyseg; + + for (keyseg=def->seg ; keyseg < def->end ; keyseg++) + { + enum ha_base_keytype type=(enum ha_base_keytype) keyseg->type; + uint a_length, b_length; + a_length= b_length= keyseg->length; + + /* If part is NULL it's regarded as different */ + if (keyseg->null_bit) + { + uint tmp; + if ((tmp=(a[keyseg->null_pos] & keyseg->null_bit)) != + (uint) (b[keyseg->null_pos] & keyseg->null_bit)) + return 1; + if (tmp) + { + if (!null_are_equal) + return 1; + continue; + } + } + pos_a= a+keyseg->start; + pos_b= b+keyseg->start; + if (keyseg->flag & HA_VAR_LENGTH_PART) + { + uint pack_length= keyseg->bit_start; + if (pack_length == 1) + { + a_length= (uint) *pos_a++; + b_length= (uint) *pos_b++; + } + else + { + a_length= uint2korr(pos_a); + b_length= uint2korr(pos_b); + pos_a+= 2; /* Skip VARCHAR length */ + pos_b+= 2; + } + set_if_smaller(a_length, keyseg->length); /* Safety */ + set_if_smaller(b_length, keyseg->length); /* safety */ + } + else if (keyseg->flag & HA_BLOB_PART) + { + /* Only compare 'length' characters if length != 0 */ + a_length= _ma_calc_blob_length(keyseg->bit_start,pos_a); + b_length= _ma_calc_blob_length(keyseg->bit_start,pos_b); + /* Check that a and b are of equal length */ + if (keyseg->length) + { + /* + This is used in some cases when we are not interested in comparing + the whole length of the blob. + */ + set_if_smaller(a_length, keyseg->length); + set_if_smaller(b_length, keyseg->length); + } + memcpy_fixed((uchar*) &pos_a,pos_a+keyseg->bit_start,sizeof(char*)); + memcpy_fixed((uchar*) &pos_b,pos_b+keyseg->bit_start,sizeof(char*)); + } + if (type == HA_KEYTYPE_TEXT || type == HA_KEYTYPE_VARTEXT1 || + type == HA_KEYTYPE_VARTEXT2) + { + if (ha_compare_text(keyseg->charset, pos_a, a_length, + pos_b, b_length, 0, 1)) + return 1; + } + else + { + if (a_length != b_length) + return 1; + end= pos_a+a_length; + while (pos_a != end) + { + if (*pos_a++ != *pos_b++) + return 1; + } + } + } + return 0; +} diff --git a/storage/maria/ma_update.c b/storage/maria/ma_update.c new file mode 100644 index 00000000000..7b9e006ec43 --- /dev/null +++ b/storage/maria/ma_update.c @@ -0,0 +1,253 @@ +/* Copyright (C) 2006 MySQL AB & MySQL Finland AB & TCX DataKonsult 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 "ma_fulltext.h" +#include "ma_rt_index.h" +#include "trnman.h" + +/** + Update an old row in a MARIA table +*/ + +int maria_update(register MARIA_HA *info, const uchar *oldrec, uchar *newrec) +{ + int flag,key_changed,save_errno; + reg3 my_off_t pos; + uint i; + uchar old_key_buff[MARIA_MAX_KEY_BUFF],*new_key_buff; + my_bool auto_key_changed= 0; + ulonglong changed; + MARIA_SHARE *share= info->s; + MARIA_KEYDEF *keyinfo; + DBUG_ENTER("maria_update"); + LINT_INIT(new_key_buff); + LINT_INIT(changed); + + DBUG_EXECUTE_IF("maria_pretend_crashed_table_on_usage", + maria_print_error(info->s, HA_ERR_CRASHED); + DBUG_RETURN(my_errno= HA_ERR_CRASHED);); + if (!(info->update & HA_STATE_AKTIV)) + { + DBUG_RETURN(my_errno=HA_ERR_KEY_NOT_FOUND); + } + if (share->options & HA_OPTION_READ_ONLY_DATA) + { + DBUG_RETURN(my_errno=EACCES); + } + if (share->state.state.key_file_length >= share->base.margin_key_file_length) + { + DBUG_RETURN(my_errno=HA_ERR_INDEX_FILE_FULL); + } + pos= info->cur_row.lastpos; + if (_ma_readinfo(info,F_WRLCK,1)) + DBUG_RETURN(my_errno); + + if ((*share->compare_record)(info,oldrec)) + { + save_errno= my_errno; + DBUG_PRINT("warning", ("Got error from compare record")); + goto err_end; /* Record has changed */ + } + + /* Calculate and check all unique constraints */ + key_changed=0; + for (i=0 ; i < share->state.header.uniques ; i++) + { + MARIA_UNIQUEDEF *def=share->uniqueinfo+i; + if (_ma_unique_comp(def, newrec, oldrec,1) && + _ma_check_unique(info, def, newrec, _ma_unique_hash(def, newrec), + pos)) + { + save_errno=my_errno; + goto err_end; + } + } + if (_ma_mark_file_changed(info)) + { + save_errno=my_errno; + goto err_end; + } + + /* Ensure we don't try to restore auto_increment if it doesn't change */ + info->last_auto_increment= ~(ulonglong) 0; + + /* Check which keys changed from the original row */ + + new_key_buff= info->lastkey_buff2; + changed=0; + for (i=0, keyinfo= share->keyinfo ; i < share->base.keys ; i++, keyinfo++) + { + if (maria_is_key_active(share->state.key_map, i)) + { + if (keyinfo->flag & HA_FULLTEXT ) + { + if (_ma_ft_cmp(info,i,oldrec, newrec)) + { + if ((int) i == info->lastinx) + { + /* + We are changeing the index we are reading on. Mark that + the index data has changed and we need to do a full search + when doing read-next + */ + key_changed|=HA_STATE_WRITTEN; + } + changed|=((ulonglong) 1 << i); + if (_ma_ft_update(info,i,old_key_buff,oldrec,newrec,pos)) + goto err; + } + } + else + { + MARIA_KEY new_key, old_key; + + (*keyinfo->make_key)(info,&new_key, i, new_key_buff, newrec, + pos, info->trn->trid); + (*keyinfo->make_key)(info,&old_key, i, old_key_buff, + oldrec, pos, info->cur_row.trid); + + /* The above changed info->lastkey2. Inform maria_rnext_same(). */ + info->update&= ~HA_STATE_RNEXT_SAME; + + if (new_key.data_length != old_key.data_length || + memcmp(old_key.data, new_key.data, new_key.data_length)) + { + if ((int) i == info->lastinx) + key_changed|=HA_STATE_WRITTEN; /* Mark that keyfile changed */ + changed|=((ulonglong) 1 << i); + keyinfo->version++; + if (keyinfo->ck_delete(info,&old_key)) + goto err; + if (keyinfo->ck_insert(info,&new_key)) + goto err; + if (share->base.auto_key == i+1) + auto_key_changed=1; + } + } + } + } + + if (share->calc_checksum) + { + /* + We can't use the row based checksum as this doesn't have enough + precision (one byte, while the table's is more bytes). + At least _ma_check_unique() modifies the 'newrec' record, so checksum + has to be computed _after_ it. Nobody apparently modifies 'oldrec'. + We need to pass the old row's checksum down to (*update_record)(), we do + this via info->new_row.checksum (not intuitive but existing code + mandated that cur_row is the new row). + If (*update_record)() fails, table will be marked corrupted so no need + to revert the live checksum change. + */ + info->cur_row.checksum= (*share->calc_checksum)(info, newrec); + info->new_row.checksum= (*share->calc_checksum)(info, oldrec); + info->state->checksum+= info->cur_row.checksum - info->new_row.checksum; + } + + if ((*share->update_record)(info, pos, oldrec, newrec)) + goto err; + + if (auto_key_changed & !share->now_transactional) + { + const HA_KEYSEG *keyseg= share->keyinfo[share->base.auto_key-1].seg; + const uchar *key= newrec + keyseg->start; + set_if_bigger(share->state.auto_increment, + ma_retrieve_auto_increment(key, keyseg->type)); + } + + /* + We can't yet have HA_STATE_AKTIV here, as block_record dosn't support it + */ + info->update= (HA_STATE_CHANGED | HA_STATE_ROW_CHANGED | key_changed); + share->state.changed|= STATE_NOT_MOVABLE | STATE_NOT_ZEROFILLED; + info->state->changed= 1; + + /* + Every Maria function that updates Maria table must end with + call to _ma_writeinfo(). If operation (second param of + _ma_writeinfo()) is not 0 it sets share->changed to 1, that is + flags that data has changed. If operation is 0, this function + equals to no-op in this case. + + ma_update() must always pass !0 value as operation, since even if + there is no index change there could be data change. + */ + VOID(_ma_writeinfo(info, WRITEINFO_UPDATE_KEYFILE)); + allow_break(); /* Allow SIGHUP & SIGINT */ + if (info->invalidator != 0) + { + DBUG_PRINT("info", ("invalidator... '%s' (update)", + share->open_file_name.str)); + (*info->invalidator)(share->open_file_name.str); + info->invalidator=0; + } + DBUG_RETURN(0); + +err: + DBUG_PRINT("error",("key: %d errno: %d",i,my_errno)); + save_errno= my_errno; + DBUG_ASSERT(save_errno); + if (!save_errno) + save_errno= HA_ERR_INTERNAL_ERROR; /* Should never happen */ + + if (my_errno == HA_ERR_FOUND_DUPP_KEY || my_errno == HA_ERR_OUT_OF_MEM || + my_errno == HA_ERR_RECORD_FILE_FULL) + { + info->errkey= (int) i; + flag=0; + do + { + if (((ulonglong) 1 << i) & changed) + { + if (share->keyinfo[i].flag & HA_FULLTEXT) + { + if ((flag++ && _ma_ft_del(info,i,new_key_buff,newrec,pos)) || + _ma_ft_add(info,i,old_key_buff,oldrec,pos)) + break; + } + else + { + MARIA_KEY new_key, old_key; + (*share->keyinfo[i].make_key)(info, &new_key, i, new_key_buff, + newrec, pos, + info->trn->trid); + (*share->keyinfo[i].make_key)(info, &old_key, i, old_key_buff, + oldrec, pos, info->cur_row.trid); + if ((flag++ && _ma_ck_delete(info, &new_key)) || + _ma_ck_write(info, &old_key)) + break; + } + } + } while (i-- != 0); + } + else + { + maria_print_error(share, HA_ERR_CRASHED); + maria_mark_crashed(info); + } + info->update= (HA_STATE_CHANGED | HA_STATE_AKTIV | HA_STATE_ROW_CHANGED | + key_changed); + + err_end: + VOID(_ma_writeinfo(info,WRITEINFO_UPDATE_KEYFILE)); + allow_break(); /* Allow SIGHUP & SIGINT */ + if (save_errno == HA_ERR_KEY_NOT_FOUND) + { + maria_print_error(share, HA_ERR_CRASHED); + save_errno=HA_ERR_CRASHED; + } + DBUG_RETURN(my_errno=save_errno); +} /* maria_update */ diff --git a/storage/maria/ma_write.c b/storage/maria/ma_write.c new file mode 100644 index 00000000000..20304618229 --- /dev/null +++ b/storage/maria/ma_write.c @@ -0,0 +1,2357 @@ +/* Copyright (C) 2004-2008 MySQL AB & MySQL Finland AB & TCX DataKonsult AB + Copyright (C) 2008-2009 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +/* Write a row to a MARIA table */ + +#include "ma_fulltext.h" +#include "ma_rt_index.h" +#include "trnman.h" +#include "ma_key_recover.h" +#include "ma_blockrec.h" + +#define MAX_POINTER_LENGTH 8 + + /* Functions declared in this file */ + +static int w_search(MARIA_HA *info, uint32 comp_flag, + MARIA_KEY *key, my_off_t page, + MARIA_PAGE *father_page, uchar *father_keypos, + my_bool insert_last); +static int _ma_balance_page(MARIA_HA *info, MARIA_KEYDEF *keyinfo, + MARIA_KEY *key, MARIA_PAGE *curr_page, + MARIA_PAGE *father_page, + uchar *father_key_pos, MARIA_KEY_PARAM *s_temp); +static uchar *_ma_find_last_pos(MARIA_KEY *int_key, + MARIA_PAGE *page, uchar **after_key); +static my_bool _ma_ck_write_tree(register MARIA_HA *info, MARIA_KEY *key); +static my_bool _ma_ck_write_btree(register MARIA_HA *info, MARIA_KEY *key); +static my_bool _ma_ck_write_btree_with_log(MARIA_HA *, MARIA_KEY *, my_off_t *, + uint32); +static my_bool _ma_log_split(MARIA_PAGE *page, uint org_length, + uint new_length, + const uchar *key_pos, + uint key_length, int move_length, + enum en_key_op prefix_or_suffix, + const uchar *data, uint data_length, + uint changed_length); +static my_bool _ma_log_del_prefix(MARIA_PAGE *page, + uint org_length, uint new_length, + const uchar *key_pos, uint key_length, + int move_length); +static my_bool _ma_log_key_middle(MARIA_PAGE *page, + uint new_length, + uint data_added_first, + uint data_changed_first, + uint data_deleted_last, + const uchar *key_pos, + uint key_length, int move_length); + +/* + @brief Default handler for returing position to new row + + @note + This is only called for non transactional tables and not for block format + which is why we use info->state here. +*/ + +MARIA_RECORD_POS _ma_write_init_default(MARIA_HA *info, + const uchar *record + __attribute__((unused))) +{ + return ((info->s->state.dellink != HA_OFFSET_ERROR && + !info->append_insert_at_end) ? + info->s->state.dellink : + info->state->data_file_length); +} + +my_bool _ma_write_abort_default(MARIA_HA *info __attribute__((unused))) +{ + return 0; +} + + +/* Write new record to a table */ + +int maria_write(MARIA_HA *info, uchar *record) +{ + MARIA_SHARE *share= info->s; + uint i; + int save_errno; + MARIA_RECORD_POS filepos; + uchar *buff; + my_bool lock_tree= share->lock_key_trees; + my_bool fatal_error; + MARIA_KEYDEF *keyinfo; + DBUG_ENTER("maria_write"); + DBUG_PRINT("enter",("index_file: %d data_file: %d", + share->kfile.file, info->dfile.file)); + + DBUG_EXECUTE_IF("maria_pretend_crashed_table_on_usage", + maria_print_error(info->s, HA_ERR_CRASHED); + DBUG_RETURN(my_errno= HA_ERR_CRASHED);); + if (share->options & HA_OPTION_READ_ONLY_DATA) + { + DBUG_RETURN(my_errno=EACCES); + } + if (_ma_readinfo(info,F_WRLCK,1)) + DBUG_RETURN(my_errno); + dont_break(); /* Dont allow SIGHUP or SIGINT */ + + if (share->base.reloc == (ha_rows) 1 && + share->base.records == (ha_rows) 1 && + share->state.state.records == (ha_rows) 1) + { /* System file */ + my_errno=HA_ERR_RECORD_FILE_FULL; + goto err2; + } + if (share->state.state.key_file_length >= share->base.margin_key_file_length) + { + my_errno=HA_ERR_INDEX_FILE_FULL; + goto err2; + } + if (_ma_mark_file_changed(info)) + goto err2; + + /* Calculate and check all unique constraints */ + for (i=0 ; i < share->state.header.uniques ; i++) + { + if (_ma_check_unique(info,share->uniqueinfo+i,record, + _ma_unique_hash(share->uniqueinfo+i,record), + HA_OFFSET_ERROR)) + goto err2; + } + + /* Ensure we don't try to restore auto_increment if it doesn't change */ + info->last_auto_increment= ~(ulonglong) 0; + + if ((info->opt_flag & OPT_NO_ROWS)) + filepos= HA_OFFSET_ERROR; + else + { + /* + This may either calculate a record or, or write the record and return + the record id + */ + if ((filepos= (*share->write_record_init)(info, record)) == + HA_OFFSET_ERROR) + goto err2; + } + + /* Write all keys to indextree */ + buff= info->lastkey_buff2; + for (i=0, keyinfo= share->keyinfo ; i < share->base.keys ; i++, keyinfo++) + { + MARIA_KEY int_key; + if (maria_is_key_active(share->state.key_map, i)) + { + my_bool local_lock_tree= (lock_tree && + !(info->bulk_insert && + is_tree_inited(&info->bulk_insert[i]))); + if (local_lock_tree) + { + rw_wrlock(&keyinfo->root_lock); + keyinfo->version++; + } + if (keyinfo->flag & HA_FULLTEXT ) + { + if (_ma_ft_add(info,i, buff,record,filepos)) + { + if (local_lock_tree) + rw_unlock(&keyinfo->root_lock); + DBUG_PRINT("error",("Got error: %d on write",my_errno)); + goto err; + } + } + else + { + while (keyinfo->ck_insert(info, + (*keyinfo->make_key)(info, &int_key, i, + buff, record, filepos, + info->trn->trid))) + { + TRN *blocker; + DBUG_PRINT("error",("Got error: %d on write",my_errno)); + /* + explicit check to filter out temp tables, they aren't + transactional and don't have a proper TRN so the code + below doesn't work for them. + Also, filter out non-thread maria use, and table modified in + the same transaction. + At last, filter out non-dup-unique errors. + */ + if (!local_lock_tree) + goto err; + if (info->dup_key_trid == info->trn->trid || + my_errno != HA_ERR_FOUND_DUPP_KEY) + { + rw_unlock(&keyinfo->root_lock); + goto err; + } + /* Different TrIDs: table must be transactional */ + DBUG_ASSERT(share->base.born_transactional); + /* + If transactions are disabled, and dup_key_trid is different from + our TrID, it must be ALTER TABLE with dup_key_trid==0 (no + transaction). ALTER TABLE does have MARIA_HA::TRN not dummy but + puts TrID=0 in rows/keys. + */ + DBUG_ASSERT(share->now_transactional || + (info->dup_key_trid == 0)); + blocker= trnman_trid_to_trn(info->trn, info->dup_key_trid); + /* + if blocker TRN was not found, it means that the conflicting + transaction was committed long time ago. It could not be + aborted, as it would have to wait on the key tree lock + to remove the conflicting key it has inserted. + */ + if (!blocker || blocker->commit_trid != ~(TrID)0) + { /* committed */ + if (blocker) + pthread_mutex_unlock(& blocker->state_lock); + rw_unlock(&keyinfo->root_lock); + goto err; + } + rw_unlock(&keyinfo->root_lock); + { + /* running. now we wait */ + WT_RESOURCE_ID rc; + int res; + const char *old_proc_info; + + rc.type= &ma_rc_dup_unique; + /* TODO savepoint id when we'll have them */ + rc.value= (intptr)blocker; + res= wt_thd_will_wait_for(info->trn->wt, blocker->wt, & rc); + if (res != WT_OK) + { + pthread_mutex_unlock(& blocker->state_lock); + my_errno= HA_ERR_LOCK_DEADLOCK; + goto err; + } + old_proc_info= proc_info_hook(0, + "waiting for a resource", + __func__, __FILE__, __LINE__); + res= wt_thd_cond_timedwait(info->trn->wt, & blocker->state_lock); + proc_info_hook(0, old_proc_info, __func__, __FILE__, __LINE__); + + pthread_mutex_unlock(& blocker->state_lock); + if (res != WT_OK) + { + my_errno= res == WT_TIMEOUT ? HA_ERR_LOCK_WAIT_TIMEOUT + : HA_ERR_LOCK_DEADLOCK; + goto err; + } + } + rw_wrlock(&keyinfo->root_lock); +#ifndef MARIA_CANNOT_ROLLBACK + keyinfo->version++; +#endif + } + } + + /* The above changed info->lastkey2. Inform maria_rnext_same(). */ + info->update&= ~HA_STATE_RNEXT_SAME; + + if (local_lock_tree) + rw_unlock(&keyinfo->root_lock); + } + } + if (share->calc_write_checksum) + info->cur_row.checksum= (*share->calc_write_checksum)(info,record); + if (filepos != HA_OFFSET_ERROR) + { + if ((*share->write_record)(info,record)) + goto err; + info->state->checksum+= info->cur_row.checksum; + } + if (!share->now_transactional) + { + if (share->base.auto_key != 0) + { + const HA_KEYSEG *keyseg= share->keyinfo[share->base.auto_key-1].seg; + const uchar *key= record + keyseg->start; + set_if_bigger(share->state.auto_increment, + ma_retrieve_auto_increment(key, keyseg->type)); + } + } + info->state->records++; + info->update= (HA_STATE_CHANGED | HA_STATE_AKTIV | HA_STATE_WRITTEN | + HA_STATE_ROW_CHANGED); + share->state.changed|= STATE_NOT_MOVABLE | STATE_NOT_ZEROFILLED; + info->state->changed= 1; + + info->cur_row.lastpos= filepos; + VOID(_ma_writeinfo(info, WRITEINFO_UPDATE_KEYFILE)); + if (info->invalidator != 0) + { + DBUG_PRINT("info", ("invalidator... '%s' (update)", + share->open_file_name.str)); + (*info->invalidator)(share->open_file_name.str); + info->invalidator=0; + } + + /* + Update status of the table. We need to do so after each row write + for the log tables, as we want the new row to become visible to + other threads as soon as possible. We don't lock mutex here + (as it is required by pthread memory visibility rules) as (1) it's + not critical to use outdated share->is_log_table value (2) locking + mutex here for every write is too expensive. + */ + if (share->is_log_table) + _ma_update_status((void*) info); + + allow_break(); /* Allow SIGHUP & SIGINT */ + DBUG_RETURN(0); + +err: + save_errno= my_errno; + fatal_error= 0; + if (my_errno == HA_ERR_FOUND_DUPP_KEY || + my_errno == HA_ERR_RECORD_FILE_FULL || + my_errno == HA_ERR_LOCK_DEADLOCK || + my_errno == HA_ERR_LOCK_WAIT_TIMEOUT || + my_errno == HA_ERR_NULL_IN_SPATIAL || + my_errno == HA_ERR_OUT_OF_MEM) + { + if (info->bulk_insert) + { + uint j; + for (j=0 ; j < share->base.keys ; j++) + maria_flush_bulk_insert(info, j); + } + info->errkey= (int) i; + /* + We delete keys in the reverse order of insertion. This is the order that + a rollback would do and is important for CLR_ENDs generated by + _ma_ft|ck_delete() and write_record_abort() to work (with any other + order they would cause wrong jumps in the chain). + */ + while ( i-- > 0) + { + if (maria_is_key_active(share->state.key_map, i)) + { + my_bool local_lock_tree= (lock_tree && + !(info->bulk_insert && + is_tree_inited(&info->bulk_insert[i]))); + keyinfo= share->keyinfo + i; + if (local_lock_tree) + rw_wrlock(&keyinfo->root_lock); + /** + @todo RECOVERY BUG + The key deletes below should generate CLR_ENDs + */ + if (keyinfo->flag & HA_FULLTEXT) + { + if (_ma_ft_del(info,i,buff,record,filepos)) + { + if (local_lock_tree) + rw_unlock(&keyinfo->root_lock); + break; + } + } + else + { + MARIA_KEY key; + if (_ma_ck_delete(info, + (*keyinfo->make_key)(info, &key, i, buff, record, + filepos, info->trn->trid))) + { + if (local_lock_tree) + rw_unlock(&keyinfo->root_lock); + break; + } + } + if (local_lock_tree) + rw_unlock(&keyinfo->root_lock); + } + } + } + else + fatal_error= 1; + + if ((*share->write_record_abort)(info)) + fatal_error= 1; + if (fatal_error) + { + maria_print_error(info->s, HA_ERR_CRASHED); + maria_mark_crashed(info); + } + + info->update= (HA_STATE_CHANGED | HA_STATE_WRITTEN | HA_STATE_ROW_CHANGED); + my_errno=save_errno; +err2: + save_errno=my_errno; + DBUG_ASSERT(save_errno); + if (!save_errno) + save_errno= HA_ERR_INTERNAL_ERROR; /* Should never happen */ + DBUG_PRINT("error", ("got error: %d", save_errno)); + VOID(_ma_writeinfo(info,WRITEINFO_UPDATE_KEYFILE)); + allow_break(); /* Allow SIGHUP & SIGINT */ + DBUG_RETURN(my_errno=save_errno); +} /* maria_write */ + + +/* + Write one key to btree + + TODO + Remove this function and have bulk insert change keyinfo->ck_insert + to point to the right function +*/ + +my_bool _ma_ck_write(MARIA_HA *info, MARIA_KEY *key) +{ + DBUG_ENTER("_ma_ck_write"); + + if (info->bulk_insert && + is_tree_inited(&info->bulk_insert[key->keyinfo->key_nr])) + { + DBUG_RETURN(_ma_ck_write_tree(info, key)); + } + DBUG_RETURN(_ma_ck_write_btree(info, key)); +} /* _ma_ck_write */ + + +/********************************************************************** + Insert key into btree (normal case) +**********************************************************************/ + +static my_bool _ma_ck_write_btree(MARIA_HA *info, MARIA_KEY *key) +{ + my_bool error; + MARIA_KEYDEF *keyinfo= key->keyinfo; + my_off_t *root= &info->s->state.key_root[keyinfo->key_nr]; + DBUG_ENTER("_ma_ck_write_btree"); + + error= _ma_ck_write_btree_with_log(info, key, root, + keyinfo->write_comp_flag | key->flag); + if (info->ft1_to_ft2) + { + if (!error) + error= _ma_ft_convert_to_ft2(info, key); + delete_dynamic(info->ft1_to_ft2); + my_free(info->ft1_to_ft2, MYF(0)); + info->ft1_to_ft2=0; + } + DBUG_RETURN(error); +} /* _ma_ck_write_btree */ + + +/** + @brief Write a key to the b-tree + + @retval 1 error + @retval 0 ok +*/ + +static my_bool _ma_ck_write_btree_with_log(MARIA_HA *info, MARIA_KEY *key, + my_off_t *root, uint32 comp_flag) +{ + MARIA_SHARE *share= info->s; + LSN lsn= LSN_IMPOSSIBLE; + int error; + my_off_t new_root= *root; + uchar key_buff[MARIA_MAX_KEY_BUFF]; + MARIA_KEY org_key; + DBUG_ENTER("_ma_ck_write_btree_with_log"); + + LINT_INIT_STRUCT(org_key); + if (share->now_transactional) + { + /* Save original value as the key may change */ + org_key= *key; + memcpy(key_buff, key->data, key->data_length + key->ref_length); + } + + error= _ma_ck_real_write_btree(info, key, &new_root, comp_flag); + if (!error && share->now_transactional) + { + /* Log the original value */ + *key= org_key; + key->data= key_buff; + error= _ma_write_undo_key_insert(info, key, root, new_root, &lsn); + } + else + { + *root= new_root; + _ma_fast_unlock_key_del(info); + } + _ma_unpin_all_pages_and_finalize_row(info, lsn); + + DBUG_RETURN(error != 0); +} /* _ma_ck_write_btree_with_log */ + + +/** + @brief Write a key to the b-tree + + @retval 1 error + @retval 0 ok +*/ + +my_bool _ma_ck_real_write_btree(MARIA_HA *info, MARIA_KEY *key, my_off_t *root, + uint32 comp_flag) +{ + int error; + DBUG_ENTER("_ma_ck_real_write_btree"); + + /* key_length parameter is used only if comp_flag is SEARCH_FIND */ + if (*root == HA_OFFSET_ERROR || + (error= w_search(info, comp_flag, key, *root, (MARIA_PAGE *) 0, + (uchar*) 0, 1)) > 0) + error= _ma_enlarge_root(info, key, root); + DBUG_RETURN(error != 0); +} /* _ma_ck_real_write_btree */ + + +/** + @brief Make a new root with key as only pointer + + @retval 1 error + @retval 0 ok +*/ + +my_bool _ma_enlarge_root(MARIA_HA *info, MARIA_KEY *key, my_off_t *root) +{ + uint t_length, nod_flag; + MARIA_KEY_PARAM s_temp; + MARIA_SHARE *share= info->s; + MARIA_PINNED_PAGE tmp_page_link, *page_link= &tmp_page_link; + MARIA_KEYDEF *keyinfo= key->keyinfo; + MARIA_PAGE page; + my_bool res= 0; + DBUG_ENTER("_ma_enlarge_root"); + + page.info= info; + page.keyinfo= keyinfo; + page.buff= info->buff; + page.flag= 0; + + nod_flag= (*root != HA_OFFSET_ERROR) ? share->base.key_reflength : 0; + /* Store pointer to prev page if nod */ + _ma_kpointer(info, page.buff + share->keypage_header, *root); + t_length= (*keyinfo->pack_key)(key, nod_flag, (uchar*) 0, + (uchar*) 0, (uchar*) 0, &s_temp); + page.size= share->keypage_header + t_length + nod_flag; + + bzero(page.buff, share->keypage_header); + _ma_store_keynr(share, page.buff, keyinfo->key_nr); + if (nod_flag) + page.flag|= KEYPAGE_FLAG_ISNOD; + if (key->flag & (SEARCH_USER_KEY_HAS_TRANSID | SEARCH_PAGE_KEY_HAS_TRANSID)) + page.flag|= KEYPAGE_FLAG_HAS_TRANSID; + (*keyinfo->store_key)(keyinfo, page.buff + share->keypage_header + + nod_flag, &s_temp); + + /* Mark that info->buff was used */ + info->keyread_buff_used= info->page_changed= 1; + if ((page.pos= _ma_new(info, PAGECACHE_PRIORITY_HIGH, &page_link)) == + HA_OFFSET_ERROR) + DBUG_RETURN(1); + *root= page.pos; + + page_store_info(share, &page); + + /* + Clear unitialized part of page to avoid valgrind/purify warnings + and to get a clean page that is easier to compress and compare with + pages generated with redo + */ + bzero(page.buff + page.size, share->block_size - page.size); + + if (share->now_transactional && _ma_log_new(&page, 1)) + res= 1; + + if (_ma_write_keypage(&page, page_link->write_lock, + PAGECACHE_PRIORITY_HIGH)) + res= 1; + + DBUG_RETURN(res); +} /* _ma_enlarge_root */ + + +/* + Search after a position for a key and store it there + + @return + @retval -1 error + @retval 0 ok + @retval > 0 Key should be stored in higher tree +*/ + +static int w_search(register MARIA_HA *info, uint32 comp_flag, MARIA_KEY *key, + my_off_t page_pos, + MARIA_PAGE *father_page, uchar *father_keypos, + my_bool insert_last) +{ + int error,flag; + uchar *temp_buff,*keypos; + uchar keybuff[MARIA_MAX_KEY_BUFF]; + my_bool was_last_key; + my_off_t next_page, dup_key_pos; + MARIA_SHARE *share= info->s; + MARIA_KEYDEF *keyinfo= key->keyinfo; + MARIA_PAGE page; + DBUG_ENTER("w_search"); + DBUG_PRINT("enter",("page: %ld", (long) page_pos)); + + if (!(temp_buff= (uchar*) my_alloca((uint) keyinfo->block_length+ + MARIA_MAX_KEY_BUFF*2))) + DBUG_RETURN(-1); + if (_ma_fetch_keypage(&page, info, keyinfo, page_pos, PAGECACHE_LOCK_WRITE, + DFLT_INIT_HITS, temp_buff, 0)) + goto err; + + flag= (*keyinfo->bin_search)(key, &page, comp_flag, &keypos, + keybuff, &was_last_key); + if (flag == 0) + { + MARIA_KEY tmp_key; + /* get position to record with duplicated key */ + + tmp_key.keyinfo= keyinfo; + tmp_key.data= keybuff; + + if ((*keyinfo->get_key)(&tmp_key, page.flag, page.node, &keypos)) + dup_key_pos= _ma_row_pos_from_key(&tmp_key); + else + dup_key_pos= HA_OFFSET_ERROR; + + if (keyinfo->flag & HA_FULLTEXT) + { + uint off; + int subkeys; + + get_key_full_length_rdonly(off, keybuff); + subkeys=ft_sintXkorr(keybuff+off); + comp_flag=SEARCH_SAME; + if (subkeys >= 0) + { + /* normal word, one-level tree structure */ + flag=(*keyinfo->bin_search)(key, &page, comp_flag, + &keypos, keybuff, &was_last_key); + } + else + { + /* popular word. two-level tree. going down */ + my_off_t root=dup_key_pos; + keyinfo= &share->ft2_keyinfo; + get_key_full_length_rdonly(off, key); + key+=off; + /* we'll modify key entry 'in vivo' */ + keypos-= keyinfo->keylength + page.node; + error= _ma_ck_real_write_btree(info, key, &root, comp_flag); + _ma_dpointer(share, keypos+HA_FT_WLEN, root); + subkeys--; /* should there be underflow protection ? */ + DBUG_ASSERT(subkeys < 0); + ft_intXstore(keypos, subkeys); + if (!error) + { + page_mark_changed(info, &page); + if (_ma_write_keypage(&page, PAGECACHE_LOCK_LEFT_WRITELOCKED, + DFLT_INIT_HITS)) + goto err; + } + my_afree(temp_buff); + DBUG_RETURN(error); + } + } + else /* not HA_FULLTEXT, normal HA_NOSAME key */ + { + DBUG_PRINT("warning", ("Duplicate key")); + /* + TODO + When the index will support true versioning - with multiple + identical values in the UNIQUE index, invisible to each other - + the following should be changed to "continue inserting keys, at the + end (of the row or statement) wait". We need to wait on *all* + unique conflicts at once, not one-at-a-time, because we need to + know all blockers in advance, otherwise we'll have incomplete wait-for + graph. + */ + /* + transaction that has inserted the conflicting key may be in progress. + the caller will wait for it to be committed or aborted. + */ + info->dup_key_trid= _ma_trid_from_key(&tmp_key); + info->dup_key_pos= dup_key_pos; + my_errno= HA_ERR_FOUND_DUPP_KEY; + goto err; + } + } + if (flag == MARIA_FOUND_WRONG_KEY) + goto err; + if (!was_last_key) + insert_last=0; + next_page= _ma_kpos(page.node, keypos); + if (next_page == HA_OFFSET_ERROR || + (error= w_search(info, comp_flag, key, next_page, + &page, keypos, insert_last)) > 0) + { + error= _ma_insert(info, key, &page, keypos, keybuff, + father_page, father_keypos, insert_last); + page_mark_changed(info, &page); + if (_ma_write_keypage(&page, PAGECACHE_LOCK_LEFT_WRITELOCKED, + DFLT_INIT_HITS)) + goto err; + } + my_afree(temp_buff); + DBUG_RETURN(error); +err: + my_afree(temp_buff); + DBUG_PRINT("exit",("Error: %d",my_errno)); + DBUG_RETURN(-1); +} /* w_search */ + + +/* + Insert new key. + + SYNOPSIS + _ma_insert() + info Open table information. + keyinfo Key definition information. + key New key + anc_page Key page (beginning) + key_pos Position in key page where to insert. + key_buff Copy of previous key if keys where packed. + father_page position of parent key page in file. + father_key_pos position in parent key page for balancing. + insert_last If to append at end of page. + + DESCRIPTION + Insert new key at right of key_pos. + Note that caller must save anc_buff + + This function writes log records for all changed pages + (Including anc_buff and father page) + + RETURN + < 0 Error. + 0 OK + 1 If key contains key to upper level (from balance page) + 2 If key contains key to upper level (from split space) +*/ + +int _ma_insert(register MARIA_HA *info, MARIA_KEY *key, + MARIA_PAGE *anc_page, uchar *key_pos, uchar *key_buff, + MARIA_PAGE *father_page, uchar *father_key_pos, + my_bool insert_last) +{ + uint a_length, nod_flag, org_anc_length; + int t_length; + uchar *endpos, *prev_key, *anc_buff; + MARIA_KEY_PARAM s_temp; + MARIA_SHARE *share= info->s; + MARIA_KEYDEF *keyinfo= key->keyinfo; + DBUG_ENTER("_ma_insert"); + DBUG_PRINT("enter",("key_pos: 0x%lx", (ulong) key_pos)); + DBUG_EXECUTE("key", _ma_print_key(DBUG_FILE, key);); + + org_anc_length= a_length= anc_page->size; + nod_flag= anc_page->node; + + anc_buff= anc_page->buff; + endpos= anc_buff+ a_length; + prev_key= (key_pos == anc_buff + share->keypage_header + nod_flag ? + (uchar*) 0 : key_buff); + t_length= (*keyinfo->pack_key)(key, nod_flag, + (key_pos == endpos ? (uchar*) 0 : key_pos), + prev_key, prev_key, &s_temp); +#ifndef DBUG_OFF + if (prev_key && (keyinfo->flag & (HA_BINARY_PACK_KEY | HA_PACK_KEY))) + { + DBUG_DUMP("prev_key", prev_key, _ma_keylength(keyinfo,prev_key)); + } + if (keyinfo->flag & HA_PACK_KEY) + { + DBUG_PRINT("test",("t_length: %d ref_len: %d", + t_length,s_temp.ref_length)); + DBUG_PRINT("test",("n_ref_len: %d n_length: %d key_pos: 0x%lx", + s_temp.n_ref_length, s_temp.n_length, (long) s_temp.key)); + } +#endif + if (t_length > 0) + { + if (t_length >= keyinfo->maxlength*2+MAX_POINTER_LENGTH) + { + my_errno=HA_ERR_CRASHED; + DBUG_RETURN(-1); + } + bmove_upp(endpos+t_length, endpos, (uint) (endpos-key_pos)); + } + else + { + if (-t_length >= keyinfo->maxlength*2+MAX_POINTER_LENGTH) + { + my_errno=HA_ERR_CRASHED; + DBUG_RETURN(-1); + } + bmove(key_pos,key_pos-t_length,(uint) (endpos-key_pos)+t_length); + } + (*keyinfo->store_key)(keyinfo,key_pos,&s_temp); + a_length+=t_length; + + if (key->flag & (SEARCH_USER_KEY_HAS_TRANSID | SEARCH_PAGE_KEY_HAS_TRANSID)) + { + _ma_mark_page_with_transid(share, anc_page); + } + anc_page->size= a_length; + page_store_size(share, anc_page); + + /* + Check if the new key fits totally into the the page + (anc_buff is big enough to contain a full page + one key) + */ + if (a_length <= (uint) keyinfo->block_length - KEYPAGE_CHECKSUM_SIZE) + { + if (keyinfo->block_length - KEYPAGE_CHECKSUM_SIZE - a_length < 32 && + (keyinfo->flag & HA_FULLTEXT) && key_pos == endpos && + share->base.key_reflength <= share->base.rec_reflength && + share->options & (HA_OPTION_PACK_RECORD | HA_OPTION_COMPRESS_RECORD)) + { + /* + Normal word. One-level tree. Page is almost full. + Let's consider converting. + We'll compare 'key' and the first key at anc_buff + */ + const uchar *a= key->data; + const uchar *b= anc_buff + share->keypage_header + nod_flag; + uint alen, blen, ft2len= share->ft2_keyinfo.keylength; + /* the very first key on the page is always unpacked */ + DBUG_ASSERT((*b & 128) == 0); +#if HA_FT_MAXLEN >= 127 + blen= mi_uint2korr(b); b+=2; + When you enable this code, as part of the MyISAM->Maria merge of +ChangeSet@1.2562, 2008-04-09 07:41:40+02:00, serg@janus.mylan +9 -0 + restore ft2 functionality, fix bugs. + Then this will enable two-level fulltext index, which is not totally + recoverable yet. + So remove this text and inform Guilhem so that he fixes the issue. +#else + blen= *b++; +#endif + get_key_length(alen,a); + DBUG_ASSERT(info->ft1_to_ft2==0); + if (alen == blen && + ha_compare_text(keyinfo->seg->charset, a, alen, + b, blen, 0, 0) == 0) + { + /* Yup. converting */ + info->ft1_to_ft2=(DYNAMIC_ARRAY *) + my_malloc(sizeof(DYNAMIC_ARRAY), MYF(MY_WME)); + my_init_dynamic_array(info->ft1_to_ft2, ft2len, 300, 50); + + /* + Now, adding all keys from the page to dynarray + if the page is a leaf (if not keys will be deleted later) + */ + if (!nod_flag) + { + /* + Let's leave the first key on the page, though, because + we cannot easily dispatch an empty page here + */ + b+=blen+ft2len+2; + for (a=anc_buff+a_length ; b < a ; b+=ft2len+2) + insert_dynamic(info->ft1_to_ft2, b); + + /* fixing the page's length - it contains only one key now */ + anc_page->size= share->keypage_header + blen + ft2len + 2; + page_store_size(share, anc_page); + } + /* the rest will be done when we're back from recursion */ + } + } + else + { + if (share->now_transactional && + _ma_log_add(anc_page, org_anc_length, + key_pos, s_temp.changed_length, t_length, 0)) + DBUG_RETURN(-1); + } + DBUG_RETURN(0); /* There is room on page */ + } + /* Page is full */ + if (nod_flag) + insert_last=0; + /* + TODO: + Remove 'born_transactional' here. + The only reason for having it here is that the current + _ma_balance_page_ can't handle variable length keys. + */ + if (!(keyinfo->flag & (HA_VAR_LENGTH_KEY | HA_BINARY_PACK_KEY)) && + father_page && !insert_last && !info->quick_mode && + !info->s->base.born_transactional) + { + s_temp.key_pos= key_pos; + page_mark_changed(info, father_page); + DBUG_RETURN(_ma_balance_page(info, keyinfo, key, anc_page, + father_page, father_key_pos, + &s_temp)); + } + DBUG_RETURN(_ma_split_page(info, key, anc_page, org_anc_length, + key_pos, s_temp.changed_length, t_length, + key_buff, insert_last)); +} /* _ma_insert */ + + +/** + @brief split a full page in two and assign emerging item to key + + @fn _ma_split_page() + info Maria handler + keyinfo Key handler + key Buffer for middle key + split_page Page that should be split + org_split_length Original length of split_page before key was inserted + inserted_key_pos Address in buffer where key was inserted + changed_length Number of bytes changed at 'inserted_key_pos' + move_length Number of bytes buffer was moved when key was inserted + key_buff Key buffer to use for temporary storage of key + insert_last_key If we are insert key on rightmost key page + + @note + split_buff is not stored on disk (caller has to do this) + + @return + @retval 2 ok (Middle key up from _ma_insert()) + @retval -1 error +*/ + +int _ma_split_page(MARIA_HA *info, MARIA_KEY *key, MARIA_PAGE *split_page, + uint org_split_length, + uchar *inserted_key_pos, uint changed_length, + int move_length, + uchar *key_buff, my_bool insert_last_key) +{ + uint length,a_length,key_ref_length,t_length,nod_flag,key_length; + uint page_length, split_length, page_flag; + uchar *key_pos,*pos, *after_key; + MARIA_KEY_PARAM s_temp; + MARIA_PINNED_PAGE tmp_page_link, *page_link= &tmp_page_link; + MARIA_SHARE *share= info->s; + MARIA_KEYDEF *keyinfo= key->keyinfo; + MARIA_KEY tmp_key; + MARIA_PAGE new_page; + int res; + DBUG_ENTER("_ma_split_page"); + + LINT_INIT(after_key); + DBUG_DUMP("buff", split_page->buff, split_page->size); + + info->page_changed=1; /* Info->buff is used */ + info->keyread_buff_used=1; + page_flag= split_page->flag; + nod_flag= split_page->node; + key_ref_length= share->keypage_header + nod_flag; + + new_page.info= info; + new_page.buff= info->buff; + new_page.keyinfo= keyinfo; + + tmp_key.data= key_buff; + tmp_key.keyinfo= keyinfo; + if (insert_last_key) + key_pos= _ma_find_last_pos(&tmp_key, split_page, &after_key); + else + key_pos= _ma_find_half_pos(&tmp_key, split_page, &after_key); + if (!key_pos) + DBUG_RETURN(-1); + + key_length= tmp_key.data_length + tmp_key.ref_length; + split_length= (uint) (key_pos - split_page->buff); + a_length= split_page->size; + split_page->size= split_length; + page_store_size(share, split_page); + + key_pos=after_key; + if (nod_flag) + { + DBUG_PRINT("test",("Splitting nod")); + pos=key_pos-nod_flag; + memcpy(new_page.buff + share->keypage_header, pos, (size_t) nod_flag); + } + + /* Move middle item to key and pointer to new page */ + if ((new_page.pos= _ma_new(info, PAGECACHE_PRIORITY_HIGH, &page_link)) == + HA_OFFSET_ERROR) + DBUG_RETURN(-1); + + _ma_copy_key(key, &tmp_key); + _ma_kpointer(info, key->data + key_length, new_page.pos); + + /* Store new page */ + if (!(*keyinfo->get_key)(&tmp_key, page_flag, nod_flag, &key_pos)) + DBUG_RETURN(-1); + + t_length=(*keyinfo->pack_key)(&tmp_key, nod_flag, (uchar *) 0, + (uchar*) 0, (uchar*) 0, &s_temp); + length=(uint) ((split_page->buff + a_length) - key_pos); + memcpy(new_page.buff + key_ref_length + t_length, key_pos, + (size_t) length); + (*keyinfo->store_key)(keyinfo,new_page.buff+key_ref_length,&s_temp); + page_length= length + t_length + key_ref_length; + + bzero(new_page.buff, share->keypage_header); + /* Copy KEYFLAG_FLAG_ISNODE and KEYPAGE_FLAG_HAS_TRANSID from parent page */ + new_page.flag= page_flag; + new_page.size= page_length; + page_store_info(share, &new_page); + + /* Copy key number */ + new_page.buff[share->keypage_header - KEYPAGE_USED_SIZE - + KEYPAGE_KEYID_SIZE - KEYPAGE_FLAG_SIZE]= + split_page->buff[share->keypage_header - KEYPAGE_USED_SIZE - + KEYPAGE_KEYID_SIZE - KEYPAGE_FLAG_SIZE]; + + res= 2; /* Middle key up */ + if (share->now_transactional && _ma_log_new(&new_page, 0)) + res= -1; + + /* + Clear unitialized part of page to avoid valgrind/purify warnings + and to get a clean page that is easier to compress and compare with + pages generated with redo + */ + bzero(new_page.buff + page_length, share->block_size - page_length); + + if (_ma_write_keypage(&new_page, page_link->write_lock, + DFLT_INIT_HITS)) + res= -1; + + /* Save changes to split pages */ + if (share->now_transactional && + _ma_log_split(split_page, org_split_length, split_length, + inserted_key_pos, changed_length, move_length, + KEY_OP_NONE, (uchar*) 0, 0, 0)) + res= -1; + + DBUG_DUMP_KEY("middle_key", key); + DBUG_RETURN(res); +} /* _ma_split_page */ + + +/* + Calculate how to much to move to split a page in two + + Returns pointer to start of key. + key will contain the key. + return_key_length will contain the length of key + after_key will contain the position to where the next key starts +*/ + +uchar *_ma_find_half_pos(MARIA_KEY *key, MARIA_PAGE *ma_page, + uchar **after_key) +{ + uint keys, length, key_ref_length, page_flag, nod_flag; + uchar *page, *end, *lastpos; + MARIA_HA *info= ma_page->info; + MARIA_SHARE *share= info->s; + MARIA_KEYDEF *keyinfo= key->keyinfo; + DBUG_ENTER("_ma_find_half_pos"); + + nod_flag= ma_page->node; + key_ref_length= share->keypage_header + nod_flag; + page_flag= ma_page->flag; + length= ma_page->size - key_ref_length; + page= ma_page->buff+ key_ref_length; /* Point to first key */ + + if (!(keyinfo->flag & + (HA_PACK_KEY | HA_SPACE_PACK_USED | HA_VAR_LENGTH_KEY | + HA_BINARY_PACK_KEY)) && !(page_flag & KEYPAGE_FLAG_HAS_TRANSID)) + { + key_ref_length= keyinfo->keylength+nod_flag; + key->data_length= keyinfo->keylength - info->s->rec_reflength; + key->ref_length= info->s->rec_reflength; + key->flag= 0; + keys=length/(key_ref_length*2); + end=page+keys*key_ref_length; + *after_key=end+key_ref_length; + memcpy(key->data, end, key_ref_length); + DBUG_RETURN(end); + } + + end=page+length/2-key_ref_length; /* This is aprox. half */ + key->data[0]= 0; /* Safety */ + do + { + lastpos=page; + if (!(length= (*keyinfo->get_key)(key, page_flag, nod_flag, &page))) + DBUG_RETURN(0); + } while (page < end); + *after_key= page; + DBUG_PRINT("exit",("returns: 0x%lx page: 0x%lx half: 0x%lx", + (long) lastpos, (long) page, (long) end)); + DBUG_RETURN(lastpos); +} /* _ma_find_half_pos */ + + +/** + Find second to last key on leaf page + + @notes + Used to split buffer at last key. In this case the next to last + key will be moved to parent page and last key will be on it's own page. + + @TODO + Add one argument for 'last key value' to get_key so that one can + do the loop without having to copy the found key the whole time + + @return + @retval Pointer to the start of the key before the last key + @retval int_key will contain the last key +*/ + +static uchar *_ma_find_last_pos(MARIA_KEY *int_key, MARIA_PAGE *ma_page, + uchar **after_key) +{ + uint keys, length, key_ref_length, page_flag; + uchar *page, *end, *lastpos, *prevpos; + uchar key_buff[MARIA_MAX_KEY_BUFF]; + MARIA_HA *info= ma_page->info; + MARIA_SHARE *share= info->s; + MARIA_KEYDEF *keyinfo= int_key->keyinfo; + MARIA_KEY tmp_key; + DBUG_ENTER("_ma_find_last_pos"); + + key_ref_length= share->keypage_header; + page_flag= ma_page->flag; + length= ma_page->size - key_ref_length; + page= ma_page->buff + key_ref_length; + + if (!(keyinfo->flag & + (HA_PACK_KEY | HA_SPACE_PACK_USED | HA_VAR_LENGTH_KEY | + HA_BINARY_PACK_KEY)) && !(page_flag & KEYPAGE_FLAG_HAS_TRANSID)) + { + keys= length / keyinfo->keylength - 2; + length= keyinfo->keylength; + int_key->data_length= length - info->s->rec_reflength; + int_key->ref_length= info->s->rec_reflength; + int_key->flag= 0; + end=page+keys*length; + *after_key=end+length; + memcpy(int_key->data, end, length); + DBUG_RETURN(end); + } + + end=page+length-key_ref_length; + lastpos=page; + tmp_key.data= key_buff; + tmp_key.keyinfo= int_key->keyinfo; + key_buff[0]= 0; /* Safety */ + + /* We know that there are at least 2 keys on the page */ + + if (!(length=(*keyinfo->get_key)(&tmp_key, page_flag, 0, &page))) + { + my_errno=HA_ERR_CRASHED; + DBUG_RETURN(0); + } + + do + { + prevpos=lastpos; lastpos=page; + int_key->data_length= tmp_key.data_length; + int_key->ref_length= tmp_key.ref_length; + int_key->flag= tmp_key.flag; + memcpy(int_key->data, key_buff, length); /* previous key */ + if (!(length=(*keyinfo->get_key)(&tmp_key, page_flag, 0, &page))) + { + my_errno=HA_ERR_CRASHED; + DBUG_RETURN(0); + } + } while (page < end); + + *after_key=lastpos; + DBUG_PRINT("exit",("returns: 0x%lx page: 0x%lx end: 0x%lx", + (long) prevpos,(long) page,(long) end)); + DBUG_RETURN(prevpos); +} /* _ma_find_last_pos */ + + +/** + @brief Balance page with static size keys with page on right/left + + @param key Middle key will be stored here + + @notes + Father_buff will always be changed + Caller must handle saving of curr_buff + + @return + @retval 0 Balance was done (father buff is saved) + @retval 1 Middle key up (father buff is not saved) + @retval -1 Error +*/ + +static int _ma_balance_page(MARIA_HA *info, MARIA_KEYDEF *keyinfo, + MARIA_KEY *key, MARIA_PAGE *curr_page, + MARIA_PAGE *father_page, + uchar *father_key_pos, MARIA_KEY_PARAM *s_temp) +{ + MARIA_PINNED_PAGE tmp_page_link, *new_page_link= &tmp_page_link; + MARIA_SHARE *share= info->s; + my_bool right; + uint k_length,father_length,father_keylength,nod_flag,curr_keylength; + uint right_length,left_length,new_right_length,new_left_length,extra_length; + uint keys, tmp_length, extra_buff_length; + uchar *pos, *extra_buff, *parting_key; + uchar tmp_part_key[MARIA_MAX_KEY_BUFF]; + MARIA_PAGE next_page, extra_page, *left_page, *right_page; + DBUG_ENTER("_ma_balance_page"); + + k_length= keyinfo->keylength; + father_length= father_page->size; + father_keylength= k_length + share->base.key_reflength; + nod_flag= curr_page->node; + curr_keylength= k_length+nod_flag; + info->page_changed=1; + + if ((father_key_pos != father_page->buff+father_length && + (info->state->records & 1)) || + father_key_pos == father_page->buff+ share->keypage_header + + share->base.key_reflength) + { + right=1; + next_page.pos= _ma_kpos(share->base.key_reflength, + father_key_pos+father_keylength); + left_page= curr_page; + right_page= &next_page; + DBUG_PRINT("info", ("use right page: %lu", (ulong) next_page.pos)); + } + else + { + right=0; + father_key_pos-=father_keylength; + next_page.pos= _ma_kpos(share->base.key_reflength,father_key_pos); + left_page= &next_page; + right_page= curr_page; + DBUG_PRINT("info", ("use left page: %lu", (ulong) next_page.pos)); + } /* father_key_pos ptr to parting key */ + + if (_ma_fetch_keypage(&next_page, info, keyinfo, next_page.pos, + PAGECACHE_LOCK_WRITE, + DFLT_INIT_HITS, info->buff, 0)) + goto err; + page_mark_changed(info, &next_page); + DBUG_DUMP("next", next_page.buff, next_page.size); + + /* Test if there is room to share keys */ + left_length= left_page->size; + right_length= right_page->size; + keys= ((left_length+right_length-share->keypage_header*2-nod_flag*2)/ + curr_keylength); + + if ((right ? right_length : left_length) + curr_keylength <= + (uint) keyinfo->block_length - KEYPAGE_CHECKSUM_SIZE) + { + /* Enough space to hold all keys in the two buffers ; Balance bufferts */ + new_left_length= share->keypage_header+nod_flag+(keys/2)*curr_keylength; + new_right_length=share->keypage_header+nod_flag+(((keys+1)/2)* + curr_keylength); + left_page->size= new_left_length; + page_store_size(share, left_page); + right_page->size= new_right_length; + page_store_size(share, right_page); + + DBUG_PRINT("info", ("left_length: %u -> %u right_length: %u -> %u", + left_length, new_left_length, + right_length, new_right_length)); + if (left_length < new_left_length) + { + uint length; + DBUG_PRINT("info", ("move keys to end of buff")); + + /* Move keys right_page -> left_page */ + pos= left_page->buff+left_length; + memcpy(pos,father_key_pos, (size_t) k_length); + memcpy(pos+k_length, right_page->buff + share->keypage_header, + (size_t) (length=new_left_length - left_length - k_length)); + pos= right_page->buff + share->keypage_header + length; + memcpy(father_key_pos, pos, (size_t) k_length); + bmove(right_page->buff + share->keypage_header, + pos + k_length, new_right_length); + + if (share->now_transactional) + { + if (right) + { + /* + Log changes to page on left + The original page is on the left and stored in left_page->buff + We have on the page the newly inserted key and data + from buff added last on the page + */ + if (_ma_log_split(curr_page, + left_length - s_temp->move_length, + new_left_length, + s_temp->key_pos, s_temp->changed_length, + s_temp->move_length, + KEY_OP_ADD_SUFFIX, + curr_page->buff + left_length, + new_left_length - left_length, + new_left_length - left_length+ k_length)) + goto err; + /* + Log changes to page on right + This contains the original data with some keys deleted from + start of page + */ + if (_ma_log_prefix(&next_page, 0, + ((int) new_right_length - (int) right_length))) + goto err; + } + else + { + /* + Log changes to page on right (the original page) which is in buff + Data is removed from start of page + The inserted key may be in buff or moved to curr_buff + */ + if (_ma_log_del_prefix(curr_page, + right_length - s_temp->changed_length, + new_right_length, + s_temp->key_pos, s_temp->changed_length, + s_temp->move_length)) + goto err; + /* + Log changes to page on left, which has new data added last + */ + if (_ma_log_suffix(&next_page, left_length, new_left_length)) + goto err; + } + } + } + else + { + uint length; + DBUG_PRINT("info", ("move keys to start of right_page")); + + bmove_upp(right_page->buff + new_right_length, + right_page->buff + right_length, + right_length - share->keypage_header); + length= new_right_length -right_length - k_length; + memcpy(right_page->buff + share->keypage_header + length, father_key_pos, + (size_t) k_length); + pos= left_page->buff + new_left_length; + memcpy(father_key_pos, pos, (size_t) k_length); + memcpy(right_page->buff + share->keypage_header, pos+k_length, + (size_t) length); + + if (share->now_transactional) + { + if (right) + { + /* + Log changes to page on left + The original page is on the left and stored in curr_buff + The page is shortened from end and the key may be on the page + */ + if (_ma_log_split(curr_page, + left_length - s_temp->move_length, + new_left_length, + s_temp->key_pos, s_temp->changed_length, + s_temp->move_length, + KEY_OP_NONE, (uchar*) 0, 0, 0)) + goto err; + /* + Log changes to page on right + This contains the original data, with some data from cur_buff + added first + */ + if (_ma_log_prefix(&next_page, + (uint) (new_right_length - right_length), + (int) (new_right_length - right_length))) + goto err; + } + else + { + /* + Log changes to page on right (the original page) which is in buff + We have on the page the newly inserted key and data + from buff added first on the page + */ + uint diff_length= new_right_length - right_length; + if (_ma_log_split(curr_page, + left_length - s_temp->move_length, + new_right_length, + s_temp->key_pos + diff_length, + s_temp->changed_length, + s_temp->move_length, + KEY_OP_ADD_PREFIX, + curr_page->buff + share->keypage_header, + diff_length, diff_length + k_length)) + goto err; + /* + Log changes to page on left, which is shortened from end + */ + if (_ma_log_suffix(&next_page, left_length, new_left_length)) + goto err; + } + } + } + + /* Log changes to father (one level up) page */ + + if (share->now_transactional && + _ma_log_change(father_page, father_key_pos, k_length)) + goto err; + + /* + next_page_link->changed is marked as true above and fathers + page_link->changed is marked as true in caller + */ + if (_ma_write_keypage(&next_page, PAGECACHE_LOCK_LEFT_WRITELOCKED, + DFLT_INIT_HITS) || + _ma_write_keypage(father_page, + PAGECACHE_LOCK_LEFT_WRITELOCKED, DFLT_INIT_HITS)) + goto err; + DBUG_RETURN(0); + } + + /* left_page and right_page are full, lets split and make new nod */ + + extra_buff= info->buff+share->base.max_key_block_length; + new_left_length= new_right_length= (share->keypage_header + nod_flag + + (keys+1) / 3 * curr_keylength); + extra_page.info= info; + extra_page.keyinfo= keyinfo; + extra_page.buff= extra_buff; + + /* + 5 is the minum number of keys we can have here. This comes from + the fact that each full page can store at least 2 keys and in this case + we have a 'split' key, ie 2+2+1 = 5 + */ + if (keys == 5) /* Too few keys to balance */ + new_left_length-=curr_keylength; + extra_length= (nod_flag + left_length + right_length - + new_left_length - new_right_length - curr_keylength); + extra_buff_length= extra_length + share->keypage_header; + DBUG_PRINT("info",("left_length: %d right_length: %d new_left_length: %d new_right_length: %d extra_length: %d", + left_length, right_length, + new_left_length, new_right_length, + extra_length)); + + left_page->size= new_left_length; + page_store_size(share, left_page); + right_page->size= new_right_length; + page_store_size(share, right_page); + + bzero(extra_buff, share->keypage_header); + extra_page.flag= nod_flag ? KEYPAGE_FLAG_ISNOD : 0; + extra_page.size= extra_buff_length; + page_store_info(share, &extra_page); + + /* Copy key number */ + extra_buff[share->keypage_header - KEYPAGE_USED_SIZE - KEYPAGE_KEYID_SIZE - + KEYPAGE_FLAG_SIZE]= keyinfo->key_nr; + + /* move first largest keys to new page */ + pos= right_page->buff + right_length-extra_length; + memcpy(extra_buff + share->keypage_header, pos, extra_length); + /* Zero old data from buffer */ + bzero(extra_buff + extra_buff_length, + share->block_size - extra_buff_length); + + /* Save new parting key between buff and extra_buff */ + memcpy(tmp_part_key, pos-k_length,k_length); + /* Make place for new keys */ + bmove_upp(right_page->buff + new_right_length, pos - k_length, + right_length - extra_length - k_length - share->keypage_header); + /* Copy keys from left page */ + pos= left_page->buff + new_left_length; + memcpy(right_page->buff + share->keypage_header, pos + k_length, + (size_t) (tmp_length= left_length - new_left_length - k_length)); + /* Copy old parting key */ + parting_key= right_page->buff + share->keypage_header + tmp_length; + memcpy(parting_key, father_key_pos, (size_t) k_length); + + /* Move new parting keys up to caller */ + memcpy((right ? key->data : father_key_pos),pos,(size_t) k_length); + memcpy((right ? father_key_pos : key->data),tmp_part_key, k_length); + + if ((extra_page.pos= _ma_new(info, DFLT_INIT_HITS, &new_page_link)) + == HA_OFFSET_ERROR) + goto err; + _ma_kpointer(info,key->data+k_length, extra_page.pos); + /* This is safe as long we are using not keys with transid */ + key->data_length= k_length - info->s->rec_reflength; + key->ref_length= info->s->rec_reflength; + + if (right) + { + /* + Page order according to key values: + orignal_page (curr_page = left_page), next_page (buff), extra_buff + + Move page positions so that we store data in extra_page where + next_page was and next_page will be stored at the new position + */ + swap_variables(my_off_t, extra_page.pos, next_page.pos); + } + + if (share->now_transactional) + { + if (right) + { + /* + left_page is shortened, + right_page is getting new keys at start and shortened from end. + extra_page is new page + + Note that extra_page (largest key parts) will be stored at the + place of the original 'right' page (next_page) and right page + will be stored at the new page position + + This makes the log entries smaller as right_page contains all + data to generate the data extra_buff + */ + + /* + Log changes to page on left (page shortened page at end) + */ + if (_ma_log_split(curr_page, + left_length - s_temp->move_length, new_left_length, + s_temp->key_pos, s_temp->changed_length, + s_temp->move_length, + KEY_OP_NONE, (uchar*) 0, 0, 0)) + goto err; + /* + Log changes to right page (stored at next page) + This contains the last 'extra_buff' from 'buff' + */ + if (_ma_log_prefix(&extra_page, + 0, (int) (extra_buff_length - right_length))) + goto err; + + /* + Log changes to middle page, which is stored at the new page + position + */ + if (_ma_log_new(&next_page, 0)) + goto err; + } + else + { + /* + Log changes to page on right (the original page) which is in buff + This contains the original data, with some data from curr_buff + added first and shortened at end + */ + int data_added_first= left_length - new_left_length; + if (_ma_log_key_middle(right_page, + new_right_length, + data_added_first, + data_added_first, + extra_length, + s_temp->key_pos, + s_temp->changed_length, + s_temp->move_length)) + goto err; + + /* Log changes to page on left, which is shortened from end */ + if (_ma_log_suffix(left_page, left_length, new_left_length)) + goto err; + + /* Log change to rightmost (new) page */ + if (_ma_log_new(&extra_page, 0)) + goto err; + } + + /* Log changes to father (one level up) page */ + if (share->now_transactional && + _ma_log_change(father_page, father_key_pos, k_length)) + goto err; + } + + if (_ma_write_keypage(&next_page, + (right ? new_page_link->write_lock : + PAGECACHE_LOCK_LEFT_WRITELOCKED), + DFLT_INIT_HITS) || + _ma_write_keypage(&extra_page, + (!right ? new_page_link->write_lock : + PAGECACHE_LOCK_LEFT_WRITELOCKED), + DFLT_INIT_HITS)) + goto err; + + DBUG_RETURN(1); /* Middle key up */ + +err: + DBUG_RETURN(-1); +} /* _ma_balance_page */ + + +/********************************************************************** + * Bulk insert code * + **********************************************************************/ + +typedef struct { + MARIA_HA *info; + uint keynr; +} bulk_insert_param; + + +static my_bool _ma_ck_write_tree(register MARIA_HA *info, MARIA_KEY *key) +{ + my_bool error; + uint keynr= key->keyinfo->key_nr; + DBUG_ENTER("_ma_ck_write_tree"); + + /* Store ref_length as this is always constant */ + info->bulk_insert_ref_length= key->ref_length; + error= tree_insert(&info->bulk_insert[keynr], key->data, + key->data_length + key->ref_length, + info->bulk_insert[keynr].custom_arg) == 0; + DBUG_RETURN(error); +} /* _ma_ck_write_tree */ + + +/* typeof(_ma_keys_compare)=qsort_cmp2 */ + +static int keys_compare(bulk_insert_param *param, uchar *key1, uchar *key2) +{ + uint not_used[2]; + return ha_key_cmp(param->info->s->keyinfo[param->keynr].seg, + key1, key2, USE_WHOLE_KEY, SEARCH_SAME, + not_used); +} + + +static int keys_free(uchar *key, TREE_FREE mode, bulk_insert_param *param) +{ + /* + Probably I can use info->lastkey here, but I'm not sure, + and to be safe I'd better use local lastkey. + */ + MARIA_SHARE *share= param->info->s; + uchar lastkey[MARIA_MAX_KEY_BUFF]; + uint keylen; + MARIA_KEYDEF *keyinfo= share->keyinfo + param->keynr; + MARIA_KEY tmp_key; + + switch (mode) { + case free_init: + if (share->lock_key_trees) + { + rw_wrlock(&keyinfo->root_lock); + keyinfo->version++; + } + return 0; + case free_free: + /* Note: keylen doesn't contain transid lengths */ + keylen= _ma_keylength(keyinfo, key); + tmp_key.data= lastkey; + tmp_key.keyinfo= keyinfo; + tmp_key.data_length= keylen - share->rec_reflength; + tmp_key.ref_length= param->info->bulk_insert_ref_length; + tmp_key.flag= (param->info->bulk_insert_ref_length == + share->rec_reflength ? 0 : SEARCH_USER_KEY_HAS_TRANSID); + /* + We have to copy key as ma_ck_write_btree may need the buffer for + copying middle key up if tree is growing + */ + memcpy(lastkey, key, tmp_key.data_length + tmp_key.ref_length); + return _ma_ck_write_btree(param->info, &tmp_key); + case free_end: + if (share->lock_key_trees) + rw_unlock(&keyinfo->root_lock); + return 0; + } + return 1; +} + + +int maria_init_bulk_insert(MARIA_HA *info, ulong cache_size, ha_rows rows) +{ + MARIA_SHARE *share= info->s; + MARIA_KEYDEF *key=share->keyinfo; + bulk_insert_param *params; + uint i, num_keys, total_keylength; + ulonglong key_map; + DBUG_ENTER("_ma_init_bulk_insert"); + DBUG_PRINT("enter",("cache_size: %lu", cache_size)); + + DBUG_ASSERT(!info->bulk_insert && + (!rows || rows >= MARIA_MIN_ROWS_TO_USE_BULK_INSERT)); + + maria_clear_all_keys_active(key_map); + for (i=total_keylength=num_keys=0 ; i < share->base.keys ; i++) + { + if (! (key[i].flag & HA_NOSAME) && (share->base.auto_key != i + 1) && + maria_is_key_active(share->state.key_map, i)) + { + num_keys++; + maria_set_key_active(key_map, i); + total_keylength+=key[i].maxlength+TREE_ELEMENT_EXTRA_SIZE; + } + } + + if (num_keys==0 || + num_keys * MARIA_MIN_SIZE_BULK_INSERT_TREE > cache_size) + DBUG_RETURN(0); + + if (rows && rows*total_keylength < cache_size) + cache_size= (ulong)rows; + else + cache_size/=total_keylength*16; + + info->bulk_insert=(TREE *) + my_malloc((sizeof(TREE)*share->base.keys+ + sizeof(bulk_insert_param)*num_keys),MYF(0)); + + if (!info->bulk_insert) + DBUG_RETURN(HA_ERR_OUT_OF_MEM); + + params=(bulk_insert_param *)(info->bulk_insert+share->base.keys); + for (i=0 ; i < share->base.keys ; i++) + { + if (maria_is_key_active(key_map, i)) + { + params->info=info; + params->keynr=i; + /* Only allocate a 16'th of the buffer at a time */ + init_tree(&info->bulk_insert[i], + cache_size * key[i].maxlength, + cache_size * key[i].maxlength, 0, + (qsort_cmp2)keys_compare, 0, + (tree_element_free) keys_free, (void *)params++); + } + else + info->bulk_insert[i].root=0; + } + + DBUG_RETURN(0); +} + +void maria_flush_bulk_insert(MARIA_HA *info, uint inx) +{ + if (info->bulk_insert) + { + if (is_tree_inited(&info->bulk_insert[inx])) + reset_tree(&info->bulk_insert[inx]); + } +} + +void maria_end_bulk_insert(MARIA_HA *info, my_bool abort) +{ + DBUG_ENTER("maria_end_bulk_insert"); + if (info->bulk_insert) + { + uint i; + for (i=0 ; i < info->s->base.keys ; i++) + { + if (is_tree_inited(&info->bulk_insert[i])) + { + if (abort) + reset_free_element(&info->bulk_insert[i]); + delete_tree(&info->bulk_insert[i]); + } + } + my_free(info->bulk_insert, MYF(0)); + info->bulk_insert= 0; + } + DBUG_VOID_RETURN; +} + + +/**************************************************************************** + Dedicated functions that generate log entries +****************************************************************************/ + + +int _ma_write_undo_key_insert(MARIA_HA *info, const MARIA_KEY *key, + my_off_t *root, my_off_t new_root, LSN *res_lsn) +{ + MARIA_SHARE *share= info->s; + MARIA_KEYDEF *keyinfo= key->keyinfo; + uchar log_data[LSN_STORE_SIZE + FILEID_STORE_SIZE + + KEY_NR_STORE_SIZE]; + const uchar *key_value; + LEX_CUSTRING log_array[TRANSLOG_INTERNAL_PARTS + 2]; + struct st_msg_to_write_hook_for_undo_key msg; + uint key_length; + + /* Save if we need to write a clr record */ + lsn_store(log_data, info->trn->undo_lsn); + key_nr_store(log_data + LSN_STORE_SIZE + FILEID_STORE_SIZE, + keyinfo->key_nr); + key_length= key->data_length + key->ref_length; + log_array[TRANSLOG_INTERNAL_PARTS + 0].str= log_data; + log_array[TRANSLOG_INTERNAL_PARTS + 0].length= sizeof(log_data); + log_array[TRANSLOG_INTERNAL_PARTS + 1].str= key->data; + log_array[TRANSLOG_INTERNAL_PARTS + 1].length= key_length; + + msg.root= root; + msg.value= new_root; + msg.auto_increment= 0; + key_value= key->data; + if (share->base.auto_key == ((uint) keyinfo->key_nr + 1)) + { + const HA_KEYSEG *keyseg= keyinfo->seg; + uchar reversed[MARIA_MAX_KEY_BUFF]; + if (keyseg->flag & HA_SWAP_KEY) + { + /* We put key from log record to "data record" packing format... */ + const uchar *key_ptr= key->data, *key_end= key->data + keyseg->length; + uchar *to= reversed + keyseg->length; + do + { + *--to= *key_ptr++; + } while (key_ptr != key_end); + key_value= to; + } + /* ... so that we can read it with: */ + msg.auto_increment= + ma_retrieve_auto_increment(key_value, keyseg->type); + /* and write_hook_for_undo_key_insert() will pick this. */ + } + + return translog_write_record(res_lsn, LOGREC_UNDO_KEY_INSERT, + info->trn, info, + (translog_size_t) + log_array[TRANSLOG_INTERNAL_PARTS + 0].length + + key_length, + TRANSLOG_INTERNAL_PARTS + 2, log_array, + log_data + LSN_STORE_SIZE, &msg) ? -1 : 0; +} + + +/** + @brief Log creation of new page + + @note + We don't have to store the page_length into the log entry as we can + calculate this from the length of the log entry + + @retval 1 error + @retval 0 ok +*/ + +my_bool _ma_log_new(MARIA_PAGE *ma_page, my_bool root_page) +{ + LSN lsn; + uchar log_data[FILEID_STORE_SIZE + PAGE_STORE_SIZE * 2 + KEY_NR_STORE_SIZE + +1]; + uint page_length; + LEX_CUSTRING log_array[TRANSLOG_INTERNAL_PARTS + 2]; + MARIA_HA *info= ma_page->info; + MARIA_SHARE *share= info->s; + my_off_t page; + DBUG_ENTER("_ma_log_new"); + DBUG_PRINT("enter", ("page: %lu", (ulong) ma_page->pos)); + + DBUG_ASSERT(share->now_transactional); + + /* Store address of new root page */ + page= ma_page->pos / share->block_size; + page_store(log_data + FILEID_STORE_SIZE, page); + + /* Store link to next unused page */ + if (info->key_del_used == 2) + page= 0; /* key_del not changed */ + else + page= ((share->key_del_current == HA_OFFSET_ERROR) ? IMPOSSIBLE_PAGE_NO : + share->key_del_current / share->block_size); + + page_store(log_data + FILEID_STORE_SIZE + PAGE_STORE_SIZE, page); + key_nr_store(log_data + FILEID_STORE_SIZE + PAGE_STORE_SIZE*2, + ma_page->keyinfo->key_nr); + log_data[FILEID_STORE_SIZE + PAGE_STORE_SIZE*2 + KEY_NR_STORE_SIZE]= + (uchar) root_page; + + log_array[TRANSLOG_INTERNAL_PARTS + 0].str= log_data; + log_array[TRANSLOG_INTERNAL_PARTS + 0].length= sizeof(log_data); + + page_length= ma_page->size - LSN_STORE_SIZE; + log_array[TRANSLOG_INTERNAL_PARTS + 1].str= ma_page->buff + LSN_STORE_SIZE; + log_array[TRANSLOG_INTERNAL_PARTS + 1].length= page_length; + + if (translog_write_record(&lsn, LOGREC_REDO_INDEX_NEW_PAGE, + info->trn, info, + (translog_size_t) + (sizeof(log_data) + page_length), + TRANSLOG_INTERNAL_PARTS + 2, log_array, + log_data, NULL)) + DBUG_RETURN(1); + DBUG_RETURN(0); +} + + +/** + @brief + Log when some part of the key page changes +*/ + +my_bool _ma_log_change(MARIA_PAGE *ma_page, + const uchar *key_pos, uint length) +{ + LSN lsn; + uchar log_data[FILEID_STORE_SIZE + PAGE_STORE_SIZE + 6 + 7], *log_pos; + LEX_CUSTRING log_array[TRANSLOG_INTERNAL_PARTS + 3]; + uint offset= (uint) (key_pos - ma_page->buff), translog_parts; + uint extra_length= 0; + my_off_t page; + MARIA_HA *info= ma_page->info; + DBUG_ENTER("_ma_log_change"); + DBUG_PRINT("enter", ("page: %lu length: %u", (ulong) ma_page->pos, length)); + + DBUG_ASSERT(info->s->now_transactional); + + /* Store address of new root page */ + page= ma_page->pos / info->s->block_size; + page_store(log_data + FILEID_STORE_SIZE, page); + log_pos= log_data+ FILEID_STORE_SIZE + PAGE_STORE_SIZE; + log_pos[0]= KEY_OP_OFFSET; + int2store(log_pos+1, offset); + log_pos[3]= KEY_OP_CHANGE; + int2store(log_pos+4, length); + + log_array[TRANSLOG_INTERNAL_PARTS + 0].str= log_data; + log_array[TRANSLOG_INTERNAL_PARTS + 0].length= sizeof(log_data) - 7; + log_array[TRANSLOG_INTERNAL_PARTS + 1].str= key_pos; + log_array[TRANSLOG_INTERNAL_PARTS + 1].length= length; + translog_parts= 2; + +#ifdef EXTRA_DEBUG_KEY_CHANGES + { + int page_length= ma_page->size; + ha_checksum crc; + crc= my_checksum(0, ma_page->buff + LSN_STORE_SIZE, + page_length - LSN_STORE_SIZE); + log_pos+= 6; + log_pos[0]= KEY_OP_CHECK; + int2store(log_pos+1, page_length); + int4store(log_pos+3, crc); + log_array[TRANSLOG_INTERNAL_PARTS + translog_parts].str= log_pos; + log_array[TRANSLOG_INTERNAL_PARTS + translog_parts].length= 7; + extra_length+= 7; + translog_parts++; + } +#endif + + if (translog_write_record(&lsn, LOGREC_REDO_INDEX, + info->trn, info, + (translog_size_t) (sizeof(log_data) - 7 + length + + extra_length), + TRANSLOG_INTERNAL_PARTS + translog_parts, + log_array, log_data, NULL)) + DBUG_RETURN(1); + DBUG_RETURN(0); +} + + +/** + @brief Write log entry for page splitting + + @note + Write log entry for page that has got a key added to the page under + one and only one of the following senarios: + - Page is shortened from end + - Data is added to end of page + - Data added at front of page + + @param prefix_or_suffix KEY_OP_NONE Ignored + KEY_OP_ADD_PREFIX Add data to start of page + KEY_OP_ADD_SUFFIX Add data to end of page + +*/ + +static my_bool _ma_log_split(MARIA_PAGE *ma_page, + uint org_length, uint new_length, + const uchar *key_pos, uint key_length, + int move_length, enum en_key_op prefix_or_suffix, + const uchar *data, uint data_length, + uint changed_length) +{ + LSN lsn; + uchar log_data[FILEID_STORE_SIZE + PAGE_STORE_SIZE + 3+3+3+3+3+2]; + uchar *log_pos; + LEX_CUSTRING log_array[TRANSLOG_INTERNAL_PARTS + 3]; + uint offset= (uint) (key_pos - ma_page->buff); + uint translog_parts, extra_length; + MARIA_HA *info= ma_page->info; + my_off_t page; + DBUG_ENTER("_ma_log_split"); + DBUG_PRINT("enter", ("page: %lu org_length: %u new_length: %u", + (ulong) ma_page->pos, org_length, new_length)); + + log_pos= log_data + FILEID_STORE_SIZE; + page= ma_page->pos / info->s->block_size; + page_store(log_pos, page); + log_pos+= PAGE_STORE_SIZE; + + if (new_length <= offset || !key_pos) + { + /* + Page was split before inserted key. Write redo entry where + we just cut current page at page_length + */ + uint length_offset= org_length - new_length; + log_pos[0]= KEY_OP_DEL_SUFFIX; + int2store(log_pos+1, length_offset); + log_pos+= 3; + translog_parts= 1; + extra_length= 0; + } + else + { + /* Key was added to page which was split after the inserted key */ + uint max_key_length; + + /* + Handle case when split happened directly after the newly inserted key. + */ + max_key_length= new_length - offset; + extra_length= min(key_length, max_key_length); + + if ((int) new_length < (int) (org_length + move_length + data_length)) + { + /* Shorten page */ + uint diff= org_length + move_length + data_length - new_length; + log_pos[0]= KEY_OP_DEL_SUFFIX; + int2store(log_pos + 1, diff); + log_pos+= 3; + } + else + { + DBUG_ASSERT(new_length == org_length + move_length + data_length); + } + + log_pos[0]= KEY_OP_OFFSET; + int2store(log_pos+1, offset); + log_pos+= 3; + + if (move_length) + { + log_pos[0]= KEY_OP_SHIFT; + int2store(log_pos+1, move_length); + log_pos+= 3; + } + + log_pos[0]= KEY_OP_CHANGE; + int2store(log_pos+1, extra_length); + log_pos+= 3; + + /* Point to original inserted key data */ + if (prefix_or_suffix == KEY_OP_ADD_PREFIX) + key_pos+= data_length; + + translog_parts= 2; + log_array[TRANSLOG_INTERNAL_PARTS + 1].str= key_pos; + log_array[TRANSLOG_INTERNAL_PARTS + 1].length= extra_length; + } + + if (data_length) + { + /* Add prefix or suffix */ + log_pos[0]= prefix_or_suffix; + int2store(log_pos+1, data_length); + log_pos+= 3; + if (prefix_or_suffix == KEY_OP_ADD_PREFIX) + { + int2store(log_pos+1, changed_length); + log_pos+= 2; + data_length= changed_length; + } + log_array[TRANSLOG_INTERNAL_PARTS + translog_parts].str= data; + log_array[TRANSLOG_INTERNAL_PARTS + translog_parts].length= data_length; + translog_parts++; + extra_length+= data_length; + } + + log_array[TRANSLOG_INTERNAL_PARTS + 0].str= log_data; + log_array[TRANSLOG_INTERNAL_PARTS + 0].length= (uint) (log_pos - + log_data); + DBUG_RETURN(translog_write_record(&lsn, LOGREC_REDO_INDEX, + info->trn, info, + (translog_size_t) + log_array[TRANSLOG_INTERNAL_PARTS + + 0].length + extra_length, + TRANSLOG_INTERNAL_PARTS + translog_parts, + log_array, log_data, NULL)); +} + + +/** + @brief + Write log entry for page that has got a key added to the page + and page is shortened from start of page + + @fn _ma_log_del_prefix() + @param info Maria handler + @param page Page number + @param buff Page buffer + @param org_length Length of buffer when read + @param new_length Final length + @param key_pos Where on page buffer key was added. This is position + before prefix was removed + @param key_length How many bytes was changed at 'key_pos' + @param move_length How many bytes was moved up when key was added + + @return + @retval 0 ok + @retval 1 error +*/ + +static my_bool _ma_log_del_prefix(MARIA_PAGE *ma_page, + uint org_length, uint new_length, + const uchar *key_pos, uint key_length, + int move_length) +{ + LSN lsn; + uchar log_data[FILEID_STORE_SIZE + PAGE_STORE_SIZE + 12], *log_pos; + LEX_CUSTRING log_array[TRANSLOG_INTERNAL_PARTS + 2]; + uint offset= (uint) (key_pos - ma_page->buff); + uint diff_length= org_length + move_length - new_length; + uint translog_parts, extra_length; + MARIA_HA *info= ma_page->info; + my_off_t page; + DBUG_ENTER("_ma_log_del_prefix"); + DBUG_PRINT("enter", ("page: %lu org_length: %u new_length: %u", + (ulong) ma_page->pos, org_length, new_length)); + + DBUG_ASSERT((int) diff_length > 0); + + log_pos= log_data + FILEID_STORE_SIZE; + page= ma_page->pos / info->s->block_size; + page_store(log_pos, page); + log_pos+= PAGE_STORE_SIZE; + + translog_parts= 1; + extra_length= 0; + + if (offset < diff_length + info->s->keypage_header) + { + /* + Key is not anymore on page. Move data down, but take into account that + the original page had grown with 'move_length bytes' + */ + DBUG_ASSERT(offset + key_length <= diff_length + info->s->keypage_header); + + log_pos[0]= KEY_OP_DEL_PREFIX; + int2store(log_pos+1, diff_length - move_length); + log_pos+= 3; + } + else + { + /* + Correct position to key, as data before key has been delete and key + has thus been moved down + */ + offset-= diff_length; + key_pos-= diff_length; + + /* Move data down */ + log_pos[0]= KEY_OP_DEL_PREFIX; + int2store(log_pos+1, diff_length); + log_pos+= 3; + + log_pos[0]= KEY_OP_OFFSET; + int2store(log_pos+1, offset); + log_pos+= 3; + + if (move_length) + { + log_pos[0]= KEY_OP_SHIFT; + int2store(log_pos+1, move_length); + log_pos+= 3; + } + log_pos[0]= KEY_OP_CHANGE; + int2store(log_pos+1, key_length); + log_pos+= 3; + log_array[TRANSLOG_INTERNAL_PARTS + 1].str= key_pos; + log_array[TRANSLOG_INTERNAL_PARTS + 1].length= key_length; + translog_parts= 2; + extra_length= key_length; + } + log_array[TRANSLOG_INTERNAL_PARTS + 0].str= log_data; + log_array[TRANSLOG_INTERNAL_PARTS + 0].length= (uint) (log_pos - + log_data); + DBUG_RETURN(translog_write_record(&lsn, LOGREC_REDO_INDEX, + info->trn, info, + (translog_size_t) + log_array[TRANSLOG_INTERNAL_PARTS + + 0].length + extra_length, + TRANSLOG_INTERNAL_PARTS + translog_parts, + log_array, log_data, NULL)); +} + + +/** + @brief + Write log entry for page that has got data added first and + data deleted last. Old changed key may be part of page +*/ + +static my_bool _ma_log_key_middle(MARIA_PAGE *ma_page, + uint new_length, + uint data_added_first, + uint data_changed_first, + uint data_deleted_last, + const uchar *key_pos, + uint key_length, int move_length) +{ + LSN lsn; + uchar log_data[FILEID_STORE_SIZE + PAGE_STORE_SIZE + 3+5+3+3+3]; + uchar *log_pos; + LEX_CUSTRING log_array[TRANSLOG_INTERNAL_PARTS + 4]; + uint key_offset; + uint translog_parts, extra_length; + my_off_t page; + MARIA_HA *info= ma_page->info; + DBUG_ENTER("_ma_log_key_middle"); + DBUG_PRINT("enter", ("page: %lu", (ulong) ma_page->pos)); + + /* new place of key after changes */ + key_pos+= data_added_first; + key_offset= (uint) (key_pos - ma_page->buff); + if (key_offset < new_length) + { + /* key is on page; Calculate how much of the key is there */ + uint max_key_length= new_length - key_offset; + if (max_key_length < key_length) + { + /* Key is last on page */ + key_length= max_key_length; + move_length= 0; + } + /* + Take into account that new data was added as part of original key + that also needs to be removed from page + */ + data_deleted_last+= move_length; + } + + page= ma_page->pos / info->s->block_size; + + /* First log changes to page */ + log_pos= log_data + FILEID_STORE_SIZE; + page_store(log_pos, page); + log_pos+= PAGE_STORE_SIZE; + + log_pos[0]= KEY_OP_DEL_SUFFIX; + int2store(log_pos+1, data_deleted_last); + log_pos+= 3; + + log_pos[0]= KEY_OP_ADD_PREFIX; + int2store(log_pos+1, data_added_first); + int2store(log_pos+3, data_changed_first); + log_pos+= 5; + + log_array[TRANSLOG_INTERNAL_PARTS + 0].str= log_data; + log_array[TRANSLOG_INTERNAL_PARTS + 0].length= (uint) (log_pos - + log_data); + log_array[TRANSLOG_INTERNAL_PARTS + 1].str= (ma_page->buff + + info->s->keypage_header); + log_array[TRANSLOG_INTERNAL_PARTS + 1].length= data_changed_first; + translog_parts= 2; + extra_length= data_changed_first; + + /* If changed key is on page, log those changes too */ + + if (key_offset < new_length) + { + uchar *start_log_pos= log_pos; + + log_pos[0]= KEY_OP_OFFSET; + int2store(log_pos+1, key_offset); + log_pos+= 3; + if (move_length) + { + log_pos[0]= KEY_OP_SHIFT; + int2store(log_pos+1, move_length); + log_pos+= 3; + } + log_pos[0]= KEY_OP_CHANGE; + int2store(log_pos+1, key_length); + log_pos+= 3; + + log_array[TRANSLOG_INTERNAL_PARTS + 2].str= start_log_pos; + log_array[TRANSLOG_INTERNAL_PARTS + 2].length= (uint) (log_pos - + start_log_pos); + + log_array[TRANSLOG_INTERNAL_PARTS + 3].str= key_pos; + log_array[TRANSLOG_INTERNAL_PARTS + 3].length= key_length; + translog_parts+=2; + extra_length+= (uint) (log_array[TRANSLOG_INTERNAL_PARTS + 2].length + + key_length); + } + + DBUG_RETURN(translog_write_record(&lsn, LOGREC_REDO_INDEX, + info->trn, info, + (translog_size_t) + (log_array[TRANSLOG_INTERNAL_PARTS + + 0].length + extra_length), + TRANSLOG_INTERNAL_PARTS + translog_parts, + log_array, log_data, NULL)); +} + + +#ifdef NOT_NEEDED + +/** + @brief + Write log entry for page that has got data added first and + data deleted last +*/ + +static my_bool _ma_log_middle(MARIA_PAGE *ma_page, + uint data_added_first, uint data_changed_first, + uint data_deleted_last) +{ + LSN lsn; + LEX_STRING log_array[TRANSLOG_INTERNAL_PARTS + 2]; + uchar log_data[FILEID_STORE_SIZE + PAGE_STORE_SIZE + 3 + 5], *log_pos; + MARIA_HA *info= ma_page->info; + my_off_t page; + DBUG_ENTER("_ma_log_middle"); + DBUG_PRINT("enter", ("page: %lu", (ulong) page)); + + page= ma_page->page / info->s->block_size; + + log_pos= log_data + FILEID_STORE_SIZE; + page_store(log_pos, page); + log_pos+= PAGE_STORE_SIZE; + + log_pos[0]= KEY_OP_DEL_PREFIX; + int2store(log_pos+1, data_deleted_last); + log_pos+= 3; + + log_pos[0]= KEY_OP_ADD_PREFIX; + int2store(log_pos+1, data_added_first); + int2store(log_pos+3, data_changed_first); + log_pos+= 5; + + log_array[TRANSLOG_INTERNAL_PARTS + 0].str= log_data; + log_array[TRANSLOG_INTERNAL_PARTS + 0].length= (uint) (log_pos - + log_data); + + log_array[TRANSLOG_INTERNAL_PARTS + 1].str= ((char*) buff + + info->s->keypage_header); + log_array[TRANSLOG_INTERNAL_PARTS + 1].length= data_changed_first; + DBUG_RETURN(translog_write_record(&lsn, LOGREC_REDO_INDEX, + info->trn, info, + (translog_size_t) + log_array[TRANSLOG_INTERNAL_PARTS + + 0].length + data_changed_first, + TRANSLOG_INTERNAL_PARTS + 2, + log_array, log_data, NULL)); +} +#endif diff --git a/storage/maria/maria_chk.c b/storage/maria/maria_chk.c new file mode 100644 index 00000000000..ab3c641b55f --- /dev/null +++ b/storage/maria/maria_chk.c @@ -0,0 +1,1968 @@ +/* Copyright (C) 2006-2003 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 */ + +/* Describe, check and repair of MARIA tables */ + +#include "ma_fulltext.h" +#include <myisamchk.h> +#include <my_bit.h> +#include <m_ctype.h> +#include <stdarg.h> +#include <my_getopt.h> +#ifdef HAVE_SYS_VADVICE_H +#include <sys/vadvise.h> +#endif +#ifdef HAVE_SYS_MMAN_H +#include <sys/mman.h> +#endif +SET_STACK_SIZE(9000) /* Minimum stack size for program */ + +#ifndef USE_RAID +#define my_raid_create(A,B,C,D,E,F,G) my_create(A,B,C,G) +#define my_raid_delete(A,B,C) my_delete(A,B) +#endif + +static uint decode_bits; +static char **default_argv; +static const char *load_default_groups[]= { "maria_chk", 0 }; +static const char *set_collation_name, *opt_tmpdir, *opt_log_dir; +static CHARSET_INFO *set_collation; +static int stopwords_inited= 0; +static MY_TMPDIR maria_chk_tmpdir; +static my_bool opt_transaction_logging, opt_debug, opt_require_control_file; + +static const char *type_names[]= +{ + "impossible","char","binary", "short", "long", "float", + "double","number","unsigned short", + "unsigned long","longlong","ulonglong","int24", + "uint24","int8","varchar", "varbin", "varchar2", "varbin2", "bit", + "?","?" +}; + +static const char *prefix_packed_txt="packed ", + *bin_packed_txt="prefix ", + *diff_txt="stripped ", + *null_txt="NULL", + *blob_txt="BLOB "; + +static const char *field_pack[]= +{ + "","no endspace", "no prespace", + "no zeros", "blob", "constant", "table-lockup", + "always zero","varchar","unique-hash","?","?" +}; + +static const char *record_formats[]= +{ + "Fixed length", "Packed", "Compressed", "Block", "?" +}; + +static const char *maria_stats_method_str="nulls_unequal"; +static char default_open_errmsg[]= "%d when opening MARIA-table '%s'"; +static char default_close_errmsg[]= "%d when closing MARIA-table '%s'"; + +static void get_options(int *argc,char * * *argv); +static void print_version(void); +static void usage(void); +static int maria_chk(HA_CHECK *param, char *filename); +static void descript(HA_CHECK *param, register MARIA_HA *info, char *name); +static int maria_sort_records(HA_CHECK *param, register MARIA_HA *info, + char *name, uint sort_key, + my_bool write_info, my_bool update_index); +static int sort_record_index(MARIA_SORT_PARAM *sort_param, MARIA_PAGE *page, + uint sortkey, File new_file, + my_bool update_index); +static my_bool write_log_record(HA_CHECK *param); + +HA_CHECK check_param; + + /* Main program */ + +int main(int argc, char **argv) +{ + int error; + MY_INIT(argv[0]); + + opt_log_dir= maria_data_root= (char *)"."; + maria_chk_init(&check_param); + check_param.opt_lock_memory= 1; /* Lock memory if possible */ + check_param.using_global_keycache = 0; + get_options(&argc,(char***) &argv); + maria_quick_table_bits=decode_bits; + error=0; + maria_init(); + + if (ma_control_file_open(FALSE, opt_require_control_file) && + (opt_require_control_file || + (opt_transaction_logging && (check_param.testflag & T_REP_ANY)))) + { + error= 1; + goto end; + } + + /* + If we are doing a repair, user may want to store this repair into the log + so that the log has a complete history and can be used to replay. + */ + if (opt_transaction_logging && (check_param.testflag & T_REP_ANY)) + { + if (init_pagecache(maria_log_pagecache, + TRANSLOG_PAGECACHE_SIZE, 0, 0, + TRANSLOG_PAGE_SIZE, MY_WME) == 0 || + translog_init(opt_log_dir, TRANSLOG_FILE_SIZE, + 0, 0, maria_log_pagecache, + TRANSLOG_DEFAULT_FLAGS, 0)) + { + _ma_check_print_error(&check_param, + "Can't initialize transaction logging. Run " + "recovery with switch --skip-transaction-log"); + error= 1; + goto end; + } + } + + while (--argc >= 0) + { + int new_error=maria_chk(&check_param, *(argv++)); + if ((check_param.testflag & T_REP_ANY) != T_REP) + check_param.testflag&= ~T_REP; + VOID(fflush(stdout)); + VOID(fflush(stderr)); + if ((check_param.error_printed | check_param.warning_printed) && + (check_param.testflag & T_FORCE_CREATE) && + (!(check_param.testflag & (T_REP | T_REP_BY_SORT | T_SORT_RECORDS | + T_SORT_INDEX)))) + { + ulonglong old_testflag=check_param.testflag; + if (!(check_param.testflag & T_REP)) + check_param.testflag|= T_REP_BY_SORT; + check_param.testflag&= ~T_EXTEND; /* Not needed */ + error|=maria_chk(&check_param, argv[-1]); + check_param.testflag= old_testflag; + VOID(fflush(stdout)); + VOID(fflush(stderr)); + } + else + error|=new_error; + if (argc && (!(check_param.testflag & T_SILENT) || + check_param.testflag & T_INFO)) + { + puts("\n---------\n"); + VOID(fflush(stdout)); + } + } +end: + if (check_param.total_files > 1) + { /* Only if descript */ + char buff[22],buff2[22]; + if (!(check_param.testflag & T_SILENT) || check_param.testflag & T_INFO) + puts("\n---------"); + printf("\nTotal of all %d MARIA-files:\nData records: %9s Deleted blocks: %9s\n",check_param.total_files,llstr(check_param.total_records,buff), + llstr(check_param.total_deleted,buff2)); + } + free_defaults(default_argv); + free_tmpdir(&maria_chk_tmpdir); + maria_end(); + my_end(check_param.testflag & T_INFO ? + MY_CHECK_ERROR | MY_GIVE_INFO : MY_CHECK_ERROR); + exit(error); +#ifndef _lint + return 0; /* No compiler warning */ +#endif +} /* main */ + +enum options_mc { + OPT_CHARSETS_DIR=256, OPT_SET_COLLATION,OPT_START_CHECK_POS, + OPT_CORRECT_CHECKSUM, OPT_PAGE_BUFFER_SIZE, + OPT_KEY_CACHE_BLOCK_SIZE, OPT_MARIA_BLOCK_SIZE, + OPT_READ_BUFFER_SIZE, OPT_WRITE_BUFFER_SIZE, OPT_SORT_BUFFER_SIZE, + OPT_SORT_KEY_BLOCKS, OPT_DECODE_BITS, OPT_FT_MIN_WORD_LEN, + OPT_FT_MAX_WORD_LEN, OPT_FT_STOPWORD_FILE, + OPT_MAX_RECORD_LENGTH, OPT_AUTO_CLOSE, OPT_STATS_METHOD, OPT_TRANSACTION_LOG, + OPT_SKIP_SAFEMALLOC, OPT_ZEROFILL_KEEP_LSN, OPT_REQUIRE_CONTROL_FILE, + OPT_LOG_DIR, OPT_DATADIR +}; + +static struct my_option my_long_options[] = +{ + {"analyze", 'a', + "Analyze distribution of keys. Will make some joins in MySQL faster. You can check the calculated distribution.", + 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0}, +#ifdef __NETWARE__ + {"autoclose", OPT_AUTO_CLOSE, "Auto close the screen on exit for Netware.", + 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0}, +#endif + {"block-search", 'b', + "No help available.", + 0, 0, 0, GET_ULONG, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + {"backup", 'B', + "Make a backup of the .MAD file as 'filename-time.BAK'.", + 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"character-sets-dir", OPT_CHARSETS_DIR, + "Directory where character sets are.", + (uchar**) &charsets_dir, 0, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + {"check", 'c', + "Check table for errors.", + 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"check-only-changed", 'C', + "Check only tables that have changed since last check. It also applies to other requested actions (e.g. --analyze will be ignored if the table is already analyzed).", + 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"correct-checksum", OPT_CORRECT_CHECKSUM, + "Correct checksum information for table.", + 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0}, +#ifndef DBUG_OFF + {"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 + {"description", 'd', + "Prints some information about table.", + 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"data-file-length", 'D', + "Max length of data file (when recreating data-file when it's full).", + (uchar**) &check_param.max_data_file_length, + (uchar**) &check_param.max_data_file_length, + 0, GET_LL, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + {"extend-check", 'e', + "If used when checking a table, ensure that the table is 100 percent consistent, which will take a long time. If used when repairing a table, try to recover every possible row from the data file. Normally this will also find a lot of garbage rows; Don't use this option with repair if you are not totally desperate.", + 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"fast", 'F', + "Check only tables that haven't been closed properly. It also applies to other requested actions (e.g. --analyze will be ignored if the table is already analyzed).", + 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"force", 'f', + "Restart with -r if there are any errors in the table. States will be updated as with --update-state.", + 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"HELP", 'H', + "Display this help and exit.", + 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"help", '?', + "Display this help and exit.", + 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"information", 'i', + "Print statistics information about table that is checked.", + 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"keys-used", 'k', + "Tell MARIA to update only some specific keys. # is a bit mask of which keys to use. This can be used to get faster inserts.", + (uchar**) &check_param.keys_in_use, + (uchar**) &check_param.keys_in_use, + 0, GET_ULL, REQUIRED_ARG, -1, 0, 0, 0, 0, 0}, + {"datadir", OPT_DATADIR, + "Path for control file (and logs if --log-dir not used).", + (uchar**) &maria_data_root, 0, 0, GET_STR, REQUIRED_ARG, + 0, 0, 0, 0, 0, 0}, + {"log-dir", OPT_LOG_DIR, + "Path for log files.", + (uchar**) &opt_log_dir, 0, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + {"max-record-length", OPT_MAX_RECORD_LENGTH, + "Skip rows bigger than this if maria_chk can't allocate memory to hold it", + (uchar**) &check_param.max_record_length, + (uchar**) &check_param.max_record_length, + 0, GET_ULL, REQUIRED_ARG, LONGLONG_MAX, 0, LONGLONG_MAX, 0, 0, 0}, + {"medium-check", 'm', + "Faster than extend-check, but only finds 99.99% of all errors. Should be good enough for most cases.", + 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"quick", 'q', "Faster repair by not modifying the data file.", + 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"read-only", 'T', + "Don't mark table as checked.", + 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"recover", 'r', + "Can fix almost anything except unique keys that aren't unique.", + 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"parallel-recover", 'p', + "Same as '-r' but creates all the keys in parallel.", + 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"safe-recover", 'o', + "Uses old recovery method; Slower than '-r' but can handle a couple of cases where '-r' reports that it can't fix the data file.", + 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"sort-recover", 'n', + "Force recovering with sorting even if the temporary file was very big.", + 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0}, + { "require-control-file", OPT_REQUIRE_CONTROL_FILE, + "Abort if cannot find control file", + (uchar**)&opt_require_control_file, 0, 0, GET_BOOL, NO_ARG, + 0, 0, 0, 0, 0, 0}, +#ifdef DEBUG + {"start-check-pos", OPT_START_CHECK_POS, + "No help available.", + 0, 0, 0, GET_ULL, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, +#endif + {"set-auto-increment", 'A', + "Force auto_increment to start at this or higher value. If no value is given, then sets the next auto_increment value to the highest used value for the auto key + 1.", + (uchar**) &check_param.auto_increment_value, + (uchar**) &check_param.auto_increment_value, + 0, GET_ULL, OPT_ARG, 0, 0, 0, 0, 0, 0}, + {"set-collation", OPT_SET_COLLATION, + "Change the collation used by the index", + (uchar**) &set_collation_name, 0, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + {"set-variable", 'O', + "Change the value of a variable. Please note that this option is deprecated; you can set variables directly with --variable-name=value.", + 0, 0, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + {"silent", 's', + "Only print errors. One can use two -s to make maria_chk very silent.", + 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0}, +#ifndef DBUG_OFF +#ifdef SAFEMALLOC + {"skip-safemalloc", OPT_SKIP_SAFEMALLOC, + "Don't use the memory allocation checking.", 0, 0, 0, GET_NO_ARG, NO_ARG, + 0, 0, 0, 0, 0, 0}, +#endif +#endif + {"sort-index", 'S', + "Sort index blocks. This speeds up 'read-next' in applications.", + 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"sort-records", 'R', + "Sort records according to an index. This makes your data much more localized and may speed up things. (It may be VERY slow to do a sort the first time!)", + (uchar**) &check_param.opt_sort_key, + (uchar**) &check_param.opt_sort_key, + 0, GET_UINT, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + {"tmpdir", 't', + "Path for temporary files.", + (uchar**) &opt_tmpdir, + 0, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + {"transaction-log", OPT_TRANSACTION_LOG, + "Log repair command to transaction log", + (uchar**) &opt_transaction_logging, (uchar**) &opt_transaction_logging, + 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"update-state", 'U', + "Mark tables as crashed if any errors were found.", + 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"unpack", 'u', + "Unpack file packed with mariapack.", + 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"verbose", 'v', + "Print more information. This can be used with --description and --check. Use many -v for more verbosity!", + 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"version", 'V', "Print version and exit.", + 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"wait", 'w', "Wait if table is locked.", + 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0}, + { "page_buffer_size", OPT_PAGE_BUFFER_SIZE, + "Size of page buffer. Used by --safe-repair", + (uchar**) &check_param.use_buffers, (uchar**) &check_param.use_buffers, 0, + GET_ULONG, REQUIRED_ARG, (long) USE_BUFFER_INIT, 1024L*1024L, + (long) ~0L, (long) MALLOC_OVERHEAD, (long) IO_SIZE, 0}, + { "read_buffer_size", OPT_READ_BUFFER_SIZE, "", + (uchar**) &check_param.read_buffer_length, + (uchar**) &check_param.read_buffer_length, 0, GET_ULONG, REQUIRED_ARG, + (long) READ_BUFFER_INIT, (long) MALLOC_OVERHEAD, + (long) ~0L, (long) MALLOC_OVERHEAD, (long) 1L, 0}, + { "write_buffer_size", OPT_WRITE_BUFFER_SIZE, "", + (uchar**) &check_param.write_buffer_length, + (uchar**) &check_param.write_buffer_length, 0, GET_ULONG, REQUIRED_ARG, + (long) READ_BUFFER_INIT, (long) MALLOC_OVERHEAD, + (long) ~0L, (long) MALLOC_OVERHEAD, (long) 1L, 0}, + { "sort_buffer_size", OPT_SORT_BUFFER_SIZE, + "Size of sort buffer. Used by --recover", + (uchar**) &check_param.sort_buffer_length, + (uchar**) &check_param.sort_buffer_length, 0, GET_ULONG, REQUIRED_ARG, + (long) SORT_BUFFER_INIT, (long) (MIN_SORT_BUFFER + MALLOC_OVERHEAD), + (long) ~0L, (long) MALLOC_OVERHEAD, (long) 1L, 0}, + { "sort_key_blocks", OPT_SORT_KEY_BLOCKS, "", + (uchar**) &check_param.sort_key_blocks, + (uchar**) &check_param.sort_key_blocks, 0, GET_ULONG, REQUIRED_ARG, + BUFFERS_WHEN_SORTING, 4L, 100L, 0L, 1L, 0}, + { "decode_bits", OPT_DECODE_BITS, "", (uchar**) &decode_bits, + (uchar**) &decode_bits, 0, GET_UINT, REQUIRED_ARG, 9L, 4L, 17L, 0L, 1L, 0}, + { "ft_min_word_len", OPT_FT_MIN_WORD_LEN, "", (uchar**) &ft_min_word_len, + (uchar**) &ft_min_word_len, 0, GET_ULONG, REQUIRED_ARG, 4, 1, HA_FT_MAXCHARLEN, + 0, 1, 0}, + { "ft_max_word_len", OPT_FT_MAX_WORD_LEN, "", (uchar**) &ft_max_word_len, + (uchar**) &ft_max_word_len, 0, GET_ULONG, REQUIRED_ARG, HA_FT_MAXCHARLEN, 10, + HA_FT_MAXCHARLEN, 0, 1, 0}, + { "maria_ft_stopword_file", OPT_FT_STOPWORD_FILE, + "Use stopwords from this file instead of built-in list.", + (uchar**) &ft_stopword_file, (uchar**) &ft_stopword_file, 0, GET_STR, + REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + { "stats_method", OPT_STATS_METHOD, + "Specifies how index statistics collection code should treat NULLs. " + "Possible values of name are \"nulls_unequal\" (default behavior for 4.1/5.0), " + "\"nulls_equal\" (emulate 4.0 behavior), and \"nulls_ignored\".", + (uchar**) &maria_stats_method_str, (uchar**) &maria_stats_method_str, 0, + GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + { "zerofill", 'z', + "Fill empty space in data and index files with zeroes", + 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0}, + { "zerofill-keep-lsn", OPT_ZEROFILL_KEEP_LSN, + "Like --zerofill but does not zero out LSN of data/index pages;" + " used only for testing and debugging", + 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} +}; + + +#include <help_start.h> + +static void print_version(void) +{ + printf("%s Ver 1.0 for %s at %s\n", my_progname, SYSTEM_TYPE, + MACHINE_TYPE); + NETWARE_SET_SCREEN_MODE(1); +} + + +static void usage(void) +{ + print_version(); + puts("By Monty, for your professional use"); + puts("This software comes with NO WARRANTY: see the PUBLIC for details.\n"); + puts("Description, check and repair of MARIA tables."); + puts("Used without options all tables on the command will be checked for errors"); + printf("Usage: %s [OPTIONS] tables[.MAI]\n", my_progname_short); + printf("\nGlobal options:\n"); +#ifndef DBUG_OFF + printf("\ + -#, --debug=... Output debug log. Often this is 'd:t:o,filename'.\n"); +#endif + printf("\ + -H, --HELP Display this help and exit.\n\ + -?, --help Display this help and exit.\n\ + -O, --set-variable var=option.\n\ + Change the value of a variable. Please note that\n\ + this option is deprecated; you can set variables\n\ + directly with '--variable-name=value'.\n\ + -t, --tmpdir=path Path for temporary files. Multiple paths can be\n\ + specified, separated by "); +#if defined( __WIN__) || defined(__NETWARE__) + printf("semicolon (;)"); +#else + printf("colon (:)"); +#endif + printf(", they will be used\n\ + in a round-robin fashion.\n\ + --require-control-file Abort if we can't find/read the maria_log_control\n\ + file\n\ + -s, --silent Only print errors. One can use two -s to make\n\ + maria_chk very silent.\n\ + -v, --verbose Print more information. This can be used with\n\ + --description and --check. Use many -v for more verbosity.\n\ + -V, --version Print version and exit.\n\ + -w, --wait Wait if table is locked.\n\n"); +#ifdef DEBUG + puts(" --start-check-pos=# Start reading file at given offset.\n"); +#endif + + puts("Check options (check is the default action for maria_chk):\n\ + -c, --check Check table for errors.\n\ + -e, --extend-check Check the table VERY throughly. Only use this in\n\ + extreme cases as maria_chk should normally be able to\n\ + find out if the table is ok even without this switch.\n\ + -F, --fast Check only tables that haven't been closed properly.\n\ + -C, --check-only-changed\n\ + Check only tables that have changed since last check.\n\ + -f, --force Restart with '-r' if there are any errors in the table.\n\ + States will be updated as with '--update-state'.\n\ + -i, --information Print statistics information about table that is checked.\n\ + -m, --medium-check Faster than extend-check, but only finds 99.99% of\n\ + all errors. Should be good enough for most cases.\n\ + -U --update-state Mark tables as crashed if you find any errors.\n\ + -T, --read-only Don't mark table as checked.\n"); + + puts("Recover (repair)/ options (When using '-r' or '-o'):\n\ + -B, --backup Make a backup of the .MAD file as 'filename-time.BAK'.\n\ + --correct-checksum Correct checksum information for table.\n\ + -D, --data-file-length=# Max length of data file (when recreating data\n\ + file when it's full).\n\ + -e, --extend-check Try to recover every possible row from the data file\n\ + Normally this will also find a lot of garbage rows;\n\ + Don't use this option if you are not totally desperate.\n\ + -f, --force Overwrite old temporary files.\n\ + -k, --keys-used=# Tell MARIA to update only some specific keys. # is a\n\ + bit mask of which keys to use. This can be used to\n\ + get faster inserts.\n\ + --max-record-length=#\n\ + Skip rows bigger than this if maria_chk can't allocate\n\ + memory to hold it.\n\ + -r, --recover Can fix almost anything except unique keys that aren't\n\ + unique.\n\ + -n, --sort-recover Forces recovering with sorting even if the temporary\n\ + file would be very big.\n\ + -p, --parallel-recover\n\ + Uses the same technique as '-r' and '-n', but creates\n\ + all the keys in parallel, in different threads."); + puts("\ + -o, --safe-recover Uses old recovery method; Slower than '-r' but can\n \ + handle a couple of cases where '-r' reports that it\n\ + can't fix the data file.\n\ + --transaction-log Log repair command to transaction log. This is needed\n\ + if one wants to use the maria_read_log to repeat the \n\ + repair\n\ + --character-sets-dir=...\n\ + Directory where character sets are.\n\ + --set-collation=name\n\ + Change the collation used by the index.\n\ + -q, --quick Faster repair by not modifying the data file.\n\ + One can give a second '-q' to force maria_chk to\n\ + modify the original datafile in case of duplicate keys.\n\ + NOTE: Tables where the data file is currupted can't be\n\ + fixed with this option.\n\ + -u, --unpack Unpack file packed with mariapack.\n\ +"); + + puts("Other actions:\n\ + -a, --analyze Analyze distribution of keys. Will make some joins in\n\ + MySQL faster. You can check the calculated distribution\n\ + by using '--description --verbose table_name'.\n\ + --stats_method=name Specifies how index statistics collection code should\n\ + treat NULLs. Possible values of name are \"nulls_unequal\"\n\ + (default for 4.1/5.0), \"nulls_equal\" (emulate 4.0), and \n\ + \"nulls_ignored\".\n\ + -d, --description Prints some information about table.\n\ + -A, --set-auto-increment[=value]\n\ + Force auto_increment to start at this or higher value\n\ + If no value is given, then sets the next auto_increment\n\ + value to the highest used value for the auto key + 1.\n\ + -S, --sort-index Sort index blocks. This speeds up 'read-next' in\n\ + applications.\n\ + -R, --sort-records=#\n\ + Sort records according to an index. This makes your\n\ + data much more localized and may speed up things\n\ + (It may be VERY slow to do a sort the first time!).\n\ + -b, --block-search=#\n\ + Find a record, a block at given offset belongs to.\n\ + -z, --zerofill Fill empty space in data and index files with zeroes\n\ + --zerofill-keep-lsn Like --zerofill but does not zero out LSN of\n\ + data/index pages."); + + print_defaults("my", load_default_groups); + my_print_variables(my_long_options); +} + +#include <help_end.h> + +const char *maria_stats_method_names[] = {"nulls_unequal", "nulls_equal", + "nulls_ignored", NullS}; +TYPELIB maria_stats_method_typelib= { + array_elements(maria_stats_method_names) - 1, "", + maria_stats_method_names, NULL}; + + /* Read options */ + +static my_bool +get_one_option(int optid, + const struct my_option *opt __attribute__((unused)), + char *argument) +{ + switch (optid) { +#ifdef __NETWARE__ + case OPT_AUTO_CLOSE: + setscreenmode(SCR_AUTOCLOSE_ON_EXIT); + break; +#endif + case 'a': + if (argument == disabled_my_option) + check_param.testflag&= ~T_STATISTICS; + else + check_param.testflag|= T_STATISTICS; + break; + case 'A': + if (argument) + check_param.auto_increment_value= strtoull(argument, NULL, 0); + else + check_param.auto_increment_value= 0; /* Set to max used value */ + check_param.testflag|= T_AUTO_INC; + break; + case 'b': + check_param.search_after_block= strtoul(argument, NULL, 10); + break; + case 'B': + if (argument == disabled_my_option) + check_param.testflag&= ~T_BACKUP_DATA; + else + check_param.testflag|= T_BACKUP_DATA; + break; + case 'c': + if (argument == disabled_my_option) + check_param.testflag&= ~T_CHECK; + else + check_param.testflag|= T_CHECK; + break; + case 'C': + if (argument == disabled_my_option) + check_param.testflag&= ~(T_CHECK | T_CHECK_ONLY_CHANGED); + else + check_param.testflag|= T_CHECK | T_CHECK_ONLY_CHANGED; + break; + case 'D': + check_param.max_data_file_length=strtoll(argument, NULL, 10); + break; + case 's': /* silent */ + if (argument == disabled_my_option) + check_param.testflag&= ~(T_SILENT | T_VERY_SILENT); + else + { + if (check_param.testflag & T_SILENT) + check_param.testflag|= T_VERY_SILENT; + check_param.testflag|= T_SILENT; + check_param.testflag&= ~T_WRITE_LOOP; + } + break; + case 'w': + if (argument == disabled_my_option) + check_param.testflag&= ~T_WAIT_FOREVER; + else + check_param.testflag|= T_WAIT_FOREVER; + break; + case 'd': /* description if isam-file */ + if (argument == disabled_my_option) + check_param.testflag&= ~T_DESCRIPT; + else + check_param.testflag|= T_DESCRIPT; + break; + case 'e': /* extend check */ + if (argument == disabled_my_option) + check_param.testflag&= ~T_EXTEND; + else + check_param.testflag|= T_EXTEND; + break; + case 'i': + if (argument == disabled_my_option) + check_param.testflag&= ~T_INFO; + else + check_param.testflag|= T_INFO; + break; + case 'f': + if (argument == disabled_my_option) + { + check_param.tmpfile_createflag= O_RDWR | O_TRUNC | O_EXCL; + check_param.testflag&= ~(T_FORCE_CREATE | T_UPDATE_STATE); + } + else + { + check_param.tmpfile_createflag= O_RDWR | O_TRUNC; + check_param.testflag|= T_FORCE_CREATE | T_UPDATE_STATE; + } + break; + case 'F': + if (argument == disabled_my_option) + check_param.testflag&= ~T_FAST; + else + check_param.testflag|= T_FAST; + break; + case 'k': + check_param.keys_in_use= (ulonglong) strtoll(argument, NULL, 10); + break; + case 'm': + if (argument == disabled_my_option) + check_param.testflag&= ~T_MEDIUM; + else + check_param.testflag|= T_MEDIUM; /* Medium check */ + break; + case 'r': /* Repair table */ + check_param.testflag&= ~T_REP_ANY; + if (argument != disabled_my_option) + check_param.testflag|= T_REP_BY_SORT; + break; + case 'p': + check_param.testflag&= ~T_REP_ANY; + if (argument != disabled_my_option) + check_param.testflag|= T_REP_PARALLEL; + break; + case 'o': + check_param.testflag&= ~T_REP_ANY; + check_param.force_sort= 0; + if (argument != disabled_my_option) + { + check_param.testflag|= T_REP; + my_disable_async_io= 1; /* More safety */ + } + break; + case 'n': + check_param.testflag&= ~T_REP_ANY; + if (argument == disabled_my_option) + check_param.force_sort= 0; + else + { + check_param.testflag|= T_REP_BY_SORT; + check_param.force_sort= 1; + } + break; + case 'q': + if (argument == disabled_my_option) + check_param.testflag&= ~(T_QUICK | T_FORCE_UNIQUENESS); + else + check_param.testflag|= + (check_param.testflag & T_QUICK) ? T_FORCE_UNIQUENESS : T_QUICK; + break; + case 'u': + if (argument == disabled_my_option) + check_param.testflag&= ~T_UNPACK; + else + { + check_param.testflag|= T_UNPACK; + if (!(check_param.testflag & T_REP_ANY)) + check_param.testflag|= T_REP_BY_SORT; + } + break; + case 'v': /* Verbose */ + if (argument == disabled_my_option) + { + check_param.testflag&= ~T_VERBOSE; + check_param.verbose=0; + } + else + { + check_param.testflag|= T_VERBOSE; + check_param.verbose++; + } + break; + case 'R': /* Sort records */ + if (argument == disabled_my_option) + check_param.testflag&= ~T_SORT_RECORDS; + else + { + check_param.testflag|= T_SORT_RECORDS; + check_param.opt_sort_key= (uint) atoi(argument) - 1; + if (check_param.opt_sort_key >= MARIA_MAX_KEY) + { + fprintf(stderr, + "The value of the sort key is bigger than max key: %d.\n", + MARIA_MAX_KEY); + exit(1); + } + } + break; + case 'S': /* Sort index */ + if (argument == disabled_my_option) + check_param.testflag&= ~T_SORT_INDEX; + else + check_param.testflag|= T_SORT_INDEX; + break; + case 'T': + if (argument == disabled_my_option) + check_param.testflag&= ~T_READONLY; + else + check_param.testflag|= T_READONLY; + break; + case 'U': + if (argument == disabled_my_option) + check_param.testflag&= ~T_UPDATE_STATE; + else + check_param.testflag|= T_UPDATE_STATE; + break; + case '#': + DBUG_SET_INITIAL(argument ? argument : "d:t:o,/tmp/maria_chk.trace"); + opt_debug= 1; + break; + case OPT_SKIP_SAFEMALLOC: +#ifdef SAFEMALLOC + sf_malloc_quick=1; +#endif + break; + case 'V': + print_version(); + exit(0); + case OPT_CORRECT_CHECKSUM: + if (argument == disabled_my_option) + check_param.testflag&= ~T_CALC_CHECKSUM; + else + check_param.testflag|= T_CALC_CHECKSUM; + break; + case OPT_STATS_METHOD: + { + int method; + enum_handler_stats_method method_conv; + LINT_INIT(method_conv); + maria_stats_method_str= argument; + if ((method=find_type(argument, &maria_stats_method_typelib, 2)) <= 0) + { + fprintf(stderr, "Invalid value of stats_method: %s.\n", argument); + exit(1); + } + switch (method-1) { + case 0: + method_conv= MI_STATS_METHOD_NULLS_EQUAL; + break; + case 1: + method_conv= MI_STATS_METHOD_NULLS_NOT_EQUAL; + break; + case 2: + method_conv= MI_STATS_METHOD_IGNORE_NULLS; + break; + default: assert(0); /* Impossible */ + } + check_param.stats_method= method_conv; + break; + } +#ifdef DEBUG /* Only useful if debugging */ + case OPT_START_CHECK_POS: + check_param.start_check_pos= strtoull(argument, NULL, 0); + break; +#endif + case 'z': + if (argument == disabled_my_option) + check_param.testflag&= ~T_ZEROFILL; + else + check_param.testflag|= T_ZEROFILL; + break; + case OPT_ZEROFILL_KEEP_LSN: + if (argument == disabled_my_option) + check_param.testflag&= ~(T_ZEROFILL_KEEP_LSN | T_ZEROFILL); + else + check_param.testflag|= (T_ZEROFILL_KEEP_LSN | T_ZEROFILL); + break; + case 'H': + my_print_help(my_long_options); + exit(0); + case '?': + usage(); + exit(0); + } + return 0; +} + + +static void get_options(register int *argc,register char ***argv) +{ + int ho_error; + + load_defaults("my", load_default_groups, argc, argv); + default_argv= *argv; + if (isatty(fileno(stdout))) + check_param.testflag|=T_WRITE_LOOP; + + if ((ho_error=handle_options(argc, argv, my_long_options, get_one_option))) + exit(ho_error); + + /* If using repair, then update checksum if one uses --update-state */ + if ((check_param.testflag & T_UPDATE_STATE) && + (check_param.testflag & T_REP_ANY)) + check_param.testflag|= T_CALC_CHECKSUM; + + if (*argc == 0) + { + usage(); + exit(-1); + } + + if ((check_param.testflag & T_UNPACK) && + (check_param.testflag & (T_QUICK | T_SORT_RECORDS))) + { + VOID(fprintf(stderr, + "%s: --unpack can't be used with --quick or --sort-records\n", + my_progname_short)); + exit(1); + } + if ((check_param.testflag & T_READONLY) && + (check_param.testflag & + (T_REP_ANY | T_STATISTICS | T_AUTO_INC | + T_SORT_RECORDS | T_SORT_INDEX | T_FORCE_CREATE))) + { + VOID(fprintf(stderr, + "%s: Can't use --readonly when repairing or sorting\n", + my_progname_short)); + exit(1); + } + + if (!opt_debug) + { + DEBUGGER_OFF; /* Speed up things a bit */ + } + if (init_tmpdir(&maria_chk_tmpdir, opt_tmpdir)) + exit(1); + + check_param.tmpdir=&maria_chk_tmpdir; + + if (set_collation_name) + if (!(set_collation= get_charset_by_name(set_collation_name, + MYF(MY_WME)))) + exit(1); + + return; +} /* get options */ + + + /* Check table */ + +static int maria_chk(HA_CHECK *param, char *filename) +{ + int error,lock_type,recreate; + my_bool rep_quick= test(param->testflag & (T_QUICK | T_FORCE_UNIQUENESS)); + MARIA_HA *info; + File datafile; + char llbuff[22],llbuff2[22]; + my_bool state_updated=0; + MARIA_SHARE *share; + DBUG_ENTER("maria_chk"); + + param->out_flag=error=param->warning_printed=param->error_printed= + recreate=0; + datafile=0; + param->isam_file_name=filename; /* For error messages */ + if (!(info=maria_open(filename, + (param->testflag & (T_DESCRIPT | T_READONLY)) ? + O_RDONLY : O_RDWR, + HA_OPEN_FOR_REPAIR | + ((param->testflag & T_WAIT_FOREVER) ? + HA_OPEN_WAIT_IF_LOCKED : + (param->testflag & T_DESCRIPT) ? + HA_OPEN_IGNORE_IF_LOCKED : HA_OPEN_ABORT_IF_LOCKED)))) + { + /* Avoid twice printing of isam file name */ + param->error_printed=1; + switch (my_errno) { + case HA_ERR_CRASHED: + _ma_check_print_error(param,"'%s' doesn't have a correct index definition. You need to recreate it before you can do a repair",filename); + break; + case HA_ERR_NOT_A_TABLE: + _ma_check_print_error(param,"'%s' is not a MARIA-table",filename); + break; + case HA_ERR_CRASHED_ON_USAGE: + _ma_check_print_error(param,"'%s' is marked as crashed",filename); + break; + case HA_ERR_CRASHED_ON_REPAIR: + _ma_check_print_error(param,"'%s' is marked as crashed after last repair",filename); + break; + case HA_ERR_OLD_FILE: + _ma_check_print_error(param,"'%s' is a old type of MARIA-table", filename); + break; + case HA_ERR_NEW_FILE: + _ma_check_print_error(param,"'%s' uses new features not supported by this version of the MARIA library", filename); + break; + case HA_ERR_END_OF_FILE: + _ma_check_print_error(param,"Couldn't read complete header from '%s'", filename); + break; + case EAGAIN: + _ma_check_print_error(param,"'%s' is locked. Use -w to wait until unlocked",filename); + break; + case ENOENT: + _ma_check_print_error(param,"File '%s' doesn't exist",filename); + break; + case EACCES: + _ma_check_print_error(param,"You don't have permission to use '%s'", + filename); + break; + default: + _ma_check_print_error(param,"%d when opening MARIA-table '%s'", + my_errno,filename); + break; + } + DBUG_RETURN(1); + } + share= info->s; + share->tot_locks-= share->r_locks; + share->r_locks=0; + maria_block_size= share->base.block_size; + + if (share->data_file_type == BLOCK_RECORD || + ((param->testflag & T_UNPACK) && + share->state.header.org_data_file_type == BLOCK_RECORD)) + { + if (param->testflag & T_SORT_RECORDS) + { + _ma_check_print_error(param, + "Record format used by '%s' is is not yet supported with sort-records", + filename); + param->error_printed= 0; + error= 1; + goto end2; + } + /* We can't do parallell repair with BLOCK_RECORD yet */ + if (param->testflag & T_REP_PARALLEL) + { + param->testflag&= ~T_REP_PARALLEL; + param->testflag|= T_REP_BY_SORT; + } + } + + /* + Skip the checking of the file if: + We are using --fast and the table is closed properly + We are using --check-only-changed-tables and the table hasn't changed + */ + if (param->testflag & (T_FAST | T_CHECK_ONLY_CHANGED)) + { + my_bool need_to_check= (maria_is_crashed(info) || + share->state.open_count != 0); + + if ((param->testflag & (T_REP_ANY | T_SORT_RECORDS)) && + ((share->state.changed & (STATE_CHANGED | STATE_CRASHED | + STATE_CRASHED_ON_REPAIR) || + !(param->testflag & T_CHECK_ONLY_CHANGED)))) + need_to_check=1; + + if (info->s->base.keys && info->state->records) + { + if ((param->testflag & T_STATISTICS) && + (share->state.changed & STATE_NOT_ANALYZED)) + need_to_check=1; + if ((param->testflag & T_SORT_INDEX) && + (share->state.changed & STATE_NOT_SORTED_PAGES)) + need_to_check=1; + if ((param->testflag & T_REP_BY_SORT) && + (share->state.changed & STATE_NOT_OPTIMIZED_KEYS)) + need_to_check=1; + } + if ((param->testflag & T_CHECK_ONLY_CHANGED) && + (share->state.changed & (STATE_CHANGED | STATE_CRASHED | + STATE_CRASHED_ON_REPAIR))) + need_to_check=1; + if (!need_to_check) + { + if (!(param->testflag & T_SILENT) || param->testflag & T_INFO) + printf("MARIA file: %s is already checked\n",filename); + if (maria_close(info)) + { + _ma_check_print_error(param,"%d when closing MARIA-table '%s'", + my_errno,filename); + DBUG_RETURN(1); + } + DBUG_RETURN(0); + } + } + if ((param->testflag & (T_REP_ANY | T_STATISTICS | + T_SORT_RECORDS | T_SORT_INDEX)) && + (((param->testflag & T_UNPACK) && + share->data_file_type == COMPRESSED_RECORD) || + mi_uint2korr(share->state.header.state_info_length) != + MARIA_STATE_INFO_SIZE || + mi_uint2korr(share->state.header.base_info_length) != + MARIA_BASE_INFO_SIZE || + maria_is_any_intersect_keys_active(param->keys_in_use, share->base.keys, + ~share->state.key_map) || + maria_test_if_almost_full(info) || + info->s->state.header.file_version[3] != maria_file_magic[3] || + (set_collation && + set_collation->number != share->state.header.language))) + { + if (set_collation) + param->language= set_collation->number; + if (maria_recreate_table(param, &info,filename)) + { + VOID(fprintf(stderr, + "MARIA-table '%s' is not fixed because of errors\n", + filename)); + return(-1); + } + recreate=1; + if (!(param->testflag & T_REP_ANY)) + { + param->testflag|=T_REP_BY_SORT; /* if only STATISTICS */ + if (!(param->testflag & T_SILENT)) + printf("- '%s' has old table-format. Recreating index\n",filename); + rep_quick= 1; + } + share= info->s; + share->tot_locks-= share->r_locks; + share->r_locks=0; + } + + if (param->testflag & T_DESCRIPT) + { + param->total_files++; + param->total_records+=info->state->records; + param->total_deleted+=info->state->del; + descript(param, info, filename); + maria_close(info); /* Should always succeed */ + return(0); + } + + if (!stopwords_inited++) + ft_init_stopwords(); + + if (!(param->testflag & T_READONLY)) + lock_type = F_WRLCK; /* table is changed */ + else + lock_type= F_RDLCK; + if (info->lock_type == F_RDLCK) + info->lock_type=F_UNLCK; /* Read only table */ + if (_ma_readinfo(info,lock_type,0)) + { + _ma_check_print_error(param,"Can't lock indexfile of '%s', error: %d", + filename,my_errno); + param->error_printed=0; + error= 1; + goto end2; + } + /* + _ma_readinfo() has locked the table. + We mark the table as locked (without doing file locks) to be able to + use functions that only works on locked tables (like row caching). + */ + maria_lock_database(info, F_EXTRA_LCK); + datafile= info->dfile.file; + if (init_pagecache(maria_pagecache, param->use_buffers, 0, 0, + maria_block_size, MY_WME) == 0) + { + _ma_check_print_error(param, "Can't initialize page cache with %lu memory", + (ulong) param->use_buffers); + error= 1; + goto end2; + } + + if (param->testflag & (T_REP_ANY | T_SORT_RECORDS | T_SORT_INDEX | + T_ZEROFILL)) + { + /* + Mark table as not transactional to avoid logging. Should not be needed, + maria_repair and maria_zerofill do it already. + */ + _ma_tmp_disable_logging_for_table(info, FALSE); + + if (param->testflag & T_REP_ANY) + { + ulonglong tmp=share->state.key_map; + maria_copy_keys_active(share->state.key_map, share->base.keys, + param->keys_in_use); + if (tmp != share->state.key_map) + info->update|=HA_STATE_CHANGED; + + if (rep_quick && + maria_chk_del(param, info, param->testflag & ~T_VERBOSE)) + { + if (param->testflag & T_FORCE_CREATE) + { + rep_quick=0; + _ma_check_print_info(param,"Creating new data file\n"); + } + else + { + error=1; + _ma_check_print_error(param, + "Quick-recover aborted; Run recovery without switch 'q'"); + } + } + } + if (!error) + { + /* + Unless this was only --zerofill-keep-lsn, old REDOs are not + applicable, tell the server's Recovery to ignore them; we don't + know what the log's end LSN is now, so we just let the server know + that it will have to find and store it. + This is the only case where create_rename_lsn can be a horizon and not + a LSN. + If this was only --zerofill-keep-lsn, the table can be used in + Recovery and especially in this scenario: do a dirty-copy-based backup + (snapshot-like), --zerofill-keep-lsn on the copies to achieve better + compression, compress the copies with an external tool, and after a + restore, Recovery still works (because pages and state still have + their correct LSNs). + */ + if (share->base.born_transactional && + ((param->testflag & (T_REP_ANY | T_SORT_RECORDS | T_SORT_INDEX | + T_ZEROFILL | T_ZEROFILL_KEEP_LSN)) != + (T_ZEROFILL | T_ZEROFILL_KEEP_LSN))) + share->state.create_rename_lsn= share->state.is_of_horizon= + share->state.skip_redo_lsn= LSN_NEEDS_NEW_STATE_LSNS; + } + if (!error && (param->testflag & T_REP_ANY)) + { + if ((param->testflag & (T_REP_BY_SORT | T_REP_PARALLEL)) && + (maria_is_any_key_active(share->state.key_map) || + (rep_quick && !param->keys_in_use && !recreate)) && + maria_test_if_sort_rep(info, info->state->records, + info->s->state.key_map, + param->force_sort)) + { + if (param->testflag & T_REP_BY_SORT) + error=maria_repair_by_sort(param,info,filename,rep_quick); + else + error=maria_repair_parallel(param,info,filename,rep_quick); + state_updated=1; + } + else + error=maria_repair(param, info,filename,rep_quick); + } + if (!error && (param->testflag & T_SORT_RECORDS)) + { + /* + The data file is nowadays reopened in the repair code so we should + soon remove the following reopen-code + */ +#ifndef TO_BE_REMOVED + if (param->out_flag & O_NEW_DATA) + { /* Change temp file to org file */ + VOID(my_close(info->dfile.file, MYF(MY_WME))); /* Close new file */ + error|=maria_change_to_newfile(filename,MARIA_NAME_DEXT,DATA_TMP_EXT, + MYF(0)); + if (_ma_open_datafile(info,info->s, NullS, -1)) + error=1; + param->out_flag&= ~O_NEW_DATA; /* We are using new datafile */ + param->read_cache.file= info->dfile.file; + } +#endif + if (! error) + { + uint key; + /* + We can't update the index in maria_sort_records if we have a + prefix compressed or fulltext index + */ + my_bool update_index=1; + for (key=0 ; key < share->base.keys; key++) + if (share->keyinfo[key].flag & (HA_BINARY_PACK_KEY|HA_FULLTEXT)) + update_index=0; + + error=maria_sort_records(param,info,filename,param->opt_sort_key, + /* what is the following parameter for ? */ + (my_bool) !(param->testflag & T_REP), + update_index); + datafile= info->dfile.file; /* This is now locked */ + if (!error && !update_index) + { + if (param->verbose) + puts("Table had a compressed index; We must now recreate the index"); + error=maria_repair_by_sort(param,info,filename,1); + } + } + } + if (!error && (param->testflag & T_SORT_INDEX)) + error= maria_sort_index(param,info,filename); + if (!error && (param->testflag & T_ZEROFILL)) + error= maria_zerofill(param, info, filename); + if (!error) + share->state.changed&= ~(STATE_CHANGED | STATE_CRASHED | + STATE_CRASHED_ON_REPAIR); + else + maria_mark_crashed(info); + } + else if ((param->testflag & T_CHECK) || !(param->testflag & T_AUTO_INC)) + { + if (!(param->testflag & T_SILENT) || param->testflag & T_INFO) + printf("Checking MARIA file: %s\n",filename); + if (!(param->testflag & T_SILENT)) + printf("Data records: %7s Deleted blocks: %7s\n", + llstr(info->state->records,llbuff), + llstr(info->state->del,llbuff2)); + maria_chk_init_for_check(param, info); + error= maria_chk_status(param,info); + maria_intersect_keys_active(share->state.key_map, param->keys_in_use); + error|= maria_chk_size(param,info); + if (!error || !(param->testflag & (T_FAST | T_FORCE_CREATE))) + error|=maria_chk_del(param, info,param->testflag); + if ((!error || (!(param->testflag & (T_FAST | T_FORCE_CREATE)) && + !param->start_check_pos))) + { + error|=maria_chk_key(param, info); + if (!error && (param->testflag & (T_STATISTICS | T_AUTO_INC))) + error=maria_update_state_info(param, info, + ((param->testflag & T_STATISTICS) ? + UPDATE_STAT : 0) | + ((param->testflag & T_AUTO_INC) ? + UPDATE_AUTO_INC : 0)); + } + if ((!rep_quick && !error) || + !(param->testflag & (T_FAST | T_FORCE_CREATE))) + { + VOID(init_io_cache(¶m->read_cache,datafile, + (uint) param->read_buffer_length, + READ_CACHE, + (param->start_check_pos ? + param->start_check_pos : + share->pack.header_length), + 1, + MYF(MY_WME))); + maria_lock_memory(param); + if ((info->s->data_file_type != STATIC_RECORD) || + (param->testflag & (T_EXTEND | T_MEDIUM))) + error|=maria_chk_data_link(param, info, + test(param->testflag & T_EXTEND)); + VOID(end_io_cache(¶m->read_cache)); + } + if (!error) + { + if ((share->state.changed & STATE_CHANGED) && + (param->testflag & T_UPDATE_STATE)) + info->update|=HA_STATE_CHANGED | HA_STATE_ROW_CHANGED; + share->state.changed&= ~(STATE_CHANGED | STATE_CRASHED | + STATE_CRASHED_ON_REPAIR); + } + else if (!maria_is_crashed(info) && + (param->testflag & T_UPDATE_STATE)) + { /* Mark crashed */ + maria_mark_crashed(info); + info->update|=HA_STATE_CHANGED | HA_STATE_ROW_CHANGED; + } + } + + if ((param->testflag & T_AUTO_INC) || + ((param->testflag & T_REP_ANY) && info->s->base.auto_key)) + _ma_update_auto_increment_key(param, info, + (my_bool) !test(param->testflag & T_AUTO_INC)); + + if (info->update & HA_STATE_CHANGED && ! (param->testflag & T_READONLY)) + error|=maria_update_state_info(param, info, + UPDATE_OPEN_COUNT | + (((param->testflag & T_REP_ANY) ? + UPDATE_TIME : 0) | + (state_updated ? UPDATE_STAT : 0) | + ((param->testflag & T_SORT_RECORDS) ? + UPDATE_SORT : 0))); + info->update&= ~HA_STATE_CHANGED; + _ma_reenable_logging_for_table(info, FALSE); + maria_lock_database(info, F_UNLCK); + +end2: + end_pagecache(maria_pagecache, 1); + if (maria_close(info)) + { + _ma_check_print_error(param, default_close_errmsg, my_errno, filename); + DBUG_RETURN(1); + } + if (error == 0) + { + if (param->out_flag & O_NEW_DATA) + error|=maria_change_to_newfile(filename,MARIA_NAME_DEXT,DATA_TMP_EXT, + ((param->testflag & T_BACKUP_DATA) ? + MYF(MY_REDEL_MAKE_BACKUP) : MYF(0))); + if (param->out_flag & O_NEW_INDEX) + error|=maria_change_to_newfile(filename,MARIA_NAME_IEXT,INDEX_TMP_EXT, + MYF(0)); + } + if (opt_transaction_logging && + share->base.born_transactional && !error && + (param->testflag & (T_REP_ANY | T_SORT_RECORDS | T_SORT_INDEX | + T_ZEROFILL))) + error= write_log_record(param); + + if (param->not_visible_rows_found && (param->testflag & T_VERBOSE)) + { + char buff[22]; + printf("Max transaction id found: %s\n", + llstr(param->max_found_trid, buff)); + } + + VOID(fflush(stdout)); VOID(fflush(stderr)); + + if (param->error_printed) + { + if (param->testflag & (T_REP_ANY | T_SORT_RECORDS | T_SORT_INDEX)) + { + VOID(fprintf(stderr, + "MARIA-table '%s' is not fixed because of errors\n", + filename)); + if (param->testflag & T_REP_ANY) + VOID(fprintf(stderr, + "Try fixing it by using the --safe-recover (-o), the --force (-f) option or by not using the --quick (-q) flag\n")); + } + else if (!(param->error_printed & 2) && + !(param->testflag & T_FORCE_CREATE)) + VOID(fprintf(stderr, + "MARIA-table '%s' is corrupted\nFix it using switch \"-r\" or \"-o\"\n", + filename)); + } + else if (param->warning_printed && + ! (param->testflag & (T_REP_ANY | T_SORT_RECORDS | T_SORT_INDEX | + T_FORCE_CREATE))) + VOID(fprintf(stderr, "MARIA-table '%s' is usable but should be fixed\n", + filename)); + VOID(fflush(stderr)); + DBUG_RETURN(error); +} /* maria_chk */ + + +/* Write info about table */ + +static void descript(HA_CHECK *param, register MARIA_HA *info, char *name) +{ + uint key,keyseg_nr,field; + reg3 MARIA_KEYDEF *keyinfo; + reg2 HA_KEYSEG *keyseg; + reg4 const char *text; + char buff[200],length[10],*pos,*end; + enum en_fieldtype type; + MARIA_SHARE *share= info->s; + char llbuff[22],llbuff2[22]; + DBUG_ENTER("descript"); + + if (param->testflag & T_VERY_SILENT) + { + longlong checksum= info->state->checksum; + if (!(share->options & (HA_OPTION_CHECKSUM | HA_OPTION_COMPRESS_RECORD))) + checksum= 0; + printf("%s %s %s\n", name, llstr(info->state->records,llbuff), + llstr(checksum, llbuff2)); + DBUG_VOID_RETURN; + } + + printf("MARIA file: %s\n",name); + printf("Record format: %s\n", record_formats[share->data_file_type]); + printf("Crashsafe: %s\n", + share->base.born_transactional ? "yes" : "no"); + printf("Character set: %s (%d)\n", + get_charset_name(share->state.header.language), + share->state.header.language); + + if (param->testflag & T_VERBOSE) + { + printf("File-version: %d\n", + (int) share->state.header.file_version[3]); + if (share->state.create_time) + { + get_date(buff,1,share->state.create_time); + printf("Creation time: %s\n",buff); + } + if (share->state.check_time) + { + get_date(buff,1,share->state.check_time); + printf("Recover time: %s\n",buff); + } + if (share->base.born_transactional) + { + printf("LSNs: create_rename (%lu,0x%lx)," + " state_horizon (%lu,0x%lx), skip_redo (%lu,0x%lx)\n", + LSN_IN_PARTS(share->state.create_rename_lsn), + LSN_IN_PARTS(share->state.is_of_horizon), + LSN_IN_PARTS(share->state.skip_redo_lsn)); + } + compile_time_assert((MY_UUID_STRING_LENGTH + 1) <= sizeof(buff)); + buff[MY_UUID_STRING_LENGTH]= 0; + my_uuid2str(share->base.uuid, buff); + printf("UUID: %s\n", buff); + pos=buff; + if (share->state.changed & STATE_CRASHED) + strmov(buff,"crashed"); + else + { + if (share->state.open_count) + pos=strmov(pos,"open,"); + if (share->state.changed & STATE_CHANGED) + pos=strmov(pos,"changed,"); + else + pos=strmov(pos,"checked,"); + if (!(share->state.changed & STATE_NOT_ANALYZED)) + pos=strmov(pos,"analyzed,"); + if (!(share->state.changed & STATE_NOT_OPTIMIZED_KEYS)) + pos=strmov(pos,"optimized keys,"); + if (!(share->state.changed & STATE_NOT_SORTED_PAGES)) + pos=strmov(pos,"sorted index pages,"); + if (!(share->state.changed & STATE_NOT_ZEROFILLED)) + pos=strmov(pos,"zerofilled,"); + if (!(share->state.changed & STATE_NOT_MOVABLE)) + pos=strmov(pos,"movable,"); + pos[-1]=0; /* Remove extra ',' */ + } + printf("Status: %s\n",buff); + if (share->options & (HA_OPTION_CHECKSUM | HA_OPTION_COMPRESS_RECORD)) + printf("Checksum: %26s\n",llstr(info->state->checksum,llbuff)); +; + if (share->options & HA_OPTION_DELAY_KEY_WRITE) + printf("Keys are only flushed at close\n"); + + if (share->options & HA_OPTION_PAGE_CHECKSUM) + printf("Page checksums are used\n"); + if (share->base.auto_key) + { + printf("Auto increment key: %16d Last value: %18s\n", + share->base.auto_key, + llstr(share->state.auto_increment,llbuff)); + } + } + printf("Data records: %16s Deleted blocks: %18s\n", + llstr(info->state->records,llbuff),llstr(info->state->del,llbuff2)); + if (param->testflag & T_SILENT) + DBUG_VOID_RETURN; /* This is enough */ + + if (param->testflag & T_VERBOSE) + { +#ifdef USE_RELOC + printf("Init-relocation: %16s\n",llstr(share->base.reloc,llbuff)); +#endif + printf("Datafile parts: %16s Deleted data: %18s\n", + llstr(share->state.split,llbuff), + llstr(info->state->empty,llbuff2)); + printf("Datafile pointer (bytes): %11d Keyfile pointer (bytes): %13d\n", + share->rec_reflength,share->base.key_reflength); + printf("Datafile length: %16s Keyfile length: %18s\n", + llstr(info->state->data_file_length,llbuff), + llstr(info->state->key_file_length,llbuff2)); + + if (info->s->base.reloc == 1L && info->s->base.records == 1L) + puts("This is a one-record table"); + else + { + if (share->base.max_data_file_length != HA_OFFSET_ERROR || + share->base.max_key_file_length != HA_OFFSET_ERROR) + printf("Max datafile length: %16s Max keyfile length: %18s\n", + llstr(share->base.max_data_file_length-1,llbuff), + llstr(share->base.max_key_file_length-1,llbuff2)); + } + } + printf("Block_size: %16d\n",(int) share->block_size); + printf("Recordlength: %16d\n",(int) share->base.pack_reclength); + if (! maria_is_all_keys_active(share->state.key_map, share->base.keys)) + { + longlong2str(share->state.key_map,buff,2); + printf("Using only keys '%s' of %d possibly keys\n", + buff, share->base.keys); + } + puts("\ntable description:"); + printf("Key Start Len Index Type"); + if (param->testflag & T_VERBOSE) + printf(" Rec/key Root Blocksize"); + VOID(putchar('\n')); + + for (key=keyseg_nr=0, keyinfo= &share->keyinfo[0] ; + key < share->base.keys; + key++,keyinfo++) + { + keyseg=keyinfo->seg; + if (keyinfo->flag & HA_NOSAME) text="unique "; + else if (keyinfo->flag & HA_FULLTEXT) text="fulltext "; + else text="multip."; + + pos=buff; + if (keyseg->flag & HA_REVERSE_SORT) + *pos++ = '-'; + pos=strmov(pos,type_names[keyseg->type]); + *pos++ = ' '; + *pos=0; + if (keyinfo->flag & HA_PACK_KEY) + pos=strmov(pos,prefix_packed_txt); + if (keyinfo->flag & HA_BINARY_PACK_KEY) + pos=strmov(pos,bin_packed_txt); + if (keyseg->flag & HA_SPACE_PACK) + pos=strmov(pos,diff_txt); + if (keyseg->flag & HA_BLOB_PART) + pos=strmov(pos,blob_txt); + if (keyseg->flag & HA_NULL_PART) + pos=strmov(pos,null_txt); + *pos=0; + + printf("%-4d%-6ld%-3d %-8s%-23s", + key+1,(long) keyseg->start+1,keyseg->length,text,buff); + if (share->state.key_root[key] != HA_OFFSET_ERROR) + llstr(share->state.key_root[key],buff); + else + buff[0]=0; + if (param->testflag & T_VERBOSE) + printf("%9.0f %12s %10d", + share->state.rec_per_key_part[keyseg_nr++], + buff,keyinfo->block_length); + VOID(putchar('\n')); + while ((++keyseg)->type != HA_KEYTYPE_END) + { + pos=buff; + if (keyseg->flag & HA_REVERSE_SORT) + *pos++ = '-'; + pos=strmov(pos,type_names[keyseg->type]); + *pos++= ' '; + if (keyseg->flag & HA_SPACE_PACK) + pos=strmov(pos,diff_txt); + if (keyseg->flag & HA_BLOB_PART) + pos=strmov(pos,blob_txt); + if (keyseg->flag & HA_NULL_PART) + pos=strmov(pos,null_txt); + *pos=0; + printf(" %-6ld%-3d %-21s", + (long) keyseg->start+1,keyseg->length,buff); + if (param->testflag & T_VERBOSE) + printf("%11.0f", share->state.rec_per_key_part[keyseg_nr++]); + VOID(putchar('\n')); + } + keyseg++; + } + if (share->state.header.uniques) + { + MARIA_UNIQUEDEF *uniqueinfo; + puts("\nUnique Key Start Len Nullpos Nullbit Type"); + for (key=0,uniqueinfo= &share->uniqueinfo[0] ; + key < share->state.header.uniques; key++, uniqueinfo++) + { + my_bool new_row=0; + char null_bit[8],null_pos[8]; + printf("%-8d%-5d",key+1,uniqueinfo->key+1); + for (keyseg=uniqueinfo->seg ; keyseg->type != HA_KEYTYPE_END ; keyseg++) + { + if (new_row) + fputs(" ",stdout); + null_bit[0]=null_pos[0]=0; + if (keyseg->null_bit) + { + sprintf(null_bit,"%d",keyseg->null_bit); + sprintf(null_pos,"%ld",(long) keyseg->null_pos+1); + } + printf("%-7ld%-5d%-9s%-10s%-30s\n", + (long) keyseg->start+1,keyseg->length, + null_pos,null_bit, + type_names[keyseg->type]); + new_row=1; + } + } + } + if (param->verbose > 1) + { + char null_bit[8],null_pos[8]; + printf("\nField Start Length Nullpos Nullbit Type"); + if (share->options & HA_OPTION_COMPRESS_RECORD) + printf(" Huff tree Bits"); + VOID(putchar('\n')); + + for (field=0 ; field < share->base.fields ; field++) + { + if (share->options & HA_OPTION_COMPRESS_RECORD) + type=share->columndef[field].base_type; + else + type=(enum en_fieldtype) share->columndef[field].type; + end=strmov(buff,field_pack[type]); + if (share->options & HA_OPTION_COMPRESS_RECORD) + { + if (share->columndef[field].pack_type & PACK_TYPE_SELECTED) + end=strmov(end,", not_always"); + if (share->columndef[field].pack_type & PACK_TYPE_SPACE_FIELDS) + end=strmov(end,", no empty"); + if (share->columndef[field].pack_type & PACK_TYPE_ZERO_FILL) + { + sprintf(end,", zerofill(%d)",share->columndef[field].space_length_bits); + end=strend(end); + } + } + if (buff[0] == ',') + strmov(buff,buff+2); + int10_to_str((long) share->columndef[field].length,length,10); + null_bit[0]=null_pos[0]=0; + if (share->columndef[field].null_bit) + { + sprintf(null_bit,"%d",share->columndef[field].null_bit); + sprintf(null_pos,"%d",share->columndef[field].null_pos+1); + } + printf("%-6d%-6u%-7s%-8s%-8s%-35s",field+1, + (uint) share->columndef[field].offset+1, + length, null_pos, null_bit, buff); + if (share->options & HA_OPTION_COMPRESS_RECORD) + { + if (share->columndef[field].huff_tree) + printf("%3d %2d", + (uint) (share->columndef[field].huff_tree-share->decode_trees)+1, + share->columndef[field].huff_tree->quick_table_bits); + } + VOID(putchar('\n')); + } + } + DBUG_VOID_RETURN; +} /* describe */ + + + /* Sort records according to one key */ + +static int maria_sort_records(HA_CHECK *param, + register MARIA_HA *info, char *name, + uint sort_key, + my_bool write_info, + my_bool update_index) +{ + int got_error; + uint key; + MARIA_KEYDEF *keyinfo; + File new_file; + uchar *temp_buff; + ha_rows old_record_count; + MARIA_SHARE *share= info->s; + char llbuff[22],llbuff2[22]; + MARIA_SORT_INFO sort_info; + MARIA_SORT_PARAM sort_param; + MARIA_PAGE page; + DBUG_ENTER("sort_records"); + + bzero((char*)&sort_info,sizeof(sort_info)); + bzero((char*)&sort_param,sizeof(sort_param)); + sort_param.sort_info=&sort_info; + sort_info.param=param; + keyinfo= &share->keyinfo[sort_key]; + got_error=1; + temp_buff=0; + new_file= -1; + + if (! maria_is_key_active(share->state.key_map, sort_key)) + { + _ma_check_print_warning(param, + "Can't sort table '%s' on key %d; No such key", + name,sort_key+1); + param->error_printed=0; + DBUG_RETURN(0); /* Nothing to do */ + } + if (keyinfo->flag & HA_FULLTEXT) + { + _ma_check_print_warning(param,"Can't sort table '%s' on FULLTEXT key %d", + name,sort_key+1); + param->error_printed=0; + DBUG_RETURN(0); /* Nothing to do */ + } + if (keyinfo->flag & HA_BINARY_PACK_KEY) + { + _ma_check_print_warning(param, + "Can't sort table '%s' on a key with prefix " + "packing %d", + name,sort_key+1); + param->error_printed=0; + DBUG_RETURN(0); + } + + + if (share->data_file_type == COMPRESSED_RECORD) + { + _ma_check_print_warning(param,"Can't sort read-only table '%s'", name); + param->error_printed=0; + DBUG_RETURN(0); /* Nothing to do */ + } + if (!(param->testflag & T_SILENT)) + { + printf("- Sorting records for MARIA-table '%s'\n",name); + if (write_info) + printf("Data records: %9s Deleted: %9s\n", + llstr(info->state->records,llbuff), + llstr(info->state->del,llbuff2)); + } + if (share->state.key_root[sort_key] == HA_OFFSET_ERROR) + DBUG_RETURN(0); /* Nothing to do */ + + if (init_io_cache(&info->rec_cache,-1,(uint) param->write_buffer_length, + WRITE_CACHE,share->pack.header_length,1, + MYF(MY_WME | MY_WAIT_IF_FULL))) + goto err; + info->opt_flag|=WRITE_CACHE_USED; + + if (!(temp_buff=(uchar*) my_alloca((uint) keyinfo->block_length))) + { + _ma_check_print_error(param,"Not enough memory for key block"); + goto err; + } + + if (!(sort_param.record= + (uchar*) my_malloc((uint) share->base.default_rec_buff_size, MYF(0)))) + { + _ma_check_print_error(param,"Not enough memory for record"); + goto err; + } + + fn_format(param->temp_filename,name,"", MARIA_NAME_DEXT,2+4+32); + new_file= my_create(fn_format(param->temp_filename, + param->temp_filename,"", + DATA_TMP_EXT, + MY_REPLACE_EXT | MY_UNPACK_FILENAME), + 0, param->tmpfile_createflag, + MYF(0)); + if (new_file < 0) + { + _ma_check_print_error(param,"Can't create new tempfile: '%s'", + param->temp_filename); + goto err; + } + if (share->pack.header_length) + if (maria_filecopy(param, new_file, info->dfile.file, 0L, + share->pack.header_length, + "datafile-header")) + goto err; + info->rec_cache.file=new_file; /* Use this file for cacheing*/ + + maria_lock_memory(param); + for (key=0 ; key < share->base.keys ; key++) + share->keyinfo[key].flag|= HA_SORT_ALLOWS_SAME; + + if (my_pread(share->kfile.file, temp_buff, + (uint) keyinfo->block_length, + share->state.key_root[sort_key], + MYF(MY_NABP+MY_WME))) + { + _ma_check_print_error(param, "Can't read indexpage from filepos: %s", + llstr(share->state.key_root[sort_key], llbuff)); + goto err; + } + + /* Setup param for _ma_sort_write_record */ + sort_info.info=info; + sort_info.new_data_file_type=share->data_file_type; + sort_param.fix_datafile=1; + sort_param.master=1; + sort_param.filepos=share->pack.header_length; + old_record_count=info->state->records; + info->state->records=0; + if (sort_info.new_data_file_type != COMPRESSED_RECORD) + info->state->checksum=0; + + _ma_page_setup(&page, info, keyinfo, share->state.key_root[sort_key], + temp_buff); + if (sort_record_index(&sort_param, &page, sort_key,new_file,update_index) || + maria_write_data_suffix(&sort_info,1) || + flush_io_cache(&info->rec_cache)) + goto err; + + if (info->state->records != old_record_count) + { + _ma_check_print_error(param,"found %s of %s records", + llstr(info->state->records,llbuff), + llstr(old_record_count,llbuff2)); + goto err; + } + + VOID(my_close(info->dfile.file, MYF(MY_WME))); + param->out_flag|=O_NEW_DATA; /* Data in new file */ + info->dfile.file= new_file; /* Use new datafile */ + _ma_set_data_pagecache_callbacks(&info->dfile, info->s); + + info->state->del=0; + info->state->empty=0; + share->state.dellink= HA_OFFSET_ERROR; + info->state->data_file_length=sort_param.filepos; + share->state.split=info->state->records; /* Only hole records */ + share->state.version=(ulong) time((time_t*) 0); + + info->update= (short) (HA_STATE_CHANGED | HA_STATE_ROW_CHANGED); + + if (param->testflag & T_WRITE_LOOP) + { + VOID(fputs(" \r",stdout)); VOID(fflush(stdout)); + } + got_error=0; + +err: + if (got_error && new_file >= 0) + { + VOID(end_io_cache(&info->rec_cache)); + (void) my_close(new_file,MYF(MY_WME)); + (void) my_delete(param->temp_filename, MYF(MY_WME)); + } + if (temp_buff) + { + my_afree(temp_buff); + } + my_free(sort_param.record,MYF(MY_ALLOW_ZERO_PTR)); + info->opt_flag&= ~(READ_CACHE_USED | WRITE_CACHE_USED); + VOID(end_io_cache(&info->rec_cache)); + my_free(sort_info.buff,MYF(MY_ALLOW_ZERO_PTR)); + sort_info.buff=0; + share->state.sortkey=sort_key; + DBUG_RETURN(got_error); +} /* sort_records */ + + +/* Sort records recursive using one index */ + +static int sort_record_index(MARIA_SORT_PARAM *sort_param, + MARIA_PAGE *ma_page, uint sort_key, + File new_file,my_bool update_index) +{ + MARIA_HA *info= ma_page->info; + MARIA_SHARE *share= info->s; + uint page_flag, nod_flag,used_length; + uchar *temp_buff,*keypos,*endpos; + my_off_t next_page,rec_pos; + uchar lastkey[MARIA_MAX_KEY_BUFF]; + char llbuff[22]; + MARIA_SORT_INFO *sort_info= sort_param->sort_info; + HA_CHECK *param=sort_info->param; + MARIA_KEY tmp_key; + MARIA_PAGE new_page; + const MARIA_KEYDEF *keyinfo= ma_page->keyinfo; + DBUG_ENTER("sort_record_index"); + + page_flag= ma_page->flag; + nod_flag= ma_page->node; + temp_buff=0; + tmp_key.keyinfo= (MARIA_KEYDEF*) keyinfo; + tmp_key.data= lastkey; + + if (nod_flag) + { + if (!(temp_buff= (uchar*) my_alloca(tmp_key.keyinfo->block_length))) + { + _ma_check_print_error(param,"Not Enough memory"); + DBUG_RETURN(-1); + } + } + used_length= ma_page->size; + keypos= ma_page->buff + share->keypage_header + nod_flag; + endpos= ma_page->buff + used_length; + for ( ;; ) + { + _sanity(__FILE__,__LINE__); + if (nod_flag) + { + next_page= _ma_kpos(nod_flag, keypos); + if (my_pread(share->kfile.file, temp_buff, + (uint) tmp_key.keyinfo->block_length, next_page, + MYF(MY_NABP+MY_WME))) + { + _ma_check_print_error(param,"Can't read keys from filepos: %s", + llstr(next_page,llbuff)); + goto err; + } + _ma_page_setup(&new_page, info, ma_page->keyinfo, next_page, temp_buff); + + if (sort_record_index(sort_param, &new_page, sort_key, + new_file, update_index)) + goto err; + } + _sanity(__FILE__,__LINE__); + if (keypos >= endpos || + !(*keyinfo->get_key)(&tmp_key, page_flag, nod_flag, &keypos)) + break; + rec_pos= _ma_row_pos_from_key(&tmp_key); + + if ((*share->read_record)(info,sort_param->record,rec_pos)) + { + _ma_check_print_error(param,"%d when reading datafile",my_errno); + goto err; + } + if (rec_pos != sort_param->filepos && update_index) + { + _ma_dpointer(share, keypos - nod_flag - tmp_key.ref_length, + sort_param->filepos); + if (maria_movepoint(info,sort_param->record,rec_pos,sort_param->filepos, + sort_key)) + { + _ma_check_print_error(param,"%d when updating key-pointers",my_errno); + goto err; + } + } + if (_ma_sort_write_record(sort_param)) + goto err; + } + /* Clear end of block to get better compression if the table is backuped */ + bzero(ma_page->buff + used_length, keyinfo->block_length - used_length); + if (my_pwrite(share->kfile.file, ma_page->buff, (uint)keyinfo->block_length, + ma_page->pos, param->myf_rw)) + { + _ma_check_print_error(param,"%d when updating keyblock",my_errno); + goto err; + } + if (temp_buff) + my_afree(temp_buff); + DBUG_RETURN(0); +err: + if (temp_buff) + my_afree(temp_buff); + DBUG_RETURN(1); +} /* sort_record_index */ + + +static my_bool write_log_record(HA_CHECK *param) +{ + /* + Now that all operations including O_NEW_DATA|INDEX are successfully + done, we can write a log record. + */ + MARIA_HA *info= maria_open(param->isam_file_name, O_RDWR, 0); + if (info == NULL) + _ma_check_print_error(param, default_open_errmsg, my_errno, + param->isam_file_name); + else + { + if (write_log_record_for_repair(param, info)) + _ma_check_print_error(param, "%d when writing log record for" + " MARIA-table '%s'", my_errno, + param->isam_file_name); + else if (maria_close(info)) + _ma_check_print_error(param, default_close_errmsg, my_errno, + param->isam_file_name); + else + return FALSE; + } + return TRUE; +} + +#include "ma_check_standalone.h" diff --git a/storage/maria/maria_def.h b/storage/maria/maria_def.h new file mode 100644 index 00000000000..9385c8602b0 --- /dev/null +++ b/storage/maria/maria_def.h @@ -0,0 +1,1234 @@ +/* Copyright (C) 2006 MySQL AB & MySQL Finland AB & TCX DataKonsult 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 is included by all internal maria files */ + +#include "maria.h" /* Structs & some defines */ +#include <myisampack.h> /* packing of keys */ +#include <my_tree.h> +#include <my_bitmap.h> +#ifdef THREAD +#include <my_pthread.h> +#include <thr_lock.h> +#else +#include <my_no_pthread.h> +#endif +#include <hash.h> +#include "ma_loghandler.h" +#include "ma_control_file.h" +#include "ma_state.h" +#include <waiting_threads.h> + +/* For testing recovery */ +#ifdef TO_BE_REMOVED +#define IDENTICAL_PAGES_AFTER_RECOVERY 1 +#endif +/* Do extra sanity checking */ +#define SANITY_CHECKS 1 +#ifdef EXTRA_DEBUG +#define EXTRA_DEBUG_KEY_CHANGES +#endif + +#define MAX_NONMAPPED_INSERTS 1000 +#define MARIA_MAX_TREE_LEVELS 32 + +/* maria_open() flag, specific for maria_pack */ +#define HA_OPEN_IGNORE_MOVED_STATE (1U << 30) + +struct st_transaction; + +/* undef map from my_nosys; We need test-if-disk full */ +#undef my_write + +#define CRC_SIZE 4 + +typedef struct st_maria_state_info +{ + struct + { /* Fileheader (24 bytes) */ + uchar file_version[4]; + uchar options[2]; + uchar header_length[2]; + uchar state_info_length[2]; + uchar base_info_length[2]; + uchar base_pos[2]; + uchar key_parts[2]; /* Key parts */ + uchar unique_key_parts[2]; /* Key parts + unique parts */ + uchar keys; /* number of keys in file */ + uchar uniques; /* number of UNIQUE definitions */ + uchar language; /* Language for indexes */ + uchar fulltext_keys; + uchar data_file_type; + /* Used by mariapack to store the original data_file_type */ + uchar org_data_file_type; + } header; + + MARIA_STATUS_INFO state; + /* maria_ha->state points here for crash-safe but not versioned tables */ + MARIA_STATUS_INFO common; + ha_rows split; /* number of split blocks */ + my_off_t dellink; /* Link to next removed block */ + pgcache_page_no_t first_bitmap_with_space; + ulonglong auto_increment; + TrID create_trid; /* Minum trid for file */ + ulong update_count; /* Updated for each write lock */ + ulong status; + double *rec_per_key_part; + ulong *nulls_per_key_part; + ha_checksum checksum; /* Table checksum */ + my_off_t *key_root; /* Start of key trees */ + my_off_t key_del; /* delete links for index pages */ + my_off_t records_at_analyze; /* Rows when calculating rec_per_key */ + + ulong sec_index_changed; /* Updated when new sec_index */ + ulong sec_index_used; /* which extra index are in use */ + ulonglong key_map; /* Which keys are in use */ + ulong version; /* timestamp of create */ + time_t create_time; /* Time when created database */ + time_t recover_time; /* Time for last recover */ + time_t check_time; /* Time for last check */ + uint sortkey; /* sorted by this key (not used) */ + uint open_count; + uint changed; /* Changed since maria_chk */ + /** + Birthday of the table: no record in the log before this LSN should ever + be applied to the table. Updated when created, renamed, explicitely + repaired (REPAIR|OPTIMIZE TABLE, ALTER TABLE ENABLE KEYS, maria_chk). + */ + LSN create_rename_lsn; + /** @brief Log horizon when state was last updated on disk */ + TRANSLOG_ADDRESS is_of_horizon; + /** + REDO phase should ignore any record before this LSN. UNDO phase + shouldn't, this is the difference with create_rename_lsn. + skip_redo_lsn >= create_rename_lsn. + The distinction is for these cases: + - after a repair at end of bulk insert (enabling indices), REDO phase + should skip the table but UNDO phase should not, so only skip_redo_lsn is + increased, not create_rename_lsn + - if one table is corrupted and so recovery fails, user may repair the + table with maria_chk and let recovery restart: that recovery should then + skip the repaired table even in the UNDO phase, so create_rename_lsn is + increased. + */ + LSN skip_redo_lsn; + + /* the following isn't saved on disk */ + uint state_diff_length; /* Should be 0 */ + uint state_length; /* Length of state header in file */ + ulong *key_info; +} MARIA_STATE_INFO; + + +#define MARIA_STATE_INFO_SIZE \ + (24 + 2 + LSN_STORE_SIZE*3 + 4 + 11*8 + 4*4 + 8 + 3*4 + 5*8) +#define MARIA_FILE_OPEN_COUNT_OFFSET 0 +#define MARIA_FILE_CHANGED_OFFSET 2 +#define MARIA_FILE_CREATE_RENAME_LSN_OFFSET 4 +#define MARIA_FILE_CREATE_TRID_OFFSET (4 + LSN_STORE_SIZE*3 + 11*8) + +#define MARIA_STATE_KEY_SIZE (8 + 4) +#define MARIA_STATE_KEYBLOCK_SIZE 8 +#define MARIA_STATE_KEYSEG_SIZE 12 +#define MARIA_STATE_EXTRA_SIZE (MARIA_MAX_KEY*MARIA_STATE_KEY_SIZE + MARIA_MAX_KEY*HA_MAX_KEY_SEG*MARIA_STATE_KEYSEG_SIZE) +#define MARIA_KEYDEF_SIZE (2+ 5*2) +#define MARIA_UNIQUEDEF_SIZE (2+1+1) +#define HA_KEYSEG_SIZE (6+ 2*2 + 4*2) +#define MARIA_MAX_KEY_BUFF (HA_MAX_KEY_BUFF + MARIA_MAX_PACK_TRANSID_SIZE) +#define MARIA_COLUMNDEF_SIZE (2*7+1+1+4) +#define MARIA_BASE_INFO_SIZE (MY_UUID_SIZE + 5*8 + 6*4 + 11*2 + 6 + 5*2 + 1 + 16) +#define MARIA_INDEX_BLOCK_MARGIN 16 /* Safety margin for .MYI tables */ +/* Internal management bytes needed to store 2 transid/key on an index page */ +#define MARIA_MAX_PACK_TRANSID_SIZE (TRANSID_SIZE+1) +#define MARIA_TRANSID_PACK_OFFSET (256- TRANSID_SIZE - 1) +#define MARIA_MIN_TRANSID_PACK_OFFSET (MARIA_TRANSID_PACK_OFFSET-TRANSID_SIZE) +#define MARIA_INDEX_OVERHEAD_SIZE (MARIA_MAX_PACK_TRANSID_SIZE * 2) +#define MARIA_DELETE_KEY_NR 255 /* keynr for deleted blocks */ + +/* + Basic information of the Maria table. This is stored on disk + and not changed (unless we do DLL changes). +*/ + +typedef struct st_ma_base_info +{ + my_off_t keystart; /* Start of keys */ + my_off_t max_data_file_length; + my_off_t max_key_file_length; + my_off_t margin_key_file_length; + ha_rows records, reloc; /* Create information */ + ulong mean_row_length; /* Create information */ + ulong reclength; /* length of unpacked record */ + ulong pack_reclength; /* Length of full packed rec */ + ulong min_pack_length; + ulong max_pack_length; /* Max possibly length of packed rec */ + ulong min_block_length; + uint fields; /* fields in table */ + uint fixed_not_null_fields; + uint fixed_not_null_fields_length; + uint max_field_lengths; + uint pack_fields; /* packed fields in table */ + uint varlength_fields; /* char/varchar/blobs */ + /* Number of bytes in the index used to refer to a row (2-8) */ + uint rec_reflength; + /* Number of bytes in the index used to refer to another index page (2-8) */ + uint key_reflength; /* = 2-8 */ + uint keys; /* same as in state.header */ + uint auto_key; /* Which key-1 is a auto key */ + uint blobs; /* Number of blobs */ + /* Length of packed bits (when table was created first time) */ + uint pack_bytes; + /* Length of null bits (when table was created first time) */ + uint original_null_bytes; + uint null_bytes; /* Null bytes in record */ + uint field_offsets; /* Number of field offsets */ + uint max_key_block_length; /* Max block length */ + uint max_key_length; /* Max key length */ + /* Extra allocation when using dynamic record format */ + uint extra_alloc_bytes; + uint extra_alloc_procent; + uint is_nulls_extended; /* 1 if new null bytes */ + uint default_row_flag; /* 0 or ROW_FLAG_NULLS_EXTENDED */ + uint block_size; + /* Size of initial record buffer */ + uint default_rec_buff_size; + /* Extra number of bytes the row format require in the record buffer */ + uint extra_rec_buff_size; + /* Tuning flags that can be ignored by older Maria versions */ + uint extra_options; + + /* The following are from the header */ + uint key_parts, all_key_parts; + uchar uuid[MY_UUID_SIZE]; + /** + @brief If false, we disable logging, versioning, transaction etc. Observe + difference with MARIA_SHARE::now_transactional + */ + my_bool born_transactional; +} MARIA_BASE_INFO; + + +/* Structs used intern in database */ + +typedef struct st_maria_blob /* Info of record */ +{ + ulong offset; /* Offset to blob in record */ + uint pack_length; /* Type of packed length */ + ulong length; /* Calc:ed for each record */ +} MARIA_BLOB; + + +typedef struct st_maria_pack +{ + ulong header_length; + uint ref_length; + uchar version; +} MARIA_PACK; + +typedef struct st_maria_file_bitmap +{ + uchar *map; + pgcache_page_no_t page; /* Page number for current bitmap */ + uint used_size; /* Size of bitmap head that is not 0 */ + my_bool changed; /* 1 if page needs to be flushed */ + my_bool flush_all_requested; /**< If _ma_bitmap_flush_all waiting */ + uint non_flushable; /**< 0 if bitmap and log are in sync */ + PAGECACHE_FILE file; /* datafile where bitmap is stored */ + +#ifdef THREAD + pthread_mutex_t bitmap_lock; + pthread_cond_t bitmap_cond; /**< When bitmap becomes flushable */ +#endif + /* Constants, allocated when initiating bitmaps */ + uint sizes[8]; /* Size per bit combination */ + uint total_size; /* Total usable size of bitmap page */ + uint block_size; /* Block size of file */ + ulong pages_covered; /* Pages covered by bitmap + 1 */ + DYNAMIC_ARRAY pinned_pages; /**< not-yet-flushable bitmap pages */ +} MARIA_FILE_BITMAP; + +#define MARIA_CHECKPOINT_LOOKS_AT_ME 1 +#define MARIA_CHECKPOINT_SHOULD_FREE_ME 2 +#define MARIA_CHECKPOINT_SEEN_IN_LOOP 4 + +typedef struct st_maria_share +{ /* Shared between opens */ + MARIA_STATE_INFO state; + MARIA_BASE_INFO base; + MARIA_STATE_HISTORY *state_history; + MARIA_KEYDEF ft2_keyinfo; /* Second-level ft-key definition */ + MARIA_KEYDEF *keyinfo; /* Key definitions */ + MARIA_UNIQUEDEF *uniqueinfo; /* unique definitions */ + HA_KEYSEG *keyparts; /* key part info */ + MARIA_COLUMNDEF *columndef; /* Pointer to column information */ + MARIA_PACK pack; /* Data about packed records */ + MARIA_BLOB *blobs; /* Pointer to blobs */ + uint16 *column_nr; /* Original column order */ + LEX_STRING unique_file_name; /* realpath() of index file */ + LEX_STRING data_file_name; /* Resolved path names from symlinks */ + LEX_STRING index_file_name; + LEX_STRING open_file_name; /* parameter to open filename */ + uchar *file_map; /* mem-map of file if possible */ + PAGECACHE *pagecache; /* ref to the current key cache */ + MARIA_DECODE_TREE *decode_trees; + /* + Previous auto-increment value. Used to verify if we can restore the + auto-increment counter if we have to abort an insert (duplicate key). + */ + ulonglong last_auto_increment; + uint16 *decode_tables; + uint16 id; /**< 2-byte id by which log records refer to the table */ + /* Called the first time the table instance is opened */ + my_bool (*once_init)(struct st_maria_share *, File); + /* Called when the last instance of the table is closed */ + my_bool (*once_end)(struct st_maria_share *); + /* Is called for every open of the table */ + my_bool (*init)(MARIA_HA *); + /* Is called for every close of the table */ + void (*end)(MARIA_HA *); + /* Called when we want to read a record from a specific position */ + int (*read_record)(MARIA_HA *, uchar *, MARIA_RECORD_POS); + /* Initialize a scan */ + my_bool (*scan_init)(MARIA_HA *); + /* Read next record while scanning */ + int (*scan)(MARIA_HA *, uchar *, MARIA_RECORD_POS, my_bool); + /* End scan */ + void (*scan_end)(MARIA_HA *); + int (*scan_remember_pos)(MARIA_HA *, MARIA_RECORD_POS*); + void (*scan_restore_pos)(MARIA_HA *, MARIA_RECORD_POS); + /* Pre-write of row (some handlers may do the actual write here) */ + MARIA_RECORD_POS (*write_record_init)(MARIA_HA *, const uchar *); + /* Write record (or accept write_record_init) */ + my_bool (*write_record)(MARIA_HA *, const uchar *); + /* Called when write failed */ + my_bool (*write_record_abort)(MARIA_HA *); + my_bool (*update_record)(MARIA_HA *, MARIA_RECORD_POS, + const uchar *, const uchar *); + my_bool (*delete_record)(MARIA_HA *, const uchar *record); + my_bool (*compare_record)(MARIA_HA *, const uchar *); + /* calculate checksum for a row */ + ha_checksum(*calc_checksum)(MARIA_HA *, const uchar *); + /* + Calculate checksum for a row during write. May be 0 if we calculate + the checksum in write_record_init() + */ + ha_checksum(*calc_write_checksum)(MARIA_HA *, const uchar *); + /* calculate checksum for a row during check table */ + ha_checksum(*calc_check_checksum)(MARIA_HA *, const uchar *); + /* Compare a row in memory with a row on disk */ + my_bool (*compare_unique)(MARIA_HA *, MARIA_UNIQUEDEF *, + const uchar *record, MARIA_RECORD_POS pos); + my_off_t (*keypos_to_recpos)(struct st_maria_share *share, my_off_t pos); + my_off_t (*recpos_to_keypos)(struct st_maria_share *share, my_off_t pos); + my_bool (*row_is_visible)(MARIA_HA *); + + /* Mapings to read/write the data file */ + size_t (*file_read)(MARIA_HA *, uchar *, size_t, my_off_t, myf); + size_t (*file_write)(MARIA_HA *, const uchar *, size_t, my_off_t, myf); + invalidator_by_filename invalidator; /* query cache invalidator */ + my_off_t key_del_current; /* delete links for index pages */ + ulong this_process; /* processid */ + ulong last_process; /* For table-change-check */ + ulong last_version; /* Version on start */ + ulong options; /* Options used */ + ulong min_pack_length; /* These are used by packed data */ + ulong max_pack_length; + ulong state_diff_length; + uint rec_reflength; /* rec_reflength in use now */ + uint keypage_header; + uint32 ftkeys; /* Number of distinct full-text keys + + 1 */ + PAGECACHE_FILE kfile; /* Shared keyfile */ + File data_file; /* Shared data file */ + int mode; /* mode of file on open */ + uint reopen; /* How many times opened */ + uint in_trans; /* Number of references by trn */ + uint w_locks, r_locks, tot_locks; /* Number of read/write locks */ + uint block_size; /* block_size of keyfile & data file*/ + /* Fixed length part of a packed row in BLOCK_RECORD format */ + uint base_length; + myf write_flag; + enum data_file_type data_file_type; + enum pagecache_page_type page_type; /* value depending transactional */ + /** + if Checkpoint looking at table; protected by close_lock or THR_LOCK_maria + */ + uint8 in_checkpoint; + my_bool temporary; + /* Below flag is needed to make log tables work with concurrent insert */ + my_bool is_log_table; + + my_bool changed, /* If changed since lock */ + global_changed, /* If changed since open */ + not_flushed; + my_bool lock_key_trees; /* If we have to lock trees on read */ + my_bool non_transactional_concurrent_insert; + my_bool delay_key_write; + my_bool have_rtree; + /** + @brief if the table is transactional right now. It may have been created + transactional (base.born_transactional==TRUE) but with transactionality + (logging) temporarily disabled (now_transactional==FALSE). The opposite + (FALSE, TRUE) is impossible. + */ + my_bool now_transactional; + my_bool have_versioning; + my_bool key_del_used; /* != 0 if key_del is locked */ +#ifdef THREAD + THR_LOCK lock; + void (*lock_restore_status)(void *); + /** + Protects kfile, dfile, most members of the state, state disk writes, + versioning information (like in_trans, state_history). + @todo find the exhaustive list. + */ + pthread_mutex_t intern_lock; + pthread_mutex_t key_del_lock; + pthread_cond_t key_del_cond; + /** + _Always_ held while closing table; prevents checkpoint from looking at + structures freed during closure (like bitmap). If you need close_lock and + intern_lock, lock them in this order. + */ + pthread_mutex_t close_lock; +#endif + my_off_t mmaped_length; + uint nonmmaped_inserts; /* counter of writing in + non-mmaped area */ + MARIA_FILE_BITMAP bitmap; + rw_lock_t mmap_lock; + LSN lsn_of_file_id; /**< LSN of its last LOGREC_FILE_ID */ +} MARIA_SHARE; + + +typedef uchar MARIA_BITMAP_BUFFER; + +typedef struct st_maria_bitmap_block +{ + pgcache_page_no_t page; /* Page number */ + /* Number of continuous pages. TAIL_BIT is set if this is a tail page */ + uint page_count; + uint empty_space; /* Set for head and tail pages */ + /* + Number of BLOCKS for block-region (holds all non-blob-fields or one blob) + */ + uint sub_blocks; + /* set to <> 0 in write_record() if this block was actually used */ + uint8 used; + uint8 org_bitmap_value; +} MARIA_BITMAP_BLOCK; + + +typedef struct st_maria_bitmap_blocks +{ + MARIA_BITMAP_BLOCK *block; + uint count; + my_bool tail_page_skipped; /* If some tail pages was not used */ + my_bool page_skipped; /* If some full pages was not used */ +} MARIA_BITMAP_BLOCKS; + + +/* Data about the currently read row */ +typedef struct st_maria_row +{ + MARIA_BITMAP_BLOCKS insert_blocks; + MARIA_BITMAP_BUFFER *extents; + MARIA_RECORD_POS lastpos, nextpos; + MARIA_RECORD_POS *tail_positions; + ha_checksum checksum; + LSN orig_undo_lsn; /* Lsn at start of row insert */ + TrID trid; /* Transaction id for current row */ + uchar *empty_bits, *field_lengths; + uint *null_field_lengths; /* All null field lengths */ + ulong *blob_lengths; /* Length for each blob */ + ulong min_length, normal_length, char_length, varchar_length; + ulong blob_length, head_length, total_length; + size_t extents_buffer_length; /* Size of 'extents' buffer */ + uint field_lengths_length; /* Length of data in field_lengths */ + uint extents_count; /* number of extents in 'extents' */ + uint full_page_count, tail_count; /* For maria_chk */ + uint space_on_head_page; +} MARIA_ROW; + +/* Data to scan row in blocked format */ +typedef struct st_maria_block_scan +{ + uchar *bitmap_buff, *bitmap_pos, *bitmap_end, *page_buff; + uchar *dir, *dir_end; + pgcache_page_no_t bitmap_page; + ulonglong bits; + uint number_of_rows, bit_pos; + MARIA_RECORD_POS row_base_page; +} MARIA_BLOCK_SCAN; + + +struct st_maria_handler +{ + MARIA_SHARE *s; /* Shared between open:s */ + struct st_ma_transaction *trn; /* Pointer to active transaction */ + MARIA_STATUS_INFO *state, state_save; + MARIA_STATUS_INFO *state_start; /* State at start of transaction */ + MARIA_ROW cur_row; /* The active row that we just read */ + MARIA_ROW new_row; /* Storage for a row during update */ + MARIA_KEY last_key; /* Last found key */ + MARIA_BLOCK_SCAN scan, *scan_save; + MARIA_BLOB *blobs; /* Pointer to blobs */ + MARIA_BIT_BUFF bit_buff; + DYNAMIC_ARRAY bitmap_blocks; + DYNAMIC_ARRAY pinned_pages; + /* accumulate indexfile changes between write's */ + TREE *bulk_insert; + LEX_CUSTRING *log_row_parts; /* For logging */ + DYNAMIC_ARRAY *ft1_to_ft2; /* used only in ft1->ft2 conversion */ + MEM_ROOT ft_memroot; /* used by the parser */ + MYSQL_FTPARSER_PARAM *ftparser_param; /* share info between init/deinit */ + uchar *buff; /* page buffer */ + uchar *keyread_buff; /* Buffer for last key read */ + uchar *lastkey_buff; /* Last used search key */ + uchar *lastkey_buff2; + uchar *first_mbr_key; /* Searhed spatial key */ + uchar *rec_buff; /* Temp buffer for recordpack */ + uchar *blob_buff; /* Temp buffer for blobs */ + uchar *int_keypos, /* Save position for next/previous */ + *int_maxpos; /* -""- */ + uchar *update_field_data; /* Used by update in rows-in-block */ + uint int_nod_flag; /* -""- */ + uint32 int_keytree_version; /* -""- */ + int (*read_record)(MARIA_HA *, uchar*, MARIA_RECORD_POS); + invalidator_by_filename invalidator; /* query cache invalidator */ + ulonglong last_auto_increment; /* auto value at start of statement */ + ulong this_unique; /* uniq filenumber or thread */ + ulong last_unique; /* last unique number */ + ulong this_loop; /* counter for this open */ + ulong last_loop; /* last used counter */ + MARIA_RECORD_POS save_lastpos; + MARIA_RECORD_POS dup_key_pos; + TrID dup_key_trid; + my_off_t pos; /* Intern variable */ + my_off_t last_keypage; /* Last key page read */ + my_off_t last_search_keypage; /* Last keypage when searching */ + + /* + QQ: the folloing two xxx_length fields should be removed, + as they are not compatible with parallel repair + */ + ulong packed_length, blob_length; /* Length of found, packed record */ + size_t rec_buff_size, blob_buff_size; + PAGECACHE_FILE dfile; /* The datafile */ + IO_CACHE rec_cache; /* When cacheing records */ + LIST open_list; + MY_BITMAP changed_fields; + ulong row_base_length; /* Length of row header */ + uint row_flag; /* Flag to store in row header */ + uint opt_flag; /* Optim. for space/speed */ + uint update; /* If file changed since open */ + int lastinx; /* Last used index */ + uint last_rkey_length; /* Last length in maria_rkey() */ + uint *last_rtree_keypos; /* Last key positions for rtrees */ + uint bulk_insert_ref_length; /* Lenght of row ref during bi */ + uint non_flushable_state; + enum ha_rkey_function last_key_func; /* CONTAIN, OVERLAP, etc */ + uint save_lastkey_data_length; + uint save_lastkey_ref_length; + uint pack_key_length; /* For MARIA_MRG */ + myf lock_wait; /* is 0 or MY_SHORT_WAIT */ + int errkey; /* Got last error on this key */ + int lock_type; /* How database was locked */ + int tmp_lock_type; /* When locked by readinfo */ + uint data_changed; /* Somebody has changed data */ + uint save_update; /* When using KEY_READ */ + int save_lastinx; + uint preload_buff_size; /* When preloading indexes */ + uint16 last_used_keyseg; /* For MARIAMRG */ + uint8 key_del_used; /* != 0 if key_del is used */ + my_bool was_locked; /* Was locked in panic */ + my_bool append_insert_at_end; /* Set if concurrent insert */ + my_bool quick_mode; + /* Marker if key_del_changed */ + /* If info->keyread_buff can't be used for rnext */ + my_bool page_changed; + /* If info->keyread_buff has to be re-read for rnext */ + my_bool keyread_buff_used; + my_bool once_flags; /* For MARIA_MRG */ + /* For bulk insert enable/disable transactions control */ + my_bool switched_transactional; +#ifdef __WIN__ + my_bool owned_by_merge; /* This Maria table is part of a merge union */ +#endif +#ifdef THREAD + THR_LOCK_DATA lock; +#endif + uchar *maria_rtree_recursion_state; /* For RTREE */ + uchar length_buff[5]; /* temp buff to store blob lengths */ + int maria_rtree_recursion_depth; +}; + +/* Some defines used by maria-functions */ + +#define USE_WHOLE_KEY 65535 /* Use whole key in _search() */ +#define F_EXTRA_LCK -1 + +/* bits in opt_flag */ +#define MEMMAP_USED 32 +#define REMEMBER_OLD_POS 64 + +#define WRITEINFO_UPDATE_KEYFILE 1 +#define WRITEINFO_NO_UNLOCK 2 + +/* once_flags */ +#define USE_PACKED_KEYS 1 +#define RRND_PRESERVE_LASTINX 2 + +/* bits in state.changed */ + +#define STATE_CHANGED 1 +#define STATE_CRASHED 2 +#define STATE_CRASHED_ON_REPAIR 4 +#define STATE_NOT_ANALYZED 8 +#define STATE_NOT_OPTIMIZED_KEYS 16 +#define STATE_NOT_SORTED_PAGES 32 +#define STATE_NOT_OPTIMIZED_ROWS 64 +#define STATE_NOT_ZEROFILLED 128 +#define STATE_NOT_MOVABLE 256 +#define STATE_MOVED 512 /* set if base->uuid != maria_uuid */ + +/* options to maria_read_cache */ + +#define READING_NEXT 1 +#define READING_HEADER 2 + +/* Number of bytes on key pages to indicate used size */ +#define KEYPAGE_USED_SIZE 2 +#define KEYPAGE_KEYID_SIZE 1 +#define KEYPAGE_FLAG_SIZE 1 +#define KEYPAGE_CHECKSUM_SIZE 4 +#define MAX_KEYPAGE_HEADER_SIZE (LSN_STORE_SIZE + KEYPAGE_USED_SIZE + \ + KEYPAGE_KEYID_SIZE + KEYPAGE_FLAG_SIZE + \ + TRANSID_SIZE) +#define KEYPAGE_FLAG_ISNOD 1 +#define KEYPAGE_FLAG_HAS_TRANSID 2 +/* Position to KEYPAGE_FLAG for transactional tables */ +#define KEYPAGE_TRANSFLAG_OFFSET LSN_STORE_SIZE + TRANSID_SIZE + KEYPAGE_KEYID_SIZE + +#define _ma_get_page_used(share,x) \ + ((uint) mi_uint2korr((x) + (share)->keypage_header - KEYPAGE_USED_SIZE)) +#define _ma_store_page_used(share,x,y) \ + mi_int2store((x) + (share)->keypage_header - KEYPAGE_USED_SIZE, (y)) +#define _ma_get_keypage_flag(share,x) x[(share)->keypage_header - KEYPAGE_USED_SIZE - KEYPAGE_FLAG_SIZE] +#define _ma_test_if_nod(share,x) \ + ((_ma_get_keypage_flag(share,x) & KEYPAGE_FLAG_ISNOD) ? (share)->base.key_reflength : 0) + +#define _ma_store_keynr(share, x, nr) x[(share)->keypage_header - KEYPAGE_KEYID_SIZE - KEYPAGE_FLAG_SIZE - KEYPAGE_USED_SIZE]= (nr) +#define _ma_get_keynr(share, x) ((uchar) x[(share)->keypage_header - KEYPAGE_KEYID_SIZE - KEYPAGE_FLAG_SIZE - KEYPAGE_USED_SIZE]) +#define _ma_store_transid(buff, transid) \ + transid_store((buff) + LSN_STORE_SIZE, (transid)) +#define _ma_korr_transid(buff) \ + transid_korr((buff) + LSN_STORE_SIZE) +#define _ma_store_keypage_flag(share,x,flag) x[(share)->keypage_header - KEYPAGE_USED_SIZE - KEYPAGE_FLAG_SIZE]= (flag) +#define _ma_mark_page_with_transid(share, page) \ + (page)->flag|= KEYPAGE_FLAG_HAS_TRANSID; \ + (page)->buff[(share)->keypage_header - KEYPAGE_USED_SIZE - KEYPAGE_FLAG_SIZE]= (page)->flag; + + +/* + TODO: write int4store_aligned as *((uint32 *) (T))= (uint32) (A) for + architectures where it is possible +*/ +#define int4store_aligned(A,B) int4store((A),(B)) + +#define maria_mark_crashed(x) do{(x)->s->state.changed|= STATE_CRASHED; \ + DBUG_PRINT("error", ("Marked table crashed")); \ + }while(0) +#define maria_mark_crashed_share(x) \ + do{(x)->state.changed|= STATE_CRASHED; \ + DBUG_PRINT("error", ("Marked table crashed")); \ + }while(0) +#define maria_mark_crashed_on_repair(x) do{(x)->s->state.changed|= \ + STATE_CRASHED|STATE_CRASHED_ON_REPAIR; \ + (x)->update|= HA_STATE_CHANGED; \ + DBUG_PRINT("error", \ + ("Marked table crashed")); \ + }while(0) +#define maria_is_crashed(x) ((x)->s->state.changed & STATE_CRASHED) +#define maria_is_crashed_on_repair(x) ((x)->s->state.changed & STATE_CRASHED_ON_REPAIR) +#ifdef EXTRA_DEBUG +/** + Brings additional information in certain debug builds and in standalone + (non-ha_maria) programs. To help debugging. Not in ha_maria, to not spam the + user (some messages can be produced many times per statement, or even + wrongly during some repair operations). +*/ +#define maria_print_error(SHARE, ERRNO) \ + do{ if (!maria_in_ha_maria) \ + _ma_report_error((ERRNO), &(SHARE)->index_file_name); } \ + while(0) +#else +#define maria_print_error(SHARE, ERRNO) while (0) +#endif +#define DBUG_DUMP_KEY(name, key) DBUG_DUMP(name, (key)->data, (key)->data_length + (key)->ref_length) + + +/* Functions to store length of space packed keys, VARCHAR or BLOB keys */ + +#define store_key_length(key,length) \ +{ if ((length) < 255) \ + { *(key)=(length); } \ + else \ + { *(key)=255; mi_int2store((key)+1,(length)); } \ +} + +#define get_key_full_length(length,key) \ + { if (*(const uchar*) (key) != 255) \ + length= ((uint) *(const uchar*) ((key)++))+1; \ + else \ + { length=mi_uint2korr((key)+1)+3; (key)+=3; } \ +} + +#define get_key_full_length_rdonly(length,key) \ +{ if (*(const uchar*) (key) != 255) \ + length= ((uint) *(const uchar*) ((key)))+1; \ + else \ + { length=mi_uint2korr((key)+1)+3; } \ +} + +#define maria_max_key_length() ((maria_block_size - MAX_KEYPAGE_HEADER_SIZE)/2 - MARIA_INDEX_OVERHEAD_SIZE) +#define get_pack_length(length) ((length) >= 255 ? 3 : 1) +#define _ma_have_versioning(info) ((info)->row_flag & ROW_FLAG_TRANSID) + +/** + Sets table's trn and prints debug information + @param tbl MARIA_HA of table + @param newtrn what to put into tbl->trn + @note cast of newtrn is because %p of NULL gives warning (NULL is int) +*/ +#define _ma_set_trn_for_table(tbl, newtrn) do { \ + DBUG_PRINT("info",("table: %p trn: %p -> %p", \ + (tbl), (tbl)->trn, (void *)(newtrn))); \ + (tbl)->trn= (newtrn); \ + } while (0) + + +#define MARIA_MIN_BLOCK_LENGTH 20 /* Because of delete-link */ +/* Don't use to small record-blocks */ +#define MARIA_EXTEND_BLOCK_LENGTH 20 +#define MARIA_SPLIT_LENGTH ((MARIA_EXTEND_BLOCK_LENGTH+4)*2) + /* Max prefix of record-block */ +#define MARIA_MAX_DYN_BLOCK_HEADER 20 +#define MARIA_BLOCK_INFO_HEADER_LENGTH 20 +#define MARIA_DYN_DELETE_BLOCK_HEADER 20 /* length of delete-block-header */ +#define MARIA_DYN_MAX_BLOCK_LENGTH ((1L << 24)-4L) +#define MARIA_DYN_MAX_ROW_LENGTH (MARIA_DYN_MAX_BLOCK_LENGTH - MARIA_SPLIT_LENGTH) +#define MARIA_DYN_ALIGN_SIZE 4 /* Align blocks on this */ +#define MARIA_MAX_DYN_HEADER_BYTE 13 /* max header uchar for dynamic rows */ +#define MARIA_MAX_BLOCK_LENGTH ((((ulong) 1 << 24)-1) & (~ (ulong) (MARIA_DYN_ALIGN_SIZE-1))) +#define MARIA_REC_BUFF_OFFSET ALIGN_SIZE(MARIA_DYN_DELETE_BLOCK_HEADER+sizeof(uint32)) + +#define MEMMAP_EXTRA_MARGIN 7 /* Write this as a suffix for file */ + +#define PACK_TYPE_SELECTED 1 /* Bits in field->pack_type */ +#define PACK_TYPE_SPACE_FIELDS 2 +#define PACK_TYPE_ZERO_FILL 4 +#define MARIA_FOUND_WRONG_KEY 32738 /* Impossible value from ha_key_cmp */ + +#define MARIA_BLOCK_SIZE(key_length,data_pointer,key_pointer,block_size) (((((key_length)+(data_pointer)+(key_pointer))*4+(key_pointer)+2)/(block_size)+1)*(block_size)) +#define MARIA_MAX_KEYPTR_SIZE 5 /* For calculating block lengths */ + +/* Marker for impossible delete link */ +#define IMPOSSIBLE_PAGE_NO LL(0xFFFFFFFFFF) + +/* The UNIQUE check is done with a hashed long key */ + +#define MARIA_UNIQUE_HASH_TYPE HA_KEYTYPE_ULONG_INT +#define maria_unique_store(A,B) mi_int4store((A),(B)) + +#ifdef THREAD +extern pthread_mutex_t THR_LOCK_maria; +#endif +#if !defined(THREAD) || defined(DONT_USE_RW_LOCKS) +#define rw_wrlock(A) {} +#define rw_rdlock(A) {} +#define rw_unlock(A) {} +#endif + +/* Some tuning parameters */ +#define MARIA_MIN_KEYBLOCK_LENGTH 50 /* When to split delete blocks */ +#define MARIA_MIN_SIZE_BULK_INSERT_TREE 16384 /* this is per key */ +#define MARIA_MIN_ROWS_TO_USE_BULK_INSERT 100 +#define MARIA_MIN_ROWS_TO_DISABLE_INDEXES 100 +#define MARIA_MIN_ROWS_TO_USE_WRITE_CACHE 10 +/* Keep a small buffer for tables only using small blobs */ +#define MARIA_SMALL_BLOB_BUFFER 1024 +#define MARIA_MAX_CONTROL_FILE_LOCK_RETRY 30 /* Retry this many times */ + + +/* Some extern variables */ +extern LIST *maria_open_list; +extern uchar maria_file_magic[], maria_pack_file_magic[]; +extern uchar maria_uuid[MY_UUID_SIZE]; +extern uint32 maria_read_vec[], maria_readnext_vec[]; +extern uint maria_quick_table_bits; +extern char *maria_data_root; +extern uchar maria_zero_string[]; +extern my_bool maria_inited, maria_in_ha_maria; +extern HASH maria_stored_state; + +/* This is used by _ma_calc_xxx_key_length och _ma_store_key */ +typedef struct st_maria_s_param +{ + const uchar *key; + uchar *prev_key, *next_key_pos; + uchar *key_pos; /* For balance page */ + uint ref_length, key_length, n_ref_length; + uint n_length, totlength, part_of_prev_key, prev_length, pack_marker; + uint changed_length; + int move_length; /* For balance_page */ + my_bool store_not_null; +} MARIA_KEY_PARAM; + + +/* Used to store reference to pinned page */ +typedef struct st_pinned_page +{ + PAGECACHE_BLOCK_LINK *link; + enum pagecache_page_lock unlock, write_lock; + my_bool changed; +} MARIA_PINNED_PAGE; + + +/* Keeps all information about a page and related to a page */ +typedef struct st_maria_page +{ + MARIA_HA *info; + const MARIA_KEYDEF *keyinfo; + uchar *buff; /* Data for page */ + my_off_t pos; /* Disk address to page */ + uint size; /* Size of data on page */ + uint node; /* 0 or share->base.key_reflength */ + uint flag; /* Page flag */ + uint link_offset; +} MARIA_PAGE; + + +/* Prototypes for intern functions */ +extern int _ma_read_dynamic_record(MARIA_HA *, uchar *, MARIA_RECORD_POS); +extern int _ma_read_rnd_dynamic_record(MARIA_HA *, uchar *, MARIA_RECORD_POS, + my_bool); +extern my_bool _ma_write_dynamic_record(MARIA_HA *, const uchar *); +extern my_bool _ma_update_dynamic_record(MARIA_HA *, MARIA_RECORD_POS, + const uchar *, const uchar *); +extern my_bool _ma_delete_dynamic_record(MARIA_HA *info, const uchar *record); +extern my_bool _ma_cmp_dynamic_record(MARIA_HA *info, const uchar *record); +extern my_bool _ma_write_blob_record(MARIA_HA *, const uchar *); +extern my_bool _ma_update_blob_record(MARIA_HA *, MARIA_RECORD_POS, + const uchar *, const uchar *); +extern int _ma_read_static_record(MARIA_HA *info, uchar *, MARIA_RECORD_POS); +extern int _ma_read_rnd_static_record(MARIA_HA *, uchar *, MARIA_RECORD_POS, + my_bool); +extern my_bool _ma_write_static_record(MARIA_HA *, const uchar *); +extern my_bool _ma_update_static_record(MARIA_HA *, MARIA_RECORD_POS, + const uchar *, const uchar *); +extern my_bool _ma_delete_static_record(MARIA_HA *info, const uchar *record); +extern my_bool _ma_cmp_static_record(MARIA_HA *info, const uchar *record); +extern my_bool _ma_ck_write(MARIA_HA *info, MARIA_KEY *key); +extern my_bool _ma_enlarge_root(MARIA_HA *info, MARIA_KEY *key, + MARIA_RECORD_POS *root); +int _ma_insert(register MARIA_HA *info, MARIA_KEY *key, + MARIA_PAGE *anc_page, uchar *key_pos, uchar *key_buff, + MARIA_PAGE *father_page, uchar *father_key_pos, + my_bool insert_last); +extern my_bool _ma_ck_real_write_btree(MARIA_HA *info, MARIA_KEY *key, + MARIA_RECORD_POS *root, uint32 comp_flag); +extern int _ma_split_page(MARIA_HA *info, MARIA_KEY *key, + MARIA_PAGE *split_page, + uint org_split_length, + uchar *inserted_key_pos, uint changed_length, + int move_length, + uchar *key_buff, my_bool insert_last_key); +extern uchar *_ma_find_half_pos(MARIA_KEY *key, MARIA_PAGE *page, + uchar ** after_key); +extern int _ma_calc_static_key_length(const MARIA_KEY *key, uint nod_flag, + uchar *key_pos, uchar *org_key, + uchar *key_buff, + MARIA_KEY_PARAM *s_temp); +extern int _ma_calc_var_key_length(const MARIA_KEY *key, uint nod_flag, + uchar *key_pos, uchar *org_key, + uchar *key_buff, + MARIA_KEY_PARAM *s_temp); +extern int _ma_calc_var_pack_key_length(const MARIA_KEY *key, + uint nod_flag, uchar *next_key, + uchar *org_key, uchar *prev_key, + MARIA_KEY_PARAM *s_temp); +extern int _ma_calc_bin_pack_key_length(const MARIA_KEY *key, + uint nod_flag, uchar *next_key, + uchar *org_key, uchar *prev_key, + MARIA_KEY_PARAM *s_temp); +extern void _ma_store_static_key(MARIA_KEYDEF *keyinfo, uchar *key_pos, + MARIA_KEY_PARAM *s_temp); +extern void _ma_store_var_pack_key(MARIA_KEYDEF *keyinfo, uchar *key_pos, + MARIA_KEY_PARAM *s_temp); +#ifdef NOT_USED +extern void _ma_store_pack_key(MARIA_KEYDEF *keyinfo, uchar *key_pos, + MARIA_KEY_PARAM *s_temp); +#endif +extern void _ma_store_bin_pack_key(MARIA_KEYDEF *keyinfo, uchar *key_pos, + MARIA_KEY_PARAM *s_temp); + +extern my_bool _ma_ck_delete(MARIA_HA *info, MARIA_KEY *key); +extern my_bool _ma_ck_real_delete(register MARIA_HA *info, MARIA_KEY *key, + my_off_t *root); +extern int _ma_readinfo(MARIA_HA *info, int lock_flag, int check_keybuffer); +extern int _ma_writeinfo(MARIA_HA *info, uint options); +extern int _ma_test_if_changed(MARIA_HA *info); +extern int _ma_mark_file_changed(MARIA_HA *info); +extern void _ma_mark_file_crashed(MARIA_SHARE *share); +extern my_bool _ma_set_uuid(MARIA_HA *info, my_bool reset_uuid); +extern my_bool _ma_check_if_zero(uchar *pos, size_t size); +extern int _ma_decrement_open_count(MARIA_HA *info); +extern int _ma_check_index(MARIA_HA *info, int inx); +extern int _ma_search(MARIA_HA *info, MARIA_KEY *key, uint32 nextflag, + my_off_t pos); +extern int _ma_bin_search(const MARIA_KEY *key, const MARIA_PAGE *page, + uint32 comp_flag, uchar **ret_pos, uchar *buff, + my_bool *was_last_key); +extern int _ma_seq_search(const MARIA_KEY *key, const MARIA_PAGE *page, + uint comp_flag, uchar ** ret_pos, uchar *buff, + my_bool *was_last_key); +extern int _ma_prefix_search(const MARIA_KEY *key, const MARIA_PAGE *page, + uint32 comp_flag, uchar ** ret_pos, uchar *buff, + my_bool *was_last_key); +extern my_off_t _ma_kpos(uint nod_flag, const uchar *after_key); +extern void _ma_kpointer(MARIA_HA *info, uchar *buff, my_off_t pos); +MARIA_RECORD_POS _ma_row_pos_from_key(const MARIA_KEY *key); +TrID _ma_trid_from_key(const MARIA_KEY *key); +extern MARIA_RECORD_POS _ma_rec_pos(MARIA_SHARE *share, uchar *ptr); +extern void _ma_dpointer(MARIA_SHARE *share, uchar *buff, + MARIA_RECORD_POS pos); +extern uint _ma_get_static_key(MARIA_KEY *key, uint page_flag, uint nod_flag, + uchar **page); +extern uchar *_ma_skip_static_key(MARIA_KEY *key, uint page_flag, + uint nod_flag, uchar *page); +extern uint _ma_get_pack_key(MARIA_KEY *key, uint page_flag, uint nod_flag, + uchar **page); +extern uchar *_ma_skip_pack_key(MARIA_KEY *key, uint page_flag, + uint nod_flag, uchar *page); +extern uint _ma_get_binary_pack_key(MARIA_KEY *key, uint page_flag, + uint nod_flag, uchar **page_pos); +uchar *_ma_skip_binary_pack_key(MARIA_KEY *key, uint page_flag, + uint nod_flag, uchar *page); +extern uchar *_ma_get_last_key(MARIA_KEY *key, MARIA_PAGE *page, + uchar *endpos); +extern uchar *_ma_get_key(MARIA_KEY *key, MARIA_PAGE *page, uchar *keypos); +extern uint _ma_keylength(MARIA_KEYDEF *keyinfo, const uchar *key); +extern uint _ma_keylength_part(MARIA_KEYDEF *keyinfo, const uchar *key, + HA_KEYSEG *end); +extern int _ma_search_next(MARIA_HA *info, MARIA_KEY *key, + uint32 nextflag, my_off_t pos); +extern int _ma_search_first(MARIA_HA *info, MARIA_KEYDEF *keyinfo, + my_off_t pos); +extern int _ma_search_last(MARIA_HA *info, MARIA_KEYDEF *keyinfo, + my_off_t pos); +extern my_off_t _ma_static_keypos_to_recpos(MARIA_SHARE *share, my_off_t pos); +extern my_off_t _ma_static_recpos_to_keypos(MARIA_SHARE *share, my_off_t pos); +extern my_off_t _ma_transparent_recpos(MARIA_SHARE *share, my_off_t pos); +extern my_off_t _ma_transaction_keypos_to_recpos(MARIA_SHARE *, my_off_t pos); +extern my_off_t _ma_transaction_recpos_to_keypos(MARIA_SHARE *, my_off_t pos); + +extern void _ma_page_setup(MARIA_PAGE *page, MARIA_HA *info, + const MARIA_KEYDEF *keyinfo, my_off_t pos, + uchar *buff); +extern my_bool _ma_fetch_keypage(MARIA_PAGE *page, MARIA_HA *info, + const MARIA_KEYDEF *keyinfo, + my_off_t pos, enum pagecache_page_lock lock, + int level, uchar *buff, + my_bool return_buffer); +extern my_bool _ma_write_keypage(MARIA_PAGE *page, + enum pagecache_page_lock lock, int level); +extern int _ma_dispose(MARIA_HA *info, my_off_t pos, my_bool page_not_read); +extern my_off_t _ma_new(register MARIA_HA *info, int level, + MARIA_PINNED_PAGE **page_link); +extern my_bool _ma_compact_keypage(MARIA_PAGE *page, TrID min_read_from); +extern uint transid_store_packed(MARIA_HA *info, uchar *to, ulonglong trid); +extern ulonglong transid_get_packed(MARIA_SHARE *share, const uchar *from); +#define transid_packed_length(data) \ + ((data)[0] < MARIA_MIN_TRANSID_PACK_OFFSET ? 1 : \ + (uint) ((uchar) (data)[0]) - (MARIA_TRANSID_PACK_OFFSET - 1)) +#define key_has_transid(key) (*(key) & 1) + +#define page_mark_changed(info, page) \ + dynamic_element(&(info)->pinned_pages, (page)->link_offset, \ + MARIA_PINNED_PAGE*)->changed= 1; +#define page_store_size(share, page) \ + _ma_store_page_used((share), (page)->buff, (page)->size); +#define page_store_info(share, page) \ + _ma_store_keypage_flag((share), (page)->buff, (page)->flag); \ + _ma_store_page_used((share), (page)->buff, (page)->size); + +extern MARIA_KEY *_ma_make_key(MARIA_HA *info, MARIA_KEY *int_key, uint keynr, + uchar *key, const uchar *record, + MARIA_RECORD_POS filepos, ulonglong trid); +extern MARIA_KEY *_ma_pack_key(MARIA_HA *info, MARIA_KEY *int_key, + uint keynr, uchar *key, + const uchar *old, key_part_map keypart_map, + HA_KEYSEG ** last_used_keyseg); +extern void _ma_copy_key(MARIA_KEY *to, const MARIA_KEY *from); +extern int _ma_read_key_record(MARIA_HA *info, uchar *buf, MARIA_RECORD_POS); +extern my_bool _ma_read_cache(IO_CACHE *info, uchar *buff, + MARIA_RECORD_POS pos, size_t length, + uint re_read_if_possibly); +extern ulonglong ma_retrieve_auto_increment(const uchar *key, uint8 key_type); +extern my_bool _ma_alloc_buffer(uchar **old_addr, size_t *old_size, + size_t new_size); +extern ulong _ma_rec_unpack(MARIA_HA *info, uchar *to, uchar *from, + ulong reclength); +extern my_bool _ma_rec_check(MARIA_HA *info, const uchar *record, + uchar *packpos, ulong packed_length, + my_bool with_checkum, ha_checksum checksum); +extern int _ma_write_part_record(MARIA_HA *info, my_off_t filepos, + ulong length, my_off_t next_filepos, + uchar ** record, ulong *reclength, + int *flag); +extern void _ma_print_key(FILE *stream, MARIA_KEY *key); +extern void _ma_print_keydata(FILE *stream, HA_KEYSEG *keyseg, + const uchar *key, uint length); +extern my_bool _ma_once_init_pack_row(MARIA_SHARE *share, File dfile); +extern my_bool _ma_once_end_pack_row(MARIA_SHARE *share); +extern int _ma_read_pack_record(MARIA_HA *info, uchar *buf, + MARIA_RECORD_POS filepos); +extern int _ma_read_rnd_pack_record(MARIA_HA *, uchar *, MARIA_RECORD_POS, + my_bool); +extern int _ma_pack_rec_unpack(MARIA_HA *info, MARIA_BIT_BUFF *bit_buff, + uchar *to, uchar *from, ulong reclength); +extern ulonglong _ma_safe_mul(ulonglong a, ulonglong b); +extern int _ma_ft_update(MARIA_HA *info, uint keynr, uchar *keybuf, + const uchar *oldrec, const uchar *newrec, + my_off_t pos); + +/* + Parameter to _ma_get_block_info + The dynamic row header is read into this struct. For an explanation of + the fields, look at the function _ma_get_block_info(). +*/ + +typedef struct st_maria_block_info +{ + uchar header[MARIA_BLOCK_INFO_HEADER_LENGTH]; + ulong rec_len; + ulong data_len; + ulong block_len; + ulong blob_len; + MARIA_RECORD_POS filepos; + MARIA_RECORD_POS next_filepos; + MARIA_RECORD_POS prev_filepos; + uint second_read; + uint offset; +} MARIA_BLOCK_INFO; + + +/* bits in return from _ma_get_block_info */ + +#define BLOCK_FIRST 1 +#define BLOCK_LAST 2 +#define BLOCK_DELETED 4 +#define BLOCK_ERROR 8 /* Wrong data */ +#define BLOCK_SYNC_ERROR 16 /* Right data at wrong place */ +#define BLOCK_FATAL_ERROR 32 /* hardware-error */ + +#define NEED_MEM ((uint) 10*4*(IO_SIZE+32)+32) /* Nead for recursion */ +#define MAXERR 20 +#define BUFFERS_WHEN_SORTING 16 /* Alloc for sort-key-tree */ +#define WRITE_COUNT MY_HOW_OFTEN_TO_WRITE +#define INDEX_TMP_EXT ".TMM" +#define DATA_TMP_EXT ".TMD" + +#define UPDATE_TIME 1 +#define UPDATE_STAT 2 +#define UPDATE_SORT 4 +#define UPDATE_AUTO_INC 8 +#define UPDATE_OPEN_COUNT 16 + +#define USE_BUFFER_INIT (((1024L*1024L*128-MALLOC_OVERHEAD)/8192)*8192) +#define READ_BUFFER_INIT (1024L*256L-MALLOC_OVERHEAD) +#define SORT_BUFFER_INIT (1024L*1024L*64-MALLOC_OVERHEAD) +#define MIN_SORT_BUFFER (4096-MALLOC_OVERHEAD) + +#define fast_ma_writeinfo(INFO) if (!(INFO)->s->tot_locks) (void) _ma_writeinfo((INFO),0) +#define fast_ma_readinfo(INFO) ((INFO)->lock_type == F_UNLCK) && _ma_readinfo((INFO),F_RDLCK,1) + +extern uint _ma_get_block_info(MARIA_BLOCK_INFO *, File, my_off_t); +extern uint _ma_rec_pack(MARIA_HA *info, uchar *to, const uchar *from); +extern uint _ma_pack_get_block_info(MARIA_HA *maria, MARIA_BIT_BUFF *bit_buff, + MARIA_BLOCK_INFO *info, uchar **rec_buff_p, + size_t *rec_buff_size, + File file, my_off_t filepos); +extern void _ma_store_blob_length(uchar *pos, uint pack_length, uint length); +extern void _ma_report_error(int errcode, const LEX_STRING *file_name); +extern my_bool _ma_memmap_file(MARIA_HA *info); +extern void _ma_unmap_file(MARIA_HA *info); +extern uint _ma_save_pack_length(uint version, uchar * block_buff, + ulong length); +extern uint _ma_calc_pack_length(uint version, ulong length); +extern ulong _ma_calc_blob_length(uint length, const uchar *pos); +extern size_t _ma_mmap_pread(MARIA_HA *info, uchar *Buffer, + size_t Count, my_off_t offset, myf MyFlags); +extern size_t _ma_mmap_pwrite(MARIA_HA *info, const uchar *Buffer, + size_t Count, my_off_t offset, myf MyFlags); +extern size_t _ma_nommap_pread(MARIA_HA *info, uchar *Buffer, + size_t Count, my_off_t offset, myf MyFlags); +extern size_t _ma_nommap_pwrite(MARIA_HA *info, const uchar *Buffer, + size_t Count, my_off_t offset, myf MyFlags); + +/* my_pwrite instead of my_write used */ +#define MA_STATE_INFO_WRITE_DONT_MOVE_OFFSET 1 +/* info should be written */ +#define MA_STATE_INFO_WRITE_FULL_INFO 2 +/* intern_lock taking is needed */ +#define MA_STATE_INFO_WRITE_LOCK 4 +uint _ma_state_info_write(MARIA_SHARE *share, uint pWrite); +uint _ma_state_info_write_sub(File file, MARIA_STATE_INFO *state, uint pWrite); +uint _ma_state_info_read_dsk(File file, MARIA_STATE_INFO *state); +uint _ma_base_info_write(File file, MARIA_BASE_INFO *base); +my_bool _ma_keyseg_write(File file, const HA_KEYSEG *keyseg); +uchar *_ma_keyseg_read(uchar *ptr, HA_KEYSEG *keyseg); +my_bool _ma_keydef_write(File file, MARIA_KEYDEF *keydef); +uchar *_ma_keydef_read(uchar *ptr, MARIA_KEYDEF *keydef); +my_bool _ma_uniquedef_write(File file, MARIA_UNIQUEDEF *keydef); +uchar *_ma_uniquedef_read(uchar *ptr, MARIA_UNIQUEDEF *keydef); +my_bool _ma_columndef_write(File file, MARIA_COLUMNDEF *columndef); +uchar *_ma_columndef_read(uchar *ptr, MARIA_COLUMNDEF *columndef); +my_bool _ma_column_nr_write(File file, uint16 *offsets, uint columns); +uchar *_ma_column_nr_read(uchar *ptr, uint16 *offsets, uint columns); +ulong _ma_calc_total_blob_length(MARIA_HA *info, const uchar *record); +ha_checksum _ma_checksum(MARIA_HA *info, const uchar *buf); +ha_checksum _ma_static_checksum(MARIA_HA *info, const uchar *buf); +my_bool _ma_check_unique(MARIA_HA *info, MARIA_UNIQUEDEF *def, + uchar *record, ha_checksum unique_hash, + MARIA_RECORD_POS pos); +ha_checksum _ma_unique_hash(MARIA_UNIQUEDEF *def, const uchar *buf); +my_bool _ma_cmp_static_unique(MARIA_HA *info, MARIA_UNIQUEDEF *def, + const uchar *record, MARIA_RECORD_POS pos); +my_bool _ma_cmp_dynamic_unique(MARIA_HA *info, MARIA_UNIQUEDEF *def, + const uchar *record, MARIA_RECORD_POS pos); +my_bool _ma_unique_comp(MARIA_UNIQUEDEF *def, const uchar *a, const uchar *b, + my_bool null_are_equal); +void _ma_get_status(void *param, my_bool concurrent_insert); +void _ma_update_status(void *param); +void _ma_restore_status(void *param); +void _ma_copy_status(void *to, void *from); +my_bool _ma_check_status(void *param); +void _ma_restore_status(void *param); +void _ma_reset_status(MARIA_HA *maria); +int _ma_def_scan_remember_pos(MARIA_HA *info, MARIA_RECORD_POS *lastpos); +void _ma_def_scan_restore_pos(MARIA_HA *info, MARIA_RECORD_POS lastpos); + +#include "ma_commit.h" + +extern MARIA_HA *_ma_test_if_reopen(const char *filename); +my_bool _ma_check_table_is_closed(const char *name, const char *where); +int _ma_open_datafile(MARIA_HA *info, MARIA_SHARE *share, const char *org_name, + File file_to_dup); +int _ma_open_keyfile(MARIA_SHARE *share); +void _ma_setup_functions(register MARIA_SHARE *share); +my_bool _ma_dynmap_file(MARIA_HA *info, my_off_t size); +void _ma_remap_file(MARIA_HA *info, my_off_t size); + +MARIA_RECORD_POS _ma_write_init_default(MARIA_HA *info, const uchar *record); +my_bool _ma_write_abort_default(MARIA_HA *info); + +C_MODE_START +#define MARIA_FLUSH_DATA 1 +#define MARIA_FLUSH_INDEX 2 +int _ma_flush_table_files(MARIA_HA *info, uint flush_data_or_index, + enum flush_type flush_type_for_data, + enum flush_type flush_type_for_index); +/* + Functions needed by _ma_check (are overridden in MySQL/ha_maria.cc). + See ma_check_standalone.h . +*/ +volatile int *_ma_killed_ptr(HA_CHECK *param); +void _ma_check_print_error _VARARGS((HA_CHECK *param, const char *fmt, ...)) + ATTRIBUTE_FORMAT(printf, 2, 3); +void _ma_check_print_warning _VARARGS((HA_CHECK *param, const char *fmt, ...)) + ATTRIBUTE_FORMAT(printf, 2, 3); +void _ma_check_print_info _VARARGS((HA_CHECK *param, const char *fmt, ...)) + ATTRIBUTE_FORMAT(printf, 2, 3); +my_bool write_log_record_for_repair(const HA_CHECK *param, MARIA_HA *info); +C_MODE_END + +int _ma_flush_pending_blocks(MARIA_SORT_PARAM *param); +int _ma_sort_ft_buf_flush(MARIA_SORT_PARAM *sort_param); +int _ma_thr_write_keys(MARIA_SORT_PARAM *sort_param); +#ifdef THREAD +pthread_handler_t _ma_thr_find_all_keys(void *arg); +#endif + +int _ma_sort_write_record(MARIA_SORT_PARAM *sort_param); +int _ma_create_index_by_sort(MARIA_SORT_PARAM *info, my_bool no_messages, + size_t); +int _ma_sync_table_files(const MARIA_HA *info); +int _ma_initialize_data_file(MARIA_SHARE *share, File dfile); +int _ma_update_state_lsns(MARIA_SHARE *share, + LSN lsn, TrID create_trid, my_bool do_sync, + my_bool update_create_rename_lsn); +int _ma_update_state_lsns_sub(MARIA_SHARE *share, LSN lsn, + TrID create_trid, my_bool do_sync, + my_bool update_create_rename_lsn); +void _ma_set_data_pagecache_callbacks(PAGECACHE_FILE *file, + MARIA_SHARE *share); +void _ma_set_index_pagecache_callbacks(PAGECACHE_FILE *file, + MARIA_SHARE *share); +void _ma_tmp_disable_logging_for_table(MARIA_HA *info, + my_bool log_incomplete); +my_bool _ma_reenable_logging_for_table(MARIA_HA *info, my_bool flush_pages); +my_bool write_log_record_for_bulk_insert(MARIA_HA *info); + + +#define MARIA_NO_CRC_NORMAL_PAGE 0xffffffff +#define MARIA_NO_CRC_BITMAP_PAGE 0xfffffffe +extern my_bool maria_page_crc_set_index(uchar *page, + pgcache_page_no_t page_no, + uchar *data_ptr); +extern my_bool maria_page_crc_set_normal(uchar *page, + pgcache_page_no_t page_no, + uchar *data_ptr); +extern my_bool maria_page_crc_check_bitmap(uchar *page, + pgcache_page_no_t page_no, + uchar *data_ptr); +extern my_bool maria_page_crc_check_data(uchar *page, + pgcache_page_no_t page_no, + uchar *data_ptr); +extern my_bool maria_page_crc_check_index(uchar *page, + pgcache_page_no_t page_no, + uchar *data_ptr); +extern my_bool maria_page_crc_check_none(uchar *page, + pgcache_page_no_t page_no, + uchar *data_ptr); +extern my_bool maria_page_filler_set_bitmap(uchar *page, + pgcache_page_no_t page_no, + uchar *data_ptr); +extern my_bool maria_page_filler_set_normal(uchar *page, + pgcache_page_no_t page_no, + uchar *data_ptr); +extern my_bool maria_page_filler_set_none(uchar *page, + pgcache_page_no_t page_no, + uchar *data_ptr); +extern void maria_page_write_failure(uchar* data_ptr); +extern my_bool maria_flush_log_for_page(uchar *page, + pgcache_page_no_t page_no, + uchar *data_ptr); +extern my_bool maria_flush_log_for_page_none(uchar *page, + pgcache_page_no_t page_no, + uchar *data_ptr); +void maria_concurrent_inserts(MARIA_HA *info, my_bool concurrent_insert); +extern PAGECACHE *maria_log_pagecache; diff --git a/storage/maria/maria_ftdump.c b/storage/maria/maria_ftdump.c new file mode 100644 index 00000000000..5e3b47b956e --- /dev/null +++ b/storage/maria/maria_ftdump.c @@ -0,0 +1,280 @@ +/* Copyright (C) 2006 MySQL AB & MySQL Finland AB & TCX DataKonsult 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 */ + +/* Written by Sergei A. Golubchik, who has a shared copyright to this code + added support for long options (my_getopt) 22.5.2002 by Jani Tolonen */ + +#include "ma_ftdefs.h" +#include <my_getopt.h> + +static void usage(); +static void complain(int val); +static my_bool get_one_option(int, const struct my_option *, char *); + +static int count=0, stats=0, dump=0, lstats=0; +static my_bool verbose; +static char *query=NULL; +static uint lengths[256]; + +#define MAX_LEN (HA_FT_MAXBYTELEN+10) +#define HOW_OFTEN_TO_WRITE 10000 + +static struct my_option my_long_options[] = +{ + {"help", 'h', "Display help and exit.", + 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"help", '?', "Synonym for -h.", + 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"count", 'c', "Calculate per-word stats (counts and global weights).", + 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"dump", 'd', "Dump index (incl. data offsets and word weights).", + 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"length", 'l', "Report length distribution.", + 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"stats", 's', "Report global stats.", + 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"verbose", 'v', "Be verbose.", + (uchar**) &verbose, (uchar**) &verbose, 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} +}; + + +int main(int argc,char *argv[]) +{ + int error=0, subkeys; + uint keylen, keylen2=0, inx, doc_cnt=0; + float weight= 1.0; + double gws, min_gws=0, avg_gws=0; + MARIA_HA *info; + char buf[MAX_LEN], buf2[MAX_LEN], buf_maxlen[MAX_LEN], buf_min_gws[MAX_LEN]; + ulong total=0, maxlen=0, uniq=0, max_doc_cnt=0; + struct { MARIA_HA *info; } aio0, *aio=&aio0; /* for GWS_IN_USE */ + + MY_INIT(argv[0]); + if ((error= handle_options(&argc, &argv, my_long_options, get_one_option))) + exit(error); + maria_init(); + if (count || dump) + verbose=0; + if (!count && !dump && !lstats && !query) + stats=1; + + if (verbose) + setbuf(stdout,NULL); + + if (argc < 2) + usage(); + + { + char *end; + inx= (uint) strtoll(argv[1], &end, 10); + if (*end) + usage(); + } + + init_pagecache(maria_pagecache, USE_BUFFER_INIT, 0, 0, + MARIA_KEY_BLOCK_LENGTH, MY_WME); + + if (!(info=maria_open(argv[0], O_RDONLY, + HA_OPEN_ABORT_IF_LOCKED|HA_OPEN_FROM_SQL_LAYER))) + { + error=my_errno; + goto err; + } + + *buf2=0; + aio->info=info; + + if ((inx >= info->s->base.keys) || + !(info->s->keyinfo[inx].flag & HA_FULLTEXT)) + { + printf("Key %d in table %s is not a FULLTEXT key\n", inx, + info->s->open_file_name.str); + goto err; + } + + maria_lock_database(info, F_EXTRA_LCK); + + info->cur_row.lastpos= HA_OFFSET_ERROR; + info->update|= HA_STATE_PREV_FOUND; + + while (!(error=maria_rnext(info,NULL,inx))) + { + keylen=*(info->lastkey_buff); + + subkeys=ft_sintXkorr(info->lastkey_buff + keylen + 1); + if (subkeys >= 0) + weight=*(float*)&subkeys; + +#ifdef HAVE_SNPRINTF + snprintf(buf,MAX_LEN,"%.*s",(int) keylen,info->lastkey_buff+1); +#else + sprintf(buf,"%.*s",(int) keylen,info->lastkey_buff+1); +#endif + my_casedn_str(default_charset_info,buf); + total++; + lengths[keylen]++; + + if (count || stats) + { + if (strcmp(buf, buf2)) + { + if (*buf2) + { + uniq++; + avg_gws+=gws=GWS_IN_USE; + if (count) + printf("%9u %20.7f %s\n",doc_cnt,gws,buf2); + if (maxlen<keylen2) + { + maxlen=keylen2; + strmov(buf_maxlen, buf2); + } + if (max_doc_cnt < doc_cnt) + { + max_doc_cnt=doc_cnt; + strmov(buf_min_gws, buf2); + min_gws=gws; + } + } + strmov(buf2, buf); + keylen2=keylen; + doc_cnt=0; + } + doc_cnt+= (subkeys >= 0 ? 1 : -subkeys); + } + if (dump) + { + if (subkeys>=0) + printf("%9lx %20.7f %s\n", (long) info->cur_row.lastpos,weight,buf); + else + printf("%9lx => %17d %s\n",(long) info->cur_row.lastpos,-subkeys,buf); + } + if (verbose && (total%HOW_OFTEN_TO_WRITE)==0) + printf("%10ld\r",total); + } + maria_lock_database(info, F_UNLCK); + + if (count || stats) + { + if (*buf2) + { + uniq++; + avg_gws+=gws=GWS_IN_USE; + if (count) + printf("%9u %20.7f %s\n",doc_cnt,gws,buf2); + if (maxlen<keylen2) + { + maxlen=keylen2; + strmov(buf_maxlen, buf2); + } + if (max_doc_cnt < doc_cnt) + { + max_doc_cnt=doc_cnt; + strmov(buf_min_gws, buf2); + min_gws=gws; + } + } + } + + if (stats) + { + count=0; + for (inx=0;inx<256;inx++) + { + count+=lengths[inx]; + if ((ulong) count >= total/2) + break; + } + printf("Total rows: %lu\nTotal words: %lu\n" + "Unique words: %lu\nLongest word: %lu chars (%s)\n" + "Median length: %u\n" + "Average global weight: %f\n" + "Most common word: %lu times, weight: %f (%s)\n", + (long) info->state->records, total, uniq, maxlen, buf_maxlen, + inx, avg_gws/uniq, max_doc_cnt, min_gws, buf_min_gws); + } + if (lstats) + { + count=0; + for (inx=0; inx<256; inx++) + { + count+=lengths[inx]; + if (count && lengths[inx]) + printf("%3u: %10lu %5.2f%% %20lu %4.1f%%\n", inx, + (ulong) lengths[inx],100.0*lengths[inx]/total,(ulong) count, + 100.0*count/total); + } + } + +err: + if (error && error != HA_ERR_END_OF_FILE) + printf("got error %d\n",my_errno); + if (info) + maria_close(info); + maria_end(); + return 0; +} + + +static my_bool +get_one_option(int optid, const struct my_option *opt __attribute__((unused)), + char *argument __attribute__((unused))) +{ + switch(optid) { + case 'd': + dump=1; + complain(count || query); + break; + case 's': + stats=1; + complain(query!=0); + break; + case 'c': + count= 1; + complain(dump || query); + break; + case 'l': + lstats=1; + complain(query!=0); + break; + case '?': + case 'h': + usage(); + } + return 0; +} + +#include <help_start.h> + +static void usage() +{ + printf("Use: maria_ft_dump <table_name> <index_num>\n"); + my_print_help(my_long_options); + my_print_variables(my_long_options); + NETWARE_SET_SCREEN_MODE(1); + exit(1); +} + +#include <help_end.h> + +static void complain(int val) /* Kinda assert :-) */ +{ + if (val) + { + printf("You cannot use these options together!\n"); + exit(1); + } +} diff --git a/storage/maria/maria_pack.c b/storage/maria/maria_pack.c new file mode 100644 index 00000000000..78cf86fc6a9 --- /dev/null +++ b/storage/maria/maria_pack.c @@ -0,0 +1,3242 @@ +/* Copyright (C) 2006 MySQL AB & MySQL Finland AB & TCX DataKonsult 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 */ + +/* Pack MARIA file */ + +#ifndef USE_MY_FUNC +#define USE_MY_FUNC /* We need at least my_malloc */ +#endif + +#include "maria_def.h" +#include <queues.h> +#include <my_tree.h> +#include "mysys_err.h" +#ifdef MSDOS +#include <io.h> +#endif +#ifndef __GNU_LIBRARY__ +#define __GNU_LIBRARY__ /* Skip warnings in getopt.h */ +#endif +#include <my_getopt.h> +#include <assert.h> + +#if SIZEOF_LONG_LONG > 4 +#define BITS_SAVED 64 +#else +#define BITS_SAVED 32 +#endif + +#define IS_OFFSET ((uint) 32768) /* Bit if offset or char in tree */ +#define HEAD_LENGTH 32 +#define ALLOWED_JOIN_DIFF 256 /* Diff allowed to join trees */ + +#define DATA_TMP_EXT ".TMD" +#define OLD_EXT ".OLD" +#define WRITE_COUNT MY_HOW_OFTEN_TO_WRITE + +struct st_file_buffer { + File file; + uchar *buffer,*pos,*end; + my_off_t pos_in_file; + int bits; + ulonglong bitbucket; +}; + +struct st_huff_tree; +struct st_huff_element; + +typedef struct st_huff_counts { + uint field_length,max_zero_fill; + uint pack_type; + uint max_end_space,max_pre_space,length_bits,min_space; + ulong max_length; + enum en_fieldtype field_type; + struct st_huff_tree *tree; /* Tree for field */ + my_off_t counts[256]; + my_off_t end_space[8]; + my_off_t pre_space[8]; + my_off_t tot_end_space,tot_pre_space,zero_fields,empty_fields,bytes_packed; + TREE int_tree; /* Tree for detecting distinct column values. */ + uchar *tree_buff; /* Column values, 'field_length' each. */ + uchar *tree_pos; /* Points to end of column values in 'tree_buff'. */ +} HUFF_COUNTS; + +typedef struct st_huff_element HUFF_ELEMENT; + +/* + WARNING: It is crucial for the optimizations in calc_packed_length() + that 'count' is the first element of 'HUFF_ELEMENT'. +*/ +struct st_huff_element { + my_off_t count; + union un_element { + struct st_nod { + HUFF_ELEMENT *left,*right; + } nod; + struct st_leaf { + HUFF_ELEMENT *null; + uint element_nr; /* Number of element */ + } leaf; + } a; +}; + + +typedef struct st_huff_tree { + HUFF_ELEMENT *root,*element_buffer; + HUFF_COUNTS *counts; + uint tree_number; + uint elements; + my_off_t bytes_packed; + uint tree_pack_length; + uint min_chr,max_chr,char_bits,offset_bits,max_offset,height; + ulonglong *code; + uchar *code_len; +} HUFF_TREE; + + +typedef struct st_isam_mrg { + MARIA_HA **file,**current,**end; + uint free_file; + uint count; + uint min_pack_length; /* Theese is used by packed data */ + uint max_pack_length; + uint ref_length; + uint max_blob_length; + my_off_t records; + /* true if at least one source file has at least one disabled index */ + my_bool src_file_has_indexes_disabled; +} PACK_MRG_INFO; + + +extern int main(int argc,char * *argv); +static void get_options(int *argc,char ***argv); +static MARIA_HA *open_maria_file(char *name,int mode); +static my_bool open_maria_files(PACK_MRG_INFO *mrg,char **names,uint count); +static int compress(PACK_MRG_INFO *file,char *join_name); +static HUFF_COUNTS *init_huff_count(MARIA_HA *info,my_off_t records); +static void free_counts_and_tree_and_queue(HUFF_TREE *huff_trees, + uint trees, + HUFF_COUNTS *huff_counts, + uint fields); +static int compare_tree(void* cmp_arg __attribute__((unused)), + const uchar *s,const uchar *t); +static int get_statistic(PACK_MRG_INFO *mrg,HUFF_COUNTS *huff_counts); +static void check_counts(HUFF_COUNTS *huff_counts,uint trees, + my_off_t records); +static int test_space_compress(HUFF_COUNTS *huff_counts,my_off_t records, + uint max_space_length,my_off_t *space_counts, + my_off_t tot_space_count, + enum en_fieldtype field_type); +static HUFF_TREE* make_huff_trees(HUFF_COUNTS *huff_counts,uint trees); +static int make_huff_tree(HUFF_TREE *tree,HUFF_COUNTS *huff_counts); +static int compare_huff_elements(void *not_used, uchar *a,uchar *b); +static int save_counts_in_queue(uchar *key,element_count count, + HUFF_TREE *tree); +static my_off_t calc_packed_length(HUFF_COUNTS *huff_counts,uint flag); +static uint join_same_trees(HUFF_COUNTS *huff_counts,uint trees); +static int make_huff_decode_table(HUFF_TREE *huff_tree,uint trees); +static void make_traverse_code_tree(HUFF_TREE *huff_tree, + HUFF_ELEMENT *element,uint size, + ulonglong code); +static int write_header(PACK_MRG_INFO *isam_file, uint header_length,uint trees, + my_off_t tot_elements,my_off_t filelength); +static void write_field_info(HUFF_COUNTS *counts, uint fields,uint trees); +static my_off_t write_huff_tree(HUFF_TREE *huff_tree,uint trees); +static uint *make_offset_code_tree(HUFF_TREE *huff_tree, + HUFF_ELEMENT *element, + uint *offset); +static uint max_bit(uint value); +static int compress_maria_file(PACK_MRG_INFO *file,HUFF_COUNTS *huff_counts); +static char *make_new_name(char *new_name,char *old_name); +static char *make_old_name(char *new_name,char *old_name); +static void init_file_buffer(File file,pbool read_buffer); +static int flush_buffer(ulong neaded_length); +static void end_file_buffer(void); +static void write_bits(ulonglong value, uint bits); +static void flush_bits(void); +static int save_state(MARIA_HA *isam_file,PACK_MRG_INFO *mrg, + my_off_t new_length, ha_checksum crc); +static int save_state_mrg(File file,PACK_MRG_INFO *isam_file, + my_off_t new_length, ha_checksum crc); +static int mrg_close(PACK_MRG_INFO *mrg); +static int mrg_rrnd(PACK_MRG_INFO *info,uchar *buf); +static void mrg_reset(PACK_MRG_INFO *mrg); +#if !defined(DBUG_OFF) +static void fakebigcodes(HUFF_COUNTS *huff_counts, HUFF_COUNTS *end_count); +static int fakecmp(my_off_t **count1, my_off_t **count2); +#endif + + +static int error_on_write=0,test_only=0,verbose=0,silent=0, + write_loop=0,force_pack=0, isamchk_neaded=0; +static int tmpfile_createflag=O_RDWR | O_TRUNC | O_EXCL; +static my_bool backup, opt_wait; +/* + tree_buff_length is somewhat arbitrary. The bigger it is the better + the chance to win in terms of compression factor. On the other hand, + this table becomes part of the compressed file header. And its length + is coded with 16 bits in the header. Hence the limit is 2**16 - 1. +*/ +static uint tree_buff_length= 65536 - MALLOC_OVERHEAD; +static char tmp_dir[FN_REFLEN]={0},*join_table; +static my_off_t intervall_length; +static ha_checksum glob_crc; +static struct st_file_buffer file_buffer; +static QUEUE queue; +static HUFF_COUNTS *global_count; +static char zero_string[]={0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}; +static const char *load_default_groups[]= { "mariapack",0 }; + + /* The main program */ + +int main(int argc, char **argv) +{ + int error,ok; + PACK_MRG_INFO merge; + char **default_argv; + MY_INIT(argv[0]); + + load_defaults("my",load_default_groups,&argc,&argv); + default_argv= argv; + get_options(&argc,&argv); + maria_init(); + + error=ok=isamchk_neaded=0; + if (join_table) + { /* Join files into one */ + if (open_maria_files(&merge,argv,(uint) argc) || + compress(&merge,join_table)) + error=1; + } + else while (argc--) + { + MARIA_HA *isam_file; + if (!(isam_file=open_maria_file(*argv++,O_RDWR))) + error=1; + else + { + merge.file= &isam_file; + merge.current=0; + merge.free_file=0; + merge.count=1; + if (compress(&merge,0)) + error=1; + else + ok=1; + } + } + if (ok && isamchk_neaded && !silent) + puts("Remember to run maria_chk -rq on compressed tables"); + VOID(fflush(stdout)); + VOID(fflush(stderr)); + free_defaults(default_argv); + maria_end(); + my_end(verbose ? MY_CHECK_ERROR | MY_GIVE_INFO : MY_CHECK_ERROR); + exit(error ? 2 : 0); +#ifndef _lint + return 0; /* No compiler warning */ +#endif +} + +enum options_mp {OPT_CHARSETS_DIR_MP=256, OPT_AUTO_CLOSE}; + +static struct my_option my_long_options[] = +{ +#ifdef __NETWARE__ + {"autoclose", OPT_AUTO_CLOSE, "Auto close the screen on exit for Netware.", + 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0}, +#endif + {"backup", 'b', "Make a backup of the table as table_name.OLD.", + (uchar**) &backup, (uchar**) &backup, 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"character-sets-dir", OPT_CHARSETS_DIR_MP, + "Directory where character sets are.", (uchar**) &charsets_dir, + (uchar**) &charsets_dir, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + {"debug", '#', "Output debug log. Often this is 'd:t:o,filename'.", + 0, 0, 0, GET_STR, OPT_ARG, 0, 0, 0, 0, 0, 0}, + {"force", 'f', + "Force packing of table even if it gets bigger or if tempfile exists.", + 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"join", 'j', + "Join all given tables into 'new_table_name'. All tables MUST have identical layouts.", + (uchar**) &join_table, (uchar**) &join_table, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, + 0, 0, 0}, + {"help", '?', "Display this help and exit.", + 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"silent", 's', "Be more silent.", + 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"tmpdir", 'T', "Use temporary directory to store temporary table.", + 0, 0, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + {"test", 't', "Don't pack table, only test packing it.", + 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"verbose", 'v', "Write info about progress and packing result. Use many -v for more verbosity!", + 0, 0, 0, GET_NO_ARG, 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}, + {"wait", 'w', "Wait and retry if table is in use.", (uchar**) &opt_wait, + (uchar**) &opt_wait, 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} +}; + +#include <help_start.h> + +static void print_version(void) +{ + VOID(printf("%s Ver 1.0 for %s on %s\n", + my_progname, SYSTEM_TYPE, MACHINE_TYPE)); + NETWARE_SET_SCREEN_MODE(1); +} + + +static void usage(void) +{ + print_version(); + puts("Copyright 2002-2008 MySQL AB, 2008-2009 Sun Microsystems, Inc."); + puts("This software comes with ABSOLUTELY NO WARRANTY. This is free software,"); + puts("and you are welcome to modify and redistribute it under the GPL license\n"); + + puts("Pack a MARIA-table to take much less space."); + puts("Keys are not updated, you must run maria_chk -rq on the index (.MAI) file"); + puts("afterwards to update the keys."); + puts("You should give the .MAI file as the filename argument."); + puts("To unpack a packed table, run maria_chk -u on the table"); + + VOID(printf("\nUsage: %s [OPTIONS] filename...\n", my_progname)); + my_print_help(my_long_options); + print_defaults("my", load_default_groups); + my_print_variables(my_long_options); +} + +#include <help_end.h> + +static my_bool +get_one_option(int optid, const struct my_option *opt __attribute__((unused)), + char *argument) +{ + uint length; + + switch(optid) { +#ifdef __NETWARE__ + case OPT_AUTO_CLOSE: + setscreenmode(SCR_AUTOCLOSE_ON_EXIT); + break; +#endif + case 'f': + force_pack= 1; + tmpfile_createflag= O_RDWR | O_TRUNC; + break; + case 's': + write_loop= verbose= 0; + silent= 1; + break; + case 't': + test_only= 1; + /* Avoid to reset 'verbose' if it was already set > 1. */ + if (! verbose) + verbose= 1; + break; + case 'T': + length= (uint) (strmov(tmp_dir, argument) - tmp_dir); + if (length != dirname_length(tmp_dir)) + { + tmp_dir[length]=FN_LIBCHAR; + tmp_dir[length+1]=0; + } + break; + case 'v': + verbose++; /* Allow for selecting the level of verbosity. */ + silent= 0; + break; + case '#': + DBUG_PUSH(argument ? argument : "d:t:o,/tmp/maria_pack.trace"); + break; + case 'V': + print_version(); + exit(0); + case 'I': + case '?': + usage(); + exit(0); + } + return 0; +} + + /* reads options */ + /* Initiates DEBUG - but no debugging here ! */ + +static void get_options(int *argc,char ***argv) +{ + int ho_error; + + my_progname= argv[0][0]; + if (isatty(fileno(stdout))) + write_loop=1; + + if ((ho_error=handle_options(argc, argv, my_long_options, get_one_option))) + exit(ho_error); + + if (!*argc) + { + usage(); + exit(1); + } + if (join_table) + { + backup=0; /* Not needed */ + tmp_dir[0]=0; + } + return; +} + + +static MARIA_HA *open_maria_file(char *name,int mode) +{ + MARIA_HA *isam_file; + MARIA_SHARE *share; + DBUG_ENTER("open_maria_file"); + + if (!(isam_file=maria_open(name, mode, HA_OPEN_IGNORE_MOVED_STATE | + (opt_wait ? HA_OPEN_WAIT_IF_LOCKED : + HA_OPEN_ABORT_IF_LOCKED)))) + { + VOID(fprintf(stderr, "%s gave error %d on open\n", name, my_errno)); + DBUG_RETURN(0); + } + share=isam_file->s; + if (share->options & HA_OPTION_COMPRESS_RECORD && !join_table) + { + if (!force_pack) + { + VOID(fprintf(stderr, "%s is already compressed\n", name)); + VOID(maria_close(isam_file)); + DBUG_RETURN(0); + } + if (verbose) + puts("Recompressing already compressed table"); + share->options&= ~HA_OPTION_READ_ONLY_DATA; /* We are modifing it */ + } + if (! force_pack && share->state.state.records != 0 && + (share->state.state.records <= 1 || + share->state.state.data_file_length < 1024)) + { + VOID(fprintf(stderr, "%s is too small to compress\n", name)); + VOID(maria_close(isam_file)); + DBUG_RETURN(0); + } + VOID(maria_lock_database(isam_file,F_WRLCK)); + maria_ignore_trids(isam_file); + DBUG_RETURN(isam_file); +} + + +static my_bool open_maria_files(PACK_MRG_INFO *mrg,char **names,uint count) +{ + uint i,j; + mrg->count=0; + mrg->current=0; + mrg->file=(MARIA_HA**) my_malloc(sizeof(MARIA_HA*)*count,MYF(MY_FAE)); + mrg->free_file=1; + mrg->src_file_has_indexes_disabled= 0; + for (i=0; i < count ; i++) + { + if (!(mrg->file[i]=open_maria_file(names[i],O_RDONLY))) + goto error; + + mrg->src_file_has_indexes_disabled|= + ! maria_is_all_keys_active(mrg->file[i]->s->state.key_map, + mrg->file[i]->s->base.keys); + } + /* Check that files are identical */ + for (j=0 ; j < count-1 ; j++) + { + MARIA_COLUMNDEF *m1,*m2,*end; + if (mrg->file[j]->s->base.reclength != mrg->file[j+1]->s->base.reclength || + mrg->file[j]->s->base.fields != mrg->file[j+1]->s->base.fields) + goto diff_file; + m1=mrg->file[j]->s->columndef; + end=m1+mrg->file[j]->s->base.fields; + m2=mrg->file[j+1]->s->columndef; + for ( ; m1 != end ; m1++,m2++) + { + if (m1->type != m2->type || m1->length != m2->length) + goto diff_file; + } + } + mrg->count=count; + return 0; + + diff_file: + VOID(fprintf(stderr, "%s: Tables '%s' and '%s' are not identical\n", + my_progname, names[j], names[j+1])); + error: + while (i--) + maria_close(mrg->file[i]); + my_free(mrg->file, MYF(0)); + return 1; +} + + +static int compress(PACK_MRG_INFO *mrg,char *result_table) +{ + int error; + File new_file,join_maria_file; + MARIA_HA *isam_file; + MARIA_SHARE *share; + char org_name[FN_REFLEN],new_name[FN_REFLEN],temp_name[FN_REFLEN]; + uint i,header_length,fields,trees,used_trees; + my_off_t old_length,new_length,tot_elements; + HUFF_COUNTS *huff_counts; + HUFF_TREE *huff_trees; + DBUG_ENTER("compress"); + + isam_file=mrg->file[0]; /* Take this as an example */ + share=isam_file->s; + new_file=join_maria_file= -1; + trees=fields=0; + huff_trees=0; + huff_counts=0; + maria_block_size= isam_file->s->block_size; + + /* Create temporary or join file */ + if (backup) + VOID(fn_format(org_name,isam_file->s->open_file_name.str, + "",MARIA_NAME_DEXT, 2)); + else + VOID(fn_format(org_name,isam_file->s->open_file_name.str, + "",MARIA_NAME_DEXT, 2+4+16)); + + if (init_pagecache(maria_pagecache, MARIA_MIN_PAGE_CACHE_SIZE, 0, 0, + maria_block_size, MY_WME) == 0) + { + fprintf(stderr, "Can't initialize page cache\n"); + goto err; + } + + if (!test_only && result_table) + { + /* Make a new indexfile based on first file in list */ + uint length; + uchar *buff; + strmov(org_name,result_table); /* Fix error messages */ + VOID(fn_format(new_name,result_table,"",MARIA_NAME_IEXT,2)); + if ((join_maria_file=my_create(new_name,0,tmpfile_createflag,MYF(MY_WME))) + < 0) + goto err; + length=(uint) share->base.keystart; + if (!(buff= (uchar*) my_malloc(length,MYF(MY_WME)))) + goto err; + if (my_pread(share->kfile.file, buff, length, 0L, MYF(MY_WME | MY_NABP)) || + my_write(join_maria_file,buff,length, + MYF(MY_WME | MY_NABP | MY_WAIT_IF_FULL))) + { + my_free(buff,MYF(0)); + goto err; + } + my_free(buff,MYF(0)); + VOID(fn_format(new_name,result_table,"",MARIA_NAME_DEXT,2)); + } + else if (!tmp_dir[0]) + VOID(make_new_name(new_name,org_name)); + else + VOID(fn_format(new_name,org_name,tmp_dir,DATA_TMP_EXT,1+2+4)); + if (!test_only && + (new_file=my_create(new_name,0,tmpfile_createflag,MYF(MY_WME))) < 0) + goto err; + + /* Start calculating statistics */ + + mrg->records=0; + for (i=0 ; i < mrg->count ; i++) + mrg->records+=mrg->file[i]->s->state.state.records; + + DBUG_PRINT("info", ("Compressing %s: (%lu records)", + result_table ? new_name : org_name, + (ulong) mrg->records)); + if (write_loop || verbose) + { + VOID(printf("Compressing %s: (%lu records)\n", + result_table ? new_name : org_name, (ulong) mrg->records)); + } + trees=fields=share->base.fields; + huff_counts=init_huff_count(isam_file,mrg->records); + QUICK_SAFEMALLOC; + + /* + Read the whole data file(s) for statistics. + */ + DBUG_PRINT("info", ("- Calculating statistics")); + if (write_loop || verbose) + VOID(printf("- Calculating statistics\n")); + if (get_statistic(mrg,huff_counts)) + goto err; + NORMAL_SAFEMALLOC; + old_length=0; + for (i=0; i < mrg->count ; i++) + old_length+= (mrg->file[i]->s->state.state.data_file_length - + mrg->file[i]->s->state.state.empty); + + /* + Create a global priority queue in preparation for making + temporary Huffman trees. + */ + if (init_queue(&queue,256,0,0,compare_huff_elements,0)) + goto err; + + /* + Check each column if we should use pre-space-compress, end-space- + compress, empty-field-compress or zero-field-compress. + */ + check_counts(huff_counts,fields,mrg->records); + + /* + Build a Huffman tree for each column. + */ + huff_trees=make_huff_trees(huff_counts,trees); + + /* + If the packed lengths of combined columns is less then the sum of + the non-combined columns, then create common Huffman trees for them. + We do this only for uchar compressed columns, not for distinct values + compressed columns. + */ + if ((int) (used_trees=join_same_trees(huff_counts,trees)) < 0) + goto err; + + /* + Assign codes to all uchar or column values. + */ + if (make_huff_decode_table(huff_trees,fields)) + goto err; + + /* Prepare a file buffer. */ + init_file_buffer(new_file,0); + + /* + Reserve space in the target file for the fixed compressed file header. + */ + file_buffer.pos_in_file=HEAD_LENGTH; + if (! test_only) + VOID(my_seek(new_file,file_buffer.pos_in_file,MY_SEEK_SET,MYF(0))); + + /* + Write field infos: field type, pack type, length bits, tree number. + */ + write_field_info(huff_counts,fields,used_trees); + + /* + Write decode trees. + */ + if (!(tot_elements=write_huff_tree(huff_trees,trees))) + goto err; + + /* + Calculate the total length of the compression info header. + This includes the fixed compressed file header, the column compression + type descriptions, and the decode trees. + */ + header_length=(uint) file_buffer.pos_in_file+ + (uint) (file_buffer.pos-file_buffer.buffer); + + /* + Compress the source file into the target file. + */ + DBUG_PRINT("info", ("- Compressing file")); + if (write_loop || verbose) + VOID(printf("- Compressing file\n")); + error=compress_maria_file(mrg,huff_counts); + new_length=file_buffer.pos_in_file; + if (!error && !test_only) + { + uchar buff[MEMMAP_EXTRA_MARGIN]; /* End marginal for memmap */ + bzero(buff,sizeof(buff)); + error=my_write(file_buffer.file,buff,sizeof(buff), + MYF(MY_WME | MY_NABP | MY_WAIT_IF_FULL)) != 0; + } + + /* + Write the fixed compressed file header. + */ + if (!error) + error=write_header(mrg,header_length,used_trees,tot_elements, + new_length); + + /* Flush the file buffer. */ + end_file_buffer(); + + /* Display statistics. */ + DBUG_PRINT("info", ("Min record length: %6d Max length: %6d " + "Mean total length: %6ld", + mrg->min_pack_length, mrg->max_pack_length, + (ulong) (mrg->records ? (new_length/mrg->records) : 0))); + if (verbose && mrg->records) + VOID(printf("Min record length: %6d Max length: %6d " + "Mean total length: %6ld\n", mrg->min_pack_length, + mrg->max_pack_length, (ulong) (new_length/mrg->records))); + + /* Close source and target file. */ + if (!test_only) + { + error|=my_close(new_file,MYF(MY_WME)); + if (!result_table) + { + error|=my_close(isam_file->dfile.file, MYF(MY_WME)); + isam_file->dfile.file= -1; /* Tell maria_close file is closed */ + isam_file->s->bitmap.file.file= -1; + } + } + + /* Cleanup. */ + free_counts_and_tree_and_queue(huff_trees,trees,huff_counts,fields); + if (! test_only && ! error) + { + if (result_table) + { + error=save_state_mrg(join_maria_file,mrg,new_length,glob_crc); + } + else + { + if (backup) + { + if (my_rename(org_name,make_old_name(temp_name, + isam_file->s->open_file_name.str), + MYF(MY_WME))) + error=1; + else + { + if (tmp_dir[0]) + error=my_copy(new_name,org_name,MYF(MY_WME)); + else + error=my_rename(new_name,org_name,MYF(MY_WME)); + if (!error) + { + VOID(my_copystat(temp_name,org_name,MYF(MY_COPYTIME))); + if (tmp_dir[0]) + VOID(my_delete(new_name,MYF(MY_WME))); + } + } + } + else + { + if (tmp_dir[0]) + { + error=my_copy(new_name,org_name, + MYF(MY_WME | MY_HOLD_ORIGINAL_MODES | MY_COPYTIME)); + if (!error) + VOID(my_delete(new_name,MYF(MY_WME))); + } + else + error=my_redel(org_name,new_name,MYF(MY_WME | MY_COPYTIME)); + } + if (! error) + error=save_state(isam_file,mrg,new_length,glob_crc); + } + } + error|=mrg_close(mrg); + if (join_maria_file >= 0) + error|=my_close(join_maria_file,MYF(MY_WME)); + if (error) + { + VOID(fprintf(stderr, "Aborting: %s is not compressed\n", org_name)); + VOID(my_delete(new_name,MYF(MY_WME))); + DBUG_RETURN(-1); + } + if (write_loop || verbose) + { + if (old_length) + VOID(printf("%.4g%% \n", + (((longlong) (old_length - new_length)) * 100.0 / + (longlong) old_length))); + else + puts("Empty file saved in compressed format"); + } + DBUG_RETURN(0); + + err: + end_pagecache(maria_pagecache, 1); + free_counts_and_tree_and_queue(huff_trees,trees,huff_counts,fields); + if (new_file >= 0) + VOID(my_close(new_file,MYF(0))); + if (join_maria_file >= 0) + VOID(my_close(join_maria_file,MYF(0))); + mrg_close(mrg); + VOID(fprintf(stderr, "Aborted: %s is not compressed\n", org_name)); + DBUG_RETURN(-1); +} + + /* Init a huff_count-struct for each field and init it */ + +static HUFF_COUNTS *init_huff_count(MARIA_HA *info,my_off_t records) +{ + reg2 uint i; + reg1 HUFF_COUNTS *count; + if ((count = (HUFF_COUNTS*) my_malloc(info->s->base.fields* + sizeof(HUFF_COUNTS), + MYF(MY_ZEROFILL | MY_WME)))) + { + for (i=0 ; i < info->s->base.fields ; i++) + { + enum en_fieldtype type; + count[i].field_length=info->s->columndef[i].length; + type= count[i].field_type= (enum en_fieldtype) info->s->columndef[i].type; + if (type == FIELD_INTERVALL || + type == FIELD_CONSTANT || + type == FIELD_ZERO) + type = FIELD_NORMAL; + if (count[i].field_length <= 8 && + (type == FIELD_NORMAL || + type == FIELD_SKIP_ZERO)) + count[i].max_zero_fill= count[i].field_length; + /* + For every column initialize a tree, which is used to detect distinct + column values. 'int_tree' works together with 'tree_buff' and + 'tree_pos'. It's keys are implemented by pointers into 'tree_buff'. + This is accomplished by '-1' as the element size. + */ + init_tree(&count[i].int_tree,0,0,-1,(qsort_cmp2) compare_tree,0, NULL, + NULL); + if (records && type != FIELD_BLOB && type != FIELD_VARCHAR) + count[i].tree_pos=count[i].tree_buff = + my_malloc(count[i].field_length > 1 ? tree_buff_length : 2, + MYF(MY_WME)); + } + } + return count; +} + + + /* Free memory used by counts and trees */ + +static void free_counts_and_tree_and_queue(HUFF_TREE *huff_trees, uint trees, + HUFF_COUNTS *huff_counts, + uint fields) +{ + register uint i; + + if (huff_trees) + { + for (i=0 ; i < trees ; i++) + { + if (huff_trees[i].element_buffer) + my_free(huff_trees[i].element_buffer,MYF(0)); + if (huff_trees[i].code) + my_free(huff_trees[i].code,MYF(0)); + } + my_free(huff_trees,MYF(0)); + } + if (huff_counts) + { + for (i=0 ; i < fields ; i++) + { + if (huff_counts[i].tree_buff) + { + my_free(huff_counts[i].tree_buff,MYF(0)); + delete_tree(&huff_counts[i].int_tree); + } + } + my_free(huff_counts, MYF(0)); + } + delete_queue(&queue); /* This is safe to free */ + return; +} + + /* Read through old file and gather some statistics */ + +static int get_statistic(PACK_MRG_INFO *mrg,HUFF_COUNTS *huff_counts) +{ + int error; + uint length, null_bytes; + ulong reclength,max_blob_length; + uchar *record,*pos,*next_pos,*end_pos,*start_pos; + ha_rows record_count; + HUFF_COUNTS *count,*end_count; + TREE_ELEMENT *element; + ha_checksum(*calc_checksum)(MARIA_HA *, const uchar *); + DBUG_ENTER("get_statistic"); + + reclength= mrg->file[0]->s->base.reclength; + null_bytes= mrg->file[0]->s->base.null_bytes; + record=(uchar*) my_alloca(reclength); + end_count=huff_counts+mrg->file[0]->s->base.fields; + record_count=0; glob_crc=0; + max_blob_length=0; + + /* Check how to calculate checksum */ + if (mrg->file[0]->s->data_file_type == STATIC_RECORD) + calc_checksum= _ma_static_checksum; + else + calc_checksum= _ma_checksum; + + mrg_reset(mrg); + while ((error=mrg_rrnd(mrg,record)) != HA_ERR_END_OF_FILE) + { + ulong tot_blob_length=0; + if (! error) + { + /* glob_crc is a checksum over all bytes of all records. */ + glob_crc+= (*calc_checksum)(mrg->file[0],record); + + /* Count the incidence of values separately for every column. */ + for (pos=record + null_bytes, count=huff_counts ; + count < end_count ; + count++, + pos=next_pos) + { + next_pos=end_pos=(start_pos=pos)+count->field_length; + + /* + Put the whole column value in a tree if there is room for it. + 'int_tree' is used to quickly check for duplicate values. + 'tree_buff' collects as many distinct column values as + possible. If the field length is > 1, it is tree_buff_length, + else 2 bytes. Each value is 'field_length' bytes big. If there + are more distinct column values than fit into the buffer, we + give up with this tree. BLOBs and VARCHARs do not have a + tree_buff as it can only be used with fixed length columns. + For the special case of field length == 1, we handle only the + case that there is only one distinct value in the table(s). + Otherwise, we can have a maximum of 256 distinct values. This + is then handled by the normal Huffman tree build. + + Another limit for collecting distinct column values is the + number of values itself. Since we would need to build a + Huffman tree for the values, we are limited by the 'IS_OFFSET' + constant. This constant expresses a bit which is used to + determine if a tree element holds a final value or an offset + to a child element. Hence, all values and offsets need to be + smaller than 'IS_OFFSET'. A tree element is implemented with + two integer values, one for the left branch and one for the + right branch. For the extreme case that the first element + points to the last element, the number of integers in the tree + must be less or equal to IS_OFFSET. So the number of elements + must be less or equal to IS_OFFSET / 2. + + WARNING: At first, we insert a pointer into the record buffer + as the key for the tree. If we got a new distinct value, which + is really inserted into the tree, instead of being counted + only, we will copy the column value from the record buffer to + 'tree_buff' and adjust the key pointer of the tree accordingly. + */ + if (count->tree_buff) + { + global_count=count; + if (!(element=tree_insert(&count->int_tree,pos, 0, + count->int_tree.custom_arg)) || + (element->count == 1 && + (count->tree_buff + tree_buff_length < + count->tree_pos + count->field_length)) || + (count->int_tree.elements_in_tree > IS_OFFSET / 2) || + (count->field_length == 1 && + count->int_tree.elements_in_tree > 1)) + { + delete_tree(&count->int_tree); + my_free(count->tree_buff,MYF(0)); + count->tree_buff=0; + } + else + { + /* + If tree_insert() succeeds, it either creates a new element + or increments the counter of an existing element. + */ + if (element->count == 1) + { + /* Copy the new column value into 'tree_buff'. */ + memcpy(count->tree_pos,pos,(size_t) count->field_length); + /* Adjust the key pointer in the tree. */ + tree_set_pointer(element,count->tree_pos); + /* Point behind the last column value so far. */ + count->tree_pos+=count->field_length; + } + } + } + + /* Save character counters and space-counts and zero-field-counts */ + if (count->field_type == FIELD_NORMAL || + count->field_type == FIELD_SKIP_ENDSPACE) + { + /* Ignore trailing space. */ + for ( ; end_pos > pos ; end_pos--) + if (end_pos[-1] != ' ') + break; + /* Empty fields are just counted. Go to the next record. */ + if (end_pos == pos) + { + count->empty_fields++; + count->max_zero_fill=0; + continue; + } + /* + Count the total of all trailing spaces and the number of + short trailing spaces. Remember the longest trailing space. + */ + length= (uint) (next_pos-end_pos); + count->tot_end_space+=length; + if (length < 8) + count->end_space[length]++; + if (count->max_end_space < length) + count->max_end_space = length; + } + + if (count->field_type == FIELD_NORMAL || + count->field_type == FIELD_SKIP_PRESPACE) + { + /* Ignore leading space. */ + for (pos=start_pos; pos < end_pos ; pos++) + if (pos[0] != ' ') + break; + /* Empty fields are just counted. Go to the next record. */ + if (end_pos == pos) + { + count->empty_fields++; + count->max_zero_fill=0; + continue; + } + /* + Count the total of all leading spaces and the number of + short leading spaces. Remember the longest leading space. + */ + length= (uint) (pos-start_pos); + count->tot_pre_space+=length; + if (length < 8) + count->pre_space[length]++; + if (count->max_pre_space < length) + count->max_pre_space = length; + } + + /* Calculate pos, end_pos, and max_length for variable length fields. */ + if (count->field_type == FIELD_BLOB) + { + uint field_length=count->field_length -portable_sizeof_char_ptr; + ulong blob_length= _ma_calc_blob_length(field_length, start_pos); + memcpy_fixed((char*) &pos, start_pos+field_length,sizeof(char*)); + end_pos=pos+blob_length; + tot_blob_length+=blob_length; + set_if_bigger(count->max_length,blob_length); + } + else if (count->field_type == FIELD_VARCHAR) + { + uint pack_length= HA_VARCHAR_PACKLENGTH(count->field_length-1); + length= (pack_length == 1 ? (uint) *(uchar*) start_pos : + uint2korr(start_pos)); + pos= start_pos+pack_length; + end_pos= pos+length; + set_if_bigger(count->max_length,length); + } + + /* Evaluate 'max_zero_fill' for short fields. */ + if (count->field_length <= 8 && + (count->field_type == FIELD_NORMAL || + count->field_type == FIELD_SKIP_ZERO)) + { + uint i; + /* Zero fields are just counted. Go to the next record. */ + if (!memcmp(start_pos, zero_string, count->field_length)) + { + count->zero_fields++; + continue; + } + /* + max_zero_fill starts with field_length. It is decreased every + time a shorter "zero trailer" is found. It is set to zero when + an empty field is found (see above). This suggests that the + variable should be called 'min_zero_fill'. + */ + for (i =0 ; i < count->max_zero_fill && ! end_pos[-1 - (int) i] ; + i++) ; + if (i < count->max_zero_fill) + count->max_zero_fill=i; + } + + /* Ignore zero fields and check fields. */ + if (count->field_type == FIELD_ZERO || + count->field_type == FIELD_CHECK) + continue; + + /* + Count the incidence of every uchar value in the + significant field value. + */ + for ( ; pos < end_pos ; pos++) + count->counts[(uchar) *pos]++; + + /* Step to next field. */ + } + + if (tot_blob_length > max_blob_length) + max_blob_length=tot_blob_length; + record_count++; + if (write_loop && record_count % WRITE_COUNT == 0) + { + VOID(printf("%lu\r", (ulong) record_count)); + VOID(fflush(stdout)); + } + } + else if (error != HA_ERR_RECORD_DELETED) + { + VOID(fprintf(stderr, "Got error %d while reading rows\n", error)); + break; + } + + /* Step to next record. */ + } + if (write_loop) + { + VOID(printf(" \r")); + VOID(fflush(stdout)); + } + + /* + If --debug=d,fakebigcodes is set, fake the counts to get big Huffman + codes. + */ + DBUG_EXECUTE_IF("fakebigcodes", fakebigcodes(huff_counts, end_count);); + + DBUG_PRINT("info", ("Found the following number of incidents " + "of the uchar codes:")); + if (verbose >= 2) + VOID(printf("Found the following number of incidents " + "of the uchar codes:\n")); + for (count= huff_counts ; count < end_count; count++) + { + uint idx; + my_off_t total_count; + char llbuf[32]; + + DBUG_PRINT("info", ("column: %3u", (uint) (count - huff_counts + 1))); + if (verbose >= 2) + VOID(printf("column: %3u\n", (uint) (count - huff_counts + 1))); + if (count->tree_buff) + { + DBUG_PRINT("info", ("number of distinct values: %u", + (uint) ((count->tree_pos - count->tree_buff) / + count->field_length))); + if (verbose >= 2) + VOID(printf("number of distinct values: %u\n", + (uint) ((count->tree_pos - count->tree_buff) / + count->field_length))); + } + total_count= 0; + for (idx= 0; idx < 256; idx++) + { + if (count->counts[idx]) + { + total_count+= count->counts[idx]; + DBUG_PRINT("info", ("counts[0x%02x]: %12s", idx, + llstr((longlong) count->counts[idx], llbuf))); + if (verbose >= 2) + VOID(printf("counts[0x%02x]: %12s\n", idx, + llstr((longlong) count->counts[idx], llbuf))); + } + } + DBUG_PRINT("info", ("total: %12s", llstr((longlong) total_count, + llbuf))); + if ((verbose >= 2) && total_count) + { + VOID(printf("total: %12s\n", + llstr((longlong) total_count, llbuf))); + } + } + + mrg->records=record_count; + mrg->max_blob_length=max_blob_length; + my_afree(record); + DBUG_RETURN(error != HA_ERR_END_OF_FILE); +} + +static int compare_huff_elements(void *not_used __attribute__((unused)), + uchar *a, uchar *b) +{ + return *((my_off_t*) a) < *((my_off_t*) b) ? -1 : + (*((my_off_t*) a) == *((my_off_t*) b) ? 0 : 1); +} + + /* Check each tree if we should use pre-space-compress, end-space- + compress, empty-field-compress or zero-field-compress */ + +static void check_counts(HUFF_COUNTS *huff_counts, uint trees, + my_off_t records) +{ + uint space_fields,fill_zero_fields,field_count[(int) FIELD_enum_val_count]; + my_off_t old_length,new_length,length; + DBUG_ENTER("check_counts"); + + bzero((uchar*) field_count,sizeof(field_count)); + space_fields=fill_zero_fields=0; + + for (; trees-- ; huff_counts++) + { + if (huff_counts->field_type == FIELD_BLOB) + { + huff_counts->length_bits=max_bit(huff_counts->max_length); + goto found_pack; + } + else if (huff_counts->field_type == FIELD_VARCHAR) + { + huff_counts->length_bits=max_bit(huff_counts->max_length); + goto found_pack; + } + else if (huff_counts->field_type == FIELD_CHECK) + { + huff_counts->bytes_packed=0; + huff_counts->counts[0]=0; + goto found_pack; + } + + huff_counts->field_type=FIELD_NORMAL; + huff_counts->pack_type=0; + + /* Check for zero-filled records (in this column), or zero records. */ + if (huff_counts->zero_fields || ! records) + { + my_off_t old_space_count; + /* + If there are only zero filled records (in this column), + or no records at all, we are done. + */ + if (huff_counts->zero_fields == records) + { + huff_counts->field_type= FIELD_ZERO; + huff_counts->bytes_packed=0; + huff_counts->counts[0]=0; + goto found_pack; + } + /* Remeber the number of significant spaces. */ + old_space_count=huff_counts->counts[' ']; + /* Add all leading and trailing spaces. */ + huff_counts->counts[' ']+= (huff_counts->tot_end_space + + huff_counts->tot_pre_space + + huff_counts->empty_fields * + huff_counts->field_length); + /* Check, what the compressed length of this would be. */ + old_length=calc_packed_length(huff_counts,0)+records/8; + /* Get the number of zero bytes. */ + length=huff_counts->zero_fields*huff_counts->field_length; + /* Add it to the counts. */ + huff_counts->counts[0]+=length; + /* Check, what the compressed length of this would be. */ + new_length=calc_packed_length(huff_counts,0); + /* If the compression without the zeroes would be shorter, we are done. */ + if (old_length < new_length && huff_counts->field_length > 1) + { + huff_counts->field_type=FIELD_SKIP_ZERO; + huff_counts->counts[0]-=length; + huff_counts->bytes_packed=old_length- records/8; + goto found_pack; + } + /* Remove the insignificant spaces, but keep the zeroes. */ + huff_counts->counts[' ']=old_space_count; + } + /* Check, what the compressed length of this column would be. */ + huff_counts->bytes_packed=calc_packed_length(huff_counts,0); + + /* + If there are enough empty records (in this column), + treating them specially may pay off. + */ + if (huff_counts->empty_fields) + { + if (huff_counts->field_length > 2 && + huff_counts->empty_fields + (records - huff_counts->empty_fields)* + (1+max_bit(max(huff_counts->max_pre_space, + huff_counts->max_end_space))) < + records * max_bit(huff_counts->field_length)) + { + huff_counts->pack_type |= PACK_TYPE_SPACE_FIELDS; + } + else + { + length=huff_counts->empty_fields*huff_counts->field_length; + if (huff_counts->tot_end_space || ! huff_counts->tot_pre_space) + { + huff_counts->tot_end_space+=length; + huff_counts->max_end_space=huff_counts->field_length; + if (huff_counts->field_length < 8) + huff_counts->end_space[huff_counts->field_length]+= + huff_counts->empty_fields; + } + if (huff_counts->tot_pre_space) + { + huff_counts->tot_pre_space+=length; + huff_counts->max_pre_space=huff_counts->field_length; + if (huff_counts->field_length < 8) + huff_counts->pre_space[huff_counts->field_length]+= + huff_counts->empty_fields; + } + } + } + + /* + If there are enough trailing spaces (in this column), + treating them specially may pay off. + */ + if (huff_counts->tot_end_space) + { + huff_counts->counts[' ']+=huff_counts->tot_pre_space; + if (test_space_compress(huff_counts,records,huff_counts->max_end_space, + huff_counts->end_space, + huff_counts->tot_end_space,FIELD_SKIP_ENDSPACE)) + goto found_pack; + huff_counts->counts[' ']-=huff_counts->tot_pre_space; + } + + /* + If there are enough leading spaces (in this column), + treating them specially may pay off. + */ + if (huff_counts->tot_pre_space) + { + if (test_space_compress(huff_counts,records,huff_counts->max_pre_space, + huff_counts->pre_space, + huff_counts->tot_pre_space,FIELD_SKIP_PRESPACE)) + goto found_pack; + } + + found_pack: /* Found field-packing */ + + /* Test if we can use zero-fill */ + + if (huff_counts->max_zero_fill && + (huff_counts->field_type == FIELD_NORMAL || + huff_counts->field_type == FIELD_SKIP_ZERO)) + { + huff_counts->counts[0]-=huff_counts->max_zero_fill* + (huff_counts->field_type == FIELD_SKIP_ZERO ? + records - huff_counts->zero_fields : records); + huff_counts->pack_type|=PACK_TYPE_ZERO_FILL; + huff_counts->bytes_packed=calc_packed_length(huff_counts,0); + } + + /* Test if intervall-field is better */ + + if (huff_counts->tree_buff) + { + HUFF_TREE tree; + + DBUG_EXECUTE_IF("forceintervall", + huff_counts->bytes_packed= ~ (my_off_t) 0;); + tree.element_buffer=0; + if (!make_huff_tree(&tree,huff_counts) && + tree.bytes_packed+tree.tree_pack_length < huff_counts->bytes_packed) + { + if (tree.elements == 1) + huff_counts->field_type=FIELD_CONSTANT; + else + huff_counts->field_type=FIELD_INTERVALL; + huff_counts->pack_type=0; + } + else + { + my_free(huff_counts->tree_buff,MYF(0)); + delete_tree(&huff_counts->int_tree); + huff_counts->tree_buff=0; + } + if (tree.element_buffer) + my_free(tree.element_buffer,MYF(0)); + } + if (huff_counts->pack_type & PACK_TYPE_SPACE_FIELDS) + space_fields++; + if (huff_counts->pack_type & PACK_TYPE_ZERO_FILL) + fill_zero_fields++; + field_count[huff_counts->field_type]++; + } + DBUG_PRINT("info", ("normal: %3d empty-space: %3d " + "empty-zero: %3d empty-fill: %3d", + field_count[FIELD_NORMAL],space_fields, + field_count[FIELD_SKIP_ZERO],fill_zero_fields)); + DBUG_PRINT("info", ("pre-space: %3d end-space: %3d " + "intervall-fields: %3d zero: %3d", + field_count[FIELD_SKIP_PRESPACE], + field_count[FIELD_SKIP_ENDSPACE], + field_count[FIELD_INTERVALL], + field_count[FIELD_ZERO])); + if (verbose) + VOID(printf("\nnormal: %3d empty-space: %3d " + "empty-zero: %3d empty-fill: %3d\n" + "pre-space: %3d end-space: %3d " + "intervall-fields: %3d zero: %3d\n", + field_count[FIELD_NORMAL],space_fields, + field_count[FIELD_SKIP_ZERO],fill_zero_fields, + field_count[FIELD_SKIP_PRESPACE], + field_count[FIELD_SKIP_ENDSPACE], + field_count[FIELD_INTERVALL], + field_count[FIELD_ZERO])); + DBUG_VOID_RETURN; +} + + +/* Test if we can use space-compression and empty-field-compression */ + +static int +test_space_compress(HUFF_COUNTS *huff_counts, my_off_t records, + uint max_space_length, my_off_t *space_counts, + my_off_t tot_space_count, enum en_fieldtype field_type) +{ + int min_pos; + uint length_bits,i; + my_off_t space_count,min_space_count,min_pack,new_length,skip; + + length_bits=max_bit(max_space_length); + + /* Default no end_space-packing */ + space_count=huff_counts->counts[(uint) ' ']; + min_space_count= (huff_counts->counts[(uint) ' ']+= tot_space_count); + min_pack=calc_packed_length(huff_counts,0); + min_pos= -2; + huff_counts->counts[(uint) ' ']=space_count; + + /* Test with allways space-count */ + new_length=huff_counts->bytes_packed+length_bits*records/8; + if (new_length+1 < min_pack) + { + min_pos= -1; + min_pack=new_length; + min_space_count=space_count; + } + /* Test with length-flag */ + for (skip=0L, i=0 ; i < 8 ; i++) + { + if (space_counts[i]) + { + if (i) + huff_counts->counts[(uint) ' ']+=space_counts[i]; + skip+=huff_counts->pre_space[i]; + new_length=calc_packed_length(huff_counts,0)+ + (records+(records-skip)*(1+length_bits))/8; + if (new_length < min_pack) + { + min_pos=(int) i; + min_pack=new_length; + min_space_count=huff_counts->counts[(uint) ' ']; + } + } + } + + huff_counts->counts[(uint) ' ']=min_space_count; + huff_counts->bytes_packed=min_pack; + switch (min_pos) { + case -2: + return(0); /* No space-compress */ + case -1: /* Always space-count */ + huff_counts->field_type=field_type; + huff_counts->min_space=0; + huff_counts->length_bits=max_bit(max_space_length); + break; + default: + huff_counts->field_type=field_type; + huff_counts->min_space=(uint) min_pos; + huff_counts->pack_type|=PACK_TYPE_SELECTED; + huff_counts->length_bits=max_bit(max_space_length); + break; + } + return(1); /* Using space-compress */ +} + + + /* Make a huff_tree of each huff_count */ + +static HUFF_TREE* make_huff_trees(HUFF_COUNTS *huff_counts, uint trees) +{ + uint tree; + HUFF_TREE *huff_tree; + DBUG_ENTER("make_huff_trees"); + + if (!(huff_tree=(HUFF_TREE*) my_malloc(trees*sizeof(HUFF_TREE), + MYF(MY_WME | MY_ZEROFILL)))) + DBUG_RETURN(0); + + for (tree=0 ; tree < trees ; tree++) + { + if (make_huff_tree(huff_tree+tree,huff_counts+tree)) + { + while (tree--) + my_free(huff_tree[tree].element_buffer,MYF(0)); + my_free(huff_tree,MYF(0)); + DBUG_RETURN(0); + } + } + DBUG_RETURN(huff_tree); +} + +/* + Build a Huffman tree. + + SYNOPSIS + make_huff_tree() + huff_tree The Huffman tree. + huff_counts The counts. + + DESCRIPTION + Build a Huffman tree according to huff_counts->counts or + huff_counts->tree_buff. tree_buff, if non-NULL contains up to + tree_buff_length of distinct column values. In that case, whole + values can be Huffman encoded instead of single bytes. + + RETURN + 0 OK + != 0 Error +*/ + +static int make_huff_tree(HUFF_TREE *huff_tree, HUFF_COUNTS *huff_counts) +{ + uint i,found,bits_packed,first,last; + my_off_t bytes_packed; + HUFF_ELEMENT *a,*b,*new_huff_el; + + first=last=0; + if (huff_counts->tree_buff) + { + /* Calculate the number of distinct values in tree_buff. */ + found= (uint) (huff_counts->tree_pos - huff_counts->tree_buff) / + huff_counts->field_length; + first=0; last=found-1; + } + else + { + /* Count the number of uchar codes found in the column. */ + for (i=found=0 ; i < 256 ; i++) + { + if (huff_counts->counts[i]) + { + if (! found++) + first=i; + last=i; + } + } + if (found < 2) + found=2; + } + + /* When using 'tree_buff' we can have more that 256 values. */ + if (queue.max_elements < found) + { + delete_queue(&queue); + if (init_queue(&queue,found,0,0,compare_huff_elements,0)) + return -1; + } + + /* Allocate or reallocate an element buffer for the Huffman tree. */ + if (!huff_tree->element_buffer) + { + if (!(huff_tree->element_buffer= + (HUFF_ELEMENT*) my_malloc(found*2*sizeof(HUFF_ELEMENT),MYF(MY_WME)))) + return 1; + } + else + { + HUFF_ELEMENT *temp; + if (!(temp= + (HUFF_ELEMENT*) my_realloc((uchar*) huff_tree->element_buffer, + found*2*sizeof(HUFF_ELEMENT), + MYF(MY_WME)))) + return 1; + huff_tree->element_buffer=temp; + } + + huff_counts->tree=huff_tree; + huff_tree->counts=huff_counts; + huff_tree->min_chr=first; + huff_tree->max_chr=last; + huff_tree->char_bits=max_bit(last-first); + huff_tree->offset_bits=max_bit(found-1)+1; + + if (huff_counts->tree_buff) + { + huff_tree->elements=0; + huff_tree->tree_pack_length=(1+15+16+5+5+ + (huff_tree->char_bits+1)*found+ + (huff_tree->offset_bits+1)* + (found-2)+7)/8 + + (uint) (huff_tree->counts->tree_pos- + huff_tree->counts->tree_buff); + /* + Put a HUFF_ELEMENT into the queue for every distinct column value. + + tree_walk() calls save_counts_in_queue() for every element in + 'int_tree'. This takes elements from the target trees element + buffer and places references to them into the buffer of the + priority queue. We insert in column value order, but the order is + in fact irrelevant here. We will establish the correct order + later. + */ + tree_walk(&huff_counts->int_tree, + (int (*)(void*, element_count,void*)) save_counts_in_queue, + (uchar*) huff_tree, left_root_right); + } + else + { + huff_tree->elements=found; + huff_tree->tree_pack_length=(9+9+5+5+ + (huff_tree->char_bits+1)*found+ + (huff_tree->offset_bits+1)* + (found-2)+7)/8; + /* + Put a HUFF_ELEMENT into the queue for every uchar code found in the column. + + The elements are taken from the target trees element buffer. + Instead of using queue_insert(), we just place references to the + elements into the buffer of the priority queue. We insert in byte + value order, but the order is in fact irrelevant here. We will + establish the correct order later. + */ + for (i=first, found=0 ; i <= last ; i++) + { + if (huff_counts->counts[i]) + { + new_huff_el=huff_tree->element_buffer+(found++); + new_huff_el->count=huff_counts->counts[i]; + new_huff_el->a.leaf.null=0; + new_huff_el->a.leaf.element_nr=i; + queue.root[found]=(uchar*) new_huff_el; + } + } + /* + If there is only a single uchar value in this field in all records, + add a second element with zero incidence. This is required to enter + the loop, which builds the Huffman tree. + */ + while (found < 2) + { + new_huff_el=huff_tree->element_buffer+(found++); + new_huff_el->count=0; + new_huff_el->a.leaf.null=0; + if (last) + new_huff_el->a.leaf.element_nr=huff_tree->min_chr=last-1; + else + new_huff_el->a.leaf.element_nr=huff_tree->max_chr=last+1; + queue.root[found]=(uchar*) new_huff_el; + } + } + + /* Make a queue from the queue buffer. */ + queue.elements=found; + + /* + Make a priority queue from the queue. Construct its index so that we + have a partially ordered tree. + */ + for (i=found/2 ; i > 0 ; i--) + _downheap(&queue,i); + + /* The Huffman algorithm. */ + bytes_packed=0; bits_packed=0; + for (i=1 ; i < found ; i++) + { + /* + Pop the top element from the queue (the one with the least incidence). + Popping from a priority queue includes a re-ordering of the queue, + to get the next least incidence element to the top. + */ + a=(HUFF_ELEMENT*) queue_remove(&queue,0); + /* + Copy the next least incidence element. The queue implementation + reserves root[0] for temporary purposes. root[1] is the top. + */ + b=(HUFF_ELEMENT*) queue.root[1]; + /* Get a new element from the element buffer. */ + new_huff_el=huff_tree->element_buffer+found+i; + /* The new element gets the sum of the two least incidence elements. */ + new_huff_el->count=a->count+b->count; + /* + The Huffman algorithm assigns another bit to the code for a byte + every time that bytes incidence is combined (directly or indirectly) + to a new element as one of the two least incidence elements. + This means that one more bit per incidence of that uchar is required + in the resulting file. So we add the new combined incidence as the + number of bits by which the result grows. + */ + bits_packed+=(uint) (new_huff_el->count & 7); + bytes_packed+=new_huff_el->count/8; + /* The new element points to its children, lesser in left. */ + new_huff_el->a.nod.left=a; + new_huff_el->a.nod.right=b; + /* + Replace the copied top element by the new element and re-order the + queue. + */ + queue.root[1]=(uchar*) new_huff_el; + queue_replaced(&queue); + } + huff_tree->root=(HUFF_ELEMENT*) queue.root[1]; + huff_tree->bytes_packed=bytes_packed+(bits_packed+7)/8; + return 0; +} + +static int compare_tree(void* cmp_arg __attribute__((unused)), + register const uchar *s, register const uchar *t) +{ + uint length; + for (length=global_count->field_length; length-- ;) + if (*s++ != *t++) + return (int) s[-1] - (int) t[-1]; + return 0; +} + +/* + Organize distinct column values and their incidences into a priority queue. + + SYNOPSIS + save_counts_in_queue() + key The column value. + count The incidence of this value. + tree The Huffman tree to be built later. + + DESCRIPTION + We use the element buffer of the targeted tree. The distinct column + values are organized in a priority queue first. The Huffman + algorithm will later organize the elements into a Huffman tree. For + the time being, we just place references to the elements into the + queue buffer. The buffer will later be organized into a priority + queue. + + RETURN + 0 + */ + +static int save_counts_in_queue(uchar *key, element_count count, + HUFF_TREE *tree) +{ + HUFF_ELEMENT *new_huff_el; + + new_huff_el=tree->element_buffer+(tree->elements++); + new_huff_el->count=count; + new_huff_el->a.leaf.null=0; + new_huff_el->a.leaf.element_nr= (uint) (key- tree->counts->tree_buff) / + tree->counts->field_length; + queue.root[tree->elements]=(uchar*) new_huff_el; + return 0; +} + + +/* + Calculate length of file if given counts should be used. + + SYNOPSIS + calc_packed_length() + huff_counts The counts for a column of the table(s). + add_tree_lenght If the decode tree length should be added. + + DESCRIPTION + We need to follow the Huffman algorithm until we know, how many bits + are required for each uchar code. But we do not need the resulting + Huffman tree. Hence, we can leave out some steps which are essential + in make_huff_tree(). + + RETURN + Number of bytes required to compress this table column. +*/ + +static my_off_t calc_packed_length(HUFF_COUNTS *huff_counts, + uint add_tree_lenght) +{ + uint i,found,bits_packed,first,last; + my_off_t bytes_packed; + HUFF_ELEMENT element_buffer[256]; + DBUG_ENTER("calc_packed_length"); + + /* + WARNING: We use a small hack for efficiency: Instead of placing + references to HUFF_ELEMENTs into the queue, we just insert + references to the counts of the uchar codes which appeared in this + table column. During the Huffman algorithm they are successively + replaced by references to HUFF_ELEMENTs. This works, because + HUFF_ELEMENTs have the incidence count at their beginning. + Regardless, wether the queue array contains references to counts of + type my_off_t or references to HUFF_ELEMENTs which have the count of + type my_off_t at their beginning, it always points to a count of the + same type. + + Instead of using queue_insert(), we just copy the references into + the buffer of the priority queue. We insert in uchar value order, but + the order is in fact irrelevant here. We will establish the correct + order later. + */ + first=last=0; + for (i=found=0 ; i < 256 ; i++) + { + if (huff_counts->counts[i]) + { + if (! found++) + first=i; + last=i; + /* We start with root[1], which is the queues top element. */ + queue.root[found]=(uchar*) &huff_counts->counts[i]; + } + } + if (!found) + DBUG_RETURN(0); /* Empty tree */ + /* + If there is only a single uchar value in this field in all records, + add a second element with zero incidence. This is required to enter + the loop, which follows the Huffman algorithm. + */ + if (found < 2) + queue.root[++found]=(uchar*) &huff_counts->counts[last ? 0 : 1]; + + /* Make a queue from the queue buffer. */ + queue.elements=found; + + bytes_packed=0; bits_packed=0; + /* Add the length of the coding table, which would become part of the file. */ + if (add_tree_lenght) + bytes_packed=(8+9+5+5+(max_bit(last-first)+1)*found+ + (max_bit(found-1)+1+1)*(found-2) +7)/8; + + /* + Make a priority queue from the queue. Construct its index so that we + have a partially ordered tree. + */ + for (i=(found+1)/2 ; i > 0 ; i--) + _downheap(&queue,i); + + /* The Huffman algorithm. */ + for (i=0 ; i < found-1 ; i++) + { + my_off_t *a; + my_off_t *b; + HUFF_ELEMENT *new_huff_el; + + /* + Pop the top element from the queue (the one with the least + incidence). Popping from a priority queue includes a re-ordering + of the queue, to get the next least incidence element to the top. + */ + a= (my_off_t*) queue_remove(&queue, 0); + /* + Copy the next least incidence element. The queue implementation + reserves root[0] for temporary purposes. root[1] is the top. + */ + b= (my_off_t*) queue.root[1]; + /* Create a new element in a local (automatic) buffer. */ + new_huff_el= element_buffer + i; + /* The new element gets the sum of the two least incidence elements. */ + new_huff_el->count= *a + *b; + /* + The Huffman algorithm assigns another bit to the code for a byte + every time that bytes incidence is combined (directly or indirectly) + to a new element as one of the two least incidence elements. + This means that one more bit per incidence of that uchar is required + in the resulting file. So we add the new combined incidence as the + number of bits by which the result grows. + */ + bits_packed+=(uint) (new_huff_el->count & 7); + bytes_packed+=new_huff_el->count/8; + /* + Replace the copied top element by the new element and re-order the + queue. This successively replaces the references to counts by + references to HUFF_ELEMENTs. + */ + queue.root[1]=(uchar*) new_huff_el; + queue_replaced(&queue); + } + DBUG_RETURN(bytes_packed+(bits_packed+7)/8); +} + + + /* Remove trees that don't give any compression */ + +static uint join_same_trees(HUFF_COUNTS *huff_counts, uint trees) +{ + uint k,tree_number; + HUFF_COUNTS count,*i,*j,*last_count; + + last_count=huff_counts+trees; + for (tree_number=0, i=huff_counts ; i < last_count ; i++) + { + if (!i->tree->tree_number) + { + i->tree->tree_number= ++tree_number; + if (i->tree_buff) + continue; /* Don't join intervall */ + for (j=i+1 ; j < last_count ; j++) + { + if (! j->tree->tree_number && ! j->tree_buff) + { + for (k=0 ; k < 256 ; k++) + count.counts[k]=i->counts[k]+j->counts[k]; + if (calc_packed_length(&count,1) <= + i->tree->bytes_packed + j->tree->bytes_packed+ + i->tree->tree_pack_length+j->tree->tree_pack_length+ + ALLOWED_JOIN_DIFF) + { + memcpy_fixed((uchar*) i->counts,(uchar*) count.counts, + sizeof(count.counts[0])*256); + my_free((uchar*) j->tree->element_buffer,MYF(0)); + j->tree->element_buffer=0; + j->tree=i->tree; + bmove((uchar*) i->counts,(uchar*) count.counts, + sizeof(count.counts[0])*256); + if (make_huff_tree(i->tree,i)) + return (uint) -1; + } + } + } + } + } + DBUG_PRINT("info", ("Original trees: %d After join: %d", + trees, tree_number)); + if (verbose) + VOID(printf("Original trees: %d After join: %d\n", trees, tree_number)); + return tree_number; /* Return trees left */ +} + + +/* + Fill in huff_tree encode tables. + + SYNOPSIS + make_huff_decode_table() + huff_tree An array of HUFF_TREE which are to be encoded. + trees The number of HUFF_TREE in the array. + + RETURN + 0 success + != 0 error +*/ + +static int make_huff_decode_table(HUFF_TREE *huff_tree, uint trees) +{ + uint elements; + for ( ; trees-- ; huff_tree++) + { + if (huff_tree->tree_number > 0) + { + elements=huff_tree->counts->tree_buff ? huff_tree->elements : 256; + if (!(huff_tree->code = + (ulonglong*) my_malloc(elements* + (sizeof(ulonglong) + sizeof(uchar)), + MYF(MY_WME | MY_ZEROFILL)))) + return 1; + huff_tree->code_len=(uchar*) (huff_tree->code+elements); + make_traverse_code_tree(huff_tree, huff_tree->root, + 8 * sizeof(ulonglong), LL(0)); + } + } + return 0; +} + + +static void make_traverse_code_tree(HUFF_TREE *huff_tree, + HUFF_ELEMENT *element, + uint size, ulonglong code) +{ + uint chr; + if (!element->a.leaf.null) + { + chr=element->a.leaf.element_nr; + huff_tree->code_len[chr]= (uchar) (8 * sizeof(ulonglong) - size); + huff_tree->code[chr]= (code >> size); + if (huff_tree->height < 8 * sizeof(ulonglong) - size) + huff_tree->height= 8 * sizeof(ulonglong) - size; + } + else + { + size--; + make_traverse_code_tree(huff_tree,element->a.nod.left,size,code); + make_traverse_code_tree(huff_tree, element->a.nod.right, size, + code + (((ulonglong) 1) << size)); + } + return; +} + + +/* + Convert a value into binary digits. + + SYNOPSIS + bindigits() + value The value. + length The number of low order bits to convert. + + NOTE + The result string is in static storage. It is reused on every call. + So you cannot use it twice in one expression. + + RETURN + A pointer to a static NUL-terminated string. + */ + +static char *bindigits(ulonglong value, uint bits) +{ + static char digits[72]; + char *ptr= digits; + uint idx= bits; + + DBUG_ASSERT(idx < sizeof(digits)); + while (idx) + *(ptr++)= '0' + ((char) (value >> (--idx)) & (char) 1); + *ptr= '\0'; + return digits; +} + + +/* + Convert a value into hexadecimal digits. + + SYNOPSIS + hexdigits() + value The value. + + NOTE + The result string is in static storage. It is reused on every call. + So you cannot use it twice in one expression. + + RETURN + A pointer to a static NUL-terminated string. + */ + +static char *hexdigits(ulonglong value) +{ + static char digits[20]; + char *ptr= digits; + uint idx= 2 * sizeof(value); /* Two hex digits per byte. */ + + DBUG_ASSERT(idx < sizeof(digits)); + while (idx) + { + if ((*(ptr++)= '0' + ((char) (value >> (4 * (--idx))) & (char) 0xf)) > '9') + *(ptr - 1)+= 'a' - '9' - 1; + } + *ptr= '\0'; + return digits; +} + + + /* Write header to new packed data file */ + +static int write_header(PACK_MRG_INFO *mrg,uint head_length,uint trees, + my_off_t tot_elements,my_off_t filelength) +{ + uchar *buff= (uchar*) file_buffer.pos; + + bzero(buff,HEAD_LENGTH); + memcpy_fixed(buff,maria_pack_file_magic,4); + int4store(buff+4,head_length); + int4store(buff+8, mrg->min_pack_length); + int4store(buff+12,mrg->max_pack_length); + int4store(buff+16,tot_elements); + int4store(buff+20,intervall_length); + int2store(buff+24,trees); + buff[26]=(char) mrg->ref_length; + /* Save record pointer length */ + buff[27]= (uchar) maria_get_pointer_length((ulonglong) filelength,2); + if (test_only) + return 0; + VOID(my_seek(file_buffer.file,0L,MY_SEEK_SET,MYF(0))); + return my_write(file_buffer.file,(const uchar *) file_buffer.pos,HEAD_LENGTH, + MYF(MY_WME | MY_NABP | MY_WAIT_IF_FULL)) != 0; +} + + /* Write fieldinfo to new packed file */ + +static void write_field_info(HUFF_COUNTS *counts, uint fields, uint trees) +{ + reg1 uint i; + uint huff_tree_bits; + huff_tree_bits=max_bit(trees ? trees-1 : 0); + + DBUG_PRINT("info", (" ")); + DBUG_PRINT("info", ("column types:")); + DBUG_PRINT("info", ("FIELD_NORMAL 0")); + DBUG_PRINT("info", ("FIELD_SKIP_ENDSPACE 1")); + DBUG_PRINT("info", ("FIELD_SKIP_PRESPACE 2")); + DBUG_PRINT("info", ("FIELD_SKIP_ZERO 3")); + DBUG_PRINT("info", ("FIELD_BLOB 4")); + DBUG_PRINT("info", ("FIELD_CONSTANT 5")); + DBUG_PRINT("info", ("FIELD_INTERVALL 6")); + DBUG_PRINT("info", ("FIELD_ZERO 7")); + DBUG_PRINT("info", ("FIELD_VARCHAR 8")); + DBUG_PRINT("info", ("FIELD_CHECK 9")); + DBUG_PRINT("info", (" ")); + DBUG_PRINT("info", ("pack type as a set of flags:")); + DBUG_PRINT("info", ("PACK_TYPE_SELECTED 1")); + DBUG_PRINT("info", ("PACK_TYPE_SPACE_FIELDS 2")); + DBUG_PRINT("info", ("PACK_TYPE_ZERO_FILL 4")); + DBUG_PRINT("info", (" ")); + if (verbose >= 2) + { + VOID(printf("\n")); + VOID(printf("column types:\n")); + VOID(printf("FIELD_NORMAL 0\n")); + VOID(printf("FIELD_SKIP_ENDSPACE 1\n")); + VOID(printf("FIELD_SKIP_PRESPACE 2\n")); + VOID(printf("FIELD_SKIP_ZERO 3\n")); + VOID(printf("FIELD_BLOB 4\n")); + VOID(printf("FIELD_CONSTANT 5\n")); + VOID(printf("FIELD_INTERVALL 6\n")); + VOID(printf("FIELD_ZERO 7\n")); + VOID(printf("FIELD_VARCHAR 8\n")); + VOID(printf("FIELD_CHECK 9\n")); + VOID(printf("\n")); + VOID(printf("pack type as a set of flags:\n")); + VOID(printf("PACK_TYPE_SELECTED 1\n")); + VOID(printf("PACK_TYPE_SPACE_FIELDS 2\n")); + VOID(printf("PACK_TYPE_ZERO_FILL 4\n")); + VOID(printf("\n")); + } + for (i=0 ; i++ < fields ; counts++) + { + write_bits((ulonglong) (int) counts->field_type, 5); + write_bits(counts->pack_type,6); + if (counts->pack_type & PACK_TYPE_ZERO_FILL) + write_bits(counts->max_zero_fill,5); + else + write_bits(counts->length_bits,5); + write_bits((ulonglong) counts->tree->tree_number - 1, huff_tree_bits); + DBUG_PRINT("info", ("column: %3u type: %2u pack: %2u zero: %4u " + "lbits: %2u tree: %2u length: %4u", + i , counts->field_type, counts->pack_type, + counts->max_zero_fill, counts->length_bits, + counts->tree->tree_number, counts->field_length)); + if (verbose >= 2) + VOID(printf("column: %3u type: %2u pack: %2u zero: %4u lbits: %2u " + "tree: %2u length: %4u\n", i , counts->field_type, + counts->pack_type, counts->max_zero_fill, counts->length_bits, + counts->tree->tree_number, counts->field_length)); + } + flush_bits(); + return; +} + + /* Write all huff_trees to new datafile. Return tot count of + elements in all trees + Returns 0 on error */ + +static my_off_t write_huff_tree(HUFF_TREE *huff_tree, uint trees) +{ + uint i,int_length; + uint tree_no; + uint codes; + uint errors= 0; + uint *packed_tree,*offset,length; + my_off_t elements; + + /* Find the highest number of elements in the trees. */ + for (i=length=0 ; i < trees ; i++) + if (huff_tree[i].tree_number > 0 && huff_tree[i].elements > length) + length=huff_tree[i].elements; + /* + Allocate a buffer for packing a decode tree. Two numbers per element + (left child and right child). + */ + if (!(packed_tree=(uint*) my_alloca(sizeof(uint)*length*2))) + { + my_error(EE_OUTOFMEMORY,MYF(ME_BELL),sizeof(uint)*length*2); + return 0; + } + + DBUG_PRINT("info", (" ")); + if (verbose >= 2) + VOID(printf("\n")); + tree_no= 0; + intervall_length=0; + for (elements=0; trees-- ; huff_tree++) + { + /* Skip columns that have been joined with other columns. */ + if (huff_tree->tree_number == 0) + continue; /* Deleted tree */ + tree_no++; + DBUG_PRINT("info", (" ")); + if (verbose >= 3) + VOID(printf("\n")); + /* Count the total number of elements (byte codes or column values). */ + elements+=huff_tree->elements; + huff_tree->max_offset=2; + /* Build a tree of offsets and codes for decoding in 'packed_tree'. */ + if (huff_tree->elements <= 1) + offset=packed_tree; + else + offset=make_offset_code_tree(huff_tree,huff_tree->root,packed_tree); + + /* This should be the same as 'length' above. */ + huff_tree->offset_bits=max_bit(huff_tree->max_offset); + + /* + Since we check this during collecting the distinct column values, + this should never happen. + */ + if (huff_tree->max_offset >= IS_OFFSET) + { /* This should be impossible */ + VOID(fprintf(stderr, "Tree offset got too big: %d, aborted\n", + huff_tree->max_offset)); + my_afree(packed_tree); + return 0; + } + + DBUG_PRINT("info", ("pos: %lu elements: %u tree-elements: %lu " + "char_bits: %u\n", + (ulong) (file_buffer.pos - file_buffer.buffer), + huff_tree->elements, (ulong) (offset - packed_tree), + huff_tree->char_bits)); + if (!huff_tree->counts->tree_buff) + { + /* We do a uchar compression on this column. Mark with bit 0. */ + write_bits(0,1); + write_bits(huff_tree->min_chr,8); + write_bits(huff_tree->elements,9); + write_bits(huff_tree->char_bits,5); + write_bits(huff_tree->offset_bits,5); + int_length=0; + } + else + { + int_length=(uint) (huff_tree->counts->tree_pos - + huff_tree->counts->tree_buff); + /* We have distinct column values for this column. Mark with bit 1. */ + write_bits(1,1); + write_bits(huff_tree->elements,15); + write_bits(int_length,16); + write_bits(huff_tree->char_bits,5); + write_bits(huff_tree->offset_bits,5); + intervall_length+=int_length; + } + DBUG_PRINT("info", ("tree: %2u elements: %4u char_bits: %2u " + "offset_bits: %2u %s: %5u codelen: %2u", + tree_no, huff_tree->elements, huff_tree->char_bits, + huff_tree->offset_bits, huff_tree->counts->tree_buff ? + "bufflen" : "min_chr", huff_tree->counts->tree_buff ? + int_length : huff_tree->min_chr, huff_tree->height)); + if (verbose >= 2) + VOID(printf("tree: %2u elements: %4u char_bits: %2u offset_bits: %2u " + "%s: %5u codelen: %2u\n", tree_no, huff_tree->elements, + huff_tree->char_bits, huff_tree->offset_bits, + huff_tree->counts->tree_buff ? "bufflen" : "min_chr", + huff_tree->counts->tree_buff ? int_length : + huff_tree->min_chr, huff_tree->height)); + + /* Check that the code tree length matches the element count. */ + length=(uint) (offset-packed_tree); + if (length != huff_tree->elements*2-2) + { + VOID(fprintf(stderr, "error: Huff-tree-length: %d != calc_length: %d\n", + length, huff_tree->elements * 2 - 2)); + errors++; + break; + } + + for (i=0 ; i < length ; i++) + { + if (packed_tree[i] & IS_OFFSET) + write_bits(packed_tree[i] - IS_OFFSET+ (1 << huff_tree->offset_bits), + huff_tree->offset_bits+1); + else + write_bits(packed_tree[i]-huff_tree->min_chr,huff_tree->char_bits+1); + DBUG_PRINT("info", ("tree[0x%04x]: %s0x%04x", + i, (packed_tree[i] & IS_OFFSET) ? + " -> " : "", (packed_tree[i] & IS_OFFSET) ? + packed_tree[i] - IS_OFFSET + i : packed_tree[i])); + if (verbose >= 3) + VOID(printf("tree[0x%04x]: %s0x%04x\n", + i, (packed_tree[i] & IS_OFFSET) ? " -> " : "", + (packed_tree[i] & IS_OFFSET) ? + packed_tree[i] - IS_OFFSET + i : packed_tree[i])); + } + flush_bits(); + + /* + Display coding tables and check their correctness. + */ + codes= huff_tree->counts->tree_buff ? huff_tree->elements : 256; + for (i= 0; i < codes; i++) + { + ulonglong code; + uint bits; + uint len; + uint idx; + + if (! (len= huff_tree->code_len[i])) + continue; + DBUG_PRINT("info", ("code[0x%04x]: 0x%s bits: %2u bin: %s", i, + hexdigits(huff_tree->code[i]), huff_tree->code_len[i], + bindigits(huff_tree->code[i], + huff_tree->code_len[i]))); + if (verbose >= 3) + VOID(printf("code[0x%04x]: 0x%s bits: %2u bin: %s\n", i, + hexdigits(huff_tree->code[i]), huff_tree->code_len[i], + bindigits(huff_tree->code[i], huff_tree->code_len[i]))); + + /* Check that the encode table decodes correctly. */ + code= 0; + bits= 0; + idx= 0; + DBUG_EXECUTE_IF("forcechkerr1", len--;); + DBUG_EXECUTE_IF("forcechkerr2", bits= 8 * sizeof(code);); + DBUG_EXECUTE_IF("forcechkerr3", idx= length;); + for (;;) + { + if (! len) + { + VOID(fflush(stdout)); + VOID(fprintf(stderr, "error: code 0x%s with %u bits not found\n", + hexdigits(huff_tree->code[i]), huff_tree->code_len[i])); + errors++; + break; + } + code<<= 1; + code|= (huff_tree->code[i] >> (--len)) & 1; + bits++; + if (bits > 8 * sizeof(code)) + { + VOID(fflush(stdout)); + VOID(fprintf(stderr, "error: Huffman code too long: %u/%u\n", + bits, (uint) (8 * sizeof(code)))); + errors++; + break; + } + idx+= (uint) code & 1; + if (idx >= length) + { + VOID(fflush(stdout)); + VOID(fprintf(stderr, "error: illegal tree offset: %u/%u\n", + idx, length)); + errors++; + break; + } + if (packed_tree[idx] & IS_OFFSET) + idx+= packed_tree[idx] & ~IS_OFFSET; + else + break; /* Hit a leaf. This contains the result value. */ + } + if (errors) + break; + + DBUG_EXECUTE_IF("forcechkerr4", packed_tree[idx]++;); + if (packed_tree[idx] != i) + { + VOID(fflush(stdout)); + VOID(fprintf(stderr, "error: decoded value 0x%04x should be: 0x%04x\n", + packed_tree[idx], i)); + errors++; + break; + } + } /*end for (codes)*/ + if (errors) + break; + + /* Write column values in case of distinct column value compression. */ + if (huff_tree->counts->tree_buff) + { + for (i=0 ; i < int_length ; i++) + { + write_bits((ulonglong) (uchar) huff_tree->counts->tree_buff[i], 8); + DBUG_PRINT("info", ("column_values[0x%04x]: 0x%02x", + i, (uchar) huff_tree->counts->tree_buff[i])); + if (verbose >= 3) + VOID(printf("column_values[0x%04x]: 0x%02x\n", + i, (uchar) huff_tree->counts->tree_buff[i])); + } + } + flush_bits(); + } + DBUG_PRINT("info", (" ")); + if (verbose >= 2) + VOID(printf("\n")); + my_afree(packed_tree); + if (errors) + { + VOID(fprintf(stderr, "Error: Generated decode trees are corrupt. Stop.\n")); + return 0; + } + return elements; +} + + +static uint *make_offset_code_tree(HUFF_TREE *huff_tree, HUFF_ELEMENT *element, + uint *offset) +{ + uint *prev_offset; + + prev_offset= offset; + /* + 'a.leaf.null' takes the same place as 'a.nod.left'. If this is null, + then there is no left child and, hence no right child either. This + is a property of a binary tree. An element is either a node with two + childs, or a leaf without childs. + + The current element is always a node with two childs. Go left first. + */ + if (!element->a.nod.left->a.leaf.null) + { + /* Store the uchar code or the index of the column value. */ + prev_offset[0] =(uint) element->a.nod.left->a.leaf.element_nr; + offset+=2; + } + else + { + /* + Recursively traverse the tree to the left. Mark it as an offset to + another tree node (in contrast to a uchar code or column value index). + */ + prev_offset[0]= IS_OFFSET+2; + offset=make_offset_code_tree(huff_tree,element->a.nod.left,offset+2); + } + + /* Now, check the right child. */ + if (!element->a.nod.right->a.leaf.null) + { + /* Store the uchar code or the index of the column value. */ + prev_offset[1]=element->a.nod.right->a.leaf.element_nr; + return offset; + } + else + { + /* + Recursively traverse the tree to the right. Mark it as an offset to + another tree node (in contrast to a uchar code or column value index). + */ + uint temp=(uint) (offset-prev_offset-1); + prev_offset[1]= IS_OFFSET+ temp; + if (huff_tree->max_offset < temp) + huff_tree->max_offset = temp; + return make_offset_code_tree(huff_tree,element->a.nod.right,offset); + } +} + + /* Get number of bits neaded to represent value */ + +static uint max_bit(register uint value) +{ + reg2 uint power=1; + + while ((value>>=1)) + power++; + return (power); +} + + +static int compress_maria_file(PACK_MRG_INFO *mrg, HUFF_COUNTS *huff_counts) +{ + int error; + uint i,max_calc_length,pack_ref_length,min_record_length,max_record_length; + uint intervall,field_length,max_pack_length,pack_blob_length, null_bytes; + my_off_t record_count; + char llbuf[32]; + ulong length,pack_length; + uchar *record,*pos,*end_pos,*record_pos,*start_pos; + HUFF_COUNTS *count,*end_count; + HUFF_TREE *tree; + MARIA_HA *isam_file=mrg->file[0]; + uint pack_version= (uint) isam_file->s->pack.version; + DBUG_ENTER("compress_maria_file"); + + /* Allocate a buffer for the records (excluding blobs). */ + if (!(record=(uchar*) my_alloca(isam_file->s->base.reclength))) + return -1; + + end_count=huff_counts+isam_file->s->base.fields; + min_record_length= (uint) ~0; + max_record_length=0; + null_bytes= isam_file->s->base.null_bytes; + + /* + Calculate the maximum number of bits required to pack the records. + Remember to understand 'max_zero_fill' as 'min_zero_fill'. + The tree height determines the maximum number of bits per value. + Some fields skip leading or trailing spaces or zeroes. The skipped + number of bytes is encoded by 'length_bits' bits. + Empty blobs and varchar are encoded with a single 1 bit. Other blobs + and varchar get a leading 0 bit. + */ + max_calc_length= null_bytes; + for (i= 0 ; i < isam_file->s->base.fields ; i++) + { + if (!(huff_counts[i].pack_type & PACK_TYPE_ZERO_FILL)) + huff_counts[i].max_zero_fill=0; + if (huff_counts[i].field_type == FIELD_CONSTANT || + huff_counts[i].field_type == FIELD_ZERO || + huff_counts[i].field_type == FIELD_CHECK) + continue; + if (huff_counts[i].field_type == FIELD_INTERVALL) + max_calc_length+=huff_counts[i].tree->height; + else if (huff_counts[i].field_type == FIELD_BLOB || + huff_counts[i].field_type == FIELD_VARCHAR) + max_calc_length+=huff_counts[i].tree->height*huff_counts[i].max_length + huff_counts[i].length_bits +1; + else + max_calc_length+= + (huff_counts[i].field_length - huff_counts[i].max_zero_fill)* + huff_counts[i].tree->height+huff_counts[i].length_bits; + } + max_calc_length= (max_calc_length + 7) / 8; + pack_ref_length= _ma_calc_pack_length(pack_version, max_calc_length); + record_count=0; + /* 'max_blob_length' is the max length of all blobs of a record. */ + pack_blob_length= isam_file->s->base.blobs ? + _ma_calc_pack_length(pack_version, mrg->max_blob_length) : 0; + max_pack_length=pack_ref_length+pack_blob_length; + + DBUG_PRINT("fields", ("===")); + mrg_reset(mrg); + while ((error=mrg_rrnd(mrg,record)) != HA_ERR_END_OF_FILE) + { + ulong tot_blob_length=0; + if (! error) + { + if (flush_buffer((ulong) max_calc_length + (ulong) max_pack_length + + null_bytes)) + break; + record_pos= file_buffer.pos; + file_buffer.pos+= max_pack_length; + if (null_bytes) + { + /* Copy null bits 'as is' */ + memcpy(file_buffer.pos, record, null_bytes); + file_buffer.pos+= null_bytes; + } + for (start_pos=record+null_bytes, count= huff_counts; + count < end_count ; + count++) + { + end_pos=start_pos+(field_length=count->field_length); + tree=count->tree; + + DBUG_PRINT("fields", ("column: %3lu type: %2u pack: %2u zero: %4u " + "lbits: %2u tree: %2u length: %4u", + (ulong) (count - huff_counts + 1), + count->field_type, + count->pack_type, count->max_zero_fill, + count->length_bits, count->tree->tree_number, + count->field_length)); + + /* Check if the column contains spaces only. */ + if (count->pack_type & PACK_TYPE_SPACE_FIELDS) + { + for (pos=start_pos ; *pos == ' ' && pos < end_pos; pos++) ; + if (pos == end_pos) + { + DBUG_PRINT("fields", + ("PACK_TYPE_SPACE_FIELDS spaces only, bits: 1")); + DBUG_PRINT("fields", ("---")); + write_bits(1,1); + start_pos=end_pos; + continue; + } + DBUG_PRINT("fields", + ("PACK_TYPE_SPACE_FIELDS not only spaces, bits: 1")); + write_bits(0,1); + } + end_pos-=count->max_zero_fill; + field_length-=count->max_zero_fill; + + switch (count->field_type) { + case FIELD_SKIP_ZERO: + if (!memcmp(start_pos, zero_string, field_length)) + { + DBUG_PRINT("fields", ("FIELD_SKIP_ZERO zeroes only, bits: 1")); + write_bits(1,1); + start_pos=end_pos; + break; + } + DBUG_PRINT("fields", ("FIELD_SKIP_ZERO not only zeroes, bits: 1")); + write_bits(0,1); + /* Fall through */ + case FIELD_NORMAL: + DBUG_PRINT("fields", ("FIELD_NORMAL %lu bytes", + (ulong) (end_pos - start_pos))); + for ( ; start_pos < end_pos ; start_pos++) + { + DBUG_PRINT("fields", + ("value: 0x%02x code: 0x%s bits: %2u bin: %s", + (uchar) *start_pos, + hexdigits(tree->code[(uchar) *start_pos]), + (uint) tree->code_len[(uchar) *start_pos], + bindigits(tree->code[(uchar) *start_pos], + (uint) tree->code_len[(uchar) *start_pos]))); + write_bits(tree->code[(uchar) *start_pos], + (uint) tree->code_len[(uchar) *start_pos]); + } + break; + case FIELD_SKIP_ENDSPACE: + for (pos=end_pos ; pos > start_pos && pos[-1] == ' ' ; pos--) ; + length= (ulong) (end_pos - pos); + if (count->pack_type & PACK_TYPE_SELECTED) + { + if (length > count->min_space) + { + DBUG_PRINT("fields", + ("FIELD_SKIP_ENDSPACE more than min_space, bits: 1")); + DBUG_PRINT("fields", + ("FIELD_SKIP_ENDSPACE skip %lu/%u bytes, bits: %2u", + length, field_length, count->length_bits)); + write_bits(1,1); + write_bits(length,count->length_bits); + } + else + { + DBUG_PRINT("fields", + ("FIELD_SKIP_ENDSPACE not more than min_space, " + "bits: 1")); + write_bits(0,1); + pos=end_pos; + } + } + else + { + DBUG_PRINT("fields", + ("FIELD_SKIP_ENDSPACE skip %lu/%u bytes, bits: %2u", + length, field_length, count->length_bits)); + write_bits(length,count->length_bits); + } + /* Encode all significant bytes. */ + DBUG_PRINT("fields", ("FIELD_SKIP_ENDSPACE %lu bytes", + (ulong) (pos - start_pos))); + for ( ; start_pos < pos ; start_pos++) + { + DBUG_PRINT("fields", + ("value: 0x%02x code: 0x%s bits: %2u bin: %s", + (uchar) *start_pos, + hexdigits(tree->code[(uchar) *start_pos]), + (uint) tree->code_len[(uchar) *start_pos], + bindigits(tree->code[(uchar) *start_pos], + (uint) tree->code_len[(uchar) *start_pos]))); + write_bits(tree->code[(uchar) *start_pos], + (uint) tree->code_len[(uchar) *start_pos]); + } + start_pos=end_pos; + break; + case FIELD_SKIP_PRESPACE: + for (pos=start_pos ; pos < end_pos && pos[0] == ' ' ; pos++) ; + length= (ulong) (pos - start_pos); + if (count->pack_type & PACK_TYPE_SELECTED) + { + if (length > count->min_space) + { + DBUG_PRINT("fields", + ("FIELD_SKIP_PRESPACE more than min_space, bits: 1")); + DBUG_PRINT("fields", + ("FIELD_SKIP_PRESPACE skip %lu/%u bytes, bits: %2u", + length, field_length, count->length_bits)); + write_bits(1,1); + write_bits(length,count->length_bits); + } + else + { + DBUG_PRINT("fields", + ("FIELD_SKIP_PRESPACE not more than min_space, " + "bits: 1")); + pos=start_pos; + write_bits(0,1); + } + } + else + { + DBUG_PRINT("fields", + ("FIELD_SKIP_PRESPACE skip %lu/%u bytes, bits: %2u", + length, field_length, count->length_bits)); + write_bits(length,count->length_bits); + } + /* Encode all significant bytes. */ + DBUG_PRINT("fields", ("FIELD_SKIP_PRESPACE %lu bytes", + (ulong) (end_pos - start_pos))); + for (start_pos=pos ; start_pos < end_pos ; start_pos++) + { + DBUG_PRINT("fields", + ("value: 0x%02x code: 0x%s bits: %2u bin: %s", + (uchar) *start_pos, + hexdigits(tree->code[(uchar) *start_pos]), + (uint) tree->code_len[(uchar) *start_pos], + bindigits(tree->code[(uchar) *start_pos], + (uint) tree->code_len[(uchar) *start_pos]))); + write_bits(tree->code[(uchar) *start_pos], + (uint) tree->code_len[(uchar) *start_pos]); + } + break; + case FIELD_CONSTANT: + case FIELD_ZERO: + case FIELD_CHECK: + DBUG_PRINT("fields", ("FIELD_CONSTANT/ZERO/CHECK")); + start_pos=end_pos; + break; + case FIELD_INTERVALL: + global_count=count; + pos=(uchar*) tree_search(&count->int_tree, start_pos, + count->int_tree.custom_arg); + intervall=(uint) (pos - count->tree_buff)/field_length; + DBUG_PRINT("fields", ("FIELD_INTERVALL")); + DBUG_PRINT("fields", ("index: %4u code: 0x%s bits: %2u", + intervall, hexdigits(tree->code[intervall]), + (uint) tree->code_len[intervall])); + write_bits(tree->code[intervall],(uint) tree->code_len[intervall]); + start_pos=end_pos; + break; + case FIELD_BLOB: + { + ulong blob_length= _ma_calc_blob_length(field_length- + portable_sizeof_char_ptr, + start_pos); + /* Empty blobs are encoded with a single 1 bit. */ + if (!blob_length) + { + DBUG_PRINT("fields", ("FIELD_BLOB empty, bits: 1")); + write_bits(1,1); + } + else + { + uchar *blob,*blob_end; + DBUG_PRINT("fields", ("FIELD_BLOB not empty, bits: 1")); + write_bits(0,1); + /* Write the blob length. */ + DBUG_PRINT("fields", ("FIELD_BLOB %lu bytes, bits: %2u", + blob_length, count->length_bits)); + write_bits(blob_length,count->length_bits); + memcpy_fixed(&blob,end_pos-portable_sizeof_char_ptr, + sizeof(char*)); + blob_end=blob+blob_length; + /* Encode the blob bytes. */ + for ( ; blob < blob_end ; blob++) + { + DBUG_PRINT("fields", + ("value: 0x%02x code: 0x%s bits: %2u bin: %s", + (uchar) *blob, hexdigits(tree->code[(uchar) *blob]), + (uint) tree->code_len[(uchar) *blob], + bindigits(tree->code[(uchar) *start_pos], + (uint)tree->code_len[(uchar) *start_pos]))); + write_bits(tree->code[(uchar) *blob], + (uint) tree->code_len[(uchar) *blob]); + } + tot_blob_length+=blob_length; + } + start_pos= end_pos; + break; + } + case FIELD_VARCHAR: + { + uint var_pack_length= HA_VARCHAR_PACKLENGTH(count->field_length-1); + ulong col_length= (var_pack_length == 1 ? + (uint) *(uchar*) start_pos : + uint2korr(start_pos)); + /* Empty varchar are encoded with a single 1 bit. */ + if (!col_length) + { + DBUG_PRINT("fields", ("FIELD_VARCHAR empty, bits: 1")); + write_bits(1,1); /* Empty varchar */ + } + else + { + uchar *end= start_pos + var_pack_length + col_length; + DBUG_PRINT("fields", ("FIELD_VARCHAR not empty, bits: 1")); + write_bits(0,1); + /* Write the varchar length. */ + DBUG_PRINT("fields", ("FIELD_VARCHAR %lu bytes, bits: %2u", + col_length, count->length_bits)); + write_bits(col_length,count->length_bits); + /* Encode the varchar bytes. */ + for (start_pos+= var_pack_length ; start_pos < end ; start_pos++) + { + DBUG_PRINT("fields", + ("value: 0x%02x code: 0x%s bits: %2u bin: %s", + (uchar) *start_pos, + hexdigits(tree->code[(uchar) *start_pos]), + (uint) tree->code_len[(uchar) *start_pos], + bindigits(tree->code[(uchar) *start_pos], + (uint)tree->code_len[(uchar) *start_pos]))); + write_bits(tree->code[(uchar) *start_pos], + (uint) tree->code_len[(uchar) *start_pos]); + } + } + start_pos= end_pos; + break; + } + case FIELD_LAST: + case FIELD_enum_val_count: + abort(); /* Impossible */ + } + start_pos+=count->max_zero_fill; + DBUG_PRINT("fields", ("---")); + } + flush_bits(); + length=(ulong) (file_buffer.pos - record_pos) - max_pack_length; + pack_length= _ma_save_pack_length(pack_version, record_pos, length); + if (pack_blob_length) + pack_length+= _ma_save_pack_length(pack_version, + record_pos + pack_length, + tot_blob_length); + DBUG_PRINT("fields", ("record: %lu length: %lu blob-length: %lu " + "length-bytes: %lu", (ulong) record_count, length, + tot_blob_length, pack_length)); + DBUG_PRINT("fields", ("===")); + + /* Correct file buffer if the header was smaller */ + if (pack_length != max_pack_length) + { + bmove(record_pos+pack_length,record_pos+max_pack_length,length); + file_buffer.pos-= (max_pack_length-pack_length); + } + if (length < (ulong) min_record_length) + min_record_length=(uint) length; + if (length > (ulong) max_record_length) + max_record_length=(uint) length; + record_count++; + if (write_loop && record_count % WRITE_COUNT == 0) + { + VOID(printf("%lu\r", (ulong) record_count)); + VOID(fflush(stdout)); + } + } + else if (error != HA_ERR_RECORD_DELETED) + break; + } + if (error == HA_ERR_END_OF_FILE) + error=0; + else + { + VOID(fprintf(stderr, "%s: Got error %d reading records\n", + my_progname, error)); + } + if (verbose >= 2) + VOID(printf("wrote %s records.\n", llstr((longlong) record_count, llbuf))); + + my_afree(record); + mrg->ref_length=max_pack_length; + mrg->min_pack_length=max_record_length ? min_record_length : 0; + mrg->max_pack_length=max_record_length; + DBUG_RETURN(error || error_on_write || flush_buffer(~(ulong) 0)); +} + + +static char *make_new_name(char *new_name, char *old_name) +{ + return fn_format(new_name,old_name,"",DATA_TMP_EXT,2+4); +} + +static char *make_old_name(char *new_name, char *old_name) +{ + return fn_format(new_name,old_name,"",OLD_EXT,2+4); +} + + /* rutines for bit writing buffer */ + +static void init_file_buffer(File file, pbool read_buffer) +{ + file_buffer.file=file; + file_buffer.buffer= (uchar*) my_malloc(ALIGN_SIZE(RECORD_CACHE_SIZE), + MYF(MY_WME)); + file_buffer.end=file_buffer.buffer+ALIGN_SIZE(RECORD_CACHE_SIZE)-8; + file_buffer.pos_in_file=0; + error_on_write=0; + if (read_buffer) + { + + file_buffer.pos=file_buffer.end; + file_buffer.bits=0; + } + else + { + file_buffer.pos=file_buffer.buffer; + file_buffer.bits=BITS_SAVED; + } + file_buffer.bitbucket= 0; +} + + +static int flush_buffer(ulong neaded_length) +{ + ulong length; + + /* + file_buffer.end is 8 bytes lower than the real end of the buffer. + This is done so that the end-of-buffer condition does not need to be + checked for every uchar (see write_bits()). Consequently, + file_buffer.pos can become greater than file_buffer.end. The + algorithms in the other functions ensure that there will never be + more than 8 bytes written to the buffer without an end-of-buffer + check. So the buffer cannot be overrun. But we need to check for the + near-to-buffer-end condition to avoid a negative result, which is + casted to unsigned and thus becomes giant. + */ + if ((file_buffer.pos < file_buffer.end) && + ((ulong) (file_buffer.end - file_buffer.pos) > neaded_length)) + return 0; + length=(ulong) (file_buffer.pos-file_buffer.buffer); + file_buffer.pos=file_buffer.buffer; + file_buffer.pos_in_file+=length; + if (test_only) + return 0; + if (error_on_write|| my_write(file_buffer.file, + (const uchar*) file_buffer.buffer, + length, + MYF(MY_WME | MY_NABP | MY_WAIT_IF_FULL))) + { + error_on_write=1; + return 1; + } + + if (neaded_length != ~(ulong) 0 && + (ulong) (file_buffer.end-file_buffer.buffer) < neaded_length) + { + uchar *tmp; + neaded_length+=256; /* some margin */ + tmp= (uchar*) my_realloc(file_buffer.buffer, neaded_length,MYF(MY_WME)); + if (!tmp) + return 1; + file_buffer.pos= (tmp + (ulong) (file_buffer.pos - file_buffer.buffer)); + file_buffer.buffer= tmp; + file_buffer.end= (tmp+neaded_length-8); + } + return 0; +} + + +static void end_file_buffer(void) +{ + my_free(file_buffer.buffer, MYF(0)); +} + + /* output `bits` low bits of `value' */ + +static void write_bits(register ulonglong value, register uint bits) +{ + DBUG_ASSERT(((bits < 8 * sizeof(value)) && ! (value >> bits)) || + (bits == 8 * sizeof(value))); + + if ((file_buffer.bits-= (int) bits) >= 0) + { + file_buffer.bitbucket|= value << file_buffer.bits; + } + else + { + reg3 ulonglong bit_buffer; + bits= (uint) -file_buffer.bits; + bit_buffer= (file_buffer.bitbucket | + ((bits != 8 * sizeof(value)) ? (value >> bits) : 0)); +#if BITS_SAVED == 64 + *file_buffer.pos++= (uchar) (bit_buffer >> 56); + *file_buffer.pos++= (uchar) (bit_buffer >> 48); + *file_buffer.pos++= (uchar) (bit_buffer >> 40); + *file_buffer.pos++= (uchar) (bit_buffer >> 32); +#endif + *file_buffer.pos++= (uchar) (bit_buffer >> 24); + *file_buffer.pos++= (uchar) (bit_buffer >> 16); + *file_buffer.pos++= (uchar) (bit_buffer >> 8); + *file_buffer.pos++= (uchar) (bit_buffer); + + if (bits != 8 * sizeof(value)) + value&= (((ulonglong) 1) << bits) - 1; + if (file_buffer.pos >= file_buffer.end) + VOID(flush_buffer(~ (ulong) 0)); + file_buffer.bits=(int) (BITS_SAVED - bits); + file_buffer.bitbucket= value << (BITS_SAVED - bits); + } + return; +} + + /* Flush bits in bit_buffer to buffer */ + +static void flush_bits(void) +{ + int bits; + ulonglong bit_buffer; + + bits= file_buffer.bits & ~7; + bit_buffer= file_buffer.bitbucket >> bits; + bits= BITS_SAVED - bits; + while (bits > 0) + { + bits-= 8; + *file_buffer.pos++= (uchar) (bit_buffer >> bits); + } + if (file_buffer.pos >= file_buffer.end) + VOID(flush_buffer(~ (ulong) 0)); + file_buffer.bits= BITS_SAVED; + file_buffer.bitbucket= 0; +} + + +/**************************************************************************** +** functions to handle the joined files +****************************************************************************/ + +static int save_state(MARIA_HA *isam_file,PACK_MRG_INFO *mrg, + my_off_t new_length, + ha_checksum crc) +{ + MARIA_SHARE *share=isam_file->s; + uint options=mi_uint2korr(share->state.header.options); + uint key; + DBUG_ENTER("save_state"); + + options|= HA_OPTION_COMPRESS_RECORD | HA_OPTION_READ_ONLY_DATA; + mi_int2store(share->state.header.options,options); + /* Save the original file type of we have to undo the packing later */ + share->state.header.org_data_file_type= share->state.header.data_file_type; + share->state.header.data_file_type= COMPRESSED_RECORD; + + share->state.state.data_file_length=new_length; + share->state.state.del=0; + share->state.state.empty=0; + share->state.dellink= HA_OFFSET_ERROR; + share->state.split=(ha_rows) mrg->records; + share->state.version=(ulong) time((time_t*) 0); + if (share->base.born_transactional) + share->state.create_rename_lsn= share->state.is_of_horizon= + share->state.skip_redo_lsn= LSN_NEEDS_NEW_STATE_LSNS; + if (! maria_is_all_keys_active(share->state.key_map, share->base.keys)) + { + /* + Some indexes are disabled, cannot use current key_file_length value + as an estimate of upper bound of index file size. Use packed data file + size instead. + */ + share->state.state.key_file_length= new_length; + } + /* + If there are no disabled indexes, keep key_file_length value from + original file so "maria_chk -rq" can use this value (this is necessary + because index size cannot be easily calculated for fulltext keys) + */ + maria_clear_all_keys_active(share->state.key_map); + for (key=0 ; key < share->base.keys ; key++) + share->state.key_root[key]= HA_OFFSET_ERROR; + share->state.key_del= HA_OFFSET_ERROR; + share->state.state.checksum= crc; /* Save crc in file */ + share->changed=1; /* Force write of header */ + share->state.open_count=0; + share->global_changed=0; + VOID(my_chsize(share->kfile.file, share->base.keystart, 0, MYF(0))); + if (share->base.keys) + isamchk_neaded=1; + DBUG_RETURN(_ma_state_info_write_sub(share->kfile.file, + &share->state, + MA_STATE_INFO_WRITE_DONT_MOVE_OFFSET | + MA_STATE_INFO_WRITE_FULL_INFO)); +} + + +static int save_state_mrg(File file,PACK_MRG_INFO *mrg,my_off_t new_length, + ha_checksum crc) +{ + MARIA_STATE_INFO state; + MARIA_HA *isam_file=mrg->file[0]; + uint options; + DBUG_ENTER("save_state_mrg"); + + state= isam_file->s->state; + options= (mi_uint2korr(state.header.options) | HA_OPTION_COMPRESS_RECORD | + HA_OPTION_READ_ONLY_DATA); + mi_int2store(state.header.options,options); + /* Save the original file type of we have to undo the packing later */ + state.header.org_data_file_type= state.header.data_file_type; + state.header.data_file_type= COMPRESSED_RECORD; + + state.state.data_file_length=new_length; + state.state.del=0; + state.state.empty=0; + state.state.records=state.split=(ha_rows) mrg->records; + state.create_rename_lsn= state.is_of_horizon= state.skip_redo_lsn= + LSN_NEEDS_NEW_STATE_LSNS; + + /* See comment above in save_state about key_file_length handling. */ + if (mrg->src_file_has_indexes_disabled) + { + isam_file->s->state.state.key_file_length= + max(isam_file->s->state.state.key_file_length, new_length); + } + state.dellink= HA_OFFSET_ERROR; + state.version=(ulong) time((time_t*) 0); + maria_clear_all_keys_active(state.key_map); + state.state.checksum=crc; + if (isam_file->s->base.keys) + isamchk_neaded=1; + state.changed=STATE_CHANGED | STATE_NOT_ANALYZED; /* Force check of table */ + DBUG_RETURN (_ma_state_info_write_sub(file, &state, + MA_STATE_INFO_WRITE_DONT_MOVE_OFFSET | + MA_STATE_INFO_WRITE_FULL_INFO)); +} + + +/* reset for mrg_rrnd */ + +static void mrg_reset(PACK_MRG_INFO *mrg) +{ + if (mrg->current) + { + maria_extra(*mrg->current, HA_EXTRA_NO_CACHE, 0); + mrg->current=0; + } +} + +static int mrg_rrnd(PACK_MRG_INFO *info,uchar *buf) +{ + int error; + MARIA_HA *isam_info; + my_off_t filepos; + + if (!info->current) + { + isam_info= *(info->current=info->file); + info->end=info->current+info->count; + maria_reset(isam_info); + maria_extra(isam_info, HA_EXTRA_CACHE, 0); + if ((error= maria_scan_init(isam_info))) + return(error); + } + else + isam_info= *info->current; + + for (;;) + { + if (!(error= maria_scan(isam_info, buf)) || + error != HA_ERR_END_OF_FILE) + return (error); + maria_scan_end(isam_info); + maria_extra(isam_info,HA_EXTRA_NO_CACHE, 0); + if (info->current+1 == info->end) + return(HA_ERR_END_OF_FILE); + info->current++; + isam_info= *info->current; + filepos=isam_info->s->pack.header_length; + maria_reset(isam_info); + maria_extra(isam_info,HA_EXTRA_CACHE, 0); + if ((error= maria_scan_init(isam_info))) + return(error); + } +} + + +static int mrg_close(PACK_MRG_INFO *mrg) +{ + uint i; + int error=0; + DBUG_ENTER("mrg_close"); + + for (i=0 ; i < mrg->count ; i++) + error|=maria_close(mrg->file[i]); + if (mrg->free_file) + my_free(mrg->file, MYF(0)); + DBUG_RETURN(error); +} + + +#if !defined(DBUG_OFF) +/* + Fake the counts to get big Huffman codes. + + SYNOPSIS + fakebigcodes() + huff_counts A pointer to the counts array. + end_count A pointer past the counts array. + + DESCRIPTION + + Huffman coding works by removing the two least frequent values from + the list of values and add a new value with the sum of their + incidences in a loop until only one value is left. Every time a + value is reused for a new value, it gets one more bit for its + encoding. Hence, the least frequent values get the longest codes. + + To get a maximum code length for a value, two of the values must + have an incidence of 1. As their sum is 2, the next infrequent value + must have at least an incidence of 2, then 4, 8, 16 and so on. This + means that one needs 2**n bytes (values) for a code length of n + bits. However, using more distinct values forces the use of longer + codes, or reaching the code length with less total bytes (values). + + To get 64(32)-bit codes, I sort the counts by decreasing incidence. + I assign counts of 1 to the two most frequent values, a count of 2 + for the next one, then 4, 8, and so on until 2**64-1(2**30-1). All + the remaining values get 1. That way every possible uchar has an + assigned code, though not all codes are used if not all uchar values + are present in the column. + + This strategy would work with distinct column values too, but + requires that at least 64(32) values are present. To make things + easier here, I cancel all distinct column values and force byte + compression for all columns. + + RETURN + void +*/ + +static void fakebigcodes(HUFF_COUNTS *huff_counts, HUFF_COUNTS *end_count) +{ + HUFF_COUNTS *count; + my_off_t *cur_count_p; + my_off_t *end_count_p; + my_off_t **cur_sort_p; + my_off_t **end_sort_p; + my_off_t *sort_counts[256]; + my_off_t total; + DBUG_ENTER("fakebigcodes"); + + for (count= huff_counts; count < end_count; count++) + { + /* + Remove distinct column values. + */ + if (huff_counts->tree_buff) + { + my_free(huff_counts->tree_buff, MYF(0)); + delete_tree(&huff_counts->int_tree); + huff_counts->tree_buff= NULL; + DBUG_PRINT("fakebigcodes", ("freed distinct column values")); + } + + /* + Sort counts by decreasing incidence. + */ + cur_count_p= count->counts; + end_count_p= cur_count_p + 256; + cur_sort_p= sort_counts; + while (cur_count_p < end_count_p) + *(cur_sort_p++)= cur_count_p++; + (void) my_qsort(sort_counts, 256, sizeof(my_off_t*), (qsort_cmp) fakecmp); + + /* + Assign faked counts. + */ + cur_sort_p= sort_counts; +#if SIZEOF_LONG_LONG > 4 + end_sort_p= sort_counts + 8 * sizeof(ulonglong) - 1; +#else + end_sort_p= sort_counts + 8 * sizeof(ulonglong) - 2; +#endif + /* Most frequent value gets a faked count of 1. */ + **(cur_sort_p++)= 1; + total= 1; + while (cur_sort_p < end_sort_p) + { + **(cur_sort_p++)= total; + total<<= 1; + } + /* Set the last value. */ + **(cur_sort_p++)= --total; + /* + Set the remaining counts. + */ + end_sort_p= sort_counts + 256; + while (cur_sort_p < end_sort_p) + **(cur_sort_p++)= 1; + } + DBUG_VOID_RETURN; +} + + +/* + Compare two counts for reverse sorting. + + SYNOPSIS + fakecmp() + count1 One count. + count2 Another count. + + RETURN + 1 count1 < count2 + 0 count1 == count2 + -1 count1 > count2 +*/ + +static int fakecmp(my_off_t **count1, my_off_t **count2) +{ + return ((**count1 < **count2) ? 1 : + (**count1 > **count2) ? -1 : 0); +} +#endif diff --git a/storage/maria/maria_read_log.c b/storage/maria/maria_read_log.c new file mode 100644 index 00000000000..2b2fa692f24 --- /dev/null +++ b/storage/maria/maria_read_log.c @@ -0,0 +1,278 @@ +/* Copyright (C) 2007 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 */ + +#include "maria_def.h" +#include "ma_recovery.h" +#include <my_getopt.h> + +#define LOG_FLAGS 0 + +static const char *load_default_groups[]= { "maria_read_log",0 }; +static void get_options(int *argc,char * * *argv); +#ifndef DBUG_OFF +#if defined(__WIN__) +const char *default_dbug_option= "d:t:O,\\maria_read_log.trace"; +#else +const char *default_dbug_option= "d:t:o,/tmp/maria_read_log.trace"; +#endif +#endif /* DBUG_OFF */ +static my_bool opt_display_only, opt_apply, opt_apply_undo, opt_silent; +static my_bool opt_check; +static const char *opt_tmpdir; +static ulong opt_page_buffer_size; +static ulonglong opt_start_from_lsn; +static MY_TMPDIR maria_chk_tmpdir; + + +int main(int argc, char **argv) +{ + LSN lsn; + char **default_argv; + uint warnings_count; + MY_INIT(argv[0]); + + load_defaults("my", load_default_groups, &argc, &argv); + default_argv= argv; + maria_data_root= (char *)"."; + get_options(&argc, &argv); + + maria_in_recovery= TRUE; + + if (maria_init()) + { + fprintf(stderr, "Can't init Maria engine (%d)\n", errno); + goto err; + } + /* we don't want to create a control file, it MUST exist */ + if (ma_control_file_open(FALSE, TRUE)) + { + fprintf(stderr, "Can't open control file (%d)\n", errno); + goto err; + } + if (last_logno == FILENO_IMPOSSIBLE) + { + fprintf(stderr, "Can't find any log\n"); + goto err; + } + if (init_pagecache(maria_pagecache, opt_page_buffer_size, 0, 0, + TRANSLOG_PAGE_SIZE, MY_WME) == 0) + { + fprintf(stderr, "Got error in init_pagecache() (errno: %d)\n", errno); + goto err; + } + /* + If log handler does not find the "last_logno" log it will return error, + which is good. + But if it finds a log and this log was crashed, it will create a new log, + which is useless. TODO: start log handler in read-only mode. + */ + if (init_pagecache(maria_log_pagecache, + TRANSLOG_PAGECACHE_SIZE, 0, 0, + TRANSLOG_PAGE_SIZE, MY_WME) == 0 || + translog_init(maria_data_root, TRANSLOG_FILE_SIZE, + 0, 0, maria_log_pagecache, TRANSLOG_DEFAULT_FLAGS, + opt_display_only)) + { + fprintf(stderr, "Can't init loghandler (%d)\n", errno); + goto err; + } + + if (opt_display_only) + printf("You are using --display-only, NOTHING will be written to disk\n"); + + /* LSN could be also --start-from-lsn=# */ + lsn= translog_first_lsn_in_log(); + if (lsn == LSN_ERROR) + { + fprintf(stderr, "Opening transaction log failed\n"); + goto end; + } + if (lsn == LSN_IMPOSSIBLE) + { + fprintf(stdout, "The transaction log is empty\n"); + } + fprintf(stdout, "The transaction log starts from lsn (%lu,0x%lx)\n", + LSN_IN_PARTS(lsn)); + + if (opt_start_from_lsn) + { + if (opt_start_from_lsn < (ulonglong) lsn) + { + fprintf(stderr, "start_from_lsn is too small. Aborting\n"); + maria_end(); + goto err; + } + lsn= (LSN) opt_start_from_lsn; + fprintf(stdout, "Starting reading log from lsn (%lu,0x%lx)\n", + LSN_IN_PARTS(lsn)); + } + + fprintf(stdout, "TRACE of the last maria_read_log\n"); + if (maria_apply_log(lsn, opt_apply ? MARIA_LOG_APPLY : + (opt_check ? MARIA_LOG_CHECK : + MARIA_LOG_DISPLAY_HEADER), opt_silent ? NULL : stdout, + opt_apply_undo, FALSE, FALSE, &warnings_count)) + goto err; + if (warnings_count == 0) + fprintf(stdout, "%s: SUCCESS\n", my_progname_short); + else + fprintf(stdout, "%s: DOUBTFUL (%u warnings, check previous output)\n", + my_progname_short, warnings_count); + +end: + maria_end(); + free_tmpdir(&maria_chk_tmpdir); + free_defaults(default_argv); + my_end(0); + exit(0); + return 0; /* No compiler warning */ + +err: + /* don't touch anything more, in case we hit a bug */ + fprintf(stderr, "%s: FAILED\n", my_progname_short); + free_tmpdir(&maria_chk_tmpdir); + free_defaults(default_argv); + exit(1); +} + + +#include "ma_check_standalone.h" + + +static struct my_option my_long_options[] = +{ + {"apply", 'a', + "Apply log to tables: modifies tables! you should make a backup first! " + " Displays a lot of information if not run with --silent", + (uchar **) &opt_apply, (uchar **) &opt_apply, 0, + GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"check", 'c', + "if --display-only, check if record is fully readable (for debugging)", + (uchar **) &opt_check, (uchar **) &opt_check, 0, + GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, +#ifndef DBUG_OFF + {"debug", '#', "Output debug log. Often the argument is 'd:t:o,filename'.", + 0, 0, 0, GET_STR, OPT_ARG, 0, 0, 0, 0, 0, 0}, +#endif + {"help", '?', "Display this help and exit.", + 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"display-only", 'd', "display brief info read from records' header", + (uchar **) &opt_display_only, (uchar **) &opt_display_only, 0, GET_BOOL, + NO_ARG,0, 0, 0, 0, 0, 0}, + {"maria_log_dir_path", 'l', + "Path to the directory where to store transactional log", + (uchar **) &maria_data_root, (uchar **) &maria_data_root, 0, + GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + { "page_buffer_size", 'P', "", + (uchar**) &opt_page_buffer_size, (uchar**) &opt_page_buffer_size, 0, + GET_ULONG, REQUIRED_ARG, (long) USE_BUFFER_INIT, + (long) USE_BUFFER_INIT, (long) ~(ulong) 0, (long) MALLOC_OVERHEAD, + (long) IO_SIZE, 0}, + { "start_from_lsn", 'o', "Start reading log from this lsn", + (uchar**) &opt_start_from_lsn, (uchar**) &opt_start_from_lsn, + 0, GET_ULL, REQUIRED_ARG, 0, 0, ~(longlong) 0, 0, 0, 0 }, + {"silent", 's', "Print less information during apply/undo phase", + (uchar **) &opt_silent, (uchar **) &opt_silent, 0, + GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"tmpdir", 't', "Path for temporary files. Multiple paths can be specified, " + "separated by " +#if defined( __WIN__) || defined(__NETWARE__) + "semicolon (;)" +#else + "colon (:)" +#endif + , (uchar**) &opt_tmpdir, 0, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + {"undo", 'u', "Apply UNDO records to tables. (disable with --disable-undo)", + (uchar **) &opt_apply_undo, (uchar **) &opt_apply_undo, 0, + GET_BOOL, NO_ARG, 1, 0, 0, 0, 0, 0}, + {"version", 'V', "Print version 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} +}; + +#include <help_start.h> + +static void print_version(void) +{ + VOID(printf("%s Ver 1.2 for %s on %s\n", + my_progname_short, SYSTEM_TYPE, MACHINE_TYPE)); + NETWARE_SET_SCREEN_MODE(1); +} + + +static void usage(void) +{ + print_version(); + puts("Copyright (C) 2007 MySQL AB"); + puts("This software comes with ABSOLUTELY NO WARRANTY. This is free software,"); + puts("and you are welcome to modify and redistribute it under the GPL license\n"); + + puts("Display and apply log records from a MARIA transaction log"); + puts("found in the current directory (for now)"); +#ifndef IDENTICAL_PAGES_AFTER_RECOVERY + puts("\nNote: Maria is compiled without -DIDENTICAL_PAGES_AFTER_RECOVERY\n" + "which means that the table files are not byte-to-byte identical to\n" + "files created during normal execution. This should be ok, except for\n" + "test scripts that tries to compare files before and after recovery."); +#endif + VOID(printf("\nUsage: %s OPTIONS\n", my_progname_short)); + puts("You need to use one of -d or -a"); + my_print_help(my_long_options); + print_defaults("my", load_default_groups); + my_print_variables(my_long_options); +} + +#include <help_end.h> + +static my_bool +get_one_option(int optid __attribute__((unused)), + const struct my_option *opt __attribute__((unused)), + char *argument __attribute__((unused))) +{ + switch (optid) { + case '?': + usage(); + exit(0); + case 'V': + print_version(); + exit(0); +#ifndef DBUG_OFF + case '#': + DBUG_SET_INITIAL(argument ? argument : default_dbug_option); + break; +#endif + } + return 0; +} + +static void get_options(int *argc,char ***argv) +{ + int ho_error; + + if ((ho_error=handle_options(argc, argv, my_long_options, get_one_option))) + exit(ho_error); + + if (!opt_apply) + opt_apply_undo= FALSE; + + if (((opt_display_only + opt_apply) != 1) || (*argc > 0)) + { + usage(); + exit(1); + } + if (init_tmpdir(&maria_chk_tmpdir, opt_tmpdir)) + exit(1); + maria_tmpdir= &maria_chk_tmpdir; +} diff --git a/storage/maria/maria_rename.sh b/storage/maria/maria_rename.sh new file mode 100755 index 00000000000..fb20e47e635 --- /dev/null +++ b/storage/maria/maria_rename.sh @@ -0,0 +1,17 @@ +#!/bin/sh + +replace myisam maria MYISAM MARIA MyISAM MARIA -- mysql-test/t/*maria*test mysql-test/r/*maria*result + +FILES=`echo sql/ha_maria.{cc,h} include/maria*h storage/maria/*.{c,h}` + +replace myisam maria MYISAM MARIA MyISAM MARIA myisam.h maria.h myisamdef.h maria_def.h mi_ maria_ ft_ maria_ft_ "Copyright (C) 2000" "Copyright (C) 2006" MI_ISAMINFO MARIA_INFO MI_CREATE_INFO MARIA_CREATE_INFO maria_isam_ maria_ MI_INFO MARIA_HA MI_ MARIA_ MARIACHK MARIA_CHK rt_index.h ma_rt_index.h rtree_ maria_rtree rt_key.h ma_rt_key.h rt_mbr.h ma_rt_mbr.h -- $FILES + +replace check_table_is_closed _ma_check_table_is_closed test_if_reopen _ma_test_if_reopen my_n_base_info_read maria_n_base_info_read update_auto_increment _ma_update_auto_increment save_pack_length _ma_save_packlength calc_pack_length _ma_calc_pack_length -- $FILES + +replace mi_ ma_ ft_ ma_ft_ rt_ ma_rt_ myisam maria myisamchk maria_chk myisampack maria_pack myisamlog maria_log -- storage/maria/Makefile.am + +# +# Restore wrong replaces +# + +replace maria_sint1korr mi_sint1korr maria_uint1korr mi_uint1korr maria_sint2korr mi_sint2korr maria_sint3korr mi_sint3korr maria_sint4korr mi_sint4korr maria_sint8korr mi_sint8korr maria_uint2korr mi_uint2korr maria_uint3korr mi_uint3korr maria_uint4korr mi_uint4korr maria_uint5korr mi_uint5korr maria_uint6korr mi_uint6korr maria_uint7korr mi_uint7korr maria_uint8korr mi_uint8korr maria_int1store mi_int1store maria_int2store mi_int2store maria_int3store mi_int3store maria_int4store mi_int4store maria_int5store mi_int5store maria_int6store mi_int6store maria_int7store mi_int7store maria_int8store mi_int8store maria_float4store mi_float4store maria_float4get mi_float4get maria_float8store mi_float8store maria_float8get mi_float8get maria_rowstore mi_rowstore maria_rowkorr mi_rowkorr maria_sizestore mi_sizestore maria_sizekorr mi_sizekorr _maria_maria_ _maria MARIA_MAX_POSSIBLE_KEY HA_MAX_POSSIBLE_KEY MARIA_MAX_KEY_BUFF HA_MAX_KEY_BUFF MARIA_MAX_KEY_SEG HA_MAX_KEY_SEG maria_ft_sintXkorr ft_sintXkorr maria_ft_intXstore ft_intXstore maria_ft_boolean_syntax ft_boolean_syntax maria_ft_min_word_len ft_min_word_len maria_ft_max_word_len ft_max_word_len -- $FILES diff --git a/storage/maria/plug.in b/storage/maria/plug.in new file mode 100644 index 00000000000..686c8361a87 --- /dev/null +++ b/storage/maria/plug.in @@ -0,0 +1,21 @@ +MYSQL_STORAGE_ENGINE(maria,, [Maria Storage Engine], + [Crash-safe tables with MyISAM heritage], [default,max,max-no-ndb]) +MYSQL_PLUGIN_DIRECTORY(maria, [storage/maria]) +MYSQL_PLUGIN_STATIC(maria, [libmaria.a]) +# Maria will probably go first into max builds, not all builds, +# so we don't declare it mandatory. +MYSQL_PLUGIN_DEPENDS_ON_MYSQL_INTERNALS(maria, [ha_maria.cc]) + +MYSQL_PLUGIN_ACTIONS(maria, [ +# AC_CONFIG_FILES(storage/maria/unittest/Makefile) +AC_ARG_WITH(maria-tmp-tables, + AC_HELP_STRING([--with-maria-tmp-tables],[Use Maria for internal temporary tables]), + [with_maria_tmp_tables=$withval], + [with_maria_tmp_tables=yes] +) + +if test "$with_maria_tmp_tables" = "yes" +then + AC_DEFINE([USE_MARIA_FOR_TMP_TABLES], [1], [Maria is used for internal temporary tables]) +fi +]) diff --git a/storage/maria/tablockman.c b/storage/maria/tablockman.c new file mode 100644 index 00000000000..1bb8889aaa7 --- /dev/null +++ b/storage/maria/tablockman.c @@ -0,0 +1,674 @@ +/* QQ: TODO - allocate everything from dynarrays !!! (benchmark) */ +/* QQ: automatically place S instead of LS if possible */ +/* Copyright (C) 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 */ + +#include <my_base.h> +#include <hash.h> +#include "tablockman.h" + +/* + Lock Manager for Table Locks + + The code below handles locks on resources - but it is optimized for a + case when a number of resources is not very large, and there are many of + locks per resource - that is a resource is likely to be a table or a + database, but hardly a row in a table. + + Locks belong to "lock owners". A Lock Owner is uniquely identified by a + 16-bit number - loid (lock owner identifier). A function loid_to_tlo must + be provided by the application that takes such a number as an argument + and returns a TABLE_LOCK_OWNER structure. + + Lock levels are completely defined by three tables. Lock compatibility + matrix specifies which locks can be held at the same time on a resource. + Lock combining matrix specifies what lock level has the same behaviour as + a pair of two locks of given levels. getlock_result matrix simplifies + intention locking and lock escalation for an application, basically it + defines which locks are intention locks and which locks are "loose" + locks. It is only used to provide better diagnostics for the + application, lock manager itself does not differentiate between normal, + intention, and loose locks. + + The assumptions are: few distinct resources, many locks are held at the + same time on one resource. Thus: a lock structure _per resource_ can be + rather large; a lock structure _per lock_ does not need to be very small + either; we need to optimize for _speed_. Operations we need are: place a + lock, check if a particular transaction already has a lock on this + resource, check if a conflicting lock exists, if yes - find who owns it. + + Solution: every resource has a structure with + 1. Hash of latest (see the lock upgrade section below) granted locks with + loid as a key. Thus, checking if a given transaction has a lock on + this resource is O(1) operation. + 2. Doubly-linked lists of all granted locks - one list for every lock + type. Thus, checking if a conflicting lock exists is a check whether + an appropriate list head pointer is not null, also O(1). + 3. Every lock has a loid of the owner, thus checking who owns a + conflicting lock is also O(1). + 4. Deque of waiting locks. It's a deque (double-ended queue) not a fifo, + because for lock upgrades requests are added to the queue head, not + tail. This is a single place where there it gets O(N) on number + of locks - when a transaction wakes up from waiting on a condition, + it may need to scan the queue backward to the beginning to find + a conflicting lock. It is guaranteed though that "all transactions + before it" received the same - or earlier - signal. In other words a + transaction needs to scan all transactions before it that received the + signal but didn't have a chance to resume the execution yet, so + practically OS scheduler won't let the scan to be O(N). + + Waiting: if there is a conflicting lock or if wait queue is not empty, a + requested lock cannot be granted at once. It is added to the end of the + wait queue. If a queue was empty and there is a conflicting lock - the + "blocker" transaction is the owner of this lock. If a queue is not empty, + an owner of the previous lock in the queue is the "blocker". But if the + previous lock is compatible with the request, then the "blocker" is the + transaction that the owner of the lock at the end of the queue is waiting + for (in other words, our lock is added to the end of the wait queue, and + our blocker is the same as of the lock right before us). + + Lock upgrades: when a thread that has a lock on a given resource, + requests a new lock on the same resource and the old lock is not enough + to satisfy new lock requirements (which is defined by + lock_combining_matrix[old_lock][new_lock] != old_lock), a new lock + (defined by lock_combining_matrix as above) is placed. Depending on + other granted locks it is immediately granted or it has to wait. Here the + lock is added to the start of the waiting queue, not to the end. Old + lock, is removed from the hash, but not from the doubly-linked lists. + (indeed, a transaction checks "do I have a lock on this resource ?" by + looking in a hash, and it should find a latest lock, so old locks must be + removed; but a transaction checks "are there conflicting locks ?" by + checking doubly-linked lists, it doesn't matter if it will find an old + lock - if it would be removed, a new lock would be also a conflict). + So, a hash contains only "latest" locks - there can be only one latest + lock per resource per transaction. But doubly-linked lists contain all + locks, even "obsolete" ones, because it doesnt't hurt. Note that old + locks can not be freed early, in particular they stay in the + 'active_locks' list of a lock owner, because they may be "re-enabled" + on a savepoint rollback. + + To better support table-row relations where one needs to lock the table + with an intention lock before locking the row, extended diagnostics is + provided. When an intention lock (presumably on a table) is granted, + lockman_getlock() returns one of GOT_THE_LOCK (no need to lock the row, + perhaps the thread already has a normal lock on this table), + GOT_THE_LOCK_NEED_TO_LOCK_A_SUBRESOURCE (need to lock the row, as usual), + GOT_THE_LOCK_NEED_TO_INSTANT_LOCK_A_SUBRESOURCE (only need to check + whether it's possible to lock the row, but no need to lock it - perhaps + the thread has a loose lock on this table). This is defined by + getlock_result[] table. + + Instant duration locks are not supported. Though they're trivial to add, + they are normally only used on rows, not on tables. So, presumably, + they are not needed here. + + Mutexes: there're table mutexes (LOCKED_TABLE::mutex), lock owner mutexes + (TABLE_LOCK_OWNER::mutex), and a pool mutex (TABLOCKMAN::pool_mutex). + table mutex protects operations on the table lock structures, and lock + owner pointers waiting_for and waiting_for_loid. + lock owner mutex is only used to wait on lock owner condition + (TABLE_LOCK_OWNER::cond), there's no need to protect owner's lock + structures, and only lock owner itself may access them. + The pool mutex protects a pool of unused locks. Note the locking order: + first the table mutex, then the owner mutex or a pool mutex. + Table mutex lock cannot be attempted when owner or pool mutex are locked. + No mutex lock can be attempted if owner or pool mutex are locked. +*/ + +/* + Lock compatibility matrix. + + It's asymmetric. Read it as "Somebody has the lock <value in the row + label>, can I set the lock <value in the column label> ?" + + ') Though you can take LS lock while somebody has S lock, it makes no + sense - it's simpler to take S lock too. + + 1 - compatible + 0 - incompatible + -1 - "impossible", so that we can assert the impossibility. +*/ +static const int lock_compatibility_matrix[10][10]= +{ /* N S X IS IX SIX LS LX SLX LSIX */ + { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, /* N */ + { -1, 1, 0, 1, 0, 0, 1, 0, 0, 0 }, /* S */ + { -1, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, /* X */ + { -1, 1, 0, 1, 1, 1, 1, 1, 1, 1 }, /* IS */ + { -1, 0, 0, 1, 1, 0, 1, 1, 0, 1 }, /* IX */ + { -1, 0, 0, 1, 0, 0, 1, 0, 0, 0 }, /* SIX */ + { -1, 1, 0, 1, 0, 0, 1, 0, 0, 0 }, /* LS */ + { -1, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, /* LX */ + { -1, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, /* SLX */ + { -1, 0, 0, 1, 0, 0, 1, 0, 0, 0 } /* LSIX */ +}; + +/* + Lock combining matrix. + + It's symmetric. Read it as "what lock level L is identical to the + set of two locks A and B" + + One should never get N from it, we assert the impossibility +*/ +static const enum lockman_lock_type lock_combining_matrix[10][10]= +{/* N S X IS IX SIX LS LX SLX LSIX */ + { N, N, N, N, N, N, N, N, N, N}, /* N */ + { N, S, X, S, SIX, SIX, S, SLX, SLX, SIX}, /* S */ + { N, X, X, X, X, X, X, X, X, X}, /* X */ + { N, S, X, IS, IX, SIX, LS, LX, SLX, LSIX}, /* IS */ + { N, SIX, X, IX, IX, SIX, LSIX, LX, SLX, LSIX}, /* IX */ + { N, SIX, X, SIX, SIX, SIX, SIX, SLX, SLX, SIX}, /* SIX */ + { N, S, X, LS, LSIX, SIX, LS, LX, SLX, LSIX}, /* LS */ + { N, SLX, X, LX, LX, SLX, LX, LX, SLX, LX}, /* LX */ + { N, SLX, X, SLX, SLX, SLX, SLX, SLX, SLX, SLX}, /* SLX */ + { N, SIX, X, LSIX, LSIX, SIX, LSIX, LX, SLX, LSIX} /* LSIX */ +}; + +/* + the return codes for lockman_getlock + + It's asymmetric. Read it as "I have the lock <value in the row label>, + what value should be returned for <value in the column label> ?" + + 0 means impossible combination (assert!) + + Defines below help to preserve the table structure. + I/L/A values are self explanatory + x means the combination is possible (assert should not crash) + but it cannot happen in row locks, only in table locks (S,X), + or lock escalations (LS,LX) +*/ +#define I GOT_THE_LOCK_NEED_TO_LOCK_A_SUBRESOURCE +#define L GOT_THE_LOCK_NEED_TO_INSTANT_LOCK_A_SUBRESOURCE +#define A GOT_THE_LOCK +#define x GOT_THE_LOCK +static const enum lockman_getlock_result getlock_result[10][10]= +{/* N S X IS IX SIX LS LX SLX LSIX */ + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, /* N */ + { 0, x, 0, A, 0, 0, x, 0, 0, 0}, /* S */ + { 0, x, x, A, A, 0, x, x, 0, 0}, /* X */ + { 0, 0, 0, I, 0, 0, 0, 0, 0, 0}, /* IS */ + { 0, 0, 0, I, I, 0, 0, 0, 0, 0}, /* IX */ + { 0, x, 0, A, I, 0, x, 0, 0, 0}, /* SIX */ + { 0, 0, 0, L, 0, 0, x, 0, 0, 0}, /* LS */ + { 0, 0, 0, L, L, 0, x, x, 0, 0}, /* LX */ + { 0, x, 0, A, L, 0, x, x, 0, 0}, /* SLX */ + { 0, 0, 0, L, I, 0, x, 0, 0, 0} /* LSIX */ +}; +#undef I +#undef L +#undef A +#undef x + +/* + this structure is optimized for a case when there're many locks + on the same resource - e.g. a table +*/ + +struct st_table_lock { + /* QQ: do we need upgraded_from ? */ + struct st_table_lock *next_in_lo, *upgraded_from, *next, *prev; + struct st_locked_table *table; + uint16 loid; + uchar lock_type; +}; + +#define hash_insert my_hash_insert /* for consistency :) */ + +static inline +TABLE_LOCK *find_by_loid(LOCKED_TABLE *table, uint16 loid) +{ + return (TABLE_LOCK *)hash_search(& table->latest_locks, + (uchar *)& loid, sizeof(loid)); +} + +static inline +void remove_from_wait_queue(TABLE_LOCK *lock, LOCKED_TABLE *table) +{ + DBUG_ASSERT(table == lock->table); + if (lock->prev) + { + DBUG_ASSERT(table->wait_queue_out != lock); + lock->prev->next= lock->next; + } + else + { + DBUG_ASSERT(table->wait_queue_out == lock); + table->wait_queue_out= lock->next; + } + if (lock->next) + { + DBUG_ASSERT(table->wait_queue_in != lock); + lock->next->prev= lock->prev; + } + else + { + DBUG_ASSERT(table->wait_queue_in == lock); + table->wait_queue_in= lock->prev; + } +} + +/* + DESCRIPTION + tries to lock a resource 'table' with a lock level 'lock'. + + RETURN + see enum lockman_getlock_result +*/ +enum lockman_getlock_result +tablockman_getlock(TABLOCKMAN *lm, TABLE_LOCK_OWNER *lo, + LOCKED_TABLE *table, enum lockman_lock_type lock) +{ + TABLE_LOCK *old, *new, *blocker, *blocker2; + TABLE_LOCK_OWNER *wait_for; + struct timespec timeout; + enum lockman_lock_type new_lock; + enum lockman_getlock_result res; + int i; + + DBUG_ASSERT(lo->waiting_lock == 0); + DBUG_ASSERT(lo->waiting_for == 0); + DBUG_ASSERT(lo->waiting_for_loid == 0); + + pthread_mutex_lock(& table->mutex); + /* do we already have a lock on this resource ? */ + old= find_by_loid(table, lo->loid); + + /* calculate the level of the upgraded lock, if yes */ + new_lock= old ? lock_combining_matrix[old->lock_type][lock] : lock; + + /* and check if old lock is enough to satisfy the new request */ + if (old && new_lock == old->lock_type) + { + /* yes */ + res= getlock_result[old->lock_type][lock]; + goto ret; + } + + /* no, placing a new lock. first - take a free lock structure from the pool */ + pthread_mutex_lock(& lm->pool_mutex); + new= lm->pool; + if (new) + { + lm->pool= new->next; + pthread_mutex_unlock(& lm->pool_mutex); + } + else + { + pthread_mutex_unlock(& lm->pool_mutex); + new= (TABLE_LOCK *)my_malloc(sizeof(*new), MYF(MY_WME)); + if (unlikely(!new)) + { + res= NO_MEMORY_FOR_LOCK; + goto ret; + } + } + + new->loid= lo->loid; + new->lock_type= new_lock; + new->table= table; + + /* and try to place it */ + for (new->prev= table->wait_queue_in;;) + { + wait_for= 0; + if (!old) + { + /* not upgrading - a lock must be added to the _end_ of the wait queue */ + for (blocker= new->prev; blocker && !wait_for; blocker= blocker->prev) + { + TABLE_LOCK_OWNER *tmp= lm->loid_to_tlo(blocker->loid); + + /* find a blocking lock */ + DBUG_ASSERT(table->wait_queue_out); + DBUG_ASSERT(table->wait_queue_in); + if (!lock_compatibility_matrix[blocker->lock_type][lock]) + { + /* found! */ + wait_for= tmp; + break; + } + + /* + hmm, the lock before doesn't block us, let's look one step further. + the condition below means: + + if we never waited on a condition yet + OR + the lock before ours (blocker) waits on a lock (blocker2) that is + present in the hash AND and conflicts with 'blocker' + + the condition after OR may fail if 'blocker2' was removed from + the hash, its signal woke us up, but 'blocker' itself didn't see + the signal yet. + */ + if (!lo->waiting_lock || + ((blocker2= find_by_loid(table, tmp->waiting_for_loid)) && + !lock_compatibility_matrix[blocker2->lock_type] + [blocker->lock_type])) + { + /* but it's waiting for a real lock. we'll wait for the same lock */ + wait_for= tmp->waiting_for; + /* + We don't really need tmp->waiting_for, as tmp->waiting_for_loid + is enough. waiting_for is just a local cache to avoid calling + loid_to_tlo(). + But it's essensial that tmp->waiting_for pointer can ONLY + be dereferenced if find_by_loid() above returns a non-null + pointer, because a TABLE_LOCK_OWNER object that it points to + may've been freed when we come here after a signal. + In particular tmp->waiting_for_loid cannot be replaced + with tmp->waiting_for->loid. + */ + DBUG_ASSERT(wait_for == lm->loid_to_tlo(tmp->waiting_for_loid)); + break; + } + + /* + otherwise - a lock it's waiting for doesn't exist. + We've no choice but to scan the wait queue backwards, looking + for a conflicting lock or a lock waiting for a real lock. + QQ is there a way to avoid this scanning ? + */ + } + } + + if (wait_for == 0) + { + /* checking for compatibility with existing locks */ + for (blocker= 0, i= 0; i < LOCK_TYPES; i++) + { + if (table->active_locks[i] && !lock_compatibility_matrix[i+1][lock]) + { + blocker= table->active_locks[i]; + /* if the first lock in the list is our own - skip it */ + if (blocker->loid == lo->loid) + blocker= blocker->next; + if (blocker) /* found a conflicting lock, need to wait */ + break; + } + } + if (!blocker) /* free to go */ + break; + wait_for= lm->loid_to_tlo(blocker->loid); + } + + /* ok, we're here - the wait is inevitable */ + lo->waiting_for= wait_for; + lo->waiting_for_loid= wait_for->loid; + if (!lo->waiting_lock) /* first iteration of the for() loop */ + { + /* lock upgrade or new lock request ? */ + if (old) + { + /* upgrade - add the lock to the _start_ of the wait queue */ + new->prev= 0; + if ((new->next= table->wait_queue_out)) + new->next->prev= new; + table->wait_queue_out= new; + if (!table->wait_queue_in) + table->wait_queue_in= table->wait_queue_out; + } + else + { + /* new lock - add the lock to the _end_ of the wait queue */ + new->next= 0; + if ((new->prev= table->wait_queue_in)) + new->prev->next= new; + table->wait_queue_in= new; + if (!table->wait_queue_out) + table->wait_queue_out= table->wait_queue_in; + } + lo->waiting_lock= new; + + set_timespec_nsec(timeout,lm->lock_timeout * 1000000); + + } + + /* + prepare to wait. + we must lock blocker's mutex to wait on blocker's cond. + and we must release table's mutex. + note that blocker's mutex is locked _before_ table's mutex is released + */ + pthread_mutex_lock(wait_for->mutex); + pthread_mutex_unlock(& table->mutex); + + /* now really wait */ + i= pthread_cond_timedwait(wait_for->cond, wait_for->mutex, & timeout); + + pthread_mutex_unlock(wait_for->mutex); + + if (i == ETIMEDOUT || i == ETIME) + { + /* we rely on the caller to rollback and release all locks */ + res= LOCK_TIMEOUT; + goto ret2; + } + + pthread_mutex_lock(& table->mutex); + + /* ... and repeat from the beginning */ + } + /* yeah! we can place the lock now */ + + /* remove the lock from the wait queue, if it was there */ + if (lo->waiting_lock) + { + remove_from_wait_queue(new, table); + lo->waiting_lock= 0; + lo->waiting_for= 0; + lo->waiting_for_loid= 0; + } + + /* add it to the list of all locks of this lock owner */ + new->next_in_lo= lo->active_locks; + lo->active_locks= new; + + /* and to the list of active locks of this lock type */ + new->prev= 0; + if ((new->next= table->active_locks[new_lock-1])) + new->next->prev= new; + table->active_locks[new_lock-1]= new; + + /* update the latest_locks hash */ + if (old) + hash_delete(& table->latest_locks, (uchar *)old); + hash_insert(& table->latest_locks, (uchar *)new); + + new->upgraded_from= old; + + res= getlock_result[lock][lock]; + +ret: + pthread_mutex_unlock(& table->mutex); +ret2: + DBUG_ASSERT(res); + return res; +} + +/* + DESCRIPTION + release all locks belonging to a transaction. + signal waiters to continue +*/ +void tablockman_release_locks(TABLOCKMAN *lm, TABLE_LOCK_OWNER *lo) +{ + TABLE_LOCK *lock, *local_pool= 0, *local_pool_end; + + /* + instead of adding released locks to a pool one by one, we'll link + them in a list and add to a pool in one short action (under a mutex) + */ + local_pool_end= lo->waiting_lock ? lo->waiting_lock : lo->active_locks; + if (!local_pool_end) + return; + + /* release a waiting lock, if any */ + if ((lock= lo->waiting_lock)) + { + DBUG_ASSERT(lock->loid == lo->loid); + pthread_mutex_lock(& lock->table->mutex); + remove_from_wait_queue(lock, lock->table); + + /* + a special case: if this lock was not the last in the wait queue + and it's compatible with the next lock, than the next lock + is waiting for our blocker though really it waits for us, indirectly. + Signal our blocker to release this next lock (after we removed our + lock from the wait queue, of course). + */ + /* + An example to clarify the above: + trn1> S-lock the table. Granted. + trn2> IX-lock the table. Added to the wait queue. trn2 waits on trn1 + trn3> IS-lock the table. The queue is not empty, so IS-lock is added + to the queue. It's compatible with the waiting IX-lock, so trn3 + waits for trn2->waiting_for, that is trn1. + if trn1 releases the lock it signals trn1->cond and both waiting + transactions are awaken. But if trn2 times out, trn3 must be notified + too (as IS and S locks are compatible). So trn2 must signal trn1->cond. + */ + if (lock->next && + lock_compatibility_matrix[lock->next->lock_type][lock->lock_type]) + { + pthread_mutex_lock(lo->waiting_for->mutex); + pthread_cond_broadcast(lo->waiting_for->cond); + pthread_mutex_unlock(lo->waiting_for->mutex); + } + lo->waiting_for= 0; + lo->waiting_for_loid= 0; + pthread_mutex_unlock(& lock->table->mutex); + + lock->next= local_pool; + local_pool= lock; + } + + /* now release granted locks */ + lock= lo->active_locks; + while (lock) + { + TABLE_LOCK *cur= lock; + pthread_mutex_t *mutex= & lock->table->mutex; + DBUG_ASSERT(cur->loid == lo->loid); + + DBUG_ASSERT(lock != lock->next_in_lo); + lock= lock->next_in_lo; + + /* TODO ? group locks by table to reduce the number of mutex locks */ + pthread_mutex_lock(mutex); + hash_delete(& cur->table->latest_locks, (uchar *)cur); + + if (cur->prev) + cur->prev->next= cur->next; + if (cur->next) + cur->next->prev= cur->prev; + if (cur->table->active_locks[cur->lock_type-1] == cur) + cur->table->active_locks[cur->lock_type-1]= cur->next; + + cur->next= local_pool; + local_pool= cur; + + pthread_mutex_unlock(mutex); + } + + lo->waiting_lock= lo->active_locks= 0; + + /* + okay, all locks released. now signal that we're leaving, + in case somebody's waiting for it + */ + pthread_mutex_lock(lo->mutex); + pthread_cond_broadcast(lo->cond); + pthread_mutex_unlock(lo->mutex); + + /* and push all freed locks to the lockman's pool */ + pthread_mutex_lock(& lm->pool_mutex); + local_pool_end->next= lm->pool; + lm->pool= local_pool; + pthread_mutex_unlock(& lm->pool_mutex); +} + +void tablockman_init(TABLOCKMAN *lm, loid_to_tlo_func *func, uint timeout) +{ + lm->pool= 0; + lm->loid_to_tlo= func; + lm->lock_timeout= timeout; + pthread_mutex_init(& lm->pool_mutex, MY_MUTEX_INIT_FAST); + my_getsystime(); /* ensure that my_getsystime() is initialized */ +} + +void tablockman_destroy(TABLOCKMAN *lm) +{ + while (lm->pool) + { + TABLE_LOCK *tmp= lm->pool; + lm->pool= tmp->next; + my_free((void *)tmp, MYF(0)); + } + pthread_mutex_destroy(& lm->pool_mutex); +} + +/* + initialize a LOCKED_TABLE structure + + SYNOPSYS + lt a LOCKED_TABLE to initialize + initial_hash_size initial size for 'latest_locks' hash +*/ +void tablockman_init_locked_table(LOCKED_TABLE *lt, int initial_hash_size) +{ + bzero(lt, sizeof(*lt)); + pthread_mutex_init(& lt->mutex, MY_MUTEX_INIT_FAST); + hash_init(& lt->latest_locks, & my_charset_bin, initial_hash_size, + offsetof(TABLE_LOCK, loid), + sizeof(((TABLE_LOCK*)0)->loid), 0, 0, 0); +} + +void tablockman_destroy_locked_table(LOCKED_TABLE *lt) +{ + int i; + + DBUG_ASSERT(lt->wait_queue_out == 0); + DBUG_ASSERT(lt->wait_queue_in == 0); + DBUG_ASSERT(lt->latest_locks.records == 0); + for (i= 0; i<LOCK_TYPES; i++) + DBUG_ASSERT(lt->active_locks[i] == 0); + + hash_free(& lt->latest_locks); + pthread_mutex_destroy(& lt->mutex); +} + +#ifdef EXTRA_DEBUG +static const char *lock2str[LOCK_TYPES+1]= {"N", "S", "X", "IS", "IX", "SIX", + "LS", "LX", "SLX", "LSIX"}; + +void tablockman_print_tlo(TABLE_LOCK_OWNER *lo) +{ + TABLE_LOCK *lock; + + printf("lo%d>", lo->loid); + if ((lock= lo->waiting_lock)) + printf(" (%s.0x%lx)", lock2str[lock->lock_type], (ulong)lock->table); + for (lock= lo->active_locks; + lock && lock != lock->next_in_lo; + lock= lock->next_in_lo) + printf(" %s.0x%lx", lock2str[lock->lock_type], (ulong)lock->table); + if (lock && lock == lock->next_in_lo) + printf("!"); + printf("\n"); +} +#endif + diff --git a/storage/maria/tablockman.h b/storage/maria/tablockman.h new file mode 100644 index 00000000000..e33d1aa44e8 --- /dev/null +++ b/storage/maria/tablockman.h @@ -0,0 +1,87 @@ +/* Copyright (C) 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 */ + +#ifndef _tablockman_h +#define _tablockman_h + +/* + Lock levels: + ^^^^^^^^^^^ + + N - "no lock", not a lock, used sometimes internally to simplify the code + S - Shared + X - eXclusive + IS - Intention Shared + IX - Intention eXclusive + SIX - Shared + Intention eXclusive + LS - Loose Shared + LX - Loose eXclusive + SLX - Shared + Loose eXclusive + LSIX - Loose Shared + Intention eXclusive +*/ +#ifndef _lockman_h +/* QQ: TODO remove N-locks */ +enum lockman_lock_type { N, S, X, IS, IX, SIX, LS, LX, SLX, LSIX, LOCK_TYPE_LAST }; +enum lockman_getlock_result { + NO_MEMORY_FOR_LOCK=1, DEADLOCK, LOCK_TIMEOUT, + GOT_THE_LOCK, + GOT_THE_LOCK_NEED_TO_LOCK_A_SUBRESOURCE, + GOT_THE_LOCK_NEED_TO_INSTANT_LOCK_A_SUBRESOURCE +}; +#endif + +#define LOCK_TYPES (LOCK_TYPE_LAST-1) + +typedef struct st_table_lock TABLE_LOCK; + +typedef struct st_table_lock_owner { + TABLE_LOCK *active_locks; /* list of active locks */ + TABLE_LOCK *waiting_lock; /* waiting lock (one lock only) */ + struct st_table_lock_owner *waiting_for; /* transaction we're waiting for */ + pthread_cond_t *cond; /* transactions waiting for us, wait on 'cond' */ + pthread_mutex_t *mutex; /* mutex is required to use 'cond' */ + uint16 loid, waiting_for_loid; /* Lock Owner IDentifier */ +} TABLE_LOCK_OWNER; + +typedef struct st_locked_table { + pthread_mutex_t mutex; /* mutex for everything below */ + HASH latest_locks; /* latest locks in a hash */ + TABLE_LOCK *active_locks[LOCK_TYPES]; /* dl-list of locks per type */ + TABLE_LOCK *wait_queue_in, *wait_queue_out; /* wait deque (double-end queue)*/ +} LOCKED_TABLE; + +typedef TABLE_LOCK_OWNER *loid_to_tlo_func(uint16); + +typedef struct { + pthread_mutex_t pool_mutex; + TABLE_LOCK *pool; /* lifo pool of free locks */ + uint lock_timeout; /* lock timeout in milliseconds */ + loid_to_tlo_func *loid_to_tlo; /* for mapping loid to TABLE_LOCK_OWNER */ +} TABLOCKMAN; + +void tablockman_init(TABLOCKMAN *, loid_to_tlo_func *, uint); +void tablockman_destroy(TABLOCKMAN *); +enum lockman_getlock_result tablockman_getlock(TABLOCKMAN *, TABLE_LOCK_OWNER *, + LOCKED_TABLE *, enum lockman_lock_type); +void tablockman_release_locks(TABLOCKMAN *, TABLE_LOCK_OWNER *); +void tablockman_init_locked_table(LOCKED_TABLE *, int); +void tablockman_destroy_locked_table(LOCKED_TABLE *); + +#ifdef EXTRA_DEBUG +void tablockman_print_tlo(TABLE_LOCK_OWNER *); +#endif + +#endif + diff --git a/storage/maria/test_pack b/storage/maria/test_pack new file mode 100755 index 00000000000..689645b1661 --- /dev/null +++ b/storage/maria/test_pack @@ -0,0 +1,10 @@ +silent="-s" +suffix="" + +ma_test1$suffix -s ; maria_pack$suffix --force -s test1 ; maria_chk$suffix -es test1 ; maria_chk$suffix -rqs test1 ; maria_chk$suffix -es test1 ; maria_chk$suffix -us test1 ; maria_chk$suffix -es test1 +ma_test1$suffix -s -S ; maria_pack$suffix --force -s test1 ; maria_chk$suffix -es test1 ; maria_chk$suffix -rqs test1 ; maria_chk$suffix -es test1 ;maria_chk$suffix -us test1 ; maria_chk$suffix -es test1 +ma_test1$suffix -s -b ; maria_pack$suffix --force -s test1 ; maria_chk$suffix -es test1 ; maria_chk$suffix -rqs test1 ; maria_chk$suffix -es test1 +ma_test1$suffix -s -w ; maria_pack$suffix --force -s test1 ; maria_chk$suffix -es test1 ; maria_chk$suffix -ros test1 ; maria_chk$suffix -es test1 + +ma_test2$suffix -s -t4 ; maria_pack$suffix --force -s test2 ; maria_chk$suffix -es test2 ; maria_chk$suffix -ros test2 ; maria_chk$suffix -es test2 ; maria_chk$suffix -s -u test2 ; maria_chk$suffix -sm test2 +ma_test2$suffix -s -t4 -b ; maria_pack$suffix --force -s test2 ; maria_chk$suffix -es test2 ; maria_chk$suffix -ros test2 ; maria_chk$suffix -es test2 ; maria_chk$suffix -s -u test2 ; maria_chk$suffix -sm test2 diff --git a/storage/maria/trnman.c b/storage/maria/trnman.c new file mode 100644 index 00000000000..43fac68806f --- /dev/null +++ b/storage/maria/trnman.c @@ -0,0 +1,975 @@ +/* Copyright (C) 2006-2008 MySQL AB, 2008-2009 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + + +#include <my_global.h> +#include <my_sys.h> +#include <m_string.h> +#include "trnman.h" +#include "ma_checkpoint.h" +#include "ma_control_file.h" + +/* + status variables: + how many trns in the active list currently, + in the committed list currently, allocated since startup. +*/ +uint trnman_active_transactions, trnman_committed_transactions, + trnman_allocated_transactions; + +/* list of active transactions in the trid order */ +static TRN active_list_min, active_list_max; +/* list of committed transactions in the trid order */ +static TRN committed_list_min, committed_list_max; + +/* a counter, used to generate transaction ids */ +static TrID global_trid_generator; + +/* + The minimum existing transaction id for trnman_get_min_trid() + The default value is used when transaction manager not initialize; + Probably called from maria_chk +*/ +static TrID trid_min_read_from= MAX_TRID; + +/* the mutex for everything above */ +static pthread_mutex_t LOCK_trn_list; + +/* LIFO pool of unused TRN structured for reuse */ +static TRN *pool; + +/* a hash for committed transactions that maps trid to a TRN structure */ +static LF_HASH trid_to_trn; + +/* an array that maps short_id of an active transaction to a TRN structure */ +static TRN **short_trid_to_active_trn; + +/* locks for short_trid_to_active_trn and pool */ +static my_atomic_rwlock_t LOCK_short_trid_to_trn, LOCK_pool; +static my_bool default_trnman_end_trans_hook(TRN *, my_bool, my_bool); +static void trnman_free_trn(TRN *); + +my_bool (*trnman_end_trans_hook)(TRN *, my_bool, my_bool)= + default_trnman_end_trans_hook; + +/* + Simple interface functions + QQ: if they stay so simple, should we make them inline? +*/ + +uint trnman_increment_locked_tables(TRN *trn) +{ + return trn->locked_tables++; +} + +uint trnman_has_locked_tables(TRN *trn) +{ + return trn->locked_tables; +} + +uint trnman_decrement_locked_tables(TRN *trn) +{ + return --trn->locked_tables; +} + +void trnman_reset_locked_tables(TRN *trn, uint locked_tables) +{ + trn->locked_tables= locked_tables; +} + +#ifdef EXTRA_DEBUG +uint16 trnman_get_flags(TRN *trn) +{ + return trn->flags; +} + +void trnman_set_flags(TRN *trn, uint16 flags) +{ + trn->flags= flags; +} +#endif + +/** Wake up threads waiting for this transaction */ +static void wt_thd_release_self(TRN *trn) +{ + if (trn->wt) + { + WT_RESOURCE_ID rc; + rc.type= &ma_rc_dup_unique; + rc.value= (intptr)trn; + wt_thd_release(trn->wt, & rc); + trn->wt= 0; + } +} + +static my_bool +default_trnman_end_trans_hook(TRN *trn __attribute__ ((unused)), + my_bool commit __attribute__ ((unused)), + my_bool active_transactions + __attribute__ ((unused))) +{ + return 0; +} + + +static uchar *trn_get_hash_key(const uchar *trn, size_t *len, + my_bool unused __attribute__ ((unused))) +{ + *len= sizeof(TrID); + return (uchar *) & ((*((TRN **)trn))->trid); +} + + +/** + @brief Initializes transaction manager. + + @param initial_trid Generated TrIDs will start from initial_trid+1. + + @return Operation status + @retval 0 OK + @retval !=0 Error +*/ + +int trnman_init(TrID initial_trid) +{ + DBUG_ENTER("trnman_init"); + + short_trid_to_active_trn= (TRN **)my_malloc(SHORT_TRID_MAX*sizeof(TRN*), + MYF(MY_WME|MY_ZEROFILL)); + if (unlikely(!short_trid_to_active_trn)) + DBUG_RETURN(1); + short_trid_to_active_trn--; /* min short_id is 1 */ + + /* + Initialize lists. + active_list_max.min_read_from must be larger than any trid, + so that when an active list is empty we would could free + all committed list. + And committed_list_max itself can not be freed so + committed_list_max.commit_trid must not be smaller that + active_list_max.min_read_from + */ + + active_list_max.trid= active_list_min.trid= 0; + active_list_max.min_read_from= MAX_TRID; + active_list_max.next= active_list_min.prev= 0; + active_list_max.prev= &active_list_min; + active_list_min.next= &active_list_max; + + committed_list_max.commit_trid= MAX_TRID; + committed_list_max.next= committed_list_min.prev= 0; + committed_list_max.prev= &committed_list_min; + committed_list_min.next= &committed_list_max; + + trnman_active_transactions= 0; + trnman_committed_transactions= 0; + trnman_allocated_transactions= 0; + + pool= 0; + global_trid_generator= initial_trid; + trid_min_read_from= initial_trid; + lf_hash_init(&trid_to_trn, sizeof(TRN*), LF_HASH_UNIQUE, + 0, 0, trn_get_hash_key, 0); + DBUG_PRINT("info", ("pthread_mutex_init LOCK_trn_list")); + pthread_mutex_init(&LOCK_trn_list, MY_MUTEX_INIT_FAST); + my_atomic_rwlock_init(&LOCK_short_trid_to_trn); + my_atomic_rwlock_init(&LOCK_pool); + + DBUG_RETURN(0); +} + +/* + NOTE + this could only be called in the "idle" state - no transaction can be + running. See asserts below. +*/ +void trnman_destroy() +{ + DBUG_ENTER("trnman_destroy"); + + if (short_trid_to_active_trn == NULL) /* trnman already destroyed */ + DBUG_VOID_RETURN; + DBUG_ASSERT(trid_to_trn.count == 0); + DBUG_ASSERT(trnman_active_transactions == 0); + DBUG_ASSERT(trnman_committed_transactions == 0); + DBUG_ASSERT(active_list_max.prev == &active_list_min); + DBUG_ASSERT(active_list_min.next == &active_list_max); + DBUG_ASSERT(committed_list_max.prev == &committed_list_min); + DBUG_ASSERT(committed_list_min.next == &committed_list_max); + while (pool) + { + TRN *trn= pool; + pool= pool->next; + DBUG_ASSERT(trn->wt == NULL); + pthread_mutex_destroy(&trn->state_lock); + my_free((void *)trn, MYF(0)); + } + lf_hash_destroy(&trid_to_trn); + DBUG_PRINT("info", ("pthread_mutex_destroy LOCK_trn_list")); + pthread_mutex_destroy(&LOCK_trn_list); + my_atomic_rwlock_destroy(&LOCK_short_trid_to_trn); + my_atomic_rwlock_destroy(&LOCK_pool); + my_free((void *)(short_trid_to_active_trn+1), MYF(0)); + short_trid_to_active_trn= NULL; + + DBUG_VOID_RETURN; +} + +/* + NOTE + TrID is limited to 6 bytes. Initial value of the generator + is set by the recovery code - being read from the last checkpoint + (or 1 on a first run). +*/ +static TrID new_trid() +{ + DBUG_ENTER("new_trid"); + DBUG_ASSERT(global_trid_generator < 0xffffffffffffLL); + DBUG_PRINT("info", ("safe_mutex_assert_owner LOCK_trn_list")); + safe_mutex_assert_owner(&LOCK_trn_list); + DBUG_RETURN(++global_trid_generator); +} + +static uint get_short_trid(TRN *trn) +{ + int i= (int) ((global_trid_generator + (intptr)trn) * 312089 % + SHORT_TRID_MAX) + 1; + uint res=0; + + for ( ; !res ; i= 1) + { + my_atomic_rwlock_wrlock(&LOCK_short_trid_to_trn); + for ( ; i <= SHORT_TRID_MAX; i++) /* the range is [1..SHORT_TRID_MAX] */ + { + void *tmp= NULL; + if (short_trid_to_active_trn[i] == NULL && + my_atomic_casptr((void **)&short_trid_to_active_trn[i], &tmp, trn)) + { + res= i; + break; + } + } + my_atomic_rwlock_wrunlock(&LOCK_short_trid_to_trn); + } + return res; +} + +/** + Allocates and initialzies a new TRN object + + @note the 'wt' parameter can only be 0 in a single-threaded code (or, + generally, where threads cannot block each other), otherwise the + first call to the deadlock detector will sigsegv. +*/ + +TRN *trnman_new_trn(WT_THD *wt) +{ + int res; + TRN *trn; + union { TRN *trn; void *v; } tmp; + DBUG_ENTER("trnman_new_trn"); + + /* + we have a mutex, to do simple things under it - allocate a TRN, + increment trnman_active_transactions, set trn->min_read_from. + + Note that all the above is fast. generating short_id may be slow, + as it involves scanning a large array - so it's done outside of the + mutex. + */ + + DBUG_PRINT("info", ("pthread_mutex_lock LOCK_trn_list")); + pthread_mutex_lock(&LOCK_trn_list); + + /* Allocating a new TRN structure */ + tmp.trn= pool; + /* + Popping an unused TRN from the pool + (ABA isn't possible, we're behind a mutex + */ + my_atomic_rwlock_wrlock(&LOCK_pool); + while (tmp.trn && !my_atomic_casptr((void **)&pool, &tmp.v, + (void *)tmp.trn->next)) + /* no-op */; + my_atomic_rwlock_wrunlock(&LOCK_pool); + + /* Nothing in the pool ? Allocate a new one */ + if (!(trn= tmp.trn)) + { + /* + trn should be completely initalized at create time to allow + one to keep a known state on it. + (Like redo_lns, which is assumed to be 0 at start of row handling + and reset to zero before end of row handling) + */ + trn= (TRN *)my_malloc(sizeof(TRN), MYF(MY_WME | MY_ZEROFILL)); + if (unlikely(!trn)) + { + DBUG_PRINT("info", ("pthread_mutex_unlock LOCK_trn_list")); + pthread_mutex_unlock(&LOCK_trn_list); + return 0; + } + trnman_allocated_transactions++; + pthread_mutex_init(&trn->state_lock, MY_MUTEX_INIT_FAST); + } + trn->wt= wt; + trn->pins= lf_hash_get_pins(&trid_to_trn); + if (!trn->pins) + { + trnman_free_trn(trn); + pthread_mutex_unlock(&LOCK_trn_list); + return 0; + } + + trnman_active_transactions++; + + trn->min_read_from= active_list_min.next->trid; + + trn->trid= new_trid(); + + trn->next= &active_list_max; + trn->prev= active_list_max.prev; + active_list_max.prev= trn->prev->next= trn; + trid_min_read_from= active_list_min.next->min_read_from; + DBUG_PRINT("info", ("pthread_mutex_unlock LOCK_trn_list")); + pthread_mutex_unlock(&LOCK_trn_list); + + if (unlikely(!trn->min_read_from)) + { + /* + We are the only transaction. Set min_read_from so that we can read + our own rows + */ + trn->min_read_from= trn->trid + 1; + } + + /* no other transaction can read changes done by this one */ + trn->commit_trid= MAX_TRID; + trn->rec_lsn= trn->undo_lsn= trn->first_undo_lsn= 0; + trn->used_tables= 0; + + trn->locked_tables= 0; + + /* + only after the following function TRN is considered initialized, + so it must be done the last + */ + pthread_mutex_lock(&trn->state_lock); + trn->short_id= get_short_trid(trn); + pthread_mutex_unlock(&trn->state_lock); + + res= lf_hash_insert(&trid_to_trn, trn->pins, &trn); + DBUG_ASSERT(res <= 0); + if (res) + { + trnman_end_trn(trn, 0); + return 0; + } + + DBUG_PRINT("exit", ("trn: 0x%lx trid: 0x%lu", + (ulong) trn, (ulong) trn->trid)); + + DBUG_RETURN(trn); +} + +/* + remove a trn from the active list. + if necessary - move to committed list and set commit_trid + + NOTE + Locks are released at the end. In particular, after placing the + transaction in commit list, and after setting commit_trid. It's + important, as commit_trid affects visibility. Locks don't affect + anything they simply delay execution of other threads - they could be + released arbitrarily late. In other words, when locks are released it + serves as a start banner for other threads, they start to run. So + everything they may need must be ready at that point. + + RETURN + 0 ok + 1 error +*/ +my_bool trnman_end_trn(TRN *trn, my_bool commit) +{ + int res= 1; + uint16 cached_short_id= trn->short_id; /* we have to cache it, see below */ + TRN *free_me= 0; + LF_PINS *pins= trn->pins; + DBUG_ENTER("trnman_end_trn"); + DBUG_PRINT("enter", ("trn=0x%lx commit=%d", (ulong) trn, commit)); + + /* if a rollback, all UNDO records should have been executed */ + DBUG_ASSERT(commit || trn->undo_lsn == 0); + DBUG_ASSERT(trn != &dummy_transaction_object); + DBUG_PRINT("info", ("pthread_mutex_lock LOCK_trn_list")); + + pthread_mutex_lock(&LOCK_trn_list); + + /* remove from active list */ + trn->next->prev= trn->prev; + trn->prev->next= trn->next; + + /* + if trn was the oldest active transaction, now that it goes away there + may be committed transactions in the list which no active transaction + needs to bother about - clean up the committed list + */ + if (trn->prev == &active_list_min) + { + uint free_me_count; + TRN *t; + for (t= committed_list_min.next, free_me_count= 0; + t->commit_trid < active_list_min.next->min_read_from; + t= t->next, free_me_count++) /* no-op */; + + DBUG_ASSERT((t != committed_list_min.next && free_me_count > 0) || + (t == committed_list_min.next && free_me_count == 0)); + /* found transactions committed before the oldest active one */ + if (t != committed_list_min.next) + { + free_me= committed_list_min.next; + committed_list_min.next= t; + t->prev->next= 0; + t->prev= &committed_list_min; + trnman_committed_transactions-= free_me_count; + } + } + + pthread_mutex_lock(&trn->state_lock); + if (commit) + trn->commit_trid= global_trid_generator; + wt_thd_release_self(trn); + pthread_mutex_unlock(&trn->state_lock); + + /* + if transaction is committed and it was not the only active transaction - + add it to the committed list + */ + if (commit && active_list_min.next != &active_list_max) + { + trn->next= &committed_list_max; + trn->prev= committed_list_max.prev; + trnman_committed_transactions++; + committed_list_max.prev= trn->prev->next= trn; + } + else + { + trn->next= free_me; + free_me= trn; + } + trid_min_read_from= active_list_min.next->min_read_from; + + if ((*trnman_end_trans_hook)(trn, commit, + active_list_min.next != &active_list_max)) + res= -1; + trnman_active_transactions--; + + DBUG_PRINT("info", ("pthread_mutex_unlock LOCK_trn_list")); + pthread_mutex_unlock(&LOCK_trn_list); + + /* + the rest is done outside of a critical section + + note that we don't own trn anymore, it may be in a shared list now. + Thus, we cannot dereference it, and must use cached_short_id below. + */ + my_atomic_rwlock_rdlock(&LOCK_short_trid_to_trn); + my_atomic_storeptr((void **)&short_trid_to_active_trn[cached_short_id], 0); + my_atomic_rwlock_rdunlock(&LOCK_short_trid_to_trn); + + /* + we, under the mutex, removed going-in-free_me transactions from the + active and committed lists, thus nobody else may see them when it scans + those lists, and thus nobody may want to free them. Now we don't + need a mutex to access free_me list + */ + /* QQ: send them to the purge thread */ + while (free_me) + { + TRN *t= free_me; + free_me= free_me->next; + + /* ignore OOM. it's harmless, and we can do nothing here anyway */ + (void)lf_hash_delete(&trid_to_trn, pins, &t->trid, sizeof(TrID)); + + trnman_free_trn(t); + } + + lf_hash_put_pins(pins); + + DBUG_RETURN(res < 0); +} + +/* + free a trn (add to the pool, that is) + note - we can never really free() a TRN if there's at least one other + running transaction - see, e.g., how lock waits are implemented in + lockman.c + The same is true for other lock-free data structures too. We may need some + kind of FLUSH command to reset them all - ensuring that no transactions are + running. It may even be called automatically on checkpoints if no + transactions are running. +*/ +static void trnman_free_trn(TRN *trn) +{ + /* + union is to solve strict aliasing issue. + without it gcc 3.4.3 doesn't notice that updating *(void **)&tmp + modifies the value of tmp. + */ + union { TRN *trn; void *v; } tmp; + + pthread_mutex_lock(&trn->state_lock); + trn->short_id= 0; + pthread_mutex_unlock(&trn->state_lock); + + tmp.trn= pool; + + my_atomic_rwlock_wrlock(&LOCK_pool); + do + { + /* + without this volatile cast gcc-3.4.4 moves the assignment + down after the loop at -O2 + */ + *(TRN * volatile *)&(trn->next)= tmp.trn; + } while (!my_atomic_casptr((void **)&pool, &tmp.v, trn)); + my_atomic_rwlock_wrunlock(&LOCK_pool); +} + +/* + NOTE + here we access the hash in a lock-free manner. + It's safe, a 'found' TRN can never be freed/reused before we access it. + In fact, it cannot be freed before 'trn' ends, because a 'found' TRN + can only be removed from the hash when: + found->commit_trid < ALL (trn->min_read_from) + that is, at least + found->commit_trid < trn->min_read_from + but + found->trid >= trn->min_read_from + and + found->commit_trid > found->trid + + RETURN + 1 can + 0 cannot + -1 error (OOM) +*/ +int trnman_can_read_from(TRN *trn, TrID trid) +{ + TRN **found; + my_bool can; + LF_REQUIRE_PINS(3); + + if (trid < trn->min_read_from) + return 1; /* Row is visible by all transactions in the system */ + + if (trid >= trn->trid) + { + /* + We have now two cases + trid > trn->trid, in which case the row is from a new transaction + and not visible, in which case we should return 0. + trid == trn->trid in which case the row is from the current transaction + and we should return 1 + */ + return trid == trn->trid; + } + + found= lf_hash_search(&trid_to_trn, trn->pins, &trid, sizeof(trid)); + if (found == NULL) + return 0; /* not in the hash of transactions = cannot read */ + if (found == MY_ERRPTR) + return -1; + + can= (*found)->commit_trid < trn->trid; + lf_hash_search_unpin(trn->pins); + return can; +} + +/** + Finds a TRN by its TrID + + @param trn current trn. Needed for pinning pointers (see lf_pin) + @param trid trid to search for + + @return found trn or 0 + + @note that trn is returned with its state locked! +*/ +TRN *trnman_trid_to_trn(TRN *trn, TrID trid) +{ + TRN **found; + LF_REQUIRE_PINS(3); + + if (trid < trn->min_read_from) + return 0; /* it's committed eons ago */ + + found= lf_hash_search(&trid_to_trn, trn->pins, &trid, sizeof(trid)); + if (found == NULL || found == MY_ERRPTR) + return 0; /* no luck */ + + /* we've found something */ + pthread_mutex_lock(&(*found)->state_lock); + + if ((*found)->short_id == 0) + { + pthread_mutex_unlock(&(*found)->state_lock); + lf_hash_search_unpin(trn->pins); + return 0; /* but it was a ghost */ + } + lf_hash_search_unpin(trn->pins); + + /* Gotcha! */ + return *found; +} + +/* TODO: the stubs below are waiting for savepoints to be implemented */ + +void trnman_new_statement(TRN *trn __attribute__ ((unused))) +{ +} + +void trnman_rollback_statement(TRN *trn __attribute__ ((unused))) +{ +} + + +/** + @brief Allocates buffers and stores in them some info about transactions + + Does the allocation because the caller cannot know the size itself. + Memory freeing is to be done by the caller (if the "str" member of the + LEX_STRING is not NULL). + The caller has the intention of doing checkpoints. + + @param[out] str_act pointer to where the allocated buffer, + and its size, will be put; buffer will be filled + with info about active transactions + @param[out] str_com pointer to where the allocated buffer, + and its size, will be put; buffer will be filled + with info about committed transactions + @param[out] min_first_undo_lsn pointer to where the minimum + first_undo_lsn of all transactions will be put + + @return Operation status + @retval 0 OK + @retval 1 Error +*/ + +my_bool trnman_collect_transactions(LEX_STRING *str_act, LEX_STRING *str_com, + LSN *min_rec_lsn, LSN *min_first_undo_lsn) +{ + my_bool error; + TRN *trn; + char *ptr; + uint stored_transactions= 0; + LSN minimum_rec_lsn= LSN_MAX, minimum_first_undo_lsn= LSN_MAX; + DBUG_ENTER("trnman_collect_transactions"); + + DBUG_ASSERT((NULL == str_act->str) && (NULL == str_com->str)); + + /* validate the use of read_non_atomic() in general: */ + compile_time_assert((sizeof(LSN) == 8) && (sizeof(LSN_WITH_FLAGS) == 8)); + pthread_mutex_lock(&LOCK_trn_list); + str_act->length= 2 + /* number of active transactions */ + LSN_STORE_SIZE + /* minimum of their rec_lsn */ + TRANSID_SIZE + /* current TrID generator value */ + (2 + /* short id */ + 6 + /* long id */ + LSN_STORE_SIZE + /* undo_lsn */ +#ifdef MARIA_VERSIONING /* not enabled yet */ + LSN_STORE_SIZE + /* undo_purge_lsn */ +#endif + LSN_STORE_SIZE /* first_undo_lsn */ + ) * trnman_active_transactions; + str_com->length= 4 + /* number of committed transactions */ + (6 + /* long id */ +#ifdef MARIA_VERSIONING /* not enabled yet */ + LSN_STORE_SIZE + /* undo_purge_lsn */ +#endif + LSN_STORE_SIZE /* first_undo_lsn */ + ) * trnman_committed_transactions; + if ((NULL == (str_act->str= my_malloc(str_act->length, MYF(MY_WME)))) || + (NULL == (str_com->str= my_malloc(str_com->length, MYF(MY_WME))))) + goto err; + /* First, the active transactions */ + ptr= str_act->str + 2 + LSN_STORE_SIZE; + transid_store(ptr, global_trid_generator); + ptr+= TRANSID_SIZE; + for (trn= active_list_min.next; trn != &active_list_max; trn= trn->next) + { + uint sid; + LSN rec_lsn, undo_lsn, first_undo_lsn; + pthread_mutex_lock(&trn->state_lock); + sid= trn->short_id; + pthread_mutex_unlock(&trn->state_lock); + if (sid == 0) + { + /* + Not even inited, has done nothing. Or it is the + dummy_transaction_object, which does only non-transactional + immediate-sync operations (CREATE/DROP/RENAME/REPAIR TABLE), and so + can be forgotten for Checkpoint. + */ + continue; + } + /* needed for low-water mark calculation */ + if (((rec_lsn= lsn_read_non_atomic(trn->rec_lsn)) > 0) && + (cmp_translog_addr(rec_lsn, minimum_rec_lsn) < 0)) + minimum_rec_lsn= rec_lsn; + /* + If trn has not logged LOGREC_LONG_TRANSACTION_ID, this trn will be + discovered when seeing that log record which is for sure located after + checkpoint_start_log_horizon. + */ + if ((LSN_WITH_FLAGS_TO_FLAGS(trn->first_undo_lsn) & + TRANSACTION_LOGGED_LONG_ID) == 0) + continue; + /* + On the other hand, if undo_lsn is LSN_IMPOSSIBLE, trn may later log + records; so we must include trn in the checkpoint now, because we cannot + count on LOGREC_LONG_TRANSACTION_ID (as we are already past it). + */ + undo_lsn= trn->undo_lsn; + stored_transactions++; + int2store(ptr, sid); + ptr+= 2; + int6store(ptr, trn->trid); + ptr+= 6; + lsn_store(ptr, undo_lsn); /* needed for rollback */ + ptr+= LSN_STORE_SIZE; + /* needed for low-water mark calculation */ + if (((first_undo_lsn= lsn_read_non_atomic(trn->first_undo_lsn)) > 0) && + (cmp_translog_addr(first_undo_lsn, minimum_first_undo_lsn) < 0)) + minimum_first_undo_lsn= first_undo_lsn; + lsn_store(ptr, first_undo_lsn); + ptr+= LSN_STORE_SIZE; +#ifdef MARIA_VERSIONING /* not enabled yet */ + /* to know where purging should start (last delete of this trn) */ + lsn_store(ptr, trn->undo_purge_lsn); + ptr+= LSN_STORE_SIZE; +#endif + /** + @todo RECOVERY: add a comment explaining why we can dirtily read some + vars, inspired by the text of "assumption 8" in WL#3072 + */ + } + str_act->length= ptr - str_act->str; /* as we maybe over-estimated */ + ptr= str_act->str; + DBUG_PRINT("info",("collected %u active transactions", + (uint)stored_transactions)); + int2store(ptr, stored_transactions); + ptr+= 2; + /* this LSN influences how REDOs for any page can be ignored by Recovery */ + lsn_store(ptr, minimum_rec_lsn); + /* one day there will also be a list of prepared transactions */ + /* do the same for committed ones */ + ptr= str_com->str; + int4store(ptr, trnman_committed_transactions); + ptr+= 4; + DBUG_PRINT("info",("collected %u committed transactions", + (uint)trnman_committed_transactions)); + for (trn= committed_list_min.next; trn != &committed_list_max; + trn= trn->next) + { + LSN first_undo_lsn; + int6store(ptr, trn->trid); + ptr+= 6; +#ifdef MARIA_VERSIONING /* not enabled yet */ + lsn_store(ptr, trn->undo_purge_lsn); + ptr+= LSN_STORE_SIZE; +#endif + first_undo_lsn= LSN_WITH_FLAGS_TO_LSN(trn->first_undo_lsn); + if (cmp_translog_addr(first_undo_lsn, minimum_first_undo_lsn) < 0) + minimum_first_undo_lsn= first_undo_lsn; + lsn_store(ptr, first_undo_lsn); + ptr+= LSN_STORE_SIZE; + } + /* + TODO: if we see there exists no transaction (active and committed) we can + tell the lock-free structures to do some freeing (my_free()). + */ + error= 0; + *min_rec_lsn= minimum_rec_lsn; + *min_first_undo_lsn= minimum_first_undo_lsn; + goto end; +err: + error= 1; +end: + pthread_mutex_unlock(&LOCK_trn_list); + DBUG_RETURN(error); +} + + +TRN *trnman_recreate_trn_from_recovery(uint16 shortid, TrID longid) +{ + TrID old_trid_generator= global_trid_generator; + TRN *trn; + DBUG_ASSERT(maria_in_recovery && !maria_multi_threaded); + global_trid_generator= longid-1; /* force a correct trid in the new trn */ + if (unlikely((trn= trnman_new_trn(NULL)) == NULL)) + return NULL; + /* deallocate excessive allocations of trnman_new_trn() */ + global_trid_generator= old_trid_generator; + set_if_bigger(global_trid_generator, longid); + short_trid_to_active_trn[trn->short_id]= 0; + DBUG_ASSERT(short_trid_to_active_trn[shortid] == NULL); + short_trid_to_active_trn[shortid]= trn; + trn->short_id= shortid; + return trn; +} + + +TRN *trnman_get_any_trn() +{ + TRN *trn= active_list_min.next; + return (trn != &active_list_max) ? trn : NULL; +} + + +/** + Returns the minimum existing transaction id. May return a too small + number in race conditions, but this is ok as the value is used to + remove not visible transid from index/rows. +*/ + +TrID trnman_get_min_trid() +{ + return trid_min_read_from; +} + + +/** + Returns the minimum possible transaction id + + @notes + If there is no transactions running, returns number for next running + transaction. + If one has an active transaction, the returned number will be less or + equal to this. If one is not running in a transaction one will ge the + number for the next started transaction. This is used in create table + to get a safe minimum trid to use. +*/ + +TrID trnman_get_min_safe_trid() +{ + TrID trid; + pthread_mutex_lock(&LOCK_trn_list); + trid= min(active_list_min.next->min_read_from, + global_trid_generator); + pthread_mutex_unlock(&LOCK_trn_list); + return trid; +} + + +/** + Returns maximum transaction id given to a transaction so far. +*/ + +TrID trnman_get_max_trid() +{ + TrID id; + if (short_trid_to_active_trn == NULL) + return 0; + pthread_mutex_lock(&LOCK_trn_list); + id= global_trid_generator; + pthread_mutex_unlock(&LOCK_trn_list); + return id; +} + +/** + @brief Check if there exist an active transaction between two commit_id's + + @todo + Improve speed of this. + - Store transactions in tree or skip list + - Have function to copying all active transaction id's to b-tree + and use b-tree for checking states. This could be a big win + for checkpoint that will call this function for a lot of objects. + + @return + 0 No transaction exists + 1 There is at least on active transaction in the given range +*/ + +my_bool trnman_exists_active_transactions(TrID min_id, TrID max_id, + my_bool trnman_is_locked) +{ + TRN *trn; + my_bool ret= 0; + + if (!trnman_is_locked) + pthread_mutex_lock(&LOCK_trn_list); + safe_mutex_assert_owner(&LOCK_trn_list); + for (trn= active_list_min.next; trn != &active_list_max; trn= trn->next) + { + /* + We use <= for max_id as max_id is a commit_trid and trn->trid + is transaction id. When calculating commit_trid we use the + current value of global_trid_generator. global_trid_generator is + incremented for each new transaction. + + For example, assuming we have + min_id = 5 + max_id = 10 + + A trid of value 5 can't see the history event between 5 & 10 + at it vas started before min_id 5 was committed. + A trid of value 10 can't see the next history event (max_id = 10) + as it started before this was committed. In this case it must use + the this event. + */ + if (trn->trid > min_id && trn->trid <= max_id) + { + ret= 1; + break; + } + } + if (!trnman_is_locked) + pthread_mutex_unlock(&LOCK_trn_list); + return ret; +} + + +/** + lock transaction list +*/ + +void trnman_lock() +{ + pthread_mutex_lock(&LOCK_trn_list); +} + + +/** + unlock transaction list +*/ + +void trnman_unlock() +{ + pthread_mutex_unlock(&LOCK_trn_list); +} + + +/** + Is trman initialized +*/ + +my_bool trman_is_inited() +{ + return (short_trid_to_active_trn != NULL); +} diff --git a/storage/maria/trnman.h b/storage/maria/trnman.h new file mode 100644 index 00000000000..afe01d4ad10 --- /dev/null +++ b/storage/maria/trnman.h @@ -0,0 +1,67 @@ +/* Copyright (C) 2006-2008 MySQL AB, 2008-2009 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#ifndef _trnman_h +#define _trnman_h + +C_MODE_START + +#include <lf.h> +#include "trnman_public.h" +#include "ma_loghandler_lsn.h" + +/** + trid - 6 uchar transaction identifier. Assigned when a transaction + is created. Transaction can always be identified by its trid, + even after transaction has ended. + + short_id - 2-byte transaction identifier, identifies a running + transaction, is reassigned when transaction ends. + + when short_id is 0, TRN is not initialized, for all practical purposes + it could be considered unused. + + when commit_trid is MAX_TRID the transaction is running, otherwise it's + committed. + + state_lock mutex protects the state of a TRN, that is whether a TRN + is committed/running/unused. Meaning that modifications of short_id and + commit_trid happen under this mutex. +*/ + +struct st_ma_transaction +{ + LF_PINS *pins; + WT_THD *wt; + pthread_mutex_t state_lock; + void *used_tables; /**< Tables used by transaction */ + TRN *next, *prev; + TrID trid, min_read_from, commit_trid; + LSN rec_lsn, undo_lsn; + LSN_WITH_FLAGS first_undo_lsn; + uint locked_tables; + uint16 short_id; + uint16 flags; /**< Various flags */ +}; + +#define TRANSACTION_LOGGED_LONG_ID ULL(0x8000000000000000) +#define MAX_TRID (~(TrID)0) + +extern WT_RESOURCE_TYPE ma_rc_dup_unique; + +C_MODE_END + +#endif + diff --git a/storage/maria/trnman_public.h b/storage/maria/trnman_public.h new file mode 100644 index 00000000000..9523eb5de8f --- /dev/null +++ b/storage/maria/trnman_public.h @@ -0,0 +1,85 @@ +/* Copyright (C) 2006-2008 MySQL AB, 2008-2009 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + + +/* + External definitions for trnman.h + We need to split this into two files as gcc 4.1.2 gives error if it tries + to include my_atomic.h in C++ code. +*/ + +#ifndef _trnman_public_h +#define _trnman_public_h + +#include "ma_loghandler_lsn.h" +#include <waiting_threads.h> + +C_MODE_START +typedef uint64 TrID; /* our TrID is 6 bytes */ +typedef struct st_ma_transaction TRN; + +#define SHORT_TRID_MAX 65535 + +extern uint trnman_active_transactions, trnman_allocated_transactions; +extern TRN dummy_transaction_object; +extern my_bool (*trnman_end_trans_hook)(TRN *trn, my_bool commit, + my_bool active_transactions); + +int trnman_init(TrID); +void trnman_destroy(void); +TRN *trnman_new_trn(WT_THD *wt); +my_bool trnman_end_trn(TRN *trn, my_bool commit); +#define trnman_commit_trn(T) trnman_end_trn(T, TRUE) +#define trnman_abort_trn(T) trnman_end_trn(T, FALSE) +#define trnman_rollback_trn(T) trnman_end_trn(T, FALSE) +int trnman_can_read_from(TRN *trn, TrID trid); +TRN *trnman_trid_to_trn(TRN *trn, TrID trid); +void trnman_new_statement(TRN *trn); +void trnman_rollback_statement(TRN *trn); +my_bool trnman_collect_transactions(LEX_STRING *str_act, LEX_STRING *str_com, + LSN *min_rec_lsn, + LSN *min_first_undo_lsn); + +uint trnman_increment_locked_tables(TRN *trn); +uint trnman_decrement_locked_tables(TRN *trn); +uint trnman_has_locked_tables(TRN *trn); +void trnman_reset_locked_tables(TRN *trn, uint locked_tables); +TRN *trnman_recreate_trn_from_recovery(uint16 shortid, TrID longid); +TRN *trnman_get_any_trn(void); +TrID trnman_get_min_trid(void); +TrID trnman_get_max_trid(void); +TrID trnman_get_min_safe_trid(); +my_bool trnman_exists_active_transactions(TrID min_id, TrID max_id, + my_bool trnman_is_locked); +#define TRANSID_SIZE 6 +#define transid_store(dst, id) int6store(dst,id) +#define transid_korr(P) uint6korr(P) +void trnman_lock(); +void trnman_unlock(); +my_bool trman_is_inited(); +#ifdef EXTRA_DEBUG +uint16 trnman_get_flags(TRN *); +void trnman_set_flags(TRN *, uint16 flags); +#else +#define trnman_get_flags(A) 0 +#define trnman_set_flags(A, B) do { } while (0) +#endif + +/* Flag bits */ +#define TRN_STATE_INFO_LOGGED 1 /* Query is logged */ +#define TRN_STATE_TABLES_CAN_CHANGE 2 /* Things can change during trans. */ + +C_MODE_END +#endif diff --git a/storage/maria/unittest/CMakeLists.txt b/storage/maria/unittest/CMakeLists.txt new file mode 100644 index 00000000000..3d732f6245d --- /dev/null +++ b/storage/maria/unittest/CMakeLists.txt @@ -0,0 +1,98 @@ +# Copyright (C) 2007 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +SET(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -DSAFEMALLOC -DSAFE_MUTEX") +SET(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -DSAFEMALLOC -DSAFE_MUTEX") + +INCLUDE_DIRECTORIES(${CMAKE_SOURCE_DIR}/include ${CMAKE_SOURCE_DIR}/zlib + ${CMAKE_SOURCE_DIR}/unittest/mytap) +LINK_LIBRARIES(maria myisam mytap mysys dbug strings wsock32 zlib) + +ADD_EXECUTABLE(ma_control_file-t ma_control_file-t.c) +ADD_EXECUTABLE(trnman-t trnman-t.c) +ADD_EXECUTABLE(ma_test_loghandler-t + ma_test_loghandler-t.c ma_maria_log_cleanup.c ma_loghandler_examples.c) +ADD_EXECUTABLE(ma_test_loghandler_multigroup-t + ma_test_loghandler_multigroup-t.c ma_maria_log_cleanup.c ma_loghandler_examples.c sequence_storage.c) +ADD_EXECUTABLE(ma_test_loghandler_multithread-t + ma_test_loghandler_multithread-t.c ma_maria_log_cleanup.c ma_loghandler_examples.c) +ADD_EXECUTABLE(ma_test_loghandler_pagecache-t + ma_test_loghandler_pagecache-t.c ma_maria_log_cleanup.c ma_loghandler_examples.c) +ADD_EXECUTABLE(ma_test_loghandler_long-t + ma_test_loghandler-t.c ma_maria_log_cleanup.c ma_loghandler_examples.c) +SET_TARGET_PROPERTIES(ma_test_loghandler_long-t PROPERTIES COMPILE_FLAGS "-DLONG_LOG_TEST") + +ADD_EXECUTABLE(ma_test_loghandler_noflush-t + ma_test_loghandler_noflush-t.c ma_maria_log_cleanup.c ma_loghandler_examples.c) +ADD_EXECUTABLE(ma_test_loghandler_first_lsn-t + ma_test_loghandler_first_lsn-t.c ma_maria_log_cleanup.c ma_loghandler_examples.c) +ADD_EXECUTABLE(ma_test_loghandler_max_lsn-t + ma_test_loghandler_max_lsn-t.c ma_maria_log_cleanup.c ma_loghandler_examples.c) +ADD_EXECUTABLE(ma_test_loghandler_purge-t + ma_test_loghandler_purge-t.c ma_maria_log_cleanup.c ma_loghandler_examples.c) +ADD_EXECUTABLE(ma_test_loghandler_readonly-t + ma_test_loghandler_multigroup-t.c ma_maria_log_cleanup.c ma_loghandler_examples.c sequence_storage.c) +SET_TARGET_PROPERTIES(ma_test_loghandler_readonly-t PROPERTIES COMPILE_FLAGS "-DREADONLY_TEST") +ADD_EXECUTABLE(ma_test_loghandler_nologs-t + ma_test_loghandler_nologs-t.c ma_maria_log_cleanup.c ma_loghandler_examples.c) + +SET(ma_pagecache_single_src ma_pagecache_single.c test_file.c test_file.h) +SET(ma_pagecache_consist_src ma_pagecache_consist.c test_file.c test_file.h) +SET(ma_pagecache_common_cppflags "-DEXTRA_DEBUG -DPAGECACHE_DEBUG -DMAIN") + +ADD_EXECUTABLE(ma_pagecache_single_1k-t ${ma_pagecache_single_src}) +SET_TARGET_PROPERTIES(ma_pagecache_single_1k-t + PROPERTIES COMPILE_FLAGS "${ma_pagecache_common_cppflags} -DTEST_PAGE_SIZE=1024") + +ADD_EXECUTABLE(ma_pagecache_single_8k-t ${ma_pagecache_single_src}) +SET_TARGET_PROPERTIES(ma_pagecache_single_8k-t + PROPERTIES COMPILE_FLAGS "${ma_pagecache_common_cppflags} -DTEST_PAGE_SIZE=8192") + +ADD_EXECUTABLE(ma_pagecache_single_64k-t ${ma_pagecache_single_src}) +SET_TARGET_PROPERTIES(ma_pagecache_single_64k-t + PROPERTIES COMPILE_FLAGS "${ma_pagecache_common_cppflags} -DTEST_PAGE_SIZE=65536") + +ADD_EXECUTABLE(ma_pagecache_consist_1k-t ${ma_pagecache_consist_src}) +SET_TARGET_PROPERTIES(ma_pagecache_consist_1k-t + PROPERTIES COMPILE_FLAGS "${ma_pagecache_common_cppflags} -DTEST_PAGE_SIZE=1024") + +ADD_EXECUTABLE(ma_pagecache_consist_64k-t ${ma_pagecache_consist_src}) +SET_TARGET_PROPERTIES(ma_pagecache_consist_64k-t + PROPERTIES COMPILE_FLAGS "${ma_pagecache_common_cppflags} -DTEST_PAGE_SIZE=65536") + +ADD_EXECUTABLE(ma_pagecache_consist_1kHC-t + ${ma_pagecache_consist_src}) +SET_TARGET_PROPERTIES(ma_pagecache_consist_1kHC-t + PROPERTIES COMPILE_FLAGS "${ma_pagecache_common_cppflags} -DTEST_PAGE_SIZE=1024 -DTEST_HIGH_CONCURENCY") +ADD_EXECUTABLE(ma_pagecache_consist_64kHC-t + ${ma_pagecache_consist_src}) +SET_TARGET_PROPERTIES(ma_pagecache_consist_64kHC-t + PROPERTIES COMPILE_FLAGS "${ma_pagecache_common_cppflags} -DTEST_PAGE_SIZE=65536 -DTEST_HIGH_CONCURENCY") +ADD_EXECUTABLE(ma_pagecache_consist_1kRD-t ${ma_pagecache_consist_src}) +SET_TARGET_PROPERTIES(ma_pagecache_consist_1kRD-t + PROPERTIES COMPILE_FLAGS "${ma_pagecache_common_cppflags} -DTEST_PAGE_SIZE=1024 -DTEST_READERS") +ADD_EXECUTABLE(ma_pagecache_consist_64kRD-t ${ma_pagecache_consist_src}) +SET_TARGET_PROPERTIES(ma_pagecache_consist_64kRD-t + PROPERTIES COMPILE_FLAGS "${ma_pagecache_common_cppflags} -DTEST_PAGE_SIZE=65536 -DTEST_READERS") +ADD_EXECUTABLE(ma_pagecache_consist_1kWR-t ${ma_pagecache_consist_src}) +SET_TARGET_PROPERTIES(ma_pagecache_consist_1kWR-t + PROPERTIES COMPILE_FLAGS "${ma_pagecache_common_cppflags} -DTEST_PAGE_SIZE=1024 -DTEST_WRITERS") +ADD_EXECUTABLE(ma_pagecache_consist_64kWR-t ${ma_pagecache_consist_src}) +SET_TARGET_PROPERTIES(ma_pagecache_consist_64kWR-t + PROPERTIES COMPILE_FLAGS "${ma_pagecache_common_cppflags} -DTEST_PAGE_SIZE=65536 -DTEST_WRITERS") +ADD_EXECUTABLE(ma_pagecache_rwconsist_1k-t ma_pagecache_rwconsist.c) +SET_TARGET_PROPERTIES(ma_pagecache_rwconsist_1k-t PROPERTIES COMPILE_FLAGS "-DTEST_PAGE_SIZE=1024") +ADD_EXECUTABLE(ma_pagecache_rwconsist2_1k-t ma_pagecache_rwconsist2.c) +SET_TARGET_PROPERTIES(ma_pagecache_rwconsist2_1k-t PROPERTIES COMPILE_FLAGS "-DTEST_PAGE_SIZE=1024") diff --git a/storage/maria/unittest/Makefile.am b/storage/maria/unittest/Makefile.am new file mode 100644 index 00000000000..28c75a06b29 --- /dev/null +++ b/storage/maria/unittest/Makefile.am @@ -0,0 +1,115 @@ +# Copyright (C) 2006-2008 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 + +AM_CPPFLAGS = @ZLIB_INCLUDES@ -I$(top_builddir)/include \ + -I$(top_srcdir)/include -I$(top_srcdir)/unittest/mytap +INCLUDES = @ZLIB_INCLUDES@ -I$(top_builddir)/include \ + -I$(top_srcdir)/include -I$(top_srcdir)/unittest/mytap +EXTRA_DIST= ma_test_all-t CMakeLists.txt \ + ma_test_recovery.pl ma_test_recovery.expected +# Only reason to link with libmyisam.a here is that it's where some fulltext +# pieces are (but soon we'll remove fulltext dependencies from Maria). +LDADD= $(top_builddir)/unittest/mytap/libmytap.a \ + $(top_builddir)/storage/maria/libmaria.a \ + $(top_builddir)/storage/myisam/libmyisam.a \ + $(top_builddir)/mysys/libmysys.a \ + $(top_builddir)/dbug/libdbug.a \ + $(top_builddir)/strings/libmystrings.a @ZLIB_LIBS@ +noinst_PROGRAMS = ma_control_file-t trnman-t \ + ma_pagecache_single_1k-t ma_pagecache_single_8k-t \ + ma_pagecache_single_64k-t \ + ma_pagecache_consist_1k-t \ + ma_pagecache_consist_64k-t \ + ma_pagecache_consist_1kHC-t \ + ma_pagecache_consist_64kHC-t \ + ma_pagecache_consist_1kRD-t \ + ma_pagecache_consist_64kRD-t \ + ma_pagecache_consist_1kWR-t \ + ma_pagecache_consist_64kWR-t \ + ma_pagecache_rwconsist_1k-t \ + ma_pagecache_rwconsist2_1k-t \ + ma_test_loghandler-t \ + ma_test_loghandler_multigroup-t \ + ma_test_loghandler_multithread-t \ + ma_test_loghandler_multiflush-t \ + ma_test_loghandler_pagecache-t \ + ma_test_loghandler_long-t \ + ma_test_loghandler_noflush-t \ + ma_test_loghandler_first_lsn-t \ + ma_test_loghandler_max_lsn-t \ + ma_test_loghandler_purge-t \ + ma_test_loghandler_readonly-t\ + ma_test_loghandler_nologs-t + +ma_test_loghandler_t_SOURCES = ma_test_loghandler-t.c ma_maria_log_cleanup.c ma_loghandler_examples.c +ma_test_loghandler_multigroup_t_SOURCES = ma_test_loghandler_multigroup-t.c ma_maria_log_cleanup.c ma_loghandler_examples.c sequence_storage.c sequence_storage.h +ma_test_loghandler_multithread_t_SOURCES = ma_test_loghandler_multithread-t.c ma_maria_log_cleanup.c ma_loghandler_examples.c +ma_test_loghandler_multiflush_t_SOURCES = ma_test_loghandler_multithread-t.c ma_maria_log_cleanup.c ma_loghandler_examples.c +ma_test_loghandler_multiflush_t_CPPFLAGS = -DMULTIFLUSH_TEST +ma_test_loghandler_pagecache_t_SOURCES = ma_test_loghandler_pagecache-t.c ma_maria_log_cleanup.c ma_loghandler_examples.c +ma_test_loghandler_long_t_SOURCES = ma_test_loghandler-t.c ma_maria_log_cleanup.c ma_loghandler_examples.c +ma_test_loghandler_long_t_CPPFLAGS = -DLONG_LOG_TEST +ma_test_loghandler_noflush_t_SOURCES = ma_test_loghandler_noflush-t.c ma_maria_log_cleanup.c ma_loghandler_examples.c +ma_test_loghandler_first_lsn_t_SOURCES = ma_test_loghandler_first_lsn-t.c ma_maria_log_cleanup.c ma_loghandler_examples.c +ma_test_loghandler_max_lsn_t_SOURCES = ma_test_loghandler_max_lsn-t.c ma_maria_log_cleanup.c ma_loghandler_examples.c +ma_test_loghandler_purge_t_SOURCES = ma_test_loghandler_purge-t.c ma_maria_log_cleanup.c ma_loghandler_examples.c +ma_test_loghandler_readonly_t_SOURCES = ma_test_loghandler_multigroup-t.c ma_maria_log_cleanup.c ma_loghandler_examples.c sequence_storage.c sequence_storage.h +ma_test_loghandler_readonly_t_CPPFLAGS = -DREADONLY_TEST +ma_test_loghandler_nologs_t_SOURCES = ma_test_loghandler_nologs-t.c ma_maria_log_cleanup.c ma_loghandler_examples.c + +ma_pagecache_single_src = ma_pagecache_single.c test_file.c test_file.h +ma_pagecache_consist_src = ma_pagecache_consist.c test_file.c test_file.h +ma_pagecache_common_cppflags = -DEXTRA_DEBUG -DPAGECACHE_DEBUG -DMAIN + +ma_pagecache_single_1k_t_SOURCES = $(ma_pagecache_single_src) +ma_pagecache_single_8k_t_SOURCES = $(ma_pagecache_single_src) +ma_pagecache_single_64k_t_SOURCES = $(ma_pagecache_single_src) +ma_pagecache_single_1k_t_CPPFLAGS = $(ma_pagecache_common_cppflags) -DTEST_PAGE_SIZE=1024 +ma_pagecache_single_8k_t_CPPFLAGS = $(ma_pagecache_common_cppflags) -DTEST_PAGE_SIZE=8192 +ma_pagecache_single_64k_t_CPPFLAGS = $(ma_pagecache_common_cppflags) -DTEST_PAGE_SIZE=65536 -DBIG + +ma_pagecache_consist_1k_t_SOURCES = $(ma_pagecache_consist_src) +ma_pagecache_consist_1k_t_CPPFLAGS = $(ma_pagecache_common_cppflags) -DTEST_PAGE_SIZE=1024 +ma_pagecache_consist_64k_t_SOURCES = $(ma_pagecache_consist_src) +ma_pagecache_consist_64k_t_CPPFLAGS = $(ma_pagecache_common_cppflags) -DTEST_PAGE_SIZE=65536 + +ma_pagecache_consist_1kHC_t_SOURCES = $(ma_pagecache_consist_src) +ma_pagecache_consist_1kHC_t_CPPFLAGS = $(ma_pagecache_common_cppflags) -DTEST_PAGE_SIZE=1024 -DTEST_HIGH_CONCURENCY +ma_pagecache_consist_64kHC_t_SOURCES = $(ma_pagecache_consist_src) +ma_pagecache_consist_64kHC_t_CPPFLAGS = $(ma_pagecache_common_cppflags) -DTEST_PAGE_SIZE=65536 -DTEST_HIGH_CONCURENCY + +ma_pagecache_consist_1kRD_t_SOURCES = $(ma_pagecache_consist_src) +ma_pagecache_consist_1kRD_t_CPPFLAGS = $(ma_pagecache_common_cppflags) -DTEST_PAGE_SIZE=1024 -DTEST_READERS +ma_pagecache_consist_64kRD_t_SOURCES = $(ma_pagecache_consist_src) +ma_pagecache_consist_64kRD_t_CPPFLAGS = $(ma_pagecache_common_cppflags) -DTEST_PAGE_SIZE=65536 -DTEST_READERS + +ma_pagecache_consist_1kWR_t_SOURCES = $(ma_pagecache_consist_src) +ma_pagecache_consist_1kWR_t_CPPFLAGS = $(ma_pagecache_common_cppflags) -DTEST_PAGE_SIZE=1024 -DTEST_WRITERS +ma_pagecache_consist_64kWR_t_SOURCES = $(ma_pagecache_consist_src) +ma_pagecache_consist_64kWR_t_CPPFLAGS = $(ma_pagecache_common_cppflags) -DTEST_PAGE_SIZE=65536 -DTEST_WRITERS + +ma_pagecache_rwconsist_1k_t_SOURCES = ma_pagecache_rwconsist.c +ma_pagecache_rwconsist_1k_t_CPPFLAGS = -DTEST_PAGE_SIZE=1024 +ma_pagecache_rwconsist2_1k_t_SOURCES = ma_pagecache_rwconsist2.c +ma_pagecache_rwconsist2_1k_t_CPPFLAGS = -DTEST_PAGE_SIZE=1024 + +# the generic lock manager may not be used in the end and lockman1-t crashes, +# and lockman2-t takes at least quarter an hour, +# so we don't build lockman-t and lockman1-t and lockman2-t +CLEANFILES = maria_log_control page_cache_test_file_1 \ + maria_log.???????? + +# Don't update the files from bitkeeper +%::SCCS/s.% diff --git a/storage/maria/unittest/lockman-t.c b/storage/maria/unittest/lockman-t.c new file mode 100644 index 00000000000..9b54a3d8ff9 --- /dev/null +++ b/storage/maria/unittest/lockman-t.c @@ -0,0 +1,308 @@ +/* Copyright (C) 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 */ + +/* + lockman for row and table locks +*/ + +/* #define EXTRA_VERBOSE */ + +#include <tap.h> + +#include <my_global.h> +#include <my_sys.h> +#include <my_atomic.h> +#include <lf.h> +#include "../lockman.h" + +#define Nlos 100 +LOCK_OWNER loarray[Nlos]; +pthread_mutex_t mutexes[Nlos]; +pthread_cond_t conds[Nlos]; +LOCKMAN lockman; + +#ifndef EXTRA_VERBOSE +#define print_lockhash(X) /* no-op */ +#define DIAG(X) /* no-op */ +#else +#define DIAG(X) diag X +#endif + +LOCK_OWNER *loid2lo(uint16 loid) +{ + return loarray+loid-1; +} + +#define unlock_all(O) diag("lo" #O "> release all locks"); \ + lockman_release_locks(&lockman, loid2lo(O));print_lockhash(&lockman) +#define test_lock(O, R, L, S, RES) \ + ok(lockman_getlock(&lockman, loid2lo(O), R, L) == RES, \ + "lo" #O "> " S "lock resource " #R " with " #L "-lock"); \ + print_lockhash(&lockman) +#define lock_ok_a(O, R, L) \ + test_lock(O, R, L, "", GOT_THE_LOCK) +#define lock_ok_i(O, R, L) \ + test_lock(O, R, L, "", GOT_THE_LOCK_NEED_TO_LOCK_A_SUBRESOURCE) +#define lock_ok_l(O, R, L) \ + test_lock(O, R, L, "", GOT_THE_LOCK_NEED_TO_INSTANT_LOCK_A_SUBRESOURCE) +#define lock_conflict(O, R, L) \ + test_lock(O, R, L, "cannot ", DIDNT_GET_THE_LOCK); + +void test_lockman_simple() +{ + /* simple */ + lock_ok_a(1, 1, S); + lock_ok_i(2, 2, IS); + lock_ok_i(1, 2, IX); + /* lock escalation */ + lock_ok_a(1, 1, X); + lock_ok_i(2, 2, IX); + /* failures */ + lock_conflict(2, 1, X); + unlock_all(2); + lock_ok_a(1, 2, S); + lock_ok_a(1, 2, IS); + lock_ok_a(1, 2, LS); + lock_ok_i(1, 3, IX); + lock_ok_a(2, 3, LS); + lock_ok_i(1, 3, IX); + lock_ok_l(2, 3, IS); + unlock_all(1); + unlock_all(2); + + lock_ok_i(1, 1, IX); + lock_conflict(2, 1, S); + lock_ok_a(1, 1, LS); + unlock_all(1); + unlock_all(2); + + lock_ok_i(1, 1, IX); + lock_ok_a(2, 1, LS); + lock_ok_a(1, 1, LS); + lock_ok_i(1, 1, IX); + lock_ok_i(3, 1, IS); + unlock_all(1); + unlock_all(2); + unlock_all(3); + + lock_ok_i(1, 4, IS); + lock_ok_i(2, 4, IS); + lock_ok_i(3, 4, IS); + lock_ok_a(3, 4, LS); + lock_ok_i(4, 4, IS); + lock_conflict(4, 4, IX); + lock_conflict(2, 4, IX); + lock_ok_a(1, 4, LS); + unlock_all(1); + unlock_all(2); + unlock_all(3); + unlock_all(4); + + lock_ok_i(1, 1, IX); + lock_ok_i(2, 1, IX); + lock_conflict(1, 1, S); + lock_conflict(2, 1, X); + unlock_all(1); + unlock_all(2); +} + +int rt_num_threads; +int litmus; +int thread_number= 0, timeouts= 0; +void run_test(const char *test, pthread_handler handler, int n, int m) +{ + pthread_t *threads; + ulonglong now= my_getsystime(); + int i; + + thread_number= timeouts= 0; + litmus= 0; + + threads= (pthread_t *)my_malloc(sizeof(void *)*n, MYF(0)); + if (!threads) + { + diag("Out of memory"); + abort(); + } + + diag("Running %s with %d threads, %d iterations... ", test, n, m); + rt_num_threads= n; + for (i= 0; i < n ; i++) + if (pthread_create(threads+i, 0, handler, &m)) + { + diag("Could not create thread"); + abort(); + } + for (i= 0 ; i < n ; i++) + pthread_join(threads[i], 0); + now= my_getsystime()-now; + ok(litmus == 0, "Finished %s in %g secs (%d)", test, ((double)now)/1e7, litmus); + my_free((void*)threads, MYF(0)); +} + +pthread_mutex_t rt_mutex; +int Nrows= 100; +int Ntables= 10; +int table_lock_ratio= 10; +enum lockman_lock_type lock_array[6]= {S, X, LS, LX, IS, IX}; +char *lock2str[6]= {"S", "X", "LS", "LX", "IS", "IX"}; +char *res2str[4]= { + "DIDN'T GET THE LOCK", + "GOT THE LOCK", + "GOT THE LOCK NEED TO LOCK A SUBRESOURCE", + "GOT THE LOCK NEED TO INSTANT LOCK A SUBRESOURCE"}; +pthread_handler_t test_lockman(void *arg) +{ + int m= (*(int *)arg); + uint x, loid, row, table, res, locklevel, timeout= 0; + LOCK_OWNER *lo; + + pthread_mutex_lock(&rt_mutex); + loid= ++thread_number; + pthread_mutex_unlock(&rt_mutex); + lo= loid2lo(loid); + + for (x= ((int)(intptr)(&m)); m > 0; m--) + { + x= (x*3628273133 + 1500450271) % 9576890767; /* three prime numbers */ + row= x % Nrows + Ntables; + table= row % Ntables; + locklevel= (x/Nrows) & 3; + if (table_lock_ratio && (x/Nrows/4) % table_lock_ratio == 0) + { /* table lock */ + res= lockman_getlock(&lockman, lo, table, lock_array[locklevel]); + DIAG(("loid %2d, table %d, lock %s, res %s", loid, table, + lock2str[locklevel], res2str[res])); + if (res == DIDNT_GET_THE_LOCK) + { + lockman_release_locks(&lockman, lo); + DIAG(("loid %2d, release all locks", loid)); + timeout++; + continue; + } + DBUG_ASSERT(res == GOT_THE_LOCK); + } + else + { /* row lock */ + locklevel&= 1; + res= lockman_getlock(&lockman, lo, table, lock_array[locklevel + 4]); + DIAG(("loid %2d, row %d, lock %s, res %s", loid, row, + lock2str[locklevel+4], res2str[res])); + switch (res) + { + case DIDNT_GET_THE_LOCK: + lockman_release_locks(&lockman, lo); + DIAG(("loid %2d, release all locks", loid)); + timeout++; + continue; + case GOT_THE_LOCK: + continue; + case GOT_THE_LOCK_NEED_TO_INSTANT_LOCK_A_SUBRESOURCE: + /* not implemented, so take a regular lock */ + case GOT_THE_LOCK_NEED_TO_LOCK_A_SUBRESOURCE: + res= lockman_getlock(&lockman, lo, row, lock_array[locklevel]); + DIAG(("loid %2d, ROW %d, lock %s, res %s", loid, row, + lock2str[locklevel], res2str[res])); + if (res == DIDNT_GET_THE_LOCK) + { + lockman_release_locks(&lockman, lo); + DIAG(("loid %2d, release all locks", loid)); + timeout++; + continue; + } + DBUG_ASSERT(res == GOT_THE_LOCK); + continue; + default: + DBUG_ASSERT(0); + } + } + } + + lockman_release_locks(&lockman, lo); + + pthread_mutex_lock(&rt_mutex); + rt_num_threads--; + timeouts+= timeout; + if (!rt_num_threads) + diag("number of timeouts: %d", timeouts); + pthread_mutex_unlock(&rt_mutex); + + return 0; +} + +int main() +{ + int i; + + my_init(); + pthread_mutex_init(&rt_mutex, 0); + + plan(35); + + if (my_atomic_initialize()) + return exit_status(); + + + lockman_init(&lockman, &loid2lo, 50); + + for (i= 0; i < Nlos; i++) + { + loarray[i].pins= lf_alloc_get_pins(&lockman.alloc); + loarray[i].all_locks= 0; + loarray[i].waiting_for= 0; + pthread_mutex_init(&mutexes[i], MY_MUTEX_INIT_FAST); + pthread_cond_init (&conds[i], 0); + loarray[i].mutex= &mutexes[i]; + loarray[i].cond= &conds[i]; + loarray[i].loid= i+1; + } + + test_lockman_simple(); + +#define CYCLES 10000 +#define THREADS Nlos /* don't change this line */ + + /* mixed load, stress-test with random locks */ + Nrows= 100; + Ntables= 10; + table_lock_ratio= 10; + run_test("\"random lock\" stress test", test_lockman, THREADS, CYCLES); + + /* "real-life" simulation - many rows, no table locks */ + Nrows= 1000000; + Ntables= 10; + table_lock_ratio= 0; + run_test("\"real-life\" simulation test", test_lockman, THREADS, CYCLES*10); + + for (i= 0; i < Nlos; i++) + { + lockman_release_locks(&lockman, &loarray[i]); + pthread_mutex_destroy(loarray[i].mutex); + pthread_cond_destroy(loarray[i].cond); + lf_pinbox_put_pins(loarray[i].pins); + } + + { + ulonglong now= my_getsystime(); + lockman_destroy(&lockman); + now= my_getsystime()-now; + diag("lockman_destroy: %g secs", ((double)now)/1e7); + } + + pthread_mutex_destroy(&rt_mutex); + my_end(0); + return exit_status(); +} + diff --git a/storage/maria/unittest/lockman1-t.c b/storage/maria/unittest/lockman1-t.c new file mode 100644 index 00000000000..ca959c6e6e3 --- /dev/null +++ b/storage/maria/unittest/lockman1-t.c @@ -0,0 +1,334 @@ +/* Copyright (C) 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 */ + +/* + lockman for row locks, tablockman for table locks +*/ + +/* #define EXTRA_VERBOSE */ + +#include <tap.h> + +#include <my_global.h> +#include <my_sys.h> +#include <my_atomic.h> +#include <lf.h> +#include "../lockman.h" +#include "../tablockman.h" + +#define Nlos 100 +#define Ntbls 10 +LOCK_OWNER loarray[Nlos]; +TABLE_LOCK_OWNER loarray1[Nlos]; +pthread_mutex_t mutexes[Nlos]; +pthread_cond_t conds[Nlos]; +LOCKED_TABLE ltarray[Ntbls]; +LOCKMAN lockman; +TABLOCKMAN tablockman; + +#ifndef EXTRA_VERBOSE +#define print_lo1(X) /* no-op */ +#define DIAG(X) /* no-op */ +#else +#define DIAG(X) diag X +#endif + +LOCK_OWNER *loid2lo(uint16 loid) +{ + return loarray+loid-1; +} +TABLE_LOCK_OWNER *loid2lo1(uint16 loid) +{ + return loarray1+loid-1; +} + +#define unlock_all(O) diag("lo" #O "> release all locks"); \ + tablockman_release_locks(&tablockman, loid2lo1(O)); +#define test_lock(O, R, L, S, RES) \ + ok(tablockman_getlock(&tablockman, loid2lo1(O), <array[R], L) == RES, \ + "lo" #O "> " S "lock resource " #R " with " #L "-lock"); \ + print_lo1(loid2lo1(O)); +#define lock_ok_a(O, R, L) \ + test_lock(O, R, L, "", GOT_THE_LOCK) +#define lock_ok_i(O, R, L) \ + test_lock(O, R, L, "", GOT_THE_LOCK_NEED_TO_LOCK_A_SUBRESOURCE) +#define lock_ok_l(O, R, L) \ + test_lock(O, R, L, "", GOT_THE_LOCK_NEED_TO_INSTANT_LOCK_A_SUBRESOURCE) +#define lock_conflict(O, R, L) \ + test_lock(O, R, L, "cannot ", LOCK_TIMEOUT); + +void test_tablockman_simple() +{ + /* simple */ + lock_ok_a(1, 1, S); + lock_ok_i(2, 2, IS); + lock_ok_i(1, 2, IX); + /* lock escalation */ + lock_ok_a(1, 1, X); + lock_ok_i(2, 2, IX); + /* failures */ + lock_conflict(2, 1, X); + unlock_all(2); + lock_ok_a(1, 2, S); + lock_ok_a(1, 2, IS); + lock_ok_a(1, 2, LS); + lock_ok_i(1, 3, IX); + lock_ok_a(2, 3, LS); + lock_ok_i(1, 3, IX); + lock_ok_l(2, 3, IS); + unlock_all(1); + unlock_all(2); + + lock_ok_i(1, 1, IX); + lock_conflict(2, 1, S); + lock_ok_a(1, 1, LS); + unlock_all(1); + unlock_all(2); + + lock_ok_i(1, 1, IX); + lock_ok_a(2, 1, LS); + lock_ok_a(1, 1, LS); + lock_ok_i(1, 1, IX); + lock_ok_i(3, 1, IS); + unlock_all(1); + unlock_all(2); + unlock_all(3); + + lock_ok_i(1, 4, IS); + lock_ok_i(2, 4, IS); + lock_ok_i(3, 4, IS); + lock_ok_a(3, 4, LS); + lock_ok_i(4, 4, IS); + lock_conflict(4, 4, IX); + lock_conflict(2, 4, IX); + lock_ok_a(1, 4, LS); + unlock_all(1); + unlock_all(2); + unlock_all(3); + unlock_all(4); + + lock_ok_i(1, 1, IX); + lock_ok_i(2, 1, IX); + lock_conflict(1, 1, S); + lock_conflict(2, 1, X); + unlock_all(1); + unlock_all(2); +} + +int rt_num_threads; +int litmus; +int thread_number= 0, timeouts= 0; +void run_test(const char *test, pthread_handler handler, int n, int m) +{ + pthread_t *threads; + ulonglong now= my_getsystime(); + int i; + + thread_number= timeouts= 0; + litmus= 0; + + threads= (pthread_t *)my_malloc(sizeof(void *)*n, MYF(0)); + if (!threads) + { + diag("Out of memory"); + abort(); + } + + diag("Running %s with %d threads, %d iterations... ", test, n, m); + rt_num_threads= n; + for (i= 0; i < n ; i++) + if (pthread_create(threads+i, 0, handler, &m)) + { + diag("Could not create thread"); + abort(); + } + for (i= 0 ; i < n ; i++) + pthread_join(threads[i], 0); + now= my_getsystime()-now; + ok(litmus == 0, "Finished %s in %g secs (%d)", test, ((double)now)/1e7, litmus); + my_free((void*)threads, MYF(0)); +} + +pthread_mutex_t rt_mutex; +int Nrows= 100; +int Ntables= 10; +int table_lock_ratio= 10; +enum lockman_lock_type lock_array[6]= {S, X, LS, LX, IS, IX}; +char *lock2str[6]= {"S", "X", "LS", "LX", "IS", "IX"}; +char *res2str[]= { + "DIDN'T GET THE LOCK", + "OUT OF MEMORY", + "DEADLOCK", + "LOCK TIMEOUT", + "GOT THE LOCK", + "GOT THE LOCK NEED TO LOCK A SUBRESOURCE", + "GOT THE LOCK NEED TO INSTANT LOCK A SUBRESOURCE"}; +pthread_handler_t test_lockman(void *arg) +{ + int m= (*(int *)arg); + uint x, loid, row, table, res, locklevel, timeout= 0; + LOCK_OWNER *lo; TABLE_LOCK_OWNER *lo1; DBUG_ASSERT(Ntables <= Ntbls); + + pthread_mutex_lock(&rt_mutex); + loid= ++thread_number; + pthread_mutex_unlock(&rt_mutex); + lo= loid2lo(loid); lo1= loid2lo1(loid); + + for (x= ((int)(intptr)(&m)); m > 0; m--) + { + x= (x*3628273133 + 1500450271) % 9576890767; /* three prime numbers */ + row= x % Nrows + Ntables; + table= row % Ntables; + locklevel= (x/Nrows) & 3; + if (table_lock_ratio && (x/Nrows/4) % table_lock_ratio == 0) + { /* table lock */ + res= tablockman_getlock(&tablockman, lo1, ltarray+table, lock_array[locklevel]); + DIAG(("loid %2d, table %d, lock %s, res %s", loid, table, + lock2str[locklevel], res2str[res])); + if (res < GOT_THE_LOCK) + { + lockman_release_locks(&lockman, lo); tablockman_release_locks(&tablockman, lo1); + DIAG(("loid %2d, release all locks", loid)); + timeout++; + continue; + } + DBUG_ASSERT(res == GOT_THE_LOCK); + } + else + { /* row lock */ + locklevel&= 1; + res= tablockman_getlock(&tablockman, lo1, ltarray+table, lock_array[locklevel + 4]); + DIAG(("loid %2d, row %d, lock %s, res %s", loid, row, + lock2str[locklevel+4], res2str[res])); + switch (res) + { + case GOT_THE_LOCK: + continue; + case GOT_THE_LOCK_NEED_TO_INSTANT_LOCK_A_SUBRESOURCE: + /* not implemented, so take a regular lock */ + case GOT_THE_LOCK_NEED_TO_LOCK_A_SUBRESOURCE: + res= lockman_getlock(&lockman, lo, row, lock_array[locklevel]); + DIAG(("loid %2d, ROW %d, lock %s, res %s", loid, row, + lock2str[locklevel], res2str[res])); + if (res == DIDNT_GET_THE_LOCK) + { + lockman_release_locks(&lockman, lo); + tablockman_release_locks(&tablockman, lo1); + DIAG(("loid %2d, release all locks", loid)); + timeout++; + continue; + } + DBUG_ASSERT(res == GOT_THE_LOCK); + continue; + default: + lockman_release_locks(&lockman, lo); tablockman_release_locks(&tablockman, lo1); + DIAG(("loid %2d, release all locks", loid)); + timeout++; + continue; + } + } + } + + lockman_release_locks(&lockman, lo); + tablockman_release_locks(&tablockman, lo1); + + pthread_mutex_lock(&rt_mutex); + rt_num_threads--; + timeouts+= timeout; + if (!rt_num_threads) + diag("number of timeouts: %d", timeouts); + pthread_mutex_unlock(&rt_mutex); + + return 0; +} + +int main() +{ + int i; + + my_init(); + pthread_mutex_init(&rt_mutex, 0); + + plan(35); + + if (my_atomic_initialize()) + return exit_status(); + + + lockman_init(&lockman, &loid2lo, 50); + tablockman_init(&tablockman, &loid2lo1, 50); + + for (i= 0; i < Nlos; i++) + { + pthread_mutex_init(&mutexes[i], MY_MUTEX_INIT_FAST); + pthread_cond_init (&conds[i], 0); + + loarray[i].pins= lf_alloc_get_pins(&lockman.alloc); + loarray[i].all_locks= 0; + loarray[i].waiting_for= 0; + loarray[i].mutex= &mutexes[i]; + loarray[i].cond= &conds[i]; + loarray[i].loid= i+1; + + loarray1[i].active_locks= 0; + loarray1[i].waiting_lock= 0; + loarray1[i].waiting_for= 0; + loarray1[i].mutex= &mutexes[i]; + loarray1[i].cond= &conds[i]; + loarray1[i].loid= i+1; + } + + for (i= 0; i < Ntbls; i++) + { + tablockman_init_locked_table(ltarray+i, Nlos); + } + + test_tablockman_simple(); + +#define CYCLES 10000 +#define THREADS Nlos /* don't change this line */ + + /* mixed load, stress-test with random locks */ + Nrows= 100; + Ntables= 10; + table_lock_ratio= 10; + run_test("\"random lock\" stress test", test_lockman, THREADS, CYCLES); + + /* "real-life" simulation - many rows, no table locks */ + Nrows= 1000000; + Ntables= 10; + table_lock_ratio= 0; + run_test("\"real-life\" simulation test", test_lockman, THREADS, CYCLES*10); + + for (i= 0; i < Nlos; i++) + { + lockman_release_locks(&lockman, &loarray[i]); + pthread_mutex_destroy(loarray[i].mutex); + pthread_cond_destroy(loarray[i].cond); + lf_pinbox_put_pins(loarray[i].pins); + } + + { + ulonglong now= my_getsystime(); + lockman_destroy(&lockman); + now= my_getsystime()-now; + diag("lockman_destroy: %g secs", ((double)now)/1e7); + } + + pthread_mutex_destroy(&rt_mutex); + my_end(0); + return exit_status(); +} + diff --git a/storage/maria/unittest/lockman2-t.c b/storage/maria/unittest/lockman2-t.c new file mode 100644 index 00000000000..c1d40159500 --- /dev/null +++ b/storage/maria/unittest/lockman2-t.c @@ -0,0 +1,361 @@ +/* Copyright (C) 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 */ + +/* + tablockman for row and table locks +*/ + +/* #define EXTRA_VERBOSE */ + +#include <tap.h> + +#include <my_global.h> +#include <my_sys.h> +#include <my_atomic.h> +#include <lf.h> +#include "../tablockman.h" + +#define Nlos 100 +#define Ntbls 110 +TABLE_LOCK_OWNER loarray1[Nlos]; +pthread_mutex_t mutexes[Nlos]; +pthread_cond_t conds[Nlos]; +LOCKED_TABLE ltarray[Ntbls]; +TABLOCKMAN tablockman; + +#ifndef EXTRA_VERBOSE +#define print_lo1(X) /* no-op */ +#define DIAG(X) /* no-op */ +#else +#define DIAG(X) diag X +#endif + +TABLE_LOCK_OWNER *loid2lo1(uint16 loid) +{ + return loarray1+loid-1; +} + +#define unlock_all(O) diag("lo" #O "> release all locks"); \ + tablockman_release_locks(&tablockman, loid2lo1(O)); +#define test_lock(O, R, L, S, RES) \ + ok(tablockman_getlock(&tablockman, loid2lo1(O), <array[R], L) == RES, \ + "lo" #O "> " S "lock resource " #R " with " #L "-lock"); \ + print_lo1(loid2lo1(O)); +#define lock_ok_a(O, R, L) \ + test_lock(O, R, L, "", GOT_THE_LOCK) +#define lock_ok_i(O, R, L) \ + test_lock(O, R, L, "", GOT_THE_LOCK_NEED_TO_LOCK_A_SUBRESOURCE) +#define lock_ok_l(O, R, L) \ + test_lock(O, R, L, "", GOT_THE_LOCK_NEED_TO_INSTANT_LOCK_A_SUBRESOURCE) +#define lock_conflict(O, R, L) \ + test_lock(O, R, L, "cannot ", LOCK_TIMEOUT); + +void test_tablockman_simple() +{ + /* simple */ + lock_ok_a(1, 1, S); + lock_ok_i(2, 2, IS); + lock_ok_i(1, 2, IX); + /* lock escalation */ + lock_ok_a(1, 1, X); + lock_ok_i(2, 2, IX); + /* failures */ + lock_conflict(2, 1, X); + unlock_all(2); + lock_ok_a(1, 2, S); + lock_ok_a(1, 2, IS); + lock_ok_a(1, 2, LS); + lock_ok_i(1, 3, IX); + lock_ok_a(2, 3, LS); + lock_ok_i(1, 3, IX); + lock_ok_l(2, 3, IS); + unlock_all(1); + unlock_all(2); + + lock_ok_i(1, 1, IX); + lock_conflict(2, 1, S); + lock_ok_a(1, 1, LS); + unlock_all(1); + unlock_all(2); + + lock_ok_i(1, 1, IX); + lock_ok_a(2, 1, LS); + lock_ok_a(1, 1, LS); + lock_ok_i(1, 1, IX); + lock_ok_i(3, 1, IS); + unlock_all(1); + unlock_all(2); + unlock_all(3); + + lock_ok_i(1, 4, IS); + lock_ok_i(2, 4, IS); + lock_ok_i(3, 4, IS); + lock_ok_a(3, 4, LS); + lock_ok_i(4, 4, IS); + lock_conflict(4, 4, IX); + lock_conflict(2, 4, IX); + lock_ok_a(1, 4, LS); + unlock_all(1); + unlock_all(2); + unlock_all(3); + unlock_all(4); + + lock_ok_i(1, 1, IX); + lock_ok_i(2, 1, IX); + lock_conflict(1, 1, S); + lock_conflict(2, 1, X); + unlock_all(1); + unlock_all(2); + + lock_ok_i(1, 1, IS); + lock_conflict(2, 1, X); + lock_conflict(3, 1, IS); + unlock_all(1); + unlock_all(2); + unlock_all(3); + + lock_ok_a(1, 1, S); + lock_conflict(2, 1, IX); + lock_conflict(3, 1, IS); + unlock_all(1); + unlock_all(2); + unlock_all(3); +} + +int rt_num_threads; +int litmus; +int thread_number= 0, timeouts= 0; +void run_test(const char *test, pthread_handler handler, int n, int m) +{ + pthread_t *threads; + ulonglong now= my_getsystime(); + int i; + + thread_number= timeouts= 0; + litmus= 0; + + threads= (pthread_t *)my_malloc(sizeof(void *)*n, MYF(0)); + if (!threads) + { + diag("Out of memory"); + abort(); + } + + diag("Running %s with %d threads, %d iterations... ", test, n, m); + rt_num_threads= n; + for (i= 0; i < n ; i++) + if (pthread_create(threads+i, 0, handler, &m)) + { + diag("Could not create thread"); + abort(); + } + for (i= 0 ; i < n ; i++) + pthread_join(threads[i], 0); + now= my_getsystime()-now; + ok(litmus == 0, "Finished %s in %g secs (%d)", test, ((double)now)/1e7, litmus); + my_free((void*)threads, MYF(0)); +} + +static void reinit_tlo(TABLOCKMAN *lm, TABLE_LOCK_OWNER *lo) +{ +#ifdef NOT_USED_YET + TABLE_LOCK_OWNER backup= *lo; +#endif + + tablockman_release_locks(lm, lo); +#ifdef NOT_USED_YET + pthread_mutex_destroy(lo->mutex); + pthread_cond_destroy(lo->cond); + bzero(lo, sizeof(*lo)); + + lo->mutex= backup.mutex; + lo->cond= backup.cond; + lo->loid= backup.loid; + pthread_mutex_init(lo->mutex, MY_MUTEX_INIT_FAST); + pthread_cond_init(lo->cond, 0); +#endif +} + +pthread_mutex_t rt_mutex; +int Nrows= 100; +int Ntables= 10; +int table_lock_ratio= 10; +enum lockman_lock_type lock_array[6]= {S, X, LS, LX, IS, IX}; +const char *lock2str[6]= {"S", "X", "LS", "LX", "IS", "IX"}; +const char *res2str[]= { + 0, + "OUT OF MEMORY", + "DEADLOCK", + "LOCK TIMEOUT", + "GOT THE LOCK", + "GOT THE LOCK NEED TO LOCK A SUBRESOURCE", + "GOT THE LOCK NEED TO INSTANT LOCK A SUBRESOURCE"}; + +pthread_handler_t test_lockman(void *arg) +{ + int m= (*(int *)arg); + uint x, loid, row, table, res, locklevel, timeout= 0; + TABLE_LOCK_OWNER *lo1; + DBUG_ASSERT(Ntables <= Ntbls); + DBUG_ASSERT(Nrows + Ntables <= Ntbls); + + pthread_mutex_lock(&rt_mutex); + loid= ++thread_number; + pthread_mutex_unlock(&rt_mutex); + lo1= loid2lo1(loid); + + for (x= ((int)(intptr)(&m)); m > 0; m--) + { + /* three prime numbers */ + x= (uint) ((x*LL(3628273133) + LL(1500450271)) % LL(9576890767)); + row= x % Nrows + Ntables; + table= row % Ntables; + locklevel= (x/Nrows) & 3; + if (table_lock_ratio && (x/Nrows/4) % table_lock_ratio == 0) + { + /* table lock */ + res= tablockman_getlock(&tablockman, lo1, ltarray+table, + lock_array[locklevel]); + DIAG(("loid %2d, table %d, lock %s, res %s", loid, table, + lock2str[locklevel], res2str[res])); + if (res < GOT_THE_LOCK) + { + reinit_tlo(&tablockman, lo1); + DIAG(("loid %2d, release all locks", loid)); + timeout++; + continue; + } + DBUG_ASSERT(res == GOT_THE_LOCK); + } + else + { /* row lock */ + locklevel&= 1; + res= tablockman_getlock(&tablockman, lo1, ltarray+table, lock_array[locklevel + 4]); + DIAG(("loid %2d, row %d, lock %s, res %s", loid, row, + lock2str[locklevel+4], res2str[res])); + switch (res) + { + case GOT_THE_LOCK: + continue; + case GOT_THE_LOCK_NEED_TO_INSTANT_LOCK_A_SUBRESOURCE: + /* not implemented, so take a regular lock */ + case GOT_THE_LOCK_NEED_TO_LOCK_A_SUBRESOURCE: + res= tablockman_getlock(&tablockman, lo1, ltarray+row, lock_array[locklevel]); + DIAG(("loid %2d, ROW %d, lock %s, res %s", loid, row, + lock2str[locklevel], res2str[res])); + if (res < GOT_THE_LOCK) + { + reinit_tlo(&tablockman, lo1); + DIAG(("loid %2d, release all locks", loid)); + timeout++; + continue; + } + DBUG_ASSERT(res == GOT_THE_LOCK); + continue; + default: + reinit_tlo(&tablockman, lo1); + DIAG(("loid %2d, release all locks", loid)); + timeout++; + continue; + } + } + } + + reinit_tlo(&tablockman, lo1); + + pthread_mutex_lock(&rt_mutex); + rt_num_threads--; + timeouts+= timeout; + if (!rt_num_threads) + diag("number of timeouts: %d", timeouts); + pthread_mutex_unlock(&rt_mutex); + + return 0; +} + +int main(int argc __attribute__((unused)), char **argv) +{ + int i; + MY_INIT(argv[0]); + + my_init(); + pthread_mutex_init(&rt_mutex, 0); + + plan(40); + + if (my_atomic_initialize()) + return exit_status(); + + + tablockman_init(&tablockman, &loid2lo1, 50); + + for (i= 0; i < Nlos; i++) + { + pthread_mutex_init(&mutexes[i], MY_MUTEX_INIT_FAST); + pthread_cond_init (&conds[i], 0); + + loarray1[i].active_locks= 0; + loarray1[i].waiting_lock= 0; + loarray1[i].waiting_for= 0; + loarray1[i].mutex= &mutexes[i]; + loarray1[i].cond= &conds[i]; + loarray1[i].loid= i+1; + } + + for (i= 0; i < Ntbls; i++) + { + tablockman_init_locked_table(ltarray+i, Nlos); + } + + test_tablockman_simple(); + +#define CYCLES 10000 +#define THREADS Nlos /* don't change this line */ + + /* mixed load, stress-test with random locks */ + Nrows= 100; + Ntables= 10; + table_lock_ratio= 10; + run_test("\"random lock\" stress test", test_lockman, THREADS, CYCLES); +#if 0 + /* "real-life" simulation - many rows, no table locks */ + Nrows= 1000000; + Ntables= 10; + table_lock_ratio= 0; + run_test("\"real-life\" simulation test", test_lockman, THREADS, CYCLES*10); +#endif + for (i= 0; i < Nlos; i++) + { + tablockman_release_locks(&tablockman, &loarray1[i]); + pthread_mutex_destroy(loarray1[i].mutex); + pthread_cond_destroy(loarray1[i].cond); + } + + { + ulonglong now= my_getsystime(); + for (i= 0; i < Ntbls; i++) + { + tablockman_destroy_locked_table(ltarray+i); + } + tablockman_destroy(&tablockman); + now= my_getsystime()-now; + diag("lockman_destroy: %g secs", ((double)now)/1e7); + } + + pthread_mutex_destroy(&rt_mutex); + my_end(0); + return exit_status(); +} + diff --git a/storage/maria/unittest/ma_control_file-t.c b/storage/maria/unittest/ma_control_file-t.c new file mode 100644 index 00000000000..6702e4deb2f --- /dev/null +++ b/storage/maria/unittest/ma_control_file-t.c @@ -0,0 +1,592 @@ +/* Copyright (C) 2006 MySQL AB & MySQL Finland AB & TCX DataKonsult 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 */ + +/* Unit test of the control file module of the Maria engine WL#3234 */ + +/* + Note that it is not possible to test the durability of the write (can't + pull the plug programmatically :) +*/ + +#include <my_global.h> +#include <my_sys.h> +#include <tap.h> + +#ifndef WITH_MARIA_STORAGE_ENGINE +/* + If Maria is not compiled in, normally we don't come to building this test. +*/ +#error "Maria engine is not compiled in, test cannot be built" +#endif + +#include "maria.h" +#include "../../../storage/maria/maria_def.h" +#include <my_getopt.h> + +#define EXTRACT_DEFINITIONS +#include "../ma_control_file.c" +#undef EXTRACT_DEFINITIONS + +char file_name[FN_REFLEN]; + +/* The values we'll set and expect the control file module to return */ +LSN expect_checkpoint_lsn; +uint32 expect_logno; +TrID expect_max_trid; +uint8 expect_recovery_failures; + +static int delete_file(myf my_flags); +/* + Those are test-specific wrappers around the module's API functions: after + calling the module's API functions they perform checks on the result. +*/ +static int close_file(void); /* wraps ma_control_file_end */ +/* wraps ma_control_file_open_or_create */ +static int open_file(void); +/* wraps ma_control_file_write_and_force */ +static int write_file(LSN checkpoint_lsn, uint32 logno, TrID trid, + uint8 rec_failures); + +/* Tests */ +static int test_one_log_and_recovery_failures(void); +static int test_five_logs_and_max_trid(void); +static int test_3_checkpoints_and_2_logs(void); +static int test_binary_content(void); +static int test_start_stop(void); +static int test_2_open_and_2_close(void); +static int test_bad_magic_string(void); +static int test_bad_checksum(void); +static int test_bad_hchecksum(void); +static int test_future_size(void); +static int test_bad_blocksize(void); +static int test_bad_size(void); + +/* Utility */ +static int verify_module_values_match_expected(void); +static int verify_module_values_are_impossible(void); +static void usage(void); +static void get_options(int argc, char *argv[]); + +/* + If "expr" is FALSE, this macro will make the function print a diagnostic + message and immediately return 1. + This is inspired from assert() but does not crash the binary (sometimes we + may want to see how other tests go even if one fails). + RET_ERR means "return error". +*/ + +#define RET_ERR_UNLESS(expr) \ + {if (!(expr)) {diag("line %d: failure: '%s'", __LINE__, #expr); assert(0);return 1;}} + + +/* Used to ignore error messages from ma_control_file_open() */ + +static int my_ignore_message(uint error __attribute__((unused)), + const char *str __attribute__((unused)), + myf MyFlags __attribute__((unused))) +{ + DBUG_ENTER("my_message_no_curses"); + DBUG_PRINT("enter",("message: %s",str)); + DBUG_RETURN(0); +} + +int (*default_error_handler_hook)(uint my_err, const char *str, + myf MyFlags) = 0; + + +/* like ma_control_file_open(), but without error messages */ + +static CONTROL_FILE_ERROR local_ma_control_file_open(void) +{ + CONTROL_FILE_ERROR error; + error_handler_hook= my_ignore_message; + error= ma_control_file_open(TRUE, TRUE); + error_handler_hook= default_error_handler_hook; + return error; +} + + + +int main(int argc,char *argv[]) +{ + MY_INIT(argv[0]); + my_init(); + + maria_data_root= (char *)"."; + default_error_handler_hook= error_handler_hook; + + plan(12); + + diag("Unit tests for control file"); + + get_options(argc,argv); + + diag("Deleting control file at startup, if there is an old one"); + RET_ERR_UNLESS(0 == delete_file(0)); /* if fails, can't continue */ + + diag("Tests of normal conditions"); + ok(0 == test_one_log_and_recovery_failures(), + "test of creating one log and recording recovery failures"); + ok(0 == test_five_logs_and_max_trid(), + "test of creating five logs and many transactions"); + ok(0 == test_3_checkpoints_and_2_logs(), + "test of creating three checkpoints and two logs"); + ok(0 == test_binary_content(), "test of the binary content of the file"); + ok(0 == test_start_stop(), "test of multiple starts and stops"); + diag("Tests of abnormal conditions"); + ok(0 == test_2_open_and_2_close(), + "test of two open and two close (strange call sequence)"); + ok(0 == test_bad_magic_string(), "test of bad magic string"); + ok(0 == test_bad_checksum(), "test of bad checksum"); + ok(0 == test_bad_hchecksum(), "test of bad hchecksum"); + ok(0 == test_future_size(), "test of ability to handlr future versions"); + ok(0 == test_bad_blocksize(), "test of bad blocksize"); + ok(0 == test_bad_size(), "test of too small/big file"); + + return exit_status(); +} + + +static int delete_file(myf my_flags) +{ + RET_ERR_UNLESS(fn_format(file_name, CONTROL_FILE_BASE_NAME, + maria_data_root, "", MYF(MY_WME)) != NullS); + /* + Maybe file does not exist, ignore error. + The error will however be printed on stderr. + */ + my_delete(file_name, my_flags); + expect_checkpoint_lsn= LSN_IMPOSSIBLE; + expect_logno= FILENO_IMPOSSIBLE; + expect_max_trid= expect_recovery_failures= 0; + + return 0; +} + +/* + Verifies that global values last_checkpoint_lsn, last_logno, + max_trid_in_control_file (belonging to the module) match what we expect. +*/ +static int verify_module_values_match_expected(void) +{ + RET_ERR_UNLESS(last_logno == expect_logno); + RET_ERR_UNLESS(last_checkpoint_lsn == expect_checkpoint_lsn); + RET_ERR_UNLESS(max_trid_in_control_file == expect_max_trid); + RET_ERR_UNLESS(recovery_failures == expect_recovery_failures); + return 0; +} + + +/* + Verifies that global values last_checkpoint_lsn and last_logno (belonging + to the module) are impossible (this is used when the file has been closed). +*/ +static int verify_module_values_are_impossible(void) +{ + RET_ERR_UNLESS(last_logno == FILENO_IMPOSSIBLE); + RET_ERR_UNLESS(last_checkpoint_lsn == LSN_IMPOSSIBLE); + RET_ERR_UNLESS(max_trid_in_control_file == 0); + return 0; +} + + +static int close_file(void) +{ + /* Simulate shutdown */ + ma_control_file_end(); + /* Verify amnesia */ + RET_ERR_UNLESS(verify_module_values_are_impossible() == 0); + return 0; +} + +static int open_file(void) +{ + RET_ERR_UNLESS(local_ma_control_file_open() == CONTROL_FILE_OK); + /* Check that the module reports expected information */ + RET_ERR_UNLESS(verify_module_values_match_expected() == 0); + return 0; +} + +static int write_file(LSN checkpoint_lsn, uint32 logno, TrID trid, + uint8 rec_failures) +{ + RET_ERR_UNLESS(ma_control_file_write_and_force(checkpoint_lsn, logno, trid, + rec_failures) + == 0); + /* Check that the module reports expected information */ + RET_ERR_UNLESS(verify_module_values_match_expected() == 0); + return 0; +} + +static int test_one_log_and_recovery_failures(void) +{ + RET_ERR_UNLESS(open_file() == CONTROL_FILE_OK); + expect_logno= 123; + RET_ERR_UNLESS(write_file(last_checkpoint_lsn, expect_logno, + max_trid_in_control_file, + recovery_failures) == 0); + expect_recovery_failures= 158; + RET_ERR_UNLESS(write_file(last_checkpoint_lsn, expect_logno, + max_trid_in_control_file, + expect_recovery_failures) == 0); + RET_ERR_UNLESS(close_file() == 0); + return 0; +} + +static int test_five_logs_and_max_trid(void) +{ + uint i; + + RET_ERR_UNLESS(open_file() == CONTROL_FILE_OK); + expect_logno= 100; + expect_max_trid= ULL(14111978111); + for (i= 0; i<5; i++) + { + expect_logno*= 3; + RET_ERR_UNLESS(write_file(last_checkpoint_lsn, expect_logno, + expect_max_trid, + recovery_failures) == 0); + } + RET_ERR_UNLESS(close_file() == 0); + return 0; +} + +static int test_3_checkpoints_and_2_logs(void) +{ + /* + Simulate one checkpoint, one log creation, two checkpoints, one + log creation. + */ + RET_ERR_UNLESS(open_file() == CONTROL_FILE_OK); + expect_checkpoint_lsn= MAKE_LSN(5, 10000); + RET_ERR_UNLESS(write_file(expect_checkpoint_lsn, expect_logno, + max_trid_in_control_file, + recovery_failures) == 0); + + expect_logno= 17; + RET_ERR_UNLESS(write_file(expect_checkpoint_lsn, expect_logno, + max_trid_in_control_file, + recovery_failures) == 0); + + expect_checkpoint_lsn= MAKE_LSN(17, 20000); + RET_ERR_UNLESS(write_file(expect_checkpoint_lsn, expect_logno, + max_trid_in_control_file, + recovery_failures) == 0); + + expect_checkpoint_lsn= MAKE_LSN(17, 45000); + RET_ERR_UNLESS(write_file(expect_checkpoint_lsn, expect_logno, + max_trid_in_control_file, + recovery_failures) == 0); + + expect_logno= 19; + RET_ERR_UNLESS(write_file(expect_checkpoint_lsn, expect_logno, + max_trid_in_control_file, + recovery_failures) == 0); + RET_ERR_UNLESS(close_file() == 0); + return 0; +} + +static int test_binary_content(void) +{ + uint i; + int fd; + + /* + TEST4: actually check by ourselves the content of the file. + Note that constants (offsets) are hard-coded here, precisely to prevent + someone from changing them in the control file module and breaking + backward-compatibility. + TODO: when we reach the format-freeze state, we may even just do a + comparison with a raw binary string, to not depend on any uint4korr + future change/breakage. + */ + + uchar buffer[45]; + RET_ERR_UNLESS((fd= my_open(file_name, + O_BINARY | O_RDWR, + MYF(MY_WME))) >= 0); + RET_ERR_UNLESS(my_read(fd, buffer, 45, MYF(MY_FNABP | MY_WME)) == 0); + RET_ERR_UNLESS(my_close(fd, MYF(MY_WME)) == 0); + RET_ERR_UNLESS(open_file() == CONTROL_FILE_OK); + i= uint3korr(buffer + 34 ); + RET_ERR_UNLESS(i == LSN_FILE_NO(last_checkpoint_lsn)); + i= uint4korr(buffer + 37); + RET_ERR_UNLESS(i == LSN_OFFSET(last_checkpoint_lsn)); + i= uint4korr(buffer + 41); + RET_ERR_UNLESS(i == last_logno); + RET_ERR_UNLESS(close_file() == 0); + return 0; +} + +static int test_start_stop(void) +{ + /* TEST5: Simulate start/nothing/stop/start/nothing/stop/start */ + + RET_ERR_UNLESS(open_file() == CONTROL_FILE_OK); + RET_ERR_UNLESS(close_file() == 0); + RET_ERR_UNLESS(open_file() == CONTROL_FILE_OK); + RET_ERR_UNLESS(close_file() == 0); + RET_ERR_UNLESS(open_file() == CONTROL_FILE_OK); + RET_ERR_UNLESS(close_file() == 0); + return 0; +} + +static int test_2_open_and_2_close(void) +{ + RET_ERR_UNLESS(open_file() == CONTROL_FILE_OK); + RET_ERR_UNLESS(open_file() == CONTROL_FILE_OK); + RET_ERR_UNLESS(close_file() == 0); + RET_ERR_UNLESS(close_file() == 0); + return 0; +} + + +static int test_bad_magic_string(void) +{ + uchar buffer[4]; + int fd; + + RET_ERR_UNLESS(open_file() == CONTROL_FILE_OK); + RET_ERR_UNLESS(close_file() == 0); + + /* Corrupt magic string */ + RET_ERR_UNLESS((fd= my_open(file_name, + O_BINARY | O_RDWR, + MYF(MY_WME))) >= 0); + RET_ERR_UNLESS(my_pread(fd, buffer, 4, 0, MYF(MY_FNABP | MY_WME)) == 0); + RET_ERR_UNLESS(my_pwrite(fd, (const uchar *)"papa", 4, 0, + MYF(MY_FNABP | MY_WME)) == 0); + + /* Check that control file module sees the problem */ + RET_ERR_UNLESS(local_ma_control_file_open() == + CONTROL_FILE_BAD_MAGIC_STRING); + /* Restore magic string */ + RET_ERR_UNLESS(my_pwrite(fd, buffer, 4, 0, MYF(MY_FNABP | MY_WME)) == 0); + RET_ERR_UNLESS(my_close(fd, MYF(MY_WME)) == 0); + RET_ERR_UNLESS(open_file() == CONTROL_FILE_OK); + RET_ERR_UNLESS(close_file() == 0); + return 0; +} + +static int test_bad_checksum(void) +{ + uchar buffer[4]; + int fd; + + RET_ERR_UNLESS(open_file() == CONTROL_FILE_OK); + RET_ERR_UNLESS(close_file() == 0); + + /* Corrupt checksum */ + RET_ERR_UNLESS((fd= my_open(file_name, + O_BINARY | O_RDWR, + MYF(MY_WME))) >= 0); + RET_ERR_UNLESS(my_pread(fd, buffer, 1, 30, MYF(MY_FNABP | MY_WME)) == 0); + buffer[0]+= 3; /* mangle checksum */ + RET_ERR_UNLESS(my_pwrite(fd, buffer, 1, 30, MYF(MY_FNABP | MY_WME)) == 0); + /* Check that control file module sees the problem */ + RET_ERR_UNLESS(local_ma_control_file_open() == + CONTROL_FILE_BAD_CHECKSUM); + /* Restore checksum */ + buffer[0]-= 3; + RET_ERR_UNLESS(my_pwrite(fd, buffer, 1, 30, MYF(MY_FNABP | MY_WME)) == 0); + RET_ERR_UNLESS(my_close(fd, MYF(MY_WME)) == 0); + + return 0; +} + + +static int test_bad_blocksize(void) +{ + maria_block_size<<= 1; + /* Check that control file module sees the problem */ + RET_ERR_UNLESS(local_ma_control_file_open() == + CONTROL_FILE_WRONG_BLOCKSIZE); + /* Restore blocksize */ + maria_block_size>>= 1; + + RET_ERR_UNLESS(open_file() == CONTROL_FILE_OK); + RET_ERR_UNLESS(close_file() == 0); + return 0; +} + + +static int test_future_size(void) +{ + /* + Here we check ability to add fields only so we can use + defined constants + */ + uint32 sum; + int fd; + uchar buffer[CF_CREATE_TIME_TOTAL_SIZE + CF_CHANGEABLE_TOTAL_SIZE + 2]; + RET_ERR_UNLESS((fd= my_open(file_name, + O_BINARY | O_RDWR, + MYF(MY_WME))) >= 0); + RET_ERR_UNLESS(my_read(fd, buffer, + CF_CREATE_TIME_TOTAL_SIZE + CF_CHANGEABLE_TOTAL_SIZE, + MYF(MY_FNABP | MY_WME)) == 0); + RET_ERR_UNLESS(my_close(fd, MYF(MY_WME)) == 0); + /* "add" new field of 1 byte (value 1) to header and variable part */ + memmove(buffer + CF_CREATE_TIME_TOTAL_SIZE + 1, + buffer + CF_CREATE_TIME_TOTAL_SIZE, + CF_CHANGEABLE_TOTAL_SIZE); + buffer[CF_CREATE_TIME_TOTAL_SIZE - CF_CHECKSUM_SIZE]= '\1'; + buffer[CF_CREATE_TIME_TOTAL_SIZE + CF_CHANGEABLE_TOTAL_SIZE + 1]= '\1'; + /* fix lengths */ + int2store(buffer + CF_CREATE_TIME_SIZE_OFFSET, CF_CREATE_TIME_TOTAL_SIZE + 1); + int2store(buffer + CF_CHANGEABLE_SIZE_OFFSET, CF_CHANGEABLE_TOTAL_SIZE + 1); + /* recalculete checksums */ + sum= (uint32) my_checksum(0, buffer, CF_CREATE_TIME_TOTAL_SIZE - + CF_CHECKSUM_SIZE + 1); + int4store(buffer + CF_CREATE_TIME_TOTAL_SIZE - CF_CHECKSUM_SIZE + 1, sum); + sum= (uint32) my_checksum(0, buffer + CF_CREATE_TIME_TOTAL_SIZE + 1 + + CF_CHECKSUM_SIZE, + CF_CHANGEABLE_TOTAL_SIZE - CF_CHECKSUM_SIZE + 1); + int4store(buffer + CF_CREATE_TIME_TOTAL_SIZE + 1, sum); + /* write new file and check it */ + RET_ERR_UNLESS((fd= my_open(file_name, + O_BINARY | O_RDWR, + MYF(MY_WME))) >= 0); + RET_ERR_UNLESS(my_pwrite(fd, buffer, + CF_CREATE_TIME_TOTAL_SIZE + + CF_CHANGEABLE_TOTAL_SIZE + 2, + 0, MYF(MY_FNABP | MY_WME)) == 0); + RET_ERR_UNLESS(my_close(fd, MYF(MY_WME)) == 0); + RET_ERR_UNLESS(open_file() == CONTROL_FILE_OK); + RET_ERR_UNLESS(close_file() == 0); + + return(0); +} + +static int test_bad_hchecksum(void) +{ + uchar buffer[4]; + int fd; + + RET_ERR_UNLESS(open_file() == CONTROL_FILE_OK); + RET_ERR_UNLESS(close_file() == 0); + + /* Corrupt checksum */ + RET_ERR_UNLESS((fd= my_open(file_name, + O_BINARY | O_RDWR, + MYF(MY_WME))) >= 0); + RET_ERR_UNLESS(my_pread(fd, buffer, 1, 26, MYF(MY_FNABP | MY_WME)) == 0); + buffer[0]+= 3; /* mangle checksum */ + RET_ERR_UNLESS(my_pwrite(fd, buffer, 1, 26, MYF(MY_FNABP | MY_WME)) == 0); + /* Check that control file module sees the problem */ + RET_ERR_UNLESS(local_ma_control_file_open() == + CONTROL_FILE_BAD_HEAD_CHECKSUM); + /* Restore checksum */ + buffer[0]-= 3; + RET_ERR_UNLESS(my_pwrite(fd, buffer, 1, 26, MYF(MY_FNABP | MY_WME)) == 0); + RET_ERR_UNLESS(my_close(fd, MYF(MY_WME)) == 0); + + return 0; +} + + +static int test_bad_size(void) +{ + uchar buffer[]= + "123456789012345678901234567890123456789012345678901234567890123456"; + int fd, i; + + /* A too short file */ + RET_ERR_UNLESS(delete_file(MYF(MY_WME)) == 0); + RET_ERR_UNLESS((fd= my_open(file_name, + O_BINARY | O_RDWR | O_CREAT, + MYF(MY_WME))) >= 0); + RET_ERR_UNLESS(my_write(fd, buffer, 10, MYF(MY_FNABP | MY_WME)) == 0); + /* Check that control file module sees the problem */ + RET_ERR_UNLESS(local_ma_control_file_open() == + CONTROL_FILE_TOO_SMALL); + for (i= 0; i < 8; i++) + { + RET_ERR_UNLESS(my_write(fd, buffer, 66, MYF(MY_FNABP | MY_WME)) == 0); + } + /* Check that control file module sees the problem */ + RET_ERR_UNLESS(local_ma_control_file_open() == + CONTROL_FILE_TOO_BIG); + RET_ERR_UNLESS(my_close(fd, MYF(MY_WME)) == 0); + + /* Leave a correct control file */ + RET_ERR_UNLESS(delete_file(MYF(MY_WME)) == 0); + RET_ERR_UNLESS(open_file() == CONTROL_FILE_OK); + RET_ERR_UNLESS(close_file() == 0); + + return 0; +} + + +static struct my_option my_long_options[] = +{ +#ifndef DBUG_OFF + {"debug", '#', "Debug log.", + 0, 0, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, +#endif + {"help", '?', "Display help and exit", + 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"version", 'V', "Print version number 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} +}; + + +static void version(void) +{ + printf("ma_control_file_test: unit test for the control file " + "module of the Maria storage engine. Ver 1.0 \n"); +} + +static my_bool +get_one_option(int optid, const struct my_option *opt __attribute__((unused)), + char *argument __attribute__((unused))) +{ + switch(optid) { + case 'V': + version(); + exit(0); + case '#': + DBUG_PUSH (argument); + break; + case '?': + version(); + usage(); + exit(0); + } + return 0; +} + + +/* Read options */ + +static void get_options(int argc, char *argv[]) +{ + int ho_error; + + if ((ho_error=handle_options(&argc, &argv, my_long_options, + get_one_option))) + exit(ho_error); + + return; +} /* get options */ + + +static void usage(void) +{ + printf("Usage: %s [options]\n\n", my_progname); + my_print_help(my_long_options); + my_print_variables(my_long_options); +} diff --git a/storage/maria/unittest/ma_loghandler_examples.c b/storage/maria/unittest/ma_loghandler_examples.c new file mode 100644 index 00000000000..0c11a3b9a8e --- /dev/null +++ b/storage/maria/unittest/ma_loghandler_examples.c @@ -0,0 +1,65 @@ +/* Copyright (C) 2006-2008 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 */ + +#include "../maria_def.h" + +static LOG_DESC INIT_LOGREC_FIXED_RECORD_0LSN_EXAMPLE= +{LOGRECTYPE_FIXEDLENGTH, 6, 6, NULL, NULL, NULL, 0, + "fixed0example", LOGREC_NOT_LAST_IN_GROUP, NULL, NULL}; + +static LOG_DESC INIT_LOGREC_VARIABLE_RECORD_0LSN_EXAMPLE= +{LOGRECTYPE_VARIABLE_LENGTH, 0, 9, NULL, NULL, NULL, 0, +"variable0example", LOGREC_NOT_LAST_IN_GROUP, NULL, NULL}; + +static LOG_DESC INIT_LOGREC_FIXED_RECORD_1LSN_EXAMPLE= +{LOGRECTYPE_PSEUDOFIXEDLENGTH, 7, 7, NULL, NULL, NULL, 1, +"fixed1example", LOGREC_NOT_LAST_IN_GROUP, NULL, NULL}; + +static LOG_DESC INIT_LOGREC_VARIABLE_RECORD_1LSN_EXAMPLE= +{LOGRECTYPE_VARIABLE_LENGTH, 0, 12, NULL, NULL, NULL, 1, +"variable1example", LOGREC_NOT_LAST_IN_GROUP, NULL, NULL}; + +static LOG_DESC INIT_LOGREC_FIXED_RECORD_2LSN_EXAMPLE= +{LOGRECTYPE_PSEUDOFIXEDLENGTH, 23, 23, NULL, NULL, NULL, 2, +"fixed2example", LOGREC_NOT_LAST_IN_GROUP, NULL, NULL}; + +static LOG_DESC INIT_LOGREC_VARIABLE_RECORD_2LSN_EXAMPLE= +{LOGRECTYPE_VARIABLE_LENGTH, 0, 19, NULL, NULL, NULL, 2, +"variable2example", LOGREC_NOT_LAST_IN_GROUP, NULL, NULL}; + + +void translog_example_table_init() +{ + int i; + log_record_type_descriptor[LOGREC_FIXED_RECORD_0LSN_EXAMPLE]= + INIT_LOGREC_FIXED_RECORD_0LSN_EXAMPLE; + log_record_type_descriptor[LOGREC_VARIABLE_RECORD_0LSN_EXAMPLE]= + INIT_LOGREC_VARIABLE_RECORD_0LSN_EXAMPLE; + log_record_type_descriptor[LOGREC_FIXED_RECORD_1LSN_EXAMPLE]= + INIT_LOGREC_FIXED_RECORD_1LSN_EXAMPLE; + log_record_type_descriptor[LOGREC_VARIABLE_RECORD_1LSN_EXAMPLE]= + INIT_LOGREC_VARIABLE_RECORD_1LSN_EXAMPLE; + log_record_type_descriptor[LOGREC_FIXED_RECORD_2LSN_EXAMPLE]= + INIT_LOGREC_FIXED_RECORD_2LSN_EXAMPLE; + log_record_type_descriptor[LOGREC_VARIABLE_RECORD_2LSN_EXAMPLE]= + INIT_LOGREC_VARIABLE_RECORD_2LSN_EXAMPLE; + for (i= LOGREC_VARIABLE_RECORD_2LSN_EXAMPLE + 1; + i < LOGREC_NUMBER_OF_TYPES; + i++) + log_record_type_descriptor[i].rclass= LOGRECTYPE_NOT_ALLOWED; +} + + + diff --git a/storage/maria/unittest/ma_maria_log_cleanup.c b/storage/maria/unittest/ma_maria_log_cleanup.c new file mode 100644 index 00000000000..7c78bf3a1a4 --- /dev/null +++ b/storage/maria/unittest/ma_maria_log_cleanup.c @@ -0,0 +1,64 @@ +/* Copyright (C) 2006-2008 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 */ + +#include "../maria_def.h" +#include <my_dir.h> + +my_bool maria_log_remove() +{ + MY_DIR *dirp; + uint i; + MY_STAT stat_buff; + char file_name[FN_REFLEN]; + + /* Removes control file */ + if (fn_format(file_name, CONTROL_FILE_BASE_NAME, + maria_data_root, "", MYF(MY_WME)) == NullS) + return 1; + if (my_stat(file_name, &stat_buff, MYF(0)) && + my_delete(file_name, MYF(MY_WME)) != 0) + return 1; + + /* Finds and removes transaction log files */ + if (!(dirp = my_dir(maria_data_root, MYF(MY_DONT_SORT)))) + return 1; + + for (i= 0; i < dirp->number_off_files; i++) + { + char *file= dirp->dir_entry[i].name; + if (strncmp(file, "maria_log.", 10) == 0 && + file[10] >= '0' && file[10] <= '9' && + file[11] >= '0' && file[11] <= '9' && + file[12] >= '0' && file[12] <= '9' && + file[13] >= '0' && file[13] <= '9' && + file[14] >= '0' && file[14] <= '9' && + file[15] >= '0' && file[15] <= '9' && + file[16] >= '0' && file[16] <= '9' && + file[17] >= '0' && file[17] <= '9' && + file[18] == '\0') + { + if (fn_format(file_name, file, + maria_data_root, "", MYF(MY_WME)) == NullS || + my_delete(file_name, MYF(MY_WME)) != 0) + { + my_dirend(dirp); + return 1; + } + } + } + my_dirend(dirp); + return 0; +} + diff --git a/storage/maria/unittest/ma_pagecache_consist.c b/storage/maria/unittest/ma_pagecache_consist.c new file mode 100644 index 00000000000..7dbdba433c6 --- /dev/null +++ b/storage/maria/unittest/ma_pagecache_consist.c @@ -0,0 +1,498 @@ +/* Copyright (C) 2006-2008 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 */ + +/* + TODO: use pthread_join instead of wait_for_thread_count_to_be_zero, like in + my_atomic-t.c (see BUG#22320). +*/ + +#include <tap.h> +#include <my_sys.h> +#include <m_string.h> +#include "test_file.h" +#include <tap.h> + +#define PCACHE_SIZE (TEST_PAGE_SIZE*1024*8) + +#ifndef DBUG_OFF +static const char* default_dbug_option; +#endif + +static char *file1_name= (char*)"page_cache_test_file_1"; +static PAGECACHE_FILE file1; +static pthread_cond_t COND_thread_count; +static pthread_mutex_t LOCK_thread_count; +static uint thread_count; +static PAGECACHE pagecache; + +#ifdef TEST_HIGH_CONCURENCY +static uint number_of_readers= 10; +static uint number_of_writers= 20; +static uint number_of_tests= 30000; +static uint record_length_limit= TEST_PAGE_SIZE/200; +static uint number_of_pages= 20; +static uint flush_divider= 1000; +#else /*TEST_HIGH_CONCURENCY*/ +#ifdef TEST_READERS +static uint number_of_readers= 10; +static uint number_of_writers= 1; +static uint number_of_tests= 30000; +static uint record_length_limit= TEST_PAGE_SIZE/200; +static uint number_of_pages= 20; +static uint flush_divider= 1000; +#undef SKIP_BIG_TESTS +#define SKIP_BIG_TESTS(X) /* no-op */ +#else /*TEST_READERS*/ +#ifdef TEST_WRITERS +static uint number_of_readers= 0; +static uint number_of_writers= 10; +static uint number_of_tests= 30000; +static uint record_length_limit= TEST_PAGE_SIZE/200; +static uint number_of_pages= 20; +static uint flush_divider= 1000; +#undef SKIP_BIG_TESTS +#define SKIP_BIG_TESTS(X) /* no-op */ +#else /*TEST_WRITERS*/ +static uint number_of_readers= 10; +static uint number_of_writers= 10; +static uint number_of_tests= 50000; +static uint record_length_limit= TEST_PAGE_SIZE/200; +static uint number_of_pages= 20000; +static uint flush_divider= 1000; +#endif /*TEST_WRITERS*/ +#endif /*TEST_READERS*/ +#endif /*TEST_HIGH_CONCURENCY*/ + + +/** + @brief Dummy pagecache callback. +*/ + +static my_bool +dummy_callback(uchar *page __attribute__((unused)), + pgcache_page_no_t page_no __attribute__((unused)), + uchar* data_ptr __attribute__((unused))) +{ + return 0; +} + + +/** + @brief Dummy pagecache callback. +*/ + +static void +dummy_fail_callback(uchar* data_ptr __attribute__((unused))) +{ + return; +} + + +/* + Get pseudo-random length of the field in (0;limit) + + SYNOPSYS + get_len() + limit limit for generated value + + RETURN + length where length >= 0 & length < limit +*/ + +static uint get_len(uint limit) +{ + return (uint)((ulonglong)rand()*(limit-1)/RAND_MAX); +} + + +/* + Check page's consistency: layout is + 4 bytes: number 'num' of records in this page, then num occurences of + { 4 bytes: record's length 'len'; then 4 bytes unchecked ('tag') then + 'len' bytes each equal to the record's sequential number in this page, + modulo 256 }, then zeroes. + */ +uint check_page(uchar *buff, ulong offset, int page_locked, int page_no, + int tag) +{ + uint end= sizeof(uint); + uint num= uint4korr(buff); + uint i; + DBUG_ENTER("check_page"); + + for (i= 0; i < num; i++) + { + uint len= uint4korr(buff + end); + uint j; + end+= 4 + 4; + if (len + end > TEST_PAGE_SIZE) + { + diag("incorrect field header #%u by offset %lu\n", i, offset + end); + goto err; + } + for(j= 0; j < len; j++) + { + if (buff[end + j] != (uchar)((i+1) % 256)) + { + diag("incorrect %lu byte\n", offset + end + j); + goto err; + } + } + end+= len; + } + for(i= end; i < TEST_PAGE_SIZE; i++) + { + if (buff[i] != 0) + { + int h; + DBUG_PRINT("err", + ("byte %lu (%lu + %u), page %u (%s, end: %u, recs: %u, tag: %d) should be 0\n", + offset + i, offset, i, page_no, + (page_locked ? "locked" : "unlocked"), + end, num, tag)); + diag("byte %lu (%lu + %u), page %u (%s, end: %u, recs: %u, tag: %d) should be 0\n", + offset + i, offset, i, page_no, + (page_locked ? "locked" : "unlocked"), + end, num, tag); + h= my_open("wrong_page", O_CREAT | O_TRUNC | O_RDWR, MYF(0)); + my_pwrite(h, (uchar*) buff, TEST_PAGE_SIZE, 0, MYF(0)); + my_close(h, MYF(0)); + goto err; + } + } + DBUG_RETURN(end); +err: + DBUG_PRINT("err", ("try to flush")); + if (page_locked) + { + pagecache_delete(&pagecache, &file1, page_no, + PAGECACHE_LOCK_LEFT_WRITELOCKED, 1); + } + else + { + flush_pagecache_blocks(&pagecache, &file1, FLUSH_RELEASE); + } + exit(1); +} + +void put_rec(uchar *buff, uint end, uint len, uint tag) +{ + uint i; + uint num; + num= uint4korr(buff); + if (!len) + len= 1; + if (end + 4*2 + len > TEST_PAGE_SIZE) + return; + int4store(buff + end, len); + end+= 4; + int4store(buff + end, tag); + end+= 4; + num++; + int4store(buff, num); + for (i= end; i < (len + end); i++) + { + buff[i]= (uchar) num % 256; + } +} + +/* + Recreate and reopen a file for test + + SYNOPSIS + reset_file() + file File to reset + file_name Path (and name) of file which should be reset +*/ + +void reset_file(PAGECACHE_FILE file, char *file_name) +{ + flush_pagecache_blocks(&pagecache, &file1, FLUSH_RELEASE); + if (my_close(file1.file, MYF(0)) != 0) + { + diag("Got error during %s closing from close() (errno: %d)\n", + file_name, errno); + exit(1); + } + my_delete(file_name, MYF(0)); + if ((file.file= my_open(file_name, + O_CREAT | O_TRUNC | O_RDWR, MYF(0))) == -1) + { + diag("Got error during %s creation from open() (errno: %d)\n", + file_name, errno); + exit(1); + } +} + + +void reader(int num) +{ + unsigned char *buffr= malloc(TEST_PAGE_SIZE); + uint i; + + for (i= 0; i < number_of_tests; i++) + { + uint page= get_len(number_of_pages); + pagecache_read(&pagecache, &file1, page, 3, buffr, + PAGECACHE_PLAIN_PAGE, + PAGECACHE_LOCK_LEFT_UNLOCKED, + 0); + check_page(buffr, page * TEST_PAGE_SIZE, 0, page, -num); + + } + free(buffr); +} + + +void writer(int num) +{ + unsigned char *buffr= malloc(TEST_PAGE_SIZE); + uint i; + + for (i= 0; i < number_of_tests; i++) + { + uint end; + uint page= get_len(number_of_pages); + pagecache_read(&pagecache, &file1, page, 3, buffr, + PAGECACHE_PLAIN_PAGE, + PAGECACHE_LOCK_WRITE, + 0); + end= check_page(buffr, page * TEST_PAGE_SIZE, 1, page, num); + put_rec(buffr, end, get_len(record_length_limit), num); + pagecache_write(&pagecache, &file1, page, 3, buffr, + PAGECACHE_PLAIN_PAGE, + PAGECACHE_LOCK_WRITE_UNLOCK, + PAGECACHE_UNPIN, + PAGECACHE_WRITE_DELAY, + 0, LSN_IMPOSSIBLE); + + if (i % flush_divider == 0) + flush_pagecache_blocks(&pagecache, &file1, FLUSH_FORCE_WRITE); + } + free(buffr); +} + + +static void *test_thread_reader(void *arg) +{ + int param=*((int*) arg); + my_thread_init(); + { + DBUG_ENTER("test_reader"); + DBUG_PRINT("enter", ("param: %d", param)); + + reader(param); + + DBUG_PRINT("info", ("Thread %s ended", my_thread_name())); + pthread_mutex_lock(&LOCK_thread_count); + ok(1, "reader%d: done", param); + thread_count--; + VOID(pthread_cond_signal(&COND_thread_count)); /* Tell main we are ready */ + pthread_mutex_unlock(&LOCK_thread_count); + free((uchar*) arg); + my_thread_end(); + } + return 0; +} + + +static void *test_thread_writer(void *arg) +{ + int param=*((int*) arg); + my_thread_init(); + { + DBUG_ENTER("test_writer"); + DBUG_PRINT("enter", ("param: %d", param)); + + writer(param); + + DBUG_PRINT("info", ("Thread %s ended", my_thread_name())); + pthread_mutex_lock(&LOCK_thread_count); + ok(1, "writer%d: done", param); + thread_count--; + VOID(pthread_cond_signal(&COND_thread_count)); /* Tell main we are ready */ + pthread_mutex_unlock(&LOCK_thread_count); + free((uchar*) arg); + my_thread_end(); + } + return 0; +} + + +int main(int argc __attribute__((unused)), + char **argv __attribute__((unused))) +{ + pthread_t tid; + pthread_attr_t thr_attr; + int *param, error, pagen; + + MY_INIT(argv[0]); + +#ifndef DBUG_OFF +#if defined(__WIN__) + default_dbug_option= "d:t:i:O,\\test_pagecache_consist.trace"; +#else + default_dbug_option= "d:t:i:o,/tmp/test_pagecache_consist.trace"; +#endif + if (argc > 1) + { + DBUG_SET(default_dbug_option); + DBUG_SET_INITIAL(default_dbug_option); + } +#endif + + { + DBUG_ENTER("main"); + DBUG_PRINT("info", ("Main thread: %s\n", my_thread_name())); + plan(number_of_writers + number_of_readers); + SKIP_BIG_TESTS(number_of_writers + number_of_readers) + { + + if ((file1.file= my_open(file1_name, + O_CREAT | O_TRUNC | O_RDWR, MYF(0))) == -1) + { + diag( "Got error during file1 creation from open() (errno: %d)\n", + errno); + exit(1); + } + pagecache_file_init(file1, &dummy_callback, &dummy_callback, + &dummy_fail_callback, &dummy_callback, NULL); + DBUG_PRINT("info", ("file1: %d", file1.file)); + if (my_chmod(file1_name, S_IRWXU | S_IRWXG | S_IRWXO, MYF(MY_WME))) + exit(1); + my_pwrite(file1.file, (const uchar *)"test file", 9, 0, MYF(0)); + + if ((error= pthread_cond_init(&COND_thread_count, NULL))) + { + diag( "COND_thread_count: %d from pthread_cond_init (errno: %d)\n", + error, errno); + exit(1); + } + if ((error= pthread_mutex_init(&LOCK_thread_count, MY_MUTEX_INIT_FAST))) + { + diag( "LOCK_thread_count: %d from pthread_cond_init (errno: %d)\n", + error, errno); + exit(1); + } + + if ((error= pthread_attr_init(&thr_attr))) + { + diag("Got error: %d from pthread_attr_init (errno: %d)\n", + error,errno); + exit(1); + } + if ((error= pthread_attr_setdetachstate(&thr_attr, PTHREAD_CREATE_DETACHED))) + { + diag( + "Got error: %d from pthread_attr_setdetachstate (errno: %d)\n", + error,errno); + exit(1); + } + +#ifdef HAVE_THR_SETCONCURRENCY + VOID(thr_setconcurrency(2)); +#endif + + if ((pagen= init_pagecache(&pagecache, PCACHE_SIZE, 0, 0, + TEST_PAGE_SIZE, 0)) == 0) + { + diag("Got error: init_pagecache() (errno: %d)\n", + errno); + exit(1); + } + DBUG_PRINT("info", ("Page cache %d pages", pagen)); + { + unsigned char *buffr= malloc(TEST_PAGE_SIZE); + uint i; + memset(buffr, '\0', TEST_PAGE_SIZE); + for (i= 0; i < number_of_pages; i++) + { + pagecache_write(&pagecache, &file1, i, 3, buffr, + PAGECACHE_PLAIN_PAGE, + PAGECACHE_LOCK_LEFT_UNLOCKED, + PAGECACHE_PIN_LEFT_UNPINNED, + PAGECACHE_WRITE_DELAY, + 0, LSN_IMPOSSIBLE); + } + flush_pagecache_blocks(&pagecache, &file1, FLUSH_FORCE_WRITE); + free(buffr); + } + pthread_mutex_lock(&LOCK_thread_count); + while (number_of_readers != 0 || number_of_writers != 0) + { + if (number_of_readers != 0) + { + param=(int*) malloc(sizeof(int)); + *param= number_of_readers; + if ((error= pthread_create(&tid, &thr_attr, test_thread_reader, + (void*) param))) + { + diag("Got error: %d from pthread_create (errno: %d)\n", + error,errno); + exit(1); + } + thread_count++; + number_of_readers--; + } + if (number_of_writers != 0) + { + param=(int*) malloc(sizeof(int)); + *param= number_of_writers; + if ((error= pthread_create(&tid, &thr_attr, test_thread_writer, + (void*) param))) + { + diag("Got error: %d from pthread_create (errno: %d)\n", + error,errno); + exit(1); + } + thread_count++; + number_of_writers--; + } + } + DBUG_PRINT("info", ("Thread started")); + pthread_mutex_unlock(&LOCK_thread_count); + + pthread_attr_destroy(&thr_attr); + + /* wait finishing */ + pthread_mutex_lock(&LOCK_thread_count); + while (thread_count) + { + if ((error= pthread_cond_wait(&COND_thread_count,&LOCK_thread_count))) + diag("COND_thread_count: %d from pthread_cond_wait\n",error); + } + pthread_mutex_unlock(&LOCK_thread_count); + DBUG_PRINT("info", ("thread ended")); + + end_pagecache(&pagecache, 1); + DBUG_PRINT("info", ("Page cache ended")); + + if (my_close(file1.file, MYF(0)) != 0) + { + diag( "Got error during file1 closing from close() (errno: %d)\n", + errno); + exit(1); + } + my_delete(file1_name, MYF(0)); + + DBUG_PRINT("info", ("file1 (%d) closed", file1.file)); + DBUG_PRINT("info", ("Program end")); + + } /* SKIP_BIG_TESTS */ + my_end(0); + + return exit_status(); + } +} diff --git a/storage/maria/unittest/ma_pagecache_rwconsist.c b/storage/maria/unittest/ma_pagecache_rwconsist.c new file mode 100644 index 00000000000..a1a22b5e18d --- /dev/null +++ b/storage/maria/unittest/ma_pagecache_rwconsist.c @@ -0,0 +1,362 @@ +/* Copyright (C) 2006-2008 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 */ + +/* + TODO: use pthread_join instead of wait_for_thread_count_to_be_zero, like in + my_atomic-t.c (see BUG#22320). +*/ + +#include <tap.h> +#include <my_sys.h> +#include <m_string.h> +#include "test_file.h" +#include <tap.h> + +#define PCACHE_SIZE (TEST_PAGE_SIZE*1024*8) + +#ifndef DBUG_OFF +static const char* default_dbug_option; +#endif + + +#define SLEEP my_sleep(5) + +static char *file1_name= (char*)"page_cache_test_file_1"; +static PAGECACHE_FILE file1; +static pthread_cond_t COND_thread_count; +static pthread_mutex_t LOCK_thread_count; +static uint thread_count= 0; +static PAGECACHE pagecache; + +static uint number_of_readers= 5; +static uint number_of_writers= 5; +static uint number_of_read_tests= 2000; +static uint number_of_write_tests= 1000; +static uint read_sleep_limit= 3; +static uint report_divisor= 50; + +/** + @brief Dummy pagecache callback. +*/ + +static my_bool +dummy_callback(uchar *page __attribute__((unused)), + pgcache_page_no_t page_no __attribute__((unused)), + uchar* data_ptr __attribute__((unused))) +{ + return 0; +} + + +/** + @brief Dummy pagecache callback. +*/ + +static void +dummy_fail_callback(uchar* data_ptr __attribute__((unused))) +{ + return; +} + + +/** + @brief Checks page consistency + + @param buff pointer to the page content + @param task task ID +*/ +void check_page(uchar *buff, int task) +{ + uint i; + DBUG_ENTER("check_page"); + + for (i= 1; i < TEST_PAGE_SIZE; i++) + { + if (buff[0] != buff[i]) + goto err; + } + DBUG_VOID_RETURN; +err: + diag("Task %d char #%u '%u' != '%u'", task, i, (uint) buff[0], + (uint) buff[i]); + DBUG_PRINT("err", ("try to flush")); + exit(1); +} + + + +void reader(int num) +{ + unsigned char *buff; + uint i; + PAGECACHE_BLOCK_LINK *link; + + for (i= 0; i < number_of_read_tests; i++) + { + if (i % report_divisor == 0) + diag("Reader %d - %u", num, i); + buff= pagecache_read(&pagecache, &file1, 0, 3, NULL, + PAGECACHE_PLAIN_PAGE, + PAGECACHE_LOCK_READ, + &link); + check_page(buff, num); + pagecache_unlock_by_link(&pagecache, link, + PAGECACHE_LOCK_READ_UNLOCK, + PAGECACHE_UNPIN, 0, 0, 0, FALSE); + { + int lim= rand() % read_sleep_limit; + int j; + for (j= 0; j < lim; j++) + SLEEP; + } + } +} + + +void writer(int num) +{ + uint i; + uchar *buff; + PAGECACHE_BLOCK_LINK *link; + + for (i= 0; i < number_of_write_tests; i++) + { + uchar c= (uchar) rand() % 256; + + if (i % report_divisor == 0) + diag("Writer %d - %u", num, i); + buff= pagecache_read(&pagecache, &file1, 0, 3, NULL, + PAGECACHE_PLAIN_PAGE, + PAGECACHE_LOCK_WRITE, + &link); + + check_page(buff, num); + bfill(buff, TEST_PAGE_SIZE / 2, c); + SLEEP; + bfill(buff + TEST_PAGE_SIZE/2, TEST_PAGE_SIZE / 2, c); + check_page(buff, num); + pagecache_unlock_by_link(&pagecache, link, + PAGECACHE_LOCK_WRITE_UNLOCK, + PAGECACHE_UNPIN, 0, 0, 1, FALSE); + SLEEP; + } +} + + +static void *test_thread_reader(void *arg) +{ + int param=*((int*) arg); + my_thread_init(); + { + DBUG_ENTER("test_reader"); + + DBUG_PRINT("enter", ("param: %d", param)); + + reader(param); + + DBUG_PRINT("info", ("Thread %s ended", my_thread_name())); + pthread_mutex_lock(&LOCK_thread_count); + ok(1, "reader%d: done", param); + thread_count--; + VOID(pthread_cond_signal(&COND_thread_count)); /* Tell main we are ready */ + pthread_mutex_unlock(&LOCK_thread_count); + free((uchar*) arg); + my_thread_end(); + } + return 0; +} + + +static void *test_thread_writer(void *arg) +{ + int param=*((int*) arg); + my_thread_init(); + { + DBUG_ENTER("test_writer"); + + writer(param); + + DBUG_PRINT("info", ("Thread %s ended", my_thread_name())); + pthread_mutex_lock(&LOCK_thread_count); + ok(1, "writer%d: done", param); + thread_count--; + VOID(pthread_cond_signal(&COND_thread_count)); /* Tell main we are ready */ + pthread_mutex_unlock(&LOCK_thread_count); + free((uchar*) arg); + my_thread_end(); + } + return 0; +} + + +int main(int argc __attribute__((unused)), + char **argv __attribute__((unused))) +{ + pthread_t tid; + pthread_attr_t thr_attr; + int *param, error, pagen; + + MY_INIT(argv[0]); + +#ifndef DBUG_OFF +#if defined(__WIN__) + default_dbug_option= "d:t:i:O,\\test_pagecache_consist.trace"; +#else + default_dbug_option= "d:t:i:O,/tmp/test_pagecache_consist.trace"; +#endif + if (argc > 1) + { + DBUG_SET(default_dbug_option); + DBUG_SET_INITIAL(default_dbug_option); + } +#endif + + { + DBUG_ENTER("main"); + DBUG_PRINT("info", ("Main thread: %s\n", my_thread_name())); + plan(number_of_writers + number_of_readers); + SKIP_BIG_TESTS(number_of_writers + number_of_readers) + { + + if ((file1.file= my_open(file1_name, + O_CREAT | O_TRUNC | O_RDWR, MYF(0))) == -1) + { + diag( "Got error during file1 creation from open() (errno: %d)\n", + errno); + exit(1); + } + pagecache_file_init(file1, &dummy_callback, &dummy_callback, + &dummy_fail_callback, &dummy_callback, NULL); + DBUG_PRINT("info", ("file1: %d", file1.file)); + if (my_chmod(file1_name, S_IRWXU | S_IRWXG | S_IRWXO, MYF(MY_WME))) + exit(1); + my_pwrite(file1.file, (const uchar*) "test file", 9, 0, MYF(0)); + + if ((error= pthread_cond_init(&COND_thread_count, NULL))) + { + diag( "COND_thread_count: %d from pthread_cond_init (errno: %d)\n", + error, errno); + exit(1); + } + if ((error= pthread_mutex_init(&LOCK_thread_count, MY_MUTEX_INIT_FAST))) + { + diag( "LOCK_thread_count: %d from pthread_cond_init (errno: %d)\n", + error, errno); + exit(1); + } + + if ((error= pthread_attr_init(&thr_attr))) + { + diag("Got error: %d from pthread_attr_init (errno: %d)\n", + error,errno); + exit(1); + } + if ((error= pthread_attr_setdetachstate(&thr_attr, PTHREAD_CREATE_DETACHED))) + { + diag( + "Got error: %d from pthread_attr_setdetachstate (errno: %d)\n", + error,errno); + exit(1); + } + +#ifdef HAVE_THR_SETCONCURRENCY + VOID(thr_setconcurrency(2)); +#endif + + if ((pagen= init_pagecache(&pagecache, PCACHE_SIZE, 0, 0, + TEST_PAGE_SIZE, 0)) == 0) + { + diag("Got error: init_pagecache() (errno: %d)\n", + errno); + exit(1); + } + DBUG_PRINT("info", ("Page cache %d pages", pagen)); + { + unsigned char *buffr= malloc(TEST_PAGE_SIZE); + memset(buffr, '\0', TEST_PAGE_SIZE); + pagecache_write(&pagecache, &file1, 0, 3, buffr, + PAGECACHE_PLAIN_PAGE, + PAGECACHE_LOCK_LEFT_UNLOCKED, + PAGECACHE_PIN_LEFT_UNPINNED, + PAGECACHE_WRITE_DELAY, + 0, LSN_IMPOSSIBLE); + } + pthread_mutex_lock(&LOCK_thread_count); + + while (number_of_readers != 0 || number_of_writers != 0) + { + if (number_of_readers != 0) + { + param=(int*) malloc(sizeof(int)); + *param= number_of_readers + number_of_writers; + if ((error= pthread_create(&tid, &thr_attr, test_thread_reader, + (void*) param))) + { + diag("Got error: %d from pthread_create (errno: %d)\n", + error,errno); + exit(1); + } + thread_count++; + number_of_readers--; + } + if (number_of_writers != 0) + { + param=(int*) malloc(sizeof(int)); + *param= number_of_writers + number_of_readers; + if ((error= pthread_create(&tid, &thr_attr, test_thread_writer, + (void*) param))) + { + diag("Got error: %d from pthread_create (errno: %d)\n", + error,errno); + exit(1); + } + thread_count++; + number_of_writers--; + } + } + DBUG_PRINT("info", ("Thread started")); + pthread_mutex_unlock(&LOCK_thread_count); + + pthread_attr_destroy(&thr_attr); + + /* wait finishing */ + pthread_mutex_lock(&LOCK_thread_count); + while (thread_count) + { + if ((error= pthread_cond_wait(&COND_thread_count, &LOCK_thread_count))) + diag("COND_thread_count: %d from pthread_cond_wait\n", error); + } + pthread_mutex_unlock(&LOCK_thread_count); + DBUG_PRINT("info", ("thread ended")); + + end_pagecache(&pagecache, 1); + DBUG_PRINT("info", ("Page cache ended")); + + if (my_close(file1.file, MYF(0)) != 0) + { + diag( "Got error during file1 closing from close() (errno: %d)\n", + errno); + exit(1); + } + my_delete(file1_name, MYF(0)); + + DBUG_PRINT("info", ("file1 (%d) closed", file1.file)); + DBUG_PRINT("info", ("Program end")); + } /* SKIP_BIG_TESTS */ + my_end(0); + + return exit_status(); + } +} diff --git a/storage/maria/unittest/ma_pagecache_rwconsist2.c b/storage/maria/unittest/ma_pagecache_rwconsist2.c new file mode 100644 index 00000000000..34183a2d0ab --- /dev/null +++ b/storage/maria/unittest/ma_pagecache_rwconsist2.c @@ -0,0 +1,358 @@ +/* Copyright (C) 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + + +/** + @file this unit tests consistence of long block writing under write lock + and simultaneous reading of this block with read request without read lock + requirement. +*/ + +/* + TODO: use pthread_join instead of wait_for_thread_count_to_be_zero, like in + my_atomic-t.c (see BUG#22320). +*/ + +#include <tap.h> +#include <my_sys.h> +#include <m_string.h> +#include "test_file.h" +#include <tap.h> + +#define PCACHE_SIZE (TEST_PAGE_SIZE*1024*8) + +#ifndef DBUG_OFF +static const char* default_dbug_option; +#endif + + +#define SLEEP my_sleep(5) + +static char *file1_name= (char*)"page_cache_test_file_1"; +static PAGECACHE_FILE file1; +static pthread_cond_t COND_thread_count; +static pthread_mutex_t LOCK_thread_count; +static uint thread_count= 0; +static PAGECACHE pagecache; + +static uint number_of_readers= 5; +static uint number_of_writers= 5; +static uint number_of_read_tests= 20000; +static uint number_of_write_tests= 1000; +static uint report_divisor= 50; + +/** + @brief Dummy pagecache callback. +*/ + +static my_bool +dummy_callback(uchar *page __attribute__((unused)), + pgcache_page_no_t page_no __attribute__((unused)), + uchar* data_ptr __attribute__((unused))) +{ + return 0; +} + + +/** + @brief Dummy pagecache callback. +*/ + +static void +dummy_fail_callback(uchar* data_ptr __attribute__((unused))) +{ + return; +} + + +/** + @brief Checks page consistency + + @param buff pointer to the page content + @param task task ID +*/ +void check_page(uchar *buff, int task) +{ + uint i; + DBUG_ENTER("check_page"); + + for (i= 1; i < TEST_PAGE_SIZE; i++) + { + if (buff[0] != buff[i]) + goto err; + } + DBUG_VOID_RETURN; +err: + diag("Task %d char #%u '%u' != '%u'", task, i, (uint) buff[0], + (uint) buff[i]); + DBUG_PRINT("err", ("try to flush")); + exit(1); +} + + + +void reader(int num) +{ + unsigned char buff[TEST_PAGE_SIZE]; + uint i; + + for (i= 0; i < number_of_read_tests; i++) + { + if (i % report_divisor == 0) + diag("Reader %d - %u", num, i); + pagecache_read(&pagecache, &file1, 0, 3, buff, + PAGECACHE_PLAIN_PAGE, + PAGECACHE_LOCK_LEFT_UNLOCKED, + NULL); + check_page(buff, num); + } +} + + +void writer(int num) +{ + uint i; + uchar *buff; + PAGECACHE_BLOCK_LINK *link; + + for (i= 0; i < number_of_write_tests; i++) + { + uchar c= (uchar) rand() % 256; + + if (i % report_divisor == 0) + diag("Writer %d - %u", num, i); + buff= pagecache_read(&pagecache, &file1, 0, 3, NULL, + PAGECACHE_PLAIN_PAGE, + PAGECACHE_LOCK_WRITE, + &link); + + check_page(buff, num); + bfill(buff, TEST_PAGE_SIZE / 2, c); + SLEEP; + bfill(buff + TEST_PAGE_SIZE/2, TEST_PAGE_SIZE / 2, c); + check_page(buff, num); + pagecache_unlock_by_link(&pagecache, link, + PAGECACHE_LOCK_WRITE_UNLOCK, + PAGECACHE_UNPIN, 0, 0, 1, FALSE); + SLEEP; + } +} + + +static void *test_thread_reader(void *arg) +{ + int param=*((int*) arg); + my_thread_init(); + { + DBUG_ENTER("test_reader"); + + DBUG_PRINT("enter", ("param: %d", param)); + + reader(param); + + DBUG_PRINT("info", ("Thread %s ended", my_thread_name())); + pthread_mutex_lock(&LOCK_thread_count); + ok(1, "reader%d: done", param); + thread_count--; + VOID(pthread_cond_signal(&COND_thread_count)); /* Tell main we are ready */ + pthread_mutex_unlock(&LOCK_thread_count); + free((uchar*) arg); + my_thread_end(); + } + return 0; +} + + +static void *test_thread_writer(void *arg) +{ + int param=*((int*) arg); + my_thread_init(); + { + DBUG_ENTER("test_writer"); + + writer(param); + + DBUG_PRINT("info", ("Thread %s ended", my_thread_name())); + pthread_mutex_lock(&LOCK_thread_count); + ok(1, "writer%d: done", param); + thread_count--; + VOID(pthread_cond_signal(&COND_thread_count)); /* Tell main we are ready */ + pthread_mutex_unlock(&LOCK_thread_count); + free((uchar*) arg); + my_thread_end(); + } + return 0; +} + + +int main(int argc __attribute__((unused)), + char **argv __attribute__((unused))) +{ + pthread_t tid; + pthread_attr_t thr_attr; + int *param, error, pagen; + + MY_INIT(argv[0]); + +#ifndef DBUG_OFF +#if defined(__WIN__) + default_dbug_option= "d:t:i:O,\\test_pagecache_consist.trace"; +#else + default_dbug_option= "d:t:i:O,/tmp/test_pagecache_consist.trace"; +#endif + if (argc > 1) + { + DBUG_SET(default_dbug_option); + DBUG_SET_INITIAL(default_dbug_option); + } +#endif + + { + DBUG_ENTER("main"); + DBUG_PRINT("info", ("Main thread: %s\n", my_thread_name())); + plan(number_of_writers + number_of_readers); + SKIP_BIG_TESTS(number_of_writers + number_of_readers) + { + + if ((file1.file= my_open(file1_name, + O_CREAT | O_TRUNC | O_RDWR, MYF(0))) == -1) + { + diag( "Got error during file1 creation from open() (errno: %d)\n", + errno); + exit(1); + } + pagecache_file_init(file1, &dummy_callback, &dummy_callback, + &dummy_fail_callback, &dummy_callback, NULL); + DBUG_PRINT("info", ("file1: %d", file1.file)); + if (my_chmod(file1_name, S_IRWXU | S_IRWXG | S_IRWXO, MYF(MY_WME))) + exit(1); + my_pwrite(file1.file, (const uchar*) "test file", 9, 0, MYF(0)); + + if ((error= pthread_cond_init(&COND_thread_count, NULL))) + { + diag( "COND_thread_count: %d from pthread_cond_init (errno: %d)\n", + error, errno); + exit(1); + } + if ((error= pthread_mutex_init(&LOCK_thread_count, MY_MUTEX_INIT_FAST))) + { + diag( "LOCK_thread_count: %d from pthread_cond_init (errno: %d)\n", + error, errno); + exit(1); + } + + if ((error= pthread_attr_init(&thr_attr))) + { + diag("Got error: %d from pthread_attr_init (errno: %d)\n", + error,errno); + exit(1); + } + if ((error= pthread_attr_setdetachstate(&thr_attr, PTHREAD_CREATE_DETACHED))) + { + diag( + "Got error: %d from pthread_attr_setdetachstate (errno: %d)\n", + error,errno); + exit(1); + } + +#ifdef HAVE_THR_SETCONCURRENCY + VOID(thr_setconcurrency(2)); +#endif + + if ((pagen= init_pagecache(&pagecache, PCACHE_SIZE, 0, 0, + TEST_PAGE_SIZE, 0)) == 0) + { + diag("Got error: init_pagecache() (errno: %d)\n", + errno); + exit(1); + } + DBUG_PRINT("info", ("Page cache %d pages", pagen)); + { + unsigned char *buffr= malloc(TEST_PAGE_SIZE); + memset(buffr, '\0', TEST_PAGE_SIZE); + pagecache_write(&pagecache, &file1, 0, 3, buffr, + PAGECACHE_PLAIN_PAGE, + PAGECACHE_LOCK_LEFT_UNLOCKED, + PAGECACHE_PIN_LEFT_UNPINNED, + PAGECACHE_WRITE_DELAY, + 0, LSN_IMPOSSIBLE); + } + pthread_mutex_lock(&LOCK_thread_count); + + while (number_of_readers != 0 || number_of_writers != 0) + { + if (number_of_readers != 0) + { + param=(int*) malloc(sizeof(int)); + *param= number_of_readers + number_of_writers; + if ((error= pthread_create(&tid, &thr_attr, test_thread_reader, + (void*) param))) + { + diag("Got error: %d from pthread_create (errno: %d)\n", + error,errno); + exit(1); + } + thread_count++; + number_of_readers--; + } + if (number_of_writers != 0) + { + param=(int*) malloc(sizeof(int)); + *param= number_of_writers + number_of_readers; + if ((error= pthread_create(&tid, &thr_attr, test_thread_writer, + (void*) param))) + { + diag("Got error: %d from pthread_create (errno: %d)\n", + error,errno); + exit(1); + } + thread_count++; + number_of_writers--; + } + } + DBUG_PRINT("info", ("Thread started")); + pthread_mutex_unlock(&LOCK_thread_count); + + pthread_attr_destroy(&thr_attr); + + /* wait finishing */ + pthread_mutex_lock(&LOCK_thread_count); + while (thread_count) + { + if ((error= pthread_cond_wait(&COND_thread_count, &LOCK_thread_count))) + diag("COND_thread_count: %d from pthread_cond_wait\n", error); + } + pthread_mutex_unlock(&LOCK_thread_count); + DBUG_PRINT("info", ("thread ended")); + + end_pagecache(&pagecache, 1); + DBUG_PRINT("info", ("Page cache ended")); + + if (my_close(file1.file, MYF(0)) != 0) + { + diag( "Got error during file1 closing from close() (errno: %d)\n", + errno); + exit(1); + } + my_delete(file1_name, MYF(0)); + + DBUG_PRINT("info", ("file1 (%d) closed", file1.file)); + DBUG_PRINT("info", ("Program end")); + } /* SKIP_BIG_TESTS */ + my_end(0); + + return exit_status(); + } +} diff --git a/storage/maria/unittest/ma_pagecache_single.c b/storage/maria/unittest/ma_pagecache_single.c new file mode 100644 index 00000000000..53c820dcd2e --- /dev/null +++ b/storage/maria/unittest/ma_pagecache_single.c @@ -0,0 +1,765 @@ +/* Copyright (C) 2006-2008 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 */ + +/* + TODO: use pthread_join instead of wait_for_thread_count_to_be_zero, like in + my_atomic-t.c (see BUG#22320). + Use diag() instead of fprintf(stderr). +*/ +#include <tap.h> +#include <my_sys.h> +#include <m_string.h> +#include "test_file.h" +#include <tap.h> + +#define PCACHE_SIZE (TEST_PAGE_SIZE*1024*10) + +#ifndef DBUG_OFF +static const char* default_dbug_option; +#endif + +#ifndef BIG +#undef SKIP_BIG_TESTS +#define SKIP_BIG_TESTS(X) /* no-op */ +#endif + +static char *file1_name= (char*)"page_cache_test_file_1"; +static char *file2_name= (char*)"page_cache_test_file_2"; +static PAGECACHE_FILE file1; +static pthread_cond_t COND_thread_count; +static pthread_mutex_t LOCK_thread_count; +static uint thread_count; +static PAGECACHE pagecache; + +/* + File contance descriptors +*/ +static struct file_desc simple_read_write_test_file[]= +{ + { TEST_PAGE_SIZE, '\1'}, + {0, 0} +}; +static struct file_desc simple_read_change_write_read_test_file[]= +{ + { TEST_PAGE_SIZE/2, '\65'}, + { TEST_PAGE_SIZE/2, '\1'}, + {0, 0} +}; +static struct file_desc simple_pin_test_file1[]= +{ + { TEST_PAGE_SIZE*2, '\1'}, + {0, 0} +}; +static struct file_desc simple_pin_test_file2[]= +{ + { TEST_PAGE_SIZE/2, '\1'}, + { TEST_PAGE_SIZE/2, (unsigned char)129}, + { TEST_PAGE_SIZE, '\1'}, + {0, 0} +}; +static struct file_desc simple_pin_no_lock_test_file1[]= +{ + { TEST_PAGE_SIZE, '\4'}, + {0, 0} +}; +static struct file_desc simple_pin_no_lock_test_file2[]= +{ + { TEST_PAGE_SIZE, '\5'}, + {0, 0} +}; +static struct file_desc simple_pin_no_lock_test_file3[]= +{ + { TEST_PAGE_SIZE, '\6'}, + {0, 0} +}; +static struct file_desc simple_delete_forget_test_file[]= +{ + { TEST_PAGE_SIZE, '\1'}, + {0, 0} +}; +static struct file_desc simple_delete_flush_test_file[]= +{ + { TEST_PAGE_SIZE, '\2'}, + {0, 0} +}; + + +/** + @brief Dummy pagecache callback. +*/ + +static my_bool +dummy_callback(uchar *page __attribute__((unused)), + pgcache_page_no_t page_no __attribute__((unused)), + uchar* data_ptr __attribute__((unused))) +{ + return 0; +} + + +/** + @brief Dummy pagecache callback. +*/ + +static void +dummy_fail_callback(uchar* data_ptr __attribute__((unused))) +{ + return; +} + + +/* + Recreate and reopen a file for test + + SYNOPSIS + reset_file() + file File to reset + file_name Path (and name) of file which should be reset +*/ + +void reset_file(PAGECACHE_FILE *file, const char *file_name) +{ + flush_pagecache_blocks(&pagecache, file, FLUSH_RELEASE); + if (my_close(file->file, MYF(MY_WME))) + exit(1); + my_delete(file_name, MYF(MY_WME)); + if ((file->file= my_open(file_name, + O_CREAT | O_TRUNC | O_RDWR, MYF(0))) == -1) + { + diag("Got error during %s creation from open() (errno: %d)\n", + file_name, my_errno); + exit(1); + } +} + +/* + Write then read page, check file on disk +*/ + +int simple_read_write_test() +{ + unsigned char *buffw= malloc(TEST_PAGE_SIZE); + unsigned char *buffr= malloc(TEST_PAGE_SIZE); + int res; + DBUG_ENTER("simple_read_write_test"); + bfill(buffw, TEST_PAGE_SIZE, '\1'); + pagecache_write(&pagecache, &file1, 0, 3, buffw, + PAGECACHE_PLAIN_PAGE, + PAGECACHE_LOCK_LEFT_UNLOCKED, + PAGECACHE_PIN_LEFT_UNPINNED, + PAGECACHE_WRITE_DELAY, + 0, LSN_IMPOSSIBLE); + pagecache_read(&pagecache, &file1, 0, 3, buffr, + PAGECACHE_PLAIN_PAGE, + PAGECACHE_LOCK_LEFT_UNLOCKED, + 0); + ok((res= test(memcmp(buffr, buffw, TEST_PAGE_SIZE) == 0)), + "Simple write-read page "); + if (flush_pagecache_blocks(&pagecache, &file1, FLUSH_FORCE_WRITE)) + { + diag("Got error during flushing pagecache\n"); + exit(1); + } + ok((res&= test(test_file(file1, file1_name, TEST_PAGE_SIZE, TEST_PAGE_SIZE, + simple_read_write_test_file))), + "Simple write-read page file"); + if (res) + reset_file(&file1, file1_name); + free(buffw); + free(buffr); + DBUG_RETURN(res); +} + + +/* + Prepare page, then read (and lock), change (write new value and unlock), + then check the page in the cache and on the disk +*/ +int simple_read_change_write_read_test() +{ + unsigned char *buffw= malloc(TEST_PAGE_SIZE); + unsigned char *buffr= malloc(TEST_PAGE_SIZE); + int res, res2; + DBUG_ENTER("simple_read_change_write_read_test"); + + /* prepare the file */ + bfill(buffw, TEST_PAGE_SIZE, '\1'); + pagecache_write(&pagecache, &file1, 0, 3, buffw, + PAGECACHE_PLAIN_PAGE, + PAGECACHE_LOCK_LEFT_UNLOCKED, + PAGECACHE_PIN_LEFT_UNPINNED, + PAGECACHE_WRITE_DELAY, + 0, LSN_IMPOSSIBLE); + if (flush_pagecache_blocks(&pagecache, &file1, FLUSH_FORCE_WRITE)) + { + diag("Got error during flushing pagecache\n"); + exit(1); + } + /* test */ + pagecache_read(&pagecache, &file1, 0, 3, buffw, + PAGECACHE_PLAIN_PAGE, + PAGECACHE_LOCK_WRITE, + 0); + bfill(buffw, TEST_PAGE_SIZE/2, '\65'); + pagecache_write(&pagecache, &file1, 0, 3, buffw, + PAGECACHE_PLAIN_PAGE, + PAGECACHE_LOCK_WRITE_UNLOCK, + PAGECACHE_UNPIN, + PAGECACHE_WRITE_DELAY, + 0, LSN_IMPOSSIBLE); + + pagecache_read(&pagecache, &file1, 0, 3, buffr, + PAGECACHE_PLAIN_PAGE, + PAGECACHE_LOCK_LEFT_UNLOCKED, + 0); + ok((res= test(memcmp(buffr, buffw, TEST_PAGE_SIZE) == 0)), + "Simple read-change-write-read page "); + DBUG_ASSERT(pagecache.blocks_changed == 1); + if (flush_pagecache_blocks(&pagecache, &file1, FLUSH_FORCE_WRITE)) + { + diag("Got error during flushing pagecache\n"); + exit(1); + } + DBUG_ASSERT(pagecache.blocks_changed == 0); + ok((res2= test(test_file(file1, file1_name, TEST_PAGE_SIZE, TEST_PAGE_SIZE, + simple_read_change_write_read_test_file))), + "Simple read-change-write-read page file"); + if (res && res2) + reset_file(&file1, file1_name); + free(buffw); + free(buffr); + DBUG_RETURN(res && res2); +} + + +/* + Prepare page, read page 0 (and pin) then write page 1 and page 0. + Flush the file (shold flush only page 1 and return 1 (page 0 is + still pinned). + Check file on the disk. + Unpin and flush. + Check file on the disk. +*/ +int simple_pin_test() +{ + unsigned char *buffw= malloc(TEST_PAGE_SIZE); + int res; + DBUG_ENTER("simple_pin_test"); + /* prepare the file */ + bfill(buffw, TEST_PAGE_SIZE, '\1'); + pagecache_write(&pagecache, &file1, 0, 3, buffw, + PAGECACHE_PLAIN_PAGE, + PAGECACHE_LOCK_LEFT_UNLOCKED, + PAGECACHE_PIN_LEFT_UNPINNED, + PAGECACHE_WRITE_DELAY, + 0, LSN_IMPOSSIBLE); + /* test */ + if (flush_pagecache_blocks(&pagecache, &file1, FLUSH_FORCE_WRITE)) + { + diag("Got error during flushing pagecache\n"); + exit(1); + } + pagecache_read(&pagecache, &file1, 0, 3, buffw, + PAGECACHE_PLAIN_PAGE, + PAGECACHE_LOCK_WRITE, + 0); + pagecache_write(&pagecache, &file1, 1, 3, buffw, + PAGECACHE_PLAIN_PAGE, + PAGECACHE_LOCK_LEFT_UNLOCKED, + PAGECACHE_PIN_LEFT_UNPINNED, + PAGECACHE_WRITE_DELAY, + 0, LSN_IMPOSSIBLE); + bfill(buffw + TEST_PAGE_SIZE/2, TEST_PAGE_SIZE/2, ((unsigned char) 129)); + pagecache_write(&pagecache, &file1, 0, 3, buffw, + PAGECACHE_PLAIN_PAGE, + PAGECACHE_LOCK_WRITE_TO_READ, + PAGECACHE_PIN_LEFT_PINNED, + PAGECACHE_WRITE_DELAY, + 0, LSN_IMPOSSIBLE); + /* + We have to get error because one page of the file is pinned, + other page should be flushed + */ + if (!flush_pagecache_blocks(&pagecache, &file1, FLUSH_FORCE_WRITE)) + { + diag("Did not get error in flush_pagecache_blocks\n"); + res= 0; + goto err; + } + ok((res= test(test_file(file1, file1_name, TEST_PAGE_SIZE*2, TEST_PAGE_SIZE*2, + simple_pin_test_file1))), + "Simple pin page file with pin"); + pagecache_unlock(&pagecache, + &file1, + 0, + PAGECACHE_LOCK_READ_UNLOCK, + PAGECACHE_UNPIN, + 0, 0, 0); + if (flush_pagecache_blocks(&pagecache, &file1, FLUSH_FORCE_WRITE)) + { + diag("Got error in flush_pagecache_blocks\n"); + res= 0; + goto err; + } + ok((res&= test(test_file(file1, file1_name, TEST_PAGE_SIZE*2, TEST_PAGE_SIZE, + simple_pin_test_file2))), + "Simple pin page result file"); + if (res) + reset_file(&file1, file1_name); +err: + free(buffw); + DBUG_RETURN(res); +} + +/* + Checks pins without lock. +*/ +int simple_pin_no_lock_test() +{ + unsigned char *buffw= malloc(TEST_PAGE_SIZE); + PAGECACHE_BLOCK_LINK *link; + int res; + DBUG_ENTER("simple_pin_no_lock_test"); + /* prepare the file */ + bfill(buffw, TEST_PAGE_SIZE, '\4'); + pagecache_write(&pagecache, &file1, 0, 3, buffw, + PAGECACHE_PLAIN_PAGE, + PAGECACHE_LOCK_LEFT_UNLOCKED, + PAGECACHE_PIN_LEFT_UNPINNED, + PAGECACHE_WRITE_DELAY, + 0, LSN_IMPOSSIBLE); + /* test */ + if (flush_pagecache_blocks(&pagecache, &file1, FLUSH_FORCE_WRITE)) + { + diag("Got error during flushing pagecache 2\n"); + exit(1); + } + bfill(buffw, TEST_PAGE_SIZE, '\5'); + pagecache_write(&pagecache, &file1, 0, 3, buffw, + PAGECACHE_PLAIN_PAGE, + PAGECACHE_LOCK_LEFT_UNLOCKED, + PAGECACHE_PIN, + PAGECACHE_WRITE_DELAY, + 0, LSN_IMPOSSIBLE); + /* + We have to get error because one page of the file is pinned, + other page should be flushed + */ + if (!flush_pagecache_blocks(&pagecache, &file1, FLUSH_FORCE_WRITE)) + { + diag("Did not get error in flush_pagecache_blocks 2\n"); + res= 0; + goto err; + } + ok((res= test(test_file(file1, file1_name, TEST_PAGE_SIZE, TEST_PAGE_SIZE, + simple_pin_no_lock_test_file1))), + "Simple pin (no lock) page file with pin 2"); + pagecache_unlock(&pagecache, + &file1, + 0, + PAGECACHE_LOCK_LEFT_UNLOCKED, + PAGECACHE_UNPIN, + 0, 0, 0); + if (flush_pagecache_blocks(&pagecache, &file1, FLUSH_FORCE_WRITE)) + { + diag("Got error in flush_pagecache_blocks 2\n"); + res= 0; + goto err; + } + ok((res&= test(test_file(file1, file1_name, TEST_PAGE_SIZE, TEST_PAGE_SIZE, + simple_pin_no_lock_test_file2))), + "Simple pin (no lock) page result file 2"); + + bfill(buffw, TEST_PAGE_SIZE, '\6'); + pagecache_write(&pagecache, &file1, 0, 3, buffw, + PAGECACHE_PLAIN_PAGE, + PAGECACHE_LOCK_WRITE, + PAGECACHE_PIN, + PAGECACHE_WRITE_DELAY, + &link, LSN_IMPOSSIBLE); + pagecache_unlock_by_link(&pagecache, link, + PAGECACHE_LOCK_WRITE_UNLOCK, + PAGECACHE_PIN_LEFT_PINNED, 0, 0, 1, FALSE); + if (!flush_pagecache_blocks(&pagecache, &file1, FLUSH_FORCE_WRITE)) + { + diag("Did not get error in flush_pagecache_blocks 3\n"); + res= 0; + goto err; + } + ok((res= test(test_file(file1, file1_name, TEST_PAGE_SIZE, TEST_PAGE_SIZE, + simple_pin_no_lock_test_file2))), + "Simple pin (no lock) page file with pin 3"); + pagecache_unpin_by_link(&pagecache, link, 0); + if (flush_pagecache_blocks(&pagecache, &file1, FLUSH_FORCE_WRITE)) + { + diag("Got error in flush_pagecache_blocks 3\n"); + res= 0; + goto err; + } + ok((res&= test(test_file(file1, file1_name, TEST_PAGE_SIZE, TEST_PAGE_SIZE, + simple_pin_no_lock_test_file3))), + "Simple pin (no lock) page result file 3"); + if (res) + reset_file(&file1, file1_name); +err: + free(buffw); + DBUG_RETURN(res); +} +/* + Prepare page, write new value, then delete page from cache without flush, + on the disk should be page with old content written during preparation +*/ + +int simple_delete_forget_test() +{ + unsigned char *buffw= malloc(TEST_PAGE_SIZE); + unsigned char *buffr= malloc(TEST_PAGE_SIZE); + int res; + DBUG_ENTER("simple_delete_forget_test"); + /* prepare the file */ + bfill(buffw, TEST_PAGE_SIZE, '\1'); + pagecache_write(&pagecache, &file1, 0, 3, buffw, + PAGECACHE_PLAIN_PAGE, + PAGECACHE_LOCK_LEFT_UNLOCKED, + PAGECACHE_PIN_LEFT_UNPINNED, + PAGECACHE_WRITE_DELAY, + 0, LSN_IMPOSSIBLE); + flush_pagecache_blocks(&pagecache, &file1, FLUSH_FORCE_WRITE); + /* test */ + bfill(buffw, TEST_PAGE_SIZE, '\2'); + pagecache_write(&pagecache, &file1, 0, 3, buffw, + PAGECACHE_PLAIN_PAGE, + PAGECACHE_LOCK_LEFT_UNLOCKED, + PAGECACHE_PIN_LEFT_UNPINNED, + PAGECACHE_WRITE_DELAY, + 0, LSN_IMPOSSIBLE); + pagecache_delete(&pagecache, &file1, 0, + PAGECACHE_LOCK_WRITE, 0); + flush_pagecache_blocks(&pagecache, &file1, FLUSH_FORCE_WRITE); + ok((res= test(test_file(file1, file1_name, TEST_PAGE_SIZE, TEST_PAGE_SIZE, + simple_delete_forget_test_file))), + "Simple delete-forget page file"); + if (res) + reset_file(&file1, file1_name); + free(buffw); + free(buffr); + DBUG_RETURN(res); +} + +/* + Prepare page with locking, write new content to the page, + delete page with flush and on existing lock, + check that page on disk contain new value. +*/ + +int simple_delete_flush_test() +{ + unsigned char *buffw= malloc(TEST_PAGE_SIZE); + unsigned char *buffr= malloc(TEST_PAGE_SIZE); + PAGECACHE_BLOCK_LINK *link; + int res; + DBUG_ENTER("simple_delete_flush_test"); + /* prepare the file */ + bfill(buffw, TEST_PAGE_SIZE, '\1'); + pagecache_write(&pagecache, &file1, 0, 3, buffw, + PAGECACHE_PLAIN_PAGE, + PAGECACHE_LOCK_WRITE, + PAGECACHE_PIN, + PAGECACHE_WRITE_DELAY, + &link, LSN_IMPOSSIBLE); + flush_pagecache_blocks(&pagecache, &file1, FLUSH_FORCE_WRITE); + /* test */ + bfill(buffw, TEST_PAGE_SIZE, '\2'); + pagecache_write(&pagecache, &file1, 0, 3, buffw, + PAGECACHE_PLAIN_PAGE, + PAGECACHE_LOCK_LEFT_WRITELOCKED, + PAGECACHE_PIN_LEFT_PINNED, + PAGECACHE_WRITE_DELAY, + 0, LSN_IMPOSSIBLE); + if (pagecache_delete_by_link(&pagecache, link, + PAGECACHE_LOCK_LEFT_WRITELOCKED, 1)) + { + diag("simple_delete_flush_test: error during delete"); + exit(1); + } + flush_pagecache_blocks(&pagecache, &file1, FLUSH_FORCE_WRITE); + ok((res= test(test_file(file1, file1_name, TEST_PAGE_SIZE, TEST_PAGE_SIZE, + simple_delete_flush_test_file))), + "Simple delete flush (link) page file"); + if (res) + reset_file(&file1, file1_name); + free(buffw); + free(buffr); + DBUG_RETURN(res); +} + + +/* + write then read file bigger then cache +*/ + +int simple_big_test() +{ + unsigned char *buffw= (unsigned char *) my_malloc(TEST_PAGE_SIZE, MYF(MY_WME)); + unsigned char *buffr= (unsigned char *) my_malloc(TEST_PAGE_SIZE, MYF(MY_WME)); + struct file_desc *desc= ((struct file_desc *) + my_malloc((PCACHE_SIZE/(TEST_PAGE_SIZE/2) + 1) * + sizeof(struct file_desc), MYF(MY_WME))); + int res, i; + DBUG_ENTER("simple_big_test"); + + /* prepare the file twice larger then cache */ + for (i= 0; i < PCACHE_SIZE/(TEST_PAGE_SIZE/2); i++) + { + bfill(buffw, TEST_PAGE_SIZE, (unsigned char) (i & 0xff)); + desc[i].length= TEST_PAGE_SIZE; + desc[i].content= (i & 0xff); + pagecache_write(&pagecache, &file1, i, 3, buffw, + PAGECACHE_PLAIN_PAGE, + PAGECACHE_LOCK_LEFT_UNLOCKED, + PAGECACHE_PIN_LEFT_UNPINNED, + PAGECACHE_WRITE_DELAY, + 0, LSN_IMPOSSIBLE); + } + desc[i].length= 0; + desc[i].content= '\0'; + ok(1, "Simple big file write"); + /* check written pages sequentally read */ + for (i= 0; i < PCACHE_SIZE/(TEST_PAGE_SIZE/2); i++) + { + int j; + pagecache_read(&pagecache, &file1, i, 3, buffr, + PAGECACHE_PLAIN_PAGE, + PAGECACHE_LOCK_LEFT_UNLOCKED, + 0); + for(j= 0; j < TEST_PAGE_SIZE; j++) + { + if (buffr[j] != (i & 0xff)) + { + diag("simple_big_test seq: page %u byte %u mismatch\n", i, j); + res= 0; + goto err; + } + } + } + ok(1, "Simple big file sequential read"); + /* chack random reads */ + for (i= 0; i < PCACHE_SIZE/(TEST_PAGE_SIZE); i++) + { + int j, page; + page= rand() % (PCACHE_SIZE/(TEST_PAGE_SIZE/2)); + pagecache_read(&pagecache, &file1, page, 3, buffr, + PAGECACHE_PLAIN_PAGE, + PAGECACHE_LOCK_LEFT_UNLOCKED, + 0); + for(j= 0; j < TEST_PAGE_SIZE; j++) + { + if (buffr[j] != (page & 0xff)) + { + diag("simple_big_test rnd: page %u byte %u mismatch\n", page, j); + res= 0; + goto err; + } + } + } + ok(1, "Simple big file random read"); + flush_pagecache_blocks(&pagecache, &file1, FLUSH_FORCE_WRITE); + + ok((res= test(test_file(file1, file1_name, PCACHE_SIZE*2, TEST_PAGE_SIZE, + desc))), + "Simple big file"); + if (res) + reset_file(&file1, file1_name); + +err: + my_free(buffw, 0); + my_free(buffr, 0); + my_free(desc, 0); + DBUG_RETURN(res); +} + + +/* + Thread function +*/ + +static void *test_thread(void *arg) +{ +#ifndef DBUG_OFF + int param= *((int*) arg); +#endif + + my_thread_init(); + { + DBUG_ENTER("test_thread"); + DBUG_PRINT("enter", ("param: %d", param)); + + if (!simple_read_write_test() || + !simple_read_change_write_read_test() || + !simple_pin_test() || + !simple_pin_no_lock_test() || + !simple_delete_forget_test() || + !simple_delete_flush_test()) + exit(1); + + SKIP_BIG_TESTS(4) + { + if (!simple_big_test()) + exit(1); + } + + DBUG_PRINT("info", ("Thread %s ended\n", my_thread_name())); + pthread_mutex_lock(&LOCK_thread_count); + thread_count--; + VOID(pthread_cond_signal(&COND_thread_count)); /* Tell main we are ready */ + pthread_mutex_unlock(&LOCK_thread_count); + free((uchar*) arg); + my_thread_end(); + DBUG_RETURN(0); + } +} + + +int main(int argc __attribute__((unused)), + char **argv __attribute__((unused))) +{ + pthread_t tid; + pthread_attr_t thr_attr; + int *param, error, pagen; + File tmp_file; + MY_INIT(argv[0]); + +#ifndef DBUG_OFF +#if defined(__WIN__) + default_dbug_option= "d:t:i:O,\\test_pagecache_single.trace"; +#else + default_dbug_option= "d:t:i:o,/tmp/test_pagecache_single.trace"; +#endif + if (argc > 1) + { + DBUG_SET(default_dbug_option); + DBUG_SET_INITIAL(default_dbug_option); + } +#endif + { + DBUG_ENTER("main"); + DBUG_PRINT("info", ("Main thread: %s\n", my_thread_name())); + + plan(16); + SKIP_BIG_TESTS(16) + { + + if ((tmp_file= my_open(file2_name, O_CREAT | O_TRUNC | O_RDWR, + MYF(MY_WME))) < 0) + exit(1); + + if ((file1.file= my_open(file1_name, + O_CREAT | O_TRUNC | O_RDWR, MYF(0))) == -1) + { + fprintf(stderr, "Got error during file1 creation from open() (errno: %d)\n", + errno); + exit(1); + } + pagecache_file_init(file1, &dummy_callback, &dummy_callback, + &dummy_fail_callback, &dummy_callback, NULL); + my_close(tmp_file, MYF(0)); + my_delete(file2_name, MYF(0)); + + DBUG_PRINT("info", ("file1: %d", file1.file)); + if (my_chmod(file1_name, S_IRWXU | S_IRWXG | S_IRWXO, MYF(MY_WME))) + exit(1); + my_pwrite(file1.file, (const uchar*)"test file", 9, 0, MYF(MY_WME)); + + if ((error= pthread_cond_init(&COND_thread_count, NULL))) + { + fprintf(stderr, "Got error: %d from pthread_cond_init (errno: %d)\n", + error, errno); + exit(1); + } + if ((error= pthread_mutex_init(&LOCK_thread_count, MY_MUTEX_INIT_FAST))) + { + fprintf(stderr, "Got error: %d from pthread_cond_init (errno: %d)\n", + error, errno); + exit(1); + } + + if ((error= pthread_attr_init(&thr_attr))) + { + fprintf(stderr,"Got error: %d from pthread_attr_init (errno: %d)\n", + error,errno); + exit(1); + } + if ((error= pthread_attr_setdetachstate(&thr_attr, PTHREAD_CREATE_DETACHED))) + { + fprintf(stderr, + "Got error: %d from pthread_attr_setdetachstate (errno: %d)\n", + error,errno); + exit(1); + } + +#ifdef HAVE_THR_SETCONCURRENCY + VOID(thr_setconcurrency(2)); +#endif + + if ((pagen= init_pagecache(&pagecache, PCACHE_SIZE, 0, 0, + TEST_PAGE_SIZE, MYF(MY_WME))) == 0) + { + fprintf(stderr,"Got error: init_pagecache() (errno: %d)\n", + errno); + exit(1); + } + DBUG_PRINT("info", ("Page cache %d pages", pagen)); + + pthread_mutex_lock(&LOCK_thread_count); + param=(int*) malloc(sizeof(int)); + *param= 1; + if ((error= pthread_create(&tid, &thr_attr, test_thread, (void*) param))) + { + fprintf(stderr,"Got error: %d from pthread_create (errno: %d)\n", + error,errno); + exit(1); + } + thread_count++; + DBUG_PRINT("info", ("Thread started")); + pthread_mutex_unlock(&LOCK_thread_count); + + pthread_attr_destroy(&thr_attr); + + pthread_mutex_lock(&LOCK_thread_count); + while (thread_count) + { + if ((error= pthread_cond_wait(&COND_thread_count,&LOCK_thread_count))) + fprintf(stderr,"Got error: %d from pthread_cond_wait\n",error); + } + pthread_mutex_unlock(&LOCK_thread_count); + DBUG_PRINT("info", ("thread ended")); + + end_pagecache(&pagecache, 1); + DBUG_PRINT("info", ("Page cache ended")); + + if (my_close(file1.file, MYF(MY_WME))) + exit(1); + + my_delete(file1_name, MYF(0)); + + } /* SKIP_BIG_TESTS */ + DBUG_PRINT("info", ("file1 (%d) closed", file1.file)); + DBUG_PRINT("info", ("Program end")); + + my_end(0); + + } + return exit_status(); +} diff --git a/storage/maria/unittest/ma_test_all-t b/storage/maria/unittest/ma_test_all-t new file mode 100755 index 00000000000..8e52f42b483 --- /dev/null +++ b/storage/maria/unittest/ma_test_all-t @@ -0,0 +1,710 @@ +#!/usr/bin/env perl +# +# Run various unit tests. +# + +use Getopt::Long; +use File::Basename; + +$|= 1; +$^W = 1; # warnings, because env cannot parse 'perl -w' +$VER= "1.4"; + +$opt_version= 0; +$opt_help= 0; +$opt_verbose= 0; +$opt_abort_on_error= 0; +$opt_valgrind= "valgrind --alignment=8 --leak-check=yes"; +$opt_silent= "-s"; +$opt_number_of_tests= 0; +$opt_run_tests= undef(); + +my $maria_path; # path to "storage/maria" +my $maria_exe_path; # path to executables (ma_test1, maria_chk etc) +my $my_progname= $0; +$my_progname=~ s/.*[\/]//; +my $runtime_error= 0; # Return 1 if error(s) occur during run +my $NEW_TEST= 0; # Test group separator in an array of tests +my $test_begin= 0; +my $test_end= 0; +my $test_counter= 0; + +run_tests(); + +#### +#### Initialise variables, clean temporary files and run the tests +#### + +sub run_tests +{ + my $nr_tests= 0; + my $flag_exit= 0; + + if (!GetOptions("help" => \$opt_help, + "version" => \$opt_version, + "verbose" => \$opt_verbose, + "abort-on-error" => \$opt_abort_on_error, + "valgrind=s" => \$opt_valgrind, + "silent=s" => \$opt_silent, + "number-of-tests" => \$opt_number_of_tests, + "run-tests=s" => \$opt_run_tests, + "start-from=s" => \$opt_run_tests)) + { + $flag_exit= 1; + } + if ($opt_version) + { + print "$my_progname version $VER\n"; + exit(0); + } + $maria_path= dirname($0) . "/.."; + + my $suffix= ( $^O =~ /win/i && $^O !~ /darwin/i ) ? ".exe" : ""; + $maria_exe_path= "$maria_path/release"; + # we use -f, sometimes -x is unexpectedly false in Cygwin + if ( ! -f "$maria_exe_path/ma_test1$suffix" ) + { + $maria_exe_path= "$maria_path/relwithdebinfo"; + if ( ! -f "$maria_exe_path/ma_test1$suffix" ) + { + $maria_exe_path= "$maria_path/debug"; + if ( ! -f "$maria_exe_path/ma_test1$suffix" ) + { + $maria_exe_path= $maria_path; + if ( ! -f "$maria_exe_path/ma_test1$suffix" ) + { + die("Cannot find ma_test1 executable\n"); + } + } + } + } + + usage() if ($opt_help || $flag_exit); + + # + # IMPORTANT: If you modify this file, please read this: + # + # Count total number of tests. Make sure that the functions return + # number of unit tests correctly, e.g. calls to ok(). The last argument + # for each function is a flag counter and will return the number of + # unit tests in each. Please see comments on function ok() at the end. + # + # If you modify any functions or add any new ones, please make sure the + # unit tests are appropriately detected here. A wrong count will + # make the unit test fail during 'make test'. $nr_tests must be right. + # + + $nr_tests+= run_check_tests(0, 0, 0, 0, 1) * 5; # + $nr_tests+= run_repair_tests(0, 0, 0, 0, 1) * 5; # called 4 times + $nr_tests+= run_pack_tests(0, 0, 0, 0, 1) * 5; # + $nr_tests+= run_tests_on_warnings_and_errors(0, 0, 0, 1); + $nr_tests+= run_ma_test_recovery(0, 1); + $nr_tests+= run_tests_on_clrs(0, 0, 1); + + if ($opt_number_of_tests) + { + print "Total number of tests is $nr_tests\n"; + exit(0); + } + + if (defined($opt_run_tests)) + { + if ($opt_run_tests =~ m/^(\d+)$/ || + $opt_run_tests =~ m/^(\d+)\.+$/) + { + $test_begin= $1; + } + elsif ($opt_run_tests =~ m/^(\d+)\.+(\d+)$/) + { + $test_begin= $1; + $test_end= $2; + } + else + { + print "Wrong syntax for option --run-tests=$opt_run_tests\n"; + print "Please use --run-tests=<begin>..<end>\nwhere 'begin' is the "; + print "first test to be run and 'end' is the last.\n"; + exit(1); + } + if ($test_end > $nr_tests) + { + print "Test range ($test_begin..$test_end) out of range. "; + print "There are only $nr_tests in the test suite.\n"; + exit(1); + } + $test_begin++ if (!$test_begin); # Handle zero, if user gave that + if ($test_end && $test_begin > $test_end) + { + print "Bad test range ($test_begin..$test_end)\n"; + exit(1); + } + # Now adjust number of tests + $nr_tests= ($test_end ? $test_end : $nr_tests) - $test_begin + 1; + } + + # + # clean-up + # + + unlink <*.TMD maria_log*>; # Delete temporary files + + # + # Run tests + # + + if (!$opt_verbose) + { + print "1..$nr_tests\n"; + } + else + { + print "Total tests: $nr_tests\n"; + } + + if ($opt_verbose) + { + print "Running tests with dynamic row format\n" + } + run_check_tests($suffix, $opt_silent, "", $opt_verbose, 0); + run_repair_tests($suffix, $opt_silent, "", $opt_verbose, 0); + run_pack_tests($suffix, $opt_silent, "", $opt_verbose, 0); + + if ($opt_verbose) + { + print "\nRunning tests with static row format\n"; + } + run_check_tests($suffix, $opt_silent, "-S", $opt_verbose, 0); + run_repair_tests($suffix, $opt_silent, "-S", $opt_verbose, 0); + run_pack_tests($suffix, $opt_silent, "-S", $opt_verbose, 0); + + if ($opt_verbose) + { + print "\nRunning tests with block row format\n"; + } + run_check_tests($suffix, $opt_silent, "-M", $opt_verbose, 0); + run_repair_tests($suffix, $opt_silent, "-M", $opt_verbose, 0); + run_pack_tests($suffix, $opt_silent, "-M", $opt_verbose, 0); + + if ($opt_verbose) + { + print "\nRunning tests with block row format and transactions\n"; + } + run_check_tests($suffix, $opt_silent, "-M -T", $opt_verbose, 0); + run_repair_tests($suffix, $opt_silent, "-M -T", $opt_verbose, 0); + run_pack_tests($suffix, $opt_silent, "-M -T", $opt_verbose, 0); + + if ($opt_verbose) + { + print "\nRunning tests with block row format, transactions and versioning\n"; + } + run_check_tests($suffix, $opt_silent, "-M -T -C", $opt_verbose, 0); + run_repair_tests($suffix, $opt_silent, "-M -T -C", $opt_verbose, 0); + run_pack_tests($suffix, $opt_silent, "-M -T -C", $opt_verbose, 0); + + + if ($opt_verbose) + { + print "\nRunning tests with warnings and recovery\n"; + } + run_tests_on_warnings_and_errors($suffix, $opt_silent, $opt_verbose, 0); + run_ma_test_recovery($opt_verbose, 0); + run_tests_on_clrs($suffix, $opt_verbose, 0); + + exit($runtime_error); +} + +#### +#### regular tests +#### + +sub run_check_tests +{ + my ($suffix, $silent, $row_type, $verbose, $count)= @_; + my ($i, $nr_tests); + my @ma_test1_opt= ( ["","-se"], + ["-N","-se"], + ["-P --checksum","-se"], + ["-P -N","-se"], + ["-B -N -R2","-sm"], + ["-a -k 480 --unique","-sm"], + ["-a -N -R1 ","-sm"], + ["-p","-sm"], + ["-p -N --unique","-sm"], + ["-p -N --key_length=127 --checksum","-sm"], + ["-p -N --key_length=128","-sm"], + ["-p --key_length=480","-sm"], + ["-a -B","-sm"], + ["-a -B --key_length=64 --unique","-sm"], + ["-a -B -k 480 --checksum","-sm"], + ["-a -B -k 480 -N --unique --checksum","-sm"], + ["-a -m","-sm"], + ["-a -m -P --unique --checksum","-sm"], + ["-a -m -P --key_length=480 --key_cache","-sm"], + ["-m -p","-sm"], + ["-w --unique","-sm"], + ["-a -w --key_length=64 --checksum","-sm"], + ["-a -w -N --key_length=480","-sm"], + ["-a -w --key_length=480 --checksum","-sm"], + ["-a -b -N","-sm"], + ["-a -b --key_length=480","-sm"], + ["-p -B --key_length=480","-sm"], + ["--checksum --unique","-se"], + ["--unique","-se"], + ["--key_multiple -N -S","-sm"], + ["--key_multiple -a -p --key_length=480","-sm"], + ["--key_multiple -a -B --key_length=480","-sm"], + ["--key_multiple -P -S","-sm"] ); + my @ma_test2_opt= ( ["-L -K -W -P","-sm"], + ["-L -K -W -P -A","-sm"], + ["-L -K -W -P -b32768", "-sm"], + ["-L -K -W -P -M -T -c -b32768 -t4 -m300", "-sm"], + ["-L -K -P -R3 -m50 -b1000000", "-sm"], + ["-L -B","-sm"], + ["-D -B -c","-sm"], + ["-m10000 -e4096 -K","-sm"], + ["-m10000 -e8192 -K","-sm"], + ["-m10000 -e16384 -E16384 -K -L","-sm"], + ["-L -K -W -P -b32768", "-se"], + ["-c -b65000","-se"] ); + my @ma_rt_test_opt= ( ); # (["--checksum", "-se"] ); + + + if ($count) + { + $nr_tests= 2; # Number of tests outside loops + for ($i= 0; defined($ma_test1_opt[$i]); $i++) { $nr_tests+=2; } + for ($i= 0; defined($ma_test2_opt[$i]); $i++) { $nr_tests+=2; } + for ($i= 0; defined($ma_rt_test_opt[$i]); $i++) { $nr_tests+=2; } + return $nr_tests; + } + + for ($i= 0; defined($ma_test1_opt[$i]); $i++) + { + unlink <maria_log_control maria_log.*>; + ok("$maria_exe_path/ma_test1$suffix $silent $ma_test1_opt[$i][0] $row_type", + $verbose, $i + 1); + ok("$maria_exe_path/maria_chk$suffix $ma_test1_opt[$i][1] test1", + $verbose, $i + 1); + } + # + # These tests are outside the loops. Make sure to include them in + # nr_tests manually + # + ok("$maria_exe_path/maria_pack$suffix --force -s test1", $verbose, 0); + ok("$maria_exe_path/maria_chk$suffix -ess test1", $verbose, 0); + + for ($i= 0; defined($ma_test2_opt[$i]); $i++) + { + unlink <maria_log_control maria_log.*>; + ok("$maria_exe_path/ma_test2$suffix $silent $ma_test2_opt[$i][0] $row_type", + $verbose, $i + 1); + ok("$maria_exe_path/maria_chk$suffix $ma_test2_opt[$i][1] test2", + $verbose, $i + 1); + } + + for ($i= 0; defined($ma_rt_test_opt[$i]); $i++) + { + unlink <maria_log_control maria_log.*>; + ok("$maria_exe_path/ma_rt_test$suffix $silent $ma_rt_test_opt[$i][0] $row_type", + $verbose, $i + 1); + ok("$maria_exe_path/maria_chk$suffix $ma_rt_test_opt[$i][1] rt_test", + $verbose, $i + 1); + } + + unlink <maria_log_control maria_log.*>; + + return 0; +} + +#### +#### repair tests +#### + +sub run_repair_tests() +{ + my ($suffix, $silent, $row_type, $verbose, $count)= @_; + my ($i); + + my @t= ($NEW_TEST, + "$maria_exe_path/ma_test1$suffix $silent --checksum $row_type", + "$maria_exe_path/maria_chk$suffix -se test1", + "$maria_exe_path/maria_chk$suffix --silent -re --transaction-log test1", + "$maria_exe_path/maria_chk$suffix -rs test1", + "$maria_exe_path/maria_chk$suffix -se test1", + "$maria_exe_path/maria_chk$suffix -rqs test1", + "$maria_exe_path/maria_chk$suffix -se test1", + "$maria_exe_path/maria_chk$suffix -rs --correct-checksum test1", + "$maria_exe_path/maria_chk$suffix -se test1", + "$maria_exe_path/maria_chk$suffix -rqs --correct-checksum test1", + "$maria_exe_path/maria_chk$suffix -se test1", + "$maria_exe_path/maria_chk$suffix -ros --correct-checksum test1", + "$maria_exe_path/maria_chk$suffix -se test1", + "$maria_exe_path/maria_chk$suffix -rqos --correct-checksum test1", + "$maria_exe_path/maria_chk$suffix -se test1", + "$maria_exe_path/maria_chk$suffix -sz test1", + "$maria_exe_path/maria_chk$suffix -se test1", + "$maria_exe_path/ma_test2$suffix $silent -c -d1 $row_type", + "$maria_exe_path/maria_chk$suffix -s --parallel-recover test2", + "$maria_exe_path/maria_chk$suffix -se test2", + "$maria_exe_path/maria_chk$suffix -s --parallel-recover --quick test2", + "$maria_exe_path/maria_chk$suffix -se test2", + "$maria_exe_path/ma_test2$suffix $silent -c $row_type", + "$maria_exe_path/maria_chk$suffix -se test2", + "$maria_exe_path/maria_chk$suffix -sr test2", + "$maria_exe_path/maria_chk$suffix -se test2", + "$maria_exe_path/ma_test2$suffix $silent -c -t4 -b32768 $row_type", + "$maria_exe_path/maria_chk$suffix -s --zerofill test1", + "$maria_exe_path/maria_chk$suffix -se test1" + ); + + return &count_tests(\@t) if ($count); + &run_test_bunch(\@t, $verbose, 0); + return 0; +} + +#### +#### pack tests +#### + +sub run_pack_tests() +{ + my ($suffix, $silent, $row_type, $verbose, $count)= @_; + my ($i); + + my @t= ($NEW_TEST, + "$maria_exe_path/ma_test1$suffix $silent --checksum $row_type", + "$maria_exe_path/maria_pack$suffix --force -s test1", + "$maria_exe_path/maria_chk$suffix -ess test1", + "$maria_exe_path/maria_chk$suffix -rqs test1", + "$maria_exe_path/maria_chk$suffix -es test1", + "$maria_exe_path/maria_chk$suffix -rs test1", + "$maria_exe_path/maria_chk$suffix -es test1", + "$maria_exe_path/maria_chk$suffix -rus test1", + "$maria_exe_path/maria_chk$suffix -es test1", + $NEW_TEST, + "$maria_exe_path/ma_test1$suffix $silent --checksum $row_type", + "$maria_exe_path/maria_pack$suffix --force -s test1", + "$maria_exe_path/maria_chk$suffix -rus --safe-recover test1", + "$maria_exe_path/maria_chk$suffix -es test1", + $NEW_TEST, + "$maria_exe_path/ma_test1$suffix $silent --checksum -S $row_type", + "$maria_exe_path/maria_chk$suffix -se test1", + "$maria_exe_path/maria_chk$suffix -ros test1", + "$maria_exe_path/maria_chk$suffix -rqs test1", + "$maria_exe_path/maria_chk$suffix -se test1", + $NEW_TEST, + "$maria_exe_path/maria_pack$suffix --force -s test1", + "$maria_exe_path/maria_chk$suffix -rqs test1", + "$maria_exe_path/maria_chk$suffix -es test1", + "$maria_exe_path/maria_chk$suffix -rus test1", + "$maria_exe_path/maria_chk$suffix -es test1", + $NEW_TEST, + "$maria_exe_path/ma_test2$suffix $silent -c -d1 $row_type", + "$maria_exe_path/maria_chk$suffix -s --parallel-recover test2", + "$maria_exe_path/maria_chk$suffix -se test2", + "$maria_exe_path/maria_chk$suffix -s --unpack --parallel-recover test2", + "$maria_exe_path/maria_chk$suffix -se test2", + "$maria_exe_path/maria_pack$suffix --force -s test1", + "$maria_exe_path/maria_chk$suffix -s --unpack --parallel-recover test2", + "$maria_exe_path/maria_chk$suffix -se test2", + $NEW_TEST, + "$maria_exe_path/ma_test1$suffix $silent -c $row_type", + "cp test1.MAD test2.MAD", + "cp test1.MAI test2.MAI", + "$maria_exe_path/maria_pack$suffix --force -s --join=test3 test1 test2", + "$maria_exe_path/maria_chk -s test3", + "$maria_exe_path/maria_chk -s --safe-recover test3", + "$maria_exe_path/maria_chk -s test3" + ); + + return &count_tests(\@t) if ($count); + &run_test_bunch(\@t, $verbose, 0); + return 0; +} + +#### +#### Tests that gives warnings or errors +#### + +sub run_tests_on_warnings_and_errors +{ + my ($suffix, $silent, $verbose, $count)= @_; + my ($com); + + return 9 if ($count); # Number of tests in this function, e.g. calls to ok() + + ok("$maria_exe_path/ma_test2$suffix $silent -L -K -W -P -S -R1 -m500", + $verbose, 0); + ok("$maria_exe_path/maria_chk$suffix -sm test2", $verbose, 0); + # ma_test2$suffix $silent -L -K -R1 -m2000 ; Should give error 135\n + # In the following a failure is a success and success is a failure + $com= "$maria_exe_path/ma_test2$suffix $silent -L -K -R1 -m2000 "; + $com.= ">ma_test2_message.txt 2>&1"; + ok($com, $verbose, 0, 1); + ok("cat ma_test2_message.txt", $verbose, 0); + ok("grep \"Error: 135\" ma_test2_message.txt > /dev/null", $verbose, 0); + # maria_exe_path/maria_chk$suffix -sm test2 will warn that + # Datafile is almost full + ok("$maria_exe_path/maria_chk$suffix -sm test2 >ma_test2_message.txt 2>&1", + $verbose, 0); + ok("cat ma_test2_message.txt", $verbose, 0); + ok("grep \"warning: Datafile is almost full\" ma_test2_message.txt>/dev/null", + $verbose, 0); + unlink <ma_test2_message.txt>; + ok("$maria_exe_path/maria_chk$suffix -ssm test2", $verbose, 0); + + return 0; +} + +#### +#### Test that removing tables and applying the log leads to identical tables +#### + +sub run_ma_test_recovery +{ + my ($verbose, $count)= @_; + + return 1 if ($count); # Number of tests in this function + ok("$maria_path/unittest/ma_test_recovery.pl", $verbose, 0); + return 0; +} + +#### +#### Tests on CLR's +#### + +sub run_tests_on_clrs +{ + my ($suffix, $verbose, $count)= @_; + my ($i); + + my @t= ($NEW_TEST, + "$maria_exe_path/ma_test2$suffix -s -L -K -W -P -M -T -c -b -t2 -A1", + "cp maria_log_control tmp", + "$maria_exe_path/maria_read_log$suffix -a -s", + "$maria_exe_path/maria_chk$suffix -s -e test2", + "cp tmp/maria_log_control .", + "rm test2.MA?", + "$maria_exe_path/maria_read_log$suffix -a -s", + "$maria_exe_path/maria_chk$suffix -s -e test2", + "rm test2.MA?", + $NEW_TEST, + "$maria_exe_path/ma_test2$suffix -s -L -K -W -P -M -T -c -b -t2 -A1", + "$maria_exe_path/maria_read_log$suffix -a -s", + "$maria_exe_path/maria_chk$suffix -s -e test2", + "rm test2.MA?", + "$maria_exe_path/maria_read_log$suffix -a -s", + "$maria_exe_path/maria_chk$suffix -e -s test2", + "rm test2.MA?", + $NEW_TEST, + "$maria_exe_path/ma_test2$suffix -s -L -K -W -P -M -T -c -b32768 -t4 -A1", + "$maria_exe_path/maria_read_log$suffix -a -s", + "$maria_exe_path/maria_chk$suffix -es test2", + "$maria_exe_path/maria_read_log$suffix -a -s", + "$maria_exe_path/maria_chk$suffix -es test2", + "rm test2.MA?", + "$maria_exe_path/maria_read_log$suffix -a -s", + "$maria_exe_path/maria_chk$suffix -es test2", + "rm test2.MA?" + ); + + return &count_tests(\@t) if ($count); + &run_test_bunch(\@t, $verbose, 1); + return 0; +} + +# +# Print "ok" on success and "not ok" on error +# +# Note: Every time this function is called it will be counted +# as a unit test. +# +# Args: $com: The actual command run. Will be printed on a failure +# $verbose: Be more verbose. +# $iteration: Number of iterations in a loop when the error +# occurred. If not in loop, this should be blank +# (e.g. send zero). +# $expected_error: Optional; put here expected error code. Test +# will pass with this result only. +# +# Return value: Will return 1 on success and 0 on an error +# + +sub ok +{ + my ($com, $verbose, $iteration, $expected_error)= @_; + my ($msg, $output, $err, $len); + + $test_counter++; + if ($test_begin > $test_counter) + { + return 0; + } + if ($test_end && $test_end < $test_counter) + { + exit(0); + } + + $msg= ""; + $expected_error= 0 if (!defined($expected_error)); + + if ($verbose) + { + print "$com "; + } + $output= `$com 2>&1`; + $len= length($com); + if ($verbose) + { + print " " x (62 - $len); + } + $err= $?; + if ((!$err && !$expected_error) || + (($err >> 8) == $expected_error && $expected_error)) + { + print "[ " if ($verbose); + print "ok"; + if ($verbose) + { + print " ]"; + print " " x (5 - length("$test_counter")); + print "$test_counter"; + } + else + { + print " $test_counter - $com" + } + print "\n"; + return 1; + } + print "[ " if ($verbose); + print "not ok"; + print " ]" if ($verbose); + print " $test_counter - $com" unless $verbose; + print "\n"; + if ($verbose && defined($output) && length($output)) + { + print "$output\n"; + } + if (!$verbose) + { + $msg= "\n"; # Get a nicer output in perl unit test mode + } + $msg.= "Failed test '$com' "; + if ($iteration) + { + $msg.= "(loop iteration $iteration.) "; + } + $msg.= "at line "; + $msg.= (caller)[2]; + $msg.= "\n(errcode: $err, test: $test_counter)\n"; + if ($expected_error) + { + $msg.= "Was expecting errcode: $expected_error\n"; + } + warn $msg; + $runtime_error= 1; + if ($opt_abort_on_error) + { + exit 1; + } + return 0; +} + +# +# Print "skip" and the reason +# +# Note: Every time this function is called it will be counted +# as a unit test. +# +# Args: $com: The actual command run. Will be printed on a failure +# $reason: The reason to skip a test +# $verbose: Be more verbose. +# + +sub skip +{ + my ($com, $reason, $verbose)= @_; + + $test_counter++; + return 0 if $test_begin > $test_counter; + exit 0 if $test_end && $test_end < $test_counter; + printf '%-64s[ skipped ]%5d', $com, $test_counter if $verbose; + print "ok $test_counter # skip $reason" unless $verbose; + print "\n"; + return 1; +} + +#### +#### Count tests +#### Arguments: $t: an array of the tests +#### + +sub count_tests +{ + my ($t)= @_; + my ($i, $nr_tests); + + $nr_tests= 0; + for ($i= 0; defined(@$t[$i]); $i++) { $nr_tests++ if (@$t[$i]); } + return $nr_tests; +} + +#### +#### Run a bunch of tests +#### Arguments: $t: an array of the tests +#### $verbose: to be passed to ok() +#### $clear: clear log files if set +#### + +sub run_test_bunch +{ + my ($t, $verbose, $clear)= @_; + my ($i); + + for ($i= 0; defined(@$t[$i]); $i++) + { + if ($clear && @$t[$i] eq $NEW_TEST) + { + unlink <maria_log.* maria_log_control>; + } + if (@$t[$i] ne $NEW_TEST) + { + ok(@$t[$i], $verbose, $i + 1); + } + } +} + +#### +#### usage +#### + +sub usage +{ + print <<EOF; +$my_progname version $VER + +Description: + +Run various Maria related tests. Typically used via make test as a unittest. + +Options +--help Show this help and exit. +--abort-on-error Abort at once in case of error. +--number-of-tests Print the total number of tests and exit. +--run-tests=... Test number(s) that should be run. You can give just + one number or a range. For example 45..89. To run a specific + test alone, for example test 215, use --run-tests=215..215 + Use this option with caution, because some of the tests + might depend on previous ones. +--start-from=... Alias for --run-tests +--silent=... Silent option passed to ma_test* tests ('$opt_silent') +--valgrind=... Options for valgrind. + ('$opt_valgrind') +--verbose Be more verbose. Will print each unittest on a line + and result after. This mode cannot be used with unit.pl + when running in normal unit test mode. +--version Show version number and exit. +EOF + exit(0); +} diff --git a/storage/maria/unittest/ma_test_loghandler-t.c b/storage/maria/unittest/ma_test_loghandler-t.c new file mode 100644 index 00000000000..6027be1e5ea --- /dev/null +++ b/storage/maria/unittest/ma_test_loghandler-t.c @@ -0,0 +1,663 @@ +/* Copyright (C) 2006-2008 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 */ + +#include "../maria_def.h" +#include <stdio.h> +#include <errno.h> +#include <tap.h> +#include "../trnman.h" + +extern my_bool maria_log_remove(); +extern void example_loghandler_init(); + +#ifndef DBUG_OFF +static const char *default_dbug_option; +#endif +static TRN *trn= &dummy_transaction_object; + +#define PCACHE_SIZE (1024*1024*10) + +#define LONG_BUFFER_SIZE (100 * 1024) + +#ifdef LONG_LOG_TEST +#define LOG_FLAGS 0 +#define LOG_FILE_SIZE (1024L*1024L*8) +#define ITERATIONS (1600*4) + +#else +#undef SKIP_BIG_TESTS +#define SKIP_BIG_TESTS(X) /* no-op */ +#define LOG_FLAGS (TRANSLOG_SECTOR_PROTECTION | TRANSLOG_PAGE_CRC) +#define LOG_FILE_SIZE (1024L*1024L*8L) +#define ITERATIONS 1600 +#endif + +/* +#define LOG_FLAGS 0 +#define LOG_FILE_SIZE 1024L*1024L*1024L +#define ITERATIONS 181000 +*/ + +/* +#define LOG_FLAGS 0 +#define LOG_FILE_SIZE 1024L*1024L*3L +#define ITERATIONS 1600 +*/ + +/* +#define LOG_FLAGS 0 +#define LOG_FILE_SIZE 1024L*1024L*100L +#define ITERATIONS 65000 +*/ + +/* + Generate random value in the range (0,LONG_BUFFER_SIZE) +*/ +static uint32 rand_buffer_size() +{ + return (uint32)((ulonglong)rand()*(LONG_BUFFER_SIZE + 1)/RAND_MAX); +} + +/* + Check that the buffer filled correctly + + SYNOPSIS + check_content() + ptr Pointer to the buffer + length length of the buffer + + RETURN + 0 - OK + 1 - Error +*/ + + +static my_bool check_content(uchar *ptr, ulong length) +{ + ulong i; + uchar buff[2]; + for (i= 0; i < length; i++) + { + if (i % 2 == 0) + int2store(buff, i >> 1); + if (ptr[i] != buff[i % 2]) + { + fprintf(stderr, "Byte # %lu is %x instead of %x", + i, (uint) ptr[i], (uint) buff[i % 2]); + return 1; + } + } + return 0; +} + + +/* + Report OK for read operation + + SYNOPSIS + read_ok() + rec the record header +*/ + +void read_ok(TRANSLOG_HEADER_BUFFER *rec) +{ + char buff[80]; + my_snprintf(buff, sizeof(buff), "read record type: %u LSN: (%lu,0x%lx)", + rec->type, LSN_IN_PARTS(rec->lsn)); + ok(1, buff); +} + +/* + Read whole record content, and check content (put with offset) + + SYNOPSIS + read_and_check_content() + rec The record header buffer + buffer The buffer to read the record in + skip Skip this number of bytes ot the record content + + RETURN + 0 - OK + 1 - Error +*/ + +static my_bool read_and_check_content(TRANSLOG_HEADER_BUFFER *rec, + uchar *buffer, uint skip) +{ + DBUG_ASSERT(rec->record_length < LONG_BUFFER_SIZE * 2 + 7 * 2 + 2); + if (translog_read_record(rec->lsn, 0, rec->record_length, buffer, NULL) != + rec->record_length) + return 1; + return check_content(buffer + skip, rec->record_length - skip); +} + + +int main(int argc __attribute__((unused)), char *argv[]) +{ + uint32 i; + uint32 rec_len; + uint pagen; + uchar long_tr_id[6]; + uchar lsn_buff[23]= + { + 0x55, 0xAA, 0x55, 0xAA, 0x55, 0xAA, 0x55, 0xAA, + 0x55, 0xAA, 0x55, 0xAA, 0x55, 0xAA, 0x55, 0xAA, + 0x55, 0xAA, 0x55, 0xAA, 0x55, 0xAA, 0x55 + }; + uchar long_buffer[LONG_BUFFER_SIZE * 2 + LSN_STORE_SIZE * 2 + 2]; + PAGECACHE pagecache; + LSN lsn, lsn_base, first_lsn; + TRANSLOG_HEADER_BUFFER rec; + LEX_CUSTRING parts[TRANSLOG_INTERNAL_PARTS + 3]; + struct st_translog_scanner_data scanner; + int rc; + + MY_INIT(argv[0]); + + if (my_set_max_open_files(100) < 100) + { + fprintf(stderr, "can't allocate 100 file descriptors\n"); + exit(1); + } + bzero(&pagecache, sizeof(pagecache)); + maria_data_root= (char *)"."; + if (maria_log_remove()) + exit(1); + + for (i= 0; i < (LONG_BUFFER_SIZE + LSN_STORE_SIZE * 2 + 2); i+= 2) + { + int2store(long_buffer + i, (i >> 1)); + /* long_buffer[i]= (i & 0xFF); */ + } + + bzero(long_tr_id, 6); +#ifndef DBUG_OFF +#if defined(__WIN__) + default_dbug_option= "d:t:i:O,\\ma_test_loghandler.trace"; +#else + default_dbug_option= "d:t:i:o,/tmp/ma_test_loghandler.trace"; +#endif + if (argc > 1) + { + DBUG_SET(default_dbug_option); + DBUG_SET_INITIAL(default_dbug_option); + } +#endif + + if (ma_control_file_open(TRUE, TRUE)) + { + fprintf(stderr, "Can't init control file (%d)\n", errno); + exit(1); + } + if ((pagen= init_pagecache(&pagecache, PCACHE_SIZE, 0, 0, + TRANSLOG_PAGE_SIZE, 0)) == 0) + { + fprintf(stderr, "Got error: init_pagecache() (errno: %d)\n", errno); + exit(1); + } + if (translog_init_with_table(".", LOG_FILE_SIZE, 50112, 0, &pagecache, + LOG_FLAGS, 0, &translog_example_table_init, + 0)) + { + fprintf(stderr, "Can't init loghandler (%d)\n", errno); + exit(1); + } + /* Suppressing of automatic record writing */ + trn->first_undo_lsn|= TRANSACTION_LOGGED_LONG_ID; + + plan(((ITERATIONS - 1) * 4 + 1)*2 + ITERATIONS - 1 + 1); + + SKIP_BIG_TESTS(((ITERATIONS - 1) * 4 + 1)*2 + ITERATIONS - 1 + 1) + { + + srand(122334817L); + + long_tr_id[5]= 0xff; + + int4store(long_tr_id, 0); + parts[TRANSLOG_INTERNAL_PARTS + 0].str= long_tr_id; + parts[TRANSLOG_INTERNAL_PARTS + 0].length= 6; + trn->short_id= 0; + trn->first_undo_lsn= TRANSACTION_LOGGED_LONG_ID; + if (translog_write_record(&lsn, + LOGREC_FIXED_RECORD_0LSN_EXAMPLE, + trn, NULL, 6, TRANSLOG_INTERNAL_PARTS + 1, + parts, NULL, NULL)) + { + fprintf(stderr, "Can't write record #%lu\n", (ulong) 0); + translog_destroy(); + ok(0, "write LOGREC_FIXED_RECORD_0LSN_EXAMPLE"); + exit(1); + } + ok(1, "write LOGREC_FIXED_RECORD_0LSN_EXAMPLE"); + lsn_base= first_lsn= lsn; + + for (i= 1; i < ITERATIONS; i++) + { + trn->short_id= i % 0xFFFF; + if (i % 2) + { + lsn_store(lsn_buff, lsn_base); + parts[TRANSLOG_INTERNAL_PARTS + 0].str= lsn_buff; + parts[TRANSLOG_INTERNAL_PARTS + 0].length= LSN_STORE_SIZE; + /* check auto-count feature */ + parts[TRANSLOG_INTERNAL_PARTS + 1].str= NULL; + parts[TRANSLOG_INTERNAL_PARTS + 1].length= 0; + if (translog_write_record(&lsn, LOGREC_FIXED_RECORD_1LSN_EXAMPLE, trn, + NULL, LSN_STORE_SIZE, 0, parts, NULL, NULL)) + { + fprintf(stderr, "1 Can't write reference defore record #%lu\n", + (ulong) i); + translog_destroy(); + ok(0, "write LOGREC_FIXED_RECORD_1LSN_EXAMPLE"); + exit(1); + } + ok(1, "write LOGREC_FIXED_RECORD_1LSN_EXAMPLE"); + lsn_store(lsn_buff, lsn_base); + if ((rec_len= rand_buffer_size()) < 12) + rec_len= 12; + parts[TRANSLOG_INTERNAL_PARTS + 0].str= lsn_buff; + parts[TRANSLOG_INTERNAL_PARTS + 0].length= LSN_STORE_SIZE; + parts[TRANSLOG_INTERNAL_PARTS + 1].str= long_buffer; + parts[TRANSLOG_INTERNAL_PARTS + 1].length= rec_len; + /* check record length auto-counting */ + if (translog_write_record(&lsn, + LOGREC_VARIABLE_RECORD_1LSN_EXAMPLE, + trn, NULL, 0, TRANSLOG_INTERNAL_PARTS + 2, + parts, NULL, NULL)) + { + fprintf(stderr, "1 Can't write var reference defore record #%lu\n", + (ulong) i); + translog_destroy(); + ok(0, "write LOGREC_VARIABLE_RECORD_1LSN_EXAMPLE"); + exit(1); + } + ok(1, "write LOGREC_VARIABLE_RECORD_1LSN_EXAMPLE"); + } + else + { + lsn_store(lsn_buff, lsn_base); + lsn_store(lsn_buff + LSN_STORE_SIZE, first_lsn); + parts[TRANSLOG_INTERNAL_PARTS + 0].str= lsn_buff; + parts[TRANSLOG_INTERNAL_PARTS + 0].length= 23; + if (translog_write_record(&lsn, + LOGREC_FIXED_RECORD_2LSN_EXAMPLE, + trn, NULL, 23, TRANSLOG_INTERNAL_PARTS + 1, + parts, NULL, NULL)) + { + fprintf(stderr, "0 Can't write reference defore record #%lu\n", + (ulong) i); + translog_destroy(); + ok(0, "write LOGREC_FIXED_RECORD_2LSN_EXAMPLE"); + exit(1); + } + ok(1, "write LOGREC_FIXED_RECORD_2LSN_EXAMPLE"); + lsn_store(lsn_buff, lsn_base); + lsn_store(lsn_buff + LSN_STORE_SIZE, first_lsn); + if ((rec_len= rand_buffer_size()) < 19) + rec_len= 19; + parts[TRANSLOG_INTERNAL_PARTS + 0].str= lsn_buff; + parts[TRANSLOG_INTERNAL_PARTS + 0].length= 14; + parts[TRANSLOG_INTERNAL_PARTS + 1].str= long_buffer; + parts[TRANSLOG_INTERNAL_PARTS + 1].length= rec_len; + if (translog_write_record(&lsn, + LOGREC_VARIABLE_RECORD_2LSN_EXAMPLE, + trn, NULL, 14 + rec_len, + TRANSLOG_INTERNAL_PARTS + 2, parts, NULL, + NULL)) + { + fprintf(stderr, "0 Can't write var reference defore record #%lu\n", + (ulong) i); + translog_destroy(); + ok(0, "write LOGREC_VARIABLE_RECORD_2LSN_EXAMPLE"); + exit(1); + } + ok(1, "write LOGREC_VARIABLE_RECORD_2LSN_EXAMPLE"); + } + int4store(long_tr_id, i); + parts[TRANSLOG_INTERNAL_PARTS + 0].str= long_tr_id; + parts[TRANSLOG_INTERNAL_PARTS + 0].length= 6; + if (translog_write_record(&lsn, + LOGREC_FIXED_RECORD_0LSN_EXAMPLE, + trn, NULL, 6, + TRANSLOG_INTERNAL_PARTS + 1, + parts, NULL, NULL)) + { + fprintf(stderr, "Can't write record #%lu\n", (ulong) i); + translog_destroy(); + ok(0, "write LOGREC_FIXED_RECORD_0LSN_EXAMPLE"); + exit(1); + } + ok(1, "write LOGREC_FIXED_RECORD_0LSN_EXAMPLE"); + + lsn_base= lsn; + + if ((rec_len= rand_buffer_size()) < 9) + rec_len= 9; + parts[TRANSLOG_INTERNAL_PARTS + 0].str= long_buffer; + parts[TRANSLOG_INTERNAL_PARTS + 0].length= rec_len; + if (translog_write_record(&lsn, + LOGREC_VARIABLE_RECORD_0LSN_EXAMPLE, + trn, NULL, rec_len, + TRANSLOG_INTERNAL_PARTS + 1, + parts, NULL, NULL)) + { + fprintf(stderr, "Can't write variable record #%lu\n", (ulong) i); + translog_destroy(); + ok(0, "write LOGREC_VARIABLE_RECORD_0LSN_EXAMPLE"); + exit(1); + } + ok(1, "write LOGREC_VARIABLE_RECORD_0LSN_EXAMPLE"); + if (translog_flush(lsn)) + { + fprintf(stderr, "Can't flush #%lu\n", (ulong) i); + translog_destroy(); + ok(0, "flush"); + exit(1); + } + ok(1, "flush"); + } + + if (translog_flush(translog_get_horizon())) + { + fprintf(stderr, "Can't flush up to horizon\n"); + translog_destroy(); + ok(0, "flush"); + exit(1); + } + ok(1, "flush"); + + srand(122334817L); + + rc= 1; + + { + int len= translog_read_record_header(first_lsn, &rec); + if (len == RECHEADER_READ_ERROR) + { + fprintf(stderr, "translog_read_record_header failed (%d)\n", errno); + goto err; + } + if (rec.type !=LOGREC_FIXED_RECORD_0LSN_EXAMPLE || rec.short_trid != 0 || + rec.record_length != 6 || uint4korr(rec.header) != 0 || + ((uchar)rec.header[4]) != 0 || ((uchar)rec.header[5]) != 0xFF || + first_lsn != rec.lsn) + { + fprintf(stderr, "Incorrect LOGREC_FIXED_RECORD_0LSN_EXAMPLE " + "data read(0)\n" + "type %u, strid %u, len %u, i: %u, 4: %u 5: %u, " + "lsn(%lu,0x%lx)\n", + (uint) rec.type, (uint) rec.short_trid, (uint) rec.record_length, + (uint) uint4korr(rec.header), (uint) rec.header[4], + (uint) rec.header[5], + LSN_IN_PARTS(rec.lsn)); + goto err; + } + read_ok(&rec); + translog_free_record_header(&rec); + lsn= first_lsn; + if (translog_scanner_init(first_lsn, 1, &scanner, 0)) + { + fprintf(stderr, "scanner init failed\n"); + goto err; + } + for (i= 1;; i++) + { + len= translog_read_next_record_header(&scanner, &rec); + if (len == RECHEADER_READ_ERROR) + { + fprintf(stderr, "1-%d translog_read_next_record_header failed (%d)\n", + i, errno); + goto err; + } + if (len == RECHEADER_READ_EOF) + { + if (i != ITERATIONS) + { + fprintf(stderr, "EOL met at iteration %u instead of %u\n", + i, ITERATIONS); + goto err; + } + break; + } + if (i % 2) + { + LSN ref; + ref= lsn_korr(rec.header); + if (rec.type != LOGREC_FIXED_RECORD_1LSN_EXAMPLE || + rec.short_trid != (i % 0xFFFF) || + rec.record_length != 7 || ref != lsn) + { + fprintf(stderr, "Incorrect LOGREC_FIXED_RECORD_1LSN_EXAMPLE " + "data read(%d) " + "type: %u strid: %u len: %u" + "ref: (%lu,0x%lx) (%lu,0x%lx) " + "lsn(%lu,0x%lx)\n", + i, (uint) rec.type, (uint) rec.short_trid, + (uint) rec.record_length, + LSN_IN_PARTS(ref), LSN_IN_PARTS(lsn), + LSN_IN_PARTS(rec.lsn)); + goto err; + } + } + else + { + LSN ref1, ref2; + ref1= lsn_korr(rec.header); + ref2= lsn_korr(rec.header + LSN_STORE_SIZE); + if (rec.type != LOGREC_FIXED_RECORD_2LSN_EXAMPLE || + rec.short_trid != (i % 0xFFFF) || + rec.record_length != 23 || + ref1 != lsn || + ref2 != first_lsn || + ((uchar)rec.header[22]) != 0x55 || + ((uchar)rec.header[21]) != 0xAA || + ((uchar)rec.header[20]) != 0x55 || + ((uchar)rec.header[19]) != 0xAA || + ((uchar)rec.header[18]) != 0x55 || + ((uchar)rec.header[17]) != 0xAA || + ((uchar)rec.header[16]) != 0x55 || + ((uchar)rec.header[15]) != 0xAA || + ((uchar)rec.header[14]) != 0x55) + { + fprintf(stderr, "Incorrect LOGREC_FIXED_RECORD_2LSN_EXAMPLE " + "data read(%d) " + "type %u, strid %u, len %u, ref1(%lu,0x%lx), " + "ref2(%lu,0x%lx) %x%x%x%x%x%x%x%x%x " + "lsn(%lu,0x%lx)\n", + i, (uint) rec.type, (uint) rec.short_trid, + (uint) rec.record_length, + LSN_IN_PARTS(ref1), LSN_IN_PARTS(ref2), + (uint) rec.header[14], (uint) rec.header[15], + (uint) rec.header[16], (uint) rec.header[17], + (uint) rec.header[18], (uint) rec.header[19], + (uint) rec.header[20], (uint) rec.header[21], + (uint) rec.header[22], + LSN_IN_PARTS(rec.lsn)); + goto err; + } + } + read_ok(&rec); + translog_free_record_header(&rec); + + len= translog_read_next_record_header(&scanner, &rec); + if (len == RECHEADER_READ_ERROR) + { + fprintf(stderr, "1-%d translog_read_next_record_header (var) " + "failed (%d)\n", i, errno); + goto err; + } + if (len == RECHEADER_READ_EOF) + { + fprintf(stderr, "EOL met at the middle of iteration (first var) %u " + "instead of beginning of %u\n", i, ITERATIONS); + goto err; + } + if (i % 2) + { + LSN ref; + ref= lsn_korr(rec.header); + if ((rec_len= rand_buffer_size()) < 12) + rec_len= 12; + if (rec.type != LOGREC_VARIABLE_RECORD_1LSN_EXAMPLE || + rec.short_trid != (i % 0xFFFF) || + rec.record_length != rec_len + LSN_STORE_SIZE || + len != 12 || ref != lsn || + check_content(rec.header + LSN_STORE_SIZE, len - LSN_STORE_SIZE)) + { + fprintf(stderr, "Incorrect LOGREC_VARIABLE_RECORD_1LSN_EXAMPLE " + "data read(%d)" + "type %u (%d), strid %u (%d), len %lu, %lu + 7 (%d), " + "hdr len: %u (%d), " + "ref(%lu,0x%lx), lsn(%lu,0x%lx) (%d), content: %d\n", + i, (uint) rec.type, + rec.type != LOGREC_VARIABLE_RECORD_1LSN_EXAMPLE, + (uint) rec.short_trid, + rec.short_trid != (i % 0xFFFF), + (ulong) rec.record_length, (ulong) rec_len, + rec.record_length != rec_len + LSN_STORE_SIZE, + (uint) len, + len != 12, + LSN_IN_PARTS(ref), LSN_IN_PARTS(rec.lsn), + (len != 12 || ref != lsn), + check_content(rec.header + LSN_STORE_SIZE, + len - LSN_STORE_SIZE)); + goto err; + } + if (read_and_check_content(&rec, long_buffer, LSN_STORE_SIZE)) + { + fprintf(stderr, + "Incorrect LOGREC_VARIABLE_RECORD_1LSN_EXAMPLE " + "in whole rec read lsn(%lu,0x%lx)\n", + LSN_IN_PARTS(rec.lsn)); + goto err; + } + } + else + { + LSN ref1, ref2; + ref1= lsn_korr(rec.header); + ref2= lsn_korr(rec.header + LSN_STORE_SIZE); + if ((rec_len= rand_buffer_size()) < 19) + rec_len= 19; + if (rec.type != LOGREC_VARIABLE_RECORD_2LSN_EXAMPLE || + rec.short_trid != (i % 0xFFFF) || + rec.record_length != rec_len + LSN_STORE_SIZE * 2 || + len != 19 || + ref1 != lsn || + ref2 != first_lsn || + check_content(rec.header + LSN_STORE_SIZE * 2, + len - LSN_STORE_SIZE * 2)) + { + fprintf(stderr, "Incorrect LOGREC_VARIABLE_RECORD_2LSN_EXAMPLE " + "data read(%d) " + "type %u, strid %u, len %lu != %lu + 14, hdr len: %d, " + "ref1(%lu,0x%lx), ref2(%lu,0x%lx), " + "lsn(%lu,0x%lx)\n", + i, (uint) rec.type, (uint) rec.short_trid, + (ulong) rec.record_length, (ulong) rec_len, + len, LSN_IN_PARTS(ref1), LSN_IN_PARTS(ref2), + LSN_IN_PARTS(rec.lsn)); + goto err; + } + if (read_and_check_content(&rec, long_buffer, LSN_STORE_SIZE * 2)) + { + fprintf(stderr, + "Incorrect LOGREC_VARIABLE_RECORD_2LSN_EXAMPLE " + "in whole rec read lsn(%lu,0x%lx)\n", + LSN_IN_PARTS(rec.lsn)); + goto err; + } + } + read_ok(&rec); + translog_free_record_header(&rec); + + len= translog_read_next_record_header(&scanner, &rec); + if (len == RECHEADER_READ_ERROR) + { + fprintf(stderr, "1-%d translog_read_next_record_header failed (%d)\n", + i, errno); + goto err; + } + if (len == RECHEADER_READ_EOF) + { + fprintf(stderr, "EOL met at the middle of iteration %u " + "instead of beginning of %u\n", i, ITERATIONS); + goto err; + } + if (rec.type != LOGREC_FIXED_RECORD_0LSN_EXAMPLE || + rec.short_trid != (i % 0xFFFF) || + rec.record_length != 6 || uint4korr(rec.header) != i || + ((uchar)rec.header[4]) != 0 || ((uchar)rec.header[5]) != 0xFF) + { + fprintf(stderr, "Incorrect LOGREC_FIXED_RECORD_0LSN_EXAMPLE " + "data read(%d)\n" + "type %u, strid %u, len %u, i: %u, 4: %u 5: %u " + "lsn(%lu,0x%lx)\n", + i, (uint) rec.type, (uint) rec.short_trid, + (uint) rec.record_length, + (uint) uint4korr(rec.header), (uint) rec.header[4], + (uint) rec.header[5], + LSN_IN_PARTS(rec.lsn)); + goto err; + } + lsn= rec.lsn; + read_ok(&rec); + translog_free_record_header(&rec); + + len= translog_read_next_record_header(&scanner, &rec); + if ((rec_len= rand_buffer_size()) < 9) + rec_len= 9; + if (rec.type != LOGREC_VARIABLE_RECORD_0LSN_EXAMPLE || + rec.short_trid != (i % 0xFFFF) || + rec.record_length != rec_len || + len != 9 || check_content(rec.header, (uint)len)) + { + fprintf(stderr, "Incorrect LOGREC_VARIABLE_RECORD_0LSN_EXAMPLE " + "data read(%d) " + "type %u, strid %u, len %lu != %lu, hdr len: %d, " + "lsn(%lu,0x%lx)\n", + i, (uint) rec.type, (uint) rec.short_trid, + (ulong) rec.record_length, (ulong) rec_len, + len, LSN_IN_PARTS(rec.lsn)); + goto err; + } + if (read_and_check_content(&rec, long_buffer, 0)) + { + fprintf(stderr, + "Incorrect LOGREC_VARIABLE_RECORD_2LSN_EXAMPLE " + "in whole rec read lsn(%lu,0x%lx)\n", + LSN_IN_PARTS(rec.lsn)); + goto err; + } + read_ok(&rec); + translog_free_record_header(&rec); + } + } + + rc= 0; +err: + if (rc) + ok(0, "read record"); + } /* SKIP_BIG_TESTS */ + translog_destroy(); + end_pagecache(&pagecache, 1); + ma_control_file_end(); + + if (maria_log_remove()) + exit(1); + + return(test(exit_status())); +} diff --git a/storage/maria/unittest/ma_test_loghandler_first_lsn-t.c b/storage/maria/unittest/ma_test_loghandler_first_lsn-t.c new file mode 100644 index 00000000000..06d9a00c04c --- /dev/null +++ b/storage/maria/unittest/ma_test_loghandler_first_lsn-t.c @@ -0,0 +1,160 @@ +/* Copyright (C) 2006-2008 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 */ + +#include "../maria_def.h" +#include <stdio.h> +#include <errno.h> +#include <tap.h> +#include "../trnman.h" + +extern my_bool maria_log_remove(); +extern void translog_example_table_init(); + +#ifndef DBUG_OFF +static const char *default_dbug_option; +#endif + +#define PCACHE_SIZE (1024*1024*10) +#define PCACHE_PAGE TRANSLOG_PAGE_SIZE +#define LOG_FILE_SIZE (1024L*1024L*1024L + 1024L*1024L*512) +#define LOG_FLAGS 0 + +static char *first_translog_file= (char*)"maria_log.00000001"; + +int main(int argc __attribute__((unused)), char *argv[]) +{ + uint pagen; + uchar long_tr_id[6]; + PAGECACHE pagecache; + LSN lsn, first_lsn, theor_lsn; + LEX_CUSTRING parts[TRANSLOG_INTERNAL_PARTS + 1]; + + MY_INIT(argv[0]); + + plan(2); + + bzero(&pagecache, sizeof(pagecache)); + maria_data_root= (char *)"."; + if (maria_log_remove()) + exit(1); + /* be sure that we have no logs in the directory*/ + my_delete(CONTROL_FILE_BASE_NAME, MYF(0)); + my_delete(first_translog_file, MYF(0)); + + bzero(long_tr_id, 6); +#ifndef DBUG_OFF +#if defined(__WIN__) + default_dbug_option= "d:t:i:O,\\ma_test_loghandler.trace"; +#else + default_dbug_option= "d:t:i:o,/tmp/ma_test_loghandler.trace"; +#endif + if (argc > 1) + { + DBUG_SET(default_dbug_option); + DBUG_SET_INITIAL(default_dbug_option); + } +#endif + + if (ma_control_file_open(TRUE, TRUE)) + { + fprintf(stderr, "Can't init control file (%d)\n", errno); + exit(1); + } + if ((pagen= init_pagecache(&pagecache, PCACHE_SIZE, 0, 0, + PCACHE_PAGE, 0)) == 0) + { + fprintf(stderr, "Got error: init_pagecache() (errno: %d)\n", errno); + exit(1); + } + if (translog_init_with_table(".", LOG_FILE_SIZE, 50112, 0, &pagecache, + LOG_FLAGS, 0, &translog_example_table_init, + 0)) + { + fprintf(stderr, "Can't init loghandler (%d)\n", errno); + exit(1); + } + /* Suppressing of automatic record writing */ + dummy_transaction_object.first_undo_lsn|= TRANSACTION_LOGGED_LONG_ID; + + theor_lsn= translog_first_theoretical_lsn(); + if (theor_lsn == 1) + { + fprintf(stderr, "Error reading the first log file."); + translog_destroy(); + exit(1); + } + if (theor_lsn == LSN_IMPOSSIBLE) + { + fprintf(stderr, "There is no first log file."); + translog_destroy(); + exit(1); + } + first_lsn= translog_first_lsn_in_log(); + if (first_lsn != LSN_IMPOSSIBLE) + { + fprintf(stderr, "Incorrect first lsn response (%lu,0x%lx).", + LSN_IN_PARTS(first_lsn)); + translog_destroy(); + exit(1); + } + ok(1, "Empty log response"); + + + int4store(long_tr_id, 0); + parts[TRANSLOG_INTERNAL_PARTS + 0].str= long_tr_id; + parts[TRANSLOG_INTERNAL_PARTS + 0].length= 6; + if (translog_write_record(&lsn, + LOGREC_FIXED_RECORD_0LSN_EXAMPLE, + &dummy_transaction_object, NULL, 6, + TRANSLOG_INTERNAL_PARTS + 1, + parts, NULL, NULL)) + { + fprintf(stderr, "Can't write record #%lu\n", (ulong) 0); + translog_destroy(); + exit(1); + } + + theor_lsn= translog_first_theoretical_lsn(); + if (theor_lsn == 1) + { + fprintf(stderr, "Error reading the first log file\n"); + translog_destroy(); + exit(1); + } + if (theor_lsn == LSN_IMPOSSIBLE) + { + fprintf(stderr, "There is no first log file\n"); + translog_destroy(); + exit(1); + } + first_lsn= translog_first_lsn_in_log(); + if (first_lsn != theor_lsn) + { + fprintf(stderr, "Incorrect first lsn: (%lu,0x%lx) " + " theoretical first: (%lu,0x%lx)\n", + LSN_IN_PARTS(first_lsn), LSN_IN_PARTS(theor_lsn)); + translog_destroy(); + exit(1); + } + + ok(1, "Full log response"); + + translog_destroy(); + end_pagecache(&pagecache, 1); + ma_control_file_end(); + if (maria_log_remove()) + exit(1); + exit(0); +} diff --git a/storage/maria/unittest/ma_test_loghandler_max_lsn-t.c b/storage/maria/unittest/ma_test_loghandler_max_lsn-t.c new file mode 100644 index 00000000000..64f486b8cf1 --- /dev/null +++ b/storage/maria/unittest/ma_test_loghandler_max_lsn-t.c @@ -0,0 +1,156 @@ +/* Copyright (C) 2006-2008 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 */ + +#include "../maria_def.h" +#include <stdio.h> +#include <errno.h> +#include <tap.h> +#include "../trnman.h" + +extern my_bool maria_log_remove(); +extern void translog_example_table_init(); + +#ifndef DBUG_OFF +static const char *default_dbug_option; +#endif + +#define PCACHE_SIZE (1024*1024*10) +#define PCACHE_PAGE TRANSLOG_PAGE_SIZE +#define LOG_FILE_SIZE (8*1024L*1024L) +#define LOG_FLAGS 0 + + +int main(int argc __attribute__((unused)), char *argv[]) +{ + ulong i; + uint pagen; + uchar long_tr_id[6]; + PAGECACHE pagecache; + LSN lsn, max_lsn, last_lsn= LSN_IMPOSSIBLE; + LEX_CUSTRING parts[TRANSLOG_INTERNAL_PARTS + 1]; + + MY_INIT(argv[0]); + + plan(2); + + bzero(&pagecache, sizeof(pagecache)); + maria_data_root= (char *)"."; + if (maria_log_remove()) + exit(1); + + bzero(long_tr_id, 6); +#ifndef DBUG_OFF +#if defined(__WIN__) + default_dbug_option= "d:t:i:O,\\ma_test_loghandler.trace"; +#else + default_dbug_option= "d:t:i:o,/tmp/ma_test_loghandler.trace"; +#endif + if (argc > 1) + { + DBUG_SET(default_dbug_option); + DBUG_SET_INITIAL(default_dbug_option); + } +#endif + + if (ma_control_file_open(TRUE, TRUE)) + { + fprintf(stderr, "Can't init control file (%d)\n", errno); + exit(1); + } + if ((pagen= init_pagecache(&pagecache, PCACHE_SIZE, 0, 0, + PCACHE_PAGE, 0)) == 0) + { + fprintf(stderr, "Got error: init_pagecache() (errno: %d)\n", errno); + exit(1); + } + if (translog_init_with_table(".", LOG_FILE_SIZE, 50112, 0, &pagecache, + LOG_FLAGS, 0, &translog_example_table_init, + 0)) + { + fprintf(stderr, "Can't init loghandler (%d)\n", errno); + exit(1); + } + /* Suppressing of automatic record writing */ + dummy_transaction_object.first_undo_lsn|= TRANSACTION_LOGGED_LONG_ID; + + max_lsn= translog_get_file_max_lsn_stored(1); + if (max_lsn == 1) + { + fprintf(stderr, "Error reading the first log file."); + translog_destroy(); + exit(1); + } + if (max_lsn != LSN_IMPOSSIBLE) + { + fprintf(stderr, "Incorrect first lsn response (%lu,0x%lx).", + LSN_IN_PARTS(max_lsn)); + translog_destroy(); + exit(1); + } + ok(1, "Empty log response"); + + + /* write more then 1 file */ + int4store(long_tr_id, 0); + parts[TRANSLOG_INTERNAL_PARTS + 0].str= long_tr_id; + parts[TRANSLOG_INTERNAL_PARTS + 0].length= 6; + for(i= 0; i < LOG_FILE_SIZE/6; i++) + { + if (translog_write_record(&lsn, + LOGREC_FIXED_RECORD_0LSN_EXAMPLE, + &dummy_transaction_object, NULL, 6, + TRANSLOG_INTERNAL_PARTS + 1, + parts, NULL, NULL)) + { + fprintf(stderr, "Can't write record #%lu\n", (ulong) 0); + translog_destroy(); + exit(1); + } + if (LSN_FILE_NO(lsn) == 1) + last_lsn= lsn; + } + + + max_lsn= translog_get_file_max_lsn_stored(1); + if (max_lsn == 1) + { + fprintf(stderr, "Error reading the first log file\n"); + translog_destroy(); + exit(1); + } + if (max_lsn == LSN_IMPOSSIBLE) + { + fprintf(stderr, "Isn't first file still finished?!!\n"); + translog_destroy(); + exit(1); + } + if (max_lsn != last_lsn) + { + fprintf(stderr, "Incorrect max lsn: (%lu,0x%lx) " + " last lsn on first file: (%lu,0x%lx)\n", + LSN_IN_PARTS(max_lsn), LSN_IN_PARTS(last_lsn)); + translog_destroy(); + exit(1); + } + + ok(1, "First file max LSN"); + + translog_destroy(); + end_pagecache(&pagecache, 1); + ma_control_file_end(); + if (maria_log_remove()) + exit(1); + exit(0); +} diff --git a/storage/maria/unittest/ma_test_loghandler_multigroup-t.c b/storage/maria/unittest/ma_test_loghandler_multigroup-t.c new file mode 100644 index 00000000000..7ba7ce3176d --- /dev/null +++ b/storage/maria/unittest/ma_test_loghandler_multigroup-t.c @@ -0,0 +1,746 @@ +/* Copyright (C) 2006-2008 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 */ + +#include "../maria_def.h" +#include <stdio.h> +#include <errno.h> +#include <tap.h> +#include "../trnman.h" +#include "sequence_storage.h" +#include <my_getopt.h> + +extern my_bool maria_log_remove(); +extern void translog_example_table_init(); + +#ifndef DBUG_OFF +static const char *default_dbug_option; +#endif +static TRN *trn= &dummy_transaction_object; + + +#ifndef READONLY_TEST + +#define PCACHE_SIZE (1024*1024*10) +#define LONG_BUFFER_SIZE ((1024L*1024L*1024L) + (1024L*1024L*512)) +#define MIN_REC_LENGTH (1024L*1024L + 1024L*512L + 1) +#define LOG_FILE_SIZE (1024L*1024L*1024L + 1024L*1024L*512) +#define ITERATIONS 2 +#define READONLY 0 + +#else + +#define PCACHE_SIZE (1024*1024*10) +#define LONG_BUFFER_SIZE (1024L*1024L) +#define MIN_REC_LENGTH (1024L) +#define LOG_FILE_SIZE (1024L*1024L*1024L + 1024L*1024L*512) +#define ITERATIONS 2 +#define READONLY 1 + +#endif /*READONLY_TEST*/ + + +/* +#define LOG_FILE_SIZE 1024L*1024L*3L +#define ITERATIONS 1600 +*/ +/* +#define LOG_FILE_SIZE 1024L*1024L*100L +#define ITERATIONS 65000 +*/ + + +/* + Check that the buffer filled correctly + + SYNOPSIS + check_content() + ptr Pointer to the buffer + length length of the buffer + + RETURN + 0 - OK + 1 - Error +*/ + +static my_bool check_content(uchar *ptr, ulong length) +{ + ulong i; + uchar buff[4]; + DBUG_ENTER("check_content"); + for (i= 0; i < length; i++) + { + if (i % 4 == 0) + int4store(buff, (i >> 2)); + if (ptr[i] != buff[i % 4]) + { + fprintf(stderr, "Byte # %lu is %x instead of %x", + i, (uint) ptr[i], (uint) buff[i % 4]); + DBUG_DUMP("mem", ptr +(ulong) (i > 16 ? i - 16 : 0), + (i > 16 ? 16 : i) + (i + 16 < length ? 16 : length - i)); + DBUG_RETURN(1); + } + } + DBUG_RETURN(0); +} + + +/* + Read whole record content, and check content (put with offset) + + SYNOPSIS + read_and_check_content() + rec The record header buffer + buffer The buffer to read the record in + skip Skip this number of bytes ot the record content + + RETURN + 0 - OK + 1 - Error +*/ + +static my_bool read_and_check_content(TRANSLOG_HEADER_BUFFER *rec, + uchar *buffer, uint skip) +{ + int res= 0; + translog_size_t len; + DBUG_ENTER("read_and_check_content"); + DBUG_ASSERT(rec->record_length < LONG_BUFFER_SIZE + LSN_STORE_SIZE * 2 + 2); + if ((len= translog_read_record(rec->lsn, 0, rec->record_length, + buffer, NULL)) != rec->record_length) + { + fprintf(stderr, "Requested %lu byte, read %lu\n", + (ulong) rec->record_length, (ulong) len); + res= 1; + } + res|= check_content(buffer + skip, rec->record_length - skip); + DBUG_RETURN(res); +} + +static const char *load_default_groups[]= {"ma_unit_loghandler", 0}; +#ifndef DBUG_OFF +static const char *default_dbug_option= + IF_WIN("d:t:i:O,\\ma_test_loghandler.trace", + "d:t:i:o,/tmp/ma_test_loghandler.trace"); +#endif +static const char *opt_wfile= NULL; +static const char *opt_rfile= NULL; +static struct my_option my_long_options[] = +{ +#ifndef DBUG_OFF + {"debug", '#', "Output debug log. Often the argument is 'd:t:o,filename'.", + 0, 0, 0, GET_STR, OPT_ARG, 0, 0, 0, 0, 0, 0}, +#endif + {"write-seq", 'w', "Path to file in which \"random\" sequence used in the test will be written", + (uchar**) &opt_wfile, 0, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + {"read-seq", 'r', "Path to file from which \"random\" sequence used in the test will be read", + (uchar**) &opt_rfile, 0, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + {"help", '?', "Display this help 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} +}; +static SEQ_STORAGE seq; + +static uint32 get_len() +{ + uint32 res; + DBUG_ENTER("get_len"); + if (opt_rfile) + res= seq_storage_next(&seq); + else + { + res= (uint32) + ((ulonglong) rand() * + (LONG_BUFFER_SIZE - MIN_REC_LENGTH - 1) / RAND_MAX) + MIN_REC_LENGTH; + if (opt_wfile && + seq_storage_write(opt_wfile, res)) + exit(1); + } + DBUG_PRINT("info", ("length value : %lu", (ulong) res)); + DBUG_RETURN(res); +} + +static void usage(void) +{ + puts("Copyright (C) 2008 MySQL AB"); + puts("This software comes with ABSOLUTELY NO WARRANTY. This is free software,"); + puts("and you are welcome to modify and redistribute it under the GPL license\n"); + + puts("Unit test of maria engine"); + VOID(printf("\nUsage: %s [OPTIONS]\n", my_progname_short)); + my_print_help(my_long_options); + print_defaults("my", load_default_groups); + my_print_variables(my_long_options); +} + + +static my_bool +get_one_option(int optid __attribute__((unused)), + const struct my_option *opt __attribute__((unused)), + char *argument __attribute__((unused))) +{ + switch (optid) { + case '?': + usage(); + exit(0); +#ifndef DBUG_OFF + case '#': + DBUG_SET_INITIAL(argument ? argument : default_dbug_option); + break; +#endif + } + return 0; +} + + +static void get_options(int *argc,char ***argv) +{ + int ho_error; + + if ((ho_error= handle_options(argc, argv, my_long_options, get_one_option))) + exit(ho_error); + + if (opt_rfile && opt_wfile) + { + usage(); + exit(1); + } +} + + +int main(int argc __attribute__((unused)), char *argv[]) +{ + uint32 i; + uint32 rec_len; + uint pagen; + uchar long_tr_id[6]; + uchar lsn_buff[23]= + { + 0x55, 0xAA, 0x55, 0xAA, 0x55, 0xAA, 0x55, 0xAA, + 0x55, 0xAA, 0x55, 0xAA, 0x55, 0xAA, 0x55, 0xAA, + 0x55, 0xAA, 0x55, 0xAA, 0x55, 0xAA, 0x55 + }; + uchar *long_buffer= malloc(LONG_BUFFER_SIZE + LSN_STORE_SIZE * 2 + 2); + char **default_argv; + PAGECACHE pagecache; + LSN lsn, lsn_base, first_lsn; + TRANSLOG_HEADER_BUFFER rec; + LEX_CUSTRING parts[TRANSLOG_INTERNAL_PARTS + 2]; + struct st_translog_scanner_data scanner; + int rc; + + MY_INIT(argv[0]); + + bzero(&pagecache, sizeof(pagecache)); + maria_data_root= (char *)"."; + load_defaults("my", load_default_groups, &argc, &argv); + default_argv= argv; + get_options(&argc, &argv); + + if (maria_log_remove()) + exit(1); + + { + uchar buff[4]; + for (i= 0; i < (LONG_BUFFER_SIZE + LSN_STORE_SIZE * 2 + 2); i++) + { + if (i % 4 == 0) + int4store(buff, (i >> 2)); + long_buffer[i]= buff[i % 4]; + } + } + + bzero(long_tr_id, 6); + + if (ma_control_file_open(TRUE, TRUE)) + { + fprintf(stderr, "Can't init control file (%d)\n", errno); + exit(1); + } + if ((pagen= init_pagecache(&pagecache, PCACHE_SIZE, 0, 0, + TRANSLOG_PAGE_SIZE, 0)) == 0) + { + fprintf(stderr, "Got error: init_pagecache() (errno: %d)\n", errno); + exit(1); + } + if (translog_init_with_table(".", LOG_FILE_SIZE, 50112, 0, &pagecache, + 0, 0, &translog_example_table_init, 0)) + { + fprintf(stderr, "Can't init loghandler (%d)\n", errno); + exit(1); + } + /* Suppressing of automatic record writing */ + trn->first_undo_lsn|= TRANSACTION_LOGGED_LONG_ID; + + plan(((ITERATIONS - 1) * 4 + 1) * 2); + + if (opt_rfile && + seq_storage_reader_init(&seq, opt_rfile)) + exit(1); + srand(122334817L); + + long_tr_id[5]= 0xff; + + int4store(long_tr_id, 0); + parts[TRANSLOG_INTERNAL_PARTS + 0].str= long_tr_id; + parts[TRANSLOG_INTERNAL_PARTS + 0].length= 6; + trn->short_id= 0; + trn->first_undo_lsn= TRANSACTION_LOGGED_LONG_ID; + if (translog_write_record(&lsn, LOGREC_FIXED_RECORD_0LSN_EXAMPLE, + trn, NULL, 6, TRANSLOG_INTERNAL_PARTS + 1, parts, + NULL, NULL)) + { + fprintf(stderr, "Can't write record #%u\n", 0); + translog_destroy(); + ok(0, "write LOGREC_FIXED_RECORD_0LSN_EXAMPLE"); + exit(1); + } + ok(1, "write LOGREC_FIXED_RECORD_0LSN_EXAMPLE"); + lsn_base= first_lsn= lsn; + + for (i= 1; i < ITERATIONS; i++) + { + if (i % 2) + { + lsn_store(lsn_buff, lsn_base); + parts[TRANSLOG_INTERNAL_PARTS + 0].str= lsn_buff; + parts[TRANSLOG_INTERNAL_PARTS + 0].length= LSN_STORE_SIZE; + trn->short_id= i % 0xFFFF; + if (translog_write_record(&lsn, + LOGREC_FIXED_RECORD_1LSN_EXAMPLE, trn, NULL, + LSN_STORE_SIZE, TRANSLOG_INTERNAL_PARTS + 1, + parts, NULL, NULL)) + { + fprintf(stderr, "1 Can't write reference before record #%u\n", i); + translog_destroy(); + ok(0, "write LOGREC_FIXED_RECORD_1LSN_EXAMPLE"); + exit(1); + } + ok(1, "write LOGREC_FIXED_RECORD_1LSN_EXAMPLE"); + lsn_store(lsn_buff, lsn_base); + rec_len= get_len(); + parts[TRANSLOG_INTERNAL_PARTS + 0].str= lsn_buff; + parts[TRANSLOG_INTERNAL_PARTS + 0].length= LSN_STORE_SIZE; + parts[TRANSLOG_INTERNAL_PARTS + 1].str= long_buffer; + parts[TRANSLOG_INTERNAL_PARTS + 1].length= rec_len; + trn->short_id= i % 0xFFFF; + if (translog_write_record(&lsn, + LOGREC_VARIABLE_RECORD_1LSN_EXAMPLE, + trn, NULL, LSN_STORE_SIZE + rec_len, + TRANSLOG_INTERNAL_PARTS + 2, + parts, NULL, NULL)) + { + fprintf(stderr, "1 Can't write var reference before record #%u\n", i); + translog_destroy(); + ok(0, "write LOGREC_VARIABLE_RECORD_1LSN_EXAMPLE"); + exit(1); + } + ok(1, "write LOGREC_VARIABLE_RECORD_1LSN_EXAMPLE"); + } + else + { + lsn_store(lsn_buff, lsn_base); + lsn_store(lsn_buff + LSN_STORE_SIZE, first_lsn); + parts[TRANSLOG_INTERNAL_PARTS + 1].str= lsn_buff; + parts[TRANSLOG_INTERNAL_PARTS + 1].length= 23; + trn->short_id= i % 0xFFFF; + if (translog_write_record(&lsn, + LOGREC_FIXED_RECORD_2LSN_EXAMPLE, + trn, NULL, 23, TRANSLOG_INTERNAL_PARTS + 1, + parts, NULL, NULL)) + { + fprintf(stderr, "0 Can't write reference before record #%u\n", i); + translog_destroy(); + ok(0, "write LOGREC_FIXED_RECORD_2LSN_EXAMPLE"); + exit(1); + } + ok(1, "write LOGREC_FIXED_RECORD_2LSN_EXAMPLE"); + lsn_store(lsn_buff, lsn_base); + lsn_store(lsn_buff + LSN_STORE_SIZE, first_lsn); + rec_len= get_len(); + parts[TRANSLOG_INTERNAL_PARTS + 0].str= lsn_buff; + parts[TRANSLOG_INTERNAL_PARTS + 0].length= LSN_STORE_SIZE * 2; + parts[TRANSLOG_INTERNAL_PARTS + 1].str= long_buffer; + parts[TRANSLOG_INTERNAL_PARTS + 1].length= rec_len; + trn->short_id= i % 0xFFFF; + if (translog_write_record(&lsn, + LOGREC_VARIABLE_RECORD_2LSN_EXAMPLE, + trn, NULL, LSN_STORE_SIZE * 2 + rec_len, + TRANSLOG_INTERNAL_PARTS + 2, + parts, NULL, NULL)) + { + fprintf(stderr, "0 Can't write var reference before record #%u\n", i); + translog_destroy(); + ok(0, "write LOGREC_VARIABLE_RECORD_2LSN_EXAMPLE"); + exit(1); + } + ok(1, "write LOGREC_VARIABLE_RECORD_2LSN_EXAMPLE"); + } + int4store(long_tr_id, i); + parts[TRANSLOG_INTERNAL_PARTS + 0].str= long_tr_id; + parts[TRANSLOG_INTERNAL_PARTS + 0].length= 6; + trn->short_id= i % 0xFFFF; + if (translog_write_record(&lsn, + LOGREC_FIXED_RECORD_0LSN_EXAMPLE, + trn, NULL, 6, + TRANSLOG_INTERNAL_PARTS + 1, parts, NULL, NULL)) + { + fprintf(stderr, "Can't write record #%u\n", i); + translog_destroy(); + ok(0, "write LOGREC_FIXED_RECORD_0LSN_EXAMPLE"); + exit(1); + } + ok(1, "write LOGREC_FIXED_RECORD_0LSN_EXAMPLE"); + + lsn_base= lsn; + + rec_len= get_len(); + parts[TRANSLOG_INTERNAL_PARTS + 0].str= long_buffer; + parts[TRANSLOG_INTERNAL_PARTS + 0].length= rec_len; + trn->short_id= i % 0xFFFF; + if (translog_write_record(&lsn, + LOGREC_VARIABLE_RECORD_0LSN_EXAMPLE, + trn, NULL, rec_len, + TRANSLOG_INTERNAL_PARTS + 1, parts, NULL, NULL)) + { + fprintf(stderr, "Can't write variable record #%u\n", i); + translog_destroy(); + ok(0, "write LOGREC_VARIABLE_RECORD_0LSN_EXAMPLE"); + exit(1); + } + ok(1, "write LOGREC_VARIABLE_RECORD_0LSN_EXAMPLE"); + } + + translog_destroy(); + end_pagecache(&pagecache, 1); + ma_control_file_end(); + + if (ma_control_file_open(TRUE,TRUE)) + { + fprintf(stderr, "pass2: Can't init control file (%d)\n", errno); + exit(1); + } + if ((pagen= init_pagecache(&pagecache, PCACHE_SIZE, 0, 0, + TRANSLOG_PAGE_SIZE, 0)) == 0) + { + fprintf(stderr, "pass2: Got error: init_pagecache() (errno: %d)\n", errno); + exit(1); + } + if (translog_init_with_table(".", LOG_FILE_SIZE, 50112, 0, &pagecache, + 0, READONLY, &translog_example_table_init, 0)) + { + fprintf(stderr, "pass2: Can't init loghandler (%d)\n", errno); + exit(1); + } + + + /* If we were writing sequence we need it only once */ + opt_wfile= NULL; + if (opt_rfile) + seq_storage_rewind(&seq); + srand(122334817L); + + rc= 1; + + { + int len= translog_read_record_header(first_lsn, &rec); + if (len == RECHEADER_READ_ERROR) + { + fprintf(stderr, "translog_read_record_header failed (%d)\n", errno); + translog_free_record_header(&rec); + goto err; + } + if (rec.type !=LOGREC_FIXED_RECORD_0LSN_EXAMPLE || rec.short_trid != 0 || + rec.record_length != 6 || uint4korr(rec.header) != 0 || + ((uchar)rec.header[4]) != 0 || ((uchar)rec.header[5]) != 0xFF || + first_lsn != rec.lsn) + { + fprintf(stderr, "Incorrect LOGREC_FIXED_RECORD_0LSN_EXAMPLE " + "data read(0)\n" + "type %u, strid %u, len %u, i: %u, 4: %u 5: %u, " + "lsn(0x%lu,0x%lx)\n", + (uint) rec.type, (uint) rec.short_trid, (uint) rec.record_length, + (uint)uint4korr(rec.header), (uint) rec.header[4], + (uint) rec.header[5], + LSN_IN_PARTS(rec.lsn)); + translog_free_record_header(&rec); + goto err; + } + ok(1, "read record"); + translog_free_record_header(&rec); + lsn= first_lsn; + if (translog_scanner_init(first_lsn, 1, &scanner, 0)) + { + fprintf(stderr, "scanner init failed\n"); + goto err; + } + for (i= 1;; i++) + { + len= translog_read_next_record_header(&scanner, &rec); + if (len == RECHEADER_READ_ERROR) + { + fprintf(stderr, "1-%d translog_read_next_record_header failed (%d)\n", + i, errno); + translog_free_record_header(&rec); + goto err; + } + if (len == RECHEADER_READ_EOF) + { + if (i != ITERATIONS) + { + fprintf(stderr, "EOL met at iteration %u instead of %u\n", + i, ITERATIONS); + translog_free_record_header(&rec); + goto err; + } + break; + } + + if (i % 2) + { + LSN ref; + ref= lsn_korr(rec.header); + if (rec.type != LOGREC_FIXED_RECORD_1LSN_EXAMPLE || + rec.short_trid != (i % 0xFFFF) || + rec.record_length != LSN_STORE_SIZE || ref != lsn) + { + fprintf(stderr, "Incorrect LOGREC_FIXED_RECORD_1LSN_EXAMPLE " + "data read(%d)" + "type %u, strid %u, len %u, ref(%lu,0x%lx), lsn(%lu,0x%lx)\n", + i, (uint) rec.type, (uint) rec.short_trid, + (uint) rec.record_length, + LSN_IN_PARTS(ref), LSN_IN_PARTS(rec.lsn)); + translog_free_record_header(&rec); + goto err; + } + } + else + { + LSN ref1, ref2; + ref1= lsn_korr(rec.header); + ref2= lsn_korr(rec.header + LSN_STORE_SIZE); + if (rec.type != LOGREC_FIXED_RECORD_2LSN_EXAMPLE || + rec.short_trid != (i % 0xFFFF) || + rec.record_length != 23 || + ref1 != lsn || + ref2 != first_lsn || + ((uchar)rec.header[22]) != 0x55 || + ((uchar)rec.header[21]) != 0xAA || + ((uchar)rec.header[20]) != 0x55 || + ((uchar)rec.header[19]) != 0xAA || + ((uchar)rec.header[18]) != 0x55 || + ((uchar)rec.header[17]) != 0xAA || + ((uchar)rec.header[16]) != 0x55 || + ((uchar)rec.header[15]) != 0xAA || + ((uchar)rec.header[14]) != 0x55) + { + fprintf(stderr, "Incorrect LOGREC_FIXED_RECORD_2LSN_EXAMPLE " + "data read(%d) " + "type %u, strid %u, len %u, ref1(%lu,0x%lx), " + "ref2(%lu,0x%lx) %x%x%x%x%x%x%x%x%x " + "lsn(%lu,0x%lx)\n", + i, (uint) rec.type, (uint) rec.short_trid, + (uint) rec.record_length, + LSN_IN_PARTS(ref1), LSN_IN_PARTS(ref2), + (uint) rec.header[14], (uint) rec.header[15], + (uint) rec.header[16], (uint) rec.header[17], + (uint) rec.header[18], (uint) rec.header[19], + (uint) rec.header[20], (uint) rec.header[21], + (uint) rec.header[22], + LSN_IN_PARTS(rec.lsn)); + translog_free_record_header(&rec); + DBUG_ASSERT(0); + goto err; + } + } + ok(1, "read record"); + translog_free_record_header(&rec); + + len= translog_read_next_record_header(&scanner, &rec); + if (len == RECHEADER_READ_ERROR) + { + fprintf(stderr, "1-%d translog_read_next_record_header (var) " + "failed (%d)\n", i, errno); + goto err; + } + if (len == RECHEADER_READ_EOF) + { + fprintf(stderr, "EOL met at the middle of iteration (first var) %u " + "instead of beginning of %u\n", i, ITERATIONS); + goto err; + } + if (i % 2) + { + LSN ref; + ref= lsn_korr(rec.header); + rec_len= get_len(); + if (rec.type !=LOGREC_VARIABLE_RECORD_1LSN_EXAMPLE || + rec.short_trid != (i % 0xFFFF) || + rec.record_length != rec_len + LSN_STORE_SIZE || + len != 12 || ref != lsn || + check_content(rec.header + LSN_STORE_SIZE, len - LSN_STORE_SIZE)) + { + fprintf(stderr, "Incorrect LOGREC_VARIABLE_RECORD_1LSN_EXAMPLE " + "data read(%d)" + "type %u (%d), strid %u (%d), len %lu, %lu + 7 (%d), " + "hdr len: %d (%d), " + "ref(%lu,0x%lx), lsn(%lu,0x%lx) (%d), content: %d\n", + i, (uint) rec.type, + rec.type !=LOGREC_VARIABLE_RECORD_1LSN_EXAMPLE, + (uint) rec.short_trid, + rec.short_trid != (i % 0xFFFF), + (ulong) rec.record_length, (ulong) rec_len, + rec.record_length != rec_len + LSN_STORE_SIZE, + len, + len != 12, + LSN_IN_PARTS(ref), LSN_IN_PARTS(rec.lsn), + (ref != lsn), + check_content(rec.header + LSN_STORE_SIZE, + len - LSN_STORE_SIZE)); + translog_free_record_header(&rec); + goto err; + } + if (read_and_check_content(&rec, long_buffer, LSN_STORE_SIZE)) + { + fprintf(stderr, + "Incorrect LOGREC_VARIABLE_RECORD_1LSN_EXAMPLE " + "in whole rec read lsn(%lu,0x%lx)\n", + LSN_IN_PARTS(rec.lsn)); + translog_free_record_header(&rec); + goto err; + } + } + else + { + LSN ref1, ref2; + ref1= lsn_korr(rec.header); + ref2= lsn_korr(rec.header + LSN_STORE_SIZE); + rec_len= get_len(); + if (rec.type != LOGREC_VARIABLE_RECORD_2LSN_EXAMPLE || + rec.short_trid != (i % 0xFFFF) || + rec.record_length != rec_len + LSN_STORE_SIZE * 2 || + len != 19 || + ref1 != lsn || + ref2 != first_lsn || + check_content(rec.header + LSN_STORE_SIZE * 2, + len - LSN_STORE_SIZE * 2)) + { + fprintf(stderr, "Incorrect LOGREC_VARIABLE_RECORD_2LSN_EXAMPLE " + " data read(%d) " + "type %u, strid %u, len %lu != %lu + 14, hdr len: %d, " + "ref1(%lu,0x%lx), ref2(%lu,0x%lx), " + "lsn(%lu,0x%lx)\n", + i, (uint) rec.type, (uint) rec.short_trid, + (ulong) rec.record_length, (ulong) rec_len, + len, + LSN_IN_PARTS(ref1), LSN_IN_PARTS(ref2), + LSN_IN_PARTS(rec.lsn)); + translog_free_record_header(&rec); + goto err; + } + if (read_and_check_content(&rec, long_buffer, LSN_STORE_SIZE * 2)) + { + fprintf(stderr, + "Incorrect LOGREC_VARIABLE_RECORD_2LSN_EXAMPLE " + "in whole rec read lsn(%lu,0x%lx)\n", + LSN_IN_PARTS(rec.lsn)); + translog_free_record_header(&rec); + goto err; + } + } + ok(1, "read record"); + translog_free_record_header(&rec); + + len= translog_read_next_record_header(&scanner, &rec); + if (len == RECHEADER_READ_ERROR) + { + fprintf(stderr, "1-%d translog_read_next_record_header failed (%d)\n", + i, errno); + translog_free_record_header(&rec); + goto err; + } + if (len == RECHEADER_READ_EOF) + { + fprintf(stderr, "EOL met at the middle of iteration %u " + "instead of beginning of %u\n", i, ITERATIONS); + translog_free_record_header(&rec); + goto err; + } + if (rec.type != LOGREC_FIXED_RECORD_0LSN_EXAMPLE || + rec.short_trid != (i % 0xFFFF) || + rec.record_length != 6 || uint4korr(rec.header) != i || + ((uchar)rec.header[4]) != 0 || ((uchar)rec.header[5]) != 0xFF) + { + fprintf(stderr, "Incorrect LOGREC_FIXED_RECORD_0LSN_EXAMPLE " + "data read(%d)\n" + "type %u, strid %u, len %u, i: %u, 4: %u 5: %u " + "lsn(%lu,0x%lx)\n", + i, (uint) rec.type, (uint) rec.short_trid, + (uint) rec.record_length, + (uint)uint4korr(rec.header), (uint) rec.header[4], + (uint) rec.header[5], + LSN_IN_PARTS(rec.lsn)); + translog_free_record_header(&rec); + goto err; + } + ok(1, "read record"); + translog_free_record_header(&rec); + + lsn= rec.lsn; + + len= translog_read_next_record_header(&scanner, &rec); + rec_len= get_len(); + if (rec.type != LOGREC_VARIABLE_RECORD_0LSN_EXAMPLE || + rec.short_trid != (i % 0xFFFF) || + rec.record_length != rec_len || + len != 9 || check_content(rec.header, len)) + { + fprintf(stderr, "Incorrect LOGREC_VARIABLE_RECORD_0LSN_EXAMPLE " + "data read(%d) " + "type %u, strid %u, len %lu != %lu, hdr len: %d, " + "lsn(%lu,0x%lx)\n", + i, (uint) rec.type, (uint) rec.short_trid, + (ulong) rec.record_length, (ulong) rec_len, + len, LSN_IN_PARTS(rec.lsn)); + translog_free_record_header(&rec); + goto err; + } + if (read_and_check_content(&rec, long_buffer, 0)) + { + fprintf(stderr, + "Incorrect LOGREC_VARIABLE_RECORD_2LSN_EXAMPLE " + "in whole rec read lsn(%lu,0x%lx)\n", + LSN_IN_PARTS(rec.lsn)); + translog_free_record_header(&rec); + goto err; + } + ok(1, "read record"); + translog_free_record_header(&rec); + } + } + + rc= 0; +err: + if (rc) + ok(0, "read record"); + translog_destroy(); + end_pagecache(&pagecache, 1); + ma_control_file_end(); + free_defaults(default_argv); + seq_storage_destroy(&seq); + if (maria_log_remove()) + exit(1); + + return (test(exit_status())); +} diff --git a/storage/maria/unittest/ma_test_loghandler_multithread-t.c b/storage/maria/unittest/ma_test_loghandler_multithread-t.c new file mode 100644 index 00000000000..354f5d12e08 --- /dev/null +++ b/storage/maria/unittest/ma_test_loghandler_multithread-t.c @@ -0,0 +1,556 @@ +/* Copyright (C) 2006-2008 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 */ + +#include "../maria_def.h" +#include <stdio.h> +#include <errno.h> +#include <tap.h> +#include "../trnman.h" + +extern my_bool maria_log_remove(); +extern void translog_example_table_init(); + +#ifndef DBUG_OFF +static const char *default_dbug_option; +#endif + +#define PCACHE_SIZE (1024*1024*10) + +#define LOG_FILE_SIZE (1024L*1024L*1024L + 1024L*1024L*512) +/*#define LOG_FLAGS TRANSLOG_SECTOR_PROTECTION | TRANSLOG_PAGE_CRC */ +#define LOG_FLAGS 0 +/*#define LONG_BUFFER_SIZE (1024L*1024L*1024L + 1024L*1024L*512)*/ + +#ifdef MULTIFLUSH_TEST + +#define LONG_BUFFER_SIZE (16384L) +#define MIN_REC_LENGTH 10 +#define SHOW_DIVIDER 20 +#define ITERATIONS 10000 +#define FLUSH_ITERATIONS 1000 +#define WRITERS 2 +#define FLUSHERS 10 + +#else + +#define LONG_BUFFER_SIZE (512L*1024L*1024L) +#define MIN_REC_LENGTH 30 +#define SHOW_DIVIDER 10 +#define ITERATIONS 3 +#define FLUSH_ITERATIONS 0 +#define WRITERS 3 +#define FLUSHERS 0 + +#endif + +static uint number_of_writers= WRITERS; +static uint number_of_flushers= FLUSHERS; + +static pthread_cond_t COND_thread_count; +static pthread_mutex_t LOCK_thread_count; +static uint thread_count; + +static ulong lens[WRITERS][ITERATIONS]; +static LSN lsns1[WRITERS][ITERATIONS]; +static LSN lsns2[WRITERS][ITERATIONS]; +static uchar *long_buffer; + + +static LSN last_lsn; /* For test purposes the variable allow dirty read/write */ + +/* + Get pseudo-random length of the field in + limits [MIN_REC_LENGTH..LONG_BUFFER_SIZE] + + SYNOPSIS + get_len() + + RETURN + length - length >= 0 length <= LONG_BUFFER_SIZE +*/ + +static uint32 get_len() +{ + return MIN_REC_LENGTH + + (uint32)(((ulonglong)rand())* + (LONG_BUFFER_SIZE - MIN_REC_LENGTH - 1)/RAND_MAX); +} + + +/* + Check that the buffer filled correctly + + SYNOPSIS + check_content() + ptr Pointer to the buffer + length length of the buffer + + RETURN + 0 - OK + 1 - Error +*/ + +static my_bool check_content(uchar *ptr, ulong length) +{ + ulong i; + for (i= 0; i < length; i++) + { + if (((uchar)ptr[i]) != (i & 0xFF)) + { + fprintf(stderr, "Byte # %lu is %x instead of %x", + i, (uint) ptr[i], (uint) (i & 0xFF)); + return 1; + } + } + return 0; +} + + +/* + Read whole record content, and check content (put with offset) + + SYNOPSIS + read_and_check_content() + rec The record header buffer + buffer The buffer to read the record in + skip Skip this number of bytes ot the record content + + RETURN + 0 - OK + 1 - Error +*/ + + +static my_bool read_and_check_content(TRANSLOG_HEADER_BUFFER *rec, + uchar *buffer, uint skip) +{ + int res= 0; + translog_size_t len; + + if ((len= translog_read_record(rec->lsn, 0, rec->record_length, + buffer, NULL)) != rec->record_length) + { + fprintf(stderr, "Requested %lu byte, read %lu\n", + (ulong) rec->record_length, (ulong) len); + res= 1; + } + res|= check_content(buffer + skip, rec->record_length - skip); + return(res); +} + +void writer(int num) +{ + LSN lsn; + TRN trn; + uchar long_tr_id[6]; + uint i; + + trn.short_id= num; + trn.first_undo_lsn= TRANSACTION_LOGGED_LONG_ID; + for (i= 0; i < ITERATIONS; i++) + { + uint len= get_len(); + LEX_CUSTRING parts[TRANSLOG_INTERNAL_PARTS + 1]; + lens[num][i]= len; + + int2store(long_tr_id, num); + int4store(long_tr_id + 2, i); + parts[TRANSLOG_INTERNAL_PARTS + 0].str= long_tr_id; + parts[TRANSLOG_INTERNAL_PARTS + 0].length= 6; + if (translog_write_record(&lsn, + LOGREC_FIXED_RECORD_0LSN_EXAMPLE, + &trn, NULL, 6, TRANSLOG_INTERNAL_PARTS + 1, + parts, NULL, NULL)) + { + fprintf(stderr, "Can't write LOGREC_FIXED_RECORD_0LSN_EXAMPLE record #%lu " + "thread %i\n", (ulong) i, num); + translog_destroy(); + pthread_mutex_lock(&LOCK_thread_count); + ok(0, "write records"); + pthread_mutex_unlock(&LOCK_thread_count); + return; + } + lsns1[num][i]= lsn; + parts[TRANSLOG_INTERNAL_PARTS + 0].str= long_buffer; + parts[TRANSLOG_INTERNAL_PARTS + 0].length= len; + if (translog_write_record(&lsn, + LOGREC_VARIABLE_RECORD_0LSN_EXAMPLE, + &trn, NULL, + len, TRANSLOG_INTERNAL_PARTS + 1, + parts, NULL, NULL)) + { + fprintf(stderr, "Can't write variable record #%lu\n", (ulong) i); + translog_destroy(); + pthread_mutex_lock(&LOCK_thread_count); + ok(0, "write records"); + pthread_mutex_unlock(&LOCK_thread_count); + return; + } + lsns2[num][i]= lsn; + last_lsn= lsn; + pthread_mutex_lock(&LOCK_thread_count); + ok(1, "write records"); + pthread_mutex_unlock(&LOCK_thread_count); + } + return; +} + + +static void *test_thread_writer(void *arg) +{ + int param= *((int*) arg); + + my_thread_init(); + + writer(param); + + pthread_mutex_lock(&LOCK_thread_count); + thread_count--; + ok(1, "writer finished"); /* just to show progress */ + VOID(pthread_cond_signal(&COND_thread_count)); /* Tell main we are + ready */ + pthread_mutex_unlock(&LOCK_thread_count); + free((uchar*) arg); + my_thread_end(); + return(0); +} + + +static void *test_thread_flusher(void *arg) +{ + int param= *((int*) arg); + int i; + + my_thread_init(); + + for(i= 0; i < FLUSH_ITERATIONS; i++) + { + translog_flush(last_lsn); + pthread_mutex_lock(&LOCK_thread_count); + ok(1, "-- flush %d", param); + pthread_mutex_unlock(&LOCK_thread_count); + } + + pthread_mutex_lock(&LOCK_thread_count); + thread_count--; + ok(1, "flusher finished"); /* just to show progress */ + VOID(pthread_cond_signal(&COND_thread_count)); /* Tell main we are + ready */ + pthread_mutex_unlock(&LOCK_thread_count); + free((uchar*) arg); + my_thread_end(); + return(0); +} + + +int main(int argc __attribute__((unused)), + char **argv __attribute__ ((unused))) +{ + uint32 i; + uint pagen; + PAGECACHE pagecache; + LSN first_lsn; + TRANSLOG_HEADER_BUFFER rec; + struct st_translog_scanner_data scanner; + pthread_t tid; + pthread_attr_t thr_attr; + int *param, error; + int rc; + + /* Disabled until Sanja tests */ + plan(1); + ok(1, "disabled"); + exit(0); + + plan(WRITERS + FLUSHERS + + ITERATIONS * WRITERS * 3 + FLUSH_ITERATIONS * FLUSHERS ); + + bzero(&pagecache, sizeof(pagecache)); + maria_data_root= (char *)"."; + long_buffer= malloc(LONG_BUFFER_SIZE + 7 * 2 + 2); + if (long_buffer == 0) + { + fprintf(stderr, "End of memory\n"); + exit(1); + } + for (i= 0; i < (LONG_BUFFER_SIZE + 7 * 2 + 2); i++) + long_buffer[i]= (i & 0xFF); + + MY_INIT(argv[0]); + if (maria_log_remove()) + exit(1); + + +#ifndef DBUG_OFF +#if defined(__WIN__) + default_dbug_option= "d:t:i:O,\\ma_test_loghandler.trace"; +#else + default_dbug_option= "d:t:i:o,/tmp/ma_test_loghandler.trace"; +#endif + if (argc > 1) + { + DBUG_SET(default_dbug_option); + DBUG_SET_INITIAL(default_dbug_option); + } +#endif + + + if ((error= pthread_cond_init(&COND_thread_count, NULL))) + { + fprintf(stderr, "COND_thread_count: %d from pthread_cond_init " + "(errno: %d)\n", error, errno); + exit(1); + } + if ((error= pthread_mutex_init(&LOCK_thread_count, MY_MUTEX_INIT_FAST))) + { + fprintf(stderr, "LOCK_thread_count: %d from pthread_cond_init " + "(errno: %d)\n", error, errno); + exit(1); + } + if ((error= pthread_attr_init(&thr_attr))) + { + fprintf(stderr, "Got error: %d from pthread_attr_init " + "(errno: %d)\n", error, errno); + exit(1); + } + if ((error= pthread_attr_setdetachstate(&thr_attr, PTHREAD_CREATE_DETACHED))) + { + fprintf(stderr, + "Got error: %d from pthread_attr_setdetachstate (errno: %d)\n", + error, errno); + exit(1); + } + +#ifdef HAVE_THR_SETCONCURRENCY + VOID(thr_setconcurrency(2)); +#endif + + my_thread_global_init(); + + if (ma_control_file_open(TRUE, TRUE)) + { + fprintf(stderr, "Can't init control file (%d)\n", errno); + exit(1); + } + if ((pagen= init_pagecache(&pagecache, PCACHE_SIZE, 0, 0, + TRANSLOG_PAGE_SIZE, 0)) == 0) + { + fprintf(stderr, "Got error: init_pagecache() (errno: %d)\n", errno); + exit(1); + } + if (translog_init_with_table(".", LOG_FILE_SIZE, 50112, 0, &pagecache, + LOG_FLAGS, 0, &translog_example_table_init, + 0)) + { + fprintf(stderr, "Can't init loghandler (%d)\n", errno); + exit(1); + } + /* Suppressing of automatic record writing */ + dummy_transaction_object.first_undo_lsn|= TRANSACTION_LOGGED_LONG_ID; + + srand(122334817L); + { + LEX_CUSTRING parts[TRANSLOG_INTERNAL_PARTS + 1]; + uchar long_tr_id[6]= + { + 0x11, 0x22, 0x33, 0x44, 0x55, 0x66 + }; + + parts[TRANSLOG_INTERNAL_PARTS + 0].str= long_tr_id; + parts[TRANSLOG_INTERNAL_PARTS + 0].length= 6; + dummy_transaction_object.first_undo_lsn= TRANSACTION_LOGGED_LONG_ID; + if (translog_write_record(&first_lsn, + LOGREC_FIXED_RECORD_0LSN_EXAMPLE, + &dummy_transaction_object, NULL, 6, + TRANSLOG_INTERNAL_PARTS + 1, + parts, NULL, NULL)) + { + fprintf(stderr, "Can't write the first record\n"); + translog_destroy(); + exit(1); + } + } + + + pthread_mutex_lock(&LOCK_thread_count); + while (number_of_writers != 0 || number_of_flushers != 0) + { + if (number_of_writers) + { + param= (int*) malloc(sizeof(int)); + *param= number_of_writers - 1; + if ((error= pthread_create(&tid, &thr_attr, test_thread_writer, + (void*) param))) + { + fprintf(stderr, "Got error: %d from pthread_create (errno: %d)\n", + error, errno); + exit(1); + } + thread_count++; + number_of_writers--; + } + if (number_of_flushers) + { + param= (int*) malloc(sizeof(int)); + *param= number_of_flushers - 1; + if ((error= pthread_create(&tid, &thr_attr, test_thread_flusher, + (void*) param))) + { + fprintf(stderr, "Got error: %d from pthread_create (errno: %d)\n", + error, errno); + exit(1); + } + thread_count++; + number_of_flushers--; + } + } + pthread_mutex_unlock(&LOCK_thread_count); + + pthread_attr_destroy(&thr_attr); + + /* wait finishing */ + pthread_mutex_lock(&LOCK_thread_count); + while (thread_count) + { + if ((error= pthread_cond_wait(&COND_thread_count, &LOCK_thread_count))) + fprintf(stderr, "COND_thread_count: %d from pthread_cond_wait\n", error); + } + pthread_mutex_unlock(&LOCK_thread_count); + + /* Find last LSN and flush up to it (all our log) */ + { + LSN max= 0; + for (i= 0; i < WRITERS; i++) + { + if (cmp_translog_addr(lsns2[i][ITERATIONS - 1], max) > 0) + max= lsns2[i][ITERATIONS - 1]; + } + translog_flush(max); + } + + rc= 1; + + { + uint indeces[WRITERS]; + uint index, stage; + int len; + bzero(indeces, sizeof(uint) * WRITERS); + + bzero(indeces, sizeof(indeces)); + + if (translog_scanner_init(first_lsn, 1, &scanner, 0)) + { + fprintf(stderr, "scanner init failed\n"); + goto err; + } + for (i= 0;; i++) + { + len= translog_read_next_record_header(&scanner, &rec); + + if (len == RECHEADER_READ_ERROR) + { + fprintf(stderr, "1-%d translog_read_next_record_header failed (%d)\n", + i, errno); + translog_free_record_header(&rec); + goto err; + } + if (len == RECHEADER_READ_EOF) + { + if (i != WRITERS * ITERATIONS * 2) + { + fprintf(stderr, "EOL met at iteration %u instead of %u\n", + i, ITERATIONS * WRITERS * 2); + translog_free_record_header(&rec); + goto err; + } + break; + } + index= indeces[rec.short_trid] / 2; + stage= indeces[rec.short_trid] % 2; + if (stage == 0) + { + if (rec.type !=LOGREC_FIXED_RECORD_0LSN_EXAMPLE || + rec.record_length != 6 || + uint2korr(rec.header) != rec.short_trid || + index != uint4korr(rec.header + 2) || + cmp_translog_addr(lsns1[rec.short_trid][index], rec.lsn) != 0) + { + fprintf(stderr, "Incorrect LOGREC_FIXED_RECORD_0LSN_EXAMPLE " + "data read(%d)\n" + "type %u, strid %u %u, len %u, i: %u %u, " + "lsn(%lu,0x%lx) (%lu,0x%lx)\n", + i, (uint) rec.type, + (uint) rec.short_trid, (uint) uint2korr(rec.header), + (uint) rec.record_length, + (uint) index, (uint) uint4korr(rec.header + 2), + LSN_IN_PARTS(rec.lsn), + LSN_IN_PARTS(lsns1[rec.short_trid][index])); + translog_free_record_header(&rec); + goto err; + } + } + else + { + if (rec.type != LOGREC_VARIABLE_RECORD_0LSN_EXAMPLE || + len != 9 || + rec.record_length != lens[rec.short_trid][index] || + cmp_translog_addr(lsns2[rec.short_trid][index], rec.lsn) != 0 || + check_content(rec.header, (uint)len)) + { + fprintf(stderr, + "Incorrect LOGREC_VARIABLE_RECORD_0LSN_EXAMPLE " + "data read(%d) " + "thread: %d, iteration %d, stage %d\n" + "type %u (%d), len %d, length %lu %lu (%d) " + "lsn(%lu,0x%lx) (%lu,0x%lx)\n", + i, (uint) rec.short_trid, index, stage, + (uint) rec.type, (rec.type != + LOGREC_VARIABLE_RECORD_0LSN_EXAMPLE), + len, + (ulong) rec.record_length, lens[rec.short_trid][index], + (rec.record_length != lens[rec.short_trid][index]), + LSN_IN_PARTS(rec.lsn), + LSN_IN_PARTS(lsns2[rec.short_trid][index])); + translog_free_record_header(&rec); + goto err; + } + if (read_and_check_content(&rec, long_buffer, 0)) + { + fprintf(stderr, + "Incorrect LOGREC_VARIABLE_RECORD_0LSN_EXAMPLE " + "in whole rec read lsn(%lu,0x%lx)\n", + LSN_IN_PARTS(rec.lsn)); + translog_free_record_header(&rec); + goto err; + } + } + ok(1, "record read"); + translog_free_record_header(&rec); + indeces[rec.short_trid]++; + } + } + + rc= 0; +err: + if (rc) + ok(0, "record read"); + translog_destroy(); + end_pagecache(&pagecache, 1); + ma_control_file_end(); + if (maria_log_remove()) + exit(1); + + return(exit_status()); +} diff --git a/storage/maria/unittest/ma_test_loghandler_noflush-t.c b/storage/maria/unittest/ma_test_loghandler_noflush-t.c new file mode 100644 index 00000000000..973dfd03bcf --- /dev/null +++ b/storage/maria/unittest/ma_test_loghandler_noflush-t.c @@ -0,0 +1,146 @@ +/* Copyright (C) 2006-2008 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 */ + +#include "../maria_def.h" +#include <stdio.h> +#include <errno.h> +#include <tap.h> +#include "../trnman.h" + +extern my_bool maria_log_remove(); +extern void translog_example_table_init(); + +#ifndef DBUG_OFF +static const char *default_dbug_option; +#endif + +#define PCACHE_SIZE (1024*1024*10) +#define PCACHE_PAGE TRANSLOG_PAGE_SIZE +#define LOG_FILE_SIZE (1024L*1024L*1024L + 1024L*1024L*512) +#define LOG_FLAGS 0 + +static char *first_translog_file= (char*)"maria_log.00000001"; + +int main(int argc __attribute__((unused)), char *argv[]) +{ + uint pagen; + int rc= 1; + uchar long_tr_id[6]; + PAGECACHE pagecache; + LSN first_lsn; + TRANSLOG_HEADER_BUFFER rec; + LEX_CUSTRING parts[TRANSLOG_INTERNAL_PARTS + 1]; + translog_size_t len; + + MY_INIT(argv[0]); + + plan(1); + + bzero(&pagecache, sizeof(pagecache)); + maria_data_root= (char *)"."; + if (maria_log_remove()) + exit(1); + /* be sure that we have no logs in the directory*/ + my_delete(CONTROL_FILE_BASE_NAME, MYF(0)); + my_delete(first_translog_file, MYF(0)); + + bzero(long_tr_id, 6); +#ifndef DBUG_OFF +#if defined(__WIN__) + default_dbug_option= "d:t:i:O,\\ma_test_loghandler.trace"; +#else + default_dbug_option= "d:t:i:o,/tmp/ma_test_loghandler.trace"; +#endif + if (argc > 1) + { + DBUG_SET(default_dbug_option); + DBUG_SET_INITIAL(default_dbug_option); + } +#endif + + if (ma_control_file_open(TRUE, TRUE)) + { + fprintf(stderr, "Can't init control file (%d)\n", errno); + exit(1); + } + if ((pagen= init_pagecache(&pagecache, PCACHE_SIZE, 0, 0, + PCACHE_PAGE, 0)) == 0) + { + fprintf(stderr, "Got error: init_pagecache() (errno: %d)\n", errno); + exit(1); + } + if (translog_init_with_table(".", LOG_FILE_SIZE, 50112, 0, &pagecache, + LOG_FLAGS, 0, &translog_example_table_init, + 0)) + { + fprintf(stderr, "Can't init loghandler (%d)\n", errno); + exit(1); + } + /* Suppressing of automatic record writing */ + dummy_transaction_object.first_undo_lsn|= TRANSACTION_LOGGED_LONG_ID; + + int4store(long_tr_id, 0); + long_tr_id[5]= 0xff; + parts[TRANSLOG_INTERNAL_PARTS + 0].str= long_tr_id; + parts[TRANSLOG_INTERNAL_PARTS + 0].length= 6; + if (translog_write_record(&first_lsn, + LOGREC_FIXED_RECORD_0LSN_EXAMPLE, + &dummy_transaction_object, NULL, 6, + TRANSLOG_INTERNAL_PARTS + 1, + parts, NULL, NULL)) + { + fprintf(stderr, "Can't write record #%lu\n", (ulong) 0); + translog_destroy(); + exit(1); + } + + len= translog_read_record_header(first_lsn, &rec); + if (len == 0) + { + fprintf(stderr, "translog_read_record_header failed (%d)\n", errno); + goto err; + } + if (rec.type !=LOGREC_FIXED_RECORD_0LSN_EXAMPLE || rec.short_trid != 0 || + rec.record_length != 6 || uint4korr(rec.header) != 0 || + ((uchar)rec.header[4]) != 0 || ((uchar)rec.header[5]) != 0xFF || + first_lsn != rec.lsn) + { + fprintf(stderr, "Incorrect LOGREC_FIXED_RECORD_0LSN_EXAMPLE " + "data read(0)\n" + "type: %u (%d) strid: %u (%d) len: %u (%d) i: %u (%d), " + "4: %u (%d) 5: %u (%d) " + "lsn(%lu,0x%lx) (%d)\n", + (uint) rec.type, (rec.type !=LOGREC_FIXED_RECORD_0LSN_EXAMPLE), + (uint) rec.short_trid, (rec.short_trid != 0), + (uint) rec.record_length, (rec.record_length != 6), + (uint) uint4korr(rec.header), (uint4korr(rec.header) != 0), + (uint) rec.header[4], (((uchar)rec.header[4]) != 0), + (uint) rec.header[5], (((uchar)rec.header[5]) != 0xFF), + LSN_IN_PARTS(rec.lsn), (first_lsn != rec.lsn)); + goto err; + } + + ok(1, "read OK"); + rc= 0; + +err: + translog_destroy(); + end_pagecache(&pagecache, 1); + ma_control_file_end(); + if (maria_log_remove()) + exit(1); + + exit(rc); +} diff --git a/storage/maria/unittest/ma_test_loghandler_nologs-t.c b/storage/maria/unittest/ma_test_loghandler_nologs-t.c new file mode 100644 index 00000000000..34508d1d751 --- /dev/null +++ b/storage/maria/unittest/ma_test_loghandler_nologs-t.c @@ -0,0 +1,195 @@ +/* Copyright (C) 2006-2008 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 */ + +#include "../maria_def.h" +#include <stdio.h> +#include <errno.h> +#include <tap.h> +#include "../trnman.h" + +extern my_bool maria_log_remove(); +extern void example_loghandler_init(); + +#ifndef DBUG_OFF +static const char *default_dbug_option; +#endif + +#define PCACHE_SIZE (1024*1024*10) +#define PCACHE_PAGE TRANSLOG_PAGE_SIZE +#define LOG_FILE_SIZE (8*1024L*1024L) +#define LOG_FLAGS 0 +#define LONG_BUFFER_SIZE (LOG_FILE_SIZE + LOG_FILE_SIZE / 2) + + +int main(int argc __attribute__((unused)), char *argv[]) +{ + ulong i; + uint pagen; + uchar long_tr_id[6]; + PAGECACHE pagecache; + LSN lsn; + LEX_CUSTRING parts[TRANSLOG_INTERNAL_PARTS + 1]; + uchar *long_buffer= malloc(LONG_BUFFER_SIZE); + + MY_INIT(argv[0]); + + plan(2); + + bzero(&pagecache, sizeof(pagecache)); + bzero(long_buffer, LONG_BUFFER_SIZE); + maria_data_root= (char *)"."; + if (maria_log_remove()) + exit(1); + + bzero(long_tr_id, 6); +#ifndef DBUG_OFF +#if defined(__WIN__) + default_dbug_option= "d:t:i:O,\\ma_test_loghandler.trace"; +#else + default_dbug_option= "d:t:i:o,/tmp/ma_test_loghandler.trace"; +#endif + if (argc > 1) + { + DBUG_SET(default_dbug_option); + DBUG_SET_INITIAL(default_dbug_option); + } +#endif + + if (ma_control_file_open(TRUE, TRUE)) + { + fprintf(stderr, "Can't init control file (%d)\n", errno); + exit(1); + } + if ((pagen= init_pagecache(&pagecache, PCACHE_SIZE, 0, 0, + PCACHE_PAGE, 0)) == 0) + { + fprintf(stderr, "Got error: init_pagecache() (errno: %d)\n", errno); + exit(1); + } + if (translog_init_with_table(".", LOG_FILE_SIZE, 50112, 0, &pagecache, + LOG_FLAGS, 0, &translog_example_table_init, + 0)) + { + fprintf(stderr, "Can't init loghandler (%d)\n", errno); + exit(1); + } + /* Suppressing of automatic record writing */ + dummy_transaction_object.first_undo_lsn|= TRANSACTION_LOGGED_LONG_ID; + + /* write more then 1 file */ + int4store(long_tr_id, 0); + parts[TRANSLOG_INTERNAL_PARTS + 0].str= long_tr_id; + parts[TRANSLOG_INTERNAL_PARTS + 0].length= 6; + if (translog_write_record(&lsn, + LOGREC_FIXED_RECORD_0LSN_EXAMPLE, + &dummy_transaction_object, NULL, 6, + TRANSLOG_INTERNAL_PARTS + 1, + parts, NULL, NULL)) + { + fprintf(stderr, "Can't write record #0\n"); + translog_destroy(); + exit(1); + } + + for(i= 0; i < LOG_FILE_SIZE/6 && LSN_FILE_NO(lsn) == 1; i++) + { + if (translog_write_record(&lsn, + LOGREC_FIXED_RECORD_0LSN_EXAMPLE, + &dummy_transaction_object, NULL, 6, + TRANSLOG_INTERNAL_PARTS + 1, + parts, NULL, NULL)) + { + fprintf(stderr, "Can't write record #0\n"); + translog_destroy(); + exit(1); + } + } + + translog_destroy(); + end_pagecache(&pagecache, 1); + ma_control_file_end(); + + { + char file_name[FN_REFLEN]; + for (i= 1; i <= 2; i++) + { + translog_filename_by_fileno(i, file_name); + if (my_access(file_name, W_OK)) + { + fprintf(stderr, "No file '%s'\n", file_name); + exit(1); + } + if (my_delete(file_name, MYF(MY_WME)) != 0) + { + fprintf(stderr, "Error %d during removing file'%s'\n", + errno, file_name); + exit(1); + } + } + } + + if (ma_control_file_open(TRUE, TRUE)) + { + fprintf(stderr, "Can't init control file (%d)\n", errno); + exit(1); + } + if ((pagen= init_pagecache(&pagecache, PCACHE_SIZE, 0, 0, + PCACHE_PAGE, 0)) == 0) + { + fprintf(stderr, "Got error: init_pagecache() (errno: %d)\n", errno); + exit(1); + } + if (translog_init_with_table(".", LOG_FILE_SIZE, 50112, 0, &pagecache, + LOG_FLAGS, 0, &translog_example_table_init, + 1)) + { + fprintf(stderr, "Can't init loghandler (%d)\n", errno); + exit(1); + } + /* Suppressing of automatic record writing */ + dummy_transaction_object.first_undo_lsn|= TRANSACTION_LOGGED_LONG_ID; + + ok(1, "Log init OK"); + + int4store(long_tr_id, 0); + parts[TRANSLOG_INTERNAL_PARTS + 0].str= long_tr_id; + parts[TRANSLOG_INTERNAL_PARTS + 0].length= 6; + if (translog_write_record(&lsn, + LOGREC_FIXED_RECORD_0LSN_EXAMPLE, + &dummy_transaction_object, NULL, 6, + TRANSLOG_INTERNAL_PARTS + 1, + parts, NULL, NULL)) + { + fprintf(stderr, "Can't write record #0\n"); + translog_destroy(); + exit(1); + } + + translog_destroy(); + end_pagecache(&pagecache, 1); + ma_control_file_end(); + + if (!translog_is_file(3)) + { + fprintf(stderr, "No file #3\n"); + exit(1); + } + + ok(1, "New log is OK"); + + if (maria_log_remove()) + exit(1); + exit(0); +} diff --git a/storage/maria/unittest/ma_test_loghandler_pagecache-t.c b/storage/maria/unittest/ma_test_loghandler_pagecache-t.c new file mode 100644 index 00000000000..bfbba5407c1 --- /dev/null +++ b/storage/maria/unittest/ma_test_loghandler_pagecache-t.c @@ -0,0 +1,200 @@ +/* Copyright (C) 2006-2008 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 */ + +#include "../maria_def.h" +#include <stdio.h> +#include <errno.h> +#include <tap.h> +#include "../trnman.h" + +extern my_bool maria_log_remove(); +extern void translog_example_table_init(); + +#ifndef DBUG_OFF +static const char *default_dbug_option; +#endif + +#define PCACHE_SIZE (1024*1024*10) +#define PCACHE_PAGE TRANSLOG_PAGE_SIZE +#define LOG_FILE_SIZE (1024L*1024L*1024L + 1024L*1024L*512) +#define LOG_FLAGS 0 + +static char *first_translog_file= (char*)"maria_log.00000001"; +static char *file1_name= (char*)"page_cache_test_file_1"; +static PAGECACHE_FILE file1; + + +/** + @brief Dummy pagecache callback. +*/ + +static my_bool +dummy_callback(uchar *page __attribute__((unused)), + pgcache_page_no_t page_no __attribute__((unused)), + uchar* data_ptr __attribute__((unused))) +{ + return 0; +} + + +/** + @brief Dummy pagecache callback. +*/ + +static void +dummy_fail_callback(uchar* data_ptr __attribute__((unused))) +{ + return; +} + + +int main(int argc __attribute__((unused)), char *argv[]) +{ + uint pagen; + uchar long_tr_id[6]; + PAGECACHE pagecache; + LSN lsn; + my_off_t file_size; + LEX_CUSTRING parts[TRANSLOG_INTERNAL_PARTS + 1]; + + MY_INIT(argv[0]); + + plan(1); + + bzero(&pagecache, sizeof(pagecache)); + maria_data_root= (char *)"."; + if (maria_log_remove()) + exit(1); + /* be sure that we have no logs in the directory*/ + my_delete(CONTROL_FILE_BASE_NAME, MYF(0)); + my_delete(first_translog_file, MYF(0)); + + bzero(long_tr_id, 6); +#ifndef DBUG_OFF +#if defined(__WIN__) + default_dbug_option= "d:t:i:O,\\ma_test_loghandler_pagecache.trace"; +#else + default_dbug_option= "d:t:i:o,/tmp/ma_test_loghandler_pagecache.trace"; +#endif + if (argc > 1) + { + DBUG_SET(default_dbug_option); + DBUG_SET_INITIAL(default_dbug_option); + } +#endif + + if (ma_control_file_open(TRUE, TRUE)) + { + fprintf(stderr, "Can't init control file (%d)\n", errno); + exit(1); + } + if ((pagen= init_pagecache(&pagecache, PCACHE_SIZE, 0, 0, + PCACHE_PAGE, 0)) == 0) + { + fprintf(stderr, "Got error: init_pagecache() (errno: %d)\n", errno); + exit(1); + } + if (translog_init_with_table(".", LOG_FILE_SIZE, 50112, 0, &pagecache, + LOG_FLAGS, 0, &translog_example_table_init, + 0)) + { + fprintf(stderr, "Can't init loghandler (%d)\n", errno); + exit(1); + } + /* Suppressing of automatic record writing */ + dummy_transaction_object.first_undo_lsn|= TRANSACTION_LOGGED_LONG_ID; + + if ((file1.file= my_open(first_translog_file, O_RDONLY, MYF(MY_WME))) < 0) + { + fprintf(stderr, "There is no %s (%d)\n", first_translog_file, errno); + exit(1); + } + file_size= my_seek(file1.file, 0, SEEK_END, MYF(MY_WME)); + if (file_size != TRANSLOG_PAGE_SIZE) + { + fprintf(stderr, + "incorrect initial size of %s: %ld instead of %ld\n", + first_translog_file, (long)file_size, (long)TRANSLOG_PAGE_SIZE); + exit(1); + } + my_close(file1.file, MYF(MY_WME)); + int4store(long_tr_id, 0); + parts[TRANSLOG_INTERNAL_PARTS + 0].str= long_tr_id; + parts[TRANSLOG_INTERNAL_PARTS + 0].length= 6; + dummy_transaction_object.first_undo_lsn= TRANSACTION_LOGGED_LONG_ID; + if (translog_write_record(&lsn, + LOGREC_FIXED_RECORD_0LSN_EXAMPLE, + &dummy_transaction_object, NULL, 6, + TRANSLOG_INTERNAL_PARTS + 1, + parts, NULL, NULL)) + { + fprintf(stderr, "Can't write record #%lu\n", (ulong) 0); + translog_destroy(); + exit(1); + } + + if ((file1.file= my_open(file1_name, + O_CREAT | O_TRUNC | O_RDWR, MYF(0))) == -1) + { + fprintf(stderr, "Got error during file1 creation from open() (errno: %d)\n", + errno); + exit(1); + } + pagecache_file_init(file1, &dummy_callback, &dummy_callback, + &dummy_fail_callback, maria_flush_log_for_page, NULL); + if (my_chmod(file1_name, S_IRWXU | S_IRWXG | S_IRWXO, MYF(MY_WME))) + exit(1); + + { + uchar page[PCACHE_PAGE]; + + bzero(page, PCACHE_PAGE); + lsn_store(page, lsn); + pagecache_write(&pagecache, &file1, 0, 3, page, + PAGECACHE_LSN_PAGE, + PAGECACHE_LOCK_LEFT_UNLOCKED, + PAGECACHE_PIN_LEFT_UNPINNED, + PAGECACHE_WRITE_DELAY, + 0, LSN_IMPOSSIBLE); + flush_pagecache_blocks(&pagecache, &file1, FLUSH_FORCE_WRITE); + } + my_close(file1.file, MYF(MY_WME)); + if ((file1.file= my_open(first_translog_file, O_RDONLY, MYF(MY_WME))) < 0) + { + fprintf(stderr, "can't open %s (%d)\n", first_translog_file, errno); + exit(1); + } + file_size= my_seek(file1.file, 0, SEEK_END, MYF(MY_WME)); + if (file_size != TRANSLOG_PAGE_SIZE * 2) + { + fprintf(stderr, + "incorrect initial size of %s: %ld instead of %ld\n", + first_translog_file, + (long)file_size, (long)(TRANSLOG_PAGE_SIZE * 2)); + ok(0, "log triggered"); + exit(1); + } + my_close(file1.file, MYF(MY_WME)); + ok(1, "log triggered"); + + translog_destroy(); + end_pagecache(&pagecache, 1); + ma_control_file_end(); + my_delete(CONTROL_FILE_BASE_NAME, MYF(0)); + my_delete(first_translog_file, MYF(0)); + my_delete(file1_name, MYF(0)); + + exit(0); +} diff --git a/storage/maria/unittest/ma_test_loghandler_purge-t.c b/storage/maria/unittest/ma_test_loghandler_purge-t.c new file mode 100644 index 00000000000..d37b45bc3ca --- /dev/null +++ b/storage/maria/unittest/ma_test_loghandler_purge-t.c @@ -0,0 +1,192 @@ +/* Copyright (C) 2006-2008 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 */ + +#include "../maria_def.h" +#include <stdio.h> +#include <errno.h> +#include <tap.h> +#include "../trnman.h" + +extern my_bool maria_log_remove(); +extern void translog_example_table_init(); + +#ifndef DBUG_OFF +static const char *default_dbug_option; +#endif + +#define PCACHE_SIZE (1024*1024*10) +#define PCACHE_PAGE TRANSLOG_PAGE_SIZE +#define LOG_FILE_SIZE (8*1024L*1024L) +#define LOG_FLAGS 0 +#define LONG_BUFFER_SIZE (LOG_FILE_SIZE + LOG_FILE_SIZE / 2) + + +int main(int argc __attribute__((unused)), char *argv[]) +{ + ulong i; + uint pagen; + uchar long_tr_id[6]; + PAGECACHE pagecache; + LSN lsn; + LEX_CUSTRING parts[TRANSLOG_INTERNAL_PARTS + 1]; + uchar *long_buffer= malloc(LONG_BUFFER_SIZE); + + MY_INIT(argv[0]); + + plan(4); + + bzero(&pagecache, sizeof(pagecache)); + bzero(long_buffer, LONG_BUFFER_SIZE); + maria_data_root= (char *)"."; + if (maria_log_remove()) + exit(1); + + bzero(long_tr_id, 6); +#ifndef DBUG_OFF +#if defined(__WIN__) + default_dbug_option= "d:t:i:O,\\ma_test_loghandler.trace"; +#else + default_dbug_option= "d:t:i:o,/tmp/ma_test_loghandler.trace"; +#endif + if (argc > 1) + { + DBUG_SET(default_dbug_option); + DBUG_SET_INITIAL(default_dbug_option); + } +#endif + + if (ma_control_file_open(TRUE, TRUE)) + { + fprintf(stderr, "Can't init control file (%d)\n", errno); + exit(1); + } + if ((pagen= init_pagecache(&pagecache, PCACHE_SIZE, 0, 0, + PCACHE_PAGE, 0)) == 0) + { + fprintf(stderr, "Got error: init_pagecache() (errno: %d)\n", errno); + exit(1); + } + if (translog_init_with_table(".", LOG_FILE_SIZE, 50112, 0, &pagecache, + LOG_FLAGS, 0, &translog_example_table_init, + 0)) + { + fprintf(stderr, "Can't init loghandler (%d)\n", errno); + exit(1); + } + /* Suppressing of automatic record writing */ + dummy_transaction_object.first_undo_lsn|= TRANSACTION_LOGGED_LONG_ID; + + /* write more then 1 file */ + int4store(long_tr_id, 0); + parts[TRANSLOG_INTERNAL_PARTS + 0].str= long_tr_id; + parts[TRANSLOG_INTERNAL_PARTS + 0].length= 6; + if (translog_write_record(&lsn, + LOGREC_FIXED_RECORD_0LSN_EXAMPLE, + &dummy_transaction_object, NULL, 6, + TRANSLOG_INTERNAL_PARTS + 1, + parts, NULL, NULL)) + { + fprintf(stderr, "Can't write record #%lu\n", (ulong) 0); + translog_destroy(); + exit(1); + } + + translog_purge(lsn); + if (!translog_is_file(1)) + { + fprintf(stderr, "First file was removed after first record\n"); + translog_destroy(); + exit(1); + } + ok(1, "First is not removed"); + + for(i= 0; i < LOG_FILE_SIZE/6 && LSN_FILE_NO(lsn) == 1; i++) + { + if (translog_write_record(&lsn, + LOGREC_FIXED_RECORD_0LSN_EXAMPLE, + &dummy_transaction_object, NULL, 6, + TRANSLOG_INTERNAL_PARTS + 1, + parts, NULL, NULL)) + { + fprintf(stderr, "Can't write record #%lu\n", (ulong) 0); + translog_destroy(); + exit(1); + } + } + + translog_purge(lsn); + if (translog_is_file(1)) + { + fprintf(stderr, "First file was not removed.\n"); + translog_destroy(); + exit(1); + } + + ok(1, "First file is removed"); + + parts[TRANSLOG_INTERNAL_PARTS + 0].str= long_buffer; + parts[TRANSLOG_INTERNAL_PARTS + 0].length= LONG_BUFFER_SIZE; + if (translog_write_record(&lsn, + LOGREC_VARIABLE_RECORD_0LSN_EXAMPLE, + &dummy_transaction_object, NULL, LONG_BUFFER_SIZE, + TRANSLOG_INTERNAL_PARTS + 1, parts, NULL, NULL)) + { + fprintf(stderr, "Can't write variable record\n"); + translog_destroy(); + exit(1); + } + + translog_purge(lsn); + if (!translog_is_file(2) || !translog_is_file(3)) + { + fprintf(stderr, "Second file (%d) or third file (%d) is not present.\n", + translog_is_file(2), translog_is_file(3)); + translog_destroy(); + exit(1); + } + + ok(1, "Second and third files are not removed"); + + int4store(long_tr_id, 0); + parts[TRANSLOG_INTERNAL_PARTS + 0].str= long_tr_id; + parts[TRANSLOG_INTERNAL_PARTS + 0].length= 6; + if (translog_write_record(&lsn, + LOGREC_FIXED_RECORD_0LSN_EXAMPLE, + &dummy_transaction_object, NULL, 6, + TRANSLOG_INTERNAL_PARTS + 1, + parts, NULL, NULL)) + { + fprintf(stderr, "Can't write last record\n"); + translog_destroy(); + exit(1); + } + + translog_purge(lsn); + if (translog_is_file(2)) + { + fprintf(stderr, "Second file is not removed\n"); + translog_destroy(); + exit(1); + } + + ok(1, "Second file is removed"); + + translog_destroy(); + end_pagecache(&pagecache, 1); + ma_control_file_end(); + if (maria_log_remove()) + exit(1); + exit(0); +} diff --git a/storage/maria/unittest/ma_test_recovery.expected b/storage/maria/unittest/ma_test_recovery.expected new file mode 100644 index 00000000000..b95575173ee --- /dev/null +++ b/storage/maria/unittest/ma_test_recovery.expected @@ -0,0 +1,1578 @@ +Testing the REDO PHASE ALONE +TEST WITH ma_test1 -s -M -T -c +applying log +testing idempotency +applying log +TEST WITH ma_test2 -s -L -K -W -P -M -T -c -d500 +applying log +testing idempotency +applying log +TEST WITH ma_test2 -s -M -T -c -b65000 +applying log +testing idempotency +applying log +TEST WITH ma_test2 -s -M -T -c -b65000 -d800 +applying log +testing idempotency +applying log +TEST WITH ma_test1 -s -M -T -c -C +applying log +testing idempotency +applying log +TEST WITH ma_test2 -s -L -K -W -P -M -T -c -d500 -C +applying log +testing idempotency +applying log +Testing the REDO AND UNDO PHASE +TEST WITH ma_test1 -s -M -T -c -N --testflag=1 (commit at end) +TEST WITH ma_test1 -s -M -T -c -N --testflag=2 --test-undo=1 (additional aborted work) +Terminating after inserts +Dying on request without maria_commit()/maria_close() +applying log +testing idempotency +applying log +testing applying of CLRs to recreate table +applying log +TEST WITH ma_test1 -s -M -T -c -N --testflag=3 (commit at end) +Terminating after updates +TEST WITH ma_test1 -s -M -T -c -N --testflag=4 --test-undo=1 (additional aborted work) +Terminating after deletes +Dying on request without maria_commit()/maria_close() +applying log +testing idempotency +applying log +testing applying of CLRs to recreate table +applying log +TEST WITH ma_test1 -s -M -T -c -N --versioning --testflag=3 (commit at end) +Terminating after updates +TEST WITH ma_test1 -s -M -T -c -N --versioning --testflag=4 --test-undo=1 (additional aborted work) +Terminating after deletes +Dying on request without maria_commit()/maria_close() +applying log +testing idempotency +applying log +testing applying of CLRs to recreate table +applying log +TEST WITH ma_test1 -s -M -T -c -N --testflag=2 (commit at end) +Terminating after inserts +TEST WITH ma_test1 -s -M -T -c -N --testflag=3 --test-undo=1 (additional aborted work) +Terminating after updates +Dying on request without maria_commit()/maria_close() +applying log +testing idempotency +applying log +testing applying of CLRs to recreate table +applying log +TEST WITH ma_test2 -s -L -K -W -P -M -T -c -t1 (commit at end) +TEST WITH ma_test2 -s -L -K -W -P -M -T -c -t2 -A1 (additional aborted work) +Dying on request without maria_commit()/maria_close() +applying log +Differences in maria_chk -dvv, recovery not yet perfect ! +========DIFF START======= +6c6 +< Status: checked,analyzed,optimized keys,sorted index pages,zerofilled,movable +--- +> Status: changed +========DIFF END======= +testing idempotency +applying log +Differences in maria_chk -dvv, recovery not yet perfect ! +========DIFF START======= +6c6 +< Status: checked,analyzed,optimized keys,sorted index pages,zerofilled,movable +--- +> Status: changed +========DIFF END======= +testing applying of CLRs to recreate table +applying log +Differences in maria_chk -dvv, recovery not yet perfect ! +========DIFF START======= +6c6 +< Status: checked,analyzed,optimized keys,sorted index pages,zerofilled,movable +--- +> Status: changed +========DIFF END======= +TEST WITH ma_test2 -s -L -K -W -P -M -T -c -t1 (commit at end) +TEST WITH ma_test2 -s -L -K -W -P -M -T -c -t6 -A1 (additional aborted work) +Dying on request without maria_commit()/maria_close() +applying log +Differences in maria_chk -dvv, recovery not yet perfect ! +========DIFF START======= +6c6 +< Status: checked,analyzed,optimized keys,sorted index pages,zerofilled,movable +--- +> Status: changed +========DIFF END======= +testing idempotency +applying log +Differences in maria_chk -dvv, recovery not yet perfect ! +========DIFF START======= +6c6 +< Status: checked,analyzed,optimized keys,sorted index pages,zerofilled,movable +--- +> Status: changed +========DIFF END======= +testing applying of CLRs to recreate table +applying log +Differences in maria_chk -dvv, recovery not yet perfect ! +========DIFF START======= +6c6 +< Status: checked,analyzed,optimized keys,sorted index pages,zerofilled,movable +--- +> Status: changed +========DIFF END======= +TEST WITH ma_test1 -s -M -T -c -N --testflag=1 (commit at end) +TEST WITH ma_test1 -s -M -T -c -N --testflag=2 --test-undo=2 (additional aborted work) +Terminating after inserts +Dying on request without maria_commit()/maria_close() +applying log +testing idempotency +applying log +testing applying of CLRs to recreate table +applying log +TEST WITH ma_test1 -s -M -T -c -N --testflag=3 (commit at end) +Terminating after updates +TEST WITH ma_test1 -s -M -T -c -N --testflag=4 --test-undo=2 (additional aborted work) +Terminating after deletes +Dying on request without maria_commit()/maria_close() +applying log +testing idempotency +applying log +testing applying of CLRs to recreate table +applying log +TEST WITH ma_test1 -s -M -T -c -N --versioning --testflag=3 (commit at end) +Terminating after updates +TEST WITH ma_test1 -s -M -T -c -N --versioning --testflag=4 --test-undo=2 (additional aborted work) +Terminating after deletes +Dying on request without maria_commit()/maria_close() +applying log +testing idempotency +applying log +testing applying of CLRs to recreate table +applying log +TEST WITH ma_test1 -s -M -T -c -N --testflag=2 (commit at end) +Terminating after inserts +TEST WITH ma_test1 -s -M -T -c -N --testflag=3 --test-undo=2 (additional aborted work) +Terminating after updates +Dying on request without maria_commit()/maria_close() +applying log +testing idempotency +applying log +testing applying of CLRs to recreate table +applying log +TEST WITH ma_test2 -s -L -K -W -P -M -T -c -t1 (commit at end) +TEST WITH ma_test2 -s -L -K -W -P -M -T -c -t2 -A2 (additional aborted work) +Dying on request without maria_commit()/maria_close() +applying log +Differences in maria_chk -dvv, recovery not yet perfect ! +========DIFF START======= +6c6 +< Status: checked,analyzed,optimized keys,sorted index pages,zerofilled,movable +--- +> Status: changed +========DIFF END======= +testing idempotency +applying log +Differences in maria_chk -dvv, recovery not yet perfect ! +========DIFF START======= +6c6 +< Status: checked,analyzed,optimized keys,sorted index pages,zerofilled,movable +--- +> Status: changed +========DIFF END======= +testing applying of CLRs to recreate table +applying log +Differences in maria_chk -dvv, recovery not yet perfect ! +========DIFF START======= +6c6 +< Status: checked,analyzed,optimized keys,sorted index pages,zerofilled,movable +--- +> Status: changed +========DIFF END======= +TEST WITH ma_test2 -s -L -K -W -P -M -T -c -t1 (commit at end) +TEST WITH ma_test2 -s -L -K -W -P -M -T -c -t6 -A2 (additional aborted work) +Dying on request without maria_commit()/maria_close() +applying log +Differences in maria_chk -dvv, recovery not yet perfect ! +========DIFF START======= +6c6 +< Status: checked,analyzed,optimized keys,sorted index pages,zerofilled,movable +--- +> Status: changed +========DIFF END======= +testing idempotency +applying log +Differences in maria_chk -dvv, recovery not yet perfect ! +========DIFF START======= +6c6 +< Status: checked,analyzed,optimized keys,sorted index pages,zerofilled,movable +--- +> Status: changed +========DIFF END======= +testing applying of CLRs to recreate table +applying log +Differences in maria_chk -dvv, recovery not yet perfect ! +========DIFF START======= +6c6 +< Status: checked,analyzed,optimized keys,sorted index pages,zerofilled,movable +--- +> Status: changed +========DIFF END======= +TEST WITH ma_test1 -s -M -T -c -N --testflag=1 (commit at end) +TEST WITH ma_test1 -s -M -T -c -N --testflag=2 --test-undo=3 (additional aborted work) +Terminating after inserts +Dying on request without maria_commit()/maria_close() +applying log +testing idempotency +applying log +testing applying of CLRs to recreate table +applying log +TEST WITH ma_test1 -s -M -T -c -N --testflag=3 (commit at end) +Terminating after updates +TEST WITH ma_test1 -s -M -T -c -N --testflag=4 --test-undo=3 (additional aborted work) +Terminating after deletes +Dying on request without maria_commit()/maria_close() +applying log +testing idempotency +applying log +testing applying of CLRs to recreate table +applying log +TEST WITH ma_test1 -s -M -T -c -N --versioning --testflag=3 (commit at end) +Terminating after updates +TEST WITH ma_test1 -s -M -T -c -N --versioning --testflag=4 --test-undo=3 (additional aborted work) +Terminating after deletes +Dying on request without maria_commit()/maria_close() +applying log +testing idempotency +applying log +testing applying of CLRs to recreate table +applying log +TEST WITH ma_test1 -s -M -T -c -N --testflag=2 (commit at end) +Terminating after inserts +TEST WITH ma_test1 -s -M -T -c -N --testflag=3 --test-undo=3 (additional aborted work) +Terminating after updates +Dying on request without maria_commit()/maria_close() +applying log +testing idempotency +applying log +testing applying of CLRs to recreate table +applying log +TEST WITH ma_test2 -s -L -K -W -P -M -T -c -t1 (commit at end) +TEST WITH ma_test2 -s -L -K -W -P -M -T -c -t2 -A3 (additional aborted work) +Dying on request without maria_commit()/maria_close() +applying log +Differences in maria_chk -dvv, recovery not yet perfect ! +========DIFF START======= +6c6 +< Status: checked,analyzed,optimized keys,sorted index pages,zerofilled,movable +--- +> Status: changed +========DIFF END======= +testing idempotency +applying log +Differences in maria_chk -dvv, recovery not yet perfect ! +========DIFF START======= +6c6 +< Status: checked,analyzed,optimized keys,sorted index pages,zerofilled,movable +--- +> Status: changed +========DIFF END======= +testing applying of CLRs to recreate table +applying log +Differences in maria_chk -dvv, recovery not yet perfect ! +========DIFF START======= +6c6 +< Status: checked,analyzed,optimized keys,sorted index pages,zerofilled,movable +--- +> Status: changed +========DIFF END======= +TEST WITH ma_test2 -s -L -K -W -P -M -T -c -t1 (commit at end) +TEST WITH ma_test2 -s -L -K -W -P -M -T -c -t6 -A3 (additional aborted work) +Dying on request without maria_commit()/maria_close() +applying log +Differences in maria_chk -dvv, recovery not yet perfect ! +========DIFF START======= +6c6 +< Status: checked,analyzed,optimized keys,sorted index pages,zerofilled,movable +--- +> Status: changed +========DIFF END======= +testing idempotency +applying log +Differences in maria_chk -dvv, recovery not yet perfect ! +========DIFF START======= +6c6 +< Status: checked,analyzed,optimized keys,sorted index pages,zerofilled,movable +--- +> Status: changed +========DIFF END======= +testing applying of CLRs to recreate table +applying log +Differences in maria_chk -dvv, recovery not yet perfect ! +========DIFF START======= +6c6 +< Status: checked,analyzed,optimized keys,sorted index pages,zerofilled,movable +--- +> Status: changed +========DIFF END======= +TEST WITH ma_test1 -s -M -T -c -N --testflag=1 (commit at end) +TEST WITH ma_test1 -s -M -T -c -N --testflag=2 --test-undo=4 (additional aborted work) +Terminating after inserts +Dying on request without maria_commit()/maria_close() +applying log +testing idempotency +applying log +testing applying of CLRs to recreate table +applying log +TEST WITH ma_test1 -s -M -T -c -N --testflag=3 (commit at end) +Terminating after updates +TEST WITH ma_test1 -s -M -T -c -N --testflag=4 --test-undo=4 (additional aborted work) +Terminating after deletes +Dying on request without maria_commit()/maria_close() +applying log +testing idempotency +applying log +testing applying of CLRs to recreate table +applying log +TEST WITH ma_test1 -s -M -T -c -N --versioning --testflag=3 (commit at end) +Terminating after updates +TEST WITH ma_test1 -s -M -T -c -N --versioning --testflag=4 --test-undo=4 (additional aborted work) +Terminating after deletes +Dying on request without maria_commit()/maria_close() +applying log +testing idempotency +applying log +testing applying of CLRs to recreate table +applying log +TEST WITH ma_test1 -s -M -T -c -N --testflag=2 (commit at end) +Terminating after inserts +TEST WITH ma_test1 -s -M -T -c -N --testflag=3 --test-undo=4 (additional aborted work) +Terminating after updates +Dying on request without maria_commit()/maria_close() +applying log +testing idempotency +applying log +testing applying of CLRs to recreate table +applying log +TEST WITH ma_test2 -s -L -K -W -P -M -T -c -t1 (commit at end) +TEST WITH ma_test2 -s -L -K -W -P -M -T -c -t2 -A4 (additional aborted work) +Dying on request without maria_commit()/maria_close() +applying log +Differences in maria_chk -dvv, recovery not yet perfect ! +========DIFF START======= +6c6 +< Status: checked,analyzed,optimized keys,sorted index pages,zerofilled,movable +--- +> Status: changed +========DIFF END======= +testing idempotency +applying log +Differences in maria_chk -dvv, recovery not yet perfect ! +========DIFF START======= +6c6 +< Status: checked,analyzed,optimized keys,sorted index pages,zerofilled,movable +--- +> Status: changed +========DIFF END======= +testing applying of CLRs to recreate table +applying log +Differences in maria_chk -dvv, recovery not yet perfect ! +========DIFF START======= +6c6 +< Status: checked,analyzed,optimized keys,sorted index pages,zerofilled,movable +--- +> Status: changed +========DIFF END======= +TEST WITH ma_test2 -s -L -K -W -P -M -T -c -t1 (commit at end) +TEST WITH ma_test2 -s -L -K -W -P -M -T -c -t6 -A4 (additional aborted work) +Dying on request without maria_commit()/maria_close() +applying log +Differences in maria_chk -dvv, recovery not yet perfect ! +========DIFF START======= +6c6 +< Status: checked,analyzed,optimized keys,sorted index pages,zerofilled,movable +--- +> Status: changed +========DIFF END======= +testing idempotency +applying log +Differences in maria_chk -dvv, recovery not yet perfect ! +========DIFF START======= +6c6 +< Status: checked,analyzed,optimized keys,sorted index pages,zerofilled,movable +--- +> Status: changed +========DIFF END======= +testing applying of CLRs to recreate table +applying log +Differences in maria_chk -dvv, recovery not yet perfect ! +========DIFF START======= +6c6 +< Status: checked,analyzed,optimized keys,sorted index pages,zerofilled,movable +--- +> Status: changed +========DIFF END======= +TEST WITH ma_test1 -s -M -T -c -N -b32768 --testflag=1 (commit at end) +TEST WITH ma_test1 -s -M -T -c -N -b32768 --testflag=2 --test-undo=1 (additional aborted work) +Terminating after inserts +Dying on request without maria_commit()/maria_close() +applying log +testing idempotency +applying log +testing applying of CLRs to recreate table +applying log +TEST WITH ma_test1 -s -M -T -c -N -b32768 --testflag=3 (commit at end) +Terminating after updates +TEST WITH ma_test1 -s -M -T -c -N -b32768 --testflag=4 --test-undo=1 (additional aborted work) +Terminating after deletes +Dying on request without maria_commit()/maria_close() +applying log +testing idempotency +applying log +testing applying of CLRs to recreate table +applying log +TEST WITH ma_test1 -s -M -T -c -N -b32768 --versioning --testflag=3 (commit at end) +Terminating after updates +TEST WITH ma_test1 -s -M -T -c -N -b32768 --versioning --testflag=4 --test-undo=1 (additional aborted work) +Terminating after deletes +Dying on request without maria_commit()/maria_close() +applying log +testing idempotency +applying log +testing applying of CLRs to recreate table +applying log +TEST WITH ma_test1 -s -M -T -c -N -b32768 --testflag=2 (commit at end) +Terminating after inserts +TEST WITH ma_test1 -s -M -T -c -N -b32768 --testflag=3 --test-undo=1 (additional aborted work) +Terminating after updates +Dying on request without maria_commit()/maria_close() +applying log +testing idempotency +applying log +testing applying of CLRs to recreate table +applying log +TEST WITH ma_test2 -s -L -K -W -P -M -T -c -b32768 -t1 (commit at end) +TEST WITH ma_test2 -s -L -K -W -P -M -T -c -b32768 -t2 -A1 (additional aborted work) +Dying on request without maria_commit()/maria_close() +applying log +Differences in maria_chk -dvv, recovery not yet perfect ! +========DIFF START======= +6c6 +< Status: checked,analyzed,optimized keys,sorted index pages,zerofilled,movable +--- +> Status: changed +========DIFF END======= +testing idempotency +applying log +Differences in maria_chk -dvv, recovery not yet perfect ! +========DIFF START======= +6c6 +< Status: checked,analyzed,optimized keys,sorted index pages,zerofilled,movable +--- +> Status: changed +========DIFF END======= +testing applying of CLRs to recreate table +applying log +Differences in maria_chk -dvv, recovery not yet perfect ! +========DIFF START======= +6c6 +< Status: checked,analyzed,optimized keys,sorted index pages,zerofilled,movable +--- +> Status: changed +========DIFF END======= +TEST WITH ma_test2 -s -L -K -W -P -M -T -c -b32768 -t1 (commit at end) +TEST WITH ma_test2 -s -L -K -W -P -M -T -c -b32768 -t6 -A1 (additional aborted work) +Dying on request without maria_commit()/maria_close() +applying log +Differences in maria_chk -dvv, recovery not yet perfect ! +========DIFF START======= +6c6 +< Status: checked,analyzed,optimized keys,sorted index pages,zerofilled,movable +--- +> Status: changed +========DIFF END======= +testing idempotency +applying log +Differences in maria_chk -dvv, recovery not yet perfect ! +========DIFF START======= +6c6 +< Status: checked,analyzed,optimized keys,sorted index pages,zerofilled,movable +--- +> Status: changed +========DIFF END======= +testing applying of CLRs to recreate table +applying log +Differences in maria_chk -dvv, recovery not yet perfect ! +========DIFF START======= +6c6 +< Status: checked,analyzed,optimized keys,sorted index pages,zerofilled,movable +--- +> Status: changed +========DIFF END======= +TEST WITH ma_test1 -s -M -T -c -N -b32768 --testflag=1 (commit at end) +TEST WITH ma_test1 -s -M -T -c -N -b32768 --testflag=2 --test-undo=2 (additional aborted work) +Terminating after inserts +Dying on request without maria_commit()/maria_close() +applying log +testing idempotency +applying log +testing applying of CLRs to recreate table +applying log +TEST WITH ma_test1 -s -M -T -c -N -b32768 --testflag=3 (commit at end) +Terminating after updates +TEST WITH ma_test1 -s -M -T -c -N -b32768 --testflag=4 --test-undo=2 (additional aborted work) +Terminating after deletes +Dying on request without maria_commit()/maria_close() +applying log +testing idempotency +applying log +testing applying of CLRs to recreate table +applying log +TEST WITH ma_test1 -s -M -T -c -N -b32768 --versioning --testflag=3 (commit at end) +Terminating after updates +TEST WITH ma_test1 -s -M -T -c -N -b32768 --versioning --testflag=4 --test-undo=2 (additional aborted work) +Terminating after deletes +Dying on request without maria_commit()/maria_close() +applying log +testing idempotency +applying log +testing applying of CLRs to recreate table +applying log +TEST WITH ma_test1 -s -M -T -c -N -b32768 --testflag=2 (commit at end) +Terminating after inserts +TEST WITH ma_test1 -s -M -T -c -N -b32768 --testflag=3 --test-undo=2 (additional aborted work) +Terminating after updates +Dying on request without maria_commit()/maria_close() +applying log +testing idempotency +applying log +testing applying of CLRs to recreate table +applying log +TEST WITH ma_test2 -s -L -K -W -P -M -T -c -b32768 -t1 (commit at end) +TEST WITH ma_test2 -s -L -K -W -P -M -T -c -b32768 -t2 -A2 (additional aborted work) +Dying on request without maria_commit()/maria_close() +applying log +Differences in maria_chk -dvv, recovery not yet perfect ! +========DIFF START======= +6c6 +< Status: checked,analyzed,optimized keys,sorted index pages,zerofilled,movable +--- +> Status: changed +========DIFF END======= +testing idempotency +applying log +Differences in maria_chk -dvv, recovery not yet perfect ! +========DIFF START======= +6c6 +< Status: checked,analyzed,optimized keys,sorted index pages,zerofilled,movable +--- +> Status: changed +========DIFF END======= +testing applying of CLRs to recreate table +applying log +Differences in maria_chk -dvv, recovery not yet perfect ! +========DIFF START======= +6c6 +< Status: checked,analyzed,optimized keys,sorted index pages,zerofilled,movable +--- +> Status: changed +========DIFF END======= +TEST WITH ma_test2 -s -L -K -W -P -M -T -c -b32768 -t1 (commit at end) +TEST WITH ma_test2 -s -L -K -W -P -M -T -c -b32768 -t6 -A2 (additional aborted work) +Dying on request without maria_commit()/maria_close() +applying log +Differences in maria_chk -dvv, recovery not yet perfect ! +========DIFF START======= +6c6 +< Status: checked,analyzed,optimized keys,sorted index pages,zerofilled,movable +--- +> Status: changed +========DIFF END======= +testing idempotency +applying log +Differences in maria_chk -dvv, recovery not yet perfect ! +========DIFF START======= +6c6 +< Status: checked,analyzed,optimized keys,sorted index pages,zerofilled,movable +--- +> Status: changed +========DIFF END======= +testing applying of CLRs to recreate table +applying log +Differences in maria_chk -dvv, recovery not yet perfect ! +========DIFF START======= +6c6 +< Status: checked,analyzed,optimized keys,sorted index pages,zerofilled,movable +--- +> Status: changed +========DIFF END======= +TEST WITH ma_test1 -s -M -T -c -N -b32768 --testflag=1 (commit at end) +TEST WITH ma_test1 -s -M -T -c -N -b32768 --testflag=2 --test-undo=3 (additional aborted work) +Terminating after inserts +Dying on request without maria_commit()/maria_close() +applying log +testing idempotency +applying log +testing applying of CLRs to recreate table +applying log +TEST WITH ma_test1 -s -M -T -c -N -b32768 --testflag=3 (commit at end) +Terminating after updates +TEST WITH ma_test1 -s -M -T -c -N -b32768 --testflag=4 --test-undo=3 (additional aborted work) +Terminating after deletes +Dying on request without maria_commit()/maria_close() +applying log +testing idempotency +applying log +testing applying of CLRs to recreate table +applying log +TEST WITH ma_test1 -s -M -T -c -N -b32768 --versioning --testflag=3 (commit at end) +Terminating after updates +TEST WITH ma_test1 -s -M -T -c -N -b32768 --versioning --testflag=4 --test-undo=3 (additional aborted work) +Terminating after deletes +Dying on request without maria_commit()/maria_close() +applying log +testing idempotency +applying log +testing applying of CLRs to recreate table +applying log +TEST WITH ma_test1 -s -M -T -c -N -b32768 --testflag=2 (commit at end) +Terminating after inserts +TEST WITH ma_test1 -s -M -T -c -N -b32768 --testflag=3 --test-undo=3 (additional aborted work) +Terminating after updates +Dying on request without maria_commit()/maria_close() +applying log +testing idempotency +applying log +testing applying of CLRs to recreate table +applying log +TEST WITH ma_test2 -s -L -K -W -P -M -T -c -b32768 -t1 (commit at end) +TEST WITH ma_test2 -s -L -K -W -P -M -T -c -b32768 -t2 -A3 (additional aborted work) +Dying on request without maria_commit()/maria_close() +applying log +Differences in maria_chk -dvv, recovery not yet perfect ! +========DIFF START======= +6c6 +< Status: checked,analyzed,optimized keys,sorted index pages,zerofilled,movable +--- +> Status: changed +========DIFF END======= +testing idempotency +applying log +Differences in maria_chk -dvv, recovery not yet perfect ! +========DIFF START======= +6c6 +< Status: checked,analyzed,optimized keys,sorted index pages,zerofilled,movable +--- +> Status: changed +========DIFF END======= +testing applying of CLRs to recreate table +applying log +Differences in maria_chk -dvv, recovery not yet perfect ! +========DIFF START======= +6c6 +< Status: checked,analyzed,optimized keys,sorted index pages,zerofilled,movable +--- +> Status: changed +========DIFF END======= +TEST WITH ma_test2 -s -L -K -W -P -M -T -c -b32768 -t1 (commit at end) +TEST WITH ma_test2 -s -L -K -W -P -M -T -c -b32768 -t6 -A3 (additional aborted work) +Dying on request without maria_commit()/maria_close() +applying log +Differences in maria_chk -dvv, recovery not yet perfect ! +========DIFF START======= +6c6 +< Status: checked,analyzed,optimized keys,sorted index pages,zerofilled,movable +--- +> Status: changed +========DIFF END======= +testing idempotency +applying log +Differences in maria_chk -dvv, recovery not yet perfect ! +========DIFF START======= +6c6 +< Status: checked,analyzed,optimized keys,sorted index pages,zerofilled,movable +--- +> Status: changed +========DIFF END======= +testing applying of CLRs to recreate table +applying log +Differences in maria_chk -dvv, recovery not yet perfect ! +========DIFF START======= +6c6 +< Status: checked,analyzed,optimized keys,sorted index pages,zerofilled,movable +--- +> Status: changed +========DIFF END======= +TEST WITH ma_test1 -s -M -T -c -N -b32768 --testflag=1 (commit at end) +TEST WITH ma_test1 -s -M -T -c -N -b32768 --testflag=2 --test-undo=4 (additional aborted work) +Terminating after inserts +Dying on request without maria_commit()/maria_close() +applying log +testing idempotency +applying log +testing applying of CLRs to recreate table +applying log +TEST WITH ma_test1 -s -M -T -c -N -b32768 --testflag=3 (commit at end) +Terminating after updates +TEST WITH ma_test1 -s -M -T -c -N -b32768 --testflag=4 --test-undo=4 (additional aborted work) +Terminating after deletes +Dying on request without maria_commit()/maria_close() +applying log +testing idempotency +applying log +testing applying of CLRs to recreate table +applying log +TEST WITH ma_test1 -s -M -T -c -N -b32768 --versioning --testflag=3 (commit at end) +Terminating after updates +TEST WITH ma_test1 -s -M -T -c -N -b32768 --versioning --testflag=4 --test-undo=4 (additional aborted work) +Terminating after deletes +Dying on request without maria_commit()/maria_close() +applying log +testing idempotency +applying log +testing applying of CLRs to recreate table +applying log +TEST WITH ma_test1 -s -M -T -c -N -b32768 --testflag=2 (commit at end) +Terminating after inserts +TEST WITH ma_test1 -s -M -T -c -N -b32768 --testflag=3 --test-undo=4 (additional aborted work) +Terminating after updates +Dying on request without maria_commit()/maria_close() +applying log +testing idempotency +applying log +testing applying of CLRs to recreate table +applying log +TEST WITH ma_test2 -s -L -K -W -P -M -T -c -b32768 -t1 (commit at end) +TEST WITH ma_test2 -s -L -K -W -P -M -T -c -b32768 -t2 -A4 (additional aborted work) +Dying on request without maria_commit()/maria_close() +applying log +Differences in maria_chk -dvv, recovery not yet perfect ! +========DIFF START======= +6c6 +< Status: checked,analyzed,optimized keys,sorted index pages,zerofilled,movable +--- +> Status: changed +========DIFF END======= +testing idempotency +applying log +Differences in maria_chk -dvv, recovery not yet perfect ! +========DIFF START======= +6c6 +< Status: checked,analyzed,optimized keys,sorted index pages,zerofilled,movable +--- +> Status: changed +========DIFF END======= +testing applying of CLRs to recreate table +applying log +Differences in maria_chk -dvv, recovery not yet perfect ! +========DIFF START======= +6c6 +< Status: checked,analyzed,optimized keys,sorted index pages,zerofilled,movable +--- +> Status: changed +========DIFF END======= +TEST WITH ma_test2 -s -L -K -W -P -M -T -c -b32768 -t1 (commit at end) +TEST WITH ma_test2 -s -L -K -W -P -M -T -c -b32768 -t6 -A4 (additional aborted work) +Dying on request without maria_commit()/maria_close() +applying log +Differences in maria_chk -dvv, recovery not yet perfect ! +========DIFF START======= +6c6 +< Status: checked,analyzed,optimized keys,sorted index pages,zerofilled,movable +--- +> Status: changed +========DIFF END======= +testing idempotency +applying log +Differences in maria_chk -dvv, recovery not yet perfect ! +========DIFF START======= +6c6 +< Status: checked,analyzed,optimized keys,sorted index pages,zerofilled,movable +--- +> Status: changed +========DIFF END======= +testing applying of CLRs to recreate table +applying log +Differences in maria_chk -dvv, recovery not yet perfect ! +========DIFF START======= +6c6 +< Status: checked,analyzed,optimized keys,sorted index pages,zerofilled,movable +--- +> Status: changed +========DIFF END======= +TEST WITH ma_test1 -s -M -T -c -N -H1 --testflag=1 (commit at end) +TEST WITH ma_test1 -s -M -T -c -N -H1 --testflag=2 --test-undo=1 (additional aborted work) +Terminating after inserts +Dying on request without maria_commit()/maria_close() +applying log +testing idempotency +applying log +testing applying of CLRs to recreate table +applying log +TEST WITH ma_test1 -s -M -T -c -N -H2 --testflag=3 (commit at end) +Terminating after updates +TEST WITH ma_test1 -s -M -T -c -N -H2 --testflag=4 --test-undo=1 (additional aborted work) +Terminating after deletes +Dying on request without maria_commit()/maria_close() +applying log +testing idempotency +applying log +testing applying of CLRs to recreate table +applying log +TEST WITH ma_test1 -s -M -T -c -N -H2 --versioning --testflag=3 (commit at end) +Terminating after updates +TEST WITH ma_test1 -s -M -T -c -N -H2 --versioning --testflag=4 --test-undo=1 (additional aborted work) +Terminating after deletes +Dying on request without maria_commit()/maria_close() +applying log +testing idempotency +applying log +testing applying of CLRs to recreate table +applying log +TEST WITH ma_test1 -s -M -T -c -N -H2 --testflag=2 (commit at end) +Terminating after inserts +TEST WITH ma_test1 -s -M -T -c -N -H2 --testflag=3 --test-undo=1 (additional aborted work) +Terminating after updates +Dying on request without maria_commit()/maria_close() +applying log +testing idempotency +applying log +testing applying of CLRs to recreate table +applying log +TEST WITH ma_test2 -s -L -K -W -P -M -T -c -H1 -t1 (commit at end) +TEST WITH ma_test2 -s -L -K -W -P -M -T -c -H1 -t2 -A1 (additional aborted work) +Dying on request without maria_commit()/maria_close() +applying log +Differences in maria_chk -dvv, recovery not yet perfect ! +========DIFF START======= +6c6 +< Status: checked,analyzed,optimized keys,sorted index pages,zerofilled,movable +--- +> Status: changed +========DIFF END======= +testing idempotency +applying log +Differences in maria_chk -dvv, recovery not yet perfect ! +========DIFF START======= +6c6 +< Status: checked,analyzed,optimized keys,sorted index pages,zerofilled,movable +--- +> Status: changed +========DIFF END======= +testing applying of CLRs to recreate table +applying log +Differences in maria_chk -dvv, recovery not yet perfect ! +========DIFF START======= +6c6 +< Status: checked,analyzed,optimized keys,sorted index pages,zerofilled,movable +--- +> Status: changed +========DIFF END======= +TEST WITH ma_test2 -s -L -K -W -P -M -T -c -H1 -t1 (commit at end) +TEST WITH ma_test2 -s -L -K -W -P -M -T -c -H1 -t6 -A1 (additional aborted work) +Dying on request without maria_commit()/maria_close() +applying log +Differences in maria_chk -dvv, recovery not yet perfect ! +========DIFF START======= +6c6 +< Status: checked,analyzed,optimized keys,sorted index pages,zerofilled,movable +--- +> Status: changed +========DIFF END======= +testing idempotency +applying log +Differences in maria_chk -dvv, recovery not yet perfect ! +========DIFF START======= +6c6 +< Status: checked,analyzed,optimized keys,sorted index pages,zerofilled,movable +--- +> Status: changed +========DIFF END======= +testing applying of CLRs to recreate table +applying log +Differences in maria_chk -dvv, recovery not yet perfect ! +========DIFF START======= +6c6 +< Status: checked,analyzed,optimized keys,sorted index pages,zerofilled,movable +--- +> Status: changed +========DIFF END======= +TEST WITH ma_test1 -s -M -T -c -N -H1 --testflag=1 (commit at end) +TEST WITH ma_test1 -s -M -T -c -N -H1 --testflag=2 --test-undo=2 (additional aborted work) +Terminating after inserts +Dying on request without maria_commit()/maria_close() +applying log +testing idempotency +applying log +testing applying of CLRs to recreate table +applying log +TEST WITH ma_test1 -s -M -T -c -N -H2 --testflag=3 (commit at end) +Terminating after updates +TEST WITH ma_test1 -s -M -T -c -N -H2 --testflag=4 --test-undo=2 (additional aborted work) +Terminating after deletes +Dying on request without maria_commit()/maria_close() +applying log +testing idempotency +applying log +testing applying of CLRs to recreate table +applying log +TEST WITH ma_test1 -s -M -T -c -N -H2 --versioning --testflag=3 (commit at end) +Terminating after updates +TEST WITH ma_test1 -s -M -T -c -N -H2 --versioning --testflag=4 --test-undo=2 (additional aborted work) +Terminating after deletes +Dying on request without maria_commit()/maria_close() +applying log +testing idempotency +applying log +testing applying of CLRs to recreate table +applying log +TEST WITH ma_test1 -s -M -T -c -N -H2 --testflag=2 (commit at end) +Terminating after inserts +TEST WITH ma_test1 -s -M -T -c -N -H2 --testflag=3 --test-undo=2 (additional aborted work) +Terminating after updates +Dying on request without maria_commit()/maria_close() +applying log +testing idempotency +applying log +testing applying of CLRs to recreate table +applying log +TEST WITH ma_test2 -s -L -K -W -P -M -T -c -H1 -t1 (commit at end) +TEST WITH ma_test2 -s -L -K -W -P -M -T -c -H1 -t2 -A2 (additional aborted work) +Dying on request without maria_commit()/maria_close() +applying log +Differences in maria_chk -dvv, recovery not yet perfect ! +========DIFF START======= +6c6 +< Status: checked,analyzed,optimized keys,sorted index pages,zerofilled,movable +--- +> Status: changed +========DIFF END======= +testing idempotency +applying log +Differences in maria_chk -dvv, recovery not yet perfect ! +========DIFF START======= +6c6 +< Status: checked,analyzed,optimized keys,sorted index pages,zerofilled,movable +--- +> Status: changed +========DIFF END======= +testing applying of CLRs to recreate table +applying log +Differences in maria_chk -dvv, recovery not yet perfect ! +========DIFF START======= +6c6 +< Status: checked,analyzed,optimized keys,sorted index pages,zerofilled,movable +--- +> Status: changed +========DIFF END======= +TEST WITH ma_test2 -s -L -K -W -P -M -T -c -H1 -t1 (commit at end) +TEST WITH ma_test2 -s -L -K -W -P -M -T -c -H1 -t6 -A2 (additional aborted work) +Dying on request without maria_commit()/maria_close() +applying log +Differences in maria_chk -dvv, recovery not yet perfect ! +========DIFF START======= +6c6 +< Status: checked,analyzed,optimized keys,sorted index pages,zerofilled,movable +--- +> Status: changed +========DIFF END======= +testing idempotency +applying log +Differences in maria_chk -dvv, recovery not yet perfect ! +========DIFF START======= +6c6 +< Status: checked,analyzed,optimized keys,sorted index pages,zerofilled,movable +--- +> Status: changed +========DIFF END======= +testing applying of CLRs to recreate table +applying log +Differences in maria_chk -dvv, recovery not yet perfect ! +========DIFF START======= +6c6 +< Status: checked,analyzed,optimized keys,sorted index pages,zerofilled,movable +--- +> Status: changed +========DIFF END======= +TEST WITH ma_test1 -s -M -T -c -N -H1 --testflag=1 (commit at end) +TEST WITH ma_test1 -s -M -T -c -N -H1 --testflag=2 --test-undo=3 (additional aborted work) +Terminating after inserts +Dying on request without maria_commit()/maria_close() +applying log +testing idempotency +applying log +testing applying of CLRs to recreate table +applying log +TEST WITH ma_test1 -s -M -T -c -N -H2 --testflag=3 (commit at end) +Terminating after updates +TEST WITH ma_test1 -s -M -T -c -N -H2 --testflag=4 --test-undo=3 (additional aborted work) +Terminating after deletes +Dying on request without maria_commit()/maria_close() +applying log +testing idempotency +applying log +testing applying of CLRs to recreate table +applying log +TEST WITH ma_test1 -s -M -T -c -N -H2 --versioning --testflag=3 (commit at end) +Terminating after updates +TEST WITH ma_test1 -s -M -T -c -N -H2 --versioning --testflag=4 --test-undo=3 (additional aborted work) +Terminating after deletes +Dying on request without maria_commit()/maria_close() +applying log +testing idempotency +applying log +testing applying of CLRs to recreate table +applying log +TEST WITH ma_test1 -s -M -T -c -N -H2 --testflag=2 (commit at end) +Terminating after inserts +TEST WITH ma_test1 -s -M -T -c -N -H2 --testflag=3 --test-undo=3 (additional aborted work) +Terminating after updates +Dying on request without maria_commit()/maria_close() +applying log +testing idempotency +applying log +testing applying of CLRs to recreate table +applying log +TEST WITH ma_test2 -s -L -K -W -P -M -T -c -H1 -t1 (commit at end) +TEST WITH ma_test2 -s -L -K -W -P -M -T -c -H1 -t2 -A3 (additional aborted work) +Dying on request without maria_commit()/maria_close() +applying log +Differences in maria_chk -dvv, recovery not yet perfect ! +========DIFF START======= +6c6 +< Status: checked,analyzed,optimized keys,sorted index pages,zerofilled,movable +--- +> Status: changed +========DIFF END======= +testing idempotency +applying log +Differences in maria_chk -dvv, recovery not yet perfect ! +========DIFF START======= +6c6 +< Status: checked,analyzed,optimized keys,sorted index pages,zerofilled,movable +--- +> Status: changed +========DIFF END======= +testing applying of CLRs to recreate table +applying log +Differences in maria_chk -dvv, recovery not yet perfect ! +========DIFF START======= +6c6 +< Status: checked,analyzed,optimized keys,sorted index pages,zerofilled,movable +--- +> Status: changed +========DIFF END======= +TEST WITH ma_test2 -s -L -K -W -P -M -T -c -H1 -t1 (commit at end) +TEST WITH ma_test2 -s -L -K -W -P -M -T -c -H1 -t6 -A3 (additional aborted work) +Dying on request without maria_commit()/maria_close() +applying log +Differences in maria_chk -dvv, recovery not yet perfect ! +========DIFF START======= +6c6 +< Status: checked,analyzed,optimized keys,sorted index pages,zerofilled,movable +--- +> Status: changed +========DIFF END======= +testing idempotency +applying log +Differences in maria_chk -dvv, recovery not yet perfect ! +========DIFF START======= +6c6 +< Status: checked,analyzed,optimized keys,sorted index pages,zerofilled,movable +--- +> Status: changed +========DIFF END======= +testing applying of CLRs to recreate table +applying log +Differences in maria_chk -dvv, recovery not yet perfect ! +========DIFF START======= +6c6 +< Status: checked,analyzed,optimized keys,sorted index pages,zerofilled,movable +--- +> Status: changed +========DIFF END======= +TEST WITH ma_test1 -s -M -T -c -N -H1 --testflag=1 (commit at end) +TEST WITH ma_test1 -s -M -T -c -N -H1 --testflag=2 --test-undo=4 (additional aborted work) +Terminating after inserts +Dying on request without maria_commit()/maria_close() +applying log +testing idempotency +applying log +testing applying of CLRs to recreate table +applying log +TEST WITH ma_test1 -s -M -T -c -N -H2 --testflag=3 (commit at end) +Terminating after updates +TEST WITH ma_test1 -s -M -T -c -N -H2 --testflag=4 --test-undo=4 (additional aborted work) +Terminating after deletes +Dying on request without maria_commit()/maria_close() +applying log +testing idempotency +applying log +testing applying of CLRs to recreate table +applying log +TEST WITH ma_test1 -s -M -T -c -N -H2 --versioning --testflag=3 (commit at end) +Terminating after updates +TEST WITH ma_test1 -s -M -T -c -N -H2 --versioning --testflag=4 --test-undo=4 (additional aborted work) +Terminating after deletes +Dying on request without maria_commit()/maria_close() +applying log +testing idempotency +applying log +testing applying of CLRs to recreate table +applying log +TEST WITH ma_test1 -s -M -T -c -N -H2 --testflag=2 (commit at end) +Terminating after inserts +TEST WITH ma_test1 -s -M -T -c -N -H2 --testflag=3 --test-undo=4 (additional aborted work) +Terminating after updates +Dying on request without maria_commit()/maria_close() +applying log +testing idempotency +applying log +testing applying of CLRs to recreate table +applying log +TEST WITH ma_test2 -s -L -K -W -P -M -T -c -H1 -t1 (commit at end) +TEST WITH ma_test2 -s -L -K -W -P -M -T -c -H1 -t2 -A4 (additional aborted work) +Dying on request without maria_commit()/maria_close() +applying log +Differences in maria_chk -dvv, recovery not yet perfect ! +========DIFF START======= +6c6 +< Status: checked,analyzed,optimized keys,sorted index pages,zerofilled,movable +--- +> Status: changed +========DIFF END======= +testing idempotency +applying log +Differences in maria_chk -dvv, recovery not yet perfect ! +========DIFF START======= +6c6 +< Status: checked,analyzed,optimized keys,sorted index pages,zerofilled,movable +--- +> Status: changed +========DIFF END======= +testing applying of CLRs to recreate table +applying log +Differences in maria_chk -dvv, recovery not yet perfect ! +========DIFF START======= +6c6 +< Status: checked,analyzed,optimized keys,sorted index pages,zerofilled,movable +--- +> Status: changed +========DIFF END======= +TEST WITH ma_test2 -s -L -K -W -P -M -T -c -H1 -t1 (commit at end) +TEST WITH ma_test2 -s -L -K -W -P -M -T -c -H1 -t6 -A4 (additional aborted work) +Dying on request without maria_commit()/maria_close() +applying log +Differences in maria_chk -dvv, recovery not yet perfect ! +========DIFF START======= +6c6 +< Status: checked,analyzed,optimized keys,sorted index pages,zerofilled,movable +--- +> Status: changed +========DIFF END======= +testing idempotency +applying log +Differences in maria_chk -dvv, recovery not yet perfect ! +========DIFF START======= +6c6 +< Status: checked,analyzed,optimized keys,sorted index pages,zerofilled,movable +--- +> Status: changed +========DIFF END======= +testing applying of CLRs to recreate table +applying log +Differences in maria_chk -dvv, recovery not yet perfect ! +========DIFF START======= +6c6 +< Status: checked,analyzed,optimized keys,sorted index pages,zerofilled,movable +--- +> Status: changed +========DIFF END======= +TEST WITH ma_test1 -s -M -T -c -N -b32768 -H1 --testflag=1 (commit at end) +TEST WITH ma_test1 -s -M -T -c -N -b32768 -H1 --testflag=2 --test-undo=1 (additional aborted work) +Terminating after inserts +Dying on request without maria_commit()/maria_close() +applying log +testing idempotency +applying log +testing applying of CLRs to recreate table +applying log +TEST WITH ma_test1 -s -M -T -c -N -b32768 -H2 --testflag=3 (commit at end) +Terminating after updates +TEST WITH ma_test1 -s -M -T -c -N -b32768 -H2 --testflag=4 --test-undo=1 (additional aborted work) +Terminating after deletes +Dying on request without maria_commit()/maria_close() +applying log +testing idempotency +applying log +testing applying of CLRs to recreate table +applying log +TEST WITH ma_test1 -s -M -T -c -N -b32768 -H2 --versioning --testflag=3 (commit at end) +Terminating after updates +TEST WITH ma_test1 -s -M -T -c -N -b32768 -H2 --versioning --testflag=4 --test-undo=1 (additional aborted work) +Terminating after deletes +Dying on request without maria_commit()/maria_close() +applying log +testing idempotency +applying log +testing applying of CLRs to recreate table +applying log +TEST WITH ma_test1 -s -M -T -c -N -b32768 -H2 --testflag=2 (commit at end) +Terminating after inserts +TEST WITH ma_test1 -s -M -T -c -N -b32768 -H2 --testflag=3 --test-undo=1 (additional aborted work) +Terminating after updates +Dying on request without maria_commit()/maria_close() +applying log +testing idempotency +applying log +testing applying of CLRs to recreate table +applying log +TEST WITH ma_test2 -s -L -K -W -P -M -T -c -b32768 -H1 -t1 (commit at end) +TEST WITH ma_test2 -s -L -K -W -P -M -T -c -b32768 -H1 -t2 -A1 (additional aborted work) +Dying on request without maria_commit()/maria_close() +applying log +Differences in maria_chk -dvv, recovery not yet perfect ! +========DIFF START======= +6c6 +< Status: checked,analyzed,optimized keys,sorted index pages,zerofilled,movable +--- +> Status: changed +========DIFF END======= +testing idempotency +applying log +Differences in maria_chk -dvv, recovery not yet perfect ! +========DIFF START======= +6c6 +< Status: checked,analyzed,optimized keys,sorted index pages,zerofilled,movable +--- +> Status: changed +========DIFF END======= +testing applying of CLRs to recreate table +applying log +Differences in maria_chk -dvv, recovery not yet perfect ! +========DIFF START======= +6c6 +< Status: checked,analyzed,optimized keys,sorted index pages,zerofilled,movable +--- +> Status: changed +========DIFF END======= +TEST WITH ma_test2 -s -L -K -W -P -M -T -c -b32768 -H1 -t1 (commit at end) +TEST WITH ma_test2 -s -L -K -W -P -M -T -c -b32768 -H1 -t6 -A1 (additional aborted work) +Dying on request without maria_commit()/maria_close() +applying log +Differences in maria_chk -dvv, recovery not yet perfect ! +========DIFF START======= +6c6 +< Status: checked,analyzed,optimized keys,sorted index pages,zerofilled,movable +--- +> Status: changed +========DIFF END======= +testing idempotency +applying log +Differences in maria_chk -dvv, recovery not yet perfect ! +========DIFF START======= +6c6 +< Status: checked,analyzed,optimized keys,sorted index pages,zerofilled,movable +--- +> Status: changed +========DIFF END======= +testing applying of CLRs to recreate table +applying log +Differences in maria_chk -dvv, recovery not yet perfect ! +========DIFF START======= +6c6 +< Status: checked,analyzed,optimized keys,sorted index pages,zerofilled,movable +--- +> Status: changed +========DIFF END======= +TEST WITH ma_test1 -s -M -T -c -N -b32768 -H1 --testflag=1 (commit at end) +TEST WITH ma_test1 -s -M -T -c -N -b32768 -H1 --testflag=2 --test-undo=2 (additional aborted work) +Terminating after inserts +Dying on request without maria_commit()/maria_close() +applying log +testing idempotency +applying log +testing applying of CLRs to recreate table +applying log +TEST WITH ma_test1 -s -M -T -c -N -b32768 -H2 --testflag=3 (commit at end) +Terminating after updates +TEST WITH ma_test1 -s -M -T -c -N -b32768 -H2 --testflag=4 --test-undo=2 (additional aborted work) +Terminating after deletes +Dying on request without maria_commit()/maria_close() +applying log +testing idempotency +applying log +testing applying of CLRs to recreate table +applying log +TEST WITH ma_test1 -s -M -T -c -N -b32768 -H2 --versioning --testflag=3 (commit at end) +Terminating after updates +TEST WITH ma_test1 -s -M -T -c -N -b32768 -H2 --versioning --testflag=4 --test-undo=2 (additional aborted work) +Terminating after deletes +Dying on request without maria_commit()/maria_close() +applying log +testing idempotency +applying log +testing applying of CLRs to recreate table +applying log +TEST WITH ma_test1 -s -M -T -c -N -b32768 -H2 --testflag=2 (commit at end) +Terminating after inserts +TEST WITH ma_test1 -s -M -T -c -N -b32768 -H2 --testflag=3 --test-undo=2 (additional aborted work) +Terminating after updates +Dying on request without maria_commit()/maria_close() +applying log +testing idempotency +applying log +testing applying of CLRs to recreate table +applying log +TEST WITH ma_test2 -s -L -K -W -P -M -T -c -b32768 -H1 -t1 (commit at end) +TEST WITH ma_test2 -s -L -K -W -P -M -T -c -b32768 -H1 -t2 -A2 (additional aborted work) +Dying on request without maria_commit()/maria_close() +applying log +Differences in maria_chk -dvv, recovery not yet perfect ! +========DIFF START======= +6c6 +< Status: checked,analyzed,optimized keys,sorted index pages,zerofilled,movable +--- +> Status: changed +========DIFF END======= +testing idempotency +applying log +Differences in maria_chk -dvv, recovery not yet perfect ! +========DIFF START======= +6c6 +< Status: checked,analyzed,optimized keys,sorted index pages,zerofilled,movable +--- +> Status: changed +========DIFF END======= +testing applying of CLRs to recreate table +applying log +Differences in maria_chk -dvv, recovery not yet perfect ! +========DIFF START======= +6c6 +< Status: checked,analyzed,optimized keys,sorted index pages,zerofilled,movable +--- +> Status: changed +========DIFF END======= +TEST WITH ma_test2 -s -L -K -W -P -M -T -c -b32768 -H1 -t1 (commit at end) +TEST WITH ma_test2 -s -L -K -W -P -M -T -c -b32768 -H1 -t6 -A2 (additional aborted work) +Dying on request without maria_commit()/maria_close() +applying log +Differences in maria_chk -dvv, recovery not yet perfect ! +========DIFF START======= +6c6 +< Status: checked,analyzed,optimized keys,sorted index pages,zerofilled,movable +--- +> Status: changed +========DIFF END======= +testing idempotency +applying log +Differences in maria_chk -dvv, recovery not yet perfect ! +========DIFF START======= +6c6 +< Status: checked,analyzed,optimized keys,sorted index pages,zerofilled,movable +--- +> Status: changed +========DIFF END======= +testing applying of CLRs to recreate table +applying log +Differences in maria_chk -dvv, recovery not yet perfect ! +========DIFF START======= +6c6 +< Status: checked,analyzed,optimized keys,sorted index pages,zerofilled,movable +--- +> Status: changed +========DIFF END======= +TEST WITH ma_test1 -s -M -T -c -N -b32768 -H1 --testflag=1 (commit at end) +TEST WITH ma_test1 -s -M -T -c -N -b32768 -H1 --testflag=2 --test-undo=3 (additional aborted work) +Terminating after inserts +Dying on request without maria_commit()/maria_close() +applying log +testing idempotency +applying log +testing applying of CLRs to recreate table +applying log +TEST WITH ma_test1 -s -M -T -c -N -b32768 -H2 --testflag=3 (commit at end) +Terminating after updates +TEST WITH ma_test1 -s -M -T -c -N -b32768 -H2 --testflag=4 --test-undo=3 (additional aborted work) +Terminating after deletes +Dying on request without maria_commit()/maria_close() +applying log +testing idempotency +applying log +testing applying of CLRs to recreate table +applying log +TEST WITH ma_test1 -s -M -T -c -N -b32768 -H2 --versioning --testflag=3 (commit at end) +Terminating after updates +TEST WITH ma_test1 -s -M -T -c -N -b32768 -H2 --versioning --testflag=4 --test-undo=3 (additional aborted work) +Terminating after deletes +Dying on request without maria_commit()/maria_close() +applying log +testing idempotency +applying log +testing applying of CLRs to recreate table +applying log +TEST WITH ma_test1 -s -M -T -c -N -b32768 -H2 --testflag=2 (commit at end) +Terminating after inserts +TEST WITH ma_test1 -s -M -T -c -N -b32768 -H2 --testflag=3 --test-undo=3 (additional aborted work) +Terminating after updates +Dying on request without maria_commit()/maria_close() +applying log +testing idempotency +applying log +testing applying of CLRs to recreate table +applying log +TEST WITH ma_test2 -s -L -K -W -P -M -T -c -b32768 -H1 -t1 (commit at end) +TEST WITH ma_test2 -s -L -K -W -P -M -T -c -b32768 -H1 -t2 -A3 (additional aborted work) +Dying on request without maria_commit()/maria_close() +applying log +Differences in maria_chk -dvv, recovery not yet perfect ! +========DIFF START======= +6c6 +< Status: checked,analyzed,optimized keys,sorted index pages,zerofilled,movable +--- +> Status: changed +========DIFF END======= +testing idempotency +applying log +Differences in maria_chk -dvv, recovery not yet perfect ! +========DIFF START======= +6c6 +< Status: checked,analyzed,optimized keys,sorted index pages,zerofilled,movable +--- +> Status: changed +========DIFF END======= +testing applying of CLRs to recreate table +applying log +Differences in maria_chk -dvv, recovery not yet perfect ! +========DIFF START======= +6c6 +< Status: checked,analyzed,optimized keys,sorted index pages,zerofilled,movable +--- +> Status: changed +========DIFF END======= +TEST WITH ma_test2 -s -L -K -W -P -M -T -c -b32768 -H1 -t1 (commit at end) +TEST WITH ma_test2 -s -L -K -W -P -M -T -c -b32768 -H1 -t6 -A3 (additional aborted work) +Dying on request without maria_commit()/maria_close() +applying log +Differences in maria_chk -dvv, recovery not yet perfect ! +========DIFF START======= +6c6 +< Status: checked,analyzed,optimized keys,sorted index pages,zerofilled,movable +--- +> Status: changed +========DIFF END======= +testing idempotency +applying log +Differences in maria_chk -dvv, recovery not yet perfect ! +========DIFF START======= +6c6 +< Status: checked,analyzed,optimized keys,sorted index pages,zerofilled,movable +--- +> Status: changed +========DIFF END======= +testing applying of CLRs to recreate table +applying log +Differences in maria_chk -dvv, recovery not yet perfect ! +========DIFF START======= +6c6 +< Status: checked,analyzed,optimized keys,sorted index pages,zerofilled,movable +--- +> Status: changed +========DIFF END======= +TEST WITH ma_test1 -s -M -T -c -N -b32768 -H1 --testflag=1 (commit at end) +TEST WITH ma_test1 -s -M -T -c -N -b32768 -H1 --testflag=2 --test-undo=4 (additional aborted work) +Terminating after inserts +Dying on request without maria_commit()/maria_close() +applying log +testing idempotency +applying log +testing applying of CLRs to recreate table +applying log +TEST WITH ma_test1 -s -M -T -c -N -b32768 -H2 --testflag=3 (commit at end) +Terminating after updates +TEST WITH ma_test1 -s -M -T -c -N -b32768 -H2 --testflag=4 --test-undo=4 (additional aborted work) +Terminating after deletes +Dying on request without maria_commit()/maria_close() +applying log +testing idempotency +applying log +testing applying of CLRs to recreate table +applying log +TEST WITH ma_test1 -s -M -T -c -N -b32768 -H2 --versioning --testflag=3 (commit at end) +Terminating after updates +TEST WITH ma_test1 -s -M -T -c -N -b32768 -H2 --versioning --testflag=4 --test-undo=4 (additional aborted work) +Terminating after deletes +Dying on request without maria_commit()/maria_close() +applying log +testing idempotency +applying log +testing applying of CLRs to recreate table +applying log +TEST WITH ma_test1 -s -M -T -c -N -b32768 -H2 --testflag=2 (commit at end) +Terminating after inserts +TEST WITH ma_test1 -s -M -T -c -N -b32768 -H2 --testflag=3 --test-undo=4 (additional aborted work) +Terminating after updates +Dying on request without maria_commit()/maria_close() +applying log +testing idempotency +applying log +testing applying of CLRs to recreate table +applying log +TEST WITH ma_test2 -s -L -K -W -P -M -T -c -b32768 -H1 -t1 (commit at end) +TEST WITH ma_test2 -s -L -K -W -P -M -T -c -b32768 -H1 -t2 -A4 (additional aborted work) +Dying on request without maria_commit()/maria_close() +applying log +Differences in maria_chk -dvv, recovery not yet perfect ! +========DIFF START======= +6c6 +< Status: checked,analyzed,optimized keys,sorted index pages,zerofilled,movable +--- +> Status: changed +========DIFF END======= +testing idempotency +applying log +Differences in maria_chk -dvv, recovery not yet perfect ! +========DIFF START======= +6c6 +< Status: checked,analyzed,optimized keys,sorted index pages,zerofilled,movable +--- +> Status: changed +========DIFF END======= +testing applying of CLRs to recreate table +applying log +Differences in maria_chk -dvv, recovery not yet perfect ! +========DIFF START======= +6c6 +< Status: checked,analyzed,optimized keys,sorted index pages,zerofilled,movable +--- +> Status: changed +========DIFF END======= +TEST WITH ma_test2 -s -L -K -W -P -M -T -c -b32768 -H1 -t1 (commit at end) +TEST WITH ma_test2 -s -L -K -W -P -M -T -c -b32768 -H1 -t6 -A4 (additional aborted work) +Dying on request without maria_commit()/maria_close() +applying log +Differences in maria_chk -dvv, recovery not yet perfect ! +========DIFF START======= +6c6 +< Status: checked,analyzed,optimized keys,sorted index pages,zerofilled,movable +--- +> Status: changed +========DIFF END======= +testing idempotency +applying log +Differences in maria_chk -dvv, recovery not yet perfect ! +========DIFF START======= +6c6 +< Status: checked,analyzed,optimized keys,sorted index pages,zerofilled,movable +--- +> Status: changed +========DIFF END======= +testing applying of CLRs to recreate table +applying log +Differences in maria_chk -dvv, recovery not yet perfect ! +========DIFF START======= +6c6 +< Status: checked,analyzed,optimized keys,sorted index pages,zerofilled,movable +--- +> Status: changed +========DIFF END======= diff --git a/storage/maria/unittest/ma_test_recovery.pl b/storage/maria/unittest/ma_test_recovery.pl new file mode 100755 index 00000000000..58b9cc3b56b --- /dev/null +++ b/storage/maria/unittest/ma_test_recovery.pl @@ -0,0 +1,481 @@ +#!/usr/bin/env perl + +use Getopt::Long; +use File::Copy; +use File::Compare; +use File::Basename; +use Digest::MD5; + +$|= 1; +$^W = 1; # warnings, because env cannot parse 'perl -w' +$VER= "1.2"; + +$opt_version= 0; +$opt_help= 0; +$opt_verbose= 0; +$opt_abort_on_error=0; + +my $silent= "-s"; +my $maria_path; # path to "storage/maria" +my $maria_exe_path; # path to executables (ma_test1, maria_chk etc) +my $tmp= "./tmp"; +my $my_progname= $0; +my $suffix; +my $zerofilled_tables= 0; + +$my_progname=~ s/.*[\/]//; +$maria_path= dirname($0) . "/.."; + +main(); + +#### +#### main function +#### + +sub main +{ + my ($res, $table); + + if (!GetOptions("abort-on-error", "help", "version", "verbose")) + { + $flag_exit= 1; + } + if ($opt_version) + { + print "$my_progname version $VER\n"; + exit(0); + } + usage() if ($opt_help || $flag_exit); + + $suffix= ( $^O =~ /win/i && $^O !~ /darwin/i ) ? ".exe" : ""; + $maria_exe_path= "$maria_path/release"; + # we use -f, sometimes -x is unexpectedly false in Cygwin + if ( ! -f "$maria_exe_path/ma_test1$suffix" ) + { + $maria_exe_path= "$maria_path/relwithdebinfo"; + if ( ! -f "$maria_exe_path/ma_test1$suffix" ) + { + $maria_exe_path= "$maria_path/debug"; + if ( ! -f "$maria_exe_path/ma_test1$suffix" ) + { + $maria_exe_path= $maria_path; + if ( ! -f "$maria_exe_path/ma_test1$suffix" ) + { + die("Cannot find ma_test1 executable\n"); + } + } + } + } + + # test data is always put in the current directory or a tmp subdirectory + # of it + + if (! -d "$tmp") + { + mkdir $tmp; + } + print "MARIA RECOVERY TESTS\n"; + + # To not flood the screen, we redirect all the commands below to a text file + # and just give a final error if their output is not as expected + + open (MY_LOG, ">$tmp/ma_test_recovery.output") or die "Can't open log file\n"; + print MY_LOG "Testing the REDO PHASE ALONE\n"; + + # runs a program inserting/deleting rows, then moves the resulting table + # elsewhere; applies the log and checks that the data file is + # identical to the saved original. + + my @t= ("ma_test1$suffix $silent -M -T -c", + "ma_test2$suffix $silent -L -K -W -P -M -T -c -d500", + "ma_test2$suffix $silent -M -T -c -b65000", + "ma_test2$suffix $silent -M -T -c -b65000 -d800", + "ma_test1$suffix $silent -M -T -c -C", + "ma_test2$suffix $silent -L -K -W -P -M -T -c -d500 -C", + #"ma_rt_test$suffix $silent -M -T -c -C", + # @todo: also add to @t2 + ); + + foreach my $prog (@t) + { + unlink <maria_log.* maria_log_control>; + my $prog_no_suffix= $prog; + $prog_no_suffix=~ s/$suffix// if ($suffix); + print MY_LOG "TEST WITH $prog_no_suffix\n"; + $res= my_exec("$maria_exe_path/$prog"); + print MY_LOG $res; + # derive table's name from program's name + if ($prog =~ m/^ma_(\S+)\s.*/) + { + $table= $1; + } + else + { + die("can't guess table name"); + } + $com= "$maria_exe_path/maria_chk$suffix -dvv $table "; + $com.= "| grep -v \"Creation time:\" | grep -v \"file length\" | grep -v \"LSNs:\" | grep -v \"UUID:\""; + $com.= "> $tmp/maria_chk_message.good.txt 2>&1"; + my_exec($com); + my $checksum= my_exec("$maria_exe_path/maria_chk$suffix -dss $table"); + move("$table.MAD", "$tmp/$table-good.MAD") || + die "Can't move $table.MAD to $tmp/$table-good.MAD\n"; + move("$table.MAI", "$tmp/$table-good.MAI") || + die "Can't move $table.MAI to $tmp/$table-good.MAI\n"; + apply_log($table, "shouldnotchangelog"); + check_table_is_same($table, $checksum); + $res= physical_cmp($table, "$tmp/$table-good"); + print MY_LOG $res; + print MY_LOG "testing idempotency\n"; + apply_log($table, "shouldnotchangelog"); + check_table_is_same($table, $checksum); + $res= physical_cmp($table, "$tmp/$table-good"); + print MY_LOG $res; + } + + print MY_LOG "Testing the REDO AND UNDO PHASE\n"; + # The test programs look like: + # work; commit (time T1); work; exit-without-commit (time T2) + # We first run the test program and let it exit after T1's commit. + # Then we run it again and let it exit at T2. Then we compare + # and expect identity. + + my @take_checkpoints= ("no", "yes"); + my @blobs= ("", "-b32768"); + my @test_undo= (1, 2, 3, 4); + my @t2= ("ma_test1$suffix $silent -M -T -c -N blob -H1", + "--testflag=1", + "--testflag=2 --test-undo=", + "ma_test1$suffix $silent -M -T -c -N blob -H2", + "--testflag=3", + "--testflag=4 --test-undo=", + "ma_test1$suffix $silent -M -T -c -N blob -H2 --versioning", + "--testflag=3", + "--testflag=4 --test-undo=", + "ma_test1$suffix $silent -M -T -c -N blob -H2", + "--testflag=2", + "--testflag=3 --test-undo=", + "ma_test2$suffix $silent -L -K -W -P -M -T -c blob -H1", + "-t1", + "-t2 -A", + "ma_test2$suffix $silent -L -K -W -P -M -T -c blob -H1", + "-t1", + "-t6 -A"); + + foreach my $take_checkpoint (@take_checkpoints) + { + my ($i, $j, $k, $commit_run_args, $abort_run_args); + # we test table without blobs and then table with blobs + for ($i= 0; defined($blobs[$i]); $i++) + { + for ($j= 0; defined($test_undo[$j]); $j++) + { + # first iteration tests rollback of insert, second tests rollback of delete + # -N (create NULL fields) is needed because --test-undo adds it anyway + for ($k= 0; defined($t2[$k]); $k+= 3) + { + $prog= $t2[$k]; + $prog=~ s/blob/$blobs[$i]/; + if ("$take_checkpoint" eq "no") { + $prog=~ s/\s+\-H[0-9]+//; + } + $commit_run_args= $t2[$k + 1]; + $abort_run_args= $t2[$k + 2]; + unlink <maria_log.* maria_log_control>; + my $prog_no_suffix= $prog; + $prog_no_suffix=~ s/$suffix// if ($suffix); + print MY_LOG "TEST WITH $prog_no_suffix $commit_run_args (commit at end)\n"; + $res= my_exec("$maria_exe_path/$prog $commit_run_args"); + print MY_LOG $res; + # derive table's name from program's name + if ($prog =~ m/^ma_(\S+)\s.*/) + { + $table= $1; + } + else + { + die("can't guess table name"); + } + $com= "$maria_exe_path/maria_chk$suffix -dvv $table "; + $com.= "| grep -v \"Creation time:\" | grep -v \"file length\" | grep -v \"LSNs:\" | grep -v \"UUID:\" "; + $com.= "> $tmp/maria_chk_message.good.txt 2>&1"; + $res= my_exec($com); + print MY_LOG $res; + $checksum= my_exec("$maria_exe_path/maria_chk$suffix -dss $table"); + move("$table.MAD", "$tmp/$table-good.MAD") || + die "Can't move $table.MAD to $tmp/$table-good.MAD\n"; + move("$table.MAI", "$tmp/$table-good.MAI") || + die "Can't move $table.MAI to $tmp/$table-good.MAI\n"; + unlink <maria_log.* maria_log_control>; + print MY_LOG "TEST WITH $prog_no_suffix $abort_run_args$test_undo[$j] (additional aborted work)\n"; + $res= my_exec("$maria_exe_path/$prog $abort_run_args$test_undo[$j]"); + print MY_LOG $res; + copy("$table.MAD", "$tmp/$table-before_undo.MAD") || + die "Can't copy $table.MAD to $tmp/$table-before_undo.MAD\n"; + copy("$table.MAI", "$tmp/$table-before_undo.MAI") || + die "Can't copy $table.MAI to $tmp/$table-before_undo.MAI\n"; + + # The lines below seem unneeded, will be removed soon + # We have to copy and restore logs, as running maria_read_log will + # change the maria_control_file + # rm -f $tmp/maria_log.* $tmp/maria_log_control + # cp $maria_path/maria_log* $tmp + + if ($test_undo[$j] != 3) { + apply_log($table, "shouldchangelog"); # should undo aborted work + } else { + # probably nothing to undo went to log or data file + apply_log($table, "dontknow"); + } + copy("$table.MAD", "$tmp/$table-after_undo.MAD") || + die "Can't copy $table.MAD to $tmp/$table-after_undo.MAD\n"; + copy("$table.MAI", "$tmp/$table-after_undo.MAI") || + die "Can't copy $table.MAI to $tmp/$table-after_undo.MAI\n"; + + # It is impossible to do a "cmp" between .good and .after_undo, + # because the UNDO phase generated log + # records whose LSN tagged pages. Another reason is that rolling back + # INSERT only marks the rows free, does not empty them (optimization), so + # traces of the INSERT+rollback remain. + + check_table_is_same($table, $checksum); + print MY_LOG "testing idempotency\n"; + apply_log($table, "shouldnotchangelog"); + check_table_is_same($table, $checksum); + $res= physical_cmp($table, "$tmp/$table-after_undo"); + print MY_LOG $res; + print MY_LOG "testing applying of CLRs to recreate table\n"; + unlink <$table.MA?>; + # cp $tmp/maria_log* $maria_path #unneeded + apply_log($table, "shouldnotchangelog"); + check_table_is_same($table, $checksum); + $res= physical_cmp($table, "$tmp/$table-after_undo"); + print MY_LOG $res; + } + unlink <$table.* $tmp/$table* $tmp/maria_chk_*.txt $tmp/maria_read_log_$table.txt>; + } + } + } + + if ($? >> 8) { + print "Some test failed\n"; + exit(1); + } + + close(MY_LOG); + # also note that maria_chk -dvv shows differences for ma_test2 in UNDO phase, + # this is normal: removing records does not shrink the data/key file, + # does not put back the "analyzed,optimized keys"(etc) index state. + `diff -b $maria_path/unittest/ma_test_recovery.expected $tmp/ma_test_recovery.output`; + if ($? >> 8) { + print "UNEXPECTED OUTPUT OF TESTS, FAILED"; + print " (zerofilled $zerofilled_tables tables)\n"; + print "For more info, do diff -b $maria_path/unittest/ma_test_recovery.expected "; + print "$tmp/ma_test_recovery.output\n"; + exit(1); + } + print "ALL RECOVERY TESTS OK (zerofilled $zerofilled_tables tables)\n"; +} + +#### +#### check_table_is_same +#### + +sub check_table_is_same +{ + my ($table, $checksum)= @_; + my ($com, $checksum2, $res); + + # Computes checksum of new table and compares to checksum of old table + # Shows any difference in table's state (info from the index's header) + # Data/key file length is random in ma_test2 (as it uses srand() which + # may differ between machines). + + if ($opt_verbose) + { + print "checking if table $table has changed\n"; + } + + $com= "$maria_exe_path/maria_chk$suffix -dvv $table | grep -v \"Creation time:\" "; + $com.= "| grep -v \"file length\" | grep -v \"LSNs:\" | grep -v \"UUID:\" > $tmp/maria_chk_message.txt 2>&1"; + $res= `$com`; + print MY_LOG $res; + $res= `$maria_exe_path/maria_chk$suffix -s -e --read-only $table`; + print MY_LOG $res; + $checksum2= `$maria_exe_path/maria_chk$suffix -dss $table`; + if ("$checksum" ne "$checksum2") + { + print MY_LOG "checksum differs for $table before and after recovery\n"; + return 1; + } + + $com= "diff $tmp/maria_chk_message.good.txt $tmp/maria_chk_message.txt "; + $com.= "> $tmp/maria_chk_diff.txt || true"; + $res= `$com`; + print MY_LOG $res; + + if (-s "$tmp/maria_chk_diff.txt") + { + print MY_LOG "Differences in maria_chk -dvv, recovery not yet perfect !\n"; + print MY_LOG "========DIFF START=======\n"; + open(MY_FILE, "<$tmp/maria_chk_diff.txt") || die "Can't open file maria_chk_diff.txt\n"; + while (<MY_FILE>) + { + print MY_LOG $_; + } + close(MY_FILE); + print MY_LOG "========DIFF END=======\n"; + } +} + +#### +#### apply_log +#### + +sub apply_log +{ + my ($table, $shouldchangelog)= @_; + my ($log_md5, $log_md5_2); + + # applies log, can verify if applying did write to log or not + + if ("$shouldchangelog" ne "shouldnotchangelog" && + "$shouldchangelog" ne "shouldchangelog" && + "$shouldchangelog" ne "dontknow" ) + { + print MY_LOG "bad argument '$shouldchangelog'\n"; + return 1; + } + foreach (<maria_log.*>) + { + $log_md5.= md5_conv($_); + } + print MY_LOG "applying log\n"; + my_exec("$maria_exe_path/maria_read_log$suffix -a > $tmp/maria_read_log_$table.txt"); + foreach (<maria_log.*>) + { + $log_md5_2.= md5_conv($_); + } + if ("$log_md5" ne "$log_md5_2" ) + { + if ("$shouldchangelog" eq "shouldnotchangelog") + { + print MY_LOG "maria_read_log should not have modified the log\n"; + return 1; + } + } + elsif ("$shouldchangelog" eq "shouldchangelog") + { + print MY_LOG "maria_read_log should have modified the log\n"; + return 1; + } +} + +#### +#### md5_conv +#### + +sub md5_conv +{ + my ($file)= @_; + + open(FILE, $file) or die "Can't open '$file': $!\n"; + binmode(FILE); + my $md5= Digest::MD5->new; + $md5->addfile(FILE); + close (FILE); + return $md5->hexdigest . "\n"; +} + +#### +#### physical_cmp: compares two tables (MAI and MAD) physically; +#### uses zerofill-keep-lsn to reduce irrelevant differences. +#### + +sub physical_cmp +{ + my ($table1, $table2)= @_; + my ($zerofilled, $ret_text)= (0, ""); + #return `cmp $table1.MAD $table2.MAD`.`cmp $table1.MAI $table2.MAI`; + foreach my $file_suffix ("MAD", "MAI") + { + my $file1= "$table1.$file_suffix"; + my $file2= "$table2.$file_suffix"; + my $res= File::Compare::compare($file1, $file2); + die() if ($res == -1); + if ($res == 1 # they differ + and !$zerofilled) + { + # let's try with --zerofill-keep-lsn + $zerofilled= 1; # but no need to do it twice + $zerofilled_tables= $zerofilled_tables + 1; + my $table_no= 1; + foreach my $table ($table1, $table2) + { + # save original tables to restore them later + copy("$table.MAD", "$tmp/before_zerofill$table_no.MAD") || die(); + copy("$table.MAI", "$tmp/before_zerofill$table_no.MAI") || die(); + $com= "$maria_exe_path/maria_chk$suffix -s --zerofill-keep-lsn $table"; + $res= `$com`; + print MY_LOG $res; + $table_no= $table_no + 1; + } + $res= File::Compare::compare($file1, $file2); + die() if ($res == -1); + } + $ret_text.= "$file1 and $file2 differ\n" if ($res != 0); + } + if ($zerofilled) + { + my $table_no= 1; + foreach my $table ($table1, $table2) + { + move("$tmp/before_zerofill$table_no.MAD", "$table.MAD") || die(); + move("$tmp/before_zerofill$table_no.MAI", "$table.MAI") || die(); + $table_no= $table_no + 1; + } + } + return $ret_text; +} + + +sub my_exec +{ + my($command)= @_; + my $res; + if ($opt_verbose) + { + print "$command\n"; + } + $res= `$command`; + if ($? != 0 && $opt_abort_on_error) + { + exit(1); + } + return $res; +} + + +#### +#### usage +#### + +sub usage +{ + print <<EOF; +$my_progname version $VER + +Description: + +Run various maria recovery tests and print the results + +Options +--help Show this help and exit. + +--abort-on-error Abort at once in case of error. +--verbose Show commands while there are executing. +--version Show version number and exit. + +EOF + exit(0); +} diff --git a/storage/maria/unittest/sequence_storage.c b/storage/maria/unittest/sequence_storage.c new file mode 100644 index 00000000000..d5db20d31ca --- /dev/null +++ b/storage/maria/unittest/sequence_storage.c @@ -0,0 +1,110 @@ +/* Copyright (C) 2008 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 */ + +#include "../maria_def.h" +#include "sequence_storage.h" + + +/** + @brief Initializes the sequence from the sequence file. + + @param seq Reference on the sequence storage. + @param file Path to the file where to write the sequence + + @retval 0 OK + @retval 1 Error +*/ + +my_bool seq_storage_reader_init(SEQ_STORAGE *seq, const char *file) +{ + FILE *fd; + seq->pos= 0; + if ((fd= my_fopen(file, O_RDONLY, MYF(MY_WME))) == NULL) + return 1; + if (my_init_dynamic_array(&seq->seq, sizeof(ulong), 10, 10)) + return 1; + + for(;;) + { + ulong num; + char line[22]; + if (fgets(line, sizeof(line), fd) == NULL) + break; + num= atol(line); + if (insert_dynamic(&seq->seq, (uchar*) &num)) + return 1; + } + fclose(fd); + return 0; +} + + +/** + @brief Gets next number from the sequence storage + + @param seq Reference on the sequence storage. + + @return Next number from the sequence. +*/ + +ulong seq_storage_next(SEQ_STORAGE *seq) +{ + DBUG_ASSERT(seq->seq.elements > 0); + DBUG_ASSERT(seq->pos < seq->seq.elements); + return (*(dynamic_element(&seq->seq, seq->pos++, ulong *))); +} + + +/** + @brief Frees resources allocated for the storage + + @param seq Reference on the sequence storage. +*/ + +void seq_storage_destroy(SEQ_STORAGE *seq) +{ + delete_dynamic(&seq->seq); +} + + +/** + @brief Starts the sequence from begining + + @param seq Reference on the sequence storage. +*/ + +void seq_storage_rewind(SEQ_STORAGE *seq) +{ + seq->pos= 0; +} + +/** + @brief Writes a number to the sequence file. + + @param file Path to the file where to write the sequence + @pagem num Number to be written + + @retval 0 OK + @retval 1 Error +*/ + +my_bool seq_storage_write(const char *file, ulong num) +{ + FILE *fd; + return ((fd= my_fopen(file, O_CREAT | O_APPEND | O_WRONLY, MYF(MY_WME))) == + NULL || + fprintf(fd, "%lu\n", num) < 0 || + fclose(fd) != 0); +} diff --git a/storage/maria/unittest/sequence_storage.h b/storage/maria/unittest/sequence_storage.h new file mode 100644 index 00000000000..78ce15a6253 --- /dev/null +++ b/storage/maria/unittest/sequence_storage.h @@ -0,0 +1,28 @@ +/* Copyright (C) 2008 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 */ + + +typedef struct st_seq_storage +{ + uint pos; + DYNAMIC_ARRAY seq; +} SEQ_STORAGE; + +extern my_bool seq_storage_reader_init(SEQ_STORAGE *seq, const char *file); +extern ulong seq_storage_next(SEQ_STORAGE *seq); +extern void seq_storage_destroy(SEQ_STORAGE *seq); +extern void seq_storage_rewind(SEQ_STORAGE *seq); +extern my_bool seq_storage_write(const char *file, ulong num); + diff --git a/storage/maria/unittest/test_file.c b/storage/maria/unittest/test_file.c new file mode 100644 index 00000000000..5f7e3939592 --- /dev/null +++ b/storage/maria/unittest/test_file.c @@ -0,0 +1,118 @@ +/* Copyright (C) 2006-2008 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 */ + +#include <tap.h> +#include <my_sys.h> +#include <my_dir.h> +#include "test_file.h" + + +/* + Check that file contance correspond to descriptor + + SYNOPSIS + test_file() + file File to test + file_name Path (and name) of file which is tested + size size of file + buff_size size of buffer which is enought to check the file + desc file descriptor to check with + + RETURN + 1 file if OK + 0 error +*/ + +int test_file(PAGECACHE_FILE file, char *file_name, + off_t size, size_t buff_size, struct file_desc *desc) +{ + unsigned char *buffr= my_malloc(buff_size, MYF(0)); + off_t pos= 0; + size_t byte; + int step= 0; + int res= 1; /* ok */ + +#ifdef __WIN__ + /* + On Windows, the info returned by stat(), specifically file length + is not necessarily current, because this is the behavior of + underlying FindFirstFile() function. + */ + WIN32_FILE_ATTRIBUTE_DATA file_attr; + LARGE_INTEGER li; + if(GetFileAttributesEx(file_name, GetFileExInfoStandard, &file_attr) == 0) + { + diag("Can't GetFileAttributesEx %s (errno: %d)\n", file_name, + GetLastError()); + res= 0; + goto err; + } + li.HighPart= file_attr.nFileSizeHigh; + li.LowPart= file_attr.nFileSizeLow; + if(li.QuadPart != size) + { + diag("file %s size is %llu (should be %llu)\n", + file_name, (ulonglong)size, (ulonglong)li.QuadPart); + res= 0; /* failed */ + /* continue to get more information */ + } +#else + MY_STAT stat_buff, *stat; + if ((stat= my_stat(file_name, &stat_buff, MYF(0))) == NULL) + { + diag("Can't stat() %s (errno: %d)\n", file_name, errno); + res= 0; + goto err; + } + if (stat->st_size != size) + { + diag("file %s size is %lu (should be %lu)\n", + file_name, (ulong) stat->st_size, (ulong) size); + res= 0; /* failed */ + /* continue to get more information */ + } +#endif + + /* check content */ + my_seek(file.file, 0, SEEK_SET, MYF(MY_WME)); + while (desc[step].length != 0) + { + if (my_read(file.file, buffr, desc[step].length, MYF(0)) != + desc[step].length) + { + diag("Can't read %u bytes from %s (file: %d errno: %d)\n", + (uint)desc[step].length, file_name, file.file, errno); + res= 0; + goto err; + } + for (byte= 0; byte < desc[step].length; byte++) + { + if (buffr[byte] != desc[step].content) + { + diag("content of %s mismatch 0x%x in position %lu instead of 0x%x\n", + file_name, (uint) buffr[byte], (ulong) (pos + byte), + desc[step].content); + res= 0; + goto err; + } + } + pos+= desc[step].length; + step++; + } + +err: + my_free(buffr, 0); + return res; +} diff --git a/storage/maria/unittest/test_file.h b/storage/maria/unittest/test_file.h new file mode 100644 index 00000000000..0a1ccf4ab54 --- /dev/null +++ b/storage/maria/unittest/test_file.h @@ -0,0 +1,29 @@ +/* Copyright (C) 2006-2008 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 */ + +#include <m_string.h> +#include "../ma_pagecache.h" + +/* + File content descriptor +*/ +struct file_desc +{ + unsigned int length; + unsigned char content; +}; + +int test_file(PAGECACHE_FILE file, char *file_name, + off_t size, size_t buff_size, struct file_desc *desc); diff --git a/storage/maria/unittest/trnman-t.c b/storage/maria/unittest/trnman-t.c new file mode 100644 index 00000000000..43cf982a7f2 --- /dev/null +++ b/storage/maria/unittest/trnman-t.c @@ -0,0 +1,175 @@ +/* Copyright (C) 2006-2008 MySQL AB, 2008-2009 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#include <tap.h> + +#include <my_global.h> +#include <my_sys.h> +#include <my_atomic.h> +#include <lf.h> +#include <m_string.h> +#include "../trnman.h" + +pthread_mutex_t rt_mutex; +pthread_attr_t attr; +size_t stacksize= 0; +#define STACK_SIZE (((int)stacksize-2048)*STACK_DIRECTION) + +int rt_num_threads; +int litmus; + +/* + create and end (commit or rollback) transactions randomly +*/ +#define MAX_ITER 100 +pthread_handler_t test_trnman(void *arg) +{ + uint x, y, i, n; + TRN *trn[MAX_ITER]; + int m= (*(int *)arg); + + if (my_thread_init()) + BAIL_OUT("my_thread_init failed!"); + + for (x= ((int)(intptr)(&m)); m > 0; ) + { + y= x= (x*LL(3628273133) + LL(1500450271)) % LL(9576890767); /* three prime numbers */ + m-= n= x % MAX_ITER; + for (i= 0; i < n; i++) + { + trn[i]= trnman_new_trn(0); + if (!trn[i]) + { + diag("trnman_new_trn() failed"); + litmus++; + } + } + for (i= 0; i < n; i++) + { + y= (y*19 + 7) % 31; + trnman_end_trn(trn[i], y & 1); + } + } + pthread_mutex_lock(&rt_mutex); + rt_num_threads--; + pthread_mutex_unlock(&rt_mutex); + + my_thread_end(); + + return 0; +} +#undef MAX_ITER + +void run_test(const char *test, pthread_handler handler, int n, int m) +{ + pthread_t *threads; + ulonglong now= my_getsystime(); + int i; + + litmus= 0; + + threads= (pthread_t *)my_malloc(sizeof(void *)*n, MYF(0)); + if (!threads) + { + diag("Out of memory"); + abort(); + } + + diag("Testing %s with %d threads, %d iterations... ", test, n, m); + rt_num_threads= n; + for (i= 0; i < n ; i++) + if (pthread_create(threads+i, &attr, handler, &m)) + { + diag("Could not create thread"); + abort(); + } + for (i= 0 ; i < n ; i++) + pthread_join(threads[i], 0); + now= my_getsystime()-now; + ok(litmus == 0, "Tested %s in %g secs (%d)", test, ((double)now)/1e7, litmus); + my_free((void*)threads, MYF(0)); +} + +#define ok_read_from(T1, T2, RES) \ + i= trnman_can_read_from(trn[T1], trid[T2]); \ + ok(i == RES, "trn" #T1 " %s read from trn" #T2, i ? "can" : "cannot") +#define start_transaction(T) \ + trn[T]= trnman_new_trn(0); \ + trid[T]= trn[T]->trid +#define commit(T) trnman_commit_trn(trn[T]) +#define abort(T) trnman_abort_trn(trn[T]) + +#define Ntrns 4 +void test_trnman_read_from() +{ + TRN *trn[Ntrns]; + TrID trid[Ntrns]; + int i; + + start_transaction(0); /* start trn1 */ + start_transaction(1); /* start trn2 */ + ok_read_from(1, 0, 0); + commit(0); /* commit trn1 */ + start_transaction(2); /* start trn4 */ + abort(2); /* abort trn4 */ + start_transaction(3); /* start trn5 */ + ok_read_from(3, 0, 1); + ok_read_from(3, 1, 0); + ok_read_from(3, 2, 0); + ok_read_from(3, 3, 1); + commit(1); /* commit trn2 */ + ok_read_from(3, 1, 0); + commit(3); /* commit trn5 */ + +} + +int main(int argc __attribute__((unused)), char **argv) +{ + MY_INIT(argv[0]); + + plan(7); + + if (my_atomic_initialize()) + return exit_status(); + + pthread_mutex_init(&rt_mutex, 0); + pthread_attr_init(&attr); +#ifdef HAVE_PTHREAD_ATTR_GETSTACKSIZE + pthread_attr_getstacksize(&attr, &stacksize); + if (stacksize == 0) +#endif + stacksize= PTHREAD_STACK_MIN; + +#define CYCLES 10000 +#define THREADS 10 + + trnman_init(0); + + test_trnman_read_from(); + run_test("trnman", test_trnman, THREADS, CYCLES); + + diag("mallocs: %d", trnman_allocated_transactions); + { + ulonglong now= my_getsystime(); + trnman_destroy(); + now= my_getsystime()-now; + diag("trnman_destroy: %g", ((double)now)/1e7); + } + + pthread_mutex_destroy(&rt_mutex); + my_end(0); + return exit_status(); +} + diff --git a/storage/myisam/CMakeLists.txt b/storage/myisam/CMakeLists.txt index c05e0046e64..1a667e271af 100755..100644 --- a/storage/myisam/CMakeLists.txt +++ b/storage/myisam/CMakeLists.txt @@ -20,6 +20,7 @@ SET(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -DSAFEMALLOC -DSAFE_MUTEX") SET(MYISAM_SOURCES ft_boolean_search.c ft_nlq_search.c ft_parser.c ft_static.c ft_stem.c ha_myisam.cc + ft_myisam.c ft_stopwords.c ft_update.c mi_cache.c mi_changed.c mi_check.c mi_checksum.c mi_close.c mi_create.c mi_dbug.c mi_delete.c mi_delete_all.c mi_delete_table.c mi_dynrec.c mi_extra.c mi_info.c @@ -45,6 +46,21 @@ IF(NOT SOURCE_SUBLIBS) ADD_EXECUTABLE(myisampack myisampack.c) TARGET_LINK_LIBRARIES(myisampack myisam mysys debug dbug strings zlib wsock32) + ADD_EXECUTABLE(mi_test1 mi_test1.c) + TARGET_LINK_LIBRARIES(mi_test1 myisam mysys debug dbug strings zlib wsock32) + + ADD_EXECUTABLE(mi_test2 mi_test2.c) + TARGET_LINK_LIBRARIES(mi_test2 myisam mysys debug dbug strings zlib wsock32) + + ADD_EXECUTABLE(mi_test3 mi_test3.c) + TARGET_LINK_LIBRARIES(mi_test3 myisam mysys debug dbug strings zlib wsock32) + + ADD_EXECUTABLE(sp_test sp_test.c) + TARGET_LINK_LIBRARIES(sp_test myisam mysys debug dbug strings zlib wsock32) + + ADD_EXECUTABLE(rt_test rt_test.c) + TARGET_LINK_LIBRARIES(rt_test myisam mysys debug dbug strings zlib wsock32) + SET_TARGET_PROPERTIES(myisamchk myisampack PROPERTIES LINK_FLAGS "setargv.obj") IF(EMBED_MANIFESTS) diff --git a/storage/myisam/Makefile.am b/storage/myisam/Makefile.am index f50c312b8e4..4bd0b177daa 100644 --- a/storage/myisam/Makefile.am +++ b/storage/myisam/Makefile.am @@ -97,8 +97,8 @@ libmyisam_a_SOURCES = mi_open.c mi_extra.c mi_info.c mi_rkey.c \ mi_delete_table.c mi_rename.c mi_check.c \ mi_keycache.c mi_preload.c \ ft_parser.c ft_stopwords.c ft_static.c \ - ft_update.c ft_boolean_search.c ft_nlq_search.c sort.c \ - ha_myisam.cc \ + ft_update.c ft_boolean_search.c ft_nlq_search.c \ + sort.c ha_myisam.cc ft_myisam.c \ rt_index.c rt_key.c rt_mbr.c rt_split.c sp_key.c CLEANFILES = test?.MY? FT?.MY? isam.log mi_test_all rt_test.MY? sp_test.MY? diff --git a/storage/myisam/ft_boolean_search.c b/storage/myisam/ft_boolean_search.c index 58363894cff..20ead971afd 100644 --- a/storage/myisam/ft_boolean_search.c +++ b/storage/myisam/ft_boolean_search.c @@ -196,7 +196,7 @@ static int ftb_query_add_word(MYSQL_FTPARSER_PARAM *param, case FT_TOKEN_WORD: ftbw= (FTB_WORD *)alloc_root(&ftb_param->ftb->mem_root, sizeof(FTB_WORD) + - (info->trunc ? MI_MAX_KEY_BUFF : + (info->trunc ? HA_MAX_KEY_BUFF : word_len * ftb_param->ftb->charset->mbmaxlen + HA_FT_WLEN + ftb_param->ftb->info->s->rec_reflength)); @@ -360,8 +360,8 @@ static int _ft2_search(FTB *ftb, FTB_WORD *ftbw, my_bool init_search) MI_INFO *info=ftb->info; uint off, extra=HA_FT_WLEN+info->s->base.rec_reflength; uchar *lastkey_buf=ftbw->word+ftbw->off; - LINT_INIT(off); + if (ftbw->flags & FTB_FLAG_TRUNC) lastkey_buf+=ftbw->len; diff --git a/storage/myisam/ft_myisam.c b/storage/myisam/ft_myisam.c new file mode 100644 index 00000000000..bef3fbfd5f5 --- /dev/null +++ b/storage/myisam/ft_myisam.c @@ -0,0 +1,36 @@ +/* Copyright (C) 2000 MySQL AB & MySQL Finland AB & TCX DataKonsult 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; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You 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 */ + +/* Written by Sergei A. Golubchik, who has a shared copyright to this code */ + +/* + This function is for interface functions between fulltext and myisam +*/ + +#include "ftdefs.h" + +FT_INFO *ft_init_search(uint flags, void *info, uint keynr, + uchar *query, uint query_len, CHARSET_INFO *cs, + uchar *record) +{ + FT_INFO *res; + if (flags & FT_BOOL) + res= ft_init_boolean_search((MI_INFO *)info, keynr, query, query_len,cs); + else + res= ft_init_nlq_search((MI_INFO *)info, keynr, query, query_len, flags, + record); + return res; +} diff --git a/storage/myisam/ft_static.c b/storage/myisam/ft_static.c index 1b6c7458fd7..4a5ee18920f 100644 --- a/storage/myisam/ft_static.c +++ b/storage/myisam/ft_static.c @@ -54,20 +54,6 @@ const struct _ft_vft _ft_vft_boolean = { ft_boolean_get_relevance, ft_boolean_reinit_search }; - -FT_INFO *ft_init_search(uint flags, void *info, uint keynr, - uchar *query, uint query_len, CHARSET_INFO *cs, - uchar *record) -{ - FT_INFO *res; - if (flags & FT_BOOL) - res= ft_init_boolean_search((MI_INFO *)info, keynr, query, query_len,cs); - else - res= ft_init_nlq_search((MI_INFO *)info, keynr, query, query_len, flags, - record); - return res; -} - const char *ft_stopword_file = 0; const char *ft_precompiled_stopwords[] = { diff --git a/storage/myisam/ft_stopwords.c b/storage/myisam/ft_stopwords.c index 9838b15af34..8aefffbee1d 100644 --- a/storage/myisam/ft_stopwords.c +++ b/storage/myisam/ft_stopwords.c @@ -51,10 +51,11 @@ static int ft_add_stopword(const char *w) int ft_init_stopwords() { + DBUG_ENTER("ft_init_stopwords"); if (!stopwords3) { if (!(stopwords3=(TREE *)my_malloc(sizeof(TREE),MYF(0)))) - return -1; + DBUG_RETURN(-1); init_tree(stopwords3,0,0,sizeof(FT_STOPWORD),(qsort_cmp2)&FT_STOPWORD_cmp, 0, (ft_stopword_file ? (tree_element_free)&FT_STOPWORD_free : 0), @@ -70,10 +71,10 @@ int ft_init_stopwords() int error=-1; if (!*ft_stopword_file) - return 0; + DBUG_RETURN(0); if ((fd=my_open(ft_stopword_file, O_RDONLY, MYF(MY_WME))) == -1) - return -1; + DBUG_RETURN(-1); len=(uint)my_seek(fd, 0L, MY_SEEK_END, MYF(0)); my_seek(fd, 0L, MY_SEEK_SET, MYF(0)); if (!(start=buffer=my_malloc(len+1, MYF(MY_WME)))) @@ -90,7 +91,7 @@ err1: my_free(buffer, MYF(0)); err0: my_close(fd, MYF(MY_WME)); - return error; + DBUG_RETURN(error); } else { @@ -100,13 +101,14 @@ err0: for (;*sws;sws++) { if (ft_add_stopword(*sws)) - return -1; + DBUG_RETURN(-1); } ft_stopword_file="(built-in)"; /* for SHOW VARIABLES */ } - return 0; + DBUG_RETURN(0); } + int is_stopword(char *word, uint len) { FT_STOPWORD sw; @@ -118,6 +120,8 @@ int is_stopword(char *word, uint len) void ft_free_stopwords() { + DBUG_ENTER("ft_free_stopwords"); + if (stopwords3) { delete_tree(stopwords3); /* purecov: inspected */ @@ -125,4 +129,5 @@ void ft_free_stopwords() stopwords3=0; } ft_stopword_file= 0; + DBUG_VOID_RETURN; } diff --git a/storage/myisam/fulltext.h b/storage/myisam/fulltext.h index 856e93e034d..9aef2d0d002 100644 --- a/storage/myisam/fulltext.h +++ b/storage/myisam/fulltext.h @@ -20,18 +20,8 @@ #include "myisamdef.h" #include "ft_global.h" -#define HA_FT_WTYPE HA_KEYTYPE_FLOAT -#define HA_FT_WLEN 4 -#define FT_SEGS 2 - -#define ft_sintXkorr(A) mi_sint4korr(A) -#define ft_intXstore(T,A) mi_int4store(T,A) - -extern const HA_KEYSEG ft_keysegs[FT_SEGS]; - int _mi_ft_cmp(MI_INFO *, uint, const uchar *, const uchar *); int _mi_ft_add(MI_INFO *, uint, uchar *, const uchar *, my_off_t); int _mi_ft_del(MI_INFO *, uint, uchar *, const uchar *, my_off_t); uint _mi_ft_convert_to_ft2(MI_INFO *, uint, uchar *); - diff --git a/storage/myisam/ha_myisam.cc b/storage/myisam/ha_myisam.cc index 5198e685817..ea9ba7012c3 100644 --- a/storage/myisam/ha_myisam.cc +++ b/storage/myisam/ha_myisam.cc @@ -79,13 +79,13 @@ static handler *myisam_create_handler(handlerton *hton, // collect errors printed by mi_check routines -static void mi_check_print_msg(MI_CHECK *param, const char* msg_type, +static void mi_check_print_msg(HA_CHECK *param, const char* msg_type, const char *fmt, va_list args) { THD* thd = (THD*)param->thd; Protocol *protocol= thd->protocol; size_t length, msg_length; - char msgbuf[MI_MAX_MSG_BUF]; + char msgbuf[HA_MAX_MSG_BUF]; char name[NAME_LEN*2+2]; msg_length= my_vsnprintf(msgbuf, sizeof(msgbuf), fmt, args); @@ -278,30 +278,31 @@ int table2myisam(TABLE *table_arg, MI_KEYDEF **keydef_out, DBUG_PRINT("loop", ("found: 0x%lx recpos: %d minpos: %d length: %d", (long) found, recpos, minpos, length)); if (recpos != minpos) - { // Reserved space (Null bits?) + { + /* reserve space for null bits */ bzero((char*) recinfo_pos, sizeof(*recinfo_pos)); - recinfo_pos->type= (int) FIELD_NORMAL; + recinfo_pos->type= FIELD_NORMAL; recinfo_pos++->length= (uint16) (minpos - recpos); } if (!found) break; if (found->flags & BLOB_FLAG) - recinfo_pos->type= (int) FIELD_BLOB; + recinfo_pos->type= FIELD_BLOB; else if (found->type() == MYSQL_TYPE_VARCHAR) recinfo_pos->type= FIELD_VARCHAR; else if (!(options & HA_OPTION_PACK_RECORD)) - recinfo_pos->type= (int) FIELD_NORMAL; + recinfo_pos->type= FIELD_NORMAL; else if (found->zero_pack()) - recinfo_pos->type= (int) FIELD_SKIP_ZERO; + recinfo_pos->type= FIELD_SKIP_ZERO; else - recinfo_pos->type= (int) ((length <= 3 || - (found->flags & ZEROFILL_FLAG)) ? - FIELD_NORMAL : - found->type() == MYSQL_TYPE_STRING || - found->type() == MYSQL_TYPE_VAR_STRING ? - FIELD_SKIP_ENDSPACE : - FIELD_SKIP_PRESPACE); + recinfo_pos->type= ((length <= 3 || + (found->flags & ZEROFILL_FLAG)) ? + FIELD_NORMAL : + found->type() == MYSQL_TYPE_STRING || + found->type() == MYSQL_TYPE_VAR_STRING ? + FIELD_SKIP_ENDSPACE : + FIELD_SKIP_PRESPACE); if (found->null_ptr) { recinfo_pos->null_bit= found->null_bit; @@ -327,7 +328,7 @@ int table2myisam(TABLE *table_arg, MI_KEYDEF **keydef_out, Check for underlying table conformance SYNOPSIS - check_definition() + myisam_check_definition() t1_keyinfo in First table key definition t1_recinfo in First table record definition t1_keys in Number of keys in first table @@ -496,13 +497,13 @@ int check_definition(MI_KEYDEF *t1_keyinfo, MI_COLUMNDEF *t1_recinfo, extern "C" { -volatile int *killed_ptr(MI_CHECK *param) +volatile int *killed_ptr(HA_CHECK *param) { /* In theory Unsafe conversion, but should be ok for now */ return (int*) &(((THD *)(param->thd))->killed); } -void mi_check_print_error(MI_CHECK *param, const char *fmt,...) +void mi_check_print_error(HA_CHECK *param, const char *fmt,...) { param->error_printed|=1; param->out_flag|= O_DATA_LOST; @@ -512,7 +513,7 @@ void mi_check_print_error(MI_CHECK *param, const char *fmt,...) va_end(args); } -void mi_check_print_info(MI_CHECK *param, const char *fmt,...) +void mi_check_print_info(HA_CHECK *param, const char *fmt,...) { va_list args; va_start(args, fmt); @@ -520,7 +521,7 @@ void mi_check_print_info(MI_CHECK *param, const char *fmt,...) va_end(args); } -void mi_check_print_warning(MI_CHECK *param, const char *fmt,...) +void mi_check_print_warning(HA_CHECK *param, const char *fmt,...) { param->warning_printed=1; param->out_flag|= O_DATA_LOST; @@ -720,7 +721,19 @@ int ha_myisam::open(const char *name, int mode, uint test_if_locked) if (!table->s->db_record_offset) int_table_flags|=HA_REC_NOT_IN_SEQ; if (file->s->options & (HA_OPTION_CHECKSUM | HA_OPTION_COMPRESS_RECORD)) - int_table_flags|=HA_HAS_CHECKSUM; + { + /* + Set which type of automatic checksum we have + The old checksum and new checksum are identical if there is no + null fields. + Files with new checksum has the HA_OPTION_NULL_FIELDS bit set. + */ + if ((file->s->options & HA_OPTION_NULL_FIELDS) || + !file->s->has_null_fields) + int_table_flags|= HA_HAS_NEW_CHECKSUM; + if (!(file->s->options & HA_OPTION_NULL_FIELDS)) + int_table_flags|= HA_HAS_OLD_CHECKSUM; + } for (i= 0; i < table->s->keys; i++) { @@ -776,7 +789,7 @@ int ha_myisam::check(THD* thd, HA_CHECK_OPT* check_opt) { if (!file) return HA_ADMIN_INTERNAL_ERROR; int error; - MI_CHECK param; + HA_CHECK param; MYISAM_SHARE* share = file->s; const char *old_proc_info=thd->proc_info; @@ -787,7 +800,7 @@ int ha_myisam::check(THD* thd, HA_CHECK_OPT* check_opt) param.db_name= table->s->db.str; param.table_name= table->alias; param.testflag = check_opt->flags | T_CHECK | T_SILENT; - param.stats_method= (enum_mi_stats_method)thd->variables.myisam_stats_method; + param.stats_method= (enum_handler_stats_method)thd->variables.myisam_stats_method; if (!(table->db_stat & HA_READ_ONLY)) param.testflag|= T_STATISTICS; @@ -816,13 +829,13 @@ int ha_myisam::check(THD* thd, HA_CHECK_OPT* check_opt) (param.testflag & (T_EXTEND | T_MEDIUM)))) || mi_is_crashed(file)) { - uint old_testflag=param.testflag; + ulonglong old_testflag= param.testflag; param.testflag|=T_MEDIUM; if (!(error= init_io_cache(¶m.read_cache, file->dfile, my_default_record_cache_size, READ_CACHE, share->pack.header_length, 1, MYF(MY_WME)))) { - error= chk_data_link(¶m, file, param.testflag & T_EXTEND); + error= chk_data_link(¶m, file, test(param.testflag & T_EXTEND)); end_io_cache(&(param.read_cache)); } param.testflag= old_testflag; @@ -868,7 +881,7 @@ int ha_myisam::check(THD* thd, HA_CHECK_OPT* check_opt) int ha_myisam::analyze(THD *thd, HA_CHECK_OPT* check_opt) { int error=0; - MI_CHECK param; + HA_CHECK param; MYISAM_SHARE* share = file->s; myisamchk_init(¶m); @@ -879,7 +892,7 @@ int ha_myisam::analyze(THD *thd, HA_CHECK_OPT* check_opt) param.testflag= (T_FAST | T_CHECK | T_SILENT | T_STATISTICS | T_DONT_CHECK_CHECKSUM); param.using_global_keycache = 1; - param.stats_method= (enum_mi_stats_method)thd->variables.myisam_stats_method; + param.stats_method= (enum_handler_stats_method)thd->variables.myisam_stats_method; if (!(share->state.changed & STATE_NOT_ANALYZED)) return HA_ADMIN_ALREADY_DONE; @@ -928,14 +941,21 @@ int ha_myisam::restore(THD* thd, HA_CHECK_OPT *check_opt) err: { - MI_CHECK param; - myisamchk_init(¶m); - param.thd= thd; - param.op_name= "restore"; - param.db_name= table->s->db.str; - param.table_name= table->s->table_name.str; - param.testflag= 0; - mi_check_print_error(¶m, errmsg, my_errno); + /* + Don't allocate param on stack here as this may be huge and it's + also allocated by repair() + */ + HA_CHECK *param; + if (!(param= (HA_CHECK*) my_malloc(sizeof(*param), MYF(MY_WME | MY_FAE)))) + DBUG_RETURN(error); + myisamchk_init(param); + param->thd= thd; + param->op_name= "restore"; + param->db_name= table->s->db.str; + param->table_name= table->s->table_name.str; + param->testflag= 0; + mi_check_print_error(param, errmsg, my_errno); + my_free(param, MYF(0)); DBUG_RETURN(error); } } @@ -991,7 +1011,7 @@ int ha_myisam::backup(THD* thd, HA_CHECK_OPT *check_opt) err: { - MI_CHECK param; + HA_CHECK param; myisamchk_init(¶m); param.thd= thd; param.op_name= "backup"; @@ -1007,7 +1027,7 @@ int ha_myisam::backup(THD* thd, HA_CHECK_OPT *check_opt) int ha_myisam::repair(THD* thd, HA_CHECK_OPT *check_opt) { int error; - MI_CHECK param; + HA_CHECK param; ha_rows start_records; if (!file) return HA_ADMIN_INTERNAL_ERROR; @@ -1018,7 +1038,7 @@ int ha_myisam::repair(THD* thd, HA_CHECK_OPT *check_opt) param.testflag= ((check_opt->flags & ~(T_EXTEND)) | T_SILENT | T_FORCE_CREATE | T_CALC_CHECKSUM | (check_opt->flags & T_EXTEND ? T_REP : T_REP_BY_SORT)); - param.sort_buffer_length= check_opt->sort_buffer_size; + param.sort_buffer_length= thd->variables.myisam_sort_buff_size; start_records=file->state->records; while ((error=repair(thd,param,0)) && param.retry_repair) { @@ -1026,7 +1046,9 @@ int ha_myisam::repair(THD* thd, HA_CHECK_OPT *check_opt) if (test_all_bits(param.testflag, (uint) (T_RETRY_WITHOUT_QUICK | T_QUICK))) { - param.testflag&= ~T_RETRY_WITHOUT_QUICK; + param.testflag&= ~(T_RETRY_WITHOUT_QUICK | T_QUICK); + /* Ensure we don't loose any rows when retrying without quick */ + param.testflag|= T_SAFE_REPAIR; sql_print_information("Retrying repair of: '%s' without quick", table->s->path.str); continue; @@ -1057,14 +1079,14 @@ int ha_myisam::optimize(THD* thd, HA_CHECK_OPT *check_opt) { int error; if (!file) return HA_ADMIN_INTERNAL_ERROR; - MI_CHECK param; + HA_CHECK param; myisamchk_init(¶m); param.thd = thd; param.op_name= "optimize"; param.testflag= (check_opt->flags | T_SILENT | T_FORCE_CREATE | T_REP_BY_SORT | T_STATISTICS | T_SORT_INDEX); - param.sort_buffer_length= check_opt->sort_buffer_size; + param.sort_buffer_length= thd->variables.myisam_sort_buff_size; if ((error= repair(thd,param,1)) && param.retry_repair) { sql_print_warning("Warning: Optimize table got errno %d on %s.%s, retrying", @@ -1076,10 +1098,10 @@ int ha_myisam::optimize(THD* thd, HA_CHECK_OPT *check_opt) } -int ha_myisam::repair(THD *thd, MI_CHECK ¶m, bool do_optimize) +int ha_myisam::repair(THD *thd, HA_CHECK ¶m, bool do_optimize) { int error=0; - uint local_testflag=param.testflag; + ulonglong local_testflag= param.testflag; bool optimize_done= !do_optimize, statistics_done=0; const char *old_proc_info=thd->proc_info; char fixed_name[FN_REFLEN]; @@ -1131,7 +1153,7 @@ int ha_myisam::repair(THD *thd, MI_CHECK ¶m, bool do_optimize) ulonglong key_map= ((local_testflag & T_CREATE_MISSING_KEYS) ? mi_get_mask_all_keys_active(share->base.keys) : share->state.key_map); - uint testflag=param.testflag; + ulonglong testflag= param.testflag; if (mi_test_if_sort_rep(file,file->state->records,key_map,0) && (local_testflag & T_REP_BY_SORT)) { @@ -1145,7 +1167,7 @@ int ha_myisam::repair(THD *thd, MI_CHECK ¶m, bool do_optimize) my_snprintf(buf, 40, "Repair with %d threads", my_count_bits(key_map)); thd_proc_info(thd, buf); error = mi_repair_parallel(¶m, file, fixed_name, - param.testflag & T_QUICK); + test(param.testflag & T_QUICK)); thd_proc_info(thd, "Repair done"); // to reset proc_info, as // it was pointing to local buffer } @@ -1153,7 +1175,7 @@ int ha_myisam::repair(THD *thd, MI_CHECK ¶m, bool do_optimize) { thd_proc_info(thd, "Repair by sorting"); error = mi_repair_by_sort(¶m, file, fixed_name, - param.testflag & T_QUICK); + test(param.testflag & T_QUICK)); } } else @@ -1161,9 +1183,9 @@ int ha_myisam::repair(THD *thd, MI_CHECK ¶m, bool do_optimize) thd_proc_info(thd, "Repair with keycache"); param.testflag &= ~T_REP_BY_SORT; error= mi_repair(¶m, file, fixed_name, - param.testflag & T_QUICK); + test(param.testflag & T_QUICK)); } - param.testflag=testflag; + param.testflag= testflag | (param.testflag & T_RETRY_WITHOUT_QUICK); optimize_done=1; } if (!error) @@ -1267,7 +1289,7 @@ int ha_myisam::assign_to_keycache(THD* thd, HA_CHECK_OPT *check_opt) if (error != HA_ADMIN_OK) { /* Send error to user */ - MI_CHECK param; + HA_CHECK param; myisamchk_init(¶m); param.thd= thd; param.op_name= "assign_to_keycache"; @@ -1331,7 +1353,7 @@ int ha_myisam::preload_keys(THD* thd, HA_CHECK_OPT *check_opt) err: { - MI_CHECK param; + HA_CHECK param; myisamchk_init(¶m); param.thd= thd; param.op_name= "preload_keys"; @@ -1441,7 +1463,7 @@ int ha_myisam::enable_indexes(uint mode) else if (mode == HA_KEY_SWITCH_NONUNIQ_SAVE) { THD *thd=current_thd; - MI_CHECK param; + HA_CHECK param; const char *save_proc_info=thd->proc_info; thd_proc_info(thd, "Creating index"); myisamchk_init(¶m); @@ -1450,7 +1472,8 @@ int ha_myisam::enable_indexes(uint mode) T_CREATE_MISSING_KEYS); param.myf_rw&= ~MY_WAIT_IF_FULL; param.sort_buffer_length= thd->variables.myisam_sort_buff_size; - param.stats_method= (enum_mi_stats_method)thd->variables.myisam_stats_method; + param.stats_method= + (enum_handler_stats_method)thd->variables.myisam_stats_method; param.tmpdir=&mysql_tmpdir_list; if ((error= (repair(thd,param,0) != HA_ADMIN_OK)) && param.retry_repair) { @@ -1566,11 +1589,11 @@ void ha_myisam::start_bulk_insert(ha_rows rows) != 0 Error */ -int ha_myisam::end_bulk_insert() +int ha_myisam::end_bulk_insert(bool abort) { mi_end_bulk_insert(file); int err=mi_extra(file, HA_EXTRA_NO_CACHE, 0); - if (!err) + if (!err && !abort) { if (can_enable_indexes) { @@ -1750,9 +1773,15 @@ int ha_myisam::rnd_next(uchar *buf) return error; } -int ha_myisam::restart_rnd_next(uchar *buf, uchar *pos) +int ha_myisam::remember_rnd_pos() +{ + position((uchar*) 0); + return 0; +} + +int ha_myisam::restart_rnd_next(uchar *buf) { - return rnd_pos(buf,pos); + return rnd_pos(buf, ref); } int ha_myisam::rnd_pos(uchar *buf, uchar *pos) @@ -1977,7 +2006,7 @@ void ha_myisam::get_auto_increment(ulonglong offset, ulonglong increment, { ulonglong nr; int error; - uchar key[MI_MAX_KEY_LENGTH]; + uchar key[HA_MAX_KEY_LENGTH]; if (!table->s->next_number_key_offset) { // Autoincrement at key-start @@ -2092,6 +2121,27 @@ bool ha_myisam::check_if_incompatible_data(HA_CREATE_INFO *info, return COMPATIBLE_DATA_YES; } + +/** + Check if a table is incompatible with the current version. + + The cases are: + - Table has checksum, varchars and are not of dynamic record type +*/ + +int ha_myisam::check_for_upgrade(HA_CHECK_OPT *check_opt) +{ + if (!(file->s->options & HA_OPTION_NULL_FIELDS) && + !(file->s->options & HA_OPTION_PACK_RECORD) && + file->s->has_varchar_fields) + { + /* We need alter there to get the HA_OPTION_NULL_FIELDS flag to be set */ + return HA_ADMIN_NEEDS_ALTER; + } + return HA_ADMIN_OK; +} + + extern int mi_panic(enum ha_panic_function flag); int myisam_panic(handlerton *hton, ha_panic_function flag) { diff --git a/storage/myisam/ha_myisam.h b/storage/myisam/ha_myisam.h index 55a5eac92de..1b7e948b928 100644 --- a/storage/myisam/ha_myisam.h +++ b/storage/myisam/ha_myisam.h @@ -21,6 +21,7 @@ /* class for the the myisam handler */ #include <myisam.h> +#include <myisamchk.h> #include <ft_global.h> #define HA_RECOVER_NONE 0 /* No automatic recover */ @@ -39,7 +40,7 @@ class ha_myisam: public handler ulonglong int_table_flags; char *data_file_name, *index_file_name; bool can_enable_indexes; - int repair(THD *thd, MI_CHECK ¶m, bool optimize); + int repair(THD *thd, HA_CHECK ¶m, bool optimize); public: ha_myisam(handlerton *hton, TABLE_SHARE *table_arg); @@ -56,8 +57,8 @@ class ha_myisam: public handler HA_READ_ORDER | HA_KEYREAD_ONLY); } uint max_supported_keys() const { return MI_MAX_KEY; } - uint max_supported_key_length() const { return MI_MAX_KEY_LENGTH; } - uint max_supported_key_part_length() const { return MI_MAX_KEY_LENGTH; } + uint max_supported_key_length() const { return HA_MAX_KEY_LENGTH; } + uint max_supported_key_part_length() const { return HA_MAX_KEY_LENGTH; } uint checksum() const; int open(const char *name, int mode, uint test_if_locked); @@ -93,7 +94,8 @@ class ha_myisam: public handler int rnd_init(bool scan); int rnd_next(uchar *buf); int rnd_pos(uchar * buf, uchar *pos); - int restart_rnd_next(uchar *buf, uchar *pos); + int remember_rnd_pos(); + int restart_rnd_next(uchar *buf); void position(const uchar *record); int info(uint); int extra(enum ha_extra_function operation); @@ -106,7 +108,7 @@ class ha_myisam: public handler int enable_indexes(uint mode); int indexes_are_disabled(void); void start_bulk_insert(ha_rows rows); - int end_bulk_insert(); + int end_bulk_insert(bool abort); ha_rows records_in_range(uint inx, key_range *min_key, key_range *max_key); void update_create_info(HA_CREATE_INFO *create_info); int create(const char *name, TABLE *form, HA_CREATE_INFO *create_info); @@ -118,6 +120,7 @@ class ha_myisam: public handler ulonglong *nb_reserved_values); int rename_table(const char * from, const char * to); int delete_table(const char *name); + int check_for_upgrade(HA_CHECK_OPT *check_opt); int check(THD* thd, HA_CHECK_OPT* check_opt); int analyze(THD* thd,HA_CHECK_OPT* check_opt); int repair(THD* thd, HA_CHECK_OPT* check_opt); diff --git a/storage/myisam/mi_cache.c b/storage/myisam/mi_cache.c index d6dcc431a8d..1ccf038a570 100644 --- a/storage/myisam/mi_cache.c +++ b/storage/myisam/mi_cache.c @@ -97,8 +97,8 @@ int _mi_read_cache(IO_CACHE *info, uchar *buff, my_off_t pos, uint length, DBUG_PRINT("error", ("Error %d reading next-multi-part block (Got %d bytes)", my_errno, (int) read_length)); - if (!my_errno || my_errno == -1) - my_errno=HA_ERR_WRONG_IN_RECORD; + if (!my_errno || my_errno == -1 || my_errno == HA_ERR_FILE_TOO_SHORT) + my_errno= HA_ERR_WRONG_IN_RECORD; DBUG_RETURN(1); } bzero(buff+read_length,MI_BLOCK_INFO_HEADER_LENGTH - in_buff_length - diff --git a/storage/myisam/mi_check.c b/storage/myisam/mi_check.c index 535b4f7f537..3de667ee4e2 100644 --- a/storage/myisam/mi_check.c +++ b/storage/myisam/mi_check.c @@ -59,14 +59,14 @@ /* Functions defined in this file */ -static int check_k_link(MI_CHECK *param, MI_INFO *info,uint nr); -static int chk_index(MI_CHECK *param, MI_INFO *info,MI_KEYDEF *keyinfo, +static int check_k_link(HA_CHECK *param, MI_INFO *info,uint nr); +static int chk_index(HA_CHECK *param, MI_INFO *info,MI_KEYDEF *keyinfo, my_off_t page, uchar *buff, ha_rows *keys, ha_checksum *key_checksum, uint level); static uint isam_key_length(MI_INFO *info,MI_KEYDEF *keyinfo); static ha_checksum calc_checksum(ha_rows count); static int writekeys(MI_SORT_PARAM *sort_param); -static int sort_one_index(MI_CHECK *param, MI_INFO *info,MI_KEYDEF *keyinfo, +static int sort_one_index(HA_CHECK *param, MI_INFO *info,MI_KEYDEF *keyinfo, my_off_t pagepos, File new_file); static int sort_key_read(MI_SORT_PARAM *sort_param,void *key); static int sort_ft_key_read(MI_SORT_PARAM *sort_param,void *key); @@ -80,13 +80,13 @@ static int sort_insert_key(MI_SORT_PARAM *sort_param, reg1 SORT_KEY_BLOCKS *key_block, uchar *key, my_off_t prev_block); static int sort_delete_record(MI_SORT_PARAM *sort_param); -/*static int flush_pending_blocks(MI_CHECK *param);*/ -static SORT_KEY_BLOCKS *alloc_key_blocks(MI_CHECK *param, uint blocks, +/*static int flush_pending_blocks(HA_CHECK *param);*/ +static SORT_KEY_BLOCKS *alloc_key_blocks(HA_CHECK *param, uint blocks, uint buffer_length); static ha_checksum mi_byte_checksum(const uchar *buf, uint length); -static void set_data_file_type(SORT_INFO *sort_info, MYISAM_SHARE *share); +static void set_data_file_type(MI_SORT_INFO *sort_info, MYISAM_SHARE *share); -void myisamchk_init(MI_CHECK *param) +void myisamchk_init(HA_CHECK *param) { bzero((uchar*) param,sizeof(*param)); param->opt_follow_links=1; @@ -108,7 +108,7 @@ void myisamchk_init(MI_CHECK *param) /* Check the status flags for the table */ -int chk_status(MI_CHECK *param, register MI_INFO *info) +int chk_status(HA_CHECK *param, register MI_INFO *info) { MYISAM_SHARE *share=info->s; @@ -136,7 +136,7 @@ int chk_status(MI_CHECK *param, register MI_INFO *info) /* Check delete links */ -int chk_del(MI_CHECK *param, register MI_INFO *info, uint test_flag) +int chk_del(HA_CHECK *param, register MI_INFO *info, ulonglong test_flag) { reg2 ha_rows i; uint delete_link_length; @@ -245,7 +245,7 @@ wrong: /* Check delete links in index file */ -static int check_k_link(MI_CHECK *param, register MI_INFO *info, uint nr) +static int check_k_link(HA_CHECK *param, register MI_INFO *info, uint nr) { my_off_t next_link; uint block_size=(nr+1)*MI_MIN_KEY_BLOCK_LENGTH; @@ -323,7 +323,7 @@ static int check_k_link(MI_CHECK *param, register MI_INFO *info, uint nr) /* Check sizes of files */ -int chk_size(MI_CHECK *param, register MI_INFO *info) +int chk_size(HA_CHECK *param, register MI_INFO *info) { int error=0; register my_off_t skr,size; @@ -399,7 +399,7 @@ int chk_size(MI_CHECK *param, register MI_INFO *info) /* Check keys */ -int chk_key(MI_CHECK *param, register MI_INFO *info) +int chk_key(HA_CHECK *param, register MI_INFO *info) { uint key,found_keys=0,full_text_keys=0,result=0; ha_rows keys; @@ -584,7 +584,7 @@ do_stat: } /* chk_key */ -static int chk_index_down(MI_CHECK *param, MI_INFO *info, MI_KEYDEF *keyinfo, +static int chk_index_down(HA_CHECK *param, MI_INFO *info, MI_KEYDEF *keyinfo, my_off_t page, uchar *buff, ha_rows *keys, ha_checksum *key_checksum, uint level) { @@ -731,7 +731,7 @@ int mi_collect_stats_nonulls_next(HA_KEYSEG *keyseg, ulonglong *notnull, /* Check if index is ok */ -static int chk_index(MI_CHECK *param, MI_INFO *info, MI_KEYDEF *keyinfo, +static int chk_index(HA_CHECK *param, MI_INFO *info, MI_KEYDEF *keyinfo, my_off_t page, uchar *buff, ha_rows *keys, ha_checksum *key_checksum, uint level) { @@ -755,7 +755,10 @@ static int chk_index(MI_CHECK *param, MI_INFO *info, MI_KEYDEF *keyinfo, } if (keyinfo->flag & HA_NOSAME) - comp_flag=SEARCH_FIND | SEARCH_UPDATE; /* Not real duplicates */ + { + /* Not real duplicates */ + comp_flag= SEARCH_FIND | SEARCH_UPDATE | SEARCH_INSERT; + } else comp_flag=SEARCH_SAME; /* Keys in positionorder */ nod_flag=mi_test_if_nod(buff); @@ -800,9 +803,9 @@ static int chk_index(MI_CHECK *param, MI_INFO *info, MI_KEYDEF *keyinfo, (flag=ha_key_cmp(keyinfo->seg,info->lastkey,key,key_length, comp_flag, diff_pos)) >=0) { - DBUG_DUMP("old",(uchar*) info->lastkey, info->lastkey_length); - DBUG_DUMP("new",(uchar*) key, key_length); - DBUG_DUMP("new_in_page",(char*) old_keypos,(uint) (keypos-old_keypos)); + DBUG_DUMP("old",info->lastkey, info->lastkey_length); + DBUG_DUMP("new",key, key_length); + DBUG_DUMP("new_in_page",old_keypos,(uint) (keypos-old_keypos)); if (comp_flag & SEARCH_FIND && flag == 0) mi_check_print_error(param,"Found duplicated key at page %s",llstr(page,llbuff)); @@ -871,8 +874,8 @@ static int chk_index(MI_CHECK *param, MI_INFO *info, MI_KEYDEF *keyinfo, DBUG_PRINT("test",("page: %s record: %s filelength: %s", llstr(page,llbuff),llstr(record,llbuff2), llstr(info->state->data_file_length,llbuff3))); - DBUG_DUMP("key",(uchar*) key,key_length); - DBUG_DUMP("new_in_page",(char*) old_keypos,(uint) (keypos-old_keypos)); + DBUG_DUMP("key",key,key_length); + DBUG_DUMP("new_in_page",old_keypos,(uint) (keypos-old_keypos)); goto err; } param->record_checksum+=(ha_checksum) record; @@ -934,7 +937,7 @@ static uint isam_key_length(MI_INFO *info, register MI_KEYDEF *keyinfo) /* Check that record-link is ok */ -int chk_data_link(MI_CHECK *param, MI_INFO *info,int extend) +int chk_data_link(HA_CHECK *param, MI_INFO *info, my_bool extend) { int error,got_error,flag; uint key,left_length,b_type,field; @@ -992,6 +995,9 @@ int chk_data_link(MI_CHECK *param, MI_INFO *info,int extend) if (*killed_ptr(param)) goto err2; switch (info->s->data_file_type) { + case BLOCK_RECORD: + DBUG_ASSERT(0); /* Impossible */ + break; case STATIC_RECORD: if (my_b_read(¶m->read_cache,(uchar*) record, info->s->base.pack_reclength)) @@ -1005,7 +1011,7 @@ int chk_data_link(MI_CHECK *param, MI_INFO *info,int extend) del_length+=info->s->base.pack_reclength; continue; /* Record removed */ } - param->glob_crc+= mi_static_checksum(info,record); + param->glob_crc+= (*info->s->calc_check_checksum)(info,record); used+=info->s->base.pack_reclength; break; case DYNAMIC_RECORD: @@ -1159,7 +1165,7 @@ int chk_data_link(MI_CHECK *param, MI_INFO *info,int extend) } else { - info->checksum=mi_checksum(info,record); + info->checksum= (*info->s->calc_check_checksum)(info,record); if (param->testflag & (T_EXTEND | T_MEDIUM | T_VERBOSE)) { if (_mi_rec_check(info,record, info->rec_buff,block_info.rec_len, @@ -1205,15 +1211,10 @@ int chk_data_link(MI_CHECK *param, MI_INFO *info,int extend) llstr(start_recpos,llbuff)); got_error=1; } - if (static_row_size) - param->glob_crc+= mi_static_checksum(info,record); - else - param->glob_crc+= mi_checksum(info,record); + param->glob_crc+= (*info->s->calc_check_checksum)(info,record); link_used+= (block_info.filepos - start_recpos); used+= (pos-start_recpos); break; - case BLOCK_RECORD: - assert(0); /* Impossible */ } /* switch */ if (! got_error) { @@ -1432,7 +1433,7 @@ int chk_data_link(MI_CHECK *param, MI_INFO *info,int extend) then recrate all indexes. */ -static int mi_drop_all_indexes(MI_CHECK *param, MI_INFO *info, my_bool force) +static int mi_drop_all_indexes(HA_CHECK *param, MI_INFO *info, my_bool force) { MYISAM_SHARE *share= info->s; MI_STATE_INFO *state= &share->state; @@ -1515,7 +1516,7 @@ static int mi_drop_all_indexes(MI_CHECK *param, MI_INFO *info, my_bool force) /* Recover old table by reading each record and writing all keys */ /* Save new datafile-name in temp_filename */ -int mi_repair(MI_CHECK *param, register MI_INFO *info, +int mi_repair(HA_CHECK *param, register MI_INFO *info, char * name, int rep_quick) { int error,got_error; @@ -1524,7 +1525,7 @@ int mi_repair(MI_CHECK *param, register MI_INFO *info, File new_file; MYISAM_SHARE *share=info->s; char llbuff[22],llbuff2[22]; - SORT_INFO sort_info; + MI_SORT_INFO sort_info; MI_SORT_PARAM sort_param; DBUG_ENTER("mi_repair"); @@ -1892,7 +1893,7 @@ int movepoint(register MI_INFO *info, uchar *record, my_off_t oldpos, /* Tell system that we want all memory for our cache */ -void lock_memory(MI_CHECK *param __attribute__((unused))) +void lock_memory(HA_CHECK *param __attribute__((unused))) { #ifdef SUN_OS /* Key-cacheing thrases on sun 4.1 */ if (param->opt_lock_memory) @@ -1908,7 +1909,7 @@ void lock_memory(MI_CHECK *param __attribute__((unused))) /* Flush all changed blocks to disk */ -int flush_blocks(MI_CHECK *param, KEY_CACHE *key_cache, File file) +int flush_blocks(HA_CHECK *param, KEY_CACHE *key_cache, File file) { if (flush_key_blocks(key_cache, file, FLUSH_RELEASE)) { @@ -1923,7 +1924,7 @@ int flush_blocks(MI_CHECK *param, KEY_CACHE *key_cache, File file) /* Sort index for more efficent reads */ -int mi_sort_index(MI_CHECK *param, register MI_INFO *info, char * name) +int mi_sort_index(HA_CHECK *param, register MI_INFO *info, char * name) { reg2 uint key; reg1 MI_KEYDEF *keyinfo; @@ -2023,7 +2024,7 @@ err2: /* Sort records recursive using one index */ -static int sort_one_index(MI_CHECK *param, MI_INFO *info, MI_KEYDEF *keyinfo, +static int sort_one_index(HA_CHECK *param, MI_INFO *info, MI_KEYDEF *keyinfo, my_off_t pagepos, File new_file) { uint length,nod_flag,used_length, key_length; @@ -2143,12 +2144,12 @@ int change_to_newfile(const char * filename, const char * old_ext, /* Locks a whole file */ /* Gives an error-message if file can't be locked */ -int lock_file(MI_CHECK *param, File file, my_off_t start, int lock_type, +int lock_file(HA_CHECK *param, File file, my_off_t start, int lock_type, const char *filetype, const char *filename) { if (my_lock(file,lock_type,start,F_TO_EOF, param->testflag & T_WAIT_FOREVER ? MYF(MY_SEEK_NOT_DONE) : - MYF(MY_SEEK_NOT_DONE | MY_DONT_WAIT))) + MYF(MY_SEEK_NOT_DONE | MY_SHORT_WAIT))) { mi_check_print_error(param," %d when locking %s '%s'",my_errno,filetype,filename); param->error_printed=2; /* Don't give that data is crashed */ @@ -2160,7 +2161,7 @@ int lock_file(MI_CHECK *param, File file, my_off_t start, int lock_type, /* Copy a block between two files */ -int filecopy(MI_CHECK *param, File to,File from,my_off_t start, +int filecopy(HA_CHECK *param, File to,File from,my_off_t start, my_off_t length, const char *type) { char tmp_buff[IO_SIZE],*buff; @@ -2211,7 +2212,7 @@ err: <>0 Error */ -int mi_repair_by_sort(MI_CHECK *param, register MI_INFO *info, +int mi_repair_by_sort(HA_CHECK *param, register MI_INFO *info, const char * name, int rep_quick) { int got_error; @@ -2225,7 +2226,7 @@ int mi_repair_by_sort(MI_CHECK *param, register MI_INFO *info, HA_KEYSEG *keyseg; ulong *rec_per_key_part; char llbuff[22]; - SORT_INFO sort_info; + MI_SORT_INFO sort_info; ulonglong key_map; DBUG_ENTER("mi_repair_by_sort"); LINT_INIT(key_map); @@ -2497,7 +2498,7 @@ int mi_repair_by_sort(MI_CHECK *param, register MI_INFO *info, goto err; } - if (rep_quick & T_FORCE_UNIQUENESS) + if (rep_quick && (param->testflag & T_FORCE_UNIQUENESS)) { my_off_t skr=info->state->data_file_length+ (share->options & HA_OPTION_COMPRESS_RECORD ? @@ -2630,7 +2631,7 @@ err: <>0 Error */ -int mi_repair_parallel(MI_CHECK *param, register MI_INFO *info, +int mi_repair_parallel(HA_CHECK *param, register MI_INFO *info, const char * name, int rep_quick) { #ifndef THREAD @@ -2649,7 +2650,7 @@ int mi_repair_parallel(MI_CHECK *param, register MI_INFO *info, char llbuff[22]; IO_CACHE new_data_cache; /* For non-quick repair. */ IO_CACHE_SHARE io_share; - SORT_INFO sort_info; + MI_SORT_INFO sort_info; ulonglong key_map; pthread_attr_t thr_attr; ulong max_pack_reclength; @@ -2674,14 +2675,14 @@ int mi_repair_parallel(MI_CHECK *param, register MI_INFO *info, /* Quick repair (not touching data file, rebuilding indexes): { - Read cache is (MI_CHECK *param)->read_cache using info->dfile. + Read cache is (HA_CHECK *param)->read_cache using info->dfile. } Non-quick repair (rebuilding data file and indexes): { Master thread: - Read cache is (MI_CHECK *param)->read_cache using info->dfile. + Read cache is (HA_CHECK *param)->read_cache using info->dfile. Write cache is (MI_INFO *info)->rec_cache using new_file. Slave threads: @@ -3020,7 +3021,7 @@ int mi_repair_parallel(MI_CHECK *param, register MI_INFO *info, goto err; } - if (rep_quick & T_FORCE_UNIQUENESS) + if (rep_quick && (param->testflag & T_FORCE_UNIQUENESS)) { my_off_t skr=info->state->data_file_length+ (share->options & HA_OPTION_COMPRESS_RECORD ? @@ -3129,7 +3130,7 @@ err: static int sort_key_read(MI_SORT_PARAM *sort_param, void *key) { int error; - SORT_INFO *sort_info=sort_param->sort_info; + MI_SORT_INFO *sort_info=sort_param->sort_info; MI_INFO *info=sort_info->info; DBUG_ENTER("sort_key_read"); @@ -3146,7 +3147,7 @@ static int sort_key_read(MI_SORT_PARAM *sort_param, void *key) (info->s->rec_reflength+ _mi_make_key(info, sort_param->key, (uchar*) key, sort_param->record, sort_param->filepos)); -#ifdef HAVE_purify +#ifdef HAVE_valgrind bzero(key+sort_param->real_key_length, (sort_param->key_length-sort_param->real_key_length)); #endif @@ -3156,7 +3157,7 @@ static int sort_key_read(MI_SORT_PARAM *sort_param, void *key) static int sort_ft_key_read(MI_SORT_PARAM *sort_param, void *key) { int error; - SORT_INFO *sort_info=sort_param->sort_info; + MI_SORT_INFO *sort_info=sort_param->sort_info; MI_INFO *info=sort_info->info; FT_WORD *wptr=0; DBUG_ENTER("sort_ft_key_read"); @@ -3186,7 +3187,7 @@ static int sort_ft_key_read(MI_SORT_PARAM *sort_param, void *key) sort_param->real_key_length=(info->s->rec_reflength+ _ft_make_key(info, sort_param->key, key, wptr++, sort_param->filepos)); -#ifdef HAVE_purify +#ifdef HAVE_valgrind if (sort_param->key_length > sort_param->real_key_length) bzero(key+sort_param->real_key_length, (sort_param->key_length-sort_param->real_key_length)); @@ -3243,8 +3244,8 @@ static int sort_get_next_record(MI_SORT_PARAM *sort_param) my_off_t pos; uchar *to; MI_BLOCK_INFO block_info; - SORT_INFO *sort_info=sort_param->sort_info; - MI_CHECK *param=sort_info->param; + MI_SORT_INFO *sort_info=sort_param->sort_info; + HA_CHECK *param=sort_info->param; MI_INFO *info=sort_info->info; MYISAM_SHARE *share=info->s; char llbuff[22],llbuff2[22]; @@ -3254,6 +3255,9 @@ static int sort_get_next_record(MI_SORT_PARAM *sort_param) DBUG_RETURN(1); switch (share->data_file_type) { + case BLOCK_RECORD: + DBUG_ASSERT(0); /* Impossible */ + break; case STATIC_RECORD: for (;;) { @@ -3278,7 +3282,9 @@ static int sort_get_next_record(MI_SORT_PARAM *sort_param) { if (sort_param->calc_checksum) param->glob_crc+= (info->checksum= - mi_static_checksum(info,sort_param->record)); + (*info->s->calc_check_checksum)(info, + sort_param-> + record)); DBUG_RETURN(0); } if (!sort_param->fix_datafile && sort_param->master) @@ -3554,7 +3560,8 @@ static int sort_get_next_record(MI_SORT_PARAM *sort_param) if (sort_param->read_cache.error < 0) DBUG_RETURN(1); if (sort_param->calc_checksum) - info->checksum= mi_checksum(info, sort_param->record); + info->checksum= (*info->s->calc_check_checksum)(info, + sort_param->record); if ((param->testflag & (T_EXTEND | T_REP)) || searching) { if (_mi_rec_check(info, sort_param->record, sort_param->rec_buff, @@ -3639,11 +3646,11 @@ static int sort_get_next_record(MI_SORT_PARAM *sort_param) info->packed_length=block_info.rec_len; if (sort_param->calc_checksum) param->glob_crc+= (info->checksum= - mi_checksum(info, sort_param->record)); + (*info->s->calc_check_checksum)(info, + sort_param-> + record)); DBUG_RETURN(0); } - case BLOCK_RECORD: - assert(0); /* Impossible */ } DBUG_RETURN(1); /* Impossible */ } @@ -3671,8 +3678,8 @@ int sort_write_record(MI_SORT_PARAM *sort_param) ulong block_length,reclength; uchar *from; uchar block_buff[8]; - SORT_INFO *sort_info=sort_param->sort_info; - MI_CHECK *param=sort_info->param; + MI_SORT_INFO *sort_info=sort_param->sort_info; + HA_CHECK *param=sort_info->param; MI_INFO *info=sort_info->info; MYISAM_SHARE *share=info->s; DBUG_ENTER("sort_write_record"); @@ -3680,6 +3687,9 @@ int sort_write_record(MI_SORT_PARAM *sort_param) if (sort_param->fix_datafile) { switch (sort_info->new_data_file_type) { + case BLOCK_RECORD: + DBUG_ASSERT(0); /* Impossible */ + break; case STATIC_RECORD: if (my_b_write(&info->rec_cache,sort_param->record, share->base.pack_reclength)) @@ -3689,7 +3699,6 @@ int sort_write_record(MI_SORT_PARAM *sort_param) } sort_param->filepos+=share->base.pack_reclength; info->s->state.split++; - /* sort_info->param->glob_crc+=mi_static_checksum(info, sort_param->record); */ break; case DYNAMIC_RECORD: if (! info->blobs) @@ -3698,7 +3707,7 @@ int sort_write_record(MI_SORT_PARAM *sort_param) { /* must be sure that local buffer is big enough */ reclength=info->s->base.pack_reclength+ - _my_calc_total_blob_length(info,sort_param->record)+ + _mi_calc_total_blob_length(info,sort_param->record)+ ALIGN_SIZE(MI_MAX_DYN_BLOCK_HEADER)+MI_SPLIT_LENGTH+ MI_DYN_DELETE_BLOCK_HEADER; if (sort_info->buff_length < reclength) @@ -3712,10 +3721,9 @@ int sort_write_record(MI_SORT_PARAM *sort_param) from= sort_info->buff+ALIGN_SIZE(MI_MAX_DYN_BLOCK_HEADER); } /* We can use info->checksum here as only one thread calls this. */ - info->checksum=mi_checksum(info,sort_param->record); + info->checksum= (*info->s->calc_check_checksum)(info,sort_param->record); reclength=_mi_rec_pack(info,from,sort_param->record); flag=0; - /* sort_info->param->glob_crc+=info->checksum; */ do { @@ -3755,8 +3763,6 @@ int sort_write_record(MI_SORT_PARAM *sort_param) sort_param->filepos+=reclength+length; info->s->state.split++; break; - case BLOCK_RECORD: - assert(0); /* Impossible */ } } if (sort_param->master) @@ -3789,24 +3795,26 @@ static int sort_key_write(MI_SORT_PARAM *sort_param, const void *a) { uint diff_pos[2]; char llbuff[22],llbuff2[22]; - SORT_INFO *sort_info=sort_param->sort_info; - MI_CHECK *param= sort_info->param; + MI_SORT_INFO *sort_info=sort_param->sort_info; + HA_CHECK *param= sort_info->param; int cmp; if (sort_info->key_block->inited) { - cmp=ha_key_cmp(sort_param->seg,sort_info->key_block->lastkey, - (uchar*) a, USE_WHOLE_KEY,SEARCH_FIND | SEARCH_UPDATE, + cmp=ha_key_cmp(sort_param->seg, (uchar*) sort_info->key_block->lastkey, + (uchar*) a, USE_WHOLE_KEY, + SEARCH_FIND | SEARCH_UPDATE | SEARCH_INSERT, diff_pos); if (param->stats_method == MI_STATS_METHOD_NULLS_NOT_EQUAL) - ha_key_cmp(sort_param->seg,sort_info->key_block->lastkey, + ha_key_cmp(sort_param->seg, (uchar*) sort_info->key_block->lastkey, (uchar*) a, USE_WHOLE_KEY, SEARCH_FIND | SEARCH_NULL_ARE_NOT_EQUAL, diff_pos); else if (param->stats_method == MI_STATS_METHOD_IGNORE_NULLS) { diff_pos[0]= mi_collect_stats_nonulls_next(sort_param->seg, sort_param->notnull, - sort_info->key_block->lastkey, + (uchar*) sort_info-> + key_block->lastkey, (uchar*)a); } sort_param->unique[diff_pos[0]-1]++; @@ -3829,8 +3837,8 @@ static int sort_key_write(MI_SORT_PARAM *sort_param, const void *a) llstr(sort_info->info->lastpos,llbuff), llstr(get_record_for_key(sort_info->info, sort_param->keyinfo, - sort_info->key_block-> - lastkey), + (uchar*) sort_info-> + key_block->lastkey), llbuff2)); param->testflag|=T_RETRY_WITHOUT_QUICK; if (sort_info->param->testflag & T_VERBOSE) @@ -3851,7 +3859,7 @@ static int sort_key_write(MI_SORT_PARAM *sort_param, const void *a) int sort_ft_buf_flush(MI_SORT_PARAM *sort_param) { - SORT_INFO *sort_info=sort_param->sort_info; + MI_SORT_INFO *sort_info=sort_param->sort_info; SORT_KEY_BLOCKS *key_block=sort_info->key_block; MYISAM_SHARE *share=sort_info->info->s; uint val_off, val_len; @@ -3861,19 +3869,19 @@ int sort_ft_buf_flush(MI_SORT_PARAM *sort_param) val_len=share->ft2_keyinfo.keylength; get_key_full_length_rdonly(val_off, ft_buf->lastkey); - to=ft_buf->lastkey+val_off; + to= (uchar*) ft_buf->lastkey+val_off; if (ft_buf->buf) { /* flushing first-level tree */ - error=sort_insert_key(sort_param,key_block,ft_buf->lastkey, + error=sort_insert_key(sort_param,key_block, (uchar*) ft_buf->lastkey, HA_OFFSET_ERROR); for (from=to+val_len; - !error && from < ft_buf->buf; + !error && from < (uchar*) ft_buf->buf; from+= val_len) { memcpy(to, from, val_len); - error=sort_insert_key(sort_param,key_block,ft_buf->lastkey, + error=sort_insert_key(sort_param,key_block, (uchar*) ft_buf->lastkey, HA_OFFSET_ERROR); } return error; @@ -3882,8 +3890,8 @@ int sort_ft_buf_flush(MI_SORT_PARAM *sort_param) error=flush_pending_blocks(sort_param); /* updating lastkey with second-level tree info */ ft_intXstore(ft_buf->lastkey+val_off, -ft_buf->count); - _mi_dpointer(sort_info->info, ft_buf->lastkey+val_off+HA_FT_WLEN, - share->state.key_root[sort_param->key]); + _mi_dpointer(sort_info->info, (uchar*) ft_buf->lastkey+val_off+HA_FT_WLEN, + share->state.key_root[sort_param->key]); /* restoring first level tree data in sort_info/sort_param */ sort_info->key_block=sort_info->key_block_end- sort_info->param->sort_key_blocks; sort_param->keyinfo=share->keyinfo+sort_param->key; @@ -3891,14 +3899,14 @@ int sort_ft_buf_flush(MI_SORT_PARAM *sort_param) /* writing lastkey in first-level tree */ return error ? error : sort_insert_key(sort_param,sort_info->key_block, - ft_buf->lastkey,HA_OFFSET_ERROR); + (uchar*) ft_buf->lastkey,HA_OFFSET_ERROR); } static int sort_ft_key_write(MI_SORT_PARAM *sort_param, const void *a) { uint a_len, val_off, val_len, error; uchar *p; - SORT_INFO *sort_info=sort_param->sort_info; + MI_SORT_INFO *sort_info=sort_param->sort_info; SORT_FT_BUF *ft_buf=sort_info->ft_buf; SORT_KEY_BLOCKS *key_block=sort_info->key_block; @@ -3930,7 +3938,7 @@ static int sort_ft_key_write(MI_SORT_PARAM *sort_param, const void *a) if (ha_compare_text(sort_param->seg->charset, ((uchar *)a)+1,a_len-1, - ft_buf->lastkey+1,val_off-1, 0, 0)==0) + (uchar*) ft_buf->lastkey+1,val_off-1, 0, 0)==0) { if (!ft_buf->buf) /* store in second-level tree */ { @@ -3946,16 +3954,16 @@ static int sort_ft_key_write(MI_SORT_PARAM *sort_param, const void *a) return 0; /* converting to two-level tree */ - p=ft_buf->lastkey+val_off; + p= (uchar*) ft_buf->lastkey+val_off; while (key_block->inited) key_block++; sort_info->key_block=key_block; sort_param->keyinfo=& sort_info->info->s->ft2_keyinfo; - ft_buf->count=(uint) (ft_buf->buf - p)/val_len; + ft_buf->count=((uchar*) ft_buf->buf - p)/val_len; /* flushing buffer to second-level tree */ - for (error=0; !error && p < ft_buf->buf; p+= val_len) + for (error=0; !error && p < (uchar*) ft_buf->buf; p+= val_len) error=sort_insert_key(sort_param,key_block,p,HA_OFFSET_ERROR); ft_buf->buf=0; return error; @@ -4003,13 +4011,13 @@ static int sort_insert_key(MI_SORT_PARAM *sort_param, MI_KEY_PARAM s_temp; MI_INFO *info; MI_KEYDEF *keyinfo=sort_param->keyinfo; - SORT_INFO *sort_info= sort_param->sort_info; - MI_CHECK *param=sort_info->param; + MI_SORT_INFO *sort_info= sort_param->sort_info; + HA_CHECK *param=sort_info->param; DBUG_ENTER("sort_insert_key"); - anc_buff=key_block->buff; + anc_buff= (uchar*) key_block->buff; info=sort_info->info; - lastkey=key_block->lastkey; + lastkey= (uchar*) key_block->lastkey; nod_flag= (key_block == sort_info->key_block ? 0 : info->s->base.key_reflength); @@ -4022,7 +4030,7 @@ static int sort_insert_key(MI_SORT_PARAM *sort_param, DBUG_RETURN(1); } a_length=2+nod_flag; - key_block->end_pos=anc_buff+2; + key_block->end_pos= anc_buff+2; lastkey=0; /* No previous key in block */ } else @@ -4030,18 +4038,18 @@ static int sort_insert_key(MI_SORT_PARAM *sort_param, /* Save pointer to previous block */ if (nod_flag) - _mi_kpointer(info,key_block->end_pos,prev_block); + _mi_kpointer(info,(uchar*) key_block->end_pos,prev_block); t_length=(*keyinfo->pack_key)(keyinfo,nod_flag, (uchar*) 0,lastkey,lastkey,key, &s_temp); - (*keyinfo->store_key)(keyinfo, key_block->end_pos+nod_flag,&s_temp); + (*keyinfo->store_key)(keyinfo, (uchar*) key_block->end_pos+nod_flag,&s_temp); a_length+=t_length; mi_putint(anc_buff,a_length,nod_flag); key_block->end_pos+=t_length; if (a_length <= keyinfo->block_length) { - VOID(_mi_move_key(keyinfo,key_block->lastkey,key)); + VOID(_mi_move_key(keyinfo,(uchar*) key_block->lastkey,key)); key_block->last_length=a_length-t_length; DBUG_RETURN(0); } @@ -4066,7 +4074,8 @@ static int sort_insert_key(MI_SORT_PARAM *sort_param, DBUG_DUMP("buff",(uchar*) anc_buff,mi_getint(anc_buff)); /* Write separator-key to block in next level */ - if (sort_insert_key(sort_param,key_block+1,key_block->lastkey,filepos)) + if (sort_insert_key(sort_param,key_block+1,(uchar*) key_block->lastkey, + filepos)) DBUG_RETURN(1); /* clear old block and write new key in it */ @@ -4082,8 +4091,8 @@ static int sort_delete_record(MI_SORT_PARAM *sort_param) uint i; int old_file,error; uchar *key; - SORT_INFO *sort_info=sort_param->sort_info; - MI_CHECK *param=sort_info->param; + MI_SORT_INFO *sort_info=sort_param->sort_info; + HA_CHECK *param=sort_info->param; MI_INFO *info=sort_info->info; DBUG_ENTER("sort_delete_record"); @@ -4139,7 +4148,7 @@ int flush_pending_blocks(MI_SORT_PARAM *sort_param) uint nod_flag,length; my_off_t filepos,key_file_length; SORT_KEY_BLOCKS *key_block; - SORT_INFO *sort_info= sort_param->sort_info; + MI_SORT_INFO *sort_info= sort_param->sort_info; myf myf_rw=sort_info->param->myf_rw; MI_INFO *info=sort_info->info; MI_KEYDEF *keyinfo=sort_param->keyinfo; @@ -4152,7 +4161,7 @@ int flush_pending_blocks(MI_SORT_PARAM *sort_param) key_block->inited=0; length=mi_getint(key_block->buff); if (nod_flag) - _mi_kpointer(info,key_block->end_pos,filepos); + _mi_kpointer(info,(uchar*) key_block->end_pos,filepos); key_file_length=info->state->key_file_length; bzero((uchar*) key_block->buff+length, keyinfo->block_length-length); if ((filepos=_mi_new(info,keyinfo,DFLT_INIT_HITS)) == HA_OFFSET_ERROR) @@ -4162,7 +4171,7 @@ int flush_pending_blocks(MI_SORT_PARAM *sort_param) if (key_file_length == info->state->key_file_length) { if (_mi_write_keypage(info, keyinfo, filepos, - DFLT_INIT_HITS, key_block->buff)) + DFLT_INIT_HITS, (uchar*) key_block->buff)) DBUG_RETURN(1); } else if (my_pwrite(info->s->kfile,(uchar*) key_block->buff, @@ -4177,7 +4186,7 @@ int flush_pending_blocks(MI_SORT_PARAM *sort_param) /* alloc space and pointers for key_blocks */ -static SORT_KEY_BLOCKS *alloc_key_blocks(MI_CHECK *param, uint blocks, +static SORT_KEY_BLOCKS *alloc_key_blocks(HA_CHECK *param, uint blocks, uint buffer_length) { reg1 uint i; @@ -4214,7 +4223,7 @@ int test_if_almost_full(MI_INFO *info) /* Recreate table with bigger more alloced record-data */ -int recreate_table(MI_CHECK *param, MI_INFO **org_info, char *filename) +int recreate_table(HA_CHECK *param, MI_INFO **org_info, char *filename) { int error; MI_INFO info; @@ -4387,7 +4396,7 @@ end: /* write suffix to data file if neaded */ -int write_data_suffix(SORT_INFO *sort_info, my_bool fix_datafile) +int write_data_suffix(MI_SORT_INFO *sort_info, my_bool fix_datafile) { MI_INFO *info=sort_info->info; @@ -4408,7 +4417,7 @@ int write_data_suffix(SORT_INFO *sort_info, my_bool fix_datafile) /* Update state and myisamchk_time of indexfile */ -int update_state_info(MI_CHECK *param, MI_INFO *info,uint update) +int update_state_info(HA_CHECK *param, MI_INFO *info,uint update) { MYISAM_SHARE *share=info->s; @@ -4480,7 +4489,7 @@ err: param->auto_increment is bigger than the biggest key. */ -void update_auto_increment_key(MI_CHECK *param, MI_INFO *info, +void update_auto_increment_key(HA_CHECK *param, MI_INFO *info, my_bool repair_only) { uchar *record= 0; @@ -4620,8 +4629,9 @@ void update_key_parts(MI_KEYDEF *keyinfo, ulong *rec_per_key_part, let's ensure it is not */ set_if_bigger(tmp,1); - if (tmp >= (ulonglong) ~(ulong) 0) - tmp=(ulonglong) ~(ulong) 0; + /* Keys are stored as 32 byte int's; Ensure we don't get an overflow */ + if (tmp >= (ulonglong) ~(uint32) 0) + tmp=(ulonglong) ~(uint32) 0; *rec_per_key_part=(ulong) tmp; rec_per_key_part++; @@ -4712,7 +4722,7 @@ my_bool mi_test_if_sort_rep(MI_INFO *info, ha_rows rows, static void -set_data_file_type(SORT_INFO *sort_info, MYISAM_SHARE *share) +set_data_file_type(MI_SORT_INFO *sort_info, MYISAM_SHARE *share) { if ((sort_info->new_data_file_type=share->data_file_type) == COMPRESSED_RECORD && sort_info->param->testflag & T_UNPACK) diff --git a/storage/myisam/mi_checksum.c b/storage/myisam/mi_checksum.c index 1aa56e571e3..8c408ef7ff5 100644 --- a/storage/myisam/mi_checksum.c +++ b/storage/myisam/mi_checksum.c @@ -19,27 +19,34 @@ ha_checksum mi_checksum(MI_INFO *info, const uchar *buf) { - uint i; ha_checksum crc=0; - MI_COLUMNDEF *rec=info->s->rec; + const uchar *record= buf; + MI_COLUMNDEF *column= info->s->rec; + MI_COLUMNDEF *column_end= column+ info->s->base.fields; + my_bool skip_null_bits= test(info->s->options & HA_OPTION_NULL_FIELDS); - for (i=info->s->base.fields ; i-- ; buf+=(rec++)->length) + for ( ; column != column_end ; buf+= column++->length) { const uchar *pos; ulong length; - switch (rec->type) { + + if ((record[column->null_pos] & column->null_bit) && + skip_null_bits) + continue; /* Null field */ + + switch (column->type) { case FIELD_BLOB: { - length=_mi_calc_blob_length(rec->length- - portable_sizeof_char_ptr, - buf); - memcpy((char*) &pos, buf+rec->length- portable_sizeof_char_ptr, + length=_mi_calc_blob_length(column->length- + portable_sizeof_char_ptr, + buf); + memcpy((char*) &pos, buf+column->length- portable_sizeof_char_ptr, sizeof(char*)); break; } case FIELD_VARCHAR: { - uint pack_length= HA_VARCHAR_PACKLENGTH(rec->length-1); + uint pack_length= HA_VARCHAR_PACKLENGTH(column->length-1); if (pack_length == 1) length= (ulong) *(uchar*) buf; else @@ -48,7 +55,7 @@ ha_checksum mi_checksum(MI_INFO *info, const uchar *buf) break; } default: - length=rec->length; + length=column->length; pos=buf; break; } diff --git a/storage/myisam/mi_close.c b/storage/myisam/mi_close.c index 2066d092e1f..3d860bbead6 100644 --- a/storage/myisam/mi_close.c +++ b/storage/myisam/mi_close.c @@ -74,6 +74,7 @@ int mi_close(register MI_INFO *info) not change the crashed state. We can NOT write the state in other cases as other threads may be using the file at this point + IF using --external-locking. */ if (share->mode != O_RDONLY && mi_is_crashed(info)) mi_state_info_write(share->kfile, &share->state, 1); diff --git a/storage/myisam/mi_create.c b/storage/myisam/mi_create.c index 448e1b3c6f8..1ad58bee3e3 100644 --- a/storage/myisam/mi_create.c +++ b/storage/myisam/mi_create.c @@ -41,11 +41,11 @@ int mi_create(const char *name,uint keys,MI_KEYDEF *keydefs, File dfile,file; int errpos,save_errno, create_mode= O_RDWR | O_TRUNC; myf create_flag; - uint fields,length,max_key_length,packed,pointer,real_length_diff, + uint fields,length,max_key_length,packed,pack_bytes,pointer,real_length_diff, key_length,info_length,key_segs,options,min_key_length_skip, base_pos,long_varchar_count,varchar_length, max_key_block_length,unique_key_parts,fulltext_keys,offset; - uint aligned_key_start, block_length; + uint aligned_key_start, block_length, res; ulong reclength, real_reclength,min_pack_length; char filename[FN_REFLEN],linkname[FN_REFLEN], *linkname_ptr; ulong pack_reclength; @@ -95,7 +95,7 @@ int mi_create(const char *name,uint keys,MI_KEYDEF *keydefs, ci->reloc_rows=ci->max_rows; /* Check if wrong parameter */ if (!(rec_per_key_part= - (ulong*) my_malloc((keys + uniques)*MI_MAX_KEY_SEG*sizeof(long), + (ulong*) my_malloc((keys + uniques)*HA_MAX_KEY_SEG*sizeof(long), MYF(MY_WME | MY_ZEROFILL)))) DBUG_RETURN(my_errno); @@ -108,6 +108,9 @@ int mi_create(const char *name,uint keys,MI_KEYDEF *keydefs, rec++,fields++) { reclength+=rec->length; + if (rec->null_bit) + options|= HA_OPTION_NULL_FIELDS; + if ((type=(enum en_fieldtype) rec->type) != FIELD_NORMAL && type != FIELD_CHECK) { @@ -142,6 +145,7 @@ int mi_create(const char *name,uint keys,MI_KEYDEF *keydefs, long_varchar_count++; pack_reclength+= 2; /* May be packed on 3 bytes */ } + options|= HA_OPTION_NULL_FIELDS; /* Use of mi_checksum() */ } else if (type != FIELD_SKIP_ZERO) { @@ -181,7 +185,7 @@ int mi_create(const char *name,uint keys,MI_KEYDEF *keydefs, if (flags & HA_CREATE_TMP_TABLE) { options|= HA_OPTION_TMP_TABLE; - create_mode|= O_EXCL | O_NOFOLLOW; + create_mode|= O_NOFOLLOW; } if (flags & HA_CREATE_CHECKSUM || (options & HA_OPTION_CHECKSUM)) { @@ -193,11 +197,11 @@ int mi_create(const char *name,uint keys,MI_KEYDEF *keydefs, if (flags & HA_CREATE_RELIES_ON_SQL_LAYER) options|= HA_OPTION_RELIES_ON_SQL_LAYER; - packed=(packed+7)/8; + pack_bytes= (packed+7)/8; if (pack_reclength != INT_MAX32) pack_reclength+= reclength+packed + test(test_all_bits(options, HA_OPTION_CHECKSUM | HA_OPTION_PACK_RECORD)); - min_pack_length+=packed; + min_pack_length+= pack_bytes; if (!ci->data_file_length && ci->max_rows) { @@ -274,7 +278,7 @@ int mi_create(const char *name,uint keys,MI_KEYDEF *keydefs, keyseg->type != HA_KEYTYPE_VARBINARY2) { my_errno=HA_WRONG_CREATE_OPTION; - goto err; + goto err_no_lock; } } keydef->keysegs+=sp_segs; @@ -283,7 +287,7 @@ int mi_create(const char *name,uint keys,MI_KEYDEF *keydefs, min_key_length_skip+=SPLEN*2*SPDIMS; #else my_errno= HA_ERR_UNSUPPORTED; - goto err; + goto err_no_lock; #endif /*HAVE_SPATIAL*/ } else if (keydef->flag & HA_FULLTEXT) @@ -299,7 +303,7 @@ int mi_create(const char *name,uint keys,MI_KEYDEF *keydefs, keyseg->type != HA_KEYTYPE_VARTEXT2) { my_errno=HA_WRONG_CREATE_OPTION; - goto err; + goto err_no_lock; } if (!(keyseg->flag & HA_BLOB_PART) && (keyseg->type == HA_KEYTYPE_VARTEXT1 || @@ -421,10 +425,10 @@ int mi_create(const char *name,uint keys,MI_KEYDEF *keydefs, } } /* if HA_FULLTEXT */ key_segs+=keydef->keysegs; - if (keydef->keysegs > MI_MAX_KEY_SEG) + if (keydef->keysegs > HA_MAX_KEY_SEG) { my_errno=HA_WRONG_CREATE_OPTION; - goto err; + goto err_no_lock; } /* key_segs may be 0 in the case when we only want to be able to @@ -436,7 +440,7 @@ int mi_create(const char *name,uint keys,MI_KEYDEF *keydefs, share.state.rec_per_key_part[key_segs-1]=1L; length+=key_length; /* Get block length for key, if defined by user */ - block_length= (keydef->block_length ? + block_length= (keydef->block_length ? my_round_up_to_next_power(keydef->block_length) : myisam_block_size); block_length= max(block_length, MI_MIN_KEY_BLOCK_LENGTH); @@ -446,10 +450,10 @@ int mi_create(const char *name,uint keys,MI_KEYDEF *keydefs, pointer,MI_MAX_KEYPTR_SIZE, block_length); if (keydef->block_length > MI_MAX_KEY_BLOCK_LENGTH || - length >= MI_MAX_KEY_BUFF) + length >= HA_MAX_KEY_BUFF) { my_errno=HA_WRONG_CREATE_OPTION; - goto err; + goto err_no_lock; } set_if_bigger(max_key_block_length,keydef->block_length); keydef->keylength= (uint16) key_length; @@ -492,11 +496,12 @@ int mi_create(const char *name,uint keys,MI_KEYDEF *keydefs, /* There are only 16 bits for the total header length. */ if (info_length > 65535) { - my_printf_error(0, "MyISAM table '%s' has too many columns and/or " + my_printf_error(HA_WRONG_CREATE_OPTION, + "MyISAM table '%s' has too many columns and/or " "indexes and/or unique constraints.", MYF(0), name + dirname_length(name)); my_errno= HA_WRONG_CREATE_OPTION; - goto err; + goto err_no_lock; } bmove(share.state.header.file_version,(uchar*) myisam_file_magic,4); @@ -551,9 +556,9 @@ int mi_create(const char *name,uint keys,MI_KEYDEF *keydefs, share.base.pack_reclength=reclength+ test(options & HA_OPTION_CHECKSUM); share.base.max_pack_length=pack_reclength; share.base.min_pack_length=min_pack_length; - share.base.pack_bits=packed; + share.base.pack_bits= pack_bytes; share.base.fields=fields; - share.base.pack_fields=packed; + share.base.pack_fields= packed; #ifdef USE_RAID share.base.raid_type=ci->raid_type; share.base.raid_chunks=ci->raid_chunks; @@ -828,13 +833,16 @@ int mi_create(const char *name,uint keys,MI_KEYDEF *keydefs, } errpos=0; pthread_mutex_unlock(&THR_LOCK_myisam); + res= 0; if (my_close(file,MYF(0))) - goto err; + res= my_errno; my_free((char*) rec_per_key_part,MYF(0)); - DBUG_RETURN(0); + DBUG_RETURN(res); err: pthread_mutex_unlock(&THR_LOCK_myisam); +err_no_lock: + save_errno=my_errno; switch (errpos) { case 3: diff --git a/storage/myisam/mi_dbug.c b/storage/myisam/mi_dbug.c index 07c314c43e6..659abdce131 100644 --- a/storage/myisam/mi_dbug.c +++ b/storage/myisam/mi_dbug.c @@ -45,6 +45,7 @@ void _mi_print_key(FILE *stream, register HA_KEYSEG *keyseg, fprintf(stream,"NULL"); continue; } + end++; } switch (keyseg->type) { @@ -91,7 +92,7 @@ void _mi_print_key(FILE *stream, register HA_KEYSEG *keyseg, key=end; break; case HA_KEYTYPE_ULONG_INT: - l_1=mi_sint4korr(key); + l_1=mi_uint4korr(key); VOID(fprintf(stream,"%lu",(ulong) l_1)); key=end; break; diff --git a/storage/myisam/mi_delete.c b/storage/myisam/mi_delete.c index aa09216ce89..bda4b31064e 100644 --- a/storage/myisam/mi_delete.c +++ b/storage/myisam/mi_delete.c @@ -159,7 +159,7 @@ static int _mi_ck_real_delete(register MI_INFO *info, MI_KEYDEF *keyinfo, DBUG_RETURN(my_errno=HA_ERR_CRASHED); } if (!(root_buff= (uchar*) my_alloca((uint) keyinfo->block_length+ - MI_MAX_KEY_BUFF*2))) + HA_MAX_KEY_BUFF*2))) { DBUG_PRINT("error",("Couldn't allocate memory")); DBUG_RETURN(my_errno=ENOMEM); @@ -171,8 +171,9 @@ static int _mi_ck_real_delete(register MI_INFO *info, MI_KEYDEF *keyinfo, goto err; } if ((error=d_search(info,keyinfo, - (keyinfo->flag & HA_FULLTEXT ? SEARCH_FIND | SEARCH_UPDATE - : SEARCH_SAME), + (keyinfo->flag & HA_FULLTEXT ? + SEARCH_FIND | SEARCH_UPDATE | SEARCH_INSERT : + SEARCH_SAME), key,key_length,old_root,root_buff)) >0) { if (error == 2) @@ -221,7 +222,7 @@ static int d_search(register MI_INFO *info, register MI_KEYDEF *keyinfo, my_bool last_key; uchar *leaf_buff,*keypos; my_off_t leaf_page,next_block; - uchar lastkey[MI_MAX_KEY_BUFF]; + uchar lastkey[HA_MAX_KEY_BUFF]; DBUG_ENTER("d_search"); DBUG_DUMP("page",(uchar*) anc_buff,mi_getint(anc_buff)); @@ -310,7 +311,7 @@ static int d_search(register MI_INFO *info, register MI_KEYDEF *keyinfo, { leaf_page=_mi_kpos(nod_flag,keypos); if (!(leaf_buff= (uchar*) my_alloca((uint) keyinfo->block_length+ - MI_MAX_KEY_BUFF*2))) + HA_MAX_KEY_BUFF*2))) { DBUG_PRINT("error",("Couldn't allocate memory")); my_errno=ENOMEM; @@ -369,9 +370,7 @@ static int d_search(register MI_INFO *info, register MI_KEYDEF *keyinfo, { /* This happens only with packed keys */ DBUG_PRINT("test",("Enlarging of key when deleting")); if (!_mi_get_last_key(info,keyinfo,anc_buff,lastkey,keypos,&length)) - { goto err; - } ret_value=_mi_insert(info,keyinfo,key,anc_buff,keypos,lastkey, (uchar*) 0,(uchar*) 0,(my_off_t) 0,(my_bool) 0); } @@ -409,7 +408,7 @@ static int del(register MI_INFO *info, register MI_KEYDEF *keyinfo, uchar *key, int ret_value,length; uint a_length,nod_flag,tmp; my_off_t next_page; - uchar keybuff[MI_MAX_KEY_BUFF],*endpos,*next_buff,*key_start, *prev_key; + uchar keybuff[HA_MAX_KEY_BUFF],*endpos,*next_buff,*key_start, *prev_key; MYISAM_SHARE *share=info->s; MI_KEY_PARAM s_temp; DBUG_ENTER("del"); @@ -426,7 +425,7 @@ static int del(register MI_INFO *info, register MI_KEYDEF *keyinfo, uchar *key, { next_page= _mi_kpos(nod_flag,endpos); if (!(next_buff= (uchar*) my_alloca((uint) keyinfo->block_length+ - MI_MAX_KEY_BUFF*2))) + HA_MAX_KEY_BUFF*2))) DBUG_RETURN(-1); if (!_mi_fetch_keypage(info,keyinfo,next_page,DFLT_INIT_HITS,next_buff,0)) ret_value= -1; @@ -513,7 +512,7 @@ static int underflow(register MI_INFO *info, register MI_KEYDEF *keyinfo, uint length,anc_length,buff_length,leaf_length,p_length,s_length,nod_flag, key_reflength,key_length; my_off_t next_page; - uchar anc_key[MI_MAX_KEY_BUFF],leaf_key[MI_MAX_KEY_BUFF], + uchar anc_key[HA_MAX_KEY_BUFF],leaf_key[HA_MAX_KEY_BUFF], *buff,*endpos,*next_keypos,*anc_pos,*half_pos,*temp_pos,*prev_key, *after_key; MI_KEY_PARAM s_temp; diff --git a/storage/myisam/mi_dynrec.c b/storage/myisam/mi_dynrec.c index d1cbd6955dd..d2ff91a45f3 100644 --- a/storage/myisam/mi_dynrec.c +++ b/storage/myisam/mi_dynrec.c @@ -252,7 +252,7 @@ int _mi_write_blob_record(MI_INFO *info, const uchar *record) extra= (ALIGN_SIZE(MI_MAX_DYN_BLOCK_HEADER)+MI_SPLIT_LENGTH+ MI_DYN_DELETE_BLOCK_HEADER+1); reclength= (info->s->base.pack_reclength + - _my_calc_total_blob_length(info,record)+ extra); + _mi_calc_total_blob_length(info,record)+ extra); #ifdef NOT_USED /* We now support big rows */ if (reclength > MI_DYN_MAX_ROW_LENGTH) { @@ -286,7 +286,7 @@ int _mi_update_blob_record(MI_INFO *info, my_off_t pos, const uchar *record) extra= (ALIGN_SIZE(MI_MAX_DYN_BLOCK_HEADER)+MI_SPLIT_LENGTH+ MI_DYN_DELETE_BLOCK_HEADER); reclength= (info->s->base.pack_reclength+ - _my_calc_total_blob_length(info,record)+ extra); + _mi_calc_total_blob_length(info,record)+ extra); #ifdef NOT_USED /* We now support big rows */ if (reclength > MI_DYN_MAX_ROW_LENGTH) { @@ -1329,7 +1329,7 @@ err: /* Calc length of blob. Update info in blobs->length */ -ulong _my_calc_total_blob_length(MI_INFO *info, const uchar *record) +ulong _mi_calc_total_blob_length(MI_INFO *info, const uchar *record) { ulong length; MI_BLOB *blob,*end; @@ -1363,7 +1363,7 @@ ulong _mi_calc_blob_length(uint length, const uchar *pos) } -void _my_store_blob_length(uchar *pos,uint pack_length,uint length) +void _mi_store_blob_length(uchar *pos,uint pack_length,uint length) { switch (pack_length) { case 1: @@ -1576,7 +1576,7 @@ int _mi_cmp_dynamic_record(register MI_INFO *info, register const uchar *record) if (info->s->base.blobs) { if (!(buffer=(uchar*) my_alloca(info->s->base.pack_reclength+ - _my_calc_total_blob_length(info,record)))) + _mi_calc_total_blob_length(info,record)))) DBUG_RETURN(-1); } reclength=_mi_rec_pack(info,buffer,record); @@ -1834,7 +1834,7 @@ int _mi_read_rnd_dynamic_record(MI_INFO *info, uchar *buf, /* VOID(my_seek(info->dfile,filepos,MY_SEEK_SET,MYF(0))); */ if (my_read(info->dfile,(uchar*) to,block_info.data_len,MYF(MY_NABP))) { - if (my_errno == -1) + if (my_errno == HA_ERR_FILE_TOO_SHORT) my_errno= HA_ERR_WRONG_IN_RECORD; /* Unexpected end of file */ goto err; } diff --git a/storage/myisam/mi_extra.c b/storage/myisam/mi_extra.c index f0ddc15b325..d798ef50d7e 100644 --- a/storage/myisam/mi_extra.c +++ b/storage/myisam/mi_extra.c @@ -216,7 +216,7 @@ int mi_extra(MI_INFO *info, enum ha_extra_function function, void *extra_arg) info->lock_wait=0; break; case HA_EXTRA_NO_WAIT_LOCK: - info->lock_wait=MY_DONT_WAIT; + info->lock_wait= MY_SHORT_WAIT; break; case HA_EXTRA_NO_KEYS: if (info->lock_type == F_UNLCK) @@ -256,6 +256,7 @@ int mi_extra(MI_INFO *info, enum ha_extra_function function, void *extra_arg) share->last_version= 0L; /* Impossible version */ pthread_mutex_unlock(&THR_LOCK_myisam); break; + case HA_EXTRA_PREPARE_FOR_RENAME: case HA_EXTRA_PREPARE_FOR_DROP: pthread_mutex_lock(&THR_LOCK_myisam); share->last_version= 0L; /* Impossible version */ @@ -263,8 +264,8 @@ int mi_extra(MI_INFO *info, enum ha_extra_function function, void *extra_arg) /* Close the isam and data files as Win32 can't drop an open table */ pthread_mutex_lock(&share->intern_lock); if (flush_key_blocks(share->key_cache, share->kfile, - (function == HA_EXTRA_FORCE_REOPEN ? - FLUSH_RELEASE : FLUSH_IGNORE_CHANGED))) + (function == HA_EXTRA_PREPARE_FOR_DROP ? + FLUSH_IGNORE_CHANGED : FLUSH_RELEASE))) { error=my_errno; share->changed=1; diff --git a/storage/myisam/mi_key.c b/storage/myisam/mi_key.c index 3f445ebf44d..94f3f34ec58 100644 --- a/storage/myisam/mi_key.c +++ b/storage/myisam/mi_key.c @@ -426,7 +426,7 @@ static int _mi_put_key_in_record(register MI_INFO *info, uint keynr, /* The above changed info->lastkey2. Inform mi_rnext_same(). */ info->update&= ~HA_STATE_RNEXT_SAME; - _my_store_blob_length(record+keyseg->start, + _mi_store_blob_length(record+keyseg->start, (uint) keyseg->bit_start,length); key+=length; } diff --git a/storage/myisam/mi_locking.c b/storage/myisam/mi_locking.c index 6a4c21160f4..f3d9934ed8c 100644 --- a/storage/myisam/mi_locking.c +++ b/storage/myisam/mi_locking.c @@ -56,9 +56,15 @@ int mi_lock_database(MI_INFO *info, int lock_type) case F_UNLCK: ftparser_call_deinitializer(info); if (info->lock_type == F_RDLCK) + { count= --share->r_locks; + mi_restore_status(info); + } else + { count= --share->w_locks; + mi_update_status(info); + } --share->tot_locks; if (info->lock_type == F_WRLCK && !share->w_locks && !share->delay_key_write && flush_key_blocks(share->key_cache, @@ -84,16 +90,16 @@ int mi_lock_database(MI_INFO *info, int lock_type) if (share->changed && !share->w_locks) { #ifdef HAVE_MMAP - if ((info->s->mmaped_length != info->s->state.state.data_file_length) && - (info->s->nonmmaped_inserts > MAX_NONMAPPED_INSERTS)) - { - if (info->s->concurrent_insert) - rw_wrlock(&info->s->mmap_lock); - mi_remap_file(info, info->s->state.state.data_file_length); - info->s->nonmmaped_inserts= 0; - if (info->s->concurrent_insert) - rw_unlock(&info->s->mmap_lock); - } + if ((info->s->mmaped_length != info->s->state.state.data_file_length) && + (info->s->nonmmaped_inserts > MAX_NONMAPPED_INSERTS)) + { + if (info->s->concurrent_insert) + rw_wrlock(&info->s->mmap_lock); + mi_remap_file(info, info->s->state.state.data_file_length); + info->s->nonmmaped_inserts= 0; + if (info->s->concurrent_insert) + rw_unlock(&info->s->mmap_lock); + } #endif share->state.process= share->last_process=share->this_process; share->state.unique= info->last_unique= info->this_unique; @@ -275,7 +281,7 @@ int mi_lock_database(MI_INFO *info, int lock_type) (THR_WRITE_CONCURRENT_INSERT was used) */ -void mi_get_status(void* param, int concurrent_insert) +void mi_get_status(void* param, my_bool concurrent_insert) { MI_INFO *info=(MI_INFO*) param; DBUG_ENTER("mi_get_status"); @@ -302,6 +308,7 @@ void mi_get_status(void* param, int concurrent_insert) void mi_update_status(void* param) { MI_INFO *info=(MI_INFO*) param; + DBUG_ENTER("mi_update_status"); /* Because someone may have closed the table we point at, we only update the state if its our own state. This isn't a problem as @@ -338,20 +345,32 @@ void mi_update_status(void* param) } info->opt_flag&= ~WRITE_CACHE_USED; } + DBUG_VOID_RETURN; } void mi_restore_status(void *param) { MI_INFO *info= (MI_INFO*) param; + DBUG_ENTER("mi_restore_status"); + DBUG_PRINT("info",("key_file: %ld data_file: %ld", + (long) info->s->state.state.key_file_length, + (long) info->s->state.state.data_file_length)); info->state= &info->s->state.state; info->append_insert_at_end= 0; + DBUG_VOID_RETURN; } void mi_copy_status(void* to,void *from) { - ((MI_INFO*) to)->state= &((MI_INFO*) from)->save_state; + MI_INFO *info= (MI_INFO*) to; + DBUG_ENTER("mi_copy_status"); + info->state= &((MI_INFO*) from)->save_state; + DBUG_PRINT("info",("key_file: %ld data_file: %ld", + (long) info->state->key_file_length, + (long) info->state->data_file_length)); + DBUG_VOID_RETURN; } @@ -379,17 +398,18 @@ void mi_copy_status(void* to,void *from) my_bool mi_check_status(void *param) { MI_INFO *info=(MI_INFO*) param; + DBUG_ENTER("mi_check_status"); + DBUG_PRINT("info",("dellink: %ld r_locks: %u w_locks: %u", + (long) info->s->state.dellink, (uint) info->s->r_locks, + (uint) info->s->w_locks)); /* The test for w_locks == 1 is here because this thread has already done an external lock (in other words: w_locks == 1 means no other threads has a write lock) */ - DBUG_PRINT("info",("dellink: %ld r_locks: %u w_locks: %u", - (long) info->s->state.dellink, (uint) info->s->r_locks, - (uint) info->s->w_locks)); - return (my_bool) !(info->s->state.dellink == HA_OFFSET_ERROR || + DBUG_RETURN((my_bool) !(info->s->state.dellink == HA_OFFSET_ERROR || (myisam_concurrent_insert == 2 && info->s->r_locks && - info->s->w_locks == 1)); + info->s->w_locks == 1))); } @@ -411,10 +431,10 @@ int _mi_readinfo(register MI_INFO *info, int lock_type, int check_keybuffer) DBUG_RETURN(1); if (mi_state_info_read_dsk(share->kfile, &share->state, 1)) { - int error=my_errno ? my_errno : -1; + int error= my_errno ? my_errno : HA_ERR_FILE_TOO_SHORT; VOID(my_lock(share->kfile,F_UNLCK,0L,F_TO_EOF, MYF(MY_SEEK_NOT_DONE))); - my_errno=error; + my_errno= error; DBUG_RETURN(1); } } diff --git a/storage/myisam/mi_log.c b/storage/myisam/mi_log.c index 8b9ca038fec..982ba8b4367 100644 --- a/storage/myisam/mi_log.c +++ b/storage/myisam/mi_log.c @@ -133,7 +133,7 @@ void _myisam_log_record(enum myisam_log_commands command, MI_INFO *info, if (!info->s->base.blobs) length=info->s->base.reclength; else - length=info->s->base.reclength+ _my_calc_total_blob_length(info,record); + length=info->s->base.reclength+ _mi_calc_total_blob_length(info,record); buff[0]=(uchar) command; mi_int2store(buff+1,info->dfile); mi_int4store(buff+3,pid); diff --git a/storage/myisam/mi_open.c b/storage/myisam/mi_open.c index 328cde20923..34bc67ee95b 100644 --- a/storage/myisam/mi_open.c +++ b/storage/myisam/mi_open.c @@ -19,6 +19,7 @@ #include "sp_defs.h" #include "rt_index.h" #include <m_ctype.h> +#include <mysql_version.h> #if defined(MSDOS) || defined(__WIN__) #ifdef __WIN__ @@ -82,7 +83,7 @@ MI_INFO *mi_open(const char *name, int mode, uint open_flags) uchar *disk_cache, *disk_pos, *end_pos; MI_INFO info,*m_info,*old_info; MYISAM_SHARE share_buff,*share; - ulong rec_per_key_part[HA_MAX_POSSIBLE_KEY*MI_MAX_KEY_SEG]; + ulong rec_per_key_part[HA_MAX_POSSIBLE_KEY*HA_MAX_KEY_SEG]; my_off_t key_root[HA_MAX_POSSIBLE_KEY],key_del[MI_MAX_KEY_BLOCK_SIZE]; ulonglong max_key_file_length, max_data_file_length; DBUG_ENTER("mi_open"); @@ -95,7 +96,7 @@ MI_INFO *mi_open(const char *name, int mode, uint open_flags) bzero((uchar*) &info,sizeof(info)); realpath_err= my_realpath(name_buff, - fn_format(org_name,name,"",MI_NAME_IEXT,4),MYF(0)); + fn_format(org_name,name,"",MI_NAME_IEXT,4),MYF(0)); if (my_is_symlink(org_name) && (realpath_err || (*myisam_test_invalid_symlink)(name_buff))) { @@ -112,7 +113,8 @@ MI_INFO *mi_open(const char *name, int mode, uint open_flags) share_buff.state.key_root=key_root; share_buff.state.key_del=key_del; share_buff.key_cache= multi_key_cache_search((uchar*) name_buff, - strlen(name_buff)); + strlen(name_buff), + dflt_key_cache); DBUG_EXECUTE_IF("myisam_pretend_crashed_table_on_open", if (strstr(name, "/t1")) @@ -139,8 +141,8 @@ MI_INFO *mi_open(const char *name, int mode, uint open_flags) (uchar*) myisam_file_magic, 4)) { DBUG_PRINT("error",("Wrong header in %s",name_buff)); - DBUG_DUMP("error_dump",(char*) share->state.header.file_version, - head_length); + DBUG_DUMP("error_dump", share->state.header.file_version, + (size_t) head_length); my_errno=HA_ERR_NOT_A_TABLE; goto err; } @@ -150,7 +152,7 @@ MI_INFO *mi_open(const char *name, int mode, uint open_flags) HA_OPTION_COMPRESS_RECORD | HA_OPTION_READ_ONLY_DATA | HA_OPTION_TEMP_COMPRESS_RECORD | HA_OPTION_CHECKSUM | HA_OPTION_TMP_TABLE | HA_OPTION_DELAY_KEY_WRITE | - HA_OPTION_RELIES_ON_SQL_LAYER)) + HA_OPTION_RELIES_ON_SQL_LAYER | HA_OPTION_NULL_FIELDS)) { DBUG_PRINT("error",("wrong options: 0x%lx", share->options)); my_errno=HA_ERR_OLD_FILE; @@ -186,7 +188,7 @@ MI_INFO *mi_open(const char *name, int mode, uint open_flags) { if ((lock_error=my_lock(kfile,F_RDLCK,0L,F_TO_EOF, MYF(open_flags & HA_OPEN_WAIT_IF_LOCKED ? - 0 : MY_DONT_WAIT))) && + 0 : MY_SHORT_WAIT))) && !(open_flags & HA_OPEN_IGNORE_IF_LOCKED)) goto err; } @@ -217,7 +219,7 @@ MI_INFO *mi_open(const char *name, int mode, uint open_flags) DBUG_PRINT("warning",("saved_base_info_length: %d base_info_length: %d", len,MI_BASE_INFO_SIZE)); } - disk_pos= my_n_base_info_read(disk_cache + base_pos, &share->base); + disk_pos= mi_n_base_info_read(disk_cache + base_pos, &share->base); share->state.state_length=base_pos; if (!(open_flags & HA_OPEN_FOR_REPAIR) && @@ -242,8 +244,8 @@ MI_INFO *mi_open(const char *name, int mode, uint open_flags) } key_parts+=fulltext_keys*FT_SEGS; - if (share->base.max_key_length > MI_MAX_KEY_BUFF || keys > MI_MAX_KEY || - key_parts > MI_MAX_KEY * MI_MAX_KEY_SEG) + if (share->base.max_key_length > HA_MAX_KEY_BUFF || keys > MI_MAX_KEY || + key_parts > MI_MAX_KEY * HA_MAX_KEY_SEG) { DBUG_PRINT("error",("Wrong key info: Max_key_length: %d keys: %d key_parts: %d", share->base.max_key_length, keys, key_parts)); my_errno=HA_ERR_UNSUPPORTED; @@ -462,13 +464,20 @@ MI_INFO *mi_open(const char *name, int mode, uint open_flags) share->rec[i].pack_type=0; share->rec[i].huff_tree=0; share->rec[i].offset=offset; - if (share->rec[i].type == (int) FIELD_BLOB) + if (share->rec[i].type == FIELD_BLOB) { share->blobs[j].pack_length= - share->rec[i].length-portable_sizeof_char_ptr; + share->rec[i].length - portable_sizeof_char_ptr; share->blobs[j].offset=offset; j++; } +#if MYSQL_VERSION_ID <= 60100 + /* This is to detect old checksum option */ + if (share->rec[i].null_bit) + share->has_null_fields= 1; + if (share->rec[i].type == FIELD_VARCHAR) + share->has_varchar_fields= 1; +#endif offset+=share->rec[i].length; } share->rec[i].type=(int) FIELD_LAST; /* End marker */ @@ -752,12 +761,14 @@ void mi_setup_functions(register MYISAM_SHARE *share) { share->read_record=_mi_read_pack_record; share->read_rnd=_mi_read_rnd_pack_record; - if (!(share->options & HA_OPTION_TEMP_COMPRESS_RECORD)) - share->calc_checksum=0; /* No checksum */ - else if (share->options & HA_OPTION_PACK_RECORD) + if ((share->options & + (HA_OPTION_PACK_RECORD | HA_OPTION_NULL_FIELDS))) share->calc_checksum= mi_checksum; else share->calc_checksum= mi_static_checksum; + share->calc_check_checksum= share->calc_checksum; + if (!(share->options & HA_OPTION_TEMP_COMPRESS_RECORD)) + share->calc_checksum=0; /* No checksum */ } else if (share->options & HA_OPTION_PACK_RECORD) { @@ -767,6 +778,7 @@ void mi_setup_functions(register MYISAM_SHARE *share) share->compare_record=_mi_cmp_dynamic_record; share->compare_unique=_mi_cmp_dynamic_unique; share->calc_checksum= mi_checksum; + share->calc_check_checksum= share->calc_checksum; /* add bits used to pack data to pack_reclength for faster allocation */ share->base.pack_reclength+= share->base.pack_bits; @@ -790,7 +802,11 @@ void mi_setup_functions(register MYISAM_SHARE *share) share->update_record=_mi_update_static_record; share->write_record=_mi_write_static_record; share->compare_unique=_mi_cmp_static_unique; - share->calc_checksum= mi_static_checksum; + if (share->options & HA_OPTION_NULL_FIELDS) + share->calc_checksum= mi_checksum; + else + share->calc_checksum= mi_static_checksum; + share->calc_check_checksum= share->calc_checksum; } share->file_read= mi_nommap_pread; share->file_write= mi_nommap_pwrite; @@ -1042,7 +1058,7 @@ uint mi_base_info_write(File file, MI_BASE_INFO *base) } -uchar *my_n_base_info_read(uchar *ptr, MI_BASE_INFO *base) +uchar *mi_n_base_info_read(uchar *ptr, MI_BASE_INFO *base) { base->keystart = mi_sizekorr(ptr); ptr +=8; base->max_data_file_length = mi_sizekorr(ptr); ptr +=8; diff --git a/storage/myisam/mi_packrec.c b/storage/myisam/mi_packrec.c index d9abcbce050..895ce2b8c85 100644 --- a/storage/myisam/mi_packrec.c +++ b/storage/myisam/mi_packrec.c @@ -105,6 +105,7 @@ static void init_bit_buffer(MI_BIT_BUFF *bit_buff,uchar *buffer,uint length); static uint fill_and_get_bits(MI_BIT_BUFF *bit_buff,uint count); static void fill_buffer(MI_BIT_BUFF *bit_buff); static uint max_bit(uint value); +static uint read_pack_length(uint version, const uchar *buf, ulong *length); #ifdef HAVE_MMAP static uchar *_mi_mempack_get_block_info(MI_INFO *myisam, MI_BIT_BUFF *bit_buff, MI_BLOCK_INFO *info, uchar **rec_buff_p, @@ -1050,7 +1051,7 @@ static void uf_blob(MI_COLUMNDEF *rec, MI_BIT_BUFF *bit_buff, return; } decode_bytes(rec,bit_buff,bit_buff->blob_pos,bit_buff->blob_pos+length); - _my_store_blob_length((uchar*) to,pack_length,length); + _mi_store_blob_length((uchar*) to,pack_length,length); memcpy_fixed((char*) to+pack_length,(char*) &bit_buff->blob_pos, sizeof(char*)); bit_buff->blob_pos+=length; @@ -1634,7 +1635,7 @@ uint save_pack_length(uint version, uchar *block_buff, ulong length) } -uint read_pack_length(uint version, const uchar *buf, ulong *length) +static uint read_pack_length(uint version, const uchar *buf, ulong *length) { if (buf[0] < 254) { diff --git a/storage/myisam/mi_page.c b/storage/myisam/mi_page.c index 23a2526f756..a05a96e514b 100644 --- a/storage/myisam/mi_page.c +++ b/storage/myisam/mi_page.c @@ -49,7 +49,7 @@ uchar *_mi_fetch_keypage(register MI_INFO *info, MI_KEYDEF *keyinfo, { DBUG_PRINT("error",("page %lu had wrong page length: %u", (ulong) page, page_size)); - DBUG_DUMP("page", (char*) tmp, keyinfo->block_length); + DBUG_DUMP("page",tmp, keyinfo->block_length); info->last_keypage = HA_OFFSET_ERROR; mi_print_error(info->s, HA_ERR_CRASHED); my_errno = HA_ERR_CRASHED; @@ -86,7 +86,7 @@ int _mi_write_keypage(register MI_INFO *info, register MI_KEYDEF *keyinfo, if ((length=keyinfo->block_length) > IO_SIZE*2 && info->state->key_file_length != page+length) length= ((mi_getint(buff)+IO_SIZE-1) & (uint) ~(IO_SIZE-1)); -#ifdef HAVE_purify +#ifdef HAVE_valgrind { length=mi_getint(buff); bzero((uchar*) buff+length,keyinfo->block_length-length); diff --git a/storage/myisam/mi_range.c b/storage/myisam/mi_range.c index dc6dc9d62b7..aabbe1277c4 100644 --- a/storage/myisam/mi_range.c +++ b/storage/myisam/mi_range.c @@ -152,7 +152,7 @@ static ha_rows _mi_record_pos(MI_INFO *info, const uchar *key, operations with a comment like "Not real duplicates", whatever this means. From the condition above we can see that 'skip_end_space' is always false for these operations. The result is that trailing space - counts in key comparison and hence, emtpy strings ('', string length + counts in key comparison and hence, empty strings ('', string length zero, but not NULL) compare less that strings starting with control characters and these in turn compare less than strings starting with blanks. @@ -166,7 +166,7 @@ static ha_rows _mi_record_pos(MI_INFO *info, const uchar *key, This is the reason that we add the SEARCH_UPDATE flag here. It makes the key estimation compare in the same way like key write operations - do. Olny so we will find the keys where they have been inserted. + do. Only so we will find the keys where they have been inserted. Adding the flag unconditionally does not hurt as it is used in the above mentioned condition only. So it can safely be used together @@ -260,7 +260,7 @@ static uint _mi_keynr(MI_INFO *info, register MI_KEYDEF *keyinfo, uchar *page, uchar *keypos, uint *ret_max_key) { uint nod_flag,keynr,max_key; - uchar t_buff[MI_MAX_KEY_BUFF],*end; + uchar t_buff[HA_MAX_KEY_BUFF],*end; end= page+mi_getint(page); nod_flag=mi_test_if_nod(page); diff --git a/storage/myisam/mi_rkey.c b/storage/myisam/mi_rkey.c index f1d35810d36..f20b0366683 100644 --- a/storage/myisam/mi_rkey.c +++ b/storage/myisam/mi_rkey.c @@ -85,6 +85,8 @@ int mi_rkey(MI_INFO *info, uchar *buf, int inx, const uchar *key, { mi_print_error(info->s, HA_ERR_CRASHED); my_errno=HA_ERR_CRASHED; + if (share->concurrent_insert) + rw_unlock(&share->key_root_lock[inx]); goto err; } break; diff --git a/storage/myisam/mi_search.c b/storage/myisam/mi_search.c index 1dd6c6b5f0d..a7836280a2b 100644 --- a/storage/myisam/mi_search.c +++ b/storage/myisam/mi_search.c @@ -60,7 +60,7 @@ int _mi_search(register MI_INFO *info, register MI_KEYDEF *keyinfo, int error,flag; uint nod_flag; uchar *keypos,*maxpos; - uchar lastkey[MI_MAX_KEY_BUFF],*buff; + uchar lastkey[HA_MAX_KEY_BUFF],*buff; DBUG_ENTER("_mi_search"); DBUG_PRINT("enter",("pos: %lu nextflag: %u lastpos: %lu", (ulong) pos, nextflag, (ulong) info->lastpos)); @@ -242,7 +242,7 @@ int _mi_seq_search(MI_INFO *info, register MI_KEYDEF *keyinfo, uchar *page, { int flag; uint nod_flag,length,not_used[2]; - uchar t_buff[MI_MAX_KEY_BUFF],*end; + uchar t_buff[HA_MAX_KEY_BUFF],*end; DBUG_ENTER("_mi_seq_search"); LINT_INIT(flag); LINT_INIT(length); @@ -296,7 +296,7 @@ int _mi_prefix_search(MI_INFO *info, register MI_KEYDEF *keyinfo, uchar *page, int key_len_skip, seg_len_pack, key_len_left; uchar *end, *kseg, *vseg; uchar *sort_order=keyinfo->seg->charset->sort_order; - uchar tt_buff[MI_MAX_KEY_BUFF+2], *t_buff=tt_buff+2; + uchar tt_buff[HA_MAX_KEY_BUFF+2], *t_buff=tt_buff+2; uchar *saved_from, *saved_to, *saved_vseg; uint saved_length=0, saved_prefix_len=0; uint length_pack; @@ -816,7 +816,7 @@ uint _mi_get_pack_key(register MI_KEYDEF *keyinfo, uint nod_flag, DBUG_PRINT("error", ("Found too long null packed key: %u of %u at 0x%lx", length, keyseg->length, (long) *page_pos)); - DBUG_DUMP("key",(char*) *page_pos,16); + DBUG_DUMP("key",*page_pos,16); mi_print_error(keyinfo->share, HA_ERR_CRASHED); my_errno=HA_ERR_CRASHED; return 0; @@ -873,7 +873,7 @@ uint _mi_get_pack_key(register MI_KEYDEF *keyinfo, uint nod_flag, { DBUG_PRINT("error",("Found too long packed key: %u of %u at 0x%lx", length, keyseg->length, (long) *page_pos)); - DBUG_DUMP("key",(char*) *page_pos,16); + DBUG_DUMP("key",*page_pos,16); mi_print_error(keyinfo->share, HA_ERR_CRASHED); my_errno=HA_ERR_CRASHED; return 0; /* Error */ @@ -920,7 +920,7 @@ uint _mi_get_binary_pack_key(register MI_KEYDEF *keyinfo, uint nod_flag, DBUG_ENTER("_mi_get_binary_pack_key"); page= *page_pos; - page_end=page+MI_MAX_KEY_BUFF+1; + page_end=page+HA_MAX_KEY_BUFF+1; start_key=key; /* @@ -945,7 +945,7 @@ uint _mi_get_binary_pack_key(register MI_KEYDEF *keyinfo, uint nod_flag, DBUG_PRINT("error", ("Found too long binary packed key: %u of %u at 0x%lx", length, keyinfo->maxlength, (long) *page_pos)); - DBUG_DUMP("key",(char*) *page_pos,16); + DBUG_DUMP("key",*page_pos,16); mi_print_error(keyinfo->share, HA_ERR_CRASHED); my_errno=HA_ERR_CRASHED; DBUG_RETURN(0); /* Wrong key */ @@ -1238,7 +1238,7 @@ int _mi_search_next(register MI_INFO *info, register MI_KEYDEF *keyinfo, { int error; uint nod_flag; - uchar lastkey[MI_MAX_KEY_BUFF]; + uchar lastkey[HA_MAX_KEY_BUFF]; DBUG_ENTER("_mi_search_next"); DBUG_PRINT("enter",("nextflag: %u lastpos: %lu int_keypos: %lu", nextflag, (ulong) info->lastpos, @@ -1749,7 +1749,7 @@ _mi_calc_bin_pack_key_length(MI_KEYDEF *keyinfo,uint nod_flag,uchar *next_key, uint length,key_length,ref_length; s_temp->totlength=key_length=_mi_keylength(keyinfo,key)+nod_flag; -#ifdef HAVE_purify +#ifdef HAVE_valgrind s_temp->n_length= s_temp->n_ref_length=0; /* For valgrind */ #endif s_temp->key=key; @@ -1802,13 +1802,13 @@ _mi_calc_bin_pack_key_length(MI_KEYDEF *keyinfo,uint nod_flag,uchar *next_key, } /* Check how many characters are identical to next key */ key= s_temp->key+next_length; + s_temp->prev_length= 0; while (*key++ == *next_key++) ; if ((ref_length= (uint) (key - s_temp->key)-1) == next_length) { s_temp->next_key_pos=0; return length; /* can't pack next key */ } - s_temp->prev_length=0; s_temp->n_ref_length=ref_length; return (int) (length-(ref_length - next_length) - next_length_pack + get_pack_length(ref_length)); diff --git a/storage/myisam/mi_test1.c b/storage/myisam/mi_test1.c index f218bf4e77f..8e491823939 100644 --- a/storage/myisam/mi_test1.c +++ b/storage/myisam/mi_test1.c @@ -79,6 +79,8 @@ static int run_test(const char *filename) recinfo[2].length= (extra_field == FIELD_BLOB ? 4 + portable_sizeof_char_ptr : 24); if (extra_field == FIELD_VARCHAR) recinfo[2].length+= HA_VARCHAR_PACKLENGTH(recinfo[2].length); + recinfo[1].null_bit= null_fields ? 2 : 0; + if (opt_unique) { recinfo[3].type=FIELD_CHECK; @@ -630,7 +632,7 @@ get_one_option(int optid, const struct my_option *opt __attribute__((unused)), key_type= HA_KEYTYPE_VARTEXT1; break; case 'k': - if (key_length < 4 || key_length > MI_MAX_KEY_LENGTH) + if (key_length < 4 || key_length > HA_MAX_KEY_LENGTH) { fprintf(stderr,"Wrong key length\n"); exit(1); diff --git a/storage/myisam/mi_test2.c b/storage/myisam/mi_test2.c index 23c58638166..9844126e7dd 100644 --- a/storage/myisam/mi_test2.c +++ b/storage/myisam/mi_test2.c @@ -18,12 +18,6 @@ #ifndef USE_MY_FUNC /* We want to be able to dbug this !! */ #define USE_MY_FUNC #endif -#ifdef DBUG_OFF -#undef DBUG_OFF -#endif -#ifndef SAFEMALLOC -#define SAFEMALLOC -#endif #include "myisamdef.h" #include <m_ctype.h> #include <my_bit.h> @@ -43,7 +37,7 @@ static void copy_key(struct st_myisam_info *info,uint inx, uchar *record,uchar *key); static int verbose=0,testflag=0, - first_key=0,async_io=0,key_cacheing=0,write_cacheing=0,locking=0, + first_key=0,async_io=0,key_cacheing=0,write_cacheing=0,do_locking=0, rec_pointer_size=0,pack_fields=1,use_log=0,silent=0, opt_quick_mode=0; static int pack_seg=HA_SPACE_PACK,pack_type=HA_PACK_KEY,remove_count=-1, @@ -222,7 +216,7 @@ int main(int argc, char *argv[]) printf("- Writing key:s\n"); if (key_cacheing) init_key_cache(dflt_key_cache,key_cache_block_size,key_cache_size,0,0); - if (locking) + if (do_locking) mi_lock_database(file,F_WRLCK); if (write_cacheing) mi_extra(file,HA_EXTRA_WRITE_CACHE,0); @@ -334,9 +328,9 @@ int main(int argc, char *argv[]) if (use_blob) { if (i & 1) - put_blob_in_record(record+blob_pos,&blob_buffer); + put_blob_in_record(record2+blob_pos,&blob_buffer); else - bmove(record+blob_pos,read_record+blob_pos,8); + bmove(record2+blob_pos,read_record+blob_pos,8); } if (mi_update(file,read_record,record2)) { @@ -606,7 +600,7 @@ int main(int argc, char *argv[]) if (mi_rsame(file,read_record2,(int) i)) goto err; if (bcmp(read_record,read_record2,reclength) != 0) { - printf("is_rsame didn't find same record\n"); + printf("mi_rsame didn't find same record\n"); goto end; } } @@ -657,10 +651,10 @@ int main(int argc, char *argv[]) sprintf((char*) key2,"%6d",k); min_key.key= key; - min_key.length= USE_WHOLE_KEY; + min_key.keypart_map= HA_WHOLE_KEY; min_key.flag= HA_READ_AFTER_KEY; max_key.key= key2; - max_key.length= USE_WHOLE_KEY; + max_key.keypart_map= HA_WHOLE_KEY; max_key.flag= HA_READ_BEFORE_KEY; range_records= mi_records_in_range(file, 0, &min_key, &max_key); records=0; @@ -713,7 +707,7 @@ int main(int argc, char *argv[]) printf("- mi_extra(CACHE) + mi_rrnd.... + mi_extra(NO_CACHE)\n"); if (mi_reset(file) || mi_extra(file,HA_EXTRA_CACHE,0)) { - if (locking || (!use_blob && !pack_fields)) + if (do_locking || (!use_blob && !pack_fields)) { puts("got error from mi_extra(HA_EXTRA_CACHE)"); goto end; @@ -780,9 +774,8 @@ int main(int argc, char *argv[]) { ulong blob_length,pos; uchar *ptr; - longget(blob_length,read_record+blob_pos+4); - ptr=(uchar*) blob_length; - longget(blob_length,read_record+blob_pos); + memcpy_fixed(&ptr, read_record+blob_pos+4, sizeof(ptr)); + blob_length= uint4korr(read_record+blob_pos); for (pos=0 ; pos < blob_length ; pos++) { if (ptr[pos] != (uchar) (blob_length+pos)) @@ -834,9 +827,9 @@ end: puts("Write cacheing used"); if (write_cacheing) puts("quick mode"); - if (async_io && locking) + if (async_io && do_locking) puts("Asyncron io with locking used"); - else if (locking) + else if (do_locking) puts("Locking used"); if (use_blob) puts("blobs used"); @@ -905,7 +898,7 @@ static void get_options(int argc, char **argv) use_log=1; break; case 'L': - locking=1; + do_locking=1; break; case 'A': /* use asyncron io */ async_io=1; diff --git a/storage/myisam/mi_test3.c b/storage/myisam/mi_test3.c index 5bdc33b8518..93f2b10fd3e 100644 --- a/storage/myisam/mi_test3.c +++ b/storage/myisam/mi_test3.c @@ -15,7 +15,7 @@ /* Test av locking */ -#ifndef __NETWARE__ +#if !(defined (__NETWARE_) || defined (_WIN32)) /*no fork() in Windows*/ #include "myisam.h" #include <sys/types.h> @@ -488,14 +488,14 @@ int test_update(MI_INFO *file,int id,int lock_type) return 0; } -#else /* __NETWARE__ */ +#else /* __NETWARE__ || __WIN__*/ #include <stdio.h> -main() +int main() { - fprintf(stderr,"this test has not been ported to NetWare\n"); + fprintf(stderr,"this test has not been ported to NetWare or Windows\n"); return 0; } -#endif /* __NETWARE__ */ +#endif /* __NETWARE__ || __WIN__ */ diff --git a/storage/myisam/mi_update.c b/storage/myisam/mi_update.c index 2ac405dd785..b252892a96d 100644 --- a/storage/myisam/mi_update.c +++ b/storage/myisam/mi_update.c @@ -23,7 +23,7 @@ int mi_update(register MI_INFO *info, const uchar *oldrec, uchar *newrec) int flag,key_changed,save_errno; reg3 my_off_t pos; uint i; - uchar old_key[MI_MAX_KEY_BUFF],*new_key; + uchar old_key[HA_MAX_KEY_BUFF],*new_key; my_bool auto_key_changed=0; ulonglong changed; MYISAM_SHARE *share=info->s; diff --git a/storage/myisam/mi_write.c b/storage/myisam/mi_write.c index 91cc146e706..4cea6bbba85 100644 --- a/storage/myisam/mi_write.c +++ b/storage/myisam/mi_write.c @@ -272,7 +272,7 @@ int _mi_ck_write_btree(register MI_INFO *info, uint keynr, uchar *key, comp_flag=SEARCH_BIGGER; /* Put after same key */ else if (keyinfo->flag & (HA_NOSAME|HA_FULLTEXT)) { - comp_flag=SEARCH_FIND | SEARCH_UPDATE; /* No duplicates */ + comp_flag=SEARCH_FIND | SEARCH_UPDATE | SEARCH_INSERT; /* No duplicates */ if (keyinfo->flag & HA_NULL_ARE_EQUAL) comp_flag|= SEARCH_NULL_ARE_EQUAL; } @@ -346,7 +346,7 @@ static int w_search(register MI_INFO *info, register MI_KEYDEF *keyinfo, int error,flag; uint nod_flag, search_key_length; uchar *temp_buff,*keypos; - uchar keybuff[MI_MAX_KEY_BUFF]; + uchar keybuff[HA_MAX_KEY_BUFF]; my_bool was_last_key; my_off_t next_page, dupp_key_pos; DBUG_ENTER("w_search"); @@ -354,7 +354,7 @@ static int w_search(register MI_INFO *info, register MI_KEYDEF *keyinfo, search_key_length= (comp_flag & SEARCH_FIND) ? key_length : USE_WHOLE_KEY; if (!(temp_buff= (uchar*) my_alloca((uint) keyinfo->block_length+ - MI_MAX_KEY_BUFF*2))) + HA_MAX_KEY_BUFF*2))) DBUG_RETURN(-1); if (!_mi_fetch_keypage(info,keyinfo,page,DFLT_INIT_HITS,temp_buff,0)) goto err; @@ -714,7 +714,7 @@ static uchar *_mi_find_last_pos(MI_KEYDEF *keyinfo, uchar *page, { uint keys,length,last_length,key_ref_length; uchar *end,*lastpos,*prevpos; - uchar key_buff[MI_MAX_KEY_BUFF]; + uchar key_buff[HA_MAX_KEY_BUFF]; DBUG_ENTER("_mi_find_last_pos"); key_ref_length=2; @@ -771,7 +771,7 @@ static int _mi_balance_page(register MI_INFO *info, MI_KEYDEF *keyinfo, length,keys; uchar *pos,*buff,*extra_buff; my_off_t next_page,new_pos; - uchar tmp_part_key[MI_MAX_KEY_BUFF]; + uchar tmp_part_key[HA_MAX_KEY_BUFF]; DBUG_ENTER("_mi_balance_page"); k_length=keyinfo->keylength; @@ -937,7 +937,7 @@ static int keys_free(uchar *key, TREE_FREE mode, bulk_insert_param *param) Probably I can use info->lastkey here, but I'm not sure, and to be safe I'd better use local lastkey. */ - uchar lastkey[MI_MAX_KEY_BUFF]; + uchar lastkey[HA_MAX_KEY_BUFF]; uint keylen; MI_KEYDEF *keyinfo; diff --git a/storage/myisam/myisamchk.c b/storage/myisam/myisamchk.c index 75678375ce7..446c3aa2464 100644 --- a/storage/myisam/myisamchk.c +++ b/storage/myisam/myisamchk.c @@ -16,7 +16,6 @@ /* Describe, check and repair of MyISAM tables */ #include "fulltext.h" - #include <m_ctype.h> #include <stdarg.h> #include <my_getopt.h> @@ -41,7 +40,6 @@ static const char *set_collation_name, *opt_tmpdir; static CHARSET_INFO *set_collation; static long opt_myisam_block_size; static long opt_key_cache_block_size; -static const char *my_progname_short; static int stopwords_inited= 0; static MY_TMPDIR myisamchk_tmpdir; @@ -68,9 +66,9 @@ static const char *myisam_stats_method_str="nulls_unequal"; static void get_options(int *argc,char * * *argv); static void print_version(void); static void usage(void); -static int myisamchk(MI_CHECK *param, char *filename); -static void descript(MI_CHECK *param, register MI_INFO *info, char * name); -static int mi_sort_records(MI_CHECK *param, register MI_INFO *info, +static int myisamchk(HA_CHECK *param, char *filename); +static void descript(HA_CHECK *param, register MI_INFO *info, char * name); +static int mi_sort_records(HA_CHECK *param, register MI_INFO *info, char * name, uint sort_key, my_bool write_info, my_bool update_index); static int sort_record_index(MI_SORT_PARAM *sort_param, MI_INFO *info, @@ -78,7 +76,7 @@ static int sort_record_index(MI_SORT_PARAM *sort_param, MI_INFO *info, my_off_t page,uchar *buff,uint sortkey, File new_file, my_bool update_index); -MI_CHECK check_param; +HA_CHECK check_param; /* Main program */ @@ -86,7 +84,6 @@ int main(int argc, char **argv) { int error; MY_INIT(argv[0]); - my_progname_short= my_progname+dirname_length(my_progname); myisamchk_init(&check_param); check_param.opt_lock_memory=1; /* Lock memory if possible */ @@ -106,7 +103,7 @@ int main(int argc, char **argv) (!(check_param.testflag & (T_REP | T_REP_BY_SORT | T_SORT_RECORDS | T_SORT_INDEX)))) { - uint old_testflag=check_param.testflag; + ulonglong old_testflag=check_param.testflag; if (!(check_param.testflag & T_REP)) check_param.testflag|= T_REP_BY_SORT; check_param.testflag&= ~T_EXTEND; /* Don't needed */ @@ -697,7 +694,7 @@ get_one_option(int optid, case OPT_STATS_METHOD: { int method; - enum_mi_stats_method method_conv; + enum_handler_stats_method method_conv; LINT_INIT(method_conv); myisam_stats_method_str= argument; if ((method=find_type(argument, &myisam_stats_method_typelib, 2)) <= 0) @@ -796,10 +793,10 @@ static void get_options(register int *argc,register char ***argv) /* Check table */ -static int myisamchk(MI_CHECK *param, char * filename) +static int myisamchk(HA_CHECK *param, char * filename) { int error,lock_type,recreate; - int rep_quick= param->testflag & (T_QUICK | T_FORCE_UNIQUENESS); + int rep_quick= test(param->testflag & (T_QUICK | T_FORCE_UNIQUENESS)); uint raid_chunks; MI_INFO *info; File datafile; @@ -939,7 +936,7 @@ static int myisamchk(MI_CHECK *param, char * filename) param->testflag|=T_REP_BY_SORT; /* if only STATISTICS */ if (!(param->testflag & T_SILENT)) printf("- '%s' has old table-format. Recreating index\n",filename); - rep_quick|=T_QUICK; + rep_quick= 1; } share=info->s; share->tot_locks-= share->r_locks; @@ -1115,7 +1112,7 @@ static int myisamchk(MI_CHECK *param, char * filename) if ((info->s->options & (HA_OPTION_PACK_RECORD | HA_OPTION_COMPRESS_RECORD)) || (param->testflag & (T_EXTEND | T_MEDIUM))) - error|=chk_data_link(param, info, param->testflag & T_EXTEND); + error|=chk_data_link(param, info, test(param->testflag & T_EXTEND)); error|=flush_blocks(param, share->key_cache, share->kfile); VOID(end_io_cache(¶m->read_cache)); } @@ -1201,7 +1198,7 @@ end2: /* Write info about table */ -static void descript(MI_CHECK *param, register MI_INFO *info, char * name) +static void descript(HA_CHECK *param, register MI_INFO *info, char * name) { uint key,keyseg_nr,field,start; reg3 MI_KEYDEF *keyinfo; @@ -1466,7 +1463,7 @@ static void descript(MI_CHECK *param, register MI_INFO *info, char * name) /* Sort records according to one key */ -static int mi_sort_records(MI_CHECK *param, +static int mi_sort_records(HA_CHECK *param, register MI_INFO *info, char * name, uint sort_key, my_bool write_info, @@ -1480,7 +1477,7 @@ static int mi_sort_records(MI_CHECK *param, ha_rows old_record_count; MYISAM_SHARE *share=info->s; char llbuff[22],llbuff2[22]; - SORT_INFO sort_info; + MI_SORT_INFO sort_info; MI_SORT_PARAM sort_param; DBUG_ENTER("sort_records"); @@ -1656,10 +1653,10 @@ static int sort_record_index(MI_SORT_PARAM *sort_param,MI_INFO *info, uint nod_flag,used_length,key_length; uchar *temp_buff,*keypos,*endpos; my_off_t next_page,rec_pos; - uchar lastkey[MI_MAX_KEY_BUFF]; + uchar lastkey[HA_MAX_KEY_BUFF]; char llbuff[22]; - SORT_INFO *sort_info= sort_param->sort_info; - MI_CHECK *param=sort_info->param; + MI_SORT_INFO *sort_info= sort_param->sort_info; + HA_CHECK *param=sort_info->param; DBUG_ENTER("sort_record_index"); nod_flag=mi_test_if_nod(buff); @@ -1747,7 +1744,7 @@ err: static int not_killed= 0; -volatile int *killed_ptr(MI_CHECK *param __attribute__((unused))) +volatile int *killed_ptr(HA_CHECK *param __attribute__((unused))) { return ¬_killed; /* always NULL */ } @@ -1755,7 +1752,7 @@ volatile int *killed_ptr(MI_CHECK *param __attribute__((unused))) /* print warnings and errors */ /* VARARGS */ -void mi_check_print_info(MI_CHECK *param __attribute__((unused)), +void mi_check_print_info(HA_CHECK *param __attribute__((unused)), const char *fmt,...) { va_list args; @@ -1768,7 +1765,7 @@ void mi_check_print_info(MI_CHECK *param __attribute__((unused)), /* VARARGS */ -void mi_check_print_warning(MI_CHECK *param, const char *fmt,...) +void mi_check_print_warning(HA_CHECK *param, const char *fmt,...) { va_list args; DBUG_ENTER("mi_check_print_warning"); @@ -1793,7 +1790,7 @@ void mi_check_print_warning(MI_CHECK *param, const char *fmt,...) /* VARARGS */ -void mi_check_print_error(MI_CHECK *param, const char *fmt,...) +void mi_check_print_error(HA_CHECK *param, const char *fmt,...) { va_list args; DBUG_ENTER("mi_check_print_error"); diff --git a/storage/myisam/myisamdef.h b/storage/myisam/myisamdef.h index b64c7f6bd7f..f311202e47a 100644 --- a/storage/myisam/myisamdef.h +++ b/storage/myisam/myisamdef.h @@ -15,8 +15,8 @@ /* This file is included by all internal myisam files */ -#include "myisam.h" /* Structs & some defines */ -#include "myisampack.h" /* packing of keys */ +#include "myisam.h" /* Structs & some defines */ +#include "myisampack.h" /* packing of keys */ #include <my_tree.h> #ifdef THREAD #include <my_pthread.h> @@ -26,15 +26,16 @@ #endif #if defined(my_write) && !defined(MAP_TO_USE_RAID) -#undef my_write /* undef map from my_nosys; We need test-if-disk full */ +/* undef map from my_nosys; We need test-if-disk full */ +#undef my_write #endif typedef struct st_mi_status_info { - ha_rows records; /* Rows in table */ - ha_rows del; /* Removed rows */ - my_off_t empty; /* lost space in datafile */ - my_off_t key_empty; /* lost space in indexfile */ + ha_rows records; /* Rows in table */ + ha_rows del; /* Removed rows */ + my_off_t empty; /* lost space in datafile */ + my_off_t key_empty; /* lost space in indexfile */ my_off_t key_file_length; my_off_t data_file_length; ha_checksum checksum; @@ -43,153 +44,158 @@ typedef struct st_mi_status_info typedef struct st_mi_state_info { - struct { /* Fileheader */ + struct + { /* Fileheader */ uchar file_version[4]; uchar options[2]; uchar header_length[2]; uchar state_info_length[2]; uchar base_info_length[2]; uchar base_pos[2]; - uchar key_parts[2]; /* Key parts */ - uchar unique_key_parts[2]; /* Key parts + unique parts */ - uchar keys; /* number of keys in file */ - uchar uniques; /* number of UNIQUE definitions */ - uchar language; /* Language for indexes */ - uchar max_block_size_index; /* max keyblock size */ + uchar key_parts[2]; /* Key parts */ + uchar unique_key_parts[2]; /* Key parts + unique parts */ + uchar keys; /* number of keys in file */ + uchar uniques; /* number of UNIQUE definitions */ + uchar language; /* Language for indexes */ + uchar max_block_size_index; /* max keyblock size */ uchar fulltext_keys; uchar not_used; /* To align to 8 */ } header; MI_STATUS_INFO state; - ha_rows split; /* number of split blocks */ - my_off_t dellink; /* Link to next removed block */ + ha_rows split; /* number of split blocks */ + my_off_t dellink; /* Link to next removed block */ ulonglong auto_increment; - ulong process; /* process that updated table last */ - ulong unique; /* Unique number for this process */ - ulong update_count; /* Updated for each write lock */ + ulong process; /* process that updated table last */ + ulong unique; /* Unique number for this process */ + ulong update_count; /* Updated for each write lock */ ulong status; ulong *rec_per_key_part; - my_off_t *key_root; /* Start of key trees */ - my_off_t *key_del; /* delete links for trees */ - my_off_t rec_per_key_rows; /* Rows when calculating rec_per_key */ - - ulong sec_index_changed; /* Updated when new sec_index */ - ulong sec_index_used; /* which extra index are in use */ - ulonglong key_map; /* Which keys are in use */ ha_checksum checksum; /* Table checksum */ - ulong version; /* timestamp of create */ - time_t create_time; /* Time when created database */ - time_t recover_time; /* Time for last recover */ - time_t check_time; /* Time for last check */ - uint sortkey; /* sorted by this key (not used) */ + my_off_t *key_root; /* Start of key trees */ + my_off_t *key_del; /* delete links for trees */ + my_off_t rec_per_key_rows; /* Rows when calculating rec_per_key */ + + ulong sec_index_changed; /* Updated when new sec_index */ + ulong sec_index_used; /* which extra index are in use */ + ulonglong key_map; /* Which keys are in use */ + ulong version; /* timestamp of create */ + time_t create_time; /* Time when created database */ + time_t recover_time; /* Time for last recover */ + time_t check_time; /* Time for last check */ + uint sortkey; /* sorted by this key (not used) */ uint open_count; - uint8 changed; /* Changed since myisamchk */ + uint8 changed; /* Changed since myisamchk */ /* the following isn't saved on disk */ - uint state_diff_length; /* Should be 0 */ - uint state_length; /* Length of state header in file */ + uint state_diff_length; /* Should be 0 */ + uint state_length; /* Length of state header in file */ ulong *key_info; } MI_STATE_INFO; -#define MI_STATE_INFO_SIZE (24+14*8+7*4+2*2+8) -#define MI_STATE_KEY_SIZE 8 +#define MI_STATE_INFO_SIZE (24+14*8+7*4+2*2+8) +#define MI_STATE_KEY_SIZE 8 #define MI_STATE_KEYBLOCK_SIZE 8 -#define MI_STATE_KEYSEG_SIZE 4 -#define MI_STATE_EXTRA_SIZE ((MI_MAX_KEY+MI_MAX_KEY_BLOCK_SIZE)*MI_STATE_KEY_SIZE + MI_MAX_KEY*MI_MAX_KEY_SEG*MI_STATE_KEYSEG_SIZE) -#define MI_KEYDEF_SIZE (2+ 5*2) -#define MI_UNIQUEDEF_SIZE (2+1+1) -#define HA_KEYSEG_SIZE (6+ 2*2 + 4*2) -#define MI_COLUMNDEF_SIZE (2*3+1) -#define MI_BASE_INFO_SIZE (5*8 + 8*4 + 4 + 4*2 + 16) -#define MI_INDEX_BLOCK_MARGIN 16 /* Safety margin for .MYI tables */ +#define MI_STATE_KEYSEG_SIZE 4 +#define MI_STATE_EXTRA_SIZE ((MI_MAX_KEY+MI_MAX_KEY_BLOCK_SIZE)*MI_STATE_KEY_SIZE + MI_MAX_KEY*HA_MAX_KEY_SEG*MI_STATE_KEYSEG_SIZE) +#define MI_KEYDEF_SIZE (2+ 5*2) +#define MI_UNIQUEDEF_SIZE (2+1+1) +#define HA_KEYSEG_SIZE (6+ 2*2 + 4*2) +#define MI_COLUMNDEF_SIZE (2*3+1) +#define MI_BASE_INFO_SIZE (5*8 + 8*4 + 4 + 4*2 + 16) +#define MI_INDEX_BLOCK_MARGIN 16 /* Safety margin for .MYI tables */ typedef struct st_mi_base_info { - my_off_t keystart; /* Start of keys */ + my_off_t keystart; /* Start of keys */ my_off_t max_data_file_length; my_off_t max_key_file_length; my_off_t margin_key_file_length; - ha_rows records,reloc; /* Create information */ - ulong mean_row_length; /* Create information */ - ulong reclength; /* length of unpacked record */ - ulong pack_reclength; /* Length of full packed rec. */ + ha_rows records, reloc; /* Create information */ + ulong mean_row_length; /* Create information */ + ulong reclength; /* length of unpacked record */ + ulong pack_reclength; /* Length of full packed rec. */ ulong min_pack_length; - ulong max_pack_length; /* Max possibly length of packed rec.*/ + ulong max_pack_length; /* Max possibly length of packed rec.*/ ulong min_block_length; - ulong fields, /* fields in table */ - pack_fields; /* packed fields in table */ - uint rec_reflength; /* = 2-8 */ - uint key_reflength; /* = 2-8 */ - uint keys; /* same as in state.header */ - uint auto_key; /* Which key-1 is a auto key */ - uint blobs; /* Number of blobs */ - uint pack_bits; /* Length of packed bits */ - uint max_key_block_length; /* Max block length */ - uint max_key_length; /* Max key length */ + ulong fields, /* fields in table */ + pack_fields; /* packed fields in table */ + uint rec_reflength; /* = 2-8 */ + uint key_reflength; /* = 2-8 */ + uint keys; /* same as in state.header */ + uint auto_key; /* Which key-1 is a auto key */ + uint blobs; /* Number of blobs */ + uint pack_bits; /* Length of packed bits */ + uint max_key_block_length; /* Max block length */ + uint max_key_length; /* Max key length */ /* Extra allocation when using dynamic record format */ uint extra_alloc_bytes; uint extra_alloc_procent; /* Info about raid */ - uint raid_type,raid_chunks; + uint raid_type, raid_chunks; ulong raid_chunksize; /* The following are from the header */ - uint key_parts,all_key_parts; + uint key_parts, all_key_parts; } MI_BASE_INFO; - /* Structs used intern in database */ + /* Structs used intern in database */ -typedef struct st_mi_blob /* Info of record */ +typedef struct st_mi_blob /* Info of record */ { - ulong offset; /* Offset to blob in record */ - uint pack_length; /* Type of packed length */ - ulong length; /* Calc:ed for each record */ + ulong offset; /* Offset to blob in record */ + uint pack_length; /* Type of packed length */ + ulong length; /* Calc:ed for each record */ } MI_BLOB; -typedef struct st_mi_isam_pack { +typedef struct st_mi_isam_pack +{ ulong header_length; uint ref_length; uchar version; } MI_PACK; -#define MAX_NONMAPPED_INSERTS 1000 +#define MAX_NONMAPPED_INSERTS 1000 -typedef struct st_mi_isam_share { /* Shared between opens */ +typedef struct st_mi_isam_share +{ /* Shared between opens */ MI_STATE_INFO state; MI_BASE_INFO base; - MI_KEYDEF ft2_keyinfo; /* Second-level ft-key definition */ - MI_KEYDEF *keyinfo; /* Key definitions */ - MI_UNIQUEDEF *uniqueinfo; /* unique definitions */ - HA_KEYSEG *keyparts; /* key part info */ - MI_COLUMNDEF *rec; /* Pointer to field information */ - MI_PACK pack; /* Data about packed records */ - MI_BLOB *blobs; /* Pointer to blobs */ - char *unique_file_name; /* realpath() of index file */ - char *data_file_name, /* Resolved path names from symlinks */ - *index_file_name; - uchar *file_map; /* mem-map of file if possible */ - KEY_CACHE *key_cache; /* ref to the current key cache */ + MI_KEYDEF ft2_keyinfo; /* Second-level ft-key definition */ + MI_KEYDEF *keyinfo; /* Key definitions */ + MI_UNIQUEDEF *uniqueinfo; /* unique definitions */ + HA_KEYSEG *keyparts; /* key part info */ + MI_COLUMNDEF *rec; /* Pointer to field information */ + MI_PACK pack; /* Data about packed records */ + MI_BLOB *blobs; /* Pointer to blobs */ + char *unique_file_name; /* realpath() of index file */ + char *data_file_name, /* Resolved path names from symlinks */ + *index_file_name; + uchar *file_map; /* mem-map of file if possible */ + KEY_CACHE *key_cache; /* ref to the current key cache */ MI_DECODE_TREE *decode_trees; uint16 *decode_tables; - int (*read_record)(struct st_myisam_info*, my_off_t, uchar*); - int (*write_record)(struct st_myisam_info*, const uchar*); - int (*update_record)(struct st_myisam_info*, my_off_t, const uchar*); - int (*delete_record)(struct st_myisam_info*); - int (*read_rnd)(struct st_myisam_info*, uchar*, my_off_t, my_bool); - int (*compare_record)(struct st_myisam_info*, const uchar *); /* Function to use for a row checksum. */ - ha_checksum (*calc_checksum)(struct st_myisam_info*, const uchar *); - int (*compare_unique)(struct st_myisam_info*, MI_UNIQUEDEF *, - const uchar *record, my_off_t pos); - size_t (*file_read)(MI_INFO *, uchar *, size_t, my_off_t, myf); - size_t (*file_write)(MI_INFO *, const uchar *, size_t, my_off_t, myf); + int(*read_record) (struct st_myisam_info *, my_off_t, uchar*); + int(*write_record) (struct st_myisam_info *, const uchar*); + int(*update_record) (struct st_myisam_info *, my_off_t, const uchar*); + int(*delete_record) (struct st_myisam_info *); + int(*read_rnd) (struct st_myisam_info *, uchar*, my_off_t, my_bool); + int(*compare_record) (struct st_myisam_info *, const uchar*); + ha_checksum(*calc_checksum) (struct st_myisam_info *, const uchar*); + /* calculate checksum for a row during check table */ + ha_checksum(*calc_check_checksum)(struct st_myisam_info *, const uchar *); + int(*compare_unique) (struct st_myisam_info *, MI_UNIQUEDEF *, + const uchar *record, my_off_t pos); + size_t (*file_read) (MI_INFO *, uchar *, size_t, my_off_t, myf); + size_t (*file_write) (MI_INFO *, const uchar *, size_t, my_off_t, myf); invalidator_by_filename invalidator; /* query cache invalidator */ - ulong this_process; /* processid */ - ulong last_process; /* For table-change-check */ - ulong last_version; /* Version on start */ - ulong options; /* Options used */ - ulong min_pack_length; /* Theese are used by packed data */ + ulong this_process; /* processid */ + ulong last_process; /* For table-change-check */ + ulong last_version; /* Version on start */ + ulong options; /* Options used */ + ulong min_pack_length; /* Theese are used by packed data */ ulong max_pack_length; ulong state_diff_length; uint rec_reflength; /* rec_reflength in use now */ @@ -205,185 +211,130 @@ typedef struct st_mi_isam_share { /* Shared between opens */ enum data_file_type data_file_type; /* Below flag is needed to make log tables work with concurrent insert */ my_bool is_log_table; + /* This is 1 if they table checksum is of old type */ + my_bool has_null_fields; + my_bool has_varchar_fields; - my_bool changed, /* If changed since lock */ - global_changed, /* If changed since open */ - not_flushed, - temporary,delay_key_write, - concurrent_insert; + my_bool changed, /* If changed since lock */ + global_changed, /* If changed since open */ + not_flushed, temporary, delay_key_write, concurrent_insert; #ifdef THREAD THR_LOCK lock; - pthread_mutex_t intern_lock; /* Locking for use with _locking */ + pthread_mutex_t intern_lock; /* Locking for use with _locking */ rw_lock_t *key_root_lock; #endif my_off_t mmaped_length; - uint nonmmaped_inserts; /* counter of writing in non-mmaped - area */ + /* counter of writing in non-mmaped area */ + uint nonmmaped_inserts; rw_lock_t mmap_lock; } MYISAM_SHARE; -typedef uint mi_bit_type; - -typedef struct st_mi_bit_buff { /* Used for packing of record */ - mi_bit_type current_byte; - uint bits; - uchar *pos,*end,*blob_pos,*blob_end; - uint error; -} MI_BIT_BUFF; - -struct st_myisam_info { - MYISAM_SHARE *s; /* Shared between open:s */ - MI_STATUS_INFO *state,save_state; - MI_BLOB *blobs; /* Pointer to blobs */ - MI_BIT_BUFF bit_buff; +struct st_myisam_info +{ + MYISAM_SHARE *s; /* Shared between open:s */ + MI_STATUS_INFO *state, save_state; + MI_BLOB *blobs; /* Pointer to blobs */ + MI_BIT_BUFF bit_buff; /* accumulate indexfile changes between write's */ - TREE *bulk_insert; + TREE *bulk_insert; DYNAMIC_ARRAY *ft1_to_ft2; /* used only in ft1->ft2 conversion */ MEM_ROOT ft_memroot; /* used by the parser */ - MYSQL_FTPARSER_PARAM *ftparser_param; /* share info between init/deinit */ - char *filename; /* parameter to open filename */ - uchar *buff, /* Temp area for key */ - *lastkey,*lastkey2; /* Last used search key */ - uchar *first_mbr_key; /* Searhed spatial key */ - uchar *rec_buff; /* Tempbuff for recordpack */ - uchar *int_keypos, /* Save position for next/previous */ - *int_maxpos; /* -""- */ - uint int_nod_flag; /* -""- */ - uint32 int_keytree_version; /* -""- */ - int (*read_record)(struct st_myisam_info*, my_off_t, uchar*); + MYSQL_FTPARSER_PARAM *ftparser_param; /* share info between init/deinit */ + char *filename; /* parameter to open filename */ + uchar *buff, /* Temp area for key */ + *lastkey, *lastkey2; /* Last used search key */ + uchar *first_mbr_key; /* Searhed spatial key */ + uchar *rec_buff; /* Tempbuff for recordpack */ + uchar *int_keypos, /* Save position for next/previous */ + *int_maxpos; /* -""- */ + uint int_nod_flag; /* -""- */ + uint32 int_keytree_version; /* -""- */ + int(*read_record) (struct st_myisam_info *, my_off_t, uchar *); invalidator_by_filename invalidator; /* query cache invalidator */ - ulong this_unique; /* uniq filenumber or thread */ - ulong last_unique; /* last unique number */ - ulong this_loop; /* counter for this open */ - ulong last_loop; /* last used counter */ - my_off_t lastpos, /* Last record position */ - nextpos; /* Position to next record */ + ulong this_unique; /* uniq filenumber or thread */ + ulong last_unique; /* last unique number */ + ulong this_loop; /* counter for this open */ + ulong last_loop; /* last used counter */ + my_off_t lastpos, /* Last record position */ + nextpos; /* Position to next record */ my_off_t save_lastpos; - my_off_t pos; /* Intern variable */ - my_off_t last_keypage; /* Last key page read */ - my_off_t last_search_keypage; /* Last keypage when searching */ + my_off_t pos; /* Intern variable */ + my_off_t last_keypage; /* Last key page read */ + my_off_t last_search_keypage; /* Last keypage when searching */ my_off_t dupp_key_pos; ha_checksum checksum; /* Temp storage for row checksum */ - /* QQ: the folloing two xxx_length fields should be removed, - as they are not compatible with parallel repair */ - ulong packed_length,blob_length; /* Length of found, packed record */ - int dfile; /* The datafile */ - uint opt_flag; /* Optim. for space/speed */ - uint update; /* If file changed since open */ - int lastinx; /* Last used index */ - uint lastkey_length; /* Length of key in lastkey */ - uint last_rkey_length; /* Last length in mi_rkey() */ + /* + QQ: the folloing two xxx_length fields should be removed, + as they are not compatible with parallel repair + */ + ulong packed_length, blob_length; /* Length of found, packed record */ + int dfile; /* The datafile */ + uint opt_flag; /* Optim. for space/speed */ + uint update; /* If file changed since open */ + int lastinx; /* Last used index */ + uint lastkey_length; /* Length of key in lastkey */ + uint last_rkey_length; /* Last length in mi_rkey() */ enum ha_rkey_function last_key_func; /* CONTAIN, OVERLAP, etc */ - uint save_lastkey_length; - uint pack_key_length; /* For MYISAMMRG */ + uint save_lastkey_length; + uint pack_key_length; /* For MYISAMMRG */ uint16 last_used_keyseg; /* For MyISAMMRG */ - int errkey; /* Got last error on this key */ - int lock_type; /* How database was locked */ - int tmp_lock_type; /* When locked by readinfo */ - uint data_changed; /* Somebody has changed data */ - uint save_update; /* When using KEY_READ */ - int save_lastinx; - LIST open_list; - IO_CACHE rec_cache; /* When cacheing records */ - uint preload_buff_size; /* When preloading indexes */ - myf lock_wait; /* is 0 or MY_DONT_WAIT */ - my_bool was_locked; /* Was locked in panic */ - my_bool append_insert_at_end; /* Set if concurrent insert */ + int errkey; /* Got last error on this key */ + int lock_type; /* How database was locked */ + int tmp_lock_type; /* When locked by readinfo */ + uint data_changed; /* Somebody has changed data */ + uint save_update; /* When using KEY_READ */ + int save_lastinx; + LIST open_list; + IO_CACHE rec_cache; /* When cacheing records */ + uint preload_buff_size; /* When preloading indexes */ + myf lock_wait; /* is 0 or MY_SHORT_WAIT */ + my_bool was_locked; /* Was locked in panic */ + my_bool append_insert_at_end; /* Set if concurrent insert */ my_bool quick_mode; - my_bool page_changed; /* If info->buff can't be used for rnext */ - my_bool buff_used; /* If info->buff has to be reread for rnext */ - my_bool once_flags; /* For MYISAMMRG */ + /* If info->buff can't be used for rnext */ + my_bool page_changed; + /* If info->buff has to be reread for rnext */ + my_bool buff_used; + my_bool once_flags; /* For MYISAMMRG */ #ifdef __WIN__ my_bool owned_by_merge; /* This MyISAM table is part of a merge union */ #endif #ifdef THREAD THR_LOCK_DATA lock; #endif - uchar *rtree_recursion_state; /* For RTREE */ - int rtree_recursion_depth; + uchar *rtree_recursion_state; /* For RTREE */ + int rtree_recursion_depth; }; -typedef struct st_buffpek { - my_off_t file_pos; /* Where we are in the sort file */ - uchar *base,*key; /* Key pointers */ - ha_rows count; /* Number of rows in table */ - ulong mem_count; /* numbers of keys in memory */ - ulong max_keys; /* Max keys in buffert */ -} BUFFPEK; - -typedef struct st_mi_sort_param -{ - pthread_t thr; - IO_CACHE read_cache, tempfile, tempfile_for_exceptions; - DYNAMIC_ARRAY buffpek; - MI_BIT_BUFF bit_buff; /* For parallel repair of packrec. */ - - /* - The next two are used to collect statistics, see update_key_parts for - description. - */ - ulonglong unique[MI_MAX_KEY_SEG+1]; - ulonglong notnull[MI_MAX_KEY_SEG+1]; - - my_off_t pos,max_pos,filepos,start_recpos; - uint key, key_length,real_key_length,sortbuff_size; - uint maxbuffers, keys, find_length, sort_keys_length; - my_bool fix_datafile, master; - my_bool calc_checksum; /* calculate table checksum */ - MI_KEYDEF *keyinfo; - HA_KEYSEG *seg; - SORT_INFO *sort_info; - uchar **sort_keys; - uchar *rec_buff; - void *wordlist, *wordptr; - MEM_ROOT wordroot; - uchar *record; - MY_TMPDIR *tmpdir; - int (*key_cmp)(struct st_mi_sort_param *, const void *, const void *); - int (*key_read)(struct st_mi_sort_param *,void *); - int (*key_write)(struct st_mi_sort_param *, const void *); - void (*lock_in_memory)(MI_CHECK *); - NEAR int (*write_keys)(struct st_mi_sort_param *, register uchar **, - uint , struct st_buffpek *, IO_CACHE *); - NEAR uint (*read_to_buffer)(IO_CACHE *,struct st_buffpek *, uint); - NEAR int (*write_key)(struct st_mi_sort_param *, IO_CACHE *,uchar *, - uint, uint); -} MI_SORT_PARAM; - - /* Some defines used by isam-funktions */ - -#define USE_WHOLE_KEY MI_MAX_KEY_BUFF*2 /* Use whole key in _mi_search() */ -#define F_EXTRA_LCK -1 - - /* bits in opt_flag */ -#define MEMMAP_USED 32 +#define USE_WHOLE_KEY HA_MAX_KEY_BUFF*2 /* Use whole key in _mi_search() */ +#define F_EXTRA_LCK -1 +/* bits in opt_flag */ +#define MEMMAP_USED 32 #define REMEMBER_OLD_POS 64 -#define WRITEINFO_UPDATE_KEYFILE 1 -#define WRITEINFO_NO_UNLOCK 2 +#define WRITEINFO_UPDATE_KEYFILE 1 +#define WRITEINFO_NO_UNLOCK 2 - /* once_flags */ +/* once_flags */ #define USE_PACKED_KEYS 1 #define RRND_PRESERVE_LASTINX 2 - /* bits in state.changed */ - -#define STATE_CHANGED 1 -#define STATE_CRASHED 2 +/* bits in state.changed */ +#define STATE_CHANGED 1 +#define STATE_CRASHED 2 #define STATE_CRASHED_ON_REPAIR 4 -#define STATE_NOT_ANALYZED 8 +#define STATE_NOT_ANALYZED 8 #define STATE_NOT_OPTIMIZED_KEYS 16 -#define STATE_NOT_SORTED_PAGES 32 +#define STATE_NOT_SORTED_PAGES 32 - /* options to mi_read_cache */ +/* options to mi_read_cache */ +#define READING_NEXT 1 +#define READING_HEADER 2 -#define READING_NEXT 1 -#define READING_HEADER 2 - -#define mi_getint(x) ((uint) mi_uint2korr(x) & 32767) +#define mi_getint(x) ((uint) mi_uint2korr(x) & 32767) #define mi_putint(x,y,nod) { uint16 boh=(nod ? (uint16) 32768 : 0) + (uint16) (y);\ - mi_int2store(x,boh); } + mi_int2store(x,boh); } #define mi_test_if_nod(x) (x[0] & 128 ? info->s->base.key_reflength : 0) #define mi_mark_crashed(x) do{(x)->s->state.changed|= STATE_CRASHED; \ DBUG_PRINT("error", ("Marked table crashed")); \ @@ -424,39 +375,39 @@ typedef struct st_mi_sort_param #define get_pack_length(length) ((length) >= 255 ? 3 : 1) -#define MI_MIN_BLOCK_LENGTH 20 /* Because of delete-link */ -#define MI_EXTEND_BLOCK_LENGTH 20 /* Don't use to small record-blocks */ -#define MI_SPLIT_LENGTH ((MI_EXTEND_BLOCK_LENGTH+4)*2) -#define MI_MAX_DYN_BLOCK_HEADER 20 /* Max prefix of record-block */ +#define MI_MIN_BLOCK_LENGTH 20 /* Because of delete-link */ +#define MI_EXTEND_BLOCK_LENGTH 20 /* Don't use to small record-blocks */ +#define MI_SPLIT_LENGTH ((MI_EXTEND_BLOCK_LENGTH+4)*2) +#define MI_MAX_DYN_BLOCK_HEADER 20 /* Max prefix of record-block */ #define MI_BLOCK_INFO_HEADER_LENGTH 20 -#define MI_DYN_DELETE_BLOCK_HEADER 20 /* length of delete-block-header */ -#define MI_DYN_MAX_BLOCK_LENGTH ((1L << 24)-4L) -#define MI_DYN_MAX_ROW_LENGTH (MI_DYN_MAX_BLOCK_LENGTH - MI_SPLIT_LENGTH) -#define MI_DYN_ALIGN_SIZE 4 /* Align blocks on this */ -#define MI_MAX_DYN_HEADER_BYTE 13 /* max header byte for dynamic rows */ -#define MI_MAX_BLOCK_LENGTH ((((ulong) 1 << 24)-1) & (~ (ulong) (MI_DYN_ALIGN_SIZE-1))) +#define MI_DYN_DELETE_BLOCK_HEADER 20 /* length of delete-block-header */ +#define MI_DYN_MAX_BLOCK_LENGTH ((1L << 24)-4L) +#define MI_DYN_MAX_ROW_LENGTH (MI_DYN_MAX_BLOCK_LENGTH - MI_SPLIT_LENGTH) +#define MI_DYN_ALIGN_SIZE 4 /* Align blocks on this */ +#define MI_MAX_DYN_HEADER_BYTE 13 /* max header byte for dynamic rows */ +#define MI_MAX_BLOCK_LENGTH ((((ulong) 1 << 24)-1) & (~ (ulong) (MI_DYN_ALIGN_SIZE-1))) #define MI_REC_BUFF_OFFSET ALIGN_SIZE(MI_DYN_DELETE_BLOCK_HEADER+sizeof(uint32)) -#define MEMMAP_EXTRA_MARGIN 7 /* Write this as a suffix for file */ +#define MEMMAP_EXTRA_MARGIN 7 /* Write this as a suffix for file */ -#define PACK_TYPE_SELECTED 1 /* Bits in field->pack_type */ -#define PACK_TYPE_SPACE_FIELDS 2 -#define PACK_TYPE_ZERO_FILL 4 -#define MI_FOUND_WRONG_KEY 32738 /* Impossible value from ha_key_cmp */ +#define PACK_TYPE_SELECTED 1 /* Bits in field->pack_type */ +#define PACK_TYPE_SPACE_FIELDS 2 +#define PACK_TYPE_ZERO_FILL 4 +#define MI_FOUND_WRONG_KEY 32738 /* Impossible value from ha_key_cmp */ -#define MI_MAX_KEY_BLOCK_SIZE (MI_MAX_KEY_BLOCK_LENGTH/MI_MIN_KEY_BLOCK_LENGTH) +#define MI_MAX_KEY_BLOCK_SIZE (MI_MAX_KEY_BLOCK_LENGTH/MI_MIN_KEY_BLOCK_LENGTH) #define MI_BLOCK_SIZE(key_length,data_pointer,key_pointer,block_size) (((((key_length)+(data_pointer)+(key_pointer))*4+(key_pointer)+2)/(block_size)+1)*(block_size)) -#define MI_MAX_KEYPTR_SIZE 5 /* For calculating block lengths */ -#define MI_MIN_KEYBLOCK_LENGTH 50 /* When to split delete blocks */ +#define MI_MAX_KEYPTR_SIZE 5 /* For calculating block lengths */ +#define MI_MIN_KEYBLOCK_LENGTH 50 /* When to split delete blocks */ -#define MI_MIN_SIZE_BULK_INSERT_TREE 16384 /* this is per key */ +#define MI_MIN_SIZE_BULK_INSERT_TREE 16384 /* this is per key */ #define MI_MIN_ROWS_TO_USE_BULK_INSERT 100 #define MI_MIN_ROWS_TO_DISABLE_INDEXES 100 #define MI_MIN_ROWS_TO_USE_WRITE_CACHE 10 /* The UNIQUE check is done with a hashed long key */ -#define MI_UNIQUE_HASH_TYPE HA_KEYTYPE_ULONG_INT +#define MI_UNIQUE_HASH_TYPE HA_KEYTYPE_ULONG_INT #define mi_unique_store(A,B) mi_int4store((A),(B)) #ifdef THREAD @@ -468,175 +419,180 @@ extern pthread_mutex_t THR_LOCK_myisam; #define rw_unlock(A) {} #endif - /* Some extern variables */ +/* Some extern variables */ extern LIST *myisam_open_list; -extern uchar NEAR myisam_file_magic[],NEAR myisam_pack_file_magic[]; -extern uint NEAR myisam_read_vec[],NEAR myisam_readnext_vec[]; +extern uchar NEAR myisam_file_magic[], NEAR myisam_pack_file_magic[]; +extern uint NEAR myisam_read_vec[], NEAR myisam_readnext_vec[]; extern uint myisam_quick_table_bits; extern File myisam_log_file; extern ulong myisam_pid; - /* This is used by _mi_calc_xxx_key_length och _mi_store_key */ +/* This is used by _mi_calc_xxx_key_length och _mi_store_key */ typedef struct st_mi_s_param { - uint ref_length,key_length, - n_ref_length, - n_length, - totlength, - part_of_prev_key,prev_length,pack_marker; - uchar *key, *prev_key,*next_key_pos; + uint ref_length, key_length, + n_ref_length, + n_length, totlength, part_of_prev_key, prev_length, pack_marker; + uchar *key, *prev_key, *next_key_pos; my_bool store_not_null; } MI_KEY_PARAM; - /* Prototypes for intern functions */ +/* Prototypes for intern functions */ -extern int _mi_read_dynamic_record(MI_INFO *info,my_off_t filepos,uchar *buf); -extern int _mi_write_dynamic_record(MI_INFO*, const uchar*); -extern int _mi_update_dynamic_record(MI_INFO*, my_off_t, const uchar*); +extern int _mi_read_dynamic_record(MI_INFO *info, my_off_t filepos, uchar *buf); +extern int _mi_write_dynamic_record(MI_INFO *, const uchar *); +extern int _mi_update_dynamic_record(MI_INFO *, my_off_t, const uchar *); extern int _mi_delete_dynamic_record(MI_INFO *info); -extern int _mi_cmp_dynamic_record(MI_INFO *info,const uchar *record); -extern int _mi_read_rnd_dynamic_record(MI_INFO *, uchar *,my_off_t, my_bool); -extern int _mi_write_blob_record(MI_INFO*, const uchar*); -extern int _mi_update_blob_record(MI_INFO*, my_off_t, const uchar*); -extern int _mi_read_static_record(MI_INFO *info, my_off_t filepos,uchar *buf); -extern int _mi_write_static_record(MI_INFO*, const uchar*); -extern int _mi_update_static_record(MI_INFO*, my_off_t, const uchar*); +extern int _mi_cmp_dynamic_record(MI_INFO *info, const uchar *record); +extern int _mi_read_rnd_dynamic_record(MI_INFO *, uchar *, my_off_t, my_bool); +extern int _mi_write_blob_record(MI_INFO *, const uchar *); +extern int _mi_update_blob_record(MI_INFO *, my_off_t, const uchar *); +extern int _mi_read_static_record(MI_INFO *info, my_off_t filepos, uchar *buf); +extern int _mi_write_static_record(MI_INFO *, const uchar *); +extern int _mi_update_static_record(MI_INFO *, my_off_t, const uchar *); extern int _mi_delete_static_record(MI_INFO *info); -extern int _mi_cmp_static_record(MI_INFO *info,const uchar *record); -extern int _mi_read_rnd_static_record(MI_INFO*, uchar *,my_off_t, my_bool); -extern int _mi_ck_write(MI_INFO *info,uint keynr,uchar *key,uint length); +extern int _mi_cmp_static_record(MI_INFO *info, const uchar *record); +extern int _mi_read_rnd_static_record(MI_INFO *, uchar *, my_off_t, my_bool); +extern int _mi_ck_write(MI_INFO *info, uint keynr, uchar *key, uint length); extern int _mi_ck_real_write_btree(MI_INFO *info, MI_KEYDEF *keyinfo, uchar *key, uint key_length, my_off_t *root, uint comp_flag); -extern int _mi_enlarge_root(MI_INFO *info,MI_KEYDEF *keyinfo,uchar *key, my_off_t *root); -extern int _mi_insert(MI_INFO *info,MI_KEYDEF *keyinfo,uchar *key, - uchar *anc_buff,uchar *key_pos,uchar *key_buff, - uchar *father_buff, uchar *father_keypos, - my_off_t father_page, my_bool insert_last); -extern int _mi_split_page(MI_INFO *info,MI_KEYDEF *keyinfo,uchar *key, - uchar *buff,uchar *key_buff, my_bool insert_last); -extern uchar *_mi_find_half_pos(uint nod_flag,MI_KEYDEF *keyinfo,uchar *page, - uchar *key,uint *return_key_length, - uchar **after_key); -extern int _mi_calc_static_key_length(MI_KEYDEF *keyinfo,uint nod_flag, - uchar *key_pos, uchar *org_key, - uchar *key_buff, - uchar *key, MI_KEY_PARAM *s_temp); -extern int _mi_calc_var_key_length(MI_KEYDEF *keyinfo,uint nod_flag, - uchar *key_pos, uchar *org_key, - uchar *key_buff, - uchar *key, MI_KEY_PARAM *s_temp); -extern int _mi_calc_var_pack_key_length(MI_KEYDEF *keyinfo,uint nod_flag, - uchar *key_pos, uchar *org_key, - uchar *prev_key, - uchar *key, MI_KEY_PARAM *s_temp); -extern int _mi_calc_bin_pack_key_length(MI_KEYDEF *keyinfo,uint nod_flag, - uchar *key_pos,uchar *org_key, - uchar *prev_key, - uchar *key, MI_KEY_PARAM *s_temp); -void _mi_store_static_key(MI_KEYDEF *keyinfo, uchar *key_pos, - MI_KEY_PARAM *s_temp); -void _mi_store_var_pack_key(MI_KEYDEF *keyinfo, uchar *key_pos, - MI_KEY_PARAM *s_temp); +extern int _mi_enlarge_root(MI_INFO *info, MI_KEYDEF *keyinfo, uchar *key, + my_off_t *root); +extern int _mi_insert(MI_INFO *info, MI_KEYDEF *keyinfo, uchar *key, + uchar *anc_buff, uchar *key_pos, uchar *key_buff, + uchar *father_buff, uchar *father_keypos, + my_off_t father_page, my_bool insert_last); +extern int _mi_split_page(MI_INFO *info, MI_KEYDEF *keyinfo, uchar *key, + uchar *buff, uchar *key_buff, my_bool insert_last); +extern uchar *_mi_find_half_pos(uint nod_flag, MI_KEYDEF *keyinfo, + uchar *page, uchar *key, + uint *return_key_length, uchar ** after_key); +extern int _mi_calc_static_key_length(MI_KEYDEF *keyinfo, uint nod_flag, + uchar *key_pos, uchar *org_key, + uchar *key_buff, uchar *key, + MI_KEY_PARAM *s_temp); +extern int _mi_calc_var_key_length(MI_KEYDEF *keyinfo, uint nod_flag, + uchar *key_pos, uchar *org_key, + uchar *key_buff, uchar *key, + MI_KEY_PARAM *s_temp); +extern int _mi_calc_var_pack_key_length(MI_KEYDEF *keyinfo, uint nod_flag, + uchar *key_pos, uchar *org_key, + uchar *prev_key, uchar *key, + MI_KEY_PARAM *s_temp); +extern int _mi_calc_bin_pack_key_length(MI_KEYDEF *keyinfo, uint nod_flag, + uchar *key_pos, uchar *org_key, + uchar *prev_key, uchar *key, + MI_KEY_PARAM *s_temp); +void _mi_store_static_key(MI_KEYDEF *keyinfo, uchar *key_pos, + MI_KEY_PARAM *s_temp); +void _mi_store_var_pack_key(MI_KEYDEF *keyinfo, uchar *key_pos, + MI_KEY_PARAM *s_temp); #ifdef NOT_USED -void _mi_store_pack_key(MI_KEYDEF *keyinfo, uchar *key_pos, - MI_KEY_PARAM *s_temp); +void _mi_store_pack_key(MI_KEYDEF *keyinfo, uchar *key_pos, + MI_KEY_PARAM *s_temp); #endif -void _mi_store_bin_pack_key(MI_KEYDEF *keyinfo, uchar *key_pos, - MI_KEY_PARAM *s_temp); +void _mi_store_bin_pack_key(MI_KEYDEF *keyinfo, uchar *key_pos, + MI_KEY_PARAM *s_temp); -extern int _mi_ck_delete(MI_INFO *info,uint keynr,uchar *key,uint key_length); -extern int _mi_readinfo(MI_INFO *info,int lock_flag,int check_keybuffer); -extern int _mi_writeinfo(MI_INFO *info,uint options); +extern int _mi_ck_delete(MI_INFO *info, uint keynr, uchar *key, + uint key_length); +extern int _mi_readinfo(MI_INFO *info, int lock_flag, int check_keybuffer); +extern int _mi_writeinfo(MI_INFO *info, uint options); extern int _mi_test_if_changed(MI_INFO *info); extern int _mi_mark_file_changed(MI_INFO *info); extern int _mi_decrement_open_count(MI_INFO *info); -extern int _mi_check_index(MI_INFO *info,int inx); -extern int _mi_search(MI_INFO *info,MI_KEYDEF *keyinfo,uchar *key,uint key_len, - uint nextflag,my_off_t pos); -extern int _mi_bin_search(struct st_myisam_info *info,MI_KEYDEF *keyinfo, - uchar *page,uchar *key,uint key_len,uint comp_flag, - uchar * *ret_pos,uchar *buff, my_bool *was_last_key); -extern int _mi_seq_search(MI_INFO *info,MI_KEYDEF *keyinfo,uchar *page, - uchar *key,uint key_len,uint comp_flag, - uchar **ret_pos,uchar *buff, my_bool *was_last_key); -extern int _mi_prefix_search(MI_INFO *info,MI_KEYDEF *keyinfo,uchar *page, - uchar *key,uint key_len,uint comp_flag, - uchar **ret_pos,uchar *buff, my_bool *was_last_key); -extern my_off_t _mi_kpos(uint nod_flag,uchar *after_key); -extern void _mi_kpointer(MI_INFO *info,uchar *buff,my_off_t pos); -extern my_off_t _mi_dpos(MI_INFO *info, uint nod_flag,uchar *after_key); +extern int _mi_check_index(MI_INFO *info, int inx); +extern int _mi_search(MI_INFO *info, MI_KEYDEF *keyinfo, uchar *key, + uint key_len, uint nextflag, my_off_t pos); +extern int _mi_bin_search(struct st_myisam_info *info, MI_KEYDEF *keyinfo, + uchar *page, uchar *key, uint key_len, + uint comp_flag, uchar **ret_pos, uchar *buff, + my_bool *was_last_key); +extern int _mi_seq_search(MI_INFO *info, MI_KEYDEF *keyinfo, uchar *page, + uchar *key, uint key_len, uint comp_flag, + uchar ** ret_pos, uchar *buff, + my_bool *was_last_key); +extern int _mi_prefix_search(MI_INFO *info, MI_KEYDEF *keyinfo, uchar *page, + uchar *key, uint key_len, uint comp_flag, + uchar ** ret_pos, uchar *buff, + my_bool *was_last_key); +extern my_off_t _mi_kpos(uint nod_flag, uchar *after_key); +extern void _mi_kpointer(MI_INFO *info, uchar *buff, my_off_t pos); +extern my_off_t _mi_dpos(MI_INFO *info, uint nod_flag, uchar *after_key); extern my_off_t _mi_rec_pos(MYISAM_SHARE *info, uchar *ptr); -extern void _mi_dpointer(MI_INFO *info, uchar *buff,my_off_t pos); -extern int ha_key_cmp(HA_KEYSEG *keyseg, uchar *a,uchar *b, - uint key_length,uint nextflag,uint *diff_length); -extern uint _mi_get_static_key(MI_KEYDEF *keyinfo,uint nod_flag,uchar * *page, - uchar *key); -extern uint _mi_get_pack_key(MI_KEYDEF *keyinfo,uint nod_flag,uchar * *page, - uchar *key); +extern void _mi_dpointer(MI_INFO *info, uchar *buff, my_off_t pos); +extern uint _mi_get_static_key(MI_KEYDEF *keyinfo, uint nod_flag, + uchar **page, uchar *key); +extern uint _mi_get_pack_key(MI_KEYDEF *keyinfo, uint nod_flag, uchar **page, + uchar *key); extern uint _mi_get_binary_pack_key(MI_KEYDEF *keyinfo, uint nod_flag, - uchar **page_pos, uchar *key); -extern uchar *_mi_get_last_key(MI_INFO *info,MI_KEYDEF *keyinfo,uchar *keypos, - uchar *lastkey,uchar *endpos, - uint *return_key_length); + uchar ** page_pos, uchar *key); +extern uchar *_mi_get_last_key(MI_INFO *info, MI_KEYDEF *keyinfo, + uchar *keypos, uchar *lastkey, uchar *endpos, + uint *return_key_length); extern uchar *_mi_get_key(MI_INFO *info, MI_KEYDEF *keyinfo, uchar *page, - uchar *key, uchar *keypos, uint *return_key_length); -extern uint _mi_keylength(MI_KEYDEF *keyinfo,uchar *key); + uchar *key, uchar *keypos, + uint *return_key_length); +extern uint _mi_keylength(MI_KEYDEF *keyinfo, uchar *key); extern uint _mi_keylength_part(MI_KEYDEF *keyinfo, register uchar *key, - HA_KEYSEG *end); -extern uchar *_mi_move_key(MI_KEYDEF *keyinfo,uchar *to,uchar *from); -extern int _mi_search_next(MI_INFO *info,MI_KEYDEF *keyinfo,uchar *key, - uint key_length,uint nextflag,my_off_t pos); -extern int _mi_search_first(MI_INFO *info,MI_KEYDEF *keyinfo,my_off_t pos); -extern int _mi_search_last(MI_INFO *info,MI_KEYDEF *keyinfo,my_off_t pos); -extern uchar *_mi_fetch_keypage(MI_INFO *info,MI_KEYDEF *keyinfo,my_off_t page, - int level,uchar *buff,int return_buffer); -extern int _mi_write_keypage(MI_INFO *info,MI_KEYDEF *keyinfo,my_off_t page, - int level, uchar *buff); -extern int _mi_dispose(MI_INFO *info,MI_KEYDEF *keyinfo,my_off_t pos, - int level); -extern my_off_t _mi_new(MI_INFO *info,MI_KEYDEF *keyinfo,int level); -extern uint _mi_make_key(MI_INFO *info,uint keynr,uchar *key, - const uchar *record,my_off_t filepos); -extern uint _mi_pack_key(register MI_INFO *info, uint keynr, uchar *key, + HA_KEYSEG *end); +extern uchar *_mi_move_key(MI_KEYDEF *keyinfo, uchar *to, uchar *from); +extern int _mi_search_next(MI_INFO *info, MI_KEYDEF *keyinfo, uchar *key, + uint key_length, uint nextflag, my_off_t pos); +extern int _mi_search_first(MI_INFO *info, MI_KEYDEF *keyinfo, my_off_t pos); +extern int _mi_search_last(MI_INFO *info, MI_KEYDEF *keyinfo, my_off_t pos); +extern uchar *_mi_fetch_keypage(MI_INFO *info, MI_KEYDEF *keyinfo, + my_off_t page, int level, uchar *buff, + int return_buffer); +extern int _mi_write_keypage(MI_INFO *info, MI_KEYDEF *keyinfo, my_off_t page, + int level, uchar *buff); +extern int _mi_dispose(MI_INFO *info, MI_KEYDEF *keyinfo, my_off_t pos, + int level); +extern my_off_t _mi_new(MI_INFO *info, MI_KEYDEF *keyinfo, int level); +extern uint _mi_make_key(MI_INFO *info, uint keynr, uchar *key, + const uchar *record, my_off_t filepos); +extern uint _mi_pack_key(MI_INFO *info, uint keynr, uchar *key, uchar *old, key_part_map keypart_map, - HA_KEYSEG **last_used_keyseg); -extern int _mi_read_key_record(MI_INFO *info,my_off_t filepos,uchar *buf); -extern int _mi_read_cache(IO_CACHE *info,uchar *buff,my_off_t pos, - uint length,int re_read_if_possibly); -extern ulonglong retrieve_auto_increment(MI_INFO *info,const uchar *record); + HA_KEYSEG ** last_used_keyseg); +extern int _mi_read_key_record(MI_INFO *info, my_off_t filepos, uchar *buf); +extern int _mi_read_cache(IO_CACHE *info, uchar *buff, my_off_t pos, + uint length, int re_read_if_possibly); +extern ulonglong retrieve_auto_increment(MI_INFO *info, const uchar *record); -extern uchar *mi_alloc_rec_buff(MI_INFO *,ulong, uchar**); +extern uchar *mi_alloc_rec_buff(MI_INFO *, ulong, uchar **); #define mi_get_rec_buff_ptr(info,buf) \ ((((info)->s->options & HA_OPTION_PACK_RECORD) && (buf)) ? \ (buf) - MI_REC_BUFF_OFFSET : (buf)) #define mi_get_rec_buff_len(info,buf) \ (*((uint32 *)(mi_get_rec_buff_ptr(info,buf)))) -extern ulong _mi_rec_unpack(MI_INFO *info,uchar *to,uchar *from, - ulong reclength); +extern ulong _mi_rec_unpack(MI_INFO *info, uchar *to, uchar *from, + ulong reclength); extern my_bool _mi_rec_check(MI_INFO *info,const uchar *record, uchar *packpos, ulong packed_length, my_bool with_checkum); -extern int _mi_write_part_record(MI_INFO *info,my_off_t filepos,ulong length, - my_off_t next_filepos,uchar **record, - ulong *reclength,int *flag); -extern void _mi_print_key(FILE *stream,HA_KEYSEG *keyseg,const uchar *key, - uint length); -extern my_bool _mi_read_pack_info(MI_INFO *info,pbool fix_keys); -extern int _mi_read_pack_record(MI_INFO *info,my_off_t filepos,uchar *buf); -extern int _mi_read_rnd_pack_record(MI_INFO*, uchar *,my_off_t, my_bool); +extern int _mi_write_part_record(MI_INFO *info, my_off_t filepos, ulong length, + my_off_t next_filepos, uchar ** record, + ulong *reclength, int *flag); +extern void _mi_print_key(FILE *stream, HA_KEYSEG *keyseg, const uchar *key, + uint length); +extern my_bool _mi_read_pack_info(MI_INFO *info, pbool fix_keys); +extern int _mi_read_pack_record(MI_INFO *info, my_off_t filepos, uchar *buf); +extern int _mi_read_rnd_pack_record(MI_INFO *, uchar *, my_off_t, my_bool); extern int _mi_pack_rec_unpack(MI_INFO *info, MI_BIT_BUFF *bit_buff, uchar *to, uchar *from, ulong reclength); -extern ulonglong mi_safe_mul(ulonglong a,ulonglong b); +extern ulonglong mi_safe_mul(ulonglong a, ulonglong b); extern int _mi_ft_update(MI_INFO *info, uint keynr, uchar *keybuf, - const uchar *oldrec, const uchar *newrec, my_off_t pos); + const uchar *oldrec, const uchar *newrec, + my_off_t pos); struct st_sort_info; -typedef struct st_mi_block_info { /* Parameter to _mi_get_block_info */ +typedef struct st_mi_block_info /* Parameter to _mi_get_block_info */ +{ uchar header[MI_BLOCK_INFO_HEADER_LENGTH]; ulong rec_len; ulong data_len; @@ -649,35 +605,37 @@ typedef struct st_mi_block_info { /* Parameter to _mi_get_block_info */ uint offset; } MI_BLOCK_INFO; - /* bits in return from _mi_get_block_info */ - -#define BLOCK_FIRST 1 -#define BLOCK_LAST 2 -#define BLOCK_DELETED 4 -#define BLOCK_ERROR 8 /* Wrong data */ -#define BLOCK_SYNC_ERROR 16 /* Right data at wrong place */ -#define BLOCK_FATAL_ERROR 32 /* hardware-error */ - -#define NEED_MEM ((uint) 10*4*(IO_SIZE+32)+32) /* Nead for recursion */ -#define MAXERR 20 -#define BUFFERS_WHEN_SORTING 16 /* Alloc for sort-key-tree */ -#define WRITE_COUNT MY_HOW_OFTEN_TO_WRITE -#define INDEX_TMP_EXT ".TMM" -#define DATA_TMP_EXT ".TMD" - -#define UPDATE_TIME 1 -#define UPDATE_STAT 2 -#define UPDATE_SORT 4 -#define UPDATE_AUTO_INC 8 -#define UPDATE_OPEN_COUNT 16 - -#define USE_BUFFER_INIT (((1024L*512L-MALLOC_OVERHEAD)/IO_SIZE)*IO_SIZE) -#define READ_BUFFER_INIT (1024L*256L-MALLOC_OVERHEAD) -#define SORT_BUFFER_INIT (2048L*1024L-MALLOC_OVERHEAD) -#define MIN_SORT_BUFFER (4096-MALLOC_OVERHEAD) - -enum myisam_log_commands { - MI_LOG_OPEN,MI_LOG_WRITE,MI_LOG_UPDATE,MI_LOG_DELETE,MI_LOG_CLOSE,MI_LOG_EXTRA,MI_LOG_LOCK,MI_LOG_DELETE_ALL + /* bits in return from _mi_get_block_info */ + +#define BLOCK_FIRST 1 +#define BLOCK_LAST 2 +#define BLOCK_DELETED 4 +#define BLOCK_ERROR 8 /* Wrong data */ +#define BLOCK_SYNC_ERROR 16 /* Right data at wrong place */ +#define BLOCK_FATAL_ERROR 32 /* hardware-error */ + +#define NEED_MEM ((uint) 10*4*(IO_SIZE+32)+32) /* Nead for recursion */ +#define MAXERR 20 +#define BUFFERS_WHEN_SORTING 16 /* Alloc for sort-key-tree */ +#define WRITE_COUNT MY_HOW_OFTEN_TO_WRITE +#define INDEX_TMP_EXT ".TMM" +#define DATA_TMP_EXT ".TMD" + +#define UPDATE_TIME 1 +#define UPDATE_STAT 2 +#define UPDATE_SORT 4 +#define UPDATE_AUTO_INC 8 +#define UPDATE_OPEN_COUNT 16 + +#define USE_BUFFER_INIT (((1024L*512L-MALLOC_OVERHEAD)/IO_SIZE)*IO_SIZE) +#define READ_BUFFER_INIT (1024L*256L-MALLOC_OVERHEAD) +#define SORT_BUFFER_INIT (2048L*1024L-MALLOC_OVERHEAD) +#define MIN_SORT_BUFFER (4096-MALLOC_OVERHEAD) + +enum myisam_log_commands +{ + MI_LOG_OPEN, MI_LOG_WRITE, MI_LOG_UPDATE, MI_LOG_DELETE, MI_LOG_CLOSE, + MI_LOG_EXTRA, MI_LOG_LOCK, MI_LOG_DELETE_ALL }; #define myisam_log(a,b,c,d) if (myisam_log_file >= 0) _myisam_log(a,b,c,d) @@ -687,29 +645,27 @@ enum myisam_log_commands { #define fast_mi_writeinfo(INFO) if (!(INFO)->s->tot_locks) (void) _mi_writeinfo((INFO),0) #define fast_mi_readinfo(INFO) ((INFO)->lock_type == F_UNLCK) && _mi_readinfo((INFO),F_RDLCK,1) -#ifdef __cplusplus +#ifdef __cplusplus extern "C" { #endif - -extern uint _mi_get_block_info(MI_BLOCK_INFO *,File, my_off_t); -extern uint _mi_rec_pack(MI_INFO *info,uchar *to,const uchar *from); + extern uint _mi_get_block_info(MI_BLOCK_INFO *, File, my_off_t); +extern uint _mi_rec_pack(MI_INFO *info, uchar *to, const uchar *from); extern uint _mi_pack_get_block_info(MI_INFO *myisam, MI_BIT_BUFF *bit_buff, MI_BLOCK_INFO *info, uchar **rec_buff_p, File file, my_off_t filepos); -extern void _my_store_blob_length(uchar *pos,uint pack_length,uint length); -extern void _myisam_log(enum myisam_log_commands command,MI_INFO *info, - const uchar *buffert,uint length); +extern void _mi_store_blob_length(uchar *pos, uint pack_length, uint length); +extern void _myisam_log(enum myisam_log_commands command, MI_INFO *info, + const uchar *buffert, uint length); extern void _myisam_log_command(enum myisam_log_commands command, - MI_INFO *info, const uchar *buffert, - uint length, int result); -extern void _myisam_log_record(enum myisam_log_commands command,MI_INFO *info, - const uchar *record,my_off_t filepos, - int result); + MI_INFO *info, const uchar *buffert, + uint length, int result); +extern void _myisam_log_record(enum myisam_log_commands command, MI_INFO *info, + const uchar *record, my_off_t filepos, + int result); extern void mi_report_error(int errcode, const char *file_name); extern my_bool _mi_memmap_file(MI_INFO *info); extern void _mi_unmap_file(MI_INFO *info); extern uint save_pack_length(uint version, uchar *block_buff, ulong length); -extern uint read_pack_length(uint version, const uchar *buf, ulong *length); extern uint calc_pack_length(uint version, ulong length); extern size_t mi_mmap_pread(MI_INFO *info, uchar *Buffer, size_t Count, my_off_t offset, myf MyFlags); @@ -724,7 +680,7 @@ uint mi_state_info_write(File file, MI_STATE_INFO *state, uint pWrite); uchar *mi_state_info_read(uchar *ptr, MI_STATE_INFO *state); uint mi_state_info_read_dsk(File file, MI_STATE_INFO *state, my_bool pRead); uint mi_base_info_write(File file, MI_BASE_INFO *base); -uchar *my_n_base_info_read(uchar *ptr, MI_BASE_INFO *base); +uchar *mi_n_base_info_read(uchar *ptr, MI_BASE_INFO *base); int mi_keyseg_write(File file, const HA_KEYSEG *keyseg); uchar *mi_keyseg_read(uchar *ptr, HA_KEYSEG *keyseg); uint mi_keydef_write(File file, MI_KEYDEF *keydef); @@ -736,23 +692,23 @@ uchar *mi_recinfo_read(uchar *ptr, MI_COLUMNDEF *recinfo); extern int mi_disable_indexes(MI_INFO *info); extern int mi_enable_indexes(MI_INFO *info); extern int mi_indexes_are_disabled(MI_INFO *info); -ulong _my_calc_total_blob_length(MI_INFO *info, const uchar *record); +ulong _mi_calc_total_blob_length(MI_INFO *info, const uchar *record); ha_checksum mi_checksum(MI_INFO *info, const uchar *buf); ha_checksum mi_static_checksum(MI_INFO *info, const uchar *buf); my_bool mi_check_unique(MI_INFO *info, MI_UNIQUEDEF *def, uchar *record, - ha_checksum unique_hash, my_off_t pos); + ha_checksum unique_hash, my_off_t pos); ha_checksum mi_unique_hash(MI_UNIQUEDEF *def, const uchar *buf); int _mi_cmp_static_unique(MI_INFO *info, MI_UNIQUEDEF *def, - const uchar *record, my_off_t pos); + const uchar *record, my_off_t pos); int _mi_cmp_dynamic_unique(MI_INFO *info, MI_UNIQUEDEF *def, - const uchar *record, my_off_t pos); + const uchar *record, my_off_t pos); int mi_unique_comp(MI_UNIQUEDEF *def, const uchar *a, const uchar *b, - my_bool null_are_equal); -void mi_get_status(void* param, int concurrent_insert); -void mi_update_status(void* param); -void mi_restore_status(void* param); -void mi_copy_status(void* to,void *from); -my_bool mi_check_status(void* param); + my_bool null_are_equal); +void mi_get_status(void *param, my_bool concurrent_insert); +void mi_update_status(void *param); +void mi_restore_status(void *param); +void mi_copy_status(void *to, void *from); +my_bool mi_check_status(void *param); void mi_disable_non_unique_index(MI_INFO *info, ha_rows rows); extern MI_INFO *test_if_reopen(char *filename); @@ -766,22 +722,14 @@ my_bool mi_dynmap_file(MI_INFO *info, my_off_t size); void mi_remap_file(MI_INFO *info, my_off_t size); /* Functions needed by mi_check */ -volatile int *killed_ptr(MI_CHECK *param); -void mi_check_print_error _VARARGS((MI_CHECK *param, const char *fmt,...)); -void mi_check_print_warning _VARARGS((MI_CHECK *param, const char *fmt,...)); -void mi_check_print_info _VARARGS((MI_CHECK *param, const char *fmt,...)); -int flush_pending_blocks(MI_SORT_PARAM *param); -int sort_ft_buf_flush(MI_SORT_PARAM *sort_param); -int thr_write_keys(MI_SORT_PARAM *sort_param); +volatile int *killed_ptr(HA_CHECK *param); +void mi_check_print_error _VARARGS((HA_CHECK *param, const char *fmt, ...)); +void mi_check_print_warning _VARARGS((HA_CHECK *param, const char *fmt, ...)); +void mi_check_print_info _VARARGS((HA_CHECK *param, const char *fmt, ...)); #ifdef THREAD pthread_handler_t thr_find_all_keys(void *arg); #endif -int flush_blocks(MI_CHECK *param, KEY_CACHE *key_cache, File file); - -int sort_write_record(MI_SORT_PARAM *sort_param); -int _create_index_by_sort(MI_SORT_PARAM *info,my_bool no_messages, ulong); - +int flush_blocks(HA_CHECK *param, KEY_CACHE *key_cache, File file); #ifdef __cplusplus } #endif - diff --git a/storage/myisam/myisamlog.c b/storage/myisam/myisamlog.c index fafb5140a5e..6e681676c12 100644 --- a/storage/myisam/myisamlog.c +++ b/storage/myisam/myisamlog.c @@ -808,7 +808,7 @@ static int find_record_with_key(struct file_info *file_info, uchar *record) { uint key; MI_INFO *info=file_info->isam; - uchar tmp_key[MI_MAX_KEY_BUFF]; + uchar tmp_key[HA_MAX_KEY_BUFF]; for (key=0 ; key < info->s->base.keys ; key++) { diff --git a/storage/myisam/myisampack.c b/storage/myisam/myisampack.c index 908c32e48d3..3d96dfaafa1 100644 --- a/storage/myisam/myisampack.c +++ b/storage/myisam/myisampack.c @@ -305,7 +305,7 @@ static void usage(void) puts("and you are welcome to modify and redistribute it under the GPL license\n"); puts("Pack a MyISAM-table to take much less space."); - puts("Keys are not updated, you must run myisamchk -rq on the datafile"); + puts("Keys are not updated, you must run myisamchk -rq on the index (.MYI) file"); puts("afterwards to update the keys."); puts("You should give the .MYI file as the filename argument."); diff --git a/storage/myisam/plug.in b/storage/myisam/plug.in index 051ec2d54aa..e92b5e56d7f 100644 --- a/storage/myisam/plug.in +++ b/storage/myisam/plug.in @@ -1,7 +1,7 @@ -MYSQL_STORAGE_ENGINE(myisam,no, [MyISAM Storage Engine], - [Traditional non-transactional MySQL tables]) -MYSQL_PLUGIN_DIRECTORY(myisam, [storage/myisam]) -MYSQL_PLUGIN_STATIC(myisam, [libmyisam.a]) -MYSQL_PLUGIN_MANDATORY(myisam) dnl Default -MYSQL_PLUGIN_DEPENDS_ON_MYSQL_INTERNALS(myisam, [ha_myisam.cc]) +dnl MYSQL_STORAGE_ENGINE(myisam,no, [MyISAM Storage Engine], +dnl [Traditional non-transactional MySQL tables]) +dnl MYSQL_PLUGIN_DIRECTORY(myisam, [storage/myisam]) +dnl MYSQL_PLUGIN_STATIC(myisam, [libmyisam.a]) +dnl MYSQL_PLUGIN_MANDATORY(myisam) dnl Default +dnl MYSQL_PLUGIN_DEPENDS_ON_MYSQL_INTERNALS(myisam, [ha_myisam.cc]) diff --git a/storage/myisam/rt_index.c b/storage/myisam/rt_index.c index e094c302f92..608f91ddf30 100644 --- a/storage/myisam/rt_index.c +++ b/storage/myisam/rt_index.c @@ -534,7 +534,7 @@ static int rtree_insert_req(MI_INFO *info, MI_KEYDEF *keyinfo, uchar *key, DBUG_ENTER("rtree_insert_req"); if (!(page_buf = (uchar*)my_alloca((uint)keyinfo->block_length + - MI_MAX_KEY_BUFF))) + HA_MAX_KEY_BUFF))) { my_errno = HA_ERR_OUT_OF_MEM; DBUG_RETURN(-1); /* purecov: inspected */ @@ -650,7 +650,7 @@ static int rtree_insert_level(MI_INFO *info, uint keynr, uchar *key, DBUG_PRINT("rtree", ("root was split, grow a new root")); if (!(new_root_buf = (uchar*)my_alloca((uint)keyinfo->block_length + - MI_MAX_KEY_BUFF))) + HA_MAX_KEY_BUFF))) { my_errno = HA_ERR_OUT_OF_MEM; DBUG_RETURN(-1); /* purecov: inspected */ diff --git a/storage/myisam/rt_test.c b/storage/myisam/rt_test.c index 7d15afd12ef..6c73c9db13c 100644 --- a/storage/myisam/rt_test.c +++ b/storage/myisam/rt_test.c @@ -105,14 +105,20 @@ static int run_test(const char *filename) int nrecords=sizeof(rt_data)/(sizeof(double)*4);/* 3000;*/ int rec_length=0; int uniques=0; - int i; + int i, max_i; int error; int row_count=0; uchar record[MAX_REC_LENGTH]; uchar read_record[MAX_REC_LENGTH]; int upd= 10; ha_rows hrows; - + + bzero(&uniquedef, sizeof(uniquedef)); + bzero(&create_info, sizeof(create_info)); + bzero(recinfo, sizeof(recinfo)); + bzero(keyinfo, sizeof(keyinfo)); + bzero(keyseg, sizeof(keyseg)); + /* Define a column for NULLs and DEL markers*/ recinfo[0].type=FIELD_NORMAL; @@ -147,7 +153,6 @@ static int run_test(const char *filename) if (!silent) printf("- Creating isam-file\n"); - bzero((char*) &create_info,sizeof(create_info)); create_info.max_rows=10000000; if (mi_create(filename, @@ -194,7 +199,7 @@ static int run_test(const char *filename) create_record(record,i); bzero((char*) read_record,MAX_REC_LENGTH); - error=mi_rkey(file,read_record,0,record+1,0,HA_READ_MBR_EQUAL); + error=mi_rkey(file,read_record,0,record+1,HA_WHOLE_KEY,HA_READ_MBR_EQUAL); if (error && error!=HA_ERR_KEY_NOT_FOUND) { @@ -233,7 +238,8 @@ static int run_test(const char *filename) if (!silent) printf("- Updating rows with position\n"); - for (i=0; i < (nrecords - nrecords/4) ; i++) + /* We are looking for nrecords-necords/2 non-deleted records */ + for (i=0, max_i= nrecords - nrecords/2; i < max_i ; i++) { my_errno=0; bzero((char*) read_record,MAX_REC_LENGTH); @@ -241,7 +247,11 @@ static int run_test(const char *filename) if (error) { if (error==HA_ERR_RECORD_DELETED) + { + printf("found deleted record\n"); + max_i++; /* don't count such record */ continue; + } printf("pos: %2d mi_rrnd: %3d errno: %3d\n",i,error,my_errno); goto err; } @@ -266,7 +276,8 @@ static int run_test(const char *filename) create_record(record, nrecords*4/5); print_record(record,0," search for\n"); - if ((error=mi_rkey(file,read_record,0,record+1,0,HA_READ_MBR_INTERSECT))) + if ((error=mi_rkey(file,read_record,0,record+1,HA_WHOLE_KEY, + HA_READ_MBR_INTERSECT))) { printf("mi_rkey: %3d errno: %3d\n",error,my_errno); goto err; diff --git a/storage/myisam/sort.c b/storage/myisam/sort.c index d505d2633ce..b2507ad8886 100644 --- a/storage/myisam/sort.c +++ b/storage/myisam/sort.c @@ -15,7 +15,7 @@ /* Creates a index for a database by reading keys, sorting them and outputing - them in sorted order through SORT_INFO functions. + them in sorted order through MI_SORT_INFO functions. */ #include "fulltext.h" @@ -487,8 +487,8 @@ ok: int thr_write_keys(MI_SORT_PARAM *sort_param) { - SORT_INFO *sort_info=sort_param->sort_info; - MI_CHECK *param=sort_info->param; + MI_SORT_INFO *sort_info=sort_param->sort_info; + HA_CHECK *param=sort_info->param; ulong length, keys; ulong *rec_per_key_part=param->rec_per_key_part; int got_error=sort_info->got_error; @@ -920,7 +920,7 @@ merge_buffers(MI_SORT_PARAM *info, uint keys, IO_CACHE *from_file, for (buffpek= Fb ; buffpek <= Tb ; buffpek++) { count+= buffpek->count; - buffpek->base= strpos; + buffpek->base= (uchar*) strpos; buffpek->max_keys=maxcount; strpos+= (uint) (error=(int) info->read_to_buffer(from_file,buffpek, sort_length)); @@ -958,7 +958,7 @@ merge_buffers(MI_SORT_PARAM *info, uint keys, IO_CACHE *from_file, { if (!(error=(int) info->read_to_buffer(from_file,buffpek,sort_length))) { - uchar *base=buffpek->base; + uchar *base= buffpek->base; uint max_keys=buffpek->max_keys; VOID(queue_remove(&queue,0)); @@ -990,7 +990,7 @@ merge_buffers(MI_SORT_PARAM *info, uint keys, IO_CACHE *from_file, } } buffpek=(BUFFPEK*) queue_top(&queue); - buffpek->base=(uchar *) sort_keys; + buffpek->base= (uchar*) sort_keys; buffpek->max_keys=keys; do { @@ -1005,7 +1005,7 @@ merge_buffers(MI_SORT_PARAM *info, uint keys, IO_CACHE *from_file, else { register uchar *end; - strpos= buffpek->key; + strpos= (uchar*) buffpek->key; for (end=strpos+buffpek->mem_count*sort_length; strpos != end ; strpos+=sort_length) diff --git a/storage/myisammrg/ha_myisammrg.cc b/storage/myisammrg/ha_myisammrg.cc index 44469a03ce0..67f2d6457c7 100644 --- a/storage/myisammrg/ha_myisammrg.cc +++ b/storage/myisammrg/ha_myisammrg.cc @@ -134,9 +134,11 @@ static const char *ha_myisammrg_exts[] = { }; extern int table2myisam(TABLE *table_arg, MI_KEYDEF **keydef_out, MI_COLUMNDEF **recinfo_out, uint *records_out); -extern int check_definition(MI_KEYDEF *t1_keyinfo, MI_COLUMNDEF *t1_recinfo, +extern int check_definition(MI_KEYDEF *t1_keyinfo, + MI_COLUMNDEF *t1_recinfo, uint t1_keys, uint t1_recs, - MI_KEYDEF *t2_keyinfo, MI_COLUMNDEF *t2_recinfo, + MI_KEYDEF *t2_keyinfo, + MI_COLUMNDEF *t2_recinfo, uint t2_keys, uint t2_recs, bool strict, TABLE *table_arg); static void split_file_name(const char *file_name, @@ -403,14 +405,14 @@ static MI_INFO *myisammrg_attach_children_callback(void *callback_param) */ int ha_myisammrg::open(const char *name, int mode __attribute__((unused)), - uint test_if_locked) + uint test_if_locked_arg) { DBUG_ENTER("ha_myisammrg::open"); DBUG_PRINT("myrg", ("name: '%s' table: 0x%lx", name, (long) table)); - DBUG_PRINT("myrg", ("test_if_locked: %u", test_if_locked)); + DBUG_PRINT("myrg", ("test_if_locked: %u", test_if_locked_arg)); /* Save for later use. */ - this->test_if_locked= test_if_locked; + test_if_locked= test_if_locked_arg; /* retrieve children table list. */ my_errno= 0; @@ -917,7 +919,7 @@ int ha_myisammrg::info(uint flag) { if (table->s->key_parts && mrg_info.rec_per_key) { -#ifdef HAVE_purify +#ifdef HAVE_valgrind /* valgrind may be unhappy about it, because optimizer may access values between file->keys and table->key_parts, that will be uninitialized. @@ -964,7 +966,8 @@ int ha_myisammrg::extra(enum ha_extra_function operation) /* As this is just a mapping, we don't have to force the underlying tables to be closed */ if (operation == HA_EXTRA_FORCE_REOPEN || - operation == HA_EXTRA_PREPARE_FOR_DROP) + operation == HA_EXTRA_PREPARE_FOR_DROP || + operation == HA_EXTRA_PREPARE_FOR_RENAME) return 0; return myrg_extra(file,operation,0); } diff --git a/storage/myisammrg/ha_myisammrg.h b/storage/myisammrg/ha_myisammrg.h index 790aa15e90a..4a65a60d97b 100644 --- a/storage/myisammrg/ha_myisammrg.h +++ b/storage/myisammrg/ha_myisammrg.h @@ -54,8 +54,8 @@ class ha_myisammrg: public handler HA_READ_ORDER | HA_KEYREAD_ONLY); } uint max_supported_keys() const { return MI_MAX_KEY; } - uint max_supported_key_length() const { return MI_MAX_KEY_LENGTH; } - uint max_supported_key_part_length() const { return MI_MAX_KEY_LENGTH; } + uint max_supported_key_length() const { return HA_MAX_KEY_LENGTH; } + uint max_supported_key_part_length() const { return HA_MAX_KEY_LENGTH; } double scan_time() { return ulonglong2double(stats.data_file_length) / IO_SIZE + file->tables; } diff --git a/storage/ndb/include/ndbapi/NdbEventOperation.hpp b/storage/ndb/include/ndbapi/NdbEventOperation.hpp index 437088d2893..0f98a4debef 100644 --- a/storage/ndb/include/ndbapi/NdbEventOperation.hpp +++ b/storage/ndb/include/ndbapi/NdbEventOperation.hpp @@ -178,22 +178,22 @@ public: /** * Check if table name has changed, for event TE_ALTER */ - const bool tableNameChanged() const; + bool tableNameChanged() const; /** * Check if table frm has changed, for event TE_ALTER */ - const bool tableFrmChanged() const; + bool tableFrmChanged() const; /** * Check if table fragmentation has changed, for event TE_ALTER */ - const bool tableFragmentationChanged() const; + bool tableFragmentationChanged() const; /** * Check if table range partition list name has changed, for event TE_ALTER */ - const bool tableRangeListChanged() const; + bool tableRangeListChanged() const; /** * Retrieve the GCI of the latest retrieved event diff --git a/storage/ndb/include/ndbapi/NdbOperation.hpp b/storage/ndb/include/ndbapi/NdbOperation.hpp index 78dbadfd7ab..713c29f028d 100644 --- a/storage/ndb/include/ndbapi/NdbOperation.hpp +++ b/storage/ndb/include/ndbapi/NdbOperation.hpp @@ -779,7 +779,7 @@ public: /** * Get the type of access for this operation */ - const Type getType() const; + Type getType() const; /** @} *********************************************************************/ @@ -1135,7 +1135,7 @@ Return Value Return the Type. Remark: Gets type of access. ******************************************************************************/ inline -const NdbOperation::Type +NdbOperation::Type NdbOperation::getType() const { return m_type; diff --git a/storage/ndb/src/ndbapi/Ndb.cpp b/storage/ndb/src/ndbapi/Ndb.cpp index e6a1c2cfcfd..70e0dc7e2f2 100644 --- a/storage/ndb/src/ndbapi/Ndb.cpp +++ b/storage/ndb/src/ndbapi/Ndb.cpp @@ -1569,9 +1569,11 @@ Ndb::externalizeTableName(const char * internalTableName, bool fullyQualifiedNam register const char *ptr = internalTableName; // Skip database name - while (*ptr && *ptr++ != table_name_separator); + while (*ptr && *ptr++ != table_name_separator) + ; // Skip schema name - while (*ptr && *ptr++ != table_name_separator); + while (*ptr && *ptr++ != table_name_separator) + ; return ptr; } else @@ -1591,7 +1593,9 @@ Ndb::externalizeIndexName(const char * internalIndexName, bool fullyQualifiedNam register const char *ptr = internalIndexName; // Scan name from the end - while (*ptr++); ptr--; // strend + while (*ptr++) + ; + ptr--; // strend while (ptr >= internalIndexName && *ptr != table_name_separator) ptr--; diff --git a/storage/ndb/src/ndbapi/NdbEventOperation.cpp b/storage/ndb/src/ndbapi/NdbEventOperation.cpp index 5c6ed562dcc..1353ca40e95 100644 --- a/storage/ndb/src/ndbapi/NdbEventOperation.cpp +++ b/storage/ndb/src/ndbapi/NdbEventOperation.cpp @@ -96,22 +96,22 @@ NdbEventOperation::hasError() const return m_impl.m_has_error; } -const bool NdbEventOperation::tableNameChanged() const +bool NdbEventOperation::tableNameChanged() const { return m_impl.tableNameChanged(); } -const bool NdbEventOperation::tableFrmChanged() const +bool NdbEventOperation::tableFrmChanged() const { return m_impl.tableFrmChanged(); } -const bool NdbEventOperation::tableFragmentationChanged() const +bool NdbEventOperation::tableFragmentationChanged() const { return m_impl.tableFragmentationChanged(); } -const bool NdbEventOperation::tableRangeListChanged() const +bool NdbEventOperation::tableRangeListChanged() const { return m_impl.tableRangeListChanged(); } diff --git a/storage/ndb/src/ndbapi/NdbEventOperationImpl.cpp b/storage/ndb/src/ndbapi/NdbEventOperationImpl.cpp index dff953923fe..ed60650e33e 100644 --- a/storage/ndb/src/ndbapi/NdbEventOperationImpl.cpp +++ b/storage/ndb/src/ndbapi/NdbEventOperationImpl.cpp @@ -658,22 +658,22 @@ NdbEventOperationImpl::stop() DBUG_RETURN(r); } -const bool NdbEventOperationImpl::tableNameChanged() const +bool NdbEventOperationImpl::tableNameChanged() const { return (bool)AlterTableReq::getNameFlag(m_change_mask); } -const bool NdbEventOperationImpl::tableFrmChanged() const +bool NdbEventOperationImpl::tableFrmChanged() const { return (bool)AlterTableReq::getFrmFlag(m_change_mask); } -const bool NdbEventOperationImpl::tableFragmentationChanged() const +bool NdbEventOperationImpl::tableFragmentationChanged() const { return (bool)AlterTableReq::getFragDataFlag(m_change_mask); } -const bool NdbEventOperationImpl::tableRangeListChanged() const +bool NdbEventOperationImpl::tableRangeListChanged() const { return (bool)AlterTableReq::getRangeListFlag(m_change_mask); } diff --git a/storage/ndb/src/ndbapi/NdbEventOperationImpl.hpp b/storage/ndb/src/ndbapi/NdbEventOperationImpl.hpp index 3d71146588d..c027226584c 100644 --- a/storage/ndb/src/ndbapi/NdbEventOperationImpl.hpp +++ b/storage/ndb/src/ndbapi/NdbEventOperationImpl.hpp @@ -361,10 +361,10 @@ public: NdbBlob *getBlobHandle(const NdbColumnImpl *, int n); int readBlobParts(char* buf, NdbBlob* blob, Uint32 part, Uint32 count); int receive_event(); - const bool tableNameChanged() const; - const bool tableFrmChanged() const; - const bool tableFragmentationChanged() const; - const bool tableRangeListChanged() const; + bool tableNameChanged() const; + bool tableFrmChanged() const; + bool tableFragmentationChanged() const; + bool tableRangeListChanged() const; Uint64 getGCI(); Uint32 getAnyValue() const; Uint64 getLatestGCI(); diff --git a/storage/ndb/src/ndbapi/NdbRecAttr.cpp b/storage/ndb/src/ndbapi/NdbRecAttr.cpp index 38ca14085f0..3b0329dd7ac 100644 --- a/storage/ndb/src/ndbapi/NdbRecAttr.cpp +++ b/storage/ndb/src/ndbapi/NdbRecAttr.cpp @@ -248,7 +248,8 @@ ndbrecattr_print_formatted(NdbOut& out, const NdbRecAttr &r, { const Uint32 *buf = (Uint32 *)r.aRef(); int k = (length+31)/32; - while (k > 0 && (buf[--k] == 0)); + while (k > 0 && (buf[--k] == 0)) + ; out.print("%X", buf[k]); while (k > 0) out.print("%.8X", buf[--k]); diff --git a/storage/ndb/src/ndbapi/TransporterFacade.hpp b/storage/ndb/src/ndbapi/TransporterFacade.hpp index 23fea8792f7..9a4347b333c 100644 --- a/storage/ndb/src/ndbapi/TransporterFacade.hpp +++ b/storage/ndb/src/ndbapi/TransporterFacade.hpp @@ -365,8 +365,8 @@ inline bool TransporterFacade::get_node_stopping(NodeId n) const { const ClusterMgr::Node & node = theClusterMgr->getNodeInfo(n); - return (!node.m_state.getSingleUserMode() && - (node.m_state.startLevel == NodeState::SL_STOPPING_1) || + return ((!node.m_state.getSingleUserMode() && + (node.m_state.startLevel == NodeState::SL_STOPPING_1)) || (node.m_state.startLevel == NodeState::SL_STOPPING_2)); } diff --git a/storage/pbxt/AUTHORS b/storage/pbxt/AUTHORS new file mode 100644 index 00000000000..3c5c3db6db8 --- /dev/null +++ b/storage/pbxt/AUTHORS @@ -0,0 +1,4 @@ +Paul McCullagh +paul.mccullagh@primebase.org +http://www.primebase.org +http://pbxt.blogspot.com diff --git a/storage/pbxt/ChangeLog b/storage/pbxt/ChangeLog new file mode 100644 index 00000000000..958bcd81cd1 --- /dev/null +++ b/storage/pbxt/ChangeLog @@ -0,0 +1,689 @@ +PBXT Release Notes +================== + +------- 1.0.08 RC - Not yet released + +RN233: Fixed bugs which occur when PBXT is used together with PBMS (BLOB Streaming engine). + +RN232: Merged Drizzle-specific changes into the main tree. + +RN231: Fixed a bug that caused bad performance as the number of threads increased. This occurred when the number of open table handles exceeded 'table_open_cache', and MySQL started closing open table handlers. PBXT was flushing a table when all table handlers were closed. PBXT will now only do this when the FLUSH TABLES statement is used. + +RN230: Improved efficiency of conflict resolution: Implemented a queue for threads waiting for a lock. Threads no longer poll to take a lock. If a temp lock is granted because of an update, then the thread granted the temp lock will also wait for the transaction that did the update to quit. + +RN229: Fixed bug #313391: LOAD DATA ... REPLACE broken. + +RN228: Fixed bug #341115: 'Out of memory' error (a bug in key comparison algorithm). + +RN227: Changed conflict handling to use spin locks and improve efficiency. + +RN226: Fixed bug #340316: Issue with bigint unsigned auto-increment field. + +RN225: Fixed bug #308557: UPDATE fails to match all rows in a transactional scenario. + +RN224: Fixed a deadlock which could occur during table scans. + +RN223: Index scans now use handles to cache buffers instead of making a copy of the index page. The handles are "copy-on-write". + +RN222: Fixed a bug that caused the server to hang on startup if PBXT ran out of record cache while waiting for the sweeper to complete. + +RN221: Fixed an index recovery bug. This occurred if the server crashed after operating in low index cache sitations. + +RN220: Improved index selectivity estimation: added scanning from the end of index backwards. + +RN219: Fixed a problem: during intersected range scan not all fields were returned by engine to MySQL. + +RN218: Changed the way row locking (used by SELECT FOR UPDATE) works. Previously we locked a group of rows at once (although there were many groups). However, this caused conflicts even when the same rows were not locked. We now locks individual rows. + +RN217: Fixed bug #315564: Rollbacked inserts remain permanently in table. + +RN216: Added lock tracing. In DEBUG mode, each thread has a list of locks (semaphores, mutexes, r/w locks that it holds). + +RN215: Fixed a bug that caused a crash during restart if an index file was flushed during recovery. + +RN214: Fixed bug #310184: Deadlock when trying to wake up transactions + +RN213: Fixed an index corruption bug on SPARC Solaris. Note this error will occur on any machine that does not use the x86 (little endian) byte order. + +------- 1.0.07 RC - 2008-12-15 + +RN212: Fixed build problems on NetBSD. + +RN211: Fixed build problems on FreeBSD. + +RN210: Fixed build problems on OpenSolaris. + +RN209: Added handling of the foreign_key_checks system flag. + +RN208: xtstat will now automatically reconnect if the connection to server is lost. + +RN207: Foreign key references are now checked on CREATE TABLE. + +RN206: Fixed a crash if inserting into a table that has an FK that references a column that has no index on it. + +RN205: Added processing of foreign key action SET DEFAULT. + +RN204: Fixed an index recovery problem: unswept index entries were not recovered correctly + +RN203: Fixed foreign key bug: REPLACE fails with 'on delete cascade' + +RN202: Fixes and updates to tests, now all tests pass on windows and linux. + +RN201: Fixed ref-counting for mmapped files. + +RN200: Fixed an index recovery problem: unswept index entries were not recovered correctly . + +RN199: Recovery now takes place on plug-in startup. Previously recovery occurred when the first PBXT table was accessed. + +RN198: Fixed a recovery bug that caused index entries to get out of sync with the data file. + +RN197: Improved the efficiency of group commit. + +RN196: Changed checkpointing so that it now works during idle time. Every record, row or index file fllush now also contributes to the checkpoint (fuzzy checkpointing). Checkpointing is forced to complete after about 50% of the checkpoint threshold in order to ensure the correct maximum for log reading on recovery. + +RN195: Fixed scheduling bug that caused sweeper to get behind with the cleanup, which caused performance problems in high conflict situations. Foreground threads will now wait if the sweeper gets too far behind. + +RN194: Created the xtstat program which monitors the internal performance of PBXT. Run xtstat --help for more details information of the output. + +RN193: Implemented the pbxt.statistics virtual table. The statistics table returns information about the internal activity of the engine. This includes I/O byte counts, cache hit counts and usage, commit count, etc. + +RN192: Due to timing issues in the engine API it could happen that the client received an OK for a committed transaction before the transaction was actually committed. This problem has been fixed. + +RN191: Fixed a bug that caused a hang when conflicts occured while reading a covering index. + +RN190: Previously the sweeper delayed deletion of transaction structures until all transactions that were running during sweeping have quit. This is now handled by the same code that fixed the bug in RN189. + +RN189: Fixed a bug that could cause a row to go missing due to a visibility issue. + +RN188: Fixed a bug which ocurred when using CREATE TABLE ... AVG_ROW_LENGTH=x, and the table contained BLOBs. In this case, alter table corrupted the table. + +RN187: Windows now stores paths in the location file in UNIX format by converting all '\' characters to '/'. Note that the location file is only cross-platform if the paths are relative (which is the default). + +RN186: Set version number to 1.0.07. + +------- 1.0.06 Beta 2 - 2008-11-06 + +RN185: Disabled support for INSERT DELAYED because of MySQL bug #40505 + +RN184: Implemented info(flag == HA_STATUS_AUTO) engine API call. This call returns the next value that will be assigned as auto-increment value on the table. + +RN183: Turned off streaming on Windows (see XT_STREAMING macro in sources) + +RN182: Switch code base to the latest version of BLOB streaming engine (PBMS): www.blobstreaming.org. + +RN181: Updated pbxt-test-run default parameters (--force is on, --default-storage-engine is pbxt, --base-dir is set according to config) + +RN180: PBXT can now cope with a missing .xti file (the file that contains the table indexes). This file can be regenerated using REPAIR TABLE. + +RN179: On recovery PBXT now creates a filed called 'recovery-progress' in the pbxt database. The recovery percentage complete is written to this file as recovery progresses. Note that this file will not be created if no recoery is necessary or if PBXT estimates that it will read less then 10MB to do recovery. + +RN178: Fixed a problem in CHECK TABLE that caused memory corruption for fixed-size records + +RN177: Added "crash debugging". When enabled, crash debugging does the following: + - Create a core dump on Windows if the server crashes. + - Make a backup copy of the datadir directory before recovery if the server crashes. + - Keep at least 5 of the previous transaction logs. +Currently crash debugging is disabled by default. To disable, create a file called 'no-debug' in the pbxt database folder, and restart the server. When crash debugging is disabled by default, it can be enabled by creating a file called 'crash-debug; in the pbxt database folder. + +RN176: Fixed a bug: a lock was not released appropriately + +RN175: Fixed some debug assertions + +RN174: Fixed some of test/mysql-test tests + +RN173: Fixed a RENAME TABLE bug, that prevented index files from being properly recreated + +RN172: Added the file ./pbxt/lock-pid. This file is locked while the server is running, and contains the process of the server. PBXT will return an error on startup if the file is locked or the process is still running in order to prevent a second server from being started. + +RN171: Implemented the AVG_ROW_LENGTH table attribute. When set, this value determines the size of the fixed length data component of a record. Normally this size is estimated depending on the column definitions. The command CHECK TABLE dumps the current average row length to the log. This can be used to find a suitable value for AVG_ROW_LENGTH. + +RN170: Changed configure so that debug/optimize flags set for building the engine override the flags set for MySQL. If --with-debug is not specified, then the engine will use the flags set when building MySQL. If MySQL was built with --with-debug=full, the DEBUG will be defined for the engine. When building the engine, the following flags can be set: + yes - Debug symbols enabled, no optimization, DEBUG not defined. + full - Debug symbols enabled, no optimization, DEBUG defined. + only - Debug symbols enabled, MySQL flags used, DEBUG not defined. + prof - Profile code enabled, optimization on, DEBUG not defined. + no - No debug symbols, optimization on, DEBUG not defined. + +RN169: Used MySQL root Makefile instead of config.status in order to extract settings (such as CFLAGS and CXXFLAGS) for the PBXT build. + +RN168: Fixed Windows build after merging changes for Drizzle. + +RN167: Fixed "This table requires primary key" error in sql-bench. + +RN166: Fixed threading problems that caused crashes in sql-bench. + +RN165: Added sql-bench to pbxt source tree. + +RN164: Ported PBXT to Drizzle. To compile for Drizzle DRIZZLED must be defined on the command line. The -drz.am and -drz.in files are must be used when PBXT is embedded in Drizzle. + +RN163: Added "make test" build step. Running "make test" from the root of pbxt source tree will launch test/mysql-test/pbxt-test-run.pl with appropriate options to execute the pbxt functional test suite. On Windows where +pbxt is statically linked into mysql server binary pbxt testing works by going to test/mysql-test directory and running ./pbxt-test-run.pl with --base-dir argument pointing to a mysql source tree (mysql binaries are taken +from there) and passing the rest of usual arguments (--force --mysqld=--default-storage-engine=pbxt) + +RN162: The 'pbxt' database must now be dropped explicitly. It is automatically created when the first PBXT table is created. After that, the pbxt database can be dropped once all PBXT tables have been dropped. Dropping the pbxt database will also cause all transaction (pbxt/system directory) and data logs (pbxt/data directory) to also be deleted. + +RN161: Added pbxt.location system table. This table can only be dropped when all PBXT tables have been deleted. Dropping the system table will cause all transaction (pbxt/system directory) and data logs (pbxt/data directory) to also be deleted. + +RN160: Made changes to run with MySQL 6.0.6. + +RN159: Changes to configure: added --with-plugindir=<path>, which should be used to specify the plugin directory. This means that --libdir should no longer be used. For backwards compatibility configure will still recognize this options if the path ends with 'plugin'. + +Also updated --help, to include all options, and better desciptions of the options. + +The configure options are now as follows: + +--with-mysql=<path> - (Required) It specifies the path to the MySQL source tree. The source should already be built. All other options will be taken from the MySQL build by default. +--with-debug=yes/no - (Optional) Specify if then engine should be built with different debug options to the MySQL source tree. +--with-plugindur=<path> - (Optional) Specify an alternative installation directory for the plugin. By default it will be installed in the plugin directory of the MySQL installation. + + +RN158: Added support for core dumps on Windows. This can be enabled by defining XT_COREDUMP. On by default at the moment. If the server crashes a file called PBXTCore00000001.dmp will be created in the data directory. This file can be openned using MS VS. + +RN157: Fixed a compile problem with tv_nsec which is not supported on all platforms. + +RN156: Updated tests to run with MySQL 5.1.28. + +RN155: Errors during cascade update of VARCHAR values with trailing spaces + +RN154: Fixed a bug: impossible to create a foreign key that referenced an ENUM or SET column + +RN153: Fixed a bug that caused the following problems: #1. Foreign keys: crash if update cascade and autocommit=0 #2. Foreign keys: crash if update cascade and multi-level recursion + +RN152: Fixed missing information about foreign keys in I_S.table_constraints and I_S.referential_constraints + +------- 1.0.05 Beta - 2008-08-30 + +RN151: "Quick config": It is now possible to configure the engine by just specifying the mysql source code tree (the --with-mysql option). The --libdir and --with-debug setting will be deduced automatically. + +RN150: Added system variable pbxt_sweeper_priority, 0 = low (default), 1 = normal (same as user threads), 2 = high. The sweeper cleans up deleted records (deleted records also result from an update). If allowed to accumulate, these records can slow searches. Higher priority for the sweeper is recommended on systems with 4 or more cores. + +RN149: Record cleanup is now initiated if a deleted record is found, and the transaction that deleted the record has ended. Since waking up the sweeper is an expensive operation, normally the sweeper will run every 1/10th of a second. + +RN148: Fixed a bug which caused transaction starvation (one transaction was constantly locked out) during high conflict updates. This lead to cleanup of records not being done, which lead to a general slow down. + +RN147: Fixed a problem with TRUNCATE TABLE: a failed TRUNCATE TABLE could put the engine into an invalid state that later caused a crash + +RN146: Fixed a bug that caused the error: "-49: Record format unknown, either corrupted or upgrade required". + +RN145: Added pbxt_db_offline_log_function system variable, 0 = recycle logs (default), 1 = delete logs (default on Mac OS X), 2 = keep logs. + +------- 1.0.04 Alpha - 2008-08-02 + +RN144: Completed port and testing of Windows version. + +RN143: Fixed a bug which caused the free-er thread to hang. This was a result of an invalid operation ID, which was the result of the checkpointer flushing the table at the same time as a foreground thread. + +RN142: The fast RW/mutex lock can now handle nested calls. This is possible during a sequential scan. + +RN141: The normal behavior in MySQL is that an auto-increment values will be re-issued if you delete the row containing the current maximum auto-increment value and then restart the server. To prevent this you can use ALTER TABLE my_table AUTO_INCREMENT = <current-max-auto-increment> + 1, before deleting the current maximum auto-increment value. + +A new system variable, pbxt_auto_increment_mode, has been added so that this work around is not necessary. When set to 0 (the default), auto-increment works as described above. When set to 1, the AUTO_INCREMENT value of the table is automatically to prevent previously issued auto-increment values being returned. + +However, if the server crashes, a gap of up to 100 unique values can result, because the table AUTO_INCREMENT value is incremented in steps of 100. + +RN140: Index statistics are now automatically recalculated when the table row count exceeds 200. + +RN139: Fixed a bug that caused index corruption, error: "int idx_push(index_xt.cc:172) -2: Core B-tree too deep". + +RN138: Handle startup and recovery when an index is corrupted. + +RN137: Fixed a bug in the zero wait R/W lock that caused the lock to fail (the state is extremely volatile, and must be written to memory after increment). + +RN136: Fixed a bug that cause the error "int xt_pwrite_file(filesys_xt.cc:789) errno (14): Bad address". + +RN135: Fixed TRUNCATE TABLE that did not work correctly when the table contained BLOBs stored in the BLOB streaming engine (www.blobstreaming.org). + +RN134: Fixed a bug that caused duplicate rows to be returned from an index scan (using a SELECT FOR UPDATE) if a concurrent update was done. + +RN133: Optimised PBXT for multi-processor scale-up. This mostly involved using different types of locks instead of the standard pthread mutex and reader/writer locks [TODO: 0038]. + +------- 1.0.03 Alpha - 2008-05-30 + +RN132: Fixed bug when using PBXT in conjunction with the BLOB streaming engine (www.blobstreaming.org). Uploaded BLOBs could not be inserted into a table. + +RN131: Fixed wait for background processes on shutdown. Shutdown will wait a maximum of 16 seconds for each process. + +RN130: Fixed calculation of bytes to be read for recovery. + +RN129: Fixed bug in cleanup of unterminated transactions. + +RN128: The writer will now start working when one of the following is true: +- it is time for a checkpoint, +- the log cache is almost full, +- the free'er is waiting for the writer, +- there is no other activity. + +RN127: Fixed checkpoint frequency. Checkpointing is now done correctly after 'pbxt_checkpoint_frequency' bytes. + +RN126: Implemented index consistent write [TODO: 0050]. + +RN125: Implemented memory mapping for row pointer (.xtr) and handle data files (.xtd). + +RN124: Index files now use direct I/O. + +------- 1.0.02 Alpha - 2008-04-25 + +RN123: Fixed compile errors with MySQL 5.1.24. + +------- 1.0.01 Alpha - 2008-03-28 + +RN122: ++++ NOTE: This version is not compatible with older versions of PBXT ++++. + +RN121: Transaction logs are now global so that multi-database statements are now possible. This makes it also possible to work PBXT temporary tables. + +RN120: Transaction logs pre-allocated and recycled. + +RN119: Transaction log writes on 512 byte boundaries only. + +------- 1.0.00 Alpha - 2008-03-10 + +This version has alpha status because of the large number of changes done for full durability. + +RN118: ++++ NOTE: This version is incompatible to older versions of PBXT ++++. + +RN117: Documentation now avaliable at http://www.primebase.org/documentation. + +RN116: Corrected the plug.in file so that PBXT compiles when dropped into the storage directory in the MySQL source tree. + +RN115: Compiled and tested with MySQL 5.1.23. + +RN114: Increased index block size. Minimum is now 4K. Default is 16K. + +RN113: Calculate index selectivity to return a more accurate value from records_in_range(). NOTE: FLUSH TABLESl will update the index statistics, after data has been inserted or updated. + +RN112: Optimized table storage, saving 8 bytes per row. + +RN111: Optimized search on keys containing 2 or 3 not null integer values. + +RN110: Optimization: store the row ID in the index so that an index entry can be verified as current without loading the record. This is necessary to optimize an access with index coverage. + +RN109: Optimization: only load the record extended data if required. + +RN108: Implemented SHOW ENGINE PBXT STATUS; + +RN107: Added the following system variables: + +pbxt_index_cache_size - The amount of memory allocated to the index cache, used only to cache index data +pbxt_record_cache_size - The amount of memory allocated to the record cache used to cache table data +pbxt_log_cache_size - The amount of memory allocated to the transaction log cache used to cache on transaction log data +pbxt_log_file_threshold - The size of a transaction log before rollover, and a new log is created +pbxt_transaction_buffer_size - The size of the global transaction log buffer (the engine allocates 2 buffers of this size) +pbxt_log_buffer_size - The size of the buffer used to cache data from transaction and data logs during sequential scans, or when writing a data log +pbxt_checkpoint_frequency - The amount of data written to the transaction log before a checkpoint is performed +pbxt_data_log_threshold - The maximum size of a data log file +pbxt_garbage_threshold - The percentage of garbage in a data log file before it is compacted + +RN106: PBXT now compiles for MySQL 6.0.3. + +RN104: Updates now locks a record temporarily. This prevents most "record changed" errors, however, it makes UPDATE statements a type of "committed read". This means that you may update a different value to that which you selected in repeatable read mode. To avoid this, use SELECT FOR UPDATE if you plan to UPDATE records after reading. + +RN103: Implemented SELECT FOR UPDATE. This is implemented by turning SELECT FOR UPDATE into a type of "committed read". This means that, if you do a SELECT followed by a SELECT FOR UPDATE you can get different results, even in repeatable read mode. + +RN102: Implemented recovery of index entries. Note: indexes are not yet fully consistent. This means that index can become currupted due to a crash. Data, however, cannot be lost. The indices can be rebuild using REPAIR TABLE. + +RN101: Writing and flushing of a single transaction write-ahead log. + +RN100: Automatic rollover of transaction logs as they become full. + +RN99: Implementation of the transaction log cache. + +RN98: Group commit. + +RN97: Implementation of the writer thread that applies changes in the transaction log to the database. + +RN96: Implementation of the checkpointer thread that periodically flushes the database and writes a checkpoint which determines the recovery start point. + +RN95: Implementation of the free'er thread that is responsible for keeping the record cache at a preset level. + +RN94: Modifications to the record cache so that rows are stored in pages, in order to speed up sequence access. + +RN93: Implemented the recovery process which applies changes written to the log that are not in the database, on startup. + +RN92: Modification of the sweeper thread which cleans up rolled-back transactions and deleted data, to use the new transaction log format. + +RN91: Modifications to the data logs so that they use the same record structure as the transaction logs. + +RN90: The data logs are now managed "per database" in order to minimize the work done to flush and commit a transaction. + +RN89: Implementation of a file handle pool for the data logs. + +------- 0.9.91 Beta - 2007-10-30 + +RN88: The format of the URL genearated by MyBS has been changed. The format of the BLOB URLs is now as follows: + +'~*' <db-name> '/' <type-char> <table-id> '-' <blob-id> '-' <access-code> '-' <server-id> + +Where <type-char> is '_' or '~'. + +Examples: ~*test/_11-128-fbd590b-0, ~*test/~1-524-3dc45b09-0 + +In other words, the characters '>' has been replace by '*', '^' has been replace by '_' and ':' has been replace by '~'. The reason for this is that the characters '>' and '^' are not allowed in URLs, and must be URL-encoded. The character ':' is reserved, but allowed. + +NOTE: This change makes this version incompatible with previous versions of MyBS. If you have a table with BLOB URLs, you can upgrade the URLs as follows: + +UPDATE blob_table SET blob_col = REPLACE(REPLACE(blob_col, '~>', '~*'), '/:', '/~'); + +Replacing '^' is not necessary because BLOB URLs with '^' should not appear in tables. + +------- 0.9.90 Beta - 2007-10-17 + +RN87: Corrected stack trace of errors passed through the BLOB streaming API. + +RN86: Added new engine API accessor functions that appeared in 5.1.21 (thanks Stewart). + +RN85: Added plug.in file. PBXT now compiles when dropped into the storage directory of the MySQL build tree. However, you have rebuild configure. For example: + +rm -rf autom4te.cache/ +aclocal +autoconf +autoheader +automake -a +./configure --help +./configure --with-plugins=max --without-innodb --prefix=/usr/local/mysql --with-debug=full + +NOTE: ./configure --help should show that the PBXT has been included. + +RN84: Fixed several problems with shutdown of PBXT in combiniation with MyBS. + +------- 0.9.89 Beta - 2007-08-17 + +RN83 (2007-08-21): Fixed a crash due to a compile bug that does not like the contruct *((xtWordPS *) &(v)) = (xtWordPS) (x) (macro allocr_() and alloczr_()). + +RN82: It is now possible to insert non-URL values into a LONGBLOB field, in the previous version the generated an "Invalid URL" error. Such values can be retrieved as a stream using a field reference. + +RN81: Fixed a bug that caused PBXT to crash during certina operations when MyBS was not installed. + +RN80: Set engine as capable of row-level replication, but not as statement replication. Statement replication does not work because MVCC is not serializable. + +------- 0.9.88 Beta - 2007-07-25 + +RN79: Made some corrections in order to compile with MySQL 5.1.20. + +RN78: Support for the features of the MyBS BLOB Streaming engine, version 0.5 Alpha. + +RN77: Bugfix: The server crashes during BLOB data handling. The reason is the table field structure is shared, and may not be changed. + +------- 0.9.87 Beta - 2007-06-19 + +RN76: The major feature of this release is support for the BLOB Streaming Engine. The current version enables the download of specific BLOB columns via the Streaming Engine. For example: + +use test; +CREATE TABLE notes_tab ( + n_id INTEGER PRIMARY KEY, + n_text BLOB +) ENGINE=pbxt; +INSERT notes_tab VALUES (1, "This is a BLOB streaming test!"); + +The URL: + +http://localhost:8080/test/notes_tab/n_text/n_id=1 + +will return the value "This is a BLOB streaming test!" + +RN75: Bugfix: MySQL prints error: "Plugin 'PBXT' will be forced to shutdown". This error was caused by the plug-in having a reference to itself. + +RN74: Added system variable pbxt_index_cache_size and pbxt_record_cache_size. These variable can now be set on the mysqld command line (for example: --pbxt_record_cache_size=50MB). The values are also displayed by SHOW VARIABLES. + +------- 0.9.86 Beta - 2007-04-07 + +RN74: ++++ NOTE: This version is incompatible to older versions of PBXT ++++. + +In order to upgrade, install the older version of PBXT. Convert all tables to MyISAM using ALTER TABLE t1 ENGINE=MyISAM. Then install the new version of PBXT and convert back using ALTER TABLE t1 ENGINE=PBXT. + +RN73: Each table will now use a maximum of 4 data log files. This means a maximum of 7 files per table. The minimum is 3 for tables that do not have a variable field that exceeds about 40 bytes in size. This means that under Linux PBXT requires a maximum of 7 file handles per table used. Windows lock of pread/pwrite (atomic seek and read/write) functions means it requires a file handler per file per open table handler. [TODO: 0044] + +RN72: All threads now write to the same data log file. Recovery and compaction take this fact into account. Each thread still writes its own transaction log. + +RN71: Removed all directory scans when creating and dropping table. Increased the table limit to 10000. + +RN70: Changed locking to avoid a deadlock when TRUNCATE TABLE is used together with other DML. + +RN69: procedures and functions are now considered atomic, and execute in a single transaction. + +RN68: Bug fixed: all files are now correctly flushed before commit. + +------- 0.9.85 Beta - 2007-03-15 + +RN67: Changed the implementation of the pushsr_ and allocr_ macros because "*((void **) &(v) = " caused a crash due to a compiler error on some platforms (thanks Luciano for your help on this one and RN66). + +RN66: Fixed a bug that caused PBXT to corrupt the index file when the size exceeded 4GB. [TODO: 0031] + +RN65: PBXT now runs under Windows. This source tree must be placed in the MySQL source storage directory in order to compile. Further details of how to build are in the windows-readme.txt file. [TODO: 0027] + +RN64: Improved speed of table lookup by ID after a table has been deleted. The sweeper needs to ignore these records. Scanning the directory each time was too slow. + +RN63: Added checking for repeat update of a record in a statement. + +RN62: Committed read no longer blocks due to a change made by another transaction (the XT_REPEATABLE_READ_BLOCKS define, turns blocking on). + +RN61: Avoid checking for duplicates if an index is not modified by an update. + +RN60: Records updated repeatedly by a transaction are now updated in place. [TODO: 0040] + +------- 0.9.8 Beta - 2007-01-30 + +RN59: Reduced the number of file handles used to a maximum of one per file. This assumes that pread() and pwrite() allows multiple threads to use the same file handle (according to my tests, this is the case). + +RN58: Added the configure flag --with-debug=only which compiles a version of the plug-in with debug symbols that will link to an non-debug MySQL server. + +RN57: Changed error number returned on lock from 1205 (lock timeout) to 1020 (optimistic lock failure). + +RN56: Added UNIX environment variable for PBXT system parameters. These must be set before starting mysqld, for example: + +setenv pbxt_index_cache_size 400MB +setenv pbxt_record_cache_size "1 GB" + +Values are in bytes unless one of the following units is specified: GB, MB, Kb + +RN55: Fixed a bug which prevented VARCHAR values from being compressed correctly when stored in variable length rows. + +RN54: Fixed a bug which caused a crash when PBXT was used with MySQL 5.1.14. This bug also caused data to be corrupted on insert. + +RN53: Set query caching mode to transactional. [TODO: 0027] + +RN52: Added conditions so that the engine compiles with MySQL 5.1.14 and 5.1.13. + +------- 0.9.74 Beta - 2006-12-14 + +RN51: DELETE FROM <table>; is no longer implemented by re-creating the table. This statement now works by deleting all rows. TRUNCATE is implemented as before, by re-creating the table. + +RN50: The test scripts innodb.test and innodb-mysql.test have been modified to run with PBXT. + +RN49: [TODO: 0020] Implemented foreign keys. Functionality is identical to InnoDB with 2 exceptions: + +* Data types of referenced columns must be an exact match (e.g. you cannot mix VARCHAR and CHAR values). +* Currently an exact matching index is required on referenced columns (i.e. the index may not have more columns that the columns used in the foreign key definition). + +Also note the following: + +* It is possible to create foreign keys that reference non-existent tables or columns. An error will occur when updating a table with an incorrect foreign key declaration. +* If you alter the data-type of a column referenced by a foreign key set you need to set foreign_key_checks=0; or an error will occur. + +RN48: Fixed a bug in the implementation of indexes on ENUM and SET types. + +RN47: Fixed a bug that caused a crash when an index was place on a BLOB column, and data was retrieved from the index directly. + +------- 0.9.73 Beta - 2006-10-31 + +RN46: Updated test scripts to run with MySQL 5.1.13. + +------- 0.9.72 Beta - 2006-10-19 + +RN45: Corrected compilation errors that occurred due to a change to struct st_mysql_plugin. + +------- 0.9.71 Beta - 2006-10-04 + +RN44: Corrected compilation errors that occurred due to changes in the storage engine API. + +------- 0.9.7 Beta - 2006-09-20 + +RN43: This is the first Beta release of PrimeBase XT. It has been integrated into MySQL 4.1.21 and is available as a plug-in for MySQL 5.1.12, or later. This version has been extensively tested using mysql-test-run, on various Linux and Mac OS X platforms. + +RN42: ++++ NOTE: This version is incompatible to older versions of PBXT ++++. Files created by older versions cannot be opened by version 0.9.7. + +RN41: Renaming or deleting a table while using a name with different case to the original created name did not work. + +RN40: Fixed a bug when grouping and searching on indexed columns that contain a null. + +RN39: Fixed bugs related to trailing spaces on VARCHAR values. Values that only vary by the number of trailing spaces (for example "aa" and "aa "), are now correctly handled as identical. + +RN38: The default AUTO_INCREMENT value was not correctly preserved during ALTER TABLE. + +RN37: Created a MySQL 5.1 Plugin version of PBXT. [TODO: 0017] + +RN36: Fixed a race condition in the row cache which had the affect that inserted rows dissappeared after cleanup because the cache was out of date. I was only able to reproduce this error on multi-processor machines. + +------- 0.9.6 - 2006-08-05 + +RN35: ++++ NOTE: This version is incompatible to older versions of PBXT ++++. + +The disk format of tables and log files has changed slightly in this version. As a result, files created by older versions cannot be opened by version 0.9.6. An error will be generated. If you have data wish to preserve, first start the older version of XT and convert all tables to MyISAM. The stop the server and removed all transaction log file (files of the form xtlog-*.xt). Then start the new version and convert tables back to XT. + +RN34: Implemented READ COMMITTED transaction mode. XT now supports READ COMMITTED and SERIALIZABLE transaction modes. NOTE: if the mode is set to REPEATABLE READ, SERIALIZABLE is used. If the mode is set to READ UNCOMMITTED READ COMMITTED is used. + +RN33: The implementation of AUTO_INCREMENT on a paritial index is non-standard. A unique value is generated without regard to the value of the index prefix. For example, assume we have the following table: CREATE TABLE t1 (c1 CHAR(10) not null, c2 INT not null AUTO_INCREMENT, PRIMARY KEY(c1, c2)); + +With the following contents: c1 c2 + A 8 + B 1 + +After executing the following statement: insert into t1 (c1) values ('B'); + +This is the result using PBXT: c1 c2 + A 8 + B 1 + B 9 + +The standard result would be: c1 c2 + A 8 + B 1 + B 2 + +RN32: PBXT does not permit access to multiple databases within a single transaction. For example: + +begin; +update database_1.t1 set a=10; +update database_2.t2 set d=10; +commit; + +In this case the following error is returned: 1015: Can't lock file (errno: -1) + +RN31: The implementation of COUNT(*) has changed. For effectiency, rows are not counted. The information is taken from the header of the record (.xtr) files. This information is only 100% accurate after transaction cleanup has completed. Which basically means, only when PBXT is idle. ANALYZE TABLE waits for all background activity to stop, so the statement may be executed before a COUNT(*) to ensure an accurate result. NOTE: Other then waiting for background processes, ANALYSE TABLE is not implemented. + +RN30: Two concurrency bugs have been fixed: a shared lock was used instead of an exclusive lock when deleting from a transaction list, the transaction segment semaphore was not initialized. XT now runs correctly in a multi-processor environment. The test used was sysbench on a dual-process, dual-core, AMD 64-bit machine running SUSE Linux 10.0. + +RN29: PBXT compiles and runs on under 64-bit Lunix. [TODO: 0009] + +RN28: ./mysql-test-run --force --mysqld=--default-storage-engine=pbxt will now execute most tests successfully. Changes to the tests and the result have been documented in http://www.primebase.com/xt/download/pbxt-test-run-changes.txt. [TODO: 0004, 0019] + +RN27: Fixed a bug that caused the server to crash if when using tables locks and transactions. For example: LOCK TABLES, BEGIN, COMMIT, SELECT. This sequence now returns an error. The correct sequence is: + +LOCK TABLES, BEGIN, COMMIT, UNLOCK TABLES, SELECT +or +LOCK TABLES, BEGIN, COMMIT, BEGIN, SELECT COMMIT, UNLOCK TABLES + +RN26: Fixed a concurrency problem which caused a number of threads to hang during the sysbench test - see RN30 above (bug reported by Vadim). + +RN25: Fixed a bug that caused the server to hang when ha_pbxt::create() and ha_pbxt::ha_open() where given different, but equivalent paths for a particular table. + +RN24: Fixed bug in the indexing of blob columns, for example: create table t1(name_id int, name blob, INDEX name_idx (name(5))); + +RN23: When a duplicate key error occurs in auto-commit mode, the transaction is now rolled back. + +RN22: Fixed incorrect duplicate key error. In the case of a unique key which allows NULLs, duplicates are allowed if the inserted key contains a NULL. For example: + +create table t1 (id int not null, str char(10), unique(str)); +insert into t1 values (1, null),(2, null),(3, "foo"),(4, "bar"); + +RN21: PBXT now returns the correct error code on duplicate key: 1062 instead of 1022. + +RN19: Implemented AUTO_INCREMENT on partial keys. However, the XT implementation is non-standard. Increment of partial index works, but the ID generated is incremented like a non-partial index. For example: + +create table t1 (c1 char(10) not null, c2 int not null auto_increment, primary key(c1, c2)); +select * from t1; +c1 c2 +A 8 +B 1 + +insert into t1 (c1) values ('B'); +select * from t1; +c1 c2 +A 8 +B 1 +B 9 + +The standard result would be: +c1 c2 +A 8 +B 1 +B 2 + +RN18: Implemented TRUNCATE TABLE and DELETE FROM <table>; (i.e. a DELETE without WHERE clause). Previously DELETE FROM <table>; did not cause an error, but no rows where deleted (TRUNCATE TABLE returned an error). [TODO: 0012, 0022] + +RN17: Implemented CREATE TABLE (...) auto_increment=<value>; + +------- 0.9.51 - 2006-07-06 + +RN16: Fixed crash which could occur when creating the first table in a database (bug reported by Hakan). + +------- 0.9.5 - 2006-07-03 + +RN15: This version concludes the re-structuring of the PBXT implementation. I have made a number of major changes, including: + +- All files except the transaction logs are now associated with a particular table. All table related files begin with the name of the table. The extension indicates the function. + +- I have merged the handle and the fixed length row data for performance reasons. + +- Only the variable size component of a row is stored in the data log files. As a result the data logs can now be considered as a type of "overflow" area. + +- Memory mapped files are no longer used because it is not possible to flush changes to the disk. + +RN14: File names have the following forms: + +[table-name]-[table-id].xtr - These files contains the table row pointers. Each row pointer occupies 8 bytes and refers to a list of records. The file name also contains the table ID. This is a unique number which is used internally by XT to identify the table. + +[table-name].xtd - This file contains the fixed length data of a table. Each data item includes a handle and a record. The handle references a record in the data log file if the table contains variable length records. + +[table-name].xti - This file contains the index data of the table. + +[table-name]-[log-id].xtl - This is a data log file. It contains the variable length data of the table. A table may have any number of data log files, each with a unique ID. + +xtlog-[log-id].xt - These files are the transaction logs. Log entries that specify updates reference a data file record. Each active thread has its own transaction log in order to avoid contension. + +RN13: Fixed the bug "Hang on DROP DATABASE". [TODO: 0016] + +RN12: PBXT currently only supports the "Serializable" transaction isolation level. This is the highest isolation level possible and includes the "repeatable-read" functionality [TODO: 0015]. This is implemented by giving every transaction a snapshot of the database at the point when the transaction is started. + +If the transaction tries to update a record that was updated by some other transaction after the snapshot was taken, a locked error is returned. A deadlock can occur if 2 transactions update the same record in a different order. PBXT can detect all deadlocks. + +RN11: I have implemented write buffering on the table data files. [TODO: 0013] + +RN10: The unique constraint (UNIQUE INDEX/PRIMARY KEY) is now checked correctly. [TODO: 0008] + +RN9: I have implemented a conventional B-tree algorithm for the indices (instead of the Lehman and Yoa B*-link tree). Although this reduces concurrency it improves the performance of queries significantly because of the simplicity of the algorithm. Deletion is also implemented in a very simple manner. [TODO: 0007] + +RN8: PBXT now has only 2 caches [TODO: 0006]: + +The Index Cache (pbxt_index_cache_size): This is the amount of memory the PBXT storage engine uses to cache index data and row pointers. This is all the data in the files with the extensions '.xti' and '.xtr'. This cache is managed in blocks of 2K. + +The Record Cache (pbxt_record_cache_size): This is the amount of memory the PBXT storage engine uses to cache table row data (handles and records). This is all the data in the files with the extension '.xtd'. + +The size of the caches are determined by the values of the system variables pbxt_index_cache_size and pbxt_row_cache_size. By default these values are set to 32MB. + +RN7: Auto-increment is now implemented in memory. This is done by doing a MAX() select when a table is first opened to get the high value. After that, then high value is incremented in memory on INSERT. On UPDATE (or INSERT) the value in memory is adjusted if necessary. This method also makes it possible for rows to be inserted simultaneously on the same table. [TODO: 0005, 0014] + +RN6: ./run-all-tests --create-options=TYPE=PBXT succeeds. [TODO: 0004] + +RN5: Using sql-bench and my own Java based test I have confirmed that PBXT behaves correctly during multi-threaded access. [PARTIALY TODO: 0002] + +RN4: Load/Stability test. Using sql-bench I have tested PBXT under load over a long period of time. [PARTIALY TODO: 0001] + +------- 0.9.2 - 2006-04-01 + +RN3: Fixed a bug that cause the error "-6: Handle is out of range: [0:0]". + +RN2: Implemented SET, ENUM and YEAR data types. + +RN1: Fixed a bug in the error reporting when a table is created with a datatype that is not supported. [TODO: 0011] + + diff --git a/storage/pbxt/INSTALL b/storage/pbxt/INSTALL new file mode 100644 index 00000000000..23e5f25d0e5 --- /dev/null +++ b/storage/pbxt/INSTALL @@ -0,0 +1,236 @@ +Installation Instructions +************************* + +Copyright (C) 1994, 1995, 1996, 1999, 2000, 2001, 2002, 2004, 2005 Free +Software Foundation, Inc. + +This file is free documentation; the Free Software Foundation gives +unlimited permission to copy, distribute and modify it. + +Basic Installation +================== + +These are generic installation instructions. + + The `configure' shell script attempts to guess correct values for +various system-dependent variables used during compilation. It uses +those values to create a `Makefile' in each directory of the package. +It may also create one or more `.h' files containing system-dependent +definitions. Finally, it creates a shell script `config.status' that +you can run in the future to recreate the current configuration, and a +file `config.log' containing compiler output (useful mainly for +debugging `configure'). + + It can also use an optional file (typically called `config.cache' +and enabled with `--cache-file=config.cache' or simply `-C') that saves +the results of its tests to speed up reconfiguring. (Caching is +disabled by default to prevent problems with accidental use of stale +cache files.) + + If you need to do unusual things to compile the package, please try +to figure out how `configure' could check whether to do them, and mail +diffs or instructions to the address given in the `README' so they can +be considered for the next release. If you are using the cache, and at +some point `config.cache' contains results you don't want to keep, you +may remove or edit it. + + The file `configure.ac' (or `configure.in') is used to create +`configure' by a program called `autoconf'. You only need +`configure.ac' if you want to change it or regenerate `configure' using +a newer version of `autoconf'. + +The simplest way to compile this package is: + + 1. `cd' to the directory containing the package's source code and type + `./configure' to configure the package for your system. If you're + using `csh' on an old version of System V, you might need to type + `sh ./configure' instead to prevent `csh' from trying to execute + `configure' itself. + + Running `configure' takes awhile. While running, it prints some + messages telling which features it is checking for. + + 2. Type `make' to compile the package. + + 3. Optionally, type `make check' to run any self-tests that come with + the package. + + 4. Type `make install' to install the programs and any data files and + documentation. + + 5. You can remove the program binaries and object files from the + source code directory by typing `make clean'. To also remove the + files that `configure' created (so you can compile the package for + a different kind of computer), type `make distclean'. There is + also a `make maintainer-clean' target, but that is intended mainly + for the package's developers. If you use it, you may have to get + all sorts of other programs in order to regenerate files that came + with the distribution. + +Compilers and Options +===================== + +Some systems require unusual options for compilation or linking that the +`configure' script does not know about. Run `./configure --help' for +details on some of the pertinent environment variables. + + You can give `configure' initial values for configuration parameters +by setting variables in the command line or in the environment. Here +is an example: + + ./configure CC=c89 CFLAGS=-O2 LIBS=-lposix + + *Note Defining Variables::, for more details. + +Compiling For Multiple Architectures +==================================== + +You can compile the package for more than one kind of computer at the +same time, by placing the object files for each architecture in their +own directory. To do this, you must use a version of `make' that +supports the `VPATH' variable, such as GNU `make'. `cd' to the +directory where you want the object files and executables to go and run +the `configure' script. `configure' automatically checks for the +source code in the directory that `configure' is in and in `..'. + + If you have to use a `make' that does not support the `VPATH' +variable, you have to compile the package for one architecture at a +time in the source code directory. After you have installed the +package for one architecture, use `make distclean' before reconfiguring +for another architecture. + +Installation Names +================== + +By default, `make install' installs the package's commands under +`/usr/local/bin', include files under `/usr/local/include', etc. You +can specify an installation prefix other than `/usr/local' by giving +`configure' the option `--prefix=PREFIX'. + + You can specify separate installation prefixes for +architecture-specific files and architecture-independent files. If you +pass the option `--exec-prefix=PREFIX' to `configure', the package uses +PREFIX as the prefix for installing programs and libraries. +Documentation and other data files still use the regular prefix. + + In addition, if you use an unusual directory layout you can give +options like `--bindir=DIR' to specify different values for particular +kinds of files. Run `configure --help' for a list of the directories +you can set and what kinds of files go in them. + + If the package supports it, you can cause programs to be installed +with an extra prefix or suffix on their names by giving `configure' the +option `--program-prefix=PREFIX' or `--program-suffix=SUFFIX'. + +Optional Features +================= + +Some packages pay attention to `--enable-FEATURE' options to +`configure', where FEATURE indicates an optional part of the package. +They may also pay attention to `--with-PACKAGE' options, where PACKAGE +is something like `gnu-as' or `x' (for the X Window System). The +`README' should mention any `--enable-' and `--with-' options that the +package recognizes. + + For packages that use the X Window System, `configure' can usually +find the X include and library files automatically, but if it doesn't, +you can use the `configure' options `--x-includes=DIR' and +`--x-libraries=DIR' to specify their locations. + +Specifying the System Type +========================== + +There may be some features `configure' cannot figure out automatically, +but needs to determine by the type of machine the package will run on. +Usually, assuming the package is built to be run on the _same_ +architectures, `configure' can figure that out, but if it prints a +message saying it cannot guess the machine type, give it the +`--build=TYPE' option. TYPE can either be a short name for the system +type, such as `sun4', or a canonical name which has the form: + + CPU-COMPANY-SYSTEM + +where SYSTEM can have one of these forms: + + OS KERNEL-OS + + See the file `config.sub' for the possible values of each field. If +`config.sub' isn't included in this package, then this package doesn't +need to know the machine type. + + If you are _building_ compiler tools for cross-compiling, you should +use the option `--target=TYPE' to select the type of system they will +produce code for. + + If you want to _use_ a cross compiler, that generates code for a +platform different from the build platform, you should specify the +"host" platform (i.e., that on which the generated programs will +eventually be run) with `--host=TYPE'. + +Sharing Defaults +================ + +If you want to set default values for `configure' scripts to share, you +can create a site shell script called `config.site' that gives default +values for variables like `CC', `cache_file', and `prefix'. +`configure' looks for `PREFIX/share/config.site' if it exists, then +`PREFIX/etc/config.site' if it exists. Or, you can set the +`CONFIG_SITE' environment variable to the location of the site script. +A warning: not all `configure' scripts look for a site script. + +Defining Variables +================== + +Variables not defined in a site shell script can be set in the +environment passed to `configure'. However, some packages may run +configure again during the build, and the customized values of these +variables may be lost. In order to avoid this problem, you should set +them in the `configure' command line, using `VAR=value'. For example: + + ./configure CC=/usr/local2/bin/gcc + +causes the specified `gcc' to be used as the C compiler (unless it is +overridden in the site shell script). Here is a another example: + + /bin/bash ./configure CONFIG_SHELL=/bin/bash + +Here the `CONFIG_SHELL=/bin/bash' operand causes subsequent +configuration-related scripts to be executed by `/bin/bash'. + +`configure' Invocation +====================== + +`configure' recognizes the following options to control how it operates. + +`--help' +`-h' + Print a summary of the options to `configure', and exit. + +`--version' +`-V' + Print the version of Autoconf used to generate the `configure' + script, and exit. + +`--cache-file=FILE' + Enable the cache: use and save the results of the tests in FILE, + traditionally `config.cache'. FILE defaults to `/dev/null' to + disable caching. + +`--config-cache' +`-C' + Alias for `--cache-file=config.cache'. + +`--quiet' +`--silent' +`-q' + Do not print messages saying which checks are being made. To + suppress all normal output, redirect it to `/dev/null' (any error + messages will still be shown). + +`--srcdir=DIR' + Look for the package's source code in directory DIR. Usually + `configure' can determine that directory automatically. + +`configure' also accepts some other, not widely useful, options. Run +`configure --help' for more details. + diff --git a/storage/pbxt/Makefile.am b/storage/pbxt/Makefile.am new file mode 100644 index 00000000000..a8bfde74ee3 --- /dev/null +++ b/storage/pbxt/Makefile.am @@ -0,0 +1,3 @@ +SUBDIRS = src + +EXTRA_DIST = plug.in diff --git a/storage/pbxt/NEWS b/storage/pbxt/NEWS new file mode 100644 index 00000000000..e69de29bb2d --- /dev/null +++ b/storage/pbxt/NEWS diff --git a/storage/pbxt/README b/storage/pbxt/README new file mode 100644 index 00000000000..52d7cf6c44e --- /dev/null +++ b/storage/pbxt/README @@ -0,0 +1,19 @@ +PrimeBase XT for MySQL 5.1 +========================== + +This is the PrimeBase XT (PBXT) transactional storage engine for MySQL. PBXT is "pluggable", which means that it can be loaded dynamically by MySQL at runtime. It uses a unique "write-once" update strategy and MVCC (multi-version concurrency control) to provide optimal performance over a wide range of tasks. + +This package includes the complete source code for the engine. Although this is a standalone project it must be built against a compiled version of the MySQL 5.1 source tree, because it references headers files used internally by the server. + +Details about how to build PBXT both under UNIX or Windows, as a standalone plug-in, or as part of the MySQL source code, is distribed in the documentation which is avaliable online at: + +http://www.primebase.org/documentation + +Bug reports, questions and comments can be sent directly to me. + +Thanks for your support! + +Paul McCullagh +SNAP Innovation GmbH +paul.mccullagh@primebase.org + diff --git a/storage/pbxt/TODO b/storage/pbxt/TODO new file mode 100644 index 00000000000..b5782defb61 --- /dev/null +++ b/storage/pbxt/TODO @@ -0,0 +1,195 @@ +PBXT To-Do List +=============== + +My thanks to all who have downloaded and tested PBXT. If an issue you reported before the date below is not on this list, please e-mail me again. + +------- 2008-12-09 + +0063: The option for not using memory mapped files must be fixed. + +0062: Dynamic option for using memory mapping on a table (Dimitri). + +------- 2008-09-12 + +0061: Add records per key result to ha_pbxt:info() call (Mark). + +------- 2008-08-31 + +0060: Add table option to determine if a table should be memory mapped or not (also requested by Dimitri). + +0059: Add table options: + AVG_ROW_LENGTH [=] value + DATA DIRECTORY [=] 'absolute path to directory' + INDEX DIRECTORY [=] 'absolute path to directory' + MAX_ROWS [=] value + +------- 2008-03-28 + +0058: Consolidate writes when changes in the log are applied to the database. + +------- 2008-03-07 + +0057: Cluster updates onto a single page. + +0056: Add checksum to index and data pages. + +0055: When no index cache is available, the complete index must be flushed (not just single pages). + +0054: Optimize indexes by not creating indexes that are a complete sub-set of some other index. In this case we must be able to identify part of an index as unique. For example: primary key (a, b), index (a, b, c). Here we would just create index (a, b, c), and specify that the part (a, b) must be unique. Operations on (a, b) will be directed to index (a, b, c). + +0053: Check and test lock tables. + +0052: Cache data log data in the handle data cache. Must be purged when a handle data record is written. + +0051: Write data log data alternatively to the transaction log. The compactor must then compact transaction logs. + +0050: [RESOLVED: RN126] Implement consistent write for indexes. + +0049: [RESOLVED: RN114] Set the index block size to 4K, or 16K as used by InnoDB. + +0048: [RESOLVED: RN110] Add row ID to indexes. This should only be set once the row is cleaned by the sweeper. Then the row ID can be used to make a quite check if the row is the most recent version. + +------- 2007-06-19 + +0047: Test build with ./configure --with-innodb under Linux (Vadim). + +0046: [RESOLVED: RN85] Add plug.in file to enable drop in compile under Linux. + +0045: Provide libstdc++.so.6 binaries (Vadim). + +0044: [RESOLVED: RN73] Limit number of file handles used per table (Brian). + +0043: XA (two-phase commit) support (Peter). + +------- 2007-03-13 + +0042: [RESOLVED: RN108] Implemement STATUS commands. + +0041: Implement index prefix compression. + +------- 2007-03-07 + +0040: [RESOLVED: RN60] Update in-place when a transaction updates the same record more than once. + +0039: Set the number and size of the segments dynamically according to the amount of memory in the cache (and the number of CPUs?) (as discussed with: Peter & Vadim). + +0038: [RESOLVED: RN133] Improve the efficiency of the locks by using atomic compare and swap (Peter & Vadim). + +0037: [RESOLVED: RN133] Instead of a global LRU list, use a LRU list for segment of the cache (Peter & Vadim). [ Note: a global list using a TAS lock and change time (so that LRU is not always updated) is most efficient]. + +0036: Add support for deferred foreign key checking (requested by: Mark). + +0035: [RESOLVED: RN71] Remove the 2000 table limit (reported by: Hakan). + +------- 2007-02-28 + +0035: [RESOLVED: RN74, RN107] Build in the PBXT system parameters (currently they must be set using environment variables. + +0034: [RESOLVED: RN117] Initial documentation (yes, it must be done!) + +0033: Make the error code returned on lock error configurable. + +0032: [RESOLVED: RN65] Create a source code pluggable version for Windows. + +0031: [RESOLVED: RN66] PBXT corrupts the index file when the size exceeds 4 GB (reported by: Luciano) + +0030: [RESOLVED: RN102] Implement pbxt_index_flush_delay. Postpones index writing in order to speed up imports. [Resolution uses that fact hat index entries that are missing are added during recovery. As a result, index flushing can be delayed.] + +0029: [RESOLVED: RN103] Implement SELECT ... FOR UPDATE (recommended by: Robin). + +------- 2007-02-14 + +0028: Implement CREATE TABLE ... DATA/INDEX DIRECTORY (suggested by: Robin). + +------- 2006-12-06 + +0027: [RESOLVED: RN53] Bug in pbxt with query caching (reported by: Giuseppe) caused violation of transaction isolation. + +------- 2006-08-05 + +0026: Implement BACKUP and RESTORE table (planned for the first post release version). + +0025: Implement DISABLE/ENABLE KEYS. Works for FOREIGN KEYs, currently no plans to implement for disabling indexes. + +0024: Implement ANALYZE TABLE (planned for the first post release version). + +0023: Implement CHECK TABLE (planned for the first release candidate). + +0022: [RESOLVED: RN18] Implement TRUNCATE TABLE and DELETE FROM <table>; (i.e. a DELETE without WHERE clause). Currently this function does not cause an error, but no rows are deleted. + +------- 2006-07-06 + +0021: [RESOLVED: RN28] .../mysql-test/mysql-test-run --force --mysqld=--default-storage-engine=pbxt produces a number of errors (reported by: Hakan): As far as I can tell some failures are unnessary but others are bugs. All need to be checked. + +------- 2006-07-03 + +0020: [RESOLVED: RN49] Implement referential integrity (planned for the first release candidate). + +------- 2006-04-01 + +0019: [RESOLVED: RN28] mysql-test-run hangs on alter table (reported by: Hakan): Running a test like ./mysql-test-run.pl --mysqld=--default-storage-engine=pbxt, hangs on ALTER TABLE. + +0018: Implement GEOMETRY date type. Note: There are currently no plans to implement this feature. + +------- 2006-03-31 + +0017: [RESOLVED: RN37] MySQL 5.x Version (reported by: Ronald, Giuseppe). + +0016: [RESOLVED: RN13] Hang on "DROP DATABASE" (reported by: Giuseppe). Load the world database (http://downloads.mysql.com/docs/world.sql) and convert all tables into PBXT. Then, the drop database command hangs. + +0015: [RESOLVED: RN12] Implement isolation level "repeatable read" (reported by: Giuseppe). Current PBXT only supports isolation level "committed read". This means committed data can be seen no matter when it was committed. Use SELECT ... FOR UPDATE to guarantee repeatable read, on data already read. + +0014: [RESOLVED: RN7] Two transactions cannot insert simaltaneously if they use auto_increment (reported by: Giuseppe). See also 0005. + +0013: [RESOLVED: RN11] Implement buffered write (reported by: Giuseppe): Lack of buffered write leads to bad performance in operations such as ALTER TABLE ENGINE = PBXT and INSERT ... SELECT. + +0012: [RESOLVED: RN18] TRUNCATE does not work (reported by: Giuseppe) + +0011: [RESOLVED: RN2] Load Sakila Sample Database (reported by: Ronald): ALTER TABLE film ENGINE=PBXT; fails + +0010: [RESOLVED: RN6] sql-bench (reported by: Dmitry): ./run-all-tests --create-options=TYPE=PBXT fails. + +0009: [RESOLVED: RN29] 64-bit Linux (reported by: Hakan): PBXT current does not compile under 64-bit Linux. + +------- 2006-03-16 + +0008: [RESOLVED: RN10] Enforcing the unique index constraint: + +An index declared as "unique" must return a "duplicate unique key" error when inserting a duplicate value. The difficulty part of implementing this in PBXT is that we may encounter a duplicate value that has not yet been committed. The index reading thread must then wait for the transaction to commit or abort. + +0007: [RESOLVED: RN9] Cleaning up empty index nodes: + +The Lehman and Yoa algorithm used for indexing does not describe a way of cleaning up empty index nodes on-the-fly. A search of the relevant literature for an algorithm also turns up empty handed (periodic "reorg" is mostly suggested). I have subsequently devised an algorithm that will do the job. This needs to be implemented. + +0006: [RESOLVED: RN8] Cache Balancing: + +PBXT uses a number of small caches in order to improve concurrency (rather than one large cache). A process is required to manage the amount of cache memory used as a whole. The process must distribute the overall amount of memory available for caching over the small caches, according to demand. + +0005: [RESOLVED: RN7] Implement a faster auto-increment method + +Currently the auto-increment is handled by the default method used in MySQL. This is done by performing a "fetch-last" on the index for each insert to find the highest key value. This works well unless there are large number empty index nodes due to the problem described in (2) above. + +PBXT Testing To-Do List + +This is my first take on what still must be tested. My thanks to Ronald Bradford who is working on a generic testing framework that can be used to test PBXT. + +0004: [RESOLVED: RN6, RN28] MySQL Tests: + +Several tests (for mysql-test-run) written for other engines can be adapted and used to test PBXT. + +0003: [RESOLVED: RN30] Multi-processor Test: + +There is a difference between preemptive multitasking and true multitasking, which you have on a multi-processor (or dual core) machine. I don't expect any fundamental problems here, but it must be tested. + +0002: [RESOLVED: RN5, RN30, RN43] Multi-user/locking Test: + +How does the engine perform with a number of concurrent users running various transactions on a number of different tables? +This is a difficult test to write because it need to simulate a production situation. To test at least 2 or 3 machines is required. The idea is not to use too much data so that a lot of conflicts may occur. + +0001: [RESOLVED: RN4, RN43] Load/Stability Test: + +How does the engine perform under heavy load over a long period of time? How stable is the engine on power outage, etc? + +The test could use a variation of the test program written for test (3) above. At least 3 test machines would be required. The test must be modified to cause as much activity as possible. The test should monitor the performance under load. + + diff --git a/storage/pbxt/plug.in b/storage/pbxt/plug.in new file mode 100644 index 00000000000..67a8e7b0ac9 --- /dev/null +++ b/storage/pbxt/plug.in @@ -0,0 +1,7 @@ +MYSQL_STORAGE_ENGINE(pbxt,no, [PBXT Storage Engine], + [MVCC-based transactional engine], [max,max-no-ndb]) +MYSQL_PLUGIN_DIRECTORY(pbxt, [storage/pbxt]) +MYSQL_PLUGIN_STATIC(pbxt, [src/libpbxt.a]) +MYSQL_PLUGIN_ACTIONS(pbxt, [ +# AC_CONFIG_FILES(storage/pbxt/src/Makefile) + ]) diff --git a/storage/pbxt/src/CMakeLists.txt b/storage/pbxt/src/CMakeLists.txt new file mode 100755 index 00000000000..4533752045c --- /dev/null +++ b/storage/pbxt/src/CMakeLists.txt @@ -0,0 +1,53 @@ +# Copyright (c) 2008 PrimeBase Technologies GmbH +# +# PrimeBase XT +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You 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 +# +# 2006-03-22 Paul McCullagh +# +# H&G2JCtL +# +# This file is used to make the Windows version + +SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DMYSQL_SERVER") +SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -DMYSQL_SERVER") + +SET(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -DMYSQL_SERVER -DSAFEMALLOC -DSAFE_MUTEX -DDEBUG") +SET(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -DMYSQL_SERVER -DSAFEMALLOC -DSAFE_MUTEX -DDEBUG") + +INCLUDE_DIRECTORIES(${CMAKE_SOURCE_DIR}/include ${CMAKE_SOURCE_DIR}/sql + ${CMAKE_SOURCE_DIR}/regex + ${CMAKE_SOURCE_DIR}/extra/yassl/include) + +SET(PBXT_SOURCES ha_pbxt.cc bsearch_xt.cc index_xt.cc strutil_xt.cc cache_xt.cc linklist_xt.cc + ccutils_xt.cc lock_xt.cc table_xt.cc database_xt.cc thread_xt.cc + datadic_xt.cc memory_xt.cc trace_xt.cc datalog_xt.cc myxt_xt.cc util_xt.cc + filesys_xt.cc pthread_xt.cc xaction_xt.cc restart_xt.cc xactlog_xt.cc + hashtab_xt.cc sortedlist_xt.cc heap_xt.cc streaming_xt.cc tabcache_xt.cc + systab_xt.cc ha_xtsys.cc discover_xt.cc + bsearch_xt.h linklist_xt.h tabcache_xt.h cache_xt.h lock_xt.h table_xt.h + ccutils_xt.h thread_xt.h database_xt.h memory_xt.h trace_xt.h + datadic_xt.h pbms.h util_xt.h datalog_xt.h myxt_xt.h xaction_xt.h + filesys_xt.h pthread_xt.h xactlog_xt.h ha_pbxt.h restart_xt.h xt_config.h + hashtab_xt.h sortedlist_xt.h xt_defs.h heap_xt.h streaming_xt.h xt_errno.h + systab_xt.h ha_xtsys.h discover_xt.h + index_xt.h strutil_xt.h) + +IF(NOT SOURCE_SUBLIBS) + ADD_LIBRARY(pbxt ${PBXT_SOURCES}) + ADD_DEPENDENCIES(pbxt GenError) +ENDIF(NOT SOURCE_SUBLIBS) + diff --git a/storage/pbxt/src/Makefile.am b/storage/pbxt/src/Makefile.am new file mode 100644 index 00000000000..2272fe81464 --- /dev/null +++ b/storage/pbxt/src/Makefile.am @@ -0,0 +1,52 @@ +# Used to build Makefile.in + +MYSQLDATAdir = $(localstatedir) +MYSQLSHAREdir = $(pkgdatadir) +MYSQLBASEdir= $(prefix) +MYSQLLIBdir= $(pkglibdir) +pkgplugindir = $(pkglibdir)/plugin +INCLUDES= -I$(top_srcdir)/include -I$(top_builddir)/include \ + -I$(top_srcdir)/regex \ + -I$(top_srcdir)/storage/innobase/include \ + -I$(top_srcdir)/sql \ + -I$(srcdir) + +LIBS = + +LDADD = + +noinst_HEADERS = bsearch_xt.h cache_xt.h ccutils_xt.h database_xt.h \ + datadic_xt.h datalog_xt.h filesys_xt.h hashtab_xt.h \ + ha_pbxt.h heap_xt.h index_xt.h linklist_xt.h \ + memory_xt.h myxt_xt.h pthread_xt.h restart_xt.h \ + streaming_xt.h sortedlist_xt.h strutil_xt.h \ + tabcache_xt.h table_xt.h trace_xt.h thread_xt.h \ + util_xt.h xaction_xt.h xactlog_xt.h lock_xt.h \ + systab_xt.h ha_xtsys.h discover_xt.h \ + pbms.h xt_config.h xt_defs.h xt_errno.h locklist_xt.h +EXTRA_LTLIBRARIES = libpbxt.la + +libpbxt_la_SOURCES = bsearch_xt.cc cache_xt.cc ccutils_xt.cc database_xt.cc \ + datadic_xt.cc datalog_xt.cc filesys_xt.cc hashtab_xt.cc \ + ha_pbxt.cc heap_xt.cc index_xt.cc linklist_xt.cc \ + memory_xt.cc myxt_xt.cc pthread_xt.cc restart_xt.cc \ + streaming_xt.cc sortedlist_xt.cc strutil_xt.cc \ + tabcache_xt.cc table_xt.cc trace_xt.cc thread_xt.cc \ + systab_xt.cc ha_xtsys.cc discover_xt.cc \ + util_xt.cc xaction_xt.cc xactlog_xt.cc lock_xt.cc locklist_xt.cc + +libpbxt_la_LDFLAGS = -module + +# These are the warning Drizzle uses: +# DRIZZLE_WARNINGS = -W -Wall -Wextra -pedantic -Wundef -Wredundant-decls -Wno-strict-aliasing -Wno-long-long -Wno-unused-parameter + +libpbxt_la_CXXFLAGS = $(AM_CXXFLAGS) -DMYSQL_DYNAMIC_PLUGIN +libpbxt_la_CFLAGS = $(AM_CFLAGS) -DMYSQL_DYNAMIC_PLUGIN -std=c99 + +EXTRA_LIBRARIES = libpbxt.a +noinst_LIBRARIES = libpbxt.a +libpbxt_a_SOURCES = $(libpbxt_la_SOURCES) +libpbxt_a_CXXFLAGS = $(AM_CXXFLAGS) +libpbxt_a_CFLAGS = $(AM_CFLAGS) -std=c99 + +EXTRA_DIST = CMakeLists.txt diff --git a/storage/pbxt/src/bsearch_xt.cc b/storage/pbxt/src/bsearch_xt.cc new file mode 100644 index 00000000000..539de1ae74d --- /dev/null +++ b/storage/pbxt/src/bsearch_xt.cc @@ -0,0 +1,66 @@ +/* Copyright (c) 2005 PrimeBase Technologies GmbH + * + * PrimeBase XT + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You 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 + * + * 2004-01-03 Paul McCullagh + * + * H&G2JCtL + */ + +#include "xt_config.h" + +#include <stdio.h> + +#include "bsearch_xt.h" +#include "pthread_xt.h" +#include "thread_xt.h" + +/** + * Binary search a array of 'count' items, with byte size 'size'. This + * function returns a pointer to the element and the 'index' + * of the element if found. + * + * If not found the index of the insert point of the item + * is returned (0 <= index <= count). + * + * The comparison routine 'compar' may throw an exception. + * In this case the error details will be stored in 'thread'. + */ +void *xt_bsearch(XTThreadPtr thread, const void *key, register const void *base, size_t count, size_t size, size_t *idx, const void *thunk, XTCompareFunc compar) +{ + register size_t i; + register size_t guess; + register int r; + + i = 0; + while (i < count) { + guess = (i + count - 1) >> 1; + r = (compar)(thread, thunk, key, ((char *) base) + guess * size); + if (r == 0) { + *idx = guess; + return ((char *) base) + guess * size; + } + if (r < 0) + count = guess; + else + i = guess + 1; + } + + *idx = i; + return NULL; +} + diff --git a/storage/pbxt/src/bsearch_xt.h b/storage/pbxt/src/bsearch_xt.h new file mode 100644 index 00000000000..f15e28009fb --- /dev/null +++ b/storage/pbxt/src/bsearch_xt.h @@ -0,0 +1,32 @@ +/* Copyright (c) 2005 PrimeBase Technologies GmbH + * + * PrimeBase XT + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You 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 + * + * 2004-01-03 Paul McCullagh + * + * H&G2JCtL + */ +#ifndef __xt_bsearch_h__ +#define __xt_bsearch_h__ + +#include "xt_defs.h" + +struct XTThread; + +void *xt_bsearch(struct XTThread *self, const void *key, register const void *base, size_t count, size_t size, size_t *idx, const void *thunk, XTCompareFunc compar); + +#endif diff --git a/storage/pbxt/src/cache_xt.cc b/storage/pbxt/src/cache_xt.cc new file mode 100644 index 00000000000..0e15475f185 --- /dev/null +++ b/storage/pbxt/src/cache_xt.cc @@ -0,0 +1,1507 @@ +/* Copyright (c) 2005 PrimeBase Technologies GmbH, Germany + * + * PrimeBase XT + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You 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 + * + * 2005-05-24 Paul McCullagh + * + * H&G2JCtL + */ + +#include "xt_config.h" + +#ifndef XT_WIN +#include <unistd.h> +#endif + +#include <stdio.h> +#include <time.h> + +#include "pthread_xt.h" +#include "thread_xt.h" +#include "filesys_xt.h" +#include "cache_xt.h" +#include "table_xt.h" +#include "trace_xt.h" +#include "util_xt.h" + +#define XT_TIME_DIFF(start, now) (\ + ((xtWord4) (now) < (xtWord4) (start)) ? \ + ((xtWord4) 0XFFFFFFFF - ((xtWord4) (start) - (xtWord4) (now))) : \ + ((xtWord4) (now) - (xtWord4) (start))) + +/* + * ----------------------------------------------------------------------- + * D I S K C A C H E + */ + +#define IDX_CAC_SEGMENT_COUNT ((off_t) 1 << XT_INDEX_CACHE_SEGMENT_SHIFTS) +#define IDX_CAC_SEGMENT_MASK (IDX_CAC_SEGMENT_COUNT - 1) + +//#define IDX_USE_SPINRWLOCK +#define IDX_USE_RWMUTEX +//#define IDX_CAC_USE_PTHREAD_RW + +#ifdef IDX_CAC_USE_FASTWRLOCK +#define IDX_CAC_LOCK_TYPE XTFastRWLockRec +#define IDX_CAC_INIT_LOCK(s, i) xt_fastrwlock_init(s, &(i)->cs_lock) +#define IDX_CAC_FREE_LOCK(s, i) xt_fastrwlock_free(s, &(i)->cs_lock) +#define IDX_CAC_READ_LOCK(i, o) xt_fastrwlock_slock(&(i)->cs_lock, (o)) +#define IDX_CAC_WRITE_LOCK(i, o) xt_fastrwlock_xlock(&(i)->cs_lock, (o)) +#define IDX_CAC_UNLOCK(i, o) xt_fastrwlock_unlock(&(i)->cs_lock, (o)) +#elif defined(IDX_CAC_USE_PTHREAD_RW) +#define IDX_CAC_LOCK_TYPE xt_rwlock_type +#define IDX_CAC_INIT_LOCK(s, i) xt_init_rwlock(s, &(i)->cs_lock) +#define IDX_CAC_FREE_LOCK(s, i) xt_free_rwlock(&(i)->cs_lock) +#define IDX_CAC_READ_LOCK(i, o) xt_slock_rwlock_ns(&(i)->cs_lock) +#define IDX_CAC_WRITE_LOCK(i, o) xt_xlock_rwlock_ns(&(i)->cs_lock) +#define IDX_CAC_UNLOCK(i, o) xt_unlock_rwlock_ns(&(i)->cs_lock) +#elif defined(IDX_USE_RWMUTEX) +#define IDX_CAC_LOCK_TYPE XTRWMutexRec +#define IDX_CAC_INIT_LOCK(s, i) xt_rwmutex_init_with_autoname(s, &(i)->cs_lock) +#define IDX_CAC_FREE_LOCK(s, i) xt_rwmutex_free(s, &(i)->cs_lock) +#define IDX_CAC_READ_LOCK(i, o) xt_rwmutex_slock(&(i)->cs_lock, (o)->t_id) +#define IDX_CAC_WRITE_LOCK(i, o) xt_rwmutex_xlock(&(i)->cs_lock, (o)->t_id) +#define IDX_CAC_UNLOCK(i, o) xt_rwmutex_unlock(&(i)->cs_lock, (o)->t_id) +#endif + +#define ID_HANDLE_USE_SPINLOCK +//#define ID_HANDLE_USE_PTHREAD_RW + +#if defined(ID_HANDLE_USE_PTHREAD_RW) +#define ID_HANDLE_LOCK_TYPE xt_mutex_type +#define ID_HANDLE_INIT_LOCK(s, i) xt_init_mutex_with_autoname(s, i) +#define ID_HANDLE_FREE_LOCK(s, i) xt_free_mutex(i) +#define ID_HANDLE_LOCK(i) xt_lock_mutex_ns(i) +#define ID_HANDLE_UNLOCK(i) xt_unlock_mutex_ns(i) +#elif defined(ID_HANDLE_USE_SPINLOCK) +#define ID_HANDLE_LOCK_TYPE XTSpinLockRec +#define ID_HANDLE_INIT_LOCK(s, i) xt_spinlock_init_with_autoname(s, i) +#define ID_HANDLE_FREE_LOCK(s, i) xt_spinlock_free(s, i) +#define ID_HANDLE_LOCK(i) xt_spinlock_lock(i) +#define ID_HANDLE_UNLOCK(i) xt_spinlock_unlock(i) +#endif + +#define XT_HANDLE_SLOTS 37 + +/* +#ifdef DEBUG +#define XT_INIT_HANDLE_COUNT 0 +#define XT_INIT_HANDLE_BLOCKS 0 +#else +#define XT_INIT_HANDLE_COUNT 40 +#define XT_INIT_HANDLE_BLOCKS 10 +#endif +*/ + +/* A disk cache segment. The cache is divided into a number of segments + * to improve concurrency. + */ +typedef struct DcSegment { + IDX_CAC_LOCK_TYPE cs_lock; /* The cache segment lock. */ + XTIndBlockPtr *cs_hash_table; +} DcSegmentRec, *DcSegmentPtr; + +typedef struct DcHandleSlot { + ID_HANDLE_LOCK_TYPE hs_handles_lock; + XTIndHandleBlockPtr hs_free_blocks; + XTIndHandlePtr hs_free_handles; + XTIndHandlePtr hs_used_handles; +} DcHandleSlotRec, *DcHandleSlotPtr; + +typedef struct DcGlobals { + xt_mutex_type cg_lock; /* The public cache lock. */ + DcSegmentRec cg_segment[IDX_CAC_SEGMENT_COUNT]; + XTIndBlockPtr cg_blocks; +#ifdef XT_USE_DIRECT_IO_ON_INDEX + xtWord1 *cg_buffer; +#endif + XTIndBlockPtr cg_free_list; + xtWord4 cg_free_count; + xtWord4 cg_ru_now; /* A counter as described by Jim Starkey (my thanks) */ + XTIndBlockPtr cg_lru_block; + XTIndBlockPtr cg_mru_block; + xtWord4 cg_hash_size; + xtWord4 cg_block_count; + xtWord4 cg_max_free; +#ifdef DEBUG_CHECK_IND_CACHE + u_int cg_reserved_by_ots; /* Number of blocks reserved by open tables. */ + u_int cg_read_count; /* Number of blocks being read. */ +#endif + + /* Index cache handles: */ + DcHandleSlotRec cg_handle_slot[XT_HANDLE_SLOTS]; +} DcGlobalsRec; + +static DcGlobalsRec ind_cac_globals; + +#ifdef XT_USE_MYSYS +#ifdef xtPublic +#undef xtPublic +#endif +#include "my_global.h" +#include "my_sys.h" +#include "keycache.h" +KEY_CACHE my_cache; +#undef pthread_rwlock_rdlock +#undef pthread_rwlock_wrlock +#undef pthread_rwlock_unlock +#undef pthread_mutex_lock +#undef pthread_mutex_unlock +#undef pthread_cond_wait +#undef pthread_cond_broadcast +#undef xt_mutex_type +#define xtPublic +#endif + +/* + * ----------------------------------------------------------------------- + * INDEX CACHE HANDLES + */ + +static XTIndHandlePtr ind_alloc_handle() +{ + XTIndHandlePtr handle; + + if (!(handle = (XTIndHandlePtr) xt_calloc_ns(sizeof(XTIndHandleRec)))) + return NULL; + xt_spinlock_init_with_autoname(NULL, &handle->ih_lock); + return handle; +} + +static void ind_free_handle(XTIndHandlePtr handle) +{ + xt_spinlock_free(NULL, &handle->ih_lock); + xt_free_ns(handle); +} + +static void ind_handle_exit(XTThreadPtr self) +{ + DcHandleSlotPtr hs; + XTIndHandlePtr handle; + XTIndHandleBlockPtr hptr; + + for (int i=0; i<XT_HANDLE_SLOTS; i++) { + hs = &ind_cac_globals.cg_handle_slot[i]; + + while (hs->hs_used_handles) { + handle = hs->hs_used_handles; + xt_ind_release_handle(handle, FALSE, self); + } + + while (hs->hs_free_blocks) { + hptr = hs->hs_free_blocks; + hs->hs_free_blocks = hptr->hb_next; + xt_free(self, hptr); + } + + while (hs->hs_free_handles) { + handle = hs->hs_free_handles; + hs->hs_free_handles = handle->ih_next; + ind_free_handle(handle); + } + + ID_HANDLE_FREE_LOCK(self, &hs->hs_handles_lock); + } +} + +static void ind_handle_init(XTThreadPtr self) +{ + DcHandleSlotPtr hs; + + for (int i=0; i<XT_HANDLE_SLOTS; i++) { + hs = &ind_cac_globals.cg_handle_slot[i]; + memset(hs, 0, sizeof(DcHandleSlotRec)); + ID_HANDLE_INIT_LOCK(self, &hs->hs_handles_lock); + } +} + +//#define CHECK_HANDLE_STRUCTS + +#ifdef CHECK_HANDLE_STRUCTS +static int gdummy = 0; + +static void ic_stop_here() +{ + gdummy = gdummy + 1; + printf("Nooo %d!\n", gdummy); +} + +static void ic_check_handle_structs() +{ + XTIndHandlePtr handle, phandle; + XTIndHandleBlockPtr hptr, phptr; + int count = 0; + int ctest; + + phandle = NULL; + handle = ind_cac_globals.cg_used_handles; + while (handle) { + if (handle == phandle) + ic_stop_here(); + if (handle->ih_prev != phandle) + ic_stop_here(); + if (handle->ih_cache_reference) { + ctest = handle->x.ih_cache_block->cb_handle_count; + if (ctest == 0 || ctest > 100) + ic_stop_here(); + } + else { + ctest = handle->x.ih_handle_block->hb_ref_count; + if (ctest == 0 || ctest > 100) + ic_stop_here(); + } + phandle = handle; + handle = handle->ih_next; + count++; + if (count > 1000) + ic_stop_here(); + } + + count = 0; + hptr = ind_cac_globals.cg_free_blocks; + while (hptr) { + if (hptr == phptr) + ic_stop_here(); + phptr = hptr; + hptr = hptr->hb_next; + count++; + if (count > 1000) + ic_stop_here(); + } + + count = 0; + handle = ind_cac_globals.cg_free_handles; + while (handle) { + if (handle == phandle) + ic_stop_here(); + phandle = handle; + handle = handle->ih_next; + count++; + if (count > 1000) + ic_stop_here(); + } +} +#endif + +/* + * Get a handle to the index block. + * This function is called by index scanners (readers). + */ +xtPublic XTIndHandlePtr xt_ind_get_handle(XTOpenTablePtr ot, XTIndexPtr ind, XTIndReferencePtr iref) +{ + DcHandleSlotPtr hs; + XTIndHandlePtr handle; + + hs = &ind_cac_globals.cg_handle_slot[iref->ir_block->cb_address % XT_HANDLE_SLOTS]; + + ASSERT_NS(iref->ir_ulock == XT_UNLOCK_READ); + ID_HANDLE_LOCK(&hs->hs_handles_lock); +#ifdef CHECK_HANDLE_STRUCTS + ic_check_handle_structs(); +#endif + if ((handle = hs->hs_free_handles)) + hs->hs_free_handles = handle->ih_next; + else { + if (!(handle = ind_alloc_handle())) { + ID_HANDLE_UNLOCK(&hs->hs_handles_lock); + xt_ind_release(ot, ind, XT_UNLOCK_READ, iref); + return NULL; + } + } + if (hs->hs_used_handles) + hs->hs_used_handles->ih_prev = handle; + handle->ih_next = hs->hs_used_handles; + handle->ih_prev = NULL; + handle->ih_address = iref->ir_block->cb_address; + handle->ih_cache_reference = TRUE; + handle->x.ih_cache_block = iref->ir_block; + handle->ih_branch = iref->ir_branch; + /* {HANDLE-COUNT-USAGE} + * This is safe because: + * + * I have an Slock on the cache block, and I have + * at least an Slock on the index. + * So this excludes anyone who is reading + * cb_handle_count in the index. + * (all cache block writers, and a freeer). + * + * The increment is safe because I have the list + * lock, which is required by anyone else + * who increments or decrements this value. + */ + iref->ir_block->cb_handle_count++; + hs->hs_used_handles = handle; +#ifdef CHECK_HANDLE_STRUCTS + ic_check_handle_structs(); +#endif + ID_HANDLE_UNLOCK(&hs->hs_handles_lock); + xt_ind_release(ot, ind, XT_UNLOCK_READ, iref); + return handle; +} + +xtPublic void xt_ind_release_handle(XTIndHandlePtr handle, xtBool have_lock, XTThreadPtr thread) +{ + DcHandleSlotPtr hs; + XTIndBlockPtr block = NULL; + u_int hash_idx = NULL; + DcSegmentPtr seg = NULL; + XTIndBlockPtr xblock; + + /* The lock order is: + * 1. Cache segment (cs_lock) - This is only by ind_free_block()! + * 1. S/Slock cache block (cb_lock) + * 2. List lock (cg_handles_lock). + * 3. Handle lock (ih_lock) + */ + if (!have_lock) + xt_spinlock_lock(&handle->ih_lock); + + /* Get the lock on the cache page if required: */ + if (handle->ih_cache_reference) { + u_int file_id; + xtIndexNodeID address; + + block = handle->x.ih_cache_block; + + file_id = block->cb_file_id; + address = block->cb_address; + hash_idx = XT_NODE_ID(address) + (file_id * 223); + seg = &ind_cac_globals.cg_segment[hash_idx & IDX_CAC_SEGMENT_MASK]; + hash_idx = (hash_idx >> XT_INDEX_CACHE_SEGMENT_SHIFTS) % ind_cac_globals.cg_hash_size; + } + + xt_spinlock_unlock(&handle->ih_lock); + + /* Because of the lock order, I have to release the + * handle before I get a lock on the cache block. + * + * But, by doing this, thie cache block may be gone! + */ + if (block) { + IDX_CAC_READ_LOCK(seg, thread); + xblock = seg->cs_hash_table[hash_idx]; + while (xblock) { + if (block == xblock) { + /* Found the block... */ + xt_atomicrwlock_xlock(&block->cb_lock, thread->t_id); + goto block_found; + } + xblock = xblock->cb_next; + } + block = NULL; + block_found: + IDX_CAC_UNLOCK(seg, thread); + } + + hs = &ind_cac_globals.cg_handle_slot[handle->ih_address % XT_HANDLE_SLOTS]; + + ID_HANDLE_LOCK(&hs->hs_handles_lock); +#ifdef CHECK_HANDLE_STRUCTS + ic_check_handle_structs(); +#endif + + /* I don't need to lock the handle because I have locked + * the list, and no other thread can change the + * handle without first getting a lock on the list. + * + * In addition, the caller is the only owner of the + * handle, and the only thread with an independent + * reference to the handle. + * All other access occur over the list. + */ + + /* Remove the reference to the cache or a handle block: */ + if (handle->ih_cache_reference) { + ASSERT_NS(block == handle->x.ih_cache_block); + ASSERT_NS(block && block->cb_handle_count > 0); + /* {HANDLE-COUNT-USAGE} + * This is safe here because I have excluded + * all readers by taking an Xlock on the + * cache block. + */ + block->cb_handle_count--; + } + else { + XTIndHandleBlockPtr hptr = handle->x.ih_handle_block; + + ASSERT_NS(!handle->ih_cache_reference); + ASSERT_NS(hptr->hb_ref_count > 0); + hptr->hb_ref_count--; + if (!hptr->hb_ref_count) { + /* Put it back on the free list: */ + hptr->hb_next = hs->hs_free_blocks; + hs->hs_free_blocks = hptr; + } + } + + /* Unlink the handle: */ + if (handle->ih_next) + handle->ih_next->ih_prev = handle->ih_prev; + if (handle->ih_prev) + handle->ih_prev->ih_next = handle->ih_next; + if (hs->hs_used_handles == handle) + hs->hs_used_handles = handle->ih_next; + + /* Put it on the free list: */ + handle->ih_next = hs->hs_free_handles; + hs->hs_free_handles = handle; + +#ifdef CHECK_HANDLE_STRUCTS + ic_check_handle_structs(); +#endif + ID_HANDLE_UNLOCK(&hs->hs_handles_lock); + + if (block) + xt_atomicrwlock_unlock(&block->cb_lock, TRUE); +} + +/* Call this function before a referenced cache block is modified! + * This function is called by index updaters. + */ +xtPublic xtBool xt_ind_copy_on_write(XTIndReferencePtr iref) +{ + DcHandleSlotPtr hs; + XTIndHandleBlockPtr hptr; + u_int branch_size; + XTIndHandlePtr handle; + u_int i = 0; + + hs = &ind_cac_globals.cg_handle_slot[iref->ir_block->cb_address % XT_HANDLE_SLOTS]; + + /* {HANDLE-COUNT-USAGE} + * This is only called by updaters of this index block, or + * the free which holds an Xlock on the index block. + * + * These are all mutually exclusive for the index block. + */ + ASSERT_NS(iref->ir_block->cb_handle_count); + if (!iref->ir_block->cb_handle_count) + return OK; + + ID_HANDLE_LOCK(&hs->hs_handles_lock); +#ifdef CHECK_HANDLE_STRUCTS + ic_check_handle_structs(); +#endif + if ((hptr = hs->hs_free_blocks)) + hs->hs_free_blocks = hptr->hb_next; + else { + if (!(hptr = (XTIndHandleBlockPtr) xt_malloc_ns(sizeof(XTIndHandleBlockRec)))) { + ID_HANDLE_UNLOCK(&hs->hs_handles_lock); + return FAILED; + } + } + + branch_size = XT_GET_INDEX_BLOCK_LEN(XT_GET_DISK_2(iref->ir_branch->tb_size_2)); + memcpy(&hptr->hb_branch, iref->ir_branch, branch_size); + hptr->hb_ref_count = iref->ir_block->cb_handle_count; + + handle = hs->hs_used_handles; + while (handle) { + if (handle->ih_branch == iref->ir_branch) { + i++; + xt_spinlock_lock(&handle->ih_lock); + ASSERT_NS(handle->ih_cache_reference); + handle->ih_cache_reference = FALSE; + handle->x.ih_handle_block = hptr; + handle->ih_branch = &hptr->hb_branch; + xt_spinlock_unlock(&handle->ih_lock); +#ifndef DEBUG + if (i == hptr->hb_ref_count) + break; +#endif + } + handle = handle->ih_next; + } +#ifdef DEBUG + ASSERT_NS(hptr->hb_ref_count == i); +#endif + /* {HANDLE-COUNT-USAGE} + * It is safe to modify cb_handle_count when I have the + * list lock, and I have excluded all readers! + */ + iref->ir_block->cb_handle_count = 0; +#ifdef CHECK_HANDLE_STRUCTS + ic_check_handle_structs(); +#endif + ID_HANDLE_UNLOCK(&hs->hs_handles_lock); + + return OK; +} + +xtPublic void xt_ind_lock_handle(XTIndHandlePtr handle) +{ + xt_spinlock_lock(&handle->ih_lock); +} + +xtPublic void xt_ind_unlock_handle(XTIndHandlePtr handle) +{ + xt_spinlock_unlock(&handle->ih_lock); +} + +/* + * ----------------------------------------------------------------------- + * INIT/EXIT + */ + +/* + * Initialize the disk cache. + */ +xtPublic void xt_ind_init(XTThreadPtr self, size_t cache_size) +{ + XTIndBlockPtr block; + +#ifdef XT_USE_MYSYS + init_key_cache(&my_cache, 1024, cache_size, 100, 300); +#endif + /* Memory is devoted to the page data alone, I no longer count the size of the directory, + * or the page overhead: */ + ind_cac_globals.cg_block_count = cache_size / XT_INDEX_PAGE_SIZE; + ind_cac_globals.cg_hash_size = ind_cac_globals.cg_block_count / (IDX_CAC_SEGMENT_COUNT >> 1); + ind_cac_globals.cg_max_free = ind_cac_globals.cg_block_count / 10; + if (ind_cac_globals.cg_max_free < 8) + ind_cac_globals.cg_max_free = 8; + if (ind_cac_globals.cg_max_free > 128) + ind_cac_globals.cg_max_free = 128; + + try_(a) { + for (u_int i=0; i<IDX_CAC_SEGMENT_COUNT; i++) { + ind_cac_globals.cg_segment[i].cs_hash_table = (XTIndBlockPtr *) xt_calloc(self, ind_cac_globals.cg_hash_size * sizeof(XTIndBlockPtr)); + IDX_CAC_INIT_LOCK(self, &ind_cac_globals.cg_segment[i]); + } + + block = (XTIndBlockPtr) xt_malloc(self, ind_cac_globals.cg_block_count * sizeof(XTIndBlockRec)); + ind_cac_globals.cg_blocks = block; + xt_init_mutex_with_autoname(self, &ind_cac_globals.cg_lock); +#ifdef XT_USE_DIRECT_IO_ON_INDEX + xtWord1 *buffer; +#ifdef XT_WIN + size_t psize = 512; +#else + size_t psize = getpagesize(); +#endif + size_t diff; + + buffer = (xtWord1 *) xt_malloc(self, (ind_cac_globals.cg_block_count * XT_INDEX_PAGE_SIZE)); + diff = (size_t) buffer % psize; + if (diff != 0) { + xt_free(self, buffer); + buffer = (xtWord1 *) xt_malloc(self, (ind_cac_globals.cg_block_count * XT_INDEX_PAGE_SIZE) + psize); + diff = (size_t) buffer % psize; + if (diff != 0) + diff = psize - diff; + } + ind_cac_globals.cg_buffer = buffer; + buffer += diff; +#endif + + for (u_int i=0; i<ind_cac_globals.cg_block_count; i++) { + xt_atomicrwlock_init_with_autoname(self, &block->cb_lock); + block->cb_state = IDX_CAC_BLOCK_FREE; + block->cb_next = ind_cac_globals.cg_free_list; +#ifdef XT_USE_DIRECT_IO_ON_INDEX + block->cb_data = buffer; + buffer += XT_INDEX_PAGE_SIZE; +#endif + ind_cac_globals.cg_free_list = block; + block++; + } + ind_cac_globals.cg_free_count = ind_cac_globals.cg_block_count; +#ifdef DEBUG_CHECK_IND_CACHE + ind_cac_globals.cg_reserved_by_ots = 0; +#endif + ind_handle_init(self); + } + catch_(a) { + xt_ind_exit(self); + throw_(); + } + cont_(a); +} + +xtPublic void xt_ind_exit(XTThreadPtr self) +{ +#ifdef XT_USE_MYSYS + end_key_cache(&my_cache, 1); +#endif + for (u_int i=0; i<IDX_CAC_SEGMENT_COUNT; i++) { + if (ind_cac_globals.cg_segment[i].cs_hash_table) { + xt_free(self, ind_cac_globals.cg_segment[i].cs_hash_table); + ind_cac_globals.cg_segment[i].cs_hash_table = NULL; + IDX_CAC_FREE_LOCK(self, &ind_cac_globals.cg_segment[i]); + } + } + + if (ind_cac_globals.cg_blocks) { + xt_free(self, ind_cac_globals.cg_blocks); + ind_cac_globals.cg_blocks = NULL; + xt_free_mutex(&ind_cac_globals.cg_lock); + } +#ifdef XT_USE_DIRECT_IO_ON_INDEX + if (ind_cac_globals.cg_buffer) { + xt_free(self, ind_cac_globals.cg_buffer); + ind_cac_globals.cg_buffer = NULL; + } +#endif + ind_handle_exit(self); + + memset(&ind_cac_globals, 0, sizeof(ind_cac_globals)); +} + +xtPublic xtInt8 xt_ind_get_usage() +{ + xtInt8 size = 0; + + size = (xtInt8) (ind_cac_globals.cg_block_count - ind_cac_globals.cg_free_count) * (xtInt8) XT_INDEX_PAGE_SIZE; + return size; +} + +xtPublic xtInt8 xt_ind_get_size() +{ + xtInt8 size = 0; + + size = (xtInt8) ind_cac_globals.cg_block_count * (xtInt8) XT_INDEX_PAGE_SIZE; + return size; +} + +/* + * ----------------------------------------------------------------------- + * INDEX CHECKING + */ + +xtPublic void xt_ind_check_cache(XTIndexPtr ind) +{ + XTIndBlockPtr block; + u_int free_count, inuse_count, clean_count; + xtBool check_count = FALSE; + + if (ind == (XTIndex *) 1) { + ind = NULL; + check_count = TRUE; + } + + // Check the dirty list: + if (ind) { + u_int cnt = 0; + + block = ind->mi_dirty_list; + while (block) { + cnt++; + ASSERT_NS(block->cb_state == IDX_CAC_BLOCK_DIRTY); + block = block->cb_dirty_next; + } + ASSERT_NS(ind->mi_dirty_blocks == cnt); + } + + xt_lock_mutex_ns(&ind_cac_globals.cg_lock); + + // Check the free list: + free_count = 0; + block = ind_cac_globals.cg_free_list; + while (block) { + free_count++; + ASSERT_NS(block->cb_state == IDX_CAC_BLOCK_FREE); + block = block->cb_next; + } + ASSERT_NS(ind_cac_globals.cg_free_count == free_count); + + /* Check the LRU list: */ + XTIndBlockPtr list_block, plist_block; + + plist_block = NULL; + list_block = ind_cac_globals.cg_lru_block; + if (list_block) { + ASSERT_NS(ind_cac_globals.cg_mru_block != NULL); + ASSERT_NS(ind_cac_globals.cg_mru_block->cb_mr_used == NULL); + ASSERT_NS(list_block->cb_lr_used == NULL); + inuse_count = 0; + clean_count = 0; + while (list_block) { + inuse_count++; + ASSERT_NS(list_block->cb_state == IDX_CAC_BLOCK_DIRTY || list_block->cb_state == IDX_CAC_BLOCK_CLEAN); + if (list_block->cb_state == IDX_CAC_BLOCK_CLEAN) + clean_count++; + ASSERT_NS(block != list_block); + ASSERT_NS(list_block->cb_lr_used == plist_block); + plist_block = list_block; + list_block = list_block->cb_mr_used; + } + ASSERT_NS(ind_cac_globals.cg_mru_block == plist_block); + } + else { + inuse_count = 0; + clean_count = 0; + ASSERT_NS(ind_cac_globals.cg_mru_block == NULL); + } + +#ifdef DEBUG_CHECK_IND_CACHE + ASSERT_NS(free_count + inuse_count + ind_cac_globals.cg_reserved_by_ots + ind_cac_globals.cg_read_count == ind_cac_globals.cg_block_count); +#endif + xt_unlock_mutex_ns(&ind_cac_globals.cg_lock); + if (check_count) { + /* We have just flushed, check how much is now free/clean. */ + if (free_count + clean_count < 10) { + /* This could be a problem: */ + printf("Cache very low!\n"); + } + } +} + +#ifdef XXXXDEBUG +static void ind_cac_check_on_dirty_list(DcSegmentPtr seg, XTIndBlockPtr block) +{ + XTIndBlockPtr list_block, plist_block; + xtBool found = FALSE; + + plist_block = NULL; + list_block = seg->cs_dirty_list[block->cb_file_id % XT_INDEX_CACHE_FILE_SLOTS]; + while (list_block) { + ASSERT_NS(list_block->cb_state == IDX_CAC_BLOCK_DIRTY); + ASSERT_NS(list_block->cb_dirty_prev == plist_block); + if (list_block == block) + found = TRUE; + plist_block = list_block; + list_block = list_block->cb_dirty_next; + } + ASSERT_NS(found); +} + +static void ind_cac_check_dirty_list(DcSegmentPtr seg, XTIndBlockPtr block) +{ + XTIndBlockPtr list_block, plist_block; + + for (u_int j=0; j<XT_INDEX_CACHE_FILE_SLOTS; j++) { + plist_block = NULL; + list_block = seg->cs_dirty_list[j]; + while (list_block) { + ASSERT_NS(list_block->cb_state == IDX_CAC_BLOCK_DIRTY); + ASSERT_NS(block != list_block); + ASSERT_NS(list_block->cb_dirty_prev == plist_block); + plist_block = list_block; + list_block = list_block->cb_dirty_next; + } + } +} + +#endif + +/* + * ----------------------------------------------------------------------- + * FREEING INDEX CACHE + */ + +/* + * This function return TRUE if the block is freed. + * This function returns FALSE if the block cannot be found, or the + * block is not clean. + * + * We also return FALSE if we cannot copy the block to the handle + * (if this is required). This will be due to out-of-memory! + */ +static xtBool ind_free_block(XTOpenTablePtr ot, XTIndBlockPtr block) +{ + XTIndBlockPtr xblock, pxblock; + u_int hash_idx; + u_int file_id; + xtIndexNodeID address; + DcSegmentPtr seg; + +#ifdef DEBUG_CHECK_IND_CACHE + xt_ind_check_cache(NULL); +#endif + file_id = block->cb_file_id; + address = block->cb_address; + + hash_idx = XT_NODE_ID(address) + (file_id * 223); + seg = &ind_cac_globals.cg_segment[hash_idx & IDX_CAC_SEGMENT_MASK]; + hash_idx = (hash_idx >> XT_INDEX_CACHE_SEGMENT_SHIFTS) % ind_cac_globals.cg_hash_size; + + IDX_CAC_WRITE_LOCK(seg, ot->ot_thread); + + pxblock = NULL; + xblock = seg->cs_hash_table[hash_idx]; + while (xblock) { + if (block == xblock) { + /* Found the block... */ + xt_atomicrwlock_xlock(&block->cb_lock, ot->ot_thread->t_id); + if (block->cb_state != IDX_CAC_BLOCK_CLEAN) { + /* This block cannot be freeed: */ + xt_atomicrwlock_unlock(&block->cb_lock, TRUE); + IDX_CAC_UNLOCK(seg, ot->ot_thread); +#ifdef DEBUG_CHECK_IND_CACHE + xt_ind_check_cache(NULL); +#endif + return FALSE; + } + + goto free_the_block; + } + pxblock = xblock; + xblock = xblock->cb_next; + } + + IDX_CAC_UNLOCK(seg, ot->ot_thread); + + /* Not found (this can happen, if block was freed by another thread) */ +#ifdef DEBUG_CHECK_IND_CACHE + xt_ind_check_cache(NULL); +#endif + return FALSE; + + free_the_block: + + /* If the block is reference by a handle, then we + * have to copy the data to the handle before we + * free the page: + */ + /* {HANDLE-COUNT-USAGE} + * This access is safe because: + * + * We have an Xlock on the cache block, which excludes + * all other writers that want to change the cache block + * and also all readers of the cache block, because + * they all have at least an Slock on the cache block. + */ + if (block->cb_handle_count) { + XTIndReferenceRec iref; + + iref.ir_ulock = XT_UNLOCK_WRITE; + iref.ir_block = block; + iref.ir_branch = (XTIdxBranchDPtr) block->cb_data; + if (!xt_ind_copy_on_write(&iref)) { + xt_atomicrwlock_unlock(&block->cb_lock, TRUE); + return FALSE; + } + } + + /* Block is clean, remove from the hash table: */ + if (pxblock) + pxblock->cb_next = block->cb_next; + else + seg->cs_hash_table[hash_idx] = block->cb_next; + + xt_lock_mutex_ns(&ind_cac_globals.cg_lock); + + /* Remove from the MRU list: */ + if (ind_cac_globals.cg_lru_block == block) + ind_cac_globals.cg_lru_block = block->cb_mr_used; + if (ind_cac_globals.cg_mru_block == block) + ind_cac_globals.cg_mru_block = block->cb_lr_used; + + /* Note, I am updating blocks for which I have no lock + * here. But I think this is OK because I have a lock + * for the MRU list. + */ + if (block->cb_lr_used) + block->cb_lr_used->cb_mr_used = block->cb_mr_used; + if (block->cb_mr_used) + block->cb_mr_used->cb_lr_used = block->cb_lr_used; + + /* The block is now free: */ + block->cb_next = ind_cac_globals.cg_free_list; + ind_cac_globals.cg_free_list = block; + ind_cac_globals.cg_free_count++; + block->cb_state = IDX_CAC_BLOCK_FREE; + IDX_TRACE("%d- f%x\n", (int) XT_NODE_ID(address), (int) XT_GET_DISK_2(block->cb_data)); + + /* Unlock BEFORE the block is reused! */ + xt_atomicrwlock_unlock(&block->cb_lock, TRUE); + + xt_unlock_mutex_ns(&ind_cac_globals.cg_lock); + + IDX_CAC_UNLOCK(seg, ot->ot_thread); + +#ifdef DEBUG_CHECK_IND_CACHE + xt_ind_check_cache(NULL); +#endif + return TRUE; +} + +#define IND_CACHE_MAX_BLOCKS_TO_FREE 100 + +/* + * Return the number of blocks freed. + * + * The idea is to grab a list of blocks to free. + * The list consists of the LRU blocks that are + * clean. + * + * Free as many as possible (up to max of blocks_required) + * from the list, even if LRU position has changed + * (or we have a race if there are too few blocks). + * However, if the block cannot be found, or is dirty + * we must skip it. + * + * Repeat until we find no blocks for the list, or + * we have freed 'blocks_required'. + * + * 'not_this' is a block that must not be freed because + * it is locked by the calling thread! + */ +static u_int ind_cac_free_lru_blocks(XTOpenTablePtr ot, u_int blocks_required, XTIdxBranchDPtr not_this) +{ + register DcGlobalsRec *dcg = &ind_cac_globals; + XTIndBlockPtr to_free[IND_CACHE_MAX_BLOCKS_TO_FREE]; + int count; + XTIndBlockPtr block; + u_int blocks_freed = 0; + XTIndBlockPtr locked_block; + +#ifdef XT_USE_DIRECT_IO_ON_INDEX +#error This will not work! +#endif + locked_block = (XTIndBlockPtr) ((xtWord1 *) not_this - offsetof(XTIndBlockRec, cb_data)); + + retry: + xt_lock_mutex_ns(&ind_cac_globals.cg_lock); + block = dcg->cg_lru_block; + count = 0; + while (block && count < IND_CACHE_MAX_BLOCKS_TO_FREE) { + if (block != locked_block && block->cb_state == IDX_CAC_BLOCK_CLEAN) { + to_free[count] = block; + count++; + } + block = block->cb_mr_used; + } + xt_unlock_mutex_ns(&ind_cac_globals.cg_lock); + + if (!count) + return blocks_freed; + + for (int i=0; i<count; i++) { + if (ind_free_block(ot, to_free[i])) + blocks_freed++; + if (blocks_freed >= blocks_required && + ind_cac_globals.cg_free_count >= ind_cac_globals.cg_max_free + blocks_required) + return blocks_freed; + } + + goto retry; +} + +/* + * ----------------------------------------------------------------------- + * MAIN CACHE FUNCTIONS + */ + +/* + * Fetch the block. Note, if we are about to write the block + * then there is no need to read it from disk! + */ +static XTIndBlockPtr ind_cac_fetch(XTOpenTablePtr ot, xtIndexNodeID address, DcSegmentPtr *ret_seg, xtBool read_data) +{ + register XTOpenFilePtr file = ot->ot_ind_file; + register XTIndBlockPtr block, new_block; + register DcSegmentPtr seg; + register u_int hash_idx; + register DcGlobalsRec *dcg = &ind_cac_globals; + size_t red_size; + +#ifdef DEBUG_CHECK_IND_CACHE + xt_ind_check_cache(NULL); +#endif + /* Address, plus file ID multiplied by my favorite prime number! */ + hash_idx = XT_NODE_ID(address) + (file->fr_id * 223); + seg = &dcg->cg_segment[hash_idx & IDX_CAC_SEGMENT_MASK]; + hash_idx = (hash_idx >> XT_INDEX_CACHE_SEGMENT_SHIFTS) % dcg->cg_hash_size; + + IDX_CAC_READ_LOCK(seg, ot->ot_thread); + block = seg->cs_hash_table[hash_idx]; + while (block) { + if (XT_NODE_ID(block->cb_address) == XT_NODE_ID(address) && block->cb_file_id == file->fr_id) { + ASSERT_NS(block->cb_state != IDX_CAC_BLOCK_FREE); + + /* Check how recently this page has been used: */ + if (XT_TIME_DIFF(block->cb_ru_time, dcg->cg_ru_now) > (dcg->cg_block_count >> 1)) { + xt_lock_mutex_ns(&dcg->cg_lock); + + /* Move to the front of the MRU list: */ + block->cb_ru_time = ++dcg->cg_ru_now; + if (dcg->cg_mru_block != block) { + /* Remove from the MRU list: */ + if (dcg->cg_lru_block == block) + dcg->cg_lru_block = block->cb_mr_used; + if (block->cb_lr_used) + block->cb_lr_used->cb_mr_used = block->cb_mr_used; + if (block->cb_mr_used) + block->cb_mr_used->cb_lr_used = block->cb_lr_used; + + /* Make the block the most recently used: */ + if ((block->cb_lr_used = dcg->cg_mru_block)) + dcg->cg_mru_block->cb_mr_used = block; + block->cb_mr_used = NULL; + dcg->cg_mru_block = block; + if (!dcg->cg_lru_block) + dcg->cg_lru_block = block; + } + + xt_unlock_mutex_ns(&dcg->cg_lock); + } + + *ret_seg = seg; +#ifdef DEBUG_CHECK_IND_CACHE + xt_ind_check_cache(NULL); +#endif + ot->ot_thread->st_statistics.st_ind_cache_hit++; + return block; + } + block = block->cb_next; + } + + /* Block not found... */ + IDX_CAC_UNLOCK(seg, ot->ot_thread); + + /* Check the open table reserve list first: */ + if ((new_block = ot->ot_ind_res_bufs)) { + ot->ot_ind_res_bufs = new_block->cb_next; + ot->ot_ind_res_count--; +#ifdef DEBUG_CHECK_IND_CACHE + xt_lock_mutex_ns(&dcg->cg_lock); + dcg->cg_reserved_by_ots--; + dcg->cg_read_count++; + xt_unlock_mutex_ns(&dcg->cg_lock); +#endif + goto use_free_block; + } + + free_some_blocks: + if (!dcg->cg_free_list) { + if (!ind_cac_free_lru_blocks(ot, 1, NULL)) { + if (!dcg->cg_free_list) { + xt_register_xterr(XT_REG_CONTEXT, XT_ERR_NO_INDEX_CACHE); +#ifdef DEBUG_CHECK_IND_CACHE + xt_ind_check_cache(NULL); +#endif + return NULL; + } + } + } + + /* Get a free block: */ + xt_lock_mutex_ns(&dcg->cg_lock); + if (!(new_block = dcg->cg_free_list)) { + xt_unlock_mutex_ns(&dcg->cg_lock); + goto free_some_blocks; + } + ASSERT_NS(new_block->cb_state == IDX_CAC_BLOCK_FREE); + dcg->cg_free_list = new_block->cb_next; + dcg->cg_free_count--; +#ifdef DEBUG_CHECK_IND_CACHE + dcg->cg_read_count++; +#endif + xt_unlock_mutex_ns(&dcg->cg_lock); + + use_free_block: + new_block->cb_address = address; + new_block->cb_file_id = file->fr_id; + new_block->cb_state = IDX_CAC_BLOCK_CLEAN; + new_block->cb_handle_count = 0; + new_block->cp_flush_seq = 0; + new_block->cb_dirty_next = NULL; + new_block->cb_dirty_prev = NULL; + + if (read_data) { + if (!xt_pread_file(file, xt_ind_node_to_offset(ot->ot_table, address), XT_INDEX_PAGE_SIZE, 0, new_block->cb_data, &red_size, &ot->ot_thread->st_statistics.st_ind, ot->ot_thread)) { + xt_lock_mutex_ns(&dcg->cg_lock); + new_block->cb_next = dcg->cg_free_list; + dcg->cg_free_list = new_block; + dcg->cg_free_count++; +#ifdef DEBUG_CHECK_IND_CACHE + dcg->cg_read_count--; +#endif + new_block->cb_state = IDX_CAC_BLOCK_FREE; + IDX_TRACE("%d- F%x\n", (int) XT_NODE_ID(address), (int) XT_GET_DISK_2(new_block->cb_data)); + xt_unlock_mutex_ns(&dcg->cg_lock); +#ifdef DEBUG_CHECK_IND_CACHE + xt_ind_check_cache(NULL); +#endif + return NULL; + } + IDX_TRACE("%d- R%x\n", (int) XT_NODE_ID(address), (int) XT_GET_DISK_2(new_block->cb_data)); + ot->ot_thread->st_statistics.st_ind_cache_miss++; + } + else + red_size = 0; + // PMC - I don't think this is required! memset(new_block->cb_data + red_size, 0, XT_INDEX_PAGE_SIZE - red_size); + + IDX_CAC_WRITE_LOCK(seg, ot->ot_thread); + block = seg->cs_hash_table[hash_idx]; + while (block) { + if (XT_NODE_ID(block->cb_address) == XT_NODE_ID(address) && block->cb_file_id == file->fr_id) { + /* Oops, someone else was faster! */ + xt_lock_mutex_ns(&dcg->cg_lock); + new_block->cb_next = dcg->cg_free_list; + dcg->cg_free_list = new_block; + dcg->cg_free_count++; +#ifdef DEBUG_CHECK_IND_CACHE + dcg->cg_read_count--; +#endif + new_block->cb_state = IDX_CAC_BLOCK_FREE; + IDX_TRACE("%d- F%x\n", (int) XT_NODE_ID(address), (int) XT_GET_DISK_2(new_block->cb_data)); + xt_unlock_mutex_ns(&dcg->cg_lock); + goto done_ok; + } + block = block->cb_next; + } + block = new_block; + + /* Make the block the most recently used: */ + xt_lock_mutex_ns(&dcg->cg_lock); + block->cb_ru_time = ++dcg->cg_ru_now; + if ((block->cb_lr_used = dcg->cg_mru_block)) + dcg->cg_mru_block->cb_mr_used = block; + block->cb_mr_used = NULL; + dcg->cg_mru_block = block; + if (!dcg->cg_lru_block) + dcg->cg_lru_block = block; +#ifdef DEBUG_CHECK_IND_CACHE + dcg->cg_read_count--; +#endif + xt_unlock_mutex_ns(&dcg->cg_lock); + + /* Add to the hash table: */ + block->cb_next = seg->cs_hash_table[hash_idx]; + seg->cs_hash_table[hash_idx] = block; + + done_ok: + *ret_seg = seg; +#ifdef DEBUG_CHECK_IND_CACHE + xt_ind_check_cache(NULL); +#endif + return block; +} + +static xtBool ind_cac_get(XTOpenTablePtr ot, xtIndexNodeID address, DcSegmentPtr *ret_seg, XTIndBlockPtr *ret_block) +{ + register XTOpenFilePtr file = ot->ot_ind_file; + register XTIndBlockPtr block; + register DcSegmentPtr seg; + register u_int hash_idx; + register DcGlobalsRec *dcg = &ind_cac_globals; + + hash_idx = XT_NODE_ID(address) + (file->fr_id * 223); + seg = &dcg->cg_segment[hash_idx & IDX_CAC_SEGMENT_MASK]; + hash_idx = (hash_idx >> XT_INDEX_CACHE_SEGMENT_SHIFTS) % dcg->cg_hash_size; + + IDX_CAC_READ_LOCK(seg, ot->ot_thread); + block = seg->cs_hash_table[hash_idx]; + while (block) { + if (XT_NODE_ID(block->cb_address) == XT_NODE_ID(address) && block->cb_file_id == file->fr_id) { + ASSERT_NS(block->cb_state != IDX_CAC_BLOCK_FREE); + + *ret_seg = seg; + *ret_block = block; + return OK; + } + block = block->cb_next; + } + IDX_CAC_UNLOCK(seg, ot->ot_thread); + + /* Block not found: */ + *ret_seg = NULL; + *ret_block = NULL; + return OK; +} + +xtPublic xtBool xt_ind_write(XTOpenTablePtr ot, XTIndexPtr ind, xtIndexNodeID address, size_t size, xtWord1 *data) +{ + XTIndBlockPtr block; + DcSegmentPtr seg; + + if (!(block = ind_cac_fetch(ot, address, &seg, FALSE))) + return FAILED; + + xt_atomicrwlock_xlock(&block->cb_lock, ot->ot_thread->t_id); + ASSERT_NS(block->cb_state == IDX_CAC_BLOCK_CLEAN || block->cb_state == IDX_CAC_BLOCK_DIRTY); + memcpy(block->cb_data, data, size); + block->cp_flush_seq = ot->ot_table->tab_ind_flush_seq; + if (block->cb_state != IDX_CAC_BLOCK_DIRTY) { + TRACK_BLOCK_WRITE(offset); + xt_spinlock_lock(&ind->mi_dirty_lock); + if ((block->cb_dirty_next = ind->mi_dirty_list)) + ind->mi_dirty_list->cb_dirty_prev = block; + block->cb_dirty_prev = NULL; + ind->mi_dirty_list = block; + ind->mi_dirty_blocks++; + xt_spinlock_unlock(&ind->mi_dirty_lock); + block->cb_state = IDX_CAC_BLOCK_DIRTY; + } + xt_atomicrwlock_unlock(&block->cb_lock, TRUE); + IDX_CAC_UNLOCK(seg, ot->ot_thread); +#ifdef XT_TRACK_INDEX_UPDATES + ot->ot_ind_changed++; +#endif + return OK; +} + +/* + * Update the cache, if in RAM. + */ +xtPublic xtBool xt_ind_write_cache(XTOpenTablePtr ot, xtIndexNodeID address, size_t size, xtWord1 *data) +{ + XTIndBlockPtr block; + DcSegmentPtr seg; + + if (!ind_cac_get(ot, address, &seg, &block)) + return FAILED; + + if (block) { + xt_atomicrwlock_xlock(&block->cb_lock, ot->ot_thread->t_id); + ASSERT_NS(block->cb_state == IDX_CAC_BLOCK_CLEAN || block->cb_state == IDX_CAC_BLOCK_DIRTY); + memcpy(block->cb_data, data, size); + xt_atomicrwlock_unlock(&block->cb_lock, TRUE); + IDX_CAC_UNLOCK(seg, ot->ot_thread); + } + + return OK; +} + +xtPublic xtBool xt_ind_clean(XTOpenTablePtr ot, XTIndexPtr ind, xtIndexNodeID address) +{ + XTIndBlockPtr block; + DcSegmentPtr seg; + + if (!ind_cac_get(ot, address, &seg, &block)) + return FAILED; + if (block) { + xt_atomicrwlock_xlock(&block->cb_lock, ot->ot_thread->t_id); + ASSERT_NS(block->cb_state == IDX_CAC_BLOCK_CLEAN || block->cb_state == IDX_CAC_BLOCK_DIRTY); + + if (block->cb_state == IDX_CAC_BLOCK_DIRTY) { + /* Take the block off the dirty list: */ + xt_spinlock_lock(&ind->mi_dirty_lock); + if (block->cb_dirty_next) + block->cb_dirty_next->cb_dirty_prev = block->cb_dirty_prev; + if (block->cb_dirty_prev) + block->cb_dirty_prev->cb_dirty_next = block->cb_dirty_next; + if (ind->mi_dirty_list == block) + ind->mi_dirty_list = block->cb_dirty_next; + ind->mi_dirty_blocks--; + xt_spinlock_unlock(&ind->mi_dirty_lock); + block->cb_state = IDX_CAC_BLOCK_CLEAN; + } + xt_atomicrwlock_unlock(&block->cb_lock, TRUE); + + IDX_CAC_UNLOCK(seg, ot->ot_thread); + } + + return OK; +} + +xtPublic xtBool xt_ind_read_bytes(XTOpenTablePtr ot, xtIndexNodeID address, size_t size, xtWord1 *data) +{ + XTIndBlockPtr block; + DcSegmentPtr seg; + + if (!(block = ind_cac_fetch(ot, address, &seg, TRUE))) + return FAILED; + + xt_atomicrwlock_slock(&block->cb_lock); + memcpy(data, block->cb_data, size); + xt_atomicrwlock_unlock(&block->cb_lock, FALSE); + IDX_CAC_UNLOCK(seg, ot->ot_thread); + return OK; +} + +xtPublic xtBool xt_ind_fetch(XTOpenTablePtr ot, xtIndexNodeID address, XTPageLockType ltype, XTIndReferencePtr iref) +{ + register XTIndBlockPtr block; + DcSegmentPtr seg; + xtWord2 branch_size; + + ASSERT_NS(iref->ir_ulock == XT_UNLOCK_NONE); + if (!(block = ind_cac_fetch(ot, address, &seg, TRUE))) + return NULL; + + branch_size = XT_GET_DISK_2(((XTIdxBranchDPtr) block->cb_data)->tb_size_2); + if (XT_GET_INDEX_BLOCK_LEN(branch_size) < 2 || XT_GET_INDEX_BLOCK_LEN(branch_size) > XT_INDEX_PAGE_SIZE) { + IDX_CAC_UNLOCK(seg, ot->ot_thread); + xt_register_taberr(XT_REG_CONTEXT, XT_ERR_INDEX_CORRUPTED, ot->ot_table->tab_name); + return FAILED; + } + + if (ltype == XT_XLOCK_LEAF) { + if (XT_IS_NODE(branch_size)) + ltype = XT_LOCK_READ; + else + ltype = XT_LOCK_WRITE; + } + + if (ltype == XT_LOCK_WRITE) { + xt_atomicrwlock_xlock(&block->cb_lock, ot->ot_thread->t_id); + iref->ir_ulock = XT_UNLOCK_WRITE; + } + else { + xt_atomicrwlock_slock(&block->cb_lock); + iref->ir_ulock = XT_UNLOCK_READ; + } + + IDX_CAC_UNLOCK(seg, ot->ot_thread); + + /* {DIRECT-IO} + * Direct I/O requires that the buffer is 512 byte aligned. + * To do this, cb_data is turned into a pointer, instead + * of an array. + * As a result, we need to pass a pointer to both the + * cache block and the cache block data: + */ + iref->ir_block = block; + iref->ir_branch = (XTIdxBranchDPtr) block->cb_data; + return OK; +} + +xtPublic xtBool xt_ind_release(XTOpenTablePtr ot, XTIndexPtr ind, XTPageUnlockType XT_UNUSED(utype), XTIndReferencePtr iref) +{ + register XTIndBlockPtr block; + + block = iref->ir_block; + + if (utype == XT_UNLOCK_R_UPDATE || utype == XT_UNLOCK_W_UPDATE) { + /* The page was update: */ + ASSERT_NS(block->cb_state == IDX_CAC_BLOCK_CLEAN || block->cb_state == IDX_CAC_BLOCK_DIRTY); + block->cp_flush_seq = ot->ot_table->tab_ind_flush_seq; + if (block->cb_state != IDX_CAC_BLOCK_DIRTY) { + TRACK_BLOCK_WRITE(offset); + xt_spinlock_lock(&ind->mi_dirty_lock); + if ((block->cb_dirty_next = ind->mi_dirty_list)) + ind->mi_dirty_list->cb_dirty_prev = block; + block->cb_dirty_prev = NULL; + ind->mi_dirty_list = block; + ind->mi_dirty_blocks++; + xt_spinlock_unlock(&ind->mi_dirty_lock); + block->cb_state = IDX_CAC_BLOCK_DIRTY; + } + } + +#ifdef DEBUG + if (utype == XT_UNLOCK_W_UPDATE) + utype = XT_UNLOCK_WRITE; + else if (utype == XT_UNLOCK_R_UPDATE) + utype = XT_UNLOCK_READ; + ASSERT_NS(iref->ir_ulock == utype); +#endif + xt_atomicrwlock_unlock(&block->cb_lock, iref->ir_ulock == XT_UNLOCK_WRITE ? TRUE : FALSE); +#ifdef DEBUG + iref->ir_ulock = XT_UNLOCK_NONE; +#endif + return OK; +} + +xtPublic xtBool xt_ind_reserve(XTOpenTablePtr ot, u_int count, XTIdxBranchDPtr not_this) +{ + register XTIndBlockPtr block; + register DcGlobalsRec *dcg = &ind_cac_globals; + +#ifdef XT_TRACK_INDEX_UPDATES + ot->ot_ind_reserved = count; + ot->ot_ind_reads = 0; +#endif +#ifdef DEBUG_CHECK_IND_CACHE + xt_ind_check_cache(NULL); +#endif + while (ot->ot_ind_res_count < count) { + if (!dcg->cg_free_list) { + if (!ind_cac_free_lru_blocks(ot, count - ot->ot_ind_res_count, not_this)) { + if (!dcg->cg_free_list) { + xt_ind_free_reserved(ot); + xt_register_xterr(XT_REG_CONTEXT, XT_ERR_NO_INDEX_CACHE); +#ifdef DEBUG_CHECK_IND_CACHE + xt_ind_check_cache(NULL); +#endif + return FAILED; + } + } + } + + /* Get a free block: */ + xt_lock_mutex_ns(&dcg->cg_lock); + while (ot->ot_ind_res_count < count && (block = dcg->cg_free_list)) { + ASSERT_NS(block->cb_state == IDX_CAC_BLOCK_FREE); + dcg->cg_free_list = block->cb_next; + dcg->cg_free_count--; + block->cb_next = ot->ot_ind_res_bufs; + ot->ot_ind_res_bufs = block; + ot->ot_ind_res_count++; +#ifdef DEBUG_CHECK_IND_CACHE + dcg->cg_reserved_by_ots++; +#endif + } + xt_unlock_mutex_ns(&dcg->cg_lock); + } +#ifdef DEBUG_CHECK_IND_CACHE + xt_ind_check_cache(NULL); +#endif + return OK; +} + +xtPublic void xt_ind_free_reserved(XTOpenTablePtr ot) +{ +#ifdef DEBUG_CHECK_IND_CACHE + xt_ind_check_cache(NULL); +#endif + if (ot->ot_ind_res_bufs) { + register XTIndBlockPtr block, fblock; + register DcGlobalsRec *dcg = &ind_cac_globals; + + xt_lock_mutex_ns(&dcg->cg_lock); + block = ot->ot_ind_res_bufs; + while (block) { + fblock = block; + block = block->cb_next; + + fblock->cb_next = dcg->cg_free_list; + dcg->cg_free_list = fblock; +#ifdef DEBUG_CHECK_IND_CACHE + dcg->cg_reserved_by_ots--; +#endif + dcg->cg_free_count++; + } + xt_unlock_mutex_ns(&dcg->cg_lock); + ot->ot_ind_res_bufs = NULL; + ot->ot_ind_res_count = 0; + } +#ifdef DEBUG_CHECK_IND_CACHE + xt_ind_check_cache(NULL); +#endif +} + +xtPublic void xt_ind_unreserve(XTOpenTablePtr ot) +{ + if (!ind_cac_globals.cg_free_list) + xt_ind_free_reserved(ot); +} + +xtPublic void xt_load_indices(XTThreadPtr self, XTOpenTablePtr ot) +{ + register XTTableHPtr tab = ot->ot_table; + register XTIndBlockPtr block; + DcSegmentPtr seg; + xtIndexNodeID id; + + xt_lock_mutex_ns(&tab->tab_ind_flush_lock); + + for (id=1; id < XT_NODE_ID(tab->tab_ind_eof); id++) { + if (!(block = ind_cac_fetch(ot, id, &seg, TRUE))) { + xt_unlock_mutex_ns(&tab->tab_ind_flush_lock); + xt_throw(self); + } + IDX_CAC_UNLOCK(seg, ot->ot_thread); + } + + xt_unlock_mutex_ns(&tab->tab_ind_flush_lock); +} + + diff --git a/storage/pbxt/src/cache_xt.h b/storage/pbxt/src/cache_xt.h new file mode 100644 index 00000000000..d113bb2f907 --- /dev/null +++ b/storage/pbxt/src/cache_xt.h @@ -0,0 +1,148 @@ +/* Copyright (c) 2005 PrimeBase Technologies GmbH + * + * PrimeBase XT + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You 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 + * + * 2005-05-24 Paul McCullagh + * + * H&G2JCtL + */ +#ifndef __xt_cache_h__ +#define __xt_cache_h__ + +//#define XT_USE_MYSYS + +#include "filesys_xt.h" +#include "index_xt.h" + +struct XTOpenTable; +struct XTIdxReadBuffer; + +#ifdef DEBUG +//#define XT_USE_CACHE_DEBUG_SIZES +#endif + +#ifdef XT_USE_CACHE_DEBUG_SIZES +#define XT_INDEX_CACHE_SEGMENT_SHIFTS 1 +#else +#define XT_INDEX_CACHE_SEGMENT_SHIFTS 3 +#endif + +#define IDX_CAC_BLOCK_FREE 0 +#define IDX_CAC_BLOCK_CLEAN 1 +#define IDX_CAC_BLOCK_DIRTY 2 + +typedef enum XTPageLockType { XT_LOCK_READ, XT_LOCK_WRITE, XT_XLOCK_LEAF }; +typedef enum XTPageUnlockType { XT_UNLOCK_NONE, XT_UNLOCK_READ, XT_UNLOCK_WRITE, XT_UNLOCK_R_UPDATE, XT_UNLOCK_W_UPDATE }; + +/* A block is X locked if it is being changed or freed. + * A block is S locked if it is being read. + */ +typedef struct XTIndBlock { + xtIndexNodeID cb_address; /* The block address. */ + u_int cb_file_id; /* The file id of the block. */ + /* This is protected by cs_lock */ + struct XTIndBlock *cb_next; /* Pointer to next block on hash list, or next free block on free list. */ + /* This is protected by mi_dirty_lock */ + struct XTIndBlock *cb_dirty_next; /* Double link for dirty blocks, next pointer. */ + struct XTIndBlock *cb_dirty_prev; /* Double link for dirty blocks, previous pointer. */ + /* This is protected by cg_lock */ + xtWord4 cb_ru_time; /* If this is in the top 1/4 don't change position in MRU list. */ + struct XTIndBlock *cb_mr_used; /* More recently used blocks. */ + struct XTIndBlock *cb_lr_used; /* Less recently used blocks. */ + /* Protected by cb_lock: */ + XTAtomicRWLockRec cb_lock; + xtWord1 cb_state; /* Block status. */ + xtWord2 cb_handle_count; /* TRUE if this page is referenced by a handle. */ + xtWord2 cp_flush_seq; +#ifdef XT_USE_DIRECT_IO_ON_INDEX + xtWord1 *cb_data; +#else + xtWord1 cb_data[XT_INDEX_PAGE_SIZE]; +#endif +} XTIndBlockRec, *XTIndBlockPtr; + +typedef struct XTIndReference { + XTPageUnlockType ir_ulock; + XTIndBlockPtr ir_block; + XTIdxBranchDPtr ir_branch; +} XTIndReferenceRec, *XTIndReferencePtr; + +typedef struct XTIndFreeBlock { + XTDiskValue1 if_status_1; + XTDiskValue1 if_unused1_1; + XTDiskValue2 if_unused2_2; + XTDiskValue4 if_unused3_4; + XTDiskValue8 if_next_block_8; +} XTIndFreeBlockRec, *XTIndFreeBlockPtr; + +typedef struct XTIndHandleBlock { + xtWord4 hb_ref_count; + struct XTIndHandleBlock *hb_next; + XTIdxBranchDRec hb_branch; +} XTIndHandleBlockRec, *XTIndHandleBlockPtr; + +typedef struct XTIndHandle { + struct XTIndHandle *ih_next; + struct XTIndHandle *ih_prev; + XTSpinLockRec ih_lock; + xtIndexNodeID ih_address; + xtBool ih_cache_reference; /* True if this handle references the cache. */ + union { + XTIndBlockPtr ih_cache_block; + XTIndHandleBlockPtr ih_handle_block; + } x; + XTIdxBranchDPtr ih_branch; +} XTIndHandleRec, *XTIndHandlePtr; + +void xt_ind_init(XTThreadPtr self, size_t cache_size); +void xt_ind_exit(XTThreadPtr self); + +xtInt8 xt_ind_get_usage(); +xtInt8 xt_ind_get_size(); +xtBool xt_ind_write(struct XTOpenTable *ot, XTIndexPtr ind, xtIndexNodeID offset, size_t size, xtWord1 *data); +xtBool xt_ind_write_cache(struct XTOpenTable *ot, xtIndexNodeID offset, size_t size, xtWord1 *data); +xtBool xt_ind_clean(struct XTOpenTable *ot, XTIndexPtr ind, xtIndexNodeID offset); +xtBool xt_ind_read_bytes(struct XTOpenTable *ot, xtIndexNodeID offset, size_t size, xtWord1 *data); +void xt_ind_check_cache(XTIndexPtr ind); +xtBool xt_ind_reserve(struct XTOpenTable *ot, u_int count, XTIdxBranchDPtr not_this); +void xt_ind_free_reserved(struct XTOpenTable *ot); +void xt_ind_unreserve(struct XTOpenTable *ot); +void xt_load_indices(XTThreadPtr self, struct XTOpenTable *ot); + +xtBool xt_ind_fetch(struct XTOpenTable *ot, xtIndexNodeID node, XTPageLockType ltype, XTIndReferencePtr iref); +xtBool xt_ind_release(struct XTOpenTable *ot, XTIndexPtr ind, XTPageUnlockType utype, XTIndReferencePtr iref); + +void xt_ind_lock_handle(XTIndHandlePtr handle); +void xt_ind_unlock_handle(XTIndHandlePtr handle); +xtBool xt_ind_copy_on_write(XTIndReferencePtr iref); + +XTIndHandlePtr xt_ind_get_handle(struct XTOpenTable *ot, XTIndexPtr ind, XTIndReferencePtr iref); +void xt_ind_release_handle(XTIndHandlePtr handle, xtBool have_lock, XTThreadPtr thread); + +#ifdef DEBUG +//#define DEBUG_CHECK_IND_CACHE +#endif + +//#define XT_TRACE_INDEX + +#ifdef XT_TRACE_INDEX +#define IDX_TRACE(x, y, z) xt_trace(x, y, z) +#else +#define IDX_TRACE(x, y, z) +#endif + +#endif diff --git a/storage/pbxt/src/ccutils_xt.cc b/storage/pbxt/src/ccutils_xt.cc new file mode 100644 index 00000000000..1d93e4c34b3 --- /dev/null +++ b/storage/pbxt/src/ccutils_xt.cc @@ -0,0 +1,69 @@ +/* Copyright (c) 2005 PrimeBase Technologies GmbH + * + * PrimeBase XT + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You 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 + * + * 2006-05-16 Paul McCullagh + * + * H&G2JCtL + * + * C++ Utilities + */ + +#include "xt_config.h" + +#include "pthread_xt.h" +#include "ccutils_xt.h" +#include "bsearch_xt.h" + +static int ccu_compare_object(XTThreadPtr XT_UNUSED(self), register const void XT_UNUSED(*thunk), register const void *a, register const void *b) +{ + XTObject *obj_ptr = (XTObject *) b; + + return obj_ptr->compare(a); +} + +void XTListImp::append(XTThreadPtr self, XTObject *info, void *key) { + size_t idx; + + if (li_item_count == 0) + idx = 0; + else if (li_item_count == 1) { + int r; + + if ((r = li_items[0]->compare(key)) == 0) + idx = 0; + else if (r < 0) + idx = 0; + else + idx = 1; + } + else { + xt_bsearch(self, key, li_items, li_item_count, sizeof(void *), &idx, NULL, ccu_compare_object); + } + + if (!xt_realloc(NULL, (void **) &li_items, (li_item_count + 1) * sizeof(void *))) { + if (li_referenced) + info->release(self); + xt_throw_errno(XT_CONTEXT, XT_ENOMEM); + return; + } + memmove(&li_items[idx+1], &li_items[idx], (li_item_count-idx) * sizeof(void *)); + li_items[idx] = info; + li_item_count++; +} + + diff --git a/storage/pbxt/src/ccutils_xt.h b/storage/pbxt/src/ccutils_xt.h new file mode 100644 index 00000000000..a800073869d --- /dev/null +++ b/storage/pbxt/src/ccutils_xt.h @@ -0,0 +1,220 @@ +/* Copyright (c) 2005 PrimeBase Technologies GmbH + * + * PrimeBase XT + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You 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 + * + * 2006-05-16 Paul McCullagh + * + * H&G2JCtL + * + * C++ Utilities + */ + +#ifndef __ccutils_xt_h__ +#define __ccutils_xt_h__ + +#include <errno.h> + +#include "xt_defs.h" +#include "thread_xt.h" + +class XTObject +{ + private: + u_int o_refcnt; + + public: + inline XTObject() { o_refcnt = 1; } + + virtual ~XTObject() { } + + inline void reference() { + o_refcnt++; + } + + inline void release(XTThreadPtr self) { + ASSERT(o_refcnt > 0); + o_refcnt--; + if (o_refcnt == 0) { + finalize(self); + delete this; + } + } + + virtual XTObject *factory(XTThreadPtr self) { + XTObject *new_obj; + + if (!(new_obj = new XTObject())) + xt_throw_errno(XT_CONTEXT, XT_ENOMEM); + return new_obj; + } + + virtual XTObject *clone(XTThreadPtr self) { + XTObject *new_obj; + + new_obj = factory(self); + new_obj->init(self, this); + return new_obj; + } + + virtual void init(XTThreadPtr self) { (void) self; } + virtual void init(XTThreadPtr self, XTObject *obj) { (void) obj; init(self); } + virtual void finalize(XTThreadPtr self) { (void) self; } + virtual int compare(const void *key) { (void) key; return -1; } +}; + +class XTListImp +{ + protected: + bool li_referenced; + u_int li_item_count; + XTObject **li_items; + + public: + inline XTListImp() : li_referenced(true), li_item_count(0), li_items(NULL) { } + + inline void setNonReferenced() { li_referenced = false; } + + void append(XTThreadPtr self, XTObject *info) { + if (!xt_realloc(NULL, (void **) &li_items, (li_item_count + 1) * sizeof(void *))) { + if (li_referenced) + info->release(self); + xt_throw_errno(XT_CONTEXT, XT_ENOMEM); + return; + } + li_items[li_item_count] = info; + li_item_count++; + } + + void insert(XTThreadPtr self, XTObject *info, u_int i) { + if (!xt_realloc(NULL, (void **) &li_items, (li_item_count + 1) * sizeof(void *))) { + if (li_referenced) + info->release(self); + xt_throw_errno(XT_CONTEXT, XT_ENOMEM); + return; + } + memmove(&li_items[i+1], &li_items[i], (li_item_count-i) * sizeof(XTObject *)); + li_items[i] = info; + li_item_count++; + } + + void addToFront(XTThreadPtr self, XTObject *info) { + insert(self, info, 0); + } + + /* Will sort! */ + void append(XTThreadPtr self, XTObject *info, void *key); + + inline bool remove(XTObject *info) { + for (u_int i=0; i<li_item_count; i++) { + if (li_items[i] == info) { + li_item_count--; + memmove(&li_items[i], &li_items[i+1], (li_item_count - i) * sizeof(XTObject *)); + return true; + } + } + return false; + } + + inline bool remove(XTThreadPtr self, u_int i) { + XTObject *item; + + if (i >= li_item_count) + return false; + item = li_items[i]; + li_item_count--; + memmove(&li_items[i], &li_items[i+1], (li_item_count - i) * sizeof(void *)); + if (li_referenced) + item->release(self); + return true; + } + + inline XTObject *take(u_int i) { + XTObject *item; + + if (i >= li_item_count) + return NULL; + item = li_items[i]; + li_item_count--; + memmove(&li_items[i], &li_items[i+1], (li_item_count - i) * sizeof(void *)); + return item; + } + + inline u_int size() const { return li_item_count; } + + inline void setEmpty(XTThreadPtr self) { + if (li_items) + xt_free(self, li_items); + li_item_count = 0; + li_items = NULL; + } + + inline bool isEmpty() { return li_item_count == 0; } + + inline XTObject *itemAt(u_int i) const { + if (i >= li_item_count) + return NULL; + return li_items[i]; + } +}; + + +template <class T> class XTList : public XTListImp +{ + public: + inline XTList() : XTListImp() { } + + inline void append(XTThreadPtr self, T *a) { XTListImp::append(self, a); } + inline void insert(XTThreadPtr self, T *a, u_int i) { XTListImp::insert(self, a, i); } + inline void addToFront(XTThreadPtr self, T *a) { XTListImp::addToFront(self, a); } + + inline bool remove(T *a) { return XTListImp::remove(a); } + + inline bool remove(XTThreadPtr self, u_int i) { return XTListImp::remove(self, i); } + + inline T *take(u_int i) { return (T *) XTListImp::take(i); } + + inline T *itemAt(u_int i) const { return (T *) XTListImp::itemAt(i); } + + inline u_int indexOf(T *a) { + u_int i; + + for (i=0; i<size(); i++) { + if (itemAt(i) == a) + break; + } + return i; + } + + void deleteAll(XTThreadPtr self) + { + for (u_int i=0; i<size(); i++) { + if (li_referenced) + itemAt(i)->release(self); + } + setEmpty(self); + } + + void clone(XTThreadPtr self, XTListImp *list) + { + deleteAll(self); + for (u_int i=0; i<list->size(); i++) { + XTListImp::append(self, list->itemAt(i)->clone(self)); + } + } +}; + +#endif diff --git a/storage/pbxt/src/database_xt.cc b/storage/pbxt/src/database_xt.cc new file mode 100644 index 00000000000..d15ffc6d06e --- /dev/null +++ b/storage/pbxt/src/database_xt.cc @@ -0,0 +1,1280 @@ +/* Copyright (c) 2005 PrimeBase Technologies GmbH + * + * PrimeBase XT + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You 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 + * + * 2005-01-15 Paul McCullagh + * + * H&G2JCtL + */ + +#include "xt_config.h" + +#include <string.h> +#include <stdio.h> + +#include "pthread_xt.h" +#include "hashtab_xt.h" +#include "filesys_xt.h" +#include "database_xt.h" +#include "memory_xt.h" +#include "heap_xt.h" +#include "datalog_xt.h" +#include "strutil_xt.h" +#include "util_xt.h" +#include "trace_xt.h" + +#ifdef DEBUG +//#define XT_TEST_XACT_OVERFLOW +#endif + +#ifndef NAME_MAX +#define NAME_MAX 128 +#endif + +/* + * ----------------------------------------------------------------------- + * GLOBALS + */ + +xtPublic xtLogOffset xt_db_log_file_threshold; +xtPublic size_t xt_db_log_buffer_size; +xtPublic size_t xt_db_transaction_buffer_size; +xtPublic size_t xt_db_checkpoint_frequency; +xtPublic off_t xt_db_data_log_threshold; +xtPublic size_t xt_db_data_file_grow_size; +xtPublic size_t xt_db_row_file_grow_size; +xtPublic int xt_db_garbage_threshold; +xtPublic int xt_db_log_file_count; +xtPublic int xt_db_auto_increment_mode; /* 0 = MySQL compatible, 1 = PrimeBase Compatible. */ +xtPublic int xt_db_offline_log_function; /* 0 = recycle logs, 1 = delete logs, 2 = keep logs */ +xtPublic int xt_db_sweeper_priority; /* 0 = low (default), 1 = normal, 2 = high */ + +xtPublic XTSortedListPtr xt_db_open_db_by_id = NULL; +xtPublic XTHashTabPtr xt_db_open_databases = NULL; +xtPublic time_t xt_db_approximate_time = 0; /* A "fast" alternative timer (not too accurate). */ + +static xtDatabaseID db_next_id = 1; +static volatile XTOpenFilePtr db_lock_file = NULL; + +/* + * ----------------------------------------------------------------------- + * LOCK/UNLOCK INSTALLATION + */ + +xtPublic void xt_lock_installation(XTThreadPtr self, char *installation_path) +{ + char file_path[PATH_MAX]; + char buffer[101]; + size_t red_size; + llong pid; + xtBool cd = pbxt_crash_debug; + + xt_strcpy(PATH_MAX, file_path, installation_path); + xt_add_pbxt_file(PATH_MAX, file_path, "no-debug"); + if (xt_fs_exists(file_path)) + pbxt_crash_debug = FALSE; + xt_strcpy(PATH_MAX, file_path, installation_path); + xt_add_pbxt_file(PATH_MAX, file_path, "crash-debug"); + if (xt_fs_exists(file_path)) + pbxt_crash_debug = TRUE; + + if (pbxt_crash_debug != cd) { + if (pbxt_crash_debug) + xt_logf(XT_NT_WARNING, "Crash debugging has been turned on ('crash-debug' file exists)\n"); + else + xt_logf(XT_NT_WARNING, "Crash debugging has been turned off ('no-debug' file exists)\n"); + } + else if (pbxt_crash_debug) + xt_logf(XT_NT_WARNING, "Crash debugging is enabled\n"); + + /* Moved the lock file out of the pbxt directory so that + * it is possible to drop the pbxt database! + */ + xt_strcpy(PATH_MAX, file_path, installation_path); + xt_add_dir_char(PATH_MAX, file_path); + xt_strcat(PATH_MAX, file_path, "pbxt-lock"); + db_lock_file = xt_open_file(self, file_path, XT_FS_CREATE | XT_FS_MAKE_PATH); + + try_(a) { + if (!xt_lock_file(self, db_lock_file)) { + xt_logf(XT_NT_ERROR, "A server appears to already be running\n"); + xt_logf(XT_NT_ERROR, "The file: %s, is locked\n", file_path); + xt_throw_xterr(XT_CONTEXT, XT_ERR_SERVER_RUNNING); + } + if (!xt_pread_file(db_lock_file, 0, 100, 0, buffer, &red_size, &self->st_statistics.st_rec, self)) + xt_throw(self); + if (red_size > 0) { + buffer[red_size] = 0; +#ifdef XT_WIN + pid = (llong) _atoi64(buffer); +#else + pid = atoll(buffer); +#endif + /* Problem with this code is, after a restart + * the process ID's are reused. + * If some system process grabs the proc id that + * the server had on the last run, then + * the database will not start. + if (xt_process_exists((xtProcID) pid)) { + xt_logf(XT_NT_ERROR, "A server appears to already be running, process ID: %lld\n", pid); + xt_logf(XT_NT_ERROR, "Remove the file: %s, if this is not the case\n", file_path); + xt_throw_xterr(XT_CONTEXT, XT_ERR_SERVER_RUNNING); + } + */ + xt_logf(XT_NT_INFO, "The server was not shutdown correctly, recovery required\n"); +#ifdef XT_BACKUP_BEFORE_RECOVERY + if (pbxt_crash_debug) { + /* The server was not shut down correctly. Make a backup before + * we start recovery. + */ + char extension[100]; + + for (int i=1;;i++) { + xt_strcpy(PATH_MAX, file_path, installation_path); + xt_remove_dir_char(file_path); + sprintf(extension, "-recovery-%d", i); + xt_strcat(PATH_MAX, file_path, extension); + if (!xt_fs_exists(file_path)) + break; + } + xt_logf(XT_NT_INFO, "In order to reproduce recovery errors a backup of the installation\n"); + xt_logf(XT_NT_INFO, "will be made to:\n"); + xt_logf(XT_NT_INFO, "%s\n", file_path); + xt_logf(XT_NT_INFO, "Copy in progress...\n"); + xt_fs_copy_dir(self, installation_path, file_path); + xt_logf(XT_NT_INFO, "Copy OK\n"); + } +#endif + } + + sprintf(buffer, "%lld", (llong) xt_getpid()); + xt_set_eof_file(self, db_lock_file, 0); + if (!xt_pwrite_file(db_lock_file, 0, strlen(buffer), buffer, &self->st_statistics.st_rec, self)) + xt_throw(self); + } + catch_(a) { + xt_close_file(self, db_lock_file); + db_lock_file = NULL; + xt_throw(self); + } + cont_(a); +} + +xtPublic void xt_unlock_installation(XTThreadPtr self, char *installation_path) +{ + if (db_lock_file) { + char lock_file[PATH_MAX]; + + xt_unlock_file(NULL, db_lock_file); + xt_close_file_ns(db_lock_file); + db_lock_file = NULL; + + xt_strcpy(PATH_MAX, lock_file, installation_path); + xt_add_dir_char(PATH_MAX, lock_file); + xt_strcat(PATH_MAX, lock_file, "pbxt-lock"); + xt_fs_delete(self, lock_file); + } +} + +int *xt_bad_pointer = 0; + +void xt_crash_me(void) +{ + if (pbxt_crash_debug) + *xt_bad_pointer = 123; +} + +/* + * ----------------------------------------------------------------------- + * INIT/EXIT DATABASE + */ + +static xtBool db_hash_comp(void *key, void *data) +{ + XTDatabaseHPtr db = (XTDatabaseHPtr) data; + + return strcmp((char *) key, db->db_name) == 0; +} + +static xtHashValue db_hash(xtBool is_key, void *key_data) +{ + XTDatabaseHPtr db = (XTDatabaseHPtr) key_data; + + if (is_key) + return xt_ht_hash((char *) key_data); + return xt_ht_hash(db->db_name); +} + +static xtBool db_hash_comp_ci(void *key, void *data) +{ + XTDatabaseHPtr db = (XTDatabaseHPtr) data; + + return strcasecmp((char *) key, db->db_name) == 0; +} + +static xtHashValue db_hash_ci(xtBool is_key, void *key_data) +{ + XTDatabaseHPtr db = (XTDatabaseHPtr) key_data; + + if (is_key) + return xt_ht_casehash((char *) key_data); + return xt_ht_casehash(db->db_name); +} + +static void db_hash_free(XTThreadPtr self, void *data) +{ + xt_heap_release(self, (XTDatabaseHPtr) data); +} + +static int db_cmp_db_id(struct XTThread XT_UNUSED(*self), register const void XT_UNUSED(*thunk), register const void *a, register const void *b) +{ + xtDatabaseID db_id = *((xtDatabaseID *) a); + XTDatabaseHPtr *db_ptr = (XTDatabaseHPtr *) b; + + if (db_id == (*db_ptr)->db_id) + return 0; + if (db_id < (*db_ptr)->db_id) + return -1; + return 1; +} + +xtPublic void xt_init_databases(XTThreadPtr self) +{ + if (pbxt_ignore_case) + xt_db_open_databases = xt_new_hashtable(self, db_hash_comp_ci, db_hash_ci, db_hash_free, TRUE, TRUE); + else + xt_db_open_databases = xt_new_hashtable(self, db_hash_comp, db_hash, db_hash_free, TRUE, TRUE); + xt_db_open_db_by_id = xt_new_sortedlist(self, sizeof(XTDatabaseHPtr), 20, 10, db_cmp_db_id, NULL, NULL, FALSE, FALSE); +} + +xtPublic void xt_stop_database_threads(XTThreadPtr self, xtBool sync) +{ + u_int len = 0; + XTDatabaseHPtr *dbptr; + XTDatabaseHPtr db = NULL; + + if (xt_db_open_db_by_id) + len = xt_sl_get_size(xt_db_open_db_by_id); + for (u_int i=0; i<len; i++) { + if ((dbptr = (XTDatabaseHPtr *) xt_sl_item_at(xt_db_open_db_by_id, i))) { + db = *dbptr; + if (sync) { + /* Wait for the sweeper: */ + xt_wait_for_sweeper(self, db, 16); + + /* Wait for the writer: */ + xt_wait_for_writer(self, db); + + /* Wait for the checkpointer: */ + xt_wait_for_checkpointer(self, db); + } + xt_stop_checkpointer(self, db); + xt_stop_writer(self, db); + xt_stop_sweeper(self, db); + xt_stop_compactor(self, db); + } + } +} + +xtPublic void xt_exit_databases(XTThreadPtr self) +{ + if (xt_db_open_databases) { + xt_free_hashtable(self, xt_db_open_databases); + xt_db_open_databases = NULL; + } + if (xt_db_open_db_by_id) { + xt_free_sortedlist(self, xt_db_open_db_by_id); + xt_db_open_db_by_id = NULL; + } +} + +xtPublic void xt_create_database(XTThreadPtr self, char *path) +{ + xt_fs_mkdir(self, path); +} + +static void db_finalize(XTThreadPtr self, void *x) +{ + XTDatabaseHPtr db = (XTDatabaseHPtr) x; + + xt_stop_checkpointer(self, db); + xt_stop_compactor(self, db); + xt_stop_sweeper(self, db); + xt_stop_writer(self, db); + + xt_sl_delete(self, xt_db_open_db_by_id, &db->db_id); + /* + * Important is that xt_db_pool_exit() is called + * before xt_xn_exit_db() because xt_xn_exit_db() + * frees the checkpoint information which + * may be required to shutdown the tables, which + * flushes tables, and therefore does a checkpoint. + */ + /* This was the previous order of shutdown: + xt_xn_exit_db(self, db); + xt_dl_exit_db(self, db); + xt_db_pool_exit(self, db); + db->db_indlogs.ilp_exit(self); + */ + + xt_db_pool_exit(self, db); + db->db_indlogs.ilp_exit(self); + xt_dl_exit_db(self, db); + xt_xn_exit_db(self, db); + xt_tab_exit_db(self, db); + if (db->db_name) { + xt_free(self, db->db_name); + db->db_name = NULL; + } + if (db->db_main_path) { + xt_free(self, db->db_main_path); + db->db_main_path = NULL; + } +} + +static void db_onrelease(XTThreadPtr self, void XT_UNUSED(*x)) +{ + /* Signal threads waiting for exclusive use of the database: */ + if (xt_db_open_databases) // The database may already be closed. + xt_ht_signal(self, xt_db_open_databases); +} + +xtPublic void xt_add_pbxt_file(size_t size, char *path, const char *file) +{ + xt_add_dir_char(size, path); + xt_strcat(size, path, "pbxt"); + xt_add_dir_char(size, path); + xt_strcat(size, path, file); +} + +xtPublic void xt_add_location_file(size_t size, char *path) +{ + xt_add_dir_char(size, path); + xt_strcat(size, path, "pbxt"); + xt_add_dir_char(size, path); + xt_strcat(size, path, "location"); +} + +xtPublic void xt_add_pbxt_dir(size_t size, char *path) +{ + xt_add_dir_char(size, path); + xt_strcat(size, path, "pbxt"); +} + +xtPublic void xt_add_system_dir(size_t size, char *path) +{ + xt_add_dir_char(size, path); + xt_strcat(size, path, "pbxt"); + xt_add_dir_char(size, path); + xt_strcat(size, path, "system"); +} + +xtPublic void xt_add_data_dir(size_t size, char *path) +{ + xt_add_dir_char(size, path); + xt_strcat(size, path, "pbxt"); + xt_add_dir_char(size, path); + xt_strcat(size, path, "data"); +} + +/* + * I have a problem here. I cannot rely on the path given to xt_get_database() to be + * consistant. When called from ha_create_table() the path is not modified. + * However when called from ha_open() the path is first transformed by a call to + * fn_format(). I have given an example from a stack trace below. + * + * In this case the odd path comes from the option: + * --tmpdir=/Users/build/Development/mysql/debug-mysql/mysql-test/var//tmp + * + * #3 0x001a3818 in ha_pbxt::create(char const*, st_table*, st_ha_create_information*) + * (this=0x2036898, table_path=0xf0060bd0 "/users/build/development/mysql/debug-my + * sql/mysql-test/var//tmp/#sql5718_1_0.frm", table_arg=0xf00601c0, + * create_info=0x2017410) at ha_pbxt.cc:2323 + * #4 0x00140d74 in ha_create_table(char const*, st_ha_create_information*, bool) + * (name=0xf0060bd0 "/users/build/development/mysql/debug-mysql/mysql-te + * st/var//tmp/#sql5718_1_0.frm", create_info=0x2017410, + * update_create_info=false) at handler.cc:1387 + * + * #4 0x0013f7a4 in handler::ha_open(char const*, int, int) (this=0x203ba98, + * name=0xf005eb70 "/users/build/development/mysql/debug-mysql/mysql-te + * st/var/tmp/#sql5718_1_1", mode=2, test_if_locked=2) at handler.cc:993 + * #5 0x000cd900 in openfrm(char const*, char const*, unsigned, unsigned, + * unsigned, st_table*) (name=0xf005f260 "/users/build/development/mys + * ql/debug-mysql/mysql-test/var//tmp/#sql5718_1_1.frm", + * alias=0xf005fb90 "#sql-5718_1", db_stat=7, prgflag=44, + * ha_open_flags=0, outparam=0x2039e18) at table.cc:771 + * + * As a result, I no longer use the entire path as the key to find a database. + * Just the last component of the path (i.e. the database name) should be + * sufficient!? + */ +xtPublic XTDatabaseHPtr xt_get_database(XTThreadPtr self, char *path, xtBool multi_path) +{ + XTDatabaseHPtr db = NULL; + char db_path[PATH_MAX]; + char db_name[NAME_MAX]; + xtBool multi_path_db = FALSE; + + /* A database may not be in use when this is called. */ + ASSERT(!self->st_database); + xt_ht_lock(self, xt_db_open_databases); + pushr_(xt_ht_unlock, xt_db_open_databases); + + xt_strcpy(PATH_MAX, db_path, path); + xt_add_location_file(PATH_MAX, db_path); + if (multi_path || xt_fs_exists(db_path)) + multi_path_db = TRUE; + + xt_strcpy(PATH_MAX, db_path, path); + xt_remove_dir_char(db_path); + xt_strcpy(NAME_MAX, db_name, xt_last_directory_of_path(db_path)); + + db = (XTDatabaseHPtr) xt_ht_get(self, xt_db_open_databases, db_name); + if (!db) { + pushsr_(db, xt_heap_release, (XTDatabaseHPtr) xt_heap_new(self, sizeof(XTDatabaseRec), db_finalize)); + xt_heap_set_release_callback(self, db, db_onrelease); + db->db_id = db_next_id++; + db->db_name = xt_dup_string(self, db_name); + db->db_main_path = xt_dup_string(self, db_path); + db->db_multi_path = multi_path_db; +#ifdef XT_TEST_XACT_OVERFLOW + /* Test transaction ID overflow: */ + db->db_xn_curr_id = 0xFFFFFFFF - 30; +#endif + xt_db_pool_init(self, db); + xt_tab_init_db(self, db); + xt_dl_init_db(self, db); + + /* Initialize the index logs: */ + db->db_indlogs.ilp_init(self, db, XT_INDEX_WRITE_BUFFER_SIZE); + + xt_xn_init_db(self, db); + xt_sl_insert(self, xt_db_open_db_by_id, &db->db_id, &db); + + xt_start_sweeper(self, db); + xt_start_compactor(self, db); + xt_start_writer(self, db); + xt_start_checkpointer(self, db); + + popr_(); + xt_ht_put(self, xt_db_open_databases, db); + + /* The recovery process could attach parts of the open + * database to the thread! + */ + xt_unuse_database(self, self); + + } + xt_heap_reference(self, db); + freer_(); + + /* {INDEX-RECOV_ROWID} + * Wait for sweeper to finish processing possibly + * unswept transactions after recovery. + * This is required because during recovery for + * all index entries written the row_id is set. + * + * When the row ID is set, this means that the row + * is "clean". i.e. visible to all transactions. + * + * Obviously this is not necessary the case for all + * index entries recovered. For example, + * transactions that still need to be swept may be + * rolled back. + * + * As a result, we have to wait the the sweeper + * to complete. Only then can we be sure that + * all index entries that are not visible have + * been removed. + * + * {OPEN-DB-SWEEPER-WAIT} + * This has been moved to after the release of the open + * database lock because: + * + * - We are waiting for the sweeper which may run out of + * record cache. + * - If it runs out of cache it well wait + * for the freeer thread. + * - For the freeer thread to be able to work it needs + * to open the database. + * - To open the database it needs the open database + * lock. + */ + pushr_(xt_heap_release, db); + xt_wait_for_sweeper(self, db, 0); + popr_(); + + return db; +} + +xtPublic XTDatabaseHPtr xt_get_database_by_id(XTThreadPtr self, xtDatabaseID db_id) +{ + XTDatabaseHPtr *dbptr; + XTDatabaseHPtr db = NULL; + + xt_ht_lock(self, xt_db_open_databases); + pushr_(xt_ht_unlock, xt_db_open_databases); + if ((dbptr = (XTDatabaseHPtr *) xt_sl_find(self, xt_db_open_db_by_id, &db_id))) { + db = *dbptr; + xt_heap_reference(self, db); + } + freer_(); // xt_ht_unlock(xt_db_open_databases) + return db; +} + +xtPublic void xt_check_database(XTThreadPtr self) +{ + xt_check_tables(self); + /* + xt_check_handlefiles(self, db); + */ +} + +xtPublic void xt_drop_database(XTThreadPtr self, XTDatabaseHPtr db) +{ + char path[PATH_MAX]; + char db_name[NAME_MAX]; + XTOpenDirPtr od; + char *file; + XTTablePathPtr *tp_ptr; + + xt_ht_lock(self, xt_db_open_databases); + pushr_(xt_ht_unlock, xt_db_open_databases); + + /* Shutdown the database daemons: */ + xt_stop_checkpointer(self, db); + xt_stop_sweeper(self, db); + xt_stop_compactor(self, db); + xt_stop_writer(self, db); + + /* Remove the database from the directory: */ + xt_strcpy(NAME_MAX, db_name, db->db_name); + xt_ht_del(self, xt_db_open_databases, db_name); + + /* Release the lock on the database directory: */ + freer_(); // xt_ht_unlock(xt_db_open_databases) + + /* Delete the transaction logs: */ + xt_xlog_delete_logs(self, db); + + /* Delete the data logs: */ + xt_dl_delete_logs(self, db); + + for (u_int i=0; i<xt_sl_get_size(db->db_table_paths); i++) { + + tp_ptr = (XTTablePathPtr *) xt_sl_item_at(db->db_table_paths, i); + + xt_strcpy(PATH_MAX, path, (*tp_ptr)->tp_path); + + /* Delete all files in the database: */ + pushsr_(od, xt_dir_close, xt_dir_open(self, path, NULL)); + while (xt_dir_next(self, od)) { + file = xt_dir_name(self, od); + if (xt_ends_with(file, ".xtr") || + xt_ends_with(file, ".xtd") || + xt_ends_with(file, ".xti") || + xt_ends_with(file, ".xt")) + { + xt_add_dir_char(PATH_MAX, path); + xt_strcat(PATH_MAX, path, file); + xt_fs_delete(self, path); + xt_remove_last_name_of_path(path); + } + } + freer_(); // xt_dir_close(od) + + } + if (!db->db_multi_path) { + xt_strcpy(PATH_MAX, path, db->db_main_path); + xt_add_pbxt_dir(PATH_MAX, path); + if (!xt_fs_rmdir(NULL, path)) + xt_log_and_clear_exception(self); + } +} + +/* + * Open/use a database. + */ +xtPublic void xt_open_database(XTThreadPtr self, char *path, xtBool multi_path) +{ + XTDatabaseHPtr db; + + /* We cannot get a database, without unusing the current + * first. The reason is that the restart process will + * partially set the current database! + */ + xt_unuse_database(self, self); + db = xt_get_database(self, path, multi_path); + pushr_(xt_heap_release, db); + xt_use_database(self, db, XT_FOR_USER); + freer_(); // xt_heap_release(self, db); +} + +/* This function can only be called if you do not already have a database in + * use. This is because to get a database pointer you are not allowed + * to have a database in use! + */ +xtPublic void xt_use_database(XTThreadPtr self, XTDatabaseHPtr db, int what_for) +{ + /* Check if a transaction is in progress. If so, + * we cannot change the database! + */ + if (self->st_xact_data || self->st_database) + xt_throw_xterr(XT_CONTEXT, XT_ERR_CANNOT_CHANGE_DB); + + xt_heap_reference(self, db); + self->st_database = db; + xt_xn_init_thread(self, what_for); +} + +xtPublic void xt_unuse_database(XTThreadPtr self, XTThreadPtr other_thr) +{ + /* Abort the transacion if it belongs exclusively to this thread. */ + xt_lock_mutex(self, &other_thr->t_lock); + pushr_(xt_unlock_mutex, &other_thr->t_lock); + + xt_xn_exit_thread(other_thr); + if (other_thr->st_database) { + xt_heap_release(self, other_thr->st_database); + other_thr->st_database = NULL; + } + + freer_(); +} + +xtPublic void xt_db_init_thread(XTThreadPtr XT_UNUSED(self), XTThreadPtr XT_UNUSED(new_thread)) +{ +#ifdef XT_IMPLEMENT_NO_ACTION + memset(&new_thread->st_restrict_list, 0, sizeof(XTBasicListRec)); + new_thread->st_restrict_list.bl_item_size = sizeof(XTRestrictItemRec); +#endif +} + +xtPublic void xt_db_exit_thread(XTThreadPtr self) +{ +#ifdef XT_IMPLEMENT_NO_ACTION + xt_bl_free(NULL, &self->st_restrict_list); +#endif + xt_unuse_database(self, self); +} + +/* + * ----------------------------------------------------------------------- + * OPEN TABLE POOL + */ + +#ifdef UNUSED_CODE +static void check_free_list(XTDatabaseHPtr db) +{ + XTOpenTablePtr ot; + u_int cnt = 0; + + ot = db->db_ot_pool.otp_mr_used; + if (ot) + ASSERT_NS(!ot->ot_otp_mr_used); + ot = db->db_ot_pool.otp_lr_used; + if (ot) + ASSERT_NS(!ot->ot_otp_lr_used); + while (ot) { + cnt++; + ot = ot->ot_otp_mr_used; + } + ASSERT_NS(cnt == db->db_ot_pool.otp_total_free); +} +#endif + +xtPublic void xt_db_pool_init(XTThreadPtr self, XTDatabaseHPtr db) +{ + memset(&db->db_ot_pool, 0, sizeof(XTAllTablePoolsRec)); + xt_init_mutex_with_autoname(self, &db->db_ot_pool.opt_lock); + xt_init_cond(self, &db->db_ot_pool.opt_cond); +} + +xtPublic void xt_db_pool_exit(XTThreadPtr self, XTDatabaseHPtr db) +{ + XTOpenTablePoolPtr table_pool, tmp; + XTOpenTablePtr ot, tmp_ot; + + xt_free_mutex(&db->db_ot_pool.opt_lock); + xt_free_cond(&db->db_ot_pool.opt_cond); + + for (u_int i=0; i<XT_OPEN_TABLE_POOL_HASH_SIZE; i++) { + table_pool = db->db_ot_pool.otp_hash[i]; + while (table_pool) { + tmp = table_pool->opt_next_hash; + ot = table_pool->opt_free_list; + while (ot) { + tmp_ot = ot->ot_otp_next_free; + ot->ot_thread = self; + xt_close_table(ot, TRUE, FALSE); + ot = tmp_ot; + } + xt_free(self, table_pool); + table_pool = tmp; + } + } +} + +static XTOpenTablePoolPtr db_get_open_table_pool(XTDatabaseHPtr db, xtTableID tab_id) +{ + XTOpenTablePoolPtr table_pool; + u_int hash; + + hash = tab_id % XT_OPEN_TABLE_POOL_HASH_SIZE; + table_pool = db->db_ot_pool.otp_hash[hash]; + while (table_pool) { + if (table_pool->opt_tab_id == tab_id) + return table_pool; + table_pool = table_pool->opt_next_hash; + } + + if (!(table_pool = (XTOpenTablePoolPtr) xt_malloc_ns(sizeof(XTOpenTablePoolRec)))) + return NULL; + + table_pool->opt_db = db; + table_pool->opt_tab_id = tab_id; + table_pool->opt_total_open = 0; + table_pool->opt_locked = FALSE; + table_pool->opt_flushing = 0; + table_pool->opt_free_list = NULL; + table_pool->opt_next_hash = db->db_ot_pool.otp_hash[hash]; + db->db_ot_pool.otp_hash[hash] = table_pool; + + return table_pool; +} + +static void db_free_open_table_pool(XTThreadPtr self, XTOpenTablePoolPtr table_pool) +{ + if (!table_pool->opt_locked && !table_pool->opt_flushing && !table_pool->opt_total_open) { + XTOpenTablePoolPtr ptr, pptr = NULL; + u_int hash; + + hash = table_pool->opt_tab_id % XT_OPEN_TABLE_POOL_HASH_SIZE; + ptr = table_pool->opt_db->db_ot_pool.otp_hash[hash]; + while (ptr) { + if (ptr == table_pool) + break; + pptr = ptr; + ptr = ptr->opt_next_hash; + } + + if (ptr == table_pool) { + if (pptr) + pptr->opt_next_hash = table_pool->opt_next_hash; + else + table_pool->opt_db->db_ot_pool.otp_hash[hash] = table_pool->opt_next_hash; + } + + xt_free(self, table_pool); + } +} + +static XTOpenTablePoolPtr db_lock_table_pool(XTThreadPtr self, XTDatabaseHPtr db, xtTableID tab_id, xtBool flush_table, xtBool wait_for_open) +{ + XTOpenTablePoolPtr table_pool; + XTOpenTablePtr ot, tmp_ot; + + xt_lock_mutex(self, &db->db_ot_pool.opt_lock); + pushr_(xt_unlock_mutex, &db->db_ot_pool.opt_lock); + + if (!(table_pool = db_get_open_table_pool(db, tab_id))) + xt_throw(self); + + /* Wait for the lock: */ + while (table_pool->opt_locked) { + xt_timed_wait_cond(self, &db->db_ot_pool.opt_cond, &db->db_ot_pool.opt_lock, 2000); + if (!(table_pool = db_get_open_table_pool(db, tab_id))) + xt_throw(self); + } + + /* Lock it: */ + table_pool->opt_locked = TRUE; + + if (flush_table) { + table_pool->opt_flushing++; + freer_(); // xt_unlock_mutex(db_ot_pool.opt_lock) + + pushr_(xt_db_unlock_table_pool, table_pool); + /* During this time, background processes can use the + * pool! + * + * May also do a flush, but this is now taken care + * of here [*10*] + */ + if ((ot = xt_db_open_pool_table(self, db, tab_id, NULL, TRUE))) { + pushr_(xt_db_return_table_to_pool, ot); + xt_sync_flush_table(self, ot); + freer_(); //xt_db_return_table_to_pool_foreground(ot); + } + + popr_(); // Discard xt_db_unlock_table_pool_no_lock(table_pool) + + xt_lock_mutex(self, &db->db_ot_pool.opt_lock); + pushr_(xt_unlock_mutex, &db->db_ot_pool.opt_lock); + table_pool->opt_flushing--; + } + + /* Free all open tables not in use: */ + ot = table_pool->opt_free_list; + table_pool->opt_free_list = NULL; + while (ot) { + tmp_ot = ot->ot_otp_next_free; + + /* Remove from MRU list: */ + if (db->db_ot_pool.otp_lr_used == ot) + db->db_ot_pool.otp_lr_used = ot->ot_otp_mr_used; + if (db->db_ot_pool.otp_mr_used == ot) + db->db_ot_pool.otp_mr_used = ot->ot_otp_lr_used; + if (ot->ot_otp_lr_used) + ot->ot_otp_lr_used->ot_otp_mr_used = ot->ot_otp_mr_used; + if (ot->ot_otp_mr_used) + ot->ot_otp_mr_used->ot_otp_lr_used = ot->ot_otp_lr_used; + + if (db->db_ot_pool.otp_lr_used) + db->db_ot_pool.otp_free_time = db->db_ot_pool.otp_lr_used->ot_otp_free_time; + + ASSERT_NS(db->db_ot_pool.otp_total_free > 0); + db->db_ot_pool.otp_total_free--; + + /* Close the table: */ + ASSERT(table_pool->opt_total_open > 0); + table_pool->opt_total_open--; + + ot->ot_thread = self; + xt_close_table(ot, table_pool->opt_total_open == 0, FALSE); + + /* Go to the next: */ + ot = tmp_ot; + } + + /* Wait for other to close: */ + if (wait_for_open) { + while (table_pool->opt_total_open > 0) { + xt_timed_wait_cond_ns(&db->db_ot_pool.opt_cond, &db->db_ot_pool.opt_lock, 2000); + } + } + + freer_(); // xt_unlock_mutex(db_ot_pool.opt_lock) + return table_pool; +} + +xtPublic XTOpenTablePoolPtr xt_db_lock_table_pool_by_name(XTThreadPtr self, XTDatabaseHPtr db, XTPathStrPtr tab_name, xtBool no_load, xtBool flush_table, xtBool missing_ok, xtBool wait_for_open, XTTableHPtr *ret_tab) +{ + XTOpenTablePoolPtr table_pool; + XTTableHPtr tab; + xtTableID tab_id; + + pushsr_(tab, xt_heap_release, xt_use_table(self, tab_name, no_load, missing_ok, NULL)); + if (!tab) { + freer_(); // xt_heap_release(tab) + return NULL; + } + + tab_id = tab->tab_id; + + if (ret_tab) { + *ret_tab = tab; + table_pool = db_lock_table_pool(self, db, tab_id, flush_table, wait_for_open); + popr_(); // Discard xt_heap_release(tab) + return table_pool; + } + + freer_(); // xt_heap_release(tab) + return db_lock_table_pool(self, db, tab_id, flush_table, wait_for_open); +} + +xtPublic void xt_db_wait_for_open_tables(XTThreadPtr self, XTOpenTablePoolPtr table_pool) +{ + XTDatabaseHPtr db = table_pool->opt_db; + + xt_lock_mutex(self, &db->db_ot_pool.opt_lock); + pushr_(xt_unlock_mutex, &db->db_ot_pool.opt_lock); + + /* Wait for other to close: */ + while (table_pool->opt_total_open > 0) { + xt_timed_wait_cond(self, &db->db_ot_pool.opt_cond, &db->db_ot_pool.opt_lock, 2000); + } + + freer_(); // xt_unlock_mutex(db_ot_pool.opt_lock) +} + +xtPublic void xt_db_unlock_table_pool(XTThreadPtr self, XTOpenTablePoolPtr table_pool) +{ + XTDatabaseHPtr db; + + if (!table_pool) + return; + + db = table_pool->opt_db; + xt_lock_mutex(self, &db->db_ot_pool.opt_lock); + pushr_(xt_unlock_mutex, &db->db_ot_pool.opt_lock); + + table_pool->opt_locked = FALSE; + xt_broadcast_cond(self, &db->db_ot_pool.opt_cond); + db_free_open_table_pool(NULL, table_pool); + + freer_(); // xt_unlock_mutex(db_ot_pool.opt_lock) +} + +xtPublic XTOpenTablePtr xt_db_open_table_using_tab(XTTableHPtr tab, XTThreadPtr thread) +{ + XTDatabaseHPtr db = tab->tab_db; + XTOpenTablePoolPtr table_pool; + XTOpenTablePtr ot; + + xt_lock_mutex_ns(&db->db_ot_pool.opt_lock); + + if (!(table_pool = db_get_open_table_pool(db, tab->tab_id))) + goto failed; + + while (table_pool->opt_locked) { + if (!xt_timed_wait_cond_ns(&db->db_ot_pool.opt_cond, &db->db_ot_pool.opt_lock, 2000)) + goto failed_1; + if (!(table_pool = db_get_open_table_pool(db, tab->tab_id))) + goto failed; + } + + if ((ot = table_pool->opt_free_list)) { + /* Remove from the free list: */ + table_pool->opt_free_list = ot->ot_otp_next_free; + + /* Remove from MRU list: */ + if (db->db_ot_pool.otp_lr_used == ot) + db->db_ot_pool.otp_lr_used = ot->ot_otp_mr_used; + if (db->db_ot_pool.otp_mr_used == ot) + db->db_ot_pool.otp_mr_used = ot->ot_otp_lr_used; + if (ot->ot_otp_lr_used) + ot->ot_otp_lr_used->ot_otp_mr_used = ot->ot_otp_mr_used; + if (ot->ot_otp_mr_used) + ot->ot_otp_mr_used->ot_otp_lr_used = ot->ot_otp_lr_used; + + if (db->db_ot_pool.otp_lr_used) + db->db_ot_pool.otp_free_time = db->db_ot_pool.otp_lr_used->ot_otp_free_time; + + ASSERT_NS(db->db_ot_pool.otp_total_free > 0); + db->db_ot_pool.otp_total_free--; + + ot->ot_thread = thread; + goto done_ok; + } + + if ((ot = xt_open_table(tab))) { + ot->ot_thread = thread; + table_pool->opt_total_open++; + } + + done_ok: + db_free_open_table_pool(NULL, table_pool); + xt_unlock_mutex_ns(&db->db_ot_pool.opt_lock); + return ot; + + failed_1: + db_free_open_table_pool(NULL, table_pool); + + failed: + xt_unlock_mutex_ns(&db->db_ot_pool.opt_lock); + return NULL; +} + +xtPublic xtBool xt_db_open_pool_table_ns(XTOpenTablePtr *ret_ot, XTDatabaseHPtr db, xtTableID tab_id) +{ + XTThreadPtr self = xt_get_self(); + xtBool ok = TRUE; + + try_(a) { + *ret_ot = xt_db_open_pool_table(self, db, tab_id, NULL, FALSE); + } + catch_(a) { + ok = FALSE; + } + cont_(a); + return ok; +} + +xtPublic XTOpenTablePtr xt_db_open_pool_table(XTThreadPtr self, XTDatabaseHPtr db, xtTableID tab_id, int *result, xtBool i_am_background) +{ + XTOpenTablePtr ot; + XTOpenTablePoolPtr table_pool; + int r; + XTTableHPtr tab; + + xt_lock_mutex(self, &db->db_ot_pool.opt_lock); + pushr_(xt_unlock_mutex, &db->db_ot_pool.opt_lock); + + if (!(table_pool = db_get_open_table_pool(db, tab_id))) + xt_throw(self); + + /* Background processes do not have to wait while flushing! + * + * I think I did this so that the background process would + * not hang during flushing. Exact reason currently + * unknown. + * + * This led to the situation that the checkpointer + * could flush at the same time as a user process + * which was flushing due to a rename. + * + * This led to the situation described here: [*10*], + * which is now fixed. + */ + while (table_pool->opt_locked && !(i_am_background && table_pool->opt_flushing)) { + xt_timed_wait_cond(self, &db->db_ot_pool.opt_cond, &db->db_ot_pool.opt_lock, 2000); + if (!(table_pool = db_get_open_table_pool(db, tab_id))) + xt_throw(self); + } + + /* Moved from above, because db_get_open_table_pool() may return a different + * pool on each call! + */ + pushr_(db_free_open_table_pool, table_pool); + + if ((ot = table_pool->opt_free_list)) { + /* Remove from the free list: */ + table_pool->opt_free_list = ot->ot_otp_next_free; + + /* Remove from MRU list: */ + if (db->db_ot_pool.otp_lr_used == ot) + db->db_ot_pool.otp_lr_used = ot->ot_otp_mr_used; + if (db->db_ot_pool.otp_mr_used == ot) + db->db_ot_pool.otp_mr_used = ot->ot_otp_lr_used; + if (ot->ot_otp_lr_used) + ot->ot_otp_lr_used->ot_otp_mr_used = ot->ot_otp_mr_used; + if (ot->ot_otp_mr_used) + ot->ot_otp_mr_used->ot_otp_lr_used = ot->ot_otp_lr_used; + + if (db->db_ot_pool.otp_lr_used) + db->db_ot_pool.otp_free_time = db->db_ot_pool.otp_lr_used->ot_otp_free_time; + + ASSERT(db->db_ot_pool.otp_total_free > 0); + db->db_ot_pool.otp_total_free--; + + freer_(); // db_free_open_table_pool(table_pool) + freer_(); // xt_unlock_mutex(&db->db_ot_pool.opt_lock) + ot->ot_thread = self; + return ot; + } + + r = xt_use_table_by_id(self, &tab, db, tab_id); + if (result) { + if (r != XT_TAB_OK) { + *result = r; + freer_(); // db_free_open_table_pool(table_pool) + freer_(); // xt_unlock_mutex(&db->db_ot_pool.opt_lock) + return NULL; + } + } + else { + switch (r) { + case XT_TAB_NOT_FOUND: + /* The table no longer exists, ignore the change: */ + freer_(); // db_free_open_table_pool(table_pool) + freer_(); // xt_unlock_mutex(&db->db_ot_pool.opt_lock) + return NULL; + case XT_TAB_NO_DICTIONARY: + xt_throw_ulxterr(XT_CONTEXT, XT_ERR_NO_DICTIONARY, (u_long) tab_id); + case XT_TAB_POOL_CLOSED: + xt_throw_ulxterr(XT_CONTEXT, XT_ERR_TABLE_LOCKED, (u_long) tab_id); + default: + break; + } + } + + /* xt_use_table_by_id returns a referenced tab! */ + pushr_(xt_heap_release, tab); + if ((ot = xt_open_table(tab))) { + ot->ot_thread = self; + table_pool->opt_total_open++; + } + freer_(); // xt_release_heap(tab) + + freer_(); // db_free_open_table_pool(table_pool) + freer_(); // xt_unlock_mutex(&db->db_ot_pool.opt_lock) + return ot; +} + +xtPublic void xt_db_return_table_to_pool(XTThreadPtr XT_UNUSED(self), XTOpenTablePtr ot) +{ + xt_db_return_table_to_pool_ns(ot); +} + +xtPublic void xt_db_return_table_to_pool_ns(XTOpenTablePtr ot) +{ + XTOpenTablePoolPtr table_pool; + XTDatabaseHPtr db = ot->ot_table->tab_db; + xtBool flush_table = TRUE; + + xt_lock_mutex_ns(&db->db_ot_pool.opt_lock); + + if (!(table_pool = db_get_open_table_pool(db, ot->ot_table->tab_id))) + goto failed; + + if (table_pool->opt_locked && !table_pool->opt_flushing) { + table_pool->opt_total_open--; + /* Table will be closed below: */ + if (table_pool->opt_total_open > 0) + flush_table = FALSE; + } + else { + /* Put it on the free list: */ + db->db_ot_pool.otp_total_free++; + + ot->ot_otp_next_free = table_pool->opt_free_list; + table_pool->opt_free_list = ot; + + /* This is the time the table was freed: */ + ot->ot_otp_free_time = xt_db_approximate_time; + + /* Add to most recently used: */ + if ((ot->ot_otp_lr_used = db->db_ot_pool.otp_mr_used)) + db->db_ot_pool.otp_mr_used->ot_otp_mr_used = ot; + ot->ot_otp_mr_used = NULL; + db->db_ot_pool.otp_mr_used = ot; + if (!db->db_ot_pool.otp_lr_used) { + db->db_ot_pool.otp_lr_used = ot; + db->db_ot_pool.otp_free_time = ot->ot_otp_free_time; + } + + ot = NULL; + } + + db_free_open_table_pool(NULL, table_pool); + + if (!xt_broadcast_cond_ns(&db->db_ot_pool.opt_cond)) + goto failed; + xt_unlock_mutex_ns(&db->db_ot_pool.opt_lock); + if (ot) + xt_close_table(ot, flush_table, FALSE); + + return; + + failed: + xt_unlock_mutex_ns(&db->db_ot_pool.opt_lock); + if (ot) + xt_close_table(ot, TRUE, FALSE); + xt_log_and_clear_exception_ns(); +} + +//#define TEST_FREE_OPEN_TABLES + +#ifdef DEBUG +#undef XT_OPEN_TABLE_FREE_TIME +#define XT_OPEN_TABLE_FREE_TIME 5 +#endif + +xtPublic void xt_db_free_unused_open_tables(XTThreadPtr self, XTDatabaseHPtr db) +{ + XTOpenTablePoolPtr table_pool; + size_t count; + XTOpenTablePtr ot; + xtBool flush_table = TRUE; + u_int table_count; + + /* A quick check of the oldest free table: */ + if (xt_db_approximate_time < db->db_ot_pool.otp_free_time + XT_OPEN_TABLE_FREE_TIME) + return; + + table_count = db->db_table_by_id ? xt_sl_get_size(db->db_table_by_id) : 0; + count = table_count * 3; + if (count < 20) + count = 20; +#ifdef TEST_FREE_OPEN_TABLES + count = 10; +#endif + if (db->db_ot_pool.otp_total_free > count) { + XTOpenTablePtr ptr, pptr; + + count = table_count * 2; + if (count < 10) + count = 10; +#ifdef TEST_FREE_OPEN_TABLES + count = 5; +#endif + xt_lock_mutex(self, &db->db_ot_pool.opt_lock); + pushr_(xt_unlock_mutex, &db->db_ot_pool.opt_lock); + + while (db->db_ot_pool.otp_total_free > count) { + ASSERT_NS(db->db_ot_pool.otp_lr_used); + if (!(ot = db->db_ot_pool.otp_lr_used)) + break; + + /* Check how long the open table has been free: */ + if (xt_db_approximate_time < ot->ot_otp_free_time + XT_OPEN_TABLE_FREE_TIME) + break; + + ot->ot_thread = self; + + /* Remove from MRU list: */ + db->db_ot_pool.otp_lr_used = ot->ot_otp_mr_used; + if (db->db_ot_pool.otp_mr_used == ot) + db->db_ot_pool.otp_mr_used = ot->ot_otp_lr_used; + if (ot->ot_otp_lr_used) + ot->ot_otp_lr_used->ot_otp_mr_used = ot->ot_otp_mr_used; + if (ot->ot_otp_mr_used) + ot->ot_otp_mr_used->ot_otp_lr_used = ot->ot_otp_lr_used; + + if (db->db_ot_pool.otp_lr_used) + db->db_ot_pool.otp_free_time = db->db_ot_pool.otp_lr_used->ot_otp_free_time; + + ASSERT(db->db_ot_pool.otp_total_free > 0); + db->db_ot_pool.otp_total_free--; + + if (!(table_pool = db_get_open_table_pool(db, ot->ot_table->tab_id))) + xt_throw(self); + + /* Find the open table in the table pool, + * and remove it from the list: + */ + pptr = NULL; + ptr = table_pool->opt_free_list; + while (ptr) { + if (ptr == ot) + break; + pptr = ptr; + ptr = ptr->ot_otp_next_free; + } + + ASSERT_NS(ptr == ot); + if (ptr == ot) { + if (pptr) + pptr->ot_otp_next_free = ot->ot_otp_next_free; + else + table_pool->opt_free_list = ot->ot_otp_next_free; + } + + ASSERT_NS(table_pool->opt_total_open > 0); + table_pool->opt_total_open--; + if (table_pool->opt_total_open > 0) + flush_table = FALSE; + else + flush_table = TRUE; + + db_free_open_table_pool(self, table_pool); + + freer_(); + + /* Close the table, but not + * while holding the lock. + */ + xt_close_table(ot, flush_table, FALSE); + + xt_lock_mutex(self, &db->db_ot_pool.opt_lock); + pushr_(xt_unlock_mutex, &db->db_ot_pool.opt_lock); + } + + freer_(); + } +} diff --git a/storage/pbxt/src/database_xt.h b/storage/pbxt/src/database_xt.h new file mode 100644 index 00000000000..469bfa4adb6 --- /dev/null +++ b/storage/pbxt/src/database_xt.h @@ -0,0 +1,247 @@ +/* Copyright (c) 2005 PrimeBase Technologies GmbH + * + * PrimeBase XT + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You 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 + * + * 2005-01-15 Paul McCullagh + * + * H&G2JCtL + */ +#ifndef __xt_database_h__ +#define __xt_database_h__ + +#include <time.h> + +#include "thread_xt.h" +#include "hashtab_xt.h" +#include "table_xt.h" +#include "sortedlist_xt.h" +#include "xaction_xt.h" +#include "heap_xt.h" +#include "xactlog_xt.h" +#include "restart_xt.h" +#include "index_xt.h" + +#ifdef DEBUG +//#define XT_USE_XACTION_DEBUG_SIZES +#endif + +#ifdef XT_USE_XACTION_DEBUG_SIZES +#define XT_DB_TABLE_POOL_SIZE 2 +#else +#define XT_DB_TABLE_POOL_SIZE 10 // The number of open tables maintained by the sweeper +#endif + +/* Turn this switch on to enable spin lock based wait-for logic: */ +#define XT_USE_SPINLOCK_WAIT_FOR + +extern xtLogOffset xt_db_log_file_threshold; +extern size_t xt_db_log_buffer_size; +extern size_t xt_db_transaction_buffer_size; +extern size_t xt_db_checkpoint_frequency; +extern off_t xt_db_data_log_threshold; +extern size_t xt_db_data_file_grow_size; +extern size_t xt_db_row_file_grow_size; +extern int xt_db_garbage_threshold; +extern int xt_db_log_file_count; +extern int xt_db_auto_increment_mode; +extern int xt_db_offline_log_function; +extern int xt_db_sweeper_priority; + +extern XTSortedListPtr xt_db_open_db_by_id; +extern XTHashTabPtr xt_db_open_databases; +extern time_t xt_db_approximate_time; + +#define XT_OPEN_TABLE_POOL_HASH_SIZE 223 + +#define XT_SW_WORK_NORMAL 0 +#define XT_SW_NO_MORE_XACT_SLOTS 1 +#define XT_SW_DIRTY_RECORD_FOUND 2 +#define XT_SW_TOO_FAR_BEHIND 3 /* The sweeper is getting too far behind, although it is working! */ + +typedef struct XTOpenTablePool { + struct XTDatabase *opt_db; + xtTableID opt_tab_id; /* The table ID. */ + u_int opt_total_open; /* Total number of open tables. */ + xtBool opt_locked; /* This table is locked open tables are freed on return to pool. */ + u_int opt_flushing; + XTOpenTablePtr opt_free_list; /* A list of free, unused open tables. */ + struct XTOpenTablePool *opt_next_hash; +} XTOpenTablePoolRec, *XTOpenTablePoolPtr; + +typedef struct XTAllTablePools { + xt_mutex_type opt_lock; /* This lock protects the open table pool. */ + xt_cond_type opt_cond; /* Used to wait for an exclusive lock on a table. */ + + u_int otp_total_free; /* This is the total number of free open tables (not in use): */ + + /* All free (unused tables) are on this list: */ + XTOpenTablePtr otp_mr_used; + XTOpenTablePtr otp_lr_used; + time_t otp_free_time; /* The free time of the LRU open table. */ + + XTOpenTablePoolPtr otp_hash[XT_OPEN_TABLE_POOL_HASH_SIZE]; +} XTAllTablePoolsRec, *XTAllTablePoolsPtr; + +typedef struct XTTablePath { + u_int tp_tab_count; /* The number of tables using this path. */ + char tp_path[XT_VAR_LENGTH]; /* The table path. */ +} XTTablePathRec, *XTTablePathPtr; + +#define XT_THREAD_BUSY 0 +#define XT_THREAD_IDLE 1 +#define XT_THREAD_INERR 2 + +typedef struct XTDatabase : public XTHeap { + char *db_name; /* The name of the database, last component of the path! */ + char *db_main_path; + xtDatabaseID db_id; + xtTableID db_curr_tab_id; /* The ID of the last table created. */ + XTHashTabPtr db_tables; + XTSortedListPtr db_table_by_id; + XTSortedListPtr db_table_paths; /* A list of table paths used by this database. */ + xtBool db_multi_path; + + /* The open table pool: */ + XTAllTablePoolsRec db_ot_pool; + + /* Transaction related stuff: */ + XTSpinLockRec db_xn_id_lock; /* Lock for next transaction ID. */ + xtXactID db_xn_curr_id; /* The ID of the last transaction started. */ + xtXactID db_xn_min_ram_id; /* The lowest ID of the transactions in memory (RAM). */ + xtXactID db_xn_to_clean_id; /* The next transaction to be cleaned (>= db_xn_min_ram_id). */ + xtXactID db_xn_min_run_id; /* The lowest ID of all running transactions (not up-to-date! >= db_xn_to_clean_id) */ + xtWord4 db_xn_end_time; /* The time of the transaction end. */ + XTXactSegRec db_xn_idx[XT_XN_NO_OF_SEGMENTS]; /* Index of transactions in RAM. */ + xtWord1 *db_xn_data; /* Start of the block allocated to contain transaction data. */ + xtWord1 *db_xn_data_end; /* End of the transaction data block. */ + u_int db_stat_sweep_waits; /* STATISTICS: count the sweeper waits. */ + XTDatabaseLogRec db_xlog; /* The transaction log for this database. */ + XTXactRestartRec db_restart; /* Database recovery stuff. */ + + XTSortedListPtr db_xn_wait_for; /* The "wait-for" list, of transactions waiting for other transactions. */ + u_int db_xn_call_start; /* Start of the post wait calls. */ + XTSpinLockRec db_xn_wait_spinlock; + //xt_mutex_type db_xn_wait_lock; /* The lock associated with the wait for list. */ + //xt_cond_type db_xn_wait_cond; /* This condition is signalled when a transaction quits. */ + //u_int db_xn_wait_on_cond; /* Number of threads waiting on the condition. */ + int db_xn_wait_count; /* Number of waiting transactions. */ + u_int db_xn_total_writer_count; /* The total number of writers. */ + int db_xn_writer_count; /* The number of writer threads. */ + int db_xn_writer_wait_count; /* The number of writer threads waiting. */ + int db_xn_long_running_count; /* The number of long running writer threads. */ + + /* Sweeper stuff: */ + struct XTThread *db_sw_thread; /* The sweeper thread (cleans up transactions). */ + xt_mutex_type db_sw_lock; /* The lock associated with the sweeper. */ + xt_cond_type db_sw_cond; /* The sweeper wakeup condition. */ + u_int db_sw_check_count; + int db_sw_idle; /* BUSY/IDLE/INERR depending on the state of the sweeper. */ + int db_sw_faster; /* non-zero if the sweeper should work faster. */ + xtBool db_sw_fast; /* TRUE if the sweeper is working faster. */ + + /* Writer stuff: */ + struct XTThread *db_wr_thread; /* The writer thread (write log data to the database). */ + int db_wr_idle; /* BUSY/IDLE/INERR depending on the state of the writer. */ + xtBool db_wr_faster; /* Set to TRUE if the writer should work faster. */ + xtBool db_wr_fast; /* TRUE if the writer is working faster. */ + u_int db_wr_thread_waiting; /* Count the number of threads waiting for the writer. */ + xtBool db_wr_freeer_waiting; /* TRUE if the freeer is wating for the writer. */ + xt_mutex_type db_wr_lock; + xt_cond_type db_wr_cond; /* Writer condition when idle (must bw woken by log flush! */ + xtLogID db_wr_log_id; /* Current write log ID. */ + xtLogOffset db_wr_log_offset; /* Current write log offset. */ + xtLogID db_wr_flush_point_log_id; /* This is the point to which the writer will write (log ID). */ + xtLogOffset db_wr_flush_point_log_offset; /* This is the point to which the writer will write (log offset). */ + + /* Data log stuff: */ + XTDataLogCacheRec db_datalogs; /* The database data log stuff. */ + XTIndexLogPoolRec db_indlogs; /* Index logs used for consistent write. */ + + /* Compactor stuff: */ + struct XTThread *db_co_thread; /* The compator thread (compacts data logs). */ + xt_mutex_type db_co_ext_lock; /* Required when extended data is moved, or removed. */ + xtBool db_co_busy; /* True of the compactor is busy compacting a data log. */ + xt_mutex_type db_co_dlog_lock; /* This is the lock required to flusht the compactors data log. */ + + /* Checkpointer stuff: */ + struct XTThread *db_cp_thread; /* The checkpoint thread (flushes the database data). */ + xt_mutex_type db_cp_lock; + xt_cond_type db_cp_cond; /* Writer condition when idle (must bw woken by log flush! */ + XTCheckPointStateRec db_cp_state; /* The checkpoint state. */ +} XTDatabaseRec, *XTDatabaseHPtr; /* Heap pointer */ + +#define XT_FOR_USER 0 +#define XT_FOR_COMPACTOR 1 +#define XT_FOR_SWEEPER 2 +#define XT_FOR_WRITER 3 +#define XT_FOR_CHECKPOINTER 4 + +void xt_create_database(XTThreadPtr th, char *path); +XTDatabaseHPtr xt_get_database(XTThreadPtr self, char *path, xtBool multi_path); +XTDatabaseHPtr xt_get_database_by_id(XTThreadPtr self, xtDatabaseID db_id); +void xt_drop_database(XTThreadPtr self, XTDatabaseHPtr db); +void xt_check_database(XTThreadPtr self); + +void xt_add_pbxt_file(size_t size, char *path, const char *file); +void xt_add_location_file(size_t size, char *path); +void xt_add_system_dir(size_t size, char *path); +void xt_add_data_dir(size_t size, char *path); + +void xt_use_database(XTThreadPtr self, XTDatabaseHPtr db, int what_for); +void xt_unuse_database(XTThreadPtr self, XTThreadPtr other_thr); +void xt_open_database(XTThreadPtr self, char *path, xtBool multi_path); + +void xt_lock_installation(XTThreadPtr self, char *installation_path); +void xt_unlock_installation(XTThreadPtr self, char *installation_path); +void xt_crash_me(void); + +void xt_init_databases(XTThreadPtr self); +void xt_stop_database_threads(XTThreadPtr self, xtBool sync); +void xt_exit_databases(XTThreadPtr self); + +void xt_dump_database(XTThreadPtr self, XTDatabaseHPtr db); + +void xt_db_init_thread(XTThreadPtr self, XTThreadPtr new_thread); +void xt_db_exit_thread(XTThreadPtr self); + +void xt_db_pool_init(XTThreadPtr self, struct XTDatabase *db); +void xt_db_pool_exit(XTThreadPtr self, struct XTDatabase *db); +XTOpenTablePoolPtr xt_db_lock_table_pool_by_name(XTThreadPtr self, XTDatabaseHPtr db, XTPathStrPtr name, xtBool no_load, xtBool flush_table, xtBool missing_ok, xtBool wait_for_open, XTTableHPtr *ret_tab); +void xt_db_wait_for_open_tables(XTThreadPtr self, XTOpenTablePoolPtr table_pool); +void xt_db_unlock_table_pool(struct XTThread *self, XTOpenTablePoolPtr table_pool); +XTOpenTablePtr xt_db_open_pool_table(XTThreadPtr self, XTDatabaseHPtr db, xtTableID tab_id, int *result, xtBool i_am_background); +XTOpenTablePtr xt_db_open_table_using_tab(XTTableHPtr tab, XTThreadPtr thread); +xtBool xt_db_open_pool_table_ns(XTOpenTablePtr *ret_ot, XTDatabaseHPtr db, xtTableID tab_id); +void xt_db_return_table_to_pool(XTThreadPtr self, XTOpenTablePtr ot); +void xt_db_return_table_to_pool_ns(XTOpenTablePtr ot); +void xt_db_free_unused_open_tables(XTThreadPtr self, XTDatabaseHPtr db); + +#define XT_LONG_RUNNING_TIME 2 + +inline void xt_xlog_check_long_writer(XTThreadPtr thread) +{ + if (thread->st_xact_writer) { + if (xt_db_approximate_time - thread->st_xact_write_time > XT_LONG_RUNNING_TIME) { + if (!thread->st_xact_long_running) { + thread->st_xact_long_running = TRUE; + thread->st_database->db_xn_long_running_count++; + } + } + } +} + +#endif diff --git a/storage/pbxt/src/datadic_xt.cc b/storage/pbxt/src/datadic_xt.cc new file mode 100644 index 00000000000..c2ac186cf6f --- /dev/null +++ b/storage/pbxt/src/datadic_xt.cc @@ -0,0 +1,2875 @@ +/* Copyright (c) 2005 PrimeBase Technologies GmbH + * + * PrimeBase XT + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You 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 + * + * 2006-05-16 Paul McCullagh + * + * H&G2JCtL + * + * Implementation of the PBXT internal data dictionary. + */ + + +#include "xt_config.h" + +#include <ctype.h> +#include <errno.h> + +#ifdef DEBUG +#ifdef DRIZZLED +#include <drizzled/common_includes.h> +#else +#include "mysql_priv.h" +#endif +#endif + +#include "pthread_xt.h" +#include "datadic_xt.h" +#include "util_xt.h" +#include "database_xt.h" +#include "table_xt.h" +#include "heap_xt.h" +#include "strutil_xt.h" +#include "myxt_xt.h" +#include "hashtab_xt.h" + +/* + * ----------------------------------------------------------------------- + * Lexical analyser + */ + +#define XT_TK_EOF 0 +#define XT_TK_IDENTIFIER 1 +#define XT_TK_NUMBER 2 +#define XT_TK_STRING 3 +#define XT_TK_PUNCTUATION 4 + +#define XT_TK_RESERVER_WORDS 5 +#define XT_TK_PRIMARY 5 +#define XT_TK_UNIQUE 6 +#define XT_TK_FULLTEXT 7 +#define XT_TK_SPATIAL 8 +#define XT_TK_INDEX 9 +#define XT_TK_KEY 10 +#define XT_TK_CHECK 11 +#define XT_TK_FOREIGN 12 +#define XT_TK_COLUMN 13 +#define XT_TK_REFERENCES 14 +#define XT_TK_NOT 15 +#define XT_TK_NULL 16 +#define XT_TK_AUTO_INCREMENT 17 +#define XT_TK_COMMENT 18 +#define XT_TK_DEFAULT 19 +#define XT_TK_COLLATE 20 + +class XTToken { + public: + u_int tk_type; + char *tk_text; + size_t tk_length; + + void initCString(u_int type, char *start, char *end); + inline char charAt(u_int i) { + if (i >= tk_length) + return 0; + return toupper(tk_text[i]); + } + void expectKeyWord(XTThreadPtr self, c_char *keyword); + void expectIdentifier(XTThreadPtr self); + void expectNumber(XTThreadPtr self); + bool isKeyWord(c_char *keyword); + bool isReservedWord(); + bool isReservedWord(u_int word); + void identifyReservedWord(); + bool isEOF(); + bool isIdentifier(); + bool isNumber(); + size_t getString(char *string, size_t len); + void getTokenText(char *string, size_t len); + XTToken *clone(XTThreadPtr self); +}; + +void XTToken::initCString(u_int type, char *start, char *end) +{ + tk_type = type; + tk_text = start; + tk_length = (size_t) end - (size_t) start; +} + +bool XTToken::isKeyWord(c_char *keyword) +{ + char *str = tk_text; + size_t len = tk_length; + + while (len && *keyword) { + if (toupper(*keyword) != toupper(*str)) + return false; + keyword++; + str++; + len--; + } + return !len && !*keyword; +} + +bool XTToken::isReservedWord() +{ + return tk_type >= XT_TK_RESERVER_WORDS; +} + +bool XTToken::isReservedWord(u_int word) +{ + return tk_type == word; +} + +void XTToken::identifyReservedWord() +{ + if (tk_type == XT_TK_IDENTIFIER) { + switch (charAt(0)) { + case 'A': + if (isKeyWord("AUTO_INCREMENT")) + tk_type = XT_TK_AUTO_INCREMENT; + break; + case 'C': + switch (charAt(2)) { + case 'E': + if (isKeyWord("CHECK")) + tk_type = XT_TK_CHECK; + break; + case 'L': + if (isKeyWord("COLUMN")) + tk_type = XT_TK_COLUMN; + else if (isKeyWord("COLLATE")) + tk_type = XT_TK_COLLATE; + break; + case 'M': + if (isKeyWord("COMMENT")) + tk_type = XT_TK_COMMENT; + break; + } + break; + case 'D': + if (isKeyWord("DEFAULT")) + tk_type = XT_TK_DEFAULT; + break; + case 'F': + switch (charAt(1)) { + case 'O': + if (isKeyWord("FOREIGN")) + tk_type = XT_TK_FOREIGN; + break; + case 'U': + if (isKeyWord("FULLTEXT")) + tk_type = XT_TK_FULLTEXT; + break; + } + break; + case 'I': + if (isKeyWord("INDEX")) + tk_type = XT_TK_INDEX; + break; + case 'K': + if (isKeyWord("KEY")) + tk_type = XT_TK_KEY; + break; + case 'N': + switch (charAt(1)) { + case 'O': + if (isKeyWord("NOT")) + tk_type = XT_TK_NOT; + break; + case 'U': + if (isKeyWord("NULL")) + tk_type = XT_TK_NULL; + break; + } + break; + case 'P': + if (isKeyWord("PRIMARY")) + tk_type = XT_TK_PRIMARY; + break; + case 'R': + if (isKeyWord("REFERENCES")) + tk_type = XT_TK_REFERENCES; + break; + case 'S': + if (isKeyWord("SPATIAL")) + tk_type = XT_TK_SPATIAL; + break; + case 'U': + if (isKeyWord("UNIQUE")) + tk_type = XT_TK_UNIQUE; + break; + } + } +} + +bool XTToken::isEOF() +{ + return tk_type == XT_TK_EOF; +} + +bool XTToken::isIdentifier() +{ + return tk_type == XT_TK_IDENTIFIER; +} + +bool XTToken::isNumber() +{ + return tk_type == XT_TK_NUMBER; +} + +/* Return actual, or required string length. */ +size_t XTToken::getString(char *dtext, size_t dsize) +{ + char *buffer = dtext; + int slen; + size_t dlen; + char *stext; + char quote; + + if ((slen = (int) tk_length) == 0) { + *dtext = 0; + return 0; + } + switch (*tk_text) { + case '\'': + case '"': + case '`': + quote = *tk_text; + stext = tk_text+1; + slen -= 2; + dlen = 0; + while (slen > 0) { + if (*stext == '\\') { + stext++; + slen--; + if (slen > 0) { + switch (*stext) { + case '\0': + *dtext = 0; + break; + case '\'': + *dtext = '\''; + break; + case '"': + *dtext = '"'; + break; + case 'b': + *dtext = '\b'; + break; + case 'n': + *dtext = '\n'; + break; + case 'r': + *dtext = '\r'; + break; + case 't': + *dtext = '\t'; + break; + case 'z': + *dtext = (char) 26; + break; + case '\\': + *dtext = '\\'; + break; + default: + *dtext = *stext; + break; + } + } + } + else if (*stext == quote) { + if (dlen < dsize) + *dtext = quote; + stext++; + slen--; + } + else { + if (dlen < dsize) + *dtext = *stext; + } + dtext++; + dlen++; + stext++; + slen--; + } + if (dlen < dsize) + buffer[dlen] = 0; + else if (dsize > 0) + buffer[dsize-1] = 0; + break; + default: + if (dsize > 0) { + dlen = dsize-1; + if ((int) dlen > slen) + dlen = slen; + memcpy(dtext, tk_text, dlen); + dtext[dlen] = 0; + } + dlen = tk_length; + break; + } + return dlen; +} + +/* Return the token as a string with ... in it if it is too long + */ +void XTToken::getTokenText(char *string, size_t size) +{ + if (tk_length == 0 || !tk_text) { + xt_strcpy(size, string, "EOF"); + return; + } + + size--; + if (tk_length <= size) { + memcpy(string, tk_text, tk_length); + string[tk_length] = 0; + return; + } + + size = (size - 3) / 2; + memcpy(string, tk_text, size); + memcpy(string+size, "...", 3); + memcpy(string+size+3, tk_text + tk_length - size, size); + string[size+3+size] = 0; +} + +XTToken *XTToken::clone(XTThreadPtr self) +{ + XTToken *tk; + + if (!(tk = new XTToken())) + xt_throw_errno(XT_CONTEXT, XT_ENOMEM); + tk->initCString(tk_type, tk_text, tk_text + tk_length); + return tk; +} + +void XTToken::expectKeyWord(XTThreadPtr self, c_char *keyword) +{ + char buffer[100]; + + if (isKeyWord(keyword)) + return; + getTokenText(buffer, 100); + xt_throw_i2xterr(XT_CONTEXT, XT_ERR_A_EXPECTED_NOT_B, keyword, buffer); +} + +void XTToken::expectIdentifier(XTThreadPtr self) +{ + char buffer[100]; + + if (isIdentifier()) + return; + getTokenText(buffer, 100); + xt_throw_i2xterr(XT_CONTEXT, XT_ERR_A_EXPECTED_NOT_B, "Identifier", buffer); +} + +void XTToken::expectNumber(XTThreadPtr self) +{ + char buffer[100]; + + if (isNumber()) + return; + getTokenText(buffer, 100); + xt_throw_i2xterr(XT_CONTEXT, XT_ERR_A_EXPECTED_NOT_B, "Value", buffer); +} + +struct charset_info_st; + +class XTTokenizer { + struct charset_info_st *tkn_charset; + char *tkn_cstring; + char *tkn_curr_pos; + XTToken *tkn_current; + bool tkn_in_comment; + + public: + + XTTokenizer(bool convert, char *cstring) { + tkn_charset = myxt_getcharset(convert); + tkn_cstring = cstring; + tkn_curr_pos = cstring; + tkn_current = NULL; + tkn_in_comment = FALSE; + } + + virtual ~XTTokenizer(void) { + if (tkn_current) + delete tkn_current; + } + + inline bool isSingleChar(int ch) + { + return ch != '$' && ch != '_' && myxt_ispunct(tkn_charset, ch); + } + + inline bool isIdentifierChar(int ch) + { + return ch && !isSingleChar(ch) && !myxt_isspace(tkn_charset, ch); + } + + inline bool isNumberChar(int ch, int next_ch) + { + return myxt_isdigit(tkn_charset, ch) || ((ch == '-' || ch == '+') && myxt_isdigit(tkn_charset, next_ch)); + } + + XTToken *newToken(XTThreadPtr self, u_int type, char *start, char *end); + XTToken *nextToken(XTThreadPtr self); + XTToken *nextToken(XTThreadPtr self, c_char *keyword, XTToken *tk); +}; + +void ri_free_token(XTThreadPtr self __attribute__((unused)), XTToken *tk) +{ + delete tk; +} + +XTToken *XTTokenizer::newToken(XTThreadPtr self, u_int type, char *start, char *end) +{ + if (!tkn_current) { + if (!(tkn_current = new XTToken())) + xt_throw_errno(XT_CONTEXT, XT_ENOMEM); + } + tkn_current->initCString(type, start, end); + if (type == XT_TK_IDENTIFIER) + tkn_current->identifyReservedWord(); + return tkn_current; +} + +XTToken *XTTokenizer::nextToken(XTThreadPtr self) +{ + char *token_start; + u_int token_type = XT_TK_PUNCTUATION; + char quote; + bool must_be_num; + + restart: + + /* Ignore space: */ + while (*tkn_curr_pos && myxt_isspace(tkn_charset, *tkn_curr_pos)) tkn_curr_pos++; + + token_start = tkn_curr_pos; + switch (*tkn_curr_pos) { + case '\0': + return newToken(self, XT_TK_EOF, NULL, NULL); + // Comment: # ... EOL + case '#': + tkn_curr_pos++; + while (*tkn_curr_pos && *tkn_curr_pos != '\n' && *tkn_curr_pos != '\r') tkn_curr_pos++; + goto restart; + case '-': + if (tkn_curr_pos[1] == '-') { + // Comment: -- ... EOL + while (*tkn_curr_pos && *tkn_curr_pos != '\n' && *tkn_curr_pos != '\r') tkn_curr_pos++; + goto restart; + } + if (myxt_isdigit(tkn_charset, tkn_curr_pos[1])) + goto is_number; + tkn_curr_pos++; + break; + case '+': + if (myxt_isdigit(tkn_charset, tkn_curr_pos[1])) + goto is_number; + tkn_curr_pos++; + break; + case '/': + tkn_curr_pos++; + if (*tkn_curr_pos == '*') { + // Comment: /* ... */ + // Look for: /*!99999 ... */ version conditional statements + tkn_curr_pos++; + if (*tkn_curr_pos == '!') { + tkn_curr_pos++; + if (isdigit(*tkn_curr_pos)) { + while (isdigit(*tkn_curr_pos)) + tkn_curr_pos++; + tkn_in_comment = true; + goto restart; + } + } + + while (*tkn_curr_pos && !(*tkn_curr_pos == '*' && *(tkn_curr_pos+1) == '/')) tkn_curr_pos++; + if (*tkn_curr_pos == '*' && *(tkn_curr_pos+1) == '/') + tkn_curr_pos += 2; + goto restart; + } + break; + case '\'': + token_type = XT_TK_STRING; + goto is_string; + case '"': + case '`': + token_type = XT_TK_IDENTIFIER; + is_string: + quote = *tkn_curr_pos; + tkn_curr_pos++; + while (*tkn_curr_pos) { + if (*tkn_curr_pos == quote) { + // Doubling the quote means stay in string... + if (*(tkn_curr_pos + 1) != quote) + break; + tkn_curr_pos++; + } + tkn_curr_pos++; + } + + if (*tkn_curr_pos == quote) + tkn_curr_pos++; + break; + case '$': + goto is_identifier; + case '*': + if (tkn_in_comment) { + if (tkn_curr_pos[1] == '/') { + tkn_in_comment = false; + tkn_curr_pos += 2; + goto restart; + } + } + /* No break required! */ + default: + if (isNumberChar(tkn_curr_pos[0], tkn_curr_pos[1])) + goto is_number; + + if (isSingleChar(*tkn_curr_pos)) { + token_type = XT_TK_PUNCTUATION; + // The rest are singles... + tkn_curr_pos++; + break; + } + + is_identifier: + // Identifier (any string of characters that is not punctuation or a space: + token_type = XT_TK_IDENTIFIER; + while (isIdentifierChar(*tkn_curr_pos)) + tkn_curr_pos++; + break; + + is_number: + must_be_num = false; + token_type = XT_TK_NUMBER; + + if (*tkn_curr_pos == '-' || *tkn_curr_pos == '+') { + must_be_num = true; + tkn_curr_pos++; + } + + // Number: 9999 [ . 9999 ] [ e/E [+/-] 9999 ] + // However, 9999e or 9999E is an identifier! + while (*tkn_curr_pos && myxt_isdigit(tkn_charset, *tkn_curr_pos)) tkn_curr_pos++; + + if (*tkn_curr_pos == '.') { + must_be_num = true; + tkn_curr_pos++; + while (*tkn_curr_pos && myxt_isdigit(tkn_charset, *tkn_curr_pos)) tkn_curr_pos++; + } + + if (*tkn_curr_pos == 'e' || *tkn_curr_pos == 'E') { + tkn_curr_pos++; + + if (isNumberChar(tkn_curr_pos[0], tkn_curr_pos[1])) { + must_be_num = true; + + if (*tkn_curr_pos == '-' || *tkn_curr_pos == '+') + tkn_curr_pos++; + while (*tkn_curr_pos && myxt_isdigit(tkn_charset, *tkn_curr_pos)) + tkn_curr_pos++; + } + else if (!must_be_num) + token_type = XT_TK_IDENTIFIER; + } + + if (must_be_num || !isIdentifierChar(*tkn_curr_pos)) + break; + + /* Crazy, but true. An identifier can start by looking like a number! */ + goto is_identifier; + } + + return newToken(self, token_type, token_start, tkn_curr_pos); +} + +XTToken *XTTokenizer::nextToken(XTThreadPtr self, c_char *keyword, XTToken *tk) +{ + tk->expectKeyWord(self, keyword); + return nextToken(self); +} + +/* + * ----------------------------------------------------------------------- + * Parser + */ + +/* + We must parse the following syntax. Note that the constraints + may be embedded in a CREATE TABLE/ALTER TABLE statement. + + [CONSTRAINT symbol] FOREIGN KEY [id] (index_col_name, ...) + REFERENCES tbl_name (index_col_name, ...) + [ON DELETE {RESTRICT | CASCADE | SET NULL | SET DEFAULT | NO ACTION}] + [ON UPDATE {RESTRICT | CASCADE | SET NULL | SET DEFAULT | NO ACTION}] +*/ + +class XTParseTable : public XTObject { + public: + void raiseError(XTThreadPtr self, XTToken *tk, int err); + + private: + XTTokenizer *pt_tokenizer; + XTToken *pt_current; + XTStringBufferRec pt_sbuffer; + + void syntaxError(XTThreadPtr self, XTToken *tk); + + void parseIdentifier(XTThreadPtr self, char *name); + int parseKeyAction(XTThreadPtr self); + void parseCreateTable(XTThreadPtr self); + void parseAddTableItem(XTThreadPtr self); + void parseQualifiedName(XTThreadPtr self, char *name); + void parseTableName(XTThreadPtr self, bool alterTable); + void parseExpression(XTThreadPtr self, bool allow_reserved); + void parseBrackets(XTThreadPtr self); + void parseMoveColumn(XTThreadPtr self); + + /* If old_col_name is NULL, then this column is to be added, + * if old_col_name is empty (strlen() = 0) then the column + * exists, and should be modified, otherwize the column + * given is to be modified. + */ + void parseColumnDefinition(XTThreadPtr self, char *old_col_name); + void parseDataType(XTThreadPtr self); + void parseReferenceDefinition(XTThreadPtr self, u_int req_cols); + void optionalIndexName(XTThreadPtr self); + void optionalIndexType(XTThreadPtr self); + u_int columnList(XTThreadPtr self, bool index_cols); + void parseAlterTable(XTThreadPtr self); + void parseCreateIndex(XTThreadPtr self); + void parseDropIndex(XTThreadPtr self); + + public: + XTParseTable() { + pt_tokenizer = NULL; + pt_current = NULL; + memset(&pt_sbuffer, 0, sizeof(XTStringBufferRec)); + } + + virtual void finalize(XTThreadPtr self __attribute__((unused))) { + if (pt_tokenizer) + delete pt_tokenizer; + xt_sb_set_size(NULL, &pt_sbuffer, 0); + } + + // Hooks to receive output from the parser: + virtual void setTableName(XTThreadPtr self __attribute__((unused)), char *name __attribute__((unused)), bool alterTable __attribute__((unused))) { + } + virtual void addColumn(XTThreadPtr self __attribute__((unused)), char *col_name __attribute__((unused)), char *old_col_name __attribute__((unused))) { + } + virtual void setDataType(XTThreadPtr self, char *cstring) { + if (cstring) + xt_free(self, cstring); + } + virtual void setNull(XTThreadPtr self __attribute__((unused)), bool nullOK __attribute__((unused))) { + } + virtual void setAutoInc(XTThreadPtr self __attribute__((unused)), bool autoInc __attribute__((unused))) { + } + + /* Add a contraint. If lastColumn is TRUE then add the contraint + * to the last column. If not, expect addListedColumn() to be called. + */ + virtual void addConstraint(XTThreadPtr self __attribute__((unused)), char *name __attribute__((unused)), u_int type __attribute__((unused)), bool lastColumn __attribute__((unused))) { + } + + /* Move the last column created. If symbol is NULL then move the column to the + * first position, else move it to the position just after the given column. + */ + virtual void moveColumn(XTThreadPtr self __attribute__((unused)), char *col_name __attribute__((unused))) { + } + + virtual void dropColumn(XTThreadPtr self __attribute__((unused)), char *col_name __attribute__((unused))) { + } + + virtual void dropConstraint(XTThreadPtr self __attribute__((unused)), char *name __attribute__((unused)), u_int type __attribute__((unused))) { + } + + virtual void setIndexName(XTThreadPtr self __attribute__((unused)), char *name __attribute__((unused))) { + } + virtual void addListedColumn(XTThreadPtr self __attribute__((unused)), char *index_col_name __attribute__((unused))) { + } + virtual void setReferencedTable(XTThreadPtr self __attribute__((unused)), char *ref_table __attribute__((unused))) { + } + virtual void addReferencedColumn(XTThreadPtr self __attribute__((unused)), char *index_col_name __attribute__((unused))) { + } + virtual void setActions(XTThreadPtr self __attribute__((unused)), int on_delete __attribute__((unused)), int on_update __attribute__((unused))) { + } + + virtual void parseTable(XTThreadPtr self, bool convert, char *sql); +}; + +void XTParseTable::raiseError(XTThreadPtr self, XTToken *tk, int err) +{ + char buffer[100]; + + tk->getTokenText(buffer, 100); + xt_throw_ixterr(XT_CONTEXT, err, buffer); +} + +void XTParseTable::syntaxError(XTThreadPtr self, XTToken *tk) +{ + raiseError(self, tk, XT_ERR_SYNTAX); +} + +void XTParseTable::parseIdentifier(XTThreadPtr self, char *name) +{ + pt_current->expectIdentifier(self); + if (name) { + if (pt_current->getString(name, XT_IDENTIFIER_NAME_SIZE) >= XT_IDENTIFIER_NAME_SIZE) + raiseError(self, pt_current, XT_ERR_ID_TOO_LONG); + } + pt_current = pt_tokenizer->nextToken(self); +} + +int XTParseTable::parseKeyAction(XTThreadPtr self) +{ + XTToken *tk; + + tk = pt_tokenizer->nextToken(self); + + if (tk->isKeyWord("RESTRICT")) + return XT_KEY_ACTION_RESTRICT; + + if (tk->isKeyWord("CASCADE")) + return XT_KEY_ACTION_CASCADE; + + if (tk->isKeyWord("SET")) { + tk = pt_tokenizer->nextToken(self); + if (tk->isKeyWord("DEFAULT")) + return XT_KEY_ACTION_SET_DEFAULT; + tk->expectKeyWord(self, "NULL"); + return XT_KEY_ACTION_SET_NULL; + } + + if (tk->isKeyWord("NO")) { + tk = pt_tokenizer->nextToken(self); + tk->expectKeyWord(self, "ACTION"); + return XT_KEY_ACTION_NO_ACTION; + } + + syntaxError(self, tk); + return 0; +} + +void XTParseTable::parseTable(XTThreadPtr self, bool convert, char *sql) +{ + if (pt_tokenizer) + delete pt_tokenizer; + pt_tokenizer = new XTTokenizer(convert, sql); + if (!pt_tokenizer) + xt_throw_errno(XT_CONTEXT, XT_ENOMEM); + pt_current = pt_tokenizer->nextToken(self); + + if (pt_current->isKeyWord("CREATE")) { + pt_current = pt_tokenizer->nextToken(self); + if (pt_current->isKeyWord("TEMPORARY") || pt_current->isKeyWord("TABLE")) + parseCreateTable(self); + else + parseCreateIndex(self); + } + else if (pt_current->isKeyWord("ALTER")) + parseAlterTable(self); + else if (pt_current->isKeyWord("DROP")) + parseDropIndex(self); + else if (pt_current->isKeyWord("TRUNCATE")) { + pt_current = pt_tokenizer->nextToken(self); + if (pt_current->isKeyWord("TABLE")) + pt_current = pt_tokenizer->nextToken(self); + parseTableName(self, true); + } + else if (pt_current->isKeyWord("OPTIMIZE") || pt_current->isKeyWord("REPAIR")) { + /* OPTIMIZE [LOCAL | NO_WRITE_TO_BINLOG] TABLE tbl_name [, tbl_name] ... + * + * GOTCHA: This cannot work if more than one table is specified, + * because then I cannot find the source table?! + */ + pt_current = pt_tokenizer->nextToken(self); + while (!pt_current->isEOF() && !pt_current->isKeyWord("TABLE")) + pt_current = pt_tokenizer->nextToken(self); + pt_current = pt_tokenizer->nextToken(self); + parseTableName(self, true); + } + else + syntaxError(self, pt_current); +} + +void XTParseTable::parseCreateTable(XTThreadPtr self) +{ + if (pt_current->isKeyWord("TEMPORARY")) + pt_current = pt_tokenizer->nextToken(self); + pt_current = pt_tokenizer->nextToken(self, "TABLE", pt_current); + if (pt_current->isKeyWord("IF")) { + pt_current = pt_tokenizer->nextToken(self); + pt_current = pt_tokenizer->nextToken(self, "NOT", pt_current); + pt_current = pt_tokenizer->nextToken(self, "EXISTS", pt_current); + } + + /* Table name is optional (when loading from dictionary)! */ + if (!pt_current->isKeyWord("(")) + parseTableName(self, false); + else + setTableName(self, NULL, false); + + /* We do not support CREATE ... SELECT! */ + if (pt_current->isKeyWord("(")) { + pt_current = pt_tokenizer->nextToken(self); + // Avoid this: + // create table t3 (select group_concat(a) as a from t1 where a = 'a') union + // (select group_concat(b) as a from t1 where a = 'b'); + if (pt_current->isKeyWord("SELECT")) + return; + + /* Allow empty table definition for temporary table. */ + while (!pt_current->isEOF() && !pt_current->isKeyWord(")")) { + parseAddTableItem(self); + if (!pt_current->isKeyWord(",")) + break; + pt_current = pt_tokenizer->nextToken(self); + } + pt_current = pt_tokenizer->nextToken(self, ")", pt_current); + } +} + +void XTParseTable::parseAddTableItem(XTThreadPtr self) +{ + char name[XT_IDENTIFIER_NAME_SIZE]; + + *name = 0; + if (pt_current->isKeyWord("CONSTRAINT")) { + pt_current = pt_tokenizer->nextToken(self); + if (pt_current->isIdentifier()) + parseQualifiedName(self, name); + } + + if (pt_current->isReservedWord(XT_TK_PRIMARY)) { + pt_current = pt_tokenizer->nextToken(self); + pt_current = pt_tokenizer->nextToken(self, "KEY", pt_current); + + addConstraint(self, name, XT_DD_KEY_PRIMARY, false); + optionalIndexType(self); + + /* GATCHA: Wierd?! This syntax is used in a test: + * alter table t1 add primary key aaa(tt); + */ + if (!pt_current->isKeyWord("(")) + pt_current = pt_tokenizer->nextToken(self); + columnList(self, true); + } + else if (pt_current->isReservedWord(XT_TK_UNIQUE) || + pt_current->isReservedWord(XT_TK_FULLTEXT) || + pt_current->isReservedWord(XT_TK_SPATIAL) || + pt_current->isReservedWord(XT_TK_INDEX) || + pt_current->isReservedWord(XT_TK_KEY)) { + bool is_unique = false; + + if (pt_current->isReservedWord(XT_TK_FULLTEXT) || pt_current->isReservedWord(XT_TK_SPATIAL)) + pt_current = pt_tokenizer->nextToken(self); + else if (pt_current->isReservedWord(XT_TK_UNIQUE)) { + pt_current = pt_tokenizer->nextToken(self); + is_unique = true; + } + if (pt_current->isReservedWord(XT_TK_INDEX) || pt_current->isReservedWord(XT_TK_KEY)) + pt_current = pt_tokenizer->nextToken(self); + + addConstraint(self, name, is_unique ? XT_DD_INDEX_UNIQUE : XT_DD_INDEX, false); + optionalIndexName(self); + optionalIndexType(self); + columnList(self, true); + } + else if (pt_current->isReservedWord(XT_TK_CHECK)) { + pt_current = pt_tokenizer->nextToken(self); + parseExpression(self, false); + } + else if (pt_current->isReservedWord(XT_TK_FOREIGN)) { + u_int req_cols; + + pt_current = pt_tokenizer->nextToken(self); + pt_current = pt_tokenizer->nextToken(self, "KEY", pt_current); + + addConstraint(self, name, XT_DD_KEY_FOREIGN, false); + optionalIndexName(self); + req_cols = columnList(self, false); + /* GOTCHA: According the MySQL manual this is optional, but without domains, + * it is required! + */ + parseReferenceDefinition(self, req_cols); + } + else if (pt_current->isKeyWord("(")) { + pt_current = pt_tokenizer->nextToken(self); + for (;;) { + parseColumnDefinition(self, NULL); + if (!pt_current->isKeyWord(",")) + break; + pt_current = pt_tokenizer->nextToken(self); + } + pt_current = pt_tokenizer->nextToken(self, ")", pt_current); + } + else { + if (pt_current->isReservedWord(XT_TK_COLUMN)) + pt_current = pt_tokenizer->nextToken(self); + parseColumnDefinition(self, NULL); + parseMoveColumn(self); + } + /* GOTCHA: Support: create table t1 (a int not null, key `a` (a) key_block_size=1024) + * and any other undocumented syntax?! + */ + parseExpression(self, true); +} + +void XTParseTable::parseExpression(XTThreadPtr self, bool allow_reserved) +{ + while (!pt_current->isEOF() && !pt_current->isKeyWord(",") && + !pt_current->isKeyWord(")") && (allow_reserved || !pt_current->isReservedWord())) { + if (pt_current->isKeyWord("(")) + parseBrackets(self); + else + pt_current = pt_tokenizer->nextToken(self); + } +} + +void XTParseTable::parseBrackets(XTThreadPtr self) +{ + u_int cnt = 1; + pt_current = pt_tokenizer->nextToken(self, "(", pt_current); + while (cnt) { + if (pt_current->isEOF()) + break; + if (pt_current->isKeyWord("(")) + cnt++; + if (pt_current->isKeyWord(")")) + cnt--; + pt_current = pt_tokenizer->nextToken(self); + } +} + +void XTParseTable::parseMoveColumn(XTThreadPtr self) +{ + if (pt_current->isKeyWord("FIRST")) { + pt_current = pt_tokenizer->nextToken(self); + /* If name is NULL it means move to the front. */ + moveColumn(self, NULL); + } + else if (pt_current->isKeyWord("AFTER")) { + char name[XT_IDENTIFIER_NAME_SIZE]; + + pt_current = pt_tokenizer->nextToken(self); + parseQualifiedName(self, name); + moveColumn(self, name); + } +} + +void XTParseTable::parseQualifiedName(XTThreadPtr self, char *name) +{ + /* Should be an identifier by I have this example: + * CREATE TABLE t1 ( comment CHAR(32) ASCII NOT NULL, koi8_ru_f CHAR(32) CHARACTER SET koi8r NOT NULL default '' ) CHARSET=latin5; + * + * COMMENT is elsewhere used as reserved word?! + */ + if (pt_current->getString(name, XT_IDENTIFIER_NAME_SIZE) >= XT_IDENTIFIER_NAME_SIZE) + raiseError(self, pt_current, XT_ERR_ID_TOO_LONG); + pt_current = pt_tokenizer->nextToken(self); + while (pt_current->isKeyWord(".")) { + pt_current = pt_tokenizer->nextToken(self); + /* Accept anything after the DOT! */ + if (pt_current->getString(name, XT_IDENTIFIER_NAME_SIZE) >= XT_IDENTIFIER_NAME_SIZE) + raiseError(self, pt_current, XT_ERR_ID_TOO_LONG); + pt_current = pt_tokenizer->nextToken(self); + } +} + +void XTParseTable::parseTableName(XTThreadPtr self, bool alterTable) +{ + char name[XT_IDENTIFIER_NAME_SIZE]; + + parseQualifiedName(self, name); + setTableName(self, name, alterTable); +} + +void XTParseTable::parseColumnDefinition(XTThreadPtr self, char *old_col_name) +{ + char col_name[XT_IDENTIFIER_NAME_SIZE]; + + // column_definition + parseQualifiedName(self, col_name); + addColumn(self, col_name, old_col_name); + parseDataType(self); + + for (;;) { + if (pt_current->isReservedWord(XT_TK_NOT)) { + pt_current = pt_tokenizer->nextToken(self); + pt_current = pt_tokenizer->nextToken(self, "NULL", pt_current); + setNull(self, false); + } + else if (pt_current->isReservedWord(XT_TK_NULL)) { + pt_current = pt_tokenizer->nextToken(self); + setNull(self, true); + } + else if (pt_current->isReservedWord(XT_TK_DEFAULT)) { + pt_current = pt_tokenizer->nextToken(self); + /* Possible here [ + | - ] <value> or [ <charset> ] <string> */ + parseExpression(self, false); + } + else if (pt_current->isReservedWord(XT_TK_AUTO_INCREMENT)) { + pt_current = pt_tokenizer->nextToken(self); + setAutoInc(self, true); + } + else if (pt_current->isReservedWord(XT_TK_UNIQUE)) { + pt_current = pt_tokenizer->nextToken(self); + if (pt_current->isReservedWord(XT_TK_KEY)) + pt_current = pt_tokenizer->nextToken(self); + addConstraint(self, NULL, XT_DD_INDEX_UNIQUE, true); + } + else if (pt_current->isReservedWord(XT_TK_KEY)) { + pt_current = pt_tokenizer->nextToken(self); + addConstraint(self, NULL, XT_DD_INDEX, true); + } + else if (pt_current->isReservedWord(XT_TK_PRIMARY)) { + pt_current = pt_tokenizer->nextToken(self); + pt_current = pt_tokenizer->nextToken(self, "KEY", pt_current); + addConstraint(self, NULL, XT_DD_KEY_PRIMARY, true); + } + else if (pt_current->isReservedWord(XT_TK_COMMENT)) { + pt_current = pt_tokenizer->nextToken(self); + pt_current = pt_tokenizer->nextToken(self); + } + else if (pt_current->isReservedWord(XT_TK_REFERENCES)) { + addConstraint(self, NULL, XT_DD_KEY_FOREIGN, true); + parseReferenceDefinition(self, 1); + } + else if (pt_current->isReservedWord(XT_TK_CHECK)) { + pt_current = pt_tokenizer->nextToken(self); + parseExpression(self, false); + } + /* GOTCHA: Not in the documentation: + * CREATE TABLE t1 (c varchar(255) NOT NULL COLLATE utf8_general_ci, INDEX (c)) + */ + else if (pt_current->isReservedWord(XT_TK_COLLATE)) { + pt_current = pt_tokenizer->nextToken(self); + pt_current = pt_tokenizer->nextToken(self); + } + else + break; + } +} + +void XTParseTable::parseDataType(XTThreadPtr self) +{ + /* Not actually implemented because MySQL allows undocumented + * syntax like this: + * create table t1 (c national character varying(10)) + */ + parseExpression(self, false); + setDataType(self, NULL); +} + +void XTParseTable::optionalIndexName(XTThreadPtr self) +{ + // [index_name] + if (!pt_current->isKeyWord("USING") && !pt_current->isKeyWord("(")) { + char name[XT_IDENTIFIER_NAME_SIZE]; + + parseIdentifier(self, name); + setIndexName(self, name); + } +} + +void XTParseTable::optionalIndexType(XTThreadPtr self) +{ + // USING {BTREE | HASH} + if (pt_current->isKeyWord("USING")) { + pt_current = pt_tokenizer->nextToken(self); + pt_current = pt_tokenizer->nextToken(self); + } +} + +u_int XTParseTable::columnList(XTThreadPtr self, bool index_cols) +{ + char name[XT_IDENTIFIER_NAME_SIZE]; + u_int cols = 0; + + pt_current->expectKeyWord(self, "("); + do { + pt_current = pt_tokenizer->nextToken(self); + parseQualifiedName(self, name); + addListedColumn(self, name); + cols++; + if (index_cols) { + if (pt_current->isKeyWord("(")) { + pt_current = pt_tokenizer->nextToken(self); + pt_current = pt_tokenizer->nextToken(self); + pt_current = pt_tokenizer->nextToken(self, ")", pt_current); + } + if (pt_current->isKeyWord("ASC")) + pt_current = pt_tokenizer->nextToken(self); + else if (pt_current->isKeyWord("DESC")) + pt_current = pt_tokenizer->nextToken(self); + } + } while (pt_current->isKeyWord(",")); + pt_current = pt_tokenizer->nextToken(self, ")", pt_current); + return cols; +} + +void XTParseTable::parseReferenceDefinition(XTThreadPtr self, u_int req_cols) +{ + int on_delete = XT_KEY_ACTION_DEFAULT; + int on_update = XT_KEY_ACTION_DEFAULT; + char name[XT_IDENTIFIER_NAME_SIZE]; + u_int cols = 0; + + // REFERENCES tbl_name + pt_current = pt_tokenizer->nextToken(self, "REFERENCES", pt_current); + parseQualifiedName(self, name); + setReferencedTable(self, name); + + // [ (index_col_name,...) ] + if (pt_current->isKeyWord("(")) { + pt_current->expectKeyWord(self, "("); + do { + pt_current = pt_tokenizer->nextToken(self); + parseQualifiedName(self, name); + addReferencedColumn(self, name); + cols++; + if (cols > req_cols) + raiseError(self, pt_current, XT_ERR_INCORRECT_NO_OF_COLS); + } while (pt_current->isKeyWord(",")); + if (cols != req_cols) + raiseError(self, pt_current, XT_ERR_INCORRECT_NO_OF_COLS); + pt_current = pt_tokenizer->nextToken(self, ")", pt_current); + } + else + addReferencedColumn(self, NULL); + + // [MATCH FULL | MATCH PARTIAL | MATCH SIMPLE] + if (pt_current->isKeyWord("MATCH")) { + pt_current = pt_tokenizer->nextToken(self); + pt_current = pt_tokenizer->nextToken(self); + } + + // [ON DELETE {RESTRICT | CASCADE | SET NULL | SET DEFAULT | NO ACTION}] + // [ON UPDATE {RESTRICT | CASCADE | SET NULL | SET DEFAULT | NO ACTION}] + while (pt_current->isKeyWord("ON")) { + pt_current = pt_tokenizer->nextToken(self); + if (pt_current->isKeyWord("DELETE")) + on_delete = parseKeyAction(self); + else if (pt_current->isKeyWord("UPDATE")) + on_update = parseKeyAction(self); + else + syntaxError(self, pt_current); + pt_current = pt_tokenizer->nextToken(self); + } + + setActions(self, on_delete, on_update); +} + +void XTParseTable::parseAlterTable(XTThreadPtr self) +{ + char name[XT_IDENTIFIER_NAME_SIZE]; + + pt_current = pt_tokenizer->nextToken(self, "ALTER", pt_current); + if (pt_current->isKeyWord("IGNORE")) + pt_current = pt_tokenizer->nextToken(self); + pt_current = pt_tokenizer->nextToken(self, "TABLE", pt_current); + parseTableName(self, true); + for (;;) { + if (pt_current->isKeyWord("ADD")) { + pt_current = pt_tokenizer->nextToken(self); + parseAddTableItem(self); + } + else if (pt_current->isKeyWord("ALTER")) { + pt_current = pt_tokenizer->nextToken(self); + if (pt_current->isReservedWord(XT_TK_COLUMN)) + pt_current = pt_tokenizer->nextToken(self); + pt_current->expectIdentifier(self); + pt_current = pt_tokenizer->nextToken(self); + if (pt_current->isKeyWord("SET")) { + pt_current = pt_tokenizer->nextToken(self); + pt_current = pt_tokenizer->nextToken(self, "DEFAULT", pt_current); + pt_current = pt_tokenizer->nextToken(self); + } + else if (pt_current->isKeyWord("DROP")) { + pt_current = pt_tokenizer->nextToken(self); + pt_current = pt_tokenizer->nextToken(self, "DEFAULT", pt_current); + } + } + else if (pt_current->isKeyWord("CHANGE")) { + char old_col_name[XT_IDENTIFIER_NAME_SIZE]; + + pt_current = pt_tokenizer->nextToken(self); + if (pt_current->isReservedWord(XT_TK_COLUMN)) + pt_current = pt_tokenizer->nextToken(self); + + parseQualifiedName(self, old_col_name); + parseColumnDefinition(self, old_col_name); + parseMoveColumn(self); + } + else if (pt_current->isKeyWord("MODIFY")) { + pt_current = pt_tokenizer->nextToken(self); + if (pt_current->isReservedWord(XT_TK_COLUMN)) + pt_current = pt_tokenizer->nextToken(self); + parseColumnDefinition(self, NULL); + parseMoveColumn(self); + } + else if (pt_current->isKeyWord("DROP")) { + pt_current = pt_tokenizer->nextToken(self); + if (pt_current->isReservedWord(XT_TK_PRIMARY)) { + pt_current = pt_tokenizer->nextToken(self); + pt_current = pt_tokenizer->nextToken(self, "KEY", pt_current); + dropConstraint(self, NULL, XT_DD_KEY_PRIMARY); + } + else if (pt_current->isReservedWord(XT_TK_INDEX) || pt_current->isReservedWord(XT_TK_KEY)) { + pt_current = pt_tokenizer->nextToken(self); + parseIdentifier(self, name); + dropConstraint(self, name, XT_DD_INDEX); + } + else if (pt_current->isReservedWord(XT_TK_FOREIGN)) { + pt_current = pt_tokenizer->nextToken(self); + pt_current = pt_tokenizer->nextToken(self, "KEY", pt_current); + parseIdentifier(self, name); + dropConstraint(self, name, XT_DD_KEY_FOREIGN); + } + else { + if (pt_current->isReservedWord(XT_TK_COLUMN)) + pt_current = pt_tokenizer->nextToken(self); + parseQualifiedName(self, name); + dropColumn(self, name); + } + } + else if (pt_current->isKeyWord("RENAME")) { + pt_current = pt_tokenizer->nextToken(self); + if (pt_current->isKeyWord("TO")) + pt_current = pt_tokenizer->nextToken(self); + parseQualifiedName(self, name); + } + else + /* Just ignore the syntax until the next , */ + parseExpression(self, true); + if (!pt_current->isKeyWord(",")) + break; + pt_current = pt_tokenizer->nextToken(self); + } +} + +void XTParseTable::parseCreateIndex(XTThreadPtr self) +{ + char name[XT_IDENTIFIER_NAME_SIZE]; + bool is_unique = false; + + if (pt_current->isReservedWord(XT_TK_UNIQUE)) { + pt_current = pt_tokenizer->nextToken(self); + is_unique = true; + } + else if (pt_current->isReservedWord(XT_TK_FULLTEXT)) + pt_current = pt_tokenizer->nextToken(self); + else if (pt_current->isKeyWord("SPACIAL")) + pt_current = pt_tokenizer->nextToken(self); + pt_current = pt_tokenizer->nextToken(self, "INDEX", pt_current); + parseQualifiedName(self, name); + optionalIndexType(self); + pt_current = pt_tokenizer->nextToken(self, "ON", pt_current); + parseTableName(self, true); + addConstraint(self, NULL, is_unique ? XT_DD_INDEX_UNIQUE : XT_DD_INDEX, false); + setIndexName(self, name); + columnList(self, true); +} + +void XTParseTable::parseDropIndex(XTThreadPtr self) +{ + char name[XT_IDENTIFIER_NAME_SIZE]; + + pt_current = pt_tokenizer->nextToken(self, "DROP", pt_current); + pt_current = pt_tokenizer->nextToken(self, "INDEX", pt_current); + parseQualifiedName(self, name); + pt_current = pt_tokenizer->nextToken(self, "ON", pt_current); + parseTableName(self, true); + dropConstraint(self, name, XT_DD_INDEX); +} + +/* + * ----------------------------------------------------------------------- + * Create/Alter table table + */ + +class XTCreateTable : public XTParseTable { + public: + bool ct_convert; + struct charset_info_st *ct_charset; + XTPathStrPtr ct_tab_path; + u_int ct_contraint_no; + XTDDTable *ct_curr_table; + XTDDColumn *ct_curr_column; + XTDDConstraint *ct_curr_constraint; + + XTCreateTable(bool convert, XTPathStrPtr tab_path) : XTParseTable() { + ct_convert = convert; + ct_charset = myxt_getcharset(convert); + ct_tab_path = tab_path; + ct_curr_table = NULL; + ct_curr_column = NULL; + ct_curr_constraint = NULL; + } + + virtual void finalize(XTThreadPtr self) { + if (ct_curr_table) + ct_curr_table->release(self); + XTParseTable::finalize(self); + } + + virtual void setTableName(XTThreadPtr self, char *name, bool alterTable); + virtual void addColumn(XTThreadPtr self, char *col_name, char *old_col_name); + virtual void addConstraint(XTThreadPtr self, char *name, u_int type, bool lastColumn); + virtual void dropConstraint(XTThreadPtr self, char *name, u_int type); + virtual void addListedColumn(XTThreadPtr self, char *index_col_name); + virtual void setReferencedTable(XTThreadPtr self, char *ref_table); + virtual void addReferencedColumn(XTThreadPtr self, char *index_col_name); + virtual void setActions(XTThreadPtr self, int on_delete, int on_update); + + virtual void parseTable(XTThreadPtr self, bool convert, char *sql); +}; + +static void ri_free_create_table(XTThreadPtr self, XTCreateTable *ct) +{ + if (ct) + ct->release(self); +} + +XTDDTable *xt_ri_create_table(XTThreadPtr self, bool convert, XTPathStrPtr tab_path, char *sql, XTDDTable *start_tab) +{ + XTCreateTable *ct; + XTDDTable *dd_tab; + + if (!(ct = new XTCreateTable(convert, tab_path))) { + if (start_tab) + start_tab->release(self); + xt_throw_errno(XT_CONTEXT, XT_ENOMEM); + } + + ct->ct_curr_table = start_tab; + + pushr_(ri_free_create_table, ct); + + ct->parseTable(self, convert, sql); + + /* Return the table ... */ + dd_tab = ct->ct_curr_table; + ct->ct_curr_table = NULL; + + freer_(); + return dd_tab; +} + +void XTCreateTable::parseTable(XTThreadPtr self, bool convert, char *sql) +{ + u_int i; + + ct_contraint_no = 0; + XTParseTable::parseTable(self, convert, sql); + + /* Remove contraints that do not have matching columns. */ + for (i=0; i<ct_curr_table->dt_indexes.size();) { + if (!ct_curr_table->dt_indexes.itemAt(i)->attachColumns()) + ct_curr_table->dt_indexes.remove(self, i); + else + i++; + } + + for (i=0; i<ct_curr_table->dt_fkeys.size(); ) { + if (!ct_curr_table->dt_fkeys.itemAt(i)->attachColumns()) + ct_curr_table->dt_fkeys.remove(self, i); + else + i++; + } +} + +void XTCreateTable::setTableName(XTThreadPtr self, char *name, bool alterTable) +{ + char path[PATH_MAX]; + + if (!name) + return; + + xt_strcpy(PATH_MAX, path, ct_tab_path->ps_path); + xt_remove_last_name_of_path(path); + + if (ct_convert) { + char buffer[XT_IDENTIFIER_NAME_SIZE]; + size_t len; + + myxt_static_convert_identifier(self, ct_charset, name, buffer, XT_IDENTIFIER_NAME_SIZE); + len = strlen(path); + myxt_static_convert_table_name(self, buffer, &path[len], PATH_MAX - len); + } + else + xt_strcat(PATH_MAX, path, name); + + if (alterTable) { + XTTableHPtr tab; + + /* Find the table... */ + pushsr_(tab, xt_heap_release, xt_use_table(self, (XTPathStrPtr) path, FALSE, TRUE, NULL)); + + /* Clone the foreign key definitions: */ + if (tab && tab->tab_dic.dic_table) { + ct_curr_table->dt_fkeys.deleteAll(self); + ct_curr_table->dt_fkeys.clone(self, &tab->tab_dic.dic_table->dt_fkeys); + for (u_int i=0; i<ct_curr_table->dt_fkeys.size(); i++) + ct_curr_table->dt_fkeys.itemAt(i)->co_table = ct_curr_table; + } + + freer_(); // xt_heap_release(tab) + } +} + +/* + * old_name is given if the column name was changed. + * NOTE that we built the table desciption from the current MySQL table + * description. This means that all changes to columns and + * indexes have already been applied. + * + * Our job is to now add the foreign key changes. + * This means we have to note the current column here. It is + * possible to add a FOREIGN KEY contraint directly to a column! + */ +void XTCreateTable::addColumn(XTThreadPtr self, char *new_name, char *old_name) +{ + char new_col_name[XT_IDENTIFIER_NAME_SIZE]; + + myxt_static_convert_identifier(self, ct_charset, new_name, new_col_name, XT_IDENTIFIER_NAME_SIZE); + ct_curr_column = ct_curr_table->findColumn(new_col_name); + if (old_name) { + char old_col_name[XT_IDENTIFIER_NAME_SIZE]; + + myxt_static_convert_identifier(self, ct_charset, old_name, old_col_name, XT_IDENTIFIER_NAME_SIZE); + ct_curr_table->alterColumnName(self, old_col_name, new_col_name); + } +} + +void XTCreateTable::addConstraint(XTThreadPtr self, char *name, u_int type, bool lastColumn) +{ + /* We are only interested in foreign keys! */ + if (type == XT_DD_KEY_FOREIGN) { + char buffer[50]; + + if (!(ct_curr_constraint = new XTDDForeignKey())) + xt_throw_errno(XT_CONTEXT, XT_ENOMEM); + ct_curr_table->dt_fkeys.append(self, (XTDDForeignKey *) ct_curr_constraint); + ct_curr_constraint->co_table = ct_curr_table; + + if (name && *name) + ct_curr_constraint->co_name = myxt_convert_identifier(self, ct_charset, name); + else { + // Generate a default constraint name: + ct_contraint_no++; + sprintf(buffer, "FOREIGN_%d", ct_contraint_no); + ct_curr_constraint->co_name = xt_dup_string(self, buffer); + } + + if (lastColumn && ct_curr_column) { + /* This constraint has one column, the current column. */ + XTDDColumnRef *cref; + char *col_name = xt_dup_string(self, ct_curr_column->dc_name); + + if (!(cref = new XTDDColumnRef())) { + xt_free(self, col_name); + xt_throw_errno(XT_CONTEXT, XT_ENOMEM); + } + cref->cr_col_name = col_name; + ct_curr_constraint->co_cols.append(self, cref); + } + } + else + /* Other constraints/indexes do not interest us: */ + ct_curr_constraint = NULL; +} + +void XTCreateTable::dropConstraint(XTThreadPtr self, char *name, u_int type) +{ + if (type == XT_DD_KEY_FOREIGN && name) { + u_int i; + XTDDForeignKey *fkey; + char con_name[XT_IDENTIFIER_NAME_SIZE]; + + myxt_static_convert_identifier(self, ct_charset, name, con_name, XT_IDENTIFIER_NAME_SIZE); + for (i=0; i<ct_curr_table->dt_fkeys.size(); i++) { + fkey = ct_curr_table->dt_fkeys.itemAt(i); + if (fkey->co_name && myxt_strcasecmp(con_name, fkey->co_name) == 0) { + ct_curr_table->dt_fkeys.remove(fkey); + fkey->release(self); + } + } + } +} + +void XTCreateTable::addListedColumn(XTThreadPtr self, char *index_col_name) +{ + if (ct_curr_constraint && ct_curr_constraint->co_type == XT_DD_KEY_FOREIGN) { + XTDDColumnRef *cref; + char *name = myxt_convert_identifier(self, ct_charset, index_col_name); + + if (!(cref = new XTDDColumnRef())) { + xt_free(self, name); + xt_throw_errno(XT_CONTEXT, XT_ENOMEM); + } + cref->cr_col_name = name; + ct_curr_constraint->co_cols.append(self, cref); + } +} + +void XTCreateTable::setReferencedTable(XTThreadPtr self, char *ref_table) +{ + XTDDForeignKey *fk = (XTDDForeignKey *) ct_curr_constraint; + char path[PATH_MAX]; + + xt_strcpy(PATH_MAX, path, ct_tab_path->ps_path); + xt_remove_last_name_of_path(path); + if (ct_convert) { + char buffer[XT_IDENTIFIER_NAME_SIZE]; + size_t len; + + myxt_static_convert_identifier(self, ct_charset, ref_table, buffer, XT_IDENTIFIER_NAME_SIZE); + len = strlen(path); + myxt_static_convert_table_name(self, buffer, &path[len], PATH_MAX - len); + } + else + xt_strcat(PATH_MAX, path, ref_table); + + fk->fk_ref_tab_name = (XTPathStrPtr) xt_dup_string(self, path); +} + +/* If the referenced column is NULL, this means + * duplicate the local column list! + */ +void XTCreateTable::addReferencedColumn(XTThreadPtr self, char *index_col_name) +{ + XTDDForeignKey *fk = (XTDDForeignKey *) ct_curr_constraint; + XTDDColumnRef *cref; + char *name; + + if (index_col_name) { + name = myxt_convert_identifier(self, ct_charset, index_col_name); + if (!(cref = new XTDDColumnRef())) { + xt_free(self, name); + xt_throw_errno(XT_CONTEXT, XT_ENOMEM); + } + cref->cr_col_name = name; + fk->fk_ref_cols.append(self, cref); + } + else + fk->fk_ref_cols.clone(self, &fk->co_cols); +} + +void XTCreateTable::setActions(XTThreadPtr self __attribute__((unused)), int on_delete, int on_update) +{ + XTDDForeignKey *fk = (XTDDForeignKey *) ct_curr_constraint; + + fk->fk_on_delete = on_delete; + fk->fk_on_update = on_update; +} + +/* + * ----------------------------------------------------------------------- + * Dictionary methods + */ + +void XTDDColumn::init(XTThreadPtr self, XTObject *obj) { + XTDDColumn *col = (XTDDColumn *) obj; + + XTObject::init(self, obj); + if (col->dc_name) + dc_name = xt_dup_string(self, col->dc_name); + if (col->dc_data_type) + dc_data_type = xt_dup_string(self, col->dc_data_type); + dc_null_ok = col->dc_null_ok; + dc_auto_inc = col->dc_auto_inc; +} + +void XTDDColumn::finalize(XTThreadPtr self) +{ + if (dc_name) + xt_free(self, dc_name); + if (dc_data_type) + xt_free(self, dc_data_type); +} + +void XTDDColumn::loadString(XTThreadPtr self, XTStringBufferPtr sb) +{ + xt_sb_concat(self, sb, "`"); + xt_sb_concat(self, sb, dc_name); + xt_sb_concat(self, sb, "` "); + if (dc_data_type) { + xt_sb_concat(self, sb, dc_data_type); + if (dc_null_ok) + xt_sb_concat(self, sb, " NULL"); + else + xt_sb_concat(self, sb, " NOT NULL"); + if (dc_auto_inc) + xt_sb_concat(self, sb, " AUTO_INCREMENT"); + } +} + +void XTDDColumnRef::init(XTThreadPtr self, XTObject *obj) +{ + XTDDColumnRef *cr = (XTDDColumnRef *) obj; + + XTObject::init(self, obj); + cr_col_name = xt_dup_string(self, cr->cr_col_name); +} + +void XTDDColumnRef::finalize(XTThreadPtr self) +{ + XTObject::finalize(self); + if (cr_col_name) { + xt_free(self, cr_col_name); + cr_col_name = NULL; + } +} + +void XTDDConstraint::init(XTThreadPtr self, XTObject *obj) +{ + XTDDConstraint *co = (XTDDConstraint *) obj; + + XTObject::init(self, obj); + co_type = co->co_type; + if (co->co_name) + co_name = xt_dup_string(self, co->co_name); + if (co->co_ind_name) + co_ind_name = xt_dup_string(self, co->co_ind_name); + co_cols.clone(self, &co->co_cols); +} + +void XTDDConstraint::loadString(XTThreadPtr self, XTStringBufferPtr sb) +{ + if (co_name) { + xt_sb_concat(self, sb, "CONSTRAINT `"); + xt_sb_concat(self, sb, co_name); + xt_sb_concat(self, sb, "` "); + } + switch (co_type) { + case XT_DD_INDEX: + xt_sb_concat(self, sb, "INDEX "); + break; + case XT_DD_INDEX_UNIQUE: + xt_sb_concat(self, sb, "UNIQUE INDEX "); + break; + case XT_DD_KEY_PRIMARY: + xt_sb_concat(self, sb, "PRIMARY KEY "); + break; + case XT_DD_KEY_FOREIGN: + xt_sb_concat(self, sb, "FOREIGN KEY "); + break; + } + if (co_ind_name) { + xt_sb_concat(self, sb, "`"); + xt_sb_concat(self, sb, co_ind_name); + xt_sb_concat(self, sb, "` "); + } + xt_sb_concat(self, sb, "(`"); + xt_sb_concat(self, sb, co_cols.itemAt(0)->cr_col_name); + for (u_int i=1; i<co_cols.size(); i++) { + xt_sb_concat(self, sb, "`, `"); + xt_sb_concat(self, sb, co_cols.itemAt(i)->cr_col_name); + } + xt_sb_concat(self, sb, "`)"); +} + +void XTDDConstraint::alterColumnName(XTThreadPtr self, char *from_name, char *to_name) +{ + XTDDColumnRef *col; + + for (u_int i=0; i<co_cols.size(); i++) { + col = co_cols.itemAt(i); + if (myxt_strcasecmp(col->cr_col_name, from_name) == 0) { + char *name = xt_dup_string(self, to_name); + + xt_free(self, col->cr_col_name); + col->cr_col_name = name; + break; + } + } +} + +void XTDDConstraint::getColumnList(char *buffer, size_t size) +{ + if (co_table->dt_table) { + xt_strcat(size, buffer, "`"); + xt_strcpy(size, buffer, co_table->dt_table->tab_name->ps_path); + xt_strcat(size, buffer, "` (`"); + } + else + xt_strcpy(size, buffer, "(`"); + xt_strcat(size, buffer, co_cols.itemAt(0)->cr_col_name); + for (u_int i=1; i<co_cols.size(); i++) { + xt_strcat(size, buffer, "`, `"); + xt_strcat(size, buffer, co_cols.itemAt(i)->cr_col_name); + } + xt_strcat(size, buffer, "`)"); +} + +bool XTDDConstraint::sameColumns(XTDDConstraint *co) +{ + u_int i = 0; + + if (co_cols.size() != co->co_cols.size()) + return false; + while (i<co_cols.size()) { + if (myxt_strcasecmp(co_cols.itemAt(i)->cr_col_name, co->co_cols.itemAt(i)->cr_col_name) != 0) + return false; + i++; + } + return OK; +} + +bool XTDDConstraint::attachColumns() +{ + XTDDColumn *col; + + for (u_int i=0; i<co_cols.size(); i++) { + if (!(col = co_table->findColumn(co_cols.itemAt(i)->cr_col_name))) + return false; + /* If this is a primary key, then the column becomes not-null! */ + if (co_type == XT_DD_KEY_PRIMARY) + col->dc_null_ok = false; + } + return true; +} + +void XTDDTableRef::finalize(XTThreadPtr self) +{ + XTDDForeignKey *fk; + + if ((fk = tr_fkey)) { + tr_fkey = NULL; + fk->removeReference(self); + xt_heap_release(self, fk->co_table->dt_table); /* We referenced the database table, not the foreign key */ + } + XTObject::finalize(self); +} + +bool XTDDTableRef::checkReference(xtWord1 *before_buf, XTThreadPtr thread) +{ + XTIndexPtr loc_ind, ind; + xtBool no_null = TRUE; + XTOpenTablePtr ot; + XTIdxSearchKeyRec search_key; + xtXactID xn_id; + XTXactWaitRec xw; + + if (!(loc_ind = tr_fkey->getReferenceIndexPtr())) + return false; + + if (!(ind = tr_fkey->getIndexPtr())) + return false; + + search_key.sk_key_value.sv_flags = 0; + search_key.sk_key_value.sv_rec_id = 0; + search_key.sk_key_value.sv_row_id = 0; + search_key.sk_key_value.sv_key = search_key.sk_key_buf; + search_key.sk_key_value.sv_length = myxt_create_foreign_key_from_row(loc_ind, search_key.sk_key_buf, before_buf, ind, &no_null); + search_key.sk_on_key = FALSE; + + if (!no_null) + return true; + + /* Search for the key in the child (referencing) table: */ + if (!(ot = xt_db_open_table_using_tab(tr_fkey->co_table->dt_table, thread))) + goto failed; + + retry: + if (!xt_idx_search(ot, ind, &search_key)) + goto failed; + + while (ot->ot_curr_rec_id && search_key.sk_on_key) { + switch (xt_tab_maybe_committed(ot, ot->ot_curr_rec_id, &xn_id, &ot->ot_curr_row_id, &ot->ot_curr_updated)) { + case XT_MAYBE: + xw.xw_xn_id = xn_id; + if (!xt_xn_wait_for_xact(thread, &xw, NULL)) + goto failed; + goto retry; + case XT_ERR: + goto failed; + case TRUE: + /* We found a matching child: */ + xt_register_ixterr(XT_REG_CONTEXT, XT_ERR_ROW_IS_REFERENCED, tr_fkey->co_name); + goto failed; + break; + case FALSE: + if (!xt_idx_next(ot, ind, &search_key)) + goto failed; + break; + } + } + + /* No matching children, all OK: */ + xt_db_return_table_to_pool_ns(ot); + return true; + + failed: + xt_db_return_table_to_pool_ns(ot); + return false; +} + +/* + * A row has been deleted or updated (after_buf non-NULL), check if it is referenced by the foreign key table. + * If it is referenced, then we need to follow the specified action. + */ +bool XTDDTableRef::modifyRow(XTOpenTablePtr XT_UNUSED(ref_ot), xtWord1 *before_buf, xtWord1 *after_buf, XTThreadPtr thread) +{ + XTIndexPtr loc_ind, ind; + xtBool no_null = TRUE; + XTOpenTablePtr ot; + XTIdxSearchKeyRec search_key; + xtXactID xn_id; + int action = after_buf ? tr_fkey->fk_on_update : tr_fkey->fk_on_delete; + u_int after_key_len = 0; + xtWord1 *after_key = NULL; + XTInfoBufferRec after_info; + XTXactWaitRec xw; + + after_info.ib_free = FALSE; + + if (!(loc_ind = tr_fkey->getReferenceIndexPtr())) + return false; + + if (!(ind = tr_fkey->getIndexPtr())) + return false; + + search_key.sk_key_value.sv_flags = 0; + search_key.sk_key_value.sv_rec_id = 0; + search_key.sk_key_value.sv_row_id = 0; + search_key.sk_key_value.sv_key = search_key.sk_key_buf; + search_key.sk_key_value.sv_length = myxt_create_foreign_key_from_row(loc_ind, search_key.sk_key_buf, before_buf, ind, &no_null); + search_key.sk_on_key = FALSE; + + if (!no_null) + return true; + + if (after_buf) { + if (!(after_key = (xtWord1 *) xt_malloc_ns(XT_INDEX_MAX_KEY_SIZE))) + return false; + after_key_len = myxt_create_foreign_key_from_row(loc_ind, after_key, after_buf, ind, NULL); + + /* Check whether the key value has changed, if not, we have nothing + * to do here! + */ + if (myxt_compare_key(ind, 0, search_key.sk_key_value.sv_length, + search_key.sk_key_value.sv_key, after_key) == 0) + goto success; + + } + + /* Search for the key in the child (referencing) table: */ + if (!(ot = xt_db_open_table_using_tab(tr_fkey->co_table->dt_table, thread))) + goto failed; + + retry: + if (!xt_idx_search(ot, ind, &search_key)) + goto failed_2; + + while (ot->ot_curr_rec_id && search_key.sk_on_key) { + switch (xt_tab_maybe_committed(ot, ot->ot_curr_rec_id, &xn_id, &ot->ot_curr_row_id, &ot->ot_curr_updated)) { + case XT_MAYBE: + xw.xw_xn_id = xn_id; + if (!xt_xn_wait_for_xact(thread, &xw, NULL)) + goto failed_2; + goto retry; + case XT_ERR: + goto failed_2; + case TRUE: + /* We found a matching child: */ + switch (action) { + case XT_KEY_ACTION_CASCADE: + if (after_buf) { + /* Do a cascaded update: */ + if (!xt_tab_load_record(ot, ot->ot_curr_rec_id, &after_info)) + goto failed_2; + + if (!myxt_create_row_from_key(ot, ind, after_key, after_key_len, after_info.ib_db.db_data)) + goto failed_2; + + if (!xt_tab_update_record(ot, NULL, after_info.ib_db.db_data)) { + // Change to duplicate foreign key + if (ot->ot_thread->t_exception.e_xt_err == XT_ERR_DUPLICATE_KEY) + xt_register_ixterr(XT_REG_CONTEXT, XT_ERR_DUPLICATE_FKEY, tr_fkey->co_name); + goto failed_2; + } + } + else { + /* Do a cascaded delete: */ + if (!xt_tab_delete_record(ot, NULL)) + goto failed_2; + } + break; + case XT_KEY_ACTION_SET_NULL: + if (!xt_tab_load_record(ot, ot->ot_curr_rec_id, &after_info)) + goto failed_2; + + myxt_set_null_row_from_key(ot, ind, after_info.ib_db.db_data); + + if (!xt_tab_update_record(ot, NULL, after_info.ib_db.db_data)) + goto failed_2; + break; + case XT_KEY_ACTION_SET_DEFAULT: + + if (!xt_tab_load_record(ot, ot->ot_curr_rec_id, &after_info)) + goto failed_2; + + myxt_set_default_row_from_key(ot, ind, after_info.ib_db.db_data); + + if (!xt_tab_update_record(ot, NULL, after_info.ib_db.db_data)) + goto failed_2; + + break; + case XT_KEY_ACTION_NO_ACTION: +#ifdef XT_IMPLEMENT_NO_ACTION + XTRestrictItemRec r; + + r.ri_tab_id = ref_ot->ot_table->tab_id; + r.ri_rec_id = ref_ot->ot_curr_rec_id; + if (!xt_bl_append(NULL, &thread->st_restrict_list, (void *) &r)) + goto failed_2; + break; +#endif + default: + xt_register_ixterr(XT_REG_CONTEXT, XT_ERR_ROW_IS_REFERENCED, tr_fkey->co_name); + goto failed_2; + } + /* Fall throught to next: */ + case FALSE: + if (!xt_idx_next(ot, ind, &search_key)) + goto failed_2; + break; + } + } + + /* No matching children, all OK: */ + xt_db_return_table_to_pool_ns(ot); + + success: + xt_ib_free(NULL, &after_info); + if (after_key) + xt_free_ns(after_key); + return true; + + failed_2: + xt_db_return_table_to_pool_ns(ot); + + failed: + xt_ib_free(NULL, &after_info); + if (after_key) + xt_free_ns(after_key); + return false; +} + +void XTDDTableRef::deleteAllRows(XTThreadPtr self) +{ + XTOpenTablePtr ot; + xtInt8 row_count; + + if (!tr_fkey->getReferenceIndexPtr()) + throw_(); + + if (!tr_fkey->getIndexPtr()) + throw_(); + + if (!(ot = xt_db_open_table_using_tab(tr_fkey->co_table->dt_table, self))) + throw_(); + + row_count = ((xtInt8) ot->ot_table->tab_row_eof_id) - 1; + row_count -= (xtInt8) ot->ot_table->tab_row_fnum; + + xt_db_return_table_to_pool_ns(ot); + + if (row_count > 0) + xt_throw_ixterr(XT_CONTEXT, XT_ERR_ROW_IS_REFERENCED, tr_fkey->co_name); +} + +void XTDDIndex::init(XTThreadPtr self, XTObject *obj) +{ + XTDDConstraint::init(self, obj); +} + +XTIndexPtr XTDDIndex::getIndexPtr() +{ + if (in_index >= co_table->dt_table->tab_dic.dic_key_count) { + XTDDIndex *in; + + if (!(in = co_table->findIndex(this))) + return NULL; + in_index = in->in_index; + } + return co_table->dt_table->tab_dic.dic_keys[in_index]; +} + +void XTDDForeignKey::init(XTThreadPtr self, XTObject *obj) +{ + XTDDForeignKey *fk = (XTDDForeignKey *) obj; + + XTDDIndex::init(self, obj); + if (fk->fk_ref_tab_name) + fk_ref_tab_name = (XTPathStrPtr) xt_dup_string(self, fk->fk_ref_tab_name->ps_path); + fk_ref_cols.clone(self, &fk->fk_ref_cols); + fk_on_delete = fk->fk_on_delete; + fk_on_update = fk->fk_on_update; +} + +void XTDDForeignKey::finalize(XTThreadPtr self) +{ + XTDDTable *ref_tab; + + if (fk_ref_tab_name) { + xt_free(self, fk_ref_tab_name); + fk_ref_tab_name = NULL; + } + + if ((ref_tab = fk_ref_table)) { + fk_ref_table = NULL; + ref_tab->removeReference(self, this); + xt_heap_release(self, ref_tab->dt_table); /* We referenced the table, not the index! */ + } + + fk_ref_index = UINT_MAX; + + fk_ref_cols.deleteAll(self); + XTDDConstraint::finalize(self); +} + +void XTDDForeignKey::loadString(XTThreadPtr self, XTStringBufferPtr sb) +{ + XTDDConstraint::loadString(self, sb); + xt_sb_concat(self, sb, " REFERENCES `"); + xt_sb_concat(self, sb, xt_last_name_of_path(fk_ref_tab_name->ps_path)); + xt_sb_concat(self, sb, "` "); + + xt_sb_concat(self, sb, "(`"); + xt_sb_concat(self, sb, fk_ref_cols.itemAt(0)->cr_col_name); + for (u_int i=1; i<fk_ref_cols.size(); i++) { + xt_sb_concat(self, sb, "`, `"); + xt_sb_concat(self, sb, fk_ref_cols.itemAt(i)->cr_col_name); + } + xt_sb_concat(self, sb, "`)"); + + if (fk_on_delete != XT_KEY_ACTION_DEFAULT && fk_on_delete != XT_KEY_ACTION_RESTRICT) { + xt_sb_concat(self, sb, " ON DELETE "); + switch (fk_on_delete) { + case XT_KEY_ACTION_CASCADE: xt_sb_concat(self, sb, "CASCADE"); break; + case XT_KEY_ACTION_SET_NULL: xt_sb_concat(self, sb, "SET NULL"); break; + case XT_KEY_ACTION_SET_DEFAULT: xt_sb_concat(self, sb, "SET DEFAULT"); break; + case XT_KEY_ACTION_NO_ACTION: xt_sb_concat(self, sb, "NO ACTION"); break; + } + } + if (fk_on_update != XT_KEY_ACTION_DEFAULT && fk_on_update != XT_KEY_ACTION_RESTRICT) { + xt_sb_concat(self, sb, " ON UPDATE "); + switch (fk_on_update) { + case XT_KEY_ACTION_DEFAULT: xt_sb_concat(self, sb, "RESTRICT"); break; + case XT_KEY_ACTION_RESTRICT: xt_sb_concat(self, sb, "RESTRICT"); break; + case XT_KEY_ACTION_CASCADE: xt_sb_concat(self, sb, "CASCADE"); break; + case XT_KEY_ACTION_SET_NULL: xt_sb_concat(self, sb, "SET NULL"); break; + case XT_KEY_ACTION_SET_DEFAULT: xt_sb_concat(self, sb, "SET DEFAULT"); break; + case XT_KEY_ACTION_NO_ACTION: xt_sb_concat(self, sb, "NO ACTION"); break; + } + } +} + +void XTDDForeignKey::getReferenceList(char *buffer, size_t size) +{ + buffer[0] = '`'; + xt_strcpy(size, buffer + 1, xt_last_name_of_path(fk_ref_tab_name->ps_path)); + xt_strcat(size, buffer, "` ("); + xt_strcat(size, buffer, fk_ref_cols.itemAt(0)->cr_col_name); + for (u_int i=1; i<fk_ref_cols.size(); i++) { + xt_strcat(size, buffer, ", "); + xt_strcat(size, buffer, fk_ref_cols.itemAt(i)->cr_col_name); + } + xt_strcat(size, buffer, ")"); +} + +struct XTIndex *XTDDForeignKey::getReferenceIndexPtr() +{ + if (!fk_ref_table) { + xt_register_taberr(XT_REG_CONTEXT, XT_ERR_REF_TABLE_NOT_FOUND, fk_ref_tab_name); + return NULL; + } + if (fk_ref_index >= fk_ref_table->dt_table->tab_dic.dic_key_count) { + XTDDIndex *in; + + if (!(in = fk_ref_table->findReferenceIndex(this))) + return NULL; + if (!checkReferencedTypes(fk_ref_table)) + return NULL; + fk_ref_index = in->in_index; + } + + return fk_ref_table->dt_table->tab_dic.dic_keys[fk_ref_index]; +} + +bool XTDDForeignKey::sameReferenceColumns(XTDDConstraint *co) +{ + u_int i = 0; + + if (fk_ref_cols.size() != co->co_cols.size()) + return false; + while (i<fk_ref_cols.size()) { + if (myxt_strcasecmp(fk_ref_cols.itemAt(i)->cr_col_name, co->co_cols.itemAt(i)->cr_col_name) != 0) + return false; + i++; + } + return OK; +} + +bool XTDDForeignKey::checkReferencedTypes(XTDDTable *dt) +{ + XTDDColumn *col, *ref_col; + XTDDEnumerableColumn *enum_col, *enum_ref_col; + + if (dt->dt_table->tab_dic.dic_tab_flags & XT_TAB_FLAGS_TEMP_TAB) { + xt_register_xterr(XT_REG_CONTEXT, XT_ERR_FK_REF_TEMP_TABLE); + return false; + } + + for (u_int i=0; i<co_cols.size() && i<fk_ref_cols.size(); i++) { + col = co_table->findColumn(co_cols.itemAt(i)->cr_col_name); + ref_col = dt->findColumn(fk_ref_cols.itemAt(i)->cr_col_name); + if (!col || !ref_col) + continue; + + enum_col = col->castToEnumerable(); + enum_ref_col = ref_col->castToEnumerable(); + + if (!enum_col && !enum_ref_col && (strcmp(col->dc_data_type, ref_col->dc_data_type) == 0)) + continue; + + /* Allow match varchar(30) == varchar(40): */ + if (strncmp(col->dc_data_type, "varchar", 7) == 0 && strncmp(ref_col->dc_data_type, "varchar", 7) == 0) { + char *t1, *t2; + + t1 = col->dc_data_type + 7; + while (*t1 && (isdigit(*t1) || *t1 == '(' || *t1 == ')')) t1++; + t2 = col->dc_data_type + 7; + while (*t2 && (isdigit(*t2) || *t2 == '(' || *t2 == ')')) t2++; + + if (strcmp(t1, t2) == 0) + continue; + } + + /* + * MySQL stores ENUMs an integer indexes for string values. That's why + * it is ok to have refrences between columns that are different ENUMs as long + * as they contain equal number of members, so that for example a cascase update + * will not cause an invaid value to be stored in the child table. + * + * The above is also true for SETs. + * + */ + + if (enum_col && enum_ref_col && + (enum_col->enum_size == enum_ref_col->enum_size) && + (enum_col->is_enum == enum_ref_col->is_enum)) + continue; + + xt_register_tabcolerr(XT_REG_CONTEXT, XT_ERR_REF_TYPE_WRONG, fk_ref_tab_name, ref_col->dc_name); + return false; + } + return true; +} + +void XTDDForeignKey::removeReference(XTThreadPtr self) +{ + XTDDTable *ref_tab; + + xt_xlock_rwlock(self, &co_table->dt_ref_lock); + pushr_(xt_unlock_rwlock, &co_table->dt_ref_lock); + + if ((ref_tab = fk_ref_table)) { + fk_ref_table = NULL; + ref_tab->removeReference(self, this); + xt_heap_release(self, ref_tab->dt_table); /* We referenced the table, not the index! */ + } + + fk_ref_index = UINT_MAX; + + freer_(); // xt_unlock_rwlock(&co_table->dt_ref_lock); +} + +/* + * A row was inserted, check that a key exists in the referenced + * table. + */ +bool XTDDForeignKey::insertRow(xtWord1 *before_buf, xtWord1 *rec_buf, XTThreadPtr thread) +{ + XTIndexPtr loc_ind, ind; + xtBool no_null = TRUE; + XTOpenTablePtr ot; + XTIdxSearchKeyRec search_key; + xtXactID xn_id; + XTXactWaitRec xw; + + /* This lock ensures that the foreign key references are not + * changed. + */ + xt_slock_rwlock_ns(&co_table->dt_ref_lock); + + if (!(loc_ind = getIndexPtr())) + goto failed; + + if (!(ind = getReferenceIndexPtr())) + goto failed; + + search_key.sk_key_value.sv_flags = 0; + search_key.sk_key_value.sv_rec_id = 0; + search_key.sk_key_value.sv_row_id = 0; + search_key.sk_key_value.sv_key = search_key.sk_key_buf; + search_key.sk_key_value.sv_length = myxt_create_foreign_key_from_row(loc_ind, search_key.sk_key_buf, rec_buf, ind, &no_null); + search_key.sk_on_key = FALSE; + + if (!no_null) + goto success; + + if (before_buf) { + u_int before_key_len; + xtWord1 before_key[XT_INDEX_MAX_KEY_SIZE]; + + /* If there is a before buffer, this insert was an update, so check + * if the key value has changed. If not, we need not do anything. + */ + before_key_len = myxt_create_foreign_key_from_row(loc_ind, before_key, before_buf, ind, NULL); + + /* Check whether the key value has changed, if not, we have nothing + * to do here! + */ + if (search_key.sk_key_value.sv_length == before_key_len && + memcmp(search_key.sk_key_buf, before_key, before_key_len) == 0) + goto success; + } + + /* Search for the key in the parent (referenced) table: */ + if (!(ot = xt_db_open_table_using_tab(fk_ref_table->dt_table, thread))) + goto failed; + + retry: + if (!xt_idx_search(ot, ind, &search_key)) + goto failed_2; + + while (ot->ot_curr_rec_id) { + if (!search_key.sk_on_key) + break; + + switch (xt_tab_maybe_committed(ot, ot->ot_curr_rec_id, &xn_id, &ot->ot_curr_row_id, &ot->ot_curr_updated)) { + case XT_MAYBE: + /* We should not get a deadlock here because the thread + * that we are waiting for, should not doing + * data definition (i.e. should not be trying to + * get an exclusive lock on dt_ref_lock. + */ + xw.xw_xn_id = xn_id; + if (!xt_xn_wait_for_xact(thread, &xw, NULL)) + goto failed_2; + goto retry; + case XT_ERR: + goto failed_2; + case TRUE: + /* We found a matching parent: */ + xt_db_return_table_to_pool_ns(ot); + goto success; + case FALSE: + if (!xt_idx_next(ot, ind, &search_key)) + goto failed_2; + break; + } + } + + xt_register_ixterr(XT_REG_CONTEXT, XT_ERR_NO_REFERENCED_ROW, co_name); + + failed_2: + xt_db_return_table_to_pool_ns(ot); + + failed: + xt_unlock_rwlock_ns(&co_table->dt_ref_lock); + return false; + + success: + xt_unlock_rwlock_ns(&co_table->dt_ref_lock); + return true; +} + +/* + * Convert XT_KEY_ACTION_* constants to strings + */ +const char *XTDDForeignKey::actionTypeToString(int action) +{ + switch (action) + { + case XT_KEY_ACTION_DEFAULT: + case XT_KEY_ACTION_RESTRICT: + return "RESTRICT"; + case XT_KEY_ACTION_CASCADE: + return "CASCADE"; + case XT_KEY_ACTION_SET_NULL: + return "SET NULL"; + case XT_KEY_ACTION_SET_DEFAULT: + return ""; + case XT_KEY_ACTION_NO_ACTION: + return "NO ACTION"; + } + + return ""; +} + +void XTDDTable::init(XTThreadPtr self) +{ + xt_init_rwlock_with_autoname(self, &dt_ref_lock); + dt_trefs = NULL; +} + +void XTDDTable::init(XTThreadPtr self, XTObject *obj) +{ + XTDDTable *tab = (XTDDTable *) obj; + u_int i; + + init(self); + XTObject::init(self, obj); + dt_cols.clone(self, &tab->dt_cols); + dt_indexes.clone(self, &tab->dt_indexes); + dt_fkeys.clone(self, &tab->dt_fkeys); + + for (i=0; i<dt_indexes.size(); i++) + dt_indexes.itemAt(i)->co_table = this; + for (i=0; i<dt_fkeys.size(); i++) + dt_fkeys.itemAt(i)->co_table = this; +} + +void XTDDTable::finalize(XTThreadPtr self) +{ + XTDDTableRef *ptr; + + removeReferences(self); + + dt_cols.deleteAll(self); + dt_indexes.deleteAll(self); + dt_fkeys.deleteAll(self); + + while (dt_trefs) { + ptr = dt_trefs; + dt_trefs = dt_trefs->tr_next; + ptr->release(self); + } + + xt_free_rwlock(&dt_ref_lock); +} + +XTDDColumn *XTDDTable::findColumn(char *name) +{ + XTDDColumn *col; + + for (u_int i=0; i<dt_cols.size(); i++) { + col = dt_cols.itemAt(i); + if (myxt_strcasecmp(name, col->dc_name) == 0) + return col; + } + return NULL; +} + +void XTDDTable::loadString(XTThreadPtr self, XTStringBufferPtr sb) +{ + u_int i; + + /* I do not specify a table name because that is known */ + xt_sb_concat(self, sb, "CREATE TABLE (\n "); + + /* We only need to save the foreign key definitions!! + for (i=0; i<dt_cols.size(); i++) { + if (i != 0) + xt_sb_concat(self, sb, ",\n "); + dt_cols.itemAt(i)->loadString(self, sb); + } + + for (i=0; i<dt_indexes.size(); i++) { + xt_sb_concat(self, sb, ",\n "); + dt_indexes.itemAt(i)->loadString(self, sb); + } + */ + + for (i=0; i<dt_fkeys.size(); i++) { + if (i != 0) + xt_sb_concat(self, sb, ",\n "); + dt_fkeys.itemAt(i)->loadString(self, sb); + } + + xt_sb_concat(self, sb, "\n)\n"); +} + +void XTDDTable::loadForeignKeyString(XTThreadPtr self, XTStringBufferPtr sb) +{ + for (u_int i=0; i<dt_fkeys.size(); i++) { + xt_sb_concat(self, sb, ",\n "); + dt_fkeys.itemAt(i)->loadString(self, sb); + } +} + +/* Change all references to the given column name to new name. */ +void XTDDTable::alterColumnName(XTThreadPtr self, char *from_name, char *to_name) +{ + u_int i; + + /* We only alter references in the foreign keys (we copied the + * other changes from MySQL). + */ + for (i=0; i<dt_fkeys.size(); i++) + dt_fkeys.itemAt(i)->alterColumnName(self, from_name, to_name); +} + +void XTDDTable::attachReference(XTThreadPtr self, XTDDForeignKey *fk) +{ + XTDDTableRef *tr; + + /* Remove the reference to this FK if one exists: */ + removeReference(self, fk); + + if (!fk->checkReferencedTypes(this)) { + if (!self->st_ignore_fkeys) + throw_(); + } + + xt_xlock_rwlock(self, &dt_ref_lock); + pushr_(xt_unlock_rwlock, &dt_ref_lock); + + if (!(tr = new XTDDTableRef())) + xt_throw_errno(XT_CONTEXT, XT_ENOMEM); + tr->tr_fkey = fk; + tr->tr_next = dt_trefs; + dt_trefs = tr; + + /* Reference the database table of the foreign key, not the FK itself. + * Just referencing the key will not guarantee that the + * table remains valid because the FK does not reference the + * table. + */ + xt_heap_reference(self, fk->co_table->dt_table); + + freer_(); // xt_unlock_rwlock(&dt_ref_lock); +} + +/* + * Remove the reference to the given foreign key. + */ +void XTDDTable::removeReference(XTThreadPtr self, XTDDForeignKey *fk) +{ + XTDDTableRef *tr, *prev_tr = NULL; + + xt_xlock_rwlock(self, &dt_ref_lock); + pushr_(xt_unlock_rwlock, &dt_ref_lock); + + tr = dt_trefs; + while (tr) { + if (tr->tr_fkey == fk) { + if (prev_tr) + prev_tr->tr_next = tr->tr_next; + else + dt_trefs = tr->tr_next; + break; + } + prev_tr = tr; + tr = tr->tr_next; + } + freer_(); // xt_unlock_rwlock(&dt_ref_lock); + if (tr) + tr->release(self); +} + +void XTDDTable::checkForeignKeyReference(XTThreadPtr self, XTDDForeignKey *fk) +{ + XTDDColumnRef *cr; + + for (u_int i=0; i<fk->fk_ref_cols.size(); i++) { + cr = fk->fk_ref_cols.itemAt(i); + if (!findColumn(cr->cr_col_name)) + xt_throw_tabcolerr(XT_CONTEXT, XT_ERR_COLUMN_NOT_FOUND, fk->fk_ref_tab_name, cr->cr_col_name); + } +} + +void XTDDTable::attachReference(XTThreadPtr self, XTDDTable *dt) +{ + XTDDForeignKey *fk; + + for (u_int i=0; i<dt_fkeys.size(); i++) { + fk = dt_fkeys.itemAt(i); + if (xt_tab_compare_names(fk->fk_ref_tab_name->ps_path, dt->dt_table->tab_name->ps_path) == 0) { + fk->removeReference(self); + + dt->attachReference(self, fk); + + xt_xlock_rwlock(self, &dt_ref_lock); + pushr_(xt_unlock_rwlock, &dt_ref_lock); + /* Referenced the table, not the index! + * We do this because we know that if the table is referenced, the + * index will remain valid! + * This is because the table references the index, and only + * releases it when the table is released. The index does not + * reference the table though! + */ + xt_heap_reference(self, dt->dt_table); + fk->fk_ref_table = dt; + freer_(); // xt_unlock_rwlock(&dt_ref_lock); + } + } +} + +/* + * This function assumes the database table list is locked! + */ +void XTDDTable::attachReferences(XTThreadPtr self, XTDatabaseHPtr db) +{ + XTDDForeignKey *fk; + XTTableHPtr tab; + XTDDTable *dt; + XTHashEnumRec tables; + + /* Search for table referenced by this table. */ + for (u_int i=0; i<dt_fkeys.size(); i++) { + fk = dt_fkeys.itemAt(i); + fk->removeReference(self); + + // if self-reference + if (xt_tab_compare_names(fk->fk_ref_tab_name->ps_path, this->dt_table->tab_name->ps_path) == 0) + fk->fk_ref_table = this; + else { + /* get pointer to the referenced table, load it if needed + * cyclic references are being handled, absent table is ignored + */ + tab = xt_use_table_no_lock(self, db, fk->fk_ref_tab_name, /*TRUE*/FALSE, /*FALSE*/TRUE, NULL, NULL); + + if (tab) { + pushr_(xt_heap_release, tab); + if ((dt = tab->tab_dic.dic_table)) { + // Add a reverse reference: + dt->attachReference(self, fk); + xt_heap_reference(self, dt->dt_table); /* Referenced the table, not the index! */ + fk->fk_ref_table = dt; + } + freer_(); // xt_heap_release(tab) + } + else if (!self->st_ignore_fkeys) { + xt_throw_taberr(XT_CONTEXT, XT_ERR_REF_TABLE_NOT_FOUND, fk->fk_ref_tab_name); + } + } + } + + /* Search for tables that reference this table. */ + xt_ht_enum(self, dt_table->tab_db->db_tables, &tables); + while ((tab = (XTTableHPtr) xt_ht_next(self, &tables))) { + if (tab == this->dt_table) /* no need to re-reference itself, also this fails with "native" pthreads */ + continue; + xt_heap_reference(self, tab); + pushr_(xt_heap_release, tab); + if ((dt = tab->tab_dic.dic_table)) + dt->attachReference(self, this); + freer_(); // xt_heap_release(tab) + } +} + +void XTDDTable::removeReferences(XTThreadPtr self) +{ + XTDDForeignKey *fk; + XTDDTableRef *tr; + XTDDTable *tab; + + xt_xlock_rwlock(self, &dt_ref_lock); + pushr_(xt_unlock_rwlock, &dt_ref_lock); + + for (u_int i=0; i<dt_fkeys.size(); i++) { + fk = dt_fkeys.itemAt(i); + if ((tab = fk->fk_ref_table)) { + fk->fk_ref_table = NULL; + fk->fk_ref_index = UINT_MAX; + if (tab != this) { + /* To avoid deadlock we do not hold more than + * one lock at a time! + */ + freer_(); // xt_unlock_rwlock(&dt_ref_lock); + + tab->removeReference(self, fk); + xt_heap_release(self, tab->dt_table); /* We referenced the table, not the index! */ + + xt_xlock_rwlock(self, &dt_ref_lock); + pushr_(xt_unlock_rwlock, &dt_ref_lock); + } + } + } + + while (dt_trefs) { + tr = dt_trefs; + dt_trefs = tr->tr_next; + freer_(); // xt_unlock_rwlock(&dt_ref_lock); + tr->release(self); + xt_xlock_rwlock(self, &dt_ref_lock); + pushr_(xt_unlock_rwlock, &dt_ref_lock); + } + + freer_(); // xt_unlock_rwlock(&dt_ref_lock); +} + +void XTDDTable::checkForeignKeys(XTThreadPtr self, bool temp_table) +{ + XTDDForeignKey *fk; + + if (temp_table && dt_fkeys.size()) { + /* Temporary tables cannot have foreign keys: */ + xt_throw_xterr(XT_CONTEXT, XT_ERR_FK_ON_TEMP_TABLE); + + } + + /* Search for table referenced by this table. */ + for (u_int i=0; i<dt_fkeys.size(); i++) { + fk = dt_fkeys.itemAt(i); + + if (fk->fk_on_delete == XT_KEY_ACTION_SET_NULL || fk->fk_on_update == XT_KEY_ACTION_SET_NULL) { + /* Check that all the columns can be set to NULL! */ + XTDDColumn *col; + + for (u_int j=0; j<fk->co_cols.size(); j++) { + if ((col = findColumn(fk->co_cols.itemAt(j)->cr_col_name))) { + if (!col->dc_null_ok) + xt_throw_tabcolerr(XT_CONTEXT, XT_ERR_COLUMN_IS_NOT_NULL, fk->fk_ref_tab_name, col->dc_name); + } + } + } + + // TODO: dont close table immediately so it can be possibly reused in this loop + XTTable *ref_tab; + + pushsr_(ref_tab, xt_heap_release, xt_use_table(self, fk->fk_ref_tab_name, FALSE, TRUE, NULL)); + if (ref_tab && !fk->checkReferencedTypes(ref_tab->tab_dic.dic_table)) + throw_(); + freer_(); + + /* Currently I allow foreign keys to be created on tables that do not yet exist! + pushsr_(tab, xt_heap_release, xt_use_table(self, fk->fk_ref_tab_name, FALSE FALSE)); + if ((dt = tab->tab_dic.dic_table)) + dt->checkForeignKeyReference(self, fk); + freer_(); // xt_heap_release(tab) + */ + } +} + +XTDDIndex *XTDDTable::findIndex(XTDDConstraint *co) +{ + XTDDIndex *ind; + + for (u_int i=0; i<dt_indexes.size(); i++) { + ind = dt_indexes.itemAt(i); + if (co->sameColumns(ind)) + return ind; + } + { + char buffer[XT_ERR_MSG_SIZE - 200]; + + co->getColumnList(buffer, XT_ERR_MSG_SIZE - 200); + xt_register_ixterr(XT_REG_CONTEXT, XT_ERR_NO_MATCHING_INDEX, buffer); + } + return NULL; +} + +XTDDIndex *XTDDTable::findReferenceIndex(XTDDForeignKey *fk) +{ + XTDDIndex *ind; + XTDDColumnRef *cr; + u_int i; + + for (i=0; i<dt_indexes.size(); i++) { + ind = dt_indexes.itemAt(i); + if (fk->sameReferenceColumns(ind)) + return ind; + } + + /* If the index does not exist, maybe the columns do not exist?! */ + for (i=0; i<fk->fk_ref_cols.size(); i++) { + cr = fk->fk_ref_cols.itemAt(i); + if (!findColumn(cr->cr_col_name)) { + xt_register_tabcolerr(XT_REG_CONTEXT, XT_ERR_COLUMN_NOT_FOUND, fk->fk_ref_tab_name, cr->cr_col_name); + return NULL; + } + } + + { + char buffer[XT_ERR_MSG_SIZE - 200]; + + fk->getReferenceList(buffer, XT_ERR_MSG_SIZE - 200); + xt_register_ixterr(XT_REG_CONTEXT, XT_ERR_NO_MATCHING_INDEX, buffer); + } + return NULL; +} + +bool XTDDTable::insertRow(XTOpenTablePtr ot, xtWord1 *rec_ptr) +{ + bool ok = true; + XTInfoBufferRec rec_buf; + + if (ot->ot_thread->st_ignore_fkeys) + return true; + + rec_buf.ib_free = FALSE; + if (!rec_ptr) { + if (!xt_tab_load_record(ot, ot->ot_curr_rec_id, &rec_buf)) + return false; + rec_ptr = rec_buf.ib_db.db_data; + + } + for (u_int i=0; i<dt_fkeys.size(); i++) { + if (!dt_fkeys.itemAt(i)->insertRow(NULL, rec_ptr, ot->ot_thread)) { + ok = false; + break; + } + } + xt_ib_free(NULL, &rec_buf); + return ok; +} + +bool XTDDTable::checkNoAction(XTOpenTablePtr ot, xtRecordID rec_id) +{ + XTDDTableRef *tr; + bool ok = true; + XTInfoBufferRec rec_buf; + xtWord1 *rec_ptr; + + if (ot->ot_thread->st_ignore_fkeys) + return true; + + rec_buf.ib_free = FALSE; + if (!xt_tab_load_record(ot, rec_id, &rec_buf)) + return false; + rec_ptr = rec_buf.ib_db.db_data; + + xt_slock_rwlock_ns(&dt_ref_lock); + tr = dt_trefs; + while (tr) { + if (!tr->checkReference(rec_ptr, ot->ot_thread)) { + ok = false; + break; + } + tr = tr->tr_next; + } + xt_unlock_rwlock_ns(&dt_ref_lock); + xt_ib_free(NULL, &rec_buf); + return ok; +} + +bool XTDDTable::deleteRow(XTOpenTablePtr ot, xtWord1 *rec_ptr) +{ + XTDDTableRef *tr; + bool ok = true; + XTInfoBufferRec rec_buf; + + if (ot->ot_thread->st_ignore_fkeys) + return true; + + rec_buf.ib_free = FALSE; + if (!rec_ptr) { + if (!xt_tab_load_record(ot, ot->ot_curr_rec_id, &rec_buf)) + return false; + rec_ptr = rec_buf.ib_db.db_data; + + } + xt_slock_rwlock_ns(&dt_ref_lock); + tr = dt_trefs; + while (tr) { + if (!tr->modifyRow(ot, rec_ptr, NULL, ot->ot_thread)) { + ok = false; + break; + } + tr = tr->tr_next; + } + xt_unlock_rwlock_ns(&dt_ref_lock); + xt_ib_free(NULL, &rec_buf); + return ok; +} + +void XTDDTable::deleteAllRows(XTThreadPtr self) +{ + XTDDTableRef *tr; + + xt_slock_rwlock(self, &dt_ref_lock); + pushr_(xt_unlock_rwlock, &dt_ref_lock); + + tr = dt_trefs; + while (tr) { + tr->deleteAllRows(self); + tr = tr->tr_next; + } + + freer_(); // xt_unlock_rwlock(&dt_ref_lock); +} + +bool XTDDTable::updateRow(XTOpenTablePtr ot, xtWord1 *before, xtWord1 *after) +{ + XTDDTableRef *tr; + bool ok; + XTInfoBufferRec before_buf; + + ASSERT_NS(after); + + if (ot->ot_thread->st_ignore_fkeys) + return true; + + /* If before is NULL then this is a cascaded + * update. In this case there is no need to check + * if the column has a parent!! + */ + if (before) { + if (dt_fkeys.size() > 0) { + for (u_int i=0; i<dt_fkeys.size(); i++) { + if (!dt_fkeys.itemAt(i)->insertRow(before, after, ot->ot_thread)) + return false; + } + } + } + + ok = true; + before_buf.ib_free = FALSE; + + xt_slock_rwlock_ns(&dt_ref_lock); + if ((tr = dt_trefs)) { + if (!before) { + if (!xt_tab_load_record(ot, ot->ot_curr_rec_id, &before_buf)) + return false; + before = before_buf.ib_db.db_data; + } + + while (tr) { + if (!tr->modifyRow(ot, before, after, ot->ot_thread)) { + ok = false; + break; + } + tr = tr->tr_next; + } + } + xt_unlock_rwlock_ns(&dt_ref_lock); + + xt_ib_free(NULL, &before_buf); + return ok; +} + +xtBool XTDDTable::checkCanDrop() +{ + /* no refs or references only itself */ + return (dt_trefs == NULL) || + (dt_trefs->tr_next == NULL) && (dt_trefs->tr_fkey->co_table == this); +} diff --git a/storage/pbxt/src/datadic_xt.h b/storage/pbxt/src/datadic_xt.h new file mode 100644 index 00000000000..825914b60f3 --- /dev/null +++ b/storage/pbxt/src/datadic_xt.h @@ -0,0 +1,295 @@ +/* Copyright (c) 2005 PrimeBase Technologies GmbH + * + * PrimeBase XT + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You 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 + * + * 2004-01-03 Paul McCullagh + * + * H&G2JCtL + * + * Implementation of the PBXT internal data dictionary. + */ + +#ifndef __datadic_xt_h__ +#define __datadic_xt_h__ + +#include <stddef.h> +#include <limits.h> + +#include "ccutils_xt.h" +#include "util_xt.h" + +struct XTDatabase; +struct XTTable; +struct XTIndex; +struct XTOpenTable; +struct XTIndex; + +/* Constraint types: */ +#define XT_DD_UNKNOWN ((u_int) -1) +#define XT_DD_INDEX 0 +#define XT_DD_INDEX_UNIQUE 1 +#define XT_DD_KEY_PRIMARY 2 +#define XT_DD_KEY_FOREIGN 3 + +#define XT_KEY_ACTION_DEFAULT 0 +#define XT_KEY_ACTION_RESTRICT 1 +#define XT_KEY_ACTION_CASCADE 2 +#define XT_KEY_ACTION_SET_NULL 3 +#define XT_KEY_ACTION_SET_DEFAULT 4 +#define XT_KEY_ACTION_NO_ACTION 5 /* Like RESTRICT, but check at end of statement. */ + +class XTDDEnumerableColumn; +class XTDDColumnFactory; + +class XTDDColumn : public XTObject { + +protected: + + XTDDColumn() : XTObject(), + dc_name(NULL), + dc_data_type(NULL), + dc_null_ok(true), + dc_auto_inc(false) { + } + +public: + char *dc_name; + char *dc_data_type; + bool dc_null_ok; + bool dc_auto_inc; + + virtual XTObject *factory(XTThreadPtr self) { + XTObject *new_obj; + + if (!(new_obj = new XTDDColumn())) + xt_throw_errno(XT_CONTEXT, XT_ENOMEM); + return new_obj; + } + + virtual void init(XTThreadPtr self) { + XTObject::init(self); + } + virtual void init(XTThreadPtr self, XTObject *obj); + virtual void finalize(XTThreadPtr self); + virtual void loadString(XTThreadPtr self, XTStringBufferPtr sb); + + virtual XTDDEnumerableColumn *castToEnumerable() { + return NULL; + } + + friend class XTDDColumnFactory; +}; + +/* + * subclass for ENUMs and SETs + */ +class XTDDEnumerableColumn : public XTDDColumn { + +protected: + XTDDEnumerableColumn() : XTDDColumn(), + enum_size(0), is_enum(0) { + } + +public: + int enum_size; /* number of elements in the ENUM or SET */ + xtBool is_enum; /* TRUE if this is ENUM, FALSE if SET */ + + virtual XTObject *factory(XTThreadPtr self) { + XTObject *new_obj; + + if (!(new_obj = new XTDDEnumerableColumn())) + xt_throw_errno(XT_CONTEXT, XT_ENOMEM); + return new_obj; + } + + virtual XTDDEnumerableColumn *castToEnumerable() { + return this; + } + + friend class XTDDColumnFactory; +}; + +class XTDDColumnRef : public XTObject { + public: + char *cr_col_name; + + XTDDColumnRef() : XTObject(), cr_col_name(NULL) { } + + virtual XTObject *factory(XTThreadPtr self) { + XTObject *new_obj; + + if (!(new_obj = new XTDDColumnRef())) + xt_throw_errno(XT_CONTEXT, XT_ENOMEM); + return new_obj; + } + + virtual void init(XTThreadPtr self, XTObject *obj); + virtual void finalize(XTThreadPtr self); +}; + +class XTDDConstraint : public XTObject { + public: + class XTDDTable *co_table; /* The table of this constraint (non-referenced). */ + u_int co_type; + char *co_name; + char *co_ind_name; + XTList<XTDDColumnRef> co_cols; + + XTDDConstraint(u_int t) : XTObject(), + co_table(NULL), + co_type(t), + co_name(NULL), + co_ind_name(NULL) { + } + + virtual void init(XTThreadPtr self, XTObject *obj); + virtual void finalize(XTThreadPtr self) { + if (co_name) + xt_free(self, co_name); + if (co_ind_name) + xt_free(self, co_ind_name); + co_cols.deleteAll(self); + XTObject::finalize(self); + } + virtual void loadString(XTThreadPtr self, XTStringBufferPtr sb); + virtual void alterColumnName(XTThreadPtr self, char *from_name, char *to_name); + void getColumnList(char *buffer, size_t size); + bool sameColumns(XTDDConstraint *co); + bool attachColumns(); +}; + +class XTDDTableRef : public XTObject { + public: + class XTDDTableRef *tr_next; /* The next reference in the list. */ + class XTDDForeignKey *tr_fkey; /* The foreign key that references this table (if not-NULL). */ + + XTDDTableRef() : XTObject(), tr_next(NULL), tr_fkey(NULL) { } + virtual void finalize(XTThreadPtr self); + bool modifyRow(struct XTOpenTable *tab, xtWord1 *before, xtWord1 *after, XTThreadPtr thread); + bool checkReference(xtWord1 *before, XTThreadPtr thread); + void deleteAllRows(XTThreadPtr self); +}; + +class XTDDIndex : public XTDDConstraint { + public: + u_int in_index; + + XTDDIndex(u_int type) : XTDDConstraint(type), in_index((u_int) -1) { } + + virtual XTObject *factory(XTThreadPtr self) { + XTObject *new_obj; + + if (!(new_obj = new XTDDIndex(XT_DD_UNKNOWN))) + xt_throw_errno(XT_CONTEXT, XT_ENOMEM); + return new_obj; + } + + virtual void init(XTThreadPtr self, XTObject *obj); + struct XTIndex *getIndexPtr(); +}; + +/* + * A foreign key is based on a local index. + */ +class XTDDForeignKey : public XTDDIndex { + public: + XTPathStrPtr fk_ref_tab_name; + XTDDTable *fk_ref_table; + u_int fk_ref_index; /* The index on which this foreign key references. */ + XTList<XTDDColumnRef> fk_ref_cols; + int fk_on_delete; + int fk_on_update; + + XTDDForeignKey() : XTDDIndex(XT_DD_KEY_FOREIGN), + fk_ref_tab_name(NULL), + fk_ref_table(NULL), + fk_ref_index(UINT_MAX), + fk_on_delete(0), + fk_on_update(0) { + } + + virtual XTObject *factory(XTThreadPtr self) { + XTObject *new_obj; + + if (!(new_obj = new XTDDForeignKey())) + xt_throw_errno(XT_CONTEXT, XT_ENOMEM); + return new_obj; + } + + virtual void init(XTThreadPtr self, XTObject *obj); + virtual void finalize(XTThreadPtr self); + virtual void loadString(XTThreadPtr self, XTStringBufferPtr sb); + void getReferenceList(char *buffer, size_t size); + struct XTIndex *getReferenceIndexPtr(); + bool sameReferenceColumns(XTDDConstraint *co); + bool checkReferencedTypes(XTDDTable *dt); + void removeReference(XTThreadPtr self); + bool insertRow(xtWord1 *before, xtWord1 *after, XTThreadPtr thread); + bool updateRow(xtWord1 *before, xtWord1 *after, XTThreadPtr thread); + + static const char *actionTypeToString(int action); +}; + +class XTDDTable : public XTObject { + private: + + public: + struct XTTable *dt_table; + + XTList<XTDDColumn> dt_cols; + XTList<XTDDIndex> dt_indexes; + + xt_rwlock_type dt_ref_lock; /* The lock for adding and using references. */ + XTList<XTDDForeignKey> dt_fkeys; /* The foreign keys on this table. */ + XTDDTableRef *dt_trefs; /* A list of tables that reference this table. */ + + virtual XTObject *factory(XTThreadPtr self) { + XTObject *new_obj; + + if (!(new_obj = new XTDDTable())) + xt_throw_errno(XT_CONTEXT, XT_ENOMEM); + return new_obj; + } + + virtual void init(XTThreadPtr self); + virtual void init(XTThreadPtr self, XTObject *obj); + virtual void finalize(XTThreadPtr self); + + XTDDColumn *findColumn(char *name); + void loadString(XTThreadPtr self, XTStringBufferPtr sb); + void loadForeignKeyString(XTThreadPtr self, XTStringBufferPtr sb); + void checkForeignKeyReference(XTThreadPtr self, XTDDForeignKey *fk); + void attachReferences(XTThreadPtr self, struct XTDatabase *db); + void attachReference(XTThreadPtr self, XTDDForeignKey *fk); + void alterColumnName(XTThreadPtr self, char *from_name, char *to_name); + void attachReference(XTThreadPtr self, XTDDTable *dt); + void removeReferences(XTThreadPtr self); + void removeReference(XTThreadPtr self, XTDDForeignKey *fk); + void checkForeignKeys(XTThreadPtr self, bool temp_table); + XTDDIndex *findIndex(XTDDConstraint *co); + XTDDIndex *findReferenceIndex(XTDDForeignKey *fk); + bool insertRow(struct XTOpenTable *rec_ot, xtWord1 *buffer); + bool checkNoAction(struct XTOpenTable *ot, xtRecordID rec_id); + xtBool checkCanDrop(); + bool deleteRow(struct XTOpenTable *rec_ot, xtWord1 *buffer); + void deleteAllRows(XTThreadPtr self); + bool updateRow(struct XTOpenTable *rec_ot, xtWord1 *before, xtWord1 *after); +}; + +XTDDTable *xt_ri_create_table(XTThreadPtr self, bool convert, XTPathStrPtr tab_path, char *sql, XTDDTable *my_tab); + +#endif diff --git a/storage/pbxt/src/datalog_xt.cc b/storage/pbxt/src/datalog_xt.cc new file mode 100644 index 00000000000..dc9423e7eac --- /dev/null +++ b/storage/pbxt/src/datalog_xt.cc @@ -0,0 +1,2052 @@ +/* Copyright (c) 2005 PrimeBase Technologies GmbH + * + * PrimeBase XT + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You 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 + * + * 2005-01-24 Paul McCullagh + * + * H&G2JCtL + */ + +#include "xt_config.h" + +#include <stdio.h> +#ifndef XT_WIN +#include <unistd.h> +#include <signal.h> +#endif +#include <stdlib.h> + +#ifndef DRIZZLED +#include "mysql_priv.h" +#endif + +#include "ha_pbxt.h" + +#include "filesys_xt.h" +#include "database_xt.h" +#include "memory_xt.h" +#include "strutil_xt.h" +#include "sortedlist_xt.h" +#include "util_xt.h" +#include "heap_xt.h" +#include "table_xt.h" +#include "trace_xt.h" +#include "myxt_xt.h" + +static void dl_wake_co_thread(XTDatabaseHPtr db); + +/* + * -------------------------------------------------------------------------------- + * SEQUENTIAL READING + */ + +xtBool XTDataSeqRead::sl_seq_init(struct XTDatabase *db, size_t buffer_size) +{ + sl_db = db; + sl_buffer_size = buffer_size; + + sl_log_file = NULL; + sl_log_eof = 0; + + sl_buf_log_offset = 0; + sl_buffer_len = 0; + sl_buffer = (xtWord1 *) xt_malloc_ns(buffer_size); + + sl_rec_log_id = 0; + sl_rec_log_offset = 0; + sl_record_len = 0; + + return sl_buffer != NULL; +} + +void XTDataSeqRead::sl_seq_exit() +{ + if (sl_log_file) { + xt_close_file_ns(sl_log_file); + sl_log_file = NULL; + } + if (sl_buffer) { + xt_free_ns(sl_buffer); + sl_buffer = NULL; + } +} + +XTOpenFilePtr XTDataSeqRead::sl_seq_open_file() +{ + return sl_log_file; +} + +void XTDataSeqRead::sl_seq_pos(xtLogID *log_id, xtLogOffset *log_offset) +{ + *log_id = sl_rec_log_id; + *log_offset = sl_rec_log_offset; +} + +xtBool XTDataSeqRead::sl_seq_start(xtLogID log_id, xtLogOffset log_offset, xtBool missing_ok) +{ + if (sl_rec_log_id != log_id) { + if (sl_log_file) { + xt_close_file_ns(sl_log_file); + sl_log_file = NULL; + } + + sl_rec_log_id = log_id; + sl_buf_log_offset = sl_rec_log_offset; + sl_buffer_len = 0; + + if (!sl_db->db_datalogs.dlc_open_log(&sl_log_file, log_id, missing_ok ? XT_FS_MISSING_OK : XT_FS_DEFAULT)) + return FAILED; + if (sl_log_file) + sl_log_eof = xt_seek_eof_file(NULL, sl_log_file); + } + sl_rec_log_offset = log_offset; + sl_record_len = 0; + return OK; +} + +xtBool XTDataSeqRead::sl_rnd_read(xtLogOffset log_offset, size_t size, xtWord1 *buffer, size_t *data_read, struct XTThread *thread) +{ + if (!sl_log_file) { + *data_read = 0; + return OK; + } + return xt_pread_file(sl_log_file, log_offset, size, 0, buffer, data_read, &thread->st_statistics.st_data, thread); +} + +/* + * Unlike the transaction log sequential reader, this function only returns + * the header of a record. + */ +xtBool XTDataSeqRead::sl_seq_next(XTXactLogBufferDPtr *ret_entry, xtBool verify, struct XTThread *thread) +{ + XTXactLogBufferDPtr record; + size_t tfer; + size_t len = 0; + size_t rec_offset; + size_t max_rec_len; + xtBool reread_from_buffer; + xtWord4 size; + + /* Go to the next record (xseq_record_len must be initialized + * to 0 for this to work. + */ + sl_rec_log_offset += sl_record_len; + sl_record_len = 0; + + if (sl_rec_log_offset < sl_buf_log_offset || + sl_rec_log_offset >= sl_buf_log_offset + (xtLogOffset) sl_buffer_len) { + /* The current position is nowhere near the buffer, read data into the + * buffer: + */ + tfer = sl_buffer_size; + if (!sl_rnd_read(sl_rec_log_offset, tfer, sl_buffer, &tfer, thread)) + return FAILED; + sl_buf_log_offset = sl_rec_log_offset; + sl_buffer_len = tfer; + + /* Should we go to the next log? */ + if (!tfer) + goto return_empty; + } + + /* The start of the record is in the buffer: */ + read_from_buffer: + rec_offset = (size_t) (sl_rec_log_offset - sl_buf_log_offset); + max_rec_len = sl_buffer_len - rec_offset; + reread_from_buffer = FALSE; + size = 0; + + /* Check the type of record: */ + record = (XTXactLogBufferDPtr) (sl_buffer + rec_offset); + switch (record->xl.xl_status_1) { + case XT_LOG_ENT_HEADER: + if (offsetof(XTXactLogHeaderDRec, xh_size_4) + 4 > max_rec_len) { + reread_from_buffer = TRUE; + goto read_more; + } + len = XT_GET_DISK_4(record->xh.xh_size_4); + if (len > max_rec_len) { + reread_from_buffer = TRUE; + goto read_more; + } + if (verify) { + if (record->xh.xh_checksum_1 != XT_CHECKSUM_1(sl_rec_log_id)) + goto return_empty; + if (XT_LOG_HEAD_MAGIC(record, len) != XT_LOG_FILE_MAGIC) + goto return_empty; + if (len > offsetof(XTXactLogHeaderDRec, xh_log_id_4) + 4) { + if (XT_GET_DISK_4(record->xh.xh_log_id_4) != sl_rec_log_id) + goto return_empty; + } + } + break; + case XT_LOG_ENT_EXT_REC_OK: + case XT_LOG_ENT_EXT_REC_DEL: + len = offsetof(XTactExtRecEntryDRec, er_data); + if (len > max_rec_len) { + reread_from_buffer = TRUE; + goto read_more; + } + size = XT_GET_DISK_4(record->er.er_data_size_4); + if (verify) { + if (sl_rec_log_offset + (xtLogOffset) offsetof(XTactExtRecEntryDRec, er_data) + size > sl_log_eof) + goto return_empty; + } + break; + default: + ASSERT_NS(FALSE); + goto return_empty; + } + + if (len <= max_rec_len) { + /* The record is completely in the buffer: */ + sl_record_len = len+size; + *ret_entry = record; + return OK; + } + + read_more: + /* The record is partially in the buffer. */ + memmove(sl_buffer, sl_buffer + rec_offset, max_rec_len); + sl_buf_log_offset += rec_offset; + sl_buffer_len = max_rec_len; + + /* Read the rest, as far as possible: */ + tfer = sl_buffer_size - max_rec_len; + if (!sl_rnd_read(sl_buf_log_offset + max_rec_len, tfer, sl_buffer + max_rec_len, &tfer, thread)) + return FAILED; + sl_buffer_len += tfer; + + if (sl_buffer_len < len) + /* A partial record is in the log, must be the end of the log: */ + goto return_empty; + + if (reread_from_buffer) + goto read_from_buffer; + + /* The record is not completely in the buffer: */ + sl_record_len = len; + *ret_entry = (XTXactLogBufferDPtr) sl_buffer; + return OK; + + return_empty: + *ret_entry = NULL; + return OK; +} + +void XTDataSeqRead::sl_seq_skip(size_t size) +{ + sl_record_len += size; +} + +void XTDataSeqRead::sl_seq_skip_to(off_t log_offset) +{ + if (log_offset >= sl_rec_log_offset) + sl_record_len = (size_t) (log_offset - sl_rec_log_offset); +} + +/* + * -------------------------------------------------------------------------------- + * STATIC UTILITIES + */ + +static xtBool dl_create_log_header(XTDataLogFilePtr data_log, XTOpenFilePtr of, XTThreadPtr thread) +{ + XTXactLogHeaderDRec header; + + /* The header was not completely written, so write a new one: */ + memset(&header, 0, sizeof(XTXactLogHeaderDRec)); + header.xh_status_1 = XT_LOG_ENT_HEADER; + header.xh_checksum_1 = XT_CHECKSUM_1(data_log->dlf_log_id); + XT_SET_DISK_4(header.xh_size_4, sizeof(XTXactLogHeaderDRec)); + XT_SET_DISK_8(header.xh_free_space_8, 0); + XT_SET_DISK_8(header.xh_file_len_8, sizeof(XTXactLogHeaderDRec)); + XT_SET_DISK_4(header.xh_log_id_4, data_log->dlf_log_id); + XT_SET_DISK_2(header.xh_version_2, XT_LOG_VERSION_NO); + XT_SET_DISK_4(header.xh_magic_4, XT_LOG_FILE_MAGIC); + if (!xt_pwrite_file(of, 0, sizeof(XTXactLogHeaderDRec), &header, &thread->st_statistics.st_data, thread)) + return FAILED; + if (!xt_flush_file(of, &thread->st_statistics.st_data, thread)) + return FAILED; + return OK; +} + +static xtBool dl_write_log_header(XTDataLogFilePtr data_log, XTOpenFilePtr of, xtBool flush, XTThreadPtr thread) +{ + XTXactLogHeaderDRec header; + + /* The header was not completely written, so write a new one: */ + XT_SET_DISK_8(header.xh_free_space_8, data_log->dlf_garbage_count); + XT_SET_DISK_8(header.xh_file_len_8, data_log->dlf_log_eof); + XT_SET_DISK_8(header.xh_comp_pos_8, data_log->dlf_start_offset); + + if (!xt_pwrite_file(of, offsetof(XTXactLogHeaderDRec, xh_free_space_8), 24, (xtWord1 *) &header.xh_free_space_8, &thread->st_statistics.st_data, thread)) + return FAILED; + if (flush && !xt_flush_file(of, &thread->st_statistics.st_data, thread)) + return FAILED; + return OK; +} + +static void dl_free_seq_read(XTThreadPtr self __attribute__((unused)), XTDataSeqReadPtr seq_read) +{ + seq_read->sl_seq_exit(); +} + +static void dl_recover_log(XTThreadPtr self, XTDatabaseHPtr db, XTDataLogFilePtr data_log) +{ + XTDataSeqReadRec seq_read; + XTXactLogBufferDPtr record; + + if (!seq_read.sl_seq_init(db, xt_db_log_buffer_size)) + xt_throw(self); + pushr_(dl_free_seq_read, &seq_read); + + seq_read.sl_seq_start(data_log->dlf_log_id, 0, FALSE); + + for (;;) { + if (!seq_read.sl_seq_next(&record, TRUE, self)) + xt_throw(self); + if (!record) + break; + switch (record->xh.xh_status_1) { + case XT_LOG_ENT_HEADER: + data_log->dlf_garbage_count = XT_GET_DISK_8(record->xh.xh_free_space_8); + data_log->dlf_start_offset = XT_GET_DISK_8(record->xh.xh_comp_pos_8); + seq_read.sl_seq_skip_to((off_t) XT_GET_DISK_8(record->xh.xh_file_len_8)); + break; + } + } + + if (!(data_log->dlf_log_eof = seq_read.sl_rec_log_offset)) { + data_log->dlf_log_eof = sizeof(XTXactLogHeaderDRec); + if (!dl_create_log_header(data_log, seq_read.sl_log_file, self)) + xt_throw(self); + } + if (!dl_write_log_header(data_log, seq_read.sl_log_file, TRUE, self)) + xt_throw(self); + + freer_(); // dl_free_seq_read(&seq_read) +} + +/* + * -------------------------------------------------------------------------------- + * D A T A L O G C AC H E + */ + +void XTDataLogCache::dls_remove_log(XTDataLogFilePtr data_log) +{ + xtLogID log_id = data_log->dlf_log_id; + + switch (data_log->dlf_state) { + case XT_DL_HAS_SPACE: + xt_sl_delete(NULL, dlc_has_space, &log_id); + break; + case XT_DL_TO_COMPACT: + xt_sl_delete(NULL, dlc_to_compact, &log_id); + break; + case XT_DL_TO_DELETE: + xt_sl_delete(NULL, dlc_to_delete, &log_id); + break; + case XT_DL_DELETED: + xt_sl_delete(NULL, dlc_deleted, &log_id); + break; + } +} + +int XTDataLogCache::dls_get_log_state(XTDataLogFilePtr data_log) +{ + if (data_log->dlf_to_much_garbage()) + return XT_DL_TO_COMPACT; + if (data_log->dlf_space_avaliable() > 0) + return XT_DL_HAS_SPACE; + return XT_DL_READ_ONLY; +} + +xtBool XTDataLogCache::dls_set_log_state(XTDataLogFilePtr data_log, int state) +{ + xtLogID log_id = data_log->dlf_log_id; + + xt_lock_mutex_ns(&dlc_lock); + if (state == XT_DL_MAY_COMPACT) { + if (data_log->dlf_state != XT_DL_UNKNOWN && + data_log->dlf_state != XT_DL_HAS_SPACE && + data_log->dlf_state != XT_DL_READ_ONLY) + goto ok; + state = XT_DL_TO_COMPACT; + } + if (state == XT_DL_UNKNOWN) + state = dls_get_log_state(data_log); + switch (state) { + case XT_DL_HAS_SPACE: + if (data_log->dlf_state != XT_DL_HAS_SPACE) { + dls_remove_log(data_log); + if (!xt_sl_insert(NULL, dlc_has_space, &log_id, &log_id)) + goto failed; + } + break; + case XT_DL_TO_COMPACT: +#ifdef DEBUG_LOG_DELETE + printf("-- set to compact: %d\n", (int) log_id); +#endif + if (data_log->dlf_state != XT_DL_TO_COMPACT) { + dls_remove_log(data_log); + if (!xt_sl_insert(NULL, dlc_to_compact, &log_id, &log_id)) + goto failed; + } + dl_wake_co_thread(dlc_db); + break; + case XT_DL_COMPACTED: +#ifdef DEBUG_LOG_DELETE + printf("-- set compacted: %d\n", (int) log_id); +#endif + if (data_log->dlf_state != state) + dls_remove_log(data_log); + break; + case XT_DL_TO_DELETE: +#ifdef DEBUG_LOG_DELETE + printf("-- set to delete log: %d\n", (int) log_id); +#endif + if (data_log->dlf_state != XT_DL_TO_DELETE) { + dls_remove_log(data_log); + if (!xt_sl_insert(NULL, dlc_to_delete, &log_id, &log_id)) + goto failed; + } + break; + case XT_DL_DELETED: +#ifdef DEBUG_LOG_DELETE + printf("-- set DELETED log: %d\n", (int) log_id); +#endif + if (data_log->dlf_state != XT_DL_DELETED) { + dls_remove_log(data_log); + if (!xt_sl_insert(NULL, dlc_deleted, &log_id, &log_id)) + goto failed; + } + break; + default: + if (data_log->dlf_state != state) + dls_remove_log(data_log); + break; + } + data_log->dlf_state = state; + + ok: + xt_unlock_mutex_ns(&dlc_lock); + return OK; + + failed: + xt_unlock_mutex_ns(&dlc_lock); + return FAILED; +} + +static int dl_cmp_log_id(XTThreadPtr XT_UNUSED(self), register const void XT_UNUSED(*thunk), register const void *a, register const void *b) +{ + xtLogID log_id_a = *((xtLogID *) a); + xtLogID log_id_b = *((xtLogID *) b); + + if (log_id_a == log_id_b) + return 0; + if (log_id_a < log_id_b) + return -1; + return 1; +} + +void XTDataLogCache::dlc_init(XTThreadPtr self, XTDatabaseHPtr db) +{ + XTOpenDirPtr od; + char log_dir[PATH_MAX]; + char *file; + xtLogID log_id; + XTDataLogFilePtr data_log= NULL; + + memset(this, 0, sizeof(XTDataLogCacheRec)); + dlc_db = db; + try_(a) { + xt_init_mutex_with_autoname(self, &dlc_lock); + xt_init_cond(self, &dlc_cond); + for (u_int i=0; i<XT_DL_NO_OF_SEGMENTS; i++) { + xt_init_mutex_with_autoname(self, &dlc_segment[i].dls_lock); + xt_init_cond(self, &dlc_segment[i].dls_cond); + } + dlc_has_space = xt_new_sortedlist(self, sizeof(xtLogID), 20, 10, dl_cmp_log_id, NULL, NULL, FALSE, FALSE); + dlc_to_compact = xt_new_sortedlist(self, sizeof(xtLogID), 20, 10, dl_cmp_log_id, NULL, NULL, FALSE, FALSE); + dlc_to_delete = xt_new_sortedlist(self, sizeof(xtLogID), 20, 10, dl_cmp_log_id, NULL, NULL, FALSE, FALSE); + dlc_deleted = xt_new_sortedlist(self, sizeof(xtLogID), 20, 10, dl_cmp_log_id, NULL, NULL, FALSE, FALSE); + xt_init_mutex_with_autoname(self, &dlc_mru_lock); + xt_init_mutex_with_autoname(self, &dlc_head_lock); + + xt_strcpy(PATH_MAX, log_dir, dlc_db->db_main_path); + xt_add_data_dir(PATH_MAX, log_dir); + if (xt_fs_exists(log_dir)) { + pushsr_(od, xt_dir_close, xt_dir_open(self, log_dir, NULL)); + while (xt_dir_next(self, od)) { + file = xt_dir_name(self, od); + if (xt_ends_with(file, ".xt")) { + if ((log_id = (xtLogID) xt_file_name_to_id(file))) { + if (!dlc_get_data_log(&data_log, log_id, TRUE, NULL)) + xt_throw(self); + dl_recover_log(self, db, data_log); + if (!dls_set_log_state(data_log, XT_DL_UNKNOWN)) + xt_throw(self); + } + } + } + freer_(); + } + } + catch_(a) { + dlc_exit(self); + xt_throw(self); + } + cont_(a); +} + +void XTDataLogCache::dlc_exit(XTThreadPtr self) +{ + XTDataLogFilePtr data_log, tmp_data_log; + XTOpenLogFilePtr open_log, tmp_open_log; + + if (dlc_has_space) { + xt_free_sortedlist(self, dlc_has_space); + dlc_has_space = NULL; + } + if (dlc_to_compact) { + xt_free_sortedlist(self, dlc_to_compact); + dlc_to_compact = NULL; + } + if (dlc_to_delete) { + xt_free_sortedlist(self, dlc_to_delete); + dlc_to_delete = NULL; + } + if (dlc_deleted) { + xt_free_sortedlist(self, dlc_deleted); + dlc_deleted = NULL; + } + for (u_int i=0; i<XT_DL_NO_OF_SEGMENTS; i++) { + for (u_int j=0; j<XT_DL_SEG_HASH_TABLE_SIZE; j++) { + data_log = dlc_segment[i].dls_hash_table[j]; + while (data_log) { + if (data_log->dlf_log_file) { + xt_close_file_ns(data_log->dlf_log_file); + data_log->dlf_log_file = NULL; + } + + open_log = data_log->dlf_free_list; + while (open_log) { + if (open_log->odl_log_file) + xt_close_file(self, open_log->odl_log_file); + tmp_open_log = open_log; + open_log = open_log->odl_next_free; + xt_free(self, tmp_open_log); + } + tmp_data_log = data_log; + data_log = data_log->dlf_next_hash; + + xt_free(self, tmp_data_log); + } + } + xt_free_mutex(&dlc_segment[i].dls_lock); + xt_free_cond(&dlc_segment[i].dls_cond); + } + xt_free_mutex(&dlc_head_lock); + xt_free_mutex(&dlc_mru_lock); + xt_free_mutex(&dlc_lock); + xt_free_cond(&dlc_cond); +} + +void XTDataLogCache::dlc_name(size_t size, char *path, xtLogID log_id) +{ + char name[50]; + + sprintf(name, "dlog-%lu.xt", (u_long) log_id); + xt_strcpy(size, path, dlc_db->db_main_path); + xt_add_data_dir(size, path); + xt_add_dir_char(size, path); + xt_strcat(size, path, name); +} + +xtBool XTDataLogCache::dlc_open_log(XTOpenFilePtr *fh, xtLogID log_id, int mode) +{ + char log_path[PATH_MAX]; + + dlc_name(PATH_MAX, log_path, log_id); + return xt_open_file_ns(fh, log_path, mode); +} + +xtBool XTDataLogCache::dlc_unlock_log(XTDataLogFilePtr data_log) +{ + if (data_log->dlf_log_file) { + xt_close_file_ns(data_log->dlf_log_file); + data_log->dlf_log_file = NULL; + } + + return dls_set_log_state(data_log, XT_DL_UNKNOWN); +} + +XTDataLogFilePtr XTDataLogCache::dlc_get_log_for_writing(off_t space_required, struct XTThread *thread) +{ + xtLogID log_id, *log_id_ptr = NULL; + size_t size; + size_t idx; + XTDataLogFilePtr data_log = NULL; + + xt_lock_mutex_ns(&dlc_lock); + + /* Look for an existing log with enough space: */ + size = xt_sl_get_size(dlc_has_space); + for (idx=0; idx<size; idx++) { + log_id_ptr = (xtLogID *) xt_sl_item_at(dlc_has_space, idx); + if (!dlc_get_data_log(&data_log, *log_id_ptr, FALSE, NULL)) + goto failed; + if (data_log) { + if (data_log->dlf_space_avaliable() >= space_required) + break; + data_log = NULL; + } + else { + ASSERT_NS(FALSE); + xt_sl_delete_item_at(NULL, dlc_has_space, idx); + idx--; + size--; + } + } + + if (data_log) { + /* Found a log: */ + if (!dlc_open_log(&data_log->dlf_log_file, *log_id_ptr, XT_FS_DEFAULT)) + goto failed; + xt_sl_delete_item_at(NULL, dlc_has_space, idx); + } + else { + /* Create a new log: */ + log_id = dlc_next_log_id; + for (u_int i=0; i<XT_DL_MAX_LOG_ID; i++) { + log_id++; + if (log_id > XT_DL_MAX_LOG_ID) + log_id = 1; + if (!dlc_get_data_log(&data_log, log_id, FALSE, NULL)) + goto failed; + if (!data_log) + break; + } + dlc_next_log_id = log_id; + if (data_log) { + xt_register_ulxterr(XT_REG_CONTEXT, XT_ERR_LOG_MAX_EXCEEDED, (u_long) XT_DL_MAX_LOG_ID); + goto failed; + } + if (!dlc_get_data_log(&data_log, log_id, TRUE, NULL)) + goto failed; + if (!dlc_open_log(&data_log->dlf_log_file, log_id, XT_FS_CREATE | XT_FS_MAKE_PATH)) + goto failed; + data_log->dlf_log_eof = sizeof(XTXactLogHeaderDRec); + if (!dl_create_log_header(data_log, data_log->dlf_log_file, thread)) { + xt_close_file_ns(data_log->dlf_log_file); + goto failed; + } + /* By setting this late we ensure that the error + * will be repeated. + */ + dlc_next_log_id = log_id; + } + data_log->dlf_state = XT_DL_EXCLUSIVE; + + xt_unlock_mutex_ns(&dlc_lock); + return data_log; + + failed: + xt_unlock_mutex_ns(&dlc_lock); + return NULL; +} + +xtBool XTDataLogCache::dlc_get_data_log(XTDataLogFilePtr *lf, xtLogID log_id, xtBool create, XTDataLogSegPtr *ret_seg) +{ + register XTDataLogSegPtr seg; + register u_int hash_idx; + register XTDataLogFilePtr data_log; + + /* Which segment, and hash index: */ + seg = &dlc_segment[log_id & XT_DL_SEGMENT_MASK]; + hash_idx = (log_id >> XT_DL_SEGMENT_SHIFTS) % XT_DL_SEG_HASH_TABLE_SIZE; + + /* Lock the segment: */ + xt_lock_mutex_ns(&seg->dls_lock); + + /* Find the log file on the hash list: */ + data_log = seg->dls_hash_table[hash_idx]; + while (data_log) { + if (data_log->dlf_log_id == log_id) + break; + data_log = data_log->dlf_next_hash; + } + + if (!data_log && create) { + /* Create a new log file structure: */ + if (!(data_log = (XTDataLogFilePtr) xt_calloc_ns(sizeof(XTDataLogFileRec)))) + goto failed; + data_log->dlf_log_id = log_id; + data_log->dlf_next_hash = seg->dls_hash_table[hash_idx]; + seg->dls_hash_table[hash_idx] = data_log; + } + + if (ret_seg) { + /* This gives the caller the lock: */ + *ret_seg = seg; + *lf = data_log; + return OK; + } + + xt_unlock_mutex_ns(&seg->dls_lock); + *lf = data_log; + return OK; + + failed: + xt_unlock_mutex_ns(&seg->dls_lock); + return FAILED; +} + +/* + * If just_close is FALSE, then a log is being deleted. + * This means that that the log may still be in exclusive use by + * some thread. So we just close the log! + */ +xtBool XTDataLogCache::dlc_remove_data_log(xtLogID log_id, xtBool just_close) +{ + register XTDataLogSegPtr seg; + register u_int hash_idx; + register XTDataLogFilePtr data_log; + XTOpenLogFilePtr open_log, tmp_open_log; + + /* Which segment, and hash index: */ + seg = &dlc_segment[log_id & XT_DL_SEGMENT_MASK]; + hash_idx = (log_id >> XT_DL_SEGMENT_SHIFTS) % XT_DL_SEG_HASH_TABLE_SIZE; + + /* Lock the segment: */ + retry: + xt_lock_mutex_ns(&seg->dls_lock); + + /* Find the log file on the hash list: */ + data_log = seg->dls_hash_table[hash_idx]; + while (data_log) { + if (data_log->dlf_log_id == log_id) + break; + data_log = data_log->dlf_next_hash; + } + + if (data_log) { + xt_lock_mutex_ns(&dlc_mru_lock); + + open_log = data_log->dlf_free_list; + while (open_log) { + if (open_log->odl_log_file) + xt_close_file_ns(open_log->odl_log_file); + + /* Remove from MRU list: */ + if (dlc_lru_open_log == open_log) { + dlc_lru_open_log = open_log->odl_mr_used; + ASSERT_NS(!open_log->odl_lr_used); + } + else if (open_log->odl_lr_used) + open_log->odl_lr_used->odl_mr_used = open_log->odl_mr_used; + if (dlc_mru_open_log == open_log) { + dlc_mru_open_log = open_log->odl_lr_used; + ASSERT_NS(!open_log->odl_mr_used); + } + else if (open_log->odl_mr_used) + open_log->odl_mr_used->odl_lr_used = open_log->odl_lr_used; + + data_log->dlf_open_count--; + tmp_open_log = open_log; + open_log = open_log->odl_next_free; + xt_free_ns(tmp_open_log); + } + data_log->dlf_free_list = NULL; + + xt_unlock_mutex_ns(&dlc_mru_lock); + + if (data_log->dlf_open_count) { + if (!xt_timed_wait_cond_ns(&seg->dls_cond, &seg->dls_lock, 2000)) + goto failed; + xt_unlock_mutex_ns(&seg->dls_lock); + goto retry; + } + + /* Close the exclusive file if required: */ + if (data_log->dlf_log_file) { + xt_close_file_ns(data_log->dlf_log_file); + data_log->dlf_log_file = NULL; + } + + if (!just_close) { + /* Remove the log from the hash list: */ + XTDataLogFilePtr ptr, pptr = NULL; + + ptr = seg->dls_hash_table[hash_idx]; + while (ptr) { + if (ptr == data_log) + break; + pptr = ptr; + ptr = ptr->dlf_next_hash; + } + + if (ptr == data_log) { + if (pptr) + pptr->dlf_next_hash = ptr->dlf_next_hash; + else + seg->dls_hash_table[hash_idx] = ptr->dlf_next_hash; + } + + xt_free_ns(data_log); + } + } + + xt_unlock_mutex_ns(&seg->dls_lock); + return OK; + + failed: + xt_unlock_mutex_ns(&seg->dls_lock); + return FAILED; +} + +xtBool XTDataLogCache::dlc_get_open_log(XTOpenLogFilePtr *ol, xtLogID log_id) +{ + register XTDataLogSegPtr seg; + register u_int hash_idx; + register XTDataLogFilePtr data_log; + register XTOpenLogFilePtr open_log; + char path[PATH_MAX]; + + /* Which segment, and hash index: */ + seg = &dlc_segment[log_id & XT_DL_SEGMENT_MASK]; + hash_idx = (log_id >> XT_DL_SEGMENT_SHIFTS) % XT_DL_SEG_HASH_TABLE_SIZE; + + /* Lock the segment: */ + xt_lock_mutex_ns(&seg->dls_lock); + + /* Find the log file on the hash list: */ + data_log = seg->dls_hash_table[hash_idx]; + while (data_log) { + if (data_log->dlf_log_id == log_id) + break; + data_log = data_log->dlf_next_hash; + } + + if (!data_log) { + /* Create a new log file structure: */ + dlc_name(PATH_MAX, path, log_id); + if (!xt_fs_exists(path)) { + xt_register_ixterr(XT_REG_CONTEXT, XT_ERR_DATA_LOG_NOT_FOUND, path); + goto failed; + } + if (!(data_log = (XTDataLogFilePtr) xt_calloc_ns(sizeof(XTDataLogFileRec)))) + goto failed; + data_log->dlf_log_id = log_id; + data_log->dlf_next_hash = seg->dls_hash_table[hash_idx]; + seg->dls_hash_table[hash_idx] = data_log; + } + + if ((open_log = data_log->dlf_free_list)) { + /* Remove from the free list: */ + if ((data_log->dlf_free_list = open_log->odl_next_free)) + data_log->dlf_free_list->odl_prev_free = NULL; + + /* This file has been most recently used: */ + if (XT_TIME_DIFF(open_log->odl_ru_time, dlc_ru_now) > (XT_DL_LOG_POOL_SIZE >> 1)) { + /* Move to the front of the MRU list: */ + xt_lock_mutex_ns(&dlc_mru_lock); + + open_log->odl_ru_time = ++dlc_ru_now; + if (dlc_mru_open_log != open_log) { + /* Remove from the MRU list: */ + if (dlc_lru_open_log == open_log) { + dlc_lru_open_log = open_log->odl_mr_used; + ASSERT_NS(!open_log->odl_lr_used); + } + else if (open_log->odl_lr_used) + open_log->odl_lr_used->odl_mr_used = open_log->odl_mr_used; + if (open_log->odl_mr_used) + open_log->odl_mr_used->odl_lr_used = open_log->odl_lr_used; + + /* Make the file the most recently used: */ + if ((open_log->odl_lr_used = dlc_mru_open_log)) + dlc_mru_open_log->odl_mr_used = open_log; + open_log->odl_mr_used = NULL; + dlc_mru_open_log = open_log; + if (!dlc_lru_open_log) + dlc_lru_open_log = open_log; + } + xt_unlock_mutex_ns(&dlc_mru_lock); + } + } + else { + /* Create a new open file: */ + if (!(open_log = (XTOpenLogFilePtr) xt_calloc_ns(sizeof(XTOpenLogFileRec)))) + goto failed; + dlc_name(PATH_MAX, path, log_id); + if (!xt_open_file_ns(&open_log->odl_log_file, path, XT_FS_DEFAULT)) { + xt_free_ns(open_log); + goto failed; + } + open_log->olf_log_id = log_id; + open_log->odl_data_log = data_log; + data_log->dlf_open_count++; + + /* Make the new open file the most recently used: */ + xt_lock_mutex_ns(&dlc_mru_lock); + open_log->odl_ru_time = ++dlc_ru_now; + if ((open_log->odl_lr_used = dlc_mru_open_log)) + dlc_mru_open_log->odl_mr_used = open_log; + open_log->odl_mr_used = NULL; + dlc_mru_open_log = open_log; + if (!dlc_lru_open_log) + dlc_lru_open_log = open_log; + dlc_open_count++; + xt_unlock_mutex_ns(&dlc_mru_lock); + } + + open_log->odl_in_use = TRUE; + xt_unlock_mutex_ns(&seg->dls_lock); + *ol = open_log; + + if (dlc_open_count > XT_DL_LOG_POOL_SIZE) { + u_int target = XT_DL_LOG_POOL_SIZE / 4 * 3; + xtLogID free_log_id; + + /* Remove some open files: */ + while (dlc_open_count > target) { + XTOpenLogFilePtr to_free = dlc_lru_open_log; + + if (!to_free || to_free->odl_in_use) + break; + + /* Dirty read the file ID: */ + free_log_id = to_free->olf_log_id; + + seg = &dlc_segment[free_log_id & XT_DL_SEGMENT_MASK]; + + /* Lock the segment: */ + xt_lock_mutex_ns(&seg->dls_lock); + + /* Lock the MRU list: */ + xt_lock_mutex_ns(&dlc_mru_lock); + + /* Check if we have the same open file: */ + if (dlc_lru_open_log == to_free && !to_free->odl_in_use) { + data_log = to_free->odl_data_log; + + /* Remove from the MRU list: */ + dlc_lru_open_log = to_free->odl_mr_used; + ASSERT_NS(!to_free->odl_lr_used); + + if (dlc_mru_open_log == to_free) { + dlc_mru_open_log = to_free->odl_lr_used; + ASSERT_NS(!to_free->odl_mr_used); + } + else if (to_free->odl_mr_used) + to_free->odl_mr_used->odl_lr_used = to_free->odl_lr_used; + + /* Remove from the free list of the file: */ + if (data_log->dlf_free_list == to_free) { + data_log->dlf_free_list = to_free->odl_next_free; + ASSERT_NS(!to_free->odl_prev_free); + } + else if (to_free->odl_prev_free) + to_free->odl_prev_free->odl_next_free = to_free->odl_next_free; + if (to_free->odl_next_free) + to_free->odl_next_free->odl_prev_free = to_free->odl_prev_free; + ASSERT_NS(data_log->dlf_open_count > 0); + data_log->dlf_open_count--; + dlc_open_count--; + } + else + to_free = NULL; + + xt_unlock_mutex_ns(&dlc_mru_lock); + xt_unlock_mutex_ns(&seg->dls_lock); + + if (to_free) { + xt_close_file_ns(to_free->odl_log_file); + xt_free_ns(to_free); + } + } + } + + return OK; + + failed: + xt_unlock_mutex_ns(&seg->dls_lock); + return FAILED; +} + +void XTDataLogCache::dlc_release_open_log(XTOpenLogFilePtr open_log) +{ + register XTDataLogSegPtr seg; + register XTDataLogFilePtr data_log = open_log->odl_data_log; + + /* Which segment, and hash index: */ + seg = &dlc_segment[open_log->olf_log_id & XT_DL_SEGMENT_MASK]; + + xt_lock_mutex_ns(&seg->dls_lock); + open_log->odl_next_free = data_log->dlf_free_list; + open_log->odl_prev_free = NULL; + if (data_log->dlf_free_list) + data_log->dlf_free_list->odl_prev_free = open_log; + data_log->dlf_free_list = open_log; + open_log->odl_in_use = FALSE; + + /* Wakeup any exclusive lockers: */ + if (!xt_broadcast_cond_ns(&seg->dls_cond)) + xt_log_and_clear_exception_ns(); + + xt_unlock_mutex_ns(&seg->dls_lock); +} + +/* + * -------------------------------------------------------------------------------- + * D A T A L O G F I L E + */ + +off_t XTDataLogFile::dlf_space_avaliable() +{ + if (dlf_log_eof < xt_db_data_log_threshold) + return xt_db_data_log_threshold - dlf_log_eof; + return 0; +} + +xtBool XTDataLogFile::dlf_to_much_garbage() +{ + if (!dlf_log_eof) + return FALSE; + return dlf_garbage_count * 100 / dlf_log_eof >= xt_db_garbage_threshold; +} + +/* + * -------------------------------------------------------------------------------- + * D A T A L O G B U F F E R + */ + +void XTDataLogBuffer::dlb_init(XTDatabaseHPtr db, size_t buffer_size) +{ + ASSERT_NS(!dlb_db); + ASSERT_NS(!dlb_buffer_size); + ASSERT_NS(!dlb_data_log); + ASSERT_NS(!dlb_log_buffer); + dlb_db = db; + dlb_buffer_size = buffer_size; +} + +void XTDataLogBuffer::dlb_exit(XTThreadPtr self) +{ + dlb_close_log(self); + if (dlb_log_buffer) { + xt_free(self, dlb_log_buffer); + dlb_log_buffer = NULL; + } + dlb_db = NULL; + dlb_buffer_offset = 0; + dlb_buffer_size = 0; + dlb_buffer_len = 0; + dlb_flush_required = FALSE; +#ifdef DEBUG + dlb_max_write_offset = 0; +#endif +} + +xtBool XTDataLogBuffer::dlb_close_log(XTThreadPtr thread) +{ + if (dlb_data_log) { + /* Flush and commit the data in the old log: */ + if (!dlb_flush_log(TRUE, thread)) + return FAILED; + + if (!dlb_db->db_datalogs.dlc_unlock_log(dlb_data_log)) + return FAILED; + dlb_data_log = NULL; + } + return OK; +} + +/* When I use 'thread' instead of 'self', this means + * that I will not throw an error. + */ +xtBool XTDataLogBuffer::dlb_get_log_offset(xtLogID *log_id, xtLogOffset *out_offset, size_t req_size, struct XTThread *thread) +{ + /* Note, I am allowing a log to grow beyond the threshold. + * The amount depends on the maximum extended record size. + * If I don't some logs will never fill up, because of only having + * a few more bytes available. + */ + if (!dlb_data_log || dlb_data_log->dlf_space_avaliable() == 0) { + /* Release the old log: */ + if (!dlb_close_log(thread)) + return FAILED; + + if (!dlb_log_buffer) { + if (!(dlb_log_buffer = (xtWord1 *) xt_malloc_ns(dlb_buffer_size))) + return FAILED; + } + + /* I could use req_size instead of 1, but this would mean some logs + * are never filled up. + */ + if (!(dlb_data_log = dlb_db->db_datalogs.dlc_get_log_for_writing(1, thread))) + return FAILED; +#ifdef DEBUG + dlb_max_write_offset = dlb_data_log->dlf_log_eof; +#endif + } + + *log_id = dlb_data_log->dlf_log_id; + *out_offset = dlb_data_log->dlf_log_eof; + dlb_data_log->dlf_log_eof += req_size; + return OK; +} + +xtBool XTDataLogBuffer::dlb_flush_log(xtBool commit, XTThreadPtr thread) +{ + if (!dlb_data_log || !dlb_data_log->dlf_log_file) + return OK; + + if (dlb_buffer_len) { + if (!xt_pwrite_file(dlb_data_log->dlf_log_file, dlb_buffer_offset, dlb_buffer_len, dlb_log_buffer, &thread->st_statistics.st_data, thread)) + return FAILED; +#ifdef DEBUG + if (dlb_buffer_offset + (xtLogOffset) dlb_buffer_len > dlb_max_write_offset) + dlb_max_write_offset = dlb_buffer_offset + (xtLogOffset) dlb_buffer_len; +#endif + dlb_buffer_len = 0; + dlb_flush_required = TRUE; + } + + if (commit && dlb_flush_required) { +#ifdef DEBUG + /* This would normally be equal, however, in the case + * where some other thread flushes the compactors + * data log, the eof, can be greater than the + * write offset. + * + * This occurs because the flush can come between the + * dlb_get_log_offset() and dlb_write_thru_log() calls. + */ + ASSERT_NS(dlb_data_log->dlf_log_eof >= dlb_max_write_offset); +#endif + if (!xt_flush_file(dlb_data_log->dlf_log_file, &thread->st_statistics.st_data, thread)) + return FAILED; + dlb_flush_required = FALSE; + } + return OK; +} + +xtBool XTDataLogBuffer::dlb_write_thru_log(xtLogID log_id __attribute__((unused)), xtLogOffset log_offset, size_t size, xtWord1 *data, XTThreadPtr thread) +{ + ASSERT_NS(log_id == dlb_data_log->dlf_log_id); + + if (dlb_buffer_len) + dlb_flush_log(FALSE, thread); + + if (!xt_pwrite_file(dlb_data_log->dlf_log_file, log_offset, size, data, &thread->st_statistics.st_data, thread)) + return FAILED; +#ifdef DEBUG + if (log_offset + size > dlb_max_write_offset) + dlb_max_write_offset = log_offset + size; +#endif + dlb_flush_required = TRUE; + return OK; +} + +xtBool XTDataLogBuffer::dlb_append_log(xtLogID log_id __attribute__((unused)), xtLogOffset log_offset, size_t size, xtWord1 *data, XTThreadPtr thread) +{ + ASSERT_NS(log_id == dlb_data_log->dlf_log_id); + + if (dlb_buffer_len) { + /* Should be the case, we only write by appending: */ + ASSERT_NS(dlb_buffer_offset + (xtLogOffset) dlb_buffer_len == log_offset); + /* Check if we are appending to the existing value in the buffer: */ + if (dlb_buffer_offset + (xtLogOffset) dlb_buffer_len == log_offset) { + /* Can we just append: */ + if (dlb_buffer_size >= dlb_buffer_len + size) { + memcpy(dlb_log_buffer + dlb_buffer_len, data, size); + dlb_buffer_len += size; + return OK; + } + } + dlb_flush_log(FALSE, thread); + } + + ASSERT_NS(dlb_buffer_len == 0); + + if (dlb_buffer_size >= size) { + dlb_buffer_offset = log_offset; + dlb_buffer_len = size; + memcpy(dlb_log_buffer, data, size); + return OK; + } + + /* Write directly: */ + if (!xt_pwrite_file(dlb_data_log->dlf_log_file, log_offset, size, data, &thread->st_statistics.st_data, thread)) + return FAILED; +#ifdef DEBUG + if (log_offset + size > dlb_max_write_offset) + dlb_max_write_offset = log_offset + size; +#endif + dlb_flush_required = TRUE; + return OK; +} + +xtBool XTDataLogBuffer::dlb_read_log(xtLogID log_id, xtLogOffset log_offset, size_t size, xtWord1 *data, XTThreadPtr thread) +{ + size_t red_size; + XTOpenLogFilePtr open_log; + + if (dlb_data_log && log_id == dlb_data_log->dlf_log_id) { + /* Reading from the write log, I can do this quicker: */ + if (dlb_buffer_len) { + /* If it is in the buffer, then it is completely in the buffer. */ + if (log_offset >= dlb_buffer_offset) { + if (log_offset + (xtLogOffset) size <= dlb_buffer_offset + (xtLogOffset) dlb_buffer_len) { + memcpy(data, dlb_log_buffer + (log_offset - dlb_buffer_offset), size); + return OK; + } + /* Should not happen, reading past EOF: */ + ASSERT_NS(FALSE); + memset(data, 0, size); + return OK; + } + /* In the write log, but not in the buffer, + * must be completely not in the log, + * because only whole records are written to the + * log: + */ + ASSERT_NS(log_offset + (xtLogOffset) size <= dlb_buffer_offset); + } + return xt_pread_file(dlb_data_log->dlf_log_file, log_offset, size, size, data, NULL, &thread->st_statistics.st_data, thread); + } + + /* Read from some other log: */ + if (!dlb_db->db_datalogs.dlc_get_open_log(&open_log, log_id)) + return FAILED; + + if (!xt_pread_file(open_log->odl_log_file, log_offset, size, 0, data, &red_size, &thread->st_statistics.st_data, thread)) { + dlb_db->db_datalogs.dlc_release_open_log(open_log); + return FAILED; + } + + dlb_db->db_datalogs.dlc_release_open_log(open_log); + + if (red_size < size) + memset(data + red_size, 0, size - red_size); + + return OK; +} + +/* + * We assume that the given reference may not be valid. + * Only valid references actually cause a delete. + * Invalid references are logged, and ignored. + * + * Note this routine does not lock the compactor. + * This can lead to the some incorrect calculation is the + * amount of garbage. But nothing serious I think. + */ +xtBool XTDataLogBuffer::dlb_delete_log(xtLogID log_id, xtLogOffset log_offset, size_t size, xtTableID tab_id, xtRecordID rec_id, XTThreadPtr thread) +{ + XTactExtRecEntryDRec record; + xtWord1 status = XT_LOG_ENT_EXT_REC_DEL; + XTOpenLogFilePtr open_log; + xtBool to_much_garbage; + XTDataLogFilePtr data_log; + + if (!dlb_read_log(log_id, log_offset, offsetof(XTactExtRecEntryDRec, er_data), (xtWord1 *) &record, thread)) + return FAILED; + + /* Already deleted: */ + if (record.er_status_1 == XT_LOG_ENT_EXT_REC_DEL) + return OK; + + if (record.er_status_1 != XT_LOG_ENT_EXT_REC_OK || + size != XT_GET_DISK_4(record.er_data_size_4) || + tab_id != XT_GET_DISK_4(record.er_tab_id_4) || + rec_id != XT_GET_DISK_4(record.er_rec_id_4)) { + xt_register_xterr(XT_REG_CONTEXT, XT_ERR_BAD_EXT_RECORD); + return FAILED; + } + + if (dlb_data_log && log_id == dlb_data_log->dlf_log_id) { + /* Writing to the write log, I can do this quicker: */ + if (dlb_buffer_len) { + /* If it is in the buffer, then it is completely in the buffer. */ + if (log_offset >= dlb_buffer_offset) { + if (log_offset + 1 <= dlb_buffer_offset + (xtLogOffset) dlb_buffer_len) { + *(dlb_log_buffer + (log_offset - dlb_buffer_offset)) = XT_LOG_ENT_EXT_REC_DEL; + goto inc_garbage_count; + } + /* Should not happen, writing past EOF: */ + ASSERT_NS(FALSE); + return OK; + } + ASSERT_NS(log_offset + (xtLogOffset) size <= dlb_buffer_offset); + } + + if (!xt_pwrite_file(dlb_data_log->dlf_log_file, log_offset, 1, &status, &thread->st_statistics.st_data, thread)) + return FAILED; + + inc_garbage_count: + xt_lock_mutex_ns(&dlb_db->db_datalogs.dlc_head_lock); + dlb_data_log->dlf_garbage_count += offsetof(XTactExtRecEntryDRec, er_data) + size; + ASSERT_NS(dlb_data_log->dlf_garbage_count < dlb_data_log->dlf_log_eof); + if (!dl_write_log_header(dlb_data_log, dlb_data_log->dlf_log_file, FALSE, thread)) { + xt_unlock_mutex_ns(&dlb_db->db_datalogs.dlc_head_lock); + return FAILED; + } + dlb_flush_required = TRUE; + xt_unlock_mutex_ns(&dlb_db->db_datalogs.dlc_head_lock); + return OK; + } + + /* Write to some other log, open the log: */ + if (!dlb_db->db_datalogs.dlc_get_open_log(&open_log, log_id)) + return FAILED; + + /* Write the status byte: */ + if (!xt_pwrite_file(open_log->odl_log_file, log_offset, 1, &status, &thread->st_statistics.st_data, thread)) + goto failed; + + data_log = open_log->odl_data_log; + + /* Adjust the garbage level in the header. */ + xt_lock_mutex_ns(&dlb_db->db_datalogs.dlc_head_lock); + data_log->dlf_garbage_count += offsetof(XTactExtRecEntryDRec, er_data) + size; + ASSERT_NS(data_log->dlf_garbage_count < data_log->dlf_log_eof); + if (!dl_write_log_header(data_log, open_log->odl_log_file, FALSE, thread)) { + xt_unlock_mutex_ns(&dlb_db->db_datalogs.dlc_head_lock); + goto failed; + } + to_much_garbage = data_log->dlf_to_much_garbage(); + xt_unlock_mutex_ns(&dlb_db->db_datalogs.dlc_head_lock); + + if (to_much_garbage && + (data_log->dlf_state == XT_DL_HAS_SPACE || data_log->dlf_state == XT_DL_READ_ONLY)) { + /* There is too much garbage, it may be compacted. */ + if (!dlb_db->db_datalogs.dls_set_log_state(data_log, XT_DL_MAY_COMPACT)) + goto failed; + } + + /* Release the open log: */ + dlb_db->db_datalogs.dlc_release_open_log(open_log); + + return OK; + + failed: + dlb_db->db_datalogs.dlc_release_open_log(open_log); + return FAILED; +} + +/* + * Delete all the extended data belonging to a particular + * table. + */ +xtPublic void xt_dl_delete_ext_data(XTThreadPtr self, XTTableHPtr tab, xtBool missing_ok __attribute__((unused)), xtBool have_table_lock) +{ + XTOpenTablePtr ot; + xtRecordID page_rec_id, offs_rec_id; + XTTabRecExtDPtr rec_buf; + xtWord4 log_over_size; + xtLogID log_id; + xtLogOffset log_offset; + xtWord1 *page_data; + + page_data = (xtWord1 *) xt_malloc(self, tab->tab_recs.tci_page_size); + pushr_(xt_free, page_data); + + /* Scan the table, and remove all exended data... */ + if (!(ot = xt_open_table(tab))) { + if (self->t_exception.e_xt_err == XT_SYSTEM_ERROR && + XT_FILE_NOT_FOUND(self->t_exception.e_sys_err)) + return; + xt_throw(self); + } + ot->ot_thread = self; + + /* {LOCK-EXT-REC} This lock is to stop the compactor changing records + * while we are doing the delete. + */ + xt_lock_mutex_ns(&tab->tab_db->db_co_ext_lock); + + page_rec_id = 1; + while (page_rec_id < tab->tab_rec_eof_id) { + /* NOTE: There is a good reason for using xt_tc_read_page(). + * A deadlock can occur if using read, which can run out of + * memory, which waits for the freeer, which may need to + * open a table, which requires the db->db_tables lock, + * which is owned by the this thread, when the function + * is called from drop table. + * + * xt_tc_read_page() should work because no more changes + * should happen to the table while we are dropping it. + */ + if (!tab->tab_recs.xt_tc_read_page(ot->ot_rec_file, page_rec_id, page_data, self)) + goto failed; + + for (offs_rec_id=0; offs_rec_id<tab->tab_recs.tci_rows_per_page && page_rec_id+offs_rec_id < tab->tab_rec_eof_id; offs_rec_id++) { + rec_buf = (XTTabRecExtDPtr) (page_data + (offs_rec_id * tab->tab_recs.tci_rec_size)); + if (XT_REC_IS_EXT_DLOG(rec_buf->tr_rec_type_1)) { + log_over_size = XT_GET_DISK_4(rec_buf->re_log_dat_siz_4); + XT_GET_LOG_REF(log_id, log_offset, rec_buf); + + if (!self->st_dlog_buf.dlb_delete_log(log_id, log_offset, log_over_size, tab->tab_id, page_rec_id+offs_rec_id, self)) { + if (self->t_exception.e_xt_err != XT_ERR_BAD_EXT_RECORD && + self->t_exception.e_xt_err != XT_ERR_DATA_LOG_NOT_FOUND) + xt_log_and_clear_exception(self); + } + } + } + + page_rec_id += tab->tab_recs.tci_rows_per_page; + } + + xt_unlock_mutex_ns(&tab->tab_db->db_co_ext_lock); + + xt_close_table(ot, TRUE, have_table_lock); + + freer_(); // xt_free(page_data) + return; + + failed: + xt_unlock_mutex_ns(&tab->tab_db->db_co_ext_lock); + + xt_close_table(ot, TRUE, have_table_lock); + xt_throw(self); +} + +/* + * -------------------------------------------------------------------------------- + * GARBAGE COLLECTOR THREAD + */ + +xtPublic void xt_dl_init_db(XTThreadPtr self, XTDatabaseHPtr db) +{ + xt_init_mutex_with_autoname(self, &db->db_co_ext_lock); + xt_init_mutex_with_autoname(self, &db->db_co_dlog_lock); +} + +xtPublic void xt_dl_exit_db(XTThreadPtr self, XTDatabaseHPtr db) +{ + xt_stop_compactor(self, db); // Already done! + db->db_co_thread = NULL; + xt_free_mutex(&db->db_co_ext_lock); + xt_free_mutex(&db->db_co_dlog_lock); +} + +xtPublic void xt_dl_set_to_delete(XTThreadPtr self, XTDatabaseHPtr db, xtLogID log_id) +{ + XTDataLogFilePtr data_log; + + if (!db->db_datalogs.dlc_get_data_log(&data_log, log_id, FALSE, NULL)) + xt_throw(self); + if (data_log) { + if (!db->db_datalogs.dls_set_log_state(data_log, XT_DL_TO_DELETE)) + xt_throw(self); + } +} + +xtPublic void xt_dl_log_status(XTThreadPtr self, XTDatabaseHPtr db, XTStringBufferPtr strbuf) +{ + XTSortedListPtr list; + XTDataLogFilePtr data_log; + XTDataLogSegPtr seg; + u_int no_of_logs; + xtLogID *log_id_ptr; + + list = xt_new_sortedlist(self, sizeof(xtLogID), 20, 10, dl_cmp_log_id, NULL, NULL, FALSE, FALSE); + pushr_(xt_free_sortedlist, list); + + for (u_int i=0; i<XT_DL_NO_OF_SEGMENTS; i++) { + for (u_int j=0; j<XT_DL_SEG_HASH_TABLE_SIZE; j++) { + seg = &db->db_datalogs.dlc_segment[i]; + data_log = seg->dls_hash_table[j]; + while (data_log) { + xt_sl_insert(self, list, &data_log->dlf_log_id, &data_log->dlf_log_id); + data_log = data_log->dlf_next_hash; + } + } + } + + no_of_logs = xt_sl_get_size(list); + for (u_int i=0; i<no_of_logs; i++) { + log_id_ptr = (xtLogID *) xt_sl_item_at(list, i); + if (!db->db_datalogs.dlc_get_data_log(&data_log, *log_id_ptr, FALSE, &seg)) + xt_throw(self); + if (data_log) { + xt_sb_concat(self, strbuf, "d-log: "); + xt_sb_concat_int8(self, strbuf, data_log->dlf_log_id); + xt_sb_concat(self, strbuf, " status="); + switch (data_log->dlf_state) { + case XT_DL_UNKNOWN: + xt_sb_concat(self, strbuf, "?"); + break; + case XT_DL_HAS_SPACE: + xt_sb_concat(self, strbuf, "has-space "); + break; + case XT_DL_READ_ONLY: + xt_sb_concat(self, strbuf, "read-only "); + break; + case XT_DL_TO_COMPACT: + xt_sb_concat(self, strbuf, "to-compact"); + break; + case XT_DL_COMPACTED: + xt_sb_concat(self, strbuf, "compacted "); + break; + case XT_DL_TO_DELETE: + xt_sb_concat(self, strbuf, "to-delete "); + break; + case XT_DL_DELETED: + xt_sb_concat(self, strbuf, "deleted "); + break; + case XT_DL_EXCLUSIVE: + xt_sb_concat(self, strbuf, "x-locked "); + break; + } + xt_sb_concat(self, strbuf, " eof="); + xt_sb_concat_int8(self, strbuf, data_log->dlf_log_eof); + xt_sb_concat(self, strbuf, " garbage="); + xt_sb_concat_int8(self, strbuf, data_log->dlf_garbage_count); + xt_sb_concat(self, strbuf, " g%="); + if (data_log->dlf_log_eof) + xt_sb_concat_int8(self, strbuf, data_log->dlf_garbage_count * 100 / data_log->dlf_log_eof); + else + xt_sb_concat(self, strbuf, "100"); + xt_sb_concat(self, strbuf, " open="); + xt_sb_concat_int8(self, strbuf, data_log->dlf_open_count); + xt_sb_concat(self, strbuf, "\n"); + } + xt_unlock_mutex_ns(&seg->dls_lock); + } + + freer_(); // xt_free_sortedlist(list) +} + +xtPublic void xt_dl_delete_logs(XTThreadPtr self, XTDatabaseHPtr db) +{ + char path[PATH_MAX]; + XTOpenDirPtr od; + char *file; + xtLogID log_id; + + xt_strcpy(PATH_MAX, path, db->db_main_path); + xt_add_data_dir(PATH_MAX, path); + if (!xt_fs_exists(path)) + return; + pushsr_(od, xt_dir_close, xt_dir_open(self, path, NULL)); + while (xt_dir_next(self, od)) { + file = xt_dir_name(self, od); + if ((log_id = (xtLogID) xt_file_name_to_id(file))) { + if (!db->db_datalogs.dlc_remove_data_log(log_id, TRUE)) + xt_log_and_clear_exception(self); + } + if (xt_ends_with(file, ".xt")) { + xt_add_dir_char(PATH_MAX, path); + xt_strcat(PATH_MAX, path, file); + xt_fs_delete(self, path); + xt_remove_last_name_of_path(path); + } + } + freer_(); // xt_dir_close(od) + + /* I no longer attach the condition: !db->db_multi_path + * to removing this directory. This is because + * the pbxt directory must now be removed explicitly + * by drop database, or by delete all the PBXT + * system tables. + */ + if (!xt_fs_rmdir(NULL, path)) + xt_log_and_clear_exception(self); +} + +typedef struct XTCompactorState { + XTSeqLogReadPtr cs_seqread; + XTOpenTablePtr cs_ot; + XTDataBufferRec cs_databuf; +} XTCompactorStateRec, *XTCompactorStatePtr; + +static void dl_free_compactor_state(XTThreadPtr self, XTCompactorStatePtr cs) +{ + if (cs->cs_seqread) { + cs->cs_seqread->sl_seq_exit(); + delete cs->cs_seqread; + cs->cs_seqread = NULL; + } + if (cs->cs_ot) { + xt_db_return_table_to_pool(self, cs->cs_ot); + cs->cs_ot = NULL; + } + xt_db_set_size(self, &cs->cs_databuf, 0); +} + +static XTOpenTablePtr dl_cs_get_open_table(XTThreadPtr self, XTCompactorStatePtr cs, xtTableID tab_id) +{ + if (cs->cs_ot) { + if (cs->cs_ot->ot_table->tab_id == tab_id) + return cs->cs_ot; + + xt_db_return_table_to_pool(self, cs->cs_ot); + cs->cs_ot = NULL; + } + + if (!cs->cs_ot) { + if (!(cs->cs_ot = xt_db_open_pool_table(self, self->st_database, tab_id, NULL, TRUE))) + return NULL; + } + + return cs->cs_ot; +} + +static void dl_co_wait(XTThreadPtr self, XTDatabaseHPtr db, u_int secs) +{ + xt_lock_mutex(self, &db->db_datalogs.dlc_lock); + pushr_(xt_unlock_mutex, &db->db_datalogs.dlc_lock); + if (!self->t_quit) + xt_timed_wait_cond(self, &db->db_datalogs.dlc_cond, &db->db_datalogs.dlc_lock, secs * 1000); + freer_(); // xt_unlock_mutex(&db->db_datalogs.dlc_lock) +} + +/* + * Collect all the garbage in a file by moving all valid records + * into some other data log and updating the handles. + */ +static xtBool dl_collect_garbage(XTThreadPtr self, XTDatabaseHPtr db, XTDataLogFilePtr data_log) +{ + XTXactLogBufferDPtr record; + size_t size; + xtTableID tab_id; + xtRecordID rec_id; + XTCompactorStateRec cs; + XTOpenTablePtr ot; + XTTableHPtr tab; + XTTabRecExtDRec rec_buffer; + size_t src_size; + xtLogID src_log_id; + xtLogOffset src_log_offset; + xtLogID curr_log_id; + xtLogOffset curr_log_offset; + xtLogID dest_log_id; + xtLogOffset dest_log_offset; + off_t garbage_count = 0; + + memset(&cs, 0, sizeof(XTCompactorStateRec)); + + if (!(cs.cs_seqread = new XTDataSeqRead())) + xt_throw_errno(XT_CONTEXT, XT_ENOMEM); + + if (!cs.cs_seqread->sl_seq_init(db, xt_db_log_buffer_size)) { + delete cs.cs_seqread; + xt_throw(self); + } + pushr_(dl_free_compactor_state, &cs); + + if (!cs.cs_seqread->sl_seq_start(data_log->dlf_log_id, data_log->dlf_start_offset, FALSE)) + xt_throw(self); + + for (;;) { + if (self->t_quit) { + /* Flush the destination log: */ + xt_lock_mutex(self, &db->db_co_dlog_lock); + pushr_(xt_unlock_mutex, &db->db_co_dlog_lock); + if (!self->st_dlog_buf.dlb_flush_log(TRUE, self)) + xt_throw(self); + freer_(); // xt_unlock_mutex(&db->db_co_dlog_lock) + + /* Flush the transaction log. */ + if (!xt_xlog_flush_log(self)) + xt_throw(self); + + xt_lock_mutex_ns(&db->db_datalogs.dlc_head_lock); + data_log->dlf_garbage_count += garbage_count; + ASSERT(data_log->dlf_garbage_count < data_log->dlf_log_eof); + if (!dl_write_log_header(data_log, cs.cs_seqread->sl_seq_open_file(), TRUE, self)) { + xt_unlock_mutex_ns(&db->db_datalogs.dlc_head_lock); + xt_throw(self); + } + xt_unlock_mutex_ns(&db->db_datalogs.dlc_head_lock); + + freer_(); // dl_free_compactor_state(&cs) + return FAILED; + } + if (!cs.cs_seqread->sl_seq_next(&record, TRUE, self)) + xt_throw(self); + cs.cs_seqread->sl_seq_pos(&curr_log_id, &curr_log_offset); + if (!record) { + data_log->dlf_start_offset = curr_log_offset; + break; + } + switch (record->xh.xh_status_1) { + case XT_LOG_ENT_EXT_REC_OK: + size = XT_GET_DISK_4(record->er.er_data_size_4); + tab_id = XT_GET_DISK_4(record->er.er_tab_id_4); + rec_id = XT_GET_DISK_4(record->er.er_rec_id_4); + + if (!(ot = dl_cs_get_open_table(self, &cs, tab_id))) + break; + tab = ot->ot_table; + + /* All this is required for a valid record address: */ + if (!rec_id || rec_id >= tab->tab_rec_eof_id) + break; + + /* {LOCK-EXT-REC} It is important to prevent the compactor from modifying + * a record that has been freed (and maybe allocated again). + * + * Consider the following sequence: + * + * 1. Compactor reads the record. + * 2. The record is freed and reallocated. + * 3. The compactor updates the record. + * + * To prevent this, the compactor locks out the + * sweeper using the db_co_ext_lock lock. The db_co_ext_lock lock + * prevents a extended record from being moved and removed at the + * same time. + * + * The compactor also checks the status of the record before + * moving a record. + */ + xt_lock_mutex(self, &db->db_co_ext_lock); + pushr_(xt_unlock_mutex, &db->db_co_ext_lock); + + /* Read the record: */ + if (!xt_tab_get_rec_data(ot, rec_id, offsetof(XTTabRecExtDRec, re_data), (xtWord1 *) &rec_buffer)) { + xt_log_and_clear_warning(self); + freer_(); // xt_unlock_mutex(&db->db_co_ext_lockk) + break; + } + + /* [(7)] REMOVE is followed by FREE: + if (XT_REC_IS_REMOVED(rec_buffer.tr_rec_type_1) || !XT_REC_IS_EXT_DLOG(rec_buffer.tr_rec_type_1)) { + */ + if (!XT_REC_IS_EXT_DLOG(rec_buffer.tr_rec_type_1)) { + freer_(); // xt_unlock_mutex(&db->db_co_ext_lock) + break; + } + + XT_GET_LOG_REF(src_log_id, src_log_offset, &rec_buffer); + src_size = (size_t) XT_GET_DISK_4(rec_buffer.re_log_dat_siz_4); + + /* Does the record agree with the current position: */ + if (curr_log_id != src_log_id || + curr_log_offset != src_log_offset || + size != src_size) { + freer_(); // xt_unlock_mutex(&db->db_co_ext_lock) + break; + } + + size = offsetof(XTactExtRecEntryDRec, er_data) + size; + + /* Allocate space in a destination log: */ + xt_lock_mutex(self, &db->db_co_dlog_lock); + pushr_(xt_unlock_mutex, &db->db_co_dlog_lock); + if (!self->st_dlog_buf.dlb_get_log_offset(&dest_log_id, &dest_log_offset, size, self)) + xt_throw(self); + freer_(); // xt_unlock_mutex(&db->db_co_dlog_lock) + + /* This record is referenced by the data: */ + xt_db_set_size(self, &cs.cs_databuf, size); + if (!cs.cs_seqread->sl_rnd_read(src_log_offset, size, cs.cs_databuf.db_data, NULL, self)) + xt_throw(self); + + /* The problem with writing to the buffer here, is that other + * threads want to read the data! */ + xt_lock_mutex(self, &db->db_co_dlog_lock); + pushr_(xt_unlock_mutex, &db->db_co_dlog_lock); + if (!self->st_dlog_buf.dlb_write_thru_log(dest_log_id, dest_log_offset, size, cs.cs_databuf.db_data, self)) + xt_throw(self); + freer_(); // xt_unlock_mutex(&db->db_co_dlog_lock) + + /* Make sure we flush the compactor target log, before we + * flush the transaction log!! + * This is done here [(8)] + */ + + XT_SET_LOG_REF(&rec_buffer, dest_log_id, dest_log_offset); + xtOpSeqNo op_seq; + if (!xt_tab_put_log_rec_data(ot, XT_LOG_ENT_REC_MOVED, 0, rec_id, 8, (xtWord1 *) &rec_buffer.re_log_id_2, &op_seq)) + xt_throw(self); + tab->tab_co_op_seq = op_seq; + + /* Only records that were actually moved, count as garbage now! + * This means, lost records, remain "lost" as far as the garbage + * count is concerned! + */ + garbage_count += size; + freer_(); // xt_unlock_mutex(&db->db_co_ext_lock) + break; + } + data_log->dlf_start_offset = curr_log_offset; + } + + /* Flush the distination log. */ + xt_lock_mutex(self, &db->db_co_dlog_lock); + pushr_(xt_unlock_mutex, &db->db_co_dlog_lock); + if (!self->st_dlog_buf.dlb_flush_log(TRUE, self)) + xt_throw(self); + freer_(); // xt_unlock_mutex(&db->db_co_dlog_lock) + + /* Flush the transaction log. */ + if (!xt_xlog_flush_log(self)) + xt_throw(self); + + /* Save state in source log header. */ + xt_lock_mutex_ns(&db->db_datalogs.dlc_head_lock); + data_log->dlf_garbage_count += garbage_count; + ASSERT(data_log->dlf_garbage_count < data_log->dlf_log_eof); + if (!dl_write_log_header(data_log, cs.cs_seqread->sl_seq_open_file(), TRUE, self)) { + xt_unlock_mutex_ns(&db->db_datalogs.dlc_head_lock); + xt_throw(self); + } + xt_unlock_mutex_ns(&db->db_datalogs.dlc_head_lock); + + /* Wait for the writer to write all the changes. + * Then we can start the delete process for the log: + * + * Note, if we do not wait, then it could be some operations are held up, + * by being out of sequence. This could cause the log to be deleted + * before all the operations have been performed (which are on a table + * basis). + * + */ + for (;;) { + u_int edx; + XTTableEntryPtr tab_ptr; + xtBool wait; + + if (self->t_quit) { + freer_(); // dl_free_compactor_state(&cs) + return FAILED; + } + wait = FALSE; + xt_ht_lock(self, db->db_tables); + pushr_(xt_ht_unlock, db->db_tables); + xt_enum_tables_init(&edx); + while ((tab_ptr = xt_enum_tables_next(self, db, &edx))) { + if (tab_ptr->te_table && tab_ptr->te_table->tab_co_op_seq > tab_ptr->te_table->tab_head_op_seq) { + wait = TRUE; + break; + } + } + freer_(); // xt_ht_unlock(db->db_tables) + + if (!wait) + break; + + /* Nobody will wake me, so check again shortly! */ + dl_co_wait(self, db, 1); + } + + db->db_datalogs.dls_set_log_state(data_log, XT_DL_COMPACTED); + +#ifdef DEBUG_LOG_DELETE + printf("-- MARK FOR DELETE IN LOG: %d\n", (int) data_log->dlf_log_id); +#endif + /* Log that this log should be deleted on the next checkpoint: */ + // transaction log... + XTXactNewLogEntryDRec log_rec; + log_rec.xl_status_1 = XT_LOG_ENT_DEL_LOG; + log_rec.xl_checksum_1 = XT_CHECKSUM_1(data_log->dlf_log_id); + XT_SET_DISK_4(log_rec.xl_log_id_4, data_log->dlf_log_id); + if (!xt_xlog_log_data(self, sizeof(XTXactNewLogEntryDRec), (XTXactLogBufferDPtr) &log_rec, TRUE)) { + db->db_datalogs.dls_set_log_state(data_log, XT_DL_TO_COMPACT); + xt_throw(self); + } + + freer_(); // dl_free_compactor_state(&cs) + return OK; +} + +static void dl_co_not_busy(XTThreadPtr XT_UNUSED(self), XTDatabaseHPtr db) +{ + db->db_co_busy = FALSE; +} + +static void dl_co_main(XTThreadPtr self, xtBool once_off) +{ + XTDatabaseHPtr db = self->st_database; + xtLogID *log_id_ptr, log_id; + XTDataLogFilePtr data_log = NULL; + + xt_set_low_priority(self); + + while (!self->t_quit) { + while (!self->t_quit) { + xt_lock_mutex_ns(&db->db_datalogs.dlc_lock); + if ((log_id_ptr = (xtLogID *) xt_sl_first_item(db->db_datalogs.dlc_to_compact))) { + log_id = *log_id_ptr; + } + else + log_id = 0; + xt_unlock_mutex_ns(&db->db_datalogs.dlc_lock); + if (!log_id) + break; + if (!db->db_datalogs.dlc_get_data_log(&data_log, log_id, FALSE, NULL)) + xt_throw(self); + ASSERT(data_log); + if (data_log) { + db->db_co_busy = TRUE; + pushr_(dl_co_not_busy, db); + dl_collect_garbage(self, db, data_log); + freer_(); // dl_co_not_busy(db) + } + else { + xt_lock_mutex_ns(&db->db_datalogs.dlc_lock); + xt_sl_delete(self, db->db_datalogs.dlc_to_compact, &log_id); + xt_unlock_mutex_ns(&db->db_datalogs.dlc_lock); + } + } + + if (once_off) + break; + + /* Wait for a signal that a data log can be collected: */ + dl_co_wait(self, db, 120); + } +} + +static void *dl_run_co_thread(XTThreadPtr self) +{ + XTDatabaseHPtr db = (XTDatabaseHPtr) self->t_data; + int count; + void *mysql_thread; + + mysql_thread = myxt_create_thread(); + + while (!self->t_quit) { + try_(a) { + /* + * The garbage collector requires that the database + * is in use because. + */ + xt_use_database(self, db, XT_FOR_COMPACTOR); + + /* This action is both safe and required: + * + * safe: releasing the database is safe because as + * long as this thread is running the database + * reference is valid, and this reference cannot + * be the only one to the database because + * otherwize this thread would not be running. + * + * required: releasing the database is necessary + * otherwise we cannot close the database + * correctly because we only shutdown this + * thread when the database is closed and we + * only close the database when all references + * are removed. + */ + xt_heap_release(self, self->st_database); + + dl_co_main(self, FALSE); + } + catch_(a) { + if (!(self->t_exception.e_xt_err == XT_SIGNAL_CAUGHT && + self->t_exception.e_sys_err == SIGTERM)) + xt_log_and_clear_exception(self); + } + cont_(a); + + /* Avoid releasing the database (done above) */ + self->st_database = NULL; + xt_unuse_database(self, self); + + /* After an exception, pause before trying again... */ + /* Number of seconds */ +#ifdef DEBUG + count = 10; +#else + count = 2*60; +#endif + while (!self->t_quit && count > 0) { + sleep(1); + count--; + } + } + + myxt_destroy_thread(mysql_thread, TRUE); + return NULL; +} + +static void dl_free_co_thread(XTThreadPtr self, void *data) +{ + XTDatabaseHPtr db = (XTDatabaseHPtr) data; + + if (db->db_co_thread) { + xt_lock_mutex(self, &db->db_datalogs.dlc_lock); + pushr_(xt_unlock_mutex, &db->db_datalogs.dlc_lock); + db->db_co_thread = NULL; + freer_(); // xt_unlock_mutex(&db->db_datalogs.dlc_lock) + } +} + +xtPublic void xt_start_compactor(XTThreadPtr self, XTDatabaseHPtr db) +{ + char name[PATH_MAX]; + + sprintf(name, "GC-%s", xt_last_directory_of_path(db->db_main_path)); + xt_remove_dir_char(name); + db->db_co_thread = xt_create_daemon(self, name); + xt_set_thread_data(db->db_co_thread, db, dl_free_co_thread); + xt_run_thread(self, db->db_co_thread, dl_run_co_thread); +} + +static void dl_wake_co_thread(XTDatabaseHPtr db) +{ + if (!xt_signal_cond(NULL, &db->db_datalogs.dlc_cond)) + xt_log_and_clear_exception_ns(); +} + +xtPublic void xt_stop_compactor(XTThreadPtr self, XTDatabaseHPtr db) +{ + XTThreadPtr thr_co; + + if (db->db_co_thread) { + xt_lock_mutex(self, &db->db_datalogs.dlc_lock); + pushr_(xt_unlock_mutex, &db->db_datalogs.dlc_lock); + + /* This pointer is safe as long as you have the transaction lock. */ + if ((thr_co = db->db_co_thread)) { + xtThreadID tid = thr_co->t_id; + + /* Make sure the thread quits when woken up. */ + xt_terminate_thread(self, thr_co); + + dl_wake_co_thread(db); + + freer_(); // xt_unlock_mutex(&db->db_datalogs.dlc_lock) + + /* + * This seems to kill the whole server sometimes!! + * SIGTERM is going to a different thread??! + xt_kill_thread(thread); + */ + xt_wait_for_thread(tid, FALSE); + + /* PMC - This should not be necessary to set the signal here, but in the + * debugger the handler is not called!!? + thr_co->t_delayed_signal = SIGTERM; + xt_kill_thread(thread); + */ + db->db_co_thread = NULL; + } + else + freer_(); // xt_unlock_mutex(&db->db_datalogs.dlc_lock) + } +} + diff --git a/storage/pbxt/src/datalog_xt.h b/storage/pbxt/src/datalog_xt.h new file mode 100644 index 00000000000..245ebcbaeda --- /dev/null +++ b/storage/pbxt/src/datalog_xt.h @@ -0,0 +1,228 @@ +/* Copyright (c) 2005 PrimeBase Technologies GmbH + * + * PrimeBase XT + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You 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 + * + * 2005-01-24 Paul McCullagh + * + * H&G2JCtL + */ +#ifndef __xt_datalog_h__ +#define __xt_datalog_h__ + +#include "pthread_xt.h" +#include "filesys_xt.h" +#include "sortedlist_xt.h" +#include "xactlog_xt.h" +#include "util_xt.h" + +struct XTThread; +struct XTDatabase; +struct xXTDataLog; +struct XTTable; +struct XTOpenTable; + +#define XT_SET_LOG_REF(d, l, o) do { XT_SET_DISK_2((d)->re_log_id_2, l); \ + XT_SET_DISK_6((d)->re_log_offs_6, o); \ + } while (0) +#define XT_GET_LOG_REF(l, o, s) do { l = XT_GET_DISK_2((s)->re_log_id_2); \ + o = XT_GET_DISK_6((s)->re_log_offs_6); \ + } while (0) + +#ifdef DEBUG +//#define USE_DEBUG_SIZES +#endif + +#ifdef USE_DEBUG_SIZES +#define XT_DL_MAX_LOG_ID 500 +#define XT_DL_LOG_POOL_SIZE 10 +#define XT_DL_HASH_TABLE_SIZE 5 +#define XT_DL_SEGMENT_SHIFTS 1 +#else +#define XT_DL_MAX_LOG_ID 0x7FFF +#define XT_DL_LOG_POOL_SIZE 1000 +#define XT_DL_HASH_TABLE_SIZE 10000 +#define XT_DL_SEGMENT_SHIFTS 3 +#endif + +#define XT_DL_SEG_HASH_TABLE_SIZE (XT_DL_HASH_TABLE_SIZE / XT_DL_NO_OF_SEGMENTS) +#define XT_DL_NO_OF_SEGMENTS (1 << XT_DL_SEGMENT_SHIFTS) +#define XT_DL_SEGMENT_MASK (XT_DL_NO_OF_SEGMENTS - 1) + +typedef struct XTOpenLogFile { + xtLogID olf_log_id; + XTOpenFilePtr odl_log_file; /* The open file handle. */ + struct XTDataLogFile *odl_data_log; + + xtBool odl_in_use; + struct XTOpenLogFile *odl_next_free; /* Pointer to the next on the free list. */ + struct XTOpenLogFile *odl_prev_free; /* Pointer to the previous on the free list. */ + + xtWord4 odl_ru_time; /* If this is in the top 1/4 don't change position in MRU list. */ + struct XTOpenLogFile *odl_mr_used; /* More recently used pages. */ + struct XTOpenLogFile *odl_lr_used; /* Less recently used pages. */ +} XTOpenLogFileRec, *XTOpenLogFilePtr; + +#define XT_DL_MAY_COMPACT -1 /* This is an indication to set the state to XT_DL_TO_COMPACT. */ +#define XT_DL_UNKNOWN 0 +#define XT_DL_HAS_SPACE 1 /* The log is not yet full, and can be used for writing. */ +#define XT_DL_READ_ONLY 2 /* The log is full, and can only be read now. */ +#define XT_DL_TO_COMPACT 3 /* The log has too much garbage, and must be compacted. */ +#define XT_DL_COMPACTED 4 /* The state after compaction. */ +#define XT_DL_TO_DELETE 5 /* All references to this log have been removed, and it is to be deleted. */ +#define XT_DL_DELETED 6 /* After deletion, logs are locked until the next checkpoint. */ +#define XT_DL_EXCLUSIVE 7 /* The log is locked and being written by a thread. */ + +typedef struct XTDataLogFile { + xtLogID dlf_log_id; /* The ID of the data log. */ + int dlf_state; + struct XTDataLogFile *dlf_next_hash; /* Pointer to the next on the hash list. */ + u_int dlf_open_count; /* Number of open log files. */ + XTOpenLogFilePtr dlf_free_list; /* The open file free list. */ + off_t dlf_log_eof; + off_t dlf_start_offset; /* Start offset for garbage collection. */ + off_t dlf_garbage_count; /* The amount of garbage in the log file. */ + XTOpenFilePtr dlf_log_file; /* The open file handle (if the log is in exclusive use!!). */ + + off_t dlf_space_avaliable(); + xtBool dlf_to_much_garbage(); +} XTDataLogFileRec, *XTDataLogFilePtr; + +typedef struct XTDataLogSeg { + xt_mutex_type dls_lock; /* The cache segment lock. */ + xt_cond_type dls_cond; + XTDataLogFilePtr dls_hash_table[XT_DL_SEG_HASH_TABLE_SIZE]; +} XTDataLogSegRec, *XTDataLogSegPtr; + +typedef struct XTDataLogCache { + struct XTDatabase *dlc_db; + + xt_mutex_type dlc_lock; /* The public cache lock. */ + xt_cond_type dlc_cond; /* The public cache wait condition. */ + XTSortedListPtr dlc_has_space; /* List of logs with space for more data. */ + XTSortedListPtr dlc_to_compact; /* List of logs to be compacted. */ + XTSortedListPtr dlc_to_delete; /* List of logs to be deleted at next checkpoint. */ + XTSortedListPtr dlc_deleted; /* List of logs deleted at the previous checkpoint. */ + XTDataLogSegRec dlc_segment[XT_DL_NO_OF_SEGMENTS]; + xtLogID dlc_next_log_id; /* The next log ID to be used to create a new log. */ + + xt_mutex_type dlc_mru_lock; /* The lock for the LRU list. */ + xtWord4 dlc_ru_now; + XTOpenLogFilePtr dlc_lru_open_log; + XTOpenLogFilePtr dlc_mru_open_log; + u_int dlc_open_count; /* The total open file count. */ + + xt_mutex_type dlc_head_lock; /* The lock for changing the header of shared logs. */ + + void dls_remove_log(XTDataLogFilePtr data_log); + int dls_get_log_state(XTDataLogFilePtr data_log); + xtBool dls_set_log_state(XTDataLogFilePtr data_log, int state); + void dlc_init(struct XTThread *self, struct XTDatabase *db); + void dlc_exit(struct XTThread *self); + void dlc_name(size_t size, char *path, xtLogID log_id); + xtBool dlc_open_log(XTOpenFilePtr *fh, xtLogID log_id, int mode); + xtBool dlc_unlock_log(XTDataLogFilePtr data_log); + XTDataLogFilePtr dlc_get_log_for_writing(off_t space_required, struct XTThread *thread); + xtBool dlc_get_data_log(XTDataLogFilePtr *data_log, xtLogID log_id, xtBool create, XTDataLogSegPtr *ret_seg); + xtBool dlc_remove_data_log(xtLogID log_id, xtBool just_close); + xtBool dlc_get_open_log(XTOpenLogFilePtr *open_log, xtLogID log_id); + void dlc_release_open_log(XTOpenLogFilePtr open_log); +} XTDataLogCacheRec, *XTDataLogCachePtr; + +/* The data log buffer, used by a thread to write a + * data log file. + */ +typedef struct XTDataLogBuffer { + struct XTDatabase *dlb_db; + XTDataLogFilePtr dlb_data_log; /* The data log file. */ + + xtLogOffset dlb_buffer_offset; /* The offset into the log file. */ + size_t dlb_buffer_size; /* The size of the buffer. */ + size_t dlb_buffer_len; /* The amount of data in the buffer. */ + xtWord1 *dlb_log_buffer; + xtBool dlb_flush_required; +#ifdef DEBUG + off_t dlb_max_write_offset; +#endif + + void dlb_init(struct XTDatabase *db, size_t buffer_size); + void dlb_exit(struct XTThread *self); + xtBool dlb_close_log(struct XTThread *thread); + xtBool dlb_get_log_offset(xtLogID *log_id, off_t *out_offset, size_t req_size, struct XTThread *thread); + xtBool dlb_flush_log(xtBool commit, struct XTThread *thread); + xtBool dlb_write_thru_log(xtLogID log_id, xtLogOffset log_offset, size_t size, xtWord1 *data, struct XTThread *thread); + xtBool dlb_append_log(xtLogID log_id, off_t out_offset, size_t size, xtWord1 *data, struct XTThread *thread); + xtBool dlb_read_log(xtLogID log_id, off_t offset, size_t size, xtWord1 *data, struct XTThread *thread); + xtBool dlb_delete_log(xtLogID log_id, off_t offset, size_t size, xtTableID tab_id, xtRecordID tab_offset, struct XTThread *thread); +} XTDataLogBufferRec, *XTDataLogBufferPtr; + +typedef struct XTSeqLogRead { + struct XTDatabase *sl_db; + + virtual ~XTSeqLogRead() { } + virtual xtBool sl_seq_init(struct XTDatabase *db, size_t buffer_size) { (void) buffer_size; sl_db = db; return OK; }; + virtual void sl_seq_exit() { }; + virtual XTOpenFilePtr sl_seq_open_file() { return NULL; }; + virtual void sl_seq_pos(xtLogID *log_id, xtLogOffset *log_offset) { (void) log_id; (void) log_offset; }; + virtual xtBool sl_seq_start(xtLogID log_id, xtLogOffset log_offset, xtBool missing_ok) { + (void) log_id; (void) log_offset; (void) missing_ok; return OK; + }; + virtual xtBool sl_rnd_read(xtLogOffset log_offset, size_t size, xtWord1 *data, size_t *read, struct XTThread *thread) { + (void) log_offset; (void) size; (void) data; (void) read; (void) thread; return OK; + }; + virtual xtBool sl_seq_next(XTXactLogBufferDPtr *entry, xtBool verify, struct XTThread *thread) { + (void) entry; (void) verify; (void) thread; return OK; + }; + virtual void sl_seq_skip(size_t size) { (void) size; } +} XTSeqLogReadRec, *XTSeqLogReadPtr; + +typedef struct XTDataSeqRead : public XTSeqLogRead { + XTOpenFilePtr sl_log_file; + xtLogID sl_rec_log_id; /* The current record log ID. */ + xtLogOffset sl_rec_log_offset; /* The current log read position. */ + size_t sl_record_len; /* The length of the current record. */ + xtLogOffset sl_log_eof; + + size_t sl_buffer_size; /* Size of the buffer. */ + xtLogOffset sl_buf_log_offset; /* File offset of the buffer. */ + size_t sl_buffer_len; /* Amount of data in the buffer. */ + xtWord1 *sl_buffer; + + virtual ~XTDataSeqRead() { } + virtual xtBool sl_seq_init(struct XTDatabase *db, size_t buffer_size); + virtual void sl_seq_exit(); + virtual XTOpenFilePtr sl_seq_open_file(); + virtual void sl_seq_pos(xtLogID *log_id, xtLogOffset *log_offset); + virtual xtBool sl_seq_start(xtLogID log_id, xtLogOffset log_offset, xtBool missing_ok); + virtual xtBool sl_rnd_read(xtLogOffset log_offset, size_t size, xtWord1 *data, size_t *read, struct XTThread *thread); + virtual xtBool sl_seq_next(XTXactLogBufferDPtr *entry, xtBool verify, struct XTThread *thread); + virtual void sl_seq_skip(size_t size); + virtual void sl_seq_skip_to(off_t offset); +} XTDataSeqReadRec, *XTDataSeqReadPtr; + +void xt_dl_delete_ext_data(struct XTThread *self, struct XTTable *tab, xtBool missing_ok, xtBool have_table_lock); + +void xt_start_compactor(struct XTThread *self, struct XTDatabase *db); +void xt_stop_compactor(struct XTThread *self, struct XTDatabase *db); + +void xt_dl_init_db(struct XTThread *self, struct XTDatabase *db); +void xt_dl_exit_db(struct XTThread *self, struct XTDatabase *db); +void xt_dl_set_to_delete(struct XTThread *self, struct XTDatabase *db, xtLogID log_id); +void xt_dl_log_status(struct XTThread *self, struct XTDatabase *db, XTStringBufferPtr strbuf); +void xt_dl_delete_logs(struct XTThread *self, struct XTDatabase *db); + +#endif + diff --git a/storage/pbxt/src/discover_xt.cc b/storage/pbxt/src/discover_xt.cc new file mode 100644 index 00000000000..074132d47cb --- /dev/null +++ b/storage/pbxt/src/discover_xt.cc @@ -0,0 +1,1383 @@ +/* Copyright (c) 2008 PrimeBase Technologies GmbH, Germany + * Derived from code Copyright (C) 2000-2004 MySQL AB + * + * PrimeBase XT + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You 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 + * + * Created by Leslie on 8/27/08. + * + */ + +#include "xt_config.h" + +#ifndef DRIZZLED +#include "mysql_priv.h" +#include "item_create.h" +#include <m_ctype.h> +#else +#include <drizzled/session.h> +#include <drizzled/server_includes.h> +#include <drizzled/sql_base.h> +#endif + +#include "strutil_xt.h" +#include "ha_pbxt.h" +#include "discover_xt.h" +#include "ha_xtsys.h" + +#ifndef DRIZZLED +#if MYSQL_VERSION_ID > 60005 +#define DOT_STR(x) x.str +#else +#define DOT_STR(x) x +#endif +#endif + +#ifndef DRIZZLED +#define LOCK_OPEN_HACK_REQUIRED +#endif // DRIZZLED + +#ifdef LOCK_OPEN_HACK_REQUIRED +/////////////////////////////// +/* + * Unfortunately I cannot use the standard mysql_create_table_no_lock() because it will lock "LOCK_open" + * which has already been locked while the server is performing table discovery. So I have added this hack + * in here to create my own version. The following macros will make the changes I need to get it to work. + * The actual function code has been copied here without changes. + * + * Its almost enough to make you want to cry. :( +*/ +//----------------------------- + +#ifdef pthread_mutex_lock +#undef pthread_mutex_lock +#endif + +#ifdef pthread_mutex_unlock +#undef pthread_mutex_unlock +#endif + +#define mysql_create_table_no_lock hacked_mysql_create_table_no_lock +#define pthread_mutex_lock(l) +#define pthread_mutex_unlock(l) + +#define check_engine(t, n, c) (0) +#define set_table_default_charset(t, c, d) + +void calculate_interval_lengths(CHARSET_INFO *cs, TYPELIB *interval, + uint32 *max_length, uint32 *tot_length); + +uint build_tmptable_filename(THD* thd, char *buff, size_t bufflen); +uint build_table_filename(char *buff, size_t bufflen, const char *db, + const char *table_name, const char *ext, uint flags); + +////////////////////////////////////////////////////////// +////// START OF CUT AND PASTES FROM sql_table.cc //////// +////////////////////////////////////////////////////////// + +// sort_keys() cut and pasted directly from sql_table.cc. +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 | HA_END_SPACE_KEY)) + { + /* Sort NOT NULL keys before other keys */ + return (a_flags & (HA_NULL_PART_KEY | HA_END_SPACE_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_if_keyname_exists() cut and pasted directly from sql_table.cc. +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; +} + +// make_unique_key_name() cut and pasted directly from sql_table.cc. +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 +} + + +// prepare_blob_field() cut and pasted directly from sql_table.cc. +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->variables.sql_mode & (MODE_STRICT_TRANS_TABLES | + MODE_STRICT_ALL_TABLES))) + { + my_error(ER_TOO_BIG_FIELDLENGTH, MYF(0), sql_field->field_name, + MAX_FIELD_VARCHARLENGTH / sql_field->charset->mbmaxlen); + DBUG_RETURN(1); + } + sql_field->sql_type= MYSQL_TYPE_BLOB; + sql_field->flags|= BLOB_FLAG; + sprintf(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, MYSQL_ERROR::WARN_LEVEL_NOTE, ER_AUTO_CONVERT, + warn_buff); + } + + if ((sql_field->flags & BLOB_FLAG) && sql_field->length) + { + if (sql_field->sql_type == MYSQL_TYPE_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); +} + +////////////////////////////// +// mysql_prepare_create_table() cut and pasted directly from sql_table.cc. +static int +mysql_prepare_create_table(THD *thd, HA_CREATE_INFO *create_info, + Alter_info *alter_info, + bool tmp_table, + uint *db_options, + handler *file, KEY **key_info_buffer, + uint *key_count, int select_field_count) +{ + 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 timestamps= 0, timestamps_with_niladic= 0; + 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; + 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; + if (!sql_field->charset) + sql_field->charset= create_info->default_table_charset; + /* + table_charset is set in ALTER TABLE 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 && sql_field->charset != &my_charset_bin) + sql_field->charset= create_info->table_charset; + + save_cs= sql_field->charset; + if ((sql_field->flags & BINCMP_FLAG) && + !(sql_field->charset= get_charset_by_csname(sql_field->charset->csname, + MY_CS_BINSORT,MYF(0)))) + { + char tmp[64]; + 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[2]; + 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++) + { + uint 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)) + { + my_error(ER_ILLEGAL_VALUE_FOR_TYPE, MYF(0), "set", tmp->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, ¬_used, ¬_used2, ¬_found); + } + else /* not NULL */ + { + (void) find_set(interval, def->ptr(), def->length(), + cs, ¬_used, ¬_used2, ¬_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; + 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, + ×tamps, ×tamps_with_niladic, + 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++; + record_offset+= sql_field->pack_length; + } + if (timestamps_with_niladic > 1) + { + my_message(ER_TOO_MUCH_AUTO_TIMESTAMP_COLS, + ER(ER_TOO_MUCH_AUTO_TIMESTAMP_COLS), MYF(0)); + DBUG_RETURN(TRUE); + } + 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_message(ER_TABLE_CANT_HANDLE_AUTO_INCREMENT, + ER(ER_TABLE_CANT_HANDLE_AUTO_INCREMENT), MYF(0)); + DBUG_RETURN(TRUE); + } + + if (blob_columns && (file->ha_table_flags() & HA_NO_BLOBS)) + { + my_message(ER_TABLE_CANT_HANDLE_BLOB, ER(ER_TABLE_CANT_HANDLE_BLOB), + MYF(0)); + DBUG_RETURN(TRUE); + } + + /* 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->DOT_STR(name) ? key->DOT_STR(name) : + "(none)" , key->type)); + LEX_STRING key_name_str; + if (key->type == Key::FOREIGN_KEY) + { + fk_key_count++; + 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->DOT_STR(name) ? fk_key->DOT_STR(name) : "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); + } + key_name_str.str= (char*) key->DOT_STR(name); + key_name_str.length= key->DOT_STR(name) ? strlen(key->DOT_STR(name)) : 0; + if (check_string_char_length(&key_name_str, "", NAME_CHAR_LEN, + system_charset_info, 1)) + { + my_error(ER_TOO_LONG_IDENT, MYF(0), key->DOT_STR(name)); + 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->DOT_STR(name) != 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->DOT_STR(name)= ignore_key; + else + { + key2->DOT_STR(name)= ignore_key; + key_parts-= key2->columns.elements; + (*key_count)--; + } + break; + } + } + } + if (key->DOT_STR(name) != ignore_key) + key_parts+=key->columns.elements; + else + (*key_count)--; + if (key->DOT_STR(name) && !tmp_table && (key->type != Key::PRIMARY) && + !my_strcasecmp(system_charset_info,key->DOT_STR(name),primary_key_name)) + { + my_error(ER_WRONG_NAME_FOR_INDEX, MYF(0), key->DOT_STR(name)); + 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->DOT_STR(name) == ignore_key) + { + /* ignore redundant keys */ + do + key=key_iterator++; + while (key && key->DOT_STR(name) == 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->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; + + if (key->type == Key::FULLTEXT) + { + if (!(file->ha_table_flags() & HA_CAN_FULLTEXT)) + { + my_message(ER_TABLE_CANT_HANDLE_FT, ER(ER_TABLE_CANT_HANDLE_FT), + MYF(0)); + 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_message(ER_TABLE_CANT_HANDLE_SPKEYS, ER(ER_TABLE_CANT_HANDLE_SPKEYS), + MYF(0)); + DBUG_RETURN(TRUE); + } + if (key_info->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->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++) + { + uint length; + Key_part_spec *dup_column; + + it.rewind(); + field=0; + while ((sql_field=it++) && + my_strcasecmp(system_charset_info, + column->DOT_STR(field_name), + sql_field->field_name)) + field++; + if (!sql_field) + { + my_error(ER_KEY_COLUMN_DOES_NOT_EXITS, MYF(0), column->field_name); + DBUG_RETURN(TRUE); + } + while ((dup_column= cols2++) != column) + { + if (!my_strcasecmp(system_charset_info, + column->DOT_STR(field_name), dup_column->DOT_STR(field_name))) + { + my_printf_error(ER_DUP_FIELDNAME, + ER(ER_DUP_FIELDNAME),MYF(0), + column->field_name); + 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); + 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=test(f_is_blob(sql_field->pack_flag)); + } + else + { + column->length*= sql_field->charset->mbmaxlen; + + if (key->type == Key::SPATIAL && column->length) + { + my_error(ER_WRONG_SUB_KEY, MYF(0)); + 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); + DBUG_RETURN(TRUE); + } + if (f_is_geom(sql_field->pack_flag) && sql_field->geom_type == + Field::GEOM_POINT) + column->length= 25; + if (!column->length) + { + my_error(ER_BLOB_KEY_WITHOUT_LENGTH, MYF(0), column->field_name); + 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->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); + 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; + length= sql_field->key_length; + + if (column->length) + { + if (f_is_blob(sql_field->pack_flag)) + { + if ((length=column->length) > max_key_length || + length > file->max_key_part_length()) + { + length=min(max_key_length, file->max_key_part_length()); + if (key->type == Key::MULTIPLE) + { + /* not a critical problem */ + char warn_buff[MYSQL_ERRMSG_SIZE]; + my_snprintf(warn_buff, sizeof(warn_buff), ER(ER_TOO_LONG_KEY), + length); + push_warning(thd, MYSQL_ERROR::WARN_LEVEL_WARN, + ER_TOO_LONG_KEY, warn_buff); + /* Align key length to multibyte char boundary */ + length-= length % sql_field->charset->mbmaxlen; + } + else + { + my_error(ER_TOO_LONG_KEY,MYF(0),length); + DBUG_RETURN(TRUE); + } + } + } + else if (!f_is_geom(sql_field->pack_flag) && + (column->length > length || + !Field::type_can_have_key_part (sql_field->sql_type) || + ((f_is_packed(sql_field->pack_flag) || + ((file->ha_table_flags() & HA_NO_PREFIX_CHAR_KEYS) && + (key_info->flags & HA_NOSAME))) && + column->length != length))) + { + 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)) + length=column->length; + } + else if (length == 0) + { + my_error(ER_WRONG_KEY_COLUMN, MYF(0), column->field_name); + DBUG_RETURN(TRUE); + } + if (length > file->max_key_part_length() && key->type != Key::FULLTEXT) + { + length= file->max_key_part_length(); + if (key->type == Key::MULTIPLE) + { + /* not a critical problem */ + char warn_buff[MYSQL_ERRMSG_SIZE]; + my_snprintf(warn_buff, sizeof(warn_buff), ER(ER_TOO_LONG_KEY), + length); + push_warning(thd, MYSQL_ERROR::WARN_LEVEL_WARN, + ER_TOO_LONG_KEY, warn_buff); + /* Align key length to multibyte char boundary */ + length-= length % sql_field->charset->mbmaxlen; + } + else + { + my_error(ER_TOO_LONG_KEY,MYF(0),length); + DBUG_RETURN(TRUE); + } + } + key_part_info->length=(uint16) length; + /* Use packed keys for long strings on the first column */ + if (!((*db_options) & HA_OPTION_NO_PACK_KEYS) && + (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 (length != sql_field->key_length) + key_info->flags|= HA_KEY_HAS_PART_KEY_SEG; + + key_length+=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->DOT_STR(name))) + 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); + } + 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 && + sql_field->sql_type == MYSQL_TYPE_TIMESTAMP && + (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); + } + } + + DBUG_RETURN(FALSE); +} + +////////////////////////////// +// mysql_create_table_no_lock() cut and pasted directly from sql_table.cc. (I did make is static after copying it.) + +static bool mysql_create_table_no_lock(THD *thd, + const char *db, const char *table_name, + HA_CREATE_INFO *create_info, + Alter_info *alter_info, + bool internal_tmp_table, + uint select_field_count) +{ + char path[FN_REFLEN]; + uint path_length; + const char *alias; + uint db_options, key_count; + KEY *key_info_buffer; + handler *file; + bool error= TRUE; + DBUG_ENTER("mysql_create_table_no_lock"); + DBUG_PRINT("enter", ("db: '%s' table: '%s' tmp: %d", + db, table_name, internal_tmp_table)); + + + /* Check for duplicate fields and check type of table to create */ + if (!alter_info->create_list.elements) + { + my_message(ER_TABLE_MUST_HAVE_COLUMNS, ER(ER_TABLE_MUST_HAVE_COLUMNS), + MYF(0)); + DBUG_RETURN(TRUE); + } + if (check_engine(thd, table_name, create_info)) + DBUG_RETURN(TRUE); + db_options= create_info->table_options; + if (create_info->row_type == ROW_TYPE_DYNAMIC) + db_options|=HA_OPTION_PACK_RECORD; + alias= table_case_name(create_info, table_name); + + /* PMC - Done to avoid getting the partition handler by mistake! */ + if (!(file= new (thd->mem_root) ha_xtsys(pbxt_hton, NULL))) + { + mem_alloc_error(sizeof(handler)); + DBUG_RETURN(TRUE); + } + + set_table_default_charset(thd, create_info, (char*) db); + + if (mysql_prepare_create_table(thd, create_info, alter_info, + internal_tmp_table, + &db_options, file, + &key_info_buffer, &key_count, + select_field_count)) + goto err; + + /* Check if table exists */ + if (create_info->options & HA_LEX_CREATE_TMP_TABLE) + { + path_length= build_tmptable_filename(thd, path, sizeof(path)); + create_info->table_options|=HA_CREATE_DELAY_KEY_WRITE; + } + else + { + #ifdef FN_DEVCHAR + /* check if the table name contains FN_DEVCHAR when defined */ + if (strchr(alias, FN_DEVCHAR)) + { + my_error(ER_WRONG_TABLE_NAME, MYF(0), alias); + DBUG_RETURN(TRUE); + } +#endif + path_length= build_table_filename(path, sizeof(path), db, alias, reg_ext, + internal_tmp_table ? FN_IS_TMP : 0); + } + + /* Check if table already exists */ + if ((create_info->options & HA_LEX_CREATE_TMP_TABLE) && + find_temporary_table(thd, db, table_name)) + { + if (create_info->options & HA_LEX_CREATE_IF_NOT_EXISTS) + { + create_info->table_existed= 1; // Mark that table existed + push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_NOTE, + ER_TABLE_EXISTS_ERROR, ER(ER_TABLE_EXISTS_ERROR), + alias); + error= 0; + goto err; + } + my_error(ER_TABLE_EXISTS_ERROR, MYF(0), alias); + goto err; + } + + pthread_mutex_lock(&LOCK_open); + if (!internal_tmp_table && !(create_info->options & HA_LEX_CREATE_TMP_TABLE)) + { + if (!access(path,F_OK)) + { + if (create_info->options & HA_LEX_CREATE_IF_NOT_EXISTS) + goto warn; + my_error(ER_TABLE_EXISTS_ERROR,MYF(0),table_name); + goto unlock_and_end; + } + /* + We don't assert here, but check the result, because the table could be + in the table definition cache and in the same time the .frm could be + missing from the disk, in case of manual intervention which deletes + the .frm file. The user has to use FLUSH TABLES; to clear the cache. + Then she could create the table. This case is pretty obscure and + therefore we don't introduce a new error message only for it. + */ + if (get_cached_table_share(db, alias)) + { + my_error(ER_TABLE_EXISTS_ERROR, MYF(0), table_name); + goto unlock_and_end; + } + } + + /* + Check that table with given name does not already + exist in any storage engine. In such a case it should + be discovered and the error ER_TABLE_EXISTS_ERROR be returned + unless user specified CREATE TABLE IF EXISTS + The LOCK_open mutex has been locked to make sure no + one else is attempting to discover the table. Since + it's not on disk as a frm file, no one could be using it! + */ + if (!(create_info->options & HA_LEX_CREATE_TMP_TABLE)) + { + bool create_if_not_exists = + create_info->options & HA_LEX_CREATE_IF_NOT_EXISTS; + int retcode = ha_table_exists_in_engine(thd, db, table_name); + DBUG_PRINT("info", ("exists_in_engine: %u",retcode)); + switch (retcode) + { + case HA_ERR_NO_SUCH_TABLE: + /* Normal case, no table exists. we can go and create it */ + break; + case HA_ERR_TABLE_EXIST: + DBUG_PRINT("info", ("Table existed in handler")); + + if (create_if_not_exists) + goto warn; + my_error(ER_TABLE_EXISTS_ERROR,MYF(0),table_name); + goto unlock_and_end; + break; + default: + DBUG_PRINT("info", ("error: %u from storage engine", retcode)); + my_error(retcode, MYF(0),table_name); + goto unlock_and_end; + } + } + + thd_proc_info(thd, "creating table"); + create_info->table_existed= 0; // Mark that table is created + + create_info->table_options=db_options; + + path[path_length - reg_ext_length]= '\0'; // Remove .frm extension + if (rea_create_table(thd, path, db, table_name, + create_info, alter_info->create_list, + key_count, key_info_buffer, file)) + goto unlock_and_end; + + if (create_info->options & HA_LEX_CREATE_TMP_TABLE) + { + /* Open table and put in temporary table list */ +#if MYSQL_VERSION_ID > 60005 + if (!(open_temporary_table(thd, path, db, table_name, 1, OTM_OPEN))) +#else + if (!(open_temporary_table(thd, path, db, table_name, 1))) +#endif + { +#if MYSQL_VERSION_ID > 60005 + (void) rm_temporary_table(create_info->db_type, path, false); +#else + (void) rm_temporary_table(create_info->db_type, path); +#endif + goto unlock_and_end; + } + thd->thread_specific_used= TRUE; + } + + /* + Don't write statement if: + - It is an internal temporary table, + - Row-based logging is used and it we are creating a temporary table, or + - The binary log is not open. + Otherwise, the statement shall be binlogged. + */ + if (!internal_tmp_table && + (!thd->current_stmt_binlog_row_based || + (thd->current_stmt_binlog_row_based && + !(create_info->options & HA_LEX_CREATE_TMP_TABLE)))) + write_bin_log(thd, TRUE, thd->query, thd->query_length); + error= FALSE; +unlock_and_end: + pthread_mutex_unlock(&LOCK_open); + +err: + thd_proc_info(thd, "After create"); + delete file; + DBUG_RETURN(error); + +warn: + error= FALSE; + push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_NOTE, + ER_TABLE_EXISTS_ERROR, ER(ER_TABLE_EXISTS_ERROR), + alias); + create_info->table_existed= 1; // Mark that table existed + goto unlock_and_end; +} + +//////////////////////////////////////////////////////// +////// END OF CUT AND PASTES FROM sql_table.cc //////// +//////////////////////////////////////////////////////// + +#endif // LOCK_OPEN_HACK_REQUIRED + +//------------------------------ +int xt_create_table_frm(handlerton *hton, THD* thd, const char *db, const char *name, DT_FIELD_INFO *info, DT_KEY_INFO *keys __attribute__((unused)), xtBool skip_existing) +{ +#ifdef DRIZZLED + static const char *ext = ".dfe"; + static const int ext_len = 4; +#else + static const char *ext = ".frm"; + static const int ext_len = 4; +#endif + int err = 1; + //HA_CREATE_INFO create_info = {0}; + //Alter_info alter_info; + char field_length_buffer[12], *field_length_ptr; + LEX *save_lex= thd->lex, mylex; + + memset(&mylex.create_info, 0, sizeof(HA_CREATE_INFO)); + + thd->lex = &mylex; + lex_start(thd); + + /* setup the create info */ + mylex.create_info.db_type = hton; +#ifndef DRIZZLED + mylex.create_info.frm_only = 1; +#endif + mylex.create_info.default_table_charset = system_charset_info; + + /* setup the column info. */ + while (info->field_name) { + LEX_STRING field_name, comment; + field_name.str = (char*)(info->field_name); + field_name.length = strlen(info->field_name); + + comment.str = (char*)(info->comment); + comment.length = strlen(info->comment); + + if (info->field_length) { + sprintf(field_length_buffer, "%d", info->field_length); + field_length_ptr = field_length_buffer; + } else + field_length_ptr = NULL; + +#ifdef DRIZZLED + if (add_field_to_list(thd, &field_name, info->field_type, field_length_ptr, info->field_decimal_length, + info->field_flags, + COLUMN_FORMAT_TYPE_FIXED, + NULL /*default_value*/, NULL /*on_update_value*/, &comment, NULL /*change*/, + NULL /*interval_list*/, info->field_charset, + NULL /*vcol_info*/)) +#else + if (add_field_to_list(thd, &field_name, info->field_type, field_length_ptr, info->field_decimal_length, + info->field_flags, +#if MYSQL_VERSION_ID > 60005 + HA_SM_DISK, + COLUMN_FORMAT_TYPE_FIXED, +#endif + NULL /*default_value*/, NULL /*on_update_value*/, &comment, NULL /*change*/, + NULL /*interval_list*/, info->field_charset, 0 /*uint_geom_type*/)) +#endif + goto error; + + + info++; + } + + if (skip_existing) { + size_t db_len = strlen(db); + size_t name_len = strlen(name); + size_t len = db_len + 1 + name_len + ext_len + 1; + char *path = (char *)xt_malloc_ns(len); + memcpy(path, db, db_len); + memcpy(path + db_len + 1, name, name_len); + memcpy(path + db_len + 1 + name_len, ext, ext_len); + path[db_len] = XT_DIR_CHAR; + path[len - 1] = '\0'; + xtBool exists = xt_fs_exists(path); + xt_free_ns(path); + if (exists) + goto noerror; + } + + /* Create an internal temp table */ +#ifdef DRIZZLED + if (mysql_create_table_no_lock(thd, db, name, &mylex.create_info, &mylex.alter_info, 1, 0, false)) + goto error; +#else + if (mysql_create_table_no_lock(thd, db, name, &mylex.create_info, &mylex.alter_info, 1, 0)) + goto error; +#endif + + noerror: + err = 0; + + error: + lex_end(&mylex); + thd->lex = save_lex; + return err; +} + diff --git a/storage/pbxt/src/discover_xt.h b/storage/pbxt/src/discover_xt.h new file mode 100644 index 00000000000..733974ad59f --- /dev/null +++ b/storage/pbxt/src/discover_xt.h @@ -0,0 +1,79 @@ +/* Copyright (c) 2008 PrimeBase Technologies GmbH, Germany + * + * PrimeBase XT + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You 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 + * + * Created by Leslie on 8/27/08. + * + */ + +#ifndef __DISCOVER_XT_H__ +#define __DISCOVER_XT_H__ + +#ifdef DRIZZLED +#include <drizzled/common.h> +#else +#include "mysql_priv.h" +#endif + +/* + * --------------------------------------------------------------- + * TABLE DISCOVERY HANDLER + */ + +typedef struct dt_field_info { + /** + This is used as column name. + */ + const char* field_name; + /** + For string-type columns, this is the maximum number of + characters. For numeric data this can be NULL. + */ + uint field_length; + + /** + For decimal columns, this is the maximum number of + digits after the decimal. For other data this can be NULL. + */ + char* field_decimal_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; + + /** + This is the charater set for non numeric data types including blob data. + */ + CHARSET_INFO *field_charset; + + uint field_flags; // Field atributes(maybe_null, signed, unsigned etc.) + const char* comment; +} DT_FIELD_INFO; + +typedef struct dt_key_info +{ + const char* key_name; + uint key_type; /* PRI_KEY_FLAG, UNIQUE_KEY_FLAG, MULTIPLE_KEY_FLAG */ + const char* key_columns[8]; // The size of this can be set to what ever you need. +} DT_KEY_INFO; + +int xt_create_table_frm(handlerton *hton, THD* thd, const char *db, const char *name, DT_FIELD_INFO *info, DT_KEY_INFO *keys, xtBool skip_existing); + +#endif + diff --git a/storage/pbxt/src/filesys_xt.cc b/storage/pbxt/src/filesys_xt.cc new file mode 100644 index 00000000000..5ca36cd9244 --- /dev/null +++ b/storage/pbxt/src/filesys_xt.cc @@ -0,0 +1,1697 @@ +/* Copyright (c) 2005 PrimeBase Technologies GmbH + * + * PrimeBase XT + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You 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 + * + * 2005-01-12 Paul McCullagh + * + * H&G2JCtL + */ + +#include "xt_config.h" + +#ifndef XT_WIN +#include <unistd.h> +#include <dirent.h> +#include <sys/mman.h> +#endif +#include <stdio.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <sys/types.h> +#include <ctype.h> +#include <string.h> +#include <errno.h> + +#include "strutil_xt.h" +#include "pthread_xt.h" +#include "thread_xt.h" +#include "filesys_xt.h" +#include "memory_xt.h" +#include "cache_xt.h" +#include "sortedlist_xt.h" +#include "trace_xt.h" + +#ifdef DEBUG +//#define DEBUG_PRINT_IO +//#define DEBUG_TRACE_IO +//#define DEBUG_TRACE_MAP_IO +//#define DEBUG_TRACE_FILES +#endif + +#ifdef DEBUG_TRACE_FILES +//#define PRINTF xt_ftracef +#define PRINTF xt_trace +#endif + +/* ---------------------------------------------------------------------- + * Globals + */ + +typedef struct FsGlobals { + xt_mutex_type *fsg_lock; /* The xtPublic cache lock. */ + u_int fsg_current_id; + XTSortedListPtr fsg_open_files; +} FsGlobalsRec; + +static FsGlobalsRec fs_globals; + +#ifdef XT_WIN +static int fs_get_win_error() +{ + return (int) GetLastError(); +} + +xtPublic void xt_get_win_message(char *buffer, size_t size, int err) +{ + FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, NULL, err, + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + buffer, + size, NULL); +} +#endif + +/* ---------------------------------------------------------------------- + * Open file list + */ + +static XTFilePtr fs_new_file(XTThreadPtr self, char *file) +{ + XTFilePtr file_ptr; + + pushsr_(file_ptr, xt_free, (XTFilePtr) xt_calloc(self, sizeof(XTFileRec))); + + file_ptr->fil_path = xt_dup_string(self, file); + file_ptr->fil_id = fs_globals.fsg_current_id++; +#ifdef DEBUG_TRACE_FILES + PRINTF("%s: allocated file: (%d) %s\n", self->t_name, (int) file_ptr->fil_id, xt_last_2_names_of_path(file_ptr->fil_path)); +#endif + if (!fs_globals.fsg_current_id) + fs_globals.fsg_current_id++; + file_ptr->fil_filedes = XT_NULL_FD; + file_ptr->fil_handle_count = 0; + + popr_(); // Discard xt_free(file_ptr) + return file_ptr; +} + +static void fs_close_fmap(XTThreadPtr self, XTFileMemMapPtr mm) +{ +#ifdef XT_WIN + if (mm->mm_start) { + FlushViewOfFile(mm->mm_start, 0); + UnmapViewOfFile(mm->mm_start); + mm->mm_start = NULL; + } + if (mm->mm_mapdes != NULL) { + CloseHandle(mm->mm_mapdes); + mm->mm_mapdes = NULL; + } +#else + if (mm->mm_start) { + msync( (char *)mm->mm_start, (size_t) mm->mm_length, MS_SYNC); + munmap((caddr_t) mm->mm_start, (size_t) mm->mm_length); + mm->mm_start = NULL; + } +#endif + xt_rwmutex_free(self, &mm->mm_lock); + xt_free(self, mm); +} + +static void fs_free_file(XTThreadPtr self, void *thunk __attribute__((unused)), void *item) +{ + XTFilePtr file_ptr = *((XTFilePtr *) item); + + if (file_ptr->fil_filedes != XT_NULL_FD) { +#ifdef DEBUG_TRACE_FILES + PRINTF("%s: close file: (%d) %s\n", self->t_name, (int) file_ptr->fil_id, xt_last_2_names_of_path(file_ptr->fil_path)); +#endif +#ifdef XT_WIN + CloseHandle(file_ptr->fil_filedes); +#else + close(file_ptr->fil_filedes); +#endif + //PRINTF("close (FILE) %d %s\n", file_ptr->fil_filedes, file_ptr->fil_path); + file_ptr->fil_filedes = XT_NULL_FD; + } + + if (file_ptr->fil_memmap) { + fs_close_fmap(self, file_ptr->fil_memmap); + file_ptr->fil_memmap = NULL; + } + +#ifdef DEBUG_TRACE_FILES + PRINTF("%s: free file: (%d) %s\n", self->t_name, (int) file_ptr->fil_id, + file_ptr->fil_path ? xt_last_2_names_of_path(file_ptr->fil_path) : "?"); +#endif + + if (!file_ptr->fil_ref_count) { + /* Flush any cache before this file is invalid: */ + if (file_ptr->fil_path) { + xt_free(self, file_ptr->fil_path); + file_ptr->fil_path = NULL; + } + + xt_free(self, file_ptr); + } +} + +static int fs_comp_file(XTThreadPtr self __attribute__((unused)), register const void *thunk __attribute__((unused)), register const void *a, register const void *b) +{ + char *file_name = (char *) a; + XTFilePtr file_ptr = *((XTFilePtr *) b); + + return strcmp(file_name, file_ptr->fil_path); +} + +static int fs_comp_file_ci(XTThreadPtr self __attribute__((unused)), register const void *thunk __attribute__((unused)), register const void *a, register const void *b) +{ + char *file_name = (char *) a; + XTFilePtr file_ptr = *((XTFilePtr *) b); + + return strcasecmp(file_name, file_ptr->fil_path); +} + +/* ---------------------------------------------------------------------- + * init & exit + */ + +xtPublic void xt_fs_init(XTThreadPtr self) +{ + fs_globals.fsg_open_files = xt_new_sortedlist(self, + sizeof(XTFilePtr), 20, 20, + pbxt_ignore_case ? fs_comp_file_ci : fs_comp_file, + NULL, fs_free_file, TRUE, FALSE); + fs_globals.fsg_lock = fs_globals.fsg_open_files->sl_lock; + fs_globals.fsg_current_id = 1; +} + +xtPublic void xt_fs_exit(XTThreadPtr self) +{ + if (fs_globals.fsg_open_files) { + xt_free_sortedlist(self, fs_globals.fsg_open_files); + fs_globals.fsg_open_files = NULL; + } + fs_globals.fsg_lock = NULL; + fs_globals.fsg_current_id = 0; +} + +/* ---------------------------------------------------------------------- + * File operations + */ + +static void fs_set_stats(XTThreadPtr self, char *path) +{ + char super_path[PATH_MAX]; + struct stat stats; + char *ptr; + + ptr = xt_last_name_of_path(path); + if (ptr == path) + strcpy(super_path, "."); + else { + xt_strcpy(PATH_MAX, super_path, path); + + if ((ptr = xt_last_name_of_path(super_path))) + *ptr = 0; + } + if (stat(super_path, &stats) == -1) + xt_throw_ferrno(XT_CONTEXT, errno, super_path); + + if (chmod(path, stats.st_mode) == -1) + xt_throw_ferrno(XT_CONTEXT, errno, path); + + /*chown(path, stats.st_uid, stats.st_gid);*/ +} + +xtPublic char *xt_file_path(struct XTFileRef *of) +{ + return of->fr_file->fil_path; +} + +xtBool xt_fs_exists(char *path) +{ + int err; + + err = access(path, F_OK); + if (err == -1) + return FALSE; + return TRUE; +} + +/* + * No error is generated if the file dose not exist. + */ +xtPublic xtBool xt_fs_delete(XTThreadPtr self, char *name) +{ +#ifdef DEBUG_TRACE_FILES + PRINTF("%s: DELETE FILE: %s\n", xt_get_self()->t_name, xt_last_2_names_of_path(name)); +#endif +#ifdef XT_WIN + //PRINTF("delete %s\n", name); + if (!DeleteFile(name)) { + int err = fs_get_win_error(); + + if (!XT_FILE_NOT_FOUND(err)) { + xt_throw_ferrno(XT_CONTEXT, err, name); + return FAILED; + } + } +#else + if (unlink(name) == -1) { + int err = errno; + + if (err != ENOENT) { + xt_throw_ferrno(XT_CONTEXT, err, name); + return FAILED; + } + } +#endif + return OK; +} + +xtPublic xtBool xt_fs_file_not_found(int err) +{ +#ifdef XT_WIN + return XT_FILE_NOT_FOUND(err); +#else + return err == ENOENT; +#endif +} + +xtPublic void xt_fs_move(struct XTThread *self, char *from_path, char *to_path) +{ +#ifdef DEBUG_TRACE_FILES + PRINTF("%s: MOVE FILE: %s --> %s\n", xt_get_self()->t_name, xt_last_2_names_of_path(from_path), xt_last_2_names_of_path(to_path)); +#endif +#ifdef XT_WIN + if (!MoveFile(from_path, to_path)) + xt_throw_ferrno(XT_CONTEXT, fs_get_win_error(), from_path); +#else + int err; + + if (link(from_path, to_path) == -1) { + err = errno; + xt_throw_ferrno(XT_CONTEXT, err, from_path); + } + + if (unlink(from_path) == -1) { + err = errno; + unlink(to_path); + xt_throw_ferrno(XT_CONTEXT, err, from_path); + } +#endif +} + +xtPublic xtBool xt_fs_rename(struct XTThread *self, char *from_path, char *to_path) +{ + int err; + +#ifdef DEBUG_TRACE_FILES + PRINTF("%s: RENAME FILE: %s --> %s\n", xt_get_self()->t_name, xt_last_2_names_of_path(from_path), xt_last_2_names_of_path(to_path)); +#endif + if (rename(from_path, to_path) == -1) { + err = errno; + xt_throw_ferrno(XT_CONTEXT, err, from_path); + return FAILED; + } + return OK; +} + +xtPublic xtBool xt_fs_stat(XTThreadPtr self, char *path, off_t *size, struct timespec *mod_time) +{ +#ifdef XT_WIN + HANDLE fh; + BY_HANDLE_FILE_INFORMATION info; + SECURITY_ATTRIBUTES sa = { sizeof(SECURITY_ATTRIBUTES), 0, 0 }; + + fh = CreateFile( + path, + GENERIC_READ, + FILE_SHARE_READ, + &sa, + OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL, + NULL); + if (fh == INVALID_HANDLE_VALUE) { + xt_throw_ferrno(XT_CONTEXT, fs_get_win_error(), path); + return FAILED; + } + + if (!GetFileInformationByHandle(fh, &info)) { + CloseHandle(fh); + xt_throw_ferrno(XT_CONTEXT, fs_get_win_error(), path); + return FAILED; + } + + CloseHandle(fh); + if (size) + *size = (off_t) info.nFileSizeLow | (((off_t) info.nFileSizeHigh) << 32); + if (mod_time) + mod_time->tv.ft = info.ftLastWriteTime; +#else + struct stat sb; + + if (stat(path, &sb) == -1) { + xt_throw_ferrno(XT_CONTEXT, errno, path); + return FAILED; + } + if (size) + *size = sb.st_size; + if (mod_time) { + mod_time->tv_sec = sb.st_mtime; +#ifdef XT_MAC + /* This is the Mac OS X version: */ + mod_time->tv_nsec = sb.st_mtimespec.tv_nsec; +#else +#ifdef __USE_MISC + /* This is the Linux version: */ + mod_time->tv_nsec = sb.st_mtim.tv_nsec; +#else + /* Not supported? */ + mod_time->tv_nsec = 0; +#endif +#endif + } +#endif + return OK; +} + +void xt_fs_mkdir(XTThreadPtr self, char *name) +{ + char path[PATH_MAX]; + + xt_strcpy(PATH_MAX, path, name); + xt_remove_dir_char(path); + +#ifdef XT_WIN + { + SECURITY_ATTRIBUTES sa = { sizeof(SECURITY_ATTRIBUTES), 0, 0 }; + + if (!CreateDirectory(path, &sa)) + xt_throw_ferrno(XT_CONTEXT, fs_get_win_error(), path); + } +#else + if (mkdir(path, S_IRWXU | S_IRWXG | S_IRWXO) == -1) + xt_throw_ferrno(XT_CONTEXT, errno, path); + + try_(a) { + fs_set_stats(self, path); + } + catch_(a) { + xt_fs_rmdir(NULL, name); + throw_(); + } + cont_(a); +#endif +} + +void xt_fs_mkpath(XTThreadPtr self, char *path) +{ + char *ptr; + + if (xt_fs_exists(path)) + return; + + if (!(ptr = (char *) xt_last_directory_of_path((c_char *) path))) + return; + if (ptr == path) + return; + ptr--; + if (XT_IS_DIR_CHAR(*ptr)) { + *ptr = 0; + xt_fs_mkpath(self, path); + *ptr = XT_DIR_CHAR; + xt_fs_mkdir(self, path); + } +} + +xtBool xt_fs_rmdir(XTThreadPtr self, char *name) +{ + char path[PATH_MAX]; + + xt_strcpy(PATH_MAX, path, name); + xt_remove_dir_char(path); + +#ifdef XT_WIN + if (!RemoveDirectory(path)) { + int err = fs_get_win_error(); + + if (!XT_FILE_NOT_FOUND(err)) { + xt_throw_ferrno(XT_CONTEXT, err, path); + return FAILED; + } + } +#else + if (rmdir(path) == -1) { + int err = errno; + + if (err != ENOENT) { + xt_throw_ferrno(XT_CONTEXT, err, path); + return FAILED; + } + } +#endif + return OK; +} + +/* ---------------------------------------------------------------------- + * Open & Close operations + */ + +xtPublic XTFilePtr xt_fs_get_file(XTThreadPtr self, char *file_name) +{ + XTFilePtr file_ptr, *file_pptr; + + xt_sl_lock(self, fs_globals.fsg_open_files); + pushr_(xt_sl_unlock, fs_globals.fsg_open_files); + + if ((file_pptr = (XTFilePtr *) xt_sl_find(self, fs_globals.fsg_open_files, file_name))) + file_ptr = *file_pptr; + else { + file_ptr = fs_new_file(self, file_name); + xt_sl_insert(self, fs_globals.fsg_open_files, file_name, &file_ptr); + } + file_ptr->fil_ref_count++; + freer_(); // xt_sl_unlock(fs_globals.fsg_open_files) + return file_ptr; +} + +xtPublic void xt_fs_release_file(XTThreadPtr self, XTFilePtr file_ptr) +{ + xt_sl_lock(self, fs_globals.fsg_open_files); + pushr_(xt_sl_unlock, fs_globals.fsg_open_files); + + file_ptr->fil_ref_count--; + if (!file_ptr->fil_ref_count) { + xt_sl_delete(self, fs_globals.fsg_open_files, file_ptr->fil_path); + } + + freer_(); // xt_ht_unlock(fs_globals.fsg_open_files) +} + +static xtBool fs_open_file(XTThreadPtr self, XT_FD *fd, XTFilePtr file, int mode) +{ + int retried = FALSE; + +#ifdef DEBUG_TRACE_FILES + PRINTF("%s: OPEN FILE: (%d) %s\n", self->t_name, (int) file->fil_id, xt_last_2_names_of_path(file->fil_path)); +#endif + retry: +#ifdef XT_WIN + SECURITY_ATTRIBUTES sa = { sizeof(SECURITY_ATTRIBUTES), 0, 0 }; + DWORD flags; + + if (mode & XT_FS_EXCLUSIVE) + flags = CREATE_NEW; + else if (mode & XT_FS_CREATE) + flags = OPEN_ALWAYS; + else + flags = OPEN_EXISTING; + + *fd = CreateFile( + file->fil_path, + mode & XT_FS_READONLY ? GENERIC_READ : (GENERIC_READ | GENERIC_WRITE), + FILE_SHARE_READ | FILE_SHARE_WRITE, + &sa, + flags, + FILE_FLAG_RANDOM_ACCESS, + NULL); + if (*fd == INVALID_HANDLE_VALUE) { + int err = fs_get_win_error(); + + if (!(mode & XT_FS_MISSING_OK) || !XT_FILE_NOT_FOUND(err)) { + if (!retried && (mode & XT_FS_MAKE_PATH) && XT_FILE_NOT_FOUND(err)) { + char path[PATH_MAX]; + + xt_strcpy(PATH_MAX, path, file->fil_path); + xt_remove_last_name_of_path(path); + xt_fs_mkpath(self, path); + retried = TRUE; + goto retry; + } + + xt_throw_ferrno(XT_CONTEXT, err, file->fil_path); + } + + /* File is missing, but don't throw an error. */ + return FAILED; + } + //PRINTF("open %d %s\n", *fd, file->fil_path); + return OK; +#else + int flags = 0; + + if (mode & XT_FS_READONLY) + flags = O_RDONLY; + else + flags = O_RDWR; + if (mode & XT_FS_CREATE) + flags |= O_CREAT; + if (mode & XT_FS_EXCLUSIVE) + flags |= O_EXCL; +#ifdef O_DIRECT + if (mode & XT_FS_DIRECT_IO) + flags |= O_DIRECT; +#endif + + *fd = open(file->fil_path, flags, XT_MASK); + if (*fd == -1) { + int err = errno; + + if (!(mode & XT_FS_MISSING_OK) || err != ENOENT) { + if (!retried && (mode & XT_FS_MAKE_PATH) && err == ENOENT) { + char path[PATH_MAX]; + + xt_strcpy(PATH_MAX, path, file->fil_path); + xt_remove_last_name_of_path(path); + xt_fs_mkpath(self, path); + retried = TRUE; + goto retry; + } + + xt_throw_ferrno(XT_CONTEXT, err, file->fil_path); + } + + /* File is missing, but don't throw an error. */ + return FAILED; + } + ///PRINTF("open %d %s\n", *fd, file->fil_path); + return OK; +#endif +} + +xtPublic XTOpenFilePtr xt_open_file(XTThreadPtr self, char *file, int mode) +{ + XTOpenFilePtr of; + + pushsr_(of, xt_close_file, (XTOpenFilePtr) xt_calloc(self, sizeof(XTOpenFileRec))); + of->fr_file = xt_fs_get_file(self, file); + of->fr_id = of->fr_file->fil_id; + of->of_filedes = XT_NULL_FD; + +#ifdef XT_WIN + if (!fs_open_file(self, &of->of_filedes, of->fr_file, mode)) { + xt_close_file(self, of); + of = NULL; + } +#else + xtBool failed = FALSE; + + if (of->fr_file->fil_filedes == -1) { + xt_sl_lock(self, fs_globals.fsg_open_files); + pushr_(xt_sl_unlock, fs_globals.fsg_open_files); + if (of->fr_file->fil_filedes == -1) { + if (!fs_open_file(self, &of->fr_file->fil_filedes, of->fr_file, mode)) + failed = TRUE; + } + freer_(); // xt_ht_unlock(fs_globals.fsg_open_files) + } + + if (failed) { + /* Close, but after we have release the fsg_open_files lock! */ + xt_close_file(self, of); + of = NULL; + } + else + of->of_filedes = of->fr_file->fil_filedes; +#endif + + popr_(); // Discard xt_close_file(of) + return of; +} + +xtPublic XTOpenFilePtr xt_open_file_ns(char *file, int mode) +{ + XTThreadPtr self = xt_get_self(); + XTOpenFilePtr of; + + try_(a) { + of = xt_open_file(self, file, mode); + } + catch_(a) { + of = NULL; + } + cont_(a); + return of; +} + +xtPublic xtBool xt_open_file_ns(XTOpenFilePtr *fh, char *file, int mode) +{ + XTThreadPtr self = xt_get_self(); + xtBool ok = TRUE; + + try_(a) { + *fh = xt_open_file(self, file, mode); + } + catch_(a) { + ok = FALSE; + } + cont_(a); + return ok; +} + +xtPublic void xt_close_file(XTThreadPtr self, XTOpenFilePtr of) +{ + if (of->of_filedes != XT_NULL_FD) { +#ifdef XT_WIN + CloseHandle(of->of_filedes); +#ifdef DEBUG_TRACE_FILES + PRINTF("%s: close file: (%d) %s\n", self->t_name, (int) of->fr_file->fil_id, xt_last_2_names_of_path(of->fr_file->fil_path)); +#endif +#else + if (!of->fr_file || of->of_filedes != of->fr_file->fil_filedes) { + close(of->of_filedes); +#ifdef DEBUG_TRACE_FILES + PRINTF("%s: close file: (%d) %s\n", self->t_name, (int) of->fr_file->fil_id, xt_last_2_names_of_path(of->fr_file->fil_path)); +#endif + } +#endif + + of->of_filedes = XT_NULL_FD; + } + + if (of->fr_file) { + xt_fs_release_file(self, of->fr_file); + of->fr_file = NULL; + } + xt_free(self, of); +} + +xtPublic xtBool xt_close_file_ns(XTOpenFilePtr of) +{ + XTThreadPtr self = xt_get_self(); + xtBool failed = FALSE; + + try_(a) { + xt_close_file(self, of); + } + catch_(a) { + failed = TRUE; + } + cont_(a); + return failed; +} + +/* ---------------------------------------------------------------------- + * I/O operations + */ + +xtPublic xtBool xt_lock_file(struct XTThread *self, XTOpenFilePtr of) +{ +#ifdef XT_WIN + if (!LockFile(of->of_filedes, 0, 0, 512, 0)) { + int err = fs_get_win_error(); + + if (err == ERROR_LOCK_VIOLATION || + err == ERROR_LOCK_FAILED) + return FAILED; + + xt_throw_ferrno(XT_CONTEXT, err, xt_file_path(of)); + return FAILED; + } + return OK; +#else + if (lockf(of->of_filedes, F_TLOCK, 0) == 0) + return OK; + if (errno == EAGAIN) + return FAILED; + xt_throw_ferrno(XT_CONTEXT, errno, xt_file_path(of)); + return FAILED; +#endif +} + +xtPublic void xt_unlock_file(struct XTThread *self, XTOpenFilePtr of) +{ +#ifdef XT_WIN + if (!UnlockFile(of->of_filedes, 0, 0, 512, 0)) { + int err = fs_get_win_error(); + + if (err != ERROR_NOT_LOCKED) + xt_throw_ferrno(XT_CONTEXT, err, xt_file_path(of)); + } +#else + if (lockf(of->of_filedes, F_ULOCK, 0) == -1) + xt_throw_ferrno(XT_CONTEXT, errno, xt_file_path(of)); +#endif +} + +static off_t fs_seek_eof(XTThreadPtr self, XT_FD fd, XTFilePtr file) +{ +#ifdef XT_WIN + DWORD result; + LARGE_INTEGER lpFileSize; + + result = SetFilePointer(fd, 0, NULL, FILE_END); + if (result == 0xFFFFFFFF) { + xt_throw_ferrno(XT_CONTEXT, fs_get_win_error(), file->fil_path); + return (off_t) -1; + } + + if (!GetFileSizeEx(fd, &lpFileSize)) { + xt_throw_ferrno(XT_CONTEXT, fs_get_win_error(), file->fil_path); + return (off_t) -1; + } + + return lpFileSize.QuadPart; +#else + off_t off; + + off = lseek(fd, 0, SEEK_END); + if (off == -1) { + xt_throw_ferrno(XT_CONTEXT, errno, file->fil_path); + return -1; + } + + return off; +#endif +} + +xtPublic off_t xt_seek_eof_file(XTThreadPtr self, XTOpenFilePtr of) +{ + return fs_seek_eof(self, of->of_filedes, of->fr_file); +} + +xtPublic xtBool xt_set_eof_file(XTThreadPtr self, XTOpenFilePtr of, off_t offset) +{ +#ifdef XT_WIN + LARGE_INTEGER liDistanceToMove; + + liDistanceToMove.QuadPart = offset; + if (!SetFilePointerEx(of->of_filedes, liDistanceToMove, NULL, FILE_BEGIN)) { + xt_throw_ferrno(XT_CONTEXT, fs_get_win_error(), xt_file_path(of)); + return FAILED; + } + + if (!SetEndOfFile(of->of_filedes)) { + xt_throw_ferrno(XT_CONTEXT, fs_get_win_error(), xt_file_path(of)); + return FAILED; + } +#else + if (ftruncate(of->of_filedes, offset) == -1) { + xt_throw_ferrno(XT_CONTEXT, errno, xt_file_path(of)); + return FAILED; + } +#endif + return OK; +} + +xtPublic xtBool xt_pwrite_file(XTOpenFilePtr of, off_t offset, size_t size, void *data, XTIOStatsPtr stat, XTThreadPtr XT_UNUSED(thread)) +{ +#ifdef DEBUG_PRINT_IO + PRINTF("PBXT WRITE %s offs=%d size=%d\n", of->fr_file->fil_path, (int) offset, (int) size); +#endif +#ifdef DEBUG_TRACE_IO + char timef[50]; + xtWord8 start = xt_trace_clock(); +#endif +#ifdef XT_WIN + LARGE_INTEGER liDistanceToMove; + DWORD result; + + liDistanceToMove.QuadPart = offset; + if (!SetFilePointerEx(of->of_filedes, liDistanceToMove, NULL, FILE_BEGIN)) + return xt_register_ferrno(XT_REG_CONTEXT, fs_get_win_error(), xt_file_path(of)); + + if (!WriteFile(of->of_filedes, data, size, &result, NULL)) + return xt_register_ferrno(XT_REG_CONTEXT, fs_get_win_error(), xt_file_path(of)); + + if (result != size) + return xt_register_ferrno(XT_REG_CONTEXT, ERROR_HANDLE_EOF, xt_file_path(of)); +#else + ssize_t write_size; + + write_size = pwrite(of->of_filedes, data, size, offset); + if (write_size == -1) + return xt_register_ferrno(XT_REG_CONTEXT, errno, xt_file_path(of)); + + if ((size_t) write_size != size) + return xt_register_ferrno(XT_REG_CONTEXT, ESPIPE, xt_file_path(of)); + +#endif + stat->ts_write += (u_int) size; + +#ifdef DEBUG_TRACE_IO + xt_trace("/* %s */ pbxt_file_writ(\"%s\", %lu, %lu);\n", xt_trace_clock_diff(timef, start), of->fr_file->fil_path, (u_long) offset, (u_long) size); +#endif + return OK; +} + +xtPublic xtBool xt_flush_file(XTOpenFilePtr of, XTIOStatsPtr stat, XTThreadPtr XT_UNUSED(thread)) +{ + xtWord8 s; + +#ifdef DEBUG_PRINT_IO + PRINTF("PBXT FLUSH %s\n", of->fr_file->fil_path); +#endif +#ifdef DEBUG_TRACE_IO + char timef[50]; + xtWord8 start = xt_trace_clock(); +#endif + stat->ts_flush_start = xt_trace_clock(); +#ifdef XT_WIN + if (!FlushFileBuffers(of->of_filedes)) { + xt_register_ferrno(XT_REG_CONTEXT, fs_get_win_error(), xt_file_path(of)); + goto failed; + } +#else + if (fsync(of->of_filedes) == -1) { + xt_register_ferrno(XT_REG_CONTEXT, errno, xt_file_path(of)); + goto failed; + } +#endif +#ifdef DEBUG_TRACE_IO + xt_trace("/* %s */ pbxt_file_sync(\"%s\");\n", xt_trace_clock_diff(timef, start), of->fr_file->fil_path); +#endif + s = stat->ts_flush_start; + stat->ts_flush_start = 0; + stat->ts_flush_time += xt_trace_clock() - s; + stat->ts_flush++; + return OK; + + failed: + s = stat->ts_flush_start; + stat->ts_flush_start = 0; + stat->ts_flush_time += xt_trace_clock() - s; + return FAILED; +} + +xtBool xt_pread_file(XTOpenFilePtr of, off_t offset, size_t size, size_t min_size, void *data, size_t *red_size, XTIOStatsPtr stat, XTThreadPtr XT_UNUSED(thread)) +{ +#ifdef DEBUG_PRINT_IO + PRINTF("PBXT READ %s offset=%d size=%d\n", of->fr_file->fil_path, (int) offset, (int) size); +#endif +#ifdef DEBUG_TRACE_IO + char timef[50]; + xtWord8 start = xt_trace_clock(); +#endif +#ifdef XT_WIN + LARGE_INTEGER liDistanceToMove; + DWORD result; + + liDistanceToMove.QuadPart = offset; + if (!SetFilePointerEx(of->of_filedes, liDistanceToMove, NULL, FILE_BEGIN)) + return xt_register_ferrno(XT_REG_CONTEXT, fs_get_win_error(), xt_file_path(of)); + + if (!ReadFile(of->of_filedes, data, size, &result, NULL)) + return xt_register_ferrno(XT_REG_CONTEXT, fs_get_win_error(), xt_file_path(of)); + + if ((size_t) result < min_size) + return xt_register_ferrno(XT_REG_CONTEXT, ERROR_HANDLE_EOF, xt_file_path(of)); + + if (red_size) + *red_size = (size_t) result; + stat->ts_read += (u_int) result; +#else + ssize_t read_size; + + read_size = pread(of->of_filedes, data, size, offset); + if (read_size == -1) + return xt_register_ferrno(XT_REG_CONTEXT, errno, xt_file_path(of)); + + /* Throw an error if read less than the minimum: */ + if ((size_t) read_size < min_size) { +//PRINTF("PMC PBXT <-- offset:%llu, count:%lu \n", (u_llong) offset, (u_long) size); + return xt_register_ferrno(XT_REG_CONTEXT, ESPIPE, xt_file_path(of)); + } + + if (red_size) + *red_size = (size_t) read_size; + stat->ts_read += (u_int) read_size; +#endif +#ifdef DEBUG_TRACE_IO + xt_trace("/* %s */ pbxt_file_read(\"%s\", %lu, %lu);\n", xt_trace_clock_diff(timef, start), of->fr_file->fil_path, (u_long) offset, (u_long) size); +#endif + return OK; +} + +/* ---------------------------------------------------------------------- + * Directory operations + */ + +/* + * The filter may contain one '*' as wildcard. + */ +XTOpenDirPtr xt_dir_open(XTThreadPtr self, c_char *path, c_char *filter) +{ + XTOpenDirPtr od; + + pushsr_(od, xt_dir_close, (XTOpenDirPtr) xt_calloc(self, sizeof(XTOpenDirRec))); + +#ifdef XT_WIN + size_t len; + + od->od_handle = XT_NULL_FD; + + // path = path\(filter | *) + len = strlen(path) + 1 + (filter ? strlen(filter) : 1) + 1; + od->od_path = (char *) xt_malloc(self, len); + + strcpy(od->od_path, path); + xt_add_dir_char(len, od->od_path); + if (filter) + strcat(od->od_path, filter); + else + strcat(od->od_path, "*"); +#else + od->od_path = xt_dup_string(self, path); + + if (filter) + od->od_filter = xt_dup_string(self, filter); + + od->od_dir = opendir(path); + if (!od->od_dir) + xt_throw_ferrno(XT_CONTEXT, errno, path); +#endif + + popr_(); // Discard xt_dir_close(od) + return od; +} + +void xt_dir_close(XTThreadPtr self, XTOpenDirPtr od) +{ + if (od) { +#ifdef XT_WIN + if (od->od_handle != XT_NULL_FD) { + FindClose(od->od_handle); + od->od_handle = XT_NULL_FD; + } +#else + if (od->od_dir) { + closedir(od->od_dir); + od->od_dir = NULL; + } + if (od->od_filter) { + xt_free(self, od->od_filter); + od->od_filter = NULL; + } +#endif + if (od->od_path) { + xt_free(self, od->od_path); + od->od_path = NULL; + } + xt_free(self, od); + } +} + +#ifdef XT_WIN +xtBool xt_dir_next(XTThreadPtr self, XTOpenDirPtr od) +{ + int err = 0; + + if (od->od_handle == INVALID_HANDLE_VALUE) { + od->od_handle = FindFirstFile(od->od_path, &od->od_data); + if (od->od_handle == INVALID_HANDLE_VALUE) + err = fs_get_win_error(); + } + else { + if (!FindNextFile(od->od_handle, &od->od_data)) + err = fs_get_win_error(); + } + + if (err) { + if (err != ERROR_NO_MORE_FILES) { + if (err == ERROR_FILE_NOT_FOUND) { + char path[PATH_MAX]; + + xt_strcpy(PATH_MAX, path, od->od_path); + xt_remove_last_name_of_path(path); + if (!xt_fs_exists(path)) + xt_throw_ferrno(XT_CONTEXT, err, path); + } + else + xt_throw_ferrno(XT_CONTEXT, err, od->od_path); + } + return FAILED; + } + + return OK; +} +#else +static xtBool fs_match_filter(c_char *name, c_char *filter) +{ + while (*name && *filter) { + if (*filter == '*') { + if (filter[1] == *name) + filter++; + else + name++; + } + else { + if (*name != *filter) + return FALSE; + name++; + filter++; + } + } + if (!*name) { + if (!*filter || (*filter == '*' && !filter[1])) + return TRUE; + } + return FALSE; +} + +xtBool xt_dir_next(XTThreadPtr self, XTOpenDirPtr od) +{ + int err; + struct dirent *result; + + for (;;) { + err = readdir_r(od->od_dir, &od->od_entry, &result); + if (err) { + xt_throw_ferrno(XT_CONTEXT, err, od->od_path); + return FAILED; + } + if (!result) + break; + /* Filter out '.' and '..': */ + if (od->od_entry.d_name[0] == '.') { + if (od->od_entry.d_name[1] == '.') { + if (od->od_entry.d_name[2] == '\0') + continue; + } + else { + if (od->od_entry.d_name[1] == '\0') + continue; + } + } + if (!od->od_filter) + break; + if (fs_match_filter(od->od_entry.d_name, od->od_filter)) + break; + } + return result ? TRUE : FALSE; +} +#endif + +char *xt_dir_name(XTThreadPtr self __attribute__((unused)), XTOpenDirPtr od) +{ +#ifdef XT_WIN + return od->od_data.cFileName; +#else + return od->od_entry.d_name; +#endif +} + +xtBool xt_dir_is_file(XTThreadPtr self __attribute__((unused)), XTOpenDirPtr od) +{ +#ifdef XT_WIN + if (od->od_data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) + return FALSE; +#elif defined(XT_SOLARIS) + char path[PATH_MAX]; + struct stat sb; + + xt_strcpy(PATH_MAX, path, od->od_path); + xt_add_dir_char(PATH_MAX, path); + xt_strcat(PATH_MAX, path, od->od_entry.d_name); + + if (stat(path, &sb) == -1) { + xt_throw_ferrno(XT_CONTEXT, errno, path); + return FAILED; + } + + if ( sb.st_mode & S_IFDIR ) + return FALSE; +#else + if (od->od_entry.d_type & DT_DIR) + return FALSE; +#endif + return TRUE; +} + +off_t xt_dir_file_size(XTThreadPtr self, XTOpenDirPtr od) +{ +#ifdef XT_WIN + return (off_t) od->od_data.nFileSizeLow | (((off_t) od->od_data.nFileSizeHigh) << 32); +#else + char path[PATH_MAX]; + off_t size; + + xt_strcpy(PATH_MAX, path, od->od_path); + xt_add_dir_char(PATH_MAX, path); + xt_strcat(PATH_MAX, path, od->od_entry.d_name); + if (!xt_fs_stat(self, path, &size, NULL)) + return -1; + return size; +#endif +} + +/* ---------------------------------------------------------------------- + * File mapping operations + */ + +static xtBool fs_map_file(XTFileMemMapPtr mm, XTFilePtr file, xtBool grow) +{ + ASSERT_NS(!mm->mm_start); +#ifdef XT_WIN + /* This will grow the file to the given size: */ + mm->mm_mapdes = CreateFileMapping(file->fil_filedes, NULL, PAGE_READWRITE, (DWORD) (mm->mm_length >> 32), (DWORD) mm->mm_length, NULL); + if (mm->mm_mapdes == NULL) { + xt_register_ferrno(XT_REG_CONTEXT, fs_get_win_error(), file->fil_path); + return FAILED; + } + + mm->mm_start = (xtWord1 *) MapViewOfFile(mm->mm_mapdes, FILE_MAP_WRITE, 0, 0, 0); + if (!mm->mm_start) { + CloseHandle(mm->mm_mapdes); + mm->mm_mapdes = NULL; + xt_register_ferrno(XT_REG_CONTEXT, fs_get_win_error(), file->fil_path); + return FAILED; + } +#else + if (grow) { + char data[2]; + + if (pwrite(file->fil_filedes, data, 1, mm->mm_length - 1) == -1) { + xt_register_ferrno(XT_REG_CONTEXT, errno, file->fil_path); + return FAILED; + } + } + + /* Remap: */ + mm->mm_start = (xtWord1 *) mmap(0, (size_t) mm->mm_length, PROT_READ | PROT_WRITE, MAP_SHARED, file->fil_filedes, 0); + if (mm->mm_start == MAP_FAILED) { + mm->mm_start = NULL; + xt_register_ferrno(XT_REG_CONTEXT, errno, file->fil_path); + return FAILED; + } +#endif + return OK; +} + +xtPublic XTMapFilePtr xt_open_fmap(XTThreadPtr self, char *file, size_t grow_size) +{ + XTMapFilePtr map; + + pushsr_(map, xt_close_fmap, (XTMapFilePtr) xt_calloc(self, sizeof(XTMapFileRec))); + map->fr_file = xt_fs_get_file(self, file); + map->fr_id = map->fr_file->fil_id; + + xt_sl_lock(self, fs_globals.fsg_open_files); + pushr_(xt_sl_unlock, fs_globals.fsg_open_files); + + if (map->fr_file->fil_filedes == XT_NULL_FD) { + if (!fs_open_file(self, &map->fr_file->fil_filedes, map->fr_file, XT_FS_DEFAULT)) { + xt_close_fmap(self, map); + map = NULL; + } + } + + map->fr_file->fil_handle_count++; + + freer_(); // xt_ht_unlock(fs_globals.fsg_open_files) + + if (!map->fr_file->fil_memmap) { + xt_sl_lock(self, fs_globals.fsg_open_files); + pushr_(xt_sl_unlock, fs_globals.fsg_open_files); + if (!map->fr_file->fil_memmap) { + XTFileMemMapPtr mm; + + mm = (XTFileMemMapPtr) xt_calloc(self, sizeof(XTFileMemMapRec)); + pushr_(fs_close_fmap, mm); + +#ifdef XT_WIN + /* NULL is the value returned on error! */ + mm->mm_mapdes = NULL; +#endif + xt_rwmutex_init_with_autoname(self, &mm->mm_lock); + mm->mm_length = fs_seek_eof(self, map->fr_file->fil_filedes, map->fr_file); + if (sizeof(size_t) == 4 && mm->mm_length >= (off_t) 0xFFFFFFFF) + xt_throw_ixterr(XT_CONTEXT, XT_ERR_FILE_TOO_LONG, map->fr_file->fil_path); + mm->mm_grow_size = grow_size; + + if (mm->mm_length < (off_t) grow_size) { + mm->mm_length = (off_t) grow_size; + if (!fs_map_file(mm, map->fr_file, TRUE)) + xt_throw(self); + } + else { + if (!fs_map_file(mm, map->fr_file, FALSE)) + xt_throw(self); + } + + popr_(); // Discard fs_close_fmap(mm) + map->fr_file->fil_memmap = mm; + } + freer_(); // xt_ht_unlock(fs_globals.fsg_open_files) + } + map->mf_memmap = map->fr_file->fil_memmap; + + popr_(); // Discard xt_close_fmap(map) + return map; +} + +xtPublic void xt_close_fmap(XTThreadPtr self, XTMapFilePtr map) +{ + if (map->fr_file) { + xt_fs_release_file(self, map->fr_file); + + xt_sl_lock(self, fs_globals.fsg_open_files); + pushr_(xt_sl_unlock, fs_globals.fsg_open_files); + + map->fr_file->fil_handle_count--; + if (!map->fr_file->fil_handle_count) + fs_free_file(self, NULL, &map->fr_file); + + freer_(); + + map->fr_file = NULL; + + + } + map->mf_memmap = NULL; + xt_free(self, map); +} + +xtPublic xtBool xt_close_fmap_ns(XTMapFilePtr map) +{ + XTThreadPtr self = xt_get_self(); + xtBool failed = FALSE; + + try_(a) { + xt_close_fmap(self, map); + } + catch_(a) { + failed = TRUE; + } + cont_(a); + return failed; +} + +static xtBool fs_remap_file(XTMapFilePtr map, off_t offset, size_t size, XTIOStatsPtr stat) +{ + off_t new_size = 0; + XTFileMemMapPtr mm = map->mf_memmap; + xtWord8 s; + + if (offset + (off_t) size > mm->mm_length) { + /* Expand the file: */ + new_size = (mm->mm_length + (off_t) mm->mm_grow_size) / (off_t) mm->mm_grow_size; + new_size *= mm->mm_grow_size; + while (new_size < offset + (off_t) size) + new_size += mm->mm_grow_size; + + if (sizeof(size_t) == 4 && new_size >= (off_t) 0xFFFFFFFF) { + xt_register_ixterr(XT_REG_CONTEXT, XT_ERR_FILE_TOO_LONG, xt_file_path(map)); + return FAILED; + } + } + else if (!mm->mm_start) + new_size = mm->mm_length; + + if (new_size) { + if (mm->mm_start) { + /* Flush & unmap: */ + stat->ts_flush_start = xt_trace_clock(); +#ifdef XT_WIN + if (!FlushViewOfFile(mm->mm_start, 0)) { + xt_register_ferrno(XT_REG_CONTEXT, fs_get_win_error(), xt_file_path(map)); + goto failed; + } + + if (!UnmapViewOfFile(mm->mm_start)) { + xt_register_ferrno(XT_REG_CONTEXT, fs_get_win_error(), xt_file_path(map)); + goto failed; + } +#else + if (msync( (char *)mm->mm_start, (size_t) mm->mm_length, MS_SYNC) == -1) { + xt_register_ferrno(XT_REG_CONTEXT, errno, xt_file_path(map)); + goto failed; + } + + /* Unmap: */ + if (munmap((caddr_t) mm->mm_start, (size_t) mm->mm_length) == -1) { + xt_register_ferrno(XT_REG_CONTEXT, errno, xt_file_path(map)); + goto failed; + } +#endif + s = stat->ts_flush_start; + stat->ts_flush_start = 0; + stat->ts_flush_time += xt_trace_clock() - s; + stat->ts_flush++; + } + mm->mm_start = NULL; +#ifdef XT_WIN + if (!CloseHandle(mm->mm_mapdes)) + return xt_register_ferrno(XT_REG_CONTEXT, fs_get_win_error(), xt_file_path(map)); + mm->mm_mapdes = NULL; +#endif + mm->mm_length = new_size; + + if (!fs_map_file(mm, map->fr_file, TRUE)) + return FAILED; + } + return OK; + + failed: + s = stat->ts_flush_start; + stat->ts_flush_start = 0; + stat->ts_flush_time += xt_trace_clock() - s; + return FAILED; +} + +xtPublic xtBool xt_pwrite_fmap(XTMapFilePtr map, off_t offset, size_t size, void *data, XTIOStatsPtr stat, XTThreadPtr thread) +{ + XTFileMemMapPtr mm = map->mf_memmap; + xtThreadID thd_id = thread->t_id; + +#ifdef DEBUG_TRACE_MAP_IO + xt_trace("/* %s */ pbxt_fmap_writ(\"%s\", %lu, %lu);\n", xt_trace_clock_diff(NULL), map->fr_file->fil_path, (u_long) offset, (u_long) size); +#endif + xt_rwmutex_slock(&mm->mm_lock, thd_id); + if (!mm->mm_start || offset + (off_t) size > mm->mm_length) { + xt_rwmutex_unlock(&mm->mm_lock, thd_id); + + xt_rwmutex_xlock(&mm->mm_lock, thd_id); + if (!fs_remap_file(map, offset, size, stat)) + goto failed; + } + +#ifdef XT_WIN + __try + { + memcpy(mm->mm_start + offset, data, size); + } + // GetExceptionCode()== EXCEPTION_IN_PAGE_ERROR ? EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH + __except(EXCEPTION_EXECUTE_HANDLER) + { + xt_register_ferrno(XT_REG_CONTEXT, GetExceptionCode(), xt_file_path(map)); + goto failed; + } +#else + memcpy(mm->mm_start + offset, data, size); +#endif + + xt_rwmutex_unlock(&mm->mm_lock, thd_id); + stat->ts_write += size; + return OK; + + failed: + xt_rwmutex_unlock(&mm->mm_lock, thd_id); + return FAILED; +} + +xtPublic xtBool xt_pread_fmap_4(XTMapFilePtr map, off_t offset, xtWord4 *value, XTIOStatsPtr stat, XTThreadPtr thread) +{ + XTFileMemMapPtr mm = map->mf_memmap; + xtThreadID thd_id = thread->t_id; + +#ifdef DEBUG_TRACE_MAP_IO + xt_trace("/* %s */ pbxt_fmap_read_4(\"%s\", %lu, 4);\n", xt_trace_clock_diff(NULL), map->fr_file->fil_path, (u_long) offset); +#endif + xt_rwmutex_slock(&mm->mm_lock, thd_id); + if (!mm->mm_start) { + xt_rwmutex_unlock(&mm->mm_lock, thd_id); + xt_rwmutex_xlock(&mm->mm_lock, thd_id); + if (!fs_remap_file(map, 0, 0, stat)) { + xt_rwmutex_unlock(&mm->mm_lock, thd_id); + return FAILED; + } + } + if (offset >= mm->mm_length) + *value = 0; + else { + xtWord1 *data; + + data = mm->mm_start + offset; +#ifdef XT_WIN + __try + { + *value = XT_GET_DISK_4(data); + // GetExceptionCode()== EXCEPTION_IN_PAGE_ERROR ? EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH + } + __except(EXCEPTION_EXECUTE_HANDLER) + { + xt_rwmutex_unlock(&mm->mm_lock, thd_id); + return xt_register_ferrno(XT_REG_CONTEXT, GetExceptionCode(), xt_file_path(map)); + } +#else + *value = XT_GET_DISK_4(data); +#endif + } + + xt_rwmutex_unlock(&mm->mm_lock, thd_id); + stat->ts_read += 4; + return OK; +} + +xtPublic xtBool xt_pread_fmap(XTMapFilePtr map, off_t offset, size_t size, size_t min_size, void *data, size_t *red_size, XTIOStatsPtr stat, XTThreadPtr thread) +{ + XTFileMemMapPtr mm = map->mf_memmap; + xtThreadID thd_id = thread->t_id; + size_t tfer; + +#ifdef DEBUG_TRACE_MAP_IO + xt_trace("/* %s */ pbxt_fmap_read(\"%s\", %lu, %lu);\n", xt_trace_clock_diff(NULL), map->fr_file->fil_path, (u_long) offset, (u_long) size); +#endif + /* NOTE!! The file map may already be locked, + * by a call to xt_lock_fmap_ptr()! + * + * This can occur during a sequential scan: + * xt_pread_fmap() Line 1330 + * XTTabCache::tc_read_direct() Line 361 + * XTTabCache::xt_tc_read() Line 220 + * xt_tab_get_rec_data() + * tab_visible() Line 2412 + * xt_tab_seq_next() Line 4068 + * + * And occurs during the following test: + * create table t1 ( a int not null, b int not null) ; + * --disable_query_log + * insert into t1 values (1,1),(2,2),(3,3),(4,4); + * let $1=19; + * set @d=4; + * while ($1) + * { + * eval insert into t1 select a+@d,b+@d from t1; + * eval set @d=@d*2; + * dec $1; + * } + * + * --enable_query_log + * alter table t1 add index i1(a); + * delete from t1 where a > 2000000; + * create table t2 like t1; + * insert into t2 select * from t1; + * + * As a result, the slock must be able to handle + * nested calls to lock/unlock. + */ + xt_rwmutex_slock(&mm->mm_lock, thd_id); + tfer = size; + if (!mm->mm_start) { + xt_rwmutex_unlock(&mm->mm_lock, thd_id); + xt_rwmutex_xlock(&mm->mm_lock, thd_id); + if (!fs_remap_file(map, 0, 0, stat)) { + xt_rwmutex_unlock(&mm->mm_lock, thd_id); + return FAILED; + } + } + if (offset >= mm->mm_length) + tfer = 0; + else { + if (mm->mm_length - offset < (off_t) tfer) + tfer = (size_t) (mm->mm_length - offset); +#ifdef XT_WIN + __try + { + memcpy(data, mm->mm_start + offset, tfer); + // GetExceptionCode()== EXCEPTION_IN_PAGE_ERROR ? EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH + } + __except(EXCEPTION_EXECUTE_HANDLER) + { + xt_rwmutex_unlock(&mm->mm_lock, thd_id); + return xt_register_ferrno(XT_REG_CONTEXT, GetExceptionCode(), xt_file_path(map)); + } +#else + memcpy(data, mm->mm_start + offset, tfer); +#endif + } + + xt_rwmutex_unlock(&mm->mm_lock, thd_id); + if (tfer < min_size) + return xt_register_ferrno(XT_REG_CONTEXT, ESPIPE, xt_file_path(map)); + + if (red_size) + *red_size = tfer; + stat->ts_read += tfer; + return OK; +} + +xtPublic xtBool xt_flush_fmap(XTMapFilePtr map, XTIOStatsPtr stat, XTThreadPtr thread) +{ + XTFileMemMapPtr mm = map->mf_memmap; + xtThreadID thd_id = thread->t_id; + xtWord8 s; + +#ifdef DEBUG_TRACE_MAP_IO + xt_trace("/* %s */ pbxt_fmap_sync(\"%s\");\n", xt_trace_clock_diff(NULL), map->fr_file->fil_path); +#endif + xt_rwmutex_slock(&mm->mm_lock, thd_id); + if (!mm->mm_start) { + xt_rwmutex_unlock(&mm->mm_lock, thd_id); + xt_rwmutex_xlock(&mm->mm_lock, thd_id); + if (!fs_remap_file(map, 0, 0, stat)) { + xt_rwmutex_unlock(&mm->mm_lock, thd_id); + return FAILED; + } + } + stat->ts_flush_start = xt_trace_clock(); +#ifdef XT_WIN + if (!FlushViewOfFile(mm->mm_start, 0)) { + xt_register_ferrno(XT_REG_CONTEXT, fs_get_win_error(), xt_file_path(map)); + goto failed; + } +#else + if (msync( (char *)mm->mm_start, (size_t) mm->mm_length, MS_SYNC) == -1) { + xt_register_ferrno(XT_REG_CONTEXT, errno, xt_file_path(map)); + goto failed; + } +#endif + xt_rwmutex_unlock(&mm->mm_lock, thd_id); + s = stat->ts_flush_start; + stat->ts_flush_start = 0; + stat->ts_flush_time += xt_trace_clock() - s; + stat->ts_flush++; + return OK; + + failed: + xt_rwmutex_unlock(&mm->mm_lock, thd_id); + s = stat->ts_flush_start; + stat->ts_flush_start = 0; + stat->ts_flush_time += xt_trace_clock() - s; + return FAILED; +} + +xtPublic xtWord1 *xt_lock_fmap_ptr(XTMapFilePtr map, off_t offset, size_t size, XTIOStatsPtr stat, XTThreadPtr XT_UNUSED(thread)) +{ + XTFileMemMapPtr mm = map->mf_memmap; + xtThreadID thd_id = thread->t_id; + + xt_rwmutex_slock(&mm->mm_lock, thd_id); + if (!mm->mm_start) { + xt_rwmutex_unlock(&mm->mm_lock, thd_id); + xt_rwmutex_xlock(&mm->mm_lock, thd_id); + if (!fs_remap_file(map, 0, 0, stat)) + goto failed; + } + if (offset >= mm->mm_length) + goto failed; + + if (offset + (off_t) size > mm->mm_length) + stat->ts_read += (u_int) (offset + (off_t) size - mm->mm_length); + else + stat->ts_read += size; + return mm->mm_start + offset; + + failed: + xt_rwmutex_unlock(&mm->mm_lock, thd_id); + return NULL; +} + +xtPublic void xt_unlock_fmap_ptr(XTMapFilePtr map, XTThreadPtr thread) +{ + xt_rwmutex_unlock(&map->mf_memmap->mm_lock, thread->t_id); +} + +/* ---------------------------------------------------------------------- + * Copy files/directories + */ + +static void fs_copy_file(XTThreadPtr self, char *from_path, char *to_path, void *copy_buf) +{ + XTOpenFilePtr from; + XTOpenFilePtr to; + off_t offset = 0; + size_t read_size= 0; + + from = xt_open_file(self, from_path, XT_FS_READONLY); + pushr_(xt_close_file, from); + to = xt_open_file(self, to_path, XT_FS_CREATE | XT_FS_MAKE_PATH); + pushr_(xt_close_file, to); + + for (;;) { + if (!xt_pread_file(from, offset, 16*1024, 0, copy_buf, &read_size, &self->st_statistics.st_x, self)) + xt_throw(self); + if (!read_size) + break; + if (!xt_pwrite_file(to, offset, read_size, copy_buf, &self->st_statistics.st_x, self)) + xt_throw(self); + offset += (off_t) read_size; + } + + freer_(); + freer_(); +} + +xtPublic void xt_fs_copy_file(XTThreadPtr self, char *from_path, char *to_path) +{ + void *buffer; + + buffer = xt_malloc(self, 16*1024); + pushr_(xt_free, buffer); + fs_copy_file(self, from_path, to_path, buffer); + freer_(); +} + +static void fs_copy_dir(XTThreadPtr self, char *from_path, char *to_path, void *copy_buf) +{ + XTOpenDirPtr od; + char *file; + + xt_add_dir_char(PATH_MAX, from_path); + xt_add_dir_char(PATH_MAX, to_path); + + pushsr_(od, xt_dir_close, xt_dir_open(self, from_path, NULL)); + while (xt_dir_next(self, od)) { + file = xt_dir_name(self, od); + if (*file == '.') + continue; +#ifdef XT_WIN + if (strcmp(file, "pbxt-lock") == 0) + continue; +#endif + xt_strcat(PATH_MAX, from_path, file); + xt_strcat(PATH_MAX, to_path, file); + if (xt_dir_is_file(self, od)) + fs_copy_file(self, from_path, to_path, copy_buf); + else + fs_copy_dir(self, from_path, to_path, copy_buf); + xt_remove_last_name_of_path(from_path); + xt_remove_last_name_of_path(to_path); + } + freer_(); + + xt_remove_dir_char(from_path); + xt_remove_dir_char(to_path); +} + +xtPublic void xt_fs_copy_dir(XTThreadPtr self, const char *from, const char *to) +{ + void *buffer; + char from_path[PATH_MAX]; + char to_path[PATH_MAX]; + + xt_strcpy(PATH_MAX, from_path, from); + xt_strcpy(PATH_MAX, to_path, to); + + buffer = xt_malloc(self, 16*1024); + pushr_(xt_free, buffer); + fs_copy_dir(self, from_path, to_path, buffer); + freer_(); +} + diff --git a/storage/pbxt/src/filesys_xt.h b/storage/pbxt/src/filesys_xt.h new file mode 100644 index 00000000000..ebc4f474fc9 --- /dev/null +++ b/storage/pbxt/src/filesys_xt.h @@ -0,0 +1,167 @@ +/* Copyright (c) 2005 PrimeBase Technologies GmbH + * + * PrimeBase XT + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You 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 + * + * 2005-01-12 Paul McCullagh + * + * H&G2JCtL + */ +#ifndef __xt_filesys_h__ +#define __xt_filesys_h__ + +#ifdef XT_WIN +#include <time.h> +#else +#include <sys/time.h> +#include <dirent.h> +#endif +#include <sys/stat.h> + +#include "xt_defs.h" +#include "lock_xt.h" + +#ifdef XT_WIN +#define XT_FILE_IN_USE(x) ((x) == ERROR_SHARING_VIOLATION) +#define XT_FILE_ACCESS_DENIED(x) ((x) == ERROR_ACCESS_DENIED || (x) == ERROR_NETWORK_ACCESS_DENIED) +#define XT_FILE_TOO_MANY_OPEN(x) ((x) == ERROR_TOO_MANY_OPEN_FILES) +#define XT_FILE_NOT_FOUND(x) ((x) == ERROR_FILE_NOT_FOUND || (x) == ERROR_PATH_NOT_FOUND) +#else +#define XT_FILE_IN_USE(x) ((x) == ETXTBSY) +#define XT_FILE_ACCESS_DENIED(x) ((x) == EACCES) +#define XT_FILE_TOO_MANY_OPEN(x) ((x) == EMFILE) +#define XT_FILE_NOT_FOUND(x) ((x) == ENOENT) +#endif + +struct XTOpenFile; + +#define XT_MASK ((S_IRUSR | S_IWUSR) | (S_IRGRP | S_IWGRP) | (S_IROTH)) + +#define XT_FS_DEFAULT 0 /* Open for read/write, error if does not exist. */ +#define XT_FS_READONLY 1 /* Open for read only (otherwize read/write). */ +#define XT_FS_CREATE 2 /* Create if the file does not exist. */ +#define XT_FS_EXCLUSIVE 4 /* Create, and generate an error if it already exists. */ +#define XT_FS_MISSING_OK 8 /* Set this flag if you don't want to throw an error if the file does not exist! */ +#define XT_FS_MAKE_PATH 16 /* Create the path if it does not exist. */ +#define XT_FS_DIRECT_IO 32 /* Use direct I/O on this file if possible (O_DIRECT). */ + +xtBool xt_fs_exists(char *path); +xtBool xt_fs_delete(struct XTThread *self, char *path); +xtBool xt_fs_file_not_found(int err); +void xt_fs_mkdir(struct XTThread *self, char *path); +void xt_fs_mkpath(struct XTThread *self, char *path); +xtBool xt_fs_rmdir(struct XTThread *self, char *path); +xtBool xt_fs_stat(struct XTThread *self, char *path, off_t *size, struct timespec *mod_time); +void xt_fs_move(struct XTThread *self, char *from_path, char *to_path); +xtBool xt_fs_rename(struct XTThread *self, char *from_path, char *to_path); + +#ifdef XT_WIN +#define XT_FD HANDLE +#define XT_NULL_FD INVALID_HANDLE_VALUE +#else +#define XT_FD int +#define XT_NULL_FD (-1) +#endif + +typedef struct XTFileMemMap { + xtWord1 *mm_start; /* The in-memory start of the map. */ +#ifdef XT_WIN + HANDLE mm_mapdes; +#endif + off_t mm_length; /* The length of the file map. */ + XTRWMutexRec mm_lock; /* The file map R/W lock. */ + size_t mm_grow_size; /* The amount by which the map file is increased. */ +} XTFileMemMapRec, *XTFileMemMapPtr; + +typedef struct XTFile { + u_int fil_ref_count; /* The number of open file structure referencing this file. */ + char *fil_path; + u_int fil_id; /* This is used by the disk cache to identify a file in the hash index. */ + XT_FD fil_filedes; /* The shared file descriptor (pread and pwrite allow this), on Windows this is used only for mmapped files */ + u_int fil_handle_count; /* Number of references in the case of mmapped fil_filedes, both Windows and Unix */ + XTFileMemMapPtr fil_memmap; /* Non-null if this file is memory mapped. */ +} XTFileRec, *XTFilePtr; + +typedef struct XTFileRef { + XTFilePtr fr_file; + u_int fr_id; /* Copied from above (small optimisation). */ +} XTFileRefRec, *XTFileRefPtr; + +typedef struct XTOpenFile : public XTFileRef { + XT_FD of_filedes; +} XTOpenFileRec, *XTOpenFilePtr; + +void xt_fs_init(struct XTThread *self); +void xt_fs_exit(struct XTThread *self); + +XTFilePtr xt_fs_get_file(struct XTThread *self, char *file_name); +void xt_fs_release_file(struct XTThread *self, XTFilePtr file_ptr); + +XTOpenFilePtr xt_open_file(struct XTThread *self, char *file, int mode); +XTOpenFilePtr xt_open_file_ns(char *file, int mode); +xtBool xt_open_file_ns(XTOpenFilePtr *fh, char *file, int mode); +void xt_close_file(struct XTThread *self, XTOpenFilePtr f); +xtBool xt_close_file_ns(XTOpenFilePtr f); +char *xt_file_path(struct XTFileRef *of); + +xtBool xt_lock_file(struct XTThread *self, XTOpenFilePtr of); +void xt_unlock_file(struct XTThread *self, XTOpenFilePtr of); + +off_t xt_seek_eof_file(struct XTThread *self, XTOpenFilePtr of); +xtBool xt_set_eof_file(struct XTThread *self, XTOpenFilePtr of, off_t offset); + +xtBool xt_pwrite_file(XTOpenFilePtr of, off_t offset, size_t size, void *data, struct XTIOStats *timer, struct XTThread *thread); +xtBool xt_pread_file(XTOpenFilePtr of, off_t offset, size_t size, size_t min_size, void *data, size_t *red_size, struct XTIOStats *timer, struct XTThread *thread); +xtBool xt_flush_file(XTOpenFilePtr of, struct XTIOStats *timer, struct XTThread *thread); + +typedef struct XTOpenDir { + char *od_path; +#ifdef XT_WIN + HANDLE od_handle; + WIN32_FIND_DATA od_data; +#else + char *od_filter; + struct dirent od_entry; + DIR *od_dir; +#endif +} XTOpenDirRec, *XTOpenDirPtr; + +XTOpenDirPtr xt_dir_open(struct XTThread *self, c_char *path, c_char *filter); +void xt_dir_close(struct XTThread *self, XTOpenDirPtr od); +xtBool xt_dir_next(struct XTThread *self, XTOpenDirPtr od); +char *xt_dir_name(struct XTThread *self, XTOpenDirPtr od); +xtBool xt_dir_is_file(struct XTThread *self, XTOpenDirPtr od); +off_t xt_dir_file_size(struct XTThread *self, XTOpenDirPtr od); + +typedef struct XTMapFile : public XTFileRef { + XTFileMemMapPtr mf_memmap; +} XTMapFileRec, *XTMapFilePtr; + +XTMapFilePtr xt_open_fmap(struct XTThread *self, char *file, size_t grow_size); +void xt_close_fmap(struct XTThread *self, XTMapFilePtr map); +xtBool xt_close_fmap_ns(XTMapFilePtr map); +xtBool xt_pwrite_fmap(XTMapFilePtr map, off_t offset, size_t size, void *data, struct XTIOStats *timer, struct XTThread *thread); +xtBool xt_pread_fmap(XTMapFilePtr map, off_t offset, size_t size, size_t min_size, void *data, size_t *red_size, struct XTIOStats *timer, struct XTThread *thread); +xtBool xt_pread_fmap_4(XTMapFilePtr map, off_t offset, xtWord4 *value, struct XTIOStats *timer, struct XTThread *thread); +xtBool xt_flush_fmap(XTMapFilePtr map, struct XTIOStats *stat, struct XTThread *thread); +xtWord1 *xt_lock_fmap_ptr(XTMapFilePtr map, off_t offset, size_t size, struct XTIOStats *timer, struct XTThread *thread); +void xt_unlock_fmap_ptr(XTMapFilePtr map, struct XTThread *thread); + +void xt_fs_copy_file(struct XTThread *self, char *from_path, char *to_path); +void xt_fs_copy_dir(struct XTThread *self, const char *from, const char *to); + +#endif + diff --git a/storage/pbxt/src/ha_pbxt.cc b/storage/pbxt/src/ha_pbxt.cc new file mode 100644 index 00000000000..86cea271e0d --- /dev/null +++ b/storage/pbxt/src/ha_pbxt.cc @@ -0,0 +1,5417 @@ +/* Copyright (c) 2005 PrimeBase Technologies GmbH + * + * Derived from ha_example.h + * Copyright (C) 2003 MySQL AB + * + * PrimeBase XT + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You 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 + * + * 2005-11-10 Paul McCullagh + * + */ + +#ifdef USE_PRAGMA_IMPLEMENTATION +#pragma implementation // gcc: Class implementation +#endif + +#include "xt_config.h" + +#if defined(XT_WIN) +#include <windows.h> +#endif + +#include <stdlib.h> +#include <time.h> + +#ifdef DRIZZLED +#include <drizzled/common.h> +#include <drizzled/plugin.h> +#include <mysys/my_alloc.h> +#include <mysys/hash.h> +#include <drizzled/field.h> +#include <drizzled/current_session.h> +#include <drizzled/data_home.h> +#include <drizzled/error.h> +#include <drizzled/table.h> +#include <drizzled/field/timestamp.h> +#include <drizzled/server_includes.h> +extern "C" char **session_query(Session *session); +#define my_strdup(a,b) strdup(a) +#else +#include "mysql_priv.h" +#include <mysql/plugin.h> +#endif + +#include "ha_pbxt.h" +#include "ha_xtsys.h" + +#include "strutil_xt.h" +#include "database_xt.h" +#include "cache_xt.h" +#include "trace_xt.h" +#include "heap_xt.h" +#include "myxt_xt.h" +#include "datadic_xt.h" +#ifdef XT_STREAMING +#include "streaming_xt.h" +#endif +#include "tabcache_xt.h" +#include "systab_xt.h" +#include "xaction_xt.h" + +#ifdef DEBUG +//#define XT_USE_SYS_PAR_DEBUG_SIZES +//#define PBXT_HANDLER_TRACE +//#define PBXT_TRACE_RETURN +//#define XT_PRINT_INDEX_OPT +//#define XT_SHOW_DUMPS_TRACE +//#define XT_UNIT_TEST +//#define LOAD_TABLE_ON_OPEN +//#define CHECK_TABLE_LOADS + +/* Enable to trace the statements executed by the engine: */ +//#define TRACE_STATEMENTS + +/* Enable to print the trace to the stdout, instead of + * to the trace log. + */ +//#define PRINT_STATEMENTS +#endif + +static handler *pbxt_create_handler(handlerton *hton, TABLE_SHARE *table, MEM_ROOT *mem_root); +static int pbxt_init(void *p); +static int pbxt_end(void *p); +#ifndef DRIZZLED +static int pbxt_panic(handlerton *hton, enum ha_panic_function flag); +#endif +static void pbxt_drop_database(handlerton *hton, char *path); +static int pbxt_close_connection(handlerton *hton, THD* thd); +static int pbxt_commit(handlerton *hton, THD *thd, bool all); +static int pbxt_rollback(handlerton *hton, THD *thd, bool all); +static void ha_aquire_exclusive_use(XTThreadPtr self, XTSharePtr share, ha_pbxt *mine); +static void ha_release_exclusive_use(XTThreadPtr self, XTSharePtr share); +static void ha_close_open_tables(XTThreadPtr self, XTSharePtr share, ha_pbxt *mine); + +#ifdef TRACE_STATEMENTS + +#ifdef PRINT_STATEMENTS +#define STAT_TRACE(y, x) printf("%s: %s\n", y ? y->t_name : "-unknown-", x) +#else +#define STAT_TRACE(y, x) xt_ttraceq(y, x) +#endif + +#else + +#define STAT_TRACE(y, x) + +#endif + +#ifdef PBXT_HANDLER_TRACE +#define PBXT_ALLOW_PRINTING + +#define XT_TRACE_CALL() do { XTThreadPtr s = xt_get_self(); printf("%s %s\n", s ? s->t_name : "-unknown-", __FUNC__); } while (0) +#ifdef PBXT_TRACE_RETURN +#define XT_RETURN(x) do { printf("%d\n", (int) (x)); return (x); } while (0) +#define XT_RETURN_VOID do { printf("out\n"); return; } while (0) +#else +#define XT_RETURN(x) return (x) +#define XT_RETURN_VOID return +#endif + +#else + +#define XT_TRACE_CALL() +#define XT_RETURN(x) return (x) +#define XT_RETURN_VOID return + +#endif + +#ifdef PBXT_ALLOW_PRINTING +#define XT_PRINT0(y, x) do { XTThreadPtr s = (y); printf("%s " x, s ? s->t_name : "-unknown-"); } while (0) +#define XT_PRINT1(y, x, a) do { XTThreadPtr s = (y); printf("%s " x, s ? s->t_name : "-unknown-", a); } while (0) +#define XT_PRINT2(y, x, a, b) do { XTThreadPtr s = (y); printf("%s " x, s ? s->t_name : "-unknown-", a, b); } while (0) +#define XT_PRINT3(y, x, a, b, c) do { XTThreadPtr s = (y); printf("%s " x, s ? s->t_name : "-unknown-", a, b, c); } while (0) +#else +#define XT_PRINT0(y, x) +#define XT_PRINT1(y, x, a) +#define XT_PRINT2(y, x, a, b) +#define XT_PRINT3(y, x, a, b, c) +#endif + + +#define TS(x) (x)->s + +handlerton *pbxt_hton; +bool pbxt_inited = false; // Variable for checking the init state of hash +xtBool pbxt_ignore_case = true; +const char *pbxt_extensions[]= { ".xtr", ".xtd", ".xtl", ".xti", ".xt", "", NULL }; +#ifdef XT_CRASH_DEBUG +xtBool pbxt_crash_debug = TRUE; +#else +xtBool pbxt_crash_debug = FALSE; +#endif + +/* Variables for pbxt share methods */ +static xt_mutex_type pbxt_database_mutex; // Prevent a database from being opened while it is being dropped +static XTHashTabPtr pbxt_share_tables; // Hash used to track open tables +static XTDatabaseHPtr pbxt_database = NULL; // The global open database +static char *pbxt_index_cache_size; +static char *pbxt_record_cache_size; +static char *pbxt_log_cache_size; +static char *pbxt_log_file_threshold; +static char *pbxt_transaction_buffer_size; +static char *pbxt_log_buffer_size; +static char *pbxt_checkpoint_frequency; +static char *pbxt_data_log_threshold; +static char *pbxt_data_file_grow_size; +static char *pbxt_row_file_grow_size; + +#ifdef DEBUG +#define XT_SHARE_LOCK_WAIT 5000 +#else +#define XT_SHARE_LOCK_WAIT 500 +#endif + +/* + * Lock timeout in 1/1000ths of a second + */ +#define XT_SHARE_LOCK_TIMEOUT 30000 + +/* + * ----------------------------------------------------------------------- + * SYSTEM VARIABLES + * + */ + +//#define XT_FOR_TEAMDRIVE + +typedef struct HAVarParams { + const char *vp_var; /* Variable name. */ + const char *vp_def; /* Default value. */ + const char *vp_min; /* Minimum allowed value. */ + const char *vp_max4; /* Maximum allowed value on 32-bit processors. */ + const char *vp_max8; /* Maximum allowed value on 64-bit processors. */ +} HAVarParamsRec, *HAVarParamsPtr; + +#ifdef XT_USE_SYS_PAR_DEBUG_SIZES +static HAVarParamsRec vp_index_cache_size = { "pbxt_index_cache_size", "32MB", "8MB", "2GB", "2000GB" }; +static HAVarParamsRec vp_record_cache_size = { "pbxt_record_cache_size", "32MB", "8MB", "2GB", "2000GB" }; +static HAVarParamsRec vp_log_cache_size = { "pbxt_log_cache_size", "16MB", "4MB", "2GB", "2000GB" }; +static HAVarParamsRec vp_checkpoint_frequency = { "pbxt_checkpoint_frequency", "28MB", "512K", "1GB", "24GB" }; +static HAVarParamsRec vp_log_file_threshold = { "pbxt_log_file_threshold", "32MB", "1MB", "2GB", "256TB" }; +static HAVarParamsRec vp_transaction_buffer_size = { "pbxt_transaction_buffer_size", "1MB", "128K", "1GB", "24GB" }; +static HAVarParamsRec vp_log_buffer_size = { "pbxt_log_buffer_size", "256K", "128K", "1GB", "24GB" }; +static HAVarParamsRec vp_data_log_threshold = { "pbxt_data_log_threshold", "400K", "400K", "2GB", "256TB" }; +static HAVarParamsRec vp_data_file_grow_size = { "pbxt_data_file_grow_size", "2MB", "128K", "1GB", "2GB" }; +static HAVarParamsRec vp_row_file_grow_size = { "pbxt_row_file_grow_size", "256K", "32K", "1GB", "2GB" }; +#define XT_DL_DEFAULT_XLOG_COUNT 3 +#define XT_DL_DEFAULT_GARBAGE_LEVEL 10 +#else +static HAVarParamsRec vp_index_cache_size = { "pbxt_index_cache_size", "32MB", "8MB", "2GB", "2000GB" }; +static HAVarParamsRec vp_record_cache_size = { "pbxt_record_cache_size", "32MB", "8MB", "2GB", "2000GB" }; +static HAVarParamsRec vp_log_cache_size = { "pbxt_log_cache_size", "16MB", "4MB", "2GB", "2000GB" }; +static HAVarParamsRec vp_checkpoint_frequency = { "pbxt_checkpoint_frequency", "28MB", "512K", "1GB", "24GB" }; +static HAVarParamsRec vp_log_file_threshold = { "pbxt_log_file_threshold", "32MB", "1MB", "2GB", "256TB" }; +static HAVarParamsRec vp_transaction_buffer_size = { "pbxt_transaction_buffer_size", "1MB", "128K", "1GB", "24GB" }; +static HAVarParamsRec vp_log_buffer_size = { "pbxt_log_buffer_size", "256K", "128K", "1GB", "24GB" }; +static HAVarParamsRec vp_data_log_threshold = { "pbxt_data_log_threshold", "64MB", "1MB", "2GB", "256TB" }; +static HAVarParamsRec vp_data_file_grow_size = { "pbxt_data_file_grow_size", "2MB", "128K", "1GB", "2GB" }; +static HAVarParamsRec vp_row_file_grow_size = { "pbxt_row_file_grow_size", "256K", "32K", "1GB", "2GB" }; +#define XT_DL_DEFAULT_XLOG_COUNT 3 +#define XT_DL_DEFAULT_GARBAGE_LEVEL 50 +#endif + +#define XT_AUTO_INCREMENT_DEF 0 + +#ifdef XT_MAC +#ifdef DEBUG +/* For debugging on the Mac, we check the re-use logs: */ +#define XT_OFFLINE_LOG_FUNCTION_DEF XT_RECYCLE_LOGS +#else +#define XT_OFFLINE_LOG_FUNCTION_DEF XT_DELETE_LOGS +#endif +#else +#define XT_OFFLINE_LOG_FUNCTION_DEF XT_RECYCLE_LOGS +#endif + +/* TeamDrive, uses special auto-increment, and + * we keep the logs for the moment: + */ +#ifdef XT_FOR_TEAMDRIVE +#undef XT_OFFLINE_LOG_FUNCTION_DEF +#define XT_OFFLINE_LOG_FUNCTION_DEF XT_KEEP_LOGS +//#undef XT_AUTO_INCREMENT_DEF +//#define XT_AUTO_INCREMENT_DEF 1 +#endif + +/* + * ----------------------------------------------------------------------- + * SHARED TABLE DATA + * + */ + +static xtBool ha_hash_comp(void *key, void *data) +{ + XTSharePtr share = (XTSharePtr) data; + + return strcmp((char *) key, share->sh_table_path->ps_path) == 0; +} + +static xtHashValue ha_hash(xtBool is_key, void *key_data) +{ + XTSharePtr share = (XTSharePtr) key_data; + + if (is_key) + return xt_ht_hash((char *) key_data); + return xt_ht_hash(share->sh_table_path->ps_path); +} + +static xtBool ha_hash_comp_ci(void *key, void *data) +{ + XTSharePtr share = (XTSharePtr) data; + + return strcasecmp((char *) key, share->sh_table_path->ps_path) == 0; +} + +static xtHashValue ha_hash_ci(xtBool is_key, void *key_data) +{ + XTSharePtr share = (XTSharePtr) key_data; + + if (is_key) + return xt_ht_casehash((char *) key_data); + return xt_ht_casehash(share->sh_table_path->ps_path); +} + +static void ha_open_share(XTThreadPtr self, XTShareRec *share, xtBool *tabled_opened) +{ + xt_lock_mutex(self, (xt_mutex_type *) share->sh_ex_mutex); + pushr_(xt_unlock_mutex, share->sh_ex_mutex); + + if (!share->sh_table) { + share->sh_table = xt_use_table(self, share->sh_table_path, FALSE, FALSE, tabled_opened); + share->sh_dic_key_count = share->sh_table->tab_dic.dic_key_count; + share->sh_dic_keys = share->sh_table->tab_dic.dic_keys; + share->sh_recalc_selectivity = FALSE; + } + + freer_(); // xt_ht_unlock(pbxt_share_tables) +} + +static void ha_close_share(XTThreadPtr self, XTShareRec *share) +{ + XTTableHPtr tab; + + if ((tab = share->sh_table)) { + /* Save this, in case the share is re-opened. */ + share->sh_min_auto_inc = tab->tab_auto_inc; + + xt_heap_release(self, tab); + share->sh_table = NULL; + } + + /* This are only references: */ + share->sh_dic_key_count = 0; + share->sh_dic_keys = NULL; +} + +static void ha_cleanup_share(XTThreadPtr self, XTSharePtr share) +{ + ha_close_share(self, share); + + if (share->sh_table_path) { + xt_free(self, share->sh_table_path); + share->sh_table_path = NULL; + } + + if (share->sh_ex_cond) { + thr_lock_delete(&share->sh_lock); + xt_delete_cond(self, (xt_cond_type *) share->sh_ex_cond); + share->sh_ex_cond = NULL; + } + + if (share->sh_ex_mutex) { + xt_delete_mutex(self, (xt_mutex_type *) share->sh_ex_mutex); + share->sh_ex_mutex = NULL; + } + + xt_free(self, share); +} + +static void ha_hash_free(XTThreadPtr self, void *data) +{ + XTSharePtr share = (XTSharePtr) data; + + ha_cleanup_share(self, share); +} + +/* + * This structure contains information that is common to all handles. + * (i.e. it is table specific). + */ +static XTSharePtr ha_get_share(XTThreadPtr self, const char *table_path, bool open_table, xtBool *tabled_opened) +{ + XTShareRec *share; + + enter_(); + xt_ht_lock(self, pbxt_share_tables); + pushr_(xt_ht_unlock, pbxt_share_tables); + + // Check if the table exists... + if (!(share = (XTSharePtr) xt_ht_get(self, pbxt_share_tables, (void *) table_path))) { + share = (XTSharePtr) xt_calloc(self, sizeof(XTShareRec)); + pushr_(ha_cleanup_share, share); + + share->sh_ex_mutex = (xt_mutex_type *) xt_new_mutex(self); + share->sh_ex_cond = (xt_cond_type *) xt_new_cond(self); + + thr_lock_init(&share->sh_lock); + + share->sh_use_count = 0; + share->sh_table_path = (XTPathStrPtr) xt_dup_string(self, table_path); + + if (open_table) + ha_open_share(self, share, tabled_opened); + + popr_(); // Discard ha_cleanup_share(share); + + xt_ht_put(self, pbxt_share_tables, share); + } + + share->sh_use_count++; + freer_(); // xt_ht_unlock(pbxt_share_tables) + + return_(share); +} + +/* + * Free shared information. + */ +static void ha_unget_share(XTThreadPtr self, XTSharePtr share) +{ + xt_ht_lock(self, pbxt_share_tables); + pushr_(xt_ht_unlock, pbxt_share_tables); + + if (!--share->sh_use_count) + xt_ht_del(self, pbxt_share_tables, share->sh_table_path); + + freer_(); // xt_ht_unlock(pbxt_share_tables) +} + +static xtBool ha_unget_share_removed(XTThreadPtr self, XTSharePtr share) +{ + xtBool removed = FALSE; + + xt_ht_lock(self, pbxt_share_tables); + pushr_(xt_ht_unlock, pbxt_share_tables); + + if (!--share->sh_use_count) { + removed = TRUE; + xt_ht_del(self, pbxt_share_tables, share->sh_table_path); + } + + freer_(); // xt_ht_unlock(pbxt_share_tables) + return removed; +} + +/* + * ----------------------------------------------------------------------- + * PUBLIC FUNCTIONS + * + */ + +xtPublic void xt_ha_unlock_table(XTThreadPtr self, void *share) +{ + ha_release_exclusive_use(self, (XTSharePtr) share); + ha_unget_share(self, (XTSharePtr) share); +} + +xtPublic void xt_ha_close_global_database(XTThreadPtr self) +{ + if (pbxt_database) { + xt_heap_release(self, pbxt_database); + pbxt_database = NULL; + } +} + +/* + * Open a PBXT database given the path of a table. + * This function also returns the name of the table. + * + * We use the pbxt_database_mutex to lock this + * operation to make sure it does not occur while + * some other thread is doing a "closeall". + */ +xtPublic void xt_ha_open_database_of_table(XTThreadPtr self, XTPathStrPtr table_path __attribute__((unused))) +{ +#ifdef XT_USE_GLOBAL_DB + if (!self->st_database) { + if (!pbxt_database) { + xt_open_database(self, mysql_real_data_home, TRUE); + pbxt_database = self->st_database; + xt_heap_reference(self, pbxt_database); + } + else + xt_use_database(self, pbxt_database, XT_FOR_USER); + } +#else + char db_path[PATH_MAX]; + + xt_strcpy(PATH_MAX, db_path, (char *) table_path); + xt_remove_last_name_of_path(db_path); + xt_remove_dir_char(db_path); + + if (self->st_database && xt_tab_compare_paths(self->st_database->db_name, xt_last_name_of_path(db_path)) == 0) + /* This thread already has this database open! */ + return; + + /* Auto commit before changing the database: */ + if (self->st_xact_data) { + /* PMC - This probably indicates something strange is happening: + * + * This sequence generates this error: + * + * delimiter | + * + * create temporary table t3 (id int)| + * + * create function f10() returns int + * begin + * drop temporary table if exists t3; + * create temporary table t3 (id int) engine=myisam; + * insert into t3 select id from t4; + * return (select count(*) from t3); + * end| + * + * select f10()| + * + * An error is generated because the same thread is used + * to open table t4 (at the start of the functions), and + * then to drop table t3. To drop t3 we need to + * switch the database, so we land up here! + */ + xt_throw_xterr(XT_CONTEXT, XT_ERR_CANNOT_CHANGE_DB); + /* + if (!xt_xn_commit(self)) + throw_(); + */ + } + + xt_lock_mutex(self, &pbxt_database_mutex); + pushr_(xt_unlock_mutex, &pbxt_database_mutex); + xt_open_database(self, db_path, FALSE); + freer_(); // xt_unlock_mutex(&pbxt_database_mutex); +#endif +} + +xtPublic XTThreadPtr xt_ha_set_current_thread(THD *thd, XTExceptionPtr e) +{ + XTThreadPtr self; + static int ha_thread_count = 0, ha_id; + + if (!(self = (XTThreadPtr) *thd_ha_data(thd, pbxt_hton))) { +// const Security_context *sctx; + char name[120]; + char ha_id_str[50]; + + ha_id = ++ha_thread_count; + sprintf(ha_id_str, "_%d", ha_id); + xt_strcpy(120,name,"user"); // TODO: Fix this hack +/* + sctx = &thd->main_security_ctx; + + if (sctx->user) { + xt_strcpy(120, name, sctx->user); + xt_strcat(120, name, "@"); + } + else + *name = 0; + if (sctx->host) + xt_strcat(120, name, sctx->host); + else if (sctx->ip) + xt_strcat(120, name, sctx->ip); + else if (thd->proc_info) + xt_strcat(120, name, (char *) thd->proc_info); + else + xt_strcat(120, name, "system"); +*/ + xt_strcat(120, name, ha_id_str); + if (!(self = xt_create_thread(name, FALSE, TRUE, e))) + return NULL; + + self->st_xact_mode = XT_XACT_REPEATABLE_READ; + + *thd_ha_data(thd, pbxt_hton) = (void *) self; + } + return self; +} + +xtPublic void xt_ha_close_connection(THD* thd) +{ + XTThreadPtr self; + + if ((self = (XTThreadPtr) *thd_ha_data(thd, pbxt_hton))) { + *thd_ha_data(thd, pbxt_hton) = NULL; + xt_free_thread(self); + } +} + +xtPublic XTThreadPtr xt_ha_thd_to_self(THD *thd) +{ + return (XTThreadPtr) *thd_ha_data(thd, pbxt_hton); +} + +/* The first bit is 1. */ +static u_int ha_get_max_bit(MY_BITMAP *map) +{ + my_bitmap_map *data_ptr = map->bitmap; + my_bitmap_map *end_ptr = map->last_word_ptr; + my_bitmap_map b; + u_int cnt = map->n_bits; + + for (; end_ptr >= data_ptr; end_ptr--) { + if ((b = *end_ptr)) { + my_bitmap_map mask; + + if (end_ptr == map->last_word_ptr && map->last_word_mask) + mask = map->last_word_mask >> 1; + else + mask = 0x80000000; + while (!(b & mask)) { + b = b << 1; + /* Should not happen, but if it does, we hang! */ + if (!b) + return map->n_bits; + cnt--; + } + return cnt; + } + if (end_ptr == map->last_word_ptr) + cnt = ((cnt-1) / 32) * 32; + else + cnt -= 32; + } + return 0; +} + +/* + * ----------------------------------------------------------------------- + * SUPPORT FUNCTIONS + * + */ + +/* + * In PBXT, as in MySQL: thread == connection. + * + * So we simply attach a PBXT thread to a MySQL thread. + */ +static XTThreadPtr ha_set_current_thread(THD *thd, int *err) +{ + XTThreadPtr self; + XTExceptionRec e; + + if (!(self = xt_ha_set_current_thread(thd, &e))) { + xt_log_exception(NULL, &e, XT_LOG_DEFAULT); + *err = e.e_xt_err; + return NULL; + } + return self; +} + +xtPublic int xt_ha_pbxt_to_mysql_error(int xt_err) +{ + switch (xt_err) { + case XT_NO_ERR: + return(0); + case XT_ERR_DUPLICATE_KEY: + return HA_ERR_FOUND_DUPP_KEY; + case XT_ERR_DEADLOCK: + return HA_ERR_LOCK_DEADLOCK; + case XT_ERR_RECORD_CHANGED: + /* If we generate HA_ERR_RECORD_CHANGED instead of HA_ERR_LOCK_WAIT_TIMEOUT + * then sysbench does not work because it does not handle this error. + */ + //return HA_ERR_LOCK_WAIT_TIMEOUT; // but HA_ERR_RECORD_CHANGED is the correct error for a optimistic lock failure. + return HA_ERR_RECORD_CHANGED; + case XT_ERR_LOCK_TIMEOUT: + return HA_ERR_LOCK_WAIT_TIMEOUT; + case XT_ERR_TABLE_IN_USE: + return HA_ERR_WRONG_COMMAND; + case XT_ERR_TABLE_NOT_FOUND: + return HA_ERR_NO_SUCH_TABLE; + case XT_ERR_TABLE_EXISTS: + return HA_ERR_TABLE_EXIST; + case XT_ERR_CANNOT_CHANGE_DB: + return ER_TRG_IN_WRONG_SCHEMA; + case XT_ERR_COLUMN_NOT_FOUND: + return HA_ERR_CANNOT_ADD_FOREIGN; + case XT_ERR_NO_REFERENCED_ROW: + case XT_ERR_REF_TABLE_NOT_FOUND: + case XT_ERR_REF_TYPE_WRONG: + return HA_ERR_NO_REFERENCED_ROW; + case XT_ERR_ROW_IS_REFERENCED: + return HA_ERR_ROW_IS_REFERENCED; + case XT_ERR_COLUMN_IS_NOT_NULL: + case XT_ERR_INCORRECT_NO_OF_COLS: + case XT_ERR_FK_ON_TEMP_TABLE: + case XT_ERR_FK_REF_TEMP_TABLE: + return HA_ERR_CANNOT_ADD_FOREIGN; + case XT_ERR_DUPLICATE_FKEY: + return HA_ERR_FOREIGN_DUPLICATE_KEY; + case XT_ERR_RECORD_DELETED: + return HA_ERR_RECORD_DELETED; + } + return(-1); // Unknown error +} + +xtPublic int xt_ha_pbxt_thread_error_for_mysql(THD *thd __attribute__((unused)), const XTThreadPtr self, int ignore_dup_key) +{ + int xt_err = self->t_exception.e_xt_err; + + XT_PRINT2(self, "xt_ha_pbxt_thread_error_for_mysql xt_err=%d auto commit=%d\n", (int) xt_err, (int) self->st_auto_commit); + switch (xt_err) { + case XT_NO_ERR: + break; + case XT_ERR_DUPLICATE_KEY: + case XT_ERR_DUPLICATE_FKEY: + /* Let MySQL call rollback as and when it wants to for duplicate + * key. + * + * In addition, we are not allowed to do an auto-rollback + * inside a sub-statement (function() or procedure()) + * For example: + * + * delimiter | + * + * create table t3 (c1 char(1) primary key not null)| + * + * create function bug12379() + * returns integer + * begin + * insert into t3 values('X'); + * insert into t3 values('X'); + * return 0; + * end| + * + * --error 1062 + * select bug12379()| + * + * + * Not doing an auto-rollback should solve this problem in the + * case of duplicate key (but not in others - like deadlock)! + * I don't think this situation is handled correctly by MySQL. + */ + + /* If we are in auto-commit mode (and we are not ignoring + * duplicate keys) then rollback the transaction automatically. + */ + if (!ignore_dup_key && self->st_auto_commit) + goto abort_transaction; + break; + case XT_ERR_DEADLOCK: + case XT_ERR_NO_REFERENCED_ROW: + case XT_ERR_ROW_IS_REFERENCED: + goto abort_transaction; + case XT_ERR_RECORD_CHANGED: + /* MySQL also handles the locked error. NOTE: There is no automatic + * rollback! + */ + break; + default: + xt_log_exception(self, &self->t_exception, XT_LOG_DEFAULT); + abort_transaction: + /* PMC 2006-08-30: It should be that this is not necessary! + * + * It is only necessary to call ha_rollback() if the engine + * aborts the transaction. + * + * On the other hand, I shouldn't need to rollback the + * transaction because, if I return an error, MySQL + * should do it for me. + * + * Unfortunately, when auto-commit is off, MySQL does not + * rollback automatically (for example when a deadlock + * is provoked). + * + * And when we have a multi update we cannot rely on this + * either (see comment above). + */ + if (self->st_xact_data) { + /* + * GOTCHA: + * A result of the "st_abort_trans = TRUE" below is that + * the following code results in an empty set. + * The reason is "ignore_dup_key" is not set so + * the duplicate key leads to an error which causes + * the transaction to be aborted. + * The delayed inserts are all execute in one transaction. + * + * CREATE TABLE t1 ( + * c1 INT(11) NOT NULL AUTO_INCREMENT, + * c2 INT(11) DEFAULT NULL, + * PRIMARY KEY (c1) + * ); + * SET insert_id= 14; + * INSERT DELAYED INTO t1 VALUES(NULL, 11), (NULL, 12); + * INSERT DELAYED INTO t1 VALUES(14, 91); + * INSERT DELAYED INTO t1 VALUES (NULL, 92), (NULL, 93); + * FLUSH TABLE t1; + * SELECT * FROM t1; + */ + if (self->st_lock_count == 0) { + /* No table locks, must rollback immediately + * (there will be no possibility later! + */ + XT_PRINT1(self, "xt_xn_rollback xt_err=%d\n", xt_err); + if (!xt_xn_rollback(self)) + xt_log_exception(self, &self->t_exception, XT_LOG_DEFAULT); + } + else { + /* Locks are held on tables. + * Only rollback after locks are released. + */ + self->st_auto_commit = TRUE; + self->st_abort_trans = TRUE; + } +#ifdef xxxx +/* DBUG_ASSERT(thd->transaction.stmt.ha_list == NULL || + trans == &thd->transaction.stmt); in handler.cc now + * fails, and I don't know if this function can be called anymore! */ + /* Cause any other DBs to do a rollback as well... */ + if (thd) { + /* + * GOTCHA: + * This is a BUG in MySQL. I cannot rollback a transaction if + * pb_mysql_thd->in_sub_stmt! But I must....?! + */ +#ifdef MYSQL_SERVER + if (!thd->in_sub_stmt) + ha_rollback(thd); +#endif + } +#endif + } + break; + } + return xt_ha_pbxt_to_mysql_error(xt_err); +} + +static void ha_conditional_close_database(XTThreadPtr self, XTThreadPtr other_thr, void *db) +{ + if (other_thr->st_database == (XTDatabaseHPtr) db) + xt_unuse_database(self, other_thr); +} + +/* + * This is only called from drop database, so we know that + * no thread is actually using the database. This means that it + * must be safe to close the database. + */ +xtPublic void xt_ha_all_threads_close_database(XTThreadPtr self, XTDatabaseHPtr db) +{ + xt_lock_mutex(self, &pbxt_database_mutex); + pushr_(xt_unlock_mutex, &pbxt_database_mutex); + xt_do_to_all_threads(self, ha_conditional_close_database, db); + freer_(); // xt_unlock_mutex(&pbxt_database_mutex); +} + +static int ha_log_pbxt_thread_error_for_mysql(int ignore_dup_key) +{ + return xt_ha_pbxt_thread_error_for_mysql(current_thd, myxt_get_self(), ignore_dup_key); +} + +/* + * ----------------------------------------------------------------------- + * STATIC HOOKS + * + */ +static xtWord8 ha_set_variable(char **value, HAVarParamsPtr vp) +{ + xtWord8 result; + xtWord8 mi, ma; + char *mm; + + if (!*value) + *value = getenv(vp->vp_var); + if (!*value) + *value = (char *) vp->vp_def; + result = xt_byte_size_to_int8(*value); + mi = (xtWord8) xt_byte_size_to_int8(vp->vp_min); + if (result < mi) { + result = mi; + *value = (char *) vp->vp_min; + } + if (sizeof(size_t) == 8) + mm = (char *) vp->vp_max8; + else + mm = (char *) vp->vp_max4; + ma = (xtWord8) xt_byte_size_to_int8(mm); + if (result > ma) { + result = ma; + *value = mm; + } + return result; +} + +static void pbxt_call_init(XTThreadPtr self) +{ + xtInt8 index_cache_size; + xtInt8 record_cache_size; + xtInt8 log_cache_size; + xtInt8 log_file_threshold; + xtInt8 transaction_buffer_size; + xtInt8 log_buffer_size; + xtInt8 checkpoint_frequency; + xtInt8 data_log_threshold; + xtInt8 data_file_grow_size; + xtInt8 row_file_grow_size; + + xt_logf(XT_NT_INFO, "PrimeBase XT (PBXT) Engine %s loaded...\n", xt_get_version()); + xt_logf(XT_NT_INFO, "Paul McCullagh, PrimeBase Technologies GmbH, http://www.primebase.org\n"); + + index_cache_size = ha_set_variable(&pbxt_index_cache_size, &vp_index_cache_size); + record_cache_size = ha_set_variable(&pbxt_record_cache_size, &vp_record_cache_size); + log_cache_size = ha_set_variable(&pbxt_log_cache_size, &vp_log_cache_size); + log_file_threshold = ha_set_variable(&pbxt_log_file_threshold, &vp_log_file_threshold); + transaction_buffer_size = ha_set_variable(&pbxt_transaction_buffer_size, &vp_transaction_buffer_size); + log_buffer_size = ha_set_variable(&pbxt_log_buffer_size, &vp_log_buffer_size); + checkpoint_frequency = ha_set_variable(&pbxt_checkpoint_frequency, &vp_checkpoint_frequency); + data_log_threshold = ha_set_variable(&pbxt_data_log_threshold, &vp_data_log_threshold); + data_file_grow_size = ha_set_variable(&pbxt_data_file_grow_size, &vp_data_file_grow_size); + row_file_grow_size = ha_set_variable(&pbxt_row_file_grow_size, &vp_row_file_grow_size); + + xt_db_log_file_threshold = (xtLogOffset) log_file_threshold; + xt_db_log_buffer_size = (size_t) xt_align_offset(log_buffer_size, 512); + xt_db_transaction_buffer_size = (size_t) xt_align_offset(transaction_buffer_size, 512); + xt_db_checkpoint_frequency = (size_t) checkpoint_frequency; + xt_db_data_log_threshold = (off_t) data_log_threshold; + xt_db_data_file_grow_size = (size_t) data_file_grow_size; + xt_db_row_file_grow_size = (size_t) row_file_grow_size; + + pbxt_ignore_case = lower_case_table_names != 0; + if (pbxt_ignore_case) + pbxt_share_tables = xt_new_hashtable(self, ha_hash_comp_ci, ha_hash_ci, ha_hash_free, TRUE, FALSE); + else + pbxt_share_tables = xt_new_hashtable(self, ha_hash_comp, ha_hash, ha_hash_free, TRUE, FALSE); + + xt_thread_wait_init(self); + xt_fs_init(self); + xt_lock_installation(self, mysql_real_data_home); + XTSystemTableShare::startUp(self); + xt_init_databases(self); + xt_ind_init(self, (size_t) index_cache_size); + xt_tc_init(self, (size_t) record_cache_size); + xt_xlog_init(self, (size_t) log_cache_size); +} + +static void pbxt_call_exit(XTThreadPtr self) +{ + xt_logf(XT_NT_INFO, "PrimeBase XT Engine shutdown...\n"); + +#ifdef TRACE_STATEMENTS + xt_dump_trace(); +#endif +#ifdef XT_USE_GLOBAL_DB + xt_ha_close_global_database(self); +#endif +#ifdef DEBUG + //xt_stop_database_threads(self, FALSE); + xt_stop_database_threads(self, TRUE); +#else + xt_stop_database_threads(self, TRUE); +#endif + /* This will tell the freeer to quit ASAP: */ + xt_quit_freeer(self); + /* We conditional stop the freeer here, because if we are + * in startup, then the free will be hanging. + * {FREEER-HANG} + * + * This problem has been solved by MySQL! + */ + xt_stop_freeer(self); + xt_exit_databases(self); + XTSystemTableShare::shutDown(self); + xt_xlog_exit(self); + xt_tc_exit(self); + xt_ind_exit(self); + xt_unlock_installation(self, mysql_real_data_home); + xt_fs_exit(self); + xt_thread_wait_exit(self); + if (pbxt_share_tables) { + xt_free_hashtable(self, pbxt_share_tables); + pbxt_share_tables = NULL; + } +} + +/* + * Shutdown the PBXT sub-system. + */ +static void ha_exit(XTThreadPtr self) +{ + /* Wrap things up... */ + xt_unuse_database(self, self); /* Just in case the main thread has a database in use (for testing)? */ + /* This may cause the streaming engine to cleanup connections and + * tables belonging to this engine. This in turn may require some of + * the stuff below (like xt_create_thread() called from pbxt_close_table()! */ +#ifdef XT_STREAMING + xt_exit_streaming(); +#endif + pbxt_call_exit(self); + xt_exit_threading(self); + xt_exit_memory(); + xt_exit_logging(); + xt_p_mutex_destroy(&pbxt_database_mutex); + pbxt_inited = false; +} + +/* + * Outout the PBXT status. Return FALSE on error. + */ +static bool pbxt_show_status(handlerton *hton __attribute__((unused)), THD* thd, + stat_print_fn* stat_print, + enum ha_stat_type stat_type __attribute__((unused))) +{ + XTThreadPtr self; + int err = 0; + XTStringBufferRec strbuf = { 0, 0, 0 }; + bool not_ok = FALSE; + + if (!(self = ha_set_current_thread(thd, &err))) + return FALSE; + +#ifdef XT_SHOW_DUMPS_TRACE + //if (pbxt_database) + // xt_dump_xlogs(pbxt_database, 0); + xt_trace("// %s - dump\n", xt_trace_clock_diff(NULL)); + xt_dump_trace(); +#endif + + try_(a) { + myxt_get_status(self, &strbuf); + } + catch_(a) { + not_ok = TRUE; + } + cont_(a); + + if (!not_ok) { + if (stat_print(thd, "PBXT", 4, "", 0, strbuf.sb_cstring, strbuf.sb_len)) + not_ok = TRUE; + } + xt_sb_set_size(self, &strbuf, 0); + + return not_ok; +} + +/* + * Initialize the PBXT sub-system. + * + * return 1 on error, else 0. + */ +static int pbxt_init(void *p) +{ + int init_err = 0; + + XT_TRACE_CALL(); + + if (sizeof(xtWordPS) != sizeof(void *)) { + printf("PBXT: This won't work, I require that sizeof(xtWordPS) != sizeof(void *)!\n"); + XT_RETURN(1); + } + + /* GOTCHA: This will "detect" if are loading the plug-in + * with different --with-debug option to MySQL. + * + * In this case, you will get an error when loading the + * library that some symbol was not found. + */ + void *dummy = my_malloc(100, MYF(0)); + my_free((byte *) dummy, MYF(0)); + + if (!pbxt_inited) { + XTThreadPtr self = NULL; + + xt_p_mutex_init_with_autoname(&pbxt_database_mutex, NULL); + + pbxt_hton = (handlerton *) p; + pbxt_hton->state = SHOW_OPTION_YES; +#ifndef DRIZZLED + pbxt_hton->db_type = DB_TYPE_PBXT; // Wow! I have my own! +#endif + pbxt_hton->close_connection = pbxt_close_connection; /* close_connection, cleanup thread related data. */ + pbxt_hton->commit = pbxt_commit; /* commit */ + pbxt_hton->rollback = pbxt_rollback; /* rollback */ + pbxt_hton->create = pbxt_create_handler; /* Create a new handler */ + pbxt_hton->drop_database = pbxt_drop_database; /* Drop a database */ +#ifndef DRIZZLED + pbxt_hton->panic = pbxt_panic; /* Panic call */ +#endif + pbxt_hton->show_status = pbxt_show_status; + pbxt_hton->flags = HTON_NO_FLAGS; /* HTON_CAN_RECREATE - Without this flags TRUNCATE uses delete_all_rows() */ + + if (!xt_init_logging()) /* Initialize logging */ + goto error_1; + +#ifdef XT_STREAMING + if (!xt_init_streaming()) + goto error_2; +#endif + + if (!xt_init_memory()) /* Initialize memory */ + goto error_3; + + /* +7 assumes: + * We are not using multiple database, and: + * +1 Main thread. + * +1 Compactor thread + * +1 Writer thread + * +1 Checkpointer thread + * +1 Sweeper thread + * +1 Free'er thread + * +1 Temporary thread (e.g. TempForClose, TempForEnd) + */ + self = xt_init_threading(max_connections + 7); /* Create the main self: */ + if (!self) + goto error_4; + + pbxt_inited = true; + + try_(a) { + /* Initialize all systems */ + pbxt_call_init(self); + + /* Conditional unit test: */ +#ifdef XT_UNIT_TEST + //xt_unit_test_create_threads(self); + xt_unit_test_read_write_locks(self); + //xt_unit_test_mutex_locks(self); +#endif + + /* {OPEN-DB-SWEEPER-WAIT} + * I have to start the freeer before I open and recover the database + * because it we run out of cache while waiting for the sweeper + * we will hang! + */ + xt_start_freeer(self); + +#ifdef XT_USE_GLOBAL_DB + /* Open the global database. */ + ASSERT(!pbxt_database); + { + THD *curr_thd = current_thd; + THD *thd = curr_thd; + +#ifndef DRIZZLED + extern myxt_mutex_t LOCK_plugin; + + /* {MYSQL QUIRK} + * I have to release this lock for PBXT recovery to + * work, because it needs to open .frm files. + * So, I unlock, but during INSTALL PLUGIN this is + * risky, because we are in multi-threaded + * mode! + * + * Although, as far as I can tell from the MySQL code, + * INSTALL PLUGIN should still work ok, during + * concurrent access, because we are not + * relying on pointer/memory that may be changed by + * other users. + * + * Only real problem, 2 threads try to load the same + * plugin at the same time. + */ + myxt_mutex_unlock(&LOCK_plugin); +#endif + + /* Can't do this here yet, because I need a THD! */ + try_(b) { + /* {MYSQL QUIRK} + * Sometime we have a THD, + * sometimes we don't. + * So far, I have noticed that during INSTALL PLUGIN, + * we have one, otherwize not. + */ + if (!curr_thd) { + if (!(thd = (THD *) myxt_create_thread())) + xt_throw(self); + } + + xt_open_database(self, mysql_real_data_home, TRUE); + pbxt_database = self->st_database; + xt_heap_reference(self, pbxt_database); + } + catch_(b) { + if (!curr_thd && thd) + myxt_destroy_thread(thd, FALSE); +#ifndef DRIZZLED + myxt_mutex_lock(&LOCK_plugin); +#endif + xt_throw(self); + } + cont_(b); + + if (!curr_thd) + myxt_destroy_thread(thd, FALSE); +#ifndef DRIZZLED + myxt_mutex_lock(&LOCK_plugin); +#endif + } +#endif + } + catch_(a) { + xt_log_exception(self, &self->t_exception, XT_LOG_DEFAULT); + init_err = 1; + } + cont_(a); + + if (init_err) { + /* {FREEER-HANG} The free-er will be hung in: + #0 0x91fc6a2e in semaphore_wait_signal_trap + #1 0x91fce505 in pthread_mutex_lock + #2 0x00489633 in safe_mutex_lock at thr_mutex.c:149 + #3 0x002dfca9 in plugin_thdvar_init at sql_plugin.cc:2398 + #4 0x000d6a12 in THD::init at sql_class.cc:715 + #5 0x000de9d3 in THD::THD at sql_class.cc:597 + #6 0x000debe1 in THD::THD at sql_class.cc:631 + #7 0x00e207a4 in myxt_create_thread at myxt_xt.cc:2666 + #8 0x00e3134b in tabc_fr_run_thread at tabcache_xt.cc:982 + #9 0x00e422ca in thr_main at thread_xt.cc:1006 + #10 0x91ff7c55 in _pthread_start + #11 0x91ff7b12 in thread_start + * + * so it is not good trying to stop it here! + * + * With regard to this problem, see {OPEN-DB-SWEEPER-WAIT} + * Due to this problem, I will probably have to hack + * the mutex so that the freeer can get started... + * + * NOPE! problem has gone in 6.0.9. Also not a problem in + * 5.1.29. + */ + + /* {OPEN-DB-SWEEPER-WAIT} + * I have to stop the freeer here because it was + * started before opening the database. + */ + pbxt_call_exit(self); + pbxt_inited = FALSE; + xt_exit_threading(self); + goto error_4; + } + xt_free_thread(self); + } + XT_RETURN(init_err); + + error_4: + xt_exit_memory(); + + error_3: +#ifdef XT_STREAMING + xt_exit_streaming(); + + error_2: +#endif + xt_exit_logging(); + + error_1: + xt_p_mutex_destroy(&pbxt_database_mutex); + XT_RETURN(1); +} + +static int pbxt_end(void *p __attribute__((unused))) +{ + XTThreadPtr self; + int err = 0; + + XT_TRACE_CALL(); + + if (pbxt_inited) { + XTExceptionRec e; + + /* This flag also means "shutting down". */ + pbxt_inited = FALSE; + self = xt_create_thread("TempForEnd", FALSE, TRUE, &e); + if (self) { + self->t_main = TRUE; + ha_exit(self); + } + } + + XT_RETURN(err); +} + +#ifndef DRIZZLED +static int pbxt_panic(handlerton *hton, enum ha_panic_function flag) +{ + return pbxt_end(hton); +} +#endif + +/* + * Kill the PBXT thread associated with the MySQL thread. + */ +static int pbxt_close_connection(handlerton *hton, THD* thd) +{ + XTThreadPtr self; +#ifdef XT_STREAMING + XTExceptionRec e; +#endif + + XT_TRACE_CALL(); + if ((self = (XTThreadPtr) *thd_ha_data(thd, hton))) { + *thd_ha_data(thd, hton) = NULL; + /* Required because freeing the thread could cause + * free of database which could call xt_close_file_ns()! + */ + xt_set_self(self); + xt_free_thread(self); + } +#ifdef XT_STREAMING + if (!xt_pbms_close_connection((void *) thd, &e)) + xt_log_exception(NULL, &e, XT_LOG_DEFAULT); +#endif + return 0; +} + +/* + * Currently does nothing because it was all done + * when the last PBXT table was removed from the + * database. + */ +static void pbxt_drop_database(handlerton *hton __attribute__((unused)), char *path __attribute__((unused))) +{ + XT_TRACE_CALL(); +} + +/* + * NOTES ON TRANSACTIONS: + * + * 1. If self->st_lock_count == 0 and transaction can be ended immediately. + * If not, we must wait until the last lock is released on the last handler + * to ensure that the tables are flushed before the transaction is + * committed or aborted. + * + * 2. all (below) indicates, within a BEGIN/END (i.e. auto_commit off) whether + * the statement or the entire transation is being terminated. + * We currently ignore statement termination. + * + * 3. If in BEGIN/END we must call ha_rollback() if we abort the transaction + * internally. + */ + +/* + * Commit the PBXT transaction of the given thread. + * thd is the MySQL thread structure. + * pbxt_thr is a pointer the the PBXT thread structure. + * + */ +static int pbxt_commit(handlerton *hton, THD *thd, bool all) +{ + int err = 0; + XTThreadPtr self; + + if ((self = (XTThreadPtr) *thd_ha_data(thd, hton))) { + XT_PRINT1(self, "pbxt_commit all=%d\n", all); + + if (self->st_xact_data) { + /* There are no table locks, commit immediately in all cases + * except when this is a statement commit with an explicit + * transaction (!all && !self->st_auto_commit). + */ + if (all || self->st_auto_commit) { + XT_PRINT0(self, "xt_xn_commit\n"); + + if (!xt_xn_commit(self)) + err = xt_ha_pbxt_thread_error_for_mysql(thd, self, FALSE); + } + } + if (!all) + self->st_stat_trans = FALSE; + } + return err; +} + +static int pbxt_rollback(handlerton *hton, THD *thd, bool all) +{ + int err = 0; + XTThreadPtr self; + + if ((self = (XTThreadPtr) *thd_ha_data(thd, hton))) { + XT_PRINT1(self, "pbxt_rollback all=%d\n", all); + + if (self->st_xact_data) { + /* There are no table locks, rollback immediately in all cases + * except when this is a statement commit with an explicit + * transaction (!all && !self->st_auto_commit). + * + * Note, the only reason for a rollback of a operation is + * due to an error. In this case PBXT has already + * undone the effects of the operation. + * + * However, this is not the same as statement rollback + * which can involve a number of operations. + * + * TODO: Implement statement rollback. + */ + if (all || self->st_auto_commit) { + XT_PRINT0(self, "xt_xn_rollback\n"); + if (!xt_xn_rollback(self)) + err = xt_ha_pbxt_thread_error_for_mysql(thd, self, FALSE); + } + } + if (!all) + self->st_stat_trans = FALSE; + } + return 0; +} + +static handler *pbxt_create_handler(handlerton *hton, TABLE_SHARE *table, MEM_ROOT *mem_root) +{ + if (table && XTSystemTableShare::isSystemTable(table->path.str)) + return new (mem_root) ha_xtsys(hton, table); + else + return new (mem_root) ha_pbxt(hton, table); +} + +/* + * ----------------------------------------------------------------------- + * HANDLER LOCKING FUNCTIONS + * + * These functions are used get a lock on all handles of a particular table. + * + */ + +static void ha_add_to_handler_list(XTThreadPtr self, XTSharePtr share, ha_pbxt *handler) +{ + xt_lock_mutex(self, (xt_mutex_type *) share->sh_ex_mutex); + pushr_(xt_unlock_mutex, share->sh_ex_mutex); + + handler->pb_ex_next = share->sh_handlers; + handler->pb_ex_prev = NULL; + if (share->sh_handlers) + share->sh_handlers->pb_ex_prev = handler; + share->sh_handlers = handler; + + freer_(); // xt_unlock_mutex(share->sh_ex_mutex) +} + +static void ha_remove_from_handler_list(XTThreadPtr self, XTSharePtr share, ha_pbxt *handler) +{ + xt_lock_mutex(self, (xt_mutex_type *) share->sh_ex_mutex); + pushr_(xt_unlock_mutex, share->sh_ex_mutex); + + /* Move front pointer: */ + if (share->sh_handlers == handler) + share->sh_handlers = handler->pb_ex_next; + + /* Remove from list: */ + if (handler->pb_ex_prev) + handler->pb_ex_prev->pb_ex_next = handler->pb_ex_next; + if (handler->pb_ex_next) + handler->pb_ex_next->pb_ex_prev = handler->pb_ex_prev; + + freer_(); // xt_unlock_mutex(share->sh_ex_mutex) +} + +/* + * Aquire exclusive use of a table, by waiting for all + * threads to complete use of all handlers of the table. + * At the same time we hold up all threads + * that want to use handlers belonging to the table. + * + * But we do not hold up threads that close the handlers. + */ +static void ha_aquire_exclusive_use(XTThreadPtr self, XTSharePtr share, ha_pbxt *mine) +{ + ha_pbxt *handler; + time_t end_time = time(NULL) + XT_SHARE_LOCK_TIMEOUT / 1000; + + XT_PRINT1(self, "ha_aquire_exclusive_use %s PBXT X lock\n", share->sh_table_path->ps_path); + /* GOTCHA: It is possible to hang here, if you hold + * onto the sh_ex_mutex lock, before we really + * have the exclusive lock (i.e. before all + * handlers are no longer in use. + * The reason is, because reopen() is not possible + * when some other thread holds sh_ex_mutex. + * So this can prevent a thread from completing its + * use of a handler, when prevents exclusive use + * here. + */ + xt_lock_mutex(self, (xt_mutex_type *) share->sh_ex_mutex); + pushr_(xt_unlock_mutex, share->sh_ex_mutex); + + /* Wait until we can get an exclusive lock: */ + while (share->sh_table_lock) { + xt_timed_wait_cond(self, (xt_cond_type *) share->sh_ex_cond, (xt_mutex_type *) share->sh_ex_mutex, XT_SHARE_LOCK_WAIT); + if (time(NULL) > end_time) { + freer_(); // xt_unlock_mutex(share->sh_ex_mutex) + xt_throw_taberr(XT_CONTEXT, XT_ERR_LOCK_TIMEOUT, share->sh_table_path); + } + } + + /* This tells readers (and other exclusive lockers) that someone has an exclusive lock. */ + share->sh_table_lock = TRUE; + + /* Wait for all open handlers use count to go to 0 */ + retry: + handler = share->sh_handlers; + while (handler) { + if (handler == mine || !handler->pb_ex_in_use) + handler = handler->pb_ex_next; + else { + /* Wait a bit, and try again: */ + xt_timed_wait_cond(self, (xt_cond_type *) share->sh_ex_cond, (xt_mutex_type *) share->sh_ex_mutex, XT_SHARE_LOCK_WAIT); + if (time(NULL) > end_time) { + freer_(); // xt_unlock_mutex(share->sh_ex_mutex) + xt_throw_taberr(XT_CONTEXT, XT_ERR_LOCK_TIMEOUT, share->sh_table_path); + } + /* Handler may have been freed, check from the begining again: */ + goto retry; + } + } + + freer_(); // xt_unlock_mutex(share->sh_ex_mutex) +} + +/* + * If you have exclusively locked the table, you can close all handler + * open tables. + * + * Call ha_close_open_tables() to get an exclusive lock. + */ +static void ha_close_open_tables(XTThreadPtr self, XTSharePtr share, ha_pbxt *mine) +{ + ha_pbxt *handler; + + xt_lock_mutex(self, (xt_mutex_type *) share->sh_ex_mutex); + pushr_(xt_unlock_mutex, share->sh_ex_mutex); + + /* Now that we know no handler is in use, we can close all the + * open tables... + */ + handler = share->sh_handlers; + while (handler) { + if (handler != mine && handler->pb_open_tab) { + xt_db_return_table_to_pool_ns(handler->pb_open_tab); + handler->pb_open_tab = NULL; + } + handler = handler->pb_ex_next; + } + + freer_(); // xt_unlock_mutex(share->sh_ex_mutex) +} + +static void ha_release_exclusive_use(XTThreadPtr self __attribute__((unused)), XTSharePtr share) +{ + XT_PRINT1(self, "ha_release_exclusive_use %s PBXT X UNLOCK\n", share->sh_table_path->ps_path); + xt_lock_mutex_ns((xt_mutex_type *) share->sh_ex_mutex); + share->sh_table_lock = FALSE; + xt_broadcast_cond_ns((xt_cond_type *) share->sh_ex_cond); + xt_unlock_mutex_ns((xt_mutex_type *) share->sh_ex_mutex); +} + +static xtBool ha_wait_for_shared_use(ha_pbxt *mine, XTSharePtr share) +{ + time_t end_time = time(NULL) + XT_SHARE_LOCK_TIMEOUT / 1000; + + XT_PRINT1(xt_get_self(), "ha_wait_for_shared_use %s share lock wait...\n", share->sh_table_path->ps_path); + mine->pb_ex_in_use = 0; + xt_lock_mutex_ns((xt_mutex_type *) share->sh_ex_mutex); + while (share->sh_table_lock) { + /* Wake up the exclusive locker (may be waiting). He can try to continue: */ + xt_broadcast_cond_ns((xt_cond_type *) share->sh_ex_cond); + + if (!xt_timed_wait_cond(NULL, (xt_cond_type *) share->sh_ex_cond, (xt_mutex_type *) share->sh_ex_mutex, XT_SHARE_LOCK_WAIT)) { + xt_unlock_mutex_ns((xt_mutex_type *) share->sh_ex_mutex); + return FAILED; + } + + if (time(NULL) > end_time) { + xt_unlock_mutex_ns((xt_mutex_type *) share->sh_ex_mutex); + xt_register_taberr(XT_REG_CONTEXT, XT_ERR_LOCK_TIMEOUT, share->sh_table_path); + return FAILED; + } + } + mine->pb_ex_in_use = 1; + xt_unlock_mutex_ns((xt_mutex_type *) share->sh_ex_mutex); + return OK; +} + +xtPublic int ha_pbxt::reopen() +{ + THD *thd = current_thd; + int err = 0; + XTThreadPtr self; + xtBool tabled_opened = FALSE; + + if (!(self = ha_set_current_thread(thd, &err))) + return xt_ha_pbxt_to_mysql_error(err); + + try_(a) { + xt_ha_open_database_of_table(self, pb_share->sh_table_path); + + ha_open_share(self, pb_share, &tabled_opened); + + if (!(pb_open_tab = xt_db_open_table_using_tab(pb_share->sh_table, self))) + xt_throw(self); + pb_open_tab->ot_thread = self; + + if (tabled_opened) { +#ifdef LOAD_TABLE_ON_OPEN + xt_tab_load_table(self, pb_open_tab); +#else + xt_tab_load_row_pointers(self, pb_open_tab); +#endif + xt_ind_set_index_selectivity(self, pb_open_tab); + /* If the number of rows is less than 150 we will recalculate the + * selectity of the indices, as soon as the number of rows + * exceeds 200 (see [**]) + */ + pb_share->sh_recalc_selectivity = (pb_share->sh_table->tab_row_eof_id - 1 - pb_share->sh_table->tab_row_fnum) < 150; + } + + /* I am not doing this anymore because it was only required + * for DELETE FROM table;, which is now implemented + * by deleting each row. + * TRUNCATE TABLE does not preserve the counter value. + */ + //init_auto_increment(pb_share->sh_min_auto_inc); + init_auto_increment(0); + } + catch_(a) { + err = xt_ha_pbxt_thread_error_for_mysql(thd, self, pb_ignore_dup_key); + } + cont_(a); + + return err; +} + +/* + * ----------------------------------------------------------------------- + * INFORMATION SCHEMA FUNCTIONS + * + */ + +int pbxt_statistics_fill_table(THD *thd, TABLE_LIST *tables, COND *cond) +{ + XTThreadPtr self; + int err = 0; + + if (!(self = ha_set_current_thread(thd, &err))) + return xt_ha_pbxt_to_mysql_error(err); + try_(a) { + err = myxt_statistics_fill_table(self, thd, tables, cond, system_charset_info); + } + catch_(a) { + err = xt_ha_pbxt_thread_error_for_mysql(thd, self, FALSE); + } + cont_(a); + return err; +} + +ST_FIELD_INFO pbxt_statistics_fields_info[]= +{ + { "ID", 4, MYSQL_TYPE_LONG, 0, 0, "The ID of the statistic", SKIP_OPEN_TABLE}, + { "Name", 40, MYSQL_TYPE_STRING, 0, 0, "The name of the statistic", SKIP_OPEN_TABLE}, + { "Value", 8, MYSQL_TYPE_LONGLONG, 0, 0, "The accumulated value", SKIP_OPEN_TABLE}, + { 0, 0, MYSQL_TYPE_STRING, 0, 0, 0, SKIP_OPEN_TABLE} +}; + +int pbxt_init_statitics(void *p) +{ + ST_SCHEMA_TABLE *schema = (ST_SCHEMA_TABLE *) p; + schema->fields_info = pbxt_statistics_fields_info; + schema->fill_table = pbxt_statistics_fill_table; + +#if defined(XT_WIN) && defined(XT_COREDUMP) + void register_crash_filter(); + + if (pbxt_crash_debug) + register_crash_filter(); +#endif + + return 0; +} + +int pbxt_exit_statitics(void *p __attribute__((unused))) +{ + return(0); +} + +/* + * ----------------------------------------------------------------------- + * DYNAMIC HOOKS + * + */ + +ha_pbxt::ha_pbxt(handlerton *hton, TABLE_SHARE *table_arg) : handler(hton, table_arg) +{ + pb_share = NULL; + pb_open_tab = NULL; + pb_key_read = FALSE; + pb_ignore_dup_key = 0; + pb_lock_table = FALSE; + pb_table_locked = 0; + pb_ex_next = NULL; + pb_ex_prev = NULL; + pb_ex_in_use = 0; + pb_in_stat = FALSE; +} + +/* + * If frm_error() is called then we will use this to to find out what file extentions + * exist for the storage engine. This is also used by the default rename_table and + * delete_table method in handler.cc. + */ +const char **ha_pbxt::bas_ext() const +{ + return pbxt_extensions; +} + +/* + * Specify the caching type: HA_CACHE_TBL_NONTRANSACT, HA_CACHE_TBL_NOCACHE + * HA_CACHE_TBL_ASKTRANSACT, HA_CACHE_TBL_TRANSACT + */ +MX_UINT8_T ha_pbxt::table_cache_type() +{ + return HA_CACHE_TBL_TRANSACT; /* Use transactional query cache */ +} + +MX_TABLE_TYPES_T ha_pbxt::table_flags() const +{ + return ( + /* We need this flag because records are not packed + * into a table which means #ROWID != offset + */ + HA_REC_NOT_IN_SEQ | + /* Since PBXT caches read records itself, I believe + * this to be the case. + */ + HA_FAST_KEY_READ | + /* + * I am assuming a "key" means a unique index. + * Of course a primary key does not allow nulls. + */ + HA_NULL_IN_KEY | + /* + * This is necessary because a MySQL blob can be + * fairly small. + */ + HA_CAN_INDEX_BLOBS | + /* + * Due to transactional influences, this will be + * the case. + * Although the count is good enough for practical + * purposes! + HA_NOT_EXACT_COUNT | + */ + /* + * This basically means we have a file with the name of + * database table (which we do). + */ + HA_FILE_BASED | + /* + * Not sure what this does (but MyISAM and InnoDB have it)?! + * Could it mean that we support the handler functions. + */ + HA_CAN_SQL_HANDLER | + /* + * This is not true, we cannot insert delayed, but a + * really cannot see what's wrong with inserting normally + * when asked to insert delayed! + * And the functionallity is required to pass the alter_table + * test. + * + * Disabled because of MySQL bug #40505 + */ + /*HA_CAN_INSERT_DELAYED |*/ +#if MYSQL_VERSION_ID > 50119 + /* We can do row logging, but not statement, because + * MVCC is not serializable! + */ + HA_BINLOG_ROW_CAPABLE | +#endif + /* + * Auto-increment is allowed on a partial key. + */ + HA_AUTO_PART_KEY); +} + +/* + * The following query from the DBT1 test is VERY slow + * if we do not set HA_READ_ORDER. + * The reason is that it must scan all duplicates, then + * sort. + * + * SELECT o_id, o_carrier_id, o_entry_d, o_ol_cnt + * FROM orders FORCE INDEX (o_w_id) + * WHERE o_w_id = 2 + * AND o_d_id = 1 + * AND o_c_id = 500 + * ORDER BY o_id DESC limit 1; + * + */ +#define FLAGS_ARE_READ_DYNAMICALLY + +MX_ULONG_T ha_pbxt::index_flags(uint inx __attribute__((unused)), uint part __attribute__((unused)), bool all_parts __attribute__((unused))) const +{ + /* It would be nice if the dynamic version of this function works, + * but it does not. MySQL loads this information when the table is openned, + * and then it is fixed. + * + * The problem is, I have had to remove the HA_READ_ORDER option although + * it applies to PBXT. PBXT returns entries in index order during an index + * scan in _almost_ all cases. + * + * A number of cases are demostrated here: [(11)] + * + * If involves the following conditions: + * - a SELECT FOR UPDATE, UPDATE or DELETE statement + * - an ORDER BY, or join that requires the sort order + * - another transaction which updates the index while it is being + * scanned. + * + * In this "obscure" case, the index scan may return index + * entries in the wrong order. + */ +#ifdef FLAGS_ARE_READ_DYNAMICALLY + /* If were are in an update (SELECT FOR UPDATE, UPDATE or DELETE), then + * it may be that we return the rows from an index in the wrong + * order! This is due to the fact that update reads wait for transactions + * to commit and this means that index entries may change position during + * the scan! + */ + if (pb_open_tab && pb_open_tab->ot_for_update) + return (HA_READ_NEXT | HA_READ_PREV | HA_READ_RANGE | HA_KEYREAD_ONLY); + /* If I understand HA_KEYREAD_ONLY then this means I do not + * need to fetch the record associated with an index + * key. + */ + return (HA_READ_NEXT | HA_READ_PREV | HA_READ_ORDER | HA_READ_RANGE | HA_KEYREAD_ONLY); +#else + return (HA_READ_NEXT | HA_READ_PREV | HA_READ_RANGE | HA_KEYREAD_ONLY); +#endif +} + +void ha_pbxt::internal_close(THD *thd, struct XTThread *self) +{ + if (pb_share) { + xtBool removed; + XTOpenTablePtr ot; + + try_(a) { + /* This lock must be held when we remove the handler's + * open table because ha_close_open_tables() can run + * concurrently. + */ + xt_lock_mutex_ns(pb_share->sh_ex_mutex); + if ((ot = pb_open_tab)) { + pb_open_tab->ot_thread = self; + if (self->st_database != pb_open_tab->ot_table->tab_db) + xt_ha_open_database_of_table(self, pb_share->sh_table_path); + pb_open_tab = NULL; + pushr_(xt_db_return_table_to_pool, ot); + } + xt_unlock_mutex_ns(pb_share->sh_ex_mutex); + + ha_remove_from_handler_list(self, pb_share, this); + + /* Someone may be waiting for me to complete: */ + xt_broadcast_cond_ns((xt_cond_type *) pb_share->sh_ex_cond); + + removed = ha_unget_share_removed(self, pb_share); + + if (ot) { + /* Flush the table if this was the last handler: */ + /* This is not necessary but has the affect that + * FLUSH TABLES; does a checkpoint! + */ + if (removed) { + /* GOTCHA: + * This was killing performance as the number of threads increased! + * + * When MySQL runs out of table handlers because the table + * handler cache is too small, it starts to close handlers. + * (open_cache.records > table_cache_size) + * + * Which can lead to closing all handlers for a particular table. + * + * It does this while holding lock_OPEN! + * So this code below leads to a sync operation while lock_OPEN + * is held. The result is that the whole server comes to a stop. + */ + if (!thd || thd_sql_command(thd) == SQLCOM_FLUSH) // FLUSH TABLES + xt_sync_flush_table(self, ot); + } + freer_(); // xt_db_return_table_to_pool(ot); + } + } + catch_(a) { + xt_log_and_clear_exception(self); + } + cont_(a); + + pb_share = NULL; + } +} + +/* + * 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_pbxt::open(const char *table_path, int mode __attribute__((unused)), uint test_if_locked __attribute__((unused))) +{ + THD *thd = current_thd; + int err = 0; + XTThreadPtr self; + xtBool tabled_opened = FALSE; + + ref_length = XT_RECORD_OFFS_SIZE; + + if (!(self = ha_set_current_thread(thd, &err))) + return xt_ha_pbxt_to_mysql_error(err); + + XT_PRINT1(self, "ha_pbxt::open %s\n", table_path); + + pb_ex_in_use = 1; + try_(a) { + xt_ha_open_database_of_table(self, (XTPathStrPtr) table_path); + + pb_share = ha_get_share(self, table_path, true, &tabled_opened); + ha_add_to_handler_list(self, pb_share, this); + if (pb_share->sh_table_lock) { + if (!ha_wait_for_shared_use(this, pb_share)) + xt_throw(self); + } + + ha_open_share(self, pb_share, &tabled_opened); + + thr_lock_data_init(&pb_share->sh_lock, &pb_lock, NULL); + if (!(pb_open_tab = xt_db_open_table_using_tab(pb_share->sh_table, self))) + xt_throw(self); + pb_open_tab->ot_thread = self; + + if (tabled_opened) { +#ifdef LOAD_TABLE_ON_OPEN + xt_tab_load_table(self, pb_open_tab); +#else + xt_tab_load_row_pointers(self, pb_open_tab); +#endif + xt_ind_set_index_selectivity(self, pb_open_tab); + pb_share->sh_recalc_selectivity = (pb_share->sh_table->tab_row_eof_id - 1 - pb_share->sh_table->tab_row_fnum) < 150; + } + + init_auto_increment(0); + } + catch_(a) { + err = xt_ha_pbxt_thread_error_for_mysql(thd, self, pb_ignore_dup_key); + internal_close(thd, self); + } + cont_(a); + + if (!err) + info(HA_STATUS_NO_LOCK | HA_STATUS_VARIABLE | HA_STATUS_CONST); + + pb_ex_in_use = 0; + if (pb_share) { + /* Someone may be waiting for me to complete: */ + if (pb_share->sh_table_lock) + xt_broadcast_cond_ns((xt_cond_type *) pb_share->sh_ex_cond); + } + return err; +} + + +/* + Closes a table. We call the free_share() function to free any resources + that we have allocated in the "shared" structure. + + 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_pbxt::close(void) +{ + THD *thd = current_thd; + volatile int err = 0; + volatile XTThreadPtr self; + + if (thd) + self = ha_set_current_thread(thd, (int *) &err); + else { + XTExceptionRec e; + + if (!(self = xt_create_thread("TempForClose", FALSE, TRUE, &e))) { + xt_log_exception(NULL, &e, XT_LOG_DEFAULT); + return 0; + } + } + + XT_PRINT1(self, "ha_pbxt::close %s\n", pb_share && pb_share->sh_table_path->ps_path ? pb_share->sh_table_path->ps_path : "unknown"); + + if (self) { + try_(a) { + internal_close(thd, self); + } + catch_(a) { + err = xt_ha_pbxt_thread_error_for_mysql(thd, self, pb_ignore_dup_key); + } + cont_(a); + + if (!thd) + xt_free_thread(self); + } + else + xt_log(XT_NS_CONTEXT, XT_LOG_WARNING, "Unable to release table reference\n"); + + return err; +} + +void ha_pbxt::init_auto_increment(xtWord8 min_auto_inc) +{ + XTTableHPtr tab; + xtWord8 nr = 0; + int err; + + /* Get the value of the auto-increment value by + * loading the highest value from the index... + */ + tab = pb_open_tab->ot_table; + + /* Cannot do this if the index version is bad! */ + if (tab->tab_dic.dic_disable_index) + return; + + xt_spinlock_lock(&tab->tab_ainc_lock); + if (table->found_next_number_field && !tab->tab_auto_inc) { + Field *tmp_fie = table->next_number_field; + THD *tmp_thd = table->in_use; + xtBool xn_started = FALSE; + XTThreadPtr self = pb_open_tab->ot_thread; + + /* + * A table may be opened by a thread with a running + * transaction! + * Since get_auto_increment() does not do an update, + * it should be OK to use the transaction we already + * have to get the next auto-increment value. + */ + if (!self->st_xact_data) { + self->st_xact_mode = XT_XACT_REPEATABLE_READ; + self->st_ignore_fkeys = FALSE; + self->st_auto_commit = TRUE; + self->st_table_trans = FALSE; + self->st_abort_trans = FALSE; + self->st_stat_ended = FALSE; + self->st_stat_trans = FALSE; + self->st_is_update = FALSE; + if (!xt_xn_begin(self)) { + xt_spinlock_unlock(&tab->tab_ainc_lock); + xt_throw(self); + } + xn_started = TRUE; + } + + /* Setup the conditions for the next call! */ + table->in_use = current_thd; + table->next_number_field = table->found_next_number_field; + + extra(HA_EXTRA_KEYREAD); + table->mark_columns_used_by_index_no_reset(TS(table)->next_number_index, table->read_set); + column_bitmaps_signal(); + index_init(TS(table)->next_number_index, 0); + if (!TS(table)->next_number_key_offset) { + // Autoincrement at key-start + err = index_last(table->record[1]); + if (!err) + /* {PRE-INC} */ + nr = (xtWord8) table->next_number_field->val_int_offset(TS(table)->rec_buff_length); + } + else { + /* Do an index scan to find the largest value! */ + /* The standard method will not work because it forces + * us to lock that table! + */ + xtWord8 val; + + err = index_first(table->record[1]); + while (!err) { + /* {PRE-INC} */ + val = (xtWord8) table->next_number_field->val_int_offset(TS(table)->rec_buff_length); + if (val > nr) + nr = val; + err = index_next(table->record[1]); + } + } + + index_end(); + extra(HA_EXTRA_NO_KEYREAD); + + /* {PRE-INC} + * I have changed this from post increment to pre-increment! + * The reason is: + * When using post increment we are not able to return + * the last valid value in the range. + * + * Here the test example: + * + * drop table if exists t1; + * create table t1 (i tinyint unsigned not null auto_increment primary key) engine=pbxt; + * insert into t1 set i = 254; + * insert into t1 set i = null; + * + * With post-increment, this last insert fails because on post increment + * the value overflows! + * + * Pre-increment means we store the current max, and increment + * before returning the next value. + * + * This will work in this situation. + */ + tab->tab_auto_inc = nr; + if (tab->tab_auto_inc < tab->tab_dic.dic_min_auto_inc) + tab->tab_auto_inc = tab->tab_dic.dic_min_auto_inc-1; + if (tab->tab_auto_inc < min_auto_inc) + tab->tab_auto_inc = min_auto_inc-1; + + /* Restore the changed values: */ + table->next_number_field = tmp_fie; + table->in_use = tmp_thd; + + if (xn_started) + xt_xn_commit(self); + } + xt_spinlock_unlock(&tab->tab_ainc_lock); +} + +void ha_pbxt::get_auto_increment(MX_ULONGLONG_T offset, MX_ULONGLONG_T increment, + MX_ULONGLONG_T nb_desired_values __attribute__((unused)), + MX_ULONGLONG_T *first_value, + MX_ULONGLONG_T *nb_reserved_values __attribute__((unused))) +{ + register XTTableHPtr tab; + MX_ULONGLONG_T nr, nr_less_inc; + + ASSERT_NS(pb_ex_in_use); + + tab = pb_open_tab->ot_table; + + /* {PRE-INC} + * Assume that nr contains the last value returned! + * We will increment and then return the value. + */ + xt_spinlock_lock(&tab->tab_ainc_lock); + nr = (MX_ULONGLONG_T) tab->tab_auto_inc; + nr_less_inc = nr; + if (nr < offset) + nr = offset; + else if (increment > 1 && ((nr - offset) % increment) != 0) + nr += increment - ((nr - offset) % increment); + else + nr += increment; + if (table->next_number_field->cmp((const unsigned char *)&nr_less_inc, (const unsigned char *)&nr) < 0) + tab->tab_auto_inc = (xtWord8) (nr); + else + nr = ~0; /* indicate error to the caller */ + xt_spinlock_unlock(&tab->tab_ainc_lock); + + *first_value = nr; + *nb_reserved_values = 1; +} + +/* GOTCHA: We need to use signed value here because of the test + * (from auto_increment.test): + * create table t1 (a int not null auto_increment primary key); + * insert into t1 values (NULL); + * insert into t1 values (-1); + * insert into t1 values (NULL); + */ +void ha_pbxt::set_auto_increment(Field *nr) +{ + register XTTableHPtr tab; + MX_ULONGLONG_T nr_int_val; + + nr_int_val = nr->val_int(); + tab = pb_open_tab->ot_table; + + if (nr->cmp((const unsigned char *)&tab->tab_auto_inc) > 0) { + xt_spinlock_lock(&tab->tab_ainc_lock); + + if (nr->cmp((const unsigned char *)&tab->tab_auto_inc) > 0) { + /* {PRE-INC} + * We increment later, so just set the value! + MX_ULONGLONG_T nr_int_val_plus_one = nr_int_val + 1; + if (nr->cmp((const unsigned char *)&nr_int_val_plus_one) < 0) + tab->tab_auto_inc = nr_int_val_plus_one; + else + */ + tab->tab_auto_inc = nr_int_val; + } + xt_spinlock_unlock(&tab->tab_ainc_lock); + } + + if (xt_db_auto_increment_mode == 1) { + if (nr_int_val > (MX_ULONGLONG_T) tab->tab_dic.dic_min_auto_inc) { + /* Do this every 100 calls: */ +#ifdef DEBUG + tab->tab_dic.dic_min_auto_inc = nr_int_val + 5; +#else + tab->tab_dic.dic_min_auto_inc = nr_int_val + 100; +#endif + pb_open_tab->ot_thread = xt_get_self(); + if (!xt_tab_write_min_auto_inc(pb_open_tab)) + xt_log_and_clear_exception(pb_open_tab->ot_thread); + } + } +} + +/* +static void dump_buf(unsigned char *buf, int len) +{ + int i; + + for (i=0; i<len; i++) printf("%2c", buf[i] <= 127 ? buf[i] : '.'); + printf("\n"); + for (i=0; i<len; i++) printf("%02x", buf[i]); + printf("\n"); +} +*/ + +/* + * write_row() inserts a row. No extra() hint is given currently if a bulk load + * is happeneding. buf() is a byte array of data. 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 an example of extracting all of the data as strings. + * ha_berekly.cc has an example of how to store it intact by "packing" it + * for ha_berkeley's own native storage type. + + * See the note for update_row() on auto_increments and timestamps. This + * case also applied to write_row(). + + * 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. + */ +int ha_pbxt::write_row(byte *buf) +{ + int err = 0; + + ASSERT_NS(pb_ex_in_use); + + XT_PRINT1(pb_open_tab->ot_thread, "ha_pbxt::write_row %s\n", pb_share->sh_table_path->ps_path); + XT_DISABLED_TRACE(("INSERT tx=%d val=%d\n", (int) pb_open_tab->ot_thread->st_xact_data->xd_start_xn_id, (int) XT_GET_DISK_4(&buf[1]))); + //statistic_increment(ha_write_count,&LOCK_status); + + /* GOTCHA: I have a huge problem with the transaction statement. + * It is not ALWAYS committed (I mean ha_commit_trans() is + * not always called - for example in SELECT). + * + * If I call trans_register_ha() but ha_commit_trans() is not called + * then MySQL thinks a transaction is still running (while + * I have committed the auto-transaction in ha_pbxt::external_lock()). + * + * This causes all kinds of problems, like transactions + * are killed when they should not be. + * + * To prevent this, I only inform MySQL that a transaction + * has beens started when an update is performed. I have determined that + * ha_commit_trans() is only guarenteed to be called if an update is done. + */ + if (!pb_open_tab->ot_thread->st_stat_trans) { + trans_register_ha(pb_mysql_thd, FALSE, pbxt_hton); + XT_PRINT0(pb_open_tab->ot_thread, "ha_pbxt::write_row trans_register_ha all=FALSE\n"); + pb_open_tab->ot_thread->st_stat_trans = TRUE; + } + + xt_xlog_check_long_writer(pb_open_tab->ot_thread); + + if (table->timestamp_field_type & TIMESTAMP_AUTO_SET_ON_INSERT) + table->timestamp_field->set_time(); + + if (table->next_number_field && buf == table->record[0]) { + int update_err = update_auto_increment(); + if (update_err) { + ha_log_pbxt_thread_error_for_mysql(pb_ignore_dup_key); + return update_err; + } + set_auto_increment(table->next_number_field); + } + + if (!xt_tab_new_record(pb_open_tab, (xtWord1 *) buf)) { + err = ha_log_pbxt_thread_error_for_mysql(pb_ignore_dup_key); + + /* + * This is needed to allow the same row to be updated multiple times in case of bulk REPLACE. + * This happens during execution of LOAD DATA...REPLACE MySQL first tries to INSERT the row + * and if it gets dup-key error it tries UPDATE, so the same row can be overwriten multiple + * times within the same statement + */ + if (err == HA_ERR_FOUND_DUPP_KEY && pb_open_tab->ot_thread->st_is_update) + pb_open_tab->ot_thread->st_update_id++; + } + + return err; +} + +#ifdef UNUSED_CODE +static int equ_bin(const byte *a, const char *b) +{ + while (*a && *b) { + if (*a != *b) + return 0; + a++; + b++; + } + return 1; +} +static void dump_bin(const byte *a_in, int offset, int len_in) +{ + const byte *a = a_in; + int len = len_in; + + a += offset; + while (len > 0) { + xt_trace("%02X", (int) *a); + a++; + len--; + } + xt_trace("=="); + a = a_in; + len = len_in; + a += offset; + while (len > 0) { + xt_trace("%c", (*a > 8 && *a < 127) ? *a : '.'); + a++; + len--; + } + xt_trace("\n"); +} +#endif + +/* + * 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. + */ +int ha_pbxt::update_row(const byte * old_data, byte * new_data) +{ + int err = 0; + register XTThreadPtr self = pb_open_tab->ot_thread; + + ASSERT_NS(pb_ex_in_use); + + XT_PRINT1(self, "ha_pbxt::update_row %s\n", pb_share->sh_table_path->ps_path); + XT_DISABLED_TRACE(("UPDATE tx=%d val=%d\n", (int) self->st_xact_data->xd_start_xn_id, (int) XT_GET_DISK_4(&new_data[1]))); + //statistic_increment(ha_update_count,&LOCK_status); + + if (!self->st_stat_trans) { + trans_register_ha(pb_mysql_thd, FALSE, pbxt_hton); + XT_PRINT0(self, "ha_pbxt::update_row trans_register_ha all=FALSE\n"); + self->st_stat_trans = TRUE; + } + + xt_xlog_check_long_writer(self); + + if (!self->st_is_update) { + self->st_is_update = TRUE; + self->st_update_id++; + } + + if (table->timestamp_field_type & TIMESTAMP_AUTO_SET_ON_UPDATE) + table->timestamp_field->set_time(); + + /* GOTCHA: We need to check the auto-increment value on update + * because of the following test (which fails for InnoDB) - + * auto_increment.test: + * create table t1 (a int not null auto_increment primary key, val int); + * insert into t1 (val) values (1); + * update t1 set a=2 where a=1; + * insert into t1 (val) values (1); + */ + if (table->found_next_number_field && new_data == table->record[0]) { + MX_LONGLONG_T nr; + my_bitmap_map *old_map; + + old_map = mx_tmp_use_all_columns(table, table->read_set); + nr = table->found_next_number_field->val_int(); + set_auto_increment(table->found_next_number_field); + mx_tmp_restore_column_map(table, old_map); + } + + if (!xt_tab_update_record(pb_open_tab, (xtWord1 *) old_data, (xtWord1 *) new_data)) + err = ha_log_pbxt_thread_error_for_mysql(pb_ignore_dup_key); + + pb_open_tab->ot_table->tab_locks.xt_remove_temp_lock(pb_open_tab, TRUE); + + return err; +} + +/* + * 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 called (from + * either a previous rnd_next() or index call). + * + * 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. +*/ +int ha_pbxt::delete_row(const byte * buf) +{ + int err = 0; + + ASSERT_NS(pb_ex_in_use); + + XT_PRINT1(pb_open_tab->ot_thread, "ha_pbxt::delete_row %s\n", pb_share->sh_table_path->ps_path); + XT_DISABLED_TRACE(("DELETE tx=%d val=%d\n", (int) pb_open_tab->ot_thread->st_xact_data->xd_start_xn_id, (int) XT_GET_DISK_4(&buf[1]))); + //statistic_increment(ha_delete_count,&LOCK_status); + + if (!pb_open_tab->ot_thread->st_stat_trans) { + trans_register_ha(pb_mysql_thd, FALSE, pbxt_hton); + XT_PRINT0(pb_open_tab->ot_thread, "ha_pbxt::delete_row trans_register_ha all=FALSE\n"); + pb_open_tab->ot_thread->st_stat_trans = TRUE; + } + + xt_xlog_check_long_writer(pb_open_tab->ot_thread); + + if (!xt_tab_delete_record(pb_open_tab, (xtWord1 *) buf)) + err = ha_log_pbxt_thread_error_for_mysql(pb_ignore_dup_key); + + pb_open_tab->ot_table->tab_locks.xt_remove_temp_lock(pb_open_tab, TRUE); + + return err; +} + +/* + * ----------------------------------------------------------------------- + * INDEX METHODS + */ + +/* + * This looks like a hack, but actually, it is OK. + * It depends on the setup done by the super-class. It involves an extra + * range check that we need to do if a "new" record is returned during + * an index scan. + * + * A new record is returned if a row is updated (by another transaction) + * during the index scan. If an update is detected, then the scan stops + * and waits for the transaction to end. + * + * If the transaction commits, then the updated row is returned instead + * of the row it would have returned when doing a consistant read + * (repeatable read). + * + * These new records can appear out of index order, and may not even + * belong to the index range that we are concerned with. + * + * Notice that there is not check for the start of the range. It appears + * that this is not necessary, MySQL seems to have no problem ignoring + * such values. + * + * A number of test have been given below which demonstrate the use + * of the function. + * + * They also demonstrate the ORDER BY problem described here: [(11)]. + * + * DROP TABLE IF EXISTS test_tab, test_tab_1, test_tab_2; + * CREATE TABLE test_tab (ID int primary key, Value int, Name varchar(20), index(Value, Name)) ENGINE=pbxt; + * INSERT test_tab values(1, 1, 'A'); + * INSERT test_tab values(2, 1, 'B'); + * INSERT test_tab values(3, 1, 'C'); + * INSERT test_tab values(4, 2, 'D'); + * INSERT test_tab values(5, 2, 'E'); + * INSERT test_tab values(6, 2, 'F'); + * INSERT test_tab values(7, 2, 'G'); + * + * select * from test_tab where value = 1 order by value, name for update; + * + * -- Test: 1 + * -- C1 + * begin; + * select * from test_tab where id = 5 for update; + * + * -- C2 + * begin; + * select * from test_tab where value = 2 order by value, name for update; + * + * -- C1 + * update test_tab set value = 3 where id = 6; + * commit; + * + * -- Test: 2 + * -- C1 + * begin; + * select * from test_tab where id = 5 for update; + * + * -- C2 + * begin; + * select * from test_tab where value >= 2 order by value, name for update; + * + * -- C1 + * update test_tab set value = 3 where id = 6; + * commit; + * + * -- Test: 3 + * -- C1 + * begin; + * select * from test_tab where id = 5 for update; + * + * -- C2 + * begin; + * select * from test_tab where value = 2 order by value, name for update; + * + * -- C1 + * update test_tab set value = 1 where id = 6; + * commit; + */ + +int ha_pbxt::xt_index_in_range(register XTOpenTablePtr ot __attribute__((unused)), register XTIndexPtr ind, + register XTIdxSearchKeyPtr search_key, xtWord1 *buf) +{ + /* If search key is given, this means we want an exact match. */ + if (search_key) { + xtWord1 key_buf[XT_INDEX_MAX_KEY_SIZE]; + + myxt_create_key_from_row(ind, key_buf, buf, NULL); + search_key->sk_on_key = myxt_compare_key(ind, search_key->sk_key_value.sv_flags, search_key->sk_key_value.sv_length, + search_key->sk_key_value.sv_key, key_buf) == 0; + return search_key->sk_on_key; + } + + /* Otherwise, check the end of the range. */ + if (end_range) + return compare_key(end_range) <= 0; + return 1; +} + +int ha_pbxt::xt_index_next_read(register XTOpenTablePtr ot, register XTIndexPtr ind, xtBool key_only, + register XTIdxSearchKeyPtr search_key, byte *buf) +{ + xt_xlog_check_long_writer(ot->ot_thread); + + if (key_only) { + /* We only need to read the data from the key: */ + while (ot->ot_curr_rec_id) { + if (search_key && !search_key->sk_on_key) + break; + + switch (xt_tab_visible(ot)) { + case FALSE: + if (xt_idx_next(ot, ind, search_key)) + break; + case XT_ERR: + goto failed; + case XT_NEW: + if (!xt_idx_read(ot, ind, (xtWord1 *) buf)) + goto failed; + if (xt_index_in_range(ot, ind, search_key, buf)) { + return 0; + } + if (!xt_idx_next(ot, ind, search_key)) + goto failed; + break; + case XT_RETRY: + /* We cannot start from the beginning again, if we have + * already output rows! + * And we need the orginal search key. + * + * The case in which this occurs is: + * + * T1: UPDATE tbl_file SET GlobalID = 'DBCD5C4514210200825501089884844_6M' WHERE ID = 39 + * Locks a particular row. + * + * T2: SELECT ID,Flags FROM tbl_file WHERE SpaceID = 1 AND Path = '/zi/America/' AND + * Name = 'Cuiaba' AND Flags IN ( 0,1,4,5 ) FOR UPDATE + * scans the index and stops on the lock (of the before image) above. + * + * T1 quits, the sweeper deletes the record updated by T1?! + * BUG: Cleanup should wait until T2 is complete! + * + * T2 continues, and returns XT_RETRY. + * + * At this stage T2 has already returned some rows, so it may not retry from the + * start. Instead it tries to locate the last record it tried to lock. + * This record is gone (or not visible), so it finds the next one. + * + * POTENTIAL BUG: If cleanup does not wait until T2 is complete, then + * I may miss the update record, if it is moved before the index scan + * position. + */ + if (!pb_ind_row_count && search_key) { + if (!xt_idx_search(pb_open_tab, ind, search_key)) + return ha_log_pbxt_thread_error_for_mysql(pb_ignore_dup_key); + } + else { + if (!xt_idx_research(pb_open_tab, ind)) + goto failed; + } + break; + default: + if (!xt_idx_read(ot, ind, (xtWord1 *) buf)) + goto failed; + return 0; + } + } + } + else { + while (ot->ot_curr_rec_id) { + if (search_key && !search_key->sk_on_key) + break; + + switch (xt_tab_read_record(ot, (xtWord1 *) buf)) { + case FALSE: + XT_DISABLED_TRACE(("not visi tx=%d rec=%d\n", (int) ot->ot_thread->st_xact_data->xd_start_xn_id, (int) ot->ot_curr_rec_id)); + if (xt_idx_next(ot, ind, search_key)) + break; + case XT_ERR: + goto failed; + case XT_NEW: + if (xt_index_in_range(ot, ind, search_key, buf)) + return 0; + if (!xt_idx_next(ot, ind, search_key)) + goto failed; + break; + case XT_RETRY: + if (!pb_ind_row_count && search_key) { + if (!xt_idx_search(pb_open_tab, ind, search_key)) + return ha_log_pbxt_thread_error_for_mysql(pb_ignore_dup_key); + } + else { + if (!xt_idx_research(pb_open_tab, ind)) + goto failed; + } + break; + default: + XT_DISABLED_TRACE(("visible tx=%d rec=%d\n", (int) ot->ot_thread->st_xact_data->xd_start_xn_id, (int) ot->ot_curr_rec_id)); + return 0; + } + } + } + return HA_ERR_END_OF_FILE; + + failed: + return ha_log_pbxt_thread_error_for_mysql(FALSE); +} + +int ha_pbxt::xt_index_prev_read(XTOpenTablePtr ot, XTIndexPtr ind, xtBool key_only, + register XTIdxSearchKeyPtr search_key, byte *buf) +{ + if (key_only) { + /* We only need to read the data from the key: */ + while (ot->ot_curr_rec_id) { + if (search_key && !search_key->sk_on_key) + break; + + switch (xt_tab_visible(ot)) { + case FALSE: + if (xt_idx_prev(ot, ind, search_key)) + break; + case XT_ERR: + goto failed; + case XT_NEW: + if (!xt_idx_read(ot, ind, (xtWord1 *) buf)) + goto failed; + if (xt_index_in_range(ot, ind, search_key, buf)) + return 0; + if (!xt_idx_next(ot, ind, search_key)) + goto failed; + break; + case XT_RETRY: + if (!pb_ind_row_count && search_key) { + if (!xt_idx_search_prev(pb_open_tab, ind, search_key)) + return ha_log_pbxt_thread_error_for_mysql(pb_ignore_dup_key); + } + else { + if (!xt_idx_research(pb_open_tab, ind)) + goto failed; + } + break; + default: + if (!xt_idx_read(ot, ind, (xtWord1 *) buf)) + goto failed; + return 0; + } + } + } + else { + /* We need to read the entire record: */ + while (ot->ot_curr_rec_id) { + if (search_key && !search_key->sk_on_key) + break; + + switch (xt_tab_read_record(ot, (xtWord1 *) buf)) { + case FALSE: + if (xt_idx_prev(ot, ind, search_key)) + break; + case XT_ERR: + goto failed; + case XT_NEW: + if (xt_index_in_range(ot, ind, search_key, buf)) + return 0; + if (!xt_idx_next(ot, ind, search_key)) + goto failed; + break; + case XT_RETRY: + if (!pb_ind_row_count && search_key) { + if (!xt_idx_search_prev(pb_open_tab, ind, search_key)) + return ha_log_pbxt_thread_error_for_mysql(pb_ignore_dup_key); + } + else { + if (!xt_idx_research(pb_open_tab, ind)) + goto failed; + } + break; + default: + return 0; + } + } + } + return HA_ERR_END_OF_FILE; + + failed: + return ha_log_pbxt_thread_error_for_mysql(FALSE); +} + +int ha_pbxt::index_init(uint idx, bool sorted __attribute__((unused))) +{ + XTIndexPtr ind; + + /* select count(*) from smalltab_PBXT; + * ignores the error below, and continues to + * call index_first! + */ + active_index = idx; + + if (pb_open_tab->ot_table->tab_dic.dic_disable_index) { + xt_tab_set_index_error(pb_open_tab->ot_table); + return ha_log_pbxt_thread_error_for_mysql(pb_ignore_dup_key); + } + + /* The number of columns required: */ + if (pb_open_tab->ot_is_modify) { + pb_open_tab->ot_cols_req = table->read_set->n_bits; +#ifdef XT_PRINT_INDEX_OPT + ind = (XTIndexPtr) pb_share->sh_dic_keys[idx]; + + printf("index_init %s index %d cols req=%d/%d read_bits=%X write_bits=%X index_bits=%X\n", pb_open_tab->ot_table->tab_name->ps_path, (int) idx, pb_open_tab->ot_cols_req, pb_open_tab->ot_cols_req, (int) *table->read_set->bitmap, (int) *table->write_set->bitmap, (int) *ind->mi_col_map.bitmap); +#endif + } + else { + pb_open_tab->ot_cols_req = ha_get_max_bit(table->read_set); + + /* Check for index coverage! + * + * Given the following table: + * + * CREATE TABLE `customer` ( + * `c_id` int(11) NOT NULL DEFAULT '0', + * `c_d_id` int(11) NOT NULL DEFAULT '0', + * `c_w_id` int(11) NOT NULL DEFAULT '0', + * `c_first` varchar(16) DEFAULT NULL, + * `c_middle` char(2) DEFAULT NULL, + * `c_last` varchar(16) DEFAULT NULL, + * `c_street_1` varchar(20) DEFAULT NULL, + * `c_street_2` varchar(20) DEFAULT NULL, + * `c_city` varchar(20) DEFAULT NULL, + * `c_state` char(2) DEFAULT NULL, + * `c_zip` varchar(9) DEFAULT NULL, + * `c_phone` varchar(16) DEFAULT NULL, + * `c_since` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + * `c_credit` char(2) DEFAULT NULL, + * `c_credit_lim` decimal(24,12) DEFAULT NULL, + * `c_discount` double DEFAULT NULL, + * `c_balance` decimal(24,12) DEFAULT NULL, + * `c_ytd_payment` decimal(24,12) DEFAULT NULL, + * `c_payment_cnt` double DEFAULT NULL, + * `c_delivery_cnt` double DEFAULT NULL, + * `c_data` text, + * PRIMARY KEY (`c_w_id`,`c_d_id`,`c_id`), + * KEY `c_w_id` (`c_w_id`,`c_d_id`,`c_last`,`c_first`,`c_id`) + * ) ENGINE=PBXT; + * + * MySQL does not recognize index coverage on the followin select: + * + * SELECT c_id FROM customer WHERE c_w_id = 3 AND c_d_id = 8 AND + * c_last = 'EINGATIONANTI' ORDER BY c_first ASC LIMIT 1; + * + * TODO: Find out why this is necessary, MyISAM does not + * seem to have this problem! + */ + ind = (XTIndexPtr) pb_share->sh_dic_keys[idx]; + if (bitmap_is_subset(table->read_set, &ind->mi_col_map)) + pb_key_read = TRUE; +#ifdef XT_PRINT_INDEX_OPT + printf("index_init %s index %d cols req=%d/%d read_bits=%X write_bits=%X index_bits=%X converage=%d\n", pb_open_tab->ot_table->tab_name->ps_path, (int) idx, pb_open_tab->ot_cols_req, table->read_set->n_bits, (int) *table->read_set->bitmap, (int) *table->write_set->bitmap, (int) *ind->mi_col_map.bitmap, (int) (bitmap_is_subset(table->read_set, &ind->mi_col_map) != 0)); +#endif + } + + xt_xlog_check_long_writer(pb_open_tab->ot_thread); + + pb_open_tab->ot_thread->st_statistics.st_scan_index++; + return 0; +} + +int ha_pbxt::index_end() +{ + int err = 0; + + XT_TRACE_CALL(); + + XTThreadPtr thread = pb_open_tab->ot_thread; + + /* + * the assertion below is not always held, because the sometimes handler is unlocked + * before this function is called + */ + /*ASSERT_NS(pb_ex_in_use);*/ + + if (pb_open_tab->ot_ind_rhandle) { + xt_ind_release_handle(pb_open_tab->ot_ind_rhandle, FALSE, thread); + pb_open_tab->ot_ind_rhandle = NULL; + } + + /* + * make permanent the lock for the last scanned row + */ + if (pb_open_tab) + pb_open_tab->ot_table->tab_locks.xt_make_lock_permanent(pb_open_tab, &thread->st_lock_list); + + xt_xlog_check_long_writer(thread); + + active_index = MAX_KEY; + XT_RETURN(err); +} + +#ifdef XT_TRACK_RETURNED_ROWS +void ha_start_scan(XTOpenTablePtr ot, u_int index) +{ + xt_ttracef(ot->ot_thread, "SCAN %d:%d\n", (int) ot->ot_table->tab_id, (int) index); + ot->ot_rows_ret_curr = 0; + for (u_int i=0; i<ot->ot_rows_ret_max; i++) + ot->ot_rows_returned[i] = 0; +} + +void ha_return_row(XTOpenTablePtr ot, u_int index) +{ + xt_ttracef(ot->ot_thread, "%d:%d ROW=%d:%d\n", + (int) ot->ot_table->tab_id, (int) index, (int) ot->ot_curr_row_id, (int) ot->ot_curr_rec_id); + ot->ot_rows_ret_curr++; + if (ot->ot_curr_row_id >= ot->ot_rows_ret_max) { + if (!xt_realloc_ns((void **) &ot->ot_rows_returned, (ot->ot_curr_row_id+1) * sizeof(xtRecordID))) + ASSERT_NS(FALSE); + memset(&ot->ot_rows_returned[ot->ot_rows_ret_max], 0, (ot->ot_curr_row_id+1 - ot->ot_rows_ret_max) * sizeof(xtRecordID)); + ot->ot_rows_ret_max = ot->ot_curr_row_id+1; + } + if (!ot->ot_curr_row_id || !ot->ot_curr_rec_id || ot->ot_rows_returned[ot->ot_curr_row_id]) { + char *sql = *thd_query(current_thd); + + xt_ttracef(ot->ot_thread, "DUP %d:%d %s\n", + (int) ot->ot_table->tab_id, (int) index, *thd_query(current_thd)); + xt_dump_trace(); + printf("ERROR: row=%d rec=%d newr=%d, already returned!\n", (int) ot->ot_curr_row_id, (int) ot->ot_rows_returned[ot->ot_curr_row_id], (int) ot->ot_curr_rec_id); + printf("ERROR: %s\n", sql); +#ifdef XT_WIN + FatalAppExit(0, "Debug Me!"); +#endif + } + else + ot->ot_rows_returned[ot->ot_curr_row_id] = ot->ot_curr_rec_id; +} +#endif + +int ha_pbxt::index_read_xt(byte * buf, uint idx, const byte *key, uint key_len __attribute__((unused)), enum ha_rkey_function find_flag __attribute__((unused))) +{ + int err = 0; + XTIndexPtr ind; + int prefix = 0; + XTIdxSearchKeyRec search_key; + +#ifdef XT_TRACK_RETURNED_ROWS + ha_start_scan(pb_open_tab, idx); +#endif + + /* This call starts a search on this handler! */ + pb_ind_row_count = 0; + + ASSERT_NS(pb_ex_in_use); + + XT_PRINT1(pb_open_tab->ot_thread, "ha_pbxt::index_read_xt %s\n", pb_share->sh_table_path->ps_path); + XT_DISABLED_TRACE(("search tx=%d val=%d update=%d\n", (int) pb_open_tab->ot_thread->st_xact_data->xd_start_xn_id, (int) XT_GET_DISK_4(key), pb_modified)); + ind = (XTIndexPtr) pb_share->sh_dic_keys[idx]; + + switch (find_flag) { + case HA_READ_PREFIX_LAST: + case HA_READ_PREFIX_LAST_OR_PREV: + prefix = SEARCH_PREFIX; + case HA_READ_BEFORE_KEY: + case HA_READ_KEY_OR_PREV: // I assume you want to be positioned on the last entry in the key duplicate list!! + xt_idx_prep_key(ind, &search_key, ((find_flag == HA_READ_BEFORE_KEY) ? 0 : XT_SEARCH_AFTER_KEY) | prefix, (xtWord1 *) key, (size_t) key_len); + if (!xt_idx_search_prev(pb_open_tab, ind, &search_key)) + err = ha_log_pbxt_thread_error_for_mysql(pb_ignore_dup_key); + else + err = xt_index_prev_read(pb_open_tab, ind, pb_key_read, + (find_flag == HA_READ_PREFIX_LAST) ? &search_key : NULL, buf); + break; + case HA_READ_PREFIX: + prefix = SEARCH_PREFIX; + case HA_READ_KEY_EXACT: + case HA_READ_KEY_OR_NEXT: + case HA_READ_AFTER_KEY: + default: + xt_idx_prep_key(ind, &search_key, ((find_flag == HA_READ_AFTER_KEY) ? XT_SEARCH_AFTER_KEY : 0) | prefix, (xtWord1 *) key, key_len); + if (!xt_idx_search(pb_open_tab, ind, &search_key)) + err = ha_log_pbxt_thread_error_for_mysql(pb_ignore_dup_key); + else + err = xt_index_next_read(pb_open_tab, ind, pb_key_read, + (find_flag == HA_READ_KEY_EXACT || find_flag == HA_READ_PREFIX) ? &search_key : NULL, buf); + break; + } + + pb_ind_row_count++; +#ifdef XT_TRACK_RETURNED_ROWS + if (!err) + ha_return_row(pb_open_tab, idx); +#endif + XT_DISABLED_TRACE(("search tx=%d val=%d err=%d\n", (int) pb_open_tab->ot_thread->st_xact_data->xd_start_xn_id, (int) XT_GET_DISK_4(key), err)); + if (err) + table->status = STATUS_NOT_FOUND; + else { + pb_open_tab->ot_thread->st_statistics.st_row_select++; + table->status = 0; + } + return err; +} + +/* + * 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. + */ +int ha_pbxt::index_read(byte * buf, const byte * key, uint key_len __attribute__((unused)), enum ha_rkey_function find_flag __attribute__((unused))) +{ + //statistic_increment(ha_read_key_count,&LOCK_status); + return index_read_xt(buf, active_index, key, key_len, find_flag); +} + +int ha_pbxt::index_read_idx(byte * buf, uint idx, const byte *key, uint key_len __attribute__((unused)), enum ha_rkey_function find_flag __attribute__((unused))) +{ + //statistic_increment(ha_read_key_count,&LOCK_status); + return index_read_xt(buf, idx, key, key_len, find_flag); +} + +int ha_pbxt::index_read_last(byte * buf, const byte * key, uint key_len) +{ + //statistic_increment(ha_read_key_count,&LOCK_status); + return index_read_xt(buf, active_index, key, key_len, HA_READ_PREFIX_LAST); +} + +/* + * Used to read forward through the index. + */ +int ha_pbxt::index_next(byte * buf) +{ + int err = 0; + XTIndexPtr ind; + + XT_TRACE_CALL(); + //statistic_increment(ha_read_next_count,&LOCK_status); + ASSERT_NS(pb_ex_in_use); + + ind = (XTIndexPtr) pb_share->sh_dic_keys[active_index]; + + if (!xt_idx_next(pb_open_tab, ind, NULL)) + err = ha_log_pbxt_thread_error_for_mysql(pb_ignore_dup_key); + else + err = xt_index_next_read(pb_open_tab, ind, pb_key_read, NULL, buf); + + pb_ind_row_count++; +#ifdef XT_TRACK_RETURNED_ROWS + if (!err) + ha_return_row(pb_open_tab, active_index); +#endif + if (err) + table->status = STATUS_NOT_FOUND; + else { + pb_open_tab->ot_thread->st_statistics.st_row_select++; + table->status = 0; + } + XT_RETURN(err); +} + +/* + * I have implemented this because there is currently a + * bug in handler::index_next_same(). + * + * drop table if exists t1; + * CREATE TABLE t1 (a int, b int, primary key(a,b)) + * PARTITION BY KEY(b,a) PARTITIONS 2; + * insert into t1 values (0,0),(1,1),(2,2),(3,3),(4,4),(5,5),(6,6); + * select * from t1 where a = 4; + * + */ +int ha_pbxt::index_next_same(byte * buf, const byte *key, uint length) +{ + int err = 0; + XTIndexPtr ind; + XTIdxSearchKeyRec search_key; + + XT_TRACE_CALL(); + //statistic_increment(ha_read_next_count,&LOCK_status); + ASSERT_NS(pb_ex_in_use); + + ind = (XTIndexPtr) pb_share->sh_dic_keys[active_index]; + + search_key.sk_key_value.sv_flags = HA_READ_KEY_EXACT; + search_key.sk_key_value.sv_rec_id = 0; + search_key.sk_key_value.sv_row_id = 0; + search_key.sk_key_value.sv_key = search_key.sk_key_buf; + search_key.sk_key_value.sv_length = myxt_create_key_from_key(ind, search_key.sk_key_buf, (xtWord1 *) key, (u_int) length); + search_key.sk_on_key = TRUE; + + if (!xt_idx_next(pb_open_tab, ind, &search_key)) + err = ha_log_pbxt_thread_error_for_mysql(pb_ignore_dup_key); + else + err = xt_index_next_read(pb_open_tab, ind, pb_key_read, &search_key, buf); + + pb_ind_row_count++; +#ifdef XT_TRACK_RETURNED_ROWS + if (!err) + ha_return_row(pb_open_tab, active_index); +#endif + if (err) + table->status = STATUS_NOT_FOUND; + else { + pb_open_tab->ot_thread->st_statistics.st_row_select++; + table->status = 0; + } + XT_RETURN(err); +} + +/* + * Used to read backwards through the index. + */ +int ha_pbxt::index_prev(byte * buf) +{ + int err = 0; + XTIndexPtr ind; + + XT_TRACE_CALL(); + //statistic_increment(ha_read_prev_count,&LOCK_status); + ASSERT_NS(pb_ex_in_use); + + ind = (XTIndexPtr) pb_share->sh_dic_keys[active_index]; + + if (!xt_idx_prev(pb_open_tab, ind, NULL)) + err = ha_log_pbxt_thread_error_for_mysql(pb_ignore_dup_key); + else + err = xt_index_prev_read(pb_open_tab, ind, pb_key_read, NULL, buf); + + pb_ind_row_count++; +#ifdef XT_TRACK_RETURNED_ROWS + if (!err) + ha_return_row(pb_open_tab, active_index); +#endif + if (err) + table->status = STATUS_NOT_FOUND; + else { + pb_open_tab->ot_thread->st_statistics.st_row_select++; + table->status = 0; + } + XT_RETURN(err); +} + +/* + * index_first() asks for the first key in the index. + */ +int ha_pbxt::index_first(byte * buf) +{ + int err = 0; + XTIndexPtr ind; + XTIdxSearchKeyRec search_key; + + XT_TRACE_CALL(); + //statistic_increment(ha_read_first_count,&LOCK_status); + ASSERT_NS(pb_ex_in_use); + +#ifdef XT_TRACK_RETURNED_ROWS + ha_start_scan(pb_open_tab, active_index); +#endif + pb_ind_row_count = 0; + + ind = (XTIndexPtr) pb_share->sh_dic_keys[active_index]; + + xt_idx_prep_key(ind, &search_key, XT_SEARCH_FIRST_FLAG, NULL, 0); + if (!xt_idx_search(pb_open_tab, ind, &search_key)) + err = ha_log_pbxt_thread_error_for_mysql(pb_ignore_dup_key); + else + err = xt_index_next_read(pb_open_tab, ind, pb_key_read, NULL, buf); + + pb_ind_row_count++; +#ifdef XT_TRACK_RETURNED_ROWS + if (!err) + ha_return_row(pb_open_tab, active_index); +#endif + if (err) + table->status = STATUS_NOT_FOUND; + else { + pb_open_tab->ot_thread->st_statistics.st_row_select++; + table->status = 0; + } + XT_RETURN(err); +} + +/* + * index_last() asks for the last key in the index. + */ +int ha_pbxt::index_last(byte * buf) +{ + int err = 0; + XTIndexPtr ind; + XTIdxSearchKeyRec search_key; + + XT_TRACE_CALL(); + //statistic_increment(ha_read_last_count,&LOCK_status); + ASSERT_NS(pb_ex_in_use); + +#ifdef XT_TRACK_RETURNED_ROWS + ha_start_scan(pb_open_tab, active_index); +#endif + pb_ind_row_count = 0; + + ind = (XTIndexPtr) pb_share->sh_dic_keys[active_index]; + + xt_idx_prep_key(ind, &search_key, XT_SEARCH_AFTER_LAST_FLAG, NULL, 0); + if (!xt_idx_search_prev(pb_open_tab, ind, &search_key)) + err = ha_log_pbxt_thread_error_for_mysql(pb_ignore_dup_key); + else + err = xt_index_prev_read(pb_open_tab, ind, pb_key_read, NULL, buf); + + pb_ind_row_count++; +#ifdef XT_TRACK_RETURNED_ROWS + if (!err) + ha_return_row(pb_open_tab, active_index); +#endif + if (err) + table->status = STATUS_NOT_FOUND; + else { + pb_open_tab->ot_thread->st_statistics.st_row_select++; + table->status = 0; + } + XT_RETURN(err); +} + +/* + * ----------------------------------------------------------------------- + * RAMDOM/SEQUENTIAL READ METHODS + */ + +/* + * rnd_init() is called when the system wants the storage engine to do a table + * scan. + * See the example in the introduction at the top of this file to see when + * rnd_init() is called. + * + * Called from filesort.cc, records.cc, sql_handler.cc, sql_select.cc, sql_table.cc, + * and sql_update.cc. + */ +int ha_pbxt::rnd_init(bool scan) +{ + int err = 0; + + XT_PRINT1(pb_open_tab->ot_thread, "ha_pbxt::rnd_init %s\n", pb_share->sh_table_path->ps_path); + XT_DISABLED_TRACE(("seq scan tx=%d\n", (int) pb_open_tab->ot_thread->st_xact_data->xd_start_xn_id)); + + /* The number of columns required: */ + if (pb_open_tab->ot_is_modify) + pb_open_tab->ot_cols_req = table->read_set->n_bits; + else { + pb_open_tab->ot_cols_req = ha_get_max_bit(table->read_set); + + /* + * in case of queries like SELECT COUNT(*) FROM t + * table->read_set is empty. Otoh, ot_cols_req == 0 can be treated + * as "all columns" by some internal code (see e.g. myxt_load_row), + * which makes such queries very ineffective for the records with + * extended part. Setting column count to 1 makes sure that the + * extended part will not be acessed in most cases. + */ + + if (pb_open_tab->ot_cols_req == 0) + pb_open_tab->ot_cols_req = 1; + } + + ASSERT_NS(pb_ex_in_use); + if (scan) { + if (!xt_tab_seq_init(pb_open_tab)) + err = ha_log_pbxt_thread_error_for_mysql(pb_ignore_dup_key); + } + else + xt_tab_seq_reset(pb_open_tab); + + xt_xlog_check_long_writer(pb_open_tab->ot_thread); + + return err; +} + +int ha_pbxt::rnd_end() +{ + XT_TRACE_CALL(); + + /* + * make permanent the lock for the last scanned row + */ + XTThreadPtr thread = pb_open_tab->ot_thread; + if (pb_open_tab) + pb_open_tab->ot_table->tab_locks.xt_make_lock_permanent(pb_open_tab, &thread->st_lock_list); + + xt_xlog_check_long_writer(thread); + + xt_tab_seq_exit(pb_open_tab); + XT_RETURN(0); +} + +/* + * This is called for each row of the table scan. When you run out of records + * you should return HA_ERR_END_OF_FILE. Fill buff up with the row information. + * 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_pbxt::rnd_next(byte *buf) +{ + int err = 0; + xtBool eof; + + XT_TRACE_CALL(); + ASSERT_NS(pb_ex_in_use); + //statistic_increment(ha_read_rnd_next_count, &LOCK_status); + xt_xlog_check_long_writer(pb_open_tab->ot_thread); + + if (!xt_tab_seq_next(pb_open_tab, (xtWord1 *) buf, &eof)) + err = ha_log_pbxt_thread_error_for_mysql(pb_ignore_dup_key); + else if (eof) + err = HA_ERR_END_OF_FILE; + + if (err) + table->status = STATUS_NOT_FOUND; + else { + pb_open_tab->ot_thread->st_statistics.st_row_select++; + table->status = 0; + } + XT_RETURN(err); +} + +/* + * 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_pbxt::position(const byte *record __attribute__((unused))) +{ + XT_TRACE_CALL(); + ASSERT_NS(pb_ex_in_use); + /* + * I changed this from using little endian to big endian. + * + * The reason is because sometime the pointer are sorted. + * When they are are sorted a binary compare is used. + * A binary compare sorts big endian values correctly! + * + * Take the followin example: + * + * create table t1 (a int, b text); + * insert into t1 values (1, 'aa'), (1, 'bb'), (1, 'cc'); + * select group_concat(b) from t1 group by a; + * + * With little endian pointers the result is: + * aa,bb,cc + * + * With big-endian pointer the result is: + * aa,cc,bb + * + */ + (void) ASSERT_NS(XT_RECORD_OFFS_SIZE == 4); + mi_int4store((xtWord1 *) ref, pb_open_tab->ot_curr_rec_id); + XT_RETURN_VOID; +} + +/* + * Given the #ROWID retrieve the record. + * + * Called from filesort.cc records.cc sql_insert.cc sql_select.cc sql_update.cc. + */ +int ha_pbxt::rnd_pos(byte * buf, byte *pos) +{ + int err = 0; + + XT_TRACE_CALL(); + ASSERT_NS(pb_ex_in_use); + //statistic_increment(ha_read_rnd_count, &LOCK_status); + XT_PRINT1(pb_open_tab->ot_thread, "ha_pbxt::rnd_pos %s\n", pb_share->sh_table_path->ps_path); + + pb_open_tab->ot_curr_rec_id = mi_uint4korr((xtWord1 *) pos); + switch (xt_tab_dirty_read_record(pb_open_tab, (xtWord1 *) buf)) { + case FALSE: + err = ha_log_pbxt_thread_error_for_mysql(pb_ignore_dup_key); + break; + default: + break; + } + + if (err) + table->status = STATUS_NOT_FOUND; + else { + pb_open_tab->ot_thread->st_statistics.st_row_select++; + table->status = 0; + } + XT_RETURN(err); +} + +/* + * ----------------------------------------------------------------------- + * INFO METHODS + */ + +/* + ::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, 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 + +*/ +#if MYSQL_VERSION_ID < 50114 +void ha_pbxt::info(uint flag) +#else +int ha_pbxt::info(uint flag) +#endif +{ + XTOpenTablePtr ot; + int in_use; + + XT_TRACE_CALL(); + + if (!(in_use = pb_ex_in_use)) { + pb_ex_in_use = 1; + if (pb_share && pb_share->sh_table_lock) { + /* If some thread has an exclusive lock, then + * we wait for the lock to be removed: + */ +#if MYSQL_VERSION_ID < 50114 + ha_wait_for_shared_use(this, pb_share); + pb_ex_in_use = 1; +#else + if (!ha_wait_for_shared_use(this, pb_share)) + return ha_log_pbxt_thread_error_for_mysql(pb_ignore_dup_key); +#endif + } + } + + if ((ot = pb_open_tab)) { + if (flag & HA_STATUS_VARIABLE) { + stats.deleted = ot->ot_table->tab_row_fnum; + stats.records = (ha_rows) (ot->ot_table->tab_row_eof_id - 1 - stats.deleted); + stats.data_file_length = ot->ot_table->tab_rec_eof_id; + stats.index_file_length = xt_ind_node_to_offset(ot->ot_table, ot->ot_table->tab_ind_eof); + stats.delete_length = ot->ot_table->tab_rec_fnum * ot->ot_rec_size; + //check_time = info.check_time; + stats.mean_rec_length = ot->ot_rec_size; + } + + if (flag & HA_STATUS_CONST) { + ha_rows rec_per_key; + XTIndexPtr ind; + TABLE_SHARE *share= TS(table); + + stats.max_data_file_length = 0x00FFFFFF; + stats.max_index_file_length = 0x00FFFFFF; + //stats.create_time = info.create_time; + ref_length = XT_RECORD_OFFS_SIZE; + //share->db_options_in_use = info.options; + stats.block_size = XT_INDEX_PAGE_SIZE; + + if (share->tmp_table == NO_TMP_TABLE) +#if MYSQL_VERSION_ID > 60005 +#define WHICH_MUTEX LOCK_ha_data +#else +#define WHICH_MUTEX mutex +#endif + +#ifdef SAFE_MUTEX + +#if MYSQL_VERSION_ID < 60000 +#if MYSQL_VERSION_ID < 50123 + safe_mutex_lock(&share->mutex,__FILE__,__LINE__); +#else + safe_mutex_lock(&share->mutex,0,__FILE__,__LINE__); +#endif +#else +#if MYSQL_VERSION_ID < 60004 + safe_mutex_lock(&share->mutex,__FILE__,__LINE__); +#else + safe_mutex_lock(&share->WHICH_MUTEX,0,__FILE__,__LINE__); +#endif +#endif + +#else // SAFE_MUTEX + +#ifdef MY_PTHREAD_FASTMUTEX + my_pthread_fastmutex_lock(&share->WHICH_MUTEX); +#else + pthread_mutex_lock(&share->WHICH_MUTEX); +#endif + +#endif // SAFE_MUTEX + share->keys_in_use.set_prefix(share->keys); + //share->keys_in_use.intersect_extended(info.key_map); + share->keys_for_keyread.intersect(share->keys_in_use); + //share->db_record_offset = info.record_offset; + for (u_int i = 0; i < share->keys; i++) { + ind = pb_share->sh_dic_keys[i]; + + rec_per_key = 0; + if (ind->mi_seg_count == 1 && (ind->mi_flags & HA_NOSAME)) + rec_per_key = 1; + else { + + } + for (u_int j = 0; j < table->key_info[i].key_parts; j++) + table->key_info[i].rec_per_key[j] = (ulong) rec_per_key; + } + if (share->tmp_table == NO_TMP_TABLE) +#ifdef SAFE_MUTEX + safe_mutex_unlock(&share->WHICH_MUTEX,__FILE__,__LINE__); +#else +#ifdef MY_PTHREAD_FASTMUTEX + pthread_mutex_unlock(&share->WHICH_MUTEX.mutex); +#else + pthread_mutex_unlock(&share->WHICH_MUTEX); +#endif +#endif + /* + Set data_file_name and index_file_name to point at the symlink value + if table is symlinked (Ie; Real name is not same as generated name) + */ + /* + data_file_name = index_file_name = 0; + fn_format(name_buff, file->filename, "", MI_NAME_DEXT, 2); + if (strcmp(name_buff, info.data_file_name)) + data_file_name = info.data_file_name; + strmov(fn_ext(name_buff), MI_NAME_IEXT); + if (strcmp(name_buff, info.index_file_name)) + index_file_name = info.index_file_name; + */ + } + + if (flag & HA_STATUS_ERRKEY) + errkey = ot->ot_err_index_no; + + /* {PRE-INC} + * We assume they want the next value to be returned! + * + * At least, this is what works for the following code: + * + * create table t1 (a int auto_increment primary key) + * auto_increment=100 + * engine=pbxt + * partition by list (a) + * (partition p0 values in (1, 98,99, 100, 101)); + * create index inx on t1 (a); + * insert into t1 values (null); + * select * from t1; + */ + if (flag & HA_STATUS_AUTO) + stats.auto_increment_value = (ulonglong) ot->ot_table->tab_auto_inc+1; + } + else + errkey = (uint) -1; + + if (!in_use) { + pb_ex_in_use = 0; + if (pb_share) { + /* Someone may be waiting for me to complete: */ + if (pb_share->sh_table_lock) + xt_broadcast_cond_ns((xt_cond_type *) pb_share->sh_ex_cond); + } + } +#if MYSQL_VERSION_ID < 50114 + XT_RETURN_VOID; +#else + XT_RETURN(0); +#endif +} + +/* + * extra() is called whenever the server wishes to send a hint to + * the storage engine. The myisam engine implements the most hints. + * ha_innodb.cc has the most exhaustive list of these hints. + */ +int ha_pbxt::extra(enum ha_extra_function operation) +{ + int err = 0; + + XT_PRINT2(xt_get_self(), "ha_pbxt::extra %s operation=%d\n", pb_share->sh_table_path->ps_path, operation); + + switch (operation) { + case HA_EXTRA_RESET_STATE: + pb_key_read = FALSE; + pb_ignore_dup_key = 0; + /* As far as I can tell, this function is called for + * every table at the end of a statement. + * + * So, during a LOCK TABLES ... UNLOCK TABLES, I use + * this to find the end of a statement. + * start_stmt() indicates the start of a statement, + * and is also called once for each table in the + * statement. + * + * So the statement boundary is indicated by + * self->st_stat_count == 0 + * + * GOTCHA: I cannot end the transaction here! + * I must end it in start_stmt(). + * The reason is because there are situations + * where this would end a transaction that + * was begin by external_lock(). + * + * An example of this is when a function + * is called when doing CREATE TABLE SELECT. + */ + if (pb_in_stat) { + /* NOTE: pb_in_stat is just used to avoid getting + * self, if it is not necessary!! + */ + XTThreadPtr self; + + pb_in_stat = FALSE; + + if (!(self = ha_set_current_thread(pb_mysql_thd, &err))) + return xt_ha_pbxt_to_mysql_error(err); + + if (self->st_stat_count > 0) { + self->st_stat_count--; + if (self->st_stat_count == 0) + self->st_stat_ended = TRUE; + } + + /* This is the end of a statement, I can turn any locks into perminant locks now: */ + if (pb_open_tab) + pb_open_tab->ot_table->tab_locks.xt_make_lock_permanent(pb_open_tab, &self->st_lock_list); + } + break; + case HA_EXTRA_KEYREAD: + /* This means we so not need to read the entire record. */ + pb_key_read = TRUE; + break; + case HA_EXTRA_NO_KEYREAD: + pb_key_read = FALSE; + break; + case HA_EXTRA_IGNORE_DUP_KEY: + /* NOTE!!! Calls to extra(HA_EXTRA_IGNORE_DUP_KEY) can be nested! + * In fact, the calls are from different threads, so + * strictly speaking I should protect this variable!! + * Here is the sequence that produces the duplicate call: + * + * drop table if exists t1; + * CREATE TABLE t1 (x int not null, y int, primary key (x)) engine=pbxt; + * insert into t1 values (1, 3), (4, 1); + * replace DELAYED into t1 (x, y) VALUES (4, 2); + * select * from t1 order by x; + * + */ + pb_ignore_dup_key++; + break; + case HA_EXTRA_NO_IGNORE_DUP_KEY: + pb_ignore_dup_key--; + break; + case HA_EXTRA_KEYREAD_PRESERVE_FIELDS: + /* MySQL needs all fields */ + pb_key_read = FALSE; + break; + default: + break; + } + + return err; +} + + +/* + * Deprecated and likely to be removed in the future. Storage engines normally + * just make a call like: + * ha_pbxt::extra(HA_EXTRA_RESET); + * to handle it. + */ +int ha_pbxt::reset(void) +{ + XT_TRACE_CALL(); + extra(HA_EXTRA_RESET_STATE); + XT_RETURN(0); +} + +void ha_pbxt::unlock_row() +{ + XT_TRACE_CALL(); + if (pb_open_tab) + pb_open_tab->ot_table->tab_locks.xt_remove_temp_lock(pb_open_tab, FALSE); +} + +/* + * 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::reinit(). + * Called from sql_union.cc by st_select_lex_unit::exec(). + */ +int ha_pbxt::delete_all_rows() +{ + THD *thd = current_thd; + int err = 0; + XTThreadPtr self; + XTDDTable *tab_def = NULL; + char path[PATH_MAX]; + + XT_TRACE_CALL(); + + if (thd_sql_command(thd) != SQLCOM_TRUNCATE) { + /* Just like InnoDB we only handle TRUNCATE TABLE + * by recreating the table. + * DELETE FROM t must be handled by deleting + * each row because it may be part of a transaction, + * and there may be foreign key actions. + */ + XT_RETURN (my_errno = HA_ERR_WRONG_COMMAND); + } + + if (!(self = ha_set_current_thread(thd, &err))) + return xt_ha_pbxt_to_mysql_error(err); + + try_(a) { + XTDictionaryRec dic; + + memset(&dic, 0, sizeof(dic)); + + dic = pb_share->sh_table->tab_dic; + xt_strcpy(PATH_MAX, path, pb_share->sh_table->tab_name->ps_path); + + if ((tab_def = dic.dic_table)) + tab_def->reference(); + + if (!(thd_test_options(thd,OPTION_NO_FOREIGN_KEY_CHECKS))) + tab_def->deleteAllRows(self); + + /* We should have a table lock! */ + //ASSERT(pb_lock_table); + if (!pb_table_locked) { + ha_aquire_exclusive_use(self, pb_share, this); + pushr_(ha_release_exclusive_use, pb_share); + } + ha_close_open_tables(self, pb_share, NULL); + + /* This is required in the case of delete_all_rows, because we must + * ensure that the handlers no longer reference the old + * table, so that it will not be used again. The table + * must be re-openned, because the ID has changed! + * + * 0.9.86+ Must check if this is still necessary. + * + * the ha_close_share(self, pb_share) call was moved from above + * (before tab_def = dic.dic_table), because of a crash. + * Test case: + * + * set storage_engine = pbxt; + * create table t1 (s1 int primary key); + * insert into t1 values (1); + * create table t2 (s1 int, foreign key (s1) references t1 (s1)); + * insert into t2 values (1); + * truncate table t1; -- this should fail because of FK constraint + * alter table t1 engine = myisam; -- this caused crash + * + */ + ha_close_share(self, pb_share); + + xt_create_table(self, (XTPathStrPtr) path, &dic); + if (!pb_table_locked) + freer_(); // ha_release_exclusive_use(pb_share) + } + catch_(a) { + err = xt_ha_pbxt_thread_error_for_mysql(thd, self, pb_ignore_dup_key); + } + cont_(a); + + if (tab_def) + tab_def->release(self); + + XT_RETURN(err); +} + +/* + * TODO: Implement! + * Assuming a key (a,b,c) + * + * rec_per_key[0] = SELECT COUNT(*)/COUNT(DISTINCT a) FROM t; + * rec_per_key[1] = SELECT COUNT(*)/COUNT(DISTINCT a,b) FROM t; + * rec_per_key[2] = SELECT COUNT(*)/COUNT(DISTINCT a,b,c) FROM t; + * + * After this is implemented, the selectivity can serve as + * a quick estimate of records_in_range(). + * + * After you have done this, you need to redo the index_merge* + * tests. Restore the standard result to check if we + * now agree with the MyISAM strategy. + * + */ +int ha_pbxt::analyze(THD *thd __attribute__((unused)), HA_CHECK_OPT *check_opt __attribute__((unused))) +{ + int err = 0; + XTDatabaseHPtr db; + xtXactID my_xn_id; + xtXactID clean_xn_id = 0; + uint cnt = 10; + + XT_TRACE_CALL(); + + if (!pb_open_tab) { + if ((err = reopen())) + XT_RETURN(err); + } + + /* Wait until the sweeper is no longer busy! + * If you want an accurate count(*) value, then call + * ANALYZE TABLE first. This function waits until the + * sweeper has completed. + */ + db = pb_open_tab->ot_table->tab_db; + + /* + * Wait until everything is cleaned up before this transaction. + * But this will only work if the we quit out transaction! + * + * GOTCHA: When a PBXT table is partitioned, then analyze() is + * called for each component. The first calls xt_xn_commit(). + * All following calls have no transaction!: + * + * CREATE TABLE t1 (a int) + * PARTITION BY LIST (a) + * (PARTITION x1 VALUES IN (10), PARTITION x2 VALUES IN (20)); + * + * analyze table t1; + * + */ + if (pb_open_tab->ot_thread && pb_open_tab->ot_thread->st_xact_data) { + my_xn_id = pb_open_tab->ot_thread->st_xact_data->xd_start_xn_id; + XT_PRINT0(xt_get_self(), "xt_xn_commit\n"); + xt_xn_commit(pb_open_tab->ot_thread); + } + else + my_xn_id = db->db_xn_to_clean_id; + + while ((!db->db_sw_idle || xt_xn_is_before(db->db_xn_to_clean_id, my_xn_id)) && !thd_killed(thd)) { + xt_busy_wait(); + + /* + * It is possible that the sweeper gets stuck because + * it has no dictionary information! + * As in the example below. + * + * create table t4 ( + * pk_col int auto_increment primary key, a1 char(64), a2 char(64), b char(16), c char(16) not null, d char(16), dummy char(64) default ' ' + * ) engine=pbxt; + * + * insert into t4 (a1, a2, b, c, d, dummy) select * from t1; + * + * create index idx12672_0 on t4 (a1); + * create index idx12672_1 on t4 (a1,a2,b,c); + * create index idx12672_2 on t4 (a1,a2,b); + * analyze table t1; + */ + if (db->db_sw_idle) { + /* This will make sure we don't wait forever: */ + if (clean_xn_id != db->db_xn_to_clean_id) { + clean_xn_id = db->db_xn_to_clean_id; + cnt = 10; + } + else { + cnt--; + if (!cnt) + break; + } + xt_wakeup_sweeper(db); + } + } + + XT_RETURN(err); +} + +int ha_pbxt::repair(THD *thd __attribute__((unused)), HA_CHECK_OPT *check_opt __attribute__((unused))) +{ + return(HA_ADMIN_TRY_ALTER); +} + +/* + * This is mapped to "ALTER TABLE tablename TYPE=PBXT", which rebuilds + * the table in MySQL. + */ +int ha_pbxt::optimize(THD *thd __attribute__((unused)), HA_CHECK_OPT *check_opt __attribute__((unused))) +{ + return(HA_ADMIN_TRY_ALTER); +} + +#ifdef DEBUG +extern int pbxt_mysql_trace_on; +#endif + +int ha_pbxt::check(THD* thd, HA_CHECK_OPT* check_opt __attribute__((unused))) +{ + int err = 0; + XTThreadPtr self; + + if (!(self = ha_set_current_thread(thd, &err))) + return xt_ha_pbxt_to_mysql_error(err); + if (self->st_lock_count) + ASSERT(self->st_xact_data); + + if (!pb_table_locked) { + ha_aquire_exclusive_use(self, pb_share, this); + pushr_(ha_release_exclusive_use, pb_share); + } + +#ifdef CHECK_TABLE_LOADS + xt_tab_load_table(self, pb_open_tab); +#endif + xt_check_table(self, pb_open_tab); + + if (!pb_table_locked) + freer_(); // ha_release_exclusive_use(pb_share) + + //pbxt_mysql_trace_on = TRUE; + return 0; +} + +/* + * This function is called: + * For each table in LOCK TABLES, + * OR + * For each table in a statement. + * + * It is called with F_UNLCK: + * in UNLOCK TABLES + * OR + * at the end of a statement. + * + */ +xtPublic int ha_pbxt::external_lock(THD *thd, int lock_type) +{ + int err = 0; + XTThreadPtr self; + + if (!(self = ha_set_current_thread(thd, &err))) + return xt_ha_pbxt_to_mysql_error(err); + + /* F_UNLCK is set when this function is called at end + * of statement or UNLOCK TABLES + */ + if (lock_type == F_UNLCK) { + /* This is not TRUE if external_lock() FAILED! + * Can we rely on external_unlock being called when + * external_lock() fails? Currently yes, but it does + * not make sense! + ASSERT_NS(pb_ex_in_use); + */ + + XT_PRINT1(self, "ha_pbxt::EXTERNAL_LOCK %s lock_type=UNLOCK\n", pb_share->sh_table_path->ps_path); + + /* Make any temporary locks on this table permanent. + * + * This is required here because of the following example: + * create table t1 (a int NOT NULL, b int, primary key (a)); + * create table t2 (a int NOT NULL, b int, primary key (a)); + * insert into t1 values (0, 10),(1, 11),(2, 12); + * insert into t2 values (1, 21),(2, 22),(3, 23); + * update t1 set b= (select b from t2 where t1.a = t2.a); + * update t1 set b= (select b from t2 where t1.a = t2.a); + * select * from t1; + * drop table t1, t2; + * + */ + + /* GOTCHA! It's weird, but, if this function returns an error + * on lock, then UNLOCK is called?! + * This should not be done, because if lock fails, it should be + * assumed that no UNLOCK is required. + * Basically, I have to assume that some code will presume this, + * although the function lock_external() calls unlock, even + * when lock fails. + * The result is, that my lock count can go wrong. So I could + * change the lock method, and increment the lock count, even + * if it fails. However, the consequences are more serious, + * if some code decides not to call UNLOCK after lock fails. + * The result is that I would have a permanent too high lock, + * count and nothing will work. + * So instead, I handle the fact that I might too many unlocks + * here. + */ + if (self->st_lock_count > 0) + self->st_lock_count--; + if (!self->st_lock_count) { + /* This section handles "auto-commit"... */ + +#ifdef XT_IMPLEMENT_NO_ACTION + /* {NO-ACTION-BUG} + * This is required here because it marks the end of a statement. + * If we are in a non-auto-commit mode, then we cannot + * wait for st_is_update to be set by the begining of a new transaction. + */ + if (self->st_restrict_list.bl_count) { + if (!xt_tab_restrict_rows(&self->st_restrict_list, self)) + err = xt_ha_pbxt_thread_error_for_mysql(thd, self, pb_ignore_dup_key); + } +#endif + + if (self->st_xact_data) { + if (self->st_auto_commit) { + /* + * Normally I could assume that if the transaction + * has not been aborted by now, then it should be committed. + * + * Unfortunately, this is not the case! + * + * create table t1 (id int primary key) engine = pbxt; + * create table t2 (id int) engine = pbxt; + * + * insert into t1 values ( 1 ) ; + * insert into t1 values ( 2 ) ; + * insert into t2 values ( 1 ) ; + * insert into t2 values ( 2 ) ; + * + * --This statement is returns an error calls ha_autocommit_or_rollback(): + * update t1 set t1.id=1 where t1.id=2; + * + * --This statement is returns no error and calls ha_autocommit_or_rollback(): + * update t1,t2 set t1.id=3, t2.id=3 where t1.id=2 and t2.id = t1.id; + * + * --But this statement returns an error and does not call ha_autocommit_or_rollback(): + * update t1,t2 set t1.id=1, t2.id=1 where t1.id=3 and t2.id = t1.id; + * + * The result is, I cannot rely on ha_autocommit_or_rollback() being called :( + * So I have to abort myself here... + */ + if (pb_open_tab) + pb_open_tab->ot_table->tab_locks.xt_make_lock_permanent(pb_open_tab, &self->st_lock_list); + + if (self->st_abort_trans) { + XT_PRINT0(self, "xt_xn_rollback in unlock\n"); + if (!xt_xn_rollback(self)) + err = xt_ha_pbxt_thread_error_for_mysql(thd, self, pb_ignore_dup_key); + } + else { + XT_PRINT0(self, "xt_xn_commit in unlock\n"); + if (!xt_xn_commit(self)) + err = xt_ha_pbxt_thread_error_for_mysql(thd, self, pb_ignore_dup_key); + } + } + } + + /* If the previous statement was "for update", then set the visibilty + * so that non- for update SELECTs will see what the for update select + * (or update statement) just saw. + */ + if (pb_open_tab) { + if (pb_open_tab->ot_for_update) + self->st_visible_time = self->st_database->db_xn_end_time; + + if (pb_share->sh_recalc_selectivity) { + if ((pb_share->sh_table->tab_row_eof_id - 1 - pb_share->sh_table->tab_row_fnum) >= 200) { + /* [**] */ + pb_share->sh_recalc_selectivity = FALSE; + xt_ind_set_index_selectivity(self, pb_open_tab); + pb_share->sh_recalc_selectivity = (pb_share->sh_table->tab_row_eof_id - 1 - pb_share->sh_table->tab_row_fnum) < 150; + } + } + } + + if (self->st_stat_modify) + self->st_statistics.st_stat_write++; + else + self->st_statistics.st_stat_read++; + self->st_stat_modify = FALSE; + } + + if (pb_table_locked) { + pb_table_locked--; + if (!pb_table_locked) + ha_release_exclusive_use(self, pb_share); + } + + /* No longer in use: */ + pb_ex_in_use = 0; + /* Someone may be waiting for me to complete: */ + if (pb_share->sh_table_lock) + xt_broadcast_cond_ns((xt_cond_type *) pb_share->sh_ex_cond); + } + else { + XT_PRINT2(self, "ha_pbxt::EXTERNAL_LOCK %s lock_type=%d\n", pb_share->sh_table_path->ps_path, lock_type); + + if (pb_lock_table) { + + pb_ex_in_use = 1; + try_(a) { + if (!pb_table_locked) + ha_aquire_exclusive_use(self, pb_share, this); + pb_table_locked++; + + ha_close_open_tables(self, pb_share, this); + + if (!pb_share->sh_table) { + xt_ha_open_database_of_table(self, pb_share->sh_table_path); + + ha_open_share(self, pb_share, NULL); + } + } + catch_(a) { + err = xt_ha_pbxt_thread_error_for_mysql(thd, self, pb_ignore_dup_key); + pb_ex_in_use = 0; + goto complete; + } + cont_(a); + } + else { + pb_ex_in_use = 1; + if (pb_share->sh_table_lock && !pb_table_locked) { + /* If some thread has an exclusive lock, then + * we wait for the lock to be removed: + */ + if (!ha_wait_for_shared_use(this, pb_share)) { + err = ha_log_pbxt_thread_error_for_mysql(pb_ignore_dup_key); + goto complete; + } + } + + if (!pb_open_tab) { + if ((err = reopen())) { + pb_ex_in_use = 0; + goto complete; + } + } + + /* Set the current thread for this open table: */ + pb_open_tab->ot_thread = self; + + /* If this is a set, then it is in UPDATE/DELETE TABLE ... + * or SELECT ... FOR UPDATE + */ + pb_open_tab->ot_is_modify = FALSE; + if ((pb_open_tab->ot_for_update = (lock_type == F_WRLCK))) { + switch ((int) thd_sql_command(thd)) { + case SQLCOM_UPDATE: + case SQLCOM_UPDATE_MULTI: + case SQLCOM_DELETE: + case SQLCOM_DELETE_MULTI: + case SQLCOM_REPLACE: + case SQLCOM_REPLACE_SELECT: + case SQLCOM_INSERT: + case SQLCOM_INSERT_SELECT: + pb_open_tab->ot_is_modify = TRUE; + self->st_stat_modify = TRUE; + break; + case SQLCOM_CREATE_TABLE: + case SQLCOM_CREATE_INDEX: + case SQLCOM_ALTER_TABLE: + case SQLCOM_TRUNCATE: + case SQLCOM_DROP_TABLE: + case SQLCOM_DROP_INDEX: + case SQLCOM_LOAD: + case SQLCOM_REPAIR: + case SQLCOM_OPTIMIZE: + self->st_stat_modify = TRUE; + break; + } + } + + if (pb_open_tab->ot_is_modify && pb_open_tab->ot_table->tab_dic.dic_disable_index) { + xt_tab_set_index_error(pb_open_tab->ot_table); + err = ha_log_pbxt_thread_error_for_mysql(pb_ignore_dup_key); + goto complete; + } + } + + /* Record the associated MySQL thread: */ + pb_mysql_thd = thd; + + if (self->st_database != pb_share->sh_table->tab_db) { + try_(b) { + /* PBXT does not permit multiple databases us one statement, + * or in a single transaction! + * + * Example query: + * + * update mysqltest_1.t1, mysqltest_2.t2 set a=10,d=10; + */ + if (self->st_lock_count > 0) + xt_throw_xterr(XT_CONTEXT, XT_ERR_MULTIPLE_DATABASES); + + xt_ha_open_database_of_table(self, pb_share->sh_table_path); + } + catch_(b) { + err = xt_ha_pbxt_thread_error_for_mysql(thd, self, pb_ignore_dup_key); + pb_ex_in_use = 0; + goto complete; + } + cont_(b); + } + + /* See (***) */ + self->st_is_update = FALSE; + + /* Auto begin a transaction (if one is not already running): */ + if (!self->st_xact_data) { + /* Transaction mode numbers must be identical! */ + (void) ASSERT_NS(ISO_READ_UNCOMMITTED == XT_XACT_UNCOMMITTED_READ); + (void) ASSERT_NS(ISO_SERIALIZABLE == XT_XACT_SERIALIZABLE); + + self->st_xact_mode = thd_tx_isolation(thd) <= ISO_READ_COMMITTED ? XT_XACT_COMMITTED_READ : XT_XACT_REPEATABLE_READ; + self->st_ignore_fkeys = (thd_test_options(thd,OPTION_NO_FOREIGN_KEY_CHECKS)) != 0; + self->st_auto_commit = (thd_test_options(thd, (OPTION_NOT_AUTOCOMMIT | OPTION_BEGIN))) == 0; + self->st_table_trans = thd_sql_command(thd) == SQLCOM_LOCK_TABLES; + self->st_abort_trans = FALSE; + self->st_stat_ended = FALSE; + self->st_stat_trans = FALSE; + XT_PRINT0(self, "xt_xn_begin\n"); + if (!xt_xn_begin(self)) { + err = xt_ha_pbxt_thread_error_for_mysql(thd, self, pb_ignore_dup_key); + pb_ex_in_use = 0; + goto complete; + } + + /* + * (**) GOTCHA: trans_register_ha() is not mentioned in the documentation. + * It must be called to inform MySQL that we have a transaction (see start_stmt). + * + * Here are some tests that confirm whether things are done correctly: + * + * drop table if exists t1, t2; + * create table t1 (c1 int); + * insert t1 values (1); + * select * from t1; + * rename table t1 to t2; + * + * rename will generate an error if MySQL thinks a transaction is + * still running. + * + * create table t1 (a text character set utf8, b text character set latin1); + * insert t1 values (0x4F736E616272C3BC636B, 0x4BF66C6E); + * select * from t1; + * --exec $MYSQL_DUMP --tab=$MYSQLTEST_VARDIR/tmp/ test + * --exec $MYSQL test < $MYSQLTEST_VARDIR/tmp/t1.sql + * --exec $MYSQL_IMPORT test $MYSQLTEST_VARDIR/tmp/t1.txt + * select * from t1; + * + * This test forces a begin transaction in start_stmt() + * + * drop tables if exists t1; + * create table t1 (c1 int); + * lock tables t1 write; + * insert t1 values (1); + * insert t1 values (2); + * unlock tables; + * + * The second select will return an empty result of the + * MySQL is not informed that a transaction is running (auto-commit + * in external_lock comes too late)! + * + */ + if (!self->st_auto_commit) { + trans_register_ha(thd, TRUE, pbxt_hton); + XT_PRINT0(self, "ha_pbxt::external_lock trans_register_ha all=TRUE\n"); + } + } + + if (lock_type == F_WRLCK || self->st_xact_mode < XT_XACT_REPEATABLE_READ) + self->st_visible_time = self->st_database->db_xn_end_time; + +#ifdef TRACE_STATEMENTS + if (self->st_lock_count == 0) + STAT_TRACE(self, *thd_query(thd)); +#endif + self->st_lock_count++; + } + + complete: + return err; +} + +/* + * This function is called for each table in a statement + * after LOCK TABLES has been used. + * + * Currently I only use this function to set the + * current thread of the table handle. + * + * GOTCHA: The prototype of start_stmt() has changed + * from version 4.1 to 5.1! + */ +int ha_pbxt::start_stmt(THD *thd, thr_lock_type lock_type) +{ + int err = 0; + XTThreadPtr self; + + ASSERT_NS(pb_ex_in_use); + + if (!(self = ha_set_current_thread(thd, &err))) + return xt_ha_pbxt_to_mysql_error(err); + + XT_PRINT2(self, "ha_pbxt::start_stmt %s lock_type=%d\n", pb_share->sh_table_path->ps_path, (int) lock_type); + + if (!pb_open_tab) { + if ((err = reopen())) + goto complete; + } + + ASSERT_NS(pb_open_tab->ot_thread == self); + ASSERT_NS(thd == pb_mysql_thd); + ASSERT_NS(self->st_database == pb_open_tab->ot_table->tab_db); + + if (self->st_stat_ended) { + self->st_stat_ended = FALSE; + self->st_stat_trans = FALSE; + +#ifdef XT_IMPLEMENT_NO_ACTION + if (self->st_restrict_list.bl_count) { + if (!xt_tab_restrict_rows(&self->st_restrict_list, self)) { + err = xt_ha_pbxt_thread_error_for_mysql(pb_mysql_thd, self, pb_ignore_dup_key); + } + } +#endif + + /* This section handles "auto-commit"... */ + if (self->st_xact_data && self->st_auto_commit && self->st_table_trans) { + if (self->st_abort_trans) { + XT_PRINT0(self, "xt_xn_rollback\n"); + if (!xt_xn_rollback(self)) + err = xt_ha_pbxt_thread_error_for_mysql(pb_mysql_thd, self, pb_ignore_dup_key); + } + else { + XT_PRINT0(self, "xt_xn_commit\n"); + if (!xt_xn_commit(self)) + err = xt_ha_pbxt_thread_error_for_mysql(pb_mysql_thd, self, pb_ignore_dup_key); + } + } + + if (self->st_stat_modify) + self->st_statistics.st_stat_write++; + else + self->st_statistics.st_stat_read++; + self->st_stat_modify = FALSE; + + /* If the previous statement was "for update", then set the visibilty + * so that non- for update SELECTs will see what the for update select + * (or update statement) just saw. + */ + if (pb_open_tab->ot_for_update) + self->st_visible_time = self->st_database->db_xn_end_time; + } + + pb_open_tab->ot_for_update = + (lock_type != TL_READ && + lock_type != TL_READ_WITH_SHARED_LOCKS && + lock_type != TL_READ_HIGH_PRIORITY && + lock_type != TL_READ_NO_INSERT); + pb_open_tab->ot_is_modify = FALSE; + if (pb_open_tab->ot_for_update) { + switch ((int) thd_sql_command(thd)) { + case SQLCOM_UPDATE: + case SQLCOM_UPDATE_MULTI: + case SQLCOM_DELETE: + case SQLCOM_DELETE_MULTI: + case SQLCOM_REPLACE: + case SQLCOM_REPLACE_SELECT: + case SQLCOM_INSERT: + case SQLCOM_INSERT_SELECT: + pb_open_tab->ot_is_modify = TRUE; + self->st_stat_modify = TRUE; + break; + case SQLCOM_CREATE_TABLE: + case SQLCOM_CREATE_INDEX: + case SQLCOM_ALTER_TABLE: + case SQLCOM_TRUNCATE: + case SQLCOM_DROP_TABLE: + case SQLCOM_DROP_INDEX: + case SQLCOM_LOAD: + case SQLCOM_REPAIR: + case SQLCOM_OPTIMIZE: + self->st_stat_modify = TRUE; + break; + } + } + + + /* (***) This is required at this level! + * No matter how often it is called, it is still the start of a + * statement. We need to make sure statements that are NOT mistaken + * for different type of statement. + * + * Here is an example: + * select * from t1 where data = getcount("bar") + * + * If the procedure getcount() addresses another table. + * then open and close of the statements in getcount() + * are nested within an open close of the select t1 + * statement. + */ + self->st_is_update = FALSE; + + /* See comment (**) */ + if (!self->st_xact_data) { + self->st_xact_mode = thd_tx_isolation(thd) <= ISO_READ_COMMITTED ? XT_XACT_COMMITTED_READ : XT_XACT_REPEATABLE_READ; + self->st_ignore_fkeys = (thd_test_options(thd, OPTION_NO_FOREIGN_KEY_CHECKS)) != 0; + self->st_auto_commit = (thd_test_options(thd,(OPTION_NOT_AUTOCOMMIT | OPTION_BEGIN))) == 0; + /* self->st_table_trans = not set here! */ + self->st_abort_trans = FALSE; + self->st_stat_ended = FALSE; + self->st_stat_trans = FALSE; + XT_PRINT0(self, "xt_xn_begin\n"); + if (!xt_xn_begin(self)) { + err = xt_ha_pbxt_thread_error_for_mysql(thd, self, pb_ignore_dup_key); + goto complete; + } + if (!self->st_auto_commit) { + trans_register_ha(thd, TRUE, pbxt_hton); + XT_PRINT0(self, "ha_pbxt::start_stmt trans_register_ha all=TRUE\n"); + } + } + + if (pb_open_tab->ot_for_update || self->st_xact_mode < XT_XACT_REPEATABLE_READ) + self->st_visible_time = self->st_database->db_xn_end_time; + + pb_in_stat = TRUE; + + self->st_stat_count++; + + complete: + return err; +} + +/* + * 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). + * + * When releasing locks, store_lock() are also called. In this case one + * usually doesn't have to do anything. + * + * 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 at last + * time). In the future we will probably try to remove this. + * + * Called from lock.cc by get_lock_data(). + */ +THR_LOCK_DATA **ha_pbxt::store_lock(THD *thd, THR_LOCK_DATA **to, enum thr_lock_type lock_type) +{ + if (lock_type != TL_IGNORE && pb_lock.type == TL_UNLOCK) { + /* Set to TRUE for operations that require a table lock: */ + switch (thd_sql_command(thd)) { + case SQLCOM_TRUNCATE: + /* GOTCHA: + * The problem is, if I do not do this, then + * TRUNCATE TABLE deadlocks with a normal update of the table! + * The reason is: + * + * external_lock() is called before MySQL actually locks the + * table. In external_lock(), the table is shared locked, + * by indicating that the handler is in use. + * + * Then later, in delete_all_rows(), a exclusive lock must be + * obtained. If an UPDATE or INSERT has also gained a shared + * lock in the meantime, then TRUNCATE TABLE hangs. + * + * By setting pb_lock_table we indicate that an exclusive lock + * should be gained in external_lock(). + * + * This is the locking behaviour: + * + * TRUNCATE TABLE: + * XT SHARE LOCK (mysql_lock_tables calls external_lock) + * MySQL WRITE LOCK (mysql_lock_tables) + * ... + * XT EXCLUSIVE LOCK (delete_all_rows) + * + * INSERT: + * XT SHARED LOCK (mysql_lock_tables calls external_lock) + * MySQL WRITE_ALLOW_WRITE LOCK (mysql_lock_tables) + * + * If the locking for INSERT is done in the ... phase + * above, then we have a deadlock because + * WRITE_ALLOW_WRITE conflicts with WRITE. + * + * Making TRUNCATE TABLE take a WRITE_ALLOW_WRITE LOCK, will + * not solve the problem because then 2 TRUNCATE TABLES + * can deadlock due to lock escalation. + * + * What may work is if MySQL were to lock BEFORE calling + * external_lock()! + * + * However, using this method, TRUNCATE TABLE does deadlock + * with other operations such as ALTER TABLE! + * + * This is handled with a lock timeout. Assuming + * TRUNCATE TABLE will be mixed with DML this is the + * best solution! + */ + pb_lock_table = TRUE; + break; + default: + pb_lock_table = FALSE; + break; + } + +#ifdef PBXT_HANDLER_TRACE + pb_lock.type = lock_type; +#endif + /* GOTCHA: Before it was OK to weaken the lock after just checking + * that !thd->in_lock_tables. However, when starting a procedure, MySQL + * simulates a LOCK TABLES statement. + * + * So we need to be more specific here, and check what the actual statement + * type. Before doing this I got a deadlock (undetected) on the following test. + * However, now we get a failed assertion in ha_rollback_trans(): + * TODO: Check this with InnoDB! + * + * DBUG_ASSERT(0); + * my_error(ER_COMMIT_NOT_ALLOWED_IN_SF_OR_TRG, MYF(0)); + * + * drop table if exists t3; + * create table t3 (a smallint primary key) engine=pbxt; + * insert into t3 (a) values (40); + * insert into t3 (a) values (50); + * + * delimiter | + * + * drop function if exists t3_update| + * + * create function t3_update() returns int + * begin + * insert into t3 values (10); + * return 100; + * end| + * + * delimiter ; + * + * CONN 1: + * + * begin; + * update t3 set a = 5 where a = 50; + * + * CONN 2: + * + * begin; + * update t3 set a = 4 where a = 40; + * + * CONN 1: + * + * update t3 set a = 4 where a = 40; // Hangs waiting CONN 2. + * + * CONN 2: + * + * select t3_update(); // Hangs waiting for table lock. + * + */ + if ((lock_type >= TL_WRITE_CONCURRENT_INSERT && lock_type <= TL_WRITE) && + !(thd_in_lock_tables(thd) && thd_sql_command(thd) == SQLCOM_LOCK_TABLES) && + !thd_tablespace_op(thd) && + thd_sql_command(thd) != SQLCOM_TRUNCATE && + thd_sql_command(thd) != SQLCOM_OPTIMIZE && + thd_sql_command(thd) != SQLCOM_CREATE_TABLE) { + 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. + * + * (This one from InnoDB) + + * Stewart: removed SQLCOM_CALL, not sure of implications. + */ + if (lock_type == TL_READ_NO_INSERT && + (!thd_in_lock_tables(thd) +#ifndef DRIZZLED + || thd_sql_command(thd) == SQLCOM_CALL +#endif + )) + { + lock_type = TL_READ; + } + + XT_PRINT3(xt_get_self(), "ha_pbxt::store_lock %s %d->%d\n", pb_share->sh_table_path->ps_path, pb_lock.type, lock_type); + pb_lock.type = lock_type; + } +#ifdef PBXT_HANDLER_TRACE + else { + XT_PRINT3(xt_get_self(), "ha_pbxt::store_lock %s %d->%d (ignore/unlock)\n", pb_share->sh_table_path->ps_path, lock_type, lock_type); + } +#endif + *to++= &pb_lock; + return to; +} + +/* + * 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. + * + * 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_pbxt::delete_table(const char *table_path) +{ + THD *thd = current_thd; + int err = 0; + XTThreadPtr self; + XTSharePtr share; + + if (XTSystemTableShare::isSystemTable(table_path)) + return delete_system_table(table_path); + + if (!(self = ha_set_current_thread(thd, &err))) + return xt_ha_pbxt_to_mysql_error(err); + + self->st_ignore_fkeys = (thd_test_options(thd, OPTION_NO_FOREIGN_KEY_CHECKS)) != 0; + + STAT_TRACE(self, *thd_query(thd)); + XT_PRINT1(self, "ha_pbxt::delete_table %s\n", table_path); + + try_(a) { + xt_ha_open_database_of_table(self, (XTPathStrPtr) table_path); + + ASSERT(xt_get_self() == self); + try_(b) { + /* NOTE: MySQL does not drop a table by first locking it! + * We also cannot use pb_share because the handler used + * to delete a table is not openned correctly. + */ + share = ha_get_share(self, table_path, false, NULL); + pushr_(ha_unget_share, share); + ha_aquire_exclusive_use(self, share, NULL); + pushr_(ha_release_exclusive_use, share); + ha_close_open_tables(self, share, NULL); + + xt_drop_table(self, (XTPathStrPtr) table_path); + + freer_(); // ha_release_exclusive_use(share) + freer_(); // ha_unget_share(share) + } + catch_(b) { + /* If the table does not exist, just log the error and continue... */ + if (self->t_exception.e_xt_err == XT_ERR_TABLE_NOT_FOUND) + xt_log_and_clear_exception(self); + else + throw_(); + } + cont_(b); + + /* + * If there are no more PBXT tables in the database, we + * "drop the database", which deletes all PBXT resources + * in the database. + */ + /* We now only drop the pbxt system data, + * when the PBXT database is dropped. + */ +#ifndef XT_USE_GLOBAL_DB + if (!xt_table_exists(self->st_database)) { + xt_ha_all_threads_close_database(self, self->st_database); + xt_drop_database(self, self->st_database); + xt_unuse_database(self, self); + xt_ha_close_global_database(self); + } +#endif + } + catch_(a) { + err = xt_ha_pbxt_thread_error_for_mysql(thd, self, pb_ignore_dup_key); + } + cont_(a); + + return err; +} + +int ha_pbxt::delete_system_table(const char *table_path) +{ + THD *thd = current_thd; + XTExceptionRec e; + int err = 0; + XTThreadPtr self; + + if (!(self = xt_ha_set_current_thread(thd, &e))) + return xt_ha_pbxt_to_mysql_error(e.e_xt_err); + + try_(a) { + xt_ha_open_database_of_table(self, (XTPathStrPtr) table_path); + + if (xt_table_exists(self->st_database)) + xt_throw_xterr(XT_CONTEXT, XT_ERR_PBXT_TABLE_EXISTS); + + XTSystemTableShare::setSystemTableDeleted(table_path); + + if (!XTSystemTableShare::doesSystemTableExist()) { + xt_ha_all_threads_close_database(self, self->st_database); + xt_drop_database(self, self->st_database); + xt_unuse_database(self, self); + xt_ha_close_global_database(self); + } + } + catch_(a) { + err = xt_ha_pbxt_thread_error_for_mysql(thd, self, FALSE); + } + cont_(a); + + return err; +} + +/* + * Renames a table from one name to another from alter table call. + * This function can be used to move a table from one database to + * another. + */ +int ha_pbxt::rename_table(const char *from, const char *to) +{ + THD *thd = current_thd; + int err = 0; + XTThreadPtr self; + XTSharePtr share; + XTDatabaseHPtr to_db; + + XT_TRACE_CALL(); + + if (XTSystemTableShare::isSystemTable(from)) + return rename_system_table(from, to); + + if (!(self = ha_set_current_thread(thd, &err))) + return xt_ha_pbxt_to_mysql_error(err); + + XT_PRINT2(self, "ha_pbxt::rename_table %s -> %s\n", from, to); + + try_(a) { + xt_ha_open_database_of_table(self, (XTPathStrPtr) to); + to_db = self->st_database; + + xt_ha_open_database_of_table(self, (XTPathStrPtr) from); + + if (self->st_database != to_db) + xt_throw_xterr(XT_CONTEXT, XT_ERR_CANNOT_CHANGE_DB); + + /* + * NOTE: MySQL does not lock before calling rename table! + * + * We cannot use pb_share because rename_table() is + * called without correctly initializing + * the handler! + */ + share = ha_get_share(self, from, true, NULL); + pushr_(ha_unget_share, share); + ha_aquire_exclusive_use(self, share, NULL); + pushr_(ha_release_exclusive_use, share); + ha_close_open_tables(self, share, NULL); + + self->st_ignore_fkeys = (thd_test_options(thd, OPTION_NO_FOREIGN_KEY_CHECKS)) != 0; + xt_rename_table(self, (XTPathStrPtr) from, (XTPathStrPtr) to); + + freer_(); // ha_release_exclusive_use(share) + freer_(); // ha_unget_share(share) + +#ifdef XT_STREAMING + /* PBMS remove the table? */ + xt_pbms_rename_table(from, to); +#endif + /* + * If there are no more PBXT tables in the database, we + * "drop the database", which deletes all PBXT resources + * in the database. + */ +#ifdef XT_USE_GLOBAL_DB + /* We now only drop the pbxt system data, + * when the PBXT database is dropped. + */ + if (!xt_table_exists(self->st_database)) { + xt_ha_all_threads_close_database(self, self->st_database); + xt_drop_database(self, self->st_database); + } +#endif + } + catch_(a) { + err = xt_ha_pbxt_thread_error_for_mysql(thd, self, pb_ignore_dup_key); + } + cont_(a); + + XT_RETURN(err); +} + +int ha_pbxt::rename_system_table(const char *from __attribute__((unused)), const char *to __attribute__((unused))) +{ + return ER_NOT_SUPPORTED_YET; +} + +uint ha_pbxt::max_supported_key_length() const +{ + return XT_INDEX_MAX_KEY_SIZE; +} + +uint ha_pbxt::max_supported_key_part_length() const +{ + /* There is a little overhead in order to fit! */ + return XT_INDEX_MAX_KEY_SIZE-4; +} + +/* + * Called in test_quick_select to determine if indexes should be used. + * + * As far as I can tell, time is measured in "disk reads". So the + * calculation below means the system reads about 20 rows per read. + * + * For example a sequence scan uses a read buffer which reads a + * number of rows at once, or a sequential scan can make use + * of the cache (so it need to read less). + */ +double ha_pbxt::scan_time() +{ + double result = (double) (stats.records + stats.deleted) / 38.0 + 2; + return result; +} + +/* + * The next method will never be called if you do not implement indexes. + */ +double ha_pbxt::read_time(uint index __attribute__((unused)), uint ranges, ha_rows rows) +{ + double result = rows2double(ranges+rows); + return result; +} + +/* + * Given a starting key, and an ending key estimate the number of rows that + * will exist between the two. end_key may be empty which in case determine + * if start_key matches any rows. + * + * Called from opt_range.cc by check_quick_keys(). + * + */ +ha_rows ha_pbxt::records_in_range(uint inx, key_range *min_key, key_range *max_key) +{ + XTIndexPtr ind; + key_part_map keypart_map; + u_int segement = 0; + ha_rows result; + + if (min_key) + keypart_map = min_key->keypart_map; + else if (max_key) + keypart_map = max_key->keypart_map; + else + return 1; + ind = (XTIndexPtr) pb_share->sh_dic_keys[inx]; + + while (keypart_map & 1) { + segement++; + keypart_map = keypart_map >> 1; + } + + if (segement < 1 || segement > ind->mi_seg_count) + result = 1; + else + result = ind->mi_seg[segement-1].is_recs_in_range; +#ifdef XT_PRINT_INDEX_OPT + printf("records_in_range %s index %d cols req=%d/%d read_bits=%X write_bits=%X index_bits=%X --> %d\n", pb_open_tab->ot_table->tab_name->ps_path, (int) inx, segement, ind->mi_seg_count, (int) *table->read_set->bitmap, (int) *table->write_set->bitmap, (int) *ind->mi_col_map.bitmap, (int) result); +#endif + return result; +} + +/* + * create() is called to create a table/database. 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 handle.cc by ha_create_table(). +*/ +int ha_pbxt::create(const char *table_path, TABLE *table_arg, HA_CREATE_INFO *create_info) +{ + THD *thd = current_thd; + int err = 0; + XTThreadPtr self; + XTDDTable *tab_def = NULL; + XTDictionaryRec dic; + + memset(&dic, 0, sizeof(dic)); + + XT_TRACE_CALL(); + + if (!(self = ha_set_current_thread(thd, &err))) + return xt_ha_pbxt_to_mysql_error(err); + + STAT_TRACE(self, *thd_query(thd)); + XT_PRINT1(self, "ha_pbxt::create %s\n", table_path); + + try_(a) { + xt_ha_open_database_of_table(self, (XTPathStrPtr) table_path); + + for (uint i=0; i<TS(table_arg)->keys; i++) { + if (table_arg->key_info[i].key_length > XT_INDEX_MAX_KEY_SIZE) + xt_throw_sulxterr(XT_CONTEXT, XT_ERR_KEY_TOO_LARGE, table_arg->key_info[i].name, (u_long) XT_INDEX_MAX_KEY_SIZE); + } + + /* ($) auto_increment_value will be zero if + * AUTO_INCREMENT is not used. Otherwise + * Query was ALTER TABLE ... AUTO_INCREMENT = x; or + * CREATE TABLE ... AUTO_INCREMENT = x; + */ + tab_def = xt_ri_create_table(self, true, (XTPathStrPtr) table_path, *thd_query(thd), myxt_create_table_from_table(self, table_arg)); + tab_def->checkForeignKeys(self, create_info->options & HA_LEX_CREATE_TMP_TABLE); + + dic.dic_table = tab_def; + dic.dic_my_table = table_arg; + dic.dic_tab_flags = (create_info->options & HA_LEX_CREATE_TMP_TABLE) ? XT_TAB_FLAGS_TEMP_TAB : 0; + dic.dic_min_auto_inc = (xtWord8) create_info->auto_increment_value; /* ($) */ + dic.dic_def_ave_row_size = (xtWord8) table_arg->s->avg_row_length; + myxt_setup_dictionary(self, &dic); + + /* + * We used to ignore the value of foreign_key_checks flag and allowed creation + * of tables with "hanging" references. Now we validate FKs if foreign_key_checks != 0 + */ + self->st_ignore_fkeys = (thd_test_options(thd, OPTION_NO_FOREIGN_KEY_CHECKS)) != 0; + + /* + * Previously I set delete_if_exists=TRUE because + * CREATE TABLE was being used to TRUNCATE. + * This was due to the flag HTON_CAN_RECREATE. + * Now I could set delete_if_exists=FALSE, but + * leaving it TRUE should not cause any problems. + */ + xt_create_table(self, (XTPathStrPtr) table_path, &dic); + } + catch_(a) { + if (tab_def) + tab_def->finalize(self); + dic.dic_table = NULL; + err = xt_ha_pbxt_thread_error_for_mysql(thd, self, pb_ignore_dup_key); + } + cont_(a); + + /* Free the dictionary, but not 'table_arg'! */ + dic.dic_my_table = NULL; + myxt_free_dictionary(self, &dic); + + XT_RETURN(err); +} + +void ha_pbxt::update_create_info(HA_CREATE_INFO *create_info) +{ + XTOpenTablePtr ot; + + if ((ot = pb_open_tab)) { + if (!(create_info->used_fields & HA_CREATE_USED_AUTO)) { + /* Fill in the minimum auto-increment value! */ + create_info->auto_increment_value = ot->ot_table->tab_dic.dic_min_auto_inc; + } + } +} + +char *ha_pbxt::get_foreign_key_create_info() +{ + THD *thd = current_thd; + int err = 0; + XTThreadPtr self; + XTStringBufferRec tab_def = { 0, 0, 0 }; + + if (!(self = ha_set_current_thread(thd, &err))) { + xt_ha_pbxt_to_mysql_error(err); + return NULL; + } + + if (!pb_open_tab) { + if ((err = reopen())) + return NULL; + } + + if (!pb_open_tab->ot_table->tab_dic.dic_table) + return NULL; + + try_(a) { + pb_open_tab->ot_table->tab_dic.dic_table->loadForeignKeyString(self, &tab_def); + } + catch_(a) { + xt_sb_set_size(self, &tab_def, 0); + err = xt_ha_pbxt_thread_error_for_mysql(thd, self, pb_ignore_dup_key); + } + cont_(a); + + return tab_def.sb_cstring; +} + +void ha_pbxt::free_foreign_key_create_info(char* str) +{ + xt_free(NULL, str); +} + +bool ha_pbxt::get_error_message(int error __attribute__((unused)), String *buf) +{ + THD *thd = current_thd; + int err = 0; + XTThreadPtr self; + + if (!(self = ha_set_current_thread(thd, &err))) + return FALSE; + + if (!self->t_exception.e_xt_err) + return FALSE; + + buf->copy(self->t_exception.e_err_msg, strlen(self->t_exception.e_err_msg), system_charset_info); + return TRUE; +} + +/* + * get info about FKs of the currently open table + * used in + * 1. REPLACE; is > 0 if table is referred by a FOREIGN KEY + * 2. INFORMATION_SCHEMA tables: TABLE_CONSTRAINTS, REFERENTIAL_CONSTRAINTS + * Return value: as of 5.1.24 it's ignored + */ + +int ha_pbxt::get_foreign_key_list(THD *thd, List<FOREIGN_KEY_INFO> *f_key_list) +{ + int err = 0; + XTThreadPtr self; + const char *action; + + if (!(self = ha_set_current_thread(thd, &err))) { + return xt_ha_pbxt_to_mysql_error(err); + } + + try_(a) { + XTDDTable *table_dic = pb_open_tab->ot_table->tab_dic.dic_table; + + if (table_dic == NULL) + xt_throw_errno(XT_CONTEXT, XT_ERR_NO_DICTIONARY); + + for (int i = 0, sz = table_dic->dt_fkeys.size(); i < sz; i++) { + FOREIGN_KEY_INFO *fk_info= new // assumed that C++ exceptions are disabled + (thd_alloc(thd, sizeof(FOREIGN_KEY_INFO))) FOREIGN_KEY_INFO; + + if (fk_info == NULL) + xt_throw_errno(XT_CONTEXT, XT_ENOMEM); + + XTDDForeignKey *fk = table_dic->dt_fkeys.itemAt(i); + + const char *path = fk->fk_ref_tab_name->ps_path; + const char *ref_tbl_name = path + strlen(path); + + while (ref_tbl_name != path && !XT_IS_DIR_CHAR(*ref_tbl_name)) + ref_tbl_name--; + + const char * ref_db_name = ref_tbl_name - 1; + + while (ref_db_name != path && !XT_IS_DIR_CHAR(*ref_db_name)) + ref_db_name--; + + ref_tbl_name++; + ref_db_name++; + + fk_info->forein_id = thd_make_lex_string(thd, 0, + fk->co_name, (uint) strlen(fk->co_name), 1); + + fk_info->referenced_db = thd_make_lex_string(thd, 0, + ref_db_name, (uint) (ref_tbl_name - ref_db_name - 1), 1); + + fk_info->referenced_table = thd_make_lex_string(thd, 0, + ref_tbl_name, (uint) strlen(ref_tbl_name), 1); + + fk_info->referenced_key_name = NULL; + + XTIndex *ix = fk->getReferenceIndexPtr(); + if (ix == NULL) /* can be NULL if another thread changes referenced table at the moment */ + continue; + + XTDDTable *ref_table = fk->fk_ref_table; + + // might be a self-reference + if ((ref_table == NULL) + && (xt_tab_compare_names(path, table_dic->dt_table->tab_name->ps_path) == 0)) { + ref_table = table_dic; + } + + if (ref_table != NULL) { + const XTList<XTDDIndex>& ix_list = ref_table->dt_indexes; + for (int j = 0, sz2 = ix_list.size(); j < sz2; j++) { + XTDDIndex *ddix = ix_list.itemAt(j); + if (ddix->in_index == ix->mi_index_no) { + const char *ix_name = + ddix->co_name ? ddix->co_name : ddix->co_ind_name; + fk_info->referenced_key_name = thd_make_lex_string(thd, 0, + ix_name, (uint) strlen(ix_name), 1); + break; + } + } + } + + action = XTDDForeignKey::actionTypeToString(fk->fk_on_delete); + fk_info->delete_method = thd_make_lex_string(thd, 0, + action, (uint) strlen(action), 1); + action = XTDDForeignKey::actionTypeToString(fk->fk_on_update); + fk_info->update_method = thd_make_lex_string(thd, 0, + action, (uint) strlen(action), 1); + + const XTList<XTDDColumnRef>& cols = fk->co_cols; + for (int j = 0, sz2 = cols.size(); j < sz2; j++) { + XTDDColumnRef *col_ref= cols.itemAt(j); + fk_info->foreign_fields.push_back(thd_make_lex_string(thd, 0, + col_ref->cr_col_name, (uint) strlen(col_ref->cr_col_name), 1)); + } + + const XTList<XTDDColumnRef>& ref_cols = fk->fk_ref_cols; + for (int j = 0, sz2 = ref_cols.size(); j < sz2; j++) { + XTDDColumnRef *col_ref= ref_cols.itemAt(j); + fk_info->referenced_fields.push_back(thd_make_lex_string(thd, 0, + col_ref->cr_col_name, (uint) strlen(col_ref->cr_col_name), 1)); + } + + f_key_list->push_back(fk_info); + } + } + catch_(a) { + err = xt_ha_pbxt_thread_error_for_mysql(thd, self, pb_ignore_dup_key); + } + cont_(a); + + return err; +} + +uint ha_pbxt::referenced_by_foreign_key() +{ + XTDDTable *table_dic = pb_open_tab->ot_table->tab_dic.dic_table; + + if (!table_dic) + return 0; + /* Check the list of referencing tables: */ + return table_dic->dt_trefs ? 1 : 0; +} + + +struct st_mysql_sys_var +{ + MYSQL_PLUGIN_VAR_HEADER; +}; + +#if MYSQL_VERSION_ID < 60000 +#if MYSQL_VERSION_ID >= 50124 +#define USE_CONST_SAVE +#endif +#else +#if MYSQL_VERSION_ID >= 60005 +#define USE_CONST_SAVE +#endif +#endif + +#ifdef USE_CONST_SAVE +static void pbxt_record_cache_size_func(THD *thd __attribute__((unused)), struct st_mysql_sys_var *var, void *tgt, const void *save) +#else +static void pbxt_record_cache_size_func(THD *thd __attribute__((unused)), struct st_mysql_sys_var *var, void *tgt, void *save) +#endif +{ + xtInt8 record_cache_size; + + char *old= *(char **) tgt; + *(char **)tgt= *(char **) save; + if (var->flags & PLUGIN_VAR_MEMALLOC) + { + *(char **)tgt= my_strdup(*(char **) save, MYF(0)); + my_free(old, MYF(0)); + } + record_cache_size = ha_set_variable(&pbxt_record_cache_size, &vp_record_cache_size); + xt_tc_set_cache_size((size_t) record_cache_size); +#ifdef DEBUG + char buffer[200]; + + sprintf(buffer, "pbxt_record_cache_size=%llu\n", (u_llong) record_cache_size); + xt_logf(XT_NT_INFO, buffer); +#endif +} + +#ifndef DRIZZLED +struct st_mysql_storage_engine pbxt_storage_engine = { + MYSQL_HANDLERTON_INTERFACE_VERSION +}; +static st_mysql_information_schema pbxt_statitics = { + MYSQL_INFORMATION_SCHEMA_INTERFACE_VERSION +}; +#endif + +#if MYSQL_VERSION_ID >= 50118 +static MYSQL_SYSVAR_STR(index_cache_size, pbxt_index_cache_size, + PLUGIN_VAR_READONLY, + "The amount of memory allocated to the index cache, used only to cache index data.", + NULL, NULL, NULL); + +static MYSQL_SYSVAR_STR(record_cache_size, pbxt_record_cache_size, + PLUGIN_VAR_READONLY, // PLUGIN_VAR_OPCMDARG | PLUGIN_VAR_MEMALLOC, + "The amount of memory allocated to the record cache used to cache table data.", + NULL, pbxt_record_cache_size_func, NULL); + +static MYSQL_SYSVAR_STR(log_cache_size, pbxt_log_cache_size, + PLUGIN_VAR_READONLY, + "The amount of memory allocated to the transaction log cache used to cache transaction log data.", + NULL, NULL, NULL); + +static MYSQL_SYSVAR_STR(log_file_threshold, pbxt_log_file_threshold, + PLUGIN_VAR_READONLY, + "The size of a transaction log before rollover, and a new log is created.", + NULL, NULL, NULL); + +static MYSQL_SYSVAR_STR(transaction_buffer_size, pbxt_transaction_buffer_size, + PLUGIN_VAR_READONLY, + "The size of the global transaction log buffer (the engine allocates 2 buffers of this size).", + NULL, NULL, NULL); + +static MYSQL_SYSVAR_STR(log_buffer_size, pbxt_log_buffer_size, + PLUGIN_VAR_READONLY, + "The size of the buffer used to cache data from transaction and data logs during sequential scans, or when writing a data log.", + NULL, NULL, NULL); + +static MYSQL_SYSVAR_STR(checkpoint_frequency, pbxt_checkpoint_frequency, + PLUGIN_VAR_READONLY, + "The size of the transaction data buffer which is allocate by each thread.", + NULL, NULL, NULL); + +static MYSQL_SYSVAR_STR(data_log_threshold, pbxt_data_log_threshold, + PLUGIN_VAR_READONLY, + "The maximum size of a data log file.", + NULL, NULL, NULL); + +static MYSQL_SYSVAR_STR(data_file_grow_size, pbxt_data_file_grow_size, + PLUGIN_VAR_READONLY, + "The amount by which the handle data files (.xtd) grow.", + NULL, NULL, NULL); + +static MYSQL_SYSVAR_STR(row_file_grow_size, pbxt_row_file_grow_size, + PLUGIN_VAR_READONLY, + "The amount by which the row pointer files (.xtr) grow.", + NULL, NULL, NULL); + +static MYSQL_SYSVAR_INT(garbage_threshold, xt_db_garbage_threshold, + PLUGIN_VAR_OPCMDARG, + "The percentage of garbage in a repository file before it is compacted.", + NULL, NULL, XT_DL_DEFAULT_GARBAGE_LEVEL, 0, 100, 1); + +static MYSQL_SYSVAR_INT(log_file_count, xt_db_log_file_count, + PLUGIN_VAR_OPCMDARG, + "The minimum number of transaction logs used.", + NULL, NULL, XT_DL_DEFAULT_XLOG_COUNT, 1, 20000, 1); + +static MYSQL_SYSVAR_INT(auto_increment_mode, xt_db_auto_increment_mode, + PLUGIN_VAR_OPCMDARG, + "The auto-increment mode, 0 = MySQL standard (default), 1 = previous ID's never reused.", + NULL, NULL, XT_AUTO_INCREMENT_DEF, 0, 1, 1); + +/* {RN145} */ +static MYSQL_SYSVAR_INT(offline_log_function, xt_db_offline_log_function, + PLUGIN_VAR_OPCMDARG, + "Determines what happens to transaction logs when the are moved offline, 0 = recycle logs (default), 1 = delete logs (default on Mac OS X), 2 = keep logs.", + NULL, NULL, XT_OFFLINE_LOG_FUNCTION_DEF, 0, 2, 1); + +/* {RN150} */ +static MYSQL_SYSVAR_INT(sweeper_priority, xt_db_sweeper_priority, + PLUGIN_VAR_OPCMDARG, + "Determines the priority of the background sweeper process, 0 = low (default), 1 = normal (same as user threads), 2 = high.", + NULL, NULL, XT_PRIORITY_LOW, XT_PRIORITY_LOW, XT_PRIORITY_HIGH, 1); + +static struct st_mysql_sys_var* pbxt_system_variables[] = { + MYSQL_SYSVAR(index_cache_size), + MYSQL_SYSVAR(record_cache_size), + MYSQL_SYSVAR(log_cache_size), + MYSQL_SYSVAR(log_file_threshold), + MYSQL_SYSVAR(transaction_buffer_size), + MYSQL_SYSVAR(log_buffer_size), + MYSQL_SYSVAR(checkpoint_frequency), + MYSQL_SYSVAR(data_log_threshold), + MYSQL_SYSVAR(data_file_grow_size), + MYSQL_SYSVAR(row_file_grow_size), + MYSQL_SYSVAR(garbage_threshold), + MYSQL_SYSVAR(log_file_count), + MYSQL_SYSVAR(auto_increment_mode), + MYSQL_SYSVAR(offline_log_function), + MYSQL_SYSVAR(sweeper_priority), + NULL +}; +#endif + +#ifdef DRIZZLED +drizzle_declare_plugin(pbxt) +#else +mysql_declare_plugin(pbxt) +#endif +{ + MYSQL_STORAGE_ENGINE_PLUGIN, +#ifndef DRIZZLED + &pbxt_storage_engine, +#endif + "PBXT", +#ifdef DRIZZLED + "1.0", +#endif + "Paul McCullagh, PrimeBase Technologies GmbH", + "High performance, multi-versioning transactional engine", + PLUGIN_LICENSE_GPL, + pbxt_init, /* Plugin Init */ + pbxt_end, /* Plugin Deinit */ +#ifndef DRIZZLED + 0x0001 /* 0.1 */, +#endif + NULL, /* status variables */ +#if MYSQL_VERSION_ID >= 50118 + pbxt_system_variables, /* system variables */ +#else + NULL, +#endif + NULL /* config options */ +}, +{ + MYSQL_INFORMATION_SCHEMA_PLUGIN, +#ifndef DRIZZLED + &pbxt_statitics, +#endif + "PBXT_STATISTICS", +#ifdef DRIZZLED + "1.0", +#endif + "Paul McCullagh, PrimeBase Technologies GmbH", + "PBXT internal system statitics", + PLUGIN_LICENSE_GPL, + pbxt_init_statitics, /* plugin init */ + pbxt_exit_statitics, /* plugin deinit */ +#ifndef DRIZZLED + 0x0005, +#endif + NULL, /* status variables */ + NULL, /* system variables */ + NULL /* config options */ +} +#ifdef DRIZZLED +drizzle_declare_plugin_end; +#else +mysql_declare_plugin_end; +#endif + +#if defined(XT_WIN) && defined(XT_COREDUMP) + +/* + * WINDOWS CORE DUMP SUPPORT + * + * MySQL supports core dumping on Windows with --core-file command line option. + * However it creates dumps with the MiniDumpNormal option which saves only stack traces. + * + * We instead (or in addition) create dumps with MiniDumpWithoutOptionalData option + * which saves all available information. To enable core dumping enable XT_COREDUMP + * at compile time. + * In addition, pbxt_crash_debug must be set to TRUE which is the case if XT_CRASH_DEBUG + * is defined. + * This switch is also controlled by creating a file called "no-debug" or "crash-debug" + * in the pbxt database directory. + */ + +typedef enum _MINIDUMP_TYPE { + MiniDumpNormal = 0x0000, + MiniDumpWithDataSegs = 0x0001, + MiniDumpWithFullMemory = 0x0002, + MiniDumpWithHandleData = 0x0004, + MiniDumpFilterMemory = 0x0008, + MiniDumpScanMemory = 0x0010, + MiniDumpWithUnloadedModules = 0x0020, + MiniDumpWithIndirectlyReferencedMemory = 0x0040, + MiniDumpFilterModulePaths = 0x0080, + MiniDumpWithProcessThreadData = 0x0100, + MiniDumpWithPrivateReadWriteMemory = 0x0200, +} MINIDUMP_TYPE; + +typedef struct _MINIDUMP_EXCEPTION_INFORMATION { + DWORD ThreadId; + PEXCEPTION_POINTERS ExceptionPointers; + BOOL ClientPointers; +} MINIDUMP_EXCEPTION_INFORMATION, *PMINIDUMP_EXCEPTION_INFORMATION; + +typedef BOOL (WINAPI *MINIDUMPWRITEDUMP)( + HANDLE hProcess, + DWORD dwPid, + HANDLE hFile, + MINIDUMP_TYPE DumpType, + void *ExceptionParam, + void *UserStreamParam, + void *CallbackParam + ); + +char base_path[_MAX_PATH] = {0}; +char dump_path[_MAX_PATH] = {0}; + +void core_dump(struct _EXCEPTION_POINTERS *pExceptionInfo) +{ + SECURITY_ATTRIBUTES sa = { sizeof(SECURITY_ATTRIBUTES), 0, 0 }; + int i; + HMODULE hDll = NULL; + HANDLE hFile; + MINIDUMPWRITEDUMP pDump; + char *end_ptr = base_path; + + MINIDUMP_EXCEPTION_INFORMATION ExInfo, *ExInfoPtr = NULL; + + if (pExceptionInfo) { + ExInfo.ThreadId = GetCurrentThreadId(); + ExInfo.ExceptionPointers = pExceptionInfo; + ExInfo.ClientPointers = NULL; + ExInfoPtr = &ExInfo; + } + + end_ptr = base_path + strlen(base_path); + + strcat(base_path, "DBGHELP.DLL" ); + hDll = LoadLibrary(base_path); + *end_ptr = 0; + if (hDll==NULL) { + int err; + err = HRESULT_CODE(GetLastError()); + hDll = LoadLibrary( "DBGHELP.DLL" ); + if (hDll==NULL) { + err = HRESULT_CODE(GetLastError()); + return; + } + } + + pDump = (MINIDUMPWRITEDUMP)GetProcAddress( hDll, "MiniDumpWriteDump" ); + if (!pDump) { + int err; + err = HRESULT_CODE(GetLastError()); + return; + } + + for (i = 1; i < INT_MAX; i++) { + sprintf(dump_path, "%sPBXTCore%08d.dmp", base_path, i); + hFile = CreateFile( dump_path, GENERIC_WRITE, FILE_SHARE_WRITE, NULL, CREATE_NEW, + FILE_ATTRIBUTE_NORMAL, NULL ); + + if ( hFile != INVALID_HANDLE_VALUE ) + break; + + if (HRESULT_CODE(GetLastError()) == ERROR_FILE_EXISTS ) + continue; + + return; + } + + // write the dump + BOOL bOK = pDump( GetCurrentProcess(), GetCurrentProcessId(), hFile, + MiniDumpWithPrivateReadWriteMemory, ExInfoPtr, NULL, NULL ); + + CloseHandle(hFile); +} + +LONG crash_filter( struct _EXCEPTION_POINTERS *pExceptionInfo ) +{ + core_dump(pExceptionInfo); + return EXCEPTION_EXECUTE_HANDLER; +} + +void register_crash_filter() +{ + SetUnhandledExceptionFilter( (LPTOP_LEVEL_EXCEPTION_FILTER) crash_filter ); +} + +#endif // XT_WIN && XT_COREDUMP diff --git a/storage/pbxt/src/ha_pbxt.h b/storage/pbxt/src/ha_pbxt.h new file mode 100644 index 00000000000..6f6a194de12 --- /dev/null +++ b/storage/pbxt/src/ha_pbxt.h @@ -0,0 +1,318 @@ +/* Copyright (c) 2005 PrimeBase Technologies GmbH + * + * Derived from ha_example.h + * Copyright (C) 2003 MySQL AB + * + * PrimeBase XT + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You 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 + * + * 2005-11-10 Paul McCullagh + * + */ +#ifndef __ha_pbxt_h__ +#define __ha_pbxt_h__ + +#ifdef DRIZZLED +#include <drizzled/common.h> +#include <drizzled/handler.h> +#include <drizzled/handlerton.h> +#include <mysys/thr_lock.h> +#else +#include "mysql_priv.h" +#endif + +#include "xt_defs.h" +#include "table_xt.h" + +#ifdef USE_PRAGMA_INTERFACE +#pragma interface /* gcc class implementation */ +#endif + +#if MYSQL_VERSION_ID <= 50120 +#define thd_killed(t) (t)->killed +#endif + +#if MYSQL_VERSION_ID >= 50120 +#define byte uchar +#endif + +class ha_pbxt; + +extern handlerton *pbxt_hton; + +/* + * XTShareRec is a structure that will be shared amoung all open handlers. + */ +typedef struct XTShare { + XTPathStrPtr sh_table_path; + uint sh_use_count; + + XTTableHPtr sh_table; /* This is a XTTableHPtr, a reference to the XT internal table handle. */ + + uint sh_dic_key_count; + XTIndexPtr *sh_dic_keys; /* A reference to the XT internal index list. */ + xtBool sh_recalc_selectivity; /* This is set to TRUE if when have < 100 rows when the table is openned. */ + + /* We use a trick here to get an exclusive lock + * on a table. The trick avoids having to use a + * semaphore if a thread does not want + * exclusive use. + */ + xt_mutex_type *sh_ex_mutex; + xt_cond_type *sh_ex_cond; + xtBool sh_table_lock; /* Set to TRUE if a lock on the table is held. */ + ha_pbxt *sh_handlers; /* Double linked list of handlers for a particular table. */ + xtWord8 sh_min_auto_inc; /* Used to proporgate the current auto-inc over a DELETE FROM + * (does not work if the server shuts down in between!). + */ + + THR_LOCK sh_lock; /* MySQL lock */ +} XTShareRec, *XTSharePtr; + +/* + * Class definition for the storage engine + */ +class ha_pbxt: public handler +{ + public: + XTSharePtr pb_share; /* Shared table info */ + + XTOpenTablePtr pb_open_tab; /* This is a XTOpenTablePtr (a reference to the XT internal table handle)! */ + + xtBool pb_key_read; /* No Need to retrieve the entire row, index values are sufficient. */ + int pb_ignore_dup_key; + u_int pb_ind_row_count; + + THR_LOCK_DATA pb_lock; /* MySQL lock */ + + ha_pbxt *pb_ex_next; /* Double linked list of handlers for a particular table. */ + ha_pbxt *pb_ex_prev; + + xtBool pb_lock_table; /* The operation requires a table lock. */ + int pb_table_locked; /* TRUE of this handler holds the table lock. */ + int pb_ex_in_use; /* Set to 1 while when the handler is in use. */ + + THD *pb_mysql_thd; /* A pointer to the MySQL thread. */ + xtBool pb_in_stat; /* TRUE of start_stmt() was issued */ + + ha_pbxt(handlerton *hton, TABLE_SHARE *table_arg); + + virtual ~ha_pbxt() { } + + /* The name that will be used for display purposes */ + const char *table_type() const { return "PBXT"; } + + /* + * The name of the index type that will be used for display + * don't implement this method unless you really have indexes. + */ + const char *index_type(uint inx) { (void) inx; return "BTREE"; } + + const char **bas_ext() const; + + MX_UINT8_T table_cache_type(); + + /* + * This is a list of flags that says what the storage engine + * implements. The current table flags are documented in + * handler.h + */ + MX_TABLE_TYPES_T table_flags() const; + + /* + * 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'. + */ + MX_ULONG_T index_flags(uint inx, uint part, bool all_parts) const; + + /* + * unireg.cc will call the following to make sure that the storage engine can + * handle the data it is about to send. + * + * Return *real* limits of your storage engine here. MySQL will do + * min(your_limits, MySQL_limits) automatically + * + * Theoretically PBXT supports any number of key parts, etc. + * Practically this is not true of course. + */ + uint max_supported_record_length() const { return UINT_MAX; } + uint max_supported_keys() const { return 512; } + uint max_supported_key_parts() const { return 128; } + uint max_supported_key_length() const; + uint max_supported_key_part_length() const; + + double scan_time(); + + double read_time(uint index, uint ranges, ha_rows rows); + + bool has_transactions() { return 1; } + + /* + * Everything below are methods that we implement in ha_pbxt.cc. + */ + void internal_close(THD *thd, struct XTThread *self); + int open(const char *name, int mode, uint test_if_locked); // required + int reopen(void); + int close(void); // required + + void init_auto_increment(xtWord8 min_auto_inc); + void get_auto_increment(MX_ULONGLONG_T offset, MX_ULONGLONG_T increment, + MX_ULONGLONG_T nb_desired_values, + MX_ULONGLONG_T *first_value, + MX_ULONGLONG_T *nb_reserved_values); + void set_auto_increment(Field *nr); + + int write_row(byte * buf); + int update_row(const byte * old_data, byte * new_data); + int delete_row(const byte * buf); + + /* Index access functions: */ + int xt_index_in_range(register XTOpenTablePtr ot, register XTIndexPtr ind, register XTIdxSearchKeyPtr search_key, byte *buf); + int xt_index_next_read(register XTOpenTablePtr ot, register XTIndexPtr ind, xtBool key_only, register XTIdxSearchKeyPtr search_key, byte *buf); + int xt_index_prev_read(XTOpenTablePtr ot, XTIndexPtr ind, xtBool key_only, register XTIdxSearchKeyPtr search_key, byte *buf); + int index_init(uint idx, bool sorted); + int index_end(); + int index_read(byte * buf, const byte * key, + uint key_len, enum ha_rkey_function find_flag); + int index_read_idx(byte * buf, uint idx, const byte * key, + uint key_len, enum ha_rkey_function find_flag); + int index_read_xt(byte * buf, uint idx, const byte * key, + uint key_len, enum ha_rkey_function find_flag); + int index_next(byte * buf); + int index_next_same(byte * buf, const byte *key, uint length); + int index_prev(byte * buf); + int index_first(byte * buf); + int index_last(byte * buf); + int index_read_last(byte * buf, const byte * key, uint key_len); + + /* Sequential scan functions: */ + int rnd_init(bool scan); //required + int rnd_end(); + int rnd_next(byte *buf); //required + int rnd_pos(byte * buf, byte *pos); //required + void position(const byte *record); //required +#if MYSQL_VERSION_ID < 50114 + void info(uint); +#else + int info(uint); +#endif + + int extra(enum ha_extra_function operation); + int reset(void); + int external_lock(THD *thd, int lock_type); //required + int start_stmt(THD *thd, thr_lock_type lock_type); + void unlock_row(); + int delete_all_rows(void); + int repair(THD* thd, HA_CHECK_OPT* check_opt); + int analyze(THD* thd, HA_CHECK_OPT* check_opt); + int optimize(THD* thd, HA_CHECK_OPT* check_opt); + int check(THD* thd, HA_CHECK_OPT* check_opt); + ha_rows records_in_range(uint inx, key_range *min_key, key_range *max_key); + int delete_table(const char *from); + int delete_system_table(const char *table_path); + int rename_table(const char * from, const char * to); + int rename_system_table(const char * from, const char * to); + int create(const char *name, TABLE *form, HA_CREATE_INFO *create_info); //required + void update_create_info(HA_CREATE_INFO *create_info); + + THR_LOCK_DATA **store_lock(THD *thd, THR_LOCK_DATA **to, enum thr_lock_type lock_type); //required + + /* Foreign key support: */ + //bool is_fk_defined_on_table_or_index(uint index); + char* get_foreign_key_create_info(); + int get_foreign_key_list(THD *thd, List<FOREIGN_KEY_INFO> *f_key_list); + //bool can_switch_engines(); + uint referenced_by_foreign_key(); + void free_foreign_key_create_info(char* str); + + virtual bool get_error_message(int error, String *buf); +}; + +/* From ha_pbxt.cc: */ +#define XT_TAB_NAME_WITH_EXT_SIZE XT_TABLE_NAME_SIZE+4 + +class THD; +struct XTThread; +struct XTDatabase; + +void xt_ha_unlock_table(struct XTThread *self, void *share); +void xt_ha_close_global_database(XTThreadPtr self); +void xt_ha_open_database_of_table(struct XTThread *self, XTPathStrPtr table_path); +struct XTThread *xt_ha_set_current_thread(THD *thd, XTExceptionPtr e); +void xt_ha_close_connection(THD* thd); +struct XTThread *xt_ha_thd_to_self(THD* thd); +int xt_ha_pbxt_to_mysql_error(int xt_err); +int xt_ha_pbxt_thread_error_for_mysql(THD *thd, const XTThreadPtr self, int ignore_dup_key); +void xt_ha_all_threads_close_database(XTThreadPtr self, XTDatabase *db); + +/* + * These hooks are suppossed to only be used by InnoDB: + */ +#ifndef DRIZZLED +#ifdef INNODB_COMPATIBILITY_HOOKS +extern "C" struct charset_info_st *thd_charset(MYSQL_THD thd); +extern "C" char **thd_query(MYSQL_THD thd); +extern "C" int thd_slave_thread(const MYSQL_THD thd); +extern "C" int thd_non_transactional_update(const MYSQL_THD thd); +extern "C" int thd_binlog_format(const MYSQL_THD thd); +extern "C" void thd_mark_transaction_to_rollback(MYSQL_THD thd, bool all); +#else +#define thd_charset(t) (t)->charset() +#define thd_query(t) &(t)->query +#define thd_slave_thread(t) (t)->slave_thread +#define thd_non_transactional_update(t) (t)->transaction.all.modified_non_trans_table +#define thd_binlog_format(t) (t)->variables.binlog_format +#define thd_mark_transaction_to_rollback(t) mark_transaction_to_rollback(t, all) +#endif // INNODB_COMPATIBILITY_HOOKS */ +#endif /* !DRIZZLED */ + +/* How to lock MySQL mutexes! */ +#ifdef SAFE_MUTEX + +#if MYSQL_VERSION_ID < 60000 +#if MYSQL_VERSION_ID < 50123 +#define myxt_mutex_lock(x) safe_mutex_lock(x,__FILE__,__LINE__) +#else +#define myxt_mutex_lock(x) safe_mutex_lock(x,0,__FILE__,__LINE__) +#endif +#else +#if MYSQL_VERSION_ID < 60004 +#define myxt_mutex_lock(x) safe_mutex_lock(x,__FILE__,__LINE__) +#else +#define myxt_mutex_lock(x) safe_mutex_lock(x,0,__FILE__,__LINE__) +#endif +#endif + +#define myxt_mutex_t safe_mutex_t +#define myxt_mutex_unlock(x) safe_mutex_unlock(x,__FILE__,__LINE__) + +#else // SAFE_MUTEX + +#ifdef MY_PTHREAD_FASTMUTEX +#define myxt_mutex_lock(x) my_pthread_fastmutex_lock(x) +#define myxt_mutex_t my_pthread_fastmutex_t +#define myxt_mutex_unlock(x) pthread_mutex_unlock(&(x)->mutex) +#else +#define myxt_mutex_lock(x) pthread_mutex_lock(x) +#define myxt_mutex_t pthread_mutex_t +#define myxt_mutex_unlock(x) pthread_mutex_unlock(x) +#endif + +#endif // SAFE_MUTEX + +#endif + diff --git a/storage/pbxt/src/ha_xtsys.cc b/storage/pbxt/src/ha_xtsys.cc new file mode 100644 index 00000000000..1c76d13379a --- /dev/null +++ b/storage/pbxt/src/ha_xtsys.cc @@ -0,0 +1,252 @@ +/* Copyright (c) 2008 PrimeBase Technologies GmbH, Germany + * + * PrimeBase Media Stream for MySQL + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You 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 + * + * Paul McCullagh + * + * 2007-05-20 + * + * H&G2JCtL + * + * Table handler. + * + */ + +#ifdef USE_PRAGMA_IMPLEMENTATION +#pragma implementation // gcc: Class implementation +#endif + +#include "xt_config.h" + +#include <stdlib.h> +#include <time.h> + +#ifdef DRIZZLED +#include <drizzled/server_includes.h> +#endif + +#include "ha_xtsys.h" +#include "ha_pbxt.h" + +#include "strutil_xt.h" +#include "database_xt.h" +#include "discover_xt.h" +#include "systab_xt.h" +#include "xt_defs.h" + +/* Note: mysql_priv.h messes with new, which caused a crash. */ +#ifdef new +#undef new +#endif + +/* + * --------------------------------------------------------------- + * HANDLER INTERFACE + */ + +ha_xtsys::ha_xtsys(handlerton *hton, TABLE_SHARE *table_arg): +handler(hton, table_arg), +ha_open_tab(NULL) +{ + init(); +} + +static const char *ha_pbms_exts[] = { + "", + NullS +}; + +const char **ha_xtsys::bas_ext() const +{ + return ha_pbms_exts; +} + +int ha_xtsys::open(const char *table_path, int mode __attribute__((unused)), uint test_if_locked __attribute__((unused))) +{ + THD *thd = current_thd; + XTExceptionRec e; + XTThreadPtr self; + int err = 0; + + if (!(self = xt_ha_set_current_thread(thd, &e))) + return xt_ha_pbxt_to_mysql_error(e.e_xt_err); + + try_(a) { + xt_ha_open_database_of_table(self, (XTPathStrPtr) table_path); + + ha_open_tab = XTSystemTableShare::openSystemTable(self, table_path, table); + thr_lock_data_init(ha_open_tab->ost_share->sts_my_lock, &ha_lock, NULL); + ref_length = ha_open_tab->getRefLen(); + } + catch_(a) { + err = xt_ha_pbxt_thread_error_for_mysql(thd, self, FALSE); + if (ha_open_tab) { + ha_open_tab->release(self); + ha_open_tab = NULL; + } + } + cont_(a); + + return err; +} + +int ha_xtsys::close(void) +{ + THD *thd = current_thd; + XTExceptionRec e; + volatile XTThreadPtr self = NULL; + int err = 0; + + if (thd) + self = xt_ha_set_current_thread(thd, &e); + else { + if (!(self = xt_create_thread("TempForClose", FALSE, TRUE, &e))) { + xt_log_exception(NULL, &e, XT_LOG_DEFAULT); + return 0; + } + } + + if (self) { + try_(a) { + if (ha_open_tab) { + ha_open_tab->release(self); + ha_open_tab = NULL; + } + } + catch_(a) { + err = xt_ha_pbxt_thread_error_for_mysql(thd, self, FALSE); + } + cont_(a); + + if (!thd) + xt_free_thread(self); + } + else + xt_log(XT_NS_CONTEXT, XT_LOG_WARNING, "Unable to release table reference\n"); + + return err; +} + +int ha_xtsys::rnd_init(bool scan __attribute__((unused))) +{ + int err = 0; + + if (!ha_open_tab->seqScanInit()) + err = xt_ha_pbxt_thread_error_for_mysql(current_thd, xt_get_self(), FALSE); + + return err; +} + +int ha_xtsys::rnd_next(byte *buf) +{ + bool eof; + int err = 0; + + if (!ha_open_tab->seqScanNext((char *) buf, &eof)) { + if (eof) + err = HA_ERR_END_OF_FILE; + else + err = xt_ha_pbxt_thread_error_for_mysql(current_thd, xt_get_self(), FALSE); + } + + return err; +} + +void ha_xtsys::position(const byte *record) +{ + xtWord4 rec_id; + rec_id = ha_open_tab->seqScanPos((xtWord1 *) record); + mi_int4store((xtWord1 *) ref, rec_id); +} + +int ha_xtsys::rnd_pos(byte * buf, byte *pos) +{ + int err = 0; + xtWord4 rec_id; + + rec_id = mi_uint4korr((xtWord1 *) pos); + if (!ha_open_tab->seqScanRead(rec_id, (char *) buf)) + err = xt_ha_pbxt_thread_error_for_mysql(current_thd, xt_get_self(), FALSE); + + return err; +} + +int ha_xtsys::info(uint flag __attribute__((unused))) +{ + return 0; +} + +int ha_xtsys::external_lock(THD *thd, int lock_type) +{ + XTExceptionRec e; + XTThreadPtr self; + int err = 0; + bool ok; + + if (!(self = xt_ha_set_current_thread(thd, &e))) + return xt_ha_pbxt_to_mysql_error(e.e_xt_err); + + if (lock_type == F_UNLCK) + ok = ha_open_tab->unuse(); + else + ok = ha_open_tab->use(); + + if (!ok) + err = xt_ha_pbxt_thread_error_for_mysql(current_thd, xt_get_self(), FALSE); + + return err; +} + +THR_LOCK_DATA **ha_xtsys::store_lock(THD *thd __attribute__((unused)), THR_LOCK_DATA **to, enum thr_lock_type lock_type) +{ + if (lock_type != TL_IGNORE && ha_lock.type == TL_UNLOCK) + ha_lock.type = lock_type; + *to++ = &ha_lock; + return to; +} + +/* Note: ha_pbxt::delete_system_table is called instead. */ +int ha_xtsys::delete_table(const char *table_path __attribute__((unused))) +{ + /* Should never be called */ + return 0; +} + +int ha_xtsys::create(const char *name __attribute__((unused)), TABLE *table_arg __attribute__((unused)), HA_CREATE_INFO *create_info __attribute__((unused))) +{ + /* Allow the table to be created. + * This is required after a dump is restored. + */ + return 0; +} + +bool ha_xtsys::get_error_message(int error __attribute__((unused)), String *buf) +{ + THD *thd = current_thd; + XTExceptionRec e; + XTThreadPtr self; + + if (!(self = xt_ha_set_current_thread(thd, &e))) + return FALSE; + + if (!self->t_exception.e_xt_err) + return FALSE; + + buf->copy(self->t_exception.e_err_msg, strlen(self->t_exception.e_err_msg), system_charset_info); + return TRUE; +} + diff --git a/storage/pbxt/src/ha_xtsys.h b/storage/pbxt/src/ha_xtsys.h new file mode 100644 index 00000000000..66a4b5a5dfa --- /dev/null +++ b/storage/pbxt/src/ha_xtsys.h @@ -0,0 +1,95 @@ +/* Copyright (c) 2008 PrimeBase Technologies GmbH, Germany + * + * PrimeBase XT + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You 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 + * + * Paul McCullagh + * + * 2007-05-20 + * + * H&G2JCtL + * + * PBXT System Table handler. + * + */ +#ifndef __HA_XTSYS_H__ +#define __HA_XTSYS_H__ + +#ifdef DRIZZLED +#include <drizzled/common.h> +#include <drizzled/handler.h> +#include <drizzled/current_session.h> +#else +#include "mysql_priv.h" +#endif + +#include "xt_defs.h" + +#ifdef USE_PRAGMA_INTERFACE +#pragma interface /* gcc class implementation */ +#endif + +#if MYSQL_VERSION_ID >= 50120 +#define byte uchar +#endif + +class XTOpenSystemTable; + +class ha_xtsys: public handler +{ + THR_LOCK_DATA ha_lock; ///< MySQL lock + XTOpenSystemTable *ha_open_tab; + +public: + ha_xtsys(handlerton *hton, TABLE_SHARE *table_arg); + ~ha_xtsys() { } + + const char *table_type() const { return "PBXT"; } + + const char *index_type(uint inx __attribute__((unused))) { + return "NONE"; + } + + const char **bas_ext() const; + + MX_TABLE_TYPES_T table_flags() const { + return HA_BINLOG_ROW_CAPABLE | HA_BINLOG_STMT_CAPABLE; + } + + MX_ULONG_T index_flags(uint inx __attribute__((unused)), uint part __attribute__((unused)), bool all_parts __attribute__((unused))) const { + return (HA_READ_NEXT | HA_READ_PREV | HA_READ_RANGE | HA_KEYREAD_ONLY); + } + uint max_supported_keys() const { return 512; } + uint max_supported_key_part_length() const { return 1024; } + + int open(const char *name, int mode, uint test_if_locked); + int close(void); + int rnd_init(bool scan); + int rnd_next(byte *buf); + int rnd_pos(byte * buf, byte *pos); + void position(const byte *record); + int info(uint); + + int external_lock(THD *thd, int lock_type); + int delete_table(const char *from); + int create(const char *name, TABLE *form, HA_CREATE_INFO *create_info); + + THR_LOCK_DATA **store_lock(THD *thd, THR_LOCK_DATA **to, enum thr_lock_type lock_type); + bool get_error_message(int error, String *buf); +}; + +#endif + diff --git a/storage/pbxt/src/hashtab_xt.cc b/storage/pbxt/src/hashtab_xt.cc new file mode 100644 index 00000000000..3708f071ac5 --- /dev/null +++ b/storage/pbxt/src/hashtab_xt.cc @@ -0,0 +1,264 @@ +/* Copyright (c) 2005 PrimeBase Technologies GmbH + * + * PrimeBase XT + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You 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 + * + * 2005-01-15 Paul McCullagh + * + */ + +#include "xt_config.h" + +#include <ctype.h> + +#include "pthread_xt.h" +#include "heap_xt.h" +#include "thread_xt.h" +#include "hashtab_xt.h" + +XTHashTabPtr xt_new_hashtable(XTThreadPtr self, XTHTCompareFunc comp_func, XTHTHashFunc hash_func, XTHTFreeFunc free_func, xtBool with_lock, xtBool with_cond) +{ + XTHashTabPtr ht; + xtHashValue tab_size = 223; + + ht = (XTHashTabPtr) xt_calloc(self, offsetof(XTHashTabRec, ht_items) + (sizeof(XTHashItemPtr) * tab_size)); + ht->ht_comp_func = comp_func; + ht->ht_hash_func = hash_func; + ht->ht_free_func = free_func; + ht->ht_tab_size = tab_size; + + if (with_lock || with_cond) { + ht->ht_lock = (xt_mutex_type *) xt_calloc(self, sizeof(xt_mutex_type)); + try_(a) { + xt_init_mutex_with_autoname(self, ht->ht_lock); + } + catch_(a) { + xt_free(self, ht->ht_lock); + xt_free(self, ht); + throw_(); + } + cont_(a); + } + + if (with_cond) { + ht->ht_cond = (xt_cond_type *) xt_calloc(self, sizeof(xt_cond_type)); + try_(b) { + xt_init_cond(self, ht->ht_cond); + } + catch_(b) { + xt_free(self, ht->ht_cond); + ht->ht_cond = NULL; + xt_free_hashtable(self, ht); + throw_(); + } + cont_(b); + } + + return ht; +} + +void xt_free_hashtable(XTThreadPtr self, XTHashTabPtr ht) +{ + xtHashValue i; + XTHashItemPtr item, tmp_item; + + if (ht->ht_lock) + xt_lock_mutex(self, ht->ht_lock); + for (i=0; i<ht->ht_tab_size; i++) { + item = ht->ht_items[i]; + while (item) { + if (ht->ht_free_func) + (*ht->ht_free_func)(self, item->hi_data); + tmp_item = item; + item = item->hi_next; + xt_free(self, tmp_item); + } + } + if (ht->ht_lock) + xt_unlock_mutex(self, ht->ht_lock); + if (ht->ht_lock) { + xt_free_mutex(ht->ht_lock); + xt_free(self, ht->ht_lock); + } + if (ht->ht_cond) { + xt_free_cond(ht->ht_cond); + xt_free(self, ht->ht_cond); + } + xt_free(self, ht); +} + +xtPublic void xt_ht_put(XTThreadPtr self, XTHashTabPtr ht, void *data) +{ + XTHashItemPtr item = NULL; + xtHashValue h; + + pushr_(ht->ht_free_func, data); + h = (*ht->ht_hash_func)(FALSE, data); + item = (XTHashItemPtr) xt_malloc(self, sizeof(XTHashItemRec)); + item->hi_data = data; + item->hi_hash = h; + item->hi_next = ht->ht_items[h % ht->ht_tab_size]; + ht->ht_items[h % ht->ht_tab_size] = item; + popr_(); +} + +xtPublic void *xt_ht_get(XTThreadPtr self __attribute__((unused)), XTHashTabPtr ht, void *key) +{ + XTHashItemPtr item; + xtHashValue h; + void *data = NULL; + + h = (*ht->ht_hash_func)(TRUE, key); + + item = ht->ht_items[h % ht->ht_tab_size]; + while (item) { + if (item->hi_hash == h && (*ht->ht_comp_func)(key, item->hi_data)) { + data = item->hi_data; + break; + } + item = item->hi_next; + } + + return data; +} + +xtPublic xtBool xt_ht_del(XTThreadPtr self, XTHashTabPtr ht, void *key) +{ + XTHashItemPtr item, pitem = NULL; + xtHashValue h; + xtBool found = FALSE; + + h = (*ht->ht_hash_func)(TRUE, key); + + item = ht->ht_items[h % ht->ht_tab_size]; + while (item) { + if (item->hi_hash == h && (*ht->ht_comp_func)(key, item->hi_data)) { + void *data; + + found = TRUE; + data = item->hi_data; + + /* Unlink the item: */ + if (pitem) + pitem->hi_next = item->hi_next; + else + ht->ht_items[h % ht->ht_tab_size] = item->hi_next; + + /* Free the item: */ + xt_free(self, item); + + /* Free the data */ + if (ht->ht_free_func) + (*ht->ht_free_func)(self, data); + break; + } + pitem = item; + item = item->hi_next; + } + + return found; +} + +xtPublic xtHashValue xt_ht_hash(char *s) +{ + register char *p; + register xtHashValue h = 0, g; + + p = s; + while (*p) { + h = (h << 4) + *p; + /* Assignment intended here! */ + if ((g = h & 0xF0000000)) { + h = h ^ (g >> 24); + h = h ^ g; + } + p++; + } + return h; +} + +/* + * The case-insensitive version of the hash... + */ +xtPublic xtHashValue xt_ht_casehash(char *s) +{ + register char *p; + register xtHashValue h = 0, g; + + p = s; + while (*p) { + h = (h << 4) + tolower(*p); + /* Assignment intended here! */ + if ((g = h & 0xF0000000)) { + h = h ^ (g >> 24); + h = h ^ g; + } + p++; + } + return h; +} + +xtPublic xtBool xt_ht_lock(XTThreadPtr self, XTHashTabPtr ht) +{ + if (ht->ht_lock) + return xt_lock_mutex(self, ht->ht_lock); + return TRUE; +} + +xtPublic void xt_ht_unlock(XTThreadPtr self, XTHashTabPtr ht) +{ + if (ht->ht_lock) + xt_unlock_mutex(self, ht->ht_lock); +} + +xtPublic void xt_ht_wait(XTThreadPtr self, XTHashTabPtr ht) +{ + xt_wait_cond(self, ht->ht_cond, ht->ht_lock); +} + +xtPublic void xt_ht_timed_wait(XTThreadPtr self, XTHashTabPtr ht, u_long milli_sec) +{ + xt_timed_wait_cond(self, ht->ht_cond, ht->ht_lock, milli_sec); +} + +xtPublic void xt_ht_signal(XTThreadPtr self, XTHashTabPtr ht) +{ + xt_signal_cond(self, ht->ht_cond); +} + +xtPublic void xt_ht_enum(struct XTThread *self __attribute__((unused)), XTHashTabPtr ht, XTHashEnumPtr en) +{ + en->he_i = 0; + en->he_item = NULL; + en->he_ht = ht; +} + +xtPublic void *xt_ht_next(struct XTThread *self __attribute__((unused)), XTHashEnumPtr en) +{ + if (en->he_item) { + en->he_item = en->he_item->hi_next; + if (en->he_item) + return en->he_item->hi_data; + en->he_i++; + } + while (en->he_i < en->he_ht->ht_tab_size) { + if ((en->he_item = en->he_ht->ht_items[en->he_i])) + return en->he_item->hi_data; + en->he_i++; + } + return NULL; +} + diff --git a/storage/pbxt/src/hashtab_xt.h b/storage/pbxt/src/hashtab_xt.h new file mode 100644 index 00000000000..d6085c4288d --- /dev/null +++ b/storage/pbxt/src/hashtab_xt.h @@ -0,0 +1,78 @@ +/* Copyright (c) 2005 PrimeBase Technologies GmbH + * + * PrimeBase XT + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You 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 + * + * 2005-01-15 Paul McCullagh + * + * H&G2JCtL + */ +#ifndef __xt_hashtab_h__ +#define __xt_hashtab_h__ + +#include "xt_defs.h" + +struct XTThread; + +#define xtHashValue u_int + +typedef xtBool (*XTHTCompareFunc)(void *key, void *data); +typedef xtHashValue (*XTHTHashFunc)(xtBool is_key, void *key_data); +typedef void (*XTHTFreeFunc)(struct XTThread *self, void *item); + +typedef struct XTHashItem { + struct XTHashItem *hi_next; + xtHashValue hi_hash; + void *hi_data; +} XTHashItemRec, *XTHashItemPtr; + +typedef struct XTHashTab { + XTHTCompareFunc ht_comp_func; + XTHTHashFunc ht_hash_func; + XTHTFreeFunc ht_free_func; + xt_mutex_type *ht_lock; + xt_cond_type *ht_cond; + + xtHashValue ht_tab_size; + XTHashItemPtr ht_items[XT_VAR_LENGTH]; +} XTHashTabRec, *XTHashTabPtr; + +typedef struct XTHashEnum { + u_int he_i; + XTHashItemPtr he_item; + XTHashTabPtr he_ht; +} XTHashEnumRec, *XTHashEnumPtr; + +XTHashTabPtr xt_new_hashtable(struct XTThread *self, XTHTCompareFunc comp_func, XTHTHashFunc hash_func, XTHTFreeFunc free_func, xtBool with_lock, xtBool with_cond); +void xt_free_hashtable(struct XTThread *self, XTHashTabPtr ht); + +void xt_ht_put(struct XTThread *self, XTHashTabPtr ht, void *data); +void *xt_ht_get(struct XTThread *self, XTHashTabPtr ht, void *key); +xtBool xt_ht_del(struct XTThread *self, XTHashTabPtr ht, void *key); + +xtHashValue xt_ht_hash(char *s); +xtHashValue xt_ht_casehash(char *s); + +xtBool xt_ht_lock(struct XTThread *self, XTHashTabPtr ht); +void xt_ht_unlock(struct XTThread *self, XTHashTabPtr ht); +void xt_ht_wait(struct XTThread *self, XTHashTabPtr ht); +void xt_ht_timed_wait(struct XTThread *self, XTHashTabPtr ht, u_long milli_sec); +void xt_ht_signal(struct XTThread *self, XTHashTabPtr ht); + +void xt_ht_enum(struct XTThread *self, XTHashTabPtr ht, XTHashEnumPtr en); +void *xt_ht_next(struct XTThread *self, XTHashEnumPtr en); + +#endif diff --git a/storage/pbxt/src/heap_xt.cc b/storage/pbxt/src/heap_xt.cc new file mode 100644 index 00000000000..a88df833a4a --- /dev/null +++ b/storage/pbxt/src/heap_xt.cc @@ -0,0 +1,129 @@ +/* Copyright (c) 2005 PrimeBase Technologies GmbH + * + * PrimeBase XT + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You 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 + * + * 2005-01-10 Paul McCullagh + * + * H&G2JCtL + */ + +#include "xt_config.h" + +#include "pthread_xt.h" +#include "heap_xt.h" +#include "thread_xt.h" + +#ifdef xt_heap_new +#undef xt_heap_new +#endif + +#ifdef DEBUG +xtPublic XTHeapPtr xt_mm_heap_new(XTThreadPtr self, size_t size, XTFinalizeFunc finalize, u_int line, c_char *file, xtBool track) +#else +xtPublic XTHeapPtr xt_heap_new(XTThreadPtr self, size_t size, XTFinalizeFunc finalize) +#endif +{ + volatile XTHeapPtr hp; + +#ifdef DEBUG + hp = (XTHeapPtr) xt_mm_calloc(self, size, line, file); + hp->h_track = track; + if (track) + printf("HEAP: +1 1 %s:%d\n", file, (int) line); +#else + hp = (XTHeapPtr) xt_calloc(self, size); +#endif + if (!hp) + return NULL; + + try_(a) { + xt_spinlock_init_with_autoname(self, &hp->h_lock); + } + catch_(a) { + xt_free(self, hp); + throw_(); + } + cont_(a); + + hp->h_ref_count = 1; + hp->h_finalize = finalize; + hp->h_onrelease = NULL; + return hp; +} + +xtPublic void xt_check_heap(XTThreadPtr self __attribute__((unused)), XTHeapPtr hp __attribute__((unused))) +{ +#ifdef DEBUG + xt_mm_malloc_size(self, hp); +#endif +} + +#ifdef DEBUG +xtPublic void xt_mm_heap_reference(XTThreadPtr self, XTHeapPtr hp, u_int line, c_char *file) +#else +xtPublic void xt_heap_reference(XTThreadPtr, XTHeapPtr hp) +#endif +{ + xt_spinlock_lock(&hp->h_lock); +#ifdef DEBUG + if (hp->h_track) + printf("HEAP: +1 %d->%d %s:%d\n", (int) hp->h_ref_count, (int) hp->h_ref_count+1, file, (int) line); +#endif + hp->h_ref_count++; + xt_spinlock_unlock(&hp->h_lock); +} + +xtPublic void xt_heap_release(XTThreadPtr self, XTHeapPtr hp) +{ + if (!hp) + return; +#ifdef DEBUG + xt_spinlock_lock(&hp->h_lock); + ASSERT(hp->h_ref_count != 0); + xt_spinlock_unlock(&hp->h_lock); +#endif + xt_spinlock_lock(&hp->h_lock); + if (hp->h_onrelease) + (*hp->h_onrelease)(self, hp); + if (hp->h_ref_count > 0) { +#ifdef DEBUG + if (hp->h_track) + printf("HEAP: -1 %d->%d\n", (int) hp->h_ref_count, (int) hp->h_ref_count-1); +#endif + hp->h_ref_count--; + if (hp->h_ref_count == 0) { + if (hp->h_finalize) + (*hp->h_finalize)(self, hp); + xt_spinlock_unlock(&hp->h_lock); + xt_free(self, hp); + return; + } + } + xt_spinlock_unlock(&hp->h_lock); +} + +xtPublic void xt_heap_set_release_callback(XTThreadPtr self __attribute__((unused)), XTHeapPtr hp, XTFinalizeFunc onrelease) +{ + hp->h_onrelease = onrelease; +} + +xtPublic u_int xt_heap_get_ref_count(struct XTThread *self __attribute__((unused)), XTHeapPtr hp) +{ + return hp->h_ref_count; +} + + diff --git a/storage/pbxt/src/heap_xt.h b/storage/pbxt/src/heap_xt.h new file mode 100644 index 00000000000..afad132e1e3 --- /dev/null +++ b/storage/pbxt/src/heap_xt.h @@ -0,0 +1,68 @@ +/* Copyright (c) 2005 PrimeBase Technologies GmbH + * + * PrimeBase XT + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You 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 + * + * 2005-01-10 Paul McCullagh + * + * H&G2JCtL + */ +#ifndef __xt_heap_h__ +#define __xt_heap_h__ + +#include "xt_defs.h" +#include "lock_xt.h" + +struct XTThread; + +/* + * Heap memory has a reference count, and a lock for shared access. + * It also has a finalize routine which is called before the memory is + * freed. + */ +typedef void (*XTFinalizeFunc)(struct XTThread *self, void *heap_ptr); + +typedef struct XTHeap { + XTSpinLockRec h_lock; /* Prevent concurrent access to the heap memory: */ + u_int h_ref_count; /* So we know when to free (EVERY pointer reference MUST be counted). */ + XTFinalizeFunc h_finalize; /* If non-NULL, call before freeing. */ + XTFinalizeFunc h_onrelease; /* If non-NULL, call on release. */ +#ifdef DEBUG + xtBool h_track; +#endif +} XTHeapRec, *XTHeapPtr; + +/* Returns with reference count = 1 */ +XTHeapPtr xt_heap_new(struct XTThread *self, size_t size, XTFinalizeFunc finalize); +XTHeapPtr xt_mm_heap_new(struct XTThread *self, size_t size, XTFinalizeFunc finalize, u_int line, c_char *file, xtBool track); + +void xt_heap_set_release_callback(struct XTThread *self, XTHeapPtr mem, XTFinalizeFunc onrelease); + +void xt_heap_reference(struct XTThread *self, XTHeapPtr mem); +void xt_mm_heap_reference(struct XTThread *self, XTHeapPtr hp, u_int line, c_char *file); + +void xt_heap_release(struct XTThread *self, XTHeapPtr mem); +u_int xt_heap_get_ref_count(struct XTThread *self, XTHeapPtr mem); + +void xt_check_heap(struct XTThread *self, XTHeapPtr mem); + +#ifdef DEBUG +#define xt_heap_new(t, s, f) xt_mm_heap_new(t, s, f, __LINE__, __FILE__, FALSE) +#define xt_heap_new_track(t, s, f) xt_mm_heap_new(t, s, f, __LINE__, __FILE__, TRUE) +#define xt_heap_reference(t, s) xt_mm_heap_reference(t, s, __LINE__, __FILE__) +#endif + +#endif diff --git a/storage/pbxt/src/index_xt.cc b/storage/pbxt/src/index_xt.cc new file mode 100644 index 00000000000..9cd9a966f74 --- /dev/null +++ b/storage/pbxt/src/index_xt.cc @@ -0,0 +1,3854 @@ +/* Copyright (c) 2005 PrimeBase Technologies GmbH + * + * PrimeBase XT + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You 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 + * + * 2005-09-30 Paul McCullagh + * + * H&G2JCtL + */ + +#include "xt_config.h" + +#include <string.h> +#include <stdio.h> +#include <stddef.h> +#ifndef XT_WIN +#include <strings.h> +#endif + +#ifdef DRIZZLED +#include <drizzled/base.h> +#else +#include "mysql_priv.h" +#endif + +#include "pthread_xt.h" +#include "memory_xt.h" +#include "index_xt.h" +#include "heap_xt.h" +#include "database_xt.h" +#include "strutil_xt.h" +#include "cache_xt.h" +#include "myxt_xt.h" +#include "trace_xt.h" +#include "table_xt.h" + +#ifdef DEBUG +#define MAX_SEARCH_DEPTH 32 +//#define CHECK_AND_PRINT +//#define CHECK_NODE_REFERENCE +//#define TRACE_FLUSH +//#define CHECK_PRINTS_RECORD_REFERENCES +#else +#define MAX_SEARCH_DEPTH 100 +#endif + +#define IND_FLUSH_BUFFER_SIZE 200 + +typedef struct IdxStackItem { + XTIdxItemRec i_pos; + xtIndexNodeID i_branch; +} IdxStackItemRec, *IdxStackItemPtr; + +typedef struct IdxBranchStack { + int s_top; + IdxStackItemRec s_elements[MAX_SEARCH_DEPTH]; +} IdxBranchStackRec, *IdxBranchStackPtr; + +#ifdef DEBUG +#ifdef TEST_CODE +static void idx_check_on_key(XTOpenTablePtr ot); +#endif +static u_int idx_check_index(XTOpenTablePtr ot, XTIndexPtr ind, xtBool with_lock); +#endif + +static xtBool idx_insert_node(XTOpenTablePtr ot, XTIndexPtr ind, IdxBranchStackPtr stack, XTIdxKeyValuePtr key_value, xtIndexNodeID branch); + +#ifdef XT_TRACK_INDEX_UPDATES + +static xtBool ind_track_write(struct XTOpenTable *ot, struct XTIndex *ind, xtIndexNodeID offset, size_t size, xtWord1 *data) +{ + ot->ot_ind_reads++; + return xt_ind_write(ot, ind, offset, size, data); +} + +#define XT_IND_WRITE ind_track_write + +#else + +#define XT_IND_WRITE xt_ind_write + +#endif + + +#ifdef CHECK_NODE_REFERENCE +#define IDX_GET_NODE_REF(t, x, o) idx_get_node_ref(t, x, o) +#else +#define IDX_GET_NODE_REF(t, x, o) XT_GET_NODE_REF(t, (x) - (o)) +#endif + +/* + * ----------------------------------------------------------------------- + * DEBUG ACTIVITY + */ + +//#define TRACK_ACTIVITY + +#ifdef TRACK_ACTIVITY +#define TRACK_MAX_BLOCKS 2000 + +typedef struct TrackBlock { + xtWord1 exists; + char *activity; +} TrackBlockRec, *TrackBlockPtr; + +TrackBlockRec blocks[TRACK_MAX_BLOCKS]; + +xtPublic void track_work(u_int block, char *what) +{ + int len = 0, len2; + + ASSERT_NS(block > 0 && block <= TRACK_MAX_BLOCKS); + block--; + if (blocks[block].activity) + len = strlen(blocks[block].activity); + len2 = strlen(what); + xt_realloc_ns((void **) &blocks[block].activity, len + len2 + 1); + memcpy(blocks[block].activity + len, what, len2 + 1); +} + +static void track_block_exists(xtIndexNodeID block) +{ + if (XT_NODE_ID(block) > 0 && XT_NODE_ID(block) <= TRACK_MAX_BLOCKS) + blocks[XT_NODE_ID(block)-1].exists = TRUE; +} + +static void track_reset_missing() +{ + for (u_int i=0; i<TRACK_MAX_BLOCKS; i++) + blocks[i].exists = FALSE; +} + +static void track_dump_missing(xtIndexNodeID eof_block) +{ + for (u_int i=0; i<XT_NODE_ID(eof_block)-1; i++) { + if (!blocks[i].exists) + printf("block missing = %04d %s\n", i+1, blocks[i].activity); + } +} + +static void track_dump_all(u_int max_block) +{ + for (u_int i=0; i<max_block; i++) { + if (blocks[i].exists) + printf(" %04d %s\n", i+1, blocks[i].activity); + else + printf("-%04d %s\n", i+1, blocks[i].activity); + } +} + +#endif + +xtPublic void xt_ind_track_dump_block(XTTableHPtr tab __attribute__((unused)), xtIndexNodeID address __attribute__((unused))) +{ +#ifdef TRACK_ACTIVITY + u_int i = XT_NODE_ID(address)-1; + + printf("BLOCK %04d %s\n", i+1, blocks[i].activity); +#endif +} + +#ifdef CHECK_NODE_REFERENCE +static xtIndexNodeID idx_get_node_ref(XTTableHPtr tab, xtWord1 *ref, u_int node_ref_size) +{ + xtIndexNodeID node; + + /* Node is invalid by default: */ + XT_NODE_ID(node) = 0xFFFFEEEE; + if (node_ref_size) { + ref -= node_ref_size; + node = XT_RET_NODE_ID(XT_GET_DISK_4(ref)); + if (node >= tab->tab_ind_eof) { + xt_register_taberr(XT_REG_CONTEXT, XT_ERR_INDEX_CORRUPTED, tab->tab_name); + } + } + return node; +} +#endif + +/* + * ----------------------------------------------------------------------- + * Stack functions + */ + +static void idx_newstack(IdxBranchStackPtr stack) +{ + stack->s_top = 0; +} + +static xtBool idx_push(IdxBranchStackPtr stack, xtIndexNodeID n, XTIdxItemPtr pos) +{ + if (stack->s_top == MAX_SEARCH_DEPTH) { + xt_register_error(XT_REG_CONTEXT, XT_ERR_STACK_OVERFLOW, 0, "Index node stack overflow"); + return FAILED; + } + stack->s_elements[stack->s_top].i_branch = n; + if (pos) + stack->s_elements[stack->s_top].i_pos = *pos; + stack->s_top++; + return OK; +} + +static IdxStackItemPtr idx_pop(IdxBranchStackPtr stack) +{ + if (stack->s_top == 0) + return NULL; + stack->s_top--; + return &stack->s_elements[stack->s_top]; +} + +static IdxStackItemPtr idx_top(IdxBranchStackPtr stack) +{ + if (stack->s_top == 0) + return NULL; + return &stack->s_elements[stack->s_top-1]; +} + +/* + * ----------------------------------------------------------------------- + * Allocation of nodes + */ + +static xtBool idx_new_branch(XTOpenTablePtr ot, XTIndexPtr ind, xtIndexNodeID *address) +{ + register XTTableHPtr tab; + xtIndexNodeID wrote_pos; + XTIndFreeBlockRec free_block; + XTIndFreeListPtr list_ptr; + + tab = ot->ot_table; + + //ASSERT_NS(XT_INDEX_HAVE_XLOCK(ind, ot)); + if (ind->mi_free_list && ind->mi_free_list->fl_free_count) { + ind->mi_free_list->fl_free_count--; + *address = ind->mi_free_list->fl_page_id[ind->mi_free_list->fl_free_count]; + TRACK_BLOCK_ALLOC(*address); + return OK; + } + + xt_lock_mutex_ns(&tab->tab_ind_lock); + + /* Check the cached free list: */ + while ((list_ptr = tab->tab_ind_free_list)) { + if (list_ptr->fl_start < list_ptr->fl_free_count) { + wrote_pos = list_ptr->fl_page_id[list_ptr->fl_start]; + list_ptr->fl_start++; + xt_unlock_mutex_ns(&tab->tab_ind_lock); + *address = wrote_pos; + TRACK_BLOCK_ALLOC(wrote_pos); + return OK; + } + tab->tab_ind_free_list = list_ptr->fl_next_list; + xt_free_ns(list_ptr); + } + + if ((XT_NODE_ID(wrote_pos) = XT_NODE_ID(tab->tab_ind_free))) { + /* Use the block on the free list: */ + if (!xt_ind_read_bytes(ot, wrote_pos, sizeof(XTIndFreeBlockRec), (xtWord1 *) &free_block)) + goto failed; + XT_NODE_ID(tab->tab_ind_free) = (xtIndexNodeID) XT_GET_DISK_8(free_block.if_next_block_8); + xt_unlock_mutex_ns(&tab->tab_ind_lock); + *address = wrote_pos; + TRACK_BLOCK_ALLOC(wrote_pos); + return OK; + } + + /* PMC - Dont allow overflow! */ + if (XT_NODE_ID(tab->tab_ind_eof) >= 0xFFFFFFF) { + xt_register_ixterr(XT_REG_CONTEXT, XT_ERR_INDEX_FILE_TO_LARGE, xt_file_path(ot->ot_ind_file)); + goto failed; + } + *address = tab->tab_ind_eof; + XT_NODE_ID(tab->tab_ind_eof)++; + xt_unlock_mutex_ns(&tab->tab_ind_lock); + TRACK_BLOCK_ALLOC(*address); + return OK; + + failed: + xt_unlock_mutex_ns(&tab->tab_ind_lock); + return FAILED; +} + +/* Add the block to the private free list of the index. + * On flush, this list will be transfered to the global list. + */ +static xtBool idx_free_branch(XTOpenTablePtr ot, XTIndexPtr ind, xtIndexNodeID node_id) +{ + register u_int count; + register u_int i; + register u_int guess; + + TRACK_BLOCK_FREE(node_id); + //ASSERT_NS(XT_INDEX_HAVE_XLOCK(ind, ot)); + if (!ind->mi_free_list) { + count = 0; + if (!(ind->mi_free_list = (XTIndFreeListPtr) xt_calloc_ns(offsetof(XTIndFreeListRec, fl_page_id) + 10 * sizeof(xtIndexNodeID)))) + return FAILED; + } + else { + count = ind->mi_free_list->fl_free_count; + if (!xt_realloc_ns((void **) &ind->mi_free_list, offsetof(XTIndFreeListRec, fl_page_id) + (count + 1) * sizeof(xtIndexNodeID))) + return FAILED; + } + + i = 0; + while (i < count) { + guess = (i + count - 1) >> 1; + if (XT_NODE_ID(node_id) == XT_NODE_ID(ind->mi_free_list->fl_page_id[guess])) { + // Should not happen... + ASSERT_NS(FALSE); + return OK; + } + if (XT_NODE_ID(node_id) < XT_NODE_ID(ind->mi_free_list->fl_page_id[guess])) + count = guess; + else + i = guess + 1; + } + + /* Insert at position i */ + memmove(ind->mi_free_list->fl_page_id + i + 1, ind->mi_free_list->fl_page_id + i, (ind->mi_free_list->fl_free_count - i) * sizeof(xtIndexNodeID)); + ind->mi_free_list->fl_page_id[i] = node_id; + ind->mi_free_list->fl_free_count++; + + /* Set the cache page to clean: */ + return xt_ind_clean(ot, ind, node_id); +} + +/* + * ----------------------------------------------------------------------- + * Simple compare functions + */ + +xtPublic int xt_compare_2_int4(XTIndexPtr ind __attribute__((unused)), uint key_length, xtWord1 *key_value, xtWord1 *b_value) +{ + int r; + + ASSERT_NS(key_length == 4 || key_length == 8); + r = (xtInt4) XT_GET_DISK_4(key_value) - (xtInt4) XT_GET_DISK_4(b_value); + if (r == 0 && key_length > 4) { + key_value += 4; + b_value += 4; + r = (xtInt4) XT_GET_DISK_4(key_value) - (xtInt4) XT_GET_DISK_4(b_value); + } + return r; +} + +xtPublic int xt_compare_3_int4(XTIndexPtr ind __attribute__((unused)), uint key_length, xtWord1 *key_value, xtWord1 *b_value) +{ + int r; + + ASSERT_NS(key_length == 4 || key_length == 8 || key_length == 12); + r = (xtInt4) XT_GET_DISK_4(key_value) - (xtInt4) XT_GET_DISK_4(b_value); + if (r == 0 && key_length > 4) { + key_value += 4; + b_value += 4; + r = (xtInt4) XT_GET_DISK_4(key_value) - (xtInt4) XT_GET_DISK_4(b_value); + if (r == 0 && key_length > 8) { + key_value += 4; + b_value += 4; + r = (xtInt4) XT_GET_DISK_4(key_value) - (xtInt4) XT_GET_DISK_4(b_value); + } + } + return r; +} + +/* + * ----------------------------------------------------------------------- + * Tree branch sanning (searching nodes and leaves) + */ + +xtPublic void xt_scan_branch_single(struct XTTable *tab __attribute__((unused)), XTIndexPtr ind, XTIdxBranchDPtr branch, register XTIdxKeyValuePtr value, register XTIdxResultRec *result) +{ + XT_NODE_TEMP; + u_int branch_size; + u_int node_ref_size; + u_int full_item_size; + int search_flags; + register xtWord1 *base; + register u_int i; + register xtWord1 *bitem; + + branch_size = XT_GET_DISK_2(branch->tb_size_2); + node_ref_size = XT_IS_NODE(branch_size) ? XT_NODE_REF_SIZE : 0; + + result->sr_found = FALSE; + result->sr_duplicate = FALSE; + result->sr_item.i_total_size = XT_GET_BRANCH_DATA_SIZE(branch_size); + ASSERT_NS((int) result->sr_item.i_total_size >= 0 && result->sr_item.i_total_size <= XT_INDEX_PAGE_SIZE-2); + + result->sr_item.i_item_size = ind->mi_key_size + XT_RECORD_REF_SIZE; + full_item_size = result->sr_item.i_item_size + node_ref_size; + result->sr_item.i_node_ref_size = node_ref_size; + + search_flags = value->sv_flags; + base = branch->tb_data + node_ref_size; + if (search_flags & XT_SEARCH_FIRST_FLAG) + i = 0; + else if (search_flags & XT_SEARCH_AFTER_LAST_FLAG) + i = (result->sr_item.i_total_size - node_ref_size) / full_item_size; + else { + register u_int guess; + register u_int count; + register xtInt4 r; + xtRecordID key_record; + + key_record = value->sv_rec_id; + count = (result->sr_item.i_total_size - node_ref_size) / full_item_size; + + ASSERT_NS(ind); + i = 0; + while (i < count) { + guess = (i + count - 1) >> 1; + + bitem = base + guess * full_item_size; + + switch (ind->mi_single_type) { + case HA_KEYTYPE_LONG_INT: { + register xtInt4 a, b; + + a = XT_GET_DISK_4(value->sv_key); + b = XT_GET_DISK_4(bitem); + r = (a < b) ? -1 : (a == b ? 0 : 1); + break; + } + case HA_KEYTYPE_ULONG_INT: { + register xtWord4 a, b; + + a = XT_GET_DISK_4(value->sv_key); + b = XT_GET_DISK_4(bitem); + r = (a < b) ? -1 : (a == b ? 0 : 1); + break; + } + default: + /* Should not happen: */ + r = 1; + break; + } + if (r == 0) { + if (search_flags & XT_SEARCH_WHOLE_KEY) { + xtRecordID item_record; + xtRowID row_id; + + xt_get_record_ref(bitem + ind->mi_key_size, &item_record, &row_id); + + /* This should not happen because we should never + * try to insert the same record twice into the + * index! + */ + result->sr_duplicate = TRUE; + if (key_record == item_record) { + result->sr_found = TRUE; + result->sr_rec_id = item_record; + result->sr_row_id = row_id; + result->sr_branch = IDX_GET_NODE_REF(tab, bitem, node_ref_size); + result->sr_item.i_item_offset = node_ref_size + guess * full_item_size; + return; + } + if (key_record < item_record) + r = -1; + else + r = 1; + } + else { + result->sr_found = TRUE; + /* -1 causes a search to the beginning of the duplicate list of keys. + * 1 causes a search to just after the key. + */ + if (search_flags & XT_SEARCH_AFTER_KEY) + r = 1; + else + r = -1; + } + } + + if (r < 0) + count = guess; + else + i = guess + 1; + } + } + + bitem = base + i * full_item_size; + xt_get_res_record_ref(bitem + ind->mi_key_size, result); + result->sr_branch = IDX_GET_NODE_REF(tab, bitem, node_ref_size); /* Only valid if this is a node. */ + result->sr_item.i_item_offset = node_ref_size + i * full_item_size; +} + +/* + * We use a special binary search here. It basically assumes that the values + * in the index are not unique. + * + * Even if they are unique, when we search for part of a key, then it is + * effectively the case. + * + * So in the situation where we find duplicates in the index we usually + * want to position ourselves at the beginning of the duplicate list. + * + * Alternatively a search can find the position just after a given key. + * + * To achieve this we make the following modifications: + * - The result of the comparison is always returns 1 or -1. We only stop + * the search early in the case an exact match when inserting (but this + * should not happen anyway). + * - The search never actually fails, but sets 'found' to TRUE if it + * sees the search key in the index. + * + * If the search value exists in the index we know that + * this method will take us to the first occurrence of the key in the + * index (in the case of -1) or to the first value after the + * the search key in the case of 1. + */ +xtPublic void xt_scan_branch_fix(struct XTTable *tab __attribute__((unused)), XTIndexPtr ind, XTIdxBranchDPtr branch, register XTIdxKeyValuePtr value, register XTIdxResultRec *result) +{ + XT_NODE_TEMP; + u_int branch_size; + u_int node_ref_size; + u_int full_item_size; + int search_flags; + xtWord1 *base; + register u_int i; + xtWord1 *bitem; + + branch_size = XT_GET_DISK_2(branch->tb_size_2); + node_ref_size = XT_IS_NODE(branch_size) ? XT_NODE_REF_SIZE : 0; + + result->sr_found = FALSE; + result->sr_duplicate = FALSE; + result->sr_item.i_total_size = XT_GET_BRANCH_DATA_SIZE(branch_size); + ASSERT_NS((int) result->sr_item.i_total_size >= 0 && result->sr_item.i_total_size <= XT_INDEX_PAGE_SIZE-2); + + result->sr_item.i_item_size = ind->mi_key_size + XT_RECORD_REF_SIZE; + full_item_size = result->sr_item.i_item_size + node_ref_size; + result->sr_item.i_node_ref_size = node_ref_size; + + search_flags = value->sv_flags; + base = branch->tb_data + node_ref_size; + if (search_flags & XT_SEARCH_FIRST_FLAG) + i = 0; + else if (search_flags & XT_SEARCH_AFTER_LAST_FLAG) + i = (result->sr_item.i_total_size - node_ref_size) / full_item_size; + else { + register u_int guess; + register u_int count; + xtRecordID key_record; + int r; + + key_record = value->sv_rec_id; + count = (result->sr_item.i_total_size - node_ref_size) / full_item_size; + + ASSERT_NS(ind); + i = 0; + while (i < count) { + guess = (i + count - 1) >> 1; + + bitem = base + guess * full_item_size; + + r = myxt_compare_key(ind, search_flags, value->sv_length, value->sv_key, bitem); + + if (r == 0) { + if (search_flags & XT_SEARCH_WHOLE_KEY) { + xtRecordID item_record; + xtRowID row_id; + + xt_get_record_ref(bitem + ind->mi_key_size, &item_record, &row_id); + + /* This should not happen because we should never + * try to insert the same record twice into the + * index! + */ + result->sr_duplicate = TRUE; + if (key_record == item_record) { + result->sr_found = TRUE; + result->sr_rec_id = item_record; + result->sr_row_id = row_id; + result->sr_branch = IDX_GET_NODE_REF(tab, bitem, node_ref_size); + result->sr_item.i_item_offset = node_ref_size + guess * full_item_size; + return; + } + if (key_record < item_record) + r = -1; + else + r = 1; + } + else { + result->sr_found = TRUE; + /* -1 causes a search to the beginning of the duplicate list of keys. + * 1 causes a search to just after the key. + */ + if (search_flags & XT_SEARCH_AFTER_KEY) + r = 1; + else + r = -1; + } + } + + if (r < 0) + count = guess; + else + i = guess + 1; + } + } + + bitem = base + i * full_item_size; + xt_get_res_record_ref(bitem + ind->mi_key_size, result); + result->sr_branch = IDX_GET_NODE_REF(tab, bitem, node_ref_size); /* Only valid if this is a node. */ + result->sr_item.i_item_offset = node_ref_size + i * full_item_size; +} + +xtPublic void xt_scan_branch_fix_simple(struct XTTable *tab __attribute__((unused)), XTIndexPtr ind, XTIdxBranchDPtr branch, register XTIdxKeyValuePtr value, register XTIdxResultRec *result) +{ + XT_NODE_TEMP; + u_int branch_size; + u_int node_ref_size; + u_int full_item_size; + int search_flags; + xtWord1 *base; + register u_int i; + xtWord1 *bitem; + + branch_size = XT_GET_DISK_2(branch->tb_size_2); + node_ref_size = XT_IS_NODE(branch_size) ? XT_NODE_REF_SIZE : 0; + + result->sr_found = FALSE; + result->sr_duplicate = FALSE; + result->sr_item.i_total_size = XT_GET_BRANCH_DATA_SIZE(branch_size); + ASSERT_NS((int) result->sr_item.i_total_size >= 0 && result->sr_item.i_total_size <= XT_INDEX_PAGE_SIZE-2); + + result->sr_item.i_item_size = ind->mi_key_size + XT_RECORD_REF_SIZE; + full_item_size = result->sr_item.i_item_size + node_ref_size; + result->sr_item.i_node_ref_size = node_ref_size; + + search_flags = value->sv_flags; + base = branch->tb_data + node_ref_size; + if (search_flags & XT_SEARCH_FIRST_FLAG) + i = 0; + else if (search_flags & XT_SEARCH_AFTER_LAST_FLAG) + i = (result->sr_item.i_total_size - node_ref_size) / full_item_size; + else { + register u_int guess; + register u_int count; + xtRecordID key_record; + int r; + + key_record = value->sv_rec_id; + count = (result->sr_item.i_total_size - node_ref_size) / full_item_size; + + ASSERT_NS(ind); + i = 0; + while (i < count) { + guess = (i + count - 1) >> 1; + + bitem = base + guess * full_item_size; + + r = ind->mi_simple_comp_key(ind, value->sv_length, value->sv_key, bitem); + + if (r == 0) { + if (search_flags & XT_SEARCH_WHOLE_KEY) { + xtRecordID item_record; + xtRowID row_id; + + xt_get_record_ref(bitem + ind->mi_key_size, &item_record, &row_id); + + /* This should not happen because we should never + * try to insert the same record twice into the + * index! + */ + result->sr_duplicate = TRUE; + if (key_record == item_record) { + result->sr_found = TRUE; + result->sr_rec_id = item_record; + result->sr_row_id = row_id; + result->sr_branch = IDX_GET_NODE_REF(tab, bitem, node_ref_size); + result->sr_item.i_item_offset = node_ref_size + guess * full_item_size; + return; + } + if (key_record < item_record) + r = -1; + else + r = 1; + } + else { + result->sr_found = TRUE; + /* -1 causes a search to the beginning of the duplicate list of keys. + * 1 causes a search to just after the key. + */ + if (search_flags & XT_SEARCH_AFTER_KEY) + r = 1; + else + r = -1; + } + } + + if (r < 0) + count = guess; + else + i = guess + 1; + } + } + + bitem = base + i * full_item_size; + xt_get_res_record_ref(bitem + ind->mi_key_size, result); + result->sr_branch = IDX_GET_NODE_REF(tab, bitem, node_ref_size); /* Only valid if this is a node. */ + result->sr_item.i_item_offset = node_ref_size + i * full_item_size; +} + +/* + * Variable length key values are stored as a sorted list. Since each list item has a variable length, we + * must scan the list sequentially in order to find a key. + */ +xtPublic void xt_scan_branch_var(struct XTTable *tab __attribute__((unused)), XTIndexPtr ind, XTIdxBranchDPtr branch, register XTIdxKeyValuePtr value, register XTIdxResultRec *result) +{ + XT_NODE_TEMP; + u_int branch_size; + u_int node_ref_size; + int search_flags; + xtWord1 *base; + xtWord1 *bitem; + u_int ilen; + xtWord1 *bend; + + branch_size = XT_GET_DISK_2(branch->tb_size_2); + node_ref_size = XT_IS_NODE(branch_size) ? XT_NODE_REF_SIZE : 0; + + result->sr_found = FALSE; + result->sr_duplicate = FALSE; + result->sr_item.i_total_size = XT_GET_BRANCH_DATA_SIZE(branch_size); + ASSERT_NS((int) result->sr_item.i_total_size >= 0 && result->sr_item.i_total_size <= XT_INDEX_PAGE_SIZE-2); + + result->sr_item.i_node_ref_size = node_ref_size; + + search_flags = value->sv_flags; + base = branch->tb_data + node_ref_size; + bitem = base; + bend = &branch->tb_data[result->sr_item.i_total_size]; + ilen = 0; + if (bitem >= bend) + goto done_ok; + + if (search_flags & XT_SEARCH_FIRST_FLAG) + ilen = myxt_get_key_length(ind, bitem); + else if (search_flags & XT_SEARCH_AFTER_LAST_FLAG) { + bitem = bend; + ilen = 0; + } + else { + xtRecordID key_record; + int r; + + key_record = value->sv_rec_id; + + ASSERT_NS(ind); + while (bitem < bend) { + ilen = myxt_get_key_length(ind, bitem); + r = myxt_compare_key(ind, search_flags, value->sv_length, value->sv_key, bitem); + if (r == 0) { + if (search_flags & XT_SEARCH_WHOLE_KEY) { + xtRecordID item_record; + xtRowID row_id; + + xt_get_record_ref(bitem + ilen, &item_record, &row_id); + + /* This should not happen because we should never + * try to insert the same record twice into the + * index! + */ + result->sr_duplicate = TRUE; + if (key_record == item_record) { + result->sr_found = TRUE; + result->sr_item.i_item_size = ilen + XT_RECORD_REF_SIZE; + result->sr_rec_id = item_record; + result->sr_row_id = row_id; + result->sr_branch = IDX_GET_NODE_REF(tab, bitem, node_ref_size); + result->sr_item.i_item_offset = bitem - branch->tb_data; + return; + } + if (key_record < item_record) + r = -1; + else + r = 1; + } + else { + result->sr_found = TRUE; + /* -1 causes a search to the beginning of the duplicate list of keys. + * 1 causes a search to just after the key. + */ + if (search_flags & XT_SEARCH_AFTER_KEY) + r = 1; + else + r = -1; + } + } + if (r <= 0) + break; + bitem += ilen + XT_RECORD_REF_SIZE + node_ref_size; + } + } + + done_ok: + result->sr_item.i_item_size = ilen + XT_RECORD_REF_SIZE; + xt_get_res_record_ref(bitem + ilen, result); + result->sr_branch = IDX_GET_NODE_REF(tab, bitem, node_ref_size); /* Only valid if this is a node. */ + result->sr_item.i_item_offset = bitem - branch->tb_data; +} + +/* Go to the next item in the node. */ +static void idx_next_branch_item(XTTableHPtr tab __attribute__((unused)), XTIndexPtr ind, XTIdxBranchDPtr branch, register XTIdxResultRec *result) +{ + XT_NODE_TEMP; + xtWord1 *bitem; + u_int ilen; + + result->sr_item.i_item_offset += result->sr_item.i_item_size + result->sr_item.i_node_ref_size; + bitem = branch->tb_data + result->sr_item.i_item_offset; + if (ind->mi_fix_key) + ilen = result->sr_item.i_item_size; + else { + ilen = myxt_get_key_length(ind, bitem) + XT_RECORD_REF_SIZE; + result->sr_item.i_item_size = ilen; + } + xt_get_res_record_ref(bitem + ilen - XT_RECORD_REF_SIZE, result); /* (Only valid if i_item_offset < i_total_size) */ + result->sr_branch = IDX_GET_NODE_REF(tab, bitem, result->sr_item.i_node_ref_size); +} + +xtPublic void xt_prev_branch_item_fix(XTTableHPtr tab __attribute__((unused)), XTIndexPtr ind __attribute__((unused)), XTIdxBranchDPtr branch, register XTIdxResultRec *result) +{ + XT_NODE_TEMP; + ASSERT_NS(result->sr_item.i_item_offset >= result->sr_item.i_item_size + result->sr_item.i_node_ref_size + result->sr_item.i_node_ref_size); + result->sr_item.i_item_offset -= (result->sr_item.i_item_size + result->sr_item.i_node_ref_size); + xt_get_res_record_ref(branch->tb_data + result->sr_item.i_item_offset + result->sr_item.i_item_size - XT_RECORD_REF_SIZE, result); /* (Only valid if i_item_offset < i_total_size) */ + result->sr_branch = IDX_GET_NODE_REF(tab, branch->tb_data + result->sr_item.i_item_offset, result->sr_item.i_node_ref_size); +} + +xtPublic void xt_prev_branch_item_var(XTTableHPtr tab __attribute__((unused)), XTIndexPtr ind, XTIdxBranchDPtr branch, register XTIdxResultRec *result) +{ + XT_NODE_TEMP; + xtWord1 *bitem; + xtWord1 *bend; + u_int ilen; + + bitem = branch->tb_data + result->sr_item.i_node_ref_size; + bend = &branch->tb_data[result->sr_item.i_item_offset]; + for (;;) { + ilen = myxt_get_key_length(ind, bitem); + if (bitem + ilen + XT_RECORD_REF_SIZE + result->sr_item.i_node_ref_size >= bend) + break; + bitem += ilen + XT_RECORD_REF_SIZE + result->sr_item.i_node_ref_size; + } + + result->sr_item.i_item_size = ilen + XT_RECORD_REF_SIZE; + xt_get_res_record_ref(bitem + ilen, result); /* (Only valid if i_item_offset < i_total_size) */ + result->sr_branch = IDX_GET_NODE_REF(tab, bitem, result->sr_item.i_node_ref_size); + result->sr_item.i_item_offset = bitem - branch->tb_data; +} + +static void idx_first_branch_item(XTTableHPtr tab __attribute__((unused)), XTIndexPtr ind, XTIdxBranchDPtr branch, register XTIdxResultPtr result) +{ + XT_NODE_TEMP; + u_int branch_size; + u_int node_ref_size; + u_int key_data_size; + + branch_size = XT_GET_DISK_2(branch->tb_size_2); + node_ref_size = XT_IS_NODE(branch_size) ? XT_NODE_REF_SIZE : 0; + + result->sr_found = FALSE; + result->sr_duplicate = FALSE; + result->sr_item.i_total_size = XT_GET_BRANCH_DATA_SIZE(branch_size); + ASSERT_NS((int) result->sr_item.i_total_size >= 0 && result->sr_item.i_total_size <= XT_INDEX_PAGE_SIZE-2); + + if (ind->mi_fix_key) + key_data_size = ind->mi_key_size; + else { + xtWord1 *bitem; + + bitem = branch->tb_data + node_ref_size; + if (bitem < &branch->tb_data[result->sr_item.i_total_size]) + key_data_size = myxt_get_key_length(ind, bitem); + else + key_data_size = 0; + } + + result->sr_item.i_item_size = key_data_size + XT_RECORD_REF_SIZE; + result->sr_item.i_node_ref_size = node_ref_size; + + xt_get_res_record_ref(branch->tb_data + node_ref_size + key_data_size, result); + result->sr_branch = IDX_GET_NODE_REF(tab, branch->tb_data + node_ref_size, node_ref_size); /* Only valid if this is a node. */ + result->sr_item.i_item_offset = node_ref_size; +} + +/* + * Last means different things for leaf or node! + */ +xtPublic void xt_last_branch_item_fix(XTTableHPtr tab __attribute__((unused)), XTIndexPtr ind, XTIdxBranchDPtr branch, register XTIdxResultPtr result) +{ + XT_NODE_TEMP; + u_int branch_size; + u_int node_ref_size; + + branch_size = XT_GET_DISK_2(branch->tb_size_2); + node_ref_size = XT_IS_NODE(branch_size) ? XT_NODE_REF_SIZE : 0; + + result->sr_found = FALSE; + result->sr_duplicate = FALSE; + result->sr_item.i_total_size = XT_GET_BRANCH_DATA_SIZE(branch_size); + ASSERT_NS((int) result->sr_item.i_total_size >= 0 && result->sr_item.i_total_size <= XT_INDEX_PAGE_SIZE-2); + + result->sr_item.i_item_size = ind->mi_key_size + XT_RECORD_REF_SIZE; + result->sr_item.i_node_ref_size = node_ref_size; + + if (node_ref_size) { + result->sr_item.i_item_offset = result->sr_item.i_total_size; + result->sr_branch = IDX_GET_NODE_REF(tab, branch->tb_data + result->sr_item.i_item_offset, node_ref_size); + } + else { + if (result->sr_item.i_total_size) { + result->sr_item.i_item_offset = result->sr_item.i_total_size - result->sr_item.i_item_size; + xt_get_res_record_ref(branch->tb_data + result->sr_item.i_item_offset + ind->mi_key_size, result); + } + else + /* Leaf is empty: */ + result->sr_item.i_item_offset = 0; + } +} + +xtPublic void xt_last_branch_item_var(XTTableHPtr tab __attribute__((unused)), XTIndexPtr ind, XTIdxBranchDPtr branch, register XTIdxResultPtr result) +{ + XT_NODE_TEMP; + u_int branch_size; + u_int node_ref_size; + + branch_size = XT_GET_DISK_2(branch->tb_size_2); + node_ref_size = XT_IS_NODE(branch_size) ? XT_NODE_REF_SIZE : 0; + + result->sr_found = FALSE; + result->sr_duplicate = FALSE; + result->sr_item.i_total_size = XT_GET_BRANCH_DATA_SIZE(branch_size); + ASSERT_NS((int) result->sr_item.i_total_size >= 0 && result->sr_item.i_total_size <= XT_INDEX_PAGE_SIZE-2); + + result->sr_item.i_node_ref_size = node_ref_size; + + if (node_ref_size) { + result->sr_item.i_item_offset = result->sr_item.i_total_size; + result->sr_branch = IDX_GET_NODE_REF(tab, branch->tb_data + result->sr_item.i_item_offset, node_ref_size); + result->sr_item.i_item_size = 0; + } + else { + if (result->sr_item.i_total_size) { + xtWord1 *bitem; + u_int ilen; + xtWord1 *bend; + + bitem = branch->tb_data + node_ref_size;; + bend = &branch->tb_data[result->sr_item.i_total_size]; + ilen = 0; + if (bitem < bend) { + for (;;) { + ilen = myxt_get_key_length(ind, bitem); + if (bitem + ilen + XT_RECORD_REF_SIZE + node_ref_size >= bend) + break; + bitem += ilen + XT_RECORD_REF_SIZE + node_ref_size; + } + } + + result->sr_item.i_item_offset = bitem - branch->tb_data; + xt_get_res_record_ref(bitem + ilen, result); + result->sr_item.i_item_size = ilen + XT_RECORD_REF_SIZE; + } + else { + /* Leaf is empty: */ + result->sr_item.i_item_offset = 0; + result->sr_item.i_item_size = 0; + } + } +} + +/* + * Remove an item and save to disk. + */ +static xtBool idx_remove_branch_item_right(XTOpenTablePtr ot, XTIndexPtr ind, xtIndexNodeID, XTIndReferencePtr iref, register XTIdxItemPtr item) +{ + register XTIdxBranchDPtr branch = iref->ir_branch; + u_int size = item->i_item_size + item->i_node_ref_size; + + /* {HANDLE-COUNT-USAGE} + * This access is safe because we have the right to update + * the page, so no other thread can modify the page. + * + * This means: + * We either have an Xlock on the index, or we have + * an Xlock on the cache block. + */ + if (iref->ir_block->cb_handle_count) { + if (!xt_ind_copy_on_write(iref)) + return FAILED; + } + /* Remove the node reference to the left of the item: */ + memmove(&branch->tb_data[item->i_item_offset], + &branch->tb_data[item->i_item_offset + size], + item->i_total_size - item->i_item_offset - size); + item->i_total_size -= size; + XT_SET_DISK_2(branch->tb_size_2, XT_MAKE_BRANCH_SIZE(item->i_total_size, item->i_node_ref_size)); + IDX_TRACE("%d-> %x\n", (int) XT_NODE_ID(address), (int) XT_GET_DISK_2(branch->tb_size_2)); + xt_ind_release(ot, ind, item->i_node_ref_size ? XT_UNLOCK_R_UPDATE : XT_UNLOCK_W_UPDATE, iref); + return OK; +} + +static xtBool idx_remove_branch_item_left(XTOpenTablePtr ot, XTIndexPtr ind, xtIndexNodeID, XTIndReferencePtr iref, register XTIdxItemPtr item) +{ + register XTIdxBranchDPtr branch = iref->ir_branch; + u_int size = item->i_item_size + item->i_node_ref_size; + + if (iref->ir_block->cb_handle_count) { + if (!xt_ind_copy_on_write(iref)) + return FAILED; + } + /* Remove the node reference to the left of the item: */ + memmove(&branch->tb_data[item->i_item_offset - item->i_node_ref_size], + &branch->tb_data[item->i_item_offset + item->i_item_size], + item->i_total_size - item->i_item_offset - item->i_item_size); + item->i_total_size -= size; + XT_SET_DISK_2(branch->tb_size_2, XT_MAKE_BRANCH_SIZE(item->i_total_size, item->i_node_ref_size)); + IDX_TRACE("%d-> %x\n", (int) XT_NODE_ID(address), (int) XT_GET_DISK_2(branch->tb_size_2)); + xt_ind_release(ot, ind, item->i_node_ref_size ? XT_UNLOCK_R_UPDATE : XT_UNLOCK_W_UPDATE, iref); + return OK; +} + +static void idx_insert_leaf_item(XTIndexPtr ind __attribute__((unused)), XTIdxBranchDPtr leaf, XTIdxKeyValuePtr value, XTIdxResultPtr result) +{ + xtWord1 *item; + + /* This will ensure we do not overwrite the end of the buffer: */ + ASSERT_NS(value->sv_length <= XT_INDEX_MAX_KEY_SIZE); + memmove(&leaf->tb_data[result->sr_item.i_item_offset + value->sv_length + XT_RECORD_REF_SIZE], + &leaf->tb_data[result->sr_item.i_item_offset], + result->sr_item.i_total_size - result->sr_item.i_item_offset); + item = &leaf->tb_data[result->sr_item.i_item_offset]; + memcpy(item, value->sv_key, value->sv_length); + xt_set_val_record_ref(item + value->sv_length, value); + result->sr_item.i_total_size += value->sv_length + XT_RECORD_REF_SIZE; + XT_SET_DISK_2(leaf->tb_size_2, XT_MAKE_LEAF_SIZE(result->sr_item.i_total_size)); +} + +static void idx_insert_node_item(XTTableHPtr tab __attribute__((unused)), XTIndexPtr ind __attribute__((unused)), XTIdxBranchDPtr leaf, XTIdxKeyValuePtr value, XTIdxResultPtr result, xtIndexNodeID branch) +{ + xtWord1 *item; + + /* This will ensure we do not overwrite the end of the buffer: */ + ASSERT_NS(value->sv_length <= XT_INDEX_MAX_KEY_SIZE); + memmove(&leaf->tb_data[result->sr_item.i_item_offset + value->sv_length + XT_RECORD_REF_SIZE + result->sr_item.i_node_ref_size], + &leaf->tb_data[result->sr_item.i_item_offset], + result->sr_item.i_total_size - result->sr_item.i_item_offset); + item = &leaf->tb_data[result->sr_item.i_item_offset]; + memcpy(item, value->sv_key, value->sv_length); + xt_set_val_record_ref(item + value->sv_length, value); + XT_SET_NODE_REF(tab, item + value->sv_length + XT_RECORD_REF_SIZE, branch); + result->sr_item.i_total_size += value->sv_length + XT_RECORD_REF_SIZE + result->sr_item.i_node_ref_size; + XT_SET_DISK_2(leaf->tb_size_2, XT_MAKE_NODE_SIZE(result->sr_item.i_total_size)); +} + +static void idx_get_middle_branch_item(XTIndexPtr ind, XTIdxBranchDPtr branch, XTIdxKeyValuePtr value, XTIdxResultPtr result) +{ + xtWord1 *bitem; + + if (ind->mi_fix_key) { + u_int full_item_size = result->sr_item.i_item_size + result->sr_item.i_node_ref_size; + + result->sr_item.i_item_offset = ((result->sr_item.i_total_size - result->sr_item.i_node_ref_size) + / full_item_size / 2 * full_item_size) + result->sr_item.i_node_ref_size; + + bitem = &branch->tb_data[result->sr_item.i_item_offset]; + value->sv_flags = XT_SEARCH_WHOLE_KEY; + value->sv_length = result->sr_item.i_item_size - XT_RECORD_REF_SIZE; + xt_get_record_ref(bitem + value->sv_length, &value->sv_rec_id, &value->sv_row_id); + memcpy(value->sv_key, bitem, value->sv_length); + } + else { + u_int node_ref_size; + u_int ilen; + xtWord1 *bend; + + node_ref_size = result->sr_item.i_node_ref_size; + bitem = branch->tb_data + node_ref_size;; + bend = &branch->tb_data[(result->sr_item.i_total_size - node_ref_size) / 2 + node_ref_size]; + ilen = 0; + if (bitem < bend) { + for (;;) { + ilen = myxt_get_key_length(ind, bitem); + if (bitem + ilen + XT_RECORD_REF_SIZE + node_ref_size >= bend) + break; + bitem += ilen + XT_RECORD_REF_SIZE + node_ref_size; + } + } + + result->sr_item.i_item_offset = bitem - branch->tb_data; + result->sr_item.i_item_size = ilen + XT_RECORD_REF_SIZE; + + value->sv_flags = XT_SEARCH_WHOLE_KEY; + value->sv_length = ilen; + xt_get_record_ref(bitem + ilen, &value->sv_rec_id, &value->sv_row_id); + memcpy(value->sv_key, bitem, value->sv_length); + } +} + +static size_t idx_write_branch_item(XTIndexPtr ind __attribute__((unused)), xtWord1 *item, XTIdxKeyValuePtr value) +{ + memcpy(item, value->sv_key, value->sv_length); + xt_set_val_record_ref(item + value->sv_length, value); + return value->sv_length + XT_RECORD_REF_SIZE; +} + +static xtBool idx_replace_node_key(XTOpenTablePtr ot, XTIndexPtr ind, IdxStackItemPtr item, IdxBranchStackPtr stack, u_int item_size, xtWord1 *item_buf) +{ + XTIndReferenceRec iref; + xtIndexNodeID new_branch; + XTIdxResultRec result; + xtIndexNodeID current = item->i_branch; + u_int new_size; + XTIdxBranchDPtr new_branch_ptr; + XTIdxKeyValueRec key_value; + xtWord1 key_buf[XT_INDEX_MAX_KEY_SIZE]; + +#ifdef DEBUG + iref.ir_ulock = XT_UNLOCK_NONE; +#endif + if (!xt_ind_fetch(ot, current, XT_LOCK_WRITE, &iref)) + return FAILED; + if (iref.ir_block->cb_handle_count) { + if (!xt_ind_copy_on_write(&iref)) + goto failed_1; + } + memmove(&iref.ir_branch->tb_data[item->i_pos.i_item_offset + item_size], + &iref.ir_branch->tb_data[item->i_pos.i_item_offset + item->i_pos.i_item_size], + item->i_pos.i_total_size - item->i_pos.i_item_offset - item->i_pos.i_item_size); + memcpy(&iref.ir_branch->tb_data[item->i_pos.i_item_offset], + item_buf, item_size); + item->i_pos.i_total_size = item->i_pos.i_total_size + item_size - item->i_pos.i_item_size; + XT_SET_DISK_2(iref.ir_branch->tb_size_2, XT_MAKE_NODE_SIZE(item->i_pos.i_total_size)); + IDX_TRACE("%d-> %x\n", (int) XT_NODE_ID(current), (int) XT_GET_DISK_2(iref.ir_branch->tb_size_2)); + + if (item->i_pos.i_total_size <= XT_INDEX_PAGE_DATA_SIZE) + return xt_ind_release(ot, ind, XT_UNLOCK_W_UPDATE, &iref); + + /* The node has overflowed!! */ + result.sr_item = item->i_pos; + + /* Adjust the stack (we want the parents of the delete node): */ + for (;;) { + if (idx_pop(stack) == item) + break; + } + + /* We assume that value can be overwritten (which is the case) */ + key_value.sv_flags = XT_SEARCH_WHOLE_KEY; + key_value.sv_key = key_buf; + idx_get_middle_branch_item(ind, iref.ir_branch, &key_value, &result); + + if (!idx_new_branch(ot, ind, &new_branch)) + goto failed_1; + + /* Split the node: */ + new_size = result.sr_item.i_total_size - result.sr_item.i_item_offset - result.sr_item.i_item_size; + // TODO: Are 2 buffers now required? + new_branch_ptr = (XTIdxBranchDPtr) &ot->ot_ind_wbuf.tb_data[XT_INDEX_PAGE_DATA_SIZE]; + memmove(new_branch_ptr->tb_data, &iref.ir_branch->tb_data[result.sr_item.i_item_offset + result.sr_item.i_item_size], new_size); + + XT_SET_DISK_2(new_branch_ptr->tb_size_2, XT_MAKE_NODE_SIZE(new_size)); + IDX_TRACE("%d-> %x\n", (int) XT_NODE_ID(new_branch), (int) XT_GET_DISK_2(new_branch_ptr->tb_size_2)); + if (!xt_ind_write(ot, ind, new_branch, offsetof(XTIdxBranchDRec, tb_data) + new_size, (xtWord1 *) new_branch_ptr)) + goto failed_2; + + /* Change the size of the old branch: */ + XT_SET_DISK_2(iref.ir_branch->tb_size_2, XT_MAKE_NODE_SIZE(result.sr_item.i_item_offset)); + IDX_TRACE("%d-> %x\n", (int) XT_NODE_ID(current), (int) XT_GET_DISK_2(iref.ir_branch->tb_size_2)); + + xt_ind_release(ot, ind, XT_UNLOCK_W_UPDATE, &iref); + + /* Insert the new branch into the parent node, using the new middle key value: */ + if (!idx_insert_node(ot, ind, stack, &key_value, new_branch)) { + /* + * TODO: Mark the index as corrupt. + * This should not fail because everything has been + * preallocated. + * However, if it does fail the index + * will be corrupt. + * I could modify and release the branch above, + * after this point. + * But that would mean holding the lock longer, + * and also may not help because idx_insert_node() + * is recursive. + */ + idx_free_branch(ot, ind, new_branch); + return FAILED; + } + + return OK; + + failed_2: + idx_free_branch(ot, ind, new_branch); + + failed_1: + xt_ind_release(ot, ind, XT_UNLOCK_WRITE, &iref); + + return FAILED; +} + +/*ot_ind_wbuf + * ----------------------------------------------------------------------- + * Standard b-tree insert + */ + +/* + * Insert the given branch into the node on the top of the stack. If the stack + * is empty we need to add a new root. + */ +static xtBool idx_insert_node(XTOpenTablePtr ot, XTIndexPtr ind, IdxBranchStackPtr stack, XTIdxKeyValuePtr key_value, xtIndexNodeID branch) +{ + IdxStackItemPtr stack_item; + xtIndexNodeID new_branch; + size_t size; + xtIndexNodeID current; + XTIndReferenceRec iref; + XTIdxResultRec result; + u_int new_size; + XTIdxBranchDPtr new_branch_ptr; + +#ifdef DEBUG + iref.ir_ulock = XT_UNLOCK_NONE; +#endif + /* Insert a new branch (key, data)... */ + if (!(stack_item = idx_pop(stack))) { + xtWord1 *ditem; + + /* New root */ + if (!idx_new_branch(ot, ind, &new_branch)) + goto failed; + + ditem = ot->ot_ind_wbuf.tb_data; + XT_SET_NODE_REF(ot->ot_table, ditem, ind->mi_root); + ditem += XT_NODE_REF_SIZE; + ditem += idx_write_branch_item(ind, ditem, key_value); + XT_SET_NODE_REF(ot->ot_table, ditem, branch); + ditem += XT_NODE_REF_SIZE; + size = ditem - ot->ot_ind_wbuf.tb_data; + XT_SET_DISK_2(ot->ot_ind_wbuf.tb_size_2, XT_MAKE_NODE_SIZE(size)); + IDX_TRACE("%d-> %x\n", (int) XT_NODE_ID(new_branch), (int) XT_GET_DISK_2(ot->ot_ind_wbuf.tb_size_2)); + if (!xt_ind_write(ot, ind, new_branch, offsetof(XTIdxBranchDRec, tb_data) + size, (xtWord1 *) &ot->ot_ind_wbuf)) + goto failed_2; + ind->mi_root = new_branch; + goto done_ok; + } + + current = stack_item->i_branch; + /* This read does not count (towards ot_ind_reads), because we are only + * counting each loaded page once. We assume that the page is in + * cache, and will remain in cache when we read again below for the + * purpose of update. + */ + if (!xt_ind_fetch(ot, current, XT_LOCK_READ, &iref)) + goto failed; + ASSERT_NS(XT_IS_NODE(XT_GET_DISK_2(iref.ir_branch->tb_size_2))); + ind->mi_scan_branch(ot->ot_table, ind, iref.ir_branch, key_value, &result); + + if (result.sr_item.i_total_size + key_value->sv_length + XT_RECORD_REF_SIZE + result.sr_item.i_node_ref_size <= XT_INDEX_PAGE_DATA_SIZE) { + if (iref.ir_block->cb_handle_count) { + if (!xt_ind_copy_on_write(&iref)) + goto failed_1; + } + idx_insert_node_item(ot->ot_table, ind, iref.ir_branch, key_value, &result, branch); + IDX_TRACE("%d-> %x\n", (int) XT_NODE_ID(current), (int) XT_GET_DISK_2(ot->ot_ind_wbuf.tb_size_2)); + ASSERT_NS(result.sr_item.i_total_size <= XT_INDEX_PAGE_DATA_SIZE); + xt_ind_release(ot, ind, XT_UNLOCK_R_UPDATE, &iref); + goto done_ok; + } + + memcpy(&ot->ot_ind_wbuf, iref.ir_branch, offsetof(XTIdxBranchDRec, tb_data) + result.sr_item.i_total_size); + idx_insert_node_item(ot->ot_table, ind, &ot->ot_ind_wbuf, key_value, &result, branch); + IDX_TRACE("%d-> %x\n", (int) XT_NODE_ID(current), (int) XT_GET_DISK_2(ot->ot_ind_wbuf.tb_size_2)); + ASSERT_NS(result.sr_item.i_total_size > XT_INDEX_PAGE_DATA_SIZE); + + /* We assume that value can be overwritten (which is the case) */ + idx_get_middle_branch_item(ind, &ot->ot_ind_wbuf, key_value, &result); + + if (!idx_new_branch(ot, ind, &new_branch)) + goto failed_1; + + /* Split the node: */ + new_size = result.sr_item.i_total_size - result.sr_item.i_item_offset - result.sr_item.i_item_size; + new_branch_ptr = (XTIdxBranchDPtr) &ot->ot_ind_wbuf.tb_data[XT_INDEX_PAGE_DATA_SIZE]; + memmove(new_branch_ptr->tb_data, &ot->ot_ind_wbuf.tb_data[result.sr_item.i_item_offset + result.sr_item.i_item_size], new_size); + + XT_SET_DISK_2(new_branch_ptr->tb_size_2, XT_MAKE_NODE_SIZE(new_size)); + IDX_TRACE("%d-> %x\n", (int) XT_NODE_ID(new_branch), (int) XT_GET_DISK_2(new_branch_ptr->tb_size_2)); + if (!xt_ind_write(ot, ind, new_branch, offsetof(XTIdxBranchDRec, tb_data) + new_size, (xtWord1 *) new_branch_ptr)) + goto failed_2; + + /* Change the size of the old branch: */ + XT_SET_DISK_2(ot->ot_ind_wbuf.tb_size_2, XT_MAKE_NODE_SIZE(result.sr_item.i_item_offset)); + IDX_TRACE("%d-> %x\n", (int) XT_NODE_ID(current), (int) XT_GET_DISK_2(ot->ot_ind_wbuf.tb_size_2)); + if (iref.ir_block->cb_handle_count) { + if (!xt_ind_copy_on_write(&iref)) + goto failed_2; + } + memcpy(iref.ir_branch, &ot->ot_ind_wbuf, offsetof(XTIdxBranchDRec, tb_data) + result.sr_item.i_item_offset); + xt_ind_release(ot, ind, XT_UNLOCK_R_UPDATE, &iref); + + /* Insert the new branch into the parent node, using the new middle key value: */ + if (!idx_insert_node(ot, ind, stack, key_value, new_branch)) { + // Index may be inconsistant now... + idx_free_branch(ot, ind, new_branch); + goto failed; + } + + done_ok: + return OK; + + failed_2: + idx_free_branch(ot, ind, new_branch); + + failed_1: + xt_ind_release(ot, ind, XT_UNLOCK_READ, &iref); + + failed: + return FAILED; +} + +static xtBool idx_out_of_memory_failure(XTOpenTablePtr ot) +{ +#ifdef XT_TRACK_INDEX_UPDATES + /* If the index has been changed when we run out of memory, we + * will corrupt the index! + */ + ASSERT_NS(ot->ot_ind_changed == 0); +#endif + if (ot->ot_thread->t_exception.e_xt_err == XT_ERR_NO_INDEX_CACHE) { + /* Flush index and retry! */ + xt_clear_exception(ot->ot_thread); + if (!xt_flush_indices(ot, NULL, FALSE)) + return FAILED; + return TRUE; + } + return FALSE; +} + +/* + * Check all the duplicate variation in an index. + * If one of them is visible, then we have a duplicate key + * error. + * + * GOTCHA: This routine must use the write index buffer! + */ +static xtBool idx_check_duplicates(XTOpenTablePtr ot, XTIndexPtr ind, XTIdxKeyValuePtr key_value) +{ + IdxBranchStackRec stack; + xtIndexNodeID current; + XTIndReferenceRec iref; + XTIdxResultRec result; + xtBool on_key = FALSE; + xtXactID xn_id; + int save_flags; + XTXactWaitRec xw; + +#ifdef DEBUG + iref.ir_ulock = XT_UNLOCK_NONE; +#endif + retry: + idx_newstack(&stack); + + if (!(XT_NODE_ID(current) = XT_NODE_ID(ind->mi_root))) + return OK; + + save_flags = key_value->sv_flags; + key_value->sv_flags = 0; + + while (XT_NODE_ID(current)) { + if (!xt_ind_fetch(ot, current, XT_LOCK_READ, &iref)) { + key_value->sv_flags = save_flags; + return FAILED; + } + ind->mi_scan_branch(ot->ot_table, ind, iref.ir_branch, key_value, &result); + if (result.sr_found) + /* If we have found the key in a node: */ + on_key = TRUE; + if (!result.sr_item.i_node_ref_size) + break; + xt_ind_release(ot, ind, XT_UNLOCK_READ, &iref); + if (!idx_push(&stack, current, &result.sr_item)) { + key_value->sv_flags = save_flags; + return FAILED; + } + current = result.sr_branch; + } + + key_value->sv_flags = save_flags; + + if (!on_key) { + xt_ind_release(ot, ind, XT_UNLOCK_READ, &iref); + return OK; + } + + for (;;) { + if (result.sr_item.i_item_offset == result.sr_item.i_total_size) { + IdxStackItemPtr node; + + /* We are at the end of a leaf node. + * Go up the stack to find the start position of the next key. + * If we find none, then we are the end of the index. + */ + xt_ind_release(ot, ind, XT_UNLOCK_READ, &iref); + while ((node = idx_pop(&stack))) { + if (node->i_pos.i_item_offset < node->i_pos.i_total_size) { + current = node->i_branch; + if (!xt_ind_fetch(ot, current, XT_LOCK_READ, &iref)) + return FAILED; + xt_get_res_record_ref(&iref.ir_branch->tb_data[node->i_pos.i_item_offset + node->i_pos.i_item_size - XT_RECORD_REF_SIZE], &result); + result.sr_item = node->i_pos; + goto check_value; + } + } + break; + } + + check_value: + /* Quit the loop if the key is no longer matched! */ + if (myxt_compare_key(ind, 0, key_value->sv_length, key_value->sv_key, &iref.ir_branch->tb_data[result.sr_item.i_item_offset]) != 0) { + xt_ind_release(ot, ind, XT_UNLOCK_READ, &iref); + break; + } + + switch (xt_tab_maybe_committed(ot, result.sr_rec_id, &xn_id, NULL, NULL)) { + case XT_MAYBE: + /* Record is not committed, wait for the transaction. */ + xt_ind_release(ot, ind, XT_UNLOCK_READ, &iref); + XT_INDEX_UNLOCK(ind, ot); + xw.xw_xn_id = xn_id; + if (!xt_xn_wait_for_xact(ot->ot_thread, &xw, NULL)) { + XT_INDEX_WRITE_LOCK(ind, ot); + return FAILED; + } + XT_INDEX_WRITE_LOCK(ind, ot); + goto retry; + case XT_ERR: + /* Error while reading... */ + goto failed; + case TRUE: + /* Record is committed or belongs to me, duplicate key: */ + XT_DEBUG_TRACE(("DUPLICATE KEY tx=%d rec=%d\n", (int) ot->ot_thread->st_xact_data->xd_start_xn_id, (int) result.sr_rec_id)); + xt_register_xterr(XT_REG_CONTEXT, XT_ERR_DUPLICATE_KEY); + goto failed; + case FALSE: + /* Record is deleted or rolled-back: */ + break; + } + + idx_next_branch_item(ot->ot_table, ind, iref.ir_branch, &result); + + if (result.sr_item.i_node_ref_size) { + /* Go down to the bottom: */ + while (XT_NODE_ID(current)) { + xt_ind_release(ot, ind, XT_UNLOCK_READ, &iref); + if (!idx_push(&stack, current, &result.sr_item)) + return FAILED; + current = result.sr_branch; + if (!xt_ind_fetch(ot, current, XT_LOCK_READ, &iref)) + return FAILED; + idx_first_branch_item(ot->ot_table, ind, iref.ir_branch, &result); + if (!result.sr_item.i_node_ref_size) + break; + } + } + } + + return OK; + + failed: + xt_ind_release(ot, ind, XT_UNLOCK_READ, &iref); + return FAILED; +} + +/* + * Insert a value into the given index. Return FALSE if an error occurs. + */ +xtPublic xtBool xt_idx_insert(XTOpenTablePtr ot, XTIndexPtr ind, xtRowID row_id, xtRecordID rec_id, xtWord1 *rec_buf, xtWord1 *bef_buf, xtBool allow_dups) +{ + XTIdxKeyValueRec key_value; + xtWord1 key_buf[XT_INDEX_MAX_KEY_SIZE]; + IdxBranchStackRec stack; + xtIndexNodeID current; + XTIndReferenceRec iref; + xtIndexNodeID new_branch; + XTIdxBranchDPtr new_branch_ptr; + size_t size; + XTIdxResultRec result; + size_t new_size; + xtBool check_for_dups = ind->mi_flags & (HA_UNIQUE_CHECK | HA_NOSAME) && !allow_dups; + xtBool lock_structure = FALSE; + +#ifdef DEBUG + iref.ir_ulock = XT_UNLOCK_NONE; +#endif +#ifdef CHECK_AND_PRINT + //idx_check_index(ot, ind, TRUE); +#endif + + retry_after_oom: +#ifdef XT_TRACK_INDEX_UPDATES + ot->ot_ind_changed = 0; +#endif + key_value.sv_flags = XT_SEARCH_WHOLE_KEY; + key_value.sv_rec_id = rec_id; + key_value.sv_row_id = row_id; /* Should always be zero on insert (will be update by sweeper later). + * Non-zero only during recovery, assuming that sweeper will process such records right after recovery. + */ + key_value.sv_key = key_buf; + key_value.sv_length = myxt_create_key_from_row(ind, key_buf, rec_buf, &check_for_dups); + + if (bef_buf && check_for_dups) { + /* If we have a before image, and we are required to check for duplicates. + * then compare the before image key with the after image key. + */ + xtWord1 bef_key_buf[XT_INDEX_MAX_KEY_SIZE]; + u_int len; + xtBool has_no_null = TRUE; + + len = myxt_create_key_from_row(ind, bef_key_buf, bef_buf, &has_no_null); + if (has_no_null) { + /* If the before key has no null values, then compare with the after key value. + * We only have to check for duplicates if the key has changed! + */ + check_for_dups = myxt_compare_key(ind, 0, len, bef_key_buf, key_buf) != 0; + } + } + + /* The index appears to have no root: */ + if (!XT_NODE_ID(ind->mi_root)) + lock_structure = TRUE; + + lock_and_retry: + idx_newstack(&stack); + + /* A write lock is only required if we are going to change the + * strcuture of the index! + */ + if (lock_structure) + XT_INDEX_WRITE_LOCK(ind, ot); + else + XT_INDEX_READ_LOCK(ind, ot); + + retry: + if (!(XT_NODE_ID(current) = XT_NODE_ID(ind->mi_root))) { + /* Index is empty, create a new one: */ + ASSERT_NS(lock_structure); + if (!xt_ind_reserve(ot, 1, NULL)) + goto failed; + if (!idx_new_branch(ot, ind, &new_branch)) + goto failed; + size = idx_write_branch_item(ind, ot->ot_ind_wbuf.tb_data, &key_value); + XT_SET_DISK_2(ot->ot_ind_wbuf.tb_size_2, XT_MAKE_LEAF_SIZE(size)); + IDX_TRACE("%d-> %x\n", (int) new_branch, (int) XT_GET_DISK_2(ot->ot_ind_wbuf.tb_size_2)); + if (!xt_ind_write(ot, ind, new_branch, offsetof(XTIdxBranchDRec, tb_data) + size, (xtWord1 *) &ot->ot_ind_wbuf)) + goto failed_2; + ind->mi_root = new_branch; + goto done_ok; + } + + while (XT_NODE_ID(current)) { + if (!xt_ind_fetch(ot, current, XT_XLOCK_LEAF, &iref)) + goto failed; + ind->mi_scan_branch(ot->ot_table, ind, iref.ir_branch, &key_value, &result); + if (result.sr_duplicate) { + if (check_for_dups) { + /* Duplicates are not allowed, at least one has been + * found... + */ + + /* Leaf nodes (i_node_ref_size == 0) are write locked, + * non-leaf nodes are read locked. + */ + xt_ind_release(ot, ind, result.sr_item.i_node_ref_size ? XT_UNLOCK_READ : XT_UNLOCK_WRITE, &iref); + + if (!idx_check_duplicates(ot, ind, &key_value)) + goto failed; + /* We have checked all the "duplicate" variations. None of them are + * relevant. So this will cause a correct insert. + */ + check_for_dups = FALSE; + idx_newstack(&stack); + goto retry; + } + } + if (result.sr_found) { + /* Node found, can happen during recovery of indexes! */ + XTPageUnlockType utype; + + if (!result.sr_row_id && row_id) { + /* {INDEX-RECOV_ROWID} Set the row-id + * during recovery, even if the index entry + * is not committed. + * It will be removed later by the sweeper. + */ + size_t offset; + xtWord1 *data; + + offset = + /* This is the offset of the reference in the item we found: */ + result.sr_item.i_item_offset + result.sr_item.i_item_size - XT_RECORD_REF_SIZE + + /* This is the offset of the row id in the reference: */ + 4; + data = &iref.ir_branch->tb_data[offset]; + + /* This update does not change the structure of page, so we do it without + * copying the page before we write. + */ + XT_SET_DISK_4(data, row_id); + utype = result.sr_item.i_node_ref_size ? XT_UNLOCK_R_UPDATE : XT_UNLOCK_W_UPDATE; + } + else + utype = result.sr_item.i_node_ref_size ? XT_UNLOCK_READ : XT_UNLOCK_WRITE; + xt_ind_release(ot, ind, utype, &iref); + goto done_ok; + } + /* Stop when we get to a leaf: */ + if (!result.sr_item.i_node_ref_size) + break; + xt_ind_release(ot, ind, result.sr_item.i_node_ref_size ? XT_UNLOCK_READ : XT_UNLOCK_WRITE, &iref); + if (!idx_push(&stack, current, NULL)) + goto failed; + current = result.sr_branch; + } + ASSERT_NS(XT_NODE_ID(current)); + + /* Must be a leaf!: */ + ASSERT_NS(!result.sr_item.i_node_ref_size); + + if (result.sr_item.i_total_size + key_value.sv_length + XT_RECORD_REF_SIZE <= XT_INDEX_PAGE_DATA_SIZE) { + if (iref.ir_block->cb_handle_count) { + if (!xt_ind_copy_on_write(&iref)) + goto failed_1; + } + idx_insert_leaf_item(ind, iref.ir_branch, &key_value, &result); + IDX_TRACE("%d-> %x\n", (int) XT_NODE_ID(current), (int) XT_GET_DISK_2(ot->ot_ind_wbuf.tb_size_2)); + ASSERT_NS(result.sr_item.i_total_size <= XT_INDEX_PAGE_DATA_SIZE); + xt_ind_release(ot, ind, XT_UNLOCK_W_UPDATE, &iref); + goto done_ok; + } + + /* Key does not fit. Must split the node. + * Make sure we have a structural lock: + */ + if (!lock_structure) { + xt_ind_release(ot, ind, XT_UNLOCK_WRITE, &iref); + XT_INDEX_UNLOCK(ind, ot); + lock_structure = TRUE; + goto lock_and_retry; + } + + memcpy(&ot->ot_ind_wbuf, iref.ir_branch, offsetof(XTIdxBranchDRec, tb_data) + result.sr_item.i_total_size); + idx_insert_leaf_item(ind, &ot->ot_ind_wbuf, &key_value, &result); + IDX_TRACE("%d-> %x\n", (int) XT_NODE_ID(current), (int) XT_GET_DISK_2(ot->ot_ind_wbuf.tb_size_2)); + ASSERT_NS(result.sr_item.i_total_size > XT_INDEX_PAGE_DATA_SIZE); + + /* This is the number of potential writes. In other words, the total number + * of blocks that may be accessed. + * + * Note that this assume if a block is read and written soon after that the block + * will not be freed in-between (a safe assumption?) + */ + if (!xt_ind_reserve(ot, stack.s_top * 2 + 3, iref.ir_branch)) + goto failed_1; + + /* Key does not fit, must split... */ + idx_get_middle_branch_item(ind, &ot->ot_ind_wbuf, &key_value, &result); + + if (!idx_new_branch(ot, ind, &new_branch)) + goto failed_1; + + /* Copy and write the rest of the data to the new node: */ + new_size = result.sr_item.i_total_size - result.sr_item.i_item_offset - result.sr_item.i_item_size; + new_branch_ptr = (XTIdxBranchDPtr) &ot->ot_ind_wbuf.tb_data[XT_INDEX_PAGE_DATA_SIZE]; + memmove(new_branch_ptr->tb_data, &ot->ot_ind_wbuf.tb_data[result.sr_item.i_item_offset + result.sr_item.i_item_size], new_size); + + XT_SET_DISK_2(new_branch_ptr->tb_size_2, XT_MAKE_LEAF_SIZE(new_size)); + IDX_TRACE("%d-> %x\n", (int) XT_NODE_ID(new_branch), (int) XT_GET_DISK_2(new_branch_ptr->tb_size_2)); + if (!xt_ind_write(ot, ind, new_branch, offsetof(XTIdxBranchDRec, tb_data) + new_size, (xtWord1 *) new_branch_ptr)) + goto failed_2; + + /* Modify the first node: */ + XT_SET_DISK_2(ot->ot_ind_wbuf.tb_size_2, XT_MAKE_LEAF_SIZE(result.sr_item.i_item_offset)); + IDX_TRACE("%d-> %x\n", (int) XT_NODE_ID(current), (int) XT_GET_DISK_2(ot->ot_ind_wbuf.tb_size_2)); + + if (iref.ir_block->cb_handle_count) { + if (!xt_ind_copy_on_write(&iref)) + goto failed_2; + } + memcpy(iref.ir_branch, &ot->ot_ind_wbuf, offsetof(XTIdxBranchDRec, tb_data) + result.sr_item.i_item_offset); + xt_ind_release(ot, ind, XT_UNLOCK_W_UPDATE, &iref); + + /* Insert the new branch into the parent node, using the new middle key value: */ + if (!idx_insert_node(ot, ind, &stack, &key_value, new_branch)) { + // Index may be inconsistant now... + idx_free_branch(ot, ind, new_branch); + goto failed; + } + +#ifdef XT_TRACK_INDEX_UPDATES + ASSERT_NS(ot->ot_ind_reserved >= ot->ot_ind_reads); +#endif + + done_ok: + XT_INDEX_UNLOCK(ind, ot); + +#ifdef DEBUG + //printf("INSERT OK\n"); + //idx_check_index(ot, ind, TRUE); +#endif + xt_ind_unreserve(ot); + return OK; + + failed_2: + idx_free_branch(ot, ind, new_branch); + + failed_1: + xt_ind_release(ot, ind, XT_UNLOCK_WRITE, &iref); + + failed: + XT_INDEX_UNLOCK(ind, ot); + if (idx_out_of_memory_failure(ot)) + goto retry_after_oom; + +#ifdef DEBUG + //printf("INSERT FAILED\n"); + //idx_check_index(ot, ind, TRUE); +#endif + xt_ind_unreserve(ot); + return FAILED; +} + +static xtBool idx_delete(XTOpenTablePtr ot, XTIndexPtr ind, XTIdxKeyValuePtr key_value) +{ + IdxBranchStackRec stack; + xtIndexNodeID current; + XTIndReferenceRec iref; + XTIdxResultRec result; + IdxStackItemPtr delete_node = NULL; + IdxStackItemPtr current_top = NULL; + xtBool lock_structure = FALSE; + +#ifdef DEBUG + iref.ir_ulock = XT_UNLOCK_NONE; +#endif + /* The index appears to have no root: */ + if (!XT_NODE_ID(ind->mi_root)) + lock_structure = TRUE; + + lock_and_retry: + idx_newstack(&stack); + + if (lock_structure) + XT_INDEX_WRITE_LOCK(ind, ot); + else + XT_INDEX_READ_LOCK(ind, ot); + + if (!(XT_NODE_ID(current) = XT_NODE_ID(ind->mi_root))) + goto done_ok; + + while (XT_NODE_ID(current)) { + if (!xt_ind_fetch(ot, current, XT_XLOCK_LEAF, &iref)) + goto failed; + ind->mi_scan_branch(ot->ot_table, ind, iref.ir_branch, key_value, &result); + if (!result.sr_item.i_node_ref_size) { + /* A leaf... */ + if (result.sr_found) { + if (!idx_remove_branch_item_right(ot, ind, current, &iref, &result.sr_item)) + goto failed; + } + else + xt_ind_release(ot, ind, XT_UNLOCK_WRITE, &iref); + goto done_ok; + } + if (!idx_push(&stack, current, &result.sr_item)) { + xt_ind_release(ot, ind, XT_UNLOCK_READ, &iref); + goto failed; + } + if (result.sr_found) + /* If we have found the key in a node: */ + break; + xt_ind_release(ot, ind, XT_UNLOCK_READ, &iref); + current = result.sr_branch; + } + + /* Must be a non-leaf!: */ + ASSERT_NS(result.sr_item.i_node_ref_size); + + /* We will have to remove the key from a non-leaf node, + * which means we are changing the structure of the index. + * Make sure we have a structural lock: + */ + if (!lock_structure) { + xt_ind_release(ot, ind, XT_UNLOCK_READ, &iref); + XT_INDEX_UNLOCK(ind, ot); + lock_structure = TRUE; + goto lock_and_retry; + } + + /* This is the item we will have to replace: */ + delete_node = idx_top(&stack); + + /* Follow the branch after this item: */ + idx_next_branch_item(ot->ot_table, ind, iref.ir_branch, &result); + ASSERT_NS(XT_NODE_ID(current)); + xt_ind_release(ot, ind, XT_UNLOCK_READ, &iref); + + /* Go down the left-hand side until we reach a leaf: */ + while (XT_NODE_ID(current)) { + current = result.sr_branch; + if (!xt_ind_fetch(ot, current, XT_XLOCK_LEAF, &iref)) + goto failed; + idx_first_branch_item(ot->ot_table, ind, iref.ir_branch, &result); + if (!result.sr_item.i_node_ref_size) + break; + xt_ind_release(ot, ind, XT_UNLOCK_READ, &iref); + if (!idx_push(&stack, current, &result.sr_item)) + goto failed; + } + + ASSERT_NS(XT_NODE_ID(current)); + ASSERT_NS(!result.sr_item.i_node_ref_size); + + if (!xt_ind_reserve(ot, stack.s_top + 2, iref.ir_branch)) { + xt_ind_release(ot, ind, XT_UNLOCK_WRITE, &iref); + goto failed; + } + + /* Crawl back up the stack trace, looking for a key + * that can be used to replace the deleted key. + * + * Any empty nodes on the way up can be removed! + */ + if (result.sr_item.i_total_size > 0) { + /* There is a key in the leaf, extract it, and put it in the node: */ + memcpy(key_value->sv_key, &iref.ir_branch->tb_data[result.sr_item.i_item_offset], result.sr_item.i_item_size); + /* This call also frees the iref.ir_branch page! */ + if (!idx_remove_branch_item_right(ot, ind, current, &iref, &result.sr_item)) + goto failed; + if (!idx_replace_node_key(ot, ind, delete_node, &stack, result.sr_item.i_item_size, key_value->sv_key)) + goto failed; + goto done_ok_2; + } + + xt_ind_release(ot, ind, XT_UNLOCK_WRITE, &iref); + + for (;;) { + /* The current node/leaf is empty, remove it: */ + idx_free_branch(ot, ind, current); + + current_top = idx_pop(&stack); + current = current_top->i_branch; + if (!xt_ind_fetch(ot, current, XT_XLOCK_LEAF, &iref)) + goto failed; + + if (current_top == delete_node) { + /* All children have been removed. Delete the key and done: */ + if (!idx_remove_branch_item_right(ot, ind, current, &iref, ¤t_top->i_pos)) + goto failed; + goto done_ok_2; + } + + if (current_top->i_pos.i_total_size > current_top->i_pos.i_node_ref_size) { + /* Save the key: */ + memcpy(key_value->sv_key, &iref.ir_branch->tb_data[current_top->i_pos.i_item_offset], current_top->i_pos.i_item_size); + /* This function also frees the cache page: */ + if (!idx_remove_branch_item_left(ot, ind, current, &iref, ¤t_top->i_pos)) + goto failed; + if (!idx_replace_node_key(ot, ind, delete_node, &stack, current_top->i_pos.i_item_size, key_value->sv_key)) + goto failed; + goto done_ok_2; + } + xt_ind_release(ot, ind, current_top->i_pos.i_node_ref_size ? XT_UNLOCK_READ : XT_UNLOCK_WRITE, &iref); + } + + + done_ok_2: +#ifdef XT_TRACK_INDEX_UPDATES + ASSERT_NS(ot->ot_ind_reserved >= ot->ot_ind_reads); +#endif + + done_ok: + XT_INDEX_UNLOCK(ind, ot); + +#ifdef DEBUG + //printf("DELETE OK\n"); + //idx_check_index(ot, ind, TRUE); +#endif + xt_ind_unreserve(ot); + return OK; + + failed: + XT_INDEX_UNLOCK(ind, ot); + xt_ind_unreserve(ot); + return FAILED; +} + +xtPublic xtBool xt_idx_delete(XTOpenTablePtr ot, XTIndexPtr ind, xtRecordID rec_id, xtWord1 *rec_buf) +{ + XTIdxKeyValueRec key_value; + xtWord1 key_buf[XT_INDEX_MAX_KEY_SIZE + XT_MAX_RECORD_REF_SIZE]; + + retry_after_oom: +#ifdef XT_TRACK_INDEX_UPDATES + ot->ot_ind_changed = 0; +#endif + + key_value.sv_flags = XT_SEARCH_WHOLE_KEY; + key_value.sv_rec_id = rec_id; + key_value.sv_row_id = 0; + key_value.sv_key = key_buf; + key_value.sv_length = myxt_create_key_from_row(ind, key_buf, rec_buf, NULL); + + if (!idx_delete(ot, ind, &key_value)) { + if (idx_out_of_memory_failure(ot)) + goto retry_after_oom; + return FAILED; + } + return OK; +} + +xtPublic xtBool xt_idx_update_row_id(XTOpenTablePtr ot, XTIndexPtr ind, xtRecordID rec_id, xtRowID row_id, xtWord1 *rec_buf) +{ + xtIndexNodeID current; + XTIndReferenceRec iref; + XTIdxResultRec result; + XTIdxKeyValueRec key_value; + xtWord1 key_buf[XT_INDEX_MAX_KEY_SIZE + XT_MAX_RECORD_REF_SIZE]; + +#ifdef DEBUG + iref.ir_ulock = XT_UNLOCK_NONE; +#endif +#ifdef CHECK_AND_PRINT + idx_check_index(ot, ind, TRUE); +#endif + retry_after_oom: +#ifdef XT_TRACK_INDEX_UPDATES + ot->ot_ind_changed = 0; +#endif + key_value.sv_flags = XT_SEARCH_WHOLE_KEY; + key_value.sv_rec_id = rec_id; + key_value.sv_row_id = 0; + key_value.sv_key = key_buf; + key_value.sv_length = myxt_create_key_from_row(ind, key_buf, rec_buf, NULL); + + /* NOTE: Only a read lock is required for this!! + * + * 09.05.2008 - This has changed because the dirty list now + * hangs on the index. And the dirty list may be updated + * by any change of the index. + * However, the advantage is that I should be able to read + * lock in the first phase of the flush. + * + * 18.02.2009 - This has changed again. + * I am now using a read lock, because this update does not + * require a structural change. In fact, it does not even + * need a WRITE LOCK on the page affected, because there + * is only ONE thread that can do this (the sweeper). + * + * This has the advantage that the sweeper (which uses this + * function, causes less conflicts. + * + * However, it does mean that the dirty list must be otherwise + * protected (which it now is be a spin lock - mi_dirty_lock). + * + * It also has the dissadvantage that I am going to have to + * take an xlock in the first phase of the flush. + */ + XT_INDEX_READ_LOCK(ind, ot); + + if (!(XT_NODE_ID(current) = XT_NODE_ID(ind->mi_root))) + goto done_ok; + + while (XT_NODE_ID(current)) { + if (!xt_ind_fetch(ot, current, XT_LOCK_READ, &iref)) + goto failed; + ind->mi_scan_branch(ot->ot_table, ind, iref.ir_branch, &key_value, &result); + if (result.sr_found || !result.sr_item.i_node_ref_size) + break; + xt_ind_release(ot, ind, XT_UNLOCK_READ, &iref); + current = result.sr_branch; + } + + if (result.sr_found) { + size_t offset; + xtWord1 *data; + + offset = + /* This is the offset of the reference in the item we found: */ + result.sr_item.i_item_offset + result.sr_item.i_item_size - XT_RECORD_REF_SIZE + + /* This is the offset of the row id in the reference: */ + 4; + data = &iref.ir_branch->tb_data[offset]; + + /* This update does not change the structure of page, so we do it without + * copying the page before we write. + * + * TODO: Check that concurrent reads can handle this! + * assuming the write is not atomic. + */ + XT_SET_DISK_4(data, row_id); + xt_ind_release(ot, ind, XT_UNLOCK_R_UPDATE, &iref); + } + else + xt_ind_release(ot, ind, XT_UNLOCK_READ, &iref); + + done_ok: + XT_INDEX_UNLOCK(ind, ot); + +#ifdef DEBUG + //idx_check_index(ot, ind, TRUE); + //idx_check_on_key(ot); +#endif + return OK; + + failed: + XT_INDEX_UNLOCK(ind, ot); + if (idx_out_of_memory_failure(ot)) + goto retry_after_oom; + return FAILED; +} + +xtPublic void xt_idx_prep_key(XTIndexPtr ind, register XTIdxSearchKeyPtr search_key, int flags, xtWord1 *in_key_buf, size_t in_key_length) +{ + search_key->sk_key_value.sv_flags = flags; + search_key->sk_key_value.sv_rec_id = 0; + search_key->sk_key_value.sv_row_id = 0; + search_key->sk_key_value.sv_key = search_key->sk_key_buf; + search_key->sk_key_value.sv_length = myxt_create_key_from_key(ind, search_key->sk_key_buf, in_key_buf, in_key_length); + search_key->sk_on_key = FALSE; +} + +xtPublic xtBool xt_idx_research(XTOpenTablePtr ot, XTIndexPtr ind) +{ + XTIdxSearchKeyRec search_key; + + xt_ind_lock_handle(ot->ot_ind_rhandle); + search_key.sk_key_value.sv_flags = XT_SEARCH_WHOLE_KEY; + xt_get_record_ref(&ot->ot_ind_rhandle->ih_branch->tb_data[ot->ot_ind_state.i_item_offset + ot->ot_ind_state.i_item_size - XT_RECORD_REF_SIZE], + &search_key.sk_key_value.sv_rec_id, &search_key.sk_key_value.sv_row_id); + search_key.sk_key_value.sv_key = search_key.sk_key_buf; + search_key.sk_key_value.sv_length = ot->ot_ind_state.i_item_size - XT_RECORD_REF_SIZE; + search_key.sk_on_key = FALSE; + memcpy(search_key.sk_key_buf, &ot->ot_ind_rhandle->ih_branch->tb_data[ot->ot_ind_state.i_item_offset], search_key.sk_key_value.sv_length); + xt_ind_unlock_handle(ot->ot_ind_rhandle); + return xt_idx_search(ot, ind, &search_key); +} + +/* + * Search for a given key and position the current pointer on the first + * key in the list of duplicates. If the key is not found the current + * pointer is placed at the first position after the key. + */ +xtPublic xtBool xt_idx_search(XTOpenTablePtr ot, XTIndexPtr ind, register XTIdxSearchKeyPtr search_key) +{ + IdxBranchStackRec stack; + xtIndexNodeID current; + XTIndReferenceRec iref; + XTIdxResultRec result; + +#ifdef DEBUG + iref.ir_ulock = XT_UNLOCK_NONE; +#endif + if (ot->ot_ind_rhandle) { + xt_ind_release_handle(ot->ot_ind_rhandle, FALSE, ot->ot_thread); + ot->ot_ind_rhandle = NULL; + } +#ifdef DEBUG + //idx_check_index(ot, ind, TRUE); +#endif + + /* Calling from recovery, this is not the case. + * But the index read does not require a transaction! + * Only insert requires this to check for duplicates. + if (!ot->ot_thread->st_xact_data) { + xt_register_xterr(XT_REG_CONTEXT, XT_ERR_NO_TRANSACTION); + return FAILED; + } + */ + + retry_after_oom: +#ifdef XT_TRACK_INDEX_UPDATES + ot->ot_ind_changed = 0; +#endif + idx_newstack(&stack); + + ot->ot_curr_rec_id = 0; + ot->ot_curr_row_id = 0; + + XT_INDEX_READ_LOCK(ind, ot); + + if (!(XT_NODE_ID(current) = XT_NODE_ID(ind->mi_root))) + goto done_ok; + + while (XT_NODE_ID(current)) { + if (!xt_ind_fetch(ot, current, XT_LOCK_READ, &iref)) + goto failed; + ind->mi_scan_branch(ot->ot_table, ind, iref.ir_branch, &search_key->sk_key_value, &result); + if (result.sr_found) + /* If we have found the key in a node: */ + search_key->sk_on_key = TRUE; + if (!result.sr_item.i_node_ref_size) + break; + xt_ind_release(ot, ind, XT_UNLOCK_READ, &iref); + if (!idx_push(&stack, current, &result.sr_item)) + goto failed; + current = result.sr_branch; + } + + if (result.sr_item.i_item_offset == result.sr_item.i_total_size) { + IdxStackItemPtr node; + + /* We are at the end of a leaf node. + * Go up the stack to find the start position of the next key. + * If we find none, then we are the end of the index. + */ + xt_ind_release(ot, ind, XT_UNLOCK_READ, &iref); + while ((node = idx_pop(&stack))) { + if (node->i_pos.i_item_offset < node->i_pos.i_total_size) { + xtRecordID rec_id; + + if (!xt_ind_fetch(ot, node->i_branch, XT_LOCK_READ, &iref)) + goto failed; + xt_get_record_ref(&iref.ir_branch->tb_data[node->i_pos.i_item_offset + node->i_pos.i_item_size - XT_RECORD_REF_SIZE], &rec_id, &ot->ot_curr_row_id); + ot->ot_curr_rec_id = rec_id; + ot->ot_ind_state = node->i_pos; + + /* Convert the pointer to a handle which can be used in later operations: */ + ASSERT_NS(!ot->ot_ind_rhandle); + if (!(ot->ot_ind_rhandle = xt_ind_get_handle(ot, ind, &iref))) + goto failed; + /* Keep the node for next operations: */ + /* + branch_size = XT_GET_INDEX_BLOCK_LEN(XT_GET_DISK_2(iref.ir_branch->tb_size_2)); + memcpy(&ot->ot_ind_rbuf, iref.ir_branch, branch_size); + xt_ind_release(ot, ind, XT_UNLOCK_READ, &iref); + */ + break; + } + } + } + else { + ot->ot_curr_rec_id = result.sr_rec_id; + ot->ot_curr_row_id = result.sr_row_id; + ot->ot_ind_state = result.sr_item; + + /* Convert the pointer to a handle which can be used in later operations: */ + ASSERT_NS(!ot->ot_ind_rhandle); + if (!(ot->ot_ind_rhandle = xt_ind_get_handle(ot, ind, &iref))) + goto failed; + /* Keep the node for next operations: */ + /* + branch_size = XT_GET_INDEX_BLOCK_LEN(XT_GET_DISK_2(iref.ir_branch->tb_size_2)); + memcpy(&ot->ot_ind_rbuf, iref.ir_branch, branch_size); + xt_ind_release(ot, ind, XT_UNLOCK_READ, &iref); + */ + } + + done_ok: + XT_INDEX_UNLOCK(ind, ot); + +#ifdef DEBUG + //idx_check_index(ot, ind, TRUE); + //idx_check_on_key(ot); +#endif + ASSERT_NS(iref.ir_ulock == XT_UNLOCK_NONE); + return OK; + + failed: + XT_INDEX_UNLOCK(ind, ot); + if (idx_out_of_memory_failure(ot)) + goto retry_after_oom; + ASSERT_NS(iref.ir_ulock == XT_UNLOCK_NONE); + return FAILED; +} + +xtPublic xtBool xt_idx_search_prev(XTOpenTablePtr ot, XTIndexPtr ind, register XTIdxSearchKeyPtr search_key) +{ + IdxBranchStackRec stack; + xtIndexNodeID current; + XTIndReferenceRec iref; + XTIdxResultRec result; + +#ifdef DEBUG + iref.ir_ulock = XT_UNLOCK_NONE; +#endif + if (ot->ot_ind_rhandle) { + xt_ind_release_handle(ot->ot_ind_rhandle, FALSE, ot->ot_thread); + ot->ot_ind_rhandle = NULL; + } +#ifdef DEBUG + //idx_check_index(ot, ind, TRUE); +#endif + + /* see the comment above in xt_idx_search */ + /* + if (!ot->ot_thread->st_xact_data) { + xt_register_xterr(XT_REG_CONTEXT, XT_ERR_NO_TRANSACTION); + return FAILED; + } + */ + + retry_after_oom: +#ifdef XT_TRACK_INDEX_UPDATES + ot->ot_ind_changed = 0; +#endif + idx_newstack(&stack); + + ot->ot_curr_rec_id = 0; + ot->ot_curr_row_id = 0; + + XT_INDEX_READ_LOCK(ind, ot); + + if (!(XT_NODE_ID(current) = XT_NODE_ID(ind->mi_root))) + goto done_ok; + + while (XT_NODE_ID(current)) { + if (!xt_ind_fetch(ot, current, XT_LOCK_READ, &iref)) + goto failed; + ind->mi_scan_branch(ot->ot_table, ind, iref.ir_branch, &search_key->sk_key_value, &result); + if (result.sr_found) + /* If we have found the key in a node: */ + search_key->sk_on_key = TRUE; + if (!result.sr_item.i_node_ref_size) + break; + xt_ind_release(ot, ind, XT_UNLOCK_READ, &iref); + if (!idx_push(&stack, current, &result.sr_item)) + goto failed; + current = result.sr_branch; + } + + if (result.sr_item.i_item_offset == 0) { + IdxStackItemPtr node; + + /* We are at the end of a leaf node. + * Go up the stack to find the start poition of the next key. + * If we find none, then we are the end of the index. + */ + xt_ind_release(ot, ind, XT_UNLOCK_READ, &iref); + while ((node = idx_pop(&stack))) { + if (node->i_pos.i_item_offset > node->i_pos.i_node_ref_size) { + if (!xt_ind_fetch(ot, node->i_branch, XT_LOCK_READ, &iref)) + goto failed; + result.sr_item = node->i_pos; + ind->mi_prev_item(ot->ot_table, ind, iref.ir_branch, &result); + goto record_found; + } + } + goto done_ok; + } + + /* We must just step once to the left in this leaf node... */ + ind->mi_prev_item(ot->ot_table, ind, iref.ir_branch, &result); + + record_found: + ot->ot_curr_rec_id = result.sr_rec_id; + ot->ot_curr_row_id = result.sr_row_id; + ot->ot_ind_state = result.sr_item; + + /* Convert to handle for later operations: */ + ASSERT_NS(!ot->ot_ind_rhandle); + if (!(ot->ot_ind_rhandle = xt_ind_get_handle(ot, ind, &iref))) + goto failed; + /* Keep a copy of the node for previous operations... */ + /* + u_int branch_size; + + branch_size = XT_GET_INDEX_BLOCK_LEN(XT_GET_DISK_2(iref.ir_branch->tb_size_2)); + memcpy(&ot->ot_ind_rbuf, iref.ir_branch, branch_size); + xt_ind_release(ot, ind, XT_UNLOCK_READ, &iref); + */ + + done_ok: + XT_INDEX_UNLOCK(ind, ot); + +#ifdef DEBUG + //idx_check_index(ot, ind, TRUE); + //idx_check_on_key(ot); +#endif + return OK; + + failed: + XT_INDEX_UNLOCK(ind, ot); + if (idx_out_of_memory_failure(ot)) + goto retry_after_oom; + return FAILED; +} + +/* + * Copy the current index value to the record. + */ +xtPublic xtBool xt_idx_read(XTOpenTablePtr ot, XTIndexPtr ind, xtWord1 *rec_buf) +{ + xtWord1 *bitem; + +#ifdef DEBUG + //idx_check_on_key(ot); +#endif + xt_ind_lock_handle(ot->ot_ind_rhandle); + bitem = ot->ot_ind_rhandle->ih_branch->tb_data + ot->ot_ind_state.i_item_offset; + myxt_create_row_from_key(ot, ind, bitem, ot->ot_ind_state.i_item_size - XT_RECORD_REF_SIZE, rec_buf); + xt_ind_unlock_handle(ot->ot_ind_rhandle); + return OK; +} + +xtPublic xtBool xt_idx_next(register XTOpenTablePtr ot, register XTIndexPtr ind, register XTIdxSearchKeyPtr search_key) +{ + XTIdxKeyValueRec key_value; + xtWord1 key_buf[XT_INDEX_MAX_KEY_SIZE]; + XTIdxResultRec result; + IdxBranchStackRec stack; + xtIndexNodeID current; + XTIndReferenceRec iref; + +#ifdef DEBUG + iref.ir_ulock = XT_UNLOCK_NONE; +#endif + ASSERT_NS(ot->ot_ind_rhandle); + xt_ind_lock_handle(ot->ot_ind_rhandle); + if (!ot->ot_ind_state.i_node_ref_size && + ot->ot_ind_state.i_item_offset < ot->ot_ind_state.i_total_size && + ot->ot_ind_rhandle->ih_cache_reference) { + key_value.sv_key = &ot->ot_ind_rhandle->ih_branch->tb_data[ot->ot_ind_state.i_item_offset]; + key_value.sv_length = ot->ot_ind_state.i_item_size - XT_RECORD_REF_SIZE; + + result.sr_item = ot->ot_ind_state; + idx_next_branch_item(ot->ot_table, ind, ot->ot_ind_rhandle->ih_branch, &result); + if (result.sr_item.i_item_offset < result.sr_item.i_total_size) { + /* Still on key? */ + if (search_key && search_key->sk_on_key) { + search_key->sk_on_key = myxt_compare_key(ind, search_key->sk_key_value.sv_flags, search_key->sk_key_value.sv_length, + search_key->sk_key_value.sv_key, &ot->ot_ind_rhandle->ih_branch->tb_data[result.sr_item.i_item_offset]) == 0; + } + xt_ind_unlock_handle(ot->ot_ind_rhandle); + goto checked_on_key; + } + } + + key_value.sv_flags = XT_SEARCH_WHOLE_KEY; + xt_get_record_ref(&ot->ot_ind_rhandle->ih_branch->tb_data[ot->ot_ind_state.i_item_offset + ot->ot_ind_state.i_item_size - XT_RECORD_REF_SIZE], &key_value.sv_rec_id, &key_value.sv_row_id); + key_value.sv_key = key_buf; + key_value.sv_length = ot->ot_ind_state.i_item_size - XT_RECORD_REF_SIZE; + memcpy(key_buf, &ot->ot_ind_rhandle->ih_branch->tb_data[ot->ot_ind_state.i_item_offset], key_value.sv_length); + xt_ind_release_handle(ot->ot_ind_rhandle, TRUE, ot->ot_thread); + ot->ot_ind_rhandle = NULL; + + retry_after_oom: +#ifdef XT_TRACK_INDEX_UPDATES + ot->ot_ind_changed = 0; +#endif + idx_newstack(&stack); + + XT_INDEX_READ_LOCK(ind, ot); + + if (!(XT_NODE_ID(current) = XT_NODE_ID(ind->mi_root))) { + XT_INDEX_UNLOCK(ind, ot); + return OK; + } + + while (XT_NODE_ID(current)) { + if (!xt_ind_fetch(ot, current, XT_LOCK_READ, &iref)) + goto failed; + ind->mi_scan_branch(ot->ot_table, ind, iref.ir_branch, &key_value, &result); + if (result.sr_item.i_node_ref_size) { + if (result.sr_found) { + /* If we have found the key in a node: */ + idx_next_branch_item(ot->ot_table, ind, iref.ir_branch, &result); + + /* Go down to the bottom: */ + while (XT_NODE_ID(current)) { + xt_ind_release(ot, ind, XT_UNLOCK_READ, &iref); + if (!idx_push(&stack, current, &result.sr_item)) + goto failed; + current = result.sr_branch; + if (!xt_ind_fetch(ot, current, XT_LOCK_READ, &iref)) + goto failed; + idx_first_branch_item(ot->ot_table, ind, iref.ir_branch, &result); + if (!result.sr_item.i_node_ref_size) + break; + } + + /* Is the leaf not empty, then we are done... */ + break; + } + } + else { + /* We have reached the leaf. */ + if (result.sr_found) + /* If we have found the key in a leaf: */ + idx_next_branch_item(ot->ot_table, ind, iref.ir_branch, &result); + /* If we did not find the key (although we should have). Our + * position is automatically the next one. + */ + break; + } + xt_ind_release(ot, ind, XT_UNLOCK_READ, &iref); + if (!idx_push(&stack, current, &result.sr_item)) + goto failed; + current = result.sr_branch; + } + + /* Check the current position in a leaf: */ + if (result.sr_item.i_item_offset == result.sr_item.i_total_size) { + /* At the end: */ + IdxStackItemPtr node; + + /* We are at the end of a leaf node. + * Go up the stack to find the start poition of the next key. + * If we find none, then we are the end of the index. + */ + xt_ind_release(ot, ind, XT_UNLOCK_READ, &iref); + while ((node = idx_pop(&stack))) { + if (node->i_pos.i_item_offset < node->i_pos.i_total_size) { + if (!xt_ind_fetch(ot, node->i_branch, XT_LOCK_READ, &iref)) + goto failed; + result.sr_item = node->i_pos; + xt_get_res_record_ref(&iref.ir_branch->tb_data[result.sr_item.i_item_offset + result.sr_item.i_item_size - XT_RECORD_REF_SIZE], &result); + goto unlock_check_on_key; + } + } + + /* No more keys: */ + if (search_key) + search_key->sk_on_key = FALSE; + ot->ot_curr_rec_id = 0; + ot->ot_curr_row_id = 0; + XT_INDEX_UNLOCK(ind, ot); + return OK; + } + + unlock_check_on_key: + + ASSERT_NS(!ot->ot_ind_rhandle); + if (!(ot->ot_ind_rhandle = xt_ind_get_handle(ot, ind, &iref))) + goto failed; + /* + u_int branch_size; + + branch_size = XT_GET_INDEX_BLOCK_LEN(XT_GET_DISK_2(iref.ir_branch->tb_size_2)); + memcpy(&ot->ot_ind_rbuf, iref.ir_branch, branch_size); + xt_ind_release(ot, ind, XT_UNLOCK_READ, &iref); + */ + + XT_INDEX_UNLOCK(ind, ot); + + /* Still on key? */ + if (search_key && search_key->sk_on_key) { + /* GOTCHA: As a short-cut I was using a length compare + * and a memcmp() here to check whether we as still on + * the original search key. + * This does not work because it does not take into account + * trialing spaces (which are ignored in comparison). + * So lengths can be different, but values still equal. + * + * NOTE: We have to use the original search flags for + * this compare. + */ + xt_ind_lock_handle(ot->ot_ind_rhandle); + search_key->sk_on_key = myxt_compare_key(ind, search_key->sk_key_value.sv_flags, search_key->sk_key_value.sv_length, + search_key->sk_key_value.sv_key, &ot->ot_ind_rhandle->ih_branch->tb_data[result.sr_item.i_item_offset]) == 0; + xt_ind_unlock_handle(ot->ot_ind_rhandle); + } + + checked_on_key: + ot->ot_curr_rec_id = result.sr_rec_id; + ot->ot_curr_row_id = result.sr_row_id; + ot->ot_ind_state = result.sr_item; + + return OK; + + failed: + XT_INDEX_UNLOCK(ind, ot); + if (idx_out_of_memory_failure(ot)) + goto retry_after_oom; + return FAILED; +} + +xtPublic xtBool xt_idx_prev(register XTOpenTablePtr ot, register XTIndexPtr ind, register XTIdxSearchKeyPtr search_key) +{ + XTIdxKeyValueRec key_value; + xtWord1 key_buf[XT_INDEX_MAX_KEY_SIZE]; + XTIdxResultRec result; + IdxBranchStackRec stack; + xtIndexNodeID current; + XTIndReferenceRec iref; + IdxStackItemPtr node; + +#ifdef DEBUG + iref.ir_ulock = XT_UNLOCK_NONE; +#endif + ASSERT_NS(ot->ot_ind_rhandle); + xt_ind_lock_handle(ot->ot_ind_rhandle); + if (!ot->ot_ind_state.i_node_ref_size && ot->ot_ind_state.i_item_offset > 0) { + key_value.sv_key = &ot->ot_ind_rhandle->ih_branch->tb_data[ot->ot_ind_state.i_item_offset]; + key_value.sv_length = ot->ot_ind_state.i_item_size - XT_RECORD_REF_SIZE; + + result.sr_item = ot->ot_ind_state; + ind->mi_prev_item(ot->ot_table, ind, ot->ot_ind_rhandle->ih_branch, &result); + + if (search_key && search_key->sk_on_key) { + search_key->sk_on_key = myxt_compare_key(ind, search_key->sk_key_value.sv_flags, search_key->sk_key_value.sv_length, + search_key->sk_key_value.sv_key, &ot->ot_ind_rhandle->ih_branch->tb_data[result.sr_item.i_item_offset]) == 0; + } + + xt_ind_unlock_handle(ot->ot_ind_rhandle); + goto checked_on_key; + } + + key_value.sv_flags = XT_SEARCH_WHOLE_KEY; + key_value.sv_rec_id = ot->ot_curr_rec_id; + key_value.sv_row_id = 0; + key_value.sv_key = key_buf; + key_value.sv_length = ot->ot_ind_state.i_item_size - XT_RECORD_REF_SIZE; + memcpy(key_buf, &ot->ot_ind_rhandle->ih_branch->tb_data[ot->ot_ind_state.i_item_offset], key_value.sv_length); + xt_ind_release_handle(ot->ot_ind_rhandle, TRUE, ot->ot_thread); + ot->ot_ind_rhandle = NULL; + + retry_after_oom: +#ifdef XT_TRACK_INDEX_UPDATES + ot->ot_ind_changed = 0; +#endif + idx_newstack(&stack); + + XT_INDEX_READ_LOCK(ind, ot); + + if (!(XT_NODE_ID(current) = XT_NODE_ID(ind->mi_root))) { + XT_INDEX_UNLOCK(ind, ot); + return OK; + } + + while (XT_NODE_ID(current)) { + if (!xt_ind_fetch(ot, current, XT_LOCK_READ, &iref)) + goto failed; + ind->mi_scan_branch(ot->ot_table, ind, iref.ir_branch, &key_value, &result); + if (result.sr_item.i_node_ref_size) { + if (result.sr_found) { + /* If we have found the key in a node: */ + + /* Go down to the bottom: */ + while (XT_NODE_ID(current)) { + xt_ind_release(ot, ind, XT_UNLOCK_READ, &iref); + if (!idx_push(&stack, current, &result.sr_item)) + goto failed; + current = result.sr_branch; + if (!xt_ind_fetch(ot, current, XT_LOCK_READ, &iref)) + goto failed; + ind->mi_last_item(ot->ot_table, ind, iref.ir_branch, &result); + if (!result.sr_item.i_node_ref_size) + break; + } + + /* Is the leaf not empty, then we are done... */ + if (result.sr_item.i_total_size == 0) + break; + goto unlock_check_on_key; + } + } + else { + /* We have reached the leaf. + * Whether we found the key or not, we have + * to move one to the left. + */ + if (result.sr_item.i_item_offset == 0) + break; + ind->mi_prev_item(ot->ot_table, ind, iref.ir_branch, &result); + goto unlock_check_on_key; + } + xt_ind_release(ot, ind, XT_UNLOCK_READ, &iref); + if (!idx_push(&stack, current, &result.sr_item)) + goto failed; + current = result.sr_branch; + } + + /* We are at the start of a leaf node. + * Go up the stack to find the start poition of the next key. + * If we find none, then we are the end of the index. + */ + xt_ind_release(ot, ind, XT_UNLOCK_READ, &iref); + while ((node = idx_pop(&stack))) { + if (node->i_pos.i_item_offset > node->i_pos.i_node_ref_size) { + if (!xt_ind_fetch(ot, node->i_branch, XT_LOCK_READ, &iref)) + goto failed; + result.sr_item = node->i_pos; + ind->mi_prev_item(ot->ot_table, ind, iref.ir_branch, &result); + goto unlock_check_on_key; + } + } + + /* No more keys: */ + if (search_key) + search_key->sk_on_key = FALSE; + ot->ot_curr_rec_id = 0; + ot->ot_curr_row_id = 0; + + XT_INDEX_UNLOCK(ind, ot); + return OK; + + unlock_check_on_key: + ASSERT_NS(!ot->ot_ind_rhandle); + if (!(ot->ot_ind_rhandle = xt_ind_get_handle(ot, ind, &iref))) + goto failed; + /* + u_int branch_size; + + branch_size = XT_GET_INDEX_BLOCK_LEN(XT_GET_DISK_2(iref.ir_branch->tb_size_2)); + memcpy(&ot->ot_ind_rbuf, iref.ir_branch, branch_size); + xt_ind_release(ot, ind, XT_UNLOCK_READ, &iref); + */ + + XT_INDEX_UNLOCK(ind, ot); + + /* Still on key? */ + if (search_key && search_key->sk_on_key) { + xt_ind_lock_handle(ot->ot_ind_rhandle); + search_key->sk_on_key = myxt_compare_key(ind, search_key->sk_key_value.sv_flags, search_key->sk_key_value.sv_length, + search_key->sk_key_value.sv_key, &ot->ot_ind_rhandle->ih_branch->tb_data[result.sr_item.i_item_offset]) == 0; + xt_ind_unlock_handle(ot->ot_ind_rhandle); + } + + checked_on_key: + ot->ot_curr_rec_id = result.sr_rec_id; + ot->ot_curr_row_id = result.sr_row_id; + ot->ot_ind_state = result.sr_item; + return OK; + + failed: + XT_INDEX_UNLOCK(ind, ot); + if (idx_out_of_memory_failure(ot)) + goto retry_after_oom; + return FAILED; +} + +/* Return TRUE if the record matches the current index search! */ +xtPublic xtBool xt_idx_match_search(register XTOpenTablePtr ot __attribute__((unused)), register XTIndexPtr ind, register XTIdxSearchKeyPtr search_key, xtWord1 *buf, int mode) +{ + int r; + xtWord1 key_buf[XT_INDEX_MAX_KEY_SIZE]; + + myxt_create_key_from_row(ind, key_buf, (xtWord1 *) buf, NULL); + r = myxt_compare_key(ind, search_key->sk_key_value.sv_flags, search_key->sk_key_value.sv_length, search_key->sk_key_value.sv_key, key_buf); + switch (mode) { + case XT_S_MODE_MATCH: + return r == 0; + case XT_S_MODE_NEXT: + return r <= 0; + case XT_S_MODE_PREV: + return r >= 0; + } + return FALSE; +} + +static void idx_set_index_selectivity(XTThreadPtr self __attribute__((unused)), XTOpenTablePtr ot, XTIndexPtr ind) +{ + static const xtRecordID MAX_RECORDS = 100; + + XTIdxSearchKeyRec search_key; + XTIndexSegPtr key_seg; + u_int select_count[2] = {0, 0}; + xtWord1 key_buf[XT_INDEX_MAX_KEY_SIZE]; + u_int key_len; + xtWord1 *next_key_buf; + u_int next_key_len; + u_int curr_len; + u_int diff; + u_int j, i; + /* these 2 vars are used to check the overlapping if we have < 200 records */ + xtRecordID last_rec = 0; /* last record accounted in this iteration */ + xtRecordID last_iter_rec = 0; /* last record accounted in the previous iteration */ + + xtBool (* xt_idx_iterator[2])( + register struct XTOpenTable *ot, register struct XTIndex *ind, register XTIdxSearchKeyPtr search_key) = { + + xt_idx_next, + xt_idx_prev + }; + + xtBool (* xt_idx_begin[2])( + struct XTOpenTable *ot, struct XTIndex *ind, register XTIdxSearchKeyPtr search_key) = { + + xt_idx_search, + xt_idx_search_prev + }; + + ind->mi_select_total = 0; + key_seg = ind->mi_seg; + for (i=0; i < ind->mi_seg_count; key_seg++, i++) { + key_seg->is_selectivity = 1; + key_seg->is_recs_in_range = 1; + } + + for (j=0; j < 2; j++) { + xt_idx_prep_key(ind, &search_key, j == 0 ? XT_SEARCH_FIRST_FLAG : XT_SEARCH_AFTER_LAST_FLAG, NULL, 0); + if (!(xt_idx_begin[j])(ot, ind, &search_key)) + goto failed; + + /* Initialize the buffer with the first index valid index entry: */ + while (!select_count[j] && ot->ot_curr_rec_id != last_iter_rec) { + if (ot->ot_curr_row_id) { + select_count[j]++; + last_rec = ot->ot_curr_rec_id; + + key_len = ot->ot_ind_state.i_item_size - XT_RECORD_REF_SIZE; + xt_ind_unlock_handle(ot->ot_ind_rhandle); + memcpy(key_buf, ot->ot_ind_rhandle->ih_branch->tb_data + ot->ot_ind_state.i_item_offset, key_len); + xt_ind_unlock_handle(ot->ot_ind_rhandle); + } + if (!(xt_idx_iterator[j])(ot, ind, &search_key)) + goto failed_1; + } + + while (select_count[j] < MAX_RECORDS && ot->ot_curr_rec_id != last_iter_rec) { + /* Check if the index entry is committed: */ + if (ot->ot_curr_row_id) { + xt_ind_lock_handle(ot->ot_ind_rhandle); + select_count[j]++; + last_rec = ot->ot_curr_rec_id; + + next_key_len = ot->ot_ind_state.i_item_size - XT_RECORD_REF_SIZE; + next_key_buf = ot->ot_ind_rhandle->ih_branch->tb_data + ot->ot_ind_state.i_item_offset; + + curr_len = 0; + diff = FALSE; + key_seg = ind->mi_seg; + for (i=0; i < ind->mi_seg_count; key_seg++, i++) { + curr_len += myxt_key_seg_length(key_seg, curr_len, key_buf); + if (!diff && myxt_compare_key(ind, 0, curr_len, key_buf, next_key_buf) != 0) + diff = i+1; + if (diff) + key_seg->is_selectivity++; + } + + /* Store the key for the next comparison: */ + key_len = next_key_len; + memcpy(key_buf, next_key_buf, key_len); + xt_ind_unlock_handle(ot->ot_ind_rhandle); + } + + if (!(xt_idx_iterator[j])(ot, ind, &search_key)) + goto failed_1; + } + + last_iter_rec = last_rec; + + if (ot->ot_ind_rhandle) { + xt_ind_release_handle(ot->ot_ind_rhandle, FALSE, self); + ot->ot_ind_rhandle = NULL; + } + } + + u_int select_total; + + select_total = select_count[0] + select_count[1]; + if (select_total) { + u_int recs; + + ind->mi_select_total = select_total; + key_seg = ind->mi_seg; + for (i=0; i < ind->mi_seg_count; key_seg++, i++) { + recs = (u_int) ((double) select_total / (double) key_seg->is_selectivity + (double) 0.5); + key_seg->is_recs_in_range = recs ? recs : 1; + } + } + return; + + failed_1: + xt_ind_release_handle(ot->ot_ind_rhandle, FALSE, self); + ot->ot_ind_rhandle = NULL; + + failed: + ot->ot_table->tab_dic.dic_disable_index = XT_INDEX_CORRUPTED; + xt_log_and_clear_exception_ns(); + return; +} + +xtPublic void xt_ind_set_index_selectivity(XTThreadPtr self, XTOpenTablePtr ot) +{ + XTTableHPtr tab = ot->ot_table; + XTIndexPtr *ind; + u_int i; + + if (!tab->tab_dic.dic_disable_index) { + for (i=0, ind=tab->tab_dic.dic_keys; i<tab->tab_dic.dic_key_count; i++, ind++) + idx_set_index_selectivity(self, ot, *ind); + } +} + +/* + * ----------------------------------------------------------------------- + * Print a b-tree + */ + +#ifdef TEST_CODE +static void idx_check_on_key(XTOpenTablePtr ot) +{ + u_int offs = ot->ot_ind_state.i_item_offset + ot->ot_ind_state.i_item_size - XT_RECORD_REF_SIZE; + xtRecordID rec_id; + xtRowID row_id; + + if (ot->ot_curr_rec_id && ot->ot_ind_state.i_item_offset < ot->ot_ind_state.i_total_size) { + xt_get_record_ref(&ot->ot_ind_rbuf.tb_data[offs], &rec_id, &row_id); + + ASSERT_NS(rec_id == ot->ot_curr_rec_id); + } +} +#endif + +static void idx_check_space(int depth) +{ + for (int i=0; i<depth; i++) + printf(". "); +} + +static u_int idx_check_node(XTOpenTablePtr ot, XTIndexPtr ind, int depth, xtIndexNodeID node) +{ + XTIdxResultRec result; + u_int block_count = 1; + XTIndReferenceRec iref; + +#ifdef DEBUG + iref.ir_ulock = XT_UNLOCK_NONE; +#endif + ASSERT_NS(XT_NODE_ID(node) <= XT_NODE_ID(ot->ot_table->tab_ind_eof)); + if (!xt_ind_fetch(ot, node, XT_LOCK_READ, &iref)) + return 0; + + idx_first_branch_item(ot->ot_table, ind, iref.ir_branch, &result); + ASSERT_NS(result.sr_item.i_total_size + offsetof(XTIdxBranchDRec, tb_data) <= XT_INDEX_PAGE_SIZE); + if (result.sr_item.i_node_ref_size) { + idx_check_space(depth); + printf("%04d -->\n", (int) XT_NODE_ID(result.sr_branch)); +#ifdef TRACK_ACTIVITY + track_block_exists(result.sr_branch); +#endif + block_count += idx_check_node(ot, ind, depth+1, result.sr_branch); + } + + while (result.sr_item.i_item_offset < result.sr_item.i_total_size) { +#ifdef CHECK_PRINTS_RECORD_REFERENCES + idx_check_space(depth); + if (result.sr_item.i_item_size == 12) { + /* Assume this is a NOT-NULL INT!: */ + xtWord4 val = XT_GET_DISK_4(&iref.ir_branch->tb_data[result.sr_item.i_item_offset]); + printf("(%6d) ", (int) val); + } + printf("rec=%d row=%d ", (int) result.sr_rec_id, (int) result.sr_row_id); + printf("\n"); +#endif + idx_next_branch_item(ot->ot_table, ind, iref.ir_branch, &result); + if (result.sr_item.i_node_ref_size) { + idx_check_space(depth); + printf("%04d -->\n", (int) XT_NODE_ID(result.sr_branch)); +#ifdef TRACK_ACTIVITY + track_block_exists(result.sr_branch); +#endif + block_count += idx_check_node(ot, ind, depth+1, result.sr_branch); + } + } + + xt_ind_release(ot, ind, XT_UNLOCK_READ, &iref); + return block_count; +} + +static u_int idx_check_index(XTOpenTablePtr ot, XTIndexPtr ind, xtBool with_lock) +{ + xtIndexNodeID current; + u_int block_count = 0; + u_int i; + + if (with_lock) + XT_INDEX_WRITE_LOCK(ind, ot); + + printf("INDEX (%d) %04d ---------------------------------------\n", (int) ind->mi_index_no, (int) XT_NODE_ID(ind->mi_root)); + if ((XT_NODE_ID(current) = XT_NODE_ID(ind->mi_root))) { +#ifdef TRACK_ACTIVITY + track_block_exists(ind->mi_root); +#endif + block_count = idx_check_node(ot, ind, 0, current); + } + + if (ind->mi_free_list && ind->mi_free_list->fl_free_count) { + printf("INDEX (%d) FREE ---------------------------------------", (int) ind->mi_index_no); + ASSERT_NS(ind->mi_free_list->fl_start == 0); + for (i=0; i<ind->mi_free_list->fl_free_count; i++) { + if ((i % 40) == 0) + printf("\n"); + block_count++; +#ifdef TRACK_ACTIVITY + track_block_exists(ind->mi_free_list->fl_page_id[i]); +#endif + printf("%2d ", (int) XT_NODE_ID(ind->mi_free_list->fl_page_id[i])); + } + if ((i % 40) != 0) + printf("\n"); + } + + if (with_lock) + XT_INDEX_UNLOCK(ind, ot); + return block_count; + +} + +xtPublic void xt_check_indices(XTOpenTablePtr ot) +{ + register XTTableHPtr tab = ot->ot_table; + XTIndexPtr *ind; + xtIndexNodeID current; + XTIndFreeBlockRec free_block; + u_int ind_count, block_count = 0; + u_int free_count = 0; + u_int i, j; + + xt_lock_mutex_ns(&tab->tab_ind_flush_lock); + printf("CHECK INDICES %s ==============================\n", tab->tab_name->ps_path); +#ifdef TRACK_ACTIVITY + track_reset_missing(); +#endif + + ind = tab->tab_dic.dic_keys; + for (u_int k=0; k<tab->tab_dic.dic_key_count; k++, ind++) { + ind_count = idx_check_index(ot, *ind, TRUE); + block_count += ind_count; + } + + xt_lock_mutex_ns(&tab->tab_ind_lock); + printf("\nFREE: ---------------------------------------\n"); + if (tab->tab_ind_free_list) { + XTIndFreeListPtr ptr; + + ptr = tab->tab_ind_free_list; + while (ptr) { + printf("Memory List:"); + i = 0; + for (j=ptr->fl_start; j<ptr->fl_free_count; j++, i++) { + if ((i % 40) == 0) + printf("\n"); + free_count++; +#ifdef TRACK_ACTIVITY + track_block_exists(ptr->fl_page_id[j]); +#endif + printf("%2d ", (int) XT_NODE_ID(ptr->fl_page_id[j])); + } + if ((i % 40) != 0) + printf("\n"); + ptr = ptr->fl_next_list; + } + } + + current = tab->tab_ind_free; + if (XT_NODE_ID(current)) { + u_int k = 0; + printf("Disk List:"); + while (XT_NODE_ID(current)) { + if ((k % 40) == 0) + printf("\n"); + free_count++; +#ifdef TRACK_ACTIVITY + track_block_exists(current); +#endif + printf("%d ", (int) XT_NODE_ID(current)); + if (!xt_ind_read_bytes(ot, current, sizeof(XTIndFreeBlockRec), (xtWord1 *) &free_block)) { + xt_log_and_clear_exception_ns(); + break; + } + XT_NODE_ID(current) = (xtIndexNodeID) XT_GET_DISK_8(free_block.if_next_block_8); + k++; + } + if ((k % 40) != 0) + printf("\n"); + } + printf("\n-----------------------------\n"); + printf("used blocks %d + free blocks %d = %d\n", block_count, free_count, block_count + free_count); + printf("EOF = %"PRIu64", total blocks = %d\n", (xtWord8) xt_ind_node_to_offset(tab, tab->tab_ind_eof), (int) (XT_NODE_ID(tab->tab_ind_eof) - 1)); + printf("-----------------------------\n"); + xt_unlock_mutex_ns(&tab->tab_ind_lock); +#ifdef TRACK_ACTIVITY + track_dump_missing(tab->tab_ind_eof); + printf("===================================================\n"); + track_dump_all((u_int) (XT_NODE_ID(tab->tab_ind_eof) - 1)); +#endif + printf("===================================================\n"); + xt_unlock_mutex_ns(&tab->tab_ind_flush_lock); +} + +/* + * ----------------------------------------------------------------------- + * Index consistant flush + */ + +static xtBool idx_flush_dirty_list(XTIndexLogPtr il, XTOpenTablePtr ot, u_int *flush_count, XTIndBlockPtr *flush_list) +{ + for (u_int i=0; i<*flush_count; i++) + il->il_write_block(ot, flush_list[i]); + *flush_count = 0; + return OK; +} + +static xtBool ind_add_to_dirty_list(XTIndexLogPtr il, XTOpenTablePtr ot, u_int *flush_count, XTIndBlockPtr *flush_list, XTIndBlockPtr block) +{ + register u_int count; + register u_int i; + register u_int guess; + + if (*flush_count == IND_FLUSH_BUFFER_SIZE) { + if (!idx_flush_dirty_list(il, ot, flush_count, flush_list)) + return FAILED; + } + + count = *flush_count; + i = 0; + while (i < count) { + guess = (i + count - 1) >> 1; + if (XT_NODE_ID(block->cb_address) == XT_NODE_ID(flush_list[guess]->cb_address)) { + // Should not happen... + ASSERT_NS(FALSE); + return OK; + } + if (XT_NODE_ID(block->cb_address) < XT_NODE_ID(flush_list[guess]->cb_address)) + count = guess; + else + i = guess + 1; + } + + /* Insert at position i */ + memmove(flush_list + i + 1, flush_list + i, (*flush_count - i) * sizeof(XTIndBlockPtr)); + flush_list[i] = block; + *flush_count = *flush_count + 1; + return OK; +} + +xtPublic xtBool xt_flush_indices(XTOpenTablePtr ot, off_t *bytes_flushed, xtBool have_table_lock) +{ + register XTTableHPtr tab = ot->ot_table; + XTIndexLogPtr il; + XTIndexPtr *indp; + XTIndexPtr ind; + u_int i, j; + xtBool wrote_something = FALSE; + u_int flush_count = 0; + XTIndBlockPtr flush_list[IND_FLUSH_BUFFER_SIZE]; + XTIndBlockPtr block, fblock; + xtWord1 *data; + xtIndexNodeID ind_free; + xtBool something_to_free = FALSE; + xtIndexNodeID last_address, next_address; + xtWord2 curr_flush_seq; + XTIndFreeListPtr list_ptr; + u_int dirty_blocks; + XTCheckPointTablePtr cp_tab; + XTCheckPointStatePtr cp = NULL; + + if (!xt_begin_checkpoint(tab->tab_db, have_table_lock, ot->ot_thread)) + return FAILED; + +#ifdef DEBUG_CHECK_IND_CACHE + xt_ind_check_cache(NULL); +#endif + xt_lock_mutex_ns(&tab->tab_ind_flush_lock); + + if (!tab->tab_db->db_indlogs.ilp_get_log(&il, ot->ot_thread)) + goto failed_3; + + il->il_reset(tab->tab_id); + if (!il->il_write_byte(ot, XT_DT_FREE_LIST)) + goto failed_2; + if (!il->il_write_word4(ot, tab->tab_id)) + goto failed_2; + if (!il->il_write_word4(ot, 0)) + goto failed_2; + + /* Lock all: */ + dirty_blocks = 0; + indp = tab->tab_dic.dic_keys; + for (i=0; i<tab->tab_dic.dic_key_count; i++, indp++) { + ind = *indp; + XT_INDEX_WRITE_LOCK(ind, ot); + if (ind->mi_free_list && ind->mi_free_list->fl_free_count) + something_to_free = TRUE; + dirty_blocks += ind->mi_dirty_blocks; + } + // 128 dirty blocks == 2MB +#ifdef TRACE_FLUSH + printf("FLUSH index %d %s\n", (int) dirty_blocks * XT_INDEX_PAGE_SIZE, tab->tab_name->ps_path); + fflush(stdout); +#endif + if (bytes_flushed) + *bytes_flushed += (dirty_blocks * XT_INDEX_PAGE_SIZE); + + curr_flush_seq = tab->tab_ind_flush_seq; + tab->tab_ind_flush_seq++; + + /* Write the dirty pages: */ + indp = tab->tab_dic.dic_keys; + data = tab->tab_index_head->tp_data; + for (i=0; i<tab->tab_dic.dic_key_count; i++, indp++) { + ind = *indp; + xt_spinlock_lock(&ind->mi_dirty_lock); + if ((block = ind->mi_dirty_list)) { + wrote_something = TRUE; + while (block) { + ASSERT_NS(block->cb_state == IDX_CAC_BLOCK_DIRTY); + ASSERT_NS(block->cp_flush_seq == curr_flush_seq); + if (!ind_add_to_dirty_list(il, ot, &flush_count, flush_list, block)) + goto failed; + block = block->cb_dirty_next; + } + } + xt_spinlock_unlock(&ind->mi_dirty_lock); + XT_SET_NODE_REF(tab, data, ind->mi_root); + data += XT_NODE_REF_SIZE; + } + + /* Flush the dirty blocks: */ + if (!idx_flush_dirty_list(il, ot, &flush_count, flush_list)) + goto failed; + + xt_lock_mutex_ns(&tab->tab_ind_lock); + + /* Write the free list: */ + if (something_to_free) { + union { + xtWord1 buffer[XT_BLOCK_SIZE_FOR_DIRECT_IO]; + XTIndFreeBlockRec free_block; + } x; + memset(x.buffer, 0, sizeof(XTIndFreeBlockRec)); + + /* The old start of the free list: */ + XT_NODE_ID(ind_free) = 0; + while ((list_ptr = tab->tab_ind_free_list)) { + if (list_ptr->fl_start < list_ptr->fl_free_count) { + ind_free = list_ptr->fl_page_id[list_ptr->fl_start]; + break; + } + tab->tab_ind_free_list = list_ptr->fl_next_list; + xt_free_ns(list_ptr); + } + if (!XT_NODE_ID(ind_free)) + ind_free = tab->tab_ind_free; + + if (!il->il_write_byte(ot, XT_DT_FREE_LIST)) + goto failed; + indp = tab->tab_dic.dic_keys; + XT_NODE_ID(last_address) = 0; + for (i=0; i<tab->tab_dic.dic_key_count; i++, indp++) { + ind = *indp; + //ASSERT_NS(XT_INDEX_HAVE_XLOCK(ind, ot)); + if (ind->mi_free_list && ind->mi_free_list->fl_free_count) { + for (j=0; j<ind->mi_free_list->fl_free_count; j++) { + next_address = ind->mi_free_list->fl_page_id[j]; + if (!il->il_write_word4(ot, XT_NODE_ID(ind->mi_free_list->fl_page_id[j]))) + goto failed; + if (XT_NODE_ID(last_address)) { + XT_SET_DISK_8(x.free_block.if_next_block_8, XT_NODE_ID(next_address)); + if (!xt_ind_write_cache(ot, last_address, 8, x.buffer)) + goto failed; + } + last_address = next_address; + } + } + } + if (!il->il_write_word4(ot, XT_NODE_ID(ind_free))) + goto failed; + if (XT_NODE_ID(last_address)) { + XT_SET_DISK_8(x.free_block.if_next_block_8, XT_NODE_ID(tab->tab_ind_free)); + if (!xt_ind_write_cache(ot, last_address, 8, x.buffer)) + goto failed; + } + if (!il->il_write_word4(ot, 0xFFFFFFFF)) + goto failed; + } + + /* + * Add the free list caches to the global free list cache. + * Added backwards to match the write order. + */ + indp = tab->tab_dic.dic_keys + tab->tab_dic.dic_key_count-1; + for (i=0; i<tab->tab_dic.dic_key_count; i++, indp--) { + ind = *indp; + //ASSERT_NS(XT_INDEX_HAVE_XLOCK(ind, ot)); + if (ind->mi_free_list) { + wrote_something = TRUE; + ind->mi_free_list->fl_next_list = tab->tab_ind_free_list; + tab->tab_ind_free_list = ind->mi_free_list; + } + ind->mi_free_list = NULL; + } + + /* + * The new start of the free list is the first + * item on the table free list: + */ + XT_NODE_ID(ind_free) = 0; + while ((list_ptr = tab->tab_ind_free_list)) { + if (list_ptr->fl_start < list_ptr->fl_free_count) { + ind_free = list_ptr->fl_page_id[list_ptr->fl_start]; + break; + } + tab->tab_ind_free_list = list_ptr->fl_next_list; + xt_free_ns(list_ptr); + } + if (!XT_NODE_ID(ind_free)) + ind_free = tab->tab_ind_free; + xt_unlock_mutex_ns(&tab->tab_ind_lock); + + XT_SET_DISK_6(tab->tab_index_head->tp_ind_eof_6, XT_NODE_ID(tab->tab_ind_eof)); + XT_SET_DISK_6(tab->tab_index_head->tp_ind_free_6, XT_NODE_ID(ind_free)); + + if (!il->il_write_header(ot, XT_INDEX_HEAD_SIZE, (xtWord1 *) tab->tab_index_head)) + goto failed; + + indp = tab->tab_dic.dic_keys; + for (i=0; i<tab->tab_dic.dic_key_count; i++, indp++) { + ind = *indp; + XT_INDEX_UNLOCK(ind, ot); + } + + if (wrote_something) { + /* Flush the log before we flush the index. + * + * The reason is, we must make sure that changes that + * will be in the index are already in the transaction + * log. + * + * Only then are we able to undo those changes on + * recovery. + * + * Simple example: + * CREATE TABLE t1 (s1 INT PRIMARY KEY); + * INSERT INTO t1 VALUES (1); + * + * BEGIN; + * INSERT INTO t1 VALUES (2); + * + * --- INDEX IS FLUSHED HERE --- + * + * --- SERVER CRASH HERE --- + * + * + * The INSERT VALUES (2) has been written + * to the log, but not flushed. + * But the index has been updated. + * If the index is flushed it will contain + * the entry for record with s1=2. + * + * This entry must be removed on recovery. + * + * To prevent this situation I flush the log + * here. + */ + if (!(tab->tab_dic.dic_tab_flags & XT_TAB_FLAGS_TEMP_TAB)) { + if (!xt_xlog_flush_log(ot->ot_thread)) + goto failed_2; + if (!il->il_flush(ot)) + goto failed_2; + } + + if (!il->il_apply_log(ot)) + goto failed_2; + + indp = tab->tab_dic.dic_keys; + for (i=0; i<tab->tab_dic.dic_key_count; i++, indp++) { + ind = *indp; + XT_INDEX_WRITE_LOCK(ind, ot); + } + + /* Free up flushed pages: */ + indp = tab->tab_dic.dic_keys; + for (i=0; i<tab->tab_dic.dic_key_count; i++, indp++) { + ind = *indp; + xt_spinlock_lock(&ind->mi_dirty_lock); + if ((block = ind->mi_dirty_list)) { + while (block) { + fblock = block; + block = block->cb_dirty_next; + ASSERT_NS(fblock->cb_state == IDX_CAC_BLOCK_DIRTY); + if (fblock->cp_flush_seq == curr_flush_seq) { + /* Take the block off the dirty list: */ + if (fblock->cb_dirty_next) + fblock->cb_dirty_next->cb_dirty_prev = fblock->cb_dirty_prev; + if (fblock->cb_dirty_prev) + fblock->cb_dirty_prev->cb_dirty_next = fblock->cb_dirty_next; + if (ind->mi_dirty_list == fblock) + ind->mi_dirty_list = fblock->cb_dirty_next; + ind->mi_dirty_blocks--; + fblock->cb_state = IDX_CAC_BLOCK_CLEAN; + } + } + } + xt_spinlock_unlock(&ind->mi_dirty_lock); + } + + indp = tab->tab_dic.dic_keys; + for (i=0; i<tab->tab_dic.dic_key_count; i++, indp++) { + ind = *indp; + XT_INDEX_UNLOCK(ind, ot); + } + } + + il->il_release(); + + /* Mark this table as index flushed: */ + cp = &tab->tab_db->db_cp_state; + xt_lock_mutex_ns(&cp->cp_state_lock); + if (cp->cp_running) { + cp_tab = (XTCheckPointTablePtr) xt_sl_find(NULL, cp->cp_table_ids, &tab->tab_id); + if (cp_tab && (cp_tab->cpt_flushed & XT_CPT_ALL_FLUSHED) != XT_CPT_ALL_FLUSHED) { + cp_tab->cpt_flushed |= XT_CPT_INDEX_FLUSHED; + if ((cp_tab->cpt_flushed & XT_CPT_ALL_FLUSHED) == XT_CPT_ALL_FLUSHED) { + ASSERT_NS(cp->cp_flush_count < xt_sl_get_size(cp->cp_table_ids)); + cp->cp_flush_count++; + } + } + } + xt_unlock_mutex_ns(&cp->cp_state_lock); + + xt_unlock_mutex_ns(&tab->tab_ind_flush_lock); +#ifdef DEBUG_CHECK_IND_CACHE + xt_ind_check_cache((XTIndex *) 1); +#endif +#ifdef TRACE_FLUSH + printf("FLUSH --end-- %s\n", tab->tab_name->ps_path); + fflush(stdout); +#endif + if (!xt_end_checkpoint(tab->tab_db, ot->ot_thread, NULL)) + return FAILED; + return OK; + + failed: + indp = tab->tab_dic.dic_keys; + for (i=0; i<tab->tab_dic.dic_key_count; i++, indp++) { + ind = *indp; + XT_INDEX_UNLOCK(ind, ot); + } + + failed_2: + il->il_release(); + + failed_3: + xt_unlock_mutex_ns(&tab->tab_ind_flush_lock); +#ifdef DEBUG_CHECK_IND_CACHE + xt_ind_check_cache(NULL); +#endif + return FAILED; +} + +void XTIndexLogPool::ilp_init(struct XTThread *self, struct XTDatabase *db, size_t log_buffer_size) +{ + char path[PATH_MAX]; + XTOpenDirPtr od; + xtLogID log_id; + char *file; + XTIndexLogPtr il = NULL; + XTOpenTablePtr ot = NULL; + + ilp_db = db; + ilp_log_buffer_size = log_buffer_size; + xt_init_mutex_with_autoname(self, &ilp_lock); + + xt_strcpy(PATH_MAX, path, db->db_main_path); + xt_add_system_dir(PATH_MAX, path); + if (xt_fs_exists(path)) { + pushsr_(od, xt_dir_close, xt_dir_open(self, path, NULL)); + while (xt_dir_next(self, od)) { + file = xt_dir_name(self, od); + if (xt_starts_with(file, "ilog")) { + if ((log_id = (xtLogID) xt_file_name_to_id(file))) { + if (!ilp_open_log(&il, log_id, FALSE, self)) + goto failed; + if (il->il_tab_id && il->il_log_eof) { + if (!il->il_open_table(&ot)) + goto failed; + if (ot) { + if (!il->il_apply_log(ot)) + goto failed; + ot->ot_thread = self; + il->il_close_table(ot); + } + } + il->il_close(TRUE); + } + } + } + freer_(); // xt_dir_close(od) + } + return; + + failed: + if (ot && il) + il->il_close_table(ot); + if (il) + il->il_close(FALSE); + xt_throw(self); +} + +void XTIndexLogPool::ilp_close(struct XTThread *self __attribute__((unused)), xtBool lock) +{ + XTIndexLogPtr il; + + if (lock) + xt_lock_mutex_ns(&ilp_lock); + while ((il = ilp_log_pool)) { + ilp_log_pool = il->il_next_in_pool; + il_pool_count--; + il->il_close(TRUE); + } + if (lock) + xt_unlock_mutex_ns(&ilp_lock); +} + +void XTIndexLogPool::ilp_exit(struct XTThread *self) +{ + ilp_close(self, FALSE); + ASSERT_NS(il_pool_count == 0); + xt_free_mutex(&ilp_lock); +} + +void XTIndexLogPool::ilp_name(size_t size, char *path, xtLogID log_id) +{ + char name[50]; + + sprintf(name, "ilog-%lu.xt", (u_long) log_id); + xt_strcpy(size, path, ilp_db->db_main_path); + xt_add_system_dir(size, path); + xt_add_dir_char(size, path); + xt_strcat(size, path, name); +} + +xtBool XTIndexLogPool::ilp_open_log(XTIndexLogPtr *ret_il, xtLogID log_id, xtBool excl, XTThreadPtr thread) +{ + char log_path[PATH_MAX]; + XTIndexLogPtr il; + XTIndLogHeadDRec log_head; + size_t read_size; + + ilp_name(PATH_MAX, log_path, log_id); + if (!(il = (XTIndexLogPtr) xt_calloc_ns(sizeof(XTIndexLogRec)))) + return FAILED; + il->il_log_id = log_id; + il->il_pool = this; + + /* Writes will be rounded up to the nearest direct write block size (see [+]), + * so make sure we have space in the buffer for that: + */ + if (!(il->il_buffer = (xtWord1 *) xt_malloc_ns(ilp_log_buffer_size + XT_BLOCK_SIZE_FOR_DIRECT_IO))) + goto failed; + il->il_buffer_size = ilp_log_buffer_size; + + if (!(il->il_of = xt_open_file_ns(log_path, (excl ? XT_FS_EXCLUSIVE : 0) | XT_FS_CREATE | XT_FS_MAKE_PATH))) + goto failed; + + if (!xt_pread_file(il->il_of, 0, sizeof(XTIndLogHeadDRec), 0, &log_head, &read_size, &thread->st_statistics.st_ilog, thread)) + goto failed; + + if (read_size == sizeof(XTIndLogHeadDRec)) { + il->il_tab_id = XT_GET_DISK_4(log_head.ilh_tab_id_4); + il->il_log_eof = XT_GET_DISK_4(log_head.ilh_log_eof_4); + } + else { + il->il_tab_id = 0; + il->il_log_eof = 0; + } + + *ret_il = il; + return OK; + + failed: + il->il_close(FALSE); + return FAILED; +} + +xtBool XTIndexLogPool::ilp_get_log(XTIndexLogPtr *ret_il, XTThreadPtr thread) +{ + XTIndexLogPtr il; + xtLogID log_id = 0; + + xt_lock_mutex_ns(&ilp_lock); + if ((il = ilp_log_pool)) { + ilp_log_pool = il->il_next_in_pool; + il_pool_count--; + } + else { + ilp_next_log_id++; + log_id = ilp_next_log_id; + } + xt_unlock_mutex_ns(&ilp_lock); + if (!il) { + if (!ilp_open_log(&il, log_id, TRUE, thread)) + return FAILED; + } + *ret_il= il; + return OK; +} + +void XTIndexLogPool::ilp_release_log(XTIndexLogPtr il) +{ + xt_lock_mutex_ns(&ilp_lock); + if (il_pool_count == 5) + il->il_close(TRUE); + else { + il_pool_count++; + il->il_next_in_pool = ilp_log_pool; + ilp_log_pool = il; + } + xt_unlock_mutex_ns(&ilp_lock); +} + +void XTIndexLog::il_reset(xtTableID tab_id) +{ + il_tab_id = tab_id; + il_log_eof = 0; + il_buffer_len = 0; + il_buffer_offset = 0; +} + +void XTIndexLog::il_close(xtBool delete_it) +{ + xtLogID log_id = il_log_id; + + if (il_of) { + xt_close_file_ns(il_of); + il_of = NULL; + } + + if (delete_it && log_id) { + char log_path[PATH_MAX]; + + il_pool->ilp_name(PATH_MAX, log_path, log_id); + xt_fs_delete(NULL, log_path); + } + + if (il_buffer) { + xt_free_ns(il_buffer); + il_buffer = NULL; + } + + xt_free_ns(this); +} + + +void XTIndexLog::il_release() +{ + il_pool->ilp_db->db_indlogs.ilp_release_log(this); +} + +xtBool XTIndexLog::il_require_space(size_t bytes, XTThreadPtr thread) +{ + if (il_buffer_len + bytes > il_buffer_size) { + if (!xt_pwrite_file(il_of, il_buffer_offset, il_buffer_len, il_buffer, &thread->st_statistics.st_ilog, thread)) + return FAILED; + il_buffer_offset += il_buffer_len; + il_buffer_len = 0; + } + + return OK; +} + +xtBool XTIndexLog::il_write_byte(struct XTOpenTable *ot __attribute__((unused)), xtWord1 byte) +{ + if (!il_require_space(1, ot->ot_thread)) + return FAILED; + *(il_buffer + il_buffer_len) = byte; + il_buffer_len++; + return OK; +} + +xtBool XTIndexLog::il_write_word4(struct XTOpenTable *ot __attribute__((unused)), xtWord4 value) +{ + xtWord1 *buffer; + + if (!il_require_space(4, ot->ot_thread)) + return FAILED; + buffer = il_buffer + il_buffer_len; + XT_SET_DISK_4(buffer, value); + il_buffer_len += 4; + return OK; +} + +xtBool XTIndexLog::il_write_block(struct XTOpenTable *ot __attribute__((unused)), XTIndBlockPtr block) +{ + XTIndPageDataDPtr page_data; + xtIndexNodeID node_id; + XTIdxBranchDPtr node; + u_int block_len; + + node_id = block->cb_address; + node = (XTIdxBranchDPtr) block->cb_data; + block_len = XT_GET_INDEX_BLOCK_LEN(XT_GET_DISK_2(node->tb_size_2)); + + if (!il_require_space(offsetof(XTIndPageDataDRec, ild_data) + block_len, ot->ot_thread)) + return FAILED; + + ASSERT_NS(offsetof(XTIndPageDataDRec, ild_data) + XT_INDEX_PAGE_SIZE <= il_buffer_size); + + page_data = (XTIndPageDataDPtr) (il_buffer + il_buffer_len); + TRACK_BLOCK_TO_FLUSH(node_id); + page_data->ild_data_type = XT_DT_INDEX_PAGE; + XT_SET_DISK_4(page_data->ild_page_id_4, XT_NODE_ID(node_id)); + memcpy(page_data->ild_data, block->cb_data, block_len); + + il_buffer_len += offsetof(XTIndPageDataDRec, ild_data) + block_len; + + return OK; +} + +xtBool XTIndexLog::il_write_header(struct XTOpenTable *ot __attribute__((unused)), size_t head_size, xtWord1 *head_buf) +{ + XTIndHeadDataDPtr head_data; + + if (!il_require_space(offsetof(XTIndHeadDataDRec, ilh_data) + head_size, ot->ot_thread)) + return FAILED; + + head_data = (XTIndHeadDataDPtr) (il_buffer + il_buffer_len); + head_data->ilh_data_type = XT_DT_HEADER; + XT_SET_DISK_2(head_data->ilh_head_size_2, head_size); + memcpy(head_data->ilh_data, head_buf, head_size); + + il_buffer_len += offsetof(XTIndHeadDataDRec, ilh_data) + head_size; + + return OK; +} + +xtBool XTIndexLog::il_flush(struct XTOpenTable *ot) +{ + XTIndLogHeadDRec log_head; + xtTableID tab_id = ot->ot_table->tab_id; + + if (il_buffer_len) { + if (!xt_pwrite_file(il_of, il_buffer_offset, il_buffer_len, il_buffer, &ot->ot_thread->st_statistics.st_ilog, ot->ot_thread)) + return FAILED; + il_buffer_offset += il_buffer_len; + il_buffer_len = 0; + } + + if (il_log_eof != il_buffer_offset) { + log_head.ilh_data_type = XT_DT_LOG_HEAD; + XT_SET_DISK_4(log_head.ilh_tab_id_4, tab_id); + XT_SET_DISK_4(log_head.ilh_log_eof_4, il_buffer_offset); + + if (!xt_flush_file(il_of, &ot->ot_thread->st_statistics.st_ilog, ot->ot_thread)) + return FAILED; + + if (!xt_pwrite_file(il_of, 0, sizeof(XTIndLogHeadDRec), (xtWord1 *) &log_head, &ot->ot_thread->st_statistics.st_ilog, ot->ot_thread)) + return FAILED; + + if (!xt_flush_file(il_of, &ot->ot_thread->st_statistics.st_ilog, ot->ot_thread)) + return FAILED; + + il_tab_id = tab_id; + il_log_eof = il_buffer_offset; + } + return OK; +} + +xtBool XTIndexLog::il_apply_log(struct XTOpenTable *ot) +{ + XT_NODE_TEMP; + register XTTableHPtr tab = ot->ot_table; + off_t offset; + size_t pos; + xtWord1 *buffer; + off_t address; + xtIndexNodeID node_id; + size_t req_size = 0; + XTIndLogHeadDRec log_head; + + offset = 0; + while (offset < il_log_eof) { + if (offset < il_buffer_offset || + offset >= il_buffer_offset + (off_t) il_buffer_len) { + il_buffer_len = il_buffer_size; + if (il_log_eof - offset < (off_t) il_buffer_len) + il_buffer_len = (size_t) (il_log_eof - offset); + + /* Corrupt log?! */ + if (il_buffer_len < req_size) { + xt_register_ixterr(XT_REG_CONTEXT, XT_ERR_INDEX_LOG_CORRUPT, xt_file_path(il_of)); + xt_log_and_clear_exception_ns(); + return OK; + } + if (!xt_pread_file(il_of, offset, il_buffer_len, il_buffer_len, il_buffer, NULL, &ot->ot_thread->st_statistics.st_ilog, ot->ot_thread)) + return FAILED; + il_buffer_offset = offset; + } + pos = (size_t) (offset - il_buffer_offset); + ASSERT_NS(pos < il_buffer_len); + buffer = il_buffer + pos; + switch (*buffer) { + case XT_DT_LOG_HEAD: + req_size = sizeof(XTIndLogHeadDRec); + if (il_buffer_len - pos < req_size) { + il_buffer_len = 0; + continue; + } + offset += req_size; + req_size = 0; + break; + case XT_DT_INDEX_PAGE: + XTIndPageDataDPtr page_data; + XTIdxBranchDPtr node; + u_int block_len; + size_t size; + + req_size = offsetof(XTIndPageDataDRec, ild_data) + 2; + if (il_buffer_len - pos < req_size) { + il_buffer_len = 0; + continue; + } + page_data = (XTIndPageDataDPtr) buffer; + node_id = XT_RET_NODE_ID(XT_GET_DISK_4(page_data->ild_page_id_4)); + node = (XTIdxBranchDPtr) page_data->ild_data; + block_len = XT_GET_INDEX_BLOCK_LEN(XT_GET_DISK_2(node->tb_size_2)); + if (block_len < 2 || block_len > XT_INDEX_PAGE_SIZE) { + xt_register_taberr(XT_REG_CONTEXT, XT_ERR_INDEX_CORRUPTED, tab->tab_name); + return FAILED; + } + + req_size = offsetof(XTIndPageDataDRec, ild_data) + block_len; + if (il_buffer_len - pos < req_size) { + il_buffer_len = 0; + continue; + } + + TRACK_BLOCK_FLUSH_N(node_id); + address = xt_ind_node_to_offset(tab, node_id); + /* [+] Round up the block size. Space has been provided. */ + size = (((block_len - 1) / XT_BLOCK_SIZE_FOR_DIRECT_IO) + 1) * XT_BLOCK_SIZE_FOR_DIRECT_IO; + IDX_TRACE("%d- W%x\n", (int) XT_NODE_ID(node_id), (int) XT_GET_DISK_2(page_data->ild_data)); + ASSERT_NS(size > 0 && size <= XT_INDEX_PAGE_SIZE); + if (!xt_pwrite_file(ot->ot_ind_file, address, size, page_data->ild_data, &ot->ot_thread->st_statistics.st_ind, ot->ot_thread)) + return FAILED; + + offset += req_size; + req_size = 0; + break; + case XT_DT_FREE_LIST: + xtWord4 block, nblock; + union { + xtWord1 buffer[XT_BLOCK_SIZE_FOR_DIRECT_IO]; + XTIndFreeBlockRec free_block; + } x; + off_t aoff; + + memset(x.buffer, 0, sizeof(XTIndFreeBlockRec)); + + pos++; + offset++; + + for (;;) { + req_size = 8; + if (il_buffer_len - pos < req_size) { + il_buffer_len = il_buffer_size; + if (il_log_eof - offset < (off_t) il_buffer_len) + il_buffer_len = (size_t) (il_log_eof - offset); + /* Corrupt log?! */ + if (il_buffer_len < req_size) { + xt_register_ixterr(XT_REG_CONTEXT, XT_ERR_INDEX_LOG_CORRUPT, xt_file_path(il_of)); + xt_log_and_clear_exception_ns(); + return OK; + } + if (!xt_pread_file(il_of, offset, il_buffer_len, il_buffer_len, il_buffer, NULL, &ot->ot_thread->st_statistics.st_ilog, ot->ot_thread)) + return FAILED; + pos = 0; + } + block = XT_GET_DISK_4(il_buffer + pos); + nblock = XT_GET_DISK_4(il_buffer + pos + 4); + if (nblock == 0xFFFFFFFF) + break; + aoff = xt_ind_node_to_offset(tab, XT_RET_NODE_ID(block)); + XT_SET_DISK_8(x.free_block.if_next_block_8, nblock); + IDX_TRACE("%d- *%x\n", (int) block, (int) XT_GET_DISK_2(x.buffer)); + if (!xt_pwrite_file(ot->ot_ind_file, aoff, XT_BLOCK_SIZE_FOR_DIRECT_IO, x.buffer, &ot->ot_thread->st_statistics.st_ind, ot->ot_thread)) + return FAILED; + pos += 4; + offset += 4; + } + + offset += 8; + req_size = 0; + break; + case XT_DT_HEADER: + XTIndHeadDataDPtr head_data; + size_t len; + + req_size = offsetof(XTIndHeadDataDRec, ilh_data); + if (il_buffer_len - pos < req_size) { + il_buffer_len = 0; + continue; + } + head_data = (XTIndHeadDataDPtr) buffer; + len = XT_GET_DISK_2(head_data->ilh_head_size_2); + + req_size = offsetof(XTIndHeadDataDRec, ilh_data) + len; + if (il_buffer_len - pos < req_size) { + il_buffer_len = 0; + continue; + } + + if (!xt_pwrite_file(ot->ot_ind_file, 0, len, head_data->ilh_data, &ot->ot_thread->st_statistics.st_ind, ot->ot_thread)) + return FAILED; + + offset += req_size; + req_size = 0; + break; + default: + xt_register_ixterr(XT_REG_CONTEXT, XT_ERR_INDEX_LOG_CORRUPT, xt_file_path(il_of)); + xt_log_and_clear_exception_ns(); + return OK; + } + } + + if (!xt_flush_file(ot->ot_ind_file, &ot->ot_thread->st_statistics.st_ind, ot->ot_thread)) + return FAILED; + + log_head.ilh_data_type = XT_DT_LOG_HEAD; + XT_SET_DISK_4(log_head.ilh_tab_id_4, il_tab_id); + XT_SET_DISK_4(log_head.ilh_log_eof_4, 0); + + if (!xt_pwrite_file(il_of, 0, sizeof(XTIndLogHeadDRec), (xtWord1 *) &log_head, &ot->ot_thread->st_statistics.st_ilog, ot->ot_thread)) + return FAILED; + + if (!(tab->tab_dic.dic_tab_flags & XT_TAB_FLAGS_TEMP_TAB)) { + if (!xt_flush_file(il_of, &ot->ot_thread->st_statistics.st_ilog, ot->ot_thread)) + return FAILED; + } + return OK; +} + +xtBool XTIndexLog::il_open_table(struct XTOpenTable **ot) +{ + return xt_db_open_pool_table_ns(ot, il_pool->ilp_db, il_tab_id); +} + +void XTIndexLog::il_close_table(struct XTOpenTable *ot) +{ + xt_db_return_table_to_pool_ns(ot); +} + + diff --git a/storage/pbxt/src/index_xt.h b/storage/pbxt/src/index_xt.h new file mode 100644 index 00000000000..b0b2813f5e1 --- /dev/null +++ b/storage/pbxt/src/index_xt.h @@ -0,0 +1,509 @@ +/* Copyright (c) 2005 PrimeBase Technologies GmbH + * + * PrimeBase XT + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You 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 + * + * 2005-09-30 Paul McCullagh + * + * H&G2JCtL + */ +#ifndef __xt_index_h__ +#define __xt_index_h__ + +#ifdef DRIZZLED +#include <mysys/my_bitmap.h> +#else +#include <mysql_version.h> +#include <my_bitmap.h> +#endif + +#include "thread_xt.h" +#include "linklist_xt.h" +#include "datalog_xt.h" +#include "datadic_xt.h" +//#include "cache_xt.h" + +#ifndef MYSQL_VERSION_ID +#error MYSQL_VERSION_ID must be defined! +#endif + +struct XTDictionary; +STRUCT_TABLE; +struct XTTable; +struct XTOpenTable; +struct XTIndex; +struct XTIndBlock; +struct XTTable; +class Field; + +/* + * INDEX ROLLBACK + * + * When a transaction is rolled back, the index entries are not + * garbage collected!! Instead, the index entries are deleted + * when the data record is garbage collected. + * + * When an index record is written, and this record replaces + * some other record (i.e. a node is updated). The new record + * references its predecessor. + * + * On cleanup (rollback or commit), the predecessor records + * are garbage collected. + * + * NOTE: It is possible to loose memory if a crash occurs during + * index modification. This can occur if a node is split and + * we crash between writing the 2 new records. + * + */ + +/* + * These flags influence the way the compare and search + * routines function. + * + * The low-order 16 bits are reserved for the caller + * (i.e. MySQL specific stuff). + */ +#define XT_SEARCH_WHOLE_KEY 0x10000000 /* This flag is used to search for an insertion point, or to find + * a particular slot that has already been inserted into the + * index. The compare includes the handle of the variation. + */ +#define XT_SEARCH_AFTER_KEY 0x20000000 /* This flags searches for the position just after the given key. + * Even if the key is not found, success is possible if there + * is a value in the index that would be after the search key. + * + * If this flag is not set then we search for the first + * occurrence of the key in the index. If not found we + * take the position just after the search key. + */ +#define XT_SEARCH_FIRST_FLAG 0x40000000 /* Use this flags to find the first position in the index. + * When set, the actual key value is ignored. + */ +#define XT_SEARCH_AFTER_LAST_FLAG 0x80000000 /* Search out the position after the last in the index. + * When set, the actual key value is ignored. + */ + +#define XT_INDEX_MAX_KEY_SIZE_MAX 2048 /* These are allocated on the stack, so this is the maximum! */ + +#define XT_INDEX_MAX_KEY_SIZE ((XT_INDEX_PAGE_SIZE >> 1) > XT_INDEX_MAX_KEY_SIZE_MAX ? XT_INDEX_MAX_KEY_SIZE_MAX : (XT_INDEX_PAGE_SIZE >> 1)) + +#define XT_IS_NODE_BIT 0x8000 + +#define XT_IS_NODE(x) ((x) & XT_IS_NODE_BIT) + +#define XT_NODE_REF_SIZE 4 +#define XT_GET_NODE_REF(t, x) XT_RET_NODE_ID(XT_GET_DISK_4(x)) +#define XT_SET_NODE_REF(t, x, y) XT_SET_DISK_4((x), XT_NODE_ID(y)) + +#define XT_MAX_RECORD_REF_SIZE 8 + +#define XT_INDEX_PAGE_DATA_SIZE XT_INDEX_PAGE_SIZE - 2 /* NOTE: 2 == offsetof(XTIdxBranchDRec, tb_data) */ + +#define XT_MAKE_LEAF_SIZE(x) ((x) + offsetof(XTIdxBranchDRec, tb_data)) + +#define XT_MAKE_NODE_SIZE(x) (((x) + offsetof(XTIdxBranchDRec, tb_data)) | XT_IS_NODE_BIT) + +#define XT_MAKE_BRANCH_SIZE(x, y) (((x) + offsetof(XTIdxBranchDRec, tb_data)) | ((y) ? XT_IS_NODE_BIT : 0)) + +#define XT_GET_INDEX_BLOCK_LEN(x) ((x) & 0x7FFF) + +#define XT_GET_BRANCH_DATA_SIZE(x) (XT_GET_INDEX_BLOCK_LEN(x) - offsetof(XTIdxBranchDRec, tb_data)) + +typedef struct XTIndexHead { + XTDiskValue4 tp_format_offset_4; /* The offset of the format part of the header. */ + + XTDiskValue4 tp_header_size_4; /* The size of the header. */ + XTDiskValue6 tp_not_used_6; + + XTDiskValue6 tp_ind_eof_6; + XTDiskValue6 tp_ind_free_6; + + /* The index roots follow. Each is if_node_ref_size_1 size. */ + xtWord1 tp_data[XT_VAR_LENGTH]; +} XTIndexHeadDRec, *XTIndexHeadDPtr; + +typedef struct XTIndexFormat { + XTDiskValue4 if_format_size_4; /* The size of this structure (index format). */ + XTDiskValue2 if_tab_version_2; /* The table version number. */ + XTDiskValue2 if_ind_version_2; /* The index version number. */ + XTDiskValue1 if_node_ref_size_1; /* This size of index node reference in indexes (default 4 bytes). */ + XTDiskValue1 if_rec_ref_size_1; /* The size of record references in the indexes (default 4 bytes). */ + XTDiskValue4 if_page_size_4; +} XTIndexFormatDRec, *XTIndexFormatDPtr; + +typedef struct XTIdxBranch { + XTDiskValue2 tb_size_2; /* No of bytes used below. */ + + /* We enough space for 2 buffers when splitting! */ + xtWord1 tb_data[XT_INDEX_PAGE_DATA_SIZE]; +} XTIdxBranchDRec, *XTIdxBranchDPtr; + +typedef struct XTIdxItem { + u_int i_total_size; /* Size of the data in the searched branch (excludes 2 byte header). */ + u_int i_item_size; /* Size of the item at this position. */ + u_int i_node_ref_size; + u_int i_item_offset; /* Item offset. */ +} XTIdxItemRec, *XTIdxItemPtr; + +typedef struct XTIdxResult { + xtBool sr_found; /* TRUE if the key was found. */ + xtBool sr_duplicate; /* TRUE if the duplicate was found. */ + xtRecordID sr_rec_id; /* Reference to the record of the found key. */ + xtRowID sr_row_id; + xtIndexNodeID sr_branch; /* Branch to follow when searching a node. */ + XTIdxItemRec sr_item; +} XTIdxResultRec, *XTIdxResultPtr; + +typedef struct XTIdxKeyValue { + int sv_flags; + xtRecordID sv_rec_id; + xtRowID sv_row_id; + u_int sv_length; + xtWord1 *sv_key; +} XTIdxKeyValueRec, *XTIdxKeyValuePtr; + +typedef struct XTIdxSearchKey { + xtBool sk_on_key; /* TRUE if we are positioned on the search key. */ + XTIdxKeyValueRec sk_key_value; /* The value of the search key. */ + xtWord1 sk_key_buf[XT_INDEX_MAX_KEY_SIZE]; +} XTIdxSearchKeyRec, *XTIdxSearchKeyPtr; + +typedef void (*XTScanBranchFunc)(struct XTTable *tab, struct XTIndex *ind, XTIdxBranchDPtr branch, register XTIdxKeyValuePtr value, register XTIdxResultRec *result); +typedef void (*XTPrevItemFunc)(struct XTTable *tab, struct XTIndex *ind, XTIdxBranchDPtr branch, register XTIdxResultRec *result); +typedef void (*XTLastItemFunc)(struct XTTable *tab, struct XTIndex *ind, XTIdxBranchDPtr branch, register XTIdxResultRec *result); + +typedef int (*XTSimpleCompFunc)(struct XTIndex *ind, u_int key_length, xtWord1 *key_value, xtWord1 *b_value); + +struct charset_info_st; + +typedef struct XTIndexSeg /* Key-portion */ +{ + u_int col_idx; /* The table column index of this component. */ + u_int is_recs_in_range; /* Value returned by records_in_range(). */ + u_int is_selectivity; /* The number of unique values per mi_select_total. */ + xtWord1 type; /* Type of key (for sort) */ + xtWord1 language; + xtWord1 null_bit; /* bitmask to test for NULL */ + xtWord1 bit_start,bit_end; /* if bit field */ + xtWord1 bit_pos,bit_length; /* (not used in 4.1) */ + xtWord2 flag; + xtWord2 length; /* Keylength */ + xtWord4 start; /* Start of key in record */ + xtWord4 null_pos; /* position to NULL indicator */ + MX_CONST_CHARSET_INFO *charset; +} XTIndexSegRec, *XTIndexSegPtr; + +typedef struct XTIndFreeList { + struct XTIndFreeList *fl_next_list; /* List of free pages for this index. */ + u_int fl_start; /* Start for allocating from the front of the list. */ + u_int fl_free_count; /* Total items in the free list. */ + xtIndexNodeID fl_page_id[XT_VAR_LENGTH]; /* List of page ID's of the free pages. */ +} XTIndFreeListRec, *XTIndFreeListPtr; + +/* + * XT_INDEX_USE_PTHREAD_RW: + * The stardard pthread RW lock is currently the fastest for INSERTs + * in 32 threads on smalltab: runTest(SMALL_INSERT_TEST, 32, dbUrl) + */ +/* + * XT_INDEX_USE_RW_MUTEX: + * But the RW mutex is a close second, if not just as fast. + * If it is at least as fast, then it is better because read lock + * overhead is then zero. + * + * If definitely does get in the way of the + */ +/* XT_INDEX_USE_PTHREAD_RW: + * But this is clearly better on Linux. 216682 instead of 169259 + * payment transactions (DBT2 in non-conflict transactions, + * using only the customer table). + * + * 27.2.2009: + * The story continues. I have now fixed a bug in RW MUTEX that + * may have been slowing things down (see {RACE-WR_MUTEX}). + * + * So we will need to test "customer payment" again. + * + * 3.3.2009 + * Latest test show that RW mutex is slightly faster: + * 127460 to 123574 payment transactions. + */ +#define XT_INDEX_USE_RW_MUTEX +//#define XT_INDEX_USE_PTHREAD_RW + +#ifdef XT_INDEX_USE_FASTWRLOCK +#define XT_INDEX_LOCK_TYPE XTFastRWLockRec +#define XT_INDEX_INIT_LOCK(s, i) xt_fastrwlock_init(s, &(i)->mi_rwlock) +#define XT_INDEX_FREE_LOCK(s, i) xt_fastrwlock_free(s, &(i)->mi_rwlock) +#define XT_INDEX_READ_LOCK(i, o) xt_fastrwlock_slock(&(i)->mi_rwlock, (o)->ot_thread) +#define XT_INDEX_WRITE_LOCK(i, o) xt_fastrwlock_xlock(&(i)->mi_rwlock, (o)->ot_thread) +#define XT_INDEX_UNLOCK(i, o) xt_fastrwlock_unlock(&(i)->mi_rwlock, (o)->ot_thread) +#define XT_INDEX_HAVE_XLOCK(i, o) TRUE +#elif defined(XT_INDEX_USE_PTHREAD_RW) +#define XT_INDEX_LOCK_TYPE xt_rwlock_type +#define XT_INDEX_INIT_LOCK(s, i) xt_init_rwlock_with_autoname(s, &(i)->mi_rwlock) +#define XT_INDEX_FREE_LOCK(s, i) xt_free_rwlock(&(i)->mi_rwlock) +#define XT_INDEX_READ_LOCK(i, o) xt_slock_rwlock_ns(&(i)->mi_rwlock) +#define XT_INDEX_WRITE_LOCK(i, o) xt_xlock_rwlock_ns(&(i)->mi_rwlock) +#define XT_INDEX_UNLOCK(i, o) xt_unlock_rwlock_ns(&(i)->mi_rwlock) +#define XT_INDEX_HAVE_XLOCK(i, o) TRUE +#else // XT_INDEX_USE_RW_MUTEX +#define XT_INDEX_LOCK_TYPE XTRWMutexRec +#define XT_INDEX_INIT_LOCK(s, i) xt_rwmutex_init_with_autoname(s, &(i)->mi_rwlock) +#define XT_INDEX_FREE_LOCK(s, i) xt_rwmutex_free(s, &(i)->mi_rwlock) +#define XT_INDEX_READ_LOCK(i, o) xt_rwmutex_slock(&(i)->mi_rwlock, (o)->ot_thread->t_id) +#define XT_INDEX_WRITE_LOCK(i, o) xt_rwmutex_xlock(&(i)->mi_rwlock, (o)->ot_thread->t_id) +#define XT_INDEX_UNLOCK(i, o) xt_rwmutex_unlock(&(i)->mi_rwlock, (o)->ot_thread->t_id) +#define XT_INDEX_HAVE_XLOCK(i, o) ((i)->mi_rwlock.xs_xlocker == (o)->ot_thread->t_id) +#endif + +/* The R/W lock on the index is used as follows: + * Read Lock - used for operations on the index that are not of a structural nature. + * This includes any read operation and update operations that change an index + * node. + * Write lock - used to change the structure of the index. This includes adding + * and deleting pages. + */ +typedef struct XTIndex { + u_int mi_index_no; /* The index number (used by MySQL). */ + xt_mutex_type mi_flush_lock; /* Lock the index during flushing. */ + + /* Protected by the mi_rwlock lock: */ + XT_INDEX_LOCK_TYPE mi_rwlock; /* This lock protects the structure of the index. + * Read lock - structure may not change, but pages may change. + * Write lock - structure of index may be changed. + */ + xtIndexNodeID mi_root; /* The index root node. */ + XTIndFreeListPtr mi_free_list; /* List of free pages for this index. */ + + /* Protected by the mi_dirty_lock: */ + XTSpinLockRec mi_dirty_lock; /* Spin lock protecting the dirty & free lists. */ + struct XTIndBlock *mi_dirty_list; /* List of dirty pages for this index. */ + u_int mi_dirty_blocks; /* Count of the dirty blocks. */ + + /* Index contants: */ + u_int mi_flags; + u_int mi_key_size; + xtBool mi_low_byte_first; + xtBool mi_fix_key; + u_int mi_single_type; /* Used when the index contains a single field. */ + u_int mi_select_total; + XTScanBranchFunc mi_scan_branch; + XTPrevItemFunc mi_prev_item; + XTLastItemFunc mi_last_item; + XTSimpleCompFunc mi_simple_comp_key; + MY_BITMAP mi_col_map; /* Bit-map of columns in the index. */ + u_int mi_subset_of; /* Indicates if this index is a complete subset of someother index. */ + u_int mi_seg_count; + XTIndexSegRec mi_seg[200]; +} XTIndexRec, *XTIndexPtr; + +#define XT_INDEX_OK 0 +#define XT_INDEX_TOO_OLD 1 +#define XT_INDEX_TOO_NEW 2 +#define XT_INDEX_BAD_BLOCK 3 +#define XT_INDEX_CORRUPTED 4 +#define XT_INDEX_MISSING 5 + +typedef void (*XTFreeDicFunc)(struct XTThread *self, struct XTDictionary *dic); + +typedef struct XTDictionary { + XTDDTable *dic_table; /* XT table information. */ + + /* Table binary information. */ + u_int dic_mysql_buf_size; /* This is the size of the MySQL buffer (row size + null bytes). */ + u_int dic_mysql_rec_size; /* This is the size of the fixed length MySQL row. */ + u_int dic_rec_size; /* This is the size of the handle data file record. */ + xtBool dic_rec_fixed; /* TRUE if the record has a fixed length size. */ + u_int dic_tab_flags; /* Table flags XT_TAB_FLAGS_* */ + xtWord8 dic_min_auto_inc; /* The minimum auto-increment value. */ + xtWord8 dic_min_row_size; + xtWord8 dic_max_row_size; + xtWord8 dic_ave_row_size; + xtWord8 dic_def_ave_row_size; /* Defined row size set by the user. */ + u_int dic_no_of_cols; /* Number of columns. */ + u_int dic_fix_col_count; /* The number of columns always in the fixed part of a extended record. */ + u_int dic_ind_cols_req; /* The number of columns required to build all indexes. */ + xtWord8 dic_ind_rec_len; /* Length of the record part that is needed for all index columns! */ + + /* BLOB columns: */ + u_int dic_blob_cols_req; /* The number of the columns required to load all LONGBLOB columns. */ + u_int dic_blob_count; + Field **dic_blob_cols; + + /* MySQL related information. NULL when no tables are open from MySQL side! */ + u_int dic_disable_index; /* Non-zero if the index cannot be used. */ + u_int dic_index_ver; /* The version of the index. */ + u_int dic_key_count; + XTIndexPtr *dic_keys; /* MySQL/PBXT key description */ + STRUCT_TABLE *dic_my_table; /* MySQL table */ +} XTDictionaryRec, *XTDictionaryPtr; + +#define XT_DT_LOG_HEAD 0 +#define XT_DT_INDEX_PAGE 1 +#define XT_DT_FREE_LIST 2 +#define XT_DT_HEADER 3 + +typedef struct XTIndLogHead { + xtWord1 ilh_data_type; /* XT_DT_LOG_HEAD */ + XTDiskValue4 ilh_tab_id_4; + XTDiskValue4 ilh_log_eof_4; /* The entire size of the log (0 if invalid!) */ +} XTIndLogHeadDRec, *XTIndLogHeadDPtr; + +typedef struct XTIndPageData { + xtWord1 ild_data_type; + XTDiskValue4 ild_page_id_4; + xtWord1 ild_data[XT_VAR_LENGTH]; +} XTIndPageDataDRec, *XTIndPageDataDPtr; + +typedef struct XTIndHeadData { + xtWord1 ilh_data_type; + XTDiskValue2 ilh_head_size_2; + xtWord1 ilh_data[XT_VAR_LENGTH]; +} XTIndHeadDataDRec, *XTIndHeadDataDPtr; + +typedef struct XTIndexLog { + struct XTIndexLogPool *il_pool; + struct XTIndexLog *il_next_in_pool; + + xtLogID il_log_id; /* The ID of the data log. */ + XTOpenFilePtr il_of; + size_t il_buffer_size; + xtWord1 *il_buffer; + + xtTableID il_tab_id; + off_t il_log_eof; + size_t il_buffer_len; + off_t il_buffer_offset; + + + void il_reset(xtTableID tab_id); + void il_close(xtBool delete_it); + void il_release(); + + xtBool il_write_byte(struct XTOpenTable *ot, xtWord1 val); + xtBool il_write_word4(struct XTOpenTable *ot, xtWord4 value); + xtBool il_write_block(struct XTOpenTable *ot, struct XTIndBlock *block); + xtBool il_write_free_list(struct XTOpenTable *ot, u_int free_count, XTIndFreeListPtr free_list); + xtBool il_require_space(size_t bytes, XTThreadPtr thread); + xtBool il_write_header(struct XTOpenTable *ot, size_t head_size, xtWord1 *head_data); + xtBool il_flush(struct XTOpenTable *ot); + xtBool il_apply_log(struct XTOpenTable *ot); + + xtBool il_open_table(struct XTOpenTable **ot); + void il_close_table(struct XTOpenTable *ot); +} XTIndexLogRec, *XTIndexLogPtr; + +typedef struct XTIndexLogPool { + struct XTDatabase *ilp_db; + size_t ilp_log_buffer_size; + u_int il_pool_count; + XTIndexLogPtr ilp_log_pool; + xt_mutex_type ilp_lock; /* The public pool lock. */ + xtLogID ilp_next_log_id; + + void ilp_init(struct XTThread *self, struct XTDatabase *db, size_t log_buffer_size); + void ilp_close(struct XTThread *self, xtBool lock); + void ilp_exit(struct XTThread *self); + void ilp_name(size_t size, char *path, xtLogID log_id); + + xtBool ilp_open_log(XTIndexLogPtr *il, xtLogID log_id, xtBool excl, XTThreadPtr thread); + + xtBool ilp_get_log(XTIndexLogPtr *il, XTThreadPtr thread); + void ilp_release_log(XTIndexLogPtr il); +} XTIndexLogPoolRec, *XTIndexLogPoolPtr; + +/* A record reference consists of a record ID and a row ID: */ +inline void xt_get_record_ref(register xtWord1 *item, xtRecordID *rec_id, xtRowID *row_id) { + *rec_id = XT_GET_DISK_4(item); + item += 4; + *row_id = XT_GET_DISK_4(item); +} + +inline void xt_get_res_record_ref(register xtWord1 *item, register XTIdxResultRec *result) { + result->sr_rec_id = XT_GET_DISK_4(item); + item += 4; + result->sr_row_id = XT_GET_DISK_4(item); +} + +inline void xt_set_record_ref(register xtWord1 *item, xtRecordID rec_id, xtRowID row_id) { + XT_SET_DISK_4(item, rec_id); + item += 4; + XT_SET_DISK_4(item, row_id); +} + +inline void xt_set_val_record_ref(register xtWord1 *item, register XTIdxKeyValuePtr value) { + XT_SET_DISK_4(item, value->sv_rec_id); + item += 4; + XT_SET_DISK_4(item, value->sv_row_id); +} + +xtBool xt_idx_insert(struct XTOpenTable *ot, struct XTIndex *ind, xtRowID row_id, xtRecordID rec_id, xtWord1 *rec_buf, xtWord1 *bef_buf, xtBool allow_dups); +xtBool xt_idx_delete(struct XTOpenTable *ot, struct XTIndex *ind, xtRecordID rec_id, xtWord1 *rec_buf); +xtBool xt_idx_update_row_id(struct XTOpenTable *ot, struct XTIndex *ind, xtRecordID rec_id, xtRowID row_id, xtWord1 *rec_buf); +void xt_idx_prep_key(struct XTIndex *ind, register XTIdxSearchKeyPtr search_key, int flags, xtWord1 *in_key_buf, size_t in_key_length); +xtBool xt_idx_research(struct XTOpenTable *ot, struct XTIndex *ind); +xtBool xt_idx_search(struct XTOpenTable *ot, struct XTIndex *ind, register XTIdxSearchKeyPtr search_key); +xtBool xt_idx_search_prev(struct XTOpenTable *ot, struct XTIndex *ind, register XTIdxSearchKeyPtr search_key); +xtBool xt_idx_next(register struct XTOpenTable *ot, register struct XTIndex *ind, register XTIdxSearchKeyPtr search_key); +xtBool xt_idx_prev(register struct XTOpenTable *ot, register struct XTIndex *ind, register XTIdxSearchKeyPtr search_key); +xtBool xt_idx_read(struct XTOpenTable *ot, struct XTIndex *ind, xtWord1 *rec_buf); +void xt_ind_set_index_selectivity(XTThreadPtr self, struct XTOpenTable *ot); +void xt_check_indices(struct XTOpenTable *ot); +xtBool xt_flush_indices(struct XTOpenTable *ot, off_t *bytes_flushed, xtBool have_table_lock); +void xt_ind_track_dump_block(struct XTTable *tab, xtIndexNodeID address); + +#define XT_S_MODE_MATCH 0 +#define XT_S_MODE_NEXT 1 +#define XT_S_MODE_PREV 2 +xtBool xt_idx_match_search(struct XTOpenTable *ot, struct XTIndex *ind, register XTIdxSearchKeyPtr search_key, xtWord1 *buf, int mode); + +int xt_compare_2_int4(XTIndexPtr ind, uint key_length, xtWord1 *key_value, xtWord1 *b_value); +int xt_compare_3_int4(XTIndexPtr ind, uint key_length, xtWord1 *key_value, xtWord1 *b_value); +void xt_scan_branch_single(struct XTTable *tab, XTIndexPtr ind, XTIdxBranchDPtr branch, register XTIdxKeyValuePtr value, register XTIdxResultRec *result); +void xt_scan_branch_fix(struct XTTable *tab, XTIndexPtr ind, XTIdxBranchDPtr branch, register XTIdxKeyValuePtr value, register XTIdxResultRec *result); +void xt_scan_branch_fix_simple(struct XTTable *tab, XTIndexPtr ind, XTIdxBranchDPtr branch, register XTIdxKeyValuePtr value, register XTIdxResultRec *result); +void xt_scan_branch_var(struct XTTable *tab, XTIndexPtr ind, XTIdxBranchDPtr branch, register XTIdxKeyValuePtr value, register XTIdxResultRec *result); + +void xt_prev_branch_item_fix(struct XTTable *tab, XTIndexPtr ind, XTIdxBranchDPtr branch, register XTIdxResultRec *result); +void xt_prev_branch_item_var(struct XTTable *tab, XTIndexPtr ind, XTIdxBranchDPtr branch, register XTIdxResultRec *result); + +void xt_last_branch_item_fix(struct XTTable *tab, XTIndexPtr ind, XTIdxBranchDPtr branch, register XTIdxResultPtr result); +void xt_last_branch_item_var(struct XTTable *tab, XTIndexPtr ind, XTIdxBranchDPtr branch, register XTIdxResultPtr result); + +//#define TRACK_ACTIVITY +#ifdef TRACK_ACTIVITY + +#define TRACK_BLOCK_ALLOC(x) track_work(xt_ind_offset_to_node(tab, x), "A") +#define TRACK_BLOCK_FREE(x) track_work(xt_ind_offset_to_node(ot->ot_table, x), "-") +#define TRACK_BLOCK_SPLIT(x) track_work(xt_ind_offset_to_node(ot->ot_table, x), "/") +#define TRACK_BLOCK_WRITE(x) track_work(xt_ind_offset_to_node(ot->ot_table, x), "w") +#define TRACK_BLOCK_FLUSH_N(x) track_work(x, "F") +#define TRACK_BLOCK_TO_FLUSH(x) track_work(x, "f") + +xtPublic void track_work(u_int block, char *what); +#else + +#define TRACK_BLOCK_ALLOC(x) +#define TRACK_BLOCK_FREE(x) +#define TRACK_BLOCK_SPLIT(x) +#define TRACK_BLOCK_WRITE(x) +#define TRACK_BLOCK_FLUSH_N(x) +#define TRACK_BLOCK_TO_FLUSH(x) + +#endif + +#endif + diff --git a/storage/pbxt/src/linklist_xt.cc b/storage/pbxt/src/linklist_xt.cc new file mode 100644 index 00000000000..de5fc6170ce --- /dev/null +++ b/storage/pbxt/src/linklist_xt.cc @@ -0,0 +1,224 @@ +/* Copyright (c) 2005 PrimeBase Technologies GmbH + * + * PrimeBase XT + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You 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 + * + * 2005-02-03 Paul McCullagh + * + * H&G2JCtL + */ + +#include "xt_config.h" + +#include "pthread_xt.h" +#include "linklist_xt.h" +#include "thread_xt.h" +#include "memory_xt.h" + +xtPublic XTLinkedListPtr xt_new_linkedlist(struct XTThread *self, void *thunk, XTFreeFunc free_func, xtBool with_lock) +{ + XTLinkedListPtr ll; + + ll = (XTLinkedListPtr) xt_calloc(self, sizeof(XTLinkedListRec)); + try_(a) { + if (with_lock) { + ll->ll_lock = (xt_mutex_type *) xt_calloc(self, sizeof(xt_mutex_type)); + try_(b) { + xt_init_mutex_with_autoname(self, ll->ll_lock); + } + catch_(b) { + xt_free(self, ll->ll_lock); + ll->ll_lock = NULL; + throw_(); + } + cont_(b); + ll->ll_cond = (xt_cond_type *) xt_calloc(self, sizeof(xt_cond_type)); + try_(c) { + xt_init_cond(self, ll->ll_cond); + } + catch_(c) { + xt_free(self, ll->ll_cond); + ll->ll_cond = NULL; + throw_(); + } + cont_(c); + } + ll->ll_thunk = thunk; + ll->ll_free_func = free_func; + } + catch_(a) { + xt_free_linkedlist(self, ll); + throw_(); + } + cont_(a); + return ll; +} + +xtPublic void xt_free_linkedlist(XTThreadPtr self, XTLinkedListPtr ll) +{ + if (ll->ll_lock) + xt_lock_mutex(self, ll->ll_lock); + while (ll->ll_items) + xt_ll_remove(self, ll, ll->ll_items, FALSE); + if (ll->ll_lock) + xt_unlock_mutex(self, ll->ll_lock); + if (ll->ll_lock) { + xt_free_mutex(ll->ll_lock); + xt_free(self, ll->ll_lock); + } + if (ll->ll_cond) { + xt_free_cond(ll->ll_cond); + xt_free(self, ll->ll_cond); + } + xt_free(self, ll); +} + +xtPublic void xt_ll_add(XTThreadPtr self, XTLinkedListPtr ll, XTLinkedItemPtr li, xtBool lock) +{ + if (lock && ll->ll_lock) + xt_lock_mutex(self, ll->ll_lock); + li->li_next = ll->ll_items; + li->li_prev = NULL; + if (ll->ll_items) + ll->ll_items->li_prev = li; + ll->ll_items = li; + ll->ll_item_count++; + if (lock && ll->ll_lock) + xt_unlock_mutex(self, ll->ll_lock); +} + +xtPublic XTLinkedItemPtr xt_ll_first_item(XTThreadPtr XT_UNUSED(self), XTLinkedListPtr ll) +{ + return ll ? ll->ll_items : NULL; +} + +xtPublic XTLinkedItemPtr xt_ll_next_item(XTThreadPtr XT_UNUSED(self), XTLinkedItemPtr item) +{ + return item->li_next; +} + +xtPublic xtBool xt_ll_exists(XTThreadPtr self, XTLinkedListPtr ll, XTLinkedItemPtr li, xtBool lock) +{ + XTLinkedItemPtr ptr; + + if (lock && ll->ll_lock) + xt_lock_mutex(self, ll->ll_lock); + + ptr = ll->ll_items; + + for (ptr = ll->ll_items; ptr && (ptr != li); ptr = ptr->li_next){} + + if (lock && ll->ll_lock) + xt_unlock_mutex(self, ll->ll_lock); + + return (ptr == li); +} + +xtPublic void xt_ll_remove(XTThreadPtr self, XTLinkedListPtr ll, XTLinkedItemPtr li, xtBool lock) +{ + if (lock && ll->ll_lock) + xt_lock_mutex(self, ll->ll_lock); + + /* Move front pointer: */ + if (ll->ll_items == li) + ll->ll_items = li->li_next; + + /* Remove from list: */ + if (li->li_prev) + li->li_prev->li_next = li->li_next; + if (li->li_next) + li->li_next->li_prev = li->li_prev; + + ll->ll_item_count--; + if (ll->ll_free_func) + (*ll->ll_free_func)(self, ll->ll_thunk, li); + + /* Signal one less: */ + if (ll->ll_cond) + xt_signal_cond(self, ll->ll_cond); + + if (lock && ll->ll_lock) + xt_unlock_mutex(self, ll->ll_lock); +} + +xtPublic void xt_ll_lock(XTThreadPtr self, XTLinkedListPtr ll) +{ + if (ll->ll_lock) + xt_lock_mutex(self, ll->ll_lock); +} + +xtPublic void xt_ll_unlock(XTThreadPtr self, XTLinkedListPtr ll) +{ + if (ll->ll_lock) + xt_unlock_mutex(self, ll->ll_lock); +} + +xtPublic void xt_ll_wait_till_empty(XTThreadPtr self, XTLinkedListPtr ll) +{ + xt_lock_mutex(self, ll->ll_lock); + pushr_(xt_unlock_mutex, ll->ll_lock); + for (;;) { + if (ll->ll_item_count == 0) + break; + xt_wait_cond(self, ll->ll_cond, ll->ll_lock); + } + freer_(); // xt_unlock_mutex(ll->ll_lock) +} + +xtPublic u_int xt_ll_get_size(XTLinkedListPtr ll) +{ + return ll->ll_item_count; +} + +xtPublic void xt_init_linkedqueue(XTThreadPtr XT_UNUSED(self), XTLinkedQueuePtr lq) +{ + lq->lq_count = 0; + lq->lq_front = NULL; + lq->lq_back = NULL; +} + +xtPublic void xt_exit_linkedqueue(XTThreadPtr XT_UNUSED(self), XTLinkedQueuePtr lq) +{ + lq->lq_count = 0; + lq->lq_front = NULL; + lq->lq_back = NULL; +} + +xtPublic void xt_lq_add(XTThreadPtr XT_UNUSED(self), XTLinkedQueuePtr lq, XTLinkedQItemPtr qi) +{ + lq->lq_count++; + qi->qi_next = NULL; + if (!lq->lq_front) + lq->lq_front = qi; + if (lq->lq_back) + lq->lq_back->qi_next = qi; + lq->lq_back = qi; +} + +xtPublic XTLinkedQItemPtr xt_lq_remove(XTThreadPtr XT_UNUSED(self), XTLinkedQueuePtr lq) +{ + XTLinkedQItemPtr qi = NULL; + + if (!lq->lq_front) { + qi = lq->lq_front; + lq->lq_front = qi->qi_next; + if (!lq->lq_front) + lq->lq_back = NULL; + qi->qi_next = NULL; + } + return qi; +} + diff --git a/storage/pbxt/src/linklist_xt.h b/storage/pbxt/src/linklist_xt.h new file mode 100644 index 00000000000..1e33f71a421 --- /dev/null +++ b/storage/pbxt/src/linklist_xt.h @@ -0,0 +1,77 @@ +/* Copyright (c) 2005 PrimeBase Technologies GmbH + * + * PrimeBase XT + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You 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 + * + * 2005-02-03 Paul McCullagh + * + * H&G2JCtL + */ +#ifndef __xt_linklist_h__ +#define __xt_linklist_h__ + +#include "xt_defs.h" + +struct XTThread; + +typedef struct XTLinkedItem { + struct XTLinkedItem *li_prev; + struct XTLinkedItem *li_next; +} XTLinkedItemRec, *XTLinkedItemPtr; + +typedef struct XTLinkedList { + xt_mutex_type *ll_lock; + xt_cond_type *ll_cond; /* Condition for wait for empty. */ + void *ll_thunk; + XTFreeFunc ll_free_func; + u_int ll_item_count; + XTLinkedItemPtr ll_items; +} XTLinkedListRec, *XTLinkedListPtr; + +XTLinkedListPtr xt_new_linkedlist(struct XTThread *self, void *thunk, XTFreeFunc free_func, xtBool with_lock); +void xt_free_linkedlist(struct XTThread *self, XTLinkedListPtr ll); + +void xt_ll_add(struct XTThread *self, XTLinkedListPtr ll, XTLinkedItemPtr li, xtBool lock); +void xt_ll_remove(struct XTThread *self, XTLinkedListPtr ll, XTLinkedItemPtr li, xtBool lock); +xtBool xt_ll_exists(struct XTThread *self, XTLinkedListPtr ll, XTLinkedItemPtr li, xtBool lock); + +void xt_ll_lock(struct XTThread *self, XTLinkedListPtr ll); +void xt_ll_unlock(struct XTThread *self, XTLinkedListPtr ll); + +void xt_ll_wait_till_empty(struct XTThread *self, XTLinkedListPtr ll); + +XTLinkedItemPtr xt_ll_first_item(struct XTThread *self, XTLinkedListPtr ll); +XTLinkedItemPtr xt_ll_next_item(struct XTThread *self, XTLinkedItemPtr item); +u_int xt_ll_get_size(XTLinkedListPtr ll); + +typedef struct XTLinkedQItem { + struct XTLinkedQItem *qi_next; +} XTLinkedQItemRec, *XTLinkedQItemPtr; + +typedef struct XTLinkedQueue { + size_t lq_count; + XTLinkedQItemPtr lq_front; + XTLinkedQItemPtr lq_back; +} XTLinkedQueueRec, *XTLinkedQueuePtr; + +void xt_init_linkedqueue(struct XTThread *self, XTLinkedQueuePtr lq); +void xt_exit_linkedqueue(struct XTThread *self, XTLinkedQueuePtr lq); + +void xt_lq_add(struct XTThread *self, XTLinkedQueuePtr lq, XTLinkedQItemPtr qi); +XTLinkedQItemPtr xt_lq_remove(struct XTThread *self, XTLinkedQueuePtr lq); + +#endif + diff --git a/storage/pbxt/src/lock_xt.cc b/storage/pbxt/src/lock_xt.cc new file mode 100644 index 00000000000..ec698bb81b2 --- /dev/null +++ b/storage/pbxt/src/lock_xt.cc @@ -0,0 +1,2506 @@ +/* Copyright (c) 2005 PrimeBase Technologies GmbH + * + * PrimeBase XT + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You 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 + * + * 2008-01-24 Paul McCullagh + * + * Row lock functions. + * + * H&G2JCtL + */ + +#include "xt_config.h" + +#include <stdio.h> + +#include "lock_xt.h" +#include "thread_xt.h" +#include "table_xt.h" +#include "xaction_xt.h" +#include "database_xt.h" +#include "trace_xt.h" + +#ifdef DEBUG +//#define XT_TRACE_LOCKS +//#define CHECK_ROWLOCK_GROUP_CONSISTENCY +#endif + +/* + * ----------------------------------------------------------------------- + * ROW LOCKS, LIST BASED + */ +#ifdef XT_USE_LIST_BASED_ROW_LOCKS + +#ifdef CHECK_ROWLOCK_GROUP_CONSISTENCY +/* + * Requires a spin-lock on group->lg_lock! + */ +static void check_rowlock_group(XTLockGroupPtr group) +{ + XTThreadPtr self = xt_get_self(); + + char *crash = NULL; + + if (group->lg_lock.spl_locker != self) + *crash = 1; + + if (group->lg_list_in_use > group->lg_list_size) + *crash = 1; + + xtRowID prev_row = 0; + XTLockItemPtr item = group->lg_list; + + for (int i = 0; i < group->lg_list_in_use; i++, item++) { + + if (!item->li_thread_id) + *crash = 1; + + if(!xt_thr_array[item->li_thread_id]->st_xact_data) + *crash = 1; + + if(item->li_count > XT_TEMP_LOCK_BYTES) + *crash = 1; + + // rows per thread must obey the row_id > prev_row_id + prev_count*group_size rule + if (prev_row >= item->li_row_id) + *crash = 1; + + // calculate the new prev. row + if (item->li_count < XT_TEMP_LOCK_BYTES) + prev_row = item->li_row_id + (item->li_count - 1) * XT_ROW_LOCK_GROUP_COUNT; + else + prev_row = item->li_row_id; + } +} +#endif + +static int xlock_cmp_row_ids(XTThreadPtr XT_UNUSED(self), register const void *XT_UNUSED(thunk), register const void *a, register const void *b) +{ + xtRowID row_id = *((xtTableID *) a); + XTLockItemPtr item = (XTLockItemPtr) b; + + if (row_id < item->li_row_id) + return -1; + if (row_id > item->li_row_id) + return 1; + return 0; +} + +void XTRowLockList::xt_remove_all_locks(struct XTDatabase *, XTThreadPtr thread) +{ +#ifdef XT_TRACE_LOCKS + xt_ttracef(xt_get_self(), "remove all locks\n"); +#endif + if (!bl_count) + return; + + xtThreadID thd_id; + XTPermRowLockPtr plock; +#ifndef XT_USE_TABLE_REF + XTOpenTablePtr pot = NULL; +#endif + + thd_id = thread->t_id; + plock = (XTPermRowLockPtr) bl_data; + for (u_int i=0; i<bl_count; i++) { +#ifdef XT_USE_TABLE_REF + XTTableHPtr tab = plock->pr_table; +#else + if (!xt_db_open_pool_table_ns(&pot, db, plock->pr_tab_id)) { + /* Should not happen, but just in case, we just don't + * remove the lock. We will probably end up with a deadlock + * somewhere. + */ + xt_log_and_clear_exception_ns(); + } + else { +#endif + for (int j=0; j<XT_ROW_LOCK_GROUP_COUNT; j++) { + if (plock->pr_group[j]) { + /* Go through group j and compact. */ +#ifndef XT_USE_TABLE_REF + XTTableHPtr tab = pot->ot_table; +#endif + XTLockGroupPtr group; + XTLockItemPtr copy; + XTLockItemPtr item; + int new_count; + + group = &tab->tab_locks.rl_groups[j]; + xt_spinlock_lock(&group->lg_lock); + copy = group->lg_list; + item = group->lg_list; + new_count = 0; + for (size_t k=0; k<group->lg_list_in_use; k++) { + if (item->li_thread_id != thd_id) { + if (copy != item) { + copy->li_row_id = item->li_row_id; + copy->li_count = item->li_count; + copy->li_thread_id = item->li_thread_id; + } + new_count++; + copy++; + } +#ifdef XT_TRACE_LOCKS + else { + if (item->li_count == XT_TEMP_LOCK_BYTES) + xt_ttracef(xt_get_self(), "remove group %d lock row_id=%d TEMP\n", j, (int) item->li_row_id); + else + xt_ttracef(xt_get_self(), "remove group %d locks row_id=%d (%d)\n", j, (int) item->li_row_id, (int) item->li_count); + } +#endif + item++; + } + group->lg_list_in_use = new_count; +#ifdef CHECK_ROWLOCK_GROUP_CONSISTENCY + check_rowlock_group(group); +#endif + if (group->lg_wait_queue) + tab->tab_locks.rl_grant_locks(group, thread); + + xt_spinlock_unlock(&group->lg_lock); + + xt_xn_wakeup_thread_list(thread); + } + } +#ifdef XT_USE_TABLE_REF + xt_heap_release(NULL, plock->pr_table); +#else + xt_db_return_table_to_pool_ns(pot); + } +#endif + plock++; + } + bl_count = 0; +} + +#ifdef DEBUG_LOCK_QUEUE +int *dummy_ptr = 0; + +void XTRowLocks::rl_check(XTLockWaitPtr no_lw) +{ + XTLockGroupPtr group; + XTLockWaitPtr lw, lw_prev; + + for (int i=0; i<XT_ROW_LOCK_GROUP_COUNT; i++) { + group = &rl_groups[i]; + xt_spinlock_lock(&group->lg_lock); + + lw = group->lg_wait_queue; + lw_prev = NULL; + while (lw) { + if (lw == no_lw) + *dummy_ptr = 1; + if (lw->lw_prev != lw_prev) + *dummy_ptr = 2; + lw_prev = lw; + lw = lw->lw_next; + } + xt_spinlock_unlock(&group->lg_lock); + } +} +#endif + +xtBool XTRowLocks::rl_lock_row(XTLockGroupPtr group, XTLockWaitPtr lw, XTRowLockListPtr, int *result) +{ + XTLockItemPtr item; + size_t index; + xtRowID row_id = lw->lw_row_id; + +#ifdef CHECK_ROWLOCK_GROUP_CONSISTENCY + check_rowlock_group(group); +#endif + if (group->lg_list_size == group->lg_list_in_use) { + if (!xt_realloc_ns((void **) &group->lg_list, (group->lg_list_size + 2) * sizeof(XTLockItemRec))) + return FAILED; + group->lg_list_size += 2; + } + item = (XTLockItemPtr) xt_bsearch(NULL, &row_id, group->lg_list, group->lg_list_in_use, sizeof(XTLockItemRec), &index, NULL, xlock_cmp_row_ids); + + /* There's no item with this ID, but there could be an item with a range that covers this row */ + if (!item && group->lg_list_in_use) { + if (index > 0) { + int count; + + item = group->lg_list + index - 1; + + count = item->li_count; + if (item->li_count == XT_TEMP_LOCK_BYTES) + count = 1; + + if (row_id >= item->li_row_id + count * XT_ROW_LOCK_GROUP_COUNT) + item = NULL; + } + } + + if (item) { + /* Item already exists. */ + if (item->li_thread_id == lw->lw_thread->t_id) { + /* Already have a permanent lock: */ + *result = XT_NO_LOCK; + lw->lw_curr_lock = XT_NO_LOCK; + return OK; + } + /* {REMOVE-LOCKS} + * This must be valid because a thread must remove + * the locks before it frees its st_xact_data structure, + * xt_thr_array entry must also be valid, because + * transaction must be ended before the thread is + * killed. + */ + *result = item->li_count == XT_TEMP_LOCK_BYTES ? XT_TEMP_LOCK : XT_PERM_LOCK; + lw->lw_xn_id = xt_thr_array[item->li_thread_id]->st_xact_data->xd_start_xn_id; + lw->lw_curr_lock = *result; + return OK; + } + + /* Add the lock: */ + XT_MEMMOVE(group->lg_list, &group->lg_list[index+1], + &group->lg_list[index], (group->lg_list_in_use - index) * sizeof(XTLockItemRec)); + group->lg_list[index].li_row_id = row_id; + group->lg_list[index].li_count = XT_TEMP_LOCK_BYTES; + group->lg_list[index].li_thread_id = lw->lw_thread->t_id; + group->lg_list_in_use++; + +#ifdef XT_TRACE_LOCKS + xt_ttracef(ot->ot_thread, "set temp lock row=%d setby=%s\n", (int) row_id, xt_get_self()->t_name); +#endif +#ifdef CHECK_ROWLOCK_GROUP_CONSISTENCY + check_rowlock_group(group); +#endif + *result = XT_NO_LOCK; + lw->lw_ot->ot_temp_row_lock = row_id; + lw->lw_curr_lock = XT_NO_LOCK; + return OK; +} + +void XTRowLocks::rl_grant_locks(XTLockGroupPtr group, XTThreadPtr thread) +{ + XTLockWaitPtr lw, lw_next, lw_prev; + int result; + xtThreadID lw_thd_id; + + thread->st_thread_list_count = 0; + lw = group->lg_wait_queue; + while (lw) { + lw_next = lw->lw_next; + lw_prev = lw->lw_prev; + lw_thd_id = lw->lw_thread->t_id; + /* NOTE: after lw_curr_lock is changed, lw may no longer be referenced + * by this function!!! + */ + if (!rl_lock_row(group, lw, &lw->lw_thread->st_lock_list, &result)) { + /* We transfer the error to the other thread! */ + XTThreadPtr self = xt_get_self(); + + result = XT_LOCK_ERR; + memcpy(&lw->lw_thread->t_exception, &self->t_exception, sizeof(XTExceptionRec)); + lw->lw_curr_lock = XT_LOCK_ERR; + } + if (result == XT_NO_LOCK || result == XT_LOCK_ERR) { + /* Remove from the wait queue: */ + if (lw_next) + lw_next->lw_prev = lw_prev; + if (lw_prev) + lw_prev->lw_next = lw_next; + if (group->lg_wait_queue == lw) + group->lg_wait_queue = lw_next; + if (group->lg_wait_queue_end == lw) + group->lg_wait_queue_end = lw_prev; + if (result == XT_NO_LOCK) { + /* Add to the thread list: */ + if (thread->st_thread_list_count == thread->st_thread_list_size) { + if (!xt_realloc_ns((void **) &thread->st_thread_list, (thread->st_thread_list_size+1) * sizeof(xtThreadID))) { + xt_xn_wakeup_thread(lw_thd_id); + goto done; + } + thread->st_thread_list_size++; + } + thread->st_thread_list[thread->st_thread_list_count] = lw_thd_id; + thread->st_thread_list_count++; + done:; + } + } + lw = lw_next; + } +} + +void XTRowLocks::xt_cancel_temp_lock(XTLockWaitPtr lw) +{ + XTLockGroupPtr group; + + group = &rl_groups[lw->lw_row_id % XT_ROW_LOCK_GROUP_COUNT]; + xt_spinlock_lock(&group->lg_lock); + if (lw->lw_curr_lock == XT_TEMP_LOCK || lw->lw_curr_lock == XT_PERM_LOCK) { + /* In case of XT_LOCK_ERR or XT_NO_LOCK, the lw structure will + * no longer be on the wait queue. + */ + XTLockWaitPtr lw_next, lw_prev; + + lw_next = lw->lw_next; + lw_prev = lw->lw_prev; + + /* Remove from the wait queue: */ + if (lw_next) + lw_next->lw_prev = lw_prev; + if (lw_prev) + lw_prev->lw_next = lw_next; + if (group->lg_wait_queue == lw) + group->lg_wait_queue = lw_next; + if (group->lg_wait_queue_end == lw) + group->lg_wait_queue_end = lw_prev; + } + xt_spinlock_unlock(&group->lg_lock); +} + +//#define QUEUE_ORDER_FIFO + +/* Try to lock a row. + * This function returns: + * XT_NO_LOCK on success. + * XT_TEMP_LOCK if there is a temporary lock on the row. + * XT_PERM_LOCK if there is a permanent lock in the row. + * XT_FAILED an error occured. + * + * If there is a lock on this row, the transaction ID of the + * locker is also returned. + * + * The caller must wait if the row is locked. If the lock is + * permanent, then the caller must wait for the transaction to + * terminate. If the lock is temporary, then the caller must + * wait for the transaction to signal that the lock has been + * released. + */ +xtBool XTRowLocks::xt_set_temp_lock(XTOpenTablePtr ot, XTLockWaitPtr lw, XTRowLockListPtr lock_list) +{ + XTLockGroupPtr group; + int result; + + if (ot->ot_temp_row_lock) { + /* Check if we don't already have this temp lock: */ + if (ot->ot_temp_row_lock == lw->lw_row_id) { + lw->lw_curr_lock = XT_NO_LOCK; + return OK; + } + + xt_make_lock_permanent(ot, lock_list); + } + + /* Add a temporary lock. */ + group = &rl_groups[lw->lw_row_id % XT_ROW_LOCK_GROUP_COUNT]; + xt_spinlock_lock(&group->lg_lock); + + if (!rl_lock_row(group, lw, lock_list, &result)) { + xt_spinlock_unlock(&group->lg_lock); + return FAILED; + } + + if (result != XT_NO_LOCK) { + /* Add the thread to the end of the thread queue: */ +#ifdef QUEUE_ORDER_FIFO + if (group->lg_wait_queue_end) { + group->lg_wait_queue_end->lw_next = lw; + lw->lw_prev = group->lg_wait_queue_end; + } + else { + group->lg_wait_queue = lw; + lw->lw_prev = NULL; + } + lw->lw_next = NULL; + group->lg_wait_queue_end = lw; +#else + XTLockWaitPtr pos = group->lg_wait_queue_end; + xtXactID xn_id = ot->ot_thread->st_xact_data->xd_start_xn_id; + + while (pos) { + if (pos->lw_thread->st_xact_data->xd_start_xn_id < xn_id) + break; + pos = pos->lw_prev; + } + if (pos) { + lw->lw_prev = pos; + lw->lw_next = pos->lw_next; + if (pos->lw_next) + pos->lw_next->lw_prev = lw; + else + group->lg_wait_queue_end = lw; + pos->lw_next = lw; + } + else { + /* Front of the queue: */ + lw->lw_prev = NULL; + lw->lw_next = group->lg_wait_queue; + if (group->lg_wait_queue) + group->lg_wait_queue->lw_prev = lw; + else + group->lg_wait_queue_end = lw; + group->lg_wait_queue = lw; + } +#endif + } + + xt_spinlock_unlock(&group->lg_lock); + return OK; +} + +/* + * Remove a temporary lock. + * + * If updated is set to TRUE this means that the row was update. + * This means that any thread waiting on the temporary lock will + * also have to wait for the transaction to quit before + * continuing. + * + * If the thread were to continue it would just hang again because + * it will discover that the transaction has updated the row. + * + * So the 'updated' flag is an optimisation which prevents the + * thread from making an unncessary retry. + */ +void XTRowLocks::xt_remove_temp_lock(XTOpenTablePtr ot, xtBool updated) +{ + xtRowID row_id; + XTLockGroupPtr group; + XTLockItemPtr item; + size_t index; + xtBool lock_granted = FALSE; + xtThreadID locking_thread_id = 0; + + if (!(row_id = ot->ot_temp_row_lock)) + return; + + group = &rl_groups[row_id % XT_ROW_LOCK_GROUP_COUNT]; + xt_spinlock_lock(&group->lg_lock); +#ifdef CHECK_ROWLOCK_GROUP_CONSISTENCY + check_rowlock_group(group); +#endif + +#ifdef XT_TRACE_LOCKS + xt_ttracef(xt_get_self(), "remove temp lock %d\n", (int) row_id); +#endif + item = (XTLockItemPtr) xt_bsearch(NULL, &row_id, group->lg_list, group->lg_list_in_use, sizeof(XTLockItemRec), &index, NULL, xlock_cmp_row_ids); + if (item) { + /* Item exists. */ + if (item->li_thread_id == ot->ot_thread->t_id && + item->li_count == XT_TEMP_LOCK_BYTES) { + XTLockWaitPtr lw; + + /* First check if there is some thread waiting to take over this lock: */ + lw = group->lg_wait_queue; + while (lw) { + if (lw->lw_row_id == row_id) { + lock_granted = TRUE; + break; + } + lw = lw->lw_next; + } + + if (lock_granted) { + /* Grant the lock just released... */ + XTLockWaitPtr lw_next, lw_prev; + xtXactID locking_xact_id; + + /* Store this info, lw will soon be untouchable! */ + lw_next = lw->lw_next; + lw_prev = lw->lw_prev; + locking_xact_id = lw->lw_thread->st_xact_data->xd_start_xn_id; + locking_thread_id = lw->lw_thread->t_id; + + /* Lock has moved from one thread to the next. + * change the thread holding this lock: + */ + item->li_thread_id = locking_thread_id; + + /* Remove from the wait queue: */ + if (lw_next) + lw_next->lw_prev = lw_prev; + if (lw_prev) + lw_prev->lw_next = lw_next; + if (group->lg_wait_queue == lw) + group->lg_wait_queue = lw_next; + if (group->lg_wait_queue_end == lw) + group->lg_wait_queue_end = lw_prev; + + /* If the thread that release the lock updated the + * row then we will have to wait for the transaction + * to terminate: + */ + if (updated) { + lw->lw_row_updated = TRUE; + lw->lw_updating_xn_id = ot->ot_thread->st_xact_data->xd_start_xn_id; + } + + /* The thread has the lock now: */ + lw->lw_ot->ot_temp_row_lock = row_id; + lw->lw_curr_lock = XT_NO_LOCK; + + /* Everyone after this that is waiting for the same lock is + * now waiting for a different transaction: + */ + lw = lw_next; + while (lw) { + if (lw->lw_row_id == row_id) { + lw->lw_xn_id = locking_xact_id; + lw->lw_curr_lock = XT_TEMP_LOCK; + } + lw = lw->lw_next; + } + } + else { + /* Remove the lock: */ + XT_MEMMOVE(group->lg_list, &group->lg_list[index], + &group->lg_list[index+1], (group->lg_list_in_use - index - 1) * sizeof(XTLockItemRec)); + group->lg_list_in_use--; + } + } + } +#ifdef CHECK_ROWLOCK_GROUP_CONSISTENCY + check_rowlock_group(group); +#endif + xt_spinlock_unlock(&group->lg_lock); + + ot->ot_temp_row_lock = 0; + if (lock_granted) + xt_xn_wakeup_thread(locking_thread_id); +} + +xtBool XTRowLocks::xt_make_lock_permanent(XTOpenTablePtr ot, XTRowLockListPtr lock_list) +{ + xtRowID row_id; + XTLockGroupPtr group; + XTLockItemPtr item; + size_t index; + + if (!(row_id = ot->ot_temp_row_lock)) + return OK; + +#ifdef XT_TRACE_LOCKS + xt_ttracef(xt_get_self(), "make lock perm %d\n", (int) ot->ot_temp_row_lock); +#endif + + /* Add to the lock list: */ + XTPermRowLockPtr locks = (XTPermRowLockPtr) lock_list->bl_data; + for (unsigned i=0; i<lock_list->bl_count; i++) { +#ifdef XT_USE_TABLE_REF + if (locks->pr_table == ot->ot_table) { +#else + if (locks->pr_tab_id == ot->ot_table->tab_id) { +#endif + locks->pr_group[row_id % XT_ROW_LOCK_GROUP_COUNT] = 1; + goto done; + } + locks++; + } + + /* Add new to lock list: */ + { + XTPermRowLockRec perm_lock; + +#ifdef XT_USE_TABLE_REF + perm_lock.pr_table = ot->ot_table; + xt_heap_reference(NULL, perm_lock.pr_table); +#else + perm_lock.pr_tab_id = ot->ot_table->tab_id; +#endif + memset(perm_lock.pr_group, 0, XT_ROW_LOCK_GROUP_COUNT); + perm_lock.pr_group[row_id % XT_ROW_LOCK_GROUP_COUNT] = 1; + if (!xt_bl_append(NULL, lock_list, &perm_lock)) { + xt_remove_temp_lock(ot, FALSE); + return FAILED; + } + } + + done: + group = &rl_groups[row_id % XT_ROW_LOCK_GROUP_COUNT]; + xt_spinlock_lock(&group->lg_lock); + + item = (XTLockItemPtr) xt_bsearch(NULL, &row_id, group->lg_list, group->lg_list_in_use, sizeof(XTLockItemRec), &index, NULL, xlock_cmp_row_ids); + ASSERT_NS(item); +#ifdef CHECK_ROWLOCK_GROUP_CONSISTENCY + check_rowlock_group(group); +#endif + if (item) { + /* Lock exists (it should!). */ + if (item->li_thread_id == ot->ot_thread->t_id && + item->li_count == XT_TEMP_LOCK_BYTES) { + if (index > 0 && + group->lg_list[index-1].li_thread_id == ot->ot_thread->t_id && + group->lg_list[index-1].li_count < XT_TEMP_LOCK_BYTES-2 && + group->lg_list[index-1].li_row_id == row_id - (XT_ROW_LOCK_GROUP_COUNT * group->lg_list[index-1].li_count)) { + group->lg_list[index-1].li_count++; + /* Combine with the left: */ + if (index + 1 < group->lg_list_in_use && + group->lg_list[index+1].li_thread_id == ot->ot_thread->t_id && + group->lg_list[index+1].li_count != XT_TEMP_LOCK_BYTES && + group->lg_list[index+1].li_row_id == row_id + XT_ROW_LOCK_GROUP_COUNT) { + /* And combine with the right */ + u_int left = group->lg_list[index-1].li_count + group->lg_list[index+1].li_count; + u_int right; + + if (left > XT_TEMP_LOCK_BYTES-1) { + right = left - (XT_TEMP_LOCK_BYTES-1); + left = XT_TEMP_LOCK_BYTES-1; + } + else + right = 0; + + group->lg_list[index-1].li_count = left; + if (right) { + /* There is something left over on the right: */ + group->lg_list[index+1].li_count = right; + group->lg_list[index+1].li_row_id = group->lg_list[index-1].li_row_id + left * XT_ROW_LOCK_GROUP_COUNT; + XT_MEMMOVE(group->lg_list, &group->lg_list[index], + &group->lg_list[index+1], (group->lg_list_in_use - index - 1) * sizeof(XTLockItemRec)); + group->lg_list_in_use--; + } + else { + XT_MEMMOVE(group->lg_list, &group->lg_list[index], + &group->lg_list[index+2], (group->lg_list_in_use - index - 2) * sizeof(XTLockItemRec)); + group->lg_list_in_use -= 2; + } + } + else { + XT_MEMMOVE(group->lg_list, &group->lg_list[index], + &group->lg_list[index+1], (group->lg_list_in_use - index - 1) * sizeof(XTLockItemRec)); + group->lg_list_in_use--; + } + } + else if (index + 1 < group->lg_list_in_use && + group->lg_list[index+1].li_thread_id == ot->ot_thread->t_id && + group->lg_list[index+1].li_count < XT_TEMP_LOCK_BYTES-2 && + group->lg_list[index+1].li_row_id == row_id + XT_ROW_LOCK_GROUP_COUNT) { + /* Combine with the right: */ + group->lg_list[index+1].li_count++; + group->lg_list[index+1].li_row_id = row_id; + XT_MEMMOVE(group->lg_list, &group->lg_list[index], + &group->lg_list[index+1], (group->lg_list_in_use - index - 1) * sizeof(XTLockItemRec)); + group->lg_list_in_use--; + } + else + group->lg_list[index].li_count = 1; + } + } +#ifdef CHECK_ROWLOCK_GROUP_CONSISTENCY + check_rowlock_group(group); +#endif + xt_spinlock_unlock(&group->lg_lock); + + ot->ot_temp_row_lock = 0; + return OK; +} + +xtBool xt_init_row_locks(XTRowLocksPtr rl) +{ + for (int i=0; i<XT_ROW_LOCK_GROUP_COUNT; i++) { + xt_spinlock_init_with_autoname(NULL, &rl->rl_groups[i].lg_lock); + rl->rl_groups[i].lg_wait_queue = NULL; + rl->rl_groups[i].lg_list_size = 0; + rl->rl_groups[i].lg_list_in_use = 0; + rl->rl_groups[i].lg_list = NULL; + } + return OK; +} + +void xt_exit_row_locks(XTRowLocksPtr rl __attribute__((unused))) +{ + for (int i=0; i<XT_ROW_LOCK_GROUP_COUNT; i++) { + xt_spinlock_free(NULL, &rl->rl_groups[i].lg_lock); + rl->rl_groups[i].lg_wait_queue = NULL; + rl->rl_groups[i].lg_list_size = 0; + rl->rl_groups[i].lg_list_in_use = 0; + if (rl->rl_groups[i].lg_list) { + xt_free_ns(rl->rl_groups[i].lg_list); + rl->rl_groups[i].lg_list = NULL; + } + } +} + +/* + * ----------------------------------------------------------------------- + * ROW LOCKS, HASH BASED + */ +#else // XT_USE_LIST_BASED_ROW_LOCKS + +void XTRowLockList::old_xt_remove_all_locks(struct XTDatabase *db, xtThreadID thd_id) +{ +#ifdef XT_TRACE_LOCKS + xt_ttracef(xt_get_self(), "remove all locks\n"); +#endif + if (!bl_count) + return; + + int pgroup; + xtTableID ptab_id; + XTPermRowLockPtr plock; + XTOpenTablePtr pot = NULL; + + plock = (XTPermRowLockPtr) &bl_data[bl_count * bl_item_size]; + for (u_int i=0; i<bl_count; i++) { + plock--; + pgroup = plock->pr_group; + ptab_id = plock->pr_tab_id; + if (pot) { + if (pot->ot_table->tab_id == ptab_id) + goto remove_lock; + xt_db_return_table_to_pool_ns(pot); + pot = NULL; + } + + if (!xt_db_open_pool_table_ns(&pot, db, ptab_id)) { + /* Should not happen, but just in case, we just don't + * remove the lock. We will probably end up with a deadlock + * somewhere. + */ + xt_log_and_clear_exception_ns(); + goto skip_remove_lock; + } + if (!pot) + /* Can happen of the table has been dropped: */ + goto skip_remove_lock; + + remove_lock: +#ifdef XT_TRACE_LOCKS + xt_ttracef(xt_get_self(), "remove lock group=%d\n", pgroup); +#endif + pot->ot_table->tab_locks.tab_row_locks[pgroup] = NULL; + pot->ot_table->tab_locks.tab_lock_perm[pgroup] = 0; + skip_remove_lock:; + } + bl_count = 0; + + if (pot) + xt_db_return_table_to_pool_ns(pot); +} + +/* Try to lock a row. + * This function returns: + * XT_NO_LOCK on success. + * XT_TEMP_LOCK if there is a temporary lock on the row. + * XT_PERM_LOCK if there is a permanent lock in the row. + * + * If there is a lock on this row, the transaction ID of the + * locker is also returned. + * + * The caller must wait if the row is locked. If the lock is + * permanent, then the caller must wait for the transaction to + * terminate. If the lock is temporary, then the caller must + * wait for the transaction to signal that the lock has been + * released. + */ +int XTRowLocks::old_xt_set_temp_lock(XTOpenTablePtr ot, xtRowID row, xtXactID *xn_id, XTRowLockListPtr lock_list) +{ + int group; + XTXactDataPtr xact, my_xact; + + if (ot->ot_temp_row_lock) { + /* Check if we don't already have this temp lock: */ + if (ot->ot_temp_row_lock == row) { + gl->lw_curr_lock = XT_NO_LOCK; + return XT_NO_LOCK; + } + + xt_make_lock_permanent(ot, lock_list); + } + + my_xact = ot->ot_thread->st_xact_data; + group = row % XT_ROW_LOCK_COUNT; + if ((xact = tab_row_locks[group])) { + if (xact == my_xact) + return XT_NO_LOCK; + *xn_id = xact->xd_start_xn_id; + return tab_lock_perm[group] ? XT_PERM_LOCK : XT_TEMP_LOCK; + } + + tab_row_locks[row % XT_ROW_LOCK_COUNT] = my_xact; + +#ifdef XT_TRACE_LOCKS + xt_ttracef(xt_get_self(), "set temp lock %d group=%d for %s\n", (int) row, (int) row % XT_ROW_LOCK_COUNT, ot->ot_thread->t_name); +#endif + ot->ot_temp_row_lock = row; + return XT_NO_LOCK; +} + +/* Just check if there is a lock on the row. + * This function returns: + * XT_NO_LOCK if there is no lock. + * XT_TEMP_LOCK if there is a temporary lock on the row. + * XT_PERM_LOCK if a lock is a permanent lock in the row. + */ +int XTRowLocks::old_xt_is_locked(struct XTOpenTable *ot, xtRowID row, xtXactID *xn_id) +{ + int group; + XTXactDataPtr xact; + + group = row % XT_ROW_LOCK_COUNT; + if ((xact = tab_row_locks[group])) { + if (xact == ot->ot_thread->st_xact_data) + return XT_NO_LOCK; + *xn_id = xact->xd_start_xn_id; + if (tab_lock_perm[group]) + return XT_PERM_LOCK; + return XT_TEMP_LOCK; + } + return XT_NO_LOCK; +} + +void XTRowLocks::old_xt_remove_temp_lock(XTOpenTablePtr ot) +{ + int group; + XTXactDataPtr xact, my_xact; + + if (!ot->ot_temp_row_lock) + return; + + my_xact = ot->ot_thread->st_xact_data; + group = ot->ot_temp_row_lock % XT_ROW_LOCK_COUNT; +#ifdef XT_TRACE_LOCKS + xt_ttracef(xt_get_self(), "remove temp lock %d group=%d\n", (int) ot->ot_temp_row_lock, (int) ot->ot_temp_row_lock % XT_ROW_LOCK_COUNT); +#endif + ot->ot_temp_row_lock = 0; + if ((xact = tab_row_locks[group])) { + if (xact == my_xact) + tab_row_locks[group] = NULL; + } + + if (ot->ot_table->tab_db->db_xn_wait_count) + xt_xn_wakeup_transactions(ot->ot_table->tab_db, ot->ot_thread); +} + +xtBool XTRowLocks::old_xt_make_lock_permanent(XTOpenTablePtr ot, XTRowLockListPtr lock_list) +{ + int group; + + if (!ot->ot_temp_row_lock) + return OK; + +#ifdef XT_TRACE_LOCKS + xt_ttracef(xt_get_self(), "make lock perm %d group=%d\n", (int) ot->ot_temp_row_lock, (int) ot->ot_temp_row_lock % XT_ROW_LOCK_COUNT); +#endif + /* Check if the lock is already permanent: */ + group = ot->ot_temp_row_lock % XT_ROW_LOCK_COUNT; + if (!tab_lock_perm[group]) { + XTPermRowLockRec plock; + + plock.pr_tab_id = ot->ot_table->tab_id; + plock.pr_group = group; + if (!xt_bl_append(NULL, lock_list, &plock)) { + xt_remove_temp_lock(ot); + return FAILED; + } + tab_lock_perm[group] = 1; + } + + ot->ot_temp_row_lock = 0; + return OK; +} + +/* Release this lock, and all locks gained after this lock + * on this table. + * + * The locks are only released temporarily. The will be regained + * below using regain locks. + * + * Returns: + * XT_NO_LOCK if no lock is released. + * XT_PERM_LOCK if a lock is released. + * + * Note that only permanent locks can be released in this way. + * So if the thread has a temporary lock, it will first be made + * permanent. + * + * {RELEASING-LOCKS} + * The idea of the releasing locks comes from the fact that each + * lock, locks a group of records. + * So if T1 has a lock (e.g. when doing SELECT FOR UPDATE), + * and then encounters an updated record x + * from T2, and it must wait for T2, it firsts releases the + * lock, just in case T2 tries to gain a lock on another + * record y in the same group, which will cause it to + * wait on T1. + * + * However, there are several problems with releasing + * locks. + * - It can cause a "live-lock", where another transation + * keeps getting in before. + * - It may not solve the problem in all cases because + * the SELECT FOR UPDATE has locked other record groups + * before it encountered record x. + * - Further problems occur when locks are granted by + * callback: + * T1 waits for T2, because it has a lock on record x + * T2 releases the lock because it must wait for T3 + * T1 is granted the lock (but does not know about this yet) + * T2 tries to regain lock (after T3 quits) and + * must wait for T1 - DEADLOCK + * + * In general, it does not make sense to release locks + * when it can be granted again by a callback. + * + * TODO: 2 possible solutions: + * - Do not lock groups, lock rows. + * UPDATE INTENSION ROW LOCK + * - Use multiple lock types: + * UPDATE INTENSION LOCK (required first) + * SHARED UPDATE LOCK (used by INSERT or DELETE) + * EXCLUSIVE UPDATE LOCK (used by SELECT FOR UPDATE) + * + * Temporary solution. Do not release any locks. +int XTRowLocks::xt_release_locks(struct XTOpenTable *ot, xtRowID row, XTRowLockListPtr lock_list) + */ + +/* + * Regain a lock previously held. This function regains locks + * released by xt_release_locks(). + * + * It will return lock_type and xn_id if the row is locked, and therefore + * regain cannot continue. In this case, the caller must wait. + * It returns XT_NO_LOCK if there are no more locks to be regained. + * + * Locks are always regained in the order in which they were originally + * taken. +xtBool XTRowLocks::xt_regain_locks(struct XTOpenTable *ot, int *lock_type, xtXactID *xn_id, XTRowLockListPtr lock_list) + */ + +xtBool old_xt_init_row_locks(XTRowLocksPtr rl) +{ + memset(rl->tab_lock_perm, 0, XT_ROW_LOCK_COUNT); + memset(rl->tab_row_locks, 0, XT_ROW_LOCK_COUNT * sizeof(XTXactDataPtr)); + return OK; +} + +void old_xt_exit_row_locks(XTRowLocksPtr rl __attribute__((unused))) +{ +} + +#endif // XT_USE_LIST_BASED_ROW_LOCKS + +xtPublic xtBool xt_init_row_lock_list(XTRowLockListPtr lock_list) +{ + lock_list->bl_item_size = sizeof(XTPermRowLockRec); + lock_list->bl_size = 0; + lock_list->bl_count = 0; + lock_list->bl_data = NULL; + return OK; +} + +xtPublic void xt_exit_row_lock_list(XTRowLockListPtr lock_list) +{ + xt_bl_set_size(NULL, lock_list, 0); +} + +/* + * ----------------------------------------------------------------------- + * SPECIAL EXCLUSIVE/SHARED (XS) LOCK + */ + +#define XT_GET1(x) *(x) +#define XT_SET4(x, y) xt_atomic_set4(x, y) +#define XT_GET4(x) xt_atomic_get4(x) + +#ifdef XT_THREAD_LOCK_INFO +xtPublic void xt_rwmutex_init(struct XTThread *self, XTRWMutexPtr xsl, const char *n) +#else +xtPublic void xt_rwmutex_init(XTThreadPtr self, XTRWMutexPtr xsl) +#endif +{ +#ifdef DEBUG + xsl->xs_lock_thread = 0; + xsl->xs_inited = 12345; +#endif + xt_init_mutex_with_autoname(self, &xsl->xs_lock); + xt_init_cond(self, &xsl->xs_cond); + XT_SET4(&xsl->xs_state, 0); + xsl->xs_xlocker = 0; + /* Must be aligned! */ + ASSERT(xt_thr_maximum_threads == xt_align_size(xt_thr_maximum_threads, XT_XS_LOCK_ALIGN)); + xsl->x.xs_rlock = (xtWord1 *) xt_calloc(self, xt_thr_maximum_threads); +#ifdef XT_THREAD_LOCK_INFO + xsl->xs_name = n; + xt_thread_lock_info_init(&xsl->xs_lock_info, xsl); +#endif +} + +xtPublic void xt_rwmutex_free(XTThreadPtr self, XTRWMutexPtr xsl) +{ +#ifdef DEBUG + ASSERT(!xsl->xs_lock_thread); + ASSERT(xsl->xs_inited == 12345); + xsl->xs_inited = 0; +#endif + if (xsl->x.xs_rlock) + xt_free(self, (void *) xsl->x.xs_rlock); + xt_free_mutex(&xsl->xs_lock); + xt_free_cond(&xsl->xs_cond); +#ifdef XT_THREAD_LOCK_INFO + xt_thread_lock_info_free(&xsl->xs_lock_info); +#endif +} + +xtPublic xtBool xt_rwmutex_xlock(XTRWMutexPtr xsl, xtThreadID thd_id) +{ +#ifdef DEBUG + ASSERT_NS(xsl->xs_inited == 12345); +#endif + ASSERT_NS(xt_get_self()->t_id == thd_id); + xt_lock_mutex_ns(&xsl->xs_lock); + ASSERT_NS(xsl->x.xs_rlock[thd_id] == XT_NO_LOCK); + + /* Wait for exclusive locker: */ + while (xsl->xs_xlocker) { + if (!xt_timed_wait_cond_ns(&xsl->xs_cond, &xsl->xs_lock, 10000)) { + xt_unlock_mutex_ns(&xsl->xs_lock); + return FAILED; + } + } + + /* I am the locker (set state before locker!): */ + XT_SET4(&xsl->xs_state, 0); + xsl->xs_xlocker = thd_id; + + /* Wait for all the read lockers: */ + while (xsl->xs_state < xt_thr_current_max_threads) { + while (xsl->x.xs_rlock[xsl->xs_state]) { + /* {RACE-WR_MUTEX} + * Just in case of this, we keep the wait time down! + */ + if (!xt_timed_wait_cond_ns(&xsl->xs_cond, &xsl->xs_lock, 10)) { + XT_SET4(&xsl->xs_state, 0); + xsl->xs_xlocker = 0; + xt_unlock_mutex_ns(&xsl->xs_lock); + return FAILED; + } + } + /* State can be incremented in parallel by a reader + * thread! + */ + XT_SET4(&xsl->xs_state, xsl->xs_state + 1); + } + + /* I have waited for all: */ + XT_SET4(&xsl->xs_state, xt_thr_maximum_threads); + +#ifdef XT_THREAD_LOCK_INFO + xt_thread_lock_info_add_owner(&xsl->xs_lock_info); +#endif + + return OK; +} + +xtPublic xtBool xt_rwmutex_slock(XTRWMutexPtr xsl, xtThreadID thd_id) +{ +#ifdef DEBUG + ASSERT_NS(xsl->xs_inited == 12345); +#endif + ASSERT_NS(xt_get_self()->t_id == thd_id); + + xt_flushed_inc1(&xsl->x.xs_rlock[thd_id]); + + if (xsl->x.xs_rlock[thd_id] > 1) + return OK; + + /* Check if there could be an X locker: */ + if (xsl->xs_xlocker) { + /* There is an X locker. + * If xs_state < thd_id then the X locker will wait for me. + * So I should not wait! + */ + if (xsl->xs_state >= thd_id) { + /* If xsl->xs_state >= thd_id, then the locker has already + * checked me, and I will have to wait. + * + * Otherwise, xs_state <= thd_id, which means the + * X locker has not checked me, and will still wait for me (or + * is already waiting for me). In this case, I will have to + * take the mutex to make sure exactly how far he + * is with the checking. + */ + xt_lock_mutex_ns(&xsl->xs_lock); + while (xsl->xs_state > thd_id && xsl->xs_xlocker) { + if (!xt_timed_wait_cond_ns(&xsl->xs_cond, &xsl->xs_lock, 10000)) { + xt_unlock_mutex_ns(&xsl->xs_lock); + xsl->x.xs_rlock[thd_id]--; + return FAILED; + } + } + xt_unlock_mutex_ns(&xsl->xs_lock); + } + } + + /* There is no exclusive locker, so we have the read lock: */ + ASSERT_NS(xsl->xs_state != xt_thr_maximum_threads); +#ifdef XT_THREAD_LOCK_INFO + xt_thread_lock_info_add_owner(&xsl->xs_lock_info); +#endif + return OK; +} + +xtPublic xtBool xt_rwmutex_unlock(XTRWMutexPtr xsl, xtThreadID thd_id) +{ +#ifdef DEBUG + ASSERT_NS(xsl->xs_inited == 12345); +#endif + ASSERT_NS(xt_get_self()->t_id == thd_id); + if (xsl->xs_xlocker == thd_id) { + /* I have an X lock. */ + ASSERT_NS(xsl->x.xs_rlock[thd_id] == XT_NO_LOCK); + ASSERT_NS(xsl->xs_state == xt_thr_maximum_threads); + XT_SET4(&xsl->xs_state, 0); + xsl->xs_xlocker = 0; + xt_unlock_mutex_ns(&xsl->xs_lock); + /* Wake up any other X or shared lockers: */ + if (!xt_broadcast_cond_ns(&xsl->xs_cond)) + return FAILED; + } + else { + /* I have a shared lock: */ + ASSERT_NS(xsl->x.xs_rlock[thd_id] > 0); + ASSERT_NS(xsl->xs_state != xt_thr_maximum_threads); /* TODO: PMC - HOW can this fail?! - but it does? */ + if (xsl->x.xs_rlock[thd_id] > 1) + xsl->x.xs_rlock[thd_id]--; + else { + /* {RACE-WR_MUTEX}. + * A BUG FIX: + * + * Previously I was checking "xsl->xs_xlocker" after, + * descrementing the READ lock. + * + * This resulted in a race condition that could cause the + * unlocking reader to hang in xt_lock_mutex_ns(). + * This was because the X locker, grabbed the mutex (xs_lock) + * but did not wait for the reader. + * + * The result was that the reader had to wait in UNLOCK + * until the X locker did an unlock! + * + * This only became obvious when it caused a deadlock (because + * the reader was waiting for the locker, which it should not + * have been, of course). + */ + if (xsl->xs_xlocker) { + xt_lock_mutex_ns(&xsl->xs_lock); + if (xsl->xs_xlocker && xsl->xs_state == thd_id) { + /* If the X locker is waiting for me, + * then allow him to continue. + */ + if (!xt_broadcast_cond_ns(&xsl->xs_cond)) { + xt_unlock_mutex_ns(&xsl->xs_lock); + return FAILED; + } + } + xt_flushed_dec1(&xsl->x.xs_rlock[thd_id]); + xt_unlock_mutex_ns(&xsl->xs_lock); + } + else + /* {RACE-WR_MUTEX} + * There is a race condition between the check above, and the + * the decrement here. + * + * However, if I check xsl->xs_xlocker afterwards, and then + * try to get the lock xs_lock, I could hand for the duration + * of the X lock. + */ + xt_flushed_dec1(&xsl->x.xs_rlock[thd_id]); + } + } +#ifdef XT_THREAD_LOCK_INFO + xt_thread_lock_info_release_owner(&xsl->xs_lock_info); +#endif + return OK; +} + +/* + * ----------------------------------------------------------------------- + * SPIN LOCK + */ + +#ifdef XT_THREAD_LOCK_INFO +xtPublic void xt_spinlock_init(XTThreadPtr self __attribute__((unused)), XTSpinLockPtr spl, const char *n) +#else +xtPublic void xt_spinlock_init(XTThreadPtr self __attribute__((unused)), XTSpinLockPtr spl) +#endif +{ + spl->spl_lock = 0; +#ifdef XT_SPL_DEFAULT + xt_init_mutex(self, &spl->spl_mutex); +#endif +#ifdef DEBUG + spl->spl_locker = 0; +#endif +#ifdef XT_THREAD_LOCK_INFO + spl->spl_name = n; + xt_thread_lock_info_init(&spl->spl_lock_info, spl); +#endif +} + +xtPublic void xt_spinlock_free(XTThreadPtr self __attribute__((unused)), XTSpinLockPtr spl __attribute__((unused))) +{ +#ifdef XT_SPL_DEFAULT + xt_free_mutex(&spl->spl_mutex); +#endif +#ifdef XT_THREAD_LOCK_INFO + xt_thread_lock_info_free(&spl->spl_lock_info); +#endif +} + +xtPublic xtBool xt_spinlock_spin(XTSpinLockPtr spl) +{ + volatile xtWord4 *lck = &spl->spl_lock; + + for (;;) { + for (int i=0; i<10; i++) { + /* Check the lock variable: */ + if (!*lck) { + /* Try to get the lock: */ + if (!xt_spinlock_set(spl)) + return OK; + } + } + + /* Go to "sleep" */ + xt_critical_wait(); + } + + return OK; +} + +#ifdef DEBUG +xtPublic void xt_spinlock_set_thread(XTSpinLockPtr spl) +{ + spl->spl_locker = xt_get_self(); +} +#endif + +/* + * ----------------------------------------------------------------------- + * FAST LOCK + */ + +#ifdef XT_THREAD_LOCK_INFO +xtPublic void xt_fastlock_init(XTThreadPtr self, XTFastLockPtr fal, const char *n) +#else +xtPublic void xt_fastlock_init(XTThreadPtr self, XTFastLockPtr fal) +#endif +{ + xt_spinlock_init_with_autoname(self, &fal->fal_spinlock); + xt_spinlock_init_with_autoname(self, &fal->fal_wait_lock); + for (u_int i=0; i<XT_FAST_LOCK_MAX_WAIT; i++) + fal->fal_wait_list[i] = NULL; + fal->fal_wait_count = 0; + fal->fal_wait_wakeup = 0; + fal->fal_wait_alloc = 0; +#ifdef XT_THREAD_LOCK_INFO + fal->fal_name = n; + xt_thread_lock_info_init(&fal->fal_lock_info, fal); +#endif +} + +xtPublic void xt_fastlock_free(XTThreadPtr self, XTFastLockPtr fal) +{ + xt_spinlock_free(self, &fal->fal_spinlock); + xt_spinlock_free(self, &fal->fal_wait_lock); +#ifdef XT_THREAD_LOCK_INFO + xt_thread_lock_info_free(&fal->fal_lock_info); +#endif +} + +xtPublic xtBool xt_fastlock_spin(XTFastLockPtr fal, XTThreadPtr thread) +{ + volatile xtWord4 *lck = &fal->fal_spinlock.spl_lock; + + do { + for (int i=0; i<10; i++) { + /* Check the lock variable: */ + if (!*lck) { + /* Try to get the lock: */ + if (!xt_spinlock_set(&fal->fal_spinlock)) { + fal->fal_locker = thread; + return OK; + } + } + } + + for (int i=0; i<10; i++) { + xt_critical_wait(); + if (!*lck) { + /* Try to get the lock: */ + if (!xt_spinlock_set(&fal->fal_spinlock)) { + fal->fal_locker = thread; + return OK; + } + } + } + + /* Wait for a wakeup */ + xt_spinlock_lock(&fal->fal_wait_lock); + if (fal->fal_wait_count == XT_FAST_LOCK_MAX_WAIT) { + xt_register_ulxterr(XT_REG_CONTEXT, XT_ERR_TOO_MANY_WAITERS, (u_long) XT_FAST_LOCK_MAX_WAIT+1); + xt_spinlock_unlock(&fal->fal_wait_lock); + return FAILED; + } + while (fal->fal_wait_list[fal->fal_wait_alloc]) + fal->fal_wait_alloc = (fal->fal_wait_alloc + 1) % XT_FAST_LOCK_MAX_WAIT; + fal->fal_wait_list[fal->fal_wait_alloc] = thread; + fal->fal_wait_alloc = (fal->fal_wait_alloc + 1) % XT_FAST_LOCK_MAX_WAIT; + fal->fal_wait_count++; + xt_lock_thread(thread); + xt_spinlock_unlock(&fal->fal_wait_lock); + if (!xt_wait_thread(thread)) { + xt_unlock_thread(thread); + if (fal->fal_locker == thread) + xt_fastlock_unlock(fal, thread); + return FAILED; + } + xt_unlock_thread(thread); + } while (fal->fal_locker != thread); + return OK; +} + +/* Wake up one of the waiters. */ +xtPublic void xt_fastlock_wakeup(XTFastLockPtr fal) +{ + xt_spinlock_lock(&fal->fal_wait_lock); + if (fal->fal_wait_count) { + XTThreadPtr thread; + + /* Find a waiting thread, and give it the exclusive lock: */ + while (!fal->fal_wait_list[fal->fal_wait_wakeup]) + fal->fal_wait_wakeup = (fal->fal_wait_wakeup + 1) % XT_FAST_LOCK_MAX_WAIT; + thread = fal->fal_wait_list[fal->fal_wait_wakeup]; + fal->fal_wait_list[fal->fal_wait_wakeup] = NULL; + fal->fal_wait_wakeup = (fal->fal_wait_wakeup + 1) % XT_FAST_LOCK_MAX_WAIT; + fal->fal_wait_count--; + fal->fal_locker = thread; + + xt_lock_thread(thread); + xt_spinlock_unlock(&fal->fal_wait_lock); + xt_signal_thread(thread); + xt_unlock_thread(thread); + } + else { + xt_spinlock_unlock(&fal->fal_wait_lock); + fal->fal_locker = NULL; + xt_spinlock_reset(&fal->fal_spinlock); + } +} + +/* + * ----------------------------------------------------------------------- + * READ/WRITE SPIN LOCK + */ + +#ifdef XT_THREAD_LOCK_INFO +xtPublic void xt_spinrwlock_init(struct XTThread *self, XTSpinRWLockPtr srw, const char *name) +#else +xtPublic void xt_spinrwlock_init(struct XTThread *self, XTSpinRWLockPtr srw) +#endif +{ + xt_spinlock_init_with_autoname(self, &srw->srw_lock); + xt_spinlock_init_with_autoname(self, &srw->srw_state_lock); + srw->srw_state = 0; + srw->srw_xlocker = 0; + /* Must be aligned! */ + ASSERT(xt_thr_maximum_threads == xt_align_size(xt_thr_maximum_threads, XT_XS_LOCK_ALIGN)); + srw->x.srw_rlock = (xtWord1 *) xt_calloc(self, xt_thr_maximum_threads); +#ifdef XT_THREAD_LOCK_INFO + srw->srw_name = name; + xt_thread_lock_info_init(&srw->srw_lock_info, srw); +#endif +} + +xtPublic void xt_spinrwlock_free(struct XTThread *self, XTSpinRWLockPtr srw) +{ + if (srw->x.srw_rlock) + xt_free(self, (void *) srw->x.srw_rlock); + xt_spinlock_free(self, &srw->srw_lock); + xt_spinlock_free(self, &srw->srw_state_lock); +#ifdef XT_THREAD_LOCK_INFO + xt_thread_lock_info_free(&srw->srw_lock_info); +#endif +} + +xtPublic xtBool xt_spinrwlock_xlock(XTSpinRWLockPtr srw, xtThreadID thd_id) +{ + xt_spinlock_lock(&srw->srw_lock); + ASSERT_NS(srw->x.srw_rlock[thd_id] == XT_NO_LOCK); + + xt_spinlock_lock(&srw->srw_state_lock); + + /* Set the state before xlocker (dirty read!) */ + srw->srw_state = 0; + + /* I am the locker: */ + srw->srw_xlocker = thd_id; + + /* Wait for all the read lockers: */ + while (srw->srw_state < xt_thr_current_max_threads) { + while (srw->x.srw_rlock[srw->srw_state]) { + xt_spinlock_unlock(&srw->srw_state_lock); + /* Wait for this reader, during this time, the reader + * himself, may increment the state. */ + xt_critical_wait(); + xt_spinlock_lock(&srw->srw_state_lock); + } + /* State can be incremented in parallel by a reader + * thread! + */ + srw->srw_state++; + } + + /* I have waited for all: */ + srw->srw_state = xt_thr_maximum_threads; + + xt_spinlock_unlock(&srw->srw_state_lock); + +#ifdef XT_THREAD_LOCK_INFO + xt_thread_lock_info_add_owner(&srw->srw_lock_info); +#endif + + return OK; +} + +xtPublic xtBool xt_spinrwlock_slock(XTSpinRWLockPtr srw, xtThreadID thd_id) +{ + ASSERT_NS(srw->x.srw_rlock[thd_id] == XT_NO_LOCK); + srw->x.srw_rlock[thd_id] = XT_WANT_LOCK; + /* Check if there could be an X locker: */ + if (srw->srw_xlocker) { + /* There is an X locker. + * If srw_state < thd_id then the X locker will wait for me. + * So I should not wait! + */ + if (srw->srw_state >= thd_id) { + /* If srw->srw_state >= thd_id, then the locker may have, or + * has already checked me, and I will have to wait. + * + * Otherwise, srw_state <= thd_id, which means the + * X locker has not checked me, and will still wait for me (or + * is already waiting for me). In this case, I will have to + * take the mutex to make sure exactly how far he + * is with the checking. + */ + xt_spinlock_lock(&srw->srw_state_lock); + while (srw->srw_state > thd_id && srw->srw_xlocker) { + xt_spinlock_unlock(&srw->srw_state_lock); + xt_critical_wait(); + xt_spinlock_lock(&srw->srw_state_lock); + } + xt_spinlock_unlock(&srw->srw_state_lock); + } + } + /* There is no exclusive locker, so we have the read lock: */ + srw->x.srw_rlock[thd_id] = XT_HAVE_LOCK; + +#ifdef XT_THREAD_LOCK_INFO + xt_thread_lock_info_add_owner(&srw->srw_lock_info); +#endif + + return OK; +} + +xtPublic xtBool xt_spinrwlock_unlock(XTSpinRWLockPtr srw, xtThreadID thd_id) +{ + if (srw->srw_xlocker == thd_id) { + /* I have an X lock. */ + ASSERT_NS(srw->srw_state == xt_thr_maximum_threads); + srw->srw_state = 0; + srw->srw_xlocker = 0; + xt_spinlock_unlock(&srw->srw_lock); + } + else { + /* I have a shared lock: */ + ASSERT_NS(srw->x.srw_rlock[thd_id] == XT_HAVE_LOCK); + ASSERT_NS(srw->srw_state != xt_thr_maximum_threads); + srw->x.srw_rlock[thd_id] = XT_NO_LOCK; + if (srw->srw_xlocker && srw->srw_state == thd_id) { + xt_spinlock_lock(&srw->srw_state_lock); + if (srw->srw_xlocker && srw->srw_state == thd_id) { + /* If the X locker is waiting for me, + * then allow him to continue. + */ + srw->srw_state = thd_id+1; + } + xt_spinlock_unlock(&srw->srw_state_lock); + } + } + +#ifdef XT_THREAD_LOCK_INFO + xt_thread_lock_info_release_owner(&srw->srw_lock_info); +#endif + + return OK; +} + +/* + * ----------------------------------------------------------------------- + * FAST READ/WRITE LOCK (BASED ON FAST MUTEX) + */ + +#ifdef XT_THREAD_LOCK_INFO +xtPublic void xt_fastrwlock_init(struct XTThread *self, XTFastRWLockPtr frw, const char *n) +#else +xtPublic void xt_fastrwlock_init(struct XTThread *self, XTFastRWLockPtr frw) +#endif +{ + xt_fastlock_init_with_autoname(self, &frw->frw_lock); + frw->frw_xlocker = NULL; + xt_spinlock_init_with_autoname(self, &frw->frw_state_lock); + frw->frw_state = 0; + frw->frw_read_waiters = 0; + /* Must be aligned! */ + ASSERT(xt_thr_maximum_threads == xt_align_size(xt_thr_maximum_threads, XT_XS_LOCK_ALIGN)); + frw->x.frw_rlock = (xtWord1 *) xt_calloc(self, xt_thr_maximum_threads); +#ifdef XT_THREAD_LOCK_INFO + frw->frw_name = n; + xt_thread_lock_info_init(&frw->frw_lock_info, frw); +#endif +} + +xtPublic void xt_fastrwlock_free(struct XTThread *self, XTFastRWLockPtr frw) +{ + if (frw->x.frw_rlock) + xt_free(self, (void *) frw->x.frw_rlock); + xt_fastlock_free(self, &frw->frw_lock); + xt_spinlock_free(self, &frw->frw_state_lock); +#ifdef XT_THREAD_LOCK_INFO + xt_thread_lock_info_free(&frw->frw_lock_info); +#endif +} + +xtPublic xtBool xt_fastrwlock_xlock(XTFastRWLockPtr frw, struct XTThread *thread) +{ + xt_fastlock_lock(&frw->frw_lock, thread); + ASSERT_NS(frw->x.frw_rlock[thread->t_id] == XT_NO_LOCK); + + xt_spinlock_lock(&frw->frw_state_lock); + + /* Set the state before xlocker (dirty read!) */ + frw->frw_state = 0; + + /* I am the locker: */ + frw->frw_xlocker = thread; + + /* Wait for all the read lockers: */ + while (frw->frw_state < xt_thr_current_max_threads) { + while (frw->x.frw_rlock[frw->frw_state]) { + xt_lock_thread(thread); + xt_spinlock_unlock(&frw->frw_state_lock); + /* Wait for this reader. We rely on the reader to free + * us from this wait! */ + if (!xt_wait_thread(thread)) { + xt_unlock_thread(thread); + frw->frw_state = 0; + frw->frw_xlocker = NULL; + xt_fastlock_unlock(&frw->frw_lock, thread); + return FAILED; + } + xt_unlock_thread(thread); + xt_spinlock_lock(&frw->frw_state_lock); + } + /* State can be incremented in parallel by a reader + * thread! + */ + frw->frw_state++; + } + + /* I have waited for all: */ + frw->frw_state = xt_thr_maximum_threads; + + xt_spinlock_unlock(&frw->frw_state_lock); + +#ifdef XT_THREAD_LOCK_INFO + xt_thread_lock_info_add_owner(&frw->frw_lock_info); +#endif + + return OK; +} + +xtPublic xtBool xt_fastrwlock_slock(XTFastRWLockPtr frw, struct XTThread *thread) +{ + xtThreadID thd_id = thread->t_id; + + ASSERT_NS(frw->x.frw_rlock[thd_id] == XT_NO_LOCK); + frw->x.frw_rlock[thd_id] = XT_WANT_LOCK; + /* Check if there could be an X locker: */ + if (frw->frw_xlocker) { + /* There is an X locker. + * If frw_state < thd_id then the X locker will wait for me. + * So I should not wait! + */ + if (frw->frw_state >= thd_id) { + /* If frw->frw_state >= thd_id, then the locker may have, or + * has already checked me, and I will have to wait. + * + * Otherwise, frw_state <= thd_id, which means the + * X locker has not checked me, and will still wait for me (or + * is already waiting for me). In this case, I will have to + * take the mutex to make sure exactly how far he + * is with the checking. + */ + xt_spinlock_lock(&frw->frw_state_lock); + frw->frw_read_waiters++; + frw->x.frw_rlock[thd_id] = XT_WAITING; + while (frw->frw_state > thd_id && frw->frw_xlocker) { + xt_lock_thread(thread); + xt_spinlock_unlock(&frw->frw_state_lock); + if (!xt_wait_thread(thread)) { + xt_unlock_thread(thread); + xt_spinlock_lock(&frw->frw_state_lock); + frw->frw_read_waiters--; + frw->x.frw_rlock[thd_id] = XT_NO_LOCK; + xt_spinlock_unlock(&frw->frw_state_lock); + return FAILED; + } + xt_unlock_thread(thread); + xt_spinlock_lock(&frw->frw_state_lock); + } + frw->x.frw_rlock[thd_id] = XT_HAVE_LOCK; + frw->frw_read_waiters--; + xt_spinlock_unlock(&frw->frw_state_lock); + return OK; + } + } + /* There is no exclusive locker, so we have the read lock: */ + frw->x.frw_rlock[thd_id] = XT_HAVE_LOCK; + +#ifdef XT_THREAD_LOCK_INFO + xt_thread_lock_info_add_owner(&frw->frw_lock_info); +#endif + + return OK; +} + +xtPublic xtBool xt_fastrwlock_unlock(XTFastRWLockPtr frw, struct XTThread *thread) +{ + xtThreadID thd_id = thread->t_id; + + if (frw->frw_xlocker == thread) { + /* I have an X lock. */ + ASSERT_NS(frw->frw_state == xt_thr_maximum_threads); + frw->frw_state = 0; + frw->frw_xlocker = NULL; + + /* Wake up all read waiters: */ + if (frw->frw_read_waiters) { + xt_spinlock_lock(&frw->frw_state_lock); + if (frw->frw_read_waiters) { + XTThreadPtr target; + + for (u_int i=0; i<xt_thr_current_max_threads; i++) { + if (frw->x.frw_rlock[i] == XT_WAITING) { + if ((target = xt_thr_array[i])) { + xt_lock_thread(target); + xt_signal_thread(target); + xt_unlock_thread(target); + } + } + } + } + xt_spinlock_unlock(&frw->frw_state_lock); + } + xt_fastlock_unlock(&frw->frw_lock, thread); + } + else { + /* I have a shared lock: */ + ASSERT_NS(frw->x.frw_rlock[thd_id] == XT_HAVE_LOCK); + ASSERT_NS(frw->frw_state != xt_thr_maximum_threads); + frw->x.frw_rlock[thd_id] = XT_NO_LOCK; + if (frw->frw_xlocker && frw->frw_state == thd_id) { + xt_spinlock_lock(&frw->frw_state_lock); + if (frw->frw_xlocker && frw->frw_state == thd_id) { + /* If the X locker is waiting for me, + * then allow him to continue. + */ + frw->frw_state = thd_id+1; + /* Wake him up: */ + xt_lock_thread(frw->frw_xlocker); + xt_signal_thread(frw->frw_xlocker); + xt_unlock_thread(frw->frw_xlocker); + } + xt_spinlock_unlock(&frw->frw_state_lock); + } + } + +#ifdef XT_THREAD_LOCK_INFO + xt_thread_lock_info_release_owner(&frw->frw_lock_info); +#endif + + return OK; +} + +/* + * ----------------------------------------------------------------------- + * ATOMIC READ/WRITE LOCK (BASED ON ATOMIC OPERATIONS) + */ + +#ifdef XT_THREAD_LOCK_INFO +xtPublic void xt_atomicrwlock_init(struct XTThread XT_UNUSED(*self), XTAtomicRWLockPtr arw, const char *n) +#else +xtPublic void xt_atomicrwlock_init(struct XTThread XT_UNUSED(*self), XTAtomicRWLockPtr arw) +#endif +{ + arw->arw_reader_count = 0; + arw->arw_xlock_set = 0; +#ifdef XT_THREAD_LOCK_INFO + arw->arw_name = n; + xt_thread_lock_info_init(&arw->arw_lock_info, arw); +#endif +} + +xtPublic void xt_atomicrwlock_free(struct XTThread *, XTAtomicRWLockPtr XT_UNUSED(arw)) +{ +#ifdef XT_THREAD_LOCK_INFO + xt_thread_lock_info_free(&arw->arw_lock_info); +#endif +} + +xtPublic xtBool xt_atomicrwlock_xlock(XTAtomicRWLockPtr arw, xtThreadID XT_UNUSED(thr_id)) +{ + register xtWord2 set; + + /* First get an exclusive lock: */ + for (;;) { + set = xt_atomic_tas2(&arw->arw_xlock_set, 1); + if (!set) + break; + xt_yield(); + } + + /* Wait for the remaining readers: */ + while (arw->arw_reader_count) + xt_yield(); + +#ifdef DEBUG + arw->arw_locker = thr_id; +#endif + +#ifdef XT_THREAD_LOCK_INFO + xt_thread_lock_info_add_owner(&arw->arw_lock_info); +#endif + return OK; +} + +xtPublic xtBool xt_atomicrwlock_slock(XTAtomicRWLockPtr arw) +{ + register xtWord2 set; + + /* First get an exclusive lock: */ + for (;;) { + set = xt_atomic_tas2(&arw->arw_xlock_set, 1); + if (!set) + break; + xt_yield(); + } + + /* Add a reader: */ + xt_atomic_inc2(&arw->arw_reader_count); + + /* Release the xlock: */ + arw->arw_xlock_set = 0; + +#ifdef XT_THREAD_LOCK_INFO + xt_thread_lock_info_add_owner(&arw->arw_lock_info); +#endif + return OK; +} + +xtPublic xtBool xt_atomicrwlock_unlock(XTAtomicRWLockPtr arw, xtBool xlocked) +{ + if (xlocked) + arw->arw_xlock_set = 0; + else + xt_atomic_dec2(&arw->arw_reader_count); + +#ifdef XT_THREAD_LOCK_INFO + xt_thread_lock_info_release_owner(&arw->arw_lock_info); +#endif +#ifdef DEBUG + arw->arw_locker = 0; +#endif + + return OK; +} + +/* + * ----------------------------------------------------------------------- + * UNIT TESTS + */ + +#define JOB_MEMCPY 1 +#define JOB_SLEEP 2 +#define JOB_PRINT 3 +#define JOB_INCREMENT 4 +#define JOB_SNOOZE 5 + +#define LOCK_PTHREAD_RW 1 +#define LOCK_PTHREAD_MUTEX 2 +#define LOCK_FASTRW 3 +#define LOCK_SPINLOCK 4 +#define LOCK_FASTLOCK 5 +#define LOCK_SPINRWLOCK 6 +#define LOCK_FASTRWLOCK 7 +#define LOCK_ATOMICRWLOCK 8 + +typedef struct XSLockTest { + u_int xs_interations; + xtBool xs_which_lock; + xtBool xs_which_job; + xtBool xs_debug_print; + XTRWMutexRec xs_lock; + xt_rwlock_type xs_plock; + XTSpinLockRec xs_spinlock; + xt_mutex_type xs_mutex; + XTFastLockRec xs_fastlock; + XTSpinRWLockRec xs_spinrwlock; + XTFastRWLockRec xs_fastrwlock; + XTAtomicRWLockRec xs_atomicrwlock; + int xs_progress; + xtWord4 xs_inc; +} XSLockTestRec, *XSLockTestPtr; + +static void lck_free_thread_data(XTThreadPtr self __attribute__((unused)), void *data __attribute__((unused))) +{ +} + +static void lck_do_job(XTThreadPtr self, int job, XSLockTestPtr data) +{ + char b1[2048], b2[2048]; + + switch (job) { + case JOB_MEMCPY: + memcpy(b1, b2, 2048); + data->xs_inc++; + break; + case JOB_SLEEP: + xt_sleep_milli_second(1); + data->xs_inc++; + break; + case JOB_PRINT: + printf("- %s got lock\n", self->t_name); + xt_sleep_milli_second(10); + data->xs_inc++; + break; + case JOB_INCREMENT: + data->xs_inc++; + break; + case JOB_SNOOZE: + xt_sleep_milli_second(10); + data->xs_inc++; + break; + } +} + +#if 0 +static void *lck_run_dumper(XTThreadPtr self) +{ + int state = 0; + + while (state != 1) { + sleep(1); + if (state == 2) { + xt_dump_trace(); + state = 0; + } + } +} +#endif + +static void *lck_run_reader(XTThreadPtr self) +{ + XSLockTestRec *data = (XSLockTestRec *) self->t_data; + + if (data->xs_debug_print) + printf("- %s start\n", self->t_name); + for (u_int i=0; i<data->xs_interations; i++) { + if (data->xs_progress && ((i+1) % data->xs_progress) == 0) + printf("- %s %d\n", self->t_name, i+1); + if (data->xs_which_lock == LOCK_PTHREAD_RW) { + xt_slock_rwlock_ns(&data->xs_plock); + lck_do_job(self, data->xs_which_job, data); + xt_unlock_rwlock_ns(&data->xs_plock); + } + else if (data->xs_which_lock == LOCK_FASTRW) { + xt_rwmutex_slock(&data->xs_lock, self->t_id); + lck_do_job(self, data->xs_which_job, data); + xt_rwmutex_unlock(&data->xs_lock, self->t_id); + } + else if (data->xs_which_lock == LOCK_SPINRWLOCK) { + xt_spinrwlock_slock(&data->xs_spinrwlock, self->t_id); + lck_do_job(self, data->xs_which_job, data); + xt_spinrwlock_unlock(&data->xs_spinrwlock, self->t_id); + } + else if (data->xs_which_lock == LOCK_FASTRWLOCK) { + xt_fastrwlock_slock(&data->xs_fastrwlock, self); + lck_do_job(self, data->xs_which_job, data); + xt_fastrwlock_unlock(&data->xs_fastrwlock, self); + } + else if (data->xs_which_lock == LOCK_ATOMICRWLOCK) { + xt_atomicrwlock_slock(&data->xs_atomicrwlock); + lck_do_job(self, data->xs_which_job, data); + xt_atomicrwlock_unlock(&data->xs_atomicrwlock, FALSE); + } + else + ASSERT(FALSE); + } + if (data->xs_debug_print) + printf("- %s stop\n", self->t_name); + return NULL; +} + +static void *lck_run_writer(XTThreadPtr self) +{ + XSLockTestRec *data = (XSLockTestRec *) self->t_data; + + if (data->xs_debug_print) + printf("- %s start\n", self->t_name); + for (u_int i=0; i<data->xs_interations; i++) { + if (data->xs_progress && ((i+1) % data->xs_progress) == 0) + printf("- %s %d\n", self->t_name, i+1); + if (data->xs_which_lock == LOCK_PTHREAD_RW) { + xt_xlock_rwlock_ns(&data->xs_plock); + lck_do_job(self, data->xs_which_job, data); + xt_unlock_rwlock_ns(&data->xs_plock); + } + else if (data->xs_which_lock == LOCK_FASTRW) { + xt_rwmutex_xlock(&data->xs_lock, self->t_id); + lck_do_job(self, data->xs_which_job, data); + xt_rwmutex_unlock(&data->xs_lock, self->t_id); + } + else if (data->xs_which_lock == LOCK_SPINRWLOCK) { + xt_spinrwlock_xlock(&data->xs_spinrwlock, self->t_id); + lck_do_job(self, data->xs_which_job, data); + xt_spinrwlock_unlock(&data->xs_spinrwlock, self->t_id); + } + else if (data->xs_which_lock == LOCK_FASTRWLOCK) { + xt_fastrwlock_xlock(&data->xs_fastrwlock, self); + lck_do_job(self, data->xs_which_job, data); + xt_fastrwlock_unlock(&data->xs_fastrwlock, self); + } + else if (data->xs_which_lock == LOCK_ATOMICRWLOCK) { + xt_atomicrwlock_xlock(&data->xs_atomicrwlock, self->t_id); + lck_do_job(self, data->xs_which_job, data); + xt_atomicrwlock_unlock(&data->xs_atomicrwlock, TRUE); + } + else + ASSERT(FALSE); + } + if (data->xs_debug_print) + printf("- %s stop\n", self->t_name); + return NULL; +} + +static void lck_print_test(XSLockTestRec *data) +{ + switch (data->xs_which_lock) { + case LOCK_PTHREAD_RW: + printf("pthread read/write"); + break; + case LOCK_PTHREAD_MUTEX: + printf("pthread mutex"); + break; + case LOCK_FASTRW: + printf("fast read/write mutex"); + break; + case LOCK_SPINLOCK: + printf("spin mutex"); + break; + case LOCK_FASTLOCK: + printf("fast mutex"); + break; + case LOCK_SPINRWLOCK: + printf("spin read/write lock"); + break; + case LOCK_FASTRWLOCK: + printf("fast read/write lock"); + break; + case LOCK_ATOMICRWLOCK: + printf("atomic read/write lock"); + break; + } + + switch (data->xs_which_job) { + case JOB_MEMCPY: + printf(" MEMCPY 2K"); + break; + case JOB_SLEEP: + printf(" SLEEP 1/1000s"); + break; + case JOB_PRINT: + printf(" PRINT DEBUG"); + break; + case JOB_INCREMENT: + printf(" INCREMENT"); + break; + case JOB_SNOOZE: + printf(" SLEEP 1/100s"); + break; + } + + printf(" %d interations", data->xs_interations); +} + +static void *lck_run_mutex_locker(XTThreadPtr self) +{ + XSLockTestRec *data = (XSLockTestRec *) self->t_data; + + if (data->xs_debug_print) + printf("- %s start\n", self->t_name); + for (u_int i=0; i<data->xs_interations; i++) { + if (data->xs_progress && ((i+1) % data->xs_progress) == 0) + printf("- %s %d\n", self->t_name, i+1); + if (data->xs_which_lock == LOCK_PTHREAD_MUTEX) { + xt_lock_mutex_ns(&data->xs_mutex); + lck_do_job(self, data->xs_which_job, data); + xt_unlock_mutex_ns(&data->xs_mutex); + } + else if (data->xs_which_lock == LOCK_SPINLOCK) { + xt_spinlock_lock(&data->xs_spinlock); + lck_do_job(self, data->xs_which_job, data); + xt_spinlock_unlock(&data->xs_spinlock); + } + else if (data->xs_which_lock == LOCK_FASTLOCK) { + xt_fastlock_lock(&data->xs_fastlock, self); + lck_do_job(self, data->xs_which_job, data); + xt_fastlock_unlock(&data->xs_fastlock, self); + } + else + ASSERT(FALSE); + } + if (data->xs_debug_print) + printf("- %s stop\n", self->t_name); + return NULL; +} + +typedef struct LockThread { + xtThreadID id; + XTThreadPtr ptr; +} LockThreadRec, *LockThreadPtr; + +static void lck_reader_writer_test(XTThreadPtr self, XSLockTestRec *data, int reader_cnt, int writer_cnt) +{ + xtWord8 start; + LockThreadPtr threads; + int thread_cnt = reader_cnt + writer_cnt; + char buffer[40]; + + //XTThreadPtr dumper = xt_create_daemon(self, "DUMPER"); + //xt_run_thread(self, dumper, lck_run_dumper); + + printf("READ/WRITE TEST: "); + lck_print_test(data); + printf(", %d readers, %d writers\n", reader_cnt, writer_cnt); + threads = (LockThreadPtr) xt_malloc(self, thread_cnt * sizeof(LockThreadRec)); + + for (int i=0; i<thread_cnt; i++) { + sprintf(buffer, "%s%d", i < reader_cnt ? "READER-" : "WRITER-", i+1); + threads[i].ptr = xt_create_daemon(self, buffer); + threads[i].id = threads[i].ptr->t_id; + xt_set_thread_data(threads[i].ptr, data, lck_free_thread_data); + } + + start = xt_trace_clock(); + for (int i=0; i<reader_cnt; i++) + xt_run_thread(self, threads[i].ptr, lck_run_reader); + for (int i=reader_cnt; i<thread_cnt; i++) + xt_run_thread(self, threads[i].ptr, lck_run_writer); + + for (int i=0; i<thread_cnt; i++) + xt_wait_for_thread(threads[i].id, TRUE); + printf("----- %d reader, %d writer time=%s\n", reader_cnt, writer_cnt, xt_trace_clock_diff(buffer, start)); + + xt_free(self, threads); + printf("TEST RESULT = %d\n", data->xs_inc); + + //xt_wait_for_thread(dumper, TRUE); +} + +static void lck_mutex_lock_test(XTThreadPtr self, XSLockTestRec *data, int thread_cnt) +{ + xtWord8 start; + LockThreadPtr threads; + char buffer[40]; + + printf("LOCK MUTEX TEST: "); + lck_print_test(data); + printf(", %d threads\n", thread_cnt); + threads = (LockThreadPtr) xt_malloc(self, thread_cnt * sizeof(LockThreadRec)); + + for (int i=0; i<thread_cnt; i++) { + sprintf(buffer, "THREAD%d", i+1); + threads[i].ptr = xt_create_daemon(self, buffer); + threads[i].id = threads[i].ptr->t_id; + xt_set_thread_data(threads[i].ptr, data, lck_free_thread_data); + } + + start = xt_trace_clock(); + for (int i=0; i<thread_cnt; i++) + xt_run_thread(self, threads[i].ptr, lck_run_mutex_locker); + + for (int i=0; i<thread_cnt; i++) + xt_wait_for_thread(threads[i].id, TRUE); + printf("----- %d threads time=%s\n", thread_cnt, xt_trace_clock_diff(buffer, start)); + + xt_free(self, threads); + printf("TEST RESULT = %d\n", data->xs_inc); +} + +xtPublic void xt_unit_test_read_write_locks(XTThreadPtr self) +{ + XSLockTestRec data; + + memset(&data, 0, sizeof(data)); + + printf("TEST: xt_unit_test_read_write_locks\n"); + xt_rwmutex_init_with_autoname(self, &data.xs_lock); + xt_init_rwlock_with_autoname(self, &data.xs_plock); + xt_spinrwlock_init_with_autoname(self, &data.xs_spinrwlock); + xt_fastrwlock_init_with_autoname(self, &data.xs_fastrwlock); + xt_atomicrwlock_init_with_autoname(self, &data.xs_atomicrwlock); + + /** + data.xs_interations = 10; + data.xs_which_lock = LOCK_FASTRW; // LOCK_PTHREAD_RW, LOCK_FASTRW, LOCK_SPINRWLOCK, LOCK_FASTRWLOCK + data.xs_which_job = JOB_PRINT; + data.xs_debug_print = TRUE; + data.xs_progress = 0; + lck_reader_writer_test(self, &data, 4, 0); + lck_reader_writer_test(self, &data, 0, 2); + lck_reader_writer_test(self, &data, 1, 1); + lck_reader_writer_test(self, &data, 4, 2); + **/ + + /** + data.xs_interations = 4000; + data.xs_which_lock = LOCK_FASTRW; // LOCK_PTHREAD_RW, LOCK_FASTRW, LOCK_SPINRWLOCK, LOCK_FASTRWLOCK + data.xs_which_job = JOB_SLEEP; + data.xs_debug_print = TRUE; + data.xs_progress = 200; + lck_reader_writer_test(self, &data, 4, 0); + lck_reader_writer_test(self, &data, 0, 2); + lck_reader_writer_test(self, &data, 1, 1); + lck_reader_writer_test(self, &data, 4, 2); + **/ + + /**/ + data.xs_interations = 1000000; + data.xs_which_lock = LOCK_FASTRW; // LOCK_PTHREAD_RW, LOCK_FASTRW, LOCK_SPINRWLOCK, LOCK_FASTRWLOCK, LOCK_ATOMICRWLOCK + data.xs_which_job = JOB_INCREMENT; + data.xs_debug_print = FALSE; + data.xs_progress = 0; + lck_reader_writer_test(self, &data, 10, 0); + /**/ + + /** + data.xs_interations = 10000; + data.xs_which_lock = LOCK_FASTRW; // LOCK_PTHREAD_RW, LOCK_FASTRW, LOCK_SPINRWLOCK, LOCK_FASTRWLOCK + data.xs_which_job = JOB_MEMCPY; + data.xs_debug_print = FALSE; + data.xs_progress = 0; + lck_reader_writer_test(self, &data, 10, 5); + **/ + + /** + data.xs_interations = 1000; + data.xs_which_lock = LOCK_FASTRW; // LOCK_PTHREAD_RW, LOCK_FASTRW, LOCK_SPINRWLOCK, LOCK_FASTRWLOCK + data.xs_which_job = JOB_SLEEP; + data.xs_debug_print = FALSE; + data.xs_progress = 0; + lck_reader_writer_test(self, &data, 10, 5); + **/ + + xt_rwmutex_free(self, &data.xs_lock); + xt_free_rwlock(&data.xs_plock); + xt_spinrwlock_free(self, &data.xs_spinrwlock); + xt_fastrwlock_free(self, &data.xs_fastrwlock); +} + +xtPublic void xt_unit_test_mutex_locks(XTThreadPtr self) +{ + XSLockTestRec data; + + memset(&data, 0, sizeof(data)); + + printf("TEST: xt_unit_test_mutex_locks\n"); + xt_spinlock_init_with_autoname(self, &data.xs_spinlock); + xt_fastlock_init_with_autoname(self, &data.xs_fastlock); + xt_init_mutex_with_autoname(self, &data.xs_mutex); + + /**/ + data.xs_interations = 10; + data.xs_which_lock = LOCK_SPINLOCK; // LOCK_SPINLOCK, LOCK_PTHREAD_MUTEX, LOCK_FASTLOCK + data.xs_which_job = JOB_PRINT; + data.xs_debug_print = TRUE; + data.xs_progress = 0; + data.xs_inc = 0; + lck_mutex_lock_test(self, &data, 2); + /**/ + + /**/ + data.xs_interations = 100000; + data.xs_which_lock = LOCK_SPINLOCK; // LOCK_SPINLOCK, LOCK_PTHREAD_MUTEX, LOCK_FASTLOCK + data.xs_which_job = JOB_INCREMENT; + data.xs_debug_print = FALSE; + data.xs_progress = 0; + data.xs_inc = 0; + lck_mutex_lock_test(self, &data, 10); + /**/ + + /**/ + data.xs_interations = 10000; + data.xs_which_lock = LOCK_SPINLOCK; // LOCK_SPINLOCK, LOCK_PTHREAD_MUTEX, LOCK_FASTLOCK + data.xs_which_job = JOB_MEMCPY; + data.xs_debug_print = FALSE; + data.xs_progress = 0; + data.xs_inc = 0; + lck_mutex_lock_test(self, &data, 10); + /**/ + + /**/ + data.xs_interations = 1000; + data.xs_which_lock = LOCK_FASTLOCK; // LOCK_SPINLOCK, LOCK_PTHREAD_MUTEX, LOCK_FASTLOCK + data.xs_which_job = JOB_SLEEP; + data.xs_debug_print = FALSE; + data.xs_progress = 0; + data.xs_inc = 0; + lck_mutex_lock_test(self, &data, 10); + /**/ + + /**/ + data.xs_interations = 100; + data.xs_which_lock = LOCK_FASTLOCK; // LOCK_SPINLOCK, LOCK_PTHREAD_MUTEX, LOCK_FASTLOCK + data.xs_which_job = JOB_SNOOZE; + data.xs_debug_print = FALSE; + data.xs_progress = 0; + data.xs_inc = 0; + lck_mutex_lock_test(self, &data, 10); + /**/ + + xt_spinlock_free(self, &data.xs_spinlock); + xt_fastlock_free(self, &data.xs_fastlock); + xt_free_mutex(&data.xs_mutex); +} + +xtPublic void xt_unit_test_create_threads(XTThreadPtr self) +{ + XTThreadPtr threads[10]; + + printf("TEST: xt_unit_test_create_threads\n"); + printf("current max threads = %d, in use = %d\n", xt_thr_current_max_threads, xt_thr_current_thread_count); + + /* Create some threads: */ + threads[0] = xt_create_daemon(self, "test0"); + printf("thread = %d\n", threads[0]->t_id); + threads[1] = xt_create_daemon(self, "test1"); + printf("thread = %d\n", threads[1]->t_id); + threads[2] = xt_create_daemon(self, "test2"); + printf("thread = %d\n", threads[2]->t_id); + threads[3] = xt_create_daemon(self, "test3"); + printf("thread = %d\n", threads[3]->t_id); + threads[4] = xt_create_daemon(self, "test4"); + printf("thread = %d\n", threads[4]->t_id); + printf("current max threads = %d, in use = %d\n", xt_thr_current_max_threads, xt_thr_current_thread_count); + + /* Max stays the same: */ + xt_free_thread(threads[3]); + xt_free_thread(threads[2]); + xt_free_thread(threads[1]); + printf("current max threads = %d, in use = %d\n", xt_thr_current_max_threads, xt_thr_current_thread_count); + + /* Fill in the gaps: */ + threads[1] = xt_create_daemon(self, "test1"); + printf("thread = %d\n", threads[1]->t_id); + threads[2] = xt_create_daemon(self, "test2"); + printf("thread = %d\n", threads[2]->t_id); + threads[3] = xt_create_daemon(self, "test3"); + printf("thread = %d\n", threads[3]->t_id); + printf("current max threads = %d, in use = %d\n", xt_thr_current_max_threads, xt_thr_current_thread_count); + + /* And add one: */ + threads[5] = xt_create_daemon(self, "test5"); + printf("thread = %d\n", threads[5]->t_id); + printf("current max threads = %d, in use = %d\n", xt_thr_current_max_threads, xt_thr_current_thread_count); + + /* Max stays the same: */ + xt_free_thread(threads[3]); + xt_free_thread(threads[2]); + xt_free_thread(threads[1]); + xt_free_thread(threads[4]); + printf("current max threads = %d, in use = %d\n", xt_thr_current_max_threads, xt_thr_current_thread_count); + + /* Recalculate the max: */ + xt_free_thread(threads[5]); + printf("current max threads = %d, in use = %d\n", xt_thr_current_max_threads, xt_thr_current_thread_count); + + /* Fill in the gaps: */ + threads[1] = xt_create_daemon(self, "test1"); + printf("thread = %d\n", threads[1]->t_id); + threads[2] = xt_create_daemon(self, "test2"); + printf("thread = %d\n", threads[2]->t_id); + threads[3] = xt_create_daemon(self, "test3"); + printf("thread = %d\n", threads[3]->t_id); + printf("current max threads = %d, in use = %d\n", xt_thr_current_max_threads, xt_thr_current_thread_count); + + xt_free_thread(threads[3]); + xt_free_thread(threads[2]); + xt_free_thread(threads[1]); + xt_free_thread(threads[0]); + printf("current max threads = %d, in use = %d\n", xt_thr_current_max_threads, xt_thr_current_thread_count); +} + +#ifdef UNUSED_CODE +int XTRowLocks::xt_release_locks(struct XTOpenTable *ot, xtRowID row, XTRowLockListPtr lock_list) +{ + if (ot->ot_temp_row_lock) + xt_make_lock_permanent(ot, lock_list); + + if (!lock_list->bl_count) + return XT_NO_LOCK; + + int group, pgroup; + XTXactDataPtr xact; + xtTableID tab_id, ptab_id; + XTPermRowLockPtr plock; + XTOpenTablePtr pot = NULL; + XTRowLocksPtr row_locks; + + /* Do I have the lock? */ + group = row % XT_ROW_LOCK_COUNT; + if (!(xact = tab_row_locks[group])) + /* There is no lock: */ + return XT_NO_LOCK; + + if (xact != ot->ot_thread->st_xact_data) + /* There is a lock but it does not belong to me! */ + return XT_NO_LOCK; + + tab_id = ot->ot_table->tab_id; + plock = (XTPermRowLockPtr) &lock_list->bl_data[lock_list->bl_count * lock_list->bl_item_size]; + lock_list->rll_release_point = lock_list->bl_count; + for (u_int i=0; i<lock_list->bl_count; i++) { + plock--; + + pgroup = plock->pr_group; + ptab_id = plock->pr_tab_id; + + if (ptab_id == tab_id) + row_locks = this; + else { + if (pot) { + if (pot->ot_table->tab_id == ptab_id) + goto remove_lock; + xt_db_return_table_to_pool_ns(pot); + pot = NULL; + } + + if (!xt_db_open_pool_table_ns(&pot, ot->ot_table->tab_db, tab_id)) { + /* Should not happen, but just in case, we just don't + * remove the lock. We will probably end up with a deadlock + * somewhere. + */ + xt_log_and_clear_exception_ns(); + goto skip_remove_lock; + } + if (!pot) + /* Can happen of the table has been dropped: */ + goto skip_remove_lock; + + remove_lock: + row_locks = &pot->ot_table->tab_locks; + } + +#ifdef XT_TRACE_LOCKS + xt_ttracef(xt_get_self(), "release lock group=%d\n", pgroup); +#endif + row_locks->tab_row_locks[pgroup] = NULL; + row_locks->tab_lock_perm[pgroup] = 0; + skip_remove_lock:; + + lock_list->rll_release_point--; + if (tab_id == ptab_id && group == pgroup) + break; + } + + if (pot) + xt_db_return_table_to_pool_ns(pot); + return XT_PERM_LOCK; +} + +xtBool XTRowLocks::xt_regain_locks(struct XTOpenTable *ot, int *lock_type, xtXactID *xn_id, XTRowLockListPtr lock_list) +{ + int group; + XTXactDataPtr xact, my_xact; + XTPermRowLockPtr plock; + xtTableID tab_id; + XTOpenTablePtr pot = NULL; + XTRowLocksPtr row_locks = NULL; + XTTableHPtr tab = NULL; + + for (u_int i=lock_list->rll_release_point; i<lock_list->bl_count; i++) { + plock = (XTPermRowLockPtr) &lock_list->bl_data[i * lock_list->bl_item_size]; + + my_xact = ot->ot_thread->st_xact_data; + group = plock->pr_group; + tab_id = plock->pr_tab_id; + + if (tab_id == ot->ot_table->tab_id) { + row_locks = this; + tab = ot->ot_table; + } + else { + if (pot) { + if (tab_id == pot->ot_table->tab_id) + goto gain_lock; + xt_db_return_table_to_pool_ns(pot); + pot = NULL; + } + + if (!xt_db_open_pool_table_ns(&pot, ot->ot_table->tab_db, tab_id)) + return FAILED; + if (!pot) + goto no_gain_lock; + + gain_lock: + tab = pot->ot_table; + row_locks = &tab->tab_locks; + no_gain_lock:; + } + +#ifdef XT_TRACE_LOCKS + xt_ttracef(xt_get_self(), "regain lock group=%d\n", group); +#endif + XT_TAB_ROW_WRITE_LOCK(&tab->tab_row_rwlock[group % XT_ROW_RWLOCKS], ot->ot_thread); + if ((xact = row_locks->tab_row_locks[group])) { + if (xact != my_xact) { + *xn_id = xact->xd_start_xn_id; + *lock_type = row_locks->tab_lock_perm[group] ? XT_PERM_LOCK : XT_TEMP_LOCK; + goto done; + } + } + else + row_locks->tab_row_locks[group] = my_xact; + row_locks->tab_lock_perm[group] = 1; + XT_TAB_ROW_UNLOCK(&tab->tab_row_rwlock[group % XT_ROW_RWLOCKS], ot->ot_thread); + lock_list->rll_release_point++; + } + *lock_type = XT_NO_LOCK; + return OK; + + done: + XT_TAB_ROW_UNLOCK(&tab->tab_row_rwlock[group % XT_ROW_RWLOCKS], ot->ot_thread); + return OK; +} + +#endif diff --git a/storage/pbxt/src/lock_xt.h b/storage/pbxt/src/lock_xt.h new file mode 100644 index 00000000000..214b00a849e --- /dev/null +++ b/storage/pbxt/src/lock_xt.h @@ -0,0 +1,699 @@ +/* Copyright (c) 2005 PrimeBase Technologies GmbH + * + * PrimeBase XT + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You 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 + * + * 2008-01-24 Paul McCullagh + * + * Row lock functions. + * + * H&G2JCtL + */ +#ifndef __xt_lock_h__ +#define __xt_lock_h__ + +#include "xt_defs.h" +#include "util_xt.h" +#include "locklist_xt.h" +#include "pthread_xt.h" + +struct XTThread; +struct XTDatabase; +struct XTOpenTable; +struct XTXactData; +struct XTTable; + +/* Possibilities are 2 = align 4 or 2 = align 8 */ +#define XT_XS_LOCK_SHIFT 2 +#define XT_XS_LOCK_ALIGN (1 << XT_XS_LOCK_SHIFT) + +/* This lock is fast for reads but slow for writes. + * Use this lock in situations where you have 99% reads, + * and then some potentially long writes. + */ +typedef struct XTRWMutex { +#ifdef DEBUG + struct XTThread *xs_lock_thread; + u_int xs_inited; +#endif +#ifdef XT_THREAD_LOCK_INFO + XTThreadLockInfoRec xs_lock_info; + const char *xs_name; +#endif + xt_mutex_type xs_lock; + xt_cond_type xs_cond; + volatile xtWord4 xs_state; + volatile xtThreadID xs_xlocker; + union { +#if XT_XS_LOCK_ALIGN == 4 + volatile xtWord4 *xs_rlock_align; +#else + volatile xtWord8 *xs_rlock_align; +#endif + volatile xtWord1 *xs_rlock; + } x; +} XTRWMutexRec, *XTRWMutexPtr; + +#ifdef XT_THREAD_LOCK_INFO +#define xt_rwmutex_init_with_autoname(a,b) xt_rwmutex_init(a,b,LOCKLIST_ARG_SUFFIX(b)) +void xt_rwmutex_init(struct XTThread *self, XTRWMutexPtr xsl, const char *name); +#else +#define xt_rwmutex_init_with_autoname(a,b) xt_rwmutex_init(a,b) +void xt_rwmutex_init(struct XTThread *self, XTRWMutexPtr xsl); +#endif +void xt_rwmutex_free(struct XTThread *self, XTRWMutexPtr xsl); +xtBool xt_rwmutex_xlock(XTRWMutexPtr xsl, xtThreadID thd_id); +xtBool xt_rwmutex_slock(XTRWMutexPtr xsl, xtThreadID thd_id); +xtBool xt_rwmutex_unlock(XTRWMutexPtr xsl, xtThreadID thd_id); + +#ifdef XT_WIN +#define XT_SPL_WIN32_ASM +#else +#if defined(__GNUC__) && (defined(__x86_64__) || defined(__i386__)) +#define XT_SPL_GNUC_X86 +#else +#define XT_SPL_DEFAULT +#endif +#endif + +#ifdef XT_SOLARIS +/* Use Sun atomic operations library + * http://docs.sun.com/app/docs/doc/816-5168/atomic-ops-3c?a=view + */ +#define XT_SPL_SOLARIS_LIB +#endif + +#ifdef XT_SPL_SOLARIS_LIB +#include <atomic.h> +#endif + +typedef struct XTSpinLock { + volatile xtWord4 spl_lock; +#ifdef XT_SPL_DEFAULT + xt_mutex_type spl_mutex; +#endif +#ifdef DEBUG + struct XTThread *spl_locker; +#endif +#ifdef XT_THREAD_LOCK_INFO + XTThreadLockInfoRec spl_lock_info; + const char *spl_name; +#endif +} XTSpinLockRec, *XTSpinLockPtr; + +#ifdef XT_THREAD_LOCK_INFO +#define xt_spinlock_init_with_autoname(a,b) xt_spinlock_init(a,b,LOCKLIST_ARG_SUFFIX(b)) +void xt_spinlock_init(struct XTThread *self, XTSpinLockPtr sp, const char *name); +#else +#define xt_spinlock_init_with_autoname(a,b) xt_spinlock_init(a,b) +void xt_spinlock_init(struct XTThread *self, XTSpinLockPtr sp); +#endif +void xt_spinlock_free(struct XTThread *self, XTSpinLockPtr sp); +xtBool xt_spinlock_spin(XTSpinLockPtr spl); +#ifdef DEBUG +void xt_spinlock_set_thread(XTSpinLockPtr spl); +#endif + +/* + * This macro is to remind me where it was safe + * to use a read lock! + */ +#define xt_lck_slock xt_spinlock_lock + +/* I call these operations flushed because the result + * is written atomically. + * But the operations themselves are not atomic! + */ +inline void xt_flushed_inc1(volatile xtWord1 *mptr) +{ +#ifdef XT_SPL_WIN32_ASM + __asm MOV ECX, mptr + __asm MOV DL, BYTE PTR [ECX] + __asm INC DL + __asm XCHG DL, BYTE PTR [ECX] +#elif defined(XT_SPL_GNUC_X86) + xtWord1 val; + + asm volatile ("movb %1,%0" : "=r" (val) : "m" (*mptr) : "memory"); + val++; + asm volatile ("xchgb %1,%0" : "=r" (val) : "m" (*mptr), "0" (val) : "memory"); +#elif defined(XT_SPL_SOLARIS_LIB) + atomic_inc_8(mptr); +#else + *mptr++; +#endif +} + +inline xtWord1 xt_flushed_dec1(volatile xtWord1 *mptr) +{ + xtWord1 val; + +#ifdef XT_SPL_WIN32_ASM + __asm MOV ECX, mptr + __asm MOV DL, BYTE PTR [ECX] + __asm DEC DL + __asm MOV val, DL + __asm XCHG DL, BYTE PTR [ECX] +#elif defined(XT_SPL_GNUC_X86) + xtWord1 val2; + + asm volatile ("movb %1, %0" : "=r" (val) : "m" (*mptr) : "memory"); + val--; + asm volatile ("xchgb %1,%0" : "=r" (val2) : "m" (*mptr), "0" (val) : "memory"); + /* Should work, but compiler makes a mistake? + * asm volatile ("xchgb %1, %0" : : "r" (val), "m" (*mptr) : "memory"); + */ +#elif defined(XT_SPL_SOLARIS_LIB) + val = atomic_dec_8_nv(mptr); +#else + val = --(*mptr); +#endif + return val; +} + +inline void xt_atomic_inc2(volatile xtWord2 *mptr) +{ +#ifdef XT_SPL_WIN32_ASM + __asm LOCK INC WORD PTR mptr +#elif defined(XT_SPL_GNUC_X86) + asm volatile ("lock; incw %0" : : "m" (*mptr) : "memory"); +#elif defined(__GNUC__) + __sync_fetch_and_add(mptr, 1); +#elif defined(XT_SPL_SOLARIS_LIB) + atomic_inc_16_nv(mptr); +#else + (*mptr)++; +#endif +} + +inline void xt_atomic_dec2(volatile xtWord2 *mptr) +{ +#ifdef XT_SPL_WIN32_ASM + __asm LOCK DEC WORD PTR mptr +#elif defined(XT_SPL_GNUC_X86) + asm volatile ("lock; decw %0" : : "m" (*mptr) : "memory"); +#elif defined(__GNUC__) + __sync_fetch_and_sub(mptr, 1); +#elif defined(XT_SPL_SOLARIS_LIB) + atomic_dec_16_nv(mptr); +#else + --(*mptr); +#endif +} + +/* Atomic test and set 2 byte word! */ +inline xtWord2 xt_atomic_tas2(volatile xtWord2 *mptr, xtWord2 val) +{ +#ifdef XT_SPL_WIN32_ASM + __asm MOV ECX, mptr + __asm MOV DX, val + __asm XCHG DX, WORD PTR [ECX] + __asm MOV val, DX +#elif defined(XT_SPL_GNUC_X86) + asm volatile ("xchgw %1,%0" : "=r" (val) : "m" (*mptr), "0" (val) : "memory"); +#elif defined(XT_SPL_SOLARIS_LIB) + val = atomic_swap_16(mptr, val); +#else + /* Yikes! */ + xtWord2 nval = val; + + val = *mptr; + *mptr = nval; +#endif + return val; +} + +inline void xt_atomic_set4(volatile xtWord4 *mptr, xtWord4 val) +{ +#ifdef XT_SPL_WIN32_ASM + __asm MOV ECX, mptr + __asm MOV EDX, val + __asm XCHG EDX, DWORD PTR [ECX] + //__asm MOV DWORD PTR [ECX], EDX +#elif defined(XT_SPL_GNUC_X86) + asm volatile ("xchgl %1,%0" : "=r" (val) : "m" (*mptr), "0" (val) : "memory"); + //asm volatile ("movl %0,%1" : "=r" (val) : "m" (*mptr) : "memory"); +#elif defined(XT_SPL_SOLARIS_LIB) + atomic_swap_32(mptr, val); +#else + *mptr = val; +#endif +} + +inline xtWord4 xt_atomic_get4(volatile xtWord4 *mptr) +{ + xtWord4 val; + +#ifdef XT_SPL_WIN32_ASM + __asm MOV ECX, mptr + __asm MOV EDX, DWORD PTR [ECX] + __asm MOV val, EDX +#elif defined(XT_SPL_GNUC_X86) + asm volatile ("movl %1,%0" : "=r" (val) : "m" (*mptr) : "memory"); +#else + val = *mptr; +#endif + return val; +} + +/* Code for test and set is derived from code by Larry Zhou and + * Google: http://code.google.com/p/google-perftools + */ +inline xtWord4 xt_spinlock_set(XTSpinLockPtr spl) +{ + xtWord4 prv; + volatile xtWord4 *lck; + + lck = &spl->spl_lock; +#ifdef XT_SPL_WIN32_ASM + __asm MOV ECX, lck + __asm MOV EDX, 1 + __asm XCHG EDX, DWORD PTR [ECX] + __asm MOV prv, EDX +#elif defined(XT_SPL_GNUC_X86) + prv = 1; + asm volatile ("xchgl %1,%0" : "=r" (prv) : "m" (*lck), "0" (prv) : "memory"); +#elif defined(XT_SPL_SOLARIS_LIB) + prv = atomic_swap_32(lck, 1); +#else + /* The default implementation just uses a mutex, and + * does not spin! */ + xt_lock_mutex_ns(&spl->spl_mutex); + /* We have the lock */ + *lck = 1; + prv = 0; +#endif +#ifdef DEBUG + if (!prv) + xt_spinlock_set_thread(spl); +#endif + return prv; +} + +inline xtWord4 xt_spinlock_reset(XTSpinLockPtr spl) +{ + xtWord4 prv; + volatile xtWord4 *lck; + +#ifdef DEBUG + spl->spl_locker = NULL; +#endif + lck = &spl->spl_lock; +#ifdef XT_SPL_WIN32_ASM + __asm MOV ECX, lck + __asm MOV EDX, 0 + __asm XCHG EDX, DWORD PTR [ECX] + __asm MOV prv, EDX +#elif defined(XT_SPL_GNUC_X86) + prv = 0; + asm volatile ("xchgl %1,%0" : "=r" (prv) : "m" (*lck), "0" (prv) : "memory"); +#elif defined(XT_SPL_SOLARIS_LIB) + prv = atomic_swap_32(lck, 0); +#else + *lck = 0; + xt_unlock_mutex_ns(&spl->spl_mutex); + prv = 1; +#endif + return prv; +} + +/* + * Return FALSE, and register an error on failure. + */ +inline xtBool xt_spinlock_lock(XTSpinLockPtr spl) +{ + if (!xt_spinlock_set(spl)) { +#ifdef XT_THREAD_LOCK_INFO + xt_thread_lock_info_add_owner(&spl->spl_lock_info); +#endif + return OK; + } +#ifdef XT_THREAD_LOCK_INFO + xtBool spin_result = xt_spinlock_spin(spl); + if (spin_result) + xt_thread_lock_info_add_owner(&spl->spl_lock_info); + return spin_result; +#else + return xt_spinlock_spin(spl); +#endif +} + +inline void xt_spinlock_unlock(XTSpinLockPtr spl) +{ + xt_spinlock_reset(spl); +#ifdef XT_THREAD_LOCK_INFO + xt_thread_lock_info_release_owner(&spl->spl_lock_info); +#endif +} + +void xt_unit_test_read_write_locks(struct XTThread *self); +void xt_unit_test_mutex_locks(struct XTThread *self); +void xt_unit_test_create_threads(struct XTThread *self); + +#define XT_FAST_LOCK_MAX_WAIT 100 + +typedef struct XTFastLock { + XTSpinLockRec fal_spinlock; + struct XTThread *fal_locker; + + XTSpinLockRec fal_wait_lock; + u_int fal_wait_count; + u_int fal_wait_wakeup; + u_int fal_wait_alloc; + struct XTThread *fal_wait_list[XT_FAST_LOCK_MAX_WAIT]; +#ifdef XT_THREAD_LOCK_INFO + XTThreadLockInfoRec fal_lock_info; + const char *fal_name; +#endif +} XTFastLockRec, *XTFastLockPtr; + +#ifdef XT_THREAD_LOCK_INFO +#define xt_fastlock_init_with_autoname(a,b) xt_fastlock_init(a,b,LOCKLIST_ARG_SUFFIX(b)) +void xt_fastlock_init(struct XTThread *self, XTFastLockPtr spl, const char *name); +#else +#define xt_fastlock_init_with_autoname(a,b) xt_fastlock_init(a,b) +void xt_fastlock_init(struct XTThread *self, XTFastLockPtr spl); +#endif +void xt_fastlock_free(struct XTThread *self, XTFastLockPtr spl); +void xt_fastlock_wakeup(XTFastLockPtr spl); +xtBool xt_fastlock_spin(XTFastLockPtr spl, struct XTThread *thread); + +inline xtBool xt_fastlock_lock(XTFastLockPtr fal, struct XTThread *thread) +{ + if (!xt_spinlock_set(&fal->fal_spinlock)) { + fal->fal_locker = thread; +#ifdef XT_THREAD_LOCK_INFO + xt_thread_lock_info_add_owner(&fal->fal_lock_info); +#endif + return OK; + } +#ifdef XT_THREAD_LOCK_INFO + xtBool spin_result = xt_fastlock_spin(fal, thread); + if (spin_result) + xt_thread_lock_info_add_owner(&fal->fal_lock_info); + return spin_result; +#else + return xt_fastlock_spin(fal, thread); +#endif +} + +inline void xt_fastlock_unlock(XTFastLockPtr fal, struct XTThread *thread __attribute__((unused))) +{ + if (fal->fal_wait_count) + xt_fastlock_wakeup(fal); + else { + fal->fal_locker = NULL; + xt_spinlock_reset(&fal->fal_spinlock); + } +#ifdef XT_THREAD_LOCK_INFO + xt_thread_lock_info_release_owner(&fal->fal_lock_info); +#endif +} + +typedef struct XTSpinRWLock { + XTSpinLockRec srw_lock; + volatile xtThreadID srw_xlocker; + XTSpinLockRec srw_state_lock; + volatile u_int srw_state; + union { +#if XT_XS_LOCK_ALIGN == 4 + volatile xtWord4 *srw_rlock_align; +#else + volatile xtWord8 *srw_rlock_align; +#endif + volatile xtWord1 *srw_rlock; + } x; + +#ifdef XT_THREAD_LOCK_INFO + XTThreadLockInfoRec srw_lock_info; + const char *srw_name; +#endif + +} XTSpinRWLockRec, *XTSpinRWLockPtr; + +#ifdef XT_THREAD_LOCK_INFO +#define xt_spinrwlock_init_with_autoname(a,b) xt_spinrwlock_init(a,b,LOCKLIST_ARG_SUFFIX(b)) +void xt_spinrwlock_init(struct XTThread *self, XTSpinRWLockPtr xsl, const char *name); +#else +#define xt_spinrwlock_init_with_autoname(a,b) xt_spinrwlock_init(a,b) +void xt_spinrwlock_init(struct XTThread *self, XTSpinRWLockPtr xsl); +#endif +void xt_spinrwlock_free(struct XTThread *self, XTSpinRWLockPtr xsl); +xtBool xt_spinrwlock_xlock(XTSpinRWLockPtr xsl, xtThreadID thd_id); +xtBool xt_spinrwlock_slock(XTSpinRWLockPtr xsl, xtThreadID thd_id); +xtBool xt_spinrwlock_unlock(XTSpinRWLockPtr xsl, xtThreadID thd_id); + +typedef struct XTFastRWLock { + XTFastLockRec frw_lock; + struct XTThread *frw_xlocker; + XTSpinLockRec frw_state_lock; + volatile u_int frw_state; + u_int frw_read_waiters; + union { +#if XT_XS_LOCK_ALIGN == 4 + volatile xtWord4 *frw_rlock_align; +#else + volatile xtWord8 *frw_rlock_align; +#endif + volatile xtWord1 *frw_rlock; + } x; + +#ifdef XT_THREAD_LOCK_INFO + XTThreadLockInfoRec frw_lock_info; + const char *frw_name; +#endif + +} XTFastRWLockRec, *XTFastRWLockPtr; + +#ifdef XT_THREAD_LOCK_INFO +#define xt_fastrwlock_init_with_autoname(a,b) xt_fastrwlock_init(a,b,LOCKLIST_ARG_SUFFIX(b)) +void xt_fastrwlock_init(struct XTThread *self, XTFastRWLockPtr frw, const char *name); +#else +#define xt_fastrwlock_init_with_autoname(a,b) xt_fastrwlock_init(a,b) +void xt_fastrwlock_init(struct XTThread *self, XTFastRWLockPtr frw); +#endif + +void xt_fastrwlock_free(struct XTThread *self, XTFastRWLockPtr frw); +xtBool xt_fastrwlock_xlock(XTFastRWLockPtr frw, struct XTThread *thread); +xtBool xt_fastrwlock_slock(XTFastRWLockPtr frw, struct XTThread *thread); +xtBool xt_fastrwlock_unlock(XTFastRWLockPtr frw, struct XTThread *thread); + +typedef struct XTAtomicRWLock { + volatile xtWord2 arw_reader_count; + volatile xtWord2 arw_xlock_set; + +#ifdef XT_THREAD_LOCK_INFO + XTThreadLockInfoRec arw_lock_info; + const char *arw_name; +#endif +#ifdef DEBUG + xtThreadID arw_locker; +#endif +} XTAtomicRWLockRec, *XTAtomicRWLockPtr; + +#ifdef XT_THREAD_LOCK_INFO +#define xt_atomicrwlock_init_with_autoname(a,b) xt_atomicrwlock_init(a,b,LOCKLIST_ARG_SUFFIX(b)) +void xt_atomicrwlock_init(struct XTThread *self, XTAtomicRWLockPtr xsl, const char *name); +#else +#define xt_atomicrwlock_init_with_autoname(a,b) xt_atomicrwlock_init(a,b) +void xt_atomicrwlock_init(struct XTThread *self, XTAtomicRWLockPtr xsl); +#endif +void xt_atomicrwlock_free(struct XTThread *self, XTAtomicRWLockPtr xsl); +xtBool xt_atomicrwlock_xlock(XTAtomicRWLockPtr xsl, xtThreadID thr_id); +xtBool xt_atomicrwlock_slock(XTAtomicRWLockPtr xsl); +xtBool xt_atomicrwlock_unlock(XTAtomicRWLockPtr xsl, xtBool xlocked); + +/* + * ----------------------------------------------------------------------- + * ROW LOCKS + */ + +/* + * [(9)] + * + * These are perminent row locks. They are set on rows for 2 reasons: + * + * 1. To lock a row that is being updated. The row is locked + * when it is read, until the point that it is updated. If the row + * is not updated, the lock is removed. + * This prevents an update coming between which will cause an error + * on the first thread. + * + * 2. The locks are used to implement SELECT FOR UPDATE. + */ + +/* + * A lock that is set in order to perform an update is a temporary lock. + * This lock will be removed once the update of the record is done. + * The objective is to prevent some other thread from changine the + * record between the time the record is read and updated. This is to + * prevent unncessary "Record was updated" errors. + * + * A permanent lock is set by a SELECT FOR UPDATE. These locks are + * held until the end of the transaction. + * + * However, a SELECT FOR UPDATE will pop its lock stack before + * waiting for a transaction that has updated a record. + * This is to prevent the deadlock that can occur because a + * SELECT FOR UPDATE locks groups of records (I mean in general the + * locks used are group locks). + * + * This means a SELECT FOR UPDATE can get ahead of an UPDATE as far as + * locking is concerned. Example: + * + * Record 1,2 and 3 are in group A. + * + * T1: UPDATES record 2. + * T2: SELECT FOR UPDATE record 1, which locks group A. + * T2: SELECT FOR UPDATE record 2, which must wait for T1. + * T1: UPDATES record 3, which musts wait because of group lock A. + * + * To avoid deadlock, T2 releases its group lock A before waiting for + * record 2. It then regains the lock after waiting for record 2. + * + * (NOTE: Locks are no longer released. Please check this comment: + * {RELEASING-LOCKS} in lock_xt.cc. ) + * + * However, release group A lock mean first releasing all locks gained + * after group a lock. + * + * For example: a thread locks groups: A, B and C. To release group B + * lock the thread must release C as well. Afterwards, it must gain + * B and C again, in that order. This is to ensure that the lock + * order is NOT changed! + * + */ +#define XT_LOCK_ERR -1 +#define XT_NO_LOCK 0 +#define XT_TEMP_LOCK 1 /* A temporary lock */ +#define XT_PERM_LOCK 2 /* A permanent lock */ + +typedef struct XTRowLockList : public XTBasicList { + void xt_remove_all_locks(struct XTDatabase *db, struct XTThread *thread); +} XTRowLockListRec, *XTRowLockListPtr; + +#define XT_USE_LIST_BASED_ROW_LOCKS + +#ifdef XT_USE_LIST_BASED_ROW_LOCKS +/* + * This method stores each lock, and avoids conflicts. + * But it is a bit more expensive in time. + */ + +#ifdef DEBUG +#define XT_TEMP_LOCK_BYTES 10 +#define XT_ROW_LOCK_GROUP_COUNT 5 +#else +#define XT_TEMP_LOCK_BYTES 0xFFFF +#define XT_ROW_LOCK_GROUP_COUNT 23 +#endif + +typedef struct XTLockWait { + /* Information about the lock to be aquired: */ + struct XTThread *lw_thread; + struct XTOpenTable *lw_ot; + xtRowID lw_row_id; + + /* This is the lock currently held, and the transaction ID: */ + int lw_curr_lock; + xtXactID lw_xn_id; + + /* This is information about the updating transaction: */ + xtBool lw_row_updated; + xtXactID lw_updating_xn_id; + + /* Pointers for the lock list: */ + struct XTLockWait *lw_next; + struct XTLockWait *lw_prev; +} XTLockWaitRec, *XTLockWaitPtr; + +typedef struct XTLockItem { + xtRowID li_row_id; /* The row list is sorted in this value. */ + xtWord2 li_count; /* The number of consecutive rows locked. FFFF means a temporary lock. */ + xtWord2 li_thread_id; /* The thread that holds this lock. */ +} XTLockItemRec, *XTLockItemPtr; + +typedef struct XTLockGroup { + XTSpinLockRec lg_lock; /* A lock for the list. */ + XTLockWaitPtr lg_wait_queue; /* A queue of threads waiting for a lock in this group. */ + XTLockWaitPtr lg_wait_queue_end; /* The end of the thread queue. */ + size_t lg_list_size; /* The size of the list. */ + size_t lg_list_in_use; /* Number of slots on the list in use. */ + XTLockItemPtr lg_list; /* List of locks. */ +} XTLockGroupRec, *XTLockGroupPtr; + +struct XTLockWait; + +typedef struct XTRowLocks { + XTLockGroupRec rl_groups[XT_ROW_LOCK_GROUP_COUNT]; + + void xt_cancel_temp_lock(XTLockWaitPtr lw); + xtBool xt_set_temp_lock(struct XTOpenTable *ot, XTLockWaitPtr lw, XTRowLockListPtr lock_list); + void xt_remove_temp_lock(struct XTOpenTable *ot, xtBool updated); + xtBool xt_make_lock_permanent(struct XTOpenTable *ot, XTRowLockListPtr lock_list); + + xtBool rl_lock_row(XTLockGroupPtr group, XTLockWaitPtr lw, XTRowLockListPtr lock_list, int *result); + void rl_grant_locks(XTLockGroupPtr group, struct XTThread *thread); +#ifdef DEBUG_LOCK_QUEUE + void rl_check(XTLockWaitPtr lw); +#endif +} XTRowLocksRec, *XTRowLocksPtr; + +#define XT_USE_TABLE_REF + +typedef struct XTPermRowLock { +#ifdef XT_USE_TABLE_REF + struct XTTable *pr_table; +#else + xtTableID pr_tab_id; +#endif + xtWord1 pr_group[XT_ROW_LOCK_GROUP_COUNT]; +} XTPermRowLockRec, *XTPermRowLockPtr; + +#else // XT_ROW_LOCK_GROUP_COUNT + +/* Hash based row locking. This method allows conflics, even + * when there is none. + */ +typedef struct XTRowLocks { + xtWord1 tab_lock_perm[XT_ROW_LOCK_COUNT]; /* Byte set to 1 for permanent locks. */ + struct XTXactData *tab_row_locks[XT_ROW_LOCK_COUNT]; /* The transactions that have locked the specific rows. */ + + int xt_set_temp_lock(struct XTOpenTable *ot, xtRowID row, xtXactID *xn_id, XTRowLockListPtr lock_list); + void xt_remove_temp_lock(struct XTOpenTable *ot); + xtBool xt_make_lock_permanent(struct XTOpenTable *ot, XTRowLockListPtr lock_list); + int xt_is_locked(struct XTOpenTable *ot, xtRowID row, xtXactID *xn_id); +} XTRowLocksRec, *XTRowLocksPtr; + +typedef struct XTPermRowLock { + xtTableID pr_tab_id; + xtWord4 pr_group; +} XTPermRowLockRec, *XTPermRowLockPtr; + +#endif // XT_ROW_LOCK_GROUP_COUNT + +xtBool xt_init_row_locks(XTRowLocksPtr rl); +void xt_exit_row_locks(XTRowLocksPtr rl); + +xtBool xt_init_row_lock_list(XTRowLockListPtr rl); +void xt_exit_row_lock_list(XTRowLockListPtr rl); + +#define XT_NO_LOCK 0 +#define XT_WANT_LOCK 1 +#define XT_HAVE_LOCK 2 +#define XT_WAITING 3 + +#endif diff --git a/storage/pbxt/src/locklist_xt.cc b/storage/pbxt/src/locklist_xt.cc new file mode 100644 index 00000000000..0a3df584ba6 --- /dev/null +++ b/storage/pbxt/src/locklist_xt.cc @@ -0,0 +1,184 @@ +/* Copyright (c) 2009 PrimeBase Technologies GmbH + * + * PrimeBase XT + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You 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 + * + * 2009-01-20 Vladimir Kolesnikov + * + * H&G2JCtL + */ + +#include "xt_config.h" +#include "locklist_xt.h" + +#ifdef XT_THREAD_LOCK_INFO +#include "pthread_xt.h" +#include "thread_xt.h" +#include "trace_xt.h" + +void xt_thread_lock_info_init(XTThreadLockInfoPtr ptr, XTSpinLock *lock) +{ + ptr->li_spin_lock = lock; + ptr->li_lock_type = XTThreadLockInfo::SPIN_LOCK; +} + +void xt_thread_lock_info_init(XTThreadLockInfoPtr ptr, XTRWMutex *lock) +{ + ptr->li_rw_mutex = lock; + ptr->li_lock_type = XTThreadLockInfo::RW_MUTEX; +} + +void xt_thread_lock_info_init(XTThreadLockInfoPtr ptr, XTFastLock *lock) +{ + ptr->li_fast_lock = lock; + ptr->li_lock_type = XTThreadLockInfo::FAST_LOCK; +} + +void xt_thread_lock_info_init(XTThreadLockInfoPtr ptr, xt_mutex_struct *lock) +{ + ptr->li_mutex = lock; + ptr->li_lock_type = XTThreadLockInfo::MUTEX; +} + +void xt_thread_lock_info_init(XTThreadLockInfoPtr ptr, xt_rwlock_struct *lock) +{ + ptr->li_rwlock = lock; + ptr->li_lock_type = XTThreadLockInfo::RW_LOCK; +} + +void xt_thread_lock_info_init(XTThreadLockInfoPtr ptr, XTFastRWLock *lock) +{ + ptr->li_fast_rwlock = lock; + ptr->li_lock_type = XTThreadLockInfo::FAST_RW_LOCK; +} + +void xt_thread_lock_info_init(XTThreadLockInfoPtr ptr, XTSpinRWLock *lock) +{ + ptr->li_spin_rwlock = lock; + ptr->li_lock_type = XTThreadLockInfo::SPIN_RW_LOCK; +} + +void xt_thread_lock_info_init(XTThreadLockInfoPtr ptr, XTAtomicRWLock *lock) +{ + ptr->li_atomic_rwlock = lock; + ptr->li_lock_type = XTThreadLockInfo::ATOMIC_RW_LOCK; +} + +void xt_thread_lock_info_free(XTThreadLockInfoPtr ptr) +{ + /* TODO: check to see if it's present in a thread's list */ +} + +void xt_thread_lock_info_add_owner (XTThreadLockInfoPtr ptr) +{ + XTThread *self = xt_get_self(); + + if (!self) + return; + + if (self->st_thread_lock_count < XT_THREAD_LOCK_INFO_MAX_COUNT) { + self->st_thread_lock_list[self->st_thread_lock_count] = ptr; + self->st_thread_lock_count++; + } +} + +void xt_thread_lock_info_release_owner (XTThreadLockInfoPtr ptr) +{ + XTThread *self = xt_get_self(); + + if (!self) + return; + + for (int i = self->st_thread_lock_count - 1; i >= 0; i--) { + if (self->st_thread_lock_list[i] == ptr) { + self->st_thread_lock_count--; + memcpy(self->st_thread_lock_list + i, + self->st_thread_lock_list + i + 1, + (self->st_thread_lock_count - i)*sizeof(XTThreadLockInfoPtr)); + self->st_thread_lock_list[self->st_thread_lock_count] = NULL; + break; + } + } +} + +void xt_trace_thread_locks(XTThread *self) +{ + if (!self) + return; + + xt_ttracef(self, "thread lock list (first in list added first): "); + + if (!self->st_thread_lock_count) { + xt_trace(" <empty>\n"); + return; + } + + xt_trace("\n"); + + int count = min(self->st_thread_lock_count, XT_THREAD_LOCK_INFO_MAX_COUNT); + + for(int i = 0; i < count; i++) { + + const char *lock_type = NULL; + const char *lock_name = NULL; + + XTThreadLockInfoPtr li = self->st_thread_lock_list[i]; + + switch(li->li_lock_type) { + case XTThreadLockInfo::SPIN_LOCK: + lock_type = "XTSpinLock"; + lock_name = li->li_spin_lock->spl_name; + break; + case XTThreadLockInfo::RW_MUTEX: + lock_type = "XTRWMutex"; + lock_name = li->li_rw_mutex->xs_name; + break; + case XTThreadLockInfo::MUTEX: + lock_type = "xt_mutex_struct"; +#ifdef XT_WIN + lock_name = li->li_mutex->mt_name; +#else + lock_name = li->li_mutex->mu_name; +#endif + break; + case XTThreadLockInfo::RW_LOCK: + lock_type = "xt_rwlock_struct"; + lock_name = li->li_rwlock->rw_name; + break; + case XTThreadLockInfo::FAST_LOCK: + lock_type = "XTFastLock"; + lock_name = li->li_fast_lock->fal_name; + break; + case XTThreadLockInfo::FAST_RW_LOCK: + lock_type = "XTFastRWLock"; + lock_name = li->li_fast_rwlock->frw_name; + break; + case XTThreadLockInfo::SPIN_RW_LOCK: + lock_type = "XTSpinRWLock"; + lock_name = li->li_spin_rwlock->srw_name; + break; + case XTThreadLockInfo::ATOMIC_RW_LOCK: + lock_type = "XTAtomicRWLock"; + lock_name = li->li_atomic_rwlock->arw_name; + break; + } + + xt_ttracef(self, " #lock#%d: type: %s name: %s \n", count, lock_type, lock_name); + } +} + +#endif + diff --git a/storage/pbxt/src/locklist_xt.h b/storage/pbxt/src/locklist_xt.h new file mode 100644 index 00000000000..f0f16009ca1 --- /dev/null +++ b/storage/pbxt/src/locklist_xt.h @@ -0,0 +1,97 @@ +/* Copyright (c) 2009 PrimeBase Technologies GmbH + * + * PrimeBase XT + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You 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 + * + * 2009-01-20 Vladimir Kolesnikov + * + * H&G2JCtL + */ + +#ifndef __xt_locklist_h__ +#define __xt_locklist_h__ + +#ifdef DEBUG +//#define XT_THREAD_LOCK_INFO +#ifndef XT_WIN +/* We need DEBUG_LOCKING in order to enable pthread function wrappers */ +#define DEBUG_LOCKING +#endif +#endif + +#include "xt_defs.h" + +struct XTThread; +struct XTSpinLock; +struct XTRWMutex; +struct xt_mutex_struct; +struct xt_rwlock_struct; +struct XTFastLock; +struct XTFastRWLock; +struct XTSpinRWLock; +struct XTAtomicRWLock; + +#ifdef XT_THREAD_LOCK_INFO + +#define XT_THREAD_LOCK_INFO_MAX_COUNT 50 + +#ifdef XT_WIN +#define LOCKLIST_ARG_SUFFIX(name) #name " in " __FUNCTION__ "() at " __FILE__ ":" QUOTE(__LINE__) +#else +#define LOCKLIST_ARG_SUFFIX(name) #name " in " QUOTE(__PRETTY_FUNCTION__) "() at " QUOTE(__FILE__) ":" QUOTE(__LINE__) +#endif + +/* + * An instance of XTThreadLockInfo class keeps information about a lock kept by a thread. + * There's a list of XTThreadLockInfo instances per thread. An instance can be included + * into several thread lists in case of shared locks. + */ +typedef struct XTThreadLockInfo { + + enum LockType { SPIN_LOCK, RW_MUTEX, MUTEX, RW_LOCK, FAST_LOCK, FAST_RW_LOCK, SPIN_RW_LOCK, ATOMIC_RW_LOCK }; + + LockType li_lock_type; + + union { + XTSpinLock *li_spin_lock; // SPIN_LOCK + XTRWMutex *li_rw_mutex; // RW_MUTEX + XTFastLock *li_fast_lock; // FAST_LOCK + XTFastRWLock *li_fast_rwlock; // FAST_RW_LOCK + XTSpinRWLock *li_spin_rwlock; // SPIN_RW_LOCK + XTAtomicRWLock *li_atomic_rwlock; // ATOMIC_RW_LOCK + xt_mutex_struct *li_mutex; // MUTEX + xt_rwlock_struct *li_rwlock; // RW_LOCK + }; +} +XTThreadLockInfoRec, *XTThreadLockInfoPtr; + +void xt_thread_lock_info_init(XTThreadLockInfoPtr ptr, XTSpinLock *lock); +void xt_thread_lock_info_init(XTThreadLockInfoPtr ptr, XTRWMutex *lock); +void xt_thread_lock_info_init(XTThreadLockInfoPtr ptr, XTFastLock *lock); +void xt_thread_lock_info_init(XTThreadLockInfoPtr ptr, XTFastRWLock *lock); +void xt_thread_lock_info_init(XTThreadLockInfoPtr ptr, XTSpinRWLock *lock); +void xt_thread_lock_info_init(XTThreadLockInfoPtr ptr, XTAtomicRWLock *lock); +void xt_thread_lock_info_init(XTThreadLockInfoPtr ptr, xt_mutex_struct *lock); +void xt_thread_lock_info_init(XTThreadLockInfoPtr ptr, xt_rwlock_struct *lock); +void xt_thread_lock_info_free(XTThreadLockInfoPtr ptr); + +void xt_thread_lock_info_add_owner (XTThreadLockInfoPtr ptr); +void xt_thread_lock_info_release_owner (XTThreadLockInfoPtr ptr); + +void xt_trace_thread_locks(XTThread *self); + +#endif // XT_THREAD_LOCK_INFO +#endif // __xt_locklist_h__ diff --git a/storage/pbxt/src/memory_xt.cc b/storage/pbxt/src/memory_xt.cc new file mode 100644 index 00000000000..6da8673cfcc --- /dev/null +++ b/storage/pbxt/src/memory_xt.cc @@ -0,0 +1,1138 @@ +/* Copyright (c) 2005 PrimeBase Technologies GmbH + * + * PrimeBase XT + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You 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 + * + * 2005-01-04 Paul McCullagh + * + * H&G2JCtL + */ + +#include "xt_config.h" + +#include <stdio.h> +#include <errno.h> +#include <stdlib.h> +#include <string.h> + +#include "pthread_xt.h" +#include "thread_xt.h" +#include "strutil_xt.h" +#include "trace_xt.h" + +#ifdef DEBUG +#define RECORD_MM +#endif + +#ifdef DEBUG + +#undef xt_malloc +#undef xt_calloc +#undef xt_realloc +#undef xt_free +#undef xt_pfree + +#undef xt_malloc_ns +#undef xt_calloc_ns +#undef xt_realloc_ns +#undef xt_free_ns + +void *xt_malloc(XTThreadPtr self, size_t size); +void *xt_calloc(XTThreadPtr self, size_t size); +xtBool xt_realloc(XTThreadPtr self, void **ptr, size_t size); +void xt_free(XTThreadPtr self, void *ptr); +void xt_pfree(XTThreadPtr self, void **ptr); + +void *xt_malloc_ns(size_t size); +void *xt_calloc_ns(size_t size); +xtBool xt_realloc_ns(void **ptr, size_t size); +void xt_free_ns(void *ptr); + +#define ADD_TOTAL_ALLOCS 4000 + +#define SHIFT_RIGHT(ptr, n) memmove(((char *) (ptr)) + sizeof(MissingMemoryRec), (ptr), (long) (n) * sizeof(MissingMemoryRec)) +#define SHIFT_LEFT(ptr, n) memmove((ptr), ((char *) (ptr)) + sizeof(MissingMemoryRec), (long) (n) * sizeof(MissingMemoryRec)) + +#define STACK_TRACE_DEPTH 4 + +typedef struct MissingMemory { + void *mm_ptr; + xtWord4 id; + xtWord2 line_nr; + xtWord2 trace_count; + c_char *mm_file; + c_char *mm_func[STACK_TRACE_DEPTH]; +} MissingMemoryRec, *MissingMemoryPtr; + +static MissingMemoryRec *mm_addresses = NULL; +static long mm_nr_in_use = 0L; +static long mm_total_allocated = 0L; +static xtWord4 mm_alloc_count = 0; +static xt_mutex_type mm_mutex; + +#ifdef RECORD_MM +static long mm_find_pointer(void *ptr); +#endif + +#endif + +/* + * ----------------------------------------------------------------------- + * STANDARD SYSTEM BASED MEMORY ALLOCATION + */ + +xtPublic void *xt_malloc(XTThreadPtr self, size_t size) +{ + void *ptr; + + if (!(ptr = malloc(size))) { + xt_throw_errno(XT_CONTEXT, XT_ENOMEM); + return NULL; + } + return ptr; +} + +xtPublic xtBool xt_realloc(XTThreadPtr self, void **ptr, size_t size) +{ + void *new_ptr; + + if (!(new_ptr = realloc(*ptr, size))) { + xt_throw_errno(XT_CONTEXT, XT_ENOMEM); + return FAILED; + } + *ptr = new_ptr; + return OK; +} + +xtPublic void xt_free(XTThreadPtr self __attribute__((unused)), void *ptr) +{ + free(ptr); +} + +xtPublic void *xt_calloc(XTThreadPtr self, size_t size) +{ + void *ptr; + + if ((ptr = xt_malloc(self, size))) + memset(ptr, 0, size); + return ptr; +} + +#undef xt_pfree + +xtPublic void xt_pfree(XTThreadPtr self, void **ptr) +{ + if (*ptr) { + void *p = *ptr; + + *ptr = NULL; + xt_free(self, p); + } +} + +/* + * ----------------------------------------------------------------------- + * SYSTEM MEMORY ALLOCATION WITH A THREAD + */ + +xtPublic void *xt_malloc_ns(size_t size) +{ + void *ptr; + + if (!(ptr = malloc(size))) { + xt_register_errno(XT_REG_CONTEXT, XT_ENOMEM); + return NULL; + } + return ptr; +} + +xtPublic void *xt_calloc_ns(size_t size) +{ + void *ptr; + + if (!(ptr = malloc(size))) { + xt_register_errno(XT_REG_CONTEXT, XT_ENOMEM); + return NULL; + } + memset(ptr, 0, size); + return ptr; +} + +xtPublic xtBool xt_realloc_ns(void **ptr, size_t size) +{ + void *new_ptr; + + if (!(new_ptr = realloc(*ptr, size))) + return xt_register_errno(XT_REG_CONTEXT, XT_ENOMEM); + *ptr = new_ptr; + return OK; +} + +xtPublic void xt_free_ns(void *ptr) +{ + free(ptr); +} + +#ifdef DEBUG + +/* + * ----------------------------------------------------------------------- + * MEMORY SEARCHING CODE + */ + +#define MM_THROW_ASSERTION(str) mm_throw_assertion(self, __FUNC__, __FILE__, __LINE__, str) + +static void mm_throw_assertion(XTThreadPtr self, c_char *func, c_char *file, u_int line, c_char *str) +{ + printf("***** MM:FATAL %s\n", str); + xt_throw_assertion(self, func, file, line, str); +} + +/* + * ----------------------------------------------------------------------- + * MEMORY SEARCHING CODE + */ + +static int mm_debug_ik_inc; +static int mm_debug_ik_dec; +static int mm_debug_ik_no; + +/* + * Call this function where the missing memory + * is referenced. + */ +xtPublic void mm_trace_inc(XTThreadPtr self, XTMMTraceRefPtr tr) +{ + int i; + +#ifdef RECORD_MM + if (xt_lock_mutex(self, &mm_mutex)) { + long mm; + + mm = mm_find_pointer(tr); + if (mm >= 0) + mm_addresses[mm].trace_count = 1; + xt_unlock_mutex(self, &mm_mutex); + } +#endif + mm_debug_ik_inc++; + if (tr->mm_pos < XT_MM_STACK_TRACE-1) { + tr->mm_trace[tr->mm_pos++] = self->t_name[0] == 'S' ? XT_MM_TRACE_SW_INC : XT_MM_TRACE_INC; + for (i=1; i<=XT_MM_TRACE_DEPTH; i++) { + if (self->t_call_top-i < 0) + break; + if (tr->mm_pos < XT_MM_STACK_TRACE-1) { + tr->mm_line[tr->mm_pos] = self->t_call_stack[self->t_call_top-i].cs_line; + tr->mm_trace[tr->mm_pos++] = self->t_call_stack[self->t_call_top-i].cs_func; + } + else if (tr->mm_pos < XT_MM_STACK_TRACE) + tr->mm_trace[tr->mm_pos++] = XT_MM_TRACE_ERROR; + } + } + else if (tr->mm_pos < XT_MM_STACK_TRACE) + tr->mm_trace[tr->mm_pos++] = XT_MM_TRACE_ERROR; +} + +xtPublic void mm_trace_dec(XTThreadPtr self, XTMMTraceRefPtr tr) +{ + int i; + +#ifdef RECORD_MM + if (xt_lock_mutex(self, &mm_mutex)) { + long mm; + + mm = mm_find_pointer(tr); + if (mm >= 0) + mm_addresses[mm].trace_count = 1; + xt_unlock_mutex(self, &mm_mutex); + } +#endif + mm_debug_ik_dec++; + if (tr->mm_pos < XT_MM_STACK_TRACE-1) { + tr->mm_trace[tr->mm_pos++] = self->t_name[0] == 'S' ? XT_MM_TRACE_SW_DEC : XT_MM_TRACE_DEC; + for (i=1; i<=XT_MM_TRACE_DEPTH; i++) { + if (self->t_call_top-i < 0) + break; + if (tr->mm_pos < XT_MM_STACK_TRACE-1) { + tr->mm_line[tr->mm_pos] = self->t_call_stack[self->t_call_top-i].cs_line; + tr->mm_trace[tr->mm_pos++] = self->t_call_stack[self->t_call_top-i].cs_func; + } + else if (tr->mm_pos < XT_MM_STACK_TRACE) + tr->mm_trace[tr->mm_pos++] = XT_MM_TRACE_ERROR; + } + } + else if (tr->mm_pos < XT_MM_STACK_TRACE) + tr->mm_trace[tr->mm_pos++] = XT_MM_TRACE_ERROR; +} + +xtPublic void mm_trace_init(XTThreadPtr self, XTMMTraceRefPtr tr) +{ + mm_debug_ik_no++; + tr->mm_id = (u_int) mm_debug_ik_no; + tr->mm_pos = 0; + mm_trace_inc(self, tr); +} + +xtPublic void mm_trace_print(XTMMTraceRefPtr tr) +{ + int i, cnt = 0; + + for (i=0; i<tr->mm_pos; i++) { + if (tr->mm_trace[i] == XT_MM_TRACE_INC) { + if (i > 0) + printf("\n"); + cnt++; + printf("INC (%d) ", cnt); + } + else if (tr->mm_trace[i] == XT_MM_TRACE_SW_INC) { + if (i > 0) + printf("\n"); + printf("SW-DEC (%d) ", cnt); + cnt--; + } + else if (tr->mm_trace[i] == XT_MM_TRACE_DEC) { + if (i > 0) + printf("\n"); + printf("DEC (%d) ", cnt); + cnt--; + } + else if (tr->mm_trace[i] == XT_MM_TRACE_SW_DEC) { + if (i > 0) + printf("\n"); + printf("SW-DEC (%d) ", cnt); + cnt--; + } + else if (tr->mm_trace[i] == XT_MM_TRACE_ERROR) { + if (i > 0) + printf("\n"); + printf("ERROR: Space out"); + } + else + printf("%s(%d) ", tr->mm_trace[i], (int) tr->mm_line[i]); + } + printf("\n"); +} + +/* Call this function on exit, when you know the memory is missing. */ +static void mm_debug_trace_count(XTMMTraceRefPtr tr) +{ + printf("MM Trace ID: %d\n", tr->mm_id); + mm_trace_print(tr); +} + +/* The give the sum of allocations, etc. */ +static void mm_debug_trace_sum(void) +{ + if (mm_debug_ik_no) { + printf("MM Trace INC: %d\n", mm_debug_ik_inc); + printf("MM Trace DEC: %d\n", mm_debug_ik_dec); + printf("MM Trace ALL: %d\n", mm_debug_ik_no); + } +} + +/* + * ----------------------------------------------------------------------- + * DEBUG MEMORY ALLOCATION AND HEAP CHECKING + */ + +#ifdef RECORD_MM +static long mm_find_pointer(void *ptr) +{ + register long i, n, guess; + + i = 0; + n = mm_nr_in_use; + while (i < n) { + guess = (i + n - 1) >> 1; + if (ptr == mm_addresses[guess].mm_ptr) + return(guess); + if (ptr < mm_addresses[guess].mm_ptr) + n = guess; + else + i = guess + 1; + } + return(-1); +} + +static long mm_add_pointer(void *ptr, u_int id) +{ +#pragma unused(id) + register int i, n, guess; + + if (mm_nr_in_use == mm_total_allocated) { + /* Not enough space, add more: */ + MissingMemoryRec *new_addresses; + + new_addresses = (MissingMemoryRec *) xt_calloc_ns(sizeof(MissingMemoryRec) * (mm_total_allocated + ADD_TOTAL_ALLOCS)); + if (!new_addresses) + return(-1); + + if (mm_addresses) { + memcpy(new_addresses, mm_addresses, sizeof(MissingMemoryRec) * mm_total_allocated); + free(mm_addresses); + } + + mm_addresses = new_addresses; + mm_total_allocated += ADD_TOTAL_ALLOCS; + } + + i = 0; + n = mm_nr_in_use; + while (i < n) { + guess = (i + n - 1) >> 1; + if (ptr < mm_addresses[guess].mm_ptr) + n = guess; + else + i = guess + 1; + } + + SHIFT_RIGHT(&mm_addresses[i], mm_nr_in_use - i); + mm_nr_in_use++; + mm_addresses[i].mm_ptr = ptr; + return(i); +} + +xtPublic char *mm_watch_point = 0; + +static long mm_remove_pointer(void *ptr) +{ + register int i, n, guess; + + if (mm_watch_point == ptr) + printf("Hit watch point!\n"); + + i = 0; + n = mm_nr_in_use; + while (i < n) { + guess = (i + n - 1) >> 1; + if (ptr == mm_addresses[guess].mm_ptr) + goto remove; + if (ptr < mm_addresses[guess].mm_ptr) + n = guess; + else + i = guess + 1; + } + return(-1); + + remove: + /* Decrease the number of sets, and shift left: */ + mm_nr_in_use--; + SHIFT_LEFT(&mm_addresses[guess], mm_nr_in_use - guess); + return(guess); +} + +static void mm_add_core_ptr(XTThreadPtr self, void *ptr, u_int id, u_int line, c_char *file_name) +{ + long mm; + + mm = mm_add_pointer(ptr, id); + if (mm < 0) { + MM_THROW_ASSERTION("MM ERROR: Cannot allocate table big enough!"); + return; + } + + /* Record the pointer: */ + if (mm_alloc_count >= 4115 && mm_alloc_count <= 4130) { + if (id) + mm_addresses[mm].id = id; + else + mm_addresses[mm].id = mm_alloc_count++; + } + else { + if (id) + mm_addresses[mm].id = id; + else + mm_addresses[mm].id = mm_alloc_count++; + } + mm_addresses[mm].mm_ptr = ptr; + mm_addresses[mm].line_nr = (ushort) line; + if (file_name) + mm_addresses[mm].mm_file = file_name; + else + mm_addresses[mm].mm_file = "?"; + if (self) { + for (int i=1; i<=STACK_TRACE_DEPTH; i++) { + if (self->t_call_top-i >= 0) + mm_addresses[mm].mm_func[i-1] = self->t_call_stack[self->t_call_top-i].cs_func; + else + mm_addresses[mm].mm_func[i-1] = NULL; + } + } + else { + for (int i=0; i<STACK_TRACE_DEPTH; i++) + mm_addresses[mm].mm_func[i] = NULL; + } +} + +static void mm_remove_core_ptr(void *ptr) +{ + XTThreadPtr self = NULL; + long mm; + + mm = mm_remove_pointer(ptr); + if (mm < 0) { + MM_THROW_ASSERTION("Pointer not allocated"); + return; + } +} + +static void mm_throw_assertion(MissingMemoryPtr mm_ptr, void *p, c_char *message); + +static long mm_find_core_ptr(void *ptr) +{ + long mm; + + mm = mm_find_pointer(ptr); + if (mm < 0) + mm_throw_assertion(NULL, ptr, "Pointer not allocated"); + return(mm); +} + +static void mm_replace_core_ptr(long i, void *ptr) +{ + XTThreadPtr self = NULL; + MissingMemoryRec tmp = mm_addresses[i]; + long mm; + + mm_remove_pointer(mm_addresses[i].mm_ptr); + mm = mm_add_pointer(ptr, mm_addresses[i].id); + if (mm < 0) { + MM_THROW_ASSERTION("Cannot allocate table big enough!"); + return; + } + mm_addresses[mm] = tmp; + mm_addresses[mm].mm_ptr = ptr; +} +#endif + +static void mm_throw_assertion(MissingMemoryPtr mm_ptr, void *p, c_char *message) +{ + XTThreadPtr self = NULL; + char str[200]; + + if (mm_ptr) { + sprintf(str, "MM: %08lX (#%ld) %s:%d %s", + (unsigned long) mm_ptr->mm_ptr, + (long) mm_ptr->id, + xt_last_name_of_path(mm_ptr->mm_file), + (int) mm_ptr->line_nr, + message); + } + else + sprintf(str, "MM: %08lX %s", (unsigned long) p, message); + MM_THROW_ASSERTION(str); +} + +/* + * ----------------------------------------------------------------------- + * MISSING MEMORY PUBLIC ROUTINES + */ + +#define MEM_DEBUG_HDR_SIZE offsetof(MemoryDebugRec, data) +#define MEM_TRAILER_SIZE 2 +#define MEM_HEADER 0x01010101 +#define MEM_FREED 0x03030303 +#define MEM_TRAILER_BYTE 0x02 +#define MEM_FREED_BYTE 0x03 + +typedef struct MemoryDebug { + xtWord4 check; + xtWord4 size; + char data[200]; +} MemoryDebugRec, *MemoryDebugPtr; + +static size_t mm_checkmem(XTThreadPtr self, MissingMemoryPtr mm_ptr, void *p, xtBool freeme) +{ + unsigned char *ptr = (unsigned char *) p - MEM_DEBUG_HDR_SIZE; + MemoryDebugPtr debug_ptr = (MemoryDebugPtr) ptr; + size_t size = debug_ptr->size; + long a_value; /* Added to simplfy debugging. */ + + if (!ASSERT(p)) + return(0); + if (!ASSERT(((long) p & 1L) == 0)) + return(0); + a_value = MEM_FREED; + if (debug_ptr->check == MEM_FREED) { + mm_throw_assertion(mm_ptr, p, "Pointer already freed 'debug_ptr->check != MEM_FREED'"); + return(0); + } + a_value = MEM_HEADER; + if (debug_ptr->check != MEM_HEADER) { + mm_throw_assertion(mm_ptr, p, "Header not valid 'debug_ptr->check != MEM_HEADER'"); + return(0); + } + a_value = MEM_TRAILER_BYTE; + if (!(*((unsigned char *) ptr + size + MEM_DEBUG_HDR_SIZE) == MEM_TRAILER_BYTE && + *((unsigned char *) ptr + size + MEM_DEBUG_HDR_SIZE + 1L) == MEM_TRAILER_BYTE)) { + mm_throw_assertion(mm_ptr, p, "Trailer overwritten"); + return(0); + } + + if (freeme) { + debug_ptr->check = MEM_FREED; + *((unsigned char *) ptr + size + MEM_DEBUG_HDR_SIZE) = MEM_FREED_BYTE; + *((unsigned char *) ptr + size + MEM_DEBUG_HDR_SIZE + 1L) = MEM_FREED_BYTE; + + memset(((unsigned char *) ptr) + MEM_DEBUG_HDR_SIZE, 0xF5, size); + xt_free(self, ptr); + } + + return size; +} + +xtBool xt_mm_scan_core(void) +{ + long mm; + + if (!mm_addresses) + return TRUE; + + if (!xt_lock_mutex(NULL, &mm_mutex)) + return TRUE; + + for (mm=0; mm<mm_nr_in_use; mm++) { + mm_checkmem(NULL, &mm_addresses[mm], mm_addresses[mm].mm_ptr, FALSE); + } + + xt_unlock_mutex(NULL, &mm_mutex); + return TRUE; +} + +void xt_mm_memmove(void *block, void *dest, void *source, size_t size) +{ + if (block) { + MemoryDebugPtr debug_ptr = (MemoryDebugPtr) ((char *) block - MEM_DEBUG_HDR_SIZE); + +#ifdef RECORD_MM + if (xt_lock_mutex(NULL, &mm_mutex)) { + mm_find_core_ptr(block); + xt_unlock_mutex(NULL, &mm_mutex); + } +#endif + mm_checkmem(NULL, NULL, block, FALSE); + + if (dest < block || (char *) dest > (char *) block + debug_ptr->size) + mm_throw_assertion(NULL, block, "Destination not in block"); + if ((char *) dest + size > (char *) block + debug_ptr->size) + mm_throw_assertion(NULL, block, "Copy will overwrite memory"); + } + + memmove(dest, source, size); +} + +void xt_mm_memcpy(void *block, void *dest, void *source, size_t size) +{ + if (block) { + MemoryDebugPtr debug_ptr = (MemoryDebugPtr) ((char *) block - MEM_DEBUG_HDR_SIZE); + +#ifdef RECORD_MM + if (xt_lock_mutex(NULL, &mm_mutex)) { + mm_find_core_ptr(block); + xt_unlock_mutex(NULL, &mm_mutex); + } +#endif + mm_checkmem(NULL, NULL, block, FALSE); + + if (dest < block || (char *) dest > (char *) block + debug_ptr->size) + mm_throw_assertion(NULL, block, "Destination not in block"); + if ((char *) dest + size > (char *) block + debug_ptr->size) + mm_throw_assertion(NULL, block, "Copy will overwrite memory"); + } + + memcpy(dest, source, size); +} + +void xt_mm_memset(void *block, void *dest, int value, size_t size) +{ + if (block) { + MemoryDebugPtr debug_ptr = (MemoryDebugPtr) ((char *) block - MEM_DEBUG_HDR_SIZE); + +#ifdef RECORD_MM + if (xt_lock_mutex(NULL, &mm_mutex)) { + mm_find_core_ptr(block); + xt_unlock_mutex(NULL, &mm_mutex); + } +#endif + mm_checkmem(NULL, NULL, block, FALSE); + + if (dest < block || (char *) dest > (char *) block + debug_ptr->size) + mm_throw_assertion(NULL, block, "Destination not in block"); + if ((char *) dest + size > (char *) block + debug_ptr->size) + mm_throw_assertion(NULL, block, "Copy will overwrite memory"); + } + + memset(dest, value, size); +} + +void *xt_mm_malloc(XTThreadPtr self, size_t size, u_int line __attribute__((unused)), c_char *file __attribute__((unused))) +{ + unsigned char *p; + + if (size > (600*1024*1024)) + mm_throw_assertion(NULL, NULL, "Very large block allocated - meaybe error"); + p = (unsigned char *) xt_malloc(self, size + MEM_DEBUG_HDR_SIZE + MEM_TRAILER_SIZE); + if (!p) + return NULL; + + memset(p, 0x55, size + MEM_DEBUG_HDR_SIZE + MEM_TRAILER_SIZE); + + ((MemoryDebugPtr) p)->check = MEM_HEADER; + ((MemoryDebugPtr) p)->size = size; + *(p + size + MEM_DEBUG_HDR_SIZE) = MEM_TRAILER_BYTE; + *(p + size + MEM_DEBUG_HDR_SIZE + 1L) = MEM_TRAILER_BYTE; + +#ifdef RECORD_MM + xt_lock_mutex(self, &mm_mutex); + mm_add_core_ptr(self, p + MEM_DEBUG_HDR_SIZE, 0, line, file); + xt_unlock_mutex(self, &mm_mutex); +#endif + + return p + MEM_DEBUG_HDR_SIZE; +} + +void *xt_mm_calloc(XTThreadPtr self, size_t size, u_int line __attribute__((unused)), c_char *file __attribute__((unused))) +{ + unsigned char *p; + + if (size > (500*1024*1024)) + mm_throw_assertion(NULL, NULL, "Very large block allocated - meaybe error"); + p = (unsigned char *) xt_calloc(self, size + MEM_DEBUG_HDR_SIZE + MEM_TRAILER_SIZE); + if (!p) + return NULL; + + ((MemoryDebugPtr) p)->check = MEM_HEADER; + ((MemoryDebugPtr) p)->size = size; + *(p + size + MEM_DEBUG_HDR_SIZE) = MEM_TRAILER_BYTE; + *(p + size + MEM_DEBUG_HDR_SIZE + 1L) = MEM_TRAILER_BYTE; + +#ifdef RECORD_MM + xt_lock_mutex(self, &mm_mutex); + mm_add_core_ptr(self, p + MEM_DEBUG_HDR_SIZE, 0, line, file); + xt_unlock_mutex(self, &mm_mutex); +#endif + + return p + MEM_DEBUG_HDR_SIZE; +} + +xtBool xt_mm_sys_realloc(XTThreadPtr self, void **ptr, size_t newsize, u_int line, c_char *file) +{ + return xt_mm_realloc(self, ptr, newsize, line, file); +} + +xtBool xt_mm_realloc(XTThreadPtr self, void **ptr, size_t newsize, u_int line, c_char *file) +{ + unsigned char *oldptr = (unsigned char *) *ptr; + size_t size; +#ifdef RECORD_MM + long mm; +#endif + unsigned char *pnew; + + if (!oldptr) { + *ptr = xt_mm_malloc(self, newsize, line, file); + return *ptr ? TRUE : FALSE; + } + +#ifdef RECORD_MM + xt_lock_mutex(self, &mm_mutex); + if ((mm = mm_find_core_ptr(oldptr)) < 0) { + xt_unlock_mutex(self, &mm_mutex); + xt_throw_errno(XT_CONTEXT, XT_ENOMEM); + return FAILED; + } + xt_unlock_mutex(self, &mm_mutex); +#endif + + oldptr = oldptr - MEM_DEBUG_HDR_SIZE; + size = ((MemoryDebugPtr) oldptr)->size; + + ASSERT(((MemoryDebugPtr) oldptr)->check == MEM_HEADER); + ASSERT(*((unsigned char *) oldptr + size + MEM_DEBUG_HDR_SIZE) == MEM_TRAILER_BYTE && + *((unsigned char *) oldptr + size + MEM_DEBUG_HDR_SIZE + 1L) == MEM_TRAILER_BYTE); + + /* Realloc allways moves! */ + pnew = (unsigned char *) xt_malloc(self, newsize + MEM_DEBUG_HDR_SIZE + MEM_TRAILER_SIZE); + if (!pnew) { + xt_throw_errno(XT_CONTEXT, XT_ENOMEM); + return FAILED; + } + + if (newsize > size) { + memcpy(((MemoryDebugPtr) pnew)->data, ((MemoryDebugPtr) oldptr)->data, size); + memset(((MemoryDebugPtr) pnew)->data + size, 0x55, newsize - size); + } + else + memcpy(((MemoryDebugPtr) pnew)->data, ((MemoryDebugPtr) oldptr)->data, newsize); + + ((MemoryDebugPtr) pnew)->check = MEM_HEADER; + ((MemoryDebugPtr) pnew)->size = newsize; + *(pnew + newsize + MEM_DEBUG_HDR_SIZE) = MEM_TRAILER_BYTE; + *(pnew + newsize + MEM_DEBUG_HDR_SIZE + 1L) = MEM_TRAILER_BYTE; + +#ifdef RECORD_MM + xt_lock_mutex(self, &mm_mutex); + if ((mm = mm_find_core_ptr(oldptr + MEM_DEBUG_HDR_SIZE)) < 0) { + xt_unlock_mutex(self, &mm_mutex); + xt_throw_errno(XT_CONTEXT, XT_ENOMEM); + return FAILED; + } + mm_replace_core_ptr(mm, pnew + MEM_DEBUG_HDR_SIZE); + xt_unlock_mutex(self, &mm_mutex); +#endif + + memset(oldptr, 0x55, size + MEM_DEBUG_HDR_SIZE + MEM_TRAILER_SIZE); + xt_free(self, oldptr); + + *ptr = pnew + MEM_DEBUG_HDR_SIZE; + return OK; +} + +void xt_mm_free(XTThreadPtr self, void *ptr) +{ +#ifdef RECORD_MM + if (xt_lock_mutex(self, &mm_mutex)) { + mm_remove_core_ptr(ptr); + xt_unlock_mutex(self, &mm_mutex); + } +#endif + mm_checkmem(self, NULL, ptr, TRUE); +} + +void xt_mm_pfree(XTThreadPtr self, void **ptr) +{ + if (*ptr) { + void *p = *ptr; + + *ptr = NULL; + xt_mm_free(self, p); + } +} + +size_t xt_mm_malloc_size(XTThreadPtr self, void *ptr) +{ + size_t size = 0; + +#ifdef RECORD_MM + if (xt_lock_mutex(self, &mm_mutex)) { + mm_find_core_ptr(ptr); + xt_unlock_mutex(self, &mm_mutex); + } +#endif + size = mm_checkmem(self, NULL, ptr, FALSE); + return size; +} + +void xt_mm_check_ptr(XTThreadPtr self, void *ptr) +{ + mm_checkmem(self, NULL, ptr, FALSE); +} +#endif + +/* + * ----------------------------------------------------------------------- + * INIT/EXIT MEMORY + */ + +xtPublic xtBool xt_init_memory(void) +{ +#ifdef DEBUG + XTThreadPtr self = NULL; + + if (!xt_init_mutex_with_autoname(NULL, &mm_mutex)) + return FALSE; + + mm_addresses = (MissingMemoryRec *) malloc(sizeof(MissingMemoryRec) * ADD_TOTAL_ALLOCS); + if (!mm_addresses) { + MM_THROW_ASSERTION("MM ERROR: Insuffient memory to allocate MM table"); + xt_free_mutex(&mm_mutex); + return FALSE; + } + + memset(mm_addresses, 0, sizeof(MissingMemoryRec) * ADD_TOTAL_ALLOCS); + mm_total_allocated = ADD_TOTAL_ALLOCS; + mm_nr_in_use = 0L; + mm_alloc_count = 0L; +#endif + return TRUE; +} + +xtPublic void debug_ik_count(void *value); +xtPublic void debug_ik_sum(void); + +xtPublic void xt_exit_memory(void) +{ +#ifdef DEBUG + long mm; + int i; + + if (!mm_addresses) + return; + + xt_lock_mutex(NULL, &mm_mutex); + for (mm=0; mm<mm_nr_in_use; mm++) { + MissingMemoryPtr mm_ptr = &mm_addresses[mm]; + + xt_logf(XT_NS_CONTEXT, XT_LOG_FATAL, "MM: %p (#%ld) %s:%d Not freed\n", + mm_ptr->mm_ptr, + (long) mm_ptr->id, + xt_last_name_of_path(mm_ptr->mm_file), + (int) mm_ptr->line_nr); + for (i=0; i<STACK_TRACE_DEPTH; i++) { + if (mm_ptr->mm_func[i]) + xt_logf(XT_NS_CONTEXT, XT_LOG_FATAL, "MM: %s\n", mm_ptr->mm_func[i]); + } + /* + * Assumes we place out tracing function in the first + * position!! + */ + if (mm_ptr->trace_count) + mm_debug_trace_count((XTMMTraceRefPtr) mm_ptr->mm_ptr); + } + mm_debug_trace_sum(); + free(mm_addresses); + mm_addresses = NULL; + mm_nr_in_use = 0L; + mm_total_allocated = 0L; + mm_alloc_count = 0L; + xt_unlock_mutex(NULL, &mm_mutex); + + xt_free_mutex(&mm_mutex); +#endif +} + +/* + * ----------------------------------------------------------------------- + * MEMORY ALLOCATION UTILITIES + */ + +#ifdef DEBUG +char *xt_mm_dup_string(XTThreadPtr self, c_char *str, u_int line, c_char *file) +#else +char *xt_dup_string(XTThreadPtr self, c_char *str) +#endif +{ + size_t len; + char *new_str; + + if (!str) + return NULL; + len = strlen(str); +#ifdef DEBUG + new_str = (char *) xt_mm_malloc(self, len + 1, line, file); +#else + new_str = (char *) xt_malloc(self, len + 1); +#endif + if (new_str) + strcpy(new_str, str); + return new_str; +} + +xtPublic char *xt_long_to_str(XTThreadPtr self, long v) +{ + char str[50]; + + sprintf(str, "%lu", v); + return xt_dup_string(self, str); +} + +char *xt_dup_nstr(XTThreadPtr self, c_char *str, int start, size_t len) +{ + char *new_str = (char *) xt_malloc(self, len + 1); + + if (new_str) { + memcpy(new_str, str + start, len); + new_str[len] = 0; + } + return new_str; +} + +/* + * ----------------------------------------------------------------------- + * LIGHT WEIGHT CHECK FUNCTIONS + * Timing related memory management problems my not like the memset + * or other heavy checking. Try this... + */ + +#ifdef LIGHT_WEIGHT_CHECKS +xtPublic void *xt_malloc(XTThreadPtr self, size_t size) +{ + char *ptr; + + if (!(ptr = (char *) malloc(size+8))) { + xt_throw_errno(XT_CONTEXT, XT_ENOMEM); + return NULL; + } + *((xtWord4 *) ptr) = size; + *((xtWord4 *) (ptr + size + 4)) = 0x7E7EFEFE; + return ptr+4; +} + +xtPublic void xt_check_ptr(void *ptr) +{ + char *old_ptr; + xtWord4 size; + + old_ptr = (char *) ptr; + old_ptr -= 4; + size = *((xtWord4 *) old_ptr); + if (size == 0xDEADBEAF || *((xtWord4 *) (old_ptr + size + 4)) != 0x7E7EFEFE) { + char *dummy = NULL; + + xt_dump_trace(); + *dummy = 40; + } +} + +xtPublic xtBool xt_realloc(XTThreadPtr self, void **ptr, size_t size) +{ + char *old_ptr; + char *new_ptr; + + if ((old_ptr = (char *) *ptr)) { + void check_for_file(char *my_ptr, xtWord4 len); + + xt_check_ptr(old_ptr); + check_for_file((char *) old_ptr, *((xtWord4 *) (old_ptr - 4))); + if (!(new_ptr = (char *) realloc(old_ptr - 4, size+8))) { + xt_throw_errno(XT_CONTEXT, XT_ENOMEM); + return FAILED; + } + *((xtWord4 *) new_ptr) = size; + *((xtWord4 *) (new_ptr + size + 4)) = 0x7E7EFEFE; + *ptr = new_ptr+4; + return OK; + } + *ptr = xt_malloc(self, size); + return *ptr != NULL; +} + +xtPublic void xt_free(XTThreadPtr self __attribute__((unused)), void *ptr) +{ + char *old_ptr; + xtWord4 size; + void check_for_file(char *my_ptr, xtWord4 len); + + old_ptr = (char *) ptr; + old_ptr -= 4; + size = *((xtWord4 *) old_ptr); + if (size == 0xDEADBEAF || *((xtWord4 *) (old_ptr + size + 4)) != 0x7E7EFEFE) { + char *dummy = NULL; + + xt_dump_trace(); + *dummy = 41; + } + check_for_file((char *) ptr, size); + *((xtWord4 *) old_ptr) = 0xDEADBEAF; + *((xtWord4 *) (old_ptr + size)) = 0xEFEFDFDF; + *((xtWord4 *) (old_ptr + size + 4)) = 0x1F1F1F1F; + //memset(old_ptr, 0xEF, size+4); + free(old_ptr); +} + +xtPublic void *xt_calloc(XTThreadPtr self, size_t size) +{ + void *ptr; + + if ((ptr = xt_malloc(self, size))) + memset(ptr, 0, size); + return ptr; +} + +#undef xt_pfree + +xtPublic void xt_pfree(XTThreadPtr self, void **ptr) +{ + if (*ptr) { + void *p = *ptr; + + *ptr = NULL; + xt_free(self, p); + } +} + +xtPublic void *xt_malloc_ns(size_t size) +{ + char *ptr; + + if (!(ptr = (char *) malloc(size+8))) { + xt_register_errno(XT_REG_CONTEXT, XT_ENOMEM); + return NULL; + } + *((xtWord4 *) ptr) = size; + *((xtWord4 *) (ptr + size + 4)) = 0x7E7EFEFE; + return ptr+4; +} + +xtPublic void *xt_calloc_ns(size_t size) +{ + char *ptr; + + if (!(ptr = (char *) malloc(size+8))) { + xt_register_errno(XT_REG_CONTEXT, XT_ENOMEM); + return NULL; + } + *((xtWord4 *) ptr) = size; + *((xtWord4 *) (ptr + size + 4)) = 0x7E7EFEFE; + memset(ptr+4, 0, size); + return ptr+4; +} + +xtPublic xtBool xt_realloc_ns(void **ptr, size_t size) +{ + char *old_ptr; + char *new_ptr; + + if ((old_ptr = (char *) *ptr)) { + void check_for_file(char *my_ptr, xtWord4 len); + + xt_check_ptr(old_ptr); + check_for_file((char *) old_ptr, *((xtWord4 *) (old_ptr - 4))); + if (!(new_ptr = (char *) realloc(old_ptr - 4, size+8))) + return xt_register_errno(XT_REG_CONTEXT, XT_ENOMEM); + *((xtWord4 *) new_ptr) = size; + *((xtWord4 *) (new_ptr + size + 4)) = 0x7E7EFEFE; + *ptr = new_ptr+4; + return OK; + } + *ptr = xt_malloc_ns(size); + return *ptr != NULL; +} + +xtPublic void xt_free_ns(void *ptr) +{ + char *old_ptr; + xtWord4 size; + void check_for_file(char *my_ptr, xtWord4 len); + + old_ptr = (char *) ptr; + old_ptr -= 4; + size = *((xtWord4 *) old_ptr); + if (size == 0xDEADBEAF || *((xtWord4 *) (old_ptr + size + 4)) != 0x7E7EFEFE) { + char *dummy = NULL; + + xt_dump_trace(); + *dummy = 42; + } + check_for_file((char *) ptr, size); + *((xtWord4 *) old_ptr) = 0xDEADBEAF; + *((xtWord4 *) (old_ptr + size)) = 0xEFEFDFDF; + *((xtWord4 *) (old_ptr + size + 4)) = 0x1F1F1F1F; + //memset(old_ptr, 0xEE, size+4); + free(old_ptr); +} +#endif + diff --git a/storage/pbxt/src/memory_xt.h b/storage/pbxt/src/memory_xt.h new file mode 100644 index 00000000000..3b4150df185 --- /dev/null +++ b/storage/pbxt/src/memory_xt.h @@ -0,0 +1,126 @@ +/* Copyright (c) 2005 PrimeBase Technologies GmbH + * + * PrimeBase XT + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You 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 + * + * 2005-01-04 Paul McCullagh + * + * H&G2JCtL + */ +#ifndef __xt_memory_h__ +#define __xt_memory_h__ + +#include <string.h> + +#include "xt_defs.h" + +struct XTThread; + +#ifdef DEBUG + +#define XT_MM_STACK_TRACE 200 +#define XT_MM_TRACE_DEPTH 4 +#define XT_MM_TRACE_INC ((char *) 1) +#define XT_MM_TRACE_DEC ((char *) 2) +#define XT_MM_TRACE_SW_INC ((char *) 1) +#define XT_MM_TRACE_SW_DEC ((char *) 2) +#define XT_MM_TRACE_ERROR ((char *) 3) + +typedef struct XTMMTraceRef { + int mm_pos; + u_int mm_id; + u_int mm_line[XT_MM_STACK_TRACE]; + c_char *mm_trace[XT_MM_STACK_TRACE]; +} XTMMTraceRefRec, *XTMMTraceRefPtr; + +#define XT_MM_TRACE_INIT(x) (x)->mm_pos = 0 + +extern char *mm_watch_point; + +#define XT_MEMMOVE(b, d, s, l) xt_mm_memmove(b, d, s, l) +#define XT_MEMCPY(b, d, s, l) xt_mm_memcpy(b, d, s, l) +#define XT_MEMSET(b, d, v, l) xt_mm_memset(b, d, v, l) + +#define xt_malloc(t, s) xt_mm_malloc(t, s, __LINE__, __FILE__) +#define xt_calloc(t, s) xt_mm_calloc(t, s, __LINE__, __FILE__) +#define xt_realloc(t, p, s) xt_mm_realloc(t, p, s, __LINE__, __FILE__) +#define xt_free xt_mm_free +#define xt_pfree xt_mm_pfree + +#define xt_malloc_ns(s) xt_mm_malloc(NULL, s, __LINE__, __FILE__) +#define xt_calloc_ns(s) xt_mm_calloc(NULL, s, __LINE__, __FILE__) +#define xt_realloc_ns(p, s) xt_mm_sys_realloc(NULL, p, s, __LINE__, __FILE__) +#define xt_free_ns(p) xt_mm_free(NULL, p) + +void xt_mm_memmove(void *block, void *dest, void *source, size_t size); +void xt_mm_memcpy(void *block, void *dest, void *source, size_t size); +void xt_mm_memset(void *block, void *dest, int value, size_t size); + +void *xt_mm_malloc(struct XTThread *self, size_t size, u_int line, const char *file); +void *xt_mm_calloc(struct XTThread *self, size_t size, u_int line, const char *file); +xtBool xt_mm_realloc(struct XTThread *self, void **ptr, size_t size, u_int line, const char *file); +void xt_mm_free(struct XTThread *self, void *ptr); +void xt_mm_pfree(struct XTThread *self, void **ptr); +size_t xt_mm_malloc_size(struct XTThread *self, void *ptr); +void xt_mm_check_ptr(struct XTThread *self, void *ptr); +xtBool xt_mm_sys_realloc(struct XTThread *self, void **ptr, size_t newsize, u_int line, const char *file); + +#ifndef XT_SCAN_CORE_DEFINED +#define XT_SCAN_CORE_DEFINED +xtBool xt_mm_scan_core(void); +#endif + +void mm_trace_inc(struct XTThread *self, XTMMTraceRefPtr tr); +void mm_trace_dec(struct XTThread *self, XTMMTraceRefPtr tr); +void mm_trace_init(struct XTThread *self, XTMMTraceRefPtr tr); +void mm_trace_print(XTMMTraceRefPtr tr); + +#else + +#define XT_MEMMOVE(b, d, s, l) memmove(d, s, l) +#define XT_MEMCPY(b, d, s, l) memcpy(d, s, l) +#define XT_MEMSET(b, d, v, l) memset(d, v, l) + +void *xt_malloc(struct XTThread *self, size_t size); +void *xt_calloc(struct XTThread *self, size_t size); +xtBool xt_realloc(struct XTThread *self, void **ptr, size_t size); +void xt_free(struct XTThread *self, void *ptr); +void xt_pfree(struct XTThread *self, void **ptr); + +void *xt_malloc_ns(size_t size); +void *xt_calloc_ns(size_t size); +xtBool xt_realloc_ns(void **ptr, size_t size); +void xt_free_ns(void *ptr); + +#define xt_pfree(t, p) xt_pfree(t, (void **) p) + +#endif + +#ifdef DEBUG +#define xt_dup_string(t, s) xt_mm_dup_string(t, s, __LINE__, __FILE__) + +char *xt_mm_dup_string(struct XTThread *self, const char *path, u_int line, const char *file); +#else +char *xt_dup_string(struct XTThread *self, const char *path); +#endif + +char *xt_long_to_str(struct XTThread *self, long v); +char *xt_dup_nstr(struct XTThread *self, const char *str, int start, size_t len); + +xtBool xt_init_memory(void); +void xt_exit_memory(void); + +#endif diff --git a/storage/pbxt/src/myxt_xt.cc b/storage/pbxt/src/myxt_xt.cc new file mode 100644 index 00000000000..ba9a72c87a3 --- /dev/null +++ b/storage/pbxt/src/myxt_xt.cc @@ -0,0 +1,3245 @@ +/* Copyright (c) 2005 PrimeBase Technologies GmbH + * + * PrimeBase XT + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You 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 + * + * 2006-05-16 Paul McCullagh + * + * H&G2JCtL + * + * These functions implement the parts of PBXT which must conform to the + * key and row format used by MySQL. + */ + +#include "xt_config.h" + +#ifdef DRIZZLED +#include <drizzled/server_includes.h> +#include <drizzled/plugin.h> +#include <drizzled/show.h> +#include <drizzled/field/blob.h> +#include <drizzled/field/enum.h> +#include <drizzled/field/varstring.h> +#include <drizzled/current_session.h> +#include <drizzled/sql_lex.h> +#include <drizzled/session.h> +extern "C" struct charset_info_st *session_charset(Session *session); +extern pthread_key_t THR_Session; +#else +#include "mysql_priv.h" +#include <mysql/plugin.h> +#endif + +#ifdef HAVE_ISNAN +#include <math.h> +#endif + +#include "ha_pbxt.h" + +#include "myxt_xt.h" +#include "strutil_xt.h" +#include "database_xt.h" +#ifdef XT_STREAMING +#include "streaming_xt.h" +#endif +#include "cache_xt.h" +#include "datalog_xt.h" + +#ifdef DRIZZLED +#define swap_variables(TYPE, a, b) \ + do { \ + TYPE dummy; \ + dummy= a; \ + a= b; \ + b= dummy; \ + } while (0) + + +#define CMP_NUM(a,b) (((a) < (b)) ? -1 : ((a) == (b)) ? 0 : 1) +#else +#define get_rec_bits(bit_ptr, bit_ofs, bit_len) \ + (((((uint16) (bit_ptr)[1] << 8) | (uint16) (bit_ptr)[0]) >> (bit_ofs)) & \ + ((1 << (bit_len)) - 1)) +#endif + +#define FIX_LENGTH(cs, pos, length, char_length) \ + do { \ + if ((length) > char_length) \ + char_length= my_charpos(cs, pos, pos+length, char_length); \ + set_if_smaller(char_length,length); \ + } while(0) + +#ifdef store_key_length_inc +#undef store_key_length_inc +#endif +#define store_key_length_inc(key,length) \ +{ if ((length) < 255) \ + { *(key)++=(length); } \ + else \ + { *(key)=255; mi_int2store((key)+1,(length)); (key)+=3; } \ +} + +#define set_rec_bits(bits, bit_ptr, bit_ofs, bit_len) \ +{ \ + (bit_ptr)[0]= ((bit_ptr)[0] & ~(((1 << (bit_len)) - 1) << (bit_ofs))) | \ + ((bits) << (bit_ofs)); \ + if ((bit_ofs) + (bit_len) > 8) \ + (bit_ptr)[1]= ((bit_ptr)[1] & ~((1 << ((bit_len) - 8 + (bit_ofs))) - 1)) | \ + ((bits) >> (8 - (bit_ofs))); \ +} + +#define clr_rec_bits(bit_ptr, bit_ofs, bit_len) \ + set_rec_bits(0, bit_ptr, bit_ofs, bit_len) + +static ulong my_calc_blob_length(uint length, xtWord1 *pos) +{ + switch (length) { + case 1: + return (uint) (uchar) *pos; + case 2: + return (uint) uint2korr(pos); + case 3: + return uint3korr(pos); + case 4: + return uint4korr(pos); + default: + break; + } + return 0; /* Impossible */ +} + +static void my_store_blob_length(byte *pos,uint pack_length,uint length) +{ + switch (pack_length) { + case 1: + *pos= (uchar) length; + break; + case 2: + int2store(pos,length); + break; + case 3: + int3store(pos,length); + break; + case 4: + int4store(pos,length); + default: + break; + } + return; +} + +static int my_compare_text(MX_CONST_CHARSET_INFO *charset_info, uchar *a, uint a_length, + uchar *b, uint b_length, my_bool part_key, + my_bool skip_end_space __attribute__((unused))) +{ + if (!part_key) + /* The last parameter is diff_if_only_endspace_difference, which means + * that end spaces are not ignored. We actually always want + * to ignore end spaces! + */ + return charset_info->coll->strnncollsp(charset_info, a, a_length, + b, b_length, /*(my_bool)!skip_end_space*/0); + return charset_info->coll->strnncoll(charset_info, a, a_length, + b, b_length, part_key); +} + +/* + * ----------------------------------------------------------------------- + * Create a key + */ + +/* + * Derived from _mi_pack_key() + */ +xtPublic u_int myxt_create_key_from_key(XTIndexPtr ind, xtWord1 *key, xtWord1 *old, u_int k_length) +{ + xtWord1 *start_key = key; + XTIndexSegRec *keyseg = ind->mi_seg; + + for (u_int i=0; i<ind->mi_seg_count && (int) k_length > 0; i++, old += keyseg->length, keyseg++) + { + enum ha_base_keytype type = (enum ha_base_keytype) keyseg->type; + u_int length = keyseg->length < k_length ? keyseg->length : k_length; + u_int char_length; + xtWord1 *pos; + MX_CONST_CHARSET_INFO *cs = keyseg->charset; + + if (keyseg->null_bit) { + k_length--; + if (!(*key++ = (xtWord1) 1 - *old++)) { /* Copy null marker */ + k_length -= length; + if (keyseg->flag & (HA_VAR_LENGTH_PART | HA_BLOB_PART)) { + k_length -= 2; /* Skip length */ + old += 2; + } + continue; /* Found NULL */ + } + } + char_length= (cs && cs->mbmaxlen > 1) ? length/cs->mbmaxlen : length; + pos = old; + if (keyseg->flag & HA_SPACE_PACK) { + uchar *end = pos + length; + if (type != HA_KEYTYPE_NUM) { + while (end > pos && end[-1] == ' ') + end--; + } + else { + while (pos < end && pos[0] == ' ') + pos++; + } + k_length -= length; + length = (u_int) (end-pos); + FIX_LENGTH(cs, pos, length, char_length); + store_key_length_inc(key, char_length); + memcpy((byte*) key,pos,(size_t) char_length); + key += char_length; + continue; + } + if (keyseg->flag & (HA_VAR_LENGTH_PART | HA_BLOB_PART)) { + /* Length of key-part used with mi_rkey() always 2 */ + u_int tmp_length = uint2korr(pos); + k_length -= 2 + length; + pos += 2; + set_if_smaller(length, tmp_length); /* Safety */ + FIX_LENGTH(cs, pos, length, char_length); + store_key_length_inc(key,char_length); + old +=2; /* Skip length */ + memcpy((char *) key, pos, (size_t) char_length); + key += char_length; + continue; + } + if (keyseg->flag & HA_SWAP_KEY) + { /* Numerical column */ + pos+=length; + k_length-=length; + while (length--) { + *key++ = *--pos; + } + continue; + } + FIX_LENGTH(cs, pos, length, char_length); + memcpy((byte*) key, pos, char_length); + if (length > char_length) + cs->cset->fill(cs, (char *) (key + char_length), length - char_length, ' '); + key += length; + k_length -= length; + } + + return (u_int) (key - start_key); +} + +/* Derived from _mi_make_key */ +xtPublic u_int myxt_create_key_from_row(XTIndexPtr ind, xtWord1 *key, xtWord1 *record, xtBool *no_duplicate) +{ + register XTIndexSegRec *keyseg = ind->mi_seg; + xtWord1 *pos; + xtWord1 *end; + xtWord1 *start; + + start = key; + for (u_int i=0; i<ind->mi_seg_count; i++, keyseg++) + { + enum ha_base_keytype type = (enum ha_base_keytype) keyseg->type; + u_int length = keyseg->length; + u_int char_length; + MX_CONST_CHARSET_INFO *cs = keyseg->charset; + + if (keyseg->null_bit) { + if (record[keyseg->null_pos] & keyseg->null_bit) { + *key++ = 0; /* NULL in key */ + + /* The point is, if a key contains a NULL value + * the duplicate checking must be disabled. + * This is because a NULL value is not considered + * equal to any other value. + */ + if (no_duplicate) + *no_duplicate = FALSE; + continue; + } + *key++ = 1; /* Not NULL */ + } + + char_length= ((cs && cs->mbmaxlen > 1) ? length/cs->mbmaxlen : length); + + pos = record + keyseg->start; + if (type == HA_KEYTYPE_BIT) + { + if (keyseg->bit_length) + { + uchar bits = get_rec_bits((uchar*) record + keyseg->bit_pos, + keyseg->bit_start, keyseg->bit_length); + *key++ = bits; + length--; + } + memcpy((byte*) key, pos, length); + key+= length; + continue; + } + if (keyseg->flag & HA_SPACE_PACK) + { + end = pos + length; + if (type != HA_KEYTYPE_NUM) { + while (end > pos && end[-1] == ' ') + end--; + } + else { + while (pos < end && pos[0] == ' ') + pos++; + } + length = (u_int) (end-pos); + FIX_LENGTH(cs, pos, length, char_length); + store_key_length_inc(key,char_length); + memcpy((byte*) key,(byte*) pos,(size_t) char_length); + key += char_length; + continue; + } + if (keyseg->flag & HA_VAR_LENGTH_PART) { + uint pack_length= (keyseg->bit_start == 1 ? 1 : 2); + uint tmp_length= (pack_length == 1 ? (uint) *(uchar*) pos : + uint2korr(pos)); + pos += pack_length; /* Skip VARCHAR length */ + set_if_smaller(length,tmp_length); + FIX_LENGTH(cs, pos, length, char_length); + store_key_length_inc(key,char_length); + memcpy((byte*) key,(byte*) pos,(size_t) char_length); + key += char_length; + continue; + } + if (keyseg->flag & HA_BLOB_PART) + { + u_int tmp_length = my_calc_blob_length(keyseg->bit_start, pos); + memcpy((byte*) &pos,pos+keyseg->bit_start,sizeof(char*)); + set_if_smaller(length,tmp_length); + FIX_LENGTH(cs, pos, length, char_length); + store_key_length_inc(key,char_length); + memcpy((byte*) key,(byte*) pos,(size_t) char_length); + key+= char_length; + continue; + } + if (keyseg->flag & HA_SWAP_KEY) + { /* Numerical column */ +#ifdef HAVE_ISNAN + if (type == HA_KEYTYPE_FLOAT) + { + float nr; + float4get(nr,pos); + if (isnan(nr)) + { + /* Replace NAN with zero */ + bzero(key,length); + key+=length; + continue; + } + } + else if (type == HA_KEYTYPE_DOUBLE) { + double nr; + + float8get(nr,pos); + if (isnan(nr)) { + bzero(key,length); + key+=length; + continue; + } + } +#endif + pos+=length; + while (length--) { + *key++ = *--pos; + } + continue; + } + FIX_LENGTH(cs, pos, length, char_length); + memcpy((byte*) key, pos, char_length); + if (length > char_length) + cs->cset->fill(cs, (char *) key + char_length, length - char_length, ' '); + key += length; + } + + return ind->mi_fix_key ? ind->mi_key_size : (u_int) (key - start); /* Return keylength */ +} + +xtPublic u_int myxt_create_foreign_key_from_row(XTIndexPtr ind, xtWord1 *key, xtWord1 *record, XTIndexPtr fkey_ind, xtBool *no_null) +{ + register XTIndexSegRec *keyseg = ind->mi_seg; + register XTIndexSegRec *fkey_keyseg = fkey_ind->mi_seg; + xtWord1 *pos; + xtWord1 *end; + xtWord1 *start; + + start = key; + for (u_int i=0; i<ind->mi_seg_count; i++, keyseg++, fkey_keyseg++) + { + enum ha_base_keytype type = (enum ha_base_keytype) keyseg->type; + u_int length = keyseg->length; + u_int char_length; + MX_CONST_CHARSET_INFO *cs = keyseg->charset; + xtBool is_null = FALSE; + + if (keyseg->null_bit) { + if (record[keyseg->null_pos] & keyseg->null_bit) { + is_null = TRUE; + if (no_null) + *no_null = FALSE; + } + } + + if (fkey_keyseg->null_bit) { + if (is_null) { + *key++ = 0; /* NULL in key */ + + /* The point is, if a key contains a NULL value + * the duplicate checking must be disabled. + * This is because a NULL value is not considered + * equal to any other value. + */ + continue; + } + *key++ = 1; /* Not NULL */ + } + + char_length= ((cs && cs->mbmaxlen > 1) ? length/cs->mbmaxlen : length); + + pos = record + keyseg->start; + if (type == HA_KEYTYPE_BIT) + { + if (keyseg->bit_length) + { + uchar bits = get_rec_bits((uchar*) record + keyseg->bit_pos, + keyseg->bit_start, keyseg->bit_length); + *key++ = bits; + length--; + } + memcpy((byte*) key, pos, length); + key+= length; + continue; + } + if (keyseg->flag & HA_SPACE_PACK) + { + end = pos + length; + if (type != HA_KEYTYPE_NUM) { + while (end > pos && end[-1] == ' ') + end--; + } + else { + while (pos < end && pos[0] == ' ') + pos++; + } + length = (u_int) (end-pos); + FIX_LENGTH(cs, pos, length, char_length); + store_key_length_inc(key,char_length); + memcpy((byte*) key,(byte*) pos,(size_t) char_length); + key += char_length; + continue; + } + if (keyseg->flag & HA_VAR_LENGTH_PART) { + uint pack_length= (keyseg->bit_start == 1 ? 1 : 2); + uint tmp_length= (pack_length == 1 ? (uint) *(uchar*) pos : + uint2korr(pos)); + pos += pack_length; /* Skip VARCHAR length */ + set_if_smaller(length,tmp_length); + FIX_LENGTH(cs, pos, length, char_length); + store_key_length_inc(key,char_length); + memcpy((byte*) key,(byte*) pos,(size_t) char_length); + key += char_length; + continue; + } + if (keyseg->flag & HA_BLOB_PART) + { + u_int tmp_length = my_calc_blob_length(keyseg->bit_start, pos); + memcpy((byte*) &pos,pos+keyseg->bit_start,sizeof(char*)); + set_if_smaller(length,tmp_length); + FIX_LENGTH(cs, pos, length, char_length); + store_key_length_inc(key,char_length); + memcpy((byte*) key,(byte*) pos,(size_t) char_length); + key+= char_length; + continue; + } + if (keyseg->flag & HA_SWAP_KEY) + { /* Numerical column */ +#ifdef HAVE_ISNAN + if (type == HA_KEYTYPE_FLOAT) + { + float nr; + float4get(nr,pos); + if (isnan(nr)) + { + /* Replace NAN with zero */ + bzero(key,length); + key+=length; + continue; + } + } + else if (type == HA_KEYTYPE_DOUBLE) { + double nr; + + float8get(nr,pos); + if (isnan(nr)) { + bzero(key,length); + key+=length; + continue; + } + } +#endif + pos+=length; + while (length--) { + *key++ = *--pos; + } + continue; + } + FIX_LENGTH(cs, pos, length, char_length); + memcpy((byte*) key, pos, char_length); + if (length > char_length) + cs->cset->fill(cs, (char *) key + char_length, length - char_length, ' '); + key += length; + } + + return fkey_ind->mi_fix_key ? fkey_ind->mi_key_size : (u_int) (key - start); /* Return keylength */ +} + +/* I may be overcautious here, but can I assume that + * null_ptr refers to my buffer. If I cannot, then I + * cannot use the set_notnull() method. + */ +static void mx_set_notnull_in_record(Field *field, char *record) +{ + if (field->null_ptr) + record[(uint) (field->null_ptr - (uchar *) field->table->record[0])] &= (uchar) ~field->null_bit; +} + +static xtBool mx_is_null_in_record(Field *field, char *record) +{ + if (field->null_ptr) { + if (record[(uint) (field->null_ptr - (uchar *) field->table->record[0])] & (uchar) field->null_bit) + return TRUE; + } + return FALSE; +} + +/* + * PBXT uses a completely different disk format to MySQL so I need a + * method that just returns the byte length and + * pointer to the data in a row. + */ +static char *mx_get_length_and_data(Field *field, char *dest, xtWord4 *len) +{ + char *from; + +#if MYSQL_VERSION_ID < 50114 + from = dest + field->offset(); +#else + from = dest + field->offset(field->table->record[0]); +#endif + switch (field->real_type()) { +#ifndef DRIZZLED + case MYSQL_TYPE_TINY_BLOB: + case MYSQL_TYPE_MEDIUM_BLOB: + case MYSQL_TYPE_LONG_BLOB: +#endif + case MYSQL_TYPE_BLOB: { + /* TODO - Check: this was the original comment: I must set + * *data to non-NULL value, *data == 0, means SQL NULL value. + */ + char *data; + + /* GOTCHA: There is no way this can work! field is shared + * between threads. + char *save = field->ptr; + + field->ptr = (char *) from; + ((Field_blob *) field)->get_ptr(&data); + field->ptr = save; // Restore org row pointer + */ + + xtWord4 packlength = ((Field_blob *) field)->pack_length() - field->table->s->blob_ptr_size; + memcpy(&data, ((char *) from)+packlength, sizeof(char*)); + + *len = ((Field_blob *) field)->get_length((byte *) from); + return data; + } +#ifndef DRIZZLED + case MYSQL_TYPE_STRING: + /* To write this function you would think Field_string::pack + * would serve as a good example, but as far as I can tell + * it has a bug: the test from[length-1] == ' ' assumes + * 1-byte chars. + * + * But this is not relevant because I believe lengthsp + * will give me the correct answer! + */ + *len = field->charset()->cset->lengthsp(field->charset(), from, field->field_length); + return from; + case MYSQL_TYPE_VAR_STRING: { + uint length=uint2korr(from); + + *len = length; + return from+HA_KEY_BLOB_LENGTH; + } +#endif + case MYSQL_TYPE_VARCHAR: { + uint length; + + if (((Field_varstring *) field)->length_bytes == 1) + length = *((unsigned char *) from); + else + length = uint2korr(from); + + *len = length; + return from+((Field_varstring *) field)->length_bytes; + } +#ifndef DRIZZLED + case MYSQL_TYPE_DECIMAL: + case MYSQL_TYPE_TINY: + case MYSQL_TYPE_SHORT: + case MYSQL_TYPE_LONG: + case MYSQL_TYPE_FLOAT: + case MYSQL_TYPE_DOUBLE: + case MYSQL_TYPE_NULL: + case MYSQL_TYPE_TIMESTAMP: + case MYSQL_TYPE_LONGLONG: + case MYSQL_TYPE_INT24: + case MYSQL_TYPE_DATE: + case MYSQL_TYPE_TIME: + case MYSQL_TYPE_DATETIME: + case MYSQL_TYPE_YEAR: + case MYSQL_TYPE_NEWDATE: + case MYSQL_TYPE_BIT: + case MYSQL_TYPE_NEWDECIMAL: + case MYSQL_TYPE_ENUM: + case MYSQL_TYPE_SET: + case MYSQL_TYPE_GEOMETRY: +#else + case DRIZZLE_TYPE_TINY: + case DRIZZLE_TYPE_LONG: + case DRIZZLE_TYPE_DOUBLE: + case DRIZZLE_TYPE_NULL: + case DRIZZLE_TYPE_TIMESTAMP: + case DRIZZLE_TYPE_LONGLONG: + case DRIZZLE_TYPE_DATETIME: + case DRIZZLE_TYPE_DATE: + case DRIZZLE_TYPE_NEWDECIMAL: + case DRIZZLE_TYPE_ENUM: + case DRIZZLE_TYPE_VIRTUAL: +#endif + break; + } + + *len = field->pack_length(); + return from; +} + +/* + * Set the length and data value of a field. + * + * If input data is NULL this is a NULL value. In this case + * we assume the null bit has been set and prepared + * the field as follows: + * + * According to the InnoDB implementation, we need + * to zero out the field data... + * "MySQL seems to assume the field for an SQL NULL + * value is set to zero or space. Not taking this into + * account caused seg faults with NULL BLOB fields, and + * bug number 154 in the MySQL bug database: GROUP BY + * and DISTINCT could treat NULL values inequal". + */ +static void mx_set_length_and_data(Field *field, char *dest, xtWord4 len, char *data) +{ + char *from; + +#if MYSQL_VERSION_ID < 50114 + from = dest + field->offset(); +#else + from = dest + field->offset(field->table->record[0]); +#endif + switch (field->real_type()) { +#ifndef DRIZZLED + case MYSQL_TYPE_TINY_BLOB: + case MYSQL_TYPE_MEDIUM_BLOB: + case MYSQL_TYPE_LONG_BLOB: +#endif + case MYSQL_TYPE_BLOB: { + /* GOTCHA: There is no way that this can work. + * field is shared, because table is shared! + char *save = field->ptr; + + field->ptr = (char *) from; + ((Field_blob *) field)->set_ptr(len, data); + field->ptr = save; // Restore org row pointer + */ + xtWord4 packlength = ((Field_blob *) field)->pack_length() - field->table->s->blob_ptr_size; + + ((Field_blob *) field)->store_length((byte *) from, packlength, len); + memcpy_fixed(((char *) from)+packlength, &data, sizeof(char*)); + + if (data) + mx_set_notnull_in_record(field, dest); + return; + } +#ifndef DRIZZLED + case MYSQL_TYPE_STRING: + if (data) { + mx_set_notnull_in_record(field, dest); + memcpy(from, data, len); + } + else + len = 0; + + /* And I think that fill will do this for me... */ + field->charset()->cset->fill(field->charset(), from + len, field->field_length - len, ' '); + return; + case MYSQL_TYPE_VAR_STRING: + int2store(from, len); + if (data) { + mx_set_notnull_in_record(field, dest); + memcpy(from+HA_KEY_BLOB_LENGTH, data, len); + } + return; +#endif + case MYSQL_TYPE_VARCHAR: + if (((Field_varstring *) field)->length_bytes == 1) + *((unsigned char *) from) = (unsigned char) len; + else + int2store(from, len); + if (data) { + mx_set_notnull_in_record(field, dest); + memcpy(from+((Field_varstring *) field)->length_bytes, data, len); + } + return; +#ifndef DRIZZLED + case MYSQL_TYPE_DECIMAL: + case MYSQL_TYPE_TINY: + case MYSQL_TYPE_SHORT: + case MYSQL_TYPE_LONG: + case MYSQL_TYPE_FLOAT: + case MYSQL_TYPE_DOUBLE: + case MYSQL_TYPE_NULL: + case MYSQL_TYPE_TIMESTAMP: + case MYSQL_TYPE_LONGLONG: + case MYSQL_TYPE_INT24: + case MYSQL_TYPE_DATE: + case MYSQL_TYPE_TIME: + case MYSQL_TYPE_DATETIME: + case MYSQL_TYPE_YEAR: + case MYSQL_TYPE_NEWDATE: + case MYSQL_TYPE_BIT: + case MYSQL_TYPE_NEWDECIMAL: + case MYSQL_TYPE_ENUM: + case MYSQL_TYPE_SET: + case MYSQL_TYPE_GEOMETRY: +#else + case DRIZZLE_TYPE_TINY: + case DRIZZLE_TYPE_LONG: + case DRIZZLE_TYPE_DOUBLE: + case DRIZZLE_TYPE_NULL: + case DRIZZLE_TYPE_TIMESTAMP: + case DRIZZLE_TYPE_LONGLONG: + case DRIZZLE_TYPE_DATETIME: + case DRIZZLE_TYPE_DATE: + case DRIZZLE_TYPE_NEWDECIMAL: + case DRIZZLE_TYPE_ENUM: + case DRIZZLE_TYPE_VIRTUAL: +#endif + break; + } + + if (data) { + mx_set_notnull_in_record(field, dest); + memcpy(from, data, len); + } + else + bzero(from, field->pack_length()); +} + +xtPublic void myxt_set_null_row_from_key(XTOpenTablePtr ot __attribute__((unused)), XTIndexPtr ind, xtWord1 *record) +{ + register XTIndexSegRec *keyseg = ind->mi_seg; + + for (u_int i=0; i<ind->mi_seg_count; i++, keyseg++) { + ASSERT_NS(keyseg->null_bit); + record[keyseg->null_pos] |= keyseg->null_bit; + } +} + +xtPublic void myxt_set_default_row_from_key(XTOpenTablePtr ot, XTIndexPtr ind, xtWord1 *record) +{ + XTTableHPtr tab = ot->ot_table; + TABLE *table = tab->tab_dic.dic_my_table; + XTIndexSegRec *keyseg = ind->mi_seg; + + xt_lock_mutex_ns(&tab->tab_dic_field_lock); + + for (u_int i=0; i<ind->mi_seg_count; i++, keyseg++) { + + u_int col_idx = keyseg->col_idx; + Field *field = table->field[col_idx]; + byte *field_save = field->ptr; + + field->ptr = table->s->default_values + keyseg->start; + memcpy(record + keyseg->start, field->ptr, field->pack_length()); + record[keyseg->null_pos] &= ~keyseg->null_bit; + record[keyseg->null_pos] |= table->s->default_values[keyseg->null_pos] & keyseg->null_bit; + + field->ptr = field_save; + } + + xt_unlock_mutex_ns(&tab->tab_dic_field_lock); +} + +/* Derived from _mi_put_key_in_record */ +xtPublic xtBool myxt_create_row_from_key(XTOpenTablePtr ot __attribute__((unused)), XTIndexPtr ind, xtWord1 *b_value, u_int key_len, xtWord1 *dest_buff) +{ + byte *record = (byte *) dest_buff; + register byte *key; + byte *pos,*key_end; + register XTIndexSegRec *keyseg = ind->mi_seg; + + /* GOTCHA: When selecting from multiple + * indexes the key values are "merged" into the + * same buffer!! + * This means that this function must not affect + * the value of any other feilds. + * + * I was setting all to NULL: + memset(dest_buff, 0xFF, table->s->null_bytes); + */ + key = (byte *) b_value; + key_end = key + key_len; + for (u_int i=0; i<ind->mi_seg_count; i++, keyseg++) { + if (keyseg->null_bit) { + if (!*key++) + { + record[keyseg->null_pos] |= keyseg->null_bit; + continue; + } + record[keyseg->null_pos] &= ~keyseg->null_bit; + } + if (keyseg->type == HA_KEYTYPE_BIT) + { + uint length = keyseg->length; + + if (keyseg->bit_length) + { + uchar bits= *key++; + set_rec_bits(bits, record + keyseg->bit_pos, keyseg->bit_start, + keyseg->bit_length); + length--; + } + else + { + clr_rec_bits(record + keyseg->bit_pos, keyseg->bit_start, + keyseg->bit_length); + } + memcpy(record + keyseg->start, (byte*) key, length); + key+= length; + continue; + } + if (keyseg->flag & HA_SPACE_PACK) + { + uint length; + get_key_length(length,key); +#ifdef CHECK_KEYS + if (length > keyseg->length || key+length > key_end) + goto err; +#endif + pos = record+keyseg->start; + if (keyseg->type != (int) HA_KEYTYPE_NUM) + { + memcpy(pos,key,(size_t) length); + bfill(pos+length,keyseg->length-length,' '); + } + else + { + bfill(pos,keyseg->length-length,' '); + memcpy(pos+keyseg->length-length,key,(size_t) length); + } + key+=length; + continue; + } + + if (keyseg->flag & HA_VAR_LENGTH_PART) + { + uint length; + get_key_length(length,key); +#ifdef CHECK_KEYS + if (length > keyseg->length || key+length > key_end) + goto err; +#endif + /* Store key length */ + if (keyseg->bit_start == 1) + *(uchar*) (record+keyseg->start)= (uchar) length; + else + int2store(record+keyseg->start, length); + /* And key data */ + memcpy(record+keyseg->start + keyseg->bit_start, (byte*) key, length); + key+= length; + } + else if (keyseg->flag & HA_BLOB_PART) + { + uint length; + get_key_length(length,key); +#ifdef CHECK_KEYS + if (length > keyseg->length || key+length > key_end) + goto err; +#endif + /* key is a pointer into ot_ind_rbuf, which should be + * safe until we move to the next index item! + */ + byte *key_ptr = key; // Cannot take the address of a register variable! + memcpy(record+keyseg->start+keyseg->bit_start, + (char*) &key_ptr,sizeof(char*)); + + my_store_blob_length(record+keyseg->start, + (uint) keyseg->bit_start,length); + key+=length; + } + else if (keyseg->flag & HA_SWAP_KEY) + { + byte *to= record+keyseg->start+keyseg->length; + byte *end= key+keyseg->length; +#ifdef CHECK_KEYS + if (end > key_end) + goto err; +#endif + do { + *--to= *key++; + } while (key != end); + continue; + } + else + { +#ifdef CHECK_KEYS + if (key+keyseg->length > key_end) + goto err; +#endif + memcpy(record+keyseg->start,(byte*) key, + (size_t) keyseg->length); + key+= keyseg->length; + } + + } + return OK; + +#ifdef CHECK_KEYS + err: +#endif + return FAILED; /* Crashed row */ +} + +/* + * ----------------------------------------------------------------------- + * Compare keys + */ + +static int my_compare_bin(uchar *a, uint a_length, uchar *b, uint b_length, + my_bool part_key, my_bool skip_end_space) +{ + uint length= min(a_length,b_length); + uchar *end= a+ length; + int flag; + + while (a < end) + if ((flag= (int) *a++ - (int) *b++)) + return flag; + if (part_key && b_length < a_length) + return 0; + if (skip_end_space && a_length != b_length) + { + int swap= 1; + /* + We are using space compression. We have to check if longer key + has next character < ' ', in which case it's less than the shorter + key that has an implicite space afterwards. + + This code is identical to the one in + strings/ctype-simple.c:my_strnncollsp_simple + */ + if (a_length < b_length) + { + /* put shorter key in a */ + a_length= b_length; + a= b; + swap= -1; /* swap sign of result */ + } + for (end= a + a_length-length; a < end ; a++) + { + if (*a != ' ') + return (*a < ' ') ? -swap : swap; + } + return 0; + } + return (int) (a_length-b_length); +} + +xtPublic u_int myxt_get_key_length(XTIndexPtr ind, xtWord1 *key_buf) +{ + register XTIndexSegRec *keyseg = ind->mi_seg; + register uchar *key_data = (uchar *) key_buf; + uint seg_len; + uint pack_len; + + for (u_int i=0; i<ind->mi_seg_count; i++, keyseg++) { + /* Handle NULL part */ + if (keyseg->null_bit) { + if (!*key_data++) + continue; + } + + switch ((enum ha_base_keytype) keyseg->type) { + case HA_KEYTYPE_TEXT: /* Ascii; Key is converted */ + if (keyseg->flag & HA_SPACE_PACK) { + get_key_pack_length(seg_len, pack_len, key_data); + } + else + seg_len = keyseg->length; + key_data += seg_len; + break; + case HA_KEYTYPE_BINARY: + if (keyseg->flag & HA_SPACE_PACK) { + get_key_pack_length(seg_len, pack_len, key_data); + } + else + seg_len = keyseg->length; + key_data += seg_len; + break; + case HA_KEYTYPE_VARTEXT1: + case HA_KEYTYPE_VARTEXT2: + get_key_pack_length(seg_len, pack_len, key_data); + key_data += seg_len; + break; + case HA_KEYTYPE_VARBINARY1: + case HA_KEYTYPE_VARBINARY2: + get_key_pack_length(seg_len, pack_len, key_data); + key_data += seg_len; + break; + case HA_KEYTYPE_NUM: { + /* Numeric key */ + if (keyseg->flag & HA_SPACE_PACK) + seg_len = *key_data++; + else + seg_len = keyseg->length; + key_data += seg_len; + break; + } + case HA_KEYTYPE_INT8: + case HA_KEYTYPE_SHORT_INT: + case HA_KEYTYPE_USHORT_INT: + case HA_KEYTYPE_LONG_INT: + case HA_KEYTYPE_ULONG_INT: + case HA_KEYTYPE_INT24: + case HA_KEYTYPE_UINT24: + case HA_KEYTYPE_FLOAT: + case HA_KEYTYPE_DOUBLE: + case HA_KEYTYPE_LONGLONG: + case HA_KEYTYPE_ULONGLONG: + case HA_KEYTYPE_BIT: + key_data += keyseg->length; + break; + case HA_KEYTYPE_END: + goto end; + } + } + + end: + return (xtWord1 *) key_data - key_buf; +} + +/* Derived from ha_key_cmp */ +xtPublic int myxt_compare_key(XTIndexPtr ind, int search_flags, uint key_length, xtWord1 *key_value, xtWord1 *b_value) +{ + register XTIndexSegRec *keyseg = ind->mi_seg; + int flag; + register uchar *a = (uchar *) key_value; + uint a_length; + register uchar *b = (uchar *) b_value; + uint b_length; + uint next_key_length; + uchar *end; + uint piks; + uint pack_len; + + for (uint i=0; i < ind->mi_seg_count && (int) key_length > 0; key_length = next_key_length, keyseg++, i++) { + piks = !(keyseg->flag & HA_NO_SORT); + + /* Handle NULL part */ + if (keyseg->null_bit) { + /* 1 is not null, 0 is null */ + int b_not_null = (int) *b++; + + key_length--; + if ((int) *a != b_not_null && piks) + { + flag = (int) *a - b_not_null; + return ((keyseg->flag & HA_REVERSE_SORT) ? -flag : flag); + } + if (!*a++) { + /* If key was NULL */ + if (search_flags == (SEARCH_FIND | SEARCH_UPDATE)) + search_flags = SEARCH_SAME; /* Allow duplicate keys */ + else if (search_flags & SEARCH_NULL_ARE_NOT_EQUAL) + { + /* + * This is only used from mi_check() to calculate cardinality. + * It can't be used when searching for a key as this would cause + * compare of (a,b) and (b,a) to return the same value. + */ + return -1; + } + /* PMC - I don't know why I had next_key_length = key_length - keyseg->length; + * This was my comment: even when null we have the complete length + * + * The truth is, a NULL only takes up one byte in the key, and this has already + * been subtracted. + */ + next_key_length = key_length; + continue; /* To next key part */ + } + } + + /* Both components are not null... */ + if (keyseg->length < key_length) { + end = a + keyseg->length; + next_key_length = key_length - keyseg->length; + } + else { + end = a + key_length; + next_key_length = 0; + } + + switch ((enum ha_base_keytype) keyseg->type) { + case HA_KEYTYPE_TEXT: /* Ascii; Key is converted */ + if (keyseg->flag & HA_SPACE_PACK) { + get_key_pack_length(a_length, pack_len, a); + next_key_length = key_length - a_length - pack_len; + get_key_pack_length(b_length, pack_len, b); + + if (piks && (flag = my_compare_text(keyseg->charset, a, a_length, b, b_length, + (my_bool) ((search_flags & SEARCH_PREFIX) && next_key_length <= 0), + (my_bool)!(search_flags & SEARCH_PREFIX)))) + return ((keyseg->flag & HA_REVERSE_SORT) ? -flag : flag); + a += a_length; + } + else { + a_length = (uint) (end - a); + b_length = keyseg->length; + if (piks && (flag = my_compare_text(keyseg->charset, a, a_length, b, b_length, + (my_bool) ((search_flags & SEARCH_PREFIX) && next_key_length <= 0), + (my_bool)!(search_flags & SEARCH_PREFIX)))) + return ((keyseg->flag & HA_REVERSE_SORT) ? -flag : flag); + a = end; + } + b += b_length; + break; + case HA_KEYTYPE_BINARY: + if (keyseg->flag & HA_SPACE_PACK) { + get_key_pack_length(a_length, pack_len, a); + next_key_length = key_length - a_length - pack_len; + get_key_pack_length(b_length, pack_len, b); + + if (piks && (flag = my_compare_bin(a, a_length, b, b_length, + (my_bool) ((search_flags & SEARCH_PREFIX) && next_key_length <= 0), 1))) + return ((keyseg->flag & HA_REVERSE_SORT) ? -flag : flag); + } + else { + a_length = keyseg->length; + b_length = keyseg->length; + if (piks && (flag = my_compare_bin(a, a_length, b, b_length, + (my_bool) ((search_flags & SEARCH_PREFIX) && next_key_length <= 0), 0))) + return ((keyseg->flag & HA_REVERSE_SORT) ? -flag : flag); + } + a += a_length; + b += b_length; + break; + case HA_KEYTYPE_VARTEXT1: + case HA_KEYTYPE_VARTEXT2: + { + get_key_pack_length(a_length, pack_len, a); + next_key_length = key_length - a_length - pack_len; + get_key_pack_length(b_length, pack_len, b); + + if (piks && (flag = my_compare_text(keyseg->charset, a, a_length, b, b_length, + (my_bool) ((search_flags & SEARCH_PREFIX) && next_key_length <= 0), + (my_bool) ((search_flags & (SEARCH_FIND | SEARCH_UPDATE)) == SEARCH_FIND)))) + return ((keyseg->flag & HA_REVERSE_SORT) ? -flag : flag); + a += a_length; + b += b_length; + break; + } + case HA_KEYTYPE_VARBINARY1: + case HA_KEYTYPE_VARBINARY2: + { + get_key_pack_length(a_length, pack_len, a); + next_key_length = key_length - a_length - pack_len; + get_key_pack_length(b_length, pack_len, b); + + if (piks && (flag=my_compare_bin(a, a_length, b, b_length, + (my_bool) ((search_flags & SEARCH_PREFIX) && next_key_length <= 0), 0))) + return ((keyseg->flag & HA_REVERSE_SORT) ? -flag : flag); + a += a_length; + b += b_length; + break; + } + case HA_KEYTYPE_INT8: + { + int i_1 = (int) *((signed char *) a); + int i_2 = (int) *((signed char *) b); + if (piks && (flag = CMP_NUM(i_1,i_2))) + return ((keyseg->flag & HA_REVERSE_SORT) ? -flag : flag); + a = end; + b += keyseg->length; + break; + } + case HA_KEYTYPE_SHORT_INT: { + int16 s_1 = sint2korr(a); + int16 s_2 = sint2korr(b); + if (piks && (flag = CMP_NUM(s_1, s_2))) + return ((keyseg->flag & HA_REVERSE_SORT) ? -flag : flag); + a = end; + b += keyseg->length; + break; + } + case HA_KEYTYPE_USHORT_INT: { + uint16 us_1= sint2korr(a); + uint16 us_2= sint2korr(b); + if (piks && (flag = CMP_NUM(us_1, us_2))) + return ((keyseg->flag & HA_REVERSE_SORT) ? -flag : flag); + a = end; + b += keyseg->length; + break; + } + case HA_KEYTYPE_LONG_INT: { + int32 l_1 = sint4korr(a); + int32 l_2 = sint4korr(b); + if (piks && (flag = CMP_NUM(l_1, l_2))) + return ((keyseg->flag & HA_REVERSE_SORT) ? -flag : flag); + a = end; + b += keyseg->length; + break; + } + case HA_KEYTYPE_ULONG_INT: { + uint32 u_1 = sint4korr(a); + uint32 u_2 = sint4korr(b); + if (piks && (flag = CMP_NUM(u_1, u_2))) + return ((keyseg->flag & HA_REVERSE_SORT) ? -flag : flag); + a = end; + b += keyseg->length; + break; + } + case HA_KEYTYPE_INT24: { + int32 l_1 = sint3korr(a); + int32 l_2 = sint3korr(b); + if (piks && (flag = CMP_NUM(l_1, l_2))) + return ((keyseg->flag & HA_REVERSE_SORT) ? -flag : flag); + a = end; + b += keyseg->length; + break; + } + case HA_KEYTYPE_UINT24: { + int32 l_1 = uint3korr(a); + int32 l_2 = uint3korr(b); + if (piks && (flag = CMP_NUM(l_1, l_2))) + return ((keyseg->flag & HA_REVERSE_SORT) ? -flag : flag); + a = end; + b += keyseg->length; + break; + } + case HA_KEYTYPE_FLOAT: { + float f_1, f_2; + + float4get(f_1, a); + float4get(f_2, b); + /* + * The following may give a compiler warning about floating point + * comparison not being safe, but this is ok in this context as + * we are bascily doing sorting + */ + if (piks && (flag = CMP_NUM(f_1, f_2))) + return ((keyseg->flag & HA_REVERSE_SORT) ? -flag : flag); + a = end; + b += keyseg->length; + break; + } + case HA_KEYTYPE_DOUBLE: { + double d_1, d_2; + + float8get(d_1, a); + float8get(d_2, b); + /* + * The following may give a compiler warning about floating point + * comparison not being safe, but this is ok in this context as + * we are bascily doing sorting + */ + if (piks && (flag = CMP_NUM(d_1, d_2))) + return ((keyseg->flag & HA_REVERSE_SORT) ? -flag : flag); + a = end; + b += keyseg->length; + break; + } + case HA_KEYTYPE_NUM: { + /* Numeric key */ + if (keyseg->flag & HA_SPACE_PACK) { + a_length = *a++; + end = a + a_length; + next_key_length = key_length - a_length - 1; + b_length = *b++; + } + else { + a_length = (int) (end - a); + b_length = keyseg->length; + } + + /* remove pre space from keys */ + for ( ; a_length && *a == ' ' ; a++, a_length--) ; + for ( ; b_length && *b == ' ' ; b++, b_length--) ; + + if (keyseg->flag & HA_REVERSE_SORT) { + swap_variables(uchar *, a, b); + swap_variables(uint, a_length, b_length); + } + + if (piks) { + if (*a == '-') { + if (*b != '-') + return -1; + a++; b++; + swap_variables(uchar *, a, b); + swap_variables(uint, a_length, b_length); + a_length--; b_length--; + } + else if (*b == '-') + return 1; + while (a_length && (*a == '+' || *a == '0')) { + a++; a_length--; + } + + while (b_length && (*b == '+' || *b == '0')) { + b++; b_length--; + } + + if (a_length != b_length) + return (a_length < b_length) ? -1 : 1; + while (b_length) { + if (*a++ != *b++) + return ((int) a[-1] - (int) b[-1]); + b_length--; + } + } + a = end; + b += b_length; + break; + } +#ifdef HAVE_LONG_LONG + case HA_KEYTYPE_LONGLONG: { + longlong ll_a = sint8korr(a); + longlong ll_b = sint8korr(b); + if (piks && (flag = CMP_NUM(ll_a,ll_b))) + return ((keyseg->flag & HA_REVERSE_SORT) ? -flag : flag); + a = end; + b += keyseg->length; + break; + } + case HA_KEYTYPE_ULONGLONG: { + ulonglong ll_a = uint8korr(a); + ulonglong ll_b = uint8korr(b); + if (piks && (flag = CMP_NUM(ll_a,ll_b))) + return ((keyseg->flag & HA_REVERSE_SORT) ? -flag : flag); + a = end; + b += keyseg->length; + break; + } +#endif + case HA_KEYTYPE_BIT: + /* TODO: What here? */ + break; + case HA_KEYTYPE_END: /* Ready */ + goto end; + } + } + + end: + return 0; +} + +xtPublic u_int myxt_key_seg_length(XTIndexSegRec *keyseg, u_int key_offset, xtWord1 *key_value) +{ + register xtWord1 *a = (xtWord1 *) key_value + key_offset; + u_int a_length; + u_int has_null = 0; + u_int key_length = 0; + u_int pack_len; + + /* Handle NULL part */ + if (keyseg->null_bit) { + has_null++; + /* If the value is null, then it only requires one byte: */ + if (!*a++) + return has_null; + } + + key_length = has_null + keyseg->length; + + switch ((enum ha_base_keytype) keyseg->type) { + case HA_KEYTYPE_TEXT: /* Ascii; Key is converted */ + if (keyseg->flag & HA_SPACE_PACK) { + get_key_pack_length(a_length, pack_len, a); + key_length = has_null + a_length + pack_len; + } + break; + case HA_KEYTYPE_BINARY: + if (keyseg->flag & HA_SPACE_PACK) { + get_key_pack_length(a_length, pack_len, a); + key_length = has_null + a_length + pack_len; + } + break; + case HA_KEYTYPE_VARTEXT1: + case HA_KEYTYPE_VARTEXT2: + case HA_KEYTYPE_VARBINARY1: + case HA_KEYTYPE_VARBINARY2: { + get_key_pack_length(a_length, pack_len, a); + key_length = has_null + a_length + pack_len; + break; + } + case HA_KEYTYPE_INT8: + case HA_KEYTYPE_SHORT_INT: + case HA_KEYTYPE_USHORT_INT: + case HA_KEYTYPE_LONG_INT: + case HA_KEYTYPE_ULONG_INT: + case HA_KEYTYPE_INT24: + case HA_KEYTYPE_UINT24: + case HA_KEYTYPE_FLOAT: + case HA_KEYTYPE_DOUBLE: + break; + case HA_KEYTYPE_NUM: { + /* Numeric key */ + if (keyseg->flag & HA_SPACE_PACK) { + a_length = *a++; + key_length = has_null + a_length + 1; + } + break; + } +#ifdef HAVE_LONG_LONG + case HA_KEYTYPE_LONGLONG: + case HA_KEYTYPE_ULONGLONG: + break; +#endif + case HA_KEYTYPE_BIT: + /* TODO: What here? */ + break; + case HA_KEYTYPE_END: /* Ready */ + break; + } + + return key_length; +} + +/* + * ----------------------------------------------------------------------- + * Load and store rows + */ + +xtPublic xtWord4 myxt_store_row_length(XTOpenTablePtr ot, char *rec_buff) +{ + TABLE *table = ot->ot_table->tab_dic.dic_my_table; + char *sdata; + xtWord4 dlen; + xtWord4 item_size; + xtWord4 row_size = 0; + + for (Field **field=table->field ; *field ; field++) { + if ((*field)->is_null_in_record((const uchar *) rec_buff)) { + sdata = NULL; + dlen = 0; + item_size = 1; + } + else { + sdata = mx_get_length_and_data(*field, rec_buff, &dlen); + if (!dlen) { + /* Empty, but not null (blobs may return NULL, when + * length is 0. + */ + sdata = rec_buff; // Any valid pointer will do + item_size = 1 + dlen; + } + else if (dlen <= 240) + item_size = 1 + dlen; + else if (dlen <= 0xFFFF) + item_size = 3 + dlen; + else if (dlen <= 0xFFFFFF) + item_size = 4 + dlen; + else + item_size = 5 + dlen; + } + + row_size += item_size; + } + return row_size; +} + +static xtWord4 mx_store_row(XTOpenTablePtr ot, xtWord4 row_size, char *rec_buff) +{ + TABLE *table = ot->ot_table->tab_dic.dic_my_table; + char *sdata; + xtWord4 dlen; + xtWord4 item_size; + + for (Field **field=table->field ; *field ; field++) { + if ((*field)->is_null_in_record((const uchar *) rec_buff)) { + sdata = NULL; + dlen = 0; + item_size = 1; + } + else { + sdata = mx_get_length_and_data(*field, rec_buff, &dlen); + if (!dlen) { + /* Empty, but not null (blobs may return NULL, when + * length is 0. + */ + sdata = rec_buff; // Any valid pointer will do + item_size = 1 + dlen; + } + else if (dlen <= 240) + item_size = 1 + dlen; + else if (dlen <= 0xFFFF) + item_size = 3 + dlen; + else if (dlen <= 0xFFFFFF) + item_size = 4 + dlen; + else + item_size = 5 + dlen; + } + + if (row_size + item_size > ot->ot_row_wbuf_size) { + if (!xt_realloc_ns((void **) &ot->ot_row_wbuffer, row_size + item_size)) + return 0; + ot->ot_row_wbuf_size = row_size + item_size; + } + + if (!sdata) + ot->ot_row_wbuffer[row_size] = 255; + else if (dlen <= 240) { + ot->ot_row_wbuffer[row_size] = (unsigned char) dlen; + memcpy(&ot->ot_row_wbuffer[row_size+1], sdata, dlen); + } + else if (dlen <= 0xFFFF) { + ot->ot_row_wbuffer[row_size] = 254; + XT_SET_DISK_2(&ot->ot_row_wbuffer[row_size+1], dlen); + memcpy(&ot->ot_row_wbuffer[row_size+3], sdata, dlen); + } + else if (dlen <= 0xFFFFFF) { + ot->ot_row_wbuffer[row_size] = 253; + XT_SET_DISK_3(&ot->ot_row_wbuffer[row_size+1], dlen); + memcpy(&ot->ot_row_wbuffer[row_size+4], sdata, dlen); + } + else { + ot->ot_row_wbuffer[row_size] = 252; + XT_SET_DISK_4(&ot->ot_row_wbuffer[row_size+1], dlen); + memcpy(&ot->ot_row_wbuffer[row_size+5], sdata, dlen); + } + + row_size += item_size; + } + return row_size; +} + +/* Count the number and size of whole columns in the given buffer. */ +xtPublic size_t myxt_load_row_length(XTOpenTablePtr ot, size_t buffer_size, xtWord1 *source_buf, u_int *ret_col_cnt) +{ + u_int col_cnt; + xtWord4 len; + size_t size = 0; + u_int i; + + col_cnt = ot->ot_table->tab_dic.dic_no_of_cols; + if (ret_col_cnt) + col_cnt = *ret_col_cnt; + for (i=0; i<col_cnt; i++) { + if (size + 1 > buffer_size) + goto done; + switch (*source_buf) { + case 255: // Indicate NULL value + size++; + source_buf++; + break; + case 254: // 2 bytes length + if (size + 3 > buffer_size) + goto done; + len = XT_GET_DISK_2(source_buf + 1); + if (size + 3 + len > buffer_size) + goto done; + size += 3 + len; + source_buf += 3 + len; + break; + case 253: // 3 bytes length + if (size + 4 > buffer_size) + goto done; + len = XT_GET_DISK_3(source_buf + 1); + if (size + 4 + len > buffer_size) + goto done; + size += 4 + len; + source_buf += 4 + len; + break; + case 252: // 4 bytes length + if (size + 5 > buffer_size) + goto done; + len = XT_GET_DISK_4(source_buf + 1); + if (size + 5 + len > buffer_size) + goto done; + size += 5 + len; + source_buf += 5 + len; + break; + default: // Length byte + len = *source_buf; + if (size + 1 + len > buffer_size) + goto done; + size += 1 + len; + source_buf += 1 + len; + break; + } + } + + done: + if (ret_col_cnt) + *ret_col_cnt = i; + return size; +} + +/* Unload from PBXT variable length format to the MySQL row format. */ +xtPublic xtBool myxt_load_row(XTOpenTablePtr ot, xtWord1 *source_buf, xtWord1 *dest_buff, u_int col_cnt) +{ + TABLE *table; + xtWord4 len; + Field *curr_field; + xtBool is_null; + u_int i = 0; + + if (!(table = ot->ot_table->tab_dic.dic_my_table)) { + xt_register_taberr(XT_REG_CONTEXT, XT_ERR_NO_DICTIONARY, ot->ot_table->tab_name); + return FAILED; + } + + /* According to the InnoDB implementation: + * "MySQL assumes that all columns + * have the SQL NULL bit set unless it + * is a nullable column with a non-NULL value". + */ + memset(dest_buff, 0xFF, table->s->null_bytes); + for (Field **field=table->field ; *field && (!col_cnt || i<col_cnt); field++, i++) { + curr_field = *field; + is_null = FALSE; + switch (*source_buf) { + case 255: // Indicate NULL value + is_null = TRUE; + len = 0; + source_buf++; + break; + case 254: // 2 bytes length + len = XT_GET_DISK_2(source_buf + 1); + source_buf += 3; + break; + case 253: // 3 bytes length + len = XT_GET_DISK_3(source_buf + 1); + source_buf += 4; + break; + case 252: // 4 bytes length + len = XT_GET_DISK_4(source_buf + 1); + source_buf += 5; + break; + default: // Length byte + if (*source_buf > 240) { + xt_register_xterr(XT_REG_CONTEXT, XT_ERR_BAD_RECORD_FORMAT); + return FAILED; + } + len = *source_buf; + source_buf++; + break; + } + + if (is_null) + mx_set_length_and_data(curr_field, (char *) dest_buff, 0, NULL); + else + mx_set_length_and_data(curr_field, (char *) dest_buff, len, (char *) source_buf); + + source_buf += len; + } + return OK; +} + +xtPublic xtBool myxt_find_column(XTOpenTablePtr ot, u_int *col_idx, const char *col_name) +{ + TABLE *table = ot->ot_table->tab_dic.dic_my_table; + u_int i=0; + + for (Field **field=table->field; *field; field++, i++) { + if (!my_strcasecmp(system_charset_info, (*field)->field_name, col_name)) { + *col_idx = i; + return OK; + } + } + return FALSE; +} + +xtPublic void myxt_get_column_name(XTOpenTablePtr ot, u_int col_idx, u_int len, char *col_name) +{ + TABLE *table = ot->ot_table->tab_dic.dic_my_table; + Field *field; + + field = table->field[col_idx]; + xt_strcpy(len, col_name, field->field_name); +} + +xtPublic void myxt_get_column_as_string(XTOpenTablePtr ot, char *buffer, u_int col_idx, u_int len, char *value) +{ + XTTableHPtr tab = ot->ot_table; + XTThreadPtr self = ot->ot_thread; + TABLE *table = tab->tab_dic.dic_my_table; + Field *field = table->field[col_idx]; + char buf_val[MAX_FIELD_WIDTH]; + String val(buf_val, sizeof(buf_val), &my_charset_bin); + + if (mx_is_null_in_record(field, buffer)) + xt_strcpy(len, value, "NULL"); + else { + byte *save; + + /* Required by store() - or an assertion will fail: */ + if (table->read_set) + bitmap_set_bit(table->read_set, col_idx); + + save = field->ptr; + xt_lock_mutex(self, &tab->tab_dic_field_lock); + pushr_(xt_unlock_mutex, &tab->tab_dic_field_lock); +#if MYSQL_VERSION_ID < 50114 + field->ptr = (byte *) buffer + field->offset(); +#else + field->ptr = (byte *) buffer + field->offset(field->table->record[0]); +#endif + field->val_str(&val); + field->ptr = save; // Restore org row pointer + freer_(); // xt_unlock_mutex(&tab->tab_dic_field_lock) + xt_strcpy(len, value, val.c_ptr()); + } +} + +xtPublic xtBool myxt_set_column(XTOpenTablePtr ot, char *buffer, u_int col_idx, const char *value, u_int len) +{ + XTTableHPtr tab = ot->ot_table; + XTThreadPtr self = ot->ot_thread; + TABLE *table = tab->tab_dic.dic_my_table; + Field *field = table->field[col_idx]; + byte *save; + int error; + + /* Required by store() - or an assertion will fail: */ + if (table->write_set) + bitmap_set_bit(table->write_set, col_idx); + + mx_set_notnull_in_record(field, buffer); + + save = field->ptr; + xt_lock_mutex(self, &tab->tab_dic_field_lock); + pushr_(xt_unlock_mutex, &tab->tab_dic_field_lock); +#if MYSQL_VERSION_ID < 50114 + field->ptr = (byte *) buffer + field->offset(); +#else + field->ptr = (byte *) buffer + field->offset(field->table->record[0]); +#endif + error = field->store(value, len, &my_charset_utf8_general_ci); + field->ptr = save; // Restore org row pointer + freer_(); // xt_unlock_mutex(&tab->tab_dic_field_lock) + return error ? FAILED : OK; +} + +xtPublic void myxt_get_column_data(XTOpenTablePtr ot, char *buffer, u_int col_idx, char **value, size_t *len) +{ + TABLE *table = ot->ot_table->tab_dic.dic_my_table; + Field *field = table->field[col_idx]; + char *sdata; + xtWord4 dlen; + + sdata = mx_get_length_and_data(field, buffer, &dlen); + *value = sdata; + *len = dlen; +} + +xtPublic xtBool myxt_store_row(XTOpenTablePtr ot, XTTabRecInfoPtr rec_info, char *rec_buff) +{ + if (ot->ot_rec_fixed) { + rec_info->ri_fix_rec_buf = (XTTabRecFixDPtr) ot->ot_row_wbuffer; + rec_info->ri_rec_buf_size = ot->ot_rec_size; + rec_info->ri_ext_rec = NULL; + + rec_info->ri_fix_rec_buf->tr_rec_type_1 = XT_TAB_STATUS_FIXED; + memcpy(rec_info->ri_fix_rec_buf->rf_data, rec_buff, ot->ot_rec_size - XT_REC_FIX_HEADER_SIZE); + } + else { + xtWord4 row_size; + + if (!(row_size = mx_store_row(ot, XT_REC_EXT_HEADER_SIZE, rec_buff))) + return FAILED; + if (row_size - XT_REC_FIX_EXT_HEADER_DIFF <= ot->ot_rec_size) { + rec_info->ri_fix_rec_buf = (XTTabRecFixDPtr) &ot->ot_row_wbuffer[XT_REC_FIX_EXT_HEADER_DIFF]; + rec_info->ri_rec_buf_size = row_size - XT_REC_FIX_EXT_HEADER_DIFF; + rec_info->ri_ext_rec = NULL; + + rec_info->ri_fix_rec_buf->tr_rec_type_1 = XT_TAB_STATUS_VARIABLE; + } + else { + rec_info->ri_fix_rec_buf = (XTTabRecFixDPtr) ot->ot_row_wbuffer; + rec_info->ri_rec_buf_size = ot->ot_rec_size; + rec_info->ri_ext_rec = (XTTabRecExtDPtr) ot->ot_row_wbuffer; + rec_info->ri_log_data_size = row_size - ot->ot_rec_size; + rec_info->ri_log_buf = (XTactExtRecEntryDPtr) &ot->ot_row_wbuffer[ot->ot_rec_size - offsetof(XTactExtRecEntryDRec, er_data)]; + + rec_info->ri_ext_rec->tr_rec_type_1 = XT_TAB_STATUS_EXT_DLOG; + XT_SET_DISK_4(rec_info->ri_ext_rec->re_log_dat_siz_4, rec_info->ri_log_data_size); + } + } + return OK; +} + +static void mx_print_string(uchar *s, uint count) +{ + while (count > 0) { + if (s[count - 1] != ' ') + break; + count--; + } + printf("\""); + for (u_int i=0; i<count; i++, s++) + printf("%c", *s); + printf("\""); +} + +xtPublic void myxt_print_key(XTIndexPtr ind, xtWord1 *key_value) +{ + register XTIndexSegRec *keyseg = ind->mi_seg; + register uchar *b = (uchar *) key_value; + uint b_length; + uint pack_len; + + for (u_int i = 0; i < ind->mi_seg_count; i++, keyseg++) { + if (i!=0) + printf(" "); + if (keyseg->null_bit) { + if (!*b++) { + printf("NULL"); + continue; + } + } + switch ((enum ha_base_keytype) keyseg->type) { + case HA_KEYTYPE_TEXT: /* Ascii; Key is converted */ + if (keyseg->flag & HA_SPACE_PACK) { + get_key_pack_length(b_length, pack_len, b); + } + else + b_length = keyseg->length; + mx_print_string(b, b_length); + b += b_length; + break; + case HA_KEYTYPE_LONG_INT: { + int32 l_2 = sint4korr(b); + b += keyseg->length; + printf("%ld", (long) l_2); + break; + } + case HA_KEYTYPE_ULONG_INT: { + xtWord4 u_2 = sint4korr(b); + b += keyseg->length; + printf("%lu", (u_long) u_2); + break; + } + default: + break; + } + } +} + +/* + * ----------------------------------------------------------------------- + * MySQL Data Dictionary + */ + +#define TS(x) (x)->s + +static void my_close_table(TABLE *table) +{ +#ifndef DRIZZLED + closefrm(table, 1); // TODO: Q, why did Stewart remove this? +#endif + xt_free_ns(table); +} + +/* + * This function returns NULL if the table cannot be opened + * because this is not a MySQL thread. + */ +static TABLE *my_open_table(XTThreadPtr self, XTDatabaseHPtr db __attribute__((unused)), XTPathStrPtr tab_path) +{ + THD *thd = current_thd; + char path_buffer[PATH_MAX]; + char *table_name; + char database_name[XT_IDENTIFIER_NAME_SIZE]; + char *ptr; + size_t size; + char *buffer, *path, *db_name, *name; + TABLE_SHARE *share; + int error; + TABLE *table; + + /* If we have no MySQL thread, then we cannot open this table! + * What this means is the thread is probably the sweeper or the + * compactor. + */ + if (!thd) + return NULL; + + /* GOTCHA: Check if the table name is a partitian, + * if so we need to remove the partition + * extension, in order for this to work! + * + * Reason: the parts of a partition table do not + * have .frm files!! + */ + xt_strcpy(PATH_MAX, path_buffer, tab_path->ps_path); + table_name = xt_last_name_of_path(path_buffer); + if ((ptr = strstr(table_name, "#P#"))) + *ptr = 0; + + xt_2nd_last_name_of_path(XT_IDENTIFIER_NAME_SIZE, database_name, path_buffer); + + size = sizeof(TABLE) + sizeof(TABLE_SHARE) + + strlen(path_buffer) + 1 + + strlen(database_name) + 1 + strlen(table_name) + 1; + if (!(buffer = (char *) xt_malloc(self, size))) + return NULL; + table = (TABLE *) buffer; + buffer += sizeof(TABLE); + share = (TABLE_SHARE *) buffer; + buffer += sizeof(TABLE_SHARE); + + path = buffer; + strcpy(path, path_buffer); + buffer += strlen(path_buffer) + 1; + db_name = buffer; + strcpy(db_name, database_name); + buffer += strlen(database_name) + 1; + name = buffer; + strcpy(name, table_name); + + /* Required to call 'open_table_from_share'! */ + LEX *old_lex, new_lex; + + old_lex = thd->lex; + thd->lex = &new_lex; + new_lex.current_select= NULL; + lex_start(thd); + +#if MYSQL_VERSION_ID < 60000 +#if MYSQL_VERSION_ID < 50123 + init_tmp_table_share(share, db_name, 0, name, path); +#else + init_tmp_table_share(thd, share, db_name, 0, name, path); +#endif +#else +#if MYSQL_VERSION_ID < 60004 + init_tmp_table_share(share, db_name, 0, name, path); +#else + init_tmp_table_share(thd, share, db_name, 0, name, path); +#endif +#endif + + if ((error = open_table_def(thd, share, 0))) { + xt_free(self, table); + lex_end(&new_lex); + thd->lex = old_lex; + xt_throw_ulxterr(XT_CONTEXT, XT_ERR_LOADING_MYSQL_DIC, (u_long) error); + return NULL; + } + +#if MYSQL_VERSION_ID >= 60003 + if ((error = open_table_from_share(thd, share, "", 0, (uint) READ_ALL, 0, table, OTM_OPEN))) +#else + if ((error = open_table_from_share(thd, share, "", 0, (uint) READ_ALL, 0, table, FALSE))) +#endif + { + xt_free(self, table); + lex_end(&new_lex); + thd->lex = old_lex; + xt_throw_ulxterr(XT_CONTEXT, XT_ERR_LOADING_MYSQL_DIC, (u_long) error); + return NULL; + } + + lex_end(&new_lex); + thd->lex = old_lex; + + /* GOTCHA: I am the plug-in!!! Therefore, I should not hold + * a reference to myself. By holding this reference I prevent + * plugin_shutdown() and reap_plugins() in sql_plugin.cc + * from doing their job on shutdown! + */ + plugin_unlock(NULL, table->s->db_plugin); + table->s->db_plugin = NULL; + return table; +} + +/* +static bool my_match_index(XTDDIndex *ind, KEY *index) +{ + KEY_PART_INFO *key_part; + KEY_PART_INFO *key_part_end; + u_int j; + XTDDColumnRef *cref; + + if (index->key_parts != ind->co_cols.size()) + return false; + + j=0; + key_part_end = index->key_part + index->key_parts; + for (key_part = index->key_part; key_part != key_part_end; key_part++, j++) { + if (!(cref = ind->co_cols.itemAt(j))) + return false; + if (myxt_strcasecmp(cref->cr_col_name, (char *) key_part->field->field_name) != 0) + return false; + } + + if (ind->co_type == XT_DD_KEY_PRIMARY) { + if (!(index->flags & HA_NOSAME)) + return false; + } + else { + if (ind->co_type == XT_DD_INDEX_UNIQUE) { + if (!(index->flags & HA_NOSAME)) + return false; + } + if (ind->co_ind_name) { + if (myxt_strcasecmp(ind->co_ind_name, index->name) != 0) + return false; + } + } + + return true; +} + +static XTDDIndex *my_find_index(XTDDTable *dd_tab, KEY *index) +{ + XTDDIndex *ind; + + for (u_int i=0; i<dd_tab->dt_indexes.size(); i++) + { + ind = dd_tab->dt_indexes.itemAt(i); + if (my_match_index(ind, index)) + return ind; + } + return NULL; +} +*/ + +static void my_deref_index_data(struct XTThread *self, XTIndexPtr mi) +{ + enter_(); + /* The dirty list of cache pages should be empty here! */ + ASSERT(!mi->mi_dirty_list); + ASSERT(!mi->mi_free_list); + + xt_free_mutex(&mi->mi_flush_lock); + xt_spinlock_free(self, &mi->mi_dirty_lock); + XT_INDEX_FREE_LOCK(self, mi); + myxt_bitmap_free(self, &mi->mi_col_map); + if (mi->mi_free_list) + xt_free(self, mi->mi_free_list); + + xt_free(self, mi); + exit_(); +} + +static xtBool my_is_not_null_int4(XTIndexSegPtr seg) +{ + return (seg->type == HA_KEYTYPE_LONG_INT && !(seg->flag & HA_NULL_PART)); +} + +/* Derived from ha_myisam::create and mi_create */ +static XTIndexPtr my_create_index(XTThreadPtr self, TABLE *table_arg, u_int idx, KEY *index) +{ + XTIndexPtr ind; + KEY_PART_INFO *key_part; + KEY_PART_INFO *key_part_end; + XTIndexSegRec *seg; + Field *field; + enum ha_base_keytype type; + uint options = 0; + u_int key_length = 0; + xtBool partial_field; + + enter_(); + + pushsr_(ind, my_deref_index_data, (XTIndexPtr) xt_calloc(self, offsetof(XTIndexRec, mi_seg) + sizeof(XTIndexSegRec) * index->key_parts)); + + XT_INDEX_INIT_LOCK(self, ind); + xt_init_mutex_with_autoname(self, &ind->mi_flush_lock); + xt_spinlock_init_with_autoname(self, &ind->mi_dirty_lock); + ind->mi_index_no = idx; + ind->mi_flags = (index->flags & (HA_NOSAME | HA_NULL_ARE_EQUAL | HA_UNIQUE_CHECK)); + ind->mi_low_byte_first = TS(table_arg)->db_low_byte_first; + ind->mi_fix_key = TRUE; + ind->mi_select_total = 0; + ind->mi_subset_of = 0; + myxt_bitmap_init(self, &ind->mi_col_map, TS(table_arg)->fields); + + ind->mi_seg_count = (uint) index->key_parts; + key_part_end = index->key_part + index->key_parts; + seg = ind->mi_seg; + for (key_part = index->key_part; key_part != key_part_end; key_part++, seg++) { + partial_field = FALSE; + field = key_part->field; + + type = field->key_type(); + seg->flag = key_part->key_part_flag; + + if (options & HA_OPTION_PACK_KEYS || + (index->flags & (HA_PACK_KEY | HA_BINARY_PACK_KEY | HA_SPACE_PACK_USED))) + { + if (key_part->length > 8 && (type == HA_KEYTYPE_TEXT || type == HA_KEYTYPE_NUM || + (type == HA_KEYTYPE_BINARY && !field->zero_pack()))) + { + /* No blobs here */ + if (key_part == index->key_part) + ind->mi_flags |= HA_PACK_KEY; +#ifndef DRIZZLED + if (!(field->flags & ZEROFILL_FLAG) && + (field->type() == MYSQL_TYPE_STRING || + field->type() == MYSQL_TYPE_VAR_STRING || + ((int) (key_part->length - field->decimals())) >= 4)) + seg->flag |= HA_SPACE_PACK; +#endif + } + } + + seg->col_idx = field->field_index; + seg->is_recs_in_range = 1; + seg->is_selectivity = 1; + seg->type = (int) type; + seg->start = key_part->offset; + seg->length = key_part->length; + seg->bit_start = seg->bit_end = 0; + seg->bit_length = seg->bit_pos = 0; + seg->charset = field->charset(); + + if (field->null_ptr) { + key_length++; + seg->flag |= HA_NULL_PART; + seg->null_bit = field->null_bit; + seg->null_pos = (uint) (field->null_ptr - (uchar*) table_arg->record[0]); + } + else { + seg->null_bit = 0; + seg->null_pos = 0; + } + + if (field->real_type() == MYSQL_TYPE_ENUM +#ifndef DRIZZLED + || field->real_type() == MYSQL_TYPE_SET +#endif + ) { + /* This values are not indexed as string!! + * The index will not be built correctly if this value is non-NULL. + */ + seg->charset = NULL; + } + + if (field->type() == MYSQL_TYPE_BLOB +#ifndef DRIZZLED + || field->type() == MYSQL_TYPE_GEOMETRY +#endif + ) { + seg->flag |= HA_BLOB_PART; + /* save number of bytes used to pack length */ + seg->bit_start = (uint) (field->pack_length() - TS(table_arg)->blob_ptr_size); + } +#ifndef DRIZZLED + else if (field->type() == MYSQL_TYPE_BIT) { + seg->bit_length = ((Field_bit *) field)->bit_len; + seg->bit_start = ((Field_bit *) field)->bit_ofs; + seg->bit_pos = (uint) (((Field_bit *) field)->bit_ptr - (uchar*) table_arg->record[0]); + } +#else + /* Drizzle uses HA_KEYTYPE_ULONG_INT keys for enums > 1 byte, which is not consistent with MySQL, so we fix it here */ + else if (field->type() == MYSQL_TYPE_ENUM) { + switch (seg->length) { + case 2: + seg->type = HA_KEYTYPE_USHORT_INT; + break; + case 3: + seg->type = HA_KEYTYPE_UINT24; + break; + } + } +#endif + + switch (seg->type) { + case HA_KEYTYPE_VARTEXT1: + case HA_KEYTYPE_VARTEXT2: + case HA_KEYTYPE_VARBINARY1: + case HA_KEYTYPE_VARBINARY2: + if (!(seg->flag & HA_BLOB_PART)) { + /* Make a flag that this is a VARCHAR */ + seg->flag |= HA_VAR_LENGTH_PART; + /* Store in bit_start number of bytes used to pack the length */ + seg->bit_start = ((seg->type == HA_KEYTYPE_VARTEXT1 || seg->type == HA_KEYTYPE_VARBINARY1) ? 1 : 2); + } + break; + } + + /* All packed fields start with a length (1 or 3 bytes): */ + if (seg->flag & (HA_VAR_LENGTH_PART | HA_BLOB_PART | HA_SPACE_PACK)) { + key_length++; /* At least one length byte */ + if (seg->length >= 255) /* prefix may be 3 bytes */ + key_length +=2; + } + + key_length += seg->length; + if (seg->length > 40) + ind->mi_fix_key = FALSE; + + /* Determine if only part of the field is in the key: + * This is important for index coverage! + * Note, BLOB fields are never retrieved from + * an index! + */ + if (field->type() == MYSQL_TYPE_BLOB) + partial_field = TRUE; + else if (field->real_type() == MYSQL_TYPE_VARCHAR // For varbinary type +#ifndef DRIZZLED + || field->real_type() == MYSQL_TYPE_VAR_STRING // For varbinary type + || field->real_type() == MYSQL_TYPE_STRING // For binary type +#endif + ) + { + Field *tab_field = table_arg->field[key_part->fieldnr-1]; + u_int field_len = tab_field->key_length(); + + if (key_part->length != field_len) + partial_field = TRUE; + } + + /* NOTE: do not set if the field is only partially in the index!!! */ + if (!partial_field) + bitmap_fast_test_and_set(&ind->mi_col_map, field->field_index); + } + + if (key_length > XT_INDEX_MAX_KEY_SIZE) + xt_throw_sulxterr(XT_CONTEXT, XT_ERR_KEY_TOO_LARGE, index->name, (u_long) XT_INDEX_MAX_KEY_SIZE); + + /* This is the maximum size of the index on disk: */ + ind->mi_key_size = key_length; + + if (ind->mi_fix_key) { + /* Special case for not-NULL 4 byte int value: */ + switch (ind->mi_seg_count) { + case 1: + ind->mi_single_type = ind->mi_seg[0].type; + if (ind->mi_seg[0].type == HA_KEYTYPE_LONG_INT || + ind->mi_seg[0].type == HA_KEYTYPE_ULONG_INT) { + if (!(ind->mi_seg[0].flag & HA_NULL_PART)) + ind->mi_scan_branch = xt_scan_branch_single; + } + break; + case 2: + if (my_is_not_null_int4(&ind->mi_seg[0]) && + my_is_not_null_int4(&ind->mi_seg[1])) { + ind->mi_scan_branch = xt_scan_branch_fix_simple; + ind->mi_simple_comp_key = xt_compare_2_int4; + } + break; + case 3: + if (my_is_not_null_int4(&ind->mi_seg[0]) && + my_is_not_null_int4(&ind->mi_seg[1]) && + my_is_not_null_int4(&ind->mi_seg[2])) { + ind->mi_scan_branch = xt_scan_branch_fix_simple; + ind->mi_simple_comp_key = xt_compare_3_int4; + } + break; + } + if (!ind->mi_scan_branch) + ind->mi_scan_branch = xt_scan_branch_fix; + ind->mi_prev_item = xt_prev_branch_item_fix; + ind->mi_last_item = xt_last_branch_item_fix; + } + else { + ind->mi_scan_branch = xt_scan_branch_var; + ind->mi_prev_item = xt_prev_branch_item_var; + ind->mi_last_item = xt_last_branch_item_var; + } + + XT_NODE_ID(ind->mi_root) = 0; + + popr_(); // Discard my_deref_index_data(ind) + + return_(ind); +} + +/* We estimate the size of BLOBs depending on the number + * of BLOBs in the table. + */ +static u_int mx_blob_field_size_total[] = { + 500, // 1 + 400, // 2 + 350, // 3 + 320, // 4 + 300, // 5 + 280, // 6 + 260, // 7 + 240, // 8 + 220, // 9 + 210 // 10 +}; + +static u_int mxvarchar_field_min_ave[] = { + 120, // 1 + 105, // 2 + 90, // 3 + 65, // 4 + 50, // 5 + 40, // 6 + 40, // 7 + 40, // 8 + 40, // 9 + 40 // 10 +}; + +xtPublic void myxt_setup_dictionary(XTThreadPtr self, XTDictionaryPtr dic) +{ + TABLE *my_tab = dic->dic_my_table; + u_int field_count; + u_int var_field_count = 0; + u_int varchar_field_count = 0; + u_int blob_field_count = 0; + u_int large_blob_field_count = 0; + xtWord8 min_data_size = 0; + xtWord8 max_data_size = 0; + xtWord8 ave_data_size = 0; + xtWord8 min_row_size = 0; + xtWord8 max_row_size = 0; + xtWord8 ave_row_size = 0; + xtWord8 min_ave_row_size = 0; + xtWord8 max_ave_row_size = 0; + u_int dic_rec_size; + xtBool dic_rec_fixed; + Field *curr_field; + Field **field; + + /* How many columns are required for all indexes. */ + KEY *index; + KEY_PART_INFO *key_part; + KEY_PART_INFO *key_part_end; + + dic->dic_ind_cols_req = 0; + for (uint i=0; i<TS(my_tab)->keys; i++) { + index = &my_tab->key_info[i]; + + key_part_end = index->key_part + index->key_parts; + for (key_part = index->key_part; key_part != key_part_end; key_part++) { + curr_field = key_part->field; + + if ((u_int) curr_field->field_index+1 > dic->dic_ind_cols_req) + dic->dic_ind_cols_req = curr_field->field_index+1; + } + } + + /* We will work out how many columns are required for all blobs: */ + dic->dic_blob_cols_req = 0; + field_count = 0; + for (field=my_tab->field; (curr_field = *field); field++) { + field_count++; + min_data_size = curr_field->key_length(); + max_data_size = curr_field->key_length(); + enum_field_types tno = curr_field->type(); + + min_ave_row_size = 40; + max_ave_row_size = 128; + if (tno == MYSQL_TYPE_BLOB) { + blob_field_count++; + min_data_size = 0; + max_data_size = ((Field_blob *) curr_field)->max_data_length(); + /* Set the average length higher for BLOBs: */ + if (max_data_size == 0xFFFF || + max_data_size == 0xFFFFFF) { + if (large_blob_field_count < 10) + max_ave_row_size = mx_blob_field_size_total[large_blob_field_count]; + else + max_ave_row_size = 200; + large_blob_field_count++; + } + else if (max_data_size == 0xFFFFFFFF) { + /* Scale the estimated size of the blob depending on how many BLOBs + * are in the table! + */ + if (large_blob_field_count < 10) + max_ave_row_size = mx_blob_field_size_total[large_blob_field_count]; + else + max_ave_row_size = 200; + large_blob_field_count++; + if ((u_int) curr_field->field_index+1 > dic->dic_blob_cols_req) + dic->dic_blob_cols_req = curr_field->field_index+1; + dic->dic_blob_count++; + xt_realloc(self, (void **) &dic->dic_blob_cols, sizeof(Field *) * dic->dic_blob_count); + dic->dic_blob_cols[dic->dic_blob_count-1] = curr_field; + } + } + else if (tno == MYSQL_TYPE_VARCHAR +#ifndef DRIZZLED + || tno == MYSQL_TYPE_VAR_STRING +#endif + ) { + /* GOTCHA: MYSQL_TYPE_VAR_STRING does not exist as MYSQL_TYPE_VARCHAR define, but + * is used when creating a table with + * VARCHAR() + */ + min_data_size = 0; + if (varchar_field_count < 10) + min_ave_row_size = mxvarchar_field_min_ave[varchar_field_count]; + else + min_ave_row_size = 40; + varchar_field_count++; + } + + if (max_data_size == min_data_size) + ave_data_size = max_data_size; + else { + var_field_count++; + /* Take the average a 25% of the maximum: */ + ave_data_size = max_data_size / 4; + + /* Set the average based on min and max parameters: */ + if (ave_data_size < min_ave_row_size) + ave_data_size = min_ave_row_size; + else if (ave_data_size > max_ave_row_size) + ave_data_size = max_ave_row_size; + + if (ave_data_size > max_data_size) + ave_data_size = max_data_size; + } + + /* Add space for the length indicators: */ + if (min_data_size <= 240) + min_row_size += 1 + min_data_size; + else if (min_data_size <= 0xFFFF) + min_row_size += 3 + min_data_size; + else if (min_data_size <= 0xFFFFFF) + min_row_size += 4 + min_data_size; + else + min_row_size += 5 + min_data_size; + + if (max_data_size <= 240) + max_row_size += 1 + max_data_size; + else if (max_data_size <= 0xFFFF) + max_row_size += 3 + max_data_size; + else if (max_data_size <= 0xFFFFFF) + max_row_size += 4 + max_data_size; + else + max_row_size += 5 + max_data_size; + + if (ave_data_size <= 240) + ave_row_size += 1 + ave_data_size; + else /* Should not be more than this! */ + ave_row_size += 3 + ave_data_size; + + /* This is the length of the record required for all indexes: */ + if (field_count + 1 == dic->dic_ind_cols_req) + dic->dic_ind_rec_len = max_data_size; + } + + dic->dic_min_row_size = min_row_size; + dic->dic_max_row_size = max_row_size; + dic->dic_ave_row_size = ave_row_size; + dic->dic_no_of_cols = field_count; + + if (dic->dic_def_ave_row_size) { + /* The average row size has been set: */ + dic_rec_size = offsetof(XTTabRecFix, rf_data) + TS(my_tab)->reclength; + + /* The conditions for a fixed record are: */ + if (dic->dic_def_ave_row_size >= (xtWord8) TS(my_tab)->reclength && + dic_rec_size <= XT_TAB_MAX_FIX_REC_LENGTH && + !blob_field_count) { + dic_rec_fixed = TRUE; + } + else { + xtWord8 new_rec_size; + + dic_rec_fixed = FALSE; + if (dic->dic_def_ave_row_size > max_row_size) + new_rec_size = offsetof(XTTabRecFix, rf_data) + max_row_size; + else + new_rec_size = offsetof(XTTabRecFix, rf_data) + dic->dic_def_ave_row_size; + + /* The maximum record size 64K for explicit AVG_ROW_LENGTH! */ + if (new_rec_size > XT_TAB_MAX_FIX_REC_LENGTH_SPEC) + new_rec_size = XT_TAB_MAX_FIX_REC_LENGTH_SPEC; + + dic_rec_size = (u_int) new_rec_size; + } + } + else { + /* If the average size is within 10% if of the maximum size, then we + * we handle these rows as fixed size rows. + * Fixed size rows use the internal MySQL format. + */ + dic_rec_size = offsetof(XTTabRecFix, rf_data) + TS(my_tab)->reclength; + /* Fixed length records must be less than 16K in size, + * have an average size which is very close (20%) to the maximum size or + * be less than a minimum size, + * and not contain any BLOBs: + */ + if (dic_rec_size <= XT_TAB_MAX_FIX_REC_LENGTH && + (ave_row_size + ave_row_size / 4 >= max_row_size || + dic_rec_size < XT_TAB_MIN_VAR_REC_LENGTH) && + !blob_field_count) { + dic_rec_fixed = TRUE; + } + else { + dic_rec_fixed = FALSE; + /* Note I add offsetof(XTTabRecFix, rf_data) insteard of + * offsetof(XTTabRecExt, re_data) here! + * The reason is that, we want to include the average size + * record in the fixed data part. To do this we only need to + * calculate a fixed header size, because in the cases in which + * it fits, we will only be using a fixed header! + */ + dic_rec_size = (u_int) (offsetof(XTTabRecFix, rf_data) + ave_row_size); + /* The maximum record size (16K for autorow sizing)! */ + if (dic_rec_size > XT_TAB_MAX_FIX_REC_LENGTH) + dic_rec_size = XT_TAB_MAX_FIX_REC_LENGTH; + } + } + + if (!dic->dic_rec_size) { + dic->dic_rec_size = dic_rec_size; + dic->dic_rec_fixed = dic_rec_fixed; + } + else { + /* This just confirms that our original calculation on + * create table agrees with the current calculation. + * (i.e. if non-zero values were loaded from the table). + * + * It may be the criteria for calculating the data record size + * and whether to used a fixed or variable record has changed, + * but we need to stick to the current physical layout of the + * table. + * + * Note that this can occur in rename table when the + * method of calculation has changed. + * + * On rename, the format of the table does not change, so we + * will not take the calculated values. + */ + //ASSERT(dic->dic_rec_size == dic_rec_size); + //ASSERT(dic->dic_rec_fixed == dic_rec_fixed); + } + + if (dic_rec_fixed) { + /* Recalculate the length of the required required to address all + * index columns! + */ + if (field_count == dic->dic_ind_cols_req) + dic->dic_ind_rec_len = TS(my_tab)->reclength; + else { + field=my_tab->field; + + curr_field = field[dic->dic_ind_cols_req]; +#if MYSQL_VERSION_ID < 50114 + dic->dic_ind_rec_len = curr_field->offset(); +#else + dic->dic_ind_rec_len = curr_field->offset(curr_field->table->record[0]); +#endif + } + } + + /* We now calculate how many of the first columns in the row + * will definitely fit into the buffer, when the record is + * of type extended. + * + * In this way we can figure out if we need to load the extended + * record at all. + */ + dic->dic_fix_col_count = 0; + if (!dic_rec_fixed) { + xtWord8 max_rec_size = offsetof(XTTabRecExt, re_data); + + for (Field **f=my_tab->field; (curr_field = *f); f++) { + max_data_size = curr_field->key_length(); + enum_field_types tno = curr_field->type(); + if (tno == MYSQL_TYPE_BLOB) + max_data_size = ((Field_blob *) curr_field)->max_data_length(); + if (max_data_size <= 240) + max_rec_size += 1 + max_data_size; + else if (max_data_size <= 0xFFFF) + max_rec_size += 3 + max_data_size; + else if (max_data_size <= 0xFFFFFF) + max_rec_size += 4 + max_data_size; + else + max_rec_size += 5 + max_data_size; + if (max_rec_size > (xtWord8) dic_rec_size) + break; + dic->dic_fix_col_count++; + } + ASSERT(dic->dic_fix_col_count < dic->dic_no_of_cols); + } + + dic->dic_key_count = TS(my_tab)->keys; + dic->dic_mysql_buf_size = TS(my_tab)->rec_buff_length; + dic->dic_mysql_rec_size = TS(my_tab)->reclength; +} + +static u_int my_get_best_superset(XTThreadPtr self __attribute__((unused)), XTDictionaryPtr dic, XTIndexPtr ind) +{ + XTIndexPtr super_ind; + u_int super = 0; + u_int super_seg_count = ind->mi_seg_count; + + for (u_int i=0; i<dic->dic_key_count; i++) { + super_ind = dic->dic_keys[i]; + if (ind->mi_index_no != super_ind->mi_index_no && + super_seg_count < super_ind->mi_seg_count) { + for (u_int j=0; j<ind->mi_seg_count; j++) { + if (ind->mi_seg[j].col_idx != super_ind->mi_seg[j].col_idx) + goto next; + } + super_seg_count = super_ind->mi_seg_count; + super = i+1; + next:; + } + } + return super; +} + +/* + * Return FAILED if the MySQL dictionary is not available. + */ +xtPublic xtBool myxt_load_dictionary(XTThreadPtr self, XTDictionaryPtr dic, XTDatabaseHPtr db, XTPathStrPtr tab_path) +{ + TABLE *my_tab; + + if (!(my_tab = my_open_table(self, db, tab_path))) + return FAILED; + dic->dic_my_table = my_tab; + dic->dic_def_ave_row_size = (xtWord8) my_tab->s->avg_row_length; + myxt_setup_dictionary(self, dic); + dic->dic_keys = (XTIndexPtr *) xt_calloc(self, sizeof(XTIndexPtr) * TS(my_tab)->keys); + for (uint i=0; i<TS(my_tab)->keys; i++) + dic->dic_keys[i] = my_create_index(self, my_tab, i, &my_tab->key_info[i]); + + /* Check if any key is a subset of another: */ + for (u_int i=0; i<dic->dic_key_count; i++) + dic->dic_keys[i]->mi_subset_of = my_get_best_superset(self, dic, dic->dic_keys[i]); + + return OK; +} + +xtPublic void myxt_free_dictionary(XTThreadPtr self, XTDictionaryPtr dic) +{ + if (dic->dic_table) { + dic->dic_table->release(self); + dic->dic_table = NULL; + } + + if (dic->dic_my_table) { + my_close_table(dic->dic_my_table); + dic->dic_my_table = NULL; + } + + if (dic->dic_blob_cols) { + xt_free(self, dic->dic_blob_cols); + dic->dic_blob_cols = NULL; + } + dic->dic_blob_count = 0; + + /* If we have opened a table, then this data is freed with the dictionary: */ + if (dic->dic_keys) { + for (uint i=0; i<dic->dic_key_count; i++) { + if (dic->dic_keys[i]) + my_deref_index_data(self, (XTIndexPtr) dic->dic_keys[i]); + } + xt_free(self, dic->dic_keys); + dic->dic_key_count = 0; + dic->dic_keys = NULL; + } +} + +xtPublic void myxt_move_dictionary(XTDictionaryPtr dic, XTDictionaryPtr source_dic) +{ + dic->dic_my_table = source_dic->dic_my_table; + source_dic->dic_my_table = NULL; + + if (!dic->dic_rec_size) { + dic->dic_rec_size = source_dic->dic_rec_size; + dic->dic_rec_fixed = source_dic->dic_rec_fixed; + } + else { + /* This just confirms that our original calculation on + * create table agrees with the current calculation. + * (i.e. if non-zero values were loaded from the table). + * + * It may be the criteria for calculating the data record size + * and whether to used a fixed or variable record has changed, + * but we need to stick to the current physical layout of the + * table. + */ + ASSERT_NS(dic->dic_rec_size == source_dic->dic_rec_size); + ASSERT_NS(dic->dic_rec_fixed == source_dic->dic_rec_fixed); + } + + dic->dic_tab_flags = source_dic->dic_tab_flags; + dic->dic_blob_cols_req = source_dic->dic_blob_cols_req; + dic->dic_blob_count = source_dic->dic_blob_count; + dic->dic_blob_cols = source_dic->dic_blob_cols; + source_dic->dic_blob_cols = NULL; + + dic->dic_mysql_buf_size = source_dic->dic_mysql_buf_size; + dic->dic_mysql_rec_size = source_dic->dic_mysql_rec_size; + dic->dic_key_count = source_dic->dic_key_count; + dic->dic_keys = source_dic->dic_keys; + + /* Set this to zero, bcause later xt_flush_tables() may be called. + * This can occur when using the BLOB streaming engine, + * in command ALTER TABLE x ENGINE = PBXT; + */ + source_dic->dic_key_count = 0; + source_dic->dic_keys = NULL; + + dic->dic_min_row_size = source_dic->dic_min_row_size; + dic->dic_max_row_size = source_dic->dic_max_row_size; + dic->dic_ave_row_size = source_dic->dic_ave_row_size; + dic->dic_def_ave_row_size = source_dic->dic_def_ave_row_size; + + dic->dic_no_of_cols = source_dic->dic_no_of_cols; + dic->dic_fix_col_count = source_dic->dic_fix_col_count; + dic->dic_ind_cols_req = source_dic->dic_ind_cols_req; + dic->dic_ind_rec_len = source_dic->dic_ind_rec_len; +} + +static void my_free_dd_table(XTThreadPtr self, XTDDTable *dd_tab) +{ + if (dd_tab) + dd_tab->release(self); +} + +static void ha_create_dd_index(XTThreadPtr self, XTDDIndex *ind, KEY *key) +{ + KEY_PART_INFO *key_part; + KEY_PART_INFO *key_part_end; + XTDDColumnRef *cref; + + if (strcmp(key->name, "PRIMARY") == 0) + ind->co_type = XT_DD_KEY_PRIMARY; + else if (key->flags & HA_NOSAME) + ind->co_type = XT_DD_INDEX_UNIQUE; + else + ind->co_type = XT_DD_INDEX; + + if (ind->co_type == XT_DD_KEY_PRIMARY) + ind->co_name = xt_dup_string(self, key->name); + else + ind->co_ind_name = xt_dup_string(self, key->name); + + key_part_end = key->key_part + key->key_parts; + for (key_part = key->key_part; key_part != key_part_end; key_part++) { + if (!(cref = new XTDDColumnRef())) + xt_throw_errno(XT_CONTEXT, XT_ENOMEM); + ind->co_cols.append(self, cref); + cref->cr_col_name = xt_dup_string(self, (char *) key_part->field->field_name); + } +} + +static char *my_type_to_string(XTThreadPtr self, Field *field, TABLE *my_tab __attribute__((unused))) +{ + char buffer[MAX_FIELD_WIDTH + 400], *ptr; + String type((char *) buffer, sizeof(buffer), system_charset_info); + + /* GOTCHA: + * - Above sets the string length to the same as the buffer, + * so we must set the length to zero. + * - The result is not necessarilly zero terminated. + * - We cannot assume that the input buffer is the one + * we get back (for example text field). + */ + type.length(0); + field->sql_type(type); + ptr = type.c_ptr(); + if (ptr != buffer) + xt_strcpy(sizeof(buffer), buffer, ptr); + + if (field->has_charset()) { + /* Always include the charset so that we can compare types + * for FK/PK releations. + */ + xt_strcat(sizeof(buffer), buffer, " CHARACTER SET "); + xt_strcat(sizeof(buffer), buffer, (char *) 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)) { + xt_strcat(sizeof(buffer), buffer, " COLLATE "); + xt_strcat(sizeof(buffer), buffer, (char *) field->charset()->name); + } + } + + return xt_dup_string(self, buffer); // type.length() +} + +xtPublic XTDDTable *myxt_create_table_from_table(XTThreadPtr self, TABLE *my_tab) +{ + XTDDTable *dd_tab; + Field *curr_field; + XTDDColumn *col; + XTDDIndex *ind; + + if (!(dd_tab = new XTDDTable())) + xt_throw_errno(XT_CONTEXT, XT_ENOMEM); + dd_tab->init(self); + pushr_(my_free_dd_table, dd_tab); + + for (Field **field=my_tab->field; (curr_field = *field); field++) { + col = XTDDColumnFactory::createFromMySQLField(self, my_tab, curr_field); + dd_tab->dt_cols.append(self, col); + } + + for (uint i=0; i<TS(my_tab)->keys; i++) { + if (!(ind = (XTDDIndex *) new XTDDIndex(XT_DD_UNKNOWN))) + xt_throw_errno(XT_CONTEXT, XT_ENOMEM); + dd_tab->dt_indexes.append(self, ind); + ind->co_table = dd_tab; + ind->in_index = i; + ha_create_dd_index(self, ind, &my_tab->key_info[i]); + } + + popr_(); // my_free_dd_table(dd_tab) + return dd_tab; +} + +/* + * ----------------------------------------------------------------------- + * MySQL CHARACTER UTILITIES + */ + +xtPublic void myxt_static_convert_identifier(XTThreadPtr self __attribute__((unused)), MX_CHARSET_INFO *cs, char *from, char *to, size_t to_len) +{ + uint errors; + + /* + * Bug#4417 + * Check that identifiers and strings are not converted + * when the client character set is binary. + */ + if (cs == &my_charset_utf8_general_ci || cs == &my_charset_bin) + xt_strcpy(to_len, to, from); + else + strconvert(cs, from, &my_charset_utf8_general_ci, to, to_len, &errors); +} + +// cs == current_thd->charset() +xtPublic char *myxt_convert_identifier(XTThreadPtr self, MX_CHARSET_INFO *cs, char *from) +{ + uint errors; + u_int len; + char *to; + + if (cs == &my_charset_utf8_general_ci || cs == &my_charset_bin) + to = xt_dup_string(self, from); + else { + len = strlen(from) * 3 + 1; + to = (char *) xt_malloc(self, len); + strconvert(cs, from, &my_charset_utf8_general_ci, to, len, &errors); + } + return to; +} + +xtPublic char *myxt_convert_table_name(XTThreadPtr self, char *from) +{ + u_int len; + char *to; + + len = strlen(from) * 5 + 1; + to = (char *) xt_malloc(self, len); + tablename_to_filename(from, to, len); + return to; +} + +xtPublic void myxt_static_convert_table_name(XTThreadPtr self __attribute__((unused)), char *from, char *to, size_t to_len) +{ + tablename_to_filename(from, to, to_len); +} + +xtPublic int myxt_strcasecmp(char * a, char *b) +{ + return my_strcasecmp(&my_charset_utf8_general_ci, a, b); +} + +xtPublic int myxt_isspace(MX_CHARSET_INFO *cs, char a) +{ + return my_isspace(cs, a); +} + +xtPublic int myxt_ispunct(MX_CHARSET_INFO *cs, char a) +{ + return my_ispunct(cs, a); +} + +xtPublic int myxt_isdigit(MX_CHARSET_INFO *cs, char a) +{ + return my_isdigit(cs, a); +} + +xtPublic MX_CHARSET_INFO *myxt_getcharset(bool convert) +{ + if (convert) { + THD *thd = current_thd; + + if (thd) + return thd_charset(thd); + } + return &my_charset_utf8_general_ci; +} + +#ifdef XT_STREAMING +xtPublic xtBool myxt_use_blobs(XTOpenTablePtr ot, void **ret_pbms_table, xtWord1 *rec_buf) +{ + void *pbms_table; + XTTable *tab = ot->ot_table; + u_int idx = 0; + Field *field; + char *blob_ref; + xtWord4 len; + char in_url[PBMS_BLOB_URL_SIZE]; + char *out_url; + + if (!xt_pbms_open_table(&pbms_table, tab->tab_name->ps_path)) + return FAILED; + + for (idx=0; idx<tab->tab_dic.dic_blob_count; idx++) { + field = tab->tab_dic.dic_blob_cols[idx]; + if ((blob_ref = mx_get_length_and_data(field, (char *) rec_buf, &len)) && len) { + xt_strncpy(PBMS_BLOB_URL_SIZE, in_url, blob_ref, len); + + if (!xt_pbms_use_blob(pbms_table, &out_url, in_url, field->field_index)) { + xt_pbms_close_table(pbms_table); + return FAILED; + } + + if (out_url) { + len = strlen(out_url); + mx_set_length_and_data(field, (char *) rec_buf, len, out_url); + } + } + } + *ret_pbms_table = pbms_table; + return OK; +} + +xtPublic void myxt_unuse_blobs(XTOpenTablePtr ot __attribute__((unused)), void *pbms_table) +{ + xt_pbms_close_table(pbms_table); +} + +xtPublic xtBool myxt_retain_blobs(XTOpenTablePtr ot __attribute__((unused)), void *pbms_table, xtRecordID rec_id) +{ + xtBool ok; + PBMSEngineRefRec eng_ref; + + memset(&eng_ref, 0, sizeof(PBMSEngineRefRec)); + XT_SET_DISK_8(eng_ref.er_data, rec_id); + ok = xt_pbms_retain_blobs(pbms_table, &eng_ref); + xt_pbms_close_table(pbms_table); + return ok; +} + +xtPublic void myxt_release_blobs(XTOpenTablePtr ot, xtWord1 *rec_buf, xtRecordID rec_id) +{ + void *pbms_table; + XTTable *tab = ot->ot_table; + u_int idx = 0; + Field *field; + char *blob_ref; + xtWord4 len; + char in_url[PBMS_BLOB_URL_SIZE]; + PBMSEngineRefRec eng_ref; + + memset(&eng_ref, 0, sizeof(PBMSEngineRefRec)); + XT_SET_DISK_8(eng_ref.er_data, rec_id); + + if (!xt_pbms_open_table(&pbms_table, tab->tab_name->ps_path)) + return; + + for (idx=0; idx<tab->tab_dic.dic_blob_count; idx++) { + field = tab->tab_dic.dic_blob_cols[idx]; + if ((blob_ref = mx_get_length_and_data(field, (char *) rec_buf, &len)) && len) { + xt_strncpy(PBMS_BLOB_URL_SIZE, in_url, blob_ref, len); + + xt_pbms_release_blob(pbms_table, in_url, field->field_index, &eng_ref); + } + } + + xt_pbms_close_table(pbms_table); +} +#endif // XT_STREAMING + +xtPublic void *myxt_create_thread() +{ + THD *new_thd; + + if (my_thread_init()) { + xt_register_error(XT_REG_CONTEXT, XT_ERR_MYSQL_ERROR, 0, "Unable to initialize MySQL threading"); + return NULL; + } + + if (!(new_thd = new THD())) { + my_thread_end(); + xt_register_error(XT_REG_CONTEXT, XT_ERR_MYSQL_ERROR, 0, "Unable to create MySQL thread (THD)"); + return NULL; + } + + new_thd->thread_stack = (char *) &new_thd; + new_thd->store_globals(); + lex_start(new_thd); + + return (void *) new_thd; +} + +xtPublic void myxt_destroy_thread(void *thread, xtBool end_threads) +{ + THD *thd = (THD *) thread; + +#if MYSQL_VERSION_ID > 60005 + /* PMC - This is a HACK! It is required because + * MySQL shuts down MDL before shutting down the + * plug-ins. + */ + if (!pbxt_inited) + mdl_init(); + close_thread_tables(thd); + if (!pbxt_inited) + mdl_destroy(); +#else + close_thread_tables(thd); +#endif + + delete thd; + + /* Remember that we don't have a THD */ + my_pthread_setspecific_ptr(THR_THD, 0); + + if (end_threads) + my_thread_end(); +} + +xtPublic XTThreadPtr myxt_get_self() +{ + THD *thd; + + if ((thd = current_thd)) + return xt_ha_thd_to_self(thd); + return NULL; +} + +/* + * ----------------------------------------------------------------------- + * INFORMATION SCHEMA FUNCTIONS + * + */ + +static int mx_put_record(THD *thd, TABLE *table) +{ + return schema_table_store_record(thd, table); +} + +#ifdef UNUSED_CODE +static void mx_put_int(TABLE *table, int column, int value) +{ + table->field[column]->store(value, false); +} + +static void mx_put_real8(TABLE *table, int column, xtReal8 value) +{ + table->field[column]->store(value); +} + +static void mx_put_string(TABLE *table, int column, const char *string, u_int len, charset_info_st *charset) +{ + table->field[column]->store(string, len, charset); +} +#endif + +static void mx_put_u_llong(TABLE *table, int column, u_llong value) +{ + table->field[column]->store(value, false); +} + +static void mx_put_string(TABLE *table, int column, const char *string, charset_info_st *charset) +{ + table->field[column]->store(string, strlen(string), charset); +} + +xtPublic int myxt_statistics_fill_table(XTThreadPtr self, void *th, void *ta, void *, MX_CONST void *ch) +{ + THD *thd = (THD *) th; + TABLE_LIST *tables = (TABLE_LIST *) ta; + charset_info_st *charset = (charset_info_st *) ch; + TABLE *table = (TABLE *) tables->table; + int err = 0; + int col; + const char *stat_name; + u_llong stat_value; + XTStatisticsRec statistics; + + xt_gather_statistics(&statistics); + for (u_int rec_id=0; !err && rec_id<XT_STAT_CURRENT_MAX; rec_id++) { + stat_name = xt_get_stat_meta_data(rec_id)->sm_name; + stat_value = xt_get_statistic(&statistics, self->st_database, rec_id); + + col=0; + mx_put_u_llong(table, col++, rec_id+1); + mx_put_string(table, col++, stat_name, charset); + mx_put_u_llong(table, col++, stat_value); + err = mx_put_record(thd, table); + } + + return err; +} + +xtPublic void myxt_get_status(XTThreadPtr self, XTStringBufferPtr strbuf) +{ + char string[200]; + + xt_sb_concat(self, strbuf, "\n"); + xt_get_now(string, 200); + xt_sb_concat(self, strbuf, string); + xt_sb_concat(self, strbuf, " PBXT "); + xt_sb_concat(self, strbuf, xt_get_version()); + xt_sb_concat(self, strbuf, " STATUS OUTPUT"); + xt_sb_concat(self, strbuf, "\n"); + + xt_sb_concat(self, strbuf, "Record cache usage: "); + xt_sb_concat_int8(self, strbuf, xt_tc_get_usage()); + xt_sb_concat(self, strbuf, "\n"); + xt_sb_concat(self, strbuf, "Record cache size: "); + xt_sb_concat_int8(self, strbuf, xt_tc_get_size()); + xt_sb_concat(self, strbuf, "\n"); + xt_sb_concat(self, strbuf, "Record cache high: "); + xt_sb_concat_int8(self, strbuf, xt_tc_get_high()); + xt_sb_concat(self, strbuf, "\n"); + xt_sb_concat(self, strbuf, "Index cache usage: "); + xt_sb_concat_int8(self, strbuf, xt_ind_get_usage()); + xt_sb_concat(self, strbuf, "\n"); + xt_sb_concat(self, strbuf, "Index cache size: "); + xt_sb_concat_int8(self, strbuf, xt_ind_get_size()); + xt_sb_concat(self, strbuf, "\n"); + xt_sb_concat(self, strbuf, "Log cache usage: "); + xt_sb_concat_int8(self, strbuf, xt_xlog_get_usage()); + xt_sb_concat(self, strbuf, "\n"); + xt_sb_concat(self, strbuf, "Log cache size: "); + xt_sb_concat_int8(self, strbuf, xt_xlog_get_size()); + xt_sb_concat(self, strbuf, "\n"); + + xt_ht_lock(self, xt_db_open_databases); + pushr_(xt_ht_unlock, xt_db_open_databases); + + XTDatabaseHPtr *dbptr; + size_t len = xt_sl_get_size(xt_db_open_db_by_id); + + if (len > 0) { + xt_sb_concat(self, strbuf, "Data log files:\n"); + for (u_int i=0; i<len; i++) { + dbptr = (XTDatabaseHPtr *) xt_sl_item_at(xt_db_open_db_by_id, i); + +#ifndef XT_USE_GLOBAL_DB + xt_sb_concat(self, strbuf, "Database: "); + xt_sb_concat(self, strbuf, (*dbptr)->db_name); + xt_sb_concat(self, strbuf, "\n"); +#endif + xt_dl_log_status(self, *dbptr, strbuf); + } + } + else + xt_sb_concat(self, strbuf, "No data logs in use\n"); + + freer_(); // xt_ht_unlock(xt_db_open_databases) +} + +/* + * ----------------------------------------------------------------------- + * MySQL Bit Maps + */ + +xtPublic void myxt_bitmap_init(XTThreadPtr self, MY_BITMAP *map, u_int n_bits) +{ + my_bitmap_map *buf; + uint size_in_bytes = (((n_bits) + 31) / 32) * 4; + + buf = (my_bitmap_map *) xt_malloc(self, size_in_bytes); + map->bitmap= buf; + map->n_bits= n_bits; + create_last_word_mask(map); + bitmap_clear_all(map); +} + +xtPublic void myxt_bitmap_free(XTThreadPtr self, MY_BITMAP *map) +{ + if (map->bitmap) { + xt_free(self, map->bitmap); + map->bitmap = NULL; + } +} + +/* + * ----------------------------------------------------------------------- + * XTDDColumnFactory methods + */ + +XTDDColumn *XTDDColumnFactory::createFromMySQLField(XTThread *self, TABLE *my_tab, Field *field) +{ + XTDDEnumerableColumn *en_col; + XTDDColumn *col; + xtBool is_enum = FALSE; + + switch(field->real_type()) { + case MYSQL_TYPE_ENUM: + is_enum = TRUE; + /* fallthrough */ + +#ifndef DRIZZLED + case MYSQL_TYPE_SET: +#endif + col = en_col = new XTDDEnumerableColumn(); + if (!col) + xt_throw_errno(XT_CONTEXT, XT_ENOMEM); + col->init(self); + en_col->enum_size = ((Field_enum *)field)->typelib->count; + en_col->is_enum = is_enum; + break; + + default: + col = new XTDDColumn(); + if (!col) + xt_throw_errno(XT_CONTEXT, XT_ENOMEM); + col->init(self); + } + + col->dc_name = xt_dup_string(self, (char *) field->field_name); + col->dc_data_type = my_type_to_string(self, field, my_tab); + col->dc_null_ok = field->null_ptr != NULL; + + return col; +} + diff --git a/storage/pbxt/src/myxt_xt.h b/storage/pbxt/src/myxt_xt.h new file mode 100644 index 00000000000..4d33431088e --- /dev/null +++ b/storage/pbxt/src/myxt_xt.h @@ -0,0 +1,104 @@ +/* Copyright (c) 2005 PrimeBase Technologies GmbH + * + * PrimeBase XT + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You 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 + * + * 2006-05-16 Paul McCullagh + * + * H&G2JCtL + * + * These functions implement the parts of PBXT which must conform to the + * key and row format used by MySQL. + */ + +#ifndef __xt_myxt_h__ +#define __xt_myxt_h__ + +#include "xt_defs.h" +#include "table_xt.h" +#include "datadic_xt.h" + +#ifndef MYSQL_VERSION_ID +#error MYSQL_VERSION_ID must be defined! +#endif + +struct XTDictionary; +struct XTDatabase; +STRUCT_TABLE; +struct charset_info_st; + +u_int myxt_create_key_from_key(XTIndexPtr ind, xtWord1 *key, xtWord1 *old, u_int k_length); +u_int myxt_create_key_from_row(XTIndexPtr ind, xtWord1 *key, xtWord1 *record, xtBool *no_duplicate); +u_int myxt_create_foreign_key_from_row(XTIndexPtr ind, xtWord1 *key, xtWord1 *record, XTIndexPtr fkey_ind, xtBool *no_null); +u_int myxt_get_key_length(XTIndexPtr ind, xtWord1 *b_value); +int myxt_compare_key(XTIndexPtr ind, int search_flags, uint key_length, xtWord1 *key_value, xtWord1 *b_value); +u_int myxt_key_seg_length(XTIndexSegRec *keyseg, u_int key_offset, xtWord1 *key_value); +xtBool myxt_create_row_from_key(XTOpenTablePtr ot, XTIndexPtr ind, xtWord1 *key, u_int key_len, xtWord1 *record); +void myxt_set_null_row_from_key(XTOpenTablePtr ot, XTIndexPtr ind, xtWord1 *record); +void myxt_set_default_row_from_key(XTOpenTablePtr ot, XTIndexPtr ind, xtWord1 *record); +void myxt_print_key(XTIndexPtr ind, xtWord1 *key_value); + +xtWord4 myxt_store_row_length(XTOpenTablePtr ot, char *rec_buff); +xtBool myxt_store_row(XTOpenTablePtr ot, XTTabRecInfoPtr rec_info, char *rec_buff); +size_t myxt_load_row_length(XTOpenTablePtr ot, size_t buffer_size, xtWord1 *source_buf, u_int *ret_col_cnt); +xtBool myxt_load_row(XTOpenTablePtr ot, xtWord1 *source_buf, xtWord1 *dest_buff, u_int col_cnt); +xtBool myxt_find_column(XTOpenTablePtr ot, u_int *col_idx, const char *col_name); +void myxt_get_column_name(XTOpenTablePtr ot, u_int col_idx, u_int len, char *col_name); +void myxt_get_column_as_string(XTOpenTablePtr ot, char *buffer, u_int col_idx, u_int len, char *value); +xtBool myxt_set_column(XTOpenTablePtr ot, char *buffer, u_int col_idx, const char *value, u_int len); +void myxt_get_column_data(XTOpenTablePtr ot, char *buffer, u_int col_idx, char **value, size_t *len); + +void myxt_setup_dictionary(XTThreadPtr self, XTDictionary *dic); +xtBool myxt_load_dictionary(XTThreadPtr self, struct XTDictionary *dic, struct XTDatabase *db, XTPathStrPtr tab_path); +void myxt_free_dictionary(XTThreadPtr self, XTDictionary *dic); +void myxt_move_dictionary(XTDictionaryPtr dic, XTDictionaryPtr source_dic); +XTDDTable *myxt_create_table_from_table(XTThreadPtr self, STRUCT_TABLE *my_tab); + +void myxt_static_convert_identifier(XTThreadPtr self, struct charset_info_st *cs, char *from, char *to, size_t to_len); +char *myxt_convert_identifier(XTThreadPtr self, struct charset_info_st *cs, char *from); +void myxt_static_convert_table_name(XTThreadPtr self, char *from, char *to, size_t to_len); +char *myxt_convert_table_name(XTThreadPtr self, char *from); +int myxt_strcasecmp(char * a, char *b); +int myxt_isspace(struct charset_info_st *cs, char a); +int myxt_ispunct(struct charset_info_st *cs, char a); +int myxt_isdigit(struct charset_info_st *cs, char a); + +struct charset_info_st *myxt_getcharset(bool convert); + +#ifdef XT_STREAMING +xtBool myxt_use_blobs(XTOpenTablePtr ot, void **ret_pbms_table, xtWord1 *rec_buf); +void myxt_unuse_blobs(XTOpenTablePtr ot, void *pbms_table); +xtBool myxt_retain_blobs(XTOpenTablePtr ot, void *pbms_table, xtRecordID record); +void myxt_release_blobs(XTOpenTablePtr ot, xtWord1 *rec_buf, xtRecordID record); +#endif + +void *myxt_create_thread(); +void myxt_destroy_thread(void *thread, xtBool end_threads); +XTThreadPtr myxt_get_self(); + +int myxt_statistics_fill_table(XTThreadPtr self, void *th, void *ta, void *co, MX_CONST void *ch); +void myxt_get_status(XTThreadPtr self, XTStringBufferPtr strbuf); + +void myxt_bitmap_init(XTThreadPtr self, MY_BITMAP *map, u_int n_bits); +void myxt_bitmap_free(XTThreadPtr self, MY_BITMAP *map); + +class XTDDColumnFactory +{ +public: + static XTDDColumn *createFromMySQLField(XTThread *self, STRUCT_TABLE *, Field *); +}; + +#endif diff --git a/storage/pbxt/src/pbms.h b/storage/pbxt/src/pbms.h new file mode 100644 index 00000000000..bf29c63651e --- /dev/null +++ b/storage/pbxt/src/pbms.h @@ -0,0 +1,874 @@ +/* Copyright (c) 2007 PrimeBase Technologies GmbH + * + * PrimeBase Media Stream for MySQL + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You 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 + * + * Paul McCullagh + * H&G2JCtL + * + * 2007-06-01 + * + * This file contains the BLOB streaming interface engines that + * are streaming enabled. + * + */ +#ifndef __streaming_unx_h__ +#define __streaming_unx_h__ + +#include <stdio.h> +#include <sys/types.h> +#include <unistd.h> +#include <stdlib.h> +#include <fcntl.h> +#include <string.h> +#include <dirent.h> +#include <signal.h> +#include <ctype.h> + +#ifdef USE_PRAGMA_INTERFACE +#pragma interface /* gcc class implementation */ +#endif + +#define MS_SHARED_MEMORY_MAGIC 0x7E9A120C +#define MS_ENGINE_VERSION 1 +#define MS_CALLBACK_VERSION 1 +#define MS_SHARED_MEMORY_VERSION 1 +#define MS_ENGINE_LIST_SIZE 80 +#define MS_TEMP_FILE_PREFIX "pbms_temp_" +#define MS_TEMP_FILE_PREFIX "pbms_temp_" + +#define MS_RESULT_MESSAGE_SIZE 300 +#define MS_RESULT_STACK_SIZE 200 + +#define MS_BLOB_HANDLE_SIZE 300 + +#define SH_MASK ((S_IRUSR | S_IWUSR) | (S_IRGRP | S_IWGRP) | (S_IROTH)) + +#define MS_OK 0 +#define MS_ERR_ENGINE 1 /* Internal engine error. */ +#define MS_ERR_UNKNOWN_TABLE 2 /* Returned if the engine cannot open the given table. */ +#define MS_ERR_NOT_FOUND 3 /* The BLOB cannot be found. */ +#define MS_ERR_TABLE_LOCKED 4 /* Table is currently locked. */ +#define MS_ERR_INCORRECT_URL 5 +#define MS_ERR_AUTH_FAILED 6 +#define MS_ERR_NOT_IMPLEMENTED 7 +#define MS_ERR_UNKNOWN_DB 8 +#define MS_ERR_REMOVING_REPO 9 +#define MS_ERR_DATABASE_DELETED 10 + +#define MS_LOCK_NONE 0 +#define MS_LOCK_READONLY 1 +#define MS_LOCK_READ_WRITE 2 + +#define MS_XACT_NONE 0 +#define MS_XACT_BEGIN 1 +#define MS_XACT_COMMIT 2 +#define MS_XACT_ROLLBACK 3 + +#define PBMS_ENGINE_REF_LEN 8 +#define PBMS_BLOB_URL_SIZE 200 + +#define PBMS_FIELD_COL_SIZE 128 +#define PBMS_FIELD_COND_SIZE 300 + + +typedef struct PBMSBlobID { + u_int64_t bi_blob_size; + u_int64_t bi_blob_id; // or repo file offset if type = REPO + u_int32_t bi_tab_id; // or repo ID if type = REPO + u_int32_t bi_auth_code; + u_int32_t bi_blob_type; +} PBMSBlobIDRec, *PBMSBlobIDPtr; + +typedef struct PBMSResultRec { + int mr_code; /* Engine specific error code. */ + char mr_message[MS_RESULT_MESSAGE_SIZE]; /* Error message, required if non-zero return code. */ + char mr_stack[MS_RESULT_STACK_SIZE]; /* Trace information about where the error occurred. */ +} PBMSResultRec, *PBMSResultPtr; + +typedef struct PBMSEngineRefRec { + unsigned char er_data[PBMS_ENGINE_REF_LEN]; +} PBMSEngineRefRec, *PBMSEngineRefPtr; + +typedef struct PBMSBlobURL { + char bu_data[PBMS_BLOB_URL_SIZE]; +} PBMSBlobURLRec, *PBMSBlobURLPtr; + +typedef struct PBMSFieldRef { + char fr_column[PBMS_FIELD_COL_SIZE]; + char fr_cond[PBMS_FIELD_COND_SIZE]; +} PBMSFieldRefRec, *PBMSFieldRefPtr; +/* + * The engine must free its resources for the given thread. + */ +typedef void (*MSCloseConnFunc)(void *thd); + +/* Before access BLOBs of a table, the streaming engine will open the table. + * Open tables are managed as a pool by the streaming engine. + * When a request is received, the streaming engine will ask all + * registered engine to open the table. The engine must return a NULL + * open_table pointer if it does not handle the table. + * A callback allows an engine to request all open tables to be + * closed by the streaming engine. + */ +typedef int (*MSOpenTableFunc)(void *thd, const char *table_url, void **open_table, PBMSResultPtr result); +typedef void (*MSCloseTableFunc)(void *thd, void *open_table); + +/* + * When the streaming engine wants to use an open table handle from the + * pool, it calls the lock table function. + */ +typedef int (*MSLockTableFunc)(void *thd, int *xact, void *open_table, int lock_type, PBMSResultPtr result); +typedef int (*MSUnlockTableFunc)(void *thd, int xact, void *open_table, PBMSResultPtr result); + +/* This function is used to locate and send a BLOB on the given stream. + */ +typedef int (*MSSendBLOBFunc)(void *thd, void *open_table, const char *blob_column, const char *blob_url, void *stream, PBMSResultPtr result); + +/* + * Lookup and engine reference, and return readable text. + */ +typedef int (*MSLookupRefFunc)(void *thd, void *open_table, unsigned short col_index, PBMSEngineRefPtr eng_ref, PBMSFieldRefPtr feild_ref, PBMSResultPtr result); + +typedef struct PBMSEngineRec { + int ms_version; /* MS_ENGINE_VERSION */ + int ms_index; /* The index into the engine list. */ + int ms_removing; /* TRUE (1) if the engine is being removed. */ + const char *ms_engine_name; + void *ms_engine_info; + MSCloseConnFunc ms_close_conn; + MSOpenTableFunc ms_open_table; + MSCloseTableFunc ms_close_table; + MSLockTableFunc ms_lock_table; + MSUnlockTableFunc ms_unlock_table; + MSSendBLOBFunc ms_send_blob; + MSLookupRefFunc ms_lookup_ref; +} PBMSEngineRec, *PBMSEnginePtr; + +/* + * This function should never be called directly, it is called + * by deregisterEngine() below. + */ +typedef void (*ECDeregisterdFunc)(PBMSEnginePtr engine); + +typedef void (*ECTableCloseAllFunc)(const char *table_url); + +typedef int (*ECSetContentLenFunc)(void *stream, off_t len, PBMSResultPtr result); + +typedef int (*ECWriteHeadFunc)(void *stream, PBMSResultPtr result); + +typedef int (*ECWriteStreamFunc)(void *stream, void *buffer, size_t len, PBMSResultPtr result); + +/* + * The engine should call this function from + * its own close connection function! + */ +typedef int (*ECCloseConnFunc)(void *thd, PBMSResultPtr result); + +/* + * Call this function before retaining or releasing BLOBs in a row. + */ +typedef int (*ECOpenTableFunc)(void **open_table, char *table_path, PBMSResultPtr result); + +/* + * Call this function when the operation is complete. + */ +typedef void (*ECCloseTableFunc)(void *open_table); + +/* + * Call this function for each BLOB to be retained. When a BLOB is used, the + * URL may be changed. The returned URL is valid as long as the the + * table is open. + * + * The returned URL must be inserted into the row in place of the given + * URL. + */ +typedef int (*ECUseBlobFunc)(void *open_table, char **ret_blob_url, char *blob_url, unsigned short col_index, PBMSResultPtr result); + +/* + * Reference Blobs that has been uploaded to the streaming engine. + * + * All BLOBs specified by the use blob function are retained by + * this function. + * + * The engine reference is a (unaligned) 8 byte value which + * identifies the row that the BLOBs are in. + */ +typedef int (*ECRetainBlobsFunc)(void *open_table, PBMSEngineRefPtr eng_ref, PBMSResultPtr result); + +/* + * If a row containing a BLOB is deleted, then the BLOBs in the + * row must be released. + * + * Note: if a table is dropped, all the BLOBs referenced by the + * table are automatically released. + */ +typedef int (*ECReleaseBlobFunc)(void *open_table, char *blob_url, unsigned short col_index, PBMSEngineRefPtr eng_ref, PBMSResultPtr result); + +typedef int (*ECDropTable)(const char *table_path, PBMSResultPtr result); + +typedef int (*ECRenameTable)(const char *from_table, const char *to_table, PBMSResultPtr result); + +typedef struct PBMSCallbacksRec { + int cb_version; /* MS_CALLBACK_VERSION */ + ECDeregisterdFunc cb_deregister; + ECTableCloseAllFunc cb_table_close_all; + ECSetContentLenFunc cb_set_cont_len; + ECWriteHeadFunc cb_write_head; + ECWriteStreamFunc cb_write_stream; + ECCloseConnFunc cb_close_conn; + ECOpenTableFunc cb_open_table; + ECCloseTableFunc cb_close_table; + ECUseBlobFunc cb_use_blob; + ECRetainBlobsFunc cb_retain_blobs; + ECReleaseBlobFunc cb_release_blob; + ECDropTable cb_drop_table; + ECRenameTable cb_rename_table; +} PBMSCallbacksRec, *PBMSCallbacksPtr; + +typedef struct PBMSSharedMemoryRec { + int sm_magic; /* MS_SHARED_MEMORY_MAGIC */ + int sm_version; /* MS_SHARED_MEMORY_VERSION */ + volatile int sm_shutdown_lock; /* "Cheap" lock for shutdown! */ + PBMSCallbacksPtr sm_callbacks; + int sm_reserved1[20]; + void *sm_reserved2[20]; + int sm_list_size; + int sm_list_len; + PBMSEnginePtr sm_engine_list[MS_ENGINE_LIST_SIZE]; +} PBMSSharedMemoryRec, *PBMSSharedMemoryPtr; + +#ifndef PBMS_API +#ifndef PBMS_CLIENT_API +Please define he value of PBMS_API +#endif +#else + +class PBMS_API +{ +private: + const char *temp_prefix[3]; + +public: + PBMS_API(): sharedMemory(NULL) { + int i = 0; + temp_prefix[i++] = MS_TEMP_FILE_PREFIX; +#ifdef MS_TEMP_FILE_PREFIX + temp_prefix[i++] = MS_TEMP_FILE_PREFIX; +#endif + temp_prefix[i++] = NULL; + + } + + ~PBMS_API() { } + + /* + * Register the engine with the Stream Engine. + */ + int registerEngine(PBMSEnginePtr engine, PBMSResultPtr result) { + int err; + + deleteTempFiles(); + + if ((err = getSharedMemory(true, result))) + return err; + + for (int i=0; i<sharedMemory->sm_list_size; i++) { + if (!sharedMemory->sm_engine_list[i]) { + sharedMemory->sm_engine_list[i] = engine; + engine->ms_index = i; + if (i >= sharedMemory->sm_list_len) + sharedMemory->sm_list_len = i+1; + return MS_OK; + } + } + + result->mr_code = 15010; + strcpy(MS_RESULT_MESSAGE_SIZE, result->mr_message, "Too many BLOB streaming engines already registered"); + *result->mr_stack = 0; + return MS_ERR_ENGINE; + } + + void lock() { + while (sharedMemory->sm_shutdown_lock) + usleep(10000); + sharedMemory->sm_shutdown_lock++; + while (sharedMemory->sm_shutdown_lock != 1) { + usleep(random() % 10000); + sharedMemory->sm_shutdown_lock--; + usleep(10000); + sharedMemory->sm_shutdown_lock++; + } + } + + void unlock() { + sharedMemory->sm_shutdown_lock--; + } + + void deregisterEngine(PBMSEnginePtr engine) { + PBMSResultRec result; + int err; + + if ((err = getSharedMemory(true, &result))) + return; + + lock(); + + bool empty = true; + for (int i=0; i<sharedMemory->sm_list_len; i++) { + if (sharedMemory->sm_engine_list[i]) { + if (sharedMemory->sm_engine_list[i] == engine) { + if (sharedMemory->sm_callbacks) + sharedMemory->sm_callbacks->cb_deregister(engine); + sharedMemory->sm_engine_list[i] = NULL; + } + else + empty = false; + } + } + + unlock(); + + if (empty) { + char temp_file[100]; + + sharedMemory->sm_magic = 0; + free(sharedMemory); + sharedMemory = NULL; + const char **prefix = temp_prefix; + while (*prefix) { + getTempFileName(temp_file, *prefix, getpid()); + unlink(temp_file); + prefix++; + } + } + } + + void closeAllTables(const char *table_url) + { + PBMSResultRec result; + int err; + + if ((err = getSharedMemory(true, &result))) + return; + + if (sharedMemory->sm_callbacks) + sharedMemory->sm_callbacks->cb_table_close_all(table_url); + } + + int setContentLength(void *stream, off_t len, PBMSResultPtr result) + { + int err; + + if ((err = getSharedMemory(true, result))) + return err; + + return sharedMemory->sm_callbacks->cb_set_cont_len(stream, len, result); + } + + int writeHead(void *stream, PBMSResultPtr result) + { + int err; + + if ((err = getSharedMemory(true, result))) + return err; + + return sharedMemory->sm_callbacks->cb_write_head(stream, result); + } + + int writeStream(void *stream, void *buffer, size_t len, PBMSResultPtr result) + { + int err; + + if ((err = getSharedMemory(true, result))) + return err; + + return sharedMemory->sm_callbacks->cb_write_stream(stream, buffer, len, result); + } + + int closeConn(void *thd, PBMSResultPtr result) + { + int err; + + if ((err = getSharedMemory(true, result))) + return err; + + if (!sharedMemory->sm_callbacks) + return MS_OK; + + return sharedMemory->sm_callbacks->cb_close_conn(thd, result); + } + + int openTable(void **open_table, char *table_path, PBMSResultPtr result) + { + int err; + + if ((err = getSharedMemory(true, result))) + return err; + + if (!sharedMemory->sm_callbacks) { + *open_table = NULL; + return MS_OK; + } + + return sharedMemory->sm_callbacks->cb_open_table(open_table, table_path, result); + } + + int closeTable(void *open_table, PBMSResultPtr result) + { + int err; + + if ((err = getSharedMemory(true, result))) + return err; + + if (sharedMemory->sm_callbacks && open_table) + sharedMemory->sm_callbacks->cb_close_table(open_table); + return MS_OK; + } + + int couldBeURL(char *blob_url) + /* ~*test/~1-150-2b5e0a7-0[*<blob size>][.ext] */ + /* ~*test/_1-150-2b5e0a7-0[*<blob size>][.ext] */ + { + char *ptr; + size_t len; + bool have_blob_size = false; + + if (blob_url) { + if ((len = strlen(blob_url))) { + /* Too short: */ + if (len <= 10) + return 0; + + /* Required prefix: */ + /* NOTE: ~> is deprecated v0.5.4+, now use ~* */ + if (*blob_url != '~' || (*(blob_url + 1) != '>' && *(blob_url + 1) != '*')) + return 0; + + ptr = blob_url + len - 1; + + /* Allow for an optional extension: */ + if (!isdigit(*ptr)) { + while (ptr > blob_url && *ptr != '/' && *ptr != '.') + ptr--; + if (ptr == blob_url || *ptr != '.') + return 0; + if (ptr == blob_url || !isdigit(*ptr)) + return 0; + } + + // field 1: server id OR blob size + do_again: + while (ptr > blob_url && isdigit(*ptr)) + ptr--; + + if (ptr != blob_url && *ptr == '*' && !have_blob_size) { + ptr--; + have_blob_size = true; + goto do_again; + } + + if (ptr == blob_url || *ptr != '-') + return 0; + + + // field 2: Authoration code + ptr--; + if (!isxdigit(*ptr)) + return 0; + + while (ptr > blob_url && isxdigit(*ptr)) + ptr--; + + if (ptr == blob_url || *ptr != '-') + return 0; + + // field 3:offset + ptr--; + if (!isxdigit(*ptr)) + return 0; + + while (ptr > blob_url && isdigit(*ptr)) + ptr--; + + if (ptr == blob_url || *ptr != '-') + return 0; + + + // field 4:Table id + ptr--; + if (!isdigit(*ptr)) + return 0; + + while (ptr > blob_url && isdigit(*ptr)) + ptr--; + + /* NOTE: ^ and : are deprecated v0.5.4+, now use ! and ~ */ + if (ptr == blob_url || (*ptr != '^' && *ptr != ':' && *ptr != '_' && *ptr != '~')) + return 0; + ptr--; + + if (ptr == blob_url || *ptr != '/') + return 0; + ptr--; + if (ptr == blob_url) + return 0; + return 1; + } + } + return 0; + } + + int useBlob(void *open_table, char **ret_blob_url, char *blob_url, unsigned short col_index, PBMSResultPtr result) + { + int err; + + if ((err = getSharedMemory(true, result))) + return err; + + if (!couldBeURL(blob_url)) { + *ret_blob_url = NULL; + return MS_OK; + } + + if (!sharedMemory->sm_callbacks) { + result->mr_code = MS_ERR_INCORRECT_URL; + strcpy(MS_RESULT_MESSAGE_SIZE, result->mr_message, "BLOB streaming engine (PBMS) not installed"); + *result->mr_stack = 0; + return MS_ERR_INCORRECT_URL; + } + + return sharedMemory->sm_callbacks->cb_use_blob(open_table, ret_blob_url, blob_url, col_index, result); + } + + int retainBlobs(void *open_table, PBMSEngineRefPtr eng_ref, PBMSResultPtr result) + { + int err; + + if ((err = getSharedMemory(true, result))) + return err; + + if (!sharedMemory->sm_callbacks) + return MS_OK; + + return sharedMemory->sm_callbacks->cb_retain_blobs(open_table, eng_ref, result); + } + + int releaseBlob(void *open_table, char *blob_url, unsigned short col_index, PBMSEngineRefPtr eng_ref, PBMSResultPtr result) + { + int err; + + if ((err = getSharedMemory(true, result))) + return err; + + if (!sharedMemory->sm_callbacks) + return MS_OK; + + if (!couldBeURL(blob_url)) + return MS_OK; + + return sharedMemory->sm_callbacks->cb_release_blob(open_table, blob_url, col_index, eng_ref, result); + } + + int dropTable(const char *table_path, PBMSResultPtr result) + { + int err; + + if ((err = getSharedMemory(true, result))) + return err; + + if (!sharedMemory->sm_callbacks) + return MS_OK; + + return sharedMemory->sm_callbacks->cb_drop_table(table_path, result); + } + + int renameTable(const char *from_table, const char *to_table, PBMSResultPtr result) + { + int err; + + if ((err = getSharedMemory(true, result))) + return err; + + if (!sharedMemory->sm_callbacks) + return MS_OK; + + return sharedMemory->sm_callbacks->cb_rename_table(from_table, to_table, result); + } + + volatile PBMSSharedMemoryPtr sharedMemory; + +private: + int getSharedMemory(bool create, PBMSResultPtr result) + { + int tmp_f; + int r; + char temp_file[100]; + const char **prefix = temp_prefix; + void *tmp_p = NULL; + + if (sharedMemory) + return MS_OK; + + while (*prefix) { + getTempFileName(temp_file, *prefix, getpid()); + tmp_f = open(temp_file, O_RDWR | (create ? O_CREAT : 0), SH_MASK); + if (tmp_f == -1) + return setOSResult(errno, "open", temp_file, result); + + r = lseek(tmp_f, 0, SEEK_SET); + if (r == -1) { + close(tmp_f); + return setOSResult(errno, "lseek", temp_file, result); + } + ssize_t tfer; + char buffer[100]; + + tfer = read(tmp_f, buffer, 100); + if (tfer == -1) { + close(tmp_f); + return setOSResult(errno, "read", temp_file, result); + } + + buffer[tfer] = 0; + sscanf(buffer, "%p", &tmp_p); + sharedMemory = (PBMSSharedMemoryPtr) tmp_p; + if (!sharedMemory || sharedMemory->sm_magic != MS_SHARED_MEMORY_MAGIC) { + if (!create) + return MS_OK; + + sharedMemory = (PBMSSharedMemoryPtr) calloc(1, sizeof(PBMSSharedMemoryRec)); + sharedMemory->sm_magic = MS_SHARED_MEMORY_MAGIC; + sharedMemory->sm_version = MS_SHARED_MEMORY_VERSION; + sharedMemory->sm_list_size = MS_ENGINE_LIST_SIZE; + + r = lseek(tmp_f, 0, SEEK_SET); + if (r == -1) { + close(tmp_f); + return setOSResult(errno, "fseek", temp_file, result); + } + + sprintf(buffer, "%p", (void *) sharedMemory); + tfer = write(tmp_f, buffer, strlen(buffer)); + if (tfer != (ssize_t) strlen(buffer)) { + close(tmp_f); + return setOSResult(errno, "write", temp_file, result); + } + r = fsync(tmp_f); + if (r == -1) { + close(tmp_f); + return setOSResult(errno, "fsync", temp_file, result); + } + } + else if (sharedMemory->sm_version != MS_SHARED_MEMORY_VERSION) { + close(tmp_f); + result->mr_code = -1000; + *result->mr_stack = 0; + strcpy(MS_RESULT_MESSAGE_SIZE, result->mr_message, "Shared memory version: "); + strcat(MS_RESULT_MESSAGE_SIZE, result->mr_message, sharedMemory->sm_version); + strcat(MS_RESULT_MESSAGE_SIZE, result->mr_message, ", does not match engine shared memory version: "); + strcat(MS_RESULT_MESSAGE_SIZE, result->mr_message, MS_SHARED_MEMORY_VERSION); + strcat(MS_RESULT_MESSAGE_SIZE, result->mr_message, "."); + return MS_ERR_ENGINE; + } + close(tmp_f); + + // For backward compatability we need to create the old versions but we only need to read the current version. + if (create) + prefix++; + else + break; + } + return MS_OK; + } + + void strcpy(size_t size, char *to, const char *from) + { + if (size > 0) { + size--; + while (*from && size--) + *to++ = *from++; + *to = 0; + } + } + + void strcat(size_t size, char *to, const char *from) + { + while (*to && size--) to++; + strcpy(size, to, from); + } + + void strcat(size_t size, char *to, int val) + { + char buffer[100]; + + sprintf(buffer, "%d", val); + strcat(size, to, buffer); + } + + int setOSResult(int err, const char *func, char *file, PBMSResultPtr result) { + char *msg; + + result->mr_code = err; + *result->mr_stack = 0; + strcpy(MS_RESULT_MESSAGE_SIZE, result->mr_message, "System call "); + strcat(MS_RESULT_MESSAGE_SIZE, result->mr_message, func); + strcat(MS_RESULT_MESSAGE_SIZE, result->mr_message, "() failed on "); + strcat(MS_RESULT_MESSAGE_SIZE, result->mr_message, file); + strcat(MS_RESULT_MESSAGE_SIZE, result->mr_message, ": "); + +#ifdef XT_WIN + if (FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, NULL, err, 0, iMessage + strlen(iMessage), MS_RESULT_MESSAGE_SIZE - strlen(iMessage), NULL)) { + char *ptr; + + ptr = &iMessage[strlen(iMessage)]; + while (ptr-1 > err_msg) { + if (*(ptr-1) != '\n' && *(ptr-1) != '\r' && *(ptr-1) != '.') + break; + ptr--; + } + *ptr = 0; + + strcat(MS_RESULT_MESSAGE_SIZE, result->mr_message, " ("); + strcat(MS_RESULT_MESSAGE_SIZE, result->mr_message, err); + strcat(MS_RESULT_MESSAGE_SIZE, result->mr_message, ")"); + return MS_ERR_ENGINE; + } +#endif + + msg = strerror(err); + if (msg) { + strcat(MS_RESULT_MESSAGE_SIZE, result->mr_message, msg); + strcat(MS_RESULT_MESSAGE_SIZE, result->mr_message, " ("); + strcat(MS_RESULT_MESSAGE_SIZE, result->mr_message, err); + strcat(MS_RESULT_MESSAGE_SIZE, result->mr_message, ")"); + } + else { + strcat(MS_RESULT_MESSAGE_SIZE, result->mr_message, "Unknown OS error code "); + strcat(MS_RESULT_MESSAGE_SIZE, result->mr_message, err); + } + + return MS_ERR_ENGINE; + } + + void getTempFileName(char *temp_file, const char * prefix, int pid) + { + sprintf(temp_file, "/tmp/%s%d", prefix, pid); + } + + bool startsWith(const char *cstr, const char *w_cstr) + { + while (*cstr && *w_cstr) { + if (*cstr != *w_cstr) + return false; + cstr++; + w_cstr++; + } + return *cstr || !*w_cstr; + } + + void deleteTempFiles() + { + struct dirent *entry; + struct dirent *result; + DIR *odir; + int err; + size_t sz; + char temp_file[100]; + +#ifdef XT_SOLARIS + sz = sizeof(struct dirent) + pathconf("/tmp/", _PC_NAME_MAX); // Solaris, see readdir(3C) +#else + sz = sizeof(struct dirent); +#endif + entry = (struct dirent*)malloc(sz); + if (!(odir = opendir("/tmp/"))) + return; + err = readdir_r(odir, entry, &result); + while (!err && result) { + const char **prefix = temp_prefix; + + while (*prefix) { + if (startsWith(entry->d_name, *prefix)) { + int pid = atoi(entry->d_name + strlen(*prefix)); + + /* If the process does not exist: */ + if (kill(pid, 0) == -1 && errno == ESRCH) { + getTempFileName(temp_file, *prefix, pid); + unlink(temp_file); + } + } + prefix++; + } + + err = readdir_r(odir, entry, &result); + } + closedir(odir); + free(entry); + } +}; +#endif // PBMS_API + +/* + * The following is a low level API for accessing blobs directly. + */ + + +/* + * Any threads using the direct blob access API must first register them selves with the + * blob streaming engine before using the blob access functions. This is done by calling + * PBMSInitBlobStreamingThread(). Call PBMSDeinitBlobStreamingThread() after the thread is + * done using the direct blob access API + */ + +/* +* PBMSInitBlobStreamingThread(): Returns a pointer to a blob streaming thread. +*/ +extern void *PBMSInitBlobStreamingThread(char *thread_name, PBMSResultPtr result); +extern void PBMSDeinitBlobStreamingThread(void *v_bs_thread); + +/* +* PBMSGetError():Gets the last error reported by a blob streaming thread. +*/ +extern void PBMSGetError(void *v_bs_thread, PBMSResultPtr result); + +/* +* PBMSCreateBlob():Creates a new blob in the database of the given size. cont_type can be NULL. +*/ +extern bool PBMSCreateBlob(PBMSBlobIDPtr blob_id, char *database_name, char *cont_type, u_int64_t size); + +/* +* PBMSWriteBlob():Write the data to the blob in one or more chunks. The total size of all the chuncks of +* data written to the blob must match the size specified when the blob was created. +*/ +extern bool PBMSWriteBlob(PBMSBlobIDPtr blob_id, char *database_name, char *data, size_t size, size_t offset); + +/* +* PBMSReadBlob():Read the blob data out of the blob in one or more chunks. +*/ +extern bool PBMSReadBlob(PBMSBlobIDPtr blob_id, char *database_name, char *buffer, size_t *size, size_t offset); + +/* +* PBMSIDToURL():Convert a blob id to a blob URL. The 'url' buffer must be atleast PBMS_BLOB_URL_SIZE bytes in size. +*/ +extern bool PBMSIDToURL(PBMSBlobIDPtr blob_id, char *database_name, char *url); + +/* +* PBMSIDToURL():Convert a blob URL to a blob ID. +*/ +extern bool PBMSURLToID(char *url, PBMSBlobIDPtr blob_id); + +#endif diff --git a/storage/pbxt/src/pthread_xt.cc b/storage/pbxt/src/pthread_xt.cc new file mode 100755 index 00000000000..0a9f4da2074 --- /dev/null +++ b/storage/pbxt/src/pthread_xt.cc @@ -0,0 +1,712 @@ +/* Copyright (c) 2005 PrimeBase Technologies GmbH + * + * PrimeBase XT + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You 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 + * + * 2006-03-22 Paul McCullagh + * + * H&G2JCtL + * + * This file contains windows specific code + */ + +#include "xt_config.h" + +#ifdef XT_WIN +#include <my_pthread.h> +#else +#include <sys/resource.h> +#endif +#include <errno.h> +#include <limits.h> +#include <string.h> + +#include "pthread_xt.h" +#include "thread_xt.h" + +#ifdef XT_WIN + +void xt_p_init_threading(void) +{ +} + +int xt_p_set_normal_priority(pthread_t thr) +{ + if (!SetThreadPriority (thr, THREAD_PRIORITY_NORMAL)) + return GetLastError(); + return 0; +} + +int xt_p_set_low_priority(pthread_t thr) +{ + if (!SetThreadPriority (thr, THREAD_PRIORITY_LOWEST)) + return GetLastError(); + return 0; +} + +int xt_p_set_high_priority(pthread_t thr) +{ + if (!SetThreadPriority (thr, THREAD_PRIORITY_HIGHEST)) + return GetLastError(); + return 0; +} + +#define XT_RWLOCK_MAGIC 0x78AC390E + +#ifdef XT_THREAD_LOCK_INFO +int xt_p_mutex_init(xt_mutex_type *mutex, const pthread_mutexattr_t *attr, const char *n) +#else +int xt_p_mutex_init(xt_mutex_type *mutex, const pthread_mutexattr_t *attr) +#endif +{ + InitializeCriticalSection(&mutex->mt_cs); +#ifdef XT_THREAD_LOCK_INFO + xt_thread_lock_info_init(&mutex->mt_lock_info, mutex); + mutex->mt_name = n; +#endif + return 0; +} + +int xt_p_mutex_destroy(xt_mutex_type *mutex) +{ + DeleteCriticalSection(&mutex->mt_cs); +#ifdef XT_THREAD_LOCK_INFO + xt_thread_lock_info_free(&mutex->mt_lock_info); +#endif + return 0; +} + +int xt_p_mutex_lock(xt_mutex_type *mx) +{ + EnterCriticalSection(&mx->mt_cs); +#ifdef XT_THREAD_LOCK_INFO + xt_thread_lock_info_add_owner(&mx->mt_lock_info); +#endif + return 0; +} + +int xt_p_mutex_unlock(xt_mutex_type *mx) +{ + LeaveCriticalSection(&mx->mt_cs); +#ifdef XT_THREAD_LOCK_INFO + xt_thread_lock_info_release_owner(&mx->mt_lock_info); +#endif + return 0; +} + +int xt_p_mutex_trylock(xt_mutex_type *mutex) +{ +#if(_WIN32_WINNT >= 0x0400) + /* NOTE: MySQL bug! was using?! + * pthread_mutex_trylock(A) (WaitForSingleObject((A), 0) == WAIT_TIMEOUT) + */ + if (TryEnterCriticalSection(&mutex->mt_cs)) { +#ifdef XT_THREAD_LOCK_INFO + xt_thread_lock_info_add_owner(&mutex->mt_lock_info); +#endif + return 0; + } + return WAIT_TIMEOUT; +#else + EnterCriticalSection(&mutex->mt_cs); +#ifdef XT_THREAD_LOCK_INFO + xt_thread_lock_info_add_owner(&mutex->mt_lock_info); +#endif + return 0; +#endif +} + +#ifdef XT_THREAD_LOCK_INFO +int xt_p_rwlock_init(xt_rwlock_type *rwl, const pthread_condattr_t *attr, const char *n) +#else +int xt_p_rwlock_init(xt_rwlock_type *rwl, const pthread_condattr_t *attr) +#endif +{ + int result; + + if (rwl == NULL) + return ERROR_BAD_ARGUMENTS; + + rwl->rw_sh_count = 0; + rwl->rw_ex_count = 0; + rwl->rw_sh_complete_count = 0; + + result = xt_p_mutex_init_with_autoname(&rwl->rw_ex_lock, NULL); + if (result != 0) + goto failed; + + result = xt_p_mutex_init_with_autoname(&rwl->rw_sh_lock, NULL); + if (result != 0) + goto failed_2; + + result = pthread_cond_init(&rwl->rw_sh_cond, NULL); + if (result != 0) + goto failed_3; + + rwl->rw_magic = XT_RWLOCK_MAGIC; +#ifdef XT_THREAD_LOCK_INFO + rwl->rw_name = n; + xt_thread_lock_info_init(&rwl->rw_lock_info, rwl); +#endif + return 0; + + failed_3: + (void) xt_p_mutex_destroy(&rwl->rw_sh_lock); + + failed_2: + (void) xt_p_mutex_destroy(&rwl->rw_ex_lock); + + failed: + return result; +} + +int xt_p_rwlock_destroy(xt_rwlock_type *rwl) +{ + int result = 0, result1 = 0, result2 = 0; + + if (rwl == NULL) + return ERROR_BAD_ARGUMENTS; + + if (rwl->rw_magic != XT_RWLOCK_MAGIC) + return ERROR_BAD_ARGUMENTS; + + if ((result = xt_p_mutex_lock(&rwl->rw_ex_lock)) != 0) + return result; + + if ((result = xt_p_mutex_lock(&rwl->rw_sh_lock)) != 0) { + (void) xt_p_mutex_unlock(&rwl->rw_ex_lock); + return result; + } + + /* + * Check whether any threads own/wait for the lock (wait for ex.access); + * report "BUSY" if so. + */ + if (rwl->rw_ex_count > 0 || rwl->rw_sh_count > rwl->rw_sh_complete_count) { + result = xt_p_mutex_unlock(&rwl->rw_sh_lock); + result1 = xt_p_mutex_unlock(&rwl->rw_ex_lock); + result2 = ERROR_BUSY; + } + else { + rwl->rw_magic = 0; + + if ((result = xt_p_mutex_unlock(&rwl->rw_sh_lock)) != 0) + { + xt_p_mutex_unlock(&rwl->rw_ex_lock); + return result; + } + + if ((result = xt_p_mutex_unlock(&rwl->rw_ex_lock)) != 0) + return result; + + result = pthread_cond_destroy(&rwl->rw_sh_cond); + result1 = xt_p_mutex_destroy(&rwl->rw_sh_lock); + result2 = xt_p_mutex_destroy(&rwl->rw_ex_lock); + } + +#ifdef XT_THREAD_LOCK_INFO + xt_thread_lock_info_free(&rwl->rw_lock_info); +#endif + + return (result != 0) ? result : ((result1 != 0) ? result1 : result2); +} + + +int xt_p_rwlock_rdlock(xt_rwlock_type *rwl) +{ + int result; + + if (rwl == NULL) + return ERROR_BAD_ARGUMENTS; + + if (rwl->rw_magic != XT_RWLOCK_MAGIC) + return ERROR_BAD_ARGUMENTS; + + if ((result = xt_p_mutex_lock(&rwl->rw_ex_lock)) != 0) + return result; + + if (++rwl->rw_sh_count == INT_MAX) { + if ((result = xt_p_mutex_lock(&rwl->rw_sh_lock)) != 0) + { + (void) xt_p_mutex_unlock(&rwl->rw_ex_lock); + return result; + } + + rwl->rw_sh_count -= rwl->rw_sh_complete_count; + rwl->rw_sh_complete_count = 0; + + if ((result = xt_p_mutex_unlock(&rwl->rw_sh_lock)) != 0) + { + (void) xt_p_mutex_unlock(&rwl->rw_ex_lock); + return result; + } + } + +#ifdef XT_THREAD_LOCK_INFO + xt_thread_lock_info_add_owner(&rwl->rw_lock_info); +#endif + + return (xt_p_mutex_unlock (&(rwl->rw_ex_lock))); +} + +int xt_p_rwlock_wrlock(xt_rwlock_type *rwl) +{ + int result; + + if (rwl == NULL) + return ERROR_BAD_ARGUMENTS; + + if (rwl->rw_magic != XT_RWLOCK_MAGIC) + return ERROR_BAD_ARGUMENTS; + + if ((result = xt_p_mutex_lock (&rwl->rw_ex_lock)) != 0) + return result; + + if ((result = xt_p_mutex_lock (&rwl->rw_sh_lock)) != 0) { + (void) xt_p_mutex_unlock (&rwl->rw_ex_lock); + return result; + } + + if (rwl->rw_ex_count == 0) { + if (rwl->rw_sh_complete_count > 0) { + rwl->rw_sh_count -= rwl->rw_sh_complete_count; + rwl->rw_sh_complete_count = 0; + } + + if (rwl->rw_sh_count > 0) { + rwl->rw_sh_complete_count = -rwl->rw_sh_count; + + do { + result = pthread_cond_wait (&rwl->rw_sh_cond, &rwl->rw_sh_lock.mt_cs); + } + while (result == 0 && rwl->rw_sh_complete_count < 0); + + if (result == 0) + rwl->rw_sh_count = 0; + } + } + + if (result == 0) + rwl->rw_ex_count++; + +#ifdef XT_THREAD_LOCK_INFO + xt_thread_lock_info_add_owner(&rwl->rw_lock_info); +#endif + + return result; +} + +int xt_p_rwlock_unlock(xt_rwlock_type *rwl) +{ + int result, result1; + + if (rwl == NULL) + return (ERROR_BAD_ARGUMENTS); + + if (rwl->rw_magic != XT_RWLOCK_MAGIC) + return ERROR_BAD_ARGUMENTS; + + if (rwl->rw_ex_count == 0) { + if ((result = xt_p_mutex_lock(&rwl->rw_sh_lock)) != 0) + return result; + + if (++rwl->rw_sh_complete_count == 0) + result = pthread_cond_signal(&rwl->rw_sh_cond); + + result1 = xt_p_mutex_unlock(&rwl->rw_sh_lock); + } + else { + rwl->rw_ex_count--; + + result = xt_p_mutex_unlock(&rwl->rw_sh_lock); + result1 = xt_p_mutex_unlock(&rwl->rw_ex_lock); + } + +#ifdef XT_THREAD_LOCK_INFO + xt_thread_lock_info_release_owner(&rwl->rw_lock_info); +#endif + + return ((result != 0) ? result : result1); +} + +int xt_p_cond_wait(xt_cond_type *cond, xt_mutex_type *mutex) +{ + return xt_p_cond_timedwait(cond, mutex, NULL); +} + +int xt_p_cond_timedwait(xt_cond_type *cond, xt_mutex_type *mt, struct timespec *abstime) +{ + pthread_mutex_t *mutex = &mt->mt_cs; + int result; + long timeout; + union ft64 now; + + if (abstime != NULL) { + GetSystemTimeAsFileTime(&now.ft); + + timeout = (long)((abstime->tv.i64 - now.i64) / 10000); + if (timeout < 0) + timeout = 0L; + if (timeout > abstime->max_timeout_msec) + timeout = abstime->max_timeout_msec; + } + else + timeout= INFINITE; + + WaitForSingleObject(cond->broadcast_block_event, INFINITE); + + EnterCriticalSection(&cond->lock_waiting); + cond->waiting++; + LeaveCriticalSection(&cond->lock_waiting); + + LeaveCriticalSection(mutex); + + result= WaitForMultipleObjects(2, cond->events, FALSE, timeout); + + EnterCriticalSection(&cond->lock_waiting); + cond->waiting--; + + if (cond->waiting == 0) { + /* The last waiter must reset the broadcast + * state (whther there was a broadcast or not)! + */ + ResetEvent(cond->events[xt_cond_type::BROADCAST]); + SetEvent(cond->broadcast_block_event); + } + LeaveCriticalSection(&cond->lock_waiting); + + EnterCriticalSection(mutex); + + return result == WAIT_TIMEOUT ? ETIMEDOUT : 0; +} + +int xt_p_join(pthread_t thread, void **value) +{ + switch (WaitForSingleObject(thread, INFINITE)) { + case WAIT_OBJECT_0: + case WAIT_TIMEOUT: + /* Don't do this! According to the Win docs: + * _endthread automatically closes the thread handle + * (whereas _endthreadex does not). Therefore, when using + * _beginthread and _endthread, do not explicitly close the + * thread handle by calling the Win32 CloseHandle API. + CloseHandle(thread); + */ + break; + case WAIT_FAILED: + return GetLastError(); + } + return 0; +} + +#else // XT_WIN + +#ifdef __darwin__ +#define POLICY SCHED_RR +#else +#define POLICY pth_policy +#endif + +static int pth_policy; +static int pth_normal_priority; +static int pth_min_priority; +static int pth_max_priority; + +/* Return zero if the priority was set OK, + * else errno. + */ +static int pth_set_priority(pthread_t thread, int priority) +{ + struct sched_param sp; + + memset(&sp, 0, sizeof(struct sched_param)); + sp.sched_priority = priority; + return pthread_setschedparam(thread, POLICY, &sp); +} + +static void pth_get_priority_limits(void) +{ + XTThreadPtr self = NULL; + struct sched_param sp; + int err; + int start; + + /* Save original priority: */ + err = pthread_getschedparam(pthread_self(), &pth_policy, &sp); + if (err) { + xt_throw_errno(XT_CONTEXT, err); + return; + } + pth_normal_priority = sp.sched_priority; + + start = sp.sched_priority; + +#ifdef XT_FREEBSD + pth_min_priority = sched_get_priority_min(sched_getscheduler(0)); + pth_max_priority = sched_get_priority_max(sched_getscheduler(0)); +#else + /* Search for the minimum priority: */ + pth_min_priority = start; + for (;;) { + /* 2007-03-01: Corrected, pth_set_priority returns the error code + * (thanks to Hakan for pointing out this bug!) + */ + if (pth_set_priority(pthread_self(), pth_min_priority-1) != 0) + break; + pth_min_priority--; + } + + /* Search for the maximum priority: */ + pth_max_priority = start; + for (;;) { + if (pth_set_priority(pthread_self(), pth_max_priority+1) != 0) + break; + pth_max_priority++; + } + + /* Restore original priority: */ + pthread_setschedparam(pthread_self(), pth_policy, &sp); +#endif +} + +xtPublic void xt_p_init_threading(void) +{ + pth_get_priority_limits(); +} + +xtPublic int xt_p_set_low_priority(pthread_t thr) +{ + if (pth_min_priority == pth_max_priority) { + /* Under Linux the priority of normal (non-runtime) + * threads are set using the standard methods + * for setting process priority. + */ + + /* We could set who == 0 because it should have the same affect + * as using the PID. + */ + + /* -20 = highest, 20 = lowest */ + if (setpriority(PRIO_PROCESS, getpid(), 20) == -1) + return errno; + return 0; + } + return pth_set_priority(thr, pth_min_priority); +} + +xtPublic int xt_p_set_normal_priority(pthread_t thr) +{ + if (pth_min_priority == pth_max_priority) { + if (setpriority(PRIO_PROCESS, getpid(), 0) == -1) + return errno; + return 0; + } + return pth_set_priority(thr, pth_normal_priority); +} + +xtPublic int xt_p_set_high_priority(pthread_t thr) +{ + if (pth_min_priority == pth_max_priority) { + if (setpriority(PRIO_PROCESS, getpid(), -20) == -1) + return errno; + return 0; + } + return pth_set_priority(thr, pth_max_priority); +} + +#ifdef DEBUG_LOCKING + +xtPublic int xt_p_mutex_lock(xt_mutex_type *mutex, u_int line, const char *file) +{ + XTThreadPtr self = xt_get_self(); + int r; + + ASSERT_NS(mutex->mu_init == 12345); + r = pthread_mutex_lock(&mutex->mu_plock); + if (r == 0) { + if (mutex->mu_trace) + printf("==LOCK mutex %d %s:%d\n", (int) mutex->mu_trace, file, (int) line); + ASSERT_NS(!mutex->mu_locker); + mutex->mu_locker = self; + mutex->mu_line = line; + mutex->mu_file = file; + } +#ifdef XT_THREAD_LOCK_INFO + xt_thread_lock_info_add_owner(&mutex->mu_lock_info); +#endif + return r; +} + +xtPublic int xt_p_mutex_unlock(xt_mutex_type *mutex) +{ + XTThreadPtr self = xt_get_self(); + + ASSERT_NS(mutex->mu_init == 12345); + ASSERT_NS(mutex->mu_locker == self); + mutex->mu_locker = NULL; + if (mutex->mu_trace) + printf("UNLOCK mutex %d\n", (int) mutex->mu_trace); +#ifdef XT_THREAD_LOCK_INFO + xt_thread_lock_info_release_owner(&mutex->mu_lock_info); +#endif + return pthread_mutex_unlock(&mutex->mu_plock); +} + +xtPublic int xt_p_mutex_destroy(xt_mutex_type *mutex) +{ + ASSERT_NS(mutex->mu_init == 12345); + mutex->mu_init = 89898; +#ifdef XT_THREAD_LOCK_INFO + xt_thread_lock_info_free(&mutex->mu_lock_info); +#endif + return pthread_mutex_destroy(&mutex->mu_plock); +} + +xtPublic int xt_p_mutex_trylock(xt_mutex_type *mutex) +{ + XTThreadPtr self = xt_get_self(); + int r; + + ASSERT_NS(mutex->mu_init == 12345); + r = pthread_mutex_trylock(&mutex->mu_plock); + if (r == 0) { + ASSERT_NS(!mutex->mu_locker); + mutex->mu_locker = self; +#ifdef XT_THREAD_LOCK_INFO + xt_thread_lock_info_add_owner(&mutex->mu_lock_info); +#endif + } + return r; +} + +#ifdef XT_THREAD_LOCK_INFO +xtPublic int xt_p_mutex_init(xt_mutex_type *mutex, const pthread_mutexattr_t *attr, const char *n) +#else +xtPublic int xt_p_mutex_init(xt_mutex_type *mutex, const pthread_mutexattr_t *attr) +#endif +{ + mutex->mu_init = 12345; + mutex->mu_trace = FALSE; + mutex->mu_locker = NULL; +#ifdef XT_THREAD_LOCK_INFO + mutex->mu_name = n; + xt_thread_lock_info_init(&mutex->mu_lock_info, mutex); +#endif + return pthread_mutex_init(&mutex->mu_plock, attr); +} + +xtPublic int xt_p_cond_wait(xt_cond_type *cond, xt_mutex_type *mutex) +{ + XTThreadPtr self = xt_get_self(); + int r; + + ASSERT_NS(mutex->mu_init == 12345); + ASSERT_NS(mutex->mu_locker == self); + mutex->mu_locker = NULL; + r = pthread_cond_wait(cond, &mutex->mu_plock); + ASSERT_NS(!mutex->mu_locker); + mutex->mu_locker = self; + return r; +} + +xtPublic int xt_p_cond_timedwait(xt_cond_type *cond, xt_mutex_type *mutex, const struct timespec *abstime) +{ + XTThreadPtr self = xt_get_self(); + int r; + + ASSERT_NS(mutex->mu_init == 12345); + ASSERT_NS(mutex->mu_locker == self); + mutex->mu_locker = NULL; + r = pthread_cond_timedwait(cond, &mutex->mu_plock, abstime); + ASSERT_NS(!mutex->mu_locker); + mutex->mu_locker = self; + return r; +} + +xtPublic int xt_p_rwlock_rdlock(xt_rwlock_type *rwlock) +{ + int r; + + ASSERT_NS(rwlock->rw_init == 67890); + r = pthread_rwlock_rdlock(&rwlock->rw_plock); +#ifdef XT_THREAD_LOCK_INFO + xt_thread_lock_info_add_owner(&rwlock->rw_lock_info); +#endif + return r; +} + +xtPublic int xt_p_rwlock_wrlock(xt_rwlock_type *rwlock) +{ + XTThreadPtr self = xt_get_self(); + int r; + + ASSERT_NS(rwlock->rw_init == 67890); + r = pthread_rwlock_wrlock(&rwlock->rw_plock); + if (r == 0) { + ASSERT_NS(!rwlock->rw_locker); + rwlock->rw_locker = self; + } +#ifdef XT_THREAD_LOCK_INFO + xt_thread_lock_info_add_owner(&rwlock->rw_lock_info); +#endif + return r; +} + +xtPublic int xt_p_rwlock_unlock(xt_rwlock_type *rwlock) +{ + XTThreadPtr self = xt_get_self(); + + ASSERT_NS(rwlock->rw_init == 67890); + if (rwlock->rw_locker) { + ASSERT_NS(rwlock->rw_locker == self); + rwlock->rw_locker = NULL; + } +#ifdef XT_THREAD_LOCK_INFO + xt_thread_lock_info_release_owner(&rwlock->rw_lock_info); +#endif + return pthread_rwlock_unlock(&rwlock->rw_plock); +} + +xtPublic int xt_p_rwlock_destroy(xt_rwlock_type *rwlock) +{ + ASSERT_NS(rwlock->rw_init == 67890); + rwlock->rw_init = 0; +#ifdef XT_THREAD_LOCK_INFO + xt_thread_lock_info_free(&rwlock->rw_lock_info); +#endif + return pthread_rwlock_destroy(&rwlock->rw_plock); +} + +#ifdef XT_THREAD_LOCK_INFO +xtPublic int xt_p_rwlock_init(xt_rwlock_type *rwlock, const pthread_rwlockattr_t *attr, const char *n) +#else +xtPublic int xt_p_rwlock_init(xt_rwlock_type *rwlock, const pthread_rwlockattr_t *attr) +#endif +{ + rwlock->rw_init = 67890; + rwlock->rw_readers = 0; + rwlock->rw_locker = NULL; +#ifdef XT_THREAD_LOCK_INFO + rwlock->rw_name = n; + xt_thread_lock_info_init(&rwlock->rw_lock_info, rwlock); +#endif + return pthread_rwlock_init(&rwlock->rw_plock, attr); +} + +#endif // DEBUG_LOCKING + +#endif // XT_WIN + diff --git a/storage/pbxt/src/pthread_xt.h b/storage/pbxt/src/pthread_xt.h new file mode 100755 index 00000000000..d8ef1a85d41 --- /dev/null +++ b/storage/pbxt/src/pthread_xt.h @@ -0,0 +1,292 @@ +/* Copyright (c) 2005 PrimeBase Technologies GmbH + * + * PrimeBase XT + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You 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 + * + * 2006-03-22 Paul McCullagh + * + * H&G2JCtL + * + * This file contains windows specific code. + */ + +#ifndef __win_xt_h__ +#define __win_xt_h__ + +#ifdef XT_WIN +#include <windef.h> +#include <my_pthread.h> +#else +#include <pthread.h> +#endif + +#include "locklist_xt.h" + +#ifdef DEBUG +//#define DEBUG_LOCKING +#endif + +#define xt_cond_struct _opaque_pthread_cond_t +#define xt_cond_type pthread_cond_t + +#define xt_cond_wait pthread_cond_wait +#define xt_cond_wakeall pthread_cond_broadcast + +#ifdef __cplusplus +extern "C" { +#endif +void xt_p_init_threading(void); +int xt_p_set_normal_priority(pthread_t thr); +int xt_p_set_low_priority(pthread_t thr); +int xt_p_set_high_priority(pthread_t thr); +#ifdef __cplusplus +} +#endif + +#ifdef XT_WIN + +#ifdef __cplusplus +extern "C" { +#endif + +typedef LPVOID pthread_key_t; + +typedef struct xt_mutex_struct { + CRITICAL_SECTION mt_cs; +#ifdef XT_THREAD_LOCK_INFO + const char *mt_name; + XTThreadLockInfoRec mt_lock_info; +#endif +} xt_mutex_type; + +typedef struct xt_rwlock_struct { + xt_mutex_type rw_ex_lock; + xt_mutex_type rw_sh_lock; + pthread_cond_t rw_sh_cond; + int rw_sh_count; + int rw_ex_count; + int rw_sh_complete_count; + int rw_magic; +#ifdef XT_THREAD_LOCK_INFO + const char *rw_name; + XTThreadLockInfoRec rw_lock_info; +#endif +} xt_rwlock_type; + +#ifdef XT_THREAD_LOCK_INFO +int xt_p_mutex_init(xt_mutex_type *mutex, const pthread_mutexattr_t *attr, const char *name); +#else +int xt_p_mutex_init(xt_mutex_type *mutex, const pthread_mutexattr_t *attr); +#endif +int xt_p_mutex_destroy(xt_mutex_type *mutex); +int xt_p_mutex_lock(xt_mutex_type *mx); +int xt_p_mutex_unlock(xt_mutex_type *mx); +int xt_p_mutex_trylock(xt_mutex_type *mutex); + +#ifdef XT_THREAD_LOCK_INFO +int xt_p_rwlock_init(xt_rwlock_type *rwlock, const pthread_condattr_t *attr, const char *name); +#else +int xt_p_rwlock_init(xt_rwlock_type *rwlock, const pthread_condattr_t *attr); +#endif +int xt_p_rwlock_destroy(xt_rwlock_type *rwlock); +int xt_p_rwlock_rdlock(xt_rwlock_type *mx); +int xt_p_rwlock_wrlock(xt_rwlock_type *mx); +int xt_p_rwlock_unlock(xt_rwlock_type *mx); + +int xt_p_cond_wait(xt_cond_type *cond, xt_mutex_type *mutex); +int xt_p_cond_timedwait(xt_cond_type *cond, xt_mutex_type *mutex, struct timespec *abstime); + +int xt_p_join(pthread_t thread, void **value); + +#ifdef __cplusplus +} +#endif + +#ifdef XT_THREAD_LOCK_INFO +#define xt_p_rwlock_init_with_name(a,b,c) xt_p_rwlock_init(a,b,c) +#define xt_p_rwlock_init_with_autoname(a,b) xt_p_rwlock_init_with_name(a,b,LOCKLIST_ARG_SUFFIX(a)) +#else +#define xt_p_rwlock_init_with_name(a,b,c) xt_p_rwlock_init(a,b,c) +#define xt_p_rwlock_init_with_autoname(a,b) xt_p_rwlock_init(a,b) +#endif + +#define xt_slock_rwlock_ns xt_p_rwlock_rdlock +#define xt_xlock_rwlock_ns xt_p_rwlock_wrlock +#define xt_unlock_rwlock_ns xt_p_rwlock_unlock + +#ifdef XT_THREAD_LOCK_INFO +#define xt_p_mutex_init_with_name(a,b,c) xt_p_mutex_init(a,b,c) +#define xt_p_mutex_init_with_autoname(a,b) xt_p_mutex_init_with_name(a,b,LOCKLIST_ARG_SUFFIX(a)) +#else +#define xt_p_mutex_init_with_name(a,b,c) xt_p_mutex_init(a,b) +#define xt_p_mutex_init_with_autoname(a,b) xt_p_mutex_init(a,b) +#endif +#define xt_lock_mutex_ns xt_p_mutex_lock +#define xt_unlock_mutex_ns xt_p_mutex_unlock +#define xt_mutex_trylock xt_p_mutex_trylock + +#else // XT_WIN + +/* Finger weg! */ +#ifdef pthread_mutex_t +#undef pthread_mutex_t +#endif +#ifdef pthread_rwlock_t +#undef pthread_rwlock_t +#endif +#ifdef pthread_mutex_init +#undef pthread_mutex_init +#endif +#ifdef pthread_mutex_destroy +#undef pthread_mutex_destroy +#endif +#ifdef pthread_mutex_lock +#undef pthread_mutex_lock +#endif +#ifdef pthread_mutex_unlock +#undef pthread_mutex_unlock +#endif +#ifdef pthread_cond_wait +#undef pthread_cond_wait +#endif +#ifdef pthread_cond_broadcast +#undef pthread_cond_broadcast +#endif +#ifdef pthread_mutex_trylock +#undef pthread_mutex_trylock +#endif + +/* + * ----------------------------------------------------------------------- + * Reedefinition of pthread locking, for debugging + */ + +struct XTThread; + + +#ifdef XT_THREAD_LOCK_INFO + +#define xt_p_mutex_init_with_name(a,b,c) xt_p_mutex_init(a,b,c) +#define xt_p_mutex_init_with_autoname(a,b) xt_p_mutex_init_with_name(a,b,LOCKLIST_ARG_SUFFIX(a)) + +#define xt_p_rwlock_init_with_name(a,b,c) xt_p_rwlock_init(a,b,c) +#define xt_p_rwlock_init_with_autoname(a,b) xt_p_rwlock_init_with_name(a,b,LOCKLIST_ARG_SUFFIX(a)) + +#else + +#define xt_p_mutex_init_with_name(a,b,c) xt_p_mutex_init(a,b) +#define xt_p_mutex_init_with_autoname(a,b) xt_p_mutex_init(a,b) + +#define xt_p_rwlock_init_with_name(a,b,c) xt_p_rwlock_init(a,b) +#define xt_p_rwlock_init_with_autoname(a,b) xt_p_rwlock_init_with_name(a,b) + +#endif + +#ifdef DEBUG_LOCKING + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct xt_mutex_struct { + unsigned short mu_init; + unsigned short mu_trace; + unsigned int mu_line; + const char *mu_file; + struct XTThread *mu_locker; + pthread_mutex_t mu_plock; +#ifdef XT_THREAD_LOCK_INFO + const char *mu_name; + XTThreadLockInfoRec mu_lock_info; +#endif +} xt_mutex_type; + +typedef struct xt_rwlock_struct { + u_int rw_init; + volatile u_int rw_readers; + struct XTThread *rw_locker; + pthread_rwlock_t rw_plock; +#ifdef XT_THREAD_LOCK_INFO + const char *rw_name; + XTThreadLockInfoRec rw_lock_info; +#endif +} xt_rwlock_type; + +int xt_p_rwlock_rdlock(xt_rwlock_type *mx); +int xt_p_rwlock_wrlock(xt_rwlock_type *mx); +int xt_p_rwlock_unlock(xt_rwlock_type *mx); + +int xt_p_mutex_lock(xt_mutex_type *mx, u_int line, const char *file); +int xt_p_mutex_unlock(xt_mutex_type *mx); +int xt_p_mutex_trylock(xt_mutex_type *mutex); +int xt_p_mutex_destroy(xt_mutex_type *mutex); +#ifdef XT_THREAD_LOCK_INFO +int xt_p_mutex_init(xt_mutex_type *mutex, const pthread_mutexattr_t *attr, const char *name); +#else +int xt_p_mutex_init(xt_mutex_type *mutex, const pthread_mutexattr_t *attr); +#endif +int xt_p_rwlock_destroy(xt_rwlock_type * rwlock); +#ifdef XT_THREAD_LOCK_INFO +int xt_p_rwlock_init(xt_rwlock_type *rwlock, const pthread_rwlockattr_t *attr, const char *name); +#else +int xt_p_rwlock_init(xt_rwlock_type *rwlock, const pthread_rwlockattr_t *attr); +#endif +int xt_p_cond_wait(xt_cond_type *cond, xt_mutex_type *mutex); +int xt_p_cond_timedwait(xt_cond_type *cond, xt_mutex_type *mutex, const struct timespec *abstime); + +#ifdef __cplusplus +} +#endif + +#define xt_slock_rwlock_ns xt_p_rwlock_rdlock +#define xt_xlock_rwlock_ns xt_p_rwlock_wrlock +#define xt_unlock_rwlock_ns xt_p_rwlock_unlock + +#define xt_lock_mutex_ns(x) xt_p_mutex_lock(x, __LINE__, __FILE__) +#define xt_unlock_mutex_ns xt_p_mutex_unlock +#define xt_mutex_trylock xt_p_mutex_trylock + +#else // DEBUG_LOCKING + +#define xt_rwlock_struct _opaque_pthread_rwlock_t +#define xt_mutex_struct _opaque_pthread_mutex_t + +#define xt_rwlock_type pthread_rwlock_t +#define xt_mutex_type pthread_mutex_t + +#define xt_slock_rwlock_ns pthread_rwlock_rdlock +#define xt_xlock_rwlock_ns pthread_rwlock_wrlock +#define xt_unlock_rwlock_ns pthread_rwlock_unlock + +#define xt_lock_mutex_ns pthread_mutex_lock +#define xt_unlock_mutex_ns pthread_mutex_unlock +#define xt_mutex_trylock pthread_mutex_trylock + +#define xt_p_mutex_trylock pthread_mutex_trylock +#define xt_p_mutex_destroy pthread_mutex_destroy +#define xt_p_mutex_init pthread_mutex_init +#define xt_p_rwlock_destroy pthread_rwlock_destroy +#define xt_p_rwlock_init pthread_rwlock_init +#define xt_p_cond_wait pthread_cond_wait +#define xt_p_cond_timedwait pthread_cond_timedwait + +#endif // DEBUG_LOCKING + +#define xt_p_join pthread_join + +#endif // XT_WIN + +#endif diff --git a/storage/pbxt/src/restart_xt.cc b/storage/pbxt/src/restart_xt.cc new file mode 100644 index 00000000000..50390bfb727 --- /dev/null +++ b/storage/pbxt/src/restart_xt.cc @@ -0,0 +1,3203 @@ +/* Copyright (c) 2007 PrimeBase Technologies GmbH + * + * PrimeBase XT + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You 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 + * + * 2007-11-12 Paul McCullagh + * + * H&G2JCtL + * + * Restart and write data to the database. + */ + +#include "xt_config.h" + +#include <signal.h> +#include <time.h> + +#ifndef DRIZZLED +#include "mysql_priv.h" +#endif + +#include "ha_pbxt.h" + +#include "xactlog_xt.h" +#include "database_xt.h" +#include "util_xt.h" +#include "strutil_xt.h" +#include "filesys_xt.h" +#include "restart_xt.h" +#include "myxt_xt.h" +#include "trace_xt.h" + +#ifdef DEBUG +//#define DEBUG_PRINT +//#define DEBUG_KEEP_LOGS +//#define PRINT_LOG_ON_RECOVERY +//#define TRACE_RECORD_DATA +//#define SKIP_STARTUP_CHECKPOINT +//#define NEVER_CHECKPOINT +//#define TRACE_CHECKPOINT +#endif + +#define PRINTF printf +//#define PRINTF xt_ftracef +//#define PRINTF xt_trace + +void xt_print_bytes(xtWord1 *buf, u_int len) +{ + for (u_int i=0; i<len; i++) { + PRINTF("%02x ", (u_int) *buf); + buf++; + } +} + +void xt_print_log_record(xtLogID log, xtLogOffset offset, XTXactLogBufferDPtr record) +{ + const char *type = NULL; + const char *rec_type = NULL; + xtOpSeqNo op_no = 0; + xtTableID tab_id = 0; + xtRowID row_id = 0; + xtRecordID rec_id = 0; + xtBool xn_set = FALSE; + xtXactID xn_id = 0; + char buffer[200]; + XTTabRecExtDPtr rec_buf; + XTTabRecExtDPtr ext_rec; + XTTabRecFixDPtr fix_rec; + u_int rec_len; + xtLogID log_id = 0; + xtLogOffset log_offset = 0; + + rec_buf = NULL; + ext_rec = NULL; + fix_rec = NULL; + rec_len = 0; + switch (record->xl.xl_status_1) { + case XT_LOG_ENT_REC_MODIFIED: + case XT_LOG_ENT_UPDATE: + case XT_LOG_ENT_INSERT: + case XT_LOG_ENT_DELETE: + case XT_LOG_ENT_UPDATE_BG: + case XT_LOG_ENT_INSERT_BG: + case XT_LOG_ENT_DELETE_BG: + op_no = XT_GET_DISK_4(record->xu.xu_op_seq_4); + tab_id = XT_GET_DISK_4(record->xu.xu_tab_id_4); + rec_id = XT_GET_DISK_4(record->xu.xu_rec_id_4); + xn_id = XT_GET_DISK_4(record->xu.xu_xact_id_4); + row_id = XT_GET_DISK_4(record->xu.xu_row_id_4); + rec_len = XT_GET_DISK_2(record->xu.xu_size_2); + xn_set = TRUE; + type="rec"; + rec_buf = (XTTabRecExtDPtr) &record->xu.xu_rec_type_1; + ext_rec = (XTTabRecExtDPtr) &record->xu.xu_rec_type_1; + if (XT_REC_IS_EXT_DLOG(ext_rec->tr_rec_type_1)) { + log_id = XT_GET_DISK_2(ext_rec->re_log_id_2); + log_offset = XT_GET_DISK_6(ext_rec->re_log_offs_6); + } + else { + ext_rec = NULL; + fix_rec = (XTTabRecFixDPtr) &record->xu.xu_rec_type_1; + } + break; + case XT_LOG_ENT_UPDATE_FL: + case XT_LOG_ENT_INSERT_FL: + case XT_LOG_ENT_DELETE_FL: + case XT_LOG_ENT_UPDATE_FL_BG: + case XT_LOG_ENT_INSERT_FL_BG: + case XT_LOG_ENT_DELETE_FL_BG: + op_no = XT_GET_DISK_4(record->xf.xf_op_seq_4); + tab_id = XT_GET_DISK_4(record->xf.xf_tab_id_4); + rec_id = XT_GET_DISK_4(record->xf.xf_rec_id_4); + xn_id = XT_GET_DISK_4(record->xf.xf_xact_id_4); + row_id = XT_GET_DISK_4(record->xf.xf_row_id_4); + rec_len = XT_GET_DISK_2(record->xf.xf_size_2); + xn_set = TRUE; + type="rec"; + rec_buf = (XTTabRecExtDPtr) &record->xf.xf_rec_type_1; + ext_rec = (XTTabRecExtDPtr) &record->xf.xf_rec_type_1; + if (XT_REC_IS_EXT_DLOG(ext_rec->tr_rec_type_1)) { + log_id = XT_GET_DISK_2(ext_rec->re_log_id_2); + log_offset = XT_GET_DISK_6(ext_rec->re_log_offs_6); + } + else { + ext_rec = NULL; + fix_rec = (XTTabRecFixDPtr) &record->xf.xf_rec_type_1; + } + break; + case XT_LOG_ENT_REC_FREED: + case XT_LOG_ENT_REC_REMOVED: + case XT_LOG_ENT_REC_REMOVED_EXT: + op_no = XT_GET_DISK_4(record->fr.fr_op_seq_4); + tab_id = XT_GET_DISK_4(record->fr.fr_tab_id_4); + rec_id = XT_GET_DISK_4(record->fr.fr_rec_id_4); + xn_id = XT_GET_DISK_4(record->fr.fr_xact_id_4); + xn_set = TRUE; + type="rec"; + break; + case XT_LOG_ENT_REC_REMOVED_BI: + op_no = XT_GET_DISK_4(record->rb.rb_op_seq_4); + tab_id = XT_GET_DISK_4(record->rb.rb_tab_id_4); + rec_id = XT_GET_DISK_4(record->rb.rb_rec_id_4); + xn_id = XT_GET_DISK_4(record->rb.rb_xact_id_4); + row_id = XT_GET_DISK_4(record->rb.rb_row_id_4); + rec_len = XT_GET_DISK_2(record->rb.rb_size_2); + xn_set = TRUE; + type="rec"; + rec_buf = (XTTabRecExtDPtr) &record->rb.rb_rec_type_1; + ext_rec = (XTTabRecExtDPtr) &record->rb.rb_rec_type_1; + if (XT_REC_IS_EXT_DLOG(record->rb.rb_rec_type_1)) { + log_id = XT_GET_DISK_2(ext_rec->re_log_id_2); + log_offset = XT_GET_DISK_6(ext_rec->re_log_offs_6); + } + else { + ext_rec = NULL; + fix_rec = (XTTabRecFixDPtr) &record->rb.rb_rec_type_1; + } + break; + case XT_LOG_ENT_REC_MOVED: + op_no = XT_GET_DISK_4(record->xw.xw_op_seq_4); + tab_id = XT_GET_DISK_4(record->xw.xw_tab_id_4); + rec_id = XT_GET_DISK_4(record->xw.xw_rec_id_4); + log_id = XT_GET_DISK_2(&record->xw.xw_rec_type_1); // This is actually correct + log_offset = XT_GET_DISK_6(record->xw.xw_next_rec_id_4); // This is actually correct! + type="rec"; + break; + case XT_LOG_ENT_REC_CLEANED: + case XT_LOG_ENT_REC_CLEANED_1: + case XT_LOG_ENT_REC_UNLINKED: + op_no = XT_GET_DISK_4(record->xw.xw_op_seq_4); + tab_id = XT_GET_DISK_4(record->xw.xw_tab_id_4); + rec_id = XT_GET_DISK_4(record->xw.xw_rec_id_4); + type="rec"; + break; + case XT_LOG_ENT_ROW_NEW: + case XT_LOG_ENT_ROW_NEW_FL: + case XT_LOG_ENT_ROW_ADD_REC: + case XT_LOG_ENT_ROW_SET: + case XT_LOG_ENT_ROW_FREED: + op_no = XT_GET_DISK_4(record->xa.xa_op_seq_4); + tab_id = XT_GET_DISK_4(record->xa.xa_tab_id_4); + rec_id = XT_GET_DISK_4(record->xa.xa_row_id_4); + type="row"; + break; + case XT_LOG_ENT_NO_OP: + op_no = XT_GET_DISK_4(record->no.no_op_seq_4); + tab_id = XT_GET_DISK_4(record->no.no_tab_id_4); + type="-"; + break; + case XT_LOG_ENT_END_OF_LOG: + break; + } + + switch (record->xl.xl_status_1) { + case XT_LOG_ENT_HEADER: + rec_type = "HEADER"; + break; + case XT_LOG_ENT_NEW_LOG: + rec_type = "NEW LOG"; + break; + case XT_LOG_ENT_DEL_LOG: + sprintf(buffer, "DEL LOG log=%d ", (int) XT_GET_DISK_4(record->xl.xl_log_id_4)); + rec_type = buffer; + break; + case XT_LOG_ENT_NEW_TAB: + rec_type = "NEW TABLE"; + break; + case XT_LOG_ENT_COMMIT: + rec_type = "COMMIT"; + xn_id = XT_GET_DISK_4(record->xe.xe_xact_id_4); + xn_set = TRUE; + break; + case XT_LOG_ENT_ABORT: + rec_type = "ABORT"; + xn_id = XT_GET_DISK_4(record->xe.xe_xact_id_4); + xn_set = TRUE; + break; + case XT_LOG_ENT_CLEANUP: + rec_type = "CLEANUP"; + xn_id = XT_GET_DISK_4(record->xc.xc_xact_id_4); + xn_set = TRUE; + break; + case XT_LOG_ENT_REC_MODIFIED: + rec_type = "MODIFIED"; + break; + case XT_LOG_ENT_UPDATE: + rec_type = "UPDATE"; + break; + case XT_LOG_ENT_UPDATE_FL: + rec_type = "UPDATE-FL"; + break; + case XT_LOG_ENT_INSERT: + rec_type = "INSERT"; + break; + case XT_LOG_ENT_INSERT_FL: + rec_type = "INSERT-FL"; + break; + case XT_LOG_ENT_DELETE: + rec_type = "DELETE"; + break; + case XT_LOG_ENT_DELETE_FL: + rec_type = "DELETE-FL-BG"; + break; + case XT_LOG_ENT_UPDATE_BG: + rec_type = "UPDATE-BG"; + break; + case XT_LOG_ENT_UPDATE_FL_BG: + rec_type = "UPDATE-FL-BG"; + break; + case XT_LOG_ENT_INSERT_BG: + rec_type = "INSERT-BG"; + break; + case XT_LOG_ENT_INSERT_FL_BG: + rec_type = "INSERT-FL-BG"; + break; + case XT_LOG_ENT_DELETE_BG: + rec_type = "DELETE-BG"; + break; + case XT_LOG_ENT_DELETE_FL_BG: + rec_type = "DELETE-FL-BG"; + break; + case XT_LOG_ENT_REC_FREED: + rec_type = "FREE REC"; + break; + case XT_LOG_ENT_REC_REMOVED: + rec_type = "REMOVED REC"; + break; + case XT_LOG_ENT_REC_REMOVED_EXT: + rec_type = "REMOVED-X REC"; + break; + case XT_LOG_ENT_REC_REMOVED_BI: + rec_type = "REMOVED-BI REC"; + break; + case XT_LOG_ENT_REC_MOVED: + rec_type = "MOVED REC"; + break; + case XT_LOG_ENT_REC_CLEANED: + rec_type = "CLEAN REC"; + break; + case XT_LOG_ENT_REC_CLEANED_1: + rec_type = "CLEAN REC-1"; + break; + case XT_LOG_ENT_REC_UNLINKED: + rec_type = "UNLINK REC"; + break; + case XT_LOG_ENT_ROW_NEW: + rec_type = "NEW ROW"; + break; + case XT_LOG_ENT_ROW_NEW_FL: + rec_type = "NEW ROW-FL"; + break; + case XT_LOG_ENT_ROW_ADD_REC: + rec_type = "REC ADD ROW"; + break; + case XT_LOG_ENT_ROW_SET: + rec_type = "SET ROW"; + break; + case XT_LOG_ENT_ROW_FREED: + rec_type = "FREE ROW"; + break; + case XT_LOG_ENT_OP_SYNC: + rec_type = "OP SYNC"; + break; + case XT_LOG_ENT_NO_OP: + rec_type = "NO OP"; + break; + case XT_LOG_ENT_END_OF_LOG: + rec_type = "END OF LOG"; + break; + } + + if (log) + PRINTF("log=%d offset=%d ", (int) log, (int) offset); + PRINTF("%s ", rec_type); + if (type) + PRINTF("op=%lu tab=%lu %s=%lu ", (u_long) op_no, (u_long) tab_id, type, (u_long) rec_id); + if (row_id) + PRINTF("row=%lu ", (u_long) row_id); + if (log_id) + PRINTF("log=%lu offset=%lu ", (u_long) log_id, (u_long) log_offset); + if (xn_set) + PRINTF("xact=%lu ", (u_long) xn_id); + +#ifdef TRACE_RECORD_DATA + if (rec_buf) { + switch (rec_buf->tr_rec_type_1 & XT_TAB_STATUS_MASK) { + case XT_TAB_STATUS_FREED: + PRINTF("FREE"); + break; + case XT_TAB_STATUS_DELETE: + PRINTF("DELE"); + break; + case XT_TAB_STATUS_FIXED: + PRINTF("FIX-"); + break; + case XT_TAB_STATUS_VARIABLE: + PRINTF("VAR-"); + break; + case XT_TAB_STATUS_EXT_DLOG: + PRINTF("EXT-"); + break; + } + if (rec_buf->tr_rec_type_1 & XT_TAB_STATUS_CLEANED_BIT) + PRINTF("C"); + else + PRINTF(" "); + } + if (ext_rec) { + rec_len -= offsetof(XTTabRecExtDRec, re_data); + xt_print_bytes((xtWord1 *) ext_rec, offsetof(XTTabRecExtDRec, re_data)); + PRINTF("| "); + if (rec_len > 20) + rec_len = 20; + xt_print_bytes(ext_rec->re_data, rec_len); + } + if (fix_rec) { + rec_len -= offsetof(XTTabRecFixDRec, rf_data); + xt_print_bytes((xtWord1 *) fix_rec, offsetof(XTTabRecFixDRec, rf_data)); + PRINTF("| "); + if (rec_len > 20) + rec_len = 20; + xt_print_bytes(fix_rec->rf_data, rec_len); + } +#endif + + PRINTF("\n"); +} + +#ifdef DEBUG_PRINT +void check_rows(void) +{ + static XTOpenFilePtr of = NULL; + + if (!of) + of = xt_open_file_ns("./test/test_tab-1.xtr", XT_FS_DEFAULT); + if (of) { + size_t size = (size_t) xt_seek_eof_file(NULL, of); + xtWord8 *buffer = (xtWord8 *) xt_malloc_ns(size); + xt_pread_file(of, 0, size, size, buffer, NULL); + for (size_t i=0; i<size/8; i++) { + if (!buffer[i]) + printf("%d is NULL\n", (int) i); + } + } +} + +#endif + +/* ---------------------------------------------------------------------- + * APPLYING CHANGES IN SEQUENCE + */ + +typedef struct XTOperation { + xtOpSeqNo or_op_seq; + xtWord4 or_op_len; + xtLogID or_log_id; + xtLogOffset or_log_offset; +} XTOperationRec, *XTOperationPtr; + +static int xres_cmp_op_seq(struct XTThread *self __attribute__((unused)), register const void *thunk __attribute__((unused)), register const void *a, register const void *b) +{ + xtOpSeqNo lf_op_seq = *((xtOpSeqNo *) a); + XTOperationPtr lf_ptr = (XTOperationPtr) b; + + if (lf_op_seq == lf_ptr->or_op_seq) + return 0; + if (XTTableSeq::xt_op_is_before(lf_op_seq, lf_ptr->or_op_seq)) + return -1; + return 1; +} + +xtPublic void xt_xres_init_tab(XTThreadPtr self, XTTableHPtr tab) +{ + tab->tab_op_list = xt_new_sortedlist(self, sizeof(XTOperationRec), 20, 1000, xres_cmp_op_seq, NULL, NULL, TRUE, FALSE); +} + +xtPublic void xt_xres_exit_tab(XTThreadPtr self, XTTableHPtr tab) +{ + if (tab->tab_op_list) { + xt_free_sortedlist(self, tab->tab_op_list); + tab->tab_op_list = NULL; + } +} + +static xtBool xres_open_table(XTThreadPtr self, XTWriterStatePtr ws, xtTableID tab_id) +{ + XTOpenTablePtr ot; + + if ((ot = ws->ws_ot)) { + if (ot->ot_table->tab_id == tab_id) + return OK; + xt_db_return_table_to_pool(self, ot); + ws->ws_ot = NULL; + } + + if (ws->ws_tab_gone == tab_id) + return FAILED; + if ((ws->ws_ot = xt_db_open_pool_table(self, ws->ws_db, tab_id, NULL, TRUE))) { + XTTableHPtr tab; + + tab = ws->ws_ot->ot_table; + if (!tab->tab_ind_rec_log_id) { + /* Should not happen... */ + tab->tab_ind_rec_log_id = ws->ws_ind_rec_log_id; + tab->tab_ind_rec_log_offset = ws->ws_ind_rec_log_offset; + } + return OK; + } + ws->ws_tab_gone = tab_id; + return FAILED; +} + +/* {INDEX-RECOV_ROWID} + * Add missing index entries during recovery. + * Set the row ID even if the index entry + * is not committed. It will be removed later by + * the sweeper. + */ +static xtBool xres_add_index_entries(XTOpenTablePtr ot, xtRowID row_id, xtRecordID rec_id, xtWord1 *rec_data) +{ + XTTableHPtr tab = ot->ot_table; + u_int idx_cnt; + XTIndexPtr *ind; + //XTIdxSearchKeyRec key; + + if (tab->tab_dic.dic_disable_index) + return OK; + + for (idx_cnt=0, ind=tab->tab_dic.dic_keys; idx_cnt<tab->tab_dic.dic_key_count; idx_cnt++, ind++) { + /* + key.sk_on_key = FALSE; + key.sk_key_value.sv_flags = XT_SEARCH_WHOLE_KEY; + key.sk_key_value.sv_rec_id = rec_offset; + key.sk_key_value.sv_key = key.sk_key_buf; + key.sk_key_value.sv_length = myxt_create_key_from_row(*ind, key.sk_key_buf, rec_data, NULL); + if (!xt_idx_search(ot, *ind, &key)) { + ot->ot_err_index_no = (*ind)->mi_index_no; + return FAILED; + } + if (!key.sk_on_key) { + } + */ + if (!xt_idx_insert(ot, *ind, row_id, rec_id, rec_data, NULL, TRUE)) { + /* Check the error, certain errors are recoverable! */ + XTThreadPtr self = xt_get_self(); + + if (self->t_exception.e_xt_err == XT_SYSTEM_ERROR && + (XT_FILE_IN_USE(self->t_exception.e_sys_err) || + XT_FILE_ACCESS_DENIED(self->t_exception.e_sys_err) || + XT_FILE_TOO_MANY_OPEN(self->t_exception.e_sys_err) || + self->t_exception.e_sys_err == XT_ENOMEM)) { + ot->ot_err_index_no = (*ind)->mi_index_no; + return FAILED; + } + + /* TODO: Write something to the index header to indicate that + * it is corrupted. + */ + tab->tab_dic.dic_disable_index = XT_INDEX_CORRUPTED; + xt_log_and_clear_exception_ns(); + return OK; + } + } + return OK; +} + +static void xres_remove_index_entries(XTOpenTablePtr ot, xtRecordID rec_id, xtWord1 *rec_data) +{ + XTTableHPtr tab = ot->ot_table; + u_int idx_cnt; + XTIndexPtr *ind; + + if (tab->tab_dic.dic_disable_index) + return; + + for (idx_cnt=0, ind=tab->tab_dic.dic_keys; idx_cnt<tab->tab_dic.dic_key_count; idx_cnt++, ind++) { + if (!xt_idx_delete(ot, *ind, rec_id, rec_data)) + xt_log_and_clear_exception_ns(); + } +} + +static xtWord1 *xres_load_record(XTThreadPtr self, XTOpenTablePtr ot, xtRecordID rec_id, xtWord1 *data, size_t red_size, XTInfoBufferPtr rec_buf, u_int cols_req) +{ + XTTableHPtr tab = ot->ot_table; + xtWord1 *rec_data; + + rec_data = ot->ot_row_rbuffer; + + ASSERT(red_size <= ot->ot_row_rbuf_size); + ASSERT(tab->tab_dic.dic_rec_size <= ot->ot_row_rbuf_size); + if (data) { + if (rec_data != data) + memcpy(rec_data, data, red_size); + } + else { + /* It can be that less than 'dic_rec_size' was written for + * variable length type records. + * If this is the last record in the file, then we will read + * less than actual record size. + */ + if (!XT_PREAD_RR_FILE(ot->ot_rec_file, xt_rec_id_to_rec_offset(tab, rec_id), tab->tab_dic.dic_rec_size, 0, rec_data, &red_size, &self->st_statistics.st_rec, self)) + goto failed; + + if (red_size < sizeof(XTTabRecHeadDRec)) + return NULL; + } + + if (XT_REC_IS_FIXED(rec_data[0])) + rec_data = ot->ot_row_rbuffer + XT_REC_FIX_HEADER_SIZE; + else { + if (!xt_ib_alloc(NULL, rec_buf, tab->tab_dic.dic_mysql_buf_size)) + goto failed; + if (XT_REC_IS_VARIABLE(rec_data[0])) { + if (!myxt_load_row(ot, rec_data + XT_REC_FIX_HEADER_SIZE, rec_buf->ib_db.db_data, cols_req)) + goto failed; + } + else if (XT_REC_IS_EXT_DLOG(rec_data[0])) { + if (red_size < XT_REC_EXT_HEADER_SIZE) + return NULL; + + ASSERT(cols_req); + if (cols_req && cols_req <= tab->tab_dic.dic_fix_col_count) { + if (!myxt_load_row(ot, rec_data + XT_REC_EXT_HEADER_SIZE, rec_buf->ib_db.db_data, cols_req)) + goto failed; + } + else { + if (!xt_tab_load_ext_data(ot, rec_id, rec_buf->ib_db.db_data, cols_req)) + goto failed; + } + } + else + /* This is possible, the record has already been cleaned up. */ + return NULL; + rec_data = rec_buf->ib_db.db_data; + } + + return rec_data; + + failed: + /* Running out of memory should not be ignored. */ + if (self->t_exception.e_xt_err == XT_SYSTEM_ERROR && + self->t_exception.e_sys_err == XT_ENOMEM) + xt_throw(self); + xt_log_and_clear_exception_ns(); + return NULL; +} + +/* + * Apply a change from the log. + * + * This function is basically very straight forward, were it not + * for the option to apply operations out of sequence. + * (i.e. in_sequence == FALSE) + * + * If operations are applied in sequence, then they can be + * applied blindly. The update operation is just executed as + * it was logged. + * + * If the changes are not in sequence, then some operation are missing, + * however, the operations that are present are in the correct order. + * + * This can only happen at the end of recovery!!! + * After we have applied all operations in the log we may be + * left with some operations that have not been applied + * because operations were logged out of sequence. + * + * The application of these operations there has to take into + * account the current state of the database. + * They are then applied in a manner that maintains the + * database consistency. + * + * For example, a record that is freed, is free by placing it + * on the current free list. Part of the data logged for the + * operation is ignored. Namely: the "next block" pointer + * that was originally written into the freed record. + */ +static void xres_apply_change(XTThreadPtr self, XTOpenTablePtr ot, XTXactLogBufferDPtr record, xtBool in_sequence, xtBool check_index, XTInfoBufferPtr rec_buf) +{ + XTTableHPtr tab = ot->ot_table; + size_t len; + xtRecordID rec_id; + xtRefID free_ref_id; + XTTabRecFreeDRec free_rec; + xtRowID row_id; + XTTabRowRefDRec row_buf; + XTTabRecHeadDRec rec_head; + size_t tfer; + xtRecordID link_rec_id, prev_link_rec_id; + xtWord1 *rec_data = NULL; + XTTabRecFreeDPtr free_data; + + switch (record->xl.xl_status_1) { + case XT_LOG_ENT_REC_MODIFIED: + case XT_LOG_ENT_UPDATE: + case XT_LOG_ENT_INSERT: + case XT_LOG_ENT_DELETE: + case XT_LOG_ENT_UPDATE_BG: + case XT_LOG_ENT_INSERT_BG: + case XT_LOG_ENT_DELETE_BG: + rec_id = XT_GET_DISK_4(record->xu.xu_rec_id_4); + len = (size_t) XT_GET_DISK_2(record->xu.xu_size_2); + if (!XT_PWRITE_RR_FILE(ot->ot_rec_file, xt_rec_id_to_rec_offset(tab, rec_id), len, (xtWord1 *) &record->xu.xu_rec_type_1, &ot->ot_thread->st_statistics.st_rec, ot->ot_thread)) + xt_throw(self); + tab->tab_bytes_to_flush += len; + + if (check_index && ot->ot_table->tab_dic.dic_key_count) { + switch (record->xl.xl_status_1) { + case XT_LOG_ENT_DELETE: + case XT_LOG_ENT_DELETE_BG: + break; + case XT_LOG_ENT_REC_MODIFIED: + if ((rec_data = xres_load_record(self, ot, rec_id, NULL, 0, rec_buf, tab->tab_dic.dic_ind_cols_req))) + xres_remove_index_entries(ot, rec_id, rec_data); + /* No break required: */ + default: + if ((rec_data = xres_load_record(self, ot, rec_id, &record->xu.xu_rec_type_1, len, rec_buf, tab->tab_dic.dic_ind_cols_req))) { + row_id = XT_GET_DISK_4(record->xu.xu_row_id_4); + if (!xres_add_index_entries(ot, row_id, rec_id, rec_data)) + xt_throw(self); + } + break; + } + } + + if (!in_sequence) { + /* A record has been allocated from the EOF, but out of sequence. + * This could leave a gap where other records were allocated + * from the EOF, but those operations have been lost! + * We compensate for this by adding all blocks between + * to the free list. + */ + free_rec.rf_rec_type_1 = XT_TAB_STATUS_FREED; + free_rec.rf_not_used_1 = 0; + while (tab->tab_head_rec_eof_id < rec_id) { + XT_SET_DISK_4(free_rec.rf_next_rec_id_4, tab->tab_head_rec_free_id); + if (!XT_PWRITE_RR_FILE(ot->ot_rec_file, tab->tab_head_rec_eof_id, sizeof(XTTabRecFreeDRec), (xtWord1 *) &free_rec, &ot->ot_thread->st_statistics.st_rec, ot->ot_thread)) + xt_throw(self); + tab->tab_bytes_to_flush += sizeof(XTTabRecFreeDRec); + tab->tab_head_rec_free_id = tab->tab_head_rec_eof_id; + tab->tab_head_rec_eof_id++; + } + } + if (tab->tab_head_rec_eof_id < rec_id + 1) + tab->tab_head_rec_eof_id = rec_id + 1; + tab->tab_flush_pending = TRUE; + break; + case XT_LOG_ENT_UPDATE_FL: + case XT_LOG_ENT_INSERT_FL: + case XT_LOG_ENT_DELETE_FL: + case XT_LOG_ENT_UPDATE_FL_BG: + case XT_LOG_ENT_INSERT_FL_BG: + case XT_LOG_ENT_DELETE_FL_BG: + rec_id = XT_GET_DISK_4(record->xf.xf_rec_id_4); + len = (size_t) XT_GET_DISK_2(record->xf.xf_size_2); + free_ref_id = XT_GET_DISK_4(record->xf.xf_free_rec_id_4); + + if (check_index && + record->xf.xf_status_1 != XT_LOG_ENT_DELETE_FL && + record->xf.xf_status_1 != XT_LOG_ENT_DELETE_FL_BG) { + if ((rec_data = xres_load_record(self, ot, rec_id, &record->xf.xf_rec_type_1, len, rec_buf, tab->tab_dic.dic_ind_cols_req))) { + row_id = XT_GET_DISK_4(record->xf.xf_row_id_4); + if (!xres_add_index_entries(ot, row_id, rec_id, rec_data)) + xt_throw(self); + } + } + + if (!in_sequence) { + /* This record was allocated from the free list. + * Because this operation is out of sequence, there + * could have been other allocations from the + * free list before this, that have gone missing. + * For this reason we have to search the current + * free list and remove the record. + */ + link_rec_id = tab->tab_head_rec_free_id; + prev_link_rec_id = 0; + while (link_rec_id) { + if (!XT_PREAD_RR_FILE(ot->ot_rec_file, xt_rec_id_to_rec_offset(tab, link_rec_id), sizeof(XTTabRecFreeDRec), sizeof(XTTabRecFreeDRec), (xtWord1 *) &free_rec, NULL, &self->st_statistics.st_rec, self)) + xt_throw(self); + if (link_rec_id == rec_id) + break; + prev_link_rec_id = link_rec_id; + link_rec_id = XT_GET_DISK_4(free_rec.rf_next_rec_id_4); + } + if (link_rec_id == rec_id) { + /* The block was found on the free list. + * remove it: */ + if (prev_link_rec_id) { + /* We write the record from position 'link_rec_id' into + * position 'prev_link_rec_id'. This unlinks 'link_rec_id'! + */ + if (!XT_PWRITE_RR_FILE(ot->ot_rec_file, xt_rec_id_to_rec_offset(tab, prev_link_rec_id), sizeof(XTTabRecFreeDRec), (xtWord1 *) &free_rec, &ot->ot_thread->st_statistics.st_rec, ot->ot_thread)) + xt_throw(self); + tab->tab_bytes_to_flush += sizeof(XTTabRecFreeDRec); + free_ref_id = tab->tab_head_rec_free_id; + } + else + /* The block is at the front of the list: */ + free_ref_id = XT_GET_DISK_4(free_rec.rf_next_rec_id_4); + } + else { + /* Not found on the free list? */ + if (tab->tab_head_rec_eof_id < rec_id + 1) + tab->tab_head_rec_eof_id = rec_id + 1; + goto write_mod_data; + } + } + if (tab->tab_head_rec_eof_id < rec_id + 1) + tab->tab_head_rec_eof_id = rec_id + 1; + tab->tab_head_rec_free_id = free_ref_id; + tab->tab_head_rec_fnum--; + write_mod_data: + if (!XT_PWRITE_RR_FILE(ot->ot_rec_file, xt_rec_id_to_rec_offset(tab, rec_id), len, (xtWord1 *) &record->xf.xf_rec_type_1, &ot->ot_thread->st_statistics.st_rec, ot->ot_thread)) + xt_throw(self); + tab->tab_bytes_to_flush += len; + tab->tab_flush_pending = TRUE; + break; + case XT_LOG_ENT_REC_REMOVED: + case XT_LOG_ENT_REC_REMOVED_EXT: { + xtBool record_loaded; + XTTabRecExtDPtr ext_rec; + size_t red_size; + xtWord4 log_over_size = 0; + xtLogID data_log_id = 0; + xtLogOffset data_log_offset = 0; + u_int cols_required = 0; + + rec_id = XT_GET_DISK_4(record->fr.fr_rec_id_4); + free_data = (XTTabRecFreeDPtr) &record->fr.fr_rec_type_1; + + /* This is a short-cut, it does not require loading the record: */ + if (!check_index && !tab->tab_dic.dic_blob_count && record->fr.fr_status_1 != XT_LOG_ENT_REC_REMOVED_EXT) + goto do_rec_freed; + + ext_rec = (XTTabRecExtDPtr) ot->ot_row_rbuffer; + + if (!XT_PREAD_RR_FILE(ot->ot_rec_file, xt_rec_id_to_rec_offset(tab, rec_id), tab->tab_dic.dic_rec_size, 0, ext_rec, &red_size, &self->st_statistics.st_rec, self)) { + xt_log_and_clear_exception_ns(); + goto do_rec_freed; + } + + if (red_size < sizeof(XTTabRecHeadDRec)) + goto do_rec_freed; + + /* Check that the record is the same as the one originally removed. + * This can be different if recovery is repeated. + * For example: + * + * log=21 offset=6304472 REMOVED-X REC op=360616 tab=7 rec=25874 + * log=21 offset=6309230 UPDATE-FL op=360618 tab=7 rec=25874 row=26667 log=1 offset=26503077 xact=209 + * log=21 offset=6317500 CLEAN REC op=360631 tab=7 rec=25874 + * + * If this recovery sequence is repeated, then the REMOVED-X will free the + * extended record belonging to the update that came afterwards! + * + * Additional situation to consider: + * + * - A record "x" is created, and index entries created. + * - A checkpoint is made done. + * - Record "x" is deleted due to UPDATE. + * - The index entries are removed, but the index is not + * flushed. + * - This deletion is written to disk by the writer. + * So we have the situation that the remove is on disk, + * but the index changes have not been made. + * + * In this case, skipping to "do_rec_freed" is incorrect. + */ + if (record->fr.fr_stat_id_1 != ext_rec->tr_stat_id_1 || + XT_GET_DISK_4(record->fr.fr_xact_id_4) != XT_GET_DISK_4(ext_rec->tr_xact_id_4)) + goto dont_remove_x_record; + + if (record->xl.xl_status_1 == XT_LOG_ENT_REC_REMOVED_EXT) { + if (!XT_REC_IS_EXT_DLOG(ext_rec->tr_rec_type_1)) + goto dont_remove_x_record; + if (red_size < offsetof(XTTabRecExtDRec, re_data)) + goto dont_remove_x_record; + + /* Save this for later (can be overwritten by xres_load_record(): */ + data_log_id = XT_GET_DISK_2(ext_rec->re_log_id_2); + data_log_offset = XT_GET_DISK_6(ext_rec->re_log_offs_6); + log_over_size = XT_GET_DISK_4(ext_rec->re_log_dat_siz_4); + } + dont_remove_x_record: + + record_loaded = FALSE; + + if (check_index) { + cols_required = tab->tab_dic.dic_ind_cols_req; + if (tab->tab_dic.dic_blob_cols_req > cols_required) + cols_required = tab->tab_dic.dic_blob_cols_req; + if (!(rec_data = xres_load_record(self, ot, rec_id, ot->ot_row_rbuffer, red_size, rec_buf, cols_required))) + goto do_rec_freed; + record_loaded = TRUE; + xres_remove_index_entries(ot, rec_id, rec_data); + } + + if (tab->tab_dic.dic_blob_count) { + if (!record_loaded) { + if (tab->tab_dic.dic_blob_cols_req > cols_required) + cols_required = tab->tab_dic.dic_blob_cols_req; + if (!(rec_data = xres_load_record(self, ot, rec_id, ot->ot_row_rbuffer, red_size, rec_buf, cols_required))) + /* [(7)] REMOVE is followed by FREE: + goto get_rec_offset; + */ + goto do_rec_freed; + record_loaded = TRUE; + } +#ifdef XT_STREAMING + myxt_release_blobs(ot, rec_data, rec_id); +#endif + } + + if (record->xl.xl_status_1 == XT_LOG_ENT_REC_REMOVED_EXT) { + /* Note: dlb_delete_log() may be repeated, but should handle this: + * + * Example: + * log=5 offset=213334 CLEAN REC op=28175 tab=1 rec=317428 + * ... + * log=6 offset=321063 REMOVED-X REC op=33878 tab=1 rec=317428 + * + * When this sequence is repeated during recovery, then CLEAN REC + * will reset the status byte of the record so that it + * comes back to here! + * + * The check for zero is probably not required here. + */ + if (data_log_id && data_log_offset && log_over_size) { + if (!ot->ot_thread->st_dlog_buf.dlb_delete_log(data_log_id, data_log_offset, log_over_size, tab->tab_id, rec_id, self)) { + if (ot->ot_thread->t_exception.e_xt_err != XT_ERR_BAD_EXT_RECORD && + ot->ot_thread->t_exception.e_xt_err != XT_ERR_DATA_LOG_NOT_FOUND) + xt_log_and_clear_exception_ns(); + } + } + } + + goto do_rec_freed; + } + case XT_LOG_ENT_REC_REMOVED_BI: { + /* + * For deletion we need the complete before image because of the following problem. + * + * DROP TABLE IF EXISTS t1; + * CREATE TABLE t1 (ID int primary key auto_increment, value int, index (value)) engine=pbxt; + * + * insert t1(value) values(50); + * + * -- CHECKPOINT -- + * + * update t1 set value = 60; + * + * -- PAUSE -- + * + * update t1 set value = 70; + * + * -- CRASH -- + * + * select value from t1; + * select * from t1; + * + * 081203 12:11:46 [Note] PBXT: Recovering from 1-148, bytes to read: 33554284 + * log=1 offset=148 UPDATE-BG op=5 tab=1 rec=2 row=1 xact=3 + * log=1 offset=188 REC ADD ROW op=6 tab=1 row=1 + * log=1 offset=206 COMMIT xact=3 + * log=1 offset=216 REMOVED REC op=7 tab=1 rec=1 xact=2 + * log=1 offset=241 CLEAN REC op=8 tab=1 rec=2 + * log=1 offset=261 CLEANUP xact=3 + * log=1 offset=267 UPDATE-FL-BG op=9 tab=1 rec=1 row=1 xact=4 + * log=1 offset=311 REC ADD ROW op=10 tab=1 row=1 + * log=1 offset=329 COMMIT xact=4 + * log=1 offset=339 REMOVED REC op=11 tab=1 rec=2 xact=3 + * log=1 offset=364 CLEAN REC op=12 tab=1 rec=1 + * log=1 offset=384 CLEANUP xact=4 + * 081203 12:12:15 [Note] PBXT: Recovering complete at 1-390, bytes read: 33554284 + * + * mysql> select value from t1; + * +-------+ + * | value | + * +-------+ + * | 50 | + * | 70 | + * +-------+ + * 2 rows in set (55.99 sec) + * + * mysql> select * from t1; + * +----+-------+ + * | ID | value | + * +----+-------+ + * | 1 | 70 | + * +----+-------+ + * 1 row in set (0.00 sec) + */ + XTTabRecExtDPtr ext_rec; + xtWord4 log_over_size = 0; + xtLogID data_log_id = 0; + xtLogOffset data_log_offset = 0; + u_int cols_required = 0; + xtBool record_loaded; + size_t rec_size; + + rec_id = XT_GET_DISK_4(record->rb.rb_rec_id_4); + rec_size = XT_GET_DISK_2(record->rb.rb_size_2); + + ext_rec = (XTTabRecExtDPtr) &record->rb.rb_rec_type_1; + + if (XT_REC_IS_EXT_DLOG(record->rb.rb_rec_type_1)) { + /* Save this for later (can be overwritten by xres_load_record(): */ + data_log_id = XT_GET_DISK_2(ext_rec->re_log_id_2); + data_log_offset = XT_GET_DISK_6(ext_rec->re_log_offs_6); + log_over_size = XT_GET_DISK_4(ext_rec->re_log_dat_siz_4); + } + + record_loaded = FALSE; + + if (check_index) { + cols_required = tab->tab_dic.dic_ind_cols_req; +#ifdef XT_STREAMING + if (tab->tab_dic.dic_blob_cols_req > cols_required) + cols_required = tab->tab_dic.dic_blob_cols_req; +#endif + if (!(rec_data = xres_load_record(self, ot, rec_id, &record->rb.rb_rec_type_1, rec_size, rec_buf, cols_required))) + goto go_on_to_free; + record_loaded = TRUE; + xres_remove_index_entries(ot, rec_id, rec_data); + } + +#ifdef XT_STREAMING + if (tab->tab_dic.dic_blob_count) { + if (!record_loaded) { + cols_required = tab->tab_dic.dic_blob_cols_req; + if (!(rec_data = xres_load_record(self, ot, rec_id, &record->rb.rb_rec_type_1, rec_size, rec_buf, cols_required))) + /* [(7)] REMOVE is followed by FREE: + goto get_rec_offset; + */ + goto go_on_to_free; + record_loaded = TRUE; + } + myxt_release_blobs(ot, rec_data, rec_id); + } +#endif + + if (data_log_id && data_log_offset && log_over_size) { + if (!ot->ot_thread->st_dlog_buf.dlb_delete_log(data_log_id, data_log_offset, log_over_size, tab->tab_id, rec_id, self)) { + if (ot->ot_thread->t_exception.e_xt_err != XT_ERR_BAD_EXT_RECORD && + ot->ot_thread->t_exception.e_xt_err != XT_ERR_DATA_LOG_NOT_FOUND) + xt_log_and_clear_exception_ns(); + } + } + + go_on_to_free: + /* Use the new record type: */ + record->rb.rb_rec_type_1 = record->rb.rb_new_rec_type_1; + free_data = (XTTabRecFreeDPtr) &record->rb.rb_rec_type_1; + goto do_rec_freed; + } + case XT_LOG_ENT_REC_FREED: + rec_id = XT_GET_DISK_4(record->fr.fr_rec_id_4); + free_data = (XTTabRecFreeDPtr) &record->fr.fr_rec_type_1; + do_rec_freed: + if (!in_sequence) { + size_t red_size; + + /* Free the record. + * We place the record on front of the current + * free list. + * + * However, before we do this, we remove the record + * from its row list, if the record is on a row list. + * + * We do this here, because in the normal removal + * from the row list uses the operations: + * + * XT_LOG_ENT_REC_UNLINKED, XT_LOG_ENT_ROW_SET and + * XT_LOG_ENT_ROW_FREED. + * + * When operations are performed out of sequence, + * these operations are ignored for the purpose + * of removing the record from the row. + */ + if (!XT_PREAD_RR_FILE(ot->ot_rec_file, xt_rec_id_to_rec_offset(tab, rec_id), sizeof(XTTabRecHeadDRec), sizeof(XTTabRecHeadDRec), (xtWord1 *) &rec_head, NULL, &self->st_statistics.st_rec, self)) + xt_throw(self); + /* The record is already free: */ + if (XT_REC_IS_FREE(rec_head.tr_rec_type_1)) + goto free_done; + row_id = XT_GET_DISK_4(rec_head.tr_row_id_4); + + /* Search the row for this record: */ + if (!XT_PREAD_RR_FILE(ot->ot_row_file, xt_row_id_to_row_offset(tab, row_id), sizeof(XTTabRowRefDRec), sizeof(XTTabRowRefDRec), (xtWord1 *) &row_buf, NULL, &self->st_statistics.st_rec, self)) + xt_throw(self); + link_rec_id = XT_GET_DISK_4(row_buf.rr_ref_id_4); + prev_link_rec_id = 0; + while (link_rec_id) { + if (!XT_PREAD_RR_FILE(ot->ot_rec_file, xt_rec_id_to_rec_offset(tab, link_rec_id), sizeof(XTTabRecHeadDRec), 0, (xtWord1 *) &rec_head, &red_size, &self->st_statistics.st_rec, self)) { + xt_log_and_clear_exception(self); + break; + } + if (red_size < sizeof(XTTabRecHeadDRec)) + break; + if (link_rec_id == rec_id) + break; + if (XT_GET_DISK_4(rec_head.tr_row_id_4) != row_id) + break; + switch (rec_head.tr_rec_type_1 & XT_TAB_STATUS_MASK) { + case XT_TAB_STATUS_FREED: + break; + case XT_TAB_STATUS_DELETE: + case XT_TAB_STATUS_FIXED: + case XT_TAB_STATUS_VARIABLE: + case XT_TAB_STATUS_EXT_DLOG: + break; + default: + ASSERT(FALSE); + goto exit_loop; + } + if (rec_head.tr_rec_type_1 & ~(XT_TAB_STATUS_CLEANED_BIT | XT_TAB_STATUS_MASK)) { + ASSERT(FALSE); + break; + } + prev_link_rec_id = link_rec_id; + link_rec_id = XT_GET_DISK_4(rec_head.tr_prev_rec_id_4); + } + + exit_loop: + if (link_rec_id == rec_id) { + /* The record was found on the row list, remove it: */ + if (prev_link_rec_id) { + /* We write the previous variation pointer from position 'link_rec_id' into + * variation pointer of the 'prev_link_rec_id' record. This unlinks 'link_rec_id'! + */ + if (!XT_PWRITE_RR_FILE(ot->ot_rec_file, xt_rec_id_to_rec_offset(tab, prev_link_rec_id) + offsetof(XTTabRecHeadDRec, tr_prev_rec_id_4), XT_RECORD_ID_SIZE, (xtWord1 *) &rec_head.tr_prev_rec_id_4, &ot->ot_thread->st_statistics.st_rec, ot->ot_thread)) + xt_throw(self); + tab->tab_bytes_to_flush += XT_RECORD_ID_SIZE; + } + else { + /* The record is at the front of the row list: */ + xtRefID ref_id = XT_GET_DISK_4(rec_head.tr_prev_rec_id_4); + XT_SET_DISK_4(row_buf.rr_ref_id_4, ref_id); + if (!XT_PWRITE_RR_FILE(ot->ot_row_file, xt_row_id_to_row_offset(tab, row_id), sizeof(XTTabRowRefDRec), (xtWord1 *) &row_buf, &ot->ot_thread->st_statistics.st_rec, ot->ot_thread)) + xt_throw(self); + tab->tab_bytes_to_flush += sizeof(XTTabRowRefDRec); + } + } + + /* Now we free the record, by placing it at the front of + * the free list: + */ + XT_SET_DISK_4(free_data->rf_next_rec_id_4, tab->tab_head_rec_free_id); + } + tab->tab_head_rec_free_id = rec_id; + tab->tab_head_rec_fnum++; + if (!XT_PWRITE_RR_FILE(ot->ot_rec_file, xt_rec_id_to_rec_offset(tab, rec_id), sizeof(XTTabRecFreeDRec), (xtWord1 *) free_data, &ot->ot_thread->st_statistics.st_rec, ot->ot_thread)) + xt_throw(self); + tab->tab_bytes_to_flush += sizeof(XTTabRecFreeDRec); + tab->tab_flush_pending = TRUE; + free_done: + break; + case XT_LOG_ENT_REC_MOVED: + len = 8; + rec_id = XT_GET_DISK_4(record->xw.xw_rec_id_4); + if (!XT_PWRITE_RR_FILE(ot->ot_rec_file, xt_rec_id_to_rec_offset(tab, rec_id) + offsetof(XTTabRecExtDRec, re_log_id_2), len, (xtWord1 *) &record->xw.xw_rec_type_1, &ot->ot_thread->st_statistics.st_rec, ot->ot_thread)) + xt_throw(self); + tab->tab_bytes_to_flush += len; + tab->tab_flush_pending = TRUE; + break; + case XT_LOG_ENT_REC_CLEANED: + len = offsetof(XTTabRecHeadDRec, tr_prev_rec_id_4) + XT_RECORD_ID_SIZE; + goto get_rec_offset; + case XT_LOG_ENT_REC_CLEANED_1: + len = 1; + goto get_rec_offset; + case XT_LOG_ENT_REC_UNLINKED: + if (!in_sequence) { + /* Unlink the record. + * This is done when the record is freed. + */ + break; + } + len = offsetof(XTTabRecHeadDRec, tr_prev_rec_id_4) + XT_RECORD_ID_SIZE; + get_rec_offset: + rec_id = XT_GET_DISK_4(record->xw.xw_rec_id_4); + if (!XT_PWRITE_RR_FILE(ot->ot_rec_file, xt_rec_id_to_rec_offset(tab, rec_id), len, (xtWord1 *) &record->xw.xw_rec_type_1, &ot->ot_thread->st_statistics.st_rec, ot->ot_thread)) + xt_throw(self); + tab->tab_bytes_to_flush += len; + tab->tab_flush_pending = TRUE; + break; + case XT_LOG_ENT_ROW_NEW: + len = offsetof(XTactRowAddedEntryDRec, xa_free_list_4); + row_id = XT_GET_DISK_4(record->xa.xa_row_id_4); + if (!in_sequence) { + /* A row was allocated from the EOF. Because operations are missing. + * The blocks between the current EOF and the new EOF need to be + * place on the free list! + */ + while (tab->tab_head_row_eof_id < row_id) { + XT_SET_DISK_4(row_buf.rr_ref_id_4, tab->tab_head_row_free_id); + if (!XT_PWRITE_RR_FILE(ot->ot_row_file, xt_row_id_to_row_offset(tab, tab->tab_head_row_eof_id), sizeof(XTTabRowRefDRec), (xtWord1 *) &row_buf, &ot->ot_thread->st_statistics.st_rec, ot->ot_thread)) + xt_throw(self); + tab->tab_bytes_to_flush += sizeof(XTTabRowRefDRec); + tab->tab_head_row_free_id = tab->tab_head_row_eof_id; + tab->tab_head_row_eof_id++; + } + } + if (tab->tab_head_row_eof_id < row_id + 1) + tab->tab_head_row_eof_id = row_id + 1; + tab->tab_flush_pending = TRUE; + break; + case XT_LOG_ENT_ROW_NEW_FL: + len = sizeof(XTactRowAddedEntryDRec); + row_id = XT_GET_DISK_4(record->xa.xa_row_id_4); + free_ref_id = XT_GET_DISK_4(record->xa.xa_free_list_4); + if (!in_sequence) { + size_t red_size; + /* The record was taken from the free list. + * If the operations were in sequence, then this would be + * the front of the free list now. + * However, because operations are missing, it may no + * longer be the front of the free list! + * Search and remove: + */ + link_rec_id = tab->tab_head_row_free_id; + prev_link_rec_id = 0; + while (link_rec_id) { + if (!XT_PREAD_RR_FILE(ot->ot_row_file, xt_row_id_to_row_offset(tab, link_rec_id), sizeof(XTTabRowRefDRec), 0, (xtWord1 *) &row_buf, &red_size, &self->st_statistics.st_rec, self)) { + xt_log_and_clear_exception(self); + break; + } + if (red_size < sizeof(XTTabRowRefDRec)) + break; + if (link_rec_id == row_id) + break; + prev_link_rec_id = link_rec_id; + link_rec_id = XT_GET_DISK_4(row_buf.rr_ref_id_4); + } + if (link_rec_id == row_id) { + /* The block was found on the free list, remove it: */ + if (prev_link_rec_id) { + /* We write the record from position 'link_rec_id' into + * position 'prev_link_rec_id'. This unlinks 'link_rec_id'! + */ + if (!XT_PWRITE_RR_FILE(ot->ot_row_file, xt_row_id_to_row_offset(tab, prev_link_rec_id), sizeof(XTTabRowRefDRec), (xtWord1 *) &row_buf, &ot->ot_thread->st_statistics.st_rec, ot->ot_thread)) + xt_throw(self); + tab->tab_bytes_to_flush += sizeof(XTTabRowRefDRec); + free_ref_id = tab->tab_head_row_free_id; + } + else + /* The block is at the front of the free list: */ + free_ref_id = XT_GET_DISK_4(row_buf.rr_ref_id_4); + } + else { + /* Not found? */ + if (tab->tab_head_row_eof_id < row_id + 1) + tab->tab_head_row_eof_id = row_id + 1; + break; + } + + } + if (tab->tab_head_row_eof_id < row_id + 1) + tab->tab_head_row_eof_id = row_id + 1; + tab->tab_head_row_free_id = free_ref_id; + tab->tab_head_row_fnum--; + tab->tab_flush_pending = TRUE; + break; + case XT_LOG_ENT_ROW_FREED: + row_id = XT_GET_DISK_4(record->wr.wr_row_id_4); + if (!in_sequence) { + /* Free the row. + * Since this operation is being performed out of sequence, we + * must assume that some other free and allocation operations + * must be missing. + * For this reason, we add the row to the front of the + * existing free list. + */ + XT_SET_DISK_4(record->wr.wr_ref_id_4, tab->tab_head_row_free_id); + } + tab->tab_head_row_free_id = row_id; + tab->tab_head_row_fnum++; + goto write_row_data; + case XT_LOG_ENT_ROW_ADD_REC: + row_id = XT_GET_DISK_4(record->wr.wr_row_id_4); + if (!in_sequence) { + if (!XT_PREAD_RR_FILE(ot->ot_row_file, xt_row_id_to_row_offset(tab, row_id), sizeof(XTTabRowRefDRec), 0, (xtWord1 *) &row_buf, &tfer, &self->st_statistics.st_rec, self)) + xt_throw(self); + if (tfer == sizeof(XTTabRowRefDRec)) { + /* Add a record to the front of the row. + * This is easy, but we have to make sure that the next + * pointer in the record is correct. + */ + rec_id = XT_GET_DISK_4(record->wr.wr_ref_id_4); + if (!XT_PREAD_RR_FILE(ot->ot_rec_file, xt_rec_id_to_rec_offset(tab, rec_id), sizeof(XTTabRecHeadDRec), 0, (xtWord1 *) &rec_head, &tfer, &self->st_statistics.st_rec, self)) + xt_throw(self); + if (tfer == sizeof(XTTabRecHeadDRec) && XT_GET_DISK_4(rec_head.tr_row_id_4) == row_id) { + /* This is now the correct next pointer: */ + xtRecordID next_ref_id = XT_GET_DISK_4(row_buf.rr_ref_id_4); + if (XT_GET_DISK_4(rec_head.tr_prev_rec_id_4) != next_ref_id && + rec_id != next_ref_id) { + XT_SET_DISK_4(rec_head.tr_prev_rec_id_4, next_ref_id); + if (!XT_PWRITE_RR_FILE(ot->ot_rec_file, xt_rec_id_to_rec_offset(tab, rec_id), sizeof(XTTabRecHeadDRec), (xtWord1 *) &rec_head, &ot->ot_thread->st_statistics.st_rec, ot->ot_thread)) + xt_throw(self); + tab->tab_bytes_to_flush += sizeof(XTTabRecHeadDRec); + } + } + } + + } + goto write_row_data; + case XT_LOG_ENT_ROW_SET: + if (!in_sequence) + /* This operation is ignored when out of sequence! + * The operation is used to remove a record from a row. + * This is done automatically when the record is freed. + */ + break; + row_id = XT_GET_DISK_4(record->wr.wr_row_id_4); + write_row_data: + ASSERT_NS(XT_GET_DISK_4(record->wr.wr_ref_id_4) < tab->tab_head_rec_eof_id); + if (!XT_PWRITE_RR_FILE(ot->ot_row_file, xt_row_id_to_row_offset(tab, row_id), sizeof(XTTabRowRefDRec), (xtWord1 *) &record->wr.wr_ref_id_4, &ot->ot_thread->st_statistics.st_rec, self)) + xt_throw(self); + tab->tab_bytes_to_flush += sizeof(XTTabRowRefDRec); + if (tab->tab_head_row_eof_id < row_id + 1) + tab->tab_head_row_eof_id = row_id + 1; + tab->tab_flush_pending = TRUE; + break; + case XT_LOG_ENT_NO_OP: + case XT_LOG_ENT_END_OF_LOG: + break; + } +} + +/* + * Apply all operations that have been buffered + * for a particular table. + * Operations are buffered if they are + * read from the log out of sequence. + * + * In this case we buffer, and wait for the + * out of sequence operations to arrive. + * + * When the server is running, this will always be + * the case. A delay occurs while a transaction + * fills its private log buffer. + */ +static void xres_apply_operations(XTThreadPtr self, XTWriterStatePtr ws, xtBool in_sequence) +{ + XTTableHPtr tab = ws->ws_ot->ot_table; + u_int i = 0; + XTOperationPtr op; + xtBool check_index; + +// XTDatabaseHPtr db, XTOpenTablePtr ot, XTXactSeqReadPtr sr, XTDataBufferPtr databuf + xt_sl_lock(self, tab->tab_op_list); + for (;;) { + op = (XTOperationPtr) xt_sl_item_at(tab->tab_op_list, i); + if (!op) + break; + if (in_sequence && tab->tab_head_op_seq+1 != op->or_op_seq) + break; + xt_db_set_size(self, &ws->ws_databuf, (size_t) op->or_op_len); + if (!ws->ws_db->db_xlog.xlog_rnd_read(&ws->ws_seqread, op->or_log_id, op->or_log_offset, (size_t) op->or_op_len, ws->ws_databuf.db_data, NULL, self)) + xt_throw(self); + check_index = ws->ws_in_recover && xt_comp_log_pos(op->or_log_id, op->or_log_offset, ws->ws_ind_rec_log_id, ws->ws_ind_rec_log_offset) >= 0; + xres_apply_change(self, ws->ws_ot, (XTXactLogBufferDPtr) ws->ws_databuf.db_data, in_sequence, check_index, &ws->ws_rec_buf); + tab->tab_head_op_seq = op->or_op_seq; + if (tab->tab_wr_wake_freeer) { + if (!XTTableSeq::xt_op_is_before(tab->tab_head_op_seq, tab->tab_wake_freeer_op)) + xt_wr_wake_freeer(self); + } + i++; + } + xt_sl_remove_from_front(self, tab->tab_op_list, i); + xt_sl_unlock(self, tab->tab_op_list); +} + +/* Check for operations still remaining on tables. + * These operations are applied even though operations + * in sequence are missing. + */ +xtBool xres_sync_operations(XTThreadPtr self, XTDatabaseHPtr db, XTWriterStatePtr ws) +{ + u_int edx; + XTTableEntryPtr te_ptr; + XTTableHPtr tab; + xtBool op_synced = FALSE; + + xt_enum_tables_init(&edx); + while ((te_ptr = xt_enum_tables_next(self, db, &edx))) { + /* Dirty read of tab_op_list OK, here because this is the + * only thread that updates the list! + */ + if ((tab = te_ptr->te_table)) { + if (xt_sl_get_size(tab->tab_op_list)) { + op_synced = TRUE; + if (xres_open_table(self, ws, te_ptr->te_tab_id)) + xres_apply_operations(self, ws, FALSE); + } + + /* Update the pointer cache: */ + tab->tab_seq.xt_op_seq_set(self, tab->tab_head_op_seq+1); + tab->tab_row_eof_id = tab->tab_head_row_eof_id; + tab->tab_row_free_id = tab->tab_head_row_free_id; + tab->tab_row_fnum = tab->tab_head_row_fnum; + tab->tab_rec_eof_id = tab->tab_head_rec_eof_id; + tab->tab_rec_free_id = tab->tab_head_rec_free_id; + tab->tab_rec_fnum = tab->tab_head_rec_fnum; + } + } + return op_synced; +} + +/* + * Operations from the log are applied in sequence order. + * If the operations are out of sequence, they are buffered + * until the missing operations appear. + * + * NOTE: No lock is required because there should only be + * one thread that does this! + */ +xtPublic void xt_xres_apply_in_order(XTThreadPtr self, XTWriterStatePtr ws, xtLogID log_id, xtLogOffset log_offset, XTXactLogBufferDPtr record) +{ + xtOpSeqNo op_seq; + xtTableID tab_id; + size_t len; + xtBool check_index; + +// XTDatabaseHPtr db, XTOpenTablePtr *ot, XTXactSeqReadPtr sr, XTDataBufferPtr databuf + switch (record->xl.xl_status_1) { + case XT_LOG_ENT_REC_MODIFIED: + case XT_LOG_ENT_UPDATE: + case XT_LOG_ENT_INSERT: + case XT_LOG_ENT_DELETE: + case XT_LOG_ENT_UPDATE_BG: + case XT_LOG_ENT_INSERT_BG: + case XT_LOG_ENT_DELETE_BG: + len = offsetof(XTactUpdateEntryDRec, xu_rec_type_1) + (size_t) XT_GET_DISK_2(record->xu.xu_size_2); + op_seq = XT_GET_DISK_4(record->xu.xu_op_seq_4); + tab_id = XT_GET_DISK_4(record->xu.xu_tab_id_4); + break; + case XT_LOG_ENT_UPDATE_FL: + case XT_LOG_ENT_INSERT_FL: + case XT_LOG_ENT_DELETE_FL: + case XT_LOG_ENT_UPDATE_FL_BG: + case XT_LOG_ENT_INSERT_FL_BG: + case XT_LOG_ENT_DELETE_FL_BG: + len = offsetof(XTactUpdateFLEntryDRec, xf_rec_type_1) + (size_t) XT_GET_DISK_2(record->xf.xf_size_2); + op_seq = XT_GET_DISK_4(record->xf.xf_op_seq_4); + tab_id = XT_GET_DISK_4(record->xf.xf_tab_id_4); + break; + case XT_LOG_ENT_REC_FREED: + case XT_LOG_ENT_REC_REMOVED: + case XT_LOG_ENT_REC_REMOVED_EXT: + /* [(7)] REMOVE is now a extended version of FREE! */ + len = offsetof(XTactFreeRecEntryDRec, fr_rec_type_1) + sizeof(XTTabRecFreeDRec); + goto fixed_len_data; + case XT_LOG_ENT_REC_REMOVED_BI: + len = offsetof(XTactRemoveBIEntryDRec, rb_rec_type_1) + (size_t) XT_GET_DISK_2(record->rb.rb_size_2); + op_seq = XT_GET_DISK_4(record->rb.rb_op_seq_4); + tab_id = XT_GET_DISK_4(record->rb.rb_tab_id_4); + break; + case XT_LOG_ENT_REC_MOVED: + len = offsetof(XTactWriteRecEntryDRec, xw_rec_type_1) + 8; + goto fixed_len_data; + case XT_LOG_ENT_REC_CLEANED: + len = offsetof(XTactWriteRecEntryDRec, xw_rec_type_1) + offsetof(XTTabRecHeadDRec, tr_prev_rec_id_4) + XT_RECORD_ID_SIZE; + goto fixed_len_data; + case XT_LOG_ENT_REC_CLEANED_1: + len = offsetof(XTactWriteRecEntryDRec, xw_rec_type_1) + 1; + goto fixed_len_data; + case XT_LOG_ENT_REC_UNLINKED: + len = offsetof(XTactWriteRecEntryDRec, xw_rec_type_1) + offsetof(XTTabRecHeadDRec, tr_prev_rec_id_4) + XT_RECORD_ID_SIZE; + fixed_len_data: + op_seq = XT_GET_DISK_4(record->xw.xw_op_seq_4); + tab_id = XT_GET_DISK_4(record->xw.xw_tab_id_4); + break; + case XT_LOG_ENT_ROW_NEW: + len = sizeof(XTactRowAddedEntryDRec) - 4; + goto new_row; + case XT_LOG_ENT_ROW_NEW_FL: + len = sizeof(XTactRowAddedEntryDRec); + new_row: + op_seq = XT_GET_DISK_4(record->xa.xa_op_seq_4); + tab_id = XT_GET_DISK_4(record->xa.xa_tab_id_4); + break; + case XT_LOG_ENT_ROW_ADD_REC: + case XT_LOG_ENT_ROW_SET: + case XT_LOG_ENT_ROW_FREED: + len = offsetof(XTactWriteRowEntryDRec, wr_ref_id_4) + sizeof(XTTabRowRefDRec); + op_seq = XT_GET_DISK_4(record->wr.wr_op_seq_4); + tab_id = XT_GET_DISK_4(record->wr.wr_tab_id_4); + break; + case XT_LOG_ENT_NO_OP: + case XT_LOG_ENT_END_OF_LOG: + return; + default: + return; + } + + if (!xres_open_table(self, ws, tab_id)) + return; + + XTTableHPtr tab = ws->ws_ot->ot_table; + + /* NOTE: + * + * During normal operation this is actually given. + * + * During recovery, it only applies to the record/row files + * The index file is flushed indepently, and changes may + * have been applied to the index (due to a call to flush index, + * which comes as a result of out of memory) that have not been + * applied to the record/row files. + * + * As a result we need to do the index checks that apply to this + * change. + * + * At the moment, I will just do everything, which should not + * hurt! + * + * This error can be repeated by running the test + * runTest(OUT_OF_CACHE_UPDATE_TEST, 32, OUT_OF_CACHE_UPDATE_TEST_UPDATE_COUNT, OUT_OF_CACHE_UPDATE_TEST_SET_SIZE) + * and crashing after a while. + * + * Do this by setting not_this to NULL. This will cause the test to + * hang after a while. After a restart the indexes are corrupt if the + * ws->ws_in_recover condition is not present here. + */ + if (ws->ws_in_recover) { + if (!tab->tab_recovery_done) { + /* op_seq <= tab_head_op_seq + 1: */ + ASSERT(XTTableSeq::xt_op_is_before(op_seq, tab->tab_head_op_seq+2)); + if (XTTableSeq::xt_op_is_before(op_seq-1, tab->tab_head_op_seq)) + /* Adjust the operation sequence number: */ + tab->tab_head_op_seq = op_seq-1; + tab->tab_recovery_done = TRUE; + } + } + + if (!XTTableSeq::xt_op_is_before(tab->tab_head_op_seq, op_seq)) + return; + + if (tab->tab_head_op_seq+1 == op_seq) { + /* I could use tab_ind_rec_log_id, but this may be a problem, if + * recovery does not recover up to the last committed transaction. + */ + check_index = ws->ws_in_recover && xt_comp_log_pos(log_id, log_offset, ws->ws_ind_rec_log_id, ws->ws_ind_rec_log_offset) >= 0; + xres_apply_change(self, ws->ws_ot, record, TRUE, check_index, &ws->ws_rec_buf); + tab->tab_head_op_seq = op_seq; + if (tab->tab_wr_wake_freeer) { + if (!XTTableSeq::xt_op_is_before(tab->tab_head_op_seq, tab->tab_wake_freeer_op)) + xt_wr_wake_freeer(self); + } + + /* Apply any operations in the list that now follow on... + * NOTE: the tab_op_list only has be locked for modification. + * This is because only one thread ever changes the list + * (on startup and the writer), but the checkpoint thread + * reads it. + */ + XTOperationPtr op; + if ((op = (XTOperationPtr) xt_sl_first_item(tab->tab_op_list))) { + if (tab->tab_head_op_seq+1 == op->or_op_seq) { + xres_apply_operations(self, ws, TRUE); + } + } + } + else { + /* Add the operation to the list: */ + XTOperationRec op; + + op.or_op_seq = op_seq; + op.or_op_len = len; + op.or_log_id = log_id; + op.or_log_offset = log_offset; + xt_sl_lock(self, tab->tab_op_list); + xt_sl_insert(self, tab->tab_op_list, &op_seq, &op); + ASSERT(tab->tab_op_list->sl_usage_count < 1000000); + xt_sl_unlock(self, tab->tab_op_list); + } +} + +/* ---------------------------------------------------------------------- + * CHECKPOINTING FUNCTIONALITY + */ + +static xtBool xres_delete_data_log(XTDatabaseHPtr db, xtLogID log_id) +{ + XTDataLogFilePtr data_log; + char path[PATH_MAX]; + + db->db_datalogs.dlc_name(PATH_MAX, path, log_id); + + if (!db->db_datalogs.dlc_remove_data_log(log_id, TRUE)) + return FAILED; + + if (xt_fs_exists(path)) { +#ifdef DEBUG_LOG_DELETE + printf("-- delete log: %s\n", path); +#endif + if (!xt_fs_delete(NULL, path)) + return FAILED; + } + /* The log was deleted: */ + if (!db->db_datalogs.dlc_get_data_log(&data_log, log_id, TRUE, NULL)) + return FAILED; + if (data_log) { + if (!db->db_datalogs.dls_set_log_state(data_log, XT_DL_DELETED)) + return FAILED; + } + return OK; +} + +static int xres_comp_flush_tabs(XTThreadPtr self __attribute__((unused)), register const void *thunk __attribute__((unused)), register const void *a, register const void *b) +{ + xtTableID tab_id = *((xtTableID *) a); + XTCheckPointTablePtr cp_tab = (XTCheckPointTablePtr) b; + + if (tab_id < cp_tab->cpt_tab_id) + return -1; + if (tab_id > cp_tab->cpt_tab_id) + return 1; + return 0; +} + +static void xres_init_checkpoint_state(XTThreadPtr self, XTCheckPointStatePtr cp) +{ + xt_init_mutex_with_autoname(self, &cp->cp_state_lock); +} + +static void xres_free_checkpoint_state(XTThreadPtr self, XTCheckPointStatePtr cp) +{ + xt_free_mutex(&cp->cp_state_lock); + if (cp->cp_table_ids) { + xt_free_sortedlist(self, cp->cp_table_ids); + cp->cp_table_ids = NULL; + } +} + +/* + * Remove the deleted logs so that they can be re-used. + * This is only possible after a checkpoint has been + * written that does _not_ include these logs as logs + * to be deleted! + */ +static xtBool xres_remove_data_logs(XTDatabaseHPtr db) +{ + u_int no_of_logs = xt_sl_get_size(db->db_datalogs.dlc_deleted); + xtLogID *log_id_ptr; + + for (u_int i=0; i<no_of_logs; i++) { + log_id_ptr = (xtLogID *) xt_sl_item_at(db->db_datalogs.dlc_deleted, i); + if (!db->db_datalogs.dlc_remove_data_log(*log_id_ptr, FALSE)) + return FAILED; + } + xt_sl_set_size(db->db_datalogs.dlc_deleted, 0); + return OK; +} + +/* ---------------------------------------------------------------------- + * INIT & EXIT + */ + +xtPublic void xt_xres_init(XTThreadPtr self, XTDatabaseHPtr db) +{ + xtLogID max_log_id; + + xt_init_mutex_with_autoname(self, &db->db_cp_lock); + xt_init_cond(self, &db->db_cp_cond); + + xres_init_checkpoint_state(self, &db->db_cp_state); + db->db_restart.xres_init(self, db, &db->db_wr_log_id, &db->db_wr_log_offset, &max_log_id); + + /* It is also the position where transactions will start writing the + * log: + */ + if (!db->db_xlog.xlog_set_write_offset(db->db_wr_log_id, db->db_wr_log_offset, max_log_id, self)) + xt_throw(self); +} + +xtPublic void xt_xres_exit(XTThreadPtr self, XTDatabaseHPtr db) +{ + db->db_restart.xres_exit(self); + xres_free_checkpoint_state(self, &db->db_cp_state); + xt_free_mutex(&db->db_cp_lock); + xt_free_cond(&db->db_cp_cond); +} + +/* ---------------------------------------------------------------------- + * RESTART FUNCTIONALITY + */ + +/* + * Restart the database. This function loads the restart position, and + * applies all changes in the logs, until the end of the log, or + * a corrupted record is found. + * + * The restart position is the position in the log where we know that + * all the changes up to that point have been flushed to the + * database. + * + * This is called the checkpoint position. The checkpoint position + * is written alternatively to 2 restart files. + * + * To make a checkpoint: + * Get the current log writer log offset. + * For each table: + * Get the log offset of the next operation on the table, if an + * operation is queued for the table. + * Flush that table, and the operation sequence to the table. + * For each unclean transaction: + * Get the log offset of the begin of the transaction. + * Write the lowest of all log offsets to the restart file! + */ + +void XTXactRestart::xres_init(XTThreadPtr self, XTDatabaseHPtr db, xtLogID *log_id, xtLogOffset *log_offset, xtLogID *max_log_id) +{ + char path[PATH_MAX]; + XTOpenFilePtr of = NULL; + XTXlogCheckpointDPtr res_1_buffer = NULL; + XTXlogCheckpointDPtr res_2_buffer = NULL; + XTXlogCheckpointDPtr use_buffer; + xtLogID ind_rec_log_id = 0; + xtLogOffset ind_rec_log_offset = 0; + + enter_(); + xres_db = db; + + ASSERT(!self->st_database); + /* The following call stack: + * XTDatabaseLog::xlog_flush_pending() + * XTDatabaseLog::xlog_flush() + * xt_xlog_flush_log() + * xt_flush_indices() + * idx_out_of_memory_failure() + * xt_idx_delete() + * xres_remove_index_entries() + * xres_apply_change() + * xt_xres_apply_in_order() + * XTXactRestart::xres_restart() + * XTXactRestart::xres_init() + * Leads to st_database being used! + */ + self->st_database = db; + +#ifdef SKIP_STARTUP_CHECKPOINT + /* When debugging, we do not checkpoint immediately, just in case + * we detect a problem during recovery. + */ + xres_cp_required = FALSE; +#else + xres_cp_required = TRUE; +#endif + xres_cp_number = 0; + try_(a) { + + /* Figure out which restart file to use. + */ + xres_name(PATH_MAX, path, 1); + if ((of = xt_open_file(self, path, XT_FS_MISSING_OK))) { + size_t res_1_size; + + res_1_size = (size_t) xt_seek_eof_file(self, of); + res_1_buffer = (XTXlogCheckpointDPtr) xt_malloc(self, res_1_size); + if (!xt_pread_file(of, 0, res_1_size, res_1_size, res_1_buffer, NULL, &self->st_statistics.st_x, self)) + xt_throw(self); + xt_close_file(self, of); + of = NULL; + if (!xres_check_checksum(res_1_buffer, res_1_size)) { + xt_free(self, res_1_buffer); + res_1_buffer = NULL; + } + } + + xres_name(PATH_MAX, path, 2); + if ((of = xt_open_file(self, path, XT_FS_MISSING_OK))) { + size_t res_2_size; + + res_2_size = (size_t) xt_seek_eof_file(self, of); + res_2_buffer = (XTXlogCheckpointDPtr) xt_malloc(self, res_2_size); + if (!xt_pread_file(of, 0, res_2_size, res_2_size, res_2_buffer, NULL, &self->st_statistics.st_x, self)) + xt_throw(self); + xt_close_file(self, of); + of = NULL; + if (!xres_check_checksum(res_2_buffer, res_2_size)) { + xt_free(self, res_2_buffer); + res_2_buffer = NULL; + } + } + + if (res_1_buffer && res_2_buffer) { + if (xt_comp_log_pos( + XT_GET_DISK_4(res_1_buffer->xcp_log_id_4), + XT_GET_DISK_6(res_1_buffer->xcp_log_offs_6), + XT_GET_DISK_4(res_2_buffer->xcp_log_id_4), + XT_GET_DISK_6(res_2_buffer->xcp_log_offs_6)) > 0) { + /* The first log is the further along than the second: */ + xt_free(self, res_2_buffer); + res_2_buffer = NULL; + } + else { + if (XT_GET_DISK_6(res_1_buffer->xcp_chkpnt_no_6) > + XT_GET_DISK_6(res_2_buffer->xcp_chkpnt_no_6)) { + xt_free(self, res_2_buffer); + res_2_buffer = NULL; + } + else { + xt_free(self, res_1_buffer); + res_1_buffer = NULL; + } + } + } + + if (res_1_buffer) { + use_buffer = res_1_buffer; + xres_next_res_no = 2; + } + else { + use_buffer = res_2_buffer; + xres_next_res_no = 1; + } + + /* Read the checkpoint data: */ + if (use_buffer) { + u_int no_of_logs; + xtLogID xt_log_id; + xtTableID xt_tab_id; + + xres_cp_number = XT_GET_DISK_6(use_buffer->xcp_chkpnt_no_6); + xres_cp_log_id = XT_GET_DISK_4(use_buffer->xcp_log_id_4); + xres_cp_log_offset = XT_GET_DISK_6(use_buffer->xcp_log_offs_6); + xt_tab_id = XT_GET_DISK_4(use_buffer->xcp_tab_id_4); + if (xt_tab_id > db->db_curr_tab_id) + db->db_curr_tab_id = xt_tab_id; + db->db_xn_curr_id = XT_GET_DISK_4(use_buffer->xcp_xact_id_4); + ind_rec_log_id = XT_GET_DISK_4(use_buffer->xcp_ind_rec_log_id_4); + ind_rec_log_offset = XT_GET_DISK_6(use_buffer->xcp_ind_rec_log_offs_6); + no_of_logs = XT_GET_DISK_2(use_buffer->xcp_log_count_2); + +#ifdef DEBUG_PRINT + printf("CHECKPOINT log=%d offset=%d ", (int) xres_cp_log_id, (int) xres_cp_log_offset); + if (no_of_logs) + printf("DELETED LOGS: "); +#endif + + /* Logs that are deleted are locked until _after_ the next + * checkpoint. + * + * To prevent the following problem from occuring: + * - Recovery is performed, and log X is deleted + * - After delete a log is free for re-use. + * New data is writen to log X. + * - Server crashes. + * - Recovery is performed from previous checkpoint, + * and log X is deleted again. + * + * To lock the logs the are placed on the deleted list. + * After the next checkpoint, all logs on this list + * will be removed. + */ + for (u_int i=0; i<no_of_logs; i++) { + xt_log_id = (xtLogID) XT_GET_DISK_2(use_buffer->xcp_del_log[i]); +#ifdef DEBUG_PRINT + if (i != 0) + printf(", "); + printf("%d", (int) xt_log_id); +#endif +#ifdef DEBUG_KEEP_LOGS + xt_dl_set_to_delete(self, db, xt_log_id); +#else + if (!xres_delete_data_log(db, xt_log_id)) + xt_throw(self); +#endif + } + +#ifdef DEBUG_PRINT + printf("\n"); +#endif + } + else { + /* Try to determine the correct start point. */ + xres_cp_number = 0; + xres_cp_log_id = xt_xlog_get_min_log(self, db); + xres_cp_log_offset = 0; + ind_rec_log_id = xres_cp_log_id; + ind_rec_log_offset = xres_cp_log_offset; + +#ifdef DEBUG_PRINT + printf("CHECKPOINT log=1 offset=0\n"); +#endif + } + + if (res_1_buffer) { + xt_free(self, res_1_buffer); + res_1_buffer = NULL; + } + if (res_2_buffer) { + xt_free(self, res_2_buffer); + res_2_buffer = NULL; + } + + if (!xres_restart(self, log_id, log_offset, ind_rec_log_id, ind_rec_log_offset, max_log_id)) + xt_throw(self); + } + catch_(a) { + self->st_database = NULL; + if (of) + xt_close_file(self, of); + if (res_1_buffer) + xt_free(self, res_1_buffer); + if (res_2_buffer) + xt_free(self, res_2_buffer); + xres_exit(self); + throw_(); + } + cont_(a); + self->st_database = NULL; + + exit_(); +} + +void XTXactRestart::xres_exit(XTThreadPtr self __attribute__((unused))) +{ +} + +void XTXactRestart::xres_name(size_t size, char *path, xtLogID log_id) +{ + char name[50]; + + sprintf(name, "restart-%lu.xt", (u_long) log_id); + xt_strcpy(size, path, xres_db->db_main_path); + xt_add_system_dir(size, path); + xt_add_dir_char(size, path); + xt_strcat(size, path, name); +} + +xtBool XTXactRestart::xres_check_checksum(XTXlogCheckpointDPtr buffer, size_t size) +{ + size_t head_size; + + /* The minimum size: */ + if (size < offsetof(XTXlogCheckpointDRec, xcp_head_size_4) + 4) + return FAILED; + + /* Check the sizes: */ + head_size = XT_GET_DISK_4(buffer->xcp_head_size_4); + if (size < head_size) + return FAILED; + + if (XT_GET_DISK_2(buffer->xcp_checksum_2) != xt_get_checksum(((xtWord1 *) buffer) + 2, size - 2, 1)) + return FAILED; + + if (XT_GET_DISK_2(buffer->xcp_version_2) != XT_CHECKPOINT_VERSION) + return FAILED; + + return OK; +} + +void XTXactRestart::xres_recover_progress(XTThreadPtr self, XTOpenFilePtr *of, int perc) +{ +#ifdef XT_USE_GLOBAL_DB + if (!perc) { + char file_path[PATH_MAX]; + + xt_strcpy(PATH_MAX, file_path, xres_db->db_main_path); + xt_add_pbxt_file(PATH_MAX, file_path, "recovery-progress"); + *of = xt_open_file(self, file_path, XT_FS_CREATE | XT_FS_MAKE_PATH); + xt_set_eof_file(self, *of, 0); + } + + if (perc > 100) { + char file_path[PATH_MAX]; + + if (*of) { + xt_close_file(self, *of); + *of = NULL; + } + xt_strcpy(PATH_MAX, file_path, xres_db->db_main_path); + xt_add_pbxt_file(PATH_MAX, file_path, "recovery-progress"); + if (xt_fs_exists(file_path)) + xt_fs_delete(self, file_path); + } + else { + char number[40]; + + sprintf(number, "%d", perc); + if (!xt_pwrite_file(*of, 0, strlen(number), number, &self->st_statistics.st_x, self)) + xt_throw(self); + if (!xt_flush_file(*of, &self->st_statistics.st_x, self)) + xt_throw(self); + } +#endif +} + +xtBool XTXactRestart::xres_restart(XTThreadPtr self, xtLogID *log_id, xtLogOffset *log_offset, xtLogID ind_rec_log_id, xtLogOffset ind_rec_log_offset, xtLogID *max_log_id) +{ + xtBool ok = TRUE; + XTDatabaseHPtr db = xres_db; + XTXactLogBufferDPtr record; + xtXactID xn_id; + XTXactDataPtr xact; + xtTableID tab_id; + XTWriterStateRec ws; + off_t bytes_read = 0; + off_t bytes_to_read; + volatile xtBool print_progress = FALSE; + volatile off_t perc_size = 0, next_goal = 0; + int perc_complete = 1; + XTOpenFilePtr progress_file = NULL; + xtBool min_ram_xn_id_set = FALSE; + u_int log_count; + + memset(&ws, 0, sizeof(ws)); + + ws.ws_db = db; + ws.ws_in_recover = TRUE; + ws.ws_ind_rec_log_id = ind_rec_log_id; + ws.ws_ind_rec_log_offset = ind_rec_log_offset; + + /* Initialize the data log buffer (required if extended data is + * referenced). + * Note: this buffer is freed later. It is part of the thread + * "open database" state, and this means that a thread + * may not have another database open (in use) when + * it calls this functions. + */ + self->st_dlog_buf.dlb_init(db, xt_db_log_buffer_size); + + if (!db->db_xlog.xlog_seq_init(&ws.ws_seqread, xt_db_log_buffer_size, TRUE)) + return FAILED; + + bytes_to_read = xres_bytes_to_read(self, db, &log_count, max_log_id); + /* Don't print anything about recovering an empty database: */ + if (bytes_to_read != 0) + xt_logf(XT_NT_INFO, "PBXT: Recovering from %lu-%llu, bytes to read: %llu\n", (u_long) xres_cp_log_id, (u_llong) xres_cp_log_offset, (u_llong) bytes_to_read); + if (bytes_to_read >= 10*1024*1024) { + print_progress = TRUE; + perc_size = bytes_to_read / 100; + next_goal = perc_size; + xres_recover_progress(self, &progress_file, 0); + } + + if (!db->db_xlog.xlog_seq_start(&ws.ws_seqread, xres_cp_log_id, xres_cp_log_offset, FALSE)) { + ok = FALSE; + goto failed; + } + + try_(a) { + for (;;) { + if (!db->db_xlog.xlog_seq_next(&ws.ws_seqread, &record, TRUE, self)) { + ok = FALSE; + break; + } + /* Increment before. If record is NULL then xseq_record_len will be zero, + * UNLESS the last record was of type XT_LOG_ENT_END_OF_LOG + * which fills the log to align to block of size 512. + */ + bytes_read += ws.ws_seqread.xseq_record_len; + if (!record) + break; +#ifdef PRINT_LOG_ON_RECOVERY + xt_print_log_record(ws.ws_seqread.xseq_rec_log_id, ws.ws_seqread.xseq_rec_log_offset, record); +#endif + if (print_progress && bytes_read > next_goal) { + if (((perc_complete - 1) % 25) == 0) + xt_logf(XT_NT_INFO, "PBXT: "); + if ((perc_complete % 25) == 0) + xt_logf(XT_NT_INFO, "%2d\n", (int) perc_complete); + else + xt_logf(XT_NT_INFO, "%2d ", (int) perc_complete); + xt_log_flush(self); + xres_recover_progress(self, &progress_file, perc_complete); + next_goal += perc_size; + perc_complete++; + } + switch (record->xl.xl_status_1) { + case XT_LOG_ENT_HEADER: + break; + case XT_LOG_ENT_NEW_LOG: { + /* Adjust the bytes read for the fact that logs are written + * on 512 byte boundaries. + */ + off_t offs, eof = ws.ws_seqread.xseq_log_eof; + + offs = ws.ws_seqread.xseq_rec_log_offset + ws.ws_seqread.xseq_record_len; + if (eof > offs) + bytes_read += eof - offs; + if (!db->db_xlog.xlog_seq_start(&ws.ws_seqread, XT_GET_DISK_4(record->xl.xl_log_id_4), 0, TRUE)) + xt_throw(self); + break; + } + case XT_LOG_ENT_NEW_TAB: + tab_id = XT_GET_DISK_4(record->xt.xt_tab_id_4); + if (tab_id > db->db_curr_tab_id) + db->db_curr_tab_id = tab_id; + break; + case XT_LOG_ENT_UPDATE_BG: + case XT_LOG_ENT_INSERT_BG: + case XT_LOG_ENT_DELETE_BG: + xn_id = XT_GET_DISK_4(record->xu.xu_xact_id_4); + goto start_xact; + case XT_LOG_ENT_UPDATE_FL_BG: + case XT_LOG_ENT_INSERT_FL_BG: + case XT_LOG_ENT_DELETE_FL_BG: + xn_id = XT_GET_DISK_4(record->xf.xf_xact_id_4); + start_xact: + if (xt_xn_is_before(db->db_xn_curr_id, xn_id)) + db->db_xn_curr_id = xn_id; + + if (!(xact = xt_xn_add_old_xact(db, xn_id, self))) + xt_throw(self); + + xact->xd_begin_log = ws.ws_seqread.xseq_rec_log_id; + xact->xd_begin_offset = ws.ws_seqread.xseq_rec_log_offset; + + xact->xd_end_xn_id = xn_id; + xact->xd_end_time = db->db_xn_end_time; + xact->xd_flags = (XT_XN_XAC_LOGGED | XT_XN_XAC_ENDED | XT_XN_XAC_RECOVERED | XT_XN_XAC_SWEEP); + + /* This may affect the "minimum RAM transaction": */ + if (!min_ram_xn_id_set || xt_xn_is_before(xn_id, db->db_xn_min_ram_id)) { + min_ram_xn_id_set = TRUE; + db->db_xn_min_ram_id = xn_id; + } + xt_xres_apply_in_order(self, &ws, ws.ws_seqread.xseq_rec_log_id, ws.ws_seqread.xseq_rec_log_offset, record); + break; + case XT_LOG_ENT_COMMIT: + case XT_LOG_ENT_ABORT: + xn_id = XT_GET_DISK_4(record->xe.xe_xact_id_4); + if ((xact = xt_xn_get_xact(db, xn_id, self))) { + xact->xd_end_xn_id = xn_id; + xact->xd_flags |= XT_XN_XAC_ENDED | XT_XN_XAC_SWEEP; + xact->xd_flags &= ~XT_XN_XAC_RECOVERED; // We can expect an end record on cleanup! + if (record->xl.xl_status_1 == XT_LOG_ENT_COMMIT) + xact->xd_flags |= XT_XN_XAC_COMMITTED; + } + break; + case XT_LOG_ENT_CLEANUP: + /* The transaction was cleaned up: */ + xn_id = XT_GET_DISK_4(record->xc.xc_xact_id_4); + xt_xn_delete_xact(db, xn_id, self); + break; + case XT_LOG_ENT_OP_SYNC: + xres_sync_operations(self, db, &ws); + break; + case XT_LOG_ENT_DEL_LOG: + xtLogID rec_log_id; + + rec_log_id = XT_GET_DISK_4(record->xl.xl_log_id_4); + xt_dl_set_to_delete(self, db, rec_log_id); + break; + default: + xt_xres_apply_in_order(self, &ws, ws.ws_seqread.xseq_rec_log_id, ws.ws_seqread.xseq_rec_log_offset, record); + break; + } + } + + if (xres_sync_operations(self, db, &ws)) { + XTactOpSyncEntryDRec op_sync; + time_t now = time(NULL); + + op_sync.os_status_1 = XT_LOG_ENT_OP_SYNC; + op_sync.os_checksum_1 = XT_CHECKSUM_1(now) ^ XT_CHECKSUM_1(ws.ws_seqread.xseq_rec_log_id); + XT_SET_DISK_4(op_sync.os_time_4, (xtWord4) now); + /* TODO: If this is done, check to see that + * the byte written here are read back by the writter. + * This is in order to be in sync with 'xl_log_bytes_written'. + * i.e. xl_log_bytes_written == xl_log_bytes_read + */ + if (!db->db_xlog.xlog_write_thru(&ws.ws_seqread, sizeof(XTactOpSyncEntryDRec), (xtWord1 *) &op_sync, self)) + xt_throw(self); + } + } + catch_(a) { + ok = FALSE; + } + cont_(a); + + if (ok) { + if (print_progress) { + while (perc_complete <= 100) { + if (((perc_complete - 1) % 25) == 0) + xt_logf(XT_NT_INFO, "PBXT: "); + if ((perc_complete % 25) == 0) + xt_logf(XT_NT_INFO, "%2d\n", (int) perc_complete); + else + xt_logf(XT_NT_INFO, "%2d ", (int) perc_complete); + xt_log_flush(self); + xres_recover_progress(self, &progress_file, perc_complete); + perc_complete++; + } + } + if (bytes_to_read != 0) + xt_logf(XT_NT_INFO, "PBXT: Recovering complete at %lu-%llu, bytes read: %llu\n", (u_long) ws.ws_seqread.xseq_rec_log_id, (u_llong) ws.ws_seqread.xseq_rec_log_offset, (u_llong) bytes_read); + + *log_id = ws.ws_seqread.xseq_rec_log_id; + *log_offset = ws.ws_seqread.xseq_rec_log_offset; + + if (!min_ram_xn_id_set) + /* This is true because if no transaction was placed in RAM then + * the next transaction in RAM will have the next ID: */ + db->db_xn_min_ram_id = db->db_xn_curr_id + 1; + } + + failed: + xt_free_writer_state(self, &ws); + self->st_dlog_buf.dlb_exit(self); + xres_recover_progress(self, &progress_file, 101); + return ok; +} + +xtBool XTXactRestart::xres_is_checkpoint_pending(xtLogID curr_log_id, xtLogOffset curr_log_offset) +{ + return xt_bytes_since_last_checkpoint(xres_db, curr_log_id, curr_log_offset) >= xt_db_checkpoint_frequency / 2; +} + +/* + * Calculate the bytes to be read for recovery. + * This is only an estimate of the number of bytes that + * will be read. + */ +off_t XTXactRestart::xres_bytes_to_read(XTThreadPtr self, XTDatabaseHPtr db, u_int *log_count, xtLogID *max_log_id) +{ + off_t to_read = 0, eof; + xtLogID log_id = xres_cp_log_id; + char log_path[PATH_MAX]; + XTOpenFilePtr of; + XTXactLogHeaderDRec log_head; + size_t head_size; + size_t red_size; + + *max_log_id = log_id; + *log_count = 0; + for (;;) { + db->db_xlog.xlog_name(PATH_MAX, log_path, log_id); + of = NULL; + if (!xt_open_file_ns(&of, log_path, XT_FS_MISSING_OK)) + xt_throw(self); + if (!of) + break; + pushr_(xt_close_file, of); + + /* Check the first record of the log, to see if it is valid. */ + if (!xt_pread_file(of, 0, sizeof(XTXactLogHeaderDRec), 0, (xtWord1 *) &log_head, &red_size, &self->st_statistics.st_xlog, self)) + xt_throw(self); + /* The minimum size (old log size): */ + if (red_size < XT_MIN_LOG_HEAD_SIZE) + goto done; + head_size = XT_GET_DISK_4(log_head.xh_size_4); + if (log_head.xh_status_1 != XT_LOG_ENT_HEADER) + goto done; + if (log_head.xh_checksum_1 != XT_CHECKSUM_1(log_id)) + goto done; + if (XT_LOG_HEAD_MAGIC(&log_head, head_size) != XT_LOG_FILE_MAGIC) + goto done; + if (head_size > offsetof(XTXactLogHeaderDRec, xh_log_id_4) + 4) { + if (XT_GET_DISK_4(log_head.xh_log_id_4) != log_id) + goto done; + } + if (head_size > offsetof(XTXactLogHeaderDRec, xh_version_2) + 4) { + if (XT_GET_DISK_2(log_head.xh_version_2) > XT_LOG_VERSION_NO) + xt_throw_ulxterr(XT_CONTEXT, XT_ERR_NEW_TYPE_OF_XLOG, (u_long) log_id); + } + + eof = xt_seek_eof_file(self, of); + freer_(); // xt_close_file(of) + if (log_id == xres_cp_log_id) + to_read += (eof - xres_cp_log_offset); + else + to_read += eof; + (*log_count)++; + *max_log_id = log_id; + log_id++; + } + return to_read; + + done: + freer_(); // xt_close_file(of) + return to_read; +} + + +/* ---------------------------------------------------------------------- + * C H E C K P O I N T P R O C E S S + */ + +typedef enum XTFileType { + XT_FT_RECROW_FILE, + XT_FT_INDEX_FILE +} XTFileType; + +typedef struct XTDirtyFile { + xtTableID df_tab_id; + XTFileType df_file_type; +} XTDirtyFileRec, *XTDirtyFilePtr; + +#define XT_MAX_FLUSH_FILES 200 +#define XT_FLUSH_THRESHOLD (2 * 1024 * 1024) + +/* Sort files to be flused. */ +#ifdef USE_LATER +static void xres_cp_flush_files(XTThreadPtr self, XTDatabaseHPtr db) +{ + u_int edx; + XTTableEntryPtr te; + XTDirtyFileRec flush_list[XT_MAX_FLUSH_FILES]; + u_int file_count = 0; + XTIndexPtr *iptr; + u_int dirty_blocks; + XTOpenTablePtr ot; + XTTableHPtr tab; + + retry: + xt_enum_tables_init(&edx); + xt_ht_lock(self, db->db_tables); + pushr_(xt_ht_unlock, db->db_tables); + while (file_count < XT_MAX_FLUSH_FILES && + (te = xt_enum_tables_next(self, db, &edx))) { + if ((tab = te->te_table)) { + if (tab->tab_bytes_to_flush >= XT_FLUSH_THRESHOLD) { + flush_list[file_count].df_tab_id = te->te_tab_id; + flush_list[file_count].df_file_type = XT_FT_RECROW_FILE; + file_count++; + } + if (file_count == XT_MAX_FLUSH_FILES) + break; + iptr = tab->tab_dic.dic_keys; + dirty_blocks = 0; + for (u_int i=0;i<tab->tab_dic.dic_key_count; i++) { + dirty_blocks += (*iptr)->mi_dirty_blocks; + iptr++; + } + if ((dirty_blocks * XT_INDEX_PAGE_SIZE) >= XT_FLUSH_THRESHOLD) { + flush_list[file_count].df_tab_id = te->te_tab_id; + flush_list[file_count].df_file_type = XT_FT_INDEX_FILE; + file_count++; + } + } + } + freer_(); // xt_ht_unlock(db->db_tables) + + for (u_int i=0;i<file_count && !self->t_quit; i++) { + /* We want to flush about once a second: */ + xt_sleep_milli_second(400); + if ((ot = xt_db_open_pool_table(self, db, flush_list[i].df_tab_id, NULL, TRUE))) { + pushr_(xt_db_return_table_to_pool, ot); + + if (flush_list[i].df_file_type == XT_FT_RECROW_FILE) { + if (!xt_flush_record_row(ot, NULL)) + xt_throw(self); + } + else { + if (!xt_flush_indices(ot, NULL)) + xt_throw(self); + } + + freer_(); // xt_db_return_table_to_pool(ot) + } + } + + if (file_count == 100) + goto retry; +} +#endif + +#ifdef xxx +void XTXactRestart::xres_checkpoint_pending(xtLogID log_id, xtLogOffset log_offset) +{ +#ifdef TRACE_CHECKPOINT_ACTIVITY + xtBool tmp = xres_cp_pending; +#endif + xres_cp_pending = xres_is_checkpoint_pending(log_id, log_offset); +#ifdef TRACE_CHECKPOINT_ACTIVITY + if (tmp) { + if (!xres_cp_pending) + printf("%s xres_cp_pending = FALSE\n", xt_get_self()->t_name); + } + else { + if (xres_cp_pending) + printf("%s xres_cp_pending = TRUE\n", xt_get_self()->t_name); + } +#endif +} + + + xres_checkpoint_pending(); + + if (!xres_cp_required && + !xres_cp_pending && + xt_sl_get_size(db->db_datalogs.dlc_to_delete) == 0 && + xt_sl_get_size(db->db_datalogs.dlc_deleted) == 0) + return FALSE; +#endif + +#ifdef NEVER_CHECKPOINT +xtBool no_checkpoint = TRUE; +#endif + +#define XT_CHECKPOINT_IF_NO_ACTIVITY 0 +#define XT_CHECKPOINT_PAUSE_IF_ACTIVITY 1 +#define XT_CHECKPOINT_NO_PAUSE 2 + +/* + * This function performs table flush, as long as the system is idle. + */ +static xtBool xres_cp_checkpoint(XTThreadPtr self, XTDatabaseHPtr db, u_int curr_writer_total, xtBool force_checkpoint) +{ + XTCheckPointStatePtr cp = &db->db_cp_state; + XTOpenTablePtr ot; + XTCheckPointTablePtr to_flush_ptr; + XTCheckPointTableRec to_flush; + u_int table_count = 0; + xtBool checkpoint_done; + off_t bytes_flushed = 0; + int check_type; + +#ifdef NEVER_CHECKPOINT + if (no_checkpoint) + return FALSE; +#endif + if (force_checkpoint) { + if (db->db_restart.xres_cp_required) + check_type = XT_CHECKPOINT_NO_PAUSE; + else + check_type = XT_CHECKPOINT_PAUSE_IF_ACTIVITY; + } + else + check_type = XT_CHECKPOINT_IF_NO_ACTIVITY; + + to_flush.cpt_tab_id = 0; + to_flush.cpt_flushed = 0; + + /* Start a checkpoint: */ + if (!xt_begin_checkpoint(db, FALSE, self)) + xt_throw(self); + + while (!self->t_quit) { + xt_lock_mutex_ns(&cp->cp_state_lock); + table_count = 0; + if (cp->cp_table_ids) + table_count = xt_sl_get_size(cp->cp_table_ids); + if (!cp->cp_running || cp->cp_flush_count >= table_count) { + xt_unlock_mutex_ns(&cp->cp_state_lock); + break; + } + if (cp->cp_next_to_flush > table_count) + cp->cp_next_to_flush = 0; + + to_flush_ptr = (XTCheckPointTablePtr) xt_sl_item_at(cp->cp_table_ids, cp->cp_next_to_flush); + if (to_flush_ptr) + to_flush = *to_flush_ptr; + xt_unlock_mutex_ns(&cp->cp_state_lock); + + if (to_flush_ptr) { + if ((ot = xt_db_open_pool_table(self, db, to_flush.cpt_tab_id, NULL, TRUE))) { + pushr_(xt_db_return_table_to_pool, ot); + + if (!(to_flush.cpt_flushed & XT_CPT_REC_ROW_FLUSHED)) { + if (!xt_flush_record_row(ot, &bytes_flushed, FALSE)) + xt_throw(self); + } + + xt_lock_mutex_ns(&cp->cp_state_lock); + to_flush_ptr = NULL; + if (cp->cp_running) + to_flush_ptr = (XTCheckPointTablePtr) xt_sl_item_at(cp->cp_table_ids, cp->cp_next_to_flush); + if (to_flush_ptr) + to_flush = *to_flush_ptr; + xt_unlock_mutex_ns(&cp->cp_state_lock); + + if (to_flush_ptr && !self->t_quit) { + if (!(to_flush.cpt_flushed & XT_CPT_INDEX_FLUSHED)) { + switch (check_type) { + case XT_CHECKPOINT_IF_NO_ACTIVITY: + if (bytes_flushed > 0 && curr_writer_total != db->db_xn_total_writer_count) { + freer_(); // xt_db_return_table_to_pool(ot) + goto end_checkpoint; + } + break; + case XT_CHECKPOINT_PAUSE_IF_ACTIVITY: + if (bytes_flushed > 2 * 1024 * 1024 && curr_writer_total != db->db_xn_total_writer_count) { + curr_writer_total = db->db_xn_total_writer_count; + bytes_flushed = 0; + xt_sleep_milli_second(400); + } + break; + case XT_CHECKPOINT_NO_PAUSE: + break; + } + + if (!self->t_quit) { + if (!xt_flush_indices(ot, &bytes_flushed, FALSE)) + xt_throw(self); + to_flush.cpt_flushed |= XT_CPT_INDEX_FLUSHED; + } + } + } + + freer_(); // xt_db_return_table_to_pool(ot) + } + + if ((to_flush.cpt_flushed & XT_CPT_ALL_FLUSHED) == XT_CPT_ALL_FLUSHED) + cp->cp_next_to_flush++; + } + else + cp->cp_next_to_flush++; + + if (self->t_quit) + break; + + switch (check_type) { + case XT_CHECKPOINT_IF_NO_ACTIVITY: + if (bytes_flushed > 0 && curr_writer_total != db->db_xn_total_writer_count) + goto end_checkpoint; + break; + case XT_CHECKPOINT_PAUSE_IF_ACTIVITY: + if (bytes_flushed > 2 * 1024 * 1024 && curr_writer_total != db->db_xn_total_writer_count) { + curr_writer_total = db->db_xn_total_writer_count; + bytes_flushed = 0; + xt_sleep_milli_second(400); + } + break; + case XT_CHECKPOINT_NO_PAUSE: + break; + } + } + + end_checkpoint: + if (!xt_end_checkpoint(db, self, &checkpoint_done)) + xt_throw(self); + return checkpoint_done; +} + + +/* Wait for the log writer to tell us to do something. + */ +static void xres_cp_wait_for_log_writer(XTThreadPtr self, XTDatabaseHPtr db, u_long milli_secs) +{ + xt_lock_mutex(self, &db->db_cp_lock); + pushr_(xt_unlock_mutex, &db->db_cp_lock); + if (!self->t_quit) + xt_timed_wait_cond(self, &db->db_cp_cond, &db->db_cp_lock, milli_secs); + freer_(); // xt_unlock_mutex(&db->db_cp_lock) +} + +/* + * This is the way checkpoint works: + * + * To write a checkpoint we need to flush all tables in + * the database. + * + * Before flushing the first table we get the checkpoint + * log position. + * + * After flushing all files we write of the checkpoint + * log position. + */ +static void xres_cp_main(XTThreadPtr self) +{ + XTDatabaseHPtr db = self->st_database; + u_int curr_writer_total; + time_t now; + + xt_set_low_priority(self); + + + while (!self->t_quit) { + /* Wait 2 seconds: */ + curr_writer_total = db->db_xn_total_writer_count; + xt_db_approximate_time = time(NULL); + now = xt_db_approximate_time; + while (!self->t_quit && xt_db_approximate_time < now + 2 && !db->db_restart.xres_cp_required) { + xres_cp_wait_for_log_writer(self, db, 400); + xt_db_approximate_time = time(NULL); + xt_db_free_unused_open_tables(self, db); + } + + if (self->t_quit) + break; + + if (curr_writer_total == db->db_xn_total_writer_count) + /* No activity in 2 seconds: */ + xres_cp_checkpoint(self, db, curr_writer_total, FALSE); + else { + /* There server is busy, check if we need to + * write a checkpoint anyway... + */ + if (db->db_restart.xres_cp_required || + db->db_restart.xres_is_checkpoint_pending(db->db_xlog.xl_write_log_id, db->db_xlog.xl_write_log_offset)) { + /* Flush tables, until the checkpoint is complete. */ + xres_cp_checkpoint(self, db, curr_writer_total, TRUE); + } + } + + if (curr_writer_total == db->db_xn_total_writer_count) { + /* We did a checkpoint, and still, nothing has + * happened.... + * + * Wait for something to happen: + */ + xtLogID log_id; + xtLogOffset log_offset; + + while (!self->t_quit && curr_writer_total == db->db_xn_total_writer_count) { + /* The writer position: */ + xt_lock_mutex(self, &db->db_wr_lock); + pushr_(xt_unlock_mutex, &db->db_wr_lock); + log_id = db->db_wr_log_id; + log_offset = db->db_wr_log_offset; + freer_(); // xt_unlock_mutex(&db->db_wr_lock) + + /* This condition means we could checkpoint: */ + if (!(xt_sl_get_size(db->db_datalogs.dlc_to_delete) == 0 && + xt_sl_get_size(db->db_datalogs.dlc_deleted) == 0 && + xt_comp_log_pos(log_id, log_offset, db->db_restart.xres_cp_log_id, db->db_restart.xres_cp_log_offset) <= 0)) + break; + + xres_cp_wait_for_log_writer(self, db, 400); + xt_db_approximate_time = time(NULL); + xt_db_free_unused_open_tables(self, db); + } + } + } +} + +static void *xres_cp_run_thread(XTThreadPtr self) +{ + XTDatabaseHPtr db = (XTDatabaseHPtr) self->t_data; + int count; + void *mysql_thread; + + mysql_thread = myxt_create_thread(); + + while (!self->t_quit) { + try_(a) { + /* + * The garbage collector requires that the database + * is in use because. + */ + xt_use_database(self, db, XT_FOR_CHECKPOINTER); + + /* This action is both safe and required (see details elsewhere) */ + xt_heap_release(self, self->st_database); + + xres_cp_main(self); + } + catch_(a) { + /* This error is "normal"! */ + if (self->t_exception.e_xt_err != XT_ERR_NO_DICTIONARY && + !(self->t_exception.e_xt_err == XT_SIGNAL_CAUGHT && + self->t_exception.e_sys_err == SIGTERM)) + xt_log_and_clear_exception(self); + } + cont_(a); + + /* Avoid releasing the database (done above) */ + self->st_database = NULL; + xt_unuse_database(self, self); + + /* After an exception, pause before trying again... */ + /* Number of seconds */ + count = 60; + while (!self->t_quit && count > 0) { + sleep(1); + count--; + } + } + + myxt_destroy_thread(mysql_thread, TRUE); + return NULL; +} + +static void xres_cp_free_thread(XTThreadPtr self, void *data) +{ + XTDatabaseHPtr db = (XTDatabaseHPtr) data; + + if (db->db_cp_thread) { + xt_lock_mutex(self, &db->db_cp_lock); + pushr_(xt_unlock_mutex, &db->db_cp_lock); + db->db_cp_thread = NULL; + freer_(); // xt_unlock_mutex(&db->db_cp_lock) + } +} + +/* Start a checkpoint, if none has been started. */ +xtPublic xtBool xt_begin_checkpoint(XTDatabaseHPtr db, xtBool have_table_lock, XTThreadPtr thread) +{ + XTCheckPointStatePtr cp = &db->db_cp_state; + xtLogID log_id; + xtLogOffset log_offset; + xtLogID ind_rec_log_id; + xtLogOffset ind_rec_log_offset; + u_int edx; + XTTableEntryPtr te_ptr; + XTTableHPtr tab; + XTOperationPtr op; + XTCheckPointTableRec cpt; + XTSortedListPtr tables = NULL; + + /* First check if a checkpoint is already running: */ + xt_lock_mutex_ns(&cp->cp_state_lock); + if (cp->cp_running) { + xt_unlock_mutex_ns(&cp->cp_state_lock); + return OK; + } + if (cp->cp_table_ids) { + xt_free_sortedlist(NULL, cp->cp_table_ids); + cp->cp_table_ids = NULL; + } + xt_unlock_mutex_ns(&cp->cp_state_lock); + + /* Flush the log before we continue. This is to ensure that + * before we write a checkpoint, that the changes + * done by the sweeper and the compactor, have been + * applied. + * + * Note, the sweeper does not flush the log, so this is + * necessary! + * + * --- I have removed this flush. It is actually just a + * minor optimisation, which pushes the flush position + * below ahead. + * + * Note that the writer position used for the checkpoint + * _will_ be behind the current log flush position. + * + * This is because the writer cannot apply log changes + * until they are flushed. + */ + /* This is an alternative to the above. + if (!xt_xlog_flush_log(self)) + xt_throw(self); + */ + xt_lock_mutex_ns(&db->db_wr_lock); + + /* The theoretical maximum restart log postion, is the + * position of the writer thread: + */ + log_id = db->db_wr_log_id; + log_offset = db->db_wr_log_offset; + + ind_rec_log_id = db->db_xlog.xl_flush_log_id; + ind_rec_log_offset = db->db_xlog.xl_flush_log_offset; + + xt_unlock_mutex_ns(&db->db_wr_lock); + + /* Go through all the transactions, and find + * the lowest log start position of all the transactions. + */ + for (u_int i=0; i<XT_XN_NO_OF_SEGMENTS; i++) { + XTXactSegPtr seg; + + seg = &db->db_xn_idx[i]; + XT_XACT_WRITE_LOCK(&seg->xs_tab_lock, self); + for (u_int j=0; j<XT_XN_HASH_TABLE_SIZE; j++) { + XTXactDataPtr xact; + + xact = seg->xs_table[j]; + while (xact) { + /* If the transaction is logged, but not cleaned: */ + if ((xact->xd_flags & (XT_XN_XAC_LOGGED | XT_XN_XAC_CLEANED)) == XT_XN_XAC_LOGGED) { + if (xt_comp_log_pos(log_id, log_offset, xact->xd_begin_log, xact->xd_begin_offset) > 0) { + log_id = xact->xd_begin_log; + log_offset = xact->xd_begin_offset; + } + } + xact = xact->xd_next_xact; + } + } + XT_XACT_UNLOCK(&seg->xs_tab_lock, self); + } + +#ifdef TRACE_CHECKPOINT + printf("BEGIN CHECKPOINT %d-%llu\n", (int) log_id, (u_llong) log_offset); +#endif + /* Go through all tables, and find the lowest log position. + * The log position stored by each table shows the position of + * the next operation that still needs to be applied. + * + * This comes from the list of operations which are + * queued for the table. + * + * This function also builds a list of tables! + */ + + if (!(tables = xt_new_sortedlist_ns(sizeof(XTCheckPointTableRec), 20, xres_comp_flush_tabs, NULL, NULL))) + return FAILED; + + xt_enum_tables_init(&edx); + if (!have_table_lock) + xt_ht_lock(NULL, db->db_tables); + while ((te_ptr = xt_enum_tables_next(NULL, db, &edx))) { + if ((tab = te_ptr->te_table)) { + xt_sl_lock_ns(tab->tab_op_list, thread); + if ((op = (XTOperationPtr) xt_sl_first_item(tab->tab_op_list))) { + if (xt_comp_log_pos(log_id, log_offset, op->or_log_id, op->or_log_offset) > 0) { + log_id = op->or_log_id; + log_offset = op->or_log_offset; + } + } + xt_sl_unlock(NULL, tab->tab_op_list); + cpt.cpt_flushed = 0; + cpt.cpt_tab_id = tab->tab_id; +#ifdef TRACE_CHECKPOINT + printf("to flush: %d %s\n", (int) tab->tab_id, tab->tab_name->ps_path); +#endif + if (!xt_sl_insert(NULL, tables, &tab->tab_id, &cpt)) { + if (!have_table_lock) + xt_ht_unlock(NULL, db->db_tables); + xt_free_sortedlist(NULL, tables); + return FAILED; + } + } + } + if (!have_table_lock) + xt_ht_unlock(NULL, db->db_tables); + + xt_lock_mutex_ns(&cp->cp_state_lock); + /* If there is a table list, then someone was faster than me! */ + if (!cp->cp_running && log_id && log_offset) { + cp->cp_running = TRUE; + cp->cp_log_id = log_id; + cp->cp_log_offset = log_offset; + + cp->cp_ind_rec_log_id = ind_rec_log_id; + cp->cp_ind_rec_log_offset = ind_rec_log_offset; + + cp->cp_flush_count = 0; + cp->cp_next_to_flush = 0; + cp->cp_table_ids = tables; + } + else + xt_free_sortedlist(NULL, tables); + xt_unlock_mutex_ns(&cp->cp_state_lock); + + /* At this point, log flushing can begin... */ + return OK; +} + +/* End a checkpoint, if a checkpoint has been started, + * and all checkpoint tables have been flushed + */ +xtPublic xtBool xt_end_checkpoint(XTDatabaseHPtr db, XTThreadPtr thread, xtBool *checkpoint_done) +{ + XTCheckPointStatePtr cp = &db->db_cp_state; + XTXlogCheckpointDPtr cp_buf = NULL; + char path[PATH_MAX]; + XTOpenFilePtr of; + u_int table_count; + size_t chk_size = 0; + u_int no_of_logs = 0; + +#ifdef NEVER_CHECKPOINT + return OK; +#endif + /* Lock the checkpoint state so that only on thread can do this! */ + xt_lock_mutex_ns(&cp->cp_state_lock); + if (!cp->cp_running) + goto checkpoint_done; + + table_count = 0; + if (cp->cp_table_ids) + table_count = xt_sl_get_size(cp->cp_table_ids); + if (cp->cp_flush_count < table_count) { + /* Checkpoint is not done, yet! */ + xt_unlock_mutex_ns(&cp->cp_state_lock); + if (checkpoint_done) + *checkpoint_done = FALSE; + return OK; + } + + /* Check if anything has changed since the last checkpoint, + * if not, there is no need to write a new checkpoint! + */ + if (xt_sl_get_size(db->db_datalogs.dlc_to_delete) == 0 && + xt_sl_get_size(db->db_datalogs.dlc_deleted) == 0 && + xt_comp_log_pos(cp->cp_log_id, cp->cp_log_offset, db->db_restart.xres_cp_log_id, db->db_restart.xres_cp_log_offset) <= 0) { + /* A checkpoint is required if the size of the deleted + * list is not zero. The reason is, I cannot remove the + * logs from the deleted list BEFORE a checkpoint has been + * done which does NOT include these logs. + * + * Even though the logs have already been deleted. They + * remain on the deleted list to ensure that they are NOT + * reused during this time, until the next checkpoint. + * + * This is done because if they are used, then on restart + * they would be deleted! + */ +#ifdef TRACE_CHECKPOINT + printf("--- END CHECKPOINT - no write\n"); +#endif + goto checkpoint_done; + } + +#ifdef TRACE_CHECKPOINT + printf("--- END CHECKPOINT - write start point\n"); +#endif + xt_lock_mutex_ns(&db->db_datalogs.dlc_lock); + + no_of_logs = xt_sl_get_size(db->db_datalogs.dlc_to_delete); + chk_size = offsetof(XTXlogCheckpointDRec, xcp_del_log) + no_of_logs * 2; + xtLogID *log_id_ptr; + + if (!(cp_buf = (XTXlogCheckpointDPtr) xt_malloc_ns(chk_size))) { + xt_unlock_mutex_ns(&db->db_datalogs.dlc_lock); + goto failed_0; + } + + /* Increment the checkpoint number. This value is used if 2 checkpoint have the + * same log number. In this case checkpoints may differ in the log files + * that should be deleted. Here it is important to use the most recent + * log file! + */ + db->db_restart.xres_cp_number++; + + /* Create the checkpoint record: */ + XT_SET_DISK_4(cp_buf->xcp_head_size_4, chk_size); + XT_SET_DISK_2(cp_buf->xcp_version_2, XT_CHECKPOINT_VERSION); + XT_SET_DISK_6(cp_buf->xcp_chkpnt_no_6, db->db_restart.xres_cp_number); + XT_SET_DISK_4(cp_buf->xcp_log_id_4, cp->cp_log_id); + XT_SET_DISK_6(cp_buf->xcp_log_offs_6, cp->cp_log_offset); + XT_SET_DISK_4(cp_buf->xcp_tab_id_4, db->db_curr_tab_id); + XT_SET_DISK_4(cp_buf->xcp_xact_id_4, db->db_xn_curr_id); + XT_SET_DISK_4(cp_buf->xcp_ind_rec_log_id_4, cp->cp_ind_rec_log_id); + XT_SET_DISK_6(cp_buf->xcp_ind_rec_log_offs_6, cp->cp_ind_rec_log_offset); + XT_SET_DISK_2(cp_buf->xcp_log_count_2, no_of_logs); + + for (u_int i=0; i<no_of_logs; i++) { + log_id_ptr = (xtLogID *) xt_sl_item_at(db->db_datalogs.dlc_to_delete, i); + XT_SET_DISK_2(cp_buf->xcp_del_log[i], (xtWord2) *log_id_ptr); + } + + XT_SET_DISK_2(cp_buf->xcp_checksum_2, xt_get_checksum(((xtWord1 *) cp_buf) + 2, chk_size - 2, 1)); + + xt_unlock_mutex_ns(&db->db_datalogs.dlc_lock); + + /* Write the checkpoint: */ + db->db_restart.xres_name(PATH_MAX, path, db->db_restart.xres_next_res_no); + if (!(of = xt_open_file_ns(path, XT_FS_CREATE | XT_FS_MAKE_PATH))) + goto failed_1; + + if (!xt_set_eof_file(NULL, of, 0)) + goto failed_2; + if (!xt_pwrite_file(of, 0, chk_size, (xtWord1 *) cp_buf, &thread->st_statistics.st_x, thread)) + goto failed_2; + if (!xt_flush_file(of, &thread->st_statistics.st_x, thread)) + goto failed_2; + + xt_close_file_ns(of); + + /* Next time write the other restart file: */ + db->db_restart.xres_next_res_no = (db->db_restart.xres_next_res_no % 2) + 1; + db->db_restart.xres_cp_log_id = cp->cp_log_id; + db->db_restart.xres_cp_log_offset = cp->cp_log_offset; + db->db_restart.xres_cp_required = FALSE; + + /* + * Remove all the data logs that were deleted on the + * last checkpoint: + */ + if (!xres_remove_data_logs(db)) + goto failed_0; + +#ifndef DEBUG_KEEP_LOGS + /* After checkpoint, we can delete transaction logs that will no longer be required + * for recovery... + */ + if (cp->cp_log_id > 1) { + xtLogID current_log_id = cp->cp_log_id; + xtLogID del_log_id; + +#ifdef XT_NUMBER_OF_LOGS_TO_SAVE + if (pbxt_crash_debug) { + /* To save the logs, we just consider them in use: */ + if (current_log_id > XT_NUMBER_OF_LOGS_TO_SAVE) + current_log_id -= XT_NUMBER_OF_LOGS_TO_SAVE; + else + current_log_id = 1; + } +#endif + + del_log_id = current_log_id - 1; + + while (del_log_id > 0) { + db->db_xlog.xlog_name(PATH_MAX, path, del_log_id); + if (!xt_fs_exists(path)) + break; + del_log_id--; + } + + /* This was the lowest log ID that existed: */ + del_log_id++; + + /* Delete all logs that still exist, that come before + * the current log: + * + * Do this from least to greatest to ensure no "holes" appear. + */ + while (del_log_id < current_log_id) { + switch (db->db_xlog.xlog_delete_log(del_log_id, thread)) { + case OK: + break; + case FAILED: + goto exit_loop; + case XT_ERR: + goto failed_0; + } + del_log_id++; + } + exit_loop:; + } + + /* And we can delete data logs in the list, and place them + * on the deleted list. + */ + xtLogID log_id; + for (u_int i=0; i<no_of_logs; i++) { + log_id = (xtLogID) XT_GET_DISK_2(cp_buf->xcp_del_log[i]); + if (!xres_delete_data_log(db, log_id)) + goto failed_0; + } +#endif + + xt_free_ns(cp_buf); + cp_buf = NULL; + + checkpoint_done: + cp->cp_running = FALSE; + if (cp->cp_table_ids) { + xt_free_sortedlist(NULL, cp->cp_table_ids); + cp->cp_table_ids = NULL; + } + cp->cp_flush_count = 0; + cp->cp_next_to_flush = 0; + db->db_restart.xres_cp_required = FALSE; + xt_unlock_mutex_ns(&cp->cp_state_lock); + if (checkpoint_done) + *checkpoint_done = TRUE; + return OK; + + failed_2: + xt_close_file_ns(of); + + failed_1: + xt_free_ns(cp_buf); + + failed_0: + if (cp_buf) + xt_free_ns(cp_buf); + xt_unlock_mutex_ns(&cp->cp_state_lock); + return FAILED; +} + +xtPublic xtWord8 xt_bytes_since_last_checkpoint(XTDatabaseHPtr db, xtLogID curr_log_id, xtLogOffset curr_log_offset) +{ + xtLogID log_id; + xtLogOffset log_offset; + size_t byte_count = 0; + + log_id = db->db_restart.xres_cp_log_id; + log_offset = db->db_restart.xres_cp_log_offset; + + /* Assume the logs have the threshold: */ + if (log_id < curr_log_id) { + if (log_offset < xt_db_log_file_threshold) + byte_count = (size_t) (xt_db_log_file_threshold - log_offset); + log_offset = 0; + log_id++; + } + while (log_id < curr_log_id) { + byte_count += (size_t) xt_db_log_file_threshold; + log_id++; + } + if (log_offset < curr_log_offset) + byte_count += (size_t) (curr_log_offset - log_offset); + + return byte_count; +} + +xtPublic void xt_start_checkpointer(XTThreadPtr self, XTDatabaseHPtr db) +{ + char name[PATH_MAX]; + + sprintf(name, "CP-%s", xt_last_directory_of_path(db->db_main_path)); + xt_remove_dir_char(name); + db->db_cp_thread = xt_create_daemon(self, name); + xt_set_thread_data(db->db_cp_thread, db, xres_cp_free_thread); + xt_run_thread(self, db->db_cp_thread, xres_cp_run_thread); +} + +xtPublic void xt_wait_for_checkpointer(XTThreadPtr self, XTDatabaseHPtr db) +{ + time_t then, now; + xtBool message = FALSE; + xtLogID log_id; + xtLogOffset log_offset; + + if (db->db_cp_thread) { + then = time(NULL); + for (;;) { + xt_lock_mutex(self, &db->db_wr_lock); + pushr_(xt_unlock_mutex, &db->db_wr_lock); + log_id = db->db_wr_log_id; + log_offset = db->db_wr_log_offset; + freer_(); // xt_unlock_mutex(&db->db_wr_lock) + + if (xt_sl_get_size(db->db_datalogs.dlc_to_delete) == 0 && + xt_sl_get_size(db->db_datalogs.dlc_deleted) == 0 && + xt_comp_log_pos(log_id, log_offset, db->db_restart.xres_cp_log_id, db->db_restart.xres_cp_log_offset) <= 0) + break; + + /* Do a final checkpoint before shutdown: */ + db->db_restart.xres_cp_required = TRUE; + + xt_lock_mutex(self, &db->db_cp_lock); + pushr_(xt_unlock_mutex, &db->db_cp_lock); + if (!xt_broadcast_cond_ns(&db->db_cp_cond)) { + xt_log_and_clear_exception_ns(); + break; + } + freer_(); // xt_unlock_mutex(&db->db_cp_lock) + + xt_sleep_milli_second(10); + + now = time(NULL); + if (now >= then + 16) { + xt_logf(XT_NT_INFO, "Aborting wait for '%s' checkpointer\n", db->db_name); + message = FALSE; + break; + } + if (now >= then + 2) { + if (!message) { + message = TRUE; + xt_logf(XT_NT_INFO, "Waiting for '%s' checkpointer...\n", db->db_name); + } + } + } + + if (message) + xt_logf(XT_NT_INFO, "Checkpointer '%s' done.\n", db->db_name); + } +} + +xtPublic void xt_stop_checkpointer(XTThreadPtr self, XTDatabaseHPtr db) +{ + XTThreadPtr thr_wr; + + if (db->db_cp_thread) { + xt_lock_mutex(self, &db->db_cp_lock); + pushr_(xt_unlock_mutex, &db->db_cp_lock); + + /* This pointer is safe as long as you have the transaction lock. */ + if ((thr_wr = db->db_cp_thread)) { + xtThreadID tid = thr_wr->t_id; + + /* Make sure the thread quits when woken up. */ + xt_terminate_thread(self, thr_wr); + + xt_wake_checkpointer(self, db); + + freer_(); // xt_unlock_mutex(&db->db_cp_lock) + + /* + * GOTCHA: This is a wierd thing but the SIGTERM directed + * at a particular thread (in this case the sweeper) was + * being caught by a different thread and killing the server + * sometimes. Disconcerting. + * (this may only be a problem on Mac OS X) + xt_kill_thread(thread); + */ + xt_wait_for_thread(tid, FALSE); + + /* PMC - This should not be necessary to set the signal here, but in the + * debugger the handler is not called!!? + thr_wr->t_delayed_signal = SIGTERM; + xt_kill_thread(thread); + */ + db->db_cp_thread = NULL; + } + else + freer_(); // xt_unlock_mutex(&db->db_cp_lock) + } +} + +xtPublic void xt_wake_checkpointer(XTThreadPtr self, XTDatabaseHPtr db) +{ + if (!xt_broadcast_cond_ns(&db->db_cp_cond)) + xt_log_and_clear_exception(self); +} + +xtPublic void xt_free_writer_state(struct XTThread *self, XTWriterStatePtr ws) +{ + if (ws->ws_db) + ws->ws_db->db_xlog.xlog_seq_exit(&ws->ws_seqread); + xt_db_set_size(self, &ws->ws_databuf, 0); + xt_ib_free(self, &ws->ws_rec_buf); + if (ws->ws_ot) { + xt_db_return_table_to_pool(self, ws->ws_ot); + ws->ws_ot = NULL; + } +} + +xtPublic void xt_dump_xlogs(XTDatabaseHPtr db, xtLogID start_log) +{ + XTXactSeqReadRec seq; + XTXactLogBufferDPtr record; + xtLogID log_id = db->db_restart.xres_cp_log_id; + char log_path[PATH_MAX]; + XTThreadPtr thread = xt_get_self(); + + /* Find the first log that still exists:*/ + for (;;) { + log_id--; + db->db_xlog.xlog_name(PATH_MAX, log_path, log_id); + if (!xt_fs_exists(log_path)) + break; + } + log_id++; + + if (!db->db_xlog.xlog_seq_init(&seq, xt_db_log_buffer_size, FALSE)) + return; + + if (log_id < start_log) + log_id = start_log; + + for (;;) { + db->db_xlog.xlog_name(PATH_MAX, log_path, log_id); + if (!xt_fs_exists(log_path)) + break; + + if (!db->db_xlog.xlog_seq_start(&seq, log_id, 0, FALSE)) + goto done; + + PRINTF("---------- DUMP LOG %d\n", (int) log_id); + for (;;) { + if (!db->db_xlog.xlog_seq_next(&seq, &record, TRUE, thread)) { + PRINTF("---------- DUMP LOG %d ERROR\n", (int) log_id); + xt_log_and_clear_exception_ns(); + break; + } + if (!record) { + PRINTF("---------- DUMP LOG %d DONE\n", (int) log_id); + break; + } + xt_print_log_record(seq.xseq_rec_log_id, seq.xseq_rec_log_offset, record); + } + + log_id++; + } + + done: + db->db_xlog.xlog_seq_exit(&seq); +} diff --git a/storage/pbxt/src/restart_xt.h b/storage/pbxt/src/restart_xt.h new file mode 100644 index 00000000000..259b3cbda90 --- /dev/null +++ b/storage/pbxt/src/restart_xt.h @@ -0,0 +1,134 @@ +/* Copyright (c) 2007 PrimeBase Technologies GmbH + * + * PrimeBase XT + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You 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 + * + * 2007-11-12 Paul McCullagh + * + * H&G2JCtL + * + * Restart and write data to the database. + */ + +#ifndef __restart_xt_h__ +#define __restart_xt_h__ + +#include "pthread_xt.h" +#include "filesys_xt.h" +#include "sortedlist_xt.h" +#include "util_xt.h" +#include "xactlog_xt.h" + +struct XTThread; +struct XTOpenTable; +struct XTDatabase; +struct XTTable; + +typedef struct XTWriterState { + struct XTDatabase *ws_db; + xtBool ws_in_recover; + xtLogID ws_ind_rec_log_id; + xtLogOffset ws_ind_rec_log_offset; + XTXactSeqReadRec ws_seqread; + XTDataBufferRec ws_databuf; + XTInfoBufferRec ws_rec_buf; + xtTableID ws_tab_gone; /* Cache the ID of the last table that does not exist. */ + struct XTOpenTable *ws_ot; +} XTWriterStateRec, *XTWriterStatePtr; + +#define XT_CHECKPOINT_VERSION 1 + +typedef struct XTXlogCheckpoint { + XTDiskValue2 xcp_checksum_2; /* The checksum of the all checkpoint data. */ + XTDiskValue4 xcp_head_size_4; + XTDiskValue2 xcp_version_2; /* The version of the checkpoint record. */ + XTDiskValue6 xcp_chkpnt_no_6; /* Incremented for each checkpoint. */ + XTDiskValue4 xcp_log_id_4; /* The restart log ID. */ + XTDiskValue6 xcp_log_offs_6; /* The restart log offset. */ + XTDiskValue4 xcp_tab_id_4; /* The current high table ID. */ + XTDiskValue4 xcp_xact_id_4; /* The current high transaction ID. */ + XTDiskValue4 xcp_ind_rec_log_id_4; /* The index recovery log ID. */ + XTDiskValue6 xcp_ind_rec_log_offs_6; /* The index recovery log offset. */ + XTDiskValue2 xcp_log_count_2; /* Number of logs to be deleted in the area below. */ + XTDiskValue2 xcp_del_log[XT_VAR_LENGTH]; +} XTXlogCheckpointDRec, *XTXlogCheckpointDPtr; + +typedef struct XTXactRestart { + struct XTDatabase *xres_db; + int xres_next_res_no; /* The next restart file to be written. */ + xtLogID xres_cp_log_id; /* Log number of the last checkpoint. */ + xtLogOffset xres_cp_log_offset; /* Log offset of the last checkpoint */ + xtBool xres_cp_required; /* Checkpoint required (startup and shutdown). */ + xtWord8 xres_cp_number; /* The checkpoint number (used to decide which is the latest checkpoint). */ + +public: + void xres_init(struct XTThread *self, struct XTDatabase *db, xtLogID *log_id, xtLogOffset *log_offset, xtLogID *max_log_id); + void xres_exit(struct XTThread *self); + xtBool xres_is_checkpoint_pending(xtLogID log_id, xtLogOffset log_offset); + void xres_checkpoint_pending(xtLogID log_id, xtLogOffset log_offset); + xtBool xres_checkpoint(struct XTThread *self); + void xres_name(size_t size, char *path, xtLogID log_id); + +private: + xtBool xres_check_checksum(XTXlogCheckpointDPtr buffer, size_t size); + void xres_recover_progress(XTThreadPtr self, XTOpenFilePtr *of, int perc); + xtBool xres_restart(struct XTThread *self, xtLogID *log_id, xtLogOffset *log_offset, xtLogID ind_rec_log_id, off_t ind_rec_log_offset, xtLogID *max_log_id); + off_t xres_bytes_to_read(struct XTThread *self, struct XTDatabase *db, u_int *log_count, xtLogID *max_log_id); +} XTXactRestartRec, *XTXactRestartPtr; + +typedef struct XTCheckPointState { + xt_mutex_type cp_state_lock; /* Lock and the entire checkpoint state. */ + xtBool cp_running; /* TRUE if a checkpoint is running. */ + xtLogID cp_log_id; + xtLogOffset cp_log_offset; + xtLogID cp_ind_rec_log_id; + xtLogOffset cp_ind_rec_log_offset; + XTSortedListPtr cp_table_ids; /* List of tables to be flushed for the checkpoint. */ + u_int cp_flush_count; /* The number of tables flushed. */ + u_int cp_next_to_flush; /* The next table to be flushed. */ +} XTCheckPointStateRec, *XTCheckPointStatePtr; + +#define XT_CPT_NONE_FLUSHED 0 +#define XT_CPT_REC_ROW_FLUSHED 1 +#define XT_CPT_INDEX_FLUSHED 2 +#define XT_CPT_ALL_FLUSHED (XT_CPT_REC_ROW_FLUSHED | XT_CPT_INDEX_FLUSHED) + +typedef struct XTCheckPointTable { + u_int cpt_flushed; + xtTableID cpt_tab_id; +} XTCheckPointTableRec, *XTCheckPointTablePtr; + +void xt_xres_init(struct XTThread *self, struct XTDatabase *db); +void xt_xres_exit(struct XTThread *self, struct XTDatabase *db); + +void xt_xres_init_tab(struct XTThread *self, struct XTTable *tab); +void xt_xres_exit_tab(struct XTThread *self, struct XTTable *tab); + +void xt_xres_apply_in_order(struct XTThread *self, XTWriterStatePtr ws, xtLogID log_id, xtLogOffset log_offset, XTXactLogBufferDPtr record); + +xtBool xt_begin_checkpoint(struct XTDatabase *db, xtBool have_table_lock, struct XTThread *thread); +xtBool xt_end_checkpoint(struct XTDatabase *db, struct XTThread *thread, xtBool *checkpoint_done); +void xt_start_checkpointer(struct XTThread *self, struct XTDatabase *db); +void xt_wait_for_checkpointer(struct XTThread *self, struct XTDatabase *db); +void xt_stop_checkpointer(struct XTThread *self, struct XTDatabase *db); +void xt_wake_checkpointer(struct XTThread *self, struct XTDatabase *db); +void xt_free_writer_state(struct XTThread *self, XTWriterStatePtr ws); +xtWord8 xt_bytes_since_last_checkpoint(struct XTDatabase *db, xtLogID curr_log_id, xtLogOffset curr_log_offset); + +void xt_print_log_record(xtLogID log, off_t offset, XTXactLogBufferDPtr record); +void xt_dump_xlogs(struct XTDatabase *db, xtLogID start_log); + +#endif diff --git a/storage/pbxt/src/sortedlist_xt.cc b/storage/pbxt/src/sortedlist_xt.cc new file mode 100644 index 00000000000..b4c525dbb22 --- /dev/null +++ b/storage/pbxt/src/sortedlist_xt.cc @@ -0,0 +1,352 @@ +/* Copyright (c) 2005 PrimeBase Technologies GmbH + * + * PrimeBase XT + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You 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 + * + * 2005-02-04 Paul McCullagh + * + * H&G2JCtL + */ + +#include "xt_config.h" + +#include "pthread_xt.h" +#include "thread_xt.h" +#include "sortedlist_xt.h" + +XTSortedListPtr xt_new_sortedlist_ns(u_int item_size, u_int grow_size, XTCompareFunc comp_func, void *thunk, XTFreeFunc free_func) +{ + XTSortedListPtr sl; + + if (!(sl = (XTSortedListPtr) xt_calloc_ns(sizeof(XTSortedListRec)))) + return NULL; + sl->sl_item_size = item_size; + sl->sl_grow_size = grow_size; + sl->sl_comp_func = comp_func; + sl->sl_thunk = thunk; + sl->sl_free_func = free_func; + sl->sl_current_size = 0; + return sl; +} + +XTSortedListPtr xt_new_sortedlist(XTThreadPtr self, u_int item_size, u_int initial_size, u_int grow_size, XTCompareFunc comp_func, void *thunk, XTFreeFunc free_func, xtBool with_lock, xtBool with_cond) +{ + XTSortedListPtr sl; + + sl = (XTSortedListPtr) xt_calloc(self, sizeof(XTSortedListRec)); + xt_init_sortedlist(self, sl, item_size, initial_size, grow_size, comp_func, thunk, free_func, with_lock, with_cond); + return sl; +} + +xtPublic void xt_init_sortedlist(XTThreadPtr self, XTSortedListPtr sl, u_int item_size, u_int initial_size, u_int grow_size, XTCompareFunc comp_func, void *thunk, XTFreeFunc free_func, xtBool with_lock, xtBool with_cond) +{ + sl->sl_item_size = item_size; + sl->sl_grow_size = grow_size; + sl->sl_comp_func = comp_func; + sl->sl_thunk = thunk; + sl->sl_free_func = free_func; + sl->sl_current_size = initial_size; + + if (initial_size) { + try_(a) { + sl->sl_data = (char *) xt_malloc(self, initial_size * item_size); + } + catch_(a) { + xt_free(self, sl); + throw_(); + } + cont_(a); + } + + if (with_lock || with_cond) { + sl->sl_lock = (xt_mutex_type *) xt_calloc(self, sizeof(xt_mutex_type)); + try_(b) { + xt_init_mutex_with_autoname(self, sl->sl_lock); + } + catch_(b) { + xt_free(self, sl->sl_lock); + sl->sl_lock = NULL; + xt_free_sortedlist(self, sl); + throw_(); + } + cont_(b); + } + + if (with_cond) { + sl->sl_cond = (xt_cond_type *) xt_calloc(self, sizeof(xt_cond_type)); + try_(c) { + xt_init_cond(self, sl->sl_cond); + } + catch_(c) { + xt_free(self, sl->sl_cond); + sl->sl_cond = NULL; + xt_free_sortedlist(self, sl); + throw_(); + } + cont_(c); + } +} + +xtPublic void xt_empty_sortedlist(XTThreadPtr self, XTSortedListPtr sl) +{ + if (sl->sl_lock) + xt_lock_mutex(self, sl->sl_lock); + if (sl->sl_data) { + while (sl->sl_usage_count > 0) { + sl->sl_usage_count--; + if (sl->sl_free_func) + (*sl->sl_free_func)(self, sl->sl_thunk, &sl->sl_data[sl->sl_usage_count * sl->sl_item_size]); + } + } + if (sl->sl_lock) + xt_unlock_mutex(self, sl->sl_lock); +} + +xtPublic void xt_free_sortedlist(XTThreadPtr self, XTSortedListPtr sl) +{ + xt_empty_sortedlist(self, sl); + if (sl->sl_data) { + xt_free(self, sl->sl_data); + sl->sl_data = NULL; + } + if (sl->sl_lock) { + xt_free_mutex(sl->sl_lock); + xt_free(self, sl->sl_lock); + } + if (sl->sl_cond) { + xt_free_cond(sl->sl_cond); + xt_free(self, sl->sl_cond); + } + xt_free(self, sl); +} + +xtPublic void *xt_sl_find(XTThreadPtr self, XTSortedListPtr sl, void *key) +{ + void *result; + size_t idx; + + if (sl->sl_usage_count == 0) + return NULL; + else if (sl->sl_usage_count == 1) { + if ((*sl->sl_comp_func)(self, sl->sl_thunk, key, sl->sl_data) == 0) + return sl->sl_data; + return NULL; + } + result = xt_bsearch(self, key, sl->sl_data, sl->sl_usage_count, sl->sl_item_size, &idx, sl->sl_thunk, sl->sl_comp_func); + return result; +} + +/* + * Returns: + * 1 = Value inserted. + * 2 = Value not inserted, already in the list. + * 0 = An error occurred. + */ +xtPublic int xt_sl_insert(XTThreadPtr self, XTSortedListPtr sl, void *key, void *data) +{ + size_t idx; + + if (sl->sl_usage_count == 0) + idx = 0; + else if (sl->sl_usage_count == 1) { + int r; + + if ((r = (*sl->sl_comp_func)(self, sl->sl_thunk, key, sl->sl_data)) == 0) { + if (sl->sl_free_func) + (*sl->sl_free_func)(self, sl->sl_thunk, data); + return 2; + } + if (r < 0) + idx = 0; + else + idx = 1; + } + else { + if (xt_bsearch(self, key, sl->sl_data, sl->sl_usage_count, sl->sl_item_size, &idx, sl->sl_thunk, sl->sl_comp_func)) { + if (sl->sl_free_func) + (*sl->sl_free_func)(self, sl->sl_thunk, data); + return 2; + } + } + if (sl->sl_usage_count == sl->sl_current_size) { + if (!xt_realloc_ns((void **) &sl->sl_data, (sl->sl_current_size + sl->sl_grow_size) * sl->sl_item_size)) { + if (sl->sl_free_func) + (*sl->sl_free_func)(self, sl->sl_thunk, data); + if (self) + xt_throw(self); + return 0; + } + sl->sl_current_size = sl->sl_current_size + sl->sl_grow_size; + } + XT_MEMMOVE(sl->sl_data, &sl->sl_data[(idx+1) * sl->sl_item_size], &sl->sl_data[idx * sl->sl_item_size], (sl->sl_usage_count-idx) * sl->sl_item_size); + XT_MEMCPY(sl->sl_data, &sl->sl_data[idx * sl->sl_item_size], data, sl->sl_item_size); + sl->sl_usage_count++; + return 1; +} + +xtPublic xtBool xt_sl_delete(XTThreadPtr self, XTSortedListPtr sl, void *key) +{ + void *result; + size_t idx; + + if (sl->sl_usage_count == 0) + return FALSE; + if (sl->sl_usage_count == 1) { + if ((*sl->sl_comp_func)(self, sl->sl_thunk, key, sl->sl_data) != 0) + return FALSE; + idx = 0; + result = sl->sl_data; + } + else { + if (!(result = xt_bsearch(self, key, sl->sl_data, sl->sl_usage_count, sl->sl_item_size, &idx, sl->sl_thunk, sl->sl_comp_func))) + return FALSE; + } + if (sl->sl_free_func) + (*sl->sl_free_func)(self, sl->sl_thunk, result); + sl->sl_usage_count--; + XT_MEMMOVE(sl->sl_data, &sl->sl_data[idx * sl->sl_item_size], &sl->sl_data[(idx+1) * sl->sl_item_size], (sl->sl_usage_count-idx) * sl->sl_item_size); + return TRUE; +} + +xtPublic void xt_sl_delete_item_at(struct XTThread *self, XTSortedListPtr sl, size_t idx) +{ + void *result; + + if (idx >= sl->sl_usage_count) + return; + result = &sl->sl_data[idx * sl->sl_item_size]; + if (sl->sl_free_func) + (*sl->sl_free_func)(self, sl->sl_thunk, result); + sl->sl_usage_count--; + XT_MEMMOVE(sl->sl_data, &sl->sl_data[idx * sl->sl_item_size], &sl->sl_data[(idx+1) * sl->sl_item_size], (sl->sl_usage_count-idx) * sl->sl_item_size); +} + +xtPublic void xt_sl_remove_from_front(struct XTThread *self __attribute__((unused)), XTSortedListPtr sl, size_t items) +{ + if (sl->sl_usage_count <= items) + xt_sl_set_size(sl, 0); + else { + XT_MEMMOVE(sl->sl_data, sl->sl_data, &sl->sl_data[items * sl->sl_item_size], (sl->sl_usage_count-items) * sl->sl_item_size); + sl->sl_usage_count -= items; + } +} + +xtPublic void xt_sl_delete_from_info(XTThreadPtr self, XTSortedListInfoPtr li_undo) +{ + xt_sl_delete(self, li_undo->li_sl, li_undo->li_key); +} + +xtPublic size_t xt_sl_get_size(XTSortedListPtr sl) +{ + return sl->sl_usage_count; +} + +xtPublic void xt_sl_set_size(XTSortedListPtr sl, size_t new_size) +{ + sl->sl_usage_count = new_size; + if (sl->sl_usage_count + sl->sl_grow_size <= sl->sl_current_size) { + size_t curr_size; + + curr_size = sl->sl_usage_count; + if (curr_size < sl->sl_grow_size) + curr_size = sl->sl_grow_size; + + if (xt_realloc(NULL, (void **) &sl->sl_data, curr_size * sl->sl_item_size)) + sl->sl_current_size = curr_size; + } +} + +xtPublic void *xt_sl_item_at(XTSortedListPtr sl, size_t idx) +{ + if (idx < sl->sl_usage_count) + return &sl->sl_data[idx * sl->sl_item_size]; + return NULL; +} + +xtPublic void *xt_sl_last_item(XTSortedListPtr sl) +{ + if (sl->sl_usage_count > 0) + return xt_sl_item_at(sl, sl->sl_usage_count - 1); + return NULL; +} + +xtPublic void *xt_sl_first_item(XTSortedListPtr sl) +{ + if (sl->sl_usage_count > 0) + return xt_sl_item_at(sl, 0); + return NULL; +} + +xtPublic xtBool xt_sl_lock(XTThreadPtr self, XTSortedListPtr sl) +{ + xtBool r = OK; + + if (sl->sl_locker != self) + r = xt_lock_mutex(self, sl->sl_lock); + if (r) { + sl->sl_locker = self; + sl->sl_lock_count++; + } + return r; +} + +xtPublic void xt_sl_unlock(XTThreadPtr self, XTSortedListPtr sl) +{ + ASSERT(!self || sl->sl_locker == self); + ASSERT(sl->sl_lock_count > 0); + + sl->sl_lock_count--; + if (!sl->sl_lock_count) { + sl->sl_locker = NULL; + xt_unlock_mutex(self, sl->sl_lock); + } +} + +xtPublic void xt_sl_lock_ns(XTSortedListPtr sl, XTThreadPtr thread) +{ + if (sl->sl_locker != thread) + xt_lock_mutex_ns(sl->sl_lock); + sl->sl_locker = thread; + sl->sl_lock_count++; +} + +xtPublic void xt_sl_unlock_ns(XTSortedListPtr sl) +{ + ASSERT_NS(!sl->sl_locker || sl->sl_locker == xt_get_self()); + ASSERT_NS(sl->sl_lock_count > 0); + + sl->sl_lock_count--; + if (!sl->sl_lock_count) { + sl->sl_locker = NULL; + xt_unlock_mutex_ns(sl->sl_lock); + } +} + +xtPublic void xt_sl_wait(XTThreadPtr self, XTSortedListPtr sl) +{ + xt_wait_cond(self, sl->sl_cond, sl->sl_lock); +} + +xtPublic xtBool xt_sl_signal(XTThreadPtr self, XTSortedListPtr sl) +{ + return xt_signal_cond(self, sl->sl_cond); +} + +xtPublic void xt_sl_broadcast(XTThreadPtr self, XTSortedListPtr sl) +{ + xt_broadcast_cond(self, sl->sl_cond); +} + diff --git a/storage/pbxt/src/sortedlist_xt.h b/storage/pbxt/src/sortedlist_xt.h new file mode 100644 index 00000000000..cf3066981fe --- /dev/null +++ b/storage/pbxt/src/sortedlist_xt.h @@ -0,0 +1,79 @@ +/* Copyright (c) 2005 PrimeBase Technologies GmbH + * + * PrimeBase XT + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You 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 + * + * 2005-02-04 Paul McCullagh + * + * H&G2JCtL + */ +#ifndef __xt_sortedlist_h__ +#define __xt_sortedlist_h__ + +#include "pthread_xt.h" +#include "bsearch_xt.h" + +struct XTThread; + +typedef struct XTSortedList { + u_int sl_item_size; + u_int sl_grow_size; + XTCompareFunc sl_comp_func; + void *sl_thunk; + XTFreeFunc sl_free_func; + xt_mutex_type *sl_lock; + struct XTThread *sl_locker; + u_int sl_lock_count; + xt_cond_type *sl_cond; + + u_int sl_current_size; + u_int sl_usage_count; + char *sl_data; +} XTSortedListRec, *XTSortedListPtr; + +typedef struct XTSortedListInfo { + XTSortedListPtr li_sl; + void *li_key; +} XTSortedListInfoRec, *XTSortedListInfoPtr; + +XTSortedListPtr xt_new_sortedlist(struct XTThread *self, u_int item_size, u_int initial_size, u_int grow_size, XTCompareFunc comp_func, void *thunk, XTFreeFunc free_func, xtBool with_lock, xtBool with_cond); +void xt_init_sortedlist(struct XTThread *self, XTSortedListPtr sl, u_int item_size, u_int initial_size, u_int grow_size, XTCompareFunc comp_func, void *thunk, XTFreeFunc free_func, xtBool with_lock, xtBool with_cond); +void xt_free_sortedlist(struct XTThread *self, XTSortedListPtr ld); +void xt_empty_sortedlist(struct XTThread *self, XTSortedListPtr sl); +XTSortedListPtr xt_new_sortedlist_ns(u_int item_size, u_int grow_size, XTCompareFunc comp_func, void *thunk, XTFreeFunc free_func); + +xtBool xt_sl_insert(struct XTThread *self, XTSortedListPtr sl, void *key, void *data); +void *xt_sl_find(struct XTThread *self, XTSortedListPtr sl, void *key); +xtBool xt_sl_delete(struct XTThread *self, XTSortedListPtr sl, void *key); +void xt_sl_delete_item_at(struct XTThread *self, XTSortedListPtr sl, size_t i); +void xt_sl_remove_from_front(struct XTThread *self, XTSortedListPtr sl, size_t items); +void xt_sl_delete_from_info(struct XTThread *self, XTSortedListInfoPtr li); +size_t xt_sl_get_size(XTSortedListPtr sl); +void xt_sl_set_size(XTSortedListPtr sl, size_t new_size); +void *xt_sl_item_at(XTSortedListPtr sl, size_t i); +void *xt_sl_last_item(XTSortedListPtr sl); +void *xt_sl_first_item(XTSortedListPtr sl); + +xtBool xt_sl_lock(struct XTThread *self, XTSortedListPtr sl); +void xt_sl_unlock(struct XTThread *self, XTSortedListPtr sl); +void xt_sl_lock_ns(XTSortedListPtr sl, struct XTThread *thread); +void xt_sl_unlock_ns(XTSortedListPtr sl); + +void xt_sl_wait(struct XTThread *self, XTSortedListPtr sl); +xtBool xt_sl_signal(struct XTThread *self, XTSortedListPtr sl); +void xt_sl_broadcast(struct XTThread *self, XTSortedListPtr sl); + +#endif diff --git a/storage/pbxt/src/streaming_xt.cc b/storage/pbxt/src/streaming_xt.cc new file mode 100755 index 00000000000..710b3256d60 --- /dev/null +++ b/storage/pbxt/src/streaming_xt.cc @@ -0,0 +1,624 @@ +/* Copyright (c) 2005 PrimeBase Technologies GmbH + * + * PrimeBase XT + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You 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 + * + * 2006-06-07 Paul McCullagh + * + * H&G2JCtL + * + * This file contains PBXT streaming interface. + */ + +#include "xt_config.h" + +#ifdef XT_STREAMING +#include "ha_pbxt.h" + +#include "thread_xt.h" +#include "strutil_xt.h" +#include "table_xt.h" +#include "myxt_xt.h" +#include "xaction_xt.h" +#include "database_xt.h" +#include "streaming_xt.h" + +extern PBMSEngineRec pbxt_engine; + +static PBMS_API pbxt_streaming; + +/* ---------------------------------------------------------------------- + * INIT & EXIT + */ + +xtPublic xtBool xt_init_streaming(void) +{ + XTThreadPtr self = NULL; + int err; + PBMSResultRec result; + + if ((err = pbxt_streaming.registerEngine(&pbxt_engine, &result))) { + xt_logf(XT_CONTEXT, XT_LOG_ERROR, "%s\n", result.mr_message); + return FAILED; + } + return OK; +} + +xtPublic void xt_exit_streaming(void) +{ + pbxt_streaming.deregisterEngine(&pbxt_engine); +} + +/* ---------------------------------------------------------------------- + * UTILITY FUNCTIONS + */ + +static void str_result_to_exception(XTExceptionPtr e, int r, PBMSResultPtr result) +{ + char *str, *end_str; + + e->e_xt_err = r; + e->e_sys_err = result->mr_code; + xt_strcpy(XT_ERR_MSG_SIZE, e->e_err_msg, result->mr_message); + + e->e_source_line = 0; + str = result->mr_stack; + if ((end_str = strchr(str, '('))) { + xt_strcpy_term(XT_MAX_FUNC_NAME_SIZE, e->e_func_name, str, '('); + str = end_str+1; + if ((end_str = strchr(str, ':'))) { + xt_strcpy_term(XT_SOURCE_FILE_NAME_SIZE, e->e_source_file, str, ':'); + str = end_str+1; + if ((end_str = strchr(str, ')'))) { + char number[40]; + + xt_strcpy_term(40, number, str, ')'); + e->e_source_line = atol(number); + str = end_str+1; + if (*str == '\n') + str++; + } + } + } + + if (e->e_source_line == 0) { + *e->e_func_name = 0; + *e->e_source_file = 0; + xt_strcpy(XT_ERR_MSG_SIZE, e->e_catch_trace, result->mr_stack); + } + else + xt_strcpy(XT_ERR_MSG_SIZE, e->e_catch_trace, str); +} + +static void str_exception_to_result(XTExceptionPtr e, PBMSResultPtr result) +{ + int len; + + if (e->e_sys_err) + result->mr_code = e->e_sys_err; + else + result->mr_code = e->e_xt_err; + xt_strcpy(MS_RESULT_MESSAGE_SIZE, result->mr_message, e->e_err_msg); + xt_strcpy(MS_RESULT_STACK_SIZE, result->mr_stack, e->e_func_name); + xt_strcat(MS_RESULT_STACK_SIZE, result->mr_stack, "("); + xt_strcat(MS_RESULT_STACK_SIZE, result->mr_stack, e->e_source_file); + xt_strcat(MS_RESULT_STACK_SIZE, result->mr_stack, ":"); + xt_strcati(MS_RESULT_STACK_SIZE, result->mr_stack, (int) e->e_source_line); + xt_strcat(MS_RESULT_STACK_SIZE, result->mr_stack, ")"); + len = strlen(result->mr_stack); + if (strncmp(result->mr_stack, e->e_catch_trace, len) == 0) + xt_strcat(MS_RESULT_STACK_SIZE, result->mr_stack, e->e_catch_trace + len); + else { + xt_strcat(MS_RESULT_STACK_SIZE, result->mr_stack, "\n"); + xt_strcat(MS_RESULT_STACK_SIZE, result->mr_stack, e->e_catch_trace); + } +} + +static XTIndexPtr str_find_index(XTTableHPtr tab, u_int *col_list, u_int col_cnt) +{ + u_int i, j; + XTIndexPtr *ind; /* MySQL/PBXT key description */ + + ind = tab->tab_dic.dic_keys; + for (i=0; i<tab->tab_dic.dic_key_count; i++) { + if ((*ind)->mi_seg_count == col_cnt) { + for (j=0; j<(*ind)->mi_seg_count; j++) { + if ((*ind)->mi_seg[j].col_idx != col_list[j]) + goto loop; + } + return *ind; + } + + loop: + ind++; + } + return NULL; +} + +static XTThreadPtr str_set_current_thread(THD *thd, PBMSResultPtr result) +{ + XTThreadPtr self; + XTExceptionRec e; + + if (!(self = xt_ha_set_current_thread(thd, &e))) { + str_exception_to_result(&e, result); + return NULL; + } + return self; +} + +/* ---------------------------------------------------------------------- + * BLOB STREAMING INTERFACE + */ + +static void pbxt_close_conn(void *thread) +{ + xt_ha_close_connection((THD *) thread); +} + +static int pbxt_open_table(void *thread, const char *table_url, void **open_table, PBMSResultPtr result) +{ + THD *thd = (THD *) thread; + XTThreadPtr self; + XTTableHPtr tab = NULL; + XTOpenTablePtr ot = NULL; + int err = MS_OK; + + if (!(self = str_set_current_thread(thd, result))) + return MS_ERR_ENGINE; + + try_(a) { + xt_ha_open_database_of_table(self, (XTPathStrPtr) table_url); + if (!(tab = xt_use_table(self, (XTPathStrPtr) table_url, FALSE, TRUE, NULL))) { + err = MS_ERR_UNKNOWN_TABLE; + goto done; + } + if (!(ot = xt_open_table(tab))) + throw_(); + ot->ot_thread = self; + done:; + } + catch_(a) { + str_exception_to_result(&self->t_exception, result); + err = MS_ERR_ENGINE; + } + cont_(a); + if (tab) + xt_heap_release(self, tab); + *open_table = ot; + return err; +} + +static void pbxt_close_table(void *thread, void *open_table_ptr) +{ + THD *thd = (THD *) thread; + volatile XTThreadPtr self, new_self = NULL; + XTOpenTablePtr ot = (XTOpenTablePtr) open_table_ptr; + XTExceptionRec e; + + if (thd) { + if (!(self = xt_ha_set_current_thread(thd, &e))) { + xt_log_exception(NULL, &e, XT_LOG_DEFAULT); + return; + } + } + else if (!(self = xt_get_self())) { + if (!(new_self = xt_create_thread("TempForClose", FALSE, TRUE, &e))) { + xt_log_exception(NULL, &e, XT_LOG_DEFAULT); + return; + } + self = new_self; + } + + ot->ot_thread = self; + try_(a) { + xt_close_table(ot, TRUE, FALSE); + } + catch_(a) { + xt_log_and_clear_exception(self); + } + cont_(a); + if (new_self) + xt_free_thread(self); +} + +static int pbxt_lock_table(void *thread, int *xact, void *open_table, int lock_type, PBMSResultPtr result) +{ + THD *thd = (THD *) thread; + XTThreadPtr self; + XTOpenTablePtr ot = (XTOpenTablePtr) open_table; + int err = MS_OK; + + if (!(self = str_set_current_thread(thd, result))) + return MS_ERR_ENGINE; + + if (lock_type != MS_LOCK_NONE) { + try_(a) { + xt_ha_open_database_of_table(self, ot->ot_table->tab_name); + ot->ot_thread = self; + } + catch_(a) { + str_exception_to_result(&self->t_exception, result); + err = MS_ERR_ENGINE; + } + cont_(a); + } + + if (!err && *xact == MS_XACT_BEGIN) { + if (self->st_xact_data) + *xact = MS_XACT_NONE; + else { + if (xt_xn_begin(self)) { + *xact = MS_XACT_COMMIT; + } + else { + str_exception_to_result(&self->t_exception, result); + err = MS_ERR_ENGINE; + } + } + } + + return err; +} + +static int pbxt_unlock_table(void *thread, int xact, void *open_table __attribute__((unused)), PBMSResultPtr result) +{ + THD *thd = (THD *) thread; + XTThreadPtr self = xt_ha_thd_to_self(thd); + int err = MS_OK; + + if (xact == MS_XACT_COMMIT) { + if (!xt_xn_commit(self)) { + str_exception_to_result(&self->t_exception, result); + err = MS_ERR_ENGINE; + } + } + else if (xact == MS_XACT_ROLLBACK) { + xt_xn_rollback(self); + } + + return err; +} + +static int pbxt_send_blob(void *thread, void *open_table, const char *blob_column, const char *blob_url_p, void *stream, PBMSResultPtr result) +{ + THD *thd = (THD *) thread; + XTThreadPtr self = xt_ha_thd_to_self(thd); + XTOpenTablePtr ot = (XTOpenTablePtr) open_table; + int err = MS_OK; + u_int blob_col_idx, col_idx; + char col_name[XT_IDENTIFIER_NAME_SIZE]; + XTStringBufferRec value; + u_int col_list[XT_MAX_COLS_PER_INDEX]; + u_int col_cnt; + char col_names[XT_ERR_MSG_SIZE - 200]; + XTIdxSearchKeyRec search_key; + XTIndexPtr ind; + char *blob_data; + size_t blob_len; + const char *blob_url = blob_url_p; + + memset(&value, 0, sizeof(value)); + + *col_names = 0; + + ot->ot_thread = self; + try_(a) { + if (ot->ot_row_wbuf_size < ot->ot_table->tab_dic.dic_mysql_buf_size) { + xt_realloc(self, (void **) &ot->ot_row_wbuffer, ot->ot_table->tab_dic.dic_mysql_buf_size); + ot->ot_row_wbuf_size = ot->ot_table->tab_dic.dic_mysql_buf_size; + } + + xt_strcpy_url(XT_IDENTIFIER_NAME_SIZE, col_name, blob_column); + if (!myxt_find_column(ot, &blob_col_idx, col_name)) + xt_throw_tabcolerr(XT_CONTEXT, XT_ERR_COLUMN_NOT_FOUND, ot->ot_table->tab_name, blob_column); + + /* Prepare a row for the condition: */ + const char *ptr; + + col_cnt = 0; + while (*blob_url) { + ptr = xt_strchr(blob_url, '='); + xt_strncpy_url(XT_IDENTIFIER_NAME_SIZE, col_name, blob_url, (size_t) (ptr - blob_url)); + if (!myxt_find_column(ot, &col_idx, col_name)) + xt_throw_tabcolerr(XT_CONTEXT, XT_ERR_COLUMN_NOT_FOUND, ot->ot_table->tab_name, col_name); + if (*col_names) + xt_strcat(sizeof(col_names), col_names, ", "); + xt_strcat(sizeof(col_names), col_names, col_name); + blob_url = ptr; + if (*blob_url == '=') + blob_url++; + ptr = xt_strchr(blob_url, '&'); + value.sb_len = 0; + xt_sb_concat_url_len(self, &value, blob_url, (size_t) (ptr - blob_url)); + blob_url = ptr; + if (*blob_url == '&') + blob_url++; + if (!myxt_set_column(ot, (char *) ot->ot_row_rbuffer, col_idx, value.sb_cstring, value.sb_len)) + xt_throw_tabcolerr(XT_CONTEXT, XT_ERR_CONVERSION, ot->ot_table->tab_name, col_name); + if (col_cnt < XT_MAX_COLS_PER_INDEX) { + col_list[col_cnt] = col_idx; + col_cnt++; + } + } + + /* Find a matching index: */ + if (!(ind = str_find_index(ot->ot_table, col_list, col_cnt))) + xt_throw_ixterr(XT_CONTEXT, XT_ERR_NO_MATCHING_INDEX, col_names); + + search_key.sk_key_value.sv_flags = 0; + search_key.sk_key_value.sv_rec_id = 0; + search_key.sk_key_value.sv_row_id = 0; + search_key.sk_key_value.sv_key = search_key.sk_key_buf; + search_key.sk_key_value.sv_length = myxt_create_key_from_row(ind, search_key.sk_key_buf, ot->ot_row_rbuffer, NULL); + search_key.sk_on_key = FALSE; + + if (!xt_idx_search(ot, ind, &search_key)) + xt_throw(self); + + if (!ot->ot_curr_rec_id) + xt_throw_taberr(XT_CONTEXT, XT_ERR_NO_ROWS, ot->ot_table->tab_name); + + while (ot->ot_curr_rec_id) { + if (!search_key.sk_on_key) + xt_throw_taberr(XT_CONTEXT, XT_ERR_NO_ROWS, ot->ot_table->tab_name); + + retry: + /* X TODO - Check if the write buffer is big enough here! */ + switch (xt_tab_read_record(ot, ot->ot_row_wbuffer)) { + case FALSE: + if (xt_idx_next(ot, ind, &search_key)) + break; + case XT_ERR: + xt_throw(self); + case XT_NEW: + if (xt_idx_match_search(ot, ind, &search_key, ot->ot_row_wbuffer, XT_S_MODE_MATCH)) + goto success; + if (!xt_idx_next(ot, ind, &search_key)) + xt_throw(self); + break; + case XT_RETRY: + goto retry; + default: + goto success; + } + } + + success: + myxt_get_column_data(ot, (char *) ot->ot_row_wbuffer, blob_col_idx, &blob_data, &blob_len); + + /* + * Write the content length, then write the HTTP + * header, and then the content. + */ + err = pbxt_streaming.setContentLength(stream, blob_len, result); + if (!err) + err = pbxt_streaming.writeHead(stream, result); + if (!err) + err = pbxt_streaming.writeStream(stream, (void *) blob_data, blob_len, result); + } + catch_(a) { + str_exception_to_result(&self->t_exception, result); + if (result->mr_code == XT_ERR_NO_ROWS) + err = MS_ERR_NOT_FOUND; + else + err = MS_ERR_ENGINE; + } + cont_(a); + xt_sb_set_size(NULL, &value, 0); + return err; +} + +int pbxt_lookup_ref(void *thread, void *open_table, unsigned short col_index, PBMSEngineRefPtr eng_ref, PBMSFieldRefPtr field_ref, PBMSResultPtr result) +{ + THD *thd = (THD *) thread; + XTThreadPtr self = xt_ha_thd_to_self(thd); + XTOpenTablePtr ot = (XTOpenTablePtr) open_table; + int err = MS_OK; + u_int i, len; + char *data; + XTIndexPtr ind = NULL; + + ot->ot_thread = self; + if (ot->ot_row_wbuf_size < ot->ot_table->tab_dic.dic_mysql_buf_size) { + xt_realloc(self, (void **) &ot->ot_row_wbuffer, ot->ot_table->tab_dic.dic_mysql_buf_size); + ot->ot_row_wbuf_size = ot->ot_table->tab_dic.dic_mysql_buf_size; + } + + ot->ot_curr_rec_id = (xtRecordID) XT_GET_DISK_8(eng_ref->er_data); + switch (xt_tab_dirty_read_record(ot, ot->ot_row_wbuffer)) { + case FALSE: + err = MS_ERR_ENGINE; + break; + default: + break; + } + + if (err) { + str_exception_to_result(&self->t_exception, result); + goto exit; + } + + myxt_get_column_name(ot, col_index, PBMS_FIELD_COL_SIZE, field_ref->fr_column); + + for (i=0; i<ot->ot_table->tab_dic.dic_key_count; i++) { + ind = ot->ot_table->tab_dic.dic_keys[i]; + if (ind->mi_flags & (HA_UNIQUE_CHECK | HA_NOSAME)) + break; + } + + if (ind) { + len = 0; + data = field_ref->fr_cond; + for (i=0; i<ind->mi_seg_count; i++) { + if (i > 0) { + xt_strcat(PBMS_FIELD_COND_SIZE, data, "&"); + len = strlen(data); + } + myxt_get_column_name(ot, ind->mi_seg[i].col_idx, PBMS_FIELD_COND_SIZE - len, data + len); + len = strlen(data); + xt_strcat(PBMS_FIELD_COND_SIZE, data, "="); + len = strlen(data); + myxt_get_column_as_string(ot, (char *) ot->ot_row_wbuffer, ind->mi_seg[i].col_idx, PBMS_FIELD_COND_SIZE - len, data + len); + len = strlen(data); + } + } + else + xt_strcpy(PBMS_FIELD_COND_SIZE, field_ref->fr_cond, "*no unique key*"); + + exit: + return err; +} + +PBMSEngineRec pbxt_engine = { + MS_ENGINE_VERSION, + 0, + FALSE, + "PBXT", + NULL, + pbxt_close_conn, + pbxt_open_table, + pbxt_close_table, + pbxt_lock_table, + pbxt_unlock_table, + pbxt_send_blob, + pbxt_lookup_ref +}; + +/* ---------------------------------------------------------------------- + * CALL IN FUNCTIONS + */ + +xtPublic void xt_pbms_close_all_tables(const char *table_url) +{ + pbxt_streaming.closeAllTables(table_url); +} + +xtPublic xtBool xt_pbms_close_connection(void *thd, XTExceptionPtr e) +{ + PBMSResultRec result; + int err; + + err = pbxt_streaming.closeConn(thd, &result); + if (err) { + str_result_to_exception(e, err, &result); + return FAILED; + } + return OK; +} + +xtPublic xtBool xt_pbms_open_table(void **open_table, char *table_path) +{ + PBMSResultRec result; + int err; + + err = pbxt_streaming.openTable(open_table, table_path, &result); + if (err) { + XTThreadPtr thread = xt_get_self(); + + str_result_to_exception(&thread->t_exception, err, &result); + return FAILED; + } + return OK; +} + +xtPublic void xt_pbms_close_table(void *open_table) +{ + PBMSResultRec result; + int err; + + err = pbxt_streaming.closeTable(open_table, &result); + if (err) { + XTThreadPtr thread = xt_get_self(); + + str_result_to_exception(&thread->t_exception, err, &result); + xt_log_exception(thread, &thread->t_exception, XT_LOG_DEFAULT); + } +} + +xtPublic xtBool xt_pbms_use_blob(void *open_table, char **ret_blob_url, char *blob_url, unsigned short col_index) +{ + PBMSResultRec result; + int err; + + err = pbxt_streaming.useBlob(open_table, ret_blob_url, blob_url, col_index, &result); + if (err) { + XTThreadPtr thread = xt_get_self(); + + str_result_to_exception(&thread->t_exception, err, &result); + return FAILED; + } + return OK; +} + +xtPublic xtBool xt_pbms_retain_blobs(void *open_table, PBMSEngineRefPtr eng_ref) +{ + PBMSResultRec result; + int err; + + err = pbxt_streaming.retainBlobs(open_table, eng_ref, &result); + if (err) { + XTThreadPtr thread = xt_get_self(); + + str_result_to_exception(&thread->t_exception, err, &result); + return FAILED; + } + return OK; +} + +xtPublic void xt_pbms_release_blob(void *open_table, char *blob_url, unsigned short col_index, PBMSEngineRefPtr eng_ref) +{ + PBMSResultRec result; + int err; + + err = pbxt_streaming.releaseBlob(open_table, blob_url, col_index, eng_ref, &result); + if (err) { + XTThreadPtr thread = xt_get_self(); + + str_result_to_exception(&thread->t_exception, err, &result); + xt_log_exception(thread, &thread->t_exception, XT_LOG_DEFAULT); + } +} + +xtPublic void xt_pbms_drop_table(const char *table_path) +{ + PBMSResultRec result; + int err; + + err = pbxt_streaming.dropTable(table_path, &result); + if (err) { + XTThreadPtr thread = xt_get_self(); + + str_result_to_exception(&thread->t_exception, err, &result); + xt_log_exception(thread, &thread->t_exception, XT_LOG_DEFAULT); + } +} + +xtPublic void xt_pbms_rename_table(const char *from_table, const char *to_table) +{ + PBMSResultRec result; + int err; + + err = pbxt_streaming.renameTable(from_table, to_table, &result); + if (err) { + XTThreadPtr thread = xt_get_self(); + + str_result_to_exception(&thread->t_exception, err, &result); + xt_log_exception(thread, &thread->t_exception, XT_LOG_DEFAULT); + } +} + +#endif // XT_STREAMING diff --git a/storage/pbxt/src/streaming_xt.h b/storage/pbxt/src/streaming_xt.h new file mode 100755 index 00000000000..6fe36822383 --- /dev/null +++ b/storage/pbxt/src/streaming_xt.h @@ -0,0 +1,46 @@ +/* Copyright (c) 2005 PrimeBase Technologies GmbH + * + * PrimeBase XT + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You 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 + * + * 2006-06-07 Paul McCullagh + * + * H&G2JCtL + * + * This file contains PBXT streaming interface. + */ + +#ifndef __streaming_xt_h__ +#define __streaming_xt_h__ + +#include "xt_defs.h" +#define PBMS_API pbms_api_PBXT +#include "pbms.h" + +xtBool xt_init_streaming(void); +void xt_exit_streaming(void); + +void xt_pbms_close_all_tables(const char *table_url); +xtBool xt_pbms_close_connection(void *thd, XTExceptionPtr e); +xtBool xt_pbms_open_table(void **open_table, char *table_path); +void xt_pbms_close_table(void *open_table); +xtBool xt_pbms_use_blob(void *open_table, char **ret_blob_url, char *blob_url, unsigned short col_index); +xtBool xt_pbms_retain_blobs(void *open_table, PBMSEngineRefPtr eng_ref); +void xt_pbms_release_blob(void *open_table, char *blob_url, unsigned short col_index, PBMSEngineRefPtr eng_ref); +void xt_pbms_drop_table(const char *table_path); +void xt_pbms_rename_table(const char *from_table, const char *to_table); + +#endif diff --git a/storage/pbxt/src/strutil_xt.cc b/storage/pbxt/src/strutil_xt.cc new file mode 100644 index 00000000000..60e45c455d1 --- /dev/null +++ b/storage/pbxt/src/strutil_xt.cc @@ -0,0 +1,567 @@ +/* Copyright (c) 2005 PrimeBase Technologies GmbH + * + * PrimeBase XT + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You 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 + * + * 2005-01-03 Paul McCullagh + * + * H&G2JCtL + */ + +#include "xt_config.h" + +#include <stdio.h> +#include <string.h> +#include <ctype.h> + +#include "strutil_xt.h" + +xtPublic void xt_strcpy(size_t size, char *to, c_char *from) +{ + if (size > 0) { + size--; + while (*from && size--) + *to++ = *from++; + *to = 0; + } +} + +xtPublic void xt_strncpy(size_t size, char *to, c_char *from, size_t len_from) +{ + if (size > 0) { + size--; + while (len_from-- && size--) + *to++ = *from++; + *to = 0; + } +} + +xtPublic void xt_strcpy_term(size_t size, char *to, c_char *from, char term) +{ + if (size > 0) { + size--; + while (*from && *from != term && size--) + *to++ = *from++; + *to = 0; + } +} + +xtPublic void xt_strcat_term(size_t size, char *to, c_char *from, char term) +{ + while (*to && size--) to++; + if (size > 0) { + size--; + while (*from && *from != term && size--) + *to++ = *from++; + *to = 0; + } +} + +xtPublic void xt_strcat(size_t size, char *to, c_char *from) +{ + while (*to && size--) to++; + xt_strcpy(size, to, from); +} + +xtPublic void xt_strcati(size_t size, char *to, int i) +{ + char buffer[50]; + + sprintf(buffer, "%d", i); + xt_strcat(size, to, buffer); +} + +xtPublic xtBool xt_ends_with(c_char *str, c_char *sub) +{ + unsigned long len = strlen(str); + + if (len >= strlen(sub)) + return strcmp(&str[len-strlen(sub)], sub) == 0; + return FALSE; +} + +xtPublic xtPublic xtBool xt_starts_with(c_char *str, c_char *sub) +{ + return (strstr(str, sub) == str); +} + +/* This function returns "" if the path ends with a dir char */ +xtPublic void xt_2nd_last_name_of_path(size_t size, char *dest, c_char *path) +{ + size_t len; + c_char *ptr, *pend; + + len = strlen(path); + if (!len) { + *dest = 0; + return; + } + ptr = path + len - 1; + while (ptr != path && !XT_IS_DIR_CHAR(*ptr)) + ptr--; + if (!XT_IS_DIR_CHAR(*ptr)) { + *dest = 0; + return; + } + pend = ptr; + ptr--; + while (ptr != path && !XT_IS_DIR_CHAR(*ptr)) + ptr--; + if (XT_IS_DIR_CHAR(*ptr)) + ptr++; + len = (size_t) (pend - ptr); + if (len > size-1) + len = size-1; + memcpy(dest, ptr, len); + dest[len] = 0; +} + +/* This function returns "" if the path ends with a dir char */ +xtPublic char *xt_last_name_of_path(c_char *path) +{ + size_t length; + c_char *ptr; + + length = strlen(path); + if (!length) + return (char *) path; + ptr = path + length - 1; + while (ptr != path && !XT_IS_DIR_CHAR(*ptr)) ptr--; + if (XT_IS_DIR_CHAR(*ptr)) ptr++; + return (char *) ptr; +} + +xtPublic char *xt_last_2_names_of_path(c_char *path) +{ + size_t length; + c_char *ptr; + + length = strlen(path); + if (!length) + return (char *) path; + ptr = path + length - 1; + while (ptr != path && !XT_IS_DIR_CHAR(*ptr)) ptr--; + if (XT_IS_DIR_CHAR(*ptr)) { + ptr--; + while (ptr != path && !XT_IS_DIR_CHAR(*ptr)) ptr--; + if (XT_IS_DIR_CHAR(*ptr)) + ptr++; + } + return (char *) ptr; +} + +xtPublic c_char *xt_last_directory_of_path(c_char *path) +/* This function returns the last name component, even if the path ends with a dir char */ +{ + size_t length; + c_char *ptr; + + length = strlen(path); + if (!length) + return(path); + ptr = path + length - 1; + /* Path may end with multiple slashes: */ + while (ptr != path && XT_IS_DIR_CHAR(*ptr)) + ptr--; + while (ptr != path && !XT_IS_DIR_CHAR(*ptr)) + ptr--; + if (XT_IS_DIR_CHAR(*ptr)) ptr++; + return(ptr); +} + +xtPublic char *xt_find_extension(c_char *file_name) +{ + c_char *ptr; + + for (ptr = file_name + strlen(file_name) - 1; ptr >= file_name; ptr--) { + if (XT_IS_DIR_CHAR(*ptr)) + break; + if (*ptr == '.') + return (char *) (ptr + 1); + } + return NULL; +} + +xtPublic void xt_remove_extension(char *file_name) +{ + char *ptr = xt_find_extension(file_name); + + if (ptr) + *(ptr - 1) = 0; +} + +xtPublic xtBool xt_is_extension(c_char *file_name, c_char *ext) +{ + char *ptr; + + if (!(ptr = xt_find_extension(file_name))) + return FALSE; + return strcmp(ptr, ext) == 0; +} + +/* + * Optionally remove trailing directory delimiters (If the directory name consists of one + * character, the directory delimiter is not removed). + */ +xtPublic xtBool xt_remove_dir_char(char *dir_name) +{ + size_t length; + xtBool removed = FALSE; + + length = strlen(dir_name); + while (length > 1 && XT_IS_DIR_CHAR(dir_name[length - 1])) { + dir_name[length - 1] = '\0'; + length--; + removed = TRUE; + } + return removed; +} + +xtPublic void xt_remove_last_name_of_path(char *path) +{ + char *ptr; + + if ((ptr = xt_last_name_of_path(path))) + *ptr = 0; +} + +xtBool xt_add_dir_char(size_t max, char *path) +{ + size_t slen = strlen(path); + + if (slen >= max) + return FALSE; + + if (slen == 0) { + /* If no path is given we will be at the current working directory, under UNIX we must + * NOT add a directory delimiter character: + */ + return FALSE; + } + + if (!XT_IS_DIR_CHAR(path[slen - 1])) { + path[slen] = XT_DIR_CHAR; + path[slen + 1] = '\0'; + return TRUE; + } + return FALSE; +} + +xtPublic xtInt8 xt_str_to_int8(c_char *ptr, xtBool *overflow) +{ + xtInt8 value = 0; + + if (overflow) + *overflow = FALSE; + while (*ptr == '0') ptr++; + if (!*ptr) + value = (xtInt8) 0; + else { + sscanf(ptr, "%"PRId64, &value); + if (!value && overflow) + *overflow = TRUE; + } + return value; +} + +xtPublic void xt_int8_to_str(xtInt8 value, char *string) +{ + sprintf(string, "%"PRId64, value); +} + +xtPublic void xt_double_to_str(double value, int scale, char *string) +{ + char *ptr; + + sprintf(string, "%.*f", scale, value); + ptr = string + strlen(string) - 1; + + if (strchr(string, '.') && (*ptr == '0' || *ptr == '.')) { + while (ptr-1 > string && *(ptr-1) == '0') ptr--; + if (ptr-1 > string && *(ptr-1) == '.') ptr--; + *ptr = 0; + } +} + +/* + * This function understand GB, MB, KB. + */ +xtPublic xtInt8 xt_byte_size_to_int8(c_char *ptr) +{ + char number[101], *num_ptr; + xtInt8 size; + + while (*ptr && isspace(*ptr)) + ptr++; + + num_ptr = number; + while (*ptr && isdigit(*ptr)) { + if (num_ptr < number+100) { + *num_ptr = *ptr; + num_ptr++; + } + ptr++; + } + *num_ptr = 0; + size = xt_str_to_int8(number, NULL); + + while (*ptr && isspace(*ptr)) + ptr++; + + switch (toupper(*ptr)) { + case 'P': + size *= 1024; + case 'T': + size *= 1024; + case 'G': + size *= 1024; + case 'M': + size *= 1024; + case 'K': + size *= 1024; + break; + } + + return size; +} + +xtPublic void xt_int8_to_byte_size(xtInt8 value, char *string) +{ + double v; + c_char *unit; + char val_str[100]; + + if (value >= (xtInt8) (1024 * 1024 * 1024)) { + v = (double) value / (double) (1024 * 1024 * 1024); + unit = "GB"; + } + else if (value >= (xtInt8) (1024 * 1024)) { + v = (double) value / (double) (1024 * 1024); + unit = "MB"; + } + else if (value >= (xtInt8) 1024) { + v = (double) value / (double) (1024); + unit = "Kb"; + } + else { + v = (double) value; + unit = "bytes"; + } + + xt_double_to_str(v, 2, val_str); + sprintf(string, "%s %s (%"PRId64" bytes)", val_str, unit, value); +} + +xtPublic c_char *xt_get_version(void) +{ + return "1.0.08 RC"; +} + +/* Copy and URL decode! */ +xtPublic void xt_strcpy_url(size_t size, char *to, c_char *from) +{ + if (size > 0) { + size--; + while (*from && size--) { + if (*from == '%' && isxdigit(*(from+1)) && isxdigit(*(from+2))) { + unsigned char a = xt_hex_digit(*(from+1)); + unsigned char b = xt_hex_digit(*(from+2)); + *to++ = a << 4 | b; + from += 3; + } + else + *to++ = *from++; + } + *to = 0; + } +} + +/* Copy and URL decode! */ +xtPublic void xt_strncpy_url(size_t size, char *to, c_char *from, size_t len_from) +{ + if (size > 0) { + size--; + while (len_from-- && size--) { + if (*from == '%' && len_from >= 2 && isxdigit(*(from+1)) && isxdigit(*(from+2))) { + unsigned char a = xt_hex_digit(*(from+1)); + unsigned char b = xt_hex_digit(*(from+2)); + *to++ = a << 4 | b; + from += 3; + } + else + *to++ = *from++; + } + *to = 0; + } +} + +/* Returns a pointer to the end of the string if nothing found! */ +const char *xt_strchr(const char *str, char ch) +{ + while (*str && *str != ch) str++; + return str; +} + +unsigned char xt_hex_digit(char ch) +{ + if (isdigit(ch)) + return((unsigned char) ch - (unsigned char) '0'); + + ch = toupper(ch); + if (ch >= 'A' && ch <= 'F') + return((unsigned char) ch - (unsigned char) 'A' + (unsigned char) 10); + + return((unsigned char) 0); +} + +#ifdef XT_WIN +xtPublic void xt_win_dialog(char *message) +{ + MessageBoxA(NULL, message, "Debug Me!", MB_ICONWARNING | MB_OK); +} +#endif + +/* + * --------------- SYSTEM STATISTICS ------------------ + */ + +static char su_t_unit[10] = "usec"; +/* + * Note times, are return in microseconds, but the display in xtstat is currently + * in milliseconds. + */ +static XTStatMetaDataRec pbxt_stat_meta_data[XT_STAT_MAXIMUM] = { + { XT_STAT_TIME_CURRENT, "Current Time", "time", "curr", XT_STAT_DATE, + "The current time in seconds" }, + { XT_STAT_TIME_PASSED, "Time Since Last Call", "time", su_t_unit, XT_STAT_ACCUMULATIVE | XT_STAT_TIME_VALUE, + "Time passed in %sseconds since last statistics call" }, + + { XT_STAT_COMMITS, "Commit Count", "xact", "commt", XT_STAT_ACCUMULATIVE, + "Number of transactions committed" }, + { XT_STAT_ROLLBACKS, "Rollback Count", "xact", "rollb", XT_STAT_ACCUMULATIVE, + "Number of transactions rolled back" }, + { XT_STAT_WAIT_FOR_XACT, "Wait for Xact Count", "xact", "waits", XT_STAT_ACCUMULATIVE, + "Number of times waited for another transaction" }, + { XT_STAT_XACT_TO_CLEAN, "Dirty Xact Count", "xact", "dirty", 0, + "Number of transactions still to be cleaned up" }, + + { XT_STAT_STAT_READS, "Read Statements", "stat", "read", XT_STAT_ACCUMULATIVE, + "Number of SELECT statements" }, + { XT_STAT_STAT_WRITES, "Write Statements", "stat", "write", XT_STAT_ACCUMULATIVE, + "Number of UPDATE/INSERT/DELETE statements" }, + + { XT_STAT_REC_BYTES_IN, "Record Bytes Read", "rec", "in", XT_STAT_ACCUMULATIVE | XT_STAT_BYTE_COUNT, + "Bytes read from the record/row files" }, + { XT_STAT_REC_BYTES_OUT, "Record Bytes Written", "rec", "out", XT_STAT_ACCUMULATIVE | XT_STAT_BYTE_COUNT, + "Bytes written from the record/row files" }, + { XT_STAT_REC_SYNC_COUNT, "Record File Flushes", "rec", "syncs", XT_STAT_ACCUMULATIVE | XT_STAT_COMBO_FIELD, + "Number of flushes to record/row files" }, + { XT_STAT_REC_SYNC_TIME, "Record Flush Time", "rec", su_t_unit, XT_STAT_ACCUMULATIVE | XT_STAT_TIME_VALUE | XT_STAT_COMBO_FIELD_2, + "The time in %sseconds to flush record/row files" }, + { XT_STAT_REC_CACHE_HIT, "Record Cache Hits", "rec", "hits", XT_STAT_ACCUMULATIVE, + "Hits when accessing the record cache" }, + { XT_STAT_REC_CACHE_MISS, "Record Cache Misses", "rec", "miss", XT_STAT_ACCUMULATIVE, + "Misses when accessing the record cache" }, + { XT_STAT_REC_CACHE_FREES, "Record Cache Frees", "rec", "frees", XT_STAT_ACCUMULATIVE, + "Number of record cache pages freed" }, + { XT_STAT_REC_CACHE_USAGE, "Record Cache Usage", "rec", "%use", XT_STAT_PERCENTAGE, + "Percentage of record cache in use" }, + + { XT_STAT_IND_BYTES_IN, "Index Bytes Read", "ind", "in", XT_STAT_ACCUMULATIVE | XT_STAT_BYTE_COUNT, + "Bytes read from the index files" }, + { XT_STAT_IND_BYTES_OUT, "Index Bytes Written", "ind", "out", XT_STAT_ACCUMULATIVE | XT_STAT_BYTE_COUNT, + "Bytes written from the index files" }, + { XT_STAT_IND_SYNC_COUNT, "Index File Flushes", "ind", "syncs", XT_STAT_ACCUMULATIVE | XT_STAT_COMBO_FIELD, + "Number of flushes to index files" }, + { XT_STAT_IND_SYNC_TIME, "Index Flush Time", "ind", su_t_unit, XT_STAT_ACCUMULATIVE | XT_STAT_TIME_VALUE | XT_STAT_COMBO_FIELD_2, + "The time in %sseconds to flush index files" }, + { XT_STAT_IND_CACHE_HIT, "Index Cache Hits", "ind", "hits", XT_STAT_ACCUMULATIVE, + "Hits when accessing the index cache" }, + { XT_STAT_IND_CACHE_MISS, "Index Cache Misses", "ind", "miss", XT_STAT_ACCUMULATIVE, + "Misses when accessing the index cache" }, + { XT_STAT_IND_CACHE_USAGE, "Index Cache Usage", "ind", "%use", XT_STAT_PERCENTAGE, + "Percentage of index cache used" }, + { XT_STAT_ILOG_BYTES_IN, "Index Log Bytes In", "ilog", "in", XT_STAT_ACCUMULATIVE | XT_STAT_BYTE_COUNT, + "Bytes read from the index log files" }, + { XT_STAT_ILOG_BYTES_OUT, "Index Log Bytes Out", "ilog", "out", XT_STAT_ACCUMULATIVE | XT_STAT_BYTE_COUNT, + "Bytes written from the index log files" }, + { XT_STAT_ILOG_SYNC_COUNT, "Index Log File Syncs", "ilog", "syncs", XT_STAT_ACCUMULATIVE | XT_STAT_COMBO_FIELD, + "Number of flushes to index log files" }, + { XT_STAT_ILOG_SYNC_TIME, "Index Log Sync Time", "ilog", su_t_unit, XT_STAT_ACCUMULATIVE | XT_STAT_TIME_VALUE | XT_STAT_COMBO_FIELD_2, + "The time in %sseconds to flush index log files" }, + + { XT_STAT_XLOG_BYTES_IN, "Xact Log Bytes In", "xlog", "in", XT_STAT_ACCUMULATIVE | XT_STAT_BYTE_COUNT, + "Bytes read from the transaction log files" }, + { XT_STAT_XLOG_BYTES_OUT, "Xact Log Bytes Out", "xlog", "out", XT_STAT_ACCUMULATIVE | XT_STAT_BYTE_COUNT, + "Bytes written from the transaction log files" }, + { XT_STAT_XLOG_SYNC_COUNT, "Xact Log File Syncs", "xlog", "syncs", XT_STAT_ACCUMULATIVE, + "Number of flushes to transaction log files" }, + { XT_STAT_XLOG_SYNC_TIME, "Xact Log Sync Time", "xlog", su_t_unit, XT_STAT_ACCUMULATIVE | XT_STAT_TIME_VALUE, + "The time in %sseconds to flush transaction log files" }, + { XT_STAT_XLOG_CACHE_HIT, "Xact Log Cache Hits", "xlog", "hits", XT_STAT_ACCUMULATIVE, + "Hits when accessing the transaction log cache" }, + { XT_STAT_XLOG_CACHE_MISS, "Xact Log Cache Misses","xlog", "miss", XT_STAT_ACCUMULATIVE, + "Misses when accessing the transaction log cache" }, + { XT_STAT_XLOG_CACHE_USAGE, "Xact Log Cache Usage", "xlog", "%use", XT_STAT_PERCENTAGE, + "Percentage of transaction log cache used" }, + + { XT_STAT_DATA_BYTES_IN, "Data Log Bytes In", "data", "in", XT_STAT_ACCUMULATIVE | XT_STAT_BYTE_COUNT, + "Bytes read from the data log files" }, + { XT_STAT_DATA_BYTES_OUT, "Data Log Bytes Out", "data", "out", XT_STAT_ACCUMULATIVE | XT_STAT_BYTE_COUNT, + "Bytes written from the data log files" }, + { XT_STAT_DATA_SYNC_COUNT, "Data Log File Syncs", "data", "syncs", XT_STAT_ACCUMULATIVE, + "Number of flushes to data log files" }, + { XT_STAT_DATA_SYNC_TIME, "Data Log Sync Time", "data", su_t_unit, XT_STAT_ACCUMULATIVE | XT_STAT_TIME_VALUE, + "The time in %sseconds to flush data log files" }, + + { XT_STAT_BYTES_TO_CHKPNT, "Bytes to Checkpoint", "to", "chkpt", XT_STAT_BYTE_COUNT, + "Bytes written to the log since the last checkpoint" }, + { XT_STAT_LOG_BYTES_TO_WRITE, "Log Bytes to Write", "to", "write", XT_STAT_BYTE_COUNT, + "Bytes written to the log, still to be written to the database" }, + { XT_STAT_BYTES_TO_SWEEP, "Log Bytes to Sweep", "to", "sweep", XT_STAT_BYTE_COUNT, + "Bytes written to the log, still to be read by the sweeper" }, + { XT_STAT_SWEEPER_WAITS, "Sweeper Wait on Xact", "sweep", "waits", XT_STAT_ACCUMULATIVE, + "Attempts to cleanup a transaction" }, + + { XT_STAT_SCAN_INDEX, "Index Scan Count", "scan", "index", XT_STAT_ACCUMULATIVE, + "Number of index scans" }, + { XT_STAT_SCAN_TABLE, "Table Scan Count", "scan", "table", XT_STAT_ACCUMULATIVE, + "Number of table scans" }, + { XT_STAT_ROW_SELECT, "Select Row Count", "row", "sel", XT_STAT_ACCUMULATIVE, + "Number of rows selected" }, + { XT_STAT_ROW_INSERT, "Insert Row Count", "row", "ins", XT_STAT_ACCUMULATIVE, + "Number of rows inserted" }, + { XT_STAT_ROW_UPDATE, "Update Row Count", "row", "upd", XT_STAT_ACCUMULATIVE, + "Number of rows updated" }, + { XT_STAT_ROW_DELETE, "Delete Row Count", "row", "del", XT_STAT_ACCUMULATIVE, + "Number of rows deleted" }, + + { XT_STAT_RETRY_INDEX_SCAN, "Index Scan Retries", "retry", "iscan", XT_STAT_ACCUMULATIVE, + "Index scans restarted because of locked record" }, + { XT_STAT_REREAD_REC_LIST, "Record List Rereads", "retry", "rlist", XT_STAT_ACCUMULATIVE, + "Record list rescanned due to lock" } +}; + +xtPublic XTStatMetaDataPtr xt_get_stat_meta_data(int i) +{ + return &pbxt_stat_meta_data[i]; +} + +xtPublic void xt_set_time_unit(const char *u) +{ + xt_strcpy(10, su_t_unit, u); +} + diff --git a/storage/pbxt/src/strutil_xt.h b/storage/pbxt/src/strutil_xt.h new file mode 100644 index 00000000000..62067e0b671 --- /dev/null +++ b/storage/pbxt/src/strutil_xt.h @@ -0,0 +1,164 @@ +/* Copyright (c) 2005 PrimeBase Technologies GmbH + * + * PrimeBase XT + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You 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 + * + * 2005-01-03 Paul McCullagh + * + * H&G2JCtL + */ + +#ifndef __xt_strutil_h__ +#define __xt_strutil_h__ + +#include <string.h> + +#include "xt_defs.h" + +#ifdef XT_WIN +#define XT_DIR_CHAR '\\' +#define XT_IS_DIR_CHAR(c) ((c) == '/' || (c) == '\\') +#else +#define XT_DIR_CHAR '/' +#define XT_IS_DIR_CHAR(c) ((c) == '/') +#endif + +#define MAX_INT8_STRING_SIZE 100 + +void xt_strcpy(size_t size, char *to, c_char *from); +void xt_strncpy(size_t size, char *to, c_char *from, size_t len_from); +void xt_strcat(size_t size, char *to, c_char *from); +void xt_strcati(size_t size, char *to, int i); +void xt_strcpy_term(size_t size, char *to, c_char *from, char term); +void xt_strcat_term(size_t size, char *to, c_char *from, char term); + +xtBool xt_ends_with(c_char *str, c_char *sub); +xtBool xt_starts_with(c_char *str, c_char *sub); + +char *xt_last_2_names_of_path(c_char *path); +char *xt_last_name_of_path(c_char *path); +void xt_2nd_last_name_of_path(size_t size, char *dest, c_char *path); +c_char *xt_last_directory_of_path(c_char *path); +xtBool xt_remove_dir_char(char *dir_name); +xtBool xt_add_dir_char(size_t max, char *path); +void xt_remove_last_name_of_path(char *path); +char *xt_find_extension(c_char *file_name); +void xt_remove_extension(char *file_name); +xtBool xt_is_extension(c_char *file_name, c_char *ext); + +xtInt8 xt_str_to_int8(c_char *ptr, xtBool *overflow); +void xt_int8_to_str(xtInt8 value, char *string); +void xt_double_to_str(double value, int scale, char *string); + +xtInt8 xt_byte_size_to_int8(c_char *ptr); +void xt_int8_to_byte_size(xtInt8 value, char *string); + +c_char *xt_get_version(void); + +void xt_strcpy_url(size_t size, char *to, c_char *from); +void xt_strncpy_url(size_t size, char *to, c_char *from, size_t len_from); + +const char *xt_strchr(const char *str, char ch); +unsigned char xt_hex_digit(char ch); + +#define XT_STAT_TIME_CURRENT 0 +#define XT_STAT_TIME_PASSED 1 + +#define XT_STAT_COMMITS 2 +#define XT_STAT_ROLLBACKS 3 +#define XT_STAT_WAIT_FOR_XACT 4 +#define XT_STAT_XACT_TO_CLEAN 5 + +#define XT_STAT_STAT_READS 6 +#define XT_STAT_STAT_WRITES 7 + +#define XT_STAT_REC_BYTES_IN 8 +#define XT_STAT_REC_BYTES_OUT 9 +#define XT_STAT_REC_SYNC_COUNT 10 +#define XT_STAT_REC_SYNC_TIME 11 +#define XT_STAT_REC_CACHE_HIT 12 +#define XT_STAT_REC_CACHE_MISS 13 +#define XT_STAT_REC_CACHE_FREES 14 +#define XT_STAT_REC_CACHE_USAGE 15 + +#define XT_STAT_IND_BYTES_IN 16 +#define XT_STAT_IND_BYTES_OUT 17 +#define XT_STAT_IND_SYNC_COUNT 18 +#define XT_STAT_IND_SYNC_TIME 19 +#define XT_STAT_IND_CACHE_HIT 20 +#define XT_STAT_IND_CACHE_MISS 21 +#define XT_STAT_IND_CACHE_USAGE 22 +#define XT_STAT_ILOG_BYTES_IN 23 +#define XT_STAT_ILOG_BYTES_OUT 24 +#define XT_STAT_ILOG_SYNC_COUNT 25 +#define XT_STAT_ILOG_SYNC_TIME 26 + +#define XT_STAT_XLOG_BYTES_IN 27 +#define XT_STAT_XLOG_BYTES_OUT 28 +#define XT_STAT_XLOG_SYNC_COUNT 29 +#define XT_STAT_XLOG_SYNC_TIME 30 +#define XT_STAT_XLOG_CACHE_HIT 31 +#define XT_STAT_XLOG_CACHE_MISS 32 +#define XT_STAT_XLOG_CACHE_USAGE 33 + +#define XT_STAT_DATA_BYTES_IN 34 +#define XT_STAT_DATA_BYTES_OUT 35 +#define XT_STAT_DATA_SYNC_COUNT 36 +#define XT_STAT_DATA_SYNC_TIME 37 + +#define XT_STAT_BYTES_TO_CHKPNT 38 +#define XT_STAT_LOG_BYTES_TO_WRITE 39 +#define XT_STAT_BYTES_TO_SWEEP 40 +#define XT_STAT_SWEEPER_WAITS 41 + +#define XT_STAT_SCAN_INDEX 42 +#define XT_STAT_SCAN_TABLE 43 +#define XT_STAT_ROW_SELECT 44 +#define XT_STAT_ROW_INSERT 45 +#define XT_STAT_ROW_UPDATE 46 +#define XT_STAT_ROW_DELETE 47 + +#define XT_STAT_CURRENT_MAX 48 + +#define XT_STAT_RETRY_INDEX_SCAN 48 +#define XT_STAT_REREAD_REC_LIST 49 +#define XT_STAT_MAXIMUM 50 + +#define XT_STAT_ACCUMULATIVE 1 +#define XT_STAT_BYTE_COUNT 2 +#define XT_STAT_PERCENTAGE 4 +#define XT_STAT_COMBO_FIELD 8 /* Field is short, 2 chars instead of 5. */ +#define XT_STAT_COMBO_FIELD_2 16 /* Field is short, 2 chars instead of 5. */ +#define XT_STAT_TIME_VALUE 32 +#define XT_STAT_DATE 64 + +typedef struct XTStatMetaData { + int sm_id; + const char *sm_name; + const char *sm_short_line_1; + const char *sm_short_line_2; + int sm_flags; + const char *sm_description; +} XTStatMetaDataRec, *XTStatMetaDataPtr; + +XTStatMetaDataPtr xt_get_stat_meta_data(int i); +void xt_set_time_unit(const char *u); + +#ifdef XT_WIN +void xt_win_dialog(char *message); +#endif + +#endif diff --git a/storage/pbxt/src/systab_xt.cc b/storage/pbxt/src/systab_xt.cc new file mode 100644 index 00000000000..73ecc7a07cb --- /dev/null +++ b/storage/pbxt/src/systab_xt.cc @@ -0,0 +1,656 @@ +/* Copyright (c) 2008 PrimeBase Technologies GmbH, Germany + * + * PrimeBase Media Stream for MySQL + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You 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 + * + * Paul McCullagh + * + * 2007-07-18 + * + * H&G2JCtL + * + * System tables. + * + */ + +#include "xt_config.h" + +#include <stdlib.h> +#include <time.h> +#ifdef DRIZZLED +#include <drizzled/server_includes.h> +#include <drizzled/current_session.h> +#endif + +#include "ha_pbxt.h" +#include "systab_xt.h" +#include "discover_xt.h" +#include "table_xt.h" +#include "strutil_xt.h" +#include "database_xt.h" +#include "trace_xt.h" + +#if MYSQL_VERSION_ID >= 50120 +#define byte uchar +#endif + +/* + * ------------------------------------------------------------------------- + * SYSTEM TABLE DEFINITIONS + */ + +//-------------------------------- +static DT_FIELD_INFO xt_location_info[] = +{ + { "Path", 128, NULL, MYSQL_TYPE_VARCHAR, (CHARSET_INFO *) system_charset_info, 0, "The location of PBXT tables"}, + { "Table_count", 0, NULL, MYSQL_TYPE_LONGLONG, NULL, NOT_NULL_FLAG, "The number of PBXT table in this location"}, + { NULL, 0, NULL, MYSQL_TYPE_STRING, NULL, 0, NULL} +}; + +static DT_FIELD_INFO xt_statistics_info[] = +{ + { "ID", 0, NULL, MYSQL_TYPE_LONG, NULL, NOT_NULL_FLAG, "The ID of the statistic"}, + { "Name", 40, NULL, MYSQL_TYPE_VARCHAR, (CHARSET_INFO *) system_charset_info, 0, "The name of the statistic"}, + { "Value", 0, NULL, MYSQL_TYPE_LONGLONG, NULL, NOT_NULL_FLAG, "The accumulated value"}, + { NULL, 0, NULL, MYSQL_TYPE_STRING, NULL, 0, NULL} +}; + +/* +static DT_FIELD_INFO xt_reference_info[] = +{ + {"Table_name", 128, NULL, MYSQL_TYPE_STRING, system_charset_info, NOT_NULL_FLAG, "The name of the referencing table"}, + {"Blob_id", NULL, NULL, MYSQL_TYPE_LONGLONG, NULL, NOT_NULL_FLAG, "The BLOB reference number - part of the BLOB URL"}, + {"Column_name", 50, NULL, MYSQL_TYPE_STRING, system_charset_info, NOT_NULL_FLAG, "The column name of the referencing field"}, + {"Row_condition", 50, NULL, MYSQL_TYPE_VARCHAR, system_charset_info, 0, "This condition identifies the row in the table"}, + {"Blob_url", 50, NULL, MYSQL_TYPE_VARCHAR, system_charset_info, NOT_NULL_FLAG, "The BLOB URL for HTTP GET access"}, + {"Repository_id", NULL, NULL, MYSQL_TYPE_LONG, NULL, NOT_NULL_FLAG, "The repository file number of the BLOB"}, + {"Repo_blob_offset",NULL, NULL, MYSQL_TYPE_LONGLONG, NULL, NOT_NULL_FLAG, "The offset in the repository file"}, + {"Blob_size", NULL, NULL, MYSQL_TYPE_LONGLONG, NULL, NOT_NULL_FLAG, "The size of the BLOB in bytes"}, + {"Deletion_time", NULL, NULL, MYSQL_TYPE_TIMESTAMP, NULL, 0, "The time the BLOB was deleted"}, + {"Remove_in", NULL, NULL, MYSQL_TYPE_LONG, NULL, 0, "The number of seconds before the reference/BLOB is removed perminently"}, + {"Temp_log_id", NULL, NULL, MYSQL_TYPE_LONG, NULL, 0, "Temporary log number of the referencing deletion entry"}, + {"Temp_log_offset", NULL, NULL, MYSQL_TYPE_LONGLONG, NULL, 0, "Temporary log offset of the referencing deletion entry"}, + {NULL, NULL, NULL, MYSQL_TYPE_STRING, NULL, 0, NULL} +}; +*/ + +#define XT_SYSTAB_INVALID 0 +#define XT_SYSTAB_LOCATION_ID 1 +#define XT_SYSTAB_STATISTICS_ID 2 + +static THR_LOCK sys_location_lock; +static THR_LOCK sys_statistics_lock; +static xtBool sys_lock_inited = FALSE; + +static XTSystemTableShareRec xt_internal_tables[] = +{ + { XT_SYSTAB_LOCATION_ID, "pbxt.location", &sys_location_lock, xt_location_info, NULL, FALSE}, + { XT_SYSTAB_STATISTICS_ID, "pbxt.statistics", &sys_statistics_lock, xt_statistics_info, NULL, FALSE}, + { XT_SYSTAB_INVALID, NULL, NULL, NULL, NULL, FALSE} +}; + + +/* +static int pbms_discover_handler(handlerton *hton, THD* thd, const char *db, const char *name, uchar **frmblob, size_t *frmlen) +{ + int err = 1, i = 0; + MY_STAT stat_info; + + // Check that the database exists! + if ((!db) || ! my_stat(db,&stat_info,MYF(0))) + return err; + + while (pbms_internal_tables[i].name) { + if (!strcasecmp(name, pbms_internal_tables[i].name)) { + err = ms_create_table_frm(hton, thd, db, name, pbms_internal_tables[i].info, pbms_internal_tables[i].keys, frmblob, frmlen); + break; + } + i++; + } + + return err; +} +*/ + +/* + * ------------------------------------------------------------------------- + * MYSQL UTILITIES + */ + +void xt_my_set_notnull_in_record(Field *field, char *record) +{ + if (field->null_ptr) + record[(uint) (field->null_ptr - (uchar *) field->table->record[0])] &= (uchar) ~field->null_bit; +} + +/* + * ------------------------------------------------------------------------- + * OPEN SYSTEM TABLES + */ + +XTOpenSystemTable::XTOpenSystemTable(XTThreadPtr self, XTDatabaseHPtr db, XTSystemTableShare *share, TABLE *table): +XTObject() +{ + ost_share = share; + ost_my_table = table; + ost_db = db; + xt_heap_reference(self, db); +} + +XTOpenSystemTable::~XTOpenSystemTable() +{ + XTSystemTableShare::releaseSystemTable(this); +} + +/* + * ------------------------------------------------------------------------- + * LOCATION TABLE + */ + +XTLocationTable::XTLocationTable(XTThreadPtr self, XTDatabaseHPtr db, XTSystemTableShare *share, TABLE *table): +XTOpenSystemTable(self, db, share, table) +{ +} + +XTLocationTable::~XTLocationTable() +{ + unuse(); +} + +bool XTLocationTable::use() +{ + return true; +} + +bool XTLocationTable::unuse() +{ + return true; +} + + +bool XTLocationTable::seqScanInit() +{ + lt_index = 0; + return true; +} + +bool XTLocationTable::seqScanNext(char *buf, bool *eof) +{ + bool ok = true; + + *eof = false; + + xt_ht_lock(NULL, ost_db->db_tables); + if (lt_index >= xt_sl_get_size(ost_db->db_table_paths)) { + ok = false; + *eof = true; + goto done; + } + loadRow(buf, lt_index); + lt_index++; + + done: + xt_ht_unlock(NULL, ost_db->db_tables); + return ok; +#ifdef xxx + csWord4 last_access; + csWord4 last_ref; + csWord4 creation_time; + csWord4 access_code; + csWord2 cont_type; + size_t ref_size; + csWord2 head_size; + csWord8 blob_size; + uint32 len; + Field *curr_field; + byte *save; + MY_BITMAP *save_write_set; + + last_access = CS_GET_DISK_4(blob->rb_last_access_4); + last_ref = CS_GET_DISK_4(blob->rb_last_ref_4); + creation_time = CS_GET_DISK_4(blob->rb_create_time_4); + cont_type = CS_GET_DISK_2(blob->rb_cont_type_2); + ref_size = CS_GET_DISK_1(blob->rb_ref_size_1); + head_size = CS_GET_DISK_2(blob->rb_head_size_2); + blob_size = CS_GET_DISK_6(blob->rb_blob_size_6); + access_code = CS_GET_DISK_4(blob->rb_auth_code_4); + + /* ASSERT_COLUMN_MARKED_FOR_WRITE is failing when + * I use store()!?? + * But I want to use it! :( + */ + save_write_set = table->write_set; + table->write_set = NULL; + + memset(buf, 0xFF, table->s->null_bytes); + for (Field **field=table->field ; *field ; field++) { + curr_field = *field; + + save = curr_field->ptr; +#if MYSQL_VERSION_ID < 50114 + curr_field->ptr = (byte *) buf + curr_field->offset(); +#else + curr_field->ptr = (byte *) buf + curr_field->offset(curr_field->table->record[0]); +#endif + switch (curr_field->field_name[0]) { + case 'A': + ASSERT(strcmp(curr_field->field_name, "Access_code") == 0); + curr_field->store(access_code, true); + xt_my_set_notnull_in_record(curr_field, buf); + break; + case 'R': + switch (curr_field->field_name[6]) { + case 't': + // Repository_id INT + ASSERT(strcmp(curr_field->field_name, "Repository_id") == 0); + curr_field->store(iRepoFile->myRepo->getRepoID(), true); + xt_my_set_notnull_in_record(curr_field, buf); + break; + case 'l': + // Repo_blob_offset BIGINT + ASSERT(strcmp(curr_field->field_name, "Repo_blob_offset") == 0); + curr_field->store(iRepoOffset, true); + xt_my_set_notnull_in_record(curr_field, buf); + break; + } + break; + case 'B': + switch (curr_field->field_name[5]) { + case 's': + // Blob_size BIGINT + ASSERT(strcmp(curr_field->field_name, "Blob_size") == 0); + curr_field->store(blob_size, true); + xt_my_set_notnull_in_record(curr_field, buf); + break; + case 'd': + // Blob_data LONGBLOB + ASSERT(strcmp(curr_field->field_name, "Blob_data") == 0); + if (blob_size <= 0xFFFFFFF) { + iBlobBuffer->setLength((u_int) blob_size); + len = iRepoFile->read(iBlobBuffer->getBuffer(0), iRepoOffset + head_size, (size_t) blob_size, 0); + ((Field_blob *) curr_field)->set_ptr(len, (byte *) iBlobBuffer->getBuffer(0)); + xt_my_set_notnull_in_record(curr_field, buf); + } + break; + } + break; + case 'H': + // Head_size SMALLINT UNSIGNED + ASSERT(strcmp(curr_field->field_name, "Head_size") == 0); + curr_field->store(head_size, true); + xt_my_set_notnull_in_record(curr_field, buf); + break; + case 'C': + switch (curr_field->field_name[1]) { + case 'r': + // Creation_time TIMESTAMP + ASSERT(strcmp(curr_field->field_name, "Creation_time") == 0); + curr_field->store(ms_my_1970_to_mysql_time(creation_time), true); + xt_my_set_notnull_in_record(curr_field, buf); + break; + case 'o': + // Content_type CHAR(128) + ASSERT(strcmp(curr_field->field_name, "Content_type") == 0); + CSString *cont_type_str = ost_share->mySysDatabase->getContentType(cont_type); + if (cont_type_str) { + curr_field->store(cont_type_str->getCString(), cont_type_str->length(), &my_charset_utf8_general_ci); + cont_type_str->release(); + xt_my_set_notnull_in_record(curr_field, buf); + } + break; + } + break; + case 'L': + switch (curr_field->field_name[5]) { + case 'r': + // Last_ref_time TIMESTAMP + ASSERT(strcmp(curr_field->field_name, "Last_ref_time") == 0); + curr_field->store(ms_my_1970_to_mysql_time(last_ref), true); + xt_my_set_notnull_in_record(curr_field, buf); + break; + case 'a': + // Last_access_time TIMESTAMP + ASSERT(strcmp(curr_field->field_name, "Last_access_time") == 0); + curr_field->store(ms_my_1970_to_mysql_time(last_access), true); + xt_my_set_notnull_in_record(curr_field, buf); + break; + } + break; + } + curr_field->ptr = save; + } + + table->write_set = save_write_set; + return true; +#endif + return false; +} + +void XTLocationTable::loadRow(char *buf, xtWord4 row_id) +{ + TABLE *table = ost_my_table; + Field *curr_field; + XTTablePathPtr tp_ptr; + byte *save; + MY_BITMAP *save_write_set; + + /* ASSERT_COLUMN_MARKED_FOR_WRITE is failing when + * I use store()!?? + * But I want to use it! :( + */ + save_write_set = table->write_set; + table->write_set = NULL; + + memset(buf, 0xFF, table->s->null_bytes); + + tp_ptr = *((XTTablePathPtr *) xt_sl_item_at(ost_db->db_table_paths, row_id)); + + for (Field **field=table->field ; *field ; field++) { + curr_field = *field; + + save = curr_field->ptr; +#if MYSQL_VERSION_ID < 50114 + curr_field->ptr = (byte *) buf + curr_field->offset(); +#else + curr_field->ptr = (byte *) buf + curr_field->offset(curr_field->table->record[0]); +#endif + switch (curr_field->field_name[0]) { + case 'P': + // Path VARCHAR(128) + ASSERT_NS(strcmp(curr_field->field_name, "Path") == 0); + curr_field->store(tp_ptr->tp_path, strlen(tp_ptr->tp_path), &my_charset_utf8_general_ci); + xt_my_set_notnull_in_record(curr_field, buf); + break; + case 'T': + // Table_count INT + ASSERT_NS(strcmp(curr_field->field_name, "Table_count") == 0); + curr_field->store(tp_ptr->tp_tab_count, true); + xt_my_set_notnull_in_record(curr_field, buf); + break; + } + curr_field->ptr = save; + } + table->write_set = save_write_set; +} + +xtWord4 XTLocationTable::seqScanPos(xtWord1 *buf __attribute__((unused))) +{ + return lt_index-1; +} + +bool XTLocationTable::seqScanRead(xtWord4 rec_id, char *buf) +{ + loadRow(buf, rec_id); + return true; +} + +/* + * ------------------------------------------------------------------------- + * STATISTICS TABLE + */ + +XTStatisticsTable::XTStatisticsTable(XTThreadPtr self, XTDatabaseHPtr db, XTSystemTableShare *share, TABLE *table): +XTOpenSystemTable(self, db, share, table) +{ +} + +XTStatisticsTable::~XTStatisticsTable() +{ + unuse(); +} + +bool XTStatisticsTable::use() +{ + return true; +} + +bool XTStatisticsTable::unuse() +{ + return true; +} + + +bool XTStatisticsTable::seqScanInit() +{ + tt_index = 0; + xt_gather_statistics(&tt_statistics); + return true; +} + +bool XTStatisticsTable::seqScanNext(char *buf, bool *eof) +{ + bool ok = true; + + *eof = false; + + if (tt_index >= XT_STAT_CURRENT_MAX) { + ok = false; + *eof = true; + goto done; + } + loadRow(buf, tt_index); + tt_index++; + + done: + return ok; +} + +void XTStatisticsTable::loadRow(char *buf, xtWord4 rec_id) +{ + TABLE *table = ost_my_table; + MY_BITMAP *save_write_set; + Field *curr_field; + byte *save; + const char *stat_name; + u_llong stat_value; + + /* ASSERT_COLUMN_MARKED_FOR_WRITE is failing when + * I use store()!?? + * But I want to use it! :( + */ + save_write_set = table->write_set; + table->write_set = NULL; + + memset(buf, 0xFF, table->s->null_bytes); + + stat_name = xt_get_stat_meta_data(rec_id)->sm_name; + stat_value = xt_get_statistic(&tt_statistics, ost_db, rec_id); + + for (Field **field=table->field ; *field ; field++) { + curr_field = *field; + + save = curr_field->ptr; +#if MYSQL_VERSION_ID < 50114 + curr_field->ptr = (byte *) buf + curr_field->offset(); +#else + curr_field->ptr = (byte *) buf + curr_field->offset(curr_field->table->record[0]); +#endif + switch (curr_field->field_name[0]) { + case 'I': + // Value BIGINT + ASSERT_NS(strcmp(curr_field->field_name, "ID") == 0); + curr_field->store(rec_id+1, true); + xt_my_set_notnull_in_record(curr_field, buf); + break; + case 'N': + // Name VARCHAR(40) + ASSERT_NS(strcmp(curr_field->field_name, "Name") == 0); + curr_field->store(stat_name, strlen(stat_name), &my_charset_utf8_general_ci); + xt_my_set_notnull_in_record(curr_field, buf); + break; + case 'V': + // Value BIGINT + ASSERT_NS(strcmp(curr_field->field_name, "Value") == 0); + curr_field->store(stat_value, true); + xt_my_set_notnull_in_record(curr_field, buf); + break; + } + curr_field->ptr = save; + } + table->write_set = save_write_set; +} + +xtWord4 XTStatisticsTable::seqScanPos(xtWord1 *buf __attribute__((unused))) +{ + return tt_index-1; +} + +bool XTStatisticsTable::seqScanRead(xtWord4 rec_id, char *buf) +{ + loadRow(buf, rec_id); + return true; +} + +/* + * ------------------------------------------------------------------------- + * SYSTEM TABLE SHARES + */ + +void st_path_to_table_name(size_t size, char *buffer, const char *path) +{ + char *str; + + xt_strcpy(size, buffer, xt_last_2_names_of_path(path)); + xt_remove_extension(buffer); + if ((str = strchr(buffer, '\\'))) + *str = '.'; + if ((str = strchr(buffer, '/'))) + *str = '.'; +} + +void XTSystemTableShare::startUp(XTThreadPtr self __attribute__((unused))) +{ + thr_lock_init(&sys_location_lock); + thr_lock_init(&sys_statistics_lock); + sys_lock_inited = TRUE; +} + +void XTSystemTableShare::shutDown(XTThreadPtr self __attribute__((unused))) +{ + if (sys_lock_inited) { + thr_lock_delete(&sys_location_lock); + thr_lock_delete(&sys_statistics_lock); + sys_lock_inited = FALSE; + } +} + +bool XTSystemTableShare::isSystemTable(const char *table_path) +{ + int i = 0; + char tab_name[100]; + + st_path_to_table_name(100, tab_name, table_path); + while (xt_internal_tables[i].sts_path) { + if (strcasecmp(tab_name, xt_internal_tables[i].sts_path) == 0) + return true; + i++; + } + return false; +} + +void XTSystemTableShare::setSystemTableDeleted(const char *table_path) +{ + int i = 0; + char tab_name[100]; + + st_path_to_table_name(100, tab_name, table_path); + while (xt_internal_tables[i].sts_path) { + if (strcasecmp(tab_name, xt_internal_tables[i].sts_path) == 0) { + xt_internal_tables[i].sts_exists = FALSE; + break; + } + i++; + } +} + +bool XTSystemTableShare::doesSystemTableExist() +{ + int i = 0; + + while (xt_internal_tables[i].sts_path) { + if (xt_internal_tables[i].sts_exists) + return true; + i++; + } + return false; +} + +void XTSystemTableShare::createSystemTables(XTThreadPtr self __attribute__((unused)), XTDatabaseHPtr db __attribute__((unused))) +{ + int i = 0; + + while (xt_internal_tables[i].sts_path) { + if (!xt_create_table_frm(pbxt_hton, + current_thd, "pbxt", + strchr(xt_internal_tables[i].sts_path, '.') + 1, + xt_internal_tables[i].sts_info, + xt_internal_tables[i].sts_keys, + TRUE /*do not recreate*/)) + xt_internal_tables[i].sts_exists = TRUE; + i++; + } +} + +XTOpenSystemTable *XTSystemTableShare::openSystemTable(XTThreadPtr self, const char *table_path, TABLE *table) +{ + XTSystemTableShare *share; + XTOpenSystemTable *otab = NULL; + int i = 0; + char tab_name[100]; + + st_path_to_table_name(100, tab_name, table_path); + while (xt_internal_tables[i].sts_path) { + if (strcasecmp(tab_name, xt_internal_tables[i].sts_path) == 0) { + share = &xt_internal_tables[i]; + goto found; + } + i++; + } + return NULL; + + found: + share->sts_exists = TRUE; + switch (share->sts_id) { + case XT_SYSTAB_LOCATION_ID: + if (!(otab = new XTLocationTable(self, self->st_database, share, table))) + xt_throw_errno(XT_CONTEXT, XT_ENOMEM); + break; + case XT_SYSTAB_STATISTICS_ID: + if (!(otab = new XTStatisticsTable(self, self->st_database, share, table))) + xt_throw_errno(XT_CONTEXT, XT_ENOMEM); + break; + default: + xt_throw_taberr(XT_CONTEXT, XT_ERR_TABLE_NOT_FOUND, (XTPathStrPtr) table_path); + break; + } + + return otab; +} + +void XTSystemTableShare::releaseSystemTable(XTOpenSystemTable *tab) +{ + if (tab->ost_db) { + XTThreadPtr self = xt_get_self(); + + try_(a) { + xt_heap_release(self, tab->ost_db); + } + catch_(a) { + } + cont_(a); + tab->ost_db = NULL; + } +} diff --git a/storage/pbxt/src/systab_xt.h b/storage/pbxt/src/systab_xt.h new file mode 100644 index 00000000000..e64bcc816f4 --- /dev/null +++ b/storage/pbxt/src/systab_xt.h @@ -0,0 +1,155 @@ +/* Copyright (c) 2008 PrimeBase Technologies GmbH, Germany + * + * PrimeBase XT + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You 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 + * + * Paul McCullagh + * + * 2007-07-18 + * + * H&G2JCtL + * + * PBXT System tables. + * + */ + +/* + +DROP TABLE IF EXISTS pbms_repository; +CREATE TABLE pbms_repository ( + Repository_id INT COMMENT 'The reppository file number', + Repo_blob_offset BIGINT COMMENT 'The offset of the BLOB in the repository file', + Blob_size BIGINT COMMENT 'The size of the BLOB in bytes', + Head_size SMALLINT UNSIGNED COMMENT 'The size of the BLOB header - preceeds the BLOB data', + Access_code INT COMMENT 'The 4-byte authorisation code required to access the BLOB - part of the BLOB URL', + Creation_time TIMESTAMP COMMENT 'The time the BLOB was created', + Last_ref_time TIMESTAMP COMMENT 'The last time the BLOB was referenced', + Last_access_time TIMESTAMP COMMENT 'The last time the BLOB was accessed (read)', + Content_type CHAR(128) COMMENT 'The content type of the BLOB - returned by HTTP GET calls', + Blob_data LONGBLOB COMMENT 'The data of this BLOB' +) ENGINE=PBMS; + + PRIMARY KEY (Repository_id, Repo_blob_offset) + +DROP TABLE IF EXISTS pbms_reference; +CREATE TABLE pbms_reference ( + Table_name CHAR(64) COMMENT 'The name of the referencing table', + Blob_id BIGINT COMMENT 'The BLOB reference number - part of the BLOB URL', + Column_name CHAR(64) COMMENT 'The column name of the referencing field', + Row_condition VARCHAR(255) COMMENT 'This condition identifies the row in the table', + Blob_url VARCHAR(200) COMMENT 'The BLOB URL for HTTP GET access', + Repository_id INT COMMENT 'The repository file number of the BLOB', + Repo_blob_offset BIGINT COMMENT 'The offset in the repository file', + Blob_size BIGINT COMMENT 'The size of the BLOB in bytes', + Deletion_time TIMESTAMP COMMENT 'The time the BLOB was deleted', + Remove_in INT COMMENT 'The number of seconds before the reference/BLOB is removed perminently', + Temp_log_id INT COMMENT 'Temporary log number of the referencing deletion entry', + Temp_log_offset BIGINT COMMENT 'Temporary log offset of the referencing deletion entry' +) ENGINE=PBMS; + + PRIMARY KEY (Table_name, Blob_id, Column_name, Condition) +*/ + +#ifndef __SYSTAB_XT_H__ +#define __SYSTAB_XT_H__ + +#include "ccutils_xt.h" +#include "discover_xt.h" +#include "thread_xt.h" + +struct XTSystemTableShare; +struct XTDatabase; + +class XTOpenSystemTable : public XTObject { +public: + XTSystemTableShare *ost_share; + TABLE *ost_my_table; + struct XTDatabase *ost_db; + + XTOpenSystemTable(XTThreadPtr self, struct XTDatabase *db, XTSystemTableShare *share, TABLE *table); + virtual ~XTOpenSystemTable(); + + virtual bool use() { return true; } + virtual bool unuse() { return true; } + virtual bool seqScanInit() { return true; } + virtual bool seqScanNext(char *buf __attribute__((unused)), bool *eof) { + *eof = true; + return false; + } + virtual int getRefLen() { return 4; } + virtual xtWord4 seqScanPos(xtWord1 *buf __attribute__((unused))) { + return 0; + } + virtual bool seqScanRead(xtWord4 rec_id __attribute__((unused)), char *buf __attribute__((unused))) { + return true; + } + +private: +}; + +class XTLocationTable : public XTOpenSystemTable { + u_int lt_index; + +public: + XTLocationTable(XTThreadPtr self, struct XTDatabase *db, XTSystemTableShare *share, TABLE *table); + virtual ~XTLocationTable(); + + virtual bool use(); + virtual bool unuse(); + virtual bool seqScanInit(); + virtual bool seqScanNext(char *buf, bool *eof); + virtual void loadRow(char *buf, xtWord4 row_id); + virtual xtWord4 seqScanPos(xtWord1 *buf); + virtual bool seqScanRead(xtWord4 rec_id, char *buf); +}; + +class XTStatisticsTable : public XTOpenSystemTable { + u_int tt_index; + XTStatisticsRec tt_statistics; + +public: + XTStatisticsTable(XTThreadPtr self, struct XTDatabase *db, XTSystemTableShare *share, TABLE *table); + virtual ~XTStatisticsTable(); + + virtual bool use(); + virtual bool unuse(); + virtual bool seqScanInit(); + virtual bool seqScanNext(char *buf, bool *eof); + virtual void loadRow(char *buf, xtWord4 row_id); + virtual xtWord4 seqScanPos(xtWord1 *buf); + virtual bool seqScanRead(xtWord4 rec_id, char *buf); +}; + +typedef struct XTSystemTableShare { + u_int sts_id; + const char *sts_path; + THR_LOCK *sts_my_lock; + DT_FIELD_INFO *sts_info; + DT_KEY_INFO *sts_keys; + xtBool sts_exists; + + static void startUp(XTThreadPtr self); + static void shutDown(XTThreadPtr self); + + static bool isSystemTable(const char *table_path); + static void setSystemTableDeleted(const char *table_path); + static bool doesSystemTableExist(); + static void createSystemTables(XTThreadPtr self, struct XTDatabase *db); + static XTOpenSystemTable *openSystemTable(XTThreadPtr self, const char *table_path, TABLE *table); + static void releaseSystemTable(XTOpenSystemTable *tab); +} XTSystemTableShareRec, *XTSystemTableSharePtr; + +#endif diff --git a/storage/pbxt/src/tabcache_xt.cc b/storage/pbxt/src/tabcache_xt.cc new file mode 100644 index 00000000000..b9f9ccd37e1 --- /dev/null +++ b/storage/pbxt/src/tabcache_xt.cc @@ -0,0 +1,1254 @@ +/* Copyright (c) 2007 PrimeBase Technologies GmbH + * + * PrimeBase XT + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You 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 + * + * 2007-10-30 Paul McCullagh + * + * H&G2JCtL + * + * The new table cache. Caches all non-index data. This includes the data + * files and the row pointer files. + */ + +#include "xt_config.h" + +#include <signal.h> + +#include "pthread_xt.h" +#include "tabcache_xt.h" +#include "table_xt.h" +#include "database_xt.h" +#include "trace_xt.h" +#include "myxt_xt.h" + +xtPublic XTTabCacheMemRec xt_tab_cache; + +static void tabc_fr_wait_for_cache(XTThreadPtr self, u_int msecs); + +xtPublic void xt_tc_set_cache_size(size_t cache_size) +{ + xt_tab_cache.tcm_cache_size = cache_size; + xt_tab_cache.tcm_low_level = cache_size / 4 * 3; // Current 75% + xt_tab_cache.tcm_high_level = cache_size / 100 * 95; // Current 95% +} + +/* + * Initialize the disk cache. + */ +xtPublic void xt_tc_init(XTThreadPtr self, size_t cache_size) +{ + xt_tc_set_cache_size(cache_size); + + xt_tab_cache.tcm_approx_page_count = cache_size / sizeof(XTTabCachePageRec); + /* Determine the size of the hash table. + * The size is set to 2* the number of pages! + */ + xt_tab_cache.tcm_hash_size = (xt_tab_cache.tcm_approx_page_count * 2) / XT_TC_SEGMENT_COUNT; + + try_(a) { + for (u_int i=0; i<XT_TC_SEGMENT_COUNT; i++) { + xt_tab_cache.tcm_segment[i].tcs_cache_in_use = 0; + xt_tab_cache.tcm_segment[i].tcs_hash_table = (XTTabCachePagePtr *) xt_calloc(self, xt_tab_cache.tcm_hash_size * sizeof(XTTabCachePagePtr)); + xt_rwmutex_init_with_autoname(self, &xt_tab_cache.tcm_segment[i].tcs_lock); + } + + xt_init_mutex_with_autoname(self, &xt_tab_cache.tcm_lock); + xt_init_cond(self, &xt_tab_cache.tcm_cond); + xt_init_mutex_with_autoname(self, &xt_tab_cache.tcm_freeer_lock); + xt_init_cond(self, &xt_tab_cache.tcm_freeer_cond); + } + catch_(a) { + xt_tc_exit(self); + throw_(); + } + cont_(a); +} + +xtPublic void xt_tc_exit(XTThreadPtr self) +{ + for (u_int i=0; i<XT_TC_SEGMENT_COUNT; i++) { + if (xt_tab_cache.tcm_segment[i].tcs_hash_table) { + if (xt_tab_cache.tcm_segment[i].tcs_cache_in_use) { + XTTabCachePagePtr page, tmp_page; + + for (size_t j=0; j<xt_tab_cache.tcm_hash_size; j++) { + page = xt_tab_cache.tcm_segment[i].tcs_hash_table[j]; + while (page) { + tmp_page = page; + page = page->tcp_next; + xt_free(self, tmp_page); + } + } + } + + xt_free(self, xt_tab_cache.tcm_segment[i].tcs_hash_table); + xt_tab_cache.tcm_segment[i].tcs_hash_table = NULL; + xt_rwmutex_free(self, &xt_tab_cache.tcm_segment[i].tcs_lock); + } + } + + xt_free_mutex(&xt_tab_cache.tcm_lock); + xt_free_cond(&xt_tab_cache.tcm_cond); + xt_free_mutex(&xt_tab_cache.tcm_freeer_lock); + xt_free_cond(&xt_tab_cache.tcm_freeer_cond); +} + +xtPublic xtInt8 xt_tc_get_usage() +{ + xtInt8 size = 0; + + for (u_int i=0; i<XT_TC_SEGMENT_COUNT; i++) { + size += xt_tab_cache.tcm_segment[i].tcs_cache_in_use; + } + return size; +} + +xtPublic xtInt8 xt_tc_get_size() +{ + return (xtInt8) xt_tab_cache.tcm_cache_size; +} + +xtPublic xtInt8 xt_tc_get_high() +{ + return (xtInt8) xt_tab_cache.tcm_cache_high; +} + +#ifdef DEBUG +xtPublic void xt_check_table_cache(XTTableHPtr tab) +{ + XTTabCachePagePtr page, ppage; + + xt_lock_mutex_ns(&xt_tab_cache.tcm_lock); + ppage = NULL; + page = xt_tab_cache.tcm_lru_page; + while (page) { + if (tab) { + if (page->tcp_db_id == tab->tab_db->db_id && page->tcp_tab_id == tab->tab_id) { + ASSERT_NS(!XTTableSeq::xt_op_is_before(tab->tab_seq.ts_next_seq, page->tcp_op_seq)); + } + } + ASSERT_NS(page->tcp_lr_used == ppage); + ppage = page; + page = page->tcp_mr_used; + } + ASSERT_NS(xt_tab_cache.tcm_mru_page == ppage); + xt_unlock_mutex_ns(&xt_tab_cache.tcm_lock); +} +#endif + +void XTTabCache::xt_tc_setup(XTTableHPtr tab, size_t head_size, size_t rec_size) +{ + tci_table = tab; + tci_header_size = head_size; + tci_rec_size = rec_size; + tci_rows_per_page = (XT_TC_PAGE_SIZE / rec_size) + 1; + if (tci_rows_per_page < 2) + tci_rows_per_page = 2; + tci_page_size = tci_rows_per_page * rec_size; +} + +/* + * This function assumes that we never write past the boundary of a page. + * This should be the case, because we should never write more than + * a row, and there are only whole rows on a page. + */ +xtBool XTTabCache::xt_tc_write(XT_ROW_REC_FILE_PTR file, xtRefID ref_id, size_t inc, size_t size, xtWord1 *data, xtOpSeqNo *op_seq, xtBool read, XTThreadPtr thread) +{ + size_t offset; + XTTabCachePagePtr page; + XTTabCacheSegPtr seg; + + /* + retry: + */ + if (!tc_fetch(file, ref_id, &seg, &page, &offset, read, thread)) + return FAILED; + /* Don't write while there is a read lock on the page, + * which can happen during a sequential scan... + * + * This will have to be OK. + * I cannot wait for the lock because a thread locks + * itself out when updating during a sequential scan. + * + * However, I don't think this is a problem, because + * the only records that are changed, are records + * containing uncommitted data. Such records should + * be ignored by a sequential scan. As long as + * we don't crash due to reading half written + * data! + * + if (page->tcp_lock_count) { + if (!xt_timed_wait_cond_ns(&seg->tcs_cond, &seg->tcs_lock, 100)) { + xt_rwmutex_unlock(&seg->tcs_lock, thread->t_id); + return FAILED; + } + xt_rwmutex_unlock(&seg->tcs_lock, thread->t_id); + // The page may have dissappeared from the cache, while we were sleeping! + goto retry; + } + */ + + ASSERT_NS(offset + inc + 4 <= tci_page_size); + memcpy(page->tcp_data + offset + inc, data, size); + /* GOTCHA, this was "op_seq > page->tcp_op_seq", however + * this does not handle overflow! + if (XTTableSeq::xt_op_is_before(page->tcp_op_seq, op_seq)) + page->tcp_op_seq = op_seq; + */ + + page->tcp_dirty = TRUE; + ASSERT_NS(page->tcp_db_id == tci_table->tab_db->db_id && page->tcp_tab_id == tci_table->tab_id); + *op_seq = tci_table->tab_seq.ts_set_op_seq(page); + xt_rwmutex_unlock(&seg->tcs_lock, thread->t_id); + return OK; +} + +/* + * This is a special version of write which is used to set the "clean" bit. + * The alternative would be to read the record first, but this + * is much quicker! + * + * This function also checks if xn_id, row_id and other data match (the checks + * are similar to xn_sw_cleanup_done) before modifying the record, otherwise it + * assumes that the record was already updated earlier and we must not set it to + * clean. + * + * If the record was not modified the function returns FALSE. + * + * The function has a self pointer and can throw an exception. + */ +xtBool XTTabCache::xt_tc_write_cond(XTThreadPtr self, XT_ROW_REC_FILE_PTR file, xtRefID ref_id, xtWord1 new_type, xtOpSeqNo *op_seq, + xtXactID xn_id, xtRowID row_id, u_int stat_id, u_int rec_type) +{ + size_t offset; + XTTabCachePagePtr page; + XTTabCacheSegPtr seg; + XTTabRecHeadDPtr rec_head; + + if (!tc_fetch(file, ref_id, &seg, &page, &offset, TRUE, self)) + xt_throw(self); + + ASSERT(offset + 1 <= tci_page_size); + + rec_head = (XTTabRecHeadDPtr)(page->tcp_data + offset); + + /* Transaction must match: */ + if (XT_GET_DISK_4(rec_head->tr_xact_id_4) != xn_id) + goto no_change; + + /* Record header must match expected value from + * log or clean has been done, or is not required. + * + * For example, it is not required if a record + * has been overwritten in a transaction. + */ + if (rec_head->tr_rec_type_1 != rec_type || + rec_head->tr_stat_id_1 != stat_id) + goto no_change; + + /* Row must match: */ + if (XT_GET_DISK_4(rec_head->tr_row_id_4) != row_id) + goto no_change; + + *(page->tcp_data + offset) = new_type; + + page->tcp_dirty = TRUE; + ASSERT(page->tcp_db_id == tci_table->tab_db->db_id && page->tcp_tab_id == tci_table->tab_id); + *op_seq = tci_table->tab_seq.ts_set_op_seq(page); + xt_rwmutex_unlock(&seg->tcs_lock, self->t_id); + return TRUE; + + no_change: + xt_rwmutex_unlock(&seg->tcs_lock, self->t_id); + return FALSE; +} + +xtBool XTTabCache::xt_tc_read(XT_ROW_REC_FILE_PTR file, xtRefID ref_id, size_t size, xtWord1 *data, XTThreadPtr thread) +{ + return tc_read_direct(file, ref_id, size, data, thread); +} + +xtBool XTTabCache::xt_tc_read_4(XT_ROW_REC_FILE_PTR file, xtRefID ref_id, xtWord4 *value, XTThreadPtr thread) +{ + register u_int page_idx; + register XTTabCachePagePtr page; + register XTTabCacheSegPtr seg; + register u_int hash_idx; + register XTTabCacheMemPtr dcg = &xt_tab_cache; + off_t address; + + ASSERT_NS(ref_id); + ref_id--; + page_idx = ref_id / this->tci_rows_per_page; + address = (off_t) ref_id * (off_t) this->tci_rec_size + (off_t) this->tci_header_size; + + hash_idx = page_idx + (file->fr_id * 223); + seg = &dcg->tcm_segment[hash_idx & XT_TC_SEGMENT_MASK]; + hash_idx = (hash_idx >> XT_TC_SEGMENT_SHIFTS) % dcg->tcm_hash_size; + + xt_rwmutex_slock(&seg->tcs_lock, thread->t_id); + page = seg->tcs_hash_table[hash_idx]; + while (page) { + if (page->tcp_page_idx == page_idx && page->tcp_file_id == file->fr_id) { + size_t offset; + xtWord1 *buffer; + + offset = (ref_id % this->tci_rows_per_page) * this->tci_rec_size; + ASSERT_NS(offset + 4 <= this->tci_page_size); + buffer = page->tcp_data + offset; + *value = XT_GET_DISK_4(buffer); + xt_rwmutex_unlock(&seg->tcs_lock, thread->t_id); + return OK; + } + page = page->tcp_next; + } + xt_rwmutex_unlock(&seg->tcs_lock, thread->t_id); + +#ifdef XT_USE_ROW_REC_MMAP_FILES + return xt_pread_fmap_4(file, address, value, &thread->st_statistics.st_rec, thread); +#else + xtWord1 data[4]; + + if (!XT_PREAD_RR_FILE(file, address, 4, 4, data, NULL, &thread->st_statistics.st_rec, thread)) + return FAILED; + *value = XT_GET_DISK_4(data); + return OK; +#endif +} + +xtBool XTTabCache::xt_tc_get_page(XT_ROW_REC_FILE_PTR file, xtRefID ref_id, XTTabCachePagePtr *ret_page, size_t *offset, XTThreadPtr thread) +{ + XTTabCachePagePtr page; + XTTabCacheSegPtr seg; + +#ifdef XT_SEQ_SCAN_FROM_MEMORY + if (!tc_fetch_direct(file, ref_id, &seg, &page, offset, thread)) + return FAILED; + if (!seg) { + *ret_page = NULL; + return OK; + } +#else + if (!tc_fetch(file, ref_id, &seg, &page, offset, TRUE, thread)) + return FAILED; +#endif + page->tcp_lock_count++; + xt_rwmutex_unlock(&seg->tcs_lock, thread->t_id); + *ret_page = page; + return OK; +} + +void XTTabCache::xt_tc_release_page(XT_ROW_REC_FILE_PTR file __attribute__((unused)), XTTabCachePagePtr page, XTThreadPtr thread) +{ + XTTabCacheSegPtr seg; + + seg = &xt_tab_cache.tcm_segment[page->tcp_seg]; + xt_rwmutex_xlock(&seg->tcs_lock, thread->t_id); + +#ifdef DEBUG + XTTabCachePagePtr lpage, ppage; + + ppage = NULL; + lpage = seg->tcs_hash_table[page->tcp_hash_idx]; + while (lpage) { + if (lpage->tcp_page_idx == page->tcp_page_idx && + lpage->tcp_file_id == page->tcp_file_id) + break; + ppage = lpage; + lpage = lpage->tcp_next; + } + + ASSERT_NS(page == lpage); + ASSERT_NS(page->tcp_lock_count > 0); +#endif + + if (page->tcp_lock_count > 0) + page->tcp_lock_count--; + + xt_rwmutex_unlock(&seg->tcs_lock, thread->t_id); +} + +xtBool XTTabCache::xt_tc_read_page(XT_ROW_REC_FILE_PTR file, xtRefID ref_id, xtWord1 *data, XTThreadPtr thread) +{ + return tc_read_direct(file, ref_id, this->tci_page_size, data, thread); +} + +/* Read row and record files directly. + * This by-passed the cache when reading, which mean + * we rely in the OS for caching. + * This probably only makes sense when these files + * are memory mapped. + */ +xtBool XTTabCache::tc_read_direct(XT_ROW_REC_FILE_PTR file, xtRefID ref_id, size_t size, xtWord1 *data, XTThreadPtr thread) +{ + register u_int page_idx; + register XTTabCachePagePtr page; + register XTTabCacheSegPtr seg; + register u_int hash_idx; + register XTTabCacheMemPtr dcg = &xt_tab_cache; + size_t red_size; + off_t address; + + ASSERT_NS(ref_id); + ref_id--; + page_idx = ref_id / this->tci_rows_per_page; + address = (off_t) ref_id * (off_t) this->tci_rec_size + (off_t) this->tci_header_size; + + hash_idx = page_idx + (file->fr_id * 223); + seg = &dcg->tcm_segment[hash_idx & XT_TC_SEGMENT_MASK]; + hash_idx = (hash_idx >> XT_TC_SEGMENT_SHIFTS) % dcg->tcm_hash_size; + + xt_rwmutex_slock(&seg->tcs_lock, thread->t_id); + page = seg->tcs_hash_table[hash_idx]; + while (page) { + if (page->tcp_page_idx == page_idx && page->tcp_file_id == file->fr_id) { + size_t offset; + + offset = (ref_id % this->tci_rows_per_page) * this->tci_rec_size; + ASSERT_NS(offset + size <= this->tci_page_size); + memcpy(data, page->tcp_data + offset, size); + xt_rwmutex_unlock(&seg->tcs_lock, thread->t_id); + return OK; + } + page = page->tcp_next; + } + xt_rwmutex_unlock(&seg->tcs_lock, thread->t_id); + if (!XT_PREAD_RR_FILE(file, address, size, 0, data, &red_size, &thread->st_statistics.st_rec, thread)) + return FAILED; + memset(data + red_size, 0, size - red_size); + return OK; +} + +xtBool XTTabCache::tc_fetch_direct(XT_ROW_REC_FILE_PTR file, xtRefID ref_id, XTTabCacheSegPtr *ret_seg, XTTabCachePagePtr *ret_page, size_t *offset, XTThreadPtr thread) +{ + register u_int page_idx; + register XTTabCachePagePtr page; + register XTTabCacheSegPtr seg; + register u_int hash_idx; + register XTTabCacheMemPtr dcg = &xt_tab_cache; + + ASSERT_NS(ref_id); + ref_id--; + page_idx = ref_id / this->tci_rows_per_page; + *offset = (ref_id % this->tci_rows_per_page) * this->tci_rec_size; + + hash_idx = page_idx + (file->fr_id * 223); + seg = &dcg->tcm_segment[hash_idx & XT_TC_SEGMENT_MASK]; + hash_idx = (hash_idx >> XT_TC_SEGMENT_SHIFTS) % dcg->tcm_hash_size; + + xt_rwmutex_xlock(&seg->tcs_lock, thread->t_id); + page = seg->tcs_hash_table[hash_idx]; + while (page) { + if (page->tcp_page_idx == page_idx && page->tcp_file_id == file->fr_id) { + *ret_seg = seg; + *ret_page = page; + return OK; + } + page = page->tcp_next; + } + xt_rwmutex_unlock(&seg->tcs_lock, thread->t_id); + *ret_seg = NULL; + *ret_page = NULL; + return OK; +} + +/* + * Note, this function may return an exclusive, or a shared lock. + * If the page is in cache it will return a shared lock of the segment. + * If the page was just added to the cache it will return an + * exclusive lock. + */ +xtBool XTTabCache::tc_fetch(XT_ROW_REC_FILE_PTR file, xtRefID ref_id, XTTabCacheSegPtr *ret_seg, XTTabCachePagePtr *ret_page, size_t *offset, xtBool read, XTThreadPtr thread) +{ + register u_int page_idx; + register XTTabCachePagePtr page, new_page; + register XTTabCacheSegPtr seg; + register u_int hash_idx; + register XTTabCacheMemPtr dcg = &xt_tab_cache; + size_t red_size; + off_t address; + + ASSERT_NS(ref_id); + ref_id--; + page_idx = ref_id / this->tci_rows_per_page; + address = (off_t) page_idx * (off_t) this->tci_page_size + (off_t) this->tci_header_size; + *offset = (ref_id % this->tci_rows_per_page) * this->tci_rec_size; + + hash_idx = page_idx + (file->fr_id * 223); + seg = &dcg->tcm_segment[hash_idx & XT_TC_SEGMENT_MASK]; + hash_idx = (hash_idx >> XT_TC_SEGMENT_SHIFTS) % dcg->tcm_hash_size; + + xt_rwmutex_slock(&seg->tcs_lock, thread->t_id); + page = seg->tcs_hash_table[hash_idx]; + while (page) { + if (page->tcp_page_idx == page_idx && page->tcp_file_id == file->fr_id) { + /* This page has been most recently used: */ + if (XT_TIME_DIFF(page->tcp_ru_time, dcg->tcm_ru_now) > (dcg->tcm_approx_page_count >> 1)) { + /* Move to the front of the MRU list: */ + xt_lock_mutex_ns(&dcg->tcm_lock); + + page->tcp_ru_time = ++dcg->tcm_ru_now; + if (dcg->tcm_mru_page != page) { + /* Remove from the MRU list: */ + if (dcg->tcm_lru_page == page) + dcg->tcm_lru_page = page->tcp_mr_used; + if (page->tcp_lr_used) + page->tcp_lr_used->tcp_mr_used = page->tcp_mr_used; + if (page->tcp_mr_used) + page->tcp_mr_used->tcp_lr_used = page->tcp_lr_used; + + /* Make the page the most recently used: */ + if ((page->tcp_lr_used = dcg->tcm_mru_page)) + dcg->tcm_mru_page->tcp_mr_used = page; + page->tcp_mr_used = NULL; + dcg->tcm_mru_page = page; + if (!dcg->tcm_lru_page) + dcg->tcm_lru_page = page; + } + xt_unlock_mutex_ns(&dcg->tcm_lock); + } + *ret_seg = seg; + *ret_page = page; + thread->st_statistics.st_rec_cache_hit++; + return OK; + } + page = page->tcp_next; + } + xt_rwmutex_unlock(&seg->tcs_lock, thread->t_id); + + /* Page not found, allocate a new page: */ + size_t page_size = offsetof(XTTabCachePageRec, tcp_data) + this->tci_page_size; + if (!(new_page = (XTTabCachePagePtr) xt_malloc_ns(page_size))) + return FAILED; + /* Increment cache used. */ + seg->tcs_cache_in_use += page_size; + + /* Check the level of the cache: */ + size_t cache_used = 0; + for (int i=0; i<XT_TC_SEGMENT_COUNT; i++) + cache_used += dcg->tcm_segment[i].tcs_cache_in_use; + + if (cache_used > dcg->tcm_cache_high) + dcg->tcm_cache_high = cache_used; + + if (cache_used > dcg->tcm_cache_size) { + XTThreadPtr self; + time_t now; + + /* Wait for the cache level to go down. + * If this happens, then the freeer is not working fast + * enough! + */ + + /* But before I do this, I must flush my own log because: + * - The freeer might be waiting for a page to be cleaned. + * - The page can only be cleaned once it has been written to + * the database. + * - The writer cannot write the page data until it has been + * flushed to the log. + * - The log won't be flushed, unless this thread does it. + * So there could be a deadlock if I don't flush the log! + */ + if ((self = xt_get_self())) { + if (!xt_xlog_flush_log(self)) + goto failed; + } + + /* Wait for the free'er thread: */ + xt_lock_mutex_ns(&dcg->tcm_freeer_lock); + now = time(NULL); + do { + /* I have set the timeout to 2 here because of the following situation: + * 1. Transaction allocates an op seq + * 2. Transaction goes to update cache, but must wait for + * cache to be freed (after this, the op would be written to + * the log). + * 3. The free'er wants to free cache, but is waiting for the writter. + * 4. The writer cannot continue because an op seq is missing! + * So the writer is waiting for the transaction thread to write + * the op seq. + * - So we have a deadlock situation. + * - However, this situation can only occur if there is not enougn + * cache. + * The timeout helps, but will not solve the problem, unless we + * ignore cache level here, after a while, and just continue. + */ + + /* Wake freeer before we go to sleep: */ + if (!dcg->tcm_freeer_busy) { + if (!xt_broadcast_cond_ns(&dcg->tcm_freeer_cond)) + xt_log_and_clear_exception_ns(); + } + + dcg->tcm_threads_waiting++; +#ifdef DEBUG + if (!xt_timed_wait_cond_ns(&dcg->tcm_freeer_cond, &dcg->tcm_freeer_lock, 30000)) { + dcg->tcm_threads_waiting--; + break; + } +#else + if (!xt_timed_wait_cond_ns(&dcg->tcm_freeer_cond, &dcg->tcm_freeer_lock, 1000)) { + dcg->tcm_threads_waiting--; + break; + } +#endif + dcg->tcm_threads_waiting--; + + cache_used = 0; + for (int i=0; i<XT_TC_SEGMENT_COUNT; i++) + cache_used += dcg->tcm_segment[i].tcs_cache_in_use; + + if (cache_used <= dcg->tcm_high_level) + break; + /* + * If there is too little cache we can get stuck here. + * The problem is that seg numbers are allocated before fetching a + * record to be updated. + * + * It can happen that we end up waiting for that seq number + * to be written to the log before we can continue here. + * + * This happens as follows: + * 1. This thread waits for the freeer. + * 2. The freeer cannot free a page because it has not been + * written by the writter. + * 3. The writter cannot continue because it is waiting + * for a missing sequence number. + * 4. The missing sequence number is the one allocated + * before we entered this function! + * + * So don't wait for more than 5 seconds here! + */ + } + while (time(NULL) < now + 5); + xt_unlock_mutex_ns(&dcg->tcm_freeer_lock); + } + else if (cache_used > dcg->tcm_high_level) { + /* Wake up the freeer because the cache level, + * is higher than the high level. + */ + if (!dcg->tcm_freeer_busy) { + xt_lock_mutex_ns(&xt_tab_cache.tcm_freeer_lock); + if (!xt_broadcast_cond_ns(&xt_tab_cache.tcm_freeer_cond)) + xt_log_and_clear_exception_ns(); + xt_unlock_mutex_ns(&xt_tab_cache.tcm_freeer_lock); + } + } + + /* Read the page into memory.... */ + new_page->tcp_dirty = FALSE; + new_page->tcp_seg = (xtWord1) ((page_idx + (file->fr_id * 223)) & XT_TC_SEGMENT_MASK); + new_page->tcp_lock_count = 0; + new_page->tcp_hash_idx = hash_idx; + new_page->tcp_page_idx = page_idx; + new_page->tcp_file_id = file->fr_id; + new_page->tcp_db_id = this->tci_table->tab_db->db_id; + new_page->tcp_tab_id = this->tci_table->tab_id; + new_page->tcp_data_size = this->tci_page_size; + new_page->tcp_op_seq = 0; // Value not used because not dirty + + if (read) { + if (!XT_PREAD_RR_FILE(file, address, this->tci_page_size, 0, new_page->tcp_data, &red_size, &thread->st_statistics.st_rec, thread)) + goto failed; + } + +#ifdef XT_MEMSET_UNUSED_SPACE + /* Removing this is an optimization. It should not be required + * to clear the unused space in the page. + */ + memset(new_page->tcp_data + red_size, 0, this->tci_page_size - red_size); +#endif + + /* Add the page to the cache! */ + xt_rwmutex_xlock(&seg->tcs_lock, thread->t_id); + page = seg->tcs_hash_table[hash_idx]; + while (page) { + if (page->tcp_page_idx == page_idx && page->tcp_file_id == file->fr_id) { + /* Oops, someone else was faster! */ + xt_free_ns(new_page); + goto done_ok; + } + page = page->tcp_next; + } + page = new_page; + + /* Make the page the most recently used: */ + xt_lock_mutex_ns(&dcg->tcm_lock); + page->tcp_ru_time = ++dcg->tcm_ru_now; + if ((page->tcp_lr_used = dcg->tcm_mru_page)) + dcg->tcm_mru_page->tcp_mr_used = page; + page->tcp_mr_used = NULL; + dcg->tcm_mru_page = page; + if (!dcg->tcm_lru_page) + dcg->tcm_lru_page = page; + xt_unlock_mutex_ns(&dcg->tcm_lock); + + /* Add the page to the hash table: */ + page->tcp_next = seg->tcs_hash_table[hash_idx]; + seg->tcs_hash_table[hash_idx] = page; + + done_ok: + *ret_seg = seg; + *ret_page = page; +#ifdef DEBUG_CHECK_CACHE + //XT_TC_check_cache(); +#endif + thread->st_statistics.st_rec_cache_miss++; + return OK; + + failed: + xt_free_ns(new_page); + return FAILED; +} + + +/* ---------------------------------------------------------------------- + * OPERATION SEQUENCE + */ + +xtBool XTTableSeq::ts_log_no_op(XTThreadPtr thread, xtTableID tab_id, xtOpSeqNo op_seq) +{ + XTactNoOpEntryDRec ent_rec; + xtWord4 sum = (xtWord4) tab_id ^ (xtWord4) op_seq; + + ent_rec.no_status_1 = XT_LOG_ENT_NO_OP; + ent_rec.no_checksum_1 = XT_CHECKSUM_1(sum); + XT_SET_DISK_4(ent_rec.no_tab_id_4, tab_id); + XT_SET_DISK_4(ent_rec.no_op_seq_4, op_seq); + /* TODO - If this also fails we have a problem. + * From this point on we should actually not generate + * any more op IDs. The problem is that the + * some will be missing, so the writer will not + * be able to contniue. + */ + return xt_xlog_log_data(thread, sizeof(XTactNoOpEntryDRec), (XTXactLogBufferDPtr) &ent_rec, FALSE); +} + +#ifdef XT_NOT_INLINE +xtOpSeqNo XTTableSeq::ts_set_op_seq(XTTabCachePagePtr page) +{ + xtOpSeqNo seq; + + xt_lock_mutex_ns(&ts_ns_lock); + page->tcp_op_seq = seq = ts_next_seq++; + xt_unlock_mutex_ns(&ts_ns_lock); + return seq; +} + +xtOpSeqNo XTTableSeq::ts_get_op_seq() +{ + xtOpSeqNo seq; + + xt_lock_mutex_ns(&ts_ns_lock); + seq = ts_next_seq++; + xt_unlock_mutex_ns(&ts_ns_lock); + return seq; +} +#endif + +#ifdef XT_NOT_INLINE +/* + * Return TRUE if the current sequence is before the + * target (then) sequence number. This function + * takes into account overflow. Overflow is detected + * by checking the difference between the 2 values. + * If the difference is very large, then we + * assume overflow. + */ +xtBool XTTableSeq::xt_op_is_before(register xtOpSeqNo now, register xtOpSeqNo then) +{ + ASSERT_NS(sizeof(xtOpSeqNo) == 4); + /* The now time is being incremented. + * If it is after the then time (which is static, then + * it is not before! + */ + if (now >= then) { + if ((now - then) > (xtOpSeqNo) 0xFFFFFFFF/2) + return TRUE; + return FALSE; + } + + /* If it appears to be before, we still have to check + * for overflow. If the gap is bigger then half of + * the MAX value, then we can assume it has wrapped around + * because we know that no then can be so far in the + * future! + */ + if ((then - now) > (xtOpSeqNo) 0xFFFFFFFF/2) + return FALSE; + return TRUE; +} +#endif + + +/* ---------------------------------------------------------------------- + * F R E E E R P R O C E S S + */ + +/* + * Used by the writer to wake the freeer. + */ +xtPublic void xt_wr_wake_freeer(XTThreadPtr self) +{ + xt_lock_mutex(self, &xt_tab_cache.tcm_freeer_lock); + pushr_(xt_unlock_mutex, &xt_tab_cache.tcm_freeer_lock); + if (!xt_broadcast_cond_ns(&xt_tab_cache.tcm_freeer_cond)) + xt_log_and_clear_exception_ns(); + freer_(); // xt_unlock_mutex(&xt_tab_cache.tcm_freeer_lock) +} + +/* Wait for a transaction to quit: */ +static void tabc_fr_wait_for_cache(XTThreadPtr self, u_int msecs) +{ + if (!self->t_quit) + xt_timed_wait_cond(NULL, &xt_tab_cache.tcm_freeer_cond, &xt_tab_cache.tcm_freeer_lock, msecs); +} + +typedef struct TCResource { + XTOpenTablePtr tc_ot; +} TCResourceRec, *TCResourcePtr; + +static void tabc_free_fr_resources(XTThreadPtr self, TCResourcePtr tc) +{ + if (tc->tc_ot) { + xt_db_return_table_to_pool(self, tc->tc_ot); + tc->tc_ot = NULL; + } +} + +static XTTableHPtr tabc_get_table(XTThreadPtr self, TCResourcePtr tc, xtDatabaseID db_id, xtTableID tab_id) +{ + XTTableHPtr tab; + XTDatabaseHPtr db; + + if (tc->tc_ot) { + tab = tc->tc_ot->ot_table; + if (tab->tab_id == tab_id && tab->tab_db->db_id == db_id) + return tab; + + xt_db_return_table_to_pool(self, tc->tc_ot); + tc->tc_ot = NULL; + } + + if (!tc->tc_ot) { + if (!(db = xt_get_database_by_id(self, db_id))) + return NULL; + + pushr_(xt_heap_release, db); + tc->tc_ot = xt_db_open_pool_table(self, db, tab_id, NULL, TRUE); + freer_(); // xt_heap_release(db); + if (!tc->tc_ot) + return NULL; + } + + return tc->tc_ot->ot_table; +} + +/* + * Free the given page, or the least recently used page. + * Return the amount of bytes freed. + */ +static size_t tabc_free_page(XTThreadPtr self, TCResourcePtr tc) +{ + register XTTabCacheMemPtr dcg = &xt_tab_cache; + XTTableHPtr tab = NULL; + XTTabCachePagePtr page, lpage, ppage; + XTTabCacheSegPtr seg; + u_int page_cnt; + xtBool was_dirty; + +#ifdef DEBUG_CHECK_CACHE + //XT_TC_check_cache(); +#endif + dcg->tcm_free_try_count = 0; + + retry: + /* Note, handling the page is safe because + * there is only one free'er thread which + * can remove pages from the cache! + */ + page_cnt = 0; + if (!(page = dcg->tcm_lru_page)) { + dcg->tcm_free_try_count = 0; + return 0; + } + + retry_2: + if ((was_dirty = page->tcp_dirty)) { + /* Do all this stuff without a lock, because to + * have a lock while doing this is too expensive! + */ + + /* Wait for the page to be cleaned. */ + tab = tabc_get_table(self, tc, page->tcp_db_id, page->tcp_tab_id); + } + + seg = &dcg->tcm_segment[page->tcp_seg]; + xt_rwmutex_xlock(&seg->tcs_lock, self->t_id); + + if (page->tcp_dirty) { + if (!was_dirty) { + xt_rwmutex_unlock(&seg->tcs_lock, self->t_id); + goto retry_2; + } + + if (tab) { + ASSERT(!XTTableSeq::xt_op_is_before(tab->tab_seq.ts_next_seq, page->tcp_op_seq+1)); + /* This should never happen. However, is has been occuring, + * during multi_update test on Windows. + * In particular it occurs after rename of a table, during ALTER. + * As if the table was not flushed before the rename!? + * To guard against an infinite loop below, I will just continue here. + */ + if (XTTableSeq::xt_op_is_before(tab->tab_seq.ts_next_seq, page->tcp_op_seq+1)) + goto go_on; + /* OK, we have the table, now we check where the current + * sequence number is. + */ + if (XTTableSeq::xt_op_is_before(tab->tab_head_op_seq, page->tcp_op_seq)) { + XTDatabaseHPtr db = tab->tab_db; + + rewait: + xt_rwmutex_unlock(&seg->tcs_lock, self->t_id); + + /* Flush the log, in case this is holding up the + * writer! + */ + if (!db->db_xlog.xlog_flush(self)) { + dcg->tcm_free_try_count = 0; + xt_throw(self); + } + + xt_lock_mutex(self, &db->db_wr_lock); + pushr_(xt_unlock_mutex, &db->db_wr_lock); + + /* The freeer is now waiting: */ + db->db_wr_freeer_waiting = TRUE; + + /* If the writer is idle, wake it up. + * The writer will commit the changes to the database + * which will allow the freeer to free up the cache. + */ + if (db->db_wr_idle) { + if (!xt_broadcast_cond_ns(&db->db_wr_cond)) + xt_log_and_clear_exception_ns(); + } + + /* Go to sleep on the writer's condition. + * The writer will wake the free'er before it goes to + * sleep! + */ + tab->tab_wake_freeer_op = page->tcp_op_seq; + tab->tab_wr_wake_freeer = TRUE; + if (!xt_timed_wait_cond_ns(&db->db_wr_cond, &db->db_wr_lock, 30000)) { + tab->tab_wr_wake_freeer = FALSE; + db->db_wr_freeer_waiting = FALSE; + xt_throw(self); + } + tab->tab_wr_wake_freeer = FALSE; + db->db_wr_freeer_waiting = FALSE; + freer_(); // xt_unlock_mutex(&db->db_wr_lock) + + xt_rwmutex_xlock(&seg->tcs_lock, self->t_id); + if (XTTableSeq::xt_op_is_before(tab->tab_head_op_seq, page->tcp_op_seq)) + goto rewait; + } + go_on:; + } + } + + /* Wait if the page is being read or locked. */ + if (page->tcp_lock_count) { + /* (1) If the page is being read, then we should not free + * it immediately. + * (2) If a page is locked, the locker may be waiting + * for the freeer to free some cache - this + * causes a deadlock. + * + * Therefore, we move on, and try to free another page... + */ + if (page_cnt < (dcg->tcm_approx_page_count >> 1)) { + /* Page has not changed MRU position, and we + * have looked at less than half of the pages. + * Go to the next page... + */ + if ((page = page->tcp_mr_used)) { + page_cnt++; + xt_rwmutex_unlock(&seg->tcs_lock, self->t_id); + goto retry_2; + } + } + xt_rwmutex_unlock(&seg->tcs_lock, self->t_id); + dcg->tcm_free_try_count++; + + /* Starting to spin, free the threads: */ + if (dcg->tcm_threads_waiting) { + if (!xt_broadcast_cond_ns(&dcg->tcm_freeer_cond)) + xt_log_and_clear_exception_ns(); + } + goto retry; + } + + /* Page is clean, remove from the hash table: */ + + /* Find the page on the list: */ + u_int page_idx = page->tcp_page_idx; + u_int file_id = page->tcp_file_id; + + ppage = NULL; + lpage = seg->tcs_hash_table[page->tcp_hash_idx]; + while (lpage) { + if (lpage->tcp_page_idx == page_idx && lpage->tcp_file_id == file_id) + break; + ppage = lpage; + lpage = lpage->tcp_next; + } + + if (page == lpage) { + /* Should be the case! */ + if (ppage) + ppage->tcp_next = page->tcp_next; + else + seg->tcs_hash_table[page->tcp_hash_idx] = page->tcp_next; + } +#ifdef DEBUG + else + ASSERT_NS(FALSE); +#endif + + /* Remove from the MRU list: */ + xt_lock_mutex_ns(&dcg->tcm_lock); + if (dcg->tcm_lru_page == page) + dcg->tcm_lru_page = page->tcp_mr_used; + if (dcg->tcm_mru_page == page) + dcg->tcm_mru_page = page->tcp_lr_used; + if (page->tcp_lr_used) + page->tcp_lr_used->tcp_mr_used = page->tcp_mr_used; + if (page->tcp_mr_used) + page->tcp_mr_used->tcp_lr_used = page->tcp_lr_used; + xt_unlock_mutex_ns(&dcg->tcm_lock); + + /* Free the page: */ + size_t freed_space = offsetof(XTTabCachePageRec, tcp_data) + page->tcp_data_size; + seg->tcs_cache_in_use -= freed_space; + xt_free_ns(page); + + xt_rwmutex_unlock(&seg->tcs_lock, self->t_id); + self->st_statistics.st_rec_cache_frees++; + dcg->tcm_free_try_count = 0; + return freed_space; +} + +static void tabc_fr_main(XTThreadPtr self) +{ + register XTTabCacheMemPtr dcg = &xt_tab_cache; + TCResourceRec tc = { 0 }; + + xt_set_low_priority(self); + dcg->tcm_freeer_busy = TRUE; + + while (!self->t_quit) { + size_t cache_used, freed; + + pushr_(tabc_free_fr_resources, &tc); + + while (!self->t_quit) { + /* Total up the cache memory used: */ + cache_used = 0; + for (int i=0; i<XT_TC_SEGMENT_COUNT; i++) + cache_used += dcg->tcm_segment[i].tcs_cache_in_use; + if (cache_used > dcg->tcm_cache_high) { + dcg->tcm_cache_high = cache_used; + } + + /* Check if the cache usage is over 95%: */ + if (self->t_quit || cache_used < dcg->tcm_high_level) + break; + + /* Reduce cache to the 75% level: */ + while (!self->t_quit && cache_used > dcg->tcm_low_level) { + freed = tabc_free_page(self, &tc); + cache_used -= freed; + if (cache_used <= dcg->tcm_high_level) { + /* Wakeup any threads that are waiting for some cache to be + * freed. + */ + if (dcg->tcm_threads_waiting) { + if (!xt_broadcast_cond_ns(&dcg->tcm_freeer_cond)) + xt_log_and_clear_exception_ns(); + } + } + } + } + + freer_(); // tabc_free_fr_resources(&tc) + + xt_lock_mutex(self, &dcg->tcm_freeer_lock); + pushr_(xt_unlock_mutex, &dcg->tcm_freeer_lock); + + if (dcg->tcm_threads_waiting) { + /* Wake threads before we go to sleep: */ + if (!xt_broadcast_cond_ns(&dcg->tcm_freeer_cond)) + xt_log_and_clear_exception_ns(); + } + + /* Wait for a thread that allocates data to signal + * that the cache level has exceeeded the upper limit: + */ + xt_db_approximate_time = time(NULL); + dcg->tcm_freeer_busy = FALSE; + tabc_fr_wait_for_cache(self, 500); + //tabc_fr_wait_for_cache(self, 30*1000); + dcg->tcm_freeer_busy = TRUE; + xt_db_approximate_time = time(NULL); + freer_(); // xt_unlock_mutex(&dcg->tcm_freeer_lock) + } +} + +static void *tabc_fr_run_thread(XTThreadPtr self) +{ + int count; + void *mysql_thread; + + mysql_thread = myxt_create_thread(); + + while (!self->t_quit) { + try_(a) { + tabc_fr_main(self); + } + catch_(a) { + /* This error is "normal"! */ + if (!(self->t_exception.e_xt_err == XT_SIGNAL_CAUGHT && + self->t_exception.e_sys_err == SIGTERM)) + xt_log_and_clear_exception(self); + } + cont_(a); + + /* After an exception, pause before trying again... */ + /* Number of seconds */ +#ifdef DEBUG + count = 10; +#else + count = 2*60; +#endif + while (!self->t_quit && count > 0) { + xt_db_approximate_time = xt_trace_clock(); + sleep(1); + count--; + } + } + + myxt_destroy_thread(mysql_thread, TRUE); + return NULL; +} + +static void tabc_fr_free_thread(XTThreadPtr self, void *data __attribute__((unused))) +{ + if (xt_tab_cache.tcm_freeer_thread) { + xt_lock_mutex(self, &xt_tab_cache.tcm_freeer_lock); + pushr_(xt_unlock_mutex, &xt_tab_cache.tcm_freeer_lock); + xt_tab_cache.tcm_freeer_thread = NULL; + freer_(); // xt_unlock_mutex(&xt_tab_cache.tcm_freeer_lock) + } +} + +xtPublic void xt_start_freeer(XTThreadPtr self) +{ + xt_tab_cache.tcm_freeer_thread = xt_create_daemon(self, "free-er"); + xt_set_thread_data(xt_tab_cache.tcm_freeer_thread, NULL, tabc_fr_free_thread); + xt_run_thread(self, xt_tab_cache.tcm_freeer_thread, tabc_fr_run_thread); +} + +xtPublic void xt_quit_freeer(XTThreadPtr self) +{ + if (xt_tab_cache.tcm_freeer_thread) { + xt_lock_mutex(self, &xt_tab_cache.tcm_freeer_lock); + pushr_(xt_unlock_mutex, &xt_tab_cache.tcm_freeer_lock); + xt_terminate_thread(self, xt_tab_cache.tcm_freeer_thread); + freer_(); // xt_unlock_mutex(&xt_tab_cache.tcm_freeer_lock) + } +} + +xtPublic void xt_stop_freeer(XTThreadPtr self) +{ + XTThreadPtr thr_fr; + + if (xt_tab_cache.tcm_freeer_thread) { + xt_lock_mutex(self, &xt_tab_cache.tcm_freeer_lock); + pushr_(xt_unlock_mutex, &xt_tab_cache.tcm_freeer_lock); + + /* This pointer is safe as long as you have the transaction lock. */ + if ((thr_fr = xt_tab_cache.tcm_freeer_thread)) { + xtThreadID tid = thr_fr->t_id; + + /* Make sure the thread quits when woken up. */ + xt_terminate_thread(self, thr_fr); + + /* Wake the freeer to get it to quit: */ + if (!xt_broadcast_cond_ns(&xt_tab_cache.tcm_freeer_cond)) + xt_log_and_clear_exception_ns(); + + freer_(); // xt_unlock_mutex(&xt_tab_cache.tcm_freeer_lock) + + /* + * GOTCHA: This is a wierd thing but the SIGTERM directed + * at a particular thread (in this case the sweeper) was + * being caught by a different thread and killing the server + * sometimes. Disconcerting. + * (this may only be a problem on Mac OS X) + xt_kill_thread(thread); + */ + xt_wait_for_thread(tid, FALSE); + + /* PMC - This should not be necessary to set the signal here, but in the + * debugger the handler is not called!!? + thr_fr->t_delayed_signal = SIGTERM; + xt_kill_thread(thread); + */ + xt_tab_cache.tcm_freeer_thread = NULL; + } + else + freer_(); // xt_unlock_mutex(&xt_tab_cache.tcm_freeer_lock) + } +} + +xtPublic void xt_load_pages(XTThreadPtr self, XTOpenTablePtr ot) +{ + XTTableHPtr tab = ot->ot_table; + xtRecordID rec_id; + XTTabCachePagePtr page; + XTTabCacheSegPtr seg; + size_t poffset; + + rec_id = 1; + while (rec_id<tab->tab_row_eof_id) { + if (!tab->tab_rows.tc_fetch(ot->ot_row_file, rec_id, &seg, &page, &poffset, TRUE, self)) + xt_throw(self); + xt_rwmutex_unlock(&seg->tcs_lock, self->t_id); + rec_id += tab->tab_rows.tci_rows_per_page; + } + + rec_id = 1; + while (rec_id<tab->tab_rec_eof_id) { + if (!tab->tab_recs.tc_fetch(ot->ot_rec_file, rec_id, &seg, &page, &poffset, TRUE, self)) + xt_throw(self); + xt_rwmutex_unlock(&seg->tcs_lock, self->t_id); + rec_id += tab->tab_recs.tci_rows_per_page; + } +} + + diff --git a/storage/pbxt/src/tabcache_xt.h b/storage/pbxt/src/tabcache_xt.h new file mode 100644 index 00000000000..694244835b4 --- /dev/null +++ b/storage/pbxt/src/tabcache_xt.h @@ -0,0 +1,250 @@ +/* Copyright (c) 2007 PrimeBase Technologies GmbH + * + * PrimeBase XT + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You 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 + * + * 2007-10-31 Paul McCullagh + * + * H&G2JCtL + * + * The new table cache. Caches all non-index data. This includes the data + * files and the row pointer files. + */ +#ifndef __tabcache_h__ +#define __tabcache_h__ + +struct XTTable; +struct XTOpenTable; +struct XTTabCache; + +#include "thread_xt.h" +#include "filesys_xt.h" +#include "lock_xt.h" + +#ifdef DEBUG +//#define XT_USE_CACHE_DEBUG_SIZES +//#define XT_NOT_INLINE +#endif + +#ifdef XT_USE_CACHE_DEBUG_SIZES + +#define XT_TC_PAGE_SIZE (4*1024) +#define XT_TC_SEGMENT_SHIFTS 1 + +#else + +#define XT_TC_PAGE_SIZE (32*1024) +#define XT_TC_SEGMENT_SHIFTS 3 + +#endif + +#define XT_TIME_DIFF(start, now) (\ + ((xtWord4) (now) < (xtWord4) (start)) ? \ + ((xtWord4) 0XFFFFFFFF - ((xtWord4) (start) - (xtWord4) (now))) : \ + ((xtWord4) (now) - (xtWord4) (start))) + +#define XT_TC_SEGMENT_COUNT ((off_t) 1 << XT_TC_SEGMENT_SHIFTS) +#define XT_TC_SEGMENT_MASK (XT_TC_SEGMENT_COUNT - 1) + +typedef struct XTTabCachePage { + xtWord1 tcp_dirty; /* TRUE if the page is dirty. */ + xtWord1 tcp_seg; /* Segement number of the page. */ + u_int tcp_lock_count; /* Number of read locks on this page. */ + u_int tcp_hash_idx; /* The hash index of the page. */ + u_int tcp_page_idx; /* The page address. */ + u_int tcp_file_id; /* The file id of the page. */ + xtDatabaseID tcp_db_id; /* The ID of the database. */ + xtTableID tcp_tab_id; /* The ID of the table of this cache page. */ + xtWord4 tcp_data_size; /* Size of the data on this page. */ + xtOpSeqNo tcp_op_seq; /* The operation sequence number (dirty pages have a operations sequence) */ + xtWord4 tcp_ru_time; /* If this is in the top 1/4 don't change position in MRU list. */ + struct XTTabCachePage *tcp_next; /* Pointer to next page on hash list, or next free page on free list. */ + struct XTTabCachePage *tcp_mr_used; /* More recently used pages. */ + struct XTTabCachePage *tcp_lr_used; /* Less recently used pages. */ + xtWord1 tcp_data[XT_TC_PAGE_SIZE]; /* This is actually tci_page_size! */ +} XTTabCachePageRec, *XTTabCachePagePtr; + +/* + * Each table has a "table operation sequence". This sequence is incremented by + * each operation on the table. Each operation in the log is tagged by a + * sequence number. + * + * The writter threads re-order operations in the log, and write the operations + * to the database in sequence. + * + * It is safe to free a cache page when the sequence number of the cache page, + * is less than or equal to the written sequence number. + */ +typedef struct XTTableSeq { + xtOpSeqNo ts_next_seq; /* The next sequence number for operations on the table. */ + xt_mutex_type ts_ns_lock; /* Lock for the next sequence number. */ + + xtBool ts_log_no_op(XTThreadPtr thread, xtTableID tab_id, xtOpSeqNo op_seq); + + /* Return the next operation sequence number. */ +#ifdef XT_NOT_INLINE + xtOpSeqNo ts_set_op_seq(XTTabCachePagePtr page); + + xtOpSeqNo ts_get_op_seq(); +#else + xtOpSeqNo ts_set_op_seq(XTTabCachePagePtr page) + { + xtOpSeqNo seq; + + xt_lock_mutex_ns(&ts_ns_lock); + page->tcp_op_seq = seq = ts_next_seq++; + xt_unlock_mutex_ns(&ts_ns_lock); + return seq; + } + + xtOpSeqNo ts_get_op_seq() + { + xtOpSeqNo seq; + + xt_lock_mutex_ns(&ts_ns_lock); + seq = ts_next_seq++; + xt_unlock_mutex_ns(&ts_ns_lock); + return seq; + } +#endif + + void xt_op_seq_init(XTThreadPtr self) { + xt_init_mutex_with_autoname(self, &ts_ns_lock); + } + + void xt_op_seq_set(XTThreadPtr self __attribute__((unused)), xtOpSeqNo n) { + ts_next_seq = n; + } + + void xt_op_seq_exit(XTThreadPtr self __attribute__((unused))) { + xt_free_mutex(&ts_ns_lock); + } + +#ifdef XT_NOT_INLINE + static xtBool xt_op_is_before(register xtOpSeqNo now, register xtOpSeqNo then); +#else + static inline xtBool xt_op_is_before(register xtOpSeqNo now, register xtOpSeqNo then) + { + if (now >= then) { + if ((now - then) > (xtOpSeqNo) 0xFFFFFFFF/2) + return TRUE; + return FALSE; + } + if ((then - now) > (xtOpSeqNo) 0xFFFFFFFF/2) + return FALSE; + return TRUE; + } +#endif +} XTTableSeqRec, *XTTableSeqPtr; + +/* A disk cache segment. The cache is divided into a number of segments + * to improve concurrency. + */ +typedef struct XTTabCacheSeg { + XTRWMutexRec tcs_lock; /* The cache segment read/write lock. */ + //xt_cond_type tcs_cond; + XTTabCachePagePtr *tcs_hash_table; + size_t tcs_cache_in_use; +} XTTabCacheSegRec, *XTTabCacheSegPtr; + +/* + * The free'er thread has a list of tables to be purged from the cache. + * If a table is in the list then it is not allowed to fetch a cache page from + * that table. + * The free'er thread goes through all the cache, and removes + * all cache pages for any table in the purge list. + * When a table has been purged it signals any threads waiting for the + * purge to complete (this is usually due to a drop table). + */ +typedef struct XTTabCachePurge { + int tcp_state; /* The state of the purge. */ + XTTableSeqPtr tcp_tab_seq; /* Identifies the table to be purged from cache. */ +} XTTabCachePurgeRec, *XTTabCachePurgePtr; + +typedef struct XTTabCacheMem { + xt_mutex_type tcm_lock; /* The public cache lock. */ + xt_cond_type tcm_cond; /* The public cache wait condition. */ + XTTabCacheSegRec tcm_segment[XT_TC_SEGMENT_COUNT]; + XTTabCachePagePtr tcm_lru_page; + XTTabCachePagePtr tcm_mru_page; + xtWord4 tcm_ru_now; + size_t tcm_approx_page_count; + size_t tcm_hash_size; + u_int tcm_writer_thread_count; + size_t tcm_cache_size; + size_t tcm_cache_high; /* The high water level of cache allocation. */ + size_t tcm_low_level; /* This is the level to which the freeer will free, once it starts working. */ + size_t tcm_high_level; /* This is the level at which the freeer will start to work (to avoid waiting)! */ + + /* The free'er thread: */ + struct XTThread *tcm_freeer_thread; /* The freeer thread . */ + xt_mutex_type tcm_freeer_lock; /* The public cache lock. */ + xt_cond_type tcm_freeer_cond; /* The public cache wait condition. */ + u_int tcm_purge_list_len; /* The length of the purge list. */ + XTTabCachePurgePtr tcm_purge_list; /* Non-NULL if a table is to be purged. */ + u_int tcm_threads_waiting; /* Count of the number of threads waiting for the freeer. */ + xtBool tcm_freeer_busy; + u_int tcm_free_try_count; +} XTTabCacheMemRec, *XTTabCacheMemPtr; + +/* + * This structure contains the information about a particular table + * for the cache. Each table has its own page size, row size + * and rows per page. + * Tables also have + */ +typedef struct XTTabCache { + struct XTTable *tci_table; + size_t tci_header_size; + size_t tci_page_size; + size_t tci_rec_size; + size_t tci_rows_per_page; + +public: + void xt_tc_setup(struct XTTable *tab, size_t head_size, size_t row_size); + xtBool xt_tc_write(XT_ROW_REC_FILE_PTR file, xtRefID ref_id, size_t offset, size_t size, xtWord1 *data, xtOpSeqNo *op_seq, xtBool read, XTThreadPtr thread); + xtBool xt_tc_write_cond(XTThreadPtr self, XT_ROW_REC_FILE_PTR file, xtRefID ref_id, xtWord1 new_type, xtOpSeqNo *op_seq, xtXactID xn_id, xtRowID row_id, u_int stat_id, u_int rec_type); + xtBool xt_tc_read(XT_ROW_REC_FILE_PTR file, xtRefID ref_id, size_t size, xtWord1 *data, XTThreadPtr thread); + xtBool xt_tc_read_4(XT_ROW_REC_FILE_PTR file, xtRefID ref_id, xtWord4 *data, XTThreadPtr thread); + xtBool xt_tc_read_page(XT_ROW_REC_FILE_PTR file, xtRefID ref_id, xtWord1 *data, XTThreadPtr thread); + xtBool xt_tc_get_page(XT_ROW_REC_FILE_PTR file, xtRefID ref_id, XTTabCachePagePtr *page, size_t *offset, XTThreadPtr thread); + void xt_tc_release_page(XT_ROW_REC_FILE_PTR file, XTTabCachePagePtr page, XTThreadPtr thread); + xtBool tc_fetch(XT_ROW_REC_FILE_PTR file, xtRefID ref_id, XTTabCacheSegPtr *ret_seg, XTTabCachePagePtr *ret_page, size_t *offset, xtBool read, XTThreadPtr thread); + +private: + xtBool tc_read_direct(XT_ROW_REC_FILE_PTR file, xtRefID ref_id, size_t size, xtWord1 *data, XTThreadPtr thread); + xtBool tc_fetch_direct(XT_ROW_REC_FILE_PTR file, xtRefID ref_id, XTTabCacheSegPtr *ret_seg, XTTabCachePagePtr *ret_page, size_t *offset, XTThreadPtr thread); +} XTTabCacheRec, *XTTabCachePtr; + +extern XTTabCacheMemRec xt_tab_cache; + +void xt_tc_init(XTThreadPtr self, size_t cache_size); +void xt_tc_exit(XTThreadPtr self); +void xt_tc_set_cache_size(size_t cache_size); +xtInt8 xt_tc_get_usage(); +xtInt8 xt_tc_get_size(); +xtInt8 xt_tc_get_high(); +void xt_load_pages(XTThreadPtr self, struct XTOpenTable *ot); +#ifdef DEBUG +void xt_check_table_cache(struct XTTable *tab); +#endif + +void xt_quit_freeer(XTThreadPtr self); +void xt_stop_freeer(XTThreadPtr self); +void xt_start_freeer(XTThreadPtr self); +void xt_wr_wake_freeer(XTThreadPtr self); + +#endif diff --git a/storage/pbxt/src/table_xt.cc b/storage/pbxt/src/table_xt.cc new file mode 100644 index 00000000000..fc9ae6156cb --- /dev/null +++ b/storage/pbxt/src/table_xt.cc @@ -0,0 +1,5115 @@ +/* Copyright (c) 2005 PrimeBase Technologies GmbH + * + * PrimeBase XT + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You 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 + * + * 2005-02-08 Paul McCullagh + * + * H&G2JCtL + */ + +#include "xt_config.h" + +#include <string.h> +#include <stdio.h> +#ifndef XT_WIN +#include <strings.h> +#endif +#include <ctype.h> +#include <time.h> + +#ifdef DRIZZLED +#include <drizzled/common.h> +#include <mysys/thr_lock.h> +#include <drizzled/dtcollation.h> +#include <drizzled/handlerton.h> +#else +#include "mysql_priv.h" +#endif + +#include "table_xt.h" +#include "database_xt.h" +#include "heap_xt.h" +#include "strutil_xt.h" +#include "myxt_xt.h" +#include "cache_xt.h" +#include "trace_xt.h" +#ifdef XT_STREAMING +#include "streaming_xt.h" +#endif +#include "index_xt.h" +#include "restart_xt.h" +#include "systab_xt.h" + +#ifdef DEBUG +//#define TRACE_VARIATIONS +//#define TRACE_VARIATIONS_IN_DUP_CHECK +//#define DUMP_CHECK_TABLE +//#define CHECK_INDEX_ON_CHECK_TABLE +//#define TRACE_TABLE_IDS +//#define TRACE_FLUSH +//#define TRACE_CREATE_TABLES +#endif + +#define CHECK_TABLE_STATS + +#ifdef TRACE_TABLE_IDS +//#define PRINTF xt_ftracef +#define PRINTF xt_trace +#endif + +/* + * ----------------------------------------------------------------------- + * Internal structures + */ + +#define XT_MAX_TABLE_FILE_NAME_SIZE (XT_TABLE_NAME_SIZE+6+40) + +/* + * ----------------------------------------------------------------------- + * Compare paths: + */ + +/* GOTCHA! The problem: + * + * The server uses names like: "./test/my_tab", + * the BLOB streaming engine uses: "test/my_tab" + * which leads to the same table being loaded twice. + */ +xtPublic int xt_tab_compare_paths(char *n1, char *n2) +{ + n1 = xt_last_2_names_of_path(n1); + n2 = xt_last_2_names_of_path(n2); + if (pbxt_ignore_case) + return strcasecmp(n1, n2); + return strcmp(n1, n2); +} + +/* + * This function only compares only the last 2 components of + * the path because table names must differ in this area. + */ +xtPublic int xt_tab_compare_names(const char *n1, const char *n2) +{ + n1 = xt_last_2_names_of_path(n1); + n2 = xt_last_2_names_of_path(n2); + if (pbxt_ignore_case) + return strcasecmp(n1, n2); + return strcmp(n1, n2); +} + +/* + * ----------------------------------------------------------------------- + * Private utilities + */ + +static xtBool tab_list_comp(void *key, void *data) +{ + XTTableHPtr tab = (XTTableHPtr) data; + + return strcmp(xt_last_2_names_of_path((char *) key), xt_last_2_names_of_path(tab->tab_name->ps_path)) == 0; +} + +static xtHashValue tab_list_hash(xtBool is_key, void *key_data) +{ + XTTableHPtr tab = (XTTableHPtr) key_data; + + if (is_key) + return xt_ht_hash(xt_last_2_names_of_path((char *) key_data)); + return xt_ht_hash(xt_last_2_names_of_path(tab->tab_name->ps_path)); +} + +static xtBool tab_list_comp_ci(void *key, void *data) +{ + XTTableHPtr tab = (XTTableHPtr) data; + + return strcasecmp(xt_last_2_names_of_path((char *) key), xt_last_2_names_of_path(tab->tab_name->ps_path)) == 0; +} + +static xtHashValue tab_list_hash_ci(xtBool is_key, void *key_data) +{ + XTTableHPtr tab = (XTTableHPtr) key_data; + + if (is_key) + return xt_ht_casehash(xt_last_2_names_of_path((char *) key_data)); + return xt_ht_casehash(xt_last_2_names_of_path(tab->tab_name->ps_path)); +} + +static void tab_list_free(XTThreadPtr self, void *data) +{ + XTTableHPtr tab = (XTTableHPtr) data; + XTDatabaseHPtr db = tab->tab_db; + XTTableEntryPtr te_ptr; + + /* Remove the reference from the ID list, whem the table is + * removed from the name list: + */ + if ((te_ptr = (XTTableEntryPtr) xt_sl_find(self, db->db_table_by_id, &tab->tab_id))) + te_ptr->te_table = NULL; + + if (tab->tab_dic.dic_table) + tab->tab_dic.dic_table->removeReferences(self); + xt_heap_release(self, tab); +} + +static void tab_close_mapped_files(XTThreadPtr self, XTTableHPtr tab) +{ + if (tab->tab_rec_file) { + xt_fs_release_file(self, tab->tab_rec_file); + tab->tab_rec_file = NULL; + } + if (tab->tab_row_file) { + xt_fs_release_file(self, tab->tab_row_file); + tab->tab_row_file = NULL; + } +} + +static void tab_finalize(XTThreadPtr self, void *x) +{ + XTTableHPtr tab = (XTTableHPtr) x; + + xt_exit_row_locks(&tab->tab_locks); + + xt_xres_exit_tab(self, tab); + + if (tab->tab_ind_free_list) { + XTIndFreeListPtr list, flist; + + list = tab->tab_ind_free_list; + while (list) { + flist = list; + list = list->fl_next_list; + xt_free(self, flist); + } + tab->tab_ind_free_list = NULL; + } + + if (tab->tab_ind_file) { + xt_fs_release_file(self, tab->tab_ind_file); + tab->tab_ind_file = NULL; + } + tab_close_mapped_files(self, tab); + + if (tab->tab_index_head) { + xt_free(self, tab->tab_index_head); + tab->tab_index_head = NULL; + } + +#ifdef TRACE_TABLE_IDS + PRINTF("%s: free TABLE: db=%d tab=%d %s\n", self->t_name, (int) tab->tab_db ? tab->tab_db->db_id : 0, (int) tab->tab_id, + tab->tab_name ? xt_last_2_names_of_path(tab->tab_name->ps_path) : "?"); +#endif + if (tab->tab_name) { + xt_free(self, tab->tab_name); + tab->tab_name = NULL; + } + myxt_free_dictionary(self, &tab->tab_dic); + if (tab->tab_free_locks) { + tab->tab_seq.xt_op_seq_exit(self); + xt_spinlock_free(self, &tab->tab_ainc_lock); + xt_free_mutex(&tab->tab_rec_flush_lock); + xt_free_mutex(&tab->tab_ind_flush_lock); + xt_free_mutex(&tab->tab_dic_field_lock); + xt_free_mutex(&tab->tab_row_lock); + xt_free_mutex(&tab->tab_ind_lock); + xt_free_mutex(&tab->tab_rec_lock); + for (u_int i=0; i<XT_ROW_RWLOCKS; i++) + XT_TAB_ROW_FREE_LOCK(self, &tab->tab_row_rwlock[i]); + } +} + +static void tab_onrelease(XTThreadPtr self, void *x) +{ + XTTableHPtr tab = (XTTableHPtr) x; + + /* Signal threads waiting for exclusive use of the table: */ + if (tab->tab_db->db_tables) + xt_ht_signal(self, tab->tab_db->db_tables); +} + +/* + * ----------------------------------------------------------------------- + * PUBLIC METHODS + */ + +/* + * This function sets the table name to "", if the file + * does not belong to XT. + */ +xtPublic char *xt_tab_file_to_name(size_t size, char *tab_name, char *file_name) +{ + char *cptr; + size_t len; + + file_name = xt_last_name_of_path(file_name); + cptr = file_name + strlen(file_name) - 1; + while (cptr > file_name && *cptr != '.') + cptr--; + if (cptr > file_name && *cptr == '.') { + if (strcmp(cptr, ".xtl") == 0 || strcmp(cptr, ".xtr") == 0) { + cptr--; + while (cptr > file_name && isdigit(*cptr)) + cptr--; + } + else { + const char **ext = pbxt_extensions; + + while (*ext) { + if (strcmp(cptr, *ext) == 0) + goto ret_name; + ext++; + } + cptr = file_name; + } + } + + ret_name: + len = cptr - file_name; + if (len > size-1) + len = size-1; + + memcpy(tab_name, file_name, len); + tab_name[len] = 0; + + /* Return a pointer to what was removed! */ + return file_name + len; +} + +static void tab_get_row_file_name(char *table_name, char *name, xtTableID tab_id) +{ + sprintf(table_name, "%s-%lu.xtr", name, (u_long) tab_id); +} + +static void tab_get_data_file_name(char *table_name, char *name, xtTableID tab_id __attribute__((unused))) +{ + sprintf(table_name, "%s.xtd", name); +} + +static void tab_get_index_file_name(char *table_name, char *name, xtTableID tab_id __attribute__((unused))) +{ + sprintf(table_name, "%s.xti", name); +} + +static void tab_free_by_id(XTThreadPtr self __attribute__((unused)), void *thunk __attribute__((unused)), void *item) +{ + XTTableEntryPtr te_ptr = (XTTableEntryPtr) item; + + if (te_ptr->te_tab_name) { + xt_free(self, te_ptr->te_tab_name); + te_ptr->te_tab_name = NULL; + } + te_ptr->te_tab_id = 0; + te_ptr->te_table = NULL; +} + +static int tab_comp_by_id(XTThreadPtr self __attribute__((unused)), register const void *thunk __attribute__((unused)), register const void *a, register const void *b) +{ + xtTableID te_id = *((xtTableID *) a); + XTTableEntryPtr te_ptr = (XTTableEntryPtr) b; + + if (te_id < te_ptr->te_tab_id) + return -1; + if (te_id == te_ptr->te_tab_id) + return 0; + return 1; +} + +static void tab_free_path(XTThreadPtr self __attribute__((unused)), void *thunk __attribute__((unused)), void *item) +{ + XTTablePathPtr tp_ptr = *((XTTablePathPtr *) item); + + xt_free(self, tp_ptr); +} + +static int tab_comp_path(XTThreadPtr self __attribute__((unused)), register const void *thunk __attribute__((unused)), register const void *a, register const void *b) +{ + char *path = (char *) a; + XTTablePathPtr tp_ptr = *((XTTablePathPtr *) b); + + return xt_tab_compare_paths(path, tp_ptr->tp_path); +} + +xtPublic void xt_describe_tables_init(XTThreadPtr self, XTDatabaseHPtr db, XTTableDescPtr td) +{ + td->td_db = db; + td->td_path_idx = 0; + if (td->td_path_idx < xt_sl_get_size(db->db_table_paths)) { + XTTablePathPtr *tp_ptr; + + tp_ptr = (XTTablePathPtr *) xt_sl_item_at(db->db_table_paths, td->td_path_idx); + td->td_tab_path = *tp_ptr; + td->td_open_dir = xt_dir_open(self, td->td_tab_path->tp_path, "*.xtr"); + } + else + td->td_open_dir = NULL; +} + +xtPublic xtBool xt_describe_tables_next(XTThreadPtr self, XTTableDescPtr td) +{ + char *tab_name; + xtBool r = FALSE; + + enter_(); + retry: + if (!td->td_open_dir) + return_(FALSE); + try_(a) { + r = xt_dir_next(self, td->td_open_dir); + } + catch_(a) { + xt_describe_tables_exit(self, td); + throw_(); + } + cont_(a); + if (!r) { + XTTablePathPtr *tp_ptr; + + if (td->td_path_idx+1 >= xt_sl_get_size(td->td_db->db_table_paths)) + return_(FALSE); + + if (td->td_open_dir) + xt_dir_close(NULL, td->td_open_dir); + td->td_open_dir = NULL; + + td->td_path_idx++; + tp_ptr = (XTTablePathPtr *) xt_sl_item_at(td->td_db->db_table_paths, td->td_path_idx); + td->td_tab_path = *tp_ptr; + td->td_open_dir = xt_dir_open(self, td->td_tab_path->tp_path, "*.xtr"); + goto retry; + } + + tab_name = xt_dir_name(self, td->td_open_dir); + td->td_file_name = tab_name; + td->td_tab_id = (xtTableID) xt_file_name_to_id(tab_name); + xt_tab_file_to_name(XT_TABLE_NAME_SIZE, td->td_tab_name, tab_name); + return_(TRUE); +} + +xtPublic void xt_describe_tables_exit(XTThreadPtr self __attribute__((unused)), XTTableDescPtr td) +{ + if (td->td_open_dir) + xt_dir_close(NULL, td->td_open_dir); + td->td_open_dir = NULL; + td->td_tab_path = NULL; +} + +xtPublic void xt_tab_init_db(XTThreadPtr self, XTDatabaseHPtr db) +{ + XTTableDescRec desc; + XTTableEntryRec te_tab; + XTTablePathPtr db_path; + int len; + + enter_(); + pushr_(xt_tab_exit_db, db); + if (pbxt_ignore_case) + db->db_tables = xt_new_hashtable(self, tab_list_comp_ci, tab_list_hash_ci, tab_list_free, TRUE, TRUE); + else + db->db_tables = xt_new_hashtable(self, tab_list_comp, tab_list_hash, tab_list_free, TRUE, TRUE); + db->db_table_by_id = xt_new_sortedlist(self, sizeof(XTTableEntryRec), 20, 20, tab_comp_by_id, db, tab_free_by_id, FALSE, FALSE); + db->db_table_paths = xt_new_sortedlist(self, sizeof(XTTablePathPtr), 20, 20, tab_comp_path, db, tab_free_path, FALSE, FALSE); + + if (db->db_multi_path) { + XTOpenFilePtr of; + char *buffer, *ptr, *path; + char pbuf[PATH_MAX]; + + xt_strcpy(PATH_MAX, pbuf, db->db_main_path); + xt_add_location_file(PATH_MAX, pbuf); + if (xt_fs_exists(pbuf)) { + of = xt_open_file(self, pbuf, XT_FS_DEFAULT); + pushr_(xt_close_file, of); + len = (int) xt_seek_eof_file(self, of); + buffer = (char *) xt_malloc(self, len + 1); + pushr_(xt_free, buffer); + if (!xt_pread_file(of, 0, len, len, buffer, NULL, &self->st_statistics.st_x, self)) + xt_throw(self); + buffer[len] = 0; + ptr = buffer; + while (*ptr) { + /* Ignore preceeding space: */ + while (*ptr && isspace(*ptr)) + ptr++; + path = ptr; + while (*ptr && *ptr != '\n' && *ptr != '\r') { +#ifdef XT_WIN + /* Undo the conversion below: */ + if (*ptr == '/') + *ptr = '\\'; +#endif + ptr++; + } + if (*path != '#' && ptr > path) { + len = (int) (ptr - path); + db_path = (XTTablePathPtr) xt_malloc(self, offsetof(XTTablePathRec, tp_path) + len + 1); + db_path->tp_tab_count = 0; + memcpy(db_path->tp_path, path, len); + db_path->tp_path[len] = 0; + xt_sl_insert(self, db->db_table_paths, db_path->tp_path, &db_path); + } + ptr++; + } + freer_(); // xt_free(buffer) + freer_(); // xt_close_file(of) + } + } + else { + len = (int) strlen(db->db_main_path); + db_path = (XTTablePathPtr) xt_malloc(self, offsetof(XTTablePathRec, tp_path) + len + 1); + db_path->tp_tab_count = 0; + strcpy(db_path->tp_path, db->db_main_path); + xt_sl_insert(self, db->db_table_paths, db_path->tp_path, &db_path); + } + + xt_describe_tables_init(self, db, &desc); + pushr_(xt_describe_tables_exit, &desc); + while (xt_describe_tables_next(self, &desc)) { + te_tab.te_tab_id = desc.td_tab_id; + + if (te_tab.te_tab_id > db->db_curr_tab_id) + db->db_curr_tab_id = te_tab.te_tab_id; + + te_tab.te_tab_name = xt_dup_string(self, desc.td_tab_name); + te_tab.te_tab_path = desc.td_tab_path; + desc.td_tab_path->tp_tab_count++; + te_tab.te_table = NULL; + xt_sl_insert(self, db->db_table_by_id, &desc.td_tab_id, &te_tab); + } + freer_(); // xt_describe_tables_exit(&desc) + + popr_(); // Discard xt_tab_exit_db(db) + exit_(); +} + +static void tab_save_table_paths(XTThreadPtr self, XTDatabaseHPtr db) +{ + XTTablePathPtr *tp_ptr; + XTStringBufferRec buffer; + XTOpenFilePtr of; + char path[PATH_MAX]; + + memset(&buffer, 0, sizeof(buffer)); + + xt_strcpy(PATH_MAX, path, db->db_main_path); + xt_add_location_file(PATH_MAX, path); + + if (xt_sl_get_size(db->db_table_paths)) { + pushr_(xt_sb_free, &buffer); + for (u_int i=0; i<xt_sl_get_size(db->db_table_paths); i++) { + tp_ptr = (XTTablePathPtr *) xt_sl_item_at(db->db_table_paths, i); + xt_sb_concat(self, &buffer, (*tp_ptr)->tp_path); + xt_sb_concat(self, &buffer, "\n"); + } + +#ifdef XT_WIN + /* To make the location file cross-platform (at least + * as long as relative paths are used) we replace all '\' + * with '/': */ + char *ptr; + + ptr = buffer.sb_cstring; + while (*ptr) { + if (*ptr == '\\') + *ptr = '/'; + ptr++; + } +#endif + + of = xt_open_file(self, path, XT_FS_CREATE | XT_FS_MAKE_PATH); + pushr_(xt_close_file, of); + if (!xt_pwrite_file(of, 0, strlen(buffer.sb_cstring), buffer.sb_cstring, &self->st_statistics.st_x, self)) + xt_throw(self); + xt_set_eof_file(self, of, strlen(buffer.sb_cstring)); + freer_(); // xt_close_file(of) + + freer_(); // xt_sb_free(&buffer); + } + else + xt_fs_delete(NULL, path); +} + +static XTTablePathPtr tab_get_table_path(XTThreadPtr self, XTDatabaseHPtr db, XTPathStrPtr tab_name, xtBool save_it) +{ + XTTablePathPtr *tp, tab_path; + char path[PATH_MAX]; + + xt_strcpy(PATH_MAX, path, tab_name->ps_path); + xt_remove_last_name_of_path(path); + xt_remove_dir_char(path); + tp = (XTTablePathPtr *) xt_sl_find(self, db->db_table_paths, path); + if (tp) + tab_path = *tp; + else { + int len = (int) strlen(path); + + tab_path = (XTTablePathPtr) xt_malloc(self, offsetof(XTTablePathRec, tp_path) + len + 1); + tab_path->tp_tab_count = 0; + memcpy(tab_path->tp_path, path, len); + tab_path->tp_path[len] = 0; + xt_sl_insert(self, db->db_table_paths, tab_path->tp_path, &tab_path); + if (save_it) { + tab_save_table_paths(self, db); + if (xt_sl_get_size(db->db_table_paths) == 1) { + XTSystemTableShare::createSystemTables(self, db); + } + } + } + tab_path->tp_tab_count++; + return tab_path; +} + +static void tab_remove_table_path(XTThreadPtr self, XTDatabaseHPtr db, XTTablePathPtr tab_path) +{ + if (tab_path->tp_tab_count > 0) { + tab_path->tp_tab_count--; + if (tab_path->tp_tab_count == 0) { + xt_sl_delete(self, db->db_table_paths, tab_path->tp_path); + tab_save_table_paths(self, db); + } + } +} + +static void tab_free_table_path(XTThreadPtr self, XTTablePathPtr tab_path) +{ + XTDatabaseHPtr db = self->st_database; + + tab_remove_table_path(self, db, tab_path); +} + +xtPublic void xt_tab_exit_db(XTThreadPtr self, XTDatabaseHPtr db) +{ + if (db->db_tables) { + xt_free_hashtable(self, db->db_tables); + db->db_tables = NULL; + } + if (db->db_table_by_id) { + xt_free_sortedlist(self, db->db_table_by_id); + db->db_table_by_id = NULL; + } + if (db->db_table_paths) { + xt_free_sortedlist(self, db->db_table_paths); + db->db_table_paths = NULL; + } +} + +static void tab_check_table(XTThreadPtr self __attribute__((unused)), XTTableHPtr tab __attribute__((unused))) +{ + enter_(); + exit_(); +} + +xtPublic void xt_check_tables(XTThreadPtr self) +{ + u_int edx; + XTTableEntryPtr te_ptr; + volatile XTTableHPtr tab; + char path[PATH_MAX]; + + enter_(); + xt_logf(XT_INFO, "Check %s: Table...\n", self->st_database->db_main_path); + xt_enum_tables_init(&edx); + try_(a) { + for (;;) { + xt_ht_lock(self, self->st_database->db_tables); + pushr_(xt_ht_unlock, self->st_database->db_tables); + te_ptr = xt_enum_tables_next(self, self->st_database, &edx); + freer_(); // xt_ht_unlock(db->db_tables) + if (!te_ptr) + break; + xt_strcpy(PATH_MAX, path, te_ptr->te_tab_path->tp_path); + xt_add_dir_char(PATH_MAX, path); + xt_strcat(PATH_MAX, path, te_ptr->te_tab_name); + tab = xt_use_table(self, (XTPathStrPtr) path, FALSE, FALSE, NULL); + tab_check_table(self, tab); + xt_heap_release(self, tab); + tab = NULL; + } + } + catch_(a) { + if (tab) + xt_heap_release(self, tab); + throw_(); + } + cont_(a); + exit_(); +} + +xtPublic xtBool xt_table_exists(XTDatabaseHPtr db) +{ + return xt_sl_get_size(db->db_table_by_id) > 0; +} + +/* + * Enumerate all tables in the current database. + */ + +xtPublic void xt_enum_tables_init(u_int *edx) +{ + *edx = 0; +} + +xtPublic XTTableEntryPtr xt_enum_tables_next(XTThreadPtr self __attribute__((unused)), XTDatabaseHPtr db, u_int *edx) +{ + XTTableEntryPtr en_ptr; + + if (*edx >= xt_sl_get_size(db->db_table_by_id)) + return NULL; + en_ptr = (XTTableEntryPtr) xt_sl_item_at(db->db_table_by_id, *edx); + (*edx)++; + return en_ptr; +} + +xtPublic void xt_enum_files_of_tables_init(XTPathStrPtr tab_name, xtTableID tab_id, XTFilesOfTablePtr ft) +{ + ft->ft_state = 0; + ft->ft_tab_name = tab_name; + ft->ft_tab_id = tab_id; +} + +xtPublic xtBool xt_enum_files_of_tables_next(XTFilesOfTablePtr ft) +{ + char file_name[XT_MAX_TABLE_FILE_NAME_SIZE]; + + retry: + switch (ft->ft_state) { + case 0: + tab_get_row_file_name(file_name, xt_last_name_of_path(ft->ft_tab_name->ps_path), ft->ft_tab_id); + break; + case 1: + tab_get_data_file_name(file_name, xt_last_name_of_path(ft->ft_tab_name->ps_path), ft->ft_tab_id); + break; + case 2: + tab_get_index_file_name(file_name, xt_last_name_of_path(ft->ft_tab_name->ps_path), ft->ft_tab_id); + break; + default: + return FAILED; + } + + ft->ft_state++; + xt_strcpy(PATH_MAX, ft->ft_file_path, ft->ft_tab_name->ps_path); + xt_remove_last_name_of_path(ft->ft_file_path); + xt_strcat(PATH_MAX, ft->ft_file_path, file_name); + if (!xt_fs_exists(ft->ft_file_path)) + goto retry; + + return TRUE; +} + +static xtBool tab_find_table(XTThreadPtr self, XTDatabaseHPtr db, XTPathStrPtr name, xtTableID *tab_id) +{ + u_int edx; + XTTableEntryPtr te_ptr; + char path[PATH_MAX]; + + xt_enum_tables_init(&edx); + while ((te_ptr = xt_enum_tables_next(self, db, &edx))) { + xt_strcpy(PATH_MAX, path, te_ptr->te_tab_path->tp_path); + xt_add_dir_char(PATH_MAX, path); + xt_strcat(PATH_MAX, path, te_ptr->te_tab_name); + if (xt_tab_compare_names(path, name->ps_path) == 0) { + *tab_id = te_ptr->te_tab_id; + return TRUE; + } + } + return FALSE; +} + +xtPublic void xt_tab_set_index_error(XTTableHPtr tab) +{ + switch (tab->tab_dic.dic_disable_index) { + case XT_INDEX_OK: + break; + case XT_INDEX_TOO_OLD: + xt_register_taberr(XT_REG_CONTEXT, XT_ERR_INDEX_OLD_VERSION, tab->tab_name); + break; + case XT_INDEX_TOO_NEW: + xt_register_taberr(XT_REG_CONTEXT, XT_ERR_INDEX_NEW_VERSION, tab->tab_name); + break; + case XT_INDEX_BAD_BLOCK: + char number[40]; + + sprintf(number, "%d", (int) tab->tab_index_page_size); + xt_register_i2xterr(XT_REG_CONTEXT, XT_ERR_BAD_IND_BLOCK_SIZE, xt_last_name_of_path(tab->tab_name->ps_path), number); + break; + case XT_INDEX_CORRUPTED: + xt_register_taberr(XT_REG_CONTEXT, XT_ERR_INDEX_CORRUPTED, tab->tab_name); + break; + case XT_INDEX_MISSING: + xt_register_taberr(XT_REG_CONTEXT, XT_ERR_INDEX_MISSING, tab->tab_name); + break; + } +} + +static void tab_load_index_header(XTThreadPtr self, XTTableHPtr tab, XTOpenFilePtr file, XTPathStrPtr table_name) +{ + XT_NODE_TEMP; + XTIndexPtr *ind; + xtWord1 *data; + XTIndexFormatDPtr index_fmt; + + /* Load the pointers: */ + if (tab->tab_index_head) + xt_free_ns(tab->tab_index_head); + tab->tab_index_head = (XTIndexHeadDPtr) xt_calloc(self, XT_INDEX_HEAD_SIZE); + + if (file) { + if (!xt_pread_file(file, 0, XT_INDEX_HEAD_SIZE, 0, tab->tab_index_head, NULL, &self->st_statistics.st_ind, self)) + xt_throw(self); + + tab->tab_index_format_offset = XT_GET_DISK_4(tab->tab_index_head->tp_format_offset_4); + index_fmt = (XTIndexFormatDPtr) (((xtWord1 *) tab->tab_index_head) + tab->tab_index_format_offset); + + /* If the table version is less than or equal to an incompatible (unsupported + * version), or greater than the current version, then we cannot open this table + */ + if (XT_GET_DISK_2(index_fmt->if_tab_version_2) <= XT_TAB_INCOMPATIBLE_VERSION || + XT_GET_DISK_2(index_fmt->if_tab_version_2) > XT_TAB_CURRENT_VERSION) { + switch (XT_GET_DISK_2(index_fmt->if_tab_version_2)) { + case 4: + xt_throw_tabcolerr(XT_CONTEXT, XT_ERR_UPGRADE_TABLE, table_name, "0.9.91 Beta"); + break; + case 3: + xt_throw_tabcolerr(XT_CONTEXT, XT_ERR_UPGRADE_TABLE, table_name, "0.9.85 Beta"); + break; + default: + xt_throw_taberr(XT_CONTEXT, XT_ERR_BAD_TABLE_VERSION, table_name); + break; + } + return; + } + + tab->tab_dic.dic_index_ver = XT_GET_DISK_2(index_fmt->if_ind_version_2); + tab->tab_dic.dic_disable_index = XT_INDEX_OK; + + if (tab->tab_dic.dic_index_ver == 1) { + tab->tab_index_header_size = 1024 * 16; + tab->tab_index_page_size = 1024 * 16; + } + else { + tab->tab_index_header_size = XT_GET_DISK_4(tab->tab_index_head->tp_header_size_4); + tab->tab_index_page_size = XT_GET_DISK_4(index_fmt->if_page_size_4); + } + + /* Incorrect version of index is handled by allowing a sequential scan, but no index access. + * Recovery with the wrong index type will not recover the indexes, a REPAIR TABLE + * will be required! + */ + if (tab->tab_dic.dic_index_ver != XT_IND_CURRENT_VERSION) { + if (tab->tab_dic.dic_index_ver != XT_IND_CURRENT_VERSION) + tab->tab_dic.dic_disable_index = XT_INDEX_TOO_OLD; + else + tab->tab_dic.dic_disable_index = XT_INDEX_TOO_NEW; + } + else if (tab->tab_index_page_size != XT_INDEX_PAGE_SIZE) + tab->tab_dic.dic_disable_index = XT_INDEX_BAD_BLOCK; + } + else { + memset(tab->tab_index_head, 0, XT_INDEX_HEAD_SIZE); + tab->tab_dic.dic_disable_index = XT_INDEX_MISSING; + tab->tab_index_header_size = XT_INDEX_HEAD_SIZE; + tab->tab_index_page_size = XT_INDEX_PAGE_SIZE; + tab->tab_dic.dic_index_ver = 0; + tab->tab_index_format_offset = 0; + } + + + if (tab->tab_dic.dic_disable_index) { + xt_tab_set_index_error(tab); + xt_log_and_clear_exception_ns(); + } + + if (tab->tab_dic.dic_disable_index) { + /* Reset, as if we have empty indexes. + * Flush will wipe things out, of course. + * REPAIR TABLE will be required... + */ + XT_NODE_ID(tab->tab_ind_eof) = 1; + XT_NODE_ID(tab->tab_ind_free) = 0; + + ind = tab->tab_dic.dic_keys; + for (u_int i=0; i<tab->tab_dic.dic_key_count; i++, ind++) + XT_NODE_ID((*ind)->mi_root) = 0; + } + else { + XT_NODE_ID(tab->tab_ind_eof) = (xtIndexNodeID) XT_GET_DISK_6(tab->tab_index_head->tp_ind_eof_6); + XT_NODE_ID(tab->tab_ind_free) = (xtIndexNodeID) XT_GET_DISK_6(tab->tab_index_head->tp_ind_free_6); + + data = tab->tab_index_head->tp_data; + ind = tab->tab_dic.dic_keys; + for (u_int i=0; i<tab->tab_dic.dic_key_count; i++, ind++) { + (*ind)->mi_root = XT_GET_NODE_REF(tab, data); + data += XT_NODE_REF_SIZE; + } + } +} + +static void tab_load_table_format(XTThreadPtr self, XTOpenFilePtr file, XTPathStrPtr table_name, size_t *ret_format_offset, size_t *ret_head_size, XTDictionaryPtr dic) +{ + XTDiskValue4 size_buf; + size_t head_size; + XTTableFormatDRec tab_fmt; + size_t fmt_size; + + if (!xt_pread_file(file, 0, 4, 4, &size_buf, NULL, &self->st_statistics.st_rec, self)) + xt_throw(self); + + head_size = XT_GET_DISK_4(size_buf); + *ret_format_offset = head_size; + + /* Load the table format information: */ + if (!xt_pread_file(file, head_size, offsetof(XTTableFormatDRec, tf_definition), offsetof(XTTableFormatDRec, tf_tab_version_2) + 2, &tab_fmt, NULL, &self->st_statistics.st_rec, self)) + xt_throw(self); + + /* If the table version is less than or equal to an incompatible (unsupported + * version), or greater than the current version, then we cannot open this table + */ + if (XT_GET_DISK_2(tab_fmt.tf_tab_version_2) <= XT_TAB_INCOMPATIBLE_VERSION || + XT_GET_DISK_2(tab_fmt.tf_tab_version_2) > XT_TAB_CURRENT_VERSION) { + switch (XT_GET_DISK_2(tab_fmt.tf_tab_version_2)) { + case 4: + xt_throw_tabcolerr(XT_CONTEXT, XT_ERR_UPGRADE_TABLE, table_name, "0.9.91 Beta"); + break; + case 3: + xt_throw_tabcolerr(XT_CONTEXT, XT_ERR_UPGRADE_TABLE, table_name, "0.9.85 Beta"); + break; + default: + xt_throw_taberr(XT_CONTEXT, XT_ERR_BAD_TABLE_VERSION, table_name); + break; + } + return; + } + + fmt_size = XT_GET_DISK_4(tab_fmt.tf_format_size_4); + *ret_head_size = XT_GET_DISK_4(tab_fmt.tf_tab_head_size_4); + dic->dic_rec_size = XT_GET_DISK_4(tab_fmt.tf_rec_size_4); + dic->dic_rec_fixed = XT_GET_DISK_1(tab_fmt.tf_rec_fixed_1); + dic->dic_tab_flags = XT_GET_DISK_2(tab_fmt.tf_tab_flags_2); + dic->dic_min_auto_inc = XT_GET_DISK_8(tab_fmt.tf_min_auto_inc_8); + if (fmt_size > offsetof(XTTableFormatDRec, tf_definition)) { + size_t def_size = fmt_size - offsetof(XTTableFormatDRec, tf_definition); + char *def_sql; + + pushsr_(def_sql, xt_free, (char *) xt_malloc(self, def_size)); + if (!xt_pread_file(file, head_size+offsetof(XTTableFormatDRec, tf_definition), def_size, def_size, def_sql, NULL, &self->st_statistics.st_rec, self)) + xt_throw(self); + dic->dic_table = xt_ri_create_table(self, false, table_name, def_sql, myxt_create_table_from_table(self, dic->dic_my_table)); + freer_(); // xt_free(def_sql) + } + else + dic->dic_table = myxt_create_table_from_table(self, dic->dic_my_table); +} + +static void tab_load_table_header(XTThreadPtr self, XTTableHPtr tab, XTOpenFilePtr file) +{ + XTTableHeadDRec rec_head; + + if (!xt_pread_file(file, 0, sizeof(XTTableHeadDRec), sizeof(XTTableHeadDRec), (xtWord1 *) &rec_head, NULL, &self->st_statistics.st_rec, self)) + xt_throw(self); + + tab->tab_head_op_seq = XT_GET_DISK_4(rec_head.th_op_seq_4); + tab->tab_head_row_free_id = (xtRowID) XT_GET_DISK_6(rec_head.th_row_free_6); + tab->tab_head_row_eof_id = (xtRowID) XT_GET_DISK_6(rec_head.th_row_eof_6); + tab->tab_head_row_fnum = (xtWord4) XT_GET_DISK_6(rec_head.th_row_fnum_6); + tab->tab_head_rec_free_id = (xtRecordID) XT_GET_DISK_6(rec_head.th_rec_free_6); + tab->tab_head_rec_eof_id = (xtRecordID) XT_GET_DISK_6(rec_head.th_rec_eof_6); + tab->tab_head_rec_fnum = (xtWord4) XT_GET_DISK_6(rec_head.th_rec_fnum_6); +} + +xtPublic void xt_tab_store_header(XTOpenTablePtr ot, XTTableHeadDPtr rec_head) +{ + XTTableHPtr tab = ot->ot_table; + + XT_SET_DISK_4(rec_head->th_op_seq_4, tab->tab_head_op_seq); + XT_SET_DISK_6(rec_head->th_row_free_6, tab->tab_head_row_free_id); + XT_SET_DISK_6(rec_head->th_row_eof_6, tab->tab_head_row_eof_id); + XT_SET_DISK_6(rec_head->th_row_fnum_6, tab->tab_head_row_fnum); + XT_SET_DISK_6(rec_head->th_rec_free_6, tab->tab_head_rec_free_id); + XT_SET_DISK_6(rec_head->th_rec_eof_6, tab->tab_head_rec_eof_id); + XT_SET_DISK_6(rec_head->th_rec_fnum_6, tab->tab_head_rec_fnum); +} + +xtPublic xtBool xt_tab_write_header(XTOpenTablePtr ot, XTTableHeadDPtr rec_head, struct XTThread *thread) +{ + if (!XT_PWRITE_RR_FILE(ot->ot_rec_file, offsetof(XTTableHeadDRec, th_op_seq_4), 40, (xtWord1 *) rec_head->th_op_seq_4, &thread->st_statistics.st_rec, thread)) + return FAILED; + if (!XT_FLUSH_RR_FILE(ot->ot_rec_file, &thread->st_statistics.st_rec, thread)) + return FAILED; + return OK; +} + +xtPublic xtBool xt_tab_write_min_auto_inc(XTOpenTablePtr ot) +{ + xtWord1 value[8]; + off_t offset; + + XT_SET_DISK_8(value, ot->ot_table->tab_dic.dic_min_auto_inc); + offset = ot->ot_table->tab_table_format_offset + offsetof(XTTableFormatDRec, tf_min_auto_inc_8); + if (!XT_PWRITE_RR_FILE(ot->ot_rec_file, offset, 8, value, &ot->ot_thread->st_statistics.st_rec, ot->ot_thread)) + return FAILED; + if (!XT_FLUSH_RR_FILE(ot->ot_rec_file, &ot->ot_thread->st_statistics.st_rec, ot->ot_thread)) + return FAILED; + return OK; +} + +/* a helper function to remove table from the open tables hash on exception + * used in tab_new_handle() below + */ +static void xt_del_from_db_tables_ht(XTThreadPtr self, XTTableHPtr tab) +{ + XTTableEntryPtr te_ptr; + XTDatabaseHPtr db = tab->tab_db; + xtTableID tab_id = tab->tab_id; + + /* Oops! should use tab->tab_name, instead of tab! */ + xt_ht_del(self, db->db_tables, tab->tab_name); + + /* Remove the reference from the ID list, when a table is + * removed from the table name list: + */ + if ((te_ptr = (XTTableEntryPtr) xt_sl_find(self, db->db_table_by_id, &tab_id))) + te_ptr->te_table = NULL; +} + +/* + * Create a new table handle (i.e. open a table). + * Return NULL if the table is missing, and it is OK for the table + * to be missing. + */ +static int tab_new_handle(XTThreadPtr self, XTTableHPtr *r_tab, XTDatabaseHPtr db, xtTableID tab_id, XTPathStrPtr tab_path, xtBool missing_ok, XTDictionaryPtr dic) +{ + char path[PATH_MAX]; + XTTableHPtr tab; + char file_name[XT_MAX_TABLE_FILE_NAME_SIZE]; + XTOpenFilePtr of_rec, of_ind; + XTTableEntryPtr te_ptr; + size_t tab_format_offset; + size_t tab_head_size; + + enter_(); + + tab = (XTTableHPtr) xt_heap_new(self, sizeof(XTTableHRec), tab_finalize); + pushr_(xt_heap_release, tab); + + tab->tab_name = (XTPathStrPtr) xt_dup_string(self, tab_path->ps_path); + tab->tab_db = db; + tab->tab_id = tab_id; +#ifdef TRACE_TABLE_IDS + PRINTF("%s: allocated TABLE: db=%d tab=%d %s\n", self->t_name, (int) db->db_id, (int) tab->tab_id, xt_last_2_names_of_path(tab->tab_name->ps_path)); +#endif + + if (dic) { + myxt_move_dictionary(&tab->tab_dic, dic); + myxt_setup_dictionary(self, &tab->tab_dic); + } + else { + if (!myxt_load_dictionary(self, &tab->tab_dic, db, tab_path)) { + freer_(); // xt_heap_release(tab) + return_(XT_TAB_NO_DICTIONARY); + } + } + + tab->tab_seq.xt_op_seq_init(self); + xt_spinlock_init_with_autoname(self, &tab->tab_ainc_lock); + xt_init_mutex_with_autoname(self, &tab->tab_rec_flush_lock); + xt_init_mutex_with_autoname(self, &tab->tab_ind_flush_lock); + xt_init_mutex_with_autoname(self, &tab->tab_dic_field_lock); + xt_init_mutex_with_autoname(self, &tab->tab_row_lock); + xt_init_mutex_with_autoname(self, &tab->tab_ind_lock); + xt_init_mutex_with_autoname(self, &tab->tab_rec_lock); + for (u_int i=0; i<XT_ROW_RWLOCKS; i++) + XT_TAB_ROW_INIT_LOCK(self, &tab->tab_row_rwlock[i]); + tab->tab_free_locks = TRUE; + + xt_strcpy(PATH_MAX, path, tab_path->ps_path); + xt_remove_last_name_of_path(path); + tab_get_row_file_name(file_name, xt_last_name_of_path(tab_path->ps_path), tab_id); + xt_strcat(PATH_MAX, path, file_name); + tab->tab_row_file = xt_fs_get_file(self, path); + + xt_remove_last_name_of_path(path); + tab_get_data_file_name(file_name, xt_last_name_of_path(tab_path->ps_path), tab_id); + xt_strcat(PATH_MAX, path, file_name); + tab->tab_rec_file = xt_fs_get_file(self, path); + + xt_remove_last_name_of_path(path); + tab_get_index_file_name(file_name, xt_last_name_of_path(tab_path->ps_path), tab_id); + xt_strcat(PATH_MAX, path, file_name); + tab->tab_ind_file = xt_fs_get_file(self, path); + + of_ind = xt_open_file(self, tab->tab_ind_file->fil_path, XT_FS_MISSING_OK); + if (of_ind) { + pushr_(xt_close_file, of_ind); + tab_load_index_header(self, tab, of_ind, tab_path); + freer_(); // xt_close_file(of_ind) + } + else + tab_load_index_header(self, tab, of_ind, tab_path); + + of_rec = xt_open_file(self, tab->tab_rec_file->fil_path, missing_ok ? XT_FS_MISSING_OK : XT_FS_DEFAULT); + if (!of_rec) { + freer_(); // xt_heap_release(tab) + return_(XT_TAB_NOT_FOUND); + } + pushr_(xt_close_file, of_rec); + tab_load_table_format(self, of_rec, tab_path, &tab_format_offset, &tab_head_size, &tab->tab_dic); + tab->tab_table_format_offset = tab_format_offset; + tab->tab_table_head_size = tab_head_size; + tab->tab_dic.dic_table->dt_table = tab; + tab_load_table_header(self, tab, of_rec); + freer_(); // xt_close_file(of_rec) + + tab->tab_seq.xt_op_seq_set(self, tab->tab_head_op_seq+1); + tab->tab_row_eof_id = tab->tab_head_row_eof_id; + tab->tab_row_free_id = tab->tab_head_row_free_id; + tab->tab_row_fnum = tab->tab_head_row_fnum; + tab->tab_rec_eof_id = tab->tab_head_rec_eof_id; + tab->tab_rec_free_id = tab->tab_head_rec_free_id; + tab->tab_rec_fnum = tab->tab_head_rec_fnum; + + tab->tab_rows.xt_tc_setup(tab, sizeof(XTTabRowHeadDRec), sizeof(XTTabRowRefDRec)); + tab->tab_recs.xt_tc_setup(tab, tab_head_size, tab->tab_dic.dic_rec_size); + + xt_xres_init_tab(self, tab); + + if (!xt_init_row_locks(&tab->tab_locks)) + xt_throw(self); + + xt_heap_set_release_callback(self, tab, tab_onrelease); + + popr_(); // Discard xt_heap_release(tab) + + xt_ht_put(self, db->db_tables, tab); + + /* Add a reference to the ID list, when a table is + * added to the table name list: + */ + if ((te_ptr = (XTTableEntryPtr) xt_sl_find(self, db->db_table_by_id, &tab->tab_id))) + te_ptr->te_table = tab; + + /* Moved from after xt_init_row_locks() above, so that calling + * xt_use_table_no_lock() with no_load == FALSE from attachReferences() + * will work if we have cyclic foreign key references. + */ + if (tab->tab_dic.dic_table) { + pushr_(xt_del_from_db_tables_ht, tab); + tab->tab_dic.dic_table->attachReferences(self, db); + popr_(); + } + + *r_tab = tab; + return_(XT_TAB_OK); +} + + +/* + * Get a reference to a table in the current database. The table reference is valid, + * as long as the thread is using the database!!! + */ +xtPublic XTTableHPtr xt_use_table_no_lock(XTThreadPtr self, XTDatabaseHPtr db, XTPathStrPtr name, xtBool no_load, xtBool missing_ok, XTDictionaryPtr dic, xtBool *opened) +{ + XTTableHPtr tab; + + if (!db) + xt_throw_xterr(XT_CONTEXT, XT_ERR_NO_DATABASE_IN_USE); + + tab = (XTTableHPtr) xt_ht_get(self, db->db_tables, name); + if (!tab && !no_load) { + xtTableID tab_id = 0; + + if (!tab_find_table(self, db, name, &tab_id)) { + if (missing_ok) + return NULL; + xt_throw_taberr(XT_CONTEXT, XT_ERR_TABLE_NOT_FOUND, name); + } + + if (tab_new_handle(self, &tab, db, tab_id, name, FALSE, dic) == XT_TAB_NO_DICTIONARY) + xt_throw_taberr(XT_CONTEXT, XT_ERR_NO_DICTIONARY, name); + + if (opened) + *opened = TRUE; + } + + if (tab) + xt_heap_reference(self, tab); + + return tab; +} + +static void tab_close_table(XTOpenTablePtr ot) +{ + xt_ind_free_reserved(ot); + + if (ot->ot_rec_file) { + XT_CLOSE_RR_FILE_NS(ot->ot_rec_file); + ot->ot_rec_file = NULL; + + } + if (ot->ot_ind_file) { + xt_close_file_ns(ot->ot_ind_file); + ot->ot_ind_file = NULL; + + } + if (ot->ot_row_file) { + XT_CLOSE_RR_FILE_NS(ot->ot_row_file); + ot->ot_row_file = NULL; + + } + if (ot->ot_table) { + xt_heap_release(xt_get_self(), ot->ot_table); + ot->ot_table = NULL; + } + if (ot->ot_ind_rhandle) { + xt_ind_release_handle(ot->ot_ind_rhandle, FALSE, ot->ot_thread); + ot->ot_ind_rhandle = NULL; + } + if (ot->ot_row_rbuffer) { + xt_free_ns(ot->ot_row_rbuffer); + ot->ot_row_rbuf_size = 0; + ot->ot_row_rbuffer = NULL; + } + if (ot->ot_row_wbuffer) { + xt_free_ns(ot->ot_row_wbuffer); + ot->ot_row_wbuf_size = 0; + ot->ot_row_wbuffer = NULL; + } +#ifdef XT_TRACK_RETURNED_ROWS + if (ot->ot_rows_returned) { + xt_free_ns(ot->ot_rows_returned); + ot->ot_rows_returned = NULL; + } + ot->ot_rows_ret_curr = 0; + ot->ot_rows_ret_max = 0; +#endif + xt_free(NULL, ot); +} + +/* + * This function locks a particular table by locking the table directory + * and waiting for all open tables handles to close. + * + * Things are a bit complicated because the sweeper must be turned off before + * the table directory is locked. + */ +static XTOpenTablePoolPtr tab_lock_table(XTThreadPtr self, XTPathStrPtr name, xtBool no_load, xtBool flush_table, xtBool missing_ok, XTTableHPtr *tab) +{ + XTOpenTablePoolPtr table_pool; + XTDatabaseHPtr db = self->st_database; + + enter_(); + /* Lock the table, and close all references: */ + pushsr_(table_pool, xt_db_unlock_table_pool, xt_db_lock_table_pool_by_name(self, db, name, no_load, flush_table, missing_ok, FALSE, tab)); + if (!table_pool) { + freer_(); // xt_db_unlock_table_pool(db) + return_(NULL); + } + +#ifdef XT_STREAMING + /* Tell PBMS to close all open tables of this sort: */ + xt_pbms_close_all_tables(name->ps_path); +#endif + + /* Wait for all open tables to close: */ + xt_db_wait_for_open_tables(self, table_pool); + + popr_(); // Discard xt_db_unlock_table_pool(table_pool) + return_(table_pool); +} + +static void tab_delete_table_files(XTThreadPtr self, XTPathStrPtr tab_name, xtTableID tab_id) +{ + XTFilesOfTableRec ft; + + xt_enum_files_of_tables_init(tab_name, tab_id, &ft); + while (xt_enum_files_of_tables_next(&ft)) { + if (!xt_fs_delete(NULL, ft.ft_file_path)) + xt_log_and_clear_exception(self); + } +} + +xtPublic void xt_create_table(XTThreadPtr self, XTPathStrPtr name, XTDictionaryPtr dic) +{ + char table_name[XT_MAX_TABLE_FILE_NAME_SIZE]; + char path[PATH_MAX]; + XTDatabaseHPtr db = self->st_database; + XTOpenTablePoolPtr table_pool; + XTTableHPtr tab; + XTTableHPtr old_tab = NULL; + xtTableID old_tab_id = 0; + xtTableID tab_id = 0; + XTTabRowHeadDRec row_head; + XTTableHeadDRec rec_head; + XTTableFormatDRec table_fmt; + XTIndexFormatDPtr index_fmt; + XTStringBufferRec tab_def = { 0, 0, 0 }; + XTTableEntryRec te_tab; + XTSortedListInfoRec li_undo; + +#ifdef TRACE_CREATE_TABLES + printf("CREATE %s\n", name->ps_path); +#endif + enter_(); + if (strlen(xt_last_name_of_path(name->ps_path)) > XT_TABLE_NAME_SIZE-1) + xt_throw_taberr(XT_CONTEXT, XT_ERR_NAME_TOO_LONG, name); + if (!db) + xt_throw_xterr(XT_CONTEXT, XT_ERR_NO_DATABASE_IN_USE); + + /* Lock to prevent table list change during creation. */ + table_pool = tab_lock_table(self, name, FALSE, TRUE, TRUE, &old_tab); + pushr_(xt_db_unlock_table_pool, table_pool); + xt_ht_lock(self, db->db_tables); + pushr_(xt_ht_unlock, db->db_tables); + pushr_(xt_heap_release, old_tab); + + /* This must be done before we remove the old table + * from the directory, or we will not be able + * to find the table, which could is require + * for TRUNCATE! + */ + if (xt_sl_get_size(db->db_table_by_id) >= XT_MAX_TABLES) + xt_throw_ulxterr(XT_CONTEXT, XT_ERR_TOO_MANY_TABLES, (u_long) XT_MAX_TABLES); + + tab_id = db->db_curr_tab_id + 1; + + if (old_tab) { + old_tab_id = old_tab->tab_id; + xt_dl_delete_ext_data(self, old_tab, FALSE, TRUE); + freer_(); // xt_heap_release(self, old_tab) + + /* For the Windows version this must be done before we + * start to delete the underlying files! + */ + tab_close_mapped_files(self, old_tab); + + tab_delete_table_files(self, name, old_tab_id); + + /* Remove the PBMS table: */ + ASSERT(xt_get_self() == self); +#ifdef XT_STREAMING + xt_pbms_drop_table(name->ps_path); +#endif + + /* Remove the table from the directory. It will get a new + * ID so the handle in the directory will no longer be valid. + */ + xt_ht_del(self, db->db_tables, name); + } + else { + freer_(); // xt_heap_release(self, old_tab) + } + + /* Add the table to the directory, well remove on error! */ + li_undo.li_sl = db->db_table_by_id; + li_undo.li_key = &tab_id; + te_tab.te_tab_id = tab_id; + te_tab.te_tab_name = xt_dup_string(self, xt_last_name_of_path(name->ps_path)); + te_tab.te_tab_path = tab_get_table_path(self, db, name, TRUE); + te_tab.te_table = NULL; + xt_sl_insert(self, db->db_table_by_id, &tab_id, &te_tab); + pushr_(xt_sl_delete_from_info, &li_undo); + + *path = 0; + try_(a) { + XTOpenFilePtr of_row, of_rec, of_ind; + off_t eof; + size_t def_len = 0; + + tab = (XTTableHPtr) xt_heap_new(self, sizeof(XTTableHRec), tab_finalize); + pushr_(xt_heap_release, tab); + + /* The length of the foreign key definition: */ + if (dic->dic_table) { + dic->dic_table->loadString(self, &tab_def); + def_len = tab_def.sb_len + 1; + } + + tab->tab_head_op_seq = 0; +#ifdef DEBUG + //tab->tab_head_op_seq = 0xFFFFFFFF - 12; +#endif + + /* ------- ROW FILE: */ + xt_strcpy(PATH_MAX, path, name->ps_path); + xt_remove_last_name_of_path(path); + tab_get_row_file_name(table_name, xt_last_name_of_path(name->ps_path), tab_id); + xt_strcat(PATH_MAX, path, table_name); + + of_row = xt_open_file(self, path, XT_FS_CREATE | XT_FS_EXCLUSIVE); + pushr_(xt_close_file, of_row); + XT_SET_DISK_4(row_head.rh_magic_4, XT_TAB_ROW_MAGIC); + if (!xt_pwrite_file(of_row, 0, sizeof(row_head), &row_head, &self->st_statistics.st_rec, self)) + xt_throw(self); + freer_(); // xt_close_file(of_row) + + (void) ASSERT(sizeof(XTTabRowHeadDRec) == sizeof(XTTabRowRefDRec)); + (void) ASSERT(sizeof(XTTabRowRefDRec) == 1 << XT_TAB_ROW_SHIFTS); + + tab->tab_row_eof_id = 1; + tab->tab_row_free_id = 0; + tab->tab_row_fnum = 0; + + tab->tab_head_row_eof_id = 1; + tab->tab_head_row_free_id = 0; + tab->tab_head_row_fnum = 0; + + /* ------------ DATA FILE: */ + xt_remove_last_name_of_path(path); + tab_get_data_file_name(table_name, xt_last_name_of_path(name->ps_path), tab_id); + xt_strcat(PATH_MAX, path, table_name); + of_rec = xt_open_file(self, path, XT_FS_CREATE | XT_FS_EXCLUSIVE); + pushr_(xt_close_file, of_rec); + + /* Calculate the offset of the first record in the data handle file. */ + eof = sizeof(XTTableHeadDRec) + offsetof(XTTableFormatDRec, tf_definition) + def_len + XT_FORMAT_DEF_SPACE; + eof = (eof + 1024 - 1) / 1024 * 1024; // Round to a value divisible by 1024 + + tab->tab_table_format_offset = sizeof(XTTableHeadDRec); + tab->tab_table_head_size = (size_t) eof; + + tab->tab_rec_eof_id = 1; // This is the first record ID! + tab->tab_rec_free_id = 0; + tab->tab_rec_fnum = 0; + + tab->tab_head_rec_eof_id = 1; // The first record ID + tab->tab_head_rec_free_id = 0; + tab->tab_head_rec_fnum = 0; + + tab->tab_dic.dic_rec_size = dic->dic_rec_size; + tab->tab_dic.dic_rec_fixed = dic->dic_rec_fixed; + tab->tab_dic.dic_tab_flags = dic->dic_tab_flags; + tab->tab_dic.dic_min_auto_inc = dic->dic_min_auto_inc; + tab->tab_dic.dic_def_ave_row_size = dic->dic_def_ave_row_size; + + XT_SET_DISK_4(rec_head.th_head_size_4, sizeof(XTTableHeadDRec)); + XT_SET_DISK_4(rec_head.th_op_seq_4, tab->tab_head_op_seq); + XT_SET_DISK_6(rec_head.th_row_free_6, tab->tab_head_row_free_id); + XT_SET_DISK_6(rec_head.th_row_eof_6, tab->tab_head_row_eof_id); + XT_SET_DISK_6(rec_head.th_row_fnum_6, tab->tab_head_row_fnum); + XT_SET_DISK_6(rec_head.th_rec_free_6, tab->tab_head_rec_free_id); + XT_SET_DISK_6(rec_head.th_rec_eof_6, tab->tab_head_rec_eof_id); + XT_SET_DISK_6(rec_head.th_rec_fnum_6, tab->tab_head_rec_fnum); + + if (!xt_pwrite_file(of_rec, 0, sizeof(XTTableHeadDRec), &rec_head, &self->st_statistics.st_rec, self)) + xt_throw(self); + + /* Store the table format: */ + memset(&table_fmt, 0, offsetof(XTTableFormatDRec, tf_definition)); + XT_SET_DISK_4(table_fmt.tf_format_size_4, offsetof(XTTableFormatDRec, tf_definition) + def_len); + XT_SET_DISK_4(table_fmt.tf_tab_head_size_4, eof); + XT_SET_DISK_2(table_fmt.tf_tab_version_2, XT_TAB_CURRENT_VERSION); + XT_SET_DISK_4(table_fmt.tf_rec_size_4, tab->tab_dic.dic_rec_size); + XT_SET_DISK_1(table_fmt.tf_rec_fixed_1, tab->tab_dic.dic_rec_fixed); + XT_SET_DISK_2(table_fmt.tf_tab_flags_2, tab->tab_dic.dic_tab_flags); + XT_SET_DISK_8(table_fmt.tf_min_auto_inc_8, tab->tab_dic.dic_min_auto_inc); + + if (!xt_pwrite_file(of_rec, sizeof(XTTableHeadDRec), offsetof(XTTableFormatDRec, tf_definition), &table_fmt, &self->st_statistics.st_rec, self)) + xt_throw(self); + if (def_len) { + if (!xt_pwrite_file(of_rec, sizeof(XTTableHeadDRec) + offsetof(XTTableFormatDRec, tf_definition), def_len, tab_def.sb_cstring, &self->st_statistics.st_rec, self)) + xt_throw(self); + } + + freer_(); // xt_close_file(of_rec) + + /* ----------- INDEX FILE: */ + xt_remove_last_name_of_path(path); + tab_get_index_file_name(table_name, xt_last_name_of_path(name->ps_path), tab_id); + xt_strcat(PATH_MAX, path, table_name); + of_ind = xt_open_file(self, path, XT_FS_CREATE | XT_FS_EXCLUSIVE); + pushr_(xt_close_file, of_ind); + + /* This is the size of the index header: */ + tab->tab_index_format_offset = offsetof(XTIndexHeadDRec, tp_data) + dic->dic_key_count * XT_NODE_REF_SIZE; + if (!(tab->tab_index_head = (XTIndexHeadDPtr) xt_calloc_ns(XT_INDEX_HEAD_SIZE))) + xt_throw(self); + + XT_NODE_ID(tab->tab_ind_eof) = 1; + XT_NODE_ID(tab->tab_ind_free) = 0; + + XT_SET_DISK_4(tab->tab_index_head->tp_header_size_4, XT_INDEX_HEAD_SIZE); + XT_SET_DISK_4(tab->tab_index_head->tp_format_offset_4, tab->tab_index_format_offset); + XT_SET_DISK_6(tab->tab_index_head->tp_ind_eof_6, XT_NODE_ID(tab->tab_ind_eof)); + XT_SET_DISK_6(tab->tab_index_head->tp_ind_free_6, XT_NODE_ID(tab->tab_ind_free)); + + /* Store the index format: */ + index_fmt = (XTIndexFormatDPtr) (((xtWord1 *) tab->tab_index_head) + tab->tab_index_format_offset); + XT_SET_DISK_4(index_fmt->if_format_size_4, sizeof(XTIndexFormatDRec)); + XT_SET_DISK_2(index_fmt->if_tab_version_2, XT_TAB_CURRENT_VERSION); + XT_SET_DISK_2(index_fmt->if_ind_version_2, XT_IND_CURRENT_VERSION); + XT_SET_DISK_1(index_fmt->if_node_ref_size_1, XT_NODE_REF_SIZE); + XT_SET_DISK_1(index_fmt->if_rec_ref_size_1, XT_RECORD_REF_SIZE); + XT_SET_DISK_4(index_fmt->if_page_size_4, XT_INDEX_PAGE_SIZE); + + /* Save the header: */ + if (!xt_pwrite_file(of_ind, 0, XT_INDEX_HEAD_SIZE, tab->tab_index_head, &self->st_statistics.st_ind, self)) + xt_throw(self); + + freer_(); // xt_close_file(of_ind) + + /* ------------ */ + /* Log the new table ID! */ + db->db_curr_tab_id = tab_id; + if (!xt_xn_log_tab_id(self, tab_id)) { + db->db_curr_tab_id = tab_id - 1; + xt_throw(self); + } + + freer_(); // xt_heap_release(tab) + + /* {LOAD-FOR-FKS} + * 2008-12-10: Note, there is another problem, example: + * set storage_engine = pbxt; + * + * CREATE TABLE t1 (s1 INT PRIMARY KEY, s2 INT); + * CREATE TABLE t2 (s1 INT PRIMARY KEY, FOREIGN KEY (s1) REFERENCES t1 (s1) ON UPDATE CASCADE); + * CREATE TABLE t3 (s1 INT PRIMARY KEY, FOREIGN KEY (s1) REFERENCES t2 (s1) ON UPDATE CASCADE); + * + * DROP TABLE IF EXISTS t2,t1; + * CREATE TABLE t1 (s1 ENUM('a','b') PRIMARY KEY); + * CREATE TABLE t2 (s1 ENUM('A','B'), FOREIGN KEY (s1) REFERENCES t1 (s1)); + * + * DROP TABLE IF EXISTS t2,t1; + * + * In the example above. The second create t2 does not fail, although t3 references it, + * and the data types do not match. + * + * The main problem is that this error comes on DROP TABLE IF EXISTS t2! Which prevents + * the table from being dropped - not good. + * + * So my idea here is to open the table, and if it fails, then the create table fails + * as well. + */ + if (!old_tab_id) { + tab = xt_use_table_no_lock(self, db, name, FALSE, FALSE, NULL, NULL); + xt_heap_release(self, tab); + } + } + catch_(a) { + /* Creation failed, delete the table files: */ + if (*path) + tab_delete_table_files(self, name, tab_id); + tab_remove_table_path(self, db, te_tab.te_tab_path); + xt_sb_set_size(self, &tab_def, 0); + throw_(); + } + cont_(a); + + xt_sb_set_size(self, &tab_def, 0); + + if (old_tab_id) { + try_(b) { + XTTableEntryPtr te_ptr; + + if ((te_ptr = (XTTableEntryPtr) xt_sl_find(self, db->db_table_by_id, &old_tab_id))) { + tab_remove_table_path(self, db, te_ptr->te_tab_path); + xt_sl_delete(self, db->db_table_by_id, &old_tab_id); + } + + /* Same purpose as above {LOAD-FOR-FKS} (although this should work, + * beacuse this is a TRUNCATE TABLE. + */ + tab = xt_use_table_no_lock(self, db, name, FALSE, FALSE, NULL, NULL); + xt_heap_release(self, tab); + } + catch_(b) { + /* Log this error, but do not return it, because + * it just involves the cleanup of the old table, + * the new table has been successfully created. + */ + xt_log_and_clear_exception(self); + } + cont_(b); + } + + popr_(); // Discard xt_sl_delete_from_info(&li_undo) + + freer_(); // xt_ht_unlock(db->db_tables) + freer_(); // xt_db_unlock_table_pool(table_pool) + + /* I open the table here, because I cannot rely on MySQL to do + * it after a create. This is normally OK, but with foreign keys + * tables can be referenced and then they are not opened + * before use. In this example, the INSERT opens t2, but t1 is + * not opened of the create. As a result the foreign key + * reference is not resolved. + * + * drop table t1, t2; + * CREATE TABLE t1 + * ( + * id INT PRIMARY KEY + * ) ENGINE=pbxt; + * + * CREATE TABLE t2 + * ( + * v INT, + * CONSTRAINT c1 FOREIGN KEY (v) REFERENCES t1(id) + * ) ENGINE=pbxt; + * + * --error 1452 + * INSERT INTO t2 VALUES(2); + */ + /* this code is not needed anymore as we open tables referred by FKs as necessary during checks + xt_ht_lock(self, db->db_tables); + pushr_(xt_ht_unlock, db->db_tables); + tab = xt_use_table_no_lock(self, db, name, FALSE, FALSE, NULL, NULL); + freer_(); // xt_ht_unlock(db->db_tables) + xt_heap_release(self, tab); + * CHANGED see {LOAD-FOR-FKS} above. + */ + + exit_(); +} + +xtPublic void xt_drop_table(XTThreadPtr self, XTPathStrPtr tab_name) +{ + XTDatabaseHPtr db = self->st_database; + XTOpenTablePoolPtr table_pool; + XTTableHPtr tab = NULL; + xtTableID tab_id = 0; + xtBool can_drop = TRUE; + + enter_(); + +#ifdef TRACE_CREATE_TABLES + printf("DROP %s\n", tab_name->ps_path); +#endif + + table_pool = tab_lock_table(self, tab_name, FALSE, TRUE, TRUE, &tab); + pushr_(xt_db_unlock_table_pool, table_pool); + xt_ht_lock(self, db->db_tables); + pushr_(xt_ht_unlock, db->db_tables); + pushr_(xt_heap_release, tab); + + if (table_pool) { + tab_id = tab->tab_id; /* tab is not null if returned table_pool is not null */ + /* check if other tables refer this */ + if (!self->st_ignore_fkeys) + can_drop = tab->tab_dic.dic_table->checkCanDrop(); + } + + if (can_drop) { + if (tab_id) { + XTTableEntryPtr te_ptr; + + xt_dl_delete_ext_data(self, tab, FALSE, TRUE); + freer_(); // xt_heap_release(self, tab) + + /* For the Windows version this must be done before we + * start to delete the underlying files! + */ + tab_close_mapped_files(self, tab); + + tab_delete_table_files(self, tab_name, tab_id); + + ASSERT(xt_get_self() == self); +#ifdef XT_STREAMING + xt_pbms_drop_table(tab_name->ps_path); +#endif + if ((te_ptr = (XTTableEntryPtr) xt_sl_find(self, db->db_table_by_id, &tab_id))) { + tab_remove_table_path(self, db, te_ptr->te_tab_path); + xt_sl_delete(self, db->db_table_by_id, &tab_id); + } + } + else { + freer_(); // xt_heap_release(self, tab) + } + + xt_ht_del(self, db->db_tables, tab_name); + } + else { /* cannot drop table because of FK dependencies */ + xt_throw_xterr(XT_CONTEXT, XT_ERR_ROW_IS_REFERENCED); + } + + freer_(); // xt_ht_unlock(db->db_tables) + freer_(); // xt_db_unlock_table_pool(table_pool) + exit_(); +} + +/* + * Record buffer size: + * ------------------- + * The size of the record buffer used to hold the row + * in memory. This buffer size does not include the BLOB data. + * About 8 bytes (a pointer and a size) is reserved for each BLOB + * in this buffer. + * + * The buffer size includes a number of "NULL" bytes followed by + * the data area. The NULL bytes contain 1 bit for every column, + * to indicate of the columns is NULL or not. + * + * The size of the buffer is 4/8-byte aligned, so it may be padded + * at the end. + * + * Fixed length rec. len.: + * ----------------------- + * If the record does not include any BLOBs then this is the size of the + * fixed length record. The size if the data in the data handle record + * need never be bigger then this length, if the record does not + * contain BLOBs. So this should be the maximum size set for + * AVG_ROW_LENGTH in this case. + * + * Handle data record size: + * ------------------------ + * This is the size of the handle data record. It is the data size + * plus the "max header size". + * + * Min/max header size: + * The min and max header size of the header in the data handle file. + * The larger header is used if a record has an extended data (data log + * file) component. + * + * Min/avg/max record size: + * ------------------------ + * These are variable length records sizes. That is, the size of records + * when stored in the variable length format. Variable length records + * do not have fixed fields sizes, instead the fields are packed one + * after the other, prefixed by a number of size indicator bytes. + * + * The average is an estimate of the average record size. This estimate + * is used if no AVG_ROW_LENGTH is specifically given. + * + * If the average estimate is withing 20% of the maximum size of the record, + * then the record will be handled as a fixed length record. + * + * Avg row len set for tab: + * ------------------------ + * This is the value set using AVG_ROW_LENGTH when the table is declared. + * + * Rows fixed length: + * ------------------ + * YES if the records of this table are handled as a fixed length records. + * In this case the table records will never have an extended record + * component. + * + * The size of the data area in the handle data record is set to the + * size of the MySQL data record ("Fixed length rec. len."). + * + * It also means that the record format used is identical to the MySQL + * record format. + * + * If the records are not fixed, then the variable length record format + * is used. Records size are then in the range specified by + * "Min/avg/max record size". + * + * Maximum fixed size: + * ------------------- + * This is the maximum size of a data log record. + * + * Minimum variable size: + * ------------------------ + * Records below this size are handled as a fixed length record size, unless + * the AVG_ROW_LENGTH is specifically set. + */ +xtPublic void xt_check_table(XTThreadPtr self, XTOpenTablePtr ot) +{ + XTTableHPtr tab = ot->ot_table; + xtRecordID prec_id; + XTTabRecExtDPtr rec_buf = (XTTabRecExtDPtr) ot->ot_row_rbuffer; + XTactExtRecEntryDRec ext_rec; + size_t log_size; + xtLogID log_id; + xtLogOffset log_offset; + xtRecordID rec_id; + xtRecordID prev_rec_id; + xtXactID xn_id; + xtRowID row_id; + u_llong free_rec_count = 0, free_count2 = 0; + u_llong delete_rec_count = 0; + u_llong alloc_rec_count = 0; + u_llong alloc_rec_bytes = 0; + u_llong min_comp_rec_len = 0; + u_llong max_comp_rec_len = 0; + size_t rec_size; + size_t row_size; + +#if defined(DUMP_CHECK_TABLE) || defined(CHECK_TABLE_STATS) + printf("\nCHECK TABLE: %s\n", tab->tab_name->ps_path); +#endif + + xt_lock_mutex(self, &tab->tab_db->db_co_ext_lock); + pushr_(xt_unlock_mutex, &tab->tab_db->db_co_ext_lock); + + xt_lock_mutex(self, &tab->tab_rec_lock); + pushr_(xt_unlock_mutex, &tab->tab_rec_lock); + +#ifdef CHECK_TABLE_STATS + printf("Record buffer size = %lu\n", (u_long) tab->tab_dic.dic_mysql_buf_size); + printf("Fixed length rec. len. = %lu\n", (u_long) tab->tab_dic.dic_mysql_rec_size); + printf("Handle data record size = %lu\n", (u_long) tab->tab_dic.dic_rec_size); + printf("Min/max header size = %d/%d\n", (int) offsetof(XTTabRecFix, rf_data), tab->tab_dic.dic_rec_fixed ? (int) offsetof(XTTabRecFix, rf_data) : (int) offsetof(XTTabRecExtDRec, re_data)); + printf("Min/avg/max record size = %llu/%llu/%llu\n", (u_llong) tab->tab_dic.dic_min_row_size, (u_llong) tab->tab_dic.dic_ave_row_size, (u_llong) tab->tab_dic.dic_max_row_size); + if (tab->tab_dic.dic_def_ave_row_size) + printf("Avg row len set for tab = %lu\n", (u_long) tab->tab_dic.dic_def_ave_row_size); + else + printf("Avg row len set for tab = not specified\n"); + printf("Rows fixed length = %s\n", tab->tab_dic.dic_rec_fixed ? "YES" : "NO"); + if (tab->tab_dic.dic_tab_flags & XT_TAB_FLAGS_TEMP_TAB) + printf("Table type = TEMP\n"); + if (tab->tab_dic.dic_def_ave_row_size) + printf("Maximum fixed size = %lu\n", (u_long) XT_TAB_MAX_FIX_REC_LENGTH_SPEC); + else + printf("Maximum fixed size = %lu\n", (u_long) XT_TAB_MAX_FIX_REC_LENGTH); + printf("Minimum variable size = %lu\n", (u_long) XT_TAB_MIN_VAR_REC_LENGTH); + printf("Minimum auto-increment = %llu\n", (u_llong) tab->tab_dic.dic_min_auto_inc); + printf("Number of columns = %lu\n", (u_long) tab->tab_dic.dic_no_of_cols); + printf("Number of fixed columns = %lu\n", (u_long) tab->tab_dic.dic_fix_col_count); + printf("Columns req. for index = %lu\n", (u_long) tab->tab_dic.dic_ind_cols_req); + if (tab->tab_dic.dic_ind_rec_len) + printf("Rec len req. for index = %llu\n", (u_llong) tab->tab_dic.dic_ind_rec_len); + printf("Columns req. for blobs = %lu\n", (u_long) tab->tab_dic.dic_blob_cols_req); + printf("Number of blob columns = %lu\n", (u_long) tab->tab_dic.dic_blob_count); + printf("Number of indices = %lu\n", (u_long) tab->tab_dic.dic_key_count); +#endif + +#ifdef DUMP_CHECK_TABLE + printf("Records:-\n"); + printf("Free list: %llu (%llu)\n", (u_llong) tab->tab_rec_free_id, (u_llong) tab->tab_rec_fnum); + printf("EOF: %llu\n", (u_llong) tab->tab_rec_eof_id); +#endif + + rec_size = XT_REC_EXT_HEADER_SIZE; + if (rec_size > tab->tab_recs.tci_rec_size) + rec_size = tab->tab_recs.tci_rec_size; + rec_id = 1; + while (rec_id < tab->tab_rec_eof_id) { + if (!xt_tab_get_rec_data(ot, rec_id, tab->tab_dic.dic_rec_size, ot->ot_row_rbuffer)) + xt_throw(self); + +#ifdef DUMP_CHECK_TABLE + printf("%-4llu ", (u_llong) rec_id); +#endif + switch (rec_buf->tr_rec_type_1 & XT_TAB_STATUS_MASK) { + case XT_TAB_STATUS_FREED: +#ifdef DUMP_CHECK_TABLE + printf("======== "); +#endif + free_rec_count++; + break; + case XT_TAB_STATUS_DELETE: +#ifdef DUMP_CHECK_TABLE + printf("delete "); +#endif + delete_rec_count++; + break; + case XT_TAB_STATUS_FIXED: +#ifdef DUMP_CHECK_TABLE + printf("record-F "); +#endif + alloc_rec_count++; + row_size = myxt_store_row_length(ot, (char *) ot->ot_row_rbuffer + XT_REC_FIX_HEADER_SIZE); + alloc_rec_bytes += row_size; + if (!min_comp_rec_len || row_size < min_comp_rec_len) + min_comp_rec_len = row_size; + if (row_size > max_comp_rec_len) + max_comp_rec_len = row_size; + break; + case XT_TAB_STATUS_VARIABLE: +#ifdef DUMP_CHECK_TABLE + printf("record-V "); +#endif + alloc_rec_count++; + row_size = myxt_load_row_length(ot, tab->tab_dic.dic_rec_size, ot->ot_row_rbuffer + XT_REC_FIX_HEADER_SIZE, NULL); + alloc_rec_bytes += row_size; + if (!min_comp_rec_len || row_size < min_comp_rec_len) + min_comp_rec_len = row_size; + if (row_size > max_comp_rec_len) + max_comp_rec_len = row_size; + break; + case XT_TAB_STATUS_EXT_DLOG: +#ifdef DUMP_CHECK_TABLE + printf("record-X "); +#endif + alloc_rec_count++; + row_size = XT_GET_DISK_4(rec_buf->re_log_dat_siz_4) + ot->ot_rec_size - XT_REC_EXT_HEADER_SIZE; + alloc_rec_bytes += row_size; + if (!min_comp_rec_len || row_size < min_comp_rec_len) + min_comp_rec_len = row_size; + if (row_size > max_comp_rec_len) + max_comp_rec_len = row_size; + break; + } +#ifdef DUMP_CHECK_TABLE + if (rec_buf->tr_rec_type_1 & XT_TAB_STATUS_CLEANED_BIT) + printf("C"); + else + printf(" "); +#endif + prev_rec_id = XT_GET_DISK_4(rec_buf->tr_prev_rec_id_4); + xn_id = XT_GET_DISK_4(rec_buf->tr_xact_id_4); + row_id = XT_GET_DISK_4(rec_buf->tr_row_id_4); + switch (rec_buf->tr_rec_type_1 & XT_TAB_STATUS_MASK) { + case XT_TAB_STATUS_FREED: +#ifdef DUMP_CHECK_TABLE + printf(" prev=%-3llu (xact=%-3llu row=%lu)\n", (u_llong) prev_rec_id, (u_llong) xn_id, (u_long) row_id); +#endif + break; + case XT_TAB_STATUS_EXT_DLOG: +#ifdef DUMP_CHECK_TABLE + printf(" prev=%-3llu xact=%-3llu row=%lu Xlog=%lu Xoff=%llu Xsiz=%lu\n", (u_llong) prev_rec_id, (u_llong) xn_id, (u_long) row_id, (u_long) XT_GET_DISK_2(rec_buf->re_log_id_2), (u_llong) XT_GET_DISK_6(rec_buf->re_log_offs_6), (u_long) XT_GET_DISK_4(rec_buf->re_log_dat_siz_4)); +#endif + + log_size = XT_GET_DISK_4(rec_buf->re_log_dat_siz_4); + XT_GET_LOG_REF(log_id, log_offset, rec_buf); + if (!self->st_dlog_buf.dlb_read_log(log_id, log_offset, offsetof(XTactExtRecEntryDRec, er_data), (xtWord1 *) &ext_rec, self)) + xt_log_and_clear_exception(self); + else { + size_t log_size2; + xtTableID curr_tab_id; + xtRecordID curr_rec_id; + + log_size2 = XT_GET_DISK_4(ext_rec.er_data_size_4); + curr_tab_id = XT_GET_DISK_4(ext_rec.er_tab_id_4); + curr_rec_id = XT_GET_DISK_4(ext_rec.er_rec_id_4); + if (log_size2 != log_size || curr_tab_id != tab->tab_id || curr_rec_id != rec_id) { + xt_logf(XT_INFO, "Table %s: record %llu, extended record %lu:%llu not valid\n", tab->tab_name, (u_llong) rec_id, (u_long) log_id, (u_llong) log_offset); + } + } + break; + default: +#ifdef DUMP_CHECK_TABLE + printf(" prev=%-3llu xact=%-3llu row=%lu\n", (u_llong) prev_rec_id, (u_llong) xn_id, (u_long) row_id); +#endif + break; + } + rec_id++; + } + +#ifdef CHECK_TABLE_STATS + if (alloc_rec_count) { + printf("Minumum comp. rec. len. = %llu\n", (u_llong) min_comp_rec_len); + printf("Average comp. rec. len. = %llu\n", (u_llong) ((double) alloc_rec_bytes / (double) alloc_rec_count + (double) 0.5)); + printf("Maximum comp. rec. len. = %llu\n", (u_llong) max_comp_rec_len); + } + printf("Free record count = %llu\n", (u_llong) free_rec_count); + printf("Deleted record count = %llu\n", (u_llong) delete_rec_count); + printf("Allocated record count = %llu\n", (u_llong) alloc_rec_count); +#endif + if (tab->tab_rec_fnum != free_rec_count) + xt_logf(XT_INFO, "Table %s: incorrect number of free blocks, %llu, should be: %llu\n", tab->tab_name, (u_llong) free_rec_count, (u_llong) tab->tab_rec_fnum); + + /* Checking the free list: */ + prec_id = 0; + rec_id = tab->tab_rec_free_id; + while (rec_id) { + if (rec_id >= tab->tab_rec_eof_id) { + xt_logf(XT_INFO, "Table %s: invalid reference on free list: %llu, ", tab->tab_name, (u_llong) rec_id); + if (prec_id) + xt_logf(XT_INFO, "reference by: %llu\n", (u_llong) prec_id); + else + xt_logf(XT_INFO, "reference by list head pointer\n"); + break; + } + if (!xt_tab_get_rec_data(ot, rec_id, XT_REC_FIX_HEADER_SIZE, (xtWord1 *) rec_buf)) { + xt_log_and_clear_exception(self); + break; + } + if ((rec_buf->tr_rec_type_1 & XT_TAB_STATUS_MASK) != XT_TAB_STATUS_FREED) + xt_logf(XT_INFO, "Table %s: record, %llu, on free list is not free\n", tab->tab_name, (u_llong) rec_id); + free_count2++; + prec_id = rec_id; + rec_id = XT_GET_DISK_4(rec_buf->tr_prev_rec_id_4); + } + if (free_count2 < free_rec_count) + xt_logf(XT_INFO, "Table %s: not all free blocks (%llu) on free list: %llu\n", tab->tab_name, (u_llong) free_rec_count, (u_llong) free_count2); + + freer_(); // xt_unlock_mutex_ns(&tab->tab_rec_lock); + + xtRefID ref_id; + + xt_lock_mutex(self, &tab->tab_row_lock); + pushr_(xt_unlock_mutex, &tab->tab_row_lock); + +#ifdef DUMP_CHECK_TABLE + printf("Rows:-\n"); + printf("Free list: %llu (%llu)\n", (u_llong) tab->tab_row_free_id, (u_llong) tab->tab_row_fnum); + printf("EOF: %llu\n", (u_llong) tab->tab_row_eof_id); +#endif + + rec_id = 1; + while (rec_id < tab->tab_row_eof_id) { + if (!tab->tab_rows.xt_tc_read_4(ot->ot_row_file, rec_id, &ref_id, self)) + xt_throw(self); +#ifdef DUMP_CHECK_TABLE + printf("%-3llu ", (u_llong) rec_id); +#endif +#ifdef DUMP_CHECK_TABLE + if (ref_id == 0) + printf("====== 0\n"); + else + printf("in use %llu\n", (u_llong) ref_id); +#endif + rec_id++; + } + + freer_(); // xt_unlock_mutex(&tab->tab_row_lock); + +#ifdef CHECK_INDEX_ON_CHECK_TABLE + xt_check_indices(ot); +#endif + freer_(); // xt_unlock_mutex(&tab->tab_db->db_co_ext_lock); +} + +xtPublic void xt_rename_table(XTThreadPtr self, XTPathStrPtr old_name, XTPathStrPtr new_name) +{ + XTDatabaseHPtr db = self->st_database; + XTOpenTablePoolPtr table_pool; + XTTableHPtr tab = NULL; + char table_name[XT_MAX_TABLE_FILE_NAME_SIZE]; + char *postfix; + XTFilesOfTableRec ft; + XTDictionaryRec dic; + xtTableID tab_id; + XTTableEntryPtr te_ptr; + char *te_new_name; + XTTablePathPtr te_new_path; + XTTablePathPtr te_old_path; + char to_path[PATH_MAX]; + + memset(&dic, 0, sizeof(dic)); + +#ifdef TRACE_CREATE_TABLES + printf("RENAME %s --> %s\n", old_name->ps_path, new_name->ps_path); +#endif + if (strlen(xt_last_name_of_path(new_name->ps_path)) > XT_TABLE_NAME_SIZE-1) + xt_throw_taberr(XT_CONTEXT, XT_ERR_NAME_TOO_LONG, new_name); + + /* MySQL renames the table while it is in use. Here is + * the sequence: + * + * OPEN tab1 + * CREATE tmp_tab + * OPEN tmp_tab + * COPY tab1 -> tmp_tab + * CLOSE tmp_tab + * RENAME tab1 -> tmp2_tab + * RENAME tmp_tab -> tab1 + * CLOSE tab1 (tmp2_tab) + * DELETE tmp2_tab + * OPEN tab1 + * + * Since the table is open when it is renamed, I cannot + * get exclusive use of the table for this operation. + * + * So instead we just make sure that the sweeper is not + * using the table. + */ + table_pool = tab_lock_table(self, old_name, FALSE, TRUE, FALSE, &tab); + pushr_(xt_db_unlock_table_pool, table_pool); + xt_ht_lock(self, db->db_tables); + pushr_(xt_ht_unlock, db->db_tables); + tab_id = tab->tab_id; + myxt_move_dictionary(&dic, &tab->tab_dic); + pushr_(myxt_free_dictionary, &dic); + pushr_(xt_heap_release, tab); + + /* Unmap the memory mapped table files: + * For windows this must be done before we + * can rename the files. + */ + tab_close_mapped_files(self, tab); + + freer_(); // xt_heap_release(self, old_tab) + + /* Create the new name and path: */ + te_new_name = xt_dup_string(self, xt_last_name_of_path(new_name->ps_path)); + pushr_(xt_free, te_new_name); + te_new_path = tab_get_table_path(self, db, new_name, FALSE); + pushr_(tab_free_table_path, te_new_path); + + te_ptr = (XTTableEntryPtr) xt_sl_find(self, db->db_table_by_id, &tab_id); + + /* Remove the table from the Database directory: */ + xt_ht_del(self, db->db_tables, old_name); + + xt_enum_files_of_tables_init(old_name, tab_id, &ft); + while (xt_enum_files_of_tables_next(&ft)) { + postfix = xt_tab_file_to_name(XT_MAX_TABLE_FILE_NAME_SIZE, table_name, ft.ft_file_path); + + xt_strcpy(PATH_MAX, to_path, new_name->ps_path); + xt_strcat(PATH_MAX, to_path, postfix); + + if (!xt_fs_rename(NULL, ft.ft_file_path, to_path)) + xt_log_and_clear_exception(self); + } + + /* Switch the table name and path: */ + xt_free(self, te_ptr->te_tab_name); + te_ptr->te_tab_name = te_new_name; + te_old_path = te_ptr->te_tab_path; + te_ptr->te_tab_path = te_new_path; + tab_remove_table_path(self, db, te_old_path); + + popr_(); // Discard tab_free_table_path(te_new_path); + popr_(); // Discard xt_free(te_new_name); + + tab = xt_use_table_no_lock(self, db, new_name, FALSE, FALSE, &dic, NULL); + xt_heap_release(self, tab); + + freer_(); // myxt_free_dictionary(&dic) + freer_(); // xt_ht_unlock(db->db_tables) + freer_(); // xt_db_unlock_table_pool(table_pool) +} + +xtPublic XTTableHPtr xt_use_table(XTThreadPtr self, XTPathStrPtr name, xtBool no_load, xtBool missing_ok, xtBool *opened) +{ + XTTableHPtr tab; + XTDatabaseHPtr db = self->st_database; + + xt_ht_lock(self, db->db_tables); + pushr_(xt_ht_unlock, db->db_tables); + tab = xt_use_table_no_lock(self, db, name, no_load, missing_ok, NULL, opened); + freer_(); + return tab; +} + +xtPublic void xt_sync_flush_table(XTThreadPtr self, XTOpenTablePtr ot) +{ + XTTableHPtr tab = ot->ot_table; + XTDatabaseHPtr db = tab->tab_db; + + /* Wakeup the sweeper: + * We want the sweeper to check if there is anything to do, + * so we must wake it up. + * Once it has done all it can, it will go back to sleep. + * This should be good enough. + * + * NOTE: I all cases, we do not wait if the sweeper is in + * error state. + */ + if (db->db_sw_idle) { + u_int check_count = db->db_sw_check_count; + + for (;;) { + xt_wakeup_sweeper(db); + if (!db->db_sw_thread || db->db_sw_idle != XT_THREAD_IDLE || check_count != db->db_sw_check_count) + break; + xt_sleep_milli_second(10); + } + } + + /* Wait for the sweeper to become idle: */ + xt_lock_mutex(self, &db->db_sw_lock); + pushr_(xt_unlock_mutex, &db->db_sw_lock); + while (db->db_sw_thread && !db->db_sw_idle) { + xt_timed_wait_cond(self, &db->db_sw_cond, &db->db_sw_lock, 10); + } + freer_(); // xt_unlock_mutex(&db->db_sw_lock) + + /* Wait for the writer to write out all operations on the table: + * We also do not wait for the writer if it is in + * error state. + */ + while (db->db_wr_thread && + db->db_wr_idle != XT_THREAD_INERR && + XTTableSeq::xt_op_is_before(tab->tab_head_op_seq+1, tab->tab_seq.ts_next_seq)) { + /* Flush the log, in case this is holding up the + * writer! + */ + if (!db->db_xlog.xlog_flush(self)) + xt_throw(self); + + xt_lock_mutex(self, &db->db_wr_lock); + pushr_(xt_unlock_mutex, &db->db_wr_lock); + db->db_wr_thread_waiting++; + /* + * Wake the writer if it is sleeping. In order to + * flush a table we must wait for the writer to complete + * committing all the changes in the table to the database. + */ + if (db->db_wr_idle) { + if (!xt_broadcast_cond_ns(&db->db_wr_cond)) + xt_log_and_clear_exception_ns(); + } + + freer_(); // xt_unlock_mutex(&db->db_wr_lock) + xt_sleep_milli_second(10); + + xt_lock_mutex(self, &db->db_wr_lock); + pushr_(xt_unlock_mutex, &db->db_wr_lock); + db->db_wr_thread_waiting--; + freer_(); // xt_unlock_mutex(&db->db_wr_lock) + } + + xt_flush_table(self, ot); +} + +xtPublic xtBool xt_flush_record_row(XTOpenTablePtr ot, off_t *bytes_flushed, xtBool have_table_lock) +{ + XTTableHeadDRec rec_head; + XTTableHPtr tab = ot->ot_table; + off_t to_flush; + XTCheckPointTablePtr cp_tab; + XTCheckPointStatePtr cp = NULL; + + if (!xt_begin_checkpoint(tab->tab_db, have_table_lock, ot->ot_thread)) + return FAILED; + + xt_lock_mutex_ns(&tab->tab_rec_flush_lock); + + ASSERT_NS(ot->ot_thread == xt_get_self()); + /* Make sure that the table recovery point, in + * particular the operation ID is recorded + * before all other flush activity! + * + * This is because only operations after the + * recovery point in the header are applied + * to the table on recovery. + * + * So the operation ID is recorded before the + * flush activity, and written after all is done. + */ + xt_tab_store_header(ot, &rec_head); + +#ifdef TRACE_FLUSH + printf("FLUSH rec/row %d %s\n", (int) tab->tab_bytes_to_flush, tab->tab_name->ps_path); + fflush(stdout); +#endif + /* Write the table header: */ + if (tab->tab_flush_pending) { + tab->tab_flush_pending = FALSE; + // Want to see how much was to be flushed in the debugger: + to_flush = tab->tab_bytes_to_flush; + tab->tab_bytes_to_flush = 0; + if (bytes_flushed) + *bytes_flushed += to_flush; + /* Flush the table data: */ + if (!(tab->tab_dic.dic_tab_flags & XT_TAB_FLAGS_TEMP_TAB)) { + if (!XT_FLUSH_RR_FILE(ot->ot_rec_file, &ot->ot_thread->st_statistics.st_rec, ot->ot_thread) || + !XT_FLUSH_RR_FILE(ot->ot_row_file, &ot->ot_thread->st_statistics.st_rec, ot->ot_thread)) { + tab->tab_flush_pending = TRUE; + goto failed; + } + } + + /* The header includes the operation number which + * must be written AFTER all other data, + * because operations will not be applied again. + */ + if (!xt_tab_write_header(ot, &rec_head, ot->ot_thread)) { + tab->tab_flush_pending = TRUE; + goto failed; + } + } + + /* Flush the auto-increment: */ + if (xt_db_auto_increment_mode == 1) { + if (tab->tab_auto_inc != tab->tab_dic.dic_min_auto_inc) { + tab->tab_dic.dic_min_auto_inc = tab->tab_auto_inc; + if (!xt_tab_write_min_auto_inc(ot)) + goto failed; + } + } + + /* Mark this table as record/row flushed: */ + cp = &tab->tab_db->db_cp_state; + xt_lock_mutex_ns(&cp->cp_state_lock); + if (cp->cp_running) { + cp_tab = (XTCheckPointTablePtr) xt_sl_find(NULL, cp->cp_table_ids, &tab->tab_id); + if (cp_tab && (cp_tab->cpt_flushed & XT_CPT_ALL_FLUSHED) != XT_CPT_ALL_FLUSHED) { + cp_tab->cpt_flushed |= XT_CPT_REC_ROW_FLUSHED; + if ((cp_tab->cpt_flushed & XT_CPT_ALL_FLUSHED) == XT_CPT_ALL_FLUSHED) { + ASSERT_NS(cp->cp_flush_count < xt_sl_get_size(cp->cp_table_ids)); + cp->cp_flush_count++; + } + } + } + xt_unlock_mutex_ns(&cp->cp_state_lock); + +#ifdef TRACE_FLUSH + printf("FLUSH --end-- %s\n", tab->tab_name->ps_path); + fflush(stdout); +#endif + xt_unlock_mutex_ns(&tab->tab_rec_flush_lock); + + if (!xt_end_checkpoint(tab->tab_db, ot->ot_thread, NULL)) + return FAILED; + return OK; + + failed: + xt_unlock_mutex_ns(&tab->tab_rec_flush_lock); + return FAILED; +} + +xtPublic void xt_flush_table(XTThreadPtr self, XTOpenTablePtr ot) +{ + /* GOTCHA [*10*]: This bug was difficult to find. + * It occured on Windows in the multi_update + * test, sometimes. + * + * What happens is the checkpointer starts to + * flush the table, and gets to the + * XT_FLUSH_RR_FILE part. + * + * Then a rename occurs, and the user thread + * flushes the table, and goes through and + * writes the table header, with the most + * recent table operation (the last operation + * that occurred). + * + * The checkpointer the completes and + * also writes the header, but with old + * values (as read in xt_tab_store_header()). + * + * The then user thread continues, and + * reopens the table after rename. + * On reopen, it reads the old value from the header, + * and sets the current operation number. + * + * Now there is a problem in the able cache, + * because some cache pages have operation numbers + * that are greater than current operation + * number! + * + * This later lead to the free-er hanging while + * it waited for an operation to be + * written to the disk that never would be. + * This is because a page can only be freed when + * the head operation number has passed the + * page operation number. + * + * Which indicates that the page has been written + * to disk. + */ + + if (!xt_flush_record_row(ot, NULL, FALSE)) + xt_throw(self); + + /* This was before the table data flush, + * (after xt_tab_store_header() above, + * but I don't think it makes any difference. + * Because in the checkpointer it was at this + * position. + */ + if (!xt_flush_indices(ot, NULL, FALSE)) + xt_throw(self); + +} + +xtPublic XTOpenTablePtr tab_open_table(XTTableHPtr tab) +{ + volatile XTOpenTablePtr ot; + XTThreadPtr self; + + if (!(ot = (XTOpenTablePtr) xt_malloc_ns(sizeof(XTOpenTableRec)))) + return NULL; + memset(ot, 0, offsetof(XTOpenTableRec, ot_ind_wbuf)); + + self = xt_get_self(); + try_(a) { + xt_heap_reference(self, tab); + ot->ot_table = tab; +#ifdef XT_USE_ROW_REC_MMAP_FILES + ot->ot_row_file = xt_open_fmap(self, ot->ot_table->tab_row_file->fil_path, xt_db_row_file_grow_size); + ot->ot_rec_file = xt_open_fmap(self, ot->ot_table->tab_rec_file->fil_path, xt_db_data_file_grow_size); +#else + ot->ot_row_file = xt_open_file(self, ot->ot_table->tab_row_file->fil_path, XT_FS_DEFAULT); + ot->ot_rec_file = xt_open_file(self, ot->ot_table->tab_rec_file->fil_path, XT_FS_DEFAULT); +#endif +#ifdef XT_USE_DIRECT_IO_ON_INDEX + ot->ot_ind_file = xt_open_file(self, ot->ot_table->tab_ind_file->fil_path, XT_FS_MISSING_OK | XT_FS_DIRECT_IO); +#else + ot->ot_ind_file = xt_open_file(self, ot->ot_table->tab_ind_file->fil_path, XT_FS_MISSING_OK); +#endif + } + catch_(a) { + ; + } + cont_(a); + + if (!ot->ot_table || !ot->ot_row_file || !ot->ot_rec_file) + goto failed; + + if (!(ot->ot_row_rbuffer = (xtWord1 *) xt_malloc_ns(ot->ot_table->tab_dic.dic_rec_size))) + goto failed; + ot->ot_row_rbuf_size = ot->ot_table->tab_dic.dic_rec_size; + if (!(ot->ot_row_wbuffer = (xtWord1 *) xt_malloc_ns(ot->ot_table->tab_dic.dic_rec_size))) + goto failed; + ot->ot_row_wbuf_size = ot->ot_table->tab_dic.dic_rec_size; + + /* Cache this stuff to speed access a bit: */ + ot->ot_rec_fixed = ot->ot_table->tab_dic.dic_rec_fixed; + ot->ot_rec_size = ot->ot_table->tab_dic.dic_rec_size; + + return ot; + + failed: + tab_close_table(ot); + return NULL; +} + +xtPublic XTOpenTablePtr xt_open_table(XTTableHPtr tab) +{ + return tab_open_table(tab); +} + +xtPublic void xt_close_table(XTOpenTablePtr ot, xtBool flush, xtBool have_table_lock) +{ + if (flush) { + if (!xt_flush_record_row(ot, NULL, have_table_lock)) + xt_log_and_clear_exception_ns(); + + if (!xt_flush_indices(ot, NULL, have_table_lock)) + xt_log_and_clear_exception_ns(); + } + tab_close_table(ot); +} + +xtPublic int xt_use_table_by_id(XTThreadPtr self, XTTableHPtr *r_tab, XTDatabaseHPtr db, xtTableID tab_id) +{ + XTTableEntryPtr te_ptr; + XTTableHPtr tab = NULL; + int r = XT_TAB_OK; + char path[PATH_MAX]; + + if (!db) + xt_throw_xterr(XT_CONTEXT, XT_ERR_NO_DATABASE_IN_USE); + xt_ht_lock(self, db->db_tables); + pushr_(xt_ht_unlock, db->db_tables); + + te_ptr = (XTTableEntryPtr) xt_sl_find(self, db->db_table_by_id, &tab_id); + if (te_ptr) { + if (!(tab = te_ptr->te_table)) { + /* Open the table: */ + xt_strcpy(PATH_MAX, path, te_ptr->te_tab_path->tp_path); + xt_add_dir_char(PATH_MAX, path); + xt_strcat(PATH_MAX, path, te_ptr->te_tab_name); + r = tab_new_handle(self, &tab, db, tab_id, (XTPathStrPtr) path, TRUE, NULL); + } + } + else + r = XT_TAB_NOT_FOUND; + + if (tab) + xt_heap_reference(self, tab); + *r_tab = tab; + + freer_(); // xt_ht_unlock(db->db_tables) + return r; +} + +/* The fixed part of the record is already in the row buffer. + * This function loads the extended part, expanding the row + * buffer if necessary. + */ +xtPublic xtBool xt_tab_load_ext_data(XTOpenTablePtr ot, xtRecordID load_rec_id, xtWord1 *buffer, u_int cols_req) +{ + size_t log_size; + xtLogID log_id; + xtLogOffset log_offset; + xtWord1 save_buffer[offsetof(XTactExtRecEntryDRec, er_data)]; + xtBool retried = FALSE; + XTactExtRecEntryDPtr ext_data_ptr; + size_t log_size2; + xtTableID curr_tab_id; + xtRecordID curr_rec_id; + + log_size = XT_GET_DISK_4(((XTTabRecExtDPtr) ot->ot_row_rbuffer)->re_log_dat_siz_4); + XT_GET_LOG_REF(log_id, log_offset, (XTTabRecExtDPtr) ot->ot_row_rbuffer); + + if (ot->ot_rec_size + log_size > ot->ot_row_rbuf_size) { + if (!xt_realloc_ns((void **) &ot->ot_row_rbuffer, ot->ot_rec_size + log_size)) + return FAILED; + ot->ot_row_rbuf_size = ot->ot_rec_size + log_size; + } + + /* Read the extended part first: */ + ext_data_ptr = (XTactExtRecEntryDPtr) (ot->ot_row_rbuffer + ot->ot_rec_size - offsetof(XTactExtRecEntryDRec, er_data)); + + /* Save the data which the header will overwrite: */ + memcpy(save_buffer, ext_data_ptr, offsetof(XTactExtRecEntryDRec, er_data)); + + reread: + if (!ot->ot_thread->st_dlog_buf.dlb_read_log(log_id, log_offset, offsetof(XTactExtRecEntryDRec, er_data) + log_size, (xtWord1 *) ext_data_ptr, ot->ot_thread)) + goto retry_read; + + log_size2 = XT_GET_DISK_4(ext_data_ptr->er_data_size_4); + curr_tab_id = XT_GET_DISK_4(ext_data_ptr->er_tab_id_4); + curr_rec_id = XT_GET_DISK_4(ext_data_ptr->er_rec_id_4); + + if (log_size2 != log_size || curr_tab_id != ot->ot_table->tab_id || curr_rec_id != load_rec_id) { + /* [(3)] This can happen in the following circumstances: + * - A new record is created, but the data log is not + * flushed. + * - The server quits. + * - On restart the transaction is rolled back, but the data record + * was not written, so later a new record could be written at this + * location. + * - Later the sweeper tries to cleanup this record, and finds + * that a different record has been written at this position. + * + * NOTE: Index entries can only be written to disk for records + * that have been committed to the disk, because uncommitted + * records may not exist in order to remove the index entry + * on cleanup. + */ + xt_register_xterr(XT_REG_CONTEXT, XT_ERR_BAD_EXT_RECORD); + goto retry_read; + } + + /* Restore the saved area: */ + memcpy(ext_data_ptr, save_buffer, offsetof(XTactExtRecEntryDRec, er_data)); + + if (retried) + xt_unlock_mutex_ns(&ot->ot_table->tab_db->db_co_ext_lock); + return myxt_load_row(ot, ot->ot_row_rbuffer + XT_REC_EXT_HEADER_SIZE, buffer, cols_req); + + retry_read: + if (!retried) { + /* (1) It may be that reading the log fails because the garbage collector + * has moved the record since we determined the location. + * We handle this here, by re-reading the data the garbage collector + * would have updated. + * + * (2) It may also happen that a new record is just being updated or + * inserted. It is possible that the handle part of the record + * has been written, but not yet the overflow. + * This means that repeating the read attempt could work. + * + * (3) The extended data has been written by another handler and not yet + * flushed. This should not happen because on committed extended + * records are read, and all data should be flushed before + * commit! + * + * NOTE: (2) above is not a problem when versioning is working + * correctly. In this case, we should never try to read the extended + * part of an uncommitted record (belonging to some other thread/ + * transaction). + */ + XTTabRecExtDRec rec_buf; + + xt_lock_mutex_ns(&ot->ot_table->tab_db->db_co_ext_lock); + retried = TRUE; + + if (!xt_tab_get_rec_data(ot, load_rec_id, XT_REC_EXT_HEADER_SIZE, (xtWord1 *) &rec_buf)) + goto failed; + + XT_GET_LOG_REF(log_id, log_offset, &rec_buf); + goto reread; + } + + failed: + if (retried) + xt_unlock_mutex_ns(&ot->ot_table->tab_db->db_co_ext_lock); + return FAILED; +} + +xtPublic xtBool xt_tab_put_rec_data(XTOpenTablePtr ot, xtRecordID rec_id, size_t size, xtWord1 *buffer, xtOpSeqNo *op_seq) +{ + register XTTableHPtr tab = ot->ot_table; + + ASSERT_NS(rec_id); + + return tab->tab_recs.xt_tc_write(ot->ot_rec_file, rec_id, 0, size, buffer, op_seq, TRUE, ot->ot_thread); +} + +xtPublic xtBool xt_tab_put_log_op_rec_data(XTOpenTablePtr ot, u_int status, xtRecordID free_rec_id, xtRecordID rec_id, size_t size, xtWord1 *buffer) +{ + register XTTableHPtr tab = ot->ot_table; + xtOpSeqNo op_seq; + + ASSERT_NS(rec_id); + + if (status == XT_LOG_ENT_REC_MOVED) { + if (!tab->tab_recs.xt_tc_write(ot->ot_rec_file, rec_id, offsetof(XTTabRecExtDRec, re_log_id_2), size, buffer, &op_seq, TRUE, ot->ot_thread)) + return FAILED; + } +#ifdef DEBUG + else if (status == XT_LOG_ENT_REC_CLEANED_1) { + ASSERT_NS(0); // shouldn't be used anymore + } +#endif + else { + if (!tab->tab_recs.xt_tc_write(ot->ot_rec_file, rec_id, 0, size, buffer, &op_seq, TRUE, ot->ot_thread)) + return FAILED; + } + + return xt_xlog_modify_table(ot, status, op_seq, free_rec_id, rec_id, size, buffer); +} + +xtPublic xtBool xt_tab_put_log_rec_data(XTOpenTablePtr ot, u_int status, xtRecordID free_rec_id, xtRecordID rec_id, size_t size, xtWord1 *buffer, xtOpSeqNo *op_seq) +{ + register XTTableHPtr tab = ot->ot_table; + + ASSERT_NS(rec_id); + + if (status == XT_LOG_ENT_REC_MOVED) { + if (!tab->tab_recs.xt_tc_write(ot->ot_rec_file, rec_id, offsetof(XTTabRecExtDRec, re_log_id_2), size, buffer, op_seq, TRUE, ot->ot_thread)) + return FAILED; + } + else { + if (!tab->tab_recs.xt_tc_write(ot->ot_rec_file, rec_id, 0, size, buffer, op_seq, TRUE, ot->ot_thread)) + return FAILED; + } + + return xt_xlog_modify_table(ot, status, *op_seq, free_rec_id, rec_id, size, buffer); +} + +xtPublic xtBool xt_tab_get_rec_data(XTOpenTablePtr ot, xtRecordID rec_id, size_t size, xtWord1 *buffer) +{ + register XTTableHPtr tab = ot->ot_table; + + ASSERT_NS(rec_id); + + return tab->tab_recs.xt_tc_read(ot->ot_rec_file, rec_id, (size_t) size, buffer, ot->ot_thread); +} + +/* + * Note: this function grants locks even to transactions that + * are not specifically waiting for this transaction. + * This is required, because all threads waiting for + * a lock should be considered "equal". In other words, + * they should not have to wait for the "right" transaction + * before they get the lock, or it will turn into a + * race to wait for the correct transaction. + * + * A transaction T1 can end up waiting for the wrong transaction + * T2, because T2 has released the lock, and given it to T3. + * Of course, T1 will wake up soon and realize this, but + * it is a matter of timing. + * + * The main point is that T2 has release the lock because + * it has ended (see {RELEASING-LOCKS} for more details) + * and therefore, there is no danger of it claiming the + * lock again, which can lead to a deadlock if T1 is + * given the lock instead of T3 in the example above. + * Then, if T2 tries to regain the lock before T1 + * realizes that it has the lock. + */ +//static xtBool tab_get_lock_after_wait(XTThreadPtr thread, XTLockWaitPtr lw) +//{ +// register XTTableHPtr tab = lw->lw_ot->ot_table; + + /* {ROW-LIST-LOCK} + * I don't believe this lock is required. If it is, please explain why!! + * XT_TAB_ROW_READ_LOCK(&tab->tab_row_rwlock[gl->lw_row_id % XT_ROW_RWLOCKS], thread); + * + * With the old row lock implementation a XT_TAB_ROW_WRITE_LOCK was required because + * the row locking did not have its own locks. + * The new list locking has its own locks. I was using XT_TAB_ROW_READ_LOCK, + * but i don't think this is required. + */ +// return tab->tab_locks.xt_set_temp_lock(lw->lw_ot, lw, &lw->lw_thread->st_lock_list); +//} + +/* + * NOTE: Previously this function did not gain the row lock. + * If this change is a problem, please document why! + * The previously implementation did wait until no lock was on the + * row. + * + * I am thinking that it is simply a good idea to grab the lock, + * instead of waiting for no lock, before the retry. But it could + * result in locking more than required! + */ +static xtBool tab_wait_for_update(register XTOpenTablePtr ot, xtRowID row_id, xtXactID xn_id, XTThreadPtr thread) +{ + XTLockWaitRec lw; + XTXactWaitRec xw; + xtBool ok; + + xw.xw_xn_id = xn_id; + + lw.lw_thread = thread; + lw.lw_ot = ot; + lw.lw_row_id = row_id; + lw.lw_row_updated = FALSE; + + /* First try to get the lock: */ + if (!ot->ot_table->tab_locks.xt_set_temp_lock(ot, &lw, &thread->st_lock_list)) + return FAILED; + if (lw.lw_curr_lock != XT_NO_LOCK) + /* Wait for the lock, then the transaction: */ + ok = xt_xn_wait_for_xact(thread, &xw, &lw); + else + /* Just wait for the transaction: */ + ok = xt_xn_wait_for_xact(thread, &xw, NULL); + +#ifdef DEBUG_LOCK_QUEUE + ot->ot_table->tab_locks.rl_check(&lw); +#endif + return ok; +} + +/* {WAIT-FOR} + * XT_OLD - The record is old. No longer visible because there is + * newer committed record before it in the record list. + * This is a special case of FALSE (the record is not visible). + * (see {WAIT-FOR} for details). + * It is significant because if we find too many of these when + * searching for records, then we have reason to believe the + * sweeper is far behind. This can happen in a test like this: + * runTest(INCREMENT_TEST, 2, INCREMENT_TEST_UPDATE_COUNT); + * What happens is T1 detects an updated row by T2, + * but T2 has not committed yet. + * It waits for T2. T2 commits and updates again before T1 + * can update. + * + * Of course if we got a lock on the row when T2 quits, then + * this would not happen! + */ + +/* + * Is a record visible? + * Returns TRUE, FALSE, XT_ERR. + * + * TRUE - The record is visible. + * FALSE - The record is not visible. + * XT_ERR - An exception (error) occurred. + * XT_NEW - The most recent variation of this row has been returned + * and is to be used instead of the input! + * XT_REREAD - Re-read the record, and try again. + * + * Basically, a record is visible if it was committed on or before + * the transactions "visible time" (st_visible_time), and there + * are no other visible records before this record in the + * variation chain for the record. + * + * This holds in general, but you don't always get to see the + * visible record (as defined in this sence). + * + * On any kind of update (SELECT FOR UPDATE, UPDATE or DELETE), you + * get to see the most recent variation of the row! + * + * So on update, this function will wait if necessary for a recent + * update to be committed. + * + * So an update is a kind of "committed read" with a wait for + * uncommitted records. + * + * The result: + * - INSERTS may not seen by the update read, depending on when + * they occur. + * - Records may be returned in non-index order. + * - New records returned must be checked again by an index scan + * to make sure they conform to the condition! + * + * CREATE TABLE test_tab (ID int primary key, Value int, Name varchar(20), + * index(Value, Name)) ENGINE=pbxt; + * INSERT test_tab values(4, 2, 'D'); + * INSERT test_tab values(5, 2, 'E'); + * INSERT test_tab values(6, 2, 'F'); + * INSERT test_tab values(7, 2, 'G'); + * + * -- C1 + * begin; + * select * from test_tab where id = 6 for update; + * -- C2 + * begin; + * select * from test_tab where value = 2 order by value, name for update; + * -- C1 + * update test_tab set Name = 'A' where id = 7; + * commit; + * -- C2 + * Result order D, E, F, A. + * + * But Jim does it like this, so it should be OK. + */ +static int tab_visible(register XTOpenTablePtr ot, XTTabRecHeadDPtr rec_head, xtRecordID *new_rec_id) +{ + XTThreadPtr thread = ot->ot_thread; + xtXactID xn_id; + XTTabRecHeadDRec var_head; + xtRowID row_id; + xtRecordID var_rec_id; + register XTTableHPtr tab; + xtBool wait = FALSE; + xtXactID wait_xn_id = 0; +#ifdef TRACE_VARIATIONS + char t_buf[500]; + int len; +#endif + int result = TRUE; + xtBool rec_clean; + xtRecordID invalid_rec; + + retry: + /* It can be that between the time that I read the index, + * and the time that I try to access the + * record, that the record is removed by + * the sweeper! + */ + if (XT_REC_NOT_VALID(rec_head->tr_rec_type_1)) + return FALSE; + + row_id = XT_GET_DISK_4(rec_head->tr_row_id_4); + + /* This can happen if the row has been removed, and + * reused: + */ + if (ot->ot_curr_row_id && row_id != ot->ot_curr_row_id) + return FALSE; + +#ifdef TRACE_VARIATIONS + len = sprintf(t_buf, "row=%d rec=%d ", (int) row_id, (int) ot->ot_curr_rec_id); +#endif + if (!(rec_clean = XT_REC_IS_CLEAN(rec_head->tr_rec_type_1))) { + /* The record is not clean, which means it has not been swept. + * So we have to check if it is visible. + */ + xn_id = XT_GET_DISK_4(rec_head->tr_xact_id_4); + switch (xt_xn_status(ot, xn_id, ot->ot_curr_rec_id)) { + case XT_XN_VISIBLE: + break; + case XT_XN_NOT_VISIBLE: + if (ot->ot_for_update) { + /* It is visible, only if it is an insert, + * which means if has no previous variation. + * Note, if an insert is updated, the record + * should be overwritten (TODO - check this). + */ + var_rec_id = XT_GET_DISK_4(rec_head->tr_prev_rec_id_4); + if (!var_rec_id) + break; +#ifdef TRACE_VARIATIONS + if (len <= 450) + len += sprintf(t_buf+len, "OTHER COMMIT (OVERWRITTEN) T%d\n", (int) xn_id); + xt_ttracef(thread, "%s", t_buf); +#endif + } +#ifdef TRACE_VARIATIONS + else { + if (len <= 450) + len += sprintf(t_buf+len, "OTHER COMMIT T%d\n", (int) xn_id); + xt_ttracef(thread, "%s", t_buf); + } +#endif + /* {WAKE-SW} + * The record is not visible, although it has been committed. + * Clean the transaction ASAP. + */ + ot->ot_table->tab_db->db_sw_faster |= XT_SW_DIRTY_RECORD_FOUND; + return FALSE; + case XT_XN_ABORTED: + /* {WAKE-SW} + * Reading an aborted record, this transaction + * must be cleaned up ASAP! + */ + ot->ot_table->tab_db->db_sw_faster |= XT_SW_DIRTY_RECORD_FOUND; +#ifdef TRACE_VARIATIONS + if (len <= 450) + len += sprintf(t_buf+len, "ABORTED T%d\n", (int) xn_id); + xt_ttracef(thread, "%s", t_buf); +#endif + return FALSE; + case XT_XN_MY_UPDATE: + /* This is a record written by this transaction. */ + if (thread->st_is_update) { + /* Check that it was not written by the current update statement: */ + if (XT_STAT_ID_MASK(thread->st_update_id) == rec_head->tr_stat_id_1) { +#ifdef TRACE_VARIATIONS + if (len <= 450) + len += sprintf(t_buf+len, "MY UPDATE IN THIS STATEMENT T%d\n", (int) xn_id); + xt_ttracef(thread, "%s", t_buf); +#endif + return FALSE; + } + } + ot->ot_curr_row_id = row_id; + ot->ot_curr_updated = TRUE; + if (!(xt_tab_get_row(ot, row_id, &var_rec_id))) + return XT_ERR; + /* It is visible if it is at the front of the list. + * An update can end up not being at the front of the list + * if it is deleted afterwards! + */ +#ifdef TRACE_VARIATIONS + if (len <= 450) { + if (var_rec_id == ot->ot_curr_rec_id) + len += sprintf(t_buf+len, "MY UPDATE T%d\n", (int) xn_id); + else + len += sprintf(t_buf+len, "MY UPDATE (OVERWRITTEN) T%d\n", (int) xn_id); + } + xt_ttracef(thread, "%s", t_buf); +#endif + return var_rec_id == ot->ot_curr_rec_id; + case XT_XN_OTHER_UPDATE: + if (ot->ot_for_update) { + /* If this is an insert, we are interested! + * Updated values are handled below. This is because + * the changed (new) records returned below are always + * followed (in the version chain) by the record + * we would have returned (if nothing had changed). + * + * As a result, we only return records here which have + * no "history". + */ + var_rec_id = XT_GET_DISK_4(rec_head->tr_prev_rec_id_4); + if (!var_rec_id) { +#ifdef TRACE_VARIATIONS + if (len <= 450) + len += sprintf(t_buf+len, "OTHER INSERT (WAIT FOR) T%d\n", (int) xn_id); + xt_ttracef(thread, "%s", t_buf); +#endif + if (!tab_wait_for_update(ot, row_id, xn_id, thread)) + return XT_ERR; + if (!xt_tab_get_rec_data(ot, ot->ot_curr_rec_id, sizeof(XTTabRecHeadDRec), (xtWord1 *) &var_head)) + return XT_ERR; + rec_head = &var_head; + goto retry; + } + } +#ifdef TRACE_VARIATIONS + if (len <= 450) + len += sprintf(t_buf+len, "OTHER UPDATE T%d\n", (int) xn_id); + xt_ttracef(thread, "%s", t_buf); +#endif + return FALSE; + case XT_XN_REREAD: +#ifdef TRACE_VARIATIONS + if (len <= 450) + len += sprintf(t_buf+len, "REREAD?! T%d\n", (int) xn_id); + xt_ttracef(thread, "%s", t_buf); +#endif + return XT_REREAD; + } + } + + /* Follow the variation chain until we come to this record. + * If it is not the first visible variation then + * it is not visible at all. If it in not found on the + * variation chain, it is also not visible. + */ + tab = ot->ot_table; + + retry_2: + +#ifdef XT_USE_LIST_BASED_ROW_LOCKS + /* The list based row locks used there own locks, so + * it is not necessary to get a write lock here. + */ + XT_TAB_ROW_READ_LOCK(&tab->tab_row_rwlock[row_id % XT_ROW_RWLOCKS], thread); +#else + if (ot->ot_for_update) + XT_TAB_ROW_WRITE_LOCK(&tab->tab_row_rwlock[row_id % XT_ROW_RWLOCKS], thread); + else + XT_TAB_ROW_READ_LOCK(&tab->tab_row_rwlock[row_id % XT_ROW_RWLOCKS], thread); +#endif + + invalid_rec = 0; + retry_3: + if (!(xt_tab_get_row(ot, row_id, &var_rec_id))) + goto failed; +#ifdef TRACE_VARIATIONS + len += sprintf(t_buf+len, "ROW=%d", (int) row_id); +#endif + while (var_rec_id != ot->ot_curr_rec_id) { + if (!var_rec_id) { +#ifdef TRACE_VARIATIONS + xt_ttracef(thread, "row=%d rec=%d NOT VISI not found in list\n", (int) row_id, (int) ot->ot_curr_rec_id); +#endif + goto not_found; + } + if (!xt_tab_get_rec_data(ot, var_rec_id, sizeof(XTTabRecHeadDRec), (xtWord1 *) &var_head)) + goto failed; +#ifdef TRACE_VARIATIONS + if (len <= 450) + len += sprintf(t_buf+len, " -> %d(%d)", (int) var_rec_id, (int) var_head.tr_rec_type_1); +#endif + /* All clean records are visible, by all transactions: */ + if (XT_REC_IS_CLEAN(var_head.tr_rec_type_1)) { +#ifdef TRACE_VARIATIONS + xt_ttracef(thread, "row=%d rec=%d NOT VISI clean rec found\n", (int) row_id, (int) ot->ot_curr_rec_id); +#endif + goto not_found; + } + if (XT_REC_IS_FREE(var_head.tr_rec_type_1)) { +#ifdef TRACE_VARIATIONS + xt_ttracef(thread, "row=%d rec=%d NOT VISI free rec found?!\n", (int) row_id, (int) ot->ot_curr_rec_id); +#endif + /* + * After an analysis we came to conclusion that this situation is + * possible and valid. It can happen if index scan and row deletion + * go in parallel: + * + * Client Thread Sweeper + * ------------- ------- + * 1. start index scan, lock the index file. + * 2. start row deletion, wait for index lock + * 3. unlock the index file, start search for + * the valid version of the record + * 4. delete the row, mark record as freed, + * but not yet cleaned by sweeper + * 5. observe the record being freed + * + * after these steps we can get here, if the record was marked as free after + * the tab_visible was entered by the scanning thread. + * + */ + if (invalid_rec != var_rec_id) { + /* This was "var_rec_id = invalid_rec", caused an infinite loop (bug #310184!) */ + invalid_rec = var_rec_id; + goto retry_3; + } + /* Assume end of list. */ + goto not_found; + } + + /* This can happen if the row has been removed, and + * reused: + */ + if (row_id != XT_GET_DISK_4(var_head.tr_row_id_4)) + goto not_found; + + xn_id = XT_GET_DISK_4(var_head.tr_xact_id_4); + /* This variation is visibleif committed before this + * transaction started, or updated by this transaction. + * + * We now know that this is the valid variation for + * this record (for this table) for this transaction! + * This will not change, unless the transaction + * updates the record (again). + * + * So we can store this information as a hint, if + * we see other variations belonging to this record, + * then we can ignore them immediately! + */ + switch (xt_xn_status(ot, xn_id, var_rec_id)) { + case XT_XN_VISIBLE: + /* {WAKE-SW} + * We have encountered a record that has been overwritten, if the + * record has not been cleaned, then the sweeper is too far + * behind! + */ + if (!rec_clean) + ot->ot_table->tab_db->db_sw_faster |= XT_SW_DIRTY_RECORD_FOUND; +#ifdef TRACE_VARIATIONS + xt_ttracef(thread, "row=%d rec=%d NOT VISI committed rec found\n", (int) row_id, (int) ot->ot_curr_rec_id); +#endif + goto not_found; + case XT_XN_NOT_VISIBLE: + if (ot->ot_for_update) { + /* Substitute this record for the one we + * are reading!! + */ + if (result == TRUE) { + if (XT_REC_IS_DELETE(var_head.tr_rec_type_1)) + result = FALSE; + else { + *new_rec_id = var_rec_id; + result = XT_NEW; + } + } + } + break; + case XT_XN_ABORTED: + /* Ignore the record, it will be removed. */ + break; + case XT_XN_MY_UPDATE: +#ifdef TRACE_VARIATIONS + xt_ttracef(thread, "row=%d rec=%d NOT VISI my update found\n", (int) row_id, (int) ot->ot_curr_rec_id); +#endif + goto not_found; + case XT_XN_OTHER_UPDATE: + /* Wait for this update to commit or abort: */ + if (!wait) { + wait = TRUE; + wait_xn_id = xn_id; + } +#ifdef TRACE_VARIATIONS + if (len <= 450) + len += sprintf(t_buf+len, "-T%d", (int) wait_xn_id); +#endif + break; + case XT_XN_REREAD: + if (invalid_rec != var_rec_id) { + invalid_rec = var_rec_id; + goto retry_3; + } + /* Assume end of list. */ +#ifdef XT_CRASH_DEBUG + /* Should not happen! */ + xt_crash_me(); +#endif + goto not_found; + } + var_rec_id = XT_GET_DISK_4(var_head.tr_prev_rec_id_4); + } +#ifdef TRACE_VARIATIONS + if (len <= 450) + sprintf(t_buf+len, " -> %d(%d)\n", (int) var_rec_id, (int) rec_head->tr_rec_type_1); + else + sprintf(t_buf+len, " ...\n"); + //xt_ttracef(thread, "%s", t_buf); +#endif + + if (ot->ot_for_update) { + xtBool ok; + XTLockWaitRec lw; + + if (wait) { + XT_TAB_ROW_UNLOCK(&tab->tab_row_rwlock[row_id % XT_ROW_RWLOCKS], thread); +#ifdef TRACE_VARIATIONS + xt_ttracef(thread, "T%d WAIT FOR T%d (will retry)\n", (int) thread->st_xact_data->xd_start_xn_id, (int) wait_xn_id); +#endif + if (!tab_wait_for_update(ot, row_id, wait_xn_id, thread)) + return XT_ERR; + wait = FALSE; + wait_xn_id = 0; + /* + * Retry in order to try to avoid missing + * any records that we should see in FOR UPDATE + * mode. + * + * We also want to take another look at the record + * we just tried to read. + * + * If it has been updated, then a new record has + * been created. This will be detected when we + * try to read it again, and XT_NEW will be returned. + */ + thread->st_statistics.st_retry_index_scan++; + return XT_RETRY; + } + + /* {ROW-LIST-LOCK} */ + lw.lw_thread = thread; + lw.lw_ot = ot; + lw.lw_row_id = row_id; + lw.lw_row_updated = FALSE; + ok = tab->tab_locks.xt_set_temp_lock(ot, &lw, &thread->st_lock_list); + XT_TAB_ROW_UNLOCK(&tab->tab_row_rwlock[row_id % XT_ROW_RWLOCKS], thread); + if (!ok) { +#ifdef DEBUG_LOCK_QUEUE + ot->ot_table->tab_locks.rl_check(&lw); +#endif + return XT_ERR; + } + if (lw.lw_curr_lock != XT_NO_LOCK) { +#ifdef TRACE_VARIATIONS + xt_ttracef(thread, "T%d WAIT FOR LOCK(%D) T%d\n", (int) thread->st_xact_data->xd_start_xn_id, (int) lock_type, (int) xn_id); +#endif + if (!xt_xn_wait_for_xact(thread, NULL, &lw)) { +#ifdef DEBUG_LOCK_QUEUE + ot->ot_table->tab_locks.rl_check(&lw); +#endif + return XT_ERR; + } +#ifdef DEBUG_LOCK_QUEUE + ot->ot_table->tab_locks.rl_check(&lw); +#endif +#ifdef TRACE_VARIATIONS + len = sprintf(t_buf, "(retry): row=%d rec=%d ", (int) row_id, (int) ot->ot_curr_rec_id); +#endif + /* GOTCHA! + * Reset the result before we go down the list again, to make sure we + * get the latest record!! + */ + result = TRUE; + thread->st_statistics.st_reread_record_list++; + goto retry_2; + } +#ifdef DEBUG_LOCK_QUEUE + ot->ot_table->tab_locks.rl_check(&lw); +#endif + } + else { + XT_TAB_ROW_UNLOCK(&tab->tab_row_rwlock[row_id % XT_ROW_RWLOCKS], thread); + } + +#ifdef TRACE_VARIATIONS + if (result == XT_NEW) + xt_ttracef(thread, "row=%d rec=%d RETURN NEW %d\n", (int) row_id, (int) ot->ot_curr_rec_id, (int) *new_rec_id); + else if (result) + xt_ttracef(thread, "row=%d rec=%d VISIBLE\n", (int) row_id, (int) ot->ot_curr_rec_id); + else + xt_ttracef(thread, "row=%d rec=%d RETURN NOT VISIBLE (NEW)\n", (int) row_id, (int) ot->ot_curr_rec_id); +#endif + + ot->ot_curr_row_id = row_id; + ot->ot_curr_updated = FALSE; + return result; + + not_found: + XT_TAB_ROW_UNLOCK(&tab->tab_row_rwlock[row_id % XT_ROW_RWLOCKS], thread); + return FALSE; + + failed: + XT_TAB_ROW_UNLOCK(&tab->tab_row_rwlock[row_id % XT_ROW_RWLOCKS], thread); + return XT_ERR; +} + +/* + * Return TRUE if the record has been read, and is visible. + * Return FALSE if the record is not visible. + * Return XT_ERR if an error occurs. + */ +xtPublic int xt_tab_visible(XTOpenTablePtr ot) +{ + xtRowID row_id; + XTTabRecHeadDRec rec_head; + xtRecordID new_rec_id; + xtBool read_again = FALSE; + int r; + + if ((row_id = ot->ot_curr_row_id)) { + /* Fast track, do a quick check. + * Row ID is only set if this record has been committed, + * (and swept). + * Check if it is the first on the list! + */ + xtRecordID var_rec_id; + + retry: + if (!(xt_tab_get_row(ot, row_id, &var_rec_id))) + return XT_ERR; + if (ot->ot_curr_rec_id == var_rec_id) { + /* Looks good.. */ + if (ot->ot_for_update) { + XTThreadPtr thread = ot->ot_thread; + XTTableHPtr tab = ot->ot_table; + XTLockWaitRec lw; + + /* {ROW-LIST-LOCK} */ + lw.lw_thread = thread; + lw.lw_ot = ot; + lw.lw_row_id = row_id; + lw.lw_row_updated = FALSE; + if (!tab->tab_locks.xt_set_temp_lock(ot, &lw, &thread->st_lock_list)) { +#ifdef DEBUG_LOCK_QUEUE + ot->ot_table->tab_locks.rl_check(&lw); +#endif + return XT_ERR; + } + if (lw.lw_curr_lock != XT_NO_LOCK) { + if (!xt_xn_wait_for_xact(thread, NULL, &lw)) { +#ifdef DEBUG_LOCK_QUEUE + ot->ot_table->tab_locks.rl_check(&lw); +#endif + return XT_ERR; + } +#ifdef DEBUG_LOCK_QUEUE + ot->ot_table->tab_locks.rl_check(&lw); +#endif + goto retry; + } +#ifdef DEBUG_LOCK_QUEUE + ot->ot_table->tab_locks.rl_check(&lw); +#endif + } + return TRUE; + } + } + + reread: + if (!xt_tab_get_rec_data(ot, ot->ot_curr_rec_id, sizeof(XTTabRecHeadDRec), (xtWord1 *) &rec_head)) + return XT_ERR; + + switch ((r = tab_visible(ot, &rec_head, &new_rec_id))) { + case XT_NEW: + ot->ot_curr_rec_id = new_rec_id; + break; + case XT_REREAD: + /* Avoid infinite loop: */ + if (read_again) { + /* Should not happen! */ +#ifdef XT_CRASH_DEBUG + /* Generate a core dump! */ + xt_crash_me(); +#endif + return FALSE; + } + read_again = TRUE; + goto reread; + default: + break; + } + return r; +} + +/* + * Read a record, and return one of the following: + * TRUE - the record has been read, and is visible. + * FALSE - the record is not visible. + * XT_ERR - an error occurs. + * XT_NEW - Means the expected record has been changed. + * When doing an index scan, the conditions must be checked again! + */ +xtPublic int xt_tab_read_record(register XTOpenTablePtr ot, xtWord1 *buffer) +{ + register XTTableHPtr tab = ot->ot_table; + size_t rec_size = tab->tab_dic.dic_rec_size; + xtRecordID new_rec_id; + int result; + xtBool read_again = FALSE; + + if (!(ot->ot_thread->st_xact_data)) { + xt_register_xterr(XT_REG_CONTEXT, XT_ERR_NO_TRANSACTION); + return XT_ERR; + } + + reread: + if (!xt_tab_get_rec_data(ot, ot->ot_curr_rec_id, rec_size, ot->ot_row_rbuffer)) + return XT_ERR; + + switch (tab_visible(ot, (XTTabRecHeadDPtr) ot->ot_row_rbuffer, &new_rec_id)) { + case FALSE: + return FALSE; + case XT_ERR: + return XT_ERR; + case XT_NEW: + if (!xt_tab_get_rec_data(ot, new_rec_id, rec_size, ot->ot_row_rbuffer)) + return XT_ERR; + ot->ot_curr_rec_id = new_rec_id; + result = XT_NEW; + break; + case XT_RETRY: + return XT_RETRY; + case XT_REREAD: + /* Avoid infinite loop: */ + if (read_again) { + /* Should not happen! */ +#ifdef XT_CRASH_DEBUG + /* Generate a core dump! */ + xt_crash_me(); +#endif + return FALSE; + } + read_again = TRUE; + goto reread; + default: + result = OK; + break; + } + + if (ot->ot_rec_fixed) + memcpy(buffer, ot->ot_row_rbuffer + XT_REC_FIX_HEADER_SIZE, rec_size - XT_REC_FIX_HEADER_SIZE); + else if (ot->ot_row_rbuffer[0] == XT_TAB_STATUS_VARIABLE || ot->ot_row_rbuffer[0] == XT_TAB_STATUS_VAR_CLEAN) { + if (!myxt_load_row(ot, ot->ot_row_rbuffer + XT_REC_FIX_HEADER_SIZE, buffer, ot->ot_cols_req)) + return XT_ERR; + } + else { + u_int cols_req = ot->ot_cols_req; + + ASSERT_NS(cols_req); + if (cols_req && cols_req <= tab->tab_dic.dic_fix_col_count) { + if (!myxt_load_row(ot, ot->ot_row_rbuffer + XT_REC_EXT_HEADER_SIZE, buffer, cols_req)) + return XT_ERR; + } + else { + if (!xt_tab_load_ext_data(ot, ot->ot_curr_rec_id, buffer, cols_req)) + return XT_ERR; + } + } + + return result; +} + +/* + * Returns: + * + * TRUE/OK - record was read. + * FALSE/FAILED - An error occurred. + */ +xtPublic int xt_tab_dirty_read_record(register XTOpenTablePtr ot, xtWord1 *buffer) +{ + register XTTableHPtr tab = ot->ot_table; + size_t rec_size = tab->tab_dic.dic_rec_size; + + if (!xt_tab_get_rec_data(ot, ot->ot_curr_rec_id, rec_size, ot->ot_row_rbuffer)) + return FAILED; + + if (XT_REC_NOT_VALID(ot->ot_row_rbuffer[0])) { + /* Should not happen! */ + xt_register_xterr(XT_REG_CONTEXT, XT_ERR_RECORD_DELETED); + return FAILED; + } + + ot->ot_curr_row_id = XT_GET_DISK_4(((XTTabRecHeadDPtr) ot->ot_row_rbuffer)->tr_row_id_4); + ot->ot_curr_updated = + (XT_GET_DISK_4(((XTTabRecHeadDPtr) ot->ot_row_rbuffer)->tr_xact_id_4) == ot->ot_thread->st_xact_data->xd_start_xn_id); + + if (ot->ot_rec_fixed) + memcpy(buffer, ot->ot_row_rbuffer + XT_REC_FIX_HEADER_SIZE, rec_size - XT_REC_FIX_HEADER_SIZE); + else if (ot->ot_row_rbuffer[0] == XT_TAB_STATUS_VARIABLE || ot->ot_row_rbuffer[0] == XT_TAB_STATUS_VAR_CLEAN) { + if (!myxt_load_row(ot, ot->ot_row_rbuffer + XT_REC_FIX_HEADER_SIZE, buffer, ot->ot_cols_req)) + return FAILED; + } + else { + u_int cols_req = ot->ot_cols_req; + + ASSERT_NS(cols_req); + if (cols_req && cols_req <= tab->tab_dic.dic_fix_col_count) { + if (!myxt_load_row(ot, ot->ot_row_rbuffer + XT_REC_EXT_HEADER_SIZE, buffer, cols_req)) + return FAILED; + } + else { + if (!xt_tab_load_ext_data(ot, ot->ot_curr_rec_id, buffer, cols_req)) + return FAILED; + } + } + + return OK; +} + +/* + * Pull the entire row pointer file into memory. + */ +xtPublic void xt_tab_load_row_pointers(XTThreadPtr self, XTOpenTablePtr ot) +{ + XTTableHPtr tab = ot->ot_table; + xtRecordID eof_rec_id = tab->tab_row_eof_id; + xtInt8 usage; + xtWord1 *buffer = NULL; + + /* Check if there is enough cache: */ + usage = xt_tc_get_usage(); + if (xt_tc_get_high() > usage) + usage = xt_tc_get_high(); + if (usage + ((xtInt8) eof_rec_id * (xtInt8) tab->tab_rows.tci_rec_size) < xt_tc_get_size()) { + xtRecordID rec_id; + size_t poffset, tfer; + off_t offset, end_offset; + XTTabCachePagePtr page; + + end_offset = xt_row_id_to_row_offset(tab, eof_rec_id); + rec_id = 1; + while (rec_id < eof_rec_id) { + if (!tab->tab_rows.xt_tc_get_page(ot->ot_row_file, rec_id, &page, &poffset, self)) + xt_throw(self); + if (page) + tab->tab_rows.xt_tc_release_page(ot->ot_row_file, page, self); + else { + xtWord1 *buff_ptr; + + if (!buffer) + buffer = (xtWord1 *) xt_malloc(self, tab->tab_rows.tci_page_size); + offset = xt_row_id_to_row_offset(tab, rec_id); + tfer = tab->tab_rows.tci_page_size; + if (offset + (off_t) tfer > end_offset) + tfer = (size_t) (end_offset - offset); + XT_LOCK_MEMORY_PTR(buff_ptr, ot->ot_row_file, offset, tfer, &self->st_statistics.st_rec, self); + if (buff_ptr) { + memcpy(buffer, buff_ptr, tfer); + XT_UNLOCK_MEMORY_PTR(ot->ot_row_file, self); + } + } + rec_id += tab->tab_rows.tci_rows_per_page; + } + if (buffer) + xt_free(self, buffer); + } +} + +xtPublic void xt_tab_load_table(XTThreadPtr self, XTOpenTablePtr ot) +{ + xt_load_pages(self, ot); + xt_load_indices(self, ot); +} + +xtPublic xtBool xt_tab_load_record(register XTOpenTablePtr ot, xtRecordID rec_id, XTInfoBufferPtr rec_buf) +{ + register XTTableHPtr tab = ot->ot_table; + size_t rec_size = tab->tab_dic.dic_rec_size; + + if (!xt_tab_get_rec_data(ot, rec_id, rec_size, ot->ot_row_rbuffer)) + return FAILED; + + if (XT_REC_NOT_VALID(ot->ot_row_rbuffer[0])) { + /* Should not happen! */ + XTThreadPtr self = ot->ot_thread; + + xt_log(XT_WARNING, "Recently updated record invalid\n"); + return OK; + } + + ot->ot_curr_row_id = XT_GET_DISK_4(((XTTabRecHeadDPtr) ot->ot_row_rbuffer)->tr_row_id_4); + ot->ot_curr_updated = + (XT_GET_DISK_4(((XTTabRecHeadDPtr) ot->ot_row_rbuffer)->tr_xact_id_4) == ot->ot_thread->st_xact_data->xd_start_xn_id); + + if (ot->ot_rec_fixed) { + size_t size = rec_size - XT_REC_FIX_HEADER_SIZE; + if (!xt_ib_alloc(NULL, rec_buf, size)) + return FAILED; + memcpy(rec_buf->ib_db.db_data, ot->ot_row_rbuffer + XT_REC_FIX_HEADER_SIZE, size); + } + else { + if (!xt_ib_alloc(NULL, rec_buf, tab->tab_dic.dic_mysql_buf_size)) + return FAILED; + if (ot->ot_row_rbuffer[0] == XT_TAB_STATUS_VARIABLE || ot->ot_row_rbuffer[0] == XT_TAB_STATUS_VAR_CLEAN) { + if (!myxt_load_row(ot, ot->ot_row_rbuffer + XT_REC_FIX_HEADER_SIZE, rec_buf->ib_db.db_data, ot->ot_cols_req)) + return FAILED; + } + else { + u_int cols_req = ot->ot_cols_req; + + ASSERT_NS(cols_req); + if (cols_req && cols_req <= tab->tab_dic.dic_fix_col_count) { + if (!myxt_load_row(ot, ot->ot_row_rbuffer + XT_REC_EXT_HEADER_SIZE, rec_buf->ib_db.db_data, cols_req)) + return FAILED; + } + else { + if (!xt_tab_load_ext_data(ot, ot->ot_curr_rec_id, rec_buf->ib_db.db_data, cols_req)) + return FAILED; + } + } + } + + return OK; +} + +xtPublic xtBool xt_tab_free_row(XTOpenTablePtr ot, XTTableHPtr tab, xtRowID row_id) +{ + XTTabRowRefDRec free_row; + xtRowID prev_row; + xtOpSeqNo op_seq; + + ASSERT_NS(row_id); // Cannot free the header! + + xt_lock_mutex_ns(&tab->tab_row_lock); + prev_row = tab->tab_row_free_id; + XT_SET_DISK_4(free_row.rr_ref_id_4, prev_row); + if (!tab->tab_rows.xt_tc_write(ot->ot_row_file, row_id, 0, sizeof(XTTabRowRefDRec), (xtWord1 *) &free_row, &op_seq, TRUE, ot->ot_thread)) { + xt_unlock_mutex_ns(&tab->tab_row_lock); + return FAILED; + } + tab->tab_row_free_id = row_id; + tab->tab_row_fnum++; + xt_unlock_mutex_ns(&tab->tab_row_lock); + + if (!xt_xlog_modify_table(ot, XT_LOG_ENT_ROW_FREED, op_seq, 0, row_id, sizeof(XTTabRowRefDRec), (xtWord1 *) &free_row)) + return FAILED; + + return OK; +} + +static void tab_free_ext_record_on_fail(XTOpenTablePtr ot, xtRecordID rec_id, XTTabRecExtDPtr ext_rec, xtBool log_err) +{ + xtWord4 log_over_size = XT_GET_DISK_4(ext_rec->re_log_dat_siz_4); + xtLogID log_id; + xtLogOffset log_offset; + + XT_GET_LOG_REF(log_id, log_offset, ext_rec); + + if (!ot->ot_thread->st_dlog_buf.dlb_delete_log(log_id, log_offset, log_over_size, ot->ot_table->tab_id, rec_id, ot->ot_thread)) { + if (log_err) + xt_log_and_clear_exception_ns(); + } +} + +static void tab_save_exception(XTExceptionPtr e) +{ + XTThreadPtr self = xt_get_self(); + + *e = self->t_exception; +} + +static void tab_restore_exception(XTExceptionPtr e) +{ + XTThreadPtr self = xt_get_self(); + + self->t_exception = *e; +} + +/* + * This function assumes that a record may be partially written. + * It removes all associated data and references to the record. + * + * This function return XT_ERR if an error occurs. + * TRUE if the record has been removed, and may be freed. + * FALSE if the record has already been freed. + * + */ +xtPublic int xt_tab_remove_record(XTOpenTablePtr ot, xtRecordID rec_id, xtWord1 *rec_data, xtRecordID *prev_var_id, xtBool clean_delete, xtRowID row_id, xtXactID xn_id __attribute__((unused))) +{ + register XTTableHPtr tab = ot->ot_table; + size_t rec_size; + xtWord1 old_rec_type; + u_int cols_req; + u_int cols_in_buffer; + + *prev_var_id = 0; + + if (!rec_id) + return FALSE; + + /* + * NOTE: This function uses the read buffer. This should be OK because + * the function is only called by the sweeper. The read buffer + * is REQUIRED because of the call to xt_tab_load_ext_data()!!! + */ + rec_size = tab->tab_dic.dic_rec_size; + if (!xt_tab_get_rec_data(ot, rec_id, rec_size, ot->ot_row_rbuffer)) + return XT_ERR; + old_rec_type = ot->ot_row_rbuffer[0]; + + /* Check of the record has not already been freed: */ + if (XT_REC_IS_FREE(old_rec_type)) + return FALSE; + + /* This record must belong to the given row: */ + if (XT_GET_DISK_4(((XTTabRecExtDPtr) ot->ot_row_rbuffer)->tr_row_id_4) != row_id) + return FALSE; + + /* The transaction ID of the record must be BEFORE or equal to the given + * transaction ID. + * + * No, this does not always hold. Because we wait for updates now, + * a "younger" transaction can update before an older + * transaction. + * Commit order determined the actual order in which the transactions + * should be replicated. This is determined by the log number of + * the commit record! + if (db->db_xn_curr_id(xn_id, XT_GET_DISK_4(((XTTabRecExtDPtr) ot->ot_row_rbuffer)->tr_xact_id_4))) + return FALSE; + */ + + *prev_var_id = XT_GET_DISK_4(((XTTabRecExtDPtr) ot->ot_row_rbuffer)->tr_prev_rec_id_4); + + if (tab->tab_dic.dic_key_count) { + XTIndexPtr *ind; + + switch (old_rec_type) { + case XT_TAB_STATUS_DELETE: + case XT_TAB_STATUS_DEL_CLEAN: + rec_size = sizeof(XTTabRecHeadDRec); + goto set_removed; + case XT_TAB_STATUS_FIXED: + case XT_TAB_STATUS_FIX_CLEAN: + /* We know that for a fixed length record, + * dic_ind_rec_len <= dic_rec_size! */ + rec_size = (size_t) tab->tab_dic.dic_ind_rec_len + XT_REC_FIX_HEADER_SIZE; + rec_data = ot->ot_row_rbuffer + XT_REC_FIX_HEADER_SIZE; + break; + case XT_TAB_STATUS_VARIABLE: + case XT_TAB_STATUS_VAR_CLEAN: + cols_req = tab->tab_dic.dic_ind_cols_req; + + cols_in_buffer = cols_req; + rec_size = myxt_load_row_length(ot, rec_size - XT_REC_FIX_HEADER_SIZE, ot->ot_row_rbuffer + XT_REC_FIX_HEADER_SIZE, &cols_in_buffer); + if (cols_in_buffer < cols_req) + rec_size = tab->tab_dic.dic_rec_size; + else + rec_size += XT_REC_FIX_HEADER_SIZE; + if (!myxt_load_row(ot, ot->ot_row_rbuffer + XT_REC_FIX_HEADER_SIZE, rec_data, cols_req)) { + xt_log_and_clear_exception_ns(); + goto set_removed; + } + break; + case XT_TAB_STATUS_EXT_DLOG: + case XT_TAB_STATUS_EXT_CLEAN: + cols_req = tab->tab_dic.dic_ind_cols_req; + + ASSERT_NS(cols_req); + cols_in_buffer = cols_req; + rec_size = myxt_load_row_length(ot, rec_size - XT_REC_EXT_HEADER_SIZE, ot->ot_row_rbuffer + XT_REC_EXT_HEADER_SIZE, &cols_in_buffer); + if (cols_in_buffer < cols_req) { + rec_size = tab->tab_dic.dic_rec_size; + if (!xt_tab_load_ext_data(ot, rec_id, rec_data, cols_req)) { + /* This is actually quite possible after recovery, see [(3)] */ + if (ot->ot_thread->t_exception.e_xt_err != XT_ERR_BAD_EXT_RECORD && + ot->ot_thread->t_exception.e_xt_err != XT_ERR_DATA_LOG_NOT_FOUND) + xt_log_and_clear_exception_ns(); + goto set_removed; + } + } + else { + /* All the records we require are in the buffer... */ + rec_size += XT_REC_EXT_HEADER_SIZE; + if (!myxt_load_row(ot, ot->ot_row_rbuffer + XT_REC_EXT_HEADER_SIZE, rec_data, cols_req)) { + xt_log_and_clear_exception_ns(); + goto set_removed; + } + } + break; + default: + break; + } + + /* Could this be the case?: This change may only be flushed after the + * operation below has been flushed to the log. + * + * No, remove records are never "undone". The sweeper will delete + * the record again if it does not land in the log. + * + * The fact that the index entries have already been removed is not + * a problem. + */ + if (!tab->tab_dic.dic_disable_index) { + ind = tab->tab_dic.dic_keys; + for (u_int i=0; i<tab->tab_dic.dic_key_count; i++, ind++) { + if (!xt_idx_delete(ot, *ind, rec_id, rec_data)) + xt_log_and_clear_exception_ns(); + } + } + } + else { + /* No indices: */ + switch (old_rec_type) { + case XT_TAB_STATUS_DELETE: + case XT_TAB_STATUS_DEL_CLEAN: + rec_size = XT_REC_FIX_HEADER_SIZE; + break; + case XT_TAB_STATUS_FIXED: + case XT_TAB_STATUS_FIX_CLEAN: + case XT_TAB_STATUS_VARIABLE: + case XT_TAB_STATUS_VAR_CLEAN: + rec_size = XT_REC_FIX_HEADER_SIZE; + break; + case XT_TAB_STATUS_EXT_DLOG: + case XT_TAB_STATUS_EXT_CLEAN: + rec_size = XT_REC_EXT_HEADER_SIZE; + break; + } + } + +#ifdef XT_STREAMING + if (tab->tab_dic.dic_blob_count) { + /* If the record contains any LONGBLOB then check how much + * space we need. + */ + size_t blob_size; + + switch (old_rec_type) { + case XT_TAB_STATUS_DELETE: + case XT_TAB_STATUS_DEL_CLEAN: + break; + case XT_TAB_STATUS_FIXED: + case XT_TAB_STATUS_FIX_CLEAN: + /* Should not be the case, record with LONGBLOB can never be fixed! */ + break; + case XT_TAB_STATUS_VARIABLE: + case XT_TAB_STATUS_VAR_CLEAN: + cols_req = tab->tab_dic.dic_blob_cols_req; + cols_in_buffer = cols_req; + blob_size = myxt_load_row_length(ot, rec_size - XT_REC_FIX_HEADER_SIZE, ot->ot_row_rbuffer + XT_REC_FIX_HEADER_SIZE, &cols_in_buffer); + if (cols_in_buffer < cols_req) + blob_size = tab->tab_dic.dic_rec_size; + else + blob_size += XT_REC_FIX_HEADER_SIZE; + if (blob_size > rec_size) + rec_size = blob_size; + break; + case XT_TAB_STATUS_EXT_DLOG: + case XT_TAB_STATUS_EXT_CLEAN: + cols_req = tab->tab_dic.dic_blob_cols_req; + cols_in_buffer = cols_req; + blob_size = myxt_load_row_length(ot, rec_size - XT_REC_EXT_HEADER_SIZE, ot->ot_row_rbuffer + XT_REC_EXT_HEADER_SIZE, &cols_in_buffer); + if (cols_in_buffer < cols_req) + blob_size = tab->tab_dic.dic_rec_size; + else + blob_size += XT_REC_EXT_HEADER_SIZE; + if (blob_size > rec_size) + rec_size = blob_size; + break; + } + } +#endif + + set_removed: + if (XT_REC_IS_EXT_DLOG(old_rec_type)) { + /* {LOCK-EXT-REC} Lock, and read again to make sure that the + * compactor does not change this record, while + * we are removing it! */ + xt_lock_mutex_ns(&tab->tab_db->db_co_ext_lock); + if (!xt_tab_get_rec_data(ot, rec_id, XT_REC_EXT_HEADER_SIZE, ot->ot_row_rbuffer)) { + xt_unlock_mutex_ns(&tab->tab_db->db_co_ext_lock); + return FAILED; + } + xt_unlock_mutex_ns(&tab->tab_db->db_co_ext_lock); + + } + + xtOpSeqNo op_seq; + XTTabRecFreeDPtr free_rec = (XTTabRecFreeDPtr) ot->ot_row_rbuffer; + xtRecordID prev_rec_id; + + /* A record is "clean" deleted if the record was + * XT_TAB_STATUS_DELETE which was comitted. + * This makes sure that the record will still invalidate + * following records in a row. + * + * Example: + * + * 1. INSERT A ROW, then DELETE it, assume the sweeper is delayed. + * + * We now have the sequence row X --> del rec A --> valid rec B. + * + * 2. A SELECT can still find B. Assume it now goes to check + * if the record is valid, it reads row X, and gets A. + * + * 3. Now the sweeper gets control and removes X, A and B. + * It frees A with the clean bit. + * + * 4. Now the SELECT gets control and reads A. Normally a freed record + * would be ignored, and it would go onto B, which would then + * be considered valid (note, even after the free, the next + * pointer is not affected). + * + * However, because the clean bit has been set, it will stop at A + * and consider B invalid (which is the desired result). + * + * NOTE: We assume it is not possible for A to be allocated and refer + * to B, because B is freed before A. This means that B may refer to + * A after the next allocation. + */ + + xtWord1 new_rec_type = XT_TAB_STATUS_FREED | (clean_delete ? XT_TAB_STATUS_CLEANED_BIT : 0); + + xt_lock_mutex_ns(&tab->tab_rec_lock); + free_rec->rf_rec_type_1 = new_rec_type; + prev_rec_id = tab->tab_rec_free_id; + XT_SET_DISK_4(free_rec->rf_next_rec_id_4, prev_rec_id); + if (!xt_tab_put_rec_data(ot, rec_id, sizeof(XTTabRecFreeDRec), ot->ot_row_rbuffer, &op_seq)) { + xt_unlock_mutex_ns(&tab->tab_rec_lock); + return FAILED; + } + tab->tab_rec_free_id = rec_id; + ASSERT_NS(tab->tab_rec_free_id < tab->tab_rec_eof_id); + tab->tab_rec_fnum++; + xt_unlock_mutex_ns(&tab->tab_rec_lock); + + free_rec->rf_rec_type_1 = old_rec_type; + return xt_xlog_modify_table(ot, XT_LOG_ENT_REC_REMOVED_BI, op_seq, (xtRecordID) new_rec_type, rec_id, rec_size, ot->ot_row_rbuffer); +} + +static xtRowID tab_new_row(XTOpenTablePtr ot, XTTableHPtr tab) +{ + xtRowID row_id; + xtOpSeqNo op_seq; + xtRowID next_row_id = 0; + u_int status; + + xt_lock_mutex_ns(&tab->tab_row_lock); + if ((row_id = tab->tab_row_free_id)) { + status = XT_LOG_ENT_ROW_NEW_FL; + + if (!tab->tab_rows.xt_tc_read_4(ot->ot_row_file, row_id, &next_row_id, ot->ot_thread)) { + xt_unlock_mutex_ns(&tab->tab_row_lock); + return 0; + } + tab->tab_row_free_id = next_row_id; + tab->tab_row_fnum--; + } + else { + status = XT_LOG_ENT_ROW_NEW; + row_id = tab->tab_row_eof_id; + if (row_id == 0xFFFFFFFF) { + xt_unlock_mutex_ns(&tab->tab_row_lock); + xt_register_xterr(XT_REG_CONTEXT, XT_ERR_MAX_ROW_COUNT); + return 0; + } + if (((row_id - 1) % tab->tab_rows.tci_rows_per_page) == 0) { + /* By fetching the page now, we avoid reading it later... */ + XTTabCachePagePtr page; + XTTabCacheSegPtr seg; + size_t poffset; + + if (!tab->tab_rows.tc_fetch(ot->ot_row_file, row_id, &seg, &page, &poffset, FALSE, ot->ot_thread)) { + xt_unlock_mutex_ns(&tab->tab_row_lock); + return 0; + } + xt_rwmutex_unlock(&seg->tcs_lock, ot->ot_thread->t_id); + } + tab->tab_row_eof_id++; + } + op_seq = tab->tab_seq.ts_get_op_seq(); + xt_unlock_mutex_ns(&tab->tab_row_lock); + + if (!xt_xlog_modify_table(ot, status, op_seq, next_row_id, row_id, 0, NULL)) + return 0; + + XT_DISABLED_TRACE(("new row tx=%d row=%d\n", (int) ot->ot_thread->st_xact_data->xd_start_xn_id, (int) row_id)); + ASSERT_NS(row_id); + return row_id; +} + +xtPublic xtBool xt_tab_get_row(register XTOpenTablePtr ot, xtRowID row_id, xtRecordID *var_rec_id) +{ + register XTTableHPtr tab = ot->ot_table; + + (void) ASSERT_NS(sizeof(XTTabRowRefDRec) == 4); + + if (!tab->tab_rows.xt_tc_read_4(ot->ot_row_file, row_id, var_rec_id, ot->ot_thread)) + return FAILED; + return OK; +} + +xtPublic xtBool xt_tab_set_row(XTOpenTablePtr ot, u_int status, xtRowID row_id, xtRecordID var_rec_id) +{ + register XTTableHPtr tab = ot->ot_table; + XTTabRowRefDRec row_buf; + xtOpSeqNo op_seq; + + ASSERT_NS(var_rec_id < tab->tab_rec_eof_id); + XT_SET_DISK_4(row_buf.rr_ref_id_4, var_rec_id); + + if (!tab->tab_rows.xt_tc_write(ot->ot_row_file, row_id, 0, sizeof(XTTabRowRefDRec), (xtWord1 *) &row_buf, &op_seq, TRUE, ot->ot_thread)) + return FAILED; + + return xt_xlog_modify_table(ot, status, op_seq, 0, row_id, sizeof(XTTabRowRefDRec), (xtWord1 *) &row_buf); +} + +xtPublic xtBool xt_tab_free_record(XTOpenTablePtr ot, u_int status, xtRecordID rec_id, xtBool clean_delete) +{ + register XTTableHPtr tab = ot->ot_table; + XTTabRecHeadDRec rec_head; + XTactFreeRecEntryDRec free_rec; + xtRecordID prev_rec_id; + + /* Don't free the record if it is already free! */ + if (!xt_tab_get_rec_data(ot, rec_id, sizeof(XTTabRecHeadDRec), (xtWord1 *) &rec_head)) + return FAILED; + + if (!XT_REC_IS_FREE(rec_head.tr_rec_type_1)) { + xtOpSeqNo op_seq; + + /* This information will be used to determine if the resources of the record + * should be removed. + */ + free_rec.fr_stat_id_1 = rec_head.tr_stat_id_1; + XT_COPY_DISK_4(free_rec.fr_xact_id_4, rec_head.tr_xact_id_4); + + /* A record is "clean" deleted if the record was + * XT_TAB_STATUS_DELETE which was comitted. + * This makes sure that the record will still invalidate + * following records in a row. + * + * Example: + * + * 1. INSERT A ROW, then DELETE it, assume the sweeper is delayed. + * + * We now have the sequence row X --> del rec A --> valid rec B. + * + * 2. A SELECT can still find B. Assume it now goes to check + * if the record is valid, ti reads row X, and gets A. + * + * 3. Now the sweeper gets control and removes X, A and B. + * It frees A with the clean bit. + * + * 4. Now the SELECT gets control and reads A. Normally a freed record + * would be ignored, and it would go onto B, which would then + * be considered valid (note, even after the free, the next + * pointer is not affected). + * + * However, because the clean bit has been set, it will stop at A + * and consider B invalid (which is the desired result). + * + * NOTE: We assume it is not possible for A to be allocated and refer + * to B, because B is freed before A. This means that B may refer to + * A after the next allocation. + */ + + (void) ASSERT_NS(sizeof(XTTabRecFreeDRec) == sizeof(XTactFreeRecEntryDRec) - offsetof(XTactFreeRecEntryDRec, fr_rec_type_1)); + free_rec.fr_rec_type_1 = XT_TAB_STATUS_FREED | (clean_delete ? XT_TAB_STATUS_CLEANED_BIT : 0); + free_rec.fr_not_used_1 = 0; + + xt_lock_mutex_ns(&tab->tab_rec_lock); + prev_rec_id = tab->tab_rec_free_id; + XT_SET_DISK_4(free_rec.fr_next_rec_id_4, prev_rec_id); + if (!xt_tab_put_rec_data(ot, rec_id, sizeof(XTTabRecFreeDRec), &free_rec.fr_rec_type_1, &op_seq)) { + xt_unlock_mutex_ns(&tab->tab_rec_lock); + return FAILED; + } + tab->tab_rec_free_id = rec_id; + ASSERT_NS(tab->tab_rec_free_id < tab->tab_rec_eof_id); + tab->tab_rec_fnum++; + xt_unlock_mutex_ns(&tab->tab_rec_lock); + + if (!xt_xlog_modify_table(ot, status, op_seq, rec_id, rec_id, sizeof(XTactFreeRecEntryDRec) - offsetof(XTactFreeRecEntryDRec, fr_stat_id_1), &free_rec.fr_stat_id_1)) + return FAILED; + } + return OK; +} + +static void tab_free_row_on_fail(XTOpenTablePtr ot, XTTableHPtr tab, xtRowID row_id) +{ + XTExceptionRec e; + + tab_save_exception(&e); + xt_tab_free_row(ot, tab, row_id); + tab_restore_exception(&e); +} + +static xtBool tab_add_record(XTOpenTablePtr ot, XTTabRecInfoPtr rec_info, u_int status) +{ + register XTTableHPtr tab = ot->ot_table; + XTThreadPtr thread = ot->ot_thread; + xtRecordID rec_id; + xtLogID log_id; + xtLogOffset log_offset; + xtOpSeqNo op_seq; + xtRecordID next_rec_id = 0; + + if (rec_info->ri_ext_rec) { + /* Determine where the overflow will go... */ + if (!thread->st_dlog_buf.dlb_get_log_offset(&log_id, &log_offset, rec_info->ri_log_data_size + offsetof(XTactExtRecEntryDRec, er_data), ot->ot_thread)) + return FAILED; + XT_SET_LOG_REF(rec_info->ri_ext_rec, log_id, log_offset); + } + + /* Write the record to disk: */ + xt_lock_mutex_ns(&tab->tab_rec_lock); + if ((rec_id = tab->tab_rec_free_id)) { + XTTabRecFreeDRec free_block; + + ASSERT_NS(rec_id < tab->tab_rec_eof_id); + if (!xt_tab_get_rec_data(ot, rec_id, sizeof(XTTabRecFreeDRec), (xtWord1 *) &free_block)) { + xt_unlock_mutex_ns(&tab->tab_rec_lock); + return FAILED; + } + next_rec_id = XT_GET_DISK_4(free_block.rf_next_rec_id_4); + tab->tab_rec_free_id = next_rec_id; + + tab->tab_rec_fnum--; + + /* XT_LOG_ENT_UPDATE --> XT_LOG_ENT_UPDATE_FL */ + /* XT_LOG_ENT_INSERT --> XT_LOG_ENT_INSERT_FL */ + /* XT_LOG_ENT_DELETE --> XT_LOG_ENT_DELETE_FL */ + status += 2; + + if (!xt_tab_put_rec_data(ot, rec_id, rec_info->ri_rec_buf_size, (xtWord1 *) rec_info->ri_fix_rec_buf, &op_seq)) { + xt_unlock_mutex_ns(&tab->tab_rec_lock); + return FAILED; + } + } + else { + xtBool read; + + rec_id = tab->tab_rec_eof_id; + tab->tab_rec_eof_id++; + + /* If we are writing to a new page (at the EOF) + * then we do not need to read the page from the + * file because it is new. + * + * Note that this only works because we are holding + * a lock on the record file. + */ + read = ((rec_id - 1) % tab->tab_recs.tci_rows_per_page) != 0; + + if (!tab->tab_recs.xt_tc_write(ot->ot_rec_file, rec_id, 0, rec_info->ri_rec_buf_size, (xtWord1 *) rec_info->ri_fix_rec_buf, &op_seq, read, ot->ot_thread)) { + xt_unlock_mutex_ns(&tab->tab_rec_lock); + return FAILED; + } + } + xt_unlock_mutex_ns(&tab->tab_rec_lock); + + if (!xt_xlog_modify_table(ot, status, op_seq, next_rec_id, rec_id, rec_info->ri_rec_buf_size, (xtWord1 *) rec_info->ri_fix_rec_buf)) + return FAILED; + + if (rec_info->ri_ext_rec) { + /* Write the log buffer overflow: */ + rec_info->ri_log_buf->er_status_1 = XT_LOG_ENT_EXT_REC_OK; + XT_SET_DISK_4(rec_info->ri_log_buf->er_data_size_4, rec_info->ri_log_data_size); + XT_SET_DISK_4(rec_info->ri_log_buf->er_tab_id_4, tab->tab_id); + XT_SET_DISK_4(rec_info->ri_log_buf->er_rec_id_4, rec_id); + if (!thread->st_dlog_buf.dlb_append_log(log_id, log_offset, offsetof(XTactExtRecEntryDRec, er_data) + rec_info->ri_log_data_size, (xtWord1 *) rec_info->ri_log_buf, ot->ot_thread)) { + /* Failed to write the overflow, free the record allocated above: */ + return FAILED; + } + } + + XT_DISABLED_TRACE(("new rec tx=%d val=%d\n", (int) thread->st_xact_data->xd_start_xn_id, (int) rec_id)); + rec_info->ri_rec_id = rec_id; + return OK; +} + +static void tab_delete_record_on_fail(XTOpenTablePtr ot, xtRowID row_id, xtRecordID rec_id, XTTabRecHeadDPtr row_ptr, xtWord1 *rec_data, u_int key_count) +{ + XTExceptionRec e; + xtBool log_err = TRUE; + XTTabRecInfoRec rec_info; + + tab_save_exception(&e); + + if (e.e_xt_err == XT_ERR_DUPLICATE_KEY || + e.e_xt_err == XT_ERR_DUPLICATE_FKEY) { + /* If the error does not cause rollback, then we will ignore the + * error if an error occurs in the UNDO! + */ + log_err = FALSE; + tab_restore_exception(&e); + } + if (key_count) { + XTIndexPtr *ind; + + ind = ot->ot_table->tab_dic.dic_keys; + for (u_int i=0; i<key_count; i++, ind++) { + if (!xt_idx_delete(ot, *ind, rec_id, rec_data)) { + if (log_err) + xt_log_and_clear_exception_ns(); + } + } + } + + if (row_ptr->tr_rec_type_1 == XT_TAB_STATUS_EXT_DLOG || row_ptr->tr_rec_type_1 == XT_TAB_STATUS_EXT_CLEAN) + tab_free_ext_record_on_fail(ot, rec_id, (XTTabRecExtDPtr) row_ptr, log_err); + + rec_info.ri_fix_rec_buf = (XTTabRecFixDPtr) ot->ot_row_wbuffer; + rec_info.ri_rec_buf_size = offsetof(XTTabRecFixDRec, rf_data); + rec_info.ri_ext_rec = NULL; + rec_info.ri_fix_rec_buf->tr_rec_type_1 = XT_TAB_STATUS_DELETE; + rec_info.ri_fix_rec_buf->tr_stat_id_1 = 0; + XT_SET_DISK_4(rec_info.ri_fix_rec_buf->tr_row_id_4, row_id); + XT_SET_DISK_4(rec_info.ri_fix_rec_buf->tr_prev_rec_id_4, rec_id); + XT_SET_DISK_4(rec_info.ri_fix_rec_buf->tr_xact_id_4, ot->ot_thread->st_xact_data->xd_start_xn_id); + + if (!tab_add_record(ot, &rec_info, XT_LOG_ENT_DELETE)) + goto failed; + + if (!xt_tab_set_row(ot, XT_LOG_ENT_ROW_ADD_REC, row_id, rec_info.ri_rec_id)) + goto failed; + + if (log_err) + tab_restore_exception(&e); + return; + + failed: + if (log_err) + xt_log_and_clear_exception_ns(); + else + tab_restore_exception(&e); +} + +/* + * Wait until all the variations between the start of the chain, and + * the given record have been rolled-back. + * If any is committed, register a locked error, and return FAILED. + */ +static xtBool tab_wait_for_rollback(XTOpenTablePtr ot, xtRowID row_id, xtRecordID commit_rec_id) +{ + register XTTableHPtr tab = ot->ot_table; + xtRecordID var_rec_id; + XTTabRecHeadDRec var_head; + xtXactID xn_id; + xtRecordID invalid_rec = 0; + XTXactWaitRec xw; + + retry: + if (!xt_tab_get_row(ot, row_id, &var_rec_id)) + return FAILED; + + while (var_rec_id != commit_rec_id) { + if (!var_rec_id) + goto locked; + if (!xt_tab_get_rec_data(ot, var_rec_id, sizeof(XTTabRecHeadDRec), (xtWord1 *) &var_head)) + return FAILED; + if (XT_REC_IS_CLEAN(var_head.tr_rec_type_1)) + goto locked; + if (XT_REC_IS_FREE(var_head.tr_rec_type_1)) + /* Should not happen: */ + goto record_invalid; + xn_id = XT_GET_DISK_4(var_head.tr_xact_id_4); + switch (xt_xn_status(ot, xn_id, var_rec_id)) { + case XT_XN_VISIBLE: + case XT_XN_NOT_VISIBLE: + goto locked; + case XT_XN_ABORTED: + /* Ingore the record, it will be removed. */ + break; + case XT_XN_MY_UPDATE: + /* Should not happen: */ + goto locked; + case XT_XN_OTHER_UPDATE: + /* Wait for the transaction to commit or rollback: */ + XT_TAB_ROW_UNLOCK(&tab->tab_row_rwlock[row_id % XT_ROW_RWLOCKS], ot->ot_thread); + xw.xw_xn_id = xn_id; + if (!xt_xn_wait_for_xact(ot->ot_thread, &xw, NULL)) { + XT_TAB_ROW_WRITE_LOCK(&tab->tab_row_rwlock[row_id % XT_ROW_RWLOCKS], ot->ot_thread); + return FAILED; + } + XT_TAB_ROW_WRITE_LOCK(&tab->tab_row_rwlock[row_id % XT_ROW_RWLOCKS], ot->ot_thread); + goto retry; + case XT_XN_REREAD: + goto record_invalid; + } + var_rec_id = XT_GET_DISK_4(var_head.tr_prev_rec_id_4); + } + return OK; + + locked: + xt_register_xterr(XT_REG_CONTEXT, XT_ERR_RECORD_CHANGED); + return FAILED; + + record_invalid: + /* Prevent an infinite loop due to a bad record: */ + if (invalid_rec != var_rec_id) { + var_rec_id = invalid_rec; + goto retry; + } + /* The record is invalid, it will be "overwritten"... */ +#ifdef XT_CRASH_DEBUG + /* Should not happen! */ + xt_crash_me(); +#endif + return OK; +} + +/* Check if a record may be visible: + * Return TRUE of the record may be visible now. + * Return XT_MAYBE if the record may be visible in the future (set out_xn_id). + * Return FALSE of the record is not valid (freed or is a delete record). + * Return XT_ERR if an error occurred. + */ +xtPublic int xt_tab_maybe_committed(XTOpenTablePtr ot, xtRecordID rec_id, xtXactID *out_xn_id, xtRowID *out_rowid, xtBool *out_updated) +{ + XTTabRecHeadDRec rec_head; + xtXactID rec_xn_id = 0; + xtBool wait = FALSE; + xtXactID wait_xn_id = 0; + xtRowID row_id; + xtRecordID var_rec_id; + xtXactID xn_id; + register XTTableHPtr tab; +#ifdef TRACE_VARIATIONS_IN_DUP_CHECK + char t_buf[500]; + int len; + char *t_type = "C"; +#endif + xtRecordID invalid_rec = 0; + + reread: + if (!xt_tab_get_rec_data(ot, rec_id, sizeof(XTTabRecHeadDRec), (xtWord1 *) &rec_head)) + return XT_ERR; + + if (XT_REC_NOT_VALID(rec_head.tr_rec_type_1)) + return FALSE; + + if (!XT_REC_IS_CLEAN(rec_head.tr_rec_type_1)) { + rec_xn_id = XT_GET_DISK_4(rec_head.tr_xact_id_4); + switch (xt_xn_status(ot, rec_xn_id, rec_id)) { + case XT_XN_VISIBLE: +#ifdef TRACE_VARIATIONS_IN_DUP_CHECK + t_type="V"; +#endif + break; + case XT_XN_NOT_VISIBLE: +#ifdef TRACE_VARIATIONS_IN_DUP_CHECK + t_type="NV"; +#endif + break; + case XT_XN_ABORTED: + return FALSE; + case XT_XN_MY_UPDATE: +#ifdef TRACE_VARIATIONS_IN_DUP_CHECK + t_type="My-Upd"; +#endif + break; + case XT_XN_OTHER_UPDATE: +#ifdef TRACE_VARIATIONS_IN_DUP_CHECK + t_type="Wait"; +#endif + wait = TRUE; + wait_xn_id = rec_xn_id; + break; + case XT_XN_REREAD: +#ifdef TRACE_VARIATIONS_IN_DUP_CHECK + t_type="Re-read"; +#endif + /* Avoid infinite loop: */ + if (invalid_rec == rec_id) { + /* Should not happen! */ +#ifdef XT_CRASH_DEBUG + /* Generate a core dump! */ + xt_crash_me(); +#endif + return FALSE; + } + invalid_rec = rec_id; + goto reread; + } + } + + /* Follow the variation chain until we come to this record. + * If it is not the first visible variation then + * it is not visible at all. If it in not found on the + * variation chain, it is also not visible. + */ + row_id = XT_GET_DISK_4(rec_head.tr_row_id_4); + + tab = ot->ot_table; + XT_TAB_ROW_READ_LOCK(&tab->tab_row_rwlock[row_id % XT_ROW_RWLOCKS], ot->ot_thread); + + invalid_rec = 0; + retry: + if (!(xt_tab_get_row(ot, row_id, &var_rec_id))) + goto failed; +#ifdef TRACE_VARIATIONS_IN_DUP_CHECK + len = sprintf(t_buf, "dup row=%d", (int) row_id); +#endif + while (var_rec_id != rec_id) { + if (!var_rec_id) + goto not_found; +#ifdef TRACE_VARIATIONS_IN_DUP_CHECK + if (len <= 450) + len += sprintf(t_buf+len, " -> %d", (int) var_rec_id); +#endif + if (!xt_tab_get_rec_data(ot, var_rec_id, sizeof(XTTabRecHeadDRec), (xtWord1 *) &rec_head)) + goto failed; + /* All clean records are visible, by all transactions: */ + if (XT_REC_IS_CLEAN(rec_head.tr_rec_type_1)) + goto not_found; + + if (XT_REC_IS_FREE(rec_head.tr_rec_type_1)) { + /* Should not happen: */ + if (invalid_rec != var_rec_id) { + var_rec_id = invalid_rec; + goto retry; + } + /* Assume end of list. */ +#ifdef XT_CRASH_DEBUG + /* Should not happen! */ + xt_crash_me(); +#endif + goto not_found; + } + + xn_id = XT_GET_DISK_4(rec_head.tr_xact_id_4); + switch (xt_xn_status(ot, xn_id, var_rec_id)) { + case XT_XN_VISIBLE: + case XT_XN_NOT_VISIBLE: + goto not_found; + case XT_XN_ABORTED: + /* Ingore the record, it will be removed. */ +#ifdef TRACE_VARIATIONS_IN_DUP_CHECK + if (len <= 450) + len += sprintf(t_buf+len, "(T%d-A)", (int) xn_id); +#endif + break; + case XT_XN_MY_UPDATE: + goto not_found; + case XT_XN_OTHER_UPDATE: +#ifdef TRACE_VARIATIONS_IN_DUP_CHECK + if (len <= 450) + len += sprintf(t_buf+len, "(T%d-wait)", (int) xn_id); +#endif + /* Wait for this update to commit or abort: */ + if (!wait) { + wait = TRUE; + wait_xn_id = xn_id; + } + break; + case XT_XN_REREAD: + if (invalid_rec != var_rec_id) { + var_rec_id = invalid_rec; + goto retry; + } + /* Assume end of list. */ +#ifdef XT_CRASH_DEBUG + /* Should not happen! */ + xt_crash_me(); +#endif + goto not_found; + } + var_rec_id = XT_GET_DISK_4(rec_head.tr_prev_rec_id_4); + } +#ifdef TRACE_VARIATIONS_IN_DUP_CHECK + if (len <= 450) + sprintf(t_buf+len, " -> %d(T%d-%s)\n", (int) var_rec_id, (int) rec_xn_id, t_type); + else + sprintf(t_buf+len, " ...(T%d-%s)\n", (int) rec_xn_id, t_type); +#endif + + XT_TAB_ROW_UNLOCK(&tab->tab_row_rwlock[row_id % XT_ROW_RWLOCKS], ot->ot_thread); + if (wait) { + *out_xn_id = wait_xn_id; + return XT_MAYBE; + } +#ifdef TRACE_VARIATIONS_IN_DUP_CHECK + xt_ttracef(thread, "%s", t_buf); +#endif + if (out_rowid) { + *out_rowid = row_id; + *out_updated = (rec_xn_id == ot->ot_thread->st_xact_data->xd_start_xn_id); + } + return TRUE; + + not_found: + XT_TAB_ROW_UNLOCK(&tab->tab_row_rwlock[row_id % XT_ROW_RWLOCKS], ot->ot_thread); + return FALSE; + + failed: + XT_TAB_ROW_UNLOCK(&tab->tab_row_rwlock[row_id % XT_ROW_RWLOCKS], ot->ot_thread); + return XT_ERR; +} + +xtPublic xtBool xt_tab_new_record(XTOpenTablePtr ot, xtWord1 *rec_buf) +{ + register XTTableHPtr tab = ot->ot_table; + register XTThreadPtr self = ot->ot_thread; + XTTabRecInfoRec rec_info; + xtRowID row_id; + u_int idx_cnt = 0; + XTIndexPtr *ind; +#ifdef XT_STREAMING + void *pbms_table; + + /* PBMS: Reference BLOBs!? */ + if (tab->tab_dic.dic_blob_count) { + if (!myxt_use_blobs(ot, &pbms_table, rec_buf)) + return FAILED; + } +#endif + + if (!myxt_store_row(ot, &rec_info, (char *) rec_buf)) + goto failed_0; + + /* Get a new row ID: */ + if (!(row_id = tab_new_row(ot, tab))) + goto failed_0; + + rec_info.ri_fix_rec_buf->tr_stat_id_1 = self->st_update_id; + XT_SET_DISK_4(rec_info.ri_fix_rec_buf->tr_row_id_4, row_id); + XT_SET_DISK_4(rec_info.ri_fix_rec_buf->tr_prev_rec_id_4, 0); + XT_SET_DISK_4(rec_info.ri_fix_rec_buf->tr_xact_id_4, self->st_xact_data->xd_start_xn_id); + + /* Note, it is important that this record is written BEFORE the row + * due to the problem distributed here [(5)] + */ + if (!tab_add_record(ot, &rec_info, XT_LOG_ENT_INSERT)) + goto failed_1; + +#ifdef TRACE_VARIATIONS + xt_ttracef(self, "insert: row=%d rec=%d T%d\n", (int) row_id, (int) rec_info.ri_rec_id, (int) self->st_xact_data->xd_start_xn_id); +#endif + if (!xt_tab_set_row(ot, XT_LOG_ENT_ROW_ADD_REC, row_id, rec_info.ri_rec_id)) + goto failed_1; + XT_DISABLED_TRACE(("set new tx=%d row=%d rec=%d\n", (int) self->st_xact_data->xd_start_xn_id, (int) row_id, (int) rec_info.ri_rec_id)); + + /* Add the index references: */ + for (idx_cnt=0, ind=tab->tab_dic.dic_keys; idx_cnt<tab->tab_dic.dic_key_count; idx_cnt++, ind++) { + if (!xt_idx_insert(ot, *ind, 0, rec_info.ri_rec_id, rec_buf, NULL, FALSE)) { + ot->ot_err_index_no = (*ind)->mi_index_no; + goto failed_2; + } + } + +#ifdef XT_STREAMING + /* Reference the BLOBs in the row: */ + if (tab->tab_dic.dic_blob_count) { + if (!myxt_retain_blobs(ot, pbms_table, rec_info.ri_rec_id)) { + pbms_table = NULL; + goto failed_2; + } + pbms_table = NULL; + } +#endif + + /* Do the foreign key stuff: */ + if (ot->ot_table->tab_dic.dic_table->dt_fkeys.size() > 0) { + if (!ot->ot_table->tab_dic.dic_table->insertRow(ot, rec_buf)) + goto failed_2; + } + + self->st_statistics.st_row_insert++; + return OK; + + failed_2: + /* Once the row has been inserted, it is to late to remove it! + * Now all we can do is delete it! + */ + tab_delete_record_on_fail(ot, row_id, rec_info.ri_rec_id, (XTTabRecHeadDPtr) rec_info.ri_fix_rec_buf, rec_buf, idx_cnt); + goto failed_0; + + failed_1: + tab_free_row_on_fail(ot, tab, row_id); + + failed_0: +#ifdef XT_STREAMING + if (tab->tab_dic.dic_blob_count && pbms_table) + myxt_unuse_blobs(ot, pbms_table); +#endif + return FAILED; +} + +/* We cannot remove a change we have made to a row while a transaction + * is running, so we have to undo what we have done by + * overwriting the record we just created with + * the before image! + */ +static xtBool tab_overwrite_record_on_fail(XTOpenTablePtr ot, XTTabRecInfoPtr rec_info, xtWord1 *before_buf, xtWord1 *after_buf, u_int idx_cnt) +{ + register XTTableHPtr tab = ot->ot_table; + XTTabRecHeadDRec prev_rec_head; + u_int i; + XTIndexPtr *ind; + XTThreadPtr thread = ot->ot_thread; + xtLogID log_id; + xtLogOffset log_offset; + xtRecordID rec_id = rec_info->ri_rec_id; + + /* Remove the new extended record: */ + if (rec_info->ri_ext_rec) + tab_free_ext_record_on_fail(ot, rec_id, (XTTabRecExtDPtr) rec_info->ri_fix_rec_buf, TRUE); + + /* Undo index entries of the new record: */ + if (after_buf) { + for (i=0, ind=tab->tab_dic.dic_keys; i<idx_cnt; i++, ind++) { + if (!xt_idx_delete(ot, *ind, rec_id, after_buf)) + return FAILED; + } + } + + memcpy(&prev_rec_head, rec_info->ri_fix_rec_buf, sizeof(XTTabRecHeadDRec)); + + if (!before_buf) { + /* Can happen if the delete was called from some cascaded action. + * And this is better than a crash... + * + * TODO: to make sure the change will not be applied in case the + * transaction will be commited, we'd need to add a log entry to + * restore the record like it's done for top-level operation. In + * order to do this we'd need to read the before-image of the + * record before modifying it. + */ + if (!ot->ot_thread->t_exception.e_xt_err) + xt_register_xterr(XT_REG_CONTEXT, XT_ERR_NO_BEFORE_IMAGE); + return FAILED; + } + + /* Restore the previous record! */ + if (!myxt_store_row(ot, rec_info, (char *) before_buf)) + return FAILED; + + memcpy(rec_info->ri_fix_rec_buf, &prev_rec_head, sizeof(XTTabRecHeadDRec)); + + if (rec_info->ri_ext_rec) { + /* Determine where the overflow will go... */ + if (!thread->st_dlog_buf.dlb_get_log_offset(&log_id, &log_offset, rec_info->ri_log_data_size + offsetof(XTactExtRecEntryDRec, er_data), ot->ot_thread)) + return FAILED; + XT_SET_LOG_REF(rec_info->ri_ext_rec, log_id, log_offset); + } + + if (!xt_tab_put_log_op_rec_data(ot, XT_LOG_ENT_REC_MODIFIED, 0, rec_id, rec_info->ri_rec_buf_size, (xtWord1 *) rec_info->ri_fix_rec_buf)) + return FAILED; + + if (rec_info->ri_ext_rec) { + /* Write the log buffer overflow: */ + rec_info->ri_log_buf->er_status_1 = XT_LOG_ENT_EXT_REC_OK; + XT_SET_DISK_4(rec_info->ri_log_buf->er_data_size_4, rec_info->ri_log_data_size); + XT_SET_DISK_4(rec_info->ri_log_buf->er_tab_id_4, tab->tab_id); + XT_SET_DISK_4(rec_info->ri_log_buf->er_rec_id_4, rec_id); + if (!thread->st_dlog_buf.dlb_append_log(log_id, log_offset, offsetof(XTactExtRecEntryDRec, er_data) + rec_info->ri_log_data_size, (xtWord1 *) rec_info->ri_log_buf, ot->ot_thread)) + return FAILED; + } + + /* Put the index entries back: */ + for (idx_cnt=0, ind=tab->tab_dic.dic_keys; idx_cnt<tab->tab_dic.dic_key_count; idx_cnt++, ind++) { + if (!xt_idx_insert(ot, *ind, 0, rec_id, before_buf, after_buf, TRUE)) + /* Incomplete restore, there will be a rollback... */ + return FAILED; + } + + return OK; +} + +/* + * GOTCHA: + * If a transaction updates the same record over again, we should update + * in place. This prevents producing unnecessary variations! + */ +static xtBool tab_overwrite_record(XTOpenTablePtr ot, xtWord1 *before_buf, xtWord1 *after_buf) +{ + register XTTableHPtr tab = ot->ot_table; + xtRowID row_id = ot->ot_curr_row_id; + register XTThreadPtr self = ot->ot_thread; + xtRecordID rec_id = ot->ot_curr_rec_id; + XTTabRecExtDRec prev_rec_head; + XTTabRecInfoRec rec_info; + u_int idx_cnt = 0, i; + XTIndexPtr *ind; + xtLogID log_id; + xtLogOffset log_offset; + xtBool prev_ext_rec; + +#ifdef XT_STREAMING + void *pbms_table; + + if (tab->tab_dic.dic_blob_count) { + if (!myxt_use_blobs(ot, &pbms_table, after_buf)) + return FAILED; + } +#endif + + if (!myxt_store_row(ot, &rec_info, (char *) after_buf)) + goto failed_0; + + /* Read before we overwrite! */ + if (!xt_tab_get_rec_data(ot, rec_id, XT_REC_EXT_HEADER_SIZE, (xtWord1 *) &prev_rec_head)) + goto failed_0; + + prev_ext_rec = prev_rec_head.tr_rec_type_1 & XT_TAB_STATUS_EXT_DLOG; + + if (rec_info.ri_ext_rec) { + /* Determine where the overflow will go... */ + if (!self->st_dlog_buf.dlb_get_log_offset(&log_id, &log_offset, offsetof(XTactExtRecEntryDRec, er_data) + rec_info.ri_log_data_size, ot->ot_thread)) + goto failed_0; + XT_SET_LOG_REF(rec_info.ri_ext_rec, log_id, log_offset); + } + + rec_info.ri_fix_rec_buf->tr_stat_id_1 = self->st_update_id; + XT_SET_DISK_4(rec_info.ri_fix_rec_buf->tr_row_id_4, row_id); + XT_COPY_DISK_4(rec_info.ri_fix_rec_buf->tr_prev_rec_id_4, prev_rec_head.tr_prev_rec_id_4); + XT_SET_DISK_4(rec_info.ri_fix_rec_buf->tr_xact_id_4, self->st_xact_data->xd_start_xn_id); + + /* Remove the index references, that have changed: */ + for (idx_cnt=0, ind=tab->tab_dic.dic_keys; idx_cnt<tab->tab_dic.dic_key_count; idx_cnt++, ind++) { + if (!xt_idx_delete(ot, *ind, rec_id, before_buf)) { + goto failed_0; + } + } + +#ifdef TRACE_VARIATIONS + xt_ttracef(self, "overwrite: row=%d rec=%d T%d\n", (int) row_id, (int) rec_id, (int) self->st_xact_data->xd_start_xn_id); +#endif + /* Overwrite the record: */ + if (!xt_tab_put_log_op_rec_data(ot, XT_LOG_ENT_REC_MODIFIED, 0, rec_id, rec_info.ri_rec_buf_size, (xtWord1 *) rec_info.ri_fix_rec_buf)) + goto failed_0; + + if (rec_info.ri_ext_rec) { + /* Write the log buffer overflow: */ + rec_info.ri_log_buf->er_status_1 = XT_LOG_ENT_EXT_REC_OK; + XT_SET_DISK_4(rec_info.ri_log_buf->er_data_size_4, rec_info.ri_log_data_size); + XT_SET_DISK_4(rec_info.ri_log_buf->er_tab_id_4, tab->tab_id); + XT_SET_DISK_4(rec_info.ri_log_buf->er_rec_id_4, rec_id); + if (!self->st_dlog_buf.dlb_append_log(log_id, log_offset, offsetof(XTactExtRecEntryDRec, er_data) + rec_info.ri_log_data_size, (xtWord1 *) rec_info.ri_log_buf, ot->ot_thread)) + goto failed_1; + } + + /* Add the index references that have changed: */ + for (idx_cnt=0, ind=tab->tab_dic.dic_keys; idx_cnt<tab->tab_dic.dic_key_count; idx_cnt++, ind++) { + if (!xt_idx_insert(ot, *ind, 0, rec_id, after_buf, before_buf, FALSE)) { + ot->ot_err_index_no = (*ind)->mi_index_no; + goto failed_2; + } + } + + /* Do the foreign key stuff: */ + if (ot->ot_table->tab_dic.dic_table->dt_trefs || ot->ot_table->tab_dic.dic_table->dt_fkeys.size() > 0) { + if (!ot->ot_table->tab_dic.dic_table->updateRow(ot, before_buf, after_buf)) + goto failed_2; + } + + /* Delete the previous overflow area: */ + if (prev_ext_rec) + tab_free_ext_record_on_fail(ot, rec_id, &prev_rec_head, TRUE); + +#ifdef XT_STREAMING + if (tab->tab_dic.dic_blob_count) { + /* Retain the BLOBs new record: */ + if (!myxt_retain_blobs(ot, pbms_table, rec_id)) + return FAILED; + /* Release the BLOBs in the old record: */ + myxt_release_blobs(ot, before_buf, rec_id); + } +#endif + + return OK; + + failed_2: + /* Remove the new extended record: */ + if (rec_info.ri_ext_rec) + tab_free_ext_record_on_fail(ot, rec_id, (XTTabRecExtDPtr) rec_info.ri_fix_rec_buf, TRUE); + + /* Restore the previous record! */ + /* Undo index entries: */ + for (i=0, ind=tab->tab_dic.dic_keys; i<idx_cnt; i++, ind++) { + if (!xt_idx_delete(ot, *ind, rec_id, after_buf)) + goto failed_1; + } + + /* Restore the record: */ + if (!myxt_store_row(ot, &rec_info, (char *) before_buf)) + goto failed_1; + + if (rec_info.ri_ext_rec) + memcpy(rec_info.ri_fix_rec_buf, &prev_rec_head, XT_REC_EXT_HEADER_SIZE); + else + memcpy(rec_info.ri_fix_rec_buf, &prev_rec_head, sizeof(XTTabRecHeadDRec)); + + if (!xt_tab_put_log_op_rec_data(ot, XT_LOG_ENT_REC_MODIFIED, 0, rec_id, rec_info.ri_rec_buf_size, (xtWord1 *) rec_info.ri_fix_rec_buf)) + goto failed_1; + + /* Put the index entries back: */ + for (idx_cnt=0, ind=tab->tab_dic.dic_keys; idx_cnt<tab->tab_dic.dic_key_count; idx_cnt++, ind++) { + if (!xt_idx_insert(ot, *ind, 0, rec_id, before_buf, after_buf, TRUE)) + /* Incomplete restore, there will be a rollback... */ + goto failed_0; + } + + /* The previous record has now been restored. */ + goto failed_0; + + failed_1: + /* The old record is overwritten, I must free the previous extended record: */ + if (prev_ext_rec) + tab_free_ext_record_on_fail(ot, rec_id, &prev_rec_head, TRUE); + + failed_0: +#ifdef XT_STREAMING + /* Unuse the BLOBs of the new record: */ + if (tab->tab_dic.dic_blob_count && pbms_table) + myxt_unuse_blobs(ot, pbms_table); +#endif + return FAILED; +} + +xtPublic xtBool xt_tab_update_record(XTOpenTablePtr ot, xtWord1 *before_buf, xtWord1 *after_buf) +{ + register XTTableHPtr tab; + xtRowID row_id; + register XTThreadPtr self; + xtRecordID curr_var_rec_id; + XTTabRecInfoRec rec_info; + u_int idx_cnt = 0; + XTIndexPtr *ind; + +#ifdef XT_STREAMING + void *pbms_table; +#endif + + /* + * Originally only the flag ot->ot_curr_updated was checked, and if it was on, then + * tab_overwrite_record() was called, but this caused crashes in some cases like: + * + * set @@autocommit = 0; + * create table t1 (s1 int primary key); + * create table t2 (s1 int primary key, foreign key (s1) references t1 (s1) on update cascade); + * insert into t1 values (1); + * insert into t2 values (1); + * update t1 set s1 = 1; + * + * the last update lead to a crash on t2 cascade update because before_buf argument is NULL + * in the call below. It is NULL only during cascade update of child table. In that case we + * cannot pass before_buf value from XTDDTableRef::modifyRow as the before_buf is the original + * row for the parent (t1) table and it would be used to update any existing indexes + * in the child table which would be wrong of course. + * + * Alternative solution would be to copy the after_info in the XTDDTableRef::modifyRow(): + * + * ... + * if (!xt_tab_load_record(ot, ot->ot_curr_rec_id, &after_info)) + * goto failed_2; + * ... + * + * here the xt_tab_load_record() loads the original row, so we can copy it from there, but in + * that case we'd need to allocate a new (possibly up to 65536 bytes long) buffer, which makes + * the optimization questionable + * + */ + if (ot->ot_curr_updated && before_buf) + /* This record has already been updated by this transaction. + * Do the update in place! + */ + return tab_overwrite_record(ot, before_buf, after_buf); + + tab = ot->ot_table; + row_id = ot->ot_curr_row_id; + self = ot->ot_thread; + +#ifdef XT_STREAMING + /* PBMS: Reference BLOBs!? */ + if (tab->tab_dic.dic_blob_count) { + if (!myxt_use_blobs(ot, &pbms_table, after_buf)) + return FAILED; + } +#endif + + if (!myxt_store_row(ot, &rec_info, (char *) after_buf)) + goto failed_0; + + rec_info.ri_fix_rec_buf->tr_stat_id_1 = self->st_update_id; + XT_SET_DISK_4(rec_info.ri_fix_rec_buf->tr_row_id_4, row_id); + XT_SET_DISK_4(rec_info.ri_fix_rec_buf->tr_prev_rec_id_4, ot->ot_curr_rec_id); + XT_SET_DISK_4(rec_info.ri_fix_rec_buf->tr_xact_id_4, self->st_xact_data->xd_start_xn_id); + + /* Create the new record: */ + if (!tab_add_record(ot, &rec_info, XT_LOG_ENT_UPDATE)) + goto failed_0; + + /* Link the new variation into the list: */ + XT_TAB_ROW_WRITE_LOCK(&tab->tab_row_rwlock[row_id % XT_ROW_RWLOCKS], ot->ot_thread); + + if (!xt_tab_get_row(ot, row_id, &curr_var_rec_id)) + goto failed_1; + + if (curr_var_rec_id != ot->ot_curr_rec_id) { + /* If the transaction does not rollback, I will get an + * exception here: + */ + if (!tab_wait_for_rollback(ot, row_id, ot->ot_curr_rec_id)) + goto failed_1; + /* [(4)] This is the situation when we overwrite the + * reference to curr_var_rec_id! + * When curr_var_rec_id is cleaned up by the sweeper, the + * sweeper will notice that the record is no longer in + * the row list. + */ + } + +#ifdef TRACE_VARIATIONS + xt_ttracef(self, "update: row=%d rec=%d T%d\n", (int) row_id, (int) rec_info.ri_rec_id, (int) self->st_xact_data->xd_start_xn_id); +#endif + if (!xt_tab_set_row(ot, XT_LOG_ENT_ROW_ADD_REC, row_id, rec_info.ri_rec_id)) + goto failed_1; + XT_DISABLED_TRACE(("set upd tx=%d row=%d rec=%d\n", (int) self->st_xact_data->xd_start_xn_id, (int) row_id, (int) rec_info.ri_rec_id)); + + XT_TAB_ROW_UNLOCK(&tab->tab_row_rwlock[row_id % XT_ROW_RWLOCKS], ot->ot_thread); + + /* Add the index references: */ + for (idx_cnt=0, ind=tab->tab_dic.dic_keys; idx_cnt<tab->tab_dic.dic_key_count; idx_cnt++, ind++) { + if (!xt_idx_insert(ot, *ind, 0, rec_info.ri_rec_id, after_buf, before_buf, FALSE)) { + ot->ot_err_index_no = (*ind)->mi_index_no; + goto failed_2; + } + } + +#ifdef XT_STREAMING + /* Reference the BLOBs in the row: */ + if (tab->tab_dic.dic_blob_count) { + if (!myxt_retain_blobs(ot, pbms_table, rec_info.ri_rec_id)) { + pbms_table = NULL; + goto failed_2; + } + pbms_table = NULL; + } +#endif + + if (ot->ot_table->tab_dic.dic_table->dt_trefs || ot->ot_table->tab_dic.dic_table->dt_fkeys.size() > 0) { + if (!ot->ot_table->tab_dic.dic_table->updateRow(ot, before_buf, after_buf)) + goto failed_2; + } + + ot->ot_thread->st_statistics.st_row_update++; + return OK; + + failed_2: + tab_overwrite_record_on_fail(ot, &rec_info, before_buf, after_buf, idx_cnt); + goto failed_0; + + failed_1: + XT_TAB_ROW_UNLOCK(&tab->tab_row_rwlock[row_id % XT_ROW_RWLOCKS], ot->ot_thread); + + failed_0: +#ifdef XT_STREAMING + if (tab->tab_dic.dic_blob_count && pbms_table) + myxt_unuse_blobs(ot, pbms_table); +#endif + return FAILED; +} + +xtPublic xtBool xt_tab_delete_record(XTOpenTablePtr ot, xtWord1 *rec_buf) +{ + register XTTableHPtr tab = ot->ot_table; + xtRowID row_id = ot->ot_curr_row_id; + xtRecordID curr_var_rec_id; + XTTabRecInfoRec rec_info; + + /* Setup a delete record: */ + rec_info.ri_fix_rec_buf = (XTTabRecFixDPtr) ot->ot_row_wbuffer; + rec_info.ri_rec_buf_size = offsetof(XTTabRecFixDRec, rf_data); + rec_info.ri_ext_rec = NULL; + rec_info.ri_fix_rec_buf->tr_rec_type_1 = XT_TAB_STATUS_DELETE; + rec_info.ri_fix_rec_buf->tr_stat_id_1 = 0; + XT_SET_DISK_4(rec_info.ri_fix_rec_buf->tr_row_id_4, row_id); + XT_SET_DISK_4(rec_info.ri_fix_rec_buf->tr_prev_rec_id_4, ot->ot_curr_rec_id); + XT_SET_DISK_4(rec_info.ri_fix_rec_buf->tr_xact_id_4, ot->ot_thread->st_xact_data->xd_start_xn_id); + + if (!tab_add_record(ot, &rec_info, XT_LOG_ENT_DELETE)) + return FAILED; + + XT_TAB_ROW_WRITE_LOCK(&tab->tab_row_rwlock[row_id % XT_ROW_RWLOCKS], ot->ot_thread); + + if (!xt_tab_get_row(ot, row_id, &curr_var_rec_id)) + goto failed_1; + + if (curr_var_rec_id != ot->ot_curr_rec_id) { + if (!tab_wait_for_rollback(ot, row_id, ot->ot_curr_rec_id)) + goto failed_1; + } + +#ifdef TRACE_VARIATIONS + xt_ttracef(ot->ot_thread, "update: row=%d rec=%d T%d\n", (int) row_id, (int) rec_info.ri_rec_id, (int) ot->ot_thread->st_xact_data->xd_start_xn_id); +#endif + if (!xt_tab_set_row(ot, XT_LOG_ENT_ROW_ADD_REC, row_id, rec_info.ri_rec_id)) + goto failed_1; + XT_DISABLED_TRACE(("del row tx=%d row=%d rec=%d\n", (int) ot->ot_thread->st_xact_data->xd_start_xn_id, (int) row_id, (int) rec_info.ri_rec_id)); + + XT_TAB_ROW_UNLOCK(&tab->tab_row_rwlock[row_id % XT_ROW_RWLOCKS], ot->ot_thread); + + if (ot->ot_table->tab_dic.dic_table->dt_trefs) { + if (!ot->ot_table->tab_dic.dic_table->deleteRow(ot, rec_buf)) + goto failed_2; + } + + ot->ot_thread->st_statistics.st_row_delete++; + return OK; + + failed_2: + tab_overwrite_record_on_fail(ot, &rec_info, rec_buf, NULL, 0); + return FAILED; + + failed_1: + XT_TAB_ROW_UNLOCK(&tab->tab_row_rwlock[row_id % XT_ROW_RWLOCKS], ot->ot_thread); + return FAILED; +} + +xtPublic xtBool xt_tab_restrict_rows(XTBasicListPtr list, XTThreadPtr thread) +{ + u_int i; + XTRestrictItemPtr item; + XTOpenTablePtr pot = NULL; + XTDatabaseHPtr db = thread->st_database; + xtBool ok = TRUE; + + for (i=0; i<list->bl_count; i++) { + item = (XTRestrictItemPtr) xt_bl_item_at(list, i); + if (item) + if (pot) { + if (pot->ot_table->tab_id == item->ri_tab_id) + goto check_action; + xt_db_return_table_to_pool_ns(pot); + pot = NULL; + } + + if (!xt_db_open_pool_table_ns(&pot, db, item->ri_tab_id)) { + /* Should not happen, but just in case, we just don't + * remove the lock. We will probably end up with a deadlock + * somewhere. + */ + xt_log_and_clear_exception_ns(); + goto skip_check_action; + } + if (!pot) + /* Can happen of the table has been dropped: */ + goto skip_check_action; + + check_action: + if (!pot->ot_table->tab_dic.dic_table->checkNoAction(pot, item->ri_rec_id)) { + ok = FALSE; + break; + } + skip_check_action:; + } + + if (pot) + xt_db_return_table_to_pool_ns(pot); + xt_bl_free(NULL, list); + return ok; +} + + +xtPublic xtBool xt_tab_seq_init(XTOpenTablePtr ot) +{ + register XTTableHPtr tab = ot->ot_table; + + ot->ot_seq_page = NULL; + ot->ot_on_page = FALSE; + ot->ot_seq_offset = 0; + + ot->ot_curr_rec_id = 0; // 0 is an invalid position! + ot->ot_curr_row_id = 0; // 0 is an invalid row ID! + ot->ot_curr_updated = FALSE; + + /* We note the current EOF before we start a sequential scan. + * It is basically possible to update the same record more than + * once because an updated record creates a new record which + * has a new position which may be in the area that is + * still to be scanned. + * + * By noting the EOF before we start a sequential scan we + * reduce the possibility of this. + * + * However, the possibility still remains, but it should + * not be a problem because a record is not modified + * if there is nothing to change, which is the case + * if the record has already been changed! + * + * NOTE (2008-01-29) There is no longer a problem with updating a + * record twice because records are marked by an update. + * + * [(10)] I have changed this (see below). I now check the + * current EOF of the table. + * + * The reason is that committed read must be able to see the + * changes that occur during table table scan. * + */ + ot->ot_seq_eof_id = tab->tab_rec_eof_id; + + if (!ot->ot_thread->st_xact_data) { + /* MySQL ignores this error, so we + * setup the sequential scan so that it will + * deliver nothing! + */ + ot->ot_seq_rec_id = ot->ot_seq_eof_id; + xt_register_xterr(XT_REG_CONTEXT, XT_ERR_NO_TRANSACTION); + return FAILED; + } + + ot->ot_seq_rec_id = 1; + ot->ot_thread->st_statistics.st_scan_table++; + return OK; +} + +xtPublic void xt_tab_seq_reset(XTOpenTablePtr ot) +{ + ot->ot_seq_rec_id = 0; + ot->ot_seq_eof_id = 0; + ot->ot_seq_page = NULL; + ot->ot_on_page = FALSE; + ot->ot_seq_offset = 0; +} + +xtPublic void xt_tab_seq_exit(XTOpenTablePtr ot) +{ + register XTTableHPtr tab = ot->ot_table; + + if (ot->ot_seq_page) { + tab->tab_recs.xt_tc_release_page(ot->ot_rec_file, ot->ot_seq_page, ot->ot_thread); + ot->ot_seq_page = NULL; + } + ot->ot_on_page = FALSE; +} + +xtPublic xtBool xt_tab_seq_next(XTOpenTablePtr ot, xtWord1 *buffer, xtBool *eof) +{ + register XTTableHPtr tab = ot->ot_table; + register size_t rec_size = tab->tab_dic.dic_rec_size; + xtWord1 *buff_ptr; + xtRecordID new_rec_id; + xtBool ptr_locked; + xtRecordID invalid_rec = 0; + XTTabRecHeadDRec rec_head; + + next_page: + if (!ot->ot_on_page) { + if (!(ot->ot_on_page = tab->tab_recs.xt_tc_get_page(ot->ot_rec_file, ot->ot_seq_rec_id, &ot->ot_seq_page, &ot->ot_seq_offset, ot->ot_thread))) + return FAILED; + } + + next_record: + /* [(10)] The current EOF is used: */ + if (ot->ot_seq_rec_id >= ot->ot_seq_eof_id) { + *eof = TRUE; + return OK; + } + + if (ot->ot_seq_offset >= tab->tab_recs.tci_page_size) { + if (ot->ot_seq_page) { + tab->tab_recs.xt_tc_release_page(ot->ot_rec_file, ot->ot_seq_page, ot->ot_thread); + ot->ot_seq_page = NULL; + } + ot->ot_on_page = FALSE; + goto next_page; + } + + if (ot->ot_seq_page) { + ptr_locked = FALSE; + buff_ptr = ot->ot_seq_page->tcp_data + ot->ot_seq_offset; + } + else { + size_t red_size; + + ptr_locked = TRUE; + if (!xt_pread_fmap(ot->ot_rec_file, xt_rec_id_to_rec_offset(tab, ot->ot_seq_rec_id), sizeof(XTTabRecHeadDRec), sizeof(XTTabRecHeadDRec), &rec_head, &red_size, &ot->ot_thread->st_statistics.st_rec, ot->ot_thread)) + return FAILED; + buff_ptr = (xtWord1 *) &rec_head; + } + + /* This is the current record: */ + ot->ot_curr_rec_id = ot->ot_seq_rec_id; + ot->ot_curr_row_id = 0; + + /* Move to the next record: */ + ot->ot_seq_rec_id++; + ot->ot_seq_offset += rec_size; + + retry: + switch (tab_visible(ot, (XTTabRecHeadDPtr) buff_ptr, &new_rec_id)) { + case FALSE: + goto next_record; + case XT_ERR: + goto failed; + case XT_NEW: + ptr_locked = FALSE; + buff_ptr = ot->ot_row_rbuffer; + if (!xt_tab_get_rec_data(ot, new_rec_id, rec_size, ot->ot_row_rbuffer)) + return XT_ERR; + ot->ot_curr_rec_id = new_rec_id; + break; + case XT_RETRY: + goto retry; + case XT_REREAD: + if (invalid_rec != ot->ot_curr_rec_id) { + /* Don't re-read for the same record twice: */ + invalid_rec = ot->ot_curr_rec_id; + + /* Undo move to next: */ + ot->ot_seq_rec_id--; + ot->ot_seq_offset -= rec_size; + + /* Prepare to reread the page: */ + if (ot->ot_seq_page) { + tab->tab_recs.xt_tc_release_page(ot->ot_rec_file, ot->ot_seq_page, ot->ot_thread); + ot->ot_seq_page = NULL; + } + ot->ot_on_page = FALSE; + goto next_page; + } +#ifdef XT_CRASH_DEBUG + /* Should not happen! */ + xt_crash_me(); +#endif + /* Continue, and skip the record... */ + invalid_rec = 0; + goto next_record; + default: + if (ptr_locked) + XT_LOCK_MEMORY_PTR(buff_ptr, ot->ot_rec_file, xt_rec_id_to_rec_offset(tab, ot->ot_curr_rec_id), tab->tab_rows.tci_page_size, &ot->ot_thread->st_statistics.st_rec, ot->ot_thread); + break; + } + + switch (*buff_ptr) { + case XT_TAB_STATUS_FIXED: + case XT_TAB_STATUS_FIX_CLEAN: + memcpy(buffer, buff_ptr + XT_REC_FIX_HEADER_SIZE, rec_size - XT_REC_FIX_HEADER_SIZE); + break; + case XT_TAB_STATUS_VARIABLE: + case XT_TAB_STATUS_VAR_CLEAN: + if (!myxt_load_row(ot, buff_ptr + XT_REC_FIX_HEADER_SIZE, buffer, ot->ot_cols_req)) + goto failed_1; + break; + case XT_TAB_STATUS_EXT_DLOG: + case XT_TAB_STATUS_EXT_CLEAN: { + u_int cols_req = ot->ot_cols_req; + + ASSERT_NS(cols_req); + if (cols_req && cols_req <= tab->tab_dic.dic_fix_col_count) { + if (!myxt_load_row(ot, buff_ptr + XT_REC_EXT_HEADER_SIZE, buffer, cols_req)) + goto failed_1; + } + else { + if (buff_ptr != ot->ot_row_rbuffer) + memcpy(ot->ot_row_rbuffer, buff_ptr, rec_size); + if (!xt_tab_load_ext_data(ot, ot->ot_curr_rec_id, buffer, cols_req)) + goto failed_1; + } + break; + } + } + if (ptr_locked) + XT_UNLOCK_MEMORY_PTR(ot->ot_rec_file, ot->ot_thread); + + *eof = FALSE; + return OK; + + failed_1: + if (ptr_locked) + XT_UNLOCK_MEMORY_PTR(ot->ot_rec_file, ot->ot_thread); + + failed: + return FAILED; +} + diff --git a/storage/pbxt/src/table_xt.h b/storage/pbxt/src/table_xt.h new file mode 100644 index 00000000000..6ba3eadd2ae --- /dev/null +++ b/storage/pbxt/src/table_xt.h @@ -0,0 +1,607 @@ +/* Copyright (c) 2005 PrimeBase Technologies GmbH + * + * PrimeBase XT + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You 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 + * + * 2005-02-08 Paul McCullagh + * + * H&G2JCtL + */ +#ifndef __xt_table_h__ +#define __xt_table_h__ + +#include <time.h> + +#include "datalog_xt.h" +#include "filesys_xt.h" +#include "hashtab_xt.h" +#include "index_xt.h" +#include "cache_xt.h" +#include "util_xt.h" +#include "heap_xt.h" +#include "tabcache_xt.h" +#include "xactlog_xt.h" +#include "lock_xt.h" + +struct XTDatabase; +struct XTThread; +struct XTCache; +struct XTOpenTable; +struct XTTablePath; + +#define XT_TAB_INCOMPATIBLE_VERSION 4 +#define XT_TAB_CURRENT_VERSION 5 + +#define XT_IND_CURRENT_VERSION 3 + +#define XT_HEAD_BUFFER_SIZE 1024 + +#ifdef DEBUG +//#define XT_TRACK_INDEX_UPDATES +//#define XT_TRACK_RETURNED_ROWS +#endif + +/* + * NOTE: Records may only be freed (placed on the free list), after + * all currently running transactions have ended. + * The reason is, running transactions may have references in memory + * to these records (a sequential scan has a large buffer). + * If the records are freed they may be re-used. This will + * cause problems because the references will then refer to + * new data. + * + * As a result, deleted records are first placed in the + * REMOVED state. Later, when transactions have quit, they + * are freed. + */ +#define XT_TAB_STATUS_FREED 0x00 /* On the free list. */ +#define XT_TAB_STATUS_DELETE 0x01 /* A transactional delete record (an "update" that indicates a delete). */ +#define XT_TAB_STATUS_FIXED 0x02 +#define XT_TAB_STATUS_VARIABLE 0x03 /* Uses one block, but has the variable format. */ +#define XT_TAB_STATUS_EXT_DLOG 0x04 /* Variable format, and the trailing part of the record in the data log. */ +#define XT_TAB_STATUS_EXT_HDATA 0x05 /* Variable format, and the trailing part of the record in the handle data file. */ +#define XT_TAB_STATUS_DATA 0x06 /* A block of data with a next pointer (5 bytes overhead). */ +#define XT_TAB_STATUS_END_DATA 0x07 /* An block of data without an end pointer (1 byte overhead). */ +#define XT_TAB_STATUS_MASK 0x0F + +#define XT_TAB_STATUS_DEL_CLEAN (XT_TAB_STATUS_DELETE | XT_TAB_STATUS_CLEANED_BIT) +#define XT_TAB_STATUS_FIX_CLEAN (XT_TAB_STATUS_FIXED | XT_TAB_STATUS_CLEANED_BIT) +#define XT_TAB_STATUS_VAR_CLEAN (XT_TAB_STATUS_VARIABLE | XT_TAB_STATUS_CLEANED_BIT) +#define XT_TAB_STATUS_EXT_CLEAN (XT_TAB_STATUS_EXT_DLOG | XT_TAB_STATUS_CLEANED_BIT) + +#define XT_TAB_STATUS_CLEANED_BIT 0x80 /* This bit is set when the record is cleaned and committed. */ + +#define XT_REC_IS_CLEAN(x) ((x) & XT_TAB_STATUS_CLEANED_BIT) +#define XT_REC_IS_FREE(x) (((x) & XT_TAB_STATUS_MASK) == XT_TAB_STATUS_FREED) +#define XT_REC_IS_DELETE(x) (((x) & XT_TAB_STATUS_MASK) == XT_TAB_STATUS_DELETE) +#define XT_REC_IS_FIXED(x) (((x) & XT_TAB_STATUS_MASK) == XT_TAB_STATUS_FIXED) +#define XT_REC_IS_VARIABLE(x) (((x) & XT_TAB_STATUS_MASK) == XT_TAB_STATUS_VARIABLE) +#define XT_REC_IS_EXT_DLOG(x) (((x) & XT_TAB_STATUS_MASK) == XT_TAB_STATUS_EXT_DLOG) +#define XT_REC_IS_EXT_HDATA(x) (((x) & XT_TAB_STATUS_MASK) == XT_TAB_STATUS_EXT_HDATA) +#define XT_REC_NOT_VALID(x) (XT_REC_IS_FREE(x) || XT_REC_IS_DELETE(x)) + +/* Results for xt_use_table_by_id(): */ +#define XT_TAB_OK 0 +#define XT_TAB_NOT_FOUND 1 +#define XT_TAB_NO_DICTIONARY 2 +#define XT_TAB_POOL_CLOSED 3 /* Cannot open table at the moment, the pool is closed. */ +#define XT_TAB_FAILED 4 + +#define XT_TAB_ROW_USE_RW_MUTEX + +#ifdef XT_TAB_ROW_USE_FASTWRLOCK +#define XT_TAB_ROW_LOCK_TYPE XTFastRWLockRec +#define XT_TAB_ROW_INIT_LOCK(s, i) xt_fastrwlock_init(s, i) +#define XT_TAB_ROW_FREE_LOCK(s, i) xt_fastrwlock_free(s, i) +#define XT_TAB_ROW_READ_LOCK(i, s) xt_fastrwlock_slock(i, s) +#define XT_TAB_ROW_WRITE_LOCK(i, s) xt_fastrwlock_xlock(i, s) +#define XT_TAB_ROW_UNLOCK(i, s) xt_fastrwlock_unlock(i, s) +#elif defined(XT_TAB_ROW_USE_PTHREAD_RW) +#define XT_TAB_ROW_LOCK_TYPE xt_rwlock_type +#define XT_TAB_ROW_INIT_LOCK(s, i) xt_init_rwlock(s, i) +#define XT_TAB_ROW_FREE_LOCK(s, i) xt_free_rwlock(i) +#define XT_TAB_ROW_READ_LOCK(i, s) xt_slock_rwlock_ns(i) +#define XT_TAB_ROW_WRITE_LOCK(i, s) xt_xlock_rwlock_ns(i) +#define XT_TAB_ROW_UNLOCK(i, s) xt_unlock_rwlock_ns(i) +#elif defined(XT_TAB_ROW_USE_RW_MUTEX) +#define XT_TAB_ROW_LOCK_TYPE XTRWMutexRec +#define XT_TAB_ROW_INIT_LOCK(s, i) xt_rwmutex_init_with_autoname(s, i) +#define XT_TAB_ROW_FREE_LOCK(s, i) xt_rwmutex_free(s, i) +#define XT_TAB_ROW_READ_LOCK(i, s) xt_rwmutex_slock(i, (s)->t_id) +#define XT_TAB_ROW_WRITE_LOCK(i, s) xt_rwmutex_xlock(i, (s)->t_id) +#define XT_TAB_ROW_UNLOCK(i, s) xt_rwmutex_unlock(i, (s)->t_id) +#else +#define XT_TAB_ROW_LOCK_TYPE XTSpinLockRec +#define XT_TAB_ROW_INIT_LOCK(s, i) xt_spinlock_init(s, i) +#define XT_TAB_ROW_FREE_LOCK(s, i) xt_spinlock_free(s, i) +#define XT_TAB_ROW_READ_LOCK(i, s) xt_spinlock_lock(i) +#define XT_TAB_ROW_WRITE_LOCK(i, s) xt_spinlock_lock(i) +#define XT_TAB_ROW_UNLOCK(i, s) xt_spinlock_unlock(i) +#endif + +/* ------- TABLE DATA FILE ------- */ + +#define XT_TAB_DATA_MAGIC 0x1234ABCD + +#define XT_FORMAT_DEF_SPACE 512 + +#define XT_TAB_FLAGS_TEMP_TAB 1 + +/* + * This header ensures that no record in the data file has the offset 0. + */ +typedef struct XTTableHead { + XTDiskValue4 th_head_size_4; /* The size of the table header. */ + XTDiskValue4 th_op_seq_4; + XTDiskValue6 th_row_free_6; + XTDiskValue6 th_row_eof_6; + XTDiskValue6 th_row_fnum_6; + XTDiskValue6 th_rec_free_6; + XTDiskValue6 th_rec_eof_6; + XTDiskValue6 th_rec_fnum_6; +} XTTableHeadDRec, *XTTableHeadDPtr; + +typedef struct XTTableFormat { + XTDiskValue4 tf_format_size_4; /* The size of this structure (table format). */ + XTDiskValue4 tf_tab_head_size_4; /* The offset of the first record in the data handle file. */ + XTDiskValue2 tf_tab_version_2; /* The table version number. */ + XTDiskValue2 tf_tab_flags_2; /* Table flags XT_TAB_FLAGS_* */ + XTDiskValue4 tf_rec_size_4; /* The maximum size of records in the table. */ + XTDiskValue1 tf_rec_fixed_1; /* Set to 1 if this table contains fixed length records. */ + XTDiskValue1 tf_reserved_1; /* - */ + XTDiskValue8 tf_min_auto_inc_8; /* This is the minimum auto-increment value. */ + xtWord1 tf_reserved[64]; /* Reserved, set to 0. */ + char tf_definition[XT_VAR_LENGTH]; /* A cstring, currently it only contains the foreign key information. */ +} XTTableFormatDRec, *XTTableFormatDPtr; + +#define XT_STAT_ID_MASK(x) ((x) & (u_int) 0x000000FF) + +/* A record that fits completely in the data file record */ +typedef struct XTTabRecHead { + xtWord1 tr_rec_type_1; + xtWord1 tr_stat_id_1; + xtDiskRecordID4 tr_prev_rec_id_4; /* The previous variation of this record. */ + XTDiskValue4 tr_xact_id_4; /* The transaction ID. */ + XTDiskValue4 tr_row_id_4; /* The row ID of this record. */ +} XTTabRecHeadDRec, *XTTabRecHeadDPtr; + +typedef struct XTTabRecFix { + xtWord1 tr_rec_type_1; /* XT_TAB_STATUS_FREED, XT_TAB_STATUS_DELETE, + * XT_TAB_STATUS_FIXED, XT_TAB_STATUS_VARIABLE */ + xtWord1 tr_stat_id_1; + xtDiskRecordID4 tr_prev_rec_id_4; /* The previous variation of this record. */ + XTDiskValue4 tr_xact_id_4; /* The transaction ID. */ + XTDiskValue4 tr_row_id_4; /* The row ID of this record. */ + xtWord1 rf_data[XT_VAR_LENGTH]; /* NOTE: This data is in RAW MySQL format. */ +} XTTabRecFixDRec, *XTTabRecFixDPtr; + +/* An extended record that overflows into the log file: */ +typedef struct XTTabRecExt { + xtWord1 tr_rec_type_1; /* XT_TAB_STATUS_EXT_DLOG */ + xtWord1 tr_stat_id_1; + xtDiskRecordID4 tr_prev_rec_id_4; /* The previous variation of this record. */ + XTDiskValue4 tr_xact_id_4; /* The transaction ID. */ + XTDiskValue4 tr_row_id_4; /* The row ID of this record. */ + XTDiskValue2 re_log_id_2; /* Reference to overflow area, log ID */ + XTDiskValue6 re_log_offs_6; /* Reference to the overflow area, log offset */ + XTDiskValue4 re_log_dat_siz_4; /* Size of the overflow data. */ + xtWord1 re_data[XT_VAR_LENGTH]; /* This data is in packed PBXT format. */ +} XTTabRecExtDRec, *XTTabRecExtDPtr; + +typedef struct XTTabRecExtHdat { + xtWord1 tr_rec_type_1; /* XT_TAB_STATUS_EXT_HDATA */ + xtWord1 tr_stat_id_1; + xtDiskRecordID4 tr_prev_rec_id_4; /* The previous variation of this record. */ + XTDiskValue4 tr_xact_id_4; /* The transaction ID. */ + XTDiskValue4 tr_row_id_4; /* The row ID of this record. */ + XTDiskValue4 eh_blk_rec_id_4; /* The record ID of the next block. */ + XTDiskValue2 eh_blk_siz_2; /* The total size of the data in the trailing blocks */ + xtWord1 eh_data[XT_VAR_LENGTH]; /* This data is in packed PBXT format. */ +} XTTabRecExtHdatDRec, *XTTabRecExtHdatDPtr; + +typedef struct XTTabRecData { + xtWord1 tr_rec_type_1; /* XT_TAB_STATUS_DATA */ + XTDiskValue4 rd_blk_rec_id_4; /* The record ID of the next block. */ + xtWord1 rd_data[XT_VAR_LENGTH]; /* This data is in packed PBXT format. */ +} XTTabRecDataDRec, *XTTabRecDataDPtr; + +typedef struct XTTabRecEndDat { + xtWord1 tr_rec_type_1; /* XT_TAB_STATUS_END_DATA */ + xtWord1 ed_data[XT_VAR_LENGTH]; /* This data is in packed PBXT format. */ +} XTTabRecEndDatDRec, *XTTabRecEndDatDPtr; + +#define XT_REC_FIX_HEADER_SIZE sizeof(XTTabRecHeadDRec) +#define XT_REC_EXT_HEADER_SIZE offsetof(XTTabRecExtDRec, re_data) +#define XT_REC_FIX_EXT_HEADER_DIFF (XT_REC_EXT_HEADER_SIZE - XT_REC_FIX_HEADER_SIZE) + +typedef struct XTTabRecFree { + xtWord1 rf_rec_type_1; + xtWord1 rf_not_used_1; + xtDiskRecordID4 rf_next_rec_id_4; /* The next block on the free list. */ +} XTTabRecFreeDRec, *XTTabRecFreeDPtr; + +typedef struct XTTabRecInfo { + XTTabRecFixDPtr ri_fix_rec_buf; /* This references the start of the buffer (set for all types of records) */ + XTTabRecExtDPtr ri_ext_rec; /* This is only set for extended records. */ + xtWord4 ri_rec_buf_size; + XTactExtRecEntryDPtr ri_log_buf; + xtWord4 ri_log_data_size; /* This size of the data in the log record. */ + xtRecordID ri_rec_id; /* The record ID. */ +} XTTabRecInfoRec, *XTTabRecInfoPtr; + +/* ------- TABLE ROW FILE ------- */ + +#define XT_TAB_ROW_SHIFTS 2 +#define XT_TAB_ROW_MAGIC 0x4567CDEF +//#define XT_TAB_ROW_FREE 0 +//#define XT_TAB_ROW_IN_USE 1 + +/* + * NOTE: The shift count assumes the size of a table row + * reference is 8 bytes (XT_TAB_ROW_SHIFTS) + */ +typedef struct XTTabRowRef { + XTDiskValue4 rr_ref_id_4; /* 4-byte reference, could be a RowID or a RecordID + * If this row is free, then it is a RowID, which + * references the next free row. + * If it is in use, then it is a RecordID which + * points to the first record in the variation + * list for the row. + */ +} XTTabRowRefDRec, *XTTabRowRefDPtr; + +/* + * This is the header for the row file. The size MUST be a + * the same size as sizeof(XTTabRowRefDRec) + */ +typedef struct XTTabRowHead { + XTDiskValue4 rh_magic_4; +} XTTabRowHeadDRec, *XTTabRowHeadDPtr; + +/* ------- TABLE & OPEN TABLES & TABLE LISTING ------- */ + +/* {TEMP-TABLES} + * Temporary tables do not need to be flused, + * and they also do not need to be recovered! + * Currently this is determined by the name of the + * table! + */ +typedef struct XTTable : public XTHeap { + struct XTDatabase *tab_db; /* Heap pointer */ + XTPathStrPtr tab_name; + xtBool tab_free_locks; + xtTableID tab_id; + + xtWord8 tab_auto_inc; /* The last value returned as an auto-increment value {PRE-INC}. */ + XTSpinLockRec tab_ainc_lock; /* Lock for the auto-increment counter. */ + + size_t tab_index_format_offset; + size_t tab_index_header_size; + size_t tab_index_page_size; + u_int tab_index_block_shifts; + XTIndexHeadDPtr tab_index_head; + size_t tab_table_format_offset; + size_t tab_table_head_size; + XTDictionaryRec tab_dic; + xt_mutex_type tab_dic_field_lock; /* Lock for setting field->ptr!. */ + + XTRowLocksRec tab_locks; /* The locks held on this table. */ + + XTTableSeqRec tab_seq; /* The table operation sequence. */ + XTTabCacheRec tab_rows; + XTTabCacheRec tab_recs; + + /* Used to apply operations to the database in order. */ + XTSortedListPtr tab_op_list; /* The operation list. Operations to be applied. */ + + /* Values that belong in the header when flushed! */ + xtBool tab_flush_pending; /* TRUE if the table needs to be flushed */ + xtBool tab_recovery_done; /* TRUE if the table has been recovered */ + xtBool tab_temporary; /* TRUE if this is a temporary table {TEMP-TABLES}. */ + off_t tab_bytes_to_flush; /* Number of bytes of the record/row files to flush. */ + + xtOpSeqNo tab_head_op_seq; /* The number of the operation last applied to the database. */ + xtRowID tab_head_row_free_id; + xtRowID tab_head_row_eof_id; + xtWord4 tab_head_row_fnum; + xtRecordID tab_head_rec_free_id; + xtRecordID tab_head_rec_eof_id; + xtWord4 tab_head_rec_fnum; + + xtOpSeqNo tab_co_op_seq; /* The operation last applied by the compactor. */ + + xtBool tab_wr_wake_freeer; /* Set to TRUE if the writer must wake the freeer. */ + xtOpSeqNo tab_wake_freeer_op; /* Set to the sequence number the freeer is waiting for. */ + + XTFilePtr tab_row_file; + xtRowID tab_row_eof_id; /* Indicates the EOF of the table row file. */ + xtRowID tab_row_free_id; /* The start of the free list in the table row file. */ + xtWord4 tab_row_fnum; /* The count of the number of free rows on the free list. */ + xt_mutex_type tab_row_lock; /* Lock for updating the EOF and free list. */ + XT_TAB_ROW_LOCK_TYPE tab_row_rwlock[XT_ROW_RWLOCKS]; /* Used to lock a row during update. */ + + xt_mutex_type tab_rec_flush_lock; /* Required while the record/row files are being flushed. */ + XTFilePtr tab_rec_file; + xtRecordID tab_rec_eof_id; /* This value can only grow. */ + xtRecordID tab_rec_free_id; + xtWord4 tab_rec_fnum; /* The count of the number of free rows on the free list. */ + xt_mutex_type tab_rec_lock; /* Lock for the free list. */ + + xt_mutex_type tab_ind_flush_lock; /* Required while the index file is being flushed. */ + xtLogID tab_ind_rec_log_id; /* The point before which index entries have been written. */ + xtLogOffset tab_ind_rec_log_offset; /* The log offset of the write point. */ + XTFilePtr tab_ind_file; + xtIndexNodeID tab_ind_eof; /* This value can only grow. */ + xtIndexNodeID tab_ind_free; /* The start of the free page list of the index. */ + XTIndFreeListPtr tab_ind_free_list; /* A cache of the free list (if exists, don't go to disk!) */ + xt_mutex_type tab_ind_lock; /* Lock for reading and writing the index free list. */ + xtWord2 tab_ind_flush_seq; +} XTTableHRec, *XTTableHPtr; /* Heap pointer */ + +/* Used for an in-memory list of the tables, ordered by ID. */ +typedef struct XTTableEntry { + xtTableID te_tab_id; + char *te_tab_name; + struct XTTablePath *te_tab_path; + XTTableHPtr te_table; +} XTTableEntryRec, *XTTableEntryPtr; + +typedef struct XTOpenTable { + struct XTThread *ot_thread; /* The thread currently using this open table. */ + XTTableHPtr ot_table; /* PBXT table information. */ + + struct XTOpenTable *ot_otp_next_free; /* Next free open table in the open table pool. */ + struct XTOpenTable *ot_otp_mr_used; + struct XTOpenTable *ot_otp_lr_used; + time_t ot_otp_free_time; /* The time this table was place on the free list. */ + + //struct XTOpenTable *ot_pool_next; /* Next pointer for open table pool. */ + + XT_ROW_REC_FILE_PTR ot_rec_file; + XT_ROW_REC_FILE_PTR ot_row_file; + XTOpenFilePtr ot_ind_file; + u_int ot_err_index_no; /* The number of the index on which the last error occurred */ + + xtBool ot_rec_fixed; /* Cached from table for quick access. */ + size_t ot_rec_size; /* Cached from table for quick access. */ + + char ot_error_key[XT_IDENTIFIER_NAME_SIZE]; + xtBool ot_for_update; /* True if reading FOR UPDATE. */ + xtBool ot_is_modify; /* True if UPDATE or DELETE. */ + xtRowID ot_temp_row_lock; /* The temporary row lock set on this table. */ + u_int ot_cols_req; /* The number of columns required from the table. */ + + /* GOTCHA: Separate buffers for reading and writing rows because + * of blob references, to this buffer, as in this test: + * + * drop table if exists t1; + * CREATE TABLE t1 (id MEDIUMINT NOT NULL, b1 BIT(8), vc TEXT, + * bc CHAR(255), d DECIMAL(10,4) DEFAULT 0, + * f FLOAT DEFAULT 0, total BIGINT UNSIGNED, + * y YEAR, t DATE) + * PARTITION BY RANGE (YEAR(t)) + * (PARTITION p1 VALUES LESS THAN (2005), + * PARTITION p2 VALUES LESS THAN MAXVALUE); + * + * INSERT INTO t1 VALUES(412,1,'eTesting MySQL databases is a cool ', + * 'EEEMust make it bug free for the customer', + * 654321.4321,15.21,0,1965,"2005-11-14"); + * + * UPDATE t1 SET b1 = 0, t="2006-02-22" WHERE id = 412; + * + */ + size_t ot_row_rbuf_size; /* The current size of the read row buffer (resized dynamically). */ + xtWord1 *ot_row_rbuffer; /* The row buffer for reading rows. */ + size_t ot_row_wbuf_size; /* The current size of the write row buffer (resized dynamically). */ + xtWord1 *ot_row_wbuffer; /* The row buffer for writing rows. */ + + /* Details of the current record: */ + xtRecordID ot_curr_rec_id; /* The offset of the current record. */ + xtRowID ot_curr_row_id; /* The row ID of the current record. */ + xtBool ot_curr_updated; /* TRUE if the current record was updated by the current transaction. */ + + XTIndBlockPtr ot_ind_res_bufs; /* A list of reserved index buffers. */ + u_int ot_ind_res_count; /* The number of reserved buffers. */ +#ifdef XT_TRACK_INDEX_UPDATES + u_int ot_ind_changed; + u_int ot_ind_reserved; + u_int ot_ind_reads; +#endif +#ifdef XT_TRACK_RETURNED_ROWS + u_int ot_rows_ret_max; + u_int ot_rows_ret_curr; + xtRecordID *ot_rows_returned; +#endif + /* GOTCHA: Separate buffers for reading and writing the index are required + * because MySQL sometimes scans and updates an index with the same + * table handler. + */ + XTIdxItemRec ot_ind_state; /* Decribes the state of the index buffer. */ + XTIndHandlePtr ot_ind_rhandle; /* This handle references a block which is being used in a sequential scan. */ + //XTIdxBranchDRec ot_ind_rbuf; /* The index read buffer. */ + XTIdxBranchDRec ot_ind_wbuf; /* Buffer for the current index node for writing. */ + xtWord1 ot_ind_wbuf2[XT_INDEX_PAGE_SIZE]; /* Overflow for the write buffer when a node is too big. */ + + /* Note: the fields below ot_ind_rbuf are not zero'ed out on creation + * of this structure! + */ + xtRecordID ot_seq_rec_id; /* Current position of a sequential scan. */ + xtRecordID ot_seq_eof_id; /* The EOF at the start of the sequential scan. */ + XTTabCachePagePtr ot_seq_page; /* If ot_seq_buffer is non-NULL, then a page has been locked! */ + xtBool ot_on_page; + size_t ot_seq_offset; /* Offset on the current page. */ +} XTOpenTableRec, *XTOpenTablePtr; + +#define XT_DATABASE_NAME_SIZE XT_IDENTIFIER_NAME_SIZE + +typedef struct XTTableDesc { + char td_tab_name[XT_TABLE_NAME_SIZE+4]; // 4 extra for DEL# (tables being deleted) + xtTableID td_tab_id; + char *td_file_name; + + struct XTDatabase *td_db; + struct XTTablePath *td_tab_path; // The path of the table. + u_int td_path_idx; + XTOpenDirPtr td_open_dir; +} XTTableDescRec, *XTTableDescPtr; + + +typedef struct XTFilesOfTable { + int ft_state; + XTPathStrPtr ft_tab_name; + xtTableID ft_tab_id; + char ft_file_path[PATH_MAX]; +} XTFilesOfTableRec, *XTFilesOfTablePtr; + +typedef struct XTRestrictItem { + xtTableID ri_tab_id; + xtRecordID ri_rec_id; +} XTRestrictItemRec, *XTRestrictItemPtr; + +int xt_tab_compare_names(const char *n1, const char *n2); +int xt_tab_compare_paths(char *n1, char *n2); +void xt_tab_init_db(struct XTThread *self, struct XTDatabase *db); +void xt_tab_exit_db(struct XTThread *self, struct XTDatabase *db); +void xt_check_tables(struct XTThread *self); + +char *xt_tab_file_to_name(size_t size, char *tab_name, char *file_name); + +void xt_create_table(struct XTThread *self, XTPathStrPtr name, XTDictionaryPtr dic); +XTTableHPtr xt_use_table(struct XTThread *self, XTPathStrPtr name, xtBool no_load, xtBool missing_ok, xtBool *opened); +void xt_sync_flush_table(struct XTThread *self, XTOpenTablePtr ot); +xtBool xt_flush_record_row(XTOpenTablePtr ot, off_t *bytes_flushed, xtBool have_table_loc); +void xt_flush_table(struct XTThread *self, XTOpenTablePtr ot); +XTTableHPtr xt_use_table_no_lock(XTThreadPtr self, struct XTDatabase *db, XTPathStrPtr name, xtBool no_load, xtBool missing_ok, XTDictionaryPtr dic, xtBool *opened); +int xt_use_table_by_id(struct XTThread *self, XTTableHPtr *tab, struct XTDatabase *db, xtTableID tab_id); +XTOpenTablePtr xt_open_table(XTTableHPtr tab); +void xt_close_table(XTOpenTablePtr ot, xtBool flush, xtBool have_table_lock); +void xt_drop_table(struct XTThread *self, XTPathStrPtr name); +void xt_check_table(XTThreadPtr self, XTOpenTablePtr tab); +void xt_rename_table(struct XTThread *self, XTPathStrPtr old_name, XTPathStrPtr new_name); + +void xt_describe_tables_init(struct XTThread *self, struct XTDatabase *db, XTTableDescPtr td); +xtBool xt_describe_tables_next(struct XTThread *self, XTTableDescPtr td); +void xt_describe_tables_exit(struct XTThread *self, XTTableDescPtr td); + +xtBool xt_table_exists(struct XTDatabase *db); + +void xt_enum_tables_init(u_int *edx); +XTTableEntryPtr xt_enum_tables_next(struct XTThread *self, struct XTDatabase *db, u_int *edx); + +void xt_enum_files_of_tables_init(struct XTDatabase *db, char *tab_name, xtTableID tab_id, XTFilesOfTablePtr ft); +xtBool xt_enum_files_of_tables_next(XTFilesOfTablePtr ft); + +xtBool xt_tab_seq_init(XTOpenTablePtr ot); +void xt_tab_seq_reset(XTOpenTablePtr ot); +void xt_tab_seq_exit(XTOpenTablePtr ot); +xtBool xt_tab_seq_next(XTOpenTablePtr ot, xtWord1 *buffer, xtBool *eof); + +xtBool xt_tab_new_record(XTOpenTablePtr ot, xtWord1 *buffer); +xtBool xt_tab_delete_record(XTOpenTablePtr ot, xtWord1 *buffer); +xtBool xt_tab_restrict_rows(XTBasicListPtr list, struct XTThread *thread); +xtBool xt_tab_update_record(XTOpenTablePtr ot, xtWord1 *before_buf, xtWord1 *after_buf); +int xt_tab_visible(XTOpenTablePtr ot); +int xt_tab_read_record(register XTOpenTablePtr ot, xtWord1 *buffer); +int xt_tab_dirty_read_record(register XTOpenTablePtr ot, xtWord1 *buffer); +void xt_tab_load_row_pointers(XTThreadPtr self, XTOpenTablePtr ot); +void xt_tab_load_table(struct XTThread *self, XTOpenTablePtr ot); +xtBool xt_tab_load_record(register XTOpenTablePtr ot, xtRecordID rec_id, XTInfoBufferPtr rec_buf); +int xt_tab_remove_record(XTOpenTablePtr ot, xtRecordID rec_id, xtWord1 *rec_data, xtRecordID *prev_var_rec_id, xtBool clean_delete, xtRowID row_id, xtXactID xn_id); +int xt_tab_maybe_committed(XTOpenTablePtr ot, xtRecordID rec_id, xtXactID *xn_id, xtRowID *out_rowid, xtBool *out_updated); +xtBool xt_tab_free_record(XTOpenTablePtr ot, u_int status, xtRecordID rec_id, xtBool clean_delete); +void xt_tab_store_header(XTOpenTablePtr ot, XTTableHeadDPtr rec_head); +xtBool xt_tab_write_header(XTOpenTablePtr ot, XTTableHeadDPtr rec_head, struct XTThread *thread); +xtBool xt_tab_write_min_auto_inc(XTOpenTablePtr ot); + +xtBool xt_tab_get_row(register XTOpenTablePtr ot, xtRowID row_id, xtRecordID *var_rec_id); +xtBool xt_tab_set_row(XTOpenTablePtr ot, u_int status, xtRowID row_id, xtRecordID var_rec_id); +xtBool xt_tab_free_row(XTOpenTablePtr ot, XTTableHPtr tab, xtRowID row_id); + +xtBool xt_tab_load_ext_data(XTOpenTablePtr ot, xtRecordID load_rec_id, xtWord1 *buffer, u_int cols_req); +xtBool xt_tab_put_rec_data(XTOpenTablePtr ot, xtRecordID rec_id, size_t size, xtWord1 *buffer, xtOpSeqNo *op_seq); +xtBool xt_tab_put_eof_rec_data(XTOpenTablePtr ot, xtRecordID rec_id, size_t size, xtWord1 *buffer, xtOpSeqNo *op_seq); +xtBool xt_tab_put_log_op_rec_data(XTOpenTablePtr ot, u_int status, xtRecordID free_rec_id, xtRecordID rec_id, size_t size, xtWord1 *buffer); +xtBool xt_tab_put_log_rec_data(XTOpenTablePtr ot, u_int status, xtRecordID free_rec_id, xtRecordID rec_id, size_t size, xtWord1 *buffer, xtOpSeqNo *op_seq); +xtBool xt_tab_get_rec_data(register XTOpenTablePtr ot, xtRecordID rec_id, size_t size, xtWord1 *buffer); +void xt_tab_set_index_error(XTTableHPtr tab); + +inline off_t xt_row_id_to_row_offset(register XTTableHPtr tab, xtRowID row_id) +{ + return (off_t) tab->tab_rows.tci_header_size + (off_t) (row_id - 1) * (off_t) tab->tab_rows.tci_rec_size; +} + +inline xtRowID xt_row_offset_row_id(register XTTableHPtr tab, off_t rec_offs) +{ +#ifdef DEBUG + if (((rec_offs - (off_t) tab->tab_rows.tci_header_size) % (off_t) tab->tab_rows.tci_rec_size) != 0) { + printf("ERROR! Not a valid record offset!\n"); + } +#endif + return (xtRowID) ((rec_offs - (off_t) tab->tab_rows.tci_header_size) / (off_t) tab->tab_rows.tci_rec_size) + 1; +} + +inline off_t xt_rec_id_to_rec_offset(register XTTableHPtr tab, xtRefID ref_id) +{ + if (!ref_id) + return (off_t) 0; + return (off_t) tab->tab_recs.tci_header_size + (off_t) (ref_id-1) * (off_t) tab->tab_recs.tci_rec_size; +} + +inline xtRefID xt_rec_offset_rec_id(register XTTableHPtr tab, off_t ref_offs) +{ + if (!ref_offs) + return (xtRefID) 0; +#ifdef DEBUG + if (((ref_offs - (off_t) tab->tab_recs.tci_header_size) % (off_t) tab->tab_recs.tci_rec_size) != 0) { + printf("ERROR! Not a valid record offset!\n"); + } +#endif + + return (xtRefID) ((ref_offs - (off_t) tab->tab_recs.tci_header_size) / (off_t) tab->tab_recs.tci_rec_size)+1; +} + +inline off_t xt_ind_node_to_offset(register XTTableHPtr tab, xtIndexNodeID node_id) +{ + if (!XT_NODE_ID(node_id)) + return (off_t) 0; + return (off_t) tab->tab_index_header_size + (off_t) (XT_NODE_ID(node_id)-1) * (off_t) tab->tab_index_page_size; +} + +inline xtIndexNodeID xt_ind_offset_to_node(register XTTableHPtr tab, off_t ind_offs) +{ + XT_NODE_TEMP; + + if (!ind_offs) + return XT_RET_NODE_ID(0); +#ifdef DEBUG + if (((ind_offs - (off_t) tab->tab_index_header_size) % (off_t) tab->tab_index_page_size) != 0) { + printf("ERROR! Not a valid index offset!\n"); + } +#endif + + return XT_RET_NODE_ID(((ind_offs - (off_t) tab->tab_index_header_size) / (off_t) tab->tab_index_page_size)+1); +} + +#define XT_RESIZE_ROW_BUFFER(thr, rb, size) \ + do { \ + if (rb->rb_size < size) { \ + xt_realloc(thr, (void **) &rb->x.rb_buffer, size); \ + rb->rb_size = size; \ + } \ + } \ + while (0) + +#endif diff --git a/storage/pbxt/src/thread_xt.cc b/storage/pbxt/src/thread_xt.cc new file mode 100644 index 00000000000..bd7dca31cb5 --- /dev/null +++ b/storage/pbxt/src/thread_xt.cc @@ -0,0 +1,2300 @@ +/* Copyright (c) 2005 PrimeBase Technologies GmbH + * + * PrimeBase XT + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You 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 + * + * 2005-01-03 Paul McCullagh + * + * H&G2JCtL + */ + +#include "xt_config.h" + +#ifndef XT_WIN +#include <unistd.h> +#include <sys/time.h> +#include <sys/resource.h> +#endif +#include <time.h> +#include <stdarg.h> +#include <signal.h> +#include <stdlib.h> +#include <ctype.h> +#include <errno.h> + +#include "xt_defs.h" +#include "strutil_xt.h" +#include "pthread_xt.h" +#include "thread_xt.h" +#include "memory_xt.h" +#include "sortedlist_xt.h" +#include "trace_xt.h" +#include "myxt_xt.h" +#include "database_xt.h" + +void xt_db_init_thread(XTThreadPtr self, XTThreadPtr new_thread); +void xt_db_exit_thread(XTThreadPtr self); + +static void thr_accumulate_statistics(XTThreadPtr self); + +/* + * ----------------------------------------------------------------------- + * THREAD GLOBALS + */ + +xtPublic u_int xt_thr_maximum_threads; +xtPublic u_int xt_thr_current_thread_count; +xtPublic u_int xt_thr_current_max_threads; + +/* This structure is a double linked list of thread, with a wait + * condition on it. + */ +static XTLinkedListPtr thr_list; + +/* This structure maps thread ID's to thread pointers. */ +xtPublic XTThreadPtr *xt_thr_array; +static xt_mutex_type thr_array_lock; + +/* Global accumulated statistics: */ +static XTStatisticsRec thr_statistics; + +/* + * ----------------------------------------------------------------------- + * Error logging + */ + +static xt_mutex_type log_mutex; +static int log_level = 0; +static FILE *log_file = NULL; +static xtBool log_newline = TRUE; + +xtPublic xtBool xt_init_logging(void) +{ + int err; + + log_file = stdout; + log_level = XT_LOG_TRACE; + err = xt_p_mutex_init_with_autoname(&log_mutex, NULL); + if (err) { + xt_log_errno(XT_NS_CONTEXT, err); + log_file = NULL; + log_level = 0; + return FALSE; + } + if (!xt_init_trace()) { + xt_exit_logging(); + return FALSE; + } + return TRUE; +} + +xtPublic void xt_exit_logging(void) +{ + if (log_file) { + xt_free_mutex(&log_mutex); + log_file = NULL; + } + xt_exit_trace(); +} + +xtPublic void xt_get_now(char *buffer, size_t len) +{ + time_t ticks; + struct tm ltime; + + ticks = time(NULL); + if (ticks == (time_t) -1) { +#ifdef XT_WIN + printf(buffer, "** error %d getting time **", errno); +#else + snprintf(buffer, len, "** error %d getting time **", errno); +#endif + return; + } + localtime_r(&ticks, <ime); + strftime(buffer, len, "%y%m%d %H:%M:%S", <ime); +} + +static void thr_log_newline(XTThreadPtr self, c_char *func, c_char *file, u_int line, int level) +{ + c_char *level_str; + char time_str[200]; + char thr_name[XT_THR_NAME_SIZE+3]; + + xt_get_now(time_str, 200); + if (self && *self->t_name) { + xt_strcpy(XT_THR_NAME_SIZE+3, thr_name, " "); + xt_strcat(XT_THR_NAME_SIZE+3, thr_name, self->t_name); + } + else + thr_name[0] = 0; + switch (level) { + case XT_LOG_FATAL: level_str = " [Fatal]"; break; + case XT_LOG_ERROR: level_str = " [Error]"; break; + case XT_LOG_WARNING: level_str = " [Warning]"; break; + case XT_LOG_INFO: level_str = " [Note]"; break; + case XT_LOG_TRACE: level_str = " [Trace]"; break; + default: level_str = " "; break; + } + if (func && *func && *func != '-') { + char func_name[XT_MAX_FUNC_NAME_SIZE]; + + xt_strcpy_term(XT_MAX_FUNC_NAME_SIZE, func_name, func, '('); + if (file && *file) + fprintf(log_file, "%s%s%s %s(%s:%d) ", time_str, level_str, thr_name, func_name, xt_last_name_of_path(file), line); + else + fprintf(log_file, "%s%s%s %s() ", time_str, level_str, thr_name, func_name); + } + else { + if (file && *file) + fprintf(log_file, "%s%s%s [%s:%d] ", time_str, level_str, thr_name, xt_last_name_of_path(file), line); + else + fprintf(log_file, "%s%s%s ", time_str, level_str, thr_name); + } +} + +#ifdef XT_WIN +/* Windows uses printf()!! */ +#define DEFAULT_LOG_BUFFER_SIZE 2000 +#else +#ifdef DEBUG +#define DEFAULT_LOG_BUFFER_SIZE 10 +#else +#define DEFAULT_LOG_BUFFER_SIZE 2000 +#endif +#endif + +void xt_log_flush(XTThreadPtr self __attribute__((unused))) +{ + fflush(log_file); +} + +/* + * Log the given formated string information to the log file. + * Before each new line, this function writes the + * log header, which includes the time, log level, + * and source file and line number (optional). + */ +static void thr_log_va(XTThreadPtr self, c_char *func, c_char *file, u_int line, int level, c_char *fmt, va_list ap) +{ + char buffer[DEFAULT_LOG_BUFFER_SIZE]; + char *log_string = NULL; + + if (level > log_level) + return; + + xt_lock_mutex_ns(&log_mutex); + +#ifdef XT_WIN + vsprintf(buffer, fmt, ap); + log_string = buffer; +#else +#if !defined(va_copy) || defined(XT_SOLARIS) + int len; + + len = vsnprintf(buffer, DEFAULT_LOG_BUFFER_SIZE-1, fmt, ap); + if (len > DEFAULT_LOG_BUFFER_SIZE-1) + len = DEFAULT_LOG_BUFFER_SIZE-1; + buffer[len] = 0; + log_string = buffer; +#else + /* Use the buffer, unless it is too small */ + va_list ap2; + + va_copy(ap2, ap); + if (vsnprintf(buffer, DEFAULT_LOG_BUFFER_SIZE, fmt, ap) >= DEFAULT_LOG_BUFFER_SIZE) { + if (vasprintf(&log_string, fmt, ap2) == -1) + log_string = NULL; + } + else + log_string = buffer; +#endif +#endif + + if (log_string) { + char *str, *str_end, tmp_ch; + + str = log_string; + while (*str) { + if (log_newline) { + thr_log_newline(self, func, file, line, level); + log_newline = FALSE; + } + str_end = strchr(str, '\n'); + if (str_end) { + str_end++; + tmp_ch = *str_end; + *str_end = 0; + log_newline = TRUE; + } + else { + str_end = str + strlen(str); + tmp_ch = 0; + } + fprintf(log_file, "%s", str); + fflush(log_file); + *str_end = tmp_ch; + str = str_end; + } + + if (log_string != buffer) + free(log_string); + } + + xt_unlock_mutex_ns(&log_mutex); +} + +xtPublic void xt_logf(XTThreadPtr self, c_char *func, c_char *file, u_int line, int level, c_char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + thr_log_va(self, func, file, line, level, fmt, ap); + va_end(ap); +} + +xtPublic void xt_log(XTThreadPtr self, c_char *func, c_char *file, u_int line, int level, c_char *string) +{ + xt_logf(self, func, file, line, level, "%s", string); +} + +static int thr_log_error_va(XTThreadPtr self, c_char *func, c_char *file, u_int line, int level, int xt_err, int sys_err, c_char *fmt, va_list ap) +{ + int default_level; + char xt_err_string[50]; + + *xt_err_string = 0; + switch (xt_err) { + case XT_ASSERTION_FAILURE: + strcpy(xt_err_string, "Assertion"); + default_level = XT_LOG_FATAL; + break; + case XT_SYSTEM_ERROR: + strcpy(xt_err_string, "errno"); + default_level = XT_LOG_ERROR; + break; + case XT_SIGNAL_CAUGHT: + strcpy(xt_err_string, "Signal"); + default_level = XT_LOG_ERROR; + break; + default: + sprintf(xt_err_string, "%d", xt_err); + default_level = XT_LOG_ERROR; + break; + } + if (level == XT_LOG_DEFAULT) + level = default_level; + + if (*xt_err_string) { + if (sys_err) + xt_logf(self, func, file, line, level, "%s (%d): ", xt_err_string, sys_err); + else + xt_logf(self, func, file, line, level, "%s: ", xt_err_string); + } + thr_log_va(self, func, file, line, level, fmt, ap); + xt_logf(self, func, file, line, level, "\n"); + return level; +} + +/* The function returns the actual log level used. */ +xtPublic int xt_log_errorf(XTThreadPtr self, c_char *func, c_char *file, u_int line, int level, int xt_err, int sys_err, c_char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + level = thr_log_error_va(self, func, file, line, level, xt_err, sys_err, fmt, ap); + va_end(ap); + return level; +} + +/* The function returns the actual log level used. */ +xtPublic int xt_log_error(XTThreadPtr self, c_char *func, c_char *file, u_int line, int level, int xt_err, int sys_err, c_char *string) +{ + return xt_log_errorf(self, func, file, line, level, xt_err, sys_err, "%s", string); +} + +xtPublic void xt_log_exception(XTThreadPtr self, XTExceptionPtr e, int level) +{ + level = xt_log_error( + self, + e->e_func_name, + e->e_source_file, + e->e_source_line, + level, + e->e_xt_err, + e->e_sys_err, + e->e_err_msg); + /* Dump the catch trace: */ + if (*e->e_catch_trace) + xt_logf(self, NULL, NULL, 0, level, "%s", e->e_catch_trace); +} + +xtPublic void xt_log_and_clear_exception(XTThreadPtr self) +{ + xt_log_exception(self, &self->t_exception, XT_LOG_DEFAULT); + xt_clear_exception(self); +} + +xtPublic void xt_log_and_clear_exception_ns(void) +{ + xt_log_and_clear_exception(xt_get_self()); +} + +xtPublic void xt_log_and_clear_warning(XTThreadPtr self) +{ + xt_log_exception(self, &self->t_exception, XT_LOG_WARNING); + xt_clear_exception(self); +} + +xtPublic void xt_log_and_clear_warning_ns(void) +{ + xt_log_and_clear_warning(xt_get_self()); +} + +/* + * ----------------------------------------------------------------------- + * Exceptions + */ + +static void thr_add_catch_trace(XTExceptionPtr e, c_char *func, c_char *file, u_int line) +{ + if (func && *func && *func != '-') { + xt_strcat_term(XT_CATCH_TRACE_SIZE, e->e_catch_trace, func, '('); + xt_strcat(XT_CATCH_TRACE_SIZE, e->e_catch_trace, "("); + } + if (file && *file) { + xt_strcat(XT_CATCH_TRACE_SIZE, e->e_catch_trace, xt_last_name_of_path(file)); + if (line) { + char buffer[40]; + + sprintf(buffer, "%u", line); + xt_strcat(XT_CATCH_TRACE_SIZE, e->e_catch_trace, ":"); + xt_strcat(XT_CATCH_TRACE_SIZE, e->e_catch_trace, buffer); + } + } + if (func && *func && *func != '-') + xt_strcat(XT_CATCH_TRACE_SIZE, e->e_catch_trace, ")"); + xt_strcat(XT_CATCH_TRACE_SIZE, e->e_catch_trace, "\n"); +} + +static void thr_save_error_va(XTExceptionPtr e, XTThreadPtr self, xtBool throw_it, c_char *func, c_char *file, u_int line, int xt_err, int sys_err, c_char *fmt, va_list ap) +{ + int i; + + if (!e) + return; + + e->e_xt_err = xt_err; + e->e_sys_err = sys_err; + vsnprintf(e->e_err_msg, XT_ERR_MSG_SIZE, fmt, ap); + + /* Make the first character of the message upper case: */ + if (isalpha(e->e_err_msg[0]) && islower(e->e_err_msg[0])) + e->e_err_msg[0] = (char) toupper(e->e_err_msg[0]); + + if (func && *func && *func != '-') + xt_strcpy_term(XT_MAX_FUNC_NAME_SIZE, e->e_func_name, func, '('); + else + *e->e_func_name = 0; + if (file && *file) { + xt_strcpy(XT_SOURCE_FILE_NAME_SIZE, e->e_source_file, xt_last_name_of_path(file)); + e->e_source_line = line; + } + else { + *e->e_source_file = 0; + e->e_source_line = 0; + } + *e->e_catch_trace = 0; + + if (!self) + return; + + /* Create a stack trace for this exception: */ + thr_add_catch_trace(e, func, file, line); + for (i=self->t_call_top-1; i>=0; i--) + thr_add_catch_trace(e, self->t_call_stack[i].cs_func, self->t_call_stack[i].cs_file, self->t_call_stack[i].cs_line); + + if (throw_it) + xt_throw(self); +} + +/* + * ----------------------------------------------------------------------- + * THROWING EXCEPTIONS + */ + +/* If we have to allocate resources and the hold them temporarily during which + * time an exception could occur, then these functions provide a holding + * place for the data, which will be freed in the case of an exception. + * + * Note: the free functions could themselves allocated resources. + * to make sure all things work out we only remove the resource from + * then stack when it is freed. + */ +static void thr_free_resources(XTThreadPtr self, XTResourcePtr top) +{ + XTResourcePtr rp; + XTThreadFreeFunc free_func; + + if (!top) + return; + while (self->t_res_top > top) { + /* Pop the top resource: */ + rp = (XTResourcePtr) (((char *) self->t_res_top) - self->t_res_top->r_prev_size); + + /* Free the resource: */ + if (rp->r_free_func) { + free_func = rp->r_free_func; + rp->r_free_func = NULL; + free_func(self, rp->r_data); + } + + self->t_res_top = rp; + } +} + +xtPublic void xt_bug(XTThreadPtr self __attribute__((unused))) +{ + static int *bug_ptr = NULL; + + bug_ptr = NULL; +} + +/* + * This function is called when an exception is caught. + * It restores the function call top and frees + * any resource allocated by lower levels. + */ +xtPublic void xt_caught(XTThreadPtr self) +{ + /* Restore the call top: */ + self->t_call_top = self->t_jmp_env[self->t_jmp_depth].jb_call_top; + + /* Free the temporary data that would otherwize be lost + * This should do nothing, because we actually free things on throw + * (se below). + */ + thr_free_resources(self, self->t_jmp_env[self->t_jmp_depth].jb_res_top); +} + +/* Throw an already registered error: */ +xtPublic void xt_throw(XTThreadPtr self) +{ + if (self) { + ASSERT_NS(self->t_exception.e_xt_err); + if (self->t_jmp_depth > 0 && self->t_jmp_depth <= XT_MAX_JMP) { + /* As recommended by Barry: rree the resources before the stack is invalid! */ + thr_free_resources(self, self->t_jmp_env[self->t_jmp_depth-1].jb_res_top); + + /* Then do the longjmp: */ + longjmp(self->t_jmp_env[self->t_jmp_depth-1].jb_buffer, 1); + } + } + + /* + * We cannot throw an error, because it will not be caught. + * This means there is no try ... catch block above. + * In this case, we just return. + * The calling functions must handle errors... + xt_caught(self); + xt_log(XT_CONTEXT, XT_LOG_FATAL, "Uncaught exception\n"); + xt_exit_thread(self, NULL); + */ +} + +xtPublic void xt_throwf(XTThreadPtr self, c_char *func, c_char *file, u_int line, int xt_err, int sys_err, c_char *fmt, ...) +{ + va_list ap; + XTThreadPtr thread = self ? self : xt_get_self(); + + va_start(ap, fmt); + thr_save_error_va(thread ? &thread->t_exception : NULL, thread, self ? TRUE : FALSE, func, file, line, xt_err, sys_err, fmt, ap); + va_end(ap); +} + +xtPublic void xt_throw_error(XTThreadPtr self, c_char *func, c_char *file, u_int line, int xt_err, int sys_err, c_char *msg) +{ + xt_throwf(self, func, file, line, xt_err, sys_err, "%s", msg); +} + +#define XT_SYS_ERR_SIZE 300 + +static c_char *thr_get_sys_error(int err, char *err_msg __attribute__((unused))) +{ +#ifdef XT_WIN + char *ptr; + + if (!FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, NULL, + err, 0, err_msg, XT_SYS_ERR_SIZE, NULL)) { + return strerror(err); + } + + ptr = &err_msg[strlen(err_msg)]; + while (ptr-1 > err_msg) { + if (*(ptr-1) != '\n' && *(ptr-1) != '\r' && *(ptr-1) != '.') + break; + ptr--; + } + *ptr = 0; +return err_msg; +#else + return strerror(err); +#endif +} + +static c_char *thr_get_err_string(int xt_err) +{ + c_char *str; + + switch (xt_err) { + case XT_ERR_STACK_OVERFLOW: str = "Stack overflow"; break; + case XT_ERR_JUMP_OVERFLOW: str = "Jump overflow"; break; + case XT_ERR_TABLE_EXISTS: str = "Table `%s` already exists"; break; + case XT_ERR_NAME_TOO_LONG: str = "Name '%s' is too long"; break; + case XT_ERR_TABLE_NOT_FOUND: str = "Table `%s` not found"; break; + case XT_ERR_SESSION_NOT_FOUND: str = "Session %s not found"; break; + case XT_ERR_BAD_ADDRESS: str = "Incorrect address '%s'"; break; + case XT_ERR_UNKNOWN_SERVICE: str = "Unknown service '%s'"; break; + case XT_ERR_UNKNOWN_HOST: str = "Host '%s' not found"; break; + case XT_ERR_TOKEN_EXPECTED: str = "%s expected in place of %s"; break; + case XT_ERR_PROPERTY_REQUIRED: str = "Property '%s' required"; break; + case XT_ERR_DEADLOCK: str = "Deadlock, transaction aborted"; break; + case XT_ERR_CANNOT_CHANGE_DB: str = "Cannot change database while transaction is in progress"; break; + case XT_ERR_ILLEGAL_CHAR: str = "Illegal character: '%s'"; break; + case XT_ERR_UNTERMINATED_STRING:str = "Unterminated string: %s"; break; + case XT_ERR_SYNTAX: str = "Syntax error near %s"; break; + case XT_ERR_ILLEGAL_INSTRUCTION:str = "Illegal instruction"; break; + case XT_ERR_OUT_OF_BOUNDS: str = "Memory reference out of bounds"; break; + case XT_ERR_STACK_UNDERFLOW: str = "Stack underflow"; break; + case XT_ERR_TYPE_MISMATCH: str = "Type mismatch"; break; + case XT_ERR_ILLEGAL_TYPE: str = "Illegal type for operator"; break; + case XT_ERR_ID_TOO_LONG: str = "Identifier too long: %s"; break; + case XT_ERR_TYPE_OVERFLOW: str = "Type overflow: %s"; break; + case XT_ERR_TABLE_IN_USE: str = "Table `%s` in use"; break; + case XT_ERR_NO_DATABASE_IN_USE: str = "No database in use"; break; + case XT_ERR_CANNOT_RESOLVE_TYPE:str = "Cannot resolve type with ID: %s"; break; + case XT_ERR_BAD_INDEX_DESC: str = "Unsupported index description: %s"; break; + case XT_ERR_WRONG_NO_OF_VALUES: str = "Incorrect number of values"; break; + case XT_ERR_CANNOT_OUTPUT_VALUE:str = "Cannot output given type"; break; + case XT_ERR_COLUMN_NOT_FOUND: str = "Column `%s.%s` not found"; break; + case XT_ERR_NOT_IMPLEMENTED: str = "Not implemented"; break; + case XT_ERR_UNEXPECTED_EOS: str = "Connection unexpectedly lost"; break; + case XT_ERR_BAD_TOKEN: str = "Incorrect binary token"; break; + case XT_ERR_RES_STACK_OVERFLOW: str = "Internal error: resource stack overflow"; break; + case XT_ERR_BAD_INDEX_TYPE: str = "Unsupported index type: %s"; break; + case XT_ERR_INDEX_EXISTS: str = "Index '%s' already exists"; break; + case XT_ERR_INDEX_STRUC_EXISTS: str = "Index '%s' has an identical structure"; break; + case XT_ERR_INDEX_NOT_FOUND: str = "Index '%s' not found"; break; + case XT_ERR_INDEX_CORRUPT: str = "Cannot read index '%s'"; break; + case XT_ERR_TYPE_NOT_SUPPORTED: str = "Data type %s not supported"; break; + case XT_ERR_BAD_TABLE_VERSION: str = "Table `%s` version not supported, upgrade required"; break; + case XT_ERR_BAD_RECORD_FORMAT: str = "Record format unknown, either corrupted or upgrade required"; break; + case XT_ERR_BAD_EXT_RECORD: str = "Extended record part does not match reference"; break; + case XT_ERR_RECORD_CHANGED: str = "Record already updated, transaction aborted"; break; + case XT_ERR_XLOG_WAS_CORRUPTED: str = "Corrupted transaction log has been truncated"; break; + case XT_ERR_DUPLICATE_KEY: str = "Duplicate unique key"; break; + case XT_ERR_NO_DICTIONARY: str = "Table `%s` has not yet been opened by MySQL"; break; + case XT_ERR_TOO_MANY_TABLES: str = "Limit of %s tables per database exceeded"; break; + case XT_ERR_KEY_TOO_LARGE: str = "Index '%s' exceeds the key size limit of %s"; break; + case XT_ERR_MULTIPLE_DATABASES: str = "Multiple database in a single transaction is not permitted"; break; + case XT_ERR_NO_TRANSACTION: str = "Internal error: no transaction running"; break; + case XT_ERR_A_EXPECTED_NOT_B: str = "%s expected in place of %s"; break; + case XT_ERR_NO_MATCHING_INDEX: str = "Matching index required for '%s'"; break; + case XT_ERR_TABLE_LOCKED: str = "Table `%s` locked"; break; + case XT_ERR_NO_REFERENCED_ROW: str = "Constraint: `%s`"; break; // "Foreign key '%s', referenced row does not exist" + case XT_ERR_ROW_IS_REFERENCED: str = "Constraint: `%s`"; break; // "Foreign key '%s', has a referencing row" + case XT_ERR_BAD_DICTIONARY: str = "Internal dictionary does not match MySQL dictionary"; break; + case XT_ERR_LOADING_MYSQL_DIC: str = "Error %s loading MySQL .frm file"; break; + case XT_ERR_COLUMN_IS_NOT_NULL: str = "Column `%s` is NOT NULL"; break; + case XT_ERR_INCORRECT_NO_OF_COLS: str = "Incorrect number of columns near %s"; break; + case XT_ERR_FK_ON_TEMP_TABLE: str = "Cannot create foreign key on temporary table"; break; + case XT_ERR_REF_TABLE_NOT_FOUND: str = "Referenced table `%s` not found"; break; + case XT_ERR_REF_TYPE_WRONG: str = "Incorrect data type on referenced column `%s`"; break; + case XT_ERR_DUPLICATE_FKEY: str = "Duplicate unique foreign key, contraint: %s"; break; + case XT_ERR_INDEX_FILE_TO_LARGE: str = "Index file has grown too large: %s"; break; + case XT_ERR_UPGRADE_TABLE: str = "Table `%s` must be upgraded from PBXT version %s"; break; + case XT_ERR_INDEX_NEW_VERSION: str = "Table `%s` index created by a newer version, upgrade required"; break; + case XT_ERR_LOCK_TIMEOUT: str = "Lock timeout on table `%s`"; break; + case XT_ERR_CONVERSION: str = "Error converting value for column `%s.%s`"; break; + case XT_ERR_NO_ROWS: str = "No matching row found in table `%s`"; break; + case XT_ERR_DATA_LOG_NOT_FOUND: str = "Data log not found: '%s'"; break; + case XT_ERR_LOG_MAX_EXCEEDED: str = "Maximum log count, %s, exceeded"; break; + case XT_ERR_MAX_ROW_COUNT: str = "Maximum row count reached"; break; + case XT_ERR_FILE_TOO_LONG: str = "File cannot be mapped, too large: '%s'"; break; + case XT_ERR_BAD_IND_BLOCK_SIZE: str = "Table `%s`, incorrect index block size: %s"; break; + case XT_ERR_INDEX_CORRUPTED: str = "Table `%s` index is corrupted, REPAIR TABLE required"; break; + case XT_ERR_NO_INDEX_CACHE: str = "Not enough index cache memory to handle concurrent updates"; break; + case XT_ERR_INDEX_LOG_CORRUPT: str = "Index log corrupt: '%s'"; break; + case XT_ERR_TOO_MANY_THREADS: str = "Too many threads: %s, increase max_connections"; break; + case XT_ERR_TOO_MANY_WAITERS: str = "Too many waiting threads: %s"; break; + case XT_ERR_INDEX_OLD_VERSION: str = "Table `%s` index created by an older version, REPAIR TABLE required"; break; + case XT_ERR_PBXT_TABLE_EXISTS: str = "System table cannot be dropped because PBXT table still exists"; break; + case XT_ERR_SERVER_RUNNING: str = "A server is possibly already running"; break; + case XT_ERR_INDEX_MISSING: str = "Index file of table '%s' is missing"; break; + case XT_ERR_RECORD_DELETED: str = "Record was deleted"; break; + case XT_ERR_NEW_TYPE_OF_XLOG: str = "Transaction log %s, is using a newer format, upgrade required"; break; + case XT_ERR_NO_BEFORE_IMAGE: str = "Internal error: no before image"; break; + case XT_ERR_FK_REF_TEMP_TABLE: str = "Foreign key may not reference temporary table"; break; + default: str = "Unknown XT error"; break; + } + return str; +} + +xtPublic void xt_throw_i2xterr(XTThreadPtr self, c_char *func, c_char *file, u_int line, int xt_err, c_char *item, c_char *item2) +{ + xt_throwf(self, func, file, line, xt_err, 0, thr_get_err_string(xt_err), item, item2); +} + +xtPublic void xt_throw_ixterr(XTThreadPtr self, c_char *func, c_char *file, u_int line, int xt_err, c_char *item) +{ + xt_throw_i2xterr(self, func, file, line, xt_err, item, NULL); +} + +xtPublic void xt_throw_tabcolerr(XTThreadPtr self, c_char *func, c_char *file, u_int line, int xt_err, XTPathStrPtr tab_item, c_char *item2) +{ + char buffer[XT_IDENTIFIER_NAME_SIZE + XT_IDENTIFIER_NAME_SIZE + XT_IDENTIFIER_NAME_SIZE + 3]; + + xt_2nd_last_name_of_path(sizeof(buffer), buffer, tab_item->ps_path); + xt_strcat(sizeof(buffer), buffer, "."); + xt_strcat(sizeof(buffer), buffer, xt_last_name_of_path(tab_item->ps_path)); + + xt_throw_i2xterr(self, func, file, line, xt_err, buffer, item2); +} + +xtPublic void xt_throw_taberr(XTThreadPtr self, c_char *func, c_char *file, u_int line, int xt_err, XTPathStrPtr tab_item) +{ + char buffer[XT_IDENTIFIER_NAME_SIZE + XT_IDENTIFIER_NAME_SIZE + XT_IDENTIFIER_NAME_SIZE + 3]; + + xt_2nd_last_name_of_path(sizeof(buffer), buffer, tab_item->ps_path); + xt_strcat(sizeof(buffer), buffer, "."); + xt_strcat(sizeof(buffer), buffer, xt_last_name_of_path(tab_item->ps_path)); + + xt_throw_ixterr(self, func, file, line, xt_err, buffer); +} + +xtPublic void xt_throw_ulxterr(XTThreadPtr self, c_char *func, c_char *file, u_int line, int xt_err, u_long value) +{ + char buffer[100]; + + sprintf(buffer, "%lu", value); + xt_throw_ixterr(self, func, file, line, xt_err, buffer); +} + +xtPublic void xt_throw_sulxterr(XTThreadPtr self, c_char *func, c_char *file, u_int line, int xt_err, c_char *item, u_long value) +{ + char buffer[100]; + + sprintf(buffer, "%lu", value); + xt_throw_i2xterr(self, func, file, line, xt_err, item, buffer); +} + +xtPublic void xt_throw_xterr(XTThreadPtr self, c_char *func, c_char *file, u_int line, int xt_err) +{ + xt_throw_ixterr(self, func, file, line, xt_err, NULL); +} + +xtPublic void xt_throw_errno(XTThreadPtr self, c_char *func, c_char *file, u_int line, int err) +{ + char err_msg[XT_SYS_ERR_SIZE]; + + xt_throw_error(self, func, file, line, XT_SYSTEM_ERROR, err, thr_get_sys_error(err, err_msg)); +} + +xtPublic void xt_throw_ferrno(XTThreadPtr self, c_char *func, c_char *file, u_int line, int err, c_char *path) +{ + char err_msg[XT_SYS_ERR_SIZE]; + + xt_throwf(self, func, file, line, XT_SYSTEM_ERROR, err, "%s: '%s'", thr_get_sys_error(err, err_msg), path); +} + +xtPublic void xt_throw_assertion(XTThreadPtr self, c_char *func, c_char *file, u_int line, c_char *str) +{ + xt_throw_error(self, func, file, line, XT_ASSERTION_FAILURE, 0, str); +} + +static void xt_log_assertion(XTThreadPtr self, c_char *func, c_char *file, u_int line, c_char *str) +{ + xt_log_error(self, func, file, line, XT_LOG_DEFAULT, XT_ASSERTION_FAILURE, 0, str); +} + +xtPublic void xt_throw_signal(XTThreadPtr self, c_char *func, c_char *file, u_int line, int sig) +{ +#ifdef XT_WIN + char buffer[100]; + + sprintf(buffer, "Signal #%d", sig); + xt_throw_error(self, func, file, line, XT_SIGNAL_CAUGHT, sig, buffer); +#else + xt_throw_error(self, func, file, line, XT_SIGNAL_CAUGHT, sig, strsignal(sig)); +#endif +} + +/* + * ----------------------------------------------------------------------- + * REGISTERING EXCEPTIONS + */ + +xtPublic void xt_registerf(c_char *func, c_char *file, u_int line, int xt_err, int sys_err, c_char *fmt, ...) +{ + va_list ap; + XTThreadPtr thread = xt_get_self(); + + va_start(ap, fmt); + thr_save_error_va(thread ? &thread->t_exception : NULL, thread, FALSE, func, file, line, xt_err, sys_err, fmt, ap); + va_end(ap); +} + +xtPublic void xt_register_i2xterr(c_char *func, c_char *file, u_int line, int xt_err, c_char *item, c_char *item2) +{ + xt_registerf(func, file, line, xt_err, 0, thr_get_err_string(xt_err), item, item2); +} + +xtPublic void xt_register_ixterr(c_char *func, c_char *file, u_int line, int xt_err, c_char *item) +{ + xt_register_i2xterr(func, file, line, xt_err, item, NULL); +} + +xtPublic void xt_register_tabcolerr(c_char *func, c_char *file, u_int line, int xt_err, XTPathStrPtr tab_item, c_char *item2) +{ + char buffer[XT_IDENTIFIER_NAME_SIZE + XT_IDENTIFIER_NAME_SIZE + XT_IDENTIFIER_NAME_SIZE + 3]; + + xt_2nd_last_name_of_path(sizeof(buffer), buffer, tab_item->ps_path); + xt_strcat(sizeof(buffer), buffer, "."); + xt_strcpy(sizeof(buffer), buffer, xt_last_name_of_path(tab_item->ps_path)); + xt_strcat(sizeof(buffer), buffer, "."); + xt_strcat(sizeof(buffer), buffer, item2); + + xt_register_ixterr(func, file, line, xt_err, buffer); +} + +xtPublic void xt_register_taberr(c_char *func, c_char *file, u_int line, int xt_err, XTPathStrPtr tab_item) +{ + char buffer[XT_IDENTIFIER_NAME_SIZE + XT_IDENTIFIER_NAME_SIZE + XT_IDENTIFIER_NAME_SIZE + 3]; + + xt_2nd_last_name_of_path(sizeof(buffer), buffer, tab_item->ps_path); + xt_strcat(sizeof(buffer), buffer, "."); + xt_strcpy(sizeof(buffer), buffer, xt_last_name_of_path(tab_item->ps_path)); + + xt_register_ixterr(func, file, line, xt_err, buffer); +} + +xtPublic void xt_register_ulxterr(c_char *func, c_char *file, u_int line, int xt_err, u_long value) +{ + char buffer[100]; + + sprintf(buffer, "%lu", value); + xt_register_ixterr(func, file, line, xt_err, buffer); +} + +xtPublic xtBool xt_register_ferrno(c_char *func, c_char *file, u_int line, int err, c_char *path) +{ + char err_msg[XT_SYS_ERR_SIZE]; + + xt_registerf(func, file, line, XT_SYSTEM_ERROR, err, "%s: '%s'", thr_get_sys_error(err, err_msg), path); + return FAILED; +} + +xtPublic void xt_register_error(c_char *func, c_char *file, u_int line, int xt_err, int sys_err, c_char *msg) +{ + xt_registerf(func, file, line, xt_err, sys_err, "%s", msg); +} + +xtPublic xtBool xt_register_errno(c_char *func, c_char *file, u_int line, int err) +{ + char err_msg[XT_SYS_ERR_SIZE]; + + xt_register_error(func, file, line, XT_SYSTEM_ERROR, err, thr_get_sys_error(err, err_msg)); + return FAILED; +} + +xtPublic void xt_register_xterr(c_char *func, c_char *file, u_int line, int xt_err) +{ + xt_register_error(func, file, line, xt_err, 0, thr_get_err_string(xt_err)); +} + +/* + * ----------------------------------------------------------------------- + * CREATING EXCEPTIONS + */ + +xtPublic void xt_exceptionf(XTExceptionPtr e, XTThreadPtr self, c_char *func, c_char *file, u_int line, int xt_err, int sys_err, c_char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + thr_save_error_va(e, self, FALSE, func, file, line, xt_err, sys_err, fmt, ap); + va_end(ap); +} + +xtPublic void xt_exception_error(XTExceptionPtr e, XTThreadPtr self, c_char *func, c_char *file, u_int line, int xt_err, int sys_err, c_char *msg) +{ + xt_exceptionf(e, self, func, file, line, xt_err, sys_err, "%s", msg); +} + +xtPublic xtBool xt_exception_errno(XTExceptionPtr e, XTThreadPtr self, c_char *func, c_char *file, u_int line, int err) +{ + char err_msg[XT_SYS_ERR_SIZE]; + + xt_exception_error(e, self, func, file, line, XT_SYSTEM_ERROR, err, thr_get_sys_error(err, err_msg)); + return FAILED; +} + +/* + * ----------------------------------------------------------------------- + * LOG ERRORS + */ + +xtPublic void xt_log_errno(XTThreadPtr self, c_char *func, c_char *file, u_int line, int err) +{ + XTExceptionRec e; + + xt_exception_errno(&e, self, func, file, line, err); + xt_log_exception(self, &e, XT_LOG_DEFAULT); +} + +/* + * ----------------------------------------------------------------------- + * Assertions and failures (one breakpoints for all failures) + */ + +xtPublic xtBool xt_assert(XTThreadPtr self __attribute__((unused)), c_char *expr, c_char *func, c_char *file, u_int line) +{ +#ifdef DEBUG + //xt_set_fflush(TRUE); + //xt_dump_trace(); + printf("%s(%s:%d) %s\n", func, file, (int) line, expr); +#ifdef XT_WIN + FatalAppExit(0, "Assertion Failed!"); +#endif +#else + xt_throw_assertion(self, func, file, line, expr); +#endif + return FALSE; +} + +xtPublic xtBool xt_assume(XTThreadPtr self, c_char *expr, c_char *func, c_char *file, u_int line) +{ + xt_log_assertion(self, func, file, line, expr); + return FALSE; +} + +/* + * ----------------------------------------------------------------------- + * Create and destroy threads + */ + +typedef struct ThreadData { + xtBool td_started; + XTThreadPtr td_thr; + void *(*td_start_routine)(XTThreadPtr self); +} ThreadDataRec, *ThreadDataPtr; + +#ifdef XT_WIN +pthread_key(void *, thr_key); +#else +static pthread_key_t thr_key; +#endif + +#ifdef HANDLE_SIGNALS +static void thr_ignore_signal(int sig) +{ +#pragma unused(sig) +} + +static void thr_throw_signal(int sig) +{ + XTThreadPtr self; + + self = xt_get_self(); + + if (self->t_main) { + /* The main thread will pass on a signal to all threads: */ + xt_signal_all_threads(self, sig); + if (sig != SIGTERM) { + if (self->t_disable_interrupts) { + self->t_delayed_signal = sig; + self->t_disable_interrupts = FALSE; /* Prevent infinite loop */ + } + else { + self->t_delayed_signal = 0; + xt_throw_signal(self, "thr_throw_signal", NULL, 0, sig); + } + } + } + else { + if (self->t_disable_interrupts) { + self->t_delayed_signal = sig; + self->t_disable_interrupts = FALSE; /* Prevent infinite loop */ + } + else { + self->t_delayed_signal = 0; + xt_throw_signal(self, "thr_throw_signal", NULL, 0, sig); + } + } +} + +static xtBool thr_setup_signals(void) +{ + struct sigaction action; + + sigemptyset(&action.sa_mask); + action.sa_flags = 0; + action.sa_handler = thr_ignore_signal; + + if (sigaction(SIGPIPE, &action, NULL) == -1) + goto error_occurred; + if (sigaction(SIGHUP, &action, NULL) == -1) + goto error_occurred; + + action.sa_handler = thr_throw_signal; + + if (sigaction(SIGQUIT, &action, NULL) == -1) + goto error_occurred; + if (sigaction(SIGTERM, &action, NULL) == -1) + goto error_occurred; +#ifndef DEBUG + if (sigaction(SIGILL, &action, NULL) == -1) + goto error_occurred; + if (sigaction(SIGBUS, &action, NULL) == -1) + goto error_occurred; + if (sigaction(SIGSEGV, &action, NULL) == -1) + goto error_occurred; +#endif + return TRUE; + + error_occurred: + xt_log_errno(XT_NS_CONTEXT, errno); + return FALSE; +} +#endif + +static void *thr_main(void *data) +{ + ThreadDataPtr td = (ThreadDataPtr) data; + XTThreadPtr self = td->td_thr; + void *(*start_routine)(XTThreadPtr); + void *return_data; + + enter_(); + self->t_pthread = pthread_self(); + start_routine = td->td_start_routine; + return_data = NULL; + +#ifdef HANDLE_SIGNALS + if (!thr_setup_signals()) + return NULL; +#endif + + try_(a) { + if (!xt_set_key(thr_key, self, &self->t_exception)) + throw_(); + td->td_started = TRUE; + return_data = (*start_routine)(self); + } + catch_(a) { + xt_log_and_clear_exception(self); + } + cont_(a); + + outer_(); + xt_free_thread(self); + return return_data; +} + +static void thr_free_data(XTThreadPtr self) +{ + if (self->t_free_data) { + (*self->t_free_data)(self, self->t_data); + self->t_data = NULL; + } +} + +xtPublic void xt_set_thread_data(XTThreadPtr self, void *data, XTThreadFreeFunc free_func) +{ + thr_free_data(self); + self->t_free_data = free_func; + self->t_data = data; +} + +static void thr_exit(XTThreadPtr self) +{ + /* Free the thread temporary data. */ + thr_free_resources(self, (XTResourcePtr) self->x.t_res_stack); + xt_db_exit_thread(self); + thr_free_data(self); /* Free custom user data. */ + + if (self->t_id > 0) { + ASSERT(self->t_id < xt_thr_current_max_threads); + xt_lock_mutex(self, &thr_array_lock); + pushr_(xt_unlock_mutex, &thr_array_lock); + thr_accumulate_statistics(self); + xt_thr_array[self->t_id] = NULL; + xt_thr_current_thread_count--; + if (self->t_id+1 == xt_thr_current_max_threads) { + /* We can reduce the current maximum, + * this makes operations that scan the array faster! + */ + u_int i; + + i = self->t_id; + for(;;) { + if (xt_thr_array[i]) + break; + if (!i) + break; + i--; + } + xt_thr_current_max_threads = i+1; + } + freer_(); // xt_unlock_mutex(&thr_array_lock) + } + + xt_free_cond(&self->t_cond); + xt_free_mutex(&self->t_lock); + + self->st_thread_list_count = 0; + self->st_thread_list_size = 0; + if (self->st_thread_list) { + xt_free_ns(self->st_thread_list); + self->st_thread_list = NULL; + } +} + +static void thr_init(XTThreadPtr self, XTThreadPtr new_thread) +{ + new_thread->t_res_top = (XTResourcePtr) new_thread->x.t_res_stack; + + new_thread->st_thread_list_count = 0; + new_thread->st_thread_list_size = 0; + new_thread->st_thread_list = NULL; + try_(a) { + xt_init_cond(self, &new_thread->t_cond); + xt_init_mutex_with_autoname(self, &new_thread->t_lock); + + xt_lock_mutex(self, &thr_array_lock); + pushr_(xt_unlock_mutex, &thr_array_lock); + + ASSERT(xt_thr_current_thread_count <= xt_thr_current_max_threads); + ASSERT(xt_thr_current_max_threads <= xt_thr_maximum_threads); + if (xt_thr_current_thread_count == xt_thr_maximum_threads) + xt_throw_ulxterr(XT_CONTEXT, XT_ERR_TOO_MANY_THREADS, (u_long) xt_thr_maximum_threads+1); + if (xt_thr_current_thread_count == xt_thr_current_max_threads) { + new_thread->t_id = xt_thr_current_thread_count; + xt_thr_array[new_thread->t_id] = new_thread; + xt_thr_current_max_threads++; + } + else { + /* There must be a free slot: */ + for (u_int i=0; i<xt_thr_current_max_threads; i++) { + if (!xt_thr_array[i]) { + new_thread->t_id = i; + xt_thr_array[i] = new_thread; + break; + } + } + } + xt_thr_current_thread_count++; + freer_(); // xt_unlock_mutex(&thr_array_lock) + + xt_db_init_thread(self, new_thread); + } + catch_(a) { + thr_exit(new_thread); + throw_(); + } + cont_(a); + +} + +/* + * The caller of this function automatically becomes the main thread. + */ +xtPublic XTThreadPtr xt_init_threading(u_int max_threads) +{ + volatile XTThreadPtr self = NULL; + XTExceptionRec e; + int err; + + /* Align the number of threads: */ + xt_thr_maximum_threads = xt_align_size(max_threads, XT_XS_LOCK_ALIGN); + +#ifdef HANDLE_SIGNALS + if (!thr_setup_signals()) + return NULL; +#endif + + xt_p_init_threading(); + + err = pthread_key_create(&thr_key, NULL); + if (err) { + xt_log_errno(XT_NS_CONTEXT, err); + return NULL; + } + + if ((err = xt_p_mutex_init_with_autoname(&thr_array_lock, NULL))) { + xt_log_errno(XT_NS_CONTEXT, err); + goto failed; + } + + if (!(xt_thr_array = (XTThreadPtr *) malloc(xt_thr_maximum_threads * sizeof(XTThreadPtr)))) { + xt_log_errno(XT_NS_CONTEXT, XT_ENOMEM); + goto failed; + } + + xt_thr_array[0] = (XTThreadPtr) 1; // Dummy, not used + xt_thr_current_thread_count = 1; + xt_thr_current_max_threads = 1; + + /* Create the main thread: */ + self = xt_create_thread("MainThread", TRUE, FALSE, &e); + if (!self) { + xt_log_exception(NULL, &e, XT_LOG_DEFAULT); + goto failed; + } + + try_(a) { + XTThreadPtr thread = self; + thr_list = xt_new_linkedlist(thread, NULL, NULL, TRUE); + } + catch_(a) { + XTThreadPtr thread = self; + xt_log_and_clear_exception(thread); + xt_exit_threading(thread); + } + cont_(a); + + return self; + + failed: + xt_exit_threading(NULL); + return NULL; +} + +xtPublic void xt_exit_threading(XTThreadPtr self) +{ + if (thr_list) { + xt_free_linkedlist(self, thr_list); + thr_list = NULL; + } + + /* This should be the main thread! */ + if (self) { + ASSERT(self->t_main); + xt_free_thread(self); + } + + if (xt_thr_array) { + free(xt_thr_array); + xt_thr_array = NULL; + xt_free_mutex(&thr_array_lock); + } + + xt_thr_current_thread_count = 0; + xt_thr_current_max_threads = 0; + + /* I no longer delete 'thr_key' because + * functions that call xt_get_self() after this + * point will get junk back if we delete + * thr_key. In particular the XT_THREAD_LOCK_INFO + * code fails + if (thr_key) { + pthread_key_delete(thr_key); + thr_key = (pthread_key_t) 0; + } + */ +} + +xtPublic void xt_wait_for_all_threads(XTThreadPtr self) +{ + if (thr_list) + xt_ll_wait_till_empty(self, thr_list); +} + +/* + * Call this function in a busy wait loop! + * Use if for wait loops that are not + * time critical. + */ +xtPublic void xt_busy_wait(void) +{ +#ifdef XT_WIN + Sleep(1); +#else + usleep(10); +#endif +} + +xtPublic void xt_critical_wait(void) +{ + /* NOTE: On Mac xt_busy_wait() works better than xt_yield() + */ +#if defined(XT_MAC) || defined(XT_WIN) + xt_busy_wait(); +#else + xt_yield(); +#endif +} + + +/* + * Use this for loops that time critical. + * Time critical means we need to get going + * as soon as possible! + */ +xtPublic void xt_yield(void) +{ +#ifdef XT_WIN + Sleep(0); +#elif defined(XT_MAC) || defined(XT_SOLARIS) + usleep(0); +#elif defined(XT_NETBSD) + sched_yield(); +#else + pthread_yield(); +#endif +} + +xtPublic void xt_sleep_milli_second(u_int t) +{ +#ifdef XT_WIN + Sleep(t); +#else + usleep(t * 1000); +#endif +} + +xtPublic void xt_signal_all_threads(XTThreadPtr self, int sig) +{ + XTLinkedItemPtr li; + XTThreadPtr sig_thr; + + xt_ll_lock(self, thr_list); + try_(a) { + li = thr_list->ll_items; + while (li) { + sig_thr = (XTThreadPtr) li; + if (sig_thr != self) + pthread_kill(sig_thr->t_pthread, sig); + li = li->li_next; + } + } + catch_(a) { + xt_ll_unlock(self, thr_list); + throw_(); + } + cont_(a); + xt_ll_unlock(self, thr_list); +} + +/* + * Apply the given function to all threads except self! + */ +xtPublic void xt_do_to_all_threads(XTThreadPtr self, void (*do_func_ptr)(XTThreadPtr self, XTThreadPtr to_thr, void *thunk), void *thunk) +{ + XTLinkedItemPtr li; + XTThreadPtr to_thr; + + xt_ll_lock(self, thr_list); + pushr_(xt_ll_unlock, thr_list); + + li = thr_list->ll_items; + while (li) { + to_thr = (XTThreadPtr) li; + if (to_thr != self) + (*do_func_ptr)(self, to_thr, thunk); + li = li->li_next; + } + + freer_(); // xt_ll_unlock(thr_list) +} + +xtPublic XTThreadPtr xt_get_self(void) +{ + XTThreadPtr self; + + /* First check if the handler has the data: */ + if ((self = myxt_get_self())) + return self; + /* Then it must be a background process, and the + * thread info is stored in the local key: */ + return (XTThreadPtr) xt_get_key(thr_key); +} + +xtPublic void xt_set_self(XTThreadPtr self) +{ + xt_set_key(thr_key, self, NULL); +} + +xtPublic void xt_clear_exception(XTThreadPtr thread) +{ + thread->t_exception.e_xt_err = 0; + thread->t_exception.e_sys_err = 0; + *thread->t_exception.e_err_msg = 0; + *thread->t_exception.e_func_name = 0; + *thread->t_exception.e_source_file = 0; + thread->t_exception.e_source_line = 0; + *thread->t_exception.e_catch_trace = 0; +} + +/* + * Create a thread without requiring thread to do it (as in xt_create_daemon()). + * + * This function returns NULL on error. + */ +xtPublic XTThreadPtr xt_create_thread(c_char *name, xtBool main_thread, xtBool user_thread, XTExceptionPtr e) +{ + volatile XTThreadPtr self; + + self = (XTThreadPtr) xt_calloc_ns(sizeof(XTThreadRec)); + if (!self) { + xt_exception_errno(e, XT_CONTEXT, ENOMEM); + return NULL; + } + + if (!xt_set_key(thr_key, self, e)) { + xt_free_ns(self); + return NULL; + } + + xt_strcpy(XT_THR_NAME_SIZE, self->t_name, name); + self->t_main = main_thread; + self->t_daemon = FALSE; + + try_(a) { + thr_init(self, self); + } + catch_(a) { + *e = self->t_exception; + xt_set_key(thr_key, NULL, NULL); + xt_free_ns(self); + self = NULL; + } + cont_(a); + + if (self && user_thread) { + /* Add non-temporary threads to the thread list. */ + try_(b) { + xt_ll_add(self, thr_list, &self->t_links, TRUE); + } + catch_(b) { + *e = self->t_exception; + xt_free_thread(self); + self = NULL; + } + cont_(b); + } + + return self; +} + +/* + * Create a daemon thread. + */ +xtPublic XTThreadPtr xt_create_daemon(XTThreadPtr self, c_char *name) +{ + XTThreadPtr new_thread; + + /* NOTE: thr_key will be set when this thread start running. */ + + new_thread = (XTThreadPtr) xt_calloc(self, sizeof(XTThreadRec)); + xt_strcpy(XT_THR_NAME_SIZE, new_thread->t_name, name); + new_thread->t_main = FALSE; + new_thread->t_daemon = TRUE; + + try_(a) { + thr_init(self, new_thread); + } + catch_(a) { + xt_free(self, new_thread); + throw_(); + } + cont_(a); + return new_thread; +} + +void xt_free_thread(XTThreadPtr self) +{ + thr_exit(self); + if (!self->t_daemon && thr_list) + xt_ll_remove(self, thr_list, &self->t_links, TRUE); + /* Note, if I move this before thr_exit() then self = xt_get_self(); will fail in + * xt_close_file_ns() which is called by xt_unuse_database()! + */ + + /* + * Do not clear the pthread's key value unless it is the same as the thread just released. + * This can happen during shutdown when the engine is deregistered with the PBMS engine. + * + * What happens is that during deregistration the PBMS engine calls close to close all + * PBXT resources on all MySQL THDs created by PBMS for it's own pthreads. So the 'self' + * being freed is not the same 'self' associated with the PBXT 'thr_key'. + */ + if (thr_key && (self == ((XTThreadPtr) xt_get_key(thr_key)))) { + xt_set_key(thr_key, NULL, NULL); + } + xt_free_ns(self); +} + +xtPublic pthread_t xt_run_thread(XTThreadPtr self, XTThreadPtr child, void *(*start_routine)(XTThreadPtr)) +{ + ThreadDataRec data; + int err; + pthread_t child_thread; + + enter_(); + + // 'data' can be on the stack because we are waiting for the thread to start + // before exiting the function. + data.td_started = FALSE; + data.td_thr = child; + data.td_start_routine = start_routine; +#ifdef XT_WIN + { + pthread_attr_t attr = { 0, 0, 0 }; + + attr.priority = THREAD_PRIORITY_NORMAL; + err = pthread_create(&child_thread, &attr, thr_main, &data); + } +#else + err = pthread_create(&child_thread, NULL, thr_main, &data); +#endif + if (err) { + xt_free_thread(child); + xt_throw_errno(XT_CONTEXT, err); + } + while (!data.td_started) { + /* Check that the self is still alive: */ + if (pthread_kill(child_thread, 0)) + break; + xt_busy_wait(); + } + return_(child_thread); +} + +xtPublic void xt_exit_thread(XTThreadPtr self, void *result) +{ + xt_free_thread(self); + pthread_exit(result); +} + +xtPublic void *xt_wait_for_thread(xtThreadID tid, xtBool ignore_error) +{ + int err; + void *value_ptr = NULL; + xtBool ok = FALSE; + XTThreadPtr thread; + pthread_t t1 = 0; + + xt_lock_mutex_ns(&thr_array_lock); + if (tid < xt_thr_maximum_threads) { + if ((thread = xt_thr_array[tid])) { + t1 = thread->t_pthread; + ok = TRUE; + } + } + xt_unlock_mutex_ns(&thr_array_lock); + if (ok) { + err = xt_p_join(t1, &value_ptr); + if (err && !ignore_error) + xt_log_errno(XT_NS_CONTEXT, err); + } + return value_ptr; +} + +/* + * Kill the given thead, and wait for it to terminate. + * This function just returns if the self is already dead. + */ +xtPublic void xt_kill_thread(pthread_t t1) +{ + int err; + void *value_ptr = NULL; + + err = pthread_kill(t1, SIGTERM); + if (err) + return; + err = xt_p_join(t1, &value_ptr); + if (err) + xt_log_errno(XT_NS_CONTEXT, err); +} + +/* + * ----------------------------------------------------------------------- + * Read/write locking + */ + +#ifdef XT_THREAD_LOCK_INFO +xtPublic xtBool xt_init_rwlock(XTThreadPtr self, xt_rwlock_type *rwlock, const char *name) +#else +xtPublic xtBool xt_init_rwlock(XTThreadPtr self, xt_rwlock_type *rwlock) +#endif +{ + int err; + +#ifdef XT_THREAD_LOCK_INFO + err = xt_p_rwlock_init_with_name(rwlock, NULL, name); +#else + err = xt_p_rwlock_init(rwlock, NULL); +#endif + + if (err) { + xt_throw_errno(XT_CONTEXT, err); + return FAILED; + } + return OK; +} + +xtPublic void xt_free_rwlock(xt_rwlock_type *rwlock) +{ + int err; + + for (;;) { + err = xt_p_rwlock_destroy(rwlock); + if (err != XT_EBUSY) + break; + xt_busy_wait(); + } + /* PMC - xt_xn_exit_db() is called even when xt_xn_init_db() is not fully completed! + * This generates a lot of log entries. But I have no desire to only call + * free for those articles that I have init'ed! + if (err) + xt_log_errno(XT_NS_CONTEXT, err); + */ +} + +xtPublic xt_rwlock_type *xt_slock_rwlock(XTThreadPtr self, xt_rwlock_type *rwlock) +{ + int err; + + for (;;) { + err = xt_slock_rwlock_ns(rwlock); + if (err != XT_EAGAIN) + break; + xt_busy_wait(); + } + if (err) { + xt_throw_errno(XT_CONTEXT, err); + return NULL; + } + return rwlock; +} + +xtPublic xt_rwlock_type *xt_xlock_rwlock(XTThreadPtr self, xt_rwlock_type *rwlock) +{ + int err; + + for (;;) { + err = xt_xlock_rwlock_ns(rwlock); + if (err != XT_EAGAIN) + break; + xt_busy_wait(); + } + + if (err) { + xt_throw_errno(XT_CONTEXT, err); + return NULL; + } + return rwlock; +} + +xtPublic void xt_unlock_rwlock(XTThreadPtr XT_UNUSED(self), xt_rwlock_type *rwlock) +{ + int err; + + err = xt_unlock_rwlock_ns(rwlock); + if (err) + xt_log_errno(XT_NS_CONTEXT, err); +} + +/* + * ----------------------------------------------------------------------- + * Mutex locking + */ + +xtPublic xt_mutex_type *xt_new_mutex(XTThreadPtr self) +{ + xt_mutex_type *mx; + + if (!(mx = (xt_mutex_type *) xt_calloc(self, sizeof(xt_mutex_type)))) + return NULL; + pushr_(xt_free, mx); + if (!xt_init_mutex_with_autoname(self, mx)) { + freer_(); + return NULL; + } + popr_(); + return mx; +} + +xtPublic void xt_delete_mutex(XTThreadPtr self, xt_mutex_type *mx) +{ + if (mx) { + xt_free_mutex(mx); + xt_free(self, mx); + } +} + +#ifdef XT_THREAD_LOCK_INFO +xtPublic xtBool xt_init_mutex(XTThreadPtr self, xt_mutex_type *mx, const char *name) +#else +xtPublic xtBool xt_init_mutex(XTThreadPtr self, xt_mutex_type *mx) +#endif +{ + int err; + + err = xt_p_mutex_init_with_name(mx, NULL, name); + if (err) { + xt_throw_errno(XT_CONTEXT, err); + return FALSE; + } + return TRUE; +} + +void xt_free_mutex(xt_mutex_type *mx) +{ + int err; + + for (;;) { + err = xt_p_mutex_destroy(mx); + if (err != XT_EBUSY) + break; + xt_busy_wait(); + } + /* PMC - xt_xn_exit_db() is called even when xt_xn_init_db() is not fully completed! + if (err) + xt_log_errno(XT_NS_CONTEXT, err); + */ +} + +xtPublic xtBool xt_lock_mutex(XTThreadPtr self, xt_mutex_type *mx) +{ + int err; + + for (;;) { + err = xt_lock_mutex_ns(mx); + if (err != XT_EAGAIN) + break; + xt_busy_wait(); + } + + if (err) { + xt_throw_errno(XT_CONTEXT, err); + return FALSE; + } + return TRUE; +} + +xtPublic void xt_unlock_mutex(XTThreadPtr self, xt_mutex_type *mx) +{ + int err; + + err = xt_unlock_mutex_ns(mx); + if (err) + xt_throw_errno(XT_CONTEXT, err); +} + +xtPublic xtBool xt_set_key(pthread_key_t key, const void *value, XTExceptionPtr e) +{ +#ifdef XT_WIN + my_pthread_setspecific_ptr(thr_key, (void *) value); +#else + int err; + + err = pthread_setspecific(key, value); + if (err) { + if (e) + xt_exception_errno(e, XT_NS_CONTEXT, err); + return FALSE; + } +#endif + return TRUE; +} + +xtPublic void *xt_get_key(pthread_key_t key) +{ +#ifdef XT_WIN + return my_pthread_getspecific_ptr(void *, thr_key); +#else + return pthread_getspecific(key); +#endif +} + +xtPublic xt_cond_type *xt_new_cond(XTThreadPtr self) +{ + xt_cond_type *cond; + + if (!(cond = (xt_cond_type *) xt_calloc(self, sizeof(xt_cond_type)))) + return NULL; + pushr_(xt_free, cond); + if (!xt_init_cond(self, cond)) { + freer_(); + return NULL; + } + popr_(); + return cond; +} + +xtPublic void xt_delete_cond(XTThreadPtr self, xt_cond_type *cond) +{ + if (cond) { + xt_free_cond(cond); + xt_free(self, cond); + } +} + +xtPublic xtBool xt_init_cond(XTThreadPtr self, xt_cond_type *cond) +{ + int err; + + err = pthread_cond_init(cond, NULL); + if (err) { + xt_throw_errno(XT_CONTEXT, err); + return FALSE; + } + return TRUE; +} + +xtPublic void xt_free_cond(xt_cond_type *cond) +{ + int err; + + for (;;) { + err = pthread_cond_destroy(cond); + if (err != XT_EBUSY) + break; + xt_busy_wait(); + } + /* PMC - xt_xn_exit_db() is called even when xt_xn_init_db() is not fully completed! + if (err) + xt_log_errno(XT_NS_CONTEXT, err); + */ +} + +xtPublic xtBool xt_throw_delayed_signal(XTThreadPtr self, c_char *func, c_char *file, u_int line) +{ + XTThreadPtr me = self ? self : xt_get_self(); + + if (me->t_delayed_signal) { + int sig = me->t_delayed_signal; + + me->t_delayed_signal = 0; + xt_throw_signal(self, func, file, line, sig); + return FAILED; + } + return OK; +} + +xtPublic xtBool xt_wait_cond(XTThreadPtr self, xt_cond_type *cond, xt_mutex_type *mutex) +{ + int err; + XTThreadPtr me = self ? self : xt_get_self(); + + /* PMC - In my tests, if I throw an exception from within the wait + * the condition and the mutex remain locked. + */ + me->t_disable_interrupts = TRUE; + err = xt_p_cond_wait(cond, mutex); + me->t_disable_interrupts = FALSE; + if (err) { + xt_throw_errno(XT_CONTEXT, err); + return FALSE; + } + if (me->t_delayed_signal) { + xt_throw_delayed_signal(XT_CONTEXT); + return FALSE; + } + return TRUE; +} + +xtPublic xtBool xt_suspend(XTThreadPtr thread) +{ + xtBool ok; + + // You can only suspend yourself. + ASSERT_NS(pthread_equal(thread->t_pthread, pthread_self())); + + xt_lock_mutex_ns(&thread->t_lock); + ok = xt_wait_cond(NULL, &thread->t_cond, &thread->t_lock); + xt_unlock_mutex_ns(&thread->t_lock); + return ok; +} + +xtPublic xtBool xt_unsuspend(XTThreadPtr target) +{ + return xt_broadcast_cond_ns(&target->t_cond); +} + +xtPublic void xt_lock_thread(XTThreadPtr thread) +{ + xt_lock_mutex_ns(&thread->t_lock); +} + +xtPublic void xt_unlock_thread(XTThreadPtr thread) +{ + xt_unlock_mutex_ns(&thread->t_lock); +} + +xtPublic xtBool xt_wait_thread(XTThreadPtr thread) +{ + return xt_wait_cond(NULL, &thread->t_cond, &thread->t_lock); +} + +xtPublic void xt_signal_thread(XTThreadPtr target) +{ + xt_broadcast_cond_ns(&target->t_cond); +} + +xtPublic void xt_terminate_thread(XTThreadPtr self __attribute__((unused)), XTThreadPtr target) +{ + target->t_quit = TRUE; + target->t_delayed_signal = SIGTERM; +} + +xtPublic xtProcID xt_getpid() +{ +#ifdef XT_WIN + return GetCurrentProcessId(); +#else + return getpid(); +#endif +} + +xtPublic xtBool xt_process_exists(xtProcID pid) +{ + xtBool found; + +#ifdef XT_WIN + HANDLE h; + DWORD code; + + found = FALSE; + h = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, pid); + if (h) { + if (GetExitCodeProcess(h, &code)) { + if (code == STILL_ACTIVE) + found = TRUE; + } + CloseHandle(h); + } + else { + int err; + + err = HRESULT_CODE(GetLastError()); + if (err != ERROR_INVALID_PARAMETER) + found = TRUE; + } +#else + found = TRUE; + if (kill(pid, 0) == -1) { + if (errno == ESRCH) + found = FALSE; + } +#endif + return found; +} + +xtPublic xtBool xt_timed_wait_cond(XTThreadPtr self, xt_cond_type *cond, xt_mutex_type *mutex, u_long milli_sec) +{ + int err; + struct timespec abstime; + XTThreadPtr me = self ? self : xt_get_self(); + +#ifdef XT_WIN + union ft64 now; + + GetSystemTimeAsFileTime(&now.ft); + + /* System time is measured in 100ns units. + * This calculation will be reversed by the Windows implementation + * of pthread_cond_timedwait(), in order to extract the + * milli-second timeout! + */ + abstime.tv.i64 = now.i64 + (milli_sec * 10000); + + abstime.max_timeout_msec = milli_sec; +#else + struct timeval now; + u_llong micro_sec; + + /* Get the current time in microseconds: */ + gettimeofday(&now, NULL); + micro_sec = (u_llong) now.tv_sec * (u_llong) 1000000 + (u_llong) now.tv_usec; + + /* Add the timeout which is in milli seconds */ + micro_sec += (u_llong) milli_sec * (u_llong) 1000; + + /* Setup the end time, which is in nano-seconds. */ + abstime.tv_sec = (long) (micro_sec / 1000000); /* seconds */ + abstime.tv_nsec = (long) ((micro_sec % 1000000) * 1000); /* and nanoseconds */ +#endif + + me->t_disable_interrupts = TRUE; + err = xt_p_cond_timedwait(cond, mutex, &abstime); + me->t_disable_interrupts = FALSE; + if (err && err != ETIMEDOUT) { + xt_throw_errno(XT_CONTEXT, err); + return FALSE; + } + if (me->t_delayed_signal) { + xt_throw_delayed_signal(XT_CONTEXT); + return FALSE; + } + return TRUE; +} + +xtPublic xtBool xt_signal_cond(XTThreadPtr self, xt_cond_type *cond) +{ + int err; + + err = pthread_cond_signal(cond); + if (err) { + xt_throw_errno(XT_CONTEXT, err); + return FAILED; + } + return OK; +} + +xtPublic void xt_broadcast_cond(XTThreadPtr self, xt_cond_type *cond) +{ + int err; + + err = pthread_cond_broadcast(cond); + if (err) + xt_throw_errno(XT_CONTEXT, err); +} + +xtPublic xtBool xt_broadcast_cond_ns(xt_cond_type *cond) +{ + int err; + + err = pthread_cond_broadcast(cond); + if (err) { + xt_register_errno(XT_REG_CONTEXT, err); + return FAILED; + } + return OK; +} + +static int prof_setjmp_count = 0; + +xtPublic int prof_setjmp(void) +{ + prof_setjmp_count++; + return 0; +} + +xtPublic void xt_set_low_priority(XTThreadPtr self) +{ + int err = xt_p_set_low_priority(self->t_pthread); + if (err) { + self = NULL; /* Will cause logging, instead of throwing exception */ + xt_throw_errno(XT_CONTEXT, err); + } +} + +xtPublic void xt_set_normal_priority(XTThreadPtr self) +{ + int err = xt_p_set_normal_priority(self->t_pthread); + if (err) { + self = NULL; /* Will cause logging, instead of throwing exception */ + xt_throw_errno(XT_CONTEXT, err); + } +} + +xtPublic void xt_set_high_priority(XTThreadPtr self) +{ + int err = xt_p_set_high_priority(self->t_pthread); + if (err) { + self = NULL; /* Will cause logging, instead of throwing exception */ + xt_throw_errno(XT_CONTEXT, err); + } +} + +xtPublic void xt_set_priority(XTThreadPtr self, int priority) +{ + if (priority < XT_PRIORITY_NORMAL) + xt_set_low_priority(self); + else if (priority > XT_PRIORITY_NORMAL) + xt_set_high_priority(self); + else + xt_set_normal_priority(self); +} + +/* + * ----------------------------------------------------------------------- + * STATISTICS + */ + +xtPublic void xt_gather_statistics(XTStatisticsPtr stats) +{ + XTThreadPtr *thr; + xtWord8 s; + + xt_lock_mutex_ns(&thr_array_lock); + *stats = thr_statistics; + // Ignore index 0, it is not used! + thr = &xt_thr_array[1]; + for (u_int i=1; i<xt_thr_current_max_threads; i++) { + if (*thr) { + stats->st_commits += (*thr)->st_statistics.st_commits; + stats->st_rollbacks += (*thr)->st_statistics.st_rollbacks; + stats->st_stat_read += (*thr)->st_statistics.st_stat_read; + stats->st_stat_write += (*thr)->st_statistics.st_stat_write; + + XT_ADD_STATS(stats->st_rec, (*thr)->st_statistics.st_rec); + if ((s = (*thr)->st_statistics.st_rec.ts_flush_start)) + stats->st_rec.ts_flush_time += xt_trace_clock() - s; + stats->st_rec_cache_hit += (*thr)->st_statistics.st_rec_cache_hit; + stats->st_rec_cache_miss += (*thr)->st_statistics.st_rec_cache_miss; + stats->st_rec_cache_frees += (*thr)->st_statistics.st_rec_cache_frees; + + XT_ADD_STATS(stats->st_ind, (*thr)->st_statistics.st_ind); + if ((s = (*thr)->st_statistics.st_ind.ts_flush_start)) + stats->st_ind.ts_flush_time += xt_trace_clock() - s; + stats->st_ind_cache_hit += (*thr)->st_statistics.st_ind_cache_hit; + stats->st_ind_cache_miss += (*thr)->st_statistics.st_ind_cache_miss; + XT_ADD_STATS(stats->st_ilog, (*thr)->st_statistics.st_ilog); + + XT_ADD_STATS(stats->st_xlog, (*thr)->st_statistics.st_xlog); + if ((s = (*thr)->st_statistics.st_xlog.ts_flush_start)) + stats->st_xlog.ts_flush_time += xt_trace_clock() - s; + stats->st_xlog_cache_hit += (*thr)->st_statistics.st_xlog_cache_hit; + stats->st_xlog_cache_miss += (*thr)->st_statistics.st_xlog_cache_miss; + + XT_ADD_STATS(stats->st_data, (*thr)->st_statistics.st_data); + if ((s = (*thr)->st_statistics.st_data.ts_flush_start)) + stats->st_data.ts_flush_time += xt_trace_clock() - s; + + stats->st_scan_index += (*thr)->st_statistics.st_scan_index; + stats->st_scan_table += (*thr)->st_statistics.st_scan_table; + stats->st_row_select += (*thr)->st_statistics.st_row_select; + stats->st_row_insert += (*thr)->st_statistics.st_row_insert; + stats->st_row_update += (*thr)->st_statistics.st_row_update; + stats->st_row_delete += (*thr)->st_statistics.st_row_delete; + + stats->st_wait_for_xact += (*thr)->st_statistics.st_wait_for_xact; + stats->st_retry_index_scan += (*thr)->st_statistics.st_retry_index_scan; + stats->st_reread_record_list += (*thr)->st_statistics.st_reread_record_list; + } + thr++; + } + xt_unlock_mutex_ns(&thr_array_lock); +} + +static void thr_accumulate_statistics(XTThreadPtr self) +{ + thr_statistics.st_commits += self->st_statistics.st_commits; + thr_statistics.st_rollbacks += self->st_statistics.st_rollbacks; + thr_statistics.st_stat_read += self->st_statistics.st_stat_read; + thr_statistics.st_stat_write += self->st_statistics.st_stat_write; + + XT_ADD_STATS(thr_statistics.st_rec, self->st_statistics.st_rec); + thr_statistics.st_rec_cache_hit += self->st_statistics.st_rec_cache_hit; + thr_statistics.st_rec_cache_miss += self->st_statistics.st_rec_cache_miss; + thr_statistics.st_rec_cache_frees += self->st_statistics.st_rec_cache_frees; + + XT_ADD_STATS(thr_statistics.st_ind, self->st_statistics.st_ind); + thr_statistics.st_ind_cache_hit += self->st_statistics.st_ind_cache_hit; + thr_statistics.st_ind_cache_miss += self->st_statistics.st_ind_cache_miss; + XT_ADD_STATS(thr_statistics.st_ilog, self->st_statistics.st_ilog); + + XT_ADD_STATS(thr_statistics.st_xlog, self->st_statistics.st_xlog); + thr_statistics.st_xlog_cache_hit += self->st_statistics.st_xlog_cache_hit; + thr_statistics.st_xlog_cache_miss += self->st_statistics.st_xlog_cache_miss; + + XT_ADD_STATS(thr_statistics.st_data, self->st_statistics.st_data); + + thr_statistics.st_scan_index += self->st_statistics.st_scan_index; + thr_statistics.st_scan_table += self->st_statistics.st_scan_table; + thr_statistics.st_row_select += self->st_statistics.st_row_select; + thr_statistics.st_row_insert += self->st_statistics.st_row_insert; + thr_statistics.st_row_update += self->st_statistics.st_row_update; + thr_statistics.st_row_delete += self->st_statistics.st_row_delete; + + thr_statistics.st_wait_for_xact += self->st_statistics.st_wait_for_xact; + thr_statistics.st_retry_index_scan += self->st_statistics.st_retry_index_scan; + thr_statistics.st_reread_record_list += self->st_statistics.st_reread_record_list; +} + +xtPublic u_llong xt_get_statistic(XTStatisticsPtr stats, XTDatabaseHPtr db, u_int rec_id) +{ + u_llong stat_value; + + switch (rec_id) { + case XT_STAT_TIME_CURRENT: + stat_value = (u_llong) time(NULL); + break; + case XT_STAT_TIME_PASSED: + stat_value = (u_llong) xt_trace_clock(); + break; + case XT_STAT_COMMITS: + stat_value = stats->st_commits; + break; + case XT_STAT_ROLLBACKS: + stat_value = stats->st_rollbacks; + break; + case XT_STAT_STAT_READS: + stat_value = stats->st_stat_read; + break; + case XT_STAT_STAT_WRITES: + stat_value = stats->st_stat_write; + break; + + case XT_STAT_REC_BYTES_IN: + stat_value = stats->st_rec.ts_read; + break; + case XT_STAT_REC_BYTES_OUT: + stat_value = stats->st_rec.ts_write; + break; + case XT_STAT_REC_SYNC_COUNT: + stat_value = stats->st_rec.ts_flush; + break; + case XT_STAT_REC_SYNC_TIME: + stat_value = stats->st_rec.ts_flush_time; + break; + case XT_STAT_REC_CACHE_HIT: + stat_value = stats->st_rec_cache_hit; + break; + case XT_STAT_REC_CACHE_MISS: + stat_value = stats->st_rec_cache_miss; + break; + case XT_STAT_REC_CACHE_FREES: + stat_value = stats->st_rec_cache_frees; + break; + case XT_STAT_REC_CACHE_USAGE: + stat_value = (u_llong) xt_tc_get_usage(); + break; + + case XT_STAT_IND_BYTES_IN: + stat_value = stats->st_ind.ts_read; + break; + case XT_STAT_IND_BYTES_OUT: + stat_value = stats->st_ind.ts_write; + break; + case XT_STAT_IND_SYNC_COUNT: + stat_value = stats->st_ind.ts_flush; + break; + case XT_STAT_IND_SYNC_TIME: + stat_value = stats->st_ind.ts_flush_time; + break; + case XT_STAT_IND_CACHE_HIT: + stat_value = stats->st_ind_cache_hit; + break; + case XT_STAT_IND_CACHE_MISS: + stat_value = stats->st_ind_cache_miss; + break; + case XT_STAT_IND_CACHE_USAGE: + stat_value = (u_llong) xt_ind_get_usage(); + break; + case XT_STAT_ILOG_BYTES_IN: + stat_value = stats->st_ilog.ts_read; + break; + case XT_STAT_ILOG_BYTES_OUT: + stat_value = stats->st_ilog.ts_write; + break; + case XT_STAT_ILOG_SYNC_COUNT: + stat_value = stats->st_ilog.ts_flush; + break; + case XT_STAT_ILOG_SYNC_TIME: + stat_value = stats->st_ilog.ts_flush_time; + break; + + case XT_STAT_XLOG_BYTES_IN: + stat_value = stats->st_xlog.ts_read; + break; + case XT_STAT_XLOG_BYTES_OUT: + stat_value = stats->st_xlog.ts_write; + break; + case XT_STAT_XLOG_SYNC_COUNT: + stat_value = stats->st_xlog.ts_flush; + break; + case XT_STAT_XLOG_SYNC_TIME: + stat_value = stats->st_xlog.ts_flush_time; + break; + case XT_STAT_XLOG_CACHE_HIT: + stat_value = stats->st_xlog_cache_hit; + break; + case XT_STAT_XLOG_CACHE_MISS: + stat_value = stats->st_xlog_cache_miss; + break; + case XT_STAT_XLOG_CACHE_USAGE: + stat_value = (u_llong) xt_xlog_get_usage(); + break; + + case XT_STAT_DATA_BYTES_IN: + stat_value = stats->st_data.ts_read; + break; + case XT_STAT_DATA_BYTES_OUT: + stat_value = stats->st_data.ts_write; + break; + case XT_STAT_DATA_SYNC_COUNT: + stat_value = stats->st_data.ts_flush; + break; + case XT_STAT_DATA_SYNC_TIME: + stat_value = stats->st_data.ts_flush_time; + break; + + case XT_STAT_BYTES_TO_CHKPNT: + stat_value = db ? xt_bytes_since_last_checkpoint(db, db->db_xlog.xl_write_log_id, db->db_xlog.xl_write_log_offset) : 0; + break; + case XT_STAT_LOG_BYTES_TO_WRITE: + stat_value = db ? db->db_xlog.xl_log_bytes_written - db->db_xlog.xl_log_bytes_read : 0;//db->db_xlog.xlog_bytes_to_write(); + break; + case XT_STAT_BYTES_TO_SWEEP: + /* This stat is potentially very expensive: */ + stat_value = db ? xt_xn_bytes_to_sweep(db, xt_get_self()) : 0; + break; + case XT_STAT_WAIT_FOR_XACT: + stat_value = stats->st_wait_for_xact; + break; + case XT_STAT_XACT_TO_CLEAN: + stat_value = db ? db->db_xn_curr_id + 1 - db->db_xn_to_clean_id : 0; + break; + case XT_STAT_SWEEPER_WAITS: + stat_value = db ? db->db_stat_sweep_waits : 0; + break; + + case XT_STAT_SCAN_INDEX: + stat_value = stats->st_scan_index; + break; + case XT_STAT_SCAN_TABLE: + stat_value = stats->st_scan_table; + break; + case XT_STAT_ROW_SELECT: + stat_value = stats->st_row_select; + break; + case XT_STAT_ROW_INSERT: + stat_value = stats->st_row_insert; + break; + case XT_STAT_ROW_UPDATE: + stat_value = stats->st_row_update; + break; + case XT_STAT_ROW_DELETE: + stat_value = stats->st_row_delete; + break; + + case XT_STAT_RETRY_INDEX_SCAN: + stat_value = stats->st_retry_index_scan; + break; + case XT_STAT_REREAD_REC_LIST: + stat_value = stats->st_reread_record_list; + break; + default: + stat_value = 0; + break; + } + return stat_value; +} diff --git a/storage/pbxt/src/thread_xt.h b/storage/pbxt/src/thread_xt.h new file mode 100644 index 00000000000..4344c5335b9 --- /dev/null +++ b/storage/pbxt/src/thread_xt.h @@ -0,0 +1,675 @@ +/* Copyright (c) 2005 PrimeBase Technologies GmbH + * + * PrimeBase XT + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You 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 + * + * 2005-01-03 Paul McCullagh + * + * H&G2JCtL + */ + +#ifndef __xt_thread_h__ +#define __xt_thread_h__ + +#include <stdio.h> +#ifndef XT_WIN +#include <sys/param.h> +#endif +#include <setjmp.h> + +#include "xt_defs.h" +#include "xt_errno.h" +#include "linklist_xt.h" +#include "memory_xt.h" +#include "xactlog_xt.h" +#include "datalog_xt.h" +#include "lock_xt.h" +#include "locklist_xt.h" + +/* + * ----------------------------------------------------------------------- + * Macros and defines + */ + +#ifdef XT_WIN +#define __FUNC__ __FUNCTION__ +#elif defined(XT_SOLARIS) +#define __FUNC__ "__func__" +#else +#define __FUNC__ __PRETTY_FUNCTION__ +#endif + +#define XT_ERR_MSG_SIZE (PATH_MAX + 200) + +#ifdef DEBUG +#define ASSERT(expr) ((expr) ? TRUE : xt_assert(self, #expr, __FUNC__, __FILE__, __LINE__)) +#else +#define ASSERT(expr) ((void) 0) +#endif + +#ifdef DEBUG +#define ASSUME(expr) ((expr) ? TRUE : xt_assume(self, #expr, __FUNC__, __FILE__, __LINE__)) +#else +#define ASSUME(expr) ((void) 0) +#endif + +#ifdef DEBUG +#define ASSERT_NS(expr) ((expr) ? TRUE : xt_assert(NULL, #expr, __FUNC__, __FILE__, __LINE__)) +#else +#define ASSERT_NS(expr) ((void) 0) +#endif + +#define XT_THROW_ASSERTION(str) xt_throw_assertion(self, __FUNC__, __FILE__, __LINE__, str) + +/* Log levels */ +#define XT_LOG_DEFAULT -1 +#define XT_LOG_PROTOCOL 0 +#define XT_LOG_FATAL 1 +#define XT_LOG_ERROR 2 +#define XT_LOG_WARNING 3 +#define XT_LOG_INFO 4 +#define XT_LOG_TRACE 5 + +#define XT_PROTOCOL self, "", NULL, 0, XT_LOG_PROTOCOL +#define XT_WARNING self, "", NULL, 0, XT_LOG_WARNING +#define XT_INFO self, "", NULL, 0, XT_LOG_INFO +#define XT_ERROR self, "", NULL, 0, XT_LOG_ERROR +#define XT_TRACE self, "", NULL, 0, XT_LOG_TRACE + +#define XT_NT_PROTOCOL NULL, "", NULL, 0, XT_LOG_PROTOCOL +#define XT_NT_WARNING NULL, "", NULL, 0, XT_LOG_WARNING +#define XT_NT_INFO NULL, "", NULL, 0, XT_LOG_INFO +#define XT_NT_ERROR NULL, "", NULL, 0, XT_LOG_ERROR +#define XT_NT_TRACE NULL, "", NULL, 0, XT_LOG_TRACE + +#define XT_ERROR_CONTEXT(func) self, __FUNC__, __FILE__, __LINE__, XT_LOG_ERROR + +/* Thread types */ +#define XT_THREAD_MAIN 0 +#define XT_THREAD_WORKER 1 + +/* Thread Priorities: */ +#define XT_PRIORITY_LOW 0 +#define XT_PRIORITY_NORMAL 1 +#define XT_PRIORITY_HIGH 2 + +#define XT_CONTEXT self, __FUNC__, __FILE__, __LINE__ +#define XT_NS_CONTEXT NULL, __FUNC__, __FILE__, __LINE__ +#define XT_REG_CONTEXT __FUNC__, __FILE__, __LINE__ + +#define XT_MAX_JMP 20 +#define XT_MAX_CALL_STACK 100 /* The number of functions recorded by enter_() and exit() */ +#define XT_RES_STACK_SIZE 4000 /* The size of the stack resource stack in bytes. */ +#define XT_MAX_RESOURCE_USAGE 5 /* The maximum number of temp slots used per routine. */ +#define XT_CATCH_TRACE_SIZE 1024 +#define XT_MAX_FUNC_NAME_SIZE 120 +#define XT_SOURCE_FILE_NAME_SIZE 40 +#define XT_THR_NAME_SIZE 80 + +#ifdef XT_THREAD_LOCK_INFO +#define xt_init_rwlock_with_autoname(a,b) xt_init_rwlock(a,b,LOCKLIST_ARG_SUFFIX(b)) +#else +#define xt_init_rwlock_with_autoname(a,b) xt_init_rwlock(a,b) +#endif + +typedef struct XTException { + int e_xt_err; /* The XT error number (ALWAYS non-zero on error, else zero) */ + int e_sys_err; /* The system error number (0 if none) */ + char e_err_msg[XT_ERR_MSG_SIZE]; /* The error message text (0 terminated string) */ + char e_func_name[XT_MAX_FUNC_NAME_SIZE]; /* The name of the function in which the exception occurred */ + char e_source_file[XT_SOURCE_FILE_NAME_SIZE]; /* The source file in which the exception was thrown */ + u_int e_source_line; /* The source code line number on which the exception was thrown */ + char e_catch_trace[XT_CATCH_TRACE_SIZE]; /* A string of the catch trace. */ +} XTExceptionRec, *XTExceptionPtr; + +struct XTThread; +struct XTSortedList; +struct XTXactLog; +struct XTXactData; +struct XTDatabase; + +typedef void (*XTThreadFreeFunc)(struct XTThread *self, void *data); + +typedef struct XTResourceArgs { + void *ra_p1; + xtWord4 ra_p2; +} XTResourceArgsRec, *XTResourceArgsPtr; + +/* This structure represents a temporary resource on the resource stack. + * Resource are automatically freed if an exception occurs. + */ +typedef struct XTResource { + xtWord4 r_prev_size; /* The size of the previous resource on the stack (must be first!) */ + void *r_data; /* A pointer to the resource data (this may be on the resource stack) */ + XTThreadFreeFunc r_free_func; /* The function used to free the resource. */ +} XTResourceRec, *XTResourcePtr; + +typedef struct XTJumpBuf { + XTResourcePtr jb_res_top; + int jb_call_top; + jmp_buf jb_buffer; +} XTJumpBufRec, *XTJumpBufPtr; + +typedef struct XTCallStack { + c_char *cs_func; + c_char *cs_file; + u_int cs_line; +} XTCallStackRec, *XTCallStackPtr; + +typedef struct XTIOStats { + u_int ts_read; /* The number of bytes read. */ + u_int ts_write; /* The number of bytes written. */ + xtWord8 ts_flush_time; /* The accumulated flush time. */ + xtWord8 ts_flush_start; /* Start time, non-zero if a timer is running. */ + u_int ts_flush; /* The number of flush operations. */ +} XTIOStatsRec, *XTIOStatsPtr; + +#define XT_ADD_STATS(x, y) { \ + (x).ts_read += (y).ts_read; \ + (x).ts_write += (y).ts_write; \ + (x).ts_flush_time += (y).ts_flush_time; \ + (x).ts_flush += (y).ts_flush; \ +} + +typedef struct XTStatistics { + u_int st_commits; + u_int st_rollbacks; + u_int st_stat_read; + u_int st_stat_write; + + XTIOStatsRec st_rec; + u_int st_rec_cache_hit; + u_int st_rec_cache_miss; + u_int st_rec_cache_frees; + + XTIOStatsRec st_ind; + u_int st_ind_cache_hit; + u_int st_ind_cache_miss; + XTIOStatsRec st_ilog; + + XTIOStatsRec st_xlog; + u_int st_xlog_cache_hit; + u_int st_xlog_cache_miss; + + XTIOStatsRec st_data; + + XTIOStatsRec st_x; + + u_int st_scan_index; + u_int st_scan_table; + u_int st_row_select; + u_int st_row_insert; + u_int st_row_update; + u_int st_row_delete; + + u_int st_wait_for_xact; + u_int st_retry_index_scan; + u_int st_reread_record_list; + XTIOStatsRec st_ind_flush_time; +} XTStatisticsRec, *XTStatisticsPtr; + +/* + * PBXT supports COMMITTED READ and REPEATABLE READ. + * + * As Jim says, multi-versioning cannot implement SERIALIZABLE. Basically + * you need locking to do this. Although phantom reads do not occur with + * MVCC, it is still not serializable. + * + * This can be seen from the following example: + * + * T1: INSERT t1 VALUE (1, 1); + * T2: INSERT t1 VALUE (2, 2); + * T1: UPDATE t1 SET b = 3 WHERE a IN (1, 2); + * T2: UPDATE t1 SET b = 4 WHERE a IN (1, 2); + * Serialized result (T1, T2) or (T2, T1): + * a b or a b + * 1 4 1 3 + * 2 4 1 3 + * Non-serialized (MVCC) result: + * a b + * 1 3 + * 2 4 + */ +#define XT_XACT_UNCOMMITTED_READ 0 +#define XT_XACT_COMMITTED_READ 1 +#define XT_XACT_REPEATABLE_READ 2 /* Guarentees rows already read will not change. */ +#define XT_XACT_SERIALIZABLE 3 + +typedef struct XTThread { + XTLinkedItemRec t_links; /* Required to be a member of a double-linked list. */ + + char t_name[XT_THR_NAME_SIZE]; /* The name of the thread. */ + xtBool t_main; /* TRUE if this is the main (initial) thread */ + xtBool t_quit; /* TRUE if this thread should stop running. */ + xtBool t_daemon; /* TRUE if this thread is a daemon. */ + xtThreadID t_id; /* The thread ID (0=main), index into thread array. */ + pthread_t t_pthread; /* The pthread associated with xt thread */ + xtBool t_disable_interrupts; /* TRUE if interrupts are disabled. */ + int t_delayed_signal; /* Throw this signal as soon as you can! */ + + void *t_data; /* Data passed to the thread. */ + XTThreadFreeFunc t_free_data; /* Routine used to free the thread data */ + + int t_call_top; /* A pointer to the top of the call stack. */ + XTCallStackRec t_call_stack[XT_MAX_CALL_STACK];/* Records the function under execution (to be output on error). */ + + XTResourcePtr t_res_top; /* The top of the resource stack (reference next free space). */ + union { + char t_res_stack[XT_RES_STACK_SIZE]; /* Temporary data to be freed if an exception occurs. */ + xtWord4 t_align_res_stack; + } x; + + int t_jmp_depth; /* The current jump depth */ + XTJumpBufRec t_jmp_env[XT_MAX_JMP]; /* The process environment to be restored on exception */ + XTExceptionRec t_exception; /* The exception details. */ + + xt_cond_type t_cond; /* The pthread condition used for suspending the thread. */ + xt_mutex_type t_lock; /* Thread lock, used for operations on a thread that may be done by other threads. + * for example xt_unuse_database(). + */ + + /* Application specific data: */ + struct XTDatabase *st_database; /* The database in use by the thread. */ + u_int st_lock_count; /* We count the number of locks MySQL has set in order to know when they are all released. */ + u_int st_stat_count; /* start statement count. */ + struct XTXactData *st_xact_data; /* The transaction data, not NULL if the transaction performs an update. */ + xtBool st_xact_writer; /* TRUE if the transaction has written somthing to the log. */ + time_t st_xact_write_time; /* Approximate first write time (uses xt_db_approximate_time). */ + xtBool st_xact_long_running; /* TRUE if this is a long running writer transaction. */ + xtWord4 st_visible_time; /* Transactions committed before this time are visible. */ + XTDataLogBufferRec st_dlog_buf; + + int st_xact_mode; /* The transaction mode. */ + xtBool st_ignore_fkeys; /* TRUE if we must ignore foreign keys. */ + xtBool st_auto_commit; /* TRUE if this is an auto-commit transaction. */ + xtBool st_table_trans; /* TRUE transactions is a result of LOCK TABLES. */ + xtBool st_abort_trans; /* TRUE if the transaction should be aborted. */ + xtBool st_stat_ended; /* TRUE if the statement was ended. */ + xtBool st_stat_trans; /* TRUE if a statement transaction is running (started on UPDATE). */ + xtBool st_stat_modify; /* TRUE if the statement is an INSERT/UPDATE/DELETE */ +#ifdef XT_IMPLEMENT_NO_ACTION + XTBasicListRec st_restrict_list; /* These records have been deleted and should have no reference. */ +#endif + /* Local thread list. */ + u_int st_thread_list_count; + u_int st_thread_list_size; + xtThreadID *st_thread_list; + + /* Used to prevent a record from being updated twice in one statement. */ + xtBool st_is_update; /* TRUE if this is an UPDATE statement. */ + u_int st_update_id; /* The update statement ID. */ + + XTRowLockListRec st_lock_list; /* The thread row lock list (drop locks on transaction end). */ + XTStatisticsRec st_statistics; /* Accumulated statistics for this thread. */ +#ifdef XT_THREAD_LOCK_INFO + /* list of locks (spins, mutextes, etc) that this thread currently holds (debugging) */ + XTThreadLockInfoPtr st_thread_lock_list[XT_THREAD_LOCK_INFO_MAX_COUNT]; + int st_thread_lock_count; +#endif +} XTThreadRec, *XTThreadPtr; + +/* + * ----------------------------------------------------------------------- + * Call stack + */ + +#define XT_INIT_CHECK_STACK char xt_chk_buffer[512]; memset(xt_chk_buffer, 0xFE, 512); +#define XT_RE_CHECK_STACK memset(xt_chk_buffer, 0xFE, 512); + +/* + * This macro must be placed at the start of every function. + * It records the current context so that we can + * dump a type of stack trace later if necessary. + * + * It also sets up the current thread pointer 'self'. + */ +#ifdef DEBUG +#define XT_STACK_TRACE +#endif + +/* + * These macros generate a stack trace which can be used + * to locate an error on exception. + */ +#ifdef XT_STACK_TRACE + +/* + * Place this call at the top of a function, + * after the declaration of local variable, and + * before the first code is executed. + */ +#define enter_() int xt_frame = self->t_call_top++; \ + do { \ + if (xt_frame < XT_MAX_CALL_STACK) { \ + self->t_call_stack[xt_frame].cs_func = __FUNC__; \ + self->t_call_stack[xt_frame].cs_file = __FILE__; \ + self->t_call_stack[xt_frame].cs_line = __LINE__; \ + } \ + } while (0) + +#define outer_() self->t_call_top = xt_frame; + +/* + * On exit to a function, either exit_() or + * return_() must be called. + */ +#define exit_() do { \ + outer_(); \ + return; \ + } while (0) + +#define return_(x) do { \ + outer_(); \ + return(x); \ + } while (0) + +#define returnc_(x, typ) do { \ + typ rv; \ + rv = (x); \ + outer_(); \ + return(rv); \ + } while (0) + +/* + * Sets the line number before a call to get a better + * stack trace; + */ +#define call_(x) do { self->t_call_stack[xt_frame].cs_line = __LINE__; x; } while (0) + +#else +#define enter_() +#define outer_() +#define exit_() return; +#define return_(x) return (x) +#define returnc_(x, typ) return (x) +#define call_(x) x +#endif + +/* + * ----------------------------------------------------------------------- + * Throwing and catching + */ + +int prof_setjmp(void); + +#define TX_CHK_JMP() if ((self)->t_jmp_depth < 0 || (self)->t_jmp_depth >= XT_MAX_JMP) xt_throw_xterr(self, __FUNC__, __FILE__, __LINE__, XT_ERR_JUMP_OVERFLOW) +#ifdef PROFILE +#define profile_setjmp prof_setjmp() +#else +#define profile_setjmp +#endif + +#define try_(n) TX_CHK_JMP(); \ + (self)->t_jmp_env[(self)->t_jmp_depth].jb_res_top = (self)->t_res_top; \ + (self)->t_jmp_env[(self)->t_jmp_depth].jb_call_top = (self)->t_call_top; \ + (self)->t_jmp_depth++; profile_setjmp; if (setjmp((self)->t_jmp_env[(self)->t_jmp_depth-1].jb_buffer)) goto catch_##n; +#define catch_(n) (self)->t_jmp_depth--; goto cont_##n; catch_##n: (self)->t_jmp_depth--; xt_caught(self); +#define cont_(n) cont_##n: +#define throw_() xt_throw(self) + +/* + * ----------------------------------------------------------------------- + * Resource stack + */ + +//#define DEBUG_RESOURCE_STACK + +#ifdef DEBUG_RESOURCE_STACK +#define CHECK_RS if ((char *) (self)->t_res_top < (self)->x.t_res_stack) xt_bug(self); +#define CHECK_NS_RS { XTThreadPtr self = xt_get_self(); CHECK_RS; } +#else +#define CHECK_RS remove this! +#define CHECK_NS_RS remove this! +#endif + +/* + * Allocate a resource on the resource stack. The resource will be freed + * automatocally if an exception occurs. Before exiting the current + * procedure you must free the resource using popr_() or freer_(). + * v = value to be set to the resource, + * f = function which frees the resource, + * s = the size of the resource, + */ + +/* GOTCHA: My experience is that contructs such as *((xtWordPS *) &(v)) = (xtWordPS) (x) + * cause optimised versions to crash?! + */ +#define allocr_(v, f, s, t) do { \ + if (((char *) (self)->t_res_top) > (self)->x.t_res_stack + XT_RES_STACK_SIZE - sizeof(XTResourceRec) + (s) + 4) \ + xt_throw_xterr(self, __FUNC__, __FILE__, __LINE__, XT_ERR_RES_STACK_OVERFLOW); \ + v = (t) (((char *) (self)->t_res_top) + sizeof(XTResourceRec)); \ + (self)->t_res_top->r_data = (v); \ + (self)->t_res_top->r_free_func = (XTThreadFreeFunc) (f); \ + (self)->t_res_top = (XTResourcePtr) (((char *) (self)->t_res_top) + sizeof(XTResourceRec) + (s)); \ + (self)->t_res_top->r_prev_size = sizeof(XTResourceRec) + (s); \ + } while (0) + +#define alloczr_(v, f, s, t) do { allocr_(v, f, s, t); \ + memset(v, 0, s); } while (0) + +/* Push and set a resource: + * v = value to be set to the resource, + * f = function which frees the resource, + * r = the resource, + * NOTE: the expression (r) must come first because it may contain + * calls which use the resource stack!! + */ +#define pushsr_(v, f, r) do { \ + if (((char *) (self)->t_res_top) > (self)->x.t_res_stack + XT_RES_STACK_SIZE - sizeof(XTResourceRec) + 4) \ + xt_throw_xterr(self, __FUNC__, __FILE__, __LINE__, XT_ERR_RES_STACK_OVERFLOW); \ + v = (r); \ + (self)->t_res_top->r_data = (v); \ + (self)->t_res_top->r_free_func = (XTThreadFreeFunc) (f); \ + (self)->t_res_top = (XTResourcePtr) (((char *) (self)->t_res_top) + sizeof(XTResourceRec)); \ + (self)->t_res_top->r_prev_size = sizeof(XTResourceRec); \ + } while (0) + +/* Push a resource. In the event of an exception it will be freed + * the free routine. + * f = function which frees the resource, + * r = a pointer to the resource, + */ +#define pushr_(f, r) do { \ + if (((char *) (self)->t_res_top) > (self)->x.t_res_stack + XT_RES_STACK_SIZE - sizeof(XTResourceRec) + 4) \ + xt_throw_xterr(self, __FUNC__, __FILE__, __LINE__, XT_ERR_RES_STACK_OVERFLOW); \ + (self)->t_res_top->r_data = (r); \ + (self)->t_res_top->r_free_func = (XTThreadFreeFunc) (f); \ + (self)->t_res_top = (XTResourcePtr) (((char *) (self)->t_res_top) + sizeof(XTResourceRec)); \ + (self)->t_res_top->r_prev_size = sizeof(XTResourceRec); \ + } while (0) + +/* Pop a resource without freeing it: */ +#ifdef DEBUG_RESOURCE_STACK +#define popr_() do { \ + (self)->t_res_top = (XTResourcePtr) (((char *) (self)->t_res_top) - (self)->t_res_top->r_prev_size); \ + if ((char *) (self)->t_res_top < (self)->x.t_res_stack) \ + xt_bug(self); \ + } while (0) +#else +#define popr_() do { (self)->t_res_top = (XTResourcePtr) (((char *) (self)->t_res_top) - (self)->t_res_top->r_prev_size); } while (0) +#endif + +#define setr_(r) do { ((XTResourcePtr) (((char *) (self)->t_res_top) - (self)->t_res_top->r_prev_size))->r_data = (r); } while (0) + +/* Pop and free a resource: */ +#ifdef DEBUG_RESOURCE_STACK +#define freer_() do { \ + register XTResourcePtr rp; \ + rp = (XTResourcePtr) (((char *) (self)->t_res_top) - (self)->t_res_top->r_prev_size); \ + if ((char *) rp < (self)->x.t_res_stack) \ + xt_bug(self); \ + (rp->r_free_func)((self), rp->r_data); \ + (self)->t_res_top = rp; \ + } while (0) +#else +#define freer_() do { \ + register XTResourcePtr rp; \ + rp = (XTResourcePtr) (((char *) (self)->t_res_top) - (self)->t_res_top->r_prev_size); \ + (rp->r_free_func)((self), rp->r_data); \ + (self)->t_res_top = rp; \ + } while (0) +#endif + +/* + * ----------------------------------------------------------------------- + * Thread globals + */ + +extern u_int xt_thr_maximum_threads; +extern u_int xt_thr_current_thread_count; +extern u_int xt_thr_current_max_threads; +extern struct XTThread **xt_thr_array; + +/* + * ----------------------------------------------------------------------- + * Function prototypes + */ + +void xt_get_now(char *buffer, size_t len); +xtBool xt_init_logging(void); +void xt_exit_logging(void); +void xt_log_flush(XTThreadPtr self); +void xt_logf(XTThreadPtr self, c_char *func, c_char *file, u_int line, int level, c_char *fmt, ...); +void xt_log(XTThreadPtr self, c_char *func, c_char *file, u_int line, int level, c_char *string); +int xt_log_errorf(XTThreadPtr self, c_char *func, c_char *file, u_int line, int level, int xt_err, int sys_err, c_char *fmt, ...); +int xt_log_error(XTThreadPtr self, c_char *func, c_char *file, u_int line, int level, int xt_err, int sys_err, c_char *string); +void xt_log_exception(XTThreadPtr self, XTExceptionPtr e, int level); +void xt_clear_exception(XTThreadPtr self); +void xt_log_and_clear_exception(XTThreadPtr self); +void xt_log_and_clear_exception_ns(void); +void xt_log_and_clear_warning(XTThreadPtr self); +void xt_log_and_clear_warning_ns(void); + +void xt_bug(XTThreadPtr self); +void xt_caught(XTThreadPtr self); +void xt_throw(XTThreadPtr self); +void xt_throwf(XTThreadPtr self, c_char *func, c_char *file, u_int line, int xt_err, int sys_err, c_char *format, ...); +void xt_throw_error(XTThreadPtr self, c_char *func, c_char *file, u_int line, int xt_err, int sys_err, c_char *message); +void xt_throw_i2xterr(XTThreadPtr self, c_char *func, c_char *file, u_int line, int xt_err, c_char *item, c_char *item2); +void xt_throw_ixterr(XTThreadPtr self, c_char *func, c_char *file, u_int line, int xt_err, c_char *item); +void xt_throw_tabcolerr(XTThreadPtr self, c_char *func, c_char *file, u_int line, int xt_err, XTPathStrPtr tab_item, c_char *item2); +void xt_throw_taberr(XTThreadPtr self, c_char *func, c_char *file, u_int line, int xt_err, XTPathStrPtr tab_item); +void xt_throw_ulxterr(XTThreadPtr self, c_char *func, c_char *file, u_int line, int xt_err, u_long value); +void xt_throw_sulxterr(XTThreadPtr self, c_char *func, c_char *file, u_int line, int xt_err, c_char *item, u_long value); +void xt_throw_xterr(XTThreadPtr self, c_char *func, c_char *file, u_int line, int xt_err); +void xt_throw_errno(XTThreadPtr self, c_char *func, c_char *file, u_int line, int err_no); +void xt_throw_ferrno(XTThreadPtr self, c_char *func, c_char *file, u_int line, int err_no, c_char *path); +void xt_throw_assertion(XTThreadPtr self, c_char *func, c_char *file, u_int line, c_char *str); +void xt_throw_signal(XTThreadPtr self, c_char *func, c_char *file, u_int line, int sig); +xtBool xt_throw_delayed_signal(XTThreadPtr self, c_char *func, c_char *file, u_int line); + +void xt_registerf(c_char *func, c_char *file, u_int line, int xt_err, int sys_err, c_char *fmt, ...); +void xt_register_i2xterr(c_char *func, c_char *file, u_int line, int xt_err, c_char *item, c_char *item2); +void xt_register_ixterr(c_char *func, c_char *file, u_int line, int xt_err, c_char *item); +void xt_register_tabcolerr(c_char *func, c_char *file, u_int line, int xt_err, XTPathStrPtr tab_item, c_char *item2); +void xt_register_taberr(c_char *func, c_char *file, u_int line, int xt_err, XTPathStrPtr tab_item); +void xt_register_ulxterr(c_char *func, c_char *file, u_int line, int xt_err, u_long value); +xtBool xt_register_ferrno(c_char *func, c_char *file, u_int line, int err, c_char *path); +void xt_register_error(c_char *func, c_char *file, u_int line, int xt_err, int sys_err, c_char *msg); +xtBool xt_register_errno(c_char *func, c_char *file, u_int line, int err); +void xt_register_xterr(c_char *func, c_char *file, u_int line, int xt_err); + +void xt_exceptionf(XTExceptionPtr e, XTThreadPtr self, c_char *func, c_char *file, u_int line, int xt_err, int sys_err, c_char *fmt, ...); +void xt_exception_error(XTExceptionPtr e, XTThreadPtr self, c_char *func, c_char *file, u_int line, int xt_err, int sys_err, c_char *msg); +xtBool xt_exception_errno(XTExceptionPtr e, XTThreadPtr self, c_char *func, c_char *file, u_int line, int err); + +void xt_log_errno(XTThreadPtr self, c_char *func, c_char *file, u_int line, int err); + +xtBool xt_assert(XTThreadPtr self, c_char *expr, c_char *func, c_char *file, u_int line); +xtBool xt_assume(XTThreadPtr self, c_char *expr, c_char *func, c_char *file, u_int line); + +XTThreadPtr xt_init_threading(u_int max_threads); +void xt_exit_threading(XTThreadPtr self); + +XTThreadPtr xt_create_thread(c_char *name, xtBool main_thread, xtBool temp_thread, XTExceptionPtr e); +XTThreadPtr xt_create_daemon(XTThreadPtr parent, c_char *name); +void xt_free_thread(XTThreadPtr self); +void xt_set_thread_data(XTThreadPtr self, void *data, XTThreadFreeFunc free_func); +pthread_t xt_run_thread(XTThreadPtr parent, XTThreadPtr child, void *(*start_routine)(XTThreadPtr)); +void xt_exit_thread(XTThreadPtr self, void *result); +void *xt_wait_for_thread(xtThreadID tid, xtBool ignore_error); +void xt_signal_all_threads(XTThreadPtr self, int sig); +void xt_do_to_all_threads(XTThreadPtr self, void (*do_func_ptr)(XTThreadPtr self, XTThreadPtr to_thr, void *thunk), void *thunk); +void xt_kill_thread(pthread_t t1); +XTThreadPtr xt_get_self(void); +void xt_set_self(XTThreadPtr self); +void xt_wait_for_all_threads(XTThreadPtr self); +void xt_busy_wait(void); +void xt_critical_wait(void); +void xt_yield(void); +void xt_sleep_milli_second(u_int t); +xtBool xt_suspend(XTThreadPtr self); +xtBool xt_unsuspend(XTThreadPtr self, XTThreadPtr target); +void xt_lock_thread(XTThreadPtr thread); +void xt_unlock_thread(XTThreadPtr thread); +xtBool xt_wait_thread(XTThreadPtr thread); +void xt_signal_thread(XTThreadPtr target); +void xt_terminate_thread(XTThreadPtr self, XTThreadPtr target); +xtProcID xt_getpid(); +xtBool xt_process_exists(xtProcID pid); + +#ifdef XT_THREAD_LOCK_INFO +#define xt_init_rwlock_with_autoname(a,b) xt_init_rwlock(a,b,LOCKLIST_ARG_SUFFIX(b)) +xtBool xt_init_rwlock(XTThreadPtr self, xt_rwlock_type *rwlock, const char *name); +#else +#define xt_init_rwlock_with_autoname(a,b) xt_init_rwlock(a,b) +xtBool xt_init_rwlock(XTThreadPtr self, xt_rwlock_type *rwlock); +#endif + +void xt_free_rwlock(xt_rwlock_type *rwlock); +xt_rwlock_type *xt_slock_rwlock(XTThreadPtr self, xt_rwlock_type *rwlock); +xt_rwlock_type *xt_xlock_rwlock(XTThreadPtr self, xt_rwlock_type *rwlock); +void xt_unlock_rwlock(XTThreadPtr self, xt_rwlock_type *rwlock); + +xt_mutex_type *xt_new_mutex(XTThreadPtr self); +void xt_delete_mutex(XTThreadPtr self, xt_mutex_type *mx); +#ifdef XT_THREAD_LOCK_INFO +#define xt_init_mutex_with_autoname(a,b) xt_init_mutex(a,b,LOCKLIST_ARG_SUFFIX(b)) +xtBool xt_init_mutex(XTThreadPtr self, xt_mutex_type *mx, const char *name); +#else +#define xt_init_mutex_with_autoname(a,b) xt_init_mutex(a,b) +xtBool xt_init_mutex(XTThreadPtr self, xt_mutex_type *mx); +#endif +void xt_free_mutex(xt_mutex_type *mx); +xtBool xt_lock_mutex(XTThreadPtr self, xt_mutex_type *mx); +void xt_unlock_mutex(XTThreadPtr self, xt_mutex_type *mx); + +pthread_cond_t *xt_new_cond(XTThreadPtr self); +void xt_delete_cond(XTThreadPtr self, pthread_cond_t *cond); + +xtBool xt_init_cond(XTThreadPtr self, pthread_cond_t *cond); +void xt_free_cond(pthread_cond_t *cond); +xtBool xt_wait_cond(XTThreadPtr self, pthread_cond_t *cond, xt_mutex_type *mutex); +xtBool xt_timed_wait_cond(XTThreadPtr self, pthread_cond_t *cond, xt_mutex_type *mutex, u_long milli_sec); +xtBool xt_signal_cond(XTThreadPtr self, pthread_cond_t *cond); +void xt_broadcast_cond(XTThreadPtr self, pthread_cond_t *cond); +xtBool xt_broadcast_cond_ns(xt_cond_type *cond); + +xtBool xt_set_key(pthread_key_t key, const void *value, XTExceptionPtr e); +void *xt_get_key(pthread_key_t key); + +void xt_set_low_priority(XTThreadPtr self); +void xt_set_normal_priority(XTThreadPtr self); +void xt_set_high_priority(XTThreadPtr self); +void xt_set_priority(XTThreadPtr self, int priority); + +void xt_gather_statistics(XTStatisticsPtr stats); +u_llong xt_get_statistic(XTStatisticsPtr stats, struct XTDatabase *db, u_int rec_id); + +#define xt_timed_wait_cond_ns(a, b, c) xt_timed_wait_cond(NULL, a, b, c) + +#endif + diff --git a/storage/pbxt/src/trace_xt.cc b/storage/pbxt/src/trace_xt.cc new file mode 100644 index 00000000000..e5881cb6d12 --- /dev/null +++ b/storage/pbxt/src/trace_xt.cc @@ -0,0 +1,345 @@ +/* Copyright (c) 2005 PrimeBase Technologies GmbH + * + * PrimeBase XT + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You 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 + * + * 2005-02-07 Paul McCullagh + * + * H&G2JCtL + */ + +#include "xt_config.h" + +#include <stdio.h> +#include <stdarg.h> +#include <errno.h> +#include <stdlib.h> +#include <time.h> + +#include "trace_xt.h" +#include "pthread_xt.h" +#include "thread_xt.h" + +#ifdef DEBUG +//#define PRINT_TRACE +//#define RESET_AFTER_DUMP +#endif + +static xtBool trace_initialized = FALSE; +static xt_mutex_type trace_mutex; +static size_t trace_log_size; +static size_t trace_log_offset; +static size_t trace_log_end; +static char *trace_log_buffer; +static u_long trace_stat_count; +static FILE *trace_dump_file; +static xtBool trace_flush_dump = FALSE; + +#define DEFAULT_TRACE_LOG_SIZE (40*1024*1204) +#define MAX_PRINT_LEN 2000 + +xtPublic xtBool xt_init_trace(void) +{ + int err; + + err = xt_p_mutex_init_with_autoname(&trace_mutex, NULL); + if (err) { + xt_log_errno(XT_NS_CONTEXT, err); + trace_initialized = FALSE; + return FALSE; + } + trace_initialized = TRUE; + trace_log_buffer = (char *) malloc(DEFAULT_TRACE_LOG_SIZE+1); + if (!trace_log_buffer) { + xt_log_errno(XT_NS_CONTEXT, ENOMEM); + xt_exit_trace(); + return FALSE; + } + trace_log_size = DEFAULT_TRACE_LOG_SIZE; + trace_log_offset = 0; + trace_log_end = 0; + trace_stat_count = 0; + return TRUE; +} + +xtPublic void xt_exit_trace(void) +{ + if (trace_initialized) { +#ifdef DEBUG + xt_dump_trace(); +#endif + xt_free_mutex(&trace_mutex); + trace_initialized = FALSE; + if (trace_log_buffer) + free(trace_log_buffer); + trace_log_buffer = NULL; + trace_log_size = 0; + trace_log_offset = 0; + trace_log_end = 0; + trace_stat_count = 0; + } + if (trace_dump_file) { + fclose(trace_dump_file); + trace_dump_file = NULL; + } +} + +xtPublic void xt_print_trace(void) +{ + if (trace_log_offset) { + xt_lock_mutex_ns(&trace_mutex); + if (trace_log_end > trace_log_offset+1) { + trace_log_buffer[trace_log_end] = 0; + printf("%s", trace_log_buffer + trace_log_offset + 1); + } + trace_log_buffer[trace_log_offset] = 0; + printf("%s", trace_log_buffer); + trace_log_offset = 0; + trace_log_end = 0; + xt_unlock_mutex_ns(&trace_mutex); + } +} + +xtPublic void xt_dump_trace(void) +{ + FILE *fp; + + if (trace_log_offset) { + fp = fopen("pbxt.log", "w"); + + xt_lock_mutex_ns(&trace_mutex); + if (fp) { + if (trace_log_end > trace_log_offset+1) { + trace_log_buffer[trace_log_end] = 0; + fprintf(fp, "%s", trace_log_buffer + trace_log_offset + 1); + } + trace_log_buffer[trace_log_offset] = 0; + fprintf(fp, "%s", trace_log_buffer); + fclose(fp); + } + +#ifdef RESET_AFTER_DUMP + trace_log_offset = 0; + trace_log_end = 0; + trace_stat_count = 0; +#endif + xt_unlock_mutex_ns(&trace_mutex); + } + + if (trace_dump_file) { + xt_lock_mutex_ns(&trace_mutex); + if (trace_dump_file) { + fflush(trace_dump_file); + fclose(trace_dump_file); + trace_dump_file = NULL; + } + xt_unlock_mutex_ns(&trace_mutex); + } +} + +xtPublic void xt_trace(const char *fmt, ...) +{ + va_list ap; + size_t len; + + va_start(ap, fmt); + xt_lock_mutex_ns(&trace_mutex); + + if (trace_log_offset + MAX_PRINT_LEN > trace_log_size) { + /* Start at the beginning of the buffer again: */ + trace_log_end = trace_log_offset; + trace_log_offset = 0; + } + + len = (size_t) vsnprintf(trace_log_buffer + trace_log_offset, trace_log_size - trace_log_offset, fmt, ap); + trace_log_offset += len; + + xt_unlock_mutex_ns(&trace_mutex); + va_end(ap); + +#ifdef PRINT_TRACE + xt_print_trace(); +#endif +} + +xtPublic void xt_ttracef(XTThreadPtr self, char *fmt, ...) +{ + va_list ap; + size_t len; + + va_start(ap, fmt); + xt_lock_mutex_ns(&trace_mutex); + + if (trace_log_offset + MAX_PRINT_LEN > trace_log_size) { + trace_log_end = trace_log_offset; + trace_log_offset = 0; + } + + trace_stat_count++; + len = (size_t) sprintf(trace_log_buffer + trace_log_offset, "%lu %s: ", trace_stat_count, self->t_name); + trace_log_offset += len; + len = (size_t) vsnprintf(trace_log_buffer + trace_log_offset, trace_log_size - trace_log_offset, fmt, ap); + trace_log_offset += len; + + xt_unlock_mutex_ns(&trace_mutex); + va_end(ap); + +#ifdef PRINT_TRACE + xt_print_trace(); +#endif +} + +xtPublic void xt_ttraceq(XTThreadPtr self, char *query) +{ + size_t qlen = strlen(query), tlen; + char *ptr, *qptr; + + if (!self) + self = xt_get_self(); + + xt_lock_mutex_ns(&trace_mutex); + + if (trace_log_offset + qlen + 100 >= trace_log_size) { + /* Start at the beginning of the buffer again: */ + trace_log_end = trace_log_offset; + trace_log_offset = 0; + } + + trace_stat_count++; + tlen = (size_t) sprintf(trace_log_buffer + trace_log_offset, "%lu %s: ", trace_stat_count, self->t_name); + trace_log_offset += tlen; + + ptr = trace_log_buffer + trace_log_offset; + qlen = 0; + qptr = query; + while (*qptr) { + if (*qptr == '\n' || *qptr == '\r') + *ptr = ' '; + else + *ptr = *qptr; + if (*qptr == '\n' || *qptr == '\r' || *qptr == ' ') { + qptr++; + while (*qptr == '\n' || *qptr == '\r' || *qptr == ' ') + qptr++; + } + else + qptr++; + ptr++; + qlen++; + } + + trace_log_offset += qlen; + *(trace_log_buffer + trace_log_offset) = '\n'; + *(trace_log_buffer + trace_log_offset + 1) = '\0'; + trace_log_offset++; + + xt_unlock_mutex_ns(&trace_mutex); + +#ifdef PRINT_TRACE + xt_print_trace(); +#endif +} + +/* + * Returns the time in microseconds. + * (1/1000000 of a second) + */ +xtPublic xtWord8 xt_trace_clock(void) +{ + static xtWord8 trace_start_clock = 0; + xtWord8 now; + +#ifdef XT_WIN + now = ((xtWord8) GetTickCount()) * (xtWord8) 1000; +#else + struct timeval tv; + + gettimeofday(&tv, NULL); + now = (xtWord8) tv.tv_sec * (xtWord8) 1000000 + tv.tv_usec; +#endif + if (trace_start_clock) + return now - trace_start_clock; + trace_start_clock = now; + return 0; +} + +xtPublic char *xt_trace_clock_str(char *ptr) +{ + static char buffer[50]; + xtWord8 now = xt_trace_clock(); + + if (!ptr) + ptr = buffer; + + sprintf(ptr, "%d.%06d", (int) (now / (xtWord8) 1000000), (int) (now % (xtWord8) 1000000)); + return ptr; +} + +xtPublic char *xt_trace_clock_diff(char *ptr) +{ + static xtWord8 trace_last_clock = 0; + static char buffer[50]; + xtWord8 now = xt_trace_clock(); + + if (!ptr) + ptr = buffer; + + sprintf(ptr, "%d.%06d (%d)", (int) (now / (xtWord8) 1000000), (int) (now % (xtWord8) 1000000), (int) (now - trace_last_clock)); + trace_last_clock = now; + return ptr; +} + +xtPublic char *xt_trace_clock_diff(char *ptr, xtWord8 start_time) +{ + xtWord8 now = xt_trace_clock(); + + sprintf(ptr, "%d.%06d (%d)", (int) (now / (xtWord8) 1000000), (int) (now % (xtWord8) 1000000), (int) (now - start_time)); + return ptr; +} + + +xtPublic void xt_set_fflush(xtBool on) +{ + trace_flush_dump = on; +} + +xtPublic void xt_ftracef(char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + xt_lock_mutex_ns(&trace_mutex); + + if (!trace_dump_file) { + char buffer[100]; + + for (int i=1; ;i++) { + sprintf(buffer, "pbxt-dump-%d.log", i); + if (!xt_fs_exists(buffer)) { + trace_dump_file = fopen(buffer, "w"); + break; + } + } + } + + vfprintf(trace_dump_file, fmt, ap); + if (trace_flush_dump) + fflush(trace_dump_file); + + xt_unlock_mutex_ns(&trace_mutex); + va_end(ap); +} + diff --git a/storage/pbxt/src/trace_xt.h b/storage/pbxt/src/trace_xt.h new file mode 100644 index 00000000000..44cfa9945f1 --- /dev/null +++ b/storage/pbxt/src/trace_xt.h @@ -0,0 +1,49 @@ +/* Copyright (c) 2005 PrimeBase Technologies GmbH + * + * PrimeBase XT + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You 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 + * + * 2005-02-07 Paul McCullagh + * + * H&G2JCtL + */ +#ifndef __xt_trace_h__ +#define __xt_trace_h__ + +#include "xt_defs.h" + +xtBool xt_init_trace(void); +void xt_exit_trace(void); +void xt_dump_trace(void); +void xt_print_trace(void); + +void xt_trace(const char *fmt, ...); +void xt_ttraceq(struct XTThread *self, char *query); +void xt_ttracef(struct XTThread *self, char *fmt, ...); +xtWord8 xt_trace_clock(void); +char *xt_trace_clock_str(char *ptr); +char *xt_trace_clock_diff(char *ptr); +char *xt_trace_clock_diff(char *ptr, xtWord8 start_time); +void xt_set_fflush(xtBool on); +void xt_ftracef(char *fmt, ...); + +#define XT_DEBUG_TRACE(x) +#define XT_DISABLED_TRACE(x) +#ifdef DEBUG +//#define PBXT_HANDLER_TRACE +#endif + +#endif diff --git a/storage/pbxt/src/util_xt.cc b/storage/pbxt/src/util_xt.cc new file mode 100644 index 00000000000..6e1db1f5f73 --- /dev/null +++ b/storage/pbxt/src/util_xt.cc @@ -0,0 +1,414 @@ +/* Copyright (c) 2005 PrimeBase Technologies GmbH + * + * PrimeBase XT + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You 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 + * + * 2004-01-03 Paul McCullagh + * + * H&G2JCtL + */ + +#include "xt_config.h" + +#include <stdio.h> +#include <time.h> +#include <ctype.h> +#ifndef XT_WIN +#include <sys/param.h> +#endif + +#include "util_xt.h" +#include "strutil_xt.h" +#include "memory_xt.h" + +xtPublic int xt_comp_log_pos(xtLogID id1, off_t off1, xtLogID id2, off_t off2) +{ + if (id1 < id2) + return -1; + if (id1 > id2) + return 1; + if (off1 < off2) + return -1; + if (off1 > off2) + return 1; + return 0; +} + +/* + * This function returns the current time in micorsonds since + * 00:00:00 UTC, January 1, 1970. + * Currently it is accurate to the second :( + */ +xtPublic xtWord8 xt_time_now(void) +{ + xtWord8 ms; + + ms = (xtWord8) time(NULL); + ms *= 1000000; + return ms; +} + +xtPublic void xt_free_nothing(struct XTThread XT_UNUSED(*thr), void XT_UNUSED(*x)) +{ +} + +/* + * A file name has the form: + * <text>-<number>[.<ext>] + * This function return the number part as a + * u_long. + */ +xtPublic xtWord4 xt_file_name_to_id(char *file_name) +{ + u_long value = 0; + + if (file_name) { + char *num = file_name + strlen(file_name) - 1; + + while (num >= file_name && *num != '-') + num--; + num++; + if (isdigit(*num)) + sscanf(num, "%lu", &value); + } + return (xtWord4) value; +} + +/* + * now is moving forward. then is a static time in the + * future. What is the time difference? + * + * These variables can overflow. + */ +xtPublic int xt_time_difference(register xtWord4 now, register xtWord4 then) +{ + /* now is after then, so the now time has passed + * then. So we return a negative difference. + */ + if (now >= then) { + /* now has gone past then. If the difference is + * great, then we assume an overflow, and reverse! + */ + if ((now - then) > (xtWord4) 0xFFFFFFFF/2) + return (int) (0xFFFFFFFF - (now - then)); + + return (int) now - (int) then; + } + /* If now is before then, we check the difference. + * If the difference is very large, then we assume + * that now has gone past then, and overflowed. + */ + if ((then - now) > (xtWord4) 0xFFFFFFFF/2) + return - (int) (0xFFFFFFFF - (then - now)); + return then - now; +} + +xtPublic xtWord2 xt_get_checksum(xtWord1 *data, size_t len, u_int interval) +{ + register xtWord4 sum = 0, g; + xtWord1 *chk; + + chk = data + len - 1; + while (chk > data) { + sum = (sum << 4) + *chk; + if ((g = sum & 0xF0000000)) { + sum = sum ^ (g >> 24); + sum = sum ^ g; + } + chk -= interval; + } + return (xtWord2) (sum ^ (sum >> 16)); +} + +xtPublic xtWord1 xt_get_checksum1(xtWord1 *data, size_t len) +{ + register xtWord4 sum = 0, g; + xtWord1 *chk; + + chk = data + len - 1; + while (chk > data) { + sum = (sum << 4) + *chk; + if ((g = sum & 0xF0000000)) { + sum = sum ^ (g >> 24); + sum = sum ^ g; + } + chk--; + } + return (xtWord1) (sum ^ (sum >> 24) ^ (sum >> 16) ^ (sum >> 8)); +} + +/* + * --------------- Data Buffer ------------------ + */ + +xtPublic xtBool xt_db_set_size(struct XTThread *self, XTDataBufferPtr dbuf, size_t size) +{ + if (dbuf->db_size < size) { + if (!xt_realloc(self, (void **) &dbuf->db_data, size)) + return FAILED; + dbuf->db_size = size; + } + else if (!size) { + if (dbuf->db_data) + xt_free(self, dbuf->db_data); + dbuf->db_data = NULL; + dbuf->db_size = 0; + } + return OK; +} + +/* + * --------------- Data Buffer ------------------ + */ + +xtPublic xtBool xt_ib_alloc(struct XTThread *self, XTInfoBufferPtr ib, size_t size) +{ + if (!ib->ib_free) { + ib->ib_db.db_size = 0; + ib->ib_db.db_data = NULL; + } + if (size <= ib->ib_db.db_size) + return OK; + + if (size <= XT_IB_DEFAULT_SIZE) { + ib->ib_db.db_size = XT_IB_DEFAULT_SIZE; + ib->ib_db.db_data = ib->ib_data; + return OK; + } + + if (ib->ib_db.db_data == ib->ib_data) { + ib->ib_db.db_size = 0; + ib->ib_db.db_data = NULL; + } + + ib->ib_free = TRUE; + return xt_db_set_size(self, &ib->ib_db, size); +} + +void xt_ib_free(struct XTThread *self, XTInfoBufferPtr ib) +{ + if (ib->ib_free) { + xt_db_set_size(self, &ib->ib_db, 0); + ib->ib_free = FALSE; + } +} + +/* + * --------------- Basic List ------------------ + */ + +xtPublic xtBool xt_bl_set_size(struct XTThread *self, XTBasicListPtr bl, size_t size) +{ + if (bl->bl_size < size) { + if (!xt_realloc(self, (void **) &bl->bl_data, size * bl->bl_item_size)) + return FAILED; + bl->bl_size = size; + } + else if (!size) { + if (bl->bl_data) + xt_free(self, bl->bl_data); + bl->bl_data = NULL; + bl->bl_size = 0; + bl->bl_count = 0; + } + return OK; +} + +xtPublic xtBool xt_bl_dup(struct XTThread *self, XTBasicListPtr from_bl, XTBasicListPtr to_bl) +{ + to_bl->bl_item_size = from_bl->bl_item_size; + to_bl->bl_size = 0; + to_bl->bl_count = from_bl->bl_count; + to_bl->bl_data = NULL; + if (!xt_bl_set_size(self, to_bl, from_bl->bl_count)) + return FAILED; + memcpy(to_bl->bl_data, from_bl->bl_data, to_bl->bl_count * to_bl->bl_item_size); + return OK; +} + +xtPublic xtBool xt_bl_append(struct XTThread *self, XTBasicListPtr bl, void *value) +{ + if (bl->bl_count == bl->bl_size) { + if (!xt_bl_set_size(self, bl, bl->bl_count+1)) + return FAILED; + } + memcpy(&bl->bl_data[bl->bl_count * bl->bl_item_size], value, bl->bl_item_size); + bl->bl_count++; + return OK; +} + +xtPublic void *xt_bl_last_item(XTBasicListPtr bl) +{ + if (!bl->bl_count) + return NULL; + return &bl->bl_data[(bl->bl_count-1) * bl->bl_item_size]; +} + +xtPublic void *xt_bl_item_at(XTBasicListPtr bl, u_int i) +{ + if (i >= bl->bl_count) + return NULL; + return &bl->bl_data[i * bl->bl_item_size]; +} + +xtPublic void xt_bl_free(struct XTThread *self, XTBasicListPtr wl) +{ + xt_bl_set_size(self, wl, 0); +} + +/* + * --------------- Basic Queue ------------------ + */ + +xtPublic xtBool xt_bq_set_size(struct XTThread *self, XTBasicQueuePtr bq, size_t size) +{ + if (bq->bq_size < size) { + if (!xt_realloc(self, (void **) &bq->bq_data, size * bq->bq_item_size)) + return FAILED; + bq->bq_size = size; + } + else if (!size) { + if (bq->bq_data) + xt_free(self, bq->bq_data); + bq->bq_data = NULL; + bq->bq_size = 0; + bq->bq_front = 0; + bq->bq_back = 0; + } + return OK; +} + +xtPublic void *xt_bq_get(XTBasicQueuePtr bq) +{ + if (bq->bq_back == bq->bq_front) + return NULL; + return &bq->bq_data[bq->bq_back * bq->bq_item_size]; +} + +xtPublic void xt_bq_next(XTBasicQueuePtr bq) +{ + if (bq->bq_back < bq->bq_front) { + bq->bq_back++; + if (bq->bq_front == bq->bq_back) { + bq->bq_front = 0; + bq->bq_back = 0; + } + } +} + +xtPublic xtBool xt_bq_add(struct XTThread *self, XTBasicQueuePtr bq, void *value) +{ + if (bq->bq_front == bq->bq_size) { + if (bq->bq_back >= bq->bq_max_waste) { + bq->bq_front -= bq->bq_back; + memmove(bq->bq_data, &bq->bq_data[bq->bq_back * bq->bq_item_size], bq->bq_front * bq->bq_item_size); + bq->bq_back = 0; + } + else { + if (!xt_bq_set_size(self, bq, bq->bq_front+bq->bq_item_inc)) + return FAILED; + } + } + memcpy(&bq->bq_data[bq->bq_front * bq->bq_item_size], value, bq->bq_item_size); + bq->bq_front++; + return OK; +} + +xtPublic void xt_sb_free(struct XTThread *self, XTStringBufferPtr dbuf) +{ + xt_sb_set_size(self, dbuf, 0); +} + +xtPublic xtBool xt_sb_set_size(struct XTThread *self, XTStringBufferPtr dbuf, size_t size) +{ + if (dbuf->sb_size < size) { + if (!xt_realloc(self, (void **) &dbuf->sb_cstring, size)) + return FAILED; + dbuf->sb_size = size; + } + else if (!size) { + if (dbuf->sb_cstring) + xt_free(self, dbuf->sb_cstring); + dbuf->sb_cstring = NULL; + dbuf->sb_size = 0; + dbuf->sb_len = 0; + } + return OK; +} + +xtPublic xtBool xt_sb_concat_len(struct XTThread *self, XTStringBufferPtr dbuf, c_char *str, size_t len) +{ + if (!xt_sb_set_size(self, dbuf, dbuf->sb_len + len + 1)) + return FAILED; + memcpy(dbuf->sb_cstring + dbuf->sb_len, str, len); + dbuf->sb_len += len; + dbuf->sb_cstring[dbuf->sb_len] = 0; + return OK; +} + +xtPublic xtBool xt_sb_concat(struct XTThread *self, XTStringBufferPtr dbuf, c_char *str) +{ + return xt_sb_concat_len(self, dbuf, str, strlen(str)); +} + +xtPublic xtBool xt_sb_concat_char(struct XTThread *self, XTStringBufferPtr dbuf, int ch) +{ + if (!xt_sb_set_size(self, dbuf, dbuf->sb_len + 1 + 1)) + return FAILED; + dbuf->sb_cstring[dbuf->sb_len] = (char) ch; + dbuf->sb_len++; + dbuf->sb_cstring[dbuf->sb_len] = 0; + return OK; +} + +xtPublic xtBool xt_sb_concat_int8(struct XTThread *self, XTStringBufferPtr dbuf, xtInt8 val) +{ + char buffer[200]; + + sprintf(buffer, "%"PRId64, val); + return xt_sb_concat(self, dbuf, buffer); +} + +xtPublic char *xt_sb_take_cstring(XTStringBufferPtr sbuf) +{ + char *str = sbuf->sb_cstring; + + sbuf->sb_cstring = NULL; + sbuf->sb_size = 0; + sbuf->sb_len = 0; + return str; +} + +xtPublic xtBool xt_sb_concat_url_len(struct XTThread *self, XTStringBufferPtr dbuf, c_char *from, size_t len_from) +{ + if (!xt_sb_set_size(self, dbuf, dbuf->sb_len + len_from + 1)) + return FAILED; + while (len_from--) { + if (*from == '%' && len_from >= 2 && isxdigit(*(from+1)) && isxdigit(*(from+2))) { + unsigned char a = xt_hex_digit(*(from+1)); + unsigned char b = xt_hex_digit(*(from+2)); + dbuf->sb_cstring[dbuf->sb_len] = a << 4 | b; + from += 3; + } + else + dbuf->sb_cstring[dbuf->sb_len] = *from++; + dbuf->sb_len++; + } + dbuf->sb_cstring[dbuf->sb_len] = 0; + return OK; +} + + diff --git a/storage/pbxt/src/util_xt.h b/storage/pbxt/src/util_xt.h new file mode 100644 index 00000000000..bb5003b10fb --- /dev/null +++ b/storage/pbxt/src/util_xt.h @@ -0,0 +1,123 @@ +/* Copyright (c) 2005 PrimeBase Technologies GmbH + * + * PrimeBase XT + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You 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 + * + * 2004-01-03 Paul McCullagh + * + * H&G2JCtL + */ + +#ifndef __xt_xtutil_h__ +#define __xt_xtutil_h__ + +#include <stddef.h> + +#include "xt_defs.h" + +#define XT_CHECKSUM_1(sum) ((xtWord1) ((sum) ^ ((sum) >> 24) ^ ((sum) >> 16) ^ ((sum) >> 8))) +#define XT_CHECKSUM_2(sum) ((xtWord2) ((sum) ^ ((sum) >> 16))) +#define XT_CHECKSUM4_8(sum) ((xtWord4) (sum) ^ (xtWord4) ((sum) >> 32)) + +int xt_comp_log_pos(xtLogID id1, off_t off1, xtLogID id2, off_t off2); +xtWord8 xt_time_now(void); +void xt_free_nothing(struct XTThread *self, void *x); +xtWord4 xt_file_name_to_id(char *file_name); +xtBool xt_time_difference(register xtWord4 now, register xtWord4 then); +xtWord2 xt_get_checksum(xtWord1 *data, size_t len, u_int interval); +xtWord1 xt_get_checksum1(xtWord1 *data, size_t len); + +typedef struct XTDataBuffer { + size_t db_size; + xtWord1 *db_data; +} XTDataBufferRec, *XTDataBufferPtr; + +xtBool xt_db_set_size(struct XTThread *self, XTDataBufferPtr db, size_t size); + +#define XT_IB_DEFAULT_SIZE 512 + +typedef struct XTInfoBuffer { + xtBool ib_free; + XTDataBufferRec ib_db; + xtWord1 ib_data[XT_IB_DEFAULT_SIZE]; +} XTInfoBufferRec, *XTInfoBufferPtr; + +xtBool xt_ib_alloc(struct XTThread *self, XTInfoBufferPtr ib, size_t size); +void xt_ib_free(struct XTThread *self, XTInfoBufferPtr ib); + +typedef struct XTBasicList { + u_int bl_item_size; + u_int bl_size; + u_int bl_count; + xtWord1 *bl_data; +} XTBasicListRec, *XTBasicListPtr; + +xtBool xt_bl_set_size(struct XTThread *self, XTBasicListPtr wl, size_t size); +xtBool xt_bl_dup(struct XTThread *self, XTBasicListPtr from_bl, XTBasicListPtr to_bl); +xtBool xt_bl_append(struct XTThread *self, XTBasicListPtr wl, void *value); +void *xt_bl_last_item(XTBasicListPtr wl); +void *xt_bl_item_at(XTBasicListPtr wl, u_int i); +void xt_bl_free(struct XTThread *self, XTBasicListPtr wl); + +typedef struct XTBasicQueue { + u_int bq_item_size; + u_int bq_max_waste; + u_int bq_item_inc; + u_int bq_size; + u_int bq_front; + u_int bq_back; + xtWord1 *bq_data; +} XTBasicQueueRec, *XTBasicQueuePtr; + +xtBool xt_bq_set_size(struct XTThread *self, XTBasicQueuePtr wq, size_t size); +void *xt_bq_get(XTBasicQueuePtr wq); +void xt_bq_next(XTBasicQueuePtr wq); +xtBool xt_bq_add(struct XTThread *self, XTBasicQueuePtr wl, void *value); + +typedef struct XTStringBuffer { + size_t sb_size; + size_t sb_len; + char *sb_cstring; +} XTStringBufferRec, *XTStringBufferPtr; + +void xt_sb_free(struct XTThread *self, XTStringBufferPtr db); +xtBool xt_sb_set_size(struct XTThread *self, XTStringBufferPtr db, size_t size); +xtBool xt_sb_concat_len(struct XTThread *self, XTStringBufferPtr dbuf, c_char *str, size_t len); +xtBool xt_sb_concat(struct XTThread *self, XTStringBufferPtr dbuf, c_char *str); +xtBool xt_sb_concat_char(struct XTThread *self, XTStringBufferPtr dbuf, int ch); +xtBool xt_sb_concat_int8(struct XTThread *self, XTStringBufferPtr dbuf, xtInt8 val); +char *xt_sb_take_cstring(XTStringBufferPtr dbuf); +xtBool xt_sb_concat_url_len(struct XTThread *self, XTStringBufferPtr dbuf, c_char *str, size_t len); + +static inline size_t xt_align_size(size_t size, size_t align) +{ + register size_t diff = size % align; + + if (diff) + return size + align - diff; + return size; +} + +static inline off_t xt_align_offset(off_t size, size_t align) +{ + register off_t diff = size % (off_t) align; + + if (diff) + return size + align - diff; + return size; +} + +#endif diff --git a/storage/pbxt/src/win_inttypes.h b/storage/pbxt/src/win_inttypes.h new file mode 100644 index 00000000000..c8561939e54 --- /dev/null +++ b/storage/pbxt/src/win_inttypes.h @@ -0,0 +1,259 @@ +/* Copyright (C) 1997-2001, 2004, 2007 Free Software Foundation, Inc. + This file is part of the GNU C Library. + + The GNU C Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + The GNU C Library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with the GNU C Library; if not, write to the Free + Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA + 02111-1307 USA. */ + +/* + * ISO C99: 7.8 Format conversion of integer types <inttypes.h> + */ + +/* + * this is a reduced verion of the original linux inttypes.h file + */ + +#ifndef _INTTYPES_H +#define _INTTYPES_H 1 + +/* The ISO C99 standard specifies that these macros must only be + defined if explicitly requested. */ +#if !defined __cplusplus || defined __STDC_FORMAT_MACROS + +# if __WORDSIZE == 64 +# define __PRI64_PREFIX "l" +# define __PRIPTR_PREFIX "l" +# else +# define __PRI64_PREFIX "ll" +# define __PRIPTR_PREFIX +# endif + +/* Macros for printing format specifiers. */ + +/* Decimal notation. */ +# define PRId8 "d" +# define PRId16 "d" +# define PRId32 "d" +# define PRId64 __PRI64_PREFIX "d" + +# define PRIdLEAST8 "d" +# define PRIdLEAST16 "d" +# define PRIdLEAST32 "d" +# define PRIdLEAST64 __PRI64_PREFIX "d" + +# define PRIdFAST8 "d" +# define PRIdFAST16 __PRIPTR_PREFIX "d" +# define PRIdFAST32 __PRIPTR_PREFIX "d" +# define PRIdFAST64 __PRI64_PREFIX "d" + + +# define PRIi8 "i" +# define PRIi16 "i" +# define PRIi32 "i" +# define PRIi64 __PRI64_PREFIX "i" + +# define PRIiLEAST8 "i" +# define PRIiLEAST16 "i" +# define PRIiLEAST32 "i" +# define PRIiLEAST64 __PRI64_PREFIX "i" + +# define PRIiFAST8 "i" +# define PRIiFAST16 __PRIPTR_PREFIX "i" +# define PRIiFAST32 __PRIPTR_PREFIX "i" +# define PRIiFAST64 __PRI64_PREFIX "i" + +/* Octal notation. */ +# define PRIo8 "o" +# define PRIo16 "o" +# define PRIo32 "o" +# define PRIo64 __PRI64_PREFIX "o" + +# define PRIoLEAST8 "o" +# define PRIoLEAST16 "o" +# define PRIoLEAST32 "o" +# define PRIoLEAST64 __PRI64_PREFIX "o" + +# define PRIoFAST8 "o" +# define PRIoFAST16 __PRIPTR_PREFIX "o" +# define PRIoFAST32 __PRIPTR_PREFIX "o" +# define PRIoFAST64 __PRI64_PREFIX "o" + +/* Unsigned integers. */ +# define PRIu8 "u" +# define PRIu16 "u" +# define PRIu32 "u" +# define PRIu64 __PRI64_PREFIX "u" + +# define PRIuLEAST8 "u" +# define PRIuLEAST16 "u" +# define PRIuLEAST32 "u" +# define PRIuLEAST64 __PRI64_PREFIX "u" + +# define PRIuFAST8 "u" +# define PRIuFAST16 __PRIPTR_PREFIX "u" +# define PRIuFAST32 __PRIPTR_PREFIX "u" +# define PRIuFAST64 __PRI64_PREFIX "u" + +/* lowercase hexadecimal notation. */ +# define PRIx8 "x" +# define PRIx16 "x" +# define PRIx32 "x" +# define PRIx64 __PRI64_PREFIX "x" + +# define PRIxLEAST8 "x" +# define PRIxLEAST16 "x" +# define PRIxLEAST32 "x" +# define PRIxLEAST64 __PRI64_PREFIX "x" + +# define PRIxFAST8 "x" +# define PRIxFAST16 __PRIPTR_PREFIX "x" +# define PRIxFAST32 __PRIPTR_PREFIX "x" +# define PRIxFAST64 __PRI64_PREFIX "x" + +/* UPPERCASE hexadecimal notation. */ +# define PRIX8 "X" +# define PRIX16 "X" +# define PRIX32 "X" +# define PRIX64 __PRI64_PREFIX "X" + +# define PRIXLEAST8 "X" +# define PRIXLEAST16 "X" +# define PRIXLEAST32 "X" +# define PRIXLEAST64 __PRI64_PREFIX "X" + +# define PRIXFAST8 "X" +# define PRIXFAST16 __PRIPTR_PREFIX "X" +# define PRIXFAST32 __PRIPTR_PREFIX "X" +# define PRIXFAST64 __PRI64_PREFIX "X" + + +/* Macros for printing `intmax_t' and `uintmax_t'. */ +# define PRIdMAX __PRI64_PREFIX "d" +# define PRIiMAX __PRI64_PREFIX "i" +# define PRIoMAX __PRI64_PREFIX "o" +# define PRIuMAX __PRI64_PREFIX "u" +# define PRIxMAX __PRI64_PREFIX "x" +# define PRIXMAX __PRI64_PREFIX "X" + + +/* Macros for printing `intptr_t' and `uintptr_t'. */ +# define PRIdPTR __PRIPTR_PREFIX "d" +# define PRIiPTR __PRIPTR_PREFIX "i" +# define PRIoPTR __PRIPTR_PREFIX "o" +# define PRIuPTR __PRIPTR_PREFIX "u" +# define PRIxPTR __PRIPTR_PREFIX "x" +# define PRIXPTR __PRIPTR_PREFIX "X" + + +/* Macros for scanning format specifiers. */ + +/* Signed decimal notation. */ +# define SCNd8 "hhd" +# define SCNd16 "hd" +# define SCNd32 "d" +# define SCNd64 __PRI64_PREFIX "d" + +# define SCNdLEAST8 "hhd" +# define SCNdLEAST16 "hd" +# define SCNdLEAST32 "d" +# define SCNdLEAST64 __PRI64_PREFIX "d" + +# define SCNdFAST8 "hhd" +# define SCNdFAST16 __PRIPTR_PREFIX "d" +# define SCNdFAST32 __PRIPTR_PREFIX "d" +# define SCNdFAST64 __PRI64_PREFIX "d" + +/* Signed decimal notation. */ +# define SCNi8 "hhi" +# define SCNi16 "hi" +# define SCNi32 "i" +# define SCNi64 __PRI64_PREFIX "i" + +# define SCNiLEAST8 "hhi" +# define SCNiLEAST16 "hi" +# define SCNiLEAST32 "i" +# define SCNiLEAST64 __PRI64_PREFIX "i" + +# define SCNiFAST8 "hhi" +# define SCNiFAST16 __PRIPTR_PREFIX "i" +# define SCNiFAST32 __PRIPTR_PREFIX "i" +# define SCNiFAST64 __PRI64_PREFIX "i" + +/* Unsigned decimal notation. */ +# define SCNu8 "hhu" +# define SCNu16 "hu" +# define SCNu32 "u" +# define SCNu64 __PRI64_PREFIX "u" + +# define SCNuLEAST8 "hhu" +# define SCNuLEAST16 "hu" +# define SCNuLEAST32 "u" +# define SCNuLEAST64 __PRI64_PREFIX "u" + +# define SCNuFAST8 "hhu" +# define SCNuFAST16 __PRIPTR_PREFIX "u" +# define SCNuFAST32 __PRIPTR_PREFIX "u" +# define SCNuFAST64 __PRI64_PREFIX "u" + +/* Octal notation. */ +# define SCNo8 "hho" +# define SCNo16 "ho" +# define SCNo32 "o" +# define SCNo64 __PRI64_PREFIX "o" + +# define SCNoLEAST8 "hho" +# define SCNoLEAST16 "ho" +# define SCNoLEAST32 "o" +# define SCNoLEAST64 __PRI64_PREFIX "o" + +# define SCNoFAST8 "hho" +# define SCNoFAST16 __PRIPTR_PREFIX "o" +# define SCNoFAST32 __PRIPTR_PREFIX "o" +# define SCNoFAST64 __PRI64_PREFIX "o" + +/* Hexadecimal notation. */ +# define SCNx8 "hhx" +# define SCNx16 "hx" +# define SCNx32 "x" +# define SCNx64 __PRI64_PREFIX "x" + +# define SCNxLEAST8 "hhx" +# define SCNxLEAST16 "hx" +# define SCNxLEAST32 "x" +# define SCNxLEAST64 __PRI64_PREFIX "x" + +# define SCNxFAST8 "hhx" +# define SCNxFAST16 __PRIPTR_PREFIX "x" +# define SCNxFAST32 __PRIPTR_PREFIX "x" +# define SCNxFAST64 __PRI64_PREFIX "x" + + +/* Macros for scanning `intmax_t' and `uintmax_t'. */ +# define SCNdMAX __PRI64_PREFIX "d" +# define SCNiMAX __PRI64_PREFIX "i" +# define SCNoMAX __PRI64_PREFIX "o" +# define SCNuMAX __PRI64_PREFIX "u" +# define SCNxMAX __PRI64_PREFIX "x" + +/* Macros for scaning `intptr_t' and `uintptr_t'. */ +# define SCNdPTR __PRIPTR_PREFIX "d" +# define SCNiPTR __PRIPTR_PREFIX "i" +# define SCNoPTR __PRIPTR_PREFIX "o" +# define SCNuPTR __PRIPTR_PREFIX "u" +# define SCNxPTR __PRIPTR_PREFIX "x" + +#endif /* C++ && format macros */ + + +#endif /* inttypes.h */ diff --git a/storage/pbxt/src/xaction_xt.cc b/storage/pbxt/src/xaction_xt.cc new file mode 100644 index 00000000000..be3cb650bcb --- /dev/null +++ b/storage/pbxt/src/xaction_xt.cc @@ -0,0 +1,2685 @@ +/* Copyright (c) 2005 PrimeBase Technologies GmbH + * + * PrimeBase XT + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You 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 + * + * 2005-04-10 Paul McCullagh + * + * H&G2JCtL + */ + +#include "xt_config.h" + +#include <time.h> +#include <signal.h> + +#include "xaction_xt.h" +#include "database_xt.h" +#include "strutil_xt.h" +#include "heap_xt.h" +#include "trace_xt.h" +#include "myxt_xt.h" +#include "tabcache_xt.h" + +#ifdef DEBUG +//#define TRACE_WAIT_FOR +//#define TRACE_VARIATIONS +//#define TRACE_SWEEPER_ACTIVITY + +/* Enable to trace the statements executed by the engine: */ +//#define TRACE_STATEMENTS +#endif + +#if defined(TRACE_STATEMENTS) || defined(TRACE_VARIATIONS) +#define TRACE_TRANSACTION +#endif + +static void xn_sw_wait_for_xact(XTThreadPtr self, XTDatabaseHPtr db, u_int hsecs); +static xtBool xn_get_xact_details(XTDatabaseHPtr db, xtXactID xn_id, XTThreadPtr thread __attribute__((unused)), int *flags, xtXactID *start, xtXactID *end, xtThreadID *thd_id); +static xtBool xn_get_xact_pointer(XTDatabaseHPtr db, xtXactID xn_id, XTXactDataPtr *xact_ptr); + +/* ============================================================================================== */ + +typedef struct XNSWRecItem { + xtTableID ri_tab_id; + xtRecordID ri_rec_id; +} XNSWRecItemRec, *XNSWRecItemPtr; + +typedef struct XNSWToFreeItem { + xtTableID ri_tab_id; /* If non-zero, then this is the table of the data record to be freed. + * If zero, then this free the transaction below must be freed. + */ + union { + xtRecordID ri_rec_id; + xtXactID ri_xn_id; + } x; + xtXactID ri_wait_xn_id; /* Wait for this transaction to be cleaned (or being cleaned up) + * before freeing this resource. */ +} XNSWToFreeItemRec, *XNSWToFreeItemPtr; + +/* ---------------------------------------------------------------------- + * TRANSACTION/THREAD WAIT LIST + */ + +typedef struct XNWaitThread { + /* The wait condition of the thread. */ + xt_mutex_type wt_lock; + xt_cond_type wt_cond; + + /* The list of threads waiting for this thread. */ + XTSpinLockRec wt_wait_list_lock; + u_int wt_wait_list_count; + u_int wt_wait_list_size; + xtThreadID *wt_wait_list; +} XNWaitThreadRec, *XNWaitThreadPtr; + +static XNWaitThreadPtr xn_wait_thread_array; + +xtPublic void xt_thread_wait_init(XTThreadPtr self) +{ + xn_wait_thread_array = (XNWaitThreadPtr) xt_calloc(self, xt_thr_maximum_threads * sizeof(XNWaitThreadRec)); + for (u_int i=0; i<xt_thr_maximum_threads; i++) { + xt_init_mutex_with_autoname(self, &xn_wait_thread_array[i].wt_lock); + xt_init_cond(self, &xn_wait_thread_array[i].wt_cond); + xn_wait_thread_array[i].wt_wait_list = NULL; + xn_wait_thread_array[i].wt_wait_list_count = 0; + xn_wait_thread_array[i].wt_wait_list_size = 0; + xt_spinlock_init_with_autoname(self, &xn_wait_thread_array[i].wt_wait_list_lock); + } +} + +xtPublic void xt_thread_wait_exit(XTThreadPtr self) +{ + if (xn_wait_thread_array) { + for (u_int i=0; i<xt_thr_maximum_threads; i++) { + xt_free_mutex(&xn_wait_thread_array[i].wt_lock); + xt_free_cond(&xn_wait_thread_array[i].wt_cond); + if (xn_wait_thread_array[i].wt_wait_list) + xt_free(self, xn_wait_thread_array[i].wt_wait_list); + xt_spinlock_free(self, &xn_wait_thread_array[i].wt_wait_list_lock); + } + xt_free(self, xn_wait_thread_array); + } +} + +static xtBool xn_wait_for_thread(xtThreadID waiting_id, xtThreadID wait_for_id) +{ + XNWaitThreadPtr wt; + + wt = &xn_wait_thread_array[wait_for_id]; + xt_spinlock_lock(&wt->wt_wait_list_lock); + if (wt->wt_wait_list_count == wt->wt_wait_list_size) { + if (!xt_realloc_ns((void **) &wt->wt_wait_list, (wt->wt_wait_list_size+1) * sizeof(xtThreadID))) + return FAILED; + wt->wt_wait_list_size++; + } + for (u_int i=0; i<wt->wt_wait_list_count; i++) { + if (wt->wt_wait_list[i] == waiting_id) + goto done; + } + wt->wt_wait_list[wt->wt_wait_list_count] = waiting_id; + wt->wt_wait_list_count++; + done: + xt_spinlock_unlock(&wt->wt_wait_list_lock); + return OK; +} + +xtPublic void xt_xn_wakeup_thread(xtThreadID thd_id) +{ + XNWaitThreadPtr target_wt; + + target_wt = &xn_wait_thread_array[thd_id]; + xt_lock_mutex_ns(&target_wt->wt_lock); + xt_broadcast_cond_ns(&target_wt->wt_cond); + xt_unlock_mutex_ns(&target_wt->wt_lock); +} + +xtPublic void xt_xn_wakeup_thread_list(XTThreadPtr thread) +{ + XNWaitThreadPtr target_wt; + + for (u_int i=0; i<thread->st_thread_list_count; i++) { + target_wt = &xn_wait_thread_array[thread->st_thread_list[i]]; + xt_lock_mutex_ns(&target_wt->wt_lock); + xt_broadcast_cond_ns(&target_wt->wt_cond); + xt_unlock_mutex_ns(&target_wt->wt_lock); + } + thread->st_thread_list_count = 0; +} + +xtPublic void xt_xn_wakeup_waiting_threads(XTThreadPtr thread) +{ + XNWaitThreadPtr wt; + XNWaitThreadPtr target_wt; + + wt = &xn_wait_thread_array[thread->t_id]; + if (!wt->wt_wait_list_count) + return; + + xt_spinlock_lock(&wt->wt_wait_list_lock); + if (thread->st_thread_list_size < wt->wt_wait_list_count) { + if (!xt_realloc_ns((void **) &thread->st_thread_list, wt->wt_wait_list_count * sizeof(xtThreadID))) + goto failed; + thread->st_thread_list_size = wt->wt_wait_list_count; + } + memcpy(thread->st_thread_list, wt->wt_wait_list, wt->wt_wait_list_count * sizeof(xtThreadID)); + thread->st_thread_list_count = wt->wt_wait_list_count; + wt->wt_wait_list_count = 0; + xt_spinlock_unlock(&wt->wt_wait_list_lock); + + xt_xn_wakeup_thread_list(thread); + return; + + failed: + for (u_int i=0; i<wt->wt_wait_list_count; i++) { + target_wt = &xn_wait_thread_array[wt->wt_wait_list[i]]; + xt_lock_mutex_ns(&target_wt->wt_lock); + xt_broadcast_cond_ns(&target_wt->wt_cond); + xt_unlock_mutex_ns(&target_wt->wt_lock); + } + wt->wt_wait_list_count = 0; + xt_spinlock_unlock(&wt->wt_wait_list_lock); +} + +/* ---------------------------------------------------------------------- + * WAIT FOR TRANSACTIONS + */ + +typedef struct XNWaitFor { + xtXactID wf_waiting_xn_id; /* The transaction of the waiting thread. */ + xtXactID wf_for_me_xn_id; /* The transaction we are waiting for. */ +} XNWaitForRec, *XNWaitForPtr; + +static int xn_compare_wait_for(XTThreadPtr XT_UNUSED(self), register const void XT_UNUSED(*thunk), register const void *a, register const void *b) +{ + xtXactID *x = (xtXactID *) a; + XNWaitForPtr y = (XNWaitForPtr) b; + + if (*x == y->wf_waiting_xn_id) + return 0; + if (xt_xn_is_before(*x, y->wf_waiting_xn_id)) + return -1; + return 1; +} + +static void xn_free_wait_for(XTThreadPtr XT_UNUSED(self), void XT_UNUSED(*thunk), void XT_UNUSED(*item)) +{ +} + +/* + * A deadlock occurs when a transaction is waiting for itself! + * For example A is waiting for B which is waiting for A. + * By repeatedly scanning the wait_for list we can find out if a + * transaction is waiting for itself. + */ +static xtBool xn_detect_deadlock(XTDatabaseHPtr db, xtXactID waiting, xtXactID for_me) +{ + XNWaitForPtr wf; + + for (;;) { + if (waiting == for_me) { +#ifdef TRACE_WAIT_FOR + for (u_int i=0; i<xt_sl_get_size(db->db_xn_wait_for); i++) { + wf = (XNWaitForPtr) xt_sl_item_at(db->db_xn_wait_for, i); + xt_trace("T%lu --> T%lu\n", (u_long) wf->wf_waiting_xn_id, (u_long) wf->wf_for_me_xn_id); + } + xt_ttracef(xt_get_self(), "DEADLOCK\n"); + xt_dump_trace(); +#endif + xt_register_xterr(XT_REG_CONTEXT, XT_ERR_DEADLOCK); + return TRUE; + } + if (!(wf = (XNWaitForPtr) xt_sl_find(NULL, db->db_xn_wait_for, &for_me))) + break; + for_me = wf->wf_for_me_xn_id; + } + return FALSE; +} + +#ifdef XT_USE_SPINLOCK_WAIT_FOR + +#if defined(XT_MAC) || defined(XT_WIN) +#define WAIT_SPIN_COUNT 10 +#else +#define WAIT_SPIN_COUNT 50 +#endif + +/* Should not be required, but we wait for a second, + * just in case the wakeup is missed! + */ +#ifdef DEBUG +#define WAIT_FOR_XACT_TIME 30000 +#else +#define WAIT_FOR_XACT_TIME 1000 +#endif + +static xtBool xn_add_to_wait_for(XTDatabaseHPtr db, XNWaitForPtr wf, XTThreadPtr thread) +{ + /* If we are waiting for a transaction to end, + * put this thread on the wait list... + * + * As long as the temporary lock is removed + * or turned into a permanent lock before + * a thread waits again, all should be OK! + */ + xt_spinlock_lock(&db->db_xn_wait_spinlock); + +#ifdef TRACE_WAIT_FOR + xt_ttracef(thread, "T%lu -wait-> T%lu\n", (u_long) thread->st_xact_data->xd_start_xn_id, (u_long) wait_xn_id); +#endif + /* Check for a deadlock: */ + if (xn_detect_deadlock(db, wf->wf_waiting_xn_id, wf->wf_for_me_xn_id)) + goto failed; + + /* We will wait for this transaction... */ + db->db_xn_wait_count++; + if (thread->st_xact_writer) + db->db_xn_writer_wait_count++; + + if (!xt_sl_insert(NULL, db->db_xn_wait_for, &wf->wf_waiting_xn_id, wf)) { + db->db_xn_wait_count--; + goto failed; + } + + xt_spinlock_unlock(&db->db_xn_wait_spinlock); + return OK; + + failed: + xt_spinlock_unlock(&db->db_xn_wait_spinlock); + return FAILED; +} + +inline void xn_remove_from_wait_for(XTDatabaseHPtr db, XNWaitForPtr wf, XTThreadPtr thread) +{ + xt_spinlock_lock(&db->db_xn_wait_spinlock); + + xt_sl_delete(NULL, db->db_xn_wait_for, &wf->wf_waiting_xn_id); + db->db_xn_wait_count--; + if (thread->st_xact_writer) + db->db_xn_writer_wait_count--; + +#ifdef TRACE_WAIT_FOR + xt_ttracef(thread, "T%lu -wait-> T%lu FAILED\n", (u_long) thread->st_xact_data->xd_start_xn_id, (u_long) wait_xn_id); +#endif + xt_spinlock_unlock(&db->db_xn_wait_spinlock); +} + +/* Wait for a transation to terminate or a lock to be granted. + * + * If term_req is TRUE, then the termination of the transaction is required + * before continuing. + * + * If pw_func is set then this function will not return before this call has + * succeeded. + * + * This function returns FAILE on error. + */ +xtPublic xtBool xt_xn_wait_for_xact(XTThreadPtr thread, XTXactWaitPtr xw, XTLockWaitPtr lw) +{ + XTDatabaseHPtr db = thread->st_database; + XNWaitForRec wf; + int flags = 0; + xtXactID start = 0; + XTXactDataPtr wait_xact_ptr; + xtBool on_wait_list = FALSE; + XTXactWaitRec xw_new; + u_int loop_count = 0; + XNWaitThreadPtr my_wt; + + ASSERT_NS(thread->st_xact_data); + thread->st_statistics.st_wait_for_xact++; + + wf.wf_waiting_xn_id = thread->st_xact_data->xd_start_xn_id; + + if (lw) { + /* If we are here, then the lw structure is on the wait + * queue for the given lock. + */ + xtXactID locking_xn_id; + + wait_for_locker: + locking_xn_id = lw->lw_xn_id; + wf.wf_for_me_xn_id = lw->lw_xn_id; + if (!xn_add_to_wait_for(db, &wf, thread)) { + lw->lw_ot->ot_table->tab_locks.xt_cancel_temp_lock(lw); + return FAILED; + } + + while (loop_count < WAIT_SPIN_COUNT) { + loop_count++; + + switch (lw->lw_curr_lock) { + case XT_LOCK_ERR: + xn_remove_from_wait_for(db, &wf, thread); + return FAILED; + case XT_NO_LOCK: + /* Got the lock: */ + /* Check if we must also wait for the transaction: */ + if (lw->lw_row_updated) { + /* This will override the xw passed in. + * The reason is, because we are actually waiting + * for a lock, and the lock owner may have changed + * while we were waiting for the lock. + */ + xw_new.xw_xn_id = lw->lw_updating_xn_id; + xw = &xw_new; + } + if (xw) { + if (wf.wf_for_me_xn_id == xw->xw_xn_id) + on_wait_list = TRUE; + else + xn_remove_from_wait_for(db, &wf, thread); + goto wait_for_xact; + } + xn_remove_from_wait_for(db, &wf, thread); + return OK; + case XT_TEMP_LOCK: + case XT_PERM_LOCK: + if (locking_xn_id != lw->lw_xn_id) { + /* Change the transaction that we are waiting for: */ + xn_remove_from_wait_for(db, &wf, thread); + goto wait_for_locker; + } + break; + } + + xt_critical_wait(); + } + + + /* The non-spinning version... */ + wait_for_locker_no_spin: + my_wt = &xn_wait_thread_array[thread->t_id]; + xt_lock_mutex_ns(&my_wt->wt_lock); + + for (;;) { + switch (lw->lw_curr_lock) { + case XT_LOCK_ERR: + xt_unlock_mutex_ns(&my_wt->wt_lock); + xn_remove_from_wait_for(db, &wf, thread); + return FAILED; + case XT_NO_LOCK: + xt_unlock_mutex_ns(&my_wt->wt_lock); + if (lw->lw_row_updated) { + xw_new.xw_xn_id = lw->lw_updating_xn_id; + xw = &xw_new; + } + if (xw) { + if (wf.wf_for_me_xn_id == xw->xw_xn_id) + on_wait_list = TRUE; + else + xn_remove_from_wait_for(db, &wf, thread); + goto wait_for_xact; + } + xn_remove_from_wait_for(db, &wf, thread); + return OK; + case XT_TEMP_LOCK: + case XT_PERM_LOCK: + if (locking_xn_id != lw->lw_xn_id) { + /* Change the transaction that we are waiting for: */ + xt_unlock_mutex_ns(&my_wt->wt_lock); + xn_remove_from_wait_for(db, &wf, thread); + locking_xn_id = lw->lw_xn_id; + wf.wf_for_me_xn_id = lw->lw_xn_id; + if (!xn_add_to_wait_for(db, &wf, thread)) { + lw->lw_ot->ot_table->tab_locks.xt_cancel_temp_lock(lw); + return FAILED; + } + goto wait_for_locker_no_spin; + } + break; + } + + xt_timed_wait_cond_ns(&my_wt->wt_cond, &my_wt->wt_lock, WAIT_FOR_XACT_TIME); + } + + xt_unlock_mutex_ns(&my_wt->wt_lock); + } + + if (xw) { + xtThreadID tn_thd_id; + + wait_for_xact: + wf.wf_for_me_xn_id = xw->xw_xn_id; + + if (!xn_get_xact_pointer(db, xw->xw_xn_id, &wait_xact_ptr)) + /* The transaction was not found... */ + goto wait_done; + + if (wait_xact_ptr) { + /* This is a dirty read, but it should work! */ + flags = wait_xact_ptr->xd_flags; + start = wait_xact_ptr->xd_start_xn_id; + tn_thd_id = wait_xact_ptr->xd_thread_id; + } + else { + tn_thd_id = 0; + if (!xn_get_xact_details(db, xw->xw_xn_id, thread, &flags, &start, NULL, &tn_thd_id)) + flags = XT_XN_XAC_ENDED | XT_XN_XAC_SWEEP; + } + + if ((flags & XT_XN_XAC_ENDED) || start != xw->xw_xn_id) + /* The transaction has terminated! */ + goto wait_done; + + /* Tell the thread we are waiting for it: */ + xn_wait_for_thread(thread->t_id, tn_thd_id); + + if (!on_wait_list) { + if (!xn_add_to_wait_for(db, &wf, thread)) + return FAILED; + on_wait_list = TRUE; + } + + /* The spinning version: */ + while (loop_count < WAIT_SPIN_COUNT) { + loop_count++; + + xt_critical_wait(); + + if (wait_xact_ptr) { + /* This is a dirty read, but it should work! */ + flags = wait_xact_ptr->xd_flags; + start = wait_xact_ptr->xd_start_xn_id; + } + else { + if (!xn_get_xact_details(db, xw->xw_xn_id, thread, &flags, &start, NULL, NULL)) + flags = XT_XN_XAC_ENDED | XT_XN_XAC_SWEEP; + } + + if ((flags & XT_XN_XAC_ENDED) || start != xw->xw_xn_id) + /* The transaction has terminated! */ + goto wait_done; + } + + /* The non-spinning version: + * + * I believe I can avoid missing the wakeup signal + * by locking before we check if the transaction + * is still running. + * + * Even though db->db_xn_wait_on_cond is "dirty read". + * + * The reason is, before the signal is sent the + * lock is also aquired. This is not possible until + * this thread is safely sleaping. + */ + my_wt = &xn_wait_thread_array[thread->t_id]; + xt_lock_mutex_ns(&my_wt->wt_lock); + + for (;;) { + if (wait_xact_ptr) { + /* This is a dirty read, but it should work! */ + flags = wait_xact_ptr->xd_flags; + start = wait_xact_ptr->xd_start_xn_id; + } + else { + if (!xn_get_xact_details(db, xw->xw_xn_id, thread, &flags, &start, NULL, NULL)) + flags = XT_XN_XAC_ENDED | XT_XN_XAC_SWEEP; + } + + if ((flags & XT_XN_XAC_ENDED) || start != xw->xw_xn_id) + /* The transaction has terminated! */ + break; + + xt_timed_wait_cond_ns(&my_wt->wt_cond, &my_wt->wt_lock, WAIT_FOR_XACT_TIME); + } + + xt_unlock_mutex_ns(&my_wt->wt_lock); + + wait_done: + if (on_wait_list) + xn_remove_from_wait_for(db, &wf, thread); + } + + return OK; +} + +#else // XT_USE_SPINLOCK_WAIT_FOR +/* + * The given thread must wait for the specified transaction to terminate. This + * function places the transaction of the thread on a list of waiting threads. + * + * Before waiting we make a check for deadlocks. A deadlock occurs + * if waiting would introduce a cycle. + */ +xtPublic xtBool old_xt_xn_wait_for_xact(XTThreadPtr thread, xtXactID xn_id, xtBool will_retry, XTLockWaitFuncPtr pw_func, XTLockWaitPtr pw_data) +{ + XTDatabaseHPtr db = thread->st_database; + XNWaitForRec wf; + int flags = 0; + xtXactID start = 0; + + ASSERT_NS(thread->st_xact_data); + + thread->st_statistics.st_wait_for_xact++; + wf.wf_waiting_xn_id = thread->st_xact_data->xd_start_xn_id; + wf.wf_for_me_xn_id = xn_id; + wf.wf_thread_id = thread->t_id; + + xt_lock_mutex_ns(&db->db_xn_wait_lock); + +#ifdef TRACE_WAIT_FOR + xt_ttracef(thread, "T%lu -wait-> T%lu\n", (u_long) thread->st_xact_data->xd_start_xn_id, (u_long) xn_id); +#endif + for (;;) { + if (!xn_get_xact_details(db, xn_id, thread, &flags, &start, NULL, NULL)) + break; + + /* This is a dirty read, but it should work! */ + if ((flags & XT_XN_XAC_ENDED) || start != xn_id) + break; + + if (xn_detect_deadlock(db, wf.wf_waiting_xn_id, wf.wf_for_me_xn_id)) + goto failed; + + /* We will wait for this transaction... */ + db->db_xn_wait_count++; + if (thread->st_xact_writer) + db->db_xn_writer_wait_count++; + + if (!xt_sl_insert(NULL, db->db_xn_wait_for, &wf.wf_waiting_xn_id, &wf)) { + db->db_xn_wait_count--; + goto failed; + } + + if (!xn_get_xact_details(db, xn_id, thread, &flags, &start, NULL, NULL)) { + xt_sl_delete(NULL, db->db_xn_wait_for, &wf.wf_waiting_xn_id); + db->db_xn_wait_count--; + if (thread->st_xact_writer) + db->db_xn_writer_wait_count--; + break; + } + + if ((flags & XT_XN_XAC_ENDED) || start != xn_id) { + xt_sl_delete(NULL, db->db_xn_wait_for, &wf.wf_waiting_xn_id); + db->db_xn_wait_count--; + if (thread->st_xact_writer) + db->db_xn_writer_wait_count--; + break; + } + + db->db_xn_post_wait[thread->t_id].pw_call_me = pw_func; + db->db_xn_post_wait[thread->t_id].pw_thread = thread; + db->db_xn_post_wait[thread->t_id].pw_data = pw_data; + + /* Timed wait because it is possible that transaction quits before + * we go to sleep. + */ + if (!xt_timed_wait_cond(NULL, &db->db_xn_wait_cond, &db->db_xn_wait_lock, 2 * 1000)) { + xt_sl_delete(NULL, db->db_xn_wait_for, &wf.wf_waiting_xn_id); + db->db_xn_wait_count--; + if (thread->st_xact_writer) + db->db_xn_writer_wait_count--; + goto failed; + } + + db->db_xn_post_wait[thread->t_id].pw_call_me = NULL; + xt_sl_delete(NULL, db->db_xn_wait_for, &wf.wf_waiting_xn_id); + db->db_xn_wait_count--; + if (thread->st_xact_writer) + db->db_xn_writer_wait_count--; + + if (will_retry) + break; + } + +#ifdef TRACE_WAIT_FOR + xt_ttracef(thread, "T%lu -wait-> T%lu DONE\n", (u_long) thread->st_xact_data->xd_start_xn_id, (u_long) xn_id); +#endif + xt_unlock_mutex_ns(&db->db_xn_wait_lock); + return OK; + + failed: +#ifdef TRACE_WAIT_FOR + xt_ttracef(self, "T%lu -wait-> T%lu FAILED\n", (u_long) self->st_xact_data->xd_start_xn_id, (u_long) xn_id); +#endif + xt_unlock_mutex_ns(&db->db_xn_wait_lock); + return FAILED; +} + +xtPublic void old_xt_xn_wakeup_transactions(XTDatabaseHPtr db, XTThreadPtr thread) +{ + u_int len; + XNWaitForPtr wf; + + xt_lock_mutex_ns(&db->db_xn_wait_lock); + /* The idea here is to release the oldest transactions + * first. Although this may not be completely fair + * it has the advantage that older transactions are + * encouraged to complete first. + * + * I have found the following problem with this test: + * runTest(INCREMENT_TEST, 16, INCREMENT_TEST_UPDATE_COUNT); + * with a bit of bad luck a transaction can be starved. + * This results in the sweeper stalling because it is + * waiting for an old transaction to quite so that + * it continue. + * + * Because the sweeper is waiting, the number of + * versions of the record to be updated + * begins to increase. In the above test over + * 1600 transaction remain uncleaned. + * + * This means that there are 1600 version of the + * row which must be scanned to find the most + * recent version. + */ + if ((len = (u_int) xt_sl_get_size(db->db_xn_wait_for))) { + for (u_int i=0; i<len; i++) { + wf = (XNWaitForPtr) xt_sl_item_at(db->db_xn_wait_for, i); + if (db->db_xn_post_wait[wf->wf_thread_id].pw_call_me) { + if (db->db_xn_post_wait[wf->wf_thread_id].pw_call_me(thread, &db->db_xn_post_wait[wf->wf_thread_id])) + db->db_xn_post_wait[wf->wf_thread_id].pw_call_me = NULL; + } + } + if (!xt_broadcast_cond_ns(&db->db_xn_wait_cond)) + xt_log_and_clear_exception_ns(); + } + ASSERT_NS(db->db_xn_wait_count == len); + xt_unlock_mutex_ns(&db->db_xn_wait_lock); +} +#endif // XT_USE_SPINLOCK_WAIT_FOR + +/* ---------------------------------------------------------------------- + * Utilities + */ + +//#define HIGH_X +#ifdef HIGH_X +u_long tot_alloced; +u_long high_alloced; +u_long not_clean_max; +u_long in_ram_max; +#endif + +static void xn_free_xact(XTDatabaseHPtr db, XTXactSegPtr seg, XTXactDataPtr xact) +{ +#ifdef HIGH_X + tot_alloced--; +#endif + /* This indicates the structure is free: */ + xact->xd_start_xn_id = 0; + if ((xtWord1 *) xact >= db->db_xn_data && (xtWord1 *) xact < db->db_xn_data_end) { + /* Put it in the free list: */ + xact->xd_next_xact = seg->xs_free_list; + seg->xs_free_list = xact; + return; + } + xt_free_ns(xact); +} + +/* + * GOTCHA: The value db->db_xn_curr_id may be a bit larger + * than the actual transaction created because there is + * a gap between the issude of the transaction ID + * and the creation of a memory structure. + * (indicated here: {GAP-INC-ADD-XACT}) + * + * This function returns the actuall current transaction ID. + * This is the number of the last transaction actually + * created in memory. + * + * This means that if you call xt_xn_get_xact() with any + * number less than or equal to this value, not finding + * the transaction means it has already ended! + */ +xtPublic xtXactID xt_xn_get_curr_id(XTDatabaseHPtr db) +{ + int i; + xtXactID curr_xn_id; + register XTXactSegPtr seg = db->db_xn_idx; + + /* Find the highest transaction ID actually created... */ + curr_xn_id = seg->xs_last_xn_id; + seg++; + for (i=1; i<XT_XN_NO_OF_SEGMENTS; i++, seg++) { + if (xt_xn_is_before(curr_xn_id, seg->xs_last_xn_id)) + curr_xn_id = seg->xs_last_xn_id; + } + return curr_xn_id; +} + +xtPublic XTXactDataPtr xt_xn_add_old_xact(XTDatabaseHPtr db, xtXactID xn_id, XTThreadPtr thread __attribute__((unused))) +{ + register XTXactDataPtr xact; + register XTXactSegPtr seg; + register XTXactDataPtr *hash; + + seg = &db->db_xn_idx[xn_id & XT_XN_SEGMENT_MASK]; + XT_XACT_WRITE_LOCK(&seg->xs_tab_lock, thread); + hash = &seg->xs_table[(xn_id >> XT_XN_SEGMENT_SHIFTS) % XT_XN_HASH_TABLE_SIZE]; + xact = *hash; + while (xact) { + if (xact->xd_start_xn_id == xn_id) + goto done_ok; + xact = xact->xd_next_xact; + } + + if ((xact = seg->xs_free_list)) + seg->xs_free_list = xact->xd_next_xact; + else { + /* We have used up all the free transaction slots, + * the sweeper should work faster to free them + * up... + */ + db->db_sw_faster |= XT_SW_NO_MORE_XACT_SLOTS; + if (!(xact = (XTXactDataPtr) xt_malloc_ns(sizeof(XTXactDataRec)))) { + XT_XACT_UNLOCK(&seg->xs_tab_lock, thread); + return NULL; + } + } + + xact->xd_next_xact = *hash; + *hash = xact; + + xact->xd_start_xn_id = xn_id; + xact->xd_end_xn_id = 0; + xact->xd_end_time = 0; + xact->xd_begin_log = 0; + xact->xd_flags = 0; + + /* Get the largest transaction id. */ + if (xt_xn_is_before(seg->xs_last_xn_id, xn_id)) + seg->xs_last_xn_id = xn_id; + + done_ok: + XT_XACT_UNLOCK(&seg->xs_tab_lock, thread); +#ifdef HIGH_X + tot_alloced++; + if (tot_alloced > high_alloced) + high_alloced = tot_alloced; +#endif + return xact; +} + +static XTXactDataPtr xn_add_new_xact(XTDatabaseHPtr db, xtXactID xn_id, XTThreadPtr thread __attribute__((unused))) +{ + register XTXactDataPtr xact; + register XTXactSegPtr seg; + register XTXactDataPtr *hash; + + seg = &db->db_xn_idx[xn_id & XT_XN_SEGMENT_MASK]; + XT_XACT_WRITE_LOCK(&seg->xs_tab_lock, thread); + hash = &seg->xs_table[(xn_id >> XT_XN_SEGMENT_SHIFTS) % XT_XN_HASH_TABLE_SIZE]; + + if ((xact = seg->xs_free_list)) + seg->xs_free_list = xact->xd_next_xact; + else { + /* We have used up all the free transaction slots, + * the sweeper should work faster to free them + * up... + */ + db->db_sw_faster |= XT_SW_NO_MORE_XACT_SLOTS; + if (!(xact = (XTXactDataPtr) xt_malloc_ns(sizeof(XTXactDataRec)))) { + XT_XACT_UNLOCK(&seg->xs_tab_lock, thread); + return NULL; + } + } + + xact->xd_next_xact = *hash; + *hash = xact; + + xact->xd_thread_id = thread->t_id; + xact->xd_start_xn_id = xn_id; + xact->xd_end_xn_id = 0; + xact->xd_end_time = 0; + xact->xd_begin_log = 0; + xact->xd_flags = 0; + + seg->xs_last_xn_id = xn_id; + XT_XACT_UNLOCK(&seg->xs_tab_lock, thread); +#ifdef HIGH_X + tot_alloced++; + if (tot_alloced > high_alloced) + high_alloced = tot_alloced; +#endif + return xact; +} + +static xtBool xn_get_xact_details(XTDatabaseHPtr db, xtXactID xn_id, XTThreadPtr thread __attribute__((unused)), int *flags, xtXactID *start, xtWord4 *end, xtThreadID *thd_id) +{ + register XTXactSegPtr seg; + register XTXactDataPtr xact; + xtBool found = FALSE; + + seg = &db->db_xn_idx[xn_id & XT_XN_SEGMENT_MASK]; + XT_XACT_READ_LOCK(&seg->xs_tab_lock, thread); + xact = seg->xs_table[(xn_id >> XT_XN_SEGMENT_SHIFTS) % XT_XN_HASH_TABLE_SIZE]; + while (xact) { + if (xact->xd_start_xn_id == xn_id) { + found = TRUE; + if (flags) + *flags = xact->xd_flags; + if (start) + *start = xact->xd_start_xn_id; + if (end) + *end = xact->xd_end_time; + if (thd_id) + *thd_id = xact->xd_thread_id; + break; + } + xact = xact->xd_next_xact; + } + XT_XACT_UNLOCK(&seg->xs_tab_lock, thread); + return found; +} + +static xtBool xn_get_xact_pointer(XTDatabaseHPtr db, xtXactID xn_id, XTXactDataPtr *xact_ptr) +{ + register XTXactSegPtr seg; + register XTXactDataPtr xact; + xtBool found = FALSE; + + *xact_ptr = NULL; + seg = &db->db_xn_idx[xn_id & XT_XN_SEGMENT_MASK]; + XT_XACT_READ_LOCK(&seg->xs_tab_lock, thread); + xact = seg->xs_table[(xn_id >> XT_XN_SEGMENT_SHIFTS) % XT_XN_HASH_TABLE_SIZE]; + while (xact) { + if (xact->xd_start_xn_id == xn_id) { + found = TRUE; + /* We only return pointers to transaction structures that are permanently + * allocated! + */ + if ((xtWord1 *) xact >= db->db_xn_data && (xtWord1 *) xact < db->db_xn_data_end) + *xact_ptr = xact; + break; + } + xact = xact->xd_next_xact; + } + XT_XACT_UNLOCK(&seg->xs_tab_lock, thread); + return found; +} + +static xtBool xn_get_xact_start(XTDatabaseHPtr db, xtXactID xn_id, XTThreadPtr thread __attribute__((unused)), xtLogID *log_id, xtLogOffset *log_offset) +{ + register XTXactSegPtr seg; + register XTXactDataPtr xact; + xtBool found = FALSE; + + seg = &db->db_xn_idx[xn_id & XT_XN_SEGMENT_MASK]; + XT_XACT_READ_LOCK(&seg->xs_tab_lock, thread); + xact = seg->xs_table[(xn_id >> XT_XN_SEGMENT_SHIFTS) % XT_XN_HASH_TABLE_SIZE]; + while (xact) { + if (xact->xd_start_xn_id == xn_id) { + found = TRUE; + *log_id = xact->xd_begin_log; + *log_offset = xact->xd_begin_offset; + break; + } + xact = xact->xd_next_xact; + } + XT_XACT_UNLOCK(&seg->xs_tab_lock, thread); + return found; +} + +/* NOTE: this function may only be used by the sweeper or the recovery process. */ +xtPublic XTXactDataPtr xt_xn_get_xact(XTDatabaseHPtr db, xtXactID xn_id, XTThreadPtr thread __attribute__((unused))) +{ + register XTXactSegPtr seg; + register XTXactDataPtr xact; + + seg = &db->db_xn_idx[xn_id & XT_XN_SEGMENT_MASK]; + XT_XACT_READ_LOCK(&seg->xs_tab_lock, thread); + xact = seg->xs_table[(xn_id >> XT_XN_SEGMENT_SHIFTS) % XT_XN_HASH_TABLE_SIZE]; + while (xact) { + if (xact->xd_start_xn_id == xn_id) + break; + xact = xact->xd_next_xact; + } + XT_XACT_UNLOCK(&seg->xs_tab_lock, thread); + return xact; +} + +/* + * Delete a transaction, return TRUE if the transaction + * was found. + */ +xtPublic xtBool xt_xn_delete_xact(XTDatabaseHPtr db, xtXactID xn_id, XTThreadPtr thread __attribute__((unused))) +{ + XTXactDataPtr xact, pxact = NULL; + XTXactSegPtr seg; + + seg = &db->db_xn_idx[xn_id & XT_XN_SEGMENT_MASK]; + XT_XACT_WRITE_LOCK(&seg->xs_tab_lock, thread); + xact = seg->xs_table[(xn_id >> XT_XN_SEGMENT_SHIFTS) % XT_XN_HASH_TABLE_SIZE]; + while (xact) { + if (xact->xd_start_xn_id == xn_id) { + if (pxact) + pxact->xd_next_xact = xact->xd_next_xact; + else + seg->xs_table[(xn_id >> XT_XN_SEGMENT_SHIFTS) % XT_XN_HASH_TABLE_SIZE] = xact->xd_next_xact; + xn_free_xact(db, seg, xact); + XT_XACT_UNLOCK(&seg->xs_tab_lock, thread); + return TRUE; + } + pxact = xact; + xact = xact->xd_next_xact; + } + XT_XACT_UNLOCK(&seg->xs_tab_lock, thread); + return FALSE; +} + +//#define DEBUG_RAM_LIST +#ifdef DEBUG_RAM_LIST + +#define DEBUG_RAM_LIST_SIZE 80 + +int check_ram_init_count = 0; +xt_rwlock_type check_ram_lock; +xtXactID check_ram_trns[DEBUG_RAM_LIST_SIZE]; +int check_ram_dummy; + +static void check_ram_init(void) +{ + if (check_ram_init_count == 0) + xt_init_rwlock(NULL, &check_ram_lock); + check_ram_init_count++; +} + +static void check_ram_free(void) +{ + check_ram_init_count--; + if (check_ram_init_count == 0) + xt_free_rwlock(&check_ram_lock); +} + +static void check_ram_min_id(XTDatabaseHPtr db) +{ + int i; + + xt_slock_rwlock_ns(&check_ram_lock); + for (i=0; i<DEBUG_RAM_LIST_SIZE; i++) { + if (check_ram_trns[i] && xt_xn_is_before(check_ram_trns[i], db->db_xn_min_ram_id)) { + /* This should never happen! */ + XTXactDataPtr x_ptr; + + check_ram_dummy = 0; + for (i=0; i<DEBUG_RAM_LIST_SIZE; i++) { + if (check_ram_trns[i]) { + x_ptr = xt_xn_get_xact(db, check_ram_trns[i]); + check_ram_dummy = 1; + } + } + break; + } + } + xt_unlock_rwlock_ns(&check_ram_lock); +} + +static void check_ram_add(xtXactID xn_id) +{ + int i; + + xt_xlock_rwlock_ns(&check_ram_lock); + for (i=0; i<DEBUG_RAM_LIST_SIZE; i++) { + if (!check_ram_trns[i]) { + check_ram_trns[i] = xn_id; + xt_unlock_rwlock_ns(&check_ram_lock); + return; + } + } + xt_unlock_rwlock_ns(&check_ram_lock); + printf("DEBUG --- List too small\n"); +} + +static void check_ram_del(xtXactID xn_id) +{ + int i; + + xt_xlock_rwlock_ns(&check_ram_lock); + for (i=0; i<DEBUG_RAM_LIST_SIZE; i++) { + if (check_ram_trns[i] == xn_id) { + check_ram_trns[i] = 0; + xt_unlock_rwlock_ns(&check_ram_lock); + return; + } + } + xt_unlock_rwlock_ns(&check_ram_lock); +} +#endif + +/* ---------------------------------------------------------------------- + * Init and Exit + */ + +xtPublic void xt_xn_init_db(XTThreadPtr self, XTDatabaseHPtr db) +{ + XTXactDataPtr xact; + XTXactSegPtr seg; + +#ifdef DEBUG_RAM_LIST + check_ram_init(); +#endif + xt_spinlock_init_with_autoname(self, &db->db_xn_id_lock); + xt_spinlock_init_with_autoname(self, &db->db_xn_wait_spinlock); + //xt_init_mutex_with_autoname(self, &db->db_xn_wait_lock); + //xt_init_cond(self, &db->db_xn_wait_cond); + xt_init_mutex_with_autoname(self, &db->db_sw_lock); + xt_init_cond(self, &db->db_sw_cond); + xt_init_mutex_with_autoname(self, &db->db_wr_lock); + xt_init_cond(self, &db->db_wr_cond); + + /* Pre-allocate transaction data structures: */ + db->db_xn_data = (xtWord1 *) xt_malloc(self, sizeof(XTXactDataRec) * XT_XN_DATA_ALLOC_COUNT * XT_XN_NO_OF_SEGMENTS); + db->db_xn_data_end = db->db_xn_data + sizeof(XTXactDataRec) * XT_XN_DATA_ALLOC_COUNT * XT_XN_NO_OF_SEGMENTS; + xact = (XTXactDataPtr) db->db_xn_data; + for (u_int i=0; i<XT_XN_NO_OF_SEGMENTS; i++) { + seg = &db->db_xn_idx[i]; + XT_XACT_INIT_LOCK(self, &seg->xs_tab_lock); + for (u_int j=0; j<XT_XN_DATA_ALLOC_COUNT; j++) { + xact->xd_next_xact = seg->xs_free_list; + seg->xs_free_list = xact; + xact++; + } + } + + /* Initialize the data logs: */ + db->db_datalogs.dlc_init(self, db); + + /* Setup the transaction log: */ + db->db_xlog.xlog_setup(self, db, (off_t) xt_db_log_file_threshold, xt_db_transaction_buffer_size, xt_db_log_file_count); + + db->db_xn_end_time = 1; + + /* Initializing the restart file, also does + * recovery. This returns the log position after recovery. + * + * This is the log position where the writer thread will + * begin. The writer thread writes changes to the database that + * have been flushed to the log. + */ + xt_xres_init(self, db); + + /* Initialize the "last transaction in memory", by default + * this is the current transaction ID, which is the ID + * of the last transaction. + */ + for (u_int i=0; i<XT_XN_NO_OF_SEGMENTS; i++) { + seg = &db->db_xn_idx[i]; + XT_XACT_INIT_LOCK(self, &seg->xs_tab_lock); + seg->xs_last_xn_id = db->db_xn_curr_id; + } + + /* + * The next transaction to clean is the lowest transaction + * in memory: + */ + db->db_xn_to_clean_id = db->db_xn_min_ram_id; + + /* + * No transactions are running, so the minimum transaction + * ID is the next one to run: + */ + db->db_xn_min_run_id = db->db_xn_curr_id + 1; + + db->db_xn_wait_for = xt_new_sortedlist(self, sizeof(XNWaitForRec), 100, 50, xn_compare_wait_for, db, xn_free_wait_for, FALSE, FALSE); +} + +xtPublic void xt_xn_exit_db(XTThreadPtr self, XTDatabaseHPtr db) +{ +#ifdef HIGH_X + printf("=========> MOST TXs CURR ALLOC: %lu\n", tot_alloced); + printf("=========> MOST TXs HIGH ALLOC: %lu\n", high_alloced); + printf("=========> MAX TXs NOT CLEAN: %lu\n", not_clean_max); + printf("=========> MAX TXs IN RAM: %lu\n", in_ram_max); +#endif + + xt_stop_sweeper(self, db); // Should be done already! + xt_stop_writer(self, db); // Should be done already! + + xt_xres_exit(self, db); + db->db_xlog.xlog_exit(self); + + db->db_datalogs.dlc_exit(self); + + for (u_int i=0; i<XT_XN_NO_OF_SEGMENTS; i++) { + XTXactSegPtr seg; + + seg = &db->db_xn_idx[i]; + for (u_int j=0; j<XT_XN_HASH_TABLE_SIZE; j++) { + XTXactDataPtr xact, nxact; + + xact = seg->xs_table[j]; + while (xact) { + nxact = xact->xd_next_xact; + xn_free_xact(db, seg, xact); + xact = nxact; + } + } + XT_XACT_FREE_LOCK(self, &seg->xs_tab_lock); + } + if (db->db_xn_wait_for) { + xt_free_sortedlist(self, db->db_xn_wait_for); + db->db_xn_wait_for = NULL; + } + if (db->db_xn_data) { + xt_free(self, db->db_xn_data); + db->db_xn_data = NULL; + db->db_xn_data_end = NULL; + } + + xt_free_cond(&db->db_wr_cond); + xt_free_mutex(&db->db_wr_lock); + xt_free_cond(&db->db_sw_cond); + xt_free_mutex(&db->db_sw_lock); + //xt_free_cond(&db->db_xn_wait_cond); + //xt_free_mutex(&db->db_xn_wait_lock); + xt_spinlock_free(self, &db->db_xn_wait_spinlock); + xt_spinlock_free(self, &db->db_xn_id_lock); +#ifdef DEBUG_RAM_LIST + check_ram_free(); +#endif +} + +xtPublic void xt_xn_init_thread(XTThreadPtr self, int what_for) +{ + ASSERT(self->st_database); + + if (!xt_init_row_lock_list(&self->st_lock_list)) + xt_throw(self); + switch (what_for) { + case XT_FOR_COMPACTOR: + self->st_dlog_buf.dlb_init(self->st_database, xt_db_log_buffer_size); + break; + case XT_FOR_WRITER: + /* The writer does not need a transaction buffer. */ + self->st_dlog_buf.dlb_init(self->st_database, 0); + break; + case XT_FOR_SWEEPER: + self->st_dlog_buf.dlb_init(self->st_database, 0); + break; + case XT_FOR_USER: + self->st_dlog_buf.dlb_init(self->st_database, xt_db_log_buffer_size); + break; + } +} + +xtPublic void xt_xn_exit_thread(XTThreadPtr self) +{ + if (self->st_xact_data) + xt_xn_rollback(self); + self->st_dlog_buf.dlb_exit(self); + xt_exit_row_lock_list(&self->st_lock_list); +} + +/* ---------------------------------------------------------------------- + * Begin and End Transactions + */ + +xtPublic xtBool xt_xn_begin(XTThreadPtr self) +{ + XTDatabaseHPtr db = self->st_database; + xtXactID xn_id; + + ASSERT(!self->st_xact_data); + + xt_spinlock_lock(&db->db_xn_id_lock); + xn_id = ++db->db_xn_curr_id; + xt_spinlock_unlock(&db->db_xn_id_lock); + +#ifdef HIGH_X + if (xt_xn_is_before(not_clean_max, xn_id - db->db_xn_to_clean_id)) + not_clean_max = xn_id - db->db_xn_to_clean_id; + if (xt_xn_is_before(in_ram_max, xn_id - db->db_xn_min_ram_id)) + in_ram_max = xn_id - db->db_xn_min_ram_id; +#endif + /* {GAP-INC-ADD-XACT} This is the gap between incrementing the ID, + * and creating the transaction in memory. + * See xt_xn_get_curr_id(). + */ + + if (!(self->st_xact_data = xn_add_new_xact(db, xn_id, self))) + return FAILED; + self->st_xact_writer = FALSE; + + /* All transactions that committed before or at this time + * are this one are visible: */ + self->st_visible_time = db->db_xn_end_time; + +#ifdef TRACE_TRANSACTION + xt_ttracef(self, "BEGIN T%lu\n", (u_long) self->st_xact_data->xd_start_xn_id); +#endif + return OK; +} + +static xtBool xn_end_xact(XTThreadPtr thread, u_int status) +{ + XTXactDataPtr xact; + xtBool ok = TRUE; + + ASSERT_NS(thread->st_xact_data); + if ((xact = thread->st_xact_data)) { + XTDatabaseHPtr db = thread->st_database; + xtXactID xn_id = xact->xd_start_xn_id; + xtBool writer; + + if ((writer = thread->st_xact_writer)) { + /* The transaction wrote something: */ + XTXactEndEntryDRec entry; + xtWord4 sum; + + sum = XT_CHECKSUM4_XACT(xn_id) ^ XT_CHECKSUM4_XACT(0); + entry.xe_status_1 = status; + entry.xe_checksum_1 = XT_CHECKSUM_1(sum); + XT_SET_DISK_4(entry.xe_xact_id_4, xn_id); + XT_SET_DISK_4(entry.xe_not_used_4, 0); + +#ifdef XT_IMPLEMENT_NO_ACTION + /* This will check any resticts that have been delayed to the end of the statement. */ + if (thread->st_restrict_list.bl_count) { + if (!xt_tab_restrict_rows(&thread->st_restrict_list, thread)) { + ok = FALSE; + status = XT_LOG_ENT_ABORT; + } + } +#endif + + /* Flush the data log: */ + if (!thread->st_dlog_buf.dlb_flush_log(TRUE, thread)) { + ok = FALSE; + status = XT_LOG_ENT_ABORT; + } + + /* Write and flush the transaction log: */ + if (!xt_xlog_log_data(thread, sizeof(XTXactEndEntryDRec), (XTXactLogBufferDPtr) &entry, TRUE)) { + ok = FALSE; + status = XT_LOG_ENT_ABORT; + /* Make sure this is done, if we failed to log + * the transction end! + */ + if (thread->st_xact_writer) { + /* Adjust this in case of error, but don't forget + * to lock! + */ + xt_spinlock_lock(&db->db_xlog.xl_buffer_lock); + db->db_xn_writer_count--; + thread->st_xact_writer = FALSE; + if (thread->st_xact_long_running) { + db->db_xn_long_running_count--; + thread->st_xact_long_running = FALSE; + } + xt_spinlock_unlock(&db->db_xlog.xl_buffer_lock); + } + } + + /* Setting this flag completes the transaction, + * Do this before we release the locks, because + * the unlocked transactions expect the + * transaction they are waiting for to be + * gone! + */ + xact->xd_end_time = ++db->db_xn_end_time; + if (status == XT_LOG_ENT_COMMIT) { + thread->st_statistics.st_commits++; + xact->xd_flags |= (XT_XN_XAC_COMMITTED | XT_XN_XAC_ENDED); + } + else { + thread->st_statistics.st_rollbacks++; + xact->xd_flags |= XT_XN_XAC_ENDED; + } + + /* {REMOVE-LOCKS} Drop locks is you have any: */ + thread->st_lock_list.xt_remove_all_locks(db, thread); + + /* Do this afterwards to make sure the sweeper + * does not cleanup transactions start cleaning up + * before any transactions that were waiting for + * this transaction have completed! + */ + xact->xd_end_xn_id = db->db_xn_curr_id; + + /* Now you can sweep! */ + xact->xd_flags |= XT_XN_XAC_SWEEP; + } + else { + /* Read-only transaction can be removed, immediately */ + xact->xd_end_time = ++db->db_xn_end_time; + xact->xd_flags |= (XT_XN_XAC_COMMITTED | XT_XN_XAC_ENDED); + + /* Drop locks is you have any: */ + thread->st_lock_list.xt_remove_all_locks(db, thread); + + xact->xd_end_xn_id = db->db_xn_curr_id; + + xact->xd_flags |= XT_XN_XAC_SWEEP; + + if (xt_xn_delete_xact(db, xn_id, thread)) { + if (db->db_xn_min_ram_id == xn_id) + db->db_xn_min_ram_id = xn_id+1; + } + } + +#ifdef TRACE_TRANSACTION + if (status == XT_LOG_ENT_COMMIT) + xt_ttracef(thread, "COMMIT T%lu\n", (u_long) xn_id); + else + xt_ttracef(thread, "ABORT T%lu\n", (u_long) xn_id); +#endif + + if (db->db_xn_min_run_id == xn_id) + db->db_xn_min_run_id = xn_id+1; + + thread->st_xact_data = NULL; + + xt_xn_wakeup_waiting_threads(thread); + + /* {WAKE-SW} Waking the sweeper + * is no longer unconditional. + * (see all comments to {WAKE-SW}) + * + * We now wake the sweeper if it is + * supposed to work faster. + * + * There are now 2 cases: + * - We run out of transaction slots. + * - We encounter old index entries. + * + * The following test: + * runTest(INCREMENT_TEST, 16, INCREMENT_TEST_UPDATE_COUNT); + * has extreme problems with sweeping every 1/10s + * because a huge number of index entries accumulate + * that need to be cleaned. + * + * New code detects this case. + */ + if (db->db_sw_faster) + xt_wakeup_sweeper(db); + + /* Don't get too far ahead of the sweeper! */ + if (writer) { + if ((db->db_sw_faster & XT_SW_TOO_FAR_BEHIND) != 0) { + xtWord8 then = xt_trace_clock() + (xtWord8) 20000; + + for (;;) { + xt_critical_wait(); + if (db->db_sw_faster & XT_SW_TOO_FAR_BEHIND) + break; + if (xt_trace_clock() >= then) + break; + } + } + } + } + return ok; +} + +xtPublic xtBool xt_xn_commit(XTThreadPtr thread) +{ + return xn_end_xact(thread, XT_LOG_ENT_COMMIT); +} + +xtPublic xtBool xt_xn_rollback(XTThreadPtr thread) +{ + return xn_end_xact(thread, XT_LOG_ENT_ABORT); +} + +xtPublic xtBool xt_xn_log_tab_id(XTThreadPtr self, xtTableID tab_id) +{ + XTXactNewTabEntryDRec entry; + + entry.xt_status_1 = XT_LOG_ENT_NEW_TAB; + entry.xt_checksum_1 = XT_CHECKSUM_1(tab_id); + XT_SET_DISK_4(entry.xt_tab_id_4, tab_id); + return xt_xlog_log_data(self, sizeof(XTXactNewTabEntryDRec), (XTXactLogBufferDPtr) &entry, TRUE); +} + +xtPublic int xt_xn_status(XTOpenTablePtr ot, xtXactID xn_id, xtRecordID XT_UNUSED(rec_id)) +{ + register XTThreadPtr self = ot->ot_thread; + int flags; + xtWord4 end; + +#ifdef DRIZZLED + /* Conditional waste of time! + * Drizzle has strict warnings. + * I know this is not necessary! + */ + flags = 0; + end = 0; +#endif + if (xn_id == self->st_xact_data->xd_start_xn_id) + return XT_XN_MY_UPDATE; + if (xt_xn_is_before(xn_id, self->st_database->db_xn_min_ram_id) || + !xn_get_xact_details(self->st_database, xn_id, ot->ot_thread, &flags, NULL, &end, NULL)) { + /* Not in RAM, rollback done: */ +//*DBG*/xt_dump_xlogs(self->st_database, 0); +//*DBG*/xt_check_table(self, ot); +//*DBG*/xt_dump_trace(); + /* {XACT-NOT-IN-RAM} + * This should never happen (CHANGED see below)! + * + * Because if the transaction is no longer in RAM, then it has been + * cleaned up. So the record should be marked as clean, or not + * exist. + * + * After sweeping, we wait for all transactions to quit that were + * running at the time of cleanup before removing the transaction record. + * (see {XACT-NOT-IN-RAM}) + * + * If this was not the case, then we could be here because: + * - The user transaction (T2) reads record x and notes that the record + * has not been cleaned (CLEAN bit not set). + * + * - The sweeper is busy sweeping the transaction (T1) that created + * record x. + * The SW sets the CLEAN bit on record x, and the schedules T1 for + * deletion. + * + * Now T1 should not be deleted before T2 quits. If it does happen + * then we land up here. + * + * THIS CAN NOW HAPPEN! + * + * First of all, a MYSTERY: + * This did happen, dispite the description above! The reason why + * is left as an exercise to the reader (really, I don't now why!) + * + * This has force me to add code to handle the situation. This + * is done by re-reading the record that is being checked by this + * function. After re-reading, the record should either be + * invalid (free) or clean (CLEAN bit set). + * + * If this is the case, then we will not run land up here + * again. + * + * Because we are only here because the record was valid but not + * clean (you can confirm this by looking at the code that + * calls this function). + */ + return XT_XN_REREAD; + } + if (!(flags & XT_XN_XAC_ENDED)) + /* Transaction not ended, may be visible. */ + return XT_XN_OTHER_UPDATE; + /* Visible if the transaction was committed: */ + if (flags & XT_XN_XAC_COMMITTED) { + if (!xt_xn_is_before(self->st_visible_time, end)) // was self->st_visible_time >= xact->xd_end_time + return XT_XN_VISIBLE; + return XT_XN_NOT_VISIBLE; + } + return XT_XN_ABORTED; +} + +xtPublic xtWord8 xt_xn_bytes_to_sweep(XTDatabaseHPtr db, XTThreadPtr thread) +{ + xtXactID xn_id; + xtXactID curr_xn_id; + xtLogID xn_log_id = 0; + xtLogOffset xn_log_offset = 0; + xtLogID x_log_id = 0; + xtLogOffset x_log_offset = 0; + xtLogID log_id; + xtLogOffset log_offset; + xtWord8 byte_count = 0; + + xn_id = db->db_xn_to_clean_id; + curr_xn_id = xt_xn_get_curr_id(db); + // Limit the number of transactions checked! + for (int i=0; i<1000; i++) { + if (xt_xn_is_before(curr_xn_id, xn_id)) + break; + if (xn_get_xact_start(db, xn_id, thread, &x_log_id, &x_log_offset)) { + if (xn_log_id) { + if (xt_comp_log_pos(x_log_id, x_log_offset, xn_log_id, xn_log_offset) < 0) { + xn_log_id = x_log_id; + xn_log_offset = x_log_offset; + } + } + else { + xn_log_id = x_log_id; + x_log_offset = x_log_offset; + } + } + xn_id++; + } + if (!xn_log_id) + return 0; + + /* Assume the logs have the threshold: */ + log_id = db->db_xlog.xl_write_log_id; + log_offset = db->db_xlog.xl_write_log_offset; + if (xn_log_id < log_id) { + if (xn_log_offset < xt_db_log_file_threshold) + byte_count = (size_t) (xt_db_log_file_threshold - xn_log_offset); + xn_log_offset = 0; + xn_log_id++; + } + while (xn_log_id < log_id) { + byte_count += (size_t) xt_db_log_file_threshold; + xn_log_id++; + } + if (xn_log_offset < log_offset) + byte_count += (size_t) (log_offset - xn_log_offset); + + return byte_count; +} + +/* ---------------------------------------------------------------------- + * S W E E P E R P R O C E S S + */ + +typedef struct XNSweeperState { + XTDatabaseHPtr ss_db; + XTXactSeqReadRec ss_seqread; + XTDataBufferRec ss_databuf; + u_int ss_call_cnt; + XTBasicQueueRec ss_to_free; + xtBool ss_flush_pending; + XTOpenTablePtr ss_ot; +} XNSweeperStateRec, *XNSweeperStatePtr; + +static XTOpenTablePtr xn_sw_get_open_table(XTThreadPtr self, XNSweeperStatePtr ss, xtTableID tab_id, int *r) +{ + if (ss->ss_ot) { + if (ss->ss_ot->ot_table->tab_id == tab_id) + return ss->ss_ot; + + xt_db_return_table_to_pool(self, ss->ss_ot); + ss->ss_ot = NULL; + } + + if (!ss->ss_ot) { + if (!(ss->ss_ot = xt_db_open_pool_table(self, ss->ss_db, tab_id, r, TRUE))) + return NULL; + } + + return ss->ss_ot; +} + +static void xn_sw_close_open_table(XTThreadPtr self, XNSweeperStatePtr ss) +{ + if (ss->ss_ot) { + xt_db_return_table_to_pool(self, ss->ss_ot); + ss->ss_ot = NULL; + } +} + +/* + * A thread can set a bit in db_sw_faster to make + * the sweeper go faster. + */ +static void xn_sw_could_go_faster(XTThreadPtr self, XTDatabaseHPtr db) +{ + if (db->db_sw_faster) { + if (!db->db_sw_fast) { + xt_set_priority(self, xt_db_sweeper_priority+1); + db->db_sw_fast = TRUE; + } + } +} + +static void xn_sw_go_slower(XTThreadPtr self, XTDatabaseHPtr db) +{ + if (db->db_sw_fast) { + xt_set_priority(self, xt_db_sweeper_priority); + db->db_sw_fast = FALSE; + } + db->db_sw_faster = XT_SW_WORK_NORMAL; +} + +/* Add a record to the "to free" queue. We note the current + * transaction at the time this is done. The record will + * only be freed once this transaction terminated, together + * with all transactions that started before it! + * + * The reason for this is that a sequential scan or some + * other operation may read a committed record which is no longer + * valid because it is no longer the latest variation (the first + * variation reachable from the row pointer). + * + * In this case, the sweeper will free the variation. + * If the variation is re-used and committed before + * the sequential scan or read completes, and by some + * fluke is used by the same record as previously, + * the system will think the record is valid + * again. + * + * Without re-reading the record the sequential + * scan or other read will find it on the variation list, and + * return the record data as if valid! + * + * ------------ 2008-01-03 + * + * An example of this is: + * + * Assume we have 3 records. + * The 3rd record is deleted, and committed. + * Before cleanup can be performed + * a sequential scan takes a copy of the records. + * + * Now assume a new insert is done before + * the sequential scan gets to the 3rd record. + * + * The insert allocates the 3rd row and 3rd record + * again. + * + * Now, when the sequential scan gets to the old copy of the 3rd record, + * this is valid because the row points to this record again. + * + * HOWEVER! I have now changed the sequential scan so that it accesses + * the records from the cache, without making a copy. + * + * This means that this problem cannot occur because the sequential scan + * always reads the current data from the cache. + * + * There is also no race condition (although no lock is taken), because + * the record is writen before the row (see here [(5)]). + * + * This means that the row does not point to the record before the + * record has been modified. + * + * Once the record has been modified then the sequential scan will see + * that the record belongs to a new transaction. + * + * If the row pointer was set before the record updated then a race + * condition would exist when the sequential scan reads the record + * after the insert has updated the row pointer but before it has + * changed the record. + * + * AS A RESULT: + * + * I believe I can remove the delayed free record! + * + * This means I can combine the REMOVE and FREE operations. + * + * This is good because this takes care of the problem + * that records are lost when: + * + * The server crashes when the delayed free list still has items on it. + * AND + * The transaction that freed the records has been cleaned, and this + * fact has been committed to the log. + * + * So I have removed the delay here: [(6)] + * + * ------------ 2008-12-03 + * + * This code to delay removal of records was finally removed (see above) + */ + +/* + * As above, but instead a transaction is added to the "to free" queue. + * + * It is important that transactions remain in memory until all + * currently running transactions have ended. This is because + * sequential and index scans have copies of old data. + * + * In the old data a record may not be indicated as cleaned. Such + * a record is considered invalid if the transaction is not in RAM. + * + * GOTCHA: + * + * And this problem is demonstrated by the following example + * which was derived from flush_table.test. + * + * Each handler command below is a separate transaction. + * However the buffer is loaded by 'read first'. + * Depending on when cleanup occurs, records can disappear + * in some of the next commands. + * + * 2 solutions for the test. Use begin ... commit around + * handler open ... close. Or use analyze table t1 before + * open. analyze table waits for the sweeper to complete! + * + * create table dummy(table_id char(20) primary key); + * let $1=100; + * while ($1) + * { + * drop table if exists t1; + * create table t1(table_id char(20) primary key); + * insert into t1 values ('Record-01'); + * insert into t1 values ('Record-02'); + * insert into t1 values ('Record-03'); + * insert into t1 values ('Record-04'); + * insert into t1 values ('Record-05'); + * handler t1 open; + * handler t1 read first limit 1; + * handler t1 read next limit 1; + * handler t1 read next limit 1; + * handler t1 read next limit 1; + * handler t1 close; + * commit; + * dec $1; + * } + * + */ +#ifdef MUST_DELAY_REMOVE +static void xn_sw_add_xact_to_free(XTThreadPtr self, XNSweeperStatePtr ss, xtXactID xn_id) +{ + XNSWToFreeItemRec free_item; + + if ((ss->ss_to_free.bq_front - ss->ss_to_free.bq_back) >= XT_TN_MAX_TO_FREE) { + /* If the queue is full, try to free some items: + * We use the call count to avoid doing this every time, + * when the queue overflows! + */ + if ((ss->ss_call_cnt % XT_TN_MAX_TO_FREE_CHECK) == 0) + /* GOTCHA: This call was not locking the sweeper, + * this could cause failure, of course: + */ + xn_sw_service_to_free(self, ss, TRUE); + ss->ss_call_cnt++; + } + + free_item.ri_wait_xn_id = ss->ss_db->db_xn_curr_id; + free_item.ri_tab_id = 0; + free_item.x.ri_xn_id = xn_id; + + xt_bq_add(self, &ss->ss_to_free, &free_item); +} +#endif + +static void xt_sw_delete_variations(XTThreadPtr self, XNSweeperStatePtr ss, XTOpenTablePtr ot, xtRecordID rec_id, xtRowID row_id, xtXactID xn_id) +{ + xtRecordID prev_var_rec_id; + + while (rec_id) { + switch (xt_tab_remove_record(ot, rec_id, ss->ss_databuf.db_data, &prev_var_rec_id, FALSE, row_id, xn_id)) { + case XT_ERR: + throw_(); + return; + case TRUE: + break; + } + rec_id = prev_var_rec_id; + } +} + +static void xt_sw_delete_variation(XTThreadPtr self, XNSweeperStatePtr ss, XTOpenTablePtr ot, xtRecordID rec_id, xtBool clean_delete, xtRowID row_id, xtXactID xn_id) +{ + xtRecordID prev_var_rec_id; + + switch (xt_tab_remove_record(ot, rec_id, ss->ss_databuf.db_data, &prev_var_rec_id, clean_delete, row_id, xn_id)) { + case XT_ERR: + throw_(); + return; + case TRUE: + break; + case FALSE: + break; + } +} + +/* Set rec_type to this value in order to force cleanup, without + * a check. + */ +#define XN_FORCE_CLEANUP XT_TAB_STATUS_FREED + +/* + * Read the record to be cleaned. Return TRUE if the cleanup has already been done. + */ +static xtBool xn_sw_cleanup_done(XTThreadPtr self, XTOpenTablePtr ot, xtRecordID rec_id, xtXactID xn_id, u_int rec_type, u_int stat_id, xtRowID row_id, XTTabRecHeadDPtr rec_head) +{ + if (!xt_tab_get_rec_data(ot, rec_id, sizeof(XTTabRecHeadDRec), (xtWord1 *) rec_head)) + throw_(); + + if (rec_type == XN_FORCE_CLEANUP) { + if (XT_REC_IS_FREE(rec_head->tr_rec_type_1)) + return TRUE; + } + else { + /* Transaction must match: */ + if (XT_GET_DISK_4(rec_head->tr_xact_id_4) != xn_id) + return TRUE; + + /* Record header must match expected value from + * log or clean has been done, or is not required. + * + * For example, it is not required if a record + * has been overwritten in a transaction. + */ + if (rec_head->tr_rec_type_1 != rec_type || + rec_head->tr_stat_id_1 != stat_id) + return TRUE; + + /* Row must match: */ + if (XT_GET_DISK_4(rec_head->tr_row_id_4) != row_id) + return TRUE; + } + + return FALSE; +} + +static void xn_sw_clean_indices(XTThreadPtr self __attribute__((unused)), XTOpenTablePtr ot, xtRecordID rec_id, xtRowID row_id, xtWord1 *rec_data, xtWord1 *rec_buffer) +{ + XTTableHPtr tab = ot->ot_table; + u_int cols_req; + XTIndexPtr *ind; + + if (!tab->tab_dic.dic_key_count) + return; + + cols_req = tab->tab_dic.dic_ind_cols_req; + if (XT_REC_IS_FIXED(rec_data[0])) + rec_buffer = rec_data + XT_REC_FIX_HEADER_SIZE; + else { + if (XT_REC_IS_VARIABLE(rec_data[0])) { + if (!myxt_load_row(ot, rec_data + XT_REC_FIX_HEADER_SIZE, rec_buffer, cols_req)) + goto failed; + } + else if (XT_REC_IS_EXT_DLOG(rec_data[0])) { + ASSERT(cols_req); + if (cols_req && cols_req <= tab->tab_dic.dic_fix_col_count) { + if (!myxt_load_row(ot, rec_data + XT_REC_EXT_HEADER_SIZE, rec_buffer, cols_req)) + goto failed; + } + else { + if (rec_data != ot->ot_row_rbuffer) + memcpy(ot->ot_row_rbuffer, rec_data, tab->tab_dic.dic_rec_size); + if (!xt_tab_load_ext_data(ot, rec_id, rec_buffer, cols_req)) + goto failed; + } + } + else + /* This is possible, the record has already been cleaned up. */ + return; + } + + ind = tab->tab_dic.dic_keys; + for (u_int i=0; i<tab->tab_dic.dic_key_count; i++, ind++) { + if (!xt_idx_update_row_id(ot, *ind, rec_id, row_id, rec_buffer)) + xt_log_and_clear_exception_ns(); + } + return; + + failed: + xt_log_and_clear_exception_ns(); +} + +/* + * Return TRUE if the cleanup was done. FAILED if cleanup could not be done + * because dictionary information is not available. + */ +static xtBool xn_sw_cleanup_variation(XTThreadPtr self, XNSweeperStatePtr ss, XTXactDataPtr xact, xtTableID tab_id, xtRecordID rec_id, u_int status, u_int rec_type, u_int stat_id, xtRowID row_id, xtWord1 *rec_buf) +{ + XTOpenTablePtr ot; + XTTableHPtr tab; + XTTabRecHeadDRec rec_head; + xtRecordID after_rec_id; + xtXactID xn_id; + int r; + + if (!(ot = xn_sw_get_open_table(self, ss, tab_id, &r))) { + /* The table no longer exists, consider cleanup done: */ + switch (r) { + case XT_TAB_NOT_FOUND: + break; + case XT_TAB_NO_DICTIONARY: + case XT_TAB_POOL_CLOSED: + return FALSE; + } + return TRUE; + } + + tab = ot->ot_table; + + /* Make sure the buffer is large enough! */ + xt_db_set_size(self, &ss->ss_databuf, (size_t) tab->tab_dic.dic_mysql_buf_size); + + xn_id = xact->xd_start_xn_id; + if (xact->xd_flags & XT_XN_XAC_COMMITTED) { + /* The transaction has been committed. Clean the record and + * remove variations no longer in use. + */ + switch (status) { + case XT_LOG_ENT_REC_MODIFIED: + case XT_LOG_ENT_UPDATE: + case XT_LOG_ENT_UPDATE_FL: + case XT_LOG_ENT_UPDATE_BG: + case XT_LOG_ENT_UPDATE_FL_BG: + if (xn_sw_cleanup_done(self, ot, rec_id, xn_id, rec_type, stat_id, row_id, &rec_head)) + goto done_ok; + after_rec_id = XT_GET_DISK_4(rec_head.tr_prev_rec_id_4); + xt_sw_delete_variations(self, ss, ot, after_rec_id, row_id, xn_id); + rec_head.tr_rec_type_1 |= XT_TAB_STATUS_CLEANED_BIT; + XT_SET_NULL_DISK_4(rec_head.tr_prev_rec_id_4); + if (!xt_tab_put_log_op_rec_data(ot, XT_LOG_ENT_REC_CLEANED, 0, rec_id, offsetof(XTTabRecHeadDRec, tr_prev_rec_id_4) + XT_RECORD_ID_SIZE, (xtWord1 *) &rec_head)) + throw_(); + xn_sw_clean_indices(self, ot, rec_id, row_id, rec_buf, ss->ss_databuf.db_data); + break; + case XT_LOG_ENT_INSERT: + case XT_LOG_ENT_INSERT_FL: + case XT_LOG_ENT_INSERT_BG: + case XT_LOG_ENT_INSERT_FL_BG: { + /* POTENTIAL BUG 1: + * + * DROP TABLE IF EXISTS t1; + * CREATE TABLE t1 ( id int, name varchar(300)) engine=pbxt; + * + * begin; + * insert t1(id, name) values(1, "aaa"); + * update t1 set name=REPEAT('A', 300) where id = 1; + * commit; + * flush tables; + * select * from t1; + * + * Because the type of record changes, from VARIABLE to + * EXTENDED, the cleanup needs to take this into account. + * + * The input new status value which is written here + * depends on the first write to the record. + * However, the second write changes the record status. + * + * Previously we used a OR function to write the bit and + * return the byte value of the result. + * + * The write funtion now checks the record to be written + * to make sure it matches the record that needs to be + * cleaned. So OR'ing the bit is no longer required. + * + * POTENTIAL BUG 2: + * + * We have changed this to fix the following bug: + * + * T1 starts + * T2 starts + * T2 insert record 100 in row 50 + * T2 commits + * T1 updates row 50 and adds record 101 + * + * The sweeper does cleanup in order T1, T2, ... + * + * The sweeper cleans T1 by removing record 100 from the + * row 50 variation list. + * This means that record 100 is free. + * + * The sweeper cleans T2 by marking record 100 as clean. + * !BUG! record 100 has already been freed! + * + * To avoid this we have to check a record before + * cleaning (as we do above for update in xn_sw_cleanup_done()) + * We check that the record is, in fact, the exact + * record that was inserted. + * + * This is now done be xt_tc_write_cond(). + */ + xtOpSeqNo op_seq; + + rec_head.tr_rec_type_1 = rec_type | XT_TAB_STATUS_CLEANED_BIT; + if(!tab->tab_recs.xt_tc_write_cond(self, ot->ot_rec_file, rec_id, rec_head.tr_rec_type_1, &op_seq, xn_id, row_id, stat_id, rec_type)) + /* this means record was not updated by xt_tc_write_bor and doesn't need to */ + break; + if (!xt_xlog_modify_table(ot, XT_LOG_ENT_REC_CLEANED_1, op_seq, 0, rec_id, 1, &rec_head.tr_rec_type_1)) + throw_(); + xn_sw_clean_indices(self, ot, rec_id, row_id, rec_buf, ss->ss_databuf.db_data); + break; + } + case XT_LOG_ENT_DELETE: + case XT_LOG_ENT_DELETE_FL: + case XT_LOG_ENT_DELETE_BG: + case XT_LOG_ENT_DELETE_FL_BG: + if (xn_sw_cleanup_done(self, ot, rec_id, xn_id, rec_type, stat_id, row_id, &rec_head)) + goto done_ok; + after_rec_id = XT_GET_DISK_4(rec_head.tr_prev_rec_id_4); + xt_sw_delete_variations(self, ss, ot, after_rec_id, row_id, xn_id); + xt_sw_delete_variation(self, ss, ot, rec_id, TRUE, row_id, xn_id); + if (row_id) { + if (!xt_tab_free_row(ot, tab, row_id)) + throw_(); + } + break; + } + } + else { + /* The transaction has been aborted. Remove the variation from the + * variation list. If this means the list is empty, then remove + * the record as well. + */ + xtRecordID first_rec_id, next_rec_id, prev_rec_id; + XTTabRecHeadDRec prev_rec_head; + + if (xn_sw_cleanup_done(self, ot, rec_id, xn_id, rec_type, stat_id, row_id, &rec_head)) + goto done_ok; + + if (!row_id) + row_id = XT_GET_DISK_4(rec_head.tr_row_id_4); + after_rec_id = XT_GET_DISK_4(rec_head.tr_prev_rec_id_4); + if (!row_id) + goto unlink_done; + + /* Now remove the record from the variation list, + * (if it is still on the list). + */ + XT_TAB_ROW_WRITE_LOCK(&tab->tab_row_rwlock[row_id % XT_ROW_RWLOCKS], self); + + /* Find the variation before the variation we wish to remove: */ + if (!(xt_tab_get_row(ot, row_id, &first_rec_id))) + goto failed; + prev_rec_id = 0; + next_rec_id = first_rec_id; + while (next_rec_id != rec_id) { + if (!next_rec_id) { + /* The record was not found in the list (we are done) */ + XT_TAB_ROW_UNLOCK(&tab->tab_row_rwlock[row_id % XT_ROW_RWLOCKS], self); + goto unlink_done; + } + if (!xt_tab_get_rec_data(ot, next_rec_id, sizeof(XTTabRecHeadDRec), (xtWord1 *) &prev_rec_head)) { + xt_log_and_clear_exception(self); + break; + } + prev_rec_id = next_rec_id; + next_rec_id = XT_GET_DISK_4(prev_rec_head.tr_prev_rec_id_4); + } + + if (next_rec_id == rec_id) { + /* The record was found on the list: */ + if (prev_rec_id) { + /* Unlink the deleted variation: + * I have found the following sequence: + * + * 17933 in use 1906112 + * 1906112 delete xact=2901 row=17933 prev=2419240 + * 2419240 delete xact=2899 row=17933 prev=2153360 + * 2153360 record-X C xact=2599 row=17933 prev=0 Xlog=151 Xoff=16824 Xsiz=100 + * + * Despite the following facts which should prevent chains from + * forming: + * + * --- Only one transaction can modify a row + * at any one time. So it is not possible for a new change + * to be linked onto an uncommitted change. + * + * --- Transactions that modify the same row + * twice do not allocate a new record for each change. + * + * -- A change that has been + * rolled back will not be linked onto. Instead + * the new transaction will link to the last. + * Comitted record. + * + * So if the sweeper is slow in doing its job + * we can have the situation that a number of records + * can refer to the last committed record of the + * row. + * + * Only one will be reference by the row pointer. + * + * The other, will all have been rolled back. + * This occurs over here: [(4)] + */ + XT_SET_DISK_4(prev_rec_head.tr_prev_rec_id_4, after_rec_id); + if (!xt_tab_put_log_op_rec_data(ot, XT_LOG_ENT_REC_UNLINKED, 0, prev_rec_id, offsetof(XTTabRecHeadDRec, tr_prev_rec_id_4) + XT_RECORD_ID_SIZE, (xtWord1 *) &prev_rec_head)) + goto failed; + } + else { + /* Variation to be removed at the front of the list. */ + ASSERT(rec_id == first_rec_id); + if (after_rec_id) { + /* Unlink the deleted variation, from the front of the list: */ + if (!xt_tab_set_row(ot, XT_LOG_ENT_ROW_SET, row_id, after_rec_id)) + goto failed; + } + else { + /* No more variations, remove the row: */ + if (!xt_tab_free_row(ot, tab, row_id)) + goto failed; + } + } + } + + XT_TAB_ROW_UNLOCK(&tab->tab_row_rwlock[row_id % XT_ROW_RWLOCKS], self); + + /* Note: even when not found on the row list, the record must still + * be freed. + * + * There might be an exception to this, but there are very definite + * cases where this is required, for example when an unreferenced + * record is found and added to the clean up list xn_add_cu_record(). + */ + + unlink_done: + /* Delete the extended record and index entries: + * + * NOTE! This must be done after we have release the row lock. Because + * a thread that does a duplicate check locks the index, and then + * check whether a row is valid, and can deadlock with + * code that locks a row, then an index! + * + * However, this should all be OK, because the variation has been removed from the + * row variation list at this stage, and now just need to be deleted. + */ + xt_sw_delete_variation(self, ss, ot, rec_id, FALSE, row_id, xn_id); + } + + done_ok: + return OK; + + failed: + XT_TAB_ROW_UNLOCK(&tab->tab_row_rwlock[row_id % XT_ROW_RWLOCKS], self); + throw_(); + return FAILED; +} + +/* Go through all updated records of a transaction and cleanup. + * This means, of the transaction was aborted, then all the variations written + * by the transaction must be removed. + * If the transaction was committed then we remove older variations. + * If a delete was committed this can lead to the row being removed. + * + * After a transaction has been cleaned it can be removed from RAM. + * If this was the last transaction in a log, and the log has reached + * threshold, and the log is no longer in exclusive use, then the log + * can be deleted. + * + * This function returns OK if the transaction was cleaned up, FALSE + * if a retry is required. Othersize an error is thrown. + */ +static xtBool xn_sw_cleanup_xact(XTThreadPtr self, XNSweeperStatePtr ss, XTXactDataPtr xact) +{ + XTDatabaseHPtr db = ss->ss_db; + XTXactLogBufferDPtr record; + xtTableID tab_id; + xtRecordID rec_id; + xtXactID xn_id; + xtRowID row_id; + + if (!db->db_xlog.xlog_seq_start(&ss->ss_seqread, xact->xd_begin_log, xact->xd_begin_offset, FALSE)) + xt_throw(self); + + for (;;) { + if (self->t_quit) + return FAILED; + + xn_sw_could_go_faster(self, db); + + if (!db->db_xlog.xlog_seq_next(&ss->ss_seqread, &record, FALSE, self)) + xt_throw(self); + if (!record) { + /* Recovered transactions are considered cleaned when we + * reach the end of the transaction log. + * This is required, because transactions that do + * not have a commit (or rollback) record, because they were + * running when the server last went down, will otherwise not + * have the cleanup completed!! + */ + ASSERT(xact->xd_flags & XT_XN_XAC_RECOVERED); + if (!(xact->xd_flags & XT_XN_XAC_RECOVERED)) + return FAILED; + goto cleanup_done; + } + switch (record->xh.xh_status_1) { + case XT_LOG_ENT_NEW_LOG: + if (!db->db_xlog.xlog_seq_start(&ss->ss_seqread, XT_GET_DISK_4(record->xl.xl_log_id_4), 0, FALSE)) + xt_throw(self); + break; + case XT_LOG_ENT_COMMIT: + case XT_LOG_ENT_ABORT: + xn_id = XT_GET_DISK_4(record->xe.xe_xact_id_4); + if (xn_id == xact->xd_start_xn_id) + goto cleanup_done; + break; + case XT_LOG_ENT_REC_MODIFIED: + case XT_LOG_ENT_UPDATE: + case XT_LOG_ENT_INSERT: + case XT_LOG_ENT_DELETE: + case XT_LOG_ENT_UPDATE_BG: + case XT_LOG_ENT_INSERT_BG: + case XT_LOG_ENT_DELETE_BG: + xn_id = XT_GET_DISK_4(record->xu.xu_xact_id_4); + if (xn_id != xact->xd_start_xn_id) + break; + tab_id = XT_GET_DISK_4(record->xu.xu_tab_id_4); + rec_id = XT_GET_DISK_4(record->xu.xu_rec_id_4); + row_id = XT_GET_DISK_4(record->xu.xu_row_id_4); + if (!xn_sw_cleanup_variation(self, ss, xact, tab_id, rec_id, record->xu.xu_status_1, record->xu.xu_rec_type_1, record->xu.xu_stat_id_1, row_id, &record->xu.xu_rec_type_1)) + return FAILED; + break; + case XT_LOG_ENT_UPDATE_FL: + case XT_LOG_ENT_INSERT_FL: + case XT_LOG_ENT_DELETE_FL: + case XT_LOG_ENT_UPDATE_FL_BG: + case XT_LOG_ENT_INSERT_FL_BG: + case XT_LOG_ENT_DELETE_FL_BG: + xn_id = XT_GET_DISK_4(record->xf.xf_xact_id_4); + if (xn_id != xact->xd_start_xn_id) + break; + tab_id = XT_GET_DISK_4(record->xf.xf_tab_id_4); + rec_id = XT_GET_DISK_4(record->xf.xf_rec_id_4); + row_id = XT_GET_DISK_4(record->xf.xf_row_id_4); + if (!xn_sw_cleanup_variation(self, ss, xact, tab_id, rec_id, record->xf.xf_status_1, record->xf.xf_rec_type_1, record->xf.xf_stat_id_1, row_id, &record->xf.xf_rec_type_1)) + return FAILED; + break; + default: + break; + } + } + + cleanup_done: + /* Write the log to indicate the transaction has been cleaned: */ + XTXactCleanupEntryDRec cu; + + cu.xc_status_1 = XT_LOG_ENT_CLEANUP; + cu.xc_checksum_1 = XT_CHECKSUM_1(XT_CHECKSUM4_XACT(xact->xd_start_xn_id)); + XT_SET_DISK_4(cu.xc_xact_id_4, xact->xd_start_xn_id); + + if (!xt_xlog_log_data(self, sizeof(XTXactCleanupEntryDRec), (XTXactLogBufferDPtr) &cu, FALSE)) + return FAILED; + + ss->ss_flush_pending = TRUE; + + xact->xd_flags |= XT_XN_XAC_CLEANED; + ASSERT(db->db_xn_to_clean_id == xact->xd_start_xn_id); +#ifdef MUST_DELAY_REMOVE + xn_sw_add_xact_to_free(self, ss, xact->xd_start_xn_id); +#else + xn_id = xact->xd_start_xn_id; + if (xt_xn_delete_xact(db, xn_id, self)) { + /* Recalculate the minimum memory transaction: */ + ASSERT(!xt_xn_is_before(xn_id, db->db_xn_min_ram_id)); + + if (db->db_xn_min_ram_id == xn_id) { + db->db_xn_min_ram_id = xn_id+1; + } + else { + xtXactID xn_curr_xn_id = xt_xn_get_curr_id(db); + + while (!xt_xn_is_before(xn_curr_xn_id, db->db_xn_min_ram_id)) { // was db->db_xn_min_ram_id <= xn_curr_xn_id + /* db_xn_min_ram_id may be changed, by some other process! */ + xn_id = db->db_xn_min_ram_id; + if (xn_get_xact_details(db, xn_id, self, NULL, NULL, NULL, NULL)) + break; + db->db_xn_min_ram_id = xn_id+1; + } + } + } +#endif + + return OK; +} + +static void xn_free_sw_state(XTThreadPtr self, XNSweeperStatePtr ss) +{ + xn_sw_close_open_table(self, ss); + if (ss->ss_db) + ss->ss_db->db_xlog.xlog_seq_exit(&ss->ss_seqread); + xt_db_set_size(self, &ss->ss_databuf, 0); + xt_bq_set_size(self, &ss->ss_to_free, 0); +} + +static void xn_sw_main(XTThreadPtr self) +{ + XTDatabaseHPtr db = self->st_database; + XNSweeperStatePtr ss; + XTXactDataPtr xact, xact2; + time_t idle_start = 0; + xtXactID curr_id; + + xt_set_priority(self, xt_db_sweeper_priority); + + alloczr_(ss, xn_free_sw_state, sizeof(XNSweeperStateRec), XNSweeperStatePtr); + ss->ss_db = db; + + if (!db->db_xlog.xlog_seq_init(&ss->ss_seqread, xt_db_log_buffer_size, FALSE)) + xt_throw(self); + + ss->ss_to_free.bq_item_size = sizeof(XNSWToFreeItemRec); + ss->ss_to_free.bq_max_waste = XT_TN_MAX_TO_FREE_WASTE; + ss->ss_to_free.bq_item_inc = XT_TN_MAX_TO_FREE_INC; + ss->ss_call_cnt = 0; + ss->ss_flush_pending = FALSE; + + while (!self->t_quit) { + while (!self->t_quit) { + /* We are just about to check the condition for sleeping, + * so if the condition for sleeping holds, then we wil + * exit the loop and sleep. + * + * We will then sleep if nobody sets the flag before we + * actually do sleep! + */ + curr_id = xt_xn_get_curr_id(db); + if (xt_xn_is_before(curr_id, db->db_xn_to_clean_id)) { + db->db_sw_faster &= ~XT_SW_TOO_FAR_BEHIND; + break; + } + /* {TUNING} How far to we allow the sweeper to get behind? + * The higher this is, the higher burst performance can + * be. But too high and the sweeper falls out of reading the + * transaction log cache, and also starts to spread + * changes around in index and data blocks that are no + * longer hot. + */ + if (curr_id - db->db_xn_to_clean_id > 250) + db->db_sw_faster |= XT_SW_TOO_FAR_BEHIND; + else + db->db_sw_faster &= ~XT_SW_TOO_FAR_BEHIND; + xn_sw_could_go_faster(self, db); + idle_start = 0; + + if ((xact = xt_xn_get_xact(db, db->db_xn_to_clean_id, self))) { + xtXactID xn_id; + + if (!(xact->xd_flags & XT_XN_XAC_SWEEP)) + /* Transaction has not yet ending, and ready to sweep. */ + goto sleep; + + /* Check if we can cleanup the transaction. + * We do this by checking to see if there is any running + * transaction which start before the end of this transaction. + */ + xn_id = xact->xd_start_xn_id; + while (xt_xn_is_before(xn_id, xact->xd_end_xn_id)) { + xn_id++; + if ((xact2 = xt_xn_get_xact(db, xn_id, self))) { + if (!(xact2->xd_flags & XT_XN_XAC_ENDED)) { + /* A transaction was started before the end of + * the transaction we wish to sweep, and this + * transaction has not committed, the we have to + * wait. + */ + db->db_stat_sweep_waits++; + goto sleep; + } + } + } + + /* Can cleanup the transaction, and move to the next. */ + if (xact->xd_flags & XT_XN_XAC_LOGGED) { +#ifdef TRACE_SWEEPER_ACTIVITY + printf("SWEEPER: cleanup %d\n", (int) xact->xd_start_xn_id); +#endif + if (!xn_sw_cleanup_xact(self, ss, xact)) { + /* We failed to clean (try again later)... */ +#ifdef TRACE_SWEEPER_ACTIVITY + printf("SWEEPER: cleanup retry...\n", (int) xact->xd_start_xn_id); +#endif + goto sleep; + } +#ifdef TRACE_SWEEPER_ACTIVITY + printf("SWEEPER: cleanup DONE\n", (int) xact->xd_start_xn_id); +#endif + } + else { + /* This was a read-only transaction, it is safe to + * just remove the transaction structure from memory. + * (should not be necessary because RO transactions + * do this themselves): + */ + if (xt_xn_delete_xact(db, db->db_xn_to_clean_id, self)) { + if (db->db_xn_min_ram_id == db->db_xn_to_clean_id) + db->db_xn_min_ram_id = db->db_xn_to_clean_id+1; + } + } + } + + /* Move on to clean the next: */ + db->db_xn_to_clean_id++; + } + + sleep: + + xn_sw_close_open_table(self, ss); + + xn_sw_go_slower(self, db); + + /* Shrink the free list, if it is empty, and larger then + * the default: + */ + if (ss->ss_to_free.bq_size > XT_TN_MAX_TO_FREE) { + if (ss->ss_to_free.bq_front == 0 && ss->ss_to_free.bq_back == 0) + xt_bq_set_size(self, &ss->ss_to_free, XT_TN_MAX_TO_FREE); + } + + /* Windows: close the log file that we have open for reading, if we + * read past the end of the log on the last transaction. + * This makes sure that the log is closed when the checkpointer + * tries to remove or rename it!! + */ + if (ss->ss_seqread.xseq_log_file) { + if (ss->ss_seqread.xseq_rec_log_id != ss->ss_seqread.xseq_log_id) + db->db_xlog.xlog_seq_close(&ss->ss_seqread); + } + + if (ss->ss_flush_pending) { + /* Flush pending means we have written something to the log. + * + * if so we flush the log so that the writer will also do + * its work! + * + * This will lead to the freeer continuing if it is waiting. + */ + + time_t now = time(NULL); + if (idle_start) { + /* By default, we wait for 2 seconds idle time, the + * we flush the log. + */ + if (now >= idle_start + 2) { + if (!xt_xlog_flush_log(self)) + xt_throw(self); + ss->ss_flush_pending = FALSE; + } + } + else + idle_start = now; + } + + /* {WAKE-SW} Waking up the sweeper is very expensive! + * Cost is 3% of execution time on the test: + * runTest(SMALL_SELECT_TEST, 2, 100000) + * + * On the other hand, polling every 1/10 second + * is cheap, because the check for transactions + * ready for cleanup is very quick. + * + * So this is the prefered method. + */ + xn_sw_wait_for_xact(self, db, 10); + } + + if (ss->ss_flush_pending) { + xt_xlog_flush_log(self); + ss->ss_flush_pending = FALSE; + } + + freer_(); // xn_free_sw_state(ss) +} + +static void *xn_sw_run_thread(XTThreadPtr self) +{ + XTDatabaseHPtr db = (XTDatabaseHPtr) self->t_data; + int count; + void *mysql_thread; + + mysql_thread = myxt_create_thread(); + + while (!self->t_quit) { + try_(a) { + /* + * The garbage collector requires that the database + * is in use because. + */ + xt_use_database(self, db, XT_FOR_SWEEPER); + + /* This action is both safe and required: + * + * safe: releasing the database is safe because as + * long as this thread is running the database + * reference is valid, and this reference cannot + * be the only one to the database because + * otherwize this thread would not be running. + * + * required: releasing the database is necessary + * otherwise we cannot close the database + * correctly because we only shutdown this + * thread when the database is closed and we + * only close the database when all references + * are removed. + */ + xt_heap_release(self, self->st_database); + + xn_sw_main(self); + } + catch_(a) { + /* This error is "normal"! */ + if (self->t_exception.e_xt_err != XT_ERR_NO_DICTIONARY && + !(self->t_exception.e_xt_err == XT_SIGNAL_CAUGHT && + self->t_exception.e_sys_err == SIGTERM)) + xt_log_and_clear_exception(self); + } + cont_(a); + + /* Avoid releasing the database (done above) */ + self->st_database = NULL; + xt_unuse_database(self, self); + + /* After an exception, pause before trying again... */ + /* Number of seconds */ +#ifdef DEBUG + count = 10; +#else + count = 2*60; +#endif + db->db_sw_idle = XT_THREAD_INERR; + while (!self->t_quit && count > 0) { + sleep(1); + count--; + } + db->db_sw_idle = XT_THREAD_BUSY; + } + + myxt_destroy_thread(mysql_thread, TRUE); + return NULL; +} + +static void xn_sw_free_thread(XTThreadPtr self, void *data) +{ + XTDatabaseHPtr db = (XTDatabaseHPtr) data; + + if (db->db_sw_thread) { + xt_lock_mutex(self, &db->db_sw_lock); + pushr_(xt_unlock_mutex, &db->db_sw_lock); + db->db_sw_thread = NULL; + freer_(); // xt_unlock_mutex(&db->db_sw_lock) + } +} + +/* Wait for a transaction to quit: */ +static void xn_sw_wait_for_xact(XTThreadPtr self, XTDatabaseHPtr db, u_int hsecs) +{ + xt_lock_mutex(self, &db->db_sw_lock); + pushr_(xt_unlock_mutex, &db->db_sw_lock); + db->db_sw_idle = XT_THREAD_IDLE; + if (!self->t_quit && !db->db_sw_faster) + xt_timed_wait_cond(self, &db->db_sw_cond, &db->db_sw_lock, hsecs * 10); + db->db_sw_idle = XT_THREAD_BUSY; + db->db_sw_check_count++; + freer_(); // xt_unlock_mutex(&db->db_sw_lock) +} + +xtPublic void xt_start_sweeper(XTThreadPtr self, XTDatabaseHPtr db) +{ + char name[PATH_MAX]; + + sprintf(name, "SW-%s", xt_last_directory_of_path(db->db_main_path)); + xt_remove_dir_char(name); + db->db_sw_thread = xt_create_daemon(self, name); + xt_set_thread_data(db->db_sw_thread, db, xn_sw_free_thread); + xt_run_thread(self, db->db_sw_thread, xn_sw_run_thread); +} + +xtPublic void xt_wait_for_sweeper(XTThreadPtr self, XTDatabaseHPtr db, int abort_time) +{ + time_t then, now; + xtBool message = FALSE; + + if (db->db_sw_thread) { + then = time(NULL); + while (!xt_xn_is_before(xt_xn_get_curr_id(db), db->db_xn_to_clean_id)) { // was db->db_xn_to_clean_id <= xt_xn_get_curr_id(db) + xt_lock_mutex(self, &db->db_sw_lock); + pushr_(xt_unlock_mutex, &db->db_sw_lock); + xt_wakeup_sweeper(db); + freer_(); // xt_unlock_mutex(&db->db_sw_lock) + xt_sleep_milli_second(10); + now = time(NULL); + if (abort_time && now >= then + abort_time) { + xt_logf(XT_NT_INFO, "Aborting wait for '%s' sweeper\n", db->db_name); + message = FALSE; + break; + } + if (now >= then + 2) { + if (!message) { + message = TRUE; + xt_logf(XT_NT_INFO, "Waiting for '%s' sweeper...\n", db->db_name); + } + } + } + + if (message) + xt_logf(XT_NT_INFO, "Sweeper '%s' done.\n", db->db_name); + } +} + +xtPublic void xt_stop_sweeper(XTThreadPtr self, XTDatabaseHPtr db) +{ + XTThreadPtr thr_sw; + + if (db->db_sw_thread) { + xt_lock_mutex(self, &db->db_sw_lock); + pushr_(xt_unlock_mutex, &db->db_sw_lock); + + /* This pointer is safe as long as you have the transaction lock. */ + if ((thr_sw = db->db_sw_thread)) { + xtThreadID tid = thr_sw->t_id; + + /* Make sure the thread quits when woken up. */ + xt_terminate_thread(self, thr_sw); + + xt_wakeup_sweeper(db); + + freer_(); // xt_unlock_mutex(&db->db_sw_lock) + + /* + * GOTCHA: This is a wierd thing but the SIGTERM directed + * at a particular thread (in this case the sweeper) was + * being caught by a different thread and killing the server + * sometimes. Disconcerting. + * (this may only be a problem on Mac OS X) + xt_kill_thread(thread); + */ + xt_wait_for_thread(tid, FALSE); + + /* PMC - This should not be necessary to set the signal here, but in the + * debugger the handler is not called!!? + thr_sw->t_delayed_signal = SIGTERM; + xt_kill_thread(thread); + */ + db->db_sw_thread = NULL; + } + else + freer_(); // xt_unlock_mutex(&db->db_sw_lock) + } +} + +xtPublic void xt_wakeup_sweeper(XTDatabaseHPtr db) +{ + /* This flag makes the gap for the race condition + * very small. + * + * However, this posibility still remains because + * we do not lock the mutex db_sw_lock here. + * + * The reason is that it is too expensive. + * + * In the event that the wakeup is missed the sleeper + * wait will timeout eventually. + */ + if (db->db_sw_idle) { + if (!xt_broadcast_cond_ns(&db->db_sw_cond)) + xt_log_and_clear_exception_ns(); + } +} diff --git a/storage/pbxt/src/xaction_xt.h b/storage/pbxt/src/xaction_xt.h new file mode 100644 index 00000000000..9a651fc2532 --- /dev/null +++ b/storage/pbxt/src/xaction_xt.h @@ -0,0 +1,184 @@ +/* Copyright (c) 2005 PrimeBase Technologies GmbH + * + * PrimeBase XT + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You 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 + * + * 2005-04-10 Paul McCullagh + * + * H&G2JCtL + */ +#ifndef __xt_xaction_h__ +#define __xt_xaction_h__ + +#include "filesys_xt.h" +#include "lock_xt.h" + +struct XTThread; +struct XTDatabase; +struct XTOpenTable; + +#ifdef DEBUG +//#define XT_USE_XACTION_DEBUG_SIZES +#endif + +#ifdef XT_USE_XACTION_DEBUG_SIZES + +#define XT_XN_DATA_ALLOC_COUNT 400 +#define XT_XN_SEGMENT_SHIFTS 1 +#define XT_XN_HASH_TABLE_SIZE 31 +#define XT_TN_NUMBER_INCREMENT 20 +#define XT_TN_MAX_TO_FREE 20 +#define XT_TN_MAX_TO_FREE_WASTE 3 +#define XT_TN_MAX_TO_FREE_CHECK 3 +#define XT_TN_MAX_TO_FREE_INC 3 + +#else + +#define XT_XN_DATA_ALLOC_COUNT 1250 // Number of pre-allocated transaction data structures per segment +#define XT_XN_SEGMENT_SHIFTS 5 // (32) +#define XT_XN_HASH_TABLE_SIZE 1279 // This is a prime number! +#define XT_TN_NUMBER_INCREMENT 100 // The increment of the transaction number on restart +#define XT_TN_MAX_TO_FREE 800 // The maximum size of the "to free" list +#define XT_TN_MAX_TO_FREE_WASTE 400 +#define XT_TN_MAX_TO_FREE_CHECK 100 // Once we have exceeded the limit, we only try in intervals +#define XT_TN_MAX_TO_FREE_INC 100 + +#endif + +#define XT_XN_NO_OF_SEGMENTS (1 << XT_XN_SEGMENT_SHIFTS) +#define XT_XN_SEGMENT_MASK (XT_XN_NO_OF_SEGMENTS - 1) + +#define XT_XN_XAC_LOGGED 1 +#define XT_XN_XAC_ENDED 2 /* The transaction has ended. */ +#define XT_XN_XAC_COMMITTED 4 /* The transaction was committed. */ +#define XT_XN_XAC_CLEANED 8 /* The transaction has been cleaned. */ +#define XT_XN_XAC_RECOVERED 16 /* This transaction was detected on recovery. */ +#define XT_XN_XAC_SWEEP 32 /* End ID has been set, OK to sweep. */ + +#define XT_XN_VISIBLE 0 /* The transaction is committed, and the record is visible. */ +#define XT_XN_NOT_VISIBLE 1 /* The transaction is committed, but not visible. */ +#define XT_XN_ABORTED 2 /* Transaction was aborted. */ +#define XT_XN_MY_UPDATE 3 /* The record was update by me. */ +#define XT_XN_OTHER_UPDATE 4 /* The record was updated by someone else. */ +#define XT_XN_REREAD 5 /* The transaction is not longer in RAM, status is unkown, retry. */ + +typedef struct XTXactData { + xtXactID xd_start_xn_id; /* Note: may be zero!. */ + xtXactID xd_end_xn_id; /* Note: may be zero!. */ + + /* The begin position: */ + xtLogID xd_begin_log; /* Non-zero if begin has been logged. */ + xtLogOffset xd_begin_offset; + int xd_flags; + xtWord4 xd_end_time; + xtThreadID xd_thread_id; + + /* A transaction may be indexed twice in the hash table. + * Once on the start sequence number, and once on the + * end sequence number. + */ + struct XTXactData *xd_next_xact; /* Next pointer in the hash table, also used by the free list. */ + +} XTXactDataRec, *XTXactDataPtr; + +#define XT_XACT_USE_SPINLOCK + +#ifdef XT_XACT_USE_FASTWRLOCK +#define XT_XACT_LOCK_TYPE XTFastRWLockRec +#define XT_XACT_INIT_LOCK(s, i) xt_fastrwlock_init(s, i) +#define XT_XACT_FREE_LOCK(s, i) xt_fastrwlock_free(s, i) +#define XT_XACT_READ_LOCK(i, s) xt_fastrwlock_slock(i, s) +#define XT_XACT_WRITE_LOCK(i, s) xt_fastrwlock_xlock(i, s) +#define XT_XACT_UNLOCK(i, s) xt_fastrwlock_unlock(i, s) +#elif defined(XT_XACT_USE_PTHREAD_RW) +#define XT_XACT_LOCK_TYPE xt_rwlock_type +#define XT_XACT_INIT_LOCK(s, i) xt_init_rwlock(s, i) +#define XT_XACT_FREE_LOCK(s, i) xt_free_rwlock(i) +#define XT_XACT_READ_LOCK(i, s) xt_slock_rwlock_ns(i) +#define XT_XACT_WRITE_LOCK(i, s) xt_xlock_rwlock_ns(i) +#define XT_XACT_UNLOCK(i, s) xt_unlock_rwlock_ns(i) +#elif defined(XT_XACT_USE_RW_MUTEX) +#define XT_XACT_LOCK_TYPE XTRWMutexRec +#define XT_XACT_INIT_LOCK(s, i) xt_rwmutex_init(s, i) +#define XT_XACT_FREE_LOCK(s, i) xt_rwmutex_free(s, i) +#define XT_XACT_READ_LOCK(i, s) xt_rwmutex_slock(i, (s)->t_id) +#define XT_XACT_WRITE_LOCK(i, s) xt_rwmutex_xlock(i, (s)->t_id) +#define XT_XACT_UNLOCK(i, s) xt_rwmutex_unlock(i, (s)->t_id) +#else +#define XT_XACT_LOCK_TYPE XTSpinLockRec +#define XT_XACT_INIT_LOCK(s, i) xt_spinlock_init_with_autoname(s, i) +#define XT_XACT_FREE_LOCK(s, i) xt_spinlock_free(s, i) +#define XT_XACT_READ_LOCK(i, s) xt_spinlock_lock(i) +#define XT_XACT_WRITE_LOCK(i, s) xt_spinlock_lock(i) +#define XT_XACT_UNLOCK(i, s) xt_spinlock_unlock(i) +#endif + +/* We store the transactions in a number of segments, each + * segment has a hash table. + */ +typedef struct XTXactSeg { + XT_XACT_LOCK_TYPE xs_tab_lock; /* Lock for hash table. */ + xtXactID xs_last_xn_id; /* The last transaction ID added. */ + XTXactDataPtr xs_free_list; /* List of transaction data structures. */ + XTXactDataPtr xs_table[XT_XN_HASH_TABLE_SIZE]; /* Hash table containing the transaction data structures. */ +} XTXactSegRec, *XTXactSegPtr; + +typedef struct XTXactWait { + xtXactID xw_xn_id; +} XTXactWaitRec, *XTXactWaitPtr; + +void xt_thread_wait_init(struct XTThread *self); +void xt_thread_wait_exit(struct XTThread *self); + +void xt_xn_init_db(struct XTThread *self, struct XTDatabase *db); +void xt_xn_exit_db(struct XTThread *self, struct XTDatabase *db); +void xt_start_sweeper(struct XTThread *self, struct XTDatabase *db); +void xt_wait_for_sweeper(struct XTThread *self, struct XTDatabase *db, int abort_time); +void xt_stop_sweeper(struct XTThread *self, struct XTDatabase *db); + +void xt_xn_init_thread(struct XTThread *self, int what_for); +void xt_xn_exit_thread(struct XTThread *self); +void xt_wakeup_sweeper(struct XTDatabase *db); + +xtBool xt_xn_begin(struct XTThread *self); +xtBool xt_xn_commit(struct XTThread *self); +xtBool xt_xn_rollback(struct XTThread *self); +xtBool xt_xn_log_tab_id(struct XTThread *self, xtTableID tab_id); +int xt_xn_status(struct XTOpenTable *ot, xtXactID xn_id, xtRecordID rec_id); +xtBool xt_xn_wait_for_xact(struct XTThread *self, XTXactWaitPtr xw, struct XTLockWait *lw); +void xt_xn_wakeup_waiting_threads(struct XTThread *thread); +void xt_xn_wakeup_thread_list(struct XTThread *thread); +void xt_xn_wakeup_thread(xtThreadID thd_id); +xtXactID xt_xn_get_curr_id(struct XTDatabase *db); +xtWord8 xt_xn_bytes_to_sweep(struct XTDatabase *db, struct XTThread *thread); + +XTXactDataPtr xt_xn_add_old_xact(struct XTDatabase *db, xtXactID xn_id, struct XTThread *thread); +XTXactDataPtr xt_xn_get_xact(struct XTDatabase *db, xtXactID xn_id, struct XTThread *thread); +xtBool xt_xn_delete_xact(struct XTDatabase *db, xtXactID xn_id, struct XTThread *thread); + +inline xtBool xt_xn_is_before(register xtXactID now, register xtXactID then) +{ + if (now >= then) { + if ((now - then) > (xtXactID) 0xFFFFFFFF/2) + return TRUE; + return FALSE; + } + if ((then - now) > (xtXactID) 0xFFFFFFFF/2) + return FALSE; + return TRUE; +} + +#endif diff --git a/storage/pbxt/src/xactlog_xt.cc b/storage/pbxt/src/xactlog_xt.cc new file mode 100644 index 00000000000..82c0d85b770 --- /dev/null +++ b/storage/pbxt/src/xactlog_xt.cc @@ -0,0 +1,2853 @@ +/* Copyright (c) 2007 PrimeBase Technologies GmbH + * + * PrimeBase XT + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You 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 + * + * 2007-10-30 Paul McCullagh + * + * H&G2JCtL + * + * The transaction log contains all operations on the data handle + * and row pointer files of a table. + * + * The transaction log does not contain operations on index data. + */ + +#include "xt_config.h" + +#include <signal.h> + +#include "xactlog_xt.h" +#include "database_xt.h" +#include "util_xt.h" +#include "strutil_xt.h" +#include "filesys_xt.h" +#include "myxt_xt.h" +#include "trace_xt.h" + +#ifdef DEBUG +//#define PRINT_TABLE_MODIFICATIONS +//#define TRACE_WRITER_ACTIVITY +#endif +#ifndef XT_WIN +#ifndef XT_MAC +#define PREWRITE_LOG_COMPLETELY +#endif +#endif + +static void xlog_wr_log_written(XTDatabaseHPtr db); + +/* + * ----------------------------------------------------------------------- + * T R A N S A C T I O L O G C A C H E + */ + +static XTXLogCacheRec xt_xlog_cache; + +/* + * Initialize the disk cache. + */ +xtPublic void xt_xlog_init(XTThreadPtr self, size_t cache_size) +{ + XTXLogBlockPtr block; + + /* + * This is required to ensure that the block + * works! + */ + + /* Determine the number of block that will fit into the given memory: */ + /* + xt_xlog_cache.xlc_hash_size = (cache_size / (XLC_SEGMENT_COUNT * sizeof(XTXLogBlockPtr) + sizeof(XTXLogBlockRec))) / (XLC_SEGMENT_COUNT >> 1); + xt_xlog_cache.xlc_block_count = (cache_size - (XLC_SEGMENT_COUNT * xt_xlog_cache.xlc_hash_size * sizeof(XTXLogBlockPtr))) / sizeof(XTXLogBlockRec); + */ + /* Do not count the size of the cache directory towards the cache size: */ + xt_xlog_cache.xlc_block_count = cache_size / sizeof(XTXLogBlockRec); + xt_xlog_cache.xlc_upper_limit = ((xtWord8) xt_xlog_cache.xlc_block_count * (xtWord8) XT_XLC_BLOCK_SIZE * (xtWord8) 3) / (xtWord8) 4; + xt_xlog_cache.xlc_hash_size = xt_xlog_cache.xlc_block_count / (XLC_SEGMENT_COUNT >> 1); + if (!xt_xlog_cache.xlc_hash_size) + xt_xlog_cache.xlc_hash_size = 1; + + try_(a) { + for (u_int i=0; i<XLC_SEGMENT_COUNT; i++) { + xt_xlog_cache.xlc_segment[i].lcs_hash_table = (XTXLogBlockPtr *) xt_calloc(self, xt_xlog_cache.xlc_hash_size * sizeof(XTXLogBlockPtr)); + xt_init_mutex_with_autoname(self, &xt_xlog_cache.xlc_segment[i].lcs_lock); + xt_init_cond(self, &xt_xlog_cache.xlc_segment[i].lcs_cond); + } + + block = (XTXLogBlockPtr) xt_malloc(self, xt_xlog_cache.xlc_block_count * sizeof(XTXLogBlockRec)); + xt_xlog_cache.xlc_blocks = block; + xt_xlog_cache.xlc_blocks_end = (XTXLogBlockPtr) ((char *) block + (xt_xlog_cache.xlc_block_count * sizeof(XTXLogBlockRec))); + xt_xlog_cache.xlc_next_to_free = block; + xt_init_mutex_with_autoname(self, &xt_xlog_cache.xlc_lock); + xt_init_cond(self, &xt_xlog_cache.xlc_cond); + + for (u_int i=0; i<xt_xlog_cache.xlc_block_count; i++) { + block->xlb_address = 0; + block->xlb_log_id = 0; + block->xlb_state = XLC_BLOCK_FREE; + block++; + } + xt_xlog_cache.xlc_free_count = xt_xlog_cache.xlc_block_count; + } + catch_(a) { + xt_xlog_exit(self); + throw_(); + } + cont_(a); +} + +xtPublic void xt_xlog_exit(XTThreadPtr self) +{ + for (u_int i=0; i<XLC_SEGMENT_COUNT; i++) { + if (xt_xlog_cache.xlc_segment[i].lcs_hash_table) { + xt_free(self, xt_xlog_cache.xlc_segment[i].lcs_hash_table); + xt_xlog_cache.xlc_segment[i].lcs_hash_table = NULL; + xt_free_mutex(&xt_xlog_cache.xlc_segment[i].lcs_lock); + xt_free_cond(&xt_xlog_cache.xlc_segment[i].lcs_cond); + } + } + + if (xt_xlog_cache.xlc_blocks) { + xt_free(self, xt_xlog_cache.xlc_blocks); + xt_xlog_cache.xlc_blocks = NULL; + xt_free_mutex(&xt_xlog_cache.xlc_lock); + xt_free_cond(&xt_xlog_cache.xlc_cond); + } + memset(&xt_xlog_cache, 0, sizeof(xt_xlog_cache)); +} + +xtPublic xtInt8 xt_xlog_get_usage() +{ + xtInt8 size; + + size = (xtInt8) (xt_xlog_cache.xlc_block_count - xt_xlog_cache.xlc_free_count) * sizeof(XTXLogBlockRec); + return size; +} + +xtPublic xtInt8 xt_xlog_get_size() +{ + xtInt8 size; + + size = (xtInt8) xt_xlog_cache.xlc_block_count * sizeof(XTXLogBlockRec); + return size; +} + +xtPublic xtLogID xt_xlog_get_min_log(XTThreadPtr self, XTDatabaseHPtr db) +{ + char path[PATH_MAX]; + XTOpenDirPtr od; + char *file; + xtLogID log_id, min_log = 0; + + xt_strcpy(PATH_MAX, path, db->db_main_path); + xt_add_system_dir(PATH_MAX, path); + if (xt_fs_exists(path)) { + pushsr_(od, xt_dir_close, xt_dir_open(self, path, NULL)); + while (xt_dir_next(self, od)) { + file = xt_dir_name(self, od); + if (xt_starts_with(file, "xlog")) { + if ((log_id = (xtLogID) xt_file_name_to_id(file))) { + if (!min_log || log_id < min_log) + min_log = log_id; + } + } + } + freer_(); // xt_dir_close(od) + } + if (!min_log) + return 1; + return min_log; +} + +xtPublic void xt_xlog_delete_logs(XTThreadPtr self, XTDatabaseHPtr db) +{ + char path[PATH_MAX]; + XTOpenDirPtr od; + char *file; + + /* Close all the index logs before we delete them: */ + db->db_indlogs.ilp_close(self, TRUE); + + /* Close the transaction logs too: */ + db->db_xlog.xlog_close(self); + + xt_strcpy(PATH_MAX, path, db->db_main_path); + xt_add_system_dir(PATH_MAX, path); + if (!xt_fs_exists(path)) + return; + pushsr_(od, xt_dir_close, xt_dir_open(self, path, NULL)); + while (xt_dir_next(self, od)) { + file = xt_dir_name(self, od); + if (xt_ends_with(file, ".xt")) { + xt_add_dir_char(PATH_MAX, path); + xt_strcat(PATH_MAX, path, file); + xt_fs_delete(self, path); + xt_remove_last_name_of_path(path); + } + } + freer_(); // xt_dir_close(od) + + /* I no longer attach the condition: !db->db_multi_path + * to removing this directory. This is because + * the pbxt directory must now be removed explicitly + * by drop database, or by delete all the PBXT + * system tables. + */ + if (!xt_fs_rmdir(NULL, path)) + xt_log_and_clear_exception(self); +} + +#ifdef DEBUG_CHECK_CACHE +static void xt_xlog_check_cache(void) +{ + XTXLogBlockPtr block, pblock; + u_int used_count; + u_int free_count; + + // Check the LRU list: + used_count = 0; + pblock = NULL; + block = xt_xlog_cache.xlc_lru_block; + while (block) { + used_count++; + ASSERT_NS(block->xlb_state != XLC_BLOCK_FREE); + ASSERT_NS(block->xlb_lr_used == pblock); + pblock = block; + block = block->xlb_mr_used; + } + ASSERT_NS(xt_xlog_cache.xlc_mru_block == pblock); + ASSERT_NS(xt_xlog_cache.xlc_free_count + used_count == xt_xlog_cache.xlc_block_count); + + // Check the free list: + free_count = 0; + block = xt_xlog_cache.xlc_free_list; + while (block) { + free_count++; + ASSERT_NS(block->xlb_state == XLC_BLOCK_FREE); + block = block->xlb_next; + } + ASSERT_NS(xt_xlog_cache.xlc_free_count == free_count); +} +#endif + +#ifdef FOR_DEBUG +static void xlog_check_lru_list(XTXLogBlockPtr block) +{ + XTXLogBlockPtr list_block, plist_block; + + plist_block = NULL; + list_block = xt_xlog_cache.xlc_lru_block; + while (list_block) { + ASSERT_NS(block != list_block); + ASSERT_NS(list_block->xlb_lr_used == plist_block); + plist_block = list_block; + list_block = list_block->xlb_mr_used; + } + ASSERT_NS(xt_xlog_cache.xlc_mru_block == plist_block); +} +#endif + +/* + * Log cache blocks are used and freed on a round-robin basis. + * In addition, only data read by restart, and data transfered + * from the transaction log are stored in the transaction log. + * + * This ensures that the transaction log contains the most + * recently written log data. + * + * If the sweeper gets behind due to a long running transacation + * then it falls out of the log cache, and must read from + * the log files directly. + * + * This data read is no longer cached as it was previously. + * This has the advantage that it does not disturn the writter + * thread which would otherwise hit the cache. + * + * If transactions are not too long, it should be possible + * to keep the sweeper in the log cache. + */ +static xtBool xlog_free_block(XTXLogBlockPtr to_free) +{ + XTXLogBlockPtr block, pblock; + xtLogID log_id; + off_t address; + XTXLogCacheSegPtr seg; + u_int hash_idx; + + retry: + log_id = to_free->xlb_log_id; + address = to_free->xlb_address; + + seg = &xt_xlog_cache.xlc_segment[((u_int) address >> XT_XLC_BLOCK_SHIFTS) & XLC_SEGMENT_MASK]; + hash_idx = (((u_int) (address >> (XT_XLC_SEGMENT_SHIFTS + XT_XLC_BLOCK_SHIFTS))) ^ (log_id << 16)) % xt_xlog_cache.xlc_hash_size; + + xt_lock_mutex_ns(&seg->lcs_lock); + if (to_free->xlb_state == XLC_BLOCK_FREE) + goto done_ok; + if (to_free->xlb_log_id != log_id || to_free->xlb_address != address) { + xt_unlock_mutex_ns(&seg->lcs_lock); + goto retry; + } + + pblock = NULL; + block = seg->lcs_hash_table[hash_idx]; + while (block) { + if (block->xlb_address == address && block->xlb_log_id == log_id) { + ASSERT_NS(block == to_free); + ASSERT_NS(block->xlb_state != XLC_BLOCK_FREE); + + /* Wait if the block is being read: */ + if (block->xlb_state == XLC_BLOCK_READING) { + /* Wait for the block to be read, then try again. */ + if (!xt_timed_wait_cond_ns(&seg->lcs_cond, &seg->lcs_lock, 100)) + goto failed; + xt_unlock_mutex_ns(&seg->lcs_lock); + goto retry; + } + + goto free_the_block; + } + pblock = block; + block = block->xlb_next; + } + + /* We did not find the block, someone else freed it... */ + xt_unlock_mutex_ns(&seg->lcs_lock); + goto retry; + + free_the_block: + ASSERT_NS(block->xlb_state == XLC_BLOCK_CLEAN); + + /* Remove from the hash table: */ + if (pblock) + pblock->xlb_next = block->xlb_next; + else + seg->lcs_hash_table[hash_idx] = block->xlb_next; + + /* Free the block: */ + xt_xlog_cache.xlc_free_count++; + block->xlb_state = XLC_BLOCK_FREE; + + done_ok: + xt_unlock_mutex_ns(&seg->lcs_lock); + return OK; + + failed: + xt_unlock_mutex_ns(&seg->lcs_lock); + return FAILED; +} + +#define XT_FETCH_READ 0 +#define XT_FETCH_BLANK 1 +#define XT_FETCH_TEST 2 + +static xtBool xlog_fetch_block(XTXLogBlockPtr *ret_block, XTOpenFilePtr file, xtLogID log_id, off_t address, XTXLogCacheSegPtr *ret_seg, int fetch_type, XTThreadPtr thread) +{ + register XTXLogBlockPtr block; + register XTXLogCacheSegPtr seg; + register u_int hash_idx; + register XTXLogCacheRec *dcg = &xt_xlog_cache; + size_t red_size; + + /* Make sure we have a free block ready (to avoid unlock below): */ + if (fetch_type != XT_FETCH_TEST && dcg->xlc_next_to_free->xlb_state != XLC_BLOCK_FREE) { + if (!xlog_free_block(dcg->xlc_next_to_free)) + return FAILED; + } + + seg = &dcg->xlc_segment[((u_int) address >> XT_XLC_BLOCK_SHIFTS) & XLC_SEGMENT_MASK]; + hash_idx = (((u_int) (address >> (XT_XLC_SEGMENT_SHIFTS + XT_XLC_BLOCK_SHIFTS))) ^ (log_id << 16)) % dcg->xlc_hash_size; + + xt_lock_mutex_ns(&seg->lcs_lock); + retry: + block = seg->lcs_hash_table[hash_idx]; + while (block) { + if (block->xlb_address == address && block->xlb_log_id == log_id) { + ASSERT_NS(block->xlb_state != XLC_BLOCK_FREE); + + /* + * Wait if the block is being read. + */ + if (block->xlb_state == XLC_BLOCK_READING) { + if (!xt_timed_wait_cond_ns(&seg->lcs_cond, &seg->lcs_lock, 100)) { + xt_unlock_mutex_ns(&seg->lcs_lock); + return FAILED; + } + goto retry; + } + + *ret_seg = seg; + *ret_block = block; + thread->st_statistics.st_xlog_cache_hit++; + return OK; + } + block = block->xlb_next; + } + + if (fetch_type == XT_FETCH_TEST) { + xt_unlock_mutex_ns(&seg->lcs_lock); + *ret_seg = NULL; + *ret_block = NULL; + thread->st_statistics.st_xlog_cache_miss++; + return OK; + } + + /* Block not found: */ + get_free_block: + if (dcg->xlc_next_to_free->xlb_state != XLC_BLOCK_FREE) { + xt_unlock_mutex_ns(&seg->lcs_lock); + if (!xlog_free_block(dcg->xlc_next_to_free)) + return FAILED; + xt_lock_mutex_ns(&seg->lcs_lock); + } + + xt_lock_mutex_ns(&dcg->xlc_lock); + block = dcg->xlc_next_to_free; + if (block->xlb_state != XLC_BLOCK_FREE) { + xt_unlock_mutex_ns(&dcg->xlc_lock); + goto get_free_block; + } + dcg->xlc_next_to_free++; + if (dcg->xlc_next_to_free == dcg->xlc_blocks_end) + dcg->xlc_next_to_free = dcg->xlc_blocks; + dcg->xlc_free_count--; + + if (fetch_type == XT_FETCH_READ) { + block->xlb_address = address; + block->xlb_log_id = log_id; + block->xlb_state = XLC_BLOCK_READING; + + xt_unlock_mutex_ns(&dcg->xlc_lock); + + /* Add the block to the hash table: */ + block->xlb_next = seg->lcs_hash_table[hash_idx]; + seg->lcs_hash_table[hash_idx] = block; + + /* Read the block into memory: */ + xt_unlock_mutex_ns(&seg->lcs_lock); + + if (!xt_pread_file(file, address, XT_XLC_BLOCK_SIZE, 0, block->xlb_data, &red_size, &thread->st_statistics.st_xlog, thread)) + return FAILED; + memset(block->xlb_data + red_size, 0, XT_XLC_BLOCK_SIZE - red_size); + thread->st_statistics.st_xlog_cache_miss++; + + xt_lock_mutex_ns(&seg->lcs_lock); + block->xlb_state = XLC_BLOCK_CLEAN; + xt_cond_wakeall(&seg->lcs_cond); + } + else { + block->xlb_address = address; + block->xlb_log_id = log_id; + block->xlb_state = XLC_BLOCK_CLEAN; + memset(block->xlb_data, 0, XT_XLC_BLOCK_SIZE); + + xt_unlock_mutex_ns(&dcg->xlc_lock); + + /* Add the block to the hash table: */ + block->xlb_next = seg->lcs_hash_table[hash_idx]; + seg->lcs_hash_table[hash_idx] = block; + } + + *ret_seg = seg; + *ret_block = block; +#ifdef DEBUG_CHECK_CACHE + //xt_xlog_check_cache(); +#endif + return OK; +} + +static xtBool xlog_transfer_to_cache(XTOpenFilePtr file, xtLogID log_id, off_t offset, size_t size, xtWord1 *data, XTThreadPtr thread) +{ + off_t address; + XTXLogBlockPtr block; + XTXLogCacheSegPtr seg; + size_t boff; + size_t tfer; + xtBool read_block = FALSE; + +#ifdef DEBUG_CHECK_CACHE + //xt_xlog_check_cache(); +#endif + /* We have to read the first block, if we are + * not at the begining of the file: + */ + if (offset) + read_block = TRUE; + address = offset & ~XT_XLC_BLOCK_MASK; + + boff = (size_t) (offset - address); + tfer = XT_XLC_BLOCK_SIZE - boff; + if (tfer > size) + tfer = size; + while (size > 0) { + if (!xlog_fetch_block(&block, file, log_id, address, &seg, read_block ? XT_FETCH_READ : XT_FETCH_BLANK, thread)) { +#ifdef DEBUG_CHECK_CACHE + //xt_xlog_check_cache(); +#endif + return FAILED; + } + ASSERT_NS(block && block->xlb_state == XLC_BLOCK_CLEAN); + memcpy(block->xlb_data + boff, data, tfer); + xt_unlock_mutex_ns(&seg->lcs_lock); + size -= tfer; + data += tfer; + + /* Following block need not be read + * because we always transfer to the + * end of the file! + */ + read_block = FALSE; + address += XT_XLC_BLOCK_SIZE; + + boff = 0; + tfer = size; + if (tfer > XT_XLC_BLOCK_SIZE) + tfer = XT_XLC_BLOCK_SIZE; + } +#ifdef DEBUG_CHECK_CACHE + //xt_xlog_check_cache(); +#endif + return OK; +} + +static xtBool xt_xlog_read(XTOpenFilePtr file, xtLogID log_id, off_t offset, size_t size, xtWord1 *data, xtBool load_cache, XTThreadPtr thread) +{ + off_t address; + XTXLogBlockPtr block; + XTXLogCacheSegPtr seg; + size_t boff; + size_t tfer; + +#ifdef DEBUG_CHECK_CACHE + //xt_xlog_check_cache(); +#endif + address = offset & ~XT_XLC_BLOCK_MASK; + boff = (size_t) (offset - address); + tfer = XT_XLC_BLOCK_SIZE - boff; + if (tfer > size) + tfer = size; + while (size > 0) { + if (!xlog_fetch_block(&block, file, log_id, address, &seg, load_cache ? XT_FETCH_READ : XT_FETCH_TEST, thread)) + return FAILED; + if (!block) { + size_t red_size; + + if (!xt_pread_file(file, address + boff, size, 0, data, &red_size, &thread->st_statistics.st_xlog, thread)) + return FAILED; + memset(data + red_size, 0, size - red_size); + return OK; + } + memcpy(data, block->xlb_data + boff, tfer); + xt_unlock_mutex_ns(&seg->lcs_lock); + size -= tfer; + data += tfer; + address += XT_XLC_BLOCK_SIZE; + boff = 0; + tfer = size; + if (tfer > XT_XLC_BLOCK_SIZE) + tfer = XT_XLC_BLOCK_SIZE; + } +#ifdef DEBUG_CHECK_CACHE + //xt_xlog_check_cache(); +#endif + return OK; +} + +static xtBool xt_xlog_write(XTOpenFilePtr file, xtLogID log_id, off_t offset, size_t size, xtWord1 *data, XTThreadPtr thread) +{ + if (!xt_pwrite_file(file, offset, size, data, &thread->st_statistics.st_xlog, thread)) + return FAILED; + return xlog_transfer_to_cache(file, log_id, offset, size, data, thread); +} + +/* + * ----------------------------------------------------------------------- + * D A T A B A S E T R A N S A C T I O N L O G S + */ + +void XTDatabaseLog::xlog_setup(XTThreadPtr self, XTDatabaseHPtr db, off_t inp_log_file_size, size_t transaction_buffer_size, int log_count) +{ + volatile off_t log_file_size = inp_log_file_size; + size_t log_size; + + try_(a) { + memset(this, 0, sizeof(XTDatabaseLogRec)); + + if (log_count <= 1) + log_count = 1; + else if (log_count > 1000000) + log_count = 1000000; + + xl_db = db; + + xl_log_file_threshold = xt_align_offset(log_file_size, 1024); + xl_log_file_count = log_count; + xl_size_of_buffers = transaction_buffer_size; + + xt_init_mutex_with_autoname(self, &xl_write_lock); + xt_init_cond(self, &xl_write_cond); + xt_writing = 0; + xl_log_id = 0; + xl_log_file = 0; + + xt_spinlock_init_with_autoname(self, &xl_buffer_lock); + + /* Note that we allocate a little bit more for each buffer + * in order to make sure that we can write a trailing record + * to the log buffer. + */ + log_size = transaction_buffer_size + sizeof(XTXactNewLogEntryDRec); + + /* Add in order to round the buffer to an integral of 512 */ + if (log_size % 512) + log_size += (512 - (log_size % 512)); + + xl_write_log_id = 0; + xl_write_log_offset = 0; + xl_write_buf_pos = 0; + xl_write_buf_pos_start = 0; + xl_write_buffer = (xtWord1 *) xt_malloc(self, log_size); + xl_write_done = TRUE; + + xl_append_log_id = 0; + xl_append_log_offset = 0; + xl_append_buf_pos = 0; + xl_append_buf_pos_start = 0; + xl_append_buffer = (xtWord1 *) xt_malloc(self, log_size); + + xl_last_flush_time = 10; + xl_flush_log_id = 0; + xl_flush_log_offset = 0; + } + catch_(a) { + xlog_exit(self); + throw_(); + } + cont_(a); +} + +xtBool XTDatabaseLog::xlog_set_write_offset(xtLogID log_id, xtLogOffset log_offset, xtLogID max_log_id, XTThreadPtr thread) +{ + xl_max_log_id = max_log_id; + + xl_write_log_id = log_id; + xl_write_log_offset = log_offset; + xl_write_buf_pos = 0; + xl_write_buf_pos_start = 0; + xl_write_done = TRUE; + + xl_append_log_id = log_id; + xl_append_log_offset = log_offset; + if (log_offset == 0) { + XTXactLogHeaderDPtr log_head; + + log_head = (XTXactLogHeaderDPtr) xl_append_buffer; + memset(log_head, 0, sizeof(XTXactLogHeaderDRec)); + log_head->xh_status_1 = XT_LOG_ENT_HEADER; + log_head->xh_checksum_1 = XT_CHECKSUM_1(log_id); + XT_SET_DISK_4(log_head->xh_size_4, sizeof(XTXactLogHeaderDRec)); + XT_SET_DISK_4(log_head->xh_log_id_4, log_id); + XT_SET_DISK_2(log_head->xh_version_2, XT_LOG_VERSION_NO); + XT_SET_DISK_4(log_head->xh_magic_4, XT_LOG_FILE_MAGIC); + xl_append_buf_pos = sizeof(XTXactLogHeaderDRec); + xl_append_buf_pos_start = 0; + } + else { + /* Start the log buffer at a block boundary: */ + size_t buf_pos = (size_t) (log_offset % 512); + + xl_append_buf_pos = buf_pos; + xl_append_buf_pos_start = buf_pos; + xl_append_log_offset = log_offset - buf_pos; + + if (!xlog_open_log(log_id, log_offset, thread)) + return FAILED; + + if (!xt_pread_file(xl_log_file, xl_append_log_offset, buf_pos, buf_pos, xl_append_buffer, NULL, &thread->st_statistics.st_xlog, thread)) + return FAILED; + } + + xl_flush_log_id = log_id; + xl_flush_log_offset = log_offset; + return OK; +} + +void XTDatabaseLog::xlog_close(XTThreadPtr self) +{ + if (xl_log_file) { + xt_close_file(self, xl_log_file); + xl_log_file = NULL; + } +} + +void XTDatabaseLog::xlog_exit(XTThreadPtr self) +{ + xt_spinlock_free(self, &xl_buffer_lock); + xt_free_mutex(&xl_write_lock); + xt_free_cond(&xl_write_cond); + xlog_close(self); + if (xl_write_buffer) { + xt_free(self, xl_write_buffer); + xl_write_buffer = NULL; + } + if (xl_append_buffer) { + xt_free(self, xl_append_buffer); + xl_append_buffer = NULL; + } +} + +#define WR_NO_SPACE 1 +#define WR_FLUSH 2 + +xtBool XTDatabaseLog::xlog_flush(XTThreadPtr thread) +{ + if (!xlog_flush_pending()) + return OK; + return xlog_append(thread, 0, NULL, 0, NULL, TRUE, NULL, NULL); +} + +xtBool XTDatabaseLog::xlog_flush_pending() +{ + xtLogID req_flush_log_id; + xtLogOffset req_flush_log_offset; + + xt_lck_slock(&xl_buffer_lock); + req_flush_log_id = xl_append_log_id; + req_flush_log_offset = xl_append_log_offset + xl_append_buf_pos; + if (xt_comp_log_pos(req_flush_log_id, req_flush_log_offset, xl_flush_log_id, xl_flush_log_offset) <= 0) { + xt_spinlock_unlock(&xl_buffer_lock); + return FALSE; + } + xt_spinlock_unlock(&xl_buffer_lock); + return TRUE; +} + +/* + * Write data to the end of the log buffer. + * + * commit is set to true if the caller also requires + * the log to be flushed, after writing the data. + * + * This function returns the log ID and offset of + * the data write position. + */ +xtBool XTDatabaseLog::xlog_append(XTThreadPtr thread, size_t size1, xtWord1 *data1, size_t size2, xtWord1 *data2, xtBool commit, xtLogID *log_id, xtLogOffset *log_offset) +{ + int write_reason = 0; + xtLogID req_flush_log_id; + xtLogOffset req_flush_log_offset; + size_t part_size; + xtWord8 flush_time; + + if (!size1) { + /* Just flush the buffer... */ + xt_lck_slock(&xl_buffer_lock); + write_reason = WR_FLUSH; + req_flush_log_id = xl_append_log_id; + req_flush_log_offset = xl_append_log_offset + xl_append_buf_pos; + xt_spinlock_unlock(&xl_buffer_lock); + goto write_log_to_file; + } + else { + req_flush_log_id = 0; + req_flush_log_offset = 0; + } + + /* + * This is a dirty read, which will send us to the + * best starting position: + * + * If there is space, now, then there is probably + * still enough space, after we have locked the + * buffer for writting. + */ + if (xl_append_buf_pos + size1 + size2 <= xl_size_of_buffers) + goto copy_to_log_buffer; + + /* + * There is not enough space in the append buffer. + * So we need to write the log, until there is space. + */ + write_reason = WR_NO_SPACE; + + write_log_to_file: + if (write_reason) { + /* We need to write for one of 2 reasons: not + * enough space in the buffer, or a flush + * is required. + */ + + /* + * The objective of the following code is to + * pick one writer, out of all threads. + * The others rest will wait for the writer. + */ + xtBool i_am_writer; + + if (write_reason == WR_FLUSH) { + /* Before we flush, check if we should wait for running + * transactions that may commit shortly. + */ + if (xl_db->db_xn_writer_count - xl_db->db_xn_writer_wait_count - xl_db->db_xn_long_running_count > 0 && xl_last_flush_time) { + /* Wait for about as long as the last flush took, + * the idea is to saturate the disk with flushing...: */ + xtWord8 then = xt_trace_clock() + (xtWord8) xl_last_flush_time; + + for (;;) { + xt_critical_wait(); + /* If a thread leaves this loop because times up, or + * a thread manages to flush so fast that this thread + * sleeps during this time, then it could be that + * the required flush occurs before other conditions + * of this loop are met! + * + * So we check here to make sure that the log has not been + * flushed as we require: + */ + if (xt_comp_log_pos(req_flush_log_id, req_flush_log_offset, xl_flush_log_id, xl_flush_log_offset) <= 0) { + ASSERT_NS(xt_comp_log_pos(xl_write_log_id, xl_write_log_offset, xl_append_log_id, xl_append_log_offset) <= 0); + return OK; + } + + if (xl_db->db_xn_writer_count - xl_db->db_xn_writer_wait_count - xl_db->db_xn_long_running_count > 0) + break; + if (xt_trace_clock() >= then) + break; + } + } + } + + i_am_writer = FALSE; + xt_lock_mutex_ns(&xl_write_lock); + if (xt_writing) { + if (!xt_timed_wait_cond_ns(&xl_write_cond, &xl_write_lock, 500)) { + xt_unlock_mutex_ns(&xl_write_lock); + return FALSE; + } + } + else { + xt_writing = TRUE; + i_am_writer = TRUE; + } + xt_unlock_mutex_ns(&xl_write_lock); + + if (!i_am_writer) { + /* If I am not the writer, then I just waited for the + * writer. So it may be that my requirements have now + * been met! + */ + if (write_reason == WR_FLUSH) { + /* If the reason was to flush, then + * check the last flush sequence, maybe it is passed + * our required sequence. + */ + if (xt_comp_log_pos(req_flush_log_id, req_flush_log_offset, xl_flush_log_id, xl_flush_log_offset) <= 0) { + /* The required flush position of the log is before + * or equal to the actual flush position. This means the condition + * for this thread have been satified (via group commit). + * Nothing more to do! + */ + ASSERT_NS(xt_comp_log_pos(xl_write_log_id, xl_write_log_offset, xl_append_log_id, xl_append_log_offset) <= 0); + return OK; + } + goto write_log_to_file; + } + + /* It may be that there is now space in the append buffer: */ + if (xl_append_buf_pos + size1 + size2 <= xl_size_of_buffers) + goto copy_to_log_buffer; + + goto write_log_to_file; + } + + /* I am the writer, check the conditions, again: */ + if (write_reason == WR_FLUSH) { + /* The writer wants the log to be flushed to a particular point: */ + if (xt_comp_log_pos(req_flush_log_id, req_flush_log_offset, xl_flush_log_id, xl_flush_log_offset) <= 0) { + /* The writers required flush position is before or equal + * to the actual position, so the writer is done... + */ + xt_writing = FALSE; + xt_cond_wakeall(&xl_write_cond); + ASSERT_NS(xt_comp_log_pos(xl_write_log_id, xl_write_log_offset, xl_append_log_id, xl_append_log_offset) <= 0); + return OK; + } + /* Not flushed, but what about written? */ + if (xt_comp_log_pos(req_flush_log_id, req_flush_log_offset, xl_write_log_id, xl_write_log_offset + (xl_write_done ? xl_write_buf_pos : 0)) <= 0) { + /* The write position is after or equal to the required flush + * position. This means that all we have to do is flush + * to satisfy the writers condition. + */ + xtBool ok = TRUE; + + if (xl_log_id != xl_write_log_id) + ok = xlog_open_log(xl_write_log_id, xl_write_log_offset + (xl_write_done ? xl_write_buf_pos : 0), thread); + + if (ok) { + if (xl_db->db_co_busy) { + /* [(8)] Flush the compactor log. */ + xt_lock_mutex_ns(&xl_db->db_co_dlog_lock); + ok = xl_db->db_co_thread->st_dlog_buf.dlb_flush_log(TRUE, thread); + xt_unlock_mutex_ns(&xl_db->db_co_dlog_lock); + } + } + + if (ok) { + flush_time = thread->st_statistics.st_xlog.ts_flush_time; + if ((ok = xt_flush_file(xl_log_file, &thread->st_statistics.st_xlog, thread))) { + xl_last_flush_time = (u_int) (thread->st_statistics.st_xlog.ts_flush_time - flush_time); + xl_log_bytes_flushed = xl_log_bytes_written; + + xt_lock_mutex_ns(&xl_db->db_wr_lock); + xl_flush_log_id = xl_write_log_id; + xl_flush_log_offset = xl_write_log_offset + (xl_write_done ? xl_write_buf_pos : 0); + /* + * We have written data to the log, wake the writer to commit + * the data to the database. + */ + xlog_wr_log_written(xl_db); + xt_unlock_mutex_ns(&xl_db->db_wr_lock); + } + } + xt_writing = FALSE; + xt_cond_wakeall(&xl_write_cond); + ASSERT_NS(xt_comp_log_pos(xl_write_log_id, xl_write_log_offset, xl_append_log_id, xl_append_log_offset) <= 0); + return ok; + } + } + else { + /* If there is space in the buffer, then we can go on + * to copy our data into the buffer: + */ + if (xl_append_buf_pos + size1 + size2 <= xl_size_of_buffers) { + xt_writing = FALSE; + xt_cond_wakeall(&xl_write_cond); + goto copy_to_log_buffer; + } + } + + rewrite: + /* If the current write buffer has been written, then + * switch the logs. Otherwise we must try to existing + * write buffer. + */ + if (xl_write_done) { + /* This means that the current write buffer has been writen, + * i.e. it is empty! + */ + xt_spinlock_lock(&xl_buffer_lock); + xtWord1 *tmp_buffer = xl_write_buffer; + + /* The write position is now the append position: */ + xl_write_log_id = xl_append_log_id; + xl_write_log_offset = xl_append_log_offset; + xl_write_buf_pos = xl_append_buf_pos; + xl_write_buf_pos_start = xl_append_buf_pos_start; + xl_write_buffer = xl_append_buffer; + xl_write_done = FALSE; + + /* We have to maintain 512 byte alignment: */ + ASSERT_NS((xl_write_log_offset % 512) == 0); + part_size = xl_write_buf_pos % 512; + if (part_size != 0) + memcpy(tmp_buffer, xl_write_buffer + xl_write_buf_pos - part_size, part_size); + + /* The new append position will be after the + * current append position: + */ + xl_append_log_offset += xl_append_buf_pos - part_size; + xl_append_buf_pos = part_size; + xl_append_buf_pos_start = part_size; + xl_append_buffer = tmp_buffer; // The old write buffer (which is empty) + + /* + * If the append offset exceeds the log threshhold, then + * we set the append buffer to a new log file: + * + * NOTE: This algorithm will cause the log to be overwriten by a maximum + * of the log buffer size! + */ + if (xl_append_log_offset >= xl_log_file_threshold) { + XTXactNewLogEntryDPtr log_tail; + XTXactLogHeaderDPtr log_head; + + xl_append_log_id++; + + /* Write the final record to the old log. + * There is enough space for this because we allocate the + * buffer a little bigger than required. + */ + log_tail = (XTXactNewLogEntryDPtr) (xl_write_buffer + xl_write_buf_pos); + log_tail->xl_status_1 = XT_LOG_ENT_NEW_LOG; + log_tail->xl_checksum_1 = XT_CHECKSUM_1(xl_append_log_id) ^ XT_CHECKSUM_1(xl_write_log_id); + XT_SET_DISK_4(log_tail->xl_log_id_4, xl_append_log_id); + xl_write_buf_pos += sizeof(XTXactNewLogEntryDRec); + + /* We add the header to the next log. */ + log_head = (XTXactLogHeaderDPtr) xl_append_buffer; + memset(log_head, 0, sizeof(XTXactLogHeaderDRec)); + log_head->xh_status_1 = XT_LOG_ENT_HEADER; + log_head->xh_checksum_1 = XT_CHECKSUM_1(xl_append_log_id); + XT_SET_DISK_4(log_head->xh_size_4, sizeof(XTXactLogHeaderDRec)); + XT_SET_DISK_4(log_head->xh_log_id_4, xl_append_log_id); + XT_SET_DISK_2(log_head->xh_version_2, XT_LOG_VERSION_NO); + XT_SET_DISK_4(log_head->xh_magic_4, XT_LOG_FILE_MAGIC); + + xl_append_log_offset = 0; + xl_append_buf_pos = sizeof(XTXactLogHeaderDRec); + xl_append_buf_pos_start = 0; + } + xt_spinlock_unlock(&xl_buffer_lock); + /* We have completed the switch. The append buffer is empty, and + * other threads can begin to write to it. + * + * Meanwhile, this thread will write the write buffer... + */ + } + + /* Make sure we have the correct log open: */ + if (xl_log_id != xl_write_log_id) { + if (!xlog_open_log(xl_write_log_id, xl_write_log_offset, thread)) + goto write_failed; + } + + /* Write the buffer. */ + /* Always write an integral number of 512 byte blocks: */ + ASSERT_NS((xl_write_log_offset % 512) == 0); + if ((part_size = xl_write_buf_pos % 512)) { + part_size = 512 - part_size; + xl_write_buffer[xl_write_buf_pos] = XT_LOG_ENT_END_OF_LOG; + if (!xt_pwrite_file(xl_log_file, xl_write_log_offset, xl_write_buf_pos+part_size, xl_write_buffer, &thread->st_statistics.st_xlog, thread)) + goto write_failed; + } + else { + if (!xt_pwrite_file(xl_log_file, xl_write_log_offset, xl_write_buf_pos, xl_write_buffer, &thread->st_statistics.st_xlog, thread)) + goto write_failed; + } + + /* This part has not been written: */ + part_size = xl_write_buf_pos - xl_write_buf_pos_start; + + /* We have written the data to the log, transfer + * the buffer data into the cache. */ + if (!xlog_transfer_to_cache(xl_log_file, xl_log_id, xl_write_log_offset+xl_write_buf_pos_start, part_size, xl_write_buffer+xl_write_buf_pos_start, thread)) + goto write_failed; + + xl_write_done = TRUE; + xl_log_bytes_written += part_size; + + if (write_reason == WR_FLUSH) { + if (xl_db->db_co_busy) { + /* [(8)] Flush the compactor log. */ + xt_lock_mutex_ns(&xl_db->db_co_dlog_lock); + if (!xl_db->db_co_thread->st_dlog_buf.dlb_flush_log(TRUE, thread)) { + xt_unlock_mutex_ns(&xl_db->db_co_dlog_lock); + goto write_failed; + } + xt_unlock_mutex_ns(&xl_db->db_co_dlog_lock); + } + + /* And flush if required: */ + flush_time = thread->st_statistics.st_xlog.ts_flush_time; + if (!xt_flush_file(xl_log_file, &thread->st_statistics.st_xlog, thread)) + goto write_failed; + xl_last_flush_time = (u_int) (thread->st_statistics.st_xlog.ts_flush_time - flush_time); + + xl_log_bytes_flushed = xl_log_bytes_written; + + xt_lock_mutex_ns(&xl_db->db_wr_lock); + xl_flush_log_id = xl_write_log_id; + xl_flush_log_offset = xl_write_log_offset + xl_write_buf_pos; + /* + * We have written data to the log, wake the writer to commit + * the data to the database. + */ + xlog_wr_log_written(xl_db); + xt_unlock_mutex_ns(&xl_db->db_wr_lock); + + /* Check that the require flush condition has arrived. */ + if (xt_comp_log_pos(req_flush_log_id, req_flush_log_offset, xl_flush_log_id, xl_flush_log_offset) > 0) + /* The required position is still after the current flush + * position, continue writing: */ + goto rewrite; + + xt_writing = FALSE; + xt_cond_wakeall(&xl_write_cond); + ASSERT_NS(xt_comp_log_pos(xl_write_log_id, xl_write_log_offset, xl_append_log_id, xl_append_log_offset) <= 0); + return OK; + } + else + xlog_wr_log_written(xl_db); + + /* + * Check that the buffer is now available, otherwise, + * switch and write again! + */ + if (xl_append_buf_pos + size1 + size2 > xl_size_of_buffers) + goto rewrite; + + xt_writing = FALSE; + xt_cond_wakeall(&xl_write_cond); + } + + copy_to_log_buffer: + xt_spinlock_lock(&xl_buffer_lock); + /* Now we have to check again. The check above was a dirty read! + */ + if (xl_append_buf_pos + size1 + size2 > xl_size_of_buffers) { + xt_spinlock_unlock(&xl_buffer_lock); + /* Not enough space, write the buffer, and return here. */ + write_reason = WR_NO_SPACE; + goto write_log_to_file; + } + + memcpy(xl_append_buffer + xl_append_buf_pos, data1, size1); + if (size2) + memcpy(xl_append_buffer + xl_append_buf_pos + size1, data2, size2); + /* Add the log ID to the checksum! + * This is required because log files are re-used, and we don't + * want the records to be valid when the log is re-used. + */ + register XTXactLogBufferDPtr record; + + /* + * Adjust db_xn_writer_count here. It is protected by + * xl_buffer_lock. + */ + record = (XTXactLogBufferDPtr) (xl_append_buffer + xl_append_buf_pos); + switch (record->xh.xh_status_1) { + case XT_LOG_ENT_HEADER: + case XT_LOG_ENT_END_OF_LOG: + break; + case XT_LOG_ENT_REC_MODIFIED: + case XT_LOG_ENT_UPDATE: + case XT_LOG_ENT_UPDATE_BG: + case XT_LOG_ENT_UPDATE_FL: + case XT_LOG_ENT_UPDATE_FL_BG: + case XT_LOG_ENT_INSERT: + case XT_LOG_ENT_INSERT_BG: + case XT_LOG_ENT_INSERT_FL: + case XT_LOG_ENT_INSERT_FL_BG: + case XT_LOG_ENT_DELETE: + case XT_LOG_ENT_DELETE_BG: + case XT_LOG_ENT_DELETE_FL: + case XT_LOG_ENT_DELETE_FL_BG: + xtWord2 sum; + + sum = XT_GET_DISK_2(record->xu.xu_checksum_2) ^ XT_CHECKSUM_2(xl_append_log_id); + XT_SET_DISK_2(record->xu.xu_checksum_2, sum); + + if (!thread->st_xact_writer) { + thread->st_xact_writer = TRUE; + thread->st_xact_write_time = xt_db_approximate_time; + xl_db->db_xn_writer_count++; + xl_db->db_xn_total_writer_count++; + } + break; + case XT_LOG_ENT_ROW_NEW: + case XT_LOG_ENT_ROW_NEW_FL: + record->xl.xl_checksum_1 ^= XT_CHECKSUM_1(xl_append_log_id); + + if (!thread->st_xact_writer) { + thread->st_xact_writer = TRUE; + thread->st_xact_write_time = xt_db_approximate_time; + xl_db->db_xn_writer_count++; + xl_db->db_xn_total_writer_count++; + } + break; + case XT_LOG_ENT_COMMIT: + case XT_LOG_ENT_ABORT: + ASSERT_NS(thread->st_xact_writer); + ASSERT_NS(xl_db->db_xn_writer_count > 0); + if (thread->st_xact_writer) { + xl_db->db_xn_writer_count--; + thread->st_xact_writer = FALSE; + if (thread->st_xact_long_running) { + xl_db->db_xn_long_running_count--; + thread->st_xact_long_running = FALSE; + } + } + /* No break required! */ + default: + record->xl.xl_checksum_1 ^= XT_CHECKSUM_1(xl_append_log_id); + break; + } +#ifdef DEBUG + ASSERT_NS(xlog_verify(record, size1 + size2, xl_append_log_id)); +#endif + if (log_id) + *log_id = xl_append_log_id; + if (log_offset) + *log_offset = xl_append_log_offset + xl_append_buf_pos; + xl_append_buf_pos += size1 + size2; + if (commit) { + write_reason = WR_FLUSH; + req_flush_log_id = xl_append_log_id; + req_flush_log_offset = xl_append_log_offset + xl_append_buf_pos; + xt_spinlock_unlock(&xl_buffer_lock); + goto write_log_to_file; + } + + // Failed sometime when outside the spinlock! + ASSERT_NS(xt_comp_log_pos(xl_write_log_id, xl_write_log_offset, xl_append_log_id, xl_append_log_offset + xl_append_buf_pos) <= 0); + xt_spinlock_unlock(&xl_buffer_lock); + + return OK; + + write_failed: + xt_writing = FALSE; + xt_cond_wakeall(&xl_write_cond); + return FAILED; +} + +/* + * This function does not always delete the log. It may just rename a + * log to a new log which it will need. + * This speeds things up: + * + * - No need to pre-allocate the new log. + * - Log data is already flushed (i.e. disk blocks allocated) + * - Log is already in OS cache. + * + * However, it means that I need to checksum things differently + * on each log to make sure I do not treat an old record + * as valid! + * + * Return OK, FAILED or XT_ERR + */ +int XTDatabaseLog::xlog_delete_log(xtLogID del_log_id, XTThreadPtr thread) +{ + char path[PATH_MAX]; + + if (xl_max_log_id < xl_write_log_id) + xl_max_log_id = xl_write_log_id; + + xlog_name(PATH_MAX, path, del_log_id); + + if (xt_db_offline_log_function == XT_RECYCLE_LOGS) { + char new_path[PATH_MAX]; + xtLogID new_log_id; + xtBool ok; + + /* Make sure that the total logs is less than or equal to the log file count + * (plus dynamic component). + */ + while (xl_max_log_id - del_log_id + 1 <= (xl_log_file_count + xt_log_file_dyn_count) && + /* And the number of logs after the current log (including the current log) + * must be less or equal to the log file count. */ + xl_max_log_id - xl_write_log_id + 1 <= xl_log_file_count) { + new_log_id = xl_max_log_id+1; + xlog_name(PATH_MAX, new_path, new_log_id); + ok = xt_fs_rename(NULL, path, new_path); + if (ok) { + xl_max_log_id = new_log_id; + goto done; + } + if (!xt_fs_exists(new_path)) { + /* Try again later: */ + if (thread->t_exception.e_xt_err == XT_SYSTEM_ERROR && + XT_FILE_IN_USE(thread->t_exception.e_sys_err)) + return FAILED; + + return XT_ERR; + } + xl_max_log_id = new_log_id; + } + } + + if (xt_db_offline_log_function != XT_KEEP_LOGS) { + if (!xt_fs_delete(NULL, path)) { + if (thread->t_exception.e_xt_err == XT_SYSTEM_ERROR && + XT_FILE_IN_USE(thread->t_exception.e_sys_err)) + return FAILED; + + return XT_ERR; + } + } + + done: + return OK; +} + +/* PRIVATE FUNCTIONS */ +xtBool XTDatabaseLog::xlog_open_log(xtLogID log_id, off_t curr_write_pos, XTThreadPtr thread) +{ + char log_path[PATH_MAX]; + off_t eof; + + if (xl_log_id == log_id) + return OK; + + if (xl_log_file) { + if (!xt_flush_file(xl_log_file, &thread->st_statistics.st_xlog, thread)) + return FAILED; + xt_close_file_ns(xl_log_file); + xl_log_file = NULL; + xl_log_id = 0; + } + + xlog_name(PATH_MAX, log_path, log_id); + if (!(xl_log_file = xt_open_file_ns(log_path, XT_FS_CREATE | XT_FS_MAKE_PATH))) + return FAILED; + /* Allocate space until the required size: */ + if (curr_write_pos < xl_log_file_threshold) { + eof = xt_seek_eof_file(NULL, xl_log_file); + if (eof == 0) { + /* A new file (bad), we need a greater file count: */ + xt_log_file_dyn_count++; + xt_log_file_dyn_dec = 4; + } + else { + /* An existing file (good): */ + if (xt_log_file_dyn_count > 0) { + if (xt_log_file_dyn_dec > 0) + xt_log_file_dyn_dec--; + else + xt_log_file_dyn_count--; + } + } + if (eof < xl_log_file_threshold) { + char buffer[2048]; + size_t tfer; + + memset(buffer, 0, 2048); + + curr_write_pos = xt_align_offset(curr_write_pos, 512); +#ifdef PREWRITE_LOG_COMPLETELY + while (curr_write_pos < xl_log_file_threshold) { + tfer = 2048; + if ((off_t) tfer > xl_log_file_threshold - curr_write_pos) + tfer = (size_t) (xl_log_file_threshold - curr_write_pos); + if (curr_write_pos == 0) + *buffer = XT_LOG_ENT_END_OF_LOG; + if (!xt_pwrite_file(xl_log_file, curr_write_pos, tfer, buffer, &thread->st_statistics.st_xlog, thread)) + return FAILED; + *buffer = 0; + curr_write_pos += tfer; + } +#else + if (curr_write_pos < xl_log_file_threshold) { + tfer = 2048; + + if (curr_write_pos < xl_log_file_threshold - 2048) + curr_write_pos = xl_log_file_threshold - 2048; + if ((off_t) tfer > xl_log_file_threshold - curr_write_pos) + tfer = (size_t) (xl_log_file_threshold - curr_write_pos); + if (!xt_pwrite_file(xl_log_file, curr_write_pos, tfer, buffer, &thread->st_statistics.st_xlog, thread)) + return FAILED; + } +#endif + } + else if (eof > xl_log_file_threshold + (128 * 1024 * 1024)) { + if (!xt_set_eof_file(NULL, xl_log_file, xl_log_file_threshold)) + return FAILED; + } + } + xl_log_id = log_id; + return OK; +} + +void XTDatabaseLog::xlog_name(size_t size, char *path, xtLogID log_id) +{ + char name[50]; + + sprintf(name, "xlog-%lu.xt", (u_long) log_id); + xt_strcpy(size, path, xl_db->db_main_path); + xt_add_system_dir(size, path); + xt_add_dir_char(size, path); + xt_strcat(size, path, name); +} + +/* + * ----------------------------------------------------------------------- + * T H R E A D T R A N S A C T I O N B U F F E R + */ + +xtPublic xtBool xt_xlog_flush_log(XTThreadPtr thread) +{ + return thread->st_database->db_xlog.xlog_flush(thread); +} + +xtPublic xtBool xt_xlog_log_data(XTThreadPtr thread, size_t size, XTXactLogBufferDPtr log_entry, xtBool commit) +{ + return thread->st_database->db_xlog.xlog_append(thread, size, (xtWord1 *) log_entry, 0, NULL, commit, NULL, NULL); +} + +/* Allocate a record from the free list. */ +xtPublic xtBool xt_xlog_modify_table(struct XTOpenTable *ot, u_int status, xtOpSeqNo op_seq, xtRecordID free_rec_id, xtRecordID rec_id, size_t size, xtWord1 *data) +{ + XTXactLogBufferDRec log_entry; + XTThreadPtr thread = ot->ot_thread; + XTTableHPtr tab = ot->ot_table; + size_t len; + xtWord4 sum = 0; + int check_size = 1; + XTXactDataPtr xact = NULL; + + switch (status) { + case XT_LOG_ENT_REC_MODIFIED: + case XT_LOG_ENT_UPDATE: + case XT_LOG_ENT_INSERT: + case XT_LOG_ENT_DELETE: + check_size = 2; + XT_SET_DISK_4(log_entry.xu.xu_op_seq_4, op_seq); + XT_SET_DISK_4(log_entry.xu.xu_tab_id_4, tab->tab_id); + XT_SET_DISK_4(log_entry.xu.xu_rec_id_4, rec_id); + XT_SET_DISK_2(log_entry.xu.xu_size_2, size); + len = offsetof(XTactUpdateEntryDRec, xu_rec_type_1); + if (!(thread->st_xact_data->xd_flags & XT_XN_XAC_LOGGED)) { + /* Add _BG: */ + status++; + xact = thread->st_xact_data; + xact->xd_flags |= XT_XN_XAC_LOGGED; + } + break; + case XT_LOG_ENT_UPDATE_FL: + case XT_LOG_ENT_INSERT_FL: + case XT_LOG_ENT_DELETE_FL: + check_size = 2; + XT_SET_DISK_4(log_entry.xf.xf_op_seq_4, op_seq); + XT_SET_DISK_4(log_entry.xf.xf_tab_id_4, tab->tab_id); + XT_SET_DISK_4(log_entry.xf.xf_rec_id_4, rec_id); + XT_SET_DISK_2(log_entry.xf.xf_size_2, size); + XT_SET_DISK_4(log_entry.xf.xf_free_rec_id_4, free_rec_id); + sum ^= XT_CHECKSUM4_REC(free_rec_id); + len = offsetof(XTactUpdateFLEntryDRec, xf_rec_type_1); + if (!(thread->st_xact_data->xd_flags & XT_XN_XAC_LOGGED)) { + /* Add _BG: */ + status++; + xact = thread->st_xact_data; + xact->xd_flags |= XT_XN_XAC_LOGGED; + } + break; + case XT_LOG_ENT_REC_FREED: + case XT_LOG_ENT_REC_REMOVED: + case XT_LOG_ENT_REC_REMOVED_EXT: + ASSERT_NS(size == 1 + XT_XACT_ID_SIZE + sizeof(XTTabRecFreeDRec)); + XT_SET_DISK_4(log_entry.fr.fr_op_seq_4, op_seq); + XT_SET_DISK_4(log_entry.fr.fr_tab_id_4, tab->tab_id); + XT_SET_DISK_4(log_entry.fr.fr_rec_id_4, rec_id); + len = offsetof(XTactFreeRecEntryDRec, fr_stat_id_1); + break; + case XT_LOG_ENT_REC_REMOVED_BI: + check_size = 2; + XT_SET_DISK_4(log_entry.rb.rb_op_seq_4, op_seq); + XT_SET_DISK_4(log_entry.rb.rb_tab_id_4, tab->tab_id); + XT_SET_DISK_4(log_entry.rb.rb_rec_id_4, rec_id); + XT_SET_DISK_2(log_entry.rb.rb_size_2, size); + log_entry.rb.rb_new_rec_type_1 = (xtWord1) free_rec_id; + sum ^= XT_CHECKSUM4_REC(free_rec_id); + len = offsetof(XTactRemoveBIEntryDRec, rb_rec_type_1); + break; + case XT_LOG_ENT_REC_MOVED: + ASSERT_NS(size == 8); + XT_SET_DISK_4(log_entry.xw.xw_op_seq_4, op_seq); + XT_SET_DISK_4(log_entry.xw.xw_tab_id_4, tab->tab_id); + XT_SET_DISK_4(log_entry.xw.xw_rec_id_4, rec_id); + len = offsetof(XTactWriteRecEntryDRec, xw_rec_type_1); + break; + case XT_LOG_ENT_REC_CLEANED: + ASSERT_NS(size == offsetof(XTTabRecHeadDRec, tr_prev_rec_id_4) + XT_RECORD_ID_SIZE); + XT_SET_DISK_4(log_entry.xw.xw_op_seq_4, op_seq); + XT_SET_DISK_4(log_entry.xw.xw_tab_id_4, tab->tab_id); + XT_SET_DISK_4(log_entry.xw.xw_rec_id_4, rec_id); + len = offsetof(XTactWriteRecEntryDRec, xw_rec_type_1); + break; + case XT_LOG_ENT_REC_CLEANED_1: + ASSERT_NS(size == 1); + XT_SET_DISK_4(log_entry.xw.xw_op_seq_4, op_seq); + XT_SET_DISK_4(log_entry.xw.xw_tab_id_4, tab->tab_id); + XT_SET_DISK_4(log_entry.xw.xw_rec_id_4, rec_id); + len = offsetof(XTactWriteRecEntryDRec, xw_rec_type_1); + break; + case XT_LOG_ENT_REC_UNLINKED: + ASSERT_NS(size == offsetof(XTTabRecHeadDRec, tr_prev_rec_id_4) + XT_RECORD_ID_SIZE); + XT_SET_DISK_4(log_entry.xw.xw_op_seq_4, op_seq); + XT_SET_DISK_4(log_entry.xw.xw_tab_id_4, tab->tab_id); + XT_SET_DISK_4(log_entry.xw.xw_rec_id_4, rec_id); + len = offsetof(XTactWriteRecEntryDRec, xw_rec_type_1); + break; + case XT_LOG_ENT_ROW_NEW: + ASSERT_NS(size == 0); + XT_SET_DISK_4(log_entry.xa.xa_op_seq_4, op_seq); + XT_SET_DISK_4(log_entry.xa.xa_tab_id_4, tab->tab_id); + XT_SET_DISK_4(log_entry.xa.xa_row_id_4, rec_id); + len = offsetof(XTactRowAddedEntryDRec, xa_row_id_4) + XT_ROW_ID_SIZE; + break; + case XT_LOG_ENT_ROW_NEW_FL: + ASSERT_NS(size == 0); + XT_SET_DISK_4(log_entry.xa.xa_op_seq_4, op_seq); + XT_SET_DISK_4(log_entry.xa.xa_tab_id_4, tab->tab_id); + XT_SET_DISK_4(log_entry.xa.xa_row_id_4, rec_id); + XT_SET_DISK_4(log_entry.xa.xa_free_list_4, free_rec_id); + sum ^= XT_CHECKSUM4_REC(free_rec_id); + len = offsetof(XTactRowAddedEntryDRec, xa_free_list_4) + XT_ROW_ID_SIZE; + break; + case XT_LOG_ENT_ROW_ADD_REC: + case XT_LOG_ENT_ROW_SET: + case XT_LOG_ENT_ROW_FREED: + ASSERT_NS(size == sizeof(XTTabRowRefDRec)); + XT_SET_DISK_4(log_entry.wr.wr_op_seq_4, op_seq); + XT_SET_DISK_4(log_entry.wr.wr_tab_id_4, tab->tab_id); + XT_SET_DISK_4(log_entry.wr.wr_row_id_4, rec_id); + len = offsetof(XTactWriteRowEntryDRec, wr_ref_id_4); + break; + default: + ASSERT_NS(FALSE); + len = 0; + break; + } + + xtWord1 *dptr = data; + xtWord4 g; + + sum ^= op_seq ^ (tab->tab_id << 8) ^ XT_CHECKSUM4_REC(rec_id); + if ((g = sum & 0xF0000000)) { + sum = sum ^ (g >> 24); + sum = sum ^ g; + } + for (u_int i=0; i<(u_int) size; i++) { + sum = (sum << 4) + *dptr; + if ((g = sum & 0xF0000000)) { + sum = sum ^ (g >> 24); + sum = sum ^ g; + } + dptr++; + } + + log_entry.xh.xh_status_1 = status; + if (check_size == 1) { + log_entry.xh.xh_checksum_1 = XT_CHECKSUM_1(sum); + } + else { + xtWord2 c; + + c = XT_CHECKSUM_2(sum); + XT_SET_DISK_2(log_entry.xu.xu_checksum_2, c); + } +#ifdef PRINT_TABLE_MODIFICATIONS + xt_print_log_record(0, 0, &log_entry); +#endif + if (xact) + return thread->st_database->db_xlog.xlog_append(thread, len, (xtWord1 *) &log_entry, size, data, FALSE, &xact->xd_begin_log, &xact->xd_begin_offset); + + return thread->st_database->db_xlog.xlog_append(thread, len, (xtWord1 *) &log_entry, size, data, FALSE, NULL, NULL); +} + +/* + * ----------------------------------------------------------------------- + * S E Q U E N T I A L L O G R E A D I N G + */ + +/* + * Use the log buffer for sequential reading the log. + */ +xtBool XTDatabaseLog::xlog_seq_init(XTXactSeqReadPtr seq, size_t buffer_size, xtBool load_cache) +{ + seq->xseq_buffer_size = buffer_size; + seq->xseq_load_cache = load_cache; + + seq->xseq_log_id = 0; + seq->xseq_log_file = NULL; + seq->xseq_log_eof = 0; + + seq->xseq_buf_log_offset = 0; + seq->xseq_buffer_len = 0; + seq->xseq_buffer = (xtWord1 *) xt_malloc_ns(buffer_size); + + seq->xseq_rec_log_id = 0; + seq->xseq_rec_log_offset = 0; + seq->xseq_record_len = 0; + + return seq->xseq_buffer != NULL; +} + +void XTDatabaseLog::xlog_seq_exit(XTXactSeqReadPtr seq) +{ + xlog_seq_close(seq); + if (seq->xseq_buffer) { + xt_free_ns(seq->xseq_buffer); + seq->xseq_buffer = NULL; + } +} + +void XTDatabaseLog::xlog_seq_close(XTXactSeqReadPtr seq) +{ + if (seq->xseq_log_file) { + xt_close_file_ns(seq->xseq_log_file); + seq->xseq_log_file = NULL; + } + seq->xseq_log_id = 0; + seq->xseq_log_eof = 0; +} + +xtBool XTDatabaseLog::xlog_seq_start(XTXactSeqReadPtr seq, xtLogID log_id, xtLogOffset log_offset, xtBool missing_ok __attribute__((unused))) +{ + if (seq->xseq_rec_log_id != log_id) { + seq->xseq_rec_log_id = log_id; + seq->xseq_buf_log_offset = seq->xseq_rec_log_offset; + seq->xseq_buffer_len = 0; + } + + /* Windows version: this will help to switch + * to the new log file. + * Due to reading from the log buffers, this was + * not always done! + */ + if (seq->xseq_log_id != log_id) { + if (seq->xseq_log_file) { + xt_close_file_ns(seq->xseq_log_file); + seq->xseq_log_file = NULL; + } + } + seq->xseq_rec_log_offset = log_offset; + seq->xseq_record_len = 0; + return OK; +} + +size_t XTDatabaseLog::xlog_bytes_to_write() +{ + xtLogID log_id; + xtLogOffset log_offset; + xtLogID to_log_id; + xtLogOffset to_log_offset; + size_t byte_count = 0; + + log_id = xl_db->db_wr_log_id; + log_offset = xl_db->db_wr_log_offset; + to_log_id = xl_db->db_xlog.xl_flush_log_id; + to_log_offset = xl_db->db_xlog.xl_flush_log_offset; + + /* Assume the logs have the threshold: */ + if (log_id < to_log_id) { + if (log_offset < xt_db_log_file_threshold) + byte_count = (size_t) (xt_db_log_file_threshold - log_offset); + log_offset = 0; + log_id++; + } + while (log_id < to_log_id) { + byte_count += (size_t) xt_db_log_file_threshold; + log_id++; + } + if (log_offset < to_log_offset) + byte_count += (size_t) (to_log_offset - log_offset); + + return byte_count; +} + +xtBool XTDatabaseLog::xlog_read_from_cache(XTXactSeqReadPtr seq, xtLogID log_id, xtLogOffset log_offset, size_t size, off_t eof, xtWord1 *buffer, size_t *data_read, XTThreadPtr thread) +{ + /* xseq_log_file could be NULL because xseq_log_id is not set + * to zero when xseq_log_file is set to NULL! + * This bug caused a crash in TeamDrive. + */ + if (seq->xseq_log_id != log_id || !seq->xseq_log_file) { + char path[PATH_MAX]; + + if (seq->xseq_log_file) { + xt_close_file_ns(seq->xseq_log_file); + seq->xseq_log_file = NULL; + } + + xlog_name(PATH_MAX, path, log_id); + if (!xt_open_file_ns(&seq->xseq_log_file, path, XT_FS_MISSING_OK)) + return FAILED; + if (!seq->xseq_log_file) { + if (data_read) + *data_read = 0; + return OK; + } + seq->xseq_log_id = log_id; + seq->xseq_log_eof = 0; + } + + if (!eof) { + if (!seq->xseq_log_eof) + seq->xseq_log_eof = xt_seek_eof_file(NULL, seq->xseq_log_file); + eof = seq->xseq_log_eof; + } + + if (log_offset >= eof) { + if (data_read) + *data_read = 0; + return OK; + } + + if ((off_t) size > eof - log_offset) + size = (size_t) (eof - log_offset); + + if (data_read) + *data_read = size; + return xt_xlog_read(seq->xseq_log_file, seq->xseq_log_id, log_offset, size, buffer, seq->xseq_load_cache, thread); +} + +xtBool XTDatabaseLog::xlog_rnd_read(XTXactSeqReadPtr seq, xtLogID log_id, xtLogOffset log_offset, size_t size, xtWord1 *buffer, size_t *data_read, XTThreadPtr thread) +{ + /* Fast track to reading from cache: */ + if (log_id < xl_write_log_id) + return xlog_read_from_cache(seq, log_id, log_offset, size, 0, buffer, data_read, thread); + + if (log_id == xl_write_log_id && log_offset + (xtLogOffset) size <= xl_write_log_offset) + return xlog_read_from_cache(seq, log_id, log_offset, size, xl_write_log_offset, buffer, data_read, thread); + + /* May be in the log write or append buffer: */ + xt_lck_slock(&xl_buffer_lock); + + if (log_id < xl_write_log_id) { + xt_spinlock_unlock(&xl_buffer_lock); + return xlog_read_from_cache(seq, log_id, log_offset, size, 0, buffer, data_read, thread); + } + + /* Check the write buffer: */ + if (log_id == xl_write_log_id) { + if (log_offset + (xtLogOffset) size <= xl_write_log_offset) { + xt_spinlock_unlock(&xl_buffer_lock); + return xlog_read_from_cache(seq, log_id, log_offset, size, xl_write_log_offset, buffer, data_read, thread); + } + + if (log_offset < xl_write_log_offset + (xtLogOffset) xl_write_buf_pos) { + /* Reading partially from the write buffer: */ + if (log_offset >= xl_write_log_offset) { + /* Completely in the buffer. */ + off_t offset = log_offset - xl_write_log_offset; + + if (size > xl_write_buf_pos - offset) + size = (size_t) (xl_write_buf_pos - offset); + + memcpy(buffer, xl_write_buffer + offset, size); + if (data_read) + *data_read = size; + goto unlock_and_return; + } + + /* End part in the buffer: */ + size_t tfer; + + /* The amount that will be taken from the cache: */ + tfer = (size_t) (xl_write_log_offset - log_offset); + + size -= tfer; + if (size > xl_write_buf_pos) + size = xl_write_buf_pos; + + memcpy(buffer + tfer, xl_write_buffer, size); + + xt_spinlock_unlock(&xl_buffer_lock); + + /* Read the first part from the cache: */ + if (data_read) + *data_read = tfer + size; + return xlog_read_from_cache(seq, log_id, log_offset, tfer, log_offset + tfer, buffer, NULL, thread); + } + } + + /* Check the append buffer: */ + if (log_id == xl_append_log_id) { + if (log_offset >= xl_append_log_offset && log_offset < xl_append_log_offset + (xtLogOffset) xl_append_buf_pos) { + /* It is in the append buffer: */ + size_t offset = (size_t) (log_offset - xl_append_log_offset); + + if (size > xl_append_buf_pos - offset) + size = xl_append_buf_pos - offset; + + memcpy(buffer, xl_append_buffer + offset, size); + if (data_read) + *data_read = size; + goto unlock_and_return; + } + } + + if (xl_append_log_id == 0) { + /* This catches the case that + * the log has not yet been initialized + * for writing. + */ + xt_spinlock_unlock(&xl_buffer_lock); + return xlog_read_from_cache(seq, log_id, log_offset, size, 0, buffer, data_read, thread); + } + + if (data_read) + *data_read = 0; + + unlock_and_return: + xt_spinlock_unlock(&xl_buffer_lock); + return OK; +} + +xtBool XTDatabaseLog::xlog_write_thru(XTXactSeqReadPtr seq, size_t size, xtWord1 *data, XTThreadPtr thread) +{ + if (!xt_xlog_write(seq->xseq_log_file, seq->xseq_log_id, seq->xseq_rec_log_offset, size, data, thread)) + return FALSE; + xl_log_bytes_written += size; + seq->xseq_rec_log_offset += size; + return TRUE; +} + +xtBool XTDatabaseLog::xlog_verify(XTXactLogBufferDPtr record, size_t rec_size, xtLogID log_id) +{ + xtWord4 sum = 0; + xtOpSeqNo op_seq; + xtTableID tab_id; + xtRecordID rec_id, free_rec_id; + int check_size = 1; + xtWord1 *dptr; + + switch (record->xh.xh_status_1) { + case XT_LOG_ENT_HEADER: + if (record->xh.xh_checksum_1 != XT_CHECKSUM_1(log_id)) + return FALSE; + if (XT_LOG_HEAD_MAGIC(record, rec_size) != XT_LOG_FILE_MAGIC) + return FALSE; + if (rec_size >= offsetof(XTXactLogHeaderDRec, xh_log_id_4) + 4) { + if (XT_GET_DISK_4(record->xh.xh_log_id_4) != log_id) + return FALSE; + } + return TRUE; + case XT_LOG_ENT_NEW_LOG: + case XT_LOG_ENT_DEL_LOG: + return record->xl.xl_checksum_1 == (XT_CHECKSUM_1(XT_GET_DISK_4(record->xl.xl_log_id_4)) ^ XT_CHECKSUM_1(log_id)); + case XT_LOG_ENT_NEW_TAB: + return record->xl.xl_checksum_1 == (XT_CHECKSUM_1(XT_GET_DISK_4(record->xt.xt_tab_id_4)) ^ XT_CHECKSUM_1(log_id)); + case XT_LOG_ENT_COMMIT: + case XT_LOG_ENT_ABORT: + sum = XT_CHECKSUM4_XACT(XT_GET_DISK_4(record->xe.xe_xact_id_4)) ^ XT_CHECKSUM4_XACT(XT_GET_DISK_4(record->xe.xe_not_used_4)); + return record->xe.xe_checksum_1 == (XT_CHECKSUM_1(sum) ^ XT_CHECKSUM_1(log_id)); + case XT_LOG_ENT_CLEANUP: + sum = XT_CHECKSUM4_XACT(XT_GET_DISK_4(record->xc.xc_xact_id_4)); + return record->xc.xc_checksum_1 == (XT_CHECKSUM_1(sum) ^ XT_CHECKSUM_1(log_id)); + case XT_LOG_ENT_REC_MODIFIED: + case XT_LOG_ENT_UPDATE: + case XT_LOG_ENT_INSERT: + case XT_LOG_ENT_DELETE: + case XT_LOG_ENT_UPDATE_BG: + case XT_LOG_ENT_INSERT_BG: + case XT_LOG_ENT_DELETE_BG: + check_size = 2; + op_seq = XT_GET_DISK_4(record->xu.xu_op_seq_4); + tab_id = XT_GET_DISK_4(record->xu.xu_tab_id_4); + rec_id = XT_GET_DISK_4(record->xu.xu_rec_id_4); + dptr = &record->xu.xu_rec_type_1; + rec_size -= offsetof(XTactUpdateEntryDRec, xu_rec_type_1); + break; + case XT_LOG_ENT_UPDATE_FL: + case XT_LOG_ENT_INSERT_FL: + case XT_LOG_ENT_DELETE_FL: + case XT_LOG_ENT_UPDATE_FL_BG: + case XT_LOG_ENT_INSERT_FL_BG: + case XT_LOG_ENT_DELETE_FL_BG: + check_size = 2; + op_seq = XT_GET_DISK_4(record->xf.xf_op_seq_4); + tab_id = XT_GET_DISK_4(record->xf.xf_tab_id_4); + rec_id = XT_GET_DISK_4(record->xf.xf_rec_id_4); + free_rec_id = XT_GET_DISK_4(record->xf.xf_free_rec_id_4); + sum ^= XT_CHECKSUM4_REC(free_rec_id); + dptr = &record->xf.xf_rec_type_1; + rec_size -= offsetof(XTactUpdateFLEntryDRec, xf_rec_type_1); + break; + case XT_LOG_ENT_REC_FREED: + case XT_LOG_ENT_REC_REMOVED: + case XT_LOG_ENT_REC_REMOVED_EXT: + op_seq = XT_GET_DISK_4(record->fr.fr_op_seq_4); + tab_id = XT_GET_DISK_4(record->fr.fr_tab_id_4); + rec_id = XT_GET_DISK_4(record->fr.fr_rec_id_4); + dptr = &record->fr.fr_stat_id_1; + rec_size -= offsetof(XTactFreeRecEntryDRec, fr_stat_id_1); + break; + case XT_LOG_ENT_REC_REMOVED_BI: + check_size = 2; + op_seq = XT_GET_DISK_4(record->rb.rb_op_seq_4); + tab_id = XT_GET_DISK_4(record->rb.rb_tab_id_4); + rec_id = XT_GET_DISK_4(record->rb.rb_rec_id_4); + free_rec_id = (xtWord4) record->rb.rb_new_rec_type_1; + sum ^= XT_CHECKSUM4_REC(free_rec_id); + dptr = &record->rb.rb_rec_type_1; + rec_size -= offsetof(XTactRemoveBIEntryDRec, rb_rec_type_1); + break; + case XT_LOG_ENT_REC_MOVED: + case XT_LOG_ENT_REC_CLEANED: + case XT_LOG_ENT_REC_CLEANED_1: + case XT_LOG_ENT_REC_UNLINKED: + op_seq = XT_GET_DISK_4(record->xw.xw_op_seq_4); + tab_id = XT_GET_DISK_4(record->xw.xw_tab_id_4); + rec_id = XT_GET_DISK_4(record->xw.xw_rec_id_4); + dptr = &record->xw.xw_rec_type_1; + rec_size -= offsetof(XTactWriteRecEntryDRec, xw_rec_type_1); + break; + case XT_LOG_ENT_ROW_NEW: + case XT_LOG_ENT_ROW_NEW_FL: + op_seq = XT_GET_DISK_4(record->xa.xa_op_seq_4); + tab_id = XT_GET_DISK_4(record->xa.xa_tab_id_4); + rec_id = XT_GET_DISK_4(record->xa.xa_row_id_4); + if (record->xh.xh_status_1 == XT_LOG_ENT_ROW_NEW) { + dptr = (xtWord1 *) record + offsetof(XTactRowAddedEntryDRec, xa_free_list_4); + rec_size -= offsetof(XTactRowAddedEntryDRec, xa_free_list_4); + } + else { + free_rec_id = XT_GET_DISK_4(record->xa.xa_free_list_4); + sum ^= XT_CHECKSUM4_REC(free_rec_id); + dptr = (xtWord1 *) record + sizeof(XTactRowAddedEntryDRec); + rec_size -= sizeof(XTactRowAddedEntryDRec); + } + break; + case XT_LOG_ENT_ROW_ADD_REC: + case XT_LOG_ENT_ROW_SET: + case XT_LOG_ENT_ROW_FREED: + op_seq = XT_GET_DISK_4(record->wr.wr_op_seq_4); + tab_id = XT_GET_DISK_4(record->wr.wr_tab_id_4); + rec_id = XT_GET_DISK_4(record->wr.wr_row_id_4); + dptr = (xtWord1 *) &record->wr.wr_ref_id_4; + rec_size -= offsetof(XTactWriteRowEntryDRec, wr_ref_id_4); + break; + case XT_LOG_ENT_OP_SYNC: + return record->xl.xl_checksum_1 == (XT_CHECKSUM_1(XT_GET_DISK_4(record->os.os_time_4)) ^ XT_CHECKSUM_1(log_id)); + case XT_LOG_ENT_NO_OP: + sum = XT_GET_DISK_4(record->no.no_tab_id_4) ^ XT_GET_DISK_4(record->no.no_op_seq_4); + return record->xe.xe_checksum_1 == (XT_CHECKSUM_1(sum) ^ XT_CHECKSUM_1(log_id)); + case XT_LOG_ENT_END_OF_LOG: + return FALSE; + default: + ASSERT_NS(FALSE); + return FALSE; + } + + xtWord4 g; + + sum ^= (xtWord4) op_seq ^ ((xtWord4) tab_id << 8) ^ XT_CHECKSUM4_REC(rec_id); + + if ((g = sum & 0xF0000000)) { + sum = sum ^ (g >> 24); + sum = sum ^ g; + } + for (u_int i=0; i<(u_int) rec_size; i++) { + sum = (sum << 4) + *dptr; + if ((g = sum & 0xF0000000)) { + sum = sum ^ (g >> 24); + sum = sum ^ g; + } + dptr++; + } + + if (check_size == 1) { + if (record->xh.xh_checksum_1 != (XT_CHECKSUM_1(sum) ^ XT_CHECKSUM_1(log_id))) { + return FAILED; + } + } + else { + if (XT_GET_DISK_2(record->xu.xu_checksum_2) != (XT_CHECKSUM_2(sum) ^ XT_CHECKSUM_2(log_id))) { + return FAILED; + } + } + return TRUE; +} + +xtBool XTDatabaseLog::xlog_seq_next(XTXactSeqReadPtr seq, XTXactLogBufferDPtr *ret_entry, xtBool verify, XTThreadPtr thread) +{ + XTXactLogBufferDPtr record; + size_t tfer; + size_t len; + size_t rec_offset; + size_t max_rec_len; + size_t size; + u_int check_size = 1; + + /* Go to the next record (xseq_record_len must be initialized + * to 0 for this to work. + */ + seq->xseq_rec_log_offset += seq->xseq_record_len; + seq->xseq_record_len = 0; + + if (seq->xseq_rec_log_offset < seq->xseq_buf_log_offset || + seq->xseq_rec_log_offset >= seq->xseq_buf_log_offset + (xtLogOffset) seq->xseq_buffer_len) { + /* The current position is nowhere near the buffer, read data into the + * buffer: + */ + tfer = seq->xseq_buffer_size; + if (!xlog_rnd_read(seq, seq->xseq_rec_log_id, seq->xseq_rec_log_offset, tfer, seq->xseq_buffer, &tfer, thread)) + return FAILED; + seq->xseq_buf_log_offset = seq->xseq_rec_log_offset; + seq->xseq_buffer_len = tfer; + + /* Should we go to the next log? */ + if (!tfer) { + goto return_empty; + } + } + + /* The start of the record is in the buffer: */ + read_from_buffer: + rec_offset = (size_t) (seq->xseq_rec_log_offset - seq->xseq_buf_log_offset); + max_rec_len = seq->xseq_buffer_len - rec_offset; + size = 0; + + /* Check the type of record: */ + record = (XTXactLogBufferDPtr) (seq->xseq_buffer + rec_offset); + switch (record->xh.xh_status_1) { + case XT_LOG_ENT_HEADER: + len = sizeof(XTXactLogHeaderDRec); + break; + case XT_LOG_ENT_NEW_LOG: + case XT_LOG_ENT_DEL_LOG: + len = sizeof(XTXactNewLogEntryDRec); + break; + case XT_LOG_ENT_NEW_TAB: + len = sizeof(XTXactNewTabEntryDRec); + break; + case XT_LOG_ENT_COMMIT: + case XT_LOG_ENT_ABORT: + len = sizeof(XTXactEndEntryDRec); + break; + case XT_LOG_ENT_CLEANUP: + len = sizeof(XTXactCleanupEntryDRec); + break; + case XT_LOG_ENT_REC_MODIFIED: + case XT_LOG_ENT_UPDATE: + case XT_LOG_ENT_INSERT: + case XT_LOG_ENT_DELETE: + case XT_LOG_ENT_UPDATE_BG: + case XT_LOG_ENT_INSERT_BG: + case XT_LOG_ENT_DELETE_BG: + check_size = 2; + len = offsetof(XTactUpdateEntryDRec, xu_rec_type_1); + if (len > max_rec_len) + /* The size is not in the buffer: */ + goto read_more; + len += (size_t) XT_GET_DISK_2(record->xu.xu_size_2); + break; + case XT_LOG_ENT_UPDATE_FL: + case XT_LOG_ENT_INSERT_FL: + case XT_LOG_ENT_DELETE_FL: + case XT_LOG_ENT_UPDATE_FL_BG: + case XT_LOG_ENT_INSERT_FL_BG: + case XT_LOG_ENT_DELETE_FL_BG: + check_size = 2; + len = offsetof(XTactUpdateFLEntryDRec, xf_rec_type_1); + if (len > max_rec_len) + /* The size is not in the buffer: */ + goto read_more; + len += (size_t) XT_GET_DISK_2(record->xf.xf_size_2); + break; + case XT_LOG_ENT_REC_FREED: + case XT_LOG_ENT_REC_REMOVED: + case XT_LOG_ENT_REC_REMOVED_EXT: + /* [(7)] REMOVE is now a extended version of FREE! */ + len = offsetof(XTactFreeRecEntryDRec, fr_rec_type_1) + sizeof(XTTabRecFreeDRec); + break; + case XT_LOG_ENT_REC_REMOVED_BI: + check_size = 2; + len = offsetof(XTactRemoveBIEntryDRec, rb_rec_type_1); + if (len > max_rec_len) + /* The size is not in the buffer: */ + goto read_more; + len += (size_t) XT_GET_DISK_2(record->rb.rb_size_2); + break; + case XT_LOG_ENT_REC_MOVED: + len = offsetof(XTactWriteRecEntryDRec, xw_rec_type_1) + 8; + break; + case XT_LOG_ENT_REC_CLEANED: + len = offsetof(XTactWriteRecEntryDRec, xw_rec_type_1) + offsetof(XTTabRecHeadDRec, tr_prev_rec_id_4) + XT_RECORD_ID_SIZE; + break; + case XT_LOG_ENT_REC_CLEANED_1: + len = offsetof(XTactWriteRecEntryDRec, xw_rec_type_1) + 1; + break; + case XT_LOG_ENT_REC_UNLINKED: + len = offsetof(XTactWriteRecEntryDRec, xw_rec_type_1) + offsetof(XTTabRecHeadDRec, tr_prev_rec_id_4) + XT_RECORD_ID_SIZE; + break; + case XT_LOG_ENT_ROW_NEW: + len = offsetof(XTactRowAddedEntryDRec, xa_row_id_4) + XT_ROW_ID_SIZE; + break; + case XT_LOG_ENT_ROW_NEW_FL: + len = offsetof(XTactRowAddedEntryDRec, xa_free_list_4) + XT_ROW_ID_SIZE; + break; + case XT_LOG_ENT_ROW_ADD_REC: + case XT_LOG_ENT_ROW_SET: + case XT_LOG_ENT_ROW_FREED: + len = offsetof(XTactWriteRowEntryDRec, wr_ref_id_4) + XT_REF_ID_SIZE; + break; + case XT_LOG_ENT_OP_SYNC: + len = sizeof(XTactOpSyncEntryDRec); + break; + case XT_LOG_ENT_NO_OP: + len = sizeof(XTactNoOpEntryDRec); + break; + case XT_LOG_ENT_END_OF_LOG: { + off_t eof = seq->xseq_log_eof, adjust; + + if (eof > seq->xseq_rec_log_offset) { + adjust = eof - seq->xseq_rec_log_offset; + + seq->xseq_record_len = (size_t) adjust; + } + goto return_empty; + } + default: + ASSERT_NS(FALSE); + seq->xseq_record_len = 0; + goto return_empty; + } + + ASSERT_NS(len <= seq->xseq_buffer_size); + if (len <= max_rec_len) { + if (verify) { + if (!xlog_verify(record, len, seq->xseq_rec_log_id)) { + goto return_empty; + } + } + + /* The record is completely in the buffer: */ + seq->xseq_record_len = len; + *ret_entry = record; + return OK; + } + + /* The record is partially in the buffer. */ + memmove(seq->xseq_buffer, seq->xseq_buffer + rec_offset, max_rec_len); + seq->xseq_buf_log_offset += rec_offset; + seq->xseq_buffer_len = max_rec_len; + + /* Read the rest, as far as possible: */ + tfer = seq->xseq_buffer_size - max_rec_len; + if (!xlog_rnd_read(seq, seq->xseq_rec_log_id, seq->xseq_buf_log_offset + max_rec_len, tfer, seq->xseq_buffer + max_rec_len, &tfer, thread)) + return FAILED; + seq->xseq_buffer_len += tfer; + + if (seq->xseq_buffer_len < len) { + /* A partial record is in the log, must be the end of the log: */ + goto return_empty; + } + + /* The record is not completely in the buffer: */ + seq->xseq_record_len = len; + *ret_entry = (XTXactLogBufferDPtr) seq->xseq_buffer; + return OK; + + read_more: + ASSERT_NS(len <= seq->xseq_buffer_size); + memmove(seq->xseq_buffer, seq->xseq_buffer + rec_offset, max_rec_len); + seq->xseq_buf_log_offset += rec_offset; + seq->xseq_buffer_len = max_rec_len; + + /* Read the rest, as far as possible: */ + tfer = seq->xseq_buffer_size - max_rec_len; + if (!xlog_rnd_read(seq, seq->xseq_rec_log_id, seq->xseq_buf_log_offset + max_rec_len, tfer, seq->xseq_buffer + max_rec_len, &tfer, thread)) + return FAILED; + seq->xseq_buffer_len += tfer; + + if (seq->xseq_buffer_len < len + size) { + /* We did not get as much as we need, return an empty record: */ + goto return_empty; + } + + goto read_from_buffer; + + return_empty: + *ret_entry = NULL; + return OK; +} + +void XTDatabaseLog::xlog_seq_skip(XTXactSeqReadPtr seq, size_t size) +{ + seq->xseq_record_len += size; +} + +/* ---------------------------------------------------------------------- + * W R I T E R P R O C E S S + */ + +/* + * The log has been written. Wake the writer to commit the + * data to disk, if the transaction log cache is full. + * + * Data may not be written to the database until it has been + * flushed to the log. + * + * This is because there is no way to undo changes to the + * database. + * + * However, I have dicovered that writing constantly in the + * background can disturb the I/O in the foreground. + * + * So we can delay the writing of the database. But we should + * not delay it longer than we have transaction log cache. + * + * If so, the data that we need will fall out of the cache + * and we will have to read it again. + */ +static void xlog_wr_log_written(XTDatabaseHPtr db) +{ + if (db->db_wr_idle) { + xtWord8 cached_bytes; + + /* Determine if the cached log data is about to fall out of the cache. */ + cached_bytes = db->db_xlog.xl_log_bytes_written - db->db_xlog.xl_log_bytes_read; + /* The limit is 75%: */ + if (cached_bytes >= xt_xlog_cache.xlc_upper_limit) { + if (!xt_broadcast_cond_ns(&db->db_wr_cond)) + xt_log_and_clear_exception_ns(); + } + } +} + +#define XT_MORE_TO_WRITE 1 +#define XT_FREER_WAITING 2 +#define XT_NO_ACTIVITY 3 +#define XT_LOG_CACHE_FULL 4 +#define XT_CHECKPOINT_REQ 5 +#define XT_THREAD_WAITING 6 +#define XT_TIME_TO_WRITE 7 + +/* + * Wait for a transaction to quit, i.e. the log to be flushed. + */ +static void xlog_wr_wait_for_log_flush(XTThreadPtr self, XTDatabaseHPtr db) +{ + xtXactID last_xn_id; + xtWord8 cached_bytes; + int reason = XT_MORE_TO_WRITE; + +#ifdef TRACE_WRITER_ACTIVITY + printf("WRITER --- DONE\n"); +#endif + + xt_lock_mutex(self, &db->db_wr_lock); + pushr_(xt_unlock_mutex, &db->db_wr_lock); + + /* + * Wake the freeer if it is waiting for this writer, before + * we go to sleep! + */ + if (db->db_wr_freeer_waiting) { + if (!xt_broadcast_cond_ns(&db->db_wr_cond)) + xt_log_and_clear_exception_ns(); + } + + if (db->db_wr_flush_point_log_id == db->db_xlog.xl_flush_log_id && + db->db_wr_flush_point_log_offset == db->db_xlog.xl_flush_log_offset) { + /* Wake the checkpointer to flush the indexes: + * PMC 15.05.2008 - Not doing this anymore! + xt_wake_checkpointer(self, db); + */ + + /* Sleep as long as the flush point has not changed, from the last + * target flush point. + */ + while (!self->t_quit && + db->db_wr_flush_point_log_id == db->db_xlog.xl_flush_log_id && + db->db_wr_flush_point_log_offset == db->db_xlog.xl_flush_log_offset && + reason != XT_LOG_CACHE_FULL && + reason != XT_TIME_TO_WRITE && + reason != XT_CHECKPOINT_REQ) { + + /* + * Sleep as long as there is no reason to write any more... + */ + while (!self->t_quit) { + last_xn_id = db->db_xn_curr_id; + db->db_wr_idle = XT_THREAD_IDLE; + xt_timed_wait_cond(self, &db->db_wr_cond, &db->db_wr_lock, 500); + db->db_wr_idle = XT_THREAD_BUSY; + /* These are the reasons for doing work: */ + /* The free'er thread is waiting for the writer: */ + if (db->db_wr_freeer_waiting) { + reason = XT_FREER_WAITING; + break; + } + /* Some thread is waiting for the writer: */ + if (db->db_wr_thread_waiting) { + reason = XT_THREAD_WAITING; + break; + } + /* Check if the cache will soon overflow... */ + ASSERT(db->db_xlog.xl_log_bytes_written >= db->db_xlog.xl_log_bytes_read); + ASSERT(db->db_xlog.xl_log_bytes_written >= db->db_xlog.xl_log_bytes_flushed); + /* Sanity check: */ + ASSERT(db->db_xlog.xl_log_bytes_written < db->db_xlog.xl_log_bytes_read + 500000000); + /* This is the amount of data still to be written: */ + cached_bytes = db->db_xlog.xl_log_bytes_written - db->db_xlog.xl_log_bytes_read; + /* The limit is 75%: */ + if (cached_bytes >= xt_xlog_cache.xlc_upper_limit) { + reason = XT_LOG_CACHE_FULL; + break; + } + + /* TODO: Create a system variable which specifies the write frequency. *//* + if (cached_bytes >= (12 * 1024 * 1024)) { + reason = XT_TIME_TO_WRITE; + break; + } + */ + + /* Check if we are holding up a checkpoint: */ + if (db->db_restart.xres_cp_required || + db->db_restart.xres_is_checkpoint_pending(db->db_xlog.xl_write_log_id, db->db_xlog.xl_write_log_offset)) { + /* Enough data has been flushed for a checkpoint: */ + if (!db->db_restart.xres_is_checkpoint_pending(db->db_wr_log_id, db->db_wr_log_offset)) { + /* But not enough data has been written for a checkpoint: */ + reason = XT_CHECKPOINT_REQ; + break; + } + } + /* There is no activity, if the current ID has not changed during + * the wait, and the sweeper has nothing to do, and the checkpointer. + */ + if (db->db_xn_curr_id == last_xn_id && + xt_xn_is_before(xt_xn_get_curr_id(db), db->db_xn_to_clean_id) && // db->db_xn_curr_id < db->db_xn_to_clean_id + !db->db_restart.xres_is_checkpoint_pending(db->db_xlog.xl_write_log_id, db->db_xlog.xl_write_log_offset)) { + /* There seems to be no activity at the moment. + * this might be a good time to write the log data. + */ + reason = XT_NO_ACTIVITY; + break; + } + } + } + } + freer_(); // xt_unlock_mutex(&db->db_wr_lock) + + if (reason == XT_LOG_CACHE_FULL || reason == XT_TIME_TO_WRITE || reason == XT_CHECKPOINT_REQ) { + /* Make sure that we have something to write: */ + if (db->db_xlog.xlog_bytes_to_write() < 2 * 1204 * 1024) + xt_xlog_flush_log(self); + } + +#ifdef TRACE_WRITER_ACTIVITY + switch (reason) { + case XT_MORE_TO_WRITE: printf("WRITER --- still more to write...\n"); break; + case XT_FREER_WAITING: printf("WRITER --- free'er waiting for writer...\n"); break; + case XT_NO_ACTIVITY: printf("WRITER --- no activity...\n"); break; + case XT_LOG_CACHE_FULL: printf("WRITER --- running out of log cache...\n"); break; + case XT_CHECKPOINT_REQ: printf("WRITER --- enough flushed for a checkpoint...\n"); break; + case XT_THREAD_WAITING: printf("WRITER --- thread waiting for writer...\n"); break; + case XT_TIME_TO_WRITE: printf("WRITER --- limit of 12MB reached, time to write...\n"); break; + } +#endif +} + +static void xlog_wr_could_go_faster(XTThreadPtr self, XTDatabaseHPtr db) +{ + if (db->db_wr_faster) { + if (!db->db_wr_fast) { + xt_set_normal_priority(self); + db->db_wr_fast = TRUE; + } + db->db_wr_faster = FALSE; + } +} + +static void xlog_wr_could_go_slower(XTThreadPtr self, XTDatabaseHPtr db) +{ + if (db->db_wr_fast && !db->db_wr_faster) { + xt_set_low_priority(self); + db->db_wr_fast = FALSE; + } +} + +static void xlog_wr_main(XTThreadPtr self) +{ + XTDatabaseHPtr db = self->st_database; + XTWriterStatePtr ws; + XTXactLogBufferDPtr record; + + xt_set_low_priority(self); + + alloczr_(ws, xt_free_writer_state, sizeof(XTWriterStateRec), XTWriterStatePtr); + ws->ws_db = db; + ws->ws_in_recover = FALSE; + + if (!db->db_xlog.xlog_seq_init(&ws->ws_seqread, xt_db_log_buffer_size, FALSE)) + xt_throw(self); + + if (!db->db_xlog.xlog_seq_start(&ws->ws_seqread, db->db_wr_log_id, db->db_wr_log_offset, FALSE)) + xt_throw(self); + + while (!self->t_quit) { + while (!self->t_quit) { + /* Determine the point to which we can write. + * This is the current log flush point! + */ + xt_lock_mutex_ns(&db->db_wr_lock); + db->db_wr_flush_point_log_id = db->db_xlog.xl_flush_log_id; + db->db_wr_flush_point_log_offset = db->db_xlog.xl_flush_log_offset; + xt_unlock_mutex_ns(&db->db_wr_lock); + + if (xt_comp_log_pos(db->db_wr_log_id, db->db_wr_log_offset, db->db_wr_flush_point_log_id, db->db_wr_flush_point_log_offset) >= 0) { + break; + } + + while (!self->t_quit) { + xlog_wr_could_go_faster(self, db); + + /* This is the restart position: */ + xt_lock_mutex(self, &db->db_wr_lock); + pushr_(xt_unlock_mutex, &db->db_wr_lock); + db->db_wr_log_id = ws->ws_seqread.xseq_rec_log_id; + db->db_wr_log_offset = ws->ws_seqread.xseq_rec_log_offset + ws->ws_seqread.xseq_record_len; + freer_(); // xt_unlock_mutex(&db->db_wr_lock) + + if (xt_comp_log_pos(db->db_wr_log_id, db->db_wr_log_offset, db->db_wr_flush_point_log_id, db->db_wr_flush_point_log_offset) >= 0) { + break; + } + + /* Apply all changes that have been flushed to the log, to the + * database. + */ + if (!db->db_xlog.xlog_seq_next(&ws->ws_seqread, &record, FALSE, self)) + xt_throw(self); + if (!record) { + break; + } + /* Count the number of bytes read from the log: */ + db->db_xlog.xl_log_bytes_read += ws->ws_seqread.xseq_record_len; + + switch (record->xl.xl_status_1) { + case XT_LOG_ENT_HEADER: + break; + case XT_LOG_ENT_NEW_LOG: + if (!db->db_xlog.xlog_seq_start(&ws->ws_seqread, XT_GET_DISK_4(record->xl.xl_log_id_4), 0, TRUE)) + xt_throw(self); + break; + case XT_LOG_ENT_NEW_TAB: + case XT_LOG_ENT_COMMIT: + case XT_LOG_ENT_ABORT: + case XT_LOG_ENT_CLEANUP: + case XT_LOG_ENT_OP_SYNC: + break; + case XT_LOG_ENT_DEL_LOG: + xtLogID log_id; + + log_id = XT_GET_DISK_4(record->xl.xl_log_id_4); + xt_dl_set_to_delete(self, db, log_id); + break; + default: + xt_xres_apply_in_order(self, ws, ws->ws_seqread.xseq_rec_log_id, ws->ws_seqread.xseq_rec_log_offset, record); + break; + } + } + } + + if (ws->ws_ot) { + xt_db_return_table_to_pool(self, ws->ws_ot); + ws->ws_ot = NULL; + } + + xlog_wr_could_go_slower(self, db); + + /* Note, we delay writing the database for a maximum of + * 2 seconds. + */ + xlog_wr_wait_for_log_flush(self, db); + } + + freer_(); // xt_free_writer_state(ss) +} + +static void *xlog_wr_run_thread(XTThreadPtr self) +{ + XTDatabaseHPtr db = (XTDatabaseHPtr) self->t_data; + int count; + void *mysql_thread; + + mysql_thread = myxt_create_thread(); + + while (!self->t_quit) { + try_(a) { + /* + * The garbage collector requires that the database + * is in use because. + */ + xt_use_database(self, db, XT_FOR_WRITER); + + /* This action is both safe and required (see details elsewhere) */ + xt_heap_release(self, self->st_database); + + xlog_wr_main(self); + } + catch_(a) { + /* This error is "normal"! */ + if (self->t_exception.e_xt_err != XT_ERR_NO_DICTIONARY && + !(self->t_exception.e_xt_err == XT_SIGNAL_CAUGHT && + self->t_exception.e_sys_err == SIGTERM)) + xt_log_and_clear_exception(self); + } + cont_(a); + + /* Avoid releasing the database (done above) */ + self->st_database = NULL; + xt_unuse_database(self, self); + + /* After an exception, pause before trying again... */ + /* Number of seconds */ +#ifdef DEBUG + count = 10; +#else + count = 2*60; +#endif + db->db_wr_idle = XT_THREAD_INERR; + while (!self->t_quit && count > 0) { + sleep(1); + count--; + } + db->db_wr_idle = XT_THREAD_BUSY; + } + + myxt_destroy_thread(mysql_thread, TRUE); + return NULL; +} + +static void xlog_wr_free_thread(XTThreadPtr self, void *data) +{ + XTDatabaseHPtr db = (XTDatabaseHPtr) data; + + if (db->db_wr_thread) { + xt_lock_mutex(self, &db->db_wr_lock); + pushr_(xt_unlock_mutex, &db->db_wr_lock); + db->db_wr_thread = NULL; + freer_(); // xt_unlock_mutex(&db->db_wr_lock) + } +} + +xtPublic void xt_start_writer(XTThreadPtr self, XTDatabaseHPtr db) +{ + char name[PATH_MAX]; + + sprintf(name, "WR-%s", xt_last_directory_of_path(db->db_main_path)); + xt_remove_dir_char(name); + db->db_wr_thread = xt_create_daemon(self, name); + xt_set_thread_data(db->db_wr_thread, db, xlog_wr_free_thread); + xt_run_thread(self, db->db_wr_thread, xlog_wr_run_thread); +} + +/* + * This function is called on database shutdown. + * We will wait a certain amounnt of time for the writer to + * complete its work. + * If it takes to long we will abort! + */ +xtPublic void xt_wait_for_writer(XTThreadPtr self, XTDatabaseHPtr db) +{ + time_t then, now; + xtBool message = FALSE; + + if (db->db_wr_thread) { + then = time(NULL); + while (xt_comp_log_pos(db->db_wr_log_id, db->db_wr_log_offset, db->db_wr_flush_point_log_id, db->db_wr_flush_point_log_offset) < 0) { + + xt_lock_mutex(self, &db->db_wr_lock); + pushr_(xt_unlock_mutex, &db->db_wr_lock); + db->db_wr_thread_waiting++; + /* Wake the writer so that it con complete its work. */ + if (db->db_wr_idle) { + if (!xt_broadcast_cond_ns(&db->db_wr_cond)) + xt_log_and_clear_exception_ns(); + } + freer_(); // xt_unlock_mutex(&db->db_wr_lock) + + xt_sleep_milli_second(10); + + xt_lock_mutex(self, &db->db_wr_lock); + pushr_(xt_unlock_mutex, &db->db_wr_lock); + db->db_wr_thread_waiting--; + freer_(); // xt_unlock_mutex(&db->db_wr_lock) + + now = time(NULL); + if (now >= then + 16) { + xt_logf(XT_NT_INFO, "Aborting wait for '%s' writer\n", db->db_name); + message = FALSE; + break; + } + if (now >= then + 2) { + if (!message) { + message = TRUE; + xt_logf(XT_NT_INFO, "Waiting for '%s' writer...\n", db->db_name); + } + } + } + + if (message) + xt_logf(XT_NT_INFO, "Writer '%s' done.\n", db->db_name); + } +} + +xtPublic void xt_stop_writer(XTThreadPtr self, XTDatabaseHPtr db) +{ + XTThreadPtr thr_wr; + + if (db->db_wr_thread) { + xt_lock_mutex(self, &db->db_wr_lock); + pushr_(xt_unlock_mutex, &db->db_wr_lock); + + /* This pointer is safe as long as you have the transaction lock. */ + if ((thr_wr = db->db_wr_thread)) { + xtThreadID tid = thr_wr->t_id; + + /* Make sure the thread quits when woken up. */ + xt_terminate_thread(self, thr_wr); + + /* Wake the writer thread so that it will quit: */ + xt_broadcast_cond(self, &db->db_wr_cond); + + freer_(); // xt_unlock_mutex(&db->db_wr_lock) + + /* + * GOTCHA: This is a wierd thing but the SIGTERM directed + * at a particular thread (in this case the sweeper) was + * being caught by a different thread and killing the server + * sometimes. Disconcerting. + * (this may only be a problem on Mac OS X) + xt_kill_thread(thread); + */ + xt_wait_for_thread(tid, FALSE); + + /* PMC - This should not be necessary to set the signal here, but in the + * debugger the handler is not called!!? + thr_wr->t_delayed_signal = SIGTERM; + xt_kill_thread(thread); + */ + db->db_wr_thread = NULL; + } + else + freer_(); // xt_unlock_mutex(&db->db_wr_lock) + } +} + +#ifdef NOT_USED +static void xlog_add_to_flush_buffer(u_int flush_count, XTXLogBlockPtr *flush_buffer, XTXLogBlockPtr block) +{ + register u_int count = flush_count; + register u_int i; + register u_int guess; + register xtInt8 r; + + i = 0; + while (i < count) { + guess = (i + count - 1) >> 1; + r = (xtInt8) block->xlb_address - (xtInt8) flush_buffer[guess]->xlb_address; + if (r == 0) { + // Should not happen... + ASSERT_NS(FALSE); + return; + } + if (r < (xtInt8) 0) + count = guess; + else + i = guess + 1; + } + + /* Insert at position i */ + memmove(flush_buffer + i + 1, flush_buffer + i, (flush_count - i) * sizeof(XTXLogBlockPtr)); + flush_buffer[i] = block; +} + +static XTXLogBlockPtr xlog_find_block(XTOpenFilePtr file, xtLogID log_id, off_t address, XTXLogCacheSegPtr *ret_seg) +{ + register XTXLogCacheSegPtr seg; + register XTXLogBlockPtr block; + register u_int hash_idx; + register XTXLogCacheRec *dcg = &xt_xlog_cache; + + seg = &dcg->xlc_segment[((u_int) address >> XT_XLC_BLOCK_SHIFTS) & XLC_SEGMENT_MASK]; + hash_idx = (((u_int) (address >> (XT_XLC_SEGMENT_SHIFTS + XT_XLC_BLOCK_SHIFTS))) ^ (log_id << 16)) % dcg->xlc_hash_size; + + xt_lock_mutex_ns(&seg->lcs_lock); + retry: + block = seg->lcs_hash_table[hash_idx]; + while (block) { + if (block->xlb_address == address && block->xlb_log_id == log_id) { + ASSERT_NS(block->xlb_state != XLC_BLOCK_FREE); + + /* Wait if the block is being read or written. + * If we will just read the data, then we don't care + * if the buffer is being written. + */ + if (block->xlb_state == XLC_BLOCK_READING) { + if (!xt_timed_wait_cond_ns(&seg->lcs_cond, &seg->lcs_lock, 100)) + break; + goto retry; + } + + *ret_seg = seg; + return block; + } + block = block->xlb_next; + } + + /* Block not found: */ + xt_unlock_mutex_ns(&seg->lcs_lock); + return NULL; +} + +static int xlog_cmp_log_files(struct XTThread *self, register const void *thunk, register const void *a, register const void *b) +{ +#pragma unused(self, thunk) + xtLogID lf_id = *((xtLogID *) a); + XTXactLogFilePtr lf_ptr = (XTXactLogFilePtr) b; + + if (lf_id < lf_ptr->lf_log_id) + return -1; + if (lf_id == lf_ptr->lf_log_id) + return 0; + return 1; +} + +#endif + + +#ifdef OLD_CODE +static xtBool xlog_free_lru_blocks() +{ + XTXLogBlockPtr block, pblock; + xtWord4 ru_time; + xtLogID log_id; + off_t address; + //off_t hash; + XTXLogCacheSegPtr seg; + u_int hash_idx; + xtBool have_global_lock = FALSE; + +#ifdef DEBUG_CHECK_CACHE + //xt_xlog_check_cache(); +#endif + retry: + if (!(block = xt_xlog_cache.xlc_lru_block)) + return OK; + + ru_time = block->xlb_ru_time; + log_id = block->xlb_log_id; + address = block->xlb_address; + + /* + hash = (address >> XT_XLC_BLOCK_SHIFTS) ^ ((off_t) log_id << 28); + seg = &xt_xlog_cache.xlc_segment[hash & XLC_SEGMENT_MASK]; + hash_idx = (hash >> XT_XLC_SEGMENT_SHIFTS) % xt_xlog_cache.xlc_hash_size; + */ + seg = &xt_xlog_cache.xlc_segment[((u_int) address >> XT_XLC_BLOCK_SHIFTS) & XLC_SEGMENT_MASK]; + hash_idx = (((u_int) (address >> (XT_XLC_SEGMENT_SHIFTS + XT_XLC_BLOCK_SHIFTS))) ^ (log_id << 16)) % xt_xlog_cache.xlc_hash_size; + + xt_lock_mutex_ns(&seg->lcs_lock); + + free_more: + pblock = NULL; + block = seg->lcs_hash_table[hash_idx]; + while (block) { + if (block->xlb_address == address && block->xlb_log_id == log_id) { + ASSERT_NS(block->xlb_state != XLC_BLOCK_FREE); + + /* Try again if the block has been used in the meantime: */ + if (ru_time != block->xlb_ru_time) { + if (have_global_lock) + /* Having this lock means we have already freed at least one block so + * don't bother to free more if we are having trouble. + */ + goto done_ok; + + /* If the recently used time has changed, then the + * block is probably no longer the LR used. + */ + xt_unlock_mutex_ns(&seg->lcs_lock); + goto retry; + } + + /* Wait if the block is being read: */ + if (block->xlb_state == XLC_BLOCK_READING) { + if (have_global_lock) + goto done_ok; + + /* Wait for the block to be read, then try again. */ + if (!xt_timed_wait_cond_ns(&seg->lcs_cond, &seg->lcs_lock, 100)) + goto failed; + xt_unlock_mutex_ns(&seg->lcs_lock); + goto retry; + } + + goto free_the_block; + } + pblock = block; + block = block->xlb_next; + } + + if (have_global_lock) { + xt_unlock_mutex_ns(&xt_xlog_cache.xlc_lock); + have_global_lock = FALSE; + } + + /* We did not find the block, someone else freed it... */ + xt_unlock_mutex_ns(&seg->lcs_lock); + goto retry; + + free_the_block: + ASSERT_NS(block->xlb_state == XLC_BLOCK_CLEAN); + + /* Remove from the hash table: */ + if (pblock) + pblock->xlb_next = block->xlb_next; + else + seg->lcs_hash_table[hash_idx] = block->xlb_next; + + /* Now free the block */ + if (!have_global_lock) { + xt_lock_mutex_ns(&xt_xlog_cache.xlc_lock); + have_global_lock = TRUE; + } + + /* Remove from the MRU list: */ + if (xt_xlog_cache.xlc_lru_block == block) + xt_xlog_cache.xlc_lru_block = block->xlb_mr_used; + if (xt_xlog_cache.xlc_mru_block == block) + xt_xlog_cache.xlc_mru_block = block->xlb_lr_used; + if (block->xlb_lr_used) + block->xlb_lr_used->xlb_mr_used = block->xlb_mr_used; + if (block->xlb_mr_used) + block->xlb_mr_used->xlb_lr_used = block->xlb_lr_used; + + /* Put the block on the free list: */ + block->xlb_next = xt_xlog_cache.xlc_free_list; + xt_xlog_cache.xlc_free_list = block; + xt_xlog_cache.xlc_free_count++; + block->xlb_state = XLC_BLOCK_FREE; + + if (xt_xlog_cache.xlc_free_count < XT_XLC_MAX_FREE_COUNT) { + /* Now that we have all the locks, try to free some more in this segment: */ + block = block->xlb_mr_used; + for (u_int i=0; block && i<XLC_SEGMENT_COUNT; i++) { + ru_time = block->xlb_ru_time; + log_id = block->xlb_log_id; + address = block->xlb_address; + + if (seg == &xt_xlog_cache.xlc_segment[((u_int) address >> XT_XLC_BLOCK_SHIFTS) & XLC_SEGMENT_MASK]) { + hash_idx = (((u_int) (address >> (XT_XLC_SEGMENT_SHIFTS + XT_XLC_BLOCK_SHIFTS))) ^ (log_id << 16)) % xt_xlog_cache.xlc_hash_size; + goto free_more; + } + block = block->xlb_mr_used; + } + } + + done_ok: + xt_unlock_mutex_ns(&xt_xlog_cache.xlc_lock); + xt_unlock_mutex_ns(&seg->lcs_lock); +#ifdef DEBUG_CHECK_CACHE + //xt_xlog_check_cache(); +#endif + return OK; + + failed: + xt_unlock_mutex_ns(&seg->lcs_lock); +#ifdef DEBUG_CHECK_CACHE + //xt_xlog_check_cache(); +#endif + return FAILED; +} + +#endif diff --git a/storage/pbxt/src/xactlog_xt.h b/storage/pbxt/src/xactlog_xt.h new file mode 100644 index 00000000000..391b646b53f --- /dev/null +++ b/storage/pbxt/src/xactlog_xt.h @@ -0,0 +1,460 @@ +/* Copyright (c) 2007 PrimeBase Technologies GmbH + * + * PrimeBase XT + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You 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 + * + * 2007-10-31 Paul McCullagh + * + * H&G2JCtL + * + * The new table cache. Caches all non-index data. This includes the data + * files and the row pointer files. + */ + +#ifndef __xactlog_xt_h__ +#define __xactlog_xt_h__ + +#include "pthread_xt.h" +#include "filesys_xt.h" +#include "sortedlist_xt.h" + +struct XTThread; +struct XTOpenTable; +struct XTDatabase; + +#ifdef DEBUG +//#define XT_USE_CACHE_DEBUG_SIZES +#endif + +#ifdef XT_USE_CACHE_DEBUG_SIZES +#define XT_XLC_BLOCK_SHIFTS 5 +#define XT_XLC_FILE_SLOTS 7 +#define XT_XLC_SEGMENT_SHIFTS 1 +#define XT_XLC_MAX_FLUSH_SEG_COUNT 10 +#define XT_XLC_MAX_FREE_COUNT 10 +#else +/* Block size is determined by the number of shifts 1 << 15 = 32K */ +#define XT_XLC_BLOCK_SHIFTS 15 +#define XT_XLC_FILE_SLOTS 71 +/* The number of segments are determined by the segment shifts 1 << 3 = 8 */ +#define XT_XLC_SEGMENT_SHIFTS 3 +#define XT_XLC_MAX_FLUSH_SEG_COUNT 250 +#define XT_XLC_MAX_FREE_COUNT 100 +#endif + +#define XT_XLC_BLOCK_SIZE (1 << XT_XLC_BLOCK_SHIFTS) +#define XT_XLC_BLOCK_MASK (XT_XLC_BLOCK_SIZE - 1) + +#define XT_TIME_DIFF(start, now) (\ + ((xtWord4) (now) < (xtWord4) (start)) ? \ + ((xtWord4) 0XFFFFFFFF - ((xtWord4) (start) - (xtWord4) (now))) : \ + ((xtWord4) (now) - (xtWord4) (start))) + +#define XLC_SEGMENT_COUNT ((off_t) 1 << XT_XLC_SEGMENT_SHIFTS) +#define XLC_SEGMENT_MASK (XLC_SEGMENT_COUNT - 1) +#define XLC_MAX_FLUSH_COUNT (XT_XLC_MAX_FLUSH_SEG_COUNT * XLC_SEGMENT_COUNT) + +#define XLC_BLOCK_FREE 0 +#define XLC_BLOCK_READING 1 +#define XLC_BLOCK_CLEAN 2 + +#define XT_RECYCLE_LOGS 0 +#define XT_DELETE_LOGS 1 +#define XT_KEEP_LOGS 2 + +/* LOG CACHE ---------------------------------------------------- */ + +typedef struct XTXLogBlock { + off_t xlb_address; /* The block address. */ + xtLogID xlb_log_id; /* The log id of the block. */ + xtWord4 xlb_state; /* Block status. */ + struct XTXLogBlock *xlb_next; /* Pointer to next block on hash list, or next free block on free list. */ + xtWord1 xlb_data[XT_XLC_BLOCK_SIZE]; +} XTXLogBlockRec, *XTXLogBlockPtr; + +/* A disk cache segment. The cache is divided into a number of segments + * to improve concurrency. + */ +typedef struct XTXLogCacheSeg { + xt_mutex_type lcs_lock; /* The cache segment lock. */ + xt_cond_type lcs_cond; + XTXLogBlockPtr *lcs_hash_table; +} XTXLogCacheSegRec, *XTXLogCacheSegPtr; + +typedef struct XTXLogCache { + xt_mutex_type xlc_lock; /* The public cache lock. */ + xt_cond_type xlc_cond; /* The public cache wait condition. */ + XTXLogCacheSegRec xlc_segment[XLC_SEGMENT_COUNT]; + XTXLogBlockPtr xlc_blocks; + XTXLogBlockPtr xlc_blocks_end; + XTXLogBlockPtr xlc_next_to_free; + xtWord4 xlc_free_count; + xtWord4 xlc_hash_size; + xtWord4 xlc_block_count; + xtWord8 xlc_upper_limit; +} XTXLogCacheRec; + +/* LOG ENTRIES ---------------------------------------------------- */ + +#define XT_LOG_ENT_EOF 0 +#define XT_LOG_ENT_HEADER 1 +#define XT_LOG_ENT_NEW_LOG 2 /* Move to the next log! NOTE!! May not appear in a group!! */ +#define XT_LOG_ENT_DEL_LOG 3 /* Delete the given transaction/data log. */ +#define XT_LOG_ENT_NEW_TAB 4 /* This record indicates a new table was created. */ + +#define XT_LOG_ENT_COMMIT 5 /* Transaction was committed. */ +#define XT_LOG_ENT_ABORT 6 /* Transaction was aborted. */ +#define XT_LOG_ENT_CLEANUP 7 /* Written after a cleanup. */ + +#define XT_LOG_ENT_REC_MODIFIED 8 /* This records has been modified by the transaction. */ +#define XT_LOG_ENT_UPDATE 9 +#define XT_LOG_ENT_UPDATE_BG 10 +#define XT_LOG_ENT_UPDATE_FL 11 +#define XT_LOG_ENT_UPDATE_FL_BG 12 +#define XT_LOG_ENT_INSERT 13 +#define XT_LOG_ENT_INSERT_BG 14 +#define XT_LOG_ENT_INSERT_FL 15 +#define XT_LOG_ENT_INSERT_FL_BG 16 +#define XT_LOG_ENT_DELETE 17 +#define XT_LOG_ENT_DELETE_BG 18 +#define XT_LOG_ENT_DELETE_FL 19 +#define XT_LOG_ENT_DELETE_FL_BG 20 + +#define XT_LOG_ENT_REC_FREED 21 /* This record has been placed in the free list. */ +#define XT_LOG_ENT_REC_REMOVED 22 /* Free record and dependecies: index references, blob references. */ +#define XT_LOG_ENT_REC_REMOVED_EXT 23 /* Free record and dependecies: index references, extended data, blob references. */ +#define XT_LOG_ENT_REC_REMOVED_BI 38 /* Free record and dependecies: includes before image of record, for freeing index, etc. */ + +#define XT_LOG_ENT_REC_MOVED 24 /* The record has been moved by the compactor. */ +#define XT_LOG_ENT_REC_CLEANED 25 /* This record has been cleaned by the sweeper. */ +#define XT_LOG_ENT_REC_CLEANED_1 26 /* This record has been cleaned by the sweeper (short form). */ +#define XT_LOG_ENT_REC_UNLINKED 27 /* The record after this record is unlinked from the variation list. */ + +#define XT_LOG_ENT_ROW_NEW 28 /* Row allocated from the EOF. */ +#define XT_LOG_ENT_ROW_NEW_FL 29 /* Row allocated from the free list. */ +#define XT_LOG_ENT_ROW_ADD_REC 30 /* Record added to the row. */ +#define XT_LOG_ENT_ROW_SET 31 +#define XT_LOG_ENT_ROW_FREED 32 + +#define XT_LOG_ENT_OP_SYNC 33 /* Operations syncronised. */ +#define XT_LOG_ENT_EXT_REC_OK 34 /* An extended record */ +#define XT_LOG_ENT_EXT_REC_DEL 35 /* A deleted extended record */ + +#define XT_LOG_ENT_NO_OP 36 /* If write to the database fails, we still try to log the + * op code, in an attempt to continue, if writting to log + * still works. + */ +#define XT_LOG_ENT_END_OF_LOG 37 /* This is a record that indicates the end of the log, and + * fills to the end of a 512 byte block. + */ + +#define XT_LOG_FILE_MAGIC 0xAE88FE12 +#define XT_LOG_VERSION_NO 1 + +typedef struct XTXactLogHeader { + xtWord1 xh_status_1; /* XT_LOG_ENT_HEADER */ + xtWord1 xh_checksum_1; + XTDiskValue4 xh_size_4; /* Must be set to sizeof(XTXactLogHeaderDRec). */ + XTDiskValue8 xh_free_space_8; /* The accumulated free space in this file. */ + XTDiskValue8 xh_file_len_8; /* The last confirmed correct file length (always set on close). */ + XTDiskValue8 xh_comp_pos_8; /* Compaction position (XT_DL_STATUS_CO_SOURCE only). */ + xtWord1 xh_comp_stat_1; /* The compaction status XT_DL_STATUS_CO_SOURCE/XT_DL_STATUS_CO_TARGET */ + XTDiskValue4 xh_log_id_4; + XTDiskValue4 xh_version_2; /* XT_LOG_VERSION_NO */ + XTDiskValue4 xh_magic_4; /* MUST always be at the end of the structure!! */ +} XTXactLogHeaderDRec, *XTXactLogHeaderDPtr; + +/* This is the original log head size (don't change): */ +#define XT_MIN_LOG_HEAD_SIZE (offsetof(XTXactLogHeaderDRec, xh_log_id_4) + 4) +#define XT_LOG_HEAD_MAGIC(b, l) XT_GET_DISK_4(((xtWord1 *) (b)) + (l) - 4) + +typedef struct XTXactNewLogEntry { + xtWord1 xl_status_1; /* XT_LOG_ENT_NEW_LOG, XT_LOG_ENT_DEL_LOG */ + xtWord1 xl_checksum_1; + XTDiskValue4 xl_log_id_4; /* Store the current table ID. */ +} XTXactNewLogEntryDRec, *XTXactNewLogEntryDPtr; + +typedef struct XTXactNewTabEntry { + xtWord1 xt_status_1; /* XT_LOG_ENT_NEW_TAB */ + xtWord1 xt_checksum_1; + XTDiskValue4 xt_tab_id_4; /* Store the current table ID. */ +} XTXactNewTabEntryDRec, *XTXactNewTabEntryDPtr; + +/* This record must appear in a transaction group, and therefore has no transaction ID: */ +typedef struct XTXactEndEntry { + xtWord1 xe_status_1; /* XT_LOG_ENT_COMMIT, XT_LOG_ENT_ABORT */ + xtWord1 xe_checksum_1; + XTDiskValue4 xe_xact_id_4; /* The transaction. */ + XTDiskValue4 xe_not_used_4; /* Was the end sequence number (no longer used - v1.0.04+), set to zero). */ +} XTXactEndEntryDRec, *XTXactEndEntryDPtr; + +typedef struct XTXactCleanupEntry { + xtWord1 xc_status_1; /* XT_LOG_ENT_CLEANUP */ + xtWord1 xc_checksum_1; + XTDiskValue4 xc_xact_id_4; /* The transaction that was cleaned up. */ +} XTXactCleanupEntryDRec, *XTXactCleanupEntryDPtr; + +typedef struct XTactUpdateEntry { + xtWord1 xu_status_1; /* XT_LOG_ENT_REC_MODIFIED, XT_LOG_ENT_UPDATE, XT_LOG_ENT_INSERT, XT_LOG_ENT_DELETE */ + /* XT_LOG_ENT_UPDATE_BG, XT_LOG_ENT_INSERT_BG, XT_LOG_ENT_DELETE_BG */ + XTDiskValue2 xu_checksum_2; + XTDiskValue4 xu_op_seq_4; /* Operation sequence number. */ + XTDiskValue4 xu_tab_id_4; /* Table ID of the record. */ + xtDiskRecordID4 xu_rec_id_4; /* Offset of the new updated record. */ + XTDiskValue2 xu_size_2; /* Size of the record data. */ + /* This is the start of the actual record data: */ + xtWord1 xu_rec_type_1; /* Type of the record. */ + xtWord1 xu_stat_id_1; + xtDiskRecordID4 xu_prev_rec_id_4; /* The previous variation of this record. */ + XTDiskValue4 xu_xact_id_4; /* The transaction ID. */ + XTDiskValue4 xu_row_id_4; /* The row ID of this record. */ +} XTactUpdateEntryDRec, *XTactUpdateEntryDPtr; + +typedef struct XTactUpdateFLEntry { + xtWord1 xf_status_1; /* XT_LOG_ENT_UPDATE_FL, XT_LOG_ENT_INSERT_FL, XT_LOG_ENT_DELETE_FL */ + /* XT_LOG_ENT_UPDATE_FL_BG, XT_LOG_ENT_INSERT_FL_BG, XT_LOG_ENT_DELETE_FL_BG */ + XTDiskValue2 xf_checksum_2; + XTDiskValue4 xf_op_seq_4; /* Operation sequence number. */ + XTDiskValue4 xf_tab_id_4; /* Table ID of the record. */ + xtDiskRecordID4 xf_rec_id_4; /* Offset of the new updated record. */ + XTDiskValue2 xf_size_2; /* Size of the record data. */ + xtDiskRecordID4 xf_free_rec_id_4; /* Update to the free list. */ + /* This is the start of the actual record data: */ + xtWord1 xf_rec_type_1; /* Type of the record. */ + xtWord1 xf_stat_id_1; + xtDiskRecordID4 xf_prev_rec_id_4; /* The previous variation of this record. */ + XTDiskValue4 xf_xact_id_4; /* The transaction ID. */ + XTDiskValue4 xf_row_id_4; /* The row ID of this record. */ +} XTactUpdateFLEntryDRec, *XTactUpdateFLEntryDPtr; + +typedef struct XTactFreeRecEntry { + xtWord1 fr_status_1; /* XT_LOG_ENT_REC_REMOVED, XT_LOG_ENT_REC_REMOVED_EXT, XT_LOG_ENT_REC_FREED */ + xtWord1 fr_checksum_1; + XTDiskValue4 fr_op_seq_4; /* Operation sequence number. */ + XTDiskValue4 fr_tab_id_4; /* Table ID of the record. */ + xtDiskRecordID4 fr_rec_id_4; /* Offset of the new written record. */ + /* This data confirms the record state for release of + * attached resources (extended records, indexes and blobs) + */ + xtWord1 fr_stat_id_1; /* The statement ID of the record. */ + XTDiskValue4 fr_xact_id_4; /* The transaction ID of the record. */ + /* This is the start of the actual record data: */ + xtWord1 fr_rec_type_1; + xtWord1 fr_not_used_1; + xtDiskRecordID4 fr_next_rec_id_4; /* The next block on the free list. */ +} XTactFreeRecEntryDRec, *XTactFreeRecEntryDPtr; + +typedef struct XTactRemoveBIEntry { + xtWord1 rb_status_1; /* XT_LOG_ENT_REC_REMOVED_BI */ + XTDiskValue2 rb_checksum_2; + XTDiskValue4 rb_op_seq_4; /* Operation sequence number. */ + XTDiskValue4 rb_tab_id_4; /* Table ID of the record. */ + xtDiskRecordID4 rb_rec_id_4; /* Offset of the new written record. */ + XTDiskValue2 rb_size_2; /* Size of the record data. */ + + xtWord1 rb_new_rec_type_1; /* New type of the record (needed for below). */ + + /* This is the start of the record data, with some fields overwritten for the free: */ + xtWord1 rb_rec_type_1; /* Type of the record. */ + xtWord1 rb_stat_id_1; + xtDiskRecordID4 rb_next_rec_id_4; /* The next block on the free list (overwritten). */ + XTDiskValue4 rb_xact_id_4; /* The transaction ID. */ + XTDiskValue4 rb_row_id_4; /* The row ID of this record. */ +} XTactRemoveBIEntryDRec, *XTactRemoveBIEntryDPtr; + +typedef struct XTactWriteRecEntry { + xtWord1 xw_status_1; /* XT_LOG_ENT_REC_MOVED, XT_LOG_ENT_REC_CLEANED, XT_LOG_ENT_REC_CLEANED_1, + * XT_LOG_ENT_REC_UNLINKED */ + xtWord1 xw_checksum_1; + XTDiskValue4 xw_op_seq_4; /* Operation sequence number. */ + XTDiskValue4 xw_tab_id_4; /* Table ID of the record. */ + xtDiskRecordID4 xw_rec_id_4; /* Offset of the new written record. */ + /* This is the start of the actual record data: */ + xtWord1 xw_rec_type_1; + xtWord1 xw_stat_id_1; + xtDiskRecordID4 xw_next_rec_id_4; /* The next block on the free list. */ +} XTactWriteRecEntryDRec, *XTactWriteRecEntryDPtr; + +typedef struct XTactRowAddedEntry { + xtWord1 xa_status_1; /* XT_LOG_ENT_ROW_NEW or XT_LOG_ENT_ROW_NEW_FL */ + xtWord1 xa_checksum_1; + XTDiskValue4 xa_op_seq_4; /* Operation sequence number. */ + XTDiskValue4 xa_tab_id_4; /* Table ID of the record. */ + XTDiskValue4 xa_row_id_4; /* The row ID of the row allocated. */ + XTDiskValue4 xa_free_list_4; /* Change to the free list (ONLY for XT_LOG_ENT_ROW_NEW_FL). */ +} XTactRowAddedEntryDRec, *XTactRowAddedEntryDPtr; + +typedef struct XTactWriteRowEntry { + xtWord1 wr_status_1; /* XT_LOG_ENT_ROW_ADD_REC, XT_LOG_ENT_ROW_SET, XT_LOG_ENT_ROW_FREED */ + xtWord1 wr_checksum_1; + XTDiskValue4 wr_op_seq_4; /* Operation sequence number. */ + XTDiskValue4 wr_tab_id_4; /* Table ID of the record. */ + XTDiskValue4 wr_row_id_4; /* Row ID of the row that was modified. */ + /* This is the start of the actual record data: */ + XTDiskValue4 wr_ref_id_4; /* The row reference data. */ +} XTactWriteRowEntryDRec, *XTactWriteRowEntryDPtr; + +typedef struct XTactOpSyncEntry { + xtWord1 os_status_1; /* XT_LOG_ENT_OP_SYNC */ + xtWord1 os_checksum_1; + XTDiskValue4 os_time_4; /* Time of the restart. */ +} XTactOpSyncEntryDRec, *XTactOpSyncEntryDPtr; + +typedef struct XTactNoOpEntry { + xtWord1 no_status_1; /* XT_LOG_ENT_NO_OP */ + xtWord1 no_checksum_1; + XTDiskValue4 no_op_seq_4; /* Operation sequence number. */ + XTDiskValue4 no_tab_id_4; /* Table ID of the record. */ +} XTactNoOpEntryDRec, *XTactNoOpEntryDPtr; + +typedef struct XTactExtRecEntry { + xtWord1 er_status_1; /* XT_LOG_ENT_EXT_REC_OK, XT_LOG_ENT_EXT_REC_DEL */ + XTDiskValue4 er_data_size_4; /* Size of this record data area only. */ + XTDiskValue4 er_tab_id_4; /* The table referencing this extended record. */ + xtDiskRecordID4 er_rec_id_4; /* The ID of the reference record. */ + xtWord1 er_data[XT_VAR_LENGTH]; +} XTactExtRecEntryDRec, *XTactExtRecEntryDPtr; + +typedef union XTXactLogBuffer { + XTXactLogHeaderDRec xh; + XTXactNewLogEntryDRec xl; + XTXactNewTabEntryDRec xt; + XTXactEndEntryDRec xe; + XTXactCleanupEntryDRec xc; + XTactUpdateEntryDRec xu; + XTactUpdateFLEntryDRec xf; + XTactFreeRecEntryDRec fr; + XTactRemoveBIEntryDRec rb; + XTactWriteRecEntryDRec xw; + XTactRowAddedEntryDRec xa; + XTactWriteRowEntryDRec wr; + XTactOpSyncEntryDRec os; + XTactExtRecEntryDRec er; + XTactNoOpEntryDRec no; +} XTXactLogBufferDRec, *XTXactLogBufferDPtr; + +/* ---------------------------------------- */ + +typedef struct XTXactSeqRead { + size_t xseq_buffer_size; /* Size of the buffer. */ + xtBool xseq_load_cache; /* TRUE if reads should load the cache! */ + + xtLogID xseq_log_id; + XTOpenFilePtr xseq_log_file; + off_t xseq_log_eof; + + xtLogOffset xseq_buf_log_offset; /* File offset of the buffer. */ + size_t xseq_buffer_len; /* Amount of data in the buffer. */ + xtWord1 *xseq_buffer; + + xtLogID xseq_rec_log_id; /* The current record log ID. */ + xtLogOffset xseq_rec_log_offset; /* The current log read position. */ + size_t xseq_record_len; /* The length of the current record. */ +} XTXactSeqReadRec, *XTXactSeqReadPtr; + +typedef struct XTXactLogFile { + xtLogID lf_log_id; + off_t lr_file_len; /* The log file size (0 means this is the last log) */ +} XTXactLogFileRec, *XTXactLogFilePtr; + +/* + * The transaction log. Each database has one. + */ +typedef struct XTDatabaseLog { + struct XTDatabase *xl_db; + + off_t xl_log_file_threshold; + u_int xl_log_file_count; /* Number of logs to use (>= 1). */ + u_int xt_log_file_dyn_count; /* A dynamic value to add to log file count. */ + u_int xt_log_file_dyn_dec; /* Used to descide when to decrement the dynamic count. */ + size_t xl_size_of_buffers; /* The size of both log buffers. */ + xtWord8 xl_log_bytes_written; /* The total number of bytes written to the log, after recovery. */ + xtWord8 xl_log_bytes_flushed; /* The total number of bytes flushed to the log, after recovery. */ + xtWord8 xl_log_bytes_read; /* The total number of log bytes read, after recovery. */ + + u_int xl_last_flush_time; /* Last flush time in micro-seconds. */ + + /* The writer log buffer: */ + xt_mutex_type xl_write_lock; + xt_cond_type xl_write_cond; + xtBool xt_writing; /* TRUE if a thread is writing. */ + xtLogID xl_log_id; /* The number of the write log. */ + XTOpenFilePtr xl_log_file; /* The open write log. */ + + XTSpinLockRec xl_buffer_lock; /* This locks both the write and the append log buffers. */ + + xtLogID xl_max_log_id; /* The ID of the highest log on disk. */ + + xtLogID xl_write_log_id; /* This is the log ID were the write data will go. */ + xtLogOffset xl_write_log_offset; /* The file offset of the write log. */ + size_t xl_write_buf_pos; + size_t xl_write_buf_pos_start; + xtWord1 *xl_write_buffer; + xtBool xl_write_done; /* TRUE if the write buffer has been written! */ + + xtLogID xl_append_log_id; /* This is the log ID were the append data will go. */ + xtLogOffset xl_append_log_offset; /* The file offset in the log were the append data will go. */ + size_t xl_append_buf_pos; /* The amount of data in the append buffer. */ + size_t xl_append_buf_pos_start; /* The amount of data in the append buffer already written. */ + xtWord1 *xl_append_buffer; + + xtLogID xl_flush_log_id; /* The last log flushed. */ + xtLogOffset xl_flush_log_offset; /* The position in the log flushed. */ + + void xlog_setup(struct XTThread *self, struct XTDatabase *db, off_t log_file_size, size_t transaction_buffer_size, int log_count); + xtBool xlog_set_write_offset(xtLogID log_id, xtLogOffset log_offset, xtLogID max_log_id, struct XTThread *thread); + void xlog_close(struct XTThread *self); + void xlog_exit(struct XTThread *self); + void xlog_name(size_t size, char *path, xtLogID log_id); + int xlog_delete_log(xtLogID del_log_id, struct XTThread *thread); + + xtBool xlog_append(struct XTThread *thread, size_t size1, xtWord1 *data1, size_t size2, xtWord1 *data2, xtBool commit, xtLogID *log_id, xtLogOffset *log_offset); + xtBool xlog_flush(struct XTThread *thread); + xtBool xlog_flush_pending(); + + xtBool xlog_seq_init(XTXactSeqReadPtr seq, size_t buffer_size, xtBool load_cache); + void xlog_seq_exit(XTXactSeqReadPtr seq); + void xlog_seq_close(XTXactSeqReadPtr seq); + xtBool xlog_seq_start(XTXactSeqReadPtr seq, xtLogID log_id, xtLogOffset log_offset, xtBool missing_ok); + xtBool xlog_rnd_read(XTXactSeqReadPtr seq, xtLogID log_id, xtLogOffset log_offset, size_t size, xtWord1 *data, size_t *read, struct XTThread *thread); + size_t xlog_bytes_to_write(); + xtBool xlog_read_from_cache(XTXactSeqReadPtr seq, xtLogID log_id, xtLogOffset log_offset, size_t size, off_t eof, xtWord1 *buffer, size_t *data_read, struct XTThread *thread); + xtBool xlog_write_thru(XTXactSeqReadPtr seq, size_t size, xtWord1 *data, struct XTThread *thread); + xtBool xlog_verify(XTXactLogBufferDPtr record, size_t rec_size, xtLogID log_id); + xtBool xlog_seq_next(XTXactSeqReadPtr seq, XTXactLogBufferDPtr *entry, xtBool verify, struct XTThread *thread); + void xlog_seq_skip(XTXactSeqReadPtr seq, size_t size); + +private: + xtBool xlog_open_log(xtLogID log_id, off_t curr_eof, struct XTThread *thread); +} XTDatabaseLogRec, *XTDatabaseLogPtr; + +xtBool xt_xlog_flush_log(struct XTThread *thread); +xtBool xt_xlog_log_data(struct XTThread *thread, size_t len, XTXactLogBufferDPtr log_entry, xtBool commit); +xtBool xt_xlog_modify_table(struct XTOpenTable *ot, u_int status, xtOpSeqNo op_seq, xtRecordID free_list, xtRecordID address, size_t size, xtWord1 *data); + +void xt_xlog_init(struct XTThread *self, size_t cache_size); +void xt_xlog_exit(struct XTThread *self); +xtInt8 xt_xlog_get_usage(); +xtInt8 xt_xlog_get_size(); +xtLogID xt_xlog_get_min_log(struct XTThread *self, struct XTDatabase *db); +void xt_xlog_delete_logs(struct XTThread *self, struct XTDatabase *db); + +void xt_start_writer(struct XTThread *self, struct XTDatabase *db); +void xt_wait_for_writer(struct XTThread *self, struct XTDatabase *db); +void xt_stop_writer(struct XTThread *self, struct XTDatabase *db); + +#endif + diff --git a/storage/pbxt/src/xt_config.h b/storage/pbxt/src/xt_config.h new file mode 100644 index 00000000000..6571ebdaebe --- /dev/null +++ b/storage/pbxt/src/xt_config.h @@ -0,0 +1,99 @@ +/* Copyright (c) 2005 PrimeBase Technologies GmbH + * + * PrimeBase XT + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You 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 + * + * 2006-03-22 Paul McCullagh + * + * H&G2JCtL + * + * This header file should be included in every source, before all other + * headers. + * + * In particular: BEFORE THE SYSTEM HEADERS + */ + +#ifndef __xt_config_h__ +#define __xt_config_h__ + +#define MYSQL_SERVER 1 + +#ifdef DRIZZLED +#include "drizzled/global.h" +const int max_connections = 500; +#else +#include <mysql_version.h> +#include "my_global.h" +#endif + +/* + * This enables everything that GNU can do. The macro is actually + * recommended for new programs. + */ +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif + +/* + * Make sure we use the thread safe version of the library. + */ +#define _THREAD_SAFE + +/* + * This causes things to be defined like stuff in inttypes.h + * which is used in printf() + */ +#ifndef __STDC_FORMAT_MACROS +#define __STDC_FORMAT_MACROS +#endif + +/* + * This define is not required by Linux because the _GNU_SOURCE + * definition includes POSIX complience. But I need it for + * Mac OS X. + */ +//#define _POSIX_C_SOURCE 2 +//#define _ANSI_SOURCE + +#ifdef __APPLE__ +#define XT_MAC +#endif + +#if defined(MSDOS) || defined(__WIN__) +#define XT_WIN +#endif + +#ifdef XT_WIN +#ifdef _DEBUG +#define DEBUG +#endif // _DEBUG +#else +#define XT_STREAMING +#endif + +#ifdef __FreeBSD__ +#define XT_FREEBSD +#endif + +#ifdef __NetBSD__ +#define XT_NETBSD +#endif + +#ifdef __sun +#define XT_SOLARIS +#endif + +#endif diff --git a/storage/pbxt/src/xt_defs.h b/storage/pbxt/src/xt_defs.h new file mode 100644 index 00000000000..16981ddc672 --- /dev/null +++ b/storage/pbxt/src/xt_defs.h @@ -0,0 +1,782 @@ +/* Copyright (c) 2005 PrimeBase Technologies GmbH + * + * PrimeBase XT + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You 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 + * + * Author: Paul McCullagh + * + * H&G2JCtL + */ +#ifndef __xt_defs_h__ +#define __xt_defs_h__ + +#ifdef XT_WIN +#include "win_inttypes.h" +#else +#include <inttypes.h> +#endif +#include <sys/types.h> +#include <assert.h> +#include <stddef.h> +#include <string.h> + +//#include "pthread_xt.h" + +#ifdef DEBUG +//#define DEBUG_LOG_DELETE +#endif + +/* the following macros are used to quote compile-time numeric + * constants into strings, e.g. __LINE__ + */ +#define _QUOTE(x) #x +#define QUOTE(x) _QUOTE(x) + +/* ---------------------------------------------------------------------- + * CRASH DEBUGGING + */ + +/* Define this if crash debug should be on by default: + * pbxt_crash_debug set to TRUE by default. + * It can be turned off by creating a file called 'no-debug' + * in the pbxt database. + * It can be turned on by defining the file 'crash-debug' + * in the pbxt database. + */ +//#define XT_CRASH_DEBUG + +/* These are the things crash debug will do: */ +/* Create a core dump (windows only): */ +#define XT_COREDUMP + +/* Backup the datadir before recovery after a crash: */ +//#define XT_BACKUP_BEFORE_RECOVERY + +/* Keep this number of transaction logs around + * for analysis after a crash. + */ +#define XT_NUMBER_OF_LOGS_TO_SAVE 5 + +/* ---------------------------------------------------------------------- + * GENERIC GLOBAL TYPES + */ + +#ifdef XT_WIN + +#define xtInt1 __int8 +#define xtInt2 __int16 +#define xtInt4 __int32 +#define xtInt8 __int64 + +#define xtWord1 unsigned __int8 +#define xtWord2 unsigned __int16 +#define xtWord4 unsigned __int32 +#define xtWord8 unsigned __int64 + +#ifndef PATH_MAX +#define PATH_MAX MAX_PATH +#endif +#ifndef NAME_MAX +#define NAME_MAX MAX_PATH +#endif + +/* XT actually assumes that off_t is 8 bytes: */ +#define off_t xtWord8 + +#else // XT_WIN + +#define xtInt1 int8_t +#define xtInt2 int16_t +#define xtInt4 int32_t +#define xtInt8 int64_t + +#ifdef XT_SOLARIS +#define u_int8_t uint8_t +#define u_int16_t uint16_t +#define u_int32_t uint32_t +#define u_int64_t uint64_t +#endif + +#define xtWord1 u_int8_t +#define xtWord2 u_int16_t +#define xtWord4 u_int32_t +#define xtWord8 u_int64_t + +#endif // XT_WIN + +/* A pointer sized word value: */ +#define xtWordPS ptrdiff_t + +#define XT_MAX_INT_1 ((xtInt1) 0x7F) +#define XT_MIN_INT_1 ((xtInt1) 0x80) +#define XT_MAX_INT_2 ((xtInt2) 0x7FFF) +#define XT_MIN_INT_2 ((xtInt2) 0x8000) +#define XT_MAX_INT_4 ((xtInt4) 0x7FFFFFFF) +#define XT_MIN_INT_4 ((xtInt4) 0x80000000) + +#define xtReal4 float +#define xtReal8 double + +#ifndef u_int +#define u_int unsigned int /* Assumed at least 4 bytes long! */ +#define u_long unsigned long /* Assumed at least 4 bytes long! */ +#endif +#define llong long long /* Assumed at least 8 bytes long! */ +#define u_llong unsigned long long /* Assumed at least 8 bytes long! */ + +#define c_char const char + +#ifndef NULL +#define NULL 0 +#endif + +#define xtPublic + +#define xtBool int +#ifndef TRUE +#define TRUE 1 +#endif +#ifndef FALSE +#define FALSE 0 +#endif + +/* Additional return codes: */ +#define XT_MAYBE 2 +#define XT_ERR -1 +#define XT_NEW -2 +#define XT_RETRY -3 +#define XT_REREAD -4 + +#ifdef OK +#undef OK +#endif +#define OK TRUE + +#ifdef FAILED +#undef FAILED +#endif +#define FAILED FALSE + +typedef xtWord1 XTDiskValue1[1]; +typedef xtWord1 XTDiskValue2[2]; +typedef xtWord1 XTDiskValue3[3]; +typedef xtWord1 XTDiskValue4[4]; +typedef xtWord1 XTDiskValue6[6]; +typedef xtWord1 XTDiskValue8[8]; + +#ifdef DEBUG +#define XT_VAR_LENGTH 100 +#else +#define XT_VAR_LENGTH 1 +#endif + +typedef struct XTPathStr { + char ps_path[XT_VAR_LENGTH]; +} *XTPathStrPtr; + +#define XT_UNUSED(x) x __attribute__((__unused__)) + +/* ---------------------------------------------------------------------- + * MAIN CONSTANTS + */ + +/* + * Define if there should only be one database per server instance: + */ +#define XT_USE_GLOBAL_DB + +/* + * The rollover size is the write limit of a log file. + * After this size is reached, a thread will start a + * new log. + * + * However, logs can grow much larger than this size. + * The reason is, a transaction single transaction + * may not span more than one data log file. + * + * This means the log rollover size is actually a + * minimum size. + */ + +#ifdef DEBUG +//#define XT_USE_GLOBAL_DEBUG_SIZES +#endif + +/* + * I believe the MySQL limit is 16. This limit is currently only used for + * BLOB streaming. + */ +#define XT_MAX_COLS_PER_INDEX 32 + +/* + * The maximum number of tables that can be created in a PBXT + * database. The amount is based on the fact that XT creates + * about 5 files per table in the database, and also + * uses directory listing to find tables. + */ +#define XT_MAX_TABLES 10000 + +/* + * When the amount of garbage in the file is greater than the + * garbage threshold, then compactor is activated. + */ +#define XT_GARBAGE_THRESHOLD ((double) 50.0) + +/* A record that does not contain blobs will be handled as a fixed + * length record if its maximum size is less than this amount, + * regardless of the size of the VARCHAR fields it contains. + */ +#define XT_TAB_MIN_VAR_REC_LENGTH 320 + +/* No record in the data handle file may exceed this size: */ +#define XT_TAB_MAX_FIX_REC_LENGTH (16 * 1024) + +/* No record in the data handle file may exceed this size, if + * AVG_ROW_LENGTH is set. + */ +#define XT_TAB_MAX_FIX_REC_LENGTH_SPEC (64 * 1024) + +/* + * Determines the page size of the indexes. The value is given + * in shifts of 1 to the left (e.g. 1 << 11 == 2048, + * 1 << 12 == 4096). + * + * PMC: Note the performance of sysbench is better with 11 + * than with 12. + * + * InnoDB uses 16K pages: + * 1 << 14 == 16384. + */ +#define XT_INDEX_PAGE_SHIFTS 14 + +/* The number of RW locks used to scatter locks on the rows + * of a table. The locks are only help for a short time during which + * the row list is scanned. + * + * For more details see [(9)]. + */ +#define XT_ROW_RWLOCKS 223 + +/* + * These are the number of row lock "slots" per table. + * Row locks are taken on UPDATE/DELETE or SELECT FOR UPDATE. + */ +#define XT_ROW_LOCK_COUNT (XT_ROW_RWLOCKS * 91) + +/* + * The size of index write buffer. Must be at least as large as the + * largest index page, plus overhead. + */ +#define XT_INDEX_WRITE_BUFFER_SIZE (1024 * 1024) + +/* This is the time in seconds that a open table in the open + * table pool must be on the free list before it + * is actually freed from the pool. + * + * This is to reduce the affect from MySQL with a very low + * table cache size, which causes tables to be openned and + * closed very rapidly. + */ +#define XT_OPEN_TABLE_FREE_TIME 30 + +#ifdef XT_USE_GLOBAL_DEBUG_SIZES +/* + * DEBUG SIZES! + * Reduce the thresholds to make things happen faster. + */ + +//#undef XT_ROW_RWLOCKS +//#define XT_ROW_RWLOCKS 2 + +//#undef XT_TAB_MIN_VAR_REC_LENGTH +//#define XT_TAB_MIN_VAR_REC_LENGTH 20 + +//#undef XT_ROW_LOCK_COUNT +//#define XT_ROW_LOCK_COUNT (XT_ROW_RWLOCKS * 2) + +//#undef XT_INDEX_PAGE_SHIFTS +//#define XT_INDEX_PAGE_SHIFTS 12 + +//#undef XT_INDEX_WRITE_BUFFER_SIZE +//#define XT_INDEX_WRITE_BUFFER_SIZE (40 * 1024) + +#endif + +/* Define this in order to use memory mapped files: */ +#define XT_USE_ROW_REC_MMAP_FILES + +/* Define this in order to use direct I/O on index files: */ +/* NOTE: DO NOT ENABLE! + * {DIRECT-IO} + * It currently does not work, because of changes to the inde + * cache. + */ +//#define XT_USE_DIRECT_IO_ON_INDEX + +#ifdef XT_USE_ROW_REC_MMAP_FILES + +#define XT_SEQ_SCAN_FROM_MEMORY +#define XT_ROW_REC_FILE_PTR XTMapFilePtr +#define XT_PWRITE_RR_FILE xt_pwrite_fmap +#define XT_PREAD_RR_FILE xt_pread_fmap +#define XT_FLUSH_RR_FILE xt_flush_fmap +#define XT_CLOSE_RR_FILE_NS xt_close_fmap_ns + +#else + +#define XT_ROW_REC_FILE_PTR XTOpenFilePtr +#define XT_PWRITE_RR_FILE xt_pwrite_file +#define XT_PREAD_RR_FILE xt_pread_file +#define XT_FLUSH_RR_FILE xt_flush_file +#define XT_CLOSE_RR_FILE_NS xt_close_file_ns + +#endif + +#ifdef XT_SEQ_SCAN_FROM_MEMORY +#define XT_LOCK_MEMORY_PTR(x, f, a, s, v, c) do { x = xt_lock_fmap_ptr(f, a, s, v, c); } while (0) +#define XT_UNLOCK_MEMORY_PTR(f, v) xt_unlock_fmap_ptr(f, v); +#else +#define XT_LOCK_MEMORY_PTR(x, f, a, v, c) +#define XT_UNLOCK_MEMORY_PTR(f, v) +#endif + +/* {NO-ACTION-BUG} + * Define this to implement NO ACTION correctly + * NOTE: this does not work currently because of a bug + * in MySQL + * + * The bug prevent returning of an error in external_lock() + * on statement end. In this case an assertion fails. + * + * set storage_engine = pbxt; + * DROP TABLE IF EXISTS t4,t3,t2,t1; + * CREATE TABLE t1 (s1 INT PRIMARY KEY); + * CREATE TABLE t2 (s1 INT PRIMARY KEY, FOREIGN KEY (s1) REFERENCES t1 (s1) ON DELETE NO ACTION); + * + * INSERT INTO t1 VALUES (1); + * INSERT INTO t2 VALUES (1); + * + * begin; + * INSERT INTO t1 VALUES (2); + * DELETE FROM t1 where s1 = 1; + * <-- Assertion fails here because this DELETE returns + * an error from external_lock() + */ +//#define XT_IMPLEMENT_NO_ACTION + +/* ---------------------------------------------------------------------- + * GLOBAL CONSTANTS + */ + +#define XT_INDEX_PAGE_SIZE (1 << XT_INDEX_PAGE_SHIFTS) +#define XT_INDEX_PAGE_MASK (XT_INDEX_PAGE_SIZE - 1) + +/* The index file uses direct I/O. This is the minimum block. + * size that can be used when doing direct I/O. + */ +#define XT_BLOCK_SIZE_FOR_DIRECT_IO 512 + +/* + * The header is currently a fixed size, so the information must + * fit in this block! + * + * This must also be a multiple of XT_INDEX_MIN_BLOCK_SIZE + */ +#define XT_INDEX_HEAD_SIZE (XT_BLOCK_SIZE_FOR_DIRECT_IO * 8) // 4K + +#define XT_IDENTIFIER_CHAR_COUNT 64 + +#define XT_IDENTIFIER_NAME_SIZE ((XT_IDENTIFIER_CHAR_COUNT * 3) + 1) // The identifier length as UTF-8 +#define XT_TABLE_NAME_SIZE ((XT_IDENTIFIER_CHAR_COUNT * 5) + 1) // The maximum length of a file name that has been normalized + +#define XT_ADD_PTR(p, l) ((void *) ((char *) (p) + (l))) + +/* ---------------------------------------------------------------------- + * BYTE ORDER + */ + +/* + * Byte order on the disk is little endian! This is the byte order of the i386. + * Little endian byte order starts with the least significant byte. + * + * The reason for choosing this byte order for the disk is 2-fold: + * Firstly the i386 is the cheapest and fasted platform today. + * Secondly the i386, unlike RISK chips (with big endian) can address + * memory that is not aligned! + * + * Since the disk image of PrimeBase XT is not aligned, the second point + * is significant. A RISK chip needs to access it byte-wise, so we might as + * well do the byte swapping at the same time. + * + * The macros below are of 4 general types: + * + * GET/SET - Get and set 1,2,4,8 byte values (short, int, long, etc). + * Values are swapped only on big endian platforms. This makes these + * functions very efficient on little-endian platforms. + * + * COPY - Transfer data without swapping regardless of platform. This + * function is a bit more efficient on little-endian platforms + * because alignment is not an issue. + * + * MOVE - Similar to get and set, but the deals with memory instead + * of values. Since no swapping is done on little-endian platforms + * this function is identical to COPY on little-endian platforms. + * + * SWAP - Transfer and swap data regardless of the platform type. + * Aligment is not assumed. + * + * The DISK component of the macro names indicates that alignment of + * the value cannot be assumed. + * + */ +#if BYTE_ORDER == BIG_ENDIAN +/* The native order of the machine is big endian. Since the native disk + * disk order of XT is little endian, all data to and from disk + * must be swapped. + */ +#define XT_SET_DISK_1(d, s) ((d)[0] = (xtWord1) (s)) + +#define XT_SET_DISK_2(d, s) do { (d)[0] = (xtWord1) (((xtWord2) (s)) & 0xFF); (d)[1] = (xtWord1) ((((xtWord2) (s)) >> 8 ) & 0xFF); } while (0) + +#define XT_SET_DISK_3(d, s) do { (d)[0] = (xtWord1) (((xtWord4) (s)) & 0xFF); (d)[1] = (xtWord1) ((((xtWord4) (s)) >> 8 ) & 0xFF); \ + (d)[2] = (xtWord1) ((((xtWord4) (s)) >> 16) & 0xFF); } while (0) + +#define XT_SET_DISK_4(d, s) do { (d)[0] = (xtWord1) (((xtWord4) (s)) & 0xFF); (d)[1] = (xtWord1) ((((xtWord4) (s)) >> 8 ) & 0xFF); \ + (d)[2] = (xtWord1) ((((xtWord4) (s)) >> 16) & 0xFF); (d)[3] = (xtWord1) ((((xtWord4) (s)) >> 24) & 0xFF); } while (0) + +#define XT_SET_DISK_6(d, s) do { (d)[0] = (xtWord1) (((xtWord8) (s)) & 0xFF); (d)[1] = (xtWord1) ((((xtWord8) (s)) >> 8 ) & 0xFF); \ + (d)[2] = (xtWord1) ((((xtWord8) (s)) >> 16) & 0xFF); (d)[3] = (xtWord1) ((((xtWord8) (s)) >> 24) & 0xFF); \ + (d)[4] = (xtWord1) ((((xtWord8) (s)) >> 32) & 0xFF); (d)[5] = (xtWord1) ((((xtWord8) (s)) >> 40) & 0xFF); } while (0) + +#define XT_SET_DISK_8(d, s) do { (d)[0] = (xtWord1) (((xtWord8) (s)) & 0xFF); (d)[1] = (xtWord1) ((((xtWord8) (s)) >> 8 ) & 0xFF); \ + (d)[2] = (xtWord1) ((((xtWord8) (s)) >> 16) & 0xFF); (d)[3] = (xtWord1) ((((xtWord8) (s)) >> 24) & 0xFF); \ + (d)[4] = (xtWord1) ((((xtWord8) (s)) >> 32) & 0xFF); (d)[5] = (xtWord1) ((((xtWord8) (s)) >> 40) & 0xFF); \ + (d)[6] = (xtWord1) ((((xtWord8) (s)) >> 48) & 0xFF); (d)[7] = (xtWord1) ((((xtWord8) (s)) >> 56) & 0xFF); } while (0) + +#define XT_GET_DISK_1(s) ((s)[0]) + +#define XT_GET_DISK_2(s) ((xtWord2) (((xtWord2) (s)[0]) | (((xtWord2) (s)[1]) << 8))) + +#define XT_GET_DISK_3(s) ((xtWord4) (((xtWord4) (s)[0]) | (((xtWord4) (s)[1]) << 8) | (((xtWord4) (s)[2]) << 16))) + +#define XT_GET_DISK_4(s) (((xtWord4) (s)[0]) | (((xtWord4) (s)[1]) << 8 ) | \ + (((xtWord4) (s)[2]) << 16) | (((xtWord4) (s)[3]) << 24)) + +#define XT_GET_DISK_6(s) (((xtWord8) (s)[0]) | (((xtWord8) (s)[1]) << 8 ) | \ + (((xtWord8) (s)[2]) << 16) | (((xtWord8) (s)[3]) << 24) | \ + (((xtWord8) (s)[4]) << 32) | (((xtWord8) (s)[5]) << 40)) + +#define XT_GET_DISK_8(s) (((xtWord8) (s)[0]) | (((xtWord8) (s)[1]) << 8 ) | \ + (((xtWord8) (s)[2]) << 16) | (((xtWord8) (s)[3]) << 24) | \ + (((xtWord8) (s)[4]) << 32) | (((xtWord8) (s)[5]) << 40) | \ + (((xtWord8) (s)[6]) << 48) | (((xtWord8) (s)[7]) << 56)) + +/* Move will copy memory, and swap the bytes on a big endian machine. + * On a little endian machine it is the same as COPY. + */ +#define XT_MOVE_DISK_1(d, s) ((d)[0] = (s)[0]) +#define XT_MOVE_DISK_2(d, s) do { (d)[0] = (s)[1]; (d)[1] = (s)[0]; } while (0) +#define XT_MOVE_DISK_3(d, s) do { (d)[0] = (s)[2]; (d)[1] = (s)[1]; (d)[2] = (s)[0]; } while (0) +#define XT_MOVE_DISK_4(d, s) do { (d)[0] = (s)[3]; (d)[1] = (s)[2]; (d)[2] = (s)[1]; (d)[3] = (s)[0]; } while (0) +#define XT_MOVE_DISK_8(d, s) do { (d)[0] = (s)[7]; (d)[1] = (s)[6]; \ + (d)[2] = (s)[5]; (d)[3] = (s)[4]; \ + (d)[4] = (s)[3]; (d)[5] = (s)[2]; \ + (d)[6] = (s)[1]; (d)[7] = (s)[0]; } while (0) + +/* + * Copy just copies the number of bytes assuming the data is not alligned. + */ +#define XT_COPY_DISK_1(d, s) (d)[0] = s +#define XT_COPY_DISK_2(d, s) do { (d)[0] = (s)[0]; (d)[1] = (s)[1]; } while (0) +#define XT_COPY_DISK_3(d, s) do { (d)[0] = (s)[0]; (d)[1] = (s)[1]; (d)[2] = (s)[2]; } while (0) +#define XT_COPY_DISK_4(d, s) do { (d)[0] = (s)[0]; (d)[1] = (s)[1]; (d)[2] = (s)[2]; (d)[3] = (s)[3]; } while (0) +#define XT_COPY_DISK_6(d, s) memcpy(&((d)[0]), &((s)[0]), 6) +#define XT_COPY_DISK_8(d, s) memcpy(&((d)[0]), &((s)[0]), 8) +#define XT_COPY_DISK_10(d, s) memcpy(&((d)[0]), &((s)[0]), 10) + +#define XT_SET_NULL_DISK_1(d) XT_SET_DISK_1(d, 0) +#define XT_SET_NULL_DISK_2(d) do { (d)[0] = 0; (d)[1] = 0; } while (0) +#define XT_SET_NULL_DISK_4(d) do { (d)[0] = 0; (d)[1] = 0; (d)[2] = 0; (d)[3] = 0; } while (0) +#define XT_SET_NULL_DISK_6(d) do { (d)[0] = 0; (d)[1] = 0; (d)[2] = 0; (d)[3] = 0; (d)[4] = 0; (d)[5] = 0; } while (0) +#define XT_SET_NULL_DISK_8(d) do { (d)[0] = 0; (d)[1] = 0; (d)[2] = 0; (d)[3] = 0; (d)[4] = 0; (d)[5] = 0; (d)[6] = 0; (d)[7] = 0; } while (0) + +#define XT_IS_NULL_DISK_1(d) (!(XT_GET_DISK_1(d))) +#define XT_IS_NULL_DISK_4(d) (!(d)[0] && !(d)[1] && !(d)[2] && !(d)[3]) +#define XT_IS_NULL_DISK_8(d) (!(d)[0] && !(d)[1] && !(d)[2] && !(d)[3] && !(d)[4] && !(d)[5] && !(d)[6] && !(7)[3]) + +#define XT_EQ_DISK_4(d, s) ((d)[0] == (s)[0] && (d)[1] == (s)[1] && (d)[2] == (s)[2] && (d)[3] == (s)[3]) +#define XT_EQ_DISK_8(d, s) ((d)[0] == (s)[0] && (d)[1] == (s)[1] && (d)[2] == (s)[2] && (d)[3] == (s)[3] && \ + (d)[4] == (s)[4] && (d)[5] == (s)[5] && (d)[6] == (s)[6] && (d)[7] == (s)[7]) + +#define XT_IS_FF_DISK_4(d) ((d)[0] == 0xFF && (d)[1] == 0xFF && (d)[2] == 0xFF && (d)[3] == 0xFF) +#else +/* + * The native order of the machine is little endian. This means the data to + * and from disk need not be swapped. In addition to this, since + * the i386 can access non-aligned memory we are not required to + * handle the data byte-for-byte. + */ +#define XT_SET_DISK_1(d, s) ((d)[0] = (xtWord1) (s)) +#define XT_SET_DISK_2(d, s) (*((xtWord2 *) &((d)[0])) = (xtWord2) (s)) +#define XT_SET_DISK_3(d, s) do { (*((xtWord2 *) &((d)[0])) = (xtWord2) (s)); *((xtWord1 *) &((d)[2])) = (xtWord1) (((xtWord4) (s)) >> 16); } while (0) +#define XT_SET_DISK_4(d, s) (*((xtWord4 *) &((d)[0])) = (xtWord4) (s)) +#define XT_SET_DISK_6(d, s) do { *((xtWord4 *) &((d)[0])) = (xtWord4) (s); *((xtWord2 *) &((d)[4])) = (xtWord2) (((xtWord8) (s)) >> 32); } while (0) +#define XT_SET_DISK_8(d, s) (*((xtWord8 *) &((d)[0])) = (xtWord8) (s)) + +#define XT_GET_DISK_1(s) ((s)[0]) +#define XT_GET_DISK_2(s) *((xtWord2 *) &((s)[0])) +#define XT_GET_DISK_3(s) ((xtWord4) *((xtWord2 *) &((s)[0])) | (((xtWord4) *((xtWord1 *) &((s)[2]))) << 16)) +#define XT_GET_DISK_4(s) *((xtWord4 *) &((s)[0])) +#define XT_GET_DISK_6(s) ((xtWord8) *((xtWord4 *) &((s)[0])) | (((xtWord8) *((xtWord2 *) &((s)[4]))) << 32)) +#define XT_GET_DISK_8(s) *((xtWord8 *) &((s)[0])) + +#define XT_MOVE_DISK_1(d, s) ((d)[0] = (s)[0]) +#define XT_MOVE_DISK_2(d, s) XT_COPY_DISK_2(d, s) +#define XT_MOVE_DISK_3(d, s) XT_COPY_DISK_3(d, s) +#define XT_MOVE_DISK_4(d, s) XT_COPY_DISK_4(d, s) +#define XT_MOVE_DISK_8(d, s) XT_COPY_DISK_8(d, s) + +#define XT_COPY_DISK_1(d, s) (d)[0] = s +#define XT_COPY_DISK_2(d, s) (*((xtWord2 *) &((d)[0])) = (*((xtWord2 *) &((s)[0])))) +#define XT_COPY_DISK_3(d, s) do { *((xtWord2 *) &((d)[0])) = *((xtWord2 *) &((s)[0])); (d)[2] = (s)[2]; } while (0) +#define XT_COPY_DISK_4(d, s) (*((xtWord4 *) &((d)[0])) = (*((xtWord4 *) &((s)[0])))) +#define XT_COPY_DISK_6(d, s) do { *((xtWord4 *) &((d)[0])) = *((xtWord4 *) &((s)[0])); *((xtWord2 *) &((d)[4])) = *((xtWord2 *) &((s)[4])); } while (0) +#define XT_COPY_DISK_8(d, s) (*((xtWord8 *) &(d[0])) = (*((xtWord8 *) &((s)[0])))) +#define XT_COPY_DISK_10(d, s) memcpy(&((d)[0]), &((s)[0]), 10) + +#define XT_SET_NULL_DISK_1(d) XT_SET_DISK_1(d, 0) +#define XT_SET_NULL_DISK_2(d) XT_SET_DISK_2(d, 0) +#define XT_SET_NULL_DISK_3(d) XT_SET_DISK_3(d, 0) +#define XT_SET_NULL_DISK_4(d) XT_SET_DISK_4(d, 0L) +#define XT_SET_NULL_DISK_6(d) XT_SET_DISK_6(d, 0LL) +#define XT_SET_NULL_DISK_8(d) XT_SET_DISK_8(d, 0LL) + +#define XT_IS_NULL_DISK_1(d) (!(XT_GET_DISK_1(d))) +#define XT_IS_NULL_DISK_2(d) (!(XT_GET_DISK_2(d))) +#define XT_IS_NULL_DISK_3(d) (!(XT_GET_DISK_3(d))) +#define XT_IS_NULL_DISK_4(d) (!(XT_GET_DISK_4(d))) +#define XT_IS_NULL_DISK_8(d) (!(XT_GET_DISK_8(d))) + +#define XT_EQ_DISK_4(d, s) (XT_GET_DISK_4(d) == XT_GET_DISK_4(s)) +#define XT_EQ_DISK_8(d, s) (XT_GET_DISK_8(d) == XT_GET_DISK_8(s)) + +#define XT_IS_FF_DISK_4(d) (XT_GET_DISK_4(d) == 0xFFFFFFFF) +#endif + +#define XT_CMP_DISK_4(a, b) ((xtInt4) XT_GET_DISK_4(a) - (xtInt4) XT_GET_DISK_4(b)) +#define XT_CMP_DISK_8(d, s) memcmp(&((d)[0]), &((s)[0]), 8) +//#define XT_CMP_DISK_8(d, s) (XT_CMP_DISK_4((d).h_number_4, (s).h_number_4) == 0 ? XT_CMP_DISK_4((d).h_file_4, (s).h_file_4) : XT_CMP_DISK_4((d).h_number_4, (s).h_number_4)) + +#define XT_SWAP_DISK_2(d, s) do { (d)[0] = (s)[1]; (d)[1] = (s)[0]; } while (0) +#define XT_SWAP_DISK_3(d, s) do { (d)[0] = (s)[2]; (d)[1] = (s)[1]; (d)[2] = (s)[0]; } while (0) +#define XT_SWAP_DISK_4(d, s) do { (d)[0] = (s)[3]; (d)[1] = (s)[2]; (d)[2] = (s)[1]; (d)[3] = (s)[0]; } while (0) +#define XT_SWAP_DISK_8(d, s) do { (d)[0] = (s)[7]; (d)[1] = (s)[6]; (d)[2] = (s)[5]; (d)[3] = (s)[4]; \ + (d)[4] = (s)[3]; (d)[5] = (s)[2]; (d)[6] = (s)[1]; (d)[7] = (s)[0]; } while (0) + +/* ---------------------------------------------------------------------- + * GLOBAL APPLICATION TYPES & MACROS + */ + +struct XTThread; + +typedef void (*XTFreeFunc)(struct XTThread *self, void *thunk, void *item); +typedef int (*XTCompareFunc)(struct XTThread *self, register const void *thunk, register const void *a, register const void *b); + +/* Log ID and offset: */ +#define xtLogID xtWord4 +#define xtLogOffset off_t + +#define xtDatabaseID xtWord4 +#define xtTableID xtWord4 +#define xtOpSeqNo xtWord4 +#define xtXactID xtWord4 +#define xtThreadID xtWord4 + +#ifdef DEBUG +//#define XT_USE_NODE_ID_STRUCT +#endif + +#ifdef XT_USE_NODE_ID_STRUCT +typedef struct xtIndexNodeID { + xtWord4 x; +} xtIndexNodeID; +#define XT_NODE_TEMP xtWord4 xt_node_temp +#define XT_NODE_ID(a) (a).x +#define XT_RET_NODE_ID(a) *((xtIndexNodeID *) &(xt_node_temp = (a))) +#else +#define XT_NODE_TEMP +#define xtIndexNodeID xtWord4 +#define XT_NODE_ID(a) a +#define XT_RET_NODE_ID(a) ((xtIndexNodeID) (a)) +#endif + +/* Row, Record ID and Record offsets: */ +#define xtRowID xtWord4 +#define xtRecordID xtWord4 /* NOTE: Record offset == header-size + record-id * record-size! */ +#define xtRefID xtWord4 /* Must be big enough to contain a xtRowID and a xtRecordID! */ +#define xtRecOffset off_t +#define xtDiskRecordID4 XTDiskValue4 +#ifdef XT_WIN +#define xtProcID DWORD +#else +#define xtProcID pid_t +#endif + +#define XT_ROW_ID_SIZE 4 +#define XT_RECORD_ID_SIZE 4 +#define XT_REF_ID_SIZE 4 /* max(XT_ROW_ID_SIZE, XT_RECORD_ID_SIZE) */ +#define XT_RECORD_OFFS_SIZE 4 +#define XT_RECORD_REF_SIZE (XT_RECORD_ID_SIZE + XT_ROW_ID_SIZE) +#define XT_CHECKSUM4_REC(x) (x) + +#define XT_XACT_ID_SIZE 4 +#define XT_CHECKSUM4_XACT(x) (x) + +/* ---------------------------------------------------------------------- + * GLOBAL VARIABLES + */ + +extern bool pbxt_inited; +extern xtBool pbxt_ignore_case; +extern const char *pbxt_extensions[]; +extern xtBool pbxt_crash_debug; + + +/* ---------------------------------------------------------------------- + * DRIZZLE MAPPINGS VARIABLES + */ + +#ifdef DRIZZLED +/* Drizzle is stuck at this level: */ +#define MYSQL_VERSION_ID 60005 + +#define TABLE_LIST TableList +#define TABLE Table +#define THD Session +#define MYSQL_THD Session * +#define THR_THD THR_Session +#define STRUCT_TABLE class Table + +#define MYSQL_TYPE_STRING DRIZZLE_TYPE_VARCHAR +#define MYSQL_TYPE_VARCHAR DRIZZLE_TYPE_VARCHAR +#define MYSQL_TYPE_LONGLONG DRIZZLE_TYPE_LONGLONG +#define MYSQL_TYPE_BLOB DRIZZLE_TYPE_BLOB +#define MYSQL_TYPE_ENUM DRIZZLE_TYPE_ENUM +#define MYSQL_TYPE_LONG DRIZZLE_TYPE_LONG +#define MYSQL_PLUGIN_VAR_HEADER DRIZZLE_PLUGIN_VAR_HEADER +#define MYSQL_SYSVAR_STR DRIZZLE_SYSVAR_STR +#define MYSQL_SYSVAR_INT DRIZZLE_SYSVAR_INT +#define MYSQL_SYSVAR DRIZZLE_SYSVAR +#define MYSQL_STORAGE_ENGINE_PLUGIN DRIZZLE_STORAGE_ENGINE_PLUGIN +#define MYSQL_INFORMATION_SCHEMA_PLUGIN DRIZZLE_INFORMATION_SCHEMA_PLUGIN +#define memcpy_fixed memcpy +#define bfill(m, len, ch) memset(m, ch, len) + +#define mx_tmp_use_all_columns(x, y) (x)->use_all_columns(y) +#define mx_tmp_restore_column_map(x, y) (x)->restore_column_map(y) + +#define MX_TABLE_TYPES_T handler::Table_flags +#define MX_UINT8_T uint8_t +#define MX_ULONG_T uint32_t +#define MX_ULONGLONG_T uint64_t +#define MX_LONGLONG_T uint64_t +#define MX_CHARSET_INFO struct charset_info_st +#define MX_CONST_CHARSET_INFO const struct charset_info_st +#define MX_CONST const +#define my_bool bool +#define int16 int16_t +#define int32 int32_t +#define uint16 uint16_t +#define uint32 uint32_t +#define uchar unsigned char +#define longlong int64_t +#define ulonglong uint64_t + +#define HAVE_LONG_LONG + +#define my_malloc(x, y) malloc(x) +#define my_free(x, y) free(x) + +#define HA_CAN_SQL_HANDLER 0 +#define HA_CAN_INSERT_DELAYED 0 + +#define max cmax +#define min cmin + +#define NullS NULL + +#define thd_charset session_charset +#define thd_query session_query +#define thd_slave_thread session_slave_thread +#define thd_non_transactional_update session_non_transactional_update +#define thd_binlog_format session_binlog_format +#define thd_mark_transaction_to_rollback session_mark_transaction_to_rollback +#define thd_ha_data session_ha_data +#define current_thd current_session +#define thd_sql_command session_sql_command +#define thd_test_options session_test_options +#define thd_killed session_killed +#define thd_tx_isolation session_tx_isolation +#define thd_in_lock_tables session_in_lock_tables +#define thd_tablespace_op session_tablespace_op +#define thd_alloc session_alloc +#define thd_make_lex_string session_make_lex_string + +#define my_pthread_setspecific_ptr(T, V) pthread_setspecific(T, (void*) (V)) + +#define mysql_real_data_home drizzle_real_data_home + +#define mi_int4store(T,A) { uint32_t def_temp= (uint32_t) (A);\ + ((unsigned char*) (T))[3]= (unsigned char) (def_temp);\ + ((unsigned char*) (T))[2]= (unsigned char) (def_temp >> 8);\ + ((unsigned char*) (T))[1]= (unsigned char) (def_temp >> 16);\ + ((unsigned char*) (T))[0]= (unsigned char) (def_temp >> 24); } + +#define mi_uint4korr(A) ((uint32_t) (((uint32_t) (((const unsigned char*) (A))[3])) +\ + (((uint32_t) (((const unsigned char*) (A))[2])) << 8) +\ + (((uint32_t) (((const unsigned char*) (A))[1])) << 16) +\ + (((uint32_t) (((const unsigned char*) (A))[0])) << 24))) + +#else // DRIZZLED +/* The MySQL case: */ +#if MYSQL_VERSION_ID >= 60008 +#define STRUCT_TABLE struct TABLE +#else +#define STRUCT_TABLE struct st_table +#endif + +#define mx_tmp_use_all_columns dbug_tmp_use_all_columns +#define mx_tmp_restore_column_map(x, y) dbug_tmp_restore_column_map((x)->read_set, y) + +#define MX_TABLE_TYPES_T ulonglong +#define MX_UINT8_T uint8 +#define MX_ULONG_T ulong +#define MX_ULONGLONG_T ulonglong +#define MX_LONGLONG_T longlong +#define MX_CHARSET_INFO CHARSET_INFO +#define MX_CONST_CHARSET_INFO struct charset_info_st +#define MX_CONST + +#endif // DRIZZLED + +#ifndef XT_SCAN_CORE_DEFINED +#define XT_SCAN_CORE_DEFINED +xtBool xt_mm_scan_core(void); +#endif + +//#define DEBUG_LOCK_QUEUE + +#endif diff --git a/storage/pbxt/src/xt_errno.h b/storage/pbxt/src/xt_errno.h new file mode 100644 index 00000000000..4d74589efe3 --- /dev/null +++ b/storage/pbxt/src/xt_errno.h @@ -0,0 +1,129 @@ +/* Copyright (c) 2005 PrimeBase Technologies GmbH + * + * PrimeBase XT + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You 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 + * + * Author: Paul McCullagh + * + * H&G2JCtL + */ + +#define XT_NO_ERR 0 +#define XT_SYSTEM_ERROR -1 +#define XT_ERR_STACK_OVERFLOW -2 +#define XT_ASSERTION_FAILURE -3 +#define XT_SIGNAL_CAUGHT -4 +#define XT_ERR_JUMP_OVERFLOW -5 +#define XT_ERR_BAD_HANDLE -6 +#define XT_ERR_TABLE_EXISTS -7 +#define XT_ERR_NAME_TOO_LONG -8 +#define XT_ERR_TABLE_NOT_FOUND -9 +#define XT_ERR_SESSION_NOT_FOUND -10 +#define XT_ERR_BAD_ADDRESS -11 +#define XT_ERR_UNKNOWN_SERVICE -12 +#define XT_ERR_UNKNOWN_HOST -13 +#define XT_ERR_TOKEN_EXPECTED -14 +#define XT_ERR_PROPERTY_REQUIRED -15 +#define XT_ERR_BAD_XACTION -16 +#define XT_ERR_INVALID_SLOT -17 +#define XT_ERR_DEADLOCK -18 +#define XT_ERR_CANNOT_CHANGE_DB -19 +#define XT_ERR_ILLEGAL_CHAR -20 +#define XT_ERR_UNTERMINATED_STRING -21 +#define XT_ERR_SYNTAX -22 +#define XT_ERR_ILLEGAL_INSTRUCTION -23 +#define XT_ERR_OUT_OF_BOUNDS -24 +#define XT_ERR_STACK_UNDERFLOW -25 +#define XT_ERR_TYPE_MISMATCH -26 +#define XT_ERR_ILLEGAL_TYPE -27 +#define XT_ERR_ID_TOO_LONG -28 +#define XT_ERR_TYPE_OVERFLOW -29 +#define XT_ERR_TABLE_IN_USE -30 +#define XT_ERR_NO_DATABASE_IN_USE -31 +#define XT_ERR_CANNOT_RESOLVE_TYPE -32 +#define XT_ERR_BAD_INDEX_DESC -33 +#define XT_ERR_WRONG_NO_OF_VALUES -34 +#define XT_ERR_CANNOT_OUTPUT_VALUE -35 +#define XT_ERR_COLUMN_NOT_FOUND -36 +#define XT_ERR_NOT_IMPLEMENTED -37 +#define XT_ERR_UNEXPECTED_EOS -38 +#define XT_ERR_BAD_TOKEN -39 +#define XT_ERR_RES_STACK_OVERFLOW -40 +#define XT_ERR_BAD_INDEX_TYPE -41 +#define XT_ERR_INDEX_EXISTS -42 +#define XT_ERR_INDEX_STRUC_EXISTS -43 +#define XT_ERR_INDEX_NOT_FOUND -44 +#define XT_ERR_INDEX_CORRUPT -45 +#define XT_ERR_DUPLICATE_KEY -46 +#define XT_ERR_TYPE_NOT_SUPPORTED -47 +#define XT_ERR_BAD_TABLE_VERSION -48 +#define XT_ERR_BAD_RECORD_FORMAT -49 +#define XT_ERR_BAD_EXT_RECORD -50 +#define XT_ERR_RECORD_CHANGED -51 // Record has already been updated by some other transaction +#define XT_ERR_XLOG_WAS_CORRUPTED -52 +#define XT_ERR_NO_DICTIONARY -53 +#define XT_ERR_TOO_MANY_TABLES -54 // Maximum number of table exceeded. +#define XT_ERR_KEY_TOO_LARGE -55 // Maximum size of an index key exceeded +#define XT_ERR_MULTIPLE_DATABASES -56 +#define XT_ERR_NO_TRANSACTION -57 +#define XT_ERR_A_EXPECTED_NOT_B -58 +#define XT_ERR_NO_MATCHING_INDEX -59 +#define XT_ERR_TABLE_LOCKED -60 +#define XT_ERR_NO_REFERENCED_ROW -61 +#define XT_ERR_BAD_DICTIONARY -62 +#define XT_ERR_LOADING_MYSQL_DIC -63 +#define XT_ERR_ROW_IS_REFERENCED -64 +#define XT_ERR_COLUMN_IS_NOT_NULL -65 +#define XT_ERR_INCORRECT_NO_OF_COLS -66 +#define XT_ERR_FK_ON_TEMP_TABLE -67 +#define XT_ERR_REF_TABLE_NOT_FOUND -68 +#define XT_ERR_REF_TYPE_WRONG -69 +#define XT_ERR_DUPLICATE_FKEY -70 +#define XT_ERR_INDEX_FILE_TO_LARGE -71 +#define XT_ERR_UPGRADE_TABLE -72 +#define XT_ERR_INDEX_NEW_VERSION -73 +#define XT_ERR_LOCK_TIMEOUT -74 +#define XT_ERR_CONVERSION -75 +#define XT_ERR_NO_ROWS -76 +#define XT_ERR_MYSQL_ERROR -77 +#define XT_ERR_DATA_LOG_NOT_FOUND -78 +#define XT_ERR_LOG_MAX_EXCEEDED -79 +#define XT_ERR_MAX_ROW_COUNT -80 +#define XT_ERR_FILE_TOO_LONG -81 +#define XT_ERR_BAD_IND_BLOCK_SIZE -82 +#define XT_ERR_INDEX_CORRUPTED -83 +#define XT_ERR_NO_INDEX_CACHE -84 +#define XT_ERR_INDEX_LOG_CORRUPT -85 +#define XT_ERR_TOO_MANY_THREADS -86 +#define XT_ERR_TOO_MANY_WAITERS -87 +#define XT_ERR_INDEX_OLD_VERSION -88 +#define XT_ERR_PBXT_TABLE_EXISTS -89 +#define XT_ERR_SERVER_RUNNING -90 +#define XT_ERR_INDEX_MISSING -91 +#define XT_ERR_RECORD_DELETED -92 +#define XT_ERR_NEW_TYPE_OF_XLOG -93 +#define XT_ERR_NO_BEFORE_IMAGE -94 +#define XT_ERR_FK_REF_TEMP_TABLE -95 + +#ifdef XT_WIN +#define XT_ENOMEM ERROR_NOT_ENOUGH_MEMORY +#define XT_EAGAIN ERROR_RETRY +#define XT_EBUSY ERROR_BUSY +#else +#define XT_ENOMEM ENOMEM +#define XT_EAGAIN EAGAIN +#define XT_EBUSY EBUSY +#endif diff --git a/storage/xtradb/CMakeLists.txt b/storage/xtradb/CMakeLists.txt new file mode 100644 index 00000000000..be4206e0076 --- /dev/null +++ b/storage/xtradb/CMakeLists.txt @@ -0,0 +1,97 @@ +# Copyright (C) 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +SET(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -DSAFEMALLOC -DSAFE_MUTEX") +SET(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -DSAFEMALLOC -DSAFE_MUTEX") +ADD_DEFINITIONS(-D_WIN32 -D_LIB) + +# Bug 19424 - InnoDB: Possibly a memory overrun of the buffer being freed (64-bit Visual C) +# Removing Win64 compiler optimizations for all innodb/mem/* files. +IF(CMAKE_GENERATOR MATCHES "Visual Studio" AND CMAKE_SIZEOF_VOID_P MATCHES 8) + SET_SOURCE_FILES_PROPERTIES(${CMAKE_SOURCE_DIR}/storage/xtradb/mem/mem0mem.c + ${CMAKE_SOURCE_DIR}/storage/xtradb/mem/mem0pool.c + PROPERTIES COMPILE_FLAGS -Od) +ENDIF(CMAKE_GENERATOR MATCHES "Visual Studio" AND CMAKE_SIZEOF_VOID_P MATCHES 8) + +INCLUDE_DIRECTORIES(${CMAKE_SOURCE_DIR}/include ${CMAKE_SOURCE_DIR}/zlib + ${CMAKE_SOURCE_DIR}/storage/xtradb/include + ${CMAKE_SOURCE_DIR}/storage/xtradb/handler + ${CMAKE_SOURCE_DIR}/sql + ${CMAKE_SOURCE_DIR}/regex + ${CMAKE_SOURCE_DIR}/extra/yassl/include) + +SET(INNOBASE_SOURCES btr/btr0btr.c btr/btr0cur.c btr/btr0pcur.c btr/btr0sea.c + buf/buf0buddy.c buf/buf0buf.c buf/buf0flu.c buf/buf0lru.c buf/buf0rea.c + data/data0data.c data/data0type.c + dict/dict0boot.c dict/dict0crea.c dict/dict0dict.c dict/dict0load.c dict/dict0mem.c + dyn/dyn0dyn.c + eval/eval0eval.c eval/eval0proc.c + fil/fil0fil.c + fsp/fsp0fsp.c + fut/fut0fut.c fut/fut0lst.c + ha/ha0ha.c ha/hash0hash.c ha/ha0storage.c + ibuf/ibuf0ibuf.c + pars/lexyy.c pars/pars0grm.c pars/pars0opt.c pars/pars0pars.c pars/pars0sym.c + lock/lock0lock.c lock/lock0iter.c + log/log0log.c log/log0recv.c + mach/mach0data.c + mem/mem0mem.c mem/mem0pool.c + mtr/mtr0log.c mtr/mtr0mtr.c + os/os0file.c os/os0proc.c os/os0sync.c os/os0thread.c + page/page0cur.c page/page0page.c page/page0zip.c + que/que0que.c + handler/ha_innodb.cc handler/handler0alter.cc handler/i_s.cc handler/mysql_addons.cc + read/read0read.c + rem/rem0cmp.c rem/rem0rec.c + row/row0ext.c row/row0ins.c row/row0merge.c row/row0mysql.c + row/row0purge.c row/row0row.c row/row0sel.c row/row0uins.c + row/row0umod.c row/row0undo.c row/row0upd.c row/row0vers.c + srv/srv0que.c srv/srv0srv.c srv/srv0start.c + sync/sync0arr.c sync/sync0rw.c sync/sync0sync.c + thr/thr0loc.c + trx/trx0i_s.c trx/trx0purge.c trx/trx0rec.c trx/trx0roll.c trx/trx0rseg.c + trx/trx0sys.c trx/trx0trx.c trx/trx0undo.c + usr/usr0sess.c + ut/ut0byte.c ut/ut0dbg.c ut/ut0mem.c ut/ut0rnd.c ut/ut0ut.c ut/ut0vec.c ut/ut0list.c ut/ut0wqueue.c) + +IF(NOT SOURCE_SUBLIBS) + ADD_LIBRARY(innobase ${INNOBASE_SOURCES}) + ADD_DEPENDENCIES(innobase GenError) + SET_TARGET_PROPERTIES(innobase PROPERTIES COMPILE_FLAGS "-DMYSQL_SERVER") + + IF(INNODB_DYNAMIC_PLUGIN) + # The dynamic plugin requires CMake 2.6.0 or later. Otherwise, the /DELAYLOAD property + # will not be set + CMAKE_MINIMUM_REQUIRED(VERSION 2.6.0 FATAL_ERROR) + ADD_LIBRARY(ha_innodb SHARED ${INNOBASE_SOURCES} ha_innodb.def handler/win_delay_loader.cc) + ADD_DEPENDENCIES(ha_innodb GenError mysqld) + # If build type is not specified as Release, default to Debug + # This is a workaround to a problem in CMake 2.6, which does not + # set the path of mysqld.lib correctly + IF(CMAKE_BUILD_TYPE MATCHES Release) + SET(CMAKE_BUILD_TYPE "Release") + ELSE(CMAKE_BUILD_TYPE MATCHES Release) + SET(CMAKE_BUILD_TYPE "Debug") + ENDIF(CMAKE_BUILD_TYPE MATCHES Release) + TARGET_LINK_LIBRARIES(ha_innodb strings zlib) + TARGET_LINK_LIBRARIES(ha_innodb ${CMAKE_SOURCE_DIR}/sql/${CMAKE_BUILD_TYPE}/mysqld.lib) + SET_TARGET_PROPERTIES(ha_innodb PROPERTIES OUTPUT_NAME ha_innodb) + SET_TARGET_PROPERTIES(ha_innodb PROPERTIES LINK_FLAGS "/MAP /MAPINFO:EXPORTS") + SET_TARGET_PROPERTIES(ha_innodb PROPERTIES LINK_FLAGS "/ENTRY:\"_DllMainCRTStartup@12\"") + SET_TARGET_PROPERTIES(ha_innodb PROPERTIES COMPILE_FLAGS "-DMYSQL_DYNAMIC_PLUGIN") + SET_TARGET_PROPERTIES(ha_innodb PROPERTIES LINK_FLAGS "/DELAYLOAD:mysqld.exe") + ENDIF(INNODB_DYNAMIC_PLUGIN) + +ENDIF(NOT SOURCE_SUBLIBS) diff --git a/storage/xtradb/COPYING.Google b/storage/xtradb/COPYING.Google new file mode 100644 index 00000000000..5ade2b0e381 --- /dev/null +++ b/storage/xtradb/COPYING.Google @@ -0,0 +1,30 @@ +Portions of this software contain modifications contributed by Google, Inc. +These contributions are used with the following license: + +Copyright (c) 2008, Google Inc. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials + provided with the distribution. + * Neither the name of the Google Inc. nor the names of its + contributors may be used to endorse or promote products + derived from this software without specific prior written + permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/storage/xtradb/ChangeLog b/storage/xtradb/ChangeLog new file mode 100644 index 00000000000..f662b02eea5 --- /dev/null +++ b/storage/xtradb/ChangeLog @@ -0,0 +1,775 @@ +2009-03-05 The InnoDB Team + + * handler/ha_innodb.cc, mysql-test/innodb-autoinc.result, + mysql-test/innodb-autoinc.test: + Fix Bug#43203 Overflow from auto incrementing causes server segv + +2009-02-25 The InnoDB Team + + * handler/ha_innodb.cc, mysql-test/innodb-autoinc.result, + mysql-test/innodb-autoinc.test: + Fix Bug#42714 AUTO_INCREMENT errors in 5.1.31 + +2009-02-23 The InnoDB Team + + * btr/btr0cur.c: + Fix Bug#43043 Crash on BLOB delete operation + +2009-02-20 The InnoDB Team + + * handler/ha_innodb.cc: + Make innodb_use_sys_malloc=ON the default. + +2009-02-20 The InnoDB Team + + * handler/ha_innodb.cc, mysql-test/innodb-autoinc.result, + mysql-test/innodb-autoinc.test: + Fix Bug#42400 InnoDB autoinc code can't handle floating-point columns + +2009-02-18 The InnoDB Team + + * include/ut0mem.h, os/os0proc.c, ut/ut0mem.c: + Protect ut_total_allocated_memory with ut_list_mutex in + os_mem_alloc_large() and os_mem_free_large(). The lack of this mutex + protection could cause an assertion failure during fast index + creation. Also, add UNIV_MEM_ALLOC and UNIV_MEM_FREE instrumentation + to os_mem_alloc_large() and os_mem_free_large(), so that Valgrind can + detect more errors. + +2009-02-11 The InnoDB Team + + * handler/ha_innodb.cc: + Make innodb_thread_concurrency=0 the default. The old default value + was 8. A non-zero setting may be useful when InnoDB is showing severe + scalability problems under multiple concurrent connections. + +2009-02-10 The InnoDB Team + + * handler/ha_innodb.cc, handler/ha_innodb.h: + Fix Bug#41676 Table names are case insensitive in locking + +2009-02-10 The InnoDB Team + + * mem/mem0dbg.c, mem/mem0mem.c, mem/mem0pool.c: + When innodb_use_sys_malloc is set, ignore + innodb_additional_mem_pool_size, because nothing will be allocated + from mem_comm_pool. + +2009-02-10 The InnoDB Team + + * ut/ut0mem.c: + Map ut_malloc_low(), ut_realloc(), and ut_free() directly to malloc(), + realloc(), and free() when innodb_use_sys_malloc is set. As a side + effect, ut_total_allocated_memory ("Total memory allocated" in the + "BUFFER POOL AND MEMORY" section of SHOW ENGINE INNODB STATUS) will + exclude any memory allocated by these functions when + innodb_use_sys_malloc is set. + +2009-02-10 The InnoDB Team + + * btr/btr0cur.c, btr/btr0sea.c, buf/buf0buf.c, handler/ha_innodb.cc, + include/buf0buf.ic, include/os0sync.h, include/srv0srv.h, + include/sync0rw.h, include/sync0rw.ic, include/sync0sync.h, + include/sync0sync.ic, include/univ.i, row/row0sel.c, srv/srv0srv.c, + srv/srv0start.c, sync/sync0arr.c, sync/sync0rw.c, sync/sync0sync.c: + On those platforms that support it, implement the synchronization + primitives of InnoDB mutexes and read/write locks with GCC atomic + builtins instead of Pthreads mutexes and InnoDB mutexes. These changes + are based on a patch supplied by Mark Callaghan of Google under a BSD + license. + +2009-01-30 The InnoDB Team + + * btr/btr0cur.c, btr/btr0sea.c, buf/buf0buf.c, handler/ha_innodb.cc, + include/btr0sea.h, include/buf0buf.h, include/sync0sync.h, + sync/sync0sync.c: + Make the configuration parameter innodb_adaptive_hash_index dynamic, + so that it can be changed at runtime. + +2009-01-29 The InnoDB Team + + * handler/ha_innodb.cc, ibuf/ibuf0ibuf.c, include/ibuf0ibuf.h, + include/ibuf0ibuf.ic: + Implement the settable global variable innodb_change_buffering, + with the allowed values 'none' and 'inserts'. The default value + 'inserts' enables the buffering of inserts to non-unique secondary + index trees when the B-tree leaf page is not in the buffer pool. + +2009-01-27 The InnoDB Team + + * buf/buf0lru.c: + Fix a race condition in buf_LRU_invalidate_tablespace(): The + compressed page size (zip_size) was read while the block descriptor + was no longer protected by a mutex. This could lead to corruption + when a table is dropped on a busy system that contains compressed + tables. + +2009-01-26 The InnoDB Team + + * btr/btr0sea.c, buf/buf0buf.c, include/buf0buf.h, include/buf0buf.ic, + include/mtr0log.ic, include/row0upd.ic, mtr/mtr0mtr.c: + Implement buf_block_align() with pointer arithmetics, as it is in the + built-in InnoDB distributed with MySQL. Do not acquire the buffer pool + mutex before buf_block_align(). This removes a scalability bottleneck + in the adaptive hash index lookup. In CHECK TABLE, check that + buf_pool->page_hash is consistent with buf_block_align(). + +2009-01-23 The InnoDB Team + + * btr/btr0sea.c: + Fix Bug#42279 Race condition in btr_search_drop_page_hash_when_freed() + +2009-01-23 The InnoDB Team + + * buf/buf0buf.c, include/buf0buf.h: + Remove the unused mode BUF_GET_NOWAIT of buf_page_get_gen() + +2009-01-20 The InnoDB Team + + * include/rem0rec.h, include/rem0rec.ic: + Fix Bug#41571 MySQL segfaults after innodb recovery + +2009-01-20 The InnoDB Team + + * lock/lock0lock.c: + Fix Bug#42152 Race condition in lock_is_table_exclusive() + +2009-01-14 The InnoDB Team + + * include/trx0roll.h, trx/trx0roll.c, trx/trx0trx.c: + Fix Bug#38187 Error 153 when creating savepoints + +2009-01-14 The InnoDB Team + + * dict/dict0load.c: + Fix Bug#42075 dict_load_indexes failure in dict_load_table will + corrupt the dictionary cache + +2009-01-13 The InnoDB Team + + * buf/buf0buddy.c, dict/dict0dict.c, dict/dict0mem.c, fil/fil0fil.c, + ha/ha0storage.c, handler/ha_innodb.cc, handler/win_delay_loader.cc, + include/buf0buf.ic, include/dict0dict.ic, include/hash0hash.h, + thr/thr0loc.c, trx/trx0i_s.c: + Add the parameter ASSERTION to HASH_SEARCH() macro, and use it for + light validation of the traversed items in hash table lookups when + UNIV_DEBUG is enabled. + +2009-01-09 The InnoDB Team + + * buf/buf0flu.c, include/buf0flu.h, include/buf0flu.ic: + Remove unused code from the functions + buf_flush_insert_into_flush_list() and + buf_flush_insert_sorted_into_flush_list(). + +2009-01-09 The InnoDB Team + + * buf/buf0flu.c: + Simplify the functions buf_flush_try_page() and buf_flush_batch(). Add + debug assertions and an explanation to buf_flush_write_block_low(). + +2009-01-07 The InnoDB Team + + * row/row0merge.c: + Fix a bug in recovery when dropping temporary indexes. + +2009-01-07 The InnoDB Team + + * handler/ha_innodb.cc, handler/ha_innodb.h, handler/handler0alter.cc: + Fix Bug#41680 calls to trx_allocate_for_mysql are not consistent + +2009-01-07 The InnoDB Team + + * mysql-test/innodb_bug41904.result, mysql-test/innodb_bug41904.test, + row/row0merge.c: + Fix Bug#41904 create unique index problem + +2009-01-02 The InnoDB Team + + * handler/ha_innodb.cc, include/srv0srv.h, mem/mem0pool.c, + mysql-test/innodb-use-sys-malloc-master.opt, + mysql-test/innodb-use-sys-malloc.result, + mysql-test/innodb-use-sys-malloc.test, srv/srv0srv.c, srv/srv0start.c: + Implement the configuration parameter innodb_use_sys_malloc (false by + default), for disabling InnoDB's internal memory allocator and using + system malloc/free instead. The "BUFFER POOL AND MEMORY" section of + SHOW ENGINE INNODB STATUS will report "in additional pool allocated + allocated 0" when innodb_use_sys_malloc is set. + +2008-12-30 The InnoDB Team + + * btr/btr0btr.c: + When setting the PAGE_LEVEL of a compressed B-tree page from or to 0, + compress the page at the same time. This is necessary, because the + column information stored on the compressed page will differ between + leaf and non-leaf pages. Leaf pages are identified by PAGE_LEVEL=0. + This bug can make InnoDB crash when all rows of a compressed table are + deleted. + +2008-12-17 The InnoDB Team + + * include/row0sel.h, include/row0upd.h, pars/pars0pars.c, + row/row0mysql.c, row/row0sel.c, row/row0upd.c: + Remove update-in-place select from the internal SQL interpreter. It + was only used for updating the InnoDB internal data dictionary when + renaming or dropping tables. It could have caused deadlocks when + acquiring latches on insert buffer bitmap pages. + +2008-12-17 The InnoDB Team + + * btr/btr0sea.c, buf/buf0buf.c, buf/buf0lru.c, ha/ha0ha.c, + ha/hash0hash.c, include/buf0buf.h, include/ha0ha.h, include/ha0ha.ic, + include/hash0hash.h, include/univ.i: + Introduce the preprocessor symbol UNIV_AHI_DEBUG for enabling adaptive + hash index debugging independently of UNIV_DEBUG. + +2008-12-16 The InnoDB Team + + * btr/btr0cur.c: + Do not update the free bits in the insert buffer bitmap when inserting + or deleting from the insert buffer B-tree. Assert that records in the + insert buffer B-tree are never updated. + +2008-12-12 The InnoDB Team + + * buf/buf0buf.c, fil/fil0fil.c, fsp/fsp0fsp.c, ibuf/ibuf0ibuf.c, + include/fil0fil.h, include/ibuf0ibuf.h, include/ibuf0ibuf.ic, + include/ibuf0types.h: + Clean up the insert buffer subsystem so that only one insert + buffer B-tree exists. + Originally, there were provisions in InnoDB for multiple insert + buffer B-trees, apparently one for each tablespace. + When Heikki Tuuri implemented multiple InnoDB tablespaces in + MySQL/InnoDB 4.1, he made the insert buffer live only in the + system tablespace (space 0) but left the provisions in the code. + +2008-12-11 The InnoDB Team + + * include/srv0srv.h, os/os0proc.c, srv/srv0srv.c: + Fix the issue that the InnoDB plugin fails if innodb_buffer_pool_size + is defined bigger than 4096M on 64-bit Windows. This bug should not + have affected other 64-bit systems. + +2008-12-09 The InnoDB Team + + * handler/ha_innodb.cc: + Fix Bug#40386 Not flushing query cache after truncate. + +2008-12-09 The InnoDB Team + + * handler/ha_innodb.cc, srv/srv0srv.c, trx/trx0trx.c: + Fix Bug#40760 "set global innodb_thread_concurrency = 0;" is not safe + +2008-12-04 The InnoDB Team + + * handler/ha_innodb.cc, handler/mysql_addons.cc, + include/mysql_addons.h, trx/trx0i_s.c, win-plugin/win-plugin.diff: + Remove dependencies to MySQL internals (defining MYSQL_SERVER). + +2008-12-02 The InnoDB Team + + * page/page0cur.c: + When allocating space for a record from the free list of previously + purged records, zero out the DB_TRX_ID and DB_ROLL_PTR of the purged + record if the new record would not overwrite these fields. This fixes + a harmless content mismatch reported by page_zip_validate(). + +2008-12-02 The InnoDB Team + + * row/row0merge.c: + Replace the WHILE 1 with WHILE 1=1 in the SQL procedure, so that the + loop will actually be entered and temporary indexes be dropped during + crash recovery. + +2008-12-01 The InnoDB Team + + InnoDB Plugin 1.0.2 released + +2008-10-31 The InnoDB Team + + * dict/dict0mem.c, include/dict0mem.h, include/lock0lock.h, + include/row0mysql.h, include/trx0trx.h, include/univ.i, + include/ut0vec.h, include/ut0vec.ic, lock/lock0lock.c, + row/row0mysql.c, trx/trx0trx.c: + Fix Bug#26316 Triggers create duplicate entries on auto-increment + columns + +2008-10-30 The InnoDB Team + + * handler/ha_innodb.cc, handler/handler0vars.h, + handler/win_delay_loader.cc, mysql-test/innodb_bug40360.result, + mysql-test/innodb_bug40360.test: + Fix Bug#40360 Binlog related errors with binlog off + +2008-10-29 The InnoDB Team + + * include/data0type.ic: + Fix Bug#40369 dtype_get_sql_null_size() returns 0 or 1, not the size + +2008-10-29 The InnoDB Team + + * handler/ha_innodb.cc, include/srv0srv.h, srv/srv0srv.c: + Fix Bug#38189 innodb_stats_on_metadata missing + +2008-10-28 The InnoDB Team + + * CMakeLists.txt, ha_innodb.def, handler/ha_innodb.cc, + handler/handler0alter.cc, handler/handler0vars.h, handler/i_s.cc, + handler/win_delay_loader.cc, win-plugin/*: + Implemented the delayloading of externals for the plugin on Windows. + This makes it possible to build a dynamic plugin (ha_innodb.dll) on + Windows. + +2008-10-27 The InnoDB Team + + * CMakeLists.txt: + Fix Bug#19424 InnoDB: Possibly a memory overrun of the buffer being + freed (64-bit Visual C) + +2008-10-23 The InnoDB Team + + * ibuf/ibuf0ibuf.c: + ibuf_delete_rec(): When the cursor to the insert buffer record + cannot be restored, do not complain if the tablespace does not + exist, because the insert buffer record may have been discarded by + some other thread. This bug has existed in MySQL/InnoDB since + version 4.1, when innodb_file_per_table was implemented. + This may fix Bug#27276 InnoDB Error: ibuf cursor restoration fails. + +2008-10-22 The InnoDB Team + + * dict/dict0dict.c, dict/dict0mem.c, handler/ha_innodb.cc, + handler/ha_innodb.h, include/dict0dict.h, include/dict0mem.h, + row/row0mysql.c: + Fix Bug#39830 Table autoinc value not updated on first insert + Fix Bug#35498 Cannot get table test/table1 auto-inccounter value in + ::info + Fix Bug#36411 "Failed to read auto-increment value from storage + engine" in 5.1.24 auto-inc + +2008-10-22 The InnoDB Team + + * handler/ha_innodb.cc, include/row0mysql.h, row/row0mysql.c: + Fix Bug#40224 New AUTOINC changes mask reporting of deadlock/timeout + errors + +2008-10-16 The InnoDB Team + + * dict/dict0dict.c, mysql-test/innodb-index.result, + mysql-test/innodb-index.test: + Skip the undo log size check when creating REDUNDANT and COMPACT + tables. In ROW_FORMAT=DYNAMIC and ROW_FORMAT=COMPRESSED, column + prefix indexes require that prefixes of externally stored columns + be written to the undo log. This may make the undo log record + bigger than the record on the B-tree page. The maximum size of an + undo log record is the page size. That must be checked for, in + dict_index_add_to_cache(). However, this restriction must not + be enforced on REDUNDANT or COMPACT tables. + +2008-10-15 The InnoDB Team + + * btr/btr0cur.c, include/btr0cur.h, row/row0ext.c, row/row0sel.c, + row/row0upd.c: + When the server crashes while freeing an externally stored column + of a compressed table, the BTR_EXTERN_LEN field in the BLOB + pointer will be written as 0. Tolerate this in the functions that + deal with externally stored columns. This fixes problems after + crash recovery, in the rollback of incomplete transactions, and in + the purge of delete-marked records. + +2008-10-15 The InnoDB Team + + * btr/btr0btr.c, include/page0zip.h, page/page0zip.c, include/univ.i: + When a B-tree node of a compressed table is split or merged, the + compression may fail. In this case, the entire compressed page + will be copied and the excess records will be deleted. However, + page_zip_copy(), now renamed to page_zip_copy_recs(), copied too + many fields in the page header, overwriting PAGE_BTR_SEG_LEAF and + PAGE_BTR_SEG_TOP when splitting the B-tree root. This caused + corruption of compressed tables. Furthermore, the lock table and + the adaptive hash index would be corrupted, because we forgot to + update them when invoking page_zip_copy_recs(). + + Introduce the symbol UNIV_ZIP_DEBUG for triggering the copying of + compressed pages more often, for debugging purposes. + +2008-10-10 The InnoDB Team + + * handler/handler0alter.cc, include/row0merge.h, row/row0merge.c, + row/row0mysql.c: + Fix some locking issues, mainly in fast index creation. The + InnoDB data dictionary cache should be latched whenever a + transaction is holding locks on any data dictionary tables. + Otherwise, lock waits or deadlocks could occur. Furthermore, the + data dictionary transaction must be committed (and the locks + released) before the data dictionary latch is released. + + ha_innobase::add_index(): Lock the data dictionary before renaming + or dropping the created indexes, because neither operation will + commit the data dictionary transaction. + + ha_innobase::final_drop_index(): Commit the transactions before + unlocking the data dictionary. + +2008-10-09 The InnoDB Team + + * buf/buf0lru.c: + Fix Bug#39939 DROP TABLE/DISCARD TABLESPACE takes long time in + buf_LRU_invalidate_tablespace() + +2008-10-08 The InnoDB Team + + * dict/dict0crea.c, trx/trx0roll.c, include/row0mysql.h, + row/row0merge.c, row/row0mysql.c: + When dropping a table, hold the data dictionary latch until the + transaction has been committed. The data dictionary latch is + supposed to prevent lock waits and deadlocks in the data + dictionary tables. Due to this bug, DROP TABLE could cause a + deadlock or hang. Note that because of Bug#33650 and Bug#39833, + MySQL may also drop a (temporary) table when executing CREATE INDEX + or ALTER TABLE ... ADD INDEX. + +2008-10-04 The InnoDB Team + + * handler/ha_innodb.cc, mysql-test/innodb_bug39438-master.opt, + mysql-test/innodb_bug39438.result, mysql-test/innodb_bug39438.test: + Fix Bug#39438 Testcase for Bug#39436 crashes on 5.1 in + fil_space_get_latch + +2008-10-04 The InnoDB Team + + * include/lock0lock.h, lock/lock0lock.c, + mysql-test/innodb_bug38231.result, mysql-test/innodb_bug38231.test, + row/row0mysql.c: + Fix Bug#38231 Innodb crash in lock_reset_all_on_table() on TRUNCATE + + LOCK / UNLOCK + +2008-10-04 The InnoDB Team + + * handler/ha_innodb.cc: + Fix Bug#35498 Cannot get table test/table1 auto-inccounter value in + ::info + +2008-10-04 The InnoDB Team + + * handler/ha_innodb.cc, handler/ha_innodb.h: + Fix Bug#37788 InnoDB Plugin: AUTO_INCREMENT wrong for compressed + tables + +2008-10-04 The InnoDB Team + + * dict/dict0dict.c, handler/ha_innodb.cc, handler/ha_innodb.h, + include/dict0dict.h, include/dict0mem.h, row/row0mysql.c: + Fix Bug#39830 Table autoinc value not updated on first insert + +2008-10-03 The InnoDB Team + + * mysql-test/innodb-index.test, mysql-test/innodb-index.result, + mysql-test/innodb-timeout.test, mysql-test/innodb-timeout.result, + srv/srv0srv.c, include/srv0srv.h, handler/ha_innodb.cc, + include/ha_prototypes.h: + Fix Bug#36285 innodb_lock_wait_timeout is not dynamic, not per session + +2008-09-19 The InnoDB Team + + * os/os0proc.c: + Fix a memory leak on Windows. The memory leak was due to wrong + parameters passed into VirtualFree() call. As the result, the + call fails with Windows error 87. + +2008-09-17 The InnoDB Team + + * mysql-test/innodb.result, mysql-test/innodb-zip.result, + mysql-test/innodb-zip.test, mysql-test/innodb.test, ibuf/ibuf0ibuf.c, + dict/dict0crea.c, dict/dict0load.c, dict/dict0boot.c, + include/dict0dict.h, include/trx0trx.h, dict/dict0dict.c, + trx/trx0trx.c, include/ha_prototypes.h, handler/ha_innodb.cc: + When creating an index in innodb_strict_mode, check that the + maximum record size will never exceed the B-tree page size limit. + For uncompressed tables, there should always be enough space for + two records in an empty B-tree page. For compressed tables, there + should be enough space for storing two node pointer records or one + data record in an empty page in uncompressed format. + The purpose of this check is to guarantee that INSERT or UPDATE + will never fail due to too big record size. + +2008-09-17 The InnoDB Team + + * btr/btr0cur.c, data/data0data.c, include/page0zip.h, + include/page0zip.ic, page/page0zip.c, mysql-test/innodb_bug36172.test: + Prevent infinite B-tree page splits in compressed tables by + ensuring that there will always be enough space for two node + pointer records in an empty B-tree page. Also, require that at + least one data record will fit in an empty compressed page. This + will reduce the maximum size of records in compressed tables. + +2008-09-09 The InnoDB Team + + * mysql-test/innodb.result: + Fix the failing innodb test by merging changes that MySQL made to + that file (r2646.12.1 in MySQL BZR repository) + +2008-09-09 The InnoDB Team + + * handler/ha_innodb.cc, mysql-test/innodb-autoinc.result, + mysql-test/innodb-autoinc.test: + Fix Bug#38839 auto increment does not work properly with InnoDB after + update + +2008-09-09 The InnoDB Team + + * dict/dict0dict.c, handler/handler0alter.cc, include/dict0dict.h, + mysql-test/innodb-index.result, mysql-test/innodb-index.test: + Fix Bug#38786 InnoDB plugin crashes on drop table/create table with FK + +2008-08-21 The InnoDB Team + + * handler/ha_innodb.cc, include/ha_prototypes.h, row/row0sel.c: + Fix Bug#37885 row_search_for_mysql may gap lock unnecessarily with SQL + comments in query + +2008-08-21 The InnoDB Team + + * handler/ha_innodb.cc: + Fix Bug#38185 ha_innobase::info can hold locks even when called with + HA_STATUS_NO_LOCK + +2008-08-18 The InnoDB Team + + * buf/buf0buf.c, buf/buf0lru.c, include/buf0buf.ic, include/univ.i: + Introduce UNIV_LRU_DEBUG for debugging the LRU buffer pool cache + +2008-08-08 The InnoDB Team + + * buf/buf0lru.c, include/buf0buf.h: + Fix two recovery bugs that could lead to a crash in debug builds with + small buffer size + +2008-08-07 The InnoDB Team + + * btr/btr0cur.c, handler/ha_innodb.cc, include/srv0srv.h, + srv/srv0srv.c: + Add a parameter innodb_stats_sample_pages to allow users to control + the number of index dives when InnoDB estimates the cardinality of + an index (ANALYZE TABLE, SHOW TABLE STATUS etc) + +2008-08-07 The InnoDB Team + + * trx/trx0i_s.c: + Fix a bug that would lead to a crash if a SELECT was issued from the + INFORMATION_SCHEMA tables and there are rolling back transactions at + the same time + +2008-08-06 The InnoDB Team + + * btr/btr0btr.c, btr/btr0cur.c, ibuf/ibuf0ibuf.c, include/btr0cur.h, + include/trx0roll.h, include/trx0types.h, row/row0purge.c, + row/row0uins.c, row/row0umod.c, trx/trx0roll.c: + In the rollback of incomplete transactions after crash recovery, + tolerate clustered index records whose externally stored columns + have not been written. + +2008-07-30 The InnoDB Team + + * trx/trx0trx.c: + Fixes a race in recovery where the recovery thread recovering a + PREPARED trx and the background rollback thread can both try + to free the trx after its status is set to COMMITTED_IN_MEMORY. + +2008-07-29 The InnoDB Team + + * include/trx0rec.h, row/row0purge.c, row/row0vers.c, trx/trx0rec.c: + Fix a BLOB corruption bug + +2008-07-15 The InnoDB Team + + * btr/btr0sea.c, dict/dict0dict.c, include/btr0sea.h: + Fixed a timing hole where a thread dropping an index can free the + in-memory index struct while another thread is still using that + structure to remove entries from adaptive hash index belonging + to one of the pages that belongs to the index being dropped. + +2008-07-04 The InnoDB Team + + * mysql-test/innodb-index.result: + Fix the failing innodb-index test by adjusting the result to a new + MySQL behavior (the change occured in BZR-r2667) + +2008-07-03 The InnoDB Team + + * mysql-test/innodb-zip.result, mysql-test/innodb-zip.test: + Remove the negative test cases that produce warnings + +2008-07-02 The InnoDB Team + + * mysql-test/innodb-replace.result, mysql-test/innodb-index.test: + Disable part of innodb-index test because MySQL changed its behavior + and is not calling ::add_index() anymore when adding primary index on + non-NULL column + +2008-07-01 The InnoDB Team + + * mysql-test/innodb-replace.result, mysql-test/innodb-replace.test: + Fix the failing innodb-replace test by merging changes that MySQL + made to that file (r2659 in MySQL BZR repository) + +2008-07-01 The InnoDB Team + + * lock/lock0lock.c: + Fix Bug#36942 Performance problem in lock_get_n_rec_locks (SHOW INNODB + STATUS) + +2008-07-01 The InnoDB Team + + * ha/ha0ha.c: + Fix Bug#36941 Performance problem in ha_print_info (SHOW INNODB + STATUS) + +2008-07-01 The InnoDB Team + + * handler/ha_innodb.cc, mysql-test/innodb-autoinc.result, + mysql-test/innodb-autoinc.test: + Fix Bug#37531 After truncate, auto_increment behaves incorrectly for + InnoDB + +2008-06-19 The InnoDB Team + + * handler/ha_innodb.cc: + Rewrite the function innodb_plugin_init() to support parameters in + different order (in static and dynamic InnoDB) and to support more + parameters in the static InnoDB + +2008-06-19 The InnoDB Team + + * handler/handler0alter.cc: + Fix a bug in ::add_index() which set the transaction state to "active" + but never restored it to the original value. This bug caused warnings + to be printed by the rpl.rpl_ddl mysql-test. + +2008-06-19 The InnoDB Team + + * mysql-test/patches: + Add a directory which contains patches, which need to be applied to + MySQL source in order to get some mysql-tests to succeed. The patches + cannot be committed in MySQL repository because they are specific to + the InnoDB plugin. + +2008-06-19 The InnoDB Team + + * mysql-test/innodb-zip.result, mysql-test/innodb-zip.test, + row/row0row.c: + Fix an anomaly when updating a record with BLOB prefix + +2008-06-18 The InnoDB Team + + * include/trx0sys.h, srv/srv0start.c, trx/trx0sys.c: + Fix a bug in recovery which was a side effect of the file_format_check + changes + +2008-06-09 The InnoDB Team + + * mysql-test/innodb.result: + Fix the failing innodb test by merging changes that MySQL made to that + file + +2008-06-06 The InnoDB Team + + * buf/buf0buf.c, handler/ha_innodb.cc, include/buf0buf.h, + include/srv0srv.h, srv/srv0srv.c: + Fix Bug#36600 SHOW STATUS takes a lot of CPU in + buf_get_latched_pages_number + + * handler/ha_innodb.cc, os/os0file.c: + Fix Bug#11894 innodb_file_per_table crashes w/ Windows .sym symbolic + link hack + + * include/ut0ut.h, srv/srv0srv.c, ut/ut0ut.c: + Fix Bug#36819 ut_usectime does not handle errors from gettimeofday + + * handler/ha_innodb.cc: + Fix Bug#35602 Failed to read auto-increment value from storage engine + + * srv/srv0start.c: + Fix Bug#36149 Read buffer overflow in srv0start.c found during "make + test" + +2008-05-08 The InnoDB Team + + * btr/btr0btr.c, mysql-test/innodb_bug36172.result, + mysql-test/innodb_bug36172.test: + Fix Bug#36172 insert into compressed innodb table crashes + +2008-05-08 The InnoDB Team + + InnoDB Plugin 1.0.1 released + +2008-05-06 The InnoDB Team + + * handler/ha_innodb.cc, include/srv0srv.h, include/sync0sync.h, + include/trx0sys.h, mysql-test/innodb-zip.result, + mysql-test/innodb-zip.test, srv/srv0srv.c, srv/srv0start.c, + sync/sync0sync.c, trx/trx0sys.c: + Implement the system tablespace tagging + + * handler/ha_innodb.cc, handler/i_s.cc, include/univ.i, + srv/srv0start.c: + Add InnoDB version in INFORMATION_SCHEMA.PLUGINS.PLUGIN_VERSION, + in the startup message and in a server variable innodb_version. + + * sync/sync0sync.c: + Fix a bug in the sync debug code where a lock with level + SYNC_LEVEL_VARYING would cause an assertion failure when a thread + tried to release it. + +2008-04-30 The InnoDB Team + + * Makefile.am: + Fix Bug#36434 ha_innodb.so is installed in the wrong directory + + * handler/ha_innodb.cc: + Merge change from MySQL (Fix Bug#35406 5.1-opt crashes on select from + I_S.REFERENTIAL_CONSTRAINTS): + ChangeSet@1.2563, 2008-03-18 19:42:04+04:00, gluh@mysql.com +1 -0 + + * scripts/install_innodb_plugins.sql: + Added + + * mysql-test/innodb.result: + Merge change from MySQL (this fixes the failing innodb test): + ChangeSet@1.1810.3601.4, 2008-02-07 02:33:21+04:00 + + * row/row0sel.c: + Fix Bug#35226 RBR event crashes slave + + * handler/ha_innodb.cc: + Change the fix for Bug#32440 to show bytes instead of kilobytes in + INFORMATION_SCHEMA.TABLES.DATA_FREE + + * handler/ha_innodb.cc, mysql-test/innodb.result, + mysql-test/innodb.test: + Fix Bug#29507 TRUNCATE shows to many rows effected + + * handler/ha_innodb.cc, mysql-test/innodb.result, + mysql-test/innodb.test: + Fix Bug#35537 Innodb doesn't increment handler_update and + handler_delete + +2008-04-29 The InnoDB Team + + * handler/i_s.cc, include/srv0start.h, srv/srv0start.c: + Fix Bug#36310 InnoDB plugin crash + +2008-04-23 The InnoDB Team + + * mysql-test/innodb_bug36169.result, mysql-test/innodb_bug36169.test, + row/row0mysql.c: + Fix Bug#36169 create innodb compressed table with too large row size + crashed + + * (outside the source tree): + Fix Bug#36222 New InnoDB plugin 1.0 has wrong MKDIR_P defined in + Makefile.in + +2008-04-15 The InnoDB Team + + InnoDB Plugin 1.0.0 released diff --git a/storage/xtradb/Makefile.am b/storage/xtradb/Makefile.am new file mode 100644 index 00000000000..8f64aedb9b0 --- /dev/null +++ b/storage/xtradb/Makefile.am @@ -0,0 +1,195 @@ +# Copyright (C) 2001, 2004, 2006 MySQL AB & Innobase Oy +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; version 2 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You 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 + +# Process this file with automake to create Makefile.in + +MYSQLDATAdir= $(localstatedir) +MYSQLSHAREdir= $(pkgdatadir) +MYSQLBASEdir= $(prefix) +MYSQLLIBdir= $(pkglibdir) +pkgplugindir= $(pkglibdir)/plugin +INCLUDES= -I$(top_srcdir)/include -I$(top_builddir)/include \ + -I$(top_srcdir)/regex \ + -I$(top_srcdir)/storage/xtradb/include \ + -I$(top_srcdir)/sql \ + -I$(srcdir) + +DEFS= @DEFS@ + + +noinst_HEADERS= include/btr0btr.h include/btr0btr.ic \ + include/btr0cur.h include/btr0cur.ic \ + include/btr0pcur.h include/btr0pcur.ic \ + include/btr0sea.h include/btr0sea.ic \ + include/btr0types.h include/buf0buddy.h \ + include/buf0buddy.ic include/buf0buf.h \ + include/buf0buf.ic include/buf0flu.h \ + include/buf0flu.ic include/buf0lru.h \ + include/buf0lru.ic include/buf0rea.h \ + include/buf0types.h include/data0data.h \ + include/data0data.ic include/data0type.h \ + include/data0type.ic include/data0types.h \ + include/db0err.h include/dict0boot.h \ + include/dict0boot.ic include/dict0crea.h \ + include/dict0crea.ic include/dict0dict.h \ + include/dict0dict.ic include/dict0load.h \ + include/dict0load.ic include/dict0mem.h \ + include/dict0mem.ic include/dict0types.h \ + include/dyn0dyn.h include/dyn0dyn.ic \ + include/eval0eval.h include/eval0eval.ic \ + include/eval0proc.h include/eval0proc.ic \ + include/fil0fil.h include/fsp0fsp.h \ + include/fsp0fsp.ic include/fut0fut.h \ + include/fut0fut.ic include/fut0lst.h \ + include/fut0lst.ic include/ha0ha.h \ + include/ha0ha.ic \ + include/ha0storage.h \ + include/ha0storage.ic \ + include/hash0hash.h \ + include/hash0hash.ic include/ibuf0ibuf.h \ + include/ibuf0ibuf.ic include/ibuf0types.h \ + include/lock0iter.h \ + include/lock0lock.h include/lock0lock.ic \ + include/lock0priv.h include/lock0priv.ic \ + include/lock0types.h include/log0log.h \ + include/log0log.ic include/log0recv.h \ + include/log0recv.ic include/mach0data.h \ + include/mach0data.ic include/mem0dbg.h \ + include/mem0dbg.ic mem/mem0dbg.c \ + include/mem0mem.h include/mem0mem.ic \ + include/mem0pool.h include/mem0pool.ic \ + include/mtr0log.h include/mtr0log.ic \ + include/mtr0mtr.h include/mtr0mtr.ic \ + include/mtr0types.h \ + include/mysql_addons.h \ + include/os0file.h \ + include/os0proc.h include/os0proc.ic \ + include/os0sync.h include/os0sync.ic \ + include/os0thread.h include/os0thread.ic \ + include/page0cur.h include/page0cur.ic \ + include/page0page.h include/page0page.ic \ + include/page0zip.h include/page0zip.ic \ + include/page0types.h include/pars0grm.h \ + include/pars0opt.h include/pars0opt.ic \ + include/pars0pars.h include/pars0pars.ic \ + include/pars0sym.h include/pars0sym.ic \ + include/pars0types.h include/que0que.h \ + include/que0que.ic include/que0types.h \ + include/read0read.h include/read0read.ic \ + include/read0types.h include/rem0cmp.h \ + include/rem0cmp.ic include/rem0rec.h \ + include/rem0rec.ic include/rem0types.h \ + include/row0ext.h include/row0ext.ic \ + include/row0ins.h include/row0ins.ic \ + include/row0merge.h \ + include/row0mysql.h include/row0mysql.ic \ + include/row0purge.h include/row0purge.ic \ + include/row0row.h include/row0row.ic \ + include/row0sel.h include/row0sel.ic \ + include/row0types.h include/row0uins.h \ + include/row0uins.ic include/row0umod.h \ + include/row0umod.ic include/row0undo.h \ + include/row0undo.ic include/row0upd.h \ + include/row0upd.ic include/row0vers.h \ + include/row0vers.ic include/srv0que.h \ + include/srv0srv.h include/srv0srv.ic \ + include/srv0start.h include/sync0arr.h \ + include/sync0arr.ic include/sync0rw.h \ + include/sync0rw.ic include/sync0sync.h \ + include/sync0sync.ic include/sync0types.h \ + include/thr0loc.h include/thr0loc.ic \ + include/trx0i_s.h \ + include/trx0purge.h include/trx0purge.ic \ + include/trx0rec.h include/trx0rec.ic \ + include/trx0roll.h include/trx0roll.ic \ + include/trx0rseg.h include/trx0rseg.ic \ + include/trx0sys.h include/trx0sys.ic \ + include/trx0trx.h include/trx0trx.ic \ + include/trx0types.h include/trx0undo.h \ + include/trx0undo.ic include/trx0xa.h \ + include/univ.i include/usr0sess.h \ + include/usr0sess.ic include/usr0types.h \ + include/ut0byte.h include/ut0byte.ic \ + include/ut0dbg.h include/ut0lst.h \ + include/ut0mem.h include/ut0mem.ic \ + include/ut0rnd.h include/ut0rnd.ic \ + include/ut0sort.h include/ut0ut.h \ + include/ut0ut.ic include/ut0vec.h \ + include/ut0vec.ic include/ut0list.h \ + include/ut0list.ic include/ut0wqueue.h \ + include/ha_prototypes.h handler/ha_innodb.h \ + include/handler0alter.h \ + handler/i_s.h handler/innodb_patch_info.h + +EXTRA_LIBRARIES= libinnobase.a +noinst_LIBRARIES= @plugin_innobase_static_target@ +libinnobase_a_SOURCES= btr/btr0btr.c btr/btr0cur.c btr/btr0pcur.c \ + btr/btr0sea.c buf/buf0buddy.c \ + buf/buf0buf.c buf/buf0flu.c \ + buf/buf0lru.c buf/buf0rea.c data/data0data.c \ + data/data0type.c dict/dict0boot.c \ + dict/dict0crea.c dict/dict0dict.c \ + dict/dict0load.c dict/dict0mem.c dyn/dyn0dyn.c \ + eval/eval0eval.c eval/eval0proc.c \ + fil/fil0fil.c fsp/fsp0fsp.c fut/fut0fut.c \ + fut/fut0lst.c ha/ha0ha.c \ + ha/ha0storage.c \ + ha/hash0hash.c \ + ibuf/ibuf0ibuf.c lock/lock0iter.c \ + lock/lock0lock.c \ + log/log0log.c log/log0recv.c mach/mach0data.c \ + mem/mem0mem.c mem/mem0pool.c mtr/mtr0log.c \ + mtr/mtr0mtr.c os/os0file.c os/os0proc.c \ + os/os0sync.c os/os0thread.c page/page0cur.c \ + page/page0page.c page/page0zip.c \ + pars/lexyy.c pars/pars0grm.c \ + pars/pars0opt.c pars/pars0pars.c \ + pars/pars0sym.c que/que0que.c read/read0read.c \ + rem/rem0cmp.c rem/rem0rec.c row/row0ext.c \ + row/row0ins.c row/row0merge.c \ + row/row0mysql.c row/row0purge.c row/row0row.c \ + row/row0sel.c row/row0uins.c row/row0umod.c \ + row/row0undo.c row/row0upd.c row/row0vers.c \ + srv/srv0que.c srv/srv0srv.c srv/srv0start.c \ + sync/sync0arr.c sync/sync0rw.c \ + sync/sync0sync.c thr/thr0loc.c \ + trx/trx0i_s.c \ + trx/trx0purge.c \ + trx/trx0rec.c trx/trx0roll.c trx/trx0rseg.c \ + trx/trx0sys.c trx/trx0trx.c trx/trx0undo.c \ + usr/usr0sess.c ut/ut0byte.c ut/ut0dbg.c \ + ut/ut0list.c ut/ut0mem.c ut/ut0rnd.c \ + ut/ut0ut.c ut/ut0vec.c ut/ut0wqueue.c \ + handler/ha_innodb.cc handler/handler0alter.cc \ + handler/i_s.cc \ + handler/mysql_addons.cc + +libinnobase_a_CXXFLAGS= $(AM_CFLAGS) +libinnobase_a_CFLAGS= $(AM_CFLAGS) + +EXTRA_LTLIBRARIES= ha_innodb.la +pkgplugin_LTLIBRARIES= @plugin_innobase_shared_target@ + +ha_innodb_la_LDFLAGS= -module -rpath $(pkgplugindir) +ha_innodb_la_CXXFLAGS= $(AM_CFLAGS) $(INNODB_DYNAMIC_CFLAGS) +ha_innodb_la_CFLAGS= $(AM_CFLAGS) $(INNODB_DYNAMIC_CFLAGS) +ha_innodb_la_SOURCES= $(libinnobase_a_SOURCES) + +EXTRA_DIST= CMakeLists.txt plug.in \ + pars/make_bison.sh pars/make_flex.sh \ + pars/pars0grm.y pars/pars0lex.l + +# Don't update the files from bitkeeper +%::SCCS/s.% diff --git a/storage/xtradb/btr/btr0btr.c b/storage/xtradb/btr/btr0btr.c new file mode 100644 index 00000000000..2029a95cc19 --- /dev/null +++ b/storage/xtradb/btr/btr0btr.c @@ -0,0 +1,3656 @@ +/***************************************************************************** + +Copyright (c) 1994, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/****************************************************** +The B-tree + +Created 6/2/1994 Heikki Tuuri +*******************************************************/ + +#include "btr0btr.h" + +#ifdef UNIV_NONINL +#include "btr0btr.ic" +#endif + +#include "fsp0fsp.h" +#include "page0page.h" +#include "page0zip.h" +#include "btr0cur.h" +#include "btr0sea.h" +#include "btr0pcur.h" +#include "rem0cmp.h" +#include "lock0lock.h" +#include "ibuf0ibuf.h" +#include "trx0trx.h" + +/* +Latching strategy of the InnoDB B-tree +-------------------------------------- +A tree latch protects all non-leaf nodes of the tree. Each node of a tree +also has a latch of its own. + +A B-tree operation normally first acquires an S-latch on the tree. It +searches down the tree and releases the tree latch when it has the +leaf node latch. To save CPU time we do not acquire any latch on +non-leaf nodes of the tree during a search, those pages are only bufferfixed. + +If an operation needs to restructure the tree, it acquires an X-latch on +the tree before searching to a leaf node. If it needs, for example, to +split a leaf, +(1) InnoDB decides the split point in the leaf, +(2) allocates a new page, +(3) inserts the appropriate node pointer to the first non-leaf level, +(4) releases the tree X-latch, +(5) and then moves records from the leaf to the new allocated page. + +Node pointers +------------- +Leaf pages of a B-tree contain the index records stored in the +tree. On levels n > 0 we store 'node pointers' to pages on level +n - 1. For each page there is exactly one node pointer stored: +thus the our tree is an ordinary B-tree, not a B-link tree. + +A node pointer contains a prefix P of an index record. The prefix +is long enough so that it determines an index record uniquely. +The file page number of the child page is added as the last +field. To the child page we can store node pointers or index records +which are >= P in the alphabetical order, but < P1 if there is +a next node pointer on the level, and P1 is its prefix. + +If a node pointer with a prefix P points to a non-leaf child, +then the leftmost record in the child must have the same +prefix P. If it points to a leaf node, the child is not required +to contain any record with a prefix equal to P. The leaf case +is decided this way to allow arbitrary deletions in a leaf node +without touching upper levels of the tree. + +We have predefined a special minimum record which we +define as the smallest record in any alphabetical order. +A minimum record is denoted by setting a bit in the record +header. A minimum record acts as the prefix of a node pointer +which points to a leftmost node on any level of the tree. + +File page allocation +-------------------- +In the root node of a B-tree there are two file segment headers. +The leaf pages of a tree are allocated from one file segment, to +make them consecutive on disk if possible. From the other file segment +we allocate pages for the non-leaf levels of the tree. +*/ + +#ifdef UNIV_BTR_DEBUG +/****************************************************************** +Checks a file segment header within a B-tree root page. */ +static +ibool +btr_root_fseg_validate( +/*===================*/ + /* out: TRUE if valid */ + const fseg_header_t* seg_header, /* in: segment header */ + ulint space) /* in: tablespace identifier */ +{ + ulint offset = mach_read_from_2(seg_header + FSEG_HDR_OFFSET); + + ut_a(mach_read_from_4(seg_header + FSEG_HDR_SPACE) == space); + ut_a(offset >= FIL_PAGE_DATA); + ut_a(offset <= UNIV_PAGE_SIZE - FIL_PAGE_DATA_END); + return(TRUE); +} +#endif /* UNIV_BTR_DEBUG */ + +/****************************************************************** +Gets the root node of a tree and x-latches it. */ +static +buf_block_t* +btr_root_block_get( +/*===============*/ + /* out: root page, x-latched */ + dict_index_t* index, /* in: index tree */ + mtr_t* mtr) /* in: mtr */ +{ + ulint space; + ulint zip_size; + ulint root_page_no; + buf_block_t* block; + + space = dict_index_get_space(index); + zip_size = dict_table_zip_size(index->table); + root_page_no = dict_index_get_page(index); + + block = btr_block_get(space, zip_size, root_page_no, RW_X_LATCH, mtr); + ut_a((ibool)!!page_is_comp(buf_block_get_frame(block)) + == dict_table_is_comp(index->table)); +#ifdef UNIV_BTR_DEBUG + if (!dict_index_is_ibuf(index)) { + const page_t* root = buf_block_get_frame(block); + + ut_a(btr_root_fseg_validate(FIL_PAGE_DATA + PAGE_BTR_SEG_LEAF + + root, space)); + ut_a(btr_root_fseg_validate(FIL_PAGE_DATA + PAGE_BTR_SEG_TOP + + root, space)); + } +#endif /* UNIV_BTR_DEBUG */ + + return(block); +} + +/****************************************************************** +Gets the root node of a tree and x-latches it. */ +UNIV_INTERN +page_t* +btr_root_get( +/*=========*/ + /* out: root page, x-latched */ + dict_index_t* index, /* in: index tree */ + mtr_t* mtr) /* in: mtr */ +{ + return(buf_block_get_frame(btr_root_block_get(index, mtr))); +} + +/***************************************************************** +Gets pointer to the previous user record in the tree. It is assumed that +the caller has appropriate latches on the page and its neighbor. */ +UNIV_INTERN +rec_t* +btr_get_prev_user_rec( +/*==================*/ + /* out: previous user record, NULL if there is none */ + rec_t* rec, /* in: record on leaf level */ + mtr_t* mtr) /* in: mtr holding a latch on the page, and if + needed, also to the previous page */ +{ + page_t* page; + page_t* prev_page; + ulint prev_page_no; + + if (!page_rec_is_infimum(rec)) { + + rec_t* prev_rec = page_rec_get_prev(rec); + + if (!page_rec_is_infimum(prev_rec)) { + + return(prev_rec); + } + } + + page = page_align(rec); + prev_page_no = btr_page_get_prev(page, mtr); + + if (prev_page_no != FIL_NULL) { + + ulint space; + ulint zip_size; + buf_block_t* prev_block; + + space = page_get_space_id(page); + zip_size = fil_space_get_zip_size(space); + + prev_block = buf_page_get_with_no_latch(space, zip_size, + prev_page_no, mtr); + prev_page = buf_block_get_frame(prev_block); + /* The caller must already have a latch to the brother */ + ut_ad(mtr_memo_contains(mtr, prev_block, + MTR_MEMO_PAGE_S_FIX) + || mtr_memo_contains(mtr, prev_block, + MTR_MEMO_PAGE_X_FIX)); +#ifdef UNIV_BTR_DEBUG + ut_a(page_is_comp(prev_page) == page_is_comp(page)); + ut_a(btr_page_get_next(prev_page, mtr) + == page_get_page_no(page)); +#endif /* UNIV_BTR_DEBUG */ + + return(page_rec_get_prev(page_get_supremum_rec(prev_page))); + } + + return(NULL); +} + +/***************************************************************** +Gets pointer to the next user record in the tree. It is assumed that the +caller has appropriate latches on the page and its neighbor. */ +UNIV_INTERN +rec_t* +btr_get_next_user_rec( +/*==================*/ + /* out: next user record, NULL if there is none */ + rec_t* rec, /* in: record on leaf level */ + mtr_t* mtr) /* in: mtr holding a latch on the page, and if + needed, also to the next page */ +{ + page_t* page; + page_t* next_page; + ulint next_page_no; + + if (!page_rec_is_supremum(rec)) { + + rec_t* next_rec = page_rec_get_next(rec); + + if (!page_rec_is_supremum(next_rec)) { + + return(next_rec); + } + } + + page = page_align(rec); + next_page_no = btr_page_get_next(page, mtr); + + if (next_page_no != FIL_NULL) { + ulint space; + ulint zip_size; + buf_block_t* next_block; + + space = page_get_space_id(page); + zip_size = fil_space_get_zip_size(space); + + next_block = buf_page_get_with_no_latch(space, zip_size, + next_page_no, mtr); + next_page = buf_block_get_frame(next_block); + /* The caller must already have a latch to the brother */ + ut_ad(mtr_memo_contains(mtr, next_block, MTR_MEMO_PAGE_S_FIX) + || mtr_memo_contains(mtr, next_block, + MTR_MEMO_PAGE_X_FIX)); +#ifdef UNIV_BTR_DEBUG + ut_a(page_is_comp(next_page) == page_is_comp(page)); + ut_a(btr_page_get_prev(next_page, mtr) + == page_get_page_no(page)); +#endif /* UNIV_BTR_DEBUG */ + + return(page_rec_get_next(page_get_infimum_rec(next_page))); + } + + return(NULL); +} + +/****************************************************************** +Creates a new index page (not the root, and also not +used in page reorganization). @see btr_page_empty(). */ +static +void +btr_page_create( +/*============*/ + buf_block_t* block, /* in/out: page to be created */ + page_zip_des_t* page_zip,/* in/out: compressed page, or NULL */ + dict_index_t* index, /* in: index */ + ulint level, /* in: the B-tree level of the page */ + mtr_t* mtr) /* in: mtr */ +{ + page_t* page = buf_block_get_frame(block); + + ut_ad(mtr_memo_contains(mtr, block, MTR_MEMO_PAGE_X_FIX)); + + if (UNIV_LIKELY_NULL(page_zip)) { + page_create_zip(block, index, level, mtr); + } else { + page_create(block, mtr, dict_table_is_comp(index->table)); + /* Set the level of the new index page */ + btr_page_set_level(page, NULL, level, mtr); + } + + block->check_index_page_at_flush = TRUE; + + btr_page_set_index_id(page, page_zip, index->id, mtr); +} + +/****************************************************************** +Allocates a new file page to be used in an ibuf tree. Takes the page from +the free list of the tree, which must contain pages! */ +static +buf_block_t* +btr_page_alloc_for_ibuf( +/*====================*/ + /* out: new allocated block, x-latched */ + dict_index_t* index, /* in: index tree */ + mtr_t* mtr) /* in: mtr */ +{ + fil_addr_t node_addr; + page_t* root; + page_t* new_page; + buf_block_t* new_block; + + root = btr_root_get(index, mtr); + + node_addr = flst_get_first(root + PAGE_HEADER + + PAGE_BTR_IBUF_FREE_LIST, mtr); + ut_a(node_addr.page != FIL_NULL); + + new_block = buf_page_get(dict_index_get_space(index), + dict_table_zip_size(index->table), + node_addr.page, RW_X_LATCH, mtr); + new_page = buf_block_get_frame(new_block); + buf_block_dbg_add_level(new_block, SYNC_TREE_NODE_NEW); + + flst_remove(root + PAGE_HEADER + PAGE_BTR_IBUF_FREE_LIST, + new_page + PAGE_HEADER + PAGE_BTR_IBUF_FREE_LIST_NODE, + mtr); + ut_ad(flst_validate(root + PAGE_HEADER + PAGE_BTR_IBUF_FREE_LIST, + mtr)); + + return(new_block); +} + +/****************************************************************** +Allocates a new file page to be used in an index tree. NOTE: we assume +that the caller has made the reservation for free extents! */ +UNIV_INTERN +buf_block_t* +btr_page_alloc( +/*===========*/ + /* out: new allocated block, x-latched; + NULL if out of space */ + dict_index_t* index, /* in: index */ + ulint hint_page_no, /* in: hint of a good page */ + byte file_direction, /* in: direction where a possible + page split is made */ + ulint level, /* in: level where the page is placed + in the tree */ + mtr_t* mtr) /* in: mtr */ +{ + fseg_header_t* seg_header; + page_t* root; + buf_block_t* new_block; + ulint new_page_no; + + if (dict_index_is_ibuf(index)) { + + return(btr_page_alloc_for_ibuf(index, mtr)); + } + + root = btr_root_get(index, mtr); + + if (level == 0) { + seg_header = root + PAGE_HEADER + PAGE_BTR_SEG_LEAF; + } else { + seg_header = root + PAGE_HEADER + PAGE_BTR_SEG_TOP; + } + + /* Parameter TRUE below states that the caller has made the + reservation for free extents, and thus we know that a page can + be allocated: */ + + new_page_no = fseg_alloc_free_page_general(seg_header, hint_page_no, + file_direction, TRUE, mtr); + if (new_page_no == FIL_NULL) { + + return(NULL); + } + + new_block = buf_page_get(dict_index_get_space(index), + dict_table_zip_size(index->table), + new_page_no, RW_X_LATCH, mtr); + buf_block_dbg_add_level(new_block, SYNC_TREE_NODE_NEW); + + return(new_block); +} + +/****************************************************************** +Gets the number of pages in a B-tree. */ +UNIV_INTERN +ulint +btr_get_size( +/*=========*/ + /* out: number of pages */ + dict_index_t* index, /* in: index */ + ulint flag) /* in: BTR_N_LEAF_PAGES or BTR_TOTAL_SIZE */ +{ + fseg_header_t* seg_header; + page_t* root; + ulint n; + ulint dummy; + mtr_t mtr; + + mtr_start(&mtr); + + mtr_s_lock(dict_index_get_lock(index), &mtr); + + root = btr_root_get(index, &mtr); + + if (flag == BTR_N_LEAF_PAGES) { + seg_header = root + PAGE_HEADER + PAGE_BTR_SEG_LEAF; + + fseg_n_reserved_pages(seg_header, &n, &mtr); + + } else if (flag == BTR_TOTAL_SIZE) { + seg_header = root + PAGE_HEADER + PAGE_BTR_SEG_TOP; + + n = fseg_n_reserved_pages(seg_header, &dummy, &mtr); + + seg_header = root + PAGE_HEADER + PAGE_BTR_SEG_LEAF; + + n += fseg_n_reserved_pages(seg_header, &dummy, &mtr); + } else { + ut_error; + } + + mtr_commit(&mtr); + + return(n); +} + +/****************************************************************** +Frees a page used in an ibuf tree. Puts the page to the free list of the +ibuf tree. */ +static +void +btr_page_free_for_ibuf( +/*===================*/ + dict_index_t* index, /* in: index tree */ + buf_block_t* block, /* in: block to be freed, x-latched */ + mtr_t* mtr) /* in: mtr */ +{ + page_t* root; + + ut_ad(mtr_memo_contains(mtr, block, MTR_MEMO_PAGE_X_FIX)); + root = btr_root_get(index, mtr); + + flst_add_first(root + PAGE_HEADER + PAGE_BTR_IBUF_FREE_LIST, + buf_block_get_frame(block) + + PAGE_HEADER + PAGE_BTR_IBUF_FREE_LIST_NODE, mtr); + + ut_ad(flst_validate(root + PAGE_HEADER + PAGE_BTR_IBUF_FREE_LIST, + mtr)); +} + +/****************************************************************** +Frees a file page used in an index tree. Can be used also to (BLOB) +external storage pages, because the page level 0 can be given as an +argument. */ +UNIV_INTERN +void +btr_page_free_low( +/*==============*/ + dict_index_t* index, /* in: index tree */ + buf_block_t* block, /* in: block to be freed, x-latched */ + ulint level, /* in: page level */ + mtr_t* mtr) /* in: mtr */ +{ + fseg_header_t* seg_header; + page_t* root; + + ut_ad(mtr_memo_contains(mtr, block, MTR_MEMO_PAGE_X_FIX)); + /* The page gets invalid for optimistic searches: increment the frame + modify clock */ + + buf_block_modify_clock_inc(block); + + if (dict_index_is_ibuf(index)) { + + btr_page_free_for_ibuf(index, block, mtr); + + return; + } + + root = btr_root_get(index, mtr); + + if (level == 0) { + seg_header = root + PAGE_HEADER + PAGE_BTR_SEG_LEAF; + } else { + seg_header = root + PAGE_HEADER + PAGE_BTR_SEG_TOP; + } + + fseg_free_page(seg_header, + buf_block_get_space(block), + buf_block_get_page_no(block), mtr); +} + +/****************************************************************** +Frees a file page used in an index tree. NOTE: cannot free field external +storage pages because the page must contain info on its level. */ +UNIV_INTERN +void +btr_page_free( +/*==========*/ + dict_index_t* index, /* in: index tree */ + buf_block_t* block, /* in: block to be freed, x-latched */ + mtr_t* mtr) /* in: mtr */ +{ + ulint level; + + level = btr_page_get_level(buf_block_get_frame(block), mtr); + + btr_page_free_low(index, block, level, mtr); +} + +/****************************************************************** +Sets the child node file address in a node pointer. */ +UNIV_INLINE +void +btr_node_ptr_set_child_page_no( +/*===========================*/ + rec_t* rec, /* in: node pointer record */ + page_zip_des_t* page_zip,/* in/out: compressed page whose uncompressed + part will be updated, or NULL */ + const ulint* offsets,/* in: array returned by rec_get_offsets() */ + ulint page_no,/* in: child node address */ + mtr_t* mtr) /* in: mtr */ +{ + byte* field; + ulint len; + + ut_ad(rec_offs_validate(rec, NULL, offsets)); + ut_ad(!page_is_leaf(page_align(rec))); + ut_ad(!rec_offs_comp(offsets) || rec_get_node_ptr_flag(rec)); + + /* The child address is in the last field */ + field = rec_get_nth_field(rec, offsets, + rec_offs_n_fields(offsets) - 1, &len); + + ut_ad(len == REC_NODE_PTR_SIZE); + + if (UNIV_LIKELY_NULL(page_zip)) { + page_zip_write_node_ptr(page_zip, rec, + rec_offs_data_size(offsets), + page_no, mtr); + } else { + mlog_write_ulint(field, page_no, MLOG_4BYTES, mtr); + } +} + +/**************************************************************** +Returns the child page of a node pointer and x-latches it. */ +static +buf_block_t* +btr_node_ptr_get_child( +/*===================*/ + /* out: child page, x-latched */ + const rec_t* node_ptr,/* in: node pointer */ + dict_index_t* index, /* in: index */ + const ulint* offsets,/* in: array returned by rec_get_offsets() */ + mtr_t* mtr) /* in: mtr */ +{ + ulint page_no; + ulint space; + + ut_ad(rec_offs_validate(node_ptr, index, offsets)); + space = page_get_space_id(page_align(node_ptr)); + page_no = btr_node_ptr_get_child_page_no(node_ptr, offsets); + + return(btr_block_get(space, dict_table_zip_size(index->table), + page_no, RW_X_LATCH, mtr)); +} + +/**************************************************************** +Returns the upper level node pointer to a page. It is assumed that mtr holds +an x-latch on the tree. */ +static +ulint* +btr_page_get_father_node_ptr( +/*=========================*/ + /* out: rec_get_offsets() of the + node pointer record */ + ulint* offsets,/* in: work area for the return value */ + mem_heap_t* heap, /* in: memory heap to use */ + btr_cur_t* cursor, /* in: cursor pointing to user record, + out: cursor on node pointer record, + its page x-latched */ + mtr_t* mtr) /* in: mtr */ +{ + dtuple_t* tuple; + rec_t* user_rec; + rec_t* node_ptr; + ulint level; + ulint page_no; + dict_index_t* index; + + page_no = buf_block_get_page_no(btr_cur_get_block(cursor)); + index = btr_cur_get_index(cursor); + + ut_ad(mtr_memo_contains(mtr, dict_index_get_lock(index), + MTR_MEMO_X_LOCK)); + + ut_ad(dict_index_get_page(index) != page_no); + + level = btr_page_get_level(btr_cur_get_page(cursor), mtr); + user_rec = btr_cur_get_rec(cursor); + ut_a(page_rec_is_user_rec(user_rec)); + tuple = dict_index_build_node_ptr(index, user_rec, 0, heap, level); + + btr_cur_search_to_nth_level(index, level + 1, tuple, PAGE_CUR_LE, + BTR_CONT_MODIFY_TREE, cursor, 0, mtr); + + node_ptr = btr_cur_get_rec(cursor); + ut_ad(!page_rec_is_comp(node_ptr) + || rec_get_status(node_ptr) == REC_STATUS_NODE_PTR); + offsets = rec_get_offsets(node_ptr, index, offsets, + ULINT_UNDEFINED, &heap); + + if (UNIV_UNLIKELY(btr_node_ptr_get_child_page_no(node_ptr, offsets) + != page_no)) { + rec_t* print_rec; + fputs("InnoDB: Dump of the child page:\n", stderr); + buf_page_print(page_align(user_rec), 0); + fputs("InnoDB: Dump of the parent page:\n", stderr); + buf_page_print(page_align(node_ptr), 0); + + fputs("InnoDB: Corruption of an index tree: table ", stderr); + ut_print_name(stderr, NULL, TRUE, index->table_name); + fputs(", index ", stderr); + ut_print_name(stderr, NULL, FALSE, index->name); + fprintf(stderr, ",\n" + "InnoDB: father ptr page no %lu, child page no %lu\n", + (ulong) + btr_node_ptr_get_child_page_no(node_ptr, offsets), + (ulong) page_no); + print_rec = page_rec_get_next( + page_get_infimum_rec(page_align(user_rec))); + offsets = rec_get_offsets(print_rec, index, + offsets, ULINT_UNDEFINED, &heap); + page_rec_print(print_rec, offsets); + offsets = rec_get_offsets(node_ptr, index, offsets, + ULINT_UNDEFINED, &heap); + page_rec_print(node_ptr, offsets); + + fputs("InnoDB: You should dump + drop + reimport the table" + " to fix the\n" + "InnoDB: corruption. If the crash happens at " + "the database startup, see\n" + "InnoDB: http://dev.mysql.com/doc/refman/5.1/en/" + "forcing-recovery.html about\n" + "InnoDB: forcing recovery. " + "Then dump + drop + reimport.\n", stderr); + + ut_error; + } + + return(offsets); +} + +/**************************************************************** +Returns the upper level node pointer to a page. It is assumed that mtr holds +an x-latch on the tree. */ +static +ulint* +btr_page_get_father_block( +/*======================*/ + /* out: rec_get_offsets() of the + node pointer record */ + ulint* offsets,/* in: work area for the return value */ + mem_heap_t* heap, /* in: memory heap to use */ + dict_index_t* index, /* in: b-tree index */ + buf_block_t* block, /* in: child page in the index */ + mtr_t* mtr, /* in: mtr */ + btr_cur_t* cursor) /* out: cursor on node pointer record, + its page x-latched */ +{ + rec_t* rec + = page_rec_get_next(page_get_infimum_rec(buf_block_get_frame( + block))); + btr_cur_position(index, rec, block, cursor); + return(btr_page_get_father_node_ptr(offsets, heap, cursor, mtr)); +} + +/**************************************************************** +Seeks to the upper level node pointer to a page. +It is assumed that mtr holds an x-latch on the tree. */ +static +void +btr_page_get_father( +/*================*/ + dict_index_t* index, /* in: b-tree index */ + buf_block_t* block, /* in: child page in the index */ + mtr_t* mtr, /* in: mtr */ + btr_cur_t* cursor) /* out: cursor on node pointer record, + its page x-latched */ +{ + mem_heap_t* heap; + rec_t* rec + = page_rec_get_next(page_get_infimum_rec(buf_block_get_frame( + block))); + btr_cur_position(index, rec, block, cursor); + + heap = mem_heap_create(100); + btr_page_get_father_node_ptr(NULL, heap, cursor, mtr); + mem_heap_free(heap); +} + +/**************************************************************** +Creates the root node for a new index tree. */ +UNIV_INTERN +ulint +btr_create( +/*=======*/ + /* out: page number of the created root, + FIL_NULL if did not succeed */ + ulint type, /* in: type of the index */ + ulint space, /* in: space where created */ + ulint zip_size,/* in: compressed page size in bytes + or 0 for uncompressed pages */ + dulint index_id,/* in: index id */ + dict_index_t* index, /* in: index */ + mtr_t* mtr) /* in: mini-transaction handle */ +{ + ulint page_no; + buf_block_t* block; + buf_frame_t* frame; + page_t* page; + page_zip_des_t* page_zip; + + /* Create the two new segments (one, in the case of an ibuf tree) for + the index tree; the segment headers are put on the allocated root page + (for an ibuf tree, not in the root, but on a separate ibuf header + page) */ + + if (type & DICT_IBUF) { + /* Allocate first the ibuf header page */ + buf_block_t* ibuf_hdr_block = fseg_create( + space, 0, + IBUF_HEADER + IBUF_TREE_SEG_HEADER, mtr); + + buf_block_dbg_add_level(ibuf_hdr_block, SYNC_TREE_NODE_NEW); + + ut_ad(buf_block_get_page_no(ibuf_hdr_block) + == IBUF_HEADER_PAGE_NO); + /* Allocate then the next page to the segment: it will be the + tree root page */ + + page_no = fseg_alloc_free_page(buf_block_get_frame( + ibuf_hdr_block) + + IBUF_HEADER + + IBUF_TREE_SEG_HEADER, + IBUF_TREE_ROOT_PAGE_NO, + FSP_UP, mtr); + ut_ad(page_no == IBUF_TREE_ROOT_PAGE_NO); + + block = buf_page_get(space, zip_size, page_no, + RW_X_LATCH, mtr); + } else { + block = fseg_create(space, 0, + PAGE_HEADER + PAGE_BTR_SEG_TOP, mtr); + } + + if (block == NULL) { + + return(FIL_NULL); + } + + page_no = buf_block_get_page_no(block); + frame = buf_block_get_frame(block); + + buf_block_dbg_add_level(block, SYNC_TREE_NODE_NEW); + + if (type & DICT_IBUF) { + /* It is an insert buffer tree: initialize the free list */ + + ut_ad(page_no == IBUF_TREE_ROOT_PAGE_NO); + + flst_init(frame + PAGE_HEADER + PAGE_BTR_IBUF_FREE_LIST, mtr); + } else { + /* It is a non-ibuf tree: create a file segment for leaf + pages */ + fseg_create(space, page_no, + PAGE_HEADER + PAGE_BTR_SEG_LEAF, mtr); + /* The fseg create acquires a second latch on the page, + therefore we must declare it: */ + buf_block_dbg_add_level(block, SYNC_TREE_NODE_NEW); + } + + /* Create a new index page on the the allocated segment page */ + page_zip = buf_block_get_page_zip(block); + + if (UNIV_LIKELY_NULL(page_zip)) { + page = page_create_zip(block, index, 0, mtr); + } else { + page = page_create(block, mtr, + dict_table_is_comp(index->table)); + /* Set the level of the new index page */ + btr_page_set_level(page, NULL, 0, mtr); + } + + block->check_index_page_at_flush = TRUE; + + /* Set the index id of the page */ + btr_page_set_index_id(page, page_zip, index_id, mtr); + + /* Set the next node and previous node fields */ + btr_page_set_next(page, page_zip, FIL_NULL, mtr); + btr_page_set_prev(page, page_zip, FIL_NULL, mtr); + + /* We reset the free bits for the page to allow creation of several + trees in the same mtr, otherwise the latch on a bitmap page would + prevent it because of the latching order */ + + if (!(type & DICT_CLUSTERED)) { + ibuf_reset_free_bits(block); + } + + /* In the following assertion we test that two records of maximum + allowed size fit on the root page: this fact is needed to ensure + correctness of split algorithms */ + + ut_ad(page_get_max_insert_size(page, 2) > 2 * BTR_PAGE_MAX_REC_SIZE); + + return(page_no); +} + +/**************************************************************** +Frees a B-tree except the root page, which MUST be freed after this +by calling btr_free_root. */ +UNIV_INTERN +void +btr_free_but_not_root( +/*==================*/ + ulint space, /* in: space where created */ + ulint zip_size, /* in: compressed page size in bytes + or 0 for uncompressed pages */ + ulint root_page_no) /* in: root page number */ +{ + ibool finished; + page_t* root; + mtr_t mtr; + +leaf_loop: + mtr_start(&mtr); + + root = btr_page_get(space, zip_size, root_page_no, RW_X_LATCH, &mtr); +#ifdef UNIV_BTR_DEBUG + ut_a(btr_root_fseg_validate(FIL_PAGE_DATA + PAGE_BTR_SEG_LEAF + + root, space)); + ut_a(btr_root_fseg_validate(FIL_PAGE_DATA + PAGE_BTR_SEG_TOP + + root, space)); +#endif /* UNIV_BTR_DEBUG */ + + /* NOTE: page hash indexes are dropped when a page is freed inside + fsp0fsp. */ + + finished = fseg_free_step(root + PAGE_HEADER + PAGE_BTR_SEG_LEAF, + &mtr); + mtr_commit(&mtr); + + if (!finished) { + + goto leaf_loop; + } +top_loop: + mtr_start(&mtr); + + root = btr_page_get(space, zip_size, root_page_no, RW_X_LATCH, &mtr); +#ifdef UNIV_BTR_DEBUG + ut_a(btr_root_fseg_validate(FIL_PAGE_DATA + PAGE_BTR_SEG_TOP + + root, space)); +#endif /* UNIV_BTR_DEBUG */ + + finished = fseg_free_step_not_header( + root + PAGE_HEADER + PAGE_BTR_SEG_TOP, &mtr); + mtr_commit(&mtr); + + if (!finished) { + + goto top_loop; + } +} + +/**************************************************************** +Frees the B-tree root page. Other tree MUST already have been freed. */ +UNIV_INTERN +void +btr_free_root( +/*==========*/ + ulint space, /* in: space where created */ + ulint zip_size, /* in: compressed page size in bytes + or 0 for uncompressed pages */ + ulint root_page_no, /* in: root page number */ + mtr_t* mtr) /* in: a mini-transaction which has already + been started */ +{ + buf_block_t* block; + fseg_header_t* header; + + block = btr_block_get(space, zip_size, root_page_no, RW_X_LATCH, mtr); + + btr_search_drop_page_hash_index(block); + + header = buf_block_get_frame(block) + PAGE_HEADER + PAGE_BTR_SEG_TOP; +#ifdef UNIV_BTR_DEBUG + ut_a(btr_root_fseg_validate(header, space)); +#endif /* UNIV_BTR_DEBUG */ + + while (!fseg_free_step(header, mtr)); +} + +/***************************************************************** +Reorganizes an index page. */ +static +ibool +btr_page_reorganize_low( +/*====================*/ + ibool recovery,/* in: TRUE if called in recovery: + locks should not be updated, i.e., + there cannot exist locks on the + page, and a hash index should not be + dropped: it cannot exist */ + buf_block_t* block, /* in: page to be reorganized */ + dict_index_t* index, /* in: record descriptor */ + mtr_t* mtr) /* in: mtr */ +{ + page_t* page = buf_block_get_frame(block); + page_zip_des_t* page_zip = buf_block_get_page_zip(block); + buf_block_t* temp_block; + page_t* temp_page; + ulint log_mode; + ulint data_size1; + ulint data_size2; + ulint max_ins_size1; + ulint max_ins_size2; + ibool success = FALSE; + + ut_ad(mtr_memo_contains(mtr, block, MTR_MEMO_PAGE_X_FIX)); + ut_ad(!!page_is_comp(page) == dict_table_is_comp(index->table)); +#ifdef UNIV_ZIP_DEBUG + ut_a(!page_zip || page_zip_validate(page_zip, page)); +#endif /* UNIV_ZIP_DEBUG */ + data_size1 = page_get_data_size(page); + max_ins_size1 = page_get_max_insert_size_after_reorganize(page, 1); + + /* Write the log record */ + mlog_open_and_write_index(mtr, page, index, page_is_comp(page) + ? MLOG_COMP_PAGE_REORGANIZE + : MLOG_PAGE_REORGANIZE, 0); + + /* Turn logging off */ + log_mode = mtr_set_log_mode(mtr, MTR_LOG_NONE); + + temp_block = buf_block_alloc(0); + temp_page = temp_block->frame; + + /* Copy the old page to temporary space */ + buf_frame_copy(temp_page, page); + + if (UNIV_LIKELY(!recovery)) { + btr_search_drop_page_hash_index(block); + } + + /* Recreate the page: note that global data on page (possible + segment headers, next page-field, etc.) is preserved intact */ + + page_create(block, mtr, dict_table_is_comp(index->table)); + block->check_index_page_at_flush = TRUE; + + /* Copy the records from the temporary space to the recreated page; + do not copy the lock bits yet */ + + page_copy_rec_list_end_no_locks(block, temp_block, + page_get_infimum_rec(temp_page), + index, mtr); + /* Copy max trx id to recreated page */ + page_set_max_trx_id(block, NULL, page_get_max_trx_id(temp_page)); + + if (UNIV_LIKELY_NULL(page_zip) + && UNIV_UNLIKELY + (!page_zip_compress(page_zip, page, index, NULL))) { + + /* Restore the old page and exit. */ + buf_frame_copy(page, temp_page); + + goto func_exit; + } + + if (UNIV_LIKELY(!recovery)) { + /* Update the record lock bitmaps */ + lock_move_reorganize_page(block, temp_block); + } + + data_size2 = page_get_data_size(page); + max_ins_size2 = page_get_max_insert_size_after_reorganize(page, 1); + + if (UNIV_UNLIKELY(data_size1 != data_size2) + || UNIV_UNLIKELY(max_ins_size1 != max_ins_size2)) { + buf_page_print(page, 0); + buf_page_print(temp_page, 0); + fprintf(stderr, + "InnoDB: Error: page old data size %lu" + " new data size %lu\n" + "InnoDB: Error: page old max ins size %lu" + " new max ins size %lu\n" + "InnoDB: Submit a detailed bug report" + " to http://bugs.mysql.com\n", + (unsigned long) data_size1, (unsigned long) data_size2, + (unsigned long) max_ins_size1, + (unsigned long) max_ins_size2); + } else { + success = TRUE; + } + +func_exit: +#ifdef UNIV_ZIP_DEBUG + ut_a(!page_zip || page_zip_validate(page_zip, page)); +#endif /* UNIV_ZIP_DEBUG */ + buf_block_free(temp_block); + + /* Restore logging mode */ + mtr_set_log_mode(mtr, log_mode); + + return(success); +} + +/***************************************************************** +Reorganizes an index page. +IMPORTANT: if btr_page_reorganize() is invoked on a compressed leaf +page of a non-clustered index, the caller must update the insert +buffer free bits in the same mini-transaction in such a way that the +modification will be redo-logged. */ +UNIV_INTERN +ibool +btr_page_reorganize( +/*================*/ + /* out: TRUE on success, FALSE on failure */ + buf_block_t* block, /* in: page to be reorganized */ + dict_index_t* index, /* in: record descriptor */ + mtr_t* mtr) /* in: mtr */ +{ + return(btr_page_reorganize_low(FALSE, block, index, mtr)); +} + +/*************************************************************** +Parses a redo log record of reorganizing a page. */ +UNIV_INTERN +byte* +btr_parse_page_reorganize( +/*======================*/ + /* out: end of log record or NULL */ + byte* ptr, /* in: buffer */ + byte* end_ptr __attribute__((unused)), + /* in: buffer end */ + dict_index_t* index, /* in: record descriptor */ + buf_block_t* block, /* in: page to be reorganized, or NULL */ + mtr_t* mtr) /* in: mtr or NULL */ +{ + ut_ad(ptr && end_ptr); + + /* The record is empty, except for the record initial part */ + + if (UNIV_LIKELY(block != NULL)) { + btr_page_reorganize_low(TRUE, block, index, mtr); + } + + return(ptr); +} + +/***************************************************************** +Empties an index page. @see btr_page_create().*/ +static +void +btr_page_empty( +/*===========*/ + buf_block_t* block, /* in: page to be emptied */ + page_zip_des_t* page_zip,/* out: compressed page, or NULL */ + dict_index_t* index, /* in: index of the page */ + ulint level, /* in: the B-tree level of the page */ + mtr_t* mtr) /* in: mtr */ +{ + page_t* page = buf_block_get_frame(block); + + ut_ad(mtr_memo_contains(mtr, block, MTR_MEMO_PAGE_X_FIX)); + ut_ad(page_zip == buf_block_get_page_zip(block)); +#ifdef UNIV_ZIP_DEBUG + ut_a(!page_zip || page_zip_validate(page_zip, page)); +#endif /* UNIV_ZIP_DEBUG */ + + btr_search_drop_page_hash_index(block); + + /* Recreate the page: note that global data on page (possible + segment headers, next page-field, etc.) is preserved intact */ + + if (UNIV_LIKELY_NULL(page_zip)) { + page_create_zip(block, index, level, mtr); + } else { + page_create(block, mtr, dict_table_is_comp(index->table)); + btr_page_set_level(page, NULL, level, mtr); + } + + block->check_index_page_at_flush = TRUE; +} + +/***************************************************************** +Makes tree one level higher by splitting the root, and inserts +the tuple. It is assumed that mtr contains an x-latch on the tree. +NOTE that the operation of this function must always succeed, +we cannot reverse it: therefore enough free disk space must be +guaranteed to be available before this function is called. */ +UNIV_INTERN +rec_t* +btr_root_raise_and_insert( +/*======================*/ + /* out: inserted record */ + btr_cur_t* cursor, /* in: cursor at which to insert: must be + on the root page; when the function returns, + the cursor is positioned on the predecessor + of the inserted record */ + const dtuple_t* tuple, /* in: tuple to insert */ + ulint n_ext, /* in: number of externally stored columns */ + mtr_t* mtr) /* in: mtr */ +{ + dict_index_t* index; + page_t* root; + page_t* new_page; + ulint new_page_no; + rec_t* rec; + mem_heap_t* heap; + dtuple_t* node_ptr; + ulint level; + rec_t* node_ptr_rec; + page_cur_t* page_cursor; + page_zip_des_t* root_page_zip; + page_zip_des_t* new_page_zip; + buf_block_t* root_block; + buf_block_t* new_block; + + root = btr_cur_get_page(cursor); + root_block = btr_cur_get_block(cursor); + root_page_zip = buf_block_get_page_zip(root_block); +#ifdef UNIV_ZIP_DEBUG + ut_a(!root_page_zip || page_zip_validate(root_page_zip, root)); +#endif /* UNIV_ZIP_DEBUG */ + index = btr_cur_get_index(cursor); +#ifdef UNIV_BTR_DEBUG + if (!dict_index_is_ibuf(index)) { + ulint space = dict_index_get_space(index); + + ut_a(btr_root_fseg_validate(FIL_PAGE_DATA + PAGE_BTR_SEG_LEAF + + root, space)); + ut_a(btr_root_fseg_validate(FIL_PAGE_DATA + PAGE_BTR_SEG_TOP + + root, space)); + } + + ut_a(dict_index_get_page(index) == page_get_page_no(root)); +#endif /* UNIV_BTR_DEBUG */ + ut_ad(mtr_memo_contains(mtr, dict_index_get_lock(index), + MTR_MEMO_X_LOCK)); + ut_ad(mtr_memo_contains(mtr, root_block, MTR_MEMO_PAGE_X_FIX)); + + /* Allocate a new page to the tree. Root splitting is done by first + moving the root records to the new page, emptying the root, putting + a node pointer to the new page, and then splitting the new page. */ + + level = btr_page_get_level(root, mtr); + + new_block = btr_page_alloc(index, 0, FSP_NO_DIR, level, mtr); + new_page = buf_block_get_frame(new_block); + new_page_zip = buf_block_get_page_zip(new_block); + ut_a(!new_page_zip == !root_page_zip); + ut_a(!new_page_zip + || page_zip_get_size(new_page_zip) + == page_zip_get_size(root_page_zip)); + + btr_page_create(new_block, new_page_zip, index, level, mtr); + + /* Set the next node and previous node fields of new page */ + btr_page_set_next(new_page, new_page_zip, FIL_NULL, mtr); + btr_page_set_prev(new_page, new_page_zip, FIL_NULL, mtr); + + /* Copy the records from root to the new page one by one. */ + + if (0 +#ifdef UNIV_ZIP_COPY + || new_page_zip +#endif /* UNIV_ZIP_COPY */ + || UNIV_UNLIKELY + (!page_copy_rec_list_end(new_block, root_block, + page_get_infimum_rec(root), + index, mtr))) { + ut_a(new_page_zip); + + /* Copy the page byte for byte. */ + page_zip_copy_recs(new_page_zip, new_page, + root_page_zip, root, index, mtr); + + /* Update the lock table and possible hash index. */ + + lock_move_rec_list_end(new_block, root_block, + page_get_infimum_rec(root)); + + btr_search_move_or_delete_hash_entries(new_block, root_block, + index); + } + + /* If this is a pessimistic insert which is actually done to + perform a pessimistic update then we have stored the lock + information of the record to be inserted on the infimum of the + root page: we cannot discard the lock structs on the root page */ + + lock_update_root_raise(new_block, root_block); + + /* Create a memory heap where the node pointer is stored */ + heap = mem_heap_create(100); + + rec = page_rec_get_next(page_get_infimum_rec(new_page)); + new_page_no = buf_block_get_page_no(new_block); + + /* Build the node pointer (= node key and page address) for the + child */ + + node_ptr = dict_index_build_node_ptr(index, rec, new_page_no, heap, + level); + /* The node pointer must be marked as the predefined minimum record, + as there is no lower alphabetical limit to records in the leftmost + node of a level: */ + dtuple_set_info_bits(node_ptr, + dtuple_get_info_bits(node_ptr) + | REC_INFO_MIN_REC_FLAG); + + /* Rebuild the root page to get free space */ + btr_page_empty(root_block, root_page_zip, index, level + 1, mtr); + + /* Set the next node and previous node fields, although + they should already have been set. The previous node field + must be FIL_NULL if root_page_zip != NULL, because the + REC_INFO_MIN_REC_FLAG (of the first user record) will be + set if and only if btr_page_get_prev() == FIL_NULL. */ + btr_page_set_next(root, root_page_zip, FIL_NULL, mtr); + btr_page_set_prev(root, root_page_zip, FIL_NULL, mtr); + + page_cursor = btr_cur_get_page_cur(cursor); + + /* Insert node pointer to the root */ + + page_cur_set_before_first(root_block, page_cursor); + + node_ptr_rec = page_cur_tuple_insert(page_cursor, node_ptr, + index, 0, mtr); + + /* The root page should only contain the node pointer + to new_page at this point. Thus, the data should fit. */ + ut_a(node_ptr_rec); + + /* Free the memory heap */ + mem_heap_free(heap); + + /* We play safe and reset the free bits for the new page */ + +#if 0 + fprintf(stderr, "Root raise new page no %lu\n", new_page_no); +#endif + + if (!dict_index_is_clust(index)) { + ibuf_reset_free_bits(new_block); + } + + /* Reposition the cursor to the child node */ + page_cur_search(new_block, index, tuple, + PAGE_CUR_LE, page_cursor); + + /* Split the child and insert tuple */ + return(btr_page_split_and_insert(cursor, tuple, n_ext, mtr)); +} + +/***************************************************************** +Decides if the page should be split at the convergence point of inserts +converging to the left. */ +UNIV_INTERN +ibool +btr_page_get_split_rec_to_left( +/*===========================*/ + /* out: TRUE if split recommended */ + btr_cur_t* cursor, /* in: cursor at which to insert */ + rec_t** split_rec) /* out: if split recommended, + the first record on upper half page, + or NULL if tuple to be inserted should + be first */ +{ + page_t* page; + rec_t* insert_point; + rec_t* infimum; + + page = btr_cur_get_page(cursor); + insert_point = btr_cur_get_rec(cursor); + + if (page_header_get_ptr(page, PAGE_LAST_INSERT) + == page_rec_get_next(insert_point)) { + + infimum = page_get_infimum_rec(page); + + /* If the convergence is in the middle of a page, include also + the record immediately before the new insert to the upper + page. Otherwise, we could repeatedly move from page to page + lots of records smaller than the convergence point. */ + + if (infimum != insert_point + && page_rec_get_next(infimum) != insert_point) { + + *split_rec = insert_point; + } else { + *split_rec = page_rec_get_next(insert_point); + } + + return(TRUE); + } + + return(FALSE); +} + +/***************************************************************** +Decides if the page should be split at the convergence point of inserts +converging to the right. */ +UNIV_INTERN +ibool +btr_page_get_split_rec_to_right( +/*============================*/ + /* out: TRUE if split recommended */ + btr_cur_t* cursor, /* in: cursor at which to insert */ + rec_t** split_rec) /* out: if split recommended, + the first record on upper half page, + or NULL if tuple to be inserted should + be first */ +{ + page_t* page; + rec_t* insert_point; + + page = btr_cur_get_page(cursor); + insert_point = btr_cur_get_rec(cursor); + + /* We use eager heuristics: if the new insert would be right after + the previous insert on the same page, we assume that there is a + pattern of sequential inserts here. */ + + if (UNIV_LIKELY(page_header_get_ptr(page, PAGE_LAST_INSERT) + == insert_point)) { + + rec_t* next_rec; + + next_rec = page_rec_get_next(insert_point); + + if (page_rec_is_supremum(next_rec)) { +split_at_new: + /* Split at the new record to insert */ + *split_rec = NULL; + } else { + rec_t* next_next_rec = page_rec_get_next(next_rec); + if (page_rec_is_supremum(next_next_rec)) { + + goto split_at_new; + } + + /* If there are >= 2 user records up from the insert + point, split all but 1 off. We want to keep one because + then sequential inserts can use the adaptive hash + index, as they can do the necessary checks of the right + search position just by looking at the records on this + page. */ + + *split_rec = next_next_rec; + } + + return(TRUE); + } + + return(FALSE); +} + +/***************************************************************** +Calculates a split record such that the tuple will certainly fit on +its half-page when the split is performed. We assume in this function +only that the cursor page has at least one user record. */ +static +rec_t* +btr_page_get_sure_split_rec( +/*========================*/ + /* out: split record, or NULL if tuple + will be the first record on upper half-page */ + btr_cur_t* cursor, /* in: cursor at which insert should be made */ + const dtuple_t* tuple, /* in: tuple to insert */ + ulint n_ext) /* in: number of externally stored columns */ +{ + page_t* page; + page_zip_des_t* page_zip; + ulint insert_size; + ulint free_space; + ulint total_data; + ulint total_n_recs; + ulint total_space; + ulint incl_data; + rec_t* ins_rec; + rec_t* rec; + rec_t* next_rec; + ulint n; + mem_heap_t* heap; + ulint* offsets; + + page = btr_cur_get_page(cursor); + + insert_size = rec_get_converted_size(cursor->index, tuple, n_ext); + free_space = page_get_free_space_of_empty(page_is_comp(page)); + + page_zip = btr_cur_get_page_zip(cursor); + if (UNIV_LIKELY_NULL(page_zip)) { + /* Estimate the free space of an empty compressed page. */ + ulint free_space_zip = page_zip_empty_size( + cursor->index->n_fields, + page_zip_get_size(page_zip)); + + if (UNIV_LIKELY(free_space > (ulint) free_space_zip)) { + free_space = (ulint) free_space_zip; + } + } + + /* free_space is now the free space of a created new page */ + + total_data = page_get_data_size(page) + insert_size; + total_n_recs = page_get_n_recs(page) + 1; + ut_ad(total_n_recs >= 2); + total_space = total_data + page_dir_calc_reserved_space(total_n_recs); + + n = 0; + incl_data = 0; + ins_rec = btr_cur_get_rec(cursor); + rec = page_get_infimum_rec(page); + + heap = NULL; + offsets = NULL; + + /* We start to include records to the left half, and when the + space reserved by them exceeds half of total_space, then if + the included records fit on the left page, they will be put there + if something was left over also for the right page, + otherwise the last included record will be the first on the right + half page */ + + do { + /* Decide the next record to include */ + if (rec == ins_rec) { + rec = NULL; /* NULL denotes that tuple is + now included */ + } else if (rec == NULL) { + rec = page_rec_get_next(ins_rec); + } else { + rec = page_rec_get_next(rec); + } + + if (rec == NULL) { + /* Include tuple */ + incl_data += insert_size; + } else { + offsets = rec_get_offsets(rec, cursor->index, + offsets, ULINT_UNDEFINED, + &heap); + incl_data += rec_offs_size(offsets); + } + + n++; + } while (incl_data + page_dir_calc_reserved_space(n) + < total_space / 2); + + if (incl_data + page_dir_calc_reserved_space(n) <= free_space) { + /* The next record will be the first on + the right half page if it is not the + supremum record of page */ + + if (rec == ins_rec) { + rec = NULL; + + goto func_exit; + } else if (rec == NULL) { + next_rec = page_rec_get_next(ins_rec); + } else { + next_rec = page_rec_get_next(rec); + } + ut_ad(next_rec); + if (!page_rec_is_supremum(next_rec)) { + rec = next_rec; + } + } + +func_exit: + if (UNIV_LIKELY_NULL(heap)) { + mem_heap_free(heap); + } + return(rec); +} + +/***************************************************************** +Returns TRUE if the insert fits on the appropriate half-page with the +chosen split_rec. */ +static +ibool +btr_page_insert_fits( +/*=================*/ + /* out: TRUE if fits */ + btr_cur_t* cursor, /* in: cursor at which insert + should be made */ + const rec_t* split_rec,/* in: suggestion for first record + on upper half-page, or NULL if + tuple to be inserted should be first */ + const ulint* offsets,/* in: rec_get_offsets( + split_rec, cursor->index) */ + const dtuple_t* tuple, /* in: tuple to insert */ + ulint n_ext, /* in: number of externally stored columns */ + mem_heap_t* heap) /* in: temporary memory heap */ +{ + page_t* page; + ulint insert_size; + ulint free_space; + ulint total_data; + ulint total_n_recs; + const rec_t* rec; + const rec_t* end_rec; + ulint* offs; + + page = btr_cur_get_page(cursor); + + ut_ad(!split_rec == !offsets); + ut_ad(!offsets + || !page_is_comp(page) == !rec_offs_comp(offsets)); + ut_ad(!offsets + || rec_offs_validate(split_rec, cursor->index, offsets)); + + insert_size = rec_get_converted_size(cursor->index, tuple, n_ext); + free_space = page_get_free_space_of_empty(page_is_comp(page)); + + /* free_space is now the free space of a created new page */ + + total_data = page_get_data_size(page) + insert_size; + total_n_recs = page_get_n_recs(page) + 1; + + /* We determine which records (from rec to end_rec, not including + end_rec) will end up on the other half page from tuple when it is + inserted. */ + + if (split_rec == NULL) { + rec = page_rec_get_next(page_get_infimum_rec(page)); + end_rec = page_rec_get_next(btr_cur_get_rec(cursor)); + + } else if (cmp_dtuple_rec(tuple, split_rec, offsets) >= 0) { + + rec = page_rec_get_next(page_get_infimum_rec(page)); + end_rec = split_rec; + } else { + rec = split_rec; + end_rec = page_get_supremum_rec(page); + } + + if (total_data + page_dir_calc_reserved_space(total_n_recs) + <= free_space) { + + /* Ok, there will be enough available space on the + half page where the tuple is inserted */ + + return(TRUE); + } + + offs = NULL; + + while (rec != end_rec) { + /* In this loop we calculate the amount of reserved + space after rec is removed from page. */ + + offs = rec_get_offsets(rec, cursor->index, offs, + ULINT_UNDEFINED, &heap); + + total_data -= rec_offs_size(offs); + total_n_recs--; + + if (total_data + page_dir_calc_reserved_space(total_n_recs) + <= free_space) { + + /* Ok, there will be enough available space on the + half page where the tuple is inserted */ + + return(TRUE); + } + + rec = page_rec_get_next_const(rec); + } + + return(FALSE); +} + +/*********************************************************** +Inserts a data tuple to a tree on a non-leaf level. It is assumed +that mtr holds an x-latch on the tree. */ +UNIV_INTERN +void +btr_insert_on_non_leaf_level( +/*=========================*/ + dict_index_t* index, /* in: index */ + ulint level, /* in: level, must be > 0 */ + dtuple_t* tuple, /* in: the record to be inserted */ + mtr_t* mtr) /* in: mtr */ +{ + big_rec_t* dummy_big_rec; + btr_cur_t cursor; + ulint err; + rec_t* rec; + + ut_ad(level > 0); + + btr_cur_search_to_nth_level(index, level, tuple, PAGE_CUR_LE, + BTR_CONT_MODIFY_TREE, + &cursor, 0, mtr); + + err = btr_cur_pessimistic_insert(BTR_NO_LOCKING_FLAG + | BTR_KEEP_SYS_FLAG + | BTR_NO_UNDO_LOG_FLAG, + &cursor, tuple, &rec, + &dummy_big_rec, 0, NULL, mtr); + ut_a(err == DB_SUCCESS); +} + +/****************************************************************** +Attaches the halves of an index page on the appropriate level in an +index tree. */ +static +void +btr_attach_half_pages( +/*==================*/ + dict_index_t* index, /* in: the index tree */ + buf_block_t* block, /* in/out: page to be split */ + rec_t* split_rec, /* in: first record on upper + half page */ + buf_block_t* new_block, /* in/out: the new half page */ + ulint direction, /* in: FSP_UP or FSP_DOWN */ + mtr_t* mtr) /* in: mtr */ +{ + ulint space; + ulint zip_size; + ulint prev_page_no; + ulint next_page_no; + ulint level; + page_t* page = buf_block_get_frame(block); + page_t* lower_page; + page_t* upper_page; + ulint lower_page_no; + ulint upper_page_no; + page_zip_des_t* lower_page_zip; + page_zip_des_t* upper_page_zip; + dtuple_t* node_ptr_upper; + mem_heap_t* heap; + + ut_ad(mtr_memo_contains(mtr, block, MTR_MEMO_PAGE_X_FIX)); + ut_ad(mtr_memo_contains(mtr, new_block, MTR_MEMO_PAGE_X_FIX)); + + /* Create a memory heap where the data tuple is stored */ + heap = mem_heap_create(1024); + + /* Based on split direction, decide upper and lower pages */ + if (direction == FSP_DOWN) { + + btr_cur_t cursor; + ulint* offsets; + + lower_page = buf_block_get_frame(new_block); + lower_page_no = buf_block_get_page_no(new_block); + lower_page_zip = buf_block_get_page_zip(new_block); + upper_page = buf_block_get_frame(block); + upper_page_no = buf_block_get_page_no(block); + upper_page_zip = buf_block_get_page_zip(block); + + /* Look up the index for the node pointer to page */ + offsets = btr_page_get_father_block(NULL, heap, index, + block, mtr, &cursor); + + /* Replace the address of the old child node (= page) with the + address of the new lower half */ + + btr_node_ptr_set_child_page_no( + btr_cur_get_rec(&cursor), + btr_cur_get_page_zip(&cursor), + offsets, lower_page_no, mtr); + mem_heap_empty(heap); + } else { + lower_page = buf_block_get_frame(block); + lower_page_no = buf_block_get_page_no(block); + lower_page_zip = buf_block_get_page_zip(block); + upper_page = buf_block_get_frame(new_block); + upper_page_no = buf_block_get_page_no(new_block); + upper_page_zip = buf_block_get_page_zip(new_block); + } + + /* Get the level of the split pages */ + level = btr_page_get_level(buf_block_get_frame(block), mtr); + ut_ad(level + == btr_page_get_level(buf_block_get_frame(new_block), mtr)); + + /* Build the node pointer (= node key and page address) for the upper + half */ + + node_ptr_upper = dict_index_build_node_ptr(index, split_rec, + upper_page_no, heap, level); + + /* Insert it next to the pointer to the lower half. Note that this + may generate recursion leading to a split on the higher level. */ + + btr_insert_on_non_leaf_level(index, level + 1, node_ptr_upper, mtr); + + /* Free the memory heap */ + mem_heap_free(heap); + + /* Get the previous and next pages of page */ + + prev_page_no = btr_page_get_prev(page, mtr); + next_page_no = btr_page_get_next(page, mtr); + space = buf_block_get_space(block); + zip_size = buf_block_get_zip_size(block); + + /* Update page links of the level */ + + if (prev_page_no != FIL_NULL) { + buf_block_t* prev_block = btr_block_get(space, zip_size, + prev_page_no, + RW_X_LATCH, mtr); +#ifdef UNIV_BTR_DEBUG + ut_a(page_is_comp(prev_block->frame) == page_is_comp(page)); + ut_a(btr_page_get_next(prev_block->frame, mtr) + == buf_block_get_page_no(block)); +#endif /* UNIV_BTR_DEBUG */ + + btr_page_set_next(buf_block_get_frame(prev_block), + buf_block_get_page_zip(prev_block), + lower_page_no, mtr); + } + + if (next_page_no != FIL_NULL) { + buf_block_t* next_block = btr_block_get(space, zip_size, + next_page_no, + RW_X_LATCH, mtr); +#ifdef UNIV_BTR_DEBUG + ut_a(page_is_comp(next_block->frame) == page_is_comp(page)); + ut_a(btr_page_get_prev(next_block->frame, mtr) + == page_get_page_no(page)); +#endif /* UNIV_BTR_DEBUG */ + + btr_page_set_prev(buf_block_get_frame(next_block), + buf_block_get_page_zip(next_block), + upper_page_no, mtr); + } + + btr_page_set_prev(lower_page, lower_page_zip, prev_page_no, mtr); + btr_page_set_next(lower_page, lower_page_zip, upper_page_no, mtr); + + btr_page_set_prev(upper_page, upper_page_zip, lower_page_no, mtr); + btr_page_set_next(upper_page, upper_page_zip, next_page_no, mtr); +} + +/***************************************************************** +Splits an index page to halves and inserts the tuple. It is assumed +that mtr holds an x-latch to the index tree. NOTE: the tree x-latch +is released within this function! NOTE that the operation of this +function must always succeed, we cannot reverse it: therefore +enough free disk space must be guaranteed to be available before +this function is called. */ +UNIV_INTERN +rec_t* +btr_page_split_and_insert( +/*======================*/ + /* out: inserted record; NOTE: the tree + x-latch is released! NOTE: 2 free disk + pages must be available! */ + btr_cur_t* cursor, /* in: cursor at which to insert; when the + function returns, the cursor is positioned + on the predecessor of the inserted record */ + const dtuple_t* tuple, /* in: tuple to insert */ + ulint n_ext, /* in: number of externally stored columns */ + mtr_t* mtr) /* in: mtr */ +{ + buf_block_t* block; + page_t* page; + page_zip_des_t* page_zip; + ulint page_no; + byte direction; + ulint hint_page_no; + buf_block_t* new_block; + page_t* new_page; + page_zip_des_t* new_page_zip; + rec_t* split_rec; + buf_block_t* left_block; + buf_block_t* right_block; + buf_block_t* insert_block; + page_t* insert_page; + page_cur_t* page_cursor; + rec_t* first_rec; + byte* buf = 0; /* remove warning */ + rec_t* move_limit; + ibool insert_will_fit; + ibool insert_left; + ulint n_iterations = 0; + rec_t* rec; + mem_heap_t* heap; + ulint n_uniq; + ulint* offsets; + + heap = mem_heap_create(1024); + n_uniq = dict_index_get_n_unique_in_tree(cursor->index); +func_start: + mem_heap_empty(heap); + offsets = NULL; + + ut_ad(mtr_memo_contains(mtr, dict_index_get_lock(cursor->index), + MTR_MEMO_X_LOCK)); +#ifdef UNIV_SYNC_DEBUG + ut_ad(rw_lock_own(dict_index_get_lock(cursor->index), RW_LOCK_EX)); +#endif /* UNIV_SYNC_DEBUG */ + + block = btr_cur_get_block(cursor); + page = buf_block_get_frame(block); + page_zip = buf_block_get_page_zip(block); + + ut_ad(mtr_memo_contains(mtr, block, MTR_MEMO_PAGE_X_FIX)); + ut_ad(page_get_n_recs(page) >= 1); + + page_no = buf_block_get_page_no(block); + + /* 1. Decide the split record; split_rec == NULL means that the + tuple to be inserted should be the first record on the upper + half-page */ + + if (n_iterations > 0) { + direction = FSP_UP; + hint_page_no = page_no + 1; + split_rec = btr_page_get_sure_split_rec(cursor, tuple, n_ext); + + } else if (btr_page_get_split_rec_to_right(cursor, &split_rec)) { + direction = FSP_UP; + hint_page_no = page_no + 1; + + } else if (btr_page_get_split_rec_to_left(cursor, &split_rec)) { + direction = FSP_DOWN; + hint_page_no = page_no - 1; + } else { + direction = FSP_UP; + hint_page_no = page_no + 1; + + if (page_get_n_recs(page) == 1) { + page_cur_t pcur; + + /* There is only one record in the index page + therefore we can't split the node in the middle + by default. We need to determine whether the + new record will be inserted to the left or right. */ + + /* Read the first (and only) record in the page. */ + page_cur_set_before_first(block, &pcur); + page_cur_move_to_next(&pcur); + first_rec = page_cur_get_rec(&pcur); + + offsets = rec_get_offsets( + first_rec, cursor->index, offsets, + n_uniq, &heap); + + /* If the new record is less than the existing record + the the split in the middle will copy the existing + record to the new node. */ + if (cmp_dtuple_rec(tuple, first_rec, offsets) < 0) { + split_rec = page_get_middle_rec(page); + } else { + split_rec = NULL; + } + } else { + split_rec = page_get_middle_rec(page); + } + } + + /* 2. Allocate a new page to the index */ + new_block = btr_page_alloc(cursor->index, hint_page_no, direction, + btr_page_get_level(page, mtr), mtr); + new_page = buf_block_get_frame(new_block); + new_page_zip = buf_block_get_page_zip(new_block); + btr_page_create(new_block, new_page_zip, cursor->index, + btr_page_get_level(page, mtr), mtr); + + /* 3. Calculate the first record on the upper half-page, and the + first record (move_limit) on original page which ends up on the + upper half */ + + if (split_rec) { + first_rec = move_limit = split_rec; + + offsets = rec_get_offsets(split_rec, cursor->index, offsets, + n_uniq, &heap); + + insert_left = cmp_dtuple_rec(tuple, split_rec, offsets) < 0; + + if (UNIV_UNLIKELY(!insert_left && new_page_zip + && n_iterations > 0)) { + /* If a compressed page has already been split, + avoid further splits by inserting the record + to an empty page. */ + split_rec = NULL; + goto insert_right; + } + } else { +insert_right: + insert_left = FALSE; + buf = mem_alloc(rec_get_converted_size(cursor->index, + tuple, n_ext)); + + first_rec = rec_convert_dtuple_to_rec(buf, cursor->index, + tuple, n_ext); + move_limit = page_rec_get_next(btr_cur_get_rec(cursor)); + } + + /* 4. Do first the modifications in the tree structure */ + + btr_attach_half_pages(cursor->index, block, + first_rec, new_block, direction, mtr); + + /* If the split is made on the leaf level and the insert will fit + on the appropriate half-page, we may release the tree x-latch. + We can then move the records after releasing the tree latch, + thus reducing the tree latch contention. */ + + if (split_rec) { + insert_will_fit = !new_page_zip + && btr_page_insert_fits(cursor, split_rec, + offsets, tuple, n_ext, heap); + } else { + mem_free(buf); + insert_will_fit = !new_page_zip + && btr_page_insert_fits(cursor, NULL, + NULL, tuple, n_ext, heap); + } + + if (insert_will_fit && page_is_leaf(page)) { + + mtr_memo_release(mtr, dict_index_get_lock(cursor->index), + MTR_MEMO_X_LOCK); + } + + /* 5. Move then the records to the new page */ + if (direction == FSP_DOWN) { + /* fputs("Split left\n", stderr); */ + + if (0 +#ifdef UNIV_ZIP_COPY + || page_zip +#endif /* UNIV_ZIP_COPY */ + || UNIV_UNLIKELY + (!page_move_rec_list_start(new_block, block, move_limit, + cursor->index, mtr))) { + /* For some reason, compressing new_page failed, + even though it should contain fewer records than + the original page. Copy the page byte for byte + and then delete the records from both pages + as appropriate. Deleting will always succeed. */ + ut_a(new_page_zip); + + page_zip_copy_recs(new_page_zip, new_page, + page_zip, page, cursor->index, mtr); + page_delete_rec_list_end(move_limit - page + new_page, + new_block, cursor->index, + ULINT_UNDEFINED, + ULINT_UNDEFINED, mtr); + + /* Update the lock table and possible hash index. */ + + lock_move_rec_list_start( + new_block, block, move_limit, + new_page + PAGE_NEW_INFIMUM); + + btr_search_move_or_delete_hash_entries( + new_block, block, cursor->index); + + /* Delete the records from the source page. */ + + page_delete_rec_list_start(move_limit, block, + cursor->index, mtr); + } + + left_block = new_block; + right_block = block; + + lock_update_split_left(right_block, left_block); + } else { + /* fputs("Split right\n", stderr); */ + + if (0 +#ifdef UNIV_ZIP_COPY + || page_zip +#endif /* UNIV_ZIP_COPY */ + || UNIV_UNLIKELY + (!page_move_rec_list_end(new_block, block, move_limit, + cursor->index, mtr))) { + /* For some reason, compressing new_page failed, + even though it should contain fewer records than + the original page. Copy the page byte for byte + and then delete the records from both pages + as appropriate. Deleting will always succeed. */ + ut_a(new_page_zip); + + page_zip_copy_recs(new_page_zip, new_page, + page_zip, page, cursor->index, mtr); + page_delete_rec_list_start(move_limit - page + + new_page, new_block, + cursor->index, mtr); + + /* Update the lock table and possible hash index. */ + + lock_move_rec_list_end(new_block, block, move_limit); + + btr_search_move_or_delete_hash_entries( + new_block, block, cursor->index); + + /* Delete the records from the source page. */ + + page_delete_rec_list_end(move_limit, block, + cursor->index, + ULINT_UNDEFINED, + ULINT_UNDEFINED, mtr); + } + + left_block = block; + right_block = new_block; + + lock_update_split_right(right_block, left_block); + } + +#ifdef UNIV_ZIP_DEBUG + if (UNIV_LIKELY_NULL(page_zip)) { + ut_a(page_zip_validate(page_zip, page)); + ut_a(page_zip_validate(new_page_zip, new_page)); + } +#endif /* UNIV_ZIP_DEBUG */ + + /* At this point, split_rec, move_limit and first_rec may point + to garbage on the old page. */ + + /* 6. The split and the tree modification is now completed. Decide the + page where the tuple should be inserted */ + + if (insert_left) { + insert_block = left_block; + } else { + insert_block = right_block; + } + + insert_page = buf_block_get_frame(insert_block); + + /* 7. Reposition the cursor for insert and try insertion */ + page_cursor = btr_cur_get_page_cur(cursor); + + page_cur_search(insert_block, cursor->index, tuple, + PAGE_CUR_LE, page_cursor); + + rec = page_cur_tuple_insert(page_cursor, tuple, + cursor->index, n_ext, mtr); + +#ifdef UNIV_ZIP_DEBUG + { + page_zip_des_t* insert_page_zip + = buf_block_get_page_zip(insert_block); + ut_a(!insert_page_zip + || page_zip_validate(insert_page_zip, insert_page)); + } +#endif /* UNIV_ZIP_DEBUG */ + + if (UNIV_LIKELY(rec != NULL)) { + + goto func_exit; + } + + /* 8. If insert did not fit, try page reorganization */ + + if (UNIV_UNLIKELY + (!btr_page_reorganize(insert_block, cursor->index, mtr))) { + + goto insert_failed; + } + + page_cur_search(insert_block, cursor->index, tuple, + PAGE_CUR_LE, page_cursor); + rec = page_cur_tuple_insert(page_cursor, tuple, cursor->index, + n_ext, mtr); + + if (UNIV_UNLIKELY(rec == NULL)) { + /* The insert did not fit on the page: loop back to the + start of the function for a new split */ +insert_failed: + /* We play safe and reset the free bits for new_page */ + if (!dict_index_is_clust(cursor->index)) { + ibuf_reset_free_bits(new_block); + } + + /* fprintf(stderr, "Split second round %lu\n", + page_get_page_no(page)); */ + n_iterations++; + ut_ad(n_iterations < 2 + || buf_block_get_page_zip(insert_block)); + ut_ad(!insert_will_fit); + + goto func_start; + } + +func_exit: + /* Insert fit on the page: update the free bits for the + left and right pages in the same mtr */ + + if (!dict_index_is_clust(cursor->index) && page_is_leaf(page)) { + ibuf_update_free_bits_for_two_pages_low( + buf_block_get_zip_size(left_block), + left_block, right_block, mtr); + } + +#if 0 + fprintf(stderr, "Split and insert done %lu %lu\n", + buf_block_get_page_no(left_block), + buf_block_get_page_no(right_block)); +#endif + + ut_ad(page_validate(buf_block_get_frame(left_block), cursor->index)); + ut_ad(page_validate(buf_block_get_frame(right_block), cursor->index)); + + mem_heap_free(heap); + return(rec); +} + +/***************************************************************** +Removes a page from the level list of pages. */ +static +void +btr_level_list_remove( +/*==================*/ + ulint space, /* in: space where removed */ + ulint zip_size,/* in: compressed page size in bytes + or 0 for uncompressed pages */ + page_t* page, /* in: page to remove */ + mtr_t* mtr) /* in: mtr */ +{ + ulint prev_page_no; + ulint next_page_no; + + ut_ad(page && mtr); + ut_ad(mtr_memo_contains_page(mtr, page, MTR_MEMO_PAGE_X_FIX)); + ut_ad(space == page_get_space_id(page)); + /* Get the previous and next page numbers of page */ + + prev_page_no = btr_page_get_prev(page, mtr); + next_page_no = btr_page_get_next(page, mtr); + + /* Update page links of the level */ + + if (prev_page_no != FIL_NULL) { + buf_block_t* prev_block + = btr_block_get(space, zip_size, prev_page_no, + RW_X_LATCH, mtr); + page_t* prev_page + = buf_block_get_frame(prev_block); +#ifdef UNIV_BTR_DEBUG + ut_a(page_is_comp(prev_page) == page_is_comp(page)); + ut_a(btr_page_get_next(prev_page, mtr) + == page_get_page_no(page)); +#endif /* UNIV_BTR_DEBUG */ + + btr_page_set_next(prev_page, + buf_block_get_page_zip(prev_block), + next_page_no, mtr); + } + + if (next_page_no != FIL_NULL) { + buf_block_t* next_block + = btr_block_get(space, zip_size, next_page_no, + RW_X_LATCH, mtr); + page_t* next_page + = buf_block_get_frame(next_block); +#ifdef UNIV_BTR_DEBUG + ut_a(page_is_comp(next_page) == page_is_comp(page)); + ut_a(btr_page_get_prev(next_page, mtr) + == page_get_page_no(page)); +#endif /* UNIV_BTR_DEBUG */ + + btr_page_set_prev(next_page, + buf_block_get_page_zip(next_block), + prev_page_no, mtr); + } +} + +/******************************************************************** +Writes the redo log record for setting an index record as the predefined +minimum record. */ +UNIV_INLINE +void +btr_set_min_rec_mark_log( +/*=====================*/ + rec_t* rec, /* in: record */ + byte type, /* in: MLOG_COMP_REC_MIN_MARK or MLOG_REC_MIN_MARK */ + mtr_t* mtr) /* in: mtr */ +{ + mlog_write_initial_log_record(rec, type, mtr); + + /* Write rec offset as a 2-byte ulint */ + mlog_catenate_ulint(mtr, page_offset(rec), MLOG_2BYTES); +} + +/******************************************************************** +Parses the redo log record for setting an index record as the predefined +minimum record. */ +UNIV_INTERN +byte* +btr_parse_set_min_rec_mark( +/*=======================*/ + /* out: end of log record or NULL */ + byte* ptr, /* in: buffer */ + byte* end_ptr,/* in: buffer end */ + ulint comp, /* in: nonzero=compact page format */ + page_t* page, /* in: page or NULL */ + mtr_t* mtr) /* in: mtr or NULL */ +{ + rec_t* rec; + + if (end_ptr < ptr + 2) { + + return(NULL); + } + + if (page) { + ut_a(!page_is_comp(page) == !comp); + + rec = page + mach_read_from_2(ptr); + + btr_set_min_rec_mark(rec, mtr); + } + + return(ptr + 2); +} + +/******************************************************************** +Sets a record as the predefined minimum record. */ +UNIV_INTERN +void +btr_set_min_rec_mark( +/*=================*/ + rec_t* rec, /* in: record */ + mtr_t* mtr) /* in: mtr */ +{ + ulint info_bits; + + if (UNIV_LIKELY(page_rec_is_comp(rec))) { + info_bits = rec_get_info_bits(rec, TRUE); + + rec_set_info_bits_new(rec, info_bits | REC_INFO_MIN_REC_FLAG); + + btr_set_min_rec_mark_log(rec, MLOG_COMP_REC_MIN_MARK, mtr); + } else { + info_bits = rec_get_info_bits(rec, FALSE); + + rec_set_info_bits_old(rec, info_bits | REC_INFO_MIN_REC_FLAG); + + btr_set_min_rec_mark_log(rec, MLOG_REC_MIN_MARK, mtr); + } +} + +/***************************************************************** +Deletes on the upper level the node pointer to a page. */ +UNIV_INTERN +void +btr_node_ptr_delete( +/*================*/ + dict_index_t* index, /* in: index tree */ + buf_block_t* block, /* in: page whose node pointer is deleted */ + mtr_t* mtr) /* in: mtr */ +{ + btr_cur_t cursor; + ibool compressed; + ulint err; + + ut_ad(mtr_memo_contains(mtr, block, MTR_MEMO_PAGE_X_FIX)); + + /* Delete node pointer on father page */ + btr_page_get_father(index, block, mtr, &cursor); + + compressed = btr_cur_pessimistic_delete(&err, TRUE, &cursor, RB_NONE, + mtr); + ut_a(err == DB_SUCCESS); + + if (!compressed) { + btr_cur_compress_if_useful(&cursor, mtr); + } +} + +/***************************************************************** +If page is the only on its level, this function moves its records to the +father page, thus reducing the tree height. */ +static +void +btr_lift_page_up( +/*=============*/ + dict_index_t* index, /* in: index tree */ + buf_block_t* block, /* in: page which is the only on its level; + must not be empty: use + btr_discard_only_page_on_level if the last + record from the page should be removed */ + mtr_t* mtr) /* in: mtr */ +{ + buf_block_t* father_block; + page_t* father_page; + ulint page_level; + page_zip_des_t* father_page_zip; + page_t* page = buf_block_get_frame(block); + ulint root_page_no; + buf_block_t* blocks[BTR_MAX_LEVELS]; + ulint n_blocks; /* last used index in blocks[] */ + ulint i; + + ut_ad(btr_page_get_prev(page, mtr) == FIL_NULL); + ut_ad(btr_page_get_next(page, mtr) == FIL_NULL); + ut_ad(mtr_memo_contains(mtr, block, MTR_MEMO_PAGE_X_FIX)); + + page_level = btr_page_get_level(page, mtr); + root_page_no = dict_index_get_page(index); + + { + btr_cur_t cursor; + mem_heap_t* heap = mem_heap_create(100); + ulint* offsets; + buf_block_t* b; + + offsets = btr_page_get_father_block(NULL, heap, index, + block, mtr, &cursor); + father_block = btr_cur_get_block(&cursor); + father_page_zip = buf_block_get_page_zip(father_block); + father_page = buf_block_get_frame(father_block); + + n_blocks = 0; + + /* Store all ancestor pages so we can reset their + levels later on. We have to do all the searches on + the tree now because later on, after we've replaced + the first level, the tree is in an inconsistent state + and can not be searched. */ + for (b = father_block; + buf_block_get_page_no(b) != root_page_no; ) { + ut_a(n_blocks < BTR_MAX_LEVELS); + + offsets = btr_page_get_father_block(offsets, heap, + index, b, + mtr, &cursor); + + blocks[n_blocks++] = b = btr_cur_get_block(&cursor); + } + + mem_heap_free(heap); + } + + btr_search_drop_page_hash_index(block); + + /* Make the father empty */ + btr_page_empty(father_block, father_page_zip, index, page_level, mtr); + + /* Copy the records to the father page one by one. */ + if (0 +#ifdef UNIV_ZIP_COPY + || father_page_zip +#endif /* UNIV_ZIP_COPY */ + || UNIV_UNLIKELY + (!page_copy_rec_list_end(father_block, block, + page_get_infimum_rec(page), + index, mtr))) { + const page_zip_des_t* page_zip + = buf_block_get_page_zip(block); + ut_a(father_page_zip); + ut_a(page_zip); + + /* Copy the page byte for byte. */ + page_zip_copy_recs(father_page_zip, father_page, + page_zip, page, index, mtr); + + /* Update the lock table and possible hash index. */ + + lock_move_rec_list_end(father_block, block, + page_get_infimum_rec(page)); + + btr_search_move_or_delete_hash_entries(father_block, block, + index); + } + + lock_update_copy_and_discard(father_block, block); + + /* Go upward to root page, decrementing levels by one. */ + for (i = 0; i < n_blocks; i++, page_level++) { + page_t* page = buf_block_get_frame(blocks[i]); + page_zip_des_t* page_zip= buf_block_get_page_zip(blocks[i]); + + ut_ad(btr_page_get_level(page, mtr) == page_level + 1); + + btr_page_set_level(page, page_zip, page_level, mtr); +#ifdef UNIV_ZIP_DEBUG + ut_a(!page_zip || page_zip_validate(page_zip, page)); +#endif /* UNIV_ZIP_DEBUG */ + } + + /* Free the file page */ + btr_page_free(index, block, mtr); + + /* We play it safe and reset the free bits for the father */ + if (!dict_index_is_clust(index)) { + ibuf_reset_free_bits(father_block); + } + ut_ad(page_validate(father_page, index)); + ut_ad(btr_check_node_ptr(index, father_block, mtr)); +} + +/***************************************************************** +Tries to merge the page first to the left immediate brother if such a +brother exists, and the node pointers to the current page and to the brother +reside on the same page. If the left brother does not satisfy these +conditions, looks at the right brother. If the page is the only one on that +level lifts the records of the page to the father page, thus reducing the +tree height. It is assumed that mtr holds an x-latch on the tree and on the +page. If cursor is on the leaf level, mtr must also hold x-latches to the +brothers, if they exist. */ +UNIV_INTERN +ibool +btr_compress( +/*=========*/ + /* out: TRUE on success */ + btr_cur_t* cursor, /* in: cursor on the page to merge or lift; + the page must not be empty: in record delete + use btr_discard_page if the page would become + empty */ + mtr_t* mtr) /* in: mtr */ +{ + dict_index_t* index; + ulint space; + ulint zip_size; + ulint left_page_no; + ulint right_page_no; + buf_block_t* merge_block; + page_t* merge_page; + page_zip_des_t* merge_page_zip; + ibool is_left; + buf_block_t* block; + page_t* page; + btr_cur_t father_cursor; + mem_heap_t* heap; + ulint* offsets; + ulint data_size; + ulint n_recs; + ulint max_ins_size; + ulint max_ins_size_reorg; + ulint level; + + block = btr_cur_get_block(cursor); + page = btr_cur_get_page(cursor); + index = btr_cur_get_index(cursor); + ut_a((ibool) !!page_is_comp(page) == dict_table_is_comp(index->table)); + + ut_ad(mtr_memo_contains(mtr, dict_index_get_lock(index), + MTR_MEMO_X_LOCK)); + ut_ad(mtr_memo_contains(mtr, block, MTR_MEMO_PAGE_X_FIX)); + level = btr_page_get_level(page, mtr); + space = dict_index_get_space(index); + zip_size = dict_table_zip_size(index->table); + + left_page_no = btr_page_get_prev(page, mtr); + right_page_no = btr_page_get_next(page, mtr); + +#if 0 + fprintf(stderr, "Merge left page %lu right %lu \n", + left_page_no, right_page_no); +#endif + + heap = mem_heap_create(100); + offsets = btr_page_get_father_block(NULL, heap, index, block, mtr, + &father_cursor); + + /* Decide the page to which we try to merge and which will inherit + the locks */ + + is_left = left_page_no != FIL_NULL; + + if (is_left) { + + merge_block = btr_block_get(space, zip_size, left_page_no, + RW_X_LATCH, mtr); + merge_page = buf_block_get_frame(merge_block); +#ifdef UNIV_BTR_DEBUG + ut_a(btr_page_get_next(merge_page, mtr) + == buf_block_get_page_no(block)); +#endif /* UNIV_BTR_DEBUG */ + } else if (right_page_no != FIL_NULL) { + + merge_block = btr_block_get(space, zip_size, right_page_no, + RW_X_LATCH, mtr); + merge_page = buf_block_get_frame(merge_block); +#ifdef UNIV_BTR_DEBUG + ut_a(btr_page_get_prev(merge_page, mtr) + == buf_block_get_page_no(block)); +#endif /* UNIV_BTR_DEBUG */ + } else { + /* The page is the only one on the level, lift the records + to the father */ + btr_lift_page_up(index, block, mtr); + mem_heap_free(heap); + return(TRUE); + } + + n_recs = page_get_n_recs(page); + data_size = page_get_data_size(page); +#ifdef UNIV_BTR_DEBUG + ut_a(page_is_comp(merge_page) == page_is_comp(page)); +#endif /* UNIV_BTR_DEBUG */ + + max_ins_size_reorg = page_get_max_insert_size_after_reorganize( + merge_page, n_recs); + if (data_size > max_ins_size_reorg) { + + /* No space for merge */ +err_exit: + /* We play it safe and reset the free bits. */ + if (zip_size + && page_is_leaf(merge_page) + && !dict_index_is_clust(index)) { + ibuf_reset_free_bits(merge_block); + } + + mem_heap_free(heap); + return(FALSE); + } + + ut_ad(page_validate(merge_page, index)); + + max_ins_size = page_get_max_insert_size(merge_page, n_recs); + + if (UNIV_UNLIKELY(data_size > max_ins_size)) { + + /* We have to reorganize merge_page */ + + if (UNIV_UNLIKELY(!btr_page_reorganize(merge_block, + index, mtr))) { + + goto err_exit; + } + + max_ins_size = page_get_max_insert_size(merge_page, n_recs); + + ut_ad(page_validate(merge_page, index)); + ut_ad(max_ins_size == max_ins_size_reorg); + + if (UNIV_UNLIKELY(data_size > max_ins_size)) { + + /* Add fault tolerance, though this should + never happen */ + + goto err_exit; + } + } + + merge_page_zip = buf_block_get_page_zip(merge_block); +#ifdef UNIV_ZIP_DEBUG + if (UNIV_LIKELY_NULL(merge_page_zip)) { + const page_zip_des_t* page_zip + = buf_block_get_page_zip(block); + ut_a(page_zip); + ut_a(page_zip_validate(merge_page_zip, merge_page)); + ut_a(page_zip_validate(page_zip, page)); + } +#endif /* UNIV_ZIP_DEBUG */ + + /* Move records to the merge page */ + if (is_left) { + rec_t* orig_pred = page_copy_rec_list_start( + merge_block, block, page_get_supremum_rec(page), + index, mtr); + + if (UNIV_UNLIKELY(!orig_pred)) { + goto err_exit; + } + + btr_search_drop_page_hash_index(block); + + /* Remove the page from the level list */ + btr_level_list_remove(space, zip_size, page, mtr); + + btr_node_ptr_delete(index, block, mtr); + lock_update_merge_left(merge_block, orig_pred, block); + } else { + rec_t* orig_succ; +#ifdef UNIV_BTR_DEBUG + byte fil_page_prev[4]; +#endif /* UNIV_BTR_DEBUG */ + + if (UNIV_LIKELY_NULL(merge_page_zip)) { + /* The function page_zip_compress(), which will be + invoked by page_copy_rec_list_end() below, + requires that FIL_PAGE_PREV be FIL_NULL. + Clear the field, but prepare to restore it. */ +#ifdef UNIV_BTR_DEBUG + memcpy(fil_page_prev, merge_page + FIL_PAGE_PREV, 4); +#endif /* UNIV_BTR_DEBUG */ +#if FIL_NULL != 0xffffffff +# error "FIL_NULL != 0xffffffff" +#endif + memset(merge_page + FIL_PAGE_PREV, 0xff, 4); + } + + orig_succ = page_copy_rec_list_end(merge_block, block, + page_get_infimum_rec(page), + cursor->index, mtr); + + if (UNIV_UNLIKELY(!orig_succ)) { + ut_a(merge_page_zip); +#ifdef UNIV_BTR_DEBUG + /* FIL_PAGE_PREV was restored from merge_page_zip. */ + ut_a(!memcmp(fil_page_prev, + merge_page + FIL_PAGE_PREV, 4)); +#endif /* UNIV_BTR_DEBUG */ + goto err_exit; + } + + btr_search_drop_page_hash_index(block); + +#ifdef UNIV_BTR_DEBUG + if (UNIV_LIKELY_NULL(merge_page_zip)) { + /* Restore FIL_PAGE_PREV in order to avoid an assertion + failure in btr_level_list_remove(), which will set + the field again to FIL_NULL. Even though this makes + merge_page and merge_page_zip inconsistent for a + split second, it is harmless, because the pages + are X-latched. */ + memcpy(merge_page + FIL_PAGE_PREV, fil_page_prev, 4); + } +#endif /* UNIV_BTR_DEBUG */ + + /* Remove the page from the level list */ + btr_level_list_remove(space, zip_size, page, mtr); + + /* Replace the address of the old child node (= page) with the + address of the merge page to the right */ + + btr_node_ptr_set_child_page_no( + btr_cur_get_rec(&father_cursor), + btr_cur_get_page_zip(&father_cursor), + offsets, right_page_no, mtr); + btr_node_ptr_delete(index, merge_block, mtr); + + lock_update_merge_right(merge_block, orig_succ, block); + } + + mem_heap_free(heap); + + if (!dict_index_is_clust(index) && page_is_leaf(merge_page)) { + /* Update the free bits of the B-tree page in the + insert buffer bitmap. This has to be done in a + separate mini-transaction that is committed before the + main mini-transaction. We cannot update the insert + buffer bitmap in this mini-transaction, because + btr_compress() can be invoked recursively without + committing the mini-transaction in between. Since + insert buffer bitmap pages have a lower rank than + B-tree pages, we must not access other pages in the + same mini-transaction after accessing an insert buffer + bitmap page. */ + + /* The free bits in the insert buffer bitmap must + never exceed the free space on a page. It is safe to + decrement or reset the bits in the bitmap in a + mini-transaction that is committed before the + mini-transaction that affects the free space. */ + + /* It is unsafe to increment the bits in a separately + committed mini-transaction, because in crash recovery, + the free bits could momentarily be set too high. */ + + if (zip_size) { + /* Because the free bits may be incremented + and we cannot update the insert buffer bitmap + in the same mini-transaction, the only safe + thing we can do here is the pessimistic + approach: reset the free bits. */ + ibuf_reset_free_bits(merge_block); + } else { + /* On uncompressed pages, the free bits will + never increase here. Thus, it is safe to + write the bits accurately in a separate + mini-transaction. */ + ibuf_update_free_bits_if_full(merge_block, + UNIV_PAGE_SIZE, + ULINT_UNDEFINED); + } + } + + ut_ad(page_validate(merge_page, index)); +#ifdef UNIV_ZIP_DEBUG + ut_a(!merge_page_zip || page_zip_validate(merge_page_zip, merge_page)); +#endif /* UNIV_ZIP_DEBUG */ + + /* Free the file page */ + btr_page_free(index, block, mtr); + + ut_ad(btr_check_node_ptr(index, merge_block, mtr)); + return(TRUE); +} + +/***************************************************************** +Discards a page that is the only page on its level. This will empty +the whole B-tree, leaving just an empty root page. This function +should never be reached, because btr_compress(), which is invoked in +delete operations, calls btr_lift_page_up() to flatten the B-tree. */ +static +void +btr_discard_only_page_on_level( +/*===========================*/ + dict_index_t* index, /* in: index tree */ + buf_block_t* block, /* in: page which is the only on its level */ + mtr_t* mtr) /* in: mtr */ +{ + ulint page_level = 0; + + while (buf_block_get_page_no(block) != dict_index_get_page(index)) { + btr_cur_t cursor; + buf_block_t* father; + const page_t* page = buf_block_get_frame(block); + + ut_a(page_get_n_recs(page) == 1); + ut_a(page_level == btr_page_get_level(page, mtr)); + ut_a(btr_page_get_prev(page, mtr) == FIL_NULL); + ut_a(btr_page_get_next(page, mtr) == FIL_NULL); + + ut_ad(mtr_memo_contains(mtr, block, MTR_MEMO_PAGE_X_FIX)); + btr_search_drop_page_hash_index(block); + + btr_page_get_father(index, block, mtr, &cursor); + father = btr_cur_get_block(&cursor); + + lock_update_discard(father, PAGE_HEAP_NO_SUPREMUM, block); + + /* Free the file page */ + btr_page_free(index, block, mtr); + + block = father; + page_level++; + } + + /* block is the root page, which must be empty, except + for the node pointer to the (now discarded) block(s). */ + +#ifdef UNIV_BTR_DEBUG + if (!dict_index_is_ibuf(index)) { + const page_t* root = buf_block_get_frame(block); + const ulint space = dict_index_get_space(index); + ut_a(btr_root_fseg_validate(FIL_PAGE_DATA + PAGE_BTR_SEG_LEAF + + root, space)); + ut_a(btr_root_fseg_validate(FIL_PAGE_DATA + PAGE_BTR_SEG_TOP + + root, space)); + } +#endif /* UNIV_BTR_DEBUG */ + + btr_page_empty(block, buf_block_get_page_zip(block), index, 0, mtr); + + /* We play it safe and reset the free bits for the root */ + if (!dict_index_is_clust(index)) { + ibuf_reset_free_bits(block); + } +} + +/***************************************************************** +Discards a page from a B-tree. This is used to remove the last record from +a B-tree page: the whole page must be removed at the same time. This cannot +be used for the root page, which is allowed to be empty. */ +UNIV_INTERN +void +btr_discard_page( +/*=============*/ + btr_cur_t* cursor, /* in: cursor on the page to discard: not on + the root page */ + mtr_t* mtr) /* in: mtr */ +{ + dict_index_t* index; + ulint space; + ulint zip_size; + ulint left_page_no; + ulint right_page_no; + buf_block_t* merge_block; + page_t* merge_page; + buf_block_t* block; + page_t* page; + rec_t* node_ptr; + + block = btr_cur_get_block(cursor); + index = btr_cur_get_index(cursor); + + ut_ad(dict_index_get_page(index) != buf_block_get_page_no(block)); + ut_ad(mtr_memo_contains(mtr, dict_index_get_lock(index), + MTR_MEMO_X_LOCK)); + ut_ad(mtr_memo_contains(mtr, block, MTR_MEMO_PAGE_X_FIX)); + space = dict_index_get_space(index); + zip_size = dict_table_zip_size(index->table); + + /* Decide the page which will inherit the locks */ + + left_page_no = btr_page_get_prev(buf_block_get_frame(block), mtr); + right_page_no = btr_page_get_next(buf_block_get_frame(block), mtr); + + if (left_page_no != FIL_NULL) { + merge_block = btr_block_get(space, zip_size, left_page_no, + RW_X_LATCH, mtr); + merge_page = buf_block_get_frame(merge_block); +#ifdef UNIV_BTR_DEBUG + ut_a(btr_page_get_next(merge_page, mtr) + == buf_block_get_page_no(block)); +#endif /* UNIV_BTR_DEBUG */ + } else if (right_page_no != FIL_NULL) { + merge_block = btr_block_get(space, zip_size, right_page_no, + RW_X_LATCH, mtr); + merge_page = buf_block_get_frame(merge_block); +#ifdef UNIV_BTR_DEBUG + ut_a(btr_page_get_prev(merge_page, mtr) + == buf_block_get_page_no(block)); +#endif /* UNIV_BTR_DEBUG */ + } else { + btr_discard_only_page_on_level(index, block, mtr); + + return; + } + + page = buf_block_get_frame(block); + ut_a(page_is_comp(merge_page) == page_is_comp(page)); + btr_search_drop_page_hash_index(block); + + if (left_page_no == FIL_NULL && !page_is_leaf(page)) { + + /* We have to mark the leftmost node pointer on the right + side page as the predefined minimum record */ + node_ptr = page_rec_get_next(page_get_infimum_rec(merge_page)); + + ut_ad(page_rec_is_user_rec(node_ptr)); + + /* This will make page_zip_validate() fail on merge_page + until btr_level_list_remove() completes. This is harmless, + because everything will take place within a single + mini-transaction and because writing to the redo log + is an atomic operation (performed by mtr_commit()). */ + btr_set_min_rec_mark(node_ptr, mtr); + } + + btr_node_ptr_delete(index, block, mtr); + + /* Remove the page from the level list */ + btr_level_list_remove(space, zip_size, page, mtr); +#ifdef UNIV_ZIP_DEBUG + { + page_zip_des_t* merge_page_zip + = buf_block_get_page_zip(merge_block); + ut_a(!merge_page_zip + || page_zip_validate(merge_page_zip, merge_page)); + } +#endif /* UNIV_ZIP_DEBUG */ + + if (left_page_no != FIL_NULL) { + lock_update_discard(merge_block, PAGE_HEAP_NO_SUPREMUM, + block); + } else { + lock_update_discard(merge_block, + lock_get_min_heap_no(merge_block), + block); + } + + /* Free the file page */ + btr_page_free(index, block, mtr); + + ut_ad(btr_check_node_ptr(index, merge_block, mtr)); +} + +#ifdef UNIV_BTR_PRINT +/***************************************************************** +Prints size info of a B-tree. */ +UNIV_INTERN +void +btr_print_size( +/*===========*/ + dict_index_t* index) /* in: index tree */ +{ + page_t* root; + fseg_header_t* seg; + mtr_t mtr; + + if (dict_index_is_ibuf(index)) { + fputs("Sorry, cannot print info of an ibuf tree:" + " use ibuf functions\n", stderr); + + return; + } + + mtr_start(&mtr); + + root = btr_root_get(index, &mtr); + + seg = root + PAGE_HEADER + PAGE_BTR_SEG_TOP; + + fputs("INFO OF THE NON-LEAF PAGE SEGMENT\n", stderr); + fseg_print(seg, &mtr); + + if (!(index->type & DICT_UNIVERSAL)) { + + seg = root + PAGE_HEADER + PAGE_BTR_SEG_LEAF; + + fputs("INFO OF THE LEAF PAGE SEGMENT\n", stderr); + fseg_print(seg, &mtr); + } + + mtr_commit(&mtr); +} + +/**************************************************************** +Prints recursively index tree pages. */ +static +void +btr_print_recursive( +/*================*/ + dict_index_t* index, /* in: index tree */ + buf_block_t* block, /* in: index page */ + ulint width, /* in: print this many entries from start + and end */ + mem_heap_t** heap, /* in/out: heap for rec_get_offsets() */ + ulint** offsets,/* in/out: buffer for rec_get_offsets() */ + mtr_t* mtr) /* in: mtr */ +{ + const page_t* page = buf_block_get_frame(block); + page_cur_t cursor; + ulint n_recs; + ulint i = 0; + mtr_t mtr2; + + ut_ad(mtr_memo_contains(mtr, block, MTR_MEMO_PAGE_X_FIX)); + fprintf(stderr, "NODE ON LEVEL %lu page number %lu\n", + (ulong) btr_page_get_level(page, mtr), + (ulong) buf_block_get_page_no(block)); + + page_print(block, index, width, width); + + n_recs = page_get_n_recs(page); + + page_cur_set_before_first(block, &cursor); + page_cur_move_to_next(&cursor); + + while (!page_cur_is_after_last(&cursor)) { + + if (page_is_leaf(page)) { + + /* If this is the leaf level, do nothing */ + + } else if ((i <= width) || (i >= n_recs - width)) { + + const rec_t* node_ptr; + + mtr_start(&mtr2); + + node_ptr = page_cur_get_rec(&cursor); + + *offsets = rec_get_offsets(node_ptr, index, *offsets, + ULINT_UNDEFINED, heap); + btr_print_recursive(index, + btr_node_ptr_get_child(node_ptr, + index, + *offsets, + &mtr2), + width, heap, offsets, &mtr2); + mtr_commit(&mtr2); + } + + page_cur_move_to_next(&cursor); + i++; + } +} + +/****************************************************************** +Prints directories and other info of all nodes in the tree. */ +UNIV_INTERN +void +btr_print_index( +/*============*/ + dict_index_t* index, /* in: index */ + ulint width) /* in: print this many entries from start + and end */ +{ + mtr_t mtr; + buf_block_t* root; + mem_heap_t* heap = NULL; + ulint offsets_[REC_OFFS_NORMAL_SIZE]; + ulint* offsets = offsets_; + rec_offs_init(offsets_); + + fputs("--------------------------\n" + "INDEX TREE PRINT\n", stderr); + + mtr_start(&mtr); + + root = btr_root_block_get(index, &mtr); + + btr_print_recursive(index, root, width, &heap, &offsets, &mtr); + if (UNIV_LIKELY_NULL(heap)) { + mem_heap_free(heap); + } + + mtr_commit(&mtr); + + btr_validate_index(index, NULL); +} +#endif /* UNIV_BTR_PRINT */ + +#ifdef UNIV_DEBUG +/**************************************************************** +Checks that the node pointer to a page is appropriate. */ +UNIV_INTERN +ibool +btr_check_node_ptr( +/*===============*/ + /* out: TRUE */ + dict_index_t* index, /* in: index tree */ + buf_block_t* block, /* in: index page */ + mtr_t* mtr) /* in: mtr */ +{ + mem_heap_t* heap; + dtuple_t* tuple; + ulint* offsets; + btr_cur_t cursor; + page_t* page = buf_block_get_frame(block); + + ut_ad(mtr_memo_contains(mtr, block, MTR_MEMO_PAGE_X_FIX)); + if (dict_index_get_page(index) == buf_block_get_page_no(block)) { + + return(TRUE); + } + + heap = mem_heap_create(256); + offsets = btr_page_get_father_block(NULL, heap, index, block, mtr, + &cursor); + + if (page_is_leaf(page)) { + + goto func_exit; + } + + tuple = dict_index_build_node_ptr( + index, page_rec_get_next(page_get_infimum_rec(page)), 0, heap, + btr_page_get_level(page, mtr)); + + ut_a(!cmp_dtuple_rec(tuple, btr_cur_get_rec(&cursor), offsets)); +func_exit: + mem_heap_free(heap); + + return(TRUE); +} +#endif /* UNIV_DEBUG */ + +/**************************************************************** +Display identification information for a record. */ +static +void +btr_index_rec_validate_report( +/*==========================*/ + const page_t* page, /* in: index page */ + const rec_t* rec, /* in: index record */ + const dict_index_t* index) /* in: index */ +{ + fputs("InnoDB: Record in ", stderr); + dict_index_name_print(stderr, NULL, index); + fprintf(stderr, ", page %lu, at offset %lu\n", + page_get_page_no(page), (ulint) page_offset(rec)); +} + +/**************************************************************** +Checks the size and number of fields in a record based on the definition of +the index. */ +UNIV_INTERN +ibool +btr_index_rec_validate( +/*===================*/ + /* out: TRUE if ok */ + const rec_t* rec, /* in: index record */ + const dict_index_t* index, /* in: index */ + ibool dump_on_error) /* in: TRUE if the function + should print hex dump of record + and page on error */ +{ + ulint len; + ulint n; + ulint i; + const page_t* page; + mem_heap_t* heap = NULL; + ulint offsets_[REC_OFFS_NORMAL_SIZE]; + ulint* offsets = offsets_; + rec_offs_init(offsets_); + + page = page_align(rec); + + if (UNIV_UNLIKELY(index->type & DICT_UNIVERSAL)) { + /* The insert buffer index tree can contain records from any + other index: we cannot check the number of fields or + their length */ + + return(TRUE); + } + + if (UNIV_UNLIKELY((ibool)!!page_is_comp(page) + != dict_table_is_comp(index->table))) { + btr_index_rec_validate_report(page, rec, index); + fprintf(stderr, "InnoDB: compact flag=%lu, should be %lu\n", + (ulong) !!page_is_comp(page), + (ulong) dict_table_is_comp(index->table)); + + return(FALSE); + } + + n = dict_index_get_n_fields(index); + + if (!page_is_comp(page) + && UNIV_UNLIKELY(rec_get_n_fields_old(rec) != n)) { + btr_index_rec_validate_report(page, rec, index); + fprintf(stderr, "InnoDB: has %lu fields, should have %lu\n", + (ulong) rec_get_n_fields_old(rec), (ulong) n); + + if (dump_on_error) { + buf_page_print(page, 0); + + fputs("InnoDB: corrupt record ", stderr); + rec_print_old(stderr, rec); + putc('\n', stderr); + } + return(FALSE); + } + + offsets = rec_get_offsets(rec, index, offsets, ULINT_UNDEFINED, &heap); + + for (i = 0; i < n; i++) { + ulint fixed_size = dict_col_get_fixed_size( + dict_index_get_nth_col(index, i)); + + rec_get_nth_field_offs(offsets, i, &len); + + /* Note that if fixed_size != 0, it equals the + length of a fixed-size column in the clustered index. + A prefix index of the column is of fixed, but different + length. When fixed_size == 0, prefix_len is the maximum + length of the prefix index column. */ + + if ((dict_index_get_nth_field(index, i)->prefix_len == 0 + && len != UNIV_SQL_NULL && fixed_size + && len != fixed_size) + || (dict_index_get_nth_field(index, i)->prefix_len > 0 + && len != UNIV_SQL_NULL + && len + > dict_index_get_nth_field(index, i)->prefix_len)) { + + btr_index_rec_validate_report(page, rec, index); + fprintf(stderr, + "InnoDB: field %lu len is %lu," + " should be %lu\n", + (ulong) i, (ulong) len, (ulong) fixed_size); + + if (dump_on_error) { + buf_page_print(page, 0); + + fputs("InnoDB: corrupt record ", stderr); + rec_print_new(stderr, rec, offsets); + putc('\n', stderr); + } + if (UNIV_LIKELY_NULL(heap)) { + mem_heap_free(heap); + } + return(FALSE); + } + } + + if (UNIV_LIKELY_NULL(heap)) { + mem_heap_free(heap); + } + return(TRUE); +} + +/**************************************************************** +Checks the size and number of fields in records based on the definition of +the index. */ +static +ibool +btr_index_page_validate( +/*====================*/ + /* out: TRUE if ok */ + buf_block_t* block, /* in: index page */ + dict_index_t* index) /* in: index */ +{ + page_cur_t cur; + ibool ret = TRUE; + + page_cur_set_before_first(block, &cur); + page_cur_move_to_next(&cur); + + for (;;) { + if (page_cur_is_after_last(&cur)) { + + break; + } + + if (!btr_index_rec_validate(cur.rec, index, TRUE)) { + + return(FALSE); + } + + page_cur_move_to_next(&cur); + } + + return(ret); +} + +/**************************************************************** +Report an error on one page of an index tree. */ +static +void +btr_validate_report1( +/*=================*/ + /* out: TRUE if ok */ + dict_index_t* index, /* in: index */ + ulint level, /* in: B-tree level */ + const buf_block_t* block) /* in: index page */ +{ + fprintf(stderr, "InnoDB: Error in page %lu of ", + buf_block_get_page_no(block)); + dict_index_name_print(stderr, NULL, index); + if (level) { + fprintf(stderr, ", index tree level %lu", level); + } + putc('\n', stderr); +} + +/**************************************************************** +Report an error on two pages of an index tree. */ +static +void +btr_validate_report2( +/*=================*/ + /* out: TRUE if ok */ + const dict_index_t* index, /* in: index */ + ulint level, /* in: B-tree level */ + const buf_block_t* block1, /* in: first index page */ + const buf_block_t* block2) /* in: second index page */ +{ + fprintf(stderr, "InnoDB: Error in pages %lu and %lu of ", + buf_block_get_page_no(block1), + buf_block_get_page_no(block2)); + dict_index_name_print(stderr, NULL, index); + if (level) { + fprintf(stderr, ", index tree level %lu", level); + } + putc('\n', stderr); +} + +/**************************************************************** +Validates index tree level. */ +static +ibool +btr_validate_level( +/*===============*/ + /* out: TRUE if ok */ + dict_index_t* index, /* in: index tree */ + trx_t* trx, /* in: transaction or NULL */ + ulint level) /* in: level number */ +{ + ulint space; + ulint zip_size; + buf_block_t* block; + page_t* page; + buf_block_t* right_block = 0; /* remove warning */ + page_t* right_page = 0; /* remove warning */ + page_t* father_page; + btr_cur_t node_cur; + btr_cur_t right_node_cur; + rec_t* rec; + ulint right_page_no; + ulint left_page_no; + page_cur_t cursor; + dtuple_t* node_ptr_tuple; + ibool ret = TRUE; + mtr_t mtr; + mem_heap_t* heap = mem_heap_create(256); + ulint* offsets = NULL; + ulint* offsets2= NULL; +#ifdef UNIV_ZIP_DEBUG + page_zip_des_t* page_zip; +#endif /* UNIV_ZIP_DEBUG */ + + mtr_start(&mtr); + + mtr_x_lock(dict_index_get_lock(index), &mtr); + + block = btr_root_block_get(index, &mtr); + page = buf_block_get_frame(block); + + space = dict_index_get_space(index); + zip_size = dict_table_zip_size(index->table); + + while (level != btr_page_get_level(page, &mtr)) { + const rec_t* node_ptr; + + ut_a(space == buf_block_get_space(block)); + ut_a(space == page_get_space_id(page)); +#ifdef UNIV_ZIP_DEBUG + page_zip = buf_block_get_page_zip(block); + ut_a(!page_zip || page_zip_validate(page_zip, page)); +#endif /* UNIV_ZIP_DEBUG */ + ut_a(!page_is_leaf(page)); + + page_cur_set_before_first(block, &cursor); + page_cur_move_to_next(&cursor); + + node_ptr = page_cur_get_rec(&cursor); + offsets = rec_get_offsets(node_ptr, index, offsets, + ULINT_UNDEFINED, &heap); + block = btr_node_ptr_get_child(node_ptr, index, offsets, &mtr); + page = buf_block_get_frame(block); + } + + /* Now we are on the desired level. Loop through the pages on that + level. */ +loop: + if (trx_is_interrupted(trx)) { + mtr_commit(&mtr); + mem_heap_free(heap); + return(ret); + } + mem_heap_empty(heap); + offsets = offsets2 = NULL; + mtr_x_lock(dict_index_get_lock(index), &mtr); + +#ifdef UNIV_ZIP_DEBUG + page_zip = buf_block_get_page_zip(block); + ut_a(!page_zip || page_zip_validate(page_zip, page)); +#endif /* UNIV_ZIP_DEBUG */ + + /* Check ordering etc. of records */ + + if (!page_validate(page, index)) { + btr_validate_report1(index, level, block); + + ret = FALSE; + } else if (level == 0) { + /* We are on level 0. Check that the records have the right + number of fields, and field lengths are right. */ + + if (!btr_index_page_validate(block, index)) { + + ret = FALSE; + } + } + + ut_a(btr_page_get_level(page, &mtr) == level); + + right_page_no = btr_page_get_next(page, &mtr); + left_page_no = btr_page_get_prev(page, &mtr); + + ut_a(page_get_n_recs(page) > 0 || (level == 0 + && page_get_page_no(page) + == dict_index_get_page(index))); + + if (right_page_no != FIL_NULL) { + const rec_t* right_rec; + right_block = btr_block_get(space, zip_size, right_page_no, + RW_X_LATCH, &mtr); + right_page = buf_block_get_frame(right_block); + if (UNIV_UNLIKELY(btr_page_get_prev(right_page, &mtr) + != page_get_page_no(page))) { + btr_validate_report2(index, level, block, right_block); + fputs("InnoDB: broken FIL_PAGE_NEXT" + " or FIL_PAGE_PREV links\n", stderr); + buf_page_print(page, 0); + buf_page_print(right_page, 0); + + ret = FALSE; + } + + if (UNIV_UNLIKELY(page_is_comp(right_page) + != page_is_comp(page))) { + btr_validate_report2(index, level, block, right_block); + fputs("InnoDB: 'compact' flag mismatch\n", stderr); + buf_page_print(page, 0); + buf_page_print(right_page, 0); + + ret = FALSE; + + goto node_ptr_fails; + } + + rec = page_rec_get_prev(page_get_supremum_rec(page)); + right_rec = page_rec_get_next(page_get_infimum_rec( + right_page)); + offsets = rec_get_offsets(rec, index, + offsets, ULINT_UNDEFINED, &heap); + offsets2 = rec_get_offsets(right_rec, index, + offsets2, ULINT_UNDEFINED, &heap); + if (UNIV_UNLIKELY(cmp_rec_rec(rec, right_rec, + offsets, offsets2, + index) >= 0)) { + + btr_validate_report2(index, level, block, right_block); + + fputs("InnoDB: records in wrong order" + " on adjacent pages\n", stderr); + + buf_page_print(page, 0); + buf_page_print(right_page, 0); + + fputs("InnoDB: record ", stderr); + rec = page_rec_get_prev(page_get_supremum_rec(page)); + rec_print(stderr, rec, index); + putc('\n', stderr); + fputs("InnoDB: record ", stderr); + rec = page_rec_get_next( + page_get_infimum_rec(right_page)); + rec_print(stderr, rec, index); + putc('\n', stderr); + + ret = FALSE; + } + } + + if (level > 0 && left_page_no == FIL_NULL) { + ut_a(REC_INFO_MIN_REC_FLAG & rec_get_info_bits( + page_rec_get_next(page_get_infimum_rec(page)), + page_is_comp(page))); + } + + if (buf_block_get_page_no(block) != dict_index_get_page(index)) { + + /* Check father node pointers */ + + rec_t* node_ptr; + + offsets = btr_page_get_father_block(offsets, heap, index, + block, &mtr, &node_cur); + father_page = btr_cur_get_page(&node_cur); + node_ptr = btr_cur_get_rec(&node_cur); + + btr_cur_position( + index, page_rec_get_prev(page_get_supremum_rec(page)), + block, &node_cur); + offsets = btr_page_get_father_node_ptr(offsets, heap, + &node_cur, &mtr); + + if (UNIV_UNLIKELY(node_ptr != btr_cur_get_rec(&node_cur)) + || UNIV_UNLIKELY(btr_node_ptr_get_child_page_no(node_ptr, + offsets) + != buf_block_get_page_no(block))) { + + btr_validate_report1(index, level, block); + + fputs("InnoDB: node pointer to the page is wrong\n", + stderr); + + buf_page_print(father_page, 0); + buf_page_print(page, 0); + + fputs("InnoDB: node ptr ", stderr); + rec_print(stderr, node_ptr, index); + + rec = btr_cur_get_rec(&node_cur); + fprintf(stderr, "\n" + "InnoDB: node ptr child page n:o %lu\n", + (ulong) btr_node_ptr_get_child_page_no( + rec, offsets)); + + fputs("InnoDB: record on page ", stderr); + rec_print_new(stderr, rec, offsets); + putc('\n', stderr); + ret = FALSE; + + goto node_ptr_fails; + } + + if (!page_is_leaf(page)) { + node_ptr_tuple = dict_index_build_node_ptr( + index, + page_rec_get_next(page_get_infimum_rec(page)), + 0, heap, btr_page_get_level(page, &mtr)); + + if (cmp_dtuple_rec(node_ptr_tuple, node_ptr, + offsets)) { + const rec_t* first_rec = page_rec_get_next( + page_get_infimum_rec(page)); + + btr_validate_report1(index, level, block); + + buf_page_print(father_page, 0); + buf_page_print(page, 0); + + fputs("InnoDB: Error: node ptrs differ" + " on levels > 0\n" + "InnoDB: node ptr ", stderr); + rec_print_new(stderr, node_ptr, offsets); + fputs("InnoDB: first rec ", stderr); + rec_print(stderr, first_rec, index); + putc('\n', stderr); + ret = FALSE; + + goto node_ptr_fails; + } + } + + if (left_page_no == FIL_NULL) { + ut_a(node_ptr == page_rec_get_next( + page_get_infimum_rec(father_page))); + ut_a(btr_page_get_prev(father_page, &mtr) == FIL_NULL); + } + + if (right_page_no == FIL_NULL) { + ut_a(node_ptr == page_rec_get_prev( + page_get_supremum_rec(father_page))); + ut_a(btr_page_get_next(father_page, &mtr) == FIL_NULL); + } else { + const rec_t* right_node_ptr + = page_rec_get_next(node_ptr); + + offsets = btr_page_get_father_block( + offsets, heap, index, right_block, + &mtr, &right_node_cur); + if (right_node_ptr + != page_get_supremum_rec(father_page)) { + + if (btr_cur_get_rec(&right_node_cur) + != right_node_ptr) { + ret = FALSE; + fputs("InnoDB: node pointer to" + " the right page is wrong\n", + stderr); + + btr_validate_report1(index, level, + block); + + buf_page_print(father_page, 0); + buf_page_print(page, 0); + buf_page_print(right_page, 0); + } + } else { + page_t* right_father_page + = btr_cur_get_page(&right_node_cur); + + if (btr_cur_get_rec(&right_node_cur) + != page_rec_get_next( + page_get_infimum_rec( + right_father_page))) { + ret = FALSE; + fputs("InnoDB: node pointer 2 to" + " the right page is wrong\n", + stderr); + + btr_validate_report1(index, level, + block); + + buf_page_print(father_page, 0); + buf_page_print(right_father_page, 0); + buf_page_print(page, 0); + buf_page_print(right_page, 0); + } + + if (page_get_page_no(right_father_page) + != btr_page_get_next(father_page, &mtr)) { + + ret = FALSE; + fputs("InnoDB: node pointer 3 to" + " the right page is wrong\n", + stderr); + + btr_validate_report1(index, level, + block); + + buf_page_print(father_page, 0); + buf_page_print(right_father_page, 0); + buf_page_print(page, 0); + buf_page_print(right_page, 0); + } + } + } + } + +node_ptr_fails: + /* Commit the mini-transaction to release the latch on 'page'. + Re-acquire the latch on right_page, which will become 'page' + on the next loop. The page has already been checked. */ + mtr_commit(&mtr); + + if (right_page_no != FIL_NULL) { + mtr_start(&mtr); + + block = btr_block_get(space, zip_size, right_page_no, + RW_X_LATCH, &mtr); + page = buf_block_get_frame(block); + + goto loop; + } + + mem_heap_free(heap); + return(ret); +} + +/****************************************************************** +Checks the consistency of an index tree. */ +UNIV_INTERN +ibool +btr_validate_index( +/*===============*/ + /* out: TRUE if ok */ + dict_index_t* index, /* in: index */ + trx_t* trx) /* in: transaction or NULL */ +{ + mtr_t mtr; + page_t* root; + ulint i; + ulint n; + + mtr_start(&mtr); + mtr_x_lock(dict_index_get_lock(index), &mtr); + + root = btr_root_get(index, &mtr); + n = btr_page_get_level(root, &mtr); + + for (i = 0; i <= n && !trx_is_interrupted(trx); i++) { + if (!btr_validate_level(index, trx, n - i)) { + + mtr_commit(&mtr); + + return(FALSE); + } + } + + mtr_commit(&mtr); + + return(TRUE); +} diff --git a/storage/xtradb/btr/btr0cur.c b/storage/xtradb/btr/btr0cur.c new file mode 100644 index 00000000000..1b34a8ee887 --- /dev/null +++ b/storage/xtradb/btr/btr0cur.c @@ -0,0 +1,4848 @@ +/***************************************************************************** + +Copyright (c) 1994, 2009, Innobase Oy. All Rights Reserved. +Copyright (c) 2008, Google Inc. + +Portions of this file contain modifications contributed and copyrighted by +Google, Inc. Those modifications are gratefully acknowledged and are described +briefly in the InnoDB documentation. The contributions by Google are +incorporated with their permission, and subject to the conditions contained in +the file COPYING.Google. + +This program is free software; you can redistribute it and/or modify it under +the terms of the GNU General Public License as published by the Free Software +Foundation; version 2 of the License. + +This program is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + +You 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 + +*****************************************************************************/ + +/****************************************************** +The index tree cursor + +All changes that row operations make to a B-tree or the records +there must go through this module! Undo log records are written here +of every modify or insert of a clustered index record. + + NOTE!!! +To make sure we do not run out of disk space during a pessimistic +insert or update, we have to reserve 2 x the height of the index tree +many pages in the tablespace before we start the operation, because +if leaf splitting has been started, it is difficult to undo, except +by crashing the database and doing a roll-forward. + +Created 10/16/1994 Heikki Tuuri +*******************************************************/ + +#include "btr0cur.h" + +#ifdef UNIV_NONINL +#include "btr0cur.ic" +#endif + +#include "page0page.h" +#include "page0zip.h" +#include "rem0rec.h" +#include "rem0cmp.h" +#include "buf0lru.h" +#include "btr0btr.h" +#include "btr0sea.h" +#include "row0upd.h" +#include "trx0rec.h" +#include "trx0roll.h" /* trx_is_recv() */ +#include "que0que.h" +#include "row0row.h" +#include "srv0srv.h" +#include "ibuf0ibuf.h" +#include "lock0lock.h" +#include "zlib.h" + +#ifdef UNIV_DEBUG +/* If the following is set to TRUE, this module prints a lot of +trace information of individual record operations */ +UNIV_INTERN ibool btr_cur_print_record_ops = FALSE; +#endif /* UNIV_DEBUG */ + +UNIV_INTERN ulint btr_cur_n_non_sea = 0; +UNIV_INTERN ulint btr_cur_n_sea = 0; +UNIV_INTERN ulint btr_cur_n_non_sea_old = 0; +UNIV_INTERN ulint btr_cur_n_sea_old = 0; + +/* In the optimistic insert, if the insert does not fit, but this much space +can be released by page reorganize, then it is reorganized */ + +#define BTR_CUR_PAGE_REORGANIZE_LIMIT (UNIV_PAGE_SIZE / 32) + +/* The structure of a BLOB part header */ +/*--------------------------------------*/ +#define BTR_BLOB_HDR_PART_LEN 0 /* BLOB part len on this + page */ +#define BTR_BLOB_HDR_NEXT_PAGE_NO 4 /* next BLOB part page no, + FIL_NULL if none */ +/*--------------------------------------*/ +#define BTR_BLOB_HDR_SIZE 8 + +/* A BLOB field reference full of zero, for use in assertions and tests. +Initially, BLOB field references are set to zero, in +dtuple_convert_big_rec(). */ +UNIV_INTERN const byte field_ref_zero[BTR_EXTERN_FIELD_REF_SIZE]; + +/*********************************************************************** +Marks all extern fields in a record as owned by the record. This function +should be called if the delete mark of a record is removed: a not delete +marked record always owns all its extern fields. */ +static +void +btr_cur_unmark_extern_fields( +/*=========================*/ + page_zip_des_t* page_zip,/* in/out: compressed page whose uncompressed + part will be updated, or NULL */ + rec_t* rec, /* in/out: record in a clustered index */ + dict_index_t* index, /* in: index of the page */ + const ulint* offsets,/* in: array returned by rec_get_offsets() */ + mtr_t* mtr); /* in: mtr, or NULL if not logged */ +/*********************************************************************** +Adds path information to the cursor for the current page, for which +the binary search has been performed. */ +static +void +btr_cur_add_path_info( +/*==================*/ + btr_cur_t* cursor, /* in: cursor positioned on a page */ + ulint height, /* in: height of the page in tree; + 0 means leaf node */ + ulint root_height); /* in: root node height in tree */ +/*************************************************************** +Frees the externally stored fields for a record, if the field is mentioned +in the update vector. */ +static +void +btr_rec_free_updated_extern_fields( +/*===============================*/ + dict_index_t* index, /* in: index of rec; the index tree MUST be + X-latched */ + rec_t* rec, /* in: record */ + page_zip_des_t* page_zip,/* in: compressed page whose uncompressed + part will be updated, or NULL */ + const ulint* offsets,/* in: rec_get_offsets(rec, index) */ + const upd_t* update, /* in: update vector */ + enum trx_rb_ctx rb_ctx, /* in: rollback context */ + mtr_t* mtr); /* in: mini-transaction handle which contains + an X-latch to record page and to the tree */ +/*************************************************************** +Frees the externally stored fields for a record. */ +static +void +btr_rec_free_externally_stored_fields( +/*==================================*/ + dict_index_t* index, /* in: index of the data, the index + tree MUST be X-latched */ + rec_t* rec, /* in: record */ + const ulint* offsets,/* in: rec_get_offsets(rec, index) */ + page_zip_des_t* page_zip,/* in: compressed page whose uncompressed + part will be updated, or NULL */ + enum trx_rb_ctx rb_ctx, /* in: rollback context */ + mtr_t* mtr); /* in: mini-transaction handle which contains + an X-latch to record page and to the index + tree */ +/*************************************************************** +Gets the externally stored size of a record, in units of a database page. */ +static +ulint +btr_rec_get_externally_stored_len( +/*==============================*/ + /* out: externally stored part, + in units of a database page */ + rec_t* rec, /* in: record */ + const ulint* offsets);/* in: array returned by rec_get_offsets() */ + +/********************************************************** +The following function is used to set the deleted bit of a record. */ +UNIV_INLINE +void +btr_rec_set_deleted_flag( +/*=====================*/ + /* out: TRUE on success; + FALSE on page_zip overflow */ + rec_t* rec, /* in/out: physical record */ + page_zip_des_t* page_zip,/* in/out: compressed page (or NULL) */ + ulint flag) /* in: nonzero if delete marked */ +{ + if (page_rec_is_comp(rec)) { + rec_set_deleted_flag_new(rec, page_zip, flag); + } else { + ut_ad(!page_zip); + rec_set_deleted_flag_old(rec, flag); + } +} + +/*==================== B-TREE SEARCH =========================*/ + +/************************************************************************ +Latches the leaf page or pages requested. */ +static +void +btr_cur_latch_leaves( +/*=================*/ + page_t* page, /* in: leaf page where the search + converged */ + ulint space, /* in: space id */ + ulint zip_size, /* in: compressed page size in bytes + or 0 for uncompressed pages */ + ulint page_no, /* in: page number of the leaf */ + ulint latch_mode, /* in: BTR_SEARCH_LEAF, ... */ + btr_cur_t* cursor, /* in: cursor */ + mtr_t* mtr) /* in: mtr */ +{ + ulint mode; + ulint left_page_no; + ulint right_page_no; + buf_block_t* get_block; + + ut_ad(page && mtr); + + switch (latch_mode) { + case BTR_SEARCH_LEAF: + case BTR_MODIFY_LEAF: + mode = latch_mode == BTR_SEARCH_LEAF ? RW_S_LATCH : RW_X_LATCH; + get_block = btr_block_get(space, zip_size, page_no, mode, mtr); +#ifdef UNIV_BTR_DEBUG + ut_a(page_is_comp(get_block->frame) == page_is_comp(page)); +#endif /* UNIV_BTR_DEBUG */ + get_block->check_index_page_at_flush = TRUE; + return; + case BTR_MODIFY_TREE: + /* x-latch also brothers from left to right */ + left_page_no = btr_page_get_prev(page, mtr); + + if (left_page_no != FIL_NULL) { + get_block = btr_block_get(space, zip_size, + left_page_no, + RW_X_LATCH, mtr); +#ifdef UNIV_BTR_DEBUG + ut_a(page_is_comp(get_block->frame) + == page_is_comp(page)); + ut_a(btr_page_get_next(get_block->frame, mtr) + == page_get_page_no(page)); +#endif /* UNIV_BTR_DEBUG */ + get_block->check_index_page_at_flush = TRUE; + } + + get_block = btr_block_get(space, zip_size, page_no, + RW_X_LATCH, mtr); +#ifdef UNIV_BTR_DEBUG + ut_a(page_is_comp(get_block->frame) == page_is_comp(page)); +#endif /* UNIV_BTR_DEBUG */ + get_block->check_index_page_at_flush = TRUE; + + right_page_no = btr_page_get_next(page, mtr); + + if (right_page_no != FIL_NULL) { + get_block = btr_block_get(space, zip_size, + right_page_no, + RW_X_LATCH, mtr); +#ifdef UNIV_BTR_DEBUG + ut_a(page_is_comp(get_block->frame) + == page_is_comp(page)); + ut_a(btr_page_get_prev(get_block->frame, mtr) + == page_get_page_no(page)); +#endif /* UNIV_BTR_DEBUG */ + get_block->check_index_page_at_flush = TRUE; + } + + return; + + case BTR_SEARCH_PREV: + case BTR_MODIFY_PREV: + mode = latch_mode == BTR_SEARCH_PREV ? RW_S_LATCH : RW_X_LATCH; + /* latch also left brother */ + left_page_no = btr_page_get_prev(page, mtr); + + if (left_page_no != FIL_NULL) { + get_block = btr_block_get(space, zip_size, + left_page_no, mode, mtr); + cursor->left_block = get_block; +#ifdef UNIV_BTR_DEBUG + ut_a(page_is_comp(get_block->frame) + == page_is_comp(page)); + ut_a(btr_page_get_next(get_block->frame, mtr) + == page_get_page_no(page)); +#endif /* UNIV_BTR_DEBUG */ + get_block->check_index_page_at_flush = TRUE; + } + + get_block = btr_block_get(space, zip_size, page_no, mode, mtr); +#ifdef UNIV_BTR_DEBUG + ut_a(page_is_comp(get_block->frame) == page_is_comp(page)); +#endif /* UNIV_BTR_DEBUG */ + get_block->check_index_page_at_flush = TRUE; + return; + } + + ut_error; +} + +/************************************************************************ +Searches an index tree and positions a tree cursor on a given level. +NOTE: n_fields_cmp in tuple must be set so that it cannot be compared +to node pointer page number fields on the upper levels of the tree! +Note that if mode is PAGE_CUR_LE, which is used in inserts, then +cursor->up_match and cursor->low_match both will have sensible values. +If mode is PAGE_CUR_GE, then up_match will a have a sensible value. + +If mode is PAGE_CUR_LE , cursor is left at the place where an insert of the +search tuple should be performed in the B-tree. InnoDB does an insert +immediately after the cursor. Thus, the cursor may end up on a user record, +or on a page infimum record. */ +UNIV_INTERN +void +btr_cur_search_to_nth_level( +/*========================*/ + dict_index_t* index, /* in: index */ + ulint level, /* in: the tree level of search */ + const dtuple_t* tuple, /* in: data tuple; NOTE: n_fields_cmp in + tuple must be set so that it cannot get + compared to the node ptr page number field! */ + ulint mode, /* in: PAGE_CUR_L, ...; + Inserts should always be made using + PAGE_CUR_LE to search the position! */ + ulint latch_mode, /* in: BTR_SEARCH_LEAF, ..., ORed with + BTR_INSERT and BTR_ESTIMATE; + cursor->left_block is used to store a pointer + to the left neighbor page, in the cases + BTR_SEARCH_PREV and BTR_MODIFY_PREV; + NOTE that if has_search_latch + is != 0, we maybe do not have a latch set + on the cursor page, we assume + the caller uses his search latch + to protect the record! */ + btr_cur_t* cursor, /* in/out: tree cursor; the cursor page is + s- or x-latched, but see also above! */ + ulint has_search_latch,/* in: info on the latch mode the + caller currently has on btr_search_latch: + RW_S_LATCH, or 0 */ + mtr_t* mtr) /* in: mtr */ +{ + page_cur_t* page_cursor; + page_t* page; + buf_block_t* guess; + rec_t* node_ptr; + ulint page_no; + ulint space; + ulint up_match; + ulint up_bytes; + ulint low_match; + ulint low_bytes; + ulint height; + ulint savepoint; + ulint page_mode; + ulint insert_planned; + ulint estimate; + ulint ignore_sec_unique; + ulint root_height = 0; /* remove warning */ +#ifdef BTR_CUR_ADAPT + btr_search_t* info; +#endif + mem_heap_t* heap = NULL; + ulint offsets_[REC_OFFS_NORMAL_SIZE]; + ulint* offsets = offsets_; + rec_offs_init(offsets_); + /* Currently, PAGE_CUR_LE is the only search mode used for searches + ending to upper levels */ + + ut_ad(level == 0 || mode == PAGE_CUR_LE); + ut_ad(dict_index_check_search_tuple(index, tuple)); + ut_ad(!dict_index_is_ibuf(index) || ibuf_inside()); + ut_ad(dtuple_check_typed(tuple)); + +#ifdef UNIV_DEBUG + cursor->up_match = ULINT_UNDEFINED; + cursor->low_match = ULINT_UNDEFINED; +#endif + insert_planned = latch_mode & BTR_INSERT; + estimate = latch_mode & BTR_ESTIMATE; + ignore_sec_unique = latch_mode & BTR_IGNORE_SEC_UNIQUE; + latch_mode = latch_mode & ~(BTR_INSERT | BTR_ESTIMATE + | BTR_IGNORE_SEC_UNIQUE); + + ut_ad(!insert_planned || (mode == PAGE_CUR_LE)); + + cursor->flag = BTR_CUR_BINARY; + cursor->index = index; + +#ifndef BTR_CUR_ADAPT + guess = NULL; +#else + info = btr_search_get_info(index); + + guess = info->root_guess; + +#ifdef BTR_CUR_HASH_ADAPT + +#ifdef UNIV_SEARCH_PERF_STAT + info->n_searches++; +#endif + if (rw_lock_get_writer(&btr_search_latch) == RW_LOCK_NOT_LOCKED + && latch_mode <= BTR_MODIFY_LEAF && info->last_hash_succ + && !estimate +#ifdef PAGE_CUR_LE_OR_EXTENDS + && mode != PAGE_CUR_LE_OR_EXTENDS +#endif /* PAGE_CUR_LE_OR_EXTENDS */ + /* If !has_search_latch, we do a dirty read of + btr_search_enabled below, and btr_search_guess_on_hash() + will have to check it again. */ + && UNIV_LIKELY(btr_search_enabled) + && btr_search_guess_on_hash(index, info, tuple, mode, + latch_mode, cursor, + has_search_latch, mtr)) { + + /* Search using the hash index succeeded */ + + ut_ad(cursor->up_match != ULINT_UNDEFINED + || mode != PAGE_CUR_GE); + ut_ad(cursor->up_match != ULINT_UNDEFINED + || mode != PAGE_CUR_LE); + ut_ad(cursor->low_match != ULINT_UNDEFINED + || mode != PAGE_CUR_LE); + btr_cur_n_sea++; + + return; + } +#endif /* BTR_CUR_HASH_ADAPT */ +#endif /* BTR_CUR_ADAPT */ + btr_cur_n_non_sea++; + + /* If the hash search did not succeed, do binary search down the + tree */ + + if (has_search_latch) { + /* Release possible search latch to obey latching order */ + rw_lock_s_unlock(&btr_search_latch); + } + + /* Store the position of the tree latch we push to mtr so that we + know how to release it when we have latched leaf node(s) */ + + savepoint = mtr_set_savepoint(mtr); + + if (latch_mode == BTR_MODIFY_TREE) { + mtr_x_lock(dict_index_get_lock(index), mtr); + + } else if (latch_mode == BTR_CONT_MODIFY_TREE) { + /* Do nothing */ + ut_ad(mtr_memo_contains(mtr, dict_index_get_lock(index), + MTR_MEMO_X_LOCK)); + } else { + mtr_s_lock(dict_index_get_lock(index), mtr); + } + + page_cursor = btr_cur_get_page_cur(cursor); + + space = dict_index_get_space(index); + page_no = dict_index_get_page(index); + + up_match = 0; + up_bytes = 0; + low_match = 0; + low_bytes = 0; + + height = ULINT_UNDEFINED; + + /* We use these modified search modes on non-leaf levels of the + B-tree. These let us end up in the right B-tree leaf. In that leaf + we use the original search mode. */ + + switch (mode) { + case PAGE_CUR_GE: + page_mode = PAGE_CUR_L; + break; + case PAGE_CUR_G: + page_mode = PAGE_CUR_LE; + break; + default: +#ifdef PAGE_CUR_LE_OR_EXTENDS + ut_ad(mode == PAGE_CUR_L || mode == PAGE_CUR_LE + || mode == PAGE_CUR_LE_OR_EXTENDS); +#else /* PAGE_CUR_LE_OR_EXTENDS */ + ut_ad(mode == PAGE_CUR_L || mode == PAGE_CUR_LE); +#endif /* PAGE_CUR_LE_OR_EXTENDS */ + page_mode = mode; + break; + } + + /* Loop and search until we arrive at the desired level */ + + for (;;) { + ulint zip_size; + buf_block_t* block; + ulint rw_latch; + ulint buf_mode; + + zip_size = dict_table_zip_size(index->table); + rw_latch = RW_NO_LATCH; + buf_mode = BUF_GET; + + if (height == 0 && latch_mode <= BTR_MODIFY_LEAF) { + + rw_latch = latch_mode; + + if (insert_planned + && ibuf_should_try(index, ignore_sec_unique)) { + + /* Try insert to the insert buffer if the + page is not in the buffer pool */ + + buf_mode = BUF_GET_IF_IN_POOL; + } + } + +retry_page_get: + block = buf_page_get_gen(space, zip_size, page_no, + rw_latch, guess, buf_mode, + __FILE__, __LINE__, mtr); + if (block == NULL) { + /* This must be a search to perform an insert; + try insert to the insert buffer */ + + ut_ad(buf_mode == BUF_GET_IF_IN_POOL); + ut_ad(insert_planned); + ut_ad(cursor->thr); + + if (ibuf_insert(tuple, index, space, zip_size, + page_no, cursor->thr)) { + /* Insertion to the insert buffer succeeded */ + cursor->flag = BTR_CUR_INSERT_TO_IBUF; + if (UNIV_LIKELY_NULL(heap)) { + mem_heap_free(heap); + } + goto func_exit; + } + + /* Insert to the insert buffer did not succeed: + retry page get */ + + buf_mode = BUF_GET; + + goto retry_page_get; + } + + page = buf_block_get_frame(block); + + block->check_index_page_at_flush = TRUE; + + if (rw_latch != RW_NO_LATCH) { +#ifdef UNIV_ZIP_DEBUG + const page_zip_des_t* page_zip + = buf_block_get_page_zip(block); + ut_a(!page_zip || page_zip_validate(page_zip, page)); +#endif /* UNIV_ZIP_DEBUG */ + + buf_block_dbg_add_level(block, SYNC_TREE_NODE); + } + + ut_ad(0 == ut_dulint_cmp(index->id, + btr_page_get_index_id(page))); + + if (UNIV_UNLIKELY(height == ULINT_UNDEFINED)) { + /* We are in the root node */ + + height = btr_page_get_level(page, mtr); + root_height = height; + cursor->tree_height = root_height + 1; +#ifdef BTR_CUR_ADAPT + if (block != guess) { + info->root_guess = block; + } +#endif + } + + if (height == 0) { + if (rw_latch == RW_NO_LATCH) { + + btr_cur_latch_leaves(page, space, zip_size, + page_no, latch_mode, + cursor, mtr); + } + + if ((latch_mode != BTR_MODIFY_TREE) + && (latch_mode != BTR_CONT_MODIFY_TREE)) { + + /* Release the tree s-latch */ + + mtr_release_s_latch_at_savepoint( + mtr, savepoint, + dict_index_get_lock(index)); + } + + page_mode = mode; + } + + page_cur_search_with_match(block, index, tuple, page_mode, + &up_match, &up_bytes, + &low_match, &low_bytes, + page_cursor); + + if (estimate) { + btr_cur_add_path_info(cursor, height, root_height); + } + + /* If this is the desired level, leave the loop */ + + ut_ad(height == btr_page_get_level( + page_cur_get_page(page_cursor), mtr)); + + if (level == height) { + + if (level > 0) { + /* x-latch the page */ + page = btr_page_get(space, zip_size, + page_no, RW_X_LATCH, mtr); + ut_a((ibool)!!page_is_comp(page) + == dict_table_is_comp(index->table)); + } + + break; + } + + ut_ad(height > 0); + + height--; + + guess = NULL; + + node_ptr = page_cur_get_rec(page_cursor); + offsets = rec_get_offsets(node_ptr, cursor->index, offsets, + ULINT_UNDEFINED, &heap); + /* Go to the child node */ + page_no = btr_node_ptr_get_child_page_no(node_ptr, offsets); + } + + if (UNIV_LIKELY_NULL(heap)) { + mem_heap_free(heap); + } + + if (level == 0) { + cursor->low_match = low_match; + cursor->low_bytes = low_bytes; + cursor->up_match = up_match; + cursor->up_bytes = up_bytes; + +#ifdef BTR_CUR_ADAPT + /* We do a dirty read of btr_search_enabled here. We + will properly check btr_search_enabled again in + btr_search_build_page_hash_index() before building a + page hash index, while holding btr_search_latch. */ + if (UNIV_LIKELY(btr_search_enabled)) { + + btr_search_info_update(index, cursor); + } +#endif + ut_ad(cursor->up_match != ULINT_UNDEFINED + || mode != PAGE_CUR_GE); + ut_ad(cursor->up_match != ULINT_UNDEFINED + || mode != PAGE_CUR_LE); + ut_ad(cursor->low_match != ULINT_UNDEFINED + || mode != PAGE_CUR_LE); + } + +func_exit: + if (has_search_latch) { + + rw_lock_s_lock(&btr_search_latch); + } +} + +/********************************************************************* +Opens a cursor at either end of an index. */ +UNIV_INTERN +void +btr_cur_open_at_index_side( +/*=======================*/ + ibool from_left, /* in: TRUE if open to the low end, + FALSE if to the high end */ + dict_index_t* index, /* in: index */ + ulint latch_mode, /* in: latch mode */ + btr_cur_t* cursor, /* in: cursor */ + mtr_t* mtr) /* in: mtr */ +{ + page_cur_t* page_cursor; + ulint page_no; + ulint space; + ulint zip_size; + ulint height; + ulint root_height = 0; /* remove warning */ + rec_t* node_ptr; + ulint estimate; + ulint savepoint; + mem_heap_t* heap = NULL; + ulint offsets_[REC_OFFS_NORMAL_SIZE]; + ulint* offsets = offsets_; + rec_offs_init(offsets_); + + estimate = latch_mode & BTR_ESTIMATE; + latch_mode = latch_mode & ~BTR_ESTIMATE; + + /* Store the position of the tree latch we push to mtr so that we + know how to release it when we have latched the leaf node */ + + savepoint = mtr_set_savepoint(mtr); + + if (latch_mode == BTR_MODIFY_TREE) { + mtr_x_lock(dict_index_get_lock(index), mtr); + } else { + mtr_s_lock(dict_index_get_lock(index), mtr); + } + + page_cursor = btr_cur_get_page_cur(cursor); + cursor->index = index; + + space = dict_index_get_space(index); + zip_size = dict_table_zip_size(index->table); + page_no = dict_index_get_page(index); + + height = ULINT_UNDEFINED; + + for (;;) { + buf_block_t* block; + page_t* page; + block = buf_page_get_gen(space, zip_size, page_no, + RW_NO_LATCH, NULL, BUF_GET, + __FILE__, __LINE__, mtr); + page = buf_block_get_frame(block); + ut_ad(0 == ut_dulint_cmp(index->id, + btr_page_get_index_id(page))); + + block->check_index_page_at_flush = TRUE; + + if (height == ULINT_UNDEFINED) { + /* We are in the root node */ + + height = btr_page_get_level(page, mtr); + root_height = height; + } + + if (height == 0) { + btr_cur_latch_leaves(page, space, zip_size, page_no, + latch_mode, cursor, mtr); + + /* In versions <= 3.23.52 we had forgotten to + release the tree latch here. If in an index scan + we had to scan far to find a record visible to the + current transaction, that could starve others + waiting for the tree latch. */ + + if ((latch_mode != BTR_MODIFY_TREE) + && (latch_mode != BTR_CONT_MODIFY_TREE)) { + + /* Release the tree s-latch */ + + mtr_release_s_latch_at_savepoint( + mtr, savepoint, + dict_index_get_lock(index)); + } + } + + if (from_left) { + page_cur_set_before_first(block, page_cursor); + } else { + page_cur_set_after_last(block, page_cursor); + } + + if (height == 0) { + if (estimate) { + btr_cur_add_path_info(cursor, height, + root_height); + } + + break; + } + + ut_ad(height > 0); + + if (from_left) { + page_cur_move_to_next(page_cursor); + } else { + page_cur_move_to_prev(page_cursor); + } + + if (estimate) { + btr_cur_add_path_info(cursor, height, root_height); + } + + height--; + + node_ptr = page_cur_get_rec(page_cursor); + offsets = rec_get_offsets(node_ptr, cursor->index, offsets, + ULINT_UNDEFINED, &heap); + /* Go to the child node */ + page_no = btr_node_ptr_get_child_page_no(node_ptr, offsets); + } + + if (UNIV_LIKELY_NULL(heap)) { + mem_heap_free(heap); + } +} + +/************************************************************************** +Positions a cursor at a randomly chosen position within a B-tree. */ +UNIV_INTERN +void +btr_cur_open_at_rnd_pos( +/*====================*/ + dict_index_t* index, /* in: index */ + ulint latch_mode, /* in: BTR_SEARCH_LEAF, ... */ + btr_cur_t* cursor, /* in/out: B-tree cursor */ + mtr_t* mtr) /* in: mtr */ +{ + page_cur_t* page_cursor; + ulint page_no; + ulint space; + ulint zip_size; + ulint height; + rec_t* node_ptr; + mem_heap_t* heap = NULL; + ulint offsets_[REC_OFFS_NORMAL_SIZE]; + ulint* offsets = offsets_; + rec_offs_init(offsets_); + + if (latch_mode == BTR_MODIFY_TREE) { + mtr_x_lock(dict_index_get_lock(index), mtr); + } else { + mtr_s_lock(dict_index_get_lock(index), mtr); + } + + page_cursor = btr_cur_get_page_cur(cursor); + cursor->index = index; + + space = dict_index_get_space(index); + zip_size = dict_table_zip_size(index->table); + page_no = dict_index_get_page(index); + + height = ULINT_UNDEFINED; + + for (;;) { + buf_block_t* block; + page_t* page; + + block = buf_page_get_gen(space, zip_size, page_no, + RW_NO_LATCH, NULL, BUF_GET, + __FILE__, __LINE__, mtr); + page = buf_block_get_frame(block); + ut_ad(0 == ut_dulint_cmp(index->id, + btr_page_get_index_id(page))); + + if (height == ULINT_UNDEFINED) { + /* We are in the root node */ + + height = btr_page_get_level(page, mtr); + } + + if (height == 0) { + btr_cur_latch_leaves(page, space, zip_size, page_no, + latch_mode, cursor, mtr); + } + + page_cur_open_on_rnd_user_rec(block, page_cursor); + + if (height == 0) { + + break; + } + + ut_ad(height > 0); + + height--; + + node_ptr = page_cur_get_rec(page_cursor); + offsets = rec_get_offsets(node_ptr, cursor->index, offsets, + ULINT_UNDEFINED, &heap); + /* Go to the child node */ + page_no = btr_node_ptr_get_child_page_no(node_ptr, offsets); + } + + if (UNIV_LIKELY_NULL(heap)) { + mem_heap_free(heap); + } +} + +/*==================== B-TREE INSERT =========================*/ + +/***************************************************************** +Inserts a record if there is enough space, or if enough space can +be freed by reorganizing. Differs from btr_cur_optimistic_insert because +no heuristics is applied to whether it pays to use CPU time for +reorganizing the page or not. */ +static +rec_t* +btr_cur_insert_if_possible( +/*=======================*/ + /* out: pointer to inserted record if succeed, + else NULL */ + btr_cur_t* cursor, /* in: cursor on page after which to insert; + cursor stays valid */ + const dtuple_t* tuple, /* in: tuple to insert; the size info need not + have been stored to tuple */ + ulint n_ext, /* in: number of externally stored columns */ + mtr_t* mtr) /* in: mtr */ +{ + page_cur_t* page_cursor; + buf_block_t* block; + rec_t* rec; + + ut_ad(dtuple_check_typed(tuple)); + + block = btr_cur_get_block(cursor); + + ut_ad(mtr_memo_contains(mtr, block, MTR_MEMO_PAGE_X_FIX)); + page_cursor = btr_cur_get_page_cur(cursor); + + /* Now, try the insert */ + rec = page_cur_tuple_insert(page_cursor, tuple, + cursor->index, n_ext, mtr); + + if (UNIV_UNLIKELY(!rec)) { + /* If record did not fit, reorganize */ + + if (btr_page_reorganize(block, cursor->index, mtr)) { + + page_cur_search(block, cursor->index, tuple, + PAGE_CUR_LE, page_cursor); + + rec = page_cur_tuple_insert(page_cursor, tuple, + cursor->index, n_ext, mtr); + } + } + + return(rec); +} + +/***************************************************************** +For an insert, checks the locks and does the undo logging if desired. */ +UNIV_INLINE +ulint +btr_cur_ins_lock_and_undo( +/*======================*/ + /* out: DB_SUCCESS, DB_WAIT_LOCK, + DB_FAIL, or error number */ + ulint flags, /* in: undo logging and locking flags: if + not zero, the parameters index and thr + should be specified */ + btr_cur_t* cursor, /* in: cursor on page after which to insert */ + const dtuple_t* entry, /* in: entry to insert */ + que_thr_t* thr, /* in: query thread or NULL */ + ibool* inherit)/* out: TRUE if the inserted new record maybe + should inherit LOCK_GAP type locks from the + successor record */ +{ + dict_index_t* index; + ulint err; + rec_t* rec; + dulint roll_ptr; + + /* Check if we have to wait for a lock: enqueue an explicit lock + request if yes */ + + rec = btr_cur_get_rec(cursor); + index = cursor->index; + + err = lock_rec_insert_check_and_lock(flags, rec, + btr_cur_get_block(cursor), + index, thr, inherit); + + if (err != DB_SUCCESS) { + + return(err); + } + + if (dict_index_is_clust(index) && !dict_index_is_ibuf(index)) { + + err = trx_undo_report_row_operation(flags, TRX_UNDO_INSERT_OP, + thr, index, entry, + NULL, 0, NULL, + &roll_ptr); + if (err != DB_SUCCESS) { + + return(err); + } + + /* Now we can fill in the roll ptr field in entry */ + + if (!(flags & BTR_KEEP_SYS_FLAG)) { + + row_upd_index_entry_sys_field(entry, index, + DATA_ROLL_PTR, roll_ptr); + } + } + + return(DB_SUCCESS); +} + +#ifdef UNIV_DEBUG +/***************************************************************** +Report information about a transaction. */ +static +void +btr_cur_trx_report( +/*===============*/ + trx_t* trx, /* in: transaction */ + const dict_index_t* index, /* in: index */ + const char* op) /* in: operation */ +{ + fprintf(stderr, "Trx with id " TRX_ID_FMT " going to ", + TRX_ID_PREP_PRINTF(trx->id)); + fputs(op, stderr); + dict_index_name_print(stderr, trx, index); + putc('\n', stderr); +} +#endif /* UNIV_DEBUG */ + +/***************************************************************** +Tries to perform an insert to a page in an index tree, next to cursor. +It is assumed that mtr holds an x-latch on the page. The operation does +not succeed if there is too little space on the page. If there is just +one record on the page, the insert will always succeed; this is to +prevent trying to split a page with just one record. */ +UNIV_INTERN +ulint +btr_cur_optimistic_insert( +/*======================*/ + /* out: DB_SUCCESS, DB_WAIT_LOCK, + DB_FAIL, or error number */ + ulint flags, /* in: undo logging and locking flags: if not + zero, the parameters index and thr should be + specified */ + btr_cur_t* cursor, /* in: cursor on page after which to insert; + cursor stays valid */ + dtuple_t* entry, /* in/out: entry to insert */ + rec_t** rec, /* out: pointer to inserted record if + succeed */ + big_rec_t** big_rec,/* out: big rec vector whose fields have to + be stored externally by the caller, or + NULL */ + ulint n_ext, /* in: number of externally stored columns */ + que_thr_t* thr, /* in: query thread or NULL */ + mtr_t* mtr) /* in: mtr; if this function returns + DB_SUCCESS on a leaf page of a secondary + index in a compressed tablespace, the + mtr must be committed before latching + any further pages */ +{ + big_rec_t* big_rec_vec = NULL; + dict_index_t* index; + page_cur_t* page_cursor; + buf_block_t* block; + page_t* page; + ulint max_size; + rec_t* dummy_rec; + ibool leaf; + ibool reorg; + ibool inherit; + ulint zip_size; + ulint rec_size; + mem_heap_t* heap = NULL; + ulint err; + + *big_rec = NULL; + + block = btr_cur_get_block(cursor); + page = buf_block_get_frame(block); + index = cursor->index; + zip_size = buf_block_get_zip_size(block); +#ifdef UNIV_DEBUG_VALGRIND + if (zip_size) { + UNIV_MEM_ASSERT_RW(page, UNIV_PAGE_SIZE); + UNIV_MEM_ASSERT_RW(block->page.zip.data, zip_size); + } +#endif /* UNIV_DEBUG_VALGRIND */ + + if (!dtuple_check_typed_no_assert(entry)) { + fputs("InnoDB: Error in a tuple to insert into ", stderr); + dict_index_name_print(stderr, thr_get_trx(thr), index); + } +#ifdef UNIV_DEBUG + if (btr_cur_print_record_ops && thr) { + btr_cur_trx_report(thr_get_trx(thr), index, "insert into "); + dtuple_print(stderr, entry); + } +#endif /* UNIV_DEBUG */ + + ut_ad(mtr_memo_contains(mtr, block, MTR_MEMO_PAGE_X_FIX)); + max_size = page_get_max_insert_size_after_reorganize(page, 1); + leaf = page_is_leaf(page); + + /* Calculate the record size when entry is converted to a record */ + rec_size = rec_get_converted_size(index, entry, n_ext); + + if (page_zip_rec_needs_ext(rec_size, page_is_comp(page), + dtuple_get_n_fields(entry), zip_size)) { + + /* The record is so big that we have to store some fields + externally on separate database pages */ + big_rec_vec = dtuple_convert_big_rec(index, entry, &n_ext); + + if (UNIV_UNLIKELY(big_rec_vec == NULL)) { + + return(DB_TOO_BIG_RECORD); + } + + rec_size = rec_get_converted_size(index, entry, n_ext); + } + + if (UNIV_UNLIKELY(zip_size)) { + /* Estimate the free space of an empty compressed page. + Subtract one byte for the encoded heap_no in the + modification log. */ + ulint free_space_zip = page_zip_empty_size( + cursor->index->n_fields, zip_size) - 1; + ulint n_uniq = dict_index_get_n_unique_in_tree(index); + + ut_ad(dict_table_is_comp(index->table)); + + /* There should be enough room for two node pointer + records on an empty non-leaf page. This prevents + infinite page splits. */ + + if (UNIV_LIKELY(entry->n_fields >= n_uniq) + && UNIV_UNLIKELY(REC_NODE_PTR_SIZE + + rec_get_converted_size_comp_prefix( + index, entry->fields, n_uniq, + NULL) + /* On a compressed page, there is + a two-byte entry in the dense + page directory for every record. + But there is no record header. */ + - (REC_N_NEW_EXTRA_BYTES - 2) + > free_space_zip / 2)) { + + if (big_rec_vec) { + dtuple_convert_back_big_rec( + index, entry, big_rec_vec); + } + + if (heap) { + mem_heap_free(heap); + } + + return(DB_TOO_BIG_RECORD); + } + } + + /* If there have been many consecutive inserts, and we are on the leaf + level, check if we have to split the page to reserve enough free space + for future updates of records. */ + + if (dict_index_is_clust(index) + && (page_get_n_recs(page) >= 2) + && UNIV_LIKELY(leaf) + && (dict_index_get_space_reserve() + rec_size > max_size) + && (btr_page_get_split_rec_to_right(cursor, &dummy_rec) + || btr_page_get_split_rec_to_left(cursor, &dummy_rec))) { +fail: + err = DB_FAIL; +fail_err: + + if (big_rec_vec) { + dtuple_convert_back_big_rec(index, entry, big_rec_vec); + } + + if (UNIV_LIKELY_NULL(heap)) { + mem_heap_free(heap); + } + + return(err); + } + + if (UNIV_UNLIKELY(max_size < BTR_CUR_PAGE_REORGANIZE_LIMIT + || max_size < rec_size) + && UNIV_LIKELY(page_get_n_recs(page) > 1) + && page_get_max_insert_size(page, 1) < rec_size) { + + goto fail; + } + + /* Check locks and write to the undo log, if specified */ + err = btr_cur_ins_lock_and_undo(flags, cursor, entry, thr, &inherit); + + if (UNIV_UNLIKELY(err != DB_SUCCESS)) { + + goto fail_err; + } + + page_cursor = btr_cur_get_page_cur(cursor); + + /* Now, try the insert */ + + { + const rec_t* page_cursor_rec = page_cur_get_rec(page_cursor); + *rec = page_cur_tuple_insert(page_cursor, entry, index, + n_ext, mtr); + reorg = page_cursor_rec != page_cur_get_rec(page_cursor); + + if (UNIV_UNLIKELY(reorg)) { + ut_a(zip_size); + ut_a(*rec); + } + } + + if (UNIV_UNLIKELY(!*rec) && UNIV_LIKELY(!reorg)) { + /* If the record did not fit, reorganize */ + if (UNIV_UNLIKELY(!btr_page_reorganize(block, index, mtr))) { + ut_a(zip_size); + + goto fail; + } + + ut_ad(zip_size + || page_get_max_insert_size(page, 1) == max_size); + + reorg = TRUE; + + page_cur_search(block, index, entry, PAGE_CUR_LE, page_cursor); + + *rec = page_cur_tuple_insert(page_cursor, entry, index, + n_ext, mtr); + + if (UNIV_UNLIKELY(!*rec)) { + if (UNIV_LIKELY(zip_size != 0)) { + + goto fail; + } + + fputs("InnoDB: Error: cannot insert tuple ", stderr); + dtuple_print(stderr, entry); + fputs(" into ", stderr); + dict_index_name_print(stderr, thr_get_trx(thr), index); + fprintf(stderr, "\nInnoDB: max insert size %lu\n", + (ulong) max_size); + ut_error; + } + } + + if (UNIV_LIKELY_NULL(heap)) { + mem_heap_free(heap); + } + +#ifdef BTR_CUR_HASH_ADAPT + if (!reorg && leaf && (cursor->flag == BTR_CUR_HASH)) { + btr_search_update_hash_node_on_insert(cursor); + } else { + btr_search_update_hash_on_insert(cursor); + } +#endif + + if (!(flags & BTR_NO_LOCKING_FLAG) && inherit) { + + lock_update_insert(block, *rec); + } + +#if 0 + fprintf(stderr, "Insert into page %lu, max ins size %lu," + " rec %lu ind type %lu\n", + buf_block_get_page_no(block), max_size, + rec_size + PAGE_DIR_SLOT_SIZE, index->type); +#endif + if (leaf + && !dict_index_is_clust(index) + && !dict_index_is_ibuf(index)) { + /* Update the free bits of the B-tree page in the + insert buffer bitmap. */ + + /* The free bits in the insert buffer bitmap must + never exceed the free space on a page. It is safe to + decrement or reset the bits in the bitmap in a + mini-transaction that is committed before the + mini-transaction that affects the free space. */ + + /* It is unsafe to increment the bits in a separately + committed mini-transaction, because in crash recovery, + the free bits could momentarily be set too high. */ + + if (zip_size) { + /* Update the bits in the same mini-transaction. */ + ibuf_update_free_bits_zip(block, mtr); + } else { + /* Decrement the bits in a separate + mini-transaction. */ + ibuf_update_free_bits_if_full( + block, max_size, + rec_size + PAGE_DIR_SLOT_SIZE); + } + } + + *big_rec = big_rec_vec; + + return(DB_SUCCESS); +} + +/***************************************************************** +Performs an insert on a page of an index tree. It is assumed that mtr +holds an x-latch on the tree and on the cursor page. If the insert is +made on the leaf level, to avoid deadlocks, mtr must also own x-latches +to brothers of page, if those brothers exist. */ +UNIV_INTERN +ulint +btr_cur_pessimistic_insert( +/*=======================*/ + /* out: DB_SUCCESS or error number */ + ulint flags, /* in: undo logging and locking flags: if not + zero, the parameter thr should be + specified; if no undo logging is specified, + then the caller must have reserved enough + free extents in the file space so that the + insertion will certainly succeed */ + btr_cur_t* cursor, /* in: cursor after which to insert; + cursor stays valid */ + dtuple_t* entry, /* in/out: entry to insert */ + rec_t** rec, /* out: pointer to inserted record if + succeed */ + big_rec_t** big_rec,/* out: big rec vector whose fields have to + be stored externally by the caller, or + NULL */ + ulint n_ext, /* in: number of externally stored columns */ + que_thr_t* thr, /* in: query thread or NULL */ + mtr_t* mtr) /* in: mtr */ +{ + dict_index_t* index = cursor->index; + ulint zip_size = dict_table_zip_size(index->table); + big_rec_t* big_rec_vec = NULL; + mem_heap_t* heap = NULL; + ulint err; + ibool dummy_inh; + ibool success; + ulint n_extents = 0; + ulint n_reserved; + + ut_ad(dtuple_check_typed(entry)); + + *big_rec = NULL; + + ut_ad(mtr_memo_contains(mtr, + dict_index_get_lock(btr_cur_get_index(cursor)), + MTR_MEMO_X_LOCK)); + ut_ad(mtr_memo_contains(mtr, btr_cur_get_block(cursor), + MTR_MEMO_PAGE_X_FIX)); + + /* Try first an optimistic insert; reset the cursor flag: we do not + assume anything of how it was positioned */ + + cursor->flag = BTR_CUR_BINARY; + + err = btr_cur_optimistic_insert(flags, cursor, entry, rec, + big_rec, n_ext, thr, mtr); + if (err != DB_FAIL) { + + return(err); + } + + /* Retry with a pessimistic insert. Check locks and write to undo log, + if specified */ + + err = btr_cur_ins_lock_and_undo(flags, cursor, entry, thr, &dummy_inh); + + if (err != DB_SUCCESS) { + + return(err); + } + + if (!(flags & BTR_NO_UNDO_LOG_FLAG)) { + /* First reserve enough free space for the file segments + of the index tree, so that the insert will not fail because + of lack of space */ + + n_extents = cursor->tree_height / 16 + 3; + + success = fsp_reserve_free_extents(&n_reserved, index->space, + n_extents, FSP_NORMAL, mtr); + if (!success) { + return(DB_OUT_OF_FILE_SPACE); + } + } + + if (page_zip_rec_needs_ext(rec_get_converted_size(index, entry, n_ext), + dict_table_is_comp(index->table), + dict_index_get_n_fields(index), + zip_size)) { + /* The record is so big that we have to store some fields + externally on separate database pages */ + + if (UNIV_LIKELY_NULL(big_rec_vec)) { + /* This should never happen, but we handle + the situation in a robust manner. */ + ut_ad(0); + dtuple_convert_back_big_rec(index, entry, big_rec_vec); + } + + big_rec_vec = dtuple_convert_big_rec(index, entry, &n_ext); + + if (big_rec_vec == NULL) { + + if (n_extents > 0) { + fil_space_release_free_extents(index->space, + n_reserved); + } + return(DB_TOO_BIG_RECORD); + } + } + + if (dict_index_get_page(index) + == buf_block_get_page_no(btr_cur_get_block(cursor))) { + + /* The page is the root page */ + *rec = btr_root_raise_and_insert(cursor, entry, n_ext, mtr); + } else { + *rec = btr_page_split_and_insert(cursor, entry, n_ext, mtr); + } + + if (UNIV_LIKELY_NULL(heap)) { + mem_heap_free(heap); + } + + ut_ad(page_rec_get_next(btr_cur_get_rec(cursor)) == *rec); + +#ifdef BTR_CUR_ADAPT + btr_search_update_hash_on_insert(cursor); +#endif + if (!(flags & BTR_NO_LOCKING_FLAG)) { + + lock_update_insert(btr_cur_get_block(cursor), *rec); + } + + if (n_extents > 0) { + fil_space_release_free_extents(index->space, n_reserved); + } + + *big_rec = big_rec_vec; + + return(DB_SUCCESS); +} + +/*==================== B-TREE UPDATE =========================*/ + +/***************************************************************** +For an update, checks the locks and does the undo logging. */ +UNIV_INLINE +ulint +btr_cur_upd_lock_and_undo( +/*======================*/ + /* out: DB_SUCCESS, DB_WAIT_LOCK, or error + number */ + ulint flags, /* in: undo logging and locking flags */ + btr_cur_t* cursor, /* in: cursor on record to update */ + const upd_t* update, /* in: update vector */ + ulint cmpl_info,/* in: compiler info on secondary index + updates */ + que_thr_t* thr, /* in: query thread */ + dulint* roll_ptr)/* out: roll pointer */ +{ + dict_index_t* index; + rec_t* rec; + ulint err; + + ut_ad(cursor && update && thr && roll_ptr); + + rec = btr_cur_get_rec(cursor); + index = cursor->index; + + if (!dict_index_is_clust(index)) { + /* We do undo logging only when we update a clustered index + record */ + return(lock_sec_rec_modify_check_and_lock( + flags, btr_cur_get_block(cursor), rec, + index, thr)); + } + + /* Check if we have to wait for a lock: enqueue an explicit lock + request if yes */ + + err = DB_SUCCESS; + + if (!(flags & BTR_NO_LOCKING_FLAG)) { + mem_heap_t* heap = NULL; + ulint offsets_[REC_OFFS_NORMAL_SIZE]; + rec_offs_init(offsets_); + + err = lock_clust_rec_modify_check_and_lock( + flags, btr_cur_get_block(cursor), rec, index, + rec_get_offsets(rec, index, offsets_, + ULINT_UNDEFINED, &heap), thr); + if (UNIV_LIKELY_NULL(heap)) { + mem_heap_free(heap); + } + if (err != DB_SUCCESS) { + + return(err); + } + } + + /* Append the info about the update in the undo log */ + + err = trx_undo_report_row_operation(flags, TRX_UNDO_MODIFY_OP, thr, + index, NULL, update, + cmpl_info, rec, roll_ptr); + return(err); +} + +/*************************************************************** +Writes a redo log record of updating a record in-place. */ +UNIV_INLINE +void +btr_cur_update_in_place_log( +/*========================*/ + ulint flags, /* in: flags */ + rec_t* rec, /* in: record */ + dict_index_t* index, /* in: index where cursor positioned */ + const upd_t* update, /* in: update vector */ + trx_t* trx, /* in: transaction */ + dulint roll_ptr, /* in: roll ptr */ + mtr_t* mtr) /* in: mtr */ +{ + byte* log_ptr; + page_t* page = page_align(rec); + ut_ad(flags < 256); + ut_ad(!!page_is_comp(page) == dict_table_is_comp(index->table)); + + log_ptr = mlog_open_and_write_index(mtr, rec, index, page_is_comp(page) + ? MLOG_COMP_REC_UPDATE_IN_PLACE + : MLOG_REC_UPDATE_IN_PLACE, + 1 + DATA_ROLL_PTR_LEN + 14 + 2 + + MLOG_BUF_MARGIN); + + if (!log_ptr) { + /* Logging in mtr is switched off during crash recovery */ + return; + } + + /* The code below assumes index is a clustered index: change index to + the clustered index if we are updating a secondary index record (or we + could as well skip writing the sys col values to the log in this case + because they are not needed for a secondary index record update) */ + + index = dict_table_get_first_index(index->table); + + mach_write_to_1(log_ptr, flags); + log_ptr++; + + log_ptr = row_upd_write_sys_vals_to_log(index, trx, roll_ptr, log_ptr, + mtr); + mach_write_to_2(log_ptr, page_offset(rec)); + log_ptr += 2; + + row_upd_index_write_log(update, log_ptr, mtr); +} + +/*************************************************************** +Parses a redo log record of updating a record in-place. */ +UNIV_INTERN +byte* +btr_cur_parse_update_in_place( +/*==========================*/ + /* out: end of log record or NULL */ + byte* ptr, /* in: buffer */ + byte* end_ptr,/* in: buffer end */ + page_t* page, /* in/out: page or NULL */ + page_zip_des_t* page_zip,/* in/out: compressed page, or NULL */ + dict_index_t* index) /* in: index corresponding to page */ +{ + ulint flags; + rec_t* rec; + upd_t* update; + ulint pos; + dulint trx_id; + dulint roll_ptr; + ulint rec_offset; + mem_heap_t* heap; + ulint* offsets; + + if (end_ptr < ptr + 1) { + + return(NULL); + } + + flags = mach_read_from_1(ptr); + ptr++; + + ptr = row_upd_parse_sys_vals(ptr, end_ptr, &pos, &trx_id, &roll_ptr); + + if (ptr == NULL) { + + return(NULL); + } + + if (end_ptr < ptr + 2) { + + return(NULL); + } + + rec_offset = mach_read_from_2(ptr); + ptr += 2; + + ut_a(rec_offset <= UNIV_PAGE_SIZE); + + heap = mem_heap_create(256); + + ptr = row_upd_index_parse(ptr, end_ptr, heap, &update); + + if (!ptr || !page) { + + goto func_exit; + } + + ut_a((ibool)!!page_is_comp(page) == dict_table_is_comp(index->table)); + rec = page + rec_offset; + + /* We do not need to reserve btr_search_latch, as the page is only + being recovered, and there cannot be a hash index to it. */ + + offsets = rec_get_offsets(rec, index, NULL, ULINT_UNDEFINED, &heap); + + if (!(flags & BTR_KEEP_SYS_FLAG)) { + row_upd_rec_sys_fields_in_recovery(rec, page_zip, offsets, + pos, trx_id, roll_ptr); + } + + row_upd_rec_in_place(rec, index, offsets, update, page_zip); + +func_exit: + mem_heap_free(heap); + + return(ptr); +} + +/***************************************************************** +See if there is enough place in the page modification log to log +an update-in-place. */ +static +ibool +btr_cur_update_alloc_zip( +/*=====================*/ + /* out: TRUE if enough place */ + page_zip_des_t* page_zip,/* in/out: compressed page */ + buf_block_t* block, /* in/out: buffer page */ + dict_index_t* index, /* in: the index corresponding to the block */ + ulint length, /* in: size needed */ + mtr_t* mtr) /* in: mini-transaction */ +{ + ut_a(page_zip == buf_block_get_page_zip(block)); + ut_ad(page_zip); + ut_ad(!dict_index_is_ibuf(index)); + + if (page_zip_available(page_zip, dict_index_is_clust(index), + length, 0)) { + return(TRUE); + } + + if (!page_zip->m_nonempty) { + /* The page has been freshly compressed, so + recompressing it will not help. */ + return(FALSE); + } + + if (!page_zip_compress(page_zip, buf_block_get_frame(block), + index, mtr)) { + /* Unable to compress the page */ + return(FALSE); + } + + /* After recompressing a page, we must make sure that the free + bits in the insert buffer bitmap will not exceed the free + space on the page. Because this function will not attempt + recompression unless page_zip_available() fails above, it is + safe to reset the free bits if page_zip_available() fails + again, below. The free bits can safely be reset in a separate + mini-transaction. If page_zip_available() succeeds below, we + can be sure that the page_zip_compress() above did not reduce + the free space available on the page. */ + + if (!page_zip_available(page_zip, dict_index_is_clust(index), + length, 0)) { + /* Out of space: reset the free bits. */ + if (!dict_index_is_clust(index) + && page_is_leaf(buf_block_get_frame(block))) { + ibuf_reset_free_bits(block); + } + return(FALSE); + } + + return(TRUE); +} + +/***************************************************************** +Updates a record when the update causes no size changes in its fields. +We assume here that the ordering fields of the record do not change. */ +UNIV_INTERN +ulint +btr_cur_update_in_place( +/*====================*/ + /* out: DB_SUCCESS or error number */ + ulint flags, /* in: undo logging and locking flags */ + btr_cur_t* cursor, /* in: cursor on the record to update; + cursor stays valid and positioned on the + same record */ + const upd_t* update, /* in: update vector */ + ulint cmpl_info,/* in: compiler info on secondary index + updates */ + que_thr_t* thr, /* in: query thread */ + mtr_t* mtr) /* in: mtr; must be committed before + latching any further pages */ +{ + dict_index_t* index; + buf_block_t* block; + page_zip_des_t* page_zip; + ulint err; + rec_t* rec; + dulint roll_ptr = ut_dulint_zero; + trx_t* trx; + ulint was_delete_marked; + mem_heap_t* heap = NULL; + ulint offsets_[REC_OFFS_NORMAL_SIZE]; + ulint* offsets = offsets_; + rec_offs_init(offsets_); + + rec = btr_cur_get_rec(cursor); + index = cursor->index; + ut_ad(!!page_rec_is_comp(rec) == dict_table_is_comp(index->table)); + /* The insert buffer tree should never be updated in place. */ + ut_ad(!dict_index_is_ibuf(index)); + + trx = thr_get_trx(thr); + offsets = rec_get_offsets(rec, index, offsets, ULINT_UNDEFINED, &heap); +#ifdef UNIV_DEBUG + if (btr_cur_print_record_ops && thr) { + btr_cur_trx_report(trx, index, "update "); + rec_print_new(stderr, rec, offsets); + } +#endif /* UNIV_DEBUG */ + + block = btr_cur_get_block(cursor); + page_zip = buf_block_get_page_zip(block); + + /* Check that enough space is available on the compressed page. */ + if (UNIV_LIKELY_NULL(page_zip) + && !btr_cur_update_alloc_zip(page_zip, block, index, + rec_offs_size(offsets), mtr)) { + return(DB_ZIP_OVERFLOW); + } + + /* Do lock checking and undo logging */ + err = btr_cur_upd_lock_and_undo(flags, cursor, update, cmpl_info, + thr, &roll_ptr); + if (UNIV_UNLIKELY(err != DB_SUCCESS)) { + + if (UNIV_LIKELY_NULL(heap)) { + mem_heap_free(heap); + } + return(err); + } + + if (block->is_hashed) { + /* The function row_upd_changes_ord_field_binary works only + if the update vector was built for a clustered index, we must + NOT call it if index is secondary */ + + if (!dict_index_is_clust(index) + || row_upd_changes_ord_field_binary(NULL, index, update)) { + + /* Remove possible hash index pointer to this record */ + btr_search_update_hash_on_delete(cursor); + } + + rw_lock_x_lock(&btr_search_latch); + } + + if (!(flags & BTR_KEEP_SYS_FLAG)) { + row_upd_rec_sys_fields(rec, NULL, + index, offsets, trx, roll_ptr); + } + + was_delete_marked = rec_get_deleted_flag( + rec, page_is_comp(buf_block_get_frame(block))); + + row_upd_rec_in_place(rec, index, offsets, update, page_zip); + + if (block->is_hashed) { + rw_lock_x_unlock(&btr_search_latch); + } + + if (page_zip && !dict_index_is_clust(index) + && page_is_leaf(buf_block_get_frame(block))) { + /* Update the free bits in the insert buffer. */ + ibuf_update_free_bits_zip(block, mtr); + } + + btr_cur_update_in_place_log(flags, rec, index, update, + trx, roll_ptr, mtr); + + if (was_delete_marked + && !rec_get_deleted_flag(rec, page_is_comp( + buf_block_get_frame(block)))) { + /* The new updated record owns its possible externally + stored fields */ + + btr_cur_unmark_extern_fields(page_zip, + rec, index, offsets, mtr); + } + + if (UNIV_LIKELY_NULL(heap)) { + mem_heap_free(heap); + } + return(DB_SUCCESS); +} + +/***************************************************************** +Tries to update a record on a page in an index tree. It is assumed that mtr +holds an x-latch on the page. The operation does not succeed if there is too +little space on the page or if the update would result in too empty a page, +so that tree compression is recommended. We assume here that the ordering +fields of the record do not change. */ +UNIV_INTERN +ulint +btr_cur_optimistic_update( +/*======================*/ + /* out: DB_SUCCESS, or DB_OVERFLOW if the + updated record does not fit, DB_UNDERFLOW + if the page would become too empty, or + DB_ZIP_OVERFLOW if there is not enough + space left on the compressed page */ + ulint flags, /* in: undo logging and locking flags */ + btr_cur_t* cursor, /* in: cursor on the record to update; + cursor stays valid and positioned on the + same record */ + const upd_t* update, /* in: update vector; this must also + contain trx id and roll ptr fields */ + ulint cmpl_info,/* in: compiler info on secondary index + updates */ + que_thr_t* thr, /* in: query thread */ + mtr_t* mtr) /* in: mtr; must be committed before + latching any further pages */ +{ + dict_index_t* index; + page_cur_t* page_cursor; + ulint err; + buf_block_t* block; + page_t* page; + page_zip_des_t* page_zip; + rec_t* rec; + rec_t* orig_rec; + ulint max_size; + ulint new_rec_size; + ulint old_rec_size; + dtuple_t* new_entry; + dulint roll_ptr; + trx_t* trx; + mem_heap_t* heap; + ulint i; + ulint n_ext; + ulint* offsets; + + block = btr_cur_get_block(cursor); + page = buf_block_get_frame(block); + orig_rec = rec = btr_cur_get_rec(cursor); + index = cursor->index; + ut_ad(!!page_rec_is_comp(rec) == dict_table_is_comp(index->table)); + ut_ad(mtr_memo_contains(mtr, block, MTR_MEMO_PAGE_X_FIX)); + /* The insert buffer tree should never be updated in place. */ + ut_ad(!dict_index_is_ibuf(index)); + + heap = mem_heap_create(1024); + offsets = rec_get_offsets(rec, index, NULL, ULINT_UNDEFINED, &heap); + +#ifdef UNIV_DEBUG + if (btr_cur_print_record_ops && thr) { + btr_cur_trx_report(thr_get_trx(thr), index, "update "); + rec_print_new(stderr, rec, offsets); + } +#endif /* UNIV_DEBUG */ + + if (!row_upd_changes_field_size_or_external(index, offsets, update)) { + + /* The simplest and the most common case: the update does not + change the size of any field and none of the updated fields is + externally stored in rec or update, and there is enough space + on the compressed page to log the update. */ + + mem_heap_free(heap); + return(btr_cur_update_in_place(flags, cursor, update, + cmpl_info, thr, mtr)); + } + + if (rec_offs_any_extern(offsets)) { +any_extern: + /* Externally stored fields are treated in pessimistic + update */ + + mem_heap_free(heap); + return(DB_OVERFLOW); + } + + for (i = 0; i < upd_get_n_fields(update); i++) { + if (dfield_is_ext(&upd_get_nth_field(update, i)->new_val)) { + + goto any_extern; + } + } + + page_cursor = btr_cur_get_page_cur(cursor); + + new_entry = row_rec_to_index_entry(ROW_COPY_DATA, rec, index, offsets, + &n_ext, heap); + /* We checked above that there are no externally stored fields. */ + ut_a(!n_ext); + + /* The page containing the clustered index record + corresponding to new_entry is latched in mtr. + Thus the following call is safe. */ + row_upd_index_replace_new_col_vals_index_pos(new_entry, index, update, + FALSE, heap); + old_rec_size = rec_offs_size(offsets); + new_rec_size = rec_get_converted_size(index, new_entry, 0); + + page_zip = buf_block_get_page_zip(block); +#ifdef UNIV_ZIP_DEBUG + ut_a(!page_zip || page_zip_validate(page_zip, page)); +#endif /* UNIV_ZIP_DEBUG */ + + if (UNIV_LIKELY_NULL(page_zip) + && !btr_cur_update_alloc_zip(page_zip, block, index, + new_rec_size, mtr)) { + err = DB_ZIP_OVERFLOW; + goto err_exit; + } + + if (UNIV_UNLIKELY(new_rec_size + >= (page_get_free_space_of_empty(page_is_comp(page)) + / 2))) { + + err = DB_OVERFLOW; + goto err_exit; + } + + if (UNIV_UNLIKELY(page_get_data_size(page) + - old_rec_size + new_rec_size + < BTR_CUR_PAGE_COMPRESS_LIMIT)) { + + /* The page would become too empty */ + + err = DB_UNDERFLOW; + goto err_exit; + } + + max_size = old_rec_size + + page_get_max_insert_size_after_reorganize(page, 1); + + if (!(((max_size >= BTR_CUR_PAGE_REORGANIZE_LIMIT) + && (max_size >= new_rec_size)) + || (page_get_n_recs(page) <= 1))) { + + /* There was not enough space, or it did not pay to + reorganize: for simplicity, we decide what to do assuming a + reorganization is needed, though it might not be necessary */ + + err = DB_OVERFLOW; + goto err_exit; + } + + /* Do lock checking and undo logging */ + err = btr_cur_upd_lock_and_undo(flags, cursor, update, cmpl_info, thr, + &roll_ptr); + if (err != DB_SUCCESS) { +err_exit: + mem_heap_free(heap); + return(err); + } + + /* Ok, we may do the replacement. Store on the page infimum the + explicit locks on rec, before deleting rec (see the comment in + btr_cur_pessimistic_update). */ + + lock_rec_store_on_page_infimum(block, rec); + + btr_search_update_hash_on_delete(cursor); + + /* The call to row_rec_to_index_entry(ROW_COPY_DATA, ...) above + invokes rec_offs_make_valid() to point to the copied record that + the fields of new_entry point to. We have to undo it here. */ + ut_ad(rec_offs_validate(NULL, index, offsets)); + rec_offs_make_valid(page_cur_get_rec(page_cursor), index, offsets); + + page_cur_delete_rec(page_cursor, index, offsets, mtr); + + page_cur_move_to_prev(page_cursor); + + trx = thr_get_trx(thr); + + if (!(flags & BTR_KEEP_SYS_FLAG)) { + row_upd_index_entry_sys_field(new_entry, index, DATA_ROLL_PTR, + roll_ptr); + row_upd_index_entry_sys_field(new_entry, index, DATA_TRX_ID, + trx->id); + } + + /* There are no externally stored columns in new_entry */ + rec = btr_cur_insert_if_possible(cursor, new_entry, 0/*n_ext*/, mtr); + ut_a(rec); /* <- We calculated above the insert would fit */ + + if (page_zip && !dict_index_is_clust(index) + && page_is_leaf(page)) { + /* Update the free bits in the insert buffer. */ + ibuf_update_free_bits_zip(block, mtr); + } + + /* Restore the old explicit lock state on the record */ + + lock_rec_restore_from_page_infimum(block, rec, block); + + page_cur_move_to_next(page_cursor); + + mem_heap_free(heap); + + return(DB_SUCCESS); +} + +/***************************************************************** +If, in a split, a new supremum record was created as the predecessor of the +updated record, the supremum record must inherit exactly the locks on the +updated record. In the split it may have inherited locks from the successor +of the updated record, which is not correct. This function restores the +right locks for the new supremum. */ +static +void +btr_cur_pess_upd_restore_supremum( +/*==============================*/ + buf_block_t* block, /* in: buffer block of rec */ + const rec_t* rec, /* in: updated record */ + mtr_t* mtr) /* in: mtr */ +{ + page_t* page; + buf_block_t* prev_block; + ulint space; + ulint zip_size; + ulint prev_page_no; + + page = buf_block_get_frame(block); + + if (page_rec_get_next(page_get_infimum_rec(page)) != rec) { + /* Updated record is not the first user record on its page */ + + return; + } + + space = buf_block_get_space(block); + zip_size = buf_block_get_zip_size(block); + prev_page_no = btr_page_get_prev(page, mtr); + + ut_ad(prev_page_no != FIL_NULL); + prev_block = buf_page_get_with_no_latch(space, zip_size, + prev_page_no, mtr); +#ifdef UNIV_BTR_DEBUG + ut_a(btr_page_get_next(prev_block->frame, mtr) + == page_get_page_no(page)); +#endif /* UNIV_BTR_DEBUG */ + + /* We must already have an x-latch on prev_block! */ + ut_ad(mtr_memo_contains(mtr, prev_block, MTR_MEMO_PAGE_X_FIX)); + + lock_rec_reset_and_inherit_gap_locks(prev_block, block, + PAGE_HEAP_NO_SUPREMUM, + page_rec_get_heap_no(rec)); +} + +/***************************************************************** +Performs an update of a record on a page of a tree. It is assumed +that mtr holds an x-latch on the tree and on the cursor page. If the +update is made on the leaf level, to avoid deadlocks, mtr must also +own x-latches to brothers of page, if those brothers exist. We assume +here that the ordering fields of the record do not change. */ +UNIV_INTERN +ulint +btr_cur_pessimistic_update( +/*=======================*/ + /* out: DB_SUCCESS or error code */ + ulint flags, /* in: undo logging, locking, and rollback + flags */ + btr_cur_t* cursor, /* in: cursor on the record to update */ + mem_heap_t** heap, /* in/out: pointer to memory heap, or NULL */ + big_rec_t** big_rec,/* out: big rec vector whose fields have to + be stored externally by the caller, or NULL */ + const upd_t* update, /* in: update vector; this is allowed also + contain trx id and roll ptr fields, but + the values in update vector have no effect */ + ulint cmpl_info,/* in: compiler info on secondary index + updates */ + que_thr_t* thr, /* in: query thread */ + mtr_t* mtr) /* in: mtr; must be committed before + latching any further pages */ +{ + big_rec_t* big_rec_vec = NULL; + big_rec_t* dummy_big_rec; + dict_index_t* index; + buf_block_t* block; + page_t* page; + page_zip_des_t* page_zip; + rec_t* rec; + page_cur_t* page_cursor; + dtuple_t* new_entry; + ulint err; + ulint optim_err; + dulint roll_ptr; + trx_t* trx; + ibool was_first; + ulint n_extents = 0; + ulint n_reserved; + ulint n_ext; + ulint* offsets = NULL; + + *big_rec = NULL; + + block = btr_cur_get_block(cursor); + page = buf_block_get_frame(block); + page_zip = buf_block_get_page_zip(block); + rec = btr_cur_get_rec(cursor); + index = cursor->index; + + ut_ad(mtr_memo_contains(mtr, dict_index_get_lock(index), + MTR_MEMO_X_LOCK)); + ut_ad(mtr_memo_contains(mtr, block, MTR_MEMO_PAGE_X_FIX)); +#ifdef UNIV_ZIP_DEBUG + ut_a(!page_zip || page_zip_validate(page_zip, page)); +#endif /* UNIV_ZIP_DEBUG */ + /* The insert buffer tree should never be updated in place. */ + ut_ad(!dict_index_is_ibuf(index)); + + optim_err = btr_cur_optimistic_update(flags, cursor, update, + cmpl_info, thr, mtr); + + switch (optim_err) { + case DB_UNDERFLOW: + case DB_OVERFLOW: + case DB_ZIP_OVERFLOW: + break; + default: + return(optim_err); + } + + /* Do lock checking and undo logging */ + err = btr_cur_upd_lock_and_undo(flags, cursor, update, cmpl_info, + thr, &roll_ptr); + if (err != DB_SUCCESS) { + + return(err); + } + + if (optim_err == DB_OVERFLOW) { + ulint reserve_flag; + + /* First reserve enough free space for the file segments + of the index tree, so that the update will not fail because + of lack of space */ + + n_extents = cursor->tree_height / 16 + 3; + + if (flags & BTR_NO_UNDO_LOG_FLAG) { + reserve_flag = FSP_CLEANING; + } else { + reserve_flag = FSP_NORMAL; + } + + if (!fsp_reserve_free_extents(&n_reserved, index->space, + n_extents, reserve_flag, mtr)) { + return(DB_OUT_OF_FILE_SPACE); + } + } + + if (!*heap) { + *heap = mem_heap_create(1024); + } + offsets = rec_get_offsets(rec, index, NULL, ULINT_UNDEFINED, heap); + + trx = thr_get_trx(thr); + + new_entry = row_rec_to_index_entry(ROW_COPY_DATA, rec, index, offsets, + &n_ext, *heap); + /* The call to row_rec_to_index_entry(ROW_COPY_DATA, ...) above + invokes rec_offs_make_valid() to point to the copied record that + the fields of new_entry point to. We have to undo it here. */ + ut_ad(rec_offs_validate(NULL, index, offsets)); + rec_offs_make_valid(rec, index, offsets); + + /* The page containing the clustered index record + corresponding to new_entry is latched in mtr. If the + clustered index record is delete-marked, then its externally + stored fields cannot have been purged yet, because then the + purge would also have removed the clustered index record + itself. Thus the following call is safe. */ + row_upd_index_replace_new_col_vals_index_pos(new_entry, index, update, + FALSE, *heap); + if (!(flags & BTR_KEEP_SYS_FLAG)) { + row_upd_index_entry_sys_field(new_entry, index, DATA_ROLL_PTR, + roll_ptr); + row_upd_index_entry_sys_field(new_entry, index, DATA_TRX_ID, + trx->id); + } + + if ((flags & BTR_NO_UNDO_LOG_FLAG) && rec_offs_any_extern(offsets)) { + /* We are in a transaction rollback undoing a row + update: we must free possible externally stored fields + which got new values in the update, if they are not + inherited values. They can be inherited if we have + updated the primary key to another value, and then + update it back again. */ + + ut_ad(big_rec_vec == NULL); + + btr_rec_free_updated_extern_fields( + index, rec, page_zip, offsets, update, + trx_is_recv(trx) ? RB_RECOVERY : RB_NORMAL, mtr); + } + + /* We have to set appropriate extern storage bits in the new + record to be inserted: we have to remember which fields were such */ + + ut_ad(!page_is_comp(page) || !rec_get_node_ptr_flag(rec)); + offsets = rec_get_offsets(rec, index, offsets, ULINT_UNDEFINED, heap); + n_ext += btr_push_update_extern_fields(new_entry, update, *heap); + + if (UNIV_LIKELY_NULL(page_zip)) { + ut_ad(page_is_comp(page)); + if (page_zip_rec_needs_ext( + rec_get_converted_size(index, new_entry, n_ext), + TRUE, + dict_index_get_n_fields(index), + page_zip_get_size(page_zip))) { + + goto make_external; + } + } else if (page_zip_rec_needs_ext( + rec_get_converted_size(index, new_entry, n_ext), + page_is_comp(page), 0, 0)) { +make_external: + big_rec_vec = dtuple_convert_big_rec(index, new_entry, &n_ext); + if (UNIV_UNLIKELY(big_rec_vec == NULL)) { + + err = DB_TOO_BIG_RECORD; + goto return_after_reservations; + } + } + + /* Store state of explicit locks on rec on the page infimum record, + before deleting rec. The page infimum acts as a dummy carrier of the + locks, taking care also of lock releases, before we can move the locks + back on the actual record. There is a special case: if we are + inserting on the root page and the insert causes a call of + btr_root_raise_and_insert. Therefore we cannot in the lock system + delete the lock structs set on the root page even if the root + page carries just node pointers. */ + + lock_rec_store_on_page_infimum(block, rec); + + btr_search_update_hash_on_delete(cursor); + +#ifdef UNIV_ZIP_DEBUG + ut_a(!page_zip || page_zip_validate(page_zip, page)); +#endif /* UNIV_ZIP_DEBUG */ + page_cursor = btr_cur_get_page_cur(cursor); + + page_cur_delete_rec(page_cursor, index, offsets, mtr); + + page_cur_move_to_prev(page_cursor); + + rec = btr_cur_insert_if_possible(cursor, new_entry, n_ext, mtr); + + if (rec) { + lock_rec_restore_from_page_infimum(btr_cur_get_block(cursor), + rec, block); + + offsets = rec_get_offsets(rec, index, offsets, + ULINT_UNDEFINED, heap); + + if (!rec_get_deleted_flag(rec, rec_offs_comp(offsets))) { + /* The new inserted record owns its possible externally + stored fields */ + btr_cur_unmark_extern_fields(page_zip, + rec, index, offsets, mtr); + } + + btr_cur_compress_if_useful(cursor, mtr); + + if (page_zip && !dict_index_is_clust(index) + && page_is_leaf(page)) { + /* Update the free bits in the insert buffer. */ + ibuf_update_free_bits_zip(block, mtr); + } + + err = DB_SUCCESS; + goto return_after_reservations; + } else { + ut_a(optim_err != DB_UNDERFLOW); + + /* Out of space: reset the free bits. */ + if (!dict_index_is_clust(index) + && page_is_leaf(page)) { + ibuf_reset_free_bits(block); + } + } + + /* Was the record to be updated positioned as the first user + record on its page? */ + was_first = page_cur_is_before_first(page_cursor); + + /* The first parameter means that no lock checking and undo logging + is made in the insert */ + + err = btr_cur_pessimistic_insert(BTR_NO_UNDO_LOG_FLAG + | BTR_NO_LOCKING_FLAG + | BTR_KEEP_SYS_FLAG, + cursor, new_entry, &rec, + &dummy_big_rec, n_ext, NULL, mtr); + ut_a(rec); + ut_a(err == DB_SUCCESS); + ut_a(dummy_big_rec == NULL); + + if (!rec_get_deleted_flag(rec, rec_offs_comp(offsets))) { + /* The new inserted record owns its possible externally + stored fields */ + buf_block_t* rec_block = btr_cur_get_block(cursor); + +#ifdef UNIV_ZIP_DEBUG + ut_a(!page_zip || page_zip_validate(page_zip, page)); + page = buf_block_get_frame(rec_block); +#endif /* UNIV_ZIP_DEBUG */ + page_zip = buf_block_get_page_zip(rec_block); + + offsets = rec_get_offsets(rec, index, offsets, + ULINT_UNDEFINED, heap); + btr_cur_unmark_extern_fields(page_zip, + rec, index, offsets, mtr); + } + + lock_rec_restore_from_page_infimum(btr_cur_get_block(cursor), + rec, block); + + /* If necessary, restore also the correct lock state for a new, + preceding supremum record created in a page split. While the old + record was nonexistent, the supremum might have inherited its locks + from a wrong record. */ + + if (!was_first) { + btr_cur_pess_upd_restore_supremum(btr_cur_get_block(cursor), + rec, mtr); + } + +return_after_reservations: +#ifdef UNIV_ZIP_DEBUG + ut_a(!page_zip || page_zip_validate(page_zip, page)); +#endif /* UNIV_ZIP_DEBUG */ + + if (n_extents > 0) { + fil_space_release_free_extents(index->space, n_reserved); + } + + *big_rec = big_rec_vec; + + return(err); +} + +/*==================== B-TREE DELETE MARK AND UNMARK ===============*/ + +/******************************************************************** +Writes the redo log record for delete marking or unmarking of an index +record. */ +UNIV_INLINE +void +btr_cur_del_mark_set_clust_rec_log( +/*===============================*/ + ulint flags, /* in: flags */ + rec_t* rec, /* in: record */ + dict_index_t* index, /* in: index of the record */ + ibool val, /* in: value to set */ + trx_t* trx, /* in: deleting transaction */ + dulint roll_ptr,/* in: roll ptr to the undo log record */ + mtr_t* mtr) /* in: mtr */ +{ + byte* log_ptr; + ut_ad(flags < 256); + ut_ad(val <= 1); + + ut_ad(!!page_rec_is_comp(rec) == dict_table_is_comp(index->table)); + + log_ptr = mlog_open_and_write_index(mtr, rec, index, + page_rec_is_comp(rec) + ? MLOG_COMP_REC_CLUST_DELETE_MARK + : MLOG_REC_CLUST_DELETE_MARK, + 1 + 1 + DATA_ROLL_PTR_LEN + + 14 + 2); + + if (!log_ptr) { + /* Logging in mtr is switched off during crash recovery */ + return; + } + + mach_write_to_1(log_ptr, flags); + log_ptr++; + mach_write_to_1(log_ptr, val); + log_ptr++; + + log_ptr = row_upd_write_sys_vals_to_log(index, trx, roll_ptr, log_ptr, + mtr); + mach_write_to_2(log_ptr, page_offset(rec)); + log_ptr += 2; + + mlog_close(mtr, log_ptr); +} + +/******************************************************************** +Parses the redo log record for delete marking or unmarking of a clustered +index record. */ +UNIV_INTERN +byte* +btr_cur_parse_del_mark_set_clust_rec( +/*=================================*/ + /* out: end of log record or NULL */ + byte* ptr, /* in: buffer */ + byte* end_ptr,/* in: buffer end */ + page_t* page, /* in/out: page or NULL */ + page_zip_des_t* page_zip,/* in/out: compressed page, or NULL */ + dict_index_t* index) /* in: index corresponding to page */ +{ + ulint flags; + ulint val; + ulint pos; + dulint trx_id; + dulint roll_ptr; + ulint offset; + rec_t* rec; + + ut_ad(!page + || !!page_is_comp(page) == dict_table_is_comp(index->table)); + + if (end_ptr < ptr + 2) { + + return(NULL); + } + + flags = mach_read_from_1(ptr); + ptr++; + val = mach_read_from_1(ptr); + ptr++; + + ptr = row_upd_parse_sys_vals(ptr, end_ptr, &pos, &trx_id, &roll_ptr); + + if (ptr == NULL) { + + return(NULL); + } + + if (end_ptr < ptr + 2) { + + return(NULL); + } + + offset = mach_read_from_2(ptr); + ptr += 2; + + ut_a(offset <= UNIV_PAGE_SIZE); + + if (page) { + rec = page + offset; + + /* We do not need to reserve btr_search_latch, as the page + is only being recovered, and there cannot be a hash index to + it. */ + + btr_rec_set_deleted_flag(rec, page_zip, val); + + if (!(flags & BTR_KEEP_SYS_FLAG)) { + mem_heap_t* heap = NULL; + ulint offsets_[REC_OFFS_NORMAL_SIZE]; + rec_offs_init(offsets_); + + row_upd_rec_sys_fields_in_recovery( + rec, page_zip, + rec_get_offsets(rec, index, offsets_, + ULINT_UNDEFINED, &heap), + pos, trx_id, roll_ptr); + if (UNIV_LIKELY_NULL(heap)) { + mem_heap_free(heap); + } + } + } + + return(ptr); +} + +/*************************************************************** +Marks a clustered index record deleted. Writes an undo log record to +undo log on this delete marking. Writes in the trx id field the id +of the deleting transaction, and in the roll ptr field pointer to the +undo log record created. */ +UNIV_INTERN +ulint +btr_cur_del_mark_set_clust_rec( +/*===========================*/ + /* out: DB_SUCCESS, DB_LOCK_WAIT, or error + number */ + ulint flags, /* in: undo logging and locking flags */ + btr_cur_t* cursor, /* in: cursor */ + ibool val, /* in: value to set */ + que_thr_t* thr, /* in: query thread */ + mtr_t* mtr) /* in: mtr */ +{ + dict_index_t* index; + buf_block_t* block; + dulint roll_ptr; + ulint err; + rec_t* rec; + page_zip_des_t* page_zip; + trx_t* trx; + mem_heap_t* heap = NULL; + ulint offsets_[REC_OFFS_NORMAL_SIZE]; + ulint* offsets = offsets_; + rec_offs_init(offsets_); + + rec = btr_cur_get_rec(cursor); + index = cursor->index; + ut_ad(!!page_rec_is_comp(rec) == dict_table_is_comp(index->table)); + offsets = rec_get_offsets(rec, index, offsets, ULINT_UNDEFINED, &heap); + +#ifdef UNIV_DEBUG + if (btr_cur_print_record_ops && thr) { + btr_cur_trx_report(thr_get_trx(thr), index, "del mark "); + rec_print_new(stderr, rec, offsets); + } +#endif /* UNIV_DEBUG */ + + ut_ad(dict_index_is_clust(index)); + ut_ad(!rec_get_deleted_flag(rec, rec_offs_comp(offsets))); + + err = lock_clust_rec_modify_check_and_lock(flags, + btr_cur_get_block(cursor), + rec, index, offsets, thr); + + if (err != DB_SUCCESS) { + + goto func_exit; + } + + err = trx_undo_report_row_operation(flags, TRX_UNDO_MODIFY_OP, thr, + index, NULL, NULL, 0, rec, + &roll_ptr); + if (err != DB_SUCCESS) { + + goto func_exit; + } + + block = btr_cur_get_block(cursor); + + if (block->is_hashed) { + rw_lock_x_lock(&btr_search_latch); + } + + page_zip = buf_block_get_page_zip(block); + + btr_rec_set_deleted_flag(rec, page_zip, val); + + trx = thr_get_trx(thr); + + if (!(flags & BTR_KEEP_SYS_FLAG)) { + row_upd_rec_sys_fields(rec, page_zip, + index, offsets, trx, roll_ptr); + } + + if (block->is_hashed) { + rw_lock_x_unlock(&btr_search_latch); + } + + btr_cur_del_mark_set_clust_rec_log(flags, rec, index, val, trx, + roll_ptr, mtr); + +func_exit: + if (UNIV_LIKELY_NULL(heap)) { + mem_heap_free(heap); + } + return(err); +} + +/******************************************************************** +Writes the redo log record for a delete mark setting of a secondary +index record. */ +UNIV_INLINE +void +btr_cur_del_mark_set_sec_rec_log( +/*=============================*/ + rec_t* rec, /* in: record */ + ibool val, /* in: value to set */ + mtr_t* mtr) /* in: mtr */ +{ + byte* log_ptr; + ut_ad(val <= 1); + + log_ptr = mlog_open(mtr, 11 + 1 + 2); + + if (!log_ptr) { + /* Logging in mtr is switched off during crash recovery: + in that case mlog_open returns NULL */ + return; + } + + log_ptr = mlog_write_initial_log_record_fast( + rec, MLOG_REC_SEC_DELETE_MARK, log_ptr, mtr); + mach_write_to_1(log_ptr, val); + log_ptr++; + + mach_write_to_2(log_ptr, page_offset(rec)); + log_ptr += 2; + + mlog_close(mtr, log_ptr); +} + +/******************************************************************** +Parses the redo log record for delete marking or unmarking of a secondary +index record. */ +UNIV_INTERN +byte* +btr_cur_parse_del_mark_set_sec_rec( +/*===============================*/ + /* out: end of log record or NULL */ + byte* ptr, /* in: buffer */ + byte* end_ptr,/* in: buffer end */ + page_t* page, /* in/out: page or NULL */ + page_zip_des_t* page_zip)/* in/out: compressed page, or NULL */ +{ + ulint val; + ulint offset; + rec_t* rec; + + if (end_ptr < ptr + 3) { + + return(NULL); + } + + val = mach_read_from_1(ptr); + ptr++; + + offset = mach_read_from_2(ptr); + ptr += 2; + + ut_a(offset <= UNIV_PAGE_SIZE); + + if (page) { + rec = page + offset; + + /* We do not need to reserve btr_search_latch, as the page + is only being recovered, and there cannot be a hash index to + it. */ + + btr_rec_set_deleted_flag(rec, page_zip, val); + } + + return(ptr); +} + +/*************************************************************** +Sets a secondary index record delete mark to TRUE or FALSE. */ +UNIV_INTERN +ulint +btr_cur_del_mark_set_sec_rec( +/*=========================*/ + /* out: DB_SUCCESS, DB_LOCK_WAIT, or error + number */ + ulint flags, /* in: locking flag */ + btr_cur_t* cursor, /* in: cursor */ + ibool val, /* in: value to set */ + que_thr_t* thr, /* in: query thread */ + mtr_t* mtr) /* in: mtr */ +{ + buf_block_t* block; + rec_t* rec; + ulint err; + + block = btr_cur_get_block(cursor); + rec = btr_cur_get_rec(cursor); + +#ifdef UNIV_DEBUG + if (btr_cur_print_record_ops && thr) { + btr_cur_trx_report(thr_get_trx(thr), cursor->index, + "del mark "); + rec_print(stderr, rec, cursor->index); + } +#endif /* UNIV_DEBUG */ + + err = lock_sec_rec_modify_check_and_lock(flags, + btr_cur_get_block(cursor), + rec, cursor->index, thr); + if (err != DB_SUCCESS) { + + return(err); + } + + ut_ad(!!page_rec_is_comp(rec) + == dict_table_is_comp(cursor->index->table)); + + if (block->is_hashed) { + rw_lock_x_lock(&btr_search_latch); + } + + btr_rec_set_deleted_flag(rec, buf_block_get_page_zip(block), val); + + if (block->is_hashed) { + rw_lock_x_unlock(&btr_search_latch); + } + + btr_cur_del_mark_set_sec_rec_log(rec, val, mtr); + + return(DB_SUCCESS); +} + +/*************************************************************** +Clear a secondary index record's delete mark. This function is only +used by the insert buffer insert merge mechanism. */ +UNIV_INTERN +void +btr_cur_del_unmark_for_ibuf( +/*========================*/ + rec_t* rec, /* in/out: record to delete unmark */ + page_zip_des_t* page_zip, /* in/out: compressed page + corresponding to rec, or NULL + when the tablespace is + uncompressed */ + mtr_t* mtr) /* in: mtr */ +{ + /* We do not need to reserve btr_search_latch, as the page has just + been read to the buffer pool and there cannot be a hash index to it. */ + + btr_rec_set_deleted_flag(rec, page_zip, FALSE); + + btr_cur_del_mark_set_sec_rec_log(rec, FALSE, mtr); +} + +/*==================== B-TREE RECORD REMOVE =========================*/ + +/***************************************************************** +Tries to compress a page of the tree if it seems useful. It is assumed +that mtr holds an x-latch on the tree and on the cursor page. To avoid +deadlocks, mtr must also own x-latches to brothers of page, if those +brothers exist. NOTE: it is assumed that the caller has reserved enough +free extents so that the compression will always succeed if done! */ +UNIV_INTERN +ibool +btr_cur_compress_if_useful( +/*=======================*/ + /* out: TRUE if compression occurred */ + btr_cur_t* cursor, /* in: cursor on the page to compress; + cursor does not stay valid if compression + occurs */ + mtr_t* mtr) /* in: mtr */ +{ + ut_ad(mtr_memo_contains(mtr, + dict_index_get_lock(btr_cur_get_index(cursor)), + MTR_MEMO_X_LOCK)); + ut_ad(mtr_memo_contains(mtr, btr_cur_get_block(cursor), + MTR_MEMO_PAGE_X_FIX)); + + return(btr_cur_compress_recommendation(cursor, mtr) + && btr_compress(cursor, mtr)); +} + +/*********************************************************** +Removes the record on which the tree cursor is positioned on a leaf page. +It is assumed that the mtr has an x-latch on the page where the cursor is +positioned, but no latch on the whole tree. */ +UNIV_INTERN +ibool +btr_cur_optimistic_delete( +/*======================*/ + /* out: TRUE if success, i.e., the page + did not become too empty */ + btr_cur_t* cursor, /* in: cursor on leaf page, on the record to + delete; cursor stays valid: if deletion + succeeds, on function exit it points to the + successor of the deleted record */ + mtr_t* mtr) /* in: mtr; if this function returns + TRUE on a leaf page of a secondary + index, the mtr must be committed + before latching any further pages */ +{ + buf_block_t* block; + rec_t* rec; + mem_heap_t* heap = NULL; + ulint offsets_[REC_OFFS_NORMAL_SIZE]; + ulint* offsets = offsets_; + ibool no_compress_needed; + rec_offs_init(offsets_); + + ut_ad(mtr_memo_contains(mtr, btr_cur_get_block(cursor), + MTR_MEMO_PAGE_X_FIX)); + /* This is intended only for leaf page deletions */ + + block = btr_cur_get_block(cursor); + + ut_ad(page_is_leaf(buf_block_get_frame(block))); + + rec = btr_cur_get_rec(cursor); + offsets = rec_get_offsets(rec, cursor->index, offsets, + ULINT_UNDEFINED, &heap); + + no_compress_needed = !rec_offs_any_extern(offsets) + && btr_cur_can_delete_without_compress( + cursor, rec_offs_size(offsets), mtr); + + if (no_compress_needed) { + + page_t* page = buf_block_get_frame(block); + page_zip_des_t* page_zip= buf_block_get_page_zip(block); + ulint max_ins = 0; + + lock_update_delete(block, rec); + + btr_search_update_hash_on_delete(cursor); + + if (!page_zip) { + max_ins = page_get_max_insert_size_after_reorganize( + page, 1); + } +#ifdef UNIV_ZIP_DEBUG + ut_a(!page_zip || page_zip_validate(page_zip, page)); +#endif /* UNIV_ZIP_DEBUG */ + page_cur_delete_rec(btr_cur_get_page_cur(cursor), + cursor->index, offsets, mtr); +#ifdef UNIV_ZIP_DEBUG + ut_a(!page_zip || page_zip_validate(page_zip, page)); +#endif /* UNIV_ZIP_DEBUG */ + + if (dict_index_is_clust(cursor->index) + || dict_index_is_ibuf(cursor->index) + || !page_is_leaf(page)) { + /* The insert buffer does not handle + inserts to clustered indexes, to + non-leaf pages of secondary index B-trees, + or to the insert buffer. */ + } else if (page_zip) { + ibuf_update_free_bits_zip(block, mtr); + } else { + ibuf_update_free_bits_low(block, max_ins, mtr); + } + } + + if (UNIV_LIKELY_NULL(heap)) { + mem_heap_free(heap); + } + + return(no_compress_needed); +} + +/***************************************************************** +Removes the record on which the tree cursor is positioned. Tries +to compress the page if its fillfactor drops below a threshold +or if it is the only page on the level. It is assumed that mtr holds +an x-latch on the tree and on the cursor page. To avoid deadlocks, +mtr must also own x-latches to brothers of page, if those brothers +exist. */ +UNIV_INTERN +ibool +btr_cur_pessimistic_delete( +/*=======================*/ + /* out: TRUE if compression occurred */ + ulint* err, /* out: DB_SUCCESS or DB_OUT_OF_FILE_SPACE; + the latter may occur because we may have + to update node pointers on upper levels, + and in the case of variable length keys + these may actually grow in size */ + ibool has_reserved_extents, /* in: TRUE if the + caller has already reserved enough free + extents so that he knows that the operation + will succeed */ + btr_cur_t* cursor, /* in: cursor on the record to delete; + if compression does not occur, the cursor + stays valid: it points to successor of + deleted record on function exit */ + enum trx_rb_ctx rb_ctx, /* in: rollback context */ + mtr_t* mtr) /* in: mtr */ +{ + buf_block_t* block; + page_t* page; + page_zip_des_t* page_zip; + dict_index_t* index; + rec_t* rec; + dtuple_t* node_ptr; + ulint n_extents = 0; + ulint n_reserved; + ibool success; + ibool ret = FALSE; + ulint level; + mem_heap_t* heap; + ulint* offsets; + + block = btr_cur_get_block(cursor); + page = buf_block_get_frame(block); + index = btr_cur_get_index(cursor); + + ut_ad(mtr_memo_contains(mtr, dict_index_get_lock(index), + MTR_MEMO_X_LOCK)); + ut_ad(mtr_memo_contains(mtr, block, MTR_MEMO_PAGE_X_FIX)); + if (!has_reserved_extents) { + /* First reserve enough free space for the file segments + of the index tree, so that the node pointer updates will + not fail because of lack of space */ + + n_extents = cursor->tree_height / 32 + 1; + + success = fsp_reserve_free_extents(&n_reserved, + index->space, + n_extents, + FSP_CLEANING, mtr); + if (!success) { + *err = DB_OUT_OF_FILE_SPACE; + + return(FALSE); + } + } + + heap = mem_heap_create(1024); + rec = btr_cur_get_rec(cursor); + page_zip = buf_block_get_page_zip(block); +#ifdef UNIV_ZIP_DEBUG + ut_a(!page_zip || page_zip_validate(page_zip, page)); +#endif /* UNIV_ZIP_DEBUG */ + + offsets = rec_get_offsets(rec, index, NULL, ULINT_UNDEFINED, &heap); + + if (rec_offs_any_extern(offsets)) { + btr_rec_free_externally_stored_fields(index, + rec, offsets, page_zip, + rb_ctx, mtr); +#ifdef UNIV_ZIP_DEBUG + ut_a(!page_zip || page_zip_validate(page_zip, page)); +#endif /* UNIV_ZIP_DEBUG */ + } + + if (UNIV_UNLIKELY(page_get_n_recs(page) < 2) + && UNIV_UNLIKELY(dict_index_get_page(index) + != buf_block_get_page_no(block))) { + + /* If there is only one record, drop the whole page in + btr_discard_page, if this is not the root page */ + + btr_discard_page(cursor, mtr); + + *err = DB_SUCCESS; + ret = TRUE; + + goto return_after_reservations; + } + + lock_update_delete(block, rec); + level = btr_page_get_level(page, mtr); + + if (level > 0 + && UNIV_UNLIKELY(rec == page_rec_get_next( + page_get_infimum_rec(page)))) { + + rec_t* next_rec = page_rec_get_next(rec); + + if (btr_page_get_prev(page, mtr) == FIL_NULL) { + + /* If we delete the leftmost node pointer on a + non-leaf level, we must mark the new leftmost node + pointer as the predefined minimum record */ + + /* This will make page_zip_validate() fail until + page_cur_delete_rec() completes. This is harmless, + because everything will take place within a single + mini-transaction and because writing to the redo log + is an atomic operation (performed by mtr_commit()). */ + btr_set_min_rec_mark(next_rec, mtr); + } else { + /* Otherwise, if we delete the leftmost node pointer + on a page, we have to change the father node pointer + so that it is equal to the new leftmost node pointer + on the page */ + + btr_node_ptr_delete(index, block, mtr); + + node_ptr = dict_index_build_node_ptr( + index, next_rec, buf_block_get_page_no(block), + heap, level); + + btr_insert_on_non_leaf_level(index, + level + 1, node_ptr, mtr); + } + } + + btr_search_update_hash_on_delete(cursor); + + page_cur_delete_rec(btr_cur_get_page_cur(cursor), index, offsets, mtr); +#ifdef UNIV_ZIP_DEBUG + ut_a(!page_zip || page_zip_validate(page_zip, page)); +#endif /* UNIV_ZIP_DEBUG */ + + ut_ad(btr_check_node_ptr(index, block, mtr)); + + *err = DB_SUCCESS; + +return_after_reservations: + mem_heap_free(heap); + + if (ret == FALSE) { + ret = btr_cur_compress_if_useful(cursor, mtr); + } + + if (n_extents > 0) { + fil_space_release_free_extents(index->space, n_reserved); + } + + return(ret); +} + +/*********************************************************************** +Adds path information to the cursor for the current page, for which +the binary search has been performed. */ +static +void +btr_cur_add_path_info( +/*==================*/ + btr_cur_t* cursor, /* in: cursor positioned on a page */ + ulint height, /* in: height of the page in tree; + 0 means leaf node */ + ulint root_height) /* in: root node height in tree */ +{ + btr_path_t* slot; + rec_t* rec; + + ut_a(cursor->path_arr); + + if (root_height >= BTR_PATH_ARRAY_N_SLOTS - 1) { + /* Do nothing; return empty path */ + + slot = cursor->path_arr; + slot->nth_rec = ULINT_UNDEFINED; + + return; + } + + if (height == 0) { + /* Mark end of slots for path */ + slot = cursor->path_arr + root_height + 1; + slot->nth_rec = ULINT_UNDEFINED; + } + + rec = btr_cur_get_rec(cursor); + + slot = cursor->path_arr + (root_height - height); + + slot->nth_rec = page_rec_get_n_recs_before(rec); + slot->n_recs = page_get_n_recs(page_align(rec)); +} + +/*********************************************************************** +Estimates the number of rows in a given index range. */ +UNIV_INTERN +ib_int64_t +btr_estimate_n_rows_in_range( +/*=========================*/ + /* out: estimated number of rows */ + dict_index_t* index, /* in: index */ + const dtuple_t* tuple1, /* in: range start, may also be empty tuple */ + ulint mode1, /* in: search mode for range start */ + const dtuple_t* tuple2, /* in: range end, may also be empty tuple */ + ulint mode2) /* in: search mode for range end */ +{ + btr_path_t path1[BTR_PATH_ARRAY_N_SLOTS]; + btr_path_t path2[BTR_PATH_ARRAY_N_SLOTS]; + btr_cur_t cursor; + btr_path_t* slot1; + btr_path_t* slot2; + ibool diverged; + ibool diverged_lot; + ulint divergence_level; + ib_int64_t n_rows; + ulint i; + mtr_t mtr; + + mtr_start(&mtr); + + cursor.path_arr = path1; + + if (dtuple_get_n_fields(tuple1) > 0) { + + btr_cur_search_to_nth_level(index, 0, tuple1, mode1, + BTR_SEARCH_LEAF | BTR_ESTIMATE, + &cursor, 0, &mtr); + } else { + btr_cur_open_at_index_side(TRUE, index, + BTR_SEARCH_LEAF | BTR_ESTIMATE, + &cursor, &mtr); + } + + mtr_commit(&mtr); + + mtr_start(&mtr); + + cursor.path_arr = path2; + + if (dtuple_get_n_fields(tuple2) > 0) { + + btr_cur_search_to_nth_level(index, 0, tuple2, mode2, + BTR_SEARCH_LEAF | BTR_ESTIMATE, + &cursor, 0, &mtr); + } else { + btr_cur_open_at_index_side(FALSE, index, + BTR_SEARCH_LEAF | BTR_ESTIMATE, + &cursor, &mtr); + } + + mtr_commit(&mtr); + + /* We have the path information for the range in path1 and path2 */ + + n_rows = 1; + diverged = FALSE; /* This becomes true when the path is not + the same any more */ + diverged_lot = FALSE; /* This becomes true when the paths are + not the same or adjacent any more */ + divergence_level = 1000000; /* This is the level where paths diverged + a lot */ + for (i = 0; ; i++) { + ut_ad(i < BTR_PATH_ARRAY_N_SLOTS); + + slot1 = path1 + i; + slot2 = path2 + i; + + if (slot1->nth_rec == ULINT_UNDEFINED + || slot2->nth_rec == ULINT_UNDEFINED) { + + if (i > divergence_level + 1) { + /* In trees whose height is > 1 our algorithm + tends to underestimate: multiply the estimate + by 2: */ + + n_rows = n_rows * 2; + } + + /* Do not estimate the number of rows in the range + to over 1 / 2 of the estimated rows in the whole + table */ + + if (n_rows > index->table->stat_n_rows / 2) { + n_rows = index->table->stat_n_rows / 2; + + /* If there are just 0 or 1 rows in the table, + then we estimate all rows are in the range */ + + if (n_rows == 0) { + n_rows = index->table->stat_n_rows; + } + } + + return(n_rows); + } + + if (!diverged && slot1->nth_rec != slot2->nth_rec) { + + diverged = TRUE; + + if (slot1->nth_rec < slot2->nth_rec) { + n_rows = slot2->nth_rec - slot1->nth_rec; + + if (n_rows > 1) { + diverged_lot = TRUE; + divergence_level = i; + } + } else { + /* Maybe the tree has changed between + searches */ + + return(10); + } + + } else if (diverged && !diverged_lot) { + + if (slot1->nth_rec < slot1->n_recs + || slot2->nth_rec > 1) { + + diverged_lot = TRUE; + divergence_level = i; + + n_rows = 0; + + if (slot1->nth_rec < slot1->n_recs) { + n_rows += slot1->n_recs + - slot1->nth_rec; + } + + if (slot2->nth_rec > 1) { + n_rows += slot2->nth_rec - 1; + } + } + } else if (diverged_lot) { + + n_rows = (n_rows * (slot1->n_recs + slot2->n_recs)) + / 2; + } + } +} + +/*********************************************************************** +Estimates the number of different key values in a given index, for +each n-column prefix of the index where n <= dict_index_get_n_unique(index). +The estimates are stored in the array index->stat_n_diff_key_vals. */ +UNIV_INTERN +void +btr_estimate_number_of_different_key_vals( +/*======================================*/ + dict_index_t* index) /* in: index */ +{ + btr_cur_t cursor; + page_t* page; + rec_t* rec; + ulint n_cols; + ulint matched_fields; + ulint matched_bytes; + ib_int64_t n_recs = 0; + ib_int64_t* n_diff; + ib_int64_t* n_not_nulls; + ullint n_sample_pages; /* number of pages to sample */ + ulint not_empty_flag = 0; + ulint total_external_size = 0; + ulint i; + ulint j; + ullint add_on; + mtr_t mtr; + mem_heap_t* heap = NULL; + ulint offsets_rec_[REC_OFFS_NORMAL_SIZE]; + ulint offsets_next_rec_[REC_OFFS_NORMAL_SIZE]; + ulint* offsets_rec = offsets_rec_; + ulint* offsets_next_rec= offsets_next_rec_; + ulint stats_method = srv_stats_method; + rec_offs_init(offsets_rec_); + rec_offs_init(offsets_next_rec_); + + n_cols = dict_index_get_n_unique(index); + + n_diff = mem_zalloc((n_cols + 1) * sizeof(ib_int64_t)); + + if (stats_method == SRV_STATS_METHOD_IGNORE_NULLS) { + n_not_nulls = mem_zalloc((n_cols + 1) * sizeof(ib_int64_t)); + } + + /* It makes no sense to test more pages than are contained + in the index, thus we lower the number if it is too high */ + if (srv_stats_sample_pages > index->stat_index_size) { + if (index->stat_index_size > 0) { + n_sample_pages = index->stat_index_size; + } else { + n_sample_pages = 1; + } + } else { + n_sample_pages = srv_stats_sample_pages; + } + + /* We sample some pages in the index to get an estimate */ + + for (i = 0; i < n_sample_pages; i++) { + rec_t* supremum; + mtr_start(&mtr); + + btr_cur_open_at_rnd_pos(index, BTR_SEARCH_LEAF, &cursor, &mtr); + + /* Count the number of different key values for each prefix of + the key on this index page. If the prefix does not determine + the index record uniquely in the B-tree, then we subtract one + because otherwise our algorithm would give a wrong estimate + for an index where there is just one key value. */ + + page = btr_cur_get_page(&cursor); + + supremum = page_get_supremum_rec(page); + rec = page_rec_get_next(page_get_infimum_rec(page)); + + if (rec != supremum) { + not_empty_flag = 1; + offsets_rec = rec_get_offsets(rec, index, offsets_rec, + ULINT_UNDEFINED, &heap); + } + + while (rec != supremum) { + /* count recs */ + if (stats_method == SRV_STATS_METHOD_IGNORE_NULLS) { + n_recs++; + for (j = 0; j <= n_cols; j++) { + ulint f_len; + rec_get_nth_field(rec, offsets_rec, + j, &f_len); + if (f_len == UNIV_SQL_NULL) + break; + + n_not_nulls[j]++; + } + } + + rec_t* next_rec = page_rec_get_next(rec); + if (next_rec == supremum) { + break; + } + + matched_fields = 0; + matched_bytes = 0; + offsets_next_rec = rec_get_offsets(next_rec, index, + offsets_next_rec, + n_cols, &heap); + + cmp_rec_rec_with_match(rec, next_rec, + offsets_rec, offsets_next_rec, + index, &matched_fields, + &matched_bytes, srv_stats_method); + + for (j = matched_fields + 1; j <= n_cols; j++) { + /* We add one if this index record has + a different prefix from the previous */ + + n_diff[j]++; + } + + total_external_size + += btr_rec_get_externally_stored_len( + rec, offsets_rec); + + rec = next_rec; + /* Initialize offsets_rec for the next round + and assign the old offsets_rec buffer to + offsets_next_rec. */ + { + ulint* offsets_tmp = offsets_rec; + offsets_rec = offsets_next_rec; + offsets_next_rec = offsets_tmp; + } + } + + + if (n_cols == dict_index_get_n_unique_in_tree(index)) { + + /* If there is more than one leaf page in the tree, + we add one because we know that the first record + on the page certainly had a different prefix than the + last record on the previous index page in the + alphabetical order. Before this fix, if there was + just one big record on each clustered index page, the + algorithm grossly underestimated the number of rows + in the table. */ + + if (btr_page_get_prev(page, &mtr) != FIL_NULL + || btr_page_get_next(page, &mtr) != FIL_NULL) { + + n_diff[n_cols]++; + } + } + + offsets_rec = rec_get_offsets(rec, index, offsets_rec, + ULINT_UNDEFINED, &heap); + total_external_size += btr_rec_get_externally_stored_len( + rec, offsets_rec); + mtr_commit(&mtr); + } + + /* If we saw k borders between different key values on + n_sample_pages leaf pages, we can estimate how many + there will be in index->stat_n_leaf_pages */ + + /* We must take into account that our sample actually represents + also the pages used for external storage of fields (those pages are + included in index->stat_n_leaf_pages) */ + + for (j = 0; j <= n_cols; j++) { + index->stat_n_diff_key_vals[j] + = ((n_diff[j] + * (ib_int64_t)index->stat_n_leaf_pages + + n_sample_pages - 1 + + total_external_size + + not_empty_flag) + / (n_sample_pages + + total_external_size)); + + /* If the tree is small, smaller than + 10 * n_sample_pages + total_external_size, then + the above estimate is ok. For bigger trees it is common that we + do not see any borders between key values in the few pages + we pick. But still there may be n_sample_pages + different key values, or even more. Let us try to approximate + that: */ + + add_on = index->stat_n_leaf_pages + / (10 * (n_sample_pages + + total_external_size)); + + if (add_on > n_sample_pages) { + add_on = n_sample_pages; + } + + index->stat_n_diff_key_vals[j] += add_on; + + /* revision for 'nulls_ignored' */ + if (stats_method == SRV_STATS_METHOD_IGNORE_NULLS) { + if (!n_not_nulls[j]) + n_not_nulls[j] = 1; + index->stat_n_diff_key_vals[j] = + index->stat_n_diff_key_vals[j] * n_recs + / n_not_nulls[j]; + } + } + + mem_free(n_diff); + if (stats_method == SRV_STATS_METHOD_IGNORE_NULLS) { + mem_free(n_not_nulls); + } + if (UNIV_LIKELY_NULL(heap)) { + mem_heap_free(heap); + } +} + +/*================== EXTERNAL STORAGE OF BIG FIELDS ===================*/ + +/*************************************************************** +Gets the externally stored size of a record, in units of a database page. */ +static +ulint +btr_rec_get_externally_stored_len( +/*==============================*/ + /* out: externally stored part, + in units of a database page */ + rec_t* rec, /* in: record */ + const ulint* offsets)/* in: array returned by rec_get_offsets() */ +{ + ulint n_fields; + byte* data; + ulint local_len; + ulint extern_len; + ulint total_extern_len = 0; + ulint i; + + ut_ad(!rec_offs_comp(offsets) || !rec_get_node_ptr_flag(rec)); + n_fields = rec_offs_n_fields(offsets); + + for (i = 0; i < n_fields; i++) { + if (rec_offs_nth_extern(offsets, i)) { + + data = rec_get_nth_field(rec, offsets, i, &local_len); + + local_len -= BTR_EXTERN_FIELD_REF_SIZE; + + extern_len = mach_read_from_4(data + local_len + + BTR_EXTERN_LEN + 4); + + total_extern_len += ut_calc_align(extern_len, + UNIV_PAGE_SIZE); + } + } + + return(total_extern_len / UNIV_PAGE_SIZE); +} + +/*********************************************************************** +Sets the ownership bit of an externally stored field in a record. */ +static +void +btr_cur_set_ownership_of_extern_field( +/*==================================*/ + page_zip_des_t* page_zip,/* in/out: compressed page whose uncompressed + part will be updated, or NULL */ + rec_t* rec, /* in/out: clustered index record */ + dict_index_t* index, /* in: index of the page */ + const ulint* offsets,/* in: array returned by rec_get_offsets() */ + ulint i, /* in: field number */ + ibool val, /* in: value to set */ + mtr_t* mtr) /* in: mtr, or NULL if not logged */ +{ + byte* data; + ulint local_len; + ulint byte_val; + + data = rec_get_nth_field(rec, offsets, i, &local_len); + + ut_a(local_len >= BTR_EXTERN_FIELD_REF_SIZE); + + local_len -= BTR_EXTERN_FIELD_REF_SIZE; + + byte_val = mach_read_from_1(data + local_len + BTR_EXTERN_LEN); + + if (val) { + byte_val = byte_val & (~BTR_EXTERN_OWNER_FLAG); + } else { + byte_val = byte_val | BTR_EXTERN_OWNER_FLAG; + } + + if (UNIV_LIKELY_NULL(page_zip)) { + mach_write_to_1(data + local_len + BTR_EXTERN_LEN, byte_val); + page_zip_write_blob_ptr(page_zip, rec, index, offsets, i, mtr); + } else if (UNIV_LIKELY(mtr != NULL)) { + + mlog_write_ulint(data + local_len + BTR_EXTERN_LEN, byte_val, + MLOG_1BYTE, mtr); + } else { + mach_write_to_1(data + local_len + BTR_EXTERN_LEN, byte_val); + } +} + +/*********************************************************************** +Marks not updated extern fields as not-owned by this record. The ownership +is transferred to the updated record which is inserted elsewhere in the +index tree. In purge only the owner of externally stored field is allowed +to free the field. */ +UNIV_INTERN +void +btr_cur_mark_extern_inherited_fields( +/*=================================*/ + page_zip_des_t* page_zip,/* in/out: compressed page whose uncompressed + part will be updated, or NULL */ + rec_t* rec, /* in/out: record in a clustered index */ + dict_index_t* index, /* in: index of the page */ + const ulint* offsets,/* in: array returned by rec_get_offsets() */ + const upd_t* update, /* in: update vector */ + mtr_t* mtr) /* in: mtr, or NULL if not logged */ +{ + ulint n; + ulint j; + ulint i; + + ut_ad(rec_offs_validate(rec, NULL, offsets)); + ut_ad(!rec_offs_comp(offsets) || !rec_get_node_ptr_flag(rec)); + + if (!rec_offs_any_extern(offsets)) { + + return; + } + + n = rec_offs_n_fields(offsets); + + for (i = 0; i < n; i++) { + if (rec_offs_nth_extern(offsets, i)) { + + /* Check it is not in updated fields */ + + if (update) { + for (j = 0; j < upd_get_n_fields(update); + j++) { + if (upd_get_nth_field(update, j) + ->field_no == i) { + + goto updated; + } + } + } + + btr_cur_set_ownership_of_extern_field( + page_zip, rec, index, offsets, i, FALSE, mtr); +updated: + ; + } + } +} + +/*********************************************************************** +The complement of the previous function: in an update entry may inherit +some externally stored fields from a record. We must mark them as inherited +in entry, so that they are not freed in a rollback. */ +UNIV_INTERN +void +btr_cur_mark_dtuple_inherited_extern( +/*=================================*/ + dtuple_t* entry, /* in/out: updated entry to be + inserted to clustered index */ + const upd_t* update) /* in: update vector */ +{ + ulint i; + + for (i = 0; i < dtuple_get_n_fields(entry); i++) { + + dfield_t* dfield = dtuple_get_nth_field(entry, i); + byte* data; + ulint len; + ulint j; + + if (!dfield_is_ext(dfield)) { + continue; + } + + /* Check if it is in updated fields */ + + for (j = 0; j < upd_get_n_fields(update); j++) { + if (upd_get_nth_field(update, j)->field_no == i) { + + goto is_updated; + } + } + + data = dfield_get_data(dfield); + len = dfield_get_len(dfield); + data[len - BTR_EXTERN_FIELD_REF_SIZE + BTR_EXTERN_LEN] + |= BTR_EXTERN_INHERITED_FLAG; + +is_updated: + ; + } +} + +/*********************************************************************** +Marks all extern fields in a record as owned by the record. This function +should be called if the delete mark of a record is removed: a not delete +marked record always owns all its extern fields. */ +static +void +btr_cur_unmark_extern_fields( +/*=========================*/ + page_zip_des_t* page_zip,/* in/out: compressed page whose uncompressed + part will be updated, or NULL */ + rec_t* rec, /* in/out: record in a clustered index */ + dict_index_t* index, /* in: index of the page */ + const ulint* offsets,/* in: array returned by rec_get_offsets() */ + mtr_t* mtr) /* in: mtr, or NULL if not logged */ +{ + ulint n; + ulint i; + + ut_ad(!rec_offs_comp(offsets) || !rec_get_node_ptr_flag(rec)); + n = rec_offs_n_fields(offsets); + + if (!rec_offs_any_extern(offsets)) { + + return; + } + + for (i = 0; i < n; i++) { + if (rec_offs_nth_extern(offsets, i)) { + + btr_cur_set_ownership_of_extern_field( + page_zip, rec, index, offsets, i, TRUE, mtr); + } + } +} + +/*********************************************************************** +Marks all extern fields in a dtuple as owned by the record. */ +UNIV_INTERN +void +btr_cur_unmark_dtuple_extern_fields( +/*================================*/ + dtuple_t* entry) /* in/out: clustered index entry */ +{ + ulint i; + + for (i = 0; i < dtuple_get_n_fields(entry); i++) { + dfield_t* dfield = dtuple_get_nth_field(entry, i); + + if (dfield_is_ext(dfield)) { + byte* data = dfield_get_data(dfield); + ulint len = dfield_get_len(dfield); + + data[len - BTR_EXTERN_FIELD_REF_SIZE + BTR_EXTERN_LEN] + &= ~BTR_EXTERN_OWNER_FLAG; + } + } +} + +/*********************************************************************** +Flags the data tuple fields that are marked as extern storage in the +update vector. We use this function to remember which fields we must +mark as extern storage in a record inserted for an update. */ +UNIV_INTERN +ulint +btr_push_update_extern_fields( +/*==========================*/ + /* out: number of flagged external columns */ + dtuple_t* tuple, /* in/out: data tuple */ + const upd_t* update, /* in: update vector */ + mem_heap_t* heap) /* in: memory heap */ +{ + ulint n_pushed = 0; + ulint n; + const upd_field_t* uf; + + ut_ad(tuple); + ut_ad(update); + + uf = update->fields; + n = upd_get_n_fields(update); + + for (; n--; uf++) { + if (dfield_is_ext(&uf->new_val)) { + dfield_t* field + = dtuple_get_nth_field(tuple, uf->field_no); + + if (!dfield_is_ext(field)) { + dfield_set_ext(field); + n_pushed++; + } + + switch (uf->orig_len) { + byte* data; + ulint len; + byte* buf; + case 0: + break; + case BTR_EXTERN_FIELD_REF_SIZE: + /* Restore the original locally stored + part of the column. In the undo log, + InnoDB writes a longer prefix of externally + stored columns, so that column prefixes + in secondary indexes can be reconstructed. */ + dfield_set_data(field, (byte*) dfield_get_data(field) + + dfield_get_len(field) + - BTR_EXTERN_FIELD_REF_SIZE, + BTR_EXTERN_FIELD_REF_SIZE); + dfield_set_ext(field); + break; + default: + /* Reconstruct the original locally + stored part of the column. The data + will have to be copied. */ + ut_a(uf->orig_len > BTR_EXTERN_FIELD_REF_SIZE); + + data = dfield_get_data(field); + len = dfield_get_len(field); + + buf = mem_heap_alloc(heap, uf->orig_len); + /* Copy the locally stored prefix. */ + memcpy(buf, data, + uf->orig_len + - BTR_EXTERN_FIELD_REF_SIZE); + /* Copy the BLOB pointer. */ + memcpy(buf + uf->orig_len + - BTR_EXTERN_FIELD_REF_SIZE, + data + len - BTR_EXTERN_FIELD_REF_SIZE, + BTR_EXTERN_FIELD_REF_SIZE); + + dfield_set_data(field, buf, uf->orig_len); + dfield_set_ext(field); + } + } + } + + return(n_pushed); +} + +/*********************************************************************** +Returns the length of a BLOB part stored on the header page. */ +static +ulint +btr_blob_get_part_len( +/*==================*/ + /* out: part length */ + const byte* blob_header) /* in: blob header */ +{ + return(mach_read_from_4(blob_header + BTR_BLOB_HDR_PART_LEN)); +} + +/*********************************************************************** +Returns the page number where the next BLOB part is stored. */ +static +ulint +btr_blob_get_next_page_no( +/*======================*/ + /* out: page number or FIL_NULL if + no more pages */ + const byte* blob_header) /* in: blob header */ +{ + return(mach_read_from_4(blob_header + BTR_BLOB_HDR_NEXT_PAGE_NO)); +} + +/*********************************************************************** +Deallocate a buffer block that was reserved for a BLOB part. */ +static +void +btr_blob_free( +/*==========*/ + buf_block_t* block, /* in: buffer block */ + ibool all, /* in: TRUE=remove also the compressed page + if there is one */ + mtr_t* mtr) /* in: mini-transaction to commit */ +{ + ulint space = buf_block_get_space(block); + ulint page_no = buf_block_get_page_no(block); + + ut_ad(mtr_memo_contains(mtr, block, MTR_MEMO_PAGE_X_FIX)); + + mtr_commit(mtr); + + //buf_pool_mutex_enter(); + mutex_enter(&LRU_list_mutex); + mutex_enter(&block->mutex); + + /* Only free the block if it is still allocated to + the same file page. */ + + if (buf_block_get_state(block) + == BUF_BLOCK_FILE_PAGE + && buf_block_get_space(block) == space + && buf_block_get_page_no(block) == page_no) { + + if (buf_LRU_free_block(&block->page, all, NULL, TRUE) + != BUF_LRU_FREED + && all && block->page.zip.data + /* Now, buf_LRU_free_block() may release mutex temporarily */ + && buf_block_get_state(block) == BUF_BLOCK_FILE_PAGE + && buf_block_get_space(block) == space + && buf_block_get_page_no(block) == page_no) { + /* Attempt to deallocate the uncompressed page + if the whole block cannot be deallocted. */ + + buf_LRU_free_block(&block->page, FALSE, NULL, TRUE); + } + } + + //buf_pool_mutex_exit(); + mutex_exit(&LRU_list_mutex); + mutex_exit(&block->mutex); +} + +/*********************************************************************** +Stores the fields in big_rec_vec to the tablespace and puts pointers to +them in rec. The extern flags in rec will have to be set beforehand. +The fields are stored on pages allocated from leaf node +file segment of the index tree. */ +UNIV_INTERN +ulint +btr_store_big_rec_extern_fields( +/*============================*/ + /* out: DB_SUCCESS or error */ + dict_index_t* index, /* in: index of rec; the index tree + MUST be X-latched */ + buf_block_t* rec_block, /* in/out: block containing rec */ + rec_t* rec, /* in/out: record */ + const ulint* offsets, /* in: rec_get_offsets(rec, index); + the "external storage" flags in offsets + will not correspond to rec when + this function returns */ + big_rec_t* big_rec_vec, /* in: vector containing fields + to be stored externally */ + mtr_t* local_mtr __attribute__((unused))) /* in: mtr + containing the latch to rec and to the + tree */ +{ + ulint rec_page_no; + byte* field_ref; + ulint extern_len; + ulint store_len; + ulint page_no; + ulint space_id; + ulint zip_size; + ulint prev_page_no; + ulint hint_page_no; + ulint i; + mtr_t mtr; + mem_heap_t* heap = NULL; + page_zip_des_t* page_zip; + z_stream c_stream; + + ut_ad(rec_offs_validate(rec, index, offsets)); + ut_ad(mtr_memo_contains(local_mtr, dict_index_get_lock(index), + MTR_MEMO_X_LOCK)); + ut_ad(mtr_memo_contains(local_mtr, rec_block, MTR_MEMO_PAGE_X_FIX)); + ut_ad(buf_block_get_frame(rec_block) == page_align(rec)); + ut_a(dict_index_is_clust(index)); + + page_zip = buf_block_get_page_zip(rec_block); + ut_a(dict_table_zip_size(index->table) + == buf_block_get_zip_size(rec_block)); + + space_id = buf_block_get_space(rec_block); + zip_size = buf_block_get_zip_size(rec_block); + rec_page_no = buf_block_get_page_no(rec_block); + ut_a(fil_page_get_type(page_align(rec)) == FIL_PAGE_INDEX); + + if (UNIV_LIKELY_NULL(page_zip)) { + int err; + + /* Zlib deflate needs 128 kilobytes for the default + window size, plus 512 << memLevel, plus a few + kilobytes for small objects. We use reduced memLevel + to limit the memory consumption, and preallocate the + heap, hoping to avoid memory fragmentation. */ + heap = mem_heap_create(250000); + page_zip_set_alloc(&c_stream, heap); + + err = deflateInit2(&c_stream, Z_DEFAULT_COMPRESSION, + Z_DEFLATED, 15, 7, Z_DEFAULT_STRATEGY); + ut_a(err == Z_OK); + } + + /* We have to create a file segment to the tablespace + for each field and put the pointer to the field in rec */ + + for (i = 0; i < big_rec_vec->n_fields; i++) { + ut_ad(rec_offs_nth_extern(offsets, + big_rec_vec->fields[i].field_no)); + { + ulint local_len; + field_ref = rec_get_nth_field( + rec, offsets, big_rec_vec->fields[i].field_no, + &local_len); + ut_a(local_len >= BTR_EXTERN_FIELD_REF_SIZE); + local_len -= BTR_EXTERN_FIELD_REF_SIZE; + field_ref += local_len; + } + extern_len = big_rec_vec->fields[i].len; + + ut_a(extern_len > 0); + + prev_page_no = FIL_NULL; + + if (UNIV_LIKELY_NULL(page_zip)) { + int err = deflateReset(&c_stream); + ut_a(err == Z_OK); + + c_stream.next_in = (void*) big_rec_vec->fields[i].data; + c_stream.avail_in = extern_len; + } + + for (;;) { + buf_block_t* block; + page_t* page; + + mtr_start(&mtr); + + if (prev_page_no == FIL_NULL) { + hint_page_no = 1 + rec_page_no; + } else { + hint_page_no = prev_page_no + 1; + } + + block = btr_page_alloc(index, hint_page_no, + FSP_NO_DIR, 0, &mtr); + if (UNIV_UNLIKELY(block == NULL)) { + + mtr_commit(&mtr); + + if (UNIV_LIKELY_NULL(page_zip)) { + deflateEnd(&c_stream); + mem_heap_free(heap); + } + + return(DB_OUT_OF_FILE_SPACE); + } + + page_no = buf_block_get_page_no(block); + page = buf_block_get_frame(block); + + if (prev_page_no != FIL_NULL) { + buf_block_t* prev_block; + page_t* prev_page; + + prev_block = buf_page_get(space_id, zip_size, + prev_page_no, + RW_X_LATCH, &mtr); + buf_block_dbg_add_level(prev_block, + SYNC_EXTERN_STORAGE); + prev_page = buf_block_get_frame(prev_block); + + if (UNIV_LIKELY_NULL(page_zip)) { + mlog_write_ulint( + prev_page + FIL_PAGE_NEXT, + page_no, MLOG_4BYTES, &mtr); + memcpy(buf_block_get_page_zip( + prev_block) + ->data + FIL_PAGE_NEXT, + prev_page + FIL_PAGE_NEXT, 4); + } else { + mlog_write_ulint( + prev_page + FIL_PAGE_DATA + + BTR_BLOB_HDR_NEXT_PAGE_NO, + page_no, MLOG_4BYTES, &mtr); + } + + } + + if (UNIV_LIKELY_NULL(page_zip)) { + int err; + page_zip_des_t* blob_page_zip; + + mach_write_to_2(page + FIL_PAGE_TYPE, + prev_page_no == FIL_NULL + ? FIL_PAGE_TYPE_ZBLOB + : FIL_PAGE_TYPE_ZBLOB2); + + c_stream.next_out = page + + FIL_PAGE_DATA; + c_stream.avail_out + = page_zip_get_size(page_zip) + - FIL_PAGE_DATA; + + err = deflate(&c_stream, Z_FINISH); + ut_a(err == Z_OK || err == Z_STREAM_END); + ut_a(err == Z_STREAM_END + || c_stream.avail_out == 0); + + /* Write the "next BLOB page" pointer */ + mlog_write_ulint(page + FIL_PAGE_NEXT, + FIL_NULL, MLOG_4BYTES, &mtr); + /* Initialize the unused "prev page" pointer */ + mlog_write_ulint(page + FIL_PAGE_PREV, + FIL_NULL, MLOG_4BYTES, &mtr); + /* Write a back pointer to the record + into the otherwise unused area. This + information could be useful in + debugging. Later, we might want to + implement the possibility to relocate + BLOB pages. Then, we would need to be + able to adjust the BLOB pointer in the + record. We do not store the heap + number of the record, because it can + change in page_zip_reorganize() or + btr_page_reorganize(). However, also + the page number of the record may + change when B-tree nodes are split or + merged. */ + mlog_write_ulint(page + + FIL_PAGE_FILE_FLUSH_LSN, + space_id, + MLOG_4BYTES, &mtr); + mlog_write_ulint(page + + FIL_PAGE_FILE_FLUSH_LSN + 4, + rec_page_no, + MLOG_4BYTES, &mtr); + + /* Zero out the unused part of the page. */ + memset(page + page_zip_get_size(page_zip) + - c_stream.avail_out, + 0, c_stream.avail_out); + mlog_log_string(page + FIL_PAGE_TYPE, + page_zip_get_size(page_zip) + - FIL_PAGE_TYPE, + &mtr); + /* Copy the page to compressed storage, + because it will be flushed to disk + from there. */ + blob_page_zip = buf_block_get_page_zip(block); + ut_ad(blob_page_zip); + ut_ad(page_zip_get_size(blob_page_zip) + == page_zip_get_size(page_zip)); + memcpy(blob_page_zip->data, page, + page_zip_get_size(page_zip)); + + if (err == Z_OK && prev_page_no != FIL_NULL) { + + goto next_zip_page; + } + + rec_block = buf_page_get(space_id, zip_size, + rec_page_no, + RW_X_LATCH, &mtr); + buf_block_dbg_add_level(rec_block, + SYNC_NO_ORDER_CHECK); + + if (err == Z_STREAM_END) { + mach_write_to_4(field_ref + + BTR_EXTERN_LEN, 0); + mach_write_to_4(field_ref + + BTR_EXTERN_LEN + 4, + c_stream.total_in); + } else { + memset(field_ref + BTR_EXTERN_LEN, + 0, 8); + } + + if (prev_page_no == FIL_NULL) { + mach_write_to_4(field_ref + + BTR_EXTERN_SPACE_ID, + space_id); + + mach_write_to_4(field_ref + + BTR_EXTERN_PAGE_NO, + page_no); + + mach_write_to_4(field_ref + + BTR_EXTERN_OFFSET, + FIL_PAGE_NEXT); + } + + page_zip_write_blob_ptr( + page_zip, rec, index, offsets, + big_rec_vec->fields[i].field_no, &mtr); + +next_zip_page: + prev_page_no = page_no; + + /* Commit mtr and release the + uncompressed page frame to save memory. */ + btr_blob_free(block, FALSE, &mtr); + + if (err == Z_STREAM_END) { + break; + } + } else { + mlog_write_ulint(page + FIL_PAGE_TYPE, + FIL_PAGE_TYPE_BLOB, + MLOG_2BYTES, &mtr); + + if (extern_len > (UNIV_PAGE_SIZE + - FIL_PAGE_DATA + - BTR_BLOB_HDR_SIZE + - FIL_PAGE_DATA_END)) { + store_len = UNIV_PAGE_SIZE + - FIL_PAGE_DATA + - BTR_BLOB_HDR_SIZE + - FIL_PAGE_DATA_END; + } else { + store_len = extern_len; + } + + mlog_write_string(page + FIL_PAGE_DATA + + BTR_BLOB_HDR_SIZE, + (const byte*) + big_rec_vec->fields[i].data + + big_rec_vec->fields[i].len + - extern_len, + store_len, &mtr); + mlog_write_ulint(page + FIL_PAGE_DATA + + BTR_BLOB_HDR_PART_LEN, + store_len, MLOG_4BYTES, &mtr); + mlog_write_ulint(page + FIL_PAGE_DATA + + BTR_BLOB_HDR_NEXT_PAGE_NO, + FIL_NULL, MLOG_4BYTES, &mtr); + + extern_len -= store_len; + + rec_block = buf_page_get(space_id, zip_size, + rec_page_no, + RW_X_LATCH, &mtr); + buf_block_dbg_add_level(rec_block, + SYNC_NO_ORDER_CHECK); + + mlog_write_ulint(field_ref + BTR_EXTERN_LEN, 0, + MLOG_4BYTES, &mtr); + mlog_write_ulint(field_ref + + BTR_EXTERN_LEN + 4, + big_rec_vec->fields[i].len + - extern_len, + MLOG_4BYTES, &mtr); + + if (prev_page_no == FIL_NULL) { + mlog_write_ulint(field_ref + + BTR_EXTERN_SPACE_ID, + space_id, + MLOG_4BYTES, &mtr); + + mlog_write_ulint(field_ref + + BTR_EXTERN_PAGE_NO, + page_no, + MLOG_4BYTES, &mtr); + + mlog_write_ulint(field_ref + + BTR_EXTERN_OFFSET, + FIL_PAGE_DATA, + MLOG_4BYTES, &mtr); + } + + prev_page_no = page_no; + + mtr_commit(&mtr); + + if (extern_len == 0) { + break; + } + } + } + } + + if (UNIV_LIKELY_NULL(page_zip)) { + deflateEnd(&c_stream); + mem_heap_free(heap); + } + + return(DB_SUCCESS); +} + +/*********************************************************************** +Check the FIL_PAGE_TYPE on an uncompressed BLOB page. */ +static +void +btr_check_blob_fil_page_type( +/*=========================*/ + ulint space_id, /* in: space id */ + ulint page_no, /* in: page number */ + const page_t* page, /* in: page */ + ibool read) /* in: TRUE=read, FALSE=purge */ +{ + ulint type = fil_page_get_type(page); + + ut_a(space_id == page_get_space_id(page)); + ut_a(page_no == page_get_page_no(page)); + + if (UNIV_UNLIKELY(type != FIL_PAGE_TYPE_BLOB)) { + ulint flags = fil_space_get_flags(space_id); + + if (UNIV_LIKELY + ((flags & DICT_TF_FORMAT_MASK) == DICT_TF_FORMAT_51)) { + /* Old versions of InnoDB did not initialize + FIL_PAGE_TYPE on BLOB pages. Do not print + anything about the type mismatch when reading + a BLOB page that is in Antelope format.*/ + return; + } + + ut_print_timestamp(stderr); + fprintf(stderr, + " InnoDB: FIL_PAGE_TYPE=%lu" + " on BLOB %s space %lu page %lu flags %lx\n", + (ulong) type, read ? "read" : "purge", + (ulong) space_id, (ulong) page_no, (ulong) flags); + ut_error; + } +} + +/*********************************************************************** +Frees the space in an externally stored field to the file space +management if the field in data is owned by the externally stored field, +in a rollback we may have the additional condition that the field must +not be inherited. */ +UNIV_INTERN +void +btr_free_externally_stored_field( +/*=============================*/ + dict_index_t* index, /* in: index of the data, the index + tree MUST be X-latched; if the tree + height is 1, then also the root page + must be X-latched! (this is relevant + in the case this function is called + from purge where 'data' is located on + an undo log page, not an index + page) */ + byte* field_ref, /* in/out: field reference */ + const rec_t* rec, /* in: record containing field_ref, for + page_zip_write_blob_ptr(), or NULL */ + const ulint* offsets, /* in: rec_get_offsets(rec, index), + or NULL */ + page_zip_des_t* page_zip, /* in: compressed page corresponding + to rec, or NULL if rec == NULL */ + ulint i, /* in: field number of field_ref; + ignored if rec == NULL */ + enum trx_rb_ctx rb_ctx, /* in: rollback context */ + mtr_t* local_mtr __attribute__((unused))) /* in: mtr + containing the latch to data an an + X-latch to the index tree */ +{ + page_t* page; + ulint space_id; + ulint rec_zip_size = dict_table_zip_size(index->table); + ulint ext_zip_size; + ulint page_no; + ulint next_page_no; + mtr_t mtr; +#ifdef UNIV_DEBUG + ut_ad(mtr_memo_contains(local_mtr, dict_index_get_lock(index), + MTR_MEMO_X_LOCK)); + ut_ad(mtr_memo_contains_page(local_mtr, field_ref, + MTR_MEMO_PAGE_X_FIX)); + ut_ad(!rec || rec_offs_validate(rec, index, offsets)); + + if (rec) { + ulint local_len; + const byte* f = rec_get_nth_field(rec, offsets, + i, &local_len); + ut_a(local_len >= BTR_EXTERN_FIELD_REF_SIZE); + local_len -= BTR_EXTERN_FIELD_REF_SIZE; + f += local_len; + ut_ad(f == field_ref); + } +#endif /* UNIV_DEBUG */ + + if (UNIV_UNLIKELY(!memcmp(field_ref, field_ref_zero, + BTR_EXTERN_FIELD_REF_SIZE))) { + /* In the rollback of uncommitted transactions, we may + encounter a clustered index record whose BLOBs have + not been written. There is nothing to free then. */ + ut_a(rb_ctx == RB_RECOVERY); + return; + } + + space_id = mach_read_from_4(field_ref + BTR_EXTERN_SPACE_ID); + + if (UNIV_UNLIKELY(space_id != dict_index_get_space(index))) { + ext_zip_size = fil_space_get_zip_size(space_id); + /* This must be an undo log record in the system tablespace, + that is, in row_purge_upd_exist_or_extern(). + Currently, externally stored records are stored in the + same tablespace as the referring records. */ + ut_ad(!page_get_space_id(page_align(field_ref))); + ut_ad(!rec); + ut_ad(!page_zip); + } else { + ext_zip_size = rec_zip_size; + } + + if (!rec) { + /* This is a call from row_purge_upd_exist_or_extern(). */ + ut_ad(!page_zip); + rec_zip_size = 0; + } + + for (;;) { + buf_block_t* rec_block; + buf_block_t* ext_block; + + mtr_start(&mtr); + + rec_block = buf_page_get(page_get_space_id( + page_align(field_ref)), + rec_zip_size, + page_get_page_no( + page_align(field_ref)), + RW_X_LATCH, &mtr); + buf_block_dbg_add_level(rec_block, SYNC_NO_ORDER_CHECK); + page_no = mach_read_from_4(field_ref + BTR_EXTERN_PAGE_NO); + + if (/* There is no external storage data */ + page_no == FIL_NULL + /* This field does not own the externally stored field */ + || (mach_read_from_1(field_ref + BTR_EXTERN_LEN) + & BTR_EXTERN_OWNER_FLAG) + /* Rollback and inherited field */ + || (rb_ctx != RB_NONE + && (mach_read_from_1(field_ref + BTR_EXTERN_LEN) + & BTR_EXTERN_INHERITED_FLAG))) { + + /* Do not free */ + mtr_commit(&mtr); + + return; + } + + ext_block = buf_page_get(space_id, ext_zip_size, page_no, + RW_X_LATCH, &mtr); + buf_block_dbg_add_level(ext_block, SYNC_EXTERN_STORAGE); + page = buf_block_get_frame(ext_block); + + if (ext_zip_size) { + /* Note that page_zip will be NULL + in row_purge_upd_exist_or_extern(). */ + switch (fil_page_get_type(page)) { + case FIL_PAGE_TYPE_ZBLOB: + case FIL_PAGE_TYPE_ZBLOB2: + break; + default: + ut_error; + } + next_page_no = mach_read_from_4(page + FIL_PAGE_NEXT); + + btr_page_free_low(index, ext_block, 0, &mtr); + + if (UNIV_LIKELY(page_zip != NULL)) { + mach_write_to_4(field_ref + BTR_EXTERN_PAGE_NO, + next_page_no); + mach_write_to_4(field_ref + BTR_EXTERN_LEN + 4, + 0); + page_zip_write_blob_ptr(page_zip, rec, index, + offsets, i, &mtr); + } else { + mlog_write_ulint(field_ref + + BTR_EXTERN_PAGE_NO, + next_page_no, + MLOG_4BYTES, &mtr); + mlog_write_ulint(field_ref + + BTR_EXTERN_LEN + 4, 0, + MLOG_4BYTES, &mtr); + } + } else { + ut_a(!page_zip); + btr_check_blob_fil_page_type(space_id, page_no, page, + FALSE); + + next_page_no = mach_read_from_4( + page + FIL_PAGE_DATA + + BTR_BLOB_HDR_NEXT_PAGE_NO); + + /* We must supply the page level (= 0) as an argument + because we did not store it on the page (we save the + space overhead from an index page header. */ + + btr_page_free_low(index, ext_block, 0, &mtr); + + mlog_write_ulint(field_ref + BTR_EXTERN_PAGE_NO, + next_page_no, + MLOG_4BYTES, &mtr); + /* Zero out the BLOB length. If the server + crashes during the execution of this function, + trx_rollback_or_clean_all_recovered() could + dereference the half-deleted BLOB, fetching a + wrong prefix for the BLOB. */ + mlog_write_ulint(field_ref + BTR_EXTERN_LEN + 4, + 0, + MLOG_4BYTES, &mtr); + } + + /* Commit mtr and release the BLOB block to save memory. */ + btr_blob_free(ext_block, TRUE, &mtr); + } +} + +/*************************************************************** +Frees the externally stored fields for a record. */ +static +void +btr_rec_free_externally_stored_fields( +/*==================================*/ + dict_index_t* index, /* in: index of the data, the index + tree MUST be X-latched */ + rec_t* rec, /* in/out: record */ + const ulint* offsets,/* in: rec_get_offsets(rec, index) */ + page_zip_des_t* page_zip,/* in: compressed page whose uncompressed + part will be updated, or NULL */ + enum trx_rb_ctx rb_ctx, /* in: rollback context */ + mtr_t* mtr) /* in: mini-transaction handle which contains + an X-latch to record page and to the index + tree */ +{ + ulint n_fields; + ulint i; + + ut_ad(rec_offs_validate(rec, index, offsets)); + ut_ad(mtr_memo_contains_page(mtr, rec, MTR_MEMO_PAGE_X_FIX)); + /* Free possible externally stored fields in the record */ + + ut_ad(dict_table_is_comp(index->table) == !!rec_offs_comp(offsets)); + n_fields = rec_offs_n_fields(offsets); + + for (i = 0; i < n_fields; i++) { + if (rec_offs_nth_extern(offsets, i)) { + ulint len; + byte* data + = rec_get_nth_field(rec, offsets, i, &len); + ut_a(len >= BTR_EXTERN_FIELD_REF_SIZE); + + btr_free_externally_stored_field( + index, data + len - BTR_EXTERN_FIELD_REF_SIZE, + rec, offsets, page_zip, i, rb_ctx, mtr); + } + } +} + +/*************************************************************** +Frees the externally stored fields for a record, if the field is mentioned +in the update vector. */ +static +void +btr_rec_free_updated_extern_fields( +/*===============================*/ + dict_index_t* index, /* in: index of rec; the index tree MUST be + X-latched */ + rec_t* rec, /* in/out: record */ + page_zip_des_t* page_zip,/* in: compressed page whose uncompressed + part will be updated, or NULL */ + const ulint* offsets,/* in: rec_get_offsets(rec, index) */ + const upd_t* update, /* in: update vector */ + enum trx_rb_ctx rb_ctx, /* in: rollback context */ + mtr_t* mtr) /* in: mini-transaction handle which contains + an X-latch to record page and to the tree */ +{ + ulint n_fields; + ulint i; + + ut_ad(rec_offs_validate(rec, index, offsets)); + ut_ad(mtr_memo_contains_page(mtr, rec, MTR_MEMO_PAGE_X_FIX)); + + /* Free possible externally stored fields in the record */ + + n_fields = upd_get_n_fields(update); + + for (i = 0; i < n_fields; i++) { + const upd_field_t* ufield = upd_get_nth_field(update, i); + + if (rec_offs_nth_extern(offsets, ufield->field_no)) { + ulint len; + byte* data = rec_get_nth_field( + rec, offsets, ufield->field_no, &len); + ut_a(len >= BTR_EXTERN_FIELD_REF_SIZE); + + btr_free_externally_stored_field( + index, data + len - BTR_EXTERN_FIELD_REF_SIZE, + rec, offsets, page_zip, + ufield->field_no, rb_ctx, mtr); + } + } +} + +/*********************************************************************** +Copies the prefix of an uncompressed BLOB. The clustered index record +that points to this BLOB must be protected by a lock or a page latch. */ +static +ulint +btr_copy_blob_prefix( +/*=================*/ + /* out: number of bytes written to buf */ + byte* buf, /* out: the externally stored part of + the field, or a prefix of it */ + ulint len, /* in: length of buf, in bytes */ + ulint space_id,/* in: space id of the BLOB pages */ + ulint page_no,/* in: page number of the first BLOB page */ + ulint offset) /* in: offset on the first BLOB page */ +{ + ulint copied_len = 0; + + for (;;) { + mtr_t mtr; + buf_block_t* block; + const page_t* page; + const byte* blob_header; + ulint part_len; + ulint copy_len; + + mtr_start(&mtr); + + block = buf_page_get(space_id, 0, page_no, RW_S_LATCH, &mtr); + buf_block_dbg_add_level(block, SYNC_EXTERN_STORAGE); + page = buf_block_get_frame(block); + + btr_check_blob_fil_page_type(space_id, page_no, page, TRUE); + + blob_header = page + offset; + part_len = btr_blob_get_part_len(blob_header); + copy_len = ut_min(part_len, len - copied_len); + + memcpy(buf + copied_len, + blob_header + BTR_BLOB_HDR_SIZE, copy_len); + copied_len += copy_len; + + page_no = btr_blob_get_next_page_no(blob_header); + + mtr_commit(&mtr); + + if (page_no == FIL_NULL || copy_len != part_len) { + return(copied_len); + } + + /* On other BLOB pages except the first the BLOB header + always is at the page data start: */ + + offset = FIL_PAGE_DATA; + + ut_ad(copied_len <= len); + } +} + +/*********************************************************************** +Copies the prefix of a compressed BLOB. The clustered index record +that points to this BLOB must be protected by a lock or a page latch. */ +static +void +btr_copy_zblob_prefix( +/*==================*/ + z_stream* d_stream,/* in/out: the decompressing stream */ + ulint zip_size,/* in: compressed BLOB page size */ + ulint space_id,/* in: space id of the BLOB pages */ + ulint page_no,/* in: page number of the first BLOB page */ + ulint offset) /* in: offset on the first BLOB page */ +{ + ulint page_type = FIL_PAGE_TYPE_ZBLOB; + + ut_ad(ut_is_2pow(zip_size)); + ut_ad(zip_size >= PAGE_ZIP_MIN_SIZE); + ut_ad(zip_size <= UNIV_PAGE_SIZE); + ut_ad(space_id); + + for (;;) { + buf_page_t* bpage; + int err; + ulint next_page_no; + + /* There is no latch on bpage directly. Instead, + bpage is protected by the B-tree page latch that + is being held on the clustered index record, or, + in row_merge_copy_blobs(), by an exclusive table lock. */ + bpage = buf_page_get_zip(space_id, zip_size, page_no); + + if (UNIV_UNLIKELY(!bpage)) { + ut_print_timestamp(stderr); + fprintf(stderr, + " InnoDB: Cannot load" + " compressed BLOB" + " page %lu space %lu\n", + (ulong) page_no, (ulong) space_id); + return; + } + + if (UNIV_UNLIKELY + (fil_page_get_type(bpage->zip.data) != page_type)) { + ut_print_timestamp(stderr); + fprintf(stderr, + " InnoDB: Unexpected type %lu of" + " compressed BLOB" + " page %lu space %lu\n", + (ulong) fil_page_get_type(bpage->zip.data), + (ulong) page_no, (ulong) space_id); + goto end_of_blob; + } + + next_page_no = mach_read_from_4(bpage->zip.data + offset); + + if (UNIV_LIKELY(offset == FIL_PAGE_NEXT)) { + /* When the BLOB begins at page header, + the compressed data payload does not + immediately follow the next page pointer. */ + offset = FIL_PAGE_DATA; + } else { + offset += 4; + } + + d_stream->next_in = bpage->zip.data + offset; + d_stream->avail_in = zip_size - offset; + + err = inflate(d_stream, Z_NO_FLUSH); + switch (err) { + case Z_OK: + if (!d_stream->avail_out) { + goto end_of_blob; + } + break; + case Z_STREAM_END: + if (next_page_no == FIL_NULL) { + goto end_of_blob; + } + /* fall through */ + default: +inflate_error: + ut_print_timestamp(stderr); + fprintf(stderr, + " InnoDB: inflate() of" + " compressed BLOB" + " page %lu space %lu returned %d (%s)\n", + (ulong) page_no, (ulong) space_id, + err, d_stream->msg); + case Z_BUF_ERROR: + goto end_of_blob; + } + + if (next_page_no == FIL_NULL) { + if (!d_stream->avail_in) { + ut_print_timestamp(stderr); + fprintf(stderr, + " InnoDB: unexpected end of" + " compressed BLOB" + " page %lu space %lu\n", + (ulong) page_no, + (ulong) space_id); + } else { + err = inflate(d_stream, Z_FINISH); + switch (err) { + case Z_STREAM_END: + case Z_BUF_ERROR: + break; + default: + goto inflate_error; + } + } + +end_of_blob: + buf_page_release_zip(bpage); + return; + } + + buf_page_release_zip(bpage); + + /* On other BLOB pages except the first + the BLOB header always is at the page header: */ + + page_no = next_page_no; + offset = FIL_PAGE_NEXT; + page_type = FIL_PAGE_TYPE_ZBLOB2; + } +} + +/*********************************************************************** +Copies the prefix of an externally stored field of a record. The +clustered index record that points to this BLOB must be protected by a +lock or a page latch. */ +static +ulint +btr_copy_externally_stored_field_prefix_low( +/*========================================*/ + /* out: number of bytes written to buf */ + byte* buf, /* out: the externally stored part of + the field, or a prefix of it */ + ulint len, /* in: length of buf, in bytes */ + ulint zip_size,/* in: nonzero=compressed BLOB page size, + zero for uncompressed BLOBs */ + ulint space_id,/* in: space id of the first BLOB page */ + ulint page_no,/* in: page number of the first BLOB page */ + ulint offset) /* in: offset on the first BLOB page */ +{ + if (UNIV_UNLIKELY(len == 0)) { + return(0); + } + + if (UNIV_UNLIKELY(zip_size)) { + int err; + z_stream d_stream; + mem_heap_t* heap; + + /* Zlib inflate needs 32 kilobytes for the default + window size, plus a few kilobytes for small objects. */ + heap = mem_heap_create(40000); + page_zip_set_alloc(&d_stream, heap); + + err = inflateInit(&d_stream); + ut_a(err == Z_OK); + + d_stream.next_out = buf; + d_stream.avail_out = len; + d_stream.avail_in = 0; + + btr_copy_zblob_prefix(&d_stream, zip_size, + space_id, page_no, offset); + inflateEnd(&d_stream); + mem_heap_free(heap); + return(d_stream.total_out); + } else { + return(btr_copy_blob_prefix(buf, len, space_id, + page_no, offset)); + } +} + +/*********************************************************************** +Copies the prefix of an externally stored field of a record. The +clustered index record must be protected by a lock or a page latch. */ +UNIV_INTERN +ulint +btr_copy_externally_stored_field_prefix( +/*====================================*/ + /* out: the length of the copied field, + or 0 if the column was being or has been + deleted */ + byte* buf, /* out: the field, or a prefix of it */ + ulint len, /* in: length of buf, in bytes */ + ulint zip_size,/* in: nonzero=compressed BLOB page size, + zero for uncompressed BLOBs */ + const byte* data, /* in: 'internally' stored part of the + field containing also the reference to + the external part; must be protected by + a lock or a page latch */ + ulint local_len)/* in: length of data, in bytes */ +{ + ulint space_id; + ulint page_no; + ulint offset; + + ut_a(local_len >= BTR_EXTERN_FIELD_REF_SIZE); + + local_len -= BTR_EXTERN_FIELD_REF_SIZE; + + if (UNIV_UNLIKELY(local_len >= len)) { + memcpy(buf, data, len); + return(len); + } + + memcpy(buf, data, local_len); + data += local_len; + + ut_a(memcmp(data, field_ref_zero, BTR_EXTERN_FIELD_REF_SIZE)); + + if (!mach_read_from_4(data + BTR_EXTERN_LEN + 4)) { + /* The externally stored part of the column has been + (partially) deleted. Signal the half-deleted BLOB + to the caller. */ + + return(0); + } + + space_id = mach_read_from_4(data + BTR_EXTERN_SPACE_ID); + + page_no = mach_read_from_4(data + BTR_EXTERN_PAGE_NO); + + offset = mach_read_from_4(data + BTR_EXTERN_OFFSET); + + return(local_len + + btr_copy_externally_stored_field_prefix_low(buf + local_len, + len - local_len, + zip_size, + space_id, page_no, + offset)); +} + +/*********************************************************************** +Copies an externally stored field of a record to mem heap. The +clustered index record must be protected by a lock or a page latch. */ +static +byte* +btr_copy_externally_stored_field( +/*=============================*/ + /* out: the whole field copied to heap */ + ulint* len, /* out: length of the whole field */ + const byte* data, /* in: 'internally' stored part of the + field containing also the reference to + the external part; must be protected by + a lock or a page latch */ + ulint zip_size,/* in: nonzero=compressed BLOB page size, + zero for uncompressed BLOBs */ + ulint local_len,/* in: length of data */ + mem_heap_t* heap) /* in: mem heap */ +{ + ulint space_id; + ulint page_no; + ulint offset; + ulint extern_len; + byte* buf; + + ut_a(local_len >= BTR_EXTERN_FIELD_REF_SIZE); + + local_len -= BTR_EXTERN_FIELD_REF_SIZE; + + space_id = mach_read_from_4(data + local_len + BTR_EXTERN_SPACE_ID); + + page_no = mach_read_from_4(data + local_len + BTR_EXTERN_PAGE_NO); + + offset = mach_read_from_4(data + local_len + BTR_EXTERN_OFFSET); + + /* Currently a BLOB cannot be bigger than 4 GB; we + leave the 4 upper bytes in the length field unused */ + + extern_len = mach_read_from_4(data + local_len + BTR_EXTERN_LEN + 4); + + buf = mem_heap_alloc(heap, local_len + extern_len); + + memcpy(buf, data, local_len); + *len = local_len + + btr_copy_externally_stored_field_prefix_low(buf + local_len, + extern_len, + zip_size, + space_id, + page_no, offset); + + return(buf); +} + +/*********************************************************************** +Copies an externally stored field of a record to mem heap. */ +UNIV_INTERN +byte* +btr_rec_copy_externally_stored_field( +/*=================================*/ + /* out: the field copied to heap */ + const rec_t* rec, /* in: record in a clustered index; + must be protected by a lock or a page latch */ + const ulint* offsets,/* in: array returned by rec_get_offsets() */ + ulint zip_size,/* in: nonzero=compressed BLOB page size, + zero for uncompressed BLOBs */ + ulint no, /* in: field number */ + ulint* len, /* out: length of the field */ + mem_heap_t* heap) /* in: mem heap */ +{ + ulint local_len; + const byte* data; + + ut_a(rec_offs_nth_extern(offsets, no)); + + /* An externally stored field can contain some initial + data from the field, and in the last 20 bytes it has the + space id, page number, and offset where the rest of the + field data is stored, and the data length in addition to + the data stored locally. We may need to store some data + locally to get the local record length above the 128 byte + limit so that field offsets are stored in two bytes, and + the extern bit is available in those two bytes. */ + + data = rec_get_nth_field(rec, offsets, no, &local_len); + + return(btr_copy_externally_stored_field(len, data, + zip_size, local_len, heap)); +} diff --git a/storage/xtradb/btr/btr0pcur.c b/storage/xtradb/btr/btr0pcur.c new file mode 100644 index 00000000000..b14efefe13f --- /dev/null +++ b/storage/xtradb/btr/btr0pcur.c @@ -0,0 +1,584 @@ +/***************************************************************************** + +Copyright (c) 1996, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/****************************************************** +The index tree persistent cursor + +Created 2/23/1996 Heikki Tuuri +*******************************************************/ + +#include "btr0pcur.h" + +#ifdef UNIV_NONINL +#include "btr0pcur.ic" +#endif + +#include "ut0byte.h" +#include "rem0cmp.h" +#include "trx0trx.h" + +/****************************************************************** +Allocates memory for a persistent cursor object and initializes the cursor. */ +UNIV_INTERN +btr_pcur_t* +btr_pcur_create_for_mysql(void) +/*============================*/ + /* out, own: persistent cursor */ +{ + btr_pcur_t* pcur; + + pcur = mem_alloc(sizeof(btr_pcur_t)); + + pcur->btr_cur.index = NULL; + btr_pcur_init(pcur); + + return(pcur); +} + +/****************************************************************** +Frees the memory for a persistent cursor object. */ +UNIV_INTERN +void +btr_pcur_free_for_mysql( +/*====================*/ + btr_pcur_t* cursor) /* in, own: persistent cursor */ +{ + if (cursor->old_rec_buf != NULL) { + + mem_free(cursor->old_rec_buf); + + cursor->old_rec_buf = NULL; + } + + cursor->btr_cur.page_cur.rec = NULL; + cursor->old_rec = NULL; + cursor->old_n_fields = 0; + cursor->old_stored = BTR_PCUR_OLD_NOT_STORED; + + cursor->latch_mode = BTR_NO_LATCHES; + cursor->pos_state = BTR_PCUR_NOT_POSITIONED; + + mem_free(cursor); +} + +/****************************************************************** +The position of the cursor is stored by taking an initial segment of the +record the cursor is positioned on, before, or after, and copying it to the +cursor data structure, or just setting a flag if the cursor id before the +first in an EMPTY tree, or after the last in an EMPTY tree. NOTE that the +page where the cursor is positioned must not be empty if the index tree is +not totally empty! */ +UNIV_INTERN +void +btr_pcur_store_position( +/*====================*/ + btr_pcur_t* cursor, /* in: persistent cursor */ + mtr_t* mtr) /* in: mtr */ +{ + page_cur_t* page_cursor; + buf_block_t* block; + rec_t* rec; + dict_index_t* index; + page_t* page; + ulint offs; + + ut_a(cursor->pos_state == BTR_PCUR_IS_POSITIONED); + ut_ad(cursor->latch_mode != BTR_NO_LATCHES); + + block = btr_pcur_get_block(cursor); + index = btr_cur_get_index(btr_pcur_get_btr_cur(cursor)); + + page_cursor = btr_pcur_get_page_cur(cursor); + + rec = page_cur_get_rec(page_cursor); + page = page_align(rec); + offs = page_offset(rec); + + ut_ad(mtr_memo_contains(mtr, block, MTR_MEMO_PAGE_S_FIX) + || mtr_memo_contains(mtr, block, MTR_MEMO_PAGE_X_FIX)); + ut_a(cursor->latch_mode != BTR_NO_LATCHES); + + if (UNIV_UNLIKELY(page_get_n_recs(page) == 0)) { + /* It must be an empty index tree; NOTE that in this case + we do not store the modify_clock, but always do a search + if we restore the cursor position */ + + ut_a(btr_page_get_next(page, mtr) == FIL_NULL); + ut_a(btr_page_get_prev(page, mtr) == FIL_NULL); + + cursor->old_stored = BTR_PCUR_OLD_STORED; + + if (page_rec_is_supremum_low(offs)) { + + cursor->rel_pos = BTR_PCUR_AFTER_LAST_IN_TREE; + } else { + cursor->rel_pos = BTR_PCUR_BEFORE_FIRST_IN_TREE; + } + + return; + } + + if (page_rec_is_supremum_low(offs)) { + + rec = page_rec_get_prev(rec); + + cursor->rel_pos = BTR_PCUR_AFTER; + + } else if (page_rec_is_infimum_low(offs)) { + + rec = page_rec_get_next(rec); + + cursor->rel_pos = BTR_PCUR_BEFORE; + } else { + cursor->rel_pos = BTR_PCUR_ON; + } + + cursor->old_stored = BTR_PCUR_OLD_STORED; + cursor->old_rec = dict_index_copy_rec_order_prefix( + index, rec, &cursor->old_n_fields, + &cursor->old_rec_buf, &cursor->buf_size); + + cursor->block_when_stored = block; + cursor->modify_clock = buf_block_get_modify_clock(block); +} + +/****************************************************************** +Copies the stored position of a pcur to another pcur. */ +UNIV_INTERN +void +btr_pcur_copy_stored_position( +/*==========================*/ + btr_pcur_t* pcur_receive, /* in: pcur which will receive the + position info */ + btr_pcur_t* pcur_donate) /* in: pcur from which the info is + copied */ +{ + if (pcur_receive->old_rec_buf) { + mem_free(pcur_receive->old_rec_buf); + } + + ut_memcpy(pcur_receive, pcur_donate, sizeof(btr_pcur_t)); + + if (pcur_donate->old_rec_buf) { + + pcur_receive->old_rec_buf = mem_alloc(pcur_donate->buf_size); + + ut_memcpy(pcur_receive->old_rec_buf, pcur_donate->old_rec_buf, + pcur_donate->buf_size); + pcur_receive->old_rec = pcur_receive->old_rec_buf + + (pcur_donate->old_rec - pcur_donate->old_rec_buf); + } + + pcur_receive->old_n_fields = pcur_donate->old_n_fields; +} + +/****************************************************************** +Restores the stored position of a persistent cursor bufferfixing the page and +obtaining the specified latches. If the cursor position was saved when the +(1) cursor was positioned on a user record: this function restores the position +to the last record LESS OR EQUAL to the stored record; +(2) cursor was positioned on a page infimum record: restores the position to +the last record LESS than the user record which was the successor of the page +infimum; +(3) cursor was positioned on the page supremum: restores to the first record +GREATER than the user record which was the predecessor of the supremum. +(4) cursor was positioned before the first or after the last in an empty tree: +restores to before first or after the last in the tree. */ +UNIV_INTERN +ibool +btr_pcur_restore_position( +/*======================*/ + /* out: TRUE if the cursor position + was stored when it was on a user record + and it can be restored on a user record + whose ordering fields are identical to + the ones of the original user record */ + ulint latch_mode, /* in: BTR_SEARCH_LEAF, ... */ + btr_pcur_t* cursor, /* in: detached persistent cursor */ + mtr_t* mtr) /* in: mtr */ +{ + dict_index_t* index; + dtuple_t* tuple; + ulint mode; + ulint old_mode; + mem_heap_t* heap; + + index = btr_cur_get_index(btr_pcur_get_btr_cur(cursor)); + + if (UNIV_UNLIKELY(cursor->old_stored != BTR_PCUR_OLD_STORED) + || UNIV_UNLIKELY(cursor->pos_state != BTR_PCUR_WAS_POSITIONED + && cursor->pos_state != BTR_PCUR_IS_POSITIONED)) { + ut_print_buf(stderr, cursor, sizeof(btr_pcur_t)); + putc('\n', stderr); + if (cursor->trx_if_known) { + trx_print(stderr, cursor->trx_if_known, 0); + } + + ut_error; + } + + if (UNIV_UNLIKELY + (cursor->rel_pos == BTR_PCUR_AFTER_LAST_IN_TREE + || cursor->rel_pos == BTR_PCUR_BEFORE_FIRST_IN_TREE)) { + + /* In these cases we do not try an optimistic restoration, + but always do a search */ + + btr_cur_open_at_index_side( + cursor->rel_pos == BTR_PCUR_BEFORE_FIRST_IN_TREE, + index, latch_mode, btr_pcur_get_btr_cur(cursor), mtr); + + cursor->block_when_stored = btr_pcur_get_block(cursor); + + return(FALSE); + } + + ut_a(cursor->old_rec); + ut_a(cursor->old_n_fields); + + if (UNIV_LIKELY(latch_mode == BTR_SEARCH_LEAF) + || UNIV_LIKELY(latch_mode == BTR_MODIFY_LEAF)) { + /* Try optimistic restoration */ + + if (UNIV_LIKELY(buf_page_optimistic_get( + latch_mode, + cursor->block_when_stored, + cursor->modify_clock, mtr))) { + cursor->pos_state = BTR_PCUR_IS_POSITIONED; + + buf_block_dbg_add_level(btr_pcur_get_block(cursor), + SYNC_TREE_NODE); + + if (cursor->rel_pos == BTR_PCUR_ON) { +#ifdef UNIV_DEBUG + const rec_t* rec; + const ulint* offsets1; + const ulint* offsets2; +#endif /* UNIV_DEBUG */ + cursor->latch_mode = latch_mode; +#ifdef UNIV_DEBUG + rec = btr_pcur_get_rec(cursor); + + heap = mem_heap_create(256); + offsets1 = rec_get_offsets( + cursor->old_rec, index, NULL, + cursor->old_n_fields, &heap); + offsets2 = rec_get_offsets( + rec, index, NULL, + cursor->old_n_fields, &heap); + + ut_ad(!cmp_rec_rec(cursor->old_rec, + rec, offsets1, offsets2, + index)); + mem_heap_free(heap); +#endif /* UNIV_DEBUG */ + return(TRUE); + } + + return(FALSE); + } + } + + /* If optimistic restoration did not succeed, open the cursor anew */ + + heap = mem_heap_create(256); + + tuple = dict_index_build_data_tuple(index, cursor->old_rec, + cursor->old_n_fields, heap); + + /* Save the old search mode of the cursor */ + old_mode = cursor->search_mode; + + if (UNIV_LIKELY(cursor->rel_pos == BTR_PCUR_ON)) { + mode = PAGE_CUR_LE; + } else if (cursor->rel_pos == BTR_PCUR_AFTER) { + mode = PAGE_CUR_G; + } else { + ut_ad(cursor->rel_pos == BTR_PCUR_BEFORE); + mode = PAGE_CUR_L; + } + + btr_pcur_open_with_no_init(index, tuple, mode, latch_mode, + cursor, 0, mtr); + + /* Restore the old search mode */ + cursor->search_mode = old_mode; + + if (cursor->rel_pos == BTR_PCUR_ON + && btr_pcur_is_on_user_rec(cursor) + && 0 == cmp_dtuple_rec(tuple, btr_pcur_get_rec(cursor), + rec_get_offsets( + btr_pcur_get_rec(cursor), index, + NULL, ULINT_UNDEFINED, &heap))) { + + /* We have to store the NEW value for the modify clock, since + the cursor can now be on a different page! But we can retain + the value of old_rec */ + + cursor->block_when_stored = btr_pcur_get_block(cursor); + cursor->modify_clock = buf_block_get_modify_clock( + cursor->block_when_stored); + cursor->old_stored = BTR_PCUR_OLD_STORED; + + mem_heap_free(heap); + + return(TRUE); + } + + mem_heap_free(heap); + + /* We have to store new position information, modify_clock etc., + to the cursor because it can now be on a different page, the record + under it may have been removed, etc. */ + + btr_pcur_store_position(cursor, mtr); + + return(FALSE); +} + +/****************************************************************** +If the latch mode of the cursor is BTR_LEAF_SEARCH or BTR_LEAF_MODIFY, +releases the page latch and bufferfix reserved by the cursor. +NOTE! In the case of BTR_LEAF_MODIFY, there should not exist changes +made by the current mini-transaction to the data protected by the +cursor latch, as then the latch must not be released until mtr_commit. */ +UNIV_INTERN +void +btr_pcur_release_leaf( +/*==================*/ + btr_pcur_t* cursor, /* in: persistent cursor */ + mtr_t* mtr) /* in: mtr */ +{ + buf_block_t* block; + + ut_a(cursor->pos_state == BTR_PCUR_IS_POSITIONED); + ut_ad(cursor->latch_mode != BTR_NO_LATCHES); + + block = btr_pcur_get_block(cursor); + + btr_leaf_page_release(block, cursor->latch_mode, mtr); + + cursor->latch_mode = BTR_NO_LATCHES; + + cursor->pos_state = BTR_PCUR_WAS_POSITIONED; +} + +/************************************************************* +Moves the persistent cursor to the first record on the next page. Releases the +latch on the current page, and bufferunfixes it. Note that there must not be +modifications on the current page, as then the x-latch can be released only in +mtr_commit. */ +UNIV_INTERN +void +btr_pcur_move_to_next_page( +/*=======================*/ + btr_pcur_t* cursor, /* in: persistent cursor; must be on the + last record of the current page */ + mtr_t* mtr) /* in: mtr */ +{ + ulint next_page_no; + ulint space; + ulint zip_size; + page_t* page; + buf_block_t* next_block; + page_t* next_page; + + ut_a(cursor->pos_state == BTR_PCUR_IS_POSITIONED); + ut_ad(cursor->latch_mode != BTR_NO_LATCHES); + ut_ad(btr_pcur_is_after_last_on_page(cursor)); + + cursor->old_stored = BTR_PCUR_OLD_NOT_STORED; + + page = btr_pcur_get_page(cursor); + next_page_no = btr_page_get_next(page, mtr); + space = buf_block_get_space(btr_pcur_get_block(cursor)); + zip_size = buf_block_get_zip_size(btr_pcur_get_block(cursor)); + + ut_ad(next_page_no != FIL_NULL); + + next_block = btr_block_get(space, zip_size, next_page_no, + cursor->latch_mode, mtr); + next_page = buf_block_get_frame(next_block); +#ifdef UNIV_BTR_DEBUG + ut_a(page_is_comp(next_page) == page_is_comp(page)); + ut_a(btr_page_get_prev(next_page, mtr) + == buf_block_get_page_no(btr_pcur_get_block(cursor))); +#endif /* UNIV_BTR_DEBUG */ + next_block->check_index_page_at_flush = TRUE; + + btr_leaf_page_release(btr_pcur_get_block(cursor), + cursor->latch_mode, mtr); + + page_cur_set_before_first(next_block, btr_pcur_get_page_cur(cursor)); + + page_check_dir(next_page); +} + +/************************************************************* +Moves the persistent cursor backward if it is on the first record of the page. +Commits mtr. Note that to prevent a possible deadlock, the operation +first stores the position of the cursor, commits mtr, acquires the necessary +latches and restores the cursor position again before returning. The +alphabetical position of the cursor is guaranteed to be sensible on +return, but it may happen that the cursor is not positioned on the last +record of any page, because the structure of the tree may have changed +during the time when the cursor had no latches. */ +UNIV_INTERN +void +btr_pcur_move_backward_from_page( +/*=============================*/ + btr_pcur_t* cursor, /* in: persistent cursor, must be on the first + record of the current page */ + mtr_t* mtr) /* in: mtr */ +{ + ulint prev_page_no; + ulint space; + page_t* page; + buf_block_t* prev_block; + ulint latch_mode; + ulint latch_mode2; + + ut_a(cursor->pos_state == BTR_PCUR_IS_POSITIONED); + ut_ad(cursor->latch_mode != BTR_NO_LATCHES); + ut_ad(btr_pcur_is_before_first_on_page(cursor)); + ut_ad(!btr_pcur_is_before_first_in_tree(cursor, mtr)); + + latch_mode = cursor->latch_mode; + + if (latch_mode == BTR_SEARCH_LEAF) { + + latch_mode2 = BTR_SEARCH_PREV; + + } else if (latch_mode == BTR_MODIFY_LEAF) { + + latch_mode2 = BTR_MODIFY_PREV; + } else { + latch_mode2 = 0; /* To eliminate compiler warning */ + ut_error; + } + + btr_pcur_store_position(cursor, mtr); + + mtr_commit(mtr); + + mtr_start(mtr); + + btr_pcur_restore_position(latch_mode2, cursor, mtr); + + page = btr_pcur_get_page(cursor); + + prev_page_no = btr_page_get_prev(page, mtr); + space = buf_block_get_space(btr_pcur_get_block(cursor)); + + if (prev_page_no == FIL_NULL) { + } else if (btr_pcur_is_before_first_on_page(cursor)) { + + prev_block = btr_pcur_get_btr_cur(cursor)->left_block; + + btr_leaf_page_release(btr_pcur_get_block(cursor), + latch_mode, mtr); + + page_cur_set_after_last(prev_block, + btr_pcur_get_page_cur(cursor)); + } else { + + /* The repositioned cursor did not end on an infimum record on + a page. Cursor repositioning acquired a latch also on the + previous page, but we do not need the latch: release it. */ + + prev_block = btr_pcur_get_btr_cur(cursor)->left_block; + + btr_leaf_page_release(prev_block, latch_mode, mtr); + } + + cursor->latch_mode = latch_mode; + + cursor->old_stored = BTR_PCUR_OLD_NOT_STORED; +} + +/************************************************************* +Moves the persistent cursor to the previous record in the tree. If no records +are left, the cursor stays 'before first in tree'. */ +UNIV_INTERN +ibool +btr_pcur_move_to_prev( +/*==================*/ + /* out: TRUE if the cursor was not before first + in tree */ + btr_pcur_t* cursor, /* in: persistent cursor; NOTE that the + function may release the page latch */ + mtr_t* mtr) /* in: mtr */ +{ + ut_ad(cursor->pos_state == BTR_PCUR_IS_POSITIONED); + ut_ad(cursor->latch_mode != BTR_NO_LATCHES); + + cursor->old_stored = BTR_PCUR_OLD_NOT_STORED; + + if (btr_pcur_is_before_first_on_page(cursor)) { + + if (btr_pcur_is_before_first_in_tree(cursor, mtr)) { + + return(FALSE); + } + + btr_pcur_move_backward_from_page(cursor, mtr); + + return(TRUE); + } + + btr_pcur_move_to_prev_on_page(cursor); + + return(TRUE); +} + +/****************************************************************** +If mode is PAGE_CUR_G or PAGE_CUR_GE, opens a persistent cursor on the first +user record satisfying the search condition, in the case PAGE_CUR_L or +PAGE_CUR_LE, on the last user record. If no such user record exists, then +in the first case sets the cursor after last in tree, and in the latter case +before first in tree. The latching mode must be BTR_SEARCH_LEAF or +BTR_MODIFY_LEAF. */ +UNIV_INTERN +void +btr_pcur_open_on_user_rec( +/*======================*/ + dict_index_t* index, /* in: index */ + const dtuple_t* tuple, /* in: tuple on which search done */ + ulint mode, /* in: PAGE_CUR_L, ... */ + ulint latch_mode, /* in: BTR_SEARCH_LEAF or + BTR_MODIFY_LEAF */ + btr_pcur_t* cursor, /* in: memory buffer for persistent + cursor */ + mtr_t* mtr) /* in: mtr */ +{ + btr_pcur_open(index, tuple, mode, latch_mode, cursor, mtr); + + if ((mode == PAGE_CUR_GE) || (mode == PAGE_CUR_G)) { + + if (btr_pcur_is_after_last_on_page(cursor)) { + + btr_pcur_move_to_next_user_rec(cursor, mtr); + } + } else { + ut_ad((mode == PAGE_CUR_LE) || (mode == PAGE_CUR_L)); + + /* Not implemented yet */ + + ut_error; + } +} diff --git a/storage/xtradb/btr/btr0sea.c b/storage/xtradb/btr/btr0sea.c new file mode 100644 index 00000000000..a60f02cf86c --- /dev/null +++ b/storage/xtradb/btr/btr0sea.c @@ -0,0 +1,1878 @@ +/***************************************************************************** + +Copyright (c) 1996, 2009, Innobase Oy. All Rights Reserved. +Copyright (c) 2008, Google Inc. + +Portions of this file contain modifications contributed and copyrighted by +Google, Inc. Those modifications are gratefully acknowledged and are described +briefly in the InnoDB documentation. The contributions by Google are +incorporated with their permission, and subject to the conditions contained in +the file COPYING.Google. + +This program is free software; you can redistribute it and/or modify it under +the terms of the GNU General Public License as published by the Free Software +Foundation; version 2 of the License. + +This program is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + +You 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 + +*****************************************************************************/ + +/************************************************************************ +The index tree adaptive search + +Created 2/17/1996 Heikki Tuuri +*************************************************************************/ + +#include "btr0sea.h" +#ifdef UNIV_NONINL +#include "btr0sea.ic" +#endif + +#include "buf0buf.h" +#include "page0page.h" +#include "page0cur.h" +#include "btr0cur.h" +#include "btr0pcur.h" +#include "btr0btr.h" +#include "ha0ha.h" + +/* Flag: has the search system been enabled? +Protected by btr_search_latch and btr_search_enabled_mutex. */ +UNIV_INTERN char btr_search_enabled = TRUE; + +static mutex_t btr_search_enabled_mutex; + +/* A dummy variable to fool the compiler */ +UNIV_INTERN ulint btr_search_this_is_zero = 0; + +#ifdef UNIV_SEARCH_PERF_STAT +UNIV_INTERN ulint btr_search_n_succ = 0; +UNIV_INTERN ulint btr_search_n_hash_fail = 0; +#endif /* UNIV_SEARCH_PERF_STAT */ + +/* padding to prevent other memory update +hotspots from residing on the same memory +cache line as btr_search_latch */ +UNIV_INTERN byte btr_sea_pad1[64]; + +/* The latch protecting the adaptive search system: this latch protects the +(1) positions of records on those pages where a hash index has been built. +NOTE: It does not protect values of non-ordering fields within a record from +being updated in-place! We can use fact (1) to perform unique searches to +indexes. */ + +/* We will allocate the latch from dynamic memory to get it to the +same DRAM page as other hotspot semaphores */ +UNIV_INTERN rw_lock_t* btr_search_latch_temp; + +/* padding to prevent other memory update hotspots from residing on +the same memory cache line */ +UNIV_INTERN byte btr_sea_pad2[64]; + +UNIV_INTERN btr_search_sys_t* btr_search_sys; + +/* If the number of records on the page divided by this parameter +would have been successfully accessed using a hash index, the index +is then built on the page, assuming the global limit has been reached */ + +#define BTR_SEARCH_PAGE_BUILD_LIMIT 16 + +/* The global limit for consecutive potentially successful hash searches, +before hash index building is started */ + +#define BTR_SEARCH_BUILD_LIMIT 100 + +/************************************************************************ +Builds a hash index on a page with the given parameters. If the page already +has a hash index with different parameters, the old hash index is removed. +If index is non-NULL, this function checks if n_fields and n_bytes are +sensible values, and does not build a hash index if not. */ +static +void +btr_search_build_page_hash_index( +/*=============================*/ + dict_index_t* index, /* in: index for which to build, or NULL if + not known */ + buf_block_t* block, /* in: index page, s- or x-latched */ + ulint n_fields,/* in: hash this many full fields */ + ulint n_bytes,/* in: hash this many bytes from the next + field */ + ibool left_side);/* in: hash for searches from left side? */ + +/********************************************************************* +This function should be called before reserving any btr search mutex, if +the intended operation might add nodes to the search system hash table. +Because of the latching order, once we have reserved the btr search system +latch, we cannot allocate a free frame from the buffer pool. Checks that +there is a free buffer frame allocated for hash table heap in the btr search +system. If not, allocates a free frames for the heap. This check makes it +probable that, when have reserved the btr search system latch and we need to +allocate a new node to the hash table, it will succeed. However, the check +will not guarantee success. */ +static +void +btr_search_check_free_space_in_heap(void) +/*=====================================*/ +{ + hash_table_t* table; + mem_heap_t* heap; + +#ifdef UNIV_SYNC_DEBUG + ut_ad(!rw_lock_own(&btr_search_latch, RW_LOCK_SHARED)); + ut_ad(!rw_lock_own(&btr_search_latch, RW_LOCK_EX)); +#endif /* UNIV_SYNC_DEBUG */ + + table = btr_search_sys->hash_index; + + heap = table->heap; + + /* Note that we peek the value of heap->free_block without reserving + the latch: this is ok, because we will not guarantee that there will + be enough free space in the hash table. */ + + if (heap->free_block == NULL) { + buf_block_t* block = buf_block_alloc(0); + + rw_lock_x_lock(&btr_search_latch); + + if (heap->free_block == NULL) { + heap->free_block = block; + } else { + buf_block_free(block); + } + + rw_lock_x_unlock(&btr_search_latch); + } +} + +/********************************************************************* +Creates and initializes the adaptive search system at a database start. */ +UNIV_INTERN +void +btr_search_sys_create( +/*==================*/ + ulint hash_size) /* in: hash index hash table size */ +{ + /* We allocate the search latch from dynamic memory: + see above at the global variable definition */ + + btr_search_latch_temp = mem_alloc(sizeof(rw_lock_t)); + + rw_lock_create(&btr_search_latch, SYNC_SEARCH_SYS); + mutex_create(&btr_search_enabled_mutex, SYNC_SEARCH_SYS_CONF); + + btr_search_sys = mem_alloc(sizeof(btr_search_sys_t)); + + btr_search_sys->hash_index = ha_create(hash_size, 0, 0); +} + +/************************************************************************ +Disable the adaptive hash search system and empty the index. */ +UNIV_INTERN +void +btr_search_disable(void) +/*====================*/ +{ + mutex_enter(&btr_search_enabled_mutex); + rw_lock_x_lock(&btr_search_latch); + + btr_search_enabled = FALSE; + + /* Clear all block->is_hashed flags and remove all entries + from btr_search_sys->hash_index. */ + buf_pool_drop_hash_index(); + + /* btr_search_enabled_mutex should guarantee this. */ + ut_ad(!btr_search_enabled); + + rw_lock_x_unlock(&btr_search_latch); + mutex_exit(&btr_search_enabled_mutex); +} + +/************************************************************************ +Enable the adaptive hash search system. */ +UNIV_INTERN +void +btr_search_enable(void) +/*====================*/ +{ + mutex_enter(&btr_search_enabled_mutex); + rw_lock_x_lock(&btr_search_latch); + + btr_search_enabled = TRUE; + + rw_lock_x_unlock(&btr_search_latch); + mutex_exit(&btr_search_enabled_mutex); +} + +/********************************************************************* +Creates and initializes a search info struct. */ +UNIV_INTERN +btr_search_t* +btr_search_info_create( +/*===================*/ + /* out, own: search info struct */ + mem_heap_t* heap) /* in: heap where created */ +{ + btr_search_t* info; + + info = mem_heap_alloc(heap, sizeof(btr_search_t)); + +#ifdef UNIV_DEBUG + info->magic_n = BTR_SEARCH_MAGIC_N; +#endif /* UNIV_DEBUG */ + + info->ref_count = 0; + info->root_guess = NULL; + + info->hash_analysis = 0; + info->n_hash_potential = 0; + + info->last_hash_succ = FALSE; + +#ifdef UNIV_SEARCH_PERF_STAT + info->n_hash_succ = 0; + info->n_hash_fail = 0; + info->n_patt_succ = 0; + info->n_searches = 0; +#endif /* UNIV_SEARCH_PERF_STAT */ + + /* Set some sensible values */ + info->n_fields = 1; + info->n_bytes = 0; + + info->left_side = TRUE; + + return(info); +} + +/********************************************************************* +Returns the value of ref_count. The value is protected by +btr_search_latch. */ +UNIV_INTERN +ulint +btr_search_info_get_ref_count( +/*==========================*/ + /* out: ref_count value. */ + btr_search_t* info) /* in: search info. */ +{ + ulint ret; + + ut_ad(info); + +#ifdef UNIV_SYNC_DEBUG + ut_ad(!rw_lock_own(&btr_search_latch, RW_LOCK_SHARED)); + ut_ad(!rw_lock_own(&btr_search_latch, RW_LOCK_EX)); +#endif /* UNIV_SYNC_DEBUG */ + + rw_lock_s_lock(&btr_search_latch); + ret = info->ref_count; + rw_lock_s_unlock(&btr_search_latch); + + return(ret); +} + +/************************************************************************* +Updates the search info of an index about hash successes. NOTE that info +is NOT protected by any semaphore, to save CPU time! Do not assume its fields +are consistent. */ +static +void +btr_search_info_update_hash( +/*========================*/ + btr_search_t* info, /* in/out: search info */ + btr_cur_t* cursor) /* in: cursor which was just positioned */ +{ + dict_index_t* index; + ulint n_unique; + int cmp; + +#ifdef UNIV_SYNC_DEBUG + ut_ad(!rw_lock_own(&btr_search_latch, RW_LOCK_SHARED)); + ut_ad(!rw_lock_own(&btr_search_latch, RW_LOCK_EX)); +#endif /* UNIV_SYNC_DEBUG */ + + index = cursor->index; + + if (dict_index_is_ibuf(index)) { + /* So many deletes are performed on an insert buffer tree + that we do not consider a hash index useful on it: */ + + return; + } + + n_unique = dict_index_get_n_unique_in_tree(index); + + if (info->n_hash_potential == 0) { + + goto set_new_recomm; + } + + /* Test if the search would have succeeded using the recommended + hash prefix */ + + if (info->n_fields >= n_unique && cursor->up_match >= n_unique) { +increment_potential: + info->n_hash_potential++; + + return; + } + + cmp = ut_pair_cmp(info->n_fields, info->n_bytes, + cursor->low_match, cursor->low_bytes); + + if (info->left_side ? cmp <= 0 : cmp > 0) { + + goto set_new_recomm; + } + + cmp = ut_pair_cmp(info->n_fields, info->n_bytes, + cursor->up_match, cursor->up_bytes); + + if (info->left_side ? cmp <= 0 : cmp > 0) { + + goto increment_potential; + } + +set_new_recomm: + /* We have to set a new recommendation; skip the hash analysis + for a while to avoid unnecessary CPU time usage when there is no + chance for success */ + + info->hash_analysis = 0; + + cmp = ut_pair_cmp(cursor->up_match, cursor->up_bytes, + cursor->low_match, cursor->low_bytes); + if (cmp == 0) { + info->n_hash_potential = 0; + + /* For extra safety, we set some sensible values here */ + + info->n_fields = 1; + info->n_bytes = 0; + + info->left_side = TRUE; + + } else if (cmp > 0) { + info->n_hash_potential = 1; + + if (cursor->up_match >= n_unique) { + + info->n_fields = n_unique; + info->n_bytes = 0; + + } else if (cursor->low_match < cursor->up_match) { + + info->n_fields = cursor->low_match + 1; + info->n_bytes = 0; + } else { + info->n_fields = cursor->low_match; + info->n_bytes = cursor->low_bytes + 1; + } + + info->left_side = TRUE; + } else { + info->n_hash_potential = 1; + + if (cursor->low_match >= n_unique) { + + info->n_fields = n_unique; + info->n_bytes = 0; + + } else if (cursor->low_match > cursor->up_match) { + + info->n_fields = cursor->up_match + 1; + info->n_bytes = 0; + } else { + info->n_fields = cursor->up_match; + info->n_bytes = cursor->up_bytes + 1; + } + + info->left_side = FALSE; + } +} + +/************************************************************************* +Updates the block search info on hash successes. NOTE that info and +block->n_hash_helps, n_fields, n_bytes, side are NOT protected by any +semaphore, to save CPU time! Do not assume the fields are consistent. */ +static +ibool +btr_search_update_block_hash_info( +/*==============================*/ + /* out: TRUE if building a (new) hash index on + the block is recommended */ + btr_search_t* info, /* in: search info */ + buf_block_t* block, /* in: buffer block */ + btr_cur_t* cursor __attribute__((unused))) + /* in: cursor */ +{ +#ifdef UNIV_SYNC_DEBUG + ut_ad(!rw_lock_own(&btr_search_latch, RW_LOCK_SHARED)); + ut_ad(!rw_lock_own(&btr_search_latch, RW_LOCK_EX)); + ut_ad(rw_lock_own(&block->lock, RW_LOCK_SHARED) + || rw_lock_own(&block->lock, RW_LOCK_EX)); +#endif /* UNIV_SYNC_DEBUG */ + ut_ad(cursor); + + info->last_hash_succ = FALSE; + + ut_a(buf_block_state_valid(block)); + ut_ad(info->magic_n == BTR_SEARCH_MAGIC_N); + + if ((block->n_hash_helps > 0) + && (info->n_hash_potential > 0) + && (block->n_fields == info->n_fields) + && (block->n_bytes == info->n_bytes) + && (block->left_side == info->left_side)) { + + if ((block->is_hashed) + && (block->curr_n_fields == info->n_fields) + && (block->curr_n_bytes == info->n_bytes) + && (block->curr_left_side == info->left_side)) { + + /* The search would presumably have succeeded using + the hash index */ + + info->last_hash_succ = TRUE; + } + + block->n_hash_helps++; + } else { + block->n_hash_helps = 1; + block->n_fields = info->n_fields; + block->n_bytes = info->n_bytes; + block->left_side = info->left_side; + } + +#ifdef UNIV_DEBUG + if (cursor->index->table->does_not_fit_in_memory) { + block->n_hash_helps = 0; + } +#endif /* UNIV_DEBUG */ + + if ((block->n_hash_helps > page_get_n_recs(block->frame) + / BTR_SEARCH_PAGE_BUILD_LIMIT) + && (info->n_hash_potential >= BTR_SEARCH_BUILD_LIMIT)) { + + if ((!block->is_hashed) + || (block->n_hash_helps + > 2 * page_get_n_recs(block->frame)) + || (block->n_fields != block->curr_n_fields) + || (block->n_bytes != block->curr_n_bytes) + || (block->left_side != block->curr_left_side)) { + + /* Build a new hash index on the page */ + + return(TRUE); + } + } + + return(FALSE); +} + +/************************************************************************* +Updates a hash node reference when it has been unsuccessfully used in a +search which could have succeeded with the used hash parameters. This can +happen because when building a hash index for a page, we do not check +what happens at page boundaries, and therefore there can be misleading +hash nodes. Also, collisions in the fold value can lead to misleading +references. This function lazily fixes these imperfections in the hash +index. */ +static +void +btr_search_update_hash_ref( +/*=======================*/ + btr_search_t* info, /* in: search info */ + buf_block_t* block, /* in: buffer block where cursor positioned */ + btr_cur_t* cursor) /* in: cursor */ +{ + ulint fold; + rec_t* rec; + dulint index_id; + + ut_ad(cursor->flag == BTR_CUR_HASH_FAIL); +#ifdef UNIV_SYNC_DEBUG + ut_ad(rw_lock_own(&btr_search_latch, RW_LOCK_EX)); + ut_ad(rw_lock_own(&(block->lock), RW_LOCK_SHARED) + || rw_lock_own(&(block->lock), RW_LOCK_EX)); +#endif /* UNIV_SYNC_DEBUG */ + ut_ad(page_align(btr_cur_get_rec(cursor)) + == buf_block_get_frame(block)); + + if (!block->is_hashed) { + + return; + } + + ut_a(block->index == cursor->index); + ut_a(!dict_index_is_ibuf(cursor->index)); + + if ((info->n_hash_potential > 0) + && (block->curr_n_fields == info->n_fields) + && (block->curr_n_bytes == info->n_bytes) + && (block->curr_left_side == info->left_side)) { + mem_heap_t* heap = NULL; + ulint offsets_[REC_OFFS_NORMAL_SIZE]; + rec_offs_init(offsets_); + + rec = btr_cur_get_rec(cursor); + + if (!page_rec_is_user_rec(rec)) { + + return; + } + + index_id = cursor->index->id; + fold = rec_fold(rec, + rec_get_offsets(rec, cursor->index, offsets_, + ULINT_UNDEFINED, &heap), + block->curr_n_fields, + block->curr_n_bytes, index_id); + if (UNIV_LIKELY_NULL(heap)) { + mem_heap_free(heap); + } +#ifdef UNIV_SYNC_DEBUG + ut_ad(rw_lock_own(&btr_search_latch, RW_LOCK_EX)); +#endif /* UNIV_SYNC_DEBUG */ + + ha_insert_for_fold(btr_search_sys->hash_index, fold, + block, rec); + } +} + +/************************************************************************* +Updates the search info. */ +UNIV_INTERN +void +btr_search_info_update_slow( +/*========================*/ + btr_search_t* info, /* in/out: search info */ + btr_cur_t* cursor) /* in: cursor which was just positioned */ +{ + buf_block_t* block; + ibool build_index; + ulint* params; + ulint* params2; + +#ifdef UNIV_SYNC_DEBUG + ut_ad(!rw_lock_own(&btr_search_latch, RW_LOCK_SHARED)); + ut_ad(!rw_lock_own(&btr_search_latch, RW_LOCK_EX)); +#endif /* UNIV_SYNC_DEBUG */ + + block = btr_cur_get_block(cursor); + + /* NOTE that the following two function calls do NOT protect + info or block->n_fields etc. with any semaphore, to save CPU time! + We cannot assume the fields are consistent when we return from + those functions! */ + + btr_search_info_update_hash(info, cursor); + + build_index = btr_search_update_block_hash_info(info, block, cursor); + + if (build_index || (cursor->flag == BTR_CUR_HASH_FAIL)) { + + btr_search_check_free_space_in_heap(); + } + + if (cursor->flag == BTR_CUR_HASH_FAIL) { + /* Update the hash node reference, if appropriate */ + +#ifdef UNIV_SEARCH_PERF_STAT + btr_search_n_hash_fail++; +#endif /* UNIV_SEARCH_PERF_STAT */ + + rw_lock_x_lock(&btr_search_latch); + + btr_search_update_hash_ref(info, block, cursor); + + rw_lock_x_unlock(&btr_search_latch); + } + + if (build_index) { + /* Note that since we did not protect block->n_fields etc. + with any semaphore, the values can be inconsistent. We have + to check inside the function call that they make sense. We + also malloc an array and store the values there to make sure + the compiler does not let the function call parameters change + inside the called function. It might be that the compiler + would optimize the call just to pass pointers to block. */ + + params = mem_alloc(3 * sizeof(ulint)); + params[0] = block->n_fields; + params[1] = block->n_bytes; + params[2] = block->left_side; + + /* Make sure the compiler cannot deduce the values and do + optimizations */ + + params2 = params + btr_search_this_is_zero; + + btr_search_build_page_hash_index(cursor->index, + block, + params2[0], + params2[1], + params2[2]); + mem_free(params); + } +} + +/********************************************************************** +Checks if a guessed position for a tree cursor is right. Note that if +mode is PAGE_CUR_LE, which is used in inserts, and the function returns +TRUE, then cursor->up_match and cursor->low_match both have sensible values. */ +static +ibool +btr_search_check_guess( +/*===================*/ + /* out: TRUE if success */ + btr_cur_t* cursor, /* in: guessed cursor position */ + ibool can_only_compare_to_cursor_rec, + /* in: if we do not have a latch on the page + of cursor, but only a latch on + btr_search_latch, then ONLY the columns + of the record UNDER the cursor are + protected, not the next or previous record + in the chain: we cannot look at the next or + previous record to check our guess! */ + const dtuple_t* tuple, /* in: data tuple */ + ulint mode, /* in: PAGE_CUR_L, PAGE_CUR_LE, PAGE_CUR_G, + or PAGE_CUR_GE */ + mtr_t* mtr) /* in: mtr */ +{ + rec_t* rec; + ulint n_unique; + ulint match; + ulint bytes; + int cmp; + mem_heap_t* heap = NULL; + ulint offsets_[REC_OFFS_NORMAL_SIZE]; + ulint* offsets = offsets_; + ibool success = FALSE; + rec_offs_init(offsets_); + + n_unique = dict_index_get_n_unique_in_tree(cursor->index); + + rec = btr_cur_get_rec(cursor); + + ut_ad(page_rec_is_user_rec(rec)); + + match = 0; + bytes = 0; + + offsets = rec_get_offsets(rec, cursor->index, offsets, + n_unique, &heap); + cmp = page_cmp_dtuple_rec_with_match(tuple, rec, + offsets, &match, &bytes); + + if (mode == PAGE_CUR_GE) { + if (cmp == 1) { + goto exit_func; + } + + cursor->up_match = match; + + if (match >= n_unique) { + success = TRUE; + goto exit_func; + } + } else if (mode == PAGE_CUR_LE) { + if (cmp == -1) { + goto exit_func; + } + + cursor->low_match = match; + + } else if (mode == PAGE_CUR_G) { + if (cmp != -1) { + goto exit_func; + } + } else if (mode == PAGE_CUR_L) { + if (cmp != 1) { + goto exit_func; + } + } + + if (can_only_compare_to_cursor_rec) { + /* Since we could not determine if our guess is right just by + looking at the record under the cursor, return FALSE */ + goto exit_func; + } + + match = 0; + bytes = 0; + + if ((mode == PAGE_CUR_G) || (mode == PAGE_CUR_GE)) { + rec_t* prev_rec; + + ut_ad(!page_rec_is_infimum(rec)); + + prev_rec = page_rec_get_prev(rec); + + if (page_rec_is_infimum(prev_rec)) { + success = btr_page_get_prev(page_align(prev_rec), mtr) + == FIL_NULL; + + goto exit_func; + } + + offsets = rec_get_offsets(prev_rec, cursor->index, offsets, + n_unique, &heap); + cmp = page_cmp_dtuple_rec_with_match(tuple, prev_rec, + offsets, &match, &bytes); + if (mode == PAGE_CUR_GE) { + success = cmp == 1; + } else { + success = cmp != -1; + } + + goto exit_func; + } else { + rec_t* next_rec; + + ut_ad(!page_rec_is_supremum(rec)); + + next_rec = page_rec_get_next(rec); + + if (page_rec_is_supremum(next_rec)) { + if (btr_page_get_next(page_align(next_rec), mtr) + == FIL_NULL) { + + cursor->up_match = 0; + success = TRUE; + } + + goto exit_func; + } + + offsets = rec_get_offsets(next_rec, cursor->index, offsets, + n_unique, &heap); + cmp = page_cmp_dtuple_rec_with_match(tuple, next_rec, + offsets, &match, &bytes); + if (mode == PAGE_CUR_LE) { + success = cmp == -1; + cursor->up_match = match; + } else { + success = cmp != 1; + } + } +exit_func: + if (UNIV_LIKELY_NULL(heap)) { + mem_heap_free(heap); + } + return(success); +} + +/********************************************************************** +Tries to guess the right search position based on the hash search info +of the index. Note that if mode is PAGE_CUR_LE, which is used in inserts, +and the function returns TRUE, then cursor->up_match and cursor->low_match +both have sensible values. */ +UNIV_INTERN +ibool +btr_search_guess_on_hash( +/*=====================*/ + /* out: TRUE if succeeded */ + dict_index_t* index, /* in: index */ + btr_search_t* info, /* in: index search info */ + const dtuple_t* tuple, /* in: logical record */ + ulint mode, /* in: PAGE_CUR_L, ... */ + ulint latch_mode, /* in: BTR_SEARCH_LEAF, ...; + NOTE that only if has_search_latch + is 0, we will have a latch set on + the cursor page, otherwise we assume + the caller uses his search latch + to protect the record! */ + btr_cur_t* cursor, /* out: tree cursor */ + ulint has_search_latch,/* in: latch mode the caller + currently has on btr_search_latch: + RW_S_LATCH, RW_X_LATCH, or 0 */ + mtr_t* mtr) /* in: mtr */ +{ + buf_block_t* block; + rec_t* rec; + ulint fold; + dulint index_id; +#ifdef notdefined + btr_cur_t cursor2; + btr_pcur_t pcur; +#endif + ut_ad(index && info && tuple && cursor && mtr); + ut_ad((latch_mode == BTR_SEARCH_LEAF) + || (latch_mode == BTR_MODIFY_LEAF)); + + /* Note that, for efficiency, the struct info may not be protected by + any latch here! */ + + if (UNIV_UNLIKELY(info->n_hash_potential == 0)) { + + return(FALSE); + } + + cursor->n_fields = info->n_fields; + cursor->n_bytes = info->n_bytes; + + if (UNIV_UNLIKELY(dtuple_get_n_fields(tuple) + < cursor->n_fields + (cursor->n_bytes > 0))) { + + return(FALSE); + } + + index_id = index->id; + +#ifdef UNIV_SEARCH_PERF_STAT + info->n_hash_succ++; +#endif + fold = dtuple_fold(tuple, cursor->n_fields, cursor->n_bytes, index_id); + + cursor->fold = fold; + cursor->flag = BTR_CUR_HASH; + + if (UNIV_LIKELY(!has_search_latch)) { + rw_lock_s_lock(&btr_search_latch); + + if (UNIV_UNLIKELY(!btr_search_enabled)) { + goto failure_unlock; + } + } + + ut_ad(rw_lock_get_writer(&btr_search_latch) != RW_LOCK_EX); + ut_ad(rw_lock_get_reader_count(&btr_search_latch) > 0); + + rec = ha_search_and_get_data(btr_search_sys->hash_index, fold); + + if (UNIV_UNLIKELY(!rec)) { + goto failure_unlock; + } + + block = buf_block_align(rec); + + if (UNIV_LIKELY(!has_search_latch)) { + + if (UNIV_UNLIKELY( + !buf_page_get_known_nowait(latch_mode, block, + BUF_MAKE_YOUNG, + __FILE__, __LINE__, + mtr))) { + goto failure_unlock; + } + + rw_lock_s_unlock(&btr_search_latch); + + buf_block_dbg_add_level(block, SYNC_TREE_NODE_FROM_HASH); + } + + if (UNIV_UNLIKELY(buf_block_get_state(block) != BUF_BLOCK_FILE_PAGE)) { + ut_ad(buf_block_get_state(block) == BUF_BLOCK_REMOVE_HASH); + + if (UNIV_LIKELY(!has_search_latch)) { + + btr_leaf_page_release(block, latch_mode, mtr); + } + + goto failure; + } + + ut_ad(page_rec_is_user_rec(rec)); + + btr_cur_position(index, rec, block, cursor); + + /* Check the validity of the guess within the page */ + + /* If we only have the latch on btr_search_latch, not on the + page, it only protects the columns of the record the cursor + is positioned on. We cannot look at the next of the previous + record to determine if our guess for the cursor position is + right. */ + if (UNIV_EXPECT + (ut_dulint_cmp(index_id, btr_page_get_index_id(block->frame)), 0) + || !btr_search_check_guess(cursor, + has_search_latch, + tuple, mode, mtr)) { + if (UNIV_LIKELY(!has_search_latch)) { + btr_leaf_page_release(block, latch_mode, mtr); + } + + goto failure; + } + + if (UNIV_LIKELY(info->n_hash_potential < BTR_SEARCH_BUILD_LIMIT + 5)) { + + info->n_hash_potential++; + } + +#ifdef notdefined + /* These lines of code can be used in a debug version to check + the correctness of the searched cursor position: */ + + info->last_hash_succ = FALSE; + + /* Currently, does not work if the following fails: */ + ut_ad(!has_search_latch); + + btr_leaf_page_release(block, latch_mode, mtr); + + btr_cur_search_to_nth_level(index, 0, tuple, mode, latch_mode, + &cursor2, 0, mtr); + if (mode == PAGE_CUR_GE + && page_rec_is_supremum(btr_cur_get_rec(&cursor2))) { + + /* If mode is PAGE_CUR_GE, then the binary search + in the index tree may actually take us to the supremum + of the previous page */ + + info->last_hash_succ = FALSE; + + btr_pcur_open_on_user_rec(index, tuple, mode, latch_mode, + &pcur, mtr); + ut_ad(btr_pcur_get_rec(&pcur) == btr_cur_get_rec(cursor)); + } else { + ut_ad(btr_cur_get_rec(&cursor2) == btr_cur_get_rec(cursor)); + } + + /* NOTE that it is theoretically possible that the above assertions + fail if the page of the cursor gets removed from the buffer pool + meanwhile! Thus it might not be a bug. */ +#endif + info->last_hash_succ = TRUE; + +#ifdef UNIV_SEARCH_PERF_STAT + btr_search_n_succ++; +#endif + if (UNIV_LIKELY(!has_search_latch) + && buf_page_peek_if_too_old(&block->page)) { + + buf_page_make_young(&block->page); + } + + /* Increment the page get statistics though we did not really + fix the page: for user info only */ + + buf_pool->n_page_gets++; + + return(TRUE); + + /*-------------------------------------------*/ +failure_unlock: + if (UNIV_LIKELY(!has_search_latch)) { + rw_lock_s_unlock(&btr_search_latch); + } +failure: + cursor->flag = BTR_CUR_HASH_FAIL; + +#ifdef UNIV_SEARCH_PERF_STAT + info->n_hash_fail++; + + if (info->n_hash_succ > 0) { + info->n_hash_succ--; + } +#endif + info->last_hash_succ = FALSE; + + return(FALSE); +} + +/************************************************************************ +Drops a page hash index. */ +UNIV_INTERN +void +btr_search_drop_page_hash_index( +/*============================*/ + buf_block_t* block) /* in: block containing index page, + s- or x-latched, or an index page + for which we know that + block->buf_fix_count == 0 */ +{ + hash_table_t* table; + ulint n_fields; + ulint n_bytes; + const page_t* page; + const rec_t* rec; + ulint fold; + ulint prev_fold; + dulint index_id; + ulint n_cached; + ulint n_recs; + ulint* folds; + ulint i; + mem_heap_t* heap; + const dict_index_t* index; + ulint* offsets; + +#ifdef UNIV_SYNC_DEBUG + ut_ad(!rw_lock_own(&btr_search_latch, RW_LOCK_SHARED)); + ut_ad(!rw_lock_own(&btr_search_latch, RW_LOCK_EX)); +#endif /* UNIV_SYNC_DEBUG */ + +retry: + rw_lock_s_lock(&btr_search_latch); + page = block->frame; + + if (UNIV_LIKELY(!block->is_hashed)) { + + rw_lock_s_unlock(&btr_search_latch); + + return; + } + + table = btr_search_sys->hash_index; + +#ifdef UNIV_SYNC_DEBUG + ut_ad(rw_lock_own(&(block->lock), RW_LOCK_SHARED) + || rw_lock_own(&(block->lock), RW_LOCK_EX) + || (block->page.buf_fix_count == 0)); +#endif /* UNIV_SYNC_DEBUG */ + + n_fields = block->curr_n_fields; + n_bytes = block->curr_n_bytes; + index = block->index; + ut_a(!dict_index_is_ibuf(index)); + + /* NOTE: The fields of block must not be accessed after + releasing btr_search_latch, as the index page might only + be s-latched! */ + + rw_lock_s_unlock(&btr_search_latch); + + ut_a(n_fields + n_bytes > 0); + + n_recs = page_get_n_recs(page); + + /* Calculate and cache fold values into an array for fast deletion + from the hash index */ + + folds = mem_alloc(n_recs * sizeof(ulint)); + + n_cached = 0; + + rec = page_get_infimum_rec(page); + rec = page_rec_get_next_low(rec, page_is_comp(page)); + + index_id = btr_page_get_index_id(page); + + ut_a(0 == ut_dulint_cmp(index_id, index->id)); + + prev_fold = 0; + + heap = NULL; + offsets = NULL; + + while (!page_rec_is_supremum(rec)) { + offsets = rec_get_offsets(rec, index, offsets, + n_fields + (n_bytes > 0), &heap); + ut_a(rec_offs_n_fields(offsets) == n_fields + (n_bytes > 0)); + fold = rec_fold(rec, offsets, n_fields, n_bytes, index_id); + + if (fold == prev_fold && prev_fold != 0) { + + goto next_rec; + } + + /* Remove all hash nodes pointing to this page from the + hash chain */ + + folds[n_cached] = fold; + n_cached++; +next_rec: + rec = page_rec_get_next_low(rec, page_rec_is_comp(rec)); + prev_fold = fold; + } + + if (UNIV_LIKELY_NULL(heap)) { + mem_heap_free(heap); + } + + rw_lock_x_lock(&btr_search_latch); + + if (UNIV_UNLIKELY(!block->is_hashed)) { + /* Someone else has meanwhile dropped the hash index */ + + goto cleanup; + } + + ut_a(block->index == index); + + if (UNIV_UNLIKELY(block->curr_n_fields != n_fields) + || UNIV_UNLIKELY(block->curr_n_bytes != n_bytes)) { + + /* Someone else has meanwhile built a new hash index on the + page, with different parameters */ + + rw_lock_x_unlock(&btr_search_latch); + + mem_free(folds); + goto retry; + } + + for (i = 0; i < n_cached; i++) { + + ha_remove_all_nodes_to_page(table, folds[i], page); + } + + ut_a(index->search_info->ref_count > 0); + index->search_info->ref_count--; + + block->is_hashed = FALSE; + block->index = NULL; + +cleanup: +#if defined UNIV_AHI_DEBUG || defined UNIV_DEBUG + if (UNIV_UNLIKELY(block->n_pointers)) { + /* Corruption */ + ut_print_timestamp(stderr); + fprintf(stderr, + " InnoDB: Corruption of adaptive hash index." + " After dropping\n" + "InnoDB: the hash index to a page of %s," + " still %lu hash nodes remain.\n", + index->name, (ulong) block->n_pointers); + rw_lock_x_unlock(&btr_search_latch); + + btr_search_validate(); + } else { + rw_lock_x_unlock(&btr_search_latch); + } +#else /* UNIV_AHI_DEBUG || UNIV_DEBUG */ + rw_lock_x_unlock(&btr_search_latch); +#endif /* UNIV_AHI_DEBUG || UNIV_DEBUG */ + + mem_free(folds); +} + +/************************************************************************ +Drops a page hash index when a page is freed from a fseg to the file system. +Drops possible hash index if the page happens to be in the buffer pool. */ +UNIV_INTERN +void +btr_search_drop_page_hash_when_freed( +/*=================================*/ + ulint space, /* in: space id */ + ulint zip_size, /* in: compressed page size in bytes + or 0 for uncompressed pages */ + ulint page_no) /* in: page number */ +{ + buf_block_t* block; + mtr_t mtr; + + if (!buf_page_peek_if_search_hashed(space, page_no)) { + + return; + } + + mtr_start(&mtr); + + /* We assume that if the caller has a latch on the page, then the + caller has already dropped the hash index for the page, and we never + get here. Therefore we can acquire the s-latch to the page without + having to fear a deadlock. */ + + block = buf_page_get_gen(space, zip_size, page_no, RW_S_LATCH, NULL, + BUF_GET_IF_IN_POOL, __FILE__, __LINE__, + &mtr); + /* Because the buffer pool mutex was released by + buf_page_peek_if_search_hashed(), it is possible that the + block was removed from the buffer pool by another thread + before buf_page_get_gen() got a chance to acquire the buffer + pool mutex again. Thus, we must check for a NULL return. */ + + if (UNIV_LIKELY(block != NULL)) { + + buf_block_dbg_add_level(block, SYNC_TREE_NODE_FROM_HASH); + + btr_search_drop_page_hash_index(block); + } + + mtr_commit(&mtr); +} + +/************************************************************************ +Builds a hash index on a page with the given parameters. If the page already +has a hash index with different parameters, the old hash index is removed. +If index is non-NULL, this function checks if n_fields and n_bytes are +sensible values, and does not build a hash index if not. */ +static +void +btr_search_build_page_hash_index( +/*=============================*/ + dict_index_t* index, /* in: index for which to build */ + buf_block_t* block, /* in: index page, s- or x-latched */ + ulint n_fields,/* in: hash this many full fields */ + ulint n_bytes,/* in: hash this many bytes from the next + field */ + ibool left_side)/* in: hash for searches from left side? */ +{ + hash_table_t* table; + page_t* page; + rec_t* rec; + rec_t* next_rec; + ulint fold; + ulint next_fold; + dulint index_id; + ulint n_cached; + ulint n_recs; + ulint* folds; + rec_t** recs; + ulint i; + mem_heap_t* heap = NULL; + ulint offsets_[REC_OFFS_NORMAL_SIZE]; + ulint* offsets = offsets_; + rec_offs_init(offsets_); + + ut_ad(index); + ut_a(!dict_index_is_ibuf(index)); + + table = btr_search_sys->hash_index; + page = buf_block_get_frame(block); + +#ifdef UNIV_SYNC_DEBUG + ut_ad(!rw_lock_own(&btr_search_latch, RW_LOCK_EX)); + ut_ad(rw_lock_own(&(block->lock), RW_LOCK_SHARED) + || rw_lock_own(&(block->lock), RW_LOCK_EX)); +#endif /* UNIV_SYNC_DEBUG */ + + rw_lock_s_lock(&btr_search_latch); + + if (block->is_hashed && ((block->curr_n_fields != n_fields) + || (block->curr_n_bytes != n_bytes) + || (block->curr_left_side != left_side))) { + + rw_lock_s_unlock(&btr_search_latch); + + btr_search_drop_page_hash_index(block); + } else { + rw_lock_s_unlock(&btr_search_latch); + } + + n_recs = page_get_n_recs(page); + + if (n_recs == 0) { + + return; + } + + /* Check that the values for hash index build are sensible */ + + if (n_fields + n_bytes == 0) { + + return; + } + + if (dict_index_get_n_unique_in_tree(index) < n_fields + || (dict_index_get_n_unique_in_tree(index) == n_fields + && n_bytes > 0)) { + return; + } + + /* Calculate and cache fold values and corresponding records into + an array for fast insertion to the hash index */ + + folds = mem_alloc(n_recs * sizeof(ulint)); + recs = mem_alloc(n_recs * sizeof(rec_t*)); + + n_cached = 0; + + index_id = btr_page_get_index_id(page); + + rec = page_rec_get_next(page_get_infimum_rec(page)); + + offsets = rec_get_offsets(rec, index, offsets, + n_fields + (n_bytes > 0), &heap); + + if (!page_rec_is_supremum(rec)) { + ut_a(n_fields <= rec_offs_n_fields(offsets)); + + if (n_bytes > 0) { + ut_a(n_fields < rec_offs_n_fields(offsets)); + } + } + + fold = rec_fold(rec, offsets, n_fields, n_bytes, index_id); + + if (left_side) { + + folds[n_cached] = fold; + recs[n_cached] = rec; + n_cached++; + } + + for (;;) { + next_rec = page_rec_get_next(rec); + + if (page_rec_is_supremum(next_rec)) { + + if (!left_side) { + + folds[n_cached] = fold; + recs[n_cached] = rec; + n_cached++; + } + + break; + } + + offsets = rec_get_offsets(next_rec, index, offsets, + n_fields + (n_bytes > 0), &heap); + next_fold = rec_fold(next_rec, offsets, n_fields, + n_bytes, index_id); + + if (fold != next_fold) { + /* Insert an entry into the hash index */ + + if (left_side) { + + folds[n_cached] = next_fold; + recs[n_cached] = next_rec; + n_cached++; + } else { + folds[n_cached] = fold; + recs[n_cached] = rec; + n_cached++; + } + } + + rec = next_rec; + fold = next_fold; + } + + btr_search_check_free_space_in_heap(); + + rw_lock_x_lock(&btr_search_latch); + + if (UNIV_UNLIKELY(!btr_search_enabled)) { + goto exit_func; + } + + if (block->is_hashed && ((block->curr_n_fields != n_fields) + || (block->curr_n_bytes != n_bytes) + || (block->curr_left_side != left_side))) { + goto exit_func; + } + + /* This counter is decremented every time we drop page + hash index entries and is incremented here. Since we can + rebuild hash index for a page that is already hashed, we + have to take care not to increment the counter in that + case. */ + if (!block->is_hashed) { + index->search_info->ref_count++; + } + + block->is_hashed = TRUE; + block->n_hash_helps = 0; + + block->curr_n_fields = n_fields; + block->curr_n_bytes = n_bytes; + block->curr_left_side = left_side; + block->index = index; + + for (i = 0; i < n_cached; i++) { + + ha_insert_for_fold(table, folds[i], block, recs[i]); + } + +exit_func: + rw_lock_x_unlock(&btr_search_latch); + + mem_free(folds); + mem_free(recs); + if (UNIV_LIKELY_NULL(heap)) { + mem_heap_free(heap); + } +} + +/************************************************************************ +Moves or deletes hash entries for moved records. If new_page is already hashed, +then the hash index for page, if any, is dropped. If new_page is not hashed, +and page is hashed, then a new hash index is built to new_page with the same +parameters as page (this often happens when a page is split). */ +UNIV_INTERN +void +btr_search_move_or_delete_hash_entries( +/*===================================*/ + buf_block_t* new_block, /* in: records are copied + to this page */ + buf_block_t* block, /* in: index page from which + records were copied, and the + copied records will be deleted + from this page */ + dict_index_t* index) /* in: record descriptor */ +{ + ulint n_fields; + ulint n_bytes; + ibool left_side; + +#ifdef UNIV_SYNC_DEBUG + ut_ad(rw_lock_own(&(block->lock), RW_LOCK_EX)); + ut_ad(rw_lock_own(&(new_block->lock), RW_LOCK_EX)); +#endif /* UNIV_SYNC_DEBUG */ + ut_a(!new_block->is_hashed || new_block->index == index); + ut_a(!block->is_hashed || block->index == index); + ut_a(!(new_block->is_hashed || block->is_hashed) + || !dict_index_is_ibuf(index)); + + rw_lock_s_lock(&btr_search_latch); + + if (new_block->is_hashed) { + + rw_lock_s_unlock(&btr_search_latch); + + btr_search_drop_page_hash_index(block); + + return; + } + + if (block->is_hashed) { + + n_fields = block->curr_n_fields; + n_bytes = block->curr_n_bytes; + left_side = block->curr_left_side; + + new_block->n_fields = block->curr_n_fields; + new_block->n_bytes = block->curr_n_bytes; + new_block->left_side = left_side; + + rw_lock_s_unlock(&btr_search_latch); + + ut_a(n_fields + n_bytes > 0); + + btr_search_build_page_hash_index(index, new_block, n_fields, + n_bytes, left_side); + ut_ad(n_fields == block->curr_n_fields); + ut_ad(n_bytes == block->curr_n_bytes); + ut_ad(left_side == block->curr_left_side); + return; + } + + rw_lock_s_unlock(&btr_search_latch); +} + +/************************************************************************ +Updates the page hash index when a single record is deleted from a page. */ +UNIV_INTERN +void +btr_search_update_hash_on_delete( +/*=============================*/ + btr_cur_t* cursor) /* in: cursor which was positioned on the + record to delete using btr_cur_search_..., + the record is not yet deleted */ +{ + hash_table_t* table; + buf_block_t* block; + rec_t* rec; + ulint fold; + dulint index_id; + ibool found; + ulint offsets_[REC_OFFS_NORMAL_SIZE]; + mem_heap_t* heap = NULL; + rec_offs_init(offsets_); + + rec = btr_cur_get_rec(cursor); + + block = btr_cur_get_block(cursor); + +#ifdef UNIV_SYNC_DEBUG + ut_ad(rw_lock_own(&(block->lock), RW_LOCK_EX)); +#endif /* UNIV_SYNC_DEBUG */ + + if (!block->is_hashed) { + + return; + } + + ut_a(block->index == cursor->index); + ut_a(block->curr_n_fields + block->curr_n_bytes > 0); + ut_a(!dict_index_is_ibuf(cursor->index)); + + table = btr_search_sys->hash_index; + + index_id = cursor->index->id; + fold = rec_fold(rec, rec_get_offsets(rec, cursor->index, offsets_, + ULINT_UNDEFINED, &heap), + block->curr_n_fields, block->curr_n_bytes, index_id); + if (UNIV_LIKELY_NULL(heap)) { + mem_heap_free(heap); + } + rw_lock_x_lock(&btr_search_latch); + + found = ha_search_and_delete_if_found(table, fold, rec); + + rw_lock_x_unlock(&btr_search_latch); +} + +/************************************************************************ +Updates the page hash index when a single record is inserted on a page. */ +UNIV_INTERN +void +btr_search_update_hash_node_on_insert( +/*==================================*/ + btr_cur_t* cursor) /* in: cursor which was positioned to the + place to insert using btr_cur_search_..., + and the new record has been inserted next + to the cursor */ +{ + hash_table_t* table; + buf_block_t* block; + rec_t* rec; + + rec = btr_cur_get_rec(cursor); + + block = btr_cur_get_block(cursor); + +#ifdef UNIV_SYNC_DEBUG + ut_ad(rw_lock_own(&(block->lock), RW_LOCK_EX)); +#endif /* UNIV_SYNC_DEBUG */ + + if (!block->is_hashed) { + + return; + } + + ut_a(block->index == cursor->index); + ut_a(!dict_index_is_ibuf(cursor->index)); + + rw_lock_x_lock(&btr_search_latch); + + if ((cursor->flag == BTR_CUR_HASH) + && (cursor->n_fields == block->curr_n_fields) + && (cursor->n_bytes == block->curr_n_bytes) + && !block->curr_left_side) { + + table = btr_search_sys->hash_index; + + ha_search_and_update_if_found(table, cursor->fold, rec, + block, page_rec_get_next(rec)); + + rw_lock_x_unlock(&btr_search_latch); + } else { + rw_lock_x_unlock(&btr_search_latch); + + btr_search_update_hash_on_insert(cursor); + } +} + +/************************************************************************ +Updates the page hash index when a single record is inserted on a page. */ +UNIV_INTERN +void +btr_search_update_hash_on_insert( +/*=============================*/ + btr_cur_t* cursor) /* in: cursor which was positioned to the + place to insert using btr_cur_search_..., + and the new record has been inserted next + to the cursor */ +{ + hash_table_t* table; + buf_block_t* block; + rec_t* rec; + rec_t* ins_rec; + rec_t* next_rec; + dulint index_id; + ulint fold; + ulint ins_fold; + ulint next_fold = 0; /* remove warning (??? bug ???) */ + ulint n_fields; + ulint n_bytes; + ibool left_side; + ibool locked = FALSE; + mem_heap_t* heap = NULL; + ulint offsets_[REC_OFFS_NORMAL_SIZE]; + ulint* offsets = offsets_; + rec_offs_init(offsets_); + + table = btr_search_sys->hash_index; + + btr_search_check_free_space_in_heap(); + + rec = btr_cur_get_rec(cursor); + + block = btr_cur_get_block(cursor); + +#ifdef UNIV_SYNC_DEBUG + ut_ad(rw_lock_own(&(block->lock), RW_LOCK_EX)); +#endif /* UNIV_SYNC_DEBUG */ + + if (!block->is_hashed) { + + return; + } + + ut_a(block->index == cursor->index); + ut_a(!dict_index_is_ibuf(cursor->index)); + + index_id = cursor->index->id; + + n_fields = block->curr_n_fields; + n_bytes = block->curr_n_bytes; + left_side = block->curr_left_side; + + ins_rec = page_rec_get_next(rec); + next_rec = page_rec_get_next(ins_rec); + + offsets = rec_get_offsets(ins_rec, cursor->index, offsets, + ULINT_UNDEFINED, &heap); + ins_fold = rec_fold(ins_rec, offsets, n_fields, n_bytes, index_id); + + if (!page_rec_is_supremum(next_rec)) { + offsets = rec_get_offsets(next_rec, cursor->index, offsets, + n_fields + (n_bytes > 0), &heap); + next_fold = rec_fold(next_rec, offsets, n_fields, + n_bytes, index_id); + } + + if (!page_rec_is_infimum(rec)) { + offsets = rec_get_offsets(rec, cursor->index, offsets, + n_fields + (n_bytes > 0), &heap); + fold = rec_fold(rec, offsets, n_fields, n_bytes, index_id); + } else { + if (left_side) { + + rw_lock_x_lock(&btr_search_latch); + + locked = TRUE; + + ha_insert_for_fold(table, ins_fold, block, ins_rec); + } + + goto check_next_rec; + } + + if (fold != ins_fold) { + + if (!locked) { + + rw_lock_x_lock(&btr_search_latch); + + locked = TRUE; + } + + if (!left_side) { + ha_insert_for_fold(table, fold, block, rec); + } else { + ha_insert_for_fold(table, ins_fold, block, ins_rec); + } + } + +check_next_rec: + if (page_rec_is_supremum(next_rec)) { + + if (!left_side) { + + if (!locked) { + rw_lock_x_lock(&btr_search_latch); + + locked = TRUE; + } + + ha_insert_for_fold(table, ins_fold, block, ins_rec); + } + + goto function_exit; + } + + if (ins_fold != next_fold) { + + if (!locked) { + + rw_lock_x_lock(&btr_search_latch); + + locked = TRUE; + } + + if (!left_side) { + + ha_insert_for_fold(table, ins_fold, block, ins_rec); + /* + fputs("Hash insert for ", stderr); + dict_index_name_print(stderr, cursor->index); + fprintf(stderr, " fold %lu\n", ins_fold); + */ + } else { + ha_insert_for_fold(table, next_fold, block, next_rec); + } + } + +function_exit: + if (UNIV_LIKELY_NULL(heap)) { + mem_heap_free(heap); + } + if (locked) { + rw_lock_x_unlock(&btr_search_latch); + } +} + +/************************************************************************ +Validates the search system. */ +UNIV_INTERN +ibool +btr_search_validate(void) +/*=====================*/ + /* out: TRUE if ok */ +{ + ha_node_t* node; + ulint n_page_dumps = 0; + ibool ok = TRUE; + ulint i; + ulint cell_count; + mem_heap_t* heap = NULL; + ulint offsets_[REC_OFFS_NORMAL_SIZE]; + ulint* offsets = offsets_; + + /* How many cells to check before temporarily releasing + btr_search_latch. */ + ulint chunk_size = 10000; + + rec_offs_init(offsets_); + + rw_lock_x_lock(&btr_search_latch); + //buf_pool_mutex_enter(); + rw_lock_x_lock(&page_hash_latch); + + cell_count = hash_get_n_cells(btr_search_sys->hash_index); + + for (i = 0; i < cell_count; i++) { + /* We release btr_search_latch every once in a while to + give other queries a chance to run. */ + if ((i != 0) && ((i % chunk_size) == 0)) { + //buf_pool_mutex_exit(); + rw_lock_x_unlock(&page_hash_latch); + rw_lock_x_unlock(&btr_search_latch); + os_thread_yield(); + rw_lock_x_lock(&btr_search_latch); + //buf_pool_mutex_enter(); + rw_lock_x_lock(&page_hash_latch); + } + + node = hash_get_nth_cell(btr_search_sys->hash_index, i)->node; + + for (; node != NULL; node = node->next) { + const buf_block_t* block + = buf_block_align(node->data); + const buf_block_t* hash_block; + + if (UNIV_LIKELY(buf_block_get_state(block) + == BUF_BLOCK_FILE_PAGE)) { + + /* The space and offset are only valid + for file blocks. It is possible that + the block is being freed + (BUF_BLOCK_REMOVE_HASH, see the + assertion and the comment below) */ + hash_block = buf_block_hash_get( + buf_block_get_space(block), + buf_block_get_page_no(block)); + } else { + hash_block = NULL; + } + + if (hash_block) { + ut_a(hash_block == block); + } else { + /* When a block is being freed, + buf_LRU_search_and_free_block() first + removes the block from + buf_pool->page_hash by calling + buf_LRU_block_remove_hashed_page(). + After that, it invokes + btr_search_drop_page_hash_index() to + remove the block from + btr_search_sys->hash_index. */ + + ut_a(buf_block_get_state(block) + == BUF_BLOCK_REMOVE_HASH); + } + + ut_a(!dict_index_is_ibuf(block->index)); + + offsets = rec_get_offsets((const rec_t*) node->data, + block->index, offsets, + block->curr_n_fields + + (block->curr_n_bytes > 0), + &heap); + + if (!block->is_hashed || node->fold + != rec_fold((rec_t*)(node->data), + offsets, + block->curr_n_fields, + block->curr_n_bytes, + btr_page_get_index_id(block->frame))) { + const page_t* page = block->frame; + + ok = FALSE; + ut_print_timestamp(stderr); + + fprintf(stderr, + " InnoDB: Error in an adaptive hash" + " index pointer to page %lu\n" + "InnoDB: ptr mem address %p" + " index id %lu %lu," + " node fold %lu, rec fold %lu\n", + (ulong) page_get_page_no(page), + node->data, + (ulong) ut_dulint_get_high( + btr_page_get_index_id(page)), + (ulong) ut_dulint_get_low( + btr_page_get_index_id(page)), + (ulong) node->fold, + (ulong) rec_fold((rec_t*)(node->data), + offsets, + block->curr_n_fields, + block->curr_n_bytes, + btr_page_get_index_id( + page))); + + fputs("InnoDB: Record ", stderr); + rec_print_new(stderr, (rec_t*)node->data, + offsets); + fprintf(stderr, "\nInnoDB: on that page." + " Page mem address %p, is hashed %lu," + " n fields %lu, n bytes %lu\n" + "InnoDB: side %lu\n", + (void*) page, (ulong) block->is_hashed, + (ulong) block->curr_n_fields, + (ulong) block->curr_n_bytes, + (ulong) block->curr_left_side); + + if (n_page_dumps < 20) { + buf_page_print(page, 0); + n_page_dumps++; + } + } + } + } + + for (i = 0; i < cell_count; i += chunk_size) { + ulint end_index = ut_min(i + chunk_size - 1, cell_count - 1); + + /* We release btr_search_latch every once in a while to + give other queries a chance to run. */ + if (i != 0) { + //buf_pool_mutex_exit(); + rw_lock_x_unlock(&page_hash_latch); + rw_lock_x_unlock(&btr_search_latch); + os_thread_yield(); + rw_lock_x_lock(&btr_search_latch); + //buf_pool_mutex_enter(); + rw_lock_x_lock(&page_hash_latch); + } + + if (!ha_validate(btr_search_sys->hash_index, i, end_index)) { + ok = FALSE; + } + } + + //buf_pool_mutex_exit(); + rw_lock_x_unlock(&page_hash_latch); + rw_lock_x_unlock(&btr_search_latch); + if (UNIV_LIKELY_NULL(heap)) { + mem_heap_free(heap); + } + + return(ok); +} diff --git a/storage/xtradb/buf/buf0buddy.c b/storage/xtradb/buf/buf0buddy.c new file mode 100644 index 00000000000..893e97e5a57 --- /dev/null +++ b/storage/xtradb/buf/buf0buddy.c @@ -0,0 +1,785 @@ +/***************************************************************************** + +Copyright (c) 2006, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/****************************************************** +Binary buddy allocator for compressed pages + +Created December 2006 by Marko Makela +*******************************************************/ + +#define THIS_MODULE +#include "buf0buddy.h" +#ifdef UNIV_NONINL +# include "buf0buddy.ic" +#endif +#undef THIS_MODULE +#include "buf0buf.h" +#include "buf0lru.h" +#include "buf0flu.h" +#include "page0zip.h" + +/* Statistic counters */ + +#ifdef UNIV_DEBUG +/** Number of frames allocated from the buffer pool to the buddy system. +Protected by buf_pool_mutex. */ +static ulint buf_buddy_n_frames; +#endif /* UNIV_DEBUG */ +/** Statistics of the buddy system, indexed by block size. +Protected by buf_pool_mutex. */ +UNIV_INTERN buf_buddy_stat_t buf_buddy_stat[BUF_BUDDY_SIZES + 1]; + +/************************************************************************** +Get the offset of the buddy of a compressed page frame. */ +UNIV_INLINE +byte* +buf_buddy_get( +/*==========*/ + /* out: the buddy relative of page */ + byte* page, /* in: compressed page */ + ulint size) /* in: page size in bytes */ +{ + ut_ad(ut_is_2pow(size)); + ut_ad(size >= BUF_BUDDY_LOW); + ut_ad(size < BUF_BUDDY_HIGH); + ut_ad(!ut_align_offset(page, size)); + + if (((ulint) page) & size) { + return(page - size); + } else { + return(page + size); + } +} + +/************************************************************************** +Add a block to the head of the appropriate buddy free list. */ +UNIV_INLINE +void +buf_buddy_add_to_free( +/*==================*/ + buf_page_t* bpage, /* in,own: block to be freed */ + ulint i) /* in: index of buf_pool->zip_free[] */ +{ +#ifdef UNIV_DEBUG_VALGRIND + buf_page_t* b = UT_LIST_GET_FIRST(buf_pool->zip_free[i]); + + if (b) UNIV_MEM_VALID(b, BUF_BUDDY_LOW << i); +#endif /* UNIV_DEBUG_VALGRIND */ + + ut_ad(buf_pool->zip_free[i].start != bpage); + UT_LIST_ADD_FIRST(zip_list, buf_pool->zip_free[i], bpage); + +#ifdef UNIV_DEBUG_VALGRIND + if (b) UNIV_MEM_FREE(b, BUF_BUDDY_LOW << i); + UNIV_MEM_ASSERT_AND_FREE(bpage, BUF_BUDDY_LOW << i); +#endif /* UNIV_DEBUG_VALGRIND */ +} + +/************************************************************************** +Remove a block from the appropriate buddy free list. */ +UNIV_INLINE +void +buf_buddy_remove_from_free( +/*=======================*/ + buf_page_t* bpage, /* in: block to be removed */ + ulint i) /* in: index of buf_pool->zip_free[] */ +{ +#ifdef UNIV_DEBUG_VALGRIND + buf_page_t* prev = UT_LIST_GET_PREV(zip_list, bpage); + buf_page_t* next = UT_LIST_GET_NEXT(zip_list, bpage); + + if (prev) UNIV_MEM_VALID(prev, BUF_BUDDY_LOW << i); + if (next) UNIV_MEM_VALID(next, BUF_BUDDY_LOW << i); + + ut_ad(!prev || buf_page_get_state(prev) == BUF_BLOCK_ZIP_FREE); + ut_ad(!next || buf_page_get_state(next) == BUF_BLOCK_ZIP_FREE); +#endif /* UNIV_DEBUG_VALGRIND */ + + ut_ad(buf_page_get_state(bpage) == BUF_BLOCK_ZIP_FREE); + UT_LIST_REMOVE(zip_list, buf_pool->zip_free[i], bpage); + +#ifdef UNIV_DEBUG_VALGRIND + if (prev) UNIV_MEM_FREE(prev, BUF_BUDDY_LOW << i); + if (next) UNIV_MEM_FREE(next, BUF_BUDDY_LOW << i); +#endif /* UNIV_DEBUG_VALGRIND */ +} + +/************************************************************************** +Try to allocate a block from buf_pool->zip_free[]. */ +static +void* +buf_buddy_alloc_zip( +/*================*/ + /* out: allocated block, or NULL + if buf_pool->zip_free[] was empty */ + ulint i) /* in: index of buf_pool->zip_free[] */ +{ + buf_page_t* bpage; + + //ut_ad(buf_pool_mutex_own()); + ut_ad(mutex_own(&zip_free_mutex)); + ut_a(i < BUF_BUDDY_SIZES); + +#if defined UNIV_DEBUG && !defined UNIV_DEBUG_VALGRIND + /* Valgrind would complain about accessing free memory. */ + UT_LIST_VALIDATE(zip_list, buf_page_t, buf_pool->zip_free[i]); +#endif /* UNIV_DEBUG && !UNIV_DEBUG_VALGRIND */ + bpage = UT_LIST_GET_LAST(buf_pool->zip_free[i]); + + if (bpage) { + UNIV_MEM_VALID(bpage, BUF_BUDDY_LOW << i); + ut_a(buf_page_get_state(bpage) == BUF_BLOCK_ZIP_FREE); + + buf_buddy_remove_from_free(bpage, i); + } else if (i + 1 < BUF_BUDDY_SIZES) { + /* Attempt to split. */ + bpage = buf_buddy_alloc_zip(i + 1); + + if (bpage) { + buf_page_t* buddy = (buf_page_t*) + (((char*) bpage) + (BUF_BUDDY_LOW << i)); + + ut_ad(!buf_pool_contains_zip(buddy)); + ut_d(memset(buddy, i, BUF_BUDDY_LOW << i)); + buddy->state = BUF_BLOCK_ZIP_FREE; + buf_buddy_add_to_free(buddy, i); + } + } + +#ifdef UNIV_DEBUG + if (bpage) { + memset(bpage, ~i, BUF_BUDDY_LOW << i); + } +#endif /* UNIV_DEBUG */ + + UNIV_MEM_ALLOC(bpage, BUF_BUDDY_SIZES << i); + + return(bpage); +} + +/************************************************************************** +Deallocate a buffer frame of UNIV_PAGE_SIZE. */ +static +void +buf_buddy_block_free( +/*=================*/ + void* buf, /* in: buffer frame to deallocate */ + ibool have_page_hash_mutex) +{ + const ulint fold = BUF_POOL_ZIP_FOLD_PTR(buf); + buf_page_t* bpage; + buf_block_t* block; + + //ut_ad(buf_pool_mutex_own()); + ut_ad(!mutex_own(&buf_pool_zip_mutex)); + ut_a(!ut_align_offset(buf, UNIV_PAGE_SIZE)); + + mutex_enter(&zip_hash_mutex); + + HASH_SEARCH(hash, buf_pool->zip_hash, fold, buf_page_t*, bpage, + ut_ad(buf_page_get_state(bpage) == BUF_BLOCK_MEMORY + && bpage->in_zip_hash && !bpage->in_page_hash), + ((buf_block_t*) bpage)->frame == buf); + ut_a(bpage); + ut_a(buf_page_get_state(bpage) == BUF_BLOCK_MEMORY); + ut_ad(!bpage->in_page_hash); + ut_ad(bpage->in_zip_hash); + ut_d(bpage->in_zip_hash = FALSE); + HASH_DELETE(buf_page_t, hash, buf_pool->zip_hash, fold, bpage); + + mutex_exit(&zip_hash_mutex); + + ut_d(memset(buf, 0, UNIV_PAGE_SIZE)); + UNIV_MEM_INVALID(buf, UNIV_PAGE_SIZE); + + block = (buf_block_t*) bpage; + mutex_enter(&block->mutex); + buf_LRU_block_free_non_file_page(block, have_page_hash_mutex); + mutex_exit(&block->mutex); + + ut_ad(buf_buddy_n_frames > 0); + ut_d(buf_buddy_n_frames--); +} + +/************************************************************************** +Allocate a buffer block to the buddy allocator. */ +static +void +buf_buddy_block_register( +/*=====================*/ + buf_block_t* block) /* in: buffer frame to allocate */ +{ + const ulint fold = BUF_POOL_ZIP_FOLD(block); + //ut_ad(buf_pool_mutex_own()); + ut_ad(!mutex_own(&buf_pool_zip_mutex)); + + buf_block_set_state(block, BUF_BLOCK_MEMORY); + + ut_a(block->frame); + ut_a(!ut_align_offset(block->frame, UNIV_PAGE_SIZE)); + + ut_ad(!block->page.in_page_hash); + ut_ad(!block->page.in_zip_hash); + ut_d(block->page.in_zip_hash = TRUE); + + mutex_enter(&zip_hash_mutex); + HASH_INSERT(buf_page_t, hash, buf_pool->zip_hash, fold, &block->page); + mutex_exit(&zip_hash_mutex); + + ut_d(buf_buddy_n_frames++); +} + +/************************************************************************** +Allocate a block from a bigger object. */ +static +void* +buf_buddy_alloc_from( +/*=================*/ + /* out: allocated block */ + void* buf, /* in: a block that is free to use */ + ulint i, /* in: index of buf_pool->zip_free[] */ + ulint j) /* in: size of buf as an index + of buf_pool->zip_free[] */ +{ + ulint offs = BUF_BUDDY_LOW << j; + ut_ad(j <= BUF_BUDDY_SIZES); + ut_ad(j >= i); + ut_ad(!ut_align_offset(buf, offs)); + + /* Add the unused parts of the block to the free lists. */ + while (j > i) { + buf_page_t* bpage; + + offs >>= 1; + j--; + + bpage = (buf_page_t*) ((byte*) buf + offs); + ut_d(memset(bpage, j, BUF_BUDDY_LOW << j)); + bpage->state = BUF_BLOCK_ZIP_FREE; +#if defined UNIV_DEBUG && !defined UNIV_DEBUG_VALGRIND + /* Valgrind would complain about accessing free memory. */ + UT_LIST_VALIDATE(zip_list, buf_page_t, buf_pool->zip_free[j]); +#endif /* UNIV_DEBUG && !UNIV_DEBUG_VALGRIND */ + buf_buddy_add_to_free(bpage, j); + } + + return(buf); +} + +/************************************************************************** +Allocate a block. The thread calling this function must hold +buf_pool_mutex and must not hold buf_pool_zip_mutex or any block->mutex. +The buf_pool_mutex may only be released and reacquired if lru != NULL. */ +UNIV_INTERN +void* +buf_buddy_alloc_low( +/*================*/ + /* out: allocated block, + possibly NULL if lru==NULL */ + ulint i, /* in: index of buf_pool->zip_free[], + or BUF_BUDDY_SIZES */ + ibool* lru, /* in: pointer to a variable that will be assigned + TRUE if storage was allocated from the LRU list + and buf_pool_mutex was temporarily released, + or NULL if the LRU list should not be used */ + ibool have_page_hash_mutex) +{ + buf_block_t* block; + + //ut_ad(buf_pool_mutex_own()); + ut_ad(!mutex_own(&buf_pool_zip_mutex)); + + if (i < BUF_BUDDY_SIZES) { + /* Try to allocate from the buddy system. */ + mutex_enter(&zip_free_mutex); + block = buf_buddy_alloc_zip(i); + + if (block) { + + goto func_exit; + } + + mutex_exit(&zip_free_mutex); + } + + /* Try allocating from the buf_pool->free list. */ + block = buf_LRU_get_free_only(); + + if (block) { + + goto alloc_big; + } + + if (!lru) { + + return(NULL); + } + + /* Try replacing an uncompressed page in the buffer pool. */ + //buf_pool_mutex_exit(); + mutex_exit(&LRU_list_mutex); + if (have_page_hash_mutex) { + rw_lock_x_unlock(&page_hash_latch); + } + block = buf_LRU_get_free_block(0); + *lru = TRUE; + //buf_pool_mutex_enter(); + mutex_enter(&LRU_list_mutex); + if (have_page_hash_mutex) { + rw_lock_x_lock(&page_hash_latch); + } + +alloc_big: + buf_buddy_block_register(block); + + mutex_enter(&zip_free_mutex); + block = buf_buddy_alloc_from(block->frame, i, BUF_BUDDY_SIZES); + +func_exit: + buf_buddy_stat[i].used++; + mutex_exit(&zip_free_mutex); + + return(block); +} + +/************************************************************************** +Try to relocate the control block of a compressed page. */ +static +ibool +buf_buddy_relocate_block( +/*=====================*/ + /* out: TRUE if relocated */ + buf_page_t* bpage, /* in: block to relocate */ + buf_page_t* dpage) /* in: free block to relocate to */ +{ + buf_page_t* b; + + //ut_ad(buf_pool_mutex_own()); +#ifdef UNIV_SYNC_DEBUG + ut_ad(rw_lock_own(&page_hash_latch, RW_LOCK_EX)); +#endif + + switch (buf_page_get_state(bpage)) { + case BUF_BLOCK_ZIP_FREE: + case BUF_BLOCK_NOT_USED: + case BUF_BLOCK_READY_FOR_USE: + case BUF_BLOCK_FILE_PAGE: + case BUF_BLOCK_MEMORY: + case BUF_BLOCK_REMOVE_HASH: + /* ut_error; */ /* optimistic */ + case BUF_BLOCK_ZIP_DIRTY: + /* Cannot relocate dirty pages. */ + return(FALSE); + + case BUF_BLOCK_ZIP_PAGE: + break; + } + + mutex_enter(&buf_pool_zip_mutex); + mutex_enter(&zip_free_mutex); + + if (!buf_page_can_relocate(bpage)) { + mutex_exit(&buf_pool_zip_mutex); + mutex_exit(&zip_free_mutex); + return(FALSE); + } + + if (bpage != buf_page_hash_get(bpage->space, bpage->offset)) { + mutex_exit(&buf_pool_zip_mutex); + mutex_exit(&zip_free_mutex); + return(FALSE); + } + + buf_relocate(bpage, dpage); + ut_d(bpage->state = BUF_BLOCK_ZIP_FREE); + + /* relocate buf_pool->zip_clean */ + mutex_enter(&flush_list_mutex); + b = UT_LIST_GET_PREV(zip_list, dpage); + UT_LIST_REMOVE(zip_list, buf_pool->zip_clean, dpage); + + if (b) { + UT_LIST_INSERT_AFTER(zip_list, buf_pool->zip_clean, b, dpage); + } else { + UT_LIST_ADD_FIRST(zip_list, buf_pool->zip_clean, dpage); + } + mutex_exit(&flush_list_mutex); + + mutex_exit(&buf_pool_zip_mutex); + mutex_exit(&zip_free_mutex); + return(TRUE); +} + +/************************************************************************** +Try to relocate a block. */ +static +ibool +buf_buddy_relocate( +/*===============*/ + /* out: TRUE if relocated */ + void* src, /* in: block to relocate */ + void* dst, /* in: free block to relocate to */ + ulint i, /* in: index of buf_pool->zip_free[] */ + ibool have_page_hash_mutex) +{ + buf_page_t* bpage; + const ulint size = BUF_BUDDY_LOW << i; + ullint usec = ut_time_us(NULL); + + //ut_ad(buf_pool_mutex_own()); + ut_ad(mutex_own(&zip_free_mutex)); + ut_ad(!mutex_own(&buf_pool_zip_mutex)); + ut_ad(!ut_align_offset(src, size)); + ut_ad(!ut_align_offset(dst, size)); + UNIV_MEM_ASSERT_W(dst, size); + + /* We assume that all memory from buf_buddy_alloc() + is used for either compressed pages or buf_page_t + objects covering compressed pages. */ + + /* We look inside the allocated objects returned by + buf_buddy_alloc() and assume that anything of + PAGE_ZIP_MIN_SIZE or larger is a compressed page that contains + a valid space_id and page_no in the page header. Should the + fields be invalid, we will be unable to relocate the block. + We also assume that anything that fits sizeof(buf_page_t) + actually is a properly initialized buf_page_t object. */ + + if (size >= PAGE_ZIP_MIN_SIZE) { + if (!have_page_hash_mutex) + mutex_exit(&zip_free_mutex); + + /* This is a compressed page. */ + mutex_t* mutex; + + if (!have_page_hash_mutex) { + mutex_enter(&LRU_list_mutex); + rw_lock_x_lock(&page_hash_latch); + } + /* The src block may be split into smaller blocks, + some of which may be free. Thus, the + mach_read_from_4() calls below may attempt to read + from free memory. The memory is "owned" by the buddy + allocator (and it has been allocated from the buffer + pool), so there is nothing wrong about this. The + mach_read_from_4() calls here will only trigger bogus + Valgrind memcheck warnings in UNIV_DEBUG_VALGRIND builds. */ + bpage = buf_page_hash_get( + mach_read_from_4((const byte*) src + + FIL_PAGE_ARCH_LOG_NO_OR_SPACE_ID), + mach_read_from_4((const byte*) src + + FIL_PAGE_OFFSET)); + + if (!bpage || bpage->zip.data != src) { + /* The block has probably been freshly + allocated by buf_LRU_get_free_block() but not + added to buf_pool->page_hash yet. Obviously, + it cannot be relocated. */ + + if (!have_page_hash_mutex) { + mutex_enter(&zip_free_mutex); + mutex_exit(&LRU_list_mutex); + rw_lock_x_unlock(&page_hash_latch); + } + return(FALSE); + } + + if (page_zip_get_size(&bpage->zip) != size) { + /* The block is of different size. We would + have to relocate all blocks covered by src. + For the sake of simplicity, give up. */ + ut_ad(page_zip_get_size(&bpage->zip) < size); + + if (!have_page_hash_mutex) { + mutex_enter(&zip_free_mutex); + mutex_exit(&LRU_list_mutex); + rw_lock_x_unlock(&page_hash_latch); + } + return(FALSE); + } + + /* To keep latch order */ + if (have_page_hash_mutex) + mutex_exit(&zip_free_mutex); + + /* The block must have been allocated, but it may + contain uninitialized data. */ + UNIV_MEM_ASSERT_W(src, size); + + mutex = buf_page_get_mutex(bpage); + +retry_lock: + mutex_enter(mutex); + if (mutex != buf_page_get_mutex(bpage)) { + mutex_exit(mutex); + mutex = buf_page_get_mutex(bpage); + goto retry_lock; + } + mutex_enter(&zip_free_mutex); + + if (buf_page_can_relocate(bpage)) { + /* Relocate the compressed page. */ + ut_a(bpage->zip.data == src); + memcpy(dst, src, size); + bpage->zip.data = dst; + mutex_exit(mutex); +success: + UNIV_MEM_INVALID(src, size); + { + buf_buddy_stat_t* buddy_stat + = &buf_buddy_stat[i]; + buddy_stat->relocated++; + buddy_stat->relocated_usec + += ut_time_us(NULL) - usec; + } + + if (!have_page_hash_mutex) { + mutex_exit(&LRU_list_mutex); + rw_lock_x_unlock(&page_hash_latch); + } + return(TRUE); + } + + if (!have_page_hash_mutex) { + mutex_exit(&LRU_list_mutex); + rw_lock_x_unlock(&page_hash_latch); + } + + mutex_exit(mutex); + } else if (i == buf_buddy_get_slot(sizeof(buf_page_t))) { + /* This must be a buf_page_t object. */ + UNIV_MEM_ASSERT_RW(src, size); + + mutex_exit(&zip_free_mutex); + + if (!have_page_hash_mutex) { + mutex_enter(&LRU_list_mutex); + rw_lock_x_lock(&page_hash_latch); + } + + if (buf_buddy_relocate_block(src, dst)) { + mutex_enter(&zip_free_mutex); + + if (!have_page_hash_mutex) { + mutex_exit(&LRU_list_mutex); + rw_lock_x_unlock(&page_hash_latch); + } + + goto success; + } + + mutex_enter(&zip_free_mutex); + + if (!have_page_hash_mutex) { + mutex_exit(&LRU_list_mutex); + rw_lock_x_unlock(&page_hash_latch); + } + } + + return(FALSE); +} + +/************************************************************************** +Deallocate a block. */ +UNIV_INTERN +void +buf_buddy_free_low( +/*===============*/ + void* buf, /* in: block to be freed, must not be + pointed to by the buffer pool */ + ulint i, /* in: index of buf_pool->zip_free[] */ + ibool have_page_hash_mutex) +{ + buf_page_t* bpage; + buf_page_t* buddy; + + //ut_ad(buf_pool_mutex_own()); + ut_ad(mutex_own(&zip_free_mutex)); + ut_ad(!mutex_own(&buf_pool_zip_mutex)); + ut_ad(i <= BUF_BUDDY_SIZES); + ut_ad(buf_buddy_stat[i].used > 0); + + buf_buddy_stat[i].used--; +recombine: + UNIV_MEM_ASSERT_AND_ALLOC(buf, BUF_BUDDY_LOW << i); + ut_d(((buf_page_t*) buf)->state = BUF_BLOCK_ZIP_FREE); + + if (i == BUF_BUDDY_SIZES) { + mutex_exit(&zip_free_mutex); + buf_buddy_block_free(buf, have_page_hash_mutex); + mutex_enter(&zip_free_mutex); + return; + } + + ut_ad(i < BUF_BUDDY_SIZES); + ut_ad(buf == ut_align_down(buf, BUF_BUDDY_LOW << i)); + ut_ad(!buf_pool_contains_zip(buf)); + + /* Try to combine adjacent blocks. */ + + buddy = (buf_page_t*) buf_buddy_get(((byte*) buf), BUF_BUDDY_LOW << i); + +#ifndef UNIV_DEBUG_VALGRIND + /* Valgrind would complain about accessing free memory. */ + + if (buddy->state != BUF_BLOCK_ZIP_FREE) { + + goto buddy_nonfree; + } + + /* The field buddy->state can only be trusted for free blocks. + If buddy->state == BUF_BLOCK_ZIP_FREE, the block is free if + it is in the free list. */ +#endif /* !UNIV_DEBUG_VALGRIND */ + + for (bpage = UT_LIST_GET_FIRST(buf_pool->zip_free[i]); bpage; ) { + UNIV_MEM_VALID(bpage, BUF_BUDDY_LOW << i); + ut_ad(buf_page_get_state(bpage) == BUF_BLOCK_ZIP_FREE); + + if (bpage == buddy) { +buddy_free: + /* The buddy is free: recombine */ + buf_buddy_remove_from_free(bpage, i); +buddy_free2: + ut_ad(buf_page_get_state(buddy) == BUF_BLOCK_ZIP_FREE); + ut_ad(!buf_pool_contains_zip(buddy)); + i++; + buf = ut_align_down(buf, BUF_BUDDY_LOW << i); + + goto recombine; + } + + ut_a(bpage != buf); + + { + buf_page_t* next = UT_LIST_GET_NEXT(zip_list, bpage); + UNIV_MEM_ASSERT_AND_FREE(bpage, BUF_BUDDY_LOW << i); + bpage = next; + } + } + +#ifndef UNIV_DEBUG_VALGRIND +buddy_nonfree: + /* Valgrind would complain about accessing free memory. */ + ut_d(UT_LIST_VALIDATE(zip_list, buf_page_t, buf_pool->zip_free[i])); +#endif /* UNIV_DEBUG_VALGRIND */ + + /* The buddy is not free. Is there a free block of this size? */ + bpage = UT_LIST_GET_LAST(buf_pool->zip_free[i]); + + if (bpage) { + /* Remove the block from the free list, because a successful + buf_buddy_relocate() will overwrite bpage->list. */ + + UNIV_MEM_VALID(bpage, BUF_BUDDY_LOW << i); + buf_buddy_remove_from_free(bpage, i); + + /* Try to relocate the buddy of buf to the free block. */ + if (buf_buddy_relocate(buddy, bpage, i, have_page_hash_mutex)) { + + ut_d(buddy->state = BUF_BLOCK_ZIP_FREE); + goto buddy_free2; + } + + buf_buddy_add_to_free(bpage, i); + + /* Try to relocate the buddy of the free block to buf. */ + buddy = (buf_page_t*) buf_buddy_get(((byte*) bpage), + BUF_BUDDY_LOW << i); + +#if defined UNIV_DEBUG && !defined UNIV_DEBUG_VALGRIND + { + const buf_page_t* b; + + /* The buddy must not be (completely) free, because + we always recombine adjacent free blocks. + (Parts of the buddy can be free in + buf_pool->zip_free[j] with j < i.)*/ + for (b = UT_LIST_GET_FIRST(buf_pool->zip_free[i]); + b; b = UT_LIST_GET_NEXT(zip_list, b)) { + + ut_a(b != buddy); + } + } +#endif /* UNIV_DEBUG && !UNIV_DEBUG_VALGRIND */ + + if (buf_buddy_relocate(buddy, buf, i, have_page_hash_mutex)) { + + buf = bpage; + UNIV_MEM_VALID(bpage, BUF_BUDDY_LOW << i); + ut_d(buddy->state = BUF_BLOCK_ZIP_FREE); + goto buddy_free; + } + } + + /* Free the block to the buddy list. */ + bpage = buf; +#ifdef UNIV_DEBUG + if (i < buf_buddy_get_slot(PAGE_ZIP_MIN_SIZE)) { + /* This area has most likely been allocated for at + least one compressed-only block descriptor. Check + that there are no live objects in the area. This is + not a complete check: it may yield false positives as + well as false negatives. Also, due to buddy blocks + being recombined, it is possible (although unlikely) + that this branch is never reached. */ + + char* c; + +# ifndef UNIV_DEBUG_VALGRIND + /* Valgrind would complain about accessing + uninitialized memory. Besides, Valgrind performs a + more exhaustive check, at every memory access. */ + const buf_page_t* b = buf; + const buf_page_t* const b_end = (buf_page_t*) + ((char*) b + (BUF_BUDDY_LOW << i)); + + for (; b < b_end; b++) { + /* Avoid false positives (and cause false + negatives) by checking for b->space < 1000. */ + + if ((b->state == BUF_BLOCK_ZIP_PAGE + || b->state == BUF_BLOCK_ZIP_DIRTY) + && b->space > 0 && b->space < 1000) { + fprintf(stderr, + "buddy dirty %p %u (%u,%u) %p,%lu\n", + (void*) b, + b->state, b->space, b->offset, + buf, i); + } + } +# endif /* !UNIV_DEBUG_VALGRIND */ + + /* Scramble the block. This should make any pointers + invalid and trigger a segmentation violation. Because + the scrambling can be reversed, it may be possible to + track down the object pointing to the freed data by + dereferencing the unscrambled bpage->LRU or + bpage->list pointers. */ + for (c = (char*) buf + (BUF_BUDDY_LOW << i); + c-- > (char*) buf; ) { + *c = ~*c ^ i; + } + } else { + /* Fill large blocks with a constant pattern. */ + memset(bpage, i, BUF_BUDDY_LOW << i); + } +#endif /* UNIV_DEBUG */ + bpage->state = BUF_BLOCK_ZIP_FREE; + buf_buddy_add_to_free(bpage, i); +} diff --git a/storage/xtradb/buf/buf0buf.c b/storage/xtradb/buf/buf0buf.c new file mode 100644 index 00000000000..a02b7879121 --- /dev/null +++ b/storage/xtradb/buf/buf0buf.c @@ -0,0 +1,4143 @@ +/***************************************************************************** + +Copyright (c) 1995, 2009, Innobase Oy. All Rights Reserved. +Copyright (c) 2008, Google Inc. + +Portions of this file contain modifications contributed and copyrighted by +Google, Inc. Those modifications are gratefully acknowledged and are described +briefly in the InnoDB documentation. The contributions by Google are +incorporated with their permission, and subject to the conditions contained in +the file COPYING.Google. + +This program is free software; you can redistribute it and/or modify it under +the terms of the GNU General Public License as published by the Free Software +Foundation; version 2 of the License. + +This program is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + +You 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 + +*****************************************************************************/ + +/****************************************************** +The database buffer buf_pool + +Created 11/5/1995 Heikki Tuuri +*******************************************************/ + +#include "buf0buf.h" + +#ifdef UNIV_NONINL +#include "buf0buf.ic" +#endif + +#include "buf0buddy.h" +#include "mem0mem.h" +#include "btr0btr.h" +#include "fil0fil.h" +#include "lock0lock.h" +#include "btr0sea.h" +#include "ibuf0ibuf.h" +#include "dict0dict.h" +#include "log0recv.h" +#include "trx0undo.h" +#include "srv0srv.h" +#include "page0zip.h" + +/* + IMPLEMENTATION OF THE BUFFER POOL + ================================= + +Performance improvement: +------------------------ +Thread scheduling in NT may be so slow that the OS wait mechanism should +not be used even in waiting for disk reads to complete. +Rather, we should put waiting query threads to the queue of +waiting jobs, and let the OS thread do something useful while the i/o +is processed. In this way we could remove most OS thread switches in +an i/o-intensive benchmark like TPC-C. + +A possibility is to put a user space thread library between the database +and NT. User space thread libraries might be very fast. + +SQL Server 7.0 can be configured to use 'fibers' which are lightweight +threads in NT. These should be studied. + + Buffer frames and blocks + ------------------------ +Following the terminology of Gray and Reuter, we call the memory +blocks where file pages are loaded buffer frames. For each buffer +frame there is a control block, or shortly, a block, in the buffer +control array. The control info which does not need to be stored +in the file along with the file page, resides in the control block. + + Buffer pool struct + ------------------ +The buffer buf_pool contains a single mutex which protects all the +control data structures of the buf_pool. The content of a buffer frame is +protected by a separate read-write lock in its control block, though. +These locks can be locked and unlocked without owning the buf_pool mutex. +The OS events in the buf_pool struct can be waited for without owning the +buf_pool mutex. + +The buf_pool mutex is a hot-spot in main memory, causing a lot of +memory bus traffic on multiprocessor systems when processors +alternately access the mutex. On our Pentium, the mutex is accessed +maybe every 10 microseconds. We gave up the solution to have mutexes +for each control block, for instance, because it seemed to be +complicated. + +A solution to reduce mutex contention of the buf_pool mutex is to +create a separate mutex for the page hash table. On Pentium, +accessing the hash table takes 2 microseconds, about half +of the total buf_pool mutex hold time. + + Control blocks + -------------- + +The control block contains, for instance, the bufferfix count +which is incremented when a thread wants a file page to be fixed +in a buffer frame. The bufferfix operation does not lock the +contents of the frame, however. For this purpose, the control +block contains a read-write lock. + +The buffer frames have to be aligned so that the start memory +address of a frame is divisible by the universal page size, which +is a power of two. + +We intend to make the buffer buf_pool size on-line reconfigurable, +that is, the buf_pool size can be changed without closing the database. +Then the database administarator may adjust it to be bigger +at night, for example. The control block array must +contain enough control blocks for the maximum buffer buf_pool size +which is used in the particular database. +If the buf_pool size is cut, we exploit the virtual memory mechanism of +the OS, and just refrain from using frames at high addresses. Then the OS +can swap them to disk. + +The control blocks containing file pages are put to a hash table +according to the file address of the page. +We could speed up the access to an individual page by using +"pointer swizzling": we could replace the page references on +non-leaf index pages by direct pointers to the page, if it exists +in the buf_pool. We could make a separate hash table where we could +chain all the page references in non-leaf pages residing in the buf_pool, +using the page reference as the hash key, +and at the time of reading of a page update the pointers accordingly. +Drawbacks of this solution are added complexity and, +possibly, extra space required on non-leaf pages for memory pointers. +A simpler solution is just to speed up the hash table mechanism +in the database, using tables whose size is a power of 2. + + Lists of blocks + --------------- + +There are several lists of control blocks. + +The free list (buf_pool->free) contains blocks which are currently not +used. + +The common LRU list contains all the blocks holding a file page +except those for which the bufferfix count is non-zero. +The pages are in the LRU list roughly in the order of the last +access to the page, so that the oldest pages are at the end of the +list. We also keep a pointer to near the end of the LRU list, +which we can use when we want to artificially age a page in the +buf_pool. This is used if we know that some page is not needed +again for some time: we insert the block right after the pointer, +causing it to be replaced sooner than would noramlly be the case. +Currently this aging mechanism is used for read-ahead mechanism +of pages, and it can also be used when there is a scan of a full +table which cannot fit in the memory. Putting the pages near the +of the LRU list, we make sure that most of the buf_pool stays in the +main memory, undisturbed. + +The unzip_LRU list contains a subset of the common LRU list. The +blocks on the unzip_LRU list hold a compressed file page and the +corresponding uncompressed page frame. A block is in unzip_LRU if and +only if the predicate buf_page_belongs_to_unzip_LRU(&block->page) +holds. The blocks in unzip_LRU will be in same order as they are in +the common LRU list. That is, each manipulation of the common LRU +list will result in the same manipulation of the unzip_LRU list. + +The chain of modified blocks (buf_pool->flush_list) contains the blocks +holding file pages that have been modified in the memory +but not written to disk yet. The block with the oldest modification +which has not yet been written to disk is at the end of the chain. + +The chain of unmodified compressed blocks (buf_pool->zip_clean) +contains the control blocks (buf_page_t) of those compressed pages +that are not in buf_pool->flush_list and for which no uncompressed +page has been allocated in the buffer pool. The control blocks for +uncompressed pages are accessible via buf_block_t objects that are +reachable via buf_pool->chunks[]. + +The chains of free memory blocks (buf_pool->zip_free[]) are used by +the buddy allocator (buf0buddy.c) to keep track of currently unused +memory blocks of size sizeof(buf_page_t)..UNIV_PAGE_SIZE / 2. These +blocks are inside the UNIV_PAGE_SIZE-sized memory blocks of type +BUF_BLOCK_MEMORY that the buddy allocator requests from the buffer +pool. The buddy allocator is solely used for allocating control +blocks for compressed pages (buf_page_t) and compressed page frames. + + Loading a file page + ------------------- + +First, a victim block for replacement has to be found in the +buf_pool. It is taken from the free list or searched for from the +end of the LRU-list. An exclusive lock is reserved for the frame, +the io_fix field is set in the block fixing the block in buf_pool, +and the io-operation for loading the page is queued. The io-handler thread +releases the X-lock on the frame and resets the io_fix field +when the io operation completes. + +A thread may request the above operation using the function +buf_page_get(). It may then continue to request a lock on the frame. +The lock is granted when the io-handler releases the x-lock. + + Read-ahead + ---------- + +The read-ahead mechanism is intended to be intelligent and +isolated from the semantically higher levels of the database +index management. From the higher level we only need the +information if a file page has a natural successor or +predecessor page. On the leaf level of a B-tree index, +these are the next and previous pages in the natural +order of the pages. + +Let us first explain the read-ahead mechanism when the leafs +of a B-tree are scanned in an ascending or descending order. +When a read page is the first time referenced in the buf_pool, +the buffer manager checks if it is at the border of a so-called +linear read-ahead area. The tablespace is divided into these +areas of size 64 blocks, for example. So if the page is at the +border of such an area, the read-ahead mechanism checks if +all the other blocks in the area have been accessed in an +ascending or descending order. If this is the case, the system +looks at the natural successor or predecessor of the page, +checks if that is at the border of another area, and in this case +issues read-requests for all the pages in that area. Maybe +we could relax the condition that all the pages in the area +have to be accessed: if data is deleted from a table, there may +appear holes of unused pages in the area. + +A different read-ahead mechanism is used when there appears +to be a random access pattern to a file. +If a new page is referenced in the buf_pool, and several pages +of its random access area (for instance, 32 consecutive pages +in a tablespace) have recently been referenced, we may predict +that the whole area may be needed in the near future, and issue +the read requests for the whole area. +*/ + +/* Value in microseconds */ +static const int WAIT_FOR_READ = 5000; + +/* The buffer buf_pool of the database */ +UNIV_INTERN buf_pool_t* buf_pool = NULL; + +/* mutex protecting the buffer pool struct and control blocks, except the +read-write lock in them */ +UNIV_INTERN mutex_t buf_pool_mutex; +UNIV_INTERN mutex_t LRU_list_mutex; +UNIV_INTERN mutex_t flush_list_mutex; +UNIV_INTERN rw_lock_t page_hash_latch; +UNIV_INTERN mutex_t free_list_mutex; +UNIV_INTERN mutex_t zip_free_mutex; +UNIV_INTERN mutex_t zip_hash_mutex; +/* mutex protecting the control blocks of compressed-only pages +(of type buf_page_t, not buf_block_t) */ +UNIV_INTERN mutex_t buf_pool_zip_mutex; + +#if defined UNIV_DEBUG || defined UNIV_BUF_DEBUG +static ulint buf_dbg_counter = 0; /* This is used to insert validation + operations in excution in the + debug version */ +/** Flag to forbid the release of the buffer pool mutex. +Protected by buf_pool_mutex. */ +UNIV_INTERN ulint buf_pool_mutex_exit_forbidden = 0; +#endif /* UNIV_DEBUG || UNIV_BUF_DEBUG */ +#ifdef UNIV_DEBUG +/* If this is set TRUE, the program prints info whenever +read-ahead or flush occurs */ +UNIV_INTERN ibool buf_debug_prints = FALSE; +#endif /* UNIV_DEBUG */ + +/* A chunk of buffers. The buffer pool is allocated in chunks. */ +struct buf_chunk_struct{ + ulint mem_size; /* allocated size of the chunk */ + ulint size; /* size of frames[] and blocks[] */ + void* mem; /* pointer to the memory area which + was allocated for the frames */ + buf_block_t* blocks; /* array of buffer control blocks */ +}; + +/************************************************************************ +Calculates a page checksum which is stored to the page when it is written +to a file. Note that we must be careful to calculate the same value on +32-bit and 64-bit architectures. */ +UNIV_INTERN +ulint +buf_calc_page_new_checksum( +/*=======================*/ + /* out: checksum */ + const byte* page) /* in: buffer page */ +{ + ulint checksum; + + /* Since the field FIL_PAGE_FILE_FLUSH_LSN, and in versions <= 4.1.x + ..._ARCH_LOG_NO, are written outside the buffer pool to the first + pages of data files, we have to skip them in the page checksum + calculation. + We must also skip the field FIL_PAGE_SPACE_OR_CHKSUM where the + checksum is stored, and also the last 8 bytes of page because + there we store the old formula checksum. */ + + checksum = ut_fold_binary(page + FIL_PAGE_OFFSET, + FIL_PAGE_FILE_FLUSH_LSN - FIL_PAGE_OFFSET) + + ut_fold_binary(page + FIL_PAGE_DATA, + UNIV_PAGE_SIZE - FIL_PAGE_DATA + - FIL_PAGE_END_LSN_OLD_CHKSUM); + checksum = checksum & 0xFFFFFFFFUL; + + return(checksum); +} + +/************************************************************************ +In versions < 4.0.14 and < 4.1.1 there was a bug that the checksum only +looked at the first few bytes of the page. This calculates that old +checksum. +NOTE: we must first store the new formula checksum to +FIL_PAGE_SPACE_OR_CHKSUM before calculating and storing this old checksum +because this takes that field as an input! */ +UNIV_INTERN +ulint +buf_calc_page_old_checksum( +/*=======================*/ + /* out: checksum */ + const byte* page) /* in: buffer page */ +{ + ulint checksum; + + checksum = ut_fold_binary(page, FIL_PAGE_FILE_FLUSH_LSN); + + checksum = checksum & 0xFFFFFFFFUL; + + return(checksum); +} + +/************************************************************************ +Checks if a page is corrupt. */ +UNIV_INTERN +ibool +buf_page_is_corrupted( +/*==================*/ + /* out: TRUE if corrupted */ + const byte* read_buf, /* in: a database page */ + ulint zip_size) /* in: size of compressed page; + 0 for uncompressed pages */ +{ + ulint checksum_field; + ulint old_checksum_field; +#ifndef UNIV_HOTBACKUP + ib_uint64_t current_lsn; +#endif + if (UNIV_LIKELY(!zip_size) + && memcmp(read_buf + FIL_PAGE_LSN + 4, + read_buf + UNIV_PAGE_SIZE + - FIL_PAGE_END_LSN_OLD_CHKSUM + 4, 4)) { + + /* Stored log sequence numbers at the start and the end + of page do not match */ + + return(TRUE); + } + +#ifndef UNIV_HOTBACKUP + if (recv_lsn_checks_on && log_peek_lsn(¤t_lsn)) { + if (current_lsn < mach_read_ull(read_buf + FIL_PAGE_LSN)) { + ut_print_timestamp(stderr); + + fprintf(stderr, + " InnoDB: Error: page %lu log sequence number" + " %llu\n" + "InnoDB: is in the future! Current system " + "log sequence number %llu.\n" + "InnoDB: Your database may be corrupt or " + "you may have copied the InnoDB\n" + "InnoDB: tablespace but not the InnoDB " + "log files. See\n" + "InnoDB: http://dev.mysql.com/doc/refman/" + "5.1/en/forcing-recovery.html\n" + "InnoDB: for more information.\n", + (ulong) mach_read_from_4(read_buf + + FIL_PAGE_OFFSET), + mach_read_ull(read_buf + FIL_PAGE_LSN), + current_lsn); + } + } +#endif + + /* If we use checksums validation, make additional check before + returning TRUE to ensure that the checksum is not equal to + BUF_NO_CHECKSUM_MAGIC which might be stored by InnoDB with checksums + disabled. Otherwise, skip checksum calculation and return FALSE */ + + if (UNIV_LIKELY(srv_use_checksums)) { + checksum_field = mach_read_from_4(read_buf + + FIL_PAGE_SPACE_OR_CHKSUM); + + if (UNIV_UNLIKELY(zip_size)) { + return(checksum_field != BUF_NO_CHECKSUM_MAGIC + && checksum_field + != page_zip_calc_checksum(read_buf, zip_size)); + } + + old_checksum_field = mach_read_from_4( + read_buf + UNIV_PAGE_SIZE + - FIL_PAGE_END_LSN_OLD_CHKSUM); + + /* There are 2 valid formulas for old_checksum_field: + + 1. Very old versions of InnoDB only stored 8 byte lsn to the + start and the end of the page. + + 2. Newer InnoDB versions store the old formula checksum + there. */ + + if (old_checksum_field != mach_read_from_4(read_buf + + FIL_PAGE_LSN) + && old_checksum_field != BUF_NO_CHECKSUM_MAGIC + && old_checksum_field + != buf_calc_page_old_checksum(read_buf)) { + + return(TRUE); + } + + /* InnoDB versions < 4.0.14 and < 4.1.1 stored the space id + (always equal to 0), to FIL_PAGE_SPACE_OR_CHKSUM */ + + if (checksum_field != 0 + && checksum_field != BUF_NO_CHECKSUM_MAGIC + && checksum_field + != buf_calc_page_new_checksum(read_buf)) { + + return(TRUE); + } + } + + return(FALSE); +} + +/************************************************************************ +Prints a page to stderr. */ +UNIV_INTERN +void +buf_page_print( +/*===========*/ + const byte* read_buf, /* in: a database page */ + ulint zip_size) /* in: compressed page size, or + 0 for uncompressed pages */ +{ + dict_index_t* index; + ulint checksum; + ulint old_checksum; + ulint size = zip_size; + + if (!size) { + size = UNIV_PAGE_SIZE; + } + + ut_print_timestamp(stderr); + fprintf(stderr, " InnoDB: Page dump in ascii and hex (%lu bytes):\n", + (ulong) size); + ut_print_buf(stderr, read_buf, size); + fputs("\nInnoDB: End of page dump\n", stderr); + + if (zip_size) { + /* Print compressed page. */ + + switch (fil_page_get_type(read_buf)) { + case FIL_PAGE_TYPE_ZBLOB: + case FIL_PAGE_TYPE_ZBLOB2: + checksum = srv_use_checksums + ? page_zip_calc_checksum(read_buf, zip_size) + : BUF_NO_CHECKSUM_MAGIC; + ut_print_timestamp(stderr); + fprintf(stderr, + " InnoDB: Compressed BLOB page" + " checksum %lu, stored %lu\n" + "InnoDB: Page lsn %lu %lu\n" + "InnoDB: Page number (if stored" + " to page already) %lu,\n" + "InnoDB: space id (if stored" + " to page already) %lu\n", + (ulong) checksum, + (ulong) mach_read_from_4( + read_buf + FIL_PAGE_SPACE_OR_CHKSUM), + (ulong) mach_read_from_4( + read_buf + FIL_PAGE_LSN), + (ulong) mach_read_from_4( + read_buf + (FIL_PAGE_LSN + 4)), + (ulong) mach_read_from_4( + read_buf + FIL_PAGE_OFFSET), + (ulong) mach_read_from_4( + read_buf + + FIL_PAGE_ARCH_LOG_NO_OR_SPACE_ID)); + return; + default: + ut_print_timestamp(stderr); + fprintf(stderr, + " InnoDB: unknown page type %lu," + " assuming FIL_PAGE_INDEX\n", + fil_page_get_type(read_buf)); + /* fall through */ + case FIL_PAGE_INDEX: + checksum = srv_use_checksums + ? page_zip_calc_checksum(read_buf, zip_size) + : BUF_NO_CHECKSUM_MAGIC; + + ut_print_timestamp(stderr); + fprintf(stderr, + " InnoDB: Compressed page checksum %lu," + " stored %lu\n" + "InnoDB: Page lsn %lu %lu\n" + "InnoDB: Page number (if stored" + " to page already) %lu,\n" + "InnoDB: space id (if stored" + " to page already) %lu\n", + (ulong) checksum, + (ulong) mach_read_from_4( + read_buf + FIL_PAGE_SPACE_OR_CHKSUM), + (ulong) mach_read_from_4( + read_buf + FIL_PAGE_LSN), + (ulong) mach_read_from_4( + read_buf + (FIL_PAGE_LSN + 4)), + (ulong) mach_read_from_4( + read_buf + FIL_PAGE_OFFSET), + (ulong) mach_read_from_4( + read_buf + + FIL_PAGE_ARCH_LOG_NO_OR_SPACE_ID)); + return; + case FIL_PAGE_TYPE_XDES: + /* This is an uncompressed page. */ + break; + } + } + + checksum = srv_use_checksums + ? buf_calc_page_new_checksum(read_buf) : BUF_NO_CHECKSUM_MAGIC; + old_checksum = srv_use_checksums + ? buf_calc_page_old_checksum(read_buf) : BUF_NO_CHECKSUM_MAGIC; + + ut_print_timestamp(stderr); + fprintf(stderr, + " InnoDB: Page checksum %lu, prior-to-4.0.14-form" + " checksum %lu\n" + "InnoDB: stored checksum %lu, prior-to-4.0.14-form" + " stored checksum %lu\n" + "InnoDB: Page lsn %lu %lu, low 4 bytes of lsn" + " at page end %lu\n" + "InnoDB: Page number (if stored to page already) %lu,\n" + "InnoDB: space id (if created with >= MySQL-4.1.1" + " and stored already) %lu\n", + (ulong) checksum, (ulong) old_checksum, + (ulong) mach_read_from_4(read_buf + FIL_PAGE_SPACE_OR_CHKSUM), + (ulong) mach_read_from_4(read_buf + UNIV_PAGE_SIZE + - FIL_PAGE_END_LSN_OLD_CHKSUM), + (ulong) mach_read_from_4(read_buf + FIL_PAGE_LSN), + (ulong) mach_read_from_4(read_buf + FIL_PAGE_LSN + 4), + (ulong) mach_read_from_4(read_buf + UNIV_PAGE_SIZE + - FIL_PAGE_END_LSN_OLD_CHKSUM + 4), + (ulong) mach_read_from_4(read_buf + FIL_PAGE_OFFSET), + (ulong) mach_read_from_4(read_buf + + FIL_PAGE_ARCH_LOG_NO_OR_SPACE_ID)); + + if (mach_read_from_2(read_buf + TRX_UNDO_PAGE_HDR + TRX_UNDO_PAGE_TYPE) + == TRX_UNDO_INSERT) { + fprintf(stderr, + "InnoDB: Page may be an insert undo log page\n"); + } else if (mach_read_from_2(read_buf + TRX_UNDO_PAGE_HDR + + TRX_UNDO_PAGE_TYPE) + == TRX_UNDO_UPDATE) { + fprintf(stderr, + "InnoDB: Page may be an update undo log page\n"); + } + + switch (fil_page_get_type(read_buf)) { + case FIL_PAGE_INDEX: + fprintf(stderr, + "InnoDB: Page may be an index page where" + " index id is %lu %lu\n", + (ulong) ut_dulint_get_high( + btr_page_get_index_id(read_buf)), + (ulong) ut_dulint_get_low( + btr_page_get_index_id(read_buf))); + +#ifdef UNIV_HOTBACKUP + /* If the code is in ibbackup, dict_sys may be uninitialized, + i.e., NULL */ + + if (dict_sys == NULL) { + break; + } +#endif /* UNIV_HOTBACKUP */ + + index = dict_index_find_on_id_low( + btr_page_get_index_id(read_buf)); + if (index) { + fputs("InnoDB: (", stderr); + dict_index_name_print(stderr, NULL, index); + fputs(")\n", stderr); + } + break; + case FIL_PAGE_INODE: + fputs("InnoDB: Page may be an 'inode' page\n", stderr); + break; + case FIL_PAGE_IBUF_FREE_LIST: + fputs("InnoDB: Page may be an insert buffer free list page\n", + stderr); + break; + case FIL_PAGE_TYPE_ALLOCATED: + fputs("InnoDB: Page may be a freshly allocated page\n", + stderr); + break; + case FIL_PAGE_IBUF_BITMAP: + fputs("InnoDB: Page may be an insert buffer bitmap page\n", + stderr); + break; + case FIL_PAGE_TYPE_SYS: + fputs("InnoDB: Page may be a system page\n", + stderr); + break; + case FIL_PAGE_TYPE_TRX_SYS: + fputs("InnoDB: Page may be a transaction system page\n", + stderr); + break; + case FIL_PAGE_TYPE_FSP_HDR: + fputs("InnoDB: Page may be a file space header page\n", + stderr); + break; + case FIL_PAGE_TYPE_XDES: + fputs("InnoDB: Page may be an extent descriptor page\n", + stderr); + break; + case FIL_PAGE_TYPE_BLOB: + fputs("InnoDB: Page may be a BLOB page\n", + stderr); + break; + case FIL_PAGE_TYPE_ZBLOB: + case FIL_PAGE_TYPE_ZBLOB2: + fputs("InnoDB: Page may be a compressed BLOB page\n", + stderr); + break; + } +} + +/************************************************************************ +Initializes a buffer control block when the buf_pool is created. */ +static +void +buf_block_init( +/*===========*/ + buf_block_t* block, /* in: pointer to control block */ + byte* frame) /* in: pointer to buffer frame */ +{ + UNIV_MEM_DESC(frame, UNIV_PAGE_SIZE, block); + + block->frame = frame; + + block->page.state = BUF_BLOCK_NOT_USED; + block->page.buf_fix_count = 0; + block->page.io_fix = BUF_IO_NONE; + + block->modify_clock = 0; + +#ifdef UNIV_DEBUG_FILE_ACCESSES + block->page.file_page_was_freed = FALSE; +#endif /* UNIV_DEBUG_FILE_ACCESSES */ + + block->check_index_page_at_flush = FALSE; + block->index = NULL; + +#ifdef UNIV_DEBUG + block->page.in_page_hash = FALSE; + block->page.in_zip_hash = FALSE; + block->page.in_flush_list = FALSE; + block->page.in_free_list = FALSE; +#endif /* UNIV_DEBUG */ + block->page.in_LRU_list = FALSE; + block->in_unzip_LRU_list = FALSE; +#if defined UNIV_AHI_DEBUG || defined UNIV_DEBUG + block->n_pointers = 0; +#endif /* UNIV_AHI_DEBUG || UNIV_DEBUG */ + page_zip_des_init(&block->page.zip); + + mutex_create(&block->mutex, SYNC_BUF_BLOCK); + + rw_lock_create(&block->lock, SYNC_LEVEL_VARYING); + ut_ad(rw_lock_validate(&(block->lock))); + +#ifdef UNIV_SYNC_DEBUG + rw_lock_create(&block->debug_latch, SYNC_NO_ORDER_CHECK); +#endif /* UNIV_SYNC_DEBUG */ +} + +/************************************************************************ +Allocates a chunk of buffer frames. */ +static +buf_chunk_t* +buf_chunk_init( +/*===========*/ + /* out: chunk, or NULL on failure */ + buf_chunk_t* chunk, /* out: chunk of buffers */ + ulint mem_size) /* in: requested size in bytes */ +{ + buf_block_t* block; + byte* frame; + ulint i; + + /* Round down to a multiple of page size, + although it already should be. */ + mem_size = ut_2pow_round(mem_size, UNIV_PAGE_SIZE); + /* Reserve space for the block descriptors. */ + mem_size += ut_2pow_round((mem_size / UNIV_PAGE_SIZE) * (sizeof *block) + + (UNIV_PAGE_SIZE - 1), UNIV_PAGE_SIZE); + + chunk->mem_size = mem_size; + chunk->mem = os_mem_alloc_large(&chunk->mem_size); + + if (UNIV_UNLIKELY(chunk->mem == NULL)) { + + return(NULL); + } + + /* Allocate the block descriptors from + the start of the memory block. */ + chunk->blocks = chunk->mem; + + /* Align a pointer to the first frame. Note that when + os_large_page_size is smaller than UNIV_PAGE_SIZE, + we may allocate one fewer block than requested. When + it is bigger, we may allocate more blocks than requested. */ + + frame = ut_align(chunk->mem, UNIV_PAGE_SIZE); + chunk->size = chunk->mem_size / UNIV_PAGE_SIZE + - (frame != chunk->mem); + + /* Subtract the space needed for block descriptors. */ + { + ulint size = chunk->size; + + while (frame < (byte*) (chunk->blocks + size)) { + frame += UNIV_PAGE_SIZE; + size--; + } + + chunk->size = size; + } + + /* Init block structs and assign frames for them. Then we + assign the frames to the first blocks (we already mapped the + memory above). */ + + block = chunk->blocks; + + for (i = chunk->size; i--; ) { + + buf_block_init(block, frame); + +#ifdef HAVE_purify + /* Wipe contents of frame to eliminate a Purify warning */ + memset(block->frame, '\0', UNIV_PAGE_SIZE); +#endif + /* Add the block to the free list */ + mutex_enter(&free_list_mutex); + UT_LIST_ADD_LAST(free, buf_pool->free, (&block->page)); + ut_d(block->page.in_free_list = TRUE); + mutex_exit(&free_list_mutex); + + block++; + frame += UNIV_PAGE_SIZE; + } + + return(chunk); +} + +#ifdef UNIV_DEBUG +/************************************************************************* +Finds a block in the given buffer chunk that points to a +given compressed page. */ +static +buf_block_t* +buf_chunk_contains_zip( +/*===================*/ + /* out: buffer block pointing to + the compressed page, or NULL */ + buf_chunk_t* chunk, /* in: chunk being checked */ + const void* data) /* in: pointer to compressed page */ +{ + buf_block_t* block; + ulint i; + + ut_ad(buf_pool); + //ut_ad(buf_pool_mutex_own()); + + block = chunk->blocks; + + for (i = chunk->size; i--; block++) { + if (block->page.zip.data == data) { + + return(block); + } + } + + return(NULL); +} + +/************************************************************************* +Finds a block in the buffer pool that points to a +given compressed page. */ +UNIV_INTERN +buf_block_t* +buf_pool_contains_zip( +/*==================*/ + /* out: buffer block pointing to + the compressed page, or NULL */ + const void* data) /* in: pointer to compressed page */ +{ + ulint n; + buf_chunk_t* chunk = buf_pool->chunks; + + for (n = buf_pool->n_chunks; n--; chunk++) { + buf_block_t* block = buf_chunk_contains_zip(chunk, data); + + if (block) { + return(block); + } + } + + return(NULL); +} +#endif /* UNIV_DEBUG */ + +/************************************************************************* +Checks that all file pages in the buffer chunk are in a replaceable state. */ +static +const buf_block_t* +buf_chunk_not_freed( +/*================*/ + /* out: address of a non-free block, + or NULL if all freed */ + buf_chunk_t* chunk) /* in: chunk being checked */ +{ + buf_block_t* block; + ulint i; + + ut_ad(buf_pool); + //ut_ad(buf_pool_mutex_own()); /*optimistic...*/ + + block = chunk->blocks; + + for (i = chunk->size; i--; block++) { + mutex_enter(&block->mutex); + + if (buf_block_get_state(block) == BUF_BLOCK_FILE_PAGE + && !buf_flush_ready_for_replace(&block->page)) { + + mutex_exit(&block->mutex); + return(block); + } + + mutex_exit(&block->mutex); + } + + return(NULL); +} + +/************************************************************************* +Checks that all blocks in the buffer chunk are in BUF_BLOCK_NOT_USED state. */ +static +ibool +buf_chunk_all_free( +/*===============*/ + /* out: TRUE if all freed */ + const buf_chunk_t* chunk) /* in: chunk being checked */ +{ + const buf_block_t* block; + ulint i; + + ut_ad(buf_pool); + ut_ad(buf_pool_mutex_own()); /* but we need all mutex here */ + + block = chunk->blocks; + + for (i = chunk->size; i--; block++) { + + if (buf_block_get_state(block) != BUF_BLOCK_NOT_USED) { + + return(FALSE); + } + } + + return(TRUE); +} + +/************************************************************************ +Frees a chunk of buffer frames. */ +static +void +buf_chunk_free( +/*===========*/ + buf_chunk_t* chunk) /* out: chunk of buffers */ +{ + buf_block_t* block; + const buf_block_t* block_end; + + ut_ad(buf_pool_mutex_own()); /* but we need all mutex here */ + + block_end = chunk->blocks + chunk->size; + + for (block = chunk->blocks; block < block_end; block++) { + ut_a(buf_block_get_state(block) == BUF_BLOCK_NOT_USED); + ut_a(!block->page.zip.data); + + ut_ad(!block->page.in_LRU_list); + ut_ad(!block->in_unzip_LRU_list); + ut_ad(!block->page.in_flush_list); + /* Remove the block from the free list. */ + mutex_enter(&free_list_mutex); + ut_ad(block->page.in_free_list); + UT_LIST_REMOVE(free, buf_pool->free, (&block->page)); + mutex_exit(&free_list_mutex); + + /* Free the latches. */ + mutex_free(&block->mutex); + rw_lock_free(&block->lock); +#ifdef UNIV_SYNC_DEBUG + rw_lock_free(&block->debug_latch); +#endif /* UNIV_SYNC_DEBUG */ + UNIV_MEM_UNDESC(block); + } + + os_mem_free_large(chunk->mem, chunk->mem_size); +} + +/************************************************************************ +Creates the buffer pool. */ +UNIV_INTERN +buf_pool_t* +buf_pool_init(void) +/*===============*/ + /* out, own: buf_pool object, NULL if not + enough memory or error */ +{ + buf_chunk_t* chunk; + ulint i; + + buf_pool = mem_zalloc(sizeof(buf_pool_t)); + + /* 1. Initialize general fields + ------------------------------- */ + mutex_create(&buf_pool_mutex, SYNC_BUF_POOL); + mutex_create(&LRU_list_mutex, SYNC_BUF_LRU_LIST); + mutex_create(&flush_list_mutex, SYNC_BUF_FLUSH_LIST); + rw_lock_create(&page_hash_latch, SYNC_BUF_PAGE_HASH); + mutex_create(&free_list_mutex, SYNC_BUF_FREE_LIST); + mutex_create(&zip_free_mutex, SYNC_BUF_ZIP_FREE); + mutex_create(&zip_hash_mutex, SYNC_BUF_ZIP_HASH); + + mutex_create(&buf_pool_zip_mutex, SYNC_BUF_BLOCK); + + mutex_enter(&LRU_list_mutex); + rw_lock_x_lock(&page_hash_latch); + buf_pool_mutex_enter(); + + buf_pool->n_chunks = 1; + buf_pool->chunks = chunk = mem_alloc(sizeof *chunk); + + UT_LIST_INIT(buf_pool->free); + + if (!buf_chunk_init(chunk, srv_buf_pool_size)) { + mem_free(chunk); + mem_free(buf_pool); + buf_pool = NULL; + return(NULL); + } + + srv_buf_pool_old_size = srv_buf_pool_size; + buf_pool->curr_size = chunk->size; + srv_buf_pool_curr_size = buf_pool->curr_size * UNIV_PAGE_SIZE; + + buf_pool->page_hash = hash_create(2 * buf_pool->curr_size); + buf_pool->zip_hash = hash_create(2 * buf_pool->curr_size); + + buf_pool->last_printout_time = time(NULL); + + /* 2. Initialize flushing fields + -------------------------------- */ + + for (i = BUF_FLUSH_LRU; i < BUF_FLUSH_N_TYPES; i++) { + buf_pool->no_flush[i] = os_event_create(NULL); + } + + buf_pool->ulint_clock = 1; + + /* 3. Initialize LRU fields + --------------------------- */ + /* All fields are initialized by mem_zalloc(). */ + + mutex_exit(&LRU_list_mutex); + rw_lock_x_unlock(&page_hash_latch); + buf_pool_mutex_exit(); + + btr_search_sys_create(buf_pool->curr_size + * UNIV_PAGE_SIZE / sizeof(void*) / 64); + + /* 4. Initialize the buddy allocator fields */ + /* All fields are initialized by mem_zalloc(). */ + + return(buf_pool); +} + +/************************************************************************ +Frees the buffer pool at shutdown. This must not be invoked before +freeing all mutexes. */ +UNIV_INTERN +void +buf_pool_free(void) +/*===============*/ +{ + buf_chunk_t* chunk; + buf_chunk_t* chunks; + + chunks = buf_pool->chunks; + chunk = chunks + buf_pool->n_chunks; + + while (--chunk >= chunks) { + /* Bypass the checks of buf_chunk_free(), since they + would fail at shutdown. */ + os_mem_free_large(chunk->mem, chunk->mem_size); + } + + buf_pool->n_chunks = 0; +} + + +/************************************************************************ +Drops the adaptive hash index. To prevent a livelock, this function +is only to be called while holding btr_search_latch and while +btr_search_enabled == FALSE. */ +UNIV_INTERN +void +buf_pool_drop_hash_index(void) +/*==========================*/ +{ + ibool released_search_latch; + +#ifdef UNIV_SYNC_DEBUG + ut_ad(rw_lock_own(&btr_search_latch, RW_LOCK_EX)); +#endif /* UNIV_SYNC_DEBUG */ + ut_ad(!btr_search_enabled); + + do { + buf_chunk_t* chunks = buf_pool->chunks; + buf_chunk_t* chunk = chunks + buf_pool->n_chunks; + + released_search_latch = FALSE; + + while (--chunk >= chunks) { + buf_block_t* block = chunk->blocks; + ulint i = chunk->size; + + for (; i--; block++) { + /* block->is_hashed cannot be modified + when we have an x-latch on btr_search_latch; + see the comment in buf0buf.h */ + + if (!block->is_hashed) { + continue; + } + + /* To follow the latching order, we + have to release btr_search_latch + before acquiring block->latch. */ + rw_lock_x_unlock(&btr_search_latch); + /* When we release the search latch, + we must rescan all blocks, because + some may become hashed again. */ + released_search_latch = TRUE; + + rw_lock_x_lock(&block->lock); + + /* This should be guaranteed by the + callers, which will be holding + btr_search_enabled_mutex. */ + ut_ad(!btr_search_enabled); + + /* Because we did not buffer-fix the + block by calling buf_block_get_gen(), + it is possible that the block has been + allocated for some other use after + btr_search_latch was released above. + We do not care which file page the + block is mapped to. All we want to do + is to drop any hash entries referring + to the page. */ + + /* It is possible that + block->page.state != BUF_FILE_PAGE. + Even that does not matter, because + btr_search_drop_page_hash_index() will + check block->is_hashed before doing + anything. block->is_hashed can only + be set on uncompressed file pages. */ + + btr_search_drop_page_hash_index(block); + + rw_lock_x_unlock(&block->lock); + + rw_lock_x_lock(&btr_search_latch); + + ut_ad(!btr_search_enabled); + } + } + } while (released_search_latch); +} + +/************************************************************************ +Relocate a buffer control block. Relocates the block on the LRU list +and in buf_pool->page_hash. Does not relocate bpage->list. +The caller must take care of relocating bpage->list. */ +UNIV_INTERN +void +buf_relocate( +/*=========*/ + buf_page_t* bpage, /* in/out: control block being relocated; + buf_page_get_state(bpage) must be + BUF_BLOCK_ZIP_DIRTY or BUF_BLOCK_ZIP_PAGE */ + buf_page_t* dpage) /* in/out: destination control block */ +{ + buf_page_t* b; + ulint fold; + + //ut_ad(buf_pool_mutex_own()); + ut_ad(mutex_own(&LRU_list_mutex)); +#ifdef UNIV_SYNC_DEBUG + ut_ad(rw_lock_own(&page_hash_latch, RW_LOCK_EX)); +#endif + ut_ad(mutex_own(buf_page_get_mutex(bpage))); + ut_a(buf_page_get_io_fix(bpage) == BUF_IO_NONE); + ut_a(bpage->buf_fix_count == 0); + ut_ad(bpage->in_LRU_list); + ut_ad(!bpage->in_zip_hash); + ut_ad(bpage->in_page_hash); + ut_ad(bpage == buf_page_hash_get(bpage->space, bpage->offset)); +#ifdef UNIV_DEBUG + switch (buf_page_get_state(bpage)) { + case BUF_BLOCK_ZIP_FREE: + case BUF_BLOCK_NOT_USED: + case BUF_BLOCK_READY_FOR_USE: + case BUF_BLOCK_FILE_PAGE: + case BUF_BLOCK_MEMORY: + case BUF_BLOCK_REMOVE_HASH: + ut_error; + case BUF_BLOCK_ZIP_DIRTY: + case BUF_BLOCK_ZIP_PAGE: + break; + } +#endif /* UNIV_DEBUG */ + + memcpy(dpage, bpage, sizeof *dpage); + + bpage->in_LRU_list = FALSE; + ut_d(bpage->in_page_hash = FALSE); + + /* relocate buf_pool->LRU */ + b = UT_LIST_GET_PREV(LRU, bpage); + UT_LIST_REMOVE(LRU, buf_pool->LRU, bpage); + + if (b) { + UT_LIST_INSERT_AFTER(LRU, buf_pool->LRU, b, dpage); + } else { + UT_LIST_ADD_FIRST(LRU, buf_pool->LRU, dpage); + } + + if (UNIV_UNLIKELY(buf_pool->LRU_old == bpage)) { + buf_pool->LRU_old = dpage; +#ifdef UNIV_LRU_DEBUG + /* buf_pool->LRU_old must be the first item in the LRU list + whose "old" flag is set. */ + ut_a(!UT_LIST_GET_PREV(LRU, buf_pool->LRU_old) + || !UT_LIST_GET_PREV(LRU, buf_pool->LRU_old)->old); + ut_a(!UT_LIST_GET_NEXT(LRU, buf_pool->LRU_old) + || UT_LIST_GET_NEXT(LRU, buf_pool->LRU_old)->old); +#endif /* UNIV_LRU_DEBUG */ + } + + ut_d(UT_LIST_VALIDATE(LRU, buf_page_t, buf_pool->LRU)); + + /* relocate buf_pool->page_hash */ + fold = buf_page_address_fold(bpage->space, bpage->offset); + + HASH_DELETE(buf_page_t, hash, buf_pool->page_hash, fold, bpage); + HASH_INSERT(buf_page_t, hash, buf_pool->page_hash, fold, dpage); + + UNIV_MEM_INVALID(bpage, sizeof *bpage); +} + +/************************************************************************ +Shrinks the buffer pool. */ +static +void +buf_pool_shrink( +/*============*/ + /* out: TRUE if shrunk */ + ulint chunk_size) /* in: number of pages to remove */ +{ + buf_chunk_t* chunks; + buf_chunk_t* chunk; + ulint max_size; + ulint max_free_size; + buf_chunk_t* max_chunk; + buf_chunk_t* max_free_chunk; + + ut_ad(!buf_pool_mutex_own()); + +try_again: + btr_search_disable(); /* Empty the adaptive hash index again */ + //buf_pool_mutex_enter(); + mutex_enter(&LRU_list_mutex); + +shrink_again: + if (buf_pool->n_chunks <= 1) { + + /* Cannot shrink if there is only one chunk */ + goto func_done; + } + + /* Search for the largest free chunk + not larger than the size difference */ + chunks = buf_pool->chunks; + chunk = chunks + buf_pool->n_chunks; + max_size = max_free_size = 0; + max_chunk = max_free_chunk = NULL; + + while (--chunk >= chunks) { + if (chunk->size <= chunk_size + && chunk->size > max_free_size) { + if (chunk->size > max_size) { + max_size = chunk->size; + max_chunk = chunk; + } + + if (buf_chunk_all_free(chunk)) { + max_free_size = chunk->size; + max_free_chunk = chunk; + } + } + } + + if (!max_free_size) { + + ulint dirty = 0; + ulint nonfree = 0; + buf_block_t* block; + buf_block_t* bend; + + /* Cannot shrink: try again later + (do not assign srv_buf_pool_old_size) */ + if (!max_chunk) { + + goto func_exit; + } + + block = max_chunk->blocks; + bend = block + max_chunk->size; + + /* Move the blocks of chunk to the end of the + LRU list and try to flush them. */ + for (; block < bend; block++) { + switch (buf_block_get_state(block)) { + case BUF_BLOCK_NOT_USED: + continue; + case BUF_BLOCK_FILE_PAGE: + break; + default: + nonfree++; + continue; + } + + mutex_enter(&block->mutex); + /* The following calls will temporarily + release block->mutex and buf_pool_mutex. + Therefore, we have to always retry, + even if !dirty && !nonfree. */ + + if (!buf_flush_ready_for_replace(&block->page)) { + + buf_LRU_make_block_old(&block->page); + dirty++; + } else if (buf_LRU_free_block(&block->page, TRUE, NULL, FALSE) + != BUF_LRU_FREED) { + nonfree++; + } + + mutex_exit(&block->mutex); + } + + //buf_pool_mutex_exit(); + mutex_exit(&LRU_list_mutex); + + /* Request for a flush of the chunk if it helps. + Do not flush if there are non-free blocks, since + flushing will not make the chunk freeable. */ + if (nonfree) { + /* Avoid busy-waiting. */ + os_thread_sleep(100000); + } else if (dirty + && buf_flush_batch(BUF_FLUSH_LRU, dirty, 0) + == ULINT_UNDEFINED) { + + buf_flush_wait_batch_end(BUF_FLUSH_LRU); + } + + goto try_again; + } + + max_size = max_free_size; + max_chunk = max_free_chunk; + + srv_buf_pool_old_size = srv_buf_pool_size; + + /* Rewrite buf_pool->chunks. Copy everything but max_chunk. */ + chunks = mem_alloc((buf_pool->n_chunks - 1) * sizeof *chunks); + memcpy(chunks, buf_pool->chunks, + (max_chunk - buf_pool->chunks) * sizeof *chunks); + memcpy(chunks + (max_chunk - buf_pool->chunks), + max_chunk + 1, + buf_pool->chunks + buf_pool->n_chunks + - (max_chunk + 1)); + ut_a(buf_pool->curr_size > max_chunk->size); + buf_pool->curr_size -= max_chunk->size; + srv_buf_pool_curr_size = buf_pool->curr_size * UNIV_PAGE_SIZE; + chunk_size -= max_chunk->size; + buf_chunk_free(max_chunk); + mem_free(buf_pool->chunks); + buf_pool->chunks = chunks; + buf_pool->n_chunks--; + + /* Allow a slack of one megabyte. */ + if (chunk_size > 1048576 / UNIV_PAGE_SIZE) { + + goto shrink_again; + } + +func_done: + srv_buf_pool_old_size = srv_buf_pool_size; +func_exit: + //buf_pool_mutex_exit(); + mutex_exit(&LRU_list_mutex); + btr_search_enable(); +} + +/************************************************************************ +Rebuild buf_pool->page_hash. */ +static +void +buf_pool_page_hash_rebuild(void) +/*============================*/ +{ + ulint i; + ulint n_chunks; + buf_chunk_t* chunk; + hash_table_t* page_hash; + hash_table_t* zip_hash; + buf_page_t* b; + + //buf_pool_mutex_enter(); + mutex_enter(&LRU_list_mutex); + rw_lock_x_lock(&page_hash_latch); + mutex_enter(&flush_list_mutex); + + + /* Free, create, and populate the hash table. */ + hash_table_free(buf_pool->page_hash); + buf_pool->page_hash = page_hash = hash_create(2 * buf_pool->curr_size); + zip_hash = hash_create(2 * buf_pool->curr_size); + + HASH_MIGRATE(buf_pool->zip_hash, zip_hash, buf_page_t, hash, + BUF_POOL_ZIP_FOLD_BPAGE); + + hash_table_free(buf_pool->zip_hash); + buf_pool->zip_hash = zip_hash; + + /* Insert the uncompressed file pages to buf_pool->page_hash. */ + + chunk = buf_pool->chunks; + n_chunks = buf_pool->n_chunks; + + for (i = 0; i < n_chunks; i++, chunk++) { + ulint j; + buf_block_t* block = chunk->blocks; + + for (j = 0; j < chunk->size; j++, block++) { + if (buf_block_get_state(block) + == BUF_BLOCK_FILE_PAGE) { + ut_ad(!block->page.in_zip_hash); + ut_ad(block->page.in_page_hash); + + HASH_INSERT(buf_page_t, hash, page_hash, + buf_page_address_fold( + block->page.space, + block->page.offset), + &block->page); + } + } + } + + /* Insert the compressed-only pages to buf_pool->page_hash. + All such blocks are either in buf_pool->zip_clean or + in buf_pool->flush_list. */ + + for (b = UT_LIST_GET_FIRST(buf_pool->zip_clean); b; + b = UT_LIST_GET_NEXT(zip_list, b)) { + ut_a(buf_page_get_state(b) == BUF_BLOCK_ZIP_PAGE); + ut_ad(!b->in_flush_list); + ut_ad(b->in_LRU_list); + ut_ad(b->in_page_hash); + ut_ad(!b->in_zip_hash); + + HASH_INSERT(buf_page_t, hash, page_hash, + buf_page_address_fold(b->space, b->offset), b); + } + + for (b = UT_LIST_GET_FIRST(buf_pool->flush_list); b; + b = UT_LIST_GET_NEXT(flush_list, b)) { + ut_ad(b->in_flush_list); + ut_ad(b->in_LRU_list); + ut_ad(b->in_page_hash); + ut_ad(!b->in_zip_hash); + + switch (buf_page_get_state(b)) { + case BUF_BLOCK_ZIP_DIRTY: + HASH_INSERT(buf_page_t, hash, page_hash, + buf_page_address_fold(b->space, + b->offset), b); + break; + case BUF_BLOCK_FILE_PAGE: + /* uncompressed page */ + break; + case BUF_BLOCK_ZIP_FREE: + case BUF_BLOCK_ZIP_PAGE: + case BUF_BLOCK_NOT_USED: + case BUF_BLOCK_READY_FOR_USE: + case BUF_BLOCK_MEMORY: + case BUF_BLOCK_REMOVE_HASH: + ut_error; + break; + } + } + + //buf_pool_mutex_exit(); + mutex_exit(&LRU_list_mutex); + rw_lock_x_unlock(&page_hash_latch); + mutex_exit(&flush_list_mutex); +} + +/************************************************************************ +Resizes the buffer pool. */ +UNIV_INTERN +void +buf_pool_resize(void) +/*=================*/ +{ + //buf_pool_mutex_enter(); + mutex_enter(&LRU_list_mutex); + + if (srv_buf_pool_old_size == srv_buf_pool_size) { + + //buf_pool_mutex_exit(); + mutex_exit(&LRU_list_mutex); + return; + } + + if (srv_buf_pool_curr_size + 1048576 > srv_buf_pool_size) { + + //buf_pool_mutex_exit(); + mutex_exit(&LRU_list_mutex); + + /* Disable adaptive hash indexes and empty the index + in order to free up memory in the buffer pool chunks. */ + buf_pool_shrink((srv_buf_pool_curr_size - srv_buf_pool_size) + / UNIV_PAGE_SIZE); + } else if (srv_buf_pool_curr_size + 1048576 < srv_buf_pool_size) { + + /* Enlarge the buffer pool by at least one megabyte */ + + ulint mem_size + = srv_buf_pool_size - srv_buf_pool_curr_size; + buf_chunk_t* chunks; + buf_chunk_t* chunk; + + chunks = mem_alloc((buf_pool->n_chunks + 1) * sizeof *chunks); + + memcpy(chunks, buf_pool->chunks, buf_pool->n_chunks + * sizeof *chunks); + + chunk = &chunks[buf_pool->n_chunks]; + + if (!buf_chunk_init(chunk, mem_size)) { + mem_free(chunks); + } else { + buf_pool->curr_size += chunk->size; + srv_buf_pool_curr_size = buf_pool->curr_size + * UNIV_PAGE_SIZE; + mem_free(buf_pool->chunks); + buf_pool->chunks = chunks; + buf_pool->n_chunks++; + } + + srv_buf_pool_old_size = srv_buf_pool_size; + //buf_pool_mutex_exit(); + mutex_exit(&LRU_list_mutex); + } + + buf_pool_page_hash_rebuild(); +} + +/************************************************************************ +Moves to the block to the start of the LRU list if there is a danger +that the block would drift out of the buffer pool. */ +UNIV_INLINE +void +buf_block_make_young( +/*=================*/ + buf_page_t* bpage) /* in: block to make younger */ +{ + ut_ad(!buf_pool_mutex_own()); + + /* Note that we read freed_page_clock's without holding any mutex: + this is allowed since the result is used only in heuristics */ + + if (buf_page_peek_if_too_old(bpage)) { + + //buf_pool_mutex_enter(); + mutex_enter(&LRU_list_mutex); + /* There has been freeing activity in the LRU list: + best to move to the head of the LRU list */ + + buf_LRU_make_block_young(bpage); + //buf_pool_mutex_exit(); + mutex_exit(&LRU_list_mutex); + } +} + +/************************************************************************ +Moves a page to the start of the buffer pool LRU list. This high-level +function can be used to prevent an important page from from slipping out of +the buffer pool. */ +UNIV_INTERN +void +buf_page_make_young( +/*================*/ + buf_page_t* bpage) /* in: buffer block of a file page */ +{ + //buf_pool_mutex_enter(); + mutex_enter(&LRU_list_mutex); + + ut_a(buf_page_in_file(bpage)); + + buf_LRU_make_block_young(bpage); + + //buf_pool_mutex_exit(); + mutex_exit(&LRU_list_mutex); +} + +/************************************************************************ +Resets the check_index_page_at_flush field of a page if found in the buffer +pool. */ +UNIV_INTERN +void +buf_reset_check_index_page_at_flush( +/*================================*/ + ulint space, /* in: space id */ + ulint offset) /* in: page number */ +{ + buf_block_t* block; + + //buf_pool_mutex_enter(); + rw_lock_s_lock(&page_hash_latch); + + block = (buf_block_t*) buf_page_hash_get(space, offset); + + if (block && buf_block_get_state(block) == BUF_BLOCK_FILE_PAGE) { + block->check_index_page_at_flush = FALSE; + } + + //buf_pool_mutex_exit(); + rw_lock_s_unlock(&page_hash_latch); +} + +/************************************************************************ +Returns the current state of is_hashed of a page. FALSE if the page is +not in the pool. NOTE that this operation does not fix the page in the +pool if it is found there. */ +UNIV_INTERN +ibool +buf_page_peek_if_search_hashed( +/*===========================*/ + /* out: TRUE if page hash index is built in search + system */ + ulint space, /* in: space id */ + ulint offset) /* in: page number */ +{ + buf_block_t* block; + ibool is_hashed; + + //buf_pool_mutex_enter(); + rw_lock_s_lock(&page_hash_latch); + + block = (buf_block_t*) buf_page_hash_get(space, offset); + + if (!block || buf_block_get_state(block) != BUF_BLOCK_FILE_PAGE) { + is_hashed = FALSE; + } else { + is_hashed = block->is_hashed; + } + + //buf_pool_mutex_exit(); + rw_lock_s_unlock(&page_hash_latch); + + return(is_hashed); +} + +#ifdef UNIV_DEBUG_FILE_ACCESSES +/************************************************************************ +Sets file_page_was_freed TRUE if the page is found in the buffer pool. +This function should be called when we free a file page and want the +debug version to check that it is not accessed any more unless +reallocated. */ +UNIV_INTERN +buf_page_t* +buf_page_set_file_page_was_freed( +/*=============================*/ + /* out: control block if found in page hash table, + otherwise NULL */ + ulint space, /* in: space id */ + ulint offset) /* in: page number */ +{ + buf_page_t* bpage; + + //buf_pool_mutex_enter(); + rw_lock_s_lock(&page_hash_latch); + + bpage = buf_page_hash_get(space, offset); + + if (bpage) { + bpage->file_page_was_freed = TRUE; + } + + //buf_pool_mutex_exit(); + rw_lock_s_unlock(&page_hash_latch); + + return(bpage); +} + +/************************************************************************ +Sets file_page_was_freed FALSE if the page is found in the buffer pool. +This function should be called when we free a file page and want the +debug version to check that it is not accessed any more unless +reallocated. */ +UNIV_INTERN +buf_page_t* +buf_page_reset_file_page_was_freed( +/*===============================*/ + /* out: control block if found in page hash table, + otherwise NULL */ + ulint space, /* in: space id */ + ulint offset) /* in: page number */ +{ + buf_page_t* bpage; + + //buf_pool_mutex_enter(); + rw_lock_s_lock(&page_hash_latch); + + bpage = buf_page_hash_get(space, offset); + + if (bpage) { + bpage->file_page_was_freed = FALSE; + } + + //buf_pool_mutex_exit(); + rw_lock_s_unlock(&page_hash_latch); + + return(bpage); +} +#endif /* UNIV_DEBUG_FILE_ACCESSES */ + +/************************************************************************ +Get read access to a compressed page (usually of type +FIL_PAGE_TYPE_ZBLOB or FIL_PAGE_TYPE_ZBLOB2). +The page must be released with buf_page_release_zip(). +NOTE: the page is not protected by any latch. Mutual exclusion has to +be implemented at a higher level. In other words, all possible +accesses to a given page through this function must be protected by +the same set of mutexes or latches. */ +UNIV_INTERN +buf_page_t* +buf_page_get_zip( +/*=============*/ + /* out: pointer to the block */ + ulint space, /* in: space id */ + ulint zip_size,/* in: compressed page size */ + ulint offset) /* in: page number */ +{ + buf_page_t* bpage; + mutex_t* block_mutex; + ibool must_read; + +#ifndef UNIV_LOG_DEBUG + ut_ad(!ibuf_inside()); +#endif + buf_pool->n_page_gets++; + + for (;;) { + //buf_pool_mutex_enter(); +lookup: + rw_lock_s_lock(&page_hash_latch); + bpage = buf_page_hash_get(space, offset); + if (bpage) { + break; + } + + /* Page not in buf_pool: needs to be read from file */ + + //buf_pool_mutex_exit(); + rw_lock_s_unlock(&page_hash_latch); + + buf_read_page(space, zip_size, offset); + +#if defined UNIV_DEBUG || defined UNIV_BUF_DEBUG + ut_a(++buf_dbg_counter % 37 || buf_validate()); +#endif /* UNIV_DEBUG || UNIV_BUF_DEBUG */ + } + + if (UNIV_UNLIKELY(!bpage->zip.data)) { + /* There is no compressed page. */ + //buf_pool_mutex_exit(); + rw_lock_s_unlock(&page_hash_latch); + return(NULL); + } + + block_mutex = buf_page_get_mutex(bpage); +retry_lock: + mutex_enter(block_mutex); + if (block_mutex != buf_page_get_mutex(bpage)) { + mutex_exit(block_mutex); + block_mutex = buf_page_get_mutex(bpage); + goto retry_lock; + } + + rw_lock_s_unlock(&page_hash_latch); + + switch (buf_page_get_state(bpage)) { + case BUF_BLOCK_NOT_USED: + case BUF_BLOCK_READY_FOR_USE: + case BUF_BLOCK_MEMORY: + case BUF_BLOCK_REMOVE_HASH: + case BUF_BLOCK_ZIP_FREE: + ut_error; + break; + case BUF_BLOCK_ZIP_PAGE: + case BUF_BLOCK_ZIP_DIRTY: + bpage->buf_fix_count++; + break; + case BUF_BLOCK_FILE_PAGE: + /* Discard the uncompressed page frame if possible. */ + if (buf_LRU_free_block(bpage, FALSE, NULL, FALSE) + == BUF_LRU_FREED) { + + mutex_exit(block_mutex); + goto lookup; + } + + buf_block_buf_fix_inc((buf_block_t*) bpage, + __FILE__, __LINE__); + break; + } + + must_read = buf_page_get_io_fix(bpage) == BUF_IO_READ; + + //buf_pool_mutex_exit(); + + buf_page_set_accessed(bpage, TRUE); + + mutex_exit(block_mutex); + + buf_block_make_young(bpage); + +#ifdef UNIV_DEBUG_FILE_ACCESSES + ut_a(!bpage->file_page_was_freed); +#endif + +#if defined UNIV_DEBUG || defined UNIV_BUF_DEBUG + ut_a(++buf_dbg_counter % 5771 || buf_validate()); + ut_a(bpage->buf_fix_count > 0); + ut_a(buf_page_in_file(bpage)); +#endif /* UNIV_DEBUG || UNIV_BUF_DEBUG */ + + if (must_read) { + /* Let us wait until the read operation + completes */ + + for (;;) { + enum buf_io_fix io_fix; + + mutex_enter(block_mutex); + io_fix = buf_page_get_io_fix(bpage); + mutex_exit(block_mutex); + + if (io_fix == BUF_IO_READ) { + + os_thread_sleep(WAIT_FOR_READ); + } else { + break; + } + } + } + +#ifdef UNIV_IBUF_COUNT_DEBUG + ut_a(ibuf_count_get(buf_page_get_space(bpage), + buf_page_get_page_no(bpage)) == 0); +#endif + return(bpage); +} + +/************************************************************************ +Initialize some fields of a control block. */ +UNIV_INLINE +void +buf_block_init_low( +/*===============*/ + buf_block_t* block) /* in: block to init */ +{ + block->check_index_page_at_flush = FALSE; + block->index = NULL; + + block->n_hash_helps = 0; + block->is_hashed = FALSE; + block->n_fields = 1; + block->n_bytes = 0; + block->left_side = TRUE; +} + +/************************************************************************ +Decompress a block. */ +static +ibool +buf_zip_decompress( +/*===============*/ + /* out: TRUE if successful */ + buf_block_t* block, /* in/out: block */ + ibool check) /* in: TRUE=verify the page checksum */ +{ + const byte* frame = block->page.zip.data; + + ut_ad(buf_block_get_zip_size(block)); + ut_a(buf_block_get_space(block) != 0); + + if (UNIV_LIKELY(check)) { + ulint stamp_checksum = mach_read_from_4( + frame + FIL_PAGE_SPACE_OR_CHKSUM); + ulint calc_checksum = page_zip_calc_checksum( + frame, page_zip_get_size(&block->page.zip)); + + if (UNIV_UNLIKELY(stamp_checksum != calc_checksum)) { + ut_print_timestamp(stderr); + fprintf(stderr, + " InnoDB: compressed page checksum mismatch" + " (space %u page %u): %lu != %lu\n", + block->page.space, block->page.offset, + stamp_checksum, calc_checksum); + return(FALSE); + } + } + + switch (fil_page_get_type(frame)) { + case FIL_PAGE_INDEX: + if (page_zip_decompress(&block->page.zip, + block->frame)) { + return(TRUE); + } + + fprintf(stderr, + "InnoDB: unable to decompress space %lu page %lu\n", + (ulong) block->page.space, + (ulong) block->page.offset); + return(FALSE); + + case FIL_PAGE_TYPE_ALLOCATED: + case FIL_PAGE_INODE: + case FIL_PAGE_IBUF_BITMAP: + case FIL_PAGE_TYPE_FSP_HDR: + case FIL_PAGE_TYPE_XDES: + case FIL_PAGE_TYPE_ZBLOB: + case FIL_PAGE_TYPE_ZBLOB2: + /* Copy to uncompressed storage. */ + memcpy(block->frame, frame, + buf_block_get_zip_size(block)); + return(TRUE); + } + + ut_print_timestamp(stderr); + fprintf(stderr, + " InnoDB: unknown compressed page" + " type %lu\n", + fil_page_get_type(frame)); + return(FALSE); +} + +/*********************************************************************** +Gets the block to whose frame the pointer is pointing to. */ +UNIV_INTERN +buf_block_t* +buf_block_align( +/*============*/ + /* out: pointer to block, never NULL */ + const byte* ptr) /* in: pointer to a frame */ +{ + buf_chunk_t* chunk; + ulint i; + + /* TODO: protect buf_pool->chunks with a mutex (it will + currently remain constant after buf_pool_init()) */ + for (chunk = buf_pool->chunks, i = buf_pool->n_chunks; i--; chunk++) { + lint offs = ptr - chunk->blocks->frame; + + if (UNIV_UNLIKELY(offs < 0)) { + + continue; + } + + offs >>= UNIV_PAGE_SIZE_SHIFT; + + if (UNIV_LIKELY((ulint) offs < chunk->size)) { + buf_block_t* block = &chunk->blocks[offs]; + + /* The function buf_chunk_init() invokes + buf_block_init() so that block[n].frame == + block->frame + n * UNIV_PAGE_SIZE. Check it. */ + ut_ad(block->frame == page_align(ptr)); +#ifdef UNIV_DEBUG + /* A thread that updates these fields must + hold buf_pool_mutex and block->mutex. Acquire + only the latter. */ + mutex_enter(&block->mutex); + + switch (buf_block_get_state(block)) { + case BUF_BLOCK_ZIP_FREE: + case BUF_BLOCK_ZIP_PAGE: + case BUF_BLOCK_ZIP_DIRTY: + /* These types should only be used in + the compressed buffer pool, whose + memory is allocated from + buf_pool->chunks, in UNIV_PAGE_SIZE + blocks flagged as BUF_BLOCK_MEMORY. */ + ut_error; + break; + case BUF_BLOCK_NOT_USED: + case BUF_BLOCK_READY_FOR_USE: + case BUF_BLOCK_MEMORY: + /* Some data structures contain + "guess" pointers to file pages. The + file pages may have been freed and + reused. Do not complain. */ + break; + case BUF_BLOCK_REMOVE_HASH: + /* buf_LRU_block_remove_hashed_page() + will overwrite the FIL_PAGE_OFFSET and + FIL_PAGE_ARCH_LOG_NO_OR_SPACE_ID with + 0xff and set the state to + BUF_BLOCK_REMOVE_HASH. */ + ut_ad(page_get_space_id(page_align(ptr)) + == 0xffffffff); + ut_ad(page_get_page_no(page_align(ptr)) + == 0xffffffff); + break; + case BUF_BLOCK_FILE_PAGE: + ut_ad(block->page.space + == page_get_space_id(page_align(ptr))); + ut_ad(block->page.offset + == page_get_page_no(page_align(ptr))); + break; + } + + mutex_exit(&block->mutex); +#endif /* UNIV_DEBUG */ + + return(block); + } + } + + /* The block should always be found. */ + ut_error; + return(NULL); +} + +/************************************************************************ +Find out if a buffer block was created by buf_chunk_init(). */ +static +ibool +buf_block_is_uncompressed( +/*======================*/ + /* out: TRUE if "block" has + been added to buf_pool->free + by buf_chunk_init() */ + const buf_block_t* block) /* in: pointer to block, + not dereferenced */ +{ + const buf_chunk_t* chunk = buf_pool->chunks; + const buf_chunk_t* const echunk = chunk + buf_pool->n_chunks; + + //ut_ad(buf_pool_mutex_own()); + + if (UNIV_UNLIKELY((((ulint) block) % sizeof *block) != 0)) { + /* The pointer should be aligned. */ + return(FALSE); + } + + while (chunk < echunk) { + if (block >= chunk->blocks + && block < chunk->blocks + chunk->size) { + + return(TRUE); + } + + chunk++; + } + + return(FALSE); +} + +/************************************************************************ +This is the general function used to get access to a database page. */ +UNIV_INTERN +buf_block_t* +buf_page_get_gen( +/*=============*/ + /* out: pointer to the block or NULL */ + ulint space, /* in: space id */ + ulint zip_size,/* in: compressed page size in bytes + or 0 for uncompressed pages */ + ulint offset, /* in: page number */ + ulint rw_latch,/* in: RW_S_LATCH, RW_X_LATCH, RW_NO_LATCH */ + buf_block_t* guess, /* in: guessed block or NULL */ + ulint mode, /* in: BUF_GET, BUF_GET_IF_IN_POOL, + BUF_GET_NO_LATCH */ + const char* file, /* in: file name */ + ulint line, /* in: line where called */ + mtr_t* mtr) /* in: mini-transaction */ +{ + buf_block_t* block; + ibool accessed; + ulint fix_type; + ibool must_read; + mutex_t* block_mutex; + + ut_ad(mtr); + ut_ad((rw_latch == RW_S_LATCH) + || (rw_latch == RW_X_LATCH) + || (rw_latch == RW_NO_LATCH)); + ut_ad((mode != BUF_GET_NO_LATCH) || (rw_latch == RW_NO_LATCH)); + ut_ad((mode == BUF_GET) || (mode == BUF_GET_IF_IN_POOL) + || (mode == BUF_GET_NO_LATCH)); + ut_ad(zip_size == fil_space_get_zip_size(space)); +#ifndef UNIV_LOG_DEBUG + ut_ad(!ibuf_inside() || ibuf_page(space, zip_size, offset, NULL)); +#endif + buf_pool->n_page_gets++; +loop: + block = guess; + //buf_pool_mutex_enter(); + + if (block) { + block_mutex = buf_page_get_mutex((buf_page_t*)block); +retry_lock_1: + mutex_enter(block_mutex); + if (block_mutex != buf_page_get_mutex((buf_page_t*)block)) { + mutex_exit(block_mutex); + block_mutex = buf_page_get_mutex((buf_page_t*)block); + goto retry_lock_1; + } + + /* If the guess is a compressed page descriptor that + has been allocated by buf_buddy_alloc(), it may have + been invalidated by buf_buddy_relocate(). In that + case, block could point to something that happens to + contain the expected bits in block->page. Similarly, + the guess may be pointing to a buffer pool chunk that + has been released when resizing the buffer pool. */ + + if (!buf_block_is_uncompressed(block) + || offset != block->page.offset + || space != block->page.space + || buf_block_get_state(block) != BUF_BLOCK_FILE_PAGE) { + + mutex_exit(block_mutex); + + block = guess = NULL; + } else { + ut_ad(!block->page.in_zip_hash); + ut_ad(block->page.in_page_hash); + } + } + + if (block == NULL) { + rw_lock_s_lock(&page_hash_latch); + block = (buf_block_t*) buf_page_hash_get(space, offset); + if (block) { + block_mutex = buf_page_get_mutex((buf_page_t*)block); +retry_lock_2: + mutex_enter(block_mutex); + if (block_mutex != buf_page_get_mutex((buf_page_t*)block)) { + mutex_exit(block_mutex); + block_mutex = buf_page_get_mutex((buf_page_t*)block); + goto retry_lock_2; + } + } + rw_lock_s_unlock(&page_hash_latch); + } + +loop2: + if (block == NULL) { + /* Page not in buf_pool: needs to be read from file */ + + //buf_pool_mutex_exit(); + + if (mode == BUF_GET_IF_IN_POOL) { + + return(NULL); + } + + buf_read_page(space, zip_size, offset); + +#if defined UNIV_DEBUG || defined UNIV_BUF_DEBUG + ut_a(++buf_dbg_counter % 37 || buf_validate()); +#endif /* UNIV_DEBUG || UNIV_BUF_DEBUG */ + goto loop; + } + + ut_ad(page_zip_get_size(&block->page.zip) == zip_size); + + must_read = buf_block_get_io_fix(block) == BUF_IO_READ; + + if (must_read && mode == BUF_GET_IF_IN_POOL) { + /* The page is only being read to buffer */ + //buf_pool_mutex_exit(); + mutex_exit(block_mutex); + + return(NULL); + } + + switch (buf_block_get_state(block)) { + buf_page_t* bpage; + ibool success; + + case BUF_BLOCK_FILE_PAGE: + if (block_mutex == &buf_pool_zip_mutex) { + /* it is wrong mutex... */ + mutex_exit(block_mutex); + goto loop; + } + break; + + case BUF_BLOCK_ZIP_PAGE: + case BUF_BLOCK_ZIP_DIRTY: + ut_ad(block_mutex == &buf_pool_zip_mutex); + bpage = &block->page; + + if (bpage->buf_fix_count + || buf_page_get_io_fix(bpage) != BUF_IO_NONE) { + /* This condition often occurs when the buffer + is not buffer-fixed, but I/O-fixed by + buf_page_init_for_read(). */ +wait_until_unfixed: + /* The block is buffer-fixed or I/O-fixed. + Try again later. */ + //buf_pool_mutex_exit(); + mutex_exit(block_mutex); + os_thread_sleep(WAIT_FOR_READ); + + goto loop; + } + + /* Allocate an uncompressed page. */ + //buf_pool_mutex_exit(); + mutex_exit(block_mutex); + + block = buf_LRU_get_free_block(0); + ut_a(block); + block_mutex = &block->mutex; + + //buf_pool_mutex_enter(); + mutex_enter(&LRU_list_mutex); + rw_lock_x_lock(&page_hash_latch); + mutex_enter(block_mutex); + + { + buf_page_t* hash_bpage + = buf_page_hash_get(space, offset); + + if (UNIV_UNLIKELY(bpage != hash_bpage)) { + /* The buf_pool->page_hash was modified + while buf_pool_mutex was released. + Free the block that was allocated. */ + + buf_LRU_block_free_non_file_page(block, TRUE); + mutex_exit(block_mutex); + + block = (buf_block_t*) hash_bpage; + if (block) { + block_mutex = buf_page_get_mutex((buf_page_t*)block); +retry_lock_3: + mutex_enter(block_mutex); + if (block_mutex != buf_page_get_mutex((buf_page_t*)block)) { + mutex_exit(block_mutex); + block_mutex = buf_page_get_mutex((buf_page_t*)block); + goto retry_lock_3; + } + } + rw_lock_x_unlock(&page_hash_latch); + mutex_exit(&LRU_list_mutex); + goto loop2; + } + } + + mutex_enter(&buf_pool_zip_mutex); + + if (UNIV_UNLIKELY + (bpage->buf_fix_count + || buf_page_get_io_fix(bpage) != BUF_IO_NONE)) { + + mutex_exit(&buf_pool_zip_mutex); + /* The block was buffer-fixed or I/O-fixed + while buf_pool_mutex was not held by this thread. + Free the block that was allocated and try again. + This should be extremely unlikely. */ + + buf_LRU_block_free_non_file_page(block, TRUE); + //mutex_exit(&block->mutex); + + rw_lock_x_unlock(&page_hash_latch); + mutex_exit(&LRU_list_mutex); + goto wait_until_unfixed; + } + + /* Move the compressed page from bpage to block, + and uncompress it. */ + + mutex_enter(&flush_list_mutex); + + buf_relocate(bpage, &block->page); + + rw_lock_x_unlock(&page_hash_latch); + + buf_block_init_low(block); + block->lock_hash_val = lock_rec_hash(space, offset); + + UNIV_MEM_DESC(&block->page.zip.data, + page_zip_get_size(&block->page.zip), block); + + if (buf_page_get_state(&block->page) + == BUF_BLOCK_ZIP_PAGE) { + UT_LIST_REMOVE(zip_list, buf_pool->zip_clean, + &block->page); + ut_ad(!block->page.in_flush_list); + } else { + /* Relocate buf_pool->flush_list. */ + buf_page_t* b; + + b = UT_LIST_GET_PREV(flush_list, &block->page); + ut_ad(block->page.in_flush_list); + UT_LIST_REMOVE(flush_list, buf_pool->flush_list, + &block->page); + + if (b) { + UT_LIST_INSERT_AFTER( + flush_list, buf_pool->flush_list, b, + &block->page); + } else { + UT_LIST_ADD_FIRST( + flush_list, buf_pool->flush_list, + &block->page); + } + } + + mutex_exit(&flush_list_mutex); + + /* Buffer-fix, I/O-fix, and X-latch the block + for the duration of the decompression. + Also add the block to the unzip_LRU list. */ + block->page.state = BUF_BLOCK_FILE_PAGE; + + /* Insert at the front of unzip_LRU list */ + buf_unzip_LRU_add_block(block, FALSE); + + mutex_exit(&LRU_list_mutex); + + block->page.buf_fix_count = 1; + buf_block_set_io_fix(block, BUF_IO_READ); + + mutex_enter(&buf_pool_mutex); + buf_pool->n_pend_unzip++; + mutex_exit(&buf_pool_mutex); + + rw_lock_x_lock(&block->lock); + mutex_exit(block_mutex); + mutex_exit(&buf_pool_zip_mutex); + + buf_buddy_free(bpage, sizeof *bpage, FALSE); + + //buf_pool_mutex_exit(); + + /* Decompress the page and apply buffered operations + while not holding buf_pool_mutex or block->mutex. */ + success = buf_zip_decompress(block, srv_use_checksums); + + if (UNIV_LIKELY(success)) { + ibuf_merge_or_delete_for_page(block, space, offset, + zip_size, TRUE); + } + + /* Unfix and unlatch the block. */ + //buf_pool_mutex_enter(); + block_mutex = &block->mutex; + mutex_enter(block_mutex); + mutex_enter(&buf_pool_mutex); + buf_pool->n_pend_unzip--; + mutex_exit(&buf_pool_mutex); + block->page.buf_fix_count--; + buf_block_set_io_fix(block, BUF_IO_NONE); + //mutex_exit(&block->mutex); + rw_lock_x_unlock(&block->lock); + + if (UNIV_UNLIKELY(!success)) { + + //buf_pool_mutex_exit(); + mutex_exit(block_mutex); + return(NULL); + } + + break; + + case BUF_BLOCK_ZIP_FREE: + case BUF_BLOCK_NOT_USED: + case BUF_BLOCK_READY_FOR_USE: + case BUF_BLOCK_MEMORY: + case BUF_BLOCK_REMOVE_HASH: + ut_error; + break; + } + + ut_ad(buf_block_get_state(block) == BUF_BLOCK_FILE_PAGE); + + //mutex_enter(&block->mutex); + UNIV_MEM_ASSERT_RW(&block->page, sizeof block->page); + + buf_block_buf_fix_inc(block, file, line); + //buf_pool_mutex_exit(); + + /* Check if this is the first access to the page */ + + accessed = buf_page_is_accessed(&block->page); + + buf_page_set_accessed(&block->page, TRUE); + + mutex_exit(block_mutex); + + buf_block_make_young(&block->page); + +#ifdef UNIV_DEBUG_FILE_ACCESSES + ut_a(!block->page.file_page_was_freed); +#endif + +#if defined UNIV_DEBUG || defined UNIV_BUF_DEBUG + ut_a(++buf_dbg_counter % 5771 || buf_validate()); + ut_a(block->page.buf_fix_count > 0); + ut_a(buf_block_get_state(block) == BUF_BLOCK_FILE_PAGE); +#endif /* UNIV_DEBUG || UNIV_BUF_DEBUG */ + + switch (rw_latch) { + case RW_NO_LATCH: + if (must_read) { + /* Let us wait until the read operation + completes */ + + for (;;) { + enum buf_io_fix io_fix; + + mutex_enter(&block->mutex); + io_fix = buf_block_get_io_fix(block); + mutex_exit(&block->mutex); + + if (io_fix == BUF_IO_READ) { + + os_thread_sleep(WAIT_FOR_READ); + } else { + break; + } + } + } + + fix_type = MTR_MEMO_BUF_FIX; + break; + + case RW_S_LATCH: + rw_lock_s_lock_func(&(block->lock), 0, file, line); + + fix_type = MTR_MEMO_PAGE_S_FIX; + break; + + default: + ut_ad(rw_latch == RW_X_LATCH); + rw_lock_x_lock_func(&(block->lock), 0, file, line); + + fix_type = MTR_MEMO_PAGE_X_FIX; + break; + } + + mtr_memo_push(mtr, block, fix_type); + + if (!accessed) { + /* In the case of a first access, try to apply linear + read-ahead */ + + buf_read_ahead_linear(space, zip_size, offset); + } + +#ifdef UNIV_IBUF_COUNT_DEBUG + ut_a(ibuf_count_get(buf_block_get_space(block), + buf_block_get_page_no(block)) == 0); +#endif + return(block); +} + +/************************************************************************ +This is the general function used to get optimistic access to a database +page. */ +UNIV_INTERN +ibool +buf_page_optimistic_get_func( +/*=========================*/ + /* out: TRUE if success */ + ulint rw_latch,/* in: RW_S_LATCH, RW_X_LATCH */ + buf_block_t* block, /* in: guessed buffer block */ + ib_uint64_t modify_clock,/* in: modify clock value if mode is + ..._GUESS_ON_CLOCK */ + const char* file, /* in: file name */ + ulint line, /* in: line where called */ + mtr_t* mtr) /* in: mini-transaction */ +{ + ibool accessed; + ibool success; + ulint fix_type; + + ut_ad(mtr && block); + ut_ad((rw_latch == RW_S_LATCH) || (rw_latch == RW_X_LATCH)); + + mutex_enter(&block->mutex); + + if (UNIV_UNLIKELY(buf_block_get_state(block) != BUF_BLOCK_FILE_PAGE)) { + + mutex_exit(&block->mutex); + + return(FALSE); + } + + buf_block_buf_fix_inc(block, file, line); + accessed = buf_page_is_accessed(&block->page); + buf_page_set_accessed(&block->page, TRUE); + + mutex_exit(&block->mutex); + + buf_block_make_young(&block->page); + + /* Check if this is the first access to the page */ + + ut_ad(!ibuf_inside() + || ibuf_page(buf_block_get_space(block), + buf_block_get_zip_size(block), + buf_block_get_page_no(block), NULL)); + + if (rw_latch == RW_S_LATCH) { + success = rw_lock_s_lock_nowait(&(block->lock), + file, line); + fix_type = MTR_MEMO_PAGE_S_FIX; + } else { + success = rw_lock_x_lock_func_nowait(&(block->lock), + file, line); + fix_type = MTR_MEMO_PAGE_X_FIX; + } + + if (UNIV_UNLIKELY(!success)) { + mutex_enter(&block->mutex); + buf_block_buf_fix_dec(block); + mutex_exit(&block->mutex); + + return(FALSE); + } + + if (UNIV_UNLIKELY(modify_clock != block->modify_clock)) { + buf_block_dbg_add_level(block, SYNC_NO_ORDER_CHECK); + + if (rw_latch == RW_S_LATCH) { + rw_lock_s_unlock(&(block->lock)); + } else { + rw_lock_x_unlock(&(block->lock)); + } + + mutex_enter(&block->mutex); + buf_block_buf_fix_dec(block); + mutex_exit(&block->mutex); + + return(FALSE); + } + + mtr_memo_push(mtr, block, fix_type); + +#if defined UNIV_DEBUG || defined UNIV_BUF_DEBUG + ut_a(++buf_dbg_counter % 5771 || buf_validate()); + ut_a(block->page.buf_fix_count > 0); + ut_a(buf_block_get_state(block) == BUF_BLOCK_FILE_PAGE); +#endif /* UNIV_DEBUG || UNIV_BUF_DEBUG */ + +#ifdef UNIV_DEBUG_FILE_ACCESSES + ut_a(block->page.file_page_was_freed == FALSE); +#endif + if (UNIV_UNLIKELY(!accessed)) { + /* In the case of a first access, try to apply linear + read-ahead */ + + buf_read_ahead_linear(buf_block_get_space(block), + buf_block_get_zip_size(block), + buf_block_get_page_no(block)); + } + +#ifdef UNIV_IBUF_COUNT_DEBUG + ut_a(ibuf_count_get(buf_block_get_space(block), + buf_block_get_page_no(block)) == 0); +#endif + buf_pool->n_page_gets++; + + return(TRUE); +} + +/************************************************************************ +This is used to get access to a known database page, when no waiting can be +done. For example, if a search in an adaptive hash index leads us to this +frame. */ +UNIV_INTERN +ibool +buf_page_get_known_nowait( +/*======================*/ + /* out: TRUE if success */ + ulint rw_latch,/* in: RW_S_LATCH, RW_X_LATCH */ + buf_block_t* block, /* in: the known page */ + ulint mode, /* in: BUF_MAKE_YOUNG or BUF_KEEP_OLD */ + const char* file, /* in: file name */ + ulint line, /* in: line where called */ + mtr_t* mtr) /* in: mini-transaction */ +{ + ibool success; + ulint fix_type; + + ut_ad(mtr); + ut_ad((rw_latch == RW_S_LATCH) || (rw_latch == RW_X_LATCH)); + + mutex_enter(&block->mutex); + + if (buf_block_get_state(block) == BUF_BLOCK_REMOVE_HASH) { + /* Another thread is just freeing the block from the LRU list + of the buffer pool: do not try to access this page; this + attempt to access the page can only come through the hash + index because when the buffer block state is ..._REMOVE_HASH, + we have already removed it from the page address hash table + of the buffer pool. */ + + mutex_exit(&block->mutex); + + return(FALSE); + } + + ut_a(buf_block_get_state(block) == BUF_BLOCK_FILE_PAGE); + + buf_block_buf_fix_inc(block, file, line); + + mutex_exit(&block->mutex); + + if (mode == BUF_MAKE_YOUNG) { + buf_block_make_young(&block->page); + } + + ut_ad(!ibuf_inside() || (mode == BUF_KEEP_OLD)); + + if (rw_latch == RW_S_LATCH) { + success = rw_lock_s_lock_nowait(&(block->lock), + file, line); + fix_type = MTR_MEMO_PAGE_S_FIX; + } else { + success = rw_lock_x_lock_func_nowait(&(block->lock), + file, line); + fix_type = MTR_MEMO_PAGE_X_FIX; + } + + if (!success) { + mutex_enter(&block->mutex); + buf_block_buf_fix_dec(block); + mutex_exit(&block->mutex); + + return(FALSE); + } + + mtr_memo_push(mtr, block, fix_type); + +#if defined UNIV_DEBUG || defined UNIV_BUF_DEBUG + ut_a(++buf_dbg_counter % 5771 || buf_validate()); + ut_a(block->page.buf_fix_count > 0); + ut_a(buf_block_get_state(block) == BUF_BLOCK_FILE_PAGE); +#endif /* UNIV_DEBUG || UNIV_BUF_DEBUG */ +#ifdef UNIV_DEBUG_FILE_ACCESSES + ut_a(block->page.file_page_was_freed == FALSE); +#endif + +#ifdef UNIV_IBUF_COUNT_DEBUG + ut_a((mode == BUF_KEEP_OLD) + || (ibuf_count_get(buf_block_get_space(block), + buf_block_get_page_no(block)) == 0)); +#endif + buf_pool->n_page_gets++; + + return(TRUE); +} + +/*********************************************************************** +Given a tablespace id and page number tries to get that page. If the +page is not in the buffer pool it is not loaded and NULL is returned. +Suitable for using when holding the kernel mutex. */ +UNIV_INTERN +const buf_block_t* +buf_page_try_get_func( +/*==================*/ + /* out: pointer to a page or NULL */ + ulint space_id,/* in: tablespace id */ + ulint page_no,/* in: page number */ + const char* file, /* in: file name */ + ulint line, /* in: line where called */ + mtr_t* mtr) /* in: mini-transaction */ +{ + buf_block_t* block; + ibool success; + ulint fix_type; + + //buf_pool_mutex_enter(); + rw_lock_s_lock(&page_hash_latch); + block = buf_block_hash_get(space_id, page_no); + + if (!block) { + //buf_pool_mutex_exit(); + rw_lock_s_unlock(&page_hash_latch); + return(NULL); + } + + mutex_enter(&block->mutex); + //buf_pool_mutex_exit(); + rw_lock_s_unlock(&page_hash_latch); + +#if defined UNIV_DEBUG || defined UNIV_BUF_DEBUG + ut_a(buf_block_get_state(block) == BUF_BLOCK_FILE_PAGE); + ut_a(buf_block_get_space(block) == space_id); + ut_a(buf_block_get_page_no(block) == page_no); +#endif /* UNIV_DEBUG || UNIV_BUF_DEBUG */ + + buf_block_buf_fix_inc(block, file, line); + mutex_exit(&block->mutex); + + fix_type = MTR_MEMO_PAGE_S_FIX; + success = rw_lock_s_lock_nowait(&block->lock, file, line); + + if (!success) { + /* Let us try to get an X-latch. If the current thread + is holding an X-latch on the page, we cannot get an + S-latch. */ + + fix_type = MTR_MEMO_PAGE_X_FIX; + success = rw_lock_x_lock_func_nowait(&block->lock, + file, line); + } + + if (!success) { + mutex_enter(&block->mutex); + buf_block_buf_fix_dec(block); + mutex_exit(&block->mutex); + + return(NULL); + } + + mtr_memo_push(mtr, block, fix_type); +#if defined UNIV_DEBUG || defined UNIV_BUF_DEBUG + ut_a(++buf_dbg_counter % 5771 || buf_validate()); + ut_a(block->page.buf_fix_count > 0); + ut_a(buf_block_get_state(block) == BUF_BLOCK_FILE_PAGE); +#endif /* UNIV_DEBUG || UNIV_BUF_DEBUG */ +#ifdef UNIV_DEBUG_FILE_ACCESSES + ut_a(block->page.file_page_was_freed == FALSE); +#endif /* UNIV_DEBUG_FILE_ACCESSES */ + buf_block_dbg_add_level(block, SYNC_NO_ORDER_CHECK); + + buf_pool->n_page_gets++; + +#ifdef UNIV_IBUF_COUNT_DEBUG + ut_a(ibuf_count_get(buf_block_get_space(block), + buf_block_get_page_no(block)) == 0); +#endif + + return(block); +} + +/************************************************************************ +Initialize some fields of a control block. */ +UNIV_INLINE +void +buf_page_init_low( +/*==============*/ + buf_page_t* bpage) /* in: block to init */ +{ + bpage->flush_type = BUF_FLUSH_LRU; + bpage->accessed = FALSE; + bpage->io_fix = BUF_IO_NONE; + bpage->buf_fix_count = 0; + bpage->freed_page_clock = 0; + bpage->newest_modification = 0; + bpage->oldest_modification = 0; + HASH_INVALIDATE(bpage, hash); +#ifdef UNIV_DEBUG_FILE_ACCESSES + bpage->file_page_was_freed = FALSE; +#endif /* UNIV_DEBUG_FILE_ACCESSES */ +} + +#ifdef UNIV_HOTBACKUP +/************************************************************************ +Inits a page to the buffer buf_pool, for use in ibbackup --restore. */ +UNIV_INTERN +void +buf_page_init_for_backup_restore( +/*=============================*/ + ulint space, /* in: space id */ + ulint offset, /* in: offset of the page within space + in units of a page */ + ulint zip_size,/* in: compressed page size in bytes + or 0 for uncompressed pages */ + buf_block_t* block) /* in: block to init */ +{ + buf_block_init_low(block); + + block->lock_hash_val = 0; + + buf_page_init_low(&block->page); + block->page.state = BUF_BLOCK_FILE_PAGE; + block->page.space = space; + block->page.offset = offset; + + page_zip_des_init(&block->page.zip); + + /* We assume that block->page.data has been allocated + with zip_size == UNIV_PAGE_SIZE. */ + ut_ad(zip_size <= UNIV_PAGE_SIZE); + ut_ad(ut_is_2pow(zip_size)); + page_zip_set_size(&block->page.zip, zip_size); +} +#endif /* UNIV_HOTBACKUP */ + +/************************************************************************ +Inits a page to the buffer buf_pool. */ +static +void +buf_page_init( +/*==========*/ + ulint space, /* in: space id */ + ulint offset, /* in: offset of the page within space + in units of a page */ + buf_block_t* block) /* in: block to init */ +{ + buf_page_t* hash_page; + + //ut_ad(buf_pool_mutex_own()); +#ifdef UNIV_SYNC_DEBUG + ut_ad(rw_lock_own(&page_hash_latch, RW_LOCK_EX)); +#endif + ut_ad(mutex_own(&(block->mutex))); + ut_a(buf_block_get_state(block) != BUF_BLOCK_FILE_PAGE); + + /* Set the state of the block */ + buf_block_set_file_page(block, space, offset); + +#ifdef UNIV_DEBUG_VALGRIND + if (!space) { + /* Silence valid Valgrind warnings about uninitialized + data being written to data files. There are some unused + bytes on some pages that InnoDB does not initialize. */ + UNIV_MEM_VALID(block->frame, UNIV_PAGE_SIZE); + } +#endif /* UNIV_DEBUG_VALGRIND */ + + buf_block_init_low(block); + + block->lock_hash_val = lock_rec_hash(space, offset); + + /* Insert into the hash table of file pages */ + + hash_page = buf_page_hash_get(space, offset); + + if (UNIV_LIKELY_NULL(hash_page)) { + fprintf(stderr, + "InnoDB: Error: page %lu %lu already found" + " in the hash table: %p, %p\n", + (ulong) space, + (ulong) offset, + (const void*) hash_page, (const void*) block); +#if defined UNIV_DEBUG || defined UNIV_BUF_DEBUG + mutex_exit(&block->mutex); + //buf_pool_mutex_exit(); + rw_lock_x_unlock(&page_hash_latch); + buf_print(); + buf_LRU_print(); + buf_validate(); + buf_LRU_validate(); +#endif /* UNIV_DEBUG || UNIV_BUF_DEBUG */ + ut_error; + } + + buf_page_init_low(&block->page); + + ut_ad(!block->page.in_zip_hash); + ut_ad(!block->page.in_page_hash); + ut_d(block->page.in_page_hash = TRUE); + HASH_INSERT(buf_page_t, hash, buf_pool->page_hash, + buf_page_address_fold(space, offset), &block->page); +} + +/************************************************************************ +Function which inits a page for read to the buffer buf_pool. If the page is +(1) already in buf_pool, or +(2) if we specify to read only ibuf pages and the page is not an ibuf page, or +(3) if the space is deleted or being deleted, +then this function does nothing. +Sets the io_fix flag to BUF_IO_READ and sets a non-recursive exclusive lock +on the buffer frame. The io-handler must take care that the flag is cleared +and the lock released later. */ +UNIV_INTERN +buf_page_t* +buf_page_init_for_read( +/*===================*/ + /* out: pointer to the block or NULL */ + ulint* err, /* out: DB_SUCCESS or DB_TABLESPACE_DELETED */ + ulint mode, /* in: BUF_READ_IBUF_PAGES_ONLY, ... */ + ulint space, /* in: space id */ + ulint zip_size,/* in: compressed page size, or 0 */ + ibool unzip, /* in: TRUE=request uncompressed page */ + ib_int64_t tablespace_version,/* in: prevents reading from a wrong + version of the tablespace in case we have done + DISCARD + IMPORT */ + ulint offset) /* in: page number */ +{ + buf_block_t* block; + buf_page_t* bpage; + mtr_t mtr; + ibool lru = FALSE; + void* data; + + ut_ad(buf_pool); + + *err = DB_SUCCESS; + + if (mode == BUF_READ_IBUF_PAGES_ONLY) { + /* It is a read-ahead within an ibuf routine */ + + ut_ad(!ibuf_bitmap_page(zip_size, offset)); + ut_ad(ibuf_inside()); + + mtr_start(&mtr); + + if (!recv_no_ibuf_operations + && !ibuf_page(space, zip_size, offset, &mtr)) { + + mtr_commit(&mtr); + + return(NULL); + } + } else { + ut_ad(mode == BUF_READ_ANY_PAGE); + } + + if (zip_size && UNIV_LIKELY(!unzip) + && UNIV_LIKELY(!recv_recovery_is_on())) { + block = NULL; + } else { + block = buf_LRU_get_free_block(0); + ut_ad(block); + } + + //buf_pool_mutex_enter(); + mutex_enter(&LRU_list_mutex); + rw_lock_x_lock(&page_hash_latch); + + if (buf_page_hash_get(space, offset)) { + /* The page is already in the buffer pool. */ +err_exit: + if (block) { + mutex_enter(&block->mutex); + mutex_exit(&LRU_list_mutex); + rw_lock_x_unlock(&page_hash_latch); + buf_LRU_block_free_non_file_page(block, FALSE); + mutex_exit(&block->mutex); + } + else { + mutex_exit(&LRU_list_mutex); + rw_lock_x_unlock(&page_hash_latch); + } + + bpage = NULL; + goto func_exit; + } + + if (fil_tablespace_deleted_or_being_deleted_in_mem( + space, tablespace_version)) { + /* The page belongs to a space which has been + deleted or is being deleted. */ + *err = DB_TABLESPACE_DELETED; + + goto err_exit; + } + + if (block) { + bpage = &block->page; + mutex_enter(&block->mutex); + buf_page_init(space, offset, block); + + rw_lock_x_unlock(&page_hash_latch); + + /* The block must be put to the LRU list, to the old blocks */ + buf_LRU_add_block(bpage, TRUE/* to old blocks */); + + /* We set a pass-type x-lock on the frame because then + the same thread which called for the read operation + (and is running now at this point of code) can wait + for the read to complete by waiting for the x-lock on + the frame; if the x-lock were recursive, the same + thread would illegally get the x-lock before the page + read is completed. The x-lock is cleared by the + io-handler thread. */ + + rw_lock_x_lock_gen(&block->lock, BUF_IO_READ); + buf_page_set_io_fix(bpage, BUF_IO_READ); + + if (UNIV_UNLIKELY(zip_size)) { + page_zip_set_size(&block->page.zip, zip_size); + + /* buf_pool_mutex may be released and + reacquired by buf_buddy_alloc(). Thus, we + must release block->mutex in order not to + break the latching order in the reacquisition + of buf_pool_mutex. We also must defer this + operation until after the block descriptor has + been added to buf_pool->LRU and + buf_pool->page_hash. */ + mutex_exit(&block->mutex); + data = buf_buddy_alloc(zip_size, &lru, FALSE); + mutex_enter(&block->mutex); + block->page.zip.data = data; + + /* To maintain the invariant + block->in_unzip_LRU_list + == buf_page_belongs_to_unzip_LRU(&block->page) + we have to add this block to unzip_LRU + after block->page.zip.data is set. */ + ut_ad(buf_page_belongs_to_unzip_LRU(&block->page)); + buf_unzip_LRU_add_block(block, TRUE); + } + + mutex_exit(&LRU_list_mutex); + mutex_exit(&block->mutex); + } else { + /* Defer buf_buddy_alloc() until after the block has + been found not to exist. The buf_buddy_alloc() and + buf_buddy_free() calls may be expensive because of + buf_buddy_relocate(). */ + + /* The compressed page must be allocated before the + control block (bpage), in order to avoid the + invocation of buf_buddy_relocate_block() on + uninitialized data. */ + data = buf_buddy_alloc(zip_size, &lru, TRUE); + bpage = buf_buddy_alloc(sizeof *bpage, &lru, TRUE); + + /* If buf_buddy_alloc() allocated storage from the LRU list, + it released and reacquired buf_pool_mutex. Thus, we must + check the page_hash again, as it may have been modified. */ + if (UNIV_UNLIKELY(lru) + && UNIV_LIKELY_NULL(buf_page_hash_get(space, offset))) { + + /* The block was added by some other thread. */ + buf_buddy_free(bpage, sizeof *bpage, TRUE); + buf_buddy_free(data, zip_size, TRUE); + + mutex_exit(&LRU_list_mutex); + rw_lock_x_unlock(&page_hash_latch); + + bpage = NULL; + goto func_exit; + } + + page_zip_des_init(&bpage->zip); + page_zip_set_size(&bpage->zip, zip_size); + bpage->zip.data = data; + + mutex_enter(&buf_pool_zip_mutex); + UNIV_MEM_DESC(bpage->zip.data, + page_zip_get_size(&bpage->zip), bpage); + buf_page_init_low(bpage); + bpage->state = BUF_BLOCK_ZIP_PAGE; + bpage->space = space; + bpage->offset = offset; + +#ifdef UNIV_DEBUG + bpage->in_page_hash = FALSE; + bpage->in_zip_hash = FALSE; + bpage->in_flush_list = FALSE; + bpage->in_free_list = FALSE; +#endif /* UNIV_DEBUG */ + bpage->in_LRU_list = FALSE; + + ut_d(bpage->in_page_hash = TRUE); + HASH_INSERT(buf_page_t, hash, buf_pool->page_hash, + buf_page_address_fold(space, offset), bpage); + + rw_lock_x_unlock(&page_hash_latch); + + /* The block must be put to the LRU list, to the old blocks */ + buf_LRU_add_block(bpage, TRUE/* to old blocks */); + mutex_enter(&flush_list_mutex); + buf_LRU_insert_zip_clean(bpage); + mutex_exit(&flush_list_mutex); + + mutex_exit(&LRU_list_mutex); + + buf_page_set_io_fix(bpage, BUF_IO_READ); + + mutex_exit(&buf_pool_zip_mutex); + } + + mutex_enter(&buf_pool_mutex); + buf_pool->n_pend_reads++; + mutex_exit(&buf_pool_mutex); +func_exit: + //buf_pool_mutex_exit(); + + if (mode == BUF_READ_IBUF_PAGES_ONLY) { + + mtr_commit(&mtr); + } + + ut_ad(!bpage || buf_page_in_file(bpage)); + return(bpage); +} + +/************************************************************************ +Initializes a page to the buffer buf_pool. The page is usually not read +from a file even if it cannot be found in the buffer buf_pool. This is one +of the functions which perform to a block a state transition NOT_USED => +FILE_PAGE (the other is buf_page_get_gen). */ +UNIV_INTERN +buf_block_t* +buf_page_create( +/*============*/ + /* out: pointer to the block, page bufferfixed */ + ulint space, /* in: space id */ + ulint offset, /* in: offset of the page within space in units of + a page */ + ulint zip_size,/* in: compressed page size, or 0 */ + mtr_t* mtr) /* in: mini-transaction handle */ +{ + buf_frame_t* frame; + buf_block_t* block; + buf_block_t* free_block = NULL; + + ut_ad(mtr); + ut_ad(space || !zip_size); + + free_block = buf_LRU_get_free_block(0); + + //buf_pool_mutex_enter(); + mutex_enter(&LRU_list_mutex); + rw_lock_x_lock(&page_hash_latch); + + block = (buf_block_t*) buf_page_hash_get(space, offset); + + if (block && buf_page_in_file(&block->page)) { +#ifdef UNIV_IBUF_COUNT_DEBUG + ut_a(ibuf_count_get(space, offset) == 0); +#endif +#ifdef UNIV_DEBUG_FILE_ACCESSES + block->page.file_page_was_freed = FALSE; +#endif /* UNIV_DEBUG_FILE_ACCESSES */ + + /* Page can be found in buf_pool */ + //buf_pool_mutex_exit(); + mutex_exit(&LRU_list_mutex); + rw_lock_x_unlock(&page_hash_latch); + + buf_block_free(free_block); + + return(buf_page_get_with_no_latch(space, zip_size, + offset, mtr)); + } + + /* If we get here, the page was not in buf_pool: init it there */ + +#ifdef UNIV_DEBUG + if (buf_debug_prints) { + fprintf(stderr, "Creating space %lu page %lu to buffer\n", + (ulong) space, (ulong) offset); + } +#endif /* UNIV_DEBUG */ + + block = free_block; + + mutex_enter(&block->mutex); + + buf_page_init(space, offset, block); + rw_lock_x_unlock(&page_hash_latch); + + /* The block must be put to the LRU list */ + buf_LRU_add_block(&block->page, FALSE); + + buf_block_buf_fix_inc(block, __FILE__, __LINE__); + buf_pool->n_pages_created++; + + if (zip_size) { + void* data; + ibool lru; + + /* Prevent race conditions during buf_buddy_alloc(), + which may release and reacquire buf_pool_mutex, + by IO-fixing and X-latching the block. */ + + buf_page_set_io_fix(&block->page, BUF_IO_READ); + rw_lock_x_lock(&block->lock); + + page_zip_set_size(&block->page.zip, zip_size); + mutex_exit(&block->mutex); + /* buf_pool_mutex may be released and reacquired by + buf_buddy_alloc(). Thus, we must release block->mutex + in order not to break the latching order in + the reacquisition of buf_pool_mutex. We also must + defer this operation until after the block descriptor + has been added to buf_pool->LRU and buf_pool->page_hash. */ + data = buf_buddy_alloc(zip_size, &lru, FALSE); + mutex_enter(&block->mutex); + block->page.zip.data = data; + + /* To maintain the invariant + block->in_unzip_LRU_list + == buf_page_belongs_to_unzip_LRU(&block->page) + we have to add this block to unzip_LRU after + block->page.zip.data is set. */ + ut_ad(buf_page_belongs_to_unzip_LRU(&block->page)); + buf_unzip_LRU_add_block(block, FALSE); + + buf_page_set_io_fix(&block->page, BUF_IO_NONE); + rw_lock_x_unlock(&block->lock); + } + + //buf_pool_mutex_exit(); + mutex_exit(&LRU_list_mutex); + + mtr_memo_push(mtr, block, MTR_MEMO_BUF_FIX); + + buf_page_set_accessed(&block->page, TRUE); + + mutex_exit(&block->mutex); + + /* Delete possible entries for the page from the insert buffer: + such can exist if the page belonged to an index which was dropped */ + + ibuf_merge_or_delete_for_page(NULL, space, offset, zip_size, TRUE); + + /* Flush pages from the end of the LRU list if necessary */ + buf_flush_free_margin(FALSE); + + frame = block->frame; + + memset(frame + FIL_PAGE_PREV, 0xff, 4); + memset(frame + FIL_PAGE_NEXT, 0xff, 4); + mach_write_to_2(frame + FIL_PAGE_TYPE, FIL_PAGE_TYPE_ALLOCATED); + + /* Reset to zero the file flush lsn field in the page; if the first + page of an ibdata file is 'created' in this function into the buffer + pool then we lose the original contents of the file flush lsn stamp. + Then InnoDB could in a crash recovery print a big, false, corruption + warning if the stamp contains an lsn bigger than the ib_logfile lsn. */ + + memset(frame + FIL_PAGE_FILE_FLUSH_LSN, 0, 8); + +#if defined UNIV_DEBUG || defined UNIV_BUF_DEBUG + ut_a(++buf_dbg_counter % 357 || buf_validate()); +#endif /* UNIV_DEBUG || UNIV_BUF_DEBUG */ +#ifdef UNIV_IBUF_COUNT_DEBUG + ut_a(ibuf_count_get(buf_block_get_space(block), + buf_block_get_page_no(block)) == 0); +#endif + return(block); +} + +/************************************************************************ +Completes an asynchronous read or write request of a file page to or from +the buffer pool. */ +UNIV_INTERN +void +buf_page_io_complete( +/*=================*/ + buf_page_t* bpage) /* in: pointer to the block in question */ +{ + enum buf_io_fix io_type; + const ibool uncompressed = (buf_page_get_state(bpage) + == BUF_BLOCK_FILE_PAGE); + enum buf_flush flush_type; + mutex_t* block_mutex; + + ut_a(buf_page_in_file(bpage)); + + /* We do not need protect io_fix here by mutex to read + it because this is the only function where we can change the value + from BUF_IO_READ or BUF_IO_WRITE to some other value, and our code + ensures that this is the only thread that handles the i/o for this + block. */ + + io_type = buf_page_get_io_fix(bpage); + ut_ad(io_type == BUF_IO_READ || io_type == BUF_IO_WRITE); + + if (io_type == BUF_IO_READ) { + ulint read_page_no; + ulint read_space_id; + byte* frame; + + if (buf_page_get_zip_size(bpage)) { + frame = bpage->zip.data; + buf_pool->n_pend_unzip++; + if (uncompressed + && !buf_zip_decompress((buf_block_t*) bpage, + FALSE)) { + + buf_pool->n_pend_unzip--; + goto corrupt; + } + buf_pool->n_pend_unzip--; + } else { + ut_a(uncompressed); + frame = ((buf_block_t*) bpage)->frame; + } + + /* If this page is not uninitialized and not in the + doublewrite buffer, then the page number and space id + should be the same as in block. */ + read_page_no = mach_read_from_4(frame + FIL_PAGE_OFFSET); + read_space_id = mach_read_from_4( + frame + FIL_PAGE_ARCH_LOG_NO_OR_SPACE_ID); + + if (bpage->space == TRX_SYS_SPACE + && trx_doublewrite_page_inside(bpage->offset)) { + + ut_print_timestamp(stderr); + fprintf(stderr, + " InnoDB: Error: reading page %lu\n" + "InnoDB: which is in the" + " doublewrite buffer!\n", + (ulong) bpage->offset); + } else if (!read_space_id && !read_page_no) { + /* This is likely an uninitialized page. */ + } else if ((bpage->space + && bpage->space != read_space_id) + || bpage->offset != read_page_no) { + /* We did not compare space_id to read_space_id + if bpage->space == 0, because the field on the + page may contain garbage in MySQL < 4.1.1, + which only supported bpage->space == 0. */ + + ut_print_timestamp(stderr); + fprintf(stderr, + " InnoDB: Error: space id and page n:o" + " stored in the page\n" + "InnoDB: read in are %lu:%lu," + " should be %lu:%lu!\n", + (ulong) read_space_id, (ulong) read_page_no, + (ulong) bpage->space, + (ulong) bpage->offset); + } + + /* From version 3.23.38 up we store the page checksum + to the 4 first bytes of the page end lsn field */ + + if (buf_page_is_corrupted(frame, + buf_page_get_zip_size(bpage))) { +corrupt: + fprintf(stderr, + "InnoDB: Database page corruption on disk" + " or a failed\n" + "InnoDB: file read of page %lu.\n" + "InnoDB: You may have to recover" + " from a backup.\n", + (ulong) bpage->offset); + buf_page_print(frame, buf_page_get_zip_size(bpage)); + fprintf(stderr, + "InnoDB: Database page corruption on disk" + " or a failed\n" + "InnoDB: file read of page %lu.\n" + "InnoDB: You may have to recover" + " from a backup.\n", + (ulong) bpage->offset); + fputs("InnoDB: It is also possible that" + " your operating\n" + "InnoDB: system has corrupted its" + " own file cache\n" + "InnoDB: and rebooting your computer" + " removes the\n" + "InnoDB: error.\n" + "InnoDB: If the corrupt page is an index page\n" + "InnoDB: you can also try to" + " fix the corruption\n" + "InnoDB: by dumping, dropping," + " and reimporting\n" + "InnoDB: the corrupt table." + " You can use CHECK\n" + "InnoDB: TABLE to scan your" + " table for corruption.\n" + "InnoDB: See also" + " http://dev.mysql.com/doc/refman/5.1/en/" + "forcing-recovery.html\n" + "InnoDB: about forcing recovery.\n", stderr); + + if (srv_force_recovery < SRV_FORCE_IGNORE_CORRUPT) { + fputs("InnoDB: Ending processing because of" + " a corrupt database page.\n", + stderr); + exit(1); + } + } + + if (recv_recovery_is_on()) { + /* Pages must be uncompressed for crash recovery. */ + ut_a(uncompressed); + recv_recover_page(FALSE, TRUE, (buf_block_t*) bpage); + } + + if (uncompressed && !recv_no_ibuf_operations) { + ibuf_merge_or_delete_for_page( + (buf_block_t*) bpage, bpage->space, + bpage->offset, buf_page_get_zip_size(bpage), + TRUE); + } + } + + //buf_pool_mutex_enter(); + if (io_type == BUF_IO_WRITE) { + flush_type = buf_page_get_flush_type(bpage); + /* to keep consistency at buf_LRU_insert_zip_clean() */ + //if (flush_type == BUF_FLUSH_LRU) { /* optimistic! */ + mutex_enter(&LRU_list_mutex); + //} + } + block_mutex = buf_page_get_mutex(bpage); +retry_lock: + mutex_enter(block_mutex); + if (block_mutex != buf_page_get_mutex(bpage)) { + mutex_exit(block_mutex); + block_mutex = buf_page_get_mutex(bpage); + goto retry_lock; + } + mutex_enter(&buf_pool_mutex); + +#ifdef UNIV_IBUF_COUNT_DEBUG + if (io_type == BUF_IO_WRITE || uncompressed) { + /* For BUF_IO_READ of compressed-only blocks, the + buffered operations will be merged by buf_page_get_gen() + after the block has been uncompressed. */ + ut_a(ibuf_count_get(bpage->space, bpage->offset) == 0); + } +#endif + /* Because this thread which does the unlocking is not the same that + did the locking, we use a pass value != 0 in unlock, which simply + removes the newest lock debug record, without checking the thread + id. */ + + buf_page_set_io_fix(bpage, BUF_IO_NONE); + + switch (io_type) { + case BUF_IO_READ: + /* NOTE that the call to ibuf may have moved the ownership of + the x-latch to this OS thread: do not let this confuse you in + debugging! */ + + ut_ad(buf_pool->n_pend_reads > 0); + buf_pool->n_pend_reads--; + buf_pool->n_pages_read++; + + if (uncompressed) { + rw_lock_x_unlock_gen(&((buf_block_t*) bpage)->lock, + BUF_IO_READ); + } + + break; + + case BUF_IO_WRITE: + /* Write means a flush operation: call the completion + routine in the flush system */ + + buf_flush_write_complete(bpage); + + /* to keep consistency at buf_LRU_insert_zip_clean() */ + //if (flush_type == BUF_FLUSH_LRU) { /* optimistic! */ + mutex_exit(&LRU_list_mutex); + //} + + if (uncompressed) { + rw_lock_s_unlock_gen(&((buf_block_t*) bpage)->lock, + BUF_IO_WRITE); + } + + buf_pool->n_pages_written++; + + break; + + default: + ut_error; + } + +#ifdef UNIV_DEBUG + if (buf_debug_prints) { + fprintf(stderr, "Has %s page space %lu page no %lu\n", + io_type == BUF_IO_READ ? "read" : "written", + (ulong) buf_page_get_space(bpage), + (ulong) buf_page_get_page_no(bpage)); + } +#endif /* UNIV_DEBUG */ + + mutex_exit(&buf_pool_mutex); + mutex_exit(block_mutex); + //buf_pool_mutex_exit(); +} + +/************************************************************************* +Invalidates the file pages in the buffer pool when an archive recovery is +completed. All the file pages buffered must be in a replaceable state when +this function is called: not latched and not modified. */ +UNIV_INTERN +void +buf_pool_invalidate(void) +/*=====================*/ +{ + ibool freed; + + ut_ad(buf_all_freed()); + + freed = TRUE; + + while (freed) { + freed = buf_LRU_search_and_free_block(100); + } + + //buf_pool_mutex_enter(); + mutex_enter(&LRU_list_mutex); + + ut_ad(UT_LIST_GET_LEN(buf_pool->LRU) == 0); + ut_ad(UT_LIST_GET_LEN(buf_pool->unzip_LRU) == 0); + + //buf_pool_mutex_exit(); + mutex_exit(&LRU_list_mutex); +} + +#if defined UNIV_DEBUG || defined UNIV_BUF_DEBUG +/************************************************************************* +Validates the buffer buf_pool data structure. */ +UNIV_INTERN +ibool +buf_validate(void) +/*==============*/ +{ + buf_page_t* b; + buf_chunk_t* chunk; + ulint i; + ulint n_single_flush = 0; + ulint n_lru_flush = 0; + ulint n_list_flush = 0; + ulint n_lru = 0; + ulint n_flush = 0; + ulint n_free = 0; + ulint n_zip = 0; + + ut_ad(buf_pool); + + //buf_pool_mutex_enter(); + mutex_enter(&LRU_list_mutex); + rw_lock_x_lock(&page_hash_latch); + /* for keep the new latch order, it cannot validate correctly... */ + + chunk = buf_pool->chunks; + + /* Check the uncompressed blocks. */ + + for (i = buf_pool->n_chunks; i--; chunk++) { + + ulint j; + buf_block_t* block = chunk->blocks; + + for (j = chunk->size; j--; block++) { + + mutex_enter(&block->mutex); + + switch (buf_block_get_state(block)) { + case BUF_BLOCK_ZIP_FREE: + case BUF_BLOCK_ZIP_PAGE: + case BUF_BLOCK_ZIP_DIRTY: + /* These should only occur on + zip_clean, zip_free[], or flush_list. */ + ut_error; + break; + + case BUF_BLOCK_FILE_PAGE: + ut_a(buf_page_hash_get(buf_block_get_space( + block), + buf_block_get_page_no( + block)) + == &block->page); + +#ifdef UNIV_IBUF_COUNT_DEBUG + ut_a(buf_page_get_io_fix(&block->page) + == BUF_IO_READ + || !ibuf_count_get(buf_block_get_space( + block), + buf_block_get_page_no( + block))); +#endif + switch (buf_page_get_io_fix(&block->page)) { + case BUF_IO_NONE: + break; + + case BUF_IO_WRITE: + switch (buf_page_get_flush_type( + &block->page)) { + case BUF_FLUSH_LRU: + n_lru_flush++; + ut_a(rw_lock_is_locked( + &block->lock, + RW_LOCK_SHARED)); + break; + case BUF_FLUSH_LIST: + n_list_flush++; + break; + case BUF_FLUSH_SINGLE_PAGE: + n_single_flush++; + break; + default: + ut_error; + } + + break; + + case BUF_IO_READ: + + ut_a(rw_lock_is_locked(&block->lock, + RW_LOCK_EX)); + break; + } + + n_lru++; + + if (block->page.oldest_modification > 0) { + n_flush++; + } + + break; + + case BUF_BLOCK_NOT_USED: + n_free++; + break; + + case BUF_BLOCK_READY_FOR_USE: + case BUF_BLOCK_MEMORY: + case BUF_BLOCK_REMOVE_HASH: + /* do nothing */ + break; + } + + mutex_exit(&block->mutex); + } + } + + mutex_enter(&buf_pool_zip_mutex); + + /* Check clean compressed-only blocks. */ + + for (b = UT_LIST_GET_FIRST(buf_pool->zip_clean); b; + b = UT_LIST_GET_NEXT(zip_list, b)) { + ut_a(buf_page_get_state(b) == BUF_BLOCK_ZIP_PAGE); + switch (buf_page_get_io_fix(b)) { + case BUF_IO_NONE: + /* All clean blocks should be I/O-unfixed. */ + break; + case BUF_IO_READ: + /* In buf_LRU_free_block(), we temporarily set + b->io_fix = BUF_IO_READ for a newly allocated + control block in order to prevent + buf_page_get_gen() from decompressing the block. */ + break; + default: + ut_error; + break; + } + ut_a(!b->oldest_modification); + ut_a(buf_page_hash_get(b->space, b->offset) == b); + + n_lru++; + n_zip++; + } + + /* Check dirty compressed-only blocks. */ + + mutex_enter(&flush_list_mutex); + for (b = UT_LIST_GET_FIRST(buf_pool->flush_list); b; + b = UT_LIST_GET_NEXT(flush_list, b)) { + ut_ad(b->in_flush_list); + + switch (buf_page_get_state(b)) { + case BUF_BLOCK_ZIP_DIRTY: + ut_a(b->oldest_modification); + n_lru++; + n_flush++; + n_zip++; + switch (buf_page_get_io_fix(b)) { + case BUF_IO_NONE: + case BUF_IO_READ: + break; + + case BUF_IO_WRITE: + switch (buf_page_get_flush_type(b)) { + case BUF_FLUSH_LRU: + n_lru_flush++; + break; + case BUF_FLUSH_LIST: + n_list_flush++; + break; + case BUF_FLUSH_SINGLE_PAGE: + n_single_flush++; + break; + default: + ut_error; + } + break; + } + break; + case BUF_BLOCK_FILE_PAGE: + /* uncompressed page */ + break; + case BUF_BLOCK_ZIP_FREE: + case BUF_BLOCK_ZIP_PAGE: + case BUF_BLOCK_NOT_USED: + case BUF_BLOCK_READY_FOR_USE: + case BUF_BLOCK_MEMORY: + case BUF_BLOCK_REMOVE_HASH: + ut_error; + break; + } + ut_a(buf_page_hash_get(b->space, b->offset) == b); + } + mutex_exit(&flush_list_mutex); + + mutex_exit(&buf_pool_zip_mutex); + + if (n_lru + n_free > buf_pool->curr_size + n_zip) { + fprintf(stderr, "n LRU %lu, n free %lu, pool %lu zip %lu\n", + (ulong) n_lru, (ulong) n_free, + (ulong) buf_pool->curr_size, (ulong) n_zip); + ut_error; + } + + ut_a(UT_LIST_GET_LEN(buf_pool->LRU) == n_lru); + /* because of latching order with block->mutex, we cannot get free_list_mutex before that */ +/* + if (UT_LIST_GET_LEN(buf_pool->free) != n_free) { + fprintf(stderr, "Free list len %lu, free blocks %lu\n", + (ulong) UT_LIST_GET_LEN(buf_pool->free), + (ulong) n_free); + ut_error; + } +*/ + /* because of latching order with block->mutex, we cannot get flush_list_mutex before that */ +/* + ut_a(UT_LIST_GET_LEN(buf_pool->flush_list) == n_flush); + + ut_a(buf_pool->n_flush[BUF_FLUSH_SINGLE_PAGE] == n_single_flush); + ut_a(buf_pool->n_flush[BUF_FLUSH_LIST] == n_list_flush); + ut_a(buf_pool->n_flush[BUF_FLUSH_LRU] == n_lru_flush); +*/ + + //buf_pool_mutex_exit(); + mutex_exit(&LRU_list_mutex); + rw_lock_x_unlock(&page_hash_latch); + + ut_a(buf_LRU_validate()); + ut_a(buf_flush_validate()); + + return(TRUE); +} +#endif /* UNIV_DEBUG || UNIV_BUF_DEBUG */ + +#if defined UNIV_DEBUG_PRINT || defined UNIV_DEBUG || defined UNIV_BUF_DEBUG +/************************************************************************* +Prints info of the buffer buf_pool data structure. */ +UNIV_INTERN +void +buf_print(void) +/*===========*/ +{ + dulint* index_ids; + ulint* counts; + ulint size; + ulint i; + ulint j; + dulint id; + ulint n_found; + buf_chunk_t* chunk; + dict_index_t* index; + + ut_ad(buf_pool); + + size = buf_pool->curr_size; + + index_ids = mem_alloc(sizeof(dulint) * size); + counts = mem_alloc(sizeof(ulint) * size); + + //buf_pool_mutex_enter(); + mutex_enter(&LRU_list_mutex); + mutex_enter(&free_list_mutex); + mutex_enter(&flush_list_mutex); + + fprintf(stderr, + "buf_pool size %lu\n" + "database pages %lu\n" + "free pages %lu\n" + "modified database pages %lu\n" + "n pending decompressions %lu\n" + "n pending reads %lu\n" + "n pending flush LRU %lu list %lu single page %lu\n" + "pages read %lu, created %lu, written %lu\n", + (ulong) size, + (ulong) UT_LIST_GET_LEN(buf_pool->LRU), + (ulong) UT_LIST_GET_LEN(buf_pool->free), + (ulong) UT_LIST_GET_LEN(buf_pool->flush_list), + (ulong) buf_pool->n_pend_unzip, + (ulong) buf_pool->n_pend_reads, + (ulong) buf_pool->n_flush[BUF_FLUSH_LRU], + (ulong) buf_pool->n_flush[BUF_FLUSH_LIST], + (ulong) buf_pool->n_flush[BUF_FLUSH_SINGLE_PAGE], + (ulong) buf_pool->n_pages_read, buf_pool->n_pages_created, + (ulong) buf_pool->n_pages_written); + + /* Count the number of blocks belonging to each index in the buffer */ + + n_found = 0; + + chunk = buf_pool->chunks; + + for (i = buf_pool->n_chunks; i--; chunk++) { + buf_block_t* block = chunk->blocks; + ulint n_blocks = chunk->size; + + for (; n_blocks--; block++) { + const buf_frame_t* frame = block->frame; + + if (fil_page_get_type(frame) == FIL_PAGE_INDEX) { + + id = btr_page_get_index_id(frame); + + /* Look for the id in the index_ids array */ + j = 0; + + while (j < n_found) { + + if (ut_dulint_cmp(index_ids[j], + id) == 0) { + counts[j]++; + + break; + } + j++; + } + + if (j == n_found) { + n_found++; + index_ids[j] = id; + counts[j] = 1; + } + } + } + } + + //buf_pool_mutex_exit(); + mutex_exit(&LRU_list_mutex); + mutex_exit(&free_list_mutex); + mutex_exit(&flush_list_mutex); + + for (i = 0; i < n_found; i++) { + index = dict_index_get_if_in_cache(index_ids[i]); + + fprintf(stderr, + "Block count for index %lu in buffer is about %lu", + (ulong) ut_dulint_get_low(index_ids[i]), + (ulong) counts[i]); + + if (index) { + putc(' ', stderr); + dict_index_name_print(stderr, NULL, index); + } + + putc('\n', stderr); + } + + mem_free(index_ids); + mem_free(counts); + + ut_a(buf_validate()); +} +#endif /* UNIV_DEBUG_PRINT || UNIV_DEBUG || UNIV_BUF_DEBUG */ + +#ifdef UNIV_DEBUG +/************************************************************************* +Returns the number of latched pages in the buffer pool. */ +UNIV_INTERN +ulint +buf_get_latched_pages_number(void) +/*==============================*/ +{ + buf_chunk_t* chunk; + buf_page_t* b; + ulint i; + ulint fixed_pages_number = 0; + + //buf_pool_mutex_enter(); + + chunk = buf_pool->chunks; + + for (i = buf_pool->n_chunks; i--; chunk++) { + buf_block_t* block; + ulint j; + + block = chunk->blocks; + + for (j = chunk->size; j--; block++) { + if (buf_block_get_state(block) + != BUF_BLOCK_FILE_PAGE) { + + continue; + } + + mutex_enter(&block->mutex); + + if (block->page.buf_fix_count != 0 + || buf_page_get_io_fix(&block->page) + != BUF_IO_NONE) { + fixed_pages_number++; + } + + mutex_exit(&block->mutex); + } + } + + mutex_enter(&buf_pool_zip_mutex); + + /* Traverse the lists of clean and dirty compressed-only blocks. */ + + for (b = UT_LIST_GET_FIRST(buf_pool->zip_clean); b; + b = UT_LIST_GET_NEXT(zip_list, b)) { + ut_a(buf_page_get_state(b) == BUF_BLOCK_ZIP_PAGE); + ut_a(buf_page_get_io_fix(b) != BUF_IO_WRITE); + + if (b->buf_fix_count != 0 + || buf_page_get_io_fix(b) != BUF_IO_NONE) { + fixed_pages_number++; + } + } + + mutex_enter(&flush_list_mutex); + for (b = UT_LIST_GET_FIRST(buf_pool->flush_list); b; + b = UT_LIST_GET_NEXT(flush_list, b)) { + ut_ad(b->in_flush_list); + + switch (buf_page_get_state(b)) { + case BUF_BLOCK_ZIP_DIRTY: + if (b->buf_fix_count != 0 + || buf_page_get_io_fix(b) != BUF_IO_NONE) { + fixed_pages_number++; + } + break; + case BUF_BLOCK_FILE_PAGE: + /* uncompressed page */ + break; + case BUF_BLOCK_ZIP_FREE: + case BUF_BLOCK_ZIP_PAGE: + case BUF_BLOCK_NOT_USED: + case BUF_BLOCK_READY_FOR_USE: + case BUF_BLOCK_MEMORY: + case BUF_BLOCK_REMOVE_HASH: + ut_error; + break; + } + } + mutex_exit(&flush_list_mutex); + + mutex_exit(&buf_pool_zip_mutex); + //buf_pool_mutex_exit(); + + return(fixed_pages_number); +} +#endif /* UNIV_DEBUG */ + +/************************************************************************* +Returns the number of pending buf pool ios. */ +UNIV_INTERN +ulint +buf_get_n_pending_ios(void) +/*=======================*/ +{ + return(buf_pool->n_pend_reads + + buf_pool->n_flush[BUF_FLUSH_LRU] + + buf_pool->n_flush[BUF_FLUSH_LIST] + + buf_pool->n_flush[BUF_FLUSH_SINGLE_PAGE]); +} + +/************************************************************************* +Returns the ratio in percents of modified pages in the buffer pool / +database pages in the buffer pool. */ +UNIV_INTERN +ulint +buf_get_modified_ratio_pct(void) +/*============================*/ +{ + ulint ratio; + + //buf_pool_mutex_enter(); /* optimistic */ + + ratio = (100 * UT_LIST_GET_LEN(buf_pool->flush_list)) + / (1 + UT_LIST_GET_LEN(buf_pool->LRU) + + UT_LIST_GET_LEN(buf_pool->free)); + + /* 1 + is there to avoid division by zero */ + + //buf_pool_mutex_exit(); /* optimistic */ + + return(ratio); +} + +/************************************************************************* +Prints info of the buffer i/o. */ +UNIV_INTERN +void +buf_print_io( +/*=========*/ + FILE* file) /* in/out: buffer where to print */ +{ + time_t current_time; + double time_elapsed; + ulint size; + + ut_ad(buf_pool); + size = buf_pool->curr_size; + + //buf_pool_mutex_enter(); + mutex_enter(&LRU_list_mutex); + mutex_enter(&free_list_mutex); + mutex_enter(&buf_pool_mutex); + mutex_enter(&flush_list_mutex); + + fprintf(file, + "Buffer pool size %lu\n" + "Buffer pool size, bytes %lu\n" + "Free buffers %lu\n" + "Database pages %lu\n" + "Modified db pages %lu\n" + "Pending reads %lu\n" + "Pending writes: LRU %lu, flush list %lu, single page %lu\n", + (ulong) size, + (ulong) size * UNIV_PAGE_SIZE, + (ulong) UT_LIST_GET_LEN(buf_pool->free), + (ulong) UT_LIST_GET_LEN(buf_pool->LRU), + (ulong) UT_LIST_GET_LEN(buf_pool->flush_list), + (ulong) buf_pool->n_pend_reads, + (ulong) buf_pool->n_flush[BUF_FLUSH_LRU] + + buf_pool->init_flush[BUF_FLUSH_LRU], + (ulong) buf_pool->n_flush[BUF_FLUSH_LIST] + + buf_pool->init_flush[BUF_FLUSH_LIST], + (ulong) buf_pool->n_flush[BUF_FLUSH_SINGLE_PAGE]); + + current_time = time(NULL); + time_elapsed = 0.001 + difftime(current_time, + buf_pool->last_printout_time); + buf_pool->last_printout_time = current_time; + + fprintf(file, + "Pages read %lu, created %lu, written %lu\n" + "%.2f reads/s, %.2f creates/s, %.2f writes/s\n", + (ulong) buf_pool->n_pages_read, + (ulong) buf_pool->n_pages_created, + (ulong) buf_pool->n_pages_written, + (buf_pool->n_pages_read - buf_pool->n_pages_read_old) + / time_elapsed, + (buf_pool->n_pages_created - buf_pool->n_pages_created_old) + / time_elapsed, + (buf_pool->n_pages_written - buf_pool->n_pages_written_old) + / time_elapsed); + + if (buf_pool->n_page_gets > buf_pool->n_page_gets_old) { + fprintf(file, "Buffer pool hit rate %lu / 1000\n", + (ulong) + (1000 - ((1000 * (buf_pool->n_pages_read + - buf_pool->n_pages_read_old)) + / (buf_pool->n_page_gets + - buf_pool->n_page_gets_old)))); + } else { + fputs("No buffer pool page gets since the last printout\n", + file); + } + + buf_pool->n_page_gets_old = buf_pool->n_page_gets; + buf_pool->n_pages_read_old = buf_pool->n_pages_read; + buf_pool->n_pages_created_old = buf_pool->n_pages_created; + buf_pool->n_pages_written_old = buf_pool->n_pages_written; + + /* Print some values to help us with visualizing what is + happening with LRU eviction. */ + fprintf(file, + "LRU len: %lu, unzip_LRU len: %lu\n" + "I/O sum[%lu]:cur[%lu], unzip sum[%lu]:cur[%lu]\n", + UT_LIST_GET_LEN(buf_pool->LRU), + UT_LIST_GET_LEN(buf_pool->unzip_LRU), + buf_LRU_stat_sum.io, buf_LRU_stat_cur.io, + buf_LRU_stat_sum.unzip, buf_LRU_stat_cur.unzip); + + //buf_pool_mutex_exit(); + mutex_exit(&LRU_list_mutex); + mutex_exit(&free_list_mutex); + mutex_exit(&buf_pool_mutex); + mutex_exit(&flush_list_mutex); +} + +/************************************************************************** +Refreshes the statistics used to print per-second averages. */ +UNIV_INTERN +void +buf_refresh_io_stats(void) +/*======================*/ +{ + buf_pool->last_printout_time = time(NULL); + buf_pool->n_page_gets_old = buf_pool->n_page_gets; + buf_pool->n_pages_read_old = buf_pool->n_pages_read; + buf_pool->n_pages_created_old = buf_pool->n_pages_created; + buf_pool->n_pages_written_old = buf_pool->n_pages_written; +} + +/************************************************************************* +Checks that all file pages in the buffer are in a replaceable state. */ +UNIV_INTERN +ibool +buf_all_freed(void) +/*===============*/ +{ + buf_chunk_t* chunk; + ulint i; + + ut_ad(buf_pool); + + //buf_pool_mutex_enter(); /* optimistic */ + + chunk = buf_pool->chunks; + + for (i = buf_pool->n_chunks; i--; chunk++) { + + const buf_block_t* block = buf_chunk_not_freed(chunk); + + if (UNIV_LIKELY_NULL(block)) { + fprintf(stderr, + "Page %lu %lu still fixed or dirty\n", + (ulong) block->page.space, + (ulong) block->page.offset); + ut_error; + } + } + + //buf_pool_mutex_exit(); /* optimistic */ + + return(TRUE); +} + +/************************************************************************* +Checks that there currently are no pending i/o-operations for the buffer +pool. */ +UNIV_INTERN +ibool +buf_pool_check_no_pending_io(void) +/*==============================*/ + /* out: TRUE if there is no pending i/o */ +{ + ibool ret; + + //buf_pool_mutex_enter(); + mutex_enter(&buf_pool_mutex); + + if (buf_pool->n_pend_reads + buf_pool->n_flush[BUF_FLUSH_LRU] + + buf_pool->n_flush[BUF_FLUSH_LIST] + + buf_pool->n_flush[BUF_FLUSH_SINGLE_PAGE]) { + ret = FALSE; + } else { + ret = TRUE; + } + + //buf_pool_mutex_exit(); + mutex_exit(&buf_pool_mutex); + + return(ret); +} + +/************************************************************************* +Gets the current length of the free list of buffer blocks. */ +UNIV_INTERN +ulint +buf_get_free_list_len(void) +/*=======================*/ +{ + ulint len; + + //buf_pool_mutex_enter(); + mutex_enter(&free_list_mutex); + + len = UT_LIST_GET_LEN(buf_pool->free); + + //buf_pool_mutex_exit(); + mutex_exit(&free_list_mutex); + + return(len); +} diff --git a/storage/xtradb/buf/buf0flu.c b/storage/xtradb/buf/buf0flu.c new file mode 100644 index 00000000000..9acf4f50bf1 --- /dev/null +++ b/storage/xtradb/buf/buf0flu.c @@ -0,0 +1,1358 @@ +/***************************************************************************** + +Copyright (c) 1995, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/****************************************************** +The database buffer buf_pool flush algorithm + +Created 11/11/1995 Heikki Tuuri +*******************************************************/ + +#include "buf0flu.h" + +#ifdef UNIV_NONINL +#include "buf0flu.ic" +#include "trx0sys.h" +#endif + +#include "ut0byte.h" +#include "ut0lst.h" +#include "page0page.h" +#include "page0zip.h" +#include "fil0fil.h" +#include "buf0buf.h" +#include "buf0lru.h" +#include "buf0rea.h" +#include "ibuf0ibuf.h" +#include "log0log.h" +#include "os0file.h" +#include "trx0sys.h" +#include "srv0srv.h" + +#if defined UNIV_DEBUG || defined UNIV_BUF_DEBUG +/********************************************************************** +Validates the flush list. */ +static +ibool +buf_flush_validate_low(void); +/*========================*/ + /* out: TRUE if ok */ +#endif /* UNIV_DEBUG || UNIV_BUF_DEBUG */ + +/************************************************************************ +Inserts a modified block into the flush list. */ +UNIV_INTERN +void +buf_flush_insert_into_flush_list( +/*=============================*/ + buf_block_t* block) /* in/out: block which is modified */ +{ + //ut_ad(buf_pool_mutex_own()); + ut_ad(mutex_own(&block->mutex)); + ut_ad(mutex_own(&flush_list_mutex)); + ut_ad((UT_LIST_GET_FIRST(buf_pool->flush_list) == NULL) + || (UT_LIST_GET_FIRST(buf_pool->flush_list)->oldest_modification + <= block->page.oldest_modification)); + + ut_ad(buf_block_get_state(block) == BUF_BLOCK_FILE_PAGE); + ut_ad(block->page.in_LRU_list); + ut_ad(block->page.in_page_hash); + ut_ad(!block->page.in_zip_hash); + ut_ad(!block->page.in_flush_list); + ut_d(block->page.in_flush_list = TRUE); + UT_LIST_ADD_FIRST(flush_list, buf_pool->flush_list, &block->page); + +#if defined UNIV_DEBUG || defined UNIV_BUF_DEBUG + ut_a(buf_flush_validate_low()); +#endif /* UNIV_DEBUG || UNIV_BUF_DEBUG */ +} + +/************************************************************************ +Inserts a modified block into the flush list in the right sorted position. +This function is used by recovery, because there the modifications do not +necessarily come in the order of lsn's. */ +UNIV_INTERN +void +buf_flush_insert_sorted_into_flush_list( +/*====================================*/ + buf_block_t* block) /* in/out: block which is modified */ +{ + buf_page_t* prev_b; + buf_page_t* b; + + //ut_ad(buf_pool_mutex_own()); + ut_ad(mutex_own(&block->mutex)); + ut_ad(mutex_own(&flush_list_mutex)); + ut_ad(buf_block_get_state(block) == BUF_BLOCK_FILE_PAGE); + + ut_ad(block->page.in_LRU_list); + ut_ad(block->page.in_page_hash); + ut_ad(!block->page.in_zip_hash); + ut_ad(!block->page.in_flush_list); + ut_d(block->page.in_flush_list = TRUE); + + prev_b = NULL; + b = UT_LIST_GET_FIRST(buf_pool->flush_list); + + while (b && b->oldest_modification > block->page.oldest_modification) { + ut_ad(b->in_flush_list); + prev_b = b; + b = UT_LIST_GET_NEXT(flush_list, b); + } + + if (prev_b == NULL) { + UT_LIST_ADD_FIRST(flush_list, buf_pool->flush_list, &block->page); + } else { + UT_LIST_INSERT_AFTER(flush_list, buf_pool->flush_list, + prev_b, &block->page); + } + +#if defined UNIV_DEBUG || defined UNIV_BUF_DEBUG + ut_a(buf_flush_validate_low()); +#endif /* UNIV_DEBUG || UNIV_BUF_DEBUG */ +} + +/************************************************************************ +Returns TRUE if the file page block is immediately suitable for replacement, +i.e., the transition FILE_PAGE => NOT_USED allowed. */ +UNIV_INTERN +ibool +buf_flush_ready_for_replace( +/*========================*/ + /* out: TRUE if can replace immediately */ + buf_page_t* bpage) /* in: buffer control block, must be + buf_page_in_file(bpage) and in the LRU list */ +{ + //ut_ad(buf_pool_mutex_own()); + ut_ad(mutex_own(buf_page_get_mutex(bpage))); + //ut_ad(bpage->in_LRU_list); /* optimistic use */ + + if (UNIV_LIKELY(bpage->in_LRU_list && buf_page_in_file(bpage))) { + + return(bpage->oldest_modification == 0 + && buf_page_get_io_fix(bpage) == BUF_IO_NONE + && bpage->buf_fix_count == 0); + } + + /* permited not to own LRU_mutex.. */ +/* + ut_print_timestamp(stderr); + fprintf(stderr, + " InnoDB: Error: buffer block state %lu" + " in the LRU list!\n", + (ulong) buf_page_get_state(bpage)); + ut_print_buf(stderr, bpage, sizeof(buf_page_t)); + putc('\n', stderr); +*/ + + return(FALSE); +} + +/************************************************************************ +Returns TRUE if the block is modified and ready for flushing. */ +UNIV_INLINE +ibool +buf_flush_ready_for_flush( +/*======================*/ + /* out: TRUE if can flush immediately */ + buf_page_t* bpage, /* in: buffer control block, must be + buf_page_in_file(bpage) */ + enum buf_flush flush_type)/* in: BUF_FLUSH_LRU or BUF_FLUSH_LIST */ +{ + //ut_a(buf_page_in_file(bpage)); + //ut_ad(buf_pool_mutex_own()); /*optimistic...*/ + ut_ad(mutex_own(buf_page_get_mutex(bpage))); + ut_ad(flush_type == BUF_FLUSH_LRU || BUF_FLUSH_LIST); + + if (buf_page_in_file(bpage) && bpage->oldest_modification != 0 + && buf_page_get_io_fix(bpage) == BUF_IO_NONE) { + ut_ad(bpage->in_flush_list); + + if (flush_type != BUF_FLUSH_LRU) { + + return(TRUE); + + } else if (bpage->buf_fix_count == 0) { + + /* If we are flushing the LRU list, to avoid deadlocks + we require the block not to be bufferfixed, and hence + not latched. */ + + return(TRUE); + } + } + + return(FALSE); +} + +/************************************************************************ +Remove a block from the flush list of modified blocks. */ +UNIV_INTERN +void +buf_flush_remove( +/*=============*/ + buf_page_t* bpage) /* in: pointer to the block in question */ +{ + //ut_ad(buf_pool_mutex_own()); + ut_ad(mutex_own(buf_page_get_mutex(bpage))); + + mutex_enter(&flush_list_mutex); + + ut_ad(bpage->in_flush_list); + ut_d(bpage->in_flush_list = FALSE); + + switch (buf_page_get_state(bpage)) { + case BUF_BLOCK_ZIP_PAGE: + /* clean compressed pages should not be on the flush list */ + case BUF_BLOCK_ZIP_FREE: + case BUF_BLOCK_NOT_USED: + case BUF_BLOCK_READY_FOR_USE: + case BUF_BLOCK_MEMORY: + case BUF_BLOCK_REMOVE_HASH: + mutex_exit(&flush_list_mutex); + ut_error; + return; + case BUF_BLOCK_ZIP_DIRTY: + buf_page_set_state(bpage, BUF_BLOCK_ZIP_PAGE); + UT_LIST_REMOVE(flush_list, buf_pool->flush_list, bpage); + buf_LRU_insert_zip_clean(bpage); + break; + case BUF_BLOCK_FILE_PAGE: + UT_LIST_REMOVE(flush_list, buf_pool->flush_list, bpage); + break; + } + + bpage->oldest_modification = 0; + + ut_d(UT_LIST_VALIDATE(flush_list, buf_page_t, buf_pool->flush_list)); + mutex_exit(&flush_list_mutex); +} + +/************************************************************************ +Updates the flush system data structures when a write is completed. */ +UNIV_INTERN +void +buf_flush_write_complete( +/*=====================*/ + buf_page_t* bpage) /* in: pointer to the block in question */ +{ + enum buf_flush flush_type; + + ut_ad(bpage); + + buf_flush_remove(bpage); + + flush_type = buf_page_get_flush_type(bpage); + buf_pool->n_flush[flush_type]--; + + if (flush_type == BUF_FLUSH_LRU) { + /* Put the block to the end of the LRU list to wait to be + moved to the free list */ + + buf_LRU_make_block_old(bpage); + + buf_pool->LRU_flush_ended++; + } + + /* fprintf(stderr, "n pending flush %lu\n", + buf_pool->n_flush[flush_type]); */ + + if ((buf_pool->n_flush[flush_type] == 0) + && (buf_pool->init_flush[flush_type] == FALSE)) { + + /* The running flush batch has ended */ + + os_event_set(buf_pool->no_flush[flush_type]); + } +} + +/************************************************************************ +Flushes possible buffered writes from the doublewrite memory buffer to disk, +and also wakes up the aio thread if simulated aio is used. It is very +important to call this function after a batch of writes has been posted, +and also when we may have to wait for a page latch! Otherwise a deadlock +of threads can occur. */ +static +void +buf_flush_buffered_writes(void) +/*===========================*/ +{ + byte* write_buf; + ulint len; + ulint len2; + ulint i; + + if (!srv_use_doublewrite_buf || trx_doublewrite == NULL) { + os_aio_simulated_wake_handler_threads(); + + return; + } + + mutex_enter(&(trx_doublewrite->mutex)); + + /* Write first to doublewrite buffer blocks. We use synchronous + aio and thus know that file write has been completed when the + control returns. */ + + if (trx_doublewrite->first_free == 0) { + + mutex_exit(&(trx_doublewrite->mutex)); + + return; + } + + for (i = 0; i < trx_doublewrite->first_free; i++) { + + const buf_block_t* block; + + block = (buf_block_t*) trx_doublewrite->buf_block_arr[i]; + + if (buf_block_get_state(block) != BUF_BLOCK_FILE_PAGE + || block->page.zip.data) { + /* No simple validate for compressed pages exists. */ + continue; + } + + if (UNIV_UNLIKELY + (memcmp(block->frame + (FIL_PAGE_LSN + 4), + block->frame + (UNIV_PAGE_SIZE + - FIL_PAGE_END_LSN_OLD_CHKSUM + 4), + 4))) { + ut_print_timestamp(stderr); + fprintf(stderr, + " InnoDB: ERROR: The page to be written" + " seems corrupt!\n" + "InnoDB: The lsn fields do not match!" + " Noticed in the buffer pool\n" + "InnoDB: before posting to the" + " doublewrite buffer.\n"); + } + + if (!block->check_index_page_at_flush) { + } else if (page_is_comp(block->frame)) { + if (UNIV_UNLIKELY + (!page_simple_validate_new(block->frame))) { +corrupted_page: + buf_page_print(block->frame, 0); + + ut_print_timestamp(stderr); + fprintf(stderr, + " InnoDB: Apparent corruption of an" + " index page n:o %lu in space %lu\n" + "InnoDB: to be written to data file." + " We intentionally crash server\n" + "InnoDB: to prevent corrupt data" + " from ending up in data\n" + "InnoDB: files.\n", + (ulong) buf_block_get_page_no(block), + (ulong) buf_block_get_space(block)); + + ut_error; + } + } else if (UNIV_UNLIKELY + (!page_simple_validate_old(block->frame))) { + + goto corrupted_page; + } + } + + /* increment the doublewrite flushed pages counter */ + srv_dblwr_pages_written+= trx_doublewrite->first_free; + srv_dblwr_writes++; + + len = ut_min(TRX_SYS_DOUBLEWRITE_BLOCK_SIZE, + trx_doublewrite->first_free) * UNIV_PAGE_SIZE; + + write_buf = trx_doublewrite->write_buf; + i = 0; + + fil_io(OS_FILE_WRITE, TRUE, TRX_SYS_SPACE, 0, + trx_doublewrite->block1, 0, len, + (void*) write_buf, NULL); + + for (len2 = 0; len2 + UNIV_PAGE_SIZE <= len; + len2 += UNIV_PAGE_SIZE, i++) { + const buf_block_t* block = (buf_block_t*) + trx_doublewrite->buf_block_arr[i]; + + if (UNIV_LIKELY(!block->page.zip.data) + && UNIV_LIKELY(buf_block_get_state(block) + == BUF_BLOCK_FILE_PAGE) + && UNIV_UNLIKELY + (memcmp(write_buf + len2 + (FIL_PAGE_LSN + 4), + write_buf + len2 + + (UNIV_PAGE_SIZE + - FIL_PAGE_END_LSN_OLD_CHKSUM + 4), 4))) { + ut_print_timestamp(stderr); + fprintf(stderr, + " InnoDB: ERROR: The page to be written" + " seems corrupt!\n" + "InnoDB: The lsn fields do not match!" + " Noticed in the doublewrite block1.\n"); + } + } + + if (trx_doublewrite->first_free <= TRX_SYS_DOUBLEWRITE_BLOCK_SIZE) { + goto flush; + } + + len = (trx_doublewrite->first_free - TRX_SYS_DOUBLEWRITE_BLOCK_SIZE) + * UNIV_PAGE_SIZE; + + write_buf = trx_doublewrite->write_buf + + TRX_SYS_DOUBLEWRITE_BLOCK_SIZE * UNIV_PAGE_SIZE; + ut_ad(i == TRX_SYS_DOUBLEWRITE_BLOCK_SIZE); + + fil_io(OS_FILE_WRITE, TRUE, TRX_SYS_SPACE, 0, + trx_doublewrite->block2, 0, len, + (void*) write_buf, NULL); + + for (len2 = 0; len2 + UNIV_PAGE_SIZE <= len; + len2 += UNIV_PAGE_SIZE, i++) { + const buf_block_t* block = (buf_block_t*) + trx_doublewrite->buf_block_arr[i]; + + if (UNIV_LIKELY(!block->page.zip.data) + && UNIV_LIKELY(buf_block_get_state(block) + == BUF_BLOCK_FILE_PAGE) + && UNIV_UNLIKELY + (memcmp(write_buf + len2 + (FIL_PAGE_LSN + 4), + write_buf + len2 + + (UNIV_PAGE_SIZE + - FIL_PAGE_END_LSN_OLD_CHKSUM + 4), 4))) { + ut_print_timestamp(stderr); + fprintf(stderr, + " InnoDB: ERROR: The page to be" + " written seems corrupt!\n" + "InnoDB: The lsn fields do not match!" + " Noticed in" + " the doublewrite block2.\n"); + } + } + +flush: + /* Now flush the doublewrite buffer data to disk */ + + fil_flush(TRX_SYS_SPACE); + + /* We know that the writes have been flushed to disk now + and in recovery we will find them in the doublewrite buffer + blocks. Next do the writes to the intended positions. */ + + for (i = 0; i < trx_doublewrite->first_free; i++) { + const buf_block_t* block = (buf_block_t*) + trx_doublewrite->buf_block_arr[i]; + + ut_a(buf_page_in_file(&block->page)); + if (UNIV_LIKELY_NULL(block->page.zip.data)) { + fil_io(OS_FILE_WRITE | OS_AIO_SIMULATED_WAKE_LATER, + FALSE, buf_page_get_space(&block->page), + buf_page_get_zip_size(&block->page), + buf_page_get_page_no(&block->page), 0, + buf_page_get_zip_size(&block->page), + (void*)block->page.zip.data, + (void*)block); + + /* Increment the counter of I/O operations used + for selecting LRU policy. */ + buf_LRU_stat_inc_io(); + + continue; + } + + ut_a(buf_block_get_state(block) == BUF_BLOCK_FILE_PAGE); + + if (UNIV_UNLIKELY(memcmp(block->frame + (FIL_PAGE_LSN + 4), + block->frame + + (UNIV_PAGE_SIZE + - FIL_PAGE_END_LSN_OLD_CHKSUM + 4), + 4))) { + ut_print_timestamp(stderr); + fprintf(stderr, + " InnoDB: ERROR: The page to be written" + " seems corrupt!\n" + "InnoDB: The lsn fields do not match!" + " Noticed in the buffer pool\n" + "InnoDB: after posting and flushing" + " the doublewrite buffer.\n" + "InnoDB: Page buf fix count %lu," + " io fix %lu, state %lu\n", + (ulong)block->page.buf_fix_count, + (ulong)buf_block_get_io_fix(block), + (ulong)buf_block_get_state(block)); + } + + fil_io(OS_FILE_WRITE | OS_AIO_SIMULATED_WAKE_LATER, + FALSE, buf_block_get_space(block), 0, + buf_block_get_page_no(block), 0, UNIV_PAGE_SIZE, + (void*)block->frame, (void*)block); + + /* Increment the counter of I/O operations used + for selecting LRU policy. */ + buf_LRU_stat_inc_io(); + } + + /* Wake possible simulated aio thread to actually post the + writes to the operating system */ + + os_aio_simulated_wake_handler_threads(); + + /* Wait that all async writes to tablespaces have been posted to + the OS */ + + os_aio_wait_until_no_pending_writes(); + + /* Now we flush the data to disk (for example, with fsync) */ + + fil_flush_file_spaces(FIL_TABLESPACE); + + /* We can now reuse the doublewrite memory buffer: */ + + trx_doublewrite->first_free = 0; + + mutex_exit(&(trx_doublewrite->mutex)); +} + +/************************************************************************ +Posts a buffer page for writing. If the doublewrite memory buffer is +full, calls buf_flush_buffered_writes and waits for for free space to +appear. */ +static +void +buf_flush_post_to_doublewrite_buf( +/*==============================*/ + buf_page_t* bpage) /* in: buffer block to write */ +{ + ulint zip_size; +try_again: + mutex_enter(&(trx_doublewrite->mutex)); + + ut_a(buf_page_in_file(bpage)); + + if (trx_doublewrite->first_free + >= 2 * TRX_SYS_DOUBLEWRITE_BLOCK_SIZE) { + mutex_exit(&(trx_doublewrite->mutex)); + + buf_flush_buffered_writes(); + + goto try_again; + } + + zip_size = buf_page_get_zip_size(bpage); + + if (UNIV_UNLIKELY(zip_size)) { + /* Copy the compressed page and clear the rest. */ + memcpy(trx_doublewrite->write_buf + + UNIV_PAGE_SIZE * trx_doublewrite->first_free, + bpage->zip.data, zip_size); + memset(trx_doublewrite->write_buf + + UNIV_PAGE_SIZE * trx_doublewrite->first_free + + zip_size, 0, UNIV_PAGE_SIZE - zip_size); + } else { + ut_a(buf_page_get_state(bpage) == BUF_BLOCK_FILE_PAGE); + + memcpy(trx_doublewrite->write_buf + + UNIV_PAGE_SIZE * trx_doublewrite->first_free, + ((buf_block_t*) bpage)->frame, UNIV_PAGE_SIZE); + } + + trx_doublewrite->buf_block_arr[trx_doublewrite->first_free] = bpage; + + trx_doublewrite->first_free++; + + if (trx_doublewrite->first_free + >= 2 * TRX_SYS_DOUBLEWRITE_BLOCK_SIZE) { + mutex_exit(&(trx_doublewrite->mutex)); + + buf_flush_buffered_writes(); + + return; + } + + mutex_exit(&(trx_doublewrite->mutex)); +} + +/************************************************************************ +Initializes a page for writing to the tablespace. */ +UNIV_INTERN +void +buf_flush_init_for_writing( +/*=======================*/ + byte* page, /* in/out: page */ + void* page_zip_, /* in/out: compressed page, or NULL */ + ib_uint64_t newest_lsn) /* in: newest modification lsn + to the page */ +{ + ut_ad(page); + + if (page_zip_) { + page_zip_des_t* page_zip = page_zip_; + ulint zip_size = page_zip_get_size(page_zip); + ut_ad(zip_size); + ut_ad(ut_is_2pow(zip_size)); + ut_ad(zip_size <= UNIV_PAGE_SIZE); + + switch (UNIV_EXPECT(fil_page_get_type(page), FIL_PAGE_INDEX)) { + case FIL_PAGE_TYPE_ALLOCATED: + case FIL_PAGE_INODE: + case FIL_PAGE_IBUF_BITMAP: + case FIL_PAGE_TYPE_FSP_HDR: + case FIL_PAGE_TYPE_XDES: + /* These are essentially uncompressed pages. */ + memcpy(page_zip->data, page, zip_size); + /* fall through */ + case FIL_PAGE_TYPE_ZBLOB: + case FIL_PAGE_TYPE_ZBLOB2: + case FIL_PAGE_INDEX: + mach_write_ull(page_zip->data + + FIL_PAGE_LSN, newest_lsn); + memset(page_zip->data + FIL_PAGE_FILE_FLUSH_LSN, 0, 8); + mach_write_to_4(page_zip->data + + FIL_PAGE_SPACE_OR_CHKSUM, + srv_use_checksums + ? page_zip_calc_checksum( + page_zip->data, zip_size) + : BUF_NO_CHECKSUM_MAGIC); + return; + } + + ut_print_timestamp(stderr); + fputs(" InnoDB: ERROR: The compressed page to be written" + " seems corrupt:", stderr); + ut_print_buf(stderr, page, zip_size); + fputs("\nInnoDB: Possibly older version of the page:", stderr); + ut_print_buf(stderr, page_zip->data, zip_size); + putc('\n', stderr); + ut_error; + } + + /* Write the newest modification lsn to the page header and trailer */ + mach_write_ull(page + FIL_PAGE_LSN, newest_lsn); + + mach_write_ull(page + UNIV_PAGE_SIZE - FIL_PAGE_END_LSN_OLD_CHKSUM, + newest_lsn); + + /* Store the new formula checksum */ + + mach_write_to_4(page + FIL_PAGE_SPACE_OR_CHKSUM, + srv_use_checksums + ? buf_calc_page_new_checksum(page) + : BUF_NO_CHECKSUM_MAGIC); + + /* We overwrite the first 4 bytes of the end lsn field to store + the old formula checksum. Since it depends also on the field + FIL_PAGE_SPACE_OR_CHKSUM, it has to be calculated after storing the + new formula checksum. */ + + mach_write_to_4(page + UNIV_PAGE_SIZE - FIL_PAGE_END_LSN_OLD_CHKSUM, + srv_use_checksums + ? buf_calc_page_old_checksum(page) + : BUF_NO_CHECKSUM_MAGIC); +} + +/************************************************************************ +Does an asynchronous write of a buffer page. NOTE: in simulated aio and +also when the doublewrite buffer is used, we must call +buf_flush_buffered_writes after we have posted a batch of writes! */ +static +void +buf_flush_write_block_low( +/*======================*/ + buf_page_t* bpage) /* in: buffer block to write */ +{ + ulint zip_size = buf_page_get_zip_size(bpage); + page_t* frame = NULL; +#ifdef UNIV_LOG_DEBUG + static ibool univ_log_debug_warned; +#endif /* UNIV_LOG_DEBUG */ + + ut_ad(buf_page_in_file(bpage)); + + /* We are not holding buf_pool_mutex or block_mutex here. + Nevertheless, it is safe to access bpage, because it is + io_fixed and oldest_modification != 0. Thus, it cannot be + relocated in the buffer pool or removed from flush_list or + LRU_list. */ + //ut_ad(!buf_pool_mutex_own()); + ut_ad(!mutex_own(&LRU_list_mutex)); + ut_ad(!mutex_own(&flush_list_mutex)); + ut_ad(!mutex_own(buf_page_get_mutex(bpage))); + ut_ad(buf_page_get_io_fix(bpage) == BUF_IO_WRITE); + ut_ad(bpage->oldest_modification != 0); + +#ifdef UNIV_IBUF_COUNT_DEBUG + ut_a(ibuf_count_get(bpage->space, bpage->offset) == 0); +#endif + ut_ad(bpage->newest_modification != 0); + +#ifdef UNIV_LOG_DEBUG + if (!univ_log_debug_warned) { + univ_log_debug_warned = TRUE; + fputs("Warning: cannot force log to disk if" + " UNIV_LOG_DEBUG is defined!\n" + "Crash recovery will not work!\n", + stderr); + } +#else + /* Force the log to the disk before writing the modified block */ + log_write_up_to(bpage->newest_modification, LOG_WAIT_ALL_GROUPS, TRUE); +#endif + switch (buf_page_get_state(bpage)) { + case BUF_BLOCK_ZIP_FREE: + case BUF_BLOCK_ZIP_PAGE: /* The page should be dirty. */ + case BUF_BLOCK_NOT_USED: + case BUF_BLOCK_READY_FOR_USE: + case BUF_BLOCK_MEMORY: + case BUF_BLOCK_REMOVE_HASH: + ut_error; + break; + case BUF_BLOCK_ZIP_DIRTY: + frame = bpage->zip.data; + if (UNIV_LIKELY(srv_use_checksums)) { + ut_a(mach_read_from_4(frame + FIL_PAGE_SPACE_OR_CHKSUM) + == page_zip_calc_checksum(frame, zip_size)); + } + mach_write_ull(frame + FIL_PAGE_LSN, + bpage->newest_modification); + memset(frame + FIL_PAGE_FILE_FLUSH_LSN, 0, 8); + break; + case BUF_BLOCK_FILE_PAGE: + frame = bpage->zip.data; + if (!frame) { + frame = ((buf_block_t*) bpage)->frame; + } + + buf_flush_init_for_writing(((buf_block_t*) bpage)->frame, + bpage->zip.data + ? &bpage->zip : NULL, + bpage->newest_modification); + break; + } + + if (!srv_use_doublewrite_buf || !trx_doublewrite) { + fil_io(OS_FILE_WRITE | OS_AIO_SIMULATED_WAKE_LATER, + FALSE, buf_page_get_space(bpage), zip_size, + buf_page_get_page_no(bpage), 0, + zip_size ? zip_size : UNIV_PAGE_SIZE, + frame, bpage); + } else { + buf_flush_post_to_doublewrite_buf(bpage); + } +} + +/************************************************************************ +Writes a flushable page asynchronously from the buffer pool to a file. +NOTE: in simulated aio we must call +os_aio_simulated_wake_handler_threads after we have posted a batch of +writes! NOTE: buf_pool_mutex and buf_page_get_mutex(bpage) must be +held upon entering this function, and they will be released by this +function. */ +static +void +buf_flush_page( +/*===========*/ + buf_page_t* bpage, /* in: buffer control block */ + enum buf_flush flush_type) /* in: BUF_FLUSH_LRU + or BUF_FLUSH_LIST */ +{ + mutex_t* block_mutex; + ibool is_uncompressed; + + ut_ad(flush_type == BUF_FLUSH_LRU || flush_type == BUF_FLUSH_LIST); + //ut_ad(buf_pool_mutex_own()); +#ifdef UNIV_SYNC_DEBUG + ut_ad(rw_lock_own(&page_hash_latch, RW_LOCK_EX) + || rw_lock_own(&page_hash_latch, RW_LOCK_SHARED)); +#endif + ut_ad(buf_page_in_file(bpage)); + + block_mutex = buf_page_get_mutex(bpage); + ut_ad(mutex_own(block_mutex)); + + mutex_enter(&buf_pool_mutex); + rw_lock_s_unlock(&page_hash_latch); + + ut_ad(buf_flush_ready_for_flush(bpage, flush_type)); + + buf_page_set_io_fix(bpage, BUF_IO_WRITE); + + buf_page_set_flush_type(bpage, flush_type); + + if (buf_pool->n_flush[flush_type] == 0) { + + os_event_reset(buf_pool->no_flush[flush_type]); + } + + buf_pool->n_flush[flush_type]++; + + is_uncompressed = (buf_page_get_state(bpage) == BUF_BLOCK_FILE_PAGE); + ut_ad(is_uncompressed == (block_mutex != &buf_pool_zip_mutex)); + + switch (flush_type) { + ibool is_s_latched; + case BUF_FLUSH_LIST: + /* If the simulated aio thread is not running, we must + not wait for any latch, as we may end up in a deadlock: + if buf_fix_count == 0, then we know we need not wait */ + + is_s_latched = (bpage->buf_fix_count == 0); + if (is_s_latched && is_uncompressed) { + rw_lock_s_lock_gen(&((buf_block_t*) bpage)->lock, + BUF_IO_WRITE); + } + + mutex_exit(block_mutex); + //buf_pool_mutex_exit(); + mutex_exit(&buf_pool_mutex); + + /* Even though bpage is not protected by any mutex at + this point, it is safe to access bpage, because it is + io_fixed and oldest_modification != 0. Thus, it + cannot be relocated in the buffer pool or removed from + flush_list or LRU_list. */ + + if (!is_s_latched) { + buf_flush_buffered_writes(); + + if (is_uncompressed) { + rw_lock_s_lock_gen(&((buf_block_t*) bpage) + ->lock, BUF_IO_WRITE); + } + } + + break; + + case BUF_FLUSH_LRU: + /* VERY IMPORTANT: + Because any thread may call the LRU flush, even when owning + locks on pages, to avoid deadlocks, we must make sure that the + s-lock is acquired on the page without waiting: this is + accomplished because buf_flush_ready_for_flush() must hold, + and that requires the page not to be bufferfixed. */ + + if (is_uncompressed) { + rw_lock_s_lock_gen(&((buf_block_t*) bpage)->lock, + BUF_IO_WRITE); + } + + /* Note that the s-latch is acquired before releasing the + buf_pool mutex: this ensures that the latch is acquired + immediately. */ + + mutex_exit(block_mutex); + //buf_pool_mutex_exit(); + mutex_exit(&buf_pool_mutex); + break; + + default: + ut_error; + } + + /* Even though bpage is not protected by any mutex at this + point, it is safe to access bpage, because it is io_fixed and + oldest_modification != 0. Thus, it cannot be relocated in the + buffer pool or removed from flush_list or LRU_list. */ + +#ifdef UNIV_DEBUG + if (buf_debug_prints) { + fprintf(stderr, + "Flushing %u space %u page %u\n", + flush_type, bpage->space, bpage->offset); + } +#endif /* UNIV_DEBUG */ + buf_flush_write_block_low(bpage); +} + +/*************************************************************** +Flushes to disk all flushable pages within the flush area. */ +static +ulint +buf_flush_try_neighbors( +/*====================*/ + /* out: number of pages flushed */ + ulint space, /* in: space id */ + ulint offset, /* in: page offset */ + enum buf_flush flush_type) /* in: BUF_FLUSH_LRU or + BUF_FLUSH_LIST */ +{ + buf_page_t* bpage; + ulint low, high; + ulint count = 0; + ulint i; + + ut_ad(flush_type == BUF_FLUSH_LRU || flush_type == BUF_FLUSH_LIST); + + if (UT_LIST_GET_LEN(buf_pool->LRU) < BUF_LRU_OLD_MIN_LEN) { + /* If there is little space, it is better not to flush any + block except from the end of the LRU list */ + + low = offset; + high = offset + 1; + } else { + /* When flushed, dirty blocks are searched in neighborhoods of + this size, and flushed along with the original page. */ + + ulint buf_flush_area = ut_min(BUF_READ_AHEAD_AREA, + buf_pool->curr_size / 16); + + low = (offset / buf_flush_area) * buf_flush_area; + high = (offset / buf_flush_area + 1) * buf_flush_area; + } + + /* fprintf(stderr, "Flush area: low %lu high %lu\n", low, high); */ + + if (high > fil_space_get_size(space)) { + high = fil_space_get_size(space); + } + + //buf_pool_mutex_enter(); + rw_lock_s_lock(&page_hash_latch); + + for (i = low; i < high; i++) { + + bpage = buf_page_hash_get(space, i); + + if (!bpage) { + + continue; + } + + ut_a(buf_page_in_file(bpage)); + + /* We avoid flushing 'non-old' blocks in an LRU flush, + because the flushed blocks are soon freed */ + + if (flush_type != BUF_FLUSH_LRU + || i == offset + || buf_page_is_old(bpage)) { + mutex_t* block_mutex = buf_page_get_mutex(bpage); + +retry_lock: + mutex_enter(block_mutex); + if (block_mutex != buf_page_get_mutex(bpage)) { + mutex_exit(block_mutex); + block_mutex = buf_page_get_mutex(bpage); + goto retry_lock; + } + + if (buf_flush_ready_for_flush(bpage, flush_type) + && (i == offset || !bpage->buf_fix_count)) { + /* We only try to flush those + neighbors != offset where the buf fix count is + zero, as we then know that we probably can + latch the page without a semaphore wait. + Semaphore waits are expensive because we must + flush the doublewrite buffer before we start + waiting. */ + + buf_flush_page(bpage, flush_type); + ut_ad(!mutex_own(block_mutex)); + count++; + + //buf_pool_mutex_enter(); + rw_lock_s_lock(&page_hash_latch); + } else { + mutex_exit(block_mutex); + } + } + } + + //buf_pool_mutex_exit(); + rw_lock_s_unlock(&page_hash_latch); + + return(count); +} + +/*********************************************************************** +This utility flushes dirty blocks from the end of the LRU list or flush_list. +NOTE 1: in the case of an LRU flush the calling thread may own latches to +pages: to avoid deadlocks, this function must be written so that it cannot +end up waiting for these latches! NOTE 2: in the case of a flush list flush, +the calling thread is not allowed to own any latches on pages! */ +UNIV_INTERN +ulint +buf_flush_batch( +/*============*/ + /* out: number of blocks for which the + write request was queued; + ULINT_UNDEFINED if there was a flush + of the same type already running */ + enum buf_flush flush_type, /* in: BUF_FLUSH_LRU or + BUF_FLUSH_LIST; if BUF_FLUSH_LIST, + then the caller must not own any + latches on pages */ + ulint min_n, /* in: wished minimum mumber of blocks + flushed (it is not guaranteed that the + actual number is that big, though) */ + ib_uint64_t lsn_limit) /* in the case BUF_FLUSH_LIST all + blocks whose oldest_modification is + smaller than this should be flushed + (if their number does not exceed + min_n), otherwise ignored */ +{ + buf_page_t* bpage; + ulint page_count = 0; + ulint old_page_count; + ulint space; + ulint offset; + ulint remaining = 0; + + ut_ad((flush_type == BUF_FLUSH_LRU) + || (flush_type == BUF_FLUSH_LIST)); +#ifdef UNIV_SYNC_DEBUG + ut_ad((flush_type != BUF_FLUSH_LIST) + || sync_thread_levels_empty_gen(TRUE)); +#endif /* UNIV_SYNC_DEBUG */ + //buf_pool_mutex_enter(); + mutex_enter(&buf_pool_mutex); + + if ((buf_pool->n_flush[flush_type] > 0) + || (buf_pool->init_flush[flush_type] == TRUE)) { + + /* There is already a flush batch of the same type running */ + + //buf_pool_mutex_exit(); + mutex_exit(&buf_pool_mutex); + + return(ULINT_UNDEFINED); + } + + buf_pool->init_flush[flush_type] = TRUE; + + mutex_exit(&buf_pool_mutex); + + if (flush_type == BUF_FLUSH_LRU) { + mutex_enter(&LRU_list_mutex); + } + + for (;;) { +flush_next: + /* If we have flushed enough, leave the loop */ + if (page_count >= min_n) { + + break; + } + + /* Start from the end of the list looking for a suitable + block to be flushed. */ + + if (flush_type == BUF_FLUSH_LRU) { + bpage = UT_LIST_GET_LAST(buf_pool->LRU); + } else { + ut_ad(flush_type == BUF_FLUSH_LIST); + + mutex_enter(&flush_list_mutex); + remaining = UT_LIST_GET_LEN(buf_pool->flush_list); + bpage = UT_LIST_GET_LAST(buf_pool->flush_list); + mutex_exit(&flush_list_mutex); + if (!bpage + || bpage->oldest_modification >= lsn_limit) { + /* We have flushed enough */ + + break; + } + ut_ad(bpage->in_flush_list); + } + + /* Note that after finding a single flushable page, we try to + flush also all its neighbors, and after that start from the + END of the LRU list or flush list again: the list may change + during the flushing and we cannot safely preserve within this + function a pointer to a block in the list! */ + + do { + mutex_t*block_mutex = buf_page_get_mutex(bpage); + ibool ready; + +retry_lock_1: + ut_a(buf_page_in_file(bpage)); + + mutex_enter(block_mutex); + if (block_mutex != buf_page_get_mutex(bpage)) { + mutex_exit(block_mutex); + block_mutex = buf_page_get_mutex(bpage); + goto retry_lock_1; + } + ready = buf_flush_ready_for_flush(bpage, flush_type); + mutex_exit(block_mutex); + + if (ready) { + space = buf_page_get_space(bpage); + offset = buf_page_get_page_no(bpage); + + //buf_pool_mutex_exit(); + if (flush_type == BUF_FLUSH_LRU) { + mutex_exit(&LRU_list_mutex); + } + + old_page_count = page_count; + + if (srv_flush_neighbor_pages) { + /* Try to flush also all the neighbors */ + page_count += buf_flush_try_neighbors( + space, offset, flush_type); + } else { + /* Try to flush the page only */ + //buf_pool_mutex_enter(); + rw_lock_s_lock(&page_hash_latch); + + mutex_t* block_mutex = buf_page_get_mutex(bpage); +retry_lock_2: + mutex_enter(block_mutex); + if (block_mutex != buf_page_get_mutex(bpage)) { + mutex_exit(block_mutex); + block_mutex = buf_page_get_mutex(bpage); + goto retry_lock_2; + } + + buf_page_t* bpage_tmp = buf_page_hash_get(space, offset); + if (bpage_tmp) { + buf_flush_page(bpage_tmp, flush_type); + page_count++; + } + } + /* fprintf(stderr, + "Flush type %lu, page no %lu, neighb %lu\n", + flush_type, offset, + page_count - old_page_count); */ + + //buf_pool_mutex_enter(); + if (flush_type == BUF_FLUSH_LRU) { + mutex_enter(&LRU_list_mutex); + } + goto flush_next; + + } else if (flush_type == BUF_FLUSH_LRU) { + bpage = UT_LIST_GET_PREV(LRU, bpage); + } else { + ut_ad(flush_type == BUF_FLUSH_LIST); + + mutex_enter(&flush_list_mutex); + bpage = UT_LIST_GET_PREV(flush_list, bpage); + //ut_ad(!bpage || bpage->in_flush_list); /* optimistic */ + mutex_exit(&flush_list_mutex); + remaining--; + } + } while (bpage != NULL); + + if (remaining) + goto flush_next; + + /* If we could not find anything to flush, leave the loop */ + + break; + } + + if (flush_type == BUF_FLUSH_LRU) { + mutex_exit(&LRU_list_mutex); + } + + mutex_enter(&buf_pool_mutex); + + buf_pool->init_flush[flush_type] = FALSE; + + if (buf_pool->n_flush[flush_type] == 0) { + + /* The running flush batch has ended */ + + os_event_set(buf_pool->no_flush[flush_type]); + } + + //buf_pool_mutex_exit(); + mutex_exit(&buf_pool_mutex); + + buf_flush_buffered_writes(); + +#ifdef UNIV_DEBUG + if (buf_debug_prints && page_count > 0) { + ut_a(flush_type == BUF_FLUSH_LRU + || flush_type == BUF_FLUSH_LIST); + fprintf(stderr, flush_type == BUF_FLUSH_LRU + ? "Flushed %lu pages in LRU flush\n" + : "Flushed %lu pages in flush list flush\n", + (ulong) page_count); + } +#endif /* UNIV_DEBUG */ + + srv_buf_pool_flushed += page_count; + + return(page_count); +} + +/********************************************************************** +Waits until a flush batch of the given type ends */ +UNIV_INTERN +void +buf_flush_wait_batch_end( +/*=====================*/ + enum buf_flush type) /* in: BUF_FLUSH_LRU or BUF_FLUSH_LIST */ +{ + ut_ad((type == BUF_FLUSH_LRU) || (type == BUF_FLUSH_LIST)); + + os_event_wait(buf_pool->no_flush[type]); +} + +/********************************************************************** +Gives a recommendation of how many blocks should be flushed to establish +a big enough margin of replaceable blocks near the end of the LRU list +and in the free list. */ +static +ulint +buf_flush_LRU_recommendation(void) +/*==============================*/ + /* out: number of blocks which should be flushed + from the end of the LRU list */ +{ + buf_page_t* bpage; + ulint n_replaceable; + ulint distance = 0; + ibool have_LRU_mutex = FALSE; + + if(UT_LIST_GET_LEN(buf_pool->unzip_LRU)) + have_LRU_mutex = TRUE; + + //buf_pool_mutex_enter(); + if (have_LRU_mutex) + mutex_enter(&LRU_list_mutex); + + n_replaceable = UT_LIST_GET_LEN(buf_pool->free); + + bpage = UT_LIST_GET_LAST(buf_pool->LRU); + + while ((bpage != NULL) + && (n_replaceable < BUF_FLUSH_FREE_BLOCK_MARGIN + + BUF_FLUSH_EXTRA_MARGIN) + && (distance < BUF_LRU_FREE_SEARCH_LEN)) { + + if (!bpage->in_LRU_list) { + /* reatart. but it is very optimistic */ + bpage = UT_LIST_GET_LAST(buf_pool->LRU); + continue; + } + + mutex_t* block_mutex = buf_page_get_mutex(bpage); + +retry_lock: + mutex_enter(block_mutex); + if (block_mutex != buf_page_get_mutex(bpage)) { + mutex_exit(block_mutex); + block_mutex = buf_page_get_mutex(bpage); + goto retry_lock; + } + + if (buf_flush_ready_for_replace(bpage)) { + n_replaceable++; + } + + mutex_exit(block_mutex); + + distance++; + + bpage = UT_LIST_GET_PREV(LRU, bpage); + } + + //buf_pool_mutex_exit(); + if (have_LRU_mutex) + mutex_exit(&LRU_list_mutex); + + if (n_replaceable >= BUF_FLUSH_FREE_BLOCK_MARGIN) { + + return(0); + } + + return(BUF_FLUSH_FREE_BLOCK_MARGIN + BUF_FLUSH_EXTRA_MARGIN + - n_replaceable); +} + +/************************************************************************* +Flushes pages from the end of the LRU list if there is too small a margin +of replaceable pages there or in the free list. VERY IMPORTANT: this function +is called also by threads which have locks on pages. To avoid deadlocks, we +flush only pages such that the s-lock required for flushing can be acquired +immediately, without waiting. */ +UNIV_INTERN +void +buf_flush_free_margin( +/*=======================*/ + ibool wait) +{ + ulint n_to_flush; + ulint n_flushed; + + n_to_flush = buf_flush_LRU_recommendation(); + + if (n_to_flush > 0) { + n_flushed = buf_flush_batch(BUF_FLUSH_LRU, n_to_flush, 0); + if (wait && n_flushed == ULINT_UNDEFINED) { + /* There was an LRU type flush batch already running; + let us wait for it to end */ + + buf_flush_wait_batch_end(BUF_FLUSH_LRU); + } + } +} + +#if defined UNIV_DEBUG || defined UNIV_BUF_DEBUG +/********************************************************************** +Validates the flush list. */ +static +ibool +buf_flush_validate_low(void) +/*========================*/ + /* out: TRUE if ok */ +{ + buf_page_t* bpage; + + UT_LIST_VALIDATE(flush_list, buf_page_t, buf_pool->flush_list); + + bpage = UT_LIST_GET_FIRST(buf_pool->flush_list); + + while (bpage != NULL) { + const ib_uint64_t om = bpage->oldest_modification; + ut_ad(bpage->in_flush_list); + //ut_a(buf_page_in_file(bpage)); /* optimistic */ + ut_a(om > 0); + + bpage = UT_LIST_GET_NEXT(flush_list, bpage); + + ut_a(!bpage || om >= bpage->oldest_modification); + } + + return(TRUE); +} + +/********************************************************************** +Validates the flush list. */ +UNIV_INTERN +ibool +buf_flush_validate(void) +/*====================*/ + /* out: TRUE if ok */ +{ + ibool ret; + + //buf_pool_mutex_enter(); + mutex_enter(&flush_list_mutex); + + ret = buf_flush_validate_low(); + + //buf_pool_mutex_exit(); + mutex_exit(&flush_list_mutex); + + return(ret); +} +#endif /* UNIV_DEBUG || UNIV_BUF_DEBUG */ diff --git a/storage/xtradb/buf/buf0lru.c b/storage/xtradb/buf/buf0lru.c new file mode 100644 index 00000000000..e5613196dd9 --- /dev/null +++ b/storage/xtradb/buf/buf0lru.c @@ -0,0 +1,2229 @@ +/***************************************************************************** + +Copyright (c) 1995, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/****************************************************** +The database buffer replacement algorithm + +Created 11/5/1995 Heikki Tuuri +*******************************************************/ + +#include "buf0lru.h" + +#ifdef UNIV_NONINL +#include "buf0lru.ic" +#endif + +#include "ut0byte.h" +#include "ut0lst.h" +#include "ut0rnd.h" +#include "sync0sync.h" +#include "sync0rw.h" +#include "hash0hash.h" +#include "os0sync.h" +#include "fil0fil.h" +#include "btr0btr.h" +#include "buf0buddy.h" +#include "buf0buf.h" +#include "buf0flu.h" +#include "buf0rea.h" +#include "btr0sea.h" +#include "ibuf0ibuf.h" +#include "os0file.h" +#include "page0zip.h" +#include "log0recv.h" +#include "srv0srv.h" + +/* The number of blocks from the LRU_old pointer onward, including the block +pointed to, must be 3/8 of the whole LRU list length, except that the +tolerance defined below is allowed. Note that the tolerance must be small +enough such that for even the BUF_LRU_OLD_MIN_LEN long LRU list, the +LRU_old pointer is not allowed to point to either end of the LRU list. */ + +#define BUF_LRU_OLD_TOLERANCE 20 + +/* The whole LRU list length is divided by this number to determine an +initial segment in buf_LRU_get_recent_limit */ + +#define BUF_LRU_INITIAL_RATIO 8 + +/* When dropping the search hash index entries before deleting an ibd +file, we build a local array of pages belonging to that tablespace +in the buffer pool. Following is the size of that array. */ +#define BUF_LRU_DROP_SEARCH_HASH_SIZE 1024 + +/* If we switch on the InnoDB monitor because there are too few available +frames in the buffer pool, we set this to TRUE */ +UNIV_INTERN ibool buf_lru_switched_on_innodb_mon = FALSE; + +/********************************************************************** +These statistics are not 'of' LRU but 'for' LRU. We keep count of I/O +and page_zip_decompress() operations. Based on the statistics, +buf_LRU_evict_from_unzip_LRU() decides if we want to evict from +unzip_LRU or the regular LRU. From unzip_LRU, we will only evict the +uncompressed frame (meaning we can evict dirty blocks as well). From +the regular LRU, we will evict the entire block (i.e.: both the +uncompressed and compressed data), which must be clean. */ + +/* Number of intervals for which we keep the history of these stats. +Each interval is 1 second, defined by the rate at which +srv_error_monitor_thread() calls buf_LRU_stat_update(). */ +#define BUF_LRU_STAT_N_INTERVAL 50 + +/* Co-efficient with which we multiply I/O operations to equate them +with page_zip_decompress() operations. */ +#define BUF_LRU_IO_TO_UNZIP_FACTOR 50 + +/* Sampled values buf_LRU_stat_cur. +Protected by buf_pool_mutex. Updated by buf_LRU_stat_update(). */ +static buf_LRU_stat_t buf_LRU_stat_arr[BUF_LRU_STAT_N_INTERVAL]; +/* Cursor to buf_LRU_stat_arr[] that is updated in a round-robin fashion. */ +static ulint buf_LRU_stat_arr_ind; + +/* Current operation counters. Not protected by any mutex. Cleared +by buf_LRU_stat_update(). */ +UNIV_INTERN buf_LRU_stat_t buf_LRU_stat_cur; + +/* Running sum of past values of buf_LRU_stat_cur. +Updated by buf_LRU_stat_update(). Protected by buf_pool_mutex. */ +UNIV_INTERN buf_LRU_stat_t buf_LRU_stat_sum; + +/********************************************************************** +Takes a block out of the LRU list and page hash table. +If the block is compressed-only (BUF_BLOCK_ZIP_PAGE), +the object will be freed and buf_pool_zip_mutex will be released. + +If a compressed page or a compressed-only block descriptor is freed, +other compressed pages or compressed-only block descriptors may be +relocated. */ +static +enum buf_page_state +buf_LRU_block_remove_hashed_page( +/*=============================*/ + /* out: the new state of the block + (BUF_BLOCK_ZIP_FREE if the state was + BUF_BLOCK_ZIP_PAGE, or BUF_BLOCK_REMOVE_HASH + otherwise) */ + buf_page_t* bpage, /* in: block, must contain a file page and + be in a state where it can be freed; there + may or may not be a hash index to the page */ + ibool zip); /* in: TRUE if should remove also the + compressed page of an uncompressed page */ +/********************************************************************** +Puts a file page whose has no hash index to the free list. */ +static +void +buf_LRU_block_free_hashed_page( +/*===========================*/ + buf_block_t* block, /* in: block, must contain a file page and + be in a state where it can be freed */ + ibool have_page_hash_mutex); + +/********************************************************************** +Determines if the unzip_LRU list should be used for evicting a victim +instead of the general LRU list. */ +UNIV_INLINE +ibool +buf_LRU_evict_from_unzip_LRU( + ibool have_LRU_mutex) +/*==============================*/ + /* out: TRUE if should use unzip_LRU */ +{ + ulint io_avg; + ulint unzip_avg; + + //ut_ad(buf_pool_mutex_own()); + + if (!have_LRU_mutex) + mutex_enter(&LRU_list_mutex); + /* If the unzip_LRU list is empty, we can only use the LRU. */ + if (UT_LIST_GET_LEN(buf_pool->unzip_LRU) == 0) { + if (!have_LRU_mutex) + mutex_exit(&LRU_list_mutex); + return(FALSE); + } + + /* If unzip_LRU is at most 10% of the size of the LRU list, + then use the LRU. This slack allows us to keep hot + decompressed pages in the buffer pool. */ + if (UT_LIST_GET_LEN(buf_pool->unzip_LRU) + <= UT_LIST_GET_LEN(buf_pool->LRU) / 10) { + if (!have_LRU_mutex) + mutex_exit(&LRU_list_mutex); + return(FALSE); + } + + /* If eviction hasn't started yet, we assume by default + that a workload is disk bound. */ + if (buf_pool->freed_page_clock == 0) { + if (!have_LRU_mutex) + mutex_exit(&LRU_list_mutex); + return(TRUE); + } + if (!have_LRU_mutex) + mutex_exit(&LRU_list_mutex); + + /* Calculate the average over past intervals, and add the values + of the current interval. */ + io_avg = buf_LRU_stat_sum.io / BUF_LRU_STAT_N_INTERVAL + + buf_LRU_stat_cur.io; + unzip_avg = buf_LRU_stat_sum.unzip / BUF_LRU_STAT_N_INTERVAL + + buf_LRU_stat_cur.unzip; + + /* Decide based on our formula. If the load is I/O bound + (unzip_avg is smaller than the weighted io_avg), evict an + uncompressed frame from unzip_LRU. Otherwise we assume that + the load is CPU bound and evict from the regular LRU. */ + return(unzip_avg <= io_avg * BUF_LRU_IO_TO_UNZIP_FACTOR); +} + +/********************************************************************** +Attempts to drop page hash index on a batch of pages belonging to a +particular space id. */ +static +void +buf_LRU_drop_page_hash_batch( +/*=========================*/ + ulint space_id, /* in: space id */ + ulint zip_size, /* in: compressed page size in bytes + or 0 for uncompressed pages */ + const ulint* arr, /* in: array of page_no */ + ulint count) /* in: number of entries in array */ +{ + ulint i; + + ut_ad(arr != NULL); + ut_ad(count <= BUF_LRU_DROP_SEARCH_HASH_SIZE); + + for (i = 0; i < count; ++i) { + btr_search_drop_page_hash_when_freed(space_id, zip_size, + arr[i]); + } +} + +/********************************************************************** +When doing a DROP TABLE/DISCARD TABLESPACE we have to drop all page +hash index entries belonging to that table. This function tries to +do that in batch. Note that this is a 'best effort' attempt and does +not guarantee that ALL hash entries will be removed. */ +static +void +buf_LRU_drop_page_hash_for_tablespace( +/*==================================*/ + ulint id) /* in: space id */ +{ + buf_page_t* bpage; + ulint* page_arr; + ulint num_entries; + ulint zip_size; + + zip_size = fil_space_get_zip_size(id); + + if (UNIV_UNLIKELY(zip_size == ULINT_UNDEFINED)) { + /* Somehow, the tablespace does not exist. Nothing to drop. */ + ut_ad(0); + return; + } + + page_arr = ut_malloc(sizeof(ulint) + * BUF_LRU_DROP_SEARCH_HASH_SIZE); + //buf_pool_mutex_enter(); + mutex_enter(&LRU_list_mutex); + +scan_again: + num_entries = 0; + bpage = UT_LIST_GET_LAST(buf_pool->LRU); + + while (bpage != NULL) { + mutex_t* block_mutex = buf_page_get_mutex(bpage); + buf_page_t* prev_bpage; + +retry_lock: + mutex_enter(block_mutex); + if (block_mutex != buf_page_get_mutex(bpage)) { + mutex_exit(block_mutex); + block_mutex = buf_page_get_mutex(bpage); + goto retry_lock; + } + prev_bpage = UT_LIST_GET_PREV(LRU, bpage); + + ut_a(buf_page_in_file(bpage)); + + if (buf_page_get_state(bpage) != BUF_BLOCK_FILE_PAGE + || bpage->space != id + || bpage->buf_fix_count > 0 + || bpage->io_fix != BUF_IO_NONE) { + /* We leave the fixed pages as is in this scan. + To be dealt with later in the final scan. */ + mutex_exit(block_mutex); + goto next_page; + } + + if (((buf_block_t*) bpage)->is_hashed) { + + /* Store the offset(i.e.: page_no) in the array + so that we can drop hash index in a batch + later. */ + page_arr[num_entries] = bpage->offset; + mutex_exit(block_mutex); + ut_a(num_entries < BUF_LRU_DROP_SEARCH_HASH_SIZE); + ++num_entries; + + if (num_entries < BUF_LRU_DROP_SEARCH_HASH_SIZE) { + goto next_page; + } + /* Array full. We release the buf_pool_mutex to + obey the latching order. */ + //buf_pool_mutex_exit(); + mutex_exit(&LRU_list_mutex); + + buf_LRU_drop_page_hash_batch(id, zip_size, page_arr, + num_entries); + num_entries = 0; + //buf_pool_mutex_enter(); + mutex_enter(&LRU_list_mutex); + } else { + mutex_exit(block_mutex); + } + +next_page: + /* Note that we may have released the buf_pool mutex + above after reading the prev_bpage during processing + of a page_hash_batch (i.e.: when the array was full). + This means that prev_bpage can change in LRU list. + This is OK because this function is a 'best effort' + to drop as many search hash entries as possible and + it does not guarantee that ALL such entries will be + dropped. */ + bpage = prev_bpage; + + /* If, however, bpage has been removed from LRU list + to the free list then we should restart the scan. + bpage->state is protected by buf_pool mutex. */ + if (bpage && !buf_page_in_file(bpage)) { + ut_a(num_entries == 0); + goto scan_again; + } + } + + //buf_pool_mutex_exit(); + mutex_exit(&LRU_list_mutex); + + /* Drop any remaining batch of search hashed pages. */ + buf_LRU_drop_page_hash_batch(id, zip_size, page_arr, num_entries); + ut_free(page_arr); +} + +/********************************************************************** +Invalidates all pages belonging to a given tablespace when we are deleting +the data file(s) of that tablespace. */ +UNIV_INTERN +void +buf_LRU_invalidate_tablespace( +/*==========================*/ + ulint id) /* in: space id */ +{ + buf_page_t* bpage; + ibool all_freed; + + /* Before we attempt to drop pages one by one we first + attempt to drop page hash index entries in batches to make + it more efficient. The batching attempt is a best effort + attempt and does not guarantee that all pages hash entries + will be dropped. We get rid of remaining page hash entries + one by one below. */ + buf_LRU_drop_page_hash_for_tablespace(id); + +scan_again: + //buf_pool_mutex_enter(); + mutex_enter(&LRU_list_mutex); + rw_lock_x_lock(&page_hash_latch); + + all_freed = TRUE; + + bpage = UT_LIST_GET_LAST(buf_pool->LRU); + + while (bpage != NULL) { + mutex_t* block_mutex = buf_page_get_mutex(bpage); + buf_page_t* prev_bpage; + + ut_a(buf_page_in_file(bpage)); + +retry_lock: + mutex_enter(block_mutex); + if (block_mutex != buf_page_get_mutex(bpage)) { + mutex_exit(block_mutex); + block_mutex = buf_page_get_mutex(bpage); + goto retry_lock; + } + prev_bpage = UT_LIST_GET_PREV(LRU, bpage); + + if (buf_page_get_space(bpage) == id) { + if (bpage->buf_fix_count > 0 + || buf_page_get_io_fix(bpage) != BUF_IO_NONE) { + + /* We cannot remove this page during + this scan yet; maybe the system is + currently reading it in, or flushing + the modifications to the file */ + + all_freed = FALSE; + + goto next_page; + } + +#ifdef UNIV_DEBUG + if (buf_debug_prints) { + fprintf(stderr, + "Dropping space %lu page %lu\n", + (ulong) buf_page_get_space(bpage), + (ulong) buf_page_get_page_no(bpage)); + } +#endif + if (buf_page_get_state(bpage) == BUF_BLOCK_FILE_PAGE + && ((buf_block_t*) bpage)->is_hashed) { + ulint page_no; + ulint zip_size; + + //buf_pool_mutex_exit(); + mutex_exit(&LRU_list_mutex); + rw_lock_x_unlock(&page_hash_latch); + + zip_size = buf_page_get_zip_size(bpage); + page_no = buf_page_get_page_no(bpage); + + mutex_exit(block_mutex); + + /* Note that the following call will acquire + an S-latch on the page */ + + btr_search_drop_page_hash_when_freed( + id, zip_size, page_no); + goto scan_again; + } + + if (bpage->oldest_modification != 0) { + + buf_flush_remove(bpage); + } + + /* Remove from the LRU list */ + if (buf_LRU_block_remove_hashed_page(bpage, TRUE) + != BUF_BLOCK_ZIP_FREE) { + buf_LRU_block_free_hashed_page((buf_block_t*) + bpage, TRUE); + } else { + /* The block_mutex should have been + released by buf_LRU_block_remove_hashed_page() + when it returns BUF_BLOCK_ZIP_FREE. */ + ut_ad(block_mutex == &buf_pool_zip_mutex); + ut_ad(!mutex_own(block_mutex)); + + /* The compressed block descriptor + (bpage) has been deallocated and + block_mutex released. Also, + buf_buddy_free() may have relocated + prev_bpage. Rescan the LRU list. */ + + bpage = UT_LIST_GET_LAST(buf_pool->LRU); + continue; + } + } +next_page: + mutex_exit(block_mutex); + bpage = prev_bpage; + } + + //buf_pool_mutex_exit(); + mutex_exit(&LRU_list_mutex); + rw_lock_x_unlock(&page_hash_latch); + + if (!all_freed) { + os_thread_sleep(20000); + + goto scan_again; + } +} + +/********************************************************************** +Gets the minimum LRU_position field for the blocks in an initial segment +(determined by BUF_LRU_INITIAL_RATIO) of the LRU list. The limit is not +guaranteed to be precise, because the ulint_clock may wrap around. */ +UNIV_INTERN +ulint +buf_LRU_get_recent_limit(void) +/*==========================*/ + /* out: the limit; zero if could not determine it */ +{ + const buf_page_t* bpage; + ulint len; + ulint limit; + + //buf_pool_mutex_enter(); + mutex_enter(&LRU_list_mutex); + + len = UT_LIST_GET_LEN(buf_pool->LRU); + + if (len < BUF_LRU_OLD_MIN_LEN) { + /* The LRU list is too short to do read-ahead */ + + //buf_pool_mutex_exit(); + mutex_exit(&LRU_list_mutex); + + return(0); + } + + bpage = UT_LIST_GET_FIRST(buf_pool->LRU); + + limit = buf_page_get_LRU_position(bpage) - len / BUF_LRU_INITIAL_RATIO; + + //buf_pool_mutex_exit(); + mutex_exit(&LRU_list_mutex); + + return(limit); +} + +/************************************************************************ +Insert a compressed block into buf_pool->zip_clean in the LRU order. */ +UNIV_INTERN +void +buf_LRU_insert_zip_clean( +/*=====================*/ + buf_page_t* bpage) /* in: pointer to the block in question */ +{ + buf_page_t* b; + + //ut_ad(buf_pool_mutex_own()); + ut_ad(mutex_own(&LRU_list_mutex)); + ut_ad(mutex_own(&flush_list_mutex)); + ut_ad(buf_page_get_state(bpage) == BUF_BLOCK_ZIP_PAGE); + + /* Find the first successor of bpage in the LRU list + that is in the zip_clean list. */ + b = bpage; + do { + b = UT_LIST_GET_NEXT(LRU, b); + } while (b && (buf_page_get_state(b) != BUF_BLOCK_ZIP_PAGE || !b->in_LRU_list)); + + /* Insert bpage before b, i.e., after the predecessor of b. */ + if (b) { + b = UT_LIST_GET_PREV(zip_list, b); + } + + if (b) { + UT_LIST_INSERT_AFTER(zip_list, buf_pool->zip_clean, b, bpage); + } else { + UT_LIST_ADD_FIRST(zip_list, buf_pool->zip_clean, bpage); + } +} + +/********************************************************************** +Try to free an uncompressed page of a compressed block from the unzip +LRU list. The compressed page is preserved, and it need not be clean. */ +UNIV_INLINE +ibool +buf_LRU_free_from_unzip_LRU_list( +/*=============================*/ + /* out: TRUE if freed */ + ulint n_iterations, /* in: how many times this has been called + repeatedly without result: a high value means + that we should search farther; we will search + n_iterations / 5 of the unzip_LRU list, + or nothing if n_iterations >= 5 */ + ibool have_LRU_mutex) +{ + buf_block_t* block; + ulint distance; + + //ut_ad(buf_pool_mutex_own()); /* optimistic */ + + /* Theoratically it should be much easier to find a victim + from unzip_LRU as we can choose even a dirty block (as we'll + be evicting only the uncompressed frame). In a very unlikely + eventuality that we are unable to find a victim from + unzip_LRU, we fall back to the regular LRU list. We do this + if we have done five iterations so far. */ + + if (UNIV_UNLIKELY(n_iterations >= 5) + || !buf_LRU_evict_from_unzip_LRU(have_LRU_mutex)) { + + return(FALSE); + } + + distance = 100 + (n_iterations + * UT_LIST_GET_LEN(buf_pool->unzip_LRU)) / 5; + +restart: + for (block = UT_LIST_GET_LAST(buf_pool->unzip_LRU); + UNIV_LIKELY(block != NULL) && UNIV_LIKELY(distance > 0); + block = UT_LIST_GET_PREV(unzip_LRU, block), distance--) { + + enum buf_lru_free_block_status freed; + + mutex_enter(&block->mutex); + if (!block->in_unzip_LRU_list || !block->page.in_LRU_list + || buf_block_get_state(block) != BUF_BLOCK_FILE_PAGE) { + mutex_exit(&block->mutex); + goto restart; + } + + ut_ad(buf_block_get_state(block) == BUF_BLOCK_FILE_PAGE); + ut_ad(block->in_unzip_LRU_list); + ut_ad(block->page.in_LRU_list); + + freed = buf_LRU_free_block(&block->page, FALSE, NULL, have_LRU_mutex); + mutex_exit(&block->mutex); + + switch (freed) { + case BUF_LRU_FREED: + return(TRUE); + + case BUF_LRU_CANNOT_RELOCATE: + /* If we failed to relocate, try + regular LRU eviction. */ + return(FALSE); + + case BUF_LRU_NOT_FREED: + /* The block was buffer-fixed or I/O-fixed. + Keep looking. */ + continue; + } + + /* inappropriate return value from + buf_LRU_free_block() */ + ut_error; + } + + return(FALSE); +} + +/********************************************************************** +Try to free a clean page from the common LRU list. */ +UNIV_INLINE +ibool +buf_LRU_free_from_common_LRU_list( +/*==============================*/ + /* out: TRUE if freed */ + ulint n_iterations, /* in: how many times this has been called + repeatedly without result: a high value means + that we should search farther; if + n_iterations < 10, then we search + n_iterations / 10 * buf_pool->curr_size + pages from the end of the LRU list */ + ibool have_LRU_mutex) +{ + buf_page_t* bpage; + ulint distance; + + //ut_ad(buf_pool_mutex_own()); /* optimistic */ + + distance = 100 + (n_iterations * buf_pool->curr_size) / 10; + +restart: + for (bpage = UT_LIST_GET_LAST(buf_pool->LRU); + UNIV_LIKELY(bpage != NULL) && UNIV_LIKELY(distance > 0); + bpage = UT_LIST_GET_PREV(LRU, bpage), distance--) { + + enum buf_lru_free_block_status freed; + mutex_t* block_mutex + = buf_page_get_mutex(bpage); + +retry_lock: + mutex_enter(block_mutex); + + if (block_mutex != buf_page_get_mutex(bpage)) { + mutex_exit(block_mutex); + block_mutex = buf_page_get_mutex(bpage); + goto retry_lock; + } + + if (!bpage->in_LRU_list + || !buf_page_in_file(bpage)) { + mutex_exit(block_mutex); + goto restart; + } + + ut_ad(buf_page_in_file(bpage)); + ut_ad(bpage->in_LRU_list); + + freed = buf_LRU_free_block(bpage, TRUE, NULL, have_LRU_mutex); + mutex_exit(block_mutex); + + switch (freed) { + case BUF_LRU_FREED: + return(TRUE); + + case BUF_LRU_NOT_FREED: + /* The block was dirty, buffer-fixed, or I/O-fixed. + Keep looking. */ + continue; + + case BUF_LRU_CANNOT_RELOCATE: + /* This should never occur, because we + want to discard the compressed page too. */ + break; + } + + /* inappropriate return value from + buf_LRU_free_block() */ + ut_error; + } + + return(FALSE); +} + +/********************************************************************** +Try to free a replaceable block. */ +UNIV_INTERN +ibool +buf_LRU_search_and_free_block( +/*==========================*/ + /* out: TRUE if found and freed */ + ulint n_iterations) /* in: how many times this has been called + repeatedly without result: a high value means + that we should search farther; if + n_iterations < 10, then we search + n_iterations / 10 * buf_pool->curr_size + pages from the end of the LRU list; if + n_iterations < 5, then we will also search + n_iterations / 5 of the unzip_LRU list. */ +{ + ibool freed = FALSE; + ibool have_LRU_mutex = FALSE; + + if (UT_LIST_GET_LEN(buf_pool->unzip_LRU)) + have_LRU_mutex = TRUE; + + /* optimistic search... */ + //buf_pool_mutex_enter(); + if (have_LRU_mutex) + mutex_enter(&LRU_list_mutex); + + freed = buf_LRU_free_from_unzip_LRU_list(n_iterations, have_LRU_mutex); + + if (!freed) { + freed = buf_LRU_free_from_common_LRU_list(n_iterations, have_LRU_mutex); + } + + mutex_enter(&buf_pool_mutex); + if (!freed) { + buf_pool->LRU_flush_ended = 0; + } else if (buf_pool->LRU_flush_ended > 0) { + buf_pool->LRU_flush_ended--; + } + mutex_exit(&buf_pool_mutex); + + //buf_pool_mutex_exit(); + if (have_LRU_mutex) + mutex_exit(&LRU_list_mutex); + + return(freed); +} + +/********************************************************************** +Tries to remove LRU flushed blocks from the end of the LRU list and put them +to the free list. This is beneficial for the efficiency of the insert buffer +operation, as flushed pages from non-unique non-clustered indexes are here +taken out of the buffer pool, and their inserts redirected to the insert +buffer. Otherwise, the flushed blocks could get modified again before read +operations need new buffer blocks, and the i/o work done in flushing would be +wasted. */ +UNIV_INTERN +void +buf_LRU_try_free_flushed_blocks(void) +/*=================================*/ +{ + //buf_pool_mutex_enter(); + mutex_enter(&buf_pool_mutex); + + while (buf_pool->LRU_flush_ended > 0) { + + //buf_pool_mutex_exit(); + mutex_exit(&buf_pool_mutex); + + buf_LRU_search_and_free_block(1); + + //buf_pool_mutex_enter(); + mutex_enter(&buf_pool_mutex); + } + + //buf_pool_mutex_exit(); + mutex_exit(&buf_pool_mutex); +} + +/********************************************************************** +Returns TRUE if less than 25 % of the buffer pool is available. This can be +used in heuristics to prevent huge transactions eating up the whole buffer +pool for their locks. */ +UNIV_INTERN +ibool +buf_LRU_buf_pool_running_out(void) +/*==============================*/ + /* out: TRUE if less than 25 % of buffer pool + left */ +{ + ibool ret = FALSE; + + //buf_pool_mutex_enter(); + mutex_enter(&LRU_list_mutex); + mutex_enter(&free_list_mutex); + + if (!recv_recovery_on && UT_LIST_GET_LEN(buf_pool->free) + + UT_LIST_GET_LEN(buf_pool->LRU) < buf_pool->curr_size / 4) { + + ret = TRUE; + } + + //buf_pool_mutex_exit(); + mutex_exit(&LRU_list_mutex); + mutex_exit(&free_list_mutex); + + return(ret); +} + +/********************************************************************** +Returns a free block from the buf_pool. The block is taken off the +free list. If it is empty, returns NULL. */ +UNIV_INTERN +buf_block_t* +buf_LRU_get_free_only(void) +/*=======================*/ + /* out: a free control block, or NULL + if the buf_block->free list is empty */ +{ + buf_block_t* block; + + //ut_ad(buf_pool_mutex_own()); + + mutex_enter(&free_list_mutex); + block = (buf_block_t*) UT_LIST_GET_LAST(buf_pool->free); + + if (block) { + ut_ad(block->page.in_free_list); + ut_d(block->page.in_free_list = FALSE); + ut_ad(!block->page.in_flush_list); + ut_ad(!block->page.in_LRU_list); + ut_a(!buf_page_in_file(&block->page)); + UT_LIST_REMOVE(free, buf_pool->free, (&block->page)); + + mutex_exit(&free_list_mutex); + + mutex_enter(&block->mutex); + + buf_block_set_state(block, BUF_BLOCK_READY_FOR_USE); + UNIV_MEM_ALLOC(block->frame, UNIV_PAGE_SIZE); + + mutex_exit(&block->mutex); + } else { + mutex_exit(&free_list_mutex); + } + + return(block); +} + +/********************************************************************** +Returns a free block from the buf_pool. The block is taken off the +free list. If it is empty, blocks are moved from the end of the +LRU list to the free list. */ +UNIV_INTERN +buf_block_t* +buf_LRU_get_free_block( +/*===================*/ + /* out: the free control block, + in state BUF_BLOCK_READY_FOR_USE */ + ulint zip_size) /* in: compressed page size in bytes, + or 0 if uncompressed tablespace */ +{ + buf_block_t* block = NULL; + ibool freed; + ulint n_iterations = 1; + ibool mon_value_was = FALSE; + ibool started_monitor = FALSE; +loop: + //buf_pool_mutex_enter(); + + if (!recv_recovery_on && UT_LIST_GET_LEN(buf_pool->free) + + UT_LIST_GET_LEN(buf_pool->LRU) < buf_pool->curr_size / 20) { + ut_print_timestamp(stderr); + + fprintf(stderr, + " InnoDB: ERROR: over 95 percent of the buffer pool" + " is occupied by\n" + "InnoDB: lock heaps or the adaptive hash index!" + " Check that your\n" + "InnoDB: transactions do not set too many row locks.\n" + "InnoDB: Your buffer pool size is %lu MB." + " Maybe you should make\n" + "InnoDB: the buffer pool bigger?\n" + "InnoDB: We intentionally generate a seg fault" + " to print a stack trace\n" + "InnoDB: on Linux!\n", + (ulong) (buf_pool->curr_size + / (1024 * 1024 / UNIV_PAGE_SIZE))); + + ut_error; + + } else if (!recv_recovery_on + && (UT_LIST_GET_LEN(buf_pool->free) + + UT_LIST_GET_LEN(buf_pool->LRU)) + < buf_pool->curr_size / 3) { + + if (!buf_lru_switched_on_innodb_mon) { + + /* Over 67 % of the buffer pool is occupied by lock + heaps or the adaptive hash index. This may be a memory + leak! */ + + ut_print_timestamp(stderr); + fprintf(stderr, + " InnoDB: WARNING: over 67 percent of" + " the buffer pool is occupied by\n" + "InnoDB: lock heaps or the adaptive" + " hash index! Check that your\n" + "InnoDB: transactions do not set too many" + " row locks.\n" + "InnoDB: Your buffer pool size is %lu MB." + " Maybe you should make\n" + "InnoDB: the buffer pool bigger?\n" + "InnoDB: Starting the InnoDB Monitor to print" + " diagnostics, including\n" + "InnoDB: lock heap and hash index sizes.\n", + (ulong) (buf_pool->curr_size + / (1024 * 1024 / UNIV_PAGE_SIZE))); + + buf_lru_switched_on_innodb_mon = TRUE; + srv_print_innodb_monitor = TRUE; + os_event_set(srv_lock_timeout_thread_event); + } + } else if (buf_lru_switched_on_innodb_mon) { + + /* Switch off the InnoDB Monitor; this is a simple way + to stop the monitor if the situation becomes less urgent, + but may also surprise users if the user also switched on the + monitor! */ + + buf_lru_switched_on_innodb_mon = FALSE; + srv_print_innodb_monitor = FALSE; + } + + /* If there is a block in the free list, take it */ + block = buf_LRU_get_free_only(); + if (block) { + +#ifdef UNIV_DEBUG + block->page.zip.m_start = +#endif /* UNIV_DEBUG */ + block->page.zip.m_end = + block->page.zip.m_nonempty = + block->page.zip.n_blobs = 0; + + if (UNIV_UNLIKELY(zip_size)) { + ibool lru; + page_zip_set_size(&block->page.zip, zip_size); + mutex_enter(&LRU_list_mutex); + block->page.zip.data = buf_buddy_alloc(zip_size, &lru, FALSE); + mutex_exit(&LRU_list_mutex); + UNIV_MEM_DESC(block->page.zip.data, zip_size, block); + } else { + page_zip_set_size(&block->page.zip, 0); + block->page.zip.data = NULL; + } + + //buf_pool_mutex_exit(); + + if (started_monitor) { + srv_print_innodb_monitor = mon_value_was; + } + + return(block); + } + + /* If no block was in the free list, search from the end of the LRU + list and try to free a block there */ + + //buf_pool_mutex_exit(); + + freed = buf_LRU_search_and_free_block(n_iterations); + + if (freed > 0) { + goto loop; + } + + if (n_iterations > 30) { + ut_print_timestamp(stderr); + fprintf(stderr, + " InnoDB: Warning: difficult to find free blocks in\n" + "InnoDB: the buffer pool (%lu search iterations)!" + " Consider\n" + "InnoDB: increasing the buffer pool size.\n" + "InnoDB: It is also possible that" + " in your Unix version\n" + "InnoDB: fsync is very slow, or" + " completely frozen inside\n" + "InnoDB: the OS kernel. Then upgrading to" + " a newer version\n" + "InnoDB: of your operating system may help." + " Look at the\n" + "InnoDB: number of fsyncs in diagnostic info below.\n" + "InnoDB: Pending flushes (fsync) log: %lu;" + " buffer pool: %lu\n" + "InnoDB: %lu OS file reads, %lu OS file writes," + " %lu OS fsyncs\n" + "InnoDB: Starting InnoDB Monitor to print further\n" + "InnoDB: diagnostics to the standard output.\n", + (ulong) n_iterations, + (ulong) fil_n_pending_log_flushes, + (ulong) fil_n_pending_tablespace_flushes, + (ulong) os_n_file_reads, (ulong) os_n_file_writes, + (ulong) os_n_fsyncs); + + mon_value_was = srv_print_innodb_monitor; + started_monitor = TRUE; + srv_print_innodb_monitor = TRUE; + os_event_set(srv_lock_timeout_thread_event); + } + + /* No free block was found: try to flush the LRU list */ + + buf_flush_free_margin(TRUE); + ++srv_buf_pool_wait_free; + + os_aio_simulated_wake_handler_threads(); + + //buf_pool_mutex_enter(); + mutex_enter(&buf_pool_mutex); + + if (buf_pool->LRU_flush_ended > 0) { + /* We have written pages in an LRU flush. To make the insert + buffer more efficient, we try to move these pages to the free + list. */ + + //buf_pool_mutex_exit(); + mutex_exit(&buf_pool_mutex); + + buf_LRU_try_free_flushed_blocks(); + } else { + //buf_pool_mutex_exit(); + mutex_exit(&buf_pool_mutex); + } + + if (n_iterations > 10) { + + os_thread_sleep(500000); + } + + n_iterations++; + + goto loop; +} + +/*********************************************************************** +Moves the LRU_old pointer so that the length of the old blocks list +is inside the allowed limits. */ +UNIV_INLINE +void +buf_LRU_old_adjust_len(void) +/*========================*/ +{ + ulint old_len; + ulint new_len; + + ut_a(buf_pool->LRU_old); + //ut_ad(buf_pool_mutex_own()); + ut_ad(mutex_own(&LRU_list_mutex)); +#if 3 * (BUF_LRU_OLD_MIN_LEN / 8) <= BUF_LRU_OLD_TOLERANCE + 5 +# error "3 * (BUF_LRU_OLD_MIN_LEN / 8) <= BUF_LRU_OLD_TOLERANCE + 5" +#endif +#ifdef UNIV_LRU_DEBUG + /* buf_pool->LRU_old must be the first item in the LRU list + whose "old" flag is set. */ + ut_a(buf_pool->LRU_old->old); + ut_a(!UT_LIST_GET_PREV(LRU, buf_pool->LRU_old) + || !UT_LIST_GET_PREV(LRU, buf_pool->LRU_old)->old); + ut_a(!UT_LIST_GET_NEXT(LRU, buf_pool->LRU_old) + || UT_LIST_GET_NEXT(LRU, buf_pool->LRU_old)->old); +#endif /* UNIV_LRU_DEBUG */ + + for (;;) { + old_len = buf_pool->LRU_old_len; + new_len = 3 * (UT_LIST_GET_LEN(buf_pool->LRU) / 8); + + ut_ad(buf_pool->LRU_old->in_LRU_list); + ut_a(buf_pool->LRU_old); +#ifdef UNIV_LRU_DEBUG + ut_a(buf_pool->LRU_old->old); +#endif /* UNIV_LRU_DEBUG */ + + /* Update the LRU_old pointer if necessary */ + + if (old_len < new_len - BUF_LRU_OLD_TOLERANCE) { + + buf_pool->LRU_old = UT_LIST_GET_PREV( + LRU, buf_pool->LRU_old); +#ifdef UNIV_LRU_DEBUG + ut_a(!buf_pool->LRU_old->old); +#endif /* UNIV_LRU_DEBUG */ + buf_page_set_old(buf_pool->LRU_old, TRUE); + buf_pool->LRU_old_len++; + + } else if (old_len > new_len + BUF_LRU_OLD_TOLERANCE) { + + buf_page_set_old(buf_pool->LRU_old, FALSE); + buf_pool->LRU_old = UT_LIST_GET_NEXT( + LRU, buf_pool->LRU_old); + buf_pool->LRU_old_len--; + } else { + return; + } + } +} + +/*********************************************************************** +Initializes the old blocks pointer in the LRU list. This function should be +called when the LRU list grows to BUF_LRU_OLD_MIN_LEN length. */ +static +void +buf_LRU_old_init(void) +/*==================*/ +{ + buf_page_t* bpage; + + //ut_ad(buf_pool_mutex_own()); + ut_ad(mutex_own(&LRU_list_mutex)); + ut_a(UT_LIST_GET_LEN(buf_pool->LRU) == BUF_LRU_OLD_MIN_LEN); + + /* We first initialize all blocks in the LRU list as old and then use + the adjust function to move the LRU_old pointer to the right + position */ + + bpage = UT_LIST_GET_FIRST(buf_pool->LRU); + + while (bpage != NULL) { + ut_ad(bpage->in_LRU_list); + buf_page_set_old(bpage, TRUE); + bpage = UT_LIST_GET_NEXT(LRU, bpage); + } + + buf_pool->LRU_old = UT_LIST_GET_FIRST(buf_pool->LRU); + buf_pool->LRU_old_len = UT_LIST_GET_LEN(buf_pool->LRU); + + buf_LRU_old_adjust_len(); +} + +/********************************************************************** +Remove a block from the unzip_LRU list if it belonged to the list. */ +static +void +buf_unzip_LRU_remove_block_if_needed( +/*=================================*/ + buf_page_t* bpage) /* in/out: control block */ +{ + ut_ad(buf_pool); + ut_ad(bpage); + ut_ad(buf_page_in_file(bpage)); + //ut_ad(buf_pool_mutex_own()); + ut_ad(mutex_own(&LRU_list_mutex)); + + if (buf_page_belongs_to_unzip_LRU(bpage)) { + buf_block_t* block = (buf_block_t*) bpage; + + ut_ad(block->in_unzip_LRU_list); + block->in_unzip_LRU_list = FALSE; + + UT_LIST_REMOVE(unzip_LRU, buf_pool->unzip_LRU, block); + } +} + +/********************************************************************** +Removes a block from the LRU list. */ +UNIV_INLINE +void +buf_LRU_remove_block( +/*=================*/ + buf_page_t* bpage) /* in: control block */ +{ + ut_ad(buf_pool); + ut_ad(bpage); + //ut_ad(buf_pool_mutex_own()); + ut_ad(mutex_own(&LRU_list_mutex)); + + ut_a(buf_page_in_file(bpage)); + + ut_ad(bpage->in_LRU_list); + + /* If the LRU_old pointer is defined and points to just this block, + move it backward one step */ + + if (UNIV_UNLIKELY(bpage == buf_pool->LRU_old)) { + + /* Below: the previous block is guaranteed to exist, because + the LRU_old pointer is only allowed to differ by the + tolerance value from strict 3/8 of the LRU list length. */ + + buf_pool->LRU_old = UT_LIST_GET_PREV(LRU, bpage); + ut_a(buf_pool->LRU_old); +#ifdef UNIV_LRU_DEBUG + ut_a(!buf_pool->LRU_old->old); +#endif /* UNIV_LRU_DEBUG */ + buf_page_set_old(buf_pool->LRU_old, TRUE); + + buf_pool->LRU_old_len++; + } + + /* Remove the block from the LRU list */ + UT_LIST_REMOVE(LRU, buf_pool->LRU, bpage); + bpage->in_LRU_list = FALSE; + + buf_unzip_LRU_remove_block_if_needed(bpage); + + /* If the LRU list is so short that LRU_old not defined, return */ + if (UT_LIST_GET_LEN(buf_pool->LRU) < BUF_LRU_OLD_MIN_LEN) { + + buf_pool->LRU_old = NULL; + + return; + } + + ut_ad(buf_pool->LRU_old); + + /* Update the LRU_old_len field if necessary */ + if (buf_page_is_old(bpage)) { + + buf_pool->LRU_old_len--; + } + + /* Adjust the length of the old block list if necessary */ + buf_LRU_old_adjust_len(); +} + +/********************************************************************** +Adds a block to the LRU list of decompressed zip pages. */ +UNIV_INTERN +void +buf_unzip_LRU_add_block( +/*====================*/ + buf_block_t* block, /* in: control block */ + ibool old) /* in: TRUE if should be put to the end + of the list, else put to the start */ +{ + ut_ad(buf_pool); + ut_ad(block); + //ut_ad(buf_pool_mutex_own()); + ut_ad(mutex_own(&LRU_list_mutex)); + + ut_a(buf_page_belongs_to_unzip_LRU(&block->page)); + + ut_ad(!block->in_unzip_LRU_list); + block->in_unzip_LRU_list = TRUE; + + if (old) { + UT_LIST_ADD_LAST(unzip_LRU, buf_pool->unzip_LRU, block); + } else { + UT_LIST_ADD_FIRST(unzip_LRU, buf_pool->unzip_LRU, block); + } +} + +/********************************************************************** +Adds a block to the LRU list end. */ +UNIV_INLINE +void +buf_LRU_add_block_to_end_low( +/*=========================*/ + buf_page_t* bpage) /* in: control block */ +{ + buf_page_t* last_bpage; + + ut_ad(buf_pool); + ut_ad(bpage); + //ut_ad(buf_pool_mutex_own()); + ut_ad(mutex_own(&LRU_list_mutex)); + + ut_a(buf_page_in_file(bpage)); + + last_bpage = UT_LIST_GET_LAST(buf_pool->LRU); + + if (last_bpage) { + bpage->LRU_position = last_bpage->LRU_position; + } else { + bpage->LRU_position = buf_pool_clock_tic(); + } + + ut_ad(!bpage->in_LRU_list); + UT_LIST_ADD_LAST(LRU, buf_pool->LRU, bpage); + bpage->in_LRU_list = TRUE; + + buf_page_set_old(bpage, TRUE); + + if (UT_LIST_GET_LEN(buf_pool->LRU) >= BUF_LRU_OLD_MIN_LEN) { + + buf_pool->LRU_old_len++; + } + + if (UT_LIST_GET_LEN(buf_pool->LRU) > BUF_LRU_OLD_MIN_LEN) { + + ut_ad(buf_pool->LRU_old); + + /* Adjust the length of the old block list if necessary */ + + buf_LRU_old_adjust_len(); + + } else if (UT_LIST_GET_LEN(buf_pool->LRU) == BUF_LRU_OLD_MIN_LEN) { + + /* The LRU list is now long enough for LRU_old to become + defined: init it */ + + buf_LRU_old_init(); + } + + /* If this is a zipped block with decompressed frame as well + then put it on the unzip_LRU list */ + if (buf_page_belongs_to_unzip_LRU(bpage)) { + buf_unzip_LRU_add_block((buf_block_t*) bpage, TRUE); + } +} + +/********************************************************************** +Adds a block to the LRU list. */ +UNIV_INLINE +void +buf_LRU_add_block_low( +/*==================*/ + buf_page_t* bpage, /* in: control block */ + ibool old) /* in: TRUE if should be put to the old blocks + in the LRU list, else put to the start; if the + LRU list is very short, the block is added to + the start, regardless of this parameter */ +{ + ut_ad(buf_pool); + ut_ad(bpage); + //ut_ad(buf_pool_mutex_own()); + ut_ad(mutex_own(&LRU_list_mutex)); + + ut_a(buf_page_in_file(bpage)); + ut_ad(!bpage->in_LRU_list); + + if (!old || (UT_LIST_GET_LEN(buf_pool->LRU) < BUF_LRU_OLD_MIN_LEN)) { + + UT_LIST_ADD_FIRST(LRU, buf_pool->LRU, bpage); + + bpage->LRU_position = buf_pool_clock_tic(); + bpage->freed_page_clock = buf_pool->freed_page_clock; + } else { +#ifdef UNIV_LRU_DEBUG + /* buf_pool->LRU_old must be the first item in the LRU list + whose "old" flag is set. */ + ut_a(buf_pool->LRU_old->old); + ut_a(!UT_LIST_GET_PREV(LRU, buf_pool->LRU_old) + || !UT_LIST_GET_PREV(LRU, buf_pool->LRU_old)->old); + ut_a(!UT_LIST_GET_NEXT(LRU, buf_pool->LRU_old) + || UT_LIST_GET_NEXT(LRU, buf_pool->LRU_old)->old); +#endif /* UNIV_LRU_DEBUG */ + UT_LIST_INSERT_AFTER(LRU, buf_pool->LRU, buf_pool->LRU_old, + bpage); + buf_pool->LRU_old_len++; + + /* We copy the LRU position field of the previous block + to the new block */ + + bpage->LRU_position = (buf_pool->LRU_old)->LRU_position; + } + + bpage->in_LRU_list = TRUE; + + buf_page_set_old(bpage, old); + + if (UT_LIST_GET_LEN(buf_pool->LRU) > BUF_LRU_OLD_MIN_LEN) { + + ut_ad(buf_pool->LRU_old); + + /* Adjust the length of the old block list if necessary */ + + buf_LRU_old_adjust_len(); + + } else if (UT_LIST_GET_LEN(buf_pool->LRU) == BUF_LRU_OLD_MIN_LEN) { + + /* The LRU list is now long enough for LRU_old to become + defined: init it */ + + buf_LRU_old_init(); + } + + /* If this is a zipped block with decompressed frame as well + then put it on the unzip_LRU list */ + if (buf_page_belongs_to_unzip_LRU(bpage)) { + buf_unzip_LRU_add_block((buf_block_t*) bpage, old); + } +} + +/********************************************************************** +Adds a block to the LRU list. */ +UNIV_INTERN +void +buf_LRU_add_block( +/*==============*/ + buf_page_t* bpage, /* in: control block */ + ibool old) /* in: TRUE if should be put to the old + blocks in the LRU list, else put to the start; + if the LRU list is very short, the block is + added to the start, regardless of this + parameter */ +{ + buf_LRU_add_block_low(bpage, old); +} + +/********************************************************************** +Moves a block to the start of the LRU list. */ +UNIV_INTERN +void +buf_LRU_make_block_young( +/*=====================*/ + buf_page_t* bpage) /* in: control block */ +{ + buf_LRU_remove_block(bpage); + buf_LRU_add_block_low(bpage, FALSE); +} + +/********************************************************************** +Moves a block to the end of the LRU list. */ +UNIV_INTERN +void +buf_LRU_make_block_old( +/*===================*/ + buf_page_t* bpage) /* in: control block */ +{ + buf_LRU_remove_block(bpage); + buf_LRU_add_block_to_end_low(bpage); +} + +/********************************************************************** +Try to free a block. If bpage is a descriptor of a compressed-only +page, the descriptor object will be freed as well. + +NOTE: If this function returns BUF_LRU_FREED, it will not temporarily +release buf_pool_mutex. Furthermore, the page frame will no longer be +accessible via bpage. + +The caller must hold buf_pool_mutex and buf_page_get_mutex(bpage) and +release these two mutexes after the call. No other +buf_page_get_mutex() may be held when calling this function. */ +UNIV_INTERN +enum buf_lru_free_block_status +buf_LRU_free_block( +/*===============*/ + /* out: BUF_LRU_FREED if freed, + BUF_LRU_CANNOT_RELOCATE or + BUF_LRU_NOT_FREED otherwise. */ + buf_page_t* bpage, /* in: block to be freed */ + ibool zip, /* in: TRUE if should remove also the + compressed page of an uncompressed page */ + ibool* buf_pool_mutex_released, + /* in: pointer to a variable that will + be assigned TRUE if buf_pool_mutex + was temporarily released, or NULL */ + ibool have_LRU_mutex) +{ + buf_page_t* b = NULL; + mutex_t* block_mutex = buf_page_get_mutex(bpage); + + //ut_ad(buf_pool_mutex_own()); + ut_ad(mutex_own(block_mutex)); + ut_ad(buf_page_in_file(bpage)); + //ut_ad(bpage->in_LRU_list); + ut_ad(!bpage->in_flush_list == !bpage->oldest_modification); + UNIV_MEM_ASSERT_RW(bpage, sizeof *bpage); + + if (!bpage->in_LRU_list || !block_mutex || !buf_page_can_relocate(bpage)) { + + /* Do not free buffer-fixed or I/O-fixed blocks. */ + return(BUF_LRU_NOT_FREED); + } + +#ifdef UNIV_IBUF_COUNT_DEBUG + ut_a(ibuf_count_get(bpage->space, bpage->offset) == 0); +#endif /* UNIV_IBUF_COUNT_DEBUG */ + + if (zip || !bpage->zip.data) { + /* This would completely free the block. */ + /* Do not completely free dirty blocks. */ + + if (bpage->oldest_modification) { + return(BUF_LRU_NOT_FREED); + } + } else if (bpage->oldest_modification) { + /* Do not completely free dirty blocks. */ + + if (buf_page_get_state(bpage) != BUF_BLOCK_FILE_PAGE) { + ut_ad(buf_page_get_state(bpage) + == BUF_BLOCK_ZIP_DIRTY); + return(BUF_LRU_NOT_FREED); + } + + goto alloc; + } else if (buf_page_get_state(bpage) == BUF_BLOCK_FILE_PAGE) { + /* Allocate the control block for the compressed page. + If it cannot be allocated (without freeing a block + from the LRU list), refuse to free bpage. */ +alloc: + //buf_pool_mutex_exit_forbid(); + b = buf_buddy_alloc(sizeof *b, NULL, FALSE); + //buf_pool_mutex_exit_allow(); + + if (UNIV_UNLIKELY(!b)) { + return(BUF_LRU_CANNOT_RELOCATE); + } + + //memcpy(b, bpage, sizeof *b); + } + +#ifdef UNIV_DEBUG + if (buf_debug_prints) { + fprintf(stderr, "Putting space %lu page %lu to free list\n", + (ulong) buf_page_get_space(bpage), + (ulong) buf_page_get_page_no(bpage)); + } +#endif /* UNIV_DEBUG */ + + /* not to break latch order, must re-enter block_mutex */ + mutex_exit(block_mutex); + + if (!have_LRU_mutex) + mutex_enter(&LRU_list_mutex); /* optimistic */ + rw_lock_x_lock(&page_hash_latch); + mutex_enter(block_mutex); + + /* recheck states of block */ + if (!bpage->in_LRU_list || block_mutex != buf_page_get_mutex(bpage) + || !buf_page_can_relocate(bpage)) { +not_freed: + if (b) { + buf_buddy_free(b, sizeof *b, TRUE); + } + if (!have_LRU_mutex) + mutex_exit(&LRU_list_mutex); + rw_lock_x_unlock(&page_hash_latch); + return(BUF_LRU_NOT_FREED); + } else if (zip || !bpage->zip.data) { + if (bpage->oldest_modification) + goto not_freed; + } else if (bpage->oldest_modification) { + if (buf_page_get_state(bpage) != BUF_BLOCK_FILE_PAGE) { + ut_ad(buf_page_get_state(bpage) == BUF_BLOCK_ZIP_DIRTY); + goto not_freed; + } + } + + if (b) { + memcpy(b, bpage, sizeof *b); + } + + if (buf_LRU_block_remove_hashed_page(bpage, zip) + != BUF_BLOCK_ZIP_FREE) { + ut_a(bpage->buf_fix_count == 0); + + if (b) { + buf_page_t* prev_b = UT_LIST_GET_PREV(LRU, b); + const ulint fold = buf_page_address_fold( + bpage->space, bpage->offset); + + ut_a(!buf_page_hash_get(bpage->space, bpage->offset)); + + while (prev_b && !prev_b->in_LRU_list) { + prev_b = UT_LIST_GET_PREV(LRU, prev_b); + } + + b->state = b->oldest_modification + ? BUF_BLOCK_ZIP_DIRTY + : BUF_BLOCK_ZIP_PAGE; + UNIV_MEM_DESC(b->zip.data, + page_zip_get_size(&b->zip), b); + + /* The fields in_page_hash and in_LRU_list of + the to-be-freed block descriptor should have + been cleared in + buf_LRU_block_remove_hashed_page(), which + invokes buf_LRU_remove_block(). */ + ut_ad(!bpage->in_page_hash); + ut_ad(!bpage->in_LRU_list); + /* bpage->state was BUF_BLOCK_FILE_PAGE because + b != NULL. The type cast below is thus valid. */ + ut_ad(!((buf_block_t*) bpage)->in_unzip_LRU_list); + + /* The fields of bpage were copied to b before + buf_LRU_block_remove_hashed_page() was invoked. */ + ut_ad(!b->in_zip_hash); + ut_ad(b->in_page_hash); + ut_ad(b->in_LRU_list); + + HASH_INSERT(buf_page_t, hash, + buf_pool->page_hash, fold, b); + + /* Insert b where bpage was in the LRU list. */ + if (UNIV_LIKELY(prev_b != NULL)) { + ulint lru_len; + + ut_ad(prev_b->in_LRU_list); + ut_ad(buf_page_in_file(prev_b)); + UNIV_MEM_ASSERT_RW(prev_b, sizeof *prev_b); + + UT_LIST_INSERT_AFTER(LRU, buf_pool->LRU, + prev_b, b); + + if (buf_page_is_old(b)) { + buf_pool->LRU_old_len++; + if (UNIV_UNLIKELY + (buf_pool->LRU_old + == UT_LIST_GET_NEXT(LRU, b))) { + + buf_pool->LRU_old = b; + } +#ifdef UNIV_LRU_DEBUG + ut_a(prev_b->old + || !UT_LIST_GET_NEXT(LRU, b) + || UT_LIST_GET_NEXT(LRU, b)->old); + } else { + ut_a(!prev_b->old + || !UT_LIST_GET_NEXT(LRU, b) + || !UT_LIST_GET_NEXT(LRU, b)->old); +#endif /* UNIV_LRU_DEBUG */ + } + + lru_len = UT_LIST_GET_LEN(buf_pool->LRU); + + if (lru_len > BUF_LRU_OLD_MIN_LEN) { + ut_ad(buf_pool->LRU_old); + /* Adjust the length of the + old block list if necessary */ + buf_LRU_old_adjust_len(); + } else if (lru_len == BUF_LRU_OLD_MIN_LEN) { + /* The LRU list is now long + enough for LRU_old to become + defined: init it */ + buf_LRU_old_init(); + } + } else { + b->in_LRU_list = FALSE; + buf_LRU_add_block_low(b, buf_page_is_old(b)); + } + + mutex_enter(&flush_list_mutex); + if (b->state == BUF_BLOCK_ZIP_PAGE) { + buf_LRU_insert_zip_clean(b); + } else { + buf_page_t* prev; + + ut_ad(b->in_flush_list); + ut_d(bpage->in_flush_list = FALSE); + + prev = UT_LIST_GET_PREV(flush_list, b); + UT_LIST_REMOVE(flush_list, buf_pool->flush_list, b); + + if (prev) { + ut_ad(prev->in_flush_list); + UT_LIST_INSERT_AFTER( + flush_list, + buf_pool->flush_list, + prev, b); + } else { + UT_LIST_ADD_FIRST( + flush_list, + buf_pool->flush_list, + b); + } + } + mutex_exit(&flush_list_mutex); + + bpage->zip.data = NULL; + page_zip_set_size(&bpage->zip, 0); + + /* Prevent buf_page_get_gen() from + decompressing the block while we release + buf_pool_mutex and block_mutex. */ + b->buf_fix_count++; + b->io_fix = BUF_IO_READ; + } + + if (buf_pool_mutex_released) { + *buf_pool_mutex_released = TRUE; + } + + //buf_pool_mutex_exit(); + mutex_exit(&LRU_list_mutex); + rw_lock_x_unlock(&page_hash_latch); + mutex_exit(block_mutex); + + /* Remove possible adaptive hash index on the page. + The page was declared uninitialized by + buf_LRU_block_remove_hashed_page(). We need to flag + the contents of the page valid (which it still is) in + order to avoid bogus Valgrind warnings.*/ + + UNIV_MEM_VALID(((buf_block_t*) bpage)->frame, + UNIV_PAGE_SIZE); + btr_search_drop_page_hash_index((buf_block_t*) bpage); + UNIV_MEM_INVALID(((buf_block_t*) bpage)->frame, + UNIV_PAGE_SIZE); + + if (b) { + /* Compute and stamp the compressed page + checksum while not holding any mutex. The + block is already half-freed + (BUF_BLOCK_REMOVE_HASH) and removed from + buf_pool->page_hash, thus inaccessible by any + other thread. */ + + mach_write_to_4( + b->zip.data + FIL_PAGE_SPACE_OR_CHKSUM, + UNIV_LIKELY(srv_use_checksums) + ? page_zip_calc_checksum( + b->zip.data, + page_zip_get_size(&b->zip)) + : BUF_NO_CHECKSUM_MAGIC); + } + + //buf_pool_mutex_enter(); + if (have_LRU_mutex) + mutex_enter(&LRU_list_mutex); + mutex_enter(block_mutex); + + if (b) { + mutex_enter(&buf_pool_zip_mutex); + b->buf_fix_count--; + buf_page_set_io_fix(b, BUF_IO_NONE); + mutex_exit(&buf_pool_zip_mutex); + } + + buf_LRU_block_free_hashed_page((buf_block_t*) bpage, FALSE); + } else { + /* The block_mutex should have been released by + buf_LRU_block_remove_hashed_page() when it returns + BUF_BLOCK_ZIP_FREE. */ + ut_ad(block_mutex == &buf_pool_zip_mutex); + mutex_enter(block_mutex); + + if (!have_LRU_mutex) + mutex_exit(&LRU_list_mutex); + rw_lock_x_unlock(&page_hash_latch); + } + + return(BUF_LRU_FREED); +} + +/********************************************************************** +Puts a block back to the free list. */ +UNIV_INTERN +void +buf_LRU_block_free_non_file_page( +/*=============================*/ + buf_block_t* block, /* in: block, must not contain a file page */ + ibool have_page_hash_mutex) +{ + void* data; + + ut_ad(block); + //ut_ad(buf_pool_mutex_own()); + ut_ad(mutex_own(&block->mutex)); + + switch (buf_block_get_state(block)) { + case BUF_BLOCK_MEMORY: + case BUF_BLOCK_READY_FOR_USE: + break; + default: + ut_error; + } + +#if defined UNIV_AHI_DEBUG || defined UNIV_DEBUG + ut_a(block->n_pointers == 0); +#endif /* UNIV_AHI_DEBUG || UNIV_DEBUG */ + ut_ad(!block->page.in_free_list); + ut_ad(!block->page.in_flush_list); + ut_ad(!block->page.in_LRU_list); + + buf_block_set_state(block, BUF_BLOCK_NOT_USED); + + UNIV_MEM_ALLOC(block->frame, UNIV_PAGE_SIZE); +#ifdef UNIV_DEBUG + /* Wipe contents of page to reveal possible stale pointers to it */ + memset(block->frame, '\0', UNIV_PAGE_SIZE); +#else + /* Wipe page_no and space_id */ + memset(block->frame + FIL_PAGE_OFFSET, 0xfe, 4); + memset(block->frame + FIL_PAGE_ARCH_LOG_NO_OR_SPACE_ID, 0xfe, 4); +#endif + data = block->page.zip.data; + + if (data) { + block->page.zip.data = NULL; + mutex_exit(&block->mutex); + //buf_pool_mutex_exit_forbid(); + buf_buddy_free(data, page_zip_get_size(&block->page.zip), have_page_hash_mutex); + //buf_pool_mutex_exit_allow(); + mutex_enter(&block->mutex); + page_zip_set_size(&block->page.zip, 0); + } + + mutex_enter(&free_list_mutex); + UT_LIST_ADD_FIRST(free, buf_pool->free, (&block->page)); + ut_d(block->page.in_free_list = TRUE); + mutex_exit(&free_list_mutex); + + UNIV_MEM_ASSERT_AND_FREE(block->frame, UNIV_PAGE_SIZE); +} + +/********************************************************************** +Takes a block out of the LRU list and page hash table. +If the block is compressed-only (BUF_BLOCK_ZIP_PAGE), +the object will be freed and buf_pool_zip_mutex will be released. + +If a compressed page or a compressed-only block descriptor is freed, +other compressed pages or compressed-only block descriptors may be +relocated. */ +static +enum buf_page_state +buf_LRU_block_remove_hashed_page( +/*=============================*/ + /* out: the new state of the block + (BUF_BLOCK_ZIP_FREE if the state was + BUF_BLOCK_ZIP_PAGE, or BUF_BLOCK_REMOVE_HASH + otherwise) */ + buf_page_t* bpage, /* in: block, must contain a file page and + be in a state where it can be freed; there + may or may not be a hash index to the page */ + ibool zip) /* in: TRUE if should remove also the + compressed page of an uncompressed page */ +{ + const buf_page_t* hashed_bpage; + ut_ad(bpage); + //ut_ad(buf_pool_mutex_own()); + ut_ad(mutex_own(&LRU_list_mutex)); +#ifdef UNIV_SYNC_DEBUG + ut_ad(rw_lock_own(&page_hash_latch, RW_LOCK_EX)); +#endif + ut_ad(mutex_own(buf_page_get_mutex(bpage))); + + ut_a(buf_page_get_io_fix(bpage) == BUF_IO_NONE); + ut_a(bpage->buf_fix_count == 0); + + UNIV_MEM_ASSERT_RW(bpage, sizeof *bpage); + + buf_LRU_remove_block(bpage); + + buf_pool->freed_page_clock += 1; + + switch (buf_page_get_state(bpage)) { + case BUF_BLOCK_FILE_PAGE: + UNIV_MEM_ASSERT_W(bpage, sizeof(buf_block_t)); + UNIV_MEM_ASSERT_W(((buf_block_t*) bpage)->frame, + UNIV_PAGE_SIZE); + buf_block_modify_clock_inc((buf_block_t*) bpage); + if (bpage->zip.data) { + const page_t* page = ((buf_block_t*) bpage)->frame; + const ulint zip_size + = page_zip_get_size(&bpage->zip); + + ut_a(!zip || bpage->oldest_modification == 0); + + switch (UNIV_EXPECT(fil_page_get_type(page), + FIL_PAGE_INDEX)) { + case FIL_PAGE_TYPE_ALLOCATED: + case FIL_PAGE_INODE: + case FIL_PAGE_IBUF_BITMAP: + case FIL_PAGE_TYPE_FSP_HDR: + case FIL_PAGE_TYPE_XDES: + /* These are essentially uncompressed pages. */ + if (!zip) { + /* InnoDB writes the data to the + uncompressed page frame. Copy it + to the compressed page, which will + be preserved. */ + memcpy(bpage->zip.data, page, + zip_size); + } + break; + case FIL_PAGE_TYPE_ZBLOB: + case FIL_PAGE_TYPE_ZBLOB2: + break; + case FIL_PAGE_INDEX: +#ifdef UNIV_ZIP_DEBUG + ut_a(page_zip_validate(&bpage->zip, page)); +#endif /* UNIV_ZIP_DEBUG */ + break; + default: + ut_print_timestamp(stderr); + fputs(" InnoDB: ERROR: The compressed page" + " to be evicted seems corrupt:", stderr); + ut_print_buf(stderr, page, zip_size); + fputs("\nInnoDB: Possibly older version" + " of the page:", stderr); + ut_print_buf(stderr, bpage->zip.data, + zip_size); + putc('\n', stderr); + ut_error; + } + + break; + } + /* fall through */ + case BUF_BLOCK_ZIP_PAGE: + ut_a(bpage->oldest_modification == 0); + UNIV_MEM_ASSERT_W(bpage->zip.data, + page_zip_get_size(&bpage->zip)); + break; + case BUF_BLOCK_ZIP_FREE: + case BUF_BLOCK_ZIP_DIRTY: + case BUF_BLOCK_NOT_USED: + case BUF_BLOCK_READY_FOR_USE: + case BUF_BLOCK_MEMORY: + case BUF_BLOCK_REMOVE_HASH: + ut_error; + break; + } + + hashed_bpage = buf_page_hash_get(bpage->space, bpage->offset); + + if (UNIV_UNLIKELY(bpage != hashed_bpage)) { + fprintf(stderr, + "InnoDB: Error: page %lu %lu not found" + " in the hash table\n", + (ulong) bpage->space, + (ulong) bpage->offset); + if (hashed_bpage) { + fprintf(stderr, + "InnoDB: In hash table we find block" + " %p of %lu %lu which is not %p\n", + (const void*) hashed_bpage, + (ulong) hashed_bpage->space, + (ulong) hashed_bpage->offset, + (const void*) bpage); + } + +#if defined UNIV_DEBUG || defined UNIV_BUF_DEBUG + mutex_exit(buf_page_get_mutex(bpage)); + //buf_pool_mutex_exit(); + mutex_exit(&LRU_list_mutex); + rw_lock_x_unlock(&page_hash_latch); + buf_print(); + buf_LRU_print(); + buf_validate(); + buf_LRU_validate(); +#endif /* UNIV_DEBUG || UNIV_BUF_DEBUG */ + ut_error; + } + + ut_ad(!bpage->in_zip_hash); + ut_ad(bpage->in_page_hash); + ut_d(bpage->in_page_hash = FALSE); + HASH_DELETE(buf_page_t, hash, buf_pool->page_hash, + buf_page_address_fold(bpage->space, bpage->offset), + bpage); + switch (buf_page_get_state(bpage)) { + case BUF_BLOCK_ZIP_PAGE: + ut_ad(!bpage->in_free_list); + ut_ad(!bpage->in_flush_list); + ut_ad(!bpage->in_LRU_list); + ut_a(bpage->zip.data); + ut_a(buf_page_get_zip_size(bpage)); + + UT_LIST_REMOVE(zip_list, buf_pool->zip_clean, bpage); + + mutex_exit(&buf_pool_zip_mutex); + //buf_pool_mutex_exit_forbid(); + buf_buddy_free(bpage->zip.data, + page_zip_get_size(&bpage->zip), TRUE); + buf_buddy_free(bpage, sizeof(*bpage), TRUE); + //buf_pool_mutex_exit_allow(); + UNIV_MEM_UNDESC(bpage); + return(BUF_BLOCK_ZIP_FREE); + + case BUF_BLOCK_FILE_PAGE: + memset(((buf_block_t*) bpage)->frame + + FIL_PAGE_OFFSET, 0xff, 4); + memset(((buf_block_t*) bpage)->frame + + FIL_PAGE_ARCH_LOG_NO_OR_SPACE_ID, 0xff, 4); + UNIV_MEM_INVALID(((buf_block_t*) bpage)->frame, + UNIV_PAGE_SIZE); + buf_page_set_state(bpage, BUF_BLOCK_REMOVE_HASH); + + if (zip && bpage->zip.data) { + /* Free the compressed page. */ + void* data = bpage->zip.data; + bpage->zip.data = NULL; + + mutex_exit(&((buf_block_t*) bpage)->mutex); + //buf_pool_mutex_exit_forbid(); + buf_buddy_free(data, page_zip_get_size(&bpage->zip), TRUE); + //buf_pool_mutex_exit_allow(); + mutex_enter(&((buf_block_t*) bpage)->mutex); + page_zip_set_size(&bpage->zip, 0); + } + + return(BUF_BLOCK_REMOVE_HASH); + + case BUF_BLOCK_ZIP_FREE: + case BUF_BLOCK_ZIP_DIRTY: + case BUF_BLOCK_NOT_USED: + case BUF_BLOCK_READY_FOR_USE: + case BUF_BLOCK_MEMORY: + case BUF_BLOCK_REMOVE_HASH: + break; + } + + ut_error; + return(BUF_BLOCK_ZIP_FREE); +} + +/********************************************************************** +Puts a file page whose has no hash index to the free list. */ +static +void +buf_LRU_block_free_hashed_page( +/*===========================*/ + buf_block_t* block, /* in: block, must contain a file page and + be in a state where it can be freed */ + ibool have_page_hash_mutex) +{ + //ut_ad(buf_pool_mutex_own()); + ut_ad(mutex_own(&block->mutex)); + + buf_block_set_state(block, BUF_BLOCK_MEMORY); + + buf_LRU_block_free_non_file_page(block, have_page_hash_mutex); +} + +/************************************************************************ +Update the historical stats that we are collecting for LRU eviction +policy at the end of each interval. */ +UNIV_INTERN +void +buf_LRU_stat_update(void) +/*=====================*/ +{ + buf_LRU_stat_t* item; + + /* If we haven't started eviction yet then don't update stats. */ + if (buf_pool->freed_page_clock == 0) { + goto func_exit; + } + + //buf_pool_mutex_enter(); + mutex_enter(&buf_pool_mutex); + + /* Update the index. */ + item = &buf_LRU_stat_arr[buf_LRU_stat_arr_ind]; + buf_LRU_stat_arr_ind++; + buf_LRU_stat_arr_ind %= BUF_LRU_STAT_N_INTERVAL; + + /* Add the current value and subtract the obsolete entry. */ + buf_LRU_stat_sum.io += buf_LRU_stat_cur.io - item->io; + buf_LRU_stat_sum.unzip += buf_LRU_stat_cur.unzip - item->unzip; + + /* Put current entry in the array. */ + memcpy(item, &buf_LRU_stat_cur, sizeof *item); + + //buf_pool_mutex_exit(); + mutex_exit(&buf_pool_mutex); + +func_exit: + /* Clear the current entry. */ + memset(&buf_LRU_stat_cur, 0, sizeof buf_LRU_stat_cur); +} + +#if defined UNIV_DEBUG || defined UNIV_BUF_DEBUG +/************************************************************************** +Validates the LRU list. */ +UNIV_INTERN +ibool +buf_LRU_validate(void) +/*==================*/ +{ + buf_page_t* bpage; + buf_block_t* block; + ulint old_len; + ulint new_len; + ulint LRU_pos; + + ut_ad(buf_pool); + //buf_pool_mutex_enter(); + mutex_enter(&LRU_list_mutex); + + if (UT_LIST_GET_LEN(buf_pool->LRU) >= BUF_LRU_OLD_MIN_LEN) { + + ut_a(buf_pool->LRU_old); + old_len = buf_pool->LRU_old_len; + new_len = 3 * (UT_LIST_GET_LEN(buf_pool->LRU) / 8); + ut_a(old_len >= new_len - BUF_LRU_OLD_TOLERANCE); + ut_a(old_len <= new_len + BUF_LRU_OLD_TOLERANCE); + } + + UT_LIST_VALIDATE(LRU, buf_page_t, buf_pool->LRU); + + bpage = UT_LIST_GET_FIRST(buf_pool->LRU); + + old_len = 0; + + while (bpage != NULL) { + + switch (buf_page_get_state(bpage)) { + case BUF_BLOCK_ZIP_FREE: + case BUF_BLOCK_NOT_USED: + case BUF_BLOCK_READY_FOR_USE: + case BUF_BLOCK_MEMORY: + case BUF_BLOCK_REMOVE_HASH: + ut_error; + break; + case BUF_BLOCK_FILE_PAGE: + ut_ad(((buf_block_t*) bpage)->in_unzip_LRU_list + == buf_page_belongs_to_unzip_LRU(bpage)); + case BUF_BLOCK_ZIP_PAGE: + case BUF_BLOCK_ZIP_DIRTY: + break; + } + + if (buf_page_is_old(bpage)) { + old_len++; + } + + if (buf_pool->LRU_old && (old_len == 1)) { + ut_a(buf_pool->LRU_old == bpage); + } + + LRU_pos = buf_page_get_LRU_position(bpage); + + bpage = UT_LIST_GET_NEXT(LRU, bpage); + + if (bpage) { + /* If the following assert fails, it may + not be an error: just the buf_pool clock + has wrapped around */ + ut_a(LRU_pos >= buf_page_get_LRU_position(bpage)); + } + } + + if (buf_pool->LRU_old) { + ut_a(buf_pool->LRU_old_len == old_len); + } + + mutex_exit(&LRU_list_mutex); + mutex_enter(&free_list_mutex); + + UT_LIST_VALIDATE(free, buf_page_t, buf_pool->free); + + for (bpage = UT_LIST_GET_FIRST(buf_pool->free); + bpage != NULL; + bpage = UT_LIST_GET_NEXT(free, bpage)) { + + ut_a(buf_page_get_state(bpage) == BUF_BLOCK_NOT_USED); + } + + mutex_exit(&free_list_mutex); + mutex_enter(&LRU_list_mutex); + + UT_LIST_VALIDATE(unzip_LRU, buf_block_t, buf_pool->unzip_LRU); + + for (block = UT_LIST_GET_FIRST(buf_pool->unzip_LRU); + block; + block = UT_LIST_GET_NEXT(unzip_LRU, block)) { + + ut_ad(block->in_unzip_LRU_list); + ut_ad(block->page.in_LRU_list); + ut_a(buf_page_belongs_to_unzip_LRU(&block->page)); + } + + //buf_pool_mutex_exit(); + mutex_exit(&LRU_list_mutex); + return(TRUE); +} +#endif /* UNIV_DEBUG || UNIV_BUF_DEBUG */ + +#if defined UNIV_DEBUG_PRINT || defined UNIV_DEBUG || defined UNIV_BUF_DEBUG +/************************************************************************** +Prints the LRU list. */ +UNIV_INTERN +void +buf_LRU_print(void) +/*===============*/ +{ + const buf_page_t* bpage; + + ut_ad(buf_pool); + //buf_pool_mutex_enter(); + mutex_enter(&LRU_list_mutex); + + fprintf(stderr, "Pool ulint clock %lu\n", + (ulong) buf_pool->ulint_clock); + + bpage = UT_LIST_GET_FIRST(buf_pool->LRU); + + while (bpage != NULL) { + + fprintf(stderr, "BLOCK space %lu page %lu ", + (ulong) buf_page_get_space(bpage), + (ulong) buf_page_get_page_no(bpage)); + + if (buf_page_is_old(bpage)) { + fputs("old ", stderr); + } + + if (bpage->buf_fix_count) { + fprintf(stderr, "buffix count %lu ", + (ulong) bpage->buf_fix_count); + } + + if (buf_page_get_io_fix(bpage)) { + fprintf(stderr, "io_fix %lu ", + (ulong) buf_page_get_io_fix(bpage)); + } + + if (bpage->oldest_modification) { + fputs("modif. ", stderr); + } + + switch (buf_page_get_state(bpage)) { + const byte* frame; + case BUF_BLOCK_FILE_PAGE: + frame = buf_block_get_frame((buf_block_t*) bpage); + fprintf(stderr, "\nLRU pos %lu type %lu" + " index id %lu\n", + (ulong) buf_page_get_LRU_position(bpage), + (ulong) fil_page_get_type(frame), + (ulong) ut_dulint_get_low( + btr_page_get_index_id(frame))); + break; + case BUF_BLOCK_ZIP_PAGE: + frame = bpage->zip.data; + fprintf(stderr, "\nLRU pos %lu type %lu size %lu" + " index id %lu\n", + (ulong) buf_page_get_LRU_position(bpage), + (ulong) fil_page_get_type(frame), + (ulong) buf_page_get_zip_size(bpage), + (ulong) ut_dulint_get_low( + btr_page_get_index_id(frame))); + break; + + default: + fprintf(stderr, "\nLRU pos %lu !state %lu!\n", + (ulong) buf_page_get_LRU_position(bpage), + (ulong) buf_page_get_state(bpage)); + break; + } + + bpage = UT_LIST_GET_NEXT(LRU, bpage); + } + + //buf_pool_mutex_exit(); + mutex_exit(&LRU_list_mutex); +} +#endif /* UNIV_DEBUG_PRINT || UNIV_DEBUG || UNIV_BUF_DEBUG */ diff --git a/storage/xtradb/buf/buf0rea.c b/storage/xtradb/buf/buf0rea.c new file mode 100644 index 00000000000..086ea035a7b --- /dev/null +++ b/storage/xtradb/buf/buf0rea.c @@ -0,0 +1,872 @@ +/***************************************************************************** + +Copyright (c) 1995, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/****************************************************** +The database buffer read + +Created 11/5/1995 Heikki Tuuri +*******************************************************/ + +#include "buf0rea.h" + +#include "fil0fil.h" +#include "mtr0mtr.h" + +#include "buf0buf.h" +#include "buf0flu.h" +#include "buf0lru.h" +#include "ibuf0ibuf.h" +#include "log0recv.h" +#include "trx0sys.h" +#include "os0file.h" +#include "srv0start.h" + +extern ulint srv_read_ahead; +extern ulint srv_read_ahead_rnd; +extern ulint srv_read_ahead_seq; +extern ulint srv_buf_pool_reads; + +/* The size in blocks of the area where the random read-ahead algorithm counts +the accessed pages when deciding whether to read-ahead */ +#define BUF_READ_AHEAD_RANDOM_AREA BUF_READ_AHEAD_AREA + +/* There must be at least this many pages in buf_pool in the area to start +a random read-ahead */ +#define BUF_READ_AHEAD_RANDOM_THRESHOLD (5 + buf_read_ahead_random_area / 8) + +/* The linear read-ahead area size */ +#define BUF_READ_AHEAD_LINEAR_AREA BUF_READ_AHEAD_AREA + +/* The linear read-ahead threshold */ +#define LINEAR_AREA_THRESHOLD_COEF 5 / 8 + +/* If there are buf_pool->curr_size per the number below pending reads, then +read-ahead is not done: this is to prevent flooding the buffer pool with +i/o-fixed buffer blocks */ +#define BUF_READ_AHEAD_PEND_LIMIT 2 + +/************************************************************************ +Low-level function which reads a page asynchronously from a file to the +buffer buf_pool if it is not already there, in which case does nothing. +Sets the io_fix flag and sets an exclusive lock on the buffer frame. The +flag is cleared and the x-lock released by an i/o-handler thread. */ +static +ulint +buf_read_page_low( +/*==============*/ + /* out: 1 if a read request was queued, 0 if the page + already resided in buf_pool, or if the page is in + the doublewrite buffer blocks in which case it is never + read into the pool, or if the tablespace does not + exist or is being dropped */ + ulint* err, /* out: DB_SUCCESS or DB_TABLESPACE_DELETED if we are + trying to read from a non-existent tablespace, or a + tablespace which is just now being dropped */ + ibool sync, /* in: TRUE if synchronous aio is desired */ + ulint mode, /* in: BUF_READ_IBUF_PAGES_ONLY, ..., + ORed to OS_AIO_SIMULATED_WAKE_LATER (see below + at read-ahead functions) */ + ulint space, /* in: space id */ + ulint zip_size,/* in: compressed page size, or 0 */ + ibool unzip, /* in: TRUE=request uncompressed page */ + ib_int64_t tablespace_version, /* in: if the space memory object has + this timestamp different from what we are giving here, + treat the tablespace as dropped; this is a timestamp we + use to stop dangling page reads from a tablespace + which we have DISCARDed + IMPORTed back */ + ulint offset) /* in: page number */ +{ + buf_page_t* bpage; + ulint wake_later; + + *err = DB_SUCCESS; + + wake_later = mode & OS_AIO_SIMULATED_WAKE_LATER; + mode = mode & ~OS_AIO_SIMULATED_WAKE_LATER; + + if (trx_doublewrite && space == TRX_SYS_SPACE + && ( (offset >= trx_doublewrite->block1 + && offset < trx_doublewrite->block1 + + TRX_SYS_DOUBLEWRITE_BLOCK_SIZE) + || (offset >= trx_doublewrite->block2 + && offset < trx_doublewrite->block2 + + TRX_SYS_DOUBLEWRITE_BLOCK_SIZE))) { + ut_print_timestamp(stderr); + fprintf(stderr, + " InnoDB: Warning: trying to read" + " doublewrite buffer page %lu\n", + (ulong) offset); + + return(0); + } + + if (ibuf_bitmap_page(zip_size, offset) + || trx_sys_hdr_page(space, offset)) { + + /* Trx sys header is so low in the latching order that we play + safe and do not leave the i/o-completion to an asynchronous + i/o-thread. Ibuf bitmap pages must always be read with + syncronous i/o, to make sure they do not get involved in + thread deadlocks. */ + + sync = TRUE; + } + + /* The following call will also check if the tablespace does not exist + or is being dropped; if we succeed in initing the page in the buffer + pool for read, then DISCARD cannot proceed until the read has + completed */ + bpage = buf_page_init_for_read(err, mode, space, zip_size, unzip, + tablespace_version, offset); + if (bpage == NULL) { + /* bugfix: http://bugs.mysql.com/bug.php?id=43948 */ + if (recv_recovery_is_on() && *err == DB_TABLESPACE_DELETED) { + /* hashed log recs must be treated here */ + recv_addr_t* recv_addr; + + mutex_enter(&(recv_sys->mutex)); + + if (recv_sys->apply_log_recs == FALSE) { + mutex_exit(&(recv_sys->mutex)); + goto not_to_recover; + } + + /* recv_get_fil_addr_struct() */ + recv_addr = HASH_GET_FIRST(recv_sys->addr_hash, + hash_calc_hash(ut_fold_ulint_pair(space, offset), + recv_sys->addr_hash)); + while (recv_addr) { + if ((recv_addr->space == space) + && (recv_addr->page_no == offset)) { + break; + } + recv_addr = HASH_GET_NEXT(addr_hash, recv_addr); + } + + if ((recv_addr == NULL) + || (recv_addr->state == RECV_BEING_PROCESSED) + || (recv_addr->state == RECV_PROCESSED)) { + mutex_exit(&(recv_sys->mutex)); + goto not_to_recover; + } + + fprintf(stderr, " (cannot find space: %lu)", space); + recv_addr->state = RECV_PROCESSED; + + ut_a(recv_sys->n_addrs); + recv_sys->n_addrs--; + + mutex_exit(&(recv_sys->mutex)); + } +not_to_recover: + + return(0); + } + +#ifdef UNIV_DEBUG + if (buf_debug_prints) { + fprintf(stderr, + "Posting read request for page %lu, sync %lu\n", + (ulong) offset, + (ulong) sync); + } +#endif + + ut_ad(buf_page_in_file(bpage)); + + if (zip_size) { + *err = fil_io(OS_FILE_READ | wake_later, + sync, space, zip_size, offset, 0, zip_size, + bpage->zip.data, bpage); + } else { + ut_a(buf_page_get_state(bpage) == BUF_BLOCK_FILE_PAGE); + + *err = fil_io(OS_FILE_READ | wake_later, + sync, space, 0, offset, 0, UNIV_PAGE_SIZE, + ((buf_block_t*) bpage)->frame, bpage); + } + ut_a(*err == DB_SUCCESS); + + if (sync) { + /* The i/o is already completed when we arrive from + fil_read */ + buf_page_io_complete(bpage); + } + + return(1); +} + +/************************************************************************ +Applies a random read-ahead in buf_pool if there are at least a threshold +value of accessed pages from the random read-ahead area. Does not read any +page, not even the one at the position (space, offset), if the read-ahead +mechanism is not activated. NOTE 1: the calling thread may own latches on +pages: to avoid deadlocks this function must be written such that it cannot +end up waiting for these latches! NOTE 2: the calling thread must want +access to the page given: this rule is set to prevent unintended read-aheads +performed by ibuf routines, a situation which could result in a deadlock if +the OS does not support asynchronous i/o. */ +static +ulint +buf_read_ahead_random( +/*==================*/ + /* out: number of page read requests issued; NOTE + that if we read ibuf pages, it may happen that + the page at the given page number does not get + read even if we return a value > 0! */ + ulint space, /* in: space id */ + ulint zip_size,/* in: compressed page size in bytes, or 0 */ + ulint offset) /* in: page number of a page which the current thread + wants to access */ +{ + ib_int64_t tablespace_version; + ulint recent_blocks = 0; + ulint count; + ulint LRU_recent_limit; + ulint ibuf_mode; + ulint low, high; + ulint err; + ulint i; + ulint buf_read_ahead_random_area; + + if (!(srv_read_ahead & 1)) { + return(0); + } + + if (srv_startup_is_before_trx_rollback_phase) { + /* No read-ahead to avoid thread deadlocks */ + return(0); + } + + if (ibuf_bitmap_page(zip_size, offset) + || trx_sys_hdr_page(space, offset)) { + + /* If it is an ibuf bitmap page or trx sys hdr, we do + no read-ahead, as that could break the ibuf page access + order */ + + return(0); + } + + /* Remember the tablespace version before we ask te tablespace size + below: if DISCARD + IMPORT changes the actual .ibd file meanwhile, we + do not try to read outside the bounds of the tablespace! */ + + tablespace_version = fil_space_get_version(space); + + buf_read_ahead_random_area = BUF_READ_AHEAD_RANDOM_AREA; + + low = (offset / buf_read_ahead_random_area) + * buf_read_ahead_random_area; + high = (offset / buf_read_ahead_random_area + 1) + * buf_read_ahead_random_area; + if (high > fil_space_get_size(space)) { + + high = fil_space_get_size(space); + } + + /* Get the minimum LRU_position field value for an initial segment + of the LRU list, to determine which blocks have recently been added + to the start of the list. */ + + LRU_recent_limit = buf_LRU_get_recent_limit(); + + //buf_pool_mutex_enter(); + mutex_enter(&buf_pool_mutex); + + if (buf_pool->n_pend_reads + > buf_pool->curr_size / BUF_READ_AHEAD_PEND_LIMIT) { + //buf_pool_mutex_exit(); + mutex_exit(&buf_pool_mutex); + + return(0); + } + mutex_exit(&buf_pool_mutex); + + /* Count how many blocks in the area have been recently accessed, + that is, reside near the start of the LRU list. */ + + rw_lock_s_lock(&page_hash_latch); + for (i = low; i < high; i++) { + const buf_page_t* bpage = buf_page_hash_get(space, i); + + if (bpage + && buf_page_is_accessed(bpage) + && (buf_page_get_LRU_position(bpage) > LRU_recent_limit)) { + + recent_blocks++; + + if (recent_blocks >= BUF_READ_AHEAD_RANDOM_THRESHOLD) { + + //buf_pool_mutex_exit(); + rw_lock_s_unlock(&page_hash_latch); + goto read_ahead; + } + } + } + + //buf_pool_mutex_exit(); + rw_lock_s_unlock(&page_hash_latch); + /* Do nothing */ + return(0); + +read_ahead: + /* Read all the suitable blocks within the area */ + + if (ibuf_inside()) { + ibuf_mode = BUF_READ_IBUF_PAGES_ONLY; + } else { + ibuf_mode = BUF_READ_ANY_PAGE; + } + + count = 0; + + for (i = low; i < high; i++) { + /* It is only sensible to do read-ahead in the non-sync aio + mode: hence FALSE as the first parameter */ + + if (!ibuf_bitmap_page(zip_size, i)) { + count += buf_read_page_low( + &err, FALSE, + ibuf_mode | OS_AIO_SIMULATED_WAKE_LATER, + space, zip_size, FALSE, + tablespace_version, i); + if (err == DB_TABLESPACE_DELETED) { + ut_print_timestamp(stderr); + fprintf(stderr, + " InnoDB: Warning: in random" + " readahead trying to access\n" + "InnoDB: tablespace %lu page %lu,\n" + "InnoDB: but the tablespace does not" + " exist or is just being dropped.\n", + (ulong) space, (ulong) i); + } + } + } + + /* In simulated aio we wake the aio handler threads only after + queuing all aio requests, in native aio the following call does + nothing: */ + + os_aio_simulated_wake_handler_threads(); + +#ifdef UNIV_DEBUG + if (buf_debug_prints && (count > 0)) { + fprintf(stderr, + "Random read-ahead space %lu offset %lu pages %lu\n", + (ulong) space, (ulong) offset, + (ulong) count); + } +#endif /* UNIV_DEBUG */ + + ++srv_read_ahead_rnd; + return(count); +} + +/************************************************************************ +High-level function which reads a page asynchronously from a file to the +buffer buf_pool if it is not already there. Sets the io_fix flag and sets +an exclusive lock on the buffer frame. The flag is cleared and the x-lock +released by the i/o-handler thread. Does a random read-ahead if it seems +sensible. */ +UNIV_INTERN +ulint +buf_read_page( +/*==========*/ + /* out: number of page read requests issued: this can + be > 1 if read-ahead occurred */ + ulint space, /* in: space id */ + ulint zip_size,/* in: compressed page size in bytes, or 0 */ + ulint offset) /* in: page number */ +{ + ib_int64_t tablespace_version; + ulint count; + ulint count2; + ulint err; + + tablespace_version = fil_space_get_version(space); + + count = buf_read_ahead_random(space, zip_size, offset); + + /* We do the i/o in the synchronous aio mode to save thread + switches: hence TRUE */ + + count2 = buf_read_page_low(&err, TRUE, BUF_READ_ANY_PAGE, space, + zip_size, FALSE, + tablespace_version, offset); + srv_buf_pool_reads+= count2; + if (err == DB_TABLESPACE_DELETED) { + ut_print_timestamp(stderr); + fprintf(stderr, + " InnoDB: Error: trying to access" + " tablespace %lu page no. %lu,\n" + "InnoDB: but the tablespace does not exist" + " or is just being dropped.\n", + (ulong) space, (ulong) offset); + } + + /* Flush pages from the end of the LRU list if necessary */ + buf_flush_free_margin(FALSE); + + /* Increment number of I/O operations used for LRU policy. */ + buf_LRU_stat_inc_io(); + + return(count + count2); +} + +/************************************************************************ +Applies linear read-ahead if in the buf_pool the page is a border page of +a linear read-ahead area and all the pages in the area have been accessed. +Does not read any page if the read-ahead mechanism is not activated. Note +that the the algorithm looks at the 'natural' adjacent successor and +predecessor of the page, which on the leaf level of a B-tree are the next +and previous page in the chain of leaves. To know these, the page specified +in (space, offset) must already be present in the buf_pool. Thus, the +natural way to use this function is to call it when a page in the buf_pool +is accessed the first time, calling this function just after it has been +bufferfixed. +NOTE 1: as this function looks at the natural predecessor and successor +fields on the page, what happens, if these are not initialized to any +sensible value? No problem, before applying read-ahead we check that the +area to read is within the span of the space, if not, read-ahead is not +applied. An uninitialized value may result in a useless read operation, but +only very improbably. +NOTE 2: the calling thread may own latches on pages: to avoid deadlocks this +function must be written such that it cannot end up waiting for these +latches! +NOTE 3: the calling thread must want access to the page given: this rule is +set to prevent unintended read-aheads performed by ibuf routines, a situation +which could result in a deadlock if the OS does not support asynchronous io. */ +UNIV_INTERN +ulint +buf_read_ahead_linear( +/*==================*/ + /* out: number of page read requests issued */ + ulint space, /* in: space id */ + ulint zip_size,/* in: compressed page size in bytes, or 0 */ + ulint offset) /* in: page number of a page; NOTE: the current thread + must want access to this page (see NOTE 3 above) */ +{ + ib_int64_t tablespace_version; + buf_page_t* bpage; + buf_frame_t* frame; + buf_page_t* pred_bpage = NULL; + ulint pred_offset; + ulint succ_offset; + ulint count; + int asc_or_desc; + ulint new_offset; + ulint fail_count; + ulint ibuf_mode; + ulint low, high; + ulint err; + ulint i; + const ulint buf_read_ahead_linear_area + = BUF_READ_AHEAD_LINEAR_AREA; + + if (!(srv_read_ahead & 2)) { + return(0); + } + + if (UNIV_UNLIKELY(srv_startup_is_before_trx_rollback_phase)) { + /* No read-ahead to avoid thread deadlocks */ + return(0); + } + + low = (offset / buf_read_ahead_linear_area) + * buf_read_ahead_linear_area; + high = (offset / buf_read_ahead_linear_area + 1) + * buf_read_ahead_linear_area; + + if ((offset != low) && (offset != high - 1)) { + /* This is not a border page of the area: return */ + + return(0); + } + + if (ibuf_bitmap_page(zip_size, offset) + || trx_sys_hdr_page(space, offset)) { + + /* If it is an ibuf bitmap page or trx sys hdr, we do + no read-ahead, as that could break the ibuf page access + order */ + + return(0); + } + + /* Remember the tablespace version before we ask te tablespace size + below: if DISCARD + IMPORT changes the actual .ibd file meanwhile, we + do not try to read outside the bounds of the tablespace! */ + + tablespace_version = fil_space_get_version(space); + + //buf_pool_mutex_enter(); + mutex_enter(&buf_pool_mutex); + + if (high > fil_space_get_size(space)) { + //buf_pool_mutex_exit(); + mutex_exit(&buf_pool_mutex); + /* The area is not whole, return */ + + return(0); + } + + if (buf_pool->n_pend_reads + > buf_pool->curr_size / BUF_READ_AHEAD_PEND_LIMIT) { + //buf_pool_mutex_exit(); + mutex_exit(&buf_pool_mutex); + + return(0); + } + mutex_exit(&buf_pool_mutex); + + /* Check that almost all pages in the area have been accessed; if + offset == low, the accesses must be in a descending order, otherwise, + in an ascending order. */ + + asc_or_desc = 1; + + if (offset == low) { + asc_or_desc = -1; + } + + fail_count = 0; + + rw_lock_s_lock(&page_hash_latch); + for (i = low; i < high; i++) { + bpage = buf_page_hash_get(space, i); + + if ((bpage == NULL) || !buf_page_is_accessed(bpage)) { + /* Not accessed */ + fail_count++; + + } else if (pred_bpage + && (ut_ulint_cmp( + buf_page_get_LRU_position(bpage), + buf_page_get_LRU_position(pred_bpage)) + != asc_or_desc)) { + /* Accesses not in the right order */ + + fail_count++; + pred_bpage = bpage; + } + } + + if (fail_count > buf_read_ahead_linear_area + * LINEAR_AREA_THRESHOLD_COEF) { + /* Too many failures: return */ + + //buf_pool_mutex_exit(); + rw_lock_s_unlock(&page_hash_latch); + + return(0); + } + + /* If we got this far, we know that enough pages in the area have + been accessed in the right order: linear read-ahead can be sensible */ + + bpage = buf_page_hash_get(space, offset); + + if (bpage == NULL) { + //buf_pool_mutex_exit(); + rw_lock_s_unlock(&page_hash_latch); + + return(0); + } + + switch (buf_page_get_state(bpage)) { + case BUF_BLOCK_ZIP_PAGE: + frame = bpage->zip.data; + break; + case BUF_BLOCK_FILE_PAGE: + frame = ((buf_block_t*) bpage)->frame; + break; + default: + ut_error; + break; + } + + /* Read the natural predecessor and successor page addresses from + the page; NOTE that because the calling thread may have an x-latch + on the page, we do not acquire an s-latch on the page, this is to + prevent deadlocks. Even if we read values which are nonsense, the + algorithm will work. */ + + pred_offset = fil_page_get_prev(frame); + succ_offset = fil_page_get_next(frame); + + //buf_pool_mutex_exit(); + rw_lock_s_unlock(&page_hash_latch); + + if ((offset == low) && (succ_offset == offset + 1)) { + + /* This is ok, we can continue */ + new_offset = pred_offset; + + } else if ((offset == high - 1) && (pred_offset == offset - 1)) { + + /* This is ok, we can continue */ + new_offset = succ_offset; + } else { + /* Successor or predecessor not in the right order */ + + return(0); + } + + low = (new_offset / buf_read_ahead_linear_area) + * buf_read_ahead_linear_area; + high = (new_offset / buf_read_ahead_linear_area + 1) + * buf_read_ahead_linear_area; + + if ((new_offset != low) && (new_offset != high - 1)) { + /* This is not a border page of the area: return */ + + return(0); + } + + if (high > fil_space_get_size(space)) { + /* The area is not whole, return */ + + return(0); + } + + /* If we got this far, read-ahead can be sensible: do it */ + + if (ibuf_inside()) { + ibuf_mode = BUF_READ_IBUF_PAGES_ONLY; + } else { + ibuf_mode = BUF_READ_ANY_PAGE; + } + + count = 0; + + /* Since Windows XP seems to schedule the i/o handler thread + very eagerly, and consequently it does not wait for the + full read batch to be posted, we use special heuristics here */ + + os_aio_simulated_put_read_threads_to_sleep(); + + for (i = low; i < high; i++) { + /* It is only sensible to do read-ahead in the non-sync + aio mode: hence FALSE as the first parameter */ + + if (!ibuf_bitmap_page(zip_size, i)) { + count += buf_read_page_low( + &err, FALSE, + ibuf_mode | OS_AIO_SIMULATED_WAKE_LATER, + space, zip_size, FALSE, tablespace_version, i); + if (err == DB_TABLESPACE_DELETED) { + ut_print_timestamp(stderr); + fprintf(stderr, + " InnoDB: Warning: in" + " linear readahead trying to access\n" + "InnoDB: tablespace %lu page %lu,\n" + "InnoDB: but the tablespace does not" + " exist or is just being dropped.\n", + (ulong) space, (ulong) i); + } + } + } + + /* In simulated aio we wake the aio handler threads only after + queuing all aio requests, in native aio the following call does + nothing: */ + + os_aio_simulated_wake_handler_threads(); + + /* Flush pages from the end of the LRU list if necessary */ + buf_flush_free_margin(FALSE); + +#ifdef UNIV_DEBUG + if (buf_debug_prints && (count > 0)) { + fprintf(stderr, + "LINEAR read-ahead space %lu offset %lu pages %lu\n", + (ulong) space, (ulong) offset, (ulong) count); + } +#endif /* UNIV_DEBUG */ + + /* Read ahead is considered one I/O operation for the purpose of + LRU policy decision. */ + buf_LRU_stat_inc_io(); + + ++srv_read_ahead_seq; + return(count); +} + +/************************************************************************ +Issues read requests for pages which the ibuf module wants to read in, in +order to contract the insert buffer tree. Technically, this function is like +a read-ahead function. */ +UNIV_INTERN +void +buf_read_ibuf_merge_pages( +/*======================*/ + ibool sync, /* in: TRUE if the caller + wants this function to wait + for the highest address page + to get read in, before this + function returns */ + const ulint* space_ids, /* in: array of space ids */ + const ib_int64_t* space_versions,/* in: the spaces must have + this version number + (timestamp), otherwise we + discard the read; we use this + to cancel reads if DISCARD + + IMPORT may have changed the + tablespace size */ + const ulint* page_nos, /* in: array of page numbers + to read, with the highest page + number the last in the + array */ + ulint n_stored) /* in: number of elements + in the arrays */ +{ + ulint i; + + ut_ad(!ibuf_inside()); +#ifdef UNIV_IBUF_DEBUG + ut_a(n_stored < UNIV_PAGE_SIZE); +#endif + while (buf_pool->n_pend_reads + > buf_pool->curr_size / BUF_READ_AHEAD_PEND_LIMIT) { + os_thread_sleep(500000); + } + + for (i = 0; i < n_stored; i++) { + ulint zip_size = fil_space_get_zip_size(space_ids[i]); + ulint err; + + if (UNIV_UNLIKELY(zip_size == ULINT_UNDEFINED)) { + + goto tablespace_deleted; + } + + buf_read_page_low(&err, sync && (i + 1 == n_stored), + BUF_READ_ANY_PAGE, space_ids[i], + zip_size, TRUE, space_versions[i], + page_nos[i]); + + if (UNIV_UNLIKELY(err == DB_TABLESPACE_DELETED)) { +tablespace_deleted: + /* We have deleted or are deleting the single-table + tablespace: remove the entries for that page */ + + ibuf_merge_or_delete_for_page(NULL, space_ids[i], + page_nos[i], + zip_size, FALSE); + } + } + + os_aio_simulated_wake_handler_threads(); + + /* Flush pages from the end of the LRU list if necessary */ + buf_flush_free_margin(FALSE); + +#ifdef UNIV_DEBUG + if (buf_debug_prints) { + fprintf(stderr, + "Ibuf merge read-ahead space %lu pages %lu\n", + (ulong) space_ids[0], (ulong) n_stored); + } +#endif /* UNIV_DEBUG */ +} + +/************************************************************************ +Issues read requests for pages which recovery wants to read in. */ +UNIV_INTERN +void +buf_read_recv_pages( +/*================*/ + ibool sync, /* in: TRUE if the caller + wants this function to wait + for the highest address page + to get read in, before this + function returns */ + ulint space, /* in: space id */ + ulint zip_size, /* in: compressed page size in + bytes, or 0 */ + const ulint* page_nos, /* in: array of page numbers + to read, with the highest page + number the last in the + array */ + ulint n_stored) /* in: number of page numbers + in the array */ +{ + ib_int64_t tablespace_version; + ulint count; + ulint err; + ulint i; + + zip_size = fil_space_get_zip_size(space); + tablespace_version = fil_space_get_version(space); + + for (i = 0; i < n_stored; i++) { + + count = 0; + + os_aio_print_debug = FALSE; + + while (buf_pool->n_pend_reads >= recv_n_pool_free_frames / 2) { + + os_aio_simulated_wake_handler_threads(); + os_thread_sleep(10000); + + count++; + + if (count > 5000) { + fprintf(stderr, + "InnoDB: Error: InnoDB has waited for" + " 50 seconds for pending\n" + "InnoDB: reads to the buffer pool to" + " be finished.\n" + "InnoDB: Number of pending reads %lu," + " pending pread calls %lu\n", + (ulong) buf_pool->n_pend_reads, + (ulong)os_file_n_pending_preads); + + os_aio_print_debug = TRUE; + } + } + + os_aio_print_debug = FALSE; + + if ((i + 1 == n_stored) && sync) { + buf_read_page_low(&err, TRUE, BUF_READ_ANY_PAGE, space, + zip_size, TRUE, tablespace_version, + page_nos[i]); + } else { + buf_read_page_low(&err, FALSE, BUF_READ_ANY_PAGE + | OS_AIO_SIMULATED_WAKE_LATER, + space, zip_size, TRUE, + tablespace_version, page_nos[i]); + } + } + + os_aio_simulated_wake_handler_threads(); + + /* Flush pages from the end of the LRU list if necessary */ + buf_flush_free_margin(FALSE); + +#ifdef UNIV_DEBUG + if (buf_debug_prints) { + fprintf(stderr, + "Recovery applies read-ahead pages %lu\n", + (ulong) n_stored); + } +#endif /* UNIV_DEBUG */ +} diff --git a/storage/xtradb/data/data0data.c b/storage/xtradb/data/data0data.c new file mode 100644 index 00000000000..1cb3803b187 --- /dev/null +++ b/storage/xtradb/data/data0data.c @@ -0,0 +1,758 @@ +/***************************************************************************** + +Copyright (c) 1994, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/************************************************************************ +SQL data field and tuple + +Created 5/30/1994 Heikki Tuuri +*************************************************************************/ + +#include "data0data.h" + +#ifdef UNIV_NONINL +#include "data0data.ic" +#endif + +#include "rem0rec.h" +#include "rem0cmp.h" +#include "page0page.h" +#include "page0zip.h" +#include "dict0dict.h" +#include "btr0cur.h" + +#include <ctype.h> + +#ifdef UNIV_DEBUG +/* data pointers of tuple fields are initialized to point here +for error checking */ +UNIV_INTERN byte data_error; + +# ifndef UNIV_DEBUG_VALGRIND +/* this is used to fool the compiler in dtuple_validate */ +UNIV_INTERN ulint data_dummy; +# endif /* !UNIV_DEBUG_VALGRIND */ +#endif /* UNIV_DEBUG */ + +/************************************************************************* +Tests if dfield data length and content is equal to the given. */ +UNIV_INTERN +ibool +dfield_data_is_binary_equal( +/*========================*/ + /* out: TRUE if equal */ + const dfield_t* field, /* in: field */ + ulint len, /* in: data length or UNIV_SQL_NULL */ + const byte* data) /* in: data */ +{ + if (len != dfield_get_len(field)) { + + return(FALSE); + } + + if (len == UNIV_SQL_NULL) { + + return(TRUE); + } + + if (0 != memcmp(dfield_get_data(field), data, len)) { + + return(FALSE); + } + + return(TRUE); +} + +/**************************************************************** +Compare two data tuples, respecting the collation of character fields. */ +UNIV_INTERN +int +dtuple_coll_cmp( +/*============*/ + /* out: 1, 0 , -1 if tuple1 is greater, equal, + less, respectively, than tuple2 */ + const dtuple_t* tuple1, /* in: tuple 1 */ + const dtuple_t* tuple2) /* in: tuple 2 */ +{ + ulint n_fields; + ulint i; + + ut_ad(tuple1 && tuple2); + ut_ad(tuple1->magic_n == DATA_TUPLE_MAGIC_N); + ut_ad(tuple2->magic_n == DATA_TUPLE_MAGIC_N); + ut_ad(dtuple_check_typed(tuple1)); + ut_ad(dtuple_check_typed(tuple2)); + + n_fields = dtuple_get_n_fields(tuple1); + + if (n_fields != dtuple_get_n_fields(tuple2)) { + + return(n_fields < dtuple_get_n_fields(tuple2) ? -1 : 1); + } + + for (i = 0; i < n_fields; i++) { + int cmp; + const dfield_t* field1 = dtuple_get_nth_field(tuple1, i); + const dfield_t* field2 = dtuple_get_nth_field(tuple2, i); + + cmp = cmp_dfield_dfield(field1, field2); + + if (cmp) { + return(cmp); + } + } + + return(0); +} + +/************************************************************************* +Sets number of fields used in a tuple. Normally this is set in +dtuple_create, but if you want later to set it smaller, you can use this. */ +UNIV_INTERN +void +dtuple_set_n_fields( +/*================*/ + dtuple_t* tuple, /* in: tuple */ + ulint n_fields) /* in: number of fields */ +{ + ut_ad(tuple); + + tuple->n_fields = n_fields; + tuple->n_fields_cmp = n_fields; +} + +/************************************************************** +Checks that a data field is typed. */ +static +ibool +dfield_check_typed_no_assert( +/*=========================*/ + /* out: TRUE if ok */ + const dfield_t* field) /* in: data field */ +{ + if (dfield_get_type(field)->mtype > DATA_MYSQL + || dfield_get_type(field)->mtype < DATA_VARCHAR) { + + fprintf(stderr, + "InnoDB: Error: data field type %lu, len %lu\n", + (ulong) dfield_get_type(field)->mtype, + (ulong) dfield_get_len(field)); + return(FALSE); + } + + return(TRUE); +} + +/************************************************************** +Checks that a data tuple is typed. */ +UNIV_INTERN +ibool +dtuple_check_typed_no_assert( +/*=========================*/ + /* out: TRUE if ok */ + const dtuple_t* tuple) /* in: tuple */ +{ + const dfield_t* field; + ulint i; + + if (dtuple_get_n_fields(tuple) > REC_MAX_N_FIELDS) { + fprintf(stderr, + "InnoDB: Error: index entry has %lu fields\n", + (ulong) dtuple_get_n_fields(tuple)); +dump: + fputs("InnoDB: Tuple contents: ", stderr); + dtuple_print(stderr, tuple); + putc('\n', stderr); + + return(FALSE); + } + + for (i = 0; i < dtuple_get_n_fields(tuple); i++) { + + field = dtuple_get_nth_field(tuple, i); + + if (!dfield_check_typed_no_assert(field)) { + goto dump; + } + } + + return(TRUE); +} + +/************************************************************** +Checks that a data field is typed. Asserts an error if not. */ +UNIV_INTERN +ibool +dfield_check_typed( +/*===============*/ + /* out: TRUE if ok */ + const dfield_t* field) /* in: data field */ +{ + if (dfield_get_type(field)->mtype > DATA_MYSQL + || dfield_get_type(field)->mtype < DATA_VARCHAR) { + + fprintf(stderr, + "InnoDB: Error: data field type %lu, len %lu\n", + (ulong) dfield_get_type(field)->mtype, + (ulong) dfield_get_len(field)); + + ut_error; + } + + return(TRUE); +} + +/************************************************************** +Checks that a data tuple is typed. Asserts an error if not. */ +UNIV_INTERN +ibool +dtuple_check_typed( +/*===============*/ + /* out: TRUE if ok */ + const dtuple_t* tuple) /* in: tuple */ +{ + const dfield_t* field; + ulint i; + + for (i = 0; i < dtuple_get_n_fields(tuple); i++) { + + field = dtuple_get_nth_field(tuple, i); + + ut_a(dfield_check_typed(field)); + } + + return(TRUE); +} + +#ifdef UNIV_DEBUG +/************************************************************** +Validates the consistency of a tuple which must be complete, i.e, +all fields must have been set. */ +UNIV_INTERN +ibool +dtuple_validate( +/*============*/ + /* out: TRUE if ok */ + const dtuple_t* tuple) /* in: tuple */ +{ + const dfield_t* field; + ulint n_fields; + ulint len; + ulint i; + + ut_ad(tuple->magic_n == DATA_TUPLE_MAGIC_N); + + n_fields = dtuple_get_n_fields(tuple); + + /* We dereference all the data of each field to test + for memory traps */ + + for (i = 0; i < n_fields; i++) { + + field = dtuple_get_nth_field(tuple, i); + len = dfield_get_len(field); + + if (!dfield_is_null(field)) { + + const byte* data = dfield_get_data(field); +#ifndef UNIV_DEBUG_VALGRIND + ulint j; + + for (j = 0; j < len; j++) { + + data_dummy += *data; /* fool the compiler not + to optimize out this + code */ + data++; + } +#endif /* !UNIV_DEBUG_VALGRIND */ + + UNIV_MEM_ASSERT_RW(data, len); + } + } + + ut_a(dtuple_check_typed(tuple)); + + return(TRUE); +} +#endif /* UNIV_DEBUG */ + +/***************************************************************** +Pretty prints a dfield value according to its data type. */ +UNIV_INTERN +void +dfield_print( +/*=========*/ + const dfield_t* dfield) /* in: dfield */ +{ + const byte* data; + ulint len; + ulint i; + + len = dfield_get_len(dfield); + data = dfield_get_data(dfield); + + if (dfield_is_null(dfield)) { + fputs("NULL", stderr); + + return; + } + + switch (dtype_get_mtype(dfield_get_type(dfield))) { + case DATA_CHAR: + case DATA_VARCHAR: + for (i = 0; i < len; i++) { + int c = *data++; + putc(isprint(c) ? c : ' ', stderr); + } + + if (dfield_is_ext(dfield)) { + fputs("(external)", stderr); + } + break; + case DATA_INT: + ut_a(len == 4); /* only works for 32-bit integers */ + fprintf(stderr, "%d", (int)mach_read_from_4(data)); + break; + default: + ut_error; + } +} + +/***************************************************************** +Pretty prints a dfield value according to its data type. Also the hex string +is printed if a string contains non-printable characters. */ +UNIV_INTERN +void +dfield_print_also_hex( +/*==================*/ + const dfield_t* dfield) /* in: dfield */ +{ + const byte* data; + ulint len; + ulint prtype; + ulint i; + ibool print_also_hex; + + len = dfield_get_len(dfield); + data = dfield_get_data(dfield); + + if (dfield_is_null(dfield)) { + fputs("NULL", stderr); + + return; + } + + prtype = dtype_get_prtype(dfield_get_type(dfield)); + + switch (dtype_get_mtype(dfield_get_type(dfield))) { + dulint id; + case DATA_INT: + switch (len) { + ulint val; + case 1: + val = mach_read_from_1(data); + + if (!(prtype & DATA_UNSIGNED)) { + val &= ~0x80; + fprintf(stderr, "%ld", (long) val); + } else { + fprintf(stderr, "%lu", (ulong) val); + } + break; + + case 2: + val = mach_read_from_2(data); + + if (!(prtype & DATA_UNSIGNED)) { + val &= ~0x8000; + fprintf(stderr, "%ld", (long) val); + } else { + fprintf(stderr, "%lu", (ulong) val); + } + break; + + case 3: + val = mach_read_from_3(data); + + if (!(prtype & DATA_UNSIGNED)) { + val &= ~0x800000; + fprintf(stderr, "%ld", (long) val); + } else { + fprintf(stderr, "%lu", (ulong) val); + } + break; + + case 4: + val = mach_read_from_4(data); + + if (!(prtype & DATA_UNSIGNED)) { + val &= ~0x80000000; + fprintf(stderr, "%ld", (long) val); + } else { + fprintf(stderr, "%lu", (ulong) val); + } + break; + + case 6: + id = mach_read_from_6(data); + fprintf(stderr, "{%lu %lu}", + ut_dulint_get_high(id), + ut_dulint_get_low(id)); + break; + + case 7: + id = mach_read_from_7(data); + fprintf(stderr, "{%lu %lu}", + ut_dulint_get_high(id), + ut_dulint_get_low(id)); + break; + case 8: + id = mach_read_from_8(data); + fprintf(stderr, "{%lu %lu}", + ut_dulint_get_high(id), + ut_dulint_get_low(id)); + break; + default: + goto print_hex; + } + break; + + case DATA_SYS: + switch (prtype & DATA_SYS_PRTYPE_MASK) { + case DATA_TRX_ID: + id = mach_read_from_6(data); + + fprintf(stderr, "trx_id " TRX_ID_FMT, + TRX_ID_PREP_PRINTF(id)); + break; + + case DATA_ROLL_PTR: + id = mach_read_from_7(data); + + fprintf(stderr, "roll_ptr {%lu %lu}", + ut_dulint_get_high(id), ut_dulint_get_low(id)); + break; + + case DATA_ROW_ID: + id = mach_read_from_6(data); + + fprintf(stderr, "row_id {%lu %lu}", + ut_dulint_get_high(id), ut_dulint_get_low(id)); + break; + + default: + id = mach_dulint_read_compressed(data); + + fprintf(stderr, "mix_id {%lu %lu}", + ut_dulint_get_high(id), ut_dulint_get_low(id)); + } + break; + + case DATA_CHAR: + case DATA_VARCHAR: + print_also_hex = FALSE; + + for (i = 0; i < len; i++) { + int c = *data++; + + if (!isprint(c)) { + print_also_hex = TRUE; + + fprintf(stderr, "\\x%02x", (unsigned char) c); + } else { + putc(c, stderr); + } + } + + if (dfield_is_ext(dfield)) { + fputs("(external)", stderr); + } + + if (!print_also_hex) { + break; + } + + data = dfield_get_data(dfield); + /* fall through */ + + case DATA_BINARY: + default: +print_hex: + fputs(" Hex: ",stderr); + + for (i = 0; i < len; i++) { + fprintf(stderr, "%02lx", (ulint) *data++); + } + + if (dfield_is_ext(dfield)) { + fputs("(external)", stderr); + } + } +} + +/***************************************************************** +Print a dfield value using ut_print_buf. */ +static +void +dfield_print_raw( +/*=============*/ + FILE* f, /* in: output stream */ + const dfield_t* dfield) /* in: dfield */ +{ + ulint len = dfield_get_len(dfield); + if (!dfield_is_null(dfield)) { + ulint print_len = ut_min(len, 1000); + ut_print_buf(f, dfield_get_data(dfield), print_len); + if (len != print_len) { + fprintf(f, "(total %lu bytes%s)", + (ulong) len, + dfield_is_ext(dfield) ? ", external" : ""); + } + } else { + fputs(" SQL NULL", f); + } +} + +/************************************************************** +The following function prints the contents of a tuple. */ +UNIV_INTERN +void +dtuple_print( +/*=========*/ + FILE* f, /* in: output stream */ + const dtuple_t* tuple) /* in: tuple */ +{ + ulint n_fields; + ulint i; + + n_fields = dtuple_get_n_fields(tuple); + + fprintf(f, "DATA TUPLE: %lu fields;\n", (ulong) n_fields); + + for (i = 0; i < n_fields; i++) { + fprintf(f, " %lu:", (ulong) i); + + dfield_print_raw(f, dtuple_get_nth_field(tuple, i)); + + putc(';', f); + putc('\n', f); + } + + ut_ad(dtuple_validate(tuple)); +} + +/****************************************************************** +Moves parts of long fields in entry to the big record vector so that +the size of tuple drops below the maximum record size allowed in the +database. Moves data only from those fields which are not necessary +to determine uniquely the insertion place of the tuple in the index. */ +UNIV_INTERN +big_rec_t* +dtuple_convert_big_rec( +/*===================*/ + /* out, own: created big record vector, + NULL if we are not able to shorten + the entry enough, i.e., if there are + too many fixed-length or short fields + in entry or the index is clustered */ + dict_index_t* index, /* in: index */ + dtuple_t* entry, /* in/out: index entry */ + ulint* n_ext) /* in/out: number of + externally stored columns */ +{ + mem_heap_t* heap; + big_rec_t* vector; + dfield_t* dfield; + dict_field_t* ifield; + ulint size; + ulint n_fields; + ulint local_len; + ulint local_prefix_len; + + if (UNIV_UNLIKELY(!dict_index_is_clust(index))) { + return(NULL); + } + + if (dict_table_get_format(index->table) < DICT_TF_FORMAT_ZIP) { + /* up to MySQL 5.1: store a 768-byte prefix locally */ + local_len = BTR_EXTERN_FIELD_REF_SIZE + DICT_MAX_INDEX_COL_LEN; + } else { + /* new-format table: do not store any BLOB prefix locally */ + local_len = BTR_EXTERN_FIELD_REF_SIZE; + } + + ut_a(dtuple_check_typed_no_assert(entry)); + + size = rec_get_converted_size(index, entry, *n_ext); + + if (UNIV_UNLIKELY(size > 1000000000)) { + fprintf(stderr, + "InnoDB: Warning: tuple size very big: %lu\n", + (ulong) size); + fputs("InnoDB: Tuple contents: ", stderr); + dtuple_print(stderr, entry); + putc('\n', stderr); + } + + heap = mem_heap_create(size + dtuple_get_n_fields(entry) + * sizeof(big_rec_field_t) + 1000); + + vector = mem_heap_alloc(heap, sizeof(big_rec_t)); + + vector->heap = heap; + vector->fields = mem_heap_alloc(heap, dtuple_get_n_fields(entry) + * sizeof(big_rec_field_t)); + + /* Decide which fields to shorten: the algorithm is to look for + a variable-length field that yields the biggest savings when + stored externally */ + + n_fields = 0; + + while (page_zip_rec_needs_ext(rec_get_converted_size(index, entry, + *n_ext), + dict_table_is_comp(index->table), + dict_index_get_n_fields(index), + dict_table_zip_size(index->table))) { + ulint i; + ulint longest = 0; + ulint longest_i = ULINT_MAX; + byte* data; + big_rec_field_t* b; + + for (i = dict_index_get_n_unique_in_tree(index); + i < dtuple_get_n_fields(entry); i++) { + ulint savings; + + dfield = dtuple_get_nth_field(entry, i); + ifield = dict_index_get_nth_field(index, i); + + /* Skip fixed-length, NULL, externally stored, + or short columns */ + + if (ifield->fixed_len + || dfield_is_null(dfield) + || dfield_is_ext(dfield) + || dfield_get_len(dfield) <= local_len + || dfield_get_len(dfield) + <= BTR_EXTERN_FIELD_REF_SIZE * 2) { + goto skip_field; + } + + savings = dfield_get_len(dfield) - local_len; + + /* Check that there would be savings */ + if (longest >= savings) { + goto skip_field; + } + + longest_i = i; + longest = savings; + +skip_field: + continue; + } + + if (!longest) { + /* Cannot shorten more */ + + mem_heap_free(heap); + + return(NULL); + } + + /* Move data from field longest_i to big rec vector. + + We store the first bytes locally to the record. Then + we can calculate all ordering fields in all indexes + from locally stored data. */ + + dfield = dtuple_get_nth_field(entry, longest_i); + ifield = dict_index_get_nth_field(index, longest_i); + local_prefix_len = local_len - BTR_EXTERN_FIELD_REF_SIZE; + + b = &vector->fields[n_fields]; + b->field_no = longest_i; + b->len = dfield_get_len(dfield) - local_prefix_len; + b->data = (char*) dfield_get_data(dfield) + local_prefix_len; + + /* Allocate the locally stored part of the column. */ + data = mem_heap_alloc(heap, local_len); + + /* Copy the local prefix. */ + memcpy(data, dfield_get_data(dfield), local_prefix_len); + /* Clear the extern field reference (BLOB pointer). */ + memset(data + local_prefix_len, 0, BTR_EXTERN_FIELD_REF_SIZE); +#if 0 + /* The following would fail the Valgrind checks in + page_cur_insert_rec_low() and page_cur_insert_rec_zip(). + The BLOB pointers in the record will be initialized after + the record and the BLOBs have been written. */ + UNIV_MEM_ALLOC(data + local_prefix_len, + BTR_EXTERN_FIELD_REF_SIZE); +#endif + + dfield_set_data(dfield, data, local_len); + dfield_set_ext(dfield); + + n_fields++; + (*n_ext)++; + ut_ad(n_fields < dtuple_get_n_fields(entry)); + } + + vector->n_fields = n_fields; + return(vector); +} + +/****************************************************************** +Puts back to entry the data stored in vector. Note that to ensure the +fields in entry can accommodate the data, vector must have been created +from entry with dtuple_convert_big_rec. */ +UNIV_INTERN +void +dtuple_convert_back_big_rec( +/*========================*/ + dict_index_t* index __attribute__((unused)), /* in: index */ + dtuple_t* entry, /* in: entry whose data was put to vector */ + big_rec_t* vector) /* in, own: big rec vector; it is + freed in this function */ +{ + big_rec_field_t* b = vector->fields; + const big_rec_field_t* const end = b + vector->n_fields; + + for (; b < end; b++) { + dfield_t* dfield; + ulint local_len; + + dfield = dtuple_get_nth_field(entry, b->field_no); + local_len = dfield_get_len(dfield); + + ut_ad(dfield_is_ext(dfield)); + ut_ad(local_len >= BTR_EXTERN_FIELD_REF_SIZE); + + local_len -= BTR_EXTERN_FIELD_REF_SIZE; + + ut_ad(local_len <= DICT_MAX_INDEX_COL_LEN); + + dfield_set_data(dfield, + (char*) b->data - local_len, + b->len + local_len); + } + + mem_heap_free(vector->heap); +} diff --git a/storage/xtradb/data/data0type.c b/storage/xtradb/data/data0type.c new file mode 100644 index 00000000000..5df933ef9fd --- /dev/null +++ b/storage/xtradb/data/data0type.c @@ -0,0 +1,300 @@ +/***************************************************************************** + +Copyright (c) 1996, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/****************************************************** +Data types + +Created 1/16/1996 Heikki Tuuri +*******************************************************/ + +#include "data0type.h" + +#ifdef UNIV_NONINL +#include "data0type.ic" +#endif + +/********************************************************************** +This function is used to find the storage length in bytes of the first n +characters for prefix indexes using a multibyte character set. The function +finds charset information and returns length of prefix_len characters in the +index field in bytes. + +NOTE: the prototype of this function is copied from ha_innodb.cc! If you change +this function, you MUST change also the prototype here! */ +UNIV_INTERN +ulint +innobase_get_at_most_n_mbchars( +/*===========================*/ + /* out: number of bytes occupied by the first + n characters */ + ulint charset_id, /* in: character set id */ + ulint prefix_len, /* in: prefix length in bytes of the index + (this has to be divided by mbmaxlen to get the + number of CHARACTERS n in the prefix) */ + ulint data_len, /* in: length of the string in bytes */ + const char* str); /* in: character string */ + +/* At the database startup we store the default-charset collation number of +this MySQL installation to this global variable. If we have < 4.1.2 format +column definitions, or records in the insert buffer, we use this +charset-collation code for them. */ + +UNIV_INTERN ulint data_mysql_default_charset_coll; + +/************************************************************************* +Determine how many bytes the first n characters of the given string occupy. +If the string is shorter than n characters, returns the number of bytes +the characters in the string occupy. */ +UNIV_INTERN +ulint +dtype_get_at_most_n_mbchars( +/*========================*/ + /* out: length of the prefix, + in bytes */ + ulint prtype, /* in: precise type */ + ulint mbminlen, /* in: minimum length of a + multi-byte character */ + ulint mbmaxlen, /* in: maximum length of a + multi-byte character */ + ulint prefix_len, /* in: length of the requested + prefix, in characters, multiplied by + dtype_get_mbmaxlen(dtype) */ + ulint data_len, /* in: length of str (in bytes) */ + const char* str) /* in: the string whose prefix + length is being determined */ +{ +#ifndef UNIV_HOTBACKUP + ut_a(data_len != UNIV_SQL_NULL); + ut_ad(!mbmaxlen || !(prefix_len % mbmaxlen)); + + if (mbminlen != mbmaxlen) { + ut_a(!(prefix_len % mbmaxlen)); + return(innobase_get_at_most_n_mbchars( + dtype_get_charset_coll(prtype), + prefix_len, data_len, str)); + } + + if (prefix_len < data_len) { + + return(prefix_len); + + } + + return(data_len); +#else /* UNIV_HOTBACKUP */ + /* This function depends on MySQL code that is not included in + InnoDB Hot Backup builds. Besides, this function should never + be called in InnoDB Hot Backup. */ + ut_error; +#endif /* UNIV_HOTBACKUP */ +} + +/************************************************************************* +Checks if a data main type is a string type. Also a BLOB is considered a +string type. */ +UNIV_INTERN +ibool +dtype_is_string_type( +/*=================*/ + /* out: TRUE if string type */ + ulint mtype) /* in: InnoDB main data type code: DATA_CHAR, ... */ +{ + if (mtype <= DATA_BLOB + || mtype == DATA_MYSQL + || mtype == DATA_VARMYSQL) { + + return(TRUE); + } + + return(FALSE); +} + +/************************************************************************* +Checks if a type is a binary string type. Note that for tables created with +< 4.0.14, we do not know if a DATA_BLOB column is a BLOB or a TEXT column. For +those DATA_BLOB columns this function currently returns FALSE. */ +UNIV_INTERN +ibool +dtype_is_binary_string_type( +/*========================*/ + /* out: TRUE if binary string type */ + ulint mtype, /* in: main data type */ + ulint prtype) /* in: precise type */ +{ + if ((mtype == DATA_FIXBINARY) + || (mtype == DATA_BINARY) + || (mtype == DATA_BLOB && (prtype & DATA_BINARY_TYPE))) { + + return(TRUE); + } + + return(FALSE); +} + +/************************************************************************* +Checks if a type is a non-binary string type. That is, dtype_is_string_type is +TRUE and dtype_is_binary_string_type is FALSE. Note that for tables created +with < 4.0.14, we do not know if a DATA_BLOB column is a BLOB or a TEXT column. +For those DATA_BLOB columns this function currently returns TRUE. */ +UNIV_INTERN +ibool +dtype_is_non_binary_string_type( +/*============================*/ + /* out: TRUE if non-binary string type */ + ulint mtype, /* in: main data type */ + ulint prtype) /* in: precise type */ +{ + if (dtype_is_string_type(mtype) == TRUE + && dtype_is_binary_string_type(mtype, prtype) == FALSE) { + + return(TRUE); + } + + return(FALSE); +} + +/************************************************************************* +Forms a precise type from the < 4.1.2 format precise type plus the +charset-collation code. */ +UNIV_INTERN +ulint +dtype_form_prtype( +/*==============*/ + ulint old_prtype, /* in: the MySQL type code and the flags + DATA_BINARY_TYPE etc. */ + ulint charset_coll) /* in: MySQL charset-collation code */ +{ + ut_a(old_prtype < 256 * 256); + ut_a(charset_coll < 256); + + return(old_prtype + (charset_coll << 16)); +} + +/************************************************************************* +Validates a data type structure. */ +UNIV_INTERN +ibool +dtype_validate( +/*===========*/ + /* out: TRUE if ok */ + const dtype_t* type) /* in: type struct to validate */ +{ + ut_a(type); + ut_a(type->mtype >= DATA_VARCHAR); + ut_a(type->mtype <= DATA_MYSQL); + + if (type->mtype == DATA_SYS) { + ut_a((type->prtype & DATA_MYSQL_TYPE_MASK) < DATA_N_SYS_COLS); + } + + ut_a(type->mbminlen <= type->mbmaxlen); + + return(TRUE); +} + +/************************************************************************* +Prints a data type structure. */ +UNIV_INTERN +void +dtype_print( +/*========*/ + const dtype_t* type) /* in: type */ +{ + ulint mtype; + ulint prtype; + ulint len; + + ut_a(type); + + mtype = type->mtype; + prtype = type->prtype; + + switch (mtype) { + case DATA_VARCHAR: + fputs("DATA_VARCHAR", stderr); + break; + + case DATA_CHAR: + fputs("DATA_CHAR", stderr); + break; + + case DATA_BINARY: + fputs("DATA_BINARY", stderr); + break; + + case DATA_FIXBINARY: + fputs("DATA_FIXBINARY", stderr); + break; + + case DATA_BLOB: + fputs("DATA_BLOB", stderr); + break; + + case DATA_INT: + fputs("DATA_INT", stderr); + break; + + case DATA_MYSQL: + fputs("DATA_MYSQL", stderr); + break; + + case DATA_SYS: + fputs("DATA_SYS", stderr); + break; + + default: + fprintf(stderr, "type %lu", (ulong) mtype); + break; + } + + len = type->len; + + if ((type->mtype == DATA_SYS) + || (type->mtype == DATA_VARCHAR) + || (type->mtype == DATA_CHAR)) { + putc(' ', stderr); + if (prtype == DATA_ROW_ID) { + fputs("DATA_ROW_ID", stderr); + len = DATA_ROW_ID_LEN; + } else if (prtype == DATA_ROLL_PTR) { + fputs("DATA_ROLL_PTR", stderr); + len = DATA_ROLL_PTR_LEN; + } else if (prtype == DATA_TRX_ID) { + fputs("DATA_TRX_ID", stderr); + len = DATA_TRX_ID_LEN; + } else if (prtype == DATA_ENGLISH) { + fputs("DATA_ENGLISH", stderr); + } else { + fprintf(stderr, "prtype %lu", (ulong) prtype); + } + } else { + if (prtype & DATA_UNSIGNED) { + fputs(" DATA_UNSIGNED", stderr); + } + + if (prtype & DATA_BINARY_TYPE) { + fputs(" DATA_BINARY_TYPE", stderr); + } + + if (prtype & DATA_NOT_NULL) { + fputs(" DATA_NOT_NULL", stderr); + } + } + + fprintf(stderr, " len %lu", (ulong) len); +} diff --git a/storage/xtradb/dict/dict0boot.c b/storage/xtradb/dict/dict0boot.c new file mode 100644 index 00000000000..cd90016dd78 --- /dev/null +++ b/storage/xtradb/dict/dict0boot.c @@ -0,0 +1,466 @@ +/***************************************************************************** + +Copyright (c) 1996, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/****************************************************** +Data dictionary creation and booting + +Created 4/18/1996 Heikki Tuuri +*******************************************************/ + +#include "dict0boot.h" + +#ifdef UNIV_NONINL +#include "dict0boot.ic" +#endif + +#include "dict0crea.h" +#include "btr0btr.h" +#include "dict0load.h" +#include "dict0load.h" +#include "trx0trx.h" +#include "srv0srv.h" +#include "ibuf0ibuf.h" +#include "buf0flu.h" +#include "log0recv.h" +#include "os0file.h" + +/************************************************************************** +Gets a pointer to the dictionary header and x-latches its page. */ +UNIV_INTERN +dict_hdr_t* +dict_hdr_get( +/*=========*/ + /* out: pointer to the dictionary header, + page x-latched */ + mtr_t* mtr) /* in: mtr */ +{ + buf_block_t* block; + dict_hdr_t* header; + + block = buf_page_get(DICT_HDR_SPACE, 0, DICT_HDR_PAGE_NO, + RW_X_LATCH, mtr); + header = DICT_HDR + buf_block_get_frame(block); + + buf_block_dbg_add_level(block, SYNC_DICT_HEADER); + + return(header); +} + +/************************************************************************** +Returns a new table, index, or tree id. */ +UNIV_INTERN +dulint +dict_hdr_get_new_id( +/*================*/ + /* out: the new id */ + ulint type) /* in: DICT_HDR_ROW_ID, ... */ +{ + dict_hdr_t* dict_hdr; + dulint id; + mtr_t mtr; + + ut_ad((type == DICT_HDR_TABLE_ID) || (type == DICT_HDR_INDEX_ID)); + + mtr_start(&mtr); + + dict_hdr = dict_hdr_get(&mtr); + + id = mtr_read_dulint(dict_hdr + type, &mtr); + id = ut_dulint_add(id, 1); + + mlog_write_dulint(dict_hdr + type, id, &mtr); + + mtr_commit(&mtr); + + return(id); +} + +/************************************************************************** +Writes the current value of the row id counter to the dictionary header file +page. */ +UNIV_INTERN +void +dict_hdr_flush_row_id(void) +/*=======================*/ +{ + dict_hdr_t* dict_hdr; + dulint id; + mtr_t mtr; + + ut_ad(mutex_own(&(dict_sys->mutex))); + + id = dict_sys->row_id; + + mtr_start(&mtr); + + dict_hdr = dict_hdr_get(&mtr); + + mlog_write_dulint(dict_hdr + DICT_HDR_ROW_ID, id, &mtr); + + mtr_commit(&mtr); +} + +/********************************************************************* +Creates the file page for the dictionary header. This function is +called only at the database creation. */ +static +ibool +dict_hdr_create( +/*============*/ + /* out: TRUE if succeed */ + mtr_t* mtr) /* in: mtr */ +{ + buf_block_t* block; + dict_hdr_t* dict_header; + ulint root_page_no; + + ut_ad(mtr); + + /* Create the dictionary header file block in a new, allocated file + segment in the system tablespace */ + block = fseg_create(DICT_HDR_SPACE, 0, + DICT_HDR + DICT_HDR_FSEG_HEADER, mtr); + + ut_a(DICT_HDR_PAGE_NO == buf_block_get_page_no(block)); + + dict_header = dict_hdr_get(mtr); + + /* Start counting row, table, index, and tree ids from + DICT_HDR_FIRST_ID */ + mlog_write_dulint(dict_header + DICT_HDR_ROW_ID, + ut_dulint_create(0, DICT_HDR_FIRST_ID), mtr); + + mlog_write_dulint(dict_header + DICT_HDR_TABLE_ID, + ut_dulint_create(0, DICT_HDR_FIRST_ID), mtr); + + mlog_write_dulint(dict_header + DICT_HDR_INDEX_ID, + ut_dulint_create(0, DICT_HDR_FIRST_ID), mtr); + + /* Obsolete, but we must initialize it to 0 anyway. */ + mlog_write_dulint(dict_header + DICT_HDR_MIX_ID, + ut_dulint_create(0, DICT_HDR_FIRST_ID), mtr); + + /* Create the B-tree roots for the clustered indexes of the basic + system tables */ + + /*--------------------------*/ + root_page_no = btr_create(DICT_CLUSTERED | DICT_UNIQUE, + DICT_HDR_SPACE, 0, DICT_TABLES_ID, + srv_sys->dummy_ind1, mtr); + if (root_page_no == FIL_NULL) { + + return(FALSE); + } + + mlog_write_ulint(dict_header + DICT_HDR_TABLES, root_page_no, + MLOG_4BYTES, mtr); + /*--------------------------*/ + root_page_no = btr_create(DICT_UNIQUE, DICT_HDR_SPACE, 0, + DICT_TABLE_IDS_ID, + srv_sys->dummy_ind1, mtr); + if (root_page_no == FIL_NULL) { + + return(FALSE); + } + + mlog_write_ulint(dict_header + DICT_HDR_TABLE_IDS, root_page_no, + MLOG_4BYTES, mtr); + /*--------------------------*/ + root_page_no = btr_create(DICT_CLUSTERED | DICT_UNIQUE, + DICT_HDR_SPACE, 0, DICT_COLUMNS_ID, + srv_sys->dummy_ind1, mtr); + if (root_page_no == FIL_NULL) { + + return(FALSE); + } + + mlog_write_ulint(dict_header + DICT_HDR_COLUMNS, root_page_no, + MLOG_4BYTES, mtr); + /*--------------------------*/ + root_page_no = btr_create(DICT_CLUSTERED | DICT_UNIQUE, + DICT_HDR_SPACE, 0, DICT_INDEXES_ID, + srv_sys->dummy_ind1, mtr); + if (root_page_no == FIL_NULL) { + + return(FALSE); + } + + mlog_write_ulint(dict_header + DICT_HDR_INDEXES, root_page_no, + MLOG_4BYTES, mtr); + /*--------------------------*/ + root_page_no = btr_create(DICT_CLUSTERED | DICT_UNIQUE, + DICT_HDR_SPACE, 0, DICT_FIELDS_ID, + srv_sys->dummy_ind1, mtr); + if (root_page_no == FIL_NULL) { + + return(FALSE); + } + + mlog_write_ulint(dict_header + DICT_HDR_FIELDS, root_page_no, + MLOG_4BYTES, mtr); + /*--------------------------*/ + + return(TRUE); +} + +/********************************************************************* +Initializes the data dictionary memory structures when the database is +started. This function is also called when the data dictionary is created. */ +UNIV_INTERN +void +dict_boot(void) +/*===========*/ +{ + dict_table_t* table; + dict_index_t* index; + dict_hdr_t* dict_hdr; + mem_heap_t* heap; + mtr_t mtr; + ulint error; + + mtr_start(&mtr); + + /* Create the hash tables etc. */ + dict_init(); + + heap = mem_heap_create(450); + + mutex_enter(&(dict_sys->mutex)); + + /* Get the dictionary header */ + dict_hdr = dict_hdr_get(&mtr); + + /* Because we only write new row ids to disk-based data structure + (dictionary header) when it is divisible by + DICT_HDR_ROW_ID_WRITE_MARGIN, in recovery we will not recover + the latest value of the row id counter. Therefore we advance + the counter at the database startup to avoid overlapping values. + Note that when a user after database startup first time asks for + a new row id, then because the counter is now divisible by + ..._MARGIN, it will immediately be updated to the disk-based + header. */ + + dict_sys->row_id = ut_dulint_add( + ut_dulint_align_up(mtr_read_dulint(dict_hdr + DICT_HDR_ROW_ID, + &mtr), + DICT_HDR_ROW_ID_WRITE_MARGIN), + DICT_HDR_ROW_ID_WRITE_MARGIN); + + /* Insert into the dictionary cache the descriptions of the basic + system tables */ + /*-------------------------*/ + table = dict_mem_table_create("SYS_TABLES", DICT_HDR_SPACE, 8, 0); + table->n_mysql_handles_opened = 1; /* for pin */ + + dict_mem_table_add_col(table, heap, "NAME", DATA_BINARY, 0, 0); + dict_mem_table_add_col(table, heap, "ID", DATA_BINARY, 0, 0); + /* ROW_FORMAT = (N_COLS >> 31) ? COMPACT : REDUNDANT */ + dict_mem_table_add_col(table, heap, "N_COLS", DATA_INT, 0, 4); + /* TYPE is either DICT_TABLE_ORDINARY, or (TYPE & DICT_TF_COMPACT) + and (TYPE & DICT_TF_FORMAT_MASK) are nonzero and TYPE = table->flags */ + dict_mem_table_add_col(table, heap, "TYPE", DATA_INT, 0, 4); + dict_mem_table_add_col(table, heap, "MIX_ID", DATA_BINARY, 0, 0); + dict_mem_table_add_col(table, heap, "MIX_LEN", DATA_INT, 0, 4); + dict_mem_table_add_col(table, heap, "CLUSTER_NAME", DATA_BINARY, 0, 0); + dict_mem_table_add_col(table, heap, "SPACE", DATA_INT, 0, 4); + + table->id = DICT_TABLES_ID; + + dict_table_add_to_cache(table, heap); + dict_sys->sys_tables = table; + mem_heap_empty(heap); + + index = dict_mem_index_create("SYS_TABLES", "CLUST_IND", + DICT_HDR_SPACE, + DICT_UNIQUE | DICT_CLUSTERED, 1); + + dict_mem_index_add_field(index, "NAME", 0); + + index->id = DICT_TABLES_ID; + + error = dict_index_add_to_cache(table, index, + mtr_read_ulint(dict_hdr + + DICT_HDR_TABLES, + MLOG_4BYTES, &mtr), + FALSE); + ut_a(error == DB_SUCCESS); + + /*-------------------------*/ + index = dict_mem_index_create("SYS_TABLES", "ID_IND", + DICT_HDR_SPACE, DICT_UNIQUE, 1); + dict_mem_index_add_field(index, "ID", 0); + + index->id = DICT_TABLE_IDS_ID; + error = dict_index_add_to_cache(table, index, + mtr_read_ulint(dict_hdr + + DICT_HDR_TABLE_IDS, + MLOG_4BYTES, &mtr), + FALSE); + ut_a(error == DB_SUCCESS); + + /*-------------------------*/ + table = dict_mem_table_create("SYS_COLUMNS", DICT_HDR_SPACE, 7, 0); + table->n_mysql_handles_opened = 1; /* for pin */ + + dict_mem_table_add_col(table, heap, "TABLE_ID", DATA_BINARY, 0, 0); + dict_mem_table_add_col(table, heap, "POS", DATA_INT, 0, 4); + dict_mem_table_add_col(table, heap, "NAME", DATA_BINARY, 0, 0); + dict_mem_table_add_col(table, heap, "MTYPE", DATA_INT, 0, 4); + dict_mem_table_add_col(table, heap, "PRTYPE", DATA_INT, 0, 4); + dict_mem_table_add_col(table, heap, "LEN", DATA_INT, 0, 4); + dict_mem_table_add_col(table, heap, "PREC", DATA_INT, 0, 4); + + table->id = DICT_COLUMNS_ID; + + dict_table_add_to_cache(table, heap); + dict_sys->sys_columns = table; + mem_heap_empty(heap); + + index = dict_mem_index_create("SYS_COLUMNS", "CLUST_IND", + DICT_HDR_SPACE, + DICT_UNIQUE | DICT_CLUSTERED, 2); + + dict_mem_index_add_field(index, "TABLE_ID", 0); + dict_mem_index_add_field(index, "POS", 0); + + index->id = DICT_COLUMNS_ID; + error = dict_index_add_to_cache(table, index, + mtr_read_ulint(dict_hdr + + DICT_HDR_COLUMNS, + MLOG_4BYTES, &mtr), + FALSE); + ut_a(error == DB_SUCCESS); + + /*-------------------------*/ + table = dict_mem_table_create("SYS_INDEXES", DICT_HDR_SPACE, 7, 0); + table->n_mysql_handles_opened = 1; /* for pin */ + + dict_mem_table_add_col(table, heap, "TABLE_ID", DATA_BINARY, 0, 0); + dict_mem_table_add_col(table, heap, "ID", DATA_BINARY, 0, 0); + dict_mem_table_add_col(table, heap, "NAME", DATA_BINARY, 0, 0); + dict_mem_table_add_col(table, heap, "N_FIELDS", DATA_INT, 0, 4); + dict_mem_table_add_col(table, heap, "TYPE", DATA_INT, 0, 4); + dict_mem_table_add_col(table, heap, "SPACE", DATA_INT, 0, 4); + dict_mem_table_add_col(table, heap, "PAGE_NO", DATA_INT, 0, 4); + + /* The '+ 2' below comes from the 2 system fields */ +#if DICT_SYS_INDEXES_PAGE_NO_FIELD != 6 + 2 +#error "DICT_SYS_INDEXES_PAGE_NO_FIELD != 6 + 2" +#endif +#if DICT_SYS_INDEXES_SPACE_NO_FIELD != 5 + 2 +#error "DICT_SYS_INDEXES_SPACE_NO_FIELD != 5 + 2" +#endif +#if DICT_SYS_INDEXES_TYPE_FIELD != 4 + 2 +#error "DICT_SYS_INDEXES_TYPE_FIELD != 4 + 2" +#endif + + table->id = DICT_INDEXES_ID; + dict_table_add_to_cache(table, heap); + dict_sys->sys_indexes = table; + mem_heap_empty(heap); + + index = dict_mem_index_create("SYS_INDEXES", "CLUST_IND", + DICT_HDR_SPACE, + DICT_UNIQUE | DICT_CLUSTERED, 2); + + dict_mem_index_add_field(index, "TABLE_ID", 0); + dict_mem_index_add_field(index, "ID", 0); + + index->id = DICT_INDEXES_ID; + error = dict_index_add_to_cache(table, index, + mtr_read_ulint(dict_hdr + + DICT_HDR_INDEXES, + MLOG_4BYTES, &mtr), + FALSE); + ut_a(error == DB_SUCCESS); + + /*-------------------------*/ + table = dict_mem_table_create("SYS_FIELDS", DICT_HDR_SPACE, 3, 0); + table->n_mysql_handles_opened = 1; /* for pin */ + + dict_mem_table_add_col(table, heap, "INDEX_ID", DATA_BINARY, 0, 0); + dict_mem_table_add_col(table, heap, "POS", DATA_INT, 0, 4); + dict_mem_table_add_col(table, heap, "COL_NAME", DATA_BINARY, 0, 0); + + table->id = DICT_FIELDS_ID; + dict_table_add_to_cache(table, heap); + dict_sys->sys_fields = table; + mem_heap_free(heap); + + index = dict_mem_index_create("SYS_FIELDS", "CLUST_IND", + DICT_HDR_SPACE, + DICT_UNIQUE | DICT_CLUSTERED, 2); + + dict_mem_index_add_field(index, "INDEX_ID", 0); + dict_mem_index_add_field(index, "POS", 0); + + index->id = DICT_FIELDS_ID; + error = dict_index_add_to_cache(table, index, + mtr_read_ulint(dict_hdr + + DICT_HDR_FIELDS, + MLOG_4BYTES, &mtr), + FALSE); + ut_a(error == DB_SUCCESS); + + mtr_commit(&mtr); + /*-------------------------*/ + + /* Initialize the insert buffer table and index for each tablespace */ + + ibuf_init_at_db_start(); + + /* Load definitions of other indexes on system tables */ + + dict_load_sys_table(dict_sys->sys_tables); + dict_load_sys_table(dict_sys->sys_columns); + dict_load_sys_table(dict_sys->sys_indexes); + dict_load_sys_table(dict_sys->sys_fields); + + mutex_exit(&(dict_sys->mutex)); +} + +/********************************************************************* +Inserts the basic system table data into themselves in the database +creation. */ +static +void +dict_insert_initial_data(void) +/*==========================*/ +{ + /* Does nothing yet */ +} + +/********************************************************************* +Creates and initializes the data dictionary at the database creation. */ +UNIV_INTERN +void +dict_create(void) +/*=============*/ +{ + mtr_t mtr; + + mtr_start(&mtr); + + dict_hdr_create(&mtr); + + mtr_commit(&mtr); + + dict_boot(); + + dict_insert_initial_data(); +} diff --git a/storage/xtradb/dict/dict0crea.c b/storage/xtradb/dict/dict0crea.c new file mode 100644 index 00000000000..01d092005c3 --- /dev/null +++ b/storage/xtradb/dict/dict0crea.c @@ -0,0 +1,1511 @@ +/***************************************************************************** + +Copyright (c) 1996, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/****************************************************** +Database object creation + +Created 1/8/1996 Heikki Tuuri +*******************************************************/ + +#include "dict0crea.h" + +#ifdef UNIV_NONINL +#include "dict0crea.ic" +#endif + +#include "btr0pcur.h" +#include "btr0btr.h" +#include "page0page.h" +#include "mach0data.h" +#include "dict0boot.h" +#include "dict0dict.h" +#include "que0que.h" +#include "row0ins.h" +#include "row0mysql.h" +#include "pars0pars.h" +#include "trx0roll.h" +#include "usr0sess.h" +#include "ut0vec.h" + +/********************************************************************* +Based on a table object, this function builds the entry to be inserted +in the SYS_TABLES system table. */ +static +dtuple_t* +dict_create_sys_tables_tuple( +/*=========================*/ + /* out: the tuple which should be inserted */ + dict_table_t* table, /* in: table */ + mem_heap_t* heap) /* in: memory heap from which the memory for + the built tuple is allocated */ +{ + dict_table_t* sys_tables; + dtuple_t* entry; + dfield_t* dfield; + byte* ptr; + + ut_ad(table && heap); + + sys_tables = dict_sys->sys_tables; + + entry = dtuple_create(heap, 8 + DATA_N_SYS_COLS); + + dict_table_copy_types(entry, sys_tables); + + /* 0: NAME -----------------------------*/ + dfield = dtuple_get_nth_field(entry, 0); + + dfield_set_data(dfield, table->name, ut_strlen(table->name)); + /* 3: ID -------------------------------*/ + dfield = dtuple_get_nth_field(entry, 1); + + ptr = mem_heap_alloc(heap, 8); + mach_write_to_8(ptr, table->id); + + dfield_set_data(dfield, ptr, 8); + /* 4: N_COLS ---------------------------*/ + dfield = dtuple_get_nth_field(entry, 2); + +#if DICT_TF_COMPACT != 1 +#error +#endif + + ptr = mem_heap_alloc(heap, 4); + mach_write_to_4(ptr, table->n_def + | ((table->flags & DICT_TF_COMPACT) << 31)); + dfield_set_data(dfield, ptr, 4); + /* 5: TYPE -----------------------------*/ + dfield = dtuple_get_nth_field(entry, 3); + + ptr = mem_heap_alloc(heap, 4); + if (table->flags & ~DICT_TF_COMPACT) { + ut_a(table->flags & DICT_TF_COMPACT); + ut_a(dict_table_get_format(table) >= DICT_TF_FORMAT_ZIP); + ut_a((table->flags & DICT_TF_ZSSIZE_MASK) + <= (DICT_TF_ZSSIZE_MAX << DICT_TF_ZSSIZE_SHIFT)); + ut_a(!(table->flags & (~0 << DICT_TF_BITS))); + mach_write_to_4(ptr, table->flags); + } else { + mach_write_to_4(ptr, DICT_TABLE_ORDINARY); + } + + dfield_set_data(dfield, ptr, 4); + /* 6: MIX_ID (obsolete) ---------------------------*/ + dfield = dtuple_get_nth_field(entry, 4); + + ptr = mem_heap_zalloc(heap, 8); + + dfield_set_data(dfield, ptr, 8); + /* 7: MIX_LEN (obsolete) --------------------------*/ + + dfield = dtuple_get_nth_field(entry, 5); + + ptr = mem_heap_zalloc(heap, 4); + + dfield_set_data(dfield, ptr, 4); + /* 8: CLUSTER_NAME ---------------------*/ + dfield = dtuple_get_nth_field(entry, 6); + dfield_set_null(dfield); /* not supported */ + + /* 9: SPACE ----------------------------*/ + dfield = dtuple_get_nth_field(entry, 7); + + ptr = mem_heap_alloc(heap, 4); + mach_write_to_4(ptr, table->space); + + dfield_set_data(dfield, ptr, 4); + /*----------------------------------*/ + + return(entry); +} + +/********************************************************************* +Based on a table object, this function builds the entry to be inserted +in the SYS_COLUMNS system table. */ +static +dtuple_t* +dict_create_sys_columns_tuple( +/*==========================*/ + /* out: the tuple which should be inserted */ + dict_table_t* table, /* in: table */ + ulint i, /* in: column number */ + mem_heap_t* heap) /* in: memory heap from which the memory for + the built tuple is allocated */ +{ + dict_table_t* sys_columns; + dtuple_t* entry; + const dict_col_t* column; + dfield_t* dfield; + byte* ptr; + const char* col_name; + + ut_ad(table && heap); + + column = dict_table_get_nth_col(table, i); + + sys_columns = dict_sys->sys_columns; + + entry = dtuple_create(heap, 7 + DATA_N_SYS_COLS); + + dict_table_copy_types(entry, sys_columns); + + /* 0: TABLE_ID -----------------------*/ + dfield = dtuple_get_nth_field(entry, 0); + + ptr = mem_heap_alloc(heap, 8); + mach_write_to_8(ptr, table->id); + + dfield_set_data(dfield, ptr, 8); + /* 1: POS ----------------------------*/ + dfield = dtuple_get_nth_field(entry, 1); + + ptr = mem_heap_alloc(heap, 4); + mach_write_to_4(ptr, i); + + dfield_set_data(dfield, ptr, 4); + /* 4: NAME ---------------------------*/ + dfield = dtuple_get_nth_field(entry, 2); + + col_name = dict_table_get_col_name(table, i); + dfield_set_data(dfield, col_name, ut_strlen(col_name)); + /* 5: MTYPE --------------------------*/ + dfield = dtuple_get_nth_field(entry, 3); + + ptr = mem_heap_alloc(heap, 4); + mach_write_to_4(ptr, column->mtype); + + dfield_set_data(dfield, ptr, 4); + /* 6: PRTYPE -------------------------*/ + dfield = dtuple_get_nth_field(entry, 4); + + ptr = mem_heap_alloc(heap, 4); + mach_write_to_4(ptr, column->prtype); + + dfield_set_data(dfield, ptr, 4); + /* 7: LEN ----------------------------*/ + dfield = dtuple_get_nth_field(entry, 5); + + ptr = mem_heap_alloc(heap, 4); + mach_write_to_4(ptr, column->len); + + dfield_set_data(dfield, ptr, 4); + /* 8: PREC ---------------------------*/ + dfield = dtuple_get_nth_field(entry, 6); + + ptr = mem_heap_alloc(heap, 4); + mach_write_to_4(ptr, 0/* unused */); + + dfield_set_data(dfield, ptr, 4); + /*---------------------------------*/ + + return(entry); +} + +/******************************************************************* +Builds a table definition to insert. */ +static +ulint +dict_build_table_def_step( +/*======================*/ + /* out: DB_SUCCESS or error code */ + que_thr_t* thr, /* in: query thread */ + tab_node_t* node) /* in: table create node */ +{ + dict_table_t* table; + dtuple_t* row; + ulint error; + const char* path_or_name; + ibool is_path; + mtr_t mtr; + + ut_ad(mutex_own(&(dict_sys->mutex))); + + table = node->table; + + table->id = dict_hdr_get_new_id(DICT_HDR_TABLE_ID); + + thr_get_trx(thr)->table_id = table->id; + + if (srv_file_per_table) { + /* We create a new single-table tablespace for the table. + We initially let it be 4 pages: + - page 0 is the fsp header and an extent descriptor page, + - page 1 is an ibuf bitmap page, + - page 2 is the first inode page, + - page 3 will contain the root of the clustered index of the + table we create here. */ + + ulint space = 0; /* reset to zero for the call below */ + + if (table->dir_path_of_temp_table) { + /* We place tables created with CREATE TEMPORARY + TABLE in the tmp dir of mysqld server */ + + path_or_name = table->dir_path_of_temp_table; + is_path = TRUE; + } else { + path_or_name = table->name; + is_path = FALSE; + } + + ut_ad(dict_table_get_format(table) <= DICT_TF_FORMAT_MAX); + ut_ad(!dict_table_zip_size(table) + || dict_table_get_format(table) >= DICT_TF_FORMAT_ZIP); + + error = fil_create_new_single_table_tablespace( + &space, path_or_name, is_path, + table->flags == DICT_TF_COMPACT ? 0 : table->flags, + FIL_IBD_FILE_INITIAL_SIZE); + table->space = (unsigned int) space; + + if (error != DB_SUCCESS) { + + return(error); + } + + mtr_start(&mtr); + + fsp_header_init(table->space, FIL_IBD_FILE_INITIAL_SIZE, &mtr); + + mtr_commit(&mtr); + } else { + /* Create in the system tablespace: disallow new features */ + table->flags &= DICT_TF_COMPACT; + } + + row = dict_create_sys_tables_tuple(table, node->heap); + + ins_node_set_new_row(node->tab_def, row); + + return(DB_SUCCESS); +} + +/******************************************************************* +Builds a column definition to insert. */ +static +ulint +dict_build_col_def_step( +/*====================*/ + /* out: DB_SUCCESS */ + tab_node_t* node) /* in: table create node */ +{ + dtuple_t* row; + + row = dict_create_sys_columns_tuple(node->table, node->col_no, + node->heap); + ins_node_set_new_row(node->col_def, row); + + return(DB_SUCCESS); +} + +/********************************************************************* +Based on an index object, this function builds the entry to be inserted +in the SYS_INDEXES system table. */ +static +dtuple_t* +dict_create_sys_indexes_tuple( +/*==========================*/ + /* out: the tuple which should be inserted */ + dict_index_t* index, /* in: index */ + mem_heap_t* heap) /* in: memory heap from which the memory for + the built tuple is allocated */ +{ + dict_table_t* sys_indexes; + dict_table_t* table; + dtuple_t* entry; + dfield_t* dfield; + byte* ptr; + + ut_ad(mutex_own(&(dict_sys->mutex))); + ut_ad(index && heap); + + sys_indexes = dict_sys->sys_indexes; + + table = dict_table_get_low(index->table_name); + + entry = dtuple_create(heap, 7 + DATA_N_SYS_COLS); + + dict_table_copy_types(entry, sys_indexes); + + /* 0: TABLE_ID -----------------------*/ + dfield = dtuple_get_nth_field(entry, 0); + + ptr = mem_heap_alloc(heap, 8); + mach_write_to_8(ptr, table->id); + + dfield_set_data(dfield, ptr, 8); + /* 1: ID ----------------------------*/ + dfield = dtuple_get_nth_field(entry, 1); + + ptr = mem_heap_alloc(heap, 8); + mach_write_to_8(ptr, index->id); + + dfield_set_data(dfield, ptr, 8); + /* 4: NAME --------------------------*/ + dfield = dtuple_get_nth_field(entry, 2); + + dfield_set_data(dfield, index->name, ut_strlen(index->name)); + /* 5: N_FIELDS ----------------------*/ + dfield = dtuple_get_nth_field(entry, 3); + + ptr = mem_heap_alloc(heap, 4); + mach_write_to_4(ptr, index->n_fields); + + dfield_set_data(dfield, ptr, 4); + /* 6: TYPE --------------------------*/ + dfield = dtuple_get_nth_field(entry, 4); + + ptr = mem_heap_alloc(heap, 4); + mach_write_to_4(ptr, index->type); + + dfield_set_data(dfield, ptr, 4); + /* 7: SPACE --------------------------*/ + +#if DICT_SYS_INDEXES_SPACE_NO_FIELD != 7 +#error "DICT_SYS_INDEXES_SPACE_NO_FIELD != 7" +#endif + + dfield = dtuple_get_nth_field(entry, 5); + + ptr = mem_heap_alloc(heap, 4); + mach_write_to_4(ptr, index->space); + + dfield_set_data(dfield, ptr, 4); + /* 8: PAGE_NO --------------------------*/ + +#if DICT_SYS_INDEXES_PAGE_NO_FIELD != 8 +#error "DICT_SYS_INDEXES_PAGE_NO_FIELD != 8" +#endif + + dfield = dtuple_get_nth_field(entry, 6); + + ptr = mem_heap_alloc(heap, 4); + mach_write_to_4(ptr, FIL_NULL); + + dfield_set_data(dfield, ptr, 4); + /*--------------------------------*/ + + return(entry); +} + +/********************************************************************* +Based on an index object, this function builds the entry to be inserted +in the SYS_FIELDS system table. */ +static +dtuple_t* +dict_create_sys_fields_tuple( +/*=========================*/ + /* out: the tuple which should be inserted */ + dict_index_t* index, /* in: index */ + ulint i, /* in: field number */ + mem_heap_t* heap) /* in: memory heap from which the memory for + the built tuple is allocated */ +{ + dict_table_t* sys_fields; + dtuple_t* entry; + dict_field_t* field; + dfield_t* dfield; + byte* ptr; + ibool index_contains_column_prefix_field = FALSE; + ulint j; + + ut_ad(index && heap); + + for (j = 0; j < index->n_fields; j++) { + if (dict_index_get_nth_field(index, j)->prefix_len > 0) { + index_contains_column_prefix_field = TRUE; + break; + } + } + + field = dict_index_get_nth_field(index, i); + + sys_fields = dict_sys->sys_fields; + + entry = dtuple_create(heap, 3 + DATA_N_SYS_COLS); + + dict_table_copy_types(entry, sys_fields); + + /* 0: INDEX_ID -----------------------*/ + dfield = dtuple_get_nth_field(entry, 0); + + ptr = mem_heap_alloc(heap, 8); + mach_write_to_8(ptr, index->id); + + dfield_set_data(dfield, ptr, 8); + /* 1: POS + PREFIX LENGTH ----------------------------*/ + + dfield = dtuple_get_nth_field(entry, 1); + + ptr = mem_heap_alloc(heap, 4); + + if (index_contains_column_prefix_field) { + /* If there are column prefix fields in the index, then + we store the number of the field to the 2 HIGH bytes + and the prefix length to the 2 low bytes, */ + + mach_write_to_4(ptr, (i << 16) + field->prefix_len); + } else { + /* Else we store the number of the field to the 2 LOW bytes. + This is to keep the storage format compatible with + InnoDB versions < 4.0.14. */ + + mach_write_to_4(ptr, i); + } + + dfield_set_data(dfield, ptr, 4); + /* 4: COL_NAME -------------------------*/ + dfield = dtuple_get_nth_field(entry, 2); + + dfield_set_data(dfield, field->name, + ut_strlen(field->name)); + /*---------------------------------*/ + + return(entry); +} + +/********************************************************************* +Creates the tuple with which the index entry is searched for writing the index +tree root page number, if such a tree is created. */ +static +dtuple_t* +dict_create_search_tuple( +/*=====================*/ + /* out: the tuple for search */ + const dtuple_t* tuple, /* in: the tuple inserted in the SYS_INDEXES + table */ + mem_heap_t* heap) /* in: memory heap from which the memory for + the built tuple is allocated */ +{ + dtuple_t* search_tuple; + const dfield_t* field1; + dfield_t* field2; + + ut_ad(tuple && heap); + + search_tuple = dtuple_create(heap, 2); + + field1 = dtuple_get_nth_field(tuple, 0); + field2 = dtuple_get_nth_field(search_tuple, 0); + + dfield_copy(field2, field1); + + field1 = dtuple_get_nth_field(tuple, 1); + field2 = dtuple_get_nth_field(search_tuple, 1); + + dfield_copy(field2, field1); + + ut_ad(dtuple_validate(search_tuple)); + + return(search_tuple); +} + +/******************************************************************* +Builds an index definition row to insert. */ +static +ulint +dict_build_index_def_step( +/*======================*/ + /* out: DB_SUCCESS or error code */ + que_thr_t* thr, /* in: query thread */ + ind_node_t* node) /* in: index create node */ +{ + dict_table_t* table; + dict_index_t* index; + dtuple_t* row; + trx_t* trx; + + ut_ad(mutex_own(&(dict_sys->mutex))); + + trx = thr_get_trx(thr); + + index = node->index; + + table = dict_table_get_low(index->table_name); + + if (table == NULL) { + return(DB_TABLE_NOT_FOUND); + } + + trx->table_id = table->id; + + node->table = table; + + ut_ad((UT_LIST_GET_LEN(table->indexes) > 0) + || dict_index_is_clust(index)); + + index->id = dict_hdr_get_new_id(DICT_HDR_INDEX_ID); + + /* Inherit the space id from the table; we store all indexes of a + table in the same tablespace */ + + index->space = table->space; + node->page_no = FIL_NULL; + row = dict_create_sys_indexes_tuple(index, node->heap); + node->ind_row = row; + + ins_node_set_new_row(node->ind_def, row); + +#ifdef ROW_MERGE_IS_INDEX_USABLE + /* Note that the index was created by this transaction. */ + index->trx_id = trx->id; +#endif /* ROW_MERGE_IS_INDEX_USABLE */ + + return(DB_SUCCESS); +} + +/******************************************************************* +Builds a field definition row to insert. */ +static +ulint +dict_build_field_def_step( +/*======================*/ + /* out: DB_SUCCESS */ + ind_node_t* node) /* in: index create node */ +{ + dict_index_t* index; + dtuple_t* row; + + index = node->index; + + row = dict_create_sys_fields_tuple(index, node->field_no, node->heap); + + ins_node_set_new_row(node->field_def, row); + + return(DB_SUCCESS); +} + +/******************************************************************* +Creates an index tree for the index if it is not a member of a cluster. */ +static +ulint +dict_create_index_tree_step( +/*========================*/ + /* out: DB_SUCCESS or DB_OUT_OF_FILE_SPACE */ + ind_node_t* node) /* in: index create node */ +{ + dict_index_t* index; + dict_table_t* sys_indexes; + dict_table_t* table; + dtuple_t* search_tuple; + btr_pcur_t pcur; + mtr_t mtr; + + ut_ad(mutex_own(&(dict_sys->mutex))); + + index = node->index; + table = node->table; + + sys_indexes = dict_sys->sys_indexes; + + /* Run a mini-transaction in which the index tree is allocated for + the index and its root address is written to the index entry in + sys_indexes */ + + mtr_start(&mtr); + + search_tuple = dict_create_search_tuple(node->ind_row, node->heap); + + btr_pcur_open(UT_LIST_GET_FIRST(sys_indexes->indexes), + search_tuple, PAGE_CUR_L, BTR_MODIFY_LEAF, + &pcur, &mtr); + + btr_pcur_move_to_next_user_rec(&pcur, &mtr); + + node->page_no = btr_create(index->type, index->space, + dict_table_zip_size(index->table), + index->id, index, &mtr); + /* printf("Created a new index tree in space %lu root page %lu\n", + index->space, index->page_no); */ + + page_rec_write_index_page_no(btr_pcur_get_rec(&pcur), + DICT_SYS_INDEXES_PAGE_NO_FIELD, + node->page_no, &mtr); + btr_pcur_close(&pcur); + mtr_commit(&mtr); + + if (node->page_no == FIL_NULL) { + + return(DB_OUT_OF_FILE_SPACE); + } + + return(DB_SUCCESS); +} + +/*********************************************************************** +Drops the index tree associated with a row in SYS_INDEXES table. */ +UNIV_INTERN +void +dict_drop_index_tree( +/*=================*/ + rec_t* rec, /* in/out: record in the clustered index + of SYS_INDEXES table */ + mtr_t* mtr) /* in: mtr having the latch on the record page */ +{ + ulint root_page_no; + ulint space; + ulint zip_size; + const byte* ptr; + ulint len; + + ut_ad(mutex_own(&(dict_sys->mutex))); + ut_a(!dict_table_is_comp(dict_sys->sys_indexes)); + ptr = rec_get_nth_field_old(rec, DICT_SYS_INDEXES_PAGE_NO_FIELD, &len); + + ut_ad(len == 4); + + root_page_no = mtr_read_ulint(ptr, MLOG_4BYTES, mtr); + + if (root_page_no == FIL_NULL) { + /* The tree has already been freed */ + + return; + } + + ptr = rec_get_nth_field_old(rec, + DICT_SYS_INDEXES_SPACE_NO_FIELD, &len); + + ut_ad(len == 4); + + space = mtr_read_ulint(ptr, MLOG_4BYTES, mtr); + zip_size = fil_space_get_zip_size(space); + + if (UNIV_UNLIKELY(zip_size == ULINT_UNDEFINED)) { + /* It is a single table tablespace and the .ibd file is + missing: do nothing */ + + return; + } + + /* We free all the pages but the root page first; this operation + may span several mini-transactions */ + + btr_free_but_not_root(space, zip_size, root_page_no); + + /* Then we free the root page in the same mini-transaction where + we write FIL_NULL to the appropriate field in the SYS_INDEXES + record: this mini-transaction marks the B-tree totally freed */ + + /* printf("Dropping index tree in space %lu root page %lu\n", space, + root_page_no); */ + btr_free_root(space, zip_size, root_page_no, mtr); + + page_rec_write_index_page_no(rec, + DICT_SYS_INDEXES_PAGE_NO_FIELD, + FIL_NULL, mtr); +} + +/*********************************************************************** +Truncates the index tree associated with a row in SYS_INDEXES table. */ +UNIV_INTERN +ulint +dict_truncate_index_tree( +/*=====================*/ + /* out: new root page number, or + FIL_NULL on failure */ + dict_table_t* table, /* in: the table the index belongs to */ + ulint space, /* in: 0=truncate, + nonzero=create the index tree in the + given tablespace */ + btr_pcur_t* pcur, /* in/out: persistent cursor pointing to + record in the clustered index of + SYS_INDEXES table. The cursor may be + repositioned in this call. */ + mtr_t* mtr) /* in: mtr having the latch + on the record page. The mtr may be + committed and restarted in this call. */ +{ + ulint root_page_no; + ibool drop = !space; + ulint zip_size; + ulint type; + dulint index_id; + rec_t* rec; + const byte* ptr; + ulint len; + dict_index_t* index; + + ut_ad(mutex_own(&(dict_sys->mutex))); + ut_a(!dict_table_is_comp(dict_sys->sys_indexes)); + rec = btr_pcur_get_rec(pcur); + ptr = rec_get_nth_field_old(rec, DICT_SYS_INDEXES_PAGE_NO_FIELD, &len); + + ut_ad(len == 4); + + root_page_no = mtr_read_ulint(ptr, MLOG_4BYTES, mtr); + + if (drop && root_page_no == FIL_NULL) { + /* The tree has been freed. */ + + ut_print_timestamp(stderr); + fprintf(stderr, " InnoDB: Trying to TRUNCATE" + " a missing index of table %s!\n", table->name); + drop = FALSE; + } + + ptr = rec_get_nth_field_old(rec, + DICT_SYS_INDEXES_SPACE_NO_FIELD, &len); + + ut_ad(len == 4); + + if (drop) { + space = mtr_read_ulint(ptr, MLOG_4BYTES, mtr); + } + + zip_size = fil_space_get_zip_size(space); + + if (UNIV_UNLIKELY(zip_size == ULINT_UNDEFINED)) { + /* It is a single table tablespace and the .ibd file is + missing: do nothing */ + + ut_print_timestamp(stderr); + fprintf(stderr, " InnoDB: Trying to TRUNCATE" + " a missing .ibd file of table %s!\n", table->name); + return(FIL_NULL); + } + + ptr = rec_get_nth_field_old(rec, + DICT_SYS_INDEXES_TYPE_FIELD, &len); + ut_ad(len == 4); + type = mach_read_from_4(ptr); + + ptr = rec_get_nth_field_old(rec, 1, &len); + ut_ad(len == 8); + index_id = mach_read_from_8(ptr); + + if (!drop) { + + goto create; + } + + /* We free all the pages but the root page first; this operation + may span several mini-transactions */ + + btr_free_but_not_root(space, zip_size, root_page_no); + + /* Then we free the root page in the same mini-transaction where + we create the b-tree and write its new root page number to the + appropriate field in the SYS_INDEXES record: this mini-transaction + marks the B-tree totally truncated */ + + btr_page_get(space, zip_size, root_page_no, RW_X_LATCH, mtr); + + btr_free_root(space, zip_size, root_page_no, mtr); +create: + /* We will temporarily write FIL_NULL to the PAGE_NO field + in SYS_INDEXES, so that the database will not get into an + inconsistent state in case it crashes between the mtr_commit() + below and the following mtr_commit() call. */ + page_rec_write_index_page_no(rec, DICT_SYS_INDEXES_PAGE_NO_FIELD, + FIL_NULL, mtr); + + /* We will need to commit the mini-transaction in order to avoid + deadlocks in the btr_create() call, because otherwise we would + be freeing and allocating pages in the same mini-transaction. */ + btr_pcur_store_position(pcur, mtr); + mtr_commit(mtr); + + mtr_start(mtr); + btr_pcur_restore_position(BTR_MODIFY_LEAF, pcur, mtr); + + /* Find the index corresponding to this SYS_INDEXES record. */ + for (index = UT_LIST_GET_FIRST(table->indexes); + index; + index = UT_LIST_GET_NEXT(indexes, index)) { + if (!ut_dulint_cmp(index->id, index_id)) { + root_page_no = btr_create(type, space, zip_size, + index_id, index, mtr); + index->page = (unsigned int) root_page_no; + return(root_page_no); + } + } + + ut_print_timestamp(stderr); + fprintf(stderr, + " InnoDB: Index %lu %lu of table %s is missing\n" + "InnoDB: from the data dictionary during TRUNCATE!\n", + ut_dulint_get_high(index_id), + ut_dulint_get_low(index_id), + table->name); + + return(FIL_NULL); +} + +/************************************************************************* +Creates a table create graph. */ +UNIV_INTERN +tab_node_t* +tab_create_graph_create( +/*====================*/ + /* out, own: table create node */ + dict_table_t* table, /* in: table to create, built as a memory data + structure */ + mem_heap_t* heap) /* in: heap where created */ +{ + tab_node_t* node; + + node = mem_heap_alloc(heap, sizeof(tab_node_t)); + + node->common.type = QUE_NODE_CREATE_TABLE; + + node->table = table; + + node->state = TABLE_BUILD_TABLE_DEF; + node->heap = mem_heap_create(256); + + node->tab_def = ins_node_create(INS_DIRECT, dict_sys->sys_tables, + heap); + node->tab_def->common.parent = node; + + node->col_def = ins_node_create(INS_DIRECT, dict_sys->sys_columns, + heap); + node->col_def->common.parent = node; + + node->commit_node = commit_node_create(heap); + node->commit_node->common.parent = node; + + return(node); +} + +/************************************************************************* +Creates an index create graph. */ +UNIV_INTERN +ind_node_t* +ind_create_graph_create( +/*====================*/ + /* out, own: index create node */ + dict_index_t* index, /* in: index to create, built as a memory data + structure */ + mem_heap_t* heap) /* in: heap where created */ +{ + ind_node_t* node; + + node = mem_heap_alloc(heap, sizeof(ind_node_t)); + + node->common.type = QUE_NODE_CREATE_INDEX; + + node->index = index; + + node->state = INDEX_BUILD_INDEX_DEF; + node->page_no = FIL_NULL; + node->heap = mem_heap_create(256); + + node->ind_def = ins_node_create(INS_DIRECT, + dict_sys->sys_indexes, heap); + node->ind_def->common.parent = node; + + node->field_def = ins_node_create(INS_DIRECT, + dict_sys->sys_fields, heap); + node->field_def->common.parent = node; + + node->commit_node = commit_node_create(heap); + node->commit_node->common.parent = node; + + return(node); +} + +/*************************************************************** +Creates a table. This is a high-level function used in SQL execution graphs. */ +UNIV_INTERN +que_thr_t* +dict_create_table_step( +/*===================*/ + /* out: query thread to run next or NULL */ + que_thr_t* thr) /* in: query thread */ +{ + tab_node_t* node; + ulint err = DB_ERROR; + trx_t* trx; + + ut_ad(thr); + ut_ad(mutex_own(&(dict_sys->mutex))); + + trx = thr_get_trx(thr); + + node = thr->run_node; + + ut_ad(que_node_get_type(node) == QUE_NODE_CREATE_TABLE); + + if (thr->prev_node == que_node_get_parent(node)) { + node->state = TABLE_BUILD_TABLE_DEF; + } + + if (node->state == TABLE_BUILD_TABLE_DEF) { + + /* DO THE CHECKS OF THE CONSISTENCY CONSTRAINTS HERE */ + + err = dict_build_table_def_step(thr, node); + + if (err != DB_SUCCESS) { + + goto function_exit; + } + + node->state = TABLE_BUILD_COL_DEF; + node->col_no = 0; + + thr->run_node = node->tab_def; + + return(thr); + } + + if (node->state == TABLE_BUILD_COL_DEF) { + + if (node->col_no < (node->table)->n_def) { + + err = dict_build_col_def_step(node); + + if (err != DB_SUCCESS) { + + goto function_exit; + } + + node->col_no++; + + thr->run_node = node->col_def; + + return(thr); + } else { + node->state = TABLE_COMMIT_WORK; + } + } + + if (node->state == TABLE_COMMIT_WORK) { + + /* Table was correctly defined: do NOT commit the transaction + (CREATE TABLE does NOT do an implicit commit of the current + transaction) */ + + node->state = TABLE_ADD_TO_CACHE; + + /* thr->run_node = node->commit_node; + + return(thr); */ + } + + if (node->state == TABLE_ADD_TO_CACHE) { + + dict_table_add_to_cache(node->table, node->heap); + + err = DB_SUCCESS; + } + +function_exit: + trx->error_state = err; + + if (err == DB_SUCCESS) { + /* Ok: do nothing */ + + } else if (err == DB_LOCK_WAIT) { + + return(NULL); + } else { + /* SQL error detected */ + + return(NULL); + } + + thr->run_node = que_node_get_parent(node); + + return(thr); +} + +/*************************************************************** +Creates an index. This is a high-level function used in SQL execution +graphs. */ +UNIV_INTERN +que_thr_t* +dict_create_index_step( +/*===================*/ + /* out: query thread to run next or NULL */ + que_thr_t* thr) /* in: query thread */ +{ + ind_node_t* node; + ulint err = DB_ERROR; + trx_t* trx; + + ut_ad(thr); + ut_ad(mutex_own(&(dict_sys->mutex))); + + trx = thr_get_trx(thr); + + node = thr->run_node; + + ut_ad(que_node_get_type(node) == QUE_NODE_CREATE_INDEX); + + if (thr->prev_node == que_node_get_parent(node)) { + node->state = INDEX_BUILD_INDEX_DEF; + } + + if (node->state == INDEX_BUILD_INDEX_DEF) { + /* DO THE CHECKS OF THE CONSISTENCY CONSTRAINTS HERE */ + err = dict_build_index_def_step(thr, node); + + if (err != DB_SUCCESS) { + + goto function_exit; + } + + node->state = INDEX_BUILD_FIELD_DEF; + node->field_no = 0; + + thr->run_node = node->ind_def; + + return(thr); + } + + if (node->state == INDEX_BUILD_FIELD_DEF) { + + if (node->field_no < (node->index)->n_fields) { + + err = dict_build_field_def_step(node); + + if (err != DB_SUCCESS) { + + goto function_exit; + } + + node->field_no++; + + thr->run_node = node->field_def; + + return(thr); + } else { + node->state = INDEX_ADD_TO_CACHE; + } + } + + if (node->state == INDEX_ADD_TO_CACHE) { + + dulint index_id = node->index->id; + + err = dict_index_add_to_cache(node->table, node->index, + FIL_NULL, TRUE); + + node->index = dict_index_get_if_in_cache_low(index_id); + ut_a(!node->index == (err != DB_SUCCESS)); + + if (err != DB_SUCCESS) { + + goto function_exit; + } + + node->state = INDEX_CREATE_INDEX_TREE; + } + + if (node->state == INDEX_CREATE_INDEX_TREE) { + + err = dict_create_index_tree_step(node); + + if (err != DB_SUCCESS) { + dict_index_remove_from_cache(node->table, node->index); + node->index = NULL; + + goto function_exit; + } + + node->index->page = node->page_no; + node->state = INDEX_COMMIT_WORK; + } + + if (node->state == INDEX_COMMIT_WORK) { + + /* Index was correctly defined: do NOT commit the transaction + (CREATE INDEX does NOT currently do an implicit commit of + the current transaction) */ + + node->state = INDEX_CREATE_INDEX_TREE; + + /* thr->run_node = node->commit_node; + + return(thr); */ + } + +function_exit: + trx->error_state = err; + + if (err == DB_SUCCESS) { + /* Ok: do nothing */ + + } else if (err == DB_LOCK_WAIT) { + + return(NULL); + } else { + /* SQL error detected */ + + return(NULL); + } + + thr->run_node = que_node_get_parent(node); + + return(thr); +} + +#ifndef UNIV_HOTBACKUP +/******************************************************************** +Creates the foreign key constraints system tables inside InnoDB +at database creation or database start if they are not found or are +not of the right form. */ +UNIV_INTERN +ulint +dict_create_or_check_foreign_constraint_tables(void) +/*================================================*/ + /* out: DB_SUCCESS or error code */ +{ + dict_table_t* table1; + dict_table_t* table2; + ulint error; + trx_t* trx; + + mutex_enter(&(dict_sys->mutex)); + + table1 = dict_table_get_low("SYS_FOREIGN"); + table2 = dict_table_get_low("SYS_FOREIGN_COLS"); + + if (table1 && table2 + && UT_LIST_GET_LEN(table1->indexes) == 3 + && UT_LIST_GET_LEN(table2->indexes) == 1) { + + /* Foreign constraint system tables have already been + created, and they are ok */ + + table1->n_mysql_handles_opened = 1; /* for pin */ + table2->n_mysql_handles_opened = 1; /* for pin */ + + mutex_exit(&(dict_sys->mutex)); + + return(DB_SUCCESS); + } + + mutex_exit(&(dict_sys->mutex)); + + trx = trx_allocate_for_mysql(); + + trx->op_info = "creating foreign key sys tables"; + + row_mysql_lock_data_dictionary(trx); + + if (table1) { + fprintf(stderr, + "InnoDB: dropping incompletely created" + " SYS_FOREIGN table\n"); + row_drop_table_for_mysql("SYS_FOREIGN", trx, TRUE); + } + + if (table2) { + fprintf(stderr, + "InnoDB: dropping incompletely created" + " SYS_FOREIGN_COLS table\n"); + row_drop_table_for_mysql("SYS_FOREIGN_COLS", trx, TRUE); + } + + fprintf(stderr, + "InnoDB: Creating foreign key constraint system tables\n"); + + /* NOTE: in dict_load_foreigns we use the fact that + there are 2 secondary indexes on SYS_FOREIGN, and they + are defined just like below */ + + /* NOTE: when designing InnoDB's foreign key support in 2001, we made + an error and made the table names and the foreign key id of type + 'CHAR' (internally, really a VARCHAR). We should have made the type + VARBINARY, like in other InnoDB system tables, to get a clean + design. */ + + error = que_eval_sql(NULL, + "PROCEDURE CREATE_FOREIGN_SYS_TABLES_PROC () IS\n" + "BEGIN\n" + "CREATE TABLE\n" + "SYS_FOREIGN(ID CHAR, FOR_NAME CHAR," + " REF_NAME CHAR, N_COLS INT);\n" + "CREATE UNIQUE CLUSTERED INDEX ID_IND" + " ON SYS_FOREIGN (ID);\n" + "CREATE INDEX FOR_IND" + " ON SYS_FOREIGN (FOR_NAME);\n" + "CREATE INDEX REF_IND" + " ON SYS_FOREIGN (REF_NAME);\n" + "CREATE TABLE\n" + "SYS_FOREIGN_COLS(ID CHAR, POS INT," + " FOR_COL_NAME CHAR, REF_COL_NAME CHAR);\n" + "CREATE UNIQUE CLUSTERED INDEX ID_IND" + " ON SYS_FOREIGN_COLS (ID, POS);\n" + "END;\n" + , FALSE, trx); + + if (error != DB_SUCCESS) { + fprintf(stderr, "InnoDB: error %lu in creation\n", + (ulong) error); + + ut_a(error == DB_OUT_OF_FILE_SPACE + || error == DB_TOO_MANY_CONCURRENT_TRXS); + + fprintf(stderr, + "InnoDB: creation failed\n" + "InnoDB: tablespace is full\n" + "InnoDB: dropping incompletely created" + " SYS_FOREIGN tables\n"); + + row_drop_table_for_mysql("SYS_FOREIGN", trx, TRUE); + row_drop_table_for_mysql("SYS_FOREIGN_COLS", trx, TRUE); + + error = DB_MUST_GET_MORE_FILE_SPACE; + } + + trx_commit_for_mysql(trx); + + table1 = dict_table_get_low("SYS_FOREIGN"); + table2 = dict_table_get_low("SYS_FOREIGN_COLS"); + table1->n_mysql_handles_opened = 1; /* for pin */ + table2->n_mysql_handles_opened = 1; /* for pin */ + + row_mysql_unlock_data_dictionary(trx); + + trx_free_for_mysql(trx); + + if (error == DB_SUCCESS) { + fprintf(stderr, + "InnoDB: Foreign key constraint system tables" + " created\n"); + } + + return(error); +} + +/******************************************************************** +Evaluate the given foreign key SQL statement. */ +static +ulint +dict_foreign_eval_sql( +/*==================*/ + /* out: error code or DB_SUCCESS */ + pars_info_t* info, /* in: info struct, or NULL */ + const char* sql, /* in: SQL string to evaluate */ + dict_table_t* table, /* in: table */ + dict_foreign_t* foreign,/* in: foreign */ + trx_t* trx) /* in: transaction */ +{ + ulint error; + FILE* ef = dict_foreign_err_file; + + error = que_eval_sql(info, sql, FALSE, trx); + + if (error == DB_DUPLICATE_KEY) { + mutex_enter(&dict_foreign_err_mutex); + rewind(ef); + ut_print_timestamp(ef); + fputs(" Error in foreign key constraint creation for table ", + ef); + ut_print_name(ef, trx, TRUE, table->name); + fputs(".\nA foreign key constraint of name ", ef); + ut_print_name(ef, trx, TRUE, foreign->id); + fputs("\nalready exists." + " (Note that internally InnoDB adds 'databasename'\n" + "in front of the user-defined constraint name.)\n" + "Note that InnoDB's FOREIGN KEY system tables store\n" + "constraint names as case-insensitive, with the\n" + "MySQL standard latin1_swedish_ci collation. If you\n" + "create tables or databases whose names differ only in\n" + "the character case, then collisions in constraint\n" + "names can occur. Workaround: name your constraints\n" + "explicitly with unique names.\n", + ef); + + mutex_exit(&dict_foreign_err_mutex); + + return(error); + } + + if (error != DB_SUCCESS) { + fprintf(stderr, + "InnoDB: Foreign key constraint creation failed:\n" + "InnoDB: internal error number %lu\n", (ulong) error); + + mutex_enter(&dict_foreign_err_mutex); + ut_print_timestamp(ef); + fputs(" Internal error in foreign key constraint creation" + " for table ", ef); + ut_print_name(ef, trx, TRUE, table->name); + fputs(".\n" + "See the MySQL .err log in the datadir" + " for more information.\n", ef); + mutex_exit(&dict_foreign_err_mutex); + + return(error); + } + + return(DB_SUCCESS); +} + +/************************************************************************ +Add a single foreign key field definition to the data dictionary tables in +the database. */ +static +ulint +dict_create_add_foreign_field_to_dictionary( +/*========================================*/ + /* out: error code or DB_SUCCESS */ + ulint field_nr, /* in: foreign field number */ + dict_table_t* table, /* in: table */ + dict_foreign_t* foreign, /* in: foreign */ + trx_t* trx) /* in: transaction */ +{ + pars_info_t* info = pars_info_create(); + + pars_info_add_str_literal(info, "id", foreign->id); + + pars_info_add_int4_literal(info, "pos", field_nr); + + pars_info_add_str_literal(info, "for_col_name", + foreign->foreign_col_names[field_nr]); + + pars_info_add_str_literal(info, "ref_col_name", + foreign->referenced_col_names[field_nr]); + + return(dict_foreign_eval_sql( + info, + "PROCEDURE P () IS\n" + "BEGIN\n" + "INSERT INTO SYS_FOREIGN_COLS VALUES" + "(:id, :pos, :for_col_name, :ref_col_name);\n" + "END;\n", + table, foreign, trx)); +} + +/************************************************************************ +Add a single foreign key definition to the data dictionary tables in the +database. We also generate names to constraints that were not named by the +user. A generated constraint has a name of the format +databasename/tablename_ibfk_<number>, where the numbers start from 1, and +are given locally for this table, that is, the number is not global, as in +the old format constraints < 4.0.18 it used to be. */ +static +ulint +dict_create_add_foreign_to_dictionary( +/*==================================*/ + /* out: error code or DB_SUCCESS */ + ulint* id_nr, /* in/out: number to use in id generation; + incremented if used */ + dict_table_t* table, /* in: table */ + dict_foreign_t* foreign,/* in: foreign */ + trx_t* trx) /* in: transaction */ +{ + ulint error; + ulint i; + + pars_info_t* info = pars_info_create(); + + if (foreign->id == NULL) { + /* Generate a new constraint id */ + ulint namelen = strlen(table->name); + char* id = mem_heap_alloc(foreign->heap, namelen + 20); + /* no overflow if number < 1e13 */ + sprintf(id, "%s_ibfk_%lu", table->name, (ulong) (*id_nr)++); + foreign->id = id; + } + + pars_info_add_str_literal(info, "id", foreign->id); + + pars_info_add_str_literal(info, "for_name", table->name); + + pars_info_add_str_literal(info, "ref_name", + foreign->referenced_table_name); + + pars_info_add_int4_literal(info, "n_cols", + foreign->n_fields + (foreign->type << 24)); + + error = dict_foreign_eval_sql(info, + "PROCEDURE P () IS\n" + "BEGIN\n" + "INSERT INTO SYS_FOREIGN VALUES" + "(:id, :for_name, :ref_name, :n_cols);\n" + "END;\n" + , table, foreign, trx); + + if (error != DB_SUCCESS) { + + return(error); + } + + for (i = 0; i < foreign->n_fields; i++) { + error = dict_create_add_foreign_field_to_dictionary( + i, table, foreign, trx); + + if (error != DB_SUCCESS) { + + return(error); + } + } + + error = dict_foreign_eval_sql(NULL, + "PROCEDURE P () IS\n" + "BEGIN\n" + "COMMIT WORK;\n" + "END;\n" + , table, foreign, trx); + + return(error); +} + +/************************************************************************ +Adds foreign key definitions to data dictionary tables in the database. */ +UNIV_INTERN +ulint +dict_create_add_foreigns_to_dictionary( +/*===================================*/ + /* out: error code or DB_SUCCESS */ + ulint start_id,/* in: if we are actually doing ALTER TABLE + ADD CONSTRAINT, we want to generate constraint + numbers which are bigger than in the table so + far; we number the constraints from + start_id + 1 up; start_id should be set to 0 if + we are creating a new table, or if the table + so far has no constraints for which the name + was generated here */ + dict_table_t* table, /* in: table */ + trx_t* trx) /* in: transaction */ +{ + dict_foreign_t* foreign; + ulint number = start_id + 1; + ulint error; + + ut_ad(mutex_own(&(dict_sys->mutex))); + + if (NULL == dict_table_get_low("SYS_FOREIGN")) { + fprintf(stderr, + "InnoDB: table SYS_FOREIGN not found" + " in internal data dictionary\n"); + + return(DB_ERROR); + } + + for (foreign = UT_LIST_GET_FIRST(table->foreign_list); + foreign; + foreign = UT_LIST_GET_NEXT(foreign_list, foreign)) { + + error = dict_create_add_foreign_to_dictionary(&number, table, + foreign, trx); + + if (error != DB_SUCCESS) { + + return(error); + } + } + + return(DB_SUCCESS); +} +#endif /* !UNIV_HOTBACKUP */ diff --git a/storage/xtradb/dict/dict0dict.c b/storage/xtradb/dict/dict0dict.c new file mode 100644 index 00000000000..68fa5fccf64 --- /dev/null +++ b/storage/xtradb/dict/dict0dict.c @@ -0,0 +1,4810 @@ +/***************************************************************************** + +Copyright (c) 1996, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/********************************************************************** +Data dictionary system + +Created 1/8/1996 Heikki Tuuri +***********************************************************************/ + +#include "dict0dict.h" + +#ifdef UNIV_NONINL +#include "dict0dict.ic" +#endif + +#include "buf0buf.h" +#include "data0type.h" +#include "mach0data.h" +#include "dict0boot.h" +#include "dict0mem.h" +#include "dict0crea.h" +#include "trx0undo.h" +#include "btr0btr.h" +#include "btr0cur.h" +#include "btr0sea.h" +#include "page0zip.h" +#include "page0page.h" +#include "pars0pars.h" +#include "pars0sym.h" +#include "que0que.h" +#include "rem0cmp.h" +#include "row0merge.h" +#ifndef UNIV_HOTBACKUP +# include "m_ctype.h" /* my_isspace() */ +# include "ha_prototypes.h" /* innobase_strcasecmp() */ +#endif /* !UNIV_HOTBACKUP */ + +#include <ctype.h> + +/* the dictionary system */ +UNIV_INTERN dict_sys_t* dict_sys = NULL; + +/* table create, drop, etc. reserve this in X-mode; implicit or +backround operations purge, rollback, foreign key checks reserve this +in S-mode; we cannot trust that MySQL protects implicit or background +operations a table drop since MySQL does not know of them; therefore +we need this; NOTE: a transaction which reserves this must keep book +on the mode in trx->dict_operation_lock_mode */ +UNIV_INTERN rw_lock_t dict_operation_lock; + +#define DICT_HEAP_SIZE 100 /* initial memory heap size when + creating a table or index object */ +#define DICT_POOL_PER_TABLE_HASH 512 /* buffer pool max size per table + hash table fixed size in bytes */ +#define DICT_POOL_PER_VARYING 4 /* buffer pool max size per data + dictionary varying size in bytes */ + +/* Identifies generated InnoDB foreign key names */ +static char dict_ibfk[] = "_ibfk_"; + +/*********************************************************************** +Tries to find column names for the index and sets the col field of the +index. */ +static +void +dict_index_find_cols( +/*=================*/ + dict_table_t* table, /* in: table */ + dict_index_t* index); /* in: index */ +/*********************************************************************** +Builds the internal dictionary cache representation for a clustered +index, containing also system fields not defined by the user. */ +static +dict_index_t* +dict_index_build_internal_clust( +/*============================*/ + /* out, own: the internal + representation of the clustered + index */ + const dict_table_t* table, /* in: table */ + dict_index_t* index); /* in: user representation of + a clustered index */ +/*********************************************************************** +Builds the internal dictionary cache representation for a non-clustered +index, containing also system fields not defined by the user. */ +static +dict_index_t* +dict_index_build_internal_non_clust( +/*================================*/ + /* out, own: the internal + representation of the non-clustered + index */ + const dict_table_t* table, /* in: table */ + dict_index_t* index); /* in: user representation of + a non-clustered index */ +/************************************************************************** +Removes a foreign constraint struct from the dictionary cache. */ +static +void +dict_foreign_remove_from_cache( +/*===========================*/ + dict_foreign_t* foreign); /* in, own: foreign constraint */ +/************************************************************************** +Prints a column data. */ +static +void +dict_col_print_low( +/*===============*/ + const dict_table_t* table, /* in: table */ + const dict_col_t* col); /* in: column */ +/************************************************************************** +Prints an index data. */ +static +void +dict_index_print_low( +/*=================*/ + dict_index_t* index); /* in: index */ +/************************************************************************** +Prints a field data. */ +static +void +dict_field_print_low( +/*=================*/ + dict_field_t* field); /* in: field */ +/************************************************************************* +Frees a foreign key struct. */ +static +void +dict_foreign_free( +/*==============*/ + dict_foreign_t* foreign); /* in, own: foreign key struct */ + +/* Stream for storing detailed information about the latest foreign key +and unique key errors */ +UNIV_INTERN FILE* dict_foreign_err_file = NULL; +/* mutex protecting the foreign and unique error buffers */ +UNIV_INTERN mutex_t dict_foreign_err_mutex; + +#ifndef UNIV_HOTBACKUP +/********************************************************************** +Makes all characters in a NUL-terminated UTF-8 string lower case. */ +UNIV_INTERN +void +dict_casedn_str( +/*============*/ + char* a) /* in/out: string to put in lower case */ +{ + innobase_casedn_str(a); +} +#endif /* !UNIV_HOTBACKUP */ + +/************************************************************************ +Checks if the database name in two table names is the same. */ +UNIV_INTERN +ibool +dict_tables_have_same_db( +/*=====================*/ + /* out: TRUE if same db name */ + const char* name1, /* in: table name in the form + dbname '/' tablename */ + const char* name2) /* in: table name in the form + dbname '/' tablename */ +{ + for (; *name1 == *name2; name1++, name2++) { + if (*name1 == '/') { + return(TRUE); + } + ut_a(*name1); /* the names must contain '/' */ + } + return(FALSE); +} + +/************************************************************************ +Return the end of table name where we have removed dbname and '/'. */ +UNIV_INTERN +const char* +dict_remove_db_name( +/*================*/ + /* out: table name */ + const char* name) /* in: table name in the form + dbname '/' tablename */ +{ + const char* s = strchr(name, '/'); + ut_a(s); + + return(s + 1); +} + +/************************************************************************ +Get the database name length in a table name. */ +UNIV_INTERN +ulint +dict_get_db_name_len( +/*=================*/ + /* out: database name length */ + const char* name) /* in: table name in the form + dbname '/' tablename */ +{ + const char* s; + s = strchr(name, '/'); + ut_a(s); + return(s - name); +} + +/************************************************************************ +Reserves the dictionary system mutex for MySQL. */ +UNIV_INTERN +void +dict_mutex_enter_for_mysql(void) +/*============================*/ +{ + mutex_enter(&(dict_sys->mutex)); +} + +/************************************************************************ +Releases the dictionary system mutex for MySQL. */ +UNIV_INTERN +void +dict_mutex_exit_for_mysql(void) +/*===========================*/ +{ + mutex_exit(&(dict_sys->mutex)); +} + +/************************************************************************ +Decrements the count of open MySQL handles to a table. */ +UNIV_INTERN +void +dict_table_decrement_handle_count( +/*==============================*/ + dict_table_t* table, /* in/out: table */ + ibool dict_locked) /* in: TRUE=data dictionary locked */ +{ + if (!dict_locked) { + mutex_enter(&dict_sys->mutex); + } + + ut_ad(mutex_own(&dict_sys->mutex)); + ut_a(table->n_mysql_handles_opened > 0); + + table->n_mysql_handles_opened--; + + if (!dict_locked) { + mutex_exit(&dict_sys->mutex); + } +} + +/************************************************************************** +Returns a column's name. */ +UNIV_INTERN +const char* +dict_table_get_col_name( +/*====================*/ + /* out: column name. NOTE: not + guaranteed to stay valid if table is + modified in any way (columns added, + etc.). */ + const dict_table_t* table, /* in: table */ + ulint col_nr) /* in: column number */ +{ + ulint i; + const char* s; + + ut_ad(table); + ut_ad(col_nr < table->n_def); + ut_ad(table->magic_n == DICT_TABLE_MAGIC_N); + + s = table->col_names; + if (s) { + for (i = 0; i < col_nr; i++) { + s += strlen(s) + 1; + } + } + + return(s); +} + + +/************************************************************************ +Acquire the autoinc lock.*/ +UNIV_INTERN +void +dict_table_autoinc_lock( +/*====================*/ + dict_table_t* table) /* in/out: table */ +{ + mutex_enter(&table->autoinc_mutex); +} + +/************************************************************************ +Unconditionally set the autoinc counter. */ +UNIV_INTERN +void +dict_table_autoinc_initialize( +/*==========================*/ + dict_table_t* table, /* in/out: table */ + ib_uint64_t value) /* in: next value to assign to a row */ +{ + ut_ad(mutex_own(&table->autoinc_mutex)); + + table->autoinc = value; +} + +/************************************************************************ +Reads the next autoinc value (== autoinc counter value), 0 if not yet +initialized. */ +UNIV_INTERN +ib_uint64_t +dict_table_autoinc_read( +/*====================*/ + /* out: value for a new row, or 0 */ + const dict_table_t* table) /* in: table */ +{ + ut_ad(mutex_own(&table->autoinc_mutex)); + + return(table->autoinc); +} + +/************************************************************************ +Updates the autoinc counter if the value supplied is greater than the +current value. */ +UNIV_INTERN +void +dict_table_autoinc_update_if_greater( +/*=================================*/ + + dict_table_t* table, /* in/out: table */ + ib_uint64_t value) /* in: value which was assigned to a row */ +{ + ut_ad(mutex_own(&table->autoinc_mutex)); + + if (value > table->autoinc) { + + table->autoinc = value; + } +} + +/************************************************************************ +Release the autoinc lock.*/ +UNIV_INTERN +void +dict_table_autoinc_unlock( +/*======================*/ + dict_table_t* table) /* in/out: table */ +{ + mutex_exit(&table->autoinc_mutex); +} + +/************************************************************************** +Looks for an index with the given table and index id. +NOTE that we do not reserve the dictionary mutex. */ +UNIV_INTERN +dict_index_t* +dict_index_get_on_id_low( +/*=====================*/ + /* out: index or NULL if not found + from cache */ + dict_table_t* table, /* in: table */ + dulint id) /* in: index id */ +{ + dict_index_t* index; + + index = dict_table_get_first_index(table); + + while (index) { + if (0 == ut_dulint_cmp(id, index->id)) { + /* Found */ + + return(index); + } + + index = dict_table_get_next_index(index); + } + + return(NULL); +} + +/************************************************************************ +Looks for column n in an index. */ +UNIV_INTERN +ulint +dict_index_get_nth_col_pos( +/*=======================*/ + /* out: position in internal + representation of the index; + if not contained, returns + ULINT_UNDEFINED */ + const dict_index_t* index, /* in: index */ + ulint n) /* in: column number */ +{ + const dict_field_t* field; + const dict_col_t* col; + ulint pos; + ulint n_fields; + + ut_ad(index); + ut_ad(index->magic_n == DICT_INDEX_MAGIC_N); + + col = dict_table_get_nth_col(index->table, n); + + if (dict_index_is_clust(index)) { + + return(dict_col_get_clust_pos(col, index)); + } + + n_fields = dict_index_get_n_fields(index); + + for (pos = 0; pos < n_fields; pos++) { + field = dict_index_get_nth_field(index, pos); + + if (col == field->col && field->prefix_len == 0) { + + return(pos); + } + } + + return(ULINT_UNDEFINED); +} + +/************************************************************************ +Returns TRUE if the index contains a column or a prefix of that column. */ +UNIV_INTERN +ibool +dict_index_contains_col_or_prefix( +/*==============================*/ + /* out: TRUE if contains the column + or its prefix */ + const dict_index_t* index, /* in: index */ + ulint n) /* in: column number */ +{ + const dict_field_t* field; + const dict_col_t* col; + ulint pos; + ulint n_fields; + + ut_ad(index); + ut_ad(index->magic_n == DICT_INDEX_MAGIC_N); + + if (dict_index_is_clust(index)) { + + return(TRUE); + } + + col = dict_table_get_nth_col(index->table, n); + + n_fields = dict_index_get_n_fields(index); + + for (pos = 0; pos < n_fields; pos++) { + field = dict_index_get_nth_field(index, pos); + + if (col == field->col) { + + return(TRUE); + } + } + + return(FALSE); +} + +/************************************************************************ +Looks for a matching field in an index. The column has to be the same. The +column in index must be complete, or must contain a prefix longer than the +column in index2. That is, we must be able to construct the prefix in index2 +from the prefix in index. */ +UNIV_INTERN +ulint +dict_index_get_nth_field_pos( +/*=========================*/ + /* out: position in internal + representation of the index; + if not contained, returns + ULINT_UNDEFINED */ + const dict_index_t* index, /* in: index from which to search */ + const dict_index_t* index2, /* in: index */ + ulint n) /* in: field number in index2 */ +{ + const dict_field_t* field; + const dict_field_t* field2; + ulint n_fields; + ulint pos; + + ut_ad(index); + ut_ad(index->magic_n == DICT_INDEX_MAGIC_N); + + field2 = dict_index_get_nth_field(index2, n); + + n_fields = dict_index_get_n_fields(index); + + for (pos = 0; pos < n_fields; pos++) { + field = dict_index_get_nth_field(index, pos); + + if (field->col == field2->col + && (field->prefix_len == 0 + || (field->prefix_len >= field2->prefix_len + && field2->prefix_len != 0))) { + + return(pos); + } + } + + return(ULINT_UNDEFINED); +} + +/************************************************************************** +Returns a table object based on table id. */ +UNIV_INTERN +dict_table_t* +dict_table_get_on_id( +/*=================*/ + /* out: table, NULL if does not exist */ + dulint table_id, /* in: table id */ + trx_t* trx) /* in: transaction handle */ +{ + dict_table_t* table; + + if (ut_dulint_cmp(table_id, DICT_FIELDS_ID) <= 0 + || trx->dict_operation_lock_mode == RW_X_LATCH) { + /* It is a system table which will always exist in the table + cache: we avoid acquiring the dictionary mutex, because + if we are doing a rollback to handle an error in TABLE + CREATE, for example, we already have the mutex! */ + + ut_ad(mutex_own(&(dict_sys->mutex)) + || trx->dict_operation_lock_mode == RW_X_LATCH); + + return(dict_table_get_on_id_low(table_id)); + } + + mutex_enter(&(dict_sys->mutex)); + + table = dict_table_get_on_id_low(table_id); + + dict_table_LRU_trim(table); + + mutex_exit(&(dict_sys->mutex)); + + return(table); +} + +/************************************************************************ +Looks for column n position in the clustered index. */ +UNIV_INTERN +ulint +dict_table_get_nth_col_pos( +/*=======================*/ + /* out: position in internal + representation of + the clustered index */ + const dict_table_t* table, /* in: table */ + ulint n) /* in: column number */ +{ + return(dict_index_get_nth_col_pos(dict_table_get_first_index(table), + n)); +} + +/************************************************************************ +Checks if a column is in the ordering columns of the clustered index of a +table. Column prefixes are treated like whole columns. */ +UNIV_INTERN +ibool +dict_table_col_in_clustered_key( +/*============================*/ + /* out: TRUE if the column, or its + prefix, is in the clustered key */ + const dict_table_t* table, /* in: table */ + ulint n) /* in: column number */ +{ + const dict_index_t* index; + const dict_field_t* field; + const dict_col_t* col; + ulint pos; + ulint n_fields; + + ut_ad(table); + + col = dict_table_get_nth_col(table, n); + + index = dict_table_get_first_index(table); + + n_fields = dict_index_get_n_unique(index); + + for (pos = 0; pos < n_fields; pos++) { + field = dict_index_get_nth_field(index, pos); + + if (col == field->col) { + + return(TRUE); + } + } + + return(FALSE); +} + +/************************************************************************** +Inits the data dictionary module. */ +UNIV_INTERN +void +dict_init(void) +/*===========*/ +{ + dict_sys = mem_alloc(sizeof(dict_sys_t)); + + mutex_create(&dict_sys->mutex, SYNC_DICT); + + dict_sys->table_hash = hash_create(buf_pool_get_curr_size() + / (DICT_POOL_PER_TABLE_HASH + * UNIV_WORD_SIZE)); + dict_sys->table_id_hash = hash_create(buf_pool_get_curr_size() + / (DICT_POOL_PER_TABLE_HASH + * UNIV_WORD_SIZE)); + dict_sys->size = 0; + + UT_LIST_INIT(dict_sys->table_LRU); + + rw_lock_create(&dict_operation_lock, SYNC_DICT_OPERATION); + + dict_foreign_err_file = os_file_create_tmpfile(); + ut_a(dict_foreign_err_file); + + mutex_create(&dict_foreign_err_mutex, SYNC_ANY_LATCH); +} + +/************************************************************************** +Returns a table object and optionally increment its MySQL open handle count. +NOTE! This is a high-level function to be used mainly from outside the +'dict' directory. Inside this directory dict_table_get_low is usually the +appropriate function. */ +UNIV_INTERN +dict_table_t* +dict_table_get( +/*===========*/ + /* out: table, NULL if + does not exist */ + const char* table_name, /* in: table name */ + ibool inc_mysql_count) + /* in: whether to increment the open + handle count on the table */ +{ + dict_table_t* table; + + mutex_enter(&(dict_sys->mutex)); + + table = dict_table_get_low(table_name); + + if (inc_mysql_count && table) { + table->n_mysql_handles_opened++; + } + + dict_table_LRU_trim(table); + + mutex_exit(&(dict_sys->mutex)); + + if (table != NULL) { + if (!table->stat_initialized) { + /* If table->ibd_file_missing == TRUE, this will + print an error message and return without doing + anything. */ + dict_update_statistics(table); + } + } + + return(table); +} + +/************************************************************************** +Adds system columns to a table object. */ +UNIV_INTERN +void +dict_table_add_system_columns( +/*==========================*/ + dict_table_t* table, /* in/out: table */ + mem_heap_t* heap) /* in: temporary heap */ +{ + ut_ad(table); + ut_ad(table->n_def == table->n_cols - DATA_N_SYS_COLS); + ut_ad(table->magic_n == DICT_TABLE_MAGIC_N); + ut_ad(!table->cached); + + /* NOTE: the system columns MUST be added in the following order + (so that they can be indexed by the numerical value of DATA_ROW_ID, + etc.) and as the last columns of the table memory object. + The clustered index will not always physically contain all + system columns. */ + + dict_mem_table_add_col(table, heap, "DB_ROW_ID", DATA_SYS, + DATA_ROW_ID | DATA_NOT_NULL, + DATA_ROW_ID_LEN); +#if DATA_ROW_ID != 0 +#error "DATA_ROW_ID != 0" +#endif + dict_mem_table_add_col(table, heap, "DB_TRX_ID", DATA_SYS, + DATA_TRX_ID | DATA_NOT_NULL, + DATA_TRX_ID_LEN); +#if DATA_TRX_ID != 1 +#error "DATA_TRX_ID != 1" +#endif + dict_mem_table_add_col(table, heap, "DB_ROLL_PTR", DATA_SYS, + DATA_ROLL_PTR | DATA_NOT_NULL, + DATA_ROLL_PTR_LEN); +#if DATA_ROLL_PTR != 2 +#error "DATA_ROLL_PTR != 2" +#endif + + /* This check reminds that if a new system column is added to + the program, it should be dealt with here */ +#if DATA_N_SYS_COLS != 3 +#error "DATA_N_SYS_COLS != 3" +#endif +} + +/************************************************************************** +Adds a table object to the dictionary cache. */ +UNIV_INTERN +void +dict_table_add_to_cache( +/*====================*/ + dict_table_t* table, /* in: table */ + mem_heap_t* heap) /* in: temporary heap */ +{ + ulint fold; + ulint id_fold; + ulint i; + ulint row_len; + + /* The lower limit for what we consider a "big" row */ +#define BIG_ROW_SIZE 1024 + + ut_ad(mutex_own(&(dict_sys->mutex))); + + dict_table_add_system_columns(table, heap); + + table->cached = TRUE; + + fold = ut_fold_string(table->name); + id_fold = ut_fold_dulint(table->id); + + row_len = 0; + for (i = 0; i < table->n_def; i++) { + ulint col_len = dict_col_get_max_size( + dict_table_get_nth_col(table, i)); + + row_len += col_len; + + /* If we have a single unbounded field, or several gigantic + fields, mark the maximum row size as BIG_ROW_SIZE. */ + if (row_len >= BIG_ROW_SIZE || col_len >= BIG_ROW_SIZE) { + row_len = BIG_ROW_SIZE; + + break; + } + } + + table->big_rows = row_len >= BIG_ROW_SIZE; + + /* Look for a table with the same name: error if such exists */ + { + dict_table_t* table2; + HASH_SEARCH(name_hash, dict_sys->table_hash, fold, + dict_table_t*, table2, ut_ad(table2->cached), + ut_strcmp(table2->name, table->name) == 0); + ut_a(table2 == NULL); + +#ifdef UNIV_DEBUG + /* Look for the same table pointer with a different name */ + HASH_SEARCH_ALL(name_hash, dict_sys->table_hash, + dict_table_t*, table2, ut_ad(table2->cached), + table2 == table); + ut_ad(table2 == NULL); +#endif /* UNIV_DEBUG */ + } + + /* Look for a table with the same id: error if such exists */ + { + dict_table_t* table2; + HASH_SEARCH(id_hash, dict_sys->table_id_hash, id_fold, + dict_table_t*, table2, ut_ad(table2->cached), + ut_dulint_cmp(table2->id, table->id) == 0); + ut_a(table2 == NULL); + +#ifdef UNIV_DEBUG + /* Look for the same table pointer with a different id */ + HASH_SEARCH_ALL(id_hash, dict_sys->table_id_hash, + dict_table_t*, table2, ut_ad(table2->cached), + table2 == table); + ut_ad(table2 == NULL); +#endif /* UNIV_DEBUG */ + } + + /* Add table to hash table of tables */ + HASH_INSERT(dict_table_t, name_hash, dict_sys->table_hash, fold, + table); + + /* Add table to hash table of tables based on table id */ + HASH_INSERT(dict_table_t, id_hash, dict_sys->table_id_hash, id_fold, + table); + /* Add table to LRU list of tables */ + UT_LIST_ADD_FIRST(table_LRU, dict_sys->table_LRU, table); + + dict_sys->size += mem_heap_get_size(table->heap); +} + +/************************************************************************** +Looks for an index with the given id. NOTE that we do not reserve +the dictionary mutex: this function is for emergency purposes like +printing info of a corrupt database page! */ +UNIV_INTERN +dict_index_t* +dict_index_find_on_id_low( +/*======================*/ + /* out: index or NULL if not found from cache */ + dulint id) /* in: index id */ +{ + dict_table_t* table; + dict_index_t* index; + + table = UT_LIST_GET_FIRST(dict_sys->table_LRU); + + while (table) { + index = dict_table_get_first_index(table); + + while (index) { + if (0 == ut_dulint_cmp(id, index->id)) { + /* Found */ + + return(index); + } + + index = dict_table_get_next_index(index); + } + + table = UT_LIST_GET_NEXT(table_LRU, table); + } + + return(NULL); +} + +/************************************************************************** +Renames a table object. */ +UNIV_INTERN +ibool +dict_table_rename_in_cache( +/*=======================*/ + /* out: TRUE if success */ + dict_table_t* table, /* in/out: table */ + const char* new_name, /* in: new name */ + ibool rename_also_foreigns)/* in: in ALTER TABLE we want + to preserve the original table name + in constraints which reference it */ +{ + dict_foreign_t* foreign; + dict_index_t* index; + ulint fold; + ulint old_size; + const char* old_name; + + ut_ad(table); + ut_ad(mutex_own(&(dict_sys->mutex))); + + old_size = mem_heap_get_size(table->heap); + old_name = table->name; + + fold = ut_fold_string(new_name); + + /* Look for a table with the same name: error if such exists */ + { + dict_table_t* table2; + HASH_SEARCH(name_hash, dict_sys->table_hash, fold, + dict_table_t*, table2, ut_ad(table2->cached), + (ut_strcmp(table2->name, new_name) == 0)); + if (UNIV_LIKELY_NULL(table2)) { + ut_print_timestamp(stderr); + fputs(" InnoDB: Error: dictionary cache" + " already contains a table ", stderr); + ut_print_name(stderr, NULL, TRUE, new_name); + fputs("\n" + "InnoDB: cannot rename table ", stderr); + ut_print_name(stderr, NULL, TRUE, old_name); + putc('\n', stderr); + return(FALSE); + } + } + + /* If the table is stored in a single-table tablespace, rename the + .ibd file */ + + if (table->space != 0) { + if (table->dir_path_of_temp_table != NULL) { + ut_print_timestamp(stderr); + fputs(" InnoDB: Error: trying to rename a" + " TEMPORARY TABLE ", stderr); + ut_print_name(stderr, NULL, TRUE, old_name); + fputs(" (", stderr); + ut_print_filename(stderr, + table->dir_path_of_temp_table); + fputs(" )\n", stderr); + return(FALSE); + } else if (!fil_rename_tablespace(old_name, table->space, + new_name)) { + return(FALSE); + } + } + + /* Remove table from the hash tables of tables */ + HASH_DELETE(dict_table_t, name_hash, dict_sys->table_hash, + ut_fold_string(old_name), table); + table->name = mem_heap_strdup(table->heap, new_name); + + /* Add table to hash table of tables */ + HASH_INSERT(dict_table_t, name_hash, dict_sys->table_hash, fold, + table); + dict_sys->size += (mem_heap_get_size(table->heap) - old_size); + + /* Update the table_name field in indexes */ + index = dict_table_get_first_index(table); + + while (index != NULL) { + index->table_name = table->name; + + index = dict_table_get_next_index(index); + } + + if (!rename_also_foreigns) { + /* In ALTER TABLE we think of the rename table operation + in the direction table -> temporary table (#sql...) + as dropping the table with the old name and creating + a new with the new name. Thus we kind of drop the + constraints from the dictionary cache here. The foreign key + constraints will be inherited to the new table from the + system tables through a call of dict_load_foreigns. */ + + /* Remove the foreign constraints from the cache */ + foreign = UT_LIST_GET_LAST(table->foreign_list); + + while (foreign != NULL) { + dict_foreign_remove_from_cache(foreign); + foreign = UT_LIST_GET_LAST(table->foreign_list); + } + + /* Reset table field in referencing constraints */ + + foreign = UT_LIST_GET_FIRST(table->referenced_list); + + while (foreign != NULL) { + foreign->referenced_table = NULL; + foreign->referenced_index = NULL; + + foreign = UT_LIST_GET_NEXT(referenced_list, foreign); + } + + /* Make the list of referencing constraints empty */ + + UT_LIST_INIT(table->referenced_list); + + return(TRUE); + } + + /* Update the table name fields in foreign constraints, and update also + the constraint id of new format >= 4.0.18 constraints. Note that at + this point we have already changed table->name to the new name. */ + + foreign = UT_LIST_GET_FIRST(table->foreign_list); + + while (foreign != NULL) { + if (ut_strlen(foreign->foreign_table_name) + < ut_strlen(table->name)) { + /* Allocate a longer name buffer; + TODO: store buf len to save memory */ + + foreign->foreign_table_name + = mem_heap_alloc(foreign->heap, + ut_strlen(table->name) + 1); + } + + strcpy(foreign->foreign_table_name, table->name); + + if (strchr(foreign->id, '/')) { + ulint db_len; + char* old_id; + + /* This is a >= 4.0.18 format id */ + + old_id = mem_strdup(foreign->id); + + if (ut_strlen(foreign->id) > ut_strlen(old_name) + + ((sizeof dict_ibfk) - 1) + && !memcmp(foreign->id, old_name, + ut_strlen(old_name)) + && !memcmp(foreign->id + ut_strlen(old_name), + dict_ibfk, (sizeof dict_ibfk) - 1)) { + + /* This is a generated >= 4.0.18 format id */ + + if (strlen(table->name) > strlen(old_name)) { + foreign->id = mem_heap_alloc( + foreign->heap, + strlen(table->name) + + strlen(old_id) + 1); + } + + /* Replace the prefix 'databasename/tablename' + with the new names */ + strcpy(foreign->id, table->name); + strcat(foreign->id, + old_id + ut_strlen(old_name)); + } else { + /* This is a >= 4.0.18 format id where the user + gave the id name */ + db_len = dict_get_db_name_len(table->name) + 1; + + if (dict_get_db_name_len(table->name) + > dict_get_db_name_len(foreign->id)) { + + foreign->id = mem_heap_alloc( + foreign->heap, + db_len + strlen(old_id) + 1); + } + + /* Replace the database prefix in id with the + one from table->name */ + + ut_memcpy(foreign->id, table->name, db_len); + + strcpy(foreign->id + db_len, + dict_remove_db_name(old_id)); + } + + mem_free(old_id); + } + + foreign = UT_LIST_GET_NEXT(foreign_list, foreign); + } + + foreign = UT_LIST_GET_FIRST(table->referenced_list); + + while (foreign != NULL) { + if (ut_strlen(foreign->referenced_table_name) + < ut_strlen(table->name)) { + /* Allocate a longer name buffer; + TODO: store buf len to save memory */ + + foreign->referenced_table_name = mem_heap_alloc( + foreign->heap, strlen(table->name) + 1); + } + + strcpy(foreign->referenced_table_name, table->name); + + foreign = UT_LIST_GET_NEXT(referenced_list, foreign); + } + + return(TRUE); +} + +/************************************************************************** +Change the id of a table object in the dictionary cache. This is used in +DISCARD TABLESPACE. */ +UNIV_INTERN +void +dict_table_change_id_in_cache( +/*==========================*/ + dict_table_t* table, /* in/out: table object already in cache */ + dulint new_id) /* in: new id to set */ +{ + ut_ad(table); + ut_ad(mutex_own(&(dict_sys->mutex))); + ut_ad(table->magic_n == DICT_TABLE_MAGIC_N); + + /* Remove the table from the hash table of id's */ + + HASH_DELETE(dict_table_t, id_hash, dict_sys->table_id_hash, + ut_fold_dulint(table->id), table); + table->id = new_id; + + /* Add the table back to the hash table */ + HASH_INSERT(dict_table_t, id_hash, dict_sys->table_id_hash, + ut_fold_dulint(table->id), table); +} + +/************************************************************************** +Removes a table object from the dictionary cache. */ +UNIV_INTERN +void +dict_table_remove_from_cache( +/*=========================*/ + dict_table_t* table) /* in, own: table */ +{ + dict_foreign_t* foreign; + dict_index_t* index; + ulint size; + + ut_ad(table); + ut_ad(mutex_own(&(dict_sys->mutex))); + ut_ad(table->magic_n == DICT_TABLE_MAGIC_N); + +#if 0 + fputs("Removing table ", stderr); + ut_print_name(stderr, table->name, ULINT_UNDEFINED); + fputs(" from dictionary cache\n", stderr); +#endif + + /* Remove the foreign constraints from the cache */ + foreign = UT_LIST_GET_LAST(table->foreign_list); + + while (foreign != NULL) { + dict_foreign_remove_from_cache(foreign); + foreign = UT_LIST_GET_LAST(table->foreign_list); + } + + /* Reset table field in referencing constraints */ + + foreign = UT_LIST_GET_FIRST(table->referenced_list); + + while (foreign != NULL) { + foreign->referenced_table = NULL; + foreign->referenced_index = NULL; + + foreign = UT_LIST_GET_NEXT(referenced_list, foreign); + } + + /* Remove the indexes from the cache */ + index = UT_LIST_GET_LAST(table->indexes); + + while (index != NULL) { + dict_index_remove_from_cache(table, index); + index = UT_LIST_GET_LAST(table->indexes); + } + + /* Remove table from the hash tables of tables */ + HASH_DELETE(dict_table_t, name_hash, dict_sys->table_hash, + ut_fold_string(table->name), table); + HASH_DELETE(dict_table_t, id_hash, dict_sys->table_id_hash, + ut_fold_dulint(table->id), table); + + /* Remove table from LRU list of tables */ + UT_LIST_REMOVE(table_LRU, dict_sys->table_LRU, table); + + size = mem_heap_get_size(table->heap); + + ut_ad(dict_sys->size >= size); + + dict_sys->size -= size; + + dict_mem_table_free(table); +} + +/************************************************************************** +Frees tables from the end of table_LRU if the dictionary cache occupies +too much space. */ +UNIV_INTERN +void +dict_table_LRU_trim( +/*================*/ + dict_table_t* self) +{ + dict_table_t* table; + dict_table_t* prev_table; + dict_foreign_t* foreign; + ulint n_removed; + ulint n_have_parent; + ulint cached_foreign_tables; + +#ifdef UNIV_SYNC_DEBUG + ut_ad(mutex_own(&(dict_sys->mutex))); +#endif /* UNIV_SYNC_DEBUG */ + +retry: + n_removed = n_have_parent = 0; + table = UT_LIST_GET_LAST(dict_sys->table_LRU); + + while ( srv_dict_size_limit && table + && ((dict_sys->table_hash->n_cells + + dict_sys->table_id_hash->n_cells) * sizeof(hash_cell_t) + + dict_sys->size) > srv_dict_size_limit ) { + prev_table = UT_LIST_GET_PREV(table_LRU, table); + + if (table == self || table->n_mysql_handles_opened) + goto next_loop; + + cached_foreign_tables = 0; + foreign = UT_LIST_GET_FIRST(table->foreign_list); + while (foreign != NULL) { + if (foreign->referenced_table) + cached_foreign_tables++; + foreign = UT_LIST_GET_NEXT(foreign_list, foreign); + } + + if (cached_foreign_tables == 0) { + dict_table_remove_from_cache(table); + n_removed++; + } else { + n_have_parent++; + } +next_loop: + table = prev_table; + } + + if ( srv_dict_size_limit && n_have_parent && n_removed + && ((dict_sys->table_hash->n_cells + + dict_sys->table_id_hash->n_cells) * sizeof(hash_cell_t) + + dict_sys->size) > srv_dict_size_limit ) + goto retry; +} + +/******************************************************************** +If the given column name is reserved for InnoDB system columns, return +TRUE. */ +UNIV_INTERN +ibool +dict_col_name_is_reserved( +/*======================*/ + /* out: TRUE if name is reserved */ + const char* name) /* in: column name */ +{ + /* This check reminds that if a new system column is added to + the program, it should be dealt with here. */ +#if DATA_N_SYS_COLS != 3 +#error "DATA_N_SYS_COLS != 3" +#endif + + static const char* reserved_names[] = { + "DB_ROW_ID", "DB_TRX_ID", "DB_ROLL_PTR" + }; + + ulint i; + + for (i = 0; i < UT_ARR_SIZE(reserved_names); i++) { + if (strcmp(name, reserved_names[i]) == 0) { + + return(TRUE); + } + } + + return(FALSE); +} + +/******************************************************************** +If an undo log record for this table might not fit on a single page, +return TRUE. */ +static +ibool +dict_index_too_big_for_undo( +/*========================*/ + /* out: TRUE if the undo log + record could become too big */ + const dict_table_t* table, /* in: table */ + const dict_index_t* new_index) /* in: index */ +{ + /* Make sure that all column prefixes will fit in the undo log record + in trx_undo_page_report_modify() right after trx_undo_page_init(). */ + + ulint i; + const dict_index_t* clust_index + = dict_table_get_first_index(table); + ulint undo_page_len + = TRX_UNDO_PAGE_HDR - TRX_UNDO_PAGE_HDR_SIZE + + 2 /* next record pointer */ + + 1 /* type_cmpl */ + + 11 /* trx->undo_no */ - 11 /* table->id */ + + 1 /* rec_get_info_bits() */ + + 11 /* DB_TRX_ID */ + + 11 /* DB_ROLL_PTR */ + + 10 + FIL_PAGE_DATA_END /* trx_undo_left() */ + + 2/* pointer to previous undo log record */; + + if (UNIV_UNLIKELY(!clust_index)) { + ut_a(dict_index_is_clust(new_index)); + clust_index = new_index; + } + + /* Add the size of the ordering columns in the + clustered index. */ + for (i = 0; i < clust_index->n_uniq; i++) { + const dict_col_t* col + = dict_index_get_nth_col(clust_index, i); + + /* Use the maximum output size of + mach_write_compressed(), although the encoded + length should always fit in 2 bytes. */ + undo_page_len += 5 + dict_col_get_max_size(col); + } + + /* Add the old values of the columns to be updated. + First, the amount and the numbers of the columns. + These are written by mach_write_compressed() whose + maximum output length is 5 bytes. However, given that + the quantities are below REC_MAX_N_FIELDS (10 bits), + the maximum length is 2 bytes per item. */ + undo_page_len += 2 * (dict_table_get_n_cols(table) + 1); + + for (i = 0; i < clust_index->n_def; i++) { + const dict_col_t* col + = dict_index_get_nth_col(clust_index, i); + ulint max_size + = dict_col_get_max_size(col); + ulint fixed_size + = dict_col_get_fixed_size(col); + + if (fixed_size) { + /* Fixed-size columns are stored locally. */ + max_size = fixed_size; + } else if (max_size <= BTR_EXTERN_FIELD_REF_SIZE * 2) { + /* Short columns are stored locally. */ + } else if (!col->ord_part) { + /* See if col->ord_part would be set + because of new_index. */ + ulint j; + + for (j = 0; j < new_index->n_uniq; j++) { + if (dict_index_get_nth_col( + new_index, j) == col) { + + goto is_ord_part; + } + } + + /* This is not an ordering column in any index. + Thus, it can be stored completely externally. */ + max_size = BTR_EXTERN_FIELD_REF_SIZE; + } else { +is_ord_part: + /* This is an ordering column in some index. + A long enough prefix must be written to the + undo log. See trx_undo_page_fetch_ext(). */ + + if (max_size > REC_MAX_INDEX_COL_LEN) { + max_size = REC_MAX_INDEX_COL_LEN; + } + + max_size += BTR_EXTERN_FIELD_REF_SIZE; + } + + undo_page_len += 5 + max_size; + } + + return(undo_page_len >= UNIV_PAGE_SIZE); +} + +/******************************************************************** +If a record of this index might not fit on a single B-tree page, +return TRUE. */ +static +ibool +dict_index_too_big_for_tree( +/*========================*/ + /* out: TRUE if the index + record could become too big */ + const dict_table_t* table, /* in: table */ + const dict_index_t* new_index) /* in: index */ +{ + ulint zip_size; + ulint comp; + ulint i; + /* maximum possible storage size of a record */ + ulint rec_max_size; + /* maximum allowed size of a record on a leaf page */ + ulint page_rec_max; + /* maximum allowed size of a node pointer record */ + ulint page_ptr_max; + + comp = dict_table_is_comp(table); + zip_size = dict_table_zip_size(table); + + if (zip_size && zip_size < UNIV_PAGE_SIZE) { + /* On a compressed page, two records must fit in the + uncompressed page modification log. On compressed + pages with zip_size == UNIV_PAGE_SIZE, this limit will + never be reached. */ + ut_ad(comp); + /* The maximum allowed record size is the size of + an empty page, minus a byte for recoding the heap + number in the page modification log. The maximum + allowed node pointer size is half that. */ + page_rec_max = page_zip_empty_size(new_index->n_fields, + zip_size) - 1; + page_ptr_max = page_rec_max / 2; + /* On a compressed page, there is a two-byte entry in + the dense page directory for every record. But there + is no record header. */ + rec_max_size = 2; + } else { + /* The maximum allowed record size is half a B-tree + page. No additional sparse page directory entry will + be generated for the first few user records. */ + page_rec_max = page_get_free_space_of_empty(comp) / 2; + page_ptr_max = page_rec_max; + /* Each record has a header. */ + rec_max_size = comp + ? REC_N_NEW_EXTRA_BYTES + : REC_N_OLD_EXTRA_BYTES; + } + + if (comp) { + /* Include the "null" flags in the + maximum possible record size. */ + rec_max_size += UT_BITS_IN_BYTES(new_index->n_nullable); + } else { + /* For each column, include a 2-byte offset and a + "null" flag. The 1-byte format is only used in short + records that do not contain externally stored columns. + Such records could never exceed the page limit, even + when using the 2-byte format. */ + rec_max_size += 2 * new_index->n_fields; + } + + /* Compute the maximum possible record size. */ + for (i = 0; i < new_index->n_fields; i++) { + const dict_field_t* field + = dict_index_get_nth_field(new_index, i); + const dict_col_t* col + = dict_field_get_col(field); + ulint field_max_size; + ulint field_ext_max_size; + + /* In dtuple_convert_big_rec(), variable-length columns + that are longer than BTR_EXTERN_FIELD_REF_SIZE * 2 + may be chosen for external storage. + + Fixed-length columns, and all columns of secondary + index records are always stored inline. */ + + /* Determine the maximum length of the index field. + The field_ext_max_size should be computed as the worst + case in rec_get_converted_size_comp() for + REC_STATUS_ORDINARY records. */ + + field_max_size = dict_col_get_fixed_size(col); + if (field_max_size) { + /* dict_index_add_col() should guarantee this */ + ut_ad(!field->prefix_len + || field->fixed_len == field->prefix_len); + /* Fixed lengths are not encoded + in ROW_FORMAT=COMPACT. */ + field_ext_max_size = 0; + goto add_field_size; + } + + field_max_size = dict_col_get_max_size(col); + field_ext_max_size = field_max_size < 256 ? 1 : 2; + + if (field->prefix_len) { + if (field->prefix_len < field_max_size) { + field_max_size = field->prefix_len; + } + } else if (field_max_size > BTR_EXTERN_FIELD_REF_SIZE * 2 + && dict_index_is_clust(new_index)) { + + /* In the worst case, we have a locally stored + column of BTR_EXTERN_FIELD_REF_SIZE * 2 bytes. + The length can be stored in one byte. If the + column were stored externally, the lengths in + the clustered index page would be + BTR_EXTERN_FIELD_REF_SIZE and 2. */ + field_max_size = BTR_EXTERN_FIELD_REF_SIZE * 2; + field_ext_max_size = 1; + } + + if (comp) { + /* Add the extra size for ROW_FORMAT=COMPACT. + For ROW_FORMAT=REDUNDANT, these bytes were + added to rec_max_size before this loop. */ + rec_max_size += field_ext_max_size; + } +add_field_size: + rec_max_size += field_max_size; + + /* Check the size limit on leaf pages. */ + if (UNIV_UNLIKELY(rec_max_size >= page_rec_max)) { + + return(TRUE); + } + + /* Check the size limit on non-leaf pages. Records + stored in non-leaf B-tree pages consist of the unique + columns of the record (the key columns of the B-tree) + and a node pointer field. When we have processed the + unique columns, rec_max_size equals the size of the + node pointer record minus the node pointer column. */ + if (i + 1 == dict_index_get_n_unique_in_tree(new_index) + && rec_max_size + REC_NODE_PTR_SIZE >= page_ptr_max) { + + return(TRUE); + } + } + + return(FALSE); +} + +/************************************************************************** +Adds an index to the dictionary cache. */ +UNIV_INTERN +ulint +dict_index_add_to_cache( +/*====================*/ + /* out: DB_SUCCESS or DB_TOO_BIG_RECORD */ + dict_table_t* table, /* in: table on which the index is */ + dict_index_t* index, /* in, own: index; NOTE! The index memory + object is freed in this function! */ + ulint page_no,/* in: root page number of the index */ + ibool strict) /* in: TRUE=refuse to create the index + if records could be too big to fit in + an B-tree page */ +{ + dict_index_t* new_index; + ulint n_ord; + ulint i; + + ut_ad(index); + ut_ad(mutex_own(&(dict_sys->mutex))); + ut_ad(index->n_def == index->n_fields); + ut_ad(index->magic_n == DICT_INDEX_MAGIC_N); + + ut_ad(mem_heap_validate(index->heap)); + ut_a(!dict_index_is_clust(index) + || UT_LIST_GET_LEN(table->indexes) == 0); + + dict_index_find_cols(table, index); + + /* Build the cache internal representation of the index, + containing also the added system fields */ + + if (dict_index_is_clust(index)) { + new_index = dict_index_build_internal_clust(table, index); + } else { + new_index = dict_index_build_internal_non_clust(table, index); + } + + /* Set the n_fields value in new_index to the actual defined + number of fields in the cache internal representation */ + + new_index->n_fields = new_index->n_def; + + if (strict && dict_index_too_big_for_tree(table, new_index)) { +too_big: + dict_mem_index_free(new_index); + dict_mem_index_free(index); + return(DB_TOO_BIG_RECORD); + } + + if (UNIV_UNLIKELY(index->type & DICT_UNIVERSAL)) { + n_ord = new_index->n_fields; + } else { + n_ord = new_index->n_uniq; + } + + switch (dict_table_get_format(table)) { + case DICT_TF_FORMAT_51: + /* ROW_FORMAT=REDUNDANT and ROW_FORMAT=COMPACT store + prefixes of externally stored columns locally within + the record. There are no special considerations for + the undo log record size. */ + goto undo_size_ok; + + case DICT_TF_FORMAT_ZIP: + /* In ROW_FORMAT=DYNAMIC and ROW_FORMAT=COMPRESSED, + column prefix indexes require that prefixes of + externally stored columns are written to the undo log. + This may make the undo log record bigger than the + record on the B-tree page. The maximum size of an + undo log record is the page size. That must be + checked for below. */ + break; + +#if DICT_TF_FORMAT_ZIP != DICT_TF_FORMAT_MAX +# error "DICT_TF_FORMAT_ZIP != DICT_TF_FORMAT_MAX" +#endif + } + + for (i = 0; i < n_ord; i++) { + const dict_field_t* field + = dict_index_get_nth_field(new_index, i); + const dict_col_t* col + = dict_field_get_col(field); + + /* In dtuple_convert_big_rec(), variable-length columns + that are longer than BTR_EXTERN_FIELD_REF_SIZE * 2 + may be chosen for external storage. If the column appears + in an ordering column of an index, a longer prefix of + REC_MAX_INDEX_COL_LEN will be copied to the undo log + by trx_undo_page_report_modify() and + trx_undo_page_fetch_ext(). It suffices to check the + capacity of the undo log whenever new_index includes + a column prefix on a column that may be stored externally. */ + + if (field->prefix_len /* prefix index */ + && !col->ord_part /* not yet ordering column */ + && !dict_col_get_fixed_size(col) /* variable-length */ + && dict_col_get_max_size(col) + > BTR_EXTERN_FIELD_REF_SIZE * 2 /* long enough */) { + + if (dict_index_too_big_for_undo(table, new_index)) { + /* An undo log record might not fit in + a single page. Refuse to create this index. */ + + goto too_big; + } + + break; + } + } + +undo_size_ok: + /* Flag the ordering columns */ + + for (i = 0; i < n_ord; i++) { + + dict_index_get_nth_field(new_index, i)->col->ord_part = 1; + } + + /* Add the new index as the last index for the table */ + + UT_LIST_ADD_LAST(indexes, table->indexes, new_index); + new_index->table = table; + new_index->table_name = table->name; + + new_index->search_info = btr_search_info_create(new_index->heap); + + new_index->stat_index_size = 1; + new_index->stat_n_leaf_pages = 1; + + new_index->page = page_no; + rw_lock_create(&new_index->lock, SYNC_INDEX_TREE); + + if (!UNIV_UNLIKELY(new_index->type & DICT_UNIVERSAL)) { + + new_index->stat_n_diff_key_vals = mem_heap_alloc( + new_index->heap, + (1 + dict_index_get_n_unique(new_index)) + * sizeof(ib_int64_t)); + /* Give some sensible values to stat_n_... in case we do + not calculate statistics quickly enough */ + + for (i = 0; i <= dict_index_get_n_unique(new_index); i++) { + + new_index->stat_n_diff_key_vals[i] = 100; + } + } + + dict_sys->size += mem_heap_get_size(new_index->heap); + + dict_mem_index_free(index); + + return(DB_SUCCESS); +} + +/************************************************************************** +Removes an index from the dictionary cache. */ +UNIV_INTERN +void +dict_index_remove_from_cache( +/*=========================*/ + dict_table_t* table, /* in/out: table */ + dict_index_t* index) /* in, own: index */ +{ + ulint size; + ulint retries = 0; + btr_search_t* info; + + ut_ad(table && index); + ut_ad(table->magic_n == DICT_TABLE_MAGIC_N); + ut_ad(index->magic_n == DICT_INDEX_MAGIC_N); + ut_ad(mutex_own(&(dict_sys->mutex))); + + /* We always create search info whether or not adaptive + hash index is enabled or not. */ + info = index->search_info; + ut_ad(info); + + /* We are not allowed to free the in-memory index struct + dict_index_t until all entries in the adaptive hash index + that point to any of the page belonging to his b-tree index + are dropped. This is so because dropping of these entries + require access to dict_index_t struct. To avoid such scenario + We keep a count of number of such pages in the search_info and + only free the dict_index_t struct when this count drops to + zero. */ + + for (;;) { + ulint ref_count = btr_search_info_get_ref_count(info); + if (ref_count == 0) { + break; + } + + /* Sleep for 10ms before trying again. */ + os_thread_sleep(10000); + ++retries; + + if (retries % 500 == 0) { + /* No luck after 5 seconds of wait. */ + fprintf(stderr, "InnoDB: Error: Waited for" + " %lu secs for hash index" + " ref_count (%lu) to drop" + " to 0.\n" + "index: \"%s\"" + " table: \"%s\"\n", + retries/100, + ref_count, + index->name, + table->name); + } + + /* To avoid a hang here we commit suicide if the + ref_count doesn't drop to zero in 600 seconds. */ + if (retries >= 60000) { + ut_error; + } + } + + rw_lock_free(&index->lock); + + /* Remove the index from the list of indexes of the table */ + UT_LIST_REMOVE(indexes, table->indexes, index); + + size = mem_heap_get_size(index->heap); + + ut_ad(dict_sys->size >= size); + + dict_sys->size -= size; + + dict_mem_index_free(index); +} + +/*********************************************************************** +Tries to find column names for the index and sets the col field of the +index. */ +static +void +dict_index_find_cols( +/*=================*/ + dict_table_t* table, /* in: table */ + dict_index_t* index) /* in: index */ +{ + ulint i; + + ut_ad(table && index); + ut_ad(table->magic_n == DICT_TABLE_MAGIC_N); + ut_ad(mutex_own(&(dict_sys->mutex))); + + for (i = 0; i < index->n_fields; i++) { + ulint j; + dict_field_t* field = dict_index_get_nth_field(index, i); + + for (j = 0; j < table->n_cols; j++) { + if (!strcmp(dict_table_get_col_name(table, j), + field->name)) { + field->col = dict_table_get_nth_col(table, j); + + goto found; + } + } + + /* It is an error not to find a matching column. */ + ut_error; + +found: + ; + } +} + +/*********************************************************************** +Adds a column to index. */ +UNIV_INTERN +void +dict_index_add_col( +/*===============*/ + dict_index_t* index, /* in/out: index */ + const dict_table_t* table, /* in: table */ + dict_col_t* col, /* in: column */ + ulint prefix_len) /* in: column prefix length */ +{ + dict_field_t* field; + const char* col_name; + + col_name = dict_table_get_col_name(table, dict_col_get_no(col)); + + dict_mem_index_add_field(index, col_name, prefix_len); + + field = dict_index_get_nth_field(index, index->n_def - 1); + + field->col = col; + field->fixed_len = (unsigned int) dict_col_get_fixed_size(col); + + if (prefix_len && field->fixed_len > prefix_len) { + field->fixed_len = (unsigned int) prefix_len; + } + + /* Long fixed-length fields that need external storage are treated as + variable-length fields, so that the extern flag can be embedded in + the length word. */ + + if (field->fixed_len > DICT_MAX_INDEX_COL_LEN) { + field->fixed_len = 0; + } +#if DICT_MAX_INDEX_COL_LEN != 768 + /* The comparison limit above must be constant. If it were + changed, the disk format of some fixed-length columns would + change, which would be a disaster. */ +# error "DICT_MAX_INDEX_COL_LEN != 768" +#endif + + if (!(col->prtype & DATA_NOT_NULL)) { + index->n_nullable++; + } +} + +/*********************************************************************** +Copies fields contained in index2 to index1. */ +static +void +dict_index_copy( +/*============*/ + dict_index_t* index1, /* in: index to copy to */ + dict_index_t* index2, /* in: index to copy from */ + const dict_table_t* table, /* in: table */ + ulint start, /* in: first position to copy */ + ulint end) /* in: last position to copy */ +{ + dict_field_t* field; + ulint i; + + /* Copy fields contained in index2 */ + + for (i = start; i < end; i++) { + + field = dict_index_get_nth_field(index2, i); + dict_index_add_col(index1, table, field->col, + field->prefix_len); + } +} + +/*********************************************************************** +Copies types of fields contained in index to tuple. */ +UNIV_INTERN +void +dict_index_copy_types( +/*==================*/ + dtuple_t* tuple, /* in/out: data tuple */ + const dict_index_t* index, /* in: index */ + ulint n_fields) /* in: number of + field types to copy */ +{ + ulint i; + + if (UNIV_UNLIKELY(index->type & DICT_UNIVERSAL)) { + dtuple_set_types_binary(tuple, n_fields); + + return; + } + + for (i = 0; i < n_fields; i++) { + const dict_field_t* ifield; + dtype_t* dfield_type; + + ifield = dict_index_get_nth_field(index, i); + dfield_type = dfield_get_type(dtuple_get_nth_field(tuple, i)); + dict_col_copy_type(dict_field_get_col(ifield), dfield_type); + } +} + +/*********************************************************************** +Copies types of columns contained in table to tuple and sets all +fields of the tuple to the SQL NULL value. This function should +be called right after dtuple_create(). */ +UNIV_INTERN +void +dict_table_copy_types( +/*==================*/ + dtuple_t* tuple, /* in/out: data tuple */ + const dict_table_t* table) /* in: table */ +{ + ulint i; + + for (i = 0; i < dtuple_get_n_fields(tuple); i++) { + + dfield_t* dfield = dtuple_get_nth_field(tuple, i); + dtype_t* dtype = dfield_get_type(dfield); + + dfield_set_null(dfield); + dict_col_copy_type(dict_table_get_nth_col(table, i), dtype); + } +} + +/*********************************************************************** +Builds the internal dictionary cache representation for a clustered +index, containing also system fields not defined by the user. */ +static +dict_index_t* +dict_index_build_internal_clust( +/*============================*/ + /* out, own: the internal + representation of the clustered + index */ + const dict_table_t* table, /* in: table */ + dict_index_t* index) /* in: user representation of + a clustered index */ +{ + dict_index_t* new_index; + dict_field_t* field; + ulint fixed_size; + ulint trx_id_pos; + ulint i; + ibool* indexed; + + ut_ad(table && index); + ut_ad(dict_index_is_clust(index)); + ut_ad(mutex_own(&(dict_sys->mutex))); + ut_ad(table->magic_n == DICT_TABLE_MAGIC_N); + + /* Create a new index object with certainly enough fields */ + new_index = dict_mem_index_create(table->name, + index->name, table->space, + index->type, + index->n_fields + table->n_cols); + + /* Copy other relevant data from the old index struct to the new + struct: it inherits the values */ + + new_index->n_user_defined_cols = index->n_fields; + + new_index->id = index->id; + + /* Copy the fields of index */ + dict_index_copy(new_index, index, table, 0, index->n_fields); + + if (UNIV_UNLIKELY(index->type & DICT_UNIVERSAL)) { + /* No fixed number of fields determines an entry uniquely */ + + new_index->n_uniq = REC_MAX_N_FIELDS; + + } else if (dict_index_is_unique(index)) { + /* Only the fields defined so far are needed to identify + the index entry uniquely */ + + new_index->n_uniq = new_index->n_def; + } else { + /* Also the row id is needed to identify the entry */ + new_index->n_uniq = 1 + new_index->n_def; + } + + new_index->trx_id_offset = 0; + + if (!dict_index_is_ibuf(index)) { + /* Add system columns, trx id first */ + + trx_id_pos = new_index->n_def; + +#if DATA_ROW_ID != 0 +# error "DATA_ROW_ID != 0" +#endif +#if DATA_TRX_ID != 1 +# error "DATA_TRX_ID != 1" +#endif +#if DATA_ROLL_PTR != 2 +# error "DATA_ROLL_PTR != 2" +#endif + + if (!dict_index_is_unique(index)) { + dict_index_add_col(new_index, table, + dict_table_get_sys_col( + table, DATA_ROW_ID), + 0); + trx_id_pos++; + } + + dict_index_add_col(new_index, table, + dict_table_get_sys_col(table, DATA_TRX_ID), + 0); + + dict_index_add_col(new_index, table, + dict_table_get_sys_col(table, + DATA_ROLL_PTR), + 0); + + for (i = 0; i < trx_id_pos; i++) { + + fixed_size = dict_col_get_fixed_size( + dict_index_get_nth_col(new_index, i)); + + if (fixed_size == 0) { + new_index->trx_id_offset = 0; + + break; + } + + if (dict_index_get_nth_field(new_index, i)->prefix_len + > 0) { + new_index->trx_id_offset = 0; + + break; + } + + new_index->trx_id_offset += (unsigned int) fixed_size; + } + + } + + /* Remember the table columns already contained in new_index */ + indexed = mem_zalloc(table->n_cols * sizeof *indexed); + + /* Mark the table columns already contained in new_index */ + for (i = 0; i < new_index->n_def; i++) { + + field = dict_index_get_nth_field(new_index, i); + + /* If there is only a prefix of the column in the index + field, do not mark the column as contained in the index */ + + if (field->prefix_len == 0) { + + indexed[field->col->ind] = TRUE; + } + } + + /* Add to new_index non-system columns of table not yet included + there */ + for (i = 0; i + DATA_N_SYS_COLS < (ulint) table->n_cols; i++) { + + dict_col_t* col = dict_table_get_nth_col(table, i); + ut_ad(col->mtype != DATA_SYS); + + if (!indexed[col->ind]) { + dict_index_add_col(new_index, table, col, 0); + } + } + + mem_free(indexed); + + ut_ad(dict_index_is_ibuf(index) + || (UT_LIST_GET_LEN(table->indexes) == 0)); + + new_index->cached = TRUE; + + return(new_index); +} + +/*********************************************************************** +Builds the internal dictionary cache representation for a non-clustered +index, containing also system fields not defined by the user. */ +static +dict_index_t* +dict_index_build_internal_non_clust( +/*================================*/ + /* out, own: the internal + representation of the non-clustered + index */ + const dict_table_t* table, /* in: table */ + dict_index_t* index) /* in: user representation of + a non-clustered index */ +{ + dict_field_t* field; + dict_index_t* new_index; + dict_index_t* clust_index; + ulint i; + ibool* indexed; + + ut_ad(table && index); + ut_ad(!dict_index_is_clust(index)); + ut_ad(mutex_own(&(dict_sys->mutex))); + ut_ad(table->magic_n == DICT_TABLE_MAGIC_N); + + /* The clustered index should be the first in the list of indexes */ + clust_index = UT_LIST_GET_FIRST(table->indexes); + + ut_ad(clust_index); + ut_ad(dict_index_is_clust(clust_index)); + ut_ad(!(clust_index->type & DICT_UNIVERSAL)); + + /* Create a new index */ + new_index = dict_mem_index_create( + table->name, index->name, index->space, index->type, + index->n_fields + 1 + clust_index->n_uniq); + + /* Copy other relevant data from the old index + struct to the new struct: it inherits the values */ + + new_index->n_user_defined_cols = index->n_fields; + + new_index->id = index->id; + + /* Copy fields from index to new_index */ + dict_index_copy(new_index, index, table, 0, index->n_fields); + + /* Remember the table columns already contained in new_index */ + indexed = mem_zalloc(table->n_cols * sizeof *indexed); + + /* Mark the table columns already contained in new_index */ + for (i = 0; i < new_index->n_def; i++) { + + field = dict_index_get_nth_field(new_index, i); + + /* If there is only a prefix of the column in the index + field, do not mark the column as contained in the index */ + + if (field->prefix_len == 0) { + + indexed[field->col->ind] = TRUE; + } + } + + /* Add to new_index the columns necessary to determine the clustered + index entry uniquely */ + + for (i = 0; i < clust_index->n_uniq; i++) { + + field = dict_index_get_nth_field(clust_index, i); + + if (!indexed[field->col->ind]) { + dict_index_add_col(new_index, table, field->col, + field->prefix_len); + } + } + + mem_free(indexed); + + if (dict_index_is_unique(index)) { + new_index->n_uniq = index->n_fields; + } else { + new_index->n_uniq = new_index->n_def; + } + + /* Set the n_fields value in new_index to the actual defined + number of fields */ + + new_index->n_fields = new_index->n_def; + + new_index->cached = TRUE; + + return(new_index); +} + +/*====================== FOREIGN KEY PROCESSING ========================*/ + +/************************************************************************* +Checks if a table is referenced by foreign keys. */ +UNIV_INTERN +ibool +dict_table_is_referenced_by_foreign_key( +/*====================================*/ + /* out: TRUE if table is referenced + by a foreign key */ + const dict_table_t* table) /* in: InnoDB table */ +{ + return(UT_LIST_GET_LEN(table->referenced_list) > 0); +} + +/************************************************************************* +Check if the index is referenced by a foreign key, if TRUE return foreign +else return NULL */ +UNIV_INTERN +dict_foreign_t* +dict_table_get_referenced_constraint( +/*=================================*/ + /* out: pointer to foreign key struct if index + is defined for foreign key, otherwise NULL */ + dict_table_t* table, /* in: InnoDB table */ + dict_index_t* index) /* in: InnoDB index */ +{ + dict_foreign_t* foreign; + + ut_ad(index != NULL); + ut_ad(table != NULL); + + for (foreign = UT_LIST_GET_FIRST(table->referenced_list); + foreign; + foreign = UT_LIST_GET_NEXT(referenced_list, foreign)) { + + if (foreign->referenced_index == index) { + + return(foreign); + } + } + + return(NULL); +} + +/************************************************************************* +Checks if a index is defined for a foreign key constraint. Index is a part +of a foreign key constraint if the index is referenced by foreign key +or index is a foreign key index. */ +UNIV_INTERN +dict_foreign_t* +dict_table_get_foreign_constraint( +/*==============================*/ + /* out: pointer to foreign key struct if index + is defined for foreign key, otherwise NULL */ + dict_table_t* table, /* in: InnoDB table */ + dict_index_t* index) /* in: InnoDB index */ +{ + dict_foreign_t* foreign; + + ut_ad(index != NULL); + ut_ad(table != NULL); + + for (foreign = UT_LIST_GET_FIRST(table->foreign_list); + foreign; + foreign = UT_LIST_GET_NEXT(foreign_list, foreign)) { + + if (foreign->foreign_index == index + || foreign->referenced_index == index) { + + return(foreign); + } + } + + return(NULL); +} + +/************************************************************************* +Frees a foreign key struct. */ +static +void +dict_foreign_free( +/*==============*/ + dict_foreign_t* foreign) /* in, own: foreign key struct */ +{ + mem_heap_free(foreign->heap); +} + +/************************************************************************** +Removes a foreign constraint struct from the dictionary cache. */ +static +void +dict_foreign_remove_from_cache( +/*===========================*/ + dict_foreign_t* foreign) /* in, own: foreign constraint */ +{ + ut_ad(mutex_own(&(dict_sys->mutex))); + ut_a(foreign); + + if (foreign->referenced_table) { + UT_LIST_REMOVE(referenced_list, + foreign->referenced_table->referenced_list, + foreign); + } + + if (foreign->foreign_table) { + UT_LIST_REMOVE(foreign_list, + foreign->foreign_table->foreign_list, + foreign); + } + + dict_foreign_free(foreign); +} + +#ifndef UNIV_HOTBACKUP +/************************************************************************** +Looks for the foreign constraint from the foreign and referenced lists +of a table. */ +static +dict_foreign_t* +dict_foreign_find( +/*==============*/ + /* out: foreign constraint */ + dict_table_t* table, /* in: table object */ + const char* id) /* in: foreign constraint id */ +{ + dict_foreign_t* foreign; + + ut_ad(mutex_own(&(dict_sys->mutex))); + + foreign = UT_LIST_GET_FIRST(table->foreign_list); + + while (foreign) { + if (ut_strcmp(id, foreign->id) == 0) { + + return(foreign); + } + + foreign = UT_LIST_GET_NEXT(foreign_list, foreign); + } + + foreign = UT_LIST_GET_FIRST(table->referenced_list); + + while (foreign) { + if (ut_strcmp(id, foreign->id) == 0) { + + return(foreign); + } + + foreign = UT_LIST_GET_NEXT(referenced_list, foreign); + } + + return(NULL); +} + +/************************************************************************* +Tries to find an index whose first fields are the columns in the array, +in the same order and is not marked for deletion and is not the same +as types_idx. */ +static +dict_index_t* +dict_foreign_find_index( +/*====================*/ + /* out: matching index, NULL if not found */ + dict_table_t* table, /* in: table */ + const char** columns,/* in: array of column names */ + ulint n_cols, /* in: number of columns */ + dict_index_t* types_idx, /* in: NULL or an index to whose types the + column types must match */ + ibool check_charsets, + /* in: whether to check charsets. + only has an effect if types_idx != NULL */ + ulint check_null) + /* in: nonzero if none of the columns must + be declared NOT NULL */ +{ + dict_index_t* index; + + index = dict_table_get_first_index(table); + + while (index != NULL) { + /* Ignore matches that refer to the same instance + or the index is to be dropped */ + if (index->to_be_dropped || types_idx == index) { + + goto next_rec; + + } else if (dict_index_get_n_fields(index) >= n_cols) { + ulint i; + + for (i = 0; i < n_cols; i++) { + dict_field_t* field; + const char* col_name; + + field = dict_index_get_nth_field(index, i); + + col_name = dict_table_get_col_name( + table, dict_col_get_no(field->col)); + + if (field->prefix_len != 0) { + /* We do not accept column prefix + indexes here */ + + break; + } + + if (0 != innobase_strcasecmp(columns[i], + col_name)) { + break; + } + + if (check_null + && (field->col->prtype & DATA_NOT_NULL)) { + + return(NULL); + } + + if (types_idx && !cmp_cols_are_equal( + dict_index_get_nth_col(index, i), + dict_index_get_nth_col(types_idx, + i), + check_charsets)) { + + break; + } + } + + if (i == n_cols) { + /* We found a matching index */ + + return(index); + } + } + +next_rec: + index = dict_table_get_next_index(index); + } + + return(NULL); +} + +/************************************************************************** +Find an index that is equivalent to the one passed in and is not marked +for deletion. */ +UNIV_INTERN +dict_index_t* +dict_foreign_find_equiv_index( +/*==========================*/ + /* out: index equivalent to + foreign->foreign_index, or NULL */ + dict_foreign_t* foreign)/* in: foreign key */ +{ + ut_a(foreign != NULL); + + /* Try to find an index which contains the columns as the + first fields and in the right order, and the types are the + same as in foreign->foreign_index */ + + return(dict_foreign_find_index( + foreign->foreign_table, + foreign->foreign_col_names, foreign->n_fields, + foreign->foreign_index, TRUE, /* check types */ + FALSE/* allow columns to be NULL */)); +} + +/************************************************************************** +Returns an index object by matching on the name and column names and +if more than one index matches return the index with the max id */ +UNIV_INTERN +dict_index_t* +dict_table_get_index_by_max_id( +/*===========================*/ + /* out: matching index, NULL if not found */ + dict_table_t* table, /* in: table */ + const char* name, /* in: the index name to find */ + const char** columns,/* in: array of column names */ + ulint n_cols) /* in: number of columns */ +{ + dict_index_t* index; + dict_index_t* found; + + found = NULL; + index = dict_table_get_first_index(table); + + while (index != NULL) { + if (ut_strcmp(index->name, name) == 0 + && dict_index_get_n_ordering_defined_by_user(index) + == n_cols) { + + ulint i; + + for (i = 0; i < n_cols; i++) { + dict_field_t* field; + const char* col_name; + + field = dict_index_get_nth_field(index, i); + + col_name = dict_table_get_col_name( + table, dict_col_get_no(field->col)); + + if (0 != innobase_strcasecmp( + columns[i], col_name)) { + + break; + } + } + + if (i == n_cols) { + /* We found a matching index, select + the index with the higher id*/ + + if (!found + || ut_dulint_cmp(index->id, found->id) > 0) { + + found = index; + } + } + } + + index = dict_table_get_next_index(index); + } + + return(found); +} + +/************************************************************************** +Report an error in a foreign key definition. */ +static +void +dict_foreign_error_report_low( +/*==========================*/ + FILE* file, /* in: output stream */ + const char* name) /* in: table name */ +{ + rewind(file); + ut_print_timestamp(file); + fprintf(file, " Error in foreign key constraint of table %s:\n", + name); +} + +/************************************************************************** +Report an error in a foreign key definition. */ +static +void +dict_foreign_error_report( +/*======================*/ + FILE* file, /* in: output stream */ + dict_foreign_t* fk, /* in: foreign key constraint */ + const char* msg) /* in: the error message */ +{ + mutex_enter(&dict_foreign_err_mutex); + dict_foreign_error_report_low(file, fk->foreign_table_name); + fputs(msg, file); + fputs(" Constraint:\n", file); + dict_print_info_on_foreign_key_in_create_format(file, NULL, fk, TRUE); + putc('\n', file); + if (fk->foreign_index) { + fputs("The index in the foreign key in table is ", file); + ut_print_name(file, NULL, FALSE, fk->foreign_index->name); + fputs("\n" + "See http://dev.mysql.com/doc/refman/5.1/en/" + "innodb-foreign-key-constraints.html\n" + "for correct foreign key definition.\n", + file); + } + mutex_exit(&dict_foreign_err_mutex); +} + +/************************************************************************** +Adds a foreign key constraint object to the dictionary cache. May free +the object if there already is an object with the same identifier in. +At least one of the foreign table and the referenced table must already +be in the dictionary cache! */ +UNIV_INTERN +ulint +dict_foreign_add_to_cache( +/*======================*/ + /* out: DB_SUCCESS or error code */ + dict_foreign_t* foreign, /* in, own: foreign key constraint */ + ibool check_charsets) /* in: TRUE=check charset + compatibility */ +{ + dict_table_t* for_table; + dict_table_t* ref_table; + dict_foreign_t* for_in_cache = NULL; + dict_index_t* index; + ibool added_to_referenced_list= FALSE; + FILE* ef = dict_foreign_err_file; + + ut_ad(mutex_own(&(dict_sys->mutex))); + + for_table = dict_table_check_if_in_cache_low( + foreign->foreign_table_name); + + ref_table = dict_table_check_if_in_cache_low( + foreign->referenced_table_name); + ut_a(for_table || ref_table); + + if (for_table) { + for_in_cache = dict_foreign_find(for_table, foreign->id); + } + + if (!for_in_cache && ref_table) { + for_in_cache = dict_foreign_find(ref_table, foreign->id); + } + + if (for_in_cache) { + /* Free the foreign object */ + mem_heap_free(foreign->heap); + } else { + for_in_cache = foreign; + } + + if (for_in_cache->referenced_table == NULL && ref_table) { + index = dict_foreign_find_index( + ref_table, + for_in_cache->referenced_col_names, + for_in_cache->n_fields, for_in_cache->foreign_index, + check_charsets, FALSE); + + if (index == NULL) { + dict_foreign_error_report( + ef, for_in_cache, + "there is no index in referenced table" + " which would contain\n" + "the columns as the first columns," + " or the data types in the\n" + "referenced table do not match" + " the ones in table."); + + if (for_in_cache == foreign) { + mem_heap_free(foreign->heap); + } + + return(DB_CANNOT_ADD_CONSTRAINT); + } + + for_in_cache->referenced_table = ref_table; + for_in_cache->referenced_index = index; + UT_LIST_ADD_LAST(referenced_list, + ref_table->referenced_list, + for_in_cache); + added_to_referenced_list = TRUE; + } + + if (for_in_cache->foreign_table == NULL && for_table) { + index = dict_foreign_find_index( + for_table, + for_in_cache->foreign_col_names, + for_in_cache->n_fields, + for_in_cache->referenced_index, check_charsets, + for_in_cache->type + & (DICT_FOREIGN_ON_DELETE_SET_NULL + | DICT_FOREIGN_ON_UPDATE_SET_NULL)); + + if (index == NULL) { + dict_foreign_error_report( + ef, for_in_cache, + "there is no index in the table" + " which would contain\n" + "the columns as the first columns," + " or the data types in the\n" + "table do not match" + " the ones in the referenced table\n" + "or one of the ON ... SET NULL columns" + " is declared NOT NULL."); + + if (for_in_cache == foreign) { + if (added_to_referenced_list) { + UT_LIST_REMOVE( + referenced_list, + ref_table->referenced_list, + for_in_cache); + } + + mem_heap_free(foreign->heap); + } + + return(DB_CANNOT_ADD_CONSTRAINT); + } + + for_in_cache->foreign_table = for_table; + for_in_cache->foreign_index = index; + UT_LIST_ADD_LAST(foreign_list, + for_table->foreign_list, + for_in_cache); + } + + return(DB_SUCCESS); +} + +/************************************************************************* +Scans from pointer onwards. Stops if is at the start of a copy of +'string' where characters are compared without case sensitivity, and +only outside `` or "" quotes. Stops also at '\0'. */ +static +const char* +dict_scan_to( +/*=========*/ + /* out: scanned up to this */ + const char* ptr, /* in: scan from */ + const char* string) /* in: look for this */ +{ + char quote = '\0'; + + for (; *ptr; ptr++) { + if (*ptr == quote) { + /* Closing quote character: do not look for + starting quote or the keyword. */ + quote = '\0'; + } else if (quote) { + /* Within quotes: do nothing. */ + } else if (*ptr == '`' || *ptr == '"') { + /* Starting quote: remember the quote character. */ + quote = *ptr; + } else { + /* Outside quotes: look for the keyword. */ + ulint i; + for (i = 0; string[i]; i++) { + if (toupper((int)(unsigned char)(ptr[i])) + != toupper((int)(unsigned char) + (string[i]))) { + goto nomatch; + } + } + break; +nomatch: + ; + } + } + + return(ptr); +} + +/************************************************************************* +Accepts a specified string. Comparisons are case-insensitive. */ +static +const char* +dict_accept( +/*========*/ + /* out: if string was accepted, the pointer + is moved after that, else ptr is returned */ + struct charset_info_st* cs,/* in: the character set of ptr */ + const char* ptr, /* in: scan from this */ + const char* string, /* in: accept only this string as the next + non-whitespace string */ + ibool* success)/* out: TRUE if accepted */ +{ + const char* old_ptr = ptr; + const char* old_ptr2; + + *success = FALSE; + + while (my_isspace(cs, *ptr)) { + ptr++; + } + + old_ptr2 = ptr; + + ptr = dict_scan_to(ptr, string); + + if (*ptr == '\0' || old_ptr2 != ptr) { + return(old_ptr); + } + + *success = TRUE; + + return(ptr + ut_strlen(string)); +} + +/************************************************************************* +Scans an id. For the lexical definition of an 'id', see the code below. +Strips backquotes or double quotes from around the id. */ +static +const char* +dict_scan_id( +/*=========*/ + /* out: scanned to */ + struct charset_info_st* cs,/* in: the character set of ptr */ + const char* ptr, /* in: scanned to */ + mem_heap_t* heap, /* in: heap where to allocate the id + (NULL=id will not be allocated, but it + will point to string near ptr) */ + const char** id, /* out,own: the id; NULL if no id was + scannable */ + ibool table_id,/* in: TRUE=convert the allocated id + as a table name; FALSE=convert to UTF-8 */ + ibool accept_also_dot) + /* in: TRUE if also a dot can appear in a + non-quoted id; in a quoted id it can appear + always */ +{ + char quote = '\0'; + ulint len = 0; + const char* s; + char* str; + char* dst; + + *id = NULL; + + while (my_isspace(cs, *ptr)) { + ptr++; + } + + if (*ptr == '\0') { + + return(ptr); + } + + if (*ptr == '`' || *ptr == '"') { + quote = *ptr++; + } + + s = ptr; + + if (quote) { + for (;;) { + if (!*ptr) { + /* Syntax error */ + return(ptr); + } + if (*ptr == quote) { + ptr++; + if (*ptr != quote) { + break; + } + } + ptr++; + len++; + } + } else { + while (!my_isspace(cs, *ptr) && *ptr != '(' && *ptr != ')' + && (accept_also_dot || *ptr != '.') + && *ptr != ',' && *ptr != '\0') { + + ptr++; + } + + len = ptr - s; + } + + if (UNIV_UNLIKELY(!heap)) { + /* no heap given: id will point to source string */ + *id = s; + return(ptr); + } + + if (quote) { + char* d; + str = d = mem_heap_alloc(heap, len + 1); + while (len--) { + if ((*d++ = *s++) == quote) { + s++; + } + } + *d++ = 0; + len = d - str; + ut_ad(*s == quote); + ut_ad(s + 1 == ptr); + } else { + str = mem_heap_strdupl(heap, s, len); + } + + if (!table_id) { +convert_id: + /* Convert the identifier from connection character set + to UTF-8. */ + len = 3 * len + 1; + *id = dst = mem_heap_alloc(heap, len); + + innobase_convert_from_id(cs, dst, str, len); + } else if (!strncmp(str, srv_mysql50_table_name_prefix, + sizeof srv_mysql50_table_name_prefix)) { + /* This is a pre-5.1 table name + containing chars other than [A-Za-z0-9]. + Discard the prefix and use raw UTF-8 encoding. */ + str += sizeof srv_mysql50_table_name_prefix; + len -= sizeof srv_mysql50_table_name_prefix; + goto convert_id; + } else { + /* Encode using filename-safe characters. */ + len = 5 * len + 1; + *id = dst = mem_heap_alloc(heap, len); + + innobase_convert_from_table_id(cs, dst, str, len); + } + + return(ptr); +} + +/************************************************************************* +Tries to scan a column name. */ +static +const char* +dict_scan_col( +/*==========*/ + /* out: scanned to */ + struct charset_info_st* cs, /* in: the character set of ptr */ + const char* ptr, /* in: scanned to */ + ibool* success,/* out: TRUE if success */ + dict_table_t* table, /* in: table in which the column is */ + const dict_col_t** column, /* out: pointer to column if success */ + mem_heap_t* heap, /* in: heap where to allocate */ + const char** name) /* out,own: the column name; + NULL if no name was scannable */ +{ + ulint i; + + *success = FALSE; + + ptr = dict_scan_id(cs, ptr, heap, name, FALSE, TRUE); + + if (*name == NULL) { + + return(ptr); /* Syntax error */ + } + + if (table == NULL) { + *success = TRUE; + *column = NULL; + } else { + for (i = 0; i < dict_table_get_n_cols(table); i++) { + + const char* col_name = dict_table_get_col_name( + table, i); + + if (0 == innobase_strcasecmp(col_name, *name)) { + /* Found */ + + *success = TRUE; + *column = dict_table_get_nth_col(table, i); + strcpy((char*) *name, col_name); + + break; + } + } + } + + return(ptr); +} + +/************************************************************************* +Scans a table name from an SQL string. */ +static +const char* +dict_scan_table_name( +/*=================*/ + /* out: scanned to */ + struct charset_info_st* cs,/* in: the character set of ptr */ + const char* ptr, /* in: scanned to */ + dict_table_t** table, /* out: table object or NULL */ + const char* name, /* in: foreign key table name */ + ibool* success,/* out: TRUE if ok name found */ + mem_heap_t* heap, /* in: heap where to allocate the id */ + const char** ref_name)/* out,own: the table name; + NULL if no name was scannable */ +{ + const char* database_name = NULL; + ulint database_name_len = 0; + const char* table_name = NULL; + ulint table_name_len; + const char* scan_name; + char* ref; + + *success = FALSE; + *table = NULL; + + ptr = dict_scan_id(cs, ptr, heap, &scan_name, TRUE, FALSE); + + if (scan_name == NULL) { + + return(ptr); /* Syntax error */ + } + + if (*ptr == '.') { + /* We scanned the database name; scan also the table name */ + + ptr++; + + database_name = scan_name; + database_name_len = strlen(database_name); + + ptr = dict_scan_id(cs, ptr, heap, &table_name, TRUE, FALSE); + + if (table_name == NULL) { + + return(ptr); /* Syntax error */ + } + } else { + /* To be able to read table dumps made with InnoDB-4.0.17 or + earlier, we must allow the dot separator between the database + name and the table name also to appear within a quoted + identifier! InnoDB used to print a constraint as: + ... REFERENCES `databasename.tablename` ... + starting from 4.0.18 it is + ... REFERENCES `databasename`.`tablename` ... */ + const char* s; + + for (s = scan_name; *s; s++) { + if (*s == '.') { + database_name = scan_name; + database_name_len = s - scan_name; + scan_name = ++s; + break;/* to do: multiple dots? */ + } + } + + table_name = scan_name; + } + + if (database_name == NULL) { + /* Use the database name of the foreign key table */ + + database_name = name; + database_name_len = dict_get_db_name_len(name); + } + + table_name_len = strlen(table_name); + + /* Copy database_name, '/', table_name, '\0' */ + ref = mem_heap_alloc(heap, database_name_len + table_name_len + 2); + memcpy(ref, database_name, database_name_len); + ref[database_name_len] = '/'; + memcpy(ref + database_name_len + 1, table_name, table_name_len + 1); +#ifndef __WIN__ + if (srv_lower_case_table_names) { +#endif /* !__WIN__ */ + /* The table name is always put to lower case on Windows. */ + innobase_casedn_str(ref); +#ifndef __WIN__ + } +#endif /* !__WIN__ */ + + *success = TRUE; + *ref_name = ref; + *table = dict_table_get_low(ref); + + return(ptr); +} + +/************************************************************************* +Skips one id. The id is allowed to contain also '.'. */ +static +const char* +dict_skip_word( +/*===========*/ + /* out: scanned to */ + struct charset_info_st* cs,/* in: the character set of ptr */ + const char* ptr, /* in: scanned to */ + ibool* success)/* out: TRUE if success, FALSE if just spaces + left in string or a syntax error */ +{ + const char* start; + + *success = FALSE; + + ptr = dict_scan_id(cs, ptr, NULL, &start, FALSE, TRUE); + + if (start) { + *success = TRUE; + } + + return(ptr); +} + +/************************************************************************* +Removes MySQL comments from an SQL string. A comment is either +(a) '#' to the end of the line, +(b) '--<space>' to the end of the line, or +(c) '<slash><asterisk>' till the next '<asterisk><slash>' (like the familiar +C comment syntax). */ +static +char* +dict_strip_comments( +/*================*/ + /* out, own: SQL string stripped from + comments; the caller must free this + with mem_free()! */ + const char* sql_string) /* in: SQL string */ +{ + char* str; + const char* sptr; + char* ptr; + /* unclosed quote character (0 if none) */ + char quote = 0; + + str = mem_alloc(strlen(sql_string) + 1); + + sptr = sql_string; + ptr = str; + + for (;;) { +scan_more: + if (*sptr == '\0') { + *ptr = '\0'; + + ut_a(ptr <= str + strlen(sql_string)); + + return(str); + } + + if (*sptr == quote) { + /* Closing quote character: do not look for + starting quote or comments. */ + quote = 0; + } else if (quote) { + /* Within quotes: do not look for + starting quotes or comments. */ + } else if (*sptr == '"' || *sptr == '`' || *sptr == '\'') { + /* Starting quote: remember the quote character. */ + quote = *sptr; + } else if (*sptr == '#' + || (sptr[0] == '-' && sptr[1] == '-' + && sptr[2] == ' ')) { + for (;;) { + /* In Unix a newline is 0x0A while in Windows + it is 0x0D followed by 0x0A */ + + if (*sptr == (char)0x0A + || *sptr == (char)0x0D + || *sptr == '\0') { + + goto scan_more; + } + + sptr++; + } + } else if (!quote && *sptr == '/' && *(sptr + 1) == '*') { + for (;;) { + if (*sptr == '*' && *(sptr + 1) == '/') { + + sptr += 2; + + goto scan_more; + } + + if (*sptr == '\0') { + + goto scan_more; + } + + sptr++; + } + } + + *ptr = *sptr; + + ptr++; + sptr++; + } +} + +/************************************************************************* +Finds the highest <number> for foreign key constraints of the table. Looks +only at the >= 4.0.18-format id's, which are of the form +databasename/tablename_ibfk_<number>. */ +static +ulint +dict_table_get_highest_foreign_id( +/*==============================*/ + /* out: highest number, 0 if table has no new + format foreign key constraints */ + dict_table_t* table) /* in: table in the dictionary memory cache */ +{ + dict_foreign_t* foreign; + char* endp; + ulint biggest_id = 0; + ulint id; + ulint len; + + ut_a(table); + + len = ut_strlen(table->name); + foreign = UT_LIST_GET_FIRST(table->foreign_list); + + while (foreign) { + if (ut_strlen(foreign->id) > ((sizeof dict_ibfk) - 1) + len + && 0 == ut_memcmp(foreign->id, table->name, len) + && 0 == ut_memcmp(foreign->id + len, + dict_ibfk, (sizeof dict_ibfk) - 1) + && foreign->id[len + ((sizeof dict_ibfk) - 1)] != '0') { + /* It is of the >= 4.0.18 format */ + + id = strtoul(foreign->id + len + + ((sizeof dict_ibfk) - 1), + &endp, 10); + if (*endp == '\0') { + ut_a(id != biggest_id); + + if (id > biggest_id) { + biggest_id = id; + } + } + } + + foreign = UT_LIST_GET_NEXT(foreign_list, foreign); + } + + return(biggest_id); +} + +/************************************************************************* +Reports a simple foreign key create clause syntax error. */ +static +void +dict_foreign_report_syntax_err( +/*===========================*/ + const char* name, /* in: table name */ + const char* start_of_latest_foreign, + /* in: start of the foreign key clause + in the SQL string */ + const char* ptr) /* in: place of the syntax error */ +{ + FILE* ef = dict_foreign_err_file; + + mutex_enter(&dict_foreign_err_mutex); + dict_foreign_error_report_low(ef, name); + fprintf(ef, "%s:\nSyntax error close to:\n%s\n", + start_of_latest_foreign, ptr); + mutex_exit(&dict_foreign_err_mutex); +} + +/************************************************************************* +Scans a table create SQL string and adds to the data dictionary the foreign +key constraints declared in the string. This function should be called after +the indexes for a table have been created. Each foreign key constraint must +be accompanied with indexes in both participating tables. The indexes are +allowed to contain more fields than mentioned in the constraint. */ +static +ulint +dict_create_foreign_constraints_low( +/*================================*/ + /* out: error code or DB_SUCCESS */ + trx_t* trx, /* in: transaction */ + mem_heap_t* heap, /* in: memory heap */ + struct charset_info_st* cs,/* in: the character set of sql_string */ + const char* sql_string, + /* in: CREATE TABLE or ALTER TABLE statement + where foreign keys are declared like: + FOREIGN KEY (a, b) REFERENCES table2(c, d), + table2 can be written also with the database + name before it: test.table2; the default + database is the database of parameter name */ + const char* name, /* in: table full name in the normalized form + database_name/table_name */ + ibool reject_fks) + /* in: if TRUE, fail with error code + DB_CANNOT_ADD_CONSTRAINT if any foreign + keys are found. */ +{ + dict_table_t* table; + dict_table_t* referenced_table; + dict_table_t* table_to_alter; + ulint highest_id_so_far = 0; + dict_index_t* index; + dict_foreign_t* foreign; + const char* ptr = sql_string; + const char* start_of_latest_foreign = sql_string; + FILE* ef = dict_foreign_err_file; + const char* constraint_name; + ibool success; + ulint error; + const char* ptr1; + const char* ptr2; + ulint i; + ulint j; + ibool is_on_delete; + ulint n_on_deletes; + ulint n_on_updates; + const dict_col_t*columns[500]; + const char* column_names[500]; + const char* referenced_table_name; + + ut_ad(mutex_own(&(dict_sys->mutex))); + + table = dict_table_get_low(name); + + if (table == NULL) { + mutex_enter(&dict_foreign_err_mutex); + dict_foreign_error_report_low(ef, name); + fprintf(ef, + "Cannot find the table in the internal" + " data dictionary of InnoDB.\n" + "Create table statement:\n%s\n", sql_string); + mutex_exit(&dict_foreign_err_mutex); + + return(DB_ERROR); + } + + /* First check if we are actually doing an ALTER TABLE, and in that + case look for the table being altered */ + + ptr = dict_accept(cs, ptr, "ALTER", &success); + + if (!success) { + + goto loop; + } + + ptr = dict_accept(cs, ptr, "TABLE", &success); + + if (!success) { + + goto loop; + } + + /* We are doing an ALTER TABLE: scan the table name we are altering */ + + ptr = dict_scan_table_name(cs, ptr, &table_to_alter, name, + &success, heap, &referenced_table_name); + if (!success) { + fprintf(stderr, + "InnoDB: Error: could not find" + " the table being ALTERED in:\n%s\n", + sql_string); + + return(DB_ERROR); + } + + /* Starting from 4.0.18 and 4.1.2, we generate foreign key id's in the + format databasename/tablename_ibfk_<number>, where <number> is local + to the table; look for the highest <number> for table_to_alter, so + that we can assign to new constraints higher numbers. */ + + /* If we are altering a temporary table, the table name after ALTER + TABLE does not correspond to the internal table name, and + table_to_alter is NULL. TODO: should we fix this somehow? */ + + if (table_to_alter == NULL) { + highest_id_so_far = 0; + } else { + highest_id_so_far = dict_table_get_highest_foreign_id( + table_to_alter); + } + + /* Scan for foreign key declarations in a loop */ +loop: + /* Scan either to "CONSTRAINT" or "FOREIGN", whichever is closer */ + + ptr1 = dict_scan_to(ptr, "CONSTRAINT"); + ptr2 = dict_scan_to(ptr, "FOREIGN"); + + constraint_name = NULL; + + if (ptr1 < ptr2) { + /* The user may have specified a constraint name. Pick it so + that we can store 'databasename/constraintname' as the id of + of the constraint to system tables. */ + ptr = ptr1; + + ptr = dict_accept(cs, ptr, "CONSTRAINT", &success); + + ut_a(success); + + if (!my_isspace(cs, *ptr) && *ptr != '"' && *ptr != '`') { + goto loop; + } + + while (my_isspace(cs, *ptr)) { + ptr++; + } + + /* read constraint name unless got "CONSTRAINT FOREIGN" */ + if (ptr != ptr2) { + ptr = dict_scan_id(cs, ptr, heap, + &constraint_name, FALSE, FALSE); + } + } else { + ptr = ptr2; + } + + if (*ptr == '\0') { + /* The proper way to reject foreign keys for temporary + tables would be to split the lexing and syntactical + analysis of foreign key clauses from the actual adding + of them, so that ha_innodb.cc could first parse the SQL + command, determine if there are any foreign keys, and + if so, immediately reject the command if the table is a + temporary one. For now, this kludge will work. */ + if (reject_fks && (UT_LIST_GET_LEN(table->foreign_list) > 0)) { + + return(DB_CANNOT_ADD_CONSTRAINT); + } + + /**********************************************************/ + /* The following call adds the foreign key constraints + to the data dictionary system tables on disk */ + + error = dict_create_add_foreigns_to_dictionary( + highest_id_so_far, table, trx); + return(error); + } + + start_of_latest_foreign = ptr; + + ptr = dict_accept(cs, ptr, "FOREIGN", &success); + + if (!success) { + goto loop; + } + + if (!my_isspace(cs, *ptr)) { + goto loop; + } + + ptr = dict_accept(cs, ptr, "KEY", &success); + + if (!success) { + goto loop; + } + + ptr = dict_accept(cs, ptr, "(", &success); + + if (!success) { + /* MySQL allows also an index id before the '('; we + skip it */ + ptr = dict_skip_word(cs, ptr, &success); + + if (!success) { + dict_foreign_report_syntax_err( + name, start_of_latest_foreign, ptr); + + return(DB_CANNOT_ADD_CONSTRAINT); + } + + ptr = dict_accept(cs, ptr, "(", &success); + + if (!success) { + /* We do not flag a syntax error here because in an + ALTER TABLE we may also have DROP FOREIGN KEY abc */ + + goto loop; + } + } + + i = 0; + + /* Scan the columns in the first list */ +col_loop1: + ut_a(i < (sizeof column_names) / sizeof *column_names); + ptr = dict_scan_col(cs, ptr, &success, table, columns + i, + heap, column_names + i); + if (!success) { + mutex_enter(&dict_foreign_err_mutex); + dict_foreign_error_report_low(ef, name); + fprintf(ef, "%s:\nCannot resolve column name close to:\n%s\n", + start_of_latest_foreign, ptr); + mutex_exit(&dict_foreign_err_mutex); + + return(DB_CANNOT_ADD_CONSTRAINT); + } + + i++; + + ptr = dict_accept(cs, ptr, ",", &success); + + if (success) { + goto col_loop1; + } + + ptr = dict_accept(cs, ptr, ")", &success); + + if (!success) { + dict_foreign_report_syntax_err( + name, start_of_latest_foreign, ptr); + return(DB_CANNOT_ADD_CONSTRAINT); + } + + /* Try to find an index which contains the columns + as the first fields and in the right order */ + + index = dict_foreign_find_index(table, column_names, i, + NULL, TRUE, FALSE); + + if (!index) { + mutex_enter(&dict_foreign_err_mutex); + dict_foreign_error_report_low(ef, name); + fputs("There is no index in table ", ef); + ut_print_name(ef, NULL, TRUE, name); + fprintf(ef, " where the columns appear\n" + "as the first columns. Constraint:\n%s\n" + "See http://dev.mysql.com/doc/refman/5.1/en/" + "innodb-foreign-key-constraints.html\n" + "for correct foreign key definition.\n", + start_of_latest_foreign); + mutex_exit(&dict_foreign_err_mutex); + + return(DB_CANNOT_ADD_CONSTRAINT); + } + ptr = dict_accept(cs, ptr, "REFERENCES", &success); + + if (!success || !my_isspace(cs, *ptr)) { + dict_foreign_report_syntax_err( + name, start_of_latest_foreign, ptr); + return(DB_CANNOT_ADD_CONSTRAINT); + } + + /* Let us create a constraint struct */ + + foreign = dict_mem_foreign_create(); + + if (constraint_name) { + ulint db_len; + + /* Catenate 'databasename/' to the constraint name specified + by the user: we conceive the constraint as belonging to the + same MySQL 'database' as the table itself. We store the name + to foreign->id. */ + + db_len = dict_get_db_name_len(table->name); + + foreign->id = mem_heap_alloc( + foreign->heap, db_len + strlen(constraint_name) + 2); + + ut_memcpy(foreign->id, table->name, db_len); + foreign->id[db_len] = '/'; + strcpy(foreign->id + db_len + 1, constraint_name); + } + + foreign->foreign_table = table; + foreign->foreign_table_name = mem_heap_strdup(foreign->heap, + table->name); + foreign->foreign_index = index; + foreign->n_fields = (unsigned int) i; + foreign->foreign_col_names = mem_heap_alloc(foreign->heap, + i * sizeof(void*)); + for (i = 0; i < foreign->n_fields; i++) { + foreign->foreign_col_names[i] = mem_heap_strdup( + foreign->heap, + dict_table_get_col_name(table, + dict_col_get_no(columns[i]))); + } + + ptr = dict_scan_table_name(cs, ptr, &referenced_table, name, + &success, heap, &referenced_table_name); + + /* Note that referenced_table can be NULL if the user has suppressed + checking of foreign key constraints! */ + + if (!success || (!referenced_table && trx->check_foreigns)) { + dict_foreign_free(foreign); + + mutex_enter(&dict_foreign_err_mutex); + dict_foreign_error_report_low(ef, name); + fprintf(ef, "%s:\nCannot resolve table name close to:\n" + "%s\n", + start_of_latest_foreign, ptr); + mutex_exit(&dict_foreign_err_mutex); + + return(DB_CANNOT_ADD_CONSTRAINT); + } + + ptr = dict_accept(cs, ptr, "(", &success); + + if (!success) { + dict_foreign_free(foreign); + dict_foreign_report_syntax_err(name, start_of_latest_foreign, + ptr); + return(DB_CANNOT_ADD_CONSTRAINT); + } + + /* Scan the columns in the second list */ + i = 0; + +col_loop2: + ptr = dict_scan_col(cs, ptr, &success, referenced_table, columns + i, + heap, column_names + i); + i++; + + if (!success) { + dict_foreign_free(foreign); + + mutex_enter(&dict_foreign_err_mutex); + dict_foreign_error_report_low(ef, name); + fprintf(ef, "%s:\nCannot resolve column name close to:\n" + "%s\n", + start_of_latest_foreign, ptr); + mutex_exit(&dict_foreign_err_mutex); + + return(DB_CANNOT_ADD_CONSTRAINT); + } + + ptr = dict_accept(cs, ptr, ",", &success); + + if (success) { + goto col_loop2; + } + + ptr = dict_accept(cs, ptr, ")", &success); + + if (!success || foreign->n_fields != i) { + dict_foreign_free(foreign); + + dict_foreign_report_syntax_err(name, start_of_latest_foreign, + ptr); + return(DB_CANNOT_ADD_CONSTRAINT); + } + + n_on_deletes = 0; + n_on_updates = 0; + +scan_on_conditions: + /* Loop here as long as we can find ON ... conditions */ + + ptr = dict_accept(cs, ptr, "ON", &success); + + if (!success) { + + goto try_find_index; + } + + ptr = dict_accept(cs, ptr, "DELETE", &success); + + if (!success) { + ptr = dict_accept(cs, ptr, "UPDATE", &success); + + if (!success) { + dict_foreign_free(foreign); + + dict_foreign_report_syntax_err( + name, start_of_latest_foreign, ptr); + return(DB_CANNOT_ADD_CONSTRAINT); + } + + is_on_delete = FALSE; + n_on_updates++; + } else { + is_on_delete = TRUE; + n_on_deletes++; + } + + ptr = dict_accept(cs, ptr, "RESTRICT", &success); + + if (success) { + goto scan_on_conditions; + } + + ptr = dict_accept(cs, ptr, "CASCADE", &success); + + if (success) { + if (is_on_delete) { + foreign->type |= DICT_FOREIGN_ON_DELETE_CASCADE; + } else { + foreign->type |= DICT_FOREIGN_ON_UPDATE_CASCADE; + } + + goto scan_on_conditions; + } + + ptr = dict_accept(cs, ptr, "NO", &success); + + if (success) { + ptr = dict_accept(cs, ptr, "ACTION", &success); + + if (!success) { + dict_foreign_free(foreign); + dict_foreign_report_syntax_err( + name, start_of_latest_foreign, ptr); + + return(DB_CANNOT_ADD_CONSTRAINT); + } + + if (is_on_delete) { + foreign->type |= DICT_FOREIGN_ON_DELETE_NO_ACTION; + } else { + foreign->type |= DICT_FOREIGN_ON_UPDATE_NO_ACTION; + } + + goto scan_on_conditions; + } + + ptr = dict_accept(cs, ptr, "SET", &success); + + if (!success) { + dict_foreign_free(foreign); + dict_foreign_report_syntax_err(name, start_of_latest_foreign, + ptr); + return(DB_CANNOT_ADD_CONSTRAINT); + } + + ptr = dict_accept(cs, ptr, "NULL", &success); + + if (!success) { + dict_foreign_free(foreign); + dict_foreign_report_syntax_err(name, start_of_latest_foreign, + ptr); + return(DB_CANNOT_ADD_CONSTRAINT); + } + + for (j = 0; j < foreign->n_fields; j++) { + if ((dict_index_get_nth_col(foreign->foreign_index, j)->prtype) + & DATA_NOT_NULL) { + + /* It is not sensible to define SET NULL + if the column is not allowed to be NULL! */ + + dict_foreign_free(foreign); + + mutex_enter(&dict_foreign_err_mutex); + dict_foreign_error_report_low(ef, name); + fprintf(ef, "%s:\n" + "You have defined a SET NULL condition" + " though some of the\n" + "columns are defined as NOT NULL.\n", + start_of_latest_foreign); + mutex_exit(&dict_foreign_err_mutex); + + return(DB_CANNOT_ADD_CONSTRAINT); + } + } + + if (is_on_delete) { + foreign->type |= DICT_FOREIGN_ON_DELETE_SET_NULL; + } else { + foreign->type |= DICT_FOREIGN_ON_UPDATE_SET_NULL; + } + + goto scan_on_conditions; + +try_find_index: + if (n_on_deletes > 1 || n_on_updates > 1) { + /* It is an error to define more than 1 action */ + + dict_foreign_free(foreign); + + mutex_enter(&dict_foreign_err_mutex); + dict_foreign_error_report_low(ef, name); + fprintf(ef, "%s:\n" + "You have twice an ON DELETE clause" + " or twice an ON UPDATE clause.\n", + start_of_latest_foreign); + mutex_exit(&dict_foreign_err_mutex); + + return(DB_CANNOT_ADD_CONSTRAINT); + } + + /* Try to find an index which contains the columns as the first fields + and in the right order, and the types are the same as in + foreign->foreign_index */ + + if (referenced_table) { + index = dict_foreign_find_index(referenced_table, + column_names, i, + foreign->foreign_index, + TRUE, FALSE); + if (!index) { + dict_foreign_free(foreign); + mutex_enter(&dict_foreign_err_mutex); + dict_foreign_error_report_low(ef, name); + fprintf(ef, "%s:\n" + "Cannot find an index in the" + " referenced table where the\n" + "referenced columns appear as the" + " first columns, or column types\n" + "in the table and the referenced table" + " do not match for constraint.\n" + "Note that the internal storage type of" + " ENUM and SET changed in\n" + "tables created with >= InnoDB-4.1.12," + " and such columns in old tables\n" + "cannot be referenced by such columns" + " in new tables.\n" + "See http://dev.mysql.com/doc/refman/5.1/en/" + "innodb-foreign-key-constraints.html\n" + "for correct foreign key definition.\n", + start_of_latest_foreign); + mutex_exit(&dict_foreign_err_mutex); + + return(DB_CANNOT_ADD_CONSTRAINT); + } + } else { + ut_a(trx->check_foreigns == FALSE); + index = NULL; + } + + foreign->referenced_index = index; + foreign->referenced_table = referenced_table; + + foreign->referenced_table_name + = mem_heap_strdup(foreign->heap, referenced_table_name); + + foreign->referenced_col_names = mem_heap_alloc(foreign->heap, + i * sizeof(void*)); + for (i = 0; i < foreign->n_fields; i++) { + foreign->referenced_col_names[i] + = mem_heap_strdup(foreign->heap, column_names[i]); + } + + /* We found an ok constraint definition: add to the lists */ + + UT_LIST_ADD_LAST(foreign_list, table->foreign_list, foreign); + + if (referenced_table) { + UT_LIST_ADD_LAST(referenced_list, + referenced_table->referenced_list, + foreign); + } + + goto loop; +} + +/************************************************************************* +Scans a table create SQL string and adds to the data dictionary the foreign +key constraints declared in the string. This function should be called after +the indexes for a table have been created. Each foreign key constraint must +be accompanied with indexes in both participating tables. The indexes are +allowed to contain more fields than mentioned in the constraint. */ +UNIV_INTERN +ulint +dict_create_foreign_constraints( +/*============================*/ + /* out: error code or DB_SUCCESS */ + trx_t* trx, /* in: transaction */ + const char* sql_string, /* in: table create statement where + foreign keys are declared like: + FOREIGN KEY (a, b) REFERENCES + table2(c, d), table2 can be written + also with the database + name before it: test.table2; the + default database id the database of + parameter name */ + const char* name, /* in: table full name in the + normalized form + database_name/table_name */ + ibool reject_fks) /* in: if TRUE, fail with error + code DB_CANNOT_ADD_CONSTRAINT if + any foreign keys are found. */ +{ + char* str; + ulint err; + mem_heap_t* heap; + + ut_a(trx); + ut_a(trx->mysql_thd); + + str = dict_strip_comments(sql_string); + heap = mem_heap_create(10000); + + err = dict_create_foreign_constraints_low( + trx, heap, innobase_get_charset(trx->mysql_thd), str, name, + reject_fks); + + mem_heap_free(heap); + mem_free(str); + + return(err); +} + +/************************************************************************** +Parses the CONSTRAINT id's to be dropped in an ALTER TABLE statement. */ +UNIV_INTERN +ulint +dict_foreign_parse_drop_constraints( +/*================================*/ + /* out: DB_SUCCESS or + DB_CANNOT_DROP_CONSTRAINT if + syntax error or the constraint + id does not match */ + mem_heap_t* heap, /* in: heap from which we can + allocate memory */ + trx_t* trx, /* in: transaction */ + dict_table_t* table, /* in: table */ + ulint* n, /* out: number of constraints + to drop */ + const char*** constraints_to_drop) /* out: id's of the + constraints to drop */ +{ + dict_foreign_t* foreign; + ibool success; + char* str; + const char* ptr; + const char* id; + FILE* ef = dict_foreign_err_file; + struct charset_info_st* cs; + + ut_a(trx); + ut_a(trx->mysql_thd); + + cs = innobase_get_charset(trx->mysql_thd); + + *n = 0; + + *constraints_to_drop = mem_heap_alloc(heap, 1000 * sizeof(char*)); + + str = dict_strip_comments(*(trx->mysql_query_str)); + ptr = str; + + ut_ad(mutex_own(&(dict_sys->mutex))); +loop: + ptr = dict_scan_to(ptr, "DROP"); + + if (*ptr == '\0') { + mem_free(str); + + return(DB_SUCCESS); + } + + ptr = dict_accept(cs, ptr, "DROP", &success); + + if (!my_isspace(cs, *ptr)) { + + goto loop; + } + + ptr = dict_accept(cs, ptr, "FOREIGN", &success); + + if (!success || !my_isspace(cs, *ptr)) { + + goto loop; + } + + ptr = dict_accept(cs, ptr, "KEY", &success); + + if (!success) { + + goto syntax_error; + } + + ptr = dict_scan_id(cs, ptr, heap, &id, FALSE, TRUE); + + if (id == NULL) { + + goto syntax_error; + } + + ut_a(*n < 1000); + (*constraints_to_drop)[*n] = id; + (*n)++; + + /* Look for the given constraint id */ + + foreign = UT_LIST_GET_FIRST(table->foreign_list); + + while (foreign != NULL) { + if (0 == strcmp(foreign->id, id) + || (strchr(foreign->id, '/') + && 0 == strcmp(id, + dict_remove_db_name(foreign->id)))) { + /* Found */ + break; + } + + foreign = UT_LIST_GET_NEXT(foreign_list, foreign); + } + + if (foreign == NULL) { + mutex_enter(&dict_foreign_err_mutex); + rewind(ef); + ut_print_timestamp(ef); + fputs(" Error in dropping of a foreign key constraint" + " of table ", ef); + ut_print_name(ef, NULL, TRUE, table->name); + fputs(",\n" + "in SQL command\n", ef); + fputs(str, ef); + fputs("\nCannot find a constraint with the given id ", ef); + ut_print_name(ef, NULL, FALSE, id); + fputs(".\n", ef); + mutex_exit(&dict_foreign_err_mutex); + + mem_free(str); + + return(DB_CANNOT_DROP_CONSTRAINT); + } + + goto loop; + +syntax_error: + mutex_enter(&dict_foreign_err_mutex); + rewind(ef); + ut_print_timestamp(ef); + fputs(" Syntax error in dropping of a" + " foreign key constraint of table ", ef); + ut_print_name(ef, NULL, TRUE, table->name); + fprintf(ef, ",\n" + "close to:\n%s\n in SQL command\n%s\n", ptr, str); + mutex_exit(&dict_foreign_err_mutex); + + mem_free(str); + + return(DB_CANNOT_DROP_CONSTRAINT); +} +#endif /* UNIV_HOTBACKUP */ + +/*==================== END OF FOREIGN KEY PROCESSING ====================*/ + +/************************************************************************** +Returns an index object if it is found in the dictionary cache. +Assumes that dict_sys->mutex is already being held. */ +UNIV_INTERN +dict_index_t* +dict_index_get_if_in_cache_low( +/*===========================*/ + /* out: index, NULL if not found */ + dulint index_id) /* in: index id */ +{ + ut_ad(mutex_own(&(dict_sys->mutex))); + + return(dict_index_find_on_id_low(index_id)); +} + +#if defined UNIV_DEBUG || defined UNIV_BUF_DEBUG +/************************************************************************** +Returns an index object if it is found in the dictionary cache. */ +UNIV_INTERN +dict_index_t* +dict_index_get_if_in_cache( +/*=======================*/ + /* out: index, NULL if not found */ + dulint index_id) /* in: index id */ +{ + dict_index_t* index; + + if (dict_sys == NULL) { + return(NULL); + } + + mutex_enter(&(dict_sys->mutex)); + + index = dict_index_get_if_in_cache_low(index_id); + + mutex_exit(&(dict_sys->mutex)); + + return(index); +} +#endif /* UNIV_DEBUG || UNIV_BUF_DEBUG */ + +#ifdef UNIV_DEBUG +/************************************************************************** +Checks that a tuple has n_fields_cmp value in a sensible range, so that +no comparison can occur with the page number field in a node pointer. */ +UNIV_INTERN +ibool +dict_index_check_search_tuple( +/*==========================*/ + /* out: TRUE if ok */ + const dict_index_t* index, /* in: index tree */ + const dtuple_t* tuple) /* in: tuple used in a search */ +{ + ut_a(index); + ut_a(dtuple_get_n_fields_cmp(tuple) + <= dict_index_get_n_unique_in_tree(index)); + return(TRUE); +} +#endif /* UNIV_DEBUG */ + +/************************************************************************** +Builds a node pointer out of a physical record and a page number. */ +UNIV_INTERN +dtuple_t* +dict_index_build_node_ptr( +/*======================*/ + /* out, own: node pointer */ + const dict_index_t* index, /* in: index */ + const rec_t* rec, /* in: record for which to build node + pointer */ + ulint page_no,/* in: page number to put in node + pointer */ + mem_heap_t* heap, /* in: memory heap where pointer + created */ + ulint level) /* in: level of rec in tree: + 0 means leaf level */ +{ + dtuple_t* tuple; + dfield_t* field; + byte* buf; + ulint n_unique; + + if (UNIV_UNLIKELY(index->type & DICT_UNIVERSAL)) { + /* In a universal index tree, we take the whole record as + the node pointer if the record is on the leaf level, + on non-leaf levels we remove the last field, which + contains the page number of the child page */ + + ut_a(!dict_table_is_comp(index->table)); + n_unique = rec_get_n_fields_old(rec); + + if (level > 0) { + ut_a(n_unique > 1); + n_unique--; + } + } else { + n_unique = dict_index_get_n_unique_in_tree(index); + } + + tuple = dtuple_create(heap, n_unique + 1); + + /* When searching in the tree for the node pointer, we must not do + comparison on the last field, the page number field, as on upper + levels in the tree there may be identical node pointers with a + different page number; therefore, we set the n_fields_cmp to one + less: */ + + dtuple_set_n_fields_cmp(tuple, n_unique); + + dict_index_copy_types(tuple, index, n_unique); + + buf = mem_heap_alloc(heap, 4); + + mach_write_to_4(buf, page_no); + + field = dtuple_get_nth_field(tuple, n_unique); + dfield_set_data(field, buf, 4); + + dtype_set(dfield_get_type(field), DATA_SYS_CHILD, DATA_NOT_NULL, 4); + + rec_copy_prefix_to_dtuple(tuple, rec, index, n_unique, heap); + dtuple_set_info_bits(tuple, dtuple_get_info_bits(tuple) + | REC_STATUS_NODE_PTR); + + ut_ad(dtuple_check_typed(tuple)); + + return(tuple); +} + +/************************************************************************** +Copies an initial segment of a physical record, long enough to specify an +index entry uniquely. */ +UNIV_INTERN +rec_t* +dict_index_copy_rec_order_prefix( +/*=============================*/ + /* out: pointer to the prefix record */ + const dict_index_t* index, /* in: index */ + const rec_t* rec, /* in: record for which to + copy prefix */ + ulint* n_fields,/* out: number of fields copied */ + byte** buf, /* in/out: memory buffer for the + copied prefix, or NULL */ + ulint* buf_size)/* in/out: buffer size */ +{ + ulint n; + + UNIV_PREFETCH_R(rec); + + if (UNIV_UNLIKELY(index->type & DICT_UNIVERSAL)) { + ut_a(!dict_table_is_comp(index->table)); + n = rec_get_n_fields_old(rec); + } else { + n = dict_index_get_n_unique_in_tree(index); + } + + *n_fields = n; + return(rec_copy_prefix_to_buf(rec, index, n, buf, buf_size)); +} + +/************************************************************************** +Builds a typed data tuple out of a physical record. */ +UNIV_INTERN +dtuple_t* +dict_index_build_data_tuple( +/*========================*/ + /* out, own: data tuple */ + dict_index_t* index, /* in: index tree */ + rec_t* rec, /* in: record for which to build data tuple */ + ulint n_fields,/* in: number of data fields */ + mem_heap_t* heap) /* in: memory heap where tuple created */ +{ + dtuple_t* tuple; + + ut_ad(dict_table_is_comp(index->table) + || n_fields <= rec_get_n_fields_old(rec)); + + tuple = dtuple_create(heap, n_fields); + + dict_index_copy_types(tuple, index, n_fields); + + rec_copy_prefix_to_dtuple(tuple, rec, index, n_fields, heap); + + ut_ad(dtuple_check_typed(tuple)); + + return(tuple); +} + +/************************************************************************* +Calculates the minimum record length in an index. */ +UNIV_INTERN +ulint +dict_index_calc_min_rec_len( +/*========================*/ + const dict_index_t* index) /* in: index */ +{ + ulint sum = 0; + ulint i; + + if (dict_table_is_comp(index->table)) { + ulint nullable = 0; + sum = REC_N_NEW_EXTRA_BYTES; + for (i = 0; i < dict_index_get_n_fields(index); i++) { + const dict_col_t* col + = dict_index_get_nth_col(index, i); + ulint size = dict_col_get_fixed_size(col); + sum += size; + if (!size) { + size = col->len; + sum += size < 128 ? 1 : 2; + } + if (!(col->prtype & DATA_NOT_NULL)) { + nullable++; + } + } + + /* round the NULL flags up to full bytes */ + sum += UT_BITS_IN_BYTES(nullable); + + return(sum); + } + + for (i = 0; i < dict_index_get_n_fields(index); i++) { + sum += dict_col_get_fixed_size( + dict_index_get_nth_col(index, i)); + } + + if (sum > 127) { + sum += 2 * dict_index_get_n_fields(index); + } else { + sum += dict_index_get_n_fields(index); + } + + sum += REC_N_OLD_EXTRA_BYTES; + + return(sum); +} + +/************************************************************************* +Calculates new estimates for table and index statistics. The statistics +are used in query optimization. */ +UNIV_INTERN +void +dict_update_statistics_low( +/*=======================*/ + dict_table_t* table, /* in/out: table */ + ibool has_dict_mutex __attribute__((unused))) + /* in: TRUE if the caller has the + dictionary mutex */ +{ + dict_index_t* index; + ulint size; + ulint sum_of_index_sizes = 0; + + if (table->ibd_file_missing) { + ut_print_timestamp(stderr); + fprintf(stderr, + " InnoDB: cannot calculate statistics for table %s\n" + "InnoDB: because the .ibd file is missing. For help," + " please refer to\n" + "InnoDB: http://dev.mysql.com/doc/refman/5.1/en/" + "innodb-troubleshooting.html\n", + table->name); + + return; + } + + /* If we have set a high innodb_force_recovery level, do not calculate + statistics, as a badly corrupted index can cause a crash in it. */ + + if (srv_force_recovery >= SRV_FORCE_NO_IBUF_MERGE) { + + return; + } + + /* Find out the sizes of the indexes and how many different values + for the key they approximately have */ + + index = dict_table_get_first_index(table); + + if (index == NULL) { + /* Table definition is corrupt */ + + return; + } + + while (index) { + size = btr_get_size(index, BTR_TOTAL_SIZE); + + index->stat_index_size = size; + + sum_of_index_sizes += size; + + size = btr_get_size(index, BTR_N_LEAF_PAGES); + + if (size == 0) { + /* The root node of the tree is a leaf */ + size = 1; + } + + index->stat_n_leaf_pages = size; + + btr_estimate_number_of_different_key_vals(index); + + index = dict_table_get_next_index(index); + } + + index = dict_table_get_first_index(table); + + table->stat_n_rows = index->stat_n_diff_key_vals[ + dict_index_get_n_unique(index)]; + + table->stat_clustered_index_size = index->stat_index_size; + + table->stat_sum_of_other_index_sizes = sum_of_index_sizes + - index->stat_index_size; + + table->stat_initialized = TRUE; + + table->stat_modified_counter = 0; +} + +/************************************************************************* +Calculates new estimates for table and index statistics. The statistics +are used in query optimization. */ +UNIV_INTERN +void +dict_update_statistics( +/*===================*/ + dict_table_t* table) /* in/out: table */ +{ + dict_update_statistics_low(table, FALSE); +} + +/************************************************************************** +Prints info of a foreign key constraint. */ +static +void +dict_foreign_print_low( +/*===================*/ + dict_foreign_t* foreign) /* in: foreign key constraint */ +{ + ulint i; + + ut_ad(mutex_own(&(dict_sys->mutex))); + + fprintf(stderr, " FOREIGN KEY CONSTRAINT %s: %s (", + foreign->id, foreign->foreign_table_name); + + for (i = 0; i < foreign->n_fields; i++) { + fprintf(stderr, " %s", foreign->foreign_col_names[i]); + } + + fprintf(stderr, " )\n" + " REFERENCES %s (", + foreign->referenced_table_name); + + for (i = 0; i < foreign->n_fields; i++) { + fprintf(stderr, " %s", foreign->referenced_col_names[i]); + } + + fputs(" )\n", stderr); +} + +/************************************************************************** +Prints a table data. */ +UNIV_INTERN +void +dict_table_print( +/*=============*/ + dict_table_t* table) /* in: table */ +{ + mutex_enter(&(dict_sys->mutex)); + dict_table_print_low(table); + mutex_exit(&(dict_sys->mutex)); +} + +/************************************************************************** +Prints a table data when we know the table name. */ +UNIV_INTERN +void +dict_table_print_by_name( +/*=====================*/ + const char* name) +{ + dict_table_t* table; + + mutex_enter(&(dict_sys->mutex)); + + table = dict_table_get_low(name); + + ut_a(table); + + dict_table_print_low(table); + mutex_exit(&(dict_sys->mutex)); +} + +/************************************************************************** +Prints a table data. */ +UNIV_INTERN +void +dict_table_print_low( +/*=================*/ + dict_table_t* table) /* in: table */ +{ + dict_index_t* index; + dict_foreign_t* foreign; + ulint i; + + ut_ad(mutex_own(&(dict_sys->mutex))); + + if (srv_stats_auto_update) + dict_update_statistics_low(table, TRUE); + + fprintf(stderr, + "--------------------------------------\n" + "TABLE: name %s, id %lu %lu, flags %lx, columns %lu," + " indexes %lu, appr.rows %lu\n" + " COLUMNS: ", + table->name, + (ulong) ut_dulint_get_high(table->id), + (ulong) ut_dulint_get_low(table->id), + (ulong) table->flags, + (ulong) table->n_cols, + (ulong) UT_LIST_GET_LEN(table->indexes), + (ulong) table->stat_n_rows); + + for (i = 0; i + 1 < (ulint) table->n_cols; i++) { + dict_col_print_low(table, dict_table_get_nth_col(table, i)); + fputs("; ", stderr); + } + + putc('\n', stderr); + + index = UT_LIST_GET_FIRST(table->indexes); + + while (index != NULL) { + dict_index_print_low(index); + index = UT_LIST_GET_NEXT(indexes, index); + } + + foreign = UT_LIST_GET_FIRST(table->foreign_list); + + while (foreign != NULL) { + dict_foreign_print_low(foreign); + foreign = UT_LIST_GET_NEXT(foreign_list, foreign); + } + + foreign = UT_LIST_GET_FIRST(table->referenced_list); + + while (foreign != NULL) { + dict_foreign_print_low(foreign); + foreign = UT_LIST_GET_NEXT(referenced_list, foreign); + } +} + +/************************************************************************** +Prints a column data. */ +static +void +dict_col_print_low( +/*===============*/ + const dict_table_t* table, /* in: table */ + const dict_col_t* col) /* in: column */ +{ + dtype_t type; + + ut_ad(mutex_own(&(dict_sys->mutex))); + + dict_col_copy_type(col, &type); + fprintf(stderr, "%s: ", dict_table_get_col_name(table, + dict_col_get_no(col))); + + dtype_print(&type); +} + +/************************************************************************** +Prints an index data. */ +static +void +dict_index_print_low( +/*=================*/ + dict_index_t* index) /* in: index */ +{ + ib_int64_t n_vals; + ulint i; + const char* type_string; + + ut_ad(mutex_own(&(dict_sys->mutex))); + + if (index->n_user_defined_cols > 0) { + n_vals = index->stat_n_diff_key_vals[ + index->n_user_defined_cols]; + } else { + n_vals = index->stat_n_diff_key_vals[1]; + } + + if (dict_index_is_clust(index)) { + type_string = "clustered index"; + } else if (dict_index_is_unique(index)) { + type_string = "unique index"; + } else { + type_string = "secondary index"; + } + + fprintf(stderr, + " INDEX: name %s, id %lu %lu, fields %lu/%lu," + " uniq %lu, type %lu\n" + " root page %lu, appr.key vals %lu," + " leaf pages %lu, size pages %lu\n" + " FIELDS: ", + index->name, + (ulong) ut_dulint_get_high(index->id), + (ulong) ut_dulint_get_low(index->id), + (ulong) index->n_user_defined_cols, + (ulong) index->n_fields, + (ulong) index->n_uniq, + (ulong) index->type, + (ulong) index->page, + (ulong) n_vals, + (ulong) index->stat_n_leaf_pages, + (ulong) index->stat_index_size); + + for (i = 0; i < index->n_fields; i++) { + dict_field_print_low(dict_index_get_nth_field(index, i)); + } + + putc('\n', stderr); + +#ifdef UNIV_BTR_PRINT + btr_print_size(index); + + btr_print_index(index, 7); +#endif /* UNIV_BTR_PRINT */ +} + +/************************************************************************** +Prints a field data. */ +static +void +dict_field_print_low( +/*=================*/ + dict_field_t* field) /* in: field */ +{ + ut_ad(mutex_own(&(dict_sys->mutex))); + + fprintf(stderr, " %s", field->name); + + if (field->prefix_len != 0) { + fprintf(stderr, "(%lu)", (ulong) field->prefix_len); + } +} + +/************************************************************************** +Outputs info on a foreign key of a table in a format suitable for +CREATE TABLE. */ +UNIV_INTERN +void +dict_print_info_on_foreign_key_in_create_format( +/*============================================*/ + FILE* file, /* in: file where to print */ + trx_t* trx, /* in: transaction */ + dict_foreign_t* foreign, /* in: foreign key constraint */ + ibool add_newline) /* in: whether to add a newline */ +{ + const char* stripped_id; + ulint i; + + if (strchr(foreign->id, '/')) { + /* Strip the preceding database name from the constraint id */ + stripped_id = foreign->id + 1 + + dict_get_db_name_len(foreign->id); + } else { + stripped_id = foreign->id; + } + + putc(',', file); + + if (add_newline) { + /* SHOW CREATE TABLE wants constraints each printed nicely + on its own line, while error messages want no newlines + inserted. */ + fputs("\n ", file); + } + + fputs(" CONSTRAINT ", file); + ut_print_name(file, trx, FALSE, stripped_id); + fputs(" FOREIGN KEY (", file); + + for (i = 0;;) { + ut_print_name(file, trx, FALSE, foreign->foreign_col_names[i]); + if (++i < foreign->n_fields) { + fputs(", ", file); + } else { + break; + } + } + + fputs(") REFERENCES ", file); + + if (dict_tables_have_same_db(foreign->foreign_table_name, + foreign->referenced_table_name)) { + /* Do not print the database name of the referenced table */ + ut_print_name(file, trx, TRUE, + dict_remove_db_name( + foreign->referenced_table_name)); + } else { + ut_print_name(file, trx, TRUE, + foreign->referenced_table_name); + } + + putc(' ', file); + putc('(', file); + + for (i = 0;;) { + ut_print_name(file, trx, FALSE, + foreign->referenced_col_names[i]); + if (++i < foreign->n_fields) { + fputs(", ", file); + } else { + break; + } + } + + putc(')', file); + + if (foreign->type & DICT_FOREIGN_ON_DELETE_CASCADE) { + fputs(" ON DELETE CASCADE", file); + } + + if (foreign->type & DICT_FOREIGN_ON_DELETE_SET_NULL) { + fputs(" ON DELETE SET NULL", file); + } + + if (foreign->type & DICT_FOREIGN_ON_DELETE_NO_ACTION) { + fputs(" ON DELETE NO ACTION", file); + } + + if (foreign->type & DICT_FOREIGN_ON_UPDATE_CASCADE) { + fputs(" ON UPDATE CASCADE", file); + } + + if (foreign->type & DICT_FOREIGN_ON_UPDATE_SET_NULL) { + fputs(" ON UPDATE SET NULL", file); + } + + if (foreign->type & DICT_FOREIGN_ON_UPDATE_NO_ACTION) { + fputs(" ON UPDATE NO ACTION", file); + } +} + +/************************************************************************** +Outputs info on foreign keys of a table. */ +UNIV_INTERN +void +dict_print_info_on_foreign_keys( +/*============================*/ + ibool create_table_format, /* in: if TRUE then print in + a format suitable to be inserted into + a CREATE TABLE, otherwise in the format + of SHOW TABLE STATUS */ + FILE* file, /* in: file where to print */ + trx_t* trx, /* in: transaction */ + dict_table_t* table) /* in: table */ +{ + dict_foreign_t* foreign; + + mutex_enter(&(dict_sys->mutex)); + + foreign = UT_LIST_GET_FIRST(table->foreign_list); + + if (foreign == NULL) { + mutex_exit(&(dict_sys->mutex)); + + return; + } + + while (foreign != NULL) { + if (create_table_format) { + dict_print_info_on_foreign_key_in_create_format( + file, trx, foreign, TRUE); + } else { + ulint i; + fputs("; (", file); + + for (i = 0; i < foreign->n_fields; i++) { + if (i) { + putc(' ', file); + } + + ut_print_name(file, trx, FALSE, + foreign->foreign_col_names[i]); + } + + fputs(") REFER ", file); + ut_print_name(file, trx, TRUE, + foreign->referenced_table_name); + putc('(', file); + + for (i = 0; i < foreign->n_fields; i++) { + if (i) { + putc(' ', file); + } + ut_print_name( + file, trx, FALSE, + foreign->referenced_col_names[i]); + } + + putc(')', file); + + if (foreign->type == DICT_FOREIGN_ON_DELETE_CASCADE) { + fputs(" ON DELETE CASCADE", file); + } + + if (foreign->type == DICT_FOREIGN_ON_DELETE_SET_NULL) { + fputs(" ON DELETE SET NULL", file); + } + + if (foreign->type & DICT_FOREIGN_ON_DELETE_NO_ACTION) { + fputs(" ON DELETE NO ACTION", file); + } + + if (foreign->type & DICT_FOREIGN_ON_UPDATE_CASCADE) { + fputs(" ON UPDATE CASCADE", file); + } + + if (foreign->type & DICT_FOREIGN_ON_UPDATE_SET_NULL) { + fputs(" ON UPDATE SET NULL", file); + } + + if (foreign->type & DICT_FOREIGN_ON_UPDATE_NO_ACTION) { + fputs(" ON UPDATE NO ACTION", file); + } + } + + foreign = UT_LIST_GET_NEXT(foreign_list, foreign); + } + + mutex_exit(&(dict_sys->mutex)); +} + +/************************************************************************ +Displays the names of the index and the table. */ +UNIV_INTERN +void +dict_index_name_print( +/*==================*/ + FILE* file, /* in: output stream */ + trx_t* trx, /* in: transaction */ + const dict_index_t* index) /* in: index to print */ +{ + fputs("index ", file); + ut_print_name(file, trx, FALSE, index->name); + fputs(" of table ", file); + ut_print_name(file, trx, TRUE, index->table_name); +} + +/************************************************************************** +Get index by name */ +UNIV_INTERN +dict_index_t* +dict_table_get_index_on_name( +/*=========================*/ + /* out: index, NULL if does not exist */ + dict_table_t* table, /* in: table */ + const char* name) /* in: name of the index to find */ +{ + dict_index_t* index; + + index = dict_table_get_first_index(table); + + while (index != NULL) { + if (ut_strcmp(index->name, name) == 0) { + + return(index); + } + + index = dict_table_get_next_index(index); + } + + return(NULL); + +} + +/************************************************************************** +Replace the index passed in with another equivalent index in the tables +foreign key list. */ +UNIV_INTERN +void +dict_table_replace_index_in_foreign_list( +/*=====================================*/ + dict_table_t* table, /* in/out: table */ + dict_index_t* index) /* in: index to be replaced */ +{ + dict_foreign_t* foreign; + + for (foreign = UT_LIST_GET_FIRST(table->foreign_list); + foreign; + foreign = UT_LIST_GET_NEXT(foreign_list, foreign)) { + + if (foreign->foreign_index == index) { + dict_index_t* new_index + = dict_foreign_find_equiv_index(foreign); + ut_a(new_index); + + foreign->foreign_index = new_index; + } + } +} + +/************************************************************************** +In case there is more than one index with the same name return the index +with the min(id). */ +UNIV_INTERN +dict_index_t* +dict_table_get_index_on_name_and_min_id( +/*=====================================*/ + /* out: index, NULL if does not exist */ + dict_table_t* table, /* in: table */ + const char* name) /* in: name of the index to find */ +{ + dict_index_t* index; + dict_index_t* min_index; /* Index with matching name and min(id) */ + + min_index = NULL; + index = dict_table_get_first_index(table); + + while (index != NULL) { + if (ut_strcmp(index->name, name) == 0) { + if (!min_index + || ut_dulint_cmp(index->id, min_index->id) < 0) { + + min_index = index; + } + } + + index = dict_table_get_next_index(index); + } + + return(min_index); + +} + +#ifdef UNIV_DEBUG +/************************************************************************** +Check for duplicate index entries in a table [using the index name] */ +UNIV_INTERN +void +dict_table_check_for_dup_indexes( +/*=============================*/ + const dict_table_t* table) /* in: Check for dup indexes + in this table */ +{ + /* Check for duplicates, ignoring indexes that are marked + as to be dropped */ + + const dict_index_t* index1; + const dict_index_t* index2; + + /* The primary index _must_ exist */ + ut_a(UT_LIST_GET_LEN(table->indexes) > 0); + + index1 = UT_LIST_GET_FIRST(table->indexes); + index2 = UT_LIST_GET_NEXT(indexes, index1); + + while (index1 && index2) { + + while (index2) { + + if (!index2->to_be_dropped) { + ut_ad(ut_strcmp(index1->name, index2->name)); + } + + index2 = UT_LIST_GET_NEXT(indexes, index2); + } + + index1 = UT_LIST_GET_NEXT(indexes, index1); + index2 = UT_LIST_GET_NEXT(indexes, index1); + } +} +#endif /* UNIV_DEBUG */ diff --git a/storage/xtradb/dict/dict0load.c b/storage/xtradb/dict/dict0load.c new file mode 100644 index 00000000000..0e3f3c04c1f --- /dev/null +++ b/storage/xtradb/dict/dict0load.c @@ -0,0 +1,1460 @@ +/***************************************************************************** + +Copyright (c) 1996, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/****************************************************** +Loads to the memory cache database object definitions +from dictionary tables + +Created 4/24/1996 Heikki Tuuri +*******************************************************/ + +#include "dict0load.h" +#ifndef UNIV_HOTBACKUP +#include "mysql_version.h" +#endif /* !UNIV_HOTBACKUP */ + +#ifdef UNIV_NONINL +#include "dict0load.ic" +#endif + +#include "btr0pcur.h" +#include "btr0btr.h" +#include "page0page.h" +#include "mach0data.h" +#include "dict0dict.h" +#include "dict0boot.h" +#include "rem0cmp.h" +#include "srv0start.h" +#include "srv0srv.h" + +/******************************************************************** +Returns TRUE if index's i'th column's name is 'name' .*/ +static +ibool +name_of_col_is( +/*===========*/ + /* out: */ + dict_table_t* table, /* in: table */ + dict_index_t* index, /* in: index */ + ulint i, /* in: */ + const char* name) /* in: name to compare to */ +{ + ulint tmp = dict_col_get_no(dict_field_get_col( + dict_index_get_nth_field( + index, i))); + + return(strcmp(name, dict_table_get_col_name(table, tmp)) == 0); +} + +/************************************************************************ +Finds the first table name in the given database. */ +UNIV_INTERN +char* +dict_get_first_table_name_in_db( +/*============================*/ + /* out, own: table name, NULL if + does not exist; the caller must + free the memory in the string! */ + const char* name) /* in: database name which ends in '/' */ +{ + dict_table_t* sys_tables; + btr_pcur_t pcur; + dict_index_t* sys_index; + dtuple_t* tuple; + mem_heap_t* heap; + dfield_t* dfield; + const rec_t* rec; + const byte* field; + ulint len; + mtr_t mtr; + + ut_ad(mutex_own(&(dict_sys->mutex))); + + heap = mem_heap_create(1000); + + mtr_start(&mtr); + + sys_tables = dict_table_get_low("SYS_TABLES"); + sys_index = UT_LIST_GET_FIRST(sys_tables->indexes); + ut_a(!dict_table_is_comp(sys_tables)); + + tuple = dtuple_create(heap, 1); + dfield = dtuple_get_nth_field(tuple, 0); + + dfield_set_data(dfield, name, ut_strlen(name)); + dict_index_copy_types(tuple, sys_index, 1); + + btr_pcur_open_on_user_rec(sys_index, tuple, PAGE_CUR_GE, + BTR_SEARCH_LEAF, &pcur, &mtr); +loop: + rec = btr_pcur_get_rec(&pcur); + + if (!btr_pcur_is_on_user_rec(&pcur)) { + /* Not found */ + + btr_pcur_close(&pcur); + mtr_commit(&mtr); + mem_heap_free(heap); + + return(NULL); + } + + field = rec_get_nth_field_old(rec, 0, &len); + + if (len < strlen(name) + || ut_memcmp(name, field, strlen(name)) != 0) { + /* Not found */ + + btr_pcur_close(&pcur); + mtr_commit(&mtr); + mem_heap_free(heap); + + return(NULL); + } + + if (!rec_get_deleted_flag(rec, 0)) { + + /* We found one */ + + char* table_name = mem_strdupl((char*) field, len); + + btr_pcur_close(&pcur); + mtr_commit(&mtr); + mem_heap_free(heap); + + return(table_name); + } + + btr_pcur_move_to_next_user_rec(&pcur, &mtr); + + goto loop; +} + +/************************************************************************ +Prints to the standard output information on all tables found in the data +dictionary system table. */ +UNIV_INTERN +void +dict_print(void) +/*============*/ +{ + dict_table_t* sys_tables; + dict_index_t* sys_index; + dict_table_t* table; + btr_pcur_t pcur; + const rec_t* rec; + const byte* field; + ulint len; + mtr_t mtr; + + /* Enlarge the fatal semaphore wait timeout during the InnoDB table + monitor printout */ + + mutex_enter(&kernel_mutex); + srv_fatal_semaphore_wait_threshold += 7200; /* 2 hours */ + mutex_exit(&kernel_mutex); + + mutex_enter(&(dict_sys->mutex)); + + mtr_start(&mtr); + + sys_tables = dict_table_get_low("SYS_TABLES"); + sys_index = UT_LIST_GET_FIRST(sys_tables->indexes); + + btr_pcur_open_at_index_side(TRUE, sys_index, BTR_SEARCH_LEAF, &pcur, + TRUE, &mtr); +loop: + btr_pcur_move_to_next_user_rec(&pcur, &mtr); + + rec = btr_pcur_get_rec(&pcur); + + if (!btr_pcur_is_on_user_rec(&pcur)) { + /* end of index */ + + btr_pcur_close(&pcur); + mtr_commit(&mtr); + + mutex_exit(&(dict_sys->mutex)); + + /* Restore the fatal semaphore wait timeout */ + + mutex_enter(&kernel_mutex); + srv_fatal_semaphore_wait_threshold -= 7200; /* 2 hours */ + mutex_exit(&kernel_mutex); + + return; + } + + field = rec_get_nth_field_old(rec, 0, &len); + + if (!rec_get_deleted_flag(rec, 0)) { + + /* We found one */ + + char* table_name = mem_strdupl((char*) field, len); + + btr_pcur_store_position(&pcur, &mtr); + + mtr_commit(&mtr); + + table = dict_table_get_low(table_name); + mem_free(table_name); + + if (table == NULL) { + fputs("InnoDB: Failed to load table ", stderr); + ut_print_namel(stderr, NULL, TRUE, (char*) field, len); + putc('\n', stderr); + } else { + /* The table definition was corrupt if there + is no index */ + + if (srv_stats_auto_update && dict_table_get_first_index(table)) { + dict_update_statistics_low(table, TRUE); + } + + dict_table_print_low(table); + } + + mtr_start(&mtr); + + btr_pcur_restore_position(BTR_SEARCH_LEAF, &pcur, &mtr); + } + + goto loop; +} + +/************************************************************************ +Determine the flags of a table described in SYS_TABLES. */ +static +ulint +dict_sys_tables_get_flags( +/*======================*/ + /* out: compressed page size in kilobytes; + or 0 if the tablespace is uncompressed, + ULINT_UNDEFINED on error */ + const rec_t* rec) /* in: a record of SYS_TABLES */ +{ + const byte* field; + ulint len; + ulint n_cols; + ulint flags; + + field = rec_get_nth_field_old(rec, 5, &len); + ut_a(len == 4); + + flags = mach_read_from_4(field); + + if (UNIV_LIKELY(flags == DICT_TABLE_ORDINARY)) { + return(0); + } + + field = rec_get_nth_field_old(rec, 4, &len); + n_cols = mach_read_from_4(field); + + if (UNIV_UNLIKELY(!(n_cols & 0x80000000UL))) { + /* New file formats require ROW_FORMAT=COMPACT. */ + return(ULINT_UNDEFINED); + } + + switch (flags & (DICT_TF_FORMAT_MASK | DICT_TF_COMPACT)) { + default: + case DICT_TF_FORMAT_51 << DICT_TF_FORMAT_SHIFT: + case DICT_TF_FORMAT_51 << DICT_TF_FORMAT_SHIFT | DICT_TF_COMPACT: + /* flags should be DICT_TABLE_ORDINARY, + or DICT_TF_FORMAT_MASK should be nonzero. */ + return(ULINT_UNDEFINED); + + case DICT_TF_FORMAT_ZIP << DICT_TF_FORMAT_SHIFT | DICT_TF_COMPACT: +#if DICT_TF_FORMAT_MAX > DICT_TF_FORMAT_ZIP +# error "missing case labels for DICT_TF_FORMAT_ZIP .. DICT_TF_FORMAT_MAX" +#endif + /* We support this format. */ + break; + } + + if (UNIV_UNLIKELY((flags & DICT_TF_ZSSIZE_MASK) + > (DICT_TF_ZSSIZE_MAX << DICT_TF_ZSSIZE_SHIFT))) { + /* Unsupported compressed page size. */ + return(ULINT_UNDEFINED); + } + + if (UNIV_UNLIKELY(flags & (~0 << DICT_TF_BITS))) { + /* Some unused bits are set. */ + return(ULINT_UNDEFINED); + } + + return(flags); +} + +/************************************************************************ +In a crash recovery we already have all the tablespace objects created. +This function compares the space id information in the InnoDB data dictionary +to what we already read with fil_load_single_table_tablespaces(). + +In a normal startup, we create the tablespace objects for every table in +InnoDB's data dictionary, if the corresponding .ibd file exists. +We also scan the biggest space id, and store it to fil_system. */ +UNIV_INTERN +void +dict_check_tablespaces_and_store_max_id( +/*====================================*/ + ibool in_crash_recovery) /* in: are we doing a crash recovery */ +{ + dict_table_t* sys_tables; + dict_index_t* sys_index; + btr_pcur_t pcur; + const rec_t* rec; + ulint max_space_id = 0; + mtr_t mtr; + + mutex_enter(&(dict_sys->mutex)); + + mtr_start(&mtr); + + sys_tables = dict_table_get_low("SYS_TABLES"); + sys_index = UT_LIST_GET_FIRST(sys_tables->indexes); + ut_a(!dict_table_is_comp(sys_tables)); + + btr_pcur_open_at_index_side(TRUE, sys_index, BTR_SEARCH_LEAF, &pcur, + TRUE, &mtr); +loop: + btr_pcur_move_to_next_user_rec(&pcur, &mtr); + + rec = btr_pcur_get_rec(&pcur); + + if (!btr_pcur_is_on_user_rec(&pcur)) { + /* end of index */ + + btr_pcur_close(&pcur); + mtr_commit(&mtr); + + /* We must make the tablespace cache aware of the biggest + known space id */ + + /* printf("Biggest space id in data dictionary %lu\n", + max_space_id); */ + fil_set_max_space_id_if_bigger(max_space_id); + + mutex_exit(&(dict_sys->mutex)); + + return; + } + + if (!rec_get_deleted_flag(rec, 0)) { + + /* We found one */ + const byte* field; + ulint len; + ulint space_id; + ulint flags; + char* name; + + field = rec_get_nth_field_old(rec, 0, &len); + name = mem_strdupl((char*) field, len); + + flags = dict_sys_tables_get_flags(rec); + if (UNIV_UNLIKELY(flags == ULINT_UNDEFINED)) { + + field = rec_get_nth_field_old(rec, 5, &len); + flags = mach_read_from_4(field); + + ut_print_timestamp(stderr); + fputs(" InnoDB: Error: table ", stderr); + ut_print_filename(stderr, name); + fprintf(stderr, "\n" + "InnoDB: in InnoDB data dictionary" + " has unknown type %lx.\n", + (ulong) flags); + + goto loop; + } + + field = rec_get_nth_field_old(rec, 9, &len); + ut_a(len == 4); + + space_id = mach_read_from_4(field); + + btr_pcur_store_position(&pcur, &mtr); + + mtr_commit(&mtr); + + if (space_id != 0 && in_crash_recovery) { + /* Check that the tablespace (the .ibd file) really + exists; print a warning to the .err log if not */ + + fil_space_for_table_exists_in_mem(space_id, name, + FALSE, TRUE, TRUE); + } + + if (space_id != 0 && !in_crash_recovery) { + /* It is a normal database startup: create the space + object and check that the .ibd file exists. */ + + fil_open_single_table_tablespace(FALSE, space_id, + flags, name); + } + + mem_free(name); + + if (space_id > max_space_id) { + max_space_id = space_id; + } + + mtr_start(&mtr); + + btr_pcur_restore_position(BTR_SEARCH_LEAF, &pcur, &mtr); + } + + goto loop; +} + +/************************************************************************ +Loads definitions for table columns. */ +static +void +dict_load_columns( +/*==============*/ + dict_table_t* table, /* in: table */ + mem_heap_t* heap) /* in: memory heap for temporary storage */ +{ + dict_table_t* sys_columns; + dict_index_t* sys_index; + btr_pcur_t pcur; + dtuple_t* tuple; + dfield_t* dfield; + const rec_t* rec; + const byte* field; + ulint len; + byte* buf; + char* name; + ulint mtype; + ulint prtype; + ulint col_len; + ulint i; + mtr_t mtr; + + ut_ad(mutex_own(&(dict_sys->mutex))); + + mtr_start(&mtr); + + sys_columns = dict_table_get_low("SYS_COLUMNS"); + sys_index = UT_LIST_GET_FIRST(sys_columns->indexes); + ut_a(!dict_table_is_comp(sys_columns)); + + tuple = dtuple_create(heap, 1); + dfield = dtuple_get_nth_field(tuple, 0); + + buf = mem_heap_alloc(heap, 8); + mach_write_to_8(buf, table->id); + + dfield_set_data(dfield, buf, 8); + dict_index_copy_types(tuple, sys_index, 1); + + btr_pcur_open_on_user_rec(sys_index, tuple, PAGE_CUR_GE, + BTR_SEARCH_LEAF, &pcur, &mtr); + for (i = 0; i + DATA_N_SYS_COLS < (ulint) table->n_cols; i++) { + + rec = btr_pcur_get_rec(&pcur); + + ut_a(btr_pcur_is_on_user_rec(&pcur)); + + ut_a(!rec_get_deleted_flag(rec, 0)); + + field = rec_get_nth_field_old(rec, 0, &len); + ut_ad(len == 8); + ut_a(ut_dulint_cmp(table->id, mach_read_from_8(field)) == 0); + + field = rec_get_nth_field_old(rec, 1, &len); + ut_ad(len == 4); + ut_a(i == mach_read_from_4(field)); + + ut_a(name_of_col_is(sys_columns, sys_index, 4, "NAME")); + + field = rec_get_nth_field_old(rec, 4, &len); + name = mem_heap_strdupl(heap, (char*) field, len); + + field = rec_get_nth_field_old(rec, 5, &len); + mtype = mach_read_from_4(field); + + field = rec_get_nth_field_old(rec, 6, &len); + prtype = mach_read_from_4(field); + + if (dtype_get_charset_coll(prtype) == 0 + && dtype_is_string_type(mtype)) { + /* The table was created with < 4.1.2. */ + + if (dtype_is_binary_string_type(mtype, prtype)) { + /* Use the binary collation for + string columns of binary type. */ + + prtype = dtype_form_prtype( + prtype, + DATA_MYSQL_BINARY_CHARSET_COLL); + } else { + /* Use the default charset for + other than binary columns. */ + + prtype = dtype_form_prtype( + prtype, + data_mysql_default_charset_coll); + } + } + + field = rec_get_nth_field_old(rec, 7, &len); + col_len = mach_read_from_4(field); + + ut_a(name_of_col_is(sys_columns, sys_index, 8, "PREC")); + + dict_mem_table_add_col(table, heap, name, + mtype, prtype, col_len); + btr_pcur_move_to_next_user_rec(&pcur, &mtr); + } + + btr_pcur_close(&pcur); + mtr_commit(&mtr); +} + +/************************************************************************ +Loads definitions for index fields. */ +static +void +dict_load_fields( +/*=============*/ + dict_index_t* index, /* in: index whose fields to load */ + mem_heap_t* heap) /* in: memory heap for temporary storage */ +{ + dict_table_t* sys_fields; + dict_index_t* sys_index; + btr_pcur_t pcur; + dtuple_t* tuple; + dfield_t* dfield; + ulint pos_and_prefix_len; + ulint prefix_len; + const rec_t* rec; + const byte* field; + ulint len; + byte* buf; + ulint i; + mtr_t mtr; + + ut_ad(mutex_own(&(dict_sys->mutex))); + + mtr_start(&mtr); + + sys_fields = dict_table_get_low("SYS_FIELDS"); + sys_index = UT_LIST_GET_FIRST(sys_fields->indexes); + ut_a(!dict_table_is_comp(sys_fields)); + + tuple = dtuple_create(heap, 1); + dfield = dtuple_get_nth_field(tuple, 0); + + buf = mem_heap_alloc(heap, 8); + mach_write_to_8(buf, index->id); + + dfield_set_data(dfield, buf, 8); + dict_index_copy_types(tuple, sys_index, 1); + + btr_pcur_open_on_user_rec(sys_index, tuple, PAGE_CUR_GE, + BTR_SEARCH_LEAF, &pcur, &mtr); + for (i = 0; i < index->n_fields; i++) { + + rec = btr_pcur_get_rec(&pcur); + + ut_a(btr_pcur_is_on_user_rec(&pcur)); + + /* There could be delete marked records in SYS_FIELDS + because SYS_FIELDS.INDEX_ID can be updated + by ALTER TABLE ADD INDEX. */ + + if (rec_get_deleted_flag(rec, 0)) { + + goto next_rec; + } + + field = rec_get_nth_field_old(rec, 0, &len); + ut_ad(len == 8); + + field = rec_get_nth_field_old(rec, 1, &len); + ut_a(len == 4); + + /* The next field stores the field position in the index + and a possible column prefix length if the index field + does not contain the whole column. The storage format is + like this: if there is at least one prefix field in the index, + then the HIGH 2 bytes contain the field number (== i) and the + low 2 bytes the prefix length for the field. Otherwise the + field number (== i) is contained in the 2 LOW bytes. */ + + pos_and_prefix_len = mach_read_from_4(field); + + ut_a((pos_and_prefix_len & 0xFFFFUL) == i + || (pos_and_prefix_len & 0xFFFF0000UL) == (i << 16)); + + if ((i == 0 && pos_and_prefix_len > 0) + || (pos_and_prefix_len & 0xFFFF0000UL) > 0) { + + prefix_len = pos_and_prefix_len & 0xFFFFUL; + } else { + prefix_len = 0; + } + + ut_a(name_of_col_is(sys_fields, sys_index, 4, "COL_NAME")); + + field = rec_get_nth_field_old(rec, 4, &len); + + dict_mem_index_add_field(index, + mem_heap_strdupl(heap, + (char*) field, len), + prefix_len); + +next_rec: + btr_pcur_move_to_next_user_rec(&pcur, &mtr); + } + + btr_pcur_close(&pcur); + mtr_commit(&mtr); +} + +/************************************************************************ +Loads definitions for table indexes. Adds them to the data dictionary +cache. */ +static +ulint +dict_load_indexes( +/*==============*/ + /* out: DB_SUCCESS if ok, DB_CORRUPTION + if corruption of dictionary table or + DB_UNSUPPORTED if table has unknown index + type */ + dict_table_t* table, /* in: table */ + mem_heap_t* heap) /* in: memory heap for temporary storage */ +{ + dict_table_t* sys_indexes; + dict_index_t* sys_index; + dict_index_t* index; + btr_pcur_t pcur; + dtuple_t* tuple; + dfield_t* dfield; + const rec_t* rec; + const byte* field; + ulint len; + ulint name_len; + char* name_buf; + ulint type; + ulint space; + ulint page_no; + ulint n_fields; + byte* buf; + ibool is_sys_table; + dulint id; + mtr_t mtr; + ulint error = DB_SUCCESS; + + ut_ad(mutex_own(&(dict_sys->mutex))); + + if ((ut_dulint_get_high(table->id) == 0) + && (ut_dulint_get_low(table->id) < DICT_HDR_FIRST_ID)) { + is_sys_table = TRUE; + } else { + is_sys_table = FALSE; + } + + mtr_start(&mtr); + + sys_indexes = dict_table_get_low("SYS_INDEXES"); + sys_index = UT_LIST_GET_FIRST(sys_indexes->indexes); + ut_a(!dict_table_is_comp(sys_indexes)); + + tuple = dtuple_create(heap, 1); + dfield = dtuple_get_nth_field(tuple, 0); + + buf = mem_heap_alloc(heap, 8); + mach_write_to_8(buf, table->id); + + dfield_set_data(dfield, buf, 8); + dict_index_copy_types(tuple, sys_index, 1); + + btr_pcur_open_on_user_rec(sys_index, tuple, PAGE_CUR_GE, + BTR_SEARCH_LEAF, &pcur, &mtr); + for (;;) { + if (!btr_pcur_is_on_user_rec(&pcur)) { + + break; + } + + rec = btr_pcur_get_rec(&pcur); + + field = rec_get_nth_field_old(rec, 0, &len); + ut_ad(len == 8); + + if (ut_memcmp(buf, field, len) != 0) { + break; + } else if (rec_get_deleted_flag(rec, 0)) { + /* Skip delete marked records */ + goto next_rec; + } + + field = rec_get_nth_field_old(rec, 1, &len); + ut_ad(len == 8); + id = mach_read_from_8(field); + + ut_a(name_of_col_is(sys_indexes, sys_index, 4, "NAME")); + + field = rec_get_nth_field_old(rec, 4, &name_len); + name_buf = mem_heap_strdupl(heap, (char*) field, name_len); + + field = rec_get_nth_field_old(rec, 5, &len); + n_fields = mach_read_from_4(field); + + field = rec_get_nth_field_old(rec, 6, &len); + type = mach_read_from_4(field); + + field = rec_get_nth_field_old(rec, 7, &len); + space = mach_read_from_4(field); + + ut_a(name_of_col_is(sys_indexes, sys_index, 8, "PAGE_NO")); + + field = rec_get_nth_field_old(rec, 8, &len); + page_no = mach_read_from_4(field); + + /* We check for unsupported types first, so that the + subsequent checks are relevant for the supported types. */ + if (type & ~(DICT_CLUSTERED | DICT_UNIQUE)) { + + fprintf(stderr, + "InnoDB: Error: unknown type %lu" + " of index %s of table %s\n", + (ulong) type, name_buf, table->name); + + error = DB_UNSUPPORTED; + goto func_exit; + } else if (page_no == FIL_NULL) { + + fprintf(stderr, + "InnoDB: Error: trying to load index %s" + " for table %s\n" + "InnoDB: but the index tree has been freed!\n", + name_buf, table->name); + + error = DB_CORRUPTION; + goto func_exit; + } else if ((type & DICT_CLUSTERED) == 0 + && NULL == dict_table_get_first_index(table)) { + + fputs("InnoDB: Error: trying to load index ", + stderr); + ut_print_name(stderr, NULL, FALSE, name_buf); + fputs(" for table ", stderr); + ut_print_name(stderr, NULL, TRUE, table->name); + fputs("\nInnoDB: but the first index" + " is not clustered!\n", stderr); + + error = DB_CORRUPTION; + goto func_exit; + } else if (is_sys_table + && ((type & DICT_CLUSTERED) + || ((table == dict_sys->sys_tables) + && (name_len == (sizeof "ID_IND") - 1) + && (0 == ut_memcmp(name_buf, + "ID_IND", name_len))))) { + + /* The index was created in memory already at booting + of the database server */ + } else { + index = dict_mem_index_create(table->name, name_buf, + space, type, n_fields); + index->id = id; + + dict_load_fields(index, heap); + error = dict_index_add_to_cache(table, index, page_no, + FALSE); + /* The data dictionary tables should never contain + invalid index definitions. If we ignored this error + and simply did not load this index definition, the + .frm file would disagree with the index definitions + inside InnoDB. */ + if (UNIV_UNLIKELY(error != DB_SUCCESS)) { + + goto func_exit; + } + } + +next_rec: + btr_pcur_move_to_next_user_rec(&pcur, &mtr); + } + +func_exit: + btr_pcur_close(&pcur); + mtr_commit(&mtr); + + return(error); +} + +/************************************************************************ +Loads a table definition and also all its index definitions, and also +the cluster definition if the table is a member in a cluster. Also loads +all foreign key constraints where the foreign key is in the table or where +a foreign key references columns in this table. Adds all these to the data +dictionary cache. */ +UNIV_INTERN +dict_table_t* +dict_load_table( +/*============*/ + /* out: table, NULL if does not exist; + if the table is stored in an .ibd file, + but the file does not exist, + then we set the ibd_file_missing flag TRUE + in the table object we return */ + const char* name) /* in: table name in the + databasename/tablename format */ +{ + ibool ibd_file_missing = FALSE; + dict_table_t* table; + dict_table_t* sys_tables; + btr_pcur_t pcur; + dict_index_t* sys_index; + dtuple_t* tuple; + mem_heap_t* heap; + dfield_t* dfield; + const rec_t* rec; + const byte* field; + ulint len; + ulint space; + ulint n_cols; + ulint flags; + ulint err; + mtr_t mtr; + + ut_ad(mutex_own(&(dict_sys->mutex))); + + heap = mem_heap_create(32000); + + mtr_start(&mtr); + + sys_tables = dict_table_get_low("SYS_TABLES"); + sys_index = UT_LIST_GET_FIRST(sys_tables->indexes); + ut_a(!dict_table_is_comp(sys_tables)); + + tuple = dtuple_create(heap, 1); + dfield = dtuple_get_nth_field(tuple, 0); + + dfield_set_data(dfield, name, ut_strlen(name)); + dict_index_copy_types(tuple, sys_index, 1); + + btr_pcur_open_on_user_rec(sys_index, tuple, PAGE_CUR_GE, + BTR_SEARCH_LEAF, &pcur, &mtr); + rec = btr_pcur_get_rec(&pcur); + + if (!btr_pcur_is_on_user_rec(&pcur) + || rec_get_deleted_flag(rec, 0)) { + /* Not found */ +err_exit: + btr_pcur_close(&pcur); + mtr_commit(&mtr); + mem_heap_free(heap); + + return(NULL); + } + + field = rec_get_nth_field_old(rec, 0, &len); + + /* Check if the table name in record is the searched one */ + if (len != ut_strlen(name) || ut_memcmp(name, field, len) != 0) { + + goto err_exit; + } + + ut_a(name_of_col_is(sys_tables, sys_index, 9, "SPACE")); + + field = rec_get_nth_field_old(rec, 9, &len); + space = mach_read_from_4(field); + + /* Check if the tablespace exists and has the right name */ + if (space != 0) { + flags = dict_sys_tables_get_flags(rec); + + if (UNIV_UNLIKELY(flags == ULINT_UNDEFINED)) { + field = rec_get_nth_field_old(rec, 5, &len); + flags = mach_read_from_4(field); + + ut_print_timestamp(stderr); + fputs(" InnoDB: Error: table ", stderr); + ut_print_filename(stderr, name); + fprintf(stderr, "\n" + "InnoDB: in InnoDB data dictionary" + " has unknown type %lx.\n", + (ulong) flags); + goto err_exit; + } + + if (fil_space_for_table_exists_in_mem(space, name, FALSE, + FALSE, FALSE)) { + /* Ok; (if we did a crash recovery then the tablespace + can already be in the memory cache) */ + } else { + /* In >= 4.1.9, InnoDB scans the data dictionary also + at a normal mysqld startup. It is an error if the + space object does not exist in memory. */ + + ut_print_timestamp(stderr); + fprintf(stderr, + " InnoDB: error: space object of table %s,\n" + "InnoDB: space id %lu did not exist in memory." + " Retrying an open.\n", + name, (ulong)space); + /* Try to open the tablespace */ + if (!fil_open_single_table_tablespace( + TRUE, space, flags, name)) { + /* We failed to find a sensible tablespace + file */ + + ibd_file_missing = TRUE; + } + } + } else { + flags = 0; + } + + ut_a(name_of_col_is(sys_tables, sys_index, 4, "N_COLS")); + + field = rec_get_nth_field_old(rec, 4, &len); + n_cols = mach_read_from_4(field); + + /* The high-order bit of N_COLS is the "compact format" flag. */ + if (n_cols & 0x80000000UL) { + flags |= DICT_TF_COMPACT; + } + + table = dict_mem_table_create(name, space, n_cols & ~0x80000000UL, + flags); + + table->ibd_file_missing = (unsigned int) ibd_file_missing; + + ut_a(name_of_col_is(sys_tables, sys_index, 3, "ID")); + + field = rec_get_nth_field_old(rec, 3, &len); + table->id = mach_read_from_8(field); + + btr_pcur_close(&pcur); + mtr_commit(&mtr); + + dict_load_columns(table, heap); + + dict_table_add_to_cache(table, heap); + + mem_heap_empty(heap); + + err = dict_load_indexes(table, heap); +#ifndef UNIV_HOTBACKUP + /* If the force recovery flag is set, we open the table irrespective + of the error condition, since the user may want to dump data from the + clustered index. However we load the foreign key information only if + all indexes were loaded. */ + if (err == DB_SUCCESS) { + err = dict_load_foreigns(table->name, TRUE); + } else if (!srv_force_recovery) { + dict_table_remove_from_cache(table); + table = NULL; + } +# if 0 + if (err != DB_SUCCESS && table != NULL) { + + mutex_enter(&dict_foreign_err_mutex); + + ut_print_timestamp(stderr); + + fprintf(stderr, + " InnoDB: Error: could not make a foreign key" + " definition to match\n" + "InnoDB: the foreign key table" + " or the referenced table!\n" + "InnoDB: The data dictionary of InnoDB is corrupt." + " You may need to drop\n" + "InnoDB: and recreate the foreign key table" + " or the referenced table.\n" + "InnoDB: Submit a detailed bug report" + " to http://bugs.mysql.com\n" + "InnoDB: Latest foreign key error printout:\n%s\n", + dict_foreign_err_buf); + + mutex_exit(&dict_foreign_err_mutex); + } +# endif /* 0 */ +#endif /* !UNIV_HOTBACKUP */ + mem_heap_free(heap); + + return(table); +} + +/*************************************************************************** +Loads a table object based on the table id. */ +UNIV_INTERN +dict_table_t* +dict_load_table_on_id( +/*==================*/ + /* out: table; NULL if table does not exist */ + dulint table_id) /* in: table id */ +{ + byte id_buf[8]; + btr_pcur_t pcur; + mem_heap_t* heap; + dtuple_t* tuple; + dfield_t* dfield; + dict_index_t* sys_table_ids; + dict_table_t* sys_tables; + const rec_t* rec; + const byte* field; + ulint len; + dict_table_t* table; + mtr_t mtr; + + ut_ad(mutex_own(&(dict_sys->mutex))); + + /* NOTE that the operation of this function is protected by + the dictionary mutex, and therefore no deadlocks can occur + with other dictionary operations. */ + + mtr_start(&mtr); + /*---------------------------------------------------*/ + /* Get the secondary index based on ID for table SYS_TABLES */ + sys_tables = dict_sys->sys_tables; + sys_table_ids = dict_table_get_next_index( + dict_table_get_first_index(sys_tables)); + ut_a(!dict_table_is_comp(sys_tables)); + heap = mem_heap_create(256); + + tuple = dtuple_create(heap, 1); + dfield = dtuple_get_nth_field(tuple, 0); + + /* Write the table id in byte format to id_buf */ + mach_write_to_8(id_buf, table_id); + + dfield_set_data(dfield, id_buf, 8); + dict_index_copy_types(tuple, sys_table_ids, 1); + + btr_pcur_open_on_user_rec(sys_table_ids, tuple, PAGE_CUR_GE, + BTR_SEARCH_LEAF, &pcur, &mtr); + rec = btr_pcur_get_rec(&pcur); + + if (!btr_pcur_is_on_user_rec(&pcur) + || rec_get_deleted_flag(rec, 0)) { + /* Not found */ + + btr_pcur_close(&pcur); + mtr_commit(&mtr); + mem_heap_free(heap); + + return(NULL); + } + + /*---------------------------------------------------*/ + /* Now we have the record in the secondary index containing the + table ID and NAME */ + + rec = btr_pcur_get_rec(&pcur); + field = rec_get_nth_field_old(rec, 0, &len); + ut_ad(len == 8); + + /* Check if the table id in record is the one searched for */ + if (ut_dulint_cmp(table_id, mach_read_from_8(field)) != 0) { + + btr_pcur_close(&pcur); + mtr_commit(&mtr); + mem_heap_free(heap); + + return(NULL); + } + + /* Now we get the table name from the record */ + field = rec_get_nth_field_old(rec, 1, &len); + /* Load the table definition to memory */ + table = dict_load_table(mem_heap_strdupl(heap, (char*) field, len)); + + btr_pcur_close(&pcur); + mtr_commit(&mtr); + mem_heap_free(heap); + + return(table); +} + +/************************************************************************ +This function is called when the database is booted. Loads system table +index definitions except for the clustered index which is added to the +dictionary cache at booting before calling this function. */ +UNIV_INTERN +void +dict_load_sys_table( +/*================*/ + dict_table_t* table) /* in: system table */ +{ + mem_heap_t* heap; + + ut_ad(mutex_own(&(dict_sys->mutex))); + + heap = mem_heap_create(1000); + + dict_load_indexes(table, heap); + + mem_heap_free(heap); +} + +#ifndef UNIV_HOTBACKUP +/************************************************************************ +Loads foreign key constraint col names (also for the referenced table). */ +static +void +dict_load_foreign_cols( +/*===================*/ + const char* id, /* in: foreign constraint id as a + null-terminated string */ + dict_foreign_t* foreign)/* in: foreign constraint object */ +{ + dict_table_t* sys_foreign_cols; + dict_index_t* sys_index; + btr_pcur_t pcur; + dtuple_t* tuple; + dfield_t* dfield; + const rec_t* rec; + const byte* field; + ulint len; + ulint i; + mtr_t mtr; + + ut_ad(mutex_own(&(dict_sys->mutex))); + + foreign->foreign_col_names = mem_heap_alloc( + foreign->heap, foreign->n_fields * sizeof(void*)); + + foreign->referenced_col_names = mem_heap_alloc( + foreign->heap, foreign->n_fields * sizeof(void*)); + mtr_start(&mtr); + + sys_foreign_cols = dict_table_get_low("SYS_FOREIGN_COLS"); + sys_index = UT_LIST_GET_FIRST(sys_foreign_cols->indexes); + ut_a(!dict_table_is_comp(sys_foreign_cols)); + + tuple = dtuple_create(foreign->heap, 1); + dfield = dtuple_get_nth_field(tuple, 0); + + dfield_set_data(dfield, id, ut_strlen(id)); + dict_index_copy_types(tuple, sys_index, 1); + + btr_pcur_open_on_user_rec(sys_index, tuple, PAGE_CUR_GE, + BTR_SEARCH_LEAF, &pcur, &mtr); + for (i = 0; i < foreign->n_fields; i++) { + + rec = btr_pcur_get_rec(&pcur); + + ut_a(btr_pcur_is_on_user_rec(&pcur)); + ut_a(!rec_get_deleted_flag(rec, 0)); + + field = rec_get_nth_field_old(rec, 0, &len); + ut_a(len == ut_strlen(id)); + ut_a(ut_memcmp(id, field, len) == 0); + + field = rec_get_nth_field_old(rec, 1, &len); + ut_a(len == 4); + ut_a(i == mach_read_from_4(field)); + + field = rec_get_nth_field_old(rec, 4, &len); + foreign->foreign_col_names[i] = mem_heap_strdupl( + foreign->heap, (char*) field, len); + + field = rec_get_nth_field_old(rec, 5, &len); + foreign->referenced_col_names[i] = mem_heap_strdupl( + foreign->heap, (char*) field, len); + + btr_pcur_move_to_next_user_rec(&pcur, &mtr); + } + + btr_pcur_close(&pcur); + mtr_commit(&mtr); +} + +/*************************************************************************** +Loads a foreign key constraint to the dictionary cache. */ +static +ulint +dict_load_foreign( +/*==============*/ + /* out: DB_SUCCESS or error code */ + const char* id, /* in: foreign constraint id as a + null-terminated string */ + ibool check_charsets) + /* in: TRUE=check charset compatibility */ +{ + dict_foreign_t* foreign; + dict_table_t* sys_foreign; + btr_pcur_t pcur; + dict_index_t* sys_index; + dtuple_t* tuple; + mem_heap_t* heap2; + dfield_t* dfield; + const rec_t* rec; + const byte* field; + ulint len; + ulint n_fields_and_type; + mtr_t mtr; + + ut_ad(mutex_own(&(dict_sys->mutex))); + + heap2 = mem_heap_create(1000); + + mtr_start(&mtr); + + sys_foreign = dict_table_get_low("SYS_FOREIGN"); + sys_index = UT_LIST_GET_FIRST(sys_foreign->indexes); + ut_a(!dict_table_is_comp(sys_foreign)); + + tuple = dtuple_create(heap2, 1); + dfield = dtuple_get_nth_field(tuple, 0); + + dfield_set_data(dfield, id, ut_strlen(id)); + dict_index_copy_types(tuple, sys_index, 1); + + btr_pcur_open_on_user_rec(sys_index, tuple, PAGE_CUR_GE, + BTR_SEARCH_LEAF, &pcur, &mtr); + rec = btr_pcur_get_rec(&pcur); + + if (!btr_pcur_is_on_user_rec(&pcur) + || rec_get_deleted_flag(rec, 0)) { + /* Not found */ + + fprintf(stderr, + "InnoDB: Error A: cannot load foreign constraint %s\n", + id); + + btr_pcur_close(&pcur); + mtr_commit(&mtr); + mem_heap_free(heap2); + + return(DB_ERROR); + } + + field = rec_get_nth_field_old(rec, 0, &len); + + /* Check if the id in record is the searched one */ + if (len != ut_strlen(id) || ut_memcmp(id, field, len) != 0) { + + fprintf(stderr, + "InnoDB: Error B: cannot load foreign constraint %s\n", + id); + + btr_pcur_close(&pcur); + mtr_commit(&mtr); + mem_heap_free(heap2); + + return(DB_ERROR); + } + + /* Read the table names and the number of columns associated + with the constraint */ + + mem_heap_free(heap2); + + foreign = dict_mem_foreign_create(); + + n_fields_and_type = mach_read_from_4( + rec_get_nth_field_old(rec, 5, &len)); + + ut_a(len == 4); + + /* We store the type in the bits 24..29 of n_fields_and_type. */ + + foreign->type = (unsigned int) (n_fields_and_type >> 24); + foreign->n_fields = (unsigned int) (n_fields_and_type & 0x3FFUL); + + foreign->id = mem_heap_strdup(foreign->heap, id); + + field = rec_get_nth_field_old(rec, 3, &len); + foreign->foreign_table_name = mem_heap_strdupl( + foreign->heap, (char*) field, len); + + field = rec_get_nth_field_old(rec, 4, &len); + foreign->referenced_table_name = mem_heap_strdupl( + foreign->heap, (char*) field, len); + + btr_pcur_close(&pcur); + mtr_commit(&mtr); + + dict_load_foreign_cols(id, foreign); + + /* If the foreign table is not yet in the dictionary cache, we + have to load it so that we are able to make type comparisons + in the next function call. */ + + dict_table_get_low(foreign->foreign_table_name); + + /* Note that there may already be a foreign constraint object in + the dictionary cache for this constraint: then the following + call only sets the pointers in it to point to the appropriate table + and index objects and frees the newly created object foreign. + Adding to the cache should always succeed since we are not creating + a new foreign key constraint but loading one from the data + dictionary. */ + + return(dict_foreign_add_to_cache(foreign, check_charsets)); +} + +/*************************************************************************** +Loads foreign key constraints where the table is either the foreign key +holder or where the table is referenced by a foreign key. Adds these +constraints to the data dictionary. Note that we know that the dictionary +cache already contains all constraints where the other relevant table is +already in the dictionary cache. */ +UNIV_INTERN +ulint +dict_load_foreigns( +/*===============*/ + /* out: DB_SUCCESS or error code */ + const char* table_name, /* in: table name */ + ibool check_charsets) /* in: TRUE=check charset + compatibility */ +{ + btr_pcur_t pcur; + mem_heap_t* heap; + dtuple_t* tuple; + dfield_t* dfield; + dict_index_t* sec_index; + dict_table_t* sys_foreign; + const rec_t* rec; + const byte* field; + ulint len; + char* id ; + ulint err; + mtr_t mtr; + + ut_ad(mutex_own(&(dict_sys->mutex))); + + sys_foreign = dict_table_get_low("SYS_FOREIGN"); + + if (sys_foreign == NULL) { + /* No foreign keys defined yet in this database */ + + fprintf(stderr, + "InnoDB: Error: no foreign key system tables" + " in the database\n"); + + return(DB_ERROR); + } + + ut_a(!dict_table_is_comp(sys_foreign)); + mtr_start(&mtr); + + /* Get the secondary index based on FOR_NAME from table + SYS_FOREIGN */ + + sec_index = dict_table_get_next_index( + dict_table_get_first_index(sys_foreign)); +start_load: + heap = mem_heap_create(256); + + tuple = dtuple_create(heap, 1); + dfield = dtuple_get_nth_field(tuple, 0); + + dfield_set_data(dfield, table_name, ut_strlen(table_name)); + dict_index_copy_types(tuple, sec_index, 1); + + btr_pcur_open_on_user_rec(sec_index, tuple, PAGE_CUR_GE, + BTR_SEARCH_LEAF, &pcur, &mtr); +loop: + rec = btr_pcur_get_rec(&pcur); + + if (!btr_pcur_is_on_user_rec(&pcur)) { + /* End of index */ + + goto load_next_index; + } + + /* Now we have the record in the secondary index containing a table + name and a foreign constraint ID */ + + rec = btr_pcur_get_rec(&pcur); + field = rec_get_nth_field_old(rec, 0, &len); + + /* Check if the table name in the record is the one searched for; the + following call does the comparison in the latin1_swedish_ci + charset-collation, in a case-insensitive way. */ + + if (0 != cmp_data_data(dfield_get_type(dfield)->mtype, + dfield_get_type(dfield)->prtype, + dfield_get_data(dfield), dfield_get_len(dfield), + field, len)) { + + goto load_next_index; + } + + /* Since table names in SYS_FOREIGN are stored in a case-insensitive + order, we have to check that the table name matches also in a binary + string comparison. On Unix, MySQL allows table names that only differ + in character case. */ + + if (0 != ut_memcmp(field, table_name, len)) { + + goto next_rec; + } + + if (rec_get_deleted_flag(rec, 0)) { + + goto next_rec; + } + + /* Now we get a foreign key constraint id */ + field = rec_get_nth_field_old(rec, 1, &len); + id = mem_heap_strdupl(heap, (char*) field, len); + + btr_pcur_store_position(&pcur, &mtr); + + mtr_commit(&mtr); + + /* Load the foreign constraint definition to the dictionary cache */ + + err = dict_load_foreign(id, check_charsets); + + if (err != DB_SUCCESS) { + btr_pcur_close(&pcur); + mem_heap_free(heap); + + return(err); + } + + mtr_start(&mtr); + + btr_pcur_restore_position(BTR_SEARCH_LEAF, &pcur, &mtr); +next_rec: + btr_pcur_move_to_next_user_rec(&pcur, &mtr); + + goto loop; + +load_next_index: + btr_pcur_close(&pcur); + mtr_commit(&mtr); + mem_heap_free(heap); + + sec_index = dict_table_get_next_index(sec_index); + + if (sec_index != NULL) { + + mtr_start(&mtr); + + goto start_load; + } + + return(DB_SUCCESS); +} +#endif /* !UNIV_HOTBACKUP */ diff --git a/storage/xtradb/dict/dict0mem.c b/storage/xtradb/dict/dict0mem.c new file mode 100644 index 00000000000..31c0c23f450 --- /dev/null +++ b/storage/xtradb/dict/dict0mem.c @@ -0,0 +1,308 @@ +/***************************************************************************** + +Copyright (c) 1996, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/********************************************************************** +Data dictionary memory object creation + +Created 1/8/1996 Heikki Tuuri +***********************************************************************/ + +#include "dict0mem.h" + +#ifdef UNIV_NONINL +#include "dict0mem.ic" +#endif + +#include "rem0rec.h" +#include "data0type.h" +#include "mach0data.h" +#include "dict0dict.h" +#include "lock0lock.h" + +#define DICT_HEAP_SIZE 100 /* initial memory heap size when + creating a table or index object */ + +/************************************************************************** +Creates a table memory object. */ +UNIV_INTERN +dict_table_t* +dict_mem_table_create( +/*==================*/ + /* out, own: table object */ + const char* name, /* in: table name */ + ulint space, /* in: space where the clustered index of + the table is placed; this parameter is + ignored if the table is made a member of + a cluster */ + ulint n_cols, /* in: number of columns */ + ulint flags) /* in: table flags */ +{ + dict_table_t* table; + mem_heap_t* heap; + + ut_ad(name); + ut_a(!(flags & (~0 << DICT_TF_BITS))); + + heap = mem_heap_create(DICT_HEAP_SIZE); + + table = mem_heap_zalloc(heap, sizeof(dict_table_t)); + + table->heap = heap; + + table->flags = (unsigned int) flags; + table->name = mem_heap_strdup(heap, name); + table->space = (unsigned int) space; + table->n_cols = (unsigned int) (n_cols + DATA_N_SYS_COLS); + + table->cols = mem_heap_alloc(heap, (n_cols + DATA_N_SYS_COLS) + * sizeof(dict_col_t)); + + table->autoinc_lock = mem_heap_alloc(heap, lock_get_size()); + + mutex_create(&table->autoinc_mutex, SYNC_DICT_AUTOINC_MUTEX); + + table->autoinc = 0; + + /* The number of transactions that are either waiting on the + AUTOINC lock or have been granted the lock. */ + table->n_waiting_or_granted_auto_inc_locks = 0; + +#ifdef UNIV_DEBUG + table->magic_n = DICT_TABLE_MAGIC_N; +#endif /* UNIV_DEBUG */ + return(table); +} + +/******************************************************************** +Free a table memory object. */ +UNIV_INTERN +void +dict_mem_table_free( +/*================*/ + dict_table_t* table) /* in: table */ +{ + ut_ad(table); + ut_ad(table->magic_n == DICT_TABLE_MAGIC_N); + ut_d(table->cached = FALSE); + + mutex_free(&(table->autoinc_mutex)); + mem_heap_free(table->heap); +} + +/******************************************************************** +Append 'name' to 'col_names' (@see dict_table_t::col_names). */ +static +const char* +dict_add_col_name( +/*==============*/ + /* out: new column names array */ + const char* col_names, /* in: existing column names, or + NULL */ + ulint cols, /* in: number of existing columns */ + const char* name, /* in: new column name */ + mem_heap_t* heap) /* in: heap */ +{ + ulint old_len; + ulint new_len; + ulint total_len; + char* res; + + ut_ad(!cols == !col_names); + + /* Find out length of existing array. */ + if (col_names) { + const char* s = col_names; + ulint i; + + for (i = 0; i < cols; i++) { + s += strlen(s) + 1; + } + + old_len = s - col_names; + } else { + old_len = 0; + } + + new_len = strlen(name) + 1; + total_len = old_len + new_len; + + res = mem_heap_alloc(heap, total_len); + + if (old_len > 0) { + memcpy(res, col_names, old_len); + } + + memcpy(res + old_len, name, new_len); + + return(res); +} + +/************************************************************************** +Adds a column definition to a table. */ +UNIV_INTERN +void +dict_mem_table_add_col( +/*===================*/ + dict_table_t* table, /* in: table */ + mem_heap_t* heap, /* in: temporary memory heap, or NULL */ + const char* name, /* in: column name, or NULL */ + ulint mtype, /* in: main datatype */ + ulint prtype, /* in: precise type */ + ulint len) /* in: precision */ +{ + dict_col_t* col; + ulint mbminlen; + ulint mbmaxlen; + ulint i; + + ut_ad(table); + ut_ad(table->magic_n == DICT_TABLE_MAGIC_N); + ut_ad(!heap == !name); + + i = table->n_def++; + + if (name) { + if (UNIV_UNLIKELY(table->n_def == table->n_cols)) { + heap = table->heap; + } + if (UNIV_LIKELY(i) && UNIV_UNLIKELY(!table->col_names)) { + /* All preceding column names are empty. */ + char* s = mem_heap_zalloc(heap, table->n_def); + table->col_names = s; + } + + table->col_names = dict_add_col_name(table->col_names, + i, name, heap); + } + + col = dict_table_get_nth_col(table, i); + + col->ind = (unsigned int) i; + col->ord_part = 0; + + col->mtype = (unsigned int) mtype; + col->prtype = (unsigned int) prtype; + col->len = (unsigned int) len; + + dtype_get_mblen(mtype, prtype, &mbminlen, &mbmaxlen); + + col->mbminlen = (unsigned int) mbminlen; + col->mbmaxlen = (unsigned int) mbmaxlen; +} + +/************************************************************************** +Creates an index memory object. */ +UNIV_INTERN +dict_index_t* +dict_mem_index_create( +/*==================*/ + /* out, own: index object */ + const char* table_name, /* in: table name */ + const char* index_name, /* in: index name */ + ulint space, /* in: space where the index tree is + placed, ignored if the index is of + the clustered type */ + ulint type, /* in: DICT_UNIQUE, + DICT_CLUSTERED, ... ORed */ + ulint n_fields) /* in: number of fields */ +{ + dict_index_t* index; + mem_heap_t* heap; + + ut_ad(table_name && index_name); + + heap = mem_heap_create(DICT_HEAP_SIZE); + index = mem_heap_zalloc(heap, sizeof(dict_index_t)); + + index->heap = heap; + + index->type = type; + index->space = (unsigned int) space; + index->name = mem_heap_strdup(heap, index_name); + index->table_name = table_name; + index->n_fields = (unsigned int) n_fields; + index->fields = mem_heap_alloc(heap, 1 + n_fields + * sizeof(dict_field_t)); + /* The '1 +' above prevents allocation + of an empty mem block */ +#ifdef UNIV_DEBUG + index->magic_n = DICT_INDEX_MAGIC_N; +#endif /* UNIV_DEBUG */ + return(index); +} + +/************************************************************************** +Creates and initializes a foreign constraint memory object. */ +UNIV_INTERN +dict_foreign_t* +dict_mem_foreign_create(void) +/*=========================*/ + /* out, own: foreign constraint struct */ +{ + dict_foreign_t* foreign; + mem_heap_t* heap; + + heap = mem_heap_create(100); + + foreign = mem_heap_zalloc(heap, sizeof(dict_foreign_t)); + + foreign->heap = heap; + + return(foreign); +} + +/************************************************************************** +Adds a field definition to an index. NOTE: does not take a copy +of the column name if the field is a column. The memory occupied +by the column name may be released only after publishing the index. */ +UNIV_INTERN +void +dict_mem_index_add_field( +/*=====================*/ + dict_index_t* index, /* in: index */ + const char* name, /* in: column name */ + ulint prefix_len) /* in: 0 or the column prefix length + in a MySQL index like + INDEX (textcol(25)) */ +{ + dict_field_t* field; + + ut_ad(index); + ut_ad(index->magic_n == DICT_INDEX_MAGIC_N); + + index->n_def++; + + field = dict_index_get_nth_field(index, index->n_def - 1); + + field->name = name; + field->prefix_len = (unsigned int) prefix_len; +} + +/************************************************************************** +Frees an index memory object. */ +UNIV_INTERN +void +dict_mem_index_free( +/*================*/ + dict_index_t* index) /* in: index */ +{ + ut_ad(index); + ut_ad(index->magic_n == DICT_INDEX_MAGIC_N); + + mem_heap_free(index->heap); +} diff --git a/storage/xtradb/dyn/dyn0dyn.c b/storage/xtradb/dyn/dyn0dyn.c new file mode 100644 index 00000000000..16e82eaed66 --- /dev/null +++ b/storage/xtradb/dyn/dyn0dyn.c @@ -0,0 +1,64 @@ +/***************************************************************************** + +Copyright (c) 1996, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/****************************************************** +The dynamically allocated array + +Created 2/5/1996 Heikki Tuuri +*******************************************************/ + +#include "dyn0dyn.h" +#ifdef UNIV_NONINL +#include "dyn0dyn.ic" +#endif + +/**************************************************************** +Adds a new block to a dyn array. */ +UNIV_INTERN +dyn_block_t* +dyn_array_add_block( +/*================*/ + /* out: created block */ + dyn_array_t* arr) /* in: dyn array */ +{ + mem_heap_t* heap; + dyn_block_t* block; + + ut_ad(arr); + ut_ad(arr->magic_n == DYN_BLOCK_MAGIC_N); + + if (arr->heap == NULL) { + UT_LIST_INIT(arr->base); + UT_LIST_ADD_FIRST(list, arr->base, arr); + + arr->heap = mem_heap_create(sizeof(dyn_block_t)); + } + + block = dyn_array_get_last_block(arr); + block->used = block->used | DYN_BLOCK_FULL_FLAG; + + heap = arr->heap; + + block = mem_heap_alloc(heap, sizeof(dyn_block_t)); + + block->used = 0; + + UT_LIST_ADD_LAST(list, arr->base, block); + + return(block); +} diff --git a/storage/xtradb/eval/eval0eval.c b/storage/xtradb/eval/eval0eval.c new file mode 100644 index 00000000000..a2590c63c38 --- /dev/null +++ b/storage/xtradb/eval/eval0eval.c @@ -0,0 +1,851 @@ +/***************************************************************************** + +Copyright (c) 1997, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/****************************************************** +SQL evaluator: evaluates simple data structures, like expressions, in +a query graph + +Created 12/29/1997 Heikki Tuuri +*******************************************************/ + +#include "eval0eval.h" + +#ifdef UNIV_NONINL +#include "eval0eval.ic" +#endif + +#include "data0data.h" +#include "row0sel.h" + +/* The RND function seed */ +static ulint eval_rnd = 128367121; + +/* Dummy adress used when we should allocate a buffer of size 0 in +the function below */ + +static byte eval_dummy; + +/********************************************************************* +Allocate a buffer from global dynamic memory for a value of a que_node. +NOTE that this memory must be explicitly freed when the query graph is +freed. If the node already has an allocated buffer, that buffer is freed +here. NOTE that this is the only function where dynamic memory should be +allocated for a query node val field. */ +UNIV_INTERN +byte* +eval_node_alloc_val_buf( +/*====================*/ + /* out: pointer to allocated buffer */ + que_node_t* node, /* in: query graph node; sets the val field + data field to point to the new buffer, and + len field equal to size */ + ulint size) /* in: buffer size */ +{ + dfield_t* dfield; + byte* data; + + ut_ad(que_node_get_type(node) == QUE_NODE_SYMBOL + || que_node_get_type(node) == QUE_NODE_FUNC); + + dfield = que_node_get_val(node); + + data = dfield_get_data(dfield); + + if (data && data != &eval_dummy) { + mem_free(data); + } + + if (size == 0) { + data = &eval_dummy; + } else { + data = mem_alloc(size); + } + + que_node_set_val_buf_size(node, size); + + dfield_set_data(dfield, data, size); + + return(data); +} + +/********************************************************************* +Free the buffer from global dynamic memory for a value of a que_node, +if it has been allocated in the above function. The freeing for pushed +column values is done in sel_col_prefetch_buf_free. */ +UNIV_INTERN +void +eval_node_free_val_buf( +/*===================*/ + que_node_t* node) /* in: query graph node */ +{ + dfield_t* dfield; + byte* data; + + ut_ad(que_node_get_type(node) == QUE_NODE_SYMBOL + || que_node_get_type(node) == QUE_NODE_FUNC); + + dfield = que_node_get_val(node); + + data = dfield_get_data(dfield); + + if (que_node_get_val_buf_size(node) > 0) { + ut_a(data); + + mem_free(data); + } +} + +/********************************************************************* +Evaluates a comparison node. */ +UNIV_INTERN +ibool +eval_cmp( +/*=====*/ + /* out: the result of the comparison */ + func_node_t* cmp_node) /* in: comparison node */ +{ + que_node_t* arg1; + que_node_t* arg2; + int res; + ibool val; + int func; + + ut_ad(que_node_get_type(cmp_node) == QUE_NODE_FUNC); + + arg1 = cmp_node->args; + arg2 = que_node_get_next(arg1); + + res = cmp_dfield_dfield(que_node_get_val(arg1), + que_node_get_val(arg2)); + val = TRUE; + + func = cmp_node->func; + + if (func == '=') { + if (res != 0) { + val = FALSE; + } + } else if (func == '<') { + if (res != -1) { + val = FALSE; + } + } else if (func == PARS_LE_TOKEN) { + if (res == 1) { + val = FALSE; + } + } else if (func == PARS_NE_TOKEN) { + if (res == 0) { + val = FALSE; + } + } else if (func == PARS_GE_TOKEN) { + if (res == -1) { + val = FALSE; + } + } else { + ut_ad(func == '>'); + + if (res != 1) { + val = FALSE; + } + } + + eval_node_set_ibool_val(cmp_node, val); + + return(val); +} + +/********************************************************************* +Evaluates a logical operation node. */ +UNIV_INLINE +void +eval_logical( +/*=========*/ + func_node_t* logical_node) /* in: logical operation node */ +{ + que_node_t* arg1; + que_node_t* arg2; + ibool val1; + ibool val2 = 0; /* remove warning */ + ibool val = 0; /* remove warning */ + int func; + + ut_ad(que_node_get_type(logical_node) == QUE_NODE_FUNC); + + arg1 = logical_node->args; + arg2 = que_node_get_next(arg1); /* arg2 is NULL if func is 'NOT' */ + + val1 = eval_node_get_ibool_val(arg1); + + if (arg2) { + val2 = eval_node_get_ibool_val(arg2); + } + + func = logical_node->func; + + if (func == PARS_AND_TOKEN) { + val = val1 & val2; + } else if (func == PARS_OR_TOKEN) { + val = val1 | val2; + } else if (func == PARS_NOT_TOKEN) { + val = TRUE - val1; + } else { + ut_error; + } + + eval_node_set_ibool_val(logical_node, val); +} + +/********************************************************************* +Evaluates an arithmetic operation node. */ +UNIV_INLINE +void +eval_arith( +/*=======*/ + func_node_t* arith_node) /* in: arithmetic operation node */ +{ + que_node_t* arg1; + que_node_t* arg2; + lint val1; + lint val2 = 0; /* remove warning */ + lint val; + int func; + + ut_ad(que_node_get_type(arith_node) == QUE_NODE_FUNC); + + arg1 = arith_node->args; + arg2 = que_node_get_next(arg1); /* arg2 is NULL if func is unary '-' */ + + val1 = eval_node_get_int_val(arg1); + + if (arg2) { + val2 = eval_node_get_int_val(arg2); + } + + func = arith_node->func; + + if (func == '+') { + val = val1 + val2; + } else if ((func == '-') && arg2) { + val = val1 - val2; + } else if (func == '-') { + val = -val1; + } else if (func == '*') { + val = val1 * val2; + } else { + ut_ad(func == '/'); + val = val1 / val2; + } + + eval_node_set_int_val(arith_node, val); +} + +/********************************************************************* +Evaluates an aggregate operation node. */ +UNIV_INLINE +void +eval_aggregate( +/*===========*/ + func_node_t* node) /* in: aggregate operation node */ +{ + que_node_t* arg; + lint val; + lint arg_val; + int func; + + ut_ad(que_node_get_type(node) == QUE_NODE_FUNC); + + val = eval_node_get_int_val(node); + + func = node->func; + + if (func == PARS_COUNT_TOKEN) { + + val = val + 1; + } else { + ut_ad(func == PARS_SUM_TOKEN); + + arg = node->args; + arg_val = eval_node_get_int_val(arg); + + val = val + arg_val; + } + + eval_node_set_int_val(node, val); +} + +/********************************************************************* +Evaluates a predefined function node where the function is not relevant +in benchmarks. */ +static +void +eval_predefined_2( +/*==============*/ + func_node_t* func_node) /* in: predefined function node */ +{ + que_node_t* arg; + que_node_t* arg1; + que_node_t* arg2 = 0; /* remove warning (??? bug ???) */ + lint int_val; + byte* data; + ulint len1; + ulint len2; + int func; + ulint i; + + ut_ad(que_node_get_type(func_node) == QUE_NODE_FUNC); + + arg1 = func_node->args; + + if (arg1) { + arg2 = que_node_get_next(arg1); + } + + func = func_node->func; + + if (func == PARS_PRINTF_TOKEN) { + + arg = arg1; + + while (arg) { + dfield_print(que_node_get_val(arg)); + + arg = que_node_get_next(arg); + } + + putc('\n', stderr); + + } else if (func == PARS_ASSERT_TOKEN) { + + if (!eval_node_get_ibool_val(arg1)) { + fputs("SQL assertion fails in a stored procedure!\n", + stderr); + } + + ut_a(eval_node_get_ibool_val(arg1)); + + /* This function, or more precisely, a debug procedure, + returns no value */ + + } else if (func == PARS_RND_TOKEN) { + + len1 = (ulint)eval_node_get_int_val(arg1); + len2 = (ulint)eval_node_get_int_val(arg2); + + ut_ad(len2 >= len1); + + if (len2 > len1) { + int_val = (lint) (len1 + + (eval_rnd % (len2 - len1 + 1))); + } else { + int_val = (lint) len1; + } + + eval_rnd = ut_rnd_gen_next_ulint(eval_rnd); + + eval_node_set_int_val(func_node, int_val); + + } else if (func == PARS_RND_STR_TOKEN) { + + len1 = (ulint)eval_node_get_int_val(arg1); + + data = eval_node_ensure_val_buf(func_node, len1); + + for (i = 0; i < len1; i++) { + data[i] = (byte)(97 + (eval_rnd % 3)); + + eval_rnd = ut_rnd_gen_next_ulint(eval_rnd); + } + } else { + ut_error; + } +} + +/********************************************************************* +Evaluates a notfound-function node. */ +UNIV_INLINE +void +eval_notfound( +/*==========*/ + func_node_t* func_node) /* in: function node */ +{ + que_node_t* arg1; + que_node_t* arg2; + sym_node_t* cursor; + sel_node_t* sel_node; + ibool ibool_val; + + arg1 = func_node->args; + arg2 = que_node_get_next(arg1); + + ut_ad(func_node->func == PARS_NOTFOUND_TOKEN); + + cursor = arg1; + + ut_ad(que_node_get_type(cursor) == QUE_NODE_SYMBOL); + + if (cursor->token_type == SYM_LIT) { + + ut_ad(ut_memcmp(dfield_get_data(que_node_get_val(cursor)), + "SQL", 3) == 0); + + sel_node = cursor->sym_table->query_graph->last_sel_node; + } else { + sel_node = cursor->alias->cursor_def; + } + + if (sel_node->state == SEL_NODE_NO_MORE_ROWS) { + ibool_val = TRUE; + } else { + ibool_val = FALSE; + } + + eval_node_set_ibool_val(func_node, ibool_val); +} + +/********************************************************************* +Evaluates a substr-function node. */ +UNIV_INLINE +void +eval_substr( +/*========*/ + func_node_t* func_node) /* in: function node */ +{ + que_node_t* arg1; + que_node_t* arg2; + que_node_t* arg3; + dfield_t* dfield; + byte* str1; + ulint len1; + ulint len2; + + arg1 = func_node->args; + arg2 = que_node_get_next(arg1); + + ut_ad(func_node->func == PARS_SUBSTR_TOKEN); + + arg3 = que_node_get_next(arg2); + + str1 = dfield_get_data(que_node_get_val(arg1)); + + len1 = (ulint)eval_node_get_int_val(arg2); + len2 = (ulint)eval_node_get_int_val(arg3); + + dfield = que_node_get_val(func_node); + + dfield_set_data(dfield, str1 + len1, len2); +} + +/********************************************************************* +Evaluates a replstr-procedure node. */ +static +void +eval_replstr( +/*=========*/ + func_node_t* func_node) /* in: function node */ +{ + que_node_t* arg1; + que_node_t* arg2; + que_node_t* arg3; + que_node_t* arg4; + byte* str1; + byte* str2; + ulint len1; + ulint len2; + + arg1 = func_node->args; + arg2 = que_node_get_next(arg1); + + ut_ad(que_node_get_type(arg1) == QUE_NODE_SYMBOL); + + arg3 = que_node_get_next(arg2); + arg4 = que_node_get_next(arg3); + + str1 = dfield_get_data(que_node_get_val(arg1)); + str2 = dfield_get_data(que_node_get_val(arg2)); + + len1 = (ulint)eval_node_get_int_val(arg3); + len2 = (ulint)eval_node_get_int_val(arg4); + + if ((dfield_get_len(que_node_get_val(arg1)) < len1 + len2) + || (dfield_get_len(que_node_get_val(arg2)) < len2)) { + + ut_error; + } + + ut_memcpy(str1 + len1, str2, len2); +} + +/********************************************************************* +Evaluates an instr-function node. */ +static +void +eval_instr( +/*=======*/ + func_node_t* func_node) /* in: function node */ +{ + que_node_t* arg1; + que_node_t* arg2; + dfield_t* dfield1; + dfield_t* dfield2; + lint int_val; + byte* str1; + byte* str2; + byte match_char; + ulint len1; + ulint len2; + ulint i; + ulint j; + + arg1 = func_node->args; + arg2 = que_node_get_next(arg1); + + dfield1 = que_node_get_val(arg1); + dfield2 = que_node_get_val(arg2); + + str1 = dfield_get_data(dfield1); + str2 = dfield_get_data(dfield2); + + len1 = dfield_get_len(dfield1); + len2 = dfield_get_len(dfield2); + + if (len2 == 0) { + ut_error; + } + + match_char = str2[0]; + + for (i = 0; i < len1; i++) { + /* In this outer loop, the number of matched characters is 0 */ + + if (str1[i] == match_char) { + + if (i + len2 > len1) { + + break; + } + + for (j = 1;; j++) { + /* We have already matched j characters */ + + if (j == len2) { + int_val = i + 1; + + goto match_found; + } + + if (str1[i + j] != str2[j]) { + + break; + } + } + } + } + + int_val = 0; + +match_found: + eval_node_set_int_val(func_node, int_val); +} + +/********************************************************************* +Evaluates a predefined function node. */ +UNIV_INLINE +void +eval_binary_to_number( +/*==================*/ + func_node_t* func_node) /* in: function node */ +{ + que_node_t* arg1; + dfield_t* dfield; + byte* str1; + byte* str2; + ulint len1; + ulint int_val; + + arg1 = func_node->args; + + dfield = que_node_get_val(arg1); + + str1 = dfield_get_data(dfield); + len1 = dfield_get_len(dfield); + + if (len1 > 4) { + ut_error; + } + + if (len1 == 4) { + str2 = str1; + } else { + int_val = 0; + str2 = (byte*)&int_val; + + ut_memcpy(str2 + (4 - len1), str1, len1); + } + + eval_node_copy_and_alloc_val(func_node, str2, 4); +} + +/********************************************************************* +Evaluates a predefined function node. */ +static +void +eval_concat( +/*========*/ + func_node_t* func_node) /* in: function node */ +{ + que_node_t* arg; + dfield_t* dfield; + byte* data; + ulint len; + ulint len1; + + arg = func_node->args; + len = 0; + + while (arg) { + len1 = dfield_get_len(que_node_get_val(arg)); + + len += len1; + + arg = que_node_get_next(arg); + } + + data = eval_node_ensure_val_buf(func_node, len); + + arg = func_node->args; + len = 0; + + while (arg) { + dfield = que_node_get_val(arg); + len1 = dfield_get_len(dfield); + + ut_memcpy(data + len, dfield_get_data(dfield), len1); + + len += len1; + + arg = que_node_get_next(arg); + } +} + +/********************************************************************* +Evaluates a predefined function node. If the first argument is an integer, +this function looks at the second argument which is the integer length in +bytes, and converts the integer to a VARCHAR. +If the first argument is of some other type, this function converts it to +BINARY. */ +UNIV_INLINE +void +eval_to_binary( +/*===========*/ + func_node_t* func_node) /* in: function node */ +{ + que_node_t* arg1; + que_node_t* arg2; + dfield_t* dfield; + byte* str1; + ulint len; + ulint len1; + + arg1 = func_node->args; + + str1 = dfield_get_data(que_node_get_val(arg1)); + + if (dtype_get_mtype(que_node_get_data_type(arg1)) != DATA_INT) { + + len = dfield_get_len(que_node_get_val(arg1)); + + dfield = que_node_get_val(func_node); + + dfield_set_data(dfield, str1, len); + + return; + } + + arg2 = que_node_get_next(arg1); + + len1 = (ulint)eval_node_get_int_val(arg2); + + if (len1 > 4) { + + ut_error; + } + + dfield = que_node_get_val(func_node); + + dfield_set_data(dfield, str1 + (4 - len1), len1); +} + +/********************************************************************* +Evaluates a predefined function node. */ +UNIV_INLINE +void +eval_predefined( +/*============*/ + func_node_t* func_node) /* in: function node */ +{ + que_node_t* arg1; + lint int_val; + byte* data; + int func; + + func = func_node->func; + + arg1 = func_node->args; + + if (func == PARS_LENGTH_TOKEN) { + + int_val = (lint)dfield_get_len(que_node_get_val(arg1)); + + } else if (func == PARS_TO_CHAR_TOKEN) { + + /* Convert number to character string as a + signed decimal integer. */ + + ulint uint_val; + int int_len; + + int_val = eval_node_get_int_val(arg1); + + /* Determine the length of the string. */ + + if (int_val == 0) { + int_len = 1; /* the number 0 occupies 1 byte */ + } else { + int_len = 0; + if (int_val < 0) { + uint_val = ((ulint) -int_val - 1) + 1; + int_len++; /* reserve space for minus sign */ + } else { + uint_val = (ulint) int_val; + } + for (; uint_val > 0; int_len++) { + uint_val /= 10; + } + } + + /* allocate the string */ + data = eval_node_ensure_val_buf(func_node, int_len + 1); + + /* add terminating NUL character */ + data[int_len] = 0; + + /* convert the number */ + + if (int_val == 0) { + data[0] = '0'; + } else { + int tmp; + if (int_val < 0) { + data[0] = '-'; /* preceding minus sign */ + uint_val = ((ulint) -int_val - 1) + 1; + } else { + uint_val = (ulint) int_val; + } + for (tmp = int_len; uint_val > 0; uint_val /= 10) { + data[--tmp] = (byte) + ('0' + (byte)(uint_val % 10)); + } + } + + dfield_set_len(que_node_get_val(func_node), int_len); + + return; + + } else if (func == PARS_TO_NUMBER_TOKEN) { + + int_val = atoi((char*) + dfield_get_data(que_node_get_val(arg1))); + + } else if (func == PARS_SYSDATE_TOKEN) { + int_val = (lint)ut_time(); + } else { + eval_predefined_2(func_node); + + return; + } + + eval_node_set_int_val(func_node, int_val); +} + +/********************************************************************* +Evaluates a function node. */ +UNIV_INTERN +void +eval_func( +/*======*/ + func_node_t* func_node) /* in: function node */ +{ + que_node_t* arg; + ulint class; + ulint func; + + ut_ad(que_node_get_type(func_node) == QUE_NODE_FUNC); + + class = func_node->class; + func = func_node->func; + + arg = func_node->args; + + /* Evaluate first the argument list */ + while (arg) { + eval_exp(arg); + + /* The functions are not defined for SQL null argument + values, except for eval_cmp and notfound */ + + if (dfield_is_null(que_node_get_val(arg)) + && (class != PARS_FUNC_CMP) + && (func != PARS_NOTFOUND_TOKEN) + && (func != PARS_PRINTF_TOKEN)) { + ut_error; + } + + arg = que_node_get_next(arg); + } + + if (class == PARS_FUNC_CMP) { + eval_cmp(func_node); + } else if (class == PARS_FUNC_ARITH) { + eval_arith(func_node); + } else if (class == PARS_FUNC_AGGREGATE) { + eval_aggregate(func_node); + } else if (class == PARS_FUNC_PREDEFINED) { + + if (func == PARS_NOTFOUND_TOKEN) { + eval_notfound(func_node); + } else if (func == PARS_SUBSTR_TOKEN) { + eval_substr(func_node); + } else if (func == PARS_REPLSTR_TOKEN) { + eval_replstr(func_node); + } else if (func == PARS_INSTR_TOKEN) { + eval_instr(func_node); + } else if (func == PARS_BINARY_TO_NUMBER_TOKEN) { + eval_binary_to_number(func_node); + } else if (func == PARS_CONCAT_TOKEN) { + eval_concat(func_node); + } else if (func == PARS_TO_BINARY_TOKEN) { + eval_to_binary(func_node); + } else { + eval_predefined(func_node); + } + } else { + ut_ad(class == PARS_FUNC_LOGICAL); + + eval_logical(func_node); + } +} diff --git a/storage/xtradb/eval/eval0proc.c b/storage/xtradb/eval/eval0proc.c new file mode 100644 index 00000000000..9c7563e8c7d --- /dev/null +++ b/storage/xtradb/eval/eval0proc.c @@ -0,0 +1,294 @@ +/***************************************************************************** + +Copyright (c) 1998, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/****************************************************** +Executes SQL stored procedures and their control structures + +Created 1/20/1998 Heikki Tuuri +*******************************************************/ + +#include "eval0proc.h" + +#ifdef UNIV_NONINL +#include "eval0proc.ic" +#endif + +/************************************************************************** +Performs an execution step of an if-statement node. */ +UNIV_INTERN +que_thr_t* +if_step( +/*====*/ + /* out: query thread to run next or NULL */ + que_thr_t* thr) /* in: query thread */ +{ + if_node_t* node; + elsif_node_t* elsif_node; + + ut_ad(thr); + + node = thr->run_node; + ut_ad(que_node_get_type(node) == QUE_NODE_IF); + + if (thr->prev_node == que_node_get_parent(node)) { + + /* Evaluate the condition */ + + eval_exp(node->cond); + + if (eval_node_get_ibool_val(node->cond)) { + + /* The condition evaluated to TRUE: start execution + from the first statement in the statement list */ + + thr->run_node = node->stat_list; + + } else if (node->else_part) { + thr->run_node = node->else_part; + + } else if (node->elsif_list) { + elsif_node = node->elsif_list; + + for (;;) { + eval_exp(elsif_node->cond); + + if (eval_node_get_ibool_val( + elsif_node->cond)) { + + /* The condition evaluated to TRUE: + start execution from the first + statement in the statement list */ + + thr->run_node = elsif_node->stat_list; + + break; + } + + elsif_node = que_node_get_next(elsif_node); + + if (elsif_node == NULL) { + thr->run_node = NULL; + + break; + } + } + } else { + thr->run_node = NULL; + } + } else { + /* Move to the next statement */ + ut_ad(que_node_get_next(thr->prev_node) == NULL); + + thr->run_node = NULL; + } + + if (thr->run_node == NULL) { + thr->run_node = que_node_get_parent(node); + } + + return(thr); +} + +/************************************************************************** +Performs an execution step of a while-statement node. */ +UNIV_INTERN +que_thr_t* +while_step( +/*=======*/ + /* out: query thread to run next or NULL */ + que_thr_t* thr) /* in: query thread */ +{ + while_node_t* node; + + ut_ad(thr); + + node = thr->run_node; + ut_ad(que_node_get_type(node) == QUE_NODE_WHILE); + + ut_ad((thr->prev_node == que_node_get_parent(node)) + || (que_node_get_next(thr->prev_node) == NULL)); + + /* Evaluate the condition */ + + eval_exp(node->cond); + + if (eval_node_get_ibool_val(node->cond)) { + + /* The condition evaluated to TRUE: start execution + from the first statement in the statement list */ + + thr->run_node = node->stat_list; + } else { + thr->run_node = que_node_get_parent(node); + } + + return(thr); +} + +/************************************************************************** +Performs an execution step of an assignment statement node. */ +UNIV_INTERN +que_thr_t* +assign_step( +/*========*/ + /* out: query thread to run next or NULL */ + que_thr_t* thr) /* in: query thread */ +{ + assign_node_t* node; + + ut_ad(thr); + + node = thr->run_node; + ut_ad(que_node_get_type(node) == QUE_NODE_ASSIGNMENT); + + /* Evaluate the value to assign */ + + eval_exp(node->val); + + eval_node_copy_val(node->var->alias, node->val); + + thr->run_node = que_node_get_parent(node); + + return(thr); +} + +/************************************************************************** +Performs an execution step of a for-loop node. */ +UNIV_INTERN +que_thr_t* +for_step( +/*=====*/ + /* out: query thread to run next or NULL */ + que_thr_t* thr) /* in: query thread */ +{ + for_node_t* node; + que_node_t* parent; + lint loop_var_value; + + ut_ad(thr); + + node = thr->run_node; + + ut_ad(que_node_get_type(node) == QUE_NODE_FOR); + + parent = que_node_get_parent(node); + + if (thr->prev_node != parent) { + + /* Move to the next statement */ + thr->run_node = que_node_get_next(thr->prev_node); + + if (thr->run_node != NULL) { + + return(thr); + } + + /* Increment the value of loop_var */ + + loop_var_value = 1 + eval_node_get_int_val(node->loop_var); + } else { + /* Initialize the loop */ + + eval_exp(node->loop_start_limit); + eval_exp(node->loop_end_limit); + + loop_var_value = eval_node_get_int_val(node->loop_start_limit); + + node->loop_end_value + = (int) eval_node_get_int_val(node->loop_end_limit); + } + + /* Check if we should do another loop */ + + if (loop_var_value > node->loop_end_value) { + + /* Enough loops done */ + + thr->run_node = parent; + } else { + eval_node_set_int_val(node->loop_var, loop_var_value); + + thr->run_node = node->stat_list; + } + + return(thr); +} + +/************************************************************************** +Performs an execution step of an exit statement node. */ +UNIV_INTERN +que_thr_t* +exit_step( +/*======*/ + /* out: query thread to run next or NULL */ + que_thr_t* thr) /* in: query thread */ +{ + exit_node_t* node; + que_node_t* loop_node; + + ut_ad(thr); + + node = thr->run_node; + + ut_ad(que_node_get_type(node) == QUE_NODE_EXIT); + + /* Loops exit by setting thr->run_node as the loop node's parent, so + find our containing loop node and get its parent. */ + + loop_node = que_node_get_containing_loop_node(node); + + /* If someone uses an EXIT statement outside of a loop, this will + trigger. */ + ut_a(loop_node); + + thr->run_node = que_node_get_parent(loop_node); + + return(thr); +} + +/************************************************************************** +Performs an execution step of a return-statement node. */ +UNIV_INTERN +que_thr_t* +return_step( +/*========*/ + /* out: query thread to run next or NULL */ + que_thr_t* thr) /* in: query thread */ +{ + return_node_t* node; + que_node_t* parent; + + ut_ad(thr); + + node = thr->run_node; + + ut_ad(que_node_get_type(node) == QUE_NODE_RETURN); + + parent = node; + + while (que_node_get_type(parent) != QUE_NODE_PROC) { + + parent = que_node_get_parent(parent); + } + + ut_a(parent); + + thr->run_node = que_node_get_parent(parent); + + return(thr); +} diff --git a/storage/xtradb/fil/fil0fil.c b/storage/xtradb/fil/fil0fil.c new file mode 100644 index 00000000000..b63450b00cf --- /dev/null +++ b/storage/xtradb/fil/fil0fil.c @@ -0,0 +1,5090 @@ +/***************************************************************************** + +Copyright (c) 1995, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/****************************************************** +The tablespace memory cache + +Created 10/25/1995 Heikki Tuuri +*******************************************************/ + +#include "fil0fil.h" + +#include "mem0mem.h" +#include "sync0sync.h" +#include "hash0hash.h" +#include "os0file.h" +#include "os0sync.h" +#include "mach0data.h" +#include "ibuf0ibuf.h" +#include "buf0buf.h" +#include "buf0flu.h" +#include "buf0lru.h" +#include "log0recv.h" +#include "fsp0fsp.h" +#include "srv0srv.h" +#include "srv0start.h" +#include "mtr0mtr.h" +#include "mtr0log.h" +#include "dict0dict.h" +#include "page0zip.h" +#include "trx0trx.h" +#include "trx0sys.h" +#include "pars0pars.h" +#include "row0mysql.h" + + +/* + IMPLEMENTATION OF THE TABLESPACE MEMORY CACHE + ============================================= + +The tablespace cache is responsible for providing fast read/write access to +tablespaces and logs of the database. File creation and deletion is done +in other modules which know more of the logic of the operation, however. + +A tablespace consists of a chain of files. The size of the files does not +have to be divisible by the database block size, because we may just leave +the last incomplete block unused. When a new file is appended to the +tablespace, the maximum size of the file is also specified. At the moment, +we think that it is best to extend the file to its maximum size already at +the creation of the file, because then we can avoid dynamically extending +the file when more space is needed for the tablespace. + +A block's position in the tablespace is specified with a 32-bit unsigned +integer. The files in the chain are thought to be catenated, and the block +corresponding to an address n is the nth block in the catenated file (where +the first block is named the 0th block, and the incomplete block fragments +at the end of files are not taken into account). A tablespace can be extended +by appending a new file at the end of the chain. + +Our tablespace concept is similar to the one of Oracle. + +To acquire more speed in disk transfers, a technique called disk striping is +sometimes used. This means that logical block addresses are divided in a +round-robin fashion across several disks. Windows NT supports disk striping, +so there we do not need to support it in the database. Disk striping is +implemented in hardware in RAID disks. We conclude that it is not necessary +to implement it in the database. Oracle 7 does not support disk striping, +either. + +Another trick used at some database sites is replacing tablespace files by +raw disks, that is, the whole physical disk drive, or a partition of it, is +opened as a single file, and it is accessed through byte offsets calculated +from the start of the disk or the partition. This is recommended in some +books on database tuning to achieve more speed in i/o. Using raw disk +certainly prevents the OS from fragmenting disk space, but it is not clear +if it really adds speed. We measured on the Pentium 100 MHz + NT + NTFS file +system + EIDE Conner disk only a negligible difference in speed when reading +from a file, versus reading from a raw disk. + +To have fast access to a tablespace or a log file, we put the data structures +to a hash table. Each tablespace and log file is given an unique 32-bit +identifier. + +Some operating systems do not support many open files at the same time, +though NT seems to tolerate at least 900 open files. Therefore, we put the +open files in an LRU-list. If we need to open another file, we may close the +file at the end of the LRU-list. When an i/o-operation is pending on a file, +the file cannot be closed. We take the file nodes with pending i/o-operations +out of the LRU-list and keep a count of pending operations. When an operation +completes, we decrement the count and return the file node to the LRU-list if +the count drops to zero. */ + +/* When mysqld is run, the default directory "." is the mysqld datadir, +but in the MySQL Embedded Server Library and ibbackup it is not the default +directory, and we must set the base file path explicitly */ +UNIV_INTERN const char* fil_path_to_mysql_datadir = "."; + +/* The number of fsyncs done to the log */ +UNIV_INTERN ulint fil_n_log_flushes = 0; + +UNIV_INTERN ulint fil_n_pending_log_flushes = 0; +UNIV_INTERN ulint fil_n_pending_tablespace_flushes = 0; + +/* Null file address */ +UNIV_INTERN fil_addr_t fil_addr_null = {FIL_NULL, 0}; + +/* File node of a tablespace or the log data space */ +struct fil_node_struct { + fil_space_t* space; /* backpointer to the space where this node + belongs */ + char* name; /* path to the file */ + ibool open; /* TRUE if file open */ + os_file_t handle; /* OS handle to the file, if file open */ + ibool is_raw_disk;/* TRUE if the 'file' is actually a raw + device or a raw disk partition */ + ulint size; /* size of the file in database pages, 0 if + not known yet; the possible last incomplete + megabyte may be ignored if space == 0 */ + ulint n_pending; + /* count of pending i/o's on this file; + closing of the file is not allowed if + this is > 0 */ + ulint n_pending_flushes; + /* count of pending flushes on this file; + closing of the file is not allowed if + this is > 0 */ + ib_int64_t modification_counter;/* when we write to the file we + increment this by one */ + ib_int64_t flush_counter;/* up to what modification_counter value + we have flushed the modifications to disk */ + UT_LIST_NODE_T(fil_node_t) chain; + /* link field for the file chain */ + UT_LIST_NODE_T(fil_node_t) LRU; + /* link field for the LRU list */ + ulint magic_n; +}; + +#define FIL_NODE_MAGIC_N 89389 + +/* Tablespace or log data space: let us call them by a common name space */ +struct fil_space_struct { + char* name; /* space name = the path to the first file in + it */ + ulint id; /* space id */ + ib_int64_t tablespace_version; + /* in DISCARD/IMPORT this timestamp is used to + check if we should ignore an insert buffer + merge request for a page because it actually + was for the previous incarnation of the + space */ + ibool mark; /* this is set to TRUE at database startup if + the space corresponds to a table in the InnoDB + data dictionary; so we can print a warning of + orphaned tablespaces */ + ibool stop_ios;/* TRUE if we want to rename the .ibd file of + tablespace and want to stop temporarily + posting of new i/o requests on the file */ + ibool stop_ibuf_merges; + /* we set this TRUE when we start deleting a + single-table tablespace */ + ibool is_being_deleted; + /* this is set to TRUE when we start + deleting a single-table tablespace and its + file; when this flag is set no further i/o + or flush requests can be placed on this space, + though there may be such requests still being + processed on this space */ + ulint purpose;/* FIL_TABLESPACE, FIL_LOG, or FIL_ARCH_LOG */ + UT_LIST_BASE_NODE_T(fil_node_t) chain; + /* base node for the file chain */ + ulint size; /* space size in pages; 0 if a single-table + tablespace whose size we do not know yet; + last incomplete megabytes in data files may be + ignored if space == 0 */ + ulint flags; /* in: compressed page size + and file format, or 0 */ + ulint n_reserved_extents; + /* number of reserved free extents for + ongoing operations like B-tree page split */ + ulint n_pending_flushes; /* this is > 0 when flushing + the tablespace to disk; dropping of the + tablespace is forbidden if this is > 0 */ + ulint n_pending_ibuf_merges;/* this is > 0 when merging + insert buffer entries to a page so that we + may need to access the ibuf bitmap page in the + tablespade: dropping of the tablespace is + forbidden if this is > 0 */ + hash_node_t hash; /* hash chain node */ + hash_node_t name_hash;/* hash chain the name_hash table */ + rw_lock_t latch; /* latch protecting the file space storage + allocation */ + UT_LIST_NODE_T(fil_space_t) unflushed_spaces; + /* list of spaces with at least one unflushed + file we have written to */ + ibool is_in_unflushed_spaces; /* TRUE if this space is + currently in the list above */ + UT_LIST_NODE_T(fil_space_t) space_list; + /* list of all spaces */ + ulint magic_n; +}; + +#define FIL_SPACE_MAGIC_N 89472 + +/* The tablespace memory cache; also the totality of logs = the log data space, +is stored here; below we talk about tablespaces, but also the ib_logfiles +form a 'space' and it is handled here */ + +typedef struct fil_system_struct fil_system_t; +struct fil_system_struct { + mutex_t mutex; /* The mutex protecting the cache */ + hash_table_t* spaces; /* The hash table of spaces in the + system; they are hashed on the space + id */ + hash_table_t* name_hash; /* hash table based on the space + name */ + UT_LIST_BASE_NODE_T(fil_node_t) LRU; + /* base node for the LRU list of the + most recently used open files with no + pending i/o's; if we start an i/o on + the file, we first remove it from this + list, and return it to the start of + the list when the i/o ends; + log files and the system tablespace are + not put to this list: they are opened + after the startup, and kept open until + shutdown */ + UT_LIST_BASE_NODE_T(fil_space_t) unflushed_spaces; + /* base node for the list of those + tablespaces whose files contain + unflushed writes; those spaces have + at least one file node where + modification_counter > flush_counter */ + ulint n_open; /* number of files currently open */ + ulint max_n_open; /* n_open is not allowed to exceed + this */ + ib_int64_t modification_counter;/* when we write to a file we + increment this by one */ + ulint max_assigned_id;/* maximum space id in the existing + tables, or assigned during the time + mysqld has been up; at an InnoDB + startup we scan the data dictionary + and set here the maximum of the + space id's of the tables there */ + ib_int64_t tablespace_version; + /* a counter which is incremented for + every space object memory creation; + every space mem object gets a + 'timestamp' from this; in DISCARD/ + IMPORT this is used to check if we + should ignore an insert buffer merge + request */ + UT_LIST_BASE_NODE_T(fil_space_t) space_list; + /* list of all file spaces */ +}; + +/* The tablespace memory cache. This variable is NULL before the module is +initialized. */ +UNIV_INTERN fil_system_t* fil_system = NULL; + + +/************************************************************************ +NOTE: you must call fil_mutex_enter_and_prepare_for_io() first! + +Prepares a file node for i/o. Opens the file if it is closed. Updates the +pending i/o's field in the node and the system appropriately. Takes the node +off the LRU list if it is in the LRU list. The caller must hold the fil_sys +mutex. */ +static +void +fil_node_prepare_for_io( +/*====================*/ + fil_node_t* node, /* in: file node */ + fil_system_t* system, /* in: tablespace memory cache */ + fil_space_t* space); /* in: space */ +/************************************************************************ +Updates the data structures when an i/o operation finishes. Updates the +pending i/o's field in the node appropriately. */ +static +void +fil_node_complete_io( +/*=================*/ + fil_node_t* node, /* in: file node */ + fil_system_t* system, /* in: tablespace memory cache */ + ulint type); /* in: OS_FILE_WRITE or OS_FILE_READ; marks + the node as modified if + type == OS_FILE_WRITE */ +/*********************************************************************** +Checks if a single-table tablespace for a given table name exists in the +tablespace memory cache. */ +static +ulint +fil_get_space_id_for_table( +/*=======================*/ + /* out: space id, ULINT_UNDEFINED if not + found */ + const char* name); /* in: table name in the standard + 'databasename/tablename' format */ +/************************************************************************ +Reads data from a space to a buffer. Remember that the possible incomplete +blocks at the end of file are ignored: they are not taken into account when +calculating the byte offset within a space. */ +UNIV_INLINE +ulint +fil_read( +/*=====*/ + /* out: DB_SUCCESS, or DB_TABLESPACE_DELETED + if we are trying to do i/o on a tablespace + which does not exist */ + ibool sync, /* in: TRUE if synchronous aio is desired */ + ulint space_id, /* in: space id */ + ulint zip_size, /* in: compressed page size in bytes; + 0 for uncompressed pages */ + ulint block_offset, /* in: offset in number of blocks */ + ulint byte_offset, /* in: remainder of offset in bytes; in aio + this must be divisible by the OS block size */ + ulint len, /* in: how many bytes to read; this must not + cross a file boundary; in aio this must be a + block size multiple */ + void* buf, /* in/out: buffer where to store data read; + in aio this must be appropriately aligned */ + void* message) /* in: message for aio handler if non-sync + aio used, else ignored */ +{ + return(fil_io(OS_FILE_READ, sync, space_id, zip_size, block_offset, + byte_offset, len, buf, message)); +} + +/************************************************************************ +Writes data to a space from a buffer. Remember that the possible incomplete +blocks at the end of file are ignored: they are not taken into account when +calculating the byte offset within a space. */ +UNIV_INLINE +ulint +fil_write( +/*======*/ + /* out: DB_SUCCESS, or DB_TABLESPACE_DELETED + if we are trying to do i/o on a tablespace + which does not exist */ + ibool sync, /* in: TRUE if synchronous aio is desired */ + ulint space_id, /* in: space id */ + ulint zip_size, /* in: compressed page size in bytes; + 0 for uncompressed pages */ + ulint block_offset, /* in: offset in number of blocks */ + ulint byte_offset, /* in: remainder of offset in bytes; in aio + this must be divisible by the OS block size */ + ulint len, /* in: how many bytes to write; this must + not cross a file boundary; in aio this must + be a block size multiple */ + void* buf, /* in: buffer from which to write; in aio + this must be appropriately aligned */ + void* message) /* in: message for aio handler if non-sync + aio used, else ignored */ +{ + return(fil_io(OS_FILE_WRITE, sync, space_id, zip_size, block_offset, + byte_offset, len, buf, message)); +} + +/*********************************************************************** +Returns the table space by a given id, NULL if not found. */ +UNIV_INLINE +fil_space_t* +fil_space_get_by_id( +/*================*/ + ulint id) /* in: space id */ +{ + fil_space_t* space; + + ut_ad(mutex_own(&fil_system->mutex)); + + HASH_SEARCH(hash, fil_system->spaces, id, + fil_space_t*, space, + ut_ad(space->magic_n == FIL_SPACE_MAGIC_N), + space->id == id); + + return(space); +} + +/*********************************************************************** +Returns the table space by a given name, NULL if not found. */ +UNIV_INLINE +fil_space_t* +fil_space_get_by_name( +/*==================*/ + const char* name) /* in: space name */ +{ + fil_space_t* space; + ulint fold; + + ut_ad(mutex_own(&fil_system->mutex)); + + fold = ut_fold_string(name); + + HASH_SEARCH(name_hash, fil_system->name_hash, fold, + fil_space_t*, space, + ut_ad(space->magic_n == FIL_SPACE_MAGIC_N), + !strcmp(name, space->name)); + + return(space); +} + +/*********************************************************************** +Returns the version number of a tablespace, -1 if not found. */ +UNIV_INTERN +ib_int64_t +fil_space_get_version( +/*==================*/ + /* out: version number, -1 if the tablespace does not + exist in the memory cache */ + ulint id) /* in: space id */ +{ + fil_system_t* system = fil_system; + fil_space_t* space; + ib_int64_t version = -1; + + ut_ad(system); + + mutex_enter(&(system->mutex)); + + space = fil_space_get_by_id(id); + + if (space) { + version = space->tablespace_version; + } + + mutex_exit(&(system->mutex)); + + return(version); +} + +/*********************************************************************** +Returns the latch of a file space. */ +UNIV_INTERN +rw_lock_t* +fil_space_get_latch( +/*================*/ + /* out: latch protecting storage allocation */ + ulint id, /* in: space id */ + ulint* flags) /* out: tablespace flags */ +{ + fil_system_t* system = fil_system; + fil_space_t* space; + + ut_ad(system); + + mutex_enter(&(system->mutex)); + + space = fil_space_get_by_id(id); + + ut_a(space); + + if (flags) { + *flags = space->flags; + } + + mutex_exit(&(system->mutex)); + + return(&(space->latch)); +} + +/*********************************************************************** +Returns the type of a file space. */ +UNIV_INTERN +ulint +fil_space_get_type( +/*===============*/ + /* out: FIL_TABLESPACE or FIL_LOG */ + ulint id) /* in: space id */ +{ + fil_system_t* system = fil_system; + fil_space_t* space; + + ut_ad(system); + + mutex_enter(&(system->mutex)); + + space = fil_space_get_by_id(id); + + ut_a(space); + + mutex_exit(&(system->mutex)); + + return(space->purpose); +} + +/************************************************************************** +Checks if all the file nodes in a space are flushed. The caller must hold +the fil_system mutex. */ +static +ibool +fil_space_is_flushed( +/*=================*/ + /* out: TRUE if all are flushed */ + fil_space_t* space) /* in: space */ +{ + fil_node_t* node; + + ut_ad(mutex_own(&(fil_system->mutex))); + + node = UT_LIST_GET_FIRST(space->chain); + + while (node) { + if (node->modification_counter > node->flush_counter) { + + return(FALSE); + } + + node = UT_LIST_GET_NEXT(chain, node); + } + + return(TRUE); +} + +/*********************************************************************** +Appends a new file to the chain of files of a space. File must be closed. */ +UNIV_INTERN +void +fil_node_create( +/*============*/ + const char* name, /* in: file name (file must be closed) */ + ulint size, /* in: file size in database blocks, rounded + downwards to an integer */ + ulint id, /* in: space id where to append */ + ibool is_raw) /* in: TRUE if a raw device or + a raw disk partition */ +{ + fil_system_t* system = fil_system; + fil_node_t* node; + fil_space_t* space; + + ut_a(system); + ut_a(name); + + mutex_enter(&(system->mutex)); + + node = mem_alloc(sizeof(fil_node_t)); + + node->name = mem_strdup(name); + node->open = FALSE; + + ut_a(!is_raw || srv_start_raw_disk_in_use); + + node->is_raw_disk = is_raw; + node->size = size; + node->magic_n = FIL_NODE_MAGIC_N; + node->n_pending = 0; + node->n_pending_flushes = 0; + + node->modification_counter = 0; + node->flush_counter = 0; + + space = fil_space_get_by_id(id); + + if (!space) { + ut_print_timestamp(stderr); + fprintf(stderr, + " InnoDB: Error: Could not find tablespace %lu for\n" + "InnoDB: file ", (ulong) id); + ut_print_filename(stderr, name); + fputs(" in the tablespace memory cache.\n", stderr); + mem_free(node->name); + + mem_free(node); + + mutex_exit(&(system->mutex)); + + return; + } + + space->size += size; + + node->space = space; + + UT_LIST_ADD_LAST(chain, space->chain, node); + + mutex_exit(&(system->mutex)); +} + +/************************************************************************ +Opens a the file of a node of a tablespace. The caller must own the fil_system +mutex. */ +static +void +fil_node_open_file( +/*===============*/ + fil_node_t* node, /* in: file node */ + fil_system_t* system, /* in: tablespace memory cache */ + fil_space_t* space) /* in: space */ +{ + ib_int64_t size_bytes; + ulint size_low; + ulint size_high; + ibool ret; + ibool success; +#ifndef UNIV_HOTBACKUP + byte* buf2; + byte* page; + ulint space_id; + ulint flags; +#endif /* !UNIV_HOTBACKUP */ + + ut_ad(mutex_own(&(system->mutex))); + ut_a(node->n_pending == 0); + ut_a(node->open == FALSE); + + if (node->size == 0) { + /* It must be a single-table tablespace and we do not know the + size of the file yet. First we open the file in the normal + mode, no async I/O here, for simplicity. Then do some checks, + and close the file again. + NOTE that we could not use the simple file read function + os_file_read() in Windows to read from a file opened for + async I/O! */ + + node->handle = os_file_create_simple_no_error_handling( + node->name, OS_FILE_OPEN, OS_FILE_READ_ONLY, &success); + if (!success) { + /* The following call prints an error message */ + os_file_get_last_error(TRUE); + + ut_print_timestamp(stderr); + + fprintf(stderr, + " InnoDB: Fatal error: cannot open %s\n." + "InnoDB: Have you deleted .ibd files" + " under a running mysqld server?\n", + node->name); + ut_a(0); + } + + os_file_get_size(node->handle, &size_low, &size_high); + + size_bytes = (((ib_int64_t)size_high) << 32) + + (ib_int64_t)size_low; +#ifdef UNIV_HOTBACKUP + node->size = (ulint) (size_bytes / UNIV_PAGE_SIZE); + /* TODO: adjust to zip_size, like below? */ +#else + ut_a(space->purpose != FIL_LOG); + ut_a(space->id != 0); + + if (size_bytes < FIL_IBD_FILE_INITIAL_SIZE * UNIV_PAGE_SIZE) { + fprintf(stderr, + "InnoDB: Error: the size of single-table" + " tablespace file %s\n" + "InnoDB: is only %lu %lu," + " should be at least %lu!\n", + node->name, + (ulong) size_high, + (ulong) size_low, + (ulong) (FIL_IBD_FILE_INITIAL_SIZE + * UNIV_PAGE_SIZE)); + + ut_a(0); + } + + /* Read the first page of the tablespace */ + + buf2 = ut_malloc(2 * UNIV_PAGE_SIZE); + /* Align the memory for file i/o if we might have O_DIRECT + set */ + page = ut_align(buf2, UNIV_PAGE_SIZE); + + success = os_file_read(node->handle, page, 0, 0, + UNIV_PAGE_SIZE); + space_id = fsp_header_get_space_id(page); + flags = fsp_header_get_flags(page); + + ut_free(buf2); + + /* Close the file now that we have read the space id from it */ + + os_file_close(node->handle); + + if (UNIV_UNLIKELY(space_id != space->id)) { + fprintf(stderr, + "InnoDB: Error: tablespace id is %lu" + " in the data dictionary\n" + "InnoDB: but in file %s it is %lu!\n", + space->id, node->name, space_id); + + ut_error; + } + + if (UNIV_UNLIKELY(space_id == ULINT_UNDEFINED + || space_id == 0)) { + fprintf(stderr, + "InnoDB: Error: tablespace id %lu" + " in file %s is not sensible\n", + (ulong) space_id, node->name); + + ut_error; + } + + if (UNIV_UNLIKELY(space->flags != flags)) { + fprintf(stderr, + "InnoDB: Error: table flags are %lx" + " in the data dictionary\n" + "InnoDB: but the flags in file %s are %lx!\n", + space->flags, node->name, flags); + + ut_error; + } + + if (size_bytes >= 1024 * 1024) { + /* Truncate the size to whole megabytes. */ + size_bytes = ut_2pow_round(size_bytes, 1024 * 1024); + } + + if (!(flags & DICT_TF_ZSSIZE_MASK)) { + node->size = (ulint) (size_bytes / UNIV_PAGE_SIZE); + } else { + node->size = (ulint) + (size_bytes + / dict_table_flags_to_zip_size(flags)); + } +#endif + space->size += node->size; + } + + /* printf("Opening file %s\n", node->name); */ + + /* Open the file for reading and writing, in Windows normally in the + unbuffered async I/O mode, though global variables may make + os_file_create() to fall back to the normal file I/O mode. */ + + if (space->purpose == FIL_LOG) { + node->handle = os_file_create(node->name, OS_FILE_OPEN, + OS_FILE_AIO, OS_LOG_FILE, &ret); + } else if (node->is_raw_disk) { + node->handle = os_file_create(node->name, + OS_FILE_OPEN_RAW, + OS_FILE_AIO, OS_DATA_FILE, &ret); + } else { + node->handle = os_file_create(node->name, OS_FILE_OPEN, + OS_FILE_AIO, OS_DATA_FILE, &ret); + } + + ut_a(ret); + + node->open = TRUE; + + system->n_open++; + + if (space->purpose == FIL_TABLESPACE && space->id != 0) { + /* Put the node to the LRU list */ + UT_LIST_ADD_FIRST(LRU, system->LRU, node); + } +} + +/************************************************************************** +Closes a file. */ +static +void +fil_node_close_file( +/*================*/ + fil_node_t* node, /* in: file node */ + fil_system_t* system) /* in: tablespace memory cache */ +{ + ibool ret; + + ut_ad(node && system); + ut_ad(mutex_own(&(system->mutex))); + ut_a(node->open); + ut_a(node->n_pending == 0); + ut_a(node->n_pending_flushes == 0); + ut_a(node->modification_counter == node->flush_counter); + + ret = os_file_close(node->handle); + ut_a(ret); + + /* printf("Closing file %s\n", node->name); */ + + node->open = FALSE; + ut_a(system->n_open > 0); + system->n_open--; + + if (node->space->purpose == FIL_TABLESPACE && node->space->id != 0) { + ut_a(UT_LIST_GET_LEN(system->LRU) > 0); + + /* The node is in the LRU list, remove it */ + UT_LIST_REMOVE(LRU, system->LRU, node); + } +} + +/************************************************************************ +Tries to close a file in the LRU list. The caller must hold the fil_sys +mutex. */ +static +ibool +fil_try_to_close_file_in_LRU( +/*=========================*/ + /* out: TRUE if success, FALSE if should retry + later; since i/o's generally complete in < + 100 ms, and as InnoDB writes at most 128 pages + from the buffer pool in a batch, and then + immediately flushes the files, there is a good + chance that the next time we find a suitable + node from the LRU list */ + ibool print_info) /* in: if TRUE, prints information why it + cannot close a file */ +{ + fil_system_t* system = fil_system; + fil_node_t* node; + + ut_ad(mutex_own(&(system->mutex))); + + node = UT_LIST_GET_LAST(system->LRU); + + if (print_info) { + fprintf(stderr, + "InnoDB: fil_sys open file LRU len %lu\n", + (ulong) UT_LIST_GET_LEN(system->LRU)); + } + + while (node != NULL) { + if (node->modification_counter == node->flush_counter + && node->n_pending_flushes == 0) { + + fil_node_close_file(node, system); + + return(TRUE); + } + + if (print_info && node->n_pending_flushes > 0) { + fputs("InnoDB: cannot close file ", stderr); + ut_print_filename(stderr, node->name); + fprintf(stderr, ", because n_pending_flushes %lu\n", + (ulong) node->n_pending_flushes); + } + + if (print_info + && node->modification_counter != node->flush_counter) { + fputs("InnoDB: cannot close file ", stderr); + ut_print_filename(stderr, node->name); + fprintf(stderr, + ", because mod_count %ld != fl_count %ld\n", + (long) node->modification_counter, + (long) node->flush_counter); + } + + node = UT_LIST_GET_PREV(LRU, node); + } + + return(FALSE); +} + +/*********************************************************************** +Reserves the fil_system mutex and tries to make sure we can open at least one +file while holding it. This should be called before calling +fil_node_prepare_for_io(), because that function may need to open a file. */ +static +void +fil_mutex_enter_and_prepare_for_io( +/*===============================*/ + ulint space_id) /* in: space id */ +{ + fil_system_t* system = fil_system; + fil_space_t* space; + ibool success; + ibool print_info = FALSE; + ulint count = 0; + ulint count2 = 0; + + ut_ad(!mutex_own(&(system->mutex))); +retry: + mutex_enter(&(system->mutex)); + + if (space_id == 0 || space_id >= SRV_LOG_SPACE_FIRST_ID) { + /* We keep log files and system tablespace files always open; + this is important in preventing deadlocks in this module, as + a page read completion often performs another read from the + insert buffer. The insert buffer is in tablespace 0, and we + cannot end up waiting in this function. */ + + return; + } + + if (system->n_open < system->max_n_open) { + + return; + } + + space = fil_space_get_by_id(space_id); + + if (space != NULL && space->stop_ios) { + /* We are going to do a rename file and want to stop new i/o's + for a while */ + + if (count2 > 20000) { + fputs("InnoDB: Warning: tablespace ", stderr); + ut_print_filename(stderr, space->name); + fprintf(stderr, + " has i/o ops stopped for a long time %lu\n", + (ulong) count2); + } + + mutex_exit(&(system->mutex)); + + os_thread_sleep(20000); + + count2++; + + goto retry; + } + + /* If the file is already open, no need to do anything; if the space + does not exist, we handle the situation in the function which called + this function */ + + if (!space || UT_LIST_GET_FIRST(space->chain)->open) { + + return; + } + + if (count > 1) { + print_info = TRUE; + } + + /* Too many files are open, try to close some */ +close_more: + success = fil_try_to_close_file_in_LRU(print_info); + + if (success && system->n_open >= system->max_n_open) { + + goto close_more; + } + + if (system->n_open < system->max_n_open) { + /* Ok */ + + return; + } + + if (count >= 2) { + ut_print_timestamp(stderr); + fprintf(stderr, + " InnoDB: Warning: too many (%lu) files stay open" + " while the maximum\n" + "InnoDB: allowed value would be %lu.\n" + "InnoDB: You may need to raise the value of" + " innodb_max_files_open in\n" + "InnoDB: my.cnf.\n", + (ulong) system->n_open, (ulong) system->max_n_open); + + return; + } + + mutex_exit(&(system->mutex)); + +#ifndef UNIV_HOTBACKUP + /* Wake the i/o-handler threads to make sure pending i/o's are + performed */ + os_aio_simulated_wake_handler_threads(); + + os_thread_sleep(20000); +#endif + /* Flush tablespaces so that we can close modified files in the LRU + list */ + + fil_flush_file_spaces(FIL_TABLESPACE); + + count++; + + goto retry; +} + +/*********************************************************************** +Frees a file node object from a tablespace memory cache. */ +static +void +fil_node_free( +/*==========*/ + fil_node_t* node, /* in, own: file node */ + fil_system_t* system, /* in: tablespace memory cache */ + fil_space_t* space) /* in: space where the file node is chained */ +{ + ut_ad(node && system && space); + ut_ad(mutex_own(&(system->mutex))); + ut_a(node->magic_n == FIL_NODE_MAGIC_N); + ut_a(node->n_pending == 0); + + if (node->open) { + /* We fool the assertion in fil_node_close_file() to think + there are no unflushed modifications in the file */ + + node->modification_counter = node->flush_counter; + + if (space->is_in_unflushed_spaces + && fil_space_is_flushed(space)) { + + space->is_in_unflushed_spaces = FALSE; + + UT_LIST_REMOVE(unflushed_spaces, + system->unflushed_spaces, + space); + } + + fil_node_close_file(node, system); + } + + space->size -= node->size; + + UT_LIST_REMOVE(chain, space->chain, node); + + mem_free(node->name); + mem_free(node); +} + +#ifdef UNIV_LOG_ARCHIVE +/******************************************************************** +Drops files from the start of a file space, so that its size is cut by +the amount given. */ +UNIV_INTERN +void +fil_space_truncate_start( +/*=====================*/ + ulint id, /* in: space id */ + ulint trunc_len) /* in: truncate by this much; it is an error + if this does not equal to the combined size of + some initial files in the space */ +{ + fil_system_t* system = fil_system; + fil_node_t* node; + fil_space_t* space; + + mutex_enter(&(system->mutex)); + + space = fil_space_get_by_id(id); + + ut_a(space); + + while (trunc_len > 0) { + node = UT_LIST_GET_FIRST(space->chain); + + ut_a(node->size * UNIV_PAGE_SIZE <= trunc_len); + + trunc_len -= node->size * UNIV_PAGE_SIZE; + + fil_node_free(node, system, space); + } + + mutex_exit(&(system->mutex)); +} +#endif /* UNIV_LOG_ARCHIVE */ + +/*********************************************************************** +Creates a space memory object and puts it to the tablespace memory cache. If +there is an error, prints an error message to the .err log. */ +UNIV_INTERN +ibool +fil_space_create( +/*=============*/ + /* out: TRUE if success */ + const char* name, /* in: space name */ + ulint id, /* in: space id */ + ulint flags, /* in: compressed page size + and file format, or 0 */ + ulint purpose)/* in: FIL_TABLESPACE, or FIL_LOG if log */ +{ + fil_system_t* system = fil_system; + fil_space_t* space; + + /* The tablespace flags (FSP_SPACE_FLAGS) should be 0 for + ROW_FORMAT=COMPACT (table->flags == DICT_TF_COMPACT) and + ROW_FORMAT=REDUNDANT (table->flags == 0). For any other + format, the tablespace flags should equal table->flags. */ + ut_a(flags != DICT_TF_COMPACT); + +try_again: + /*printf( + "InnoDB: Adding tablespace %lu of name %s, purpose %lu\n", id, name, + purpose);*/ + + ut_a(system); + ut_a(name); + + mutex_enter(&(system->mutex)); + + space = fil_space_get_by_name(name); + + if (UNIV_LIKELY_NULL(space)) { + ulint namesake_id; + + ut_print_timestamp(stderr); + fprintf(stderr, + " InnoDB: Warning: trying to init to the" + " tablespace memory cache\n" + "InnoDB: a tablespace %lu of name ", (ulong) id); + ut_print_filename(stderr, name); + fprintf(stderr, ",\n" + "InnoDB: but a tablespace %lu of the same name\n" + "InnoDB: already exists in the" + " tablespace memory cache!\n", + (ulong) space->id); + + if (id == 0 || purpose != FIL_TABLESPACE) { + + mutex_exit(&(system->mutex)); + + return(FALSE); + } + + fprintf(stderr, + "InnoDB: We assume that InnoDB did a crash recovery," + " and you had\n" + "InnoDB: an .ibd file for which the table" + " did not exist in the\n" + "InnoDB: InnoDB internal data dictionary in the" + " ibdata files.\n" + "InnoDB: We assume that you later removed the" + " .ibd and .frm files,\n" + "InnoDB: and are now trying to recreate the table." + " We now remove the\n" + "InnoDB: conflicting tablespace object" + " from the memory cache and try\n" + "InnoDB: the init again.\n"); + + namesake_id = space->id; + + mutex_exit(&(system->mutex)); + + fil_space_free(namesake_id); + + goto try_again; + } + + space = fil_space_get_by_id(id); + + if (UNIV_LIKELY_NULL(space)) { + fprintf(stderr, + "InnoDB: Error: trying to add tablespace %lu" + " of name ", (ulong) id); + ut_print_filename(stderr, name); + fprintf(stderr, "\n" + "InnoDB: to the tablespace memory cache," + " but tablespace\n" + "InnoDB: %lu of name ", (ulong) space->id); + ut_print_filename(stderr, space->name); + fputs(" already exists in the tablespace\n" + "InnoDB: memory cache!\n", stderr); + + mutex_exit(&(system->mutex)); + + return(FALSE); + } + + space = mem_alloc(sizeof(fil_space_t)); + + space->name = mem_strdup(name); + space->id = id; + + system->tablespace_version++; + space->tablespace_version = system->tablespace_version; + space->mark = FALSE; + + if (purpose == FIL_TABLESPACE && id > system->max_assigned_id) { + system->max_assigned_id = id; + } + + space->stop_ios = FALSE; + space->stop_ibuf_merges = FALSE; + space->is_being_deleted = FALSE; + space->purpose = purpose; + space->size = 0; + space->flags = flags; + + space->n_reserved_extents = 0; + + space->n_pending_flushes = 0; + space->n_pending_ibuf_merges = 0; + + UT_LIST_INIT(space->chain); + space->magic_n = FIL_SPACE_MAGIC_N; + + rw_lock_create(&space->latch, SYNC_FSP); + + HASH_INSERT(fil_space_t, hash, system->spaces, id, space); + + HASH_INSERT(fil_space_t, name_hash, system->name_hash, + ut_fold_string(name), space); + space->is_in_unflushed_spaces = FALSE; + + UT_LIST_ADD_LAST(space_list, system->space_list, space); + + mutex_exit(&(system->mutex)); + + return(TRUE); +} + +/*********************************************************************** +Assigns a new space id for a new single-table tablespace. This works simply by +incrementing the global counter. If 4 billion id's is not enough, we may need +to recycle id's. */ +static +ulint +fil_assign_new_space_id(void) +/*=========================*/ + /* out: new tablespace id; ULINT_UNDEFINED if could + not assign an id */ +{ + fil_system_t* system = fil_system; + ulint id; + + mutex_enter(&(system->mutex)); + + system->max_assigned_id++; + + id = system->max_assigned_id; + + if (id > (SRV_LOG_SPACE_FIRST_ID / 2) && (id % 1000000UL == 0)) { + ut_print_timestamp(stderr); + fprintf(stderr, + "InnoDB: Warning: you are running out of new" + " single-table tablespace id's.\n" + "InnoDB: Current counter is %lu and it" + " must not exceed %lu!\n" + "InnoDB: To reset the counter to zero" + " you have to dump all your tables and\n" + "InnoDB: recreate the whole InnoDB installation.\n", + (ulong) id, + (ulong) SRV_LOG_SPACE_FIRST_ID); + } + + if (id >= SRV_LOG_SPACE_FIRST_ID) { + ut_print_timestamp(stderr); + fprintf(stderr, + "InnoDB: You have run out of single-table" + " tablespace id's!\n" + "InnoDB: Current counter is %lu.\n" + "InnoDB: To reset the counter to zero you" + " have to dump all your tables and\n" + "InnoDB: recreate the whole InnoDB installation.\n", + (ulong) id); + system->max_assigned_id--; + + id = ULINT_UNDEFINED; + } + + mutex_exit(&(system->mutex)); + + return(id); +} + +/*********************************************************************** +Frees a space object from the tablespace memory cache. Closes the files in +the chain but does not delete them. There must not be any pending i/o's or +flushes on the files. */ +UNIV_INTERN +ibool +fil_space_free( +/*===========*/ + /* out: TRUE if success */ + ulint id) /* in: space id */ +{ + fil_system_t* system = fil_system; + fil_space_t* space; + fil_space_t* namespace; + fil_node_t* fil_node; + + mutex_enter(&(system->mutex)); + + space = fil_space_get_by_id(id); + + if (!space) { + ut_print_timestamp(stderr); + fprintf(stderr, + " InnoDB: Error: trying to remove tablespace %lu" + " from the cache but\n" + "InnoDB: it is not there.\n", (ulong) id); + + mutex_exit(&(system->mutex)); + + return(FALSE); + } + + HASH_DELETE(fil_space_t, hash, system->spaces, id, space); + + namespace = fil_space_get_by_name(space->name); + ut_a(namespace); + ut_a(space == namespace); + + HASH_DELETE(fil_space_t, name_hash, system->name_hash, + ut_fold_string(space->name), space); + + if (space->is_in_unflushed_spaces) { + space->is_in_unflushed_spaces = FALSE; + + UT_LIST_REMOVE(unflushed_spaces, system->unflushed_spaces, + space); + } + + UT_LIST_REMOVE(space_list, system->space_list, space); + + ut_a(space->magic_n == FIL_SPACE_MAGIC_N); + ut_a(0 == space->n_pending_flushes); + + fil_node = UT_LIST_GET_FIRST(space->chain); + + while (fil_node != NULL) { + fil_node_free(fil_node, system, space); + + fil_node = UT_LIST_GET_FIRST(space->chain); + } + + ut_a(0 == UT_LIST_GET_LEN(space->chain)); + + mutex_exit(&(system->mutex)); + + rw_lock_free(&(space->latch)); + + mem_free(space->name); + mem_free(space); + + return(TRUE); +} + +#ifdef UNIV_HOTBACKUP +/*********************************************************************** +Returns the tablespace object for a given id, or NULL if not found from the +tablespace memory cache. */ +static +fil_space_t* +fil_get_space_for_id_low( +/*=====================*/ + /* out: tablespace object or NULL; NOTE that you must + own &(fil_system->mutex) to call this function! */ + ulint id) /* in: space id */ +{ + fil_system_t* system = fil_system; + fil_space_t* space; + + ut_ad(system); + + space = fil_space_get_by_id(id); + + return(space); +} +#endif + +/*********************************************************************** +Returns the size of the space in pages. The tablespace must be cached in the +memory cache. */ +UNIV_INTERN +ulint +fil_space_get_size( +/*===============*/ + /* out: space size, 0 if space not found */ + ulint id) /* in: space id */ +{ + fil_system_t* system = fil_system; + fil_node_t* node; + fil_space_t* space; + ulint size; + + ut_ad(system); + + fil_mutex_enter_and_prepare_for_io(id); + + space = fil_space_get_by_id(id); + + if (space == NULL) { + mutex_exit(&(system->mutex)); + + return(0); + } + + if (space->size == 0 && space->purpose == FIL_TABLESPACE) { + ut_a(id != 0); + + ut_a(1 == UT_LIST_GET_LEN(space->chain)); + + node = UT_LIST_GET_FIRST(space->chain); + + /* It must be a single-table tablespace and we have not opened + the file yet; the following calls will open it and update the + size fields */ + + fil_node_prepare_for_io(node, system, space); + fil_node_complete_io(node, system, OS_FILE_READ); + } + + size = space->size; + + mutex_exit(&(system->mutex)); + + return(size); +} + +/*********************************************************************** +Returns the flags of the space. The tablespace must be cached +in the memory cache. */ +UNIV_INTERN +ulint +fil_space_get_flags( +/*================*/ + /* out: flags, ULINT_UNDEFINED if space not found */ + ulint id) /* in: space id */ +{ + fil_system_t* system = fil_system; + fil_node_t* node; + fil_space_t* space; + ulint flags; + + ut_ad(system); + + if (UNIV_UNLIKELY(!id)) { + return(0); + } + + fil_mutex_enter_and_prepare_for_io(id); + + space = fil_space_get_by_id(id); + + if (space == NULL) { + mutex_exit(&(system->mutex)); + + return(ULINT_UNDEFINED); + } + + if (space->size == 0 && space->purpose == FIL_TABLESPACE) { + ut_a(id != 0); + + ut_a(1 == UT_LIST_GET_LEN(space->chain)); + + node = UT_LIST_GET_FIRST(space->chain); + + /* It must be a single-table tablespace and we have not opened + the file yet; the following calls will open it and update the + size fields */ + + fil_node_prepare_for_io(node, system, space); + fil_node_complete_io(node, system, OS_FILE_READ); + } + + flags = space->flags; + + mutex_exit(&(system->mutex)); + + return(flags); +} + +/*********************************************************************** +Returns the compressed page size of the space, or 0 if the space +is not compressed. The tablespace must be cached in the memory cache. */ +UNIV_INTERN +ulint +fil_space_get_zip_size( +/*===================*/ + /* out: compressed page size, ULINT_UNDEFINED + if space not found */ + ulint id) /* in: space id */ +{ + ulint flags; + + flags = fil_space_get_flags(id); + + if (flags && flags != ULINT_UNDEFINED) { + + return(dict_table_flags_to_zip_size(flags)); + } + + return(flags); +} + +/*********************************************************************** +Checks if the pair space, page_no refers to an existing page in a tablespace +file space. The tablespace must be cached in the memory cache. */ +UNIV_INTERN +ibool +fil_check_adress_in_tablespace( +/*===========================*/ + /* out: TRUE if the address is meaningful */ + ulint id, /* in: space id */ + ulint page_no)/* in: page number */ +{ + if (fil_space_get_size(id) > page_no) { + + return(TRUE); + } + + return(FALSE); +} + +/******************************************************************** +Creates a the tablespace memory cache. */ +static +fil_system_t* +fil_system_create( +/*==============*/ + /* out, own: tablespace memory cache */ + ulint hash_size, /* in: hash table size */ + ulint max_n_open) /* in: maximum number of open files; must be + > 10 */ +{ + fil_system_t* system; + + ut_a(hash_size > 0); + ut_a(max_n_open > 0); + + system = mem_alloc(sizeof(fil_system_t)); + + mutex_create(&system->mutex, SYNC_ANY_LATCH); + + system->spaces = hash_create(hash_size); + system->name_hash = hash_create(hash_size); + + UT_LIST_INIT(system->LRU); + + system->n_open = 0; + system->max_n_open = max_n_open; + + system->modification_counter = 0; + system->max_assigned_id = 0; + + system->tablespace_version = 0; + + UT_LIST_INIT(system->unflushed_spaces); + UT_LIST_INIT(system->space_list); + + return(system); +} + +/******************************************************************** +Initializes the tablespace memory cache. */ +UNIV_INTERN +void +fil_init( +/*=====*/ + ulint max_n_open) /* in: max number of open files */ +{ + ulint hash_size; + + ut_a(fil_system == NULL); + + if (srv_file_per_table) { + hash_size = 50000; + } else { + hash_size = 5000; + } + + fil_system = fil_system_create(hash_size, max_n_open); +} + +/*********************************************************************** +Opens all log files and system tablespace data files. They stay open until the +database server shutdown. This should be called at a server startup after the +space objects for the log and the system tablespace have been created. The +purpose of this operation is to make sure we never run out of file descriptors +if we need to read from the insert buffer or to write to the log. */ +UNIV_INTERN +void +fil_open_log_and_system_tablespace_files(void) +/*==========================================*/ +{ + fil_system_t* system = fil_system; + fil_space_t* space; + fil_node_t* node; + + mutex_enter(&(system->mutex)); + + space = UT_LIST_GET_FIRST(system->space_list); + + while (space != NULL) { + if (space->purpose != FIL_TABLESPACE || space->id == 0) { + node = UT_LIST_GET_FIRST(space->chain); + + while (node != NULL) { + if (!node->open) { + fil_node_open_file(node, system, + space); + } + if (system->max_n_open < 10 + system->n_open) { + fprintf(stderr, + "InnoDB: Warning: you must" + " raise the value of" + " innodb_max_open_files in\n" + "InnoDB: my.cnf! Remember that" + " InnoDB keeps all log files" + " and all system\n" + "InnoDB: tablespace files open" + " for the whole time mysqld is" + " running, and\n" + "InnoDB: needs to open also" + " some .ibd files if the" + " file-per-table storage\n" + "InnoDB: model is used." + " Current open files %lu," + " max allowed" + " open files %lu.\n", + (ulong) system->n_open, + (ulong) system->max_n_open); + } + node = UT_LIST_GET_NEXT(chain, node); + } + } + space = UT_LIST_GET_NEXT(space_list, space); + } + + mutex_exit(&(system->mutex)); +} + +/*********************************************************************** +Closes all open files. There must not be any pending i/o's or not flushed +modifications in the files. */ +UNIV_INTERN +void +fil_close_all_files(void) +/*=====================*/ +{ + fil_system_t* system = fil_system; + fil_space_t* space; + fil_node_t* node; + + mutex_enter(&(system->mutex)); + + space = UT_LIST_GET_FIRST(system->space_list); + + while (space != NULL) { + node = UT_LIST_GET_FIRST(space->chain); + + while (node != NULL) { + if (node->open) { + fil_node_close_file(node, system); + } + node = UT_LIST_GET_NEXT(chain, node); + } + space = UT_LIST_GET_NEXT(space_list, space); + } + + mutex_exit(&(system->mutex)); +} + +/*********************************************************************** +Sets the max tablespace id counter if the given number is bigger than the +previous value. */ +UNIV_INTERN +void +fil_set_max_space_id_if_bigger( +/*===========================*/ + ulint max_id) /* in: maximum known id */ +{ + fil_system_t* system = fil_system; + + if (max_id >= SRV_LOG_SPACE_FIRST_ID) { + fprintf(stderr, + "InnoDB: Fatal error: max tablespace id" + " is too high, %lu\n", (ulong) max_id); + ut_a(0); + } + + mutex_enter(&(system->mutex)); + + if (system->max_assigned_id < max_id) { + + system->max_assigned_id = max_id; + } + + mutex_exit(&(system->mutex)); +} + +/******************************************************************** +Writes the flushed lsn and the latest archived log number to the page header +of the first page of a data file of the system tablespace (space 0), +which is uncompressed. */ +static +ulint +fil_write_lsn_and_arch_no_to_file( +/*==============================*/ + ulint sum_of_sizes, /* in: combined size of previous files + in space, in database pages */ + ib_uint64_t lsn, /* in: lsn to write */ + ulint arch_log_no /* in: archived log number to write */ + __attribute__((unused))) +{ + byte* buf1; + byte* buf; + + buf1 = mem_alloc(2 * UNIV_PAGE_SIZE); + buf = ut_align(buf1, UNIV_PAGE_SIZE); + + fil_read(TRUE, 0, 0, sum_of_sizes, 0, UNIV_PAGE_SIZE, buf, NULL); + + mach_write_ull(buf + FIL_PAGE_FILE_FLUSH_LSN, lsn); + + fil_write(TRUE, 0, 0, sum_of_sizes, 0, UNIV_PAGE_SIZE, buf, NULL); + + mem_free(buf1); + + return(DB_SUCCESS); +} + +/******************************************************************** +Writes the flushed lsn and the latest archived log number to the page +header of the first page of each data file in the system tablespace. */ +UNIV_INTERN +ulint +fil_write_flushed_lsn_to_data_files( +/*================================*/ + /* out: DB_SUCCESS or error number */ + ib_uint64_t lsn, /* in: lsn to write */ + ulint arch_log_no) /* in: latest archived log + file number */ +{ + fil_space_t* space; + fil_node_t* node; + ulint sum_of_sizes; + ulint err; + + mutex_enter(&(fil_system->mutex)); + + space = UT_LIST_GET_FIRST(fil_system->space_list); + + while (space) { + /* We only write the lsn to all existing data files which have + been open during the lifetime of the mysqld process; they are + represented by the space objects in the tablespace memory + cache. Note that all data files in the system tablespace 0 are + always open. */ + + if (space->purpose == FIL_TABLESPACE + && space->id == 0) { + sum_of_sizes = 0; + + node = UT_LIST_GET_FIRST(space->chain); + while (node) { + mutex_exit(&(fil_system->mutex)); + + err = fil_write_lsn_and_arch_no_to_file( + sum_of_sizes, lsn, arch_log_no); + if (err != DB_SUCCESS) { + + return(err); + } + + mutex_enter(&(fil_system->mutex)); + + sum_of_sizes += node->size; + node = UT_LIST_GET_NEXT(chain, node); + } + } + space = UT_LIST_GET_NEXT(space_list, space); + } + + mutex_exit(&(fil_system->mutex)); + + return(DB_SUCCESS); +} + +/*********************************************************************** +Reads the flushed lsn and arch no fields from a data file at database +startup. */ +UNIV_INTERN +void +fil_read_flushed_lsn_and_arch_log_no( +/*=================================*/ + os_file_t data_file, /* in: open data file */ + ibool one_read_already, /* in: TRUE if min and max + parameters below already + contain sensible data */ +#ifdef UNIV_LOG_ARCHIVE + ulint* min_arch_log_no, /* in/out: */ + ulint* max_arch_log_no, /* in/out: */ +#endif /* UNIV_LOG_ARCHIVE */ + ib_uint64_t* min_flushed_lsn, /* in/out: */ + ib_uint64_t* max_flushed_lsn) /* in/out: */ +{ + byte* buf; + byte* buf2; + ib_uint64_t flushed_lsn; + + buf2 = ut_malloc(2 * UNIV_PAGE_SIZE); + /* Align the memory for a possible read from a raw device */ + buf = ut_align(buf2, UNIV_PAGE_SIZE); + + os_file_read(data_file, buf, 0, 0, UNIV_PAGE_SIZE); + + flushed_lsn = mach_read_ull(buf + FIL_PAGE_FILE_FLUSH_LSN); + + ut_free(buf2); + + if (!one_read_already) { + *min_flushed_lsn = flushed_lsn; + *max_flushed_lsn = flushed_lsn; +#ifdef UNIV_LOG_ARCHIVE + *min_arch_log_no = arch_log_no; + *max_arch_log_no = arch_log_no; +#endif /* UNIV_LOG_ARCHIVE */ + return; + } + + if (*min_flushed_lsn > flushed_lsn) { + *min_flushed_lsn = flushed_lsn; + } + if (*max_flushed_lsn < flushed_lsn) { + *max_flushed_lsn = flushed_lsn; + } +#ifdef UNIV_LOG_ARCHIVE + if (*min_arch_log_no > arch_log_no) { + *min_arch_log_no = arch_log_no; + } + if (*max_arch_log_no < arch_log_no) { + *max_arch_log_no = arch_log_no; + } +#endif /* UNIV_LOG_ARCHIVE */ +} + +/*================ SINGLE-TABLE TABLESPACES ==========================*/ + +/*********************************************************************** +Increments the count of pending insert buffer page merges, if space is not +being deleted. */ +UNIV_INTERN +ibool +fil_inc_pending_ibuf_merges( +/*========================*/ + /* out: TRUE if being deleted, and ibuf merges should + be skipped */ + ulint id) /* in: space id */ +{ + fil_system_t* system = fil_system; + fil_space_t* space; + + mutex_enter(&(system->mutex)); + + space = fil_space_get_by_id(id); + + if (space == NULL) { + fprintf(stderr, + "InnoDB: Error: trying to do ibuf merge to a" + " dropped tablespace %lu\n", + (ulong) id); + } + + if (space == NULL || space->stop_ibuf_merges) { + mutex_exit(&(system->mutex)); + + return(TRUE); + } + + space->n_pending_ibuf_merges++; + + mutex_exit(&(system->mutex)); + + return(FALSE); +} + +/*********************************************************************** +Decrements the count of pending insert buffer page merges. */ +UNIV_INTERN +void +fil_decr_pending_ibuf_merges( +/*=========================*/ + ulint id) /* in: space id */ +{ + fil_system_t* system = fil_system; + fil_space_t* space; + + mutex_enter(&(system->mutex)); + + space = fil_space_get_by_id(id); + + if (space == NULL) { + fprintf(stderr, + "InnoDB: Error: decrementing ibuf merge of a" + " dropped tablespace %lu\n", + (ulong) id); + } + + if (space != NULL) { + space->n_pending_ibuf_merges--; + } + + mutex_exit(&(system->mutex)); +} + +/************************************************************ +Creates the database directory for a table if it does not exist yet. */ +static +void +fil_create_directory_for_tablename( +/*===============================*/ + const char* name) /* in: name in the standard + 'databasename/tablename' format */ +{ + const char* namend; + char* path; + ulint len; + + len = strlen(fil_path_to_mysql_datadir); + namend = strchr(name, '/'); + ut_a(namend); + path = mem_alloc(len + (namend - name) + 2); + + memcpy(path, fil_path_to_mysql_datadir, len); + path[len] = '/'; + memcpy(path + len + 1, name, namend - name); + path[len + (namend - name) + 1] = 0; + + srv_normalize_path_for_win(path); + + ut_a(os_file_create_directory(path, FALSE)); + mem_free(path); +} + +#ifndef UNIV_HOTBACKUP +/************************************************************ +Writes a log record about an .ibd file create/rename/delete. */ +static +void +fil_op_write_log( +/*=============*/ + ulint type, /* in: MLOG_FILE_CREATE, + MLOG_FILE_CREATE2, + MLOG_FILE_DELETE, or + MLOG_FILE_RENAME */ + ulint space_id, /* in: space id */ + ulint flags, /* in: compressed page size + and file format + if type==MLOG_FILE_CREATE2, or 0 */ + const char* name, /* in: table name in the familiar + 'databasename/tablename' format, or + the file path in the case of + MLOG_FILE_DELETE */ + const char* new_name, /* in: if type is MLOG_FILE_RENAME, + the new table name in the + 'databasename/tablename' format */ + mtr_t* mtr) /* in: mini-transaction handle */ +{ + byte* log_ptr; + ulint len; + + log_ptr = mlog_open(mtr, 11 + 2 + 1); + + if (!log_ptr) { + /* Logging in mtr is switched off during crash recovery: + in that case mlog_open returns NULL */ + return; + } + + log_ptr = mlog_write_initial_log_record_for_file_op(type, space_id, 0, + log_ptr, mtr); + if (type == MLOG_FILE_CREATE2) { + mach_write_to_4(log_ptr, flags); + log_ptr += 4; + } + /* Let us store the strings as null-terminated for easier readability + and handling */ + + len = strlen(name) + 1; + + mach_write_to_2(log_ptr, len); + log_ptr += 2; + mlog_close(mtr, log_ptr); + + mlog_catenate_string(mtr, (byte*) name, len); + + if (type == MLOG_FILE_RENAME) { + len = strlen(new_name) + 1; + log_ptr = mlog_open(mtr, 2 + len); + ut_a(log_ptr); + mach_write_to_2(log_ptr, len); + log_ptr += 2; + mlog_close(mtr, log_ptr); + + mlog_catenate_string(mtr, (byte*) new_name, len); + } +} +#endif + +/*********************************************************************** +Parses the body of a log record written about an .ibd file operation. That is, +the log record part after the standard (type, space id, page no) header of the +log record. + +If desired, also replays the delete or rename operation if the .ibd file +exists and the space id in it matches. Replays the create operation if a file +at that path does not exist yet. If the database directory for the file to be +created does not exist, then we create the directory, too. + +Note that ibbackup --apply-log sets fil_path_to_mysql_datadir to point to the +datadir that we should use in replaying the file operations. */ +UNIV_INTERN +byte* +fil_op_log_parse_or_replay( +/*=======================*/ + /* out: end of log record, or NULL if the + record was not completely contained between + ptr and end_ptr */ + byte* ptr, /* in: buffer containing the log record body, + or an initial segment of it, if the record does + not fir completely between ptr and end_ptr */ + byte* end_ptr, /* in: buffer end */ + ulint type, /* in: the type of this log record */ + ulint space_id) /* in: the space id of the tablespace in + question, or 0 if the log record should + only be parsed but not replayed */ +{ + ulint name_len; + ulint new_name_len; + const char* name; + const char* new_name = NULL; + ulint flags = 0; + + if (type == MLOG_FILE_CREATE2) { + if (end_ptr < ptr + 4) { + + return(NULL); + } + + flags = mach_read_from_4(ptr); + ptr += 4; + } + + if (end_ptr < ptr + 2) { + + return(NULL); + } + + name_len = mach_read_from_2(ptr); + + ptr += 2; + + if (end_ptr < ptr + name_len) { + + return(NULL); + } + + name = (const char*) ptr; + + ptr += name_len; + + if (type == MLOG_FILE_RENAME) { + if (end_ptr < ptr + 2) { + + return(NULL); + } + + new_name_len = mach_read_from_2(ptr); + + ptr += 2; + + if (end_ptr < ptr + new_name_len) { + + return(NULL); + } + + new_name = (const char*) ptr; + + ptr += new_name_len; + } + + /* We managed to parse a full log record body */ + /* + printf("Parsed log rec of type %lu space %lu\n" + "name %s\n", type, space_id, name); + + if (type == MLOG_FILE_RENAME) { + printf("new name %s\n", new_name); + } + */ + if (!space_id) { + + return(ptr); + } + + /* Let us try to perform the file operation, if sensible. Note that + ibbackup has at this stage already read in all space id info to the + fil0fil.c data structures. + + NOTE that our algorithm is not guaranteed to work correctly if there + were renames of tables during the backup. See ibbackup code for more + on the problem. */ + + switch (type) { + case MLOG_FILE_DELETE: + if (fil_tablespace_exists_in_mem(space_id)) { + ut_a(fil_delete_tablespace(space_id)); + } + + break; + + case MLOG_FILE_RENAME: + /* We do the rename based on space id, not old file name; + this should guarantee that after the log replay each .ibd file + has the correct name for the latest log sequence number; the + proof is left as an exercise :) */ + + if (fil_tablespace_exists_in_mem(space_id)) { + /* Create the database directory for the new name, if + it does not exist yet */ + fil_create_directory_for_tablename(new_name); + + /* Rename the table if there is not yet a tablespace + with the same name */ + + if (fil_get_space_id_for_table(new_name) + == ULINT_UNDEFINED) { + /* We do not care of the old name, that is + why we pass NULL as the first argument */ + if (!fil_rename_tablespace(NULL, space_id, + new_name)) { + ut_error; + } + } + } + + break; + + case MLOG_FILE_CREATE: + case MLOG_FILE_CREATE2: + if (fil_tablespace_exists_in_mem(space_id)) { + /* Do nothing */ + } else if (fil_get_space_id_for_table(name) + != ULINT_UNDEFINED) { + /* Do nothing */ + } else { + /* Create the database directory for name, if it does + not exist yet */ + fil_create_directory_for_tablename(name); + + if (fil_create_new_single_table_tablespace( + &space_id, name, FALSE, flags, + FIL_IBD_FILE_INITIAL_SIZE) != DB_SUCCESS) { + ut_error; + } + } + + break; + + default: + ut_error; + } + + return(ptr); +} + +/*********************************************************************** +Deletes a single-table tablespace. The tablespace must be cached in the +memory cache. */ +UNIV_INTERN +ibool +fil_delete_tablespace( +/*==================*/ + /* out: TRUE if success */ + ulint id) /* in: space id */ +{ + fil_system_t* system = fil_system; + ibool success; + fil_space_t* space; + fil_node_t* node; + ulint count = 0; + char* path; + + ut_a(id != 0); +stop_ibuf_merges: + mutex_enter(&(system->mutex)); + + space = fil_space_get_by_id(id); + + if (space != NULL) { + space->stop_ibuf_merges = TRUE; + + if (space->n_pending_ibuf_merges == 0) { + mutex_exit(&(system->mutex)); + + count = 0; + + goto try_again; + } else { + if (count > 5000) { + ut_print_timestamp(stderr); + fputs(" InnoDB: Warning: trying to" + " delete tablespace ", stderr); + ut_print_filename(stderr, space->name); + fprintf(stderr, ",\n" + "InnoDB: but there are %lu pending" + " ibuf merges on it.\n" + "InnoDB: Loop %lu.\n", + (ulong) space->n_pending_ibuf_merges, + (ulong) count); + } + + mutex_exit(&(system->mutex)); + + os_thread_sleep(20000); + count++; + + goto stop_ibuf_merges; + } + } + + mutex_exit(&(system->mutex)); + count = 0; + +try_again: + mutex_enter(&(system->mutex)); + + space = fil_space_get_by_id(id); + + if (space == NULL) { + ut_print_timestamp(stderr); + fprintf(stderr, + " InnoDB: Error: cannot delete tablespace %lu\n" + "InnoDB: because it is not found in the" + " tablespace memory cache.\n", + (ulong) id); + + mutex_exit(&(system->mutex)); + + return(FALSE); + } + + ut_a(space); + ut_a(space->n_pending_ibuf_merges == 0); + + space->is_being_deleted = TRUE; + + ut_a(UT_LIST_GET_LEN(space->chain) == 1); + node = UT_LIST_GET_FIRST(space->chain); + + if (space->n_pending_flushes > 0 || node->n_pending > 0) { + if (count > 1000) { + ut_print_timestamp(stderr); + fputs(" InnoDB: Warning: trying to" + " delete tablespace ", stderr); + ut_print_filename(stderr, space->name); + fprintf(stderr, ",\n" + "InnoDB: but there are %lu flushes" + " and %lu pending i/o's on it\n" + "InnoDB: Loop %lu.\n", + (ulong) space->n_pending_flushes, + (ulong) node->n_pending, + (ulong) count); + } + mutex_exit(&(system->mutex)); + os_thread_sleep(20000); + + count++; + + goto try_again; + } + + path = mem_strdup(space->name); + + mutex_exit(&(system->mutex)); +#ifndef UNIV_HOTBACKUP + /* Invalidate in the buffer pool all pages belonging to the + tablespace. Since we have set space->is_being_deleted = TRUE, readahead + or ibuf merge can no longer read more pages of this tablespace to the + buffer pool. Thus we can clean the tablespace out of the buffer pool + completely and permanently. The flag is_being_deleted also prevents + fil_flush() from being applied to this tablespace. */ + + buf_LRU_invalidate_tablespace(id); +#endif + /* printf("Deleting tablespace %s id %lu\n", space->name, id); */ + + success = fil_space_free(id); + + if (success) { + success = os_file_delete(path); + + if (!success) { + success = os_file_delete_if_exists(path); + } + } + + if (success) { +#ifndef UNIV_HOTBACKUP + /* Write a log record about the deletion of the .ibd + file, so that ibbackup can replay it in the + --apply-log phase. We use a dummy mtr and the familiar + log write mechanism. */ + mtr_t mtr; + + /* When replaying the operation in ibbackup, do not try + to write any log record */ + mtr_start(&mtr); + + fil_op_write_log(MLOG_FILE_DELETE, id, 0, path, NULL, &mtr); + mtr_commit(&mtr); +#endif + mem_free(path); + + return(TRUE); + } + + mem_free(path); + + return(FALSE); +} + +/*********************************************************************** +Discards a single-table tablespace. The tablespace must be cached in the +memory cache. Discarding is like deleting a tablespace, but +1) we do not drop the table from the data dictionary; +2) we remove all insert buffer entries for the tablespace immediately; in DROP +TABLE they are only removed gradually in the background; +3) when the user does IMPORT TABLESPACE, the tablespace will have the same id +as it originally had. */ +UNIV_INTERN +ibool +fil_discard_tablespace( +/*===================*/ + /* out: TRUE if success */ + ulint id) /* in: space id */ +{ + ibool success; + + success = fil_delete_tablespace(id); + + if (!success) { + fprintf(stderr, + "InnoDB: Warning: cannot delete tablespace %lu" + " in DISCARD TABLESPACE.\n" + "InnoDB: But let us remove the" + " insert buffer entries for this tablespace.\n", + (ulong) id); + } + + /* Remove all insert buffer entries for the tablespace */ + + ibuf_delete_for_discarded_space(id); + + return(success); +} + +/*********************************************************************** +Renames the memory cache structures of a single-table tablespace. */ +static +ibool +fil_rename_tablespace_in_mem( +/*=========================*/ + /* out: TRUE if success */ + fil_space_t* space, /* in: tablespace memory object */ + fil_node_t* node, /* in: file node of that tablespace */ + const char* path) /* in: new name */ +{ + fil_system_t* system = fil_system; + fil_space_t* space2; + const char* old_name = space->name; + + space2 = fil_space_get_by_name(old_name); + if (space != space2) { + fputs("InnoDB: Error: cannot find ", stderr); + ut_print_filename(stderr, old_name); + fputs(" in tablespace memory cache\n", stderr); + + return(FALSE); + } + + space2 = fil_space_get_by_name(path); + if (space2 != NULL) { + fputs("InnoDB: Error: ", stderr); + ut_print_filename(stderr, path); + fputs(" is already in tablespace memory cache\n", stderr); + + return(FALSE); + } + + HASH_DELETE(fil_space_t, name_hash, system->name_hash, + ut_fold_string(space->name), space); + mem_free(space->name); + mem_free(node->name); + + space->name = mem_strdup(path); + node->name = mem_strdup(path); + + HASH_INSERT(fil_space_t, name_hash, system->name_hash, + ut_fold_string(path), space); + return(TRUE); +} + +/*********************************************************************** +Allocates a file name for a single-table tablespace. The string must be freed +by caller with mem_free(). */ +static +char* +fil_make_ibd_name( +/*==============*/ + /* out, own: file name */ + const char* name, /* in: table name or a dir path of a + TEMPORARY table */ + ibool is_temp) /* in: TRUE if it is a dir path */ +{ + ulint namelen = strlen(name); + ulint dirlen = strlen(fil_path_to_mysql_datadir); + char* filename = mem_alloc(namelen + dirlen + sizeof "/.ibd"); + + if (is_temp) { + memcpy(filename, name, namelen); + memcpy(filename + namelen, ".ibd", sizeof ".ibd"); + } else { + memcpy(filename, fil_path_to_mysql_datadir, dirlen); + filename[dirlen] = '/'; + + memcpy(filename + dirlen + 1, name, namelen); + memcpy(filename + dirlen + namelen + 1, ".ibd", sizeof ".ibd"); + } + + srv_normalize_path_for_win(filename); + + return(filename); +} + +/*********************************************************************** +Renames a single-table tablespace. The tablespace must be cached in the +tablespace memory cache. */ +UNIV_INTERN +ibool +fil_rename_tablespace( +/*==================*/ + /* out: TRUE if success */ + const char* old_name, /* in: old table name in the standard + databasename/tablename format of + InnoDB, or NULL if we do the rename + based on the space id only */ + ulint id, /* in: space id */ + const char* new_name) /* in: new table name in the standard + databasename/tablename format + of InnoDB */ +{ + fil_system_t* system = fil_system; + ibool success; + fil_space_t* space; + fil_node_t* node; + ulint count = 0; + char* path; + ibool old_name_was_specified = TRUE; + char* old_path; + + ut_a(id != 0); + + if (old_name == NULL) { + old_name = "(name not specified)"; + old_name_was_specified = FALSE; + } +retry: + count++; + + if (count > 1000) { + ut_print_timestamp(stderr); + fputs(" InnoDB: Warning: problems renaming ", stderr); + ut_print_filename(stderr, old_name); + fputs(" to ", stderr); + ut_print_filename(stderr, new_name); + fprintf(stderr, ", %lu iterations\n", (ulong) count); + } + + mutex_enter(&(system->mutex)); + + space = fil_space_get_by_id(id); + + if (space == NULL) { + fprintf(stderr, + "InnoDB: Error: cannot find space id %lu" + " in the tablespace memory cache\n" + "InnoDB: though the table ", (ulong) id); + ut_print_filename(stderr, old_name); + fputs(" in a rename operation should have that id\n", stderr); + mutex_exit(&(system->mutex)); + + return(FALSE); + } + + if (count > 25000) { + space->stop_ios = FALSE; + mutex_exit(&(system->mutex)); + + return(FALSE); + } + + /* We temporarily close the .ibd file because we do not trust that + operating systems can rename an open file. For the closing we have to + wait until there are no pending i/o's or flushes on the file. */ + + space->stop_ios = TRUE; + + ut_a(UT_LIST_GET_LEN(space->chain) == 1); + node = UT_LIST_GET_FIRST(space->chain); + + if (node->n_pending > 0 || node->n_pending_flushes > 0) { + /* There are pending i/o's or flushes, sleep for a while and + retry */ + + mutex_exit(&(system->mutex)); + + os_thread_sleep(20000); + + goto retry; + + } else if (node->modification_counter > node->flush_counter) { + /* Flush the space */ + + mutex_exit(&(system->mutex)); + + os_thread_sleep(20000); + + fil_flush(id); + + goto retry; + + } else if (node->open) { + /* Close the file */ + + fil_node_close_file(node, system); + } + + /* Check that the old name in the space is right */ + + if (old_name_was_specified) { + old_path = fil_make_ibd_name(old_name, FALSE); + + ut_a(strcmp(space->name, old_path) == 0); + ut_a(strcmp(node->name, old_path) == 0); + } else { + old_path = mem_strdup(space->name); + } + + /* Rename the tablespace and the node in the memory cache */ + path = fil_make_ibd_name(new_name, FALSE); + success = fil_rename_tablespace_in_mem(space, node, path); + + if (success) { + success = os_file_rename(old_path, path); + + if (!success) { + /* We have to revert the changes we made + to the tablespace memory cache */ + + ut_a(fil_rename_tablespace_in_mem(space, node, + old_path)); + } + } + + mem_free(path); + mem_free(old_path); + + space->stop_ios = FALSE; + + mutex_exit(&(system->mutex)); + +#ifndef UNIV_HOTBACKUP + if (success) { + mtr_t mtr; + + mtr_start(&mtr); + + fil_op_write_log(MLOG_FILE_RENAME, id, 0, old_name, new_name, + &mtr); + mtr_commit(&mtr); + } +#endif + return(success); +} + +/*********************************************************************** +Creates a new single-table tablespace to a database directory of MySQL. +Database directories are under the 'datadir' of MySQL. The datadir is the +directory of a running mysqld program. We can refer to it by simply the +path '.'. Tables created with CREATE TEMPORARY TABLE we place in the temp +dir of the mysqld server. */ +UNIV_INTERN +ulint +fil_create_new_single_table_tablespace( +/*===================================*/ + /* out: DB_SUCCESS or error code */ + ulint* space_id, /* in/out: space id; if this is != 0, + then this is an input parameter, + otherwise output */ + const char* tablename, /* in: the table name in the usual + databasename/tablename format + of InnoDB, or a dir path to a temp + table */ + ibool is_temp, /* in: TRUE if a table created with + CREATE TEMPORARY TABLE */ + ulint flags, /* in: tablespace flags */ + ulint size) /* in: the initial size of the + tablespace file in pages, + must be >= FIL_IBD_FILE_INITIAL_SIZE */ +{ + os_file_t file; + ibool ret; + ulint err; + byte* buf2; + byte* page; + ibool success; + char* path; + + ut_a(size >= FIL_IBD_FILE_INITIAL_SIZE); + /* The tablespace flags (FSP_SPACE_FLAGS) should be 0 for + ROW_FORMAT=COMPACT (table->flags == DICT_TF_COMPACT) and + ROW_FORMAT=REDUNDANT (table->flags == 0). For any other + format, the tablespace flags should equal table->flags. */ + ut_a(flags != DICT_TF_COMPACT); + + path = fil_make_ibd_name(tablename, is_temp); + + file = os_file_create(path, OS_FILE_CREATE, OS_FILE_NORMAL, + OS_DATA_FILE, &ret); + if (ret == FALSE) { + ut_print_timestamp(stderr); + fputs(" InnoDB: Error creating file ", stderr); + ut_print_filename(stderr, path); + fputs(".\n", stderr); + + /* The following call will print an error message */ + + err = os_file_get_last_error(TRUE); + + if (err == OS_FILE_ALREADY_EXISTS) { + fputs("InnoDB: The file already exists though" + " the corresponding table did not\n" + "InnoDB: exist in the InnoDB data dictionary." + " Have you moved InnoDB\n" + "InnoDB: .ibd files around without using the" + " SQL commands\n" + "InnoDB: DISCARD TABLESPACE and" + " IMPORT TABLESPACE, or did\n" + "InnoDB: mysqld crash in the middle of" + " CREATE TABLE? You can\n" + "InnoDB: resolve the problem by" + " removing the file ", stderr); + ut_print_filename(stderr, path); + fputs("\n" + "InnoDB: under the 'datadir' of MySQL.\n", + stderr); + + mem_free(path); + return(DB_TABLESPACE_ALREADY_EXISTS); + } + + if (err == OS_FILE_DISK_FULL) { + + mem_free(path); + return(DB_OUT_OF_FILE_SPACE); + } + + mem_free(path); + return(DB_ERROR); + } + + buf2 = ut_malloc(3 * UNIV_PAGE_SIZE); + /* Align the memory for file i/o if we might have O_DIRECT set */ + page = ut_align(buf2, UNIV_PAGE_SIZE); + + ret = os_file_set_size(path, file, size * UNIV_PAGE_SIZE, 0); + + if (!ret) { + ut_free(buf2); + os_file_close(file); + os_file_delete(path); + + mem_free(path); + return(DB_OUT_OF_FILE_SPACE); + } + + if (*space_id == 0) { + *space_id = fil_assign_new_space_id(); + } + + /* printf("Creating tablespace %s id %lu\n", path, *space_id); */ + + if (*space_id == ULINT_UNDEFINED) { + ut_free(buf2); +error_exit: + os_file_close(file); +error_exit2: + os_file_delete(path); + + mem_free(path); + return(DB_ERROR); + } + + /* We have to write the space id to the file immediately and flush the + file to disk. This is because in crash recovery we must be aware what + tablespaces exist and what are their space id's, so that we can apply + the log records to the right file. It may take quite a while until + buffer pool flush algorithms write anything to the file and flush it to + disk. If we would not write here anything, the file would be filled + with zeros from the call of os_file_set_size(), until a buffer pool + flush would write to it. */ + + memset(page, '\0', UNIV_PAGE_SIZE); + + fsp_header_init_fields(page, *space_id, flags); + mach_write_to_4(page + FIL_PAGE_ARCH_LOG_NO_OR_SPACE_ID, *space_id); + + if (!(flags & DICT_TF_ZSSIZE_MASK)) { + buf_flush_init_for_writing(page, NULL, 0); + ret = os_file_write(path, file, page, 0, 0, UNIV_PAGE_SIZE); + } else { + page_zip_des_t page_zip; + ulint zip_size; + + zip_size = ((PAGE_ZIP_MIN_SIZE >> 1) + << ((flags & DICT_TF_ZSSIZE_MASK) + >> DICT_TF_ZSSIZE_SHIFT)); + + page_zip_set_size(&page_zip, zip_size); + page_zip.data = page + UNIV_PAGE_SIZE; +#ifdef UNIV_DEBUG + page_zip.m_start = +#endif /* UNIV_DEBUG */ + page_zip.m_end = page_zip.m_nonempty = + page_zip.n_blobs = 0; + buf_flush_init_for_writing(page, &page_zip, 0); + ret = os_file_write(path, file, page_zip.data, 0, 0, zip_size); + } + + ut_free(buf2); + + if (!ret) { + fputs("InnoDB: Error: could not write the first page" + " to tablespace ", stderr); + ut_print_filename(stderr, path); + putc('\n', stderr); + goto error_exit; + } + + ret = os_file_flush(file); + + if (!ret) { + fputs("InnoDB: Error: file flush of tablespace ", stderr); + ut_print_filename(stderr, path); + fputs(" failed\n", stderr); + goto error_exit; + } + + os_file_close(file); + + if (*space_id == ULINT_UNDEFINED) { + goto error_exit2; + } + + success = fil_space_create(path, *space_id, flags, FIL_TABLESPACE); + + if (!success) { + goto error_exit2; + } + + fil_node_create(path, size, *space_id, FALSE); + +#ifndef UNIV_HOTBACKUP + { + mtr_t mtr; + + mtr_start(&mtr); + + fil_op_write_log(flags + ? MLOG_FILE_CREATE2 + : MLOG_FILE_CREATE, + *space_id, flags, + tablename, NULL, &mtr); + + mtr_commit(&mtr); + } +#endif + mem_free(path); + return(DB_SUCCESS); +} + +/************************************************************************ +It is possible, though very improbable, that the lsn's in the tablespace to be +imported have risen above the current system lsn, if a lengthy purge, ibuf +merge, or rollback was performed on a backup taken with ibbackup. If that is +the case, reset page lsn's in the file. We assume that mysqld was shut down +after it performed these cleanup operations on the .ibd file, so that it at +the shutdown stamped the latest lsn to the FIL_PAGE_FILE_FLUSH_LSN in the +first page of the .ibd file, and we can determine whether we need to reset the +lsn's just by looking at that flush lsn. */ +UNIV_INTERN +ibool +fil_reset_too_high_lsns( +/*====================*/ + /* out: TRUE if success */ + const char* name, /* in: table name in the + databasename/tablename format */ + ib_uint64_t current_lsn) /* in: reset lsn's if the lsn stamped + to FIL_PAGE_FILE_FLUSH_LSN in the + first page is too high */ +{ + os_file_t file; + char* filepath; + byte* page; + byte* buf2; + ib_uint64_t flush_lsn; + ulint space_id; + ib_int64_t file_size; + ib_int64_t offset; + ulint zip_size; + ibool success; + + filepath = fil_make_ibd_name(name, FALSE); + + file = os_file_create_simple_no_error_handling( + filepath, OS_FILE_OPEN, OS_FILE_READ_WRITE, &success); + if (!success) { + /* The following call prints an error message */ + os_file_get_last_error(TRUE); + + ut_print_timestamp(stderr); + + fputs(" InnoDB: Error: trying to open a table," + " but could not\n" + "InnoDB: open the tablespace file ", stderr); + ut_print_filename(stderr, filepath); + fputs("!\n", stderr); + mem_free(filepath); + + return(FALSE); + } + + /* Read the first page of the tablespace */ + + buf2 = ut_malloc(3 * UNIV_PAGE_SIZE); + /* Align the memory for file i/o if we might have O_DIRECT set */ + page = ut_align(buf2, UNIV_PAGE_SIZE); + + success = os_file_read(file, page, 0, 0, UNIV_PAGE_SIZE); + if (!success) { + + goto func_exit; + } + + /* We have to read the file flush lsn from the header of the file */ + + flush_lsn = mach_read_ull(page + FIL_PAGE_FILE_FLUSH_LSN); + + if (current_lsn >= flush_lsn) { + /* Ok */ + success = TRUE; + + goto func_exit; + } + + space_id = fsp_header_get_space_id(page); + zip_size = fsp_header_get_zip_size(page); + + ut_print_timestamp(stderr); + fprintf(stderr, + " InnoDB: Flush lsn in the tablespace file %lu" + " to be imported\n" + "InnoDB: is %llu, which exceeds current" + " system lsn %llu.\n" + "InnoDB: We reset the lsn's in the file ", + (ulong) space_id, + flush_lsn, current_lsn); + ut_print_filename(stderr, filepath); + fputs(".\n", stderr); + + ut_a(ut_is_2pow(zip_size)); + ut_a(zip_size <= UNIV_PAGE_SIZE); + + /* Loop through all the pages in the tablespace and reset the lsn and + the page checksum if necessary */ + + file_size = os_file_get_size_as_iblonglong(file); + + for (offset = 0; offset < file_size; + offset += zip_size ? zip_size : UNIV_PAGE_SIZE) { + success = os_file_read(file, page, + (ulint)(offset & 0xFFFFFFFFUL), + (ulint)(offset >> 32), + zip_size ? zip_size : UNIV_PAGE_SIZE); + if (!success) { + + goto func_exit; + } + if (mach_read_ull(page + FIL_PAGE_LSN) > current_lsn) { + /* We have to reset the lsn */ + + if (zip_size) { + memcpy(page + UNIV_PAGE_SIZE, page, zip_size); + buf_flush_init_for_writing( + page, page + UNIV_PAGE_SIZE, + current_lsn); + } else { + buf_flush_init_for_writing( + page, NULL, current_lsn); + } + success = os_file_write(filepath, file, page, + (ulint)(offset & 0xFFFFFFFFUL), + (ulint)(offset >> 32), + zip_size + ? zip_size + : UNIV_PAGE_SIZE); + if (!success) { + + goto func_exit; + } + } + } + + success = os_file_flush(file); + if (!success) { + + goto func_exit; + } + + /* We now update the flush_lsn stamp at the start of the file */ + success = os_file_read(file, page, 0, 0, + zip_size ? zip_size : UNIV_PAGE_SIZE); + if (!success) { + + goto func_exit; + } + + mach_write_ull(page + FIL_PAGE_FILE_FLUSH_LSN, current_lsn); + + success = os_file_write(filepath, file, page, 0, 0, + zip_size ? zip_size : UNIV_PAGE_SIZE); + if (!success) { + + goto func_exit; + } + success = os_file_flush(file); +func_exit: + os_file_close(file); + ut_free(buf2); + mem_free(filepath); + + return(success); +} + +/************************************************************************ +Tries to open a single-table tablespace and optionally checks the space id is +right in it. If does not succeed, prints an error message to the .err log. This +function is used to open a tablespace when we start up mysqld, and also in +IMPORT TABLESPACE. +NOTE that we assume this operation is used either at the database startup +or under the protection of the dictionary mutex, so that two users cannot +race here. This operation does not leave the file associated with the +tablespace open, but closes it after we have looked at the space id in it. */ +UNIV_INTERN +ibool +fil_open_single_table_tablespace( +/*=============================*/ + /* out: TRUE if success */ + ibool check_space_id, /* in: should we check that the space + id in the file is right; we assume + that this function runs much faster + if no check is made, since accessing + the file inode probably is much + faster (the OS caches them) than + accessing the first page of the file */ + ulint id, /* in: space id */ + ulint flags, /* in: tablespace flags */ + const char* name) /* in: table name in the + databasename/tablename format */ +{ + os_file_t file; + char* filepath; + ibool success; + byte* buf2; + byte* page; + ulint space_id; + ulint space_flags; + ibool ret = TRUE; + + filepath = fil_make_ibd_name(name, FALSE); + + /* The tablespace flags (FSP_SPACE_FLAGS) should be 0 for + ROW_FORMAT=COMPACT (table->flags == DICT_TF_COMPACT) and + ROW_FORMAT=REDUNDANT (table->flags == 0). For any other + format, the tablespace flags should equal table->flags. */ + ut_a(flags != DICT_TF_COMPACT); + + file = os_file_create_simple_no_error_handling( + filepath, OS_FILE_OPEN, OS_FILE_READ_WRITE, &success); + if (!success) { + /* The following call prints an error message */ + os_file_get_last_error(TRUE); + + ut_print_timestamp(stderr); + + fputs(" InnoDB: Error: trying to open a table," + " but could not\n" + "InnoDB: open the tablespace file ", stderr); + ut_print_filename(stderr, filepath); + fputs("!\n" + "InnoDB: Have you moved InnoDB .ibd files around" + " without using the\n" + "InnoDB: commands DISCARD TABLESPACE and" + " IMPORT TABLESPACE?\n" + "InnoDB: It is also possible that this is" + " a temporary table #sql...,\n" + "InnoDB: and MySQL removed the .ibd file for this.\n" + "InnoDB: Please refer to\n" + "InnoDB: http://dev.mysql.com/doc/refman/5.1/en/" + "innodb-troubleshooting.html\n" + "InnoDB: for how to resolve the issue.\n", stderr); + + mem_free(filepath); + + return(FALSE); + } + + if (!check_space_id) { + space_id = id; + + goto skip_check; + } + + /* Read the first page of the tablespace */ + + buf2 = ut_malloc(2 * UNIV_PAGE_SIZE); + /* Align the memory for file i/o if we might have O_DIRECT set */ + page = ut_align(buf2, UNIV_PAGE_SIZE); + + success = os_file_read(file, page, 0, 0, UNIV_PAGE_SIZE); + + /* We have to read the tablespace id and flags from the file. */ + + space_id = fsp_header_get_space_id(page); + space_flags = fsp_header_get_flags(page); + + if (srv_expand_import && (space_id != id || space_flags != flags)) { + dulint old_id[31]; + dulint new_id[31]; + ulint root_page[31]; + ulint n_index; + os_file_t info_file = -1; + char* info_file_path; + ulint i; + int len; + ib_uint64_t current_lsn; + + current_lsn = log_get_lsn(); + + /* overwrite fsp header */ + fsp_header_init_fields(page, id, flags); + mach_write_to_4(page + FIL_PAGE_ARCH_LOG_NO_OR_SPACE_ID, id); + space_id = id; + space_flags = flags; + if (mach_read_ull(page + FIL_PAGE_FILE_FLUSH_LSN) > current_lsn) + mach_write_ull(page + FIL_PAGE_FILE_FLUSH_LSN, current_lsn); + mach_write_to_4(page + FIL_PAGE_SPACE_OR_CHKSUM, + srv_use_checksums + ? buf_calc_page_new_checksum(page) + : BUF_NO_CHECKSUM_MAGIC); + mach_write_to_4(page + UNIV_PAGE_SIZE - FIL_PAGE_END_LSN_OLD_CHKSUM, + srv_use_checksums + ? buf_calc_page_old_checksum(page) + : BUF_NO_CHECKSUM_MAGIC); + success = os_file_write(filepath, file, page, 0, 0, UNIV_PAGE_SIZE); + + /* get file size */ + ulint size_low, size_high, size; + ib_int64_t size_bytes; + os_file_get_size(file, &size_low, &size_high); + size_bytes = (((ib_int64_t)size_high) << 32) + + (ib_int64_t)size_low; + + /* get cruster index information */ + dict_table_t* table; + dict_index_t* index; + table = dict_table_get_low(name); + index = dict_table_get_first_index(table); + ut_a(index->page==3); + + + /* read metadata from .exp file */ + n_index = 0; + bzero(old_id, sizeof(old_id)); + bzero(new_id, sizeof(new_id)); + bzero(root_page, sizeof(root_page)); + + info_file_path = fil_make_ibd_name(name, FALSE); + len = strlen(info_file_path); + info_file_path[len - 3] = 'e'; + info_file_path[len - 2] = 'x'; + info_file_path[len - 1] = 'p'; + + info_file = os_file_create_simple_no_error_handling( + info_file_path, OS_FILE_OPEN, OS_FILE_READ_ONLY, &success); + if (!success) { + fprintf(stderr, "InnoDB: cannot open %s\n", info_file_path); + goto skip_info; + } + success = os_file_read(info_file, page, 0, 0, UNIV_PAGE_SIZE); + if (!success) { + fprintf(stderr, "InnoDB: cannot read %s\n", info_file_path); + goto skip_info; + } + if (mach_read_from_4(page) != 0x78706f72UL + || mach_read_from_4(page + 4) != 0x74696e66UL) { + fprintf(stderr, "InnoDB: %s seems not to be a correct .exp file\n", info_file_path); + goto skip_info; + } + + fprintf(stderr, "InnoDB: import: extended import of %s is started.\n", name); + + n_index = mach_read_from_4(page + 8); + fprintf(stderr, "InnoDB: import: %lu indexes are detected.\n", (ulong)n_index); + for (i = 0; i < n_index; i++) { + new_id[i] = + dict_table_get_index_on_name(table, + (page + (i + 1) * 512 + 12))->id; + old_id[i] = mach_read_from_8(page + (i + 1) * 512); + root_page[i] = mach_read_from_4(page + (i + 1) * 512 + 8); + } + +skip_info: + if (info_file != -1) + os_file_close(info_file); + + /* + if (size_bytes >= 1024 * 1024) { + size_bytes = ut_2pow_round(size_bytes, 1024 * 1024); + } + */ + if (!(flags & DICT_TF_ZSSIZE_MASK)) { + mem_heap_t* heap = NULL; + ulint offsets_[REC_OFFS_NORMAL_SIZE]; + ulint* offsets = offsets_; + size = (ulint) (size_bytes / UNIV_PAGE_SIZE); + /* over write space id of all pages */ + ib_int64_t offset; + + rec_offs_init(offsets_); + + fprintf(stderr, "InnoDB: Progress in %:"); + + for (offset = 0; offset < size_bytes; offset += UNIV_PAGE_SIZE) { + success = os_file_read(file, page, + (ulint)(offset & 0xFFFFFFFFUL), + (ulint)(offset >> 32), UNIV_PAGE_SIZE); + if (mach_read_from_4(page + FIL_PAGE_OFFSET) || !offset) { + mach_write_to_4(page + FIL_PAGE_ARCH_LOG_NO_OR_SPACE_ID, id); + + for (i = 0; i < n_index; i++) { + if (offset / UNIV_PAGE_SIZE == root_page[i]) { + /* this is index root page */ + mach_write_to_4(page + FIL_PAGE_DATA + PAGE_BTR_SEG_LEAF + + FSEG_HDR_SPACE, id); + mach_write_to_4(page + FIL_PAGE_DATA + PAGE_BTR_SEG_TOP + + FSEG_HDR_SPACE, id); + break; + } + } + + if (fil_page_get_type(page) == FIL_PAGE_INDEX) { + dulint tmp = mach_read_from_8(page + (PAGE_HEADER + PAGE_INDEX_ID)); + + if (mach_read_from_2(page + PAGE_HEADER + PAGE_LEVEL) == 0 + && ut_dulint_cmp(old_id[0], tmp) == 0) { + /* leaf page of cluster index, reset trx_id of records */ + rec_t* rec; + rec_t* supremum; + ulint n_recs; + + supremum = page_get_supremum_rec(page); + rec = page_rec_get_next(page_get_infimum_rec(page)); + n_recs = page_get_n_recs(page); + + while (rec && rec != supremum && n_recs > 0) { + ulint offset = index->trx_id_offset; + if (!offset) { + offsets = rec_get_offsets(rec, index, offsets, + ULINT_UNDEFINED, &heap); + offset = row_get_trx_id_offset(rec, index, offsets); + } + trx_write_trx_id(rec + offset, ut_dulint_create(0, 1)); + rec = page_rec_get_next(rec); + n_recs--; + } + } + + for (i = 0; i < n_index; i++) { + if (ut_dulint_cmp(old_id[i], tmp) == 0) { + mach_write_to_8(page + (PAGE_HEADER + PAGE_INDEX_ID), new_id[i]); + break; + } + } + } + + if (mach_read_ull(page + FIL_PAGE_LSN) > current_lsn) { + mach_write_ull(page + FIL_PAGE_LSN, current_lsn); + mach_write_ull(page + UNIV_PAGE_SIZE - FIL_PAGE_END_LSN_OLD_CHKSUM, + current_lsn); + } + + mach_write_to_4(page + FIL_PAGE_SPACE_OR_CHKSUM, + srv_use_checksums + ? buf_calc_page_new_checksum(page) + : BUF_NO_CHECKSUM_MAGIC); + mach_write_to_4(page + UNIV_PAGE_SIZE - FIL_PAGE_END_LSN_OLD_CHKSUM, + srv_use_checksums + ? buf_calc_page_old_checksum(page) + : BUF_NO_CHECKSUM_MAGIC); + + success = os_file_write(filepath, file, page, + (ulint)(offset & 0xFFFFFFFFUL), + (ulint)(offset >> 32), UNIV_PAGE_SIZE); + } + + if (size_bytes + && ((ib_int64_t)((offset + UNIV_PAGE_SIZE) * 100) / size_bytes) + != ((offset * 100) / size_bytes)) { + fprintf(stderr, " %lu", + (ulong)((ib_int64_t)((offset + UNIV_PAGE_SIZE) * 100) / size_bytes)); + } + } + + fprintf(stderr, " done.\n"); + + /* update SYS_INDEXES set root page */ + index = dict_table_get_first_index(table); + while (index) { + for (i = 0; i < n_index; i++) { + if (ut_dulint_cmp(new_id[i], index->id) == 0) { + break; + } + } + + if (i != n_index + && root_page[i] != index->page) { + /* must update */ + ulint error; + trx_t* trx; + pars_info_t* info = NULL; + + trx = trx_allocate_for_mysql(); + trx->op_info = "extended import"; + + info = pars_info_create(); + + pars_info_add_dulint_literal(info, "indexid", new_id[i]); + pars_info_add_int4_literal(info, "new_page", (lint) root_page[i]); + + error = que_eval_sql(info, + "PROCEDURE UPDATE_INDEX_PAGE () IS\n" + "BEGIN\n" + "UPDATE SYS_INDEXES" + " SET PAGE_NO = :new_page" + " WHERE ID = :indexid;\n" + "COMMIT WORK;\n" + "END;\n", + FALSE, trx); + + if (error != DB_SUCCESS) { + fprintf(stderr, "InnoDB: failed to update SYS_INDEXES\n"); + } + + trx_commit_for_mysql(trx); + + trx_free_for_mysql(trx); + + index->page = root_page[i]; + } + + index = dict_table_get_next_index(index); + } + if (UNIV_LIKELY_NULL(heap)) { + mem_heap_free(heap); + } + } else { + /* zip page? */ + size = (ulint) + (size_bytes + / dict_table_flags_to_zip_size(flags)); + fprintf(stderr, "InnoDB: import: table %s seems to be in newer format." + " It may not be able to treated for now.\n", name); + } + /* .exp file should be removed */ + success = os_file_delete(info_file_path); + if (!success) { + success = os_file_delete_if_exists(info_file_path); + } + mem_free(info_file_path); + + fil_system_t* system = fil_system; + mutex_enter(&(system->mutex)); + fil_node_t* node = NULL; + fil_space_t* space; + space = fil_space_get_by_id(id); + if (space) + node = UT_LIST_GET_FIRST(space->chain); + if (node && node->size < size) { + space->size += (size - node->size); + node->size = size; + } + mutex_exit(&(system->mutex)); + } + + ut_free(buf2); + + if (UNIV_UNLIKELY(space_id != id || space_flags != flags)) { + ut_print_timestamp(stderr); + + fputs(" InnoDB: Error: tablespace id and flags in file ", + stderr); + ut_print_filename(stderr, filepath); + fprintf(stderr, " are %lu and %lu, but in the InnoDB\n" + "InnoDB: data dictionary they are %lu and %lu.\n" + "InnoDB: Have you moved InnoDB .ibd files" + " around without using the\n" + "InnoDB: commands DISCARD TABLESPACE and" + " IMPORT TABLESPACE?\n" + "InnoDB: Please refer to\n" + "InnoDB: http://dev.mysql.com/doc/refman/5.1/en/" + "innodb-troubleshooting.html\n" + "InnoDB: for how to resolve the issue.\n", + (ulong) space_id, (ulong) space_flags, + (ulong) id, (ulong) flags); + + ret = FALSE; + + goto func_exit; + } + +skip_check: + success = fil_space_create(filepath, space_id, flags, FIL_TABLESPACE); + + if (!success) { + goto func_exit; + } + + /* We do not measure the size of the file, that is why we pass the 0 + below */ + + fil_node_create(filepath, 0, space_id, FALSE); +func_exit: + os_file_close(file); + mem_free(filepath); + + return(ret); +} + +#ifdef UNIV_HOTBACKUP +/*********************************************************************** +Allocates a file name for an old version of a single-table tablespace. +The string must be freed by caller with mem_free()! */ +static +char* +fil_make_ibbackup_old_name( +/*=======================*/ + /* out, own: file name */ + const char* name) /* in: original file name */ +{ + static const char suffix[] = "_ibbackup_old_vers_"; + ulint len = strlen(name); + char* path = mem_alloc(len + (15 + sizeof suffix)); + + memcpy(path, name, len); + memcpy(path + len, suffix, (sizeof suffix) - 1); + ut_sprintf_timestamp_without_extra_chars(path + len + sizeof suffix); + return(path); +} +#endif /* UNIV_HOTBACKUP */ + +/************************************************************************ +Opens an .ibd file and adds the associated single-table tablespace to the +InnoDB fil0fil.c data structures. */ +static +void +fil_load_single_table_tablespace( +/*=============================*/ + const char* dbname, /* in: database name */ + const char* filename) /* in: file name (not a path), + including the .ibd extension */ +{ + os_file_t file; + char* filepath; + ibool success; + byte* buf2; + byte* page; + ulint space_id; + ulint flags; + ulint size_low; + ulint size_high; + ib_int64_t size; +#ifdef UNIV_HOTBACKUP + fil_space_t* space; +#endif + filepath = mem_alloc(strlen(dbname) + strlen(filename) + + strlen(fil_path_to_mysql_datadir) + 3); + + sprintf(filepath, "%s/%s/%s", fil_path_to_mysql_datadir, dbname, + filename); + srv_normalize_path_for_win(filepath); +#ifdef __WIN__ +# ifndef UNIV_HOTBACKUP + /* If lower_case_table_names is 0 or 2, then MySQL allows database + directory names with upper case letters. On Windows, all table and + database names in InnoDB are internally always in lower case. Put the + file path to lower case, so that we are consistent with InnoDB's + internal data dictionary. */ + + dict_casedn_str(filepath); +# endif /* !UNIV_HOTBACKUP */ +#endif + file = os_file_create_simple_no_error_handling( + filepath, OS_FILE_OPEN, OS_FILE_READ_ONLY, &success); + if (!success) { + /* The following call prints an error message */ + os_file_get_last_error(TRUE); + + fprintf(stderr, + "InnoDB: Error: could not open single-table tablespace" + " file\n" + "InnoDB: %s!\n" + "InnoDB: We do not continue the crash recovery," + " because the table may become\n" + "InnoDB: corrupt if we cannot apply the log records" + " in the InnoDB log to it.\n" + "InnoDB: To fix the problem and start mysqld:\n" + "InnoDB: 1) If there is a permission problem" + " in the file and mysqld cannot\n" + "InnoDB: open the file, you should" + " modify the permissions.\n" + "InnoDB: 2) If the table is not needed, or you can" + " restore it from a backup,\n" + "InnoDB: then you can remove the .ibd file," + " and InnoDB will do a normal\n" + "InnoDB: crash recovery and ignore that table.\n" + "InnoDB: 3) If the file system or the" + " disk is broken, and you cannot remove\n" + "InnoDB: the .ibd file, you can set" + " innodb_force_recovery > 0 in my.cnf\n" + "InnoDB: and force InnoDB to continue crash" + " recovery here.\n", filepath); + + mem_free(filepath); + + if (srv_force_recovery > 0) { + fprintf(stderr, + "InnoDB: innodb_force_recovery" + " was set to %lu. Continuing crash recovery\n" + "InnoDB: even though we cannot access" + " the .ibd file of this table.\n", + srv_force_recovery); + return; + } + + exit(1); + } + + success = os_file_get_size(file, &size_low, &size_high); + + if (!success) { + /* The following call prints an error message */ + os_file_get_last_error(TRUE); + + fprintf(stderr, + "InnoDB: Error: could not measure the size" + " of single-table tablespace file\n" + "InnoDB: %s!\n" + "InnoDB: We do not continue crash recovery," + " because the table will become\n" + "InnoDB: corrupt if we cannot apply the log records" + " in the InnoDB log to it.\n" + "InnoDB: To fix the problem and start mysqld:\n" + "InnoDB: 1) If there is a permission problem" + " in the file and mysqld cannot\n" + "InnoDB: access the file, you should" + " modify the permissions.\n" + "InnoDB: 2) If the table is not needed," + " or you can restore it from a backup,\n" + "InnoDB: then you can remove the .ibd file," + " and InnoDB will do a normal\n" + "InnoDB: crash recovery and ignore that table.\n" + "InnoDB: 3) If the file system or the disk is broken," + " and you cannot remove\n" + "InnoDB: the .ibd file, you can set" + " innodb_force_recovery > 0 in my.cnf\n" + "InnoDB: and force InnoDB to continue" + " crash recovery here.\n", filepath); + + os_file_close(file); + mem_free(filepath); + + if (srv_force_recovery > 0) { + fprintf(stderr, + "InnoDB: innodb_force_recovery" + " was set to %lu. Continuing crash recovery\n" + "InnoDB: even though we cannot access" + " the .ibd file of this table.\n", + srv_force_recovery); + return; + } + + exit(1); + } + + /* TODO: What to do in other cases where we cannot access an .ibd + file during a crash recovery? */ + + /* Every .ibd file is created >= 4 pages in size. Smaller files + cannot be ok. */ + + size = (((ib_int64_t)size_high) << 32) + (ib_int64_t)size_low; +#ifndef UNIV_HOTBACKUP + if (size < FIL_IBD_FILE_INITIAL_SIZE * UNIV_PAGE_SIZE) { + fprintf(stderr, + "InnoDB: Error: the size of single-table tablespace" + " file %s\n" + "InnoDB: is only %lu %lu, should be at least %lu!", + filepath, + (ulong) size_high, + (ulong) size_low, (ulong) (4 * UNIV_PAGE_SIZE)); + os_file_close(file); + mem_free(filepath); + + return; + } +#endif + /* Read the first page of the tablespace if the size big enough */ + + buf2 = ut_malloc(2 * UNIV_PAGE_SIZE); + /* Align the memory for file i/o if we might have O_DIRECT set */ + page = ut_align(buf2, UNIV_PAGE_SIZE); + + if (size >= FIL_IBD_FILE_INITIAL_SIZE * UNIV_PAGE_SIZE) { + success = os_file_read(file, page, 0, 0, UNIV_PAGE_SIZE); + + /* We have to read the tablespace id from the file */ + + space_id = fsp_header_get_space_id(page); + flags = fsp_header_get_flags(page); + } else { + space_id = ULINT_UNDEFINED; + flags = 0; + } + +#ifndef UNIV_HOTBACKUP + if (space_id == ULINT_UNDEFINED || space_id == 0) { + fprintf(stderr, + "InnoDB: Error: tablespace id %lu in file %s" + " is not sensible\n", + (ulong) space_id, + filepath); + goto func_exit; + } +#else + if (space_id == ULINT_UNDEFINED || space_id == 0) { + char* new_path; + + fprintf(stderr, + "InnoDB: Renaming tablespace %s of id %lu,\n" + "InnoDB: to %s_ibbackup_old_vers_<timestamp>\n" + "InnoDB: because its size %lld is too small" + " (< 4 pages 16 kB each),\n" + "InnoDB: or the space id in the file header" + " is not sensible.\n" + "InnoDB: This can happen in an ibbackup run," + " and is not dangerous.\n", + filepath, space_id, filepath, size); + os_file_close(file); + + new_path = fil_make_ibbackup_old_name(filepath); + ut_a(os_file_rename(filepath, new_path)); + + ut_free(buf2); + mem_free(filepath); + mem_free(new_path); + + return; + } + + /* A backup may contain the same space several times, if the space got + renamed at a sensitive time. Since it is enough to have one version of + the space, we rename the file if a space with the same space id + already exists in the tablespace memory cache. We rather rename the + file than delete it, because if there is a bug, we do not want to + destroy valuable data. */ + + mutex_enter(&(fil_system->mutex)); + + space = fil_get_space_for_id_low(space_id); + + if (space) { + char* new_path; + + fprintf(stderr, + "InnoDB: Renaming tablespace %s of id %lu,\n" + "InnoDB: to %s_ibbackup_old_vers_<timestamp>\n" + "InnoDB: because space %s with the same id\n" + "InnoDB: was scanned earlier. This can happen" + " if you have renamed tables\n" + "InnoDB: during an ibbackup run.\n", + filepath, space_id, filepath, + space->name); + os_file_close(file); + + new_path = fil_make_ibbackup_old_name(filepath); + + mutex_exit(&(fil_system->mutex)); + + ut_a(os_file_rename(filepath, new_path)); + + ut_free(buf2); + mem_free(filepath); + mem_free(new_path); + + return; + } + mutex_exit(&(fil_system->mutex)); +#endif + success = fil_space_create(filepath, space_id, flags, FIL_TABLESPACE); + + if (!success) { + + goto func_exit; + } + + /* We do not use the size information we have about the file, because + the rounding formula for extents and pages is somewhat complex; we + let fil_node_open() do that task. */ + + fil_node_create(filepath, 0, space_id, FALSE); +func_exit: + os_file_close(file); + ut_free(buf2); + mem_free(filepath); +} + +/*************************************************************************** +A fault-tolerant function that tries to read the next file name in the +directory. We retry 100 times if os_file_readdir_next_file() returns -1. The +idea is to read as much good data as we can and jump over bad data. */ +static +int +fil_file_readdir_next_file( +/*=======================*/ + /* out: 0 if ok, -1 if error even after the + retries, 1 if at the end of the directory */ + ulint* err, /* out: this is set to DB_ERROR if an error + was encountered, otherwise not changed */ + const char* dirname,/* in: directory name or path */ + os_file_dir_t dir, /* in: directory stream */ + os_file_stat_t* info) /* in/out: buffer where the info is returned */ +{ + ulint i; + int ret; + + for (i = 0; i < 100; i++) { + ret = os_file_readdir_next_file(dirname, dir, info); + + if (ret != -1) { + + return(ret); + } + + fprintf(stderr, + "InnoDB: Error: os_file_readdir_next_file()" + " returned -1 in\n" + "InnoDB: directory %s\n" + "InnoDB: Crash recovery may have failed" + " for some .ibd files!\n", dirname); + + *err = DB_ERROR; + } + + return(-1); +} + +/************************************************************************ +At the server startup, if we need crash recovery, scans the database +directories under the MySQL datadir, looking for .ibd files. Those files are +single-table tablespaces. We need to know the space id in each of them so that +we know into which file we should look to check the contents of a page stored +in the doublewrite buffer, also to know where to apply log records where the +space id is != 0. */ +UNIV_INTERN +ulint +fil_load_single_table_tablespaces(void) +/*===================================*/ + /* out: DB_SUCCESS or error number */ +{ + int ret; + char* dbpath = NULL; + ulint dbpath_len = 100; + os_file_dir_t dir; + os_file_dir_t dbdir; + os_file_stat_t dbinfo; + os_file_stat_t fileinfo; + ulint err = DB_SUCCESS; + + /* The datadir of MySQL is always the default directory of mysqld */ + + dir = os_file_opendir(fil_path_to_mysql_datadir, TRUE); + + if (dir == NULL) { + + return(DB_ERROR); + } + + dbpath = mem_alloc(dbpath_len); + + /* Scan all directories under the datadir. They are the database + directories of MySQL. */ + + ret = fil_file_readdir_next_file(&err, fil_path_to_mysql_datadir, dir, + &dbinfo); + while (ret == 0) { + ulint len; + /* printf("Looking at %s in datadir\n", dbinfo.name); */ + + if (dbinfo.type == OS_FILE_TYPE_FILE + || dbinfo.type == OS_FILE_TYPE_UNKNOWN) { + + goto next_datadir_item; + } + + /* We found a symlink or a directory; try opening it to see + if a symlink is a directory */ + + len = strlen(fil_path_to_mysql_datadir) + + strlen (dbinfo.name) + 2; + if (len > dbpath_len) { + dbpath_len = len; + + if (dbpath) { + mem_free(dbpath); + } + + dbpath = mem_alloc(dbpath_len); + } + sprintf(dbpath, "%s/%s", fil_path_to_mysql_datadir, + dbinfo.name); + srv_normalize_path_for_win(dbpath); + + dbdir = os_file_opendir(dbpath, FALSE); + + if (dbdir != NULL) { + /* printf("Opened dir %s\n", dbinfo.name); */ + + /* We found a database directory; loop through it, + looking for possible .ibd files in it */ + + ret = fil_file_readdir_next_file(&err, dbpath, dbdir, + &fileinfo); + while (ret == 0) { + /* printf( + " Looking at file %s\n", fileinfo.name); */ + + if (fileinfo.type == OS_FILE_TYPE_DIR) { + + goto next_file_item; + } + + /* We found a symlink or a file */ + if (strlen(fileinfo.name) > 4 + && 0 == strcmp(fileinfo.name + + strlen(fileinfo.name) - 4, + ".ibd")) { + /* The name ends in .ibd; try opening + the file */ + fil_load_single_table_tablespace( + dbinfo.name, fileinfo.name); + } +next_file_item: + ret = fil_file_readdir_next_file(&err, + dbpath, dbdir, + &fileinfo); + } + + if (0 != os_file_closedir(dbdir)) { + fputs("InnoDB: Warning: could not" + " close database directory ", stderr); + ut_print_filename(stderr, dbpath); + putc('\n', stderr); + + err = DB_ERROR; + } + } + +next_datadir_item: + ret = fil_file_readdir_next_file(&err, + fil_path_to_mysql_datadir, + dir, &dbinfo); + } + + mem_free(dbpath); + + if (0 != os_file_closedir(dir)) { + fprintf(stderr, + "InnoDB: Error: could not close MySQL datadir\n"); + + return(DB_ERROR); + } + + return(err); +} + +/************************************************************************ +If we need crash recovery, and we have called +fil_load_single_table_tablespaces() and dict_load_single_table_tablespaces(), +we can call this function to print an error message of orphaned .ibd files +for which there is not a data dictionary entry with a matching table name +and space id. */ +UNIV_INTERN +void +fil_print_orphaned_tablespaces(void) +/*================================*/ +{ + fil_system_t* system = fil_system; + fil_space_t* space; + + mutex_enter(&(system->mutex)); + + space = UT_LIST_GET_FIRST(system->space_list); + + while (space) { + if (space->purpose == FIL_TABLESPACE && space->id != 0 + && !space->mark) { + fputs("InnoDB: Warning: tablespace ", stderr); + ut_print_filename(stderr, space->name); + fprintf(stderr, " of id %lu has no matching table in\n" + "InnoDB: the InnoDB data dictionary.\n", + (ulong) space->id); + } + + space = UT_LIST_GET_NEXT(space_list, space); + } + + mutex_exit(&(system->mutex)); +} + +/*********************************************************************** +Returns TRUE if a single-table tablespace does not exist in the memory cache, +or is being deleted there. */ +UNIV_INTERN +ibool +fil_tablespace_deleted_or_being_deleted_in_mem( +/*===========================================*/ + /* out: TRUE if does not exist or is being\ + deleted */ + ulint id, /* in: space id */ + ib_int64_t version)/* in: tablespace_version should be this; if + you pass -1 as the value of this, then this + parameter is ignored */ +{ + fil_system_t* system = fil_system; + fil_space_t* space; + + ut_ad(system); + + mutex_enter(&(system->mutex)); + + space = fil_space_get_by_id(id); + + if (space == NULL || space->is_being_deleted) { + mutex_exit(&(system->mutex)); + + return(TRUE); + } + + if (version != ((ib_int64_t)-1) + && space->tablespace_version != version) { + mutex_exit(&(system->mutex)); + + return(TRUE); + } + + mutex_exit(&(system->mutex)); + + return(FALSE); +} + +/*********************************************************************** +Returns TRUE if a single-table tablespace exists in the memory cache. */ +UNIV_INTERN +ibool +fil_tablespace_exists_in_mem( +/*=========================*/ + /* out: TRUE if exists */ + ulint id) /* in: space id */ +{ + fil_system_t* system = fil_system; + fil_space_t* space; + + ut_ad(system); + + mutex_enter(&(system->mutex)); + + space = fil_space_get_by_id(id); + + if (space == NULL) { + mutex_exit(&(system->mutex)); + + return(FALSE); + } + + mutex_exit(&(system->mutex)); + + return(TRUE); +} + +/*********************************************************************** +Returns TRUE if a matching tablespace exists in the InnoDB tablespace memory +cache. Note that if we have not done a crash recovery at the database startup, +there may be many tablespaces which are not yet in the memory cache. */ +UNIV_INTERN +ibool +fil_space_for_table_exists_in_mem( +/*==============================*/ + /* out: TRUE if a matching tablespace + exists in the memory cache */ + ulint id, /* in: space id */ + const char* name, /* in: table name in the standard + 'databasename/tablename' format or + the dir path to a temp table */ + ibool is_temp, /* in: TRUE if created with CREATE + TEMPORARY TABLE */ + ibool mark_space, /* in: in crash recovery, at database + startup we mark all spaces which have + an associated table in the InnoDB + data dictionary, so that + we can print a warning about orphaned + tablespaces */ + ibool print_error_if_does_not_exist) + /* in: print detailed error + information to the .err log if a + matching tablespace is not found from + memory */ +{ + fil_system_t* system = fil_system; + fil_space_t* namespace; + fil_space_t* space; + char* path; + + ut_ad(system); + + mutex_enter(&(system->mutex)); + + path = fil_make_ibd_name(name, is_temp); + + /* Look if there is a space with the same id */ + + space = fil_space_get_by_id(id); + + /* Look if there is a space with the same name; the name is the + directory path from the datadir to the file */ + + namespace = fil_space_get_by_name(path); + if (space && space == namespace) { + /* Found */ + + if (mark_space) { + space->mark = TRUE; + } + + mem_free(path); + mutex_exit(&(system->mutex)); + + return(TRUE); + } + + if (!print_error_if_does_not_exist) { + + mem_free(path); + mutex_exit(&(system->mutex)); + + return(FALSE); + } + + if (space == NULL) { + if (namespace == NULL) { + ut_print_timestamp(stderr); + fputs(" InnoDB: Error: table ", stderr); + ut_print_filename(stderr, name); + fprintf(stderr, "\n" + "InnoDB: in InnoDB data dictionary" + " has tablespace id %lu,\n" + "InnoDB: but tablespace with that id" + " or name does not exist. Have\n" + "InnoDB: you deleted or moved .ibd files?\n" + "InnoDB: This may also be a table created with" + " CREATE TEMPORARY TABLE\n" + "InnoDB: whose .ibd and .frm files" + " MySQL automatically removed, but the\n" + "InnoDB: table still exists in the" + " InnoDB internal data dictionary.\n", + (ulong) id); + } else { + ut_print_timestamp(stderr); + fputs(" InnoDB: Error: table ", stderr); + ut_print_filename(stderr, name); + fprintf(stderr, "\n" + "InnoDB: in InnoDB data dictionary has" + " tablespace id %lu,\n" + "InnoDB: but a tablespace with that id" + " does not exist. There is\n" + "InnoDB: a tablespace of name %s and id %lu," + " though. Have\n" + "InnoDB: you deleted or moved .ibd files?\n", + (ulong) id, namespace->name, + (ulong) namespace->id); + } +error_exit: + fputs("InnoDB: Please refer to\n" + "InnoDB: http://dev.mysql.com/doc/refman/5.1/en/" + "innodb-troubleshooting.html\n" + "InnoDB: for how to resolve the issue.\n", stderr); + + mem_free(path); + mutex_exit(&(system->mutex)); + + return(FALSE); + } + + if (0 != strcmp(space->name, path)) { + ut_print_timestamp(stderr); + fputs(" InnoDB: Error: table ", stderr); + ut_print_filename(stderr, name); + fprintf(stderr, "\n" + "InnoDB: in InnoDB data dictionary has" + " tablespace id %lu,\n" + "InnoDB: but the tablespace with that id" + " has name %s.\n" + "InnoDB: Have you deleted or moved .ibd files?\n", + (ulong) id, space->name); + + if (namespace != NULL) { + fputs("InnoDB: There is a tablespace" + " with the right name\n" + "InnoDB: ", stderr); + ut_print_filename(stderr, namespace->name); + fprintf(stderr, ", but its id is %lu.\n", + (ulong) namespace->id); + } + + goto error_exit; + } + + mem_free(path); + mutex_exit(&(system->mutex)); + + return(FALSE); +} + +/*********************************************************************** +Checks if a single-table tablespace for a given table name exists in the +tablespace memory cache. */ +static +ulint +fil_get_space_id_for_table( +/*=======================*/ + /* out: space id, ULINT_UNDEFINED if not + found */ + const char* name) /* in: table name in the standard + 'databasename/tablename' format */ +{ + fil_system_t* system = fil_system; + fil_space_t* namespace; + ulint id = ULINT_UNDEFINED; + char* path; + + ut_ad(system); + + mutex_enter(&(system->mutex)); + + path = fil_make_ibd_name(name, FALSE); + + /* Look if there is a space with the same name; the name is the + directory path to the file */ + + namespace = fil_space_get_by_name(path); + + if (namespace) { + id = namespace->id; + } + + mem_free(path); + + mutex_exit(&(system->mutex)); + + return(id); +} + +/************************************************************************** +Tries to extend a data file so that it would accommodate the number of pages +given. The tablespace must be cached in the memory cache. If the space is big +enough already, does nothing. */ +UNIV_INTERN +ibool +fil_extend_space_to_desired_size( +/*=============================*/ + /* out: TRUE if success */ + ulint* actual_size, /* out: size of the space after extension; + if we ran out of disk space this may be lower + than the desired size */ + ulint space_id, /* in: space id */ + ulint size_after_extend)/* in: desired size in pages after the + extension; if the current space size is bigger + than this already, the function does nothing */ +{ + fil_system_t* system = fil_system; + fil_node_t* node; + fil_space_t* space; + byte* buf2; + byte* buf; + ulint buf_size; + ulint start_page_no; + ulint file_start_page_no; + ulint offset_high; + ulint offset_low; + ulint page_size; + ibool success = TRUE; + + fil_mutex_enter_and_prepare_for_io(space_id); + + space = fil_space_get_by_id(space_id); + ut_a(space); + + if (space->size >= size_after_extend) { + /* Space already big enough */ + + *actual_size = space->size; + + mutex_exit(&(system->mutex)); + + return(TRUE); + } + + page_size = dict_table_flags_to_zip_size(space->flags); + if (!page_size) { + page_size = UNIV_PAGE_SIZE; + } + + node = UT_LIST_GET_LAST(space->chain); + + fil_node_prepare_for_io(node, system, space); + + start_page_no = space->size; + file_start_page_no = space->size - node->size; + + /* Extend at most 64 pages at a time */ + buf_size = ut_min(64, size_after_extend - start_page_no) * page_size; + buf2 = mem_alloc(buf_size + page_size); + buf = ut_align(buf2, page_size); + + memset(buf, 0, buf_size); + + while (start_page_no < size_after_extend) { + ulint n_pages = ut_min(buf_size / page_size, + size_after_extend - start_page_no); + + offset_high = (start_page_no - file_start_page_no) + / (4096 * ((1024 * 1024) / page_size)); + offset_low = ((start_page_no - file_start_page_no) + % (4096 * ((1024 * 1024) / page_size))) + * page_size; +#ifdef UNIV_HOTBACKUP + success = os_file_write(node->name, node->handle, buf, + offset_low, offset_high, + page_size * n_pages); +#else + success = os_aio(OS_FILE_WRITE, OS_AIO_SYNC, + node->name, node->handle, buf, + offset_low, offset_high, + page_size * n_pages, + NULL, NULL); +#endif + if (success) { + node->size += n_pages; + space->size += n_pages; + + os_has_said_disk_full = FALSE; + } else { + /* Let us measure the size of the file to determine + how much we were able to extend it */ + + n_pages = ((ulint) + (os_file_get_size_as_iblonglong( + node->handle) + / page_size)) - node->size; + + node->size += n_pages; + space->size += n_pages; + + break; + } + + start_page_no += n_pages; + } + + mem_free(buf2); + + fil_node_complete_io(node, system, OS_FILE_WRITE); + + *actual_size = space->size; + +#ifndef UNIV_HOTBACKUP + if (space_id == 0) { + ulint pages_per_mb = (1024 * 1024) / page_size; + + /* Keep the last data file size info up to date, rounded to + full megabytes */ + + srv_data_file_sizes[srv_n_data_files - 1] + = (node->size / pages_per_mb) * pages_per_mb; + } +#endif /* !UNIV_HOTBACKUP */ + + /* + printf("Extended %s to %lu, actual size %lu pages\n", space->name, + size_after_extend, *actual_size); */ + mutex_exit(&(system->mutex)); + + fil_flush(space_id); + + return(success); +} + +#ifdef UNIV_HOTBACKUP +/************************************************************************ +Extends all tablespaces to the size stored in the space header. During the +ibbackup --apply-log phase we extended the spaces on-demand so that log records +could be applied, but that may have left spaces still too small compared to +the size stored in the space header. */ +UNIV_INTERN +void +fil_extend_tablespaces_to_stored_len(void) +/*======================================*/ +{ + fil_system_t* system = fil_system; + fil_space_t* space; + byte* buf; + ulint actual_size; + ulint size_in_header; + ulint error; + ibool success; + + buf = mem_alloc(UNIV_PAGE_SIZE); + + mutex_enter(&(system->mutex)); + + space = UT_LIST_GET_FIRST(system->space_list); + + while (space) { + ut_a(space->purpose == FIL_TABLESPACE); + + mutex_exit(&(system->mutex)); /* no need to protect with a + mutex, because this is a + single-threaded operation */ + error = fil_read(TRUE, space->id, space->zip_size, + 0, 0, UNIV_PAGE_SIZE, buf, NULL); + ut_a(error == DB_SUCCESS); + + size_in_header = fsp_get_size_low(buf); + + success = fil_extend_space_to_desired_size( + &actual_size, space->id, size_in_header); + if (!success) { + fprintf(stderr, + "InnoDB: Error: could not extend the" + " tablespace of %s\n" + "InnoDB: to the size stored in header," + " %lu pages;\n" + "InnoDB: size after extension %lu pages\n" + "InnoDB: Check that you have free disk space" + " and retry!\n", + space->name, size_in_header, actual_size); + exit(1); + } + + mutex_enter(&(system->mutex)); + + space = UT_LIST_GET_NEXT(space_list, space); + } + + mutex_exit(&(system->mutex)); + + mem_free(buf); +} +#endif + +/*========== RESERVE FREE EXTENTS (for a B-tree split, for example) ===*/ + +/*********************************************************************** +Tries to reserve free extents in a file space. */ +UNIV_INTERN +ibool +fil_space_reserve_free_extents( +/*===========================*/ + /* out: TRUE if succeed */ + ulint id, /* in: space id */ + ulint n_free_now, /* in: number of free extents now */ + ulint n_to_reserve) /* in: how many one wants to reserve */ +{ + fil_system_t* system = fil_system; + fil_space_t* space; + ibool success; + + ut_ad(system); + + mutex_enter(&(system->mutex)); + + space = fil_space_get_by_id(id); + + ut_a(space); + + if (space->n_reserved_extents + n_to_reserve > n_free_now) { + success = FALSE; + } else { + space->n_reserved_extents += n_to_reserve; + success = TRUE; + } + + mutex_exit(&(system->mutex)); + + return(success); +} + +/*********************************************************************** +Releases free extents in a file space. */ +UNIV_INTERN +void +fil_space_release_free_extents( +/*===========================*/ + ulint id, /* in: space id */ + ulint n_reserved) /* in: how many one reserved */ +{ + fil_system_t* system = fil_system; + fil_space_t* space; + + ut_ad(system); + + mutex_enter(&(system->mutex)); + + space = fil_space_get_by_id(id); + + ut_a(space); + ut_a(space->n_reserved_extents >= n_reserved); + + space->n_reserved_extents -= n_reserved; + + mutex_exit(&(system->mutex)); +} + +/*********************************************************************** +Gets the number of reserved extents. If the database is silent, this number +should be zero. */ +UNIV_INTERN +ulint +fil_space_get_n_reserved_extents( +/*=============================*/ + ulint id) /* in: space id */ +{ + fil_system_t* system = fil_system; + fil_space_t* space; + ulint n; + + ut_ad(system); + + mutex_enter(&(system->mutex)); + + space = fil_space_get_by_id(id); + + ut_a(space); + + n = space->n_reserved_extents; + + mutex_exit(&(system->mutex)); + + return(n); +} + +/*============================ FILE I/O ================================*/ + +/************************************************************************ +NOTE: you must call fil_mutex_enter_and_prepare_for_io() first! + +Prepares a file node for i/o. Opens the file if it is closed. Updates the +pending i/o's field in the node and the system appropriately. Takes the node +off the LRU list if it is in the LRU list. The caller must hold the fil_sys +mutex. */ +static +void +fil_node_prepare_for_io( +/*====================*/ + fil_node_t* node, /* in: file node */ + fil_system_t* system, /* in: tablespace memory cache */ + fil_space_t* space) /* in: space */ +{ + ut_ad(node && system && space); + ut_ad(mutex_own(&(system->mutex))); + + if (system->n_open > system->max_n_open + 5) { + ut_print_timestamp(stderr); + fprintf(stderr, + " InnoDB: Warning: open files %lu" + " exceeds the limit %lu\n", + (ulong) system->n_open, + (ulong) system->max_n_open); + } + + if (node->open == FALSE) { + /* File is closed: open it */ + ut_a(node->n_pending == 0); + + fil_node_open_file(node, system, space); + } + + if (node->n_pending == 0 && space->purpose == FIL_TABLESPACE + && space->id != 0) { + /* The node is in the LRU list, remove it */ + + ut_a(UT_LIST_GET_LEN(system->LRU) > 0); + + UT_LIST_REMOVE(LRU, system->LRU, node); + } + + node->n_pending++; +} + +/************************************************************************ +Updates the data structures when an i/o operation finishes. Updates the +pending i/o's field in the node appropriately. */ +static +void +fil_node_complete_io( +/*=================*/ + fil_node_t* node, /* in: file node */ + fil_system_t* system, /* in: tablespace memory cache */ + ulint type) /* in: OS_FILE_WRITE or OS_FILE_READ; marks + the node as modified if + type == OS_FILE_WRITE */ +{ + ut_ad(node); + ut_ad(system); + ut_ad(mutex_own(&(system->mutex))); + + ut_a(node->n_pending > 0); + + node->n_pending--; + + if (type == OS_FILE_WRITE) { + system->modification_counter++; + node->modification_counter = system->modification_counter; + + if (!node->space->is_in_unflushed_spaces) { + + node->space->is_in_unflushed_spaces = TRUE; + UT_LIST_ADD_FIRST(unflushed_spaces, + system->unflushed_spaces, + node->space); + } + } + + if (node->n_pending == 0 && node->space->purpose == FIL_TABLESPACE + && node->space->id != 0) { + /* The node must be put back to the LRU list */ + UT_LIST_ADD_FIRST(LRU, system->LRU, node); + } +} + +/************************************************************************ +Report information about an invalid page access. */ +static +void +fil_report_invalid_page_access( +/*===========================*/ + ulint block_offset, /* in: block offset */ + ulint space_id, /* in: space id */ + const char* space_name, /* in: space name */ + ulint byte_offset, /* in: byte offset */ + ulint len, /* in: I/O length */ + ulint type) /* in: I/O type */ +{ + fprintf(stderr, + "InnoDB: Error: trying to access page number %lu" + " in space %lu,\n" + "InnoDB: space name %s,\n" + "InnoDB: which is outside the tablespace bounds.\n" + "InnoDB: Byte offset %lu, len %lu, i/o type %lu.\n" + "InnoDB: If you get this error at mysqld startup," + " please check that\n" + "InnoDB: your my.cnf matches the ibdata files" + " that you have in the\n" + "InnoDB: MySQL server.\n", + (ulong) block_offset, (ulong) space_id, space_name, + (ulong) byte_offset, (ulong) len, (ulong) type); +} + +/************************************************************************ +Reads or writes data. This operation is asynchronous (aio). */ +UNIV_INTERN +ulint +fil_io( +/*===*/ + /* out: DB_SUCCESS, or DB_TABLESPACE_DELETED + if we are trying to do i/o on a tablespace + which does not exist */ + ulint type, /* in: OS_FILE_READ or OS_FILE_WRITE, + ORed to OS_FILE_LOG, if a log i/o + and ORed to OS_AIO_SIMULATED_WAKE_LATER + if simulated aio and we want to post a + batch of i/os; NOTE that a simulated batch + may introduce hidden chances of deadlocks, + because i/os are not actually handled until + all have been posted: use with great + caution! */ + ibool sync, /* in: TRUE if synchronous aio is desired */ + ulint space_id, /* in: space id */ + ulint zip_size, /* in: compressed page size in bytes; + 0 for uncompressed pages */ + ulint block_offset, /* in: offset in number of blocks */ + ulint byte_offset, /* in: remainder of offset in bytes; in + aio this must be divisible by the OS block + size */ + ulint len, /* in: how many bytes to read or write; this + must not cross a file boundary; in aio this + must be a block size multiple */ + void* buf, /* in/out: buffer where to store read data + or from where to write; in aio this must be + appropriately aligned */ + void* message) /* in: message for aio handler if non-sync + aio used, else ignored */ +{ + fil_system_t* system = fil_system; + ulint mode; + fil_space_t* space; + fil_node_t* node; + ulint offset_high; + ulint offset_low; + ibool ret; + ulint is_log; + ulint wake_later; + + is_log = type & OS_FILE_LOG; + type = type & ~OS_FILE_LOG; + + wake_later = type & OS_AIO_SIMULATED_WAKE_LATER; + type = type & ~OS_AIO_SIMULATED_WAKE_LATER; + + ut_ad(byte_offset < UNIV_PAGE_SIZE); + ut_ad(!zip_size || !byte_offset); + ut_ad(ut_is_2pow(zip_size)); + ut_ad(buf); + ut_ad(len > 0); +#if (1 << UNIV_PAGE_SIZE_SHIFT) != UNIV_PAGE_SIZE +# error "(1 << UNIV_PAGE_SIZE_SHIFT) != UNIV_PAGE_SIZE" +#endif + ut_ad(fil_validate()); +#ifndef UNIV_LOG_DEBUG + /* ibuf bitmap pages must be read in the sync aio mode: */ + ut_ad(recv_no_ibuf_operations || (type == OS_FILE_WRITE) + || !ibuf_bitmap_page(zip_size, block_offset) + || sync || is_log); + ut_ad(!ibuf_inside() || is_log || (type == OS_FILE_WRITE) + || ibuf_page(space_id, zip_size, block_offset, NULL)); +#endif + if (sync) { + mode = OS_AIO_SYNC; + } else if (is_log) { + mode = OS_AIO_LOG; + } else if (type == OS_FILE_READ + && !recv_no_ibuf_operations + && ibuf_page(space_id, zip_size, block_offset, NULL)) { + mode = OS_AIO_IBUF; + } else { + mode = OS_AIO_NORMAL; + } + + if (type == OS_FILE_READ) { + srv_data_read+= len; + } else if (type == OS_FILE_WRITE) { + srv_data_written+= len; + } + + /* Reserve the fil_system mutex and make sure that we can open at + least one file while holding it, if the file is not already open */ + + fil_mutex_enter_and_prepare_for_io(space_id); + + space = fil_space_get_by_id(space_id); + + if (!space) { + mutex_exit(&(system->mutex)); + + ut_print_timestamp(stderr); + fprintf(stderr, + " InnoDB: Error: trying to do i/o" + " to a tablespace which does not exist.\n" + "InnoDB: i/o type %lu, space id %lu," + " page no. %lu, i/o length %lu bytes\n", + (ulong) type, (ulong) space_id, (ulong) block_offset, + (ulong) len); + + return(DB_TABLESPACE_DELETED); + } + + ut_ad((mode != OS_AIO_IBUF) || (space->purpose == FIL_TABLESPACE)); + + node = UT_LIST_GET_FIRST(space->chain); + + for (;;) { + if (UNIV_UNLIKELY(node == NULL)) { + fil_report_invalid_page_access( + block_offset, space_id, space->name, + byte_offset, len, type); + + ut_error; + } + + if (space->id != 0 && node->size == 0) { + /* We do not know the size of a single-table tablespace + before we open the file */ + + break; + } + + if (node->size > block_offset) { + /* Found! */ + break; + } else { + block_offset -= node->size; + node = UT_LIST_GET_NEXT(chain, node); + } + } + + /* Open file if closed */ + fil_node_prepare_for_io(node, system, space); + + /* Check that at least the start offset is within the bounds of a + single-table tablespace */ + if (UNIV_UNLIKELY(node->size <= block_offset) + && space->id != 0 && space->purpose == FIL_TABLESPACE) { + + fil_report_invalid_page_access( + block_offset, space_id, space->name, byte_offset, + len, type); + + ut_error; + } + + /* Now we have made the changes in the data structures of system */ + mutex_exit(&(system->mutex)); + + /* Calculate the low 32 bits and the high 32 bits of the file offset */ + + if (!zip_size) { + offset_high = (block_offset >> (32 - UNIV_PAGE_SIZE_SHIFT)); + offset_low = ((block_offset << UNIV_PAGE_SIZE_SHIFT) + & 0xFFFFFFFFUL) + byte_offset; + + ut_a(node->size - block_offset + >= ((byte_offset + len + (UNIV_PAGE_SIZE - 1)) + / UNIV_PAGE_SIZE)); + } else { + ulint zip_size_shift; + switch (zip_size) { + case 1024: zip_size_shift = 10; break; + case 2048: zip_size_shift = 11; break; + case 4096: zip_size_shift = 12; break; + case 8192: zip_size_shift = 13; break; + case 16384: zip_size_shift = 14; break; + default: ut_error; + } + offset_high = block_offset >> (32 - zip_size_shift); + offset_low = (block_offset << zip_size_shift & 0xFFFFFFFFUL) + + byte_offset; + ut_a(node->size - block_offset + >= (len + (zip_size - 1)) / zip_size); + } + + /* Do aio */ + + ut_a(byte_offset % OS_FILE_LOG_BLOCK_SIZE == 0); + ut_a((len % OS_FILE_LOG_BLOCK_SIZE) == 0); + +#ifdef UNIV_HOTBACKUP + /* In ibbackup do normal i/o, not aio */ + if (type == OS_FILE_READ) { + ret = os_file_read(node->handle, buf, offset_low, offset_high, + len); + } else { + ret = os_file_write(node->name, node->handle, buf, + offset_low, offset_high, len); + } +#else + /* Queue the aio request */ + ret = os_aio(type, mode | wake_later, node->name, node->handle, buf, + offset_low, offset_high, len, node, message); +#endif + ut_a(ret); + + if (mode == OS_AIO_SYNC) { + /* The i/o operation is already completed when we return from + os_aio: */ + + mutex_enter(&(system->mutex)); + + fil_node_complete_io(node, system, type); + + mutex_exit(&(system->mutex)); + + ut_ad(fil_validate()); + } + + return(DB_SUCCESS); +} + +/************************************************************************** +Waits for an aio operation to complete. This function is used to write the +handler for completed requests. The aio array of pending requests is divided +into segments (see os0file.c for more info). The thread specifies which +segment it wants to wait for. */ +UNIV_INTERN +void +fil_aio_wait( +/*=========*/ + ulint segment) /* in: the number of the segment in the aio + array to wait for */ +{ + fil_system_t* system = fil_system; + ibool ret; + fil_node_t* fil_node; + void* message; + ulint type; + + ut_ad(fil_validate()); + + if (os_aio_use_native_aio) { + srv_set_io_thread_op_info(segment, "native aio handle"); +#ifdef WIN_ASYNC_IO + ret = os_aio_windows_handle(segment, 0, &fil_node, + &message, &type); +#else + ret = 0; /* Eliminate compiler warning */ + ut_error; +#endif + } else { + srv_set_io_thread_op_info(segment, "simulated aio handle"); + + ret = os_aio_simulated_handle(segment, &fil_node, + &message, &type); + } + + ut_a(ret); + + srv_set_io_thread_op_info(segment, "complete io for fil node"); + + mutex_enter(&(system->mutex)); + + fil_node_complete_io(fil_node, fil_system, type); + + mutex_exit(&(system->mutex)); + + ut_ad(fil_validate()); + + /* Do the i/o handling */ + /* IMPORTANT: since i/o handling for reads will read also the insert + buffer in tablespace 0, you have to be very careful not to introduce + deadlocks in the i/o system. We keep tablespace 0 data files always + open, and use a special i/o thread to serve insert buffer requests. */ + + if (fil_node->space->purpose == FIL_TABLESPACE) { + srv_set_io_thread_op_info(segment, "complete io for buf page"); + buf_page_io_complete(message); + } else { + srv_set_io_thread_op_info(segment, "complete io for log"); + log_io_complete(message); + } +} + +/************************************************************************** +Flushes to disk possible writes cached by the OS. If the space does not exist +or is being dropped, does not do anything. */ +UNIV_INTERN +void +fil_flush( +/*======*/ + ulint space_id) /* in: file space id (this can be a group of + log files or a tablespace of the database) */ +{ + fil_system_t* system = fil_system; + fil_space_t* space; + fil_node_t* node; + os_file_t file; + ib_int64_t old_mod_counter; + + mutex_enter(&(system->mutex)); + + space = fil_space_get_by_id(space_id); + + if (!space || space->is_being_deleted) { + mutex_exit(&(system->mutex)); + + return; + } + + space->n_pending_flushes++; /* prevent dropping of the space while + we are flushing */ + node = UT_LIST_GET_FIRST(space->chain); + + while (node) { + if (node->modification_counter > node->flush_counter) { + ut_a(node->open); + + /* We want to flush the changes at least up to + old_mod_counter */ + old_mod_counter = node->modification_counter; + + if (space->purpose == FIL_TABLESPACE) { + fil_n_pending_tablespace_flushes++; + } else { + fil_n_pending_log_flushes++; + fil_n_log_flushes++; + } +#ifdef __WIN__ + if (node->is_raw_disk) { + + goto skip_flush; + } +#endif +retry: + if (node->n_pending_flushes > 0) { + /* We want to avoid calling os_file_flush() on + the file twice at the same time, because we do + not know what bugs OS's may contain in file + i/o; sleep for a while */ + + mutex_exit(&(system->mutex)); + + os_thread_sleep(20000); + + mutex_enter(&(system->mutex)); + + if (node->flush_counter >= old_mod_counter) { + + goto skip_flush; + } + + goto retry; + } + + ut_a(node->open); + file = node->handle; + node->n_pending_flushes++; + + mutex_exit(&(system->mutex)); + + /* fprintf(stderr, "Flushing to file %s\n", + node->name); */ + + os_file_flush(file); + + mutex_enter(&(system->mutex)); + + node->n_pending_flushes--; +skip_flush: + if (node->flush_counter < old_mod_counter) { + node->flush_counter = old_mod_counter; + + if (space->is_in_unflushed_spaces + && fil_space_is_flushed(space)) { + + space->is_in_unflushed_spaces = FALSE; + + UT_LIST_REMOVE( + unflushed_spaces, + system->unflushed_spaces, + space); + } + } + + if (space->purpose == FIL_TABLESPACE) { + fil_n_pending_tablespace_flushes--; + } else { + fil_n_pending_log_flushes--; + } + } + + node = UT_LIST_GET_NEXT(chain, node); + } + + space->n_pending_flushes--; + + mutex_exit(&(system->mutex)); +} + +/************************************************************************** +Flushes to disk the writes in file spaces of the given type possibly cached by +the OS. */ +UNIV_INTERN +void +fil_flush_file_spaces( +/*==================*/ + ulint purpose) /* in: FIL_TABLESPACE, FIL_LOG */ +{ + fil_system_t* system = fil_system; + fil_space_t* space; + ulint* space_ids; + ulint n_space_ids; + ulint i; + + mutex_enter(&(system->mutex)); + + n_space_ids = UT_LIST_GET_LEN(system->unflushed_spaces); + if (n_space_ids == 0) { + + mutex_exit(&system->mutex); + return; + } + + /* Assemble a list of space ids to flush. Previously, we + traversed system->unflushed_spaces and called UT_LIST_GET_NEXT() + on a space that was just removed from the list by fil_flush(). + Thus, the space could be dropped and the memory overwritten. */ + space_ids = mem_alloc(n_space_ids * sizeof *space_ids); + + n_space_ids = 0; + + for (space = UT_LIST_GET_FIRST(system->unflushed_spaces); + space; + space = UT_LIST_GET_NEXT(unflushed_spaces, space)) { + + if (space->purpose == purpose && !space->is_being_deleted) { + + space_ids[n_space_ids++] = space->id; + } + } + + mutex_exit(&system->mutex); + + /* Flush the spaces. It will not hurt to call fil_flush() on + a non-existing space id. */ + for (i = 0; i < n_space_ids; i++) { + + fil_flush(space_ids[i]); + } + + mem_free(space_ids); +} + +/********************************************************************** +Checks the consistency of the tablespace cache. */ +UNIV_INTERN +ibool +fil_validate(void) +/*==============*/ + /* out: TRUE if ok */ +{ + fil_system_t* system = fil_system; + fil_space_t* space; + fil_node_t* fil_node; + ulint n_open = 0; + ulint i; + + mutex_enter(&(system->mutex)); + + /* Look for spaces in the hash table */ + + for (i = 0; i < hash_get_n_cells(system->spaces); i++) { + + space = HASH_GET_FIRST(system->spaces, i); + + while (space != NULL) { + UT_LIST_VALIDATE(chain, fil_node_t, space->chain); + + fil_node = UT_LIST_GET_FIRST(space->chain); + + while (fil_node != NULL) { + if (fil_node->n_pending > 0) { + ut_a(fil_node->open); + } + + if (fil_node->open) { + n_open++; + } + fil_node = UT_LIST_GET_NEXT(chain, fil_node); + } + space = HASH_GET_NEXT(hash, space); + } + } + + ut_a(system->n_open == n_open); + + UT_LIST_VALIDATE(LRU, fil_node_t, system->LRU); + + fil_node = UT_LIST_GET_FIRST(system->LRU); + + while (fil_node != NULL) { + ut_a(fil_node->n_pending == 0); + ut_a(fil_node->open); + ut_a(fil_node->space->purpose == FIL_TABLESPACE); + ut_a(fil_node->space->id != 0); + + fil_node = UT_LIST_GET_NEXT(LRU, fil_node); + } + + mutex_exit(&(system->mutex)); + + return(TRUE); +} + +/************************************************************************ +Returns TRUE if file address is undefined. */ +UNIV_INTERN +ibool +fil_addr_is_null( +/*=============*/ + /* out: TRUE if undefined */ + fil_addr_t addr) /* in: address */ +{ + return(addr.page == FIL_NULL); +} + +/************************************************************************ +Accessor functions for a file page */ +UNIV_INTERN +ulint +fil_page_get_prev(const byte* page) +{ + return(mach_read_from_4(page + FIL_PAGE_PREV)); +} + +UNIV_INTERN +ulint +fil_page_get_next(const byte* page) +{ + return(mach_read_from_4(page + FIL_PAGE_NEXT)); +} + +/************************************************************************* +Sets the file page type. */ +UNIV_INTERN +void +fil_page_set_type( +/*==============*/ + byte* page, /* in: file page */ + ulint type) /* in: type */ +{ + ut_ad(page); + + mach_write_to_2(page + FIL_PAGE_TYPE, type); +} + +/************************************************************************* +Gets the file page type. */ +UNIV_INTERN +ulint +fil_page_get_type( +/*==============*/ + /* out: type; NOTE that if the type + has not been written to page, the return value + not defined */ + const byte* page) /* in: file page */ +{ + ut_ad(page); + + return(mach_read_from_2(page + FIL_PAGE_TYPE)); +} + +/************************************************************************* +Return local hash table informations. */ + +ulint +fil_system_hash_cells(void) +/*=======================*/ +{ + if (fil_system) { + return (fil_system->spaces->n_cells + + fil_system->name_hash->n_cells); + } else { + return 0; + } +} + +ulint +fil_system_hash_nodes(void) +/*=======================*/ +{ + if (fil_system) { + return (UT_LIST_GET_LEN(fil_system->space_list) + * (sizeof(fil_space_t) + MEM_BLOCK_HEADER_SIZE)); + } else { + return 0; + } +} diff --git a/storage/xtradb/fsp/fsp0fsp.c b/storage/xtradb/fsp/fsp0fsp.c new file mode 100644 index 00000000000..25d260daeea --- /dev/null +++ b/storage/xtradb/fsp/fsp0fsp.c @@ -0,0 +1,4284 @@ +/***************************************************************************** + +Copyright (c) 1995, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/********************************************************************** +File space management + +Created 11/29/1995 Heikki Tuuri +***********************************************************************/ + +#include "fsp0fsp.h" + +#ifdef UNIV_NONINL +#include "fsp0fsp.ic" +#endif + +#include "buf0buf.h" +#include "fil0fil.h" +#include "sync0sync.h" +#include "mtr0log.h" +#include "fut0fut.h" +#include "ut0byte.h" +#include "srv0srv.h" +#include "page0zip.h" +#include "ibuf0ibuf.h" +#include "btr0btr.h" +#include "btr0sea.h" +#include "dict0boot.h" +#include "dict0mem.h" +#include "log0log.h" + + +#define FSP_HEADER_OFFSET FIL_PAGE_DATA /* Offset of the space header + within a file page */ + +/* The data structures in files are defined just as byte strings in C */ +typedef byte fsp_header_t; +typedef byte xdes_t; + +/* SPACE HEADER + ============ + +File space header data structure: this data structure is contained in the +first page of a space. The space for this header is reserved in every extent +descriptor page, but used only in the first. */ + +/*-------------------------------------*/ +#define FSP_SPACE_ID 0 /* space id */ +#define FSP_NOT_USED 4 /* this field contained a value up to + which we know that the modifications + in the database have been flushed to + the file space; not used now */ +#define FSP_SIZE 8 /* Current size of the space in + pages */ +#define FSP_FREE_LIMIT 12 /* Minimum page number for which the + free list has not been initialized: + the pages >= this limit are, by + definition, free; note that in a + single-table tablespace where size + < 64 pages, this number is 64, i.e., + we have initialized the space + about the first extent, but have not + physically allocted those pages to the + file */ +#define FSP_SPACE_FLAGS 16 /* table->flags & ~DICT_TF_COMPACT */ +#define FSP_FRAG_N_USED 20 /* number of used pages in the + FSP_FREE_FRAG list */ +#define FSP_FREE 24 /* list of free extents */ +#define FSP_FREE_FRAG (24 + FLST_BASE_NODE_SIZE) + /* list of partially free extents not + belonging to any segment */ +#define FSP_FULL_FRAG (24 + 2 * FLST_BASE_NODE_SIZE) + /* list of full extents not belonging + to any segment */ +#define FSP_SEG_ID (24 + 3 * FLST_BASE_NODE_SIZE) + /* 8 bytes which give the first unused + segment id */ +#define FSP_SEG_INODES_FULL (32 + 3 * FLST_BASE_NODE_SIZE) + /* list of pages containing segment + headers, where all the segment inode + slots are reserved */ +#define FSP_SEG_INODES_FREE (32 + 4 * FLST_BASE_NODE_SIZE) + /* list of pages containing segment + headers, where not all the segment + header slots are reserved */ +/*-------------------------------------*/ +/* File space header size */ +#define FSP_HEADER_SIZE (32 + 5 * FLST_BASE_NODE_SIZE) + +#define FSP_FREE_ADD 4 /* this many free extents are added + to the free list from above + FSP_FREE_LIMIT at a time */ + +/* FILE SEGMENT INODE + ================== + +Segment inode which is created for each segment in a tablespace. NOTE: in +purge we assume that a segment having only one currently used page can be +freed in a few steps, so that the freeing cannot fill the file buffer with +bufferfixed file pages. */ + +typedef byte fseg_inode_t; + +#define FSEG_INODE_PAGE_NODE FSEG_PAGE_DATA + /* the list node for linking + segment inode pages */ + +#define FSEG_ARR_OFFSET (FSEG_PAGE_DATA + FLST_NODE_SIZE) +/*-------------------------------------*/ +#define FSEG_ID 0 /* 8 bytes of segment id: if this is + ut_dulint_zero, it means that the + header is unused */ +#define FSEG_NOT_FULL_N_USED 8 + /* number of used segment pages in + the FSEG_NOT_FULL list */ +#define FSEG_FREE 12 + /* list of free extents of this + segment */ +#define FSEG_NOT_FULL (12 + FLST_BASE_NODE_SIZE) + /* list of partially free extents */ +#define FSEG_FULL (12 + 2 * FLST_BASE_NODE_SIZE) + /* list of full extents */ +#define FSEG_MAGIC_N (12 + 3 * FLST_BASE_NODE_SIZE) + /* magic number used in debugging */ +#define FSEG_FRAG_ARR (16 + 3 * FLST_BASE_NODE_SIZE) + /* array of individual pages + belonging to this segment in fsp + fragment extent lists */ +#define FSEG_FRAG_ARR_N_SLOTS (FSP_EXTENT_SIZE / 2) + /* number of slots in the array for + the fragment pages */ +#define FSEG_FRAG_SLOT_SIZE 4 /* a fragment page slot contains its + page number within space, FIL_NULL + means that the slot is not in use */ +/*-------------------------------------*/ +#define FSEG_INODE_SIZE \ + (16 + 3 * FLST_BASE_NODE_SIZE \ + + FSEG_FRAG_ARR_N_SLOTS * FSEG_FRAG_SLOT_SIZE) + +#define FSP_SEG_INODES_PER_PAGE(zip_size) \ + (((zip_size ? zip_size : UNIV_PAGE_SIZE) \ + - FSEG_ARR_OFFSET - 10) / FSEG_INODE_SIZE) + /* Number of segment inodes which fit on a + single page */ + +#define FSEG_MAGIC_N_VALUE 97937874 + +#define FSEG_FILLFACTOR 8 /* If this value is x, then if + the number of unused but reserved + pages in a segment is less than + reserved pages * 1/x, and there are + at least FSEG_FRAG_LIMIT used pages, + then we allow a new empty extent to + be added to the segment in + fseg_alloc_free_page. Otherwise, we + use unused pages of the segment. */ + +#define FSEG_FRAG_LIMIT FSEG_FRAG_ARR_N_SLOTS + /* If the segment has >= this many + used pages, it may be expanded by + allocating extents to the segment; + until that only individual fragment + pages are allocated from the space */ + +#define FSEG_FREE_LIST_LIMIT 40 /* If the reserved size of a segment + is at least this many extents, we + allow extents to be put to the free + list of the extent: at most + FSEG_FREE_LIST_MAX_LEN many */ +#define FSEG_FREE_LIST_MAX_LEN 4 + + +/* EXTENT DESCRIPTOR + ================= + +File extent descriptor data structure: contains bits to tell which pages in +the extent are free and which contain old tuple version to clean. */ + +/*-------------------------------------*/ +#define XDES_ID 0 /* The identifier of the segment + to which this extent belongs */ +#define XDES_FLST_NODE 8 /* The list node data structure + for the descriptors */ +#define XDES_STATE (FLST_NODE_SIZE + 8) + /* contains state information + of the extent */ +#define XDES_BITMAP (FLST_NODE_SIZE + 12) + /* Descriptor bitmap of the pages + in the extent */ +/*-------------------------------------*/ + +#define XDES_BITS_PER_PAGE 2 /* How many bits are there per page */ +#define XDES_FREE_BIT 0 /* Index of the bit which tells if + the page is free */ +#define XDES_CLEAN_BIT 1 /* NOTE: currently not used! + Index of the bit which tells if + there are old versions of tuples + on the page */ +/* States of a descriptor */ +#define XDES_FREE 1 /* extent is in free list of space */ +#define XDES_FREE_FRAG 2 /* extent is in free fragment list of + space */ +#define XDES_FULL_FRAG 3 /* extent is in full fragment list of + space */ +#define XDES_FSEG 4 /* extent belongs to a segment */ + +/* File extent data structure size in bytes. */ +#define XDES_SIZE \ + (XDES_BITMAP + UT_BITS_IN_BYTES(FSP_EXTENT_SIZE * XDES_BITS_PER_PAGE)) + +/* Offset of the descriptor array on a descriptor page */ +#define XDES_ARR_OFFSET (FSP_HEADER_OFFSET + FSP_HEADER_SIZE) + +/************************************************************************** +Returns an extent to the free list of a space. */ +static +void +fsp_free_extent( +/*============*/ + ulint space, /* in: space id */ + ulint zip_size,/* in: compressed page size in bytes + or 0 for uncompressed pages */ + ulint page, /* in: page offset in the extent */ + mtr_t* mtr); /* in: mtr */ +/************************************************************************** +Frees an extent of a segment to the space free list. */ +static +void +fseg_free_extent( +/*=============*/ + fseg_inode_t* seg_inode, /* in: segment inode */ + ulint space, /* in: space id */ + ulint zip_size,/* in: compressed page size in bytes + or 0 for uncompressed pages */ + ulint page, /* in: page offset in the extent */ + mtr_t* mtr); /* in: mtr handle */ +/************************************************************************** +Calculates the number of pages reserved by a segment, and how +many pages are currently used. */ +static +ulint +fseg_n_reserved_pages_low( +/*======================*/ + /* out: number of reserved pages */ + fseg_inode_t* header, /* in: segment inode */ + ulint* used, /* out: number of pages used (<= reserved) */ + mtr_t* mtr); /* in: mtr handle */ +/************************************************************************ +Marks a page used. The page must reside within the extents of the given +segment. */ +static +void +fseg_mark_page_used( +/*================*/ + fseg_inode_t* seg_inode,/* in: segment inode */ + ulint space, /* in: space id */ + ulint zip_size,/* in: compressed page size in bytes + or 0 for uncompressed pages */ + ulint page, /* in: page offset */ + mtr_t* mtr); /* in: mtr */ +/************************************************************************** +Returns the first extent descriptor for a segment. We think of the extent +lists of the segment catenated in the order FSEG_FULL -> FSEG_NOT_FULL +-> FSEG_FREE. */ +static +xdes_t* +fseg_get_first_extent( +/*==================*/ + /* out: the first extent descriptor, or NULL if + none */ + fseg_inode_t* inode, /* in: segment inode */ + ulint space, /* in: space id */ + ulint zip_size,/* in: compressed page size in bytes + or 0 for uncompressed pages */ + mtr_t* mtr); /* in: mtr */ +/************************************************************************** +Puts new extents to the free list if +there are free extents above the free limit. If an extent happens +to contain an extent descriptor page, the extent is put to +the FSP_FREE_FRAG list with the page marked as used. */ +static +void +fsp_fill_free_list( +/*===============*/ + ibool init_space, /* in: TRUE if this is a single-table + tablespace and we are only initing + the tablespace's first extent + descriptor page and ibuf bitmap page; + then we do not allocate more extents */ + ulint space, /* in: space */ + fsp_header_t* header, /* in: space header */ + mtr_t* mtr); /* in: mtr */ +/************************************************************************** +Allocates a single free page from a segment. This function implements +the intelligent allocation strategy which tries to minimize file space +fragmentation. */ +static +ulint +fseg_alloc_free_page_low( +/*=====================*/ + /* out: the allocated page number, FIL_NULL + if no page could be allocated */ + ulint space, /* in: space */ + ulint zip_size,/* in: compressed page size in bytes + or 0 for uncompressed pages */ + fseg_inode_t* seg_inode, /* in: segment inode */ + ulint hint, /* in: hint of which page would be desirable */ + byte direction, /* in: if the new page is needed because + of an index page split, and records are + inserted there in order, into which + direction they go alphabetically: FSP_DOWN, + FSP_UP, FSP_NO_DIR */ + mtr_t* mtr); /* in: mtr handle */ + + +/************************************************************************** +Reads the file space size stored in the header page. */ +UNIV_INTERN +ulint +fsp_get_size_low( +/*=============*/ + /* out: tablespace size stored in the space header */ + page_t* page) /* in: header page (page 0 in the tablespace) */ +{ + return(mach_read_from_4(page + FSP_HEADER_OFFSET + FSP_SIZE)); +} + +/************************************************************************** +Gets a pointer to the space header and x-locks its page. */ +UNIV_INLINE +fsp_header_t* +fsp_get_space_header( +/*=================*/ + /* out: pointer to the space header, page x-locked */ + ulint id, /* in: space id */ + ulint zip_size,/* in: compressed page size in bytes + or 0 for uncompressed pages */ + mtr_t* mtr) /* in: mtr */ +{ + buf_block_t* block; + fsp_header_t* header; + + ut_ad(ut_is_2pow(zip_size)); + ut_ad(zip_size <= UNIV_PAGE_SIZE); + ut_ad(!zip_size || zip_size >= PAGE_ZIP_MIN_SIZE); + ut_ad(id || !zip_size); + + block = buf_page_get(id, zip_size, 0, RW_X_LATCH, mtr); + header = FSP_HEADER_OFFSET + buf_block_get_frame(block); + buf_block_dbg_add_level(block, SYNC_FSP_PAGE); + + ut_ad(id == mach_read_from_4(FSP_SPACE_ID + header)); + ut_ad(zip_size == dict_table_flags_to_zip_size( + mach_read_from_4(FSP_SPACE_FLAGS + header))); + return(header); +} + +/************************************************************************** +Gets a descriptor bit of a page. */ +UNIV_INLINE +ibool +xdes_get_bit( +/*=========*/ + /* out: TRUE if free */ + xdes_t* descr, /* in: descriptor */ + ulint bit, /* in: XDES_FREE_BIT or XDES_CLEAN_BIT */ + ulint offset, /* in: page offset within extent: + 0 ... FSP_EXTENT_SIZE - 1 */ + mtr_t* mtr) /* in: mtr */ +{ + ulint index; + ulint byte_index; + ulint bit_index; + + ut_ad(mtr_memo_contains_page(mtr, descr, MTR_MEMO_PAGE_X_FIX)); + ut_ad((bit == XDES_FREE_BIT) || (bit == XDES_CLEAN_BIT)); + ut_ad(offset < FSP_EXTENT_SIZE); + + index = bit + XDES_BITS_PER_PAGE * offset; + + byte_index = index / 8; + bit_index = index % 8; + + return(ut_bit_get_nth(mtr_read_ulint(descr + XDES_BITMAP + byte_index, + MLOG_1BYTE, mtr), + bit_index)); +} + +/************************************************************************** +Sets a descriptor bit of a page. */ +UNIV_INLINE +void +xdes_set_bit( +/*=========*/ + xdes_t* descr, /* in: descriptor */ + ulint bit, /* in: XDES_FREE_BIT or XDES_CLEAN_BIT */ + ulint offset, /* in: page offset within extent: + 0 ... FSP_EXTENT_SIZE - 1 */ + ibool val, /* in: bit value */ + mtr_t* mtr) /* in: mtr */ +{ + ulint index; + ulint byte_index; + ulint bit_index; + ulint descr_byte; + + ut_ad(mtr_memo_contains_page(mtr, descr, MTR_MEMO_PAGE_X_FIX)); + ut_ad((bit == XDES_FREE_BIT) || (bit == XDES_CLEAN_BIT)); + ut_ad(offset < FSP_EXTENT_SIZE); + + index = bit + XDES_BITS_PER_PAGE * offset; + + byte_index = index / 8; + bit_index = index % 8; + + descr_byte = mtr_read_ulint(descr + XDES_BITMAP + byte_index, + MLOG_1BYTE, mtr); + descr_byte = ut_bit_set_nth(descr_byte, bit_index, val); + + mlog_write_ulint(descr + XDES_BITMAP + byte_index, descr_byte, + MLOG_1BYTE, mtr); +} + +/************************************************************************** +Looks for a descriptor bit having the desired value. Starts from hint +and scans upward; at the end of the extent the search is wrapped to +the start of the extent. */ +UNIV_INLINE +ulint +xdes_find_bit( +/*==========*/ + /* out: bit index of the bit, ULINT_UNDEFINED if not + found */ + xdes_t* descr, /* in: descriptor */ + ulint bit, /* in: XDES_FREE_BIT or XDES_CLEAN_BIT */ + ibool val, /* in: desired bit value */ + ulint hint, /* in: hint of which bit position would be desirable */ + mtr_t* mtr) /* in: mtr */ +{ + ulint i; + + ut_ad(descr && mtr); + ut_ad(val <= TRUE); + ut_ad(hint < FSP_EXTENT_SIZE); + ut_ad(mtr_memo_contains_page(mtr, descr, MTR_MEMO_PAGE_X_FIX)); + for (i = hint; i < FSP_EXTENT_SIZE; i++) { + if (val == xdes_get_bit(descr, bit, i, mtr)) { + + return(i); + } + } + + for (i = 0; i < hint; i++) { + if (val == xdes_get_bit(descr, bit, i, mtr)) { + + return(i); + } + } + + return(ULINT_UNDEFINED); +} + +/************************************************************************** +Looks for a descriptor bit having the desired value. Scans the extent in +a direction opposite to xdes_find_bit. */ +UNIV_INLINE +ulint +xdes_find_bit_downward( +/*===================*/ + /* out: bit index of the bit, ULINT_UNDEFINED if not + found */ + xdes_t* descr, /* in: descriptor */ + ulint bit, /* in: XDES_FREE_BIT or XDES_CLEAN_BIT */ + ibool val, /* in: desired bit value */ + ulint hint, /* in: hint of which bit position would be desirable */ + mtr_t* mtr) /* in: mtr */ +{ + ulint i; + + ut_ad(descr && mtr); + ut_ad(val <= TRUE); + ut_ad(hint < FSP_EXTENT_SIZE); + ut_ad(mtr_memo_contains_page(mtr, descr, MTR_MEMO_PAGE_X_FIX)); + for (i = hint + 1; i > 0; i--) { + if (val == xdes_get_bit(descr, bit, i - 1, mtr)) { + + return(i - 1); + } + } + + for (i = FSP_EXTENT_SIZE - 1; i > hint; i--) { + if (val == xdes_get_bit(descr, bit, i, mtr)) { + + return(i); + } + } + + return(ULINT_UNDEFINED); +} + +/************************************************************************** +Returns the number of used pages in a descriptor. */ +UNIV_INLINE +ulint +xdes_get_n_used( +/*============*/ + /* out: number of pages used */ + xdes_t* descr, /* in: descriptor */ + mtr_t* mtr) /* in: mtr */ +{ + ulint i; + ulint count = 0; + + ut_ad(descr && mtr); + ut_ad(mtr_memo_contains_page(mtr, descr, MTR_MEMO_PAGE_X_FIX)); + for (i = 0; i < FSP_EXTENT_SIZE; i++) { + if (FALSE == xdes_get_bit(descr, XDES_FREE_BIT, i, mtr)) { + count++; + } + } + + return(count); +} + +/************************************************************************** +Returns true if extent contains no used pages. */ +UNIV_INLINE +ibool +xdes_is_free( +/*=========*/ + /* out: TRUE if totally free */ + xdes_t* descr, /* in: descriptor */ + mtr_t* mtr) /* in: mtr */ +{ + if (0 == xdes_get_n_used(descr, mtr)) { + + return(TRUE); + } + + return(FALSE); +} + +/************************************************************************** +Returns true if extent contains no free pages. */ +UNIV_INLINE +ibool +xdes_is_full( +/*=========*/ + /* out: TRUE if full */ + xdes_t* descr, /* in: descriptor */ + mtr_t* mtr) /* in: mtr */ +{ + if (FSP_EXTENT_SIZE == xdes_get_n_used(descr, mtr)) { + + return(TRUE); + } + + return(FALSE); +} + +/************************************************************************** +Sets the state of an xdes. */ +UNIV_INLINE +void +xdes_set_state( +/*===========*/ + xdes_t* descr, /* in: descriptor */ + ulint state, /* in: state to set */ + mtr_t* mtr) /* in: mtr handle */ +{ + ut_ad(descr && mtr); + ut_ad(state >= XDES_FREE); + ut_ad(state <= XDES_FSEG); + ut_ad(mtr_memo_contains_page(mtr, descr, MTR_MEMO_PAGE_X_FIX)); + + mlog_write_ulint(descr + XDES_STATE, state, MLOG_4BYTES, mtr); +} + +/************************************************************************** +Gets the state of an xdes. */ +UNIV_INLINE +ulint +xdes_get_state( +/*===========*/ + /* out: state */ + xdes_t* descr, /* in: descriptor */ + mtr_t* mtr) /* in: mtr handle */ +{ + ulint state; + + ut_ad(descr && mtr); + ut_ad(mtr_memo_contains_page(mtr, descr, MTR_MEMO_PAGE_X_FIX)); + + state = mtr_read_ulint(descr + XDES_STATE, MLOG_4BYTES, mtr); + ut_ad(state - 1 < XDES_FSEG); + return(state); +} + +/************************************************************************** +Inits an extent descriptor to the free and clean state. */ +UNIV_INLINE +void +xdes_init( +/*======*/ + xdes_t* descr, /* in: descriptor */ + mtr_t* mtr) /* in: mtr */ +{ + ulint i; + + ut_ad(descr && mtr); + ut_ad(mtr_memo_contains_page(mtr, descr, MTR_MEMO_PAGE_X_FIX)); + ut_ad((XDES_SIZE - XDES_BITMAP) % 4 == 0); + + for (i = XDES_BITMAP; i < XDES_SIZE; i += 4) { + mlog_write_ulint(descr + i, 0xFFFFFFFFUL, MLOG_4BYTES, mtr); + } + + xdes_set_state(descr, XDES_FREE, mtr); +} + +/************************************************************************ +Calculates the page where the descriptor of a page resides. */ +UNIV_INLINE +ulint +xdes_calc_descriptor_page( +/*======================*/ + /* out: descriptor page offset */ + ulint zip_size, /* in: compressed page size in bytes; + 0 for uncompressed pages */ + ulint offset) /* in: page offset */ +{ +#if UNIV_PAGE_SIZE <= XDES_ARR_OFFSET \ + + (UNIV_PAGE_SIZE / FSP_EXTENT_SIZE) * XDES_SIZE +# error +#endif +#if PAGE_ZIP_MIN_SIZE <= XDES_ARR_OFFSET \ + + (PAGE_ZIP_MIN_SIZE / FSP_EXTENT_SIZE) * XDES_SIZE +# error +#endif + ut_ad(ut_is_2pow(zip_size)); + + if (!zip_size) { + return(ut_2pow_round(offset, UNIV_PAGE_SIZE)); + } else { + ut_ad(zip_size > XDES_ARR_OFFSET + + (zip_size / FSP_EXTENT_SIZE) * XDES_SIZE); + return(ut_2pow_round(offset, zip_size)); + } +} + +/************************************************************************ +Calculates the descriptor index within a descriptor page. */ +UNIV_INLINE +ulint +xdes_calc_descriptor_index( +/*=======================*/ + /* out: descriptor index */ + ulint zip_size, /* in: compressed page size in bytes; + 0 for uncompressed pages */ + ulint offset) /* in: page offset */ +{ + ut_ad(ut_is_2pow(zip_size)); + + if (!zip_size) { + return(ut_2pow_remainder(offset, UNIV_PAGE_SIZE) + / FSP_EXTENT_SIZE); + } else { + return(ut_2pow_remainder(offset, zip_size) / FSP_EXTENT_SIZE); + } +} + +/************************************************************************ +Gets pointer to a the extent descriptor of a page. The page where the extent +descriptor resides is x-locked. If the page offset is equal to the free limit +of the space, adds new extents from above the free limit to the space free +list, if not free limit == space size. This adding is necessary to make the +descriptor defined, as they are uninitialized above the free limit. */ +UNIV_INLINE +xdes_t* +xdes_get_descriptor_with_space_hdr( +/*===============================*/ + /* out: pointer to the extent descriptor, + NULL if the page does not exist in the + space or if offset > free limit */ + fsp_header_t* sp_header,/* in: space header, x-latched */ + ulint space, /* in: space id */ + ulint offset, /* in: page offset; + if equal to the free limit, + we try to add new extents to + the space free list */ + mtr_t* mtr) /* in: mtr handle */ +{ + ulint limit; + ulint size; + ulint zip_size; + ulint descr_page_no; + page_t* descr_page; + + ut_ad(mtr); + ut_ad(mtr_memo_contains(mtr, fil_space_get_latch(space, NULL), + MTR_MEMO_X_LOCK)); + ut_ad(mtr_memo_contains_page(mtr, sp_header, MTR_MEMO_PAGE_S_FIX) + || mtr_memo_contains_page(mtr, sp_header, MTR_MEMO_PAGE_X_FIX)); + ut_ad(page_offset(sp_header) == FSP_HEADER_OFFSET); + /* Read free limit and space size */ + limit = mach_read_from_4(sp_header + FSP_FREE_LIMIT); + size = mach_read_from_4(sp_header + FSP_SIZE); + zip_size = dict_table_flags_to_zip_size( + mach_read_from_4(sp_header + FSP_SPACE_FLAGS)); + + /* If offset is >= size or > limit, return NULL */ + + if ((offset >= size) || (offset > limit)) { + + return(NULL); + } + + /* If offset is == limit, fill free list of the space. */ + + if (offset == limit) { + fsp_fill_free_list(FALSE, space, sp_header, mtr); + } + + descr_page_no = xdes_calc_descriptor_page(zip_size, offset); + + if (descr_page_no == 0) { + /* It is on the space header page */ + + descr_page = page_align(sp_header); + } else { + buf_block_t* block; + + block = buf_page_get(space, zip_size, descr_page_no, + RW_X_LATCH, mtr); + buf_block_dbg_add_level(block, SYNC_FSP_PAGE); + + descr_page = buf_block_get_frame(block); + } + + return(descr_page + XDES_ARR_OFFSET + + XDES_SIZE * xdes_calc_descriptor_index(zip_size, offset)); +} + +/************************************************************************ +Gets pointer to a the extent descriptor of a page. The page where the +extent descriptor resides is x-locked. If the page offset is equal to +the free limit of the space, adds new extents from above the free limit +to the space free list, if not free limit == space size. This adding +is necessary to make the descriptor defined, as they are uninitialized +above the free limit. */ +static +xdes_t* +xdes_get_descriptor( +/*================*/ + /* out: pointer to the extent descriptor, NULL if the + page does not exist in the space or if offset > free + limit */ + ulint space, /* in: space id */ + ulint zip_size,/* in: compressed page size in bytes + or 0 for uncompressed pages */ + ulint offset, /* in: page offset; if equal to the free limit, + we try to add new extents to the space free list */ + mtr_t* mtr) /* in: mtr handle */ +{ + buf_block_t* block; + fsp_header_t* sp_header; + + block = buf_page_get(space, zip_size, 0, RW_X_LATCH, mtr); + buf_block_dbg_add_level(block, SYNC_FSP_PAGE); + + sp_header = FSP_HEADER_OFFSET + buf_block_get_frame(block); + return(xdes_get_descriptor_with_space_hdr(sp_header, space, offset, + mtr)); +} + +/************************************************************************ +Gets pointer to a the extent descriptor if the file address +of the descriptor list node is known. The page where the +extent descriptor resides is x-locked. */ +UNIV_INLINE +xdes_t* +xdes_lst_get_descriptor( +/*====================*/ + /* out: pointer to the extent descriptor */ + ulint space, /* in: space id */ + ulint zip_size,/* in: compressed page size in bytes + or 0 for uncompressed pages */ + fil_addr_t lst_node,/* in: file address of the list node + contained in the descriptor */ + mtr_t* mtr) /* in: mtr handle */ +{ + xdes_t* descr; + + ut_ad(mtr); + ut_ad(mtr_memo_contains(mtr, fil_space_get_latch(space, NULL), + MTR_MEMO_X_LOCK)); + descr = fut_get_ptr(space, zip_size, lst_node, RW_X_LATCH, mtr) + - XDES_FLST_NODE; + + return(descr); +} + +/************************************************************************ +Returns page offset of the first page in extent described by a descriptor. */ +UNIV_INLINE +ulint +xdes_get_offset( +/*============*/ + /* out: offset of the first page in extent */ + xdes_t* descr) /* in: extent descriptor */ +{ + ut_ad(descr); + + return(page_get_page_no(page_align(descr)) + + ((page_offset(descr) - XDES_ARR_OFFSET) / XDES_SIZE) + * FSP_EXTENT_SIZE); +} + +/*************************************************************** +Inits a file page whose prior contents should be ignored. */ +static +void +fsp_init_file_page_low( +/*===================*/ + buf_block_t* block) /* in: pointer to a page */ +{ + page_t* page = buf_block_get_frame(block); + page_zip_des_t* page_zip= buf_block_get_page_zip(block); + + block->check_index_page_at_flush = FALSE; + + if (UNIV_LIKELY_NULL(page_zip)) { + memset(page, 0, UNIV_PAGE_SIZE); + memset(page_zip->data, 0, page_zip_get_size(page_zip)); + mach_write_to_4(page + FIL_PAGE_OFFSET, + buf_block_get_page_no(block)); + mach_write_to_4(page + + FIL_PAGE_ARCH_LOG_NO_OR_SPACE_ID, + buf_block_get_space(block)); + memcpy(page_zip->data + FIL_PAGE_OFFSET, + page + FIL_PAGE_OFFSET, 4); + memcpy(page_zip->data + FIL_PAGE_ARCH_LOG_NO_OR_SPACE_ID, + page + FIL_PAGE_ARCH_LOG_NO_OR_SPACE_ID, 4); + return; + } + +#ifdef UNIV_BASIC_LOG_DEBUG + memset(page, 0xff, UNIV_PAGE_SIZE); +#endif + mach_write_to_4(page + FIL_PAGE_OFFSET, buf_block_get_page_no(block)); + memset(page + FIL_PAGE_LSN, 0, 8); + mach_write_to_4(page + FIL_PAGE_ARCH_LOG_NO_OR_SPACE_ID, + buf_block_get_space(block)); + memset(page + UNIV_PAGE_SIZE - FIL_PAGE_END_LSN_OLD_CHKSUM, 0, 8); +} + +/*************************************************************** +Inits a file page whose prior contents should be ignored. */ +static +void +fsp_init_file_page( +/*===============*/ + buf_block_t* block, /* in: pointer to a page */ + mtr_t* mtr) /* in: mtr */ +{ + fsp_init_file_page_low(block); + + mlog_write_initial_log_record(buf_block_get_frame(block), + MLOG_INIT_FILE_PAGE, mtr); +} + +/*************************************************************** +Parses a redo log record of a file page init. */ +UNIV_INTERN +byte* +fsp_parse_init_file_page( +/*=====================*/ + /* out: end of log record or NULL */ + byte* ptr, /* in: buffer */ + byte* end_ptr __attribute__((unused)), /* in: buffer end */ + buf_block_t* block) /* in: block or NULL */ +{ + ut_ad(ptr && end_ptr); + + if (block) { + fsp_init_file_page_low(block); + } + + return(ptr); +} + +/************************************************************************** +Initializes the fsp system. */ +UNIV_INTERN +void +fsp_init(void) +/*==========*/ +{ + /* Does nothing at the moment */ +} + +/************************************************************************** +Writes the space id and compressed page size to a tablespace header. +This function is used past the buffer pool when we in fil0fil.c create +a new single-table tablespace. */ +UNIV_INTERN +void +fsp_header_init_fields( +/*===================*/ + page_t* page, /* in/out: first page in the space */ + ulint space_id, /* in: space id */ + ulint flags) /* in: tablespace flags (FSP_SPACE_FLAGS): + 0, or table->flags if newer than COMPACT */ +{ + /* The tablespace flags (FSP_SPACE_FLAGS) should be 0 for + ROW_FORMAT=COMPACT (table->flags == DICT_TF_COMPACT) and + ROW_FORMAT=REDUNDANT (table->flags == 0). For any other + format, the tablespace flags should equal table->flags. */ + ut_a(flags != DICT_TF_COMPACT); + + mach_write_to_4(FSP_HEADER_OFFSET + FSP_SPACE_ID + page, + space_id); + mach_write_to_4(FSP_HEADER_OFFSET + FSP_SPACE_FLAGS + page, + flags); +} + +/************************************************************************** +Initializes the space header of a new created space and creates also the +insert buffer tree root if space == 0. */ +UNIV_INTERN +void +fsp_header_init( +/*============*/ + ulint space, /* in: space id */ + ulint size, /* in: current size in blocks */ + mtr_t* mtr) /* in: mini-transaction handle */ +{ + fsp_header_t* header; + buf_block_t* block; + page_t* page; + ulint flags; + ulint zip_size; + + ut_ad(mtr); + + mtr_x_lock(fil_space_get_latch(space, &flags), mtr); + + zip_size = dict_table_flags_to_zip_size(flags); + block = buf_page_create(space, 0, zip_size, mtr); + buf_page_get(space, zip_size, 0, RW_X_LATCH, mtr); + buf_block_dbg_add_level(block, SYNC_FSP_PAGE); + + /* The prior contents of the file page should be ignored */ + + fsp_init_file_page(block, mtr); + page = buf_block_get_frame(block); + + mlog_write_ulint(page + FIL_PAGE_TYPE, FIL_PAGE_TYPE_FSP_HDR, + MLOG_2BYTES, mtr); + + header = FSP_HEADER_OFFSET + page; + + mlog_write_ulint(header + FSP_SPACE_ID, space, MLOG_4BYTES, mtr); + mlog_write_ulint(header + FSP_NOT_USED, 0, MLOG_4BYTES, mtr); + + mlog_write_ulint(header + FSP_SIZE, size, MLOG_4BYTES, mtr); + mlog_write_ulint(header + FSP_FREE_LIMIT, 0, MLOG_4BYTES, mtr); + mlog_write_ulint(header + FSP_SPACE_FLAGS, flags, + MLOG_4BYTES, mtr); + mlog_write_ulint(header + FSP_FRAG_N_USED, 0, MLOG_4BYTES, mtr); + + flst_init(header + FSP_FREE, mtr); + flst_init(header + FSP_FREE_FRAG, mtr); + flst_init(header + FSP_FULL_FRAG, mtr); + flst_init(header + FSP_SEG_INODES_FULL, mtr); + flst_init(header + FSP_SEG_INODES_FREE, mtr); + + mlog_write_dulint(header + FSP_SEG_ID, ut_dulint_create(0, 1), mtr); + if (space == 0) { + fsp_fill_free_list(FALSE, space, header, mtr); + btr_create(DICT_CLUSTERED | DICT_UNIVERSAL | DICT_IBUF, + 0, 0, ut_dulint_add(DICT_IBUF_ID_MIN, space), + srv_sys->dummy_ind1, mtr); + } else { + fsp_fill_free_list(TRUE, space, header, mtr); + } +} + +/************************************************************************** +Reads the space id from the first page of a tablespace. */ +UNIV_INTERN +ulint +fsp_header_get_space_id( +/*====================*/ + /* out: space id, ULINT UNDEFINED if error */ + const page_t* page) /* in: first page of a tablespace */ +{ + ulint fsp_id; + ulint id; + + fsp_id = mach_read_from_4(FSP_HEADER_OFFSET + page + FSP_SPACE_ID); + + id = mach_read_from_4(page + FIL_PAGE_ARCH_LOG_NO_OR_SPACE_ID); + + if (id != fsp_id) { + fprintf(stderr, + "InnoDB: Error: space id in fsp header %lu," + " but in the page header %lu\n", + (ulong) fsp_id, (ulong) id); + + return(ULINT_UNDEFINED); + } + + return(id); +} + +/************************************************************************** +Reads the space flags from the first page of a tablespace. */ +UNIV_INTERN +ulint +fsp_header_get_flags( +/*=================*/ + /* out: flags */ + const page_t* page) /* in: first page of a tablespace */ +{ + ut_ad(!page_offset(page)); + + return(mach_read_from_4(FSP_HEADER_OFFSET + FSP_SPACE_FLAGS + page)); +} + +/************************************************************************** +Reads the compressed page size from the first page of a tablespace. */ +UNIV_INTERN +ulint +fsp_header_get_zip_size( +/*====================*/ + /* out: compressed page size in bytes, + or 0 if uncompressed */ + const page_t* page) /* in: first page of a tablespace */ +{ + ulint flags = fsp_header_get_flags(page); + + return(dict_table_flags_to_zip_size(flags)); +} + +/************************************************************************** +Increases the space size field of a space. */ +UNIV_INTERN +void +fsp_header_inc_size( +/*================*/ + ulint space, /* in: space id */ + ulint size_inc,/* in: size increment in pages */ + mtr_t* mtr) /* in: mini-transaction handle */ +{ + fsp_header_t* header; + ulint size; + ulint flags; + + ut_ad(mtr); + + mtr_x_lock(fil_space_get_latch(space, &flags), mtr); + + header = fsp_get_space_header(space, + dict_table_flags_to_zip_size(flags), + mtr); + + size = mtr_read_ulint(header + FSP_SIZE, MLOG_4BYTES, mtr); + + mlog_write_ulint(header + FSP_SIZE, size + size_inc, MLOG_4BYTES, + mtr); +} + +/************************************************************************** +Gets the current free limit of the system tablespace. The free limit +means the place of the first page which has never been put to the the +free list for allocation. The space above that address is initialized +to zero. Sets also the global variable log_fsp_current_free_limit. */ +UNIV_INTERN +ulint +fsp_header_get_free_limit(void) +/*===========================*/ + /* out: free limit in megabytes */ +{ + fsp_header_t* header; + ulint limit; + mtr_t mtr; + + mtr_start(&mtr); + + mtr_x_lock(fil_space_get_latch(0, NULL), &mtr); + + header = fsp_get_space_header(0, 0, &mtr); + + limit = mtr_read_ulint(header + FSP_FREE_LIMIT, MLOG_4BYTES, &mtr); + + limit /= ((1024 * 1024) / UNIV_PAGE_SIZE); + + log_fsp_current_free_limit_set_and_checkpoint(limit); + + mtr_commit(&mtr); + + return(limit); +} + +/************************************************************************** +Gets the size of the system tablespace from the tablespace header. If +we do not have an auto-extending data file, this should be equal to +the size of the data files. If there is an auto-extending data file, +this can be smaller. */ +UNIV_INTERN +ulint +fsp_header_get_tablespace_size(void) +/*================================*/ + /* out: size in pages */ +{ + fsp_header_t* header; + ulint size; + mtr_t mtr; + + mtr_start(&mtr); + + mtr_x_lock(fil_space_get_latch(0, NULL), &mtr); + + header = fsp_get_space_header(0, 0, &mtr); + + size = mtr_read_ulint(header + FSP_SIZE, MLOG_4BYTES, &mtr); + + mtr_commit(&mtr); + + return(size); +} + +/*************************************************************************** +Tries to extend a single-table tablespace so that a page would fit in the +data file. */ +static +ibool +fsp_try_extend_data_file_with_pages( +/*================================*/ + /* out: TRUE if success */ + ulint space, /* in: space */ + ulint page_no, /* in: page number */ + fsp_header_t* header, /* in: space header */ + mtr_t* mtr) /* in: mtr */ +{ + ibool success; + ulint actual_size; + ulint size; + + ut_a(space != 0); + + size = mtr_read_ulint(header + FSP_SIZE, MLOG_4BYTES, mtr); + + ut_a(page_no >= size); + + success = fil_extend_space_to_desired_size(&actual_size, space, + page_no + 1); + /* actual_size now has the space size in pages; it may be less than + we wanted if we ran out of disk space */ + + mlog_write_ulint(header + FSP_SIZE, actual_size, MLOG_4BYTES, mtr); + + return(success); +} + +/*************************************************************************** +Tries to extend the last data file of a tablespace if it is auto-extending. */ +static +ibool +fsp_try_extend_data_file( +/*=====================*/ + /* out: FALSE if not auto-extending */ + ulint* actual_increase,/* out: actual increase in pages, where + we measure the tablespace size from + what the header field says; it may be + the actual file size rounded down to + megabyte */ + ulint space, /* in: space */ + fsp_header_t* header, /* in: space header */ + mtr_t* mtr) /* in: mtr */ +{ + ulint size; + ulint zip_size; + ulint new_size; + ulint old_size; + ulint size_increase; + ulint actual_size; + ibool success; + + *actual_increase = 0; + + if (space == 0 && !srv_auto_extend_last_data_file) { + + return(FALSE); + } + + size = mtr_read_ulint(header + FSP_SIZE, MLOG_4BYTES, mtr); + zip_size = dict_table_flags_to_zip_size( + mach_read_from_4(header + FSP_SPACE_FLAGS)); + + old_size = size; + + if (space == 0) { + if (!srv_last_file_size_max) { + size_increase = SRV_AUTO_EXTEND_INCREMENT; + } else { + if (srv_last_file_size_max + < srv_data_file_sizes[srv_n_data_files - 1]) { + + fprintf(stderr, + "InnoDB: Error: Last data file size" + " is %lu, max size allowed %lu\n", + (ulong) srv_data_file_sizes[ + srv_n_data_files - 1], + (ulong) srv_last_file_size_max); + } + + size_increase = srv_last_file_size_max + - srv_data_file_sizes[srv_n_data_files - 1]; + if (size_increase > SRV_AUTO_EXTEND_INCREMENT) { + size_increase = SRV_AUTO_EXTEND_INCREMENT; + } + } + } else { + /* We extend single-table tablespaces first one extent + at a time, but for bigger tablespaces more. It is not + enough to extend always by one extent, because some + extents are frag page extents. */ + ulint extent_size; /* one megabyte, in pages */ + + if (!zip_size) { + extent_size = FSP_EXTENT_SIZE; + } else { + extent_size = FSP_EXTENT_SIZE + * UNIV_PAGE_SIZE / zip_size; + } + + if (size < extent_size) { + /* Let us first extend the file to extent_size */ + success = fsp_try_extend_data_file_with_pages( + space, extent_size - 1, header, mtr); + if (!success) { + new_size = mtr_read_ulint(header + FSP_SIZE, + MLOG_4BYTES, mtr); + + *actual_increase = new_size - old_size; + + return(FALSE); + } + + size = extent_size; + } + + if (size < 32 * extent_size) { + size_increase = extent_size; + } else { + /* Below in fsp_fill_free_list() we assume + that we add at most FSP_FREE_ADD extents at + a time */ + size_increase = FSP_FREE_ADD * extent_size; + } + } + + if (size_increase == 0) { + + return(TRUE); + } + + success = fil_extend_space_to_desired_size(&actual_size, space, + size + size_increase); + /* We ignore any fragments of a full megabyte when storing the size + to the space header */ + + if (!zip_size) { + new_size = ut_calc_align_down(actual_size, + (1024 * 1024) / UNIV_PAGE_SIZE); + } else { + new_size = ut_calc_align_down(actual_size, + (1024 * 1024) / zip_size); + } + mlog_write_ulint(header + FSP_SIZE, new_size, MLOG_4BYTES, mtr); + + *actual_increase = new_size - old_size; + + return(TRUE); +} + +/************************************************************************** +Puts new extents to the free list if there are free extents above the free +limit. If an extent happens to contain an extent descriptor page, the extent +is put to the FSP_FREE_FRAG list with the page marked as used. */ +static +void +fsp_fill_free_list( +/*===============*/ + ibool init_space, /* in: TRUE if this is a single-table + tablespace and we are only initing + the tablespace's first extent + descriptor page and ibuf bitmap page; + then we do not allocate more extents */ + ulint space, /* in: space */ + fsp_header_t* header, /* in: space header */ + mtr_t* mtr) /* in: mtr */ +{ + ulint limit; + ulint size; + ulint zip_size; + xdes_t* descr; + ulint count = 0; + ulint frag_n_used; + ulint actual_increase; + ulint i; + mtr_t ibuf_mtr; + + ut_ad(header && mtr); + ut_ad(page_offset(header) == FSP_HEADER_OFFSET); + + /* Check if we can fill free list from above the free list limit */ + size = mtr_read_ulint(header + FSP_SIZE, MLOG_4BYTES, mtr); + limit = mtr_read_ulint(header + FSP_FREE_LIMIT, MLOG_4BYTES, mtr); + + zip_size = dict_table_flags_to_zip_size( + mach_read_from_4(FSP_SPACE_FLAGS + header)); + ut_a(ut_is_2pow(zip_size)); + ut_a(zip_size <= UNIV_PAGE_SIZE); + ut_a(!zip_size || zip_size >= PAGE_ZIP_MIN_SIZE); + + if (space == 0 && srv_auto_extend_last_data_file + && size < limit + FSP_EXTENT_SIZE * FSP_FREE_ADD) { + + /* Try to increase the last data file size */ + fsp_try_extend_data_file(&actual_increase, space, header, mtr); + size = mtr_read_ulint(header + FSP_SIZE, MLOG_4BYTES, mtr); + } + + if (space != 0 && !init_space + && size < limit + FSP_EXTENT_SIZE * FSP_FREE_ADD) { + + /* Try to increase the .ibd file size */ + fsp_try_extend_data_file(&actual_increase, space, header, mtr); + size = mtr_read_ulint(header + FSP_SIZE, MLOG_4BYTES, mtr); + } + + i = limit; + + while ((init_space && i < 1) + || ((i + FSP_EXTENT_SIZE <= size) && (count < FSP_FREE_ADD))) { + + ibool init_xdes; + if (zip_size) { + init_xdes = ut_2pow_remainder(i, zip_size) == 0; + } else { + init_xdes = ut_2pow_remainder(i, UNIV_PAGE_SIZE) == 0; + } + + mlog_write_ulint(header + FSP_FREE_LIMIT, i + FSP_EXTENT_SIZE, + MLOG_4BYTES, mtr); + + /* Update the free limit info in the log system and make + a checkpoint */ + if (space == 0) { + ut_a(!zip_size); + log_fsp_current_free_limit_set_and_checkpoint( + (i + FSP_EXTENT_SIZE) + / ((1024 * 1024) / UNIV_PAGE_SIZE)); + } + + if (UNIV_UNLIKELY(init_xdes)) { + + buf_block_t* block; + + /* We are going to initialize a new descriptor page + and a new ibuf bitmap page: the prior contents of the + pages should be ignored. */ + + if (i > 0) { + block = buf_page_create( + space, i, zip_size, mtr); + buf_page_get(space, zip_size, i, + RW_X_LATCH, mtr); + buf_block_dbg_add_level(block, + SYNC_FSP_PAGE); + + fsp_init_file_page(block, mtr); + mlog_write_ulint(buf_block_get_frame(block) + + FIL_PAGE_TYPE, + FIL_PAGE_TYPE_XDES, + MLOG_2BYTES, mtr); + } + + /* Initialize the ibuf bitmap page in a separate + mini-transaction because it is low in the latching + order, and we must be able to release its latch + before returning from the fsp routine */ + + mtr_start(&ibuf_mtr); + + block = buf_page_create(space, + i + FSP_IBUF_BITMAP_OFFSET, + zip_size, &ibuf_mtr); + buf_page_get(space, zip_size, + i + FSP_IBUF_BITMAP_OFFSET, + RW_X_LATCH, &ibuf_mtr); + buf_block_dbg_add_level(block, SYNC_FSP_PAGE); + + fsp_init_file_page(block, &ibuf_mtr); + + ibuf_bitmap_page_init(block, &ibuf_mtr); + + mtr_commit(&ibuf_mtr); + } + + descr = xdes_get_descriptor_with_space_hdr(header, space, i, + mtr); + xdes_init(descr, mtr); + +#if UNIV_PAGE_SIZE % FSP_EXTENT_SIZE +# error "UNIV_PAGE_SIZE % FSP_EXTENT_SIZE != 0" +#endif +#if PAGE_ZIP_MIN_SIZE % FSP_EXTENT_SIZE +# error "PAGE_ZIP_MIN_SIZE % FSP_EXTENT_SIZE != 0" +#endif + + if (UNIV_UNLIKELY(init_xdes)) { + + /* The first page in the extent is a descriptor page + and the second is an ibuf bitmap page: mark them + used */ + + xdes_set_bit(descr, XDES_FREE_BIT, 0, FALSE, mtr); + xdes_set_bit(descr, XDES_FREE_BIT, + FSP_IBUF_BITMAP_OFFSET, FALSE, mtr); + xdes_set_state(descr, XDES_FREE_FRAG, mtr); + + flst_add_last(header + FSP_FREE_FRAG, + descr + XDES_FLST_NODE, mtr); + frag_n_used = mtr_read_ulint(header + FSP_FRAG_N_USED, + MLOG_4BYTES, mtr); + mlog_write_ulint(header + FSP_FRAG_N_USED, + frag_n_used + 2, MLOG_4BYTES, mtr); + } else { + flst_add_last(header + FSP_FREE, + descr + XDES_FLST_NODE, mtr); + count++; + } + + i += FSP_EXTENT_SIZE; + } +} + +/************************************************************************** +Allocates a new free extent. */ +static +xdes_t* +fsp_alloc_free_extent( +/*==================*/ + /* out: extent descriptor, NULL if cannot be + allocated */ + ulint space, /* in: space id */ + ulint zip_size,/* in: compressed page size in bytes + or 0 for uncompressed pages */ + ulint hint, /* in: hint of which extent would be desirable: any + page offset in the extent goes; the hint must not + be > FSP_FREE_LIMIT */ + mtr_t* mtr) /* in: mtr */ +{ + fsp_header_t* header; + fil_addr_t first; + xdes_t* descr; + + ut_ad(mtr); + + header = fsp_get_space_header(space, zip_size, mtr); + + descr = xdes_get_descriptor_with_space_hdr(header, space, hint, mtr); + + if (descr && (xdes_get_state(descr, mtr) == XDES_FREE)) { + /* Ok, we can take this extent */ + } else { + /* Take the first extent in the free list */ + first = flst_get_first(header + FSP_FREE, mtr); + + if (fil_addr_is_null(first)) { + fsp_fill_free_list(FALSE, space, header, mtr); + + first = flst_get_first(header + FSP_FREE, mtr); + } + + if (fil_addr_is_null(first)) { + + return(NULL); /* No free extents left */ + } + + descr = xdes_lst_get_descriptor(space, zip_size, first, mtr); + } + + flst_remove(header + FSP_FREE, descr + XDES_FLST_NODE, mtr); + + return(descr); +} + +/************************************************************************** +Allocates a single free page from a space. The page is marked as used. */ +static +ulint +fsp_alloc_free_page( +/*================*/ + /* out: the page offset, FIL_NULL if no page could + be allocated */ + ulint space, /* in: space id */ + ulint zip_size,/* in: compressed page size in bytes + or 0 for uncompressed pages */ + ulint hint, /* in: hint of which page would be desirable */ + mtr_t* mtr) /* in: mtr handle */ +{ + fsp_header_t* header; + fil_addr_t first; + xdes_t* descr; + buf_block_t* block; + ulint free; + ulint frag_n_used; + ulint page_no; + ulint space_size; + ibool success; + + ut_ad(mtr); + + header = fsp_get_space_header(space, zip_size, mtr); + + /* Get the hinted descriptor */ + descr = xdes_get_descriptor_with_space_hdr(header, space, hint, mtr); + + if (descr && (xdes_get_state(descr, mtr) == XDES_FREE_FRAG)) { + /* Ok, we can take this extent */ + } else { + /* Else take the first extent in free_frag list */ + first = flst_get_first(header + FSP_FREE_FRAG, mtr); + + if (fil_addr_is_null(first)) { + /* There are no partially full fragments: allocate + a free extent and add it to the FREE_FRAG list. NOTE + that the allocation may have as a side-effect that an + extent containing a descriptor page is added to the + FREE_FRAG list. But we will allocate our page from the + the free extent anyway. */ + + descr = fsp_alloc_free_extent(space, zip_size, + hint, mtr); + + if (descr == NULL) { + /* No free space left */ + + return(FIL_NULL); + } + + xdes_set_state(descr, XDES_FREE_FRAG, mtr); + flst_add_last(header + FSP_FREE_FRAG, + descr + XDES_FLST_NODE, mtr); + } else { + descr = xdes_lst_get_descriptor(space, zip_size, + first, mtr); + } + + /* Reset the hint */ + hint = 0; + } + + /* Now we have in descr an extent with at least one free page. Look + for a free page in the extent. */ + + free = xdes_find_bit(descr, XDES_FREE_BIT, TRUE, + hint % FSP_EXTENT_SIZE, mtr); + if (free == ULINT_UNDEFINED) { + + ut_print_buf(stderr, ((byte*)descr) - 500, 1000); + putc('\n', stderr); + + ut_error; + } + + page_no = xdes_get_offset(descr) + free; + + space_size = mtr_read_ulint(header + FSP_SIZE, MLOG_4BYTES, mtr); + + if (space_size <= page_no) { + /* It must be that we are extending a single-table tablespace + whose size is still < 64 pages */ + + ut_a(space != 0); + if (page_no >= FSP_EXTENT_SIZE) { + fprintf(stderr, + "InnoDB: Error: trying to extend a" + " single-table tablespace %lu\n" + "InnoDB: by single page(s) though the" + " space size %lu. Page no %lu.\n", + (ulong) space, (ulong) space_size, + (ulong) page_no); + return(FIL_NULL); + } + success = fsp_try_extend_data_file_with_pages(space, page_no, + header, mtr); + if (!success) { + /* No disk space left */ + return(FIL_NULL); + } + } + + xdes_set_bit(descr, XDES_FREE_BIT, free, FALSE, mtr); + + /* Update the FRAG_N_USED field */ + frag_n_used = mtr_read_ulint(header + FSP_FRAG_N_USED, MLOG_4BYTES, + mtr); + frag_n_used++; + mlog_write_ulint(header + FSP_FRAG_N_USED, frag_n_used, MLOG_4BYTES, + mtr); + if (xdes_is_full(descr, mtr)) { + /* The fragment is full: move it to another list */ + flst_remove(header + FSP_FREE_FRAG, descr + XDES_FLST_NODE, + mtr); + xdes_set_state(descr, XDES_FULL_FRAG, mtr); + + flst_add_last(header + FSP_FULL_FRAG, descr + XDES_FLST_NODE, + mtr); + mlog_write_ulint(header + FSP_FRAG_N_USED, + frag_n_used - FSP_EXTENT_SIZE, MLOG_4BYTES, + mtr); + } + + /* Initialize the allocated page to the buffer pool, so that it can + be obtained immediately with buf_page_get without need for a disk + read. */ + + buf_page_create(space, page_no, zip_size, mtr); + + block = buf_page_get(space, zip_size, page_no, RW_X_LATCH, mtr); + buf_block_dbg_add_level(block, SYNC_FSP_PAGE); + + /* Prior contents of the page should be ignored */ + fsp_init_file_page(block, mtr); + + return(page_no); +} + +/************************************************************************** +Frees a single page of a space. The page is marked as free and clean. */ +static +void +fsp_free_page( +/*==========*/ + ulint space, /* in: space id */ + ulint zip_size,/* in: compressed page size in bytes + or 0 for uncompressed pages */ + ulint page, /* in: page offset */ + mtr_t* mtr) /* in: mtr handle */ +{ + fsp_header_t* header; + xdes_t* descr; + ulint state; + ulint frag_n_used; + + ut_ad(mtr); + + /* fprintf(stderr, "Freeing page %lu in space %lu\n", page, space); */ + + header = fsp_get_space_header(space, zip_size, mtr); + + descr = xdes_get_descriptor_with_space_hdr(header, space, page, mtr); + + state = xdes_get_state(descr, mtr); + + if (state != XDES_FREE_FRAG && state != XDES_FULL_FRAG) { + fprintf(stderr, + "InnoDB: Error: File space extent descriptor" + " of page %lu has state %lu\n", + (ulong) page, + (ulong) state); + fputs("InnoDB: Dump of descriptor: ", stderr); + ut_print_buf(stderr, ((byte*)descr) - 50, 200); + putc('\n', stderr); + + if (state == XDES_FREE) { + /* We put here some fault tolerance: if the page + is already free, return without doing anything! */ + + return; + } + + ut_error; + } + + if (xdes_get_bit(descr, XDES_FREE_BIT, page % FSP_EXTENT_SIZE, mtr)) { + fprintf(stderr, + "InnoDB: Error: File space extent descriptor" + " of page %lu says it is free\n" + "InnoDB: Dump of descriptor: ", (ulong) page); + ut_print_buf(stderr, ((byte*)descr) - 50, 200); + putc('\n', stderr); + + /* We put here some fault tolerance: if the page + is already free, return without doing anything! */ + + return; + } + + xdes_set_bit(descr, XDES_FREE_BIT, page % FSP_EXTENT_SIZE, TRUE, mtr); + xdes_set_bit(descr, XDES_CLEAN_BIT, page % FSP_EXTENT_SIZE, TRUE, mtr); + + frag_n_used = mtr_read_ulint(header + FSP_FRAG_N_USED, MLOG_4BYTES, + mtr); + if (state == XDES_FULL_FRAG) { + /* The fragment was full: move it to another list */ + flst_remove(header + FSP_FULL_FRAG, descr + XDES_FLST_NODE, + mtr); + xdes_set_state(descr, XDES_FREE_FRAG, mtr); + flst_add_last(header + FSP_FREE_FRAG, descr + XDES_FLST_NODE, + mtr); + mlog_write_ulint(header + FSP_FRAG_N_USED, + frag_n_used + FSP_EXTENT_SIZE - 1, + MLOG_4BYTES, mtr); + } else { + ut_a(frag_n_used > 0); + mlog_write_ulint(header + FSP_FRAG_N_USED, frag_n_used - 1, + MLOG_4BYTES, mtr); + } + + if (xdes_is_free(descr, mtr)) { + /* The extent has become free: move it to another list */ + flst_remove(header + FSP_FREE_FRAG, descr + XDES_FLST_NODE, + mtr); + fsp_free_extent(space, zip_size, page, mtr); + } +} + +/************************************************************************** +Returns an extent to the free list of a space. */ +static +void +fsp_free_extent( +/*============*/ + ulint space, /* in: space id */ + ulint zip_size,/* in: compressed page size in bytes + or 0 for uncompressed pages */ + ulint page, /* in: page offset in the extent */ + mtr_t* mtr) /* in: mtr */ +{ + fsp_header_t* header; + xdes_t* descr; + + ut_ad(mtr); + + header = fsp_get_space_header(space, zip_size, mtr); + + descr = xdes_get_descriptor_with_space_hdr(header, space, page, mtr); + + if (xdes_get_state(descr, mtr) == XDES_FREE) { + + ut_print_buf(stderr, (byte*)descr - 500, 1000); + putc('\n', stderr); + + ut_error; + } + + xdes_init(descr, mtr); + + flst_add_last(header + FSP_FREE, descr + XDES_FLST_NODE, mtr); +} + +/************************************************************************** +Returns the nth inode slot on an inode page. */ +UNIV_INLINE +fseg_inode_t* +fsp_seg_inode_page_get_nth_inode( +/*=============================*/ + /* out: segment inode */ + page_t* page, /* in: segment inode page */ + ulint i, /* in: inode index on page */ + ulint zip_size __attribute__((unused)), + /* in: compressed page size, or 0 */ + mtr_t* mtr __attribute__((unused))) + /* in: mini-transaction handle */ +{ + ut_ad(i < FSP_SEG_INODES_PER_PAGE(zip_size)); + ut_ad(mtr_memo_contains_page(mtr, page, MTR_MEMO_PAGE_X_FIX)); + + return(page + FSEG_ARR_OFFSET + FSEG_INODE_SIZE * i); +} + +/************************************************************************** +Looks for a used segment inode on a segment inode page. */ +static +ulint +fsp_seg_inode_page_find_used( +/*=========================*/ + /* out: segment inode index, or ULINT_UNDEFINED + if not found */ + page_t* page, /* in: segment inode page */ + ulint zip_size,/* in: compressed page size, or 0 */ + mtr_t* mtr) /* in: mini-transaction handle */ +{ + ulint i; + fseg_inode_t* inode; + + for (i = 0; i < FSP_SEG_INODES_PER_PAGE(zip_size); i++) { + + inode = fsp_seg_inode_page_get_nth_inode( + page, i, zip_size, mtr); + + if (!ut_dulint_is_zero(mach_read_from_8(inode + FSEG_ID))) { + /* This is used */ + + return(i); + } + } + + return(ULINT_UNDEFINED); +} + +/************************************************************************** +Looks for an unused segment inode on a segment inode page. */ +static +ulint +fsp_seg_inode_page_find_free( +/*=========================*/ + /* out: segment inode index, or ULINT_UNDEFINED + if not found */ + page_t* page, /* in: segment inode page */ + ulint i, /* in: search forward starting from this index */ + ulint zip_size,/* in: compressed page size, or 0 */ + mtr_t* mtr) /* in: mini-transaction handle */ +{ + fseg_inode_t* inode; + + for (; i < FSP_SEG_INODES_PER_PAGE(zip_size); i++) { + + inode = fsp_seg_inode_page_get_nth_inode( + page, i, zip_size, mtr); + + if (ut_dulint_is_zero(mach_read_from_8(inode + FSEG_ID))) { + /* This is unused */ + + return(i); + } + } + + return(ULINT_UNDEFINED); +} + +/************************************************************************** +Allocates a new file segment inode page. */ +static +ibool +fsp_alloc_seg_inode_page( +/*=====================*/ + /* out: TRUE if could be allocated */ + fsp_header_t* space_header, /* in: space header */ + mtr_t* mtr) /* in: mini-transaction handle */ +{ + fseg_inode_t* inode; + buf_block_t* block; + page_t* page; + ulint page_no; + ulint space; + ulint zip_size; + ulint i; + + ut_ad(page_offset(space_header) == FSP_HEADER_OFFSET); + + space = page_get_space_id(page_align(space_header)); + zip_size = dict_table_flags_to_zip_size( + mach_read_from_4(FSP_SPACE_FLAGS + space_header)); + + page_no = fsp_alloc_free_page(space, zip_size, 0, mtr); + + if (page_no == FIL_NULL) { + + return(FALSE); + } + + block = buf_page_get(space, zip_size, page_no, RW_X_LATCH, mtr); + buf_block_dbg_add_level(block, SYNC_FSP_PAGE); + + block->check_index_page_at_flush = FALSE; + + page = buf_block_get_frame(block); + + mlog_write_ulint(page + FIL_PAGE_TYPE, FIL_PAGE_INODE, + MLOG_2BYTES, mtr); + + for (i = 0; i < FSP_SEG_INODES_PER_PAGE(zip_size); i++) { + + inode = fsp_seg_inode_page_get_nth_inode(page, i, + zip_size, mtr); + + mlog_write_dulint(inode + FSEG_ID, ut_dulint_zero, mtr); + } + + flst_add_last(space_header + FSP_SEG_INODES_FREE, + page + FSEG_INODE_PAGE_NODE, mtr); + return(TRUE); +} + +/************************************************************************** +Allocates a new file segment inode. */ +static +fseg_inode_t* +fsp_alloc_seg_inode( +/*================*/ + /* out: segment inode, or NULL if + not enough space */ + fsp_header_t* space_header, /* in: space header */ + mtr_t* mtr) /* in: mini-transaction handle */ +{ + ulint page_no; + buf_block_t* block; + page_t* page; + fseg_inode_t* inode; + ibool success; + ulint zip_size; + ulint n; + + ut_ad(page_offset(space_header) == FSP_HEADER_OFFSET); + + if (flst_get_len(space_header + FSP_SEG_INODES_FREE, mtr) == 0) { + /* Allocate a new segment inode page */ + + success = fsp_alloc_seg_inode_page(space_header, mtr); + + if (!success) { + + return(NULL); + } + } + + page_no = flst_get_first(space_header + FSP_SEG_INODES_FREE, mtr).page; + + zip_size = dict_table_flags_to_zip_size( + mach_read_from_4(FSP_SPACE_FLAGS + space_header)); + block = buf_page_get(page_get_space_id(page_align(space_header)), + zip_size, page_no, RW_X_LATCH, mtr); + buf_block_dbg_add_level(block, SYNC_FSP_PAGE); + + page = buf_block_get_frame(block); + + n = fsp_seg_inode_page_find_free(page, 0, zip_size, mtr); + + ut_a(n != ULINT_UNDEFINED); + + inode = fsp_seg_inode_page_get_nth_inode(page, n, zip_size, mtr); + + if (ULINT_UNDEFINED == fsp_seg_inode_page_find_free(page, n + 1, + zip_size, mtr)) { + /* There are no other unused headers left on the page: move it + to another list */ + + flst_remove(space_header + FSP_SEG_INODES_FREE, + page + FSEG_INODE_PAGE_NODE, mtr); + + flst_add_last(space_header + FSP_SEG_INODES_FULL, + page + FSEG_INODE_PAGE_NODE, mtr); + } + + return(inode); +} + +/************************************************************************** +Frees a file segment inode. */ +static +void +fsp_free_seg_inode( +/*===============*/ + ulint space, /* in: space id */ + ulint zip_size,/* in: compressed page size in bytes + or 0 for uncompressed pages */ + fseg_inode_t* inode, /* in: segment inode */ + mtr_t* mtr) /* in: mini-transaction handle */ +{ + page_t* page; + fsp_header_t* space_header; + + page = page_align(inode); + + space_header = fsp_get_space_header(space, zip_size, mtr); + + ut_ad(mach_read_from_4(inode + FSEG_MAGIC_N) == FSEG_MAGIC_N_VALUE); + + if (ULINT_UNDEFINED + == fsp_seg_inode_page_find_free(page, 0, zip_size, mtr)) { + + /* Move the page to another list */ + + flst_remove(space_header + FSP_SEG_INODES_FULL, + page + FSEG_INODE_PAGE_NODE, mtr); + + flst_add_last(space_header + FSP_SEG_INODES_FREE, + page + FSEG_INODE_PAGE_NODE, mtr); + } + + mlog_write_dulint(inode + FSEG_ID, ut_dulint_zero, mtr); + mlog_write_ulint(inode + FSEG_MAGIC_N, 0, MLOG_4BYTES, mtr); + + if (ULINT_UNDEFINED + == fsp_seg_inode_page_find_used(page, zip_size, mtr)) { + + /* There are no other used headers left on the page: free it */ + + flst_remove(space_header + FSP_SEG_INODES_FREE, + page + FSEG_INODE_PAGE_NODE, mtr); + + fsp_free_page(space, zip_size, page_get_page_no(page), mtr); + } +} + +/************************************************************************** +Returns the file segment inode, page x-latched. */ +static +fseg_inode_t* +fseg_inode_get( +/*===========*/ + /* out: segment inode, page x-latched */ + fseg_header_t* header, /* in: segment header */ + ulint space, /* in: space id */ + ulint zip_size,/* in: compressed page size in bytes + or 0 for uncompressed pages */ + mtr_t* mtr) /* in: mtr handle */ +{ + fil_addr_t inode_addr; + fseg_inode_t* inode; + + inode_addr.page = mach_read_from_4(header + FSEG_HDR_PAGE_NO); + inode_addr.boffset = mach_read_from_2(header + FSEG_HDR_OFFSET); + ut_ad(space == mach_read_from_4(header + FSEG_HDR_SPACE)); + + inode = fut_get_ptr(space, zip_size, inode_addr, RW_X_LATCH, mtr); + + ut_ad(mach_read_from_4(inode + FSEG_MAGIC_N) == FSEG_MAGIC_N_VALUE); + + return(inode); +} + +/************************************************************************** +Gets the page number from the nth fragment page slot. */ +UNIV_INLINE +ulint +fseg_get_nth_frag_page_no( +/*======================*/ + /* out: page number, FIL_NULL if not in use */ + fseg_inode_t* inode, /* in: segment inode */ + ulint n, /* in: slot index */ + mtr_t* mtr __attribute__((unused))) /* in: mtr handle */ +{ + ut_ad(inode && mtr); + ut_ad(n < FSEG_FRAG_ARR_N_SLOTS); + ut_ad(mtr_memo_contains_page(mtr, inode, MTR_MEMO_PAGE_X_FIX)); + return(mach_read_from_4(inode + FSEG_FRAG_ARR + + n * FSEG_FRAG_SLOT_SIZE)); +} + +/************************************************************************** +Sets the page number in the nth fragment page slot. */ +UNIV_INLINE +void +fseg_set_nth_frag_page_no( +/*======================*/ + fseg_inode_t* inode, /* in: segment inode */ + ulint n, /* in: slot index */ + ulint page_no,/* in: page number to set */ + mtr_t* mtr) /* in: mtr handle */ +{ + ut_ad(inode && mtr); + ut_ad(n < FSEG_FRAG_ARR_N_SLOTS); + ut_ad(mtr_memo_contains_page(mtr, inode, MTR_MEMO_PAGE_X_FIX)); + + mlog_write_ulint(inode + FSEG_FRAG_ARR + n * FSEG_FRAG_SLOT_SIZE, + page_no, MLOG_4BYTES, mtr); +} + +/************************************************************************** +Finds a fragment page slot which is free. */ +static +ulint +fseg_find_free_frag_page_slot( +/*==========================*/ + /* out: slot index; ULINT_UNDEFINED if none + found */ + fseg_inode_t* inode, /* in: segment inode */ + mtr_t* mtr) /* in: mtr handle */ +{ + ulint i; + ulint page_no; + + ut_ad(inode && mtr); + + for (i = 0; i < FSEG_FRAG_ARR_N_SLOTS; i++) { + page_no = fseg_get_nth_frag_page_no(inode, i, mtr); + + if (page_no == FIL_NULL) { + + return(i); + } + } + + return(ULINT_UNDEFINED); +} + +/************************************************************************** +Finds a fragment page slot which is used and last in the array. */ +static +ulint +fseg_find_last_used_frag_page_slot( +/*===============================*/ + /* out: slot index; ULINT_UNDEFINED if none + found */ + fseg_inode_t* inode, /* in: segment inode */ + mtr_t* mtr) /* in: mtr handle */ +{ + ulint i; + ulint page_no; + + ut_ad(inode && mtr); + + for (i = 0; i < FSEG_FRAG_ARR_N_SLOTS; i++) { + page_no = fseg_get_nth_frag_page_no( + inode, FSEG_FRAG_ARR_N_SLOTS - i - 1, mtr); + + if (page_no != FIL_NULL) { + + return(FSEG_FRAG_ARR_N_SLOTS - i - 1); + } + } + + return(ULINT_UNDEFINED); +} + +/************************************************************************** +Calculates reserved fragment page slots. */ +static +ulint +fseg_get_n_frag_pages( +/*==================*/ + /* out: number of fragment pages */ + fseg_inode_t* inode, /* in: segment inode */ + mtr_t* mtr) /* in: mtr handle */ +{ + ulint i; + ulint count = 0; + + ut_ad(inode && mtr); + + for (i = 0; i < FSEG_FRAG_ARR_N_SLOTS; i++) { + if (FIL_NULL != fseg_get_nth_frag_page_no(inode, i, mtr)) { + count++; + } + } + + return(count); +} + +/************************************************************************** +Creates a new segment. */ +UNIV_INTERN +buf_block_t* +fseg_create_general( +/*================*/ + /* out: the block where the segment header is placed, + x-latched, NULL if could not create segment + because of lack of space */ + ulint space, /* in: space id */ + ulint page, /* in: page where the segment header is placed: if + this is != 0, the page must belong to another segment, + if this is 0, a new page will be allocated and it + will belong to the created segment */ + ulint byte_offset, /* in: byte offset of the created segment header + on the page */ + ibool has_done_reservation, /* in: TRUE if the caller has already + done the reservation for the pages with + fsp_reserve_free_extents (at least 2 extents: one for + the inode and the other for the segment) then there is + no need to do the check for this individual + operation */ + mtr_t* mtr) /* in: mtr */ +{ + ulint flags; + ulint zip_size; + fsp_header_t* space_header; + fseg_inode_t* inode; + dulint seg_id; + buf_block_t* block = 0; /* remove warning */ + fseg_header_t* header = 0; /* remove warning */ + rw_lock_t* latch; + ibool success; + ulint n_reserved; + ulint i; + + ut_ad(mtr); + ut_ad(byte_offset + FSEG_HEADER_SIZE + <= UNIV_PAGE_SIZE - FIL_PAGE_DATA_END); + + latch = fil_space_get_latch(space, &flags); + zip_size = dict_table_flags_to_zip_size(flags); + + if (page != 0) { + block = buf_page_get(space, zip_size, page, RW_X_LATCH, mtr); + header = byte_offset + buf_block_get_frame(block); + } + + ut_ad(!mutex_own(&kernel_mutex) + || mtr_memo_contains(mtr, latch, MTR_MEMO_X_LOCK)); + + mtr_x_lock(latch, mtr); + + if (rw_lock_get_x_lock_count(latch) == 1) { + /* This thread did not own the latch before this call: free + excess pages from the insert buffer free list */ + + if (space == IBUF_SPACE_ID) { + ibuf_free_excess_pages(); + } + } + + if (!has_done_reservation) { + success = fsp_reserve_free_extents(&n_reserved, space, 2, + FSP_NORMAL, mtr); + if (!success) { + return(NULL); + } + } + + space_header = fsp_get_space_header(space, zip_size, mtr); + + inode = fsp_alloc_seg_inode(space_header, mtr); + + if (inode == NULL) { + + goto funct_exit; + } + + /* Read the next segment id from space header and increment the + value in space header */ + + seg_id = mtr_read_dulint(space_header + FSP_SEG_ID, mtr); + + mlog_write_dulint(space_header + FSP_SEG_ID, ut_dulint_add(seg_id, 1), + mtr); + + mlog_write_dulint(inode + FSEG_ID, seg_id, mtr); + mlog_write_ulint(inode + FSEG_NOT_FULL_N_USED, 0, MLOG_4BYTES, mtr); + + flst_init(inode + FSEG_FREE, mtr); + flst_init(inode + FSEG_NOT_FULL, mtr); + flst_init(inode + FSEG_FULL, mtr); + + mlog_write_ulint(inode + FSEG_MAGIC_N, FSEG_MAGIC_N_VALUE, + MLOG_4BYTES, mtr); + for (i = 0; i < FSEG_FRAG_ARR_N_SLOTS; i++) { + fseg_set_nth_frag_page_no(inode, i, FIL_NULL, mtr); + } + + if (page == 0) { + page = fseg_alloc_free_page_low(space, zip_size, + inode, 0, FSP_UP, mtr); + + if (page == FIL_NULL) { + + fsp_free_seg_inode(space, zip_size, inode, mtr); + + goto funct_exit; + } + + block = buf_page_get(space, zip_size, page, RW_X_LATCH, mtr); + header = byte_offset + buf_block_get_frame(block); + mlog_write_ulint(header - byte_offset + FIL_PAGE_TYPE, + FIL_PAGE_TYPE_SYS, MLOG_2BYTES, mtr); + } + + mlog_write_ulint(header + FSEG_HDR_OFFSET, + page_offset(inode), MLOG_2BYTES, mtr); + + mlog_write_ulint(header + FSEG_HDR_PAGE_NO, + page_get_page_no(page_align(inode)), + MLOG_4BYTES, mtr); + + mlog_write_ulint(header + FSEG_HDR_SPACE, space, MLOG_4BYTES, mtr); + +funct_exit: + if (!has_done_reservation) { + + fil_space_release_free_extents(space, n_reserved); + } + + return(block); +} + +/************************************************************************** +Creates a new segment. */ +UNIV_INTERN +buf_block_t* +fseg_create( +/*========*/ + /* out: the block where the segment header is placed, + x-latched, NULL if could not create segment + because of lack of space */ + ulint space, /* in: space id */ + ulint page, /* in: page where the segment header is placed: if + this is != 0, the page must belong to another segment, + if this is 0, a new page will be allocated and it + will belong to the created segment */ + ulint byte_offset, /* in: byte offset of the created segment header + on the page */ + mtr_t* mtr) /* in: mtr */ +{ + return(fseg_create_general(space, page, byte_offset, FALSE, mtr)); +} + +/************************************************************************** +Calculates the number of pages reserved by a segment, and how many pages are +currently used. */ +static +ulint +fseg_n_reserved_pages_low( +/*======================*/ + /* out: number of reserved pages */ + fseg_inode_t* inode, /* in: segment inode */ + ulint* used, /* out: number of pages used (<= reserved) */ + mtr_t* mtr) /* in: mtr handle */ +{ + ulint ret; + + ut_ad(inode && used && mtr); + ut_ad(mtr_memo_contains_page(mtr, inode, MTR_MEMO_PAGE_X_FIX)); + + *used = mtr_read_ulint(inode + FSEG_NOT_FULL_N_USED, MLOG_4BYTES, mtr) + + FSP_EXTENT_SIZE * flst_get_len(inode + FSEG_FULL, mtr) + + fseg_get_n_frag_pages(inode, mtr); + + ret = fseg_get_n_frag_pages(inode, mtr) + + FSP_EXTENT_SIZE * flst_get_len(inode + FSEG_FREE, mtr) + + FSP_EXTENT_SIZE * flst_get_len(inode + FSEG_NOT_FULL, mtr) + + FSP_EXTENT_SIZE * flst_get_len(inode + FSEG_FULL, mtr); + + return(ret); +} + +/************************************************************************** +Calculates the number of pages reserved by a segment, and how many pages are +currently used. */ +UNIV_INTERN +ulint +fseg_n_reserved_pages( +/*==================*/ + /* out: number of reserved pages */ + fseg_header_t* header, /* in: segment header */ + ulint* used, /* out: number of pages used (<= reserved) */ + mtr_t* mtr) /* in: mtr handle */ +{ + ulint ret; + fseg_inode_t* inode; + ulint space; + ulint flags; + ulint zip_size; + rw_lock_t* latch; + + space = page_get_space_id(page_align(header)); + latch = fil_space_get_latch(space, &flags); + zip_size = dict_table_flags_to_zip_size(flags); + + ut_ad(!mutex_own(&kernel_mutex) + || mtr_memo_contains(mtr, latch, MTR_MEMO_X_LOCK)); + + mtr_x_lock(latch, mtr); + + inode = fseg_inode_get(header, space, zip_size, mtr); + + ret = fseg_n_reserved_pages_low(inode, used, mtr); + + return(ret); +} + +/************************************************************************* +Tries to fill the free list of a segment with consecutive free extents. +This happens if the segment is big enough to allow extents in the free list, +the free list is empty, and the extents can be allocated consecutively from +the hint onward. */ +static +void +fseg_fill_free_list( +/*================*/ + fseg_inode_t* inode, /* in: segment inode */ + ulint space, /* in: space id */ + ulint zip_size,/* in: compressed page size in bytes + or 0 for uncompressed pages */ + ulint hint, /* in: hint which extent would be good as + the first extent */ + mtr_t* mtr) /* in: mtr */ +{ + xdes_t* descr; + ulint i; + dulint seg_id; + ulint reserved; + ulint used; + + ut_ad(inode && mtr); + ut_ad(!((page_offset(inode) - FSEG_ARR_OFFSET) % FSEG_INODE_SIZE)); + + reserved = fseg_n_reserved_pages_low(inode, &used, mtr); + + if (reserved < FSEG_FREE_LIST_LIMIT * FSP_EXTENT_SIZE) { + + /* The segment is too small to allow extents in free list */ + + return; + } + + if (flst_get_len(inode + FSEG_FREE, mtr) > 0) { + /* Free list is not empty */ + + return; + } + + for (i = 0; i < FSEG_FREE_LIST_MAX_LEN; i++) { + descr = xdes_get_descriptor(space, zip_size, hint, mtr); + + if ((descr == NULL) + || (XDES_FREE != xdes_get_state(descr, mtr))) { + + /* We cannot allocate the desired extent: stop */ + + return; + } + + descr = fsp_alloc_free_extent(space, zip_size, hint, mtr); + + xdes_set_state(descr, XDES_FSEG, mtr); + + seg_id = mtr_read_dulint(inode + FSEG_ID, mtr); + mlog_write_dulint(descr + XDES_ID, seg_id, mtr); + + flst_add_last(inode + FSEG_FREE, descr + XDES_FLST_NODE, mtr); + hint += FSP_EXTENT_SIZE; + } +} + +/************************************************************************* +Allocates a free extent for the segment: looks first in the free list of the +segment, then tries to allocate from the space free list. NOTE that the extent +returned still resides in the segment free list, it is not yet taken off it! */ +static +xdes_t* +fseg_alloc_free_extent( +/*===================*/ + /* out: allocated extent, still placed in the + segment free list, NULL if could + not be allocated */ + fseg_inode_t* inode, /* in: segment inode */ + ulint space, /* in: space id */ + ulint zip_size,/* in: compressed page size in bytes + or 0 for uncompressed pages */ + mtr_t* mtr) /* in: mtr */ +{ + xdes_t* descr; + dulint seg_id; + fil_addr_t first; + + ut_ad(!((page_offset(inode) - FSEG_ARR_OFFSET) % FSEG_INODE_SIZE)); + + if (flst_get_len(inode + FSEG_FREE, mtr) > 0) { + /* Segment free list is not empty, allocate from it */ + + first = flst_get_first(inode + FSEG_FREE, mtr); + + descr = xdes_lst_get_descriptor(space, zip_size, first, mtr); + } else { + /* Segment free list was empty, allocate from space */ + descr = fsp_alloc_free_extent(space, zip_size, 0, mtr); + + if (descr == NULL) { + + return(NULL); + } + + seg_id = mtr_read_dulint(inode + FSEG_ID, mtr); + + xdes_set_state(descr, XDES_FSEG, mtr); + mlog_write_dulint(descr + XDES_ID, seg_id, mtr); + flst_add_last(inode + FSEG_FREE, descr + XDES_FLST_NODE, mtr); + + /* Try to fill the segment free list */ + fseg_fill_free_list(inode, space, zip_size, + xdes_get_offset(descr) + FSP_EXTENT_SIZE, + mtr); + } + + return(descr); +} + +/************************************************************************** +Allocates a single free page from a segment. This function implements +the intelligent allocation strategy which tries to minimize file space +fragmentation. */ +static +ulint +fseg_alloc_free_page_low( +/*=====================*/ + /* out: the allocated page number, FIL_NULL + if no page could be allocated */ + ulint space, /* in: space */ + ulint zip_size,/* in: compressed page size in bytes + or 0 for uncompressed pages */ + fseg_inode_t* seg_inode, /* in: segment inode */ + ulint hint, /* in: hint of which page would be desirable */ + byte direction, /* in: if the new page is needed because + of an index page split, and records are + inserted there in order, into which + direction they go alphabetically: FSP_DOWN, + FSP_UP, FSP_NO_DIR */ + mtr_t* mtr) /* in: mtr handle */ +{ + fsp_header_t* space_header; + ulint space_size; + dulint seg_id; + ulint used; + ulint reserved; + xdes_t* descr; /* extent of the hinted page */ + ulint ret_page; /* the allocated page offset, FIL_NULL + if could not be allocated */ + xdes_t* ret_descr; /* the extent of the allocated page */ + ibool frag_page_allocated = FALSE; + ibool success; + ulint n; + + ut_ad(mtr); + ut_ad((direction >= FSP_UP) && (direction <= FSP_NO_DIR)); + ut_ad(mach_read_from_4(seg_inode + FSEG_MAGIC_N) + == FSEG_MAGIC_N_VALUE); + ut_ad(!((page_offset(seg_inode) - FSEG_ARR_OFFSET) % FSEG_INODE_SIZE)); + seg_id = mtr_read_dulint(seg_inode + FSEG_ID, mtr); + + ut_ad(!ut_dulint_is_zero(seg_id)); + + reserved = fseg_n_reserved_pages_low(seg_inode, &used, mtr); + + space_header = fsp_get_space_header(space, zip_size, mtr); + + descr = xdes_get_descriptor_with_space_hdr(space_header, space, + hint, mtr); + if (descr == NULL) { + /* Hint outside space or too high above free limit: reset + hint */ + hint = 0; + descr = xdes_get_descriptor(space, zip_size, hint, mtr); + } + + /* In the big if-else below we look for ret_page and ret_descr */ + /*-------------------------------------------------------------*/ + if ((xdes_get_state(descr, mtr) == XDES_FSEG) + && (0 == ut_dulint_cmp(mtr_read_dulint(descr + XDES_ID, + mtr), seg_id)) + && (xdes_get_bit(descr, XDES_FREE_BIT, + hint % FSP_EXTENT_SIZE, mtr) == TRUE)) { + + /* 1. We can take the hinted page + =================================*/ + ret_descr = descr; + ret_page = hint; + /*-----------------------------------------------------------*/ + } else if ((xdes_get_state(descr, mtr) == XDES_FREE) + && ((reserved - used) < reserved / FSEG_FILLFACTOR) + && (used >= FSEG_FRAG_LIMIT)) { + + /* 2. We allocate the free extent from space and can take + ========================================================= + the hinted page + ===============*/ + ret_descr = fsp_alloc_free_extent(space, zip_size, hint, mtr); + + ut_a(ret_descr == descr); + + xdes_set_state(ret_descr, XDES_FSEG, mtr); + mlog_write_dulint(ret_descr + XDES_ID, seg_id, mtr); + flst_add_last(seg_inode + FSEG_FREE, + ret_descr + XDES_FLST_NODE, mtr); + + /* Try to fill the segment free list */ + fseg_fill_free_list(seg_inode, space, zip_size, + hint + FSP_EXTENT_SIZE, mtr); + ret_page = hint; + /*-----------------------------------------------------------*/ + } else if ((direction != FSP_NO_DIR) + && ((reserved - used) < reserved / FSEG_FILLFACTOR) + && (used >= FSEG_FRAG_LIMIT) + && (!!(ret_descr + = fseg_alloc_free_extent(seg_inode, + space, zip_size, mtr)))) { + + /* 3. We take any free extent (which was already assigned above + =============================================================== + in the if-condition to ret_descr) and take the lowest or + ======================================================== + highest page in it, depending on the direction + ==============================================*/ + ret_page = xdes_get_offset(ret_descr); + + if (direction == FSP_DOWN) { + ret_page += FSP_EXTENT_SIZE - 1; + } + /*-----------------------------------------------------------*/ + } else if ((xdes_get_state(descr, mtr) == XDES_FSEG) + && (0 == ut_dulint_cmp(mtr_read_dulint(descr + XDES_ID, + mtr), seg_id)) + && (!xdes_is_full(descr, mtr))) { + + /* 4. We can take the page from the same extent as the + ====================================================== + hinted page (and the extent already belongs to the + ================================================== + segment) + ========*/ + ret_descr = descr; + ret_page = xdes_get_offset(ret_descr) + + xdes_find_bit(ret_descr, XDES_FREE_BIT, TRUE, + hint % FSP_EXTENT_SIZE, mtr); + /*-----------------------------------------------------------*/ + } else if (reserved - used > 0) { + /* 5. We take any unused page from the segment + ==============================================*/ + fil_addr_t first; + + if (flst_get_len(seg_inode + FSEG_NOT_FULL, mtr) > 0) { + first = flst_get_first(seg_inode + FSEG_NOT_FULL, + mtr); + } else if (flst_get_len(seg_inode + FSEG_FREE, mtr) > 0) { + first = flst_get_first(seg_inode + FSEG_FREE, mtr); + } else { + ut_error; + return(FIL_NULL); + } + + ret_descr = xdes_lst_get_descriptor(space, zip_size, + first, mtr); + ret_page = xdes_get_offset(ret_descr) + + xdes_find_bit(ret_descr, XDES_FREE_BIT, TRUE, + 0, mtr); + /*-----------------------------------------------------------*/ + } else if (used < FSEG_FRAG_LIMIT) { + /* 6. We allocate an individual page from the space + ===================================================*/ + ret_page = fsp_alloc_free_page(space, zip_size, hint, mtr); + ret_descr = NULL; + + frag_page_allocated = TRUE; + + if (ret_page != FIL_NULL) { + /* Put the page in the fragment page array of the + segment */ + n = fseg_find_free_frag_page_slot(seg_inode, mtr); + ut_a(n != FIL_NULL); + + fseg_set_nth_frag_page_no(seg_inode, n, ret_page, + mtr); + } + /*-----------------------------------------------------------*/ + } else { + /* 7. We allocate a new extent and take its first page + ======================================================*/ + ret_descr = fseg_alloc_free_extent(seg_inode, + space, zip_size, mtr); + + if (ret_descr == NULL) { + ret_page = FIL_NULL; + } else { + ret_page = xdes_get_offset(ret_descr); + } + } + + if (ret_page == FIL_NULL) { + /* Page could not be allocated */ + + return(FIL_NULL); + } + + if (space != 0) { + space_size = fil_space_get_size(space); + + if (space_size <= ret_page) { + /* It must be that we are extending a single-table + tablespace whose size is still < 64 pages */ + + if (ret_page >= FSP_EXTENT_SIZE) { + fprintf(stderr, + "InnoDB: Error (2): trying to extend" + " a single-table tablespace %lu\n" + "InnoDB: by single page(s) though" + " the space size %lu. Page no %lu.\n", + (ulong) space, (ulong) space_size, + (ulong) ret_page); + return(FIL_NULL); + } + + success = fsp_try_extend_data_file_with_pages( + space, ret_page, space_header, mtr); + if (!success) { + /* No disk space left */ + return(FIL_NULL); + } + } + } + + if (!frag_page_allocated) { + /* Initialize the allocated page to buffer pool, so that it + can be obtained immediately with buf_page_get without need + for a disk read */ + buf_block_t* block; + ulint zip_size = dict_table_flags_to_zip_size( + mach_read_from_4(FSP_SPACE_FLAGS + space_header)); + + block = buf_page_create(space, ret_page, zip_size, mtr); + buf_block_dbg_add_level(block, SYNC_FSP_PAGE); + + if (UNIV_UNLIKELY(block != buf_page_get(space, zip_size, + ret_page, RW_X_LATCH, + mtr))) { + ut_error; + } + + /* The prior contents of the page should be ignored */ + fsp_init_file_page(block, mtr); + + /* At this point we know the extent and the page offset. + The extent is still in the appropriate list (FSEG_NOT_FULL + or FSEG_FREE), and the page is not yet marked as used. */ + + ut_ad(xdes_get_descriptor(space, zip_size, ret_page, mtr) + == ret_descr); + ut_ad(xdes_get_bit(ret_descr, XDES_FREE_BIT, + ret_page % FSP_EXTENT_SIZE, mtr) == TRUE); + + fseg_mark_page_used(seg_inode, space, zip_size, ret_page, mtr); + } + + buf_reset_check_index_page_at_flush(space, ret_page); + + return(ret_page); +} + +/************************************************************************** +Allocates a single free page from a segment. This function implements +the intelligent allocation strategy which tries to minimize file space +fragmentation. */ +UNIV_INTERN +ulint +fseg_alloc_free_page_general( +/*=========================*/ + /* out: allocated page offset, FIL_NULL if no + page could be allocated */ + fseg_header_t* seg_header,/* in: segment header */ + ulint hint, /* in: hint of which page would be desirable */ + byte direction,/* in: if the new page is needed because + of an index page split, and records are + inserted there in order, into which + direction they go alphabetically: FSP_DOWN, + FSP_UP, FSP_NO_DIR */ + ibool has_done_reservation, /* in: TRUE if the caller has + already done the reservation for the page + with fsp_reserve_free_extents, then there + is no need to do the check for this individual + page */ + mtr_t* mtr) /* in: mtr handle */ +{ + fseg_inode_t* inode; + ulint space; + ulint flags; + ulint zip_size; + rw_lock_t* latch; + ibool success; + ulint page_no; + ulint n_reserved; + + space = page_get_space_id(page_align(seg_header)); + + latch = fil_space_get_latch(space, &flags); + + zip_size = dict_table_flags_to_zip_size(flags); + + ut_ad(!mutex_own(&kernel_mutex) + || mtr_memo_contains(mtr, latch, MTR_MEMO_X_LOCK)); + + mtr_x_lock(latch, mtr); + + if (rw_lock_get_x_lock_count(latch) == 1) { + /* This thread did not own the latch before this call: free + excess pages from the insert buffer free list */ + + if (space == IBUF_SPACE_ID) { + ibuf_free_excess_pages(); + } + } + + inode = fseg_inode_get(seg_header, space, zip_size, mtr); + + if (!has_done_reservation) { + success = fsp_reserve_free_extents(&n_reserved, space, 2, + FSP_NORMAL, mtr); + if (!success) { + return(FIL_NULL); + } + } + + page_no = fseg_alloc_free_page_low(space, zip_size, + inode, hint, direction, mtr); + if (!has_done_reservation) { + fil_space_release_free_extents(space, n_reserved); + } + + return(page_no); +} + +/************************************************************************** +Allocates a single free page from a segment. This function implements +the intelligent allocation strategy which tries to minimize file space +fragmentation. */ +UNIV_INTERN +ulint +fseg_alloc_free_page( +/*=================*/ + /* out: allocated page offset, FIL_NULL if no + page could be allocated */ + fseg_header_t* seg_header,/* in: segment header */ + ulint hint, /* in: hint of which page would be desirable */ + byte direction,/* in: if the new page is needed because + of an index page split, and records are + inserted there in order, into which + direction they go alphabetically: FSP_DOWN, + FSP_UP, FSP_NO_DIR */ + mtr_t* mtr) /* in: mtr handle */ +{ + return(fseg_alloc_free_page_general(seg_header, hint, direction, + FALSE, mtr)); +} + +/************************************************************************** +Checks that we have at least 2 frag pages free in the first extent of a +single-table tablespace, and they are also physically initialized to the data +file. That is we have already extended the data file so that those pages are +inside the data file. If not, this function extends the tablespace with +pages. */ +static +ibool +fsp_reserve_free_pages( +/*===================*/ + /* out: TRUE if there were >= 3 free + pages, or we were able to extend */ + ulint space, /* in: space id, must be != 0 */ + fsp_header_t* space_header, /* in: header of that space, + x-latched */ + ulint size, /* in: size of the tablespace in pages, + must be < FSP_EXTENT_SIZE / 2 */ + mtr_t* mtr) /* in: mtr */ +{ + xdes_t* descr; + ulint n_used; + + ut_a(space != 0); + ut_a(size < FSP_EXTENT_SIZE / 2); + + descr = xdes_get_descriptor_with_space_hdr(space_header, space, 0, + mtr); + n_used = xdes_get_n_used(descr, mtr); + + ut_a(n_used <= size); + + if (size >= n_used + 2) { + + return(TRUE); + } + + return(fsp_try_extend_data_file_with_pages(space, n_used + 1, + space_header, mtr)); +} + +/************************************************************************** +Reserves free pages from a tablespace. All mini-transactions which may +use several pages from the tablespace should call this function beforehand +and reserve enough free extents so that they certainly will be able +to do their operation, like a B-tree page split, fully. Reservations +must be released with function fil_space_release_free_extents! + +The alloc_type below has the following meaning: FSP_NORMAL means an +operation which will probably result in more space usage, like an +insert in a B-tree; FSP_UNDO means allocation to undo logs: if we are +deleting rows, then this allocation will in the long run result in +less space usage (after a purge); FSP_CLEANING means allocation done +in a physical record delete (like in a purge) or other cleaning operation +which will result in less space usage in the long run. We prefer the latter +two types of allocation: when space is scarce, FSP_NORMAL allocations +will not succeed, but the latter two allocations will succeed, if possible. +The purpose is to avoid dead end where the database is full but the +user cannot free any space because these freeing operations temporarily +reserve some space. + +Single-table tablespaces whose size is < 32 pages are a special case. In this +function we would liberally reserve several 64 page extents for every page +split or merge in a B-tree. But we do not want to waste disk space if the table +only occupies < 32 pages. That is why we apply different rules in that special +case, just ensuring that there are 3 free pages available. */ +UNIV_INTERN +ibool +fsp_reserve_free_extents( +/*=====================*/ + /* out: TRUE if we were able to make the reservation */ + ulint* n_reserved,/* out: number of extents actually reserved; if we + return TRUE and the tablespace size is < 64 pages, + then this can be 0, otherwise it is n_ext */ + ulint space, /* in: space id */ + ulint n_ext, /* in: number of extents to reserve */ + ulint alloc_type,/* in: FSP_NORMAL, FSP_UNDO, or FSP_CLEANING */ + mtr_t* mtr) /* in: mtr */ +{ + fsp_header_t* space_header; + rw_lock_t* latch; + ulint n_free_list_ext; + ulint free_limit; + ulint size; + ulint flags; + ulint zip_size; + ulint n_free; + ulint n_free_up; + ulint reserve; + ibool success; + ulint n_pages_added; + + ut_ad(mtr); + *n_reserved = n_ext; + + latch = fil_space_get_latch(space, &flags); + zip_size = dict_table_flags_to_zip_size(flags); + + ut_ad(!mutex_own(&kernel_mutex) + || mtr_memo_contains(mtr, latch, MTR_MEMO_X_LOCK)); + + mtr_x_lock(latch, mtr); + + space_header = fsp_get_space_header(space, zip_size, mtr); +try_again: + size = mtr_read_ulint(space_header + FSP_SIZE, MLOG_4BYTES, mtr); + + if (size < FSP_EXTENT_SIZE / 2) { + /* Use different rules for small single-table tablespaces */ + *n_reserved = 0; + return(fsp_reserve_free_pages(space, space_header, size, mtr)); + } + + n_free_list_ext = flst_get_len(space_header + FSP_FREE, mtr); + + free_limit = mtr_read_ulint(space_header + FSP_FREE_LIMIT, + MLOG_4BYTES, mtr); + + /* Below we play safe when counting free extents above the free limit: + some of them will contain extent descriptor pages, and therefore + will not be free extents */ + + n_free_up = (size - free_limit) / FSP_EXTENT_SIZE; + + if (n_free_up > 0) { + n_free_up--; + if (!zip_size) { + n_free_up -= n_free_up + / (UNIV_PAGE_SIZE / FSP_EXTENT_SIZE); + } else { + n_free_up -= n_free_up + / (zip_size / FSP_EXTENT_SIZE); + } + } + + n_free = n_free_list_ext + n_free_up; + + if (alloc_type == FSP_NORMAL) { + /* We reserve 1 extent + 0.5 % of the space size to undo logs + and 1 extent + 0.5 % to cleaning operations; NOTE: this source + code is duplicated in the function below! */ + + reserve = 2 + ((size / FSP_EXTENT_SIZE) * 2) / 200; + + if (n_free <= reserve + n_ext) { + + goto try_to_extend; + } + } else if (alloc_type == FSP_UNDO) { + /* We reserve 0.5 % of the space size to cleaning operations */ + + reserve = 1 + ((size / FSP_EXTENT_SIZE) * 1) / 200; + + if (n_free <= reserve + n_ext) { + + goto try_to_extend; + } + } else { + ut_a(alloc_type == FSP_CLEANING); + } + + success = fil_space_reserve_free_extents(space, n_free, n_ext); + + if (success) { + return(TRUE); + } +try_to_extend: + success = fsp_try_extend_data_file(&n_pages_added, space, + space_header, mtr); + if (success && n_pages_added > 0) { + + goto try_again; + } + + return(FALSE); +} + +/************************************************************************** +This function should be used to get information on how much we still +will be able to insert new data to the database without running out the +tablespace. Only free extents are taken into account and we also subtract +the safety margin required by the above function fsp_reserve_free_extents. */ +UNIV_INTERN +ullint +fsp_get_available_space_in_free_extents( +/*====================================*/ + /* out: available space in kB */ + ulint space) /* in: space id */ +{ + fsp_header_t* space_header; + ulint n_free_list_ext; + ulint free_limit; + ulint size; + ulint flags; + ulint zip_size; + ulint n_free; + ulint n_free_up; + ulint reserve; + rw_lock_t* latch; + mtr_t mtr; + + ut_ad(!mutex_own(&kernel_mutex)); + + mtr_start(&mtr); + + latch = fil_space_get_latch(space, &flags); + zip_size = dict_table_flags_to_zip_size(flags); + + mtr_x_lock(latch, &mtr); + + space_header = fsp_get_space_header(space, zip_size, &mtr); + + size = mtr_read_ulint(space_header + FSP_SIZE, MLOG_4BYTES, &mtr); + + n_free_list_ext = flst_get_len(space_header + FSP_FREE, &mtr); + + free_limit = mtr_read_ulint(space_header + FSP_FREE_LIMIT, + MLOG_4BYTES, &mtr); + mtr_commit(&mtr); + + if (size < FSP_EXTENT_SIZE) { + ut_a(space != 0); /* This must be a single-table + tablespace */ + + return(0); /* TODO: count free frag pages and + return a value based on that */ + } + + /* Below we play safe when counting free extents above the free limit: + some of them will contain extent descriptor pages, and therefore + will not be free extents */ + + n_free_up = (size - free_limit) / FSP_EXTENT_SIZE; + + if (n_free_up > 0) { + n_free_up--; + if (!zip_size) { + n_free_up -= n_free_up + / (UNIV_PAGE_SIZE / FSP_EXTENT_SIZE); + } else { + n_free_up -= n_free_up + / (zip_size / FSP_EXTENT_SIZE); + } + } + + n_free = n_free_list_ext + n_free_up; + + /* We reserve 1 extent + 0.5 % of the space size to undo logs + and 1 extent + 0.5 % to cleaning operations; NOTE: this source + code is duplicated in the function above! */ + + reserve = 2 + ((size / FSP_EXTENT_SIZE) * 2) / 200; + + if (reserve > n_free) { + return(0); + } + + if (!zip_size) { + return((ullint) (n_free - reserve) + * FSP_EXTENT_SIZE + * (UNIV_PAGE_SIZE / 1024)); + } else { + return((ullint) (n_free - reserve) + * FSP_EXTENT_SIZE + * (zip_size / 1024)); + } +} + +/************************************************************************ +Marks a page used. The page must reside within the extents of the given +segment. */ +static +void +fseg_mark_page_used( +/*================*/ + fseg_inode_t* seg_inode,/* in: segment inode */ + ulint space, /* in: space id */ + ulint zip_size,/* in: compressed page size in bytes + or 0 for uncompressed pages */ + ulint page, /* in: page offset */ + mtr_t* mtr) /* in: mtr */ +{ + xdes_t* descr; + ulint not_full_n_used; + + ut_ad(seg_inode && mtr); + ut_ad(!((page_offset(seg_inode) - FSEG_ARR_OFFSET) % FSEG_INODE_SIZE)); + + descr = xdes_get_descriptor(space, zip_size, page, mtr); + + ut_ad(mtr_read_ulint(seg_inode + FSEG_ID, MLOG_4BYTES, mtr) + == mtr_read_ulint(descr + XDES_ID, MLOG_4BYTES, mtr)); + + if (xdes_is_free(descr, mtr)) { + /* We move the extent from the free list to the + NOT_FULL list */ + flst_remove(seg_inode + FSEG_FREE, descr + XDES_FLST_NODE, + mtr); + flst_add_last(seg_inode + FSEG_NOT_FULL, + descr + XDES_FLST_NODE, mtr); + } + + ut_ad(xdes_get_bit(descr, XDES_FREE_BIT, page % FSP_EXTENT_SIZE, mtr) + == TRUE); + /* We mark the page as used */ + xdes_set_bit(descr, XDES_FREE_BIT, page % FSP_EXTENT_SIZE, FALSE, mtr); + + not_full_n_used = mtr_read_ulint(seg_inode + FSEG_NOT_FULL_N_USED, + MLOG_4BYTES, mtr); + not_full_n_used++; + mlog_write_ulint(seg_inode + FSEG_NOT_FULL_N_USED, not_full_n_used, + MLOG_4BYTES, mtr); + if (xdes_is_full(descr, mtr)) { + /* We move the extent from the NOT_FULL list to the + FULL list */ + flst_remove(seg_inode + FSEG_NOT_FULL, + descr + XDES_FLST_NODE, mtr); + flst_add_last(seg_inode + FSEG_FULL, + descr + XDES_FLST_NODE, mtr); + + mlog_write_ulint(seg_inode + FSEG_NOT_FULL_N_USED, + not_full_n_used - FSP_EXTENT_SIZE, + MLOG_4BYTES, mtr); + } +} + +/************************************************************************** +Frees a single page of a segment. */ +static +void +fseg_free_page_low( +/*===============*/ + fseg_inode_t* seg_inode, /* in: segment inode */ + ulint space, /* in: space id */ + ulint zip_size,/* in: compressed page size in bytes + or 0 for uncompressed pages */ + ulint page, /* in: page offset */ + mtr_t* mtr) /* in: mtr handle */ +{ + xdes_t* descr; + ulint not_full_n_used; + ulint state; + dulint descr_id; + dulint seg_id; + ulint i; + + ut_ad(seg_inode && mtr); + ut_ad(mach_read_from_4(seg_inode + FSEG_MAGIC_N) + == FSEG_MAGIC_N_VALUE); + ut_ad(!((page_offset(seg_inode) - FSEG_ARR_OFFSET) % FSEG_INODE_SIZE)); + + /* Drop search system page hash index if the page is found in + the pool and is hashed */ + + btr_search_drop_page_hash_when_freed(space, zip_size, page); + + descr = xdes_get_descriptor(space, zip_size, page, mtr); + + ut_a(descr); + if (xdes_get_bit(descr, XDES_FREE_BIT, page % FSP_EXTENT_SIZE, mtr)) { + fputs("InnoDB: Dump of the tablespace extent descriptor: ", + stderr); + ut_print_buf(stderr, descr, 40); + + fprintf(stderr, "\n" + "InnoDB: Serious error! InnoDB is trying to" + " free page %lu\n" + "InnoDB: though it is already marked as free" + " in the tablespace!\n" + "InnoDB: The tablespace free space info is corrupt.\n" + "InnoDB: You may need to dump your" + " InnoDB tables and recreate the whole\n" + "InnoDB: database!\n", (ulong) page); +crash: + fputs("InnoDB: Please refer to\n" + "InnoDB: http://dev.mysql.com/doc/refman/5.1/en/" + "forcing-recovery.html\n" + "InnoDB: about forcing recovery.\n", stderr); + ut_error; + } + + state = xdes_get_state(descr, mtr); + + if (state != XDES_FSEG) { + /* The page is in the fragment pages of the segment */ + + for (i = 0;; i++) { + if (fseg_get_nth_frag_page_no(seg_inode, i, mtr) + == page) { + + fseg_set_nth_frag_page_no(seg_inode, i, + FIL_NULL, mtr); + break; + } + } + + fsp_free_page(space, zip_size, page, mtr); + + return; + } + + /* If we get here, the page is in some extent of the segment */ + + descr_id = mtr_read_dulint(descr + XDES_ID, mtr); + seg_id = mtr_read_dulint(seg_inode + FSEG_ID, mtr); +#if 0 + fprintf(stderr, + "InnoDB: InnoDB is freeing space %lu page %lu,\n" + "InnoDB: which belongs to descr seg %lu %lu\n" + "InnoDB: segment %lu %lu.\n", + (ulong) space, (ulong) page, + (ulong) ut_dulint_get_high(descr_id), + (ulong) ut_dulint_get_low(descr_id), + (ulong) ut_dulint_get_high(seg_id), + (ulong) ut_dulint_get_low(seg_id)); +#endif /* 0 */ + if (0 != ut_dulint_cmp(descr_id, seg_id)) { + fputs("InnoDB: Dump of the tablespace extent descriptor: ", + stderr); + ut_print_buf(stderr, descr, 40); + fputs("\nInnoDB: Dump of the segment inode: ", stderr); + ut_print_buf(stderr, seg_inode, 40); + putc('\n', stderr); + + fprintf(stderr, + "InnoDB: Serious error: InnoDB is trying to" + " free space %lu page %lu,\n" + "InnoDB: which does not belong to" + " segment %lu %lu but belongs\n" + "InnoDB: to segment %lu %lu.\n", + (ulong) space, (ulong) page, + (ulong) ut_dulint_get_high(descr_id), + (ulong) ut_dulint_get_low(descr_id), + (ulong) ut_dulint_get_high(seg_id), + (ulong) ut_dulint_get_low(seg_id)); + goto crash; + } + + not_full_n_used = mtr_read_ulint(seg_inode + FSEG_NOT_FULL_N_USED, + MLOG_4BYTES, mtr); + if (xdes_is_full(descr, mtr)) { + /* The fragment is full: move it to another list */ + flst_remove(seg_inode + FSEG_FULL, + descr + XDES_FLST_NODE, mtr); + flst_add_last(seg_inode + FSEG_NOT_FULL, + descr + XDES_FLST_NODE, mtr); + mlog_write_ulint(seg_inode + FSEG_NOT_FULL_N_USED, + not_full_n_used + FSP_EXTENT_SIZE - 1, + MLOG_4BYTES, mtr); + } else { + ut_a(not_full_n_used > 0); + mlog_write_ulint(seg_inode + FSEG_NOT_FULL_N_USED, + not_full_n_used - 1, MLOG_4BYTES, mtr); + } + + xdes_set_bit(descr, XDES_FREE_BIT, page % FSP_EXTENT_SIZE, TRUE, mtr); + xdes_set_bit(descr, XDES_CLEAN_BIT, page % FSP_EXTENT_SIZE, TRUE, mtr); + + if (xdes_is_free(descr, mtr)) { + /* The extent has become free: free it to space */ + flst_remove(seg_inode + FSEG_NOT_FULL, + descr + XDES_FLST_NODE, mtr); + fsp_free_extent(space, zip_size, page, mtr); + } +} + +/************************************************************************** +Frees a single page of a segment. */ +UNIV_INTERN +void +fseg_free_page( +/*===========*/ + fseg_header_t* seg_header, /* in: segment header */ + ulint space, /* in: space id */ + ulint page, /* in: page offset */ + mtr_t* mtr) /* in: mtr handle */ +{ + ulint flags; + ulint zip_size; + fseg_inode_t* seg_inode; + rw_lock_t* latch; + + latch = fil_space_get_latch(space, &flags); + zip_size = dict_table_flags_to_zip_size(flags); + + ut_ad(!mutex_own(&kernel_mutex) + || mtr_memo_contains(mtr, latch, MTR_MEMO_X_LOCK)); + + mtr_x_lock(latch, mtr); + + seg_inode = fseg_inode_get(seg_header, space, zip_size, mtr); + + fseg_free_page_low(seg_inode, space, zip_size, page, mtr); + +#ifdef UNIV_DEBUG_FILE_ACCESSES + buf_page_set_file_page_was_freed(space, page); +#endif +} + +/************************************************************************** +Frees an extent of a segment to the space free list. */ +static +void +fseg_free_extent( +/*=============*/ + fseg_inode_t* seg_inode, /* in: segment inode */ + ulint space, /* in: space id */ + ulint zip_size,/* in: compressed page size in bytes + or 0 for uncompressed pages */ + ulint page, /* in: a page in the extent */ + mtr_t* mtr) /* in: mtr handle */ +{ + ulint first_page_in_extent; + xdes_t* descr; + ulint not_full_n_used; + ulint descr_n_used; + ulint i; + + ut_ad(seg_inode && mtr); + + descr = xdes_get_descriptor(space, zip_size, page, mtr); + + ut_a(xdes_get_state(descr, mtr) == XDES_FSEG); + ut_a(0 == ut_dulint_cmp(mtr_read_dulint(descr + XDES_ID, mtr), + mtr_read_dulint(seg_inode + FSEG_ID, mtr))); + + first_page_in_extent = page - (page % FSP_EXTENT_SIZE); + + for (i = 0; i < FSP_EXTENT_SIZE; i++) { + if (FALSE == xdes_get_bit(descr, XDES_FREE_BIT, i, mtr)) { + + /* Drop search system page hash index if the page is + found in the pool and is hashed */ + + btr_search_drop_page_hash_when_freed( + space, zip_size, first_page_in_extent + i); + } + } + + if (xdes_is_full(descr, mtr)) { + flst_remove(seg_inode + FSEG_FULL, + descr + XDES_FLST_NODE, mtr); + } else if (xdes_is_free(descr, mtr)) { + flst_remove(seg_inode + FSEG_FREE, + descr + XDES_FLST_NODE, mtr); + } else { + flst_remove(seg_inode + FSEG_NOT_FULL, + descr + XDES_FLST_NODE, mtr); + + not_full_n_used = mtr_read_ulint( + seg_inode + FSEG_NOT_FULL_N_USED, MLOG_4BYTES, mtr); + + descr_n_used = xdes_get_n_used(descr, mtr); + ut_a(not_full_n_used >= descr_n_used); + mlog_write_ulint(seg_inode + FSEG_NOT_FULL_N_USED, + not_full_n_used - descr_n_used, + MLOG_4BYTES, mtr); + } + + fsp_free_extent(space, zip_size, page, mtr); + +#ifdef UNIV_DEBUG_FILE_ACCESSES + for (i = 0; i < FSP_EXTENT_SIZE; i++) { + + buf_page_set_file_page_was_freed(space, + first_page_in_extent + i); + } +#endif +} + +/************************************************************************** +Frees part of a segment. This function can be used to free a segment by +repeatedly calling this function in different mini-transactions. Doing +the freeing in a single mini-transaction might result in too big a +mini-transaction. */ +UNIV_INTERN +ibool +fseg_free_step( +/*===========*/ + /* out: TRUE if freeing completed */ + fseg_header_t* header, /* in, own: segment header; NOTE: if the header + resides on the first page of the frag list + of the segment, this pointer becomes obsolete + after the last freeing step */ + mtr_t* mtr) /* in: mtr */ +{ + ulint n; + ulint page; + xdes_t* descr; + fseg_inode_t* inode; + ulint space; + ulint flags; + ulint zip_size; + ulint header_page; + rw_lock_t* latch; + + space = page_get_space_id(page_align(header)); + header_page = page_get_page_no(page_align(header)); + + latch = fil_space_get_latch(space, &flags); + zip_size = dict_table_flags_to_zip_size(flags); + + ut_ad(!mutex_own(&kernel_mutex) + || mtr_memo_contains(mtr, latch, MTR_MEMO_X_LOCK)); + + mtr_x_lock(latch, mtr); + + descr = xdes_get_descriptor(space, zip_size, header_page, mtr); + + /* Check that the header resides on a page which has not been + freed yet */ + + ut_a(descr); + ut_a(xdes_get_bit(descr, XDES_FREE_BIT, + header_page % FSP_EXTENT_SIZE, mtr) == FALSE); + inode = fseg_inode_get(header, space, zip_size, mtr); + + descr = fseg_get_first_extent(inode, space, zip_size, mtr); + + if (descr != NULL) { + /* Free the extent held by the segment */ + page = xdes_get_offset(descr); + + fseg_free_extent(inode, space, zip_size, page, mtr); + + return(FALSE); + } + + /* Free a frag page */ + n = fseg_find_last_used_frag_page_slot(inode, mtr); + + if (n == ULINT_UNDEFINED) { + /* Freeing completed: free the segment inode */ + fsp_free_seg_inode(space, zip_size, inode, mtr); + + return(TRUE); + } + + fseg_free_page_low(inode, space, zip_size, + fseg_get_nth_frag_page_no(inode, n, mtr), mtr); + + n = fseg_find_last_used_frag_page_slot(inode, mtr); + + if (n == ULINT_UNDEFINED) { + /* Freeing completed: free the segment inode */ + fsp_free_seg_inode(space, zip_size, inode, mtr); + + return(TRUE); + } + + return(FALSE); +} + +/************************************************************************** +Frees part of a segment. Differs from fseg_free_step because this function +leaves the header page unfreed. */ +UNIV_INTERN +ibool +fseg_free_step_not_header( +/*======================*/ + /* out: TRUE if freeing completed, except the + header page */ + fseg_header_t* header, /* in: segment header which must reside on + the first fragment page of the segment */ + mtr_t* mtr) /* in: mtr */ +{ + ulint n; + ulint page; + xdes_t* descr; + fseg_inode_t* inode; + ulint space; + ulint flags; + ulint zip_size; + ulint page_no; + rw_lock_t* latch; + + space = page_get_space_id(page_align(header)); + + latch = fil_space_get_latch(space, &flags); + zip_size = dict_table_flags_to_zip_size(flags); + + ut_ad(!mutex_own(&kernel_mutex) + || mtr_memo_contains(mtr, latch, MTR_MEMO_X_LOCK)); + + mtr_x_lock(latch, mtr); + + inode = fseg_inode_get(header, space, zip_size, mtr); + + descr = fseg_get_first_extent(inode, space, zip_size, mtr); + + if (descr != NULL) { + /* Free the extent held by the segment */ + page = xdes_get_offset(descr); + + fseg_free_extent(inode, space, zip_size, page, mtr); + + return(FALSE); + } + + /* Free a frag page */ + + n = fseg_find_last_used_frag_page_slot(inode, mtr); + + if (n == ULINT_UNDEFINED) { + ut_error; + } + + page_no = fseg_get_nth_frag_page_no(inode, n, mtr); + + if (page_no == page_get_page_no(page_align(header))) { + + return(TRUE); + } + + fseg_free_page_low(inode, space, zip_size, page_no, mtr); + + return(FALSE); +} + +/*********************************************************************** +Frees a segment. The freeing is performed in several mini-transactions, +so that there is no danger of bufferfixing too many buffer pages. */ +UNIV_INTERN +void +fseg_free( +/*======*/ + ulint space, /* in: space id */ + ulint zip_size,/* in: compressed page size in bytes + or 0 for uncompressed pages */ + ulint page_no,/* in: page number where the segment header is + placed */ + ulint offset) /* in: byte offset of the segment header on that + page */ +{ + mtr_t mtr; + ibool finished; + fseg_header_t* header; + fil_addr_t addr; + + addr.page = page_no; + addr.boffset = offset; + + for (;;) { + mtr_start(&mtr); + + header = fut_get_ptr(space, zip_size, addr, RW_X_LATCH, &mtr); + + finished = fseg_free_step(header, &mtr); + + mtr_commit(&mtr); + + if (finished) { + + return; + } + } +} + +/************************************************************************** +Returns the first extent descriptor for a segment. We think of the extent +lists of the segment catenated in the order FSEG_FULL -> FSEG_NOT_FULL +-> FSEG_FREE. */ +static +xdes_t* +fseg_get_first_extent( +/*==================*/ + /* out: the first extent descriptor, or NULL if + none */ + fseg_inode_t* inode, /* in: segment inode */ + ulint space, /* in: space id */ + ulint zip_size,/* in: compressed page size in bytes + or 0 for uncompressed pages */ + mtr_t* mtr) /* in: mtr */ +{ + fil_addr_t first; + xdes_t* descr; + + ut_ad(inode && mtr); + + ut_ad(space == page_get_space_id(page_align(inode))); + + first = fil_addr_null; + + if (flst_get_len(inode + FSEG_FULL, mtr) > 0) { + + first = flst_get_first(inode + FSEG_FULL, mtr); + + } else if (flst_get_len(inode + FSEG_NOT_FULL, mtr) > 0) { + + first = flst_get_first(inode + FSEG_NOT_FULL, mtr); + + } else if (flst_get_len(inode + FSEG_FREE, mtr) > 0) { + + first = flst_get_first(inode + FSEG_FREE, mtr); + } + + if (first.page == FIL_NULL) { + + return(NULL); + } + descr = xdes_lst_get_descriptor(space, zip_size, first, mtr); + + return(descr); +} + +/*********************************************************************** +Validates a segment. */ +static +ibool +fseg_validate_low( +/*==============*/ + /* out: TRUE if ok */ + fseg_inode_t* inode, /* in: segment inode */ + mtr_t* mtr2) /* in: mtr */ +{ + ulint space; + dulint seg_id; + mtr_t mtr; + xdes_t* descr; + fil_addr_t node_addr; + ulint n_used = 0; + ulint n_used2 = 0; + + ut_ad(mtr_memo_contains_page(mtr2, inode, MTR_MEMO_PAGE_X_FIX)); + ut_ad(mach_read_from_4(inode + FSEG_MAGIC_N) == FSEG_MAGIC_N_VALUE); + + space = page_get_space_id(page_align(inode)); + + seg_id = mtr_read_dulint(inode + FSEG_ID, mtr2); + n_used = mtr_read_ulint(inode + FSEG_NOT_FULL_N_USED, + MLOG_4BYTES, mtr2); + flst_validate(inode + FSEG_FREE, mtr2); + flst_validate(inode + FSEG_NOT_FULL, mtr2); + flst_validate(inode + FSEG_FULL, mtr2); + + /* Validate FSEG_FREE list */ + node_addr = flst_get_first(inode + FSEG_FREE, mtr2); + + while (!fil_addr_is_null(node_addr)) { + ulint flags; + ulint zip_size; + + mtr_start(&mtr); + mtr_x_lock(fil_space_get_latch(space, &flags), &mtr); + zip_size = dict_table_flags_to_zip_size(flags); + + descr = xdes_lst_get_descriptor(space, zip_size, + node_addr, &mtr); + + ut_a(xdes_get_n_used(descr, &mtr) == 0); + ut_a(xdes_get_state(descr, &mtr) == XDES_FSEG); + ut_a(!ut_dulint_cmp(mtr_read_dulint(descr + XDES_ID, &mtr), + seg_id)); + + node_addr = flst_get_next_addr(descr + XDES_FLST_NODE, &mtr); + mtr_commit(&mtr); + } + + /* Validate FSEG_NOT_FULL list */ + + node_addr = flst_get_first(inode + FSEG_NOT_FULL, mtr2); + + while (!fil_addr_is_null(node_addr)) { + ulint flags; + ulint zip_size; + + mtr_start(&mtr); + mtr_x_lock(fil_space_get_latch(space, &flags), &mtr); + zip_size = dict_table_flags_to_zip_size(flags); + + descr = xdes_lst_get_descriptor(space, zip_size, + node_addr, &mtr); + + ut_a(xdes_get_n_used(descr, &mtr) > 0); + ut_a(xdes_get_n_used(descr, &mtr) < FSP_EXTENT_SIZE); + ut_a(xdes_get_state(descr, &mtr) == XDES_FSEG); + ut_a(!ut_dulint_cmp(mtr_read_dulint(descr + XDES_ID, &mtr), + seg_id)); + + n_used2 += xdes_get_n_used(descr, &mtr); + + node_addr = flst_get_next_addr(descr + XDES_FLST_NODE, &mtr); + mtr_commit(&mtr); + } + + /* Validate FSEG_FULL list */ + + node_addr = flst_get_first(inode + FSEG_FULL, mtr2); + + while (!fil_addr_is_null(node_addr)) { + ulint flags; + ulint zip_size; + + mtr_start(&mtr); + mtr_x_lock(fil_space_get_latch(space, &flags), &mtr); + zip_size = dict_table_flags_to_zip_size(flags); + + descr = xdes_lst_get_descriptor(space, zip_size, + node_addr, &mtr); + + ut_a(xdes_get_n_used(descr, &mtr) == FSP_EXTENT_SIZE); + ut_a(xdes_get_state(descr, &mtr) == XDES_FSEG); + ut_a(!ut_dulint_cmp(mtr_read_dulint(descr + XDES_ID, &mtr), + seg_id)); + + node_addr = flst_get_next_addr(descr + XDES_FLST_NODE, &mtr); + mtr_commit(&mtr); + } + + ut_a(n_used == n_used2); + + return(TRUE); +} + +/*********************************************************************** +Validates a segment. */ +UNIV_INTERN +ibool +fseg_validate( +/*==========*/ + /* out: TRUE if ok */ + fseg_header_t* header, /* in: segment header */ + mtr_t* mtr) /* in: mtr */ +{ + fseg_inode_t* inode; + ibool ret; + ulint space; + ulint flags; + ulint zip_size; + + space = page_get_space_id(page_align(header)); + + mtr_x_lock(fil_space_get_latch(space, &flags), mtr); + zip_size = dict_table_flags_to_zip_size(flags); + + inode = fseg_inode_get(header, space, zip_size, mtr); + + ret = fseg_validate_low(inode, mtr); + + return(ret); +} + +/*********************************************************************** +Writes info of a segment. */ +static +void +fseg_print_low( +/*===========*/ + fseg_inode_t* inode, /* in: segment inode */ + mtr_t* mtr) /* in: mtr */ +{ + ulint space; + ulint seg_id_low; + ulint seg_id_high; + ulint n_used; + ulint n_frag; + ulint n_free; + ulint n_not_full; + ulint n_full; + ulint reserved; + ulint used; + ulint page_no; + dulint d_var; + + ut_ad(mtr_memo_contains_page(mtr, inode, MTR_MEMO_PAGE_X_FIX)); + space = page_get_space_id(page_align(inode)); + page_no = page_get_page_no(page_align(inode)); + + reserved = fseg_n_reserved_pages_low(inode, &used, mtr); + + d_var = mtr_read_dulint(inode + FSEG_ID, mtr); + + seg_id_low = ut_dulint_get_low(d_var); + seg_id_high = ut_dulint_get_high(d_var); + + n_used = mtr_read_ulint(inode + FSEG_NOT_FULL_N_USED, + MLOG_4BYTES, mtr); + n_frag = fseg_get_n_frag_pages(inode, mtr); + n_free = flst_get_len(inode + FSEG_FREE, mtr); + n_not_full = flst_get_len(inode + FSEG_NOT_FULL, mtr); + n_full = flst_get_len(inode + FSEG_FULL, mtr); + + fprintf(stderr, + "SEGMENT id %lu %lu space %lu; page %lu;" + " res %lu used %lu; full ext %lu\n" + "fragm pages %lu; free extents %lu;" + " not full extents %lu: pages %lu\n", + (ulong) seg_id_high, (ulong) seg_id_low, + (ulong) space, (ulong) page_no, + (ulong) reserved, (ulong) used, (ulong) n_full, + (ulong) n_frag, (ulong) n_free, (ulong) n_not_full, + (ulong) n_used); +} + +#ifdef UNIV_BTR_PRINT +/*********************************************************************** +Writes info of a segment. */ +UNIV_INTERN +void +fseg_print( +/*=======*/ + fseg_header_t* header, /* in: segment header */ + mtr_t* mtr) /* in: mtr */ +{ + fseg_inode_t* inode; + ulint space; + ulint flags; + ulint zip_size; + + space = page_get_space_id(page_align(header)); + + mtr_x_lock(fil_space_get_latch(space, &flags), mtr); + zip_size = dict_table_flags_to_zip_size(flags); + + inode = fseg_inode_get(header, space, zip_size, mtr); + + fseg_print_low(inode, mtr); +} +#endif /* UNIV_BTR_PRINT */ + +/*********************************************************************** +Validates the file space system and its segments. */ +UNIV_INTERN +ibool +fsp_validate( +/*=========*/ + /* out: TRUE if ok */ + ulint space) /* in: space id */ +{ + fsp_header_t* header; + fseg_inode_t* seg_inode; + page_t* seg_inode_page; + rw_lock_t* latch; + ulint size; + ulint flags; + ulint zip_size; + ulint free_limit; + ulint frag_n_used; + mtr_t mtr; + mtr_t mtr2; + xdes_t* descr; + fil_addr_t node_addr; + fil_addr_t next_node_addr; + ulint descr_count = 0; + ulint n_used = 0; + ulint n_used2 = 0; + ulint n_full_frag_pages; + ulint n; + ulint seg_inode_len_free; + ulint seg_inode_len_full; + + latch = fil_space_get_latch(space, &flags); + zip_size = dict_table_flags_to_zip_size(flags); + ut_a(ut_is_2pow(zip_size)); + ut_a(zip_size <= UNIV_PAGE_SIZE); + ut_a(!zip_size || zip_size >= PAGE_ZIP_MIN_SIZE); + + /* Start first a mini-transaction mtr2 to lock out all other threads + from the fsp system */ + mtr_start(&mtr2); + mtr_x_lock(latch, &mtr2); + + mtr_start(&mtr); + mtr_x_lock(latch, &mtr); + + header = fsp_get_space_header(space, zip_size, &mtr); + + size = mtr_read_ulint(header + FSP_SIZE, MLOG_4BYTES, &mtr); + free_limit = mtr_read_ulint(header + FSP_FREE_LIMIT, + MLOG_4BYTES, &mtr); + frag_n_used = mtr_read_ulint(header + FSP_FRAG_N_USED, + MLOG_4BYTES, &mtr); + + n_full_frag_pages = FSP_EXTENT_SIZE + * flst_get_len(header + FSP_FULL_FRAG, &mtr); + + if (UNIV_UNLIKELY(free_limit > size)) { + + ut_a(space != 0); + ut_a(size < FSP_EXTENT_SIZE); + } + + flst_validate(header + FSP_FREE, &mtr); + flst_validate(header + FSP_FREE_FRAG, &mtr); + flst_validate(header + FSP_FULL_FRAG, &mtr); + + mtr_commit(&mtr); + + /* Validate FSP_FREE list */ + mtr_start(&mtr); + mtr_x_lock(latch, &mtr); + + header = fsp_get_space_header(space, zip_size, &mtr); + node_addr = flst_get_first(header + FSP_FREE, &mtr); + + mtr_commit(&mtr); + + while (!fil_addr_is_null(node_addr)) { + mtr_start(&mtr); + mtr_x_lock(latch, &mtr); + + descr_count++; + descr = xdes_lst_get_descriptor(space, zip_size, + node_addr, &mtr); + + ut_a(xdes_get_n_used(descr, &mtr) == 0); + ut_a(xdes_get_state(descr, &mtr) == XDES_FREE); + + node_addr = flst_get_next_addr(descr + XDES_FLST_NODE, &mtr); + mtr_commit(&mtr); + } + + /* Validate FSP_FREE_FRAG list */ + mtr_start(&mtr); + mtr_x_lock(latch, &mtr); + + header = fsp_get_space_header(space, zip_size, &mtr); + node_addr = flst_get_first(header + FSP_FREE_FRAG, &mtr); + + mtr_commit(&mtr); + + while (!fil_addr_is_null(node_addr)) { + mtr_start(&mtr); + mtr_x_lock(latch, &mtr); + + descr_count++; + descr = xdes_lst_get_descriptor(space, zip_size, + node_addr, &mtr); + + ut_a(xdes_get_n_used(descr, &mtr) > 0); + ut_a(xdes_get_n_used(descr, &mtr) < FSP_EXTENT_SIZE); + ut_a(xdes_get_state(descr, &mtr) == XDES_FREE_FRAG); + + n_used += xdes_get_n_used(descr, &mtr); + node_addr = flst_get_next_addr(descr + XDES_FLST_NODE, &mtr); + + mtr_commit(&mtr); + } + + /* Validate FSP_FULL_FRAG list */ + mtr_start(&mtr); + mtr_x_lock(latch, &mtr); + + header = fsp_get_space_header(space, zip_size, &mtr); + node_addr = flst_get_first(header + FSP_FULL_FRAG, &mtr); + + mtr_commit(&mtr); + + while (!fil_addr_is_null(node_addr)) { + mtr_start(&mtr); + mtr_x_lock(latch, &mtr); + + descr_count++; + descr = xdes_lst_get_descriptor(space, zip_size, + node_addr, &mtr); + + ut_a(xdes_get_n_used(descr, &mtr) == FSP_EXTENT_SIZE); + ut_a(xdes_get_state(descr, &mtr) == XDES_FULL_FRAG); + + node_addr = flst_get_next_addr(descr + XDES_FLST_NODE, &mtr); + mtr_commit(&mtr); + } + + /* Validate segments */ + mtr_start(&mtr); + mtr_x_lock(latch, &mtr); + + header = fsp_get_space_header(space, zip_size, &mtr); + + node_addr = flst_get_first(header + FSP_SEG_INODES_FULL, &mtr); + + seg_inode_len_full = flst_get_len(header + FSP_SEG_INODES_FULL, &mtr); + + mtr_commit(&mtr); + + while (!fil_addr_is_null(node_addr)) { + + n = 0; + do { + mtr_start(&mtr); + mtr_x_lock(latch, &mtr); + + seg_inode_page = fut_get_ptr( + space, zip_size, node_addr, RW_X_LATCH, &mtr) + - FSEG_INODE_PAGE_NODE; + + seg_inode = fsp_seg_inode_page_get_nth_inode( + seg_inode_page, n, zip_size, &mtr); + ut_a(!ut_dulint_is_zero( + mach_read_from_8(seg_inode + FSEG_ID))); + fseg_validate_low(seg_inode, &mtr); + + descr_count += flst_get_len(seg_inode + FSEG_FREE, + &mtr); + descr_count += flst_get_len(seg_inode + FSEG_FULL, + &mtr); + descr_count += flst_get_len(seg_inode + FSEG_NOT_FULL, + &mtr); + + n_used2 += fseg_get_n_frag_pages(seg_inode, &mtr); + + next_node_addr = flst_get_next_addr( + seg_inode_page + FSEG_INODE_PAGE_NODE, &mtr); + mtr_commit(&mtr); + } while (++n < FSP_SEG_INODES_PER_PAGE(zip_size)); + + node_addr = next_node_addr; + } + + mtr_start(&mtr); + mtr_x_lock(latch, &mtr); + + header = fsp_get_space_header(space, zip_size, &mtr); + + node_addr = flst_get_first(header + FSP_SEG_INODES_FREE, &mtr); + + seg_inode_len_free = flst_get_len(header + FSP_SEG_INODES_FREE, &mtr); + + mtr_commit(&mtr); + + while (!fil_addr_is_null(node_addr)) { + + n = 0; + + do { + mtr_start(&mtr); + mtr_x_lock(latch, &mtr); + + seg_inode_page = fut_get_ptr( + space, zip_size, node_addr, RW_X_LATCH, &mtr) + - FSEG_INODE_PAGE_NODE; + + seg_inode = fsp_seg_inode_page_get_nth_inode( + seg_inode_page, n, zip_size, &mtr); + if (!ut_dulint_is_zero( + mach_read_from_8(seg_inode + FSEG_ID))) { + fseg_validate_low(seg_inode, &mtr); + + descr_count += flst_get_len( + seg_inode + FSEG_FREE, &mtr); + descr_count += flst_get_len( + seg_inode + FSEG_FULL, &mtr); + descr_count += flst_get_len( + seg_inode + FSEG_NOT_FULL, &mtr); + n_used2 += fseg_get_n_frag_pages( + seg_inode, &mtr); + } + + next_node_addr = flst_get_next_addr( + seg_inode_page + FSEG_INODE_PAGE_NODE, &mtr); + mtr_commit(&mtr); + } while (++n < FSP_SEG_INODES_PER_PAGE(zip_size)); + + node_addr = next_node_addr; + } + + ut_a(descr_count * FSP_EXTENT_SIZE == free_limit); + if (!zip_size) { + ut_a(n_used + n_full_frag_pages + == n_used2 + 2 * ((free_limit + (UNIV_PAGE_SIZE - 1)) + / UNIV_PAGE_SIZE) + + seg_inode_len_full + seg_inode_len_free); + } else { + ut_a(n_used + n_full_frag_pages + == n_used2 + 2 * ((free_limit + (zip_size - 1)) + / zip_size) + + seg_inode_len_full + seg_inode_len_free); + } + ut_a(frag_n_used == n_used); + + mtr_commit(&mtr2); + + return(TRUE); +} + +/*********************************************************************** +Prints info of a file space. */ +UNIV_INTERN +void +fsp_print( +/*======*/ + ulint space) /* in: space id */ +{ + fsp_header_t* header; + fseg_inode_t* seg_inode; + page_t* seg_inode_page; + rw_lock_t* latch; + ulint flags; + ulint zip_size; + ulint size; + ulint free_limit; + ulint frag_n_used; + fil_addr_t node_addr; + fil_addr_t next_node_addr; + ulint n_free; + ulint n_free_frag; + ulint n_full_frag; + ulint seg_id_low; + ulint seg_id_high; + ulint n; + ulint n_segs = 0; + dulint d_var; + mtr_t mtr; + mtr_t mtr2; + + latch = fil_space_get_latch(space, &flags); + zip_size = dict_table_flags_to_zip_size(flags); + + /* Start first a mini-transaction mtr2 to lock out all other threads + from the fsp system */ + + mtr_start(&mtr2); + + mtr_x_lock(latch, &mtr2); + + mtr_start(&mtr); + + mtr_x_lock(latch, &mtr); + + header = fsp_get_space_header(space, zip_size, &mtr); + + size = mtr_read_ulint(header + FSP_SIZE, MLOG_4BYTES, &mtr); + + free_limit = mtr_read_ulint(header + FSP_FREE_LIMIT, MLOG_4BYTES, + &mtr); + frag_n_used = mtr_read_ulint(header + FSP_FRAG_N_USED, MLOG_4BYTES, + &mtr); + n_free = flst_get_len(header + FSP_FREE, &mtr); + n_free_frag = flst_get_len(header + FSP_FREE_FRAG, &mtr); + n_full_frag = flst_get_len(header + FSP_FULL_FRAG, &mtr); + + d_var = mtr_read_dulint(header + FSP_SEG_ID, &mtr); + + seg_id_low = ut_dulint_get_low(d_var); + seg_id_high = ut_dulint_get_high(d_var); + + fprintf(stderr, + "FILE SPACE INFO: id %lu\n" + "size %lu, free limit %lu, free extents %lu\n" + "not full frag extents %lu: used pages %lu," + " full frag extents %lu\n" + "first seg id not used %lu %lu\n", + (ulong) space, + (ulong) size, (ulong) free_limit, (ulong) n_free, + (ulong) n_free_frag, (ulong) frag_n_used, (ulong) n_full_frag, + (ulong) seg_id_high, (ulong) seg_id_low); + + mtr_commit(&mtr); + + /* Print segments */ + + mtr_start(&mtr); + mtr_x_lock(latch, &mtr); + + header = fsp_get_space_header(space, zip_size, &mtr); + + node_addr = flst_get_first(header + FSP_SEG_INODES_FULL, &mtr); + + mtr_commit(&mtr); + + while (!fil_addr_is_null(node_addr)) { + + n = 0; + + do { + + mtr_start(&mtr); + mtr_x_lock(latch, &mtr); + + seg_inode_page = fut_get_ptr( + space, zip_size, node_addr, RW_X_LATCH, &mtr) + - FSEG_INODE_PAGE_NODE; + + seg_inode = fsp_seg_inode_page_get_nth_inode( + seg_inode_page, n, zip_size, &mtr); + ut_a(!ut_dulint_is_zero( + mach_read_from_8(seg_inode + FSEG_ID))); + fseg_print_low(seg_inode, &mtr); + + n_segs++; + + next_node_addr = flst_get_next_addr( + seg_inode_page + FSEG_INODE_PAGE_NODE, &mtr); + mtr_commit(&mtr); + } while (++n < FSP_SEG_INODES_PER_PAGE(zip_size)); + + node_addr = next_node_addr; + } + + mtr_start(&mtr); + mtr_x_lock(latch, &mtr); + + header = fsp_get_space_header(space, zip_size, &mtr); + + node_addr = flst_get_first(header + FSP_SEG_INODES_FREE, &mtr); + + mtr_commit(&mtr); + + while (!fil_addr_is_null(node_addr)) { + + n = 0; + + do { + + mtr_start(&mtr); + mtr_x_lock(latch, &mtr); + + seg_inode_page = fut_get_ptr( + space, zip_size, node_addr, RW_X_LATCH, &mtr) + - FSEG_INODE_PAGE_NODE; + + seg_inode = fsp_seg_inode_page_get_nth_inode( + seg_inode_page, n, zip_size, &mtr); + if (!ut_dulint_is_zero( + mach_read_from_8(seg_inode + FSEG_ID))) { + + fseg_print_low(seg_inode, &mtr); + n_segs++; + } + + next_node_addr = flst_get_next_addr( + seg_inode_page + FSEG_INODE_PAGE_NODE, &mtr); + mtr_commit(&mtr); + } while (++n < FSP_SEG_INODES_PER_PAGE(zip_size)); + + node_addr = next_node_addr; + } + + mtr_commit(&mtr2); + + fprintf(stderr, "NUMBER of file segments: %lu\n", (ulong) n_segs); +} diff --git a/storage/xtradb/fut/fut0fut.c b/storage/xtradb/fut/fut0fut.c new file mode 100644 index 00000000000..41ee0cb6715 --- /dev/null +++ b/storage/xtradb/fut/fut0fut.c @@ -0,0 +1,30 @@ +/***************************************************************************** + +Copyright (c) 1995, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/********************************************************************** +File-based utilities + +Created 12/13/1995 Heikki Tuuri +***********************************************************************/ + +#include "fut0fut.h" + +#ifdef UNIV_NONINL +#include "fut0fut.ic" +#endif + diff --git a/storage/xtradb/fut/fut0lst.c b/storage/xtradb/fut/fut0lst.c new file mode 100644 index 00000000000..bea27ab70d1 --- /dev/null +++ b/storage/xtradb/fut/fut0lst.c @@ -0,0 +1,529 @@ +/***************************************************************************** + +Copyright (c) 1995, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/********************************************************************** +File-based list utilities + +Created 11/28/1995 Heikki Tuuri +***********************************************************************/ + +#include "fut0lst.h" + +#ifdef UNIV_NONINL +#include "fut0lst.ic" +#endif + +#include "buf0buf.h" +#include "page0page.h" + +/************************************************************************ +Adds a node to an empty list. */ +static +void +flst_add_to_empty( +/*==============*/ + flst_base_node_t* base, /* in: pointer to base node of + empty list */ + flst_node_t* node, /* in: node to add */ + mtr_t* mtr) /* in: mini-transaction handle */ +{ + ulint space; + fil_addr_t node_addr; + ulint len; + + ut_ad(mtr && base && node); + ut_ad(base != node); + ut_ad(mtr_memo_contains_page(mtr, base, MTR_MEMO_PAGE_X_FIX)); + ut_ad(mtr_memo_contains_page(mtr, node, MTR_MEMO_PAGE_X_FIX)); + len = flst_get_len(base, mtr); + ut_a(len == 0); + + buf_ptr_get_fsp_addr(node, &space, &node_addr); + + /* Update first and last fields of base node */ + flst_write_addr(base + FLST_FIRST, node_addr, mtr); + flst_write_addr(base + FLST_LAST, node_addr, mtr); + + /* Set prev and next fields of node to add */ + flst_write_addr(node + FLST_PREV, fil_addr_null, mtr); + flst_write_addr(node + FLST_NEXT, fil_addr_null, mtr); + + /* Update len of base node */ + mlog_write_ulint(base + FLST_LEN, len + 1, MLOG_4BYTES, mtr); +} + +/************************************************************************ +Adds a node as the last node in a list. */ +UNIV_INTERN +void +flst_add_last( +/*==========*/ + flst_base_node_t* base, /* in: pointer to base node of list */ + flst_node_t* node, /* in: node to add */ + mtr_t* mtr) /* in: mini-transaction handle */ +{ + ulint space; + fil_addr_t node_addr; + ulint len; + fil_addr_t last_addr; + flst_node_t* last_node; + + ut_ad(mtr && base && node); + ut_ad(base != node); + ut_ad(mtr_memo_contains_page(mtr, base, MTR_MEMO_PAGE_X_FIX)); + ut_ad(mtr_memo_contains_page(mtr, node, MTR_MEMO_PAGE_X_FIX)); + len = flst_get_len(base, mtr); + last_addr = flst_get_last(base, mtr); + + buf_ptr_get_fsp_addr(node, &space, &node_addr); + + /* If the list is not empty, call flst_insert_after */ + if (len != 0) { + if (last_addr.page == node_addr.page) { + last_node = page_align(node) + last_addr.boffset; + } else { + ulint zip_size = fil_space_get_zip_size(space); + + last_node = fut_get_ptr(space, zip_size, last_addr, + RW_X_LATCH, mtr); + } + + flst_insert_after(base, last_node, node, mtr); + } else { + /* else call flst_add_to_empty */ + flst_add_to_empty(base, node, mtr); + } +} + +/************************************************************************ +Adds a node as the first node in a list. */ +UNIV_INTERN +void +flst_add_first( +/*===========*/ + flst_base_node_t* base, /* in: pointer to base node of list */ + flst_node_t* node, /* in: node to add */ + mtr_t* mtr) /* in: mini-transaction handle */ +{ + ulint space; + fil_addr_t node_addr; + ulint len; + fil_addr_t first_addr; + flst_node_t* first_node; + + ut_ad(mtr && base && node); + ut_ad(base != node); + ut_ad(mtr_memo_contains_page(mtr, base, MTR_MEMO_PAGE_X_FIX)); + ut_ad(mtr_memo_contains_page(mtr, node, MTR_MEMO_PAGE_X_FIX)); + len = flst_get_len(base, mtr); + first_addr = flst_get_first(base, mtr); + + buf_ptr_get_fsp_addr(node, &space, &node_addr); + + /* If the list is not empty, call flst_insert_before */ + if (len != 0) { + if (first_addr.page == node_addr.page) { + first_node = page_align(node) + first_addr.boffset; + } else { + ulint zip_size = fil_space_get_zip_size(space); + + first_node = fut_get_ptr(space, zip_size, first_addr, + RW_X_LATCH, mtr); + } + + flst_insert_before(base, node, first_node, mtr); + } else { + /* else call flst_add_to_empty */ + flst_add_to_empty(base, node, mtr); + } +} + +/************************************************************************ +Inserts a node after another in a list. */ +UNIV_INTERN +void +flst_insert_after( +/*==============*/ + flst_base_node_t* base, /* in: pointer to base node of list */ + flst_node_t* node1, /* in: node to insert after */ + flst_node_t* node2, /* in: node to add */ + mtr_t* mtr) /* in: mini-transaction handle */ +{ + ulint space; + fil_addr_t node1_addr; + fil_addr_t node2_addr; + flst_node_t* node3; + fil_addr_t node3_addr; + ulint len; + + ut_ad(mtr && node1 && node2 && base); + ut_ad(base != node1); + ut_ad(base != node2); + ut_ad(node2 != node1); + ut_ad(mtr_memo_contains_page(mtr, base, MTR_MEMO_PAGE_X_FIX)); + ut_ad(mtr_memo_contains_page(mtr, node1, MTR_MEMO_PAGE_X_FIX)); + ut_ad(mtr_memo_contains_page(mtr, node2, MTR_MEMO_PAGE_X_FIX)); + + buf_ptr_get_fsp_addr(node1, &space, &node1_addr); + buf_ptr_get_fsp_addr(node2, &space, &node2_addr); + + node3_addr = flst_get_next_addr(node1, mtr); + + /* Set prev and next fields of node2 */ + flst_write_addr(node2 + FLST_PREV, node1_addr, mtr); + flst_write_addr(node2 + FLST_NEXT, node3_addr, mtr); + + if (!fil_addr_is_null(node3_addr)) { + /* Update prev field of node3 */ + ulint zip_size = fil_space_get_zip_size(space); + + node3 = fut_get_ptr(space, zip_size, + node3_addr, RW_X_LATCH, mtr); + flst_write_addr(node3 + FLST_PREV, node2_addr, mtr); + } else { + /* node1 was last in list: update last field in base */ + flst_write_addr(base + FLST_LAST, node2_addr, mtr); + } + + /* Set next field of node1 */ + flst_write_addr(node1 + FLST_NEXT, node2_addr, mtr); + + /* Update len of base node */ + len = flst_get_len(base, mtr); + mlog_write_ulint(base + FLST_LEN, len + 1, MLOG_4BYTES, mtr); +} + +/************************************************************************ +Inserts a node before another in a list. */ +UNIV_INTERN +void +flst_insert_before( +/*===============*/ + flst_base_node_t* base, /* in: pointer to base node of list */ + flst_node_t* node2, /* in: node to insert */ + flst_node_t* node3, /* in: node to insert before */ + mtr_t* mtr) /* in: mini-transaction handle */ +{ + ulint space; + flst_node_t* node1; + fil_addr_t node1_addr; + fil_addr_t node2_addr; + fil_addr_t node3_addr; + ulint len; + + ut_ad(mtr && node2 && node3 && base); + ut_ad(base != node2); + ut_ad(base != node3); + ut_ad(node2 != node3); + ut_ad(mtr_memo_contains_page(mtr, base, MTR_MEMO_PAGE_X_FIX)); + ut_ad(mtr_memo_contains_page(mtr, node2, MTR_MEMO_PAGE_X_FIX)); + ut_ad(mtr_memo_contains_page(mtr, node3, MTR_MEMO_PAGE_X_FIX)); + + buf_ptr_get_fsp_addr(node2, &space, &node2_addr); + buf_ptr_get_fsp_addr(node3, &space, &node3_addr); + + node1_addr = flst_get_prev_addr(node3, mtr); + + /* Set prev and next fields of node2 */ + flst_write_addr(node2 + FLST_PREV, node1_addr, mtr); + flst_write_addr(node2 + FLST_NEXT, node3_addr, mtr); + + if (!fil_addr_is_null(node1_addr)) { + ulint zip_size = fil_space_get_zip_size(space); + /* Update next field of node1 */ + node1 = fut_get_ptr(space, zip_size, node1_addr, + RW_X_LATCH, mtr); + flst_write_addr(node1 + FLST_NEXT, node2_addr, mtr); + } else { + /* node3 was first in list: update first field in base */ + flst_write_addr(base + FLST_FIRST, node2_addr, mtr); + } + + /* Set prev field of node3 */ + flst_write_addr(node3 + FLST_PREV, node2_addr, mtr); + + /* Update len of base node */ + len = flst_get_len(base, mtr); + mlog_write_ulint(base + FLST_LEN, len + 1, MLOG_4BYTES, mtr); +} + +/************************************************************************ +Removes a node. */ +UNIV_INTERN +void +flst_remove( +/*========*/ + flst_base_node_t* base, /* in: pointer to base node of list */ + flst_node_t* node2, /* in: node to remove */ + mtr_t* mtr) /* in: mini-transaction handle */ +{ + ulint space; + ulint zip_size; + flst_node_t* node1; + fil_addr_t node1_addr; + fil_addr_t node2_addr; + flst_node_t* node3; + fil_addr_t node3_addr; + ulint len; + + ut_ad(mtr && node2 && base); + ut_ad(mtr_memo_contains_page(mtr, base, MTR_MEMO_PAGE_X_FIX)); + ut_ad(mtr_memo_contains_page(mtr, node2, MTR_MEMO_PAGE_X_FIX)); + + buf_ptr_get_fsp_addr(node2, &space, &node2_addr); + zip_size = fil_space_get_zip_size(space); + + node1_addr = flst_get_prev_addr(node2, mtr); + node3_addr = flst_get_next_addr(node2, mtr); + + if (!fil_addr_is_null(node1_addr)) { + + /* Update next field of node1 */ + + if (node1_addr.page == node2_addr.page) { + + node1 = page_align(node2) + node1_addr.boffset; + } else { + node1 = fut_get_ptr(space, zip_size, + node1_addr, RW_X_LATCH, mtr); + } + + ut_ad(node1 != node2); + + flst_write_addr(node1 + FLST_NEXT, node3_addr, mtr); + } else { + /* node2 was first in list: update first field in base */ + flst_write_addr(base + FLST_FIRST, node3_addr, mtr); + } + + if (!fil_addr_is_null(node3_addr)) { + /* Update prev field of node3 */ + + if (node3_addr.page == node2_addr.page) { + + node3 = page_align(node2) + node3_addr.boffset; + } else { + node3 = fut_get_ptr(space, zip_size, + node3_addr, RW_X_LATCH, mtr); + } + + ut_ad(node2 != node3); + + flst_write_addr(node3 + FLST_PREV, node1_addr, mtr); + } else { + /* node2 was last in list: update last field in base */ + flst_write_addr(base + FLST_LAST, node1_addr, mtr); + } + + /* Update len of base node */ + len = flst_get_len(base, mtr); + ut_ad(len > 0); + + mlog_write_ulint(base + FLST_LEN, len - 1, MLOG_4BYTES, mtr); +} + +/************************************************************************ +Cuts off the tail of the list, including the node given. The number of +nodes which will be removed must be provided by the caller, as this function +does not measure the length of the tail. */ +UNIV_INTERN +void +flst_cut_end( +/*=========*/ + flst_base_node_t* base, /* in: pointer to base node of list */ + flst_node_t* node2, /* in: first node to remove */ + ulint n_nodes,/* in: number of nodes to remove, + must be >= 1 */ + mtr_t* mtr) /* in: mini-transaction handle */ +{ + ulint space; + flst_node_t* node1; + fil_addr_t node1_addr; + fil_addr_t node2_addr; + ulint len; + + ut_ad(mtr && node2 && base); + ut_ad(mtr_memo_contains_page(mtr, base, MTR_MEMO_PAGE_X_FIX)); + ut_ad(mtr_memo_contains_page(mtr, node2, MTR_MEMO_PAGE_X_FIX)); + ut_ad(n_nodes > 0); + + buf_ptr_get_fsp_addr(node2, &space, &node2_addr); + + node1_addr = flst_get_prev_addr(node2, mtr); + + if (!fil_addr_is_null(node1_addr)) { + + /* Update next field of node1 */ + + if (node1_addr.page == node2_addr.page) { + + node1 = page_align(node2) + node1_addr.boffset; + } else { + node1 = fut_get_ptr(space, + fil_space_get_zip_size(space), + node1_addr, RW_X_LATCH, mtr); + } + + flst_write_addr(node1 + FLST_NEXT, fil_addr_null, mtr); + } else { + /* node2 was first in list: update the field in base */ + flst_write_addr(base + FLST_FIRST, fil_addr_null, mtr); + } + + flst_write_addr(base + FLST_LAST, node1_addr, mtr); + + /* Update len of base node */ + len = flst_get_len(base, mtr); + ut_ad(len >= n_nodes); + + mlog_write_ulint(base + FLST_LEN, len - n_nodes, MLOG_4BYTES, mtr); +} + +/************************************************************************ +Cuts off the tail of the list, not including the given node. The number of +nodes which will be removed must be provided by the caller, as this function +does not measure the length of the tail. */ +UNIV_INTERN +void +flst_truncate_end( +/*==============*/ + flst_base_node_t* base, /* in: pointer to base node of list */ + flst_node_t* node2, /* in: first node not to remove */ + ulint n_nodes,/* in: number of nodes to remove */ + mtr_t* mtr) /* in: mini-transaction handle */ +{ + fil_addr_t node2_addr; + ulint len; + ulint space; + + ut_ad(mtr && node2 && base); + ut_ad(mtr_memo_contains_page(mtr, base, MTR_MEMO_PAGE_X_FIX)); + ut_ad(mtr_memo_contains_page(mtr, node2, MTR_MEMO_PAGE_X_FIX)); + if (n_nodes == 0) { + + ut_ad(fil_addr_is_null(flst_get_next_addr(node2, mtr))); + + return; + } + + buf_ptr_get_fsp_addr(node2, &space, &node2_addr); + + /* Update next field of node2 */ + flst_write_addr(node2 + FLST_NEXT, fil_addr_null, mtr); + + flst_write_addr(base + FLST_LAST, node2_addr, mtr); + + /* Update len of base node */ + len = flst_get_len(base, mtr); + ut_ad(len >= n_nodes); + + mlog_write_ulint(base + FLST_LEN, len - n_nodes, MLOG_4BYTES, mtr); +} + +/************************************************************************ +Validates a file-based list. */ +UNIV_INTERN +ibool +flst_validate( +/*==========*/ + /* out: TRUE if ok */ + const flst_base_node_t* base, /* in: pointer to base node of list */ + mtr_t* mtr1) /* in: mtr */ +{ + ulint space; + ulint zip_size; + const flst_node_t* node; + fil_addr_t node_addr; + fil_addr_t base_addr; + ulint len; + ulint i; + mtr_t mtr2; + + ut_ad(base); + ut_ad(mtr_memo_contains_page(mtr1, base, MTR_MEMO_PAGE_X_FIX)); + + /* We use two mini-transaction handles: the first is used to + lock the base node, and prevent other threads from modifying the + list. The second is used to traverse the list. We cannot run the + second mtr without committing it at times, because if the list + is long, then the x-locked pages could fill the buffer resulting + in a deadlock. */ + + /* Find out the space id */ + buf_ptr_get_fsp_addr(base, &space, &base_addr); + zip_size = fil_space_get_zip_size(space); + + len = flst_get_len(base, mtr1); + node_addr = flst_get_first(base, mtr1); + + for (i = 0; i < len; i++) { + mtr_start(&mtr2); + + node = fut_get_ptr(space, zip_size, + node_addr, RW_X_LATCH, &mtr2); + node_addr = flst_get_next_addr(node, &mtr2); + + mtr_commit(&mtr2); /* Commit mtr2 each round to prevent buffer + becoming full */ + } + + ut_a(fil_addr_is_null(node_addr)); + + node_addr = flst_get_last(base, mtr1); + + for (i = 0; i < len; i++) { + mtr_start(&mtr2); + + node = fut_get_ptr(space, zip_size, + node_addr, RW_X_LATCH, &mtr2); + node_addr = flst_get_prev_addr(node, &mtr2); + + mtr_commit(&mtr2); /* Commit mtr2 each round to prevent buffer + becoming full */ + } + + ut_a(fil_addr_is_null(node_addr)); + + return(TRUE); +} + +/************************************************************************ +Prints info of a file-based list. */ +UNIV_INTERN +void +flst_print( +/*=======*/ + const flst_base_node_t* base, /* in: pointer to base node of list */ + mtr_t* mtr) /* in: mtr */ +{ + const buf_frame_t* frame; + ulint len; + + ut_ad(base && mtr); + ut_ad(mtr_memo_contains_page(mtr, base, MTR_MEMO_PAGE_X_FIX)); + frame = page_align((byte*) base); + + len = flst_get_len(base, mtr); + + fprintf(stderr, + "FILE-BASED LIST:\n" + "Base node in space %lu page %lu byte offset %lu; len %lu\n", + (ulong) page_get_space_id(frame), + (ulong) page_get_page_no(frame), + (ulong) page_offset(base), (ulong) len); +} diff --git a/storage/xtradb/ha/ha0ha.c b/storage/xtradb/ha/ha0ha.c new file mode 100644 index 00000000000..1ecba3df663 --- /dev/null +++ b/storage/xtradb/ha/ha0ha.c @@ -0,0 +1,442 @@ +/***************************************************************************** + +Copyright (c) 1994, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/************************************************************************ +The hash table with external chains + +Created 8/22/1994 Heikki Tuuri +*************************************************************************/ + +#include "ha0ha.h" +#ifdef UNIV_NONINL +#include "ha0ha.ic" +#endif + +#ifdef UNIV_DEBUG +# include "buf0buf.h" +#endif /* UNIV_DEBUG */ +#ifdef UNIV_SYNC_DEBUG +# include "btr0sea.h" +#endif /* UNIV_SYNC_DEBUG */ +#include "page0page.h" + +/***************************************************************** +Creates a hash table with >= n array cells. The actual number of cells is +chosen to be a prime number slightly bigger than n. */ +UNIV_INTERN +hash_table_t* +ha_create_func( +/*===========*/ + /* out, own: created table */ + ulint n, /* in: number of array cells */ +#ifdef UNIV_SYNC_DEBUG + ulint mutex_level, /* in: level of the mutexes in the latching + order: this is used in the debug version */ +#endif /* UNIV_SYNC_DEBUG */ + ulint n_mutexes) /* in: number of mutexes to protect the + hash table: must be a power of 2, or 0 */ +{ + hash_table_t* table; + ulint i; + + table = hash_create(n); + +#if defined UNIV_AHI_DEBUG || defined UNIV_DEBUG + table->adaptive = TRUE; +#endif /* UNIV_AHI_DEBUG || UNIV_DEBUG */ + /* Creating MEM_HEAP_BTR_SEARCH type heaps can potentially fail, + but in practise it never should in this case, hence the asserts. */ + + if (n_mutexes == 0) { + table->heap = mem_heap_create_in_btr_search( + ut_min(4096, MEM_MAX_ALLOC_IN_BUF)); + ut_a(table->heap); + + return(table); + } + + hash_create_mutexes(table, n_mutexes, mutex_level); + + table->heaps = mem_alloc(n_mutexes * sizeof(void*)); + + for (i = 0; i < n_mutexes; i++) { + table->heaps[i] = mem_heap_create_in_btr_search(4096); + ut_a(table->heaps[i]); + } + + return(table); +} + +/***************************************************************** +Empties a hash table and frees the memory heaps. */ +UNIV_INTERN +void +ha_clear( +/*=====*/ + hash_table_t* table) /* in, own: hash table */ +{ + ulint i; + ulint n; + +#ifdef UNIV_SYNC_DEBUG + ut_ad(rw_lock_own(&btr_search_latch, RW_LOCK_EXCLUSIVE)); +#endif /* UNIV_SYNC_DEBUG */ + + /* Free the memory heaps. */ + n = table->n_mutexes; + + for (i = 0; i < n; i++) { + mem_heap_free(table->heaps[i]); + } + + /* Clear the hash table. */ + n = hash_get_n_cells(table); + + for (i = 0; i < n; i++) { + hash_get_nth_cell(table, i)->node = NULL; + } +} + +/***************************************************************** +Inserts an entry into a hash table. If an entry with the same fold number +is found, its node is updated to point to the new data, and no new node +is inserted. */ +UNIV_INTERN +ibool +ha_insert_for_fold_func( +/*====================*/ + /* out: TRUE if succeed, FALSE if no more + memory could be allocated */ + hash_table_t* table, /* in: hash table */ + ulint fold, /* in: folded value of data; if a node with + the same fold value already exists, it is + updated to point to the same data, and no new + node is created! */ +#if defined UNIV_AHI_DEBUG || defined UNIV_DEBUG + buf_block_t* block, /* in: buffer block containing the data */ +#endif /* UNIV_AHI_DEBUG || UNIV_DEBUG */ + void* data) /* in: data, must not be NULL */ +{ + hash_cell_t* cell; + ha_node_t* node; + ha_node_t* prev_node; + ulint hash; + + ut_ad(table && data); +#if defined UNIV_AHI_DEBUG || defined UNIV_DEBUG + ut_a(block->frame == page_align(data)); +#endif /* UNIV_AHI_DEBUG || UNIV_DEBUG */ + ut_ad(!table->mutexes || mutex_own(hash_get_mutex(table, fold))); + + hash = hash_calc_hash(fold, table); + + cell = hash_get_nth_cell(table, hash); + + prev_node = cell->node; + + while (prev_node != NULL) { + if (prev_node->fold == fold) { +#if defined UNIV_AHI_DEBUG || defined UNIV_DEBUG + if (table->adaptive) { + buf_block_t* prev_block = prev_node->block; + ut_a(prev_block->frame + == page_align(prev_node->data)); + ut_a(prev_block->n_pointers > 0); + prev_block->n_pointers--; + block->n_pointers++; + } + + prev_node->block = block; +#endif /* UNIV_AHI_DEBUG || UNIV_DEBUG */ + prev_node->data = data; + + return(TRUE); + } + + prev_node = prev_node->next; + } + + /* We have to allocate a new chain node */ + + node = mem_heap_alloc(hash_get_heap(table, fold), sizeof(ha_node_t)); + + if (node == NULL) { + /* It was a btr search type memory heap and at the moment + no more memory could be allocated: return */ + + ut_ad(hash_get_heap(table, fold)->type & MEM_HEAP_BTR_SEARCH); + + return(FALSE); + } + + ha_node_set_data(node, block, data); + +#if defined UNIV_AHI_DEBUG || defined UNIV_DEBUG + if (table->adaptive) { + block->n_pointers++; + } +#endif /* UNIV_AHI_DEBUG || UNIV_DEBUG */ + node->fold = fold; + + node->next = NULL; + + prev_node = cell->node; + + if (prev_node == NULL) { + + cell->node = node; + + return(TRUE); + } + + while (prev_node->next != NULL) { + + prev_node = prev_node->next; + } + + prev_node->next = node; + + return(TRUE); +} + +/*************************************************************** +Deletes a hash node. */ +UNIV_INTERN +void +ha_delete_hash_node( +/*================*/ + hash_table_t* table, /* in: hash table */ + ha_node_t* del_node) /* in: node to be deleted */ +{ +#if defined UNIV_AHI_DEBUG || defined UNIV_DEBUG + if (table->adaptive) { + ut_a(del_node->block->frame = page_align(del_node->data)); + ut_a(del_node->block->n_pointers > 0); + del_node->block->n_pointers--; + } +#endif /* UNIV_AHI_DEBUG || UNIV_DEBUG */ + HASH_DELETE_AND_COMPACT(ha_node_t, next, table, del_node); +} + +/***************************************************************** +Deletes an entry from a hash table. */ +UNIV_INTERN +void +ha_delete( +/*======*/ + hash_table_t* table, /* in: hash table */ + ulint fold, /* in: folded value of data */ + void* data) /* in: data, must not be NULL and must exist + in the hash table */ +{ + ha_node_t* node; + + ut_ad(!table->mutexes || mutex_own(hash_get_mutex(table, fold))); + + node = ha_search_with_data(table, fold, data); + + ut_a(node); + + ha_delete_hash_node(table, node); +} + +/************************************************************* +Looks for an element when we know the pointer to the data, and updates +the pointer to data, if found. */ +UNIV_INTERN +void +ha_search_and_update_if_found_func( +/*===============================*/ + hash_table_t* table, /* in: hash table */ + ulint fold, /* in: folded value of the searched data */ + void* data, /* in: pointer to the data */ +#if defined UNIV_AHI_DEBUG || defined UNIV_DEBUG + buf_block_t* new_block,/* in: block containing new_data */ +#endif /* UNIV_AHI_DEBUG || UNIV_DEBUG */ + void* new_data)/* in: new pointer to the data */ +{ + ha_node_t* node; + + ut_ad(!table->mutexes || mutex_own(hash_get_mutex(table, fold))); +#if defined UNIV_AHI_DEBUG || defined UNIV_DEBUG + ut_a(new_block->frame == page_align(new_data)); +#endif /* UNIV_AHI_DEBUG || UNIV_DEBUG */ + + node = ha_search_with_data(table, fold, data); + + if (node) { +#if defined UNIV_AHI_DEBUG || defined UNIV_DEBUG + if (table->adaptive) { + ut_a(node->block->n_pointers > 0); + node->block->n_pointers--; + new_block->n_pointers++; + } + + node->block = new_block; +#endif /* UNIV_AHI_DEBUG || UNIV_DEBUG */ + node->data = new_data; + } +} + +/********************************************************************* +Removes from the chain determined by fold all nodes whose data pointer +points to the page given. */ +UNIV_INTERN +void +ha_remove_all_nodes_to_page( +/*========================*/ + hash_table_t* table, /* in: hash table */ + ulint fold, /* in: fold value */ + const page_t* page) /* in: buffer page */ +{ + ha_node_t* node; + + ut_ad(!table->mutexes || mutex_own(hash_get_mutex(table, fold))); + + node = ha_chain_get_first(table, fold); + + while (node) { + if (page_align(ha_node_get_data(node)) == page) { + + /* Remove the hash node */ + + ha_delete_hash_node(table, node); + + /* Start again from the first node in the chain + because the deletion may compact the heap of + nodes and move other nodes! */ + + node = ha_chain_get_first(table, fold); + } else { + node = ha_chain_get_next(node); + } + } +#ifdef UNIV_DEBUG + /* Check that all nodes really got deleted */ + + node = ha_chain_get_first(table, fold); + + while (node) { + ut_a(page_align(ha_node_get_data(node)) != page); + + node = ha_chain_get_next(node); + } +#endif +} + +/***************************************************************** +Validates a given range of the cells in hash table. */ +UNIV_INTERN +ibool +ha_validate( +/*========*/ + /* out: TRUE if ok */ + hash_table_t* table, /* in: hash table */ + ulint start_index, /* in: start index */ + ulint end_index) /* in: end index */ +{ + hash_cell_t* cell; + ha_node_t* node; + ibool ok = TRUE; + ulint i; + + ut_a(start_index <= end_index); + ut_a(start_index < hash_get_n_cells(table)); + ut_a(end_index < hash_get_n_cells(table)); + + for (i = start_index; i <= end_index; i++) { + + cell = hash_get_nth_cell(table, i); + + node = cell->node; + + while (node) { + if (hash_calc_hash(node->fold, table) != i) { + ut_print_timestamp(stderr); + fprintf(stderr, + "InnoDB: Error: hash table node" + " fold value %lu does not\n" + "InnoDB: match the cell number %lu.\n", + (ulong) node->fold, (ulong) i); + + ok = FALSE; + } + + node = node->next; + } + } + + return(ok); +} + +/***************************************************************** +Prints info of a hash table. */ +UNIV_INTERN +void +ha_print_info( +/*==========*/ + FILE* file, /* in: file where to print */ + hash_table_t* table) /* in: hash table */ +{ +#ifdef UNIV_DEBUG +/* Some of the code here is disabled for performance reasons in production +builds, see http://bugs.mysql.com/36941 */ +#define PRINT_USED_CELLS +#endif /* UNIV_DEBUG */ + +#ifdef PRINT_USED_CELLS + hash_cell_t* cell; + ulint cells = 0; + ulint i; +#endif /* PRINT_USED_CELLS */ + ulint n_bufs; + +#ifdef PRINT_USED_CELLS + for (i = 0; i < hash_get_n_cells(table); i++) { + + cell = hash_get_nth_cell(table, i); + + if (cell->node) { + + cells++; + } + } +#endif /* PRINT_USED_CELLS */ + + fprintf(file, "Hash table size %lu", + (ulong) hash_get_n_cells(table)); + +#ifdef PRINT_USED_CELLS + fprintf(file, ", used cells %lu", (ulong) cells); +#endif /* PRINT_USED_CELLS */ + + if (table->heaps == NULL && table->heap != NULL) { + + /* This calculation is intended for the adaptive hash + index: how many buffer frames we have reserved? */ + + n_bufs = UT_LIST_GET_LEN(table->heap->base) - 1; + + if (table->heap->free_block) { + n_bufs++; + } + + fprintf(file, ", node heap has %lu buffer(s)\n", + (ulong) n_bufs); + } +} diff --git a/storage/xtradb/ha/ha0storage.c b/storage/xtradb/ha/ha0storage.c new file mode 100644 index 00000000000..e7e09591193 --- /dev/null +++ b/storage/xtradb/ha/ha0storage.c @@ -0,0 +1,183 @@ +/***************************************************************************** + +Copyright (c) 2007, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/****************************************************** +Hash storage. +Provides a data structure that stores chunks of data in +its own storage, avoiding duplicates. + +Created September 22, 2007 Vasil Dimov +*******************************************************/ + +#include "univ.i" +#include "ha0storage.h" +#include "hash0hash.h" +#include "mem0mem.h" +#include "ut0rnd.h" + +#ifdef UNIV_NONINL +#include "ha0storage.ic" +#endif + +/*********************************************************************** +Retrieves a data from a storage. If it is present, a pointer to the +stored copy of data is returned, otherwise NULL is returned. */ +static +const void* +ha_storage_get( +/*===========*/ + ha_storage_t* storage, /* in: hash storage */ + const void* data, /* in: data to check for */ + ulint data_len) /* in: data length */ +{ + ha_storage_node_t* node; + ulint fold; + + /* avoid repetitive calls to ut_fold_binary() in the HASH_SEARCH + macro */ + fold = ut_fold_binary(data, data_len); + +#define IS_FOUND \ + node->data_len == data_len && memcmp(node->data, data, data_len) == 0 + + HASH_SEARCH( + next, /* node->"next" */ + storage->hash, /* the hash table */ + fold, /* key */ + ha_storage_node_t*, /* type of node->next */ + node, /* auxiliary variable */ + , /* assertion */ + IS_FOUND); /* search criteria */ + + if (node == NULL) { + + return(NULL); + } + /* else */ + + return(node->data); +} + +/*********************************************************************** +Copies data into the storage and returns a pointer to the copy. If the +same data chunk is already present, then pointer to it is returned. +Data chunks are considered to be equal if len1 == len2 and +memcmp(data1, data2, len1) == 0. If "data" is not present (and thus +data_len bytes need to be allocated) and the size of storage is going to +become more than "memlim" then "data" is not added and NULL is returned. +To disable this behavior "memlim" can be set to 0, which stands for +"no limit". */ +UNIV_INTERN +const void* +ha_storage_put_memlim( +/*==================*/ + ha_storage_t* storage, /* in/out: hash storage */ + const void* data, /* in: data to store */ + ulint data_len, /* in: data length */ + ulint memlim) /* in: memory limit to obey */ +{ + void* raw; + ha_storage_node_t* node; + const void* data_copy; + ulint fold; + + /* check if data chunk is already present */ + data_copy = ha_storage_get(storage, data, data_len); + if (data_copy != NULL) { + + return(data_copy); + } + + /* not present */ + + /* check if we are allowed to allocate data_len bytes */ + if (memlim > 0 + && ha_storage_get_size(storage) + data_len > memlim) { + + return(NULL); + } + + /* we put the auxiliary node struct and the data itself in one + continuous block */ + raw = mem_heap_alloc(storage->heap, + sizeof(ha_storage_node_t) + data_len); + + node = (ha_storage_node_t*) raw; + data_copy = (byte*) raw + sizeof(*node); + + memcpy((byte*) raw + sizeof(*node), data, data_len); + + node->data_len = data_len; + node->data = data_copy; + + /* avoid repetitive calls to ut_fold_binary() in the HASH_INSERT + macro */ + fold = ut_fold_binary(data, data_len); + + HASH_INSERT( + ha_storage_node_t, /* type used in the hash chain */ + next, /* node->"next" */ + storage->hash, /* the hash table */ + fold, /* key */ + node); /* add this data to the hash */ + + /* the output should not be changed because it will spoil the + hash table */ + return(data_copy); +} + +#ifdef UNIV_COMPILE_TEST_FUNCS + +void +test_ha_storage() +{ + ha_storage_t* storage; + char buf[1024]; + int i; + const void* stored[256]; + const void* p; + + storage = ha_storage_create(0, 0); + + for (i = 0; i < 256; i++) { + + memset(buf, i, sizeof(buf)); + stored[i] = ha_storage_put(storage, buf, sizeof(buf)); + } + + //ha_storage_empty(&storage); + + for (i = 255; i >= 0; i--) { + + memset(buf, i, sizeof(buf)); + p = ha_storage_put(storage, buf, sizeof(buf)); + + if (p != stored[i]) { + + fprintf(stderr, "ha_storage_put() returned %p " + "instead of %p, i=%d\n", p, stored[i], i); + return; + } + } + + fprintf(stderr, "all ok\n"); + + ha_storage_free(storage); +} + +#endif /* UNIV_COMPILE_TEST_FUNCS */ diff --git a/storage/xtradb/ha/hash0hash.c b/storage/xtradb/ha/hash0hash.c new file mode 100644 index 00000000000..9694a288c99 --- /dev/null +++ b/storage/xtradb/ha/hash0hash.c @@ -0,0 +1,165 @@ +/***************************************************************************** + +Copyright (c) 1997, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/****************************************************** +The simple hash table utility + +Created 5/20/1997 Heikki Tuuri +*******************************************************/ + +#include "hash0hash.h" +#ifdef UNIV_NONINL +#include "hash0hash.ic" +#endif + +#include "mem0mem.h" + +/**************************************************************** +Reserves the mutex for a fold value in a hash table. */ +UNIV_INTERN +void +hash_mutex_enter( +/*=============*/ + hash_table_t* table, /* in: hash table */ + ulint fold) /* in: fold */ +{ + mutex_enter(hash_get_mutex(table, fold)); +} + +/**************************************************************** +Releases the mutex for a fold value in a hash table. */ +UNIV_INTERN +void +hash_mutex_exit( +/*============*/ + hash_table_t* table, /* in: hash table */ + ulint fold) /* in: fold */ +{ + mutex_exit(hash_get_mutex(table, fold)); +} + +/**************************************************************** +Reserves all the mutexes of a hash table, in an ascending order. */ +UNIV_INTERN +void +hash_mutex_enter_all( +/*=================*/ + hash_table_t* table) /* in: hash table */ +{ + ulint i; + + for (i = 0; i < table->n_mutexes; i++) { + + mutex_enter(table->mutexes + i); + } +} + +/**************************************************************** +Releases all the mutexes of a hash table. */ +UNIV_INTERN +void +hash_mutex_exit_all( +/*================*/ + hash_table_t* table) /* in: hash table */ +{ + ulint i; + + for (i = 0; i < table->n_mutexes; i++) { + + mutex_exit(table->mutexes + i); + } +} + +/***************************************************************** +Creates a hash table with >= n array cells. The actual number of cells is +chosen to be a prime number slightly bigger than n. */ +UNIV_INTERN +hash_table_t* +hash_create( +/*========*/ + /* out, own: created table */ + ulint n) /* in: number of array cells */ +{ + hash_cell_t* array; + ulint prime; + hash_table_t* table; + + prime = ut_find_prime(n); + + table = mem_alloc(sizeof(hash_table_t)); + + array = ut_malloc(sizeof(hash_cell_t) * prime); + +#if defined UNIV_AHI_DEBUG || defined UNIV_DEBUG + table->adaptive = FALSE; +#endif /* UNIV_AHI_DEBUG || UNIV_DEBUG */ + table->array = array; + table->n_cells = prime; + table->n_mutexes = 0; + table->mutexes = NULL; + table->heaps = NULL; + table->heap = NULL; + table->magic_n = HASH_TABLE_MAGIC_N; + + /* Initialize the cell array */ + hash_table_clear(table); + + return(table); +} + +/***************************************************************** +Frees a hash table. */ +UNIV_INTERN +void +hash_table_free( +/*============*/ + hash_table_t* table) /* in, own: hash table */ +{ + ut_a(table->mutexes == NULL); + + ut_free(table->array); + mem_free(table); +} + +/***************************************************************** +Creates a mutex array to protect a hash table. */ +UNIV_INTERN +void +hash_create_mutexes_func( +/*=====================*/ + hash_table_t* table, /* in: hash table */ +#ifdef UNIV_SYNC_DEBUG + ulint sync_level, /* in: latching order level of the + mutexes: used in the debug version */ +#endif /* UNIV_SYNC_DEBUG */ + ulint n_mutexes) /* in: number of mutexes, must be a + power of 2 */ +{ + ulint i; + + ut_a(n_mutexes > 0); + ut_a(ut_is_2pow(n_mutexes)); + + table->mutexes = mem_alloc(n_mutexes * sizeof(mutex_t)); + + for (i = 0; i < n_mutexes; i++) { + mutex_create(table->mutexes + i, sync_level); + } + + table->n_mutexes = n_mutexes; +} diff --git a/storage/xtradb/ha_innodb.def b/storage/xtradb/ha_innodb.def new file mode 100644 index 00000000000..e0faa62deb1 --- /dev/null +++ b/storage/xtradb/ha_innodb.def @@ -0,0 +1,4 @@ +EXPORTS + _mysql_plugin_interface_version_ + _mysql_sizeof_struct_st_plugin_ + _mysql_plugin_declarations_ diff --git a/storage/xtradb/handler/ha_innodb.cc b/storage/xtradb/handler/ha_innodb.cc new file mode 100644 index 00000000000..4e6549de3e0 --- /dev/null +++ b/storage/xtradb/handler/ha_innodb.cc @@ -0,0 +1,10496 @@ +/***************************************************************************** + +Copyright (c) 2000, 2009, MySQL AB & Innobase Oy. All Rights Reserved. +Copyright (c) 2008, Google Inc. + +Portions of this file contain modifications contributed and copyrighted by +Google, Inc. Those modifications are gratefully acknowledged and are described +briefly in the InnoDB documentation. The contributions by Google are +incorporated with their permission, and subject to the conditions contained in +the file COPYING.Google. + +This program is free software; you can redistribute it and/or modify it under +the terms of the GNU General Public License as published by the Free Software +Foundation; version 2 of the License. + +This program is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + +You 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 + +*****************************************************************************/ + +/* TODO list for the InnoDB handler in 5.0: + - Remove the flag trx->active_trans and look at trx->conc_state + - fix savepoint functions to use savepoint storage area + - Find out what kind of problems the OS X case-insensitivity causes to + table and database names; should we 'normalize' the names like we do + in Windows? +*/ + +#ifdef USE_PRAGMA_IMPLEMENTATION +#pragma implementation // gcc: Class implementation +#endif + +#define MYSQL_SERVER + +#include <mysql_priv.h> +#ifdef MYSQL_SERVER +#include <log_event.h> +#endif /* MYSQL_SERVER */ + +#include <m_ctype.h> +#include <mysys_err.h> +#include <mysql/plugin.h> + +/* Include necessary InnoDB headers */ +extern "C" { +#include "../storage/xtradb/include/univ.i" +#include "../storage/xtradb/include/btr0sea.h" +#include "../storage/xtradb/include/os0file.h" +#include "../storage/xtradb/include/os0thread.h" +#include "../storage/xtradb/include/srv0start.h" +#include "../storage/xtradb/include/srv0srv.h" +#include "../storage/xtradb/include/trx0roll.h" +#include "../storage/xtradb/include/trx0trx.h" +#include "../storage/xtradb/include/trx0sys.h" +#include "../storage/xtradb/include/mtr0mtr.h" +#include "../storage/xtradb/include/row0ins.h" +#include "../storage/xtradb/include/row0mysql.h" +#include "../storage/xtradb/include/row0sel.h" +#include "../storage/xtradb/include/row0upd.h" +#include "../storage/xtradb/include/log0log.h" +#include "../storage/xtradb/include/lock0lock.h" +#include "../storage/xtradb/include/dict0crea.h" +#include "../storage/xtradb/include/btr0cur.h" +#include "../storage/xtradb/include/btr0btr.h" +#include "../storage/xtradb/include/fsp0fsp.h" +#include "../storage/xtradb/include/sync0sync.h" +#include "../storage/xtradb/include/fil0fil.h" +#include "../storage/xtradb/include/trx0xa.h" +#include "../storage/xtradb/include/row0merge.h" +#include "../storage/xtradb/include/thr0loc.h" +#include "../storage/xtradb/include/dict0boot.h" +#include "../storage/xtradb/include/ha_prototypes.h" +#include "../storage/xtradb/include/ut0mem.h" +#include "../storage/xtradb/include/ibuf0ibuf.h" +} + +#include "ha_innodb.h" +#include "i_s.h" +#include "handler0vars.h" + +#ifdef MYSQL_SERVER +// Defined in trx0sys.c +extern char trx_sys_mysql_master_log_name[]; +extern ib_int64_t trx_sys_mysql_master_log_pos; +extern char trx_sys_mysql_relay_log_name[]; +extern ib_int64_t trx_sys_mysql_relay_log_pos; +#endif /* MYSQL_SERVER */ + +#ifndef MYSQL_SERVER +/* This is needed because of Bug #3596. Let us hope that pthread_mutex_t +is defined the same in both builds: the MySQL server and the InnoDB plugin. */ +extern pthread_mutex_t LOCK_thread_count; + +#if MYSQL_VERSION_ID < 50124 +/* this is defined in mysql_priv.h inside #ifdef MYSQL_SERVER +but we need it here */ +bool check_global_access(THD *thd, ulong want_access); +#endif /* MYSQL_VERSION_ID < 50124 */ +#endif /* MYSQL_SERVER */ + +/** to protect innobase_open_files */ +static pthread_mutex_t innobase_share_mutex; +/** to force correct commit order in binlog */ +static pthread_mutex_t prepare_commit_mutex; +static ulong commit_threads = 0; +static pthread_mutex_t commit_threads_m; +static pthread_cond_t commit_cond; +static pthread_mutex_t commit_cond_m; +static bool innodb_inited = 0; + +#define INSIDE_HA_INNOBASE_CC + +/* In the Windows plugin, the return value of current_thd is +undefined. Map it to NULL. */ +#if defined MYSQL_DYNAMIC_PLUGIN && defined __WIN__ +# undef current_thd +# define current_thd NULL +# define EQ_CURRENT_THD(thd) TRUE +#else /* MYSQL_DYNAMIC_PLUGIN && __WIN__ */ +# define EQ_CURRENT_THD(thd) ((thd) == current_thd) +#endif /* MYSQL_DYNAMIC_PLUGIN && __WIN__ */ + +#ifdef MYSQL_DYNAMIC_PLUGIN +/* These must be weak global variables in the dynamic plugin. */ +struct handlerton* innodb_hton_ptr; +#ifdef __WIN__ +struct st_mysql_plugin* builtin_innobase_plugin_ptr; +#else +int builtin_innobase_plugin; +#endif /* __WIN__ */ +/******************************************************************** +Copy InnoDB system variables from the static InnoDB to the dynamic +plugin. */ +static +bool +innodb_plugin_init(void); +/*====================*/ + /* out: TRUE if the dynamic InnoDB plugin should start */ +#else /* MYSQL_DYNAMIC_PLUGIN */ +/* This must be a global variable in the statically linked InnoDB. */ +struct handlerton* innodb_hton_ptr = NULL; +#endif /* MYSQL_DYNAMIC_PLUGIN */ + +static const long AUTOINC_OLD_STYLE_LOCKING = 0; +static const long AUTOINC_NEW_STYLE_LOCKING = 1; +static const long AUTOINC_NO_LOCKING = 2; + +static long innobase_mirrored_log_groups, innobase_log_files_in_group, + innobase_log_buffer_size, + innobase_additional_mem_pool_size, innobase_file_io_threads, + innobase_force_recovery, innobase_open_files, + innobase_autoinc_lock_mode; + +static unsigned long innobase_read_io_threads, innobase_write_io_threads; +static my_bool innobase_thread_concurrency_timer_based; +static long long innobase_buffer_pool_size, innobase_log_file_size; + +/* The default values for the following char* start-up parameters +are determined in innobase_init below: */ + +static char* innobase_data_home_dir = NULL; +static char* innobase_data_file_path = NULL; +static char* innobase_log_group_home_dir = NULL; +static char* innobase_file_format_name = NULL; +static char* innobase_change_buffering = NULL; + +/* Note: This variable can be set to on/off and any of the supported +file formats in the configuration file, but can only be set to any +of the supported file formats during runtime. */ +static char* innobase_file_format_check = NULL; + +/* The following has a misleading name: starting from 4.0.5, this also +affects Windows: */ +static char* innobase_unix_file_flush_method = NULL; + +/* Below we have boolean-valued start-up parameters, and their default +values */ + +static ulong innobase_fast_shutdown = 1; +#ifdef UNIV_LOG_ARCHIVE +static my_bool innobase_log_archive = FALSE; +static char* innobase_log_arch_dir = NULL; +#endif /* UNIV_LOG_ARCHIVE */ +static my_bool innobase_use_doublewrite = TRUE; +static my_bool innobase_use_checksums = TRUE; +static my_bool innobase_extra_undoslots = FALSE; +static my_bool innobase_locks_unsafe_for_binlog = FALSE; +static my_bool innobase_overwrite_relay_log_info = FALSE; +static my_bool innobase_rollback_on_timeout = FALSE; +static my_bool innobase_create_status_file = FALSE; +static my_bool innobase_stats_on_metadata = TRUE; + +static char* internal_innobase_data_file_path = NULL; + +static char* innodb_version_str = (char*) INNODB_VERSION_STR; + +/* The following counter is used to convey information to InnoDB +about server activity: in selects it is not sensible to call +srv_active_wake_master_thread after each fetch or search, we only do +it every INNOBASE_WAKE_INTERVAL'th step. */ + +#define INNOBASE_WAKE_INTERVAL 32 +static ulong innobase_active_counter = 0; + +static hash_table_t* innobase_open_tables; + +#ifdef __NETWARE__ /* some special cleanup for NetWare */ +bool nw_panic = FALSE; +#endif + +/** Allowed values of innodb_change_buffering */ +static const char* innobase_change_buffering_values[IBUF_USE_COUNT] = { + "none", /* IBUF_USE_NONE */ + "inserts" /* IBUF_USE_INSERT */ +}; + +static INNOBASE_SHARE *get_share(const char *table_name); +static void free_share(INNOBASE_SHARE *share); +static int innobase_close_connection(handlerton *hton, THD* thd); +static int innobase_commit(handlerton *hton, THD* thd, bool all); +static int innobase_rollback(handlerton *hton, THD* thd, bool all); +static int innobase_rollback_to_savepoint(handlerton *hton, THD* thd, + void *savepoint); +static int innobase_savepoint(handlerton *hton, THD* thd, void *savepoint); +static int innobase_release_savepoint(handlerton *hton, THD* thd, + void *savepoint); +static handler *innobase_create_handler(handlerton *hton, + TABLE_SHARE *table, + MEM_ROOT *mem_root); + +/**************************************************************** +Validate the file format name and return its corresponding id. */ +static +uint +innobase_file_format_name_lookup( +/*=============================*/ + /* out: valid file format id */ + const char* format_name); /* in: pointer to file format + name */ +/**************************************************************** +Validate the file format check config parameters, as a side effect it +sets the srv_check_file_format_at_startup variable. */ +static +bool +innobase_file_format_check_on_off( +/*==============================*/ + /* out: true if one of + "on" or "off" */ + const char* format_check); /* in: parameter value */ +/**************************************************************** +Validate the file format check config parameters, as a side effect it +sets the srv_check_file_format_at_startup variable. */ +static +bool +innobase_file_format_check_validate( +/*================================*/ + /* out: true if valid + config value */ + const char* format_check); /* in: parameter value */ +/******************************************************************** +Return alter table flags supported in an InnoDB database. */ +static +uint +innobase_alter_table_flags( +/*=======================*/ + uint flags); + +static const char innobase_hton_name[]= "InnoDB"; + +static MYSQL_THDVAR_BOOL(support_xa, PLUGIN_VAR_OPCMDARG, + "Enable InnoDB support for the XA two-phase commit", + /* check_func */ NULL, /* update_func */ NULL, + /* default */ TRUE); + +static MYSQL_THDVAR_BOOL(table_locks, PLUGIN_VAR_OPCMDARG, + "Enable InnoDB locking in LOCK TABLES", + /* check_func */ NULL, /* update_func */ NULL, + /* default */ TRUE); + +static MYSQL_THDVAR_BOOL(strict_mode, PLUGIN_VAR_OPCMDARG, + "Use strict mode when evaluating create options.", + NULL, NULL, FALSE); + +static MYSQL_THDVAR_ULONG(lock_wait_timeout, PLUGIN_VAR_RQCMDARG, + "Timeout in seconds an InnoDB transaction may wait for a lock before being rolled back. Values above 100000000 disable the timeout.", + NULL, NULL, 50, 1, 1024 * 1024 * 1024, 0); + + +static handler *innobase_create_handler(handlerton *hton, + TABLE_SHARE *table, + MEM_ROOT *mem_root) +{ + return new (mem_root) ha_innobase(hton, table); +} + +/*********************************************************************** +This function is used to prepare X/Open XA distributed transaction */ +static +int +innobase_xa_prepare( +/*================*/ + /* out: 0 or error number */ + handlerton* hton, + THD* thd, /* in: handle to the MySQL thread of the user + whose XA transaction should be prepared */ + bool all); /* in: TRUE - commit transaction + FALSE - the current SQL statement ended */ +/*********************************************************************** +This function is used to recover X/Open XA distributed transactions */ +static +int +innobase_xa_recover( +/*================*/ + /* out: number of prepared transactions + stored in xid_list */ + handlerton* hton, + XID* xid_list, /* in/out: prepared transactions */ + uint len); /* in: number of slots in xid_list */ +/*********************************************************************** +This function is used to commit one X/Open XA distributed transaction +which is in the prepared state */ +static +int +innobase_commit_by_xid( +/*===================*/ + /* out: 0 or error number */ + handlerton* hton, + XID* xid); /* in: X/Open XA transaction identification */ +/*********************************************************************** +This function is used to rollback one X/Open XA distributed transaction +which is in the prepared state */ +static +int +innobase_rollback_by_xid( +/*=====================*/ + /* out: 0 or error number */ + handlerton* hton, + XID *xid); /* in: X/Open XA transaction identification */ +/*********************************************************************** +Create a consistent view for a cursor based on current transaction +which is created if the corresponding MySQL thread still lacks one. +This consistent view is then used inside of MySQL when accessing records +using a cursor. */ +static +void* +innobase_create_cursor_view( +/*========================*/ + /* out: pointer to cursor view or NULL */ + handlerton* hton, /* in: innobase hton */ + THD* thd); /* in: user thread handle */ +/*********************************************************************** +Set the given consistent cursor view to a transaction which is created +if the corresponding MySQL thread still lacks one. If the given +consistent cursor view is NULL global read view of a transaction is +restored to a transaction read view. */ +static +void +innobase_set_cursor_view( +/*=====================*/ + handlerton* hton, + THD* thd, /* in: user thread handle */ + void* curview);/* in: Consistent cursor view to be set */ +/*********************************************************************** +Close the given consistent cursor view of a transaction and restore +global read view to a transaction read view. Transaction is created if the +corresponding MySQL thread still lacks one. */ +static +void +innobase_close_cursor_view( +/*=======================*/ + handlerton* hton, + THD* thd, /* in: user thread handle */ + void* curview);/* in: Consistent read view to be closed */ +/********************************************************************* +Removes all tables in the named database inside InnoDB. */ +static +void +innobase_drop_database( +/*===================*/ + /* out: error number */ + handlerton* hton, /* in: handlerton of Innodb */ + char* path); /* in: database path; inside InnoDB the name + of the last directory in the path is used as + the database name: for example, in 'mysql/data/test' + the database name is 'test' */ +/*********************************************************************** +Closes an InnoDB database. */ +static +int +innobase_end(handlerton *hton, ha_panic_function type); + +/********************************************************************* +Creates an InnoDB transaction struct for the thd if it does not yet have one. +Starts a new InnoDB transaction if a transaction is not yet started. And +assigns a new snapshot for a consistent read if the transaction does not yet +have one. */ +static +int +innobase_start_trx_and_assign_read_view( +/*====================================*/ + /* out: 0 */ + handlerton* hton, /* in: Innodb handlerton */ + THD* thd); /* in: MySQL thread handle of the user for whom + the transaction should be committed */ +/******************************************************************** +Flushes InnoDB logs to disk and makes a checkpoint. Really, a commit flushes +the logs, and the name of this function should be innobase_checkpoint. */ +static +bool +innobase_flush_logs( +/*================*/ + /* out: TRUE if error */ + handlerton* hton); /* in: InnoDB handlerton */ + +/**************************************************************************** +Implements the SHOW INNODB STATUS command. Sends the output of the InnoDB +Monitor to the client. */ +static +bool +innodb_show_status( +/*===============*/ + handlerton* hton, /* in: the innodb handlerton */ + THD* thd, /* in: the MySQL query thread of the caller */ + stat_print_fn *stat_print); +static +bool innobase_show_status(handlerton *hton, THD* thd, + stat_print_fn* stat_print, + enum ha_stat_type stat_type); + +/********************************************************************* +Commits a transaction in an InnoDB database. */ +static +void +innobase_commit_low( +/*================*/ + trx_t* trx); /* in: transaction handle */ + +static SHOW_VAR innodb_status_variables[]= { + {"buffer_pool_pages_data", + (char*) &export_vars.innodb_buffer_pool_pages_data, SHOW_LONG}, + {"buffer_pool_pages_dirty", + (char*) &export_vars.innodb_buffer_pool_pages_dirty, SHOW_LONG}, + {"buffer_pool_pages_flushed", + (char*) &export_vars.innodb_buffer_pool_pages_flushed, SHOW_LONG}, + {"buffer_pool_pages_free", + (char*) &export_vars.innodb_buffer_pool_pages_free, SHOW_LONG}, +#ifdef UNIV_DEBUG + {"buffer_pool_pages_latched", + (char*) &export_vars.innodb_buffer_pool_pages_latched, SHOW_LONG}, +#endif /* UNIV_DEBUG */ + {"buffer_pool_pages_misc", + (char*) &export_vars.innodb_buffer_pool_pages_misc, SHOW_LONG}, + {"buffer_pool_pages_total", + (char*) &export_vars.innodb_buffer_pool_pages_total, SHOW_LONG}, + {"buffer_pool_read_ahead_rnd", + (char*) &export_vars.innodb_buffer_pool_read_ahead_rnd, SHOW_LONG}, + {"buffer_pool_read_ahead_seq", + (char*) &export_vars.innodb_buffer_pool_read_ahead_seq, SHOW_LONG}, + {"buffer_pool_read_requests", + (char*) &export_vars.innodb_buffer_pool_read_requests, SHOW_LONG}, + {"buffer_pool_reads", + (char*) &export_vars.innodb_buffer_pool_reads, SHOW_LONG}, + {"buffer_pool_wait_free", + (char*) &export_vars.innodb_buffer_pool_wait_free, SHOW_LONG}, + {"buffer_pool_write_requests", + (char*) &export_vars.innodb_buffer_pool_write_requests, SHOW_LONG}, + {"data_fsyncs", + (char*) &export_vars.innodb_data_fsyncs, SHOW_LONG}, + {"data_pending_fsyncs", + (char*) &export_vars.innodb_data_pending_fsyncs, SHOW_LONG}, + {"data_pending_reads", + (char*) &export_vars.innodb_data_pending_reads, SHOW_LONG}, + {"data_pending_writes", + (char*) &export_vars.innodb_data_pending_writes, SHOW_LONG}, + {"data_read", + (char*) &export_vars.innodb_data_read, SHOW_LONG}, + {"data_reads", + (char*) &export_vars.innodb_data_reads, SHOW_LONG}, + {"data_writes", + (char*) &export_vars.innodb_data_writes, SHOW_LONG}, + {"data_written", + (char*) &export_vars.innodb_data_written, SHOW_LONG}, + {"dblwr_pages_written", + (char*) &export_vars.innodb_dblwr_pages_written, SHOW_LONG}, + {"dblwr_writes", + (char*) &export_vars.innodb_dblwr_writes, SHOW_LONG}, + {"dict_tables", + (char*) &export_vars.innodb_dict_tables, SHOW_LONG}, + {"have_atomic_builtins", + (char*) &export_vars.innodb_have_atomic_builtins, SHOW_BOOL}, + {"log_waits", + (char*) &export_vars.innodb_log_waits, SHOW_LONG}, + {"log_write_requests", + (char*) &export_vars.innodb_log_write_requests, SHOW_LONG}, + {"log_writes", + (char*) &export_vars.innodb_log_writes, SHOW_LONG}, + {"os_log_fsyncs", + (char*) &export_vars.innodb_os_log_fsyncs, SHOW_LONG}, + {"os_log_pending_fsyncs", + (char*) &export_vars.innodb_os_log_pending_fsyncs, SHOW_LONG}, + {"os_log_pending_writes", + (char*) &export_vars.innodb_os_log_pending_writes, SHOW_LONG}, + {"os_log_written", + (char*) &export_vars.innodb_os_log_written, SHOW_LONG}, + {"page_size", + (char*) &export_vars.innodb_page_size, SHOW_LONG}, + {"pages_created", + (char*) &export_vars.innodb_pages_created, SHOW_LONG}, + {"pages_read", + (char*) &export_vars.innodb_pages_read, SHOW_LONG}, + {"pages_written", + (char*) &export_vars.innodb_pages_written, SHOW_LONG}, + {"row_lock_current_waits", + (char*) &export_vars.innodb_row_lock_current_waits, SHOW_LONG}, + {"row_lock_time", + (char*) &export_vars.innodb_row_lock_time, SHOW_LONGLONG}, + {"row_lock_time_avg", + (char*) &export_vars.innodb_row_lock_time_avg, SHOW_LONG}, + {"row_lock_time_max", + (char*) &export_vars.innodb_row_lock_time_max, SHOW_LONG}, + {"row_lock_waits", + (char*) &export_vars.innodb_row_lock_waits, SHOW_LONG}, + {"rows_deleted", + (char*) &export_vars.innodb_rows_deleted, SHOW_LONG}, + {"rows_inserted", + (char*) &export_vars.innodb_rows_inserted, SHOW_LONG}, + {"rows_read", + (char*) &export_vars.innodb_rows_read, SHOW_LONG}, + {"rows_updated", + (char*) &export_vars.innodb_rows_updated, SHOW_LONG}, + {NullS, NullS, SHOW_LONG} +}; + +/* General functions */ + +/********************************************************************** +Returns true if the thread is the replication thread on the slave +server. Used in srv_conc_enter_innodb() to determine if the thread +should be allowed to enter InnoDB - the replication thread is treated +differently than other threads. Also used in +srv_conc_force_exit_innodb(). */ +extern "C" UNIV_INTERN +ibool +thd_is_replication_slave_thread( +/*============================*/ + /* out: true if thd is the replication thread */ + void* thd) /* in: thread handle (THD*) */ +{ + return((ibool) thd_slave_thread((THD*) thd)); +} + +/********************************************************************** +Save some CPU by testing the value of srv_thread_concurrency in inline +functions. */ +static inline +void +innodb_srv_conc_enter_innodb( +/*=========================*/ + trx_t* trx) /* in: transaction handle */ +{ + if (UNIV_LIKELY(!srv_thread_concurrency)) { + + return; + } + + srv_conc_enter_innodb(trx); +} + +/********************************************************************** +Save some CPU by testing the value of srv_thread_concurrency in inline +functions. */ +static inline +void +innodb_srv_conc_exit_innodb( +/*========================*/ + trx_t* trx) /* in: transaction handle */ +{ + if (UNIV_LIKELY(!trx->declared_to_be_inside_innodb)) { + + return; + } + + srv_conc_exit_innodb(trx); +} + +/********************************************************************** +Releases possible search latch and InnoDB thread FIFO ticket. These should +be released at each SQL statement end, and also when mysqld passes the +control to the client. It does no harm to release these also in the middle +of an SQL statement. */ +static inline +void +innobase_release_stat_resources( +/*============================*/ + trx_t* trx) /* in: transaction object */ +{ + if (trx->has_search_latch) { + trx_search_latch_release_if_reserved(trx); + } + + if (trx->declared_to_be_inside_innodb) { + /* Release our possible ticket in the FIFO */ + + srv_conc_force_exit_innodb(trx); + } +} + +/********************************************************************** +Returns true if the transaction this thread is processing has edited +non-transactional tables. Used by the deadlock detector when deciding +which transaction to rollback in case of a deadlock - we try to avoid +rolling back transactions that have edited non-transactional tables. */ +extern "C" UNIV_INTERN +ibool +thd_has_edited_nontrans_tables( +/*===========================*/ + /* out: true if non-transactional tables have + been edited */ + void* thd) /* in: thread handle (THD*) */ +{ + return((ibool) thd_non_transactional_update((THD*) thd)); +} + +/********************************************************************** +Returns true if the thread is executing a SELECT statement. */ +extern "C" UNIV_INTERN +ibool +thd_is_select( +/*==========*/ + /* out: true if thd is executing SELECT */ + const void* thd) /* in: thread handle (THD*) */ +{ + return(thd_sql_command((const THD*) thd) == SQLCOM_SELECT); +} + +/********************************************************************** +Returns true if the thread supports XA, +global value of innodb_supports_xa if thd is NULL. */ +extern "C" UNIV_INTERN +ibool +thd_supports_xa( +/*============*/ + /* out: true if thd has XA support */ + void* thd) /* in: thread handle (THD*), or NULL to query + the global innodb_supports_xa */ +{ + return(THDVAR((THD*) thd, support_xa)); +} + +/********************************************************************** +Returns the lock wait timeout for the current connection. */ +extern "C" UNIV_INTERN +ulong +thd_lock_wait_timeout( +/*==================*/ + /* out: the lock wait timeout, in seconds */ + void* thd) /* in: thread handle (THD*), or NULL to query + the global innodb_lock_wait_timeout */ +{ + /* According to <mysql/plugin.h>, passing thd == NULL + returns the global value of the session variable. */ + return(THDVAR((THD*) thd, lock_wait_timeout)); +} + +/************************************************************************ +Obtain the InnoDB transaction of a MySQL thread. */ +static inline +trx_t*& +thd_to_trx( +/*=======*/ + /* out: reference to transaction pointer */ + THD* thd) /* in: MySQL thread */ +{ + return(*(trx_t**) thd_ha_data(thd, innodb_hton_ptr)); +} + +/************************************************************************ +Call this function when mysqld passes control to the client. That is to +avoid deadlocks on the adaptive hash S-latch possibly held by thd. For more +documentation, see handler.cc. */ +static +int +innobase_release_temporary_latches( +/*===============================*/ + /* out: 0 */ + handlerton* hton, /* in: handlerton */ + THD* thd) /* in: MySQL thread */ +{ + trx_t* trx; + + DBUG_ASSERT(hton == innodb_hton_ptr); + + if (!innodb_inited) { + + return(0); + } + + trx = thd_to_trx(thd); + + if (trx) { + innobase_release_stat_resources(trx); + } + return(0); +} + +/************************************************************************ +Increments innobase_active_counter and every INNOBASE_WAKE_INTERVALth +time calls srv_active_wake_master_thread. This function should be used +when a single database operation may introduce a small need for +server utility activity, like checkpointing. */ +static inline +void +innobase_active_small(void) +/*=======================*/ +{ + innobase_active_counter++; + + if ((innobase_active_counter % INNOBASE_WAKE_INTERVAL) == 0) { + srv_active_wake_master_thread(); + } +} + +/************************************************************************ +Converts an InnoDB error code to a MySQL error code and also tells to MySQL +about a possible transaction rollback inside InnoDB caused by a lock wait +timeout or a deadlock. */ +extern "C" UNIV_INTERN +int +convert_error_code_to_mysql( +/*========================*/ + /* out: MySQL error code */ + int error, /* in: InnoDB error code */ + ulint flags, /* in: InnoDB table flags, or 0 */ + THD* thd) /* in: user thread handle or NULL */ +{ + switch (error) { + case DB_SUCCESS: + return(0); + + case DB_ERROR: + default: + return(-1); /* unspecified error */ + + case DB_DUPLICATE_KEY: + return(HA_ERR_FOUND_DUPP_KEY); + + case DB_FOREIGN_DUPLICATE_KEY: + return(HA_ERR_FOREIGN_DUPLICATE_KEY); + + case DB_RECORD_NOT_FOUND: + return(HA_ERR_NO_ACTIVE_RECORD); + + case DB_DEADLOCK: + /* Since we rolled back the whole transaction, we must + tell it also to MySQL so that MySQL knows to empty the + cached binlog for this transaction */ + + if (thd) { + thd_mark_transaction_to_rollback(thd, TRUE); + } + + return(HA_ERR_LOCK_DEADLOCK); + + case DB_LOCK_WAIT_TIMEOUT: + /* Starting from 5.0.13, we let MySQL just roll back the + latest SQL statement in a lock wait timeout. Previously, we + rolled back the whole transaction. */ + + if (thd) { + thd_mark_transaction_to_rollback( + thd, (bool)row_rollback_on_timeout); + } + + return(HA_ERR_LOCK_WAIT_TIMEOUT); + + case DB_NO_REFERENCED_ROW: + return(HA_ERR_NO_REFERENCED_ROW); + + case DB_ROW_IS_REFERENCED: + return(HA_ERR_ROW_IS_REFERENCED); + + case DB_CANNOT_ADD_CONSTRAINT: + return(HA_ERR_CANNOT_ADD_FOREIGN); + + case DB_CANNOT_DROP_CONSTRAINT: + + return(HA_ERR_ROW_IS_REFERENCED); /* TODO: This is a bit + misleading, a new MySQL error + code should be introduced */ + + case DB_COL_APPEARS_TWICE_IN_INDEX: + case DB_CORRUPTION: + return(HA_ERR_CRASHED); + + case DB_OUT_OF_FILE_SPACE: + return(HA_ERR_RECORD_FILE_FULL); + + case DB_TABLE_IS_BEING_USED: + return(HA_ERR_WRONG_COMMAND); + + case DB_TABLE_NOT_FOUND: + return(HA_ERR_NO_SUCH_TABLE); + + case DB_TOO_BIG_RECORD: + my_error(ER_TOO_BIG_ROWSIZE, MYF(0), + page_get_free_space_of_empty(flags + & DICT_TF_COMPACT) / 2); + return(HA_ERR_TO_BIG_ROW); + + case DB_NO_SAVEPOINT: + return(HA_ERR_NO_SAVEPOINT); + + case DB_LOCK_TABLE_FULL: + /* Since we rolled back the whole transaction, we must + tell it also to MySQL so that MySQL knows to empty the + cached binlog for this transaction */ + + if (thd) { + thd_mark_transaction_to_rollback(thd, TRUE); + } + + return(HA_ERR_LOCK_TABLE_FULL); + + case DB_PRIMARY_KEY_IS_NULL: + return(ER_PRIMARY_CANT_HAVE_NULL); + + case DB_TOO_MANY_CONCURRENT_TRXS: + /* Once MySQL add the appropriate code to errmsg.txt then + we can get rid of this #ifdef. NOTE: The code checked by + the #ifdef is the suggested name for the error condition + and the actual error code name could very well be different. + This will require some monitoring, ie. the status + of this request on our part.*/ +#ifdef ER_TOO_MANY_CONCURRENT_TRXS + return(ER_TOO_MANY_CONCURRENT_TRXS); +#else + return(HA_ERR_RECORD_FILE_FULL); +#endif + case DB_UNSUPPORTED: + return(HA_ERR_UNSUPPORTED); + } +} + +/***************************************************************** +If you want to print a thd that is not associated with the current thread, +you must call this function before reserving the InnoDB kernel_mutex, to +protect MySQL from setting thd->query NULL. If you print a thd of the current +thread, we know that MySQL cannot modify thd->query, and it is not necessary +to call this. Call innobase_mysql_end_print_arbitrary_thd() after you release +the kernel_mutex. */ +extern "C" UNIV_INTERN +void +innobase_mysql_prepare_print_arbitrary_thd(void) +/*============================================*/ +{ + ut_ad(!mutex_own(&kernel_mutex)); + VOID(pthread_mutex_lock(&LOCK_thread_count)); +} + +/***************************************************************** +Releases the mutex reserved by innobase_mysql_prepare_print_arbitrary_thd(). +In the InnoDB latching order, the mutex sits right above the +kernel_mutex. In debug builds, we assert that the kernel_mutex is +released before this function is invoked. */ +extern "C" UNIV_INTERN +void +innobase_mysql_end_print_arbitrary_thd(void) +/*========================================*/ +{ + ut_ad(!mutex_own(&kernel_mutex)); + VOID(pthread_mutex_unlock(&LOCK_thread_count)); +} + +/***************************************************************** +Prints info of a THD object (== user session thread) to the given file. */ +extern "C" UNIV_INTERN +void +innobase_mysql_print_thd( +/*=====================*/ + FILE* f, /* in: output stream */ + void* thd, /* in: pointer to a MySQL THD object */ + uint max_query_len) /* in: max query length to print, or 0 to + use the default max length */ +{ + char buffer[1024]; + + fputs(thd_security_context((THD*) thd, buffer, sizeof buffer, + max_query_len), f); + putc('\n', f); +} + +/********************************************************************** +Get the variable length bounds of the given character set. */ +extern "C" UNIV_INTERN +void +innobase_get_cset_width( +/*====================*/ + ulint cset, /* in: MySQL charset-collation code */ + ulint* mbminlen, /* out: minimum length of a char (in bytes) */ + ulint* mbmaxlen) /* out: maximum length of a char (in bytes) */ +{ + CHARSET_INFO* cs; + ut_ad(cset < 256); + ut_ad(mbminlen); + ut_ad(mbmaxlen); + + cs = all_charsets[cset]; + if (cs) { + *mbminlen = cs->mbminlen; + *mbmaxlen = cs->mbmaxlen; + } else { + ut_a(cset == 0); + *mbminlen = *mbmaxlen = 0; + } +} + +/********************************************************************** +Converts an identifier to a table name. */ +extern "C" UNIV_INTERN +void +innobase_convert_from_table_id( +/*===========================*/ + struct charset_info_st* cs, /* in: the 'from' character set */ + char* to, /* out: converted identifier */ + const char* from, /* in: identifier to convert */ + ulint len) /* in: length of 'to', in bytes */ +{ + uint errors; + + strconvert(cs, from, &my_charset_filename, to, (uint) len, &errors); +} + +/********************************************************************** +Converts an identifier to UTF-8. */ +extern "C" UNIV_INTERN +void +innobase_convert_from_id( +/*=====================*/ + struct charset_info_st* cs, /* in: the 'from' character set */ + char* to, /* out: converted identifier */ + const char* from, /* in: identifier to convert */ + ulint len) /* in: length of 'to', in bytes */ +{ + uint errors; + + strconvert(cs, from, system_charset_info, to, (uint) len, &errors); +} + +/********************************************************************** +Compares NUL-terminated UTF-8 strings case insensitively. */ +extern "C" UNIV_INTERN +int +innobase_strcasecmp( +/*================*/ + /* out: 0 if a=b, <0 if a<b, >1 if a>b */ + const char* a, /* in: first string to compare */ + const char* b) /* in: second string to compare */ +{ + return(my_strcasecmp(system_charset_info, a, b)); +} + +/********************************************************************** +Makes all characters in a NUL-terminated UTF-8 string lower case. */ +extern "C" UNIV_INTERN +void +innobase_casedn_str( +/*================*/ + char* a) /* in/out: string to put in lower case */ +{ + my_casedn_str(system_charset_info, a); +} + +/************************************************************************** +Determines the connection character set. */ +extern "C" UNIV_INTERN +struct charset_info_st* +innobase_get_charset( +/*=================*/ + /* out: connection character set */ + void* mysql_thd) /* in: MySQL thread handle */ +{ + return(thd_charset((THD*) mysql_thd)); +} + +#if defined (__WIN__) && defined (MYSQL_DYNAMIC_PLUGIN) +/*********************************************************************** +Map an OS error to an errno value. The OS error number is stored in +_doserrno and the mapped value is stored in errno) */ +extern "C" +void __cdecl +_dosmaperr( + unsigned long); /* in: OS error value */ + +/************************************************************************* +Creates a temporary file. */ +extern "C" UNIV_INTERN +int +innobase_mysql_tmpfile(void) +/*========================*/ + /* out: temporary file descriptor, or < 0 on error */ +{ + int fd; /* handle of opened file */ + HANDLE osfh; /* OS handle of opened file */ + char* tmpdir; /* point to the directory + where to create file */ + TCHAR path_buf[MAX_PATH - 14]; /* buffer for tmp file path. + The length cannot be longer + than MAX_PATH - 14, or + GetTempFileName will fail. */ + char filename[MAX_PATH]; /* name of the tmpfile */ + DWORD fileaccess = GENERIC_READ /* OS file access */ + | GENERIC_WRITE + | DELETE; + DWORD fileshare = FILE_SHARE_READ /* OS file sharing mode */ + | FILE_SHARE_WRITE + | FILE_SHARE_DELETE; + DWORD filecreate = CREATE_ALWAYS; /* OS method of open/create */ + DWORD fileattrib = /* OS file attribute flags */ + FILE_ATTRIBUTE_NORMAL + | FILE_FLAG_DELETE_ON_CLOSE + | FILE_ATTRIBUTE_TEMPORARY + | FILE_FLAG_SEQUENTIAL_SCAN; + + DBUG_ENTER("innobase_mysql_tmpfile"); + + tmpdir = my_tmpdir(&mysql_tmpdir_list); + + /* The tmpdir parameter can not be NULL for GetTempFileName. */ + if (!tmpdir) { + uint ret; + + /* Use GetTempPath to determine path for temporary files. */ + ret = GetTempPath(sizeof(path_buf), path_buf); + if (ret > sizeof(path_buf) || (ret == 0)) { + + _dosmaperr(GetLastError()); /* map error */ + DBUG_RETURN(-1); + } + + tmpdir = path_buf; + } + + /* Use GetTempFileName to generate a unique filename. */ + if (!GetTempFileName(tmpdir, "ib", 0, filename)) { + + _dosmaperr(GetLastError()); /* map error */ + DBUG_RETURN(-1); + } + + DBUG_PRINT("info", ("filename: %s", filename)); + + /* Open/Create the file. */ + osfh = CreateFile(filename, fileaccess, fileshare, NULL, + filecreate, fileattrib, NULL); + if (osfh == INVALID_HANDLE_VALUE) { + + /* open/create file failed! */ + _dosmaperr(GetLastError()); /* map error */ + DBUG_RETURN(-1); + } + + do { + /* Associates a CRT file descriptor with the OS file handle. */ + fd = _open_osfhandle((intptr_t) osfh, 0); + } while (fd == -1 && errno == EINTR); + + if (fd == -1) { + /* Open failed, close the file handle. */ + + _dosmaperr(GetLastError()); /* map error */ + CloseHandle(osfh); /* no need to check if + CloseHandle fails */ + } + + DBUG_RETURN(fd); +} +#else +/************************************************************************* +Creates a temporary file. */ +extern "C" UNIV_INTERN +int +innobase_mysql_tmpfile(void) +/*========================*/ + /* out: temporary file descriptor, or < 0 on error */ +{ + int fd2 = -1; + File fd = mysql_tmpfile("ib"); + if (fd >= 0) { + /* Copy the file descriptor, so that the additional resources + allocated by create_temp_file() can be freed by invoking + my_close(). + + Because the file descriptor returned by this function + will be passed to fdopen(), it will be closed by invoking + fclose(), which in turn will invoke close() instead of + my_close(). */ + fd2 = dup(fd); + if (fd2 < 0) { + DBUG_PRINT("error",("Got error %d on dup",fd2)); + my_errno=errno; + my_error(EE_OUT_OF_FILERESOURCES, + MYF(ME_BELL+ME_WAITTANG), + "ib*", my_errno); + } + my_close(fd, MYF(MY_WME)); + } + return(fd2); +} +#endif /* defined (__WIN__) && defined (MYSQL_DYNAMIC_PLUGIN) */ + +/************************************************************************* +Wrapper around MySQL's copy_and_convert function, see it for +documentation. */ +extern "C" UNIV_INTERN +ulint +innobase_convert_string( +/*====================*/ + void* to, + ulint to_length, + CHARSET_INFO* to_cs, + const void* from, + ulint from_length, + CHARSET_INFO* from_cs, + uint* errors) +{ + return(copy_and_convert((char*)to, (uint32) to_length, to_cs, + (const char*)from, (uint32) from_length, from_cs, + errors)); +} + +/*********************************************************************** +Formats the raw data in "data" (in InnoDB on-disk format) that is of +type DATA_(CHAR|VARCHAR|MYSQL|VARMYSQL) using "charset_coll" and writes +the result to "buf". The result is converted to "system_charset_info". +Not more than "buf_size" bytes are written to "buf". +The result is always '\0'-terminated (provided buf_size > 0) and the +number of bytes that were written to "buf" is returned (including the +terminating '\0'). */ +extern "C" UNIV_INTERN +ulint +innobase_raw_format( +/*================*/ + /* out: number of bytes + that were written */ + const char* data, /* in: raw data */ + ulint data_len, /* in: raw data length + in bytes */ + ulint charset_coll, /* in: charset collation */ + char* buf, /* out: output buffer */ + ulint buf_size) /* in: output buffer size + in bytes */ +{ + /* XXX we use a hard limit instead of allocating + but_size bytes from the heap */ + CHARSET_INFO* data_cs; + char buf_tmp[8192]; + ulint buf_tmp_used; + uint num_errors; + + data_cs = all_charsets[charset_coll]; + + buf_tmp_used = innobase_convert_string(buf_tmp, sizeof(buf_tmp), + system_charset_info, + data, data_len, data_cs, + &num_errors); + + return(ut_str_sql_format(buf_tmp, buf_tmp_used, buf, buf_size)); +} + +/************************************************************************* +Compute the next autoinc value. + +For MySQL replication the autoincrement values can be partitioned among +the nodes. The offset is the start or origin of the autoincrement value +for a particular node. For n nodes the increment will be n and the offset +will be in the interval [1, n]. The formula tries to allocate the next +value for a particular node. + +Note: This function is also called with increment set to the number of +values we want to reserve for multi-value inserts e.g., + + INSERT INTO T VALUES(), (), (); + +innobase_next_autoinc() will be called with increment set to +n * 3 where autoinc_lock_mode != TRADITIONAL because we want +to reserve 3 values for the multi-value INSERT above. */ +static +ulonglong +innobase_next_autoinc( +/*==================*/ + /* out: the next value */ + ulonglong current, /* in: Current value */ + ulonglong increment, /* in: increment current by */ + ulonglong offset, /* in: AUTOINC offset */ + ulonglong max_value) /* in: max value for type */ +{ + ulonglong next_value; + + /* Should never be 0. */ + ut_a(increment > 0); + + /* According to MySQL documentation, if the offset is greater than + the increment then the offset is ignored. */ + if (offset > increment) { + offset = 0; + } + + if (max_value <= current) { + next_value = max_value; + } else if (offset <= 1) { + /* Offset 0 and 1 are the same, because there must be at + least one node in the system. */ + if (max_value - current <= increment) { + next_value = max_value; + } else { + next_value = current + increment; + } + } else if (max_value > current) { + if (current > offset) { + next_value = ((current - offset) / increment) + 1; + } else { + next_value = ((offset - current) / increment) + 1; + } + + ut_a(increment > 0); + ut_a(next_value > 0); + + /* Check for multiplication overflow. */ + if (increment > (max_value / next_value)) { + + next_value = max_value; + } else { + next_value *= increment; + + ut_a(max_value >= next_value); + + /* Check for overflow. */ + if (max_value - next_value <= offset) { + next_value = max_value; + } else { + next_value += offset; + } + } + } else { + next_value = max_value; + } + + ut_a(next_value <= max_value); + + return(next_value); +} + +/************************************************************************* +Initializes some fields in an InnoDB transaction object. */ +static +void +innobase_trx_init( +/*==============*/ + THD* thd, /* in: user thread handle */ + trx_t* trx) /* in/out: InnoDB transaction handle */ +{ + DBUG_ENTER("innobase_trx_init"); + DBUG_ASSERT(EQ_CURRENT_THD(thd)); + DBUG_ASSERT(thd == trx->mysql_thd); + + trx->check_foreigns = !thd_test_options( + thd, OPTION_NO_FOREIGN_KEY_CHECKS); + + trx->check_unique_secondary = !thd_test_options( + thd, OPTION_RELAXED_UNIQUE_CHECKS); + + DBUG_VOID_RETURN; +} + +/************************************************************************* +Allocates an InnoDB transaction for a MySQL handler object. */ +extern "C" UNIV_INTERN +trx_t* +innobase_trx_allocate( +/*==================*/ + /* out: InnoDB transaction handle */ + THD* thd) /* in: user thread handle */ +{ + trx_t* trx; + + DBUG_ENTER("innobase_trx_allocate"); + DBUG_ASSERT(thd != NULL); + DBUG_ASSERT(EQ_CURRENT_THD(thd)); + + trx = trx_allocate_for_mysql(); + + trx->mysql_thd = thd; + trx->mysql_query_str = thd_query(thd); + + innobase_trx_init(thd, trx); + + DBUG_RETURN(trx); +} + +/************************************************************************* +Gets the InnoDB transaction handle for a MySQL handler object, creates +an InnoDB transaction struct if the corresponding MySQL thread struct still +lacks one. */ +static +trx_t* +check_trx_exists( +/*=============*/ + /* out: InnoDB transaction handle */ + THD* thd) /* in: user thread handle */ +{ + trx_t*& trx = thd_to_trx(thd); + + ut_ad(EQ_CURRENT_THD(thd)); + + if (trx == NULL) { + trx = innobase_trx_allocate(thd); + } else if (UNIV_UNLIKELY(trx->magic_n != TRX_MAGIC_N)) { + mem_analyze_corruption(trx); + ut_error; + } + + innobase_trx_init(thd, trx); + + return(trx); +} + + +/************************************************************************* +Construct ha_innobase handler. */ +UNIV_INTERN +ha_innobase::ha_innobase(handlerton *hton, TABLE_SHARE *table_arg) + :handler(hton, table_arg), + int_table_flags(HA_REC_NOT_IN_SEQ | + HA_NULL_IN_KEY | + HA_CAN_INDEX_BLOBS | + HA_CAN_SQL_HANDLER | + HA_PRIMARY_KEY_REQUIRED_FOR_POSITION | + HA_PRIMARY_KEY_IN_READ_INDEX | + HA_BINLOG_ROW_CAPABLE | + HA_CAN_GEOMETRY | HA_PARTIAL_COLUMN_READ | + HA_TABLE_SCAN_ON_INDEX), + start_of_scan(0), + num_write_row(0) +{} + +/************************************************************************* +Destruct ha_innobase handler. */ +UNIV_INTERN +ha_innobase::~ha_innobase() +{ +} + +/************************************************************************* +Updates the user_thd field in a handle and also allocates a new InnoDB +transaction handle if needed, and updates the transaction fields in the +prebuilt struct. */ +UNIV_INTERN inline +void +ha_innobase::update_thd( +/*====================*/ + THD* thd) /* in: thd to use the handle */ +{ + trx_t* trx; + + trx = check_trx_exists(thd); + + if (prebuilt->trx != trx) { + + row_update_prebuilt_trx(prebuilt, trx); + } + + user_thd = thd; +} + +/************************************************************************* +Updates the user_thd field in a handle and also allocates a new InnoDB +transaction handle if needed, and updates the transaction fields in the +prebuilt struct. */ +UNIV_INTERN +void +ha_innobase::update_thd() +/*=====================*/ +{ + THD* thd = ha_thd(); + ut_ad(EQ_CURRENT_THD(thd)); + update_thd(thd); +} + +/************************************************************************* +Registers that InnoDB takes part in an SQL statement, so that MySQL knows to +roll back the statement if the statement results in an error. This MUST be +called for every SQL statement that may be rolled back by MySQL. Calling this +several times to register the same statement is allowed, too. */ +static inline +void +innobase_register_stmt( +/*===================*/ + handlerton* hton, /* in: Innobase hton */ + THD* thd) /* in: MySQL thd (connection) object */ +{ + DBUG_ASSERT(hton == innodb_hton_ptr); + /* Register the statement */ + trans_register_ha(thd, FALSE, hton); +} + +/************************************************************************* +Registers an InnoDB transaction in MySQL, so that the MySQL XA code knows +to call the InnoDB prepare and commit, or rollback for the transaction. This +MUST be called for every transaction for which the user may call commit or +rollback. Calling this several times to register the same transaction is +allowed, too. +This function also registers the current SQL statement. */ +static inline +void +innobase_register_trx_and_stmt( +/*===========================*/ + handlerton *hton, /* in: Innobase handlerton */ + THD* thd) /* in: MySQL thd (connection) object */ +{ + /* NOTE that actually innobase_register_stmt() registers also + the transaction in the AUTOCOMMIT=1 mode. */ + + innobase_register_stmt(hton, thd); + + if (thd_test_options(thd, OPTION_NOT_AUTOCOMMIT | OPTION_BEGIN)) { + + /* No autocommit mode, register for a transaction */ + trans_register_ha(thd, TRUE, hton); + } +} + +/* BACKGROUND INFO: HOW THE MYSQL QUERY CACHE WORKS WITH INNODB + ------------------------------------------------------------ + +1) The use of the query cache for TBL is disabled when there is an +uncommitted change to TBL. + +2) When a change to TBL commits, InnoDB stores the current value of +its global trx id counter, let us denote it by INV_TRX_ID, to the table object +in the InnoDB data dictionary, and does only allow such transactions whose +id <= INV_TRX_ID to use the query cache. + +3) When InnoDB does an INSERT/DELETE/UPDATE to a table TBL, or an implicit +modification because an ON DELETE CASCADE, we invalidate the MySQL query cache +of TBL immediately. + +How this is implemented inside InnoDB: + +1) Since every modification always sets an IX type table lock on the InnoDB +table, it is easy to check if there can be uncommitted modifications for a +table: just check if there are locks in the lock list of the table. + +2) When a transaction inside InnoDB commits, it reads the global trx id +counter and stores the value INV_TRX_ID to the tables on which it had a lock. + +3) If there is an implicit table change from ON DELETE CASCADE or SET NULL, +InnoDB calls an invalidate method for the MySQL query cache for that table. + +How this is implemented inside sql_cache.cc: + +1) The query cache for an InnoDB table TBL is invalidated immediately at an +INSERT/UPDATE/DELETE, just like in the case of MyISAM. No need to delay +invalidation to the transaction commit. + +2) To store or retrieve a value from the query cache of an InnoDB table TBL, +any query must first ask InnoDB's permission. We must pass the thd as a +parameter because InnoDB will look at the trx id, if any, associated with +that thd. + +3) Use of the query cache for InnoDB tables is now allowed also when +AUTOCOMMIT==0 or we are inside BEGIN ... COMMIT. Thus transactions no longer +put restrictions on the use of the query cache. +*/ + +/********************************************************************** +The MySQL query cache uses this to check from InnoDB if the query cache at +the moment is allowed to operate on an InnoDB table. The SQL query must +be a non-locking SELECT. + +The query cache is allowed to operate on certain query only if this function +returns TRUE for all tables in the query. + +If thd is not in the autocommit state, this function also starts a new +transaction for thd if there is no active trx yet, and assigns a consistent +read view to it if there is no read view yet. + +Why a deadlock of threads is not possible: the query cache calls this function +at the start of a SELECT processing. Then the calling thread cannot be +holding any InnoDB semaphores. The calling thread is holding the +query cache mutex, and this function will reserver the InnoDB kernel mutex. +Thus, the 'rank' in sync0sync.h of the MySQL query cache mutex is above +the InnoDB kernel mutex. */ +static +my_bool +innobase_query_caching_of_table_permitted( +/*======================================*/ + /* out: TRUE if permitted, FALSE if not; + note that the value FALSE does not mean + we should invalidate the query cache: + invalidation is called explicitly */ + THD* thd, /* in: thd of the user who is trying to + store a result to the query cache or + retrieve it */ + char* full_name, /* in: concatenation of database name, + the null character '\0', and the table + name */ + uint full_name_len, /* in: length of the full name, i.e. + len(dbname) + len(tablename) + 1 */ + ulonglong *unused) /* unused for this engine */ +{ + ibool is_autocommit; + trx_t* trx; + char norm_name[1000]; + + ut_a(full_name_len < 999); + + trx = check_trx_exists(thd); + + if (trx->isolation_level == TRX_ISO_SERIALIZABLE) { + /* In the SERIALIZABLE mode we add LOCK IN SHARE MODE to every + plain SELECT if AUTOCOMMIT is not on. */ + + return((my_bool)FALSE); + } + + if (trx->has_search_latch) { + sql_print_error("The calling thread is holding the adaptive " + "search, latch though calling " + "innobase_query_caching_of_table_permitted."); + + mutex_enter(&kernel_mutex); + trx_print(stderr, trx, 1024); + mutex_exit(&kernel_mutex); + } + + innobase_release_stat_resources(trx); + + if (!thd_test_options(thd, OPTION_NOT_AUTOCOMMIT | OPTION_BEGIN)) { + + is_autocommit = TRUE; + } else { + is_autocommit = FALSE; + + } + + if (is_autocommit && trx->n_mysql_tables_in_use == 0) { + /* We are going to retrieve the query result from the query + cache. This cannot be a store operation to the query cache + because then MySQL would have locks on tables already. + + TODO: if the user has used LOCK TABLES to lock the table, + then we open a transaction in the call of row_.. below. + That trx can stay open until UNLOCK TABLES. The same problem + exists even if we do not use the query cache. MySQL should be + modified so that it ALWAYS calls some cleanup function when + the processing of a query ends! + + We can imagine we instantaneously serialize this consistent + read trx to the current trx id counter. If trx2 would have + changed the tables of a query result stored in the cache, and + trx2 would have already committed, making the result obsolete, + then trx2 would have already invalidated the cache. Thus we + can trust the result in the cache is ok for this query. */ + + return((my_bool)TRUE); + } + + /* Normalize the table name to InnoDB format */ + + memcpy(norm_name, full_name, full_name_len); + + norm_name[strlen(norm_name)] = '/'; /* InnoDB uses '/' as the + separator between db and table */ + norm_name[full_name_len] = '\0'; +#ifdef __WIN__ + innobase_casedn_str(norm_name); +#endif + /* The call of row_search_.. will start a new transaction if it is + not yet started */ + + if (trx->active_trans == 0) { + + innobase_register_trx_and_stmt(innodb_hton_ptr, thd); + trx->active_trans = 1; + } + + if (row_search_check_if_query_cache_permitted(trx, norm_name)) { + + /* printf("Query cache for %s permitted\n", norm_name); */ + + return((my_bool)TRUE); + } + + /* printf("Query cache for %s NOT permitted\n", norm_name); */ + + return((my_bool)FALSE); +} + +/********************************************************************* +Invalidates the MySQL query cache for the table. +NOTE that the exact prototype of this function has to be in +/xtradb/row/row0ins.c! */ +extern "C" UNIV_INTERN +void +innobase_invalidate_query_cache( +/*============================*/ + trx_t* trx, /* in: transaction which modifies the table */ + char* full_name, /* in: concatenation of database name, null + char '\0', table name, null char'\0'; + NOTE that in Windows this is always + in LOWER CASE! */ + ulint full_name_len) /* in: full name length where also the null + chars count */ +{ + /* Note that the sync0sync.h rank of the query cache mutex is just + above the InnoDB kernel mutex. The caller of this function must not + have latches of a lower rank. */ + + /* Argument TRUE below means we are using transactions */ +#ifdef HAVE_QUERY_CACHE + mysql_query_cache_invalidate4((THD*) trx->mysql_thd, + (const char*) full_name, + (uint32) full_name_len, + TRUE); +#endif +} + +/********************************************************************* +Convert an SQL identifier to the MySQL system_charset_info (UTF-8) +and quote it if needed. */ +static +char* +innobase_convert_identifier( +/*========================*/ + /* out: pointer to the end of buf */ + char* buf, /* out: buffer for converted identifier */ + ulint buflen, /* in: length of buf, in bytes */ + const char* id, /* in: identifier to convert */ + ulint idlen, /* in: length of id, in bytes */ + void* thd, /* in: MySQL connection thread, or NULL */ + ibool file_id)/* in: TRUE=id is a table or database name; + FALSE=id is an UTF-8 string */ +{ + char nz[NAME_LEN + 1]; + char nz2[NAME_LEN + 1 + sizeof srv_mysql50_table_name_prefix]; + + const char* s = id; + int q; + + if (file_id) { + /* Decode the table name. The filename_to_tablename() + function expects a NUL-terminated string. The input and + output strings buffers must not be shared. */ + + if (UNIV_UNLIKELY(idlen > (sizeof nz) - 1)) { + idlen = (sizeof nz) - 1; + } + + memcpy(nz, id, idlen); + nz[idlen] = 0; + + s = nz2; + idlen = filename_to_tablename(nz, nz2, sizeof nz2); + } + + /* See if the identifier needs to be quoted. */ + if (UNIV_UNLIKELY(!thd)) { + q = '"'; + } else { + q = get_quote_char_for_identifier((THD*) thd, s, (int) idlen); + } + + if (q == EOF) { + if (UNIV_UNLIKELY(idlen > buflen)) { + idlen = buflen; + } + memcpy(buf, s, idlen); + return(buf + idlen); + } + + /* Quote the identifier. */ + if (buflen < 2) { + return(buf); + } + + *buf++ = q; + buflen--; + + for (; idlen; idlen--) { + int c = *s++; + if (UNIV_UNLIKELY(c == q)) { + if (UNIV_UNLIKELY(buflen < 3)) { + break; + } + + *buf++ = c; + *buf++ = c; + buflen -= 2; + } else { + if (UNIV_UNLIKELY(buflen < 2)) { + break; + } + + *buf++ = c; + buflen--; + } + } + + *buf++ = q; + return(buf); +} + +/********************************************************************* +Convert a table or index name to the MySQL system_charset_info (UTF-8) +and quote it if needed. */ +extern "C" UNIV_INTERN +char* +innobase_convert_name( +/*==================*/ + /* out: pointer to the end of buf */ + char* buf, /* out: buffer for converted identifier */ + ulint buflen, /* in: length of buf, in bytes */ + const char* id, /* in: identifier to convert */ + ulint idlen, /* in: length of id, in bytes */ + void* thd, /* in: MySQL connection thread, or NULL */ + ibool table_id)/* in: TRUE=id is a table or database name; + FALSE=id is an index name */ +{ + char* s = buf; + const char* bufend = buf + buflen; + + if (table_id) { + const char* slash = (const char*) memchr(id, '/', idlen); + if (!slash) { + + goto no_db_name; + } + + /* Print the database name and table name separately. */ + s = innobase_convert_identifier(s, bufend - s, id, slash - id, + thd, TRUE); + if (UNIV_LIKELY(s < bufend)) { + *s++ = '.'; + s = innobase_convert_identifier(s, bufend - s, + slash + 1, idlen + - (slash - id) - 1, + thd, TRUE); + } + } else if (UNIV_UNLIKELY(*id == TEMP_INDEX_PREFIX)) { + /* Temporary index name (smart ALTER TABLE) */ + const char temp_index_suffix[]= "--temporary--"; + + s = innobase_convert_identifier(buf, buflen, id + 1, idlen - 1, + thd, FALSE); + if (s - buf + (sizeof temp_index_suffix - 1) < buflen) { + memcpy(s, temp_index_suffix, + sizeof temp_index_suffix - 1); + s += sizeof temp_index_suffix - 1; + } + } else { +no_db_name: + s = innobase_convert_identifier(buf, buflen, id, idlen, + thd, table_id); + } + + return(s); + +} + +/************************************************************************** +Determines if the currently running transaction has been interrupted. */ +extern "C" UNIV_INTERN +ibool +trx_is_interrupted( +/*===============*/ + /* out: TRUE if interrupted */ + trx_t* trx) /* in: transaction */ +{ + return(trx && trx->mysql_thd && thd_killed((THD*) trx->mysql_thd)); +} + +/****************************************************************** +Resets some fields of a prebuilt struct. The template is used in fast +retrieval of just those column values MySQL needs in its processing. */ +static +void +reset_template( +/*===========*/ + row_prebuilt_t* prebuilt) /* in/out: prebuilt struct */ +{ + prebuilt->keep_other_fields_on_keyread = 0; + prebuilt->read_just_key = 0; +} + +/********************************************************************* +Call this when you have opened a new table handle in HANDLER, before you +call index_read_idx() etc. Actually, we can let the cursor stay open even +over a transaction commit! Then you should call this before every operation, +fetch next etc. This function inits the necessary things even after a +transaction commit. */ +UNIV_INTERN +void +ha_innobase::init_table_handle_for_HANDLER(void) +/*============================================*/ +{ + /* If current thd does not yet have a trx struct, create one. + If the current handle does not yet have a prebuilt struct, create + one. Update the trx pointers in the prebuilt struct. Normally + this operation is done in external_lock. */ + + update_thd(ha_thd()); + + /* Initialize the prebuilt struct much like it would be inited in + external_lock */ + + innobase_release_stat_resources(prebuilt->trx); + + /* If the transaction is not started yet, start it */ + + trx_start_if_not_started(prebuilt->trx); + + /* Assign a read view if the transaction does not have it yet */ + + trx_assign_read_view(prebuilt->trx); + + /* Set the MySQL flag to mark that there is an active transaction */ + + if (prebuilt->trx->active_trans == 0) { + + innobase_register_trx_and_stmt(ht, user_thd); + + prebuilt->trx->active_trans = 1; + } + + /* We did the necessary inits in this function, no need to repeat them + in row_search_for_mysql */ + + prebuilt->sql_stat_start = FALSE; + + /* We let HANDLER always to do the reads as consistent reads, even + if the trx isolation level would have been specified as SERIALIZABLE */ + + prebuilt->select_lock_type = LOCK_NONE; + prebuilt->stored_select_lock_type = LOCK_NONE; + + /* Always fetch all columns in the index record */ + + prebuilt->hint_need_to_fetch_extra_cols = ROW_RETRIEVE_ALL_COLS; + + /* We want always to fetch all columns in the whole row? Or do + we???? */ + + prebuilt->used_in_HANDLER = TRUE; + reset_template(prebuilt); +} + +/************************************************************************* +Opens an InnoDB database. */ +static +int +innobase_init( +/*==========*/ + /* out: 0 on success, error code on failure */ + void *p) /* in: InnoDB handlerton */ +{ + static char current_dir[3]; /* Set if using current lib */ + int err; + bool ret; + char *default_path; + uint format_id; + + DBUG_ENTER("innobase_init"); + handlerton *innobase_hton= (handlerton *)p; + +#ifdef MYSQL_DYNAMIC_PLUGIN + if (!innodb_plugin_init()) { + sql_print_error("InnoDB plugin init failed."); + DBUG_RETURN(-1); + } + + if (innodb_hton_ptr) { + /* Patch the statically linked handlerton and variables */ + innobase_hton = innodb_hton_ptr; + } +#endif /* MYSQL_DYNAMIC_PLUGIN */ + + innodb_hton_ptr = innobase_hton; + + innobase_hton->state = SHOW_OPTION_YES; + innobase_hton->db_type= DB_TYPE_INNODB; + innobase_hton->savepoint_offset=sizeof(trx_named_savept_t); + innobase_hton->close_connection=innobase_close_connection; + innobase_hton->savepoint_set=innobase_savepoint; + innobase_hton->savepoint_rollback=innobase_rollback_to_savepoint; + innobase_hton->savepoint_release=innobase_release_savepoint; + innobase_hton->commit=innobase_commit; + innobase_hton->rollback=innobase_rollback; + innobase_hton->prepare=innobase_xa_prepare; + innobase_hton->recover=innobase_xa_recover; + innobase_hton->commit_by_xid=innobase_commit_by_xid; + innobase_hton->rollback_by_xid=innobase_rollback_by_xid; + innobase_hton->create_cursor_read_view=innobase_create_cursor_view; + innobase_hton->set_cursor_read_view=innobase_set_cursor_view; + innobase_hton->close_cursor_read_view=innobase_close_cursor_view; + innobase_hton->create=innobase_create_handler; + innobase_hton->drop_database=innobase_drop_database; + innobase_hton->panic=innobase_end; + innobase_hton->start_consistent_snapshot=innobase_start_trx_and_assign_read_view; + innobase_hton->flush_logs=innobase_flush_logs; + innobase_hton->show_status=innobase_show_status; + innobase_hton->flags=HTON_NO_FLAGS; + innobase_hton->release_temporary_latches=innobase_release_temporary_latches; + innobase_hton->alter_table_flags = innobase_alter_table_flags; + + ut_a(DATA_MYSQL_TRUE_VARCHAR == (ulint)MYSQL_TYPE_VARCHAR); + +#ifdef UNIV_DEBUG + static const char test_filename[] = "-@"; + char test_tablename[sizeof test_filename + + sizeof srv_mysql50_table_name_prefix]; + if ((sizeof test_tablename) - 1 + != filename_to_tablename(test_filename, test_tablename, + sizeof test_tablename) + || strncmp(test_tablename, + srv_mysql50_table_name_prefix, + sizeof srv_mysql50_table_name_prefix) + || strcmp(test_tablename + + sizeof srv_mysql50_table_name_prefix, + test_filename)) { + sql_print_error("tablename encoding has been changed"); + goto error; + } +#endif /* UNIV_DEBUG */ + +#ifndef MYSQL_SERVER + innodb_overwrite_relay_log_info = FALSE; +#endif + +#ifdef HAVE_REPLICATION +#ifdef MYSQL_SERVER + /* read master log position from relay-log.info if exists */ + char fname[FN_REFLEN+128]; + int pos; + int info_fd; + IO_CACHE info_file; + + fname[0] = '\0'; + + if(innobase_overwrite_relay_log_info) { + + fprintf(stderr, + "InnoDB: Warning: innodb_overwrite_relay_log_info is enabled." + " Updates in other storage engines may have problem with consistency.\n"); + + bzero((char*) &info_file, sizeof(info_file)); + fn_format(fname, relay_log_info_file, mysql_data_home, "", 4+32); + + int error=0; + + if (!access(fname,F_OK)) { + /* exist */ + if ((info_fd = my_open(fname, O_RDWR|O_BINARY, MYF(MY_WME))) < 0) { + error=1; + } else if (init_io_cache(&info_file, info_fd, IO_SIZE*2, + READ_CACHE, 0L, 0, MYF(MY_WME))) { + error=1; + } + + if (error) { +relay_info_error: + if (info_fd >= 0) + my_close(info_fd, MYF(0)); + fname[0] = '\0'; + goto skip_relay; + } + } else { + fname[0] = '\0'; + goto skip_relay; + } + + if (init_strvar_from_file(fname, sizeof(fname), &info_file, "") || /* dummy (it is relay-log) */ + init_intvar_from_file(&pos, &info_file, BIN_LOG_HEADER_SIZE)) { + end_io_cache(&info_file); + error=1; + goto relay_info_error; + } + + fprintf(stderr, + "InnoDB: relay-log.info is detected.\n" + "InnoDB: relay log: position %u, file name %s\n", + pos, fname); + + strncpy(trx_sys_mysql_relay_log_name, fname, TRX_SYS_MYSQL_MASTER_LOG_NAME_LEN); + trx_sys_mysql_relay_log_pos = (ib_int64_t) pos; + + if (init_strvar_from_file(fname, sizeof(fname), &info_file, "") || + init_intvar_from_file(&pos, &info_file, 0)) { + end_io_cache(&info_file); + error=1; + goto relay_info_error; + } + + fprintf(stderr, + "InnoDB: master log: position %u, file name %s\n", + pos, fname); + + strncpy(trx_sys_mysql_master_log_name, fname, TRX_SYS_MYSQL_MASTER_LOG_NAME_LEN); + trx_sys_mysql_master_log_pos = (ib_int64_t) pos; + + end_io_cache(&info_file); + if (info_fd >= 0) + my_close(info_fd, MYF(0)); + } +skip_relay: +#endif /* MYSQL_SERVER */ +#endif /* HAVE_REPLICATION */ + + /* Check that values don't overflow on 32-bit systems. */ + if (sizeof(ulint) == 4) { + if (innobase_buffer_pool_size > UINT_MAX32) { + sql_print_error( + "innobase_buffer_pool_size can't be over 4GB" + " on 32-bit systems"); + + goto error; + } + + if (innobase_log_file_size > UINT_MAX32) { + sql_print_error( + "innobase_log_file_size can't be over 4GB" + " on 32-bit systems"); + + goto error; + } + } + + os_innodb_umask = (ulint)my_umask; + + /* First calculate the default path for innodb_data_home_dir etc., + in case the user has not given any value. + + Note that when using the embedded server, the datadirectory is not + necessarily the current directory of this program. */ + + if (mysqld_embedded) { + default_path = mysql_real_data_home; + fil_path_to_mysql_datadir = mysql_real_data_home; + } else { + /* It's better to use current lib, to keep paths short */ + current_dir[0] = FN_CURLIB; + current_dir[1] = FN_LIBCHAR; + current_dir[2] = 0; + default_path = current_dir; + } + + ut_a(default_path); + + if (specialflag & SPECIAL_NO_PRIOR) { + srv_set_thread_priorities = FALSE; + } else { + srv_set_thread_priorities = TRUE; + srv_query_thread_priority = QUERY_PRIOR; + } + + /* Set InnoDB initialization parameters according to the values + read from MySQL .cnf file */ + + /*--------------- Data files -------------------------*/ + + /* The default dir for data files is the datadir of MySQL */ + + srv_data_home = (innobase_data_home_dir ? innobase_data_home_dir : + default_path); + + /* Set default InnoDB data file size to 10 MB and let it be + auto-extending. Thus users can use InnoDB in >= 4.0 without having + to specify any startup options. */ + + if (!innobase_data_file_path) { + innobase_data_file_path = (char*) "ibdata1:10M:autoextend"; + } + + /* Since InnoDB edits the argument in the next call, we make another + copy of it: */ + + internal_innobase_data_file_path = my_strdup(innobase_data_file_path, + MYF(MY_FAE)); + + ret = (bool) srv_parse_data_file_paths_and_sizes( + internal_innobase_data_file_path); + if (ret == FALSE) { + sql_print_error( + "InnoDB: syntax error in innodb_data_file_path"); +mem_free_and_error: + srv_free_paths_and_sizes(); + my_free(internal_innobase_data_file_path, + MYF(MY_ALLOW_ZERO_PTR)); + goto error; + } + + srv_extra_undoslots = (ibool) innobase_extra_undoslots; + + /* -------------- Log files ---------------------------*/ + + /* The default dir for log files is the datadir of MySQL */ + + if (!innobase_log_group_home_dir) { + innobase_log_group_home_dir = default_path; + } + +#ifdef UNIV_LOG_ARCHIVE + /* Since innodb_log_arch_dir has no relevance under MySQL, + starting from 4.0.6 we always set it the same as + innodb_log_group_home_dir: */ + + innobase_log_arch_dir = innobase_log_group_home_dir; + + srv_arch_dir = innobase_log_arch_dir; +#endif /* UNIG_LOG_ARCHIVE */ + + ret = (bool) + srv_parse_log_group_home_dirs(innobase_log_group_home_dir); + + if (ret == FALSE || innobase_mirrored_log_groups != 1) { + sql_print_error("syntax error in innodb_log_group_home_dir, or a " + "wrong number of mirrored log groups"); + + goto mem_free_and_error; + } + + /* Validate the file format by animal name */ + if (innobase_file_format_name != NULL) { + + format_id = innobase_file_format_name_lookup( + innobase_file_format_name); + + if (format_id > DICT_TF_FORMAT_MAX) { + + sql_print_error("InnoDB: wrong innodb_file_format."); + + goto mem_free_and_error; + } + } else { + /* Set it to the default file format id. Though this + should never happen. */ + format_id = 0; + } + + srv_file_format = format_id; + + /* Given the type of innobase_file_format_name we have little + choice but to cast away the constness from the returned name. + innobase_file_format_name is used in the MySQL set variable + interface and so can't be const. */ + + innobase_file_format_name = + (char*) trx_sys_file_format_id_to_name(format_id); + + /* Process innobase_file_format_check variable */ + ut_a(innobase_file_format_check != NULL); + + /* As a side effect it will set srv_check_file_format_at_startup + on valid input. First we check for "on"/"off". */ + if (!innobase_file_format_check_on_off(innobase_file_format_check)) { + + /* Did the user specify a format name that we support ? + As a side effect it will update the variable + srv_check_file_format_at_startup */ + if (!innobase_file_format_check_validate( + innobase_file_format_check)) { + + sql_print_error("InnoDB: invalid " + "innodb_file_format_check value: " + "should be either 'on' or 'off' or " + "any value up to %s or its " + "equivalent numeric id", + trx_sys_file_format_id_to_name( + DICT_TF_FORMAT_MAX)); + + goto mem_free_and_error; + } + } + + ut_a((ulint) ibuf_use < UT_ARR_SIZE(innobase_change_buffering_values)); + innobase_change_buffering = (char*) + innobase_change_buffering_values[ibuf_use]; + + /* --------------------------------------------------*/ + + srv_file_flush_method_str = innobase_unix_file_flush_method; + + srv_n_log_groups = (ulint) innobase_mirrored_log_groups; + srv_n_log_files = (ulint) innobase_log_files_in_group; + srv_log_file_size = (ulint) innobase_log_file_size; + + srv_thread_concurrency_timer_based = + (ibool) innobase_thread_concurrency_timer_based; + +#ifdef UNIV_LOG_ARCHIVE + srv_log_archive_on = (ulint) innobase_log_archive; +#endif /* UNIV_LOG_ARCHIVE */ + srv_log_buffer_size = (ulint) innobase_log_buffer_size; + + srv_buf_pool_size = (ulint) innobase_buffer_pool_size; + + srv_mem_pool_size = (ulint) innobase_additional_mem_pool_size; + + srv_n_file_io_threads = (ulint) innobase_file_io_threads; + srv_n_read_io_threads = (ulint) innobase_read_io_threads; + srv_n_write_io_threads = (ulint) innobase_write_io_threads; + + srv_read_ahead &= 3; + srv_adaptive_checkpoint %= 3; + + srv_force_recovery = (ulint) innobase_force_recovery; + + srv_use_doublewrite_buf = (ibool) innobase_use_doublewrite; + srv_use_checksums = (ibool) innobase_use_checksums; + +#ifdef HAVE_LARGE_PAGES + if ((os_use_large_pages = (ibool) my_use_large_pages)) + os_large_page_size = (ulint) opt_large_page_size; +#endif + + row_rollback_on_timeout = (ibool) innobase_rollback_on_timeout; + + srv_locks_unsafe_for_binlog = (ibool) innobase_locks_unsafe_for_binlog; + + srv_max_n_open_files = (ulint) innobase_open_files; + srv_innodb_status = (ibool) innobase_create_status_file; + + srv_print_verbose_log = mysqld_embedded ? 0 : 1; + + /* Store the default charset-collation number of this MySQL + installation */ + + data_mysql_default_charset_coll = (ulint)default_charset_info->number; + + ut_a(DATA_MYSQL_LATIN1_SWEDISH_CHARSET_COLL == + my_charset_latin1.number); + ut_a(DATA_MYSQL_BINARY_CHARSET_COLL == my_charset_bin.number); + + /* Store the latin1_swedish_ci character ordering table to InnoDB. For + non-latin1_swedish_ci charsets we use the MySQL comparison functions, + and consequently we do not need to know the ordering internally in + InnoDB. */ + + ut_a(0 == strcmp(my_charset_latin1.name, "latin1_swedish_ci")); + srv_latin1_ordering = my_charset_latin1.sort_order; + + /* Since we in this module access directly the fields of a trx + struct, and due to different headers and flags it might happen that + mutex_t has a different size in this module and in InnoDB + modules, we check at run time that the size is the same in + these compilation modules. */ + + err = innobase_start_or_create_for_mysql(); + + if (err != DB_SUCCESS) { + goto mem_free_and_error; + } + +#ifdef HAVE_REPLICATION +#ifdef MYSQL_SERVER + if(innobase_overwrite_relay_log_info) { + /* If InnoDB progressed from relay-log.info, overwrite it */ + if (fname[0] == '\0') { + fprintf(stderr, + "InnoDB: something wrong with relay-info.log. InnoDB will not overwrite it.\n"); + } else if (0 != strcmp(fname, trx_sys_mysql_master_log_name) + || pos != trx_sys_mysql_master_log_pos) { + /* Overwrite relay-log.info */ + bzero((char*) &info_file, sizeof(info_file)); + fn_format(fname, relay_log_info_file, mysql_data_home, "", 4+32); + + int error = 0; + + if (!access(fname,F_OK)) { + /* exist */ + if ((info_fd = my_open(fname, O_RDWR|O_BINARY, MYF(MY_WME))) < 0) { + error = 1; + } else if (init_io_cache(&info_file, info_fd, IO_SIZE*2, + WRITE_CACHE, 0L, 0, MYF(MY_WME))) { + error = 1; + } + + if (error) { + if (info_fd >= 0) + my_close(info_fd, MYF(0)); + goto skip_overwrite; + } + } else { + error = 1; + goto skip_overwrite; + } + + char buff[FN_REFLEN*2+22*2+4], *pos; + + my_b_seek(&info_file, 0L); + pos=strmov(buff, trx_sys_mysql_relay_log_name); + *pos++='\n'; + pos=longlong2str(trx_sys_mysql_relay_log_pos, pos, 10); + *pos++='\n'; + pos=strmov(pos, trx_sys_mysql_master_log_name); + *pos++='\n'; + pos=longlong2str(trx_sys_mysql_master_log_pos, pos, 10); + *pos='\n'; + + if (my_b_write(&info_file, (uchar*) buff, (size_t) (pos-buff)+1)) + error = 1; + if (flush_io_cache(&info_file)) + error = 1; + + end_io_cache(&info_file); + if (info_fd >= 0) + my_close(info_fd, MYF(0)); +skip_overwrite: + if (error) { + fprintf(stderr, + "InnoDB: ERROR: error occured during overwriting relay-log.info.\n"); + } else { + fprintf(stderr, + "InnoDB: relay-log.info was overwritten.\n"); + } + } else { + fprintf(stderr, + "InnoDB: InnoDB and relay-log.info are synchronized. InnoDB will not overwrite it.\n"); + } + } +#endif /* MYSQL_SERVER */ +#endif /* HAVE_REPLICATION */ + + innobase_open_tables = hash_create(200); + pthread_mutex_init(&innobase_share_mutex, MY_MUTEX_INIT_FAST); + pthread_mutex_init(&prepare_commit_mutex, MY_MUTEX_INIT_FAST); + pthread_mutex_init(&commit_threads_m, MY_MUTEX_INIT_FAST); + pthread_mutex_init(&commit_cond_m, MY_MUTEX_INIT_FAST); + pthread_cond_init(&commit_cond, NULL); + innodb_inited= 1; +#ifdef MYSQL_DYNAMIC_PLUGIN + if (innobase_hton != p) { + innobase_hton = reinterpret_cast<handlerton*>(p); + *innobase_hton = *innodb_hton_ptr; + } +#endif /* MYSQL_DYNAMIC_PLUGIN */ + + /* Get the current high water mark format. */ + innobase_file_format_check = (char*) trx_sys_file_format_max_get(); + + DBUG_RETURN(FALSE); +error: + DBUG_RETURN(TRUE); +} + +/*********************************************************************** +Closes an InnoDB database. */ +static +int +innobase_end(handlerton *hton, ha_panic_function type) +/*==============*/ + /* out: TRUE if error */ +{ + int err= 0; + + DBUG_ENTER("innobase_end"); + DBUG_ASSERT(hton == innodb_hton_ptr); + +#ifdef __NETWARE__ /* some special cleanup for NetWare */ + if (nw_panic) { + set_panic_flag_for_netware(); + } +#endif + if (innodb_inited) { + + srv_fast_shutdown = (ulint) innobase_fast_shutdown; + innodb_inited = 0; + hash_table_free(innobase_open_tables); + innobase_open_tables = NULL; + if (innobase_shutdown_for_mysql() != DB_SUCCESS) { + err = 1; + } + srv_free_paths_and_sizes(); + my_free(internal_innobase_data_file_path, + MYF(MY_ALLOW_ZERO_PTR)); + pthread_mutex_destroy(&innobase_share_mutex); + pthread_mutex_destroy(&prepare_commit_mutex); + pthread_mutex_destroy(&commit_threads_m); + pthread_mutex_destroy(&commit_cond_m); + pthread_cond_destroy(&commit_cond); + } + + DBUG_RETURN(err); +} + +/******************************************************************** +Flushes InnoDB logs to disk and makes a checkpoint. Really, a commit flushes +the logs, and the name of this function should be innobase_checkpoint. */ +static +bool +innobase_flush_logs(handlerton *hton) +/*=====================*/ + /* out: TRUE if error */ +{ + bool result = 0; + + DBUG_ENTER("innobase_flush_logs"); + DBUG_ASSERT(hton == innodb_hton_ptr); + + log_buffer_flush_to_disk(); + + DBUG_RETURN(result); +} + +/******************************************************************** +Return alter table flags supported in an InnoDB database. */ +static +uint +innobase_alter_table_flags( +/*=======================*/ + uint flags) +{ + return(HA_ONLINE_ADD_INDEX_NO_WRITES + | HA_ONLINE_DROP_INDEX_NO_WRITES + | HA_ONLINE_ADD_UNIQUE_INDEX_NO_WRITES + | HA_ONLINE_DROP_UNIQUE_INDEX_NO_WRITES + | HA_ONLINE_ADD_PK_INDEX_NO_WRITES); +} + +/********************************************************************* +Commits a transaction in an InnoDB database. */ +static +void +innobase_commit_low( +/*================*/ + trx_t* trx) /* in: transaction handle */ +{ + if (trx->conc_state == TRX_NOT_STARTED) { + + return; + } + +#ifdef HAVE_REPLICATION +#ifdef MYSQL_SERVER + THD *thd=current_thd; + + if (thd && thd->slave_thread) { + /* Update the replication position info inside InnoDB. + In embedded server, does nothing. */ + const char *log_file_name, *group_relay_log_name; + ulonglong log_pos, relay_log_pos; + bool res = rpl_get_position_info(&log_file_name, &log_pos, + &group_relay_log_name, + &relay_log_pos); + if (res) { + trx->mysql_master_log_file_name = log_file_name; + trx->mysql_master_log_pos = (ib_int64_t)log_pos; + trx->mysql_relay_log_file_name = group_relay_log_name; + trx->mysql_relay_log_pos = (ib_int64_t)relay_log_pos; + } + } +#endif /* MYSQL_SERVER */ +#endif /* HAVE_REPLICATION */ + + trx_commit_for_mysql(trx); +} + +/********************************************************************* +Creates an InnoDB transaction struct for the thd if it does not yet have one. +Starts a new InnoDB transaction if a transaction is not yet started. And +assigns a new snapshot for a consistent read if the transaction does not yet +have one. */ +static +int +innobase_start_trx_and_assign_read_view( +/*====================================*/ + /* out: 0 */ + handlerton *hton, /* in: Innodb handlerton */ + THD* thd) /* in: MySQL thread handle of the user for whom + the transaction should be committed */ +{ + trx_t* trx; + + DBUG_ENTER("innobase_start_trx_and_assign_read_view"); + DBUG_ASSERT(hton == innodb_hton_ptr); + + /* Create a new trx struct for thd, if it does not yet have one */ + + trx = check_trx_exists(thd); + + /* This is just to play safe: release a possible FIFO ticket and + search latch. Since we will reserve the kernel mutex, we have to + release the search system latch first to obey the latching order. */ + + innobase_release_stat_resources(trx); + + /* If the transaction is not started yet, start it */ + + trx_start_if_not_started(trx); + + /* Assign a read view if the transaction does not have it yet */ + + trx_assign_read_view(trx); + + /* Set the MySQL flag to mark that there is an active transaction */ + + if (trx->active_trans == 0) { + innobase_register_trx_and_stmt(hton, thd); + trx->active_trans = 1; + } + + DBUG_RETURN(0); +} + +/********************************************************************* +Commits a transaction in an InnoDB database or marks an SQL statement +ended. */ +static +int +innobase_commit( +/*============*/ + /* out: 0 */ + handlerton *hton, /* in: Innodb handlerton */ + THD* thd, /* in: MySQL thread handle of the user for whom + the transaction should be committed */ + bool all) /* in: TRUE - commit transaction + FALSE - the current SQL statement ended */ +{ + trx_t* trx; + + DBUG_ENTER("innobase_commit"); + DBUG_ASSERT(hton == innodb_hton_ptr); + DBUG_PRINT("trans", ("ending transaction")); + + trx = check_trx_exists(thd); + + /* Since we will reserve the kernel mutex, we have to release + the search system latch first to obey the latching order. */ + + if (trx->has_search_latch) { + trx_search_latch_release_if_reserved(trx); + } + + /* The flag trx->active_trans is set to 1 in + + 1. ::external_lock(), + 2. ::start_stmt(), + 3. innobase_query_caching_of_table_permitted(), + 4. innobase_savepoint(), + 5. ::init_table_handle_for_HANDLER(), + 6. innobase_start_trx_and_assign_read_view(), + 7. ::transactional_table_lock() + + and it is only set to 0 in a commit or a rollback. If it is 0 we know + there cannot be resources to be freed and we could return immediately. + For the time being, we play safe and do the cleanup though there should + be nothing to clean up. */ + + if (trx->active_trans == 0 + && trx->conc_state != TRX_NOT_STARTED) { + + sql_print_error("trx->active_trans == 0, but" + " trx->conc_state != TRX_NOT_STARTED"); + } + if (all + || (!thd_test_options(thd, OPTION_NOT_AUTOCOMMIT | OPTION_BEGIN))) { + + /* We were instructed to commit the whole transaction, or + this is an SQL statement end and autocommit is on */ + + /* We need current binlog position for ibbackup to work. + Note, the position is current because of + prepare_commit_mutex */ +retry: + if (srv_commit_concurrency > 0) { + pthread_mutex_lock(&commit_cond_m); + commit_threads++; + + if (commit_threads > srv_commit_concurrency) { + commit_threads--; + pthread_cond_wait(&commit_cond, + &commit_cond_m); + pthread_mutex_unlock(&commit_cond_m); + goto retry; + } + else { + pthread_mutex_unlock(&commit_cond_m); + } + } + + trx->mysql_log_file_name = mysql_bin_log_file_name(); + trx->mysql_log_offset = (ib_int64_t) mysql_bin_log_file_pos(); + + innobase_commit_low(trx); + + if (srv_commit_concurrency > 0) { + pthread_mutex_lock(&commit_cond_m); + commit_threads--; + pthread_cond_signal(&commit_cond); + pthread_mutex_unlock(&commit_cond_m); + } + + if (trx->active_trans == 2) { + + pthread_mutex_unlock(&prepare_commit_mutex); + } + + trx->active_trans = 0; + + } else { + /* We just mark the SQL statement ended and do not do a + transaction commit */ + + /* If we had reserved the auto-inc lock for some + table in this SQL statement we release it now */ + + row_unlock_table_autoinc_for_mysql(trx); + + /* Store the current undo_no of the transaction so that we + know where to roll back if we have to roll back the next + SQL statement */ + + trx_mark_sql_stat_end(trx); + } + + trx->n_autoinc_rows = 0; /* Reset the number AUTO-INC rows required */ + + if (trx->declared_to_be_inside_innodb) { + /* Release our possible ticket in the FIFO */ + + srv_conc_force_exit_innodb(trx); + } + + /* Tell the InnoDB server that there might be work for utility + threads: */ + srv_active_wake_master_thread(); + + DBUG_RETURN(0); +} + +/********************************************************************* +Rolls back a transaction or the latest SQL statement. */ +static +int +innobase_rollback( +/*==============*/ + /* out: 0 or error number */ + handlerton *hton, /* in: Innodb handlerton */ + THD* thd, /* in: handle to the MySQL thread of the user + whose transaction should be rolled back */ + bool all) /* in: TRUE - commit transaction + FALSE - the current SQL statement ended */ +{ + int error = 0; + trx_t* trx; + + DBUG_ENTER("innobase_rollback"); + DBUG_ASSERT(hton == innodb_hton_ptr); + DBUG_PRINT("trans", ("aborting transaction")); + + trx = check_trx_exists(thd); + + /* Release a possible FIFO ticket and search latch. Since we will + reserve the kernel mutex, we have to release the search system latch + first to obey the latching order. */ + + innobase_release_stat_resources(trx); + + /* If we had reserved the auto-inc lock for some table (if + we come here to roll back the latest SQL statement) we + release it now before a possibly lengthy rollback */ + + row_unlock_table_autoinc_for_mysql(trx); + + if (all + || !thd_test_options(thd, OPTION_NOT_AUTOCOMMIT | OPTION_BEGIN)) { + + error = trx_rollback_for_mysql(trx); + trx->active_trans = 0; + } else { + error = trx_rollback_last_sql_stat_for_mysql(trx); + } + + DBUG_RETURN(convert_error_code_to_mysql(error, 0, NULL)); +} + +/********************************************************************* +Rolls back a transaction */ +static +int +innobase_rollback_trx( +/*==================*/ + /* out: 0 or error number */ + trx_t* trx) /* in: transaction */ +{ + int error = 0; + + DBUG_ENTER("innobase_rollback_trx"); + DBUG_PRINT("trans", ("aborting transaction")); + + /* Release a possible FIFO ticket and search latch. Since we will + reserve the kernel mutex, we have to release the search system latch + first to obey the latching order. */ + + innobase_release_stat_resources(trx); + + /* If we had reserved the auto-inc lock for some table (if + we come here to roll back the latest SQL statement) we + release it now before a possibly lengthy rollback */ + + row_unlock_table_autoinc_for_mysql(trx); + + error = trx_rollback_for_mysql(trx); + + DBUG_RETURN(convert_error_code_to_mysql(error, 0, NULL)); +} + +/********************************************************************* +Rolls back a transaction to a savepoint. */ +static +int +innobase_rollback_to_savepoint( +/*===========================*/ + /* out: 0 if success, HA_ERR_NO_SAVEPOINT if + no savepoint with the given name */ + handlerton *hton, /* in: Innodb handlerton */ + THD* thd, /* in: handle to the MySQL thread of the user + whose transaction should be rolled back */ + void* savepoint) /* in: savepoint data */ +{ + ib_int64_t mysql_binlog_cache_pos; + int error = 0; + trx_t* trx; + char name[64]; + + DBUG_ENTER("innobase_rollback_to_savepoint"); + DBUG_ASSERT(hton == innodb_hton_ptr); + + trx = check_trx_exists(thd); + + /* Release a possible FIFO ticket and search latch. Since we will + reserve the kernel mutex, we have to release the search system latch + first to obey the latching order. */ + + innobase_release_stat_resources(trx); + + /* TODO: use provided savepoint data area to store savepoint data */ + + longlong2str((ulint)savepoint, name, 36); + + error = (int) trx_rollback_to_savepoint_for_mysql(trx, name, + &mysql_binlog_cache_pos); + DBUG_RETURN(convert_error_code_to_mysql(error, 0, NULL)); +} + +/********************************************************************* +Release transaction savepoint name. */ +static +int +innobase_release_savepoint( +/*=======================*/ + /* out: 0 if success, HA_ERR_NO_SAVEPOINT if + no savepoint with the given name */ + handlerton* hton, /* in: handlerton for Innodb */ + THD* thd, /* in: handle to the MySQL thread of the user + whose transaction should be rolled back */ + void* savepoint) /* in: savepoint data */ +{ + int error = 0; + trx_t* trx; + char name[64]; + + DBUG_ENTER("innobase_release_savepoint"); + DBUG_ASSERT(hton == innodb_hton_ptr); + + trx = check_trx_exists(thd); + + /* TODO: use provided savepoint data area to store savepoint data */ + + longlong2str((ulint)savepoint, name, 36); + + error = (int) trx_release_savepoint_for_mysql(trx, name); + + DBUG_RETURN(convert_error_code_to_mysql(error, 0, NULL)); +} + +/********************************************************************* +Sets a transaction savepoint. */ +static +int +innobase_savepoint( +/*===============*/ + /* out: always 0, that is, always succeeds */ + handlerton* hton, /* in: handle to the Innodb handlerton */ + THD* thd, /* in: handle to the MySQL thread */ + void* savepoint) /* in: savepoint data */ +{ + int error = 0; + trx_t* trx; + + DBUG_ENTER("innobase_savepoint"); + DBUG_ASSERT(hton == innodb_hton_ptr); + + /* + In the autocommit mode there is no sense to set a savepoint + (unless we are in sub-statement), so SQL layer ensures that + this method is never called in such situation. + */ +#ifdef MYSQL_SERVER /* plugins cannot access thd->in_sub_stmt */ + DBUG_ASSERT(thd_test_options(thd, OPTION_NOT_AUTOCOMMIT | OPTION_BEGIN) || + thd->in_sub_stmt); +#endif /* MYSQL_SERVER */ + + trx = check_trx_exists(thd); + + /* Release a possible FIFO ticket and search latch. Since we will + reserve the kernel mutex, we have to release the search system latch + first to obey the latching order. */ + + innobase_release_stat_resources(trx); + + /* cannot happen outside of transaction */ + DBUG_ASSERT(trx->active_trans); + + /* TODO: use provided savepoint data area to store savepoint data */ + char name[64]; + longlong2str((ulint)savepoint,name,36); + + error = (int) trx_savepoint_for_mysql(trx, name, (ib_int64_t)0); + + DBUG_RETURN(convert_error_code_to_mysql(error, 0, NULL)); +} + +/********************************************************************* +Frees a possible InnoDB trx object associated with the current THD. */ +static +int +innobase_close_connection( +/*======================*/ + /* out: 0 or error number */ + handlerton* hton, /* in: innobase handlerton */ + THD* thd) /* in: handle to the MySQL thread of the user + whose resources should be free'd */ +{ + trx_t* trx; + + DBUG_ENTER("innobase_close_connection"); + DBUG_ASSERT(hton == innodb_hton_ptr); + trx = thd_to_trx(thd); + + ut_a(trx); + + if (trx->active_trans == 0 + && trx->conc_state != TRX_NOT_STARTED) { + + sql_print_error("trx->active_trans == 0, but" + " trx->conc_state != TRX_NOT_STARTED"); + } + + + if (trx->conc_state != TRX_NOT_STARTED && + global_system_variables.log_warnings) { + sql_print_warning( + "MySQL is closing a connection that has an active " + "InnoDB transaction. %lu row modifications will " + "roll back.", + (ulong) trx->undo_no.low); + } + + innobase_rollback_trx(trx); + + thr_local_free(trx->mysql_thread_id); + trx_free_for_mysql(trx); + + DBUG_RETURN(0); +} + + +/***************************************************************************** +** InnoDB database tables +*****************************************************************************/ + +/******************************************************************** +Get the record format from the data dictionary. */ +UNIV_INTERN +enum row_type +ha_innobase::get_row_type() const +/*=============================*/ + /* out: one of + ROW_TYPE_REDUNDANT, + ROW_TYPE_COMPACT, + ROW_TYPE_COMPRESSED, + ROW_TYPE_DYNAMIC */ +{ + if (prebuilt && prebuilt->table) { + const ulint flags = prebuilt->table->flags; + + if (UNIV_UNLIKELY(!flags)) { + return(ROW_TYPE_REDUNDANT); + } + + ut_ad(flags & DICT_TF_COMPACT); + + switch (flags & DICT_TF_FORMAT_MASK) { + case DICT_TF_FORMAT_51 << DICT_TF_FORMAT_SHIFT: + return(ROW_TYPE_COMPACT); + case DICT_TF_FORMAT_ZIP << DICT_TF_FORMAT_SHIFT: + if (flags & DICT_TF_ZSSIZE_MASK) { + return(ROW_TYPE_COMPRESSED); + } else { + return(ROW_TYPE_DYNAMIC); + } +#if DICT_TF_FORMAT_ZIP != DICT_TF_FORMAT_MAX +# error "DICT_TF_FORMAT_ZIP != DICT_TF_FORMAT_MAX" +#endif + } + } + ut_ad(0); + return(ROW_TYPE_NOT_USED); +} + + + +/******************************************************************** +Get the table flags to use for the statement. */ +UNIV_INTERN +handler::Table_flags +ha_innobase::table_flags() const +{ + /* Need to use tx_isolation here since table flags is (also) + called before prebuilt is inited. */ + ulong const tx_isolation = thd_tx_isolation(ha_thd()); + if (tx_isolation <= ISO_READ_COMMITTED) + return int_table_flags; + return int_table_flags | HA_BINLOG_STMT_CAPABLE; +} + +/******************************************************************** +Gives the file extension of an InnoDB single-table tablespace. */ +static const char* ha_innobase_exts[] = { + ".ibd", + NullS +}; + +UNIV_INTERN +const char* +ha_innobase::table_type() const +/*===========================*/ + /* out: table type */ +{ + return(innobase_hton_name); +} + +UNIV_INTERN +const char* +ha_innobase::index_type(uint) +/*=========================*/ + /* out: index type */ +{ + return("BTREE"); +} + +UNIV_INTERN +const char** +ha_innobase::bas_ext() const +/*========================*/ + /* out: file extension string */ +{ + return(ha_innobase_exts); +} + +UNIV_INTERN +ulong +ha_innobase::index_flags(uint, uint, bool) const +{ + return(HA_READ_NEXT | HA_READ_PREV | HA_READ_ORDER + | HA_READ_RANGE | HA_KEYREAD_ONLY); +} + +UNIV_INTERN +uint +ha_innobase::max_supported_keys() const +{ + return(MAX_KEY); +} + +UNIV_INTERN +uint +ha_innobase::max_supported_key_length() const +{ + /* An InnoDB page must store >= 2 keys; a secondary key record + must also contain the primary key value: max key length is + therefore set to slightly less than 1 / 4 of page size which + is 16 kB; but currently MySQL does not work with keys whose + size is > MAX_KEY_LENGTH */ + return(3500); +} + +UNIV_INTERN +const key_map* +ha_innobase::keys_to_use_for_scanning() +{ + return(&key_map_full); +} + +UNIV_INTERN +uint8 +ha_innobase::table_cache_type() +{ + return(HA_CACHE_TBL_ASKTRANSACT); +} + +UNIV_INTERN +bool +ha_innobase::primary_key_is_clustered() +{ + return(true); +} + +/********************************************************************* +Normalizes a table name string. A normalized name consists of the +database name catenated to '/' and table name. An example: +test/mytable. On Windows normalization puts both the database name and the +table name always to lower case. */ +static +void +normalize_table_name( +/*=================*/ + char* norm_name, /* out: normalized name as a + null-terminated string */ + const char* name) /* in: table name string */ +{ + char* name_ptr; + char* db_ptr; + char* ptr; + + /* Scan name from the end */ + + ptr = strend(name)-1; + + while (ptr >= name && *ptr != '\\' && *ptr != '/') { + ptr--; + } + + name_ptr = ptr + 1; + + DBUG_ASSERT(ptr > name); + + ptr--; + + while (ptr >= name && *ptr != '\\' && *ptr != '/') { + ptr--; + } + + db_ptr = ptr + 1; + + memcpy(norm_name, db_ptr, strlen(name) + 1 - (db_ptr - name)); + + norm_name[name_ptr - db_ptr - 1] = '/'; + +#ifdef __WIN__ + innobase_casedn_str(norm_name); +#endif +} + +/************************************************************************ +Set the autoinc column max value. This should only be called once from +ha_innobase::open(). Therefore there's no need for a covering lock. */ +UNIV_INTERN +ulint +ha_innobase::innobase_initialize_autoinc() +/*======================================*/ +{ + dict_index_t* index; + ulonglong auto_inc; + const char* col_name; + ulint error = DB_SUCCESS; + dict_table_t* innodb_table = prebuilt->table; + + col_name = table->found_next_number_field->field_name; + index = innobase_get_index(table->s->next_number_index); + + /* Execute SELECT MAX(col_name) FROM TABLE; */ + error = row_search_max_autoinc(index, col_name, &auto_inc); + + if (error == DB_SUCCESS) { + + /* At the this stage we dont' know the increment + or the offset, so use default inrement of 1. */ + ++auto_inc; + + dict_table_autoinc_initialize(innodb_table, auto_inc); + + } else { + ut_print_timestamp(stderr); + fprintf(stderr, " InnoDB: Error: (%lu) Couldn't read " + "the MAX(%s) autoinc value from the " + "index (%s).\n", error, col_name, index->name); + } + + return(error); +} + +/********************************************************************* +Creates and opens a handle to a table which already exists in an InnoDB +database. */ +UNIV_INTERN +int +ha_innobase::open( +/*==============*/ + /* out: 1 if error, 0 if success */ + const char* name, /* in: table name */ + int mode, /* in: not used */ + uint test_if_locked) /* in: not used */ +{ + dict_table_t* ib_table; + char norm_name[1000]; + THD* thd; + ulint retries = 0; + char* is_part = NULL; + + DBUG_ENTER("ha_innobase::open"); + + UT_NOT_USED(mode); + UT_NOT_USED(test_if_locked); + + thd = ha_thd(); + + /* Under some cases MySQL seems to call this function while + holding btr_search_latch. This breaks the latching order as + we acquire dict_sys->mutex below and leads to a deadlock. */ + if (thd != NULL) { + innobase_release_temporary_latches(ht, thd); + } + + normalize_table_name(norm_name, name); + + user_thd = NULL; + + if (!(share=get_share(name))) { + + DBUG_RETURN(1); + } + + /* Create buffers for packing the fields of a record. Why + table->reclength did not work here? Obviously, because char + fields when packed actually became 1 byte longer, when we also + stored the string length as the first byte. */ + + upd_and_key_val_buff_len = + table->s->reclength + table->s->max_key_length + + MAX_REF_PARTS * 3; + if (!(uchar*) my_multi_malloc(MYF(MY_WME), + &upd_buff, upd_and_key_val_buff_len, + &key_val_buff, upd_and_key_val_buff_len, + NullS)) { + free_share(share); + + DBUG_RETURN(1); + } + + /* We look for pattern #P# to see if the table is partitioned + MySQL table. The retry logic for partitioned tables is a + workaround for http://bugs.mysql.com/bug.php?id=33349. Look + at support issue https://support.mysql.com/view.php?id=21080 + for more details. */ + is_part = strstr(norm_name, "#P#"); +retry: + /* Get pointer to a table object in InnoDB dictionary cache */ + ib_table = dict_table_get(norm_name, TRUE); + + if (NULL == ib_table) { + if (is_part && retries < 10) { + ++retries; + os_thread_sleep(100000); + goto retry; + } + + if (is_part) { + sql_print_error("Failed to open table %s after " + "%lu attemtps.\n", norm_name, + retries); + } + + sql_print_error("Cannot find or open table %s from\n" + "the internal data dictionary of InnoDB " + "though the .frm file for the\n" + "table exists. Maybe you have deleted and " + "recreated InnoDB data\n" + "files but have forgotten to delete the " + "corresponding .frm files\n" + "of InnoDB tables, or you have moved .frm " + "files to another database?\n" + "or, the table contains indexes that this " + "version of the engine\n" + "doesn't support.\n" + "See http://dev.mysql.com/doc/refman/5.1/en/innodb-troubleshooting.html\n" + "how you can resolve the problem.\n", + norm_name); + free_share(share); + my_free(upd_buff, MYF(0)); + my_errno = ENOENT; + + DBUG_RETURN(HA_ERR_NO_SUCH_TABLE); + } + + if (ib_table->ibd_file_missing && !thd_tablespace_op(thd)) { + sql_print_error("MySQL is trying to open a table handle but " + "the .ibd file for\ntable %s does not exist.\n" + "Have you deleted the .ibd file from the " + "database directory under\nthe MySQL datadir, " + "or have you used DISCARD TABLESPACE?\n" + "See http://dev.mysql.com/doc/refman/5.1/en/innodb-troubleshooting.html\n" + "how you can resolve the problem.\n", + norm_name); + free_share(share); + my_free(upd_buff, MYF(0)); + my_errno = ENOENT; + + dict_table_decrement_handle_count(ib_table, FALSE); + DBUG_RETURN(HA_ERR_NO_SUCH_TABLE); + } + + prebuilt = row_create_prebuilt(ib_table); + + prebuilt->mysql_row_len = table->s->reclength; + prebuilt->default_rec = table->s->default_values; + ut_ad(prebuilt->default_rec); + + /* Looks like MySQL-3.23 sometimes has primary key number != 0 */ + + primary_key = table->s->primary_key; + key_used_on_scan = primary_key; + + /* Allocate a buffer for a 'row reference'. A row reference is + a string of bytes of length ref_length which uniquely specifies + a row in our table. Note that MySQL may also compare two row + references for equality by doing a simple memcmp on the strings + of length ref_length! */ + + if (!row_table_got_default_clust_index(ib_table)) { + if (primary_key >= MAX_KEY) { + sql_print_error("Table %s has a primary key in InnoDB data " + "dictionary, but not in MySQL!", name); + } + + prebuilt->clust_index_was_generated = FALSE; + + /* MySQL allocates the buffer for ref. key_info->key_length + includes space for all key columns + one byte for each column + that may be NULL. ref_length must be as exact as possible to + save space, because all row reference buffers are allocated + based on ref_length. */ + + ref_length = table->key_info[primary_key].key_length; + } else { + if (primary_key != MAX_KEY) { + sql_print_error("Table %s has no primary key in InnoDB data " + "dictionary, but has one in MySQL! If you " + "created the table with a MySQL version < " + "3.23.54 and did not define a primary key, " + "but defined a unique key with all non-NULL " + "columns, then MySQL internally treats that " + "key as the primary key. You can fix this " + "error by dump + DROP + CREATE + reimport " + "of the table.", name); + } + + prebuilt->clust_index_was_generated = TRUE; + + ref_length = DATA_ROW_ID_LEN; + + /* If we automatically created the clustered index, then + MySQL does not know about it, and MySQL must NOT be aware + of the index used on scan, to make it avoid checking if we + update the column of the index. That is why we assert below + that key_used_on_scan is the undefined value MAX_KEY. + The column is the row id in the automatical generation case, + and it will never be updated anyway. */ + + if (key_used_on_scan != MAX_KEY) { + sql_print_warning( + "Table %s key_used_on_scan is %lu even " + "though there is no primary key inside " + "InnoDB.", name, (ulong) key_used_on_scan); + } + } + + /* Index block size in InnoDB: used by MySQL in query optimization */ + stats.block_size = 16 * 1024; + + /* Init table lock structure */ + thr_lock_data_init(&share->lock,&lock,(void*) 0); + + if (prebuilt->table) { + /* We update the highest file format in the system table + space, if this table has higher file format setting. */ + + trx_sys_file_format_max_upgrade( + (const char**) &innobase_file_format_check, + dict_table_get_format(prebuilt->table)); + } + + info(HA_STATUS_NO_LOCK | HA_STATUS_VARIABLE | HA_STATUS_CONST); + + /* Only if the table has an AUTOINC column. */ + if (prebuilt->table != NULL && table->found_next_number_field != NULL) { + ulint error; + + dict_table_autoinc_lock(prebuilt->table); + + /* Since a table can already be "open" in InnoDB's internal + data dictionary, we only init the autoinc counter once, the + first time the table is loaded. We can safely reuse the + autoinc value from a previous MySQL open. */ + if (dict_table_autoinc_read(prebuilt->table) == 0) { + + error = innobase_initialize_autoinc(); + /* Should always succeed! */ + ut_a(error == DB_SUCCESS); + } + + dict_table_autoinc_unlock(prebuilt->table); + } + + DBUG_RETURN(0); +} + +UNIV_INTERN +uint +ha_innobase::max_supported_key_part_length() const +{ + return(DICT_MAX_INDEX_COL_LEN - 1); +} + +/********************************************************************** +Closes a handle to an InnoDB table. */ +UNIV_INTERN +int +ha_innobase::close(void) +/*====================*/ + /* out: 0 */ +{ + THD* thd; + + DBUG_ENTER("ha_innobase::close"); + + thd = ha_thd(); + if (thd != NULL) { + innobase_release_temporary_latches(ht, thd); + } + + row_prebuilt_free(prebuilt, FALSE); + + my_free(upd_buff, MYF(0)); + free_share(share); + + /* Tell InnoDB server that there might be work for + utility threads: */ + + srv_active_wake_master_thread(); + + DBUG_RETURN(0); +} + +/* The following accessor functions should really be inside MySQL code! */ + +/****************************************************************** +Gets field offset for a field in a table. */ +static inline +uint +get_field_offset( +/*=============*/ + /* out: offset */ + TABLE* table, /* in: MySQL table object */ + Field* field) /* in: MySQL field object */ +{ + return((uint) (field->ptr - table->record[0])); +} + +/****************************************************************** +Checks if a field in a record is SQL NULL. Uses the record format +information in table to track the null bit in record. */ +static inline +uint +field_in_record_is_null( +/*====================*/ + /* out: 1 if NULL, 0 otherwise */ + TABLE* table, /* in: MySQL table object */ + Field* field, /* in: MySQL field object */ + char* record) /* in: a row in MySQL format */ +{ + int null_offset; + + if (!field->null_ptr) { + + return(0); + } + + null_offset = (uint) ((char*) field->null_ptr + - (char*) table->record[0]); + + if (record[null_offset] & field->null_bit) { + + return(1); + } + + return(0); +} + +/****************************************************************** +Sets a field in a record to SQL NULL. Uses the record format +information in table to track the null bit in record. */ +static inline +void +set_field_in_record_to_null( +/*========================*/ + TABLE* table, /* in: MySQL table object */ + Field* field, /* in: MySQL field object */ + char* record) /* in: a row in MySQL format */ +{ + int null_offset; + + null_offset = (uint) ((char*) field->null_ptr + - (char*) table->record[0]); + + record[null_offset] = record[null_offset] | field->null_bit; +} + +/***************************************************************** +InnoDB uses this function to compare two data fields for which the data type +is such that we must use MySQL code to compare them. NOTE that the prototype +of this function is in rem0cmp.c in InnoDB source code! If you change this +function, remember to update the prototype there! */ +extern "C" UNIV_INTERN +int +innobase_mysql_cmp( +/*===============*/ + /* out: 1, 0, -1, if a is greater, + equal, less than b, respectively */ + int mysql_type, /* in: MySQL type */ + uint charset_number, /* in: number of the charset */ + const unsigned char* a, /* in: data field */ + unsigned int a_length, /* in: data field length, + not UNIV_SQL_NULL */ + const unsigned char* b, /* in: data field */ + unsigned int b_length) /* in: data field length, + not UNIV_SQL_NULL */ +{ + CHARSET_INFO* charset; + enum_field_types mysql_tp; + int ret; + + DBUG_ASSERT(a_length != UNIV_SQL_NULL); + DBUG_ASSERT(b_length != UNIV_SQL_NULL); + + mysql_tp = (enum_field_types) mysql_type; + + switch (mysql_tp) { + + case MYSQL_TYPE_BIT: + case MYSQL_TYPE_STRING: + case MYSQL_TYPE_VAR_STRING: + case MYSQL_TYPE_TINY_BLOB: + case MYSQL_TYPE_MEDIUM_BLOB: + case MYSQL_TYPE_BLOB: + case MYSQL_TYPE_LONG_BLOB: + case MYSQL_TYPE_VARCHAR: + /* Use the charset number to pick the right charset struct for + the comparison. Since the MySQL function get_charset may be + slow before Bar removes the mutex operation there, we first + look at 2 common charsets directly. */ + + if (charset_number == default_charset_info->number) { + charset = default_charset_info; + } else if (charset_number == my_charset_latin1.number) { + charset = &my_charset_latin1; + } else { + charset = get_charset(charset_number, MYF(MY_WME)); + + if (charset == NULL) { + sql_print_error("InnoDB needs charset %lu for doing " + "a comparison, but MySQL cannot " + "find that charset.", + (ulong) charset_number); + ut_a(0); + } + } + + /* Starting from 4.1.3, we use strnncollsp() in comparisons of + non-latin1_swedish_ci strings. NOTE that the collation order + changes then: 'b\0\0...' is ordered BEFORE 'b ...'. Users + having indexes on such data need to rebuild their tables! */ + + ret = charset->coll->strnncollsp(charset, + a, a_length, + b, b_length, 0); + if (ret < 0) { + return(-1); + } else if (ret > 0) { + return(1); + } else { + return(0); + } + default: + ut_error; + } + + return(0); +} + +/****************************************************************** +Converts a MySQL type to an InnoDB type. Note that this function returns +the 'mtype' of InnoDB. InnoDB differentiates between MySQL's old <= 4.1 +VARCHAR and the new true VARCHAR in >= 5.0.3 by the 'prtype'. */ +extern "C" UNIV_INTERN +ulint +get_innobase_type_from_mysql_type( +/*==============================*/ + /* out: DATA_BINARY, + DATA_VARCHAR, ... */ + ulint* unsigned_flag, /* out: DATA_UNSIGNED if an + 'unsigned type'; + at least ENUM and SET, + and unsigned integer + types are 'unsigned types' */ + const void* f) /* in: MySQL Field */ +{ + const class Field* field = reinterpret_cast<const class Field*>(f); + + /* The following asserts try to check that the MySQL type code fits in + 8 bits: this is used in ibuf and also when DATA_NOT_NULL is ORed to + the type */ + + DBUG_ASSERT((ulint)MYSQL_TYPE_STRING < 256); + DBUG_ASSERT((ulint)MYSQL_TYPE_VAR_STRING < 256); + DBUG_ASSERT((ulint)MYSQL_TYPE_DOUBLE < 256); + DBUG_ASSERT((ulint)MYSQL_TYPE_FLOAT < 256); + DBUG_ASSERT((ulint)MYSQL_TYPE_DECIMAL < 256); + + if (field->flags & UNSIGNED_FLAG) { + + *unsigned_flag = DATA_UNSIGNED; + } else { + *unsigned_flag = 0; + } + + if (field->real_type() == MYSQL_TYPE_ENUM + || field->real_type() == MYSQL_TYPE_SET) { + + /* MySQL has field->type() a string type for these, but the + data is actually internally stored as an unsigned integer + code! */ + + *unsigned_flag = DATA_UNSIGNED; /* MySQL has its own unsigned + flag set to zero, even though + internally this is an unsigned + integer type */ + return(DATA_INT); + } + + switch (field->type()) { + /* NOTE that we only allow string types in DATA_MYSQL and + DATA_VARMYSQL */ + case MYSQL_TYPE_VAR_STRING: /* old <= 4.1 VARCHAR */ + case MYSQL_TYPE_VARCHAR: /* new >= 5.0.3 true VARCHAR */ + if (field->binary()) { + return(DATA_BINARY); + } else if (strcmp( + field->charset()->name, + "latin1_swedish_ci") == 0) { + return(DATA_VARCHAR); + } else { + return(DATA_VARMYSQL); + } + case MYSQL_TYPE_BIT: + case MYSQL_TYPE_STRING: if (field->binary()) { + + return(DATA_FIXBINARY); + } else if (strcmp( + field->charset()->name, + "latin1_swedish_ci") == 0) { + return(DATA_CHAR); + } else { + return(DATA_MYSQL); + } + case MYSQL_TYPE_NEWDECIMAL: + return(DATA_FIXBINARY); + case MYSQL_TYPE_LONG: + case MYSQL_TYPE_LONGLONG: + case MYSQL_TYPE_TINY: + case MYSQL_TYPE_SHORT: + case MYSQL_TYPE_INT24: + case MYSQL_TYPE_DATE: + case MYSQL_TYPE_DATETIME: + case MYSQL_TYPE_YEAR: + case MYSQL_TYPE_NEWDATE: + case MYSQL_TYPE_TIME: + case MYSQL_TYPE_TIMESTAMP: + return(DATA_INT); + case MYSQL_TYPE_FLOAT: + return(DATA_FLOAT); + case MYSQL_TYPE_DOUBLE: + return(DATA_DOUBLE); + case MYSQL_TYPE_DECIMAL: + return(DATA_DECIMAL); + case MYSQL_TYPE_GEOMETRY: + case MYSQL_TYPE_TINY_BLOB: + case MYSQL_TYPE_MEDIUM_BLOB: + case MYSQL_TYPE_BLOB: + case MYSQL_TYPE_LONG_BLOB: + return(DATA_BLOB); + default: + ut_error; + } + + return(0); +} + +/*********************************************************************** +Writes an unsigned integer value < 64k to 2 bytes, in the little-endian +storage format. */ +static inline +void +innobase_write_to_2_little_endian( +/*==============================*/ + byte* buf, /* in: where to store */ + ulint val) /* in: value to write, must be < 64k */ +{ + ut_a(val < 256 * 256); + + buf[0] = (byte)(val & 0xFF); + buf[1] = (byte)(val / 256); +} + +/*********************************************************************** +Reads an unsigned integer value < 64k from 2 bytes, in the little-endian +storage format. */ +static inline +uint +innobase_read_from_2_little_endian( +/*===============================*/ + /* out: value */ + const uchar* buf) /* in: from where to read */ +{ + return (uint) ((ulint)(buf[0]) + 256 * ((ulint)(buf[1]))); +} + +/*********************************************************************** +Stores a key value for a row to a buffer. */ +UNIV_INTERN +uint +ha_innobase::store_key_val_for_row( +/*===============================*/ + /* out: key value length as stored in buff */ + uint keynr, /* in: key number */ + char* buff, /* in/out: buffer for the key value (in MySQL + format) */ + uint buff_len,/* in: buffer length */ + const uchar* record)/* in: row in MySQL format */ +{ + KEY* key_info = table->key_info + keynr; + KEY_PART_INFO* key_part = key_info->key_part; + KEY_PART_INFO* end = key_part + key_info->key_parts; + char* buff_start = buff; + enum_field_types mysql_type; + Field* field; + ibool is_null; + + DBUG_ENTER("store_key_val_for_row"); + + /* The format for storing a key field in MySQL is the following: + + 1. If the column can be NULL, then in the first byte we put 1 if the + field value is NULL, 0 otherwise. + + 2. If the column is of a BLOB type (it must be a column prefix field + in this case), then we put the length of the data in the field to the + next 2 bytes, in the little-endian format. If the field is SQL NULL, + then these 2 bytes are set to 0. Note that the length of data in the + field is <= column prefix length. + + 3. In a column prefix field, prefix_len next bytes are reserved for + data. In a normal field the max field length next bytes are reserved + for data. For a VARCHAR(n) the max field length is n. If the stored + value is the SQL NULL then these data bytes are set to 0. + + 4. We always use a 2 byte length for a true >= 5.0.3 VARCHAR. Note that + in the MySQL row format, the length is stored in 1 or 2 bytes, + depending on the maximum allowed length. But in the MySQL key value + format, the length always takes 2 bytes. + + We have to zero-fill the buffer so that MySQL is able to use a + simple memcmp to compare two key values to determine if they are + equal. MySQL does this to compare contents of two 'ref' values. */ + + bzero(buff, buff_len); + + for (; key_part != end; key_part++) { + is_null = FALSE; + + if (key_part->null_bit) { + if (record[key_part->null_offset] + & key_part->null_bit) { + *buff = 1; + is_null = TRUE; + } else { + *buff = 0; + } + buff++; + } + + field = key_part->field; + mysql_type = field->type(); + + if (mysql_type == MYSQL_TYPE_VARCHAR) { + /* >= 5.0.3 true VARCHAR */ + ulint lenlen; + ulint len; + const byte* data; + ulint key_len; + ulint true_len; + CHARSET_INFO* cs; + int error=0; + + key_len = key_part->length; + + if (is_null) { + buff += key_len + 2; + + continue; + } + cs = field->charset(); + + lenlen = (ulint) + (((Field_varstring*)field)->length_bytes); + + data = row_mysql_read_true_varchar(&len, + (byte*) (record + + (ulint)get_field_offset(table, field)), + lenlen); + + true_len = len; + + /* For multi byte character sets we need to calculate + the true length of the key */ + + if (len > 0 && cs->mbmaxlen > 1) { + true_len = (ulint) cs->cset->well_formed_len(cs, + (const char *) data, + (const char *) data + len, + (uint) (key_len / + cs->mbmaxlen), + &error); + } + + /* In a column prefix index, we may need to truncate + the stored value: */ + + if (true_len > key_len) { + true_len = key_len; + } + + /* The length in a key value is always stored in 2 + bytes */ + + row_mysql_store_true_var_len((byte*)buff, true_len, 2); + buff += 2; + + memcpy(buff, data, true_len); + + /* Note that we always reserve the maximum possible + length of the true VARCHAR in the key value, though + only len first bytes after the 2 length bytes contain + actual data. The rest of the space was reset to zero + in the bzero() call above. */ + + buff += key_len; + + } else if (mysql_type == MYSQL_TYPE_TINY_BLOB + || mysql_type == MYSQL_TYPE_MEDIUM_BLOB + || mysql_type == MYSQL_TYPE_BLOB + || mysql_type == MYSQL_TYPE_LONG_BLOB) { + + CHARSET_INFO* cs; + ulint key_len; + ulint true_len; + int error=0; + ulint blob_len; + const byte* blob_data; + + ut_a(key_part->key_part_flag & HA_PART_KEY_SEG); + + key_len = key_part->length; + + if (is_null) { + buff += key_len + 2; + + continue; + } + + cs = field->charset(); + + blob_data = row_mysql_read_blob_ref(&blob_len, + (byte*) (record + + (ulint)get_field_offset(table, field)), + (ulint) field->pack_length()); + + true_len = blob_len; + + ut_a(get_field_offset(table, field) + == key_part->offset); + + /* For multi byte character sets we need to calculate + the true length of the key */ + + if (blob_len > 0 && cs->mbmaxlen > 1) { + true_len = (ulint) cs->cset->well_formed_len(cs, + (const char *) blob_data, + (const char *) blob_data + + blob_len, + (uint) (key_len / + cs->mbmaxlen), + &error); + } + + /* All indexes on BLOB and TEXT are column prefix + indexes, and we may need to truncate the data to be + stored in the key value: */ + + if (true_len > key_len) { + true_len = key_len; + } + + /* MySQL reserves 2 bytes for the length and the + storage of the number is little-endian */ + + innobase_write_to_2_little_endian( + (byte*)buff, true_len); + buff += 2; + + memcpy(buff, blob_data, true_len); + + /* Note that we always reserve the maximum possible + length of the BLOB prefix in the key value. */ + + buff += key_len; + } else { + /* Here we handle all other data types except the + true VARCHAR, BLOB and TEXT. Note that the column + value we store may be also in a column prefix + index. */ + + CHARSET_INFO* cs; + ulint true_len; + ulint key_len; + const uchar* src_start; + int error=0; + enum_field_types real_type; + + key_len = key_part->length; + + if (is_null) { + buff += key_len; + + continue; + } + + src_start = record + key_part->offset; + real_type = field->real_type(); + true_len = key_len; + + /* Character set for the field is defined only + to fields whose type is string and real field + type is not enum or set. For these fields check + if character set is multi byte. */ + + if (real_type != MYSQL_TYPE_ENUM + && real_type != MYSQL_TYPE_SET + && ( mysql_type == MYSQL_TYPE_VAR_STRING + || mysql_type == MYSQL_TYPE_STRING)) { + + cs = field->charset(); + + /* For multi byte character sets we need to + calculate the true length of the key */ + + if (key_len > 0 && cs->mbmaxlen > 1) { + + true_len = (ulint) + cs->cset->well_formed_len(cs, + (const char *)src_start, + (const char *)src_start + + key_len, + (uint) (key_len / + cs->mbmaxlen), + &error); + } + } + + memcpy(buff, src_start, true_len); + buff += true_len; + + /* Pad the unused space with spaces. Note that no + padding is ever needed for UCS-2 because in MySQL, + all UCS2 characters are 2 bytes, as MySQL does not + support surrogate pairs, which are needed to represent + characters in the range U+10000 to U+10FFFF. */ + + if (true_len < key_len) { + ulint pad_len = key_len - true_len; + memset(buff, ' ', pad_len); + buff += pad_len; + } + } + } + + ut_a(buff <= buff_start + buff_len); + + DBUG_RETURN((uint)(buff - buff_start)); +} + +/****************************************************************** +Builds a 'template' to the prebuilt struct. The template is used in fast +retrieval of just those column values MySQL needs in its processing. */ +static +void +build_template( +/*===========*/ + row_prebuilt_t* prebuilt, /* in/out: prebuilt struct */ + THD* thd, /* in: current user thread, used + only if templ_type is + ROW_MYSQL_REC_FIELDS */ + TABLE* table, /* in: MySQL table */ + uint templ_type) /* in: ROW_MYSQL_WHOLE_ROW or + ROW_MYSQL_REC_FIELDS */ +{ + dict_index_t* index; + dict_index_t* clust_index; + mysql_row_templ_t* templ; + Field* field; + ulint n_fields; + ulint n_requested_fields = 0; + ibool fetch_all_in_key = FALSE; + ibool fetch_primary_key_cols = FALSE; + ulint i; + /* byte offset of the end of last requested column */ + ulint mysql_prefix_len = 0; + + if (prebuilt->select_lock_type == LOCK_X) { + /* We always retrieve the whole clustered index record if we + use exclusive row level locks, for example, if the read is + done in an UPDATE statement. */ + + templ_type = ROW_MYSQL_WHOLE_ROW; + } + + if (templ_type == ROW_MYSQL_REC_FIELDS) { + if (prebuilt->hint_need_to_fetch_extra_cols + == ROW_RETRIEVE_ALL_COLS) { + + /* We know we must at least fetch all columns in the + key, or all columns in the table */ + + if (prebuilt->read_just_key) { + /* MySQL has instructed us that it is enough + to fetch the columns in the key; looks like + MySQL can set this flag also when there is + only a prefix of the column in the key: in + that case we retrieve the whole column from + the clustered index */ + + fetch_all_in_key = TRUE; + } else { + templ_type = ROW_MYSQL_WHOLE_ROW; + } + } else if (prebuilt->hint_need_to_fetch_extra_cols + == ROW_RETRIEVE_PRIMARY_KEY) { + /* We must at least fetch all primary key cols. Note + that if the clustered index was internally generated + by InnoDB on the row id (no primary key was + defined), then row_search_for_mysql() will always + retrieve the row id to a special buffer in the + prebuilt struct. */ + + fetch_primary_key_cols = TRUE; + } + } + + clust_index = dict_table_get_first_index(prebuilt->table); + + if (templ_type == ROW_MYSQL_REC_FIELDS) { + index = prebuilt->index; + } else { + index = clust_index; + } + + if (index == clust_index) { + prebuilt->need_to_access_clustered = TRUE; + } else { + prebuilt->need_to_access_clustered = FALSE; + /* Below we check column by column if we need to access + the clustered index */ + } + + n_fields = (ulint)table->s->fields; /* number of columns */ + + if (!prebuilt->mysql_template) { + prebuilt->mysql_template = (mysql_row_templ_t*) + mem_alloc(n_fields * sizeof(mysql_row_templ_t)); + } + + prebuilt->template_type = templ_type; + prebuilt->null_bitmap_len = table->s->null_bytes; + + prebuilt->templ_contains_blob = FALSE; + + /* Note that in InnoDB, i is the column number. MySQL calls columns + 'fields'. */ + for (i = 0; i < n_fields; i++) { + templ = prebuilt->mysql_template + n_requested_fields; + field = table->field[i]; + + if (UNIV_LIKELY(templ_type == ROW_MYSQL_REC_FIELDS)) { + /* Decide which columns we should fetch + and which we can skip. */ + register const ibool index_contains_field = + dict_index_contains_col_or_prefix(index, i); + + if (!index_contains_field && prebuilt->read_just_key) { + /* If this is a 'key read', we do not need + columns that are not in the key */ + + goto skip_field; + } + + if (index_contains_field && fetch_all_in_key) { + /* This field is needed in the query */ + + goto include_field; + } + + if (bitmap_is_set(table->read_set, i) || + bitmap_is_set(table->write_set, i)) { + /* This field is needed in the query */ + + goto include_field; + } + + if (fetch_primary_key_cols + && dict_table_col_in_clustered_key( + index->table, i)) { + /* This field is needed in the query */ + + goto include_field; + } + + /* This field is not needed in the query, skip it */ + + goto skip_field; + } +include_field: + n_requested_fields++; + + templ->col_no = i; + + if (index == clust_index) { + templ->rec_field_no = dict_col_get_clust_pos( + &index->table->cols[i], index); + } else { + templ->rec_field_no = dict_index_get_nth_col_pos( + index, i); + } + + if (templ->rec_field_no == ULINT_UNDEFINED) { + prebuilt->need_to_access_clustered = TRUE; + } + + if (field->null_ptr) { + templ->mysql_null_byte_offset = + (ulint) ((char*) field->null_ptr + - (char*) table->record[0]); + + templ->mysql_null_bit_mask = (ulint) field->null_bit; + } else { + templ->mysql_null_bit_mask = 0; + } + + templ->mysql_col_offset = (ulint) + get_field_offset(table, field); + + templ->mysql_col_len = (ulint) field->pack_length(); + if (mysql_prefix_len < templ->mysql_col_offset + + templ->mysql_col_len) { + mysql_prefix_len = templ->mysql_col_offset + + templ->mysql_col_len; + } + templ->type = index->table->cols[i].mtype; + templ->mysql_type = (ulint)field->type(); + + if (templ->mysql_type == DATA_MYSQL_TRUE_VARCHAR) { + templ->mysql_length_bytes = (ulint) + (((Field_varstring*)field)->length_bytes); + } + + templ->charset = dtype_get_charset_coll( + index->table->cols[i].prtype); + templ->mbminlen = index->table->cols[i].mbminlen; + templ->mbmaxlen = index->table->cols[i].mbmaxlen; + templ->is_unsigned = index->table->cols[i].prtype + & DATA_UNSIGNED; + if (templ->type == DATA_BLOB) { + prebuilt->templ_contains_blob = TRUE; + } +skip_field: + ; + } + + prebuilt->n_template = n_requested_fields; + prebuilt->mysql_prefix_len = mysql_prefix_len; + + if (index != clust_index && prebuilt->need_to_access_clustered) { + /* Change rec_field_no's to correspond to the clustered index + record */ + for (i = 0; i < n_requested_fields; i++) { + templ = prebuilt->mysql_template + i; + + templ->rec_field_no = dict_col_get_clust_pos( + &index->table->cols[templ->col_no], + clust_index); + } + } +} + +/************************************************************************ +Get the upper limit of the MySQL integral and floating-point type. */ +UNIV_INTERN +ulonglong +ha_innobase::innobase_get_int_col_max_value( +/*========================================*/ + const Field* field) +{ + ulonglong max_value = 0; + + switch(field->key_type()) { + /* TINY */ + case HA_KEYTYPE_BINARY: + max_value = 0xFFULL; + break; + case HA_KEYTYPE_INT8: + max_value = 0x7FULL; + break; + /* SHORT */ + case HA_KEYTYPE_USHORT_INT: + max_value = 0xFFFFULL; + break; + case HA_KEYTYPE_SHORT_INT: + max_value = 0x7FFFULL; + break; + /* MEDIUM */ + case HA_KEYTYPE_UINT24: + max_value = 0xFFFFFFULL; + break; + case HA_KEYTYPE_INT24: + max_value = 0x7FFFFFULL; + break; + /* LONG */ + case HA_KEYTYPE_ULONG_INT: + max_value = 0xFFFFFFFFULL; + break; + case HA_KEYTYPE_LONG_INT: + max_value = 0x7FFFFFFFULL; + break; + /* BIG */ + case HA_KEYTYPE_ULONGLONG: + max_value = 0xFFFFFFFFFFFFFFFFULL; + break; + case HA_KEYTYPE_LONGLONG: + max_value = 0x7FFFFFFFFFFFFFFFULL; + break; + case HA_KEYTYPE_FLOAT: + /* We use the maximum as per IEEE754-2008 standard, 2^24 */ + max_value = 0x1000000ULL; + break; + case HA_KEYTYPE_DOUBLE: + /* We use the maximum as per IEEE754-2008 standard, 2^53 */ + max_value = 0x20000000000000ULL; + break; + default: + ut_error; + } + + return(max_value); +} + +/************************************************************************ +This special handling is really to overcome the limitations of MySQL's +binlogging. We need to eliminate the non-determinism that will arise in +INSERT ... SELECT type of statements, since MySQL binlog only stores the +min value of the autoinc interval. Once that is fixed we can get rid of +the special lock handling.*/ +UNIV_INTERN +ulint +ha_innobase::innobase_lock_autoinc(void) +/*====================================*/ + /* out: DB_SUCCESS if all OK else + error code */ +{ + ulint error = DB_SUCCESS; + + switch (innobase_autoinc_lock_mode) { + case AUTOINC_NO_LOCKING: + /* Acquire only the AUTOINC mutex. */ + dict_table_autoinc_lock(prebuilt->table); + break; + + case AUTOINC_NEW_STYLE_LOCKING: + /* For simple (single/multi) row INSERTs, we fallback to the + old style only if another transaction has already acquired + the AUTOINC lock on behalf of a LOAD FILE or INSERT ... SELECT + etc. type of statement. */ + if (thd_sql_command(user_thd) == SQLCOM_INSERT + || thd_sql_command(user_thd) == SQLCOM_REPLACE) { + dict_table_t* table = prebuilt->table; + + /* Acquire the AUTOINC mutex. */ + dict_table_autoinc_lock(table); + + /* We need to check that another transaction isn't + already holding the AUTOINC lock on the table. */ + if (table->n_waiting_or_granted_auto_inc_locks) { + /* Release the mutex to avoid deadlocks. */ + dict_table_autoinc_unlock(table); + } else { + break; + } + } + /* Fall through to old style locking. */ + + case AUTOINC_OLD_STYLE_LOCKING: + error = row_lock_table_autoinc_for_mysql(prebuilt); + + if (error == DB_SUCCESS) { + + /* Acquire the AUTOINC mutex. */ + dict_table_autoinc_lock(prebuilt->table); + } + break; + + default: + ut_error; + } + + return(ulong(error)); +} + +/************************************************************************ +Reset the autoinc value in the table.*/ +UNIV_INTERN +ulint +ha_innobase::innobase_reset_autoinc( +/*================================*/ + /* out: DB_SUCCESS if all went well + else error code */ + ulonglong autoinc) /* in: value to store */ +{ + ulint error; + + error = innobase_lock_autoinc(); + + if (error == DB_SUCCESS) { + + dict_table_autoinc_initialize(prebuilt->table, autoinc); + + dict_table_autoinc_unlock(prebuilt->table); + } + + return(ulong(error)); +} + +/************************************************************************ +Store the autoinc value in the table. The autoinc value is only set if +it's greater than the existing autoinc value in the table.*/ +UNIV_INTERN +ulint +ha_innobase::innobase_set_max_autoinc( +/*==================================*/ + /* out: DB_SUCCES if all went well + else error code */ + ulonglong auto_inc) /* in: value to store */ +{ + ulint error; + + error = innobase_lock_autoinc(); + + if (error == DB_SUCCESS) { + + dict_table_autoinc_update_if_greater(prebuilt->table, auto_inc); + + dict_table_autoinc_unlock(prebuilt->table); + } + + return(ulong(error)); +} + +/************************************************************************ +Stores a row in an InnoDB database, to the table specified in this +handle. */ +UNIV_INTERN +int +ha_innobase::write_row( +/*===================*/ + /* out: error code */ + uchar* record) /* in: a row in MySQL format */ +{ + ulint error = 0; + int error_result= 0; + ibool auto_inc_used= FALSE; + ulint sql_command; + trx_t* trx = thd_to_trx(user_thd); + + DBUG_ENTER("ha_innobase::write_row"); + + if (prebuilt->trx != trx) { + sql_print_error("The transaction object for the table handle is at " + "%p, but for the current thread it is at %p", + (const void*) prebuilt->trx, (const void*) trx); + + fputs("InnoDB: Dump of 200 bytes around prebuilt: ", stderr); + ut_print_buf(stderr, ((const byte*)prebuilt) - 100, 200); + fputs("\n" + "InnoDB: Dump of 200 bytes around ha_data: ", + stderr); + ut_print_buf(stderr, ((const byte*) trx) - 100, 200); + putc('\n', stderr); + ut_error; + } + + ha_statistic_increment(&SSV::ha_write_count); + + if (table->timestamp_field_type & TIMESTAMP_AUTO_SET_ON_INSERT) + table->timestamp_field->set_time(); + + sql_command = thd_sql_command(user_thd); + + if ((sql_command == SQLCOM_ALTER_TABLE + || sql_command == SQLCOM_OPTIMIZE + || sql_command == SQLCOM_CREATE_INDEX + || sql_command == SQLCOM_DROP_INDEX) + && num_write_row >= 10000) { + /* ALTER TABLE is COMMITted at every 10000 copied rows. + The IX table lock for the original table has to be re-issued. + As this method will be called on a temporary table where the + contents of the original table is being copied to, it is + a bit tricky to determine the source table. The cursor + position in the source table need not be adjusted after the + intermediate COMMIT, since writes by other transactions are + being blocked by a MySQL table lock TL_WRITE_ALLOW_READ. */ + + dict_table_t* src_table; + enum lock_mode mode; + + num_write_row = 0; + + /* Commit the transaction. This will release the table + locks, so they have to be acquired again. */ + + /* Altering an InnoDB table */ + /* Get the source table. */ + src_table = lock_get_src_table( + prebuilt->trx, prebuilt->table, &mode); + if (!src_table) { +no_commit: + /* Unknown situation: do not commit */ + /* + ut_print_timestamp(stderr); + fprintf(stderr, + " InnoDB: ALTER TABLE is holding lock" + " on %lu tables!\n", + prebuilt->trx->mysql_n_tables_locked); + */ + ; + } else if (src_table == prebuilt->table) { + /* Source table is not in InnoDB format: + no need to re-acquire locks on it. */ + + /* Altering to InnoDB format */ + innobase_commit(ht, user_thd, 1); + /* Note that this transaction is still active. */ + prebuilt->trx->active_trans = 1; + /* We will need an IX lock on the destination table. */ + prebuilt->sql_stat_start = TRUE; + } else { + /* Ensure that there are no other table locks than + LOCK_IX and LOCK_AUTO_INC on the destination table. */ + + if (!lock_is_table_exclusive(prebuilt->table, + prebuilt->trx)) { + goto no_commit; + } + + /* Commit the transaction. This will release the table + locks, so they have to be acquired again. */ + innobase_commit(ht, user_thd, 1); + /* Note that this transaction is still active. */ + prebuilt->trx->active_trans = 1; + /* Re-acquire the table lock on the source table. */ + row_lock_table_for_mysql(prebuilt, src_table, mode); + /* We will need an IX lock on the destination table. */ + prebuilt->sql_stat_start = TRUE; + } + } + + num_write_row++; + + /* This is the case where the table has an auto-increment column */ + if (table->next_number_field && record == table->record[0]) { + + /* Reset the error code before calling + innobase_get_auto_increment(). */ + prebuilt->autoinc_error = DB_SUCCESS; + + if ((error = update_auto_increment())) { + + /* We don't want to mask autoinc overflow errors. */ + if (prebuilt->autoinc_error != DB_SUCCESS) { + error = (int) prebuilt->autoinc_error; + + goto report_error; + } + + /* MySQL errors are passed straight back. */ + error_result = (int) error; + goto func_exit; + } + + auto_inc_used = TRUE; + } + + if (prebuilt->mysql_template == NULL + || prebuilt->template_type != ROW_MYSQL_WHOLE_ROW) { + + /* Build the template used in converting quickly between + the two database formats */ + + build_template(prebuilt, NULL, table, ROW_MYSQL_WHOLE_ROW); + } + + innodb_srv_conc_enter_innodb(prebuilt->trx); + + error = row_insert_for_mysql((byte*) record, prebuilt); + + /* Handle duplicate key errors */ + if (auto_inc_used) { + ulint err; + ulonglong auto_inc; + ulonglong col_max_value; + + /* Note the number of rows processed for this statement, used + by get_auto_increment() to determine the number of AUTO-INC + values to reserve. This is only useful for a mult-value INSERT + and is a statement level counter.*/ + if (trx->n_autoinc_rows > 0) { + --trx->n_autoinc_rows; + } + + /* We need the upper limit of the col type to check for + whether we update the table autoinc counter or not. */ + col_max_value = innobase_get_int_col_max_value( + table->next_number_field); + + /* Get the value that MySQL attempted to store in the table.*/ + auto_inc = table->next_number_field->val_int(); + + switch (error) { + case DB_DUPLICATE_KEY: + + /* A REPLACE command and LOAD DATA INFILE REPLACE + handle a duplicate key error themselves, but we + must update the autoinc counter if we are performing + those statements. */ + + switch (sql_command) { + case SQLCOM_LOAD: + if ((trx->duplicates + & (TRX_DUP_IGNORE | TRX_DUP_REPLACE))) { + + goto set_max_autoinc; + } + break; + + case SQLCOM_REPLACE: + case SQLCOM_INSERT_SELECT: + case SQLCOM_REPLACE_SELECT: + goto set_max_autoinc; + + default: + break; + } + + break; + + case DB_SUCCESS: + /* If the actual value inserted is greater than + the upper limit of the interval, then we try and + update the table upper limit. Note: last_value + will be 0 if get_auto_increment() was not called.*/ + + if (auto_inc <= col_max_value + && auto_inc >= prebuilt->autoinc_last_value) { +set_max_autoinc: + ut_a(prebuilt->autoinc_increment > 0); + + ulonglong need; + ulonglong offset; + + offset = prebuilt->autoinc_offset; + need = prebuilt->autoinc_increment; + + auto_inc = innobase_next_autoinc( + auto_inc, need, offset, col_max_value); + + err = innobase_set_max_autoinc(auto_inc); + + if (err != DB_SUCCESS) { + error = err; + } + } + break; + } + } + + innodb_srv_conc_exit_innodb(prebuilt->trx); + +report_error: + error_result = convert_error_code_to_mysql((int) error, + prebuilt->table->flags, + user_thd); + +func_exit: + innobase_active_small(); + + DBUG_RETURN(error_result); +} + +/************************************************************************** +Checks which fields have changed in a row and stores information +of them to an update vector. */ +static +int +calc_row_difference( +/*================*/ + /* out: error number or 0 */ + upd_t* uvect, /* in/out: update vector */ + uchar* old_row, /* in: old row in MySQL format */ + uchar* new_row, /* in: new row in MySQL format */ + struct st_table* table, /* in: table in MySQL data + dictionary */ + uchar* upd_buff, /* in: buffer to use */ + ulint buff_len, /* in: buffer length */ + row_prebuilt_t* prebuilt, /* in: InnoDB prebuilt struct */ + THD* thd) /* in: user thread */ +{ + uchar* original_upd_buff = upd_buff; + Field* field; + enum_field_types field_mysql_type; + uint n_fields; + ulint o_len; + ulint n_len; + ulint col_pack_len; + const byte* new_mysql_row_col; + const byte* o_ptr; + const byte* n_ptr; + byte* buf; + upd_field_t* ufield; + ulint col_type; + ulint n_changed = 0; + dfield_t dfield; + dict_index_t* clust_index; + uint i; + + n_fields = table->s->fields; + clust_index = dict_table_get_first_index(prebuilt->table); + + /* We use upd_buff to convert changed fields */ + buf = (byte*) upd_buff; + + for (i = 0; i < n_fields; i++) { + field = table->field[i]; + + o_ptr = (const byte*) old_row + get_field_offset(table, field); + n_ptr = (const byte*) new_row + get_field_offset(table, field); + + /* Use new_mysql_row_col and col_pack_len save the values */ + + new_mysql_row_col = n_ptr; + col_pack_len = field->pack_length(); + + o_len = col_pack_len; + n_len = col_pack_len; + + /* We use o_ptr and n_ptr to dig up the actual data for + comparison. */ + + field_mysql_type = field->type(); + + col_type = prebuilt->table->cols[i].mtype; + + switch (col_type) { + + case DATA_BLOB: + o_ptr = row_mysql_read_blob_ref(&o_len, o_ptr, o_len); + n_ptr = row_mysql_read_blob_ref(&n_len, n_ptr, n_len); + + break; + + case DATA_VARCHAR: + case DATA_BINARY: + case DATA_VARMYSQL: + if (field_mysql_type == MYSQL_TYPE_VARCHAR) { + /* This is a >= 5.0.3 type true VARCHAR where + the real payload data length is stored in + 1 or 2 bytes */ + + o_ptr = row_mysql_read_true_varchar( + &o_len, o_ptr, + (ulint) + (((Field_varstring*)field)->length_bytes)); + + n_ptr = row_mysql_read_true_varchar( + &n_len, n_ptr, + (ulint) + (((Field_varstring*)field)->length_bytes)); + } + + break; + default: + ; + } + + if (field->null_ptr) { + if (field_in_record_is_null(table, field, + (char*) old_row)) { + o_len = UNIV_SQL_NULL; + } + + if (field_in_record_is_null(table, field, + (char*) new_row)) { + n_len = UNIV_SQL_NULL; + } + } + + if (o_len != n_len || (o_len != UNIV_SQL_NULL && + 0 != memcmp(o_ptr, n_ptr, o_len))) { + /* The field has changed */ + + ufield = uvect->fields + n_changed; + + /* Let us use a dummy dfield to make the conversion + from the MySQL column format to the InnoDB format */ + + dict_col_copy_type(prebuilt->table->cols + i, + dfield_get_type(&dfield)); + + if (n_len != UNIV_SQL_NULL) { + buf = row_mysql_store_col_in_innobase_format( + &dfield, + (byte*)buf, + TRUE, + new_mysql_row_col, + col_pack_len, + dict_table_is_comp(prebuilt->table)); + dfield_copy_data(&ufield->new_val, &dfield); + } else { + dfield_set_null(&ufield->new_val); + } + + ufield->exp = NULL; + ufield->orig_len = 0; + ufield->field_no = dict_col_get_clust_pos( + &prebuilt->table->cols[i], clust_index); + n_changed++; + } + } + + uvect->n_fields = n_changed; + uvect->info_bits = 0; + + ut_a(buf <= (byte*)original_upd_buff + buff_len); + + return(0); +} + +/************************************************************************** +Updates a row given as a parameter to a new value. Note that we are given +whole rows, not just the fields which are updated: this incurs some +overhead for CPU when we check which fields are actually updated. +TODO: currently InnoDB does not prevent the 'Halloween problem': +in a searched update a single row can get updated several times +if its index columns are updated! */ +UNIV_INTERN +int +ha_innobase::update_row( +/*====================*/ + /* out: error number or 0 */ + const uchar* old_row, /* in: old row in MySQL format */ + uchar* new_row) /* in: new row in MySQL format */ +{ + upd_t* uvect; + int error = 0; + trx_t* trx = thd_to_trx(user_thd); + + DBUG_ENTER("ha_innobase::update_row"); + + ut_a(prebuilt->trx == trx); + + ha_statistic_increment(&SSV::ha_update_count); + + if (table->timestamp_field_type & TIMESTAMP_AUTO_SET_ON_UPDATE) + table->timestamp_field->set_time(); + + if (prebuilt->upd_node) { + uvect = prebuilt->upd_node->update; + } else { + uvect = row_get_prebuilt_update_vector(prebuilt); + } + + /* Build an update vector from the modified fields in the rows + (uses upd_buff of the handle) */ + + calc_row_difference(uvect, (uchar*) old_row, new_row, table, + upd_buff, (ulint)upd_and_key_val_buff_len, + prebuilt, user_thd); + + /* This is not a delete */ + prebuilt->upd_node->is_delete = FALSE; + + ut_a(prebuilt->template_type == ROW_MYSQL_WHOLE_ROW); + + innodb_srv_conc_enter_innodb(trx); + + error = row_update_for_mysql((byte*) old_row, prebuilt); + + /* We need to do some special AUTOINC handling for the following case: + + INSERT INTO t (c1,c2) VALUES(x,y) ON DUPLICATE KEY UPDATE ... + + We need to use the AUTOINC counter that was actually used by + MySQL in the UPDATE statement, which can be different from the + value used in the INSERT statement.*/ + + if (error == DB_SUCCESS + && table->next_number_field + && new_row == table->record[0] + && thd_sql_command(user_thd) == SQLCOM_INSERT + && (trx->duplicates & (TRX_DUP_IGNORE | TRX_DUP_REPLACE)) + == TRX_DUP_IGNORE) { + + ulonglong auto_inc; + ulonglong col_max_value; + + auto_inc = table->next_number_field->val_int(); + + /* We need the upper limit of the col type to check for + whether we update the table autoinc counter or not. */ + col_max_value = innobase_get_int_col_max_value( + table->next_number_field); + + if (auto_inc <= col_max_value && auto_inc != 0) { + + ulonglong need; + ulonglong offset; + + offset = prebuilt->autoinc_offset; + need = prebuilt->autoinc_increment; + + auto_inc = innobase_next_autoinc( + auto_inc, need, offset, col_max_value); + + error = innobase_set_max_autoinc(auto_inc); + } + } + + innodb_srv_conc_exit_innodb(trx); + + error = convert_error_code_to_mysql(error, + prebuilt->table->flags, user_thd); + + if (error == 0 /* success */ + && uvect->n_fields == 0 /* no columns were updated */) { + + /* This is the same as success, but instructs + MySQL that the row is not really updated and it + should not increase the count of updated rows. + This is fix for http://bugs.mysql.com/29157 */ + error = HA_ERR_RECORD_IS_THE_SAME; + } + + /* Tell InnoDB server that there might be work for + utility threads: */ + + innobase_active_small(); + + DBUG_RETURN(error); +} + +/************************************************************************** +Deletes a row given as the parameter. */ +UNIV_INTERN +int +ha_innobase::delete_row( +/*====================*/ + /* out: error number or 0 */ + const uchar* record) /* in: a row in MySQL format */ +{ + int error = 0; + trx_t* trx = thd_to_trx(user_thd); + + DBUG_ENTER("ha_innobase::delete_row"); + + ut_a(prebuilt->trx == trx); + + ha_statistic_increment(&SSV::ha_delete_count); + + if (!prebuilt->upd_node) { + row_get_prebuilt_update_vector(prebuilt); + } + + /* This is a delete */ + + prebuilt->upd_node->is_delete = TRUE; + + innodb_srv_conc_enter_innodb(trx); + + error = row_update_for_mysql((byte*) record, prebuilt); + + innodb_srv_conc_exit_innodb(trx); + + error = convert_error_code_to_mysql( + error, prebuilt->table->flags, user_thd); + + /* Tell the InnoDB server that there might be work for + utility threads: */ + + innobase_active_small(); + + DBUG_RETURN(error); +} + +/************************************************************************** +Removes a new lock set on a row, if it was not read optimistically. This can +be called after a row has been read in the processing of an UPDATE or a DELETE +query, if the option innodb_locks_unsafe_for_binlog is set. */ +UNIV_INTERN +void +ha_innobase::unlock_row(void) +/*=========================*/ +{ + DBUG_ENTER("ha_innobase::unlock_row"); + + /* Consistent read does not take any locks, thus there is + nothing to unlock. */ + + if (prebuilt->select_lock_type == LOCK_NONE) { + DBUG_VOID_RETURN; + } + + switch (prebuilt->row_read_type) { + case ROW_READ_WITH_LOCKS: + if (!srv_locks_unsafe_for_binlog + && prebuilt->trx->isolation_level + != TRX_ISO_READ_COMMITTED) { + break; + } + /* fall through */ + case ROW_READ_TRY_SEMI_CONSISTENT: + row_unlock_for_mysql(prebuilt, FALSE); + break; + case ROW_READ_DID_SEMI_CONSISTENT: + prebuilt->row_read_type = ROW_READ_TRY_SEMI_CONSISTENT; + break; + } + + DBUG_VOID_RETURN; +} + +/* See handler.h and row0mysql.h for docs on this function. */ +UNIV_INTERN +bool +ha_innobase::was_semi_consistent_read(void) +/*=======================================*/ +{ + return(prebuilt->row_read_type == ROW_READ_DID_SEMI_CONSISTENT); +} + +/* See handler.h and row0mysql.h for docs on this function. */ +UNIV_INTERN +void +ha_innobase::try_semi_consistent_read(bool yes) +/*===========================================*/ +{ + ut_a(prebuilt->trx == thd_to_trx(ha_thd())); + + /* Row read type is set to semi consistent read if this was + requested by the MySQL and either innodb_locks_unsafe_for_binlog + option is used or this session is using READ COMMITTED isolation + level. */ + + if (yes + && (srv_locks_unsafe_for_binlog + || prebuilt->trx->isolation_level == TRX_ISO_READ_COMMITTED)) { + prebuilt->row_read_type = ROW_READ_TRY_SEMI_CONSISTENT; + } else { + prebuilt->row_read_type = ROW_READ_WITH_LOCKS; + } +} + +#ifdef ROW_MERGE_IS_INDEX_USABLE +/********************************************************************** +Check if an index can be used by the optimizer. */ +UNIV_INTERN +bool +ha_innobase::is_index_available( +/*============================*/ + /* out: true if available else false*/ + uint keynr) /* in: index number to check */ +{ + DBUG_ENTER("ha_innobase::is_index_available"); + + if (table && keynr != MAX_KEY && table->s->keys > 0) { + const dict_index_t* index; + const KEY* key = table->key_info + keynr; + + ut_ad(user_thd == ha_thd()); + ut_a(prebuilt->trx == thd_to_trx(user_thd)); + + index = dict_table_get_index_on_name( + prebuilt->table, key->name); + + if (!row_merge_is_index_usable(prebuilt->trx, index)) { + + DBUG_RETURN(false); + } + } + + DBUG_RETURN(true); +} +#endif /* ROW_MERGE_IS_INDEX_USABLE */ + +/********************************************************************** +Initializes a handle to use an index. */ +UNIV_INTERN +int +ha_innobase::index_init( +/*====================*/ + /* out: 0 or error number */ + uint keynr, /* in: key (index) number */ + bool sorted) /* in: 1 if result MUST be sorted according to index */ +{ + DBUG_ENTER("index_init"); + + DBUG_RETURN(change_active_index(keynr)); +} + +/********************************************************************** +Currently does nothing. */ +UNIV_INTERN +int +ha_innobase::index_end(void) +/*========================*/ +{ + int error = 0; + DBUG_ENTER("index_end"); + active_index=MAX_KEY; + DBUG_RETURN(error); +} + +/************************************************************************* +Converts a search mode flag understood by MySQL to a flag understood +by InnoDB. */ +static inline +ulint +convert_search_mode_to_innobase( +/*============================*/ + enum ha_rkey_function find_flag) +{ + switch (find_flag) { + case HA_READ_KEY_EXACT: + /* this does not require the index to be UNIQUE */ + return(PAGE_CUR_GE); + case HA_READ_KEY_OR_NEXT: + return(PAGE_CUR_GE); + case HA_READ_KEY_OR_PREV: + return(PAGE_CUR_LE); + case HA_READ_AFTER_KEY: + return(PAGE_CUR_G); + case HA_READ_BEFORE_KEY: + return(PAGE_CUR_L); + case HA_READ_PREFIX: + return(PAGE_CUR_GE); + case HA_READ_PREFIX_LAST: + return(PAGE_CUR_LE); + case HA_READ_PREFIX_LAST_OR_PREV: + return(PAGE_CUR_LE); + /* In MySQL-4.0 HA_READ_PREFIX and HA_READ_PREFIX_LAST always + pass a complete-field prefix of a key value as the search + tuple. I.e., it is not allowed that the last field would + just contain n first bytes of the full field value. + MySQL uses a 'padding' trick to convert LIKE 'abc%' + type queries so that it can use as a search tuple + a complete-field-prefix of a key value. Thus, the InnoDB + search mode PAGE_CUR_LE_OR_EXTENDS is never used. + TODO: when/if MySQL starts to use also partial-field + prefixes, we have to deal with stripping of spaces + and comparison of non-latin1 char type fields in + innobase_mysql_cmp() to get PAGE_CUR_LE_OR_EXTENDS to + work correctly. */ + case HA_READ_MBR_CONTAIN: + case HA_READ_MBR_INTERSECT: + case HA_READ_MBR_WITHIN: + case HA_READ_MBR_DISJOINT: + case HA_READ_MBR_EQUAL: + return(PAGE_CUR_UNSUPP); + /* do not use "default:" in order to produce a gcc warning: + enumeration value '...' not handled in switch + (if -Wswitch or -Wall is used) */ + } + + my_error(ER_CHECK_NOT_IMPLEMENTED, MYF(0), "this functionality"); + + return(PAGE_CUR_UNSUPP); +} + +/* + BACKGROUND INFO: HOW A SELECT SQL QUERY IS EXECUTED + --------------------------------------------------- +The following does not cover all the details, but explains how we determine +the start of a new SQL statement, and what is associated with it. + +For each table in the database the MySQL interpreter may have several +table handle instances in use, also in a single SQL query. For each table +handle instance there is an InnoDB 'prebuilt' struct which contains most +of the InnoDB data associated with this table handle instance. + + A) if the user has not explicitly set any MySQL table level locks: + + 1) MySQL calls ::external_lock to set an 'intention' table level lock on +the table of the handle instance. There we set +prebuilt->sql_stat_start = TRUE. The flag sql_stat_start should be set +true if we are taking this table handle instance to use in a new SQL +statement issued by the user. We also increment trx->n_mysql_tables_in_use. + + 2) If prebuilt->sql_stat_start == TRUE we 'pre-compile' the MySQL search +instructions to prebuilt->template of the table handle instance in +::index_read. The template is used to save CPU time in large joins. + + 3) In row_search_for_mysql, if prebuilt->sql_stat_start is true, we +allocate a new consistent read view for the trx if it does not yet have one, +or in the case of a locking read, set an InnoDB 'intention' table level +lock on the table. + + 4) We do the SELECT. MySQL may repeatedly call ::index_read for the +same table handle instance, if it is a join. + + 5) When the SELECT ends, MySQL removes its intention table level locks +in ::external_lock. When trx->n_mysql_tables_in_use drops to zero, + (a) we execute a COMMIT there if the autocommit is on, + (b) we also release possible 'SQL statement level resources' InnoDB may +have for this SQL statement. The MySQL interpreter does NOT execute +autocommit for pure read transactions, though it should. That is why the +table handler in that case has to execute the COMMIT in ::external_lock. + + B) If the user has explicitly set MySQL table level locks, then MySQL +does NOT call ::external_lock at the start of the statement. To determine +when we are at the start of a new SQL statement we at the start of +::index_read also compare the query id to the latest query id where the +table handle instance was used. If it has changed, we know we are at the +start of a new SQL statement. Since the query id can theoretically +overwrap, we use this test only as a secondary way of determining the +start of a new SQL statement. */ + + +/************************************************************************** +Positions an index cursor to the index specified in the handle. Fetches the +row if any. */ +UNIV_INTERN +int +ha_innobase::index_read( +/*====================*/ + /* out: 0, HA_ERR_KEY_NOT_FOUND, + or error number */ + uchar* buf, /* in/out: buffer for the returned + row */ + const uchar* key_ptr, /* in: key value; if this is NULL + we position the cursor at the + start or end of index; this can + also contain an InnoDB row id, in + which case key_len is the InnoDB + row id length; the key value can + also be a prefix of a full key value, + and the last column can be a prefix + of a full column */ + uint key_len,/* in: key value length */ + enum ha_rkey_function find_flag)/* in: search flags from my_base.h */ +{ + ulint mode; + dict_index_t* index; + ulint match_mode = 0; + int error; + ulint ret; + + DBUG_ENTER("index_read"); + + ut_a(prebuilt->trx == thd_to_trx(user_thd)); + + ha_statistic_increment(&SSV::ha_read_key_count); + + index = prebuilt->index; + + /* Note that if the index for which the search template is built is not + necessarily prebuilt->index, but can also be the clustered index */ + + if (prebuilt->sql_stat_start) { + build_template(prebuilt, user_thd, table, ROW_MYSQL_REC_FIELDS); + } + + if (key_ptr) { + /* Convert the search key value to InnoDB format into + prebuilt->search_tuple */ + + row_sel_convert_mysql_key_to_innobase( + prebuilt->search_tuple, + (byte*) key_val_buff, + (ulint)upd_and_key_val_buff_len, + index, + (byte*) key_ptr, + (ulint) key_len, + prebuilt->trx); + } else { + /* We position the cursor to the last or the first entry + in the index */ + + dtuple_set_n_fields(prebuilt->search_tuple, 0); + } + + mode = convert_search_mode_to_innobase(find_flag); + + match_mode = 0; + + if (find_flag == HA_READ_KEY_EXACT) { + + match_mode = ROW_SEL_EXACT; + + } else if (find_flag == HA_READ_PREFIX + || find_flag == HA_READ_PREFIX_LAST) { + + match_mode = ROW_SEL_EXACT_PREFIX; + } + + last_match_mode = (uint) match_mode; + + if (mode != PAGE_CUR_UNSUPP) { + + innodb_srv_conc_enter_innodb(prebuilt->trx); + + ret = row_search_for_mysql((byte*) buf, mode, prebuilt, + match_mode, 0); + + innodb_srv_conc_exit_innodb(prebuilt->trx); + } else { + + ret = DB_UNSUPPORTED; + } + + switch (ret) { + case DB_SUCCESS: + error = 0; + table->status = 0; + break; + case DB_RECORD_NOT_FOUND: + error = HA_ERR_KEY_NOT_FOUND; + table->status = STATUS_NOT_FOUND; + break; + case DB_END_OF_INDEX: + error = HA_ERR_KEY_NOT_FOUND; + table->status = STATUS_NOT_FOUND; + break; + default: + error = convert_error_code_to_mysql((int) ret, + prebuilt->table->flags, + user_thd); + table->status = STATUS_NOT_FOUND; + break; + } + + DBUG_RETURN(error); +} + +/*********************************************************************** +The following functions works like index_read, but it find the last +row with the current key value or prefix. */ +UNIV_INTERN +int +ha_innobase::index_read_last( +/*=========================*/ + /* out: 0, HA_ERR_KEY_NOT_FOUND, or an + error code */ + uchar* buf, /* out: fetched row */ + const uchar* key_ptr,/* in: key value, or a prefix of a full + key value */ + uint key_len)/* in: length of the key val or prefix + in bytes */ +{ + return(index_read(buf, key_ptr, key_len, HA_READ_PREFIX_LAST)); +} + +/************************************************************************ +Get the index for a handle. Does not change active index.*/ +UNIV_INTERN +dict_index_t* +ha_innobase::innobase_get_index( +/*============================*/ + /* out: NULL or index instance. */ + uint keynr) /* in: use this index; MAX_KEY means always + clustered index, even if it was internally + generated by InnoDB */ +{ + KEY* key = 0; + dict_index_t* index = 0; + + DBUG_ENTER("innobase_get_index"); + ha_statistic_increment(&SSV::ha_read_key_count); + + ut_ad(user_thd == ha_thd()); + ut_a(prebuilt->trx == thd_to_trx(user_thd)); + + if (keynr != MAX_KEY && table->s->keys > 0) { + key = table->key_info + keynr; + + index = dict_table_get_index_on_name(prebuilt->table, + key->name); + } else { + index = dict_table_get_first_index(prebuilt->table); + } + + if (!index) { + sql_print_error( + "Innodb could not find key n:o %u with name %s " + "from dict cache for table %s", + keynr, key ? key->name : "NULL", + prebuilt->table->name); + } + + DBUG_RETURN(index); +} + +/************************************************************************ +Changes the active index of a handle. */ +UNIV_INTERN +int +ha_innobase::change_active_index( +/*=============================*/ + /* out: 0 or error code */ + uint keynr) /* in: use this index; MAX_KEY means always clustered + index, even if it was internally generated by + InnoDB */ +{ + DBUG_ENTER("change_active_index"); + + ut_ad(user_thd == ha_thd()); + ut_a(prebuilt->trx == thd_to_trx(user_thd)); + + active_index = keynr; + + prebuilt->index = innobase_get_index(keynr); + + if (UNIV_UNLIKELY(!prebuilt->index)) { + sql_print_warning("InnoDB: change_active_index(%u) failed", + keynr); + DBUG_RETURN(1); + } + + ut_a(prebuilt->search_tuple != 0); + + dtuple_set_n_fields(prebuilt->search_tuple, prebuilt->index->n_fields); + + dict_index_copy_types(prebuilt->search_tuple, prebuilt->index, + prebuilt->index->n_fields); + + /* MySQL changes the active index for a handle also during some + queries, for example SELECT MAX(a), SUM(a) first retrieves the MAX() + and then calculates the sum. Previously we played safe and used + the flag ROW_MYSQL_WHOLE_ROW below, but that caused unnecessary + copying. Starting from MySQL-4.1 we use a more efficient flag here. */ + + build_template(prebuilt, user_thd, table, ROW_MYSQL_REC_FIELDS); + + DBUG_RETURN(0); +} + +/************************************************************************** +Positions an index cursor to the index specified in keynr. Fetches the +row if any. */ +/* ??? This is only used to read whole keys ??? */ +UNIV_INTERN +int +ha_innobase::index_read_idx( +/*========================*/ + /* out: error number or 0 */ + uchar* buf, /* in/out: buffer for the returned + row */ + uint keynr, /* in: use this index */ + const uchar* key, /* in: key value; if this is NULL + we position the cursor at the + start or end of index */ + uint key_len, /* in: key value length */ + enum ha_rkey_function find_flag)/* in: search flags from my_base.h */ +{ + if (change_active_index(keynr)) { + + return(1); + } + + return(index_read(buf, key, key_len, find_flag)); +} + +/*************************************************************************** +Reads the next or previous row from a cursor, which must have previously been +positioned using index_read. */ +UNIV_INTERN +int +ha_innobase::general_fetch( +/*=======================*/ + /* out: 0, HA_ERR_END_OF_FILE, or error + number */ + uchar* buf, /* in/out: buffer for next row in MySQL + format */ + uint direction, /* in: ROW_SEL_NEXT or ROW_SEL_PREV */ + uint match_mode) /* in: 0, ROW_SEL_EXACT, or + ROW_SEL_EXACT_PREFIX */ +{ + ulint ret; + int error = 0; + + DBUG_ENTER("general_fetch"); + + ut_a(prebuilt->trx == thd_to_trx(user_thd)); + + innodb_srv_conc_enter_innodb(prebuilt->trx); + + ret = row_search_for_mysql( + (byte*)buf, 0, prebuilt, match_mode, direction); + + innodb_srv_conc_exit_innodb(prebuilt->trx); + + switch (ret) { + case DB_SUCCESS: + error = 0; + table->status = 0; + break; + case DB_RECORD_NOT_FOUND: + error = HA_ERR_END_OF_FILE; + table->status = STATUS_NOT_FOUND; + break; + case DB_END_OF_INDEX: + error = HA_ERR_END_OF_FILE; + table->status = STATUS_NOT_FOUND; + break; + default: + error = convert_error_code_to_mysql( + (int) ret, prebuilt->table->flags, user_thd); + table->status = STATUS_NOT_FOUND; + break; + } + + DBUG_RETURN(error); +} + +/*************************************************************************** +Reads the next row from a cursor, which must have previously been +positioned using index_read. */ +UNIV_INTERN +int +ha_innobase::index_next( +/*====================*/ + /* out: 0, HA_ERR_END_OF_FILE, or error + number */ + uchar* buf) /* in/out: buffer for next row in MySQL + format */ +{ + ha_statistic_increment(&SSV::ha_read_next_count); + + return(general_fetch(buf, ROW_SEL_NEXT, 0)); +} + +/*********************************************************************** +Reads the next row matching to the key value given as the parameter. */ +UNIV_INTERN +int +ha_innobase::index_next_same( +/*=========================*/ + /* out: 0, HA_ERR_END_OF_FILE, or error + number */ + uchar* buf, /* in/out: buffer for the row */ + const uchar* key, /* in: key value */ + uint keylen) /* in: key value length */ +{ + ha_statistic_increment(&SSV::ha_read_next_count); + + return(general_fetch(buf, ROW_SEL_NEXT, last_match_mode)); +} + +/*************************************************************************** +Reads the previous row from a cursor, which must have previously been +positioned using index_read. */ +UNIV_INTERN +int +ha_innobase::index_prev( +/*====================*/ + /* out: 0, HA_ERR_END_OF_FILE, or error number */ + uchar* buf) /* in/out: buffer for previous row in MySQL format */ +{ + ha_statistic_increment(&SSV::ha_read_prev_count); + + return(general_fetch(buf, ROW_SEL_PREV, 0)); +} + +/************************************************************************ +Positions a cursor on the first record in an index and reads the +corresponding row to buf. */ +UNIV_INTERN +int +ha_innobase::index_first( +/*=====================*/ + /* out: 0, HA_ERR_END_OF_FILE, or error code */ + uchar* buf) /* in/out: buffer for the row */ +{ + int error; + + DBUG_ENTER("index_first"); + ha_statistic_increment(&SSV::ha_read_first_count); + + error = index_read(buf, NULL, 0, HA_READ_AFTER_KEY); + + /* MySQL does not seem to allow this to return HA_ERR_KEY_NOT_FOUND */ + + if (error == HA_ERR_KEY_NOT_FOUND) { + error = HA_ERR_END_OF_FILE; + } + + DBUG_RETURN(error); +} + +/************************************************************************ +Positions a cursor on the last record in an index and reads the +corresponding row to buf. */ +UNIV_INTERN +int +ha_innobase::index_last( +/*====================*/ + /* out: 0, HA_ERR_END_OF_FILE, or error code */ + uchar* buf) /* in/out: buffer for the row */ +{ + int error; + + DBUG_ENTER("index_last"); + ha_statistic_increment(&SSV::ha_read_last_count); + + error = index_read(buf, NULL, 0, HA_READ_BEFORE_KEY); + + /* MySQL does not seem to allow this to return HA_ERR_KEY_NOT_FOUND */ + + if (error == HA_ERR_KEY_NOT_FOUND) { + error = HA_ERR_END_OF_FILE; + } + + DBUG_RETURN(error); +} + +/******************************************************************** +Initialize a table scan. */ +UNIV_INTERN +int +ha_innobase::rnd_init( +/*==================*/ + /* out: 0 or error number */ + bool scan) /* in: TRUE if table/index scan FALSE otherwise */ +{ + int err; + + /* Store the active index value so that we can restore the original + value after a scan */ + + if (prebuilt->clust_index_was_generated) { + err = change_active_index(MAX_KEY); + } else { + err = change_active_index(primary_key); + } + + /* Don't use semi-consistent read in random row reads (by position). + This means we must disable semi_consistent_read if scan is false */ + + if (!scan) { + try_semi_consistent_read(0); + } + + start_of_scan = 1; + + return(err); +} + +/********************************************************************* +Ends a table scan. */ +UNIV_INTERN +int +ha_innobase::rnd_end(void) +/*======================*/ + /* out: 0 or error number */ +{ + return(index_end()); +} + +/********************************************************************* +Reads the next row in a table scan (also used to read the FIRST row +in a table scan). */ +UNIV_INTERN +int +ha_innobase::rnd_next( +/*==================*/ + /* out: 0, HA_ERR_END_OF_FILE, or error number */ + uchar* buf) /* in/out: returns the row in this buffer, + in MySQL format */ +{ + int error; + + DBUG_ENTER("rnd_next"); + ha_statistic_increment(&SSV::ha_read_rnd_next_count); + + if (start_of_scan) { + error = index_first(buf); + + if (error == HA_ERR_KEY_NOT_FOUND) { + error = HA_ERR_END_OF_FILE; + } + + start_of_scan = 0; + } else { + error = general_fetch(buf, ROW_SEL_NEXT, 0); + } + + DBUG_RETURN(error); +} + +/************************************************************************** +Fetches a row from the table based on a row reference. */ +UNIV_INTERN +int +ha_innobase::rnd_pos( +/*=================*/ + /* out: 0, HA_ERR_KEY_NOT_FOUND, or error code */ + uchar* buf, /* in/out: buffer for the row */ + uchar* pos) /* in: primary key value of the row in the + MySQL format, or the row id if the clustered + index was internally generated by InnoDB; the + length of data in pos has to be ref_length */ +{ + int error; + uint keynr = active_index; + DBUG_ENTER("rnd_pos"); + DBUG_DUMP("key", pos, ref_length); + + ha_statistic_increment(&SSV::ha_read_rnd_count); + + ut_a(prebuilt->trx == thd_to_trx(ha_thd())); + + if (prebuilt->clust_index_was_generated) { + /* No primary key was defined for the table and we + generated the clustered index from the row id: the + row reference is the row id, not any key value + that MySQL knows of */ + + error = change_active_index(MAX_KEY); + } else { + error = change_active_index(primary_key); + } + + if (error) { + DBUG_PRINT("error", ("Got error: %d", error)); + DBUG_RETURN(error); + } + + /* Note that we assume the length of the row reference is fixed + for the table, and it is == ref_length */ + + error = index_read(buf, pos, ref_length, HA_READ_KEY_EXACT); + + if (error) { + DBUG_PRINT("error", ("Got error: %d", error)); + } + + change_active_index(keynr); + + DBUG_RETURN(error); +} + +/************************************************************************* +Stores a reference to the current row to 'ref' field of the handle. Note +that in the case where we have generated the clustered index for the +table, the function parameter is illogical: we MUST ASSUME that 'record' +is the current 'position' of the handle, because if row ref is actually +the row id internally generated in InnoDB, then 'record' does not contain +it. We just guess that the row id must be for the record where the handle +was positioned the last time. */ +UNIV_INTERN +void +ha_innobase::position( +/*==================*/ + const uchar* record) /* in: row in MySQL format */ +{ + uint len; + + ut_a(prebuilt->trx == thd_to_trx(ha_thd())); + + if (prebuilt->clust_index_was_generated) { + /* No primary key was defined for the table and we + generated the clustered index from row id: the + row reference will be the row id, not any key value + that MySQL knows of */ + + len = DATA_ROW_ID_LEN; + + memcpy(ref, prebuilt->row_id, len); + } else { + len = store_key_val_for_row(primary_key, (char*)ref, + ref_length, record); + } + + /* We assume that the 'ref' value len is always fixed for the same + table. */ + + if (len != ref_length) { + sql_print_error("Stored ref len is %lu, but table ref len is %lu", + (ulong) len, (ulong) ref_length); + } +} + +/* limit innodb monitor access to users with PROCESS privilege. +See http://bugs.mysql.com/32710 for expl. why we choose PROCESS. */ +#define IS_MAGIC_TABLE_AND_USER_DENIED_ACCESS(table_name, thd) \ + (row_is_magic_monitor_table(table_name) \ + && check_global_access(thd, PROCESS_ACL)) + +/********************************************************************* +Creates a table definition to an InnoDB database. */ +static +int +create_table_def( +/*=============*/ + trx_t* trx, /* in: InnoDB transaction handle */ + TABLE* form, /* in: information on table + columns and indexes */ + const char* table_name, /* in: table name */ + const char* path_of_temp_table,/* in: if this is a table explicitly + created by the user with the + TEMPORARY keyword, then this + parameter is the dir path where the + table should be placed if we create + an .ibd file for it (no .ibd extension + in the path, though); otherwise this + is NULL */ + ulint flags) /* in: table flags */ +{ + Field* field; + dict_table_t* table; + ulint n_cols; + int error; + ulint col_type; + ulint col_len; + ulint nulls_allowed; + ulint unsigned_type; + ulint binary_type; + ulint long_true_varchar; + ulint charset_no; + ulint i; + + DBUG_ENTER("create_table_def"); + DBUG_PRINT("enter", ("table_name: %s", table_name)); + + ut_a(trx->mysql_thd != NULL); + if (IS_MAGIC_TABLE_AND_USER_DENIED_ACCESS(table_name, + (THD*) trx->mysql_thd)) { + DBUG_RETURN(HA_ERR_GENERIC); + } + + n_cols = form->s->fields; + + /* We pass 0 as the space id, and determine at a lower level the space + id where to store the table */ + + table = dict_mem_table_create(table_name, 0, n_cols, flags); + + if (path_of_temp_table) { + table->dir_path_of_temp_table = + mem_heap_strdup(table->heap, path_of_temp_table); + } + + for (i = 0; i < n_cols; i++) { + field = form->field[i]; + + col_type = get_innobase_type_from_mysql_type(&unsigned_type, + field); + if (field->null_ptr) { + nulls_allowed = 0; + } else { + nulls_allowed = DATA_NOT_NULL; + } + + if (field->binary()) { + binary_type = DATA_BINARY_TYPE; + } else { + binary_type = 0; + } + + charset_no = 0; + + if (dtype_is_string_type(col_type)) { + + charset_no = (ulint)field->charset()->number; + + if (UNIV_UNLIKELY(charset_no >= 256)) { + /* in data0type.h we assume that the + number fits in one byte in prtype */ + push_warning_printf( + (THD*) trx->mysql_thd, + MYSQL_ERROR::WARN_LEVEL_ERROR, + ER_CANT_CREATE_TABLE, + "In InnoDB, charset-collation codes" + " must be below 256." + " Unsupported code %lu.", + (ulong) charset_no); + DBUG_RETURN(ER_CANT_CREATE_TABLE); + } + } + + ut_a(field->type() < 256); /* we assume in dtype_form_prtype() + that this fits in one byte */ + col_len = field->pack_length(); + + /* The MySQL pack length contains 1 or 2 bytes length field + for a true VARCHAR. Let us subtract that, so that the InnoDB + column length in the InnoDB data dictionary is the real + maximum byte length of the actual data. */ + + long_true_varchar = 0; + + if (field->type() == MYSQL_TYPE_VARCHAR) { + col_len -= ((Field_varstring*)field)->length_bytes; + + if (((Field_varstring*)field)->length_bytes == 2) { + long_true_varchar = DATA_LONG_TRUE_VARCHAR; + } + } + + dict_mem_table_add_col(table, table->heap, + (char*) field->field_name, + col_type, + dtype_form_prtype( + (ulint)field->type() + | nulls_allowed | unsigned_type + | binary_type | long_true_varchar, + charset_no), + col_len); + } + + error = row_create_table_for_mysql(table, trx); + + error = convert_error_code_to_mysql(error, flags, NULL); + + DBUG_RETURN(error); +} + +/********************************************************************* +Creates an index in an InnoDB database. */ +static +int +create_index( +/*=========*/ + trx_t* trx, /* in: InnoDB transaction handle */ + TABLE* form, /* in: information on table + columns and indexes */ + ulint flags, /* in: InnoDB table flags */ + const char* table_name, /* in: table name */ + uint key_num) /* in: index number */ +{ + Field* field; + dict_index_t* index; + int error; + ulint n_fields; + KEY* key; + KEY_PART_INFO* key_part; + ulint ind_type; + ulint col_type; + ulint prefix_len; + ulint is_unsigned; + ulint i; + ulint j; + ulint* field_lengths; + + DBUG_ENTER("create_index"); + + key = form->key_info + key_num; + + n_fields = key->key_parts; + + ind_type = 0; + + if (key_num == form->s->primary_key) { + ind_type = ind_type | DICT_CLUSTERED; + } + + if (key->flags & HA_NOSAME ) { + ind_type = ind_type | DICT_UNIQUE; + } + + /* We pass 0 as the space id, and determine at a lower level the space + id where to store the table */ + + index = dict_mem_index_create(table_name, key->name, 0, + ind_type, n_fields); + + field_lengths = (ulint*) my_malloc(sizeof(ulint) * n_fields, + MYF(MY_FAE)); + + for (i = 0; i < n_fields; i++) { + key_part = key->key_part + i; + + /* (The flag HA_PART_KEY_SEG denotes in MySQL a column prefix + field in an index: we only store a specified number of first + bytes of the column to the index field.) The flag does not + seem to be properly set by MySQL. Let us fall back on testing + the length of the key part versus the column. */ + + field = NULL; + for (j = 0; j < form->s->fields; j++) { + + field = form->field[j]; + + if (0 == innobase_strcasecmp( + field->field_name, + key_part->field->field_name)) { + /* Found the corresponding column */ + + break; + } + } + + ut_a(j < form->s->fields); + + col_type = get_innobase_type_from_mysql_type( + &is_unsigned, key_part->field); + + if (DATA_BLOB == col_type + || (key_part->length < field->pack_length() + && field->type() != MYSQL_TYPE_VARCHAR) + || (field->type() == MYSQL_TYPE_VARCHAR + && key_part->length < field->pack_length() + - ((Field_varstring*)field)->length_bytes)) { + + prefix_len = key_part->length; + + if (col_type == DATA_INT + || col_type == DATA_FLOAT + || col_type == DATA_DOUBLE + || col_type == DATA_DECIMAL) { + sql_print_error( + "MySQL is trying to create a column " + "prefix index field, on an " + "inappropriate data type. Table " + "name %s, column name %s.", + table_name, + key_part->field->field_name); + + prefix_len = 0; + } + } else { + prefix_len = 0; + } + + field_lengths[i] = key_part->length; + + dict_mem_index_add_field(index, + (char*) key_part->field->field_name, prefix_len); + } + + /* Even though we've defined max_supported_key_part_length, we + still do our own checking using field_lengths to be absolutely + sure we don't create too long indexes. */ + error = row_create_index_for_mysql(index, trx, field_lengths); + + error = convert_error_code_to_mysql(error, flags, NULL); + + my_free(field_lengths, MYF(0)); + + DBUG_RETURN(error); +} + +/********************************************************************* +Creates an index to an InnoDB table when the user has defined no +primary index. */ +static +int +create_clustered_index_when_no_primary( +/*===================================*/ + trx_t* trx, /* in: InnoDB transaction handle */ + ulint flags, /* in: InnoDB table flags */ + const char* table_name) /* in: table name */ +{ + dict_index_t* index; + int error; + + /* We pass 0 as the space id, and determine at a lower level the space + id where to store the table */ + + index = dict_mem_index_create(table_name, "GEN_CLUST_INDEX", + 0, DICT_CLUSTERED, 0); + + error = row_create_index_for_mysql(index, trx, NULL); + + error = convert_error_code_to_mysql(error, flags, NULL); + + return(error); +} + +/********************************************************************* +Validates the create options. We may build on this function +in future. For now, it checks two specifiers: +KEY_BLOCK_SIZE and ROW_FORMAT +If innodb_strict_mode is not set then this function is a no-op */ +static +ibool +create_options_are_valid( +/*=====================*/ + /* out: TRUE if valid. */ + THD* thd, /* in: connection thread. */ + TABLE* form, /* in: information on table + columns and indexes */ + HA_CREATE_INFO* create_info) /* in: create info. */ +{ + ibool kbs_specified = FALSE; + ibool ret = TRUE; + + + ut_ad(thd != NULL); + + /* If innodb_strict_mode is not set don't do any validation. */ + if (!(THDVAR(thd, strict_mode))) { + return(TRUE); + } + + ut_ad(form != NULL); + ut_ad(create_info != NULL); + + /* First check if KEY_BLOCK_SIZE was specified. */ + if (create_info->key_block_size + || (create_info->used_fields & HA_CREATE_USED_KEY_BLOCK_SIZE)) { + + kbs_specified = TRUE; + switch (create_info->key_block_size) { + case 1: + case 2: + case 4: + case 8: + case 16: + /* Valid value. */ + break; + default: + push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_ERROR, + ER_ILLEGAL_HA_CREATE_OPTION, + "InnoDB: invalid" + " KEY_BLOCK_SIZE = %lu." + " Valid values are" + " [1, 2, 4, 8, 16]", + create_info->key_block_size); + ret = FALSE; + } + } + + /* If KEY_BLOCK_SIZE was specified, check for its + dependencies. */ + if (kbs_specified && !srv_file_per_table) { + push_warning(thd, MYSQL_ERROR::WARN_LEVEL_ERROR, + ER_ILLEGAL_HA_CREATE_OPTION, + "InnoDB: KEY_BLOCK_SIZE" + " requires innodb_file_per_table."); + ret = FALSE; + } + + if (kbs_specified && srv_file_format < DICT_TF_FORMAT_ZIP) { + push_warning(thd, MYSQL_ERROR::WARN_LEVEL_ERROR, + ER_ILLEGAL_HA_CREATE_OPTION, + "InnoDB: KEY_BLOCK_SIZE" + " requires innodb_file_format >" + " Antelope."); + ret = FALSE; + } + + /* Now check for ROW_FORMAT specifier. */ + if (create_info->used_fields & HA_CREATE_USED_ROW_FORMAT) { + switch (form->s->row_type) { + const char* row_format_name; + case ROW_TYPE_COMPRESSED: + case ROW_TYPE_DYNAMIC: + row_format_name + = form->s->row_type == ROW_TYPE_COMPRESSED + ? "COMPRESSED" + : "DYNAMIC"; + + /* These two ROW_FORMATs require + srv_file_per_table and srv_file_format */ + if (!srv_file_per_table) { + push_warning_printf( + thd, + MYSQL_ERROR::WARN_LEVEL_ERROR, + ER_ILLEGAL_HA_CREATE_OPTION, + "InnoDB: ROW_FORMAT=%s" + " requires innodb_file_per_table.", + row_format_name); + ret = FALSE; + + } + + if (srv_file_format < DICT_TF_FORMAT_ZIP) { + push_warning_printf( + thd, + MYSQL_ERROR::WARN_LEVEL_ERROR, + ER_ILLEGAL_HA_CREATE_OPTION, + "InnoDB: ROW_FORMAT=%s" + " requires innodb_file_format >" + " Antelope.", + row_format_name); + ret = FALSE; + } + + /* Cannot specify KEY_BLOCK_SIZE with + ROW_FORMAT = DYNAMIC. + However, we do allow COMPRESSED to be + specified with KEY_BLOCK_SIZE. */ + if (kbs_specified + && form->s->row_type == ROW_TYPE_DYNAMIC) { + push_warning_printf( + thd, + MYSQL_ERROR::WARN_LEVEL_ERROR, + ER_ILLEGAL_HA_CREATE_OPTION, + "InnoDB: cannot specify" + " ROW_FORMAT = DYNAMIC with" + " KEY_BLOCK_SIZE."); + ret = FALSE; + } + + break; + + case ROW_TYPE_REDUNDANT: + case ROW_TYPE_COMPACT: + case ROW_TYPE_DEFAULT: + /* Default is COMPACT. */ + row_format_name + = form->s->row_type == ROW_TYPE_REDUNDANT + ? "REDUNDANT" + : "COMPACT"; + + /* Cannot specify KEY_BLOCK_SIZE with these + format specifiers. */ + if (kbs_specified) { + push_warning_printf( + thd, + MYSQL_ERROR::WARN_LEVEL_ERROR, + ER_ILLEGAL_HA_CREATE_OPTION, + "InnoDB: cannot specify" + " ROW_FORMAT = %s with" + " KEY_BLOCK_SIZE.", + row_format_name); + ret = FALSE; + } + + break; + + default: + push_warning(thd, + MYSQL_ERROR::WARN_LEVEL_ERROR, + ER_ILLEGAL_HA_CREATE_OPTION, + "InnoDB: invalid ROW_FORMAT specifier."); + ret = FALSE; + + } + } + + return(ret); +} + +/********************************************************************* +Update create_info. Used in SHOW CREATE TABLE et al. */ +UNIV_INTERN +void +ha_innobase::update_create_info( +/*============================*/ + HA_CREATE_INFO* create_info) /* in/out: create info */ +{ + if (!(create_info->used_fields & HA_CREATE_USED_AUTO)) { + ha_innobase::info(HA_STATUS_AUTO); + create_info->auto_increment_value = stats.auto_increment_value; + } +} + +/********************************************************************* +Creates a new table to an InnoDB database. */ +UNIV_INTERN +int +ha_innobase::create( +/*================*/ + /* out: error number */ + const char* name, /* in: table name */ + TABLE* form, /* in: information on table + columns and indexes */ + HA_CREATE_INFO* create_info) /* in: more information of the + created table, contains also the + create statement string */ +{ + int error; + dict_table_t* innobase_table; + trx_t* parent_trx; + trx_t* trx; + int primary_key_no; + uint i; + char name2[FN_REFLEN]; + char norm_name[FN_REFLEN]; + THD* thd = ha_thd(); + ib_int64_t auto_inc_value; + ulint flags; + /* Cache the value of innodb_file_format, in case it is + modified by another thread while the table is being created. */ + const ulint file_format = srv_file_format; + + DBUG_ENTER("ha_innobase::create"); + + DBUG_ASSERT(thd != NULL); + DBUG_ASSERT(create_info != NULL); + +#ifdef __WIN__ + /* Names passed in from server are in two formats: + 1. <database_name>/<table_name>: for normal table creation + 2. full path: for temp table creation, or sym link + + When srv_file_per_table is on, check for full path pattern, i.e. + X:\dir\..., X is a driver letter, or + \\dir1\dir2\..., UNC path + returns error if it is in full path format, but not creating a temp. + table. Currently InnoDB does not support symbolic link on Windows. */ + + if (srv_file_per_table + && (!create_info->options & HA_LEX_CREATE_TMP_TABLE)) { + + if ((name[1] == ':') + || (name[0] == '\\' && name[1] == '\\')) { + sql_print_error("Cannot create table %s\n", name); + DBUG_RETURN(HA_ERR_GENERIC); + } + } +#endif + + if (form->s->fields > 1000) { + /* The limit probably should be REC_MAX_N_FIELDS - 3 = 1020, + but we play safe here */ + + DBUG_RETURN(HA_ERR_TO_BIG_ROW); + } + + /* Get the transaction associated with the current thd, or create one + if not yet created */ + + parent_trx = check_trx_exists(thd); + + /* In case MySQL calls this in the middle of a SELECT query, release + possible adaptive hash latch to avoid deadlocks of threads */ + + trx_search_latch_release_if_reserved(parent_trx); + + trx = innobase_trx_allocate(thd); + + if (lower_case_table_names) { + srv_lower_case_table_names = TRUE; + } else { + srv_lower_case_table_names = FALSE; + } + + strcpy(name2, name); + + normalize_table_name(norm_name, name2); + + /* Latch the InnoDB data dictionary exclusively so that no deadlocks + or lock waits can happen in it during a table create operation. + Drop table etc. do this latching in row0mysql.c. */ + + row_mysql_lock_data_dictionary(trx); + + /* Create the table definition in InnoDB */ + + flags = 0; + + /* Validate create options if innodb_strict_mode is set. */ + if (!create_options_are_valid(thd, form, create_info)) { + error = ER_ILLEGAL_HA_CREATE_OPTION; + goto cleanup; + } + + if (create_info->key_block_size + || (create_info->used_fields & HA_CREATE_USED_KEY_BLOCK_SIZE)) { + /* Determine the page_zip.ssize corresponding to the + requested page size (key_block_size) in kilobytes. */ + + ulint ssize, ksize; + ulint key_block_size = create_info->key_block_size; + + for (ssize = ksize = 1; ssize <= DICT_TF_ZSSIZE_MAX; + ssize++, ksize <<= 1) { + if (key_block_size == ksize) { + flags = ssize << DICT_TF_ZSSIZE_SHIFT + | DICT_TF_COMPACT + | DICT_TF_FORMAT_ZIP + << DICT_TF_FORMAT_SHIFT; + break; + } + } + + if (!srv_file_per_table) { + push_warning(thd, MYSQL_ERROR::WARN_LEVEL_WARN, + ER_ILLEGAL_HA_CREATE_OPTION, + "InnoDB: KEY_BLOCK_SIZE" + " requires innodb_file_per_table."); + flags = 0; + } + + if (file_format < DICT_TF_FORMAT_ZIP) { + push_warning(thd, MYSQL_ERROR::WARN_LEVEL_WARN, + ER_ILLEGAL_HA_CREATE_OPTION, + "InnoDB: KEY_BLOCK_SIZE" + " requires innodb_file_format >" + " Antelope."); + flags = 0; + } + + if (!flags) { + push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_WARN, + ER_ILLEGAL_HA_CREATE_OPTION, + "InnoDB: ignoring" + " KEY_BLOCK_SIZE=%lu.", + create_info->key_block_size); + } + } + + if (create_info->used_fields & HA_CREATE_USED_ROW_FORMAT) { + if (flags) { + /* KEY_BLOCK_SIZE was specified. */ + if (form->s->row_type != ROW_TYPE_COMPRESSED) { + /* ROW_FORMAT other than COMPRESSED + ignores KEY_BLOCK_SIZE. It does not + make sense to reject conflicting + KEY_BLOCK_SIZE and ROW_FORMAT, because + such combinations can be obtained + with ALTER TABLE anyway. */ + push_warning_printf( + thd, + MYSQL_ERROR::WARN_LEVEL_WARN, + ER_ILLEGAL_HA_CREATE_OPTION, + "InnoDB: ignoring KEY_BLOCK_SIZE=%lu" + " unless ROW_FORMAT=COMPRESSED.", + create_info->key_block_size); + flags = 0; + } + } else { + /* No KEY_BLOCK_SIZE */ + if (form->s->row_type == ROW_TYPE_COMPRESSED) { + /* ROW_FORMAT=COMPRESSED without + KEY_BLOCK_SIZE implies half the + maximum KEY_BLOCK_SIZE. */ + flags = (DICT_TF_ZSSIZE_MAX - 1) + << DICT_TF_ZSSIZE_SHIFT + | DICT_TF_COMPACT + | DICT_TF_FORMAT_ZIP + << DICT_TF_FORMAT_SHIFT; +#if DICT_TF_ZSSIZE_MAX < 1 +# error "DICT_TF_ZSSIZE_MAX < 1" +#endif + } + } + + switch (form->s->row_type) { + const char* row_format_name; + case ROW_TYPE_REDUNDANT: + break; + case ROW_TYPE_COMPRESSED: + case ROW_TYPE_DYNAMIC: + row_format_name + = form->s->row_type == ROW_TYPE_COMPRESSED + ? "COMPRESSED" + : "DYNAMIC"; + + if (!srv_file_per_table) { + push_warning_printf( + thd, + MYSQL_ERROR::WARN_LEVEL_WARN, + ER_ILLEGAL_HA_CREATE_OPTION, + "InnoDB: ROW_FORMAT=%s" + " requires innodb_file_per_table.", + row_format_name); + } else if (file_format < DICT_TF_FORMAT_ZIP) { + push_warning_printf( + thd, + MYSQL_ERROR::WARN_LEVEL_WARN, + ER_ILLEGAL_HA_CREATE_OPTION, + "InnoDB: ROW_FORMAT=%s" + " requires innodb_file_format >" + " Antelope.", + row_format_name); + } else { + flags |= DICT_TF_COMPACT + | (DICT_TF_FORMAT_ZIP + << DICT_TF_FORMAT_SHIFT); + break; + } + + /* fall through */ + case ROW_TYPE_NOT_USED: + case ROW_TYPE_FIXED: + default: + push_warning(thd, + MYSQL_ERROR::WARN_LEVEL_WARN, + ER_ILLEGAL_HA_CREATE_OPTION, + "InnoDB: assuming ROW_FORMAT=COMPACT."); + case ROW_TYPE_DEFAULT: + case ROW_TYPE_COMPACT: + flags = DICT_TF_COMPACT; + break; + } + } else if (!flags) { + /* No KEY_BLOCK_SIZE or ROW_FORMAT specified: + use ROW_FORMAT=COMPACT by default. */ + flags = DICT_TF_COMPACT; + } + + error = create_table_def(trx, form, norm_name, + create_info->options & HA_LEX_CREATE_TMP_TABLE ? name2 : NULL, + flags); + + if (error) { + goto cleanup; + } + + /* Look for a primary key */ + + primary_key_no= (form->s->primary_key != MAX_KEY ? + (int) form->s->primary_key : + -1); + + /* Our function row_get_mysql_key_number_for_index assumes + the primary key is always number 0, if it exists */ + + DBUG_ASSERT(primary_key_no == -1 || primary_key_no == 0); + + /* Create the keys */ + + if (form->s->keys == 0 || primary_key_no == -1) { + /* Create an index which is used as the clustered index; + order the rows by their row id which is internally generated + by InnoDB */ + + error = create_clustered_index_when_no_primary( + trx, flags, norm_name); + if (error) { + goto cleanup; + } + } + + if (primary_key_no != -1) { + /* In InnoDB the clustered index must always be created + first */ + if ((error = create_index(trx, form, flags, norm_name, + (uint) primary_key_no))) { + goto cleanup; + } + } + + for (i = 0; i < form->s->keys; i++) { + + if (i != (uint) primary_key_no) { + + if ((error = create_index(trx, form, flags, norm_name, + i))) { + goto cleanup; + } + } + } + + if (*trx->mysql_query_str) { + error = row_table_add_foreign_constraints(trx, + *trx->mysql_query_str, norm_name, + create_info->options & HA_LEX_CREATE_TMP_TABLE); + + error = convert_error_code_to_mysql(error, flags, NULL); + + if (error) { + goto cleanup; + } + } + + innobase_commit_low(trx); + + row_mysql_unlock_data_dictionary(trx); + + /* Flush the log to reduce probability that the .frm files and + the InnoDB data dictionary get out-of-sync if the user runs + with innodb_flush_log_at_trx_commit = 0 */ + + log_buffer_flush_to_disk(); + + innobase_table = dict_table_get(norm_name, FALSE); + + DBUG_ASSERT(innobase_table != 0); + + if (innobase_table) { + /* We update the highest file format in the system table + space, if this table has higher file format setting. */ + + trx_sys_file_format_max_upgrade( + (const char**) &innobase_file_format_check, + dict_table_get_format(innobase_table)); + } + + /* Note: We can't call update_thd() as prebuilt will not be + setup at this stage and so we use thd. */ + + /* We need to copy the AUTOINC value from the old table if + this is an ALTER TABLE. */ + + if (((create_info->used_fields & HA_CREATE_USED_AUTO) + || thd_sql_command(thd) == SQLCOM_ALTER_TABLE) + && create_info->auto_increment_value != 0) { + + /* Query was ALTER TABLE...AUTO_INCREMENT = x; or + CREATE TABLE ...AUTO_INCREMENT = x; Find out a table + definition from the dictionary and get the current value + of the auto increment field. Set a new value to the + auto increment field if the value is greater than the + maximum value in the column. */ + + auto_inc_value = create_info->auto_increment_value; + + dict_table_autoinc_lock(innobase_table); + dict_table_autoinc_initialize(innobase_table, auto_inc_value); + dict_table_autoinc_unlock(innobase_table); + } + + /* Tell the InnoDB server that there might be work for + utility threads: */ + + srv_active_wake_master_thread(); + + trx_free_for_mysql(trx); + + DBUG_RETURN(0); + +cleanup: + innobase_commit_low(trx); + + row_mysql_unlock_data_dictionary(trx); + + trx_free_for_mysql(trx); + + DBUG_RETURN(error); +} + +/********************************************************************* +Discards or imports an InnoDB tablespace. */ +UNIV_INTERN +int +ha_innobase::discard_or_import_tablespace( +/*======================================*/ + /* out: 0 == success, -1 == error */ + my_bool discard) /* in: TRUE if discard, else import */ +{ + dict_table_t* dict_table; + trx_t* trx; + int err; + + DBUG_ENTER("ha_innobase::discard_or_import_tablespace"); + + ut_a(prebuilt->trx); + ut_a(prebuilt->trx->magic_n == TRX_MAGIC_N); + ut_a(prebuilt->trx == thd_to_trx(ha_thd())); + + dict_table = prebuilt->table; + trx = prebuilt->trx; + + if (discard) { + err = row_discard_tablespace_for_mysql(dict_table->name, trx); + } else { + err = row_import_tablespace_for_mysql(dict_table->name, trx); + } + + err = convert_error_code_to_mysql(err, dict_table->flags, NULL); + + DBUG_RETURN(err); +} + +/********************************************************************* +Deletes all rows of an InnoDB table. */ +UNIV_INTERN +int +ha_innobase::delete_all_rows(void) +/*==============================*/ + /* out: error number */ +{ + int error; + + DBUG_ENTER("ha_innobase::delete_all_rows"); + + /* Get the transaction associated with the current thd, or create one + if not yet created, and update prebuilt->trx */ + + update_thd(ha_thd()); + + if (thd_sql_command(user_thd) != SQLCOM_TRUNCATE) { + fallback: + /* We only handle TRUNCATE TABLE t as a special case. + DELETE FROM t will have to use ha_innobase::delete_row(), + because DELETE is transactional while TRUNCATE is not. */ + DBUG_RETURN(my_errno=HA_ERR_WRONG_COMMAND); + } + + /* Truncate the table in InnoDB */ + + error = row_truncate_table_for_mysql(prebuilt->table, prebuilt->trx); + if (error == DB_ERROR) { + /* Cannot truncate; resort to ha_innobase::delete_row() */ + goto fallback; + } + + error = convert_error_code_to_mysql(error, prebuilt->table->flags, + NULL); + + DBUG_RETURN(error); +} + +/********************************************************************* +Drops a table from an InnoDB database. Before calling this function, +MySQL calls innobase_commit to commit the transaction of the current user. +Then the current user cannot have locks set on the table. Drop table +operation inside InnoDB will remove all locks any user has on the table +inside InnoDB. */ +UNIV_INTERN +int +ha_innobase::delete_table( +/*======================*/ + /* out: error number */ + const char* name) /* in: table name */ +{ + ulint name_len; + int error; + trx_t* parent_trx; + trx_t* trx; + THD *thd = ha_thd(); + char norm_name[1000]; + + DBUG_ENTER("ha_innobase::delete_table"); + + /* Strangely, MySQL passes the table name without the '.frm' + extension, in contrast to ::create */ + normalize_table_name(norm_name, name); + + if (IS_MAGIC_TABLE_AND_USER_DENIED_ACCESS(norm_name, thd)) { + DBUG_RETURN(HA_ERR_GENERIC); + } + + /* Get the transaction associated with the current thd, or create one + if not yet created */ + + parent_trx = check_trx_exists(thd); + + /* In case MySQL calls this in the middle of a SELECT query, release + possible adaptive hash latch to avoid deadlocks of threads */ + + trx_search_latch_release_if_reserved(parent_trx); + + trx = innobase_trx_allocate(thd); + + if (lower_case_table_names) { + srv_lower_case_table_names = TRUE; + } else { + srv_lower_case_table_names = FALSE; + } + + name_len = strlen(name); + + ut_a(name_len < 1000); + + /* Drop the table in InnoDB */ + + error = row_drop_table_for_mysql(norm_name, trx, + thd_sql_command(thd) + == SQLCOM_DROP_DB); + + /* Flush the log to reduce probability that the .frm files and + the InnoDB data dictionary get out-of-sync if the user runs + with innodb_flush_log_at_trx_commit = 0 */ + + log_buffer_flush_to_disk(); + + /* Tell the InnoDB server that there might be work for + utility threads: */ + + srv_active_wake_master_thread(); + + innobase_commit_low(trx); + + trx_free_for_mysql(trx); + + error = convert_error_code_to_mysql(error, 0, NULL); + + DBUG_RETURN(error); +} + +/********************************************************************* +Removes all tables in the named database inside InnoDB. */ +static +void +innobase_drop_database( +/*===================*/ + /* out: error number */ + handlerton *hton, /* in: handlerton of Innodb */ + char* path) /* in: database path; inside InnoDB the name + of the last directory in the path is used as + the database name: for example, in 'mysql/data/test' + the database name is 'test' */ +{ + ulint len = 0; + trx_t* trx; + char* ptr; + int error; + char* namebuf; + THD* thd = current_thd; + + /* Get the transaction associated with the current thd, or create one + if not yet created */ + + DBUG_ASSERT(hton == innodb_hton_ptr); + + /* In the Windows plugin, thd = current_thd is always NULL */ + if (thd) { + trx_t* parent_trx = check_trx_exists(thd); + + /* In case MySQL calls this in the middle of a SELECT + query, release possible adaptive hash latch to avoid + deadlocks of threads */ + + trx_search_latch_release_if_reserved(parent_trx); + } + + ptr = strend(path) - 2; + + while (ptr >= path && *ptr != '\\' && *ptr != '/') { + ptr--; + len++; + } + + ptr++; + namebuf = (char*) my_malloc((uint) len + 2, MYF(0)); + + memcpy(namebuf, ptr, len); + namebuf[len] = '/'; + namebuf[len + 1] = '\0'; +#ifdef __WIN__ + innobase_casedn_str(namebuf); +#endif +#if defined __WIN__ && !defined MYSQL_SERVER + /* In the Windows plugin, thd = current_thd is always NULL */ + trx = trx_allocate_for_mysql(); + trx->mysql_thd = NULL; + trx->mysql_query_str = NULL; +#else + trx = innobase_trx_allocate(thd); +#endif + error = row_drop_database_for_mysql(namebuf, trx); + my_free(namebuf, MYF(0)); + + /* Flush the log to reduce probability that the .frm files and + the InnoDB data dictionary get out-of-sync if the user runs + with innodb_flush_log_at_trx_commit = 0 */ + + log_buffer_flush_to_disk(); + + /* Tell the InnoDB server that there might be work for + utility threads: */ + + srv_active_wake_master_thread(); + + innobase_commit_low(trx); + trx_free_for_mysql(trx); +} +/************************************************************************* +Renames an InnoDB table. */ +static +int +innobase_rename_table( +/*==================*/ + /* out: 0 or error code */ + trx_t* trx, /* in: transaction */ + const char* from, /* in: old name of the table */ + const char* to, /* in: new name of the table */ + ibool lock_and_commit) + /* in: TRUE=lock data dictionary and commit */ +{ + int error; + char* norm_to; + char* norm_from; + DBUG_ENTER("innobase_rename_table"); + + if (lower_case_table_names) { + srv_lower_case_table_names = TRUE; + } else { + srv_lower_case_table_names = FALSE; + } + + // Magic number 64 arbitrary + norm_to = (char*) my_malloc(strlen(to) + 64, MYF(0)); + norm_from = (char*) my_malloc(strlen(from) + 64, MYF(0)); + + normalize_table_name(norm_to, to); + normalize_table_name(norm_from, from); + + /* Serialize data dictionary operations with dictionary mutex: + no deadlocks can occur then in these operations */ + + if (lock_and_commit) { + row_mysql_lock_data_dictionary(trx); + } + + error = row_rename_table_for_mysql( + norm_from, norm_to, trx, lock_and_commit); + + if (error != DB_SUCCESS) { + FILE* ef = dict_foreign_err_file; + + DBUG_PRINT("info", ("rename failed: %d", error)); + fputs("InnoDB: Renaming table ", ef); + ut_print_name(ef, trx, TRUE, norm_from); + fputs(" to ", ef); + ut_print_name(ef, trx, TRUE, norm_to); + fputs(" failed!\n", ef); + } + + if (lock_and_commit) { + row_mysql_unlock_data_dictionary(trx); + + /* Flush the log to reduce probability that the .frm + files and the InnoDB data dictionary get out-of-sync + if the user runs with innodb_flush_log_at_trx_commit = 0 */ + + log_buffer_flush_to_disk(); + } + + my_free(norm_to, MYF(0)); + my_free(norm_from, MYF(0)); + + DBUG_RETURN(error); +} +/************************************************************************* +Renames an InnoDB table. */ +UNIV_INTERN +int +ha_innobase::rename_table( +/*======================*/ + /* out: 0 or error code */ + const char* from, /* in: old name of the table */ + const char* to) /* in: new name of the table */ +{ + trx_t* trx; + int error; + trx_t* parent_trx; + THD* thd = ha_thd(); + + DBUG_ENTER("ha_innobase::rename_table"); + + /* Get the transaction associated with the current thd, or create one + if not yet created */ + + parent_trx = check_trx_exists(thd); + + /* In case MySQL calls this in the middle of a SELECT query, release + possible adaptive hash latch to avoid deadlocks of threads */ + + trx_search_latch_release_if_reserved(parent_trx); + + trx = innobase_trx_allocate(thd); + + error = innobase_rename_table(trx, from, to, TRUE); + + /* Tell the InnoDB server that there might be work for + utility threads: */ + + srv_active_wake_master_thread(); + + innobase_commit_low(trx); + trx_free_for_mysql(trx); + + error = convert_error_code_to_mysql(error, 0, NULL); + + DBUG_RETURN(error); +} + +/************************************************************************* +Estimates the number of index records in a range. */ +UNIV_INTERN +ha_rows +ha_innobase::records_in_range( +/*==========================*/ + /* out: estimated number of + rows */ + uint keynr, /* in: index number */ + key_range *min_key, /* in: start key value of the + range, may also be 0 */ + key_range *max_key) /* in: range end key val, may + also be 0 */ +{ + KEY* key; + dict_index_t* index; + uchar* key_val_buff2 = (uchar*) my_malloc( + table->s->reclength + + table->s->max_key_length + 100, + MYF(MY_FAE)); + ulint buff2_len = table->s->reclength + + table->s->max_key_length + 100; + dtuple_t* range_start; + dtuple_t* range_end; + ib_int64_t n_rows; + ulint mode1; + ulint mode2; + mem_heap_t* heap; + + DBUG_ENTER("records_in_range"); + + ut_a(prebuilt->trx == thd_to_trx(ha_thd())); + + prebuilt->trx->op_info = (char*)"estimating records in index range"; + + /* In case MySQL calls this in the middle of a SELECT query, release + possible adaptive hash latch to avoid deadlocks of threads */ + + trx_search_latch_release_if_reserved(prebuilt->trx); + + active_index = keynr; + + key = table->key_info + active_index; + + index = dict_table_get_index_on_name(prebuilt->table, key->name); + + /* MySQL knows about this index and so we must be able to find it.*/ + ut_a(index); + + heap = mem_heap_create(2 * (key->key_parts * sizeof(dfield_t) + + sizeof(dtuple_t))); + + range_start = dtuple_create(heap, key->key_parts); + dict_index_copy_types(range_start, index, key->key_parts); + + range_end = dtuple_create(heap, key->key_parts); + dict_index_copy_types(range_end, index, key->key_parts); + + row_sel_convert_mysql_key_to_innobase( + range_start, (byte*) key_val_buff, + (ulint)upd_and_key_val_buff_len, + index, + (byte*) (min_key ? min_key->key : + (const uchar*) 0), + (ulint) (min_key ? min_key->length : 0), + prebuilt->trx); + + row_sel_convert_mysql_key_to_innobase( + range_end, (byte*) key_val_buff2, + buff2_len, index, + (byte*) (max_key ? max_key->key : + (const uchar*) 0), + (ulint) (max_key ? max_key->length : 0), + prebuilt->trx); + + mode1 = convert_search_mode_to_innobase(min_key ? min_key->flag : + HA_READ_KEY_EXACT); + mode2 = convert_search_mode_to_innobase(max_key ? max_key->flag : + HA_READ_KEY_EXACT); + + if (mode1 != PAGE_CUR_UNSUPP && mode2 != PAGE_CUR_UNSUPP) { + + n_rows = btr_estimate_n_rows_in_range(index, range_start, + mode1, range_end, + mode2); + } else { + + n_rows = HA_POS_ERROR; + } + + mem_heap_free(heap); + + my_free(key_val_buff2, MYF(0)); + + prebuilt->trx->op_info = (char*)""; + + /* The MySQL optimizer seems to believe an estimate of 0 rows is + always accurate and may return the result 'Empty set' based on that. + The accuracy is not guaranteed, and even if it were, for a locking + read we should anyway perform the search to set the next-key lock. + Add 1 to the value to make sure MySQL does not make the assumption! */ + + if (n_rows == 0) { + n_rows = 1; + } + + DBUG_RETURN((ha_rows) n_rows); +} + +/************************************************************************* +Gives an UPPER BOUND to the number of rows in a table. This is used in +filesort.cc. */ +UNIV_INTERN +ha_rows +ha_innobase::estimate_rows_upper_bound(void) +/*======================================*/ + /* out: upper bound of rows */ +{ + dict_index_t* index; + ulonglong estimate; + ulonglong local_data_file_length; + + DBUG_ENTER("estimate_rows_upper_bound"); + + /* We do not know if MySQL can call this function before calling + external_lock(). To be safe, update the thd of the current table + handle. */ + + update_thd(ha_thd()); + + prebuilt->trx->op_info = (char*) + "calculating upper bound for table rows"; + + /* In case MySQL calls this in the middle of a SELECT query, release + possible adaptive hash latch to avoid deadlocks of threads */ + + trx_search_latch_release_if_reserved(prebuilt->trx); + + index = dict_table_get_first_index(prebuilt->table); + + ut_a(index->stat_n_leaf_pages > 0); + + local_data_file_length = + ((ulonglong) index->stat_n_leaf_pages) * UNIV_PAGE_SIZE; + + + /* Calculate a minimum length for a clustered index record and from + that an upper bound for the number of rows. Since we only calculate + new statistics in row0mysql.c when a table has grown by a threshold + factor, we must add a safety factor 2 in front of the formula below. */ + + estimate = 2 * local_data_file_length / + dict_index_calc_min_rec_len(index); + + prebuilt->trx->op_info = (char*)""; + + DBUG_RETURN((ha_rows) estimate); +} + +/************************************************************************* +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. */ +UNIV_INTERN +double +ha_innobase::scan_time() +/*====================*/ + /* out: estimated time measured in disk seeks */ +{ + /* Since MySQL seems to favor table scans too much over index + searches, we pretend that a sequential read takes the same time + as a random disk read, that is, we do not divide the following + by 10, which would be physically realistic. */ + + return((double) (prebuilt->table->stat_clustered_index_size)); +} + +/********************************************************************** +Calculate the time it takes to read a set of ranges through an index +This enables us to optimise reads for clustered indexes. */ +UNIV_INTERN +double +ha_innobase::read_time( +/*===================*/ + /* out: estimated time measured in disk seeks */ + uint index, /* in: key number */ + uint ranges, /* in: how many ranges */ + ha_rows rows) /* in: estimated number of rows in the ranges */ +{ + ha_rows total_rows; + double time_for_scan; + + if (index != table->s->primary_key) { + /* Not clustered */ + return(handler::read_time(index, ranges, rows)); + } + + if (rows <= 2) { + + return((double) rows); + } + + /* Assume that the read time is proportional to the scan time for all + rows + at most one seek per range. */ + + time_for_scan = scan_time(); + + if ((total_rows = estimate_rows_upper_bound()) < rows) { + + return(time_for_scan); + } + + return(ranges + (double) rows / (double) total_rows * time_for_scan); +} + +/************************************************************************* +Returns statistics information of the table to the MySQL interpreter, +in various fields of the handle object. */ +UNIV_INTERN +int +ha_innobase::info( +/*==============*/ + uint flag) /* in: what information MySQL requests */ +{ + dict_table_t* ib_table; + dict_index_t* index; + ha_rows rec_per_key; + ib_int64_t n_rows; + ulong j; + ulong i; + char path[FN_REFLEN]; + os_file_stat_t stat_info; + + DBUG_ENTER("info"); + + /* If we are forcing recovery at a high level, we will suppress + statistics calculation on tables, because that may crash the + server if an index is badly corrupted. */ + + if (srv_force_recovery >= SRV_FORCE_NO_IBUF_MERGE) { + + /* We return success (0) instead of HA_ERR_CRASHED, + because we want MySQL to process this query and not + stop, like it would do if it received the error code + HA_ERR_CRASHED. */ + + DBUG_RETURN(0); + } + + /* We do not know if MySQL can call this function before calling + external_lock(). To be safe, update the thd of the current table + handle. */ + + update_thd(ha_thd()); + + /* In case MySQL calls this in the middle of a SELECT query, release + possible adaptive hash latch to avoid deadlocks of threads */ + + prebuilt->trx->op_info = (char*)"returning various info to MySQL"; + + trx_search_latch_release_if_reserved(prebuilt->trx); + + ib_table = prebuilt->table; + + if (flag & HA_STATUS_TIME) { + if (innobase_stats_on_metadata + && (thd_sql_command(user_thd) == SQLCOM_ANALYZE + || srv_stats_auto_update)) { + /* In sql_show we call with this flag: update + then statistics so that they are up-to-date */ + + prebuilt->trx->op_info = "updating table statistics"; + + dict_update_statistics(ib_table); + + prebuilt->trx->op_info = "returning various info to MySQL"; + } + + my_snprintf(path, sizeof(path), "%s/%s%s", + mysql_data_home, ib_table->name, reg_ext); + + unpack_filename(path,path); + + /* Note that we do not know the access time of the table, + nor the CHECK TABLE time, nor the UPDATE or INSERT time. */ + + if (os_file_get_status(path,&stat_info)) { + stats.create_time = stat_info.ctime; + } + } + + if (flag & HA_STATUS_VARIABLE) { + n_rows = ib_table->stat_n_rows; + + /* Because we do not protect stat_n_rows by any mutex in a + delete, it is theoretically possible that the value can be + smaller than zero! TODO: fix this race. + + The MySQL optimizer seems to assume in a left join that n_rows + is an accurate estimate if it is zero. Of course, it is not, + since we do not have any locks on the rows yet at this phase. + Since SHOW TABLE STATUS seems to call this function with the + HA_STATUS_TIME flag set, while the left join optimizer does not + set that flag, we add one to a zero value if the flag is not + set. That way SHOW TABLE STATUS will show the best estimate, + while the optimizer never sees the table empty. */ + + if (n_rows < 0) { + n_rows = 0; + } + + if (n_rows == 0 && !(flag & HA_STATUS_TIME)) { + n_rows++; + } + + /* Fix bug#40386: Not flushing query cache after truncate. + n_rows can not be 0 unless the table is empty, set to 1 + instead. The original problem of bug#29507 is actually + fixed in the server code. */ + if (thd_sql_command(user_thd) == SQLCOM_TRUNCATE) { + + n_rows = 1; + + /* We need to reset the prebuilt value too, otherwise + checks for values greater than the last value written + to the table will fail and the autoinc counter will + not be updated. This will force write_row() into + attempting an update of the table's AUTOINC counter. */ + + prebuilt->autoinc_last_value = 0; + } + + stats.records = (ha_rows)n_rows; + stats.deleted = 0; + stats.data_file_length = ((ulonglong) + ib_table->stat_clustered_index_size) + * UNIV_PAGE_SIZE; + stats.index_file_length = ((ulonglong) + ib_table->stat_sum_of_other_index_sizes) + * UNIV_PAGE_SIZE; + + /* Since fsp_get_available_space_in_free_extents() is + acquiring latches inside InnoDB, we do not call it if we + are asked by MySQL to avoid locking. Another reason to + avoid the call is that it uses quite a lot of CPU. + See Bug#38185. + We do not update delete_length if no locking is requested + so the "old" value can remain. delete_length is initialized + to 0 in the ha_statistics' constructor. */ + if (!(flag & HA_STATUS_NO_LOCK)) { + + /* lock the data dictionary to avoid races with + ibd_file_missing and tablespace_discarded */ + row_mysql_lock_data_dictionary(prebuilt->trx); + + /* ib_table->space must be an existent tablespace */ + if (!ib_table->ibd_file_missing + && !ib_table->tablespace_discarded) { + + stats.delete_length = + fsp_get_available_space_in_free_extents( + ib_table->space) * 1024; + } else { + + THD* thd; + + thd = ha_thd(); + + push_warning_printf( + thd, + MYSQL_ERROR::WARN_LEVEL_WARN, + ER_CANT_GET_STAT, + "InnoDB: Trying to get the free " + "space for table %s but its " + "tablespace has been discarded or " + "the .ibd file is missing. Setting " + "the free space to zero.", + ib_table->name); + + stats.delete_length = 0; + } + + row_mysql_unlock_data_dictionary(prebuilt->trx); + } + + stats.check_time = 0; + + if (stats.records == 0) { + stats.mean_rec_length = 0; + } else { + stats.mean_rec_length = (ulong) (stats.data_file_length / stats.records); + } + } + + if (flag & HA_STATUS_CONST) { + index = dict_table_get_first_index(ib_table); + + if (prebuilt->clust_index_was_generated) { + index = dict_table_get_next_index(index); + } + + for (i = 0; i < table->s->keys; i++) { + if (index == NULL) { + sql_print_error("Table %s contains fewer " + "indexes inside InnoDB than " + "are defined in the MySQL " + ".frm file. Have you mixed up " + ".frm files from different " + "installations? See " +"http://dev.mysql.com/doc/refman/5.1/en/innodb-troubleshooting.html\n", + + ib_table->name); + break; + } + + for (j = 0; j < table->key_info[i].key_parts; j++) { + + if (j + 1 > index->n_uniq) { + sql_print_error( +"Index %s of %s has %lu columns unique inside InnoDB, but MySQL is asking " +"statistics for %lu columns. Have you mixed up .frm files from different " +"installations? " +"See http://dev.mysql.com/doc/refman/5.1/en/innodb-troubleshooting.html\n", + index->name, + ib_table->name, + (unsigned long) + index->n_uniq, j + 1); + break; + } + + if (index->stat_n_diff_key_vals[j + 1] == 0) { + + rec_per_key = stats.records; + } else { + rec_per_key = (ha_rows)(stats.records / + index->stat_n_diff_key_vals[j + 1]); + } + + /* Since MySQL seems to favor table scans + too much over index searches, we pretend + index selectivity is 2 times better than + our estimate: */ + + rec_per_key = rec_per_key / 2; + + if (rec_per_key == 0) { + rec_per_key = 1; + } + + table->key_info[i].rec_per_key[j]= + rec_per_key >= ~(ulong) 0 ? ~(ulong) 0 : + (ulong) rec_per_key; + } + + index = dict_table_get_next_index(index); + } + } + + if (flag & HA_STATUS_ERRKEY) { + const dict_index_t* err_index; + + ut_a(prebuilt->trx); + ut_a(prebuilt->trx->magic_n == TRX_MAGIC_N); + + err_index = trx_get_error_info(prebuilt->trx); + + if (err_index) { + errkey = (unsigned int) + row_get_mysql_key_number_for_index(err_index); + } else { + errkey = (unsigned int) prebuilt->trx->error_key_num; + } + } + + if ((flag & HA_STATUS_AUTO) && table->found_next_number_field) { + stats.auto_increment_value = innobase_peek_autoinc(); + } + + prebuilt->trx->op_info = (char*)""; + + DBUG_RETURN(0); +} + +/************************************************************************** +Updates index cardinalities of the table, based on 8 random dives into +each index tree. This does NOT calculate exact statistics on the table. */ +UNIV_INTERN +int +ha_innobase::analyze( +/*=================*/ + /* out: returns always 0 (success) */ + THD* thd, /* in: connection thread handle */ + HA_CHECK_OPT* check_opt) /* in: currently ignored */ +{ + /* Simply call ::info() with all the flags */ + info(HA_STATUS_TIME | HA_STATUS_CONST | HA_STATUS_VARIABLE); + + return(0); +} + +/************************************************************************** +This is mapped to "ALTER TABLE tablename ENGINE=InnoDB", which rebuilds +the table in MySQL. */ +UNIV_INTERN +int +ha_innobase::optimize( +/*==================*/ + THD* thd, /* in: connection thread handle */ + HA_CHECK_OPT* check_opt) /* in: currently ignored */ +{ + return(HA_ADMIN_TRY_ALTER); +} + +/*********************************************************************** +Tries to check that an InnoDB table is not corrupted. If corruption is +noticed, prints to stderr information about it. In case of corruption +may also assert a failure and crash the server. */ +UNIV_INTERN +int +ha_innobase::check( +/*===============*/ + /* out: HA_ADMIN_CORRUPT or + HA_ADMIN_OK */ + THD* thd, /* in: user thread handle */ + HA_CHECK_OPT* check_opt) /* in: check options, currently + ignored */ +{ + ulint ret; + + DBUG_ASSERT(thd == ha_thd()); + ut_a(prebuilt->trx); + ut_a(prebuilt->trx->magic_n == TRX_MAGIC_N); + ut_a(prebuilt->trx == thd_to_trx(thd)); + + if (prebuilt->mysql_template == NULL) { + /* Build the template; we will use a dummy template + in index scans done in checking */ + + build_template(prebuilt, NULL, table, ROW_MYSQL_WHOLE_ROW); + } + + ret = row_check_table_for_mysql(prebuilt); + + if (ret == DB_SUCCESS) { + return(HA_ADMIN_OK); + } + + return(HA_ADMIN_CORRUPT); +} + +/***************************************************************** +Adds information about free space in the InnoDB tablespace to a table comment +which is printed out when a user calls SHOW TABLE STATUS. Adds also info on +foreign keys. */ +UNIV_INTERN +char* +ha_innobase::update_table_comment( +/*==============================*/ + /* out: table comment + InnoDB free space + + info on foreign keys */ + const char* comment)/* in: table comment defined by user */ +{ + uint length = (uint) strlen(comment); + char* str; + long flen; + + /* We do not know if MySQL can call this function before calling + external_lock(). To be safe, update the thd of the current table + handle. */ + + if (length > 64000 - 3) { + return((char*)comment); /* string too long */ + } + + update_thd(ha_thd()); + + prebuilt->trx->op_info = (char*)"returning table comment"; + + /* In case MySQL calls this in the middle of a SELECT query, release + possible adaptive hash latch to avoid deadlocks of threads */ + + trx_search_latch_release_if_reserved(prebuilt->trx); + str = NULL; + + /* output the data to a temporary file */ + + mutex_enter(&srv_dict_tmpfile_mutex); + rewind(srv_dict_tmpfile); + + fprintf(srv_dict_tmpfile, "InnoDB free: %llu kB", + fsp_get_available_space_in_free_extents( + prebuilt->table->space)); + + dict_print_info_on_foreign_keys(FALSE, srv_dict_tmpfile, + prebuilt->trx, prebuilt->table); + flen = ftell(srv_dict_tmpfile); + if (flen < 0) { + flen = 0; + } else if (length + flen + 3 > 64000) { + flen = 64000 - 3 - length; + } + + /* allocate buffer for the full string, and + read the contents of the temporary file */ + + str = (char*) my_malloc(length + flen + 3, MYF(0)); + + if (str) { + char* pos = str + length; + if (length) { + memcpy(str, comment, length); + *pos++ = ';'; + *pos++ = ' '; + } + rewind(srv_dict_tmpfile); + flen = (uint) fread(pos, 1, flen, srv_dict_tmpfile); + pos[flen] = 0; + } + + mutex_exit(&srv_dict_tmpfile_mutex); + + prebuilt->trx->op_info = (char*)""; + + return(str ? str : (char*) comment); +} + +/*********************************************************************** +Gets the foreign key create info for a table stored in InnoDB. */ +UNIV_INTERN +char* +ha_innobase::get_foreign_key_create_info(void) +/*==========================================*/ + /* out, own: character string in the form which + can be inserted to the CREATE TABLE statement, + MUST be freed with ::free_foreign_key_create_info */ +{ + char* str = 0; + long flen; + + ut_a(prebuilt != NULL); + + /* We do not know if MySQL can call this function before calling + external_lock(). To be safe, update the thd of the current table + handle. */ + + update_thd(ha_thd()); + + prebuilt->trx->op_info = (char*)"getting info on foreign keys"; + + /* In case MySQL calls this in the middle of a SELECT query, + release possible adaptive hash latch to avoid + deadlocks of threads */ + + trx_search_latch_release_if_reserved(prebuilt->trx); + + mutex_enter(&srv_dict_tmpfile_mutex); + rewind(srv_dict_tmpfile); + + /* output the data to a temporary file */ + dict_print_info_on_foreign_keys(TRUE, srv_dict_tmpfile, + prebuilt->trx, prebuilt->table); + prebuilt->trx->op_info = (char*)""; + + flen = ftell(srv_dict_tmpfile); + if (flen < 0) { + flen = 0; + } else if (flen > 64000 - 1) { + flen = 64000 - 1; + } + + /* allocate buffer for the string, and + read the contents of the temporary file */ + + str = (char*) my_malloc(flen + 1, MYF(0)); + + if (str) { + rewind(srv_dict_tmpfile); + flen = (uint) fread(str, 1, flen, srv_dict_tmpfile); + str[flen] = 0; + } + + mutex_exit(&srv_dict_tmpfile_mutex); + + return(str); +} + + +UNIV_INTERN +int +ha_innobase::get_foreign_key_list(THD *thd, List<FOREIGN_KEY_INFO> *f_key_list) +{ + dict_foreign_t* foreign; + + DBUG_ENTER("get_foreign_key_list"); + ut_a(prebuilt != NULL); + update_thd(ha_thd()); + prebuilt->trx->op_info = (char*)"getting list of foreign keys"; + trx_search_latch_release_if_reserved(prebuilt->trx); + mutex_enter(&(dict_sys->mutex)); + foreign = UT_LIST_GET_FIRST(prebuilt->table->foreign_list); + + while (foreign != NULL) { + uint i; + FOREIGN_KEY_INFO f_key_info; + LEX_STRING *name= 0; + uint ulen; + char uname[NAME_LEN+1]; /* Unencoded name */ + char db_name[NAME_LEN+1]; + const char *tmp_buff; + + tmp_buff= foreign->id; + i= 0; + while (tmp_buff[i] != '/') + i++; + tmp_buff+= i + 1; + f_key_info.forein_id = thd_make_lex_string(thd, 0, + tmp_buff, (uint) strlen(tmp_buff), 1); + tmp_buff= foreign->referenced_table_name; + + /* Database name */ + i= 0; + while (tmp_buff[i] != '/') + { + db_name[i]= tmp_buff[i]; + i++; + } + db_name[i]= 0; + ulen= filename_to_tablename(db_name, uname, sizeof(uname)); + f_key_info.referenced_db = thd_make_lex_string(thd, 0, + uname, ulen, 1); + + /* Table name */ + tmp_buff+= i + 1; + ulen= filename_to_tablename(tmp_buff, uname, sizeof(uname)); + f_key_info.referenced_table = thd_make_lex_string(thd, 0, + uname, ulen, 1); + + for (i= 0;;) { + tmp_buff= foreign->foreign_col_names[i]; + name = thd_make_lex_string(thd, name, + tmp_buff, (uint) strlen(tmp_buff), 1); + f_key_info.foreign_fields.push_back(name); + tmp_buff= foreign->referenced_col_names[i]; + name = thd_make_lex_string(thd, name, + tmp_buff, (uint) strlen(tmp_buff), 1); + f_key_info.referenced_fields.push_back(name); + if (++i >= foreign->n_fields) + break; + } + + ulong length; + if (foreign->type & DICT_FOREIGN_ON_DELETE_CASCADE) + { + length=7; + tmp_buff= "CASCADE"; + } + else if (foreign->type & DICT_FOREIGN_ON_DELETE_SET_NULL) + { + length=8; + tmp_buff= "SET NULL"; + } + else if (foreign->type & DICT_FOREIGN_ON_DELETE_NO_ACTION) + { + length=9; + tmp_buff= "NO ACTION"; + } + else + { + length=8; + tmp_buff= "RESTRICT"; + } + f_key_info.delete_method = thd_make_lex_string( + thd, f_key_info.delete_method, tmp_buff, length, 1); + + + if (foreign->type & DICT_FOREIGN_ON_UPDATE_CASCADE) + { + length=7; + tmp_buff= "CASCADE"; + } + else if (foreign->type & DICT_FOREIGN_ON_UPDATE_SET_NULL) + { + length=8; + tmp_buff= "SET NULL"; + } + else if (foreign->type & DICT_FOREIGN_ON_UPDATE_NO_ACTION) + { + length=9; + tmp_buff= "NO ACTION"; + } + else + { + length=8; + tmp_buff= "RESTRICT"; + } + f_key_info.update_method = thd_make_lex_string( + thd, f_key_info.update_method, tmp_buff, length, 1); + if (foreign->referenced_index && + foreign->referenced_index->name) + { + f_key_info.referenced_key_name = thd_make_lex_string( + thd, f_key_info.referenced_key_name, + foreign->referenced_index->name, + (uint) strlen(foreign->referenced_index->name), 1); + } + else + f_key_info.referenced_key_name= 0; + + FOREIGN_KEY_INFO *pf_key_info = (FOREIGN_KEY_INFO *) + thd_memdup(thd, &f_key_info, sizeof(FOREIGN_KEY_INFO)); + f_key_list->push_back(pf_key_info); + foreign = UT_LIST_GET_NEXT(foreign_list, foreign); + } + mutex_exit(&(dict_sys->mutex)); + prebuilt->trx->op_info = (char*)""; + + DBUG_RETURN(0); +} + +/********************************************************************* +Checks if ALTER TABLE may change the storage engine of the table. +Changing storage engines is not allowed for tables for which there +are foreign key constraints (parent or child tables). */ +UNIV_INTERN +bool +ha_innobase::can_switch_engines(void) +/*=================================*/ +{ + bool can_switch; + + DBUG_ENTER("ha_innobase::can_switch_engines"); + + ut_a(prebuilt->trx == thd_to_trx(ha_thd())); + + prebuilt->trx->op_info = + "determining if there are foreign key constraints"; + row_mysql_lock_data_dictionary(prebuilt->trx); + + can_switch = !UT_LIST_GET_FIRST(prebuilt->table->referenced_list) + && !UT_LIST_GET_FIRST(prebuilt->table->foreign_list); + + row_mysql_unlock_data_dictionary(prebuilt->trx); + prebuilt->trx->op_info = ""; + + DBUG_RETURN(can_switch); +} + +/*********************************************************************** +Checks if a table is referenced by a foreign key. The MySQL manual states that +a REPLACE is either equivalent to an INSERT, or DELETE(s) + INSERT. Only a +delete is then allowed internally to resolve a duplicate key conflict in +REPLACE, not an update. */ +UNIV_INTERN +uint +ha_innobase::referenced_by_foreign_key(void) +/*========================================*/ + /* out: > 0 if referenced by a FOREIGN KEY */ +{ + if (dict_table_is_referenced_by_foreign_key(prebuilt->table)) { + + return(1); + } + + return(0); +} + +/*********************************************************************** +Frees the foreign key create info for a table stored in InnoDB, if it is +non-NULL. */ +UNIV_INTERN +void +ha_innobase::free_foreign_key_create_info( +/*======================================*/ + char* str) /* in, own: create info string to free */ +{ + if (str) { + my_free(str, MYF(0)); + } +} + +/*********************************************************************** +Tells something additional to the handler about how to do things. */ +UNIV_INTERN +int +ha_innobase::extra( +/*===============*/ + /* out: 0 or error number */ + enum ha_extra_function operation) + /* in: HA_EXTRA_FLUSH or some other flag */ +{ + /* Warning: since it is not sure that MySQL calls external_lock + before calling this function, the trx field in prebuilt can be + obsolete! */ + + switch (operation) { + case HA_EXTRA_FLUSH: + if (prebuilt->blob_heap) { + row_mysql_prebuilt_free_blob_heap(prebuilt); + } + break; + case HA_EXTRA_RESET_STATE: + reset_template(prebuilt); + break; + case HA_EXTRA_NO_KEYREAD: + prebuilt->read_just_key = 0; + break; + case HA_EXTRA_KEYREAD: + prebuilt->read_just_key = 1; + break; + case HA_EXTRA_KEYREAD_PRESERVE_FIELDS: + prebuilt->keep_other_fields_on_keyread = 1; + break; + + /* IMPORTANT: prebuilt->trx can be obsolete in + this method, because it is not sure that MySQL + calls external_lock before this method with the + parameters below. We must not invoke update_thd() + either, because the calling threads may change. + CAREFUL HERE, OR MEMORY CORRUPTION MAY OCCUR! */ + case HA_EXTRA_IGNORE_DUP_KEY: + thd_to_trx(ha_thd())->duplicates |= TRX_DUP_IGNORE; + break; + case HA_EXTRA_WRITE_CAN_REPLACE: + thd_to_trx(ha_thd())->duplicates |= TRX_DUP_REPLACE; + break; + case HA_EXTRA_WRITE_CANNOT_REPLACE: + thd_to_trx(ha_thd())->duplicates &= ~TRX_DUP_REPLACE; + break; + case HA_EXTRA_NO_IGNORE_DUP_KEY: + thd_to_trx(ha_thd())->duplicates &= + ~(TRX_DUP_IGNORE | TRX_DUP_REPLACE); + break; + default:/* Do nothing */ + ; + } + + return(0); +} + +UNIV_INTERN +int +ha_innobase::reset() +{ + if (prebuilt->blob_heap) { + row_mysql_prebuilt_free_blob_heap(prebuilt); + } + + reset_template(prebuilt); + + /* TODO: This should really be reset in reset_template() but for now + it's safer to do it explicitly here. */ + + /* This is a statement level counter. */ + prebuilt->autoinc_last_value = 0; + + return(0); +} + +/********************************************************************** +MySQL calls this function at the start of each SQL statement inside LOCK +TABLES. Inside LOCK TABLES the ::external_lock method does not work to +mark SQL statement borders. Note also a special case: if a temporary table +is created inside LOCK TABLES, MySQL has not called external_lock() at all +on that table. +MySQL-5.0 also calls this before each statement in an execution of a stored +procedure. To make the execution more deterministic for binlogging, MySQL-5.0 +locks all tables involved in a stored procedure with full explicit table +locks (thd_in_lock_tables(thd) holds in store_lock()) before executing the +procedure. */ +UNIV_INTERN +int +ha_innobase::start_stmt( +/*====================*/ + /* out: 0 or error code */ + THD* thd, /* in: handle to the user thread */ + thr_lock_type lock_type) +{ + trx_t* trx; + + update_thd(thd); + + trx = prebuilt->trx; + + /* Here we release the search latch and the InnoDB thread FIFO ticket + if they were reserved. They should have been released already at the + end of the previous statement, but because inside LOCK TABLES the + lock count method does not work to mark the end of a SELECT statement, + that may not be the case. We MUST release the search latch before an + INSERT, for example. */ + + innobase_release_stat_resources(trx); + + /* Reset the AUTOINC statement level counter for multi-row INSERTs. */ + trx->n_autoinc_rows = 0; + + prebuilt->sql_stat_start = TRUE; + prebuilt->hint_need_to_fetch_extra_cols = 0; + reset_template(prebuilt); + + if (!prebuilt->mysql_has_locked) { + /* This handle is for a temporary table created inside + this same LOCK TABLES; since MySQL does NOT call external_lock + in this case, we must use x-row locks inside InnoDB to be + prepared for an update of a row */ + + prebuilt->select_lock_type = LOCK_X; + } else { + if (trx->isolation_level != TRX_ISO_SERIALIZABLE + && thd_sql_command(thd) == SQLCOM_SELECT + && lock_type == TL_READ) { + + /* For other than temporary tables, we obtain + no lock for consistent read (plain SELECT). */ + + prebuilt->select_lock_type = LOCK_NONE; + } else { + /* Not a consistent read: restore the + select_lock_type value. The value of + stored_select_lock_type was decided in: + 1) ::store_lock(), + 2) ::external_lock(), + 3) ::init_table_handle_for_HANDLER(), and + 4) ::transactional_table_lock(). */ + + prebuilt->select_lock_type = + prebuilt->stored_select_lock_type; + } + } + + trx->detailed_error[0] = '\0'; + + /* Set the MySQL flag to mark that there is an active transaction */ + if (trx->active_trans == 0) { + + innobase_register_trx_and_stmt(ht, thd); + trx->active_trans = 1; + } else { + innobase_register_stmt(ht, thd); + } + + return(0); +} + +/********************************************************************** +Maps a MySQL trx isolation level code to the InnoDB isolation level code */ +static inline +ulint +innobase_map_isolation_level( +/*=========================*/ + /* out: InnoDB isolation level */ + enum_tx_isolation iso) /* in: MySQL isolation level code */ +{ + switch(iso) { + case ISO_REPEATABLE_READ: return(TRX_ISO_REPEATABLE_READ); + case ISO_READ_COMMITTED: return(TRX_ISO_READ_COMMITTED); + case ISO_SERIALIZABLE: return(TRX_ISO_SERIALIZABLE); + case ISO_READ_UNCOMMITTED: return(TRX_ISO_READ_UNCOMMITTED); + default: ut_a(0); return(0); + } +} + +/********************************************************************** +As MySQL will execute an external lock for every new table it uses when it +starts to process an SQL statement (an exception is when MySQL calls +start_stmt for the handle) we can use this function to store the pointer to +the THD in the handle. We will also use this function to communicate +to InnoDB that a new SQL statement has started and that we must store a +savepoint to our transaction handle, so that we are able to roll back +the SQL statement in case of an error. */ +UNIV_INTERN +int +ha_innobase::external_lock( +/*=======================*/ + /* out: 0 */ + THD* thd, /* in: handle to the user thread */ + int lock_type) /* in: lock type */ +{ + trx_t* trx; + + DBUG_ENTER("ha_innobase::external_lock"); + DBUG_PRINT("enter",("lock_type: %d", lock_type)); + + update_thd(thd); + + /* Statement based binlogging does not work in isolation level + READ UNCOMMITTED and READ COMMITTED since the necessary + locks cannot be taken. In this case, we print an + informative error message and return with an error. */ + if (lock_type == F_WRLCK) + { + ulong const binlog_format= thd_binlog_format(thd); + ulong const tx_isolation = thd_tx_isolation(ha_thd()); + if (tx_isolation <= ISO_READ_COMMITTED && + binlog_format == BINLOG_FORMAT_STMT) + { + char buf[256]; + my_snprintf(buf, sizeof(buf), + "Transaction level '%s' in" + " InnoDB is not safe for binlog mode '%s'", + tx_isolation_names[tx_isolation], + binlog_format_names[binlog_format]); + my_error(ER_BINLOG_LOGGING_IMPOSSIBLE, MYF(0), buf); + DBUG_RETURN(HA_ERR_LOGGING_IMPOSSIBLE); + } + } + + + trx = prebuilt->trx; + + prebuilt->sql_stat_start = TRUE; + prebuilt->hint_need_to_fetch_extra_cols = 0; + + reset_template(prebuilt); + + if (lock_type == F_WRLCK) { + + /* If this is a SELECT, then it is in UPDATE TABLE ... + or SELECT ... FOR UPDATE */ + prebuilt->select_lock_type = LOCK_X; + prebuilt->stored_select_lock_type = LOCK_X; + } + + if (lock_type != F_UNLCK) { + /* MySQL is setting a new table lock */ + + trx->detailed_error[0] = '\0'; + + /* Set the MySQL flag to mark that there is an active + transaction */ + if (trx->active_trans == 0) { + + innobase_register_trx_and_stmt(ht, thd); + trx->active_trans = 1; + } else if (trx->n_mysql_tables_in_use == 0) { + innobase_register_stmt(ht, thd); + } + + if (trx->isolation_level == TRX_ISO_SERIALIZABLE + && prebuilt->select_lock_type == LOCK_NONE + && thd_test_options(thd, + OPTION_NOT_AUTOCOMMIT | OPTION_BEGIN)) { + + /* To get serializable execution, we let InnoDB + conceptually add 'LOCK IN SHARE MODE' to all SELECTs + which otherwise would have been consistent reads. An + exception is consistent reads in the AUTOCOMMIT=1 mode: + we know that they are read-only transactions, and they + can be serialized also if performed as consistent + reads. */ + + prebuilt->select_lock_type = LOCK_S; + prebuilt->stored_select_lock_type = LOCK_S; + } + + /* Starting from 4.1.9, no InnoDB table lock is taken in LOCK + TABLES if AUTOCOMMIT=1. It does not make much sense to acquire + an InnoDB table lock if it is released immediately at the end + of LOCK TABLES, and InnoDB's table locks in that case cause + VERY easily deadlocks. + + We do not set InnoDB table locks if user has not explicitly + requested a table lock. Note that thd_in_lock_tables(thd) + can hold in some cases, e.g., at the start of a stored + procedure call (SQLCOM_CALL). */ + + if (prebuilt->select_lock_type != LOCK_NONE) { + + if (thd_sql_command(thd) == SQLCOM_LOCK_TABLES + && THDVAR(thd, table_locks) + && thd_test_options(thd, OPTION_NOT_AUTOCOMMIT) + && thd_in_lock_tables(thd)) { + + ulint error = row_lock_table_for_mysql( + prebuilt, NULL, 0); + + if (error != DB_SUCCESS) { + error = convert_error_code_to_mysql( + (int) error, 0, thd); + DBUG_RETURN((int) error); + } + } + + trx->mysql_n_tables_locked++; + } + + trx->n_mysql_tables_in_use++; + prebuilt->mysql_has_locked = TRUE; + + DBUG_RETURN(0); + } + + /* MySQL is releasing a table lock */ + + trx->n_mysql_tables_in_use--; + prebuilt->mysql_has_locked = FALSE; + + /* Release a possible FIFO ticket and search latch. Since we + may reserve the kernel mutex, we have to release the search + system latch first to obey the latching order. */ + + innobase_release_stat_resources(trx); + + /* If the MySQL lock count drops to zero we know that the current SQL + statement has ended */ + + if (trx->n_mysql_tables_in_use == 0) { + + trx->mysql_n_tables_locked = 0; + prebuilt->used_in_HANDLER = FALSE; + + if (!thd_test_options(thd, OPTION_NOT_AUTOCOMMIT | OPTION_BEGIN)) { + if (trx->active_trans != 0) { + innobase_commit(ht, thd, TRUE); + } + } else { + if (trx->isolation_level <= TRX_ISO_READ_COMMITTED + && trx->global_read_view) { + + /* At low transaction isolation levels we let + each consistent read set its own snapshot */ + + read_view_close_for_mysql(trx); + } + } + } + + DBUG_RETURN(0); +} + +/********************************************************************** +With this function MySQL request a transactional lock to a table when +user issued query LOCK TABLES..WHERE ENGINE = InnoDB. */ +UNIV_INTERN +int +ha_innobase::transactional_table_lock( +/*==================================*/ + /* out: error code */ + THD* thd, /* in: handle to the user thread */ + int lock_type) /* in: lock type */ +{ + trx_t* trx; + + DBUG_ENTER("ha_innobase::transactional_table_lock"); + DBUG_PRINT("enter",("lock_type: %d", lock_type)); + + /* We do not know if MySQL can call this function before calling + external_lock(). To be safe, update the thd of the current table + handle. */ + + update_thd(thd); + + if (prebuilt->table->ibd_file_missing && !thd_tablespace_op(thd)) { + ut_print_timestamp(stderr); + fprintf(stderr, + " InnoDB: MySQL is trying to use a table handle" + " but the .ibd file for\n" + "InnoDB: table %s does not exist.\n" + "InnoDB: Have you deleted the .ibd file" + " from the database directory under\n" + "InnoDB: the MySQL datadir?" + "InnoDB: See" + " http://dev.mysql.com/doc/refman/5.1/en/innodb-troubleshooting.html\n" + "InnoDB: how you can resolve the problem.\n", + prebuilt->table->name); + DBUG_RETURN(HA_ERR_CRASHED); + } + + trx = prebuilt->trx; + + prebuilt->sql_stat_start = TRUE; + prebuilt->hint_need_to_fetch_extra_cols = 0; + + reset_template(prebuilt); + + if (lock_type == F_WRLCK) { + prebuilt->select_lock_type = LOCK_X; + prebuilt->stored_select_lock_type = LOCK_X; + } else if (lock_type == F_RDLCK) { + prebuilt->select_lock_type = LOCK_S; + prebuilt->stored_select_lock_type = LOCK_S; + } else { + ut_print_timestamp(stderr); + fprintf(stderr, " InnoDB error:\n" +"MySQL is trying to set transactional table lock with corrupted lock type\n" +"to table %s, lock type %d does not exist.\n", + prebuilt->table->name, lock_type); + DBUG_RETURN(HA_ERR_CRASHED); + } + + /* MySQL is setting a new transactional table lock */ + + /* Set the MySQL flag to mark that there is an active transaction */ + if (trx->active_trans == 0) { + + innobase_register_trx_and_stmt(ht, thd); + trx->active_trans = 1; + } + + if (THDVAR(thd, table_locks) && thd_in_lock_tables(thd)) { + ulint error = DB_SUCCESS; + + error = row_lock_table_for_mysql(prebuilt, NULL, 0); + + if (error != DB_SUCCESS) { + error = convert_error_code_to_mysql( + (int) error, prebuilt->table->flags, thd); + DBUG_RETURN((int) error); + } + + if (thd_test_options(thd, OPTION_NOT_AUTOCOMMIT | OPTION_BEGIN)) { + + /* Store the current undo_no of the transaction + so that we know where to roll back if we have + to roll back the next SQL statement */ + + trx_mark_sql_stat_end(trx); + } + } + + DBUG_RETURN(0); +} + +/**************************************************************************** +Here we export InnoDB status variables to MySQL. */ +static +int +innodb_export_status() +/*==================*/ +{ + if (innodb_inited) { + srv_export_innodb_status(); + } + + return(0); +} + +/**************************************************************************** +Implements the SHOW INNODB STATUS command. Sends the output of the InnoDB +Monitor to the client. */ +static +bool +innodb_show_status( +/*===============*/ + handlerton* hton, /* in: the innodb handlerton */ + THD* thd, /* in: the MySQL query thread of the caller */ + stat_print_fn *stat_print) +{ + trx_t* trx; + static const char truncated_msg[] = "... truncated...\n"; + const long MAX_STATUS_SIZE = 64000; + ulint trx_list_start = ULINT_UNDEFINED; + ulint trx_list_end = ULINT_UNDEFINED; + + DBUG_ENTER("innodb_show_status"); + DBUG_ASSERT(hton == innodb_hton_ptr); + + trx = check_trx_exists(thd); + + innobase_release_stat_resources(trx); + + /* We let the InnoDB Monitor to output at most MAX_STATUS_SIZE + bytes of text. */ + + long flen, usable_len; + char* str; + + mutex_enter(&srv_monitor_file_mutex); + rewind(srv_monitor_file); + srv_printf_innodb_monitor(srv_monitor_file, + &trx_list_start, &trx_list_end); + flen = ftell(srv_monitor_file); + os_file_set_eof(srv_monitor_file); + + if (flen < 0) { + flen = 0; + } + + if (flen > MAX_STATUS_SIZE) { + usable_len = MAX_STATUS_SIZE; + } else { + usable_len = flen; + } + + /* allocate buffer for the string, and + read the contents of the temporary file */ + + if (!(str = (char*) my_malloc(usable_len + 1, MYF(0)))) { + mutex_exit(&srv_monitor_file_mutex); + DBUG_RETURN(TRUE); + } + + rewind(srv_monitor_file); + if (flen < MAX_STATUS_SIZE) { + /* Display the entire output. */ + flen = (long) fread(str, 1, flen, srv_monitor_file); + } else if (trx_list_end < (ulint) flen + && trx_list_start < trx_list_end + && trx_list_start + (flen - trx_list_end) + < MAX_STATUS_SIZE - sizeof truncated_msg - 1) { + /* Omit the beginning of the list of active transactions. */ + long len = (long) fread(str, 1, trx_list_start, srv_monitor_file); + memcpy(str + len, truncated_msg, sizeof truncated_msg - 1); + len += sizeof truncated_msg - 1; + usable_len = (MAX_STATUS_SIZE - 1) - len; + fseek(srv_monitor_file, flen - usable_len, SEEK_SET); + len += (long) fread(str + len, 1, usable_len, srv_monitor_file); + flen = len; + } else { + /* Omit the end of the output. */ + flen = (long) fread(str, 1, MAX_STATUS_SIZE - 1, srv_monitor_file); + } + + mutex_exit(&srv_monitor_file_mutex); + + bool result = FALSE; + + if (stat_print(thd, innobase_hton_name, (uint) strlen(innobase_hton_name), + STRING_WITH_LEN(""), str, flen)) { + result= TRUE; + } + my_free(str, MYF(0)); + + DBUG_RETURN(FALSE); +} + +/**************************************************************************** +Implements the SHOW MUTEX STATUS command. . */ +static +bool +innodb_mutex_show_status( +/*=====================*/ + handlerton* hton, /* in: the innodb handlerton */ + THD* thd, /* in: the MySQL query thread of the + caller */ + stat_print_fn* stat_print) +{ + char buf1[IO_SIZE], buf2[IO_SIZE]; + mutex_t* mutex; + rw_lock_t* lock; +#ifdef UNIV_DEBUG + ulint rw_lock_count= 0; + ulint rw_lock_count_spin_loop= 0; + ulint rw_lock_count_spin_rounds= 0; + ulint rw_lock_count_os_wait= 0; + ulint rw_lock_count_os_yield= 0; + ulonglong rw_lock_wait_time= 0; +#endif /* UNIV_DEBUG */ + uint hton_name_len= (uint) strlen(innobase_hton_name), buf1len, buf2len; + DBUG_ENTER("innodb_mutex_show_status"); + DBUG_ASSERT(hton == innodb_hton_ptr); + + mutex_enter(&mutex_list_mutex); + + mutex = UT_LIST_GET_FIRST(mutex_list); + + while (mutex != NULL) { +#ifdef UNIV_DEBUG + if (mutex->mutex_type != 1) { + if (mutex->count_using > 0) { + buf1len= my_snprintf(buf1, sizeof(buf1), + "%s:%s", + mutex->cmutex_name, mutex->cfile_name); + buf2len= my_snprintf(buf2, sizeof(buf2), + "count=%lu, spin_waits=%lu," + " spin_rounds=%lu, " + "os_waits=%lu, os_yields=%lu," + " os_wait_times=%lu", + mutex->count_using, + mutex->count_spin_loop, + mutex->count_spin_rounds, + mutex->count_os_wait, + mutex->count_os_yield, + (ulong) (mutex->lspent_time/1000)); + + if (stat_print(thd, innobase_hton_name, + hton_name_len, buf1, buf1len, + buf2, buf2len)) { + mutex_exit(&mutex_list_mutex); + DBUG_RETURN(1); + } + } + } + else { + rw_lock_count += mutex->count_using; + rw_lock_count_spin_loop += mutex->count_spin_loop; + rw_lock_count_spin_rounds += mutex->count_spin_rounds; + rw_lock_count_os_wait += mutex->count_os_wait; + rw_lock_count_os_yield += mutex->count_os_yield; + rw_lock_wait_time += mutex->lspent_time; + } +#else /* UNIV_DEBUG */ + buf1len= (uint) my_snprintf(buf1, sizeof(buf1), "%s:%lu", + mutex->cfile_name, (ulong) mutex->cline); + buf2len= (uint) my_snprintf(buf2, sizeof(buf2), "os_waits=%lu", + mutex->count_os_wait); + + if (stat_print(thd, innobase_hton_name, + hton_name_len, buf1, buf1len, + buf2, buf2len)) { + mutex_exit(&mutex_list_mutex); + DBUG_RETURN(1); + } +#endif /* UNIV_DEBUG */ + + mutex = UT_LIST_GET_NEXT(list, mutex); + } + + mutex_exit(&mutex_list_mutex); + + mutex_enter(&rw_lock_list_mutex); + + lock = UT_LIST_GET_FIRST(rw_lock_list); + + while (lock != NULL) { + if (lock->count_os_wait) { + buf1len= my_snprintf(buf1, sizeof(buf1), "%s:%lu", + lock->cfile_name, (ulong) lock->cline); + buf2len= my_snprintf(buf2, sizeof(buf2), + "os_waits=%lu", lock->count_os_wait); + + if (stat_print(thd, innobase_hton_name, + hton_name_len, buf1, buf1len, + buf2, buf2len)) { + mutex_exit(&rw_lock_list_mutex); + DBUG_RETURN(1); + } + } + lock = UT_LIST_GET_NEXT(list, lock); + } + + mutex_exit(&rw_lock_list_mutex); + +#ifdef UNIV_DEBUG + buf2len= my_snprintf(buf2, sizeof(buf2), + "count=%lu, spin_waits=%lu, spin_rounds=%lu, " + "os_waits=%lu, os_yields=%lu, os_wait_times=%lu", + rw_lock_count, rw_lock_count_spin_loop, + rw_lock_count_spin_rounds, + rw_lock_count_os_wait, rw_lock_count_os_yield, + (ulong) (rw_lock_wait_time/1000)); + + if (stat_print(thd, innobase_hton_name, hton_name_len, + STRING_WITH_LEN("rw_lock_mutexes"), buf2, buf2len)) { + DBUG_RETURN(1); + } +#endif /* UNIV_DEBUG */ + + DBUG_RETURN(FALSE); +} + +static +bool innobase_show_status(handlerton *hton, THD* thd, + stat_print_fn* stat_print, + enum ha_stat_type stat_type) +{ + DBUG_ASSERT(hton == innodb_hton_ptr); + + switch (stat_type) { + case HA_ENGINE_STATUS: + return innodb_show_status(hton, thd, stat_print); + case HA_ENGINE_MUTEX: + return innodb_mutex_show_status(hton, thd, stat_print); + default: + return(FALSE); + } +} + +/**************************************************************************** + Handling the shared INNOBASE_SHARE structure that is needed to provide table + locking. +****************************************************************************/ + +static INNOBASE_SHARE* get_share(const char* table_name) +{ + INNOBASE_SHARE *share; + pthread_mutex_lock(&innobase_share_mutex); + + ulint fold = ut_fold_string(table_name); + + HASH_SEARCH(table_name_hash, innobase_open_tables, fold, + INNOBASE_SHARE*, share, + ut_ad(share->use_count > 0), + !strcmp(share->table_name, table_name)); + + if (!share) { + + uint length = (uint) strlen(table_name); + + /* TODO: invoke HASH_MIGRATE if innobase_open_tables + grows too big */ + + share = (INNOBASE_SHARE *) my_malloc(sizeof(*share)+length+1, + MYF(MY_FAE | MY_ZEROFILL)); + + share->table_name = (char*) memcpy(share + 1, + table_name, length + 1); + + HASH_INSERT(INNOBASE_SHARE, table_name_hash, + innobase_open_tables, fold, share); + + thr_lock_init(&share->lock); + pthread_mutex_init(&share->mutex,MY_MUTEX_INIT_FAST); + } + + share->use_count++; + pthread_mutex_unlock(&innobase_share_mutex); + + return(share); +} + +static void free_share(INNOBASE_SHARE* share) +{ + pthread_mutex_lock(&innobase_share_mutex); + +#ifdef UNIV_DEBUG + INNOBASE_SHARE* share2; + ulint fold = ut_fold_string(share->table_name); + + HASH_SEARCH(table_name_hash, innobase_open_tables, fold, + INNOBASE_SHARE*, share2, + ut_ad(share->use_count > 0), + !strcmp(share->table_name, share2->table_name)); + + ut_a(share2 == share); +#endif /* UNIV_DEBUG */ + + if (!--share->use_count) { + ulint fold = ut_fold_string(share->table_name); + + HASH_DELETE(INNOBASE_SHARE, table_name_hash, + innobase_open_tables, fold, share); + thr_lock_delete(&share->lock); + pthread_mutex_destroy(&share->mutex); + my_free(share, MYF(0)); + + /* TODO: invoke HASH_MIGRATE if innobase_open_tables + shrinks too much */ + } + + pthread_mutex_unlock(&innobase_share_mutex); +} + +/********************************************************************* +Converts a MySQL table lock stored in the 'lock' field of the handle to +a proper type before storing pointer to the lock into an array of pointers. +MySQL also calls this if it wants to reset some table locks to a not-locked +state during the processing of an SQL query. An example is that during a +SELECT the read lock is released early on the 'const' tables where we only +fetch one row. MySQL does not call this when it releases all locks at the +end of an SQL statement. */ +UNIV_INTERN +THR_LOCK_DATA** +ha_innobase::store_lock( +/*====================*/ + /* out: pointer to the next + element in the 'to' array */ + THD* thd, /* in: user thread handle */ + THR_LOCK_DATA** to, /* in: pointer to an array + of pointers to lock structs; + pointer to the 'lock' field + of current handle is stored + next to this array */ + enum thr_lock_type lock_type) /* in: lock type to store in + 'lock'; this may also be + TL_IGNORE */ +{ + trx_t* trx; + + /* Note that trx in this function is NOT necessarily prebuilt->trx + because we call update_thd() later, in ::external_lock()! Failure to + understand this caused a serious memory corruption bug in 5.1.11. */ + + trx = check_trx_exists(thd); + + /* NOTE: MySQL can call this function with lock 'type' TL_IGNORE! + Be careful to ignore TL_IGNORE if we are going to do something with + only 'real' locks! */ + + /* If no MySQL table is in use, we need to set the isolation level + of the transaction. */ + + if (lock_type != TL_IGNORE + && trx->n_mysql_tables_in_use == 0) { + trx->isolation_level = innobase_map_isolation_level( + (enum_tx_isolation) thd_tx_isolation(thd)); + + if (trx->isolation_level <= TRX_ISO_READ_COMMITTED + && trx->global_read_view) { + + /* At low transaction isolation levels we let + each consistent read set its own snapshot */ + + read_view_close_for_mysql(trx); + } + } + + DBUG_ASSERT(EQ_CURRENT_THD(thd)); + const bool in_lock_tables = thd_in_lock_tables(thd); + const uint sql_command = thd_sql_command(thd); + + if (sql_command == SQLCOM_DROP_TABLE) { + + /* MySQL calls this function in DROP TABLE though this table + handle may belong to another thd that is running a query. Let + us in that case skip any changes to the prebuilt struct. */ + + } else if ((lock_type == TL_READ && in_lock_tables) + || (lock_type == TL_READ_HIGH_PRIORITY && in_lock_tables) + || lock_type == TL_READ_WITH_SHARED_LOCKS + || lock_type == TL_READ_NO_INSERT + || (lock_type != TL_IGNORE + && sql_command != SQLCOM_SELECT)) { + + /* The OR cases above are in this order: + 1) MySQL is doing LOCK TABLES ... READ LOCAL, or we + are processing a stored procedure or function, or + 2) (we do not know when TL_READ_HIGH_PRIORITY is used), or + 3) this is a SELECT ... IN SHARE MODE, or + 4) we are doing a complex SQL statement like + INSERT INTO ... SELECT ... and the logical logging (MySQL + binlog) requires the use of a locking read, or + MySQL is doing LOCK TABLES ... READ. + 5) we let InnoDB do locking reads for all SQL statements that + are not simple SELECTs; note that select_lock_type in this + case may get strengthened in ::external_lock() to LOCK_X. + Note that we MUST use a locking read in all data modifying + SQL statements, because otherwise the execution would not be + serializable, and also the results from the update could be + unexpected if an obsolete consistent read view would be + used. */ + + ulint isolation_level; + + isolation_level = trx->isolation_level; + + if ((srv_locks_unsafe_for_binlog + || isolation_level == TRX_ISO_READ_COMMITTED) + && isolation_level != TRX_ISO_SERIALIZABLE + && (lock_type == TL_READ || lock_type == TL_READ_NO_INSERT) + && (sql_command == SQLCOM_INSERT_SELECT + || sql_command == SQLCOM_UPDATE + || sql_command == SQLCOM_CREATE_TABLE)) { + + /* If we either have innobase_locks_unsafe_for_binlog + option set or this session is using READ COMMITTED + isolation level and isolation level of the transaction + is not set to serializable and MySQL is doing + INSERT INTO...SELECT or UPDATE ... = (SELECT ...) or + CREATE ... SELECT... without FOR UPDATE or + IN SHARE MODE in select, then we use consistent + read for select. */ + + prebuilt->select_lock_type = LOCK_NONE; + prebuilt->stored_select_lock_type = LOCK_NONE; + } else if (sql_command == SQLCOM_CHECKSUM) { + /* Use consistent read for checksum table */ + + prebuilt->select_lock_type = LOCK_NONE; + prebuilt->stored_select_lock_type = LOCK_NONE; + } else { + prebuilt->select_lock_type = LOCK_S; + prebuilt->stored_select_lock_type = LOCK_S; + } + + } else if (lock_type != TL_IGNORE) { + + /* We set possible LOCK_X value in external_lock, not yet + here even if this would be SELECT ... FOR UPDATE */ + + prebuilt->select_lock_type = LOCK_NONE; + prebuilt->stored_select_lock_type = LOCK_NONE; + } + + if (lock_type != TL_IGNORE && lock.type == TL_UNLOCK) { + + /* Starting from 5.0.7, we weaken also the table locks + set at the start of a MySQL stored procedure call, just like + we weaken the locks set at the start of an SQL statement. + MySQL does set in_lock_tables TRUE there, but in reality + we do not need table locks to make the execution of a + single transaction stored procedure call deterministic + (if it does not use a consistent read). */ + + if (lock_type == TL_READ + && sql_command == SQLCOM_LOCK_TABLES) { + /* We come here if MySQL is processing LOCK TABLES + ... READ LOCAL. MyISAM under that table lock type + reads the table as it was at the time the lock was + granted (new inserts are allowed, but not seen by the + reader). To get a similar effect on an InnoDB table, + we must use LOCK TABLES ... READ. We convert the lock + type here, so that for InnoDB, READ LOCAL is + equivalent to READ. This will change the InnoDB + behavior in mysqldump, so that dumps of InnoDB tables + are consistent with dumps of MyISAM tables. */ + + lock_type = TL_READ_NO_INSERT; + } + + /* If we are not doing a LOCK TABLE, DISCARD/IMPORT + TABLESPACE or TRUNCATE TABLE then allow multiple + writers. Note that ALTER TABLE uses a TL_WRITE_ALLOW_READ + < TL_WRITE_CONCURRENT_INSERT. + + We especially allow multiple writers if MySQL is at the + start of a stored procedure call (SQLCOM_CALL) or a + stored function call (MySQL does have in_lock_tables + TRUE there). */ + + if ((lock_type >= TL_WRITE_CONCURRENT_INSERT + && lock_type <= TL_WRITE) + && !(in_lock_tables + && sql_command == SQLCOM_LOCK_TABLES) + && !thd_tablespace_op(thd) + && sql_command != SQLCOM_TRUNCATE + && sql_command != SQLCOM_OPTIMIZE + && sql_command != SQLCOM_CREATE_TABLE) { + + 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. + + We especially allow concurrent inserts if MySQL is at the + start of a stored procedure call (SQLCOM_CALL) + (MySQL does have thd_in_lock_tables() TRUE there). */ + + if (lock_type == TL_READ_NO_INSERT + && sql_command != SQLCOM_LOCK_TABLES) { + + lock_type = TL_READ; + } + + lock.type = lock_type; + } + + *to++= &lock; + + return(to); +} + +/******************************************************************************* +Read the next autoinc value. Acquire the relevant locks before reading +the AUTOINC value. If SUCCESS then the table AUTOINC mutex will be locked +on return and all relevant locks acquired. */ +UNIV_INTERN +ulint +ha_innobase::innobase_get_autoinc( +/*==============================*/ + /* out: DB_SUCCESS or error code */ + ulonglong* value) /* out: autoinc value */ +{ + *value = 0; + + prebuilt->autoinc_error = innobase_lock_autoinc(); + + if (prebuilt->autoinc_error == DB_SUCCESS) { + + /* Determine the first value of the interval */ + *value = dict_table_autoinc_read(prebuilt->table); + + /* It should have been initialized during open. */ + ut_a(*value != 0); + } + + return(prebuilt->autoinc_error); +} + +/*********************************************************************** +This function reads the global auto-inc counter. It doesn't use the +AUTOINC lock even if the lock mode is set to TRADITIONAL. */ +UNIV_INTERN +ulonglong +ha_innobase::innobase_peek_autoinc(void) +/*====================================*/ + /* out: the autoinc value */ +{ + ulonglong auto_inc; + dict_table_t* innodb_table; + + ut_a(prebuilt != NULL); + ut_a(prebuilt->table != NULL); + + innodb_table = prebuilt->table; + + dict_table_autoinc_lock(innodb_table); + + auto_inc = dict_table_autoinc_read(innodb_table); + + ut_a(auto_inc > 0); + + dict_table_autoinc_unlock(innodb_table); + + return(auto_inc); +} + +/******************************************************************************* +This function initializes the auto-inc counter if it has not been +initialized yet. This function does not change the value of the auto-inc +counter if it already has been initialized. Returns the value of the +auto-inc counter in *first_value, and ULONGLONG_MAX in *nb_reserved_values (as +we have a table-level lock). offset, increment, nb_desired_values are ignored. +*first_value is set to -1 if error (deadlock or lock wait timeout) */ +UNIV_INTERN +void +ha_innobase::get_auto_increment( +/*============================*/ + ulonglong offset, /* in: */ + ulonglong increment, /* in: table autoinc increment */ + ulonglong nb_desired_values, /* in: number of values reqd */ + ulonglong *first_value, /* out: the autoinc value */ + ulonglong *nb_reserved_values) /* out: count of reserved values */ +{ + trx_t* trx; + ulint error; + ulonglong autoinc = 0; + + /* Prepare prebuilt->trx in the table handle */ + update_thd(ha_thd()); + + error = innobase_get_autoinc(&autoinc); + + if (error != DB_SUCCESS) { + *first_value = (~(ulonglong) 0); + return; + } + + /* This is a hack, since nb_desired_values seems to be accurate only + for the first call to get_auto_increment() for multi-row INSERT and + meaningless for other statements e.g, LOAD etc. Subsequent calls to + this method for the same statement results in different values which + don't make sense. Therefore we store the value the first time we are + called and count down from that as rows are written (see write_row()). + */ + + trx = prebuilt->trx; + + /* Note: We can't rely on *first_value since some MySQL engines, + in particular the partition engine, don't initialize it to 0 when + invoking this method. So we are not sure if it's guaranteed to + be 0 or not. */ + + /* Called for the first time ? */ + if (trx->n_autoinc_rows == 0) { + + trx->n_autoinc_rows = (ulint) nb_desired_values; + + /* It's possible for nb_desired_values to be 0: + e.g., INSERT INTO T1(C) SELECT C FROM T2; */ + if (nb_desired_values == 0) { + + trx->n_autoinc_rows = 1; + } + + set_if_bigger(*first_value, autoinc); + /* Not in the middle of a mult-row INSERT. */ + } else if (prebuilt->autoinc_last_value == 0) { + set_if_bigger(*first_value, autoinc); + } + + *nb_reserved_values = trx->n_autoinc_rows; + + /* With old style AUTOINC locking we only update the table's + AUTOINC counter after attempting to insert the row. */ + if (innobase_autoinc_lock_mode != AUTOINC_OLD_STYLE_LOCKING) { + ulonglong need; + ulonglong next_value; + ulonglong col_max_value; + + /* We need the upper limit of the col type to check for + whether we update the table autoinc counter or not. */ + col_max_value = innobase_get_int_col_max_value( + table->next_number_field); + + need = *nb_reserved_values * increment; + + /* Compute the last value in the interval */ + next_value = innobase_next_autoinc( + *first_value, need, offset, col_max_value); + + prebuilt->autoinc_last_value = next_value; + + if (prebuilt->autoinc_last_value < *first_value) { + *first_value = (~(ulonglong) 0); + } else { + /* Update the table autoinc variable */ + dict_table_autoinc_update_if_greater( + prebuilt->table, prebuilt->autoinc_last_value); + } + } else { + /* This will force write_row() into attempting an update + of the table's AUTOINC counter. */ + prebuilt->autoinc_last_value = 0; + } + + /* The increment to be used to increase the AUTOINC value, we use + this in write_row() and update_row() to increase the autoinc counter + for columns that are filled by the user. We need the offset and + the increment. */ + prebuilt->autoinc_offset = offset; + prebuilt->autoinc_increment = increment; + + dict_table_autoinc_unlock(prebuilt->table); +} + +/* See comment in handler.h */ +UNIV_INTERN +int +ha_innobase::reset_auto_increment( +/*==============================*/ + ulonglong value) /* in: new value for table autoinc */ +{ + DBUG_ENTER("ha_innobase::reset_auto_increment"); + + int error; + + update_thd(ha_thd()); + + error = row_lock_table_autoinc_for_mysql(prebuilt); + + if (error != DB_SUCCESS) { + error = convert_error_code_to_mysql(error, + prebuilt->table->flags, + user_thd); + + DBUG_RETURN(error); + } + + /* The next value can never be 0. */ + if (value == 0) { + value = 1; + } + + innobase_reset_autoinc(value); + + DBUG_RETURN(0); +} + +/* See comment in handler.cc */ +UNIV_INTERN +bool +ha_innobase::get_error_message(int error, String *buf) +{ + trx_t* trx = check_trx_exists(ha_thd()); + + buf->copy(trx->detailed_error, (uint) strlen(trx->detailed_error), + system_charset_info); + + return(FALSE); +} + +/*********************************************************************** +Compares two 'refs'. A 'ref' is the (internal) primary key value of the row. +If there is no explicitly declared non-null unique key or a primary key, then +InnoDB internally uses the row id as the primary key. */ +UNIV_INTERN +int +ha_innobase::cmp_ref( +/*=================*/ + /* out: < 0 if ref1 < ref2, 0 if equal, else + > 0 */ + const uchar* ref1, /* in: an (internal) primary key value in the + MySQL key value format */ + const uchar* ref2) /* in: an (internal) primary key value in the + MySQL key value format */ +{ + enum_field_types mysql_type; + Field* field; + KEY_PART_INFO* key_part; + KEY_PART_INFO* key_part_end; + uint len1; + uint len2; + int result; + + if (prebuilt->clust_index_was_generated) { + /* The 'ref' is an InnoDB row id */ + + return(memcmp(ref1, ref2, DATA_ROW_ID_LEN)); + } + + /* Do a type-aware comparison of primary key fields. PK fields + are always NOT NULL, so no checks for NULL are performed. */ + + key_part = table->key_info[table->s->primary_key].key_part; + + key_part_end = key_part + + table->key_info[table->s->primary_key].key_parts; + + for (; key_part != key_part_end; ++key_part) { + field = key_part->field; + mysql_type = field->type(); + + if (mysql_type == MYSQL_TYPE_TINY_BLOB + || mysql_type == MYSQL_TYPE_MEDIUM_BLOB + || mysql_type == MYSQL_TYPE_BLOB + || mysql_type == MYSQL_TYPE_LONG_BLOB) { + + /* In the MySQL key value format, a column prefix of + a BLOB is preceded by a 2-byte length field */ + + len1 = innobase_read_from_2_little_endian(ref1); + len2 = innobase_read_from_2_little_endian(ref2); + + ref1 += 2; + ref2 += 2; + result = ((Field_blob*)field)->cmp( ref1, len1, + ref2, len2); + } else { + result = field->key_cmp(ref1, ref2); + } + + if (result) { + + return(result); + } + + ref1 += key_part->store_length; + ref2 += key_part->store_length; + } + + return(0); +} + +/*********************************************************************** +Ask InnoDB if a query to a table can be cached. */ +UNIV_INTERN +my_bool +ha_innobase::register_query_cache_table( +/*====================================*/ + /* out: TRUE if query caching + of the table is permitted */ + THD* thd, /* in: user thread handle */ + char* table_key, /* in: concatenation of database name, + the null character '\0', + and the table name */ + uint key_length, /* in: length of the full name, i.e. + len(dbname) + len(tablename) + 1 */ + qc_engine_callback* + call_back, /* out: pointer to function for + checking if query caching + is permitted */ + ulonglong *engine_data) /* in/out: data to call_back */ +{ + *call_back = innobase_query_caching_of_table_permitted; + *engine_data = 0; + return(innobase_query_caching_of_table_permitted(thd, table_key, + key_length, + engine_data)); +} + +UNIV_INTERN +char* +ha_innobase::get_mysql_bin_log_name() +{ + return(trx_sys_mysql_bin_log_name); +} + +UNIV_INTERN +ulonglong +ha_innobase::get_mysql_bin_log_pos() +{ + /* trx... is ib_int64_t, which is a typedef for a 64-bit integer + (__int64 or longlong) so it's ok to cast it to ulonglong. */ + + return(trx_sys_mysql_bin_log_pos); +} + +/********************************************************************** +This function is used to find the storage length in bytes of the first n +characters for prefix indexes using a multibyte character set. The function +finds charset information and returns length of prefix_len characters in the +index field in bytes. + +NOTE: the prototype of this function is copied to data0type.c! If you change +this function, you MUST change also data0type.c! */ +extern "C" UNIV_INTERN +ulint +innobase_get_at_most_n_mbchars( +/*===========================*/ + /* out: number of bytes occupied by the first + n characters */ + ulint charset_id, /* in: character set id */ + ulint prefix_len, /* in: prefix length in bytes of the index + (this has to be divided by mbmaxlen to get the + number of CHARACTERS n in the prefix) */ + ulint data_len, /* in: length of the string in bytes */ + const char* str) /* in: character string */ +{ + ulint char_length; /* character length in bytes */ + ulint n_chars; /* number of characters in prefix */ + CHARSET_INFO* charset; /* charset used in the field */ + + charset = get_charset((uint) charset_id, MYF(MY_WME)); + + ut_ad(charset); + ut_ad(charset->mbmaxlen); + + /* Calculate how many characters at most the prefix index contains */ + + n_chars = prefix_len / charset->mbmaxlen; + + /* If the charset is multi-byte, then we must find the length of the + first at most n chars in the string. If the string contains less + characters than n, then we return the length to the end of the last + character. */ + + if (charset->mbmaxlen > 1) { + /* my_charpos() returns the byte length of the first n_chars + characters, or a value bigger than the length of str, if + there were not enough full characters in str. + + Why does the code below work: + Suppose that we are looking for n UTF-8 characters. + + 1) If the string is long enough, then the prefix contains at + least n complete UTF-8 characters + maybe some extra + characters + an incomplete UTF-8 character. No problem in + this case. The function returns the pointer to the + end of the nth character. + + 2) If the string is not long enough, then the string contains + the complete value of a column, that is, only complete UTF-8 + characters, and we can store in the column prefix index the + whole string. */ + + char_length = my_charpos(charset, str, + str + data_len, (int) n_chars); + if (char_length > data_len) { + char_length = data_len; + } + } else { + if (data_len < prefix_len) { + char_length = data_len; + } else { + char_length = prefix_len; + } + } + + return(char_length); +} + +/*********************************************************************** +This function is used to prepare X/Open XA distributed transaction */ +static +int +innobase_xa_prepare( +/*================*/ + /* out: 0 or error number */ + handlerton *hton, + THD* thd, /* in: handle to the MySQL thread of the user + whose XA transaction should be prepared */ + bool all) /* in: TRUE - commit transaction + FALSE - the current SQL statement ended */ +{ + int error = 0; + trx_t* trx = check_trx_exists(thd); + + DBUG_ASSERT(hton == innodb_hton_ptr); + + if (thd_sql_command(thd) != SQLCOM_XA_PREPARE && + (all || !thd_test_options(thd, OPTION_NOT_AUTOCOMMIT | OPTION_BEGIN))) + { + if (srv_enable_unsafe_group_commit && !THDVAR(thd, support_xa)) { + /* choose group commit rather than binlog order */ + return(0); + } + + /* For ibbackup to work the order of transactions in binlog + and InnoDB must be the same. Consider the situation + + thread1> prepare; write to binlog; ... + <context switch> + thread2> prepare; write to binlog; commit + thread1> ... commit + + To ensure this will not happen we're taking the mutex on + prepare, and releasing it on commit. + + Note: only do it for normal commits, done via ha_commit_trans. + If 2pc protocol is executed by external transaction + coordinator, it will be just a regular MySQL client + executing XA PREPARE and XA COMMIT commands. + In this case we cannot know how many minutes or hours + will be between XA PREPARE and XA COMMIT, and we don't want + to block for undefined period of time. + */ + pthread_mutex_lock(&prepare_commit_mutex); + trx->active_trans = 2; + } + + /* we use support_xa value as it was seen at transaction start + time, not the current session variable value. Any possible changes + to the session variable take effect only in the next transaction */ + if (!trx->support_xa) { + + return(0); + } + + thd_get_xid(thd, (MYSQL_XID*) &trx->xid); + + /* Release a possible FIFO ticket and search latch. Since we will + reserve the kernel mutex, we have to release the search system latch + first to obey the latching order. */ + + innobase_release_stat_resources(trx); + + if (trx->active_trans == 0 && trx->conc_state != TRX_NOT_STARTED) { + + sql_print_error("trx->active_trans == 0, but trx->conc_state != " + "TRX_NOT_STARTED"); + } + + if (all + || (!thd_test_options(thd, OPTION_NOT_AUTOCOMMIT | OPTION_BEGIN))) { + + /* We were instructed to prepare the whole transaction, or + this is an SQL statement end and autocommit is on */ + + ut_ad(trx->active_trans); + + error = (int) trx_prepare_for_mysql(trx); + } else { + /* We just mark the SQL statement ended and do not do a + transaction prepare */ + + /* If we had reserved the auto-inc lock for some + table in this SQL statement we release it now */ + + row_unlock_table_autoinc_for_mysql(trx); + + /* Store the current undo_no of the transaction so that we + know where to roll back if we have to roll back the next + SQL statement */ + + trx_mark_sql_stat_end(trx); + } + + /* Tell the InnoDB server that there might be work for utility + threads: */ + + srv_active_wake_master_thread(); + + return(error); +} + +/*********************************************************************** +This function is used to recover X/Open XA distributed transactions */ +static +int +innobase_xa_recover( +/*================*/ + /* out: number of prepared transactions + stored in xid_list */ + handlerton *hton, + XID* xid_list, /* in/out: prepared transactions */ + uint len) /* in: number of slots in xid_list */ +{ + DBUG_ASSERT(hton == innodb_hton_ptr); + + if (len == 0 || xid_list == NULL) { + + return(0); + } + + return(trx_recover_for_mysql(xid_list, len)); +} + +/*********************************************************************** +This function is used to commit one X/Open XA distributed transaction +which is in the prepared state */ +static +int +innobase_commit_by_xid( +/*===================*/ + /* out: 0 or error number */ + handlerton *hton, + XID* xid) /* in: X/Open XA transaction identification */ +{ + trx_t* trx; + + DBUG_ASSERT(hton == innodb_hton_ptr); + + trx = trx_get_trx_by_xid(xid); + + if (trx) { + innobase_commit_low(trx); + + return(XA_OK); + } else { + return(XAER_NOTA); + } +} + +/*********************************************************************** +This function is used to rollback one X/Open XA distributed transaction +which is in the prepared state */ +static +int +innobase_rollback_by_xid( +/*=====================*/ + /* out: 0 or error number */ + handlerton *hton, + XID *xid) /* in: X/Open XA transaction identification */ +{ + trx_t* trx; + + DBUG_ASSERT(hton == innodb_hton_ptr); + + trx = trx_get_trx_by_xid(xid); + + if (trx) { + return(innobase_rollback_trx(trx)); + } else { + return(XAER_NOTA); + } +} + +/*********************************************************************** +Create a consistent view for a cursor based on current transaction +which is created if the corresponding MySQL thread still lacks one. +This consistent view is then used inside of MySQL when accessing records +using a cursor. */ +static +void* +innobase_create_cursor_view( +/*========================*/ + /* out: pointer to cursor view or NULL */ + handlerton *hton, /* in: innobase hton */ + THD* thd) /* in: user thread handle */ +{ + DBUG_ASSERT(hton == innodb_hton_ptr); + + return(read_cursor_view_create_for_mysql(check_trx_exists(thd))); +} + +/*********************************************************************** +Close the given consistent cursor view of a transaction and restore +global read view to a transaction read view. Transaction is created if the +corresponding MySQL thread still lacks one. */ +static +void +innobase_close_cursor_view( +/*=======================*/ + handlerton *hton, + THD* thd, /* in: user thread handle */ + void* curview)/* in: Consistent read view to be closed */ +{ + DBUG_ASSERT(hton == innodb_hton_ptr); + + read_cursor_view_close_for_mysql(check_trx_exists(thd), + (cursor_view_t*) curview); +} + +/*********************************************************************** +Set the given consistent cursor view to a transaction which is created +if the corresponding MySQL thread still lacks one. If the given +consistent cursor view is NULL global read view of a transaction is +restored to a transaction read view. */ +static +void +innobase_set_cursor_view( +/*=====================*/ + handlerton *hton, + THD* thd, /* in: user thread handle */ + void* curview)/* in: Consistent cursor view to be set */ +{ + DBUG_ASSERT(hton == innodb_hton_ptr); + + read_cursor_set_for_mysql(check_trx_exists(thd), + (cursor_view_t*) curview); +} + + +UNIV_INTERN +bool +ha_innobase::check_if_incompatible_data( + HA_CREATE_INFO* info, + uint table_changes) +{ + enum row_type row_type, info_row_type; + DBUG_ENTER("ha_innobase::check_if_incompatible_data"); + + if (table_changes != IS_EQUAL_YES) { + + DBUG_PRINT("info", ("table_changes != IS_EQUAL_YES " + "-> COMPATIBLE_DATA_NO")); + DBUG_RETURN(COMPATIBLE_DATA_NO); + } + + /* Check that auto_increment value was not changed */ + if ((info->used_fields & HA_CREATE_USED_AUTO) && + info->auto_increment_value != 0) { + + DBUG_PRINT("info", ("auto_increment_value changed -> " + "COMPATIBLE_DATA_NO")); + DBUG_RETURN(COMPATIBLE_DATA_NO); + } + + /* Check that row format didn't change */ + row_type = get_row_type(); + info_row_type = info->row_type; + /* Default is compact. */ + if (info_row_type == ROW_TYPE_DEFAULT) + info_row_type = ROW_TYPE_COMPACT; + if ((info->used_fields & HA_CREATE_USED_ROW_FORMAT) && + get_row_type() != ((info->row_type == ROW_TYPE_DEFAULT) + ? ROW_TYPE_COMPACT : info->row_type)) { + + DBUG_PRINT("info", ("get_row_type()=%d != info->row_type=%d -> " + "COMPATIBLE_DATA_NO", + row_type, info->row_type)); + DBUG_RETURN(COMPATIBLE_DATA_NO); + } + + /* Specifying KEY_BLOCK_SIZE requests a rebuild of the table. */ + if (info->used_fields & HA_CREATE_USED_KEY_BLOCK_SIZE) { + DBUG_PRINT("info", ("HA_CREATE_USED_KEY_BLOCK_SIZE -> " + "COMPATIBLE_DATA_NO")); + DBUG_RETURN(COMPATIBLE_DATA_NO); + } + + DBUG_PRINT("info", (" -> COMPATIBLE_DATA_YES")); + DBUG_RETURN(COMPATIBLE_DATA_YES); +} + +/**************************************************************** +Validate the file format name and return its corresponding id. */ +static +uint +innobase_file_format_name_lookup( +/*=============================*/ + /* out: valid file format id*/ + const char* format_name) /* in: pointer to file format name */ +{ + char* endp; + uint format_id; + + ut_a(format_name != NULL); + + /* The format name can contain the format id itself instead of + the name and we check for that. */ + format_id = (uint) strtoul(format_name, &endp, 10); + + /* Check for valid parse. */ + if (*endp == '\0' && *format_name != '\0') { + + if (format_id <= DICT_TF_FORMAT_MAX) { + + return(format_id); + } + } else { + + for (format_id = 0; format_id <= DICT_TF_FORMAT_MAX; + format_id++) { + const char* name; + + name = trx_sys_file_format_id_to_name(format_id); + + if (!innobase_strcasecmp(format_name, name)) { + + return(format_id); + } + } + } + + return(DICT_TF_FORMAT_MAX + 1); +} + +/**************************************************************** +Validate the file format check value, is it one of "on" or "off", +as a side effect it sets the srv_check_file_format_at_startup variable. */ +static +bool +innobase_file_format_check_on_off( +/*==============================*/ + /* out: true if config value one + of "on" or "off" */ + const char* format_check) /* in: parameter value */ +{ + bool ret = true; + + if (!innobase_strcasecmp(format_check, "off")) { + + /* Set the value to disable checking. */ + srv_check_file_format_at_startup = DICT_TF_FORMAT_MAX + 1; + + } else if (!innobase_strcasecmp(format_check, "on")) { + + /* Set the value to the lowest supported format. */ + srv_check_file_format_at_startup = DICT_TF_FORMAT_51; + } else { + ret = FALSE; + } + + return(ret); +} + +/**************************************************************** +Validate the file format check config parameters, as a side effect it +sets the srv_check_file_format_at_startup variable. */ +static +bool +innobase_file_format_check_validate( +/*================================*/ + /* out: true if valid config value */ + const char* format_check) /* in: parameter value */ +{ + uint format_id; + bool ret = true; + + format_id = innobase_file_format_name_lookup(format_check); + + if (format_id < DICT_TF_FORMAT_MAX + 1) { + srv_check_file_format_at_startup = format_id; + } else { + ret = false; + } + + return(ret); +} + +/***************************************************************** +Check if it is a valid file format. This function is registered as +a callback with MySQL. */ +static +int +innodb_file_format_name_validate( +/*=============================*/ + /* out: 0 for valid file + format */ + THD* thd, /* in: thread handle */ + struct st_mysql_sys_var* var, /* in: pointer to system + variable */ + void* save, /* out: immediate result + for update function */ + struct st_mysql_value* value) /* in: incoming string */ +{ + const char* file_format_input; + char buff[STRING_BUFFER_USUAL_SIZE]; + int len = sizeof(buff); + + ut_a(save != NULL); + ut_a(value != NULL); + + file_format_input = value->val_str(value, buff, &len); + + if (file_format_input != NULL) { + uint format_id; + + format_id = innobase_file_format_name_lookup( + file_format_input); + + if (format_id <= DICT_TF_FORMAT_MAX) { + + *(uint*) save = format_id; + return(0); + } + } + + return(1); +} + +/******************************************************************** +Update the system variable innodb_file_format using the "saved" +value. This function is registered as a callback with MySQL. */ +static +void +innodb_file_format_name_update( +/*===========================*/ + THD* thd, /* in: thread handle */ + struct st_mysql_sys_var* var, /* in: pointer to + system variable */ + void* var_ptr, /* out: where the + formal string goes */ + const void* save) /* in: immediate result + from check function */ +{ + ut_a(var_ptr != NULL); + ut_a(save != NULL); + ut_a((*(const uint*) save) <= DICT_TF_FORMAT_MAX); + + srv_file_format = *(const uint*) save; + + *(const char**) var_ptr + = trx_sys_file_format_id_to_name(srv_file_format); +} + +/***************************************************************** +Check if valid argument to innodb_file_format_check. This +function is registered as a callback with MySQL. */ +static +int +innodb_file_format_check_validate( +/*==============================*/ + /* out: 0 for valid file + format */ + THD* thd, /* in: thread handle */ + struct st_mysql_sys_var* var, /* in: pointer to system + variable */ + void* save, /* out: immediate result + for update function */ + struct st_mysql_value* value) /* in: incoming string */ +{ + const char* file_format_input; + char buff[STRING_BUFFER_USUAL_SIZE]; + int len = sizeof(buff); + + ut_a(save != NULL); + ut_a(value != NULL); + + file_format_input = value->val_str(value, buff, &len); + + if (file_format_input != NULL) { + + /* Check if user set on/off, we want to print a suitable + message if they did so. */ + + if (innobase_file_format_check_on_off(file_format_input)) { + sql_print_warning( + "InnoDB: invalid innodb_file_format_check " + "value; on/off can only be set at startup or " + "in the configuration file"); + } else if (innobase_file_format_check_validate( + file_format_input)) { + + uint format_id; + + format_id = innobase_file_format_name_lookup( + file_format_input); + + ut_a(format_id <= DICT_TF_FORMAT_MAX); + + *(uint*) save = format_id; + + return(0); + + } else { + sql_print_warning( + "InnoDB: invalid innodb_file_format_check " + "value; can be any format up to %s " + "or its equivalent numeric id", + trx_sys_file_format_id_to_name( + DICT_TF_FORMAT_MAX)); + } + } + + return(1); +} + +/******************************************************************** +Update the system variable innodb_file_format_check using the "saved" +value. This function is registered as a callback with MySQL. */ +static +void +innodb_file_format_check_update( +/*============================*/ + THD* thd, /* in: thread handle */ + struct st_mysql_sys_var* var, /* in: pointer to + system variable */ + void* var_ptr, /* out: where the + formal string goes */ + const void* save) /* in: immediate result + from check function */ +{ + uint format_id; + + ut_a(save != NULL); + ut_a(var_ptr != NULL); + + format_id = *(const uint*) save; + + /* Update the max format id in the system tablespace. */ + if (trx_sys_file_format_max_set(format_id, (const char**) var_ptr)) { + ut_print_timestamp(stderr); + fprintf(stderr, + " [Info] InnoDB: the file format in the system " + "tablespace is now set to %s.\n", *(char**) var_ptr); + } +} + +/******************************************************************** +Update the system variable innodb_adaptive_hash_index using the "saved" +value. This function is registered as a callback with MySQL. */ +static +void +innodb_adaptive_hash_index_update( +/*==============================*/ + THD* thd, /* in: thread handle */ + struct st_mysql_sys_var* var, /* in: pointer to + system variable */ + void* var_ptr, /* out: where the + formal string goes */ + const void* save) /* in: immediate result + from check function */ +{ + if (*(my_bool*) save) { + btr_search_enable(); + } else { + btr_search_disable(); + } +} + +/***************************************************************** +Check if it is a valid value of innodb_change_buffering. This function is +registered as a callback with MySQL. */ +static +int +innodb_change_buffering_validate( +/*=============================*/ + /* out: 0 for valid + innodb_change_buffering */ + THD* thd, /* in: thread handle */ + struct st_mysql_sys_var* var, /* in: pointer to system + variable */ + void* save, /* out: immediate result + for update function */ + struct st_mysql_value* value) /* in: incoming string */ +{ + const char* change_buffering_input; + char buff[STRING_BUFFER_USUAL_SIZE]; + int len = sizeof(buff); + + ut_a(save != NULL); + ut_a(value != NULL); + + change_buffering_input = value->val_str(value, buff, &len); + + if (change_buffering_input != NULL) { + ulint use; + + for (use = 0; use < UT_ARR_SIZE(innobase_change_buffering_values); + use++) { + if (!innobase_strcasecmp( + change_buffering_input, + innobase_change_buffering_values[use])) { + *(ibuf_use_t*) save = (ibuf_use_t) use; + return(0); + } + } + } + + return(1); +} + +/******************************************************************** +Update the system variable innodb_change_buffering using the "saved" +value. This function is registered as a callback with MySQL. */ +static +void +innodb_change_buffering_update( +/*===========================*/ + THD* thd, /* in: thread handle */ + struct st_mysql_sys_var* var, /* in: pointer to + system variable */ + void* var_ptr, /* out: where the + formal string goes */ + const void* save) /* in: immediate result + from check function */ +{ + ut_a(var_ptr != NULL); + ut_a(save != NULL); + ut_a((*(ibuf_use_t*) save) < IBUF_USE_COUNT); + + ibuf_use = *(const ibuf_use_t*) save; + + *(const char**) var_ptr = innobase_change_buffering_values[ibuf_use]; +} + +static int show_innodb_vars(THD *thd, SHOW_VAR *var, char *buff) +{ + innodb_export_status(); + var->type= SHOW_ARRAY; + var->value= (char *) &innodb_status_variables; + return 0; +} + +static SHOW_VAR innodb_status_variables_export[]= { + {"Innodb", (char*) &show_innodb_vars, SHOW_FUNC}, + {NullS, NullS, SHOW_LONG} +}; + +static struct st_mysql_storage_engine innobase_storage_engine= +{ MYSQL_HANDLERTON_INTERFACE_VERSION }; + +/* plugin options */ +static MYSQL_SYSVAR_BOOL(checksums, innobase_use_checksums, + PLUGIN_VAR_NOCMDARG | PLUGIN_VAR_READONLY, + "Enable InnoDB checksums validation (enabled by default). " + "Disable with --skip-innodb-checksums.", + NULL, NULL, TRUE); + +static MYSQL_SYSVAR_STR(data_home_dir, innobase_data_home_dir, + PLUGIN_VAR_READONLY, + "The common part for InnoDB table spaces.", + NULL, NULL, NULL); + +static MYSQL_SYSVAR_BOOL(extra_undoslots, innobase_extra_undoslots, + PLUGIN_VAR_NOCMDARG | PLUGIN_VAR_READONLY, + "Enable to use about 4000 undo slots instead of default 1024. " + "#### Attention: Once you enable this parameter, " + "don't use the datafile for normal mysqld or ibbackup! ####", + NULL, NULL, FALSE); + +static MYSQL_SYSVAR_BOOL(overwrite_relay_log_info, innobase_overwrite_relay_log_info, + PLUGIN_VAR_NOCMDARG | PLUGIN_VAR_READONLY, + "During InnoDB crash recovery on slave overwrite relay-log.info " + "to align master log file position if information in InnoDB and relay-log.info is different.", + NULL, NULL, FALSE); + +static MYSQL_SYSVAR_BOOL(doublewrite, innobase_use_doublewrite, + PLUGIN_VAR_NOCMDARG | PLUGIN_VAR_READONLY, + "Enable InnoDB doublewrite buffer (enabled by default). " + "Disable with --skip-innodb-doublewrite.", + NULL, NULL, TRUE); + +static MYSQL_SYSVAR_ULONG(fast_shutdown, innobase_fast_shutdown, + PLUGIN_VAR_OPCMDARG, + "Speeds up the shutdown process of the InnoDB storage engine. Possible " + "values are 0, 1 (faster)" + /* + NetWare can't close unclosed files, can't automatically kill remaining + threads, etc, so on this OS we disable the crash-like InnoDB shutdown. + */ + IF_NETWARE("", " or 2 (fastest - crash-like)") + ".", + NULL, NULL, 1, 0, IF_NETWARE(1,2), 0); + +static MYSQL_SYSVAR_BOOL(file_per_table, srv_file_per_table, + PLUGIN_VAR_NOCMDARG, + "Stores each InnoDB table to an .ibd file in the database dir.", + NULL, NULL, FALSE); + +static MYSQL_SYSVAR_STR(file_format, innobase_file_format_name, + PLUGIN_VAR_RQCMDARG, + "File format to use for new tables in .ibd files.", + innodb_file_format_name_validate, + innodb_file_format_name_update, "Antelope"); + +static MYSQL_SYSVAR_STR(file_format_check, innobase_file_format_check, + PLUGIN_VAR_OPCMDARG, + "The highest file format in the tablespace.", + innodb_file_format_check_validate, + innodb_file_format_check_update, + "on"); + +static MYSQL_SYSVAR_ULONG(flush_log_at_trx_commit, srv_flush_log_at_trx_commit, + PLUGIN_VAR_OPCMDARG, + "Set to 0 (write and flush once per second)," + " 1 (write and flush at each commit)" + " or 2 (write at commit, flush once per second).", + NULL, NULL, 1, 0, 2, 0); + +static MYSQL_SYSVAR_STR(flush_method, innobase_unix_file_flush_method, + PLUGIN_VAR_RQCMDARG | PLUGIN_VAR_READONLY, + "With which method to flush data.", NULL, NULL, NULL); + +static MYSQL_SYSVAR_BOOL(locks_unsafe_for_binlog, innobase_locks_unsafe_for_binlog, + PLUGIN_VAR_NOCMDARG | PLUGIN_VAR_READONLY, + "Force InnoDB to not use next-key locking, to use only row-level locking.", + NULL, NULL, FALSE); + +static MYSQL_SYSVAR_ULONG(show_verbose_locks, srv_show_verbose_locks, + PLUGIN_VAR_OPCMDARG, + "Whether to show records locked in SHOW INNODB STATUS.", + NULL, NULL, 0, 0, 1, 0); + +static MYSQL_SYSVAR_ULONG(show_locks_held, srv_show_locks_held, + PLUGIN_VAR_RQCMDARG, + "Number of locks held to print for each InnoDB transaction in SHOW INNODB STATUS.", + NULL, NULL, 10, 0, 1000, 0); + +#ifdef UNIV_LOG_ARCHIVE +static MYSQL_SYSVAR_STR(log_arch_dir, innobase_log_arch_dir, + PLUGIN_VAR_RQCMDARG | PLUGIN_VAR_READONLY, + "Where full logs should be archived.", NULL, NULL, NULL); + +static MYSQL_SYSVAR_BOOL(log_archive, innobase_log_archive, + PLUGIN_VAR_OPCMDARG | PLUGIN_VAR_READONLY, + "Set to 1 if you want to have logs archived.", NULL, NULL, FALSE); +#endif /* UNIV_LOG_ARCHIVE */ + +static MYSQL_SYSVAR_STR(log_group_home_dir, innobase_log_group_home_dir, + PLUGIN_VAR_RQCMDARG | PLUGIN_VAR_READONLY, + "Path to InnoDB log files.", NULL, NULL, NULL); + +static MYSQL_SYSVAR_ULONG(max_dirty_pages_pct, srv_max_buf_pool_modified_pct, + PLUGIN_VAR_RQCMDARG, + "Percentage of dirty pages allowed in bufferpool.", + NULL, NULL, 90, 0, 100, 0); + +static MYSQL_SYSVAR_ULONG(max_purge_lag, srv_max_purge_lag, + PLUGIN_VAR_RQCMDARG, + "Desired maximum length of the purge queue (0 = no limit)", + NULL, NULL, 0, 0, ~0L, 0); + +static MYSQL_SYSVAR_BOOL(rollback_on_timeout, innobase_rollback_on_timeout, + PLUGIN_VAR_OPCMDARG | PLUGIN_VAR_READONLY, + "Roll back the complete transaction on lock wait timeout, for 4.x compatibility (disabled by default)", + NULL, NULL, FALSE); + +static MYSQL_SYSVAR_BOOL(status_file, innobase_create_status_file, + PLUGIN_VAR_OPCMDARG | PLUGIN_VAR_NOSYSVAR, + "Enable SHOW INNODB STATUS output in the innodb_status.<pid> file", + NULL, NULL, FALSE); + +static MYSQL_SYSVAR_BOOL(stats_on_metadata, innobase_stats_on_metadata, + PLUGIN_VAR_OPCMDARG, + "Enable statistics gathering for metadata commands such as SHOW TABLE STATUS (on by default)", + NULL, NULL, TRUE); + +static MYSQL_SYSVAR_ULONGLONG(stats_sample_pages, srv_stats_sample_pages, + PLUGIN_VAR_RQCMDARG, + "The number of index pages to sample when calculating statistics (default 8)", + NULL, NULL, 8, 1, ~0ULL, 0); + +const char *innobase_stats_method_names[]= +{ + "nulls_equal", + "nulls_unequal", + "nulls_ignored", + NullS +}; +TYPELIB innobase_stats_method_typelib= +{ + array_elements(innobase_stats_method_names) - 1, "innobase_stats_method_typelib", + innobase_stats_method_names, NULL +}; +static MYSQL_SYSVAR_ENUM(stats_method, srv_stats_method, + PLUGIN_VAR_RQCMDARG, + "Specifies how InnoDB index statistics collection code should threat NULLs. " + "Possible values of name are same to for 'myisam_stats_method'. " + "This is startup parameter.", + NULL, NULL, 0, &innobase_stats_method_typelib); + +static MYSQL_SYSVAR_ULONG(stats_auto_update, srv_stats_auto_update, + PLUGIN_VAR_RQCMDARG, + "Enable/Disable InnoDB's auto update statistics of indexes. " + "(except for ANALYZE TABLE command) 0:disable 1:enable", + NULL, NULL, 1, 0, 1, 0); + +static MYSQL_SYSVAR_BOOL(adaptive_hash_index, btr_search_enabled, + PLUGIN_VAR_OPCMDARG, + "Enable InnoDB adaptive hash index (enabled by default). " + "Disable with --skip-innodb-adaptive-hash-index.", + NULL, innodb_adaptive_hash_index_update, TRUE); + +static MYSQL_SYSVAR_ULONG(replication_delay, srv_replication_delay, + PLUGIN_VAR_RQCMDARG, + "Replication thread delay (ms) on the slave server if " + "innodb_thread_concurrency is reached (0 by default)", + NULL, NULL, 0, 0, ~0UL, 0); + +static MYSQL_SYSVAR_LONG(additional_mem_pool_size, innobase_additional_mem_pool_size, + PLUGIN_VAR_RQCMDARG | PLUGIN_VAR_READONLY, + "Size of a memory pool InnoDB uses to store data dictionary information and other internal data structures.", + NULL, NULL, 1*1024*1024L, 512*1024L, LONG_MAX, 1024); + +static MYSQL_SYSVAR_ULONG(autoextend_increment, srv_auto_extend_increment, + PLUGIN_VAR_RQCMDARG, + "Data file autoextend increment in megabytes", + NULL, NULL, 8L, 1L, 1000L, 0); + +static MYSQL_SYSVAR_LONGLONG(buffer_pool_size, innobase_buffer_pool_size, + PLUGIN_VAR_RQCMDARG | PLUGIN_VAR_READONLY, + "The size of the memory buffer InnoDB uses to cache data and indexes of its tables.", + NULL, NULL, 8*1024*1024L, 5*1024*1024L, LONGLONG_MAX, 1024*1024L); + +static MYSQL_SYSVAR_ULONG(commit_concurrency, srv_commit_concurrency, + PLUGIN_VAR_RQCMDARG, + "Helps in performance tuning in heavily concurrent environments.", + NULL, NULL, 0, 0, 1000, 0); + +static MYSQL_SYSVAR_ULONG(concurrency_tickets, srv_n_free_tickets_to_enter, + PLUGIN_VAR_RQCMDARG, + "Number of times a thread is allowed to enter InnoDB within the same SQL query after it has once got the ticket", + NULL, NULL, 500L, 1L, ~0L, 0); + +static MYSQL_SYSVAR_LONG(file_io_threads, innobase_file_io_threads, + PLUGIN_VAR_RQCMDARG | PLUGIN_VAR_READONLY, + "Number of file I/O threads in InnoDB.", + NULL, NULL, 4, 4, 64, 0); + +static MYSQL_SYSVAR_LONG(force_recovery, innobase_force_recovery, + PLUGIN_VAR_RQCMDARG | PLUGIN_VAR_READONLY, + "Helps to save your data in case the disk image of the database becomes corrupt.", + NULL, NULL, 0, 0, 6, 0); + +static MYSQL_SYSVAR_LONG(log_buffer_size, innobase_log_buffer_size, + PLUGIN_VAR_RQCMDARG | PLUGIN_VAR_READONLY, + "The size of the buffer which InnoDB uses to write log to the log files on disk.", + NULL, NULL, 1024*1024L, 256*1024L, LONG_MAX, 1024); + +static MYSQL_SYSVAR_LONGLONG(log_file_size, innobase_log_file_size, + PLUGIN_VAR_RQCMDARG | PLUGIN_VAR_READONLY, + "Size of each log file in a log group.", + NULL, NULL, 5*1024*1024L, 1*1024*1024L, LONGLONG_MAX, 1024*1024L); + +static MYSQL_SYSVAR_LONG(log_files_in_group, innobase_log_files_in_group, + PLUGIN_VAR_RQCMDARG | PLUGIN_VAR_READONLY, + "Number of log files in the log group. InnoDB writes to the files in a circular fashion. Value 3 is recommended here.", + NULL, NULL, 2, 2, 100, 0); + +static MYSQL_SYSVAR_LONG(mirrored_log_groups, innobase_mirrored_log_groups, + PLUGIN_VAR_RQCMDARG | PLUGIN_VAR_READONLY, + "Number of identical copies of log groups we keep for the database. Currently this should be set to 1.", + NULL, NULL, 1, 1, 10, 0); + +static MYSQL_SYSVAR_LONG(open_files, innobase_open_files, + PLUGIN_VAR_RQCMDARG | PLUGIN_VAR_READONLY, + "How many files at the maximum InnoDB keeps open at the same time.", + NULL, NULL, 300L, 10L, LONG_MAX, 0); + +static MYSQL_SYSVAR_ULONG(sync_spin_loops, srv_n_spin_wait_rounds, + PLUGIN_VAR_RQCMDARG, + "Count of spin-loop rounds in InnoDB mutexes", + NULL, NULL, 20L, 0L, ~0L, 0); + +static MYSQL_SYSVAR_BOOL(thread_concurrency_timer_based, + innobase_thread_concurrency_timer_based, + PLUGIN_VAR_NOCMDARG | PLUGIN_VAR_READONLY, + "Use InnoDB timer based concurrency throttling. ", + NULL, NULL, FALSE); + +static MYSQL_SYSVAR_ULONG(thread_concurrency, srv_thread_concurrency, + PLUGIN_VAR_RQCMDARG, + "Helps in performance tuning in heavily concurrent environments. Sets the maximum number of threads allowed inside InnoDB. Value 0 will disable the thread throttling.", + NULL, NULL, 0, 0, 1000, 0); + +static MYSQL_SYSVAR_ULONG(thread_sleep_delay, srv_thread_sleep_delay, + PLUGIN_VAR_RQCMDARG, + "Time of innodb thread sleeping before joining InnoDB queue (usec). Value 0 disable a sleep", + NULL, NULL, 10000L, 0L, ~0L, 0); + +static MYSQL_SYSVAR_STR(data_file_path, innobase_data_file_path, + PLUGIN_VAR_RQCMDARG | PLUGIN_VAR_READONLY, + "Path to individual files and their sizes.", + NULL, NULL, NULL); + +static MYSQL_SYSVAR_LONG(autoinc_lock_mode, innobase_autoinc_lock_mode, + PLUGIN_VAR_RQCMDARG | PLUGIN_VAR_READONLY, + "The AUTOINC lock modes supported by InnoDB: " + "0 => Old style AUTOINC locking (for backward" + " compatibility) " + "1 => New style AUTOINC locking " + "2 => No AUTOINC locking (unsafe for SBR)", + NULL, NULL, + AUTOINC_NEW_STYLE_LOCKING, /* Default setting */ + AUTOINC_OLD_STYLE_LOCKING, /* Minimum value */ + AUTOINC_NO_LOCKING, 0); /* Maximum value */ + +static MYSQL_SYSVAR_STR(version, innodb_version_str, + PLUGIN_VAR_NOCMDOPT | PLUGIN_VAR_READONLY, + "Percona-InnoDB-plugin version", NULL, NULL, INNODB_VERSION_STR); + +static MYSQL_SYSVAR_BOOL(use_sys_malloc, srv_use_sys_malloc, + PLUGIN_VAR_NOCMDARG | PLUGIN_VAR_READONLY, + "Use OS memory allocator instead of InnoDB's internal memory allocator", + NULL, NULL, TRUE); + +static MYSQL_SYSVAR_STR(change_buffering, innobase_change_buffering, + PLUGIN_VAR_RQCMDARG, + "Buffer changes to reduce random access: " + "OFF, ON, inserting, deleting, changing, or purging.", + innodb_change_buffering_validate, + innodb_change_buffering_update, NULL); + +static MYSQL_SYSVAR_ULONG(io_capacity, srv_io_capacity, + PLUGIN_VAR_RQCMDARG, + "Number of IO operations per second the server can do. Tunes background IO rate.", + NULL, NULL, 200, 100, 999999999, 0); + +static MYSQL_SYSVAR_LONGLONG(ibuf_max_size, srv_ibuf_max_size, + PLUGIN_VAR_RQCMDARG | PLUGIN_VAR_READONLY, + "The maximum size of the insert buffer. (in bytes)", + NULL, NULL, LONGLONG_MAX, 0, LONGLONG_MAX, 0); + +static MYSQL_SYSVAR_ULONG(ibuf_active_contract, srv_ibuf_active_contract, + PLUGIN_VAR_RQCMDARG, + "Enable/Disable active_contract of insert buffer. 0:disable 1:enable", + NULL, NULL, 0, 0, 1, 0); + +static MYSQL_SYSVAR_ULONG(ibuf_accel_rate, srv_ibuf_accel_rate, + PLUGIN_VAR_RQCMDARG, + "Tunes amount of insert buffer processing of background, in addition to innodb_io_capacity. (in percentage)", + NULL, NULL, 100, 100, 999999999, 0); + +static MYSQL_SYSVAR_ULONG(flush_neighbor_pages, srv_flush_neighbor_pages, + PLUGIN_VAR_RQCMDARG, + "Enable/Disable flushing also neighbor pages. 0:disable 1:enable", + NULL, NULL, 1, 0, 1, 0); + +static +void +innodb_read_ahead_update( + THD* thd, + struct st_mysql_sys_var* var, + void* var_ptr, + const void* save) +{ + *(long *)var_ptr= (*(long *)save) & 3; +} +const char *read_ahead_names[]= +{ + "none", /* 0 */ + "random", + "linear", + "both", /* 3 */ + /* For compatibility of the older patch */ + "0", /* 4 ("none" + 4) */ + "1", + "2", + "3", /* 7 ("both" + 4) */ + NullS +}; +TYPELIB read_ahead_typelib= +{ + array_elements(read_ahead_names) - 1, "read_ahead_typelib", + read_ahead_names, NULL +}; +static MYSQL_SYSVAR_ENUM(read_ahead, srv_read_ahead, + PLUGIN_VAR_RQCMDARG, + "Control read ahead activity. (none, random, linear, [both])", + NULL, innodb_read_ahead_update, 3, &read_ahead_typelib); + +static +void +innodb_adaptive_checkpoint_update( + THD* thd, + struct st_mysql_sys_var* var, + void* var_ptr, + const void* save) +{ + *(long *)var_ptr= (*(long *)save) % 3; +} +const char *adaptive_checkpoint_names[]= +{ + "none", /* 0 */ + "reflex", /* 1 */ + "estimate", /* 2 */ + /* For compatibility of the older patch */ + "0", /* 3 ("none" + 3) */ + "1", /* 4 ("reflex" + 3) */ + "2", /* 5 ("estimate" + 3) */ + NullS +}; +TYPELIB adaptive_checkpoint_typelib= +{ + array_elements(adaptive_checkpoint_names) - 1, "adaptive_checkpoint_typelib", + adaptive_checkpoint_names, NULL +}; +static MYSQL_SYSVAR_ENUM(adaptive_checkpoint, srv_adaptive_checkpoint, + PLUGIN_VAR_RQCMDARG, + "Enable/Disable flushing along modified age. ([none], reflex, estimate)", + NULL, innodb_adaptive_checkpoint_update, 0, &adaptive_checkpoint_typelib); + +static MYSQL_SYSVAR_ULONG(enable_unsafe_group_commit, srv_enable_unsafe_group_commit, + PLUGIN_VAR_RQCMDARG, + "Enable/Disable unsafe group commit when support_xa=OFF and use with binlog or other XA storage engine.", + NULL, NULL, 0, 0, 1, 0); + +static MYSQL_SYSVAR_ULONG(read_io_threads, innobase_read_io_threads, + PLUGIN_VAR_RQCMDARG | PLUGIN_VAR_READONLY, + "Number of background read I/O threads in InnoDB.", + NULL, NULL, 8, 1, 64, 0); + +static MYSQL_SYSVAR_ULONG(write_io_threads, innobase_write_io_threads, + PLUGIN_VAR_RQCMDARG | PLUGIN_VAR_READONLY, + "Number of background write I/O threads in InnoDB.", + NULL, NULL, 8, 1, 64, 0); + +static MYSQL_SYSVAR_ULONG(expand_import, srv_expand_import, + PLUGIN_VAR_RQCMDARG, + "Enable/Disable converting automatically *.ibd files when import tablespace.", + NULL, NULL, 0, 0, 1, 0); + +static MYSQL_SYSVAR_ULONG(extra_rsegments, srv_extra_rsegments, + PLUGIN_VAR_RQCMDARG | PLUGIN_VAR_READONLY, + "Number of extra user rollback segments when create new database.", + NULL, NULL, 0, 0, 127, 0); + +static MYSQL_SYSVAR_ULONG(dict_size_limit, srv_dict_size_limit, + PLUGIN_VAR_RQCMDARG, + "Limit the allocated memory for dictionary cache. (0: unlimited)", + NULL, NULL, 0, 0, LONG_MAX, 0); + +static struct st_mysql_sys_var* innobase_system_variables[]= { + MYSQL_SYSVAR(additional_mem_pool_size), + MYSQL_SYSVAR(autoextend_increment), + MYSQL_SYSVAR(buffer_pool_size), + MYSQL_SYSVAR(checksums), + MYSQL_SYSVAR(commit_concurrency), + MYSQL_SYSVAR(concurrency_tickets), + MYSQL_SYSVAR(data_file_path), + MYSQL_SYSVAR(data_home_dir), + MYSQL_SYSVAR(doublewrite), + MYSQL_SYSVAR(extra_undoslots), + MYSQL_SYSVAR(fast_shutdown), + MYSQL_SYSVAR(file_io_threads), + MYSQL_SYSVAR(file_per_table), + MYSQL_SYSVAR(file_format), + MYSQL_SYSVAR(file_format_check), + MYSQL_SYSVAR(flush_log_at_trx_commit), + MYSQL_SYSVAR(flush_method), + MYSQL_SYSVAR(force_recovery), + MYSQL_SYSVAR(locks_unsafe_for_binlog), + MYSQL_SYSVAR(lock_wait_timeout), +#ifdef UNIV_LOG_ARCHIVE + MYSQL_SYSVAR(log_arch_dir), + MYSQL_SYSVAR(log_archive), +#endif /* UNIV_LOG_ARCHIVE */ + MYSQL_SYSVAR(log_buffer_size), + MYSQL_SYSVAR(log_file_size), + MYSQL_SYSVAR(log_files_in_group), + MYSQL_SYSVAR(log_group_home_dir), + MYSQL_SYSVAR(max_dirty_pages_pct), + MYSQL_SYSVAR(max_purge_lag), + MYSQL_SYSVAR(mirrored_log_groups), + MYSQL_SYSVAR(open_files), + MYSQL_SYSVAR(overwrite_relay_log_info), + MYSQL_SYSVAR(rollback_on_timeout), + MYSQL_SYSVAR(stats_on_metadata), + MYSQL_SYSVAR(stats_method), + MYSQL_SYSVAR(stats_auto_update), + MYSQL_SYSVAR(stats_sample_pages), + MYSQL_SYSVAR(adaptive_hash_index), + MYSQL_SYSVAR(replication_delay), + MYSQL_SYSVAR(status_file), + MYSQL_SYSVAR(strict_mode), + MYSQL_SYSVAR(support_xa), + MYSQL_SYSVAR(sync_spin_loops), + MYSQL_SYSVAR(table_locks), + MYSQL_SYSVAR(thread_concurrency), + MYSQL_SYSVAR(thread_concurrency_timer_based), + MYSQL_SYSVAR(thread_sleep_delay), + MYSQL_SYSVAR(autoinc_lock_mode), + MYSQL_SYSVAR(show_verbose_locks), + MYSQL_SYSVAR(show_locks_held), + MYSQL_SYSVAR(version), + MYSQL_SYSVAR(io_capacity), + MYSQL_SYSVAR(ibuf_max_size), + MYSQL_SYSVAR(ibuf_active_contract), + MYSQL_SYSVAR(ibuf_accel_rate), + MYSQL_SYSVAR(flush_neighbor_pages), + MYSQL_SYSVAR(read_ahead), + MYSQL_SYSVAR(adaptive_checkpoint), + MYSQL_SYSVAR(enable_unsafe_group_commit), + MYSQL_SYSVAR(read_io_threads), + MYSQL_SYSVAR(write_io_threads), + MYSQL_SYSVAR(expand_import), + MYSQL_SYSVAR(extra_rsegments), + MYSQL_SYSVAR(dict_size_limit), + MYSQL_SYSVAR(use_sys_malloc), + MYSQL_SYSVAR(change_buffering), + NULL +}; + +#ifdef MYSQL_DYNAMIC_PLUGIN +struct st_mysql_sys_var +{ + MYSQL_PLUGIN_VAR_HEADER; + void* value; +}; + +struct param_mapping +{ + const char* server; /* Parameter name in the server. */ + const char* plugin; /* Paramater name in the plugin. */ +}; + +/******************************************************************** +Match the parameters from the static and dynamic versions. */ +static +bool +innobase_match_parameter( +/*=====================*/ + /* out: true if names match */ + const char* from_server, /* in: variable name from server */ + const char* from_plugin) /* in: variable name from plugin */ +{ + static const param_mapping param_map[] = { + {"use_adaptive_hash_indexes", "adaptive_hash_index"} + }; + + if (strcmp(from_server, from_plugin) == 0) { + return(true); + } + + const param_mapping* param = param_map; + int n_elems = sizeof(param_map) / sizeof(param_map[0]); + + for (int i = 0; i < n_elems; ++i, ++param) { + + if (strcmp(param->server, from_server) == 0 + && strcmp(param->plugin, from_plugin) == 0) { + + return(true); + } + } + + return(false); +} + +/******************************************************************** +Copy InnoDB system variables from the static InnoDB to the dynamic +plugin. */ +static +bool +innodb_plugin_init(void) +/*====================*/ + /* out: TRUE if the dynamic InnoDB plugin should start */ +{ +#if !MYSQL_STORAGE_ENGINE_PLUGIN +#error "MYSQL_STORAGE_ENGINE_PLUGIN must be nonzero." +#endif + + /* Copy the system variables. */ + + struct st_mysql_plugin* builtin; + struct st_mysql_sys_var** sta; /* static parameters */ + struct st_mysql_sys_var** dyn; /* dynamic parameters */ + +#ifdef __WIN__ + if (!builtin_innobase_plugin_ptr) { + + return(true); + } + + builtin = builtin_innobase_plugin_ptr; +#else + switch (builtin_innobase_plugin) { + case 0: + return(true); + case MYSQL_STORAGE_ENGINE_PLUGIN: + break; + default: + return(false); + } + + builtin = (struct st_mysql_plugin*) &builtin_innobase_plugin; +#endif + + for (sta = builtin->system_vars; *sta != NULL; sta++) { + + for (dyn = innobase_system_variables; *dyn != NULL; dyn++) { + + /* do not copy session variables */ + if (((*sta)->flags | (*dyn)->flags) + & PLUGIN_VAR_THDLOCAL) { + continue; + } + + if (innobase_match_parameter((*sta)->name, + (*dyn)->name)) { + + /* found the corresponding parameter */ + + /* check if the flags are the same, + ignoring differences in the READONLY or + NOSYSVAR flags; + e.g. we are not copying string variable to + an integer one, but we do not care if it is + readonly in the static and not in the + dynamic */ + if (((*sta)->flags ^ (*dyn)->flags) + & ~(PLUGIN_VAR_READONLY + | PLUGIN_VAR_NOSYSVAR)) { + + fprintf(stderr, + "InnoDB: %s in static InnoDB " + "(flags=0x%x) differs from " + "%s in dynamic InnoDB " + "(flags=0x%x)\n", + (*sta)->name, (*sta)->flags, + (*dyn)->name, (*dyn)->flags); + + /* we could break; here leaving this + parameter uncopied */ + return(false); + } + + /* assign the value of the static parameter + to the dynamic one, according to their type */ + +#define COPY_VAR(label, type) \ + case label: \ + *(type*)(*dyn)->value = *(type*)(*sta)->value; \ + break; + + switch ((*sta)->flags + & ~(PLUGIN_VAR_MASK + | PLUGIN_VAR_UNSIGNED)) { + + COPY_VAR(PLUGIN_VAR_BOOL, char); + COPY_VAR(PLUGIN_VAR_INT, int); + COPY_VAR(PLUGIN_VAR_LONG, long); + COPY_VAR(PLUGIN_VAR_LONGLONG, long long); + COPY_VAR(PLUGIN_VAR_STR, char*); + + default: + fprintf(stderr, + "InnoDB: unknown flags " + "0x%x for %s\n", + (*sta)->flags, (*sta)->name); + } + + /* Make the static InnoDB variable point to + the dynamic one */ + (*sta)->value = (*dyn)->value; + + break; + } + } + } + + return(true); +} +#endif /* MYSQL_DYNAMIC_PLUGIN */ + +mysql_declare_plugin(innobase) +{ + MYSQL_STORAGE_ENGINE_PLUGIN, + &innobase_storage_engine, + innobase_hton_name, + "Innobase Oy", + "Supports transactions, row-level locking, and foreign keys", + PLUGIN_LICENSE_GPL, + innobase_init, /* Plugin Init */ + NULL, /* Plugin Deinit */ + INNODB_VERSION_SHORT, + innodb_status_variables_export,/* status variables */ + innobase_system_variables, /* system variables */ + NULL /* reserved */ +}, +i_s_innodb_rseg, +i_s_innodb_buffer_pool_pages, +i_s_innodb_buffer_pool_pages_index, +i_s_innodb_buffer_pool_pages_blob, +i_s_innodb_trx, +i_s_innodb_locks, +i_s_innodb_lock_waits, +i_s_innodb_cmp, +i_s_innodb_cmp_reset, +i_s_innodb_cmpmem, +i_s_innodb_cmpmem_reset, +i_s_innodb_table_stats, +i_s_innodb_index_stats, +i_s_innodb_patches +mysql_declare_plugin_end; + +#ifdef UNIV_COMPILE_TEST_FUNCS + +typedef struct innobase_convert_name_test_struct { + char* buf; + ulint buflen; + const char* id; + ulint idlen; + void* thd; + ibool file_id; + + const char* expected; +} innobase_convert_name_test_t; + +void +test_innobase_convert_name() +{ + char buf[1024]; + ulint i; + + innobase_convert_name_test_t test_input[] = { + {buf, sizeof(buf), "abcd", 4, NULL, TRUE, "\"abcd\""}, + {buf, 7, "abcd", 4, NULL, TRUE, "\"abcd\""}, + {buf, 6, "abcd", 4, NULL, TRUE, "\"abcd\""}, + {buf, 5, "abcd", 4, NULL, TRUE, "\"abc\""}, + {buf, 4, "abcd", 4, NULL, TRUE, "\"ab\""}, + + {buf, sizeof(buf), "ab@0060cd", 9, NULL, TRUE, "\"ab`cd\""}, + {buf, 9, "ab@0060cd", 9, NULL, TRUE, "\"ab`cd\""}, + {buf, 8, "ab@0060cd", 9, NULL, TRUE, "\"ab`cd\""}, + {buf, 7, "ab@0060cd", 9, NULL, TRUE, "\"ab`cd\""}, + {buf, 6, "ab@0060cd", 9, NULL, TRUE, "\"ab`c\""}, + {buf, 5, "ab@0060cd", 9, NULL, TRUE, "\"ab`\""}, + {buf, 4, "ab@0060cd", 9, NULL, TRUE, "\"ab\""}, + + {buf, sizeof(buf), "ab\"cd", 5, NULL, TRUE, + "\"#mysql50#ab\"\"cd\""}, + {buf, 17, "ab\"cd", 5, NULL, TRUE, + "\"#mysql50#ab\"\"cd\""}, + {buf, 16, "ab\"cd", 5, NULL, TRUE, + "\"#mysql50#ab\"\"c\""}, + {buf, 15, "ab\"cd", 5, NULL, TRUE, + "\"#mysql50#ab\"\"\""}, + {buf, 14, "ab\"cd", 5, NULL, TRUE, + "\"#mysql50#ab\""}, + {buf, 13, "ab\"cd", 5, NULL, TRUE, + "\"#mysql50#ab\""}, + {buf, 12, "ab\"cd", 5, NULL, TRUE, + "\"#mysql50#a\""}, + {buf, 11, "ab\"cd", 5, NULL, TRUE, + "\"#mysql50#\""}, + {buf, 10, "ab\"cd", 5, NULL, TRUE, + "\"#mysql50\""}, + + {buf, sizeof(buf), "ab/cd", 5, NULL, TRUE, "\"ab\".\"cd\""}, + {buf, 9, "ab/cd", 5, NULL, TRUE, "\"ab\".\"cd\""}, + {buf, 8, "ab/cd", 5, NULL, TRUE, "\"ab\".\"c\""}, + {buf, 7, "ab/cd", 5, NULL, TRUE, "\"ab\".\"\""}, + {buf, 6, "ab/cd", 5, NULL, TRUE, "\"ab\"."}, + {buf, 5, "ab/cd", 5, NULL, TRUE, "\"ab\"."}, + {buf, 4, "ab/cd", 5, NULL, TRUE, "\"ab\""}, + {buf, 3, "ab/cd", 5, NULL, TRUE, "\"a\""}, + {buf, 2, "ab/cd", 5, NULL, TRUE, "\"\""}, + /* XXX probably "" is a better result in this case + {buf, 1, "ab/cd", 5, NULL, TRUE, "."}, + */ + {buf, 0, "ab/cd", 5, NULL, TRUE, ""}, + }; + + for (i = 0; i < sizeof(test_input) / sizeof(test_input[0]); i++) { + + char* end; + ibool ok = TRUE; + size_t res_len; + + fprintf(stderr, "TESTING %lu, %s, %lu, %s\n", + test_input[i].buflen, + test_input[i].id, + test_input[i].idlen, + test_input[i].expected); + + end = innobase_convert_name( + test_input[i].buf, + test_input[i].buflen, + test_input[i].id, + test_input[i].idlen, + test_input[i].thd, + test_input[i].file_id); + + res_len = (size_t) (end - test_input[i].buf); + + if (res_len != strlen(test_input[i].expected)) { + + fprintf(stderr, "unexpected len of the result: %u, " + "expected: %u\n", (unsigned) res_len, + (unsigned) strlen(test_input[i].expected)); + ok = FALSE; + } + + if (memcmp(test_input[i].buf, + test_input[i].expected, + strlen(test_input[i].expected)) != 0 + || !ok) { + + fprintf(stderr, "unexpected result: %.*s, " + "expected: %s\n", (int) res_len, + test_input[i].buf, + test_input[i].expected); + ok = FALSE; + } + + if (ok) { + fprintf(stderr, "OK: res: %.*s\n\n", (int) res_len, + buf); + } else { + fprintf(stderr, "FAILED\n\n"); + return; + } + } +} + +#endif /* UNIV_COMPILE_TEST_FUNCS */ diff --git a/storage/xtradb/handler/ha_innodb.h b/storage/xtradb/handler/ha_innodb.h new file mode 100644 index 00000000000..c08dd3ed173 --- /dev/null +++ b/storage/xtradb/handler/ha_innodb.h @@ -0,0 +1,283 @@ +/***************************************************************************** + +Copyright (c) 2000, 2009, MySQL AB & Innobase Oy. 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 + +*****************************************************************************/ + +/* + This file is based on ha_berkeley.h of MySQL distribution + + This file defines the Innodb handler: the interface between MySQL and + Innodb +*/ + +#ifdef USE_PRAGMA_INTERFACE +#pragma interface /* gcc class implementation */ +#endif + +typedef struct st_innobase_share { + THR_LOCK lock; + pthread_mutex_t mutex; + const char* table_name; + uint use_count; + void* table_name_hash; +} INNOBASE_SHARE; + + +struct dict_index_struct; +struct row_prebuilt_struct; + +typedef struct dict_index_struct dict_index_t; +typedef struct row_prebuilt_struct row_prebuilt_t; + +/* The class defining a handle to an Innodb table */ +class ha_innobase: public handler +{ + row_prebuilt_t* prebuilt; /* prebuilt struct in InnoDB, used + to save CPU time with prebuilt data + structures*/ + THD* user_thd; /* the thread handle of the user + currently using the handle; this is + set in external_lock function */ + THR_LOCK_DATA lock; + INNOBASE_SHARE *share; + + uchar* upd_buff; /* buffer used in updates */ + uchar* key_val_buff; /* buffer used in converting + search key values from MySQL format + to Innodb format */ + ulong upd_and_key_val_buff_len; + /* the length of each of the previous + two buffers */ + Table_flags int_table_flags; + uint primary_key; + ulong start_of_scan; /* this is set to 1 when we are + starting a table scan but have not + yet fetched any row, else 0 */ + uint last_match_mode;/* match mode of the latest search: + ROW_SEL_EXACT, ROW_SEL_EXACT_PREFIX, + or undefined */ + uint num_write_row; /* number of write_row() calls */ + + uint store_key_val_for_row(uint keynr, char* buff, uint buff_len, + const uchar* record); + inline void update_thd(THD* thd); + void update_thd(); + int change_active_index(uint keynr); + int general_fetch(uchar* buf, uint direction, uint match_mode); + ulint innobase_lock_autoinc(); + ulonglong innobase_peek_autoinc(); + ulint innobase_set_max_autoinc(ulonglong auto_inc); + ulint innobase_reset_autoinc(ulonglong auto_inc); + ulint innobase_get_autoinc(ulonglong* value); + ulint innobase_update_autoinc(ulonglong auto_inc); + ulint innobase_initialize_autoinc(); + dict_index_t* innobase_get_index(uint keynr); + ulonglong innobase_get_int_col_max_value(const Field* field); + + /* Init values for the class: */ + public: + ha_innobase(handlerton *hton, TABLE_SHARE *table_arg); + ~ha_innobase(); + /* + 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. + */ + enum row_type get_row_type() const; + + const char* table_type() const; + const char* index_type(uint key_number); + const char** bas_ext() const; + Table_flags table_flags() const; + ulong index_flags(uint idx, uint part, bool all_parts) const; + uint max_supported_keys() const; + uint max_supported_key_length() const; + uint max_supported_key_part_length() const; + const key_map* keys_to_use_for_scanning(); + + int open(const char *name, int mode, uint test_if_locked); + int close(void); + double scan_time(); + double read_time(uint index, uint ranges, ha_rows rows); + + int write_row(uchar * buf); + int update_row(const uchar * old_data, uchar * new_data); + int delete_row(const uchar * buf); + bool was_semi_consistent_read(); + void try_semi_consistent_read(bool yes); + void unlock_row(); + +#ifdef ROW_MERGE_IS_INDEX_USABLE + /** Check if an index can be used by this transaction. + * @param keynr key number to check + * @return true if available, false if the index + * does not contain old records that exist + * in the read view of this transaction */ + bool is_index_available(uint keynr); +#endif /* ROW_MERGE_IS_INDEX_USABLE */ + 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_read_idx(uchar * buf, uint index, const uchar * key, + uint key_len, enum ha_rkey_function find_flag); + int index_read_last(uchar * buf, const uchar * key, uint key_len); + int index_next(uchar * buf); + int index_next_same(uchar * buf, const uchar *key, uint keylen); + int index_prev(uchar * buf); + int index_first(uchar * buf); + int index_last(uchar * buf); + + 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 info(uint); + int analyze(THD* thd,HA_CHECK_OPT* check_opt); + int optimize(THD* thd,HA_CHECK_OPT* check_opt); + int discard_or_import_tablespace(my_bool discard); + int extra(enum ha_extra_function operation); + int reset(); + int external_lock(THD *thd, int lock_type); + int transactional_table_lock(THD *thd, int lock_type); + int start_stmt(THD *thd, thr_lock_type lock_type); + void position(uchar *record); + ha_rows records_in_range(uint inx, key_range *min_key, key_range + *max_key); + ha_rows estimate_rows_upper_bound(); + + void update_create_info(HA_CREATE_INFO* create_info); + int create(const char *name, register TABLE *form, + HA_CREATE_INFO *create_info); + int delete_all_rows(); + int delete_table(const char *name); + int rename_table(const char* from, const char* to); + int check(THD* thd, HA_CHECK_OPT* check_opt); + char* update_table_comment(const char* comment); + char* get_foreign_key_create_info(); + int get_foreign_key_list(THD *thd, List<FOREIGN_KEY_INFO> *f_key_list); + bool can_switch_engines(); + uint referenced_by_foreign_key(); + void free_foreign_key_create_info(char* str); + THR_LOCK_DATA **store_lock(THD *thd, THR_LOCK_DATA **to, + enum thr_lock_type lock_type); + void init_table_handle_for_HANDLER(); + virtual void get_auto_increment(ulonglong offset, ulonglong increment, + ulonglong nb_desired_values, + ulonglong *first_value, + ulonglong *nb_reserved_values); + int reset_auto_increment(ulonglong value); + + virtual bool get_error_message(int error, String *buf); + + uint8 table_cache_type(); + /* + ask handler about permission to cache table during query registration + */ + my_bool register_query_cache_table(THD *thd, char *table_key, + uint key_length, + qc_engine_callback *call_back, + ulonglong *engine_data); + static char *get_mysql_bin_log_name(); + static ulonglong get_mysql_bin_log_pos(); + bool primary_key_is_clustered(); + int cmp_ref(const uchar *ref1, const uchar *ref2); + /** Fast index creation (smart ALTER TABLE) @see handler0alter.cc @{ */ + 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); + /** @} */ + bool check_if_incompatible_data(HA_CREATE_INFO *info, + uint table_changes); +}; + +/* Some accessor functions which the InnoDB plugin needs, but which +can not be added to mysql/plugin.h as part of the public interface; +the definitions are bracketed with #ifdef INNODB_COMPATIBILITY_HOOKS */ + +#ifndef INNODB_COMPATIBILITY_HOOKS +#error InnoDB needs MySQL to be built with #define INNODB_COMPATIBILITY_HOOKS +#endif + +extern "C" { +struct charset_info_st *thd_charset(MYSQL_THD thd); +char **thd_query(MYSQL_THD thd); + +/** Get the file name of the MySQL binlog. + * @return the name of the binlog file + */ +const char* mysql_bin_log_file_name(void); + +/** Get the current position of the MySQL binlog. + * @return byte offset from the beginning of the binlog + */ +ulonglong mysql_bin_log_file_pos(void); + +/** + Check if a user thread is a replication slave thread + @param thd user thread + @retval 0 the user thread is not a replication slave thread + @retval 1 the user thread is a replication slave thread +*/ +int thd_slave_thread(const MYSQL_THD thd); + +/** + Check if a user thread is running a non-transactional update + @param thd user thread + @retval 0 the user thread is not running a non-transactional update + @retval 1 the user thread is running a non-transactional update +*/ +int thd_non_transactional_update(const MYSQL_THD thd); + +/** + Get the user thread's binary logging format + @param thd user thread + @return Value to be used as index into the binlog_format_names array +*/ +int thd_binlog_format(const MYSQL_THD thd); + +/** + Mark transaction to rollback and mark error as fatal to a sub-statement. + @param thd Thread handle + @param all TRUE <=> rollback main transaction. +*/ +void thd_mark_transaction_to_rollback(MYSQL_THD thd, bool all); +} + +typedef struct trx_struct trx_t; +/************************************************************************ +Converts an InnoDB error code to a MySQL error code and also tells to MySQL +about a possible transaction rollback inside InnoDB caused by a lock wait +timeout or a deadlock. */ +extern "C" +int +convert_error_code_to_mysql( +/*========================*/ + /* out: MySQL error code */ + int error, /* in: InnoDB error code */ + ulint flags, /* in: InnoDB table flags, or 0 */ + MYSQL_THD thd); /* in: user thread handle or NULL */ + +/************************************************************************* +Allocates an InnoDB transaction for a MySQL handler object. */ +extern "C" +trx_t* +innobase_trx_allocate( +/*==================*/ + /* out: InnoDB transaction handle */ + MYSQL_THD thd); /* in: user thread handle */ diff --git a/storage/xtradb/handler/handler0alter.cc b/storage/xtradb/handler/handler0alter.cc new file mode 100644 index 00000000000..1b5466e66eb --- /dev/null +++ b/storage/xtradb/handler/handler0alter.cc @@ -0,0 +1,1214 @@ +/***************************************************************************** + +Copyright (c) 2005, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/****************************************************** +Smart ALTER TABLE +*******************************************************/ + +#include <mysql_priv.h> +#include <mysqld_error.h> + +extern "C" { +#include "log0log.h" +#include "row0merge.h" +#include "srv0srv.h" +#include "trx0trx.h" +#include "trx0roll.h" +#include "ha_prototypes.h" +#include "handler0alter.h" +} + +#include "ha_innodb.h" +#include "handler0vars.h" + +/***************************************************************** +Copies an InnoDB column to a MySQL field. This function is +adapted from row_sel_field_store_in_mysql_format(). */ +static +void +innobase_col_to_mysql( +/*==================*/ + const dict_col_t* col, /* in: InnoDB column */ + const uchar* data, /* in: InnoDB column data */ + ulint len, /* in: length of data, in bytes */ + Field* field) /* in/out: MySQL field */ +{ + uchar* ptr; + uchar* dest = field->ptr; + ulint flen = field->pack_length(); + + switch (col->mtype) { + case DATA_INT: + ut_ad(len == flen); + + /* Convert integer data from Innobase to little-endian + format, sign bit restored to normal */ + + for (ptr = dest + len; ptr != dest; ) { + *--ptr = *data++; + } + + if (!(field->flags & UNSIGNED_FLAG)) { + ((byte*) dest)[len - 1] ^= 0x80; + } + + break; + + case DATA_VARCHAR: + case DATA_VARMYSQL: + case DATA_BINARY: + field->reset(); + + if (field->type() == MYSQL_TYPE_VARCHAR) { + /* This is a >= 5.0.3 type true VARCHAR. Store the + length of the data to the first byte or the first + two bytes of dest. */ + + dest = row_mysql_store_true_var_len( + dest, len, flen - field->key_length()); + } + + /* Copy the actual data */ + memcpy(dest, data, len); + break; + + case DATA_BLOB: + /* Store a pointer to the BLOB buffer to dest: the BLOB was + already copied to the buffer in row_sel_store_mysql_rec */ + + row_mysql_store_blob_ref(dest, flen, data, len); + break; + +#ifdef UNIV_DEBUG + case DATA_MYSQL: + ut_ad(flen >= len); + ut_ad(col->mbmaxlen >= col->mbminlen); + ut_ad(col->mbmaxlen > col->mbminlen || flen == len); + memcpy(dest, data, len); + break; + + default: + case DATA_SYS_CHILD: + case DATA_SYS: + /* These column types should never be shipped to MySQL. */ + ut_ad(0); + + case DATA_CHAR: + case DATA_FIXBINARY: + case DATA_FLOAT: + case DATA_DOUBLE: + case DATA_DECIMAL: + /* Above are the valid column types for MySQL data. */ + ut_ad(flen == len); +#else /* UNIV_DEBUG */ + default: +#endif /* UNIV_DEBUG */ + memcpy(dest, data, len); + } +} + +/***************************************************************** +Copies an InnoDB record to table->record[0]. */ +extern "C" UNIV_INTERN +void +innobase_rec_to_mysql( +/*==================*/ + TABLE* table, /* in/out: MySQL table */ + const rec_t* rec, /* in: record */ + const dict_index_t* index, /* in: index */ + const ulint* offsets) /* in: rec_get_offsets( + rec, index, ...) */ +{ + uint n_fields = table->s->fields; + uint i; + + ut_ad(n_fields == dict_table_get_n_user_cols(index->table)); + + for (i = 0; i < n_fields; i++) { + Field* field = table->field[i]; + ulint ipos; + ulint ilen; + const uchar* ifield; + + field->reset(); + + ipos = dict_index_get_nth_col_pos(index, i); + + if (UNIV_UNLIKELY(ipos == ULINT_UNDEFINED)) { +null_field: + field->set_null(); + continue; + } + + ifield = rec_get_nth_field(rec, offsets, ipos, &ilen); + + /* Assign the NULL flag */ + if (ilen == UNIV_SQL_NULL) { + ut_ad(field->real_maybe_null()); + goto null_field; + } + + field->set_notnull(); + + innobase_col_to_mysql( + dict_field_get_col( + dict_index_get_nth_field(index, ipos)), + ifield, ilen, field); + } +} + +/***************************************************************** +Resets table->record[0]. */ +extern "C" UNIV_INTERN +void +innobase_rec_reset( +/*===============*/ + TABLE* table) /* in/out: MySQL table */ +{ + uint n_fields = table->s->fields; + uint i; + + for (i = 0; i < n_fields; i++) { + table->field[i]->set_default(); + } +} + +/********************************************************************** +Removes the filename encoding of a database and table name. */ +static +void +innobase_convert_tablename( +/*=======================*/ + char* s) /* in: identifier; out: decoded identifier */ +{ + uint errors; + + char* slash = strchr(s, '/'); + + if (slash) { + char* t; + /* Temporarily replace the '/' with NUL. */ + *slash = 0; + /* Convert the database name. */ + strconvert(&my_charset_filename, s, system_charset_info, + s, slash - s + 1, &errors); + + t = s + strlen(s); + ut_ad(slash >= t); + /* Append a '.' after the database name. */ + *t++ = '.'; + slash++; + /* Convert the table name. */ + strconvert(&my_charset_filename, slash, system_charset_info, + t, slash - t + strlen(slash), &errors); + } else { + strconvert(&my_charset_filename, s, + system_charset_info, s, strlen(s), &errors); + } +} + +/*********************************************************************** +This function checks that index keys are sensible. */ +static +int +innobase_check_index_keys( +/*======================*/ + /* out: 0 or error number */ + const KEY* key_info, /* in: Indexes to be created */ + ulint num_of_keys) /* in: Number of indexes to + be created */ +{ + ulint key_num; + + ut_ad(key_info); + ut_ad(num_of_keys); + + for (key_num = 0; key_num < num_of_keys; key_num++) { + const KEY& key = key_info[key_num]; + + /* Check that the same index name does not appear + twice in indexes to be created. */ + + for (ulint i = 0; i < key_num; i++) { + const KEY& key2 = key_info[i]; + + if (0 == strcmp(key.name, key2.name)) { + sql_print_error("InnoDB: key name `%s` appears" + " twice in CREATE INDEX\n", + key.name); + + return(ER_WRONG_NAME_FOR_INDEX); + } + } + + /* Check that MySQL does not try to create a column + prefix index field on an inappropriate data type and + that the same colum does not appear twice in the index. */ + + for (ulint i = 0; i < key.key_parts; i++) { + const KEY_PART_INFO& key_part1 + = key.key_part[i]; + const Field* field + = key_part1.field; + ibool is_unsigned; + + switch (get_innobase_type_from_mysql_type( + &is_unsigned, field)) { + default: + break; + case DATA_INT: + case DATA_FLOAT: + case DATA_DOUBLE: + case DATA_DECIMAL: + if (field->type() == MYSQL_TYPE_VARCHAR) { + if (key_part1.length + >= field->pack_length() + - ((Field_varstring*) field) + ->length_bytes) { + break; + } + } else { + if (key_part1.length + >= field->pack_length()) { + break; + } + } + + sql_print_error("InnoDB: MySQL is trying to" + " create a column prefix" + " index field on an" + " inappropriate data type." + " column `%s`," + " index `%s`.\n", + field->field_name, + key.name); + return(ER_WRONG_KEY_COLUMN); + } + + for (ulint j = 0; j < i; j++) { + const KEY_PART_INFO& key_part2 + = key.key_part[j]; + + if (strcmp(key_part1.field->field_name, + key_part2.field->field_name)) { + continue; + } + + sql_print_error("InnoDB: column `%s`" + " is not allowed to occur" + " twice in index `%s`.\n", + key_part1.field->field_name, + key.name); + return(ER_WRONG_KEY_COLUMN); + } + } + } + + return(0); +} + +/*********************************************************************** +Create index field definition for key part */ +static +void +innobase_create_index_field_def( +/*============================*/ + KEY_PART_INFO* key_part, /* in: MySQL key definition */ + mem_heap_t* heap, /* in: memory heap */ + merge_index_field_t* index_field) /* out: index field + definition for key_part */ +{ + Field* field; + ibool is_unsigned; + ulint col_type; + + DBUG_ENTER("innobase_create_index_field_def"); + + ut_ad(key_part); + ut_ad(index_field); + + field = key_part->field; + ut_a(field); + + col_type = get_innobase_type_from_mysql_type(&is_unsigned, field); + + if (DATA_BLOB == col_type + || (key_part->length < field->pack_length() + && field->type() != MYSQL_TYPE_VARCHAR) + || (field->type() == MYSQL_TYPE_VARCHAR + && key_part->length < field->pack_length() + - ((Field_varstring*)field)->length_bytes)) { + + index_field->prefix_len = key_part->length; + } else { + index_field->prefix_len = 0; + } + + index_field->field_name = mem_heap_strdup(heap, field->field_name); + + DBUG_VOID_RETURN; +} + +/*********************************************************************** +Create index definition for key */ +static +void +innobase_create_index_def( +/*======================*/ + KEY* key, /* in: key definition */ + bool new_primary, /* in: TRUE=generating + a new primary key + on the table */ + bool key_primary, /* in: TRUE if this key + is a primary key */ + merge_index_def_t* index, /* out: index definition */ + mem_heap_t* heap) /* in: heap where memory + is allocated */ +{ + ulint i; + ulint len; + ulint n_fields = key->key_parts; + char* index_name; + + DBUG_ENTER("innobase_create_index_def"); + + index->fields = (merge_index_field_t*) mem_heap_alloc( + heap, n_fields * sizeof *index->fields); + + index->ind_type = 0; + index->n_fields = n_fields; + len = strlen(key->name) + 1; + index->name = index_name = (char*) mem_heap_alloc(heap, + len + !new_primary); + + if (UNIV_LIKELY(!new_primary)) { + *index_name++ = TEMP_INDEX_PREFIX; + } + + memcpy(index_name, key->name, len); + + if (key->flags & HA_NOSAME) { + index->ind_type |= DICT_UNIQUE; + } + + if (key_primary) { + index->ind_type |= DICT_CLUSTERED; + } + + for (i = 0; i < n_fields; i++) { + innobase_create_index_field_def(&key->key_part[i], heap, + &index->fields[i]); + } + + DBUG_VOID_RETURN; +} + +/*********************************************************************** +Copy index field definition */ +static +void +innobase_copy_index_field_def( +/*==========================*/ + const dict_field_t* field, /* in: definition to copy */ + merge_index_field_t* index_field) /* out: copied definition */ +{ + DBUG_ENTER("innobase_copy_index_field_def"); + DBUG_ASSERT(field != NULL); + DBUG_ASSERT(index_field != NULL); + + index_field->field_name = field->name; + index_field->prefix_len = field->prefix_len; + + DBUG_VOID_RETURN; +} + +/*********************************************************************** +Copy index definition for the index */ +static +void +innobase_copy_index_def( +/*====================*/ + const dict_index_t* index, /* in: index definition to copy */ + merge_index_def_t* new_index,/* out: Index definition */ + mem_heap_t* heap) /* in: heap where allocated */ +{ + ulint n_fields; + ulint i; + + DBUG_ENTER("innobase_copy_index_def"); + + /* Note that we take only those fields that user defined to be + in the index. In the internal representation more colums were + added and those colums are not copied .*/ + + n_fields = index->n_user_defined_cols; + + new_index->fields = (merge_index_field_t*) mem_heap_alloc( + heap, n_fields * sizeof *new_index->fields); + + /* When adding a PRIMARY KEY, we may convert a previous + clustered index to a secondary index (UNIQUE NOT NULL). */ + new_index->ind_type = index->type & ~DICT_CLUSTERED; + new_index->n_fields = n_fields; + new_index->name = index->name; + + for (i = 0; i < n_fields; i++) { + innobase_copy_index_field_def(&index->fields[i], + &new_index->fields[i]); + } + + DBUG_VOID_RETURN; +} + +/*********************************************************************** +Create an index table where indexes are ordered as follows: + +IF a new primary key is defined for the table THEN + + 1) New primary key + 2) Original secondary indexes + 3) New secondary indexes + +ELSE + + 1) All new indexes in the order they arrive from MySQL + +ENDIF + +*/ +static +merge_index_def_t* +innobase_create_key_def( +/*====================*/ + /* out: key definitions or NULL */ + trx_t* trx, /* in: trx */ + const dict_table_t*table, /* in: table definition */ + mem_heap_t* heap, /* in: heap where space for key + definitions are allocated */ + KEY* key_info, /* in: Indexes to be created */ + ulint& n_keys) /* in/out: Number of indexes to + be created */ +{ + ulint i = 0; + merge_index_def_t* indexdef; + merge_index_def_t* indexdefs; + bool new_primary; + + DBUG_ENTER("innobase_create_key_def"); + + indexdef = indexdefs = (merge_index_def_t*) + mem_heap_alloc(heap, sizeof *indexdef + * (n_keys + UT_LIST_GET_LEN(table->indexes))); + + /* If there is a primary key, it is always the first index + defined for the table. */ + + new_primary = !my_strcasecmp(system_charset_info, + key_info->name, "PRIMARY"); + + /* If there is a UNIQUE INDEX consisting entirely of NOT NULL + columns, MySQL will treat it as a PRIMARY KEY unless the + table already has one. */ + + if (!new_primary && (key_info->flags & HA_NOSAME) + && row_table_got_default_clust_index(table)) { + uint key_part = key_info->key_parts; + + new_primary = TRUE; + + while (key_part--) { + if (key_info->key_part[key_part].key_type + & FIELDFLAG_MAYBE_NULL) { + new_primary = FALSE; + break; + } + } + } + + if (new_primary) { + const dict_index_t* index; + + /* Create the PRIMARY key index definition */ + innobase_create_index_def(&key_info[i++], TRUE, TRUE, + indexdef++, heap); + + row_mysql_lock_data_dictionary(trx); + + index = dict_table_get_first_index(table); + + /* Copy the index definitions of the old table. Skip + the old clustered index if it is a generated clustered + index or a PRIMARY KEY. If the clustered index is a + UNIQUE INDEX, it must be converted to a secondary index. */ + + if (dict_index_get_nth_col(index, 0)->mtype == DATA_SYS + || !my_strcasecmp(system_charset_info, + index->name, "PRIMARY")) { + index = dict_table_get_next_index(index); + } + + while (index) { + innobase_copy_index_def(index, indexdef++, heap); + index = dict_table_get_next_index(index); + } + + row_mysql_unlock_data_dictionary(trx); + } + + /* Create definitions for added secondary indexes. */ + + while (i < n_keys) { + innobase_create_index_def(&key_info[i++], new_primary, FALSE, + indexdef++, heap); + } + + n_keys = indexdef - indexdefs; + + DBUG_RETURN(indexdefs); +} + +/*********************************************************************** +Create a temporary tablename using query id, thread id, and id */ +static +char* +innobase_create_temporary_tablename( +/*================================*/ + /* out: temporary tablename */ + mem_heap_t* heap, /* in: memory heap */ + char id, /* in: identifier [0-9a-zA-Z] */ + const char* table_name) /* in: table name */ +{ + char* name; + ulint len; + static const char suffix[] = "@0023 "; /* "# " */ + + len = strlen(table_name); + + name = (char*) mem_heap_alloc(heap, len + sizeof suffix); + memcpy(name, table_name, len); + memcpy(name + len, suffix, sizeof suffix); + name[len + (sizeof suffix - 2)] = id; + + return(name); +} + +/*********************************************************************** +Create indexes. */ +UNIV_INTERN +int +ha_innobase::add_index( +/*===================*/ + /* out: 0 or error number */ + TABLE* table, /* in: Table where indexes are created */ + KEY* key_info, /* in: Indexes to be created */ + uint num_of_keys) /* in: Number of indexes to be created */ +{ + dict_index_t** index; /* Index to be created */ + dict_table_t* innodb_table; /* InnoDB table in dictionary */ + dict_table_t* indexed_table; /* Table where indexes are created */ + merge_index_def_t* index_defs; /* Index definitions */ + mem_heap_t* heap; /* Heap for index definitions */ + trx_t* trx; /* Transaction */ + ulint num_of_idx; + ulint num_created = 0; + ibool dict_locked = FALSE; + ulint new_primary; + ulint error; + + DBUG_ENTER("ha_innobase::add_index"); + ut_a(table); + ut_a(key_info); + ut_a(num_of_keys); + + if (srv_created_new_raw || srv_force_recovery) { + DBUG_RETURN(HA_ERR_WRONG_COMMAND); + } + + update_thd(); + + heap = mem_heap_create(1024); + + /* In case MySQL calls this in the middle of a SELECT query, release + possible adaptive hash latch to avoid deadlocks of threads. */ + trx_search_latch_release_if_reserved(prebuilt->trx); + + /* Create a background transaction for the operations on + the data dictionary tables. */ + trx = innobase_trx_allocate(user_thd); + trx_start_if_not_started(trx); + + innodb_table = indexed_table + = dict_table_get(prebuilt->table->name, FALSE); + + /* Check that index keys are sensible */ + + error = innobase_check_index_keys(key_info, num_of_keys); + + if (UNIV_UNLIKELY(error)) { +err_exit: + mem_heap_free(heap); + trx_general_rollback_for_mysql(trx, FALSE, NULL); + trx_free_for_mysql(trx); + trx_commit_for_mysql(prebuilt->trx); + DBUG_RETURN(error); + } + + /* Create table containing all indexes to be built in this + alter table add index so that they are in the correct order + in the table. */ + + num_of_idx = num_of_keys; + + index_defs = innobase_create_key_def( + trx, innodb_table, heap, key_info, num_of_idx); + + new_primary = DICT_CLUSTERED & index_defs[0].ind_type; + + /* Allocate memory for dictionary index definitions */ + + index = (dict_index_t**) mem_heap_alloc( + heap, num_of_idx * sizeof *index); + + /* Flag this transaction as a dictionary operation, so that + the data dictionary will be locked in crash recovery. */ + trx_set_dict_operation(trx, TRX_DICT_OP_INDEX); + + /* Acquire a lock on the table before creating any indexes. */ + error = row_merge_lock_table(prebuilt->trx, innodb_table, + new_primary ? LOCK_X : LOCK_S); + + if (UNIV_UNLIKELY(error != DB_SUCCESS)) { + + goto error_handling; + } + + /* Latch the InnoDB data dictionary exclusively so that no deadlocks + or lock waits can happen in it during an index create operation. */ + + row_mysql_lock_data_dictionary(trx); + dict_locked = TRUE; + + /* If a new primary key is defined for the table we need + to drop the original table and rebuild all indexes. */ + + if (UNIV_UNLIKELY(new_primary)) { + /* This transaction should be the only one + operating on the table. */ + ut_a(innodb_table->n_mysql_handles_opened == 1); + + char* new_table_name = innobase_create_temporary_tablename( + heap, '1', innodb_table->name); + + /* Clone the table. */ + trx_set_dict_operation(trx, TRX_DICT_OP_TABLE); + indexed_table = row_merge_create_temporary_table( + new_table_name, index_defs, innodb_table, trx); + + if (!indexed_table) { + + switch (trx->error_state) { + case DB_TABLESPACE_ALREADY_EXISTS: + case DB_DUPLICATE_KEY: + innobase_convert_tablename(new_table_name); + my_error(HA_ERR_TABLE_EXIST, MYF(0), + new_table_name); + error = HA_ERR_TABLE_EXIST; + break; + default: + error = convert_error_code_to_mysql( + trx->error_state, innodb_table->flags, + user_thd); + } + + row_mysql_unlock_data_dictionary(trx); + goto err_exit; + } + + trx->table_id = indexed_table->id; + } + + /* Create the indexes in SYS_INDEXES and load into dictionary. */ + + for (ulint i = 0; i < num_of_idx; i++) { + + index[i] = row_merge_create_index(trx, indexed_table, + &index_defs[i]); + + if (!index[i]) { + error = trx->error_state; + goto error_handling; + } + + num_created++; + } + + ut_ad(error == DB_SUCCESS); + + /* Commit the data dictionary transaction in order to release + the table locks on the system tables. Unfortunately, this + means that if MySQL crashes while creating a new primary key + inside row_merge_build_indexes(), indexed_table will not be + dropped on crash recovery. Thus, it will become orphaned. */ + trx_commit_for_mysql(trx); + + row_mysql_unlock_data_dictionary(trx); + dict_locked = FALSE; + + ut_a(trx->n_active_thrs == 0); + ut_a(UT_LIST_GET_LEN(trx->signals) == 0); + + if (UNIV_UNLIKELY(new_primary)) { + /* A primary key is to be built. Acquire an exclusive + table lock also on the table that is being created. */ + ut_ad(indexed_table != innodb_table); + + error = row_merge_lock_table(prebuilt->trx, indexed_table, + LOCK_X); + + if (UNIV_UNLIKELY(error != DB_SUCCESS)) { + + goto error_handling; + } + } + + /* Read the clustered index of the table and build indexes + based on this information using temporary files and merge sort. */ + error = row_merge_build_indexes(prebuilt->trx, + innodb_table, indexed_table, + index, num_of_idx, table); + +error_handling: +#ifdef UNIV_DEBUG + /* TODO: At the moment we can't handle the following statement + in our debugging code below: + + alter table t drop index b, add index (b); + + The fix will have to parse the SQL and note that the index + being added has the same name as the the one being dropped and + ignore that in the dup index check.*/ + //dict_table_check_for_dup_indexes(prebuilt->table); +#endif + + /* After an error, remove all those index definitions from the + dictionary which were defined. */ + + switch (error) { + const char* old_name; + char* tmp_name; + case DB_SUCCESS: + ut_a(!dict_locked); + row_mysql_lock_data_dictionary(trx); + dict_locked = TRUE; + + if (!new_primary) { + error = row_merge_rename_indexes(trx, indexed_table); + + if (error != DB_SUCCESS) { + row_merge_drop_indexes(trx, indexed_table, + index, num_created); + } + + goto convert_error; + } + + /* If a new primary key was defined for the table and + there was no error at this point, we can now rename + the old table as a temporary table, rename the new + temporary table as the old table and drop the old table. */ + old_name = innodb_table->name; + tmp_name = innobase_create_temporary_tablename(heap, '2', + old_name); + + error = row_merge_rename_tables(innodb_table, indexed_table, + tmp_name, trx); + + if (error != DB_SUCCESS) { + + row_merge_drop_table(trx, indexed_table); + + switch (error) { + case DB_TABLESPACE_ALREADY_EXISTS: + case DB_DUPLICATE_KEY: + innobase_convert_tablename(tmp_name); + my_error(HA_ERR_TABLE_EXIST, MYF(0), tmp_name); + error = HA_ERR_TABLE_EXIST; + break; + default: + goto convert_error; + } + break; + } + + trx_commit_for_mysql(prebuilt->trx); + row_prebuilt_free(prebuilt, TRUE); + prebuilt = row_create_prebuilt(indexed_table); + + indexed_table->n_mysql_handles_opened++; + + error = row_merge_drop_table(trx, innodb_table); + goto convert_error; + + case DB_TOO_BIG_RECORD: + my_error(HA_ERR_TO_BIG_ROW, MYF(0)); + goto error; + case DB_PRIMARY_KEY_IS_NULL: + my_error(ER_PRIMARY_CANT_HAVE_NULL, MYF(0)); + /* fall through */ + case DB_DUPLICATE_KEY: +error: + prebuilt->trx->error_info = NULL; + /* fall through */ + default: + if (new_primary) { + row_merge_drop_table(trx, indexed_table); + } else { + if (!dict_locked) { + row_mysql_lock_data_dictionary(trx); + dict_locked = TRUE; + } + + row_merge_drop_indexes(trx, indexed_table, + index, num_created); + } + +convert_error: + error = convert_error_code_to_mysql(error, + innodb_table->flags, + user_thd); + } + + mem_heap_free(heap); + trx_commit_for_mysql(trx); + if (prebuilt->trx) { + trx_commit_for_mysql(prebuilt->trx); + } + + if (dict_locked) { + row_mysql_unlock_data_dictionary(trx); + } + + trx_free_for_mysql(trx); + + /* There might be work for utility threads.*/ + srv_active_wake_master_thread(); + + DBUG_RETURN(error); +} + +/*********************************************************************** +Prepare to drop some indexes of a table. */ +UNIV_INTERN +int +ha_innobase::prepare_drop_index( +/*============================*/ + /* out: 0 or error number */ + TABLE* table, /* in: Table where indexes are dropped */ + uint* key_num, /* in: Key nums to be dropped */ + uint num_of_keys) /* in: Number of keys to be dropped */ +{ + trx_t* trx; + int err = 0; + uint n_key; + + DBUG_ENTER("ha_innobase::prepare_drop_index"); + ut_ad(table); + ut_ad(key_num); + ut_ad(num_of_keys); + if (srv_created_new_raw || srv_force_recovery) { + DBUG_RETURN(HA_ERR_WRONG_COMMAND); + } + + update_thd(); + + trx_search_latch_release_if_reserved(prebuilt->trx); + trx = prebuilt->trx; + + /* Test and mark all the indexes to be dropped */ + + row_mysql_lock_data_dictionary(trx); + + /* Check that none of the indexes have previously been flagged + for deletion. */ + { + const dict_index_t* index + = dict_table_get_first_index(prebuilt->table); + do { + ut_a(!index->to_be_dropped); + index = dict_table_get_next_index(index); + } while (index); + } + + for (n_key = 0; n_key < num_of_keys; n_key++) { + const KEY* key; + dict_index_t* index; + + key = table->key_info + key_num[n_key]; + index = dict_table_get_index_on_name_and_min_id( + prebuilt->table, key->name); + + if (!index) { + sql_print_error("InnoDB could not find key n:o %u " + "with name %s for table %s", + key_num[n_key], + key ? key->name : "NULL", + prebuilt->table->name); + + err = HA_ERR_KEY_NOT_FOUND; + goto func_exit; + } + + /* Refuse to drop the clustered index. It would be + better to automatically generate a clustered index, + but mysql_alter_table() will call this method only + after ha_innobase::add_index(). */ + + if (dict_index_is_clust(index)) { + my_error(ER_REQUIRES_PRIMARY_KEY, MYF(0)); + err = -1; + goto func_exit; + } + + index->to_be_dropped = TRUE; + } + + /* If FOREIGN_KEY_CHECK = 1 you may not drop an index defined + for a foreign key constraint because InnoDB requires that both + tables contain indexes for the constraint. Note that CREATE + INDEX id ON table does a CREATE INDEX and DROP INDEX, and we + can ignore here foreign keys because a new index for the + foreign key has already been created. + + We check for the foreign key constraints after marking the + candidate indexes for deletion, because when we check for an + equivalent foreign index we don't want to select an index that + is later deleted. */ + + if (trx->check_foreigns + && thd_sql_command(user_thd) != SQLCOM_CREATE_INDEX) { + dict_index_t* index; + + for (index = dict_table_get_first_index(prebuilt->table); + index; + index = dict_table_get_next_index(index)) { + dict_foreign_t* foreign; + + if (!index->to_be_dropped) { + + continue; + } + + /* Check if the index is referenced. */ + foreign = dict_table_get_referenced_constraint( + prebuilt->table, index); + + if (foreign) { +index_needed: + trx_set_detailed_error( + trx, + "Index needed in foreign key " + "constraint"); + + trx->error_info = index; + + err = HA_ERR_DROP_INDEX_FK; + break; + } else { + /* Check if this index references some + other table */ + foreign = dict_table_get_foreign_constraint( + prebuilt->table, index); + + if (foreign) { + ut_a(foreign->foreign_index == index); + + /* Search for an equivalent index that + the foreign key constraint could use + if this index were to be deleted. */ + if (!dict_foreign_find_equiv_index( + foreign)) { + + goto index_needed; + } + } + } + } + } else if (thd_sql_command(user_thd) == SQLCOM_CREATE_INDEX) { + /* This is a drop of a foreign key constraint index that + was created by MySQL when the constraint was added. MySQL + does this when the user creates an index explicitly which + can be used in place of the automatically generated index. */ + + dict_index_t* index; + + for (index = dict_table_get_first_index(prebuilt->table); + index; + index = dict_table_get_next_index(index)) { + dict_foreign_t* foreign; + + if (!index->to_be_dropped) { + + continue; + } + + /* Check if this index references some other table */ + foreign = dict_table_get_foreign_constraint( + prebuilt->table, index); + + if (foreign == NULL) { + + continue; + } + + ut_a(foreign->foreign_index == index); + + /* Search for an equivalent index that the + foreign key constraint could use if this index + were to be deleted. */ + + if (!dict_foreign_find_equiv_index(foreign)) { + trx_set_detailed_error( + trx, + "Index needed in foreign key " + "constraint"); + + trx->error_info = foreign->foreign_index; + + err = HA_ERR_DROP_INDEX_FK; + break; + } + } + } + +func_exit: + if (err) { + /* Undo our changes since there was some sort of error. */ + dict_index_t* index + = dict_table_get_first_index(prebuilt->table); + + do { + index->to_be_dropped = FALSE; + index = dict_table_get_next_index(index); + } while (index); + } + + row_mysql_unlock_data_dictionary(trx); + + DBUG_RETURN(err); +} + +/*********************************************************************** +Drop the indexes that were passed to a successful prepare_drop_index(). */ +UNIV_INTERN +int +ha_innobase::final_drop_index( +/*==========================*/ + /* out: 0 or error number */ + TABLE* table) /* in: Table where indexes are dropped */ +{ + dict_index_t* index; /* Index to be dropped */ + trx_t* trx; /* Transaction */ + int err; + + DBUG_ENTER("ha_innobase::final_drop_index"); + ut_ad(table); + + if (srv_created_new_raw || srv_force_recovery) { + DBUG_RETURN(HA_ERR_WRONG_COMMAND); + } + + update_thd(); + + trx_search_latch_release_if_reserved(prebuilt->trx); + + /* Create a background transaction for the operations on + the data dictionary tables. */ + trx = innobase_trx_allocate(user_thd); + trx_start_if_not_started(trx); + + /* Flag this transaction as a dictionary operation, so that + the data dictionary will be locked in crash recovery. */ + trx_set_dict_operation(trx, TRX_DICT_OP_INDEX); + + /* Lock the table exclusively, to ensure that no active + transaction depends on an index that is being dropped. */ + err = convert_error_code_to_mysql( + row_merge_lock_table(prebuilt->trx, prebuilt->table, LOCK_X), + prebuilt->table->flags, user_thd); + + row_mysql_lock_data_dictionary(trx); + + if (UNIV_UNLIKELY(err)) { + + /* Unmark the indexes to be dropped. */ + for (index = dict_table_get_first_index(prebuilt->table); + index; index = dict_table_get_next_index(index)) { + + index->to_be_dropped = FALSE; + } + + goto func_exit; + } + + /* Drop indexes marked to be dropped */ + + index = dict_table_get_first_index(prebuilt->table); + + while (index) { + dict_index_t* next_index; + + next_index = dict_table_get_next_index(index); + + if (index->to_be_dropped) { + + row_merge_drop_index(index, prebuilt->table, trx); + } + + index = next_index; + } + + /* Check that all flagged indexes were dropped. */ + for (index = dict_table_get_first_index(prebuilt->table); + index; index = dict_table_get_next_index(index)) { + ut_a(!index->to_be_dropped); + } + +#ifdef UNIV_DEBUG + dict_table_check_for_dup_indexes(prebuilt->table); +#endif + +func_exit: + trx_commit_for_mysql(trx); + trx_commit_for_mysql(prebuilt->trx); + row_mysql_unlock_data_dictionary(trx); + + /* Flush the log to reduce probability that the .frm files and + the InnoDB data dictionary get out-of-sync if the user runs + with innodb_flush_log_at_trx_commit = 0 */ + + log_buffer_flush_to_disk(); + + trx_free_for_mysql(trx); + + /* Tell the InnoDB server that there might be work for + utility threads: */ + + srv_active_wake_master_thread(); + + DBUG_RETURN(err); +} diff --git a/storage/xtradb/handler/handler0vars.h b/storage/xtradb/handler/handler0vars.h new file mode 100644 index 00000000000..ea9f305ce66 --- /dev/null +++ b/storage/xtradb/handler/handler0vars.h @@ -0,0 +1,68 @@ +/***************************************************************************** + +Copyright (c) 2008, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/*********************************************************************** +This file contains accessor functions for dynamic plugin on Windows. +***********************************************************************/ + +#if defined __WIN__ && defined MYSQL_DYNAMIC_PLUGIN +/*********************************************************************** +This is a list of externals that can not be resolved by delay loading. +They have to be resolved indirectly via their addresses in the .map file. +All of them are external variables. */ +extern CHARSET_INFO* wdl_my_charset_bin; +extern CHARSET_INFO* wdl_my_charset_latin1; +extern CHARSET_INFO* wdl_my_charset_filename; +extern CHARSET_INFO** wdl_system_charset_info; +extern CHARSET_INFO** wdl_default_charset_info; +extern CHARSET_INFO** wdl_all_charsets; +extern system_variables* wdl_global_system_variables; +extern char* wdl_mysql_real_data_home; +extern char** wdl_mysql_data_home; +extern char** wdl_tx_isolation_names; +extern char** wdl_binlog_format_names; +extern char* wdl_reg_ext; +extern pthread_mutex_t* wdl_LOCK_thread_count; +extern key_map* wdl_key_map_full; +extern MY_TMPDIR* wdl_mysql_tmpdir_list; +extern bool* wdl_mysqld_embedded; +extern uint* wdl_lower_case_table_names; +extern ulong* wdl_specialflag; +extern int* wdl_my_umask; + +#define my_charset_bin (*wdl_my_charset_bin) +#define my_charset_latin1 (*wdl_my_charset_latin1) +#define my_charset_filename (*wdl_my_charset_filename) +#define system_charset_info (*wdl_system_charset_info) +#define default_charset_info (*wdl_default_charset_info) +#define all_charsets (wdl_all_charsets) +#define global_system_variables (*wdl_global_system_variables) +#define mysql_real_data_home (wdl_mysql_real_data_home) +#define mysql_data_home (*wdl_mysql_data_home) +#define tx_isolation_names (wdl_tx_isolation_names) +#define binlog_format_names (wdl_binlog_format_names) +#define reg_ext (wdl_reg_ext) +#define LOCK_thread_count (*wdl_LOCK_thread_count) +#define key_map_full (*wdl_key_map_full) +#define mysql_tmpdir_list (*wdl_mysql_tmpdir_list) +#define mysqld_embedded (*wdl_mysqld_embedded) +#define lower_case_table_names (*wdl_lower_case_table_names) +#define specialflag (*wdl_specialflag) +#define my_umask (*wdl_my_umask) + +#endif diff --git a/storage/xtradb/handler/i_s.cc b/storage/xtradb/handler/i_s.cc new file mode 100644 index 00000000000..eaa1eb35454 --- /dev/null +++ b/storage/xtradb/handler/i_s.cc @@ -0,0 +1,2954 @@ +/***************************************************************************** + +Copyright (c) 2007, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/****************************************************** +InnoDB INFORMATION SCHEMA tables interface to MySQL. + +Created July 18, 2007 Vasil Dimov +*******************************************************/ + +#include <mysql_priv.h> +#include <mysqld_error.h> + +#include <m_ctype.h> +#include <hash.h> +#include <myisampack.h> +#include <mysys_err.h> +#include <my_sys.h> +#include "i_s.h" +#include "innodb_patch_info.h" +#include <mysql/plugin.h> + +extern "C" { +#include "trx0i_s.h" +#include "trx0trx.h" /* for TRX_QUE_STATE_STR_MAX_LEN */ +#include "buf0buddy.h" /* for i_s_cmpmem */ +#include "buf0buf.h" /* for buf_pool and PAGE_ZIP_MIN_SIZE */ +#include "ha_prototypes.h" /* for innobase_convert_name() */ +#include "srv0start.h" /* for srv_was_started */ +#include "btr0btr.h" /* for btr_page_get_index_id */ +#include "dict0dict.h" /* for dict_index_get_if_in_cache */ +#include "trx0rseg.h" /* for trx_rseg_struct */ +#include "trx0sys.h" /* for trx_sys */ +#include "dict0dict.h" /* for dict_sys */ +/* from buf0buf.c */ +struct buf_chunk_struct{ + ulint mem_size; /* allocated size of the chunk */ + ulint size; /* size of frames[] and blocks[] */ + void* mem; /* pointer to the memory area which + was allocated for the frames */ + buf_block_t* blocks; /* array of buffer control blocks */ +}; +} +#include "handler0vars.h" + +static const char plugin_author[] = "Innobase Oy"; + +#define OK(expr) \ + if ((expr) != 0) { \ + DBUG_RETURN(1); \ + } + +#define RETURN_IF_INNODB_NOT_STARTED(plugin_name) \ +do { \ + if (!srv_was_started) { \ + push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_WARN, \ + ER_CANT_FIND_SYSTEM_REC, \ + "InnoDB: SELECTing from " \ + "INFORMATION_SCHEMA.%s but " \ + "the InnoDB storage engine " \ + "is not installed", plugin_name); \ + DBUG_RETURN(0); \ + } \ +} while (0) + +#if !defined __STRICT_ANSI__ && defined __GNUC__ && (__GNUC__) > 2 && !defined __INTEL_COMPILER +#define STRUCT_FLD(name, value) name: value +#else +#define STRUCT_FLD(name, value) value +#endif + +static const ST_FIELD_INFO END_OF_ST_FIELD_INFO = + {STRUCT_FLD(field_name, NULL), + STRUCT_FLD(field_length, 0), + STRUCT_FLD(field_type, MYSQL_TYPE_NULL), + STRUCT_FLD(value, 0), + STRUCT_FLD(field_flags, 0), + STRUCT_FLD(old_name, ""), + STRUCT_FLD(open_method, SKIP_OPEN_TABLE)}; + +/* +Use the following types mapping: + +C type ST_FIELD_INFO::field_type +--------------------------------- +long MYSQL_TYPE_LONGLONG +(field_length=MY_INT64_NUM_DECIMAL_DIGITS) + +long unsigned MYSQL_TYPE_LONGLONG +(field_length=MY_INT64_NUM_DECIMAL_DIGITS, field_flags=MY_I_S_UNSIGNED) + +char* MYSQL_TYPE_STRING +(field_length=n) + +float MYSQL_TYPE_FLOAT +(field_length=0 is ignored) + +void* MYSQL_TYPE_LONGLONG +(field_length=MY_INT64_NUM_DECIMAL_DIGITS, field_flags=MY_I_S_UNSIGNED) + +boolean (if else) MYSQL_TYPE_LONG +(field_length=1) + +time_t MYSQL_TYPE_DATETIME +(field_length=0 ignored) +--------------------------------- +*/ + +/* XXX these are defined in mysql_priv.h inside #ifdef MYSQL_SERVER */ +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); + +/*********************************************************************** +Common function to fill any of the dynamic tables: +INFORMATION_SCHEMA.innodb_trx +INFORMATION_SCHEMA.innodb_locks +INFORMATION_SCHEMA.innodb_lock_waits */ +static +int +trx_i_s_common_fill_table( +/*======================*/ + /* out: 0 on success */ + THD* thd, /* in: thread */ + TABLE_LIST* tables, /* in/out: tables to fill */ + COND* cond); /* in: condition (not used) */ + +/*********************************************************************** +Unbind a dynamic INFORMATION_SCHEMA table. */ +static +int +i_s_common_deinit( +/*==============*/ + /* out: 0 on success */ + void* p); /* in/out: table schema object */ + +/*********************************************************************** +Auxiliary function to store time_t value in MYSQL_TYPE_DATETIME +field. */ +static +int +field_store_time_t( +/*===============*/ + /* out: 0 on success */ + Field* field, /* in/out: target field for storage */ + time_t time) /* in: value to store */ +{ + MYSQL_TIME my_time; + struct tm tm_time; + +#if 0 + /* use this if you are sure that `variables' and `time_zone' + are always initialized */ + thd->variables.time_zone->gmt_sec_to_TIME( + &my_time, (my_time_t) time); +#else + localtime_r(&time, &tm_time); + localtime_to_TIME(&my_time, &tm_time); + my_time.time_type = MYSQL_TIMESTAMP_DATETIME; +#endif + + return(field->store_time(&my_time, MYSQL_TIMESTAMP_DATETIME)); +} + +/*********************************************************************** +Auxiliary function to store char* value in MYSQL_TYPE_STRING field. */ +static +int +field_store_string( +/*===============*/ + /* out: 0 on success */ + Field* field, /* in/out: target field for storage */ + const char* str) /* in: NUL-terminated utf-8 string, + or NULL */ +{ + int ret; + + if (str != NULL) { + + ret = field->store(str, strlen(str), + system_charset_info); + field->set_notnull(); + } else { + + ret = 0; /* success */ + field->set_null(); + } + + return(ret); +} + +/*********************************************************************** +Auxiliary function to store ulint value in MYSQL_TYPE_LONGLONG field. +If the value is ULINT_UNDEFINED then the field it set to NULL. */ +static +int +field_store_ulint( +/*==============*/ + /* out: 0 on success */ + Field* field, /* in/out: target field for storage */ + ulint n) /* in: value to store */ +{ + int ret; + + if (n != ULINT_UNDEFINED) { + + ret = field->store(n); + field->set_notnull(); + } else { + + ret = 0; /* success */ + field->set_null(); + } + + return(ret); +} + +/* Fields of the dynamic table INFORMATION_SCHEMA.innodb_patches */ +static ST_FIELD_INFO innodb_patches_fields_info[] = +{ +#define IDX_PATCH_NAME 0 + {STRUCT_FLD(field_name, "name"), + STRUCT_FLD(field_length, 255), + STRUCT_FLD(field_type, MYSQL_TYPE_STRING), + STRUCT_FLD(value, 0), + STRUCT_FLD(field_flags, 0), + STRUCT_FLD(old_name, ""), + STRUCT_FLD(open_method, SKIP_OPEN_TABLE)}, + +#define IDX_PATCH_DESCR 1 + {STRUCT_FLD(field_name, "description"), + STRUCT_FLD(field_length, 255), + STRUCT_FLD(field_type, MYSQL_TYPE_STRING), + STRUCT_FLD(value, 0), + STRUCT_FLD(field_flags, 0), + STRUCT_FLD(old_name, ""), + STRUCT_FLD(open_method, SKIP_OPEN_TABLE)}, + +#define IDX_PATCH_COMMENT 2 + {STRUCT_FLD(field_name, "comment"), + STRUCT_FLD(field_length, 100), + STRUCT_FLD(field_type, MYSQL_TYPE_STRING), + STRUCT_FLD(value, 0), + STRUCT_FLD(field_flags, 0), + STRUCT_FLD(old_name, ""), + STRUCT_FLD(open_method, SKIP_OPEN_TABLE)}, + +#define IDX_PATCH_LINK 3 + {STRUCT_FLD(field_name, "link"), + STRUCT_FLD(field_length, 255), + STRUCT_FLD(field_type, MYSQL_TYPE_STRING), + STRUCT_FLD(value, 0), + STRUCT_FLD(field_flags, 0), + STRUCT_FLD(old_name, ""), + STRUCT_FLD(open_method, SKIP_OPEN_TABLE)}, + + END_OF_ST_FIELD_INFO +}; + +static struct st_mysql_information_schema i_s_info = +{ + MYSQL_INFORMATION_SCHEMA_INTERFACE_VERSION +}; + +/*********************************************************************** +Fill the dynamic table information_schema.innodb_patches */ +static +int +innodb_patches_fill( +/*=============*/ + /* out: 0 on success, 1 on failure */ + THD* thd, /* in: thread */ + TABLE_LIST* tables, /* in/out: tables to fill */ + COND* cond) /* in: condition (ignored) */ +{ + TABLE* table = (TABLE *) tables->table; + int status = 0; + int i; + Field** fields; + + + DBUG_ENTER("innodb_patches_fill"); + fields = table->field; + + /* deny access to non-superusers */ + if (check_global_access(thd, PROCESS_ACL)) { + + DBUG_RETURN(0); + } + + RETURN_IF_INNODB_NOT_STARTED(tables->schema_table_name); + + for (i = 0; innodb_enhancements[i].file; i++) { + + field_store_string(fields[0],innodb_enhancements[i].file); + field_store_string(fields[1],innodb_enhancements[i].name); + field_store_string(fields[2],innodb_enhancements[i].comment); + field_store_string(fields[3],innodb_enhancements[i].link); + + if (schema_table_store_record(thd, table)) { + status = 1; + break; + } + + } + + + DBUG_RETURN(status); +} + +/*********************************************************************** +Bind the dynamic table information_schema.innodb_patches. */ +static +int +innodb_patches_init( +/*=========*/ + /* out: 0 on success */ + void* p) /* in/out: table schema object */ +{ + DBUG_ENTER("innodb_patches_init"); + ST_SCHEMA_TABLE* schema = (ST_SCHEMA_TABLE*) p; + + schema->fields_info = innodb_patches_fields_info; + schema->fill_table = innodb_patches_fill; + + DBUG_RETURN(0); +} + + +UNIV_INTERN struct st_mysql_plugin i_s_innodb_patches = +{ + /* the plugin type (a MYSQL_XXX_PLUGIN value) */ + /* int */ + STRUCT_FLD(type, MYSQL_INFORMATION_SCHEMA_PLUGIN), + + /* pointer to type-specific plugin descriptor */ + /* void* */ + STRUCT_FLD(info, &i_s_info), + + /* plugin name */ + /* const char* */ + STRUCT_FLD(name, "XTRADB_ENHANCEMENTS"), + + /* plugin author (for SHOW PLUGINS) */ + /* const char* */ + STRUCT_FLD(author, "Percona"), + + /* general descriptive text (for SHOW PLUGINS) */ + /* const char* */ + STRUCT_FLD(descr, "Enhancements applied to InnoDB plugin"), + + /* the plugin license (PLUGIN_LICENSE_XXX) */ + /* int */ + STRUCT_FLD(license, PLUGIN_LICENSE_GPL), + + /* the function to invoke when plugin is loaded */ + /* int (*)(void*); */ + STRUCT_FLD(init, innodb_patches_init), + + /* the function to invoke when plugin is unloaded */ + /* int (*)(void*); */ + STRUCT_FLD(deinit, i_s_common_deinit), + + /* plugin version (for SHOW PLUGINS) */ + /* unsigned int */ + STRUCT_FLD(version, INNODB_VERSION_SHORT), + + /* struct st_mysql_show_var* */ + STRUCT_FLD(status_vars, NULL), + + /* struct st_mysql_sys_var** */ + STRUCT_FLD(system_vars, NULL), + + /* reserved for dependency checking */ + /* void* */ + STRUCT_FLD(__reserved1, NULL) +}; + + +static ST_FIELD_INFO i_s_innodb_buffer_pool_pages_fields_info[] = +{ + {STRUCT_FLD(field_name, "page_type"), + STRUCT_FLD(field_length, 64), + STRUCT_FLD(field_type, MYSQL_TYPE_STRING), + STRUCT_FLD(value, 0), + STRUCT_FLD(field_flags, MY_I_S_MAYBE_NULL), + STRUCT_FLD(old_name, ""), + STRUCT_FLD(open_method, SKIP_OPEN_TABLE)}, + + {STRUCT_FLD(field_name, "space_id"), + STRUCT_FLD(field_length, MY_INT64_NUM_DECIMAL_DIGITS), + STRUCT_FLD(field_type, MYSQL_TYPE_LONGLONG), + STRUCT_FLD(value, 0), + STRUCT_FLD(field_flags, MY_I_S_UNSIGNED), + STRUCT_FLD(old_name, ""), + STRUCT_FLD(open_method, SKIP_OPEN_TABLE)}, + + {STRUCT_FLD(field_name, "page_no"), + STRUCT_FLD(field_length, MY_INT64_NUM_DECIMAL_DIGITS), + STRUCT_FLD(field_type, MYSQL_TYPE_LONGLONG), + STRUCT_FLD(value, 0), + STRUCT_FLD(field_flags, MY_I_S_UNSIGNED), + STRUCT_FLD(old_name, ""), + STRUCT_FLD(open_method, SKIP_OPEN_TABLE)}, + + {STRUCT_FLD(field_name, "lru_position"), + STRUCT_FLD(field_length, MY_INT64_NUM_DECIMAL_DIGITS), + STRUCT_FLD(field_type, MYSQL_TYPE_LONGLONG), + STRUCT_FLD(value, 0), + STRUCT_FLD(field_flags, MY_I_S_UNSIGNED), + STRUCT_FLD(old_name, ""), + STRUCT_FLD(open_method, SKIP_OPEN_TABLE)}, + + {STRUCT_FLD(field_name, "fix_count"), + STRUCT_FLD(field_length, MY_INT64_NUM_DECIMAL_DIGITS), + STRUCT_FLD(field_type, MYSQL_TYPE_LONGLONG), + STRUCT_FLD(value, 0), + STRUCT_FLD(field_flags, MY_I_S_UNSIGNED), + STRUCT_FLD(old_name, ""), + STRUCT_FLD(open_method, SKIP_OPEN_TABLE)}, + + {STRUCT_FLD(field_name, "flush_type"), + STRUCT_FLD(field_length, MY_INT64_NUM_DECIMAL_DIGITS), + STRUCT_FLD(field_type, MYSQL_TYPE_LONGLONG), + STRUCT_FLD(value, 0), + STRUCT_FLD(field_flags, MY_I_S_UNSIGNED), + STRUCT_FLD(old_name, ""), + STRUCT_FLD(open_method, SKIP_OPEN_TABLE)}, + + END_OF_ST_FIELD_INFO +}; + +static ST_FIELD_INFO i_s_innodb_buffer_pool_pages_index_fields_info[] = +{ + {STRUCT_FLD(field_name, "schema_name"), + STRUCT_FLD(field_length, 64), + STRUCT_FLD(field_type, MYSQL_TYPE_STRING), + STRUCT_FLD(value, 0), + STRUCT_FLD(field_flags, MY_I_S_MAYBE_NULL), + STRUCT_FLD(old_name, ""), + STRUCT_FLD(open_method, SKIP_OPEN_TABLE)}, + + {STRUCT_FLD(field_name, "table_name"), + STRUCT_FLD(field_length, 64), + STRUCT_FLD(field_type, MYSQL_TYPE_STRING), + STRUCT_FLD(value, 0), + STRUCT_FLD(field_flags, 0), + STRUCT_FLD(old_name, ""), + STRUCT_FLD(open_method, SKIP_OPEN_TABLE)}, + + {STRUCT_FLD(field_name, "index_name"), + STRUCT_FLD(field_length, 64), + STRUCT_FLD(field_type, MYSQL_TYPE_STRING), + STRUCT_FLD(value, 0), + STRUCT_FLD(field_flags, 0), + STRUCT_FLD(old_name, ""), + STRUCT_FLD(open_method, SKIP_OPEN_TABLE)}, + + {STRUCT_FLD(field_name, "space_id"), + STRUCT_FLD(field_length, MY_INT64_NUM_DECIMAL_DIGITS), + STRUCT_FLD(field_type, MYSQL_TYPE_LONGLONG), + STRUCT_FLD(value, 0), + STRUCT_FLD(field_flags, MY_I_S_UNSIGNED), + STRUCT_FLD(old_name, ""), + STRUCT_FLD(open_method, SKIP_OPEN_TABLE)}, + + {STRUCT_FLD(field_name, "page_no"), + STRUCT_FLD(field_length, MY_INT64_NUM_DECIMAL_DIGITS), + STRUCT_FLD(field_type, MYSQL_TYPE_LONGLONG), + STRUCT_FLD(value, 0), + STRUCT_FLD(field_flags, MY_I_S_UNSIGNED), + STRUCT_FLD(old_name, ""), + STRUCT_FLD(open_method, SKIP_OPEN_TABLE)}, + + {STRUCT_FLD(field_name, "n_recs"), + STRUCT_FLD(field_length, MY_INT64_NUM_DECIMAL_DIGITS), + STRUCT_FLD(field_type, MYSQL_TYPE_LONGLONG), + STRUCT_FLD(value, 0), + STRUCT_FLD(field_flags, MY_I_S_UNSIGNED), + STRUCT_FLD(old_name, ""), + STRUCT_FLD(open_method, SKIP_OPEN_TABLE)}, + + {STRUCT_FLD(field_name, "data_size"), + STRUCT_FLD(field_length, MY_INT64_NUM_DECIMAL_DIGITS), + STRUCT_FLD(field_type, MYSQL_TYPE_LONGLONG), + STRUCT_FLD(value, 0), + STRUCT_FLD(field_flags, MY_I_S_UNSIGNED), + STRUCT_FLD(old_name, ""), + STRUCT_FLD(open_method, SKIP_OPEN_TABLE)}, + + {STRUCT_FLD(field_name, "hashed"), + STRUCT_FLD(field_length, MY_INT64_NUM_DECIMAL_DIGITS), + STRUCT_FLD(field_type, MYSQL_TYPE_LONGLONG), + STRUCT_FLD(value, 0), + STRUCT_FLD(field_flags, MY_I_S_UNSIGNED), + STRUCT_FLD(old_name, ""), + STRUCT_FLD(open_method, SKIP_OPEN_TABLE)}, + + {STRUCT_FLD(field_name, "accessed"), + STRUCT_FLD(field_length, MY_INT64_NUM_DECIMAL_DIGITS), + STRUCT_FLD(field_type, MYSQL_TYPE_LONGLONG), + STRUCT_FLD(value, 0), + STRUCT_FLD(field_flags, MY_I_S_UNSIGNED), + STRUCT_FLD(old_name, ""), + STRUCT_FLD(open_method, SKIP_OPEN_TABLE)}, + + {STRUCT_FLD(field_name, "modified"), + STRUCT_FLD(field_length, MY_INT64_NUM_DECIMAL_DIGITS), + STRUCT_FLD(field_type, MYSQL_TYPE_LONGLONG), + STRUCT_FLD(value, 0), + STRUCT_FLD(field_flags, MY_I_S_UNSIGNED), + STRUCT_FLD(old_name, ""), + STRUCT_FLD(open_method, SKIP_OPEN_TABLE)}, + + {STRUCT_FLD(field_name, "dirty"), + STRUCT_FLD(field_length, MY_INT64_NUM_DECIMAL_DIGITS), + STRUCT_FLD(field_type, MYSQL_TYPE_LONGLONG), + STRUCT_FLD(value, 0), + STRUCT_FLD(field_flags, MY_I_S_UNSIGNED), + STRUCT_FLD(old_name, ""), + STRUCT_FLD(open_method, SKIP_OPEN_TABLE)}, + + {STRUCT_FLD(field_name, "old"), + STRUCT_FLD(field_length, MY_INT64_NUM_DECIMAL_DIGITS), + STRUCT_FLD(field_type, MYSQL_TYPE_LONGLONG), + STRUCT_FLD(value, 0), + STRUCT_FLD(field_flags, MY_I_S_UNSIGNED), + STRUCT_FLD(old_name, ""), + STRUCT_FLD(open_method, SKIP_OPEN_TABLE)}, + + {STRUCT_FLD(field_name, "lru_position"), + STRUCT_FLD(field_length, MY_INT64_NUM_DECIMAL_DIGITS), + STRUCT_FLD(field_type, MYSQL_TYPE_LONGLONG), + STRUCT_FLD(value, 0), + STRUCT_FLD(field_flags, MY_I_S_UNSIGNED), + STRUCT_FLD(old_name, ""), + STRUCT_FLD(open_method, SKIP_OPEN_TABLE)}, + + {STRUCT_FLD(field_name, "fix_count"), + STRUCT_FLD(field_length, MY_INT64_NUM_DECIMAL_DIGITS), + STRUCT_FLD(field_type, MYSQL_TYPE_LONGLONG), + STRUCT_FLD(value, 0), + STRUCT_FLD(field_flags, MY_I_S_UNSIGNED), + STRUCT_FLD(old_name, ""), + STRUCT_FLD(open_method, SKIP_OPEN_TABLE)}, + + {STRUCT_FLD(field_name, "flush_type"), + STRUCT_FLD(field_length, MY_INT64_NUM_DECIMAL_DIGITS), + STRUCT_FLD(field_type, MYSQL_TYPE_LONGLONG), + STRUCT_FLD(value, 0), + STRUCT_FLD(field_flags, MY_I_S_UNSIGNED), + STRUCT_FLD(old_name, ""), + STRUCT_FLD(open_method, SKIP_OPEN_TABLE)}, + + END_OF_ST_FIELD_INFO +}; + +static ST_FIELD_INFO i_s_innodb_buffer_pool_pages_blob_fields_info[] = +{ + {STRUCT_FLD(field_name, "space_id"), + STRUCT_FLD(field_length, MY_INT64_NUM_DECIMAL_DIGITS), + STRUCT_FLD(field_type, MYSQL_TYPE_LONGLONG), + STRUCT_FLD(value, 0), + STRUCT_FLD(field_flags, MY_I_S_UNSIGNED), + STRUCT_FLD(old_name, ""), + STRUCT_FLD(open_method, SKIP_OPEN_TABLE)}, + + {STRUCT_FLD(field_name, "page_no"), + STRUCT_FLD(field_length, MY_INT64_NUM_DECIMAL_DIGITS), + STRUCT_FLD(field_type, MYSQL_TYPE_LONGLONG), + STRUCT_FLD(value, 0), + STRUCT_FLD(field_flags, MY_I_S_UNSIGNED), + STRUCT_FLD(old_name, ""), + STRUCT_FLD(open_method, SKIP_OPEN_TABLE)}, + + {STRUCT_FLD(field_name, "compressed"), + STRUCT_FLD(field_length, MY_INT64_NUM_DECIMAL_DIGITS), + STRUCT_FLD(field_type, MYSQL_TYPE_LONGLONG), + STRUCT_FLD(value, 0), + STRUCT_FLD(field_flags, MY_I_S_UNSIGNED), + STRUCT_FLD(old_name, ""), + STRUCT_FLD(open_method, SKIP_OPEN_TABLE)}, + + {STRUCT_FLD(field_name, "part_len"), + STRUCT_FLD(field_length, MY_INT64_NUM_DECIMAL_DIGITS), + STRUCT_FLD(field_type, MYSQL_TYPE_LONGLONG), + STRUCT_FLD(value, 0), + STRUCT_FLD(field_flags, MY_I_S_UNSIGNED), + STRUCT_FLD(old_name, ""), + STRUCT_FLD(open_method, SKIP_OPEN_TABLE)}, + + {STRUCT_FLD(field_name, "next_page_no"), + STRUCT_FLD(field_length, MY_INT64_NUM_DECIMAL_DIGITS), + STRUCT_FLD(field_type, MYSQL_TYPE_LONGLONG), + STRUCT_FLD(value, 0), + STRUCT_FLD(field_flags, MY_I_S_UNSIGNED), + STRUCT_FLD(old_name, ""), + STRUCT_FLD(open_method, SKIP_OPEN_TABLE)}, + + {STRUCT_FLD(field_name, "lru_position"), + STRUCT_FLD(field_length, MY_INT64_NUM_DECIMAL_DIGITS), + STRUCT_FLD(field_type, MYSQL_TYPE_LONGLONG), + STRUCT_FLD(value, 0), + STRUCT_FLD(field_flags, MY_I_S_UNSIGNED), + STRUCT_FLD(old_name, ""), + STRUCT_FLD(open_method, SKIP_OPEN_TABLE)}, + + {STRUCT_FLD(field_name, "fix_count"), + STRUCT_FLD(field_length, MY_INT64_NUM_DECIMAL_DIGITS), + STRUCT_FLD(field_type, MYSQL_TYPE_LONGLONG), + STRUCT_FLD(value, 0), + STRUCT_FLD(field_flags, MY_I_S_UNSIGNED), + STRUCT_FLD(old_name, ""), + STRUCT_FLD(open_method, SKIP_OPEN_TABLE)}, + + {STRUCT_FLD(field_name, "flush_type"), + STRUCT_FLD(field_length, MY_INT64_NUM_DECIMAL_DIGITS), + STRUCT_FLD(field_type, MYSQL_TYPE_LONGLONG), + STRUCT_FLD(value, 0), + STRUCT_FLD(field_flags, MY_I_S_UNSIGNED), + STRUCT_FLD(old_name, ""), + STRUCT_FLD(open_method, SKIP_OPEN_TABLE)}, + + END_OF_ST_FIELD_INFO +}; + +/*********************************************************************** +Fill the dynamic table information_schema.innodb_buffer_pool_pages. */ +static +int +i_s_innodb_buffer_pool_pages_fill( +/*================*/ + /* out: 0 on success, 1 on failure */ + THD* thd, /* in: thread */ + TABLE_LIST* tables, /* in/out: tables to fill */ + COND* cond) /* in: condition (ignored) */ +{ + TABLE* table = (TABLE *) tables->table; + int status = 0; + + ulint n_chunks, n_blocks; + + buf_chunk_t* chunk; + + DBUG_ENTER("i_s_innodb_buffer_pool_pages_fill"); + + /* deny access to non-superusers */ + if (check_global_access(thd, PROCESS_ACL)) { + + DBUG_RETURN(0); + } + + RETURN_IF_INNODB_NOT_STARTED(tables->schema_table_name); + + buf_pool_mutex_enter(); + mutex_enter(&(dict_sys->mutex)); + + chunk = buf_pool->chunks; + + for (n_chunks = buf_pool->n_chunks; n_chunks--; chunk++) { + buf_block_t* block = chunk->blocks; + + for (n_blocks = chunk->size; n_blocks--; block++) { + const buf_frame_t* frame = block->frame; + + char page_type[64]; + + switch(fil_page_get_type(frame)) + { + case FIL_PAGE_INDEX: + strcpy(page_type, "index"); + break; + case FIL_PAGE_UNDO_LOG: + strcpy(page_type, "undo_log"); + break; + case FIL_PAGE_INODE: + strcpy(page_type, "inode"); + break; + case FIL_PAGE_IBUF_FREE_LIST: + strcpy(page_type, "ibuf_free_list"); + break; + case FIL_PAGE_TYPE_ALLOCATED: + strcpy(page_type, "allocated"); + break; + case FIL_PAGE_IBUF_BITMAP: + strcpy(page_type, "bitmap"); + break; + case FIL_PAGE_TYPE_SYS: + strcpy(page_type, "sys"); + break; + case FIL_PAGE_TYPE_TRX_SYS: + strcpy(page_type, "trx_sys"); + break; + case FIL_PAGE_TYPE_FSP_HDR: + strcpy(page_type, "fsp_hdr"); + break; + case FIL_PAGE_TYPE_XDES: + strcpy(page_type, "xdes"); + break; + case FIL_PAGE_TYPE_BLOB: + strcpy(page_type, "blob"); + break; + case FIL_PAGE_TYPE_ZBLOB: + strcpy(page_type, "zblob"); + break; + case FIL_PAGE_TYPE_ZBLOB2: + strcpy(page_type, "zblob2"); + break; + default: + sprintf(page_type, "unknown (type=%li)", fil_page_get_type(frame)); + } + + field_store_string(table->field[0], page_type); + table->field[1]->store(block->page.space); + table->field[2]->store(block->page.offset); + table->field[3]->store(block->page.LRU_position); + table->field[4]->store(block->page.buf_fix_count); + table->field[5]->store(block->page.flush_type); + + if (schema_table_store_record(thd, table)) { + status = 1; + break; + } + + } + } + + mutex_exit(&(dict_sys->mutex)); + buf_pool_mutex_exit(); + + DBUG_RETURN(status); +} + +/*********************************************************************** +Fill the dynamic table information_schema.innodb_buffer_pool_pages_index. */ +static +int +i_s_innodb_buffer_pool_pages_index_fill( +/*================*/ + /* out: 0 on success, 1 on failure */ + THD* thd, /* in: thread */ + TABLE_LIST* tables, /* in/out: tables to fill */ + COND* cond) /* in: condition (ignored) */ +{ + TABLE* table = (TABLE *) tables->table; + int status = 0; + + ulint n_chunks, n_blocks; + dict_index_t* index; + dulint index_id; + + char *p; + char db_name_raw[NAME_LEN*5+1], db_name[NAME_LEN+1]; + char table_name_raw[NAME_LEN*5+1], table_name[NAME_LEN+1]; + + buf_chunk_t* chunk; + + DBUG_ENTER("i_s_innodb_buffer_pool_pages_index_fill"); + + /* deny access to non-superusers */ + if (check_global_access(thd, PROCESS_ACL)) { + + DBUG_RETURN(0); + } + + RETURN_IF_INNODB_NOT_STARTED(tables->schema_table_name); + + buf_pool_mutex_enter(); + mutex_enter(&(dict_sys->mutex)); + + chunk = buf_pool->chunks; + + for (n_chunks = buf_pool->n_chunks; n_chunks--; chunk++) { + buf_block_t* block = chunk->blocks; + + for (n_blocks = chunk->size; n_blocks--; block++) { + const buf_frame_t* frame = block->frame; + + if (fil_page_get_type(frame) == FIL_PAGE_INDEX) { + index_id = btr_page_get_index_id(frame); + index = dict_index_get_if_in_cache_low(index_id); + if(index) + { + if((p = strchr(index->table_name, '/'))) + { + strncpy(db_name_raw, index->table_name, p-index->table_name); + db_name_raw[p-index->table_name] = 0; + filename_to_tablename(db_name_raw, db_name, sizeof(db_name)); + field_store_string(table->field[0], db_name); + p++; + } else { + field_store_string(table->field[0], NULL); + p = (char *)index->table_name; + } + strcpy(table_name_raw, p); + filename_to_tablename(table_name_raw, table_name, sizeof(table_name)); + field_store_string(table->field[1], table_name); + field_store_string(table->field[2], index->name); + + table->field[3]->store(block->page.space); + table->field[4]->store(block->page.offset); + table->field[5]->store(page_get_n_recs(frame)); + table->field[6]->store(page_get_data_size(frame)); + table->field[7]->store(block->is_hashed); + table->field[8]->store(block->page.accessed); + table->field[9]->store(block->page.newest_modification != 0); + table->field[10]->store(block->page.oldest_modification != 0); + table->field[11]->store(block->page.old); + table->field[12]->store(block->page.LRU_position); + table->field[13]->store(block->page.buf_fix_count); + table->field[14]->store(block->page.flush_type); + + if (schema_table_store_record(thd, table)) { + status = 1; + break; + } + } + } + } + } + + mutex_exit(&(dict_sys->mutex)); + buf_pool_mutex_exit(); + + DBUG_RETURN(status); +} + +/*********************************************************************** +Fill the dynamic table information_schema.innodb_buffer_pool_pages_index. */ +static +int +i_s_innodb_buffer_pool_pages_blob_fill( +/*================*/ + /* out: 0 on success, 1 on failure */ + THD* thd, /* in: thread */ + TABLE_LIST* tables, /* in/out: tables to fill */ + COND* cond) /* in: condition (ignored) */ +{ + TABLE* table = (TABLE *) tables->table; + int status = 0; + + ulint n_chunks, n_blocks; + buf_chunk_t* chunk; + page_zip_des_t* block_page_zip; + + ulint part_len; + ulint next_page_no; + + DBUG_ENTER("i_s_innodb_buffer_pool_pages_blob_fill"); + + /* deny access to non-superusers */ + if (check_global_access(thd, PROCESS_ACL)) { + + DBUG_RETURN(0); + } + + RETURN_IF_INNODB_NOT_STARTED(tables->schema_table_name); + + buf_pool_mutex_enter(); + mutex_enter(&(dict_sys->mutex)); + + chunk = buf_pool->chunks; + + for (n_chunks = buf_pool->n_chunks; n_chunks--; chunk++) { + buf_block_t* block = chunk->blocks; + block_page_zip = buf_block_get_page_zip(block); + + for (n_blocks = chunk->size; n_blocks--; block++) { + const buf_frame_t* frame = block->frame; + + if (fil_page_get_type(frame) == FIL_PAGE_TYPE_BLOB) { + + if (UNIV_LIKELY_NULL(block_page_zip)) { + part_len = 0; /* hmm, can't figure it out */ + + next_page_no = mach_read_from_4( + buf_block_get_frame(block) + + FIL_PAGE_NEXT); + } else { + part_len = mach_read_from_4( + buf_block_get_frame(block) + + FIL_PAGE_DATA + + 0 /*BTR_BLOB_HDR_PART_LEN*/); + + next_page_no = mach_read_from_4( + buf_block_get_frame(block) + + FIL_PAGE_DATA + + 4 /*BTR_BLOB_HDR_NEXT_PAGE_NO*/); + } + + table->field[0]->store(block->page.space); + table->field[1]->store(block->page.offset); + table->field[2]->store(block_page_zip != NULL); + table->field[3]->store(part_len); + + if(next_page_no == FIL_NULL) + { + table->field[4]->store(0); + } else { + table->field[4]->store(block->page.offset); + } + + table->field[5]->store(block->page.LRU_position); + table->field[6]->store(block->page.buf_fix_count); + table->field[7]->store(block->page.flush_type); + + if (schema_table_store_record(thd, table)) { + status = 1; + break; + } + + } + } + } + + mutex_exit(&(dict_sys->mutex)); + buf_pool_mutex_exit(); + + DBUG_RETURN(status); +} + +/*********************************************************************** +Bind the dynamic table information_schema.innodb_buffer_pool_pages. */ +static +int +i_s_innodb_buffer_pool_pages_init( +/*=========*/ + /* out: 0 on success */ + void* p) /* in/out: table schema object */ +{ + DBUG_ENTER("i_s_innodb_buffer_pool_pages_init"); + ST_SCHEMA_TABLE* schema = (ST_SCHEMA_TABLE*) p; + + schema->fields_info = i_s_innodb_buffer_pool_pages_fields_info; + schema->fill_table = i_s_innodb_buffer_pool_pages_fill; + + DBUG_RETURN(0); +} + +/*********************************************************************** +Bind the dynamic table information_schema.innodb_buffer_pool_pages. */ +static +int +i_s_innodb_buffer_pool_pages_index_init( +/*=========*/ + /* out: 0 on success */ + void* p) /* in/out: table schema object */ +{ + DBUG_ENTER("i_s_innodb_buffer_pool_pages_index_init"); + ST_SCHEMA_TABLE* schema = (ST_SCHEMA_TABLE*) p; + + schema->fields_info = i_s_innodb_buffer_pool_pages_index_fields_info; + schema->fill_table = i_s_innodb_buffer_pool_pages_index_fill; + + DBUG_RETURN(0); +} + +/*********************************************************************** +Bind the dynamic table information_schema.innodb_buffer_pool_pages. */ +static +int +i_s_innodb_buffer_pool_pages_blob_init( +/*=========*/ + /* out: 0 on success */ + void* p) /* in/out: table schema object */ +{ + DBUG_ENTER("i_s_innodb_buffer_pool_pages_blob_init"); + ST_SCHEMA_TABLE* schema = (ST_SCHEMA_TABLE*) p; + + schema->fields_info = i_s_innodb_buffer_pool_pages_blob_fields_info; + schema->fill_table = i_s_innodb_buffer_pool_pages_blob_fill; + + DBUG_RETURN(0); +} + + +UNIV_INTERN struct st_mysql_plugin i_s_innodb_buffer_pool_pages = +{ + /* the plugin type (a MYSQL_XXX_PLUGIN value) */ + /* int */ + STRUCT_FLD(type, MYSQL_INFORMATION_SCHEMA_PLUGIN), + + /* pointer to type-specific plugin descriptor */ + /* void* */ + STRUCT_FLD(info, &i_s_info), + + /* plugin name */ + /* const char* */ + STRUCT_FLD(name, "INNODB_BUFFER_POOL_PAGES"), + + /* plugin author (for SHOW PLUGINS) */ + /* const char* */ + STRUCT_FLD(author, plugin_author), + + /* general descriptive text (for SHOW PLUGINS) */ + /* const char* */ + STRUCT_FLD(descr, "InnoDB buffer pool pages"), + + /* the plugin license (PLUGIN_LICENSE_XXX) */ + /* int */ + STRUCT_FLD(license, PLUGIN_LICENSE_GPL), + + /* the function to invoke when plugin is loaded */ + /* int (*)(void*); */ + STRUCT_FLD(init, i_s_innodb_buffer_pool_pages_init), + + /* the function to invoke when plugin is unloaded */ + /* int (*)(void*); */ + STRUCT_FLD(deinit, i_s_common_deinit), + + /* plugin version (for SHOW PLUGINS) */ + /* unsigned int */ + STRUCT_FLD(version, 0x0100 /* 1.0 */), + + /* struct st_mysql_show_var* */ + STRUCT_FLD(status_vars, NULL), + + /* struct st_mysql_sys_var** */ + STRUCT_FLD(system_vars, NULL), + + /* reserved for dependency checking */ + /* void* */ + STRUCT_FLD(__reserved1, NULL) +}; + +UNIV_INTERN struct st_mysql_plugin i_s_innodb_buffer_pool_pages_index = +{ + /* the plugin type (a MYSQL_XXX_PLUGIN value) */ + /* int */ + STRUCT_FLD(type, MYSQL_INFORMATION_SCHEMA_PLUGIN), + + /* pointer to type-specific plugin descriptor */ + /* void* */ + STRUCT_FLD(info, &i_s_info), + + /* plugin name */ + /* const char* */ + STRUCT_FLD(name, "INNODB_BUFFER_POOL_PAGES_INDEX"), + + /* plugin author (for SHOW PLUGINS) */ + /* const char* */ + STRUCT_FLD(author, plugin_author), + + /* general descriptive text (for SHOW PLUGINS) */ + /* const char* */ + STRUCT_FLD(descr, "InnoDB buffer pool index pages"), + + /* the plugin license (PLUGIN_LICENSE_XXX) */ + /* int */ + STRUCT_FLD(license, PLUGIN_LICENSE_GPL), + + /* the function to invoke when plugin is loaded */ + /* int (*)(void*); */ + STRUCT_FLD(init, i_s_innodb_buffer_pool_pages_index_init), + + /* the function to invoke when plugin is unloaded */ + /* int (*)(void*); */ + STRUCT_FLD(deinit, i_s_common_deinit), + + /* plugin version (for SHOW PLUGINS) */ + /* unsigned int */ + STRUCT_FLD(version, 0x0100 /* 1.0 */), + + /* struct st_mysql_show_var* */ + STRUCT_FLD(status_vars, NULL), + + /* struct st_mysql_sys_var** */ + STRUCT_FLD(system_vars, NULL), + + /* reserved for dependency checking */ + /* void* */ + STRUCT_FLD(__reserved1, NULL) +}; + +UNIV_INTERN struct st_mysql_plugin i_s_innodb_buffer_pool_pages_blob = +{ + /* the plugin type (a MYSQL_XXX_PLUGIN value) */ + /* int */ + STRUCT_FLD(type, MYSQL_INFORMATION_SCHEMA_PLUGIN), + + /* pointer to type-specific plugin descriptor */ + /* void* */ + STRUCT_FLD(info, &i_s_info), + + /* plugin name */ + /* const char* */ + STRUCT_FLD(name, "INNODB_BUFFER_POOL_PAGES_BLOB"), + + /* plugin author (for SHOW PLUGINS) */ + /* const char* */ + STRUCT_FLD(author, plugin_author), + + /* general descriptive text (for SHOW PLUGINS) */ + /* const char* */ + STRUCT_FLD(descr, "InnoDB buffer pool blob pages"), + + /* the plugin license (PLUGIN_LICENSE_XXX) */ + /* int */ + STRUCT_FLD(license, PLUGIN_LICENSE_GPL), + + /* the function to invoke when plugin is loaded */ + /* int (*)(void*); */ + STRUCT_FLD(init, i_s_innodb_buffer_pool_pages_blob_init), + + /* the function to invoke when plugin is unloaded */ + /* int (*)(void*); */ + STRUCT_FLD(deinit, i_s_common_deinit), + + /* plugin version (for SHOW PLUGINS) */ + /* unsigned int */ + STRUCT_FLD(version, 0x0100 /* 1.0 */), + + /* struct st_mysql_show_var* */ + STRUCT_FLD(status_vars, NULL), + + /* struct st_mysql_sys_var** */ + STRUCT_FLD(system_vars, NULL), + + /* reserved for dependency checking */ + /* void* */ + STRUCT_FLD(__reserved1, NULL) +}; + + +/* Fields of the dynamic table INFORMATION_SCHEMA.innodb_trx */ +static ST_FIELD_INFO innodb_trx_fields_info[] = +{ +#define IDX_TRX_ID 0 + {STRUCT_FLD(field_name, "trx_id"), + STRUCT_FLD(field_length, TRX_ID_MAX_LEN + 1), + STRUCT_FLD(field_type, MYSQL_TYPE_STRING), + STRUCT_FLD(value, 0), + STRUCT_FLD(field_flags, 0), + STRUCT_FLD(old_name, ""), + STRUCT_FLD(open_method, SKIP_OPEN_TABLE)}, + +#define IDX_TRX_STATE 1 + {STRUCT_FLD(field_name, "trx_state"), + STRUCT_FLD(field_length, TRX_QUE_STATE_STR_MAX_LEN + 1), + STRUCT_FLD(field_type, MYSQL_TYPE_STRING), + STRUCT_FLD(value, 0), + STRUCT_FLD(field_flags, 0), + STRUCT_FLD(old_name, ""), + STRUCT_FLD(open_method, SKIP_OPEN_TABLE)}, + +#define IDX_TRX_STARTED 2 + {STRUCT_FLD(field_name, "trx_started"), + STRUCT_FLD(field_length, 0), + STRUCT_FLD(field_type, MYSQL_TYPE_DATETIME), + STRUCT_FLD(value, 0), + STRUCT_FLD(field_flags, 0), + STRUCT_FLD(old_name, ""), + STRUCT_FLD(open_method, SKIP_OPEN_TABLE)}, + +#define IDX_TRX_REQUESTED_LOCK_ID 3 + {STRUCT_FLD(field_name, "trx_requested_lock_id"), + STRUCT_FLD(field_length, TRX_I_S_LOCK_ID_MAX_LEN + 1), + STRUCT_FLD(field_type, MYSQL_TYPE_STRING), + STRUCT_FLD(value, 0), + STRUCT_FLD(field_flags, MY_I_S_MAYBE_NULL), + STRUCT_FLD(old_name, ""), + STRUCT_FLD(open_method, SKIP_OPEN_TABLE)}, + +#define IDX_TRX_WAIT_STARTED 4 + {STRUCT_FLD(field_name, "trx_wait_started"), + STRUCT_FLD(field_length, 0), + STRUCT_FLD(field_type, MYSQL_TYPE_DATETIME), + STRUCT_FLD(value, 0), + STRUCT_FLD(field_flags, MY_I_S_MAYBE_NULL), + STRUCT_FLD(old_name, ""), + STRUCT_FLD(open_method, SKIP_OPEN_TABLE)}, + +#define IDX_TRX_WEIGHT 5 + {STRUCT_FLD(field_name, "trx_weight"), + STRUCT_FLD(field_length, MY_INT64_NUM_DECIMAL_DIGITS), + STRUCT_FLD(field_type, MYSQL_TYPE_LONGLONG), + STRUCT_FLD(value, 0), + STRUCT_FLD(field_flags, MY_I_S_UNSIGNED), + STRUCT_FLD(old_name, ""), + STRUCT_FLD(open_method, SKIP_OPEN_TABLE)}, + +#define IDX_TRX_MYSQL_THREAD_ID 6 + {STRUCT_FLD(field_name, "trx_mysql_thread_id"), + STRUCT_FLD(field_length, MY_INT64_NUM_DECIMAL_DIGITS), + STRUCT_FLD(field_type, MYSQL_TYPE_LONGLONG), + STRUCT_FLD(value, 0), + STRUCT_FLD(field_flags, MY_I_S_UNSIGNED), + STRUCT_FLD(old_name, ""), + STRUCT_FLD(open_method, SKIP_OPEN_TABLE)}, + +#define IDX_TRX_QUERY 7 + {STRUCT_FLD(field_name, "trx_query"), + STRUCT_FLD(field_length, TRX_I_S_TRX_QUERY_MAX_LEN), + STRUCT_FLD(field_type, MYSQL_TYPE_STRING), + STRUCT_FLD(value, 0), + STRUCT_FLD(field_flags, MY_I_S_MAYBE_NULL), + STRUCT_FLD(old_name, ""), + STRUCT_FLD(open_method, SKIP_OPEN_TABLE)}, + + END_OF_ST_FIELD_INFO +}; + +/*********************************************************************** +Read data from cache buffer and fill the INFORMATION_SCHEMA.innodb_trx +table with it. */ +static +int +fill_innodb_trx_from_cache( +/*=======================*/ + /* out: 0 on success */ + trx_i_s_cache_t* cache, /* in: cache to read from */ + THD* thd, /* in: used to call + schema_table_store_record() */ + TABLE* table) /* in/out: fill this table */ +{ + Field** fields; + ulint rows_num; + char lock_id[TRX_I_S_LOCK_ID_MAX_LEN + 1]; + ulint i; + + DBUG_ENTER("fill_innodb_trx_from_cache"); + + fields = table->field; + + rows_num = trx_i_s_cache_get_rows_used(cache, + I_S_INNODB_TRX); + + for (i = 0; i < rows_num; i++) { + + i_s_trx_row_t* row; + char trx_id[TRX_ID_MAX_LEN + 1]; + + row = (i_s_trx_row_t*) + trx_i_s_cache_get_nth_row( + cache, I_S_INNODB_TRX, i); + + /* trx_id */ + ut_snprintf(trx_id, sizeof(trx_id), TRX_ID_FMT, row->trx_id); + OK(field_store_string(fields[IDX_TRX_ID], trx_id)); + + /* trx_state */ + OK(field_store_string(fields[IDX_TRX_STATE], + row->trx_state)); + + /* trx_started */ + OK(field_store_time_t(fields[IDX_TRX_STARTED], + (time_t) row->trx_started)); + + /* trx_requested_lock_id */ + /* trx_wait_started */ + if (row->trx_wait_started != 0) { + + OK(field_store_string( + fields[IDX_TRX_REQUESTED_LOCK_ID], + trx_i_s_create_lock_id( + row->requested_lock_row, + lock_id, sizeof(lock_id)))); + /* field_store_string() sets it no notnull */ + + OK(field_store_time_t( + fields[IDX_TRX_WAIT_STARTED], + (time_t) row->trx_wait_started)); + fields[IDX_TRX_WAIT_STARTED]->set_notnull(); + } else { + + fields[IDX_TRX_REQUESTED_LOCK_ID]->set_null(); + fields[IDX_TRX_WAIT_STARTED]->set_null(); + } + + /* trx_weight */ + OK(fields[IDX_TRX_WEIGHT]->store((longlong) row->trx_weight, + true)); + + /* trx_mysql_thread_id */ + OK(fields[IDX_TRX_MYSQL_THREAD_ID]->store( + row->trx_mysql_thread_id)); + + /* trx_query */ + OK(field_store_string(fields[IDX_TRX_QUERY], + row->trx_query)); + + OK(schema_table_store_record(thd, table)); + } + + DBUG_RETURN(0); +} + +/*********************************************************************** +Bind the dynamic table INFORMATION_SCHEMA.innodb_trx */ +static +int +innodb_trx_init( +/*============*/ + /* out: 0 on success */ + void* p) /* in/out: table schema object */ +{ + ST_SCHEMA_TABLE* schema; + + DBUG_ENTER("innodb_trx_init"); + + schema = (ST_SCHEMA_TABLE*) p; + + schema->fields_info = innodb_trx_fields_info; + schema->fill_table = trx_i_s_common_fill_table; + + DBUG_RETURN(0); +} + + +UNIV_INTERN struct st_mysql_plugin i_s_innodb_trx = +{ + /* the plugin type (a MYSQL_XXX_PLUGIN value) */ + /* int */ + STRUCT_FLD(type, MYSQL_INFORMATION_SCHEMA_PLUGIN), + + /* pointer to type-specific plugin descriptor */ + /* void* */ + STRUCT_FLD(info, &i_s_info), + + /* plugin name */ + /* const char* */ + STRUCT_FLD(name, "INNODB_TRX"), + + /* plugin author (for SHOW PLUGINS) */ + /* const char* */ + STRUCT_FLD(author, plugin_author), + + /* general descriptive text (for SHOW PLUGINS) */ + /* const char* */ + STRUCT_FLD(descr, "InnoDB transactions"), + + /* the plugin license (PLUGIN_LICENSE_XXX) */ + /* int */ + STRUCT_FLD(license, PLUGIN_LICENSE_GPL), + + /* the function to invoke when plugin is loaded */ + /* int (*)(void*); */ + STRUCT_FLD(init, innodb_trx_init), + + /* the function to invoke when plugin is unloaded */ + /* int (*)(void*); */ + STRUCT_FLD(deinit, i_s_common_deinit), + + /* plugin version (for SHOW PLUGINS) */ + /* unsigned int */ + STRUCT_FLD(version, INNODB_VERSION_SHORT), + + /* struct st_mysql_show_var* */ + STRUCT_FLD(status_vars, NULL), + + /* struct st_mysql_sys_var** */ + STRUCT_FLD(system_vars, NULL), + + /* reserved for dependency checking */ + /* void* */ + STRUCT_FLD(__reserved1, NULL) +}; + +/* Fields of the dynamic table INFORMATION_SCHEMA.innodb_locks */ +static ST_FIELD_INFO innodb_locks_fields_info[] = +{ +#define IDX_LOCK_ID 0 + {STRUCT_FLD(field_name, "lock_id"), + STRUCT_FLD(field_length, TRX_I_S_LOCK_ID_MAX_LEN + 1), + STRUCT_FLD(field_type, MYSQL_TYPE_STRING), + STRUCT_FLD(value, 0), + STRUCT_FLD(field_flags, 0), + STRUCT_FLD(old_name, ""), + STRUCT_FLD(open_method, SKIP_OPEN_TABLE)}, + +#define IDX_LOCK_TRX_ID 1 + {STRUCT_FLD(field_name, "lock_trx_id"), + STRUCT_FLD(field_length, TRX_ID_MAX_LEN + 1), + STRUCT_FLD(field_type, MYSQL_TYPE_STRING), + STRUCT_FLD(value, 0), + STRUCT_FLD(field_flags, 0), + STRUCT_FLD(old_name, ""), + STRUCT_FLD(open_method, SKIP_OPEN_TABLE)}, + +#define IDX_LOCK_MODE 2 + {STRUCT_FLD(field_name, "lock_mode"), + /* S[,GAP] X[,GAP] IS[,GAP] IX[,GAP] AUTO_INC UNKNOWN */ + STRUCT_FLD(field_length, 32), + STRUCT_FLD(field_type, MYSQL_TYPE_STRING), + STRUCT_FLD(value, 0), + STRUCT_FLD(field_flags, 0), + STRUCT_FLD(old_name, ""), + STRUCT_FLD(open_method, SKIP_OPEN_TABLE)}, + +#define IDX_LOCK_TYPE 3 + {STRUCT_FLD(field_name, "lock_type"), + STRUCT_FLD(field_length, 32 /* RECORD|TABLE|UNKNOWN */), + STRUCT_FLD(field_type, MYSQL_TYPE_STRING), + STRUCT_FLD(value, 0), + STRUCT_FLD(field_flags, 0), + STRUCT_FLD(old_name, ""), + STRUCT_FLD(open_method, SKIP_OPEN_TABLE)}, + +#define IDX_LOCK_TABLE 4 + {STRUCT_FLD(field_name, "lock_table"), + STRUCT_FLD(field_length, 1024), + STRUCT_FLD(field_type, MYSQL_TYPE_STRING), + STRUCT_FLD(value, 0), + STRUCT_FLD(field_flags, 0), + STRUCT_FLD(old_name, ""), + STRUCT_FLD(open_method, SKIP_OPEN_TABLE)}, + +#define IDX_LOCK_INDEX 5 + {STRUCT_FLD(field_name, "lock_index"), + STRUCT_FLD(field_length, 1024), + STRUCT_FLD(field_type, MYSQL_TYPE_STRING), + STRUCT_FLD(value, 0), + STRUCT_FLD(field_flags, MY_I_S_MAYBE_NULL), + STRUCT_FLD(old_name, ""), + STRUCT_FLD(open_method, SKIP_OPEN_TABLE)}, + +#define IDX_LOCK_SPACE 6 + {STRUCT_FLD(field_name, "lock_space"), + STRUCT_FLD(field_length, MY_INT64_NUM_DECIMAL_DIGITS), + STRUCT_FLD(field_type, MYSQL_TYPE_LONGLONG), + STRUCT_FLD(value, 0), + STRUCT_FLD(field_flags, MY_I_S_UNSIGNED | MY_I_S_MAYBE_NULL), + STRUCT_FLD(old_name, ""), + STRUCT_FLD(open_method, SKIP_OPEN_TABLE)}, + +#define IDX_LOCK_PAGE 7 + {STRUCT_FLD(field_name, "lock_page"), + STRUCT_FLD(field_length, MY_INT64_NUM_DECIMAL_DIGITS), + STRUCT_FLD(field_type, MYSQL_TYPE_LONGLONG), + STRUCT_FLD(value, 0), + STRUCT_FLD(field_flags, MY_I_S_UNSIGNED | MY_I_S_MAYBE_NULL), + STRUCT_FLD(old_name, ""), + STRUCT_FLD(open_method, SKIP_OPEN_TABLE)}, + +#define IDX_LOCK_REC 8 + {STRUCT_FLD(field_name, "lock_rec"), + STRUCT_FLD(field_length, MY_INT64_NUM_DECIMAL_DIGITS), + STRUCT_FLD(field_type, MYSQL_TYPE_LONGLONG), + STRUCT_FLD(value, 0), + STRUCT_FLD(field_flags, MY_I_S_UNSIGNED | MY_I_S_MAYBE_NULL), + STRUCT_FLD(old_name, ""), + STRUCT_FLD(open_method, SKIP_OPEN_TABLE)}, + +#define IDX_LOCK_DATA 9 + {STRUCT_FLD(field_name, "lock_data"), + STRUCT_FLD(field_length, TRX_I_S_LOCK_DATA_MAX_LEN), + STRUCT_FLD(field_type, MYSQL_TYPE_STRING), + STRUCT_FLD(value, 0), + STRUCT_FLD(field_flags, MY_I_S_MAYBE_NULL), + STRUCT_FLD(old_name, ""), + STRUCT_FLD(open_method, SKIP_OPEN_TABLE)}, + + END_OF_ST_FIELD_INFO +}; + +/*********************************************************************** +Read data from cache buffer and fill the INFORMATION_SCHEMA.innodb_locks +table with it. */ +static +int +fill_innodb_locks_from_cache( +/*=========================*/ + /* out: 0 on success */ + trx_i_s_cache_t* cache, /* in: cache to read from */ + THD* thd, /* in: MySQL client connection */ + TABLE* table) /* in/out: fill this table */ +{ + Field** fields; + ulint rows_num; + char lock_id[TRX_I_S_LOCK_ID_MAX_LEN + 1]; + ulint i; + + DBUG_ENTER("fill_innodb_locks_from_cache"); + + fields = table->field; + + rows_num = trx_i_s_cache_get_rows_used(cache, + I_S_INNODB_LOCKS); + + for (i = 0; i < rows_num; i++) { + + i_s_locks_row_t* row; + + /* note that the decoded database or table name is + never expected to be longer than NAME_LEN; + NAME_LEN for database name + 2 for surrounding quotes around database name + NAME_LEN for table name + 2 for surrounding quotes around table name + 1 for the separating dot (.) + 9 for the #mysql50# prefix */ + char buf[2 * NAME_LEN + 14]; + const char* bufend; + + char lock_trx_id[TRX_ID_MAX_LEN + 1]; + + row = (i_s_locks_row_t*) + trx_i_s_cache_get_nth_row( + cache, I_S_INNODB_LOCKS, i); + + /* lock_id */ + trx_i_s_create_lock_id(row, lock_id, sizeof(lock_id)); + OK(field_store_string(fields[IDX_LOCK_ID], + lock_id)); + + /* lock_trx_id */ + ut_snprintf(lock_trx_id, sizeof(lock_trx_id), + TRX_ID_FMT, row->lock_trx_id); + OK(field_store_string(fields[IDX_LOCK_TRX_ID], lock_trx_id)); + + /* lock_mode */ + OK(field_store_string(fields[IDX_LOCK_MODE], + row->lock_mode)); + + /* lock_type */ + OK(field_store_string(fields[IDX_LOCK_TYPE], + row->lock_type)); + + /* lock_table */ + bufend = innobase_convert_name(buf, sizeof(buf), + row->lock_table, + strlen(row->lock_table), + thd, TRUE); + OK(fields[IDX_LOCK_TABLE]->store(buf, bufend - buf, + system_charset_info)); + + /* lock_index */ + if (row->lock_index != NULL) { + + bufend = innobase_convert_name(buf, sizeof(buf), + row->lock_index, + strlen(row->lock_index), + thd, FALSE); + OK(fields[IDX_LOCK_INDEX]->store(buf, bufend - buf, + system_charset_info)); + fields[IDX_LOCK_INDEX]->set_notnull(); + } else { + + fields[IDX_LOCK_INDEX]->set_null(); + } + + /* lock_space */ + OK(field_store_ulint(fields[IDX_LOCK_SPACE], + row->lock_space)); + + /* lock_page */ + OK(field_store_ulint(fields[IDX_LOCK_PAGE], + row->lock_page)); + + /* lock_rec */ + OK(field_store_ulint(fields[IDX_LOCK_REC], + row->lock_rec)); + + /* lock_data */ + OK(field_store_string(fields[IDX_LOCK_DATA], + row->lock_data)); + + OK(schema_table_store_record(thd, table)); + } + + DBUG_RETURN(0); +} + +/*********************************************************************** +Bind the dynamic table INFORMATION_SCHEMA.innodb_locks */ +static +int +innodb_locks_init( +/*==============*/ + /* out: 0 on success */ + void* p) /* in/out: table schema object */ +{ + ST_SCHEMA_TABLE* schema; + + DBUG_ENTER("innodb_locks_init"); + + schema = (ST_SCHEMA_TABLE*) p; + + schema->fields_info = innodb_locks_fields_info; + schema->fill_table = trx_i_s_common_fill_table; + + DBUG_RETURN(0); +} + +UNIV_INTERN struct st_mysql_plugin i_s_innodb_locks = +{ + /* the plugin type (a MYSQL_XXX_PLUGIN value) */ + /* int */ + STRUCT_FLD(type, MYSQL_INFORMATION_SCHEMA_PLUGIN), + + /* pointer to type-specific plugin descriptor */ + /* void* */ + STRUCT_FLD(info, &i_s_info), + + /* plugin name */ + /* const char* */ + STRUCT_FLD(name, "INNODB_LOCKS"), + + /* plugin author (for SHOW PLUGINS) */ + /* const char* */ + STRUCT_FLD(author, plugin_author), + + /* general descriptive text (for SHOW PLUGINS) */ + /* const char* */ + STRUCT_FLD(descr, "InnoDB conflicting locks"), + + /* the plugin license (PLUGIN_LICENSE_XXX) */ + /* int */ + STRUCT_FLD(license, PLUGIN_LICENSE_GPL), + + /* the function to invoke when plugin is loaded */ + /* int (*)(void*); */ + STRUCT_FLD(init, innodb_locks_init), + + /* the function to invoke when plugin is unloaded */ + /* int (*)(void*); */ + STRUCT_FLD(deinit, i_s_common_deinit), + + /* plugin version (for SHOW PLUGINS) */ + /* unsigned int */ + STRUCT_FLD(version, INNODB_VERSION_SHORT), + + /* struct st_mysql_show_var* */ + STRUCT_FLD(status_vars, NULL), + + /* struct st_mysql_sys_var** */ + STRUCT_FLD(system_vars, NULL), + + /* reserved for dependency checking */ + /* void* */ + STRUCT_FLD(__reserved1, NULL) +}; + +/* Fields of the dynamic table INFORMATION_SCHEMA.innodb_lock_waits */ +static ST_FIELD_INFO innodb_lock_waits_fields_info[] = +{ +#define IDX_REQUESTING_TRX_ID 0 + {STRUCT_FLD(field_name, "requesting_trx_id"), + STRUCT_FLD(field_length, TRX_ID_MAX_LEN + 1), + STRUCT_FLD(field_type, MYSQL_TYPE_STRING), + STRUCT_FLD(value, 0), + STRUCT_FLD(field_flags, 0), + STRUCT_FLD(old_name, ""), + STRUCT_FLD(open_method, SKIP_OPEN_TABLE)}, + +#define IDX_REQUESTED_LOCK_ID 1 + {STRUCT_FLD(field_name, "requested_lock_id"), + STRUCT_FLD(field_length, TRX_I_S_LOCK_ID_MAX_LEN + 1), + STRUCT_FLD(field_type, MYSQL_TYPE_STRING), + STRUCT_FLD(value, 0), + STRUCT_FLD(field_flags, 0), + STRUCT_FLD(old_name, ""), + STRUCT_FLD(open_method, SKIP_OPEN_TABLE)}, + +#define IDX_BLOCKING_TRX_ID 2 + {STRUCT_FLD(field_name, "blocking_trx_id"), + STRUCT_FLD(field_length, TRX_ID_MAX_LEN + 1), + STRUCT_FLD(field_type, MYSQL_TYPE_STRING), + STRUCT_FLD(value, 0), + STRUCT_FLD(field_flags, 0), + STRUCT_FLD(old_name, ""), + STRUCT_FLD(open_method, SKIP_OPEN_TABLE)}, + +#define IDX_BLOCKING_LOCK_ID 3 + {STRUCT_FLD(field_name, "blocking_lock_id"), + STRUCT_FLD(field_length, TRX_I_S_LOCK_ID_MAX_LEN + 1), + STRUCT_FLD(field_type, MYSQL_TYPE_STRING), + STRUCT_FLD(value, 0), + STRUCT_FLD(field_flags, 0), + STRUCT_FLD(old_name, ""), + STRUCT_FLD(open_method, SKIP_OPEN_TABLE)}, + + END_OF_ST_FIELD_INFO +}; + +/*********************************************************************** +Read data from cache buffer and fill the +INFORMATION_SCHEMA.innodb_lock_waits table with it. */ +static +int +fill_innodb_lock_waits_from_cache( +/*==============================*/ + /* out: 0 on success */ + trx_i_s_cache_t* cache, /* in: cache to read from */ + THD* thd, /* in: used to call + schema_table_store_record() */ + TABLE* table) /* in/out: fill this table */ +{ + Field** fields; + ulint rows_num; + char requested_lock_id[TRX_I_S_LOCK_ID_MAX_LEN + 1]; + char blocking_lock_id[TRX_I_S_LOCK_ID_MAX_LEN + 1]; + ulint i; + + DBUG_ENTER("fill_innodb_lock_waits_from_cache"); + + fields = table->field; + + rows_num = trx_i_s_cache_get_rows_used(cache, + I_S_INNODB_LOCK_WAITS); + + for (i = 0; i < rows_num; i++) { + + i_s_lock_waits_row_t* row; + + char requesting_trx_id[TRX_ID_MAX_LEN + 1]; + char blocking_trx_id[TRX_ID_MAX_LEN + 1]; + + row = (i_s_lock_waits_row_t*) + trx_i_s_cache_get_nth_row( + cache, I_S_INNODB_LOCK_WAITS, i); + + /* requesting_trx_id */ + ut_snprintf(requesting_trx_id, sizeof(requesting_trx_id), + TRX_ID_FMT, row->requested_lock_row->lock_trx_id); + OK(field_store_string(fields[IDX_REQUESTING_TRX_ID], + requesting_trx_id)); + + /* requested_lock_id */ + OK(field_store_string( + fields[IDX_REQUESTED_LOCK_ID], + trx_i_s_create_lock_id( + row->requested_lock_row, + requested_lock_id, + sizeof(requested_lock_id)))); + + /* blocking_trx_id */ + ut_snprintf(blocking_trx_id, sizeof(blocking_trx_id), + TRX_ID_FMT, row->blocking_lock_row->lock_trx_id); + OK(field_store_string(fields[IDX_BLOCKING_TRX_ID], + blocking_trx_id)); + + /* blocking_lock_id */ + OK(field_store_string( + fields[IDX_BLOCKING_LOCK_ID], + trx_i_s_create_lock_id( + row->blocking_lock_row, + blocking_lock_id, + sizeof(blocking_lock_id)))); + + OK(schema_table_store_record(thd, table)); + } + + DBUG_RETURN(0); +} + +/*********************************************************************** +Bind the dynamic table INFORMATION_SCHEMA.innodb_lock_waits */ +static +int +innodb_lock_waits_init( +/*===================*/ + /* out: 0 on success */ + void* p) /* in/out: table schema object */ +{ + ST_SCHEMA_TABLE* schema; + + DBUG_ENTER("innodb_lock_waits_init"); + + schema = (ST_SCHEMA_TABLE*) p; + + schema->fields_info = innodb_lock_waits_fields_info; + schema->fill_table = trx_i_s_common_fill_table; + + DBUG_RETURN(0); +} + +UNIV_INTERN struct st_mysql_plugin i_s_innodb_lock_waits = +{ + /* the plugin type (a MYSQL_XXX_PLUGIN value) */ + /* int */ + STRUCT_FLD(type, MYSQL_INFORMATION_SCHEMA_PLUGIN), + + /* pointer to type-specific plugin descriptor */ + /* void* */ + STRUCT_FLD(info, &i_s_info), + + /* plugin name */ + /* const char* */ + STRUCT_FLD(name, "INNODB_LOCK_WAITS"), + + /* plugin author (for SHOW PLUGINS) */ + /* const char* */ + STRUCT_FLD(author, "Innobase Oy"), + + /* general descriptive text (for SHOW PLUGINS) */ + /* const char* */ + STRUCT_FLD(descr, "InnoDB which lock is blocking which"), + + /* the plugin license (PLUGIN_LICENSE_XXX) */ + /* int */ + STRUCT_FLD(license, PLUGIN_LICENSE_GPL), + + /* the function to invoke when plugin is loaded */ + /* int (*)(void*); */ + STRUCT_FLD(init, innodb_lock_waits_init), + + /* the function to invoke when plugin is unloaded */ + /* int (*)(void*); */ + STRUCT_FLD(deinit, i_s_common_deinit), + + /* plugin version (for SHOW PLUGINS) */ + /* unsigned int */ + STRUCT_FLD(version, INNODB_VERSION_SHORT), + + /* struct st_mysql_show_var* */ + STRUCT_FLD(status_vars, NULL), + + /* struct st_mysql_sys_var** */ + STRUCT_FLD(system_vars, NULL), + + /* reserved for dependency checking */ + /* void* */ + STRUCT_FLD(__reserved1, NULL) +}; + +/*********************************************************************** +Common function to fill any of the dynamic tables: +INFORMATION_SCHEMA.innodb_trx +INFORMATION_SCHEMA.innodb_locks +INFORMATION_SCHEMA.innodb_lock_waits */ +static +int +trx_i_s_common_fill_table( +/*======================*/ + /* out: 0 on success */ + THD* thd, /* in: thread */ + TABLE_LIST* tables, /* in/out: tables to fill */ + COND* cond) /* in: condition (not used) */ +{ + const char* table_name; + int ret; + trx_i_s_cache_t* cache; + + DBUG_ENTER("trx_i_s_common_fill_table"); + + /* deny access to non-superusers */ + if (check_global_access(thd, PROCESS_ACL)) { + + DBUG_RETURN(0); + } + + /* minimize the number of places where global variables are + referenced */ + cache = trx_i_s_cache; + + /* which table we have to fill? */ + table_name = tables->schema_table_name; + /* or table_name = tables->schema_table->table_name; */ + + RETURN_IF_INNODB_NOT_STARTED(table_name); + + /* update the cache */ + trx_i_s_cache_start_write(cache); + trx_i_s_possibly_fetch_data_into_cache(cache); + trx_i_s_cache_end_write(cache); + + if (trx_i_s_cache_is_truncated(cache)) { + + /* XXX show warning to user if possible */ + fprintf(stderr, "Warning: data in %s truncated due to " + "memory limit of %d bytes\n", table_name, + TRX_I_S_MEM_LIMIT); + } + + ret = 0; + + trx_i_s_cache_start_read(cache); + + if (innobase_strcasecmp(table_name, "innodb_trx") == 0) { + + if (fill_innodb_trx_from_cache( + cache, thd, tables->table) != 0) { + + ret = 1; + } + + } else if (innobase_strcasecmp(table_name, "innodb_locks") == 0) { + + if (fill_innodb_locks_from_cache( + cache, thd, tables->table) != 0) { + + ret = 1; + } + + } else if (innobase_strcasecmp(table_name, "innodb_lock_waits") == 0) { + + if (fill_innodb_lock_waits_from_cache( + cache, thd, tables->table) != 0) { + + ret = 1; + } + + } else { + + /* huh! what happened!? */ + fprintf(stderr, + "InnoDB: trx_i_s_common_fill_table() was " + "called to fill unknown table: %s.\n" + "This function only knows how to fill " + "innodb_trx, innodb_locks and " + "innodb_lock_waits tables.\n", table_name); + + ret = 1; + } + + trx_i_s_cache_end_read(cache); + +#if 0 + DBUG_RETURN(ret); +#else + /* if this function returns something else than 0 then a + deadlock occurs between the mysqld server and mysql client, + see http://bugs.mysql.com/29900 ; when that bug is resolved + we can enable the DBUG_RETURN(ret) above */ + DBUG_RETURN(0); +#endif +} + +/* Fields of the dynamic table information_schema.innodb_cmp. */ +static ST_FIELD_INFO i_s_cmp_fields_info[] = +{ + {STRUCT_FLD(field_name, "page_size"), + STRUCT_FLD(field_length, 5), + STRUCT_FLD(field_type, MYSQL_TYPE_LONG), + STRUCT_FLD(value, 0), + STRUCT_FLD(field_flags, 0), + STRUCT_FLD(old_name, "Compressed Page Size"), + STRUCT_FLD(open_method, SKIP_OPEN_TABLE)}, + + {STRUCT_FLD(field_name, "compress_ops"), + STRUCT_FLD(field_length, MY_INT32_NUM_DECIMAL_DIGITS), + STRUCT_FLD(field_type, MYSQL_TYPE_LONG), + STRUCT_FLD(value, 0), + STRUCT_FLD(field_flags, 0), + STRUCT_FLD(old_name, "Total Number of Compressions"), + STRUCT_FLD(open_method, SKIP_OPEN_TABLE)}, + + {STRUCT_FLD(field_name, "compress_ops_ok"), + STRUCT_FLD(field_length, MY_INT32_NUM_DECIMAL_DIGITS), + STRUCT_FLD(field_type, MYSQL_TYPE_LONG), + STRUCT_FLD(value, 0), + STRUCT_FLD(field_flags, 0), + STRUCT_FLD(old_name, "Total Number of" + " Successful Compressions"), + STRUCT_FLD(open_method, SKIP_OPEN_TABLE)}, + + {STRUCT_FLD(field_name, "compress_time"), + STRUCT_FLD(field_length, MY_INT32_NUM_DECIMAL_DIGITS), + STRUCT_FLD(field_type, MYSQL_TYPE_LONG), + STRUCT_FLD(value, 0), + STRUCT_FLD(field_flags, 0), + STRUCT_FLD(old_name, "Total Duration of Compressions," + " in Seconds"), + STRUCT_FLD(open_method, SKIP_OPEN_TABLE)}, + + {STRUCT_FLD(field_name, "uncompress_ops"), + STRUCT_FLD(field_length, MY_INT32_NUM_DECIMAL_DIGITS), + STRUCT_FLD(field_type, MYSQL_TYPE_LONG), + STRUCT_FLD(value, 0), + STRUCT_FLD(field_flags, 0), + STRUCT_FLD(old_name, "Total Number of Decompressions"), + STRUCT_FLD(open_method, SKIP_OPEN_TABLE)}, + + {STRUCT_FLD(field_name, "uncompress_time"), + STRUCT_FLD(field_length, MY_INT32_NUM_DECIMAL_DIGITS), + STRUCT_FLD(field_type, MYSQL_TYPE_LONG), + STRUCT_FLD(value, 0), + STRUCT_FLD(field_flags, 0), + STRUCT_FLD(old_name, "Total Duration of Decompressions," + " in Seconds"), + STRUCT_FLD(open_method, SKIP_OPEN_TABLE)}, + + END_OF_ST_FIELD_INFO +}; + + +/*********************************************************************** +Fill the dynamic table information_schema.innodb_cmp or +innodb_cmp_reset. */ +static +int +i_s_cmp_fill_low( +/*=============*/ + /* out: 0 on success, 1 on failure */ + THD* thd, /* in: thread */ + TABLE_LIST* tables, /* in/out: tables to fill */ + COND* cond, /* in: condition (ignored) */ + ibool reset) /* in: TRUE=reset cumulated counts */ +{ + TABLE* table = (TABLE *) tables->table; + int status = 0; + + DBUG_ENTER("i_s_cmp_fill_low"); + + /* deny access to non-superusers */ + if (check_global_access(thd, PROCESS_ACL)) { + + DBUG_RETURN(0); + } + + RETURN_IF_INNODB_NOT_STARTED(tables->schema_table_name); + + for (uint i = 0; i < PAGE_ZIP_NUM_SSIZE - 1; i++) { + page_zip_stat_t* zip_stat = &page_zip_stat[i]; + + table->field[0]->store(PAGE_ZIP_MIN_SIZE << i); + + /* The cumulated counts are not protected by any + mutex. Thus, some operation in page0zip.c could + increment a counter between the time we read it and + clear it. We could introduce mutex protection, but it + could cause a measureable performance hit in + page0zip.c. */ + table->field[1]->store(zip_stat->compressed); + table->field[2]->store(zip_stat->compressed_ok); + table->field[3]->store( + (ulong) (zip_stat->compressed_usec / 1000000)); + table->field[4]->store(zip_stat->decompressed); + table->field[5]->store( + (ulong) (zip_stat->decompressed_usec / 1000000)); + + if (reset) { + memset(zip_stat, 0, sizeof *zip_stat); + } + + if (schema_table_store_record(thd, table)) { + status = 1; + break; + } + } + + DBUG_RETURN(status); +} + +/*********************************************************************** +Fill the dynamic table information_schema.innodb_cmp. */ +static +int +i_s_cmp_fill( +/*=========*/ + /* out: 0 on success, 1 on failure */ + THD* thd, /* in: thread */ + TABLE_LIST* tables, /* in/out: tables to fill */ + COND* cond) /* in: condition (ignored) */ +{ + return(i_s_cmp_fill_low(thd, tables, cond, FALSE)); +} + +/*********************************************************************** +Fill the dynamic table information_schema.innodb_cmp_reset. */ +static +int +i_s_cmp_reset_fill( +/*===============*/ + /* out: 0 on success, 1 on failure */ + THD* thd, /* in: thread */ + TABLE_LIST* tables, /* in/out: tables to fill */ + COND* cond) /* in: condition (ignored) */ +{ + return(i_s_cmp_fill_low(thd, tables, cond, TRUE)); +} + +/*********************************************************************** +Bind the dynamic table information_schema.innodb_cmp. */ +static +int +i_s_cmp_init( +/*=========*/ + /* out: 0 on success */ + void* p) /* in/out: table schema object */ +{ + DBUG_ENTER("i_s_cmp_init"); + ST_SCHEMA_TABLE* schema = (ST_SCHEMA_TABLE*) p; + + schema->fields_info = i_s_cmp_fields_info; + schema->fill_table = i_s_cmp_fill; + + DBUG_RETURN(0); +} + +/*********************************************************************** +Bind the dynamic table information_schema.innodb_cmp_reset. */ +static +int +i_s_cmp_reset_init( +/*===============*/ + /* out: 0 on success */ + void* p) /* in/out: table schema object */ +{ + DBUG_ENTER("i_s_cmp_reset_init"); + ST_SCHEMA_TABLE* schema = (ST_SCHEMA_TABLE*) p; + + schema->fields_info = i_s_cmp_fields_info; + schema->fill_table = i_s_cmp_reset_fill; + + DBUG_RETURN(0); +} + +UNIV_INTERN struct st_mysql_plugin i_s_innodb_cmp = +{ + /* the plugin type (a MYSQL_XXX_PLUGIN value) */ + /* int */ + STRUCT_FLD(type, MYSQL_INFORMATION_SCHEMA_PLUGIN), + + /* pointer to type-specific plugin descriptor */ + /* void* */ + STRUCT_FLD(info, &i_s_info), + + /* plugin name */ + /* const char* */ + STRUCT_FLD(name, "INNODB_CMP"), + + /* plugin author (for SHOW PLUGINS) */ + /* const char* */ + STRUCT_FLD(author, plugin_author), + + /* general descriptive text (for SHOW PLUGINS) */ + /* const char* */ + STRUCT_FLD(descr, "Statistics for the InnoDB compression"), + + /* the plugin license (PLUGIN_LICENSE_XXX) */ + /* int */ + STRUCT_FLD(license, PLUGIN_LICENSE_GPL), + + /* the function to invoke when plugin is loaded */ + /* int (*)(void*); */ + STRUCT_FLD(init, i_s_cmp_init), + + /* the function to invoke when plugin is unloaded */ + /* int (*)(void*); */ + STRUCT_FLD(deinit, i_s_common_deinit), + + /* plugin version (for SHOW PLUGINS) */ + /* unsigned int */ + STRUCT_FLD(version, INNODB_VERSION_SHORT), + + /* struct st_mysql_show_var* */ + STRUCT_FLD(status_vars, NULL), + + /* struct st_mysql_sys_var** */ + STRUCT_FLD(system_vars, NULL), + + /* reserved for dependency checking */ + /* void* */ + STRUCT_FLD(__reserved1, NULL) +}; + +UNIV_INTERN struct st_mysql_plugin i_s_innodb_cmp_reset = +{ + /* the plugin type (a MYSQL_XXX_PLUGIN value) */ + /* int */ + STRUCT_FLD(type, MYSQL_INFORMATION_SCHEMA_PLUGIN), + + /* pointer to type-specific plugin descriptor */ + /* void* */ + STRUCT_FLD(info, &i_s_info), + + /* plugin name */ + /* const char* */ + STRUCT_FLD(name, "INNODB_CMP_RESET"), + + /* plugin author (for SHOW PLUGINS) */ + /* const char* */ + STRUCT_FLD(author, plugin_author), + + /* general descriptive text (for SHOW PLUGINS) */ + /* const char* */ + STRUCT_FLD(descr, "Statistics for the InnoDB compression;" + " reset cumulated counts"), + + /* the plugin license (PLUGIN_LICENSE_XXX) */ + /* int */ + STRUCT_FLD(license, PLUGIN_LICENSE_GPL), + + /* the function to invoke when plugin is loaded */ + /* int (*)(void*); */ + STRUCT_FLD(init, i_s_cmp_reset_init), + + /* the function to invoke when plugin is unloaded */ + /* int (*)(void*); */ + STRUCT_FLD(deinit, i_s_common_deinit), + + /* plugin version (for SHOW PLUGINS) */ + /* unsigned int */ + STRUCT_FLD(version, INNODB_VERSION_SHORT), + + /* struct st_mysql_show_var* */ + STRUCT_FLD(status_vars, NULL), + + /* struct st_mysql_sys_var** */ + STRUCT_FLD(system_vars, NULL), + + /* reserved for dependency checking */ + /* void* */ + STRUCT_FLD(__reserved1, NULL) +}; + +/* Fields of the dynamic table information_schema.innodb_cmpmem. */ +static ST_FIELD_INFO i_s_cmpmem_fields_info[] = +{ + {STRUCT_FLD(field_name, "page_size"), + STRUCT_FLD(field_length, 5), + STRUCT_FLD(field_type, MYSQL_TYPE_LONG), + STRUCT_FLD(value, 0), + STRUCT_FLD(field_flags, 0), + STRUCT_FLD(old_name, "Buddy Block Size"), + STRUCT_FLD(open_method, SKIP_OPEN_TABLE)}, + + {STRUCT_FLD(field_name, "pages_used"), + STRUCT_FLD(field_length, MY_INT32_NUM_DECIMAL_DIGITS), + STRUCT_FLD(field_type, MYSQL_TYPE_LONG), + STRUCT_FLD(value, 0), + STRUCT_FLD(field_flags, 0), + STRUCT_FLD(old_name, "Currently in Use"), + STRUCT_FLD(open_method, SKIP_OPEN_TABLE)}, + + {STRUCT_FLD(field_name, "pages_free"), + STRUCT_FLD(field_length, MY_INT32_NUM_DECIMAL_DIGITS), + STRUCT_FLD(field_type, MYSQL_TYPE_LONG), + STRUCT_FLD(value, 0), + STRUCT_FLD(field_flags, 0), + STRUCT_FLD(old_name, "Currently Available"), + STRUCT_FLD(open_method, SKIP_OPEN_TABLE)}, + + {STRUCT_FLD(field_name, "relocation_ops"), + STRUCT_FLD(field_length, MY_INT64_NUM_DECIMAL_DIGITS), + STRUCT_FLD(field_type, MYSQL_TYPE_LONGLONG), + STRUCT_FLD(value, 0), + STRUCT_FLD(field_flags, 0), + STRUCT_FLD(old_name, "Total Number of Relocations"), + STRUCT_FLD(open_method, SKIP_OPEN_TABLE)}, + + {STRUCT_FLD(field_name, "relocation_time"), + STRUCT_FLD(field_length, MY_INT32_NUM_DECIMAL_DIGITS), + STRUCT_FLD(field_type, MYSQL_TYPE_LONG), + STRUCT_FLD(value, 0), + STRUCT_FLD(field_flags, 0), + STRUCT_FLD(old_name, "Total Duration of Relocations," + " in Seconds"), + STRUCT_FLD(open_method, SKIP_OPEN_TABLE)}, + + END_OF_ST_FIELD_INFO +}; + +/*********************************************************************** +Fill the dynamic table information_schema.innodb_cmpmem or +innodb_cmpmem_reset. */ +static +int +i_s_cmpmem_fill_low( +/*================*/ + /* out: 0 on success, 1 on failure */ + THD* thd, /* in: thread */ + TABLE_LIST* tables, /* in/out: tables to fill */ + COND* cond, /* in: condition (ignored) */ + ibool reset) /* in: TRUE=reset cumulated counts */ +{ + TABLE* table = (TABLE *) tables->table; + int status = 0; + + DBUG_ENTER("i_s_cmpmem_fill_low"); + + /* deny access to non-superusers */ + if (check_global_access(thd, PROCESS_ACL)) { + + DBUG_RETURN(0); + } + + RETURN_IF_INNODB_NOT_STARTED(tables->schema_table_name); + + //buf_pool_mutex_enter(); + mutex_enter(&zip_free_mutex); + + for (uint x = 0; x <= BUF_BUDDY_SIZES; x++) { + buf_buddy_stat_t* buddy_stat = &buf_buddy_stat[x]; + + table->field[0]->store(BUF_BUDDY_LOW << x); + table->field[1]->store(buddy_stat->used); + table->field[2]->store(UNIV_LIKELY(x < BUF_BUDDY_SIZES) + ? UT_LIST_GET_LEN(buf_pool->zip_free[x]) + : 0); + table->field[3]->store((longlong) buddy_stat->relocated, true); + table->field[4]->store( + (ulong) (buddy_stat->relocated_usec / 1000000)); + + if (reset) { + /* This is protected by buf_pool_mutex. */ + buddy_stat->relocated = 0; + buddy_stat->relocated_usec = 0; + } + + if (schema_table_store_record(thd, table)) { + status = 1; + break; + } + } + + //buf_pool_mutex_exit(); + mutex_exit(&zip_free_mutex); + DBUG_RETURN(status); +} + +/*********************************************************************** +Fill the dynamic table information_schema.innodb_cmpmem. */ +static +int +i_s_cmpmem_fill( +/*============*/ + /* out: 0 on success, 1 on failure */ + THD* thd, /* in: thread */ + TABLE_LIST* tables, /* in/out: tables to fill */ + COND* cond) /* in: condition (ignored) */ +{ + return(i_s_cmpmem_fill_low(thd, tables, cond, FALSE)); +} + +/*********************************************************************** +Fill the dynamic table information_schema.innodb_cmpmem_reset. */ +static +int +i_s_cmpmem_reset_fill( +/*==================*/ + /* out: 0 on success, 1 on failure */ + THD* thd, /* in: thread */ + TABLE_LIST* tables, /* in/out: tables to fill */ + COND* cond) /* in: condition (ignored) */ +{ + return(i_s_cmpmem_fill_low(thd, tables, cond, TRUE)); +} + +/*********************************************************************** +Bind the dynamic table information_schema.innodb_cmpmem. */ +static +int +i_s_cmpmem_init( +/*============*/ + /* out: 0 on success */ + void* p) /* in/out: table schema object */ +{ + DBUG_ENTER("i_s_cmpmem_init"); + ST_SCHEMA_TABLE* schema = (ST_SCHEMA_TABLE*) p; + + schema->fields_info = i_s_cmpmem_fields_info; + schema->fill_table = i_s_cmpmem_fill; + + DBUG_RETURN(0); +} + +/*********************************************************************** +Bind the dynamic table information_schema.innodb_cmpmem_reset. */ +static +int +i_s_cmpmem_reset_init( +/*==================*/ + /* out: 0 on success */ + void* p) /* in/out: table schema object */ +{ + DBUG_ENTER("i_s_cmpmem_reset_init"); + ST_SCHEMA_TABLE* schema = (ST_SCHEMA_TABLE*) p; + + schema->fields_info = i_s_cmpmem_fields_info; + schema->fill_table = i_s_cmpmem_reset_fill; + + DBUG_RETURN(0); +} + +UNIV_INTERN struct st_mysql_plugin i_s_innodb_cmpmem = +{ + /* the plugin type (a MYSQL_XXX_PLUGIN value) */ + /* int */ + STRUCT_FLD(type, MYSQL_INFORMATION_SCHEMA_PLUGIN), + + /* pointer to type-specific plugin descriptor */ + /* void* */ + STRUCT_FLD(info, &i_s_info), + + /* plugin name */ + /* const char* */ + STRUCT_FLD(name, "INNODB_CMPMEM"), + + /* plugin author (for SHOW PLUGINS) */ + /* const char* */ + STRUCT_FLD(author, plugin_author), + + /* general descriptive text (for SHOW PLUGINS) */ + /* const char* */ + STRUCT_FLD(descr, "Statistics for the InnoDB compressed buffer pool"), + + /* the plugin license (PLUGIN_LICENSE_XXX) */ + /* int */ + STRUCT_FLD(license, PLUGIN_LICENSE_GPL), + + /* the function to invoke when plugin is loaded */ + /* int (*)(void*); */ + STRUCT_FLD(init, i_s_cmpmem_init), + + /* the function to invoke when plugin is unloaded */ + /* int (*)(void*); */ + STRUCT_FLD(deinit, i_s_common_deinit), + + /* plugin version (for SHOW PLUGINS) */ + /* unsigned int */ + STRUCT_FLD(version, INNODB_VERSION_SHORT), + + /* struct st_mysql_show_var* */ + STRUCT_FLD(status_vars, NULL), + + /* struct st_mysql_sys_var** */ + STRUCT_FLD(system_vars, NULL), + + /* reserved for dependency checking */ + /* void* */ + STRUCT_FLD(__reserved1, NULL) +}; + +UNIV_INTERN struct st_mysql_plugin i_s_innodb_cmpmem_reset = +{ + /* the plugin type (a MYSQL_XXX_PLUGIN value) */ + /* int */ + STRUCT_FLD(type, MYSQL_INFORMATION_SCHEMA_PLUGIN), + + /* pointer to type-specific plugin descriptor */ + /* void* */ + STRUCT_FLD(info, &i_s_info), + + /* plugin name */ + /* const char* */ + STRUCT_FLD(name, "INNODB_CMPMEM_RESET"), + + /* plugin author (for SHOW PLUGINS) */ + /* const char* */ + STRUCT_FLD(author, plugin_author), + + /* general descriptive text (for SHOW PLUGINS) */ + /* const char* */ + STRUCT_FLD(descr, "Statistics for the InnoDB compressed buffer pool;" + " reset cumulated counts"), + + /* the plugin license (PLUGIN_LICENSE_XXX) */ + /* int */ + STRUCT_FLD(license, PLUGIN_LICENSE_GPL), + + /* the function to invoke when plugin is loaded */ + /* int (*)(void*); */ + STRUCT_FLD(init, i_s_cmpmem_reset_init), + + /* the function to invoke when plugin is unloaded */ + /* int (*)(void*); */ + STRUCT_FLD(deinit, i_s_common_deinit), + + /* plugin version (for SHOW PLUGINS) */ + /* unsigned int */ + STRUCT_FLD(version, INNODB_VERSION_SHORT), + + /* struct st_mysql_show_var* */ + STRUCT_FLD(status_vars, NULL), + + /* struct st_mysql_sys_var** */ + STRUCT_FLD(system_vars, NULL), + + /* reserved for dependency checking */ + /* void* */ + STRUCT_FLD(__reserved1, NULL) +}; + +/*********************************************************************** +Unbind a dynamic INFORMATION_SCHEMA table. */ +static +int +i_s_common_deinit( +/*==============*/ + /* out: 0 on success */ + void* p) /* in/out: table schema object */ +{ + DBUG_ENTER("i_s_common_deinit"); + + /* Do nothing */ + + DBUG_RETURN(0); +} + +/*********************************************************************** +*/ +static ST_FIELD_INFO i_s_innodb_rseg_fields_info[] = +{ + {STRUCT_FLD(field_name, "rseg_id"), + STRUCT_FLD(field_length, MY_INT64_NUM_DECIMAL_DIGITS), + STRUCT_FLD(field_type, MYSQL_TYPE_LONGLONG), + STRUCT_FLD(value, 0), + STRUCT_FLD(field_flags, MY_I_S_UNSIGNED), + STRUCT_FLD(old_name, ""), + STRUCT_FLD(open_method, SKIP_OPEN_TABLE)}, + + {STRUCT_FLD(field_name, "space_id"), + STRUCT_FLD(field_length, MY_INT64_NUM_DECIMAL_DIGITS), + STRUCT_FLD(field_type, MYSQL_TYPE_LONGLONG), + STRUCT_FLD(value, 0), + STRUCT_FLD(field_flags, MY_I_S_UNSIGNED), + STRUCT_FLD(old_name, ""), + STRUCT_FLD(open_method, SKIP_OPEN_TABLE)}, + + {STRUCT_FLD(field_name, "zip_size"), + STRUCT_FLD(field_length, MY_INT64_NUM_DECIMAL_DIGITS), + STRUCT_FLD(field_type, MYSQL_TYPE_LONGLONG), + STRUCT_FLD(value, 0), + STRUCT_FLD(field_flags, MY_I_S_UNSIGNED), + STRUCT_FLD(old_name, ""), + STRUCT_FLD(open_method, SKIP_OPEN_TABLE)}, + + {STRUCT_FLD(field_name, "page_no"), + STRUCT_FLD(field_length, MY_INT64_NUM_DECIMAL_DIGITS), + STRUCT_FLD(field_type, MYSQL_TYPE_LONGLONG), + STRUCT_FLD(value, 0), + STRUCT_FLD(field_flags, MY_I_S_UNSIGNED), + STRUCT_FLD(old_name, ""), + STRUCT_FLD(open_method, SKIP_OPEN_TABLE)}, + + {STRUCT_FLD(field_name, "max_size"), + STRUCT_FLD(field_length, MY_INT64_NUM_DECIMAL_DIGITS), + STRUCT_FLD(field_type, MYSQL_TYPE_LONGLONG), + STRUCT_FLD(value, 0), + STRUCT_FLD(field_flags, MY_I_S_UNSIGNED), + STRUCT_FLD(old_name, ""), + STRUCT_FLD(open_method, SKIP_OPEN_TABLE)}, + + {STRUCT_FLD(field_name, "curr_size"), + STRUCT_FLD(field_length, MY_INT64_NUM_DECIMAL_DIGITS), + STRUCT_FLD(field_type, MYSQL_TYPE_LONGLONG), + STRUCT_FLD(value, 0), + STRUCT_FLD(field_flags, MY_I_S_UNSIGNED), + STRUCT_FLD(old_name, ""), + STRUCT_FLD(open_method, SKIP_OPEN_TABLE)}, + + END_OF_ST_FIELD_INFO +}; + +static +int +i_s_innodb_rseg_fill( +/*=================*/ + THD* thd, /* in: thread */ + TABLE_LIST* tables, /* in/out: tables to fill */ + COND* cond) /* in: condition (ignored) */ +{ + TABLE* table = (TABLE *) tables->table; + int status = 0; + trx_rseg_t* rseg; + + DBUG_ENTER("i_s_innodb_rseg_fill"); + + /* deny access to non-superusers */ + if (check_global_access(thd, PROCESS_ACL)) { + + DBUG_RETURN(0); + } + + RETURN_IF_INNODB_NOT_STARTED(tables->schema_table_name); + + rseg = UT_LIST_GET_FIRST(trx_sys->rseg_list); + + while (rseg) { + table->field[0]->store(rseg->id); + table->field[1]->store(rseg->space); + table->field[2]->store(rseg->zip_size); + table->field[3]->store(rseg->page_no); + table->field[4]->store(rseg->max_size); + table->field[5]->store(rseg->curr_size); + + if (schema_table_store_record(thd, table)) { + status = 1; + break; + } + + rseg = UT_LIST_GET_NEXT(rseg_list, rseg); + } + + DBUG_RETURN(status); +} + +static +int +i_s_innodb_rseg_init( +/*=================*/ + /* out: 0 on success */ + void* p) /* in/out: table schema object */ +{ + DBUG_ENTER("i_s_innodb_rseg_init"); + ST_SCHEMA_TABLE* schema = (ST_SCHEMA_TABLE*) p; + + schema->fields_info = i_s_innodb_rseg_fields_info; + schema->fill_table = i_s_innodb_rseg_fill; + + DBUG_RETURN(0); +} + +UNIV_INTERN struct st_mysql_plugin i_s_innodb_rseg = +{ + /* the plugin type (a MYSQL_XXX_PLUGIN value) */ + /* int */ + STRUCT_FLD(type, MYSQL_INFORMATION_SCHEMA_PLUGIN), + + /* pointer to type-specific plugin descriptor */ + /* void* */ + STRUCT_FLD(info, &i_s_info), + + /* plugin name */ + /* const char* */ + STRUCT_FLD(name, "INNODB_RSEG"), + + /* plugin author (for SHOW PLUGINS) */ + /* const char* */ + STRUCT_FLD(author, plugin_author), + + /* general descriptive text (for SHOW PLUGINS) */ + /* const char* */ + STRUCT_FLD(descr, "InnoDB rollback segment information"), + + /* the plugin license (PLUGIN_LICENSE_XXX) */ + /* int */ + STRUCT_FLD(license, PLUGIN_LICENSE_GPL), + + /* the function to invoke when plugin is loaded */ + /* int (*)(void*); */ + STRUCT_FLD(init, i_s_innodb_rseg_init), + + /* the function to invoke when plugin is unloaded */ + /* int (*)(void*); */ + STRUCT_FLD(deinit, i_s_common_deinit), + + /* plugin version (for SHOW PLUGINS) */ + /* unsigned int */ + STRUCT_FLD(version, 0x0100 /* 1.0 */), + + /* struct st_mysql_show_var* */ + STRUCT_FLD(status_vars, NULL), + + /* struct st_mysql_sys_var** */ + STRUCT_FLD(system_vars, NULL), + + /* reserved for dependency checking */ + /* void* */ + STRUCT_FLD(__reserved1, NULL) +}; + +/*********************************************************************** +*/ +static ST_FIELD_INFO i_s_innodb_table_stats_info[] = +{ + {STRUCT_FLD(field_name, "table_name"), + STRUCT_FLD(field_length, NAME_LEN), + STRUCT_FLD(field_type, MYSQL_TYPE_STRING), + STRUCT_FLD(value, 0), + STRUCT_FLD(field_flags, 0), + STRUCT_FLD(old_name, ""), + STRUCT_FLD(open_method, SKIP_OPEN_TABLE)}, + + {STRUCT_FLD(field_name, "rows"), + STRUCT_FLD(field_length, MY_INT64_NUM_DECIMAL_DIGITS), + STRUCT_FLD(field_type, MYSQL_TYPE_LONGLONG), + STRUCT_FLD(value, 0), + STRUCT_FLD(field_flags, MY_I_S_UNSIGNED), + STRUCT_FLD(old_name, ""), + STRUCT_FLD(open_method, SKIP_OPEN_TABLE)}, + + {STRUCT_FLD(field_name, "clust_size"), + STRUCT_FLD(field_length, MY_INT64_NUM_DECIMAL_DIGITS), + STRUCT_FLD(field_type, MYSQL_TYPE_LONGLONG), + STRUCT_FLD(value, 0), + STRUCT_FLD(field_flags, MY_I_S_UNSIGNED), + STRUCT_FLD(old_name, ""), + STRUCT_FLD(open_method, SKIP_OPEN_TABLE)}, + + {STRUCT_FLD(field_name, "other_size"), + STRUCT_FLD(field_length, MY_INT64_NUM_DECIMAL_DIGITS), + STRUCT_FLD(field_type, MYSQL_TYPE_LONGLONG), + STRUCT_FLD(value, 0), + STRUCT_FLD(field_flags, MY_I_S_UNSIGNED), + STRUCT_FLD(old_name, ""), + STRUCT_FLD(open_method, SKIP_OPEN_TABLE)}, + + {STRUCT_FLD(field_name, "modified"), + STRUCT_FLD(field_length, MY_INT64_NUM_DECIMAL_DIGITS), + STRUCT_FLD(field_type, MYSQL_TYPE_LONGLONG), + STRUCT_FLD(value, 0), + STRUCT_FLD(field_flags, MY_I_S_UNSIGNED), + STRUCT_FLD(old_name, ""), + STRUCT_FLD(open_method, SKIP_OPEN_TABLE)}, + + END_OF_ST_FIELD_INFO +}; + +static ST_FIELD_INFO i_s_innodb_index_stats_info[] = +{ + {STRUCT_FLD(field_name, "table_name"), + STRUCT_FLD(field_length, NAME_LEN), + STRUCT_FLD(field_type, MYSQL_TYPE_STRING), + STRUCT_FLD(value, 0), + STRUCT_FLD(field_flags, 0), + STRUCT_FLD(old_name, ""), + STRUCT_FLD(open_method, SKIP_OPEN_TABLE)}, + + {STRUCT_FLD(field_name, "index_name"), + STRUCT_FLD(field_length, NAME_LEN), + STRUCT_FLD(field_type, MYSQL_TYPE_STRING), + STRUCT_FLD(value, 0), + STRUCT_FLD(field_flags, 0), + STRUCT_FLD(old_name, ""), + STRUCT_FLD(open_method, SKIP_OPEN_TABLE)}, + + {STRUCT_FLD(field_name, "fields"), + STRUCT_FLD(field_length, MY_INT64_NUM_DECIMAL_DIGITS), + STRUCT_FLD(field_type, MYSQL_TYPE_LONGLONG), + STRUCT_FLD(value, 0), + STRUCT_FLD(field_flags, MY_I_S_UNSIGNED), + STRUCT_FLD(old_name, ""), + STRUCT_FLD(open_method, SKIP_OPEN_TABLE)}, + + {STRUCT_FLD(field_name, "row_per_keys"), + STRUCT_FLD(field_length, 256), + STRUCT_FLD(field_type, MYSQL_TYPE_STRING), + STRUCT_FLD(value, 0), + STRUCT_FLD(field_flags, 0), + STRUCT_FLD(old_name, ""), + STRUCT_FLD(open_method, SKIP_OPEN_TABLE)}, + + {STRUCT_FLD(field_name, "index_size"), + STRUCT_FLD(field_length, MY_INT64_NUM_DECIMAL_DIGITS), + STRUCT_FLD(field_type, MYSQL_TYPE_LONGLONG), + STRUCT_FLD(value, 0), + STRUCT_FLD(field_flags, MY_I_S_UNSIGNED), + STRUCT_FLD(old_name, ""), + STRUCT_FLD(open_method, SKIP_OPEN_TABLE)}, + + {STRUCT_FLD(field_name, "leaf_pages"), + STRUCT_FLD(field_length, MY_INT64_NUM_DECIMAL_DIGITS), + STRUCT_FLD(field_type, MYSQL_TYPE_LONGLONG), + STRUCT_FLD(value, 0), + STRUCT_FLD(field_flags, MY_I_S_UNSIGNED), + STRUCT_FLD(old_name, ""), + STRUCT_FLD(open_method, SKIP_OPEN_TABLE)}, + + END_OF_ST_FIELD_INFO +}; + +static +int +i_s_innodb_table_stats_fill( +/*========================*/ + THD* thd, + TABLE_LIST* tables, + COND* cond) +{ + TABLE* i_s_table = (TABLE *) tables->table; + int status = 0; + dict_table_t* table; + + DBUG_ENTER("i_s_innodb_table_stats_fill"); + + /* deny access to non-superusers */ + if (check_global_access(thd, PROCESS_ACL)) { + DBUG_RETURN(0); + } + + mutex_enter(&(dict_sys->mutex)); + + table = UT_LIST_GET_FIRST(dict_sys->table_LRU); + + while (table) { + if (table->stat_clustered_index_size == 0) { + table = UT_LIST_GET_NEXT(table_LRU, table); + continue; + } + + field_store_string(i_s_table->field[0], table->name); + i_s_table->field[1]->store(table->stat_n_rows); + i_s_table->field[2]->store(table->stat_clustered_index_size); + i_s_table->field[3]->store(table->stat_sum_of_other_index_sizes); + i_s_table->field[4]->store(table->stat_modified_counter); + + if (schema_table_store_record(thd, i_s_table)) { + status = 1; + break; + } + + table = UT_LIST_GET_NEXT(table_LRU, table); + } + + mutex_exit(&(dict_sys->mutex)); + + DBUG_RETURN(status); +} + +static +int +i_s_innodb_index_stats_fill( +/*========================*/ + THD* thd, + TABLE_LIST* tables, + COND* cond) +{ + TABLE* i_s_table = (TABLE *) tables->table; + int status = 0; + dict_table_t* table; + dict_index_t* index; + + DBUG_ENTER("i_s_innodb_index_stats_fill"); + + /* deny access to non-superusers */ + if (check_global_access(thd, PROCESS_ACL)) { + DBUG_RETURN(0); + } + + mutex_enter(&(dict_sys->mutex)); + + table = UT_LIST_GET_FIRST(dict_sys->table_LRU); + + while (table) { + if (table->stat_clustered_index_size == 0) { + table = UT_LIST_GET_NEXT(table_LRU, table); + continue; + } + + ib_int64_t n_rows = table->stat_n_rows; + + if (n_rows < 0) { + n_rows = 0; + } + + index = dict_table_get_first_index(table); + + while (index) { + char buff[256+1]; + char row_per_keys[256+1]; + ulint i; + + field_store_string(i_s_table->field[0], table->name); + field_store_string(i_s_table->field[1], index->name); + i_s_table->field[2]->store(index->n_uniq); + + row_per_keys[0] = '\0'; + if (index->stat_n_diff_key_vals) { + for (i = 1; i <= index->n_uniq; i++) { + ib_int64_t rec_per_key; + if (index->stat_n_diff_key_vals[i]) { + rec_per_key = n_rows / index->stat_n_diff_key_vals[i]; + } else { + rec_per_key = n_rows; + } + snprintf(buff, 256, (i == index->n_uniq)?"%llu":"%llu, ", + rec_per_key); + strncat(row_per_keys, buff, 256 - strlen(row_per_keys)); + } + } + field_store_string(i_s_table->field[3], row_per_keys); + + i_s_table->field[4]->store(index->stat_index_size); + i_s_table->field[5]->store(index->stat_n_leaf_pages); + + if (schema_table_store_record(thd, i_s_table)) { + status = 1; + break; + } + + index = dict_table_get_next_index(index); + } + + if (status == 1) { + break; + } + + table = UT_LIST_GET_NEXT(table_LRU, table); + } + + mutex_exit(&(dict_sys->mutex)); + + DBUG_RETURN(status); +} + +static +int +i_s_innodb_table_stats_init( +/*========================*/ + void* p) +{ + DBUG_ENTER("i_s_innodb_table_stats_init"); + ST_SCHEMA_TABLE* schema = (ST_SCHEMA_TABLE*) p; + + schema->fields_info = i_s_innodb_table_stats_info; + schema->fill_table = i_s_innodb_table_stats_fill; + + DBUG_RETURN(0); +} + +static +int +i_s_innodb_index_stats_init( +/*========================*/ + void* p) +{ + DBUG_ENTER("i_s_innodb_index_stats_init"); + ST_SCHEMA_TABLE* schema = (ST_SCHEMA_TABLE*) p; + + schema->fields_info = i_s_innodb_index_stats_info; + schema->fill_table = i_s_innodb_index_stats_fill; + + DBUG_RETURN(0); +} + +UNIV_INTERN struct st_mysql_plugin i_s_innodb_table_stats = +{ + STRUCT_FLD(type, MYSQL_INFORMATION_SCHEMA_PLUGIN), + STRUCT_FLD(info, &i_s_info), + STRUCT_FLD(name, "INNODB_TABLE_STATS"), + STRUCT_FLD(author, plugin_author), + STRUCT_FLD(descr, "InnoDB table statistics in memory"), + STRUCT_FLD(license, PLUGIN_LICENSE_GPL), + STRUCT_FLD(init, i_s_innodb_table_stats_init), + STRUCT_FLD(deinit, i_s_common_deinit), + STRUCT_FLD(version, 0x0100 /* 1.0 */), + STRUCT_FLD(status_vars, NULL), + STRUCT_FLD(system_vars, NULL), + STRUCT_FLD(__reserved1, NULL) +}; + +UNIV_INTERN struct st_mysql_plugin i_s_innodb_index_stats = +{ + STRUCT_FLD(type, MYSQL_INFORMATION_SCHEMA_PLUGIN), + STRUCT_FLD(info, &i_s_info), + STRUCT_FLD(name, "INNODB_INDEX_STATS"), + STRUCT_FLD(author, plugin_author), + STRUCT_FLD(descr, "InnoDB index statistics in memory"), + STRUCT_FLD(license, PLUGIN_LICENSE_GPL), + STRUCT_FLD(init, i_s_innodb_index_stats_init), + STRUCT_FLD(deinit, i_s_common_deinit), + STRUCT_FLD(version, 0x0100 /* 1.0 */), + STRUCT_FLD(status_vars, NULL), + STRUCT_FLD(system_vars, NULL), + STRUCT_FLD(__reserved1, NULL) +}; diff --git a/storage/xtradb/handler/i_s.h b/storage/xtradb/handler/i_s.h new file mode 100644 index 00000000000..8601cc086ea --- /dev/null +++ b/storage/xtradb/handler/i_s.h @@ -0,0 +1,43 @@ +/***************************************************************************** + +Copyright (c) 2007, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/****************************************************** +InnoDB INFORMATION SCHEMA tables interface to MySQL. + +Created July 18, 2007 Vasil Dimov +*******************************************************/ + +#ifndef i_s_h +#define i_s_h + +extern struct st_mysql_plugin i_s_innodb_buffer_pool_pages; +extern struct st_mysql_plugin i_s_innodb_buffer_pool_pages_index; +extern struct st_mysql_plugin i_s_innodb_buffer_pool_pages_blob; +extern struct st_mysql_plugin i_s_innodb_trx; +extern struct st_mysql_plugin i_s_innodb_locks; +extern struct st_mysql_plugin i_s_innodb_lock_waits; +extern struct st_mysql_plugin i_s_innodb_cmp; +extern struct st_mysql_plugin i_s_innodb_cmp_reset; +extern struct st_mysql_plugin i_s_innodb_cmpmem; +extern struct st_mysql_plugin i_s_innodb_cmpmem_reset; +extern struct st_mysql_plugin i_s_innodb_patches; +extern struct st_mysql_plugin i_s_innodb_rseg; +extern struct st_mysql_plugin i_s_innodb_table_stats; +extern struct st_mysql_plugin i_s_innodb_index_stats; + +#endif /* i_s_h */ diff --git a/storage/xtradb/handler/innodb_patch_info.h b/storage/xtradb/handler/innodb_patch_info.h new file mode 100644 index 00000000000..770e74f39d1 --- /dev/null +++ b/storage/xtradb/handler/innodb_patch_info.h @@ -0,0 +1,42 @@ +/* Copyright (C) 2002-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 */ + +#ifdef USE_PRAGMA_INTERFACE +#pragma interface /* gcc class implementation */ +#endif + +struct innodb_enhancement { + const char *file; + const char *name; + const char *comment; + const char *link; +}innodb_enhancements[] = { +{"xtradb_show_enhancements","I_S.XTRADB_ENHANCEMENTS","","http://www.percona.com/docs/wiki/percona-xtradb"}, +{"innodb_show_status","Improvements to SHOW INNODB STATUS","Memory information and lock info fixes","http://www.percona.com/docs/wiki/percona-xtradb"}, +{"innodb_io","Improvements to InnoDB IO","","http://www.percona.com/docs/wiki/percona-xtradb"}, +{"innodb_opt_lru_count","Fix of buffer_pool mutex","Decreases contention on buffer_pool mutex on LRU operations","http://www.percona.com/docs/wiki/percona-xtradb"}, +{"innodb_buffer_pool_pages","Information of buffer pool content","","http://www.percona.com/docs/wiki/percona-xtradb"}, +{"innodb_expand_undo_slots","expandable maximum number of undo slots","from 1024 (default) to about 4000","http://www.percona.com/docs/wiki/percona-xtradb"}, +{"innodb_extra_rseg","allow to create extra rollback segments","When create new db, the new parameter allows to create more rollback segments","http://www.percona.com/docs/wiki/percona-xtradb"}, +{"innodb_overwrite_relay_log_info","overwrite relay-log.info when slave recovery","Building as plugin, it is not used.","http://www.percona.com/docs/wiki/percona-xtradb:innodb_overwrite_relay_log_info"}, +{"innodb_pause_in_spin","use 'pause' instruction during spin loop for x86 (gcc)","","http://www.percona.com/docs/wiki/percona-xtradb"}, +{"innodb_thread_concurrency_timer_based","use InnoDB timer based concurrency throttling (backport from MySQL 5.4.0)","",""}, +{"innodb_expand_import","convert .ibd file automatically when import tablespace","the files are generated by xtrabackup export mode.","http://www.percona.com/docs/wiki/percona-xtradb"}, +{"innodb_dict_size_limit","Limit dictionary cache size","Variable innodb_dict_size_limit in bytes","http://www.percona.com/docs/wiki/percona-xtradb"}, +{"innodb_split_buf_pool_mutex","More fix of buffer_pool mutex","Spliting buf_pool_mutex and optimizing based on innodb_opt_lru_count","http://www.percona.com/docs/wiki/percona-xtradb"}, +{"innodb_stats","Additional features about InnoDB statistics/optimizer","","http://www.percona.com/docs/wiki/percona-xtradb"}, +{"innodb_recovery_patches","Bugfixes and adjustments about recovery process","","http://www.percona.com/docs/wiki/percona-xtradb"}, +{NULL, NULL, NULL, NULL} +}; diff --git a/storage/xtradb/handler/mysql_addons.cc b/storage/xtradb/handler/mysql_addons.cc new file mode 100644 index 00000000000..a5d9c82c3e3 --- /dev/null +++ b/storage/xtradb/handler/mysql_addons.cc @@ -0,0 +1,41 @@ +/***************************************************************************** + +Copyright (c) 2007, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/****************************************************** +This file contains functions that need to be added to +MySQL code but have not been added yet. + +Whenever you add a function here submit a MySQL bug +report (feature request) with the implementation. Then +write the bug number in the comment before the +function in this file. + +When MySQL commits the function it can be deleted from +here. In a perfect world this file exists but is empty. + +Created November 07, 2007 Vasil Dimov +*******************************************************/ + +#ifndef MYSQL_SERVER +#define MYSQL_SERVER +#endif /* MYSQL_SERVER */ + +#include <mysql_priv.h> + +#include "mysql_addons.h" +#include "univ.i" diff --git a/storage/xtradb/handler/win_delay_loader.cc b/storage/xtradb/handler/win_delay_loader.cc new file mode 100644 index 00000000000..1572df42e30 --- /dev/null +++ b/storage/xtradb/handler/win_delay_loader.cc @@ -0,0 +1,1036 @@ +/***************************************************************************** + +Copyright (c) 2008, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/*********************************************************************** +This file contains functions that implement the delay loader on Windows. + +This is a customized version of delay loader with limited functionalities. +It does not support: + +* (manual) unloading +* multiple delay loaded DLLs +* multiple loading of the same DLL + +This delay loader is used only by the InnoDB plugin. Other components (DLLs) +can still use the default delay loader, provided by MSVC. + +Several acronyms used by Microsoft: + * IAT: import address table + * INT: import name table + * RVA: Relative Virtual Address + +See http://msdn.microsoft.com/en-us/magazine/bb985992.aspx for details of +PE format. +***********************************************************************/ +#if defined (__WIN__) && defined (MYSQL_DYNAMIC_PLUGIN) +# define WIN32_LEAN_AND_MEAN +# include <windows.h> +# include <delayimp.h> +# include <mysql_priv.h> + +extern "C" { +# include "univ.i" +# include "hash0hash.h" +} + +/*********************************************************************** +This following contains a list of externals that can not be resolved by +delay loading. They have to be resolved indirectly via their addresses +in the .map file. All of them are external variables. */ +CHARSET_INFO* wdl_my_charset_bin; +CHARSET_INFO* wdl_my_charset_latin1; +CHARSET_INFO* wdl_my_charset_filename; +CHARSET_INFO** wdl_system_charset_info; +CHARSET_INFO** wdl_default_charset_info; +CHARSET_INFO** wdl_all_charsets; +system_variables* wdl_global_system_variables; +char* wdl_mysql_real_data_home; +char** wdl_mysql_data_home; +char** wdl_tx_isolation_names; +char** wdl_binlog_format_names; +char* wdl_reg_ext; +pthread_mutex_t* wdl_LOCK_thread_count; +key_map* wdl_key_map_full; +MY_TMPDIR* wdl_mysql_tmpdir_list; +bool* wdl_mysqld_embedded; +uint* wdl_lower_case_table_names; +ulong* wdl_specialflag; +int* wdl_my_umask; + +/*********************************************************************** +The following is defined in ha_innodb.cc. It is used for copying the +system variables from the builtin innodb plugin to the dynamic plugin. +*/ +extern struct st_mysql_plugin* builtin_innobase_plugin_ptr; + +/*********************************************************************** +The preffered load-address defined in PE (portable executable format).*/ +#if defined(_M_IA64) +#pragma section(".base", long, read) +extern "C" +__declspec(allocate(".base")) +const IMAGE_DOS_HEADER __ImageBase; +#else +extern "C" +const IMAGE_DOS_HEADER __ImageBase; +#endif + +/*********************************************************************** +A template function for converting a relative address (RVA) to an +absolute address (VA). This is due to the pointers in the delay +descriptor (ImgDelayDescr in delayimp.h) have been changed from +VAs to RVAs to work on both 32- and 64-bit platforms. */ +template <class X> +X PFromRva(RVA rva) { + return X(PBYTE(&__ImageBase) + rva); +} + +/*********************************************************************** +Convert to the old format for convenience. The structure as well as its +element names follow the definition of ImgDelayDescr in delayimp.h. */ +struct InternalImgDelayDescr { + DWORD grAttrs; /* attributes */ + LPCSTR szName; /* pointer to dll name */ + HMODULE* phmod; /* address of module handle */ + PImgThunkData pIAT; /* address of the IAT */ + PCImgThunkData pINT; /* address of the INT */ + PCImgThunkData pBoundIAT; /* address of the optional bound IAT */ + PCImgThunkData pUnloadIAT; /* address of optional copy of + original IAT */ + DWORD dwTimeStamp; /* 0 if not bound, + otherwise date/time stamp of DLL + bound to (Old BIND) */ +}; + +typedef struct map_hash_chain_struct map_hash_chain_t; + +struct map_hash_chain_struct { + char* symbol; /* pointer to a symbol */ + ulint value; /* address of the symbol */ + map_hash_chain_t* next; /* pointer to the next cell + in the same folder. */ + map_hash_chain_t* chain; /* a linear chain used for + cleanup. */ +}; + +static HMODULE my_hmod = 0; +static struct hash_table_struct* m_htbl = NULL ; +static map_hash_chain_t* chain_header = NULL; +static ibool wdl_init = FALSE; +const ulint MAP_HASH_CELLS_NUM = 10000; + +#ifndef DBUG_OFF +/*********************************************************************** +In the dynamic plugin, it is required to call the following dbug functions +in the server: + _db_pargs_ + _db_doprnt_ + _db_enter_ + _db_return_ + _db_dump_ + +The plugin will get those function pointers during the initialization. +*/ +typedef void (__cdecl* pfn_db_enter_)( + const char* _func_, + const char* _file_, + uint _line_, + const char** _sfunc_, + const char** _sfile_, + uint* _slevel_, + char***); + +typedef void (__cdecl* pfn_db_return_)( + uint _line_, + const char** _sfunc_, + const char** _sfile_, + uint* _slevel_); + +typedef void (__cdecl* pfn_db_pargs_)( + uint _line_, + const char* keyword); + +typedef void (__cdecl* pfn_db_doprnt_)( + const char* format, + ...); + +typedef void (__cdecl* pfn_db_dump_)( + uint _line_, + const char* keyword, + const unsigned char* memory, + size_t length); + +static pfn_db_enter_ wdl_db_enter_; +static pfn_db_return_ wdl_db_return_; +static pfn_db_pargs_ wdl_db_pargs_; +static pfn_db_doprnt_ wdl_db_doprnt_; +static pfn_db_dump_ wdl_db_dump_; +#endif /* !DBUG_OFF */ + +/***************************************************************** +Creates a hash table with >= n array cells. The actual number of cells is +chosen to be a prime number slightly bigger than n. + +This is the same function as hash_create in hash0hash.c, except the +memory allocation. This function is invoked before the engine is +initialized, and buffer pools are not ready yet. */ +static +hash_table_t* +wdl_hash_create( +/*============*/ + /* out, own: created hash table */ + ulint n) /* in: number of array cells */ +{ + hash_cell_t* array; + ulint prime; + hash_table_t* table; + + prime = ut_find_prime(n); + + table = (hash_table_t*) malloc(sizeof(hash_table_t)); + if (table == NULL) { + return(NULL); + } + + array = (hash_cell_t*) malloc(sizeof(hash_cell_t) * prime); + if (array == NULL) { + free(table); + return(NULL); + } + + table->array = array; + table->n_cells = prime; + table->n_mutexes = 0; + table->mutexes = NULL; + table->heaps = NULL; + table->heap = NULL; + table->magic_n = HASH_TABLE_MAGIC_N; + + /* Initialize the cell array */ + hash_table_clear(table); + + return(table); +} + +/***************************************************************** +Frees a hash table. */ +static +void +wdl_hash_table_free( +/*================*/ + hash_table_t* table) /* in, own: hash table */ +{ + ut_a(table != NULL); + ut_a(table->mutexes == NULL); + + free(table->array); + free(table); +} + +/*********************************************************************** +Function for calculating the count of imports given the base of the IAT. */ +static +ulint +wdl_import_count( +/*=============*/ + /* out: number of imports */ + PCImgThunkData pitd_base) /* in: base of the IAT */ +{ + ulint ret = 0; + PCImgThunkData pitd = pitd_base; + + while (pitd->u1.Function) { + pitd++; + ret++; + } + + return(ret); +} + +/*********************************************************************** +Read Mapfile to a hashtable for faster access */ +static +ibool +wdl_load_mapfile( +/*=============*/ + /* out: TRUE if the mapfile is + loaded successfully. */ + const char* filename) /* in: name of the mapfile. */ +{ + FILE* fp; + const size_t nSize = 256; + char tmp_buf[nSize]; + char* func_name; + char* func_addr; + ulint load_addr = 0; + ibool valid_load_addr = FALSE; +#ifdef _WIN64 + const char* tmp_string = " Preferred load address is %16llx"; +#else + const char* tmp_string = " Preferred load address is %08x"; +#endif + + fp = fopen(filename, "r"); + if (fp == NULL) { + + return(FALSE); + } + + /* Check whether to create the hashtable */ + if (m_htbl == NULL) { + + m_htbl = wdl_hash_create(MAP_HASH_CELLS_NUM); + + if (m_htbl == NULL) { + + fclose(fp); + return(FALSE); + } + } + + /* Search start of symbol list and get the preferred load address */ + while (fgets(tmp_buf, sizeof(tmp_buf), fp)) { + + if (sscanf(tmp_buf, tmp_string, &load_addr) == 1) { + + valid_load_addr = TRUE; + } + + if (strstr(tmp_buf, "Rva+Base") != NULL) { + + break; + } + } + + if (valid_load_addr == FALSE) { + + /* No "Preferred load address", the map file is wrong. */ + fclose(fp); + return(FALSE); + } + + /* Read symbol list */ + while (fgets(tmp_buf, sizeof(tmp_buf), fp)) + { + map_hash_chain_t* map_cell; + ulint map_fold; + + if (*tmp_buf == 0) { + + continue; + } + + func_name = strtok(tmp_buf, " "); + func_name = strtok(NULL, " "); + func_addr = strtok(NULL, " "); + + if (func_name && func_addr) { + + ut_snprintf(tmp_buf, nSize, "0x%s", func_addr); + if (*func_name == '_') { + + func_name++; + } + + map_cell = (map_hash_chain_t*) + malloc(sizeof(map_hash_chain_t)); + if (map_cell == NULL) { + return(FALSE); + } + + /* Chain all cells together */ + map_cell->chain = chain_header; + chain_header = map_cell; + + map_cell->symbol = strdup(func_name); + map_cell->value = (ulint) strtoull(tmp_buf, NULL, 0) + - load_addr; + map_fold = ut_fold_string(map_cell->symbol); + + HASH_INSERT(map_hash_chain_t, + next, + m_htbl, + map_fold, + map_cell); + } + } + + fclose(fp); + + return(TRUE); +} + +/***************************************************************** +Cleanup.during DLL unload */ +static +void +wdl_cleanup(void) +/*=============*/ +{ + while (chain_header != NULL) { + map_hash_chain_t* tmp; + + tmp = chain_header->chain; + free(chain_header->symbol); + free(chain_header); + chain_header = tmp; + } + + if (m_htbl != NULL) { + + wdl_hash_table_free(m_htbl); + } +} + +/*********************************************************************** +Load the mapfile mysqld.map. */ +static +HMODULE +wdl_get_mysqld_mapfile(void) +/*========================*/ + /* out: the module handle */ +{ + char file_name[MAX_PATH]; + char* ext; + ulint err; + + if (my_hmod == 0) { + + size_t nSize = MAX_PATH - strlen(".map") -1; + + /* First find out the name of current executable */ + my_hmod = GetModuleHandle(NULL); + if (my_hmod == 0) { + + return(my_hmod); + } + + err = GetModuleFileName(my_hmod, file_name, nSize); + if (err == 0) { + + my_hmod = 0; + return(my_hmod); + } + + ext = strrchr(file_name, '.'); + if (ext != NULL) { + + *ext = 0; + strcat(file_name, ".map"); + + err = wdl_load_mapfile(file_name); + if (err == 0) { + + my_hmod = 0; + } + } else { + + my_hmod = 0; + } + } + + return(my_hmod); +} + +/*********************************************************************** +Retrieves the address of an exported function. It follows the convention +of GetProcAddress(). */ +static +FARPROC +wdl_get_procaddr_from_map( +/*======================*/ + /* out: address of exported + function. */ + HANDLE m_handle, /* in: module handle */ + const char* import_proc) /* in: procedure name */ +{ + map_hash_chain_t* hash_chain; + ulint map_fold; + + map_fold = ut_fold_string(import_proc); + HASH_SEARCH( + next, + m_htbl, + map_fold, + map_hash_chain_t*, + hash_chain, + , + (ut_strcmp(hash_chain->symbol, import_proc) == 0)); + + if (hash_chain == NULL) { + +#ifdef _WIN64 + /* On Win64, the leading '_' may not be taken out. In this + case, search again without the leading '_'. */ + if (*import_proc == '_') { + + import_proc++; + } + + map_fold = ut_fold_string(import_proc); + HASH_SEARCH( + next, + m_htbl, + map_fold, + map_hash_chain_t*, + hash_chain, + , + (ut_strcmp(hash_chain->symbol, import_proc) == 0)); + + if (hash_chain == NULL) { +#endif + if (wdl_init == TRUE) { + + sql_print_error( + "InnoDB: the procedure pointer of %s" + " is not found.", + import_proc); + } + + return(0); +#ifdef _WIN64 + } +#endif + } + + return((FARPROC) ((ulint) m_handle + hash_chain->value)); +} + +/*********************************************************************** +Retrieves the address of an exported variable. +Note: It does not follow the Windows call convention FARPROC. */ +static +void* +wdl_get_varaddr_from_map( +/*=====================*/ + /* out: address of exported + variable. */ + HANDLE m_handle, /* in: module handle */ + const char* import_variable) /* in: variable name */ +{ + map_hash_chain_t* hash_chain; + ulint map_fold; + + map_fold = ut_fold_string(import_variable); + HASH_SEARCH( + next, + m_htbl, + map_fold, + map_hash_chain_t*, + hash_chain, + , + (ut_strcmp(hash_chain->symbol, import_variable) == 0)); + + if (hash_chain == NULL) { + +#ifdef _WIN64 + /* On Win64, the leading '_' may not be taken out. In this + case, search again without the leading '_'. */ + if (*import_variable == '_') { + + import_variable++; + } + + map_fold = ut_fold_string(import_variable); + HASH_SEARCH( + next, + m_htbl, + map_fold, + map_hash_chain_t*, + hash_chain, + , + (ut_strcmp(hash_chain->symbol, import_variable) == 0)); + + if (hash_chain == NULL) { +#endif + if (wdl_init == TRUE) { + + sql_print_error( + "InnoDB: the variable address of %s" + " is not found.", + import_variable); + } + + return(0); +#ifdef _WIN64 + } +#endif + } + + return((void*) ((ulint) m_handle + hash_chain->value)); +} + +/*********************************************************************** +Bind all unresolved external variables from the MySQL executable. */ +static +bool +wdl_get_external_variables(void) +/*============================*/ + /* out: TRUE if successful */ +{ + HMODULE hmod = wdl_get_mysqld_mapfile(); + + if (hmod == 0) { + + return(FALSE); + } + +#define GET_SYM(sym, var, type) \ + var = (type*) wdl_get_varaddr_from_map(hmod, sym); \ + if (var == NULL) return(FALSE) +#ifdef _WIN64 +#define GET_SYM2(sym1, sym2, var, type) \ + var = (type*) wdl_get_varaddr_from_map(hmod, sym1); \ + if (var == NULL) return(FALSE) +#else +#define GET_SYM2(sym1, sym2, var, type) \ + var = (type*) wdl_get_varaddr_from_map(hmod, sym2); \ + if (var == NULL) return(FALSE) +#endif // (_WIN64) +#define GET_C_SYM(sym, type) GET_SYM(#sym, wdl_##sym, type) +#define GET_PROC_ADDR(sym) \ + wdl##sym = (pfn##sym) wdl_get_procaddr_from_map(hmod, #sym) + + GET_C_SYM(my_charset_bin, CHARSET_INFO); + GET_C_SYM(my_charset_latin1, CHARSET_INFO); + GET_C_SYM(my_charset_filename, CHARSET_INFO); + GET_C_SYM(default_charset_info, CHARSET_INFO*); + GET_C_SYM(all_charsets, CHARSET_INFO*); + GET_C_SYM(my_umask, int); + + GET_SYM("?global_system_variables@@3Usystem_variables@@A", + wdl_global_system_variables, struct system_variables); + GET_SYM("?mysql_real_data_home@@3PADA", + wdl_mysql_real_data_home, char); + GET_SYM("?reg_ext@@3PADA", wdl_reg_ext, char); + GET_SYM("?LOCK_thread_count@@3U_RTL_CRITICAL_SECTION@@A", + wdl_LOCK_thread_count, pthread_mutex_t); + GET_SYM("?key_map_full@@3V?$Bitmap@$0EA@@@A", + wdl_key_map_full, key_map); + GET_SYM("?mysql_tmpdir_list@@3Ust_my_tmpdir@@A", + wdl_mysql_tmpdir_list, MY_TMPDIR); + GET_SYM("?mysqld_embedded@@3_NA", + wdl_mysqld_embedded, bool); + GET_SYM("?lower_case_table_names@@3IA", + wdl_lower_case_table_names, uint); + GET_SYM("?specialflag@@3KA", wdl_specialflag, ulong); + + GET_SYM2("?system_charset_info@@3PEAUcharset_info_st@@EA", + "?system_charset_info@@3PAUcharset_info_st@@A", + wdl_system_charset_info, CHARSET_INFO*); + GET_SYM2("?mysql_data_home@@3PEADEA", + "?mysql_data_home@@3PADA", + wdl_mysql_data_home, char*); + GET_SYM2("?tx_isolation_names@@3PAPEBDA", + "?tx_isolation_names@@3PAPBDA", + wdl_tx_isolation_names, char*); + GET_SYM2("?binlog_format_names@@3PAPEBDA", + "?binlog_format_names@@3PAPBDA", + wdl_binlog_format_names, char*); + + /* It is fine if builtin_innobase_plugin is not available. */ + builtin_innobase_plugin_ptr = (struct st_mysql_plugin*) + wdl_get_varaddr_from_map( + hmod, + "?builtin_innobase_plugin@@3PAUst_mysql_plugin@@A"); + +#ifndef DBUG_OFF + GET_PROC_ADDR(_db_enter_); + GET_PROC_ADDR(_db_return_); + GET_PROC_ADDR(_db_pargs_); + GET_PROC_ADDR(_db_doprnt_); + GET_PROC_ADDR(_db_dump_); + + /* If any of the dbug functions is not available, just make them + all invalid. This is the case when working with a non-debug + version of the server. */ + if (wdl_db_enter_ == NULL || wdl_db_return_ == NULL + || wdl_db_pargs_ == NULL || wdl_db_doprnt_ == NULL + || wdl_db_dump_ == NULL) { + + wdl_db_enter_ = NULL; + wdl_db_return_ = NULL; + wdl_db_pargs_ = NULL; + wdl_db_doprnt_ = NULL; + wdl_db_dump_ = NULL; + } +#endif /* !DBUG_OFF */ + + wdl_init = TRUE; + return(TRUE); + +#undef GET_SYM +#undef GET_SYM2 +#undef GET_C_SYM +#undef GET_PROC_ADDR +} + +/*********************************************************************** +The DLL Delayed Loading Helper Function for resolving externals. + +The function may fail due to one of the three reasons: + +* Invalid parameter, which happens if the attributes in pidd aren't + specified correctly. +* Failed to load the map file mysqld.map. +* Failed to find an external name in the map file mysqld.map. + +Note: this function is called by run-time as well as __HrLoadAllImportsForDll. +So, it has to follow Windows call convention. */ +extern "C" +FARPROC WINAPI +__delayLoadHelper2( +/*===============*/ + /* out: the address of the imported + function*/ + PCImgDelayDescr pidd, /* in: a const pointer to a + ImgDelayDescr, see delayimp.h. */ + FARPROC* iat_entry) /* in/out: A pointer to the slot in + the delay load import address table + to be updated with the address of the + imported function. */ +{ + ulint iIAT, iINT; + HMODULE hmod; + PCImgThunkData pitd; + FARPROC fun = NULL; + + /* Set up data used for the hook procs */ + InternalImgDelayDescr idd = { + pidd->grAttrs, + PFromRva<LPCSTR>(pidd->rvaDLLName), + PFromRva<HMODULE*>(pidd->rvaHmod), + PFromRva<PImgThunkData>(pidd->rvaIAT), + PFromRva<PCImgThunkData>(pidd->rvaINT), + PFromRva<PCImgThunkData>(pidd->rvaBoundIAT), + PFromRva<PCImgThunkData>(pidd->rvaUnloadIAT), + pidd->dwTimeStamp + }; + + DelayLoadInfo dli = { + sizeof(DelayLoadInfo), + pidd, + iat_entry, + idd.szName, + {0}, + 0, + 0, + 0 + }; + + /* Check the Delay Load Attributes, log an error of invalid + parameter, which happens if the attributes in pidd are not + specified correctly. */ + if ((idd.grAttrs & dlattrRva) == 0) { + + sql_print_error("InnoDB: invalid parameter for delay loader."); + return(0); + } + + hmod = *idd.phmod; + + /* Calculate the index for the IAT entry in the import address table. + The INT entries are ordered the same as the IAT entries so the + calculation can be done on the IAT side. */ + iIAT = (PCImgThunkData) iat_entry - idd.pIAT; + iINT = iIAT; + + pitd = &(idd.pINT[iINT]); + + dli.dlp.fImportByName = !IMAGE_SNAP_BY_ORDINAL(pitd->u1.Ordinal); + + if (dli.dlp.fImportByName) { + + dli.dlp.szProcName = (LPCSTR) (PFromRva<PIMAGE_IMPORT_BY_NAME> + ((RVA) ((UINT_PTR) pitd->u1.AddressOfData))->Name); + } else { + + dli.dlp.dwOrdinal = (ulint) IMAGE_ORDINAL(pitd->u1.Ordinal); + } + + /* Now, load the mapfile, if it has not been done yet */ + if (hmod == 0) { + + hmod = wdl_get_mysqld_mapfile(); + } + + if (hmod == 0) { + /* LoadLibrary failed. */ + PDelayLoadInfo rgpdli[1] = {&dli}; + + dli.dwLastError = ::GetLastError(); + + sql_print_error( + "InnoDB: failed to load mysqld.map with error %d.", + dli.dwLastError); + + return(0); + } + + /* Store the library handle. */ + idd.phmod = &hmod; + + /* Go for the procedure now. */ + dli.hmodCur = hmod; + + if (pidd->rvaBoundIAT && pidd->dwTimeStamp) { + + /* Bound imports exist, check the timestamp from the target + image */ + PIMAGE_NT_HEADERS pinh; + + pinh = (PIMAGE_NT_HEADERS) ((byte*) hmod + + ((PIMAGE_DOS_HEADER) hmod)->e_lfanew); + + if (pinh->Signature == IMAGE_NT_SIGNATURE + && pinh->FileHeader.TimeDateStamp == idd.dwTimeStamp + && (DWORD) hmod == pinh->OptionalHeader.ImageBase) { + + /* We have a decent address in the bound IAT. */ + fun = (FARPROC) (UINT_PTR) + idd.pBoundIAT[iIAT].u1.Function; + + if (fun) { + + *iat_entry = fun; + return(fun); + } + } + } + + fun = wdl_get_procaddr_from_map(hmod, dli.dlp.szProcName); + + if (fun == 0) { + + return(0); + } + + *iat_entry = fun; + return(fun); +} + +/*********************************************************************** +Unload a DLL that was delay loaded. This function is called by run-time. */ +extern "C" +BOOL WINAPI +__FUnloadDelayLoadedDLL2( +/*=====================*/ + /* out: TRUE is returned if the DLL is found + and the IAT matches the original one. */ + LPCSTR module_name) /* in: DLL name */ +{ + return(TRUE); +} + +/****************************************************************** +Load all imports from a DLL that was specified with the /delayload linker +option. +Note: this function is called by run-time. So, it has to follow Windows call +convention. */ +extern "C" +HRESULT WINAPI +__HrLoadAllImportsForDll( +/*=====================*/ + /* out: S_OK if the DLL matches, otherwise + ERROR_MOD_NOT_FOUND is returned. */ + LPCSTR module_name) /* in: DLL name */ +{ + PIMAGE_NT_HEADERS img; + PCImgDelayDescr pidd; + IMAGE_DATA_DIRECTORY* image_data; + LPCSTR current_module; + HRESULT ret = ERROR_MOD_NOT_FOUND; + HMODULE hmod = (HMODULE) &__ImageBase; + + img = (PIMAGE_NT_HEADERS) ((byte*) hmod + + ((PIMAGE_DOS_HEADER) hmod)->e_lfanew); + image_data = + &img->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT]; + + /* Scan the delay load IAT/INT for the DLL */ + if (image_data->Size) { + + pidd = PFromRva<PCImgDelayDescr>(image_data->VirtualAddress); + + /* Check all of the listed DLLs we want to load. */ + while (pidd->rvaDLLName) { + + current_module = PFromRva<LPCSTR>(pidd->rvaDLLName); + + if (stricmp(module_name, current_module) == 0) { + + /* Found it, break out with pidd and + current_module set appropriately */ + break; + } + + /* To the next delay import descriptor */ + pidd++; + } + + if (pidd->rvaDLLName) { + + /* Found a matching DLL, now process it. */ + FARPROC* iat_entry; + size_t count; + + iat_entry = PFromRva<FARPROC*>(pidd->rvaIAT); + count = wdl_import_count((PCImgThunkData) iat_entry); + + /* now load all the imports from the DLL */ + while (count > 0) { + + /* No need to check the return value */ + __delayLoadHelper2(pidd, iat_entry); + iat_entry++; + count--; + } + + ret = S_OK; + } + } + + return ret; +} + +/****************************************************************** +The main function of a DLL */ +BOOL +WINAPI +DllMain( +/*====*/ + /* out: TRUE if the call succeeds */ + HINSTANCE hinstDLL, /* in: handle to the DLL module */ + DWORD fdwReason, /* Reason code that indicates why the + DLL entry-point function is being + called.*/ + LPVOID lpvReserved) /* in: additional parameter based on + fdwReason */ +{ + BOOL success = TRUE; + + switch (fdwReason) { + + case DLL_PROCESS_ATTACH: + success = wdl_get_external_variables(); + break; + + case DLL_PROCESS_DETACH: + wdl_cleanup(); + break; + } + + return(success); +} + +#ifndef DBUG_OFF +/****************************************************************** +Process entry point to user function. It makes the call to _db_enter_ +in mysqld.exe. The DBUG functions are defined in my_dbug.h. */ +extern "C" UNIV_INTERN +void +_db_enter_( + const char* _func_, /* in: current function name */ + const char* _file_, /* in: current file name */ + uint _line_, /* in: current source line number */ + const char** _sfunc_, /* out: previous _func_ */ + const char** _sfile_, /* out: previous _file_ */ + uint* _slevel_, /* out: previous nesting level */ + char*** _sframep_) /* out: previous frame pointer */ +{ + if (wdl_db_enter_ != NULL) { + + wdl_db_enter_(_func_, _file_, _line_, _sfunc_, _sfile_, + _slevel_, _sframep_); + } +} + +/****************************************************************** +Process exit from user function. It makes the call to _db_return_() +in the server. */ +extern "C" UNIV_INTERN +void +_db_return_( + uint _line_, /* in: current source line number */ + const char** _sfunc_, /* out: previous _func_ */ + const char** _sfile_, /* out: previous _file_ */ + uint* _slevel_) /* out: previous level */ +{ + if (wdl_db_return_ != NULL) { + + wdl_db_return_(_line_, _sfunc_, _sfile_, _slevel_); + } +} + +/****************************************************************** +Log arguments for subsequent use. It makes the call to _db_pargs_() +in the server. */ +extern "C" UNIV_INTERN +void +_db_pargs_( + uint _line_, /* in: current source line number */ + const char* keyword) /* in: keyword for current macro */ +{ + if (wdl_db_pargs_ != NULL) { + + wdl_db_pargs_(_line_, keyword); + } +} + +/****************************************************************** +Handle print of debug lines. It saves the text into a buffer first, +then makes the call to _db_doprnt_() in the server. The text is +truncated to the size of buffer. */ +extern "C" UNIV_INTERN +void +_db_doprnt_( + const char* format, /* in: the format string */ + ...) /* in: list of arguments */ +{ + va_list argp; + char buffer[512]; + + if (wdl_db_doprnt_ != NULL) { + + va_start(argp, format); + /* it is ok to ignore the trunction. */ + _vsnprintf(buffer, sizeof(buffer), format, argp); + wdl_db_doprnt_(buffer); + va_end(argp); + } +} + +/****************************************************************** +Dump a string in hex. It makes the call to _db_dump_() in the server. */ +extern "C" UNIV_INTERN +void +_db_dump_( + uint _line_, /* in: current source line + number */ + const char* keyword, /* in: keyword list */ + const unsigned char* memory, /* in: memory to dump */ + size_t length) /* in: bytes to dump */ +{ + if (wdl_db_dump_ != NULL) { + + wdl_db_dump_(_line_, keyword, memory, length); + } +} + +#endif /* !DBUG_OFF */ +#endif /* defined (__WIN__) && defined (MYSQL_DYNAMIC_PLUGIN) */ diff --git a/storage/xtradb/ibuf/ibuf0ibuf.c b/storage/xtradb/ibuf/ibuf0ibuf.c new file mode 100644 index 00000000000..5aacd9ae717 --- /dev/null +++ b/storage/xtradb/ibuf/ibuf0ibuf.c @@ -0,0 +1,3594 @@ +/***************************************************************************** + +Copyright (c) 1997, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/****************************************************** +Insert buffer + +Created 7/19/1997 Heikki Tuuri +*******************************************************/ + +#include "ibuf0ibuf.h" + +#ifdef UNIV_NONINL +#include "ibuf0ibuf.ic" +#endif + +#include "buf0buf.h" +#include "buf0rea.h" +#include "fsp0fsp.h" +#include "trx0sys.h" +#include "fil0fil.h" +#include "thr0loc.h" +#include "rem0rec.h" +#include "btr0cur.h" +#include "btr0pcur.h" +#include "btr0btr.h" +#include "sync0sync.h" +#include "dict0boot.h" +#include "fut0lst.h" +#include "lock0lock.h" +#include "log0recv.h" +#include "que0que.h" + +/* STRUCTURE OF AN INSERT BUFFER RECORD + +In versions < 4.1.x: + +1. The first field is the page number. +2. The second field is an array which stores type info for each subsequent + field. We store the information which affects the ordering of records, and + also the physical storage size of an SQL NULL value. E.g., for CHAR(10) it + is 10 bytes. +3. Next we have the fields of the actual index record. + +In versions >= 4.1.x: + +Note that contary to what we planned in the 1990's, there will only be one +insert buffer tree, and that is in the system tablespace of InnoDB. + +1. The first field is the space id. +2. The second field is a one-byte marker (0) which differentiates records from + the < 4.1.x storage format. +3. The third field is the page number. +4. The fourth field contains the type info, where we have also added 2 bytes to + store the charset. In the compressed table format of 5.0.x we must add more + information here so that we can build a dummy 'index' struct which 5.0.x + can use in the binary search on the index page in the ibuf merge phase. +5. The rest of the fields contain the fields of the actual index record. + +In versions >= 5.0.3: + +The first byte of the fourth field is an additional marker (0) if the record +is in the compact format. The presence of this marker can be detected by +looking at the length of the field modulo DATA_NEW_ORDER_NULL_TYPE_BUF_SIZE. + +The high-order bit of the character set field in the type info is the +"nullable" flag for the field. */ + + +/* PREVENTING DEADLOCKS IN THE INSERT BUFFER SYSTEM + +If an OS thread performs any operation that brings in disk pages from +non-system tablespaces into the buffer pool, or creates such a page there, +then the operation may have as a side effect an insert buffer index tree +compression. Thus, the tree latch of the insert buffer tree may be acquired +in the x-mode, and also the file space latch of the system tablespace may +be acquired in the x-mode. + +Also, an insert to an index in a non-system tablespace can have the same +effect. How do we know this cannot lead to a deadlock of OS threads? There +is a problem with the i\o-handler threads: they break the latching order +because they own x-latches to pages which are on a lower level than the +insert buffer tree latch, its page latches, and the tablespace latch an +insert buffer operation can reserve. + +The solution is the following: Let all the tree and page latches connected +with the insert buffer be later in the latching order than the fsp latch and +fsp page latches. + +Insert buffer pages must be such that the insert buffer is never invoked +when these pages are accessed as this would result in a recursion violating +the latching order. We let a special i/o-handler thread take care of i/o to +the insert buffer pages and the ibuf bitmap pages, as well as the fsp bitmap +pages and the first inode page, which contains the inode of the ibuf tree: let +us call all these ibuf pages. To prevent deadlocks, we do not let a read-ahead +access both non-ibuf and ibuf pages. + +Then an i/o-handler for the insert buffer never needs to access recursively the +insert buffer tree and thus obeys the latching order. On the other hand, other +i/o-handlers for other tablespaces may require access to the insert buffer, +but because all kinds of latches they need to access there are later in the +latching order, no violation of the latching order occurs in this case, +either. + +A problem is how to grow and contract an insert buffer tree. As it is later +in the latching order than the fsp management, we have to reserve the fsp +latch first, before adding or removing pages from the insert buffer tree. +We let the insert buffer tree have its own file space management: a free +list of pages linked to the tree root. To prevent recursive using of the +insert buffer when adding pages to the tree, we must first load these pages +to memory, obtaining a latch on them, and only after that add them to the +free list of the insert buffer tree. More difficult is removing of pages +from the free list. If there is an excess of pages in the free list of the +ibuf tree, they might be needed if some thread reserves the fsp latch, +intending to allocate more file space. So we do the following: if a thread +reserves the fsp latch, we check the writer count field of the latch. If +this field has value 1, it means that the thread did not own the latch +before entering the fsp system, and the mtr of the thread contains no +modifications to the fsp pages. Now we are free to reserve the ibuf latch, +and check if there is an excess of pages in the free list. We can then, in a +separate mini-transaction, take them out of the free list and free them to +the fsp system. + +To avoid deadlocks in the ibuf system, we divide file pages into three levels: + +(1) non-ibuf pages, +(2) ibuf tree pages and the pages in the ibuf tree free list, and +(3) ibuf bitmap pages. + +No OS thread is allowed to access higher level pages if it has latches to +lower level pages; even if the thread owns a B-tree latch it must not access +the B-tree non-leaf pages if it has latches on lower level pages. Read-ahead +is only allowed for level 1 and 2 pages. Dedicated i/o-handler threads handle +exclusively level 1 i/o. A dedicated i/o handler thread handles exclusively +level 2 i/o. However, if an OS thread does the i/o handling for itself, i.e., +it uses synchronous aio, it can access any pages, as long as it obeys the +access order rules. */ + +/* Buffer pool size per the maximum insert buffer size */ +#define IBUF_POOL_SIZE_PER_MAX_SIZE 2 + +/* Table name for the insert buffer. */ +#define IBUF_TABLE_NAME "SYS_IBUF_TABLE" + +/** Operations that can currently be buffered. */ +UNIV_INTERN ibuf_use_t ibuf_use = IBUF_USE_INSERT; + +/** The insert buffer control structure */ +UNIV_INTERN ibuf_t* ibuf = NULL; + +UNIV_INTERN ulint ibuf_flush_count = 0; + +#ifdef UNIV_IBUF_COUNT_DEBUG +/* Dimensions for the ibuf_count array */ +#define IBUF_COUNT_N_SPACES 4 +#define IBUF_COUNT_N_PAGES 130000 + +/* Buffered entry counts for file pages, used in debugging */ +static ulint ibuf_counts[IBUF_COUNT_N_SPACES][IBUF_COUNT_N_PAGES]; + +/********************************************************************** +Checks that the indexes to ibuf_counts[][] are within limits. */ +UNIV_INLINE +void +ibuf_count_check( +/*=============*/ + ulint space_id, /* in: space identifier */ + ulint page_no) /* in: page number */ +{ + if (space_id < IBUF_COUNT_N_SPACES && page_no < IBUF_COUNT_N_PAGES) { + return; + } + + fprintf(stderr, + "InnoDB: UNIV_IBUF_COUNT_DEBUG limits space_id and page_no\n" + "InnoDB: and breaks crash recovery.\n" + "InnoDB: space_id=%lu, should be 0<=space_id<%lu\n" + "InnoDB: page_no=%lu, should be 0<=page_no<%lu\n", + (ulint) space_id, (ulint) IBUF_COUNT_N_SPACES, + (ulint) page_no, (ulint) IBUF_COUNT_N_PAGES); + ut_error; +} +#endif + +/* The start address for an insert buffer bitmap page bitmap */ +#define IBUF_BITMAP PAGE_DATA + +/* Offsets in bits for the bits describing a single page in the bitmap */ +#define IBUF_BITMAP_FREE 0 +#define IBUF_BITMAP_BUFFERED 2 +#define IBUF_BITMAP_IBUF 3 /* TRUE if page is a part of the ibuf + tree, excluding the root page, or is + in the free list of the ibuf */ + +/* Number of bits describing a single page */ +#define IBUF_BITS_PER_PAGE 4 +#if IBUF_BITS_PER_PAGE % 2 +# error "IBUF_BITS_PER_PAGE must be an even number!" +#endif + +/* The mutex used to block pessimistic inserts to ibuf trees */ +static mutex_t ibuf_pessimistic_insert_mutex; + +/* The mutex protecting the insert buffer structs */ +static mutex_t ibuf_mutex; + +/* The mutex protecting the insert buffer bitmaps */ +static mutex_t ibuf_bitmap_mutex; + +/* The area in pages from which contract looks for page numbers for merge */ +#define IBUF_MERGE_AREA 8 + +/* Inside the merge area, pages which have at most 1 per this number less +buffered entries compared to maximum volume that can buffered for a single +page are merged along with the page whose buffer became full */ +#define IBUF_MERGE_THRESHOLD 4 + +/* In ibuf_contract at most this number of pages is read to memory in one +batch, in order to merge the entries for them in the insert buffer */ +#define IBUF_MAX_N_PAGES_MERGED IBUF_MERGE_AREA + +/* If the combined size of the ibuf trees exceeds ibuf->max_size by this +many pages, we start to contract it in connection to inserts there, using +non-synchronous contract */ +#define IBUF_CONTRACT_ON_INSERT_NON_SYNC 0 + +/* Same as above, but use synchronous contract */ +#define IBUF_CONTRACT_ON_INSERT_SYNC 5 + +/* Same as above, but no insert is done, only contract is called */ +#define IBUF_CONTRACT_DO_NOT_INSERT 10 + +/* TODO: how to cope with drop table if there are records in the insert +buffer for the indexes of the table? Is there actually any problem, +because ibuf merge is done to a page when it is read in, and it is +still physically like the index page even if the index would have been +dropped! So, there seems to be no problem. */ + +/********************************************************************** +Sets the flag in the current OS thread local storage denoting that it is +inside an insert buffer routine. */ +UNIV_INLINE +void +ibuf_enter(void) +/*============*/ +{ + ibool* ptr; + + ptr = thr_local_get_in_ibuf_field(); + + ut_ad(*ptr == FALSE); + + *ptr = TRUE; +} + +/********************************************************************** +Sets the flag in the current OS thread local storage denoting that it is +exiting an insert buffer routine. */ +UNIV_INLINE +void +ibuf_exit(void) +/*===========*/ +{ + ibool* ptr; + + ptr = thr_local_get_in_ibuf_field(); + + ut_ad(*ptr == TRUE); + + *ptr = FALSE; +} + +/********************************************************************** +Returns TRUE if the current OS thread is performing an insert buffer +routine. */ +UNIV_INTERN +ibool +ibuf_inside(void) +/*=============*/ + /* out: TRUE if inside an insert buffer routine: for instance, + a read-ahead of non-ibuf pages is then forbidden */ +{ + return(*thr_local_get_in_ibuf_field()); +} + +/********************************************************************** +Gets the ibuf header page and x-latches it. */ +static +page_t* +ibuf_header_page_get( +/*=================*/ + /* out: insert buffer header page */ + mtr_t* mtr) /* in: mtr */ +{ + buf_block_t* block; + + ut_ad(!ibuf_inside()); + + block = buf_page_get( + IBUF_SPACE_ID, 0, FSP_IBUF_HEADER_PAGE_NO, RW_X_LATCH, mtr); + buf_block_dbg_add_level(block, SYNC_IBUF_HEADER); + + return(buf_block_get_frame(block)); +} + +/********************************************************************** +Gets the root page and x-latches it. */ +static +page_t* +ibuf_tree_root_get( +/*===============*/ + /* out: insert buffer tree root page */ + mtr_t* mtr) /* in: mtr */ +{ + buf_block_t* block; + + ut_ad(ibuf_inside()); + + mtr_x_lock(dict_index_get_lock(ibuf->index), mtr); + + block = buf_page_get( + IBUF_SPACE_ID, 0, FSP_IBUF_TREE_ROOT_PAGE_NO, RW_X_LATCH, mtr); + + buf_block_dbg_add_level(block, SYNC_TREE_NODE); + + return(buf_block_get_frame(block)); +} + +#ifdef UNIV_IBUF_COUNT_DEBUG +/********************************************************************** +Gets the ibuf count for a given page. */ +UNIV_INTERN +ulint +ibuf_count_get( +/*===========*/ + /* out: number of entries in the insert buffer + currently buffered for this page */ + ulint space, /* in: space id */ + ulint page_no)/* in: page number */ +{ + ibuf_count_check(space, page_no); + + return(ibuf_counts[space][page_no]); +} + +/********************************************************************** +Sets the ibuf count for a given page. */ +static +void +ibuf_count_set( +/*===========*/ + ulint space, /* in: space id */ + ulint page_no,/* in: page number */ + ulint val) /* in: value to set */ +{ + ibuf_count_check(space, page_no); + ut_a(val < UNIV_PAGE_SIZE); + + ibuf_counts[space][page_no] = val; +} +#endif + +/********************************************************************** +Updates the size information of the ibuf, assuming the segment size has not +changed. */ +static +void +ibuf_size_update( +/*=============*/ + const page_t* root, /* in: ibuf tree root */ + mtr_t* mtr) /* in: mtr */ +{ + ut_ad(mutex_own(&ibuf_mutex)); + + ibuf->free_list_len = flst_get_len(root + PAGE_HEADER + + PAGE_BTR_IBUF_FREE_LIST, mtr); + + ibuf->height = 1 + btr_page_get_level(root, mtr); + + /* the '1 +' is the ibuf header page */ + ibuf->size = ibuf->seg_size - (1 + ibuf->free_list_len); + + ibuf->empty = page_get_n_recs(root) == 0; +} + +/********************************************************************** +Creates the insert buffer data structure at a database startup and initializes +the data structures for the insert buffer. */ +UNIV_INTERN +void +ibuf_init_at_db_start(void) +/*=======================*/ +{ + page_t* root; + mtr_t mtr; + dict_table_t* table; + mem_heap_t* heap; + dict_index_t* index; + ulint n_used; + page_t* header_page; + ulint error; + + ibuf = mem_alloc(sizeof(ibuf_t)); + + memset(ibuf, 0, sizeof(*ibuf)); + + /* Note that also a pessimistic delete can sometimes make a B-tree + grow in size, as the references on the upper levels of the tree can + change */ + + ibuf->max_size = ut_min( buf_pool_get_curr_size() / UNIV_PAGE_SIZE + / IBUF_POOL_SIZE_PER_MAX_SIZE, (ulint) srv_ibuf_max_size / UNIV_PAGE_SIZE); + + srv_ibuf_max_size = (long long) ibuf->max_size * UNIV_PAGE_SIZE; + + mutex_create(&ibuf_pessimistic_insert_mutex, + SYNC_IBUF_PESS_INSERT_MUTEX); + + mutex_create(&ibuf_mutex, SYNC_IBUF_MUTEX); + + mutex_create(&ibuf_bitmap_mutex, SYNC_IBUF_BITMAP_MUTEX); + + mtr_start(&mtr); + + mutex_enter(&ibuf_mutex); + + mtr_x_lock(fil_space_get_latch(IBUF_SPACE_ID, NULL), &mtr); + + header_page = ibuf_header_page_get(&mtr); + + fseg_n_reserved_pages(header_page + IBUF_HEADER + IBUF_TREE_SEG_HEADER, + &n_used, &mtr); + ibuf_enter(); + + ut_ad(n_used >= 2); + + ibuf->seg_size = n_used; + + { + buf_block_t* block; + + block = buf_page_get( + IBUF_SPACE_ID, 0, FSP_IBUF_TREE_ROOT_PAGE_NO, + RW_X_LATCH, &mtr); + buf_block_dbg_add_level(block, SYNC_TREE_NODE); + + root = buf_block_get_frame(block); + } + + ibuf_size_update(root, &mtr); + mutex_exit(&ibuf_mutex); + + mtr_commit(&mtr); + + ibuf_exit(); + + heap = mem_heap_create(450); + + /* Use old-style record format for the insert buffer. */ + table = dict_mem_table_create(IBUF_TABLE_NAME, IBUF_SPACE_ID, 1, 0); + table->n_mysql_handles_opened = 1; /* for pin */ + + dict_mem_table_add_col(table, heap, "DUMMY_COLUMN", DATA_BINARY, 0, 0); + + table->id = ut_dulint_add(DICT_IBUF_ID_MIN, IBUF_SPACE_ID); + + dict_table_add_to_cache(table, heap); + mem_heap_free(heap); + + index = dict_mem_index_create( + IBUF_TABLE_NAME, "CLUST_IND", + IBUF_SPACE_ID, DICT_CLUSTERED | DICT_UNIVERSAL | DICT_IBUF, 1); + + dict_mem_index_add_field(index, "DUMMY_COLUMN", 0); + + index->id = ut_dulint_add(DICT_IBUF_ID_MIN, IBUF_SPACE_ID); + + error = dict_index_add_to_cache(table, index, + FSP_IBUF_TREE_ROOT_PAGE_NO, FALSE); + ut_a(error == DB_SUCCESS); + + ibuf->index = dict_table_get_first_index(table); +} + +/************************************************************************* +Initializes an ibuf bitmap page. */ +UNIV_INTERN +void +ibuf_bitmap_page_init( +/*==================*/ + buf_block_t* block, /* in: bitmap page */ + mtr_t* mtr) /* in: mtr */ +{ + page_t* page; + ulint byte_offset; + ulint zip_size = buf_block_get_zip_size(block); + + ut_a(ut_is_2pow(zip_size)); + + page = buf_block_get_frame(block); + fil_page_set_type(page, FIL_PAGE_IBUF_BITMAP); + + /* Write all zeros to the bitmap */ + + if (!zip_size) { + byte_offset = UT_BITS_IN_BYTES(UNIV_PAGE_SIZE + * IBUF_BITS_PER_PAGE); + } else { + byte_offset = UT_BITS_IN_BYTES(zip_size * IBUF_BITS_PER_PAGE); + } + + memset(page + IBUF_BITMAP, 0, byte_offset); + + /* The remaining area (up to the page trailer) is uninitialized. */ + + mlog_write_initial_log_record(page, MLOG_IBUF_BITMAP_INIT, mtr); +} + +/************************************************************************* +Parses a redo log record of an ibuf bitmap page init. */ +UNIV_INTERN +byte* +ibuf_parse_bitmap_init( +/*===================*/ + /* out: end of log record or NULL */ + byte* ptr, /* in: buffer */ + byte* end_ptr __attribute__((unused)), /* in: buffer end */ + buf_block_t* block, /* in: block or NULL */ + mtr_t* mtr) /* in: mtr or NULL */ +{ + ut_ad(ptr && end_ptr); + + if (block) { + ibuf_bitmap_page_init(block, mtr); + } + + return(ptr); +} + +/************************************************************************ +Gets the desired bits for a given page from a bitmap page. */ +UNIV_INLINE +ulint +ibuf_bitmap_page_get_bits( +/*======================*/ + /* out: value of bits */ + const page_t* page, /* in: bitmap page */ + ulint page_no,/* in: page whose bits to get */ + ulint zip_size,/* in: compressed page size in bytes; + 0 for uncompressed pages */ + ulint bit, /* in: IBUF_BITMAP_FREE, + IBUF_BITMAP_BUFFERED, ... */ + mtr_t* mtr __attribute__((unused))) + /* in: mtr containing an + x-latch to the bitmap page */ +{ + ulint byte_offset; + ulint bit_offset; + ulint map_byte; + ulint value; + + ut_ad(bit < IBUF_BITS_PER_PAGE); +#if IBUF_BITS_PER_PAGE % 2 +# error "IBUF_BITS_PER_PAGE % 2 != 0" +#endif + ut_ad(ut_is_2pow(zip_size)); + ut_ad(mtr_memo_contains_page(mtr, page, MTR_MEMO_PAGE_X_FIX)); + + if (!zip_size) { + bit_offset = (page_no % UNIV_PAGE_SIZE) * IBUF_BITS_PER_PAGE + + bit; + } else { + bit_offset = (page_no & (zip_size - 1)) * IBUF_BITS_PER_PAGE + + bit; + } + + byte_offset = bit_offset / 8; + bit_offset = bit_offset % 8; + + ut_ad(byte_offset + IBUF_BITMAP < UNIV_PAGE_SIZE); + + map_byte = mach_read_from_1(page + IBUF_BITMAP + byte_offset); + + value = ut_bit_get_nth(map_byte, bit_offset); + + if (bit == IBUF_BITMAP_FREE) { + ut_ad(bit_offset + 1 < 8); + + value = value * 2 + ut_bit_get_nth(map_byte, bit_offset + 1); + } + + return(value); +} + +/************************************************************************ +Sets the desired bit for a given page in a bitmap page. */ +static +void +ibuf_bitmap_page_set_bits( +/*======================*/ + page_t* page, /* in: bitmap page */ + ulint page_no,/* in: page whose bits to set */ + ulint zip_size,/* in: compressed page size in bytes; + 0 for uncompressed pages */ + ulint bit, /* in: IBUF_BITMAP_FREE, IBUF_BITMAP_BUFFERED, ... */ + ulint val, /* in: value to set */ + mtr_t* mtr) /* in: mtr containing an x-latch to the bitmap page */ +{ + ulint byte_offset; + ulint bit_offset; + ulint map_byte; + + ut_ad(bit < IBUF_BITS_PER_PAGE); +#if IBUF_BITS_PER_PAGE % 2 +# error "IBUF_BITS_PER_PAGE % 2 != 0" +#endif + ut_ad(ut_is_2pow(zip_size)); + ut_ad(mtr_memo_contains_page(mtr, page, MTR_MEMO_PAGE_X_FIX)); +#ifdef UNIV_IBUF_COUNT_DEBUG + ut_a((bit != IBUF_BITMAP_BUFFERED) || (val != FALSE) + || (0 == ibuf_count_get(page_get_space_id(page), + page_no))); +#endif + if (!zip_size) { + bit_offset = (page_no % UNIV_PAGE_SIZE) * IBUF_BITS_PER_PAGE + + bit; + } else { + bit_offset = (page_no & (zip_size - 1)) * IBUF_BITS_PER_PAGE + + bit; + } + + byte_offset = bit_offset / 8; + bit_offset = bit_offset % 8; + + ut_ad(byte_offset + IBUF_BITMAP < UNIV_PAGE_SIZE); + + map_byte = mach_read_from_1(page + IBUF_BITMAP + byte_offset); + + if (bit == IBUF_BITMAP_FREE) { + ut_ad(bit_offset + 1 < 8); + ut_ad(val <= 3); + + map_byte = ut_bit_set_nth(map_byte, bit_offset, val / 2); + map_byte = ut_bit_set_nth(map_byte, bit_offset + 1, val % 2); + } else { + ut_ad(val <= 1); + map_byte = ut_bit_set_nth(map_byte, bit_offset, val); + } + + mlog_write_ulint(page + IBUF_BITMAP + byte_offset, map_byte, + MLOG_1BYTE, mtr); +} + +/************************************************************************ +Calculates the bitmap page number for a given page number. */ +UNIV_INLINE +ulint +ibuf_bitmap_page_no_calc( +/*=====================*/ + /* out: the bitmap page number where + the file page is mapped */ + ulint zip_size, /* in: compressed page size in bytes; + 0 for uncompressed pages */ + ulint page_no) /* in: tablespace page number */ +{ + ut_ad(ut_is_2pow(zip_size)); + + if (!zip_size) { + return(FSP_IBUF_BITMAP_OFFSET + + (page_no & ~(UNIV_PAGE_SIZE - 1))); + } else { + return(FSP_IBUF_BITMAP_OFFSET + + (page_no & ~(zip_size - 1))); + } +} + +/************************************************************************ +Gets the ibuf bitmap page where the bits describing a given file page are +stored. */ +static +page_t* +ibuf_bitmap_get_map_page( +/*=====================*/ + /* out: bitmap page where the file page is mapped, + that is, the bitmap page containing the descriptor + bits for the file page; the bitmap page is + x-latched */ + ulint space, /* in: space id of the file page */ + ulint page_no,/* in: page number of the file page */ + ulint zip_size,/* in: compressed page size in bytes; + 0 for uncompressed pages */ + mtr_t* mtr) /* in: mtr */ +{ + buf_block_t* block; + + block = buf_page_get(space, zip_size, + ibuf_bitmap_page_no_calc(zip_size, page_no), + RW_X_LATCH, mtr); + buf_block_dbg_add_level(block, SYNC_IBUF_BITMAP); + + return(buf_block_get_frame(block)); +} + +/**************************************************************************** +Sets the free bits of the page in the ibuf bitmap. This is done in a separate +mini-transaction, hence this operation does not restrict further work to only +ibuf bitmap operations, which would result if the latch to the bitmap page +were kept. */ +UNIV_INLINE +void +ibuf_set_free_bits_low( +/*===================*/ + ulint zip_size,/* in: compressed page size in bytes; + 0 for uncompressed pages */ + const buf_block_t* block, /* in: index page; free bits are set if + the index is non-clustered and page + level is 0 */ + ulint val, /* in: value to set: < 4 */ + mtr_t* mtr) /* in/out: mtr */ +{ + page_t* bitmap_page; + ulint space; + ulint page_no; + + if (!page_is_leaf(buf_block_get_frame(block))) { + + return; + } + + space = buf_block_get_space(block); + page_no = buf_block_get_page_no(block); + bitmap_page = ibuf_bitmap_get_map_page(space, page_no, zip_size, mtr); +#ifdef UNIV_IBUF_DEBUG +# if 0 + fprintf(stderr, + "Setting space %lu page %lu free bits to %lu should be %lu\n", + space, page_no, val, + ibuf_index_page_calc_free(zip_size, block)); +# endif + + ut_a(val <= ibuf_index_page_calc_free(zip_size, block)); +#endif /* UNIV_IBUF_DEBUG */ + ibuf_bitmap_page_set_bits(bitmap_page, page_no, zip_size, + IBUF_BITMAP_FREE, val, mtr); +} + +/**************************************************************************** +Sets the free bit of the page in the ibuf bitmap. This is done in a separate +mini-transaction, hence this operation does not restrict further work to only +ibuf bitmap operations, which would result if the latch to the bitmap page +were kept. */ +UNIV_INTERN +void +ibuf_set_free_bits_func( +/*====================*/ + buf_block_t* block, /* in: index page of a non-clustered index; + free bit is reset if page level is 0 */ +#ifdef UNIV_IBUF_DEBUG + ulint max_val,/* in: ULINT_UNDEFINED or a maximum + value which the bits must have before + setting; this is for debugging */ +#endif /* UNIV_IBUF_DEBUG */ + ulint val) /* in: value to set: < 4 */ +{ + mtr_t mtr; + page_t* page; + page_t* bitmap_page; + ulint space; + ulint page_no; + ulint zip_size; + + page = buf_block_get_frame(block); + + if (!page_is_leaf(page)) { + + return; + } + + mtr_start(&mtr); + + space = buf_block_get_space(block); + page_no = buf_block_get_page_no(block); + zip_size = buf_block_get_zip_size(block); + bitmap_page = ibuf_bitmap_get_map_page(space, page_no, zip_size, &mtr); + +#ifdef UNIV_IBUF_DEBUG + if (max_val != ULINT_UNDEFINED) { + ulint old_val; + + old_val = ibuf_bitmap_page_get_bits( + bitmap_page, page_no, zip_size, + IBUF_BITMAP_FREE, &mtr); +# if 0 + if (old_val != max_val) { + fprintf(stderr, + "Ibuf: page %lu old val %lu max val %lu\n", + page_get_page_no(page), + old_val, max_val); + } +# endif + + ut_a(old_val <= max_val); + } +# if 0 + fprintf(stderr, "Setting page no %lu free bits to %lu should be %lu\n", + page_get_page_no(page), val, + ibuf_index_page_calc_free(zip_size, block)); +# endif + + ut_a(val <= ibuf_index_page_calc_free(zip_size, block)); +#endif /* UNIV_IBUF_DEBUG */ + ibuf_bitmap_page_set_bits(bitmap_page, page_no, zip_size, + IBUF_BITMAP_FREE, val, &mtr); + mtr_commit(&mtr); +} + +/**************************************************************************** +Resets the free bits of the page in the ibuf bitmap. This is done in a +separate mini-transaction, hence this operation does not restrict +further work to only ibuf bitmap operations, which would result if the +latch to the bitmap page were kept. NOTE: The free bits in the insert +buffer bitmap must never exceed the free space on a page. It is safe +to decrement or reset the bits in the bitmap in a mini-transaction +that is committed before the mini-transaction that affects the free +space. */ +UNIV_INTERN +void +ibuf_reset_free_bits( +/*=================*/ + buf_block_t* block) /* in: index page; free bits are set to 0 + if the index is a non-clustered + non-unique, and page level is 0 */ +{ + ibuf_set_free_bits(block, 0, ULINT_UNDEFINED); +} + +/************************************************************************** +Updates the free bits for an uncompressed page to reflect the present +state. Does this in the mtr given, which means that the latching +order rules virtually prevent any further operations for this OS +thread until mtr is committed. NOTE: The free bits in the insert +buffer bitmap must never exceed the free space on a page. It is safe +to set the free bits in the same mini-transaction that updated the +page. */ +UNIV_INTERN +void +ibuf_update_free_bits_low( +/*======================*/ + const buf_block_t* block, /* in: index page */ + ulint max_ins_size, /* in: value of + maximum insert size + with reorganize before + the latest operation + performed to the page */ + mtr_t* mtr) /* in/out: mtr */ +{ + ulint before; + ulint after; + + ut_a(!buf_block_get_page_zip(block)); + + before = ibuf_index_page_calc_free_bits(0, max_ins_size); + + after = ibuf_index_page_calc_free(0, block); + + /* This approach cannot be used on compressed pages, since the + computed value of "before" often does not match the current + state of the bitmap. This is because the free space may + increase or decrease when a compressed page is reorganized. */ + if (before != after) { + ibuf_set_free_bits_low(0, block, after, mtr); + } +} + +/************************************************************************** +Updates the free bits for a compressed page to reflect the present +state. Does this in the mtr given, which means that the latching +order rules virtually prevent any further operations for this OS +thread until mtr is committed. NOTE: The free bits in the insert +buffer bitmap must never exceed the free space on a page. It is safe +to set the free bits in the same mini-transaction that updated the +page. */ +UNIV_INTERN +void +ibuf_update_free_bits_zip( +/*======================*/ + buf_block_t* block, /* in/out: index page */ + mtr_t* mtr) /* in/out: mtr */ +{ + page_t* bitmap_page; + ulint space; + ulint page_no; + ulint zip_size; + ulint after; + + space = buf_block_get_space(block); + page_no = buf_block_get_page_no(block); + zip_size = buf_block_get_zip_size(block); + + ut_a(page_is_leaf(buf_block_get_frame(block))); + ut_a(zip_size); + + bitmap_page = ibuf_bitmap_get_map_page(space, page_no, zip_size, mtr); + + after = ibuf_index_page_calc_free_zip(zip_size, block); + + if (after == 0) { + /* We move the page to the front of the buffer pool LRU list: + the purpose of this is to prevent those pages to which we + cannot make inserts using the insert buffer from slipping + out of the buffer pool */ + + buf_page_make_young(&block->page); + } + + ibuf_bitmap_page_set_bits(bitmap_page, page_no, zip_size, + IBUF_BITMAP_FREE, after, mtr); +} + +/************************************************************************** +Updates the free bits for the two pages to reflect the present state. +Does this in the mtr given, which means that the latching order rules +virtually prevent any further operations until mtr is committed. +NOTE: The free bits in the insert buffer bitmap must never exceed the +free space on a page. It is safe to set the free bits in the same +mini-transaction that updated the pages. */ +UNIV_INTERN +void +ibuf_update_free_bits_for_two_pages_low( +/*====================================*/ + ulint zip_size,/* in: compressed page size in bytes; + 0 for uncompressed pages */ + buf_block_t* block1, /* in: index page */ + buf_block_t* block2, /* in: index page */ + mtr_t* mtr) /* in: mtr */ +{ + ulint state; + + /* As we have to x-latch two random bitmap pages, we have to acquire + the bitmap mutex to prevent a deadlock with a similar operation + performed by another OS thread. */ + + mutex_enter(&ibuf_bitmap_mutex); + + state = ibuf_index_page_calc_free(zip_size, block1); + + ibuf_set_free_bits_low(zip_size, block1, state, mtr); + + state = ibuf_index_page_calc_free(zip_size, block2); + + ibuf_set_free_bits_low(zip_size, block2, state, mtr); + + mutex_exit(&ibuf_bitmap_mutex); +} + +/************************************************************************** +Returns TRUE if the page is one of the fixed address ibuf pages. */ +UNIV_INLINE +ibool +ibuf_fixed_addr_page( +/*=================*/ + /* out: TRUE if a fixed address ibuf i/o page */ + ulint space, /* in: space id */ + ulint zip_size,/* in: compressed page size in bytes; + 0 for uncompressed pages */ + ulint page_no)/* in: page number */ +{ + return((space == IBUF_SPACE_ID && page_no == IBUF_TREE_ROOT_PAGE_NO) + || ibuf_bitmap_page(zip_size, page_no)); +} + +/*************************************************************************** +Checks if a page is a level 2 or 3 page in the ibuf hierarchy of pages. +Must not be called when recv_no_ibuf_operations==TRUE. */ +UNIV_INTERN +ibool +ibuf_page( +/*======*/ + /* out: TRUE if level 2 or level 3 page */ + ulint space, /* in: space id */ + ulint zip_size,/* in: compressed page size in bytes, or 0 */ + ulint page_no,/* in: page number */ + mtr_t* mtr) /* in: mtr which will contain an x-latch to the + bitmap page if the page is not one of the fixed + address ibuf pages, or NULL, in which case a new + transaction is created. */ +{ + ibool ret; + mtr_t local_mtr; + page_t* bitmap_page; + + ut_ad(!recv_no_ibuf_operations); + + if (ibuf_fixed_addr_page(space, zip_size, page_no)) { + + return(TRUE); + } else if (space != IBUF_SPACE_ID) { + + return(FALSE); + } + + ut_ad(fil_space_get_type(IBUF_SPACE_ID) == FIL_TABLESPACE); + + if (mtr == NULL) { + mtr = &local_mtr; + mtr_start(mtr); + } + + bitmap_page = ibuf_bitmap_get_map_page(space, page_no, zip_size, mtr); + + ret = ibuf_bitmap_page_get_bits(bitmap_page, page_no, zip_size, + IBUF_BITMAP_IBUF, mtr); + + if (mtr == &local_mtr) { + mtr_commit(mtr); + } + + return(ret); +} + +/************************************************************************ +Returns the page number field of an ibuf record. */ +static +ulint +ibuf_rec_get_page_no( +/*=================*/ + /* out: page number */ + const rec_t* rec) /* in: ibuf record */ +{ + const byte* field; + ulint len; + + ut_ad(ibuf_inside()); + ut_ad(rec_get_n_fields_old(rec) > 2); + + field = rec_get_nth_field_old(rec, 1, &len); + + if (len == 1) { + /* This is of the >= 4.1.x record format */ + ut_a(trx_sys_multiple_tablespace_format); + + field = rec_get_nth_field_old(rec, 2, &len); + } else { + ut_a(trx_doublewrite_must_reset_space_ids); + ut_a(!trx_sys_multiple_tablespace_format); + + field = rec_get_nth_field_old(rec, 0, &len); + } + + ut_a(len == 4); + + return(mach_read_from_4(field)); +} + +/************************************************************************ +Returns the space id field of an ibuf record. For < 4.1.x format records +returns 0. */ +static +ulint +ibuf_rec_get_space( +/*===============*/ + /* out: space id */ + const rec_t* rec) /* in: ibuf record */ +{ + const byte* field; + ulint len; + + ut_ad(ibuf_inside()); + ut_ad(rec_get_n_fields_old(rec) > 2); + + field = rec_get_nth_field_old(rec, 1, &len); + + if (len == 1) { + /* This is of the >= 4.1.x record format */ + + ut_a(trx_sys_multiple_tablespace_format); + field = rec_get_nth_field_old(rec, 0, &len); + ut_a(len == 4); + + return(mach_read_from_4(field)); + } + + ut_a(trx_doublewrite_must_reset_space_ids); + ut_a(!trx_sys_multiple_tablespace_format); + + return(0); +} + +/************************************************************************ +Creates a dummy index for inserting a record to a non-clustered index. +*/ +static +dict_index_t* +ibuf_dummy_index_create( +/*====================*/ + /* out: dummy index */ + ulint n, /* in: number of fields */ + ibool comp) /* in: TRUE=use compact record format */ +{ + dict_table_t* table; + dict_index_t* index; + + table = dict_mem_table_create("IBUF_DUMMY", + DICT_HDR_SPACE, n, + comp ? DICT_TF_COMPACT : 0); + + index = dict_mem_index_create("IBUF_DUMMY", "IBUF_DUMMY", + DICT_HDR_SPACE, 0, n); + + index->table = table; + + /* avoid ut_ad(index->cached) in dict_index_get_n_unique_in_tree */ + index->cached = TRUE; + + return(index); +} +/************************************************************************ +Add a column to the dummy index */ +static +void +ibuf_dummy_index_add_col( +/*=====================*/ + dict_index_t* index, /* in: dummy index */ + const dtype_t* type, /* in: the data type of the column */ + ulint len) /* in: length of the column */ +{ + ulint i = index->table->n_def; + dict_mem_table_add_col(index->table, NULL, NULL, + dtype_get_mtype(type), + dtype_get_prtype(type), + dtype_get_len(type)); + dict_index_add_col(index, index->table, + dict_table_get_nth_col(index->table, i), len); +} +/************************************************************************ +Deallocates a dummy index for inserting a record to a non-clustered index. +*/ +static +void +ibuf_dummy_index_free( +/*==================*/ + dict_index_t* index) /* in: dummy index */ +{ + dict_table_t* table = index->table; + + dict_mem_index_free(index); + dict_mem_table_free(table); +} + +/************************************************************************* +Builds the entry to insert into a non-clustered index when we have the +corresponding record in an ibuf index. */ +UNIV_INLINE +dtuple_t* +ibuf_build_entry_pre_4_1_x( +/*=======================*/ + /* out, own: entry to insert to + a non-clustered index; NOTE that + as we copy pointers to fields in + ibuf_rec, the caller must hold a + latch to the ibuf_rec page as long + as the entry is used! */ + const rec_t* ibuf_rec, /* in: record in an insert buffer */ + mem_heap_t* heap, /* in: heap where built */ + dict_index_t** pindex) /* out, own: dummy index that + describes the entry */ +{ + ulint i; + ulint len; + const byte* types; + dtuple_t* tuple; + ulint n_fields; + + ut_a(trx_doublewrite_must_reset_space_ids); + ut_a(!trx_sys_multiple_tablespace_format); + + n_fields = rec_get_n_fields_old(ibuf_rec) - 2; + tuple = dtuple_create(heap, n_fields); + types = rec_get_nth_field_old(ibuf_rec, 1, &len); + + ut_a(len == n_fields * DATA_ORDER_NULL_TYPE_BUF_SIZE); + + for (i = 0; i < n_fields; i++) { + const byte* data; + dfield_t* field; + + field = dtuple_get_nth_field(tuple, i); + + data = rec_get_nth_field_old(ibuf_rec, i + 2, &len); + + dfield_set_data(field, data, len); + + dtype_read_for_order_and_null_size( + dfield_get_type(field), + types + i * DATA_ORDER_NULL_TYPE_BUF_SIZE); + } + + *pindex = ibuf_dummy_index_create(n_fields, FALSE); + + return(tuple); +} + +/************************************************************************* +Builds the entry to insert into a non-clustered index when we have the +corresponding record in an ibuf index. */ +static +dtuple_t* +ibuf_build_entry_from_ibuf_rec( +/*===========================*/ + /* out, own: entry to insert to + a non-clustered index; NOTE that + as we copy pointers to fields in + ibuf_rec, the caller must hold a + latch to the ibuf_rec page as long + as the entry is used! */ + const rec_t* ibuf_rec, /* in: record in an insert buffer */ + mem_heap_t* heap, /* in: heap where built */ + dict_index_t** pindex) /* out, own: dummy index that + describes the entry */ +{ + dtuple_t* tuple; + dfield_t* field; + ulint n_fields; + const byte* types; + const byte* data; + ulint len; + ulint i; + dict_index_t* index; + + data = rec_get_nth_field_old(ibuf_rec, 1, &len); + + if (len > 1) { + /* This a < 4.1.x format record */ + + return(ibuf_build_entry_pre_4_1_x(ibuf_rec, heap, pindex)); + } + + /* This a >= 4.1.x format record */ + + ut_a(trx_sys_multiple_tablespace_format); + ut_a(*data == 0); + ut_a(rec_get_n_fields_old(ibuf_rec) > 4); + + n_fields = rec_get_n_fields_old(ibuf_rec) - 4; + + tuple = dtuple_create(heap, n_fields); + + types = rec_get_nth_field_old(ibuf_rec, 3, &len); + + ut_a(len % DATA_NEW_ORDER_NULL_TYPE_BUF_SIZE <= 1); + index = ibuf_dummy_index_create( + n_fields, len % DATA_NEW_ORDER_NULL_TYPE_BUF_SIZE); + + if (len % DATA_NEW_ORDER_NULL_TYPE_BUF_SIZE) { + /* compact record format */ + len--; + ut_a(*types == 0); + types++; + } + + ut_a(len == n_fields * DATA_NEW_ORDER_NULL_TYPE_BUF_SIZE); + + for (i = 0; i < n_fields; i++) { + field = dtuple_get_nth_field(tuple, i); + + data = rec_get_nth_field_old(ibuf_rec, i + 4, &len); + + dfield_set_data(field, data, len); + + dtype_new_read_for_order_and_null_size( + dfield_get_type(field), + types + i * DATA_NEW_ORDER_NULL_TYPE_BUF_SIZE); + + ibuf_dummy_index_add_col(index, dfield_get_type(field), len); + } + + /* Prevent an ut_ad() failure in page_zip_write_rec() by + adding system columns to the dummy table pointed to by the + dummy secondary index. The insert buffer is only used for + secondary indexes, whose records never contain any system + columns, such as DB_TRX_ID. */ + ut_d(dict_table_add_system_columns(index->table, index->table->heap)); + + *pindex = index; + + return(tuple); +} + +/************************************************************************ +Returns the space taken by a stored non-clustered index entry if converted to +an index record. */ +static +ulint +ibuf_rec_get_volume( +/*================*/ + /* out: size of index record in bytes + + an upper limit of the space taken in the + page directory */ + const rec_t* ibuf_rec)/* in: ibuf record */ +{ + dtype_t dtype; + ibool new_format = FALSE; + ulint data_size = 0; + ulint n_fields; + const byte* types; + const byte* data; + ulint len; + ulint i; + + ut_ad(ibuf_inside()); + ut_ad(rec_get_n_fields_old(ibuf_rec) > 2); + + data = rec_get_nth_field_old(ibuf_rec, 1, &len); + + if (len > 1) { + /* < 4.1.x format record */ + + ut_a(trx_doublewrite_must_reset_space_ids); + ut_a(!trx_sys_multiple_tablespace_format); + + n_fields = rec_get_n_fields_old(ibuf_rec) - 2; + + types = rec_get_nth_field_old(ibuf_rec, 1, &len); + + ut_ad(len == n_fields * DATA_ORDER_NULL_TYPE_BUF_SIZE); + } else { + /* >= 4.1.x format record */ + + ut_a(trx_sys_multiple_tablespace_format); + ut_a(*data == 0); + + types = rec_get_nth_field_old(ibuf_rec, 3, &len); + + ut_a(len % DATA_NEW_ORDER_NULL_TYPE_BUF_SIZE <= 1); + if (len % DATA_NEW_ORDER_NULL_TYPE_BUF_SIZE) { + /* compact record format */ + ulint volume; + dict_index_t* dummy_index; + mem_heap_t* heap = mem_heap_create(500); + dtuple_t* entry = ibuf_build_entry_from_ibuf_rec( + ibuf_rec, heap, &dummy_index); + volume = rec_get_converted_size(dummy_index, entry, 0); + ibuf_dummy_index_free(dummy_index); + mem_heap_free(heap); + return(volume + page_dir_calc_reserved_space(1)); + } + + n_fields = rec_get_n_fields_old(ibuf_rec) - 4; + + new_format = TRUE; + } + + for (i = 0; i < n_fields; i++) { + if (new_format) { + data = rec_get_nth_field_old(ibuf_rec, i + 4, &len); + + dtype_new_read_for_order_and_null_size( + &dtype, types + i + * DATA_NEW_ORDER_NULL_TYPE_BUF_SIZE); + } else { + data = rec_get_nth_field_old(ibuf_rec, i + 2, &len); + + dtype_read_for_order_and_null_size( + &dtype, types + i + * DATA_ORDER_NULL_TYPE_BUF_SIZE); + } + + if (len == UNIV_SQL_NULL) { + data_size += dtype_get_sql_null_size(&dtype); + } else { + data_size += len; + } + } + + return(data_size + rec_get_converted_extra_size(data_size, n_fields, 0) + + page_dir_calc_reserved_space(1)); +} + +/************************************************************************* +Builds the tuple to insert to an ibuf tree when we have an entry for a +non-clustered index. */ +static +dtuple_t* +ibuf_entry_build( +/*=============*/ + /* out, own: entry to insert into an ibuf + index tree; NOTE that the original entry + must be kept because we copy pointers to its + fields */ + dict_index_t* index, /* in: non-clustered index */ + const dtuple_t* entry, /* in: entry for a non-clustered index */ + ulint space, /* in: space id */ + ulint page_no,/* in: index page number where entry should + be inserted */ + mem_heap_t* heap) /* in: heap into which to build */ +{ + dtuple_t* tuple; + dfield_t* field; + const dfield_t* entry_field; + ulint n_fields; + byte* buf; + byte* buf2; + ulint i; + + /* Starting from 4.1.x, we have to build a tuple whose + (1) first field is the space id, + (2) the second field a single marker byte (0) to tell that this + is a new format record, + (3) the third contains the page number, and + (4) the fourth contains the relevent type information of each data + field; the length of this field % DATA_NEW_ORDER_NULL_TYPE_BUF_SIZE is + (a) 0 for b-trees in the old format, and + (b) 1 for b-trees in the compact format, the first byte of the field + being the marker (0); + (5) and the rest of the fields are copied from entry. All fields + in the tuple are ordered like the type binary in our insert buffer + tree. */ + + n_fields = dtuple_get_n_fields(entry); + + tuple = dtuple_create(heap, n_fields + 4); + + /* Store the space id in tuple */ + + field = dtuple_get_nth_field(tuple, 0); + + buf = mem_heap_alloc(heap, 4); + + mach_write_to_4(buf, space); + + dfield_set_data(field, buf, 4); + + /* Store the marker byte field in tuple */ + + field = dtuple_get_nth_field(tuple, 1); + + buf = mem_heap_alloc(heap, 1); + + /* We set the marker byte zero */ + + mach_write_to_1(buf, 0); + + dfield_set_data(field, buf, 1); + + /* Store the page number in tuple */ + + field = dtuple_get_nth_field(tuple, 2); + + buf = mem_heap_alloc(heap, 4); + + mach_write_to_4(buf, page_no); + + dfield_set_data(field, buf, 4); + + /* Store the type info in buf2, and add the fields from entry to + tuple */ + buf2 = mem_heap_alloc(heap, n_fields + * DATA_NEW_ORDER_NULL_TYPE_BUF_SIZE + + dict_table_is_comp(index->table)); + if (dict_table_is_comp(index->table)) { + *buf2++ = 0; /* write the compact format indicator */ + } + for (i = 0; i < n_fields; i++) { + ulint fixed_len; + const dict_field_t* ifield; + + /* We add 4 below because we have the 4 extra fields at the + start of an ibuf record */ + + field = dtuple_get_nth_field(tuple, i + 4); + entry_field = dtuple_get_nth_field(entry, i); + dfield_copy(field, entry_field); + + ifield = dict_index_get_nth_field(index, i); + /* Prefix index columns of fixed-length columns are of + fixed length. However, in the function call below, + dfield_get_type(entry_field) contains the fixed length + of the column in the clustered index. Replace it with + the fixed length of the secondary index column. */ + fixed_len = ifield->fixed_len; + +#ifdef UNIV_DEBUG + if (fixed_len) { + /* dict_index_add_col() should guarantee these */ + ut_ad(fixed_len <= (ulint) + dfield_get_type(entry_field)->len); + if (ifield->prefix_len) { + ut_ad(ifield->prefix_len == fixed_len); + } else { + ut_ad(fixed_len == (ulint) + dfield_get_type(entry_field)->len); + } + } +#endif /* UNIV_DEBUG */ + + dtype_new_store_for_order_and_null_size( + buf2 + i * DATA_NEW_ORDER_NULL_TYPE_BUF_SIZE, + dfield_get_type(entry_field), fixed_len); + } + + /* Store the type info in buf2 to field 3 of tuple */ + + field = dtuple_get_nth_field(tuple, 3); + + if (dict_table_is_comp(index->table)) { + buf2--; + } + + dfield_set_data(field, buf2, n_fields + * DATA_NEW_ORDER_NULL_TYPE_BUF_SIZE + + dict_table_is_comp(index->table)); + /* Set all the types in the new tuple binary */ + + dtuple_set_types_binary(tuple, n_fields + 4); + + return(tuple); +} + +/************************************************************************* +Builds a search tuple used to search buffered inserts for an index page. +This is for < 4.1.x format records */ +static +dtuple_t* +ibuf_search_tuple_build( +/*====================*/ + /* out, own: search tuple */ + ulint space, /* in: space id */ + ulint page_no,/* in: index page number */ + mem_heap_t* heap) /* in: heap into which to build */ +{ + dtuple_t* tuple; + dfield_t* field; + byte* buf; + + ut_a(space == 0); + ut_a(trx_doublewrite_must_reset_space_ids); + ut_a(!trx_sys_multiple_tablespace_format); + + tuple = dtuple_create(heap, 1); + + /* Store the page number in tuple */ + + field = dtuple_get_nth_field(tuple, 0); + + buf = mem_heap_alloc(heap, 4); + + mach_write_to_4(buf, page_no); + + dfield_set_data(field, buf, 4); + + dtuple_set_types_binary(tuple, 1); + + return(tuple); +} + +/************************************************************************* +Builds a search tuple used to search buffered inserts for an index page. +This is for >= 4.1.x format records. */ +static +dtuple_t* +ibuf_new_search_tuple_build( +/*========================*/ + /* out, own: search tuple */ + ulint space, /* in: space id */ + ulint page_no,/* in: index page number */ + mem_heap_t* heap) /* in: heap into which to build */ +{ + dtuple_t* tuple; + dfield_t* field; + byte* buf; + + ut_a(trx_sys_multiple_tablespace_format); + + tuple = dtuple_create(heap, 3); + + /* Store the space id in tuple */ + + field = dtuple_get_nth_field(tuple, 0); + + buf = mem_heap_alloc(heap, 4); + + mach_write_to_4(buf, space); + + dfield_set_data(field, buf, 4); + + /* Store the new format record marker byte */ + + field = dtuple_get_nth_field(tuple, 1); + + buf = mem_heap_alloc(heap, 1); + + mach_write_to_1(buf, 0); + + dfield_set_data(field, buf, 1); + + /* Store the page number in tuple */ + + field = dtuple_get_nth_field(tuple, 2); + + buf = mem_heap_alloc(heap, 4); + + mach_write_to_4(buf, page_no); + + dfield_set_data(field, buf, 4); + + dtuple_set_types_binary(tuple, 3); + + return(tuple); +} + +/************************************************************************* +Checks if there are enough pages in the free list of the ibuf tree that we +dare to start a pessimistic insert to the insert buffer. */ +UNIV_INLINE +ibool +ibuf_data_enough_free_for_insert(void) +/*==================================*/ + /* out: TRUE if enough free pages in list */ +{ + ut_ad(mutex_own(&ibuf_mutex)); + + /* We want a big margin of free pages, because a B-tree can sometimes + grow in size also if records are deleted from it, as the node pointers + can change, and we must make sure that we are able to delete the + inserts buffered for pages that we read to the buffer pool, without + any risk of running out of free space in the insert buffer. */ + + return(ibuf->free_list_len >= (ibuf->size / 2) + 3 * ibuf->height); +} + +/************************************************************************* +Checks if there are enough pages in the free list of the ibuf tree that we +should remove them and free to the file space management. */ +UNIV_INLINE +ibool +ibuf_data_too_much_free(void) +/*=========================*/ + /* out: TRUE if enough free pages in list */ +{ + ut_ad(mutex_own(&ibuf_mutex)); + + return(ibuf->free_list_len >= 3 + (ibuf->size / 2) + 3 * ibuf->height); +} + +/************************************************************************* +Allocates a new page from the ibuf file segment and adds it to the free +list. */ +static +ulint +ibuf_add_free_page(void) +/*====================*/ + /* out: DB_SUCCESS, or DB_STRONG_FAIL + if no space left */ +{ + mtr_t mtr; + page_t* header_page; + ulint flags; + ulint zip_size; + ulint page_no; + page_t* page; + page_t* root; + page_t* bitmap_page; + + mtr_start(&mtr); + + /* Acquire the fsp latch before the ibuf header, obeying the latching + order */ + mtr_x_lock(fil_space_get_latch(IBUF_SPACE_ID, &flags), &mtr); + zip_size = dict_table_flags_to_zip_size(flags); + + header_page = ibuf_header_page_get(&mtr); + + /* Allocate a new page: NOTE that if the page has been a part of a + non-clustered index which has subsequently been dropped, then the + page may have buffered inserts in the insert buffer, and these + should be deleted from there. These get deleted when the page + allocation creates the page in buffer. Thus the call below may end + up calling the insert buffer routines and, as we yet have no latches + to insert buffer tree pages, these routines can run without a risk + of a deadlock. This is the reason why we created a special ibuf + header page apart from the ibuf tree. */ + + page_no = fseg_alloc_free_page( + header_page + IBUF_HEADER + IBUF_TREE_SEG_HEADER, 0, FSP_UP, + &mtr); + + if (page_no == FIL_NULL) { + mtr_commit(&mtr); + + return(DB_STRONG_FAIL); + } + + { + buf_block_t* block; + + block = buf_page_get( + IBUF_SPACE_ID, 0, page_no, RW_X_LATCH, &mtr); + + buf_block_dbg_add_level(block, SYNC_TREE_NODE_NEW); + + + page = buf_block_get_frame(block); + } + + ibuf_enter(); + + mutex_enter(&ibuf_mutex); + + root = ibuf_tree_root_get(&mtr); + + /* Add the page to the free list and update the ibuf size data */ + + flst_add_last(root + PAGE_HEADER + PAGE_BTR_IBUF_FREE_LIST, + page + PAGE_HEADER + PAGE_BTR_IBUF_FREE_LIST_NODE, &mtr); + + mlog_write_ulint(page + FIL_PAGE_TYPE, FIL_PAGE_IBUF_FREE_LIST, + MLOG_2BYTES, &mtr); + + ibuf->seg_size++; + ibuf->free_list_len++; + + /* Set the bit indicating that this page is now an ibuf tree page + (level 2 page) */ + + bitmap_page = ibuf_bitmap_get_map_page( + IBUF_SPACE_ID, page_no, zip_size, &mtr); + + ibuf_bitmap_page_set_bits( + bitmap_page, page_no, zip_size, IBUF_BITMAP_IBUF, TRUE, &mtr); + + mtr_commit(&mtr); + + mutex_exit(&ibuf_mutex); + + ibuf_exit(); + + return(DB_SUCCESS); +} + +/************************************************************************* +Removes a page from the free list and frees it to the fsp system. */ +static +void +ibuf_remove_free_page(void) +/*=======================*/ +{ + mtr_t mtr; + mtr_t mtr2; + page_t* header_page; + ulint flags; + ulint zip_size; + ulint page_no; + page_t* page; + page_t* root; + page_t* bitmap_page; + + mtr_start(&mtr); + + /* Acquire the fsp latch before the ibuf header, obeying the latching + order */ + mtr_x_lock(fil_space_get_latch(IBUF_SPACE_ID, &flags), &mtr); + zip_size = dict_table_flags_to_zip_size(flags); + + header_page = ibuf_header_page_get(&mtr); + + /* Prevent pessimistic inserts to insert buffer trees for a while */ + mutex_enter(&ibuf_pessimistic_insert_mutex); + + ibuf_enter(); + + mutex_enter(&ibuf_mutex); + + if (!ibuf_data_too_much_free()) { + + mutex_exit(&ibuf_mutex); + + ibuf_exit(); + + mutex_exit(&ibuf_pessimistic_insert_mutex); + + mtr_commit(&mtr); + + return; + } + + mtr_start(&mtr2); + + root = ibuf_tree_root_get(&mtr2); + + page_no = flst_get_last(root + PAGE_HEADER + PAGE_BTR_IBUF_FREE_LIST, + &mtr2).page; + + /* NOTE that we must release the latch on the ibuf tree root + because in fseg_free_page we access level 1 pages, and the root + is a level 2 page. */ + + mtr_commit(&mtr2); + mutex_exit(&ibuf_mutex); + + ibuf_exit(); + + /* Since pessimistic inserts were prevented, we know that the + page is still in the free list. NOTE that also deletes may take + pages from the free list, but they take them from the start, and + the free list was so long that they cannot have taken the last + page from it. */ + + fseg_free_page(header_page + IBUF_HEADER + IBUF_TREE_SEG_HEADER, + IBUF_SPACE_ID, page_no, &mtr); + +#ifdef UNIV_DEBUG_FILE_ACCESSES + buf_page_reset_file_page_was_freed(IBUF_SPACE_ID, page_no); +#endif + + ibuf_enter(); + + mutex_enter(&ibuf_mutex); + + root = ibuf_tree_root_get(&mtr); + + ut_ad(page_no == flst_get_last(root + PAGE_HEADER + + PAGE_BTR_IBUF_FREE_LIST, &mtr).page); + + { + buf_block_t* block; + + block = buf_page_get( + IBUF_SPACE_ID, 0, page_no, RW_X_LATCH, &mtr); + + buf_block_dbg_add_level(block, SYNC_TREE_NODE); + + + page = buf_block_get_frame(block); + } + + /* Remove the page from the free list and update the ibuf size data */ + + flst_remove(root + PAGE_HEADER + PAGE_BTR_IBUF_FREE_LIST, + page + PAGE_HEADER + PAGE_BTR_IBUF_FREE_LIST_NODE, &mtr); + + ibuf->seg_size--; + ibuf->free_list_len--; + + mutex_exit(&ibuf_pessimistic_insert_mutex); + + /* Set the bit indicating that this page is no more an ibuf tree page + (level 2 page) */ + + bitmap_page = ibuf_bitmap_get_map_page( + IBUF_SPACE_ID, page_no, zip_size, &mtr); + + ibuf_bitmap_page_set_bits( + bitmap_page, page_no, zip_size, IBUF_BITMAP_IBUF, FALSE, &mtr); + +#ifdef UNIV_DEBUG_FILE_ACCESSES + buf_page_set_file_page_was_freed(IBUF_SPACE_ID, page_no); +#endif + mtr_commit(&mtr); + + mutex_exit(&ibuf_mutex); + + ibuf_exit(); +} + +/*************************************************************************** +Frees excess pages from the ibuf free list. This function is called when an OS +thread calls fsp services to allocate a new file segment, or a new page to a +file segment, and the thread did not own the fsp latch before this call. */ +UNIV_INTERN +void +ibuf_free_excess_pages(void) +/*========================*/ +{ + ulint i; + +#ifdef UNIV_SYNC_DEBUG + ut_ad(rw_lock_own(fil_space_get_latch(IBUF_SPACE_ID, NULL), + RW_LOCK_EX)); +#endif /* UNIV_SYNC_DEBUG */ + + ut_ad(rw_lock_get_x_lock_count( + fil_space_get_latch(IBUF_SPACE_ID, NULL)) == 1); + + ut_ad(!ibuf_inside()); + + /* NOTE: We require that the thread did not own the latch before, + because then we know that we can obey the correct latching order + for ibuf latches */ + + if (!ibuf) { + /* Not yet initialized; not sure if this is possible, but + does no harm to check for it. */ + + return; + } + + /* Free at most a few pages at a time, so that we do not delay the + requested service too much */ + + for (i = 0; i < 4; i++) { + + mutex_enter(&ibuf_mutex); + + if (!ibuf_data_too_much_free()) { + + mutex_exit(&ibuf_mutex); + + return; + } + + mutex_exit(&ibuf_mutex); + + ibuf_remove_free_page(); + } +} + +/************************************************************************* +Reads page numbers from a leaf in an ibuf tree. */ +static +ulint +ibuf_get_merge_page_nos( +/*====================*/ + /* out: a lower limit for the combined volume + of records which will be merged */ + ibool contract,/* in: TRUE if this function is called to + contract the tree, FALSE if this is called + when a single page becomes full and we look + if it pays to read also nearby pages */ + rec_t* rec, /* in: record from which we read up and down + in the chain of records */ + ulint* space_ids,/* in/out: space id's of the pages */ + ib_int64_t* space_versions,/* in/out: tablespace version + timestamps; used to prevent reading in old + pages after DISCARD + IMPORT tablespace */ + ulint* page_nos,/* in/out: buffer for at least + IBUF_MAX_N_PAGES_MERGED many page numbers; + the page numbers are in an ascending order */ + ulint* n_stored)/* out: number of page numbers stored to + page_nos in this function */ +{ + ulint prev_page_no; + ulint prev_space_id; + ulint first_page_no; + ulint first_space_id; + ulint rec_page_no; + ulint rec_space_id; + ulint sum_volumes; + ulint volume_for_page; + ulint rec_volume; + ulint limit; + ulint n_pages; + + *n_stored = 0; + + limit = ut_min(IBUF_MAX_N_PAGES_MERGED, buf_pool->curr_size / 4); + + if (page_rec_is_supremum(rec)) { + + rec = page_rec_get_prev(rec); + } + + if (page_rec_is_infimum(rec)) { + + rec = page_rec_get_next(rec); + } + + if (page_rec_is_supremum(rec)) { + + return(0); + } + + first_page_no = ibuf_rec_get_page_no(rec); + first_space_id = ibuf_rec_get_space(rec); + n_pages = 0; + prev_page_no = 0; + prev_space_id = 0; + + /* Go backwards from the first rec until we reach the border of the + 'merge area', or the page start or the limit of storeable pages is + reached */ + + while (!page_rec_is_infimum(rec) && UNIV_LIKELY(n_pages < limit)) { + + rec_page_no = ibuf_rec_get_page_no(rec); + rec_space_id = ibuf_rec_get_space(rec); + + if (rec_space_id != first_space_id + || (rec_page_no / IBUF_MERGE_AREA) + != (first_page_no / IBUF_MERGE_AREA)) { + + break; + } + + if (rec_page_no != prev_page_no + || rec_space_id != prev_space_id) { + n_pages++; + } + + prev_page_no = rec_page_no; + prev_space_id = rec_space_id; + + rec = page_rec_get_prev(rec); + } + + rec = page_rec_get_next(rec); + + /* At the loop start there is no prev page; we mark this with a pair + of space id, page no (0, 0) for which there can never be entries in + the insert buffer */ + + prev_page_no = 0; + prev_space_id = 0; + sum_volumes = 0; + volume_for_page = 0; + + while (*n_stored < limit) { + if (page_rec_is_supremum(rec)) { + /* When no more records available, mark this with + another 'impossible' pair of space id, page no */ + rec_page_no = 1; + rec_space_id = 0; + } else { + rec_page_no = ibuf_rec_get_page_no(rec); + rec_space_id = ibuf_rec_get_space(rec); + ut_ad(rec_page_no > IBUF_TREE_ROOT_PAGE_NO); + } + +#ifdef UNIV_IBUF_DEBUG + ut_a(*n_stored < IBUF_MAX_N_PAGES_MERGED); +#endif + if ((rec_space_id != prev_space_id + || rec_page_no != prev_page_no) + && (prev_space_id != 0 || prev_page_no != 0)) { + + if ((prev_page_no == first_page_no + && prev_space_id == first_space_id) + || contract + || (volume_for_page + > ((IBUF_MERGE_THRESHOLD - 1) + * 4 * UNIV_PAGE_SIZE + / IBUF_PAGE_SIZE_PER_FREE_SPACE) + / IBUF_MERGE_THRESHOLD)) { + + space_ids[*n_stored] = prev_space_id; + space_versions[*n_stored] + = fil_space_get_version(prev_space_id); + page_nos[*n_stored] = prev_page_no; + + (*n_stored)++; + + sum_volumes += volume_for_page; + } + + if (rec_space_id != first_space_id + || rec_page_no / IBUF_MERGE_AREA + != first_page_no / IBUF_MERGE_AREA) { + + break; + } + + volume_for_page = 0; + } + + if (rec_page_no == 1 && rec_space_id == 0) { + /* Supremum record */ + + break; + } + + rec_volume = ibuf_rec_get_volume(rec); + + volume_for_page += rec_volume; + + prev_page_no = rec_page_no; + prev_space_id = rec_space_id; + + rec = page_rec_get_next(rec); + } + +#ifdef UNIV_IBUF_DEBUG + ut_a(*n_stored <= IBUF_MAX_N_PAGES_MERGED); +#endif +#if 0 + fprintf(stderr, "Ibuf merge batch %lu pages %lu volume\n", + *n_stored, sum_volumes); +#endif + return(sum_volumes); +} + +/************************************************************************* +Contracts insert buffer trees by reading pages to the buffer pool. */ +static +ulint +ibuf_contract_ext( +/*==============*/ + /* out: a lower limit for the combined size in bytes + of entries which will be merged from ibuf trees to the + pages read, 0 if ibuf is empty */ + ulint* n_pages,/* out: number of pages to which merged */ + ibool sync) /* in: TRUE if the caller wants to wait for the + issued read with the highest tablespace address + to complete */ +{ + btr_pcur_t pcur; + ulint page_nos[IBUF_MAX_N_PAGES_MERGED]; + ulint space_ids[IBUF_MAX_N_PAGES_MERGED]; + ib_int64_t space_versions[IBUF_MAX_N_PAGES_MERGED]; + ulint n_stored; + ulint sum_sizes; + mtr_t mtr; + + *n_pages = 0; + ut_ad(!ibuf_inside()); + + mutex_enter(&ibuf_mutex); + + if (ibuf->empty) { +ibuf_is_empty: + mutex_exit(&ibuf_mutex); + + return(0); + } + + mtr_start(&mtr); + + ibuf_enter(); + + /* Open a cursor to a randomly chosen leaf of the tree, at a random + position within the leaf */ + + btr_pcur_open_at_rnd_pos(ibuf->index, BTR_SEARCH_LEAF, &pcur, &mtr); + + if (page_get_n_recs(btr_pcur_get_page(&pcur)) == 0) { + /* When the ibuf tree is emptied completely, the last record + is removed using an optimistic delete and ibuf_size_update + is not called, causing ibuf->empty to remain FALSE. If we do + not reset it to TRUE here then database shutdown will hang + in the loop in ibuf_contract_for_n_pages. */ + + ibuf->empty = TRUE; + + ibuf_exit(); + + mtr_commit(&mtr); + btr_pcur_close(&pcur); + + goto ibuf_is_empty; + } + + mutex_exit(&ibuf_mutex); + + sum_sizes = ibuf_get_merge_page_nos(TRUE, btr_pcur_get_rec(&pcur), + space_ids, space_versions, + page_nos, &n_stored); +#if 0 /* defined UNIV_IBUF_DEBUG */ + fprintf(stderr, "Ibuf contract sync %lu pages %lu volume %lu\n", + sync, n_stored, sum_sizes); +#endif + ibuf_exit(); + + mtr_commit(&mtr); + btr_pcur_close(&pcur); + + buf_read_ibuf_merge_pages(sync, space_ids, space_versions, page_nos, + n_stored); + *n_pages = n_stored; + + return(sum_sizes + 1); +} + +/************************************************************************* +Contracts insert buffer trees by reading pages to the buffer pool. */ +UNIV_INTERN +ulint +ibuf_contract( +/*==========*/ + /* out: a lower limit for the combined size in bytes + of entries which will be merged from ibuf trees to the + pages read, 0 if ibuf is empty */ + ibool sync) /* in: TRUE if the caller wants to wait for the + issued read with the highest tablespace address + to complete */ +{ + ulint n_pages; + + return(ibuf_contract_ext(&n_pages, sync)); +} + +/************************************************************************* +Contracts insert buffer trees by reading pages to the buffer pool. */ +UNIV_INTERN +ulint +ibuf_contract_for_n_pages( +/*======================*/ + /* out: a lower limit for the combined size in bytes + of entries which will be merged from ibuf trees to the + pages read, 0 if ibuf is empty */ + ibool sync, /* in: TRUE if the caller wants to wait for the + issued read with the highest tablespace address + to complete */ + ulint n_pages)/* in: try to read at least this many pages to + the buffer pool and merge the ibuf contents to + them */ +{ + ulint sum_bytes = 0; + ulint sum_pages = 0; + ulint n_bytes; + ulint n_pag2; + + while (sum_pages < n_pages) { + n_bytes = ibuf_contract_ext(&n_pag2, sync); + + if (n_bytes == 0) { + return(sum_bytes); + } + + sum_bytes += n_bytes; + sum_pages += n_pag2; + } + + return(sum_bytes); +} + +/************************************************************************* +Contract insert buffer trees after insert if they are too big. */ +UNIV_INLINE +void +ibuf_contract_after_insert( +/*=======================*/ + ulint entry_size) /* in: size of a record which was inserted + into an ibuf tree */ +{ + ibool sync; + ulint sum_sizes; + ulint size; + + mutex_enter(&ibuf_mutex); + + if (!srv_ibuf_active_contract) { + if (ibuf->size < ibuf->max_size + IBUF_CONTRACT_ON_INSERT_NON_SYNC) { + mutex_exit(&ibuf_mutex); + + return; + } + } + + sync = FALSE; + + if (ibuf->size >= ibuf->max_size + IBUF_CONTRACT_ON_INSERT_SYNC) { + + sync = TRUE; + } + + mutex_exit(&ibuf_mutex); + + /* Contract at least entry_size many bytes */ + sum_sizes = 0; + size = 1; + + while ((size > 0) && (sum_sizes < entry_size)) { + + size = ibuf_contract(sync); + sum_sizes += size; + } +} + +/************************************************************************* +Gets an upper limit for the combined size of entries buffered in the insert +buffer for a given page. */ +static +ulint +ibuf_get_volume_buffered( +/*=====================*/ + /* out: upper limit for the volume of + buffered inserts for the index page, in bytes; + we may also return UNIV_PAGE_SIZE, if the + entries for the index page span on several + pages in the insert buffer */ + btr_pcur_t* pcur, /* in: pcur positioned at a place in an + insert buffer tree where we would insert an + entry for the index page whose number is + page_no, latch mode has to be BTR_MODIFY_PREV + or BTR_MODIFY_TREE */ + ulint space, /* in: space id */ + ulint page_no,/* in: page number of an index page */ + mtr_t* mtr) /* in: mtr */ +{ + ulint volume; + rec_t* rec; + page_t* page; + ulint prev_page_no; + page_t* prev_page; + ulint next_page_no; + page_t* next_page; + + ut_a(trx_sys_multiple_tablespace_format); + + ut_ad((pcur->latch_mode == BTR_MODIFY_PREV) + || (pcur->latch_mode == BTR_MODIFY_TREE)); + + /* Count the volume of records earlier in the alphabetical order than + pcur */ + + volume = 0; + + rec = btr_pcur_get_rec(pcur); + page = page_align(rec); + + if (page_rec_is_supremum(rec)) { + rec = page_rec_get_prev(rec); + } + + for (;;) { + if (page_rec_is_infimum(rec)) { + + break; + } + + if (page_no != ibuf_rec_get_page_no(rec) + || space != ibuf_rec_get_space(rec)) { + + goto count_later; + } + + volume += ibuf_rec_get_volume(rec); + + rec = page_rec_get_prev(rec); + } + + /* Look at the previous page */ + + prev_page_no = btr_page_get_prev(page, mtr); + + if (prev_page_no == FIL_NULL) { + + goto count_later; + } + + { + buf_block_t* block; + + block = buf_page_get( + IBUF_SPACE_ID, 0, prev_page_no, RW_X_LATCH, mtr); + + buf_block_dbg_add_level(block, SYNC_TREE_NODE); + + + prev_page = buf_block_get_frame(block); + } + +#ifdef UNIV_BTR_DEBUG + ut_a(btr_page_get_next(prev_page, mtr) + == page_get_page_no(page)); +#endif /* UNIV_BTR_DEBUG */ + + rec = page_get_supremum_rec(prev_page); + rec = page_rec_get_prev(rec); + + for (;;) { + if (page_rec_is_infimum(rec)) { + + /* We cannot go to yet a previous page, because we + do not have the x-latch on it, and cannot acquire one + because of the latching order: we have to give up */ + + return(UNIV_PAGE_SIZE); + } + + if (page_no != ibuf_rec_get_page_no(rec) + || space != ibuf_rec_get_space(rec)) { + + goto count_later; + } + + volume += ibuf_rec_get_volume(rec); + + rec = page_rec_get_prev(rec); + } + +count_later: + rec = btr_pcur_get_rec(pcur); + + if (!page_rec_is_supremum(rec)) { + rec = page_rec_get_next(rec); + } + + for (;;) { + if (page_rec_is_supremum(rec)) { + + break; + } + + if (page_no != ibuf_rec_get_page_no(rec) + || space != ibuf_rec_get_space(rec)) { + + return(volume); + } + + volume += ibuf_rec_get_volume(rec); + + rec = page_rec_get_next(rec); + } + + /* Look at the next page */ + + next_page_no = btr_page_get_next(page, mtr); + + if (next_page_no == FIL_NULL) { + + return(volume); + } + + { + buf_block_t* block; + + block = buf_page_get( + IBUF_SPACE_ID, 0, next_page_no, RW_X_LATCH, mtr); + + buf_block_dbg_add_level(block, SYNC_TREE_NODE); + + + next_page = buf_block_get_frame(block); + } + +#ifdef UNIV_BTR_DEBUG + ut_a(btr_page_get_prev(next_page, mtr) == page_get_page_no(page)); +#endif /* UNIV_BTR_DEBUG */ + + rec = page_get_infimum_rec(next_page); + rec = page_rec_get_next(rec); + + for (;;) { + if (page_rec_is_supremum(rec)) { + + /* We give up */ + + return(UNIV_PAGE_SIZE); + } + + if (page_no != ibuf_rec_get_page_no(rec) + || space != ibuf_rec_get_space(rec)) { + + return(volume); + } + + volume += ibuf_rec_get_volume(rec); + + rec = page_rec_get_next(rec); + } +} + +/************************************************************************* +Reads the biggest tablespace id from the high end of the insert buffer +tree and updates the counter in fil_system. */ +UNIV_INTERN +void +ibuf_update_max_tablespace_id(void) +/*===============================*/ +{ + ulint max_space_id; + const rec_t* rec; + const byte* field; + ulint len; + btr_pcur_t pcur; + mtr_t mtr; + + ut_a(!dict_table_is_comp(ibuf->index->table)); + + ibuf_enter(); + + mtr_start(&mtr); + + btr_pcur_open_at_index_side( + FALSE, ibuf->index, BTR_SEARCH_LEAF, &pcur, TRUE, &mtr); + + btr_pcur_move_to_prev(&pcur, &mtr); + + if (btr_pcur_is_before_first_on_page(&pcur)) { + /* The tree is empty */ + + max_space_id = 0; + } else { + rec = btr_pcur_get_rec(&pcur); + + field = rec_get_nth_field_old(rec, 0, &len); + + ut_a(len == 4); + + max_space_id = mach_read_from_4(field); + } + + mtr_commit(&mtr); + ibuf_exit(); + + /* printf("Maximum space id in insert buffer %lu\n", max_space_id); */ + + fil_set_max_space_id_if_bigger(max_space_id); +} + +/************************************************************************* +Makes an index insert to the insert buffer, instead of directly to the disk +page, if this is possible. */ +static +ulint +ibuf_insert_low( +/*============*/ + /* out: DB_SUCCESS, DB_FAIL, DB_STRONG_FAIL */ + ulint mode, /* in: BTR_MODIFY_PREV or BTR_MODIFY_TREE */ + const dtuple_t* entry, /* in: index entry to insert */ + ulint entry_size, + /* in: rec_get_converted_size(index, entry) */ + dict_index_t* index, /* in: index where to insert; must not be + unique or clustered */ + ulint space, /* in: space id where to insert */ + ulint zip_size,/* in: compressed page size in bytes, or 0 */ + ulint page_no,/* in: page number where to insert */ + que_thr_t* thr) /* in: query thread */ +{ + big_rec_t* dummy_big_rec; + btr_pcur_t pcur; + btr_cur_t* cursor; + dtuple_t* ibuf_entry; + mem_heap_t* heap; + ulint buffered; + rec_t* ins_rec; + ibool old_bit_value; + page_t* bitmap_page; + page_t* root; + ulint err; + ibool do_merge; + ulint space_ids[IBUF_MAX_N_PAGES_MERGED]; + ib_int64_t space_versions[IBUF_MAX_N_PAGES_MERGED]; + ulint page_nos[IBUF_MAX_N_PAGES_MERGED]; + ulint n_stored; + ulint bits; + mtr_t mtr; + mtr_t bitmap_mtr; + + ut_a(!dict_index_is_clust(index)); + ut_ad(dtuple_check_typed(entry)); + ut_ad(ut_is_2pow(zip_size)); + + ut_a(trx_sys_multiple_tablespace_format); + + do_merge = FALSE; + + mutex_enter(&ibuf_mutex); + + if (ibuf->size >= ibuf->max_size + IBUF_CONTRACT_DO_NOT_INSERT) { + /* Insert buffer is now too big, contract it but do not try + to insert */ + + mutex_exit(&ibuf_mutex); + +#ifdef UNIV_IBUF_DEBUG + fputs("Ibuf too big\n", stderr); +#endif + /* Use synchronous contract (== TRUE) */ + ibuf_contract(TRUE); + + return(DB_STRONG_FAIL); + } + + mutex_exit(&ibuf_mutex); + + if (mode == BTR_MODIFY_TREE) { + mutex_enter(&ibuf_pessimistic_insert_mutex); + + ibuf_enter(); + + mutex_enter(&ibuf_mutex); + + while (!ibuf_data_enough_free_for_insert()) { + + mutex_exit(&ibuf_mutex); + + ibuf_exit(); + + mutex_exit(&ibuf_pessimistic_insert_mutex); + + err = ibuf_add_free_page(); + + if (err == DB_STRONG_FAIL) { + + return(err); + } + + mutex_enter(&ibuf_pessimistic_insert_mutex); + + ibuf_enter(); + + mutex_enter(&ibuf_mutex); + } + } else { + ibuf_enter(); + } + + heap = mem_heap_create(512); + + /* Build the entry which contains the space id and the page number as + the first fields and the type information for other fields, and which + will be inserted to the insert buffer. */ + + ibuf_entry = ibuf_entry_build(index, entry, space, page_no, heap); + + /* Open a cursor to the insert buffer tree to calculate if we can add + the new entry to it without exceeding the free space limit for the + page. */ + + mtr_start(&mtr); + + btr_pcur_open(ibuf->index, ibuf_entry, PAGE_CUR_LE, mode, &pcur, &mtr); + + /* Find out the volume of already buffered inserts for the same index + page */ + buffered = ibuf_get_volume_buffered(&pcur, space, page_no, &mtr); + +#ifdef UNIV_IBUF_COUNT_DEBUG + ut_a((buffered == 0) || ibuf_count_get(space, page_no)); +#endif + mtr_start(&bitmap_mtr); + + bitmap_page = ibuf_bitmap_get_map_page(space, page_no, + zip_size, &bitmap_mtr); + + /* We check if the index page is suitable for buffered entries */ + + if (buf_page_peek(space, page_no) + || lock_rec_expl_exist_on_page(space, page_no)) { + err = DB_STRONG_FAIL; + + mtr_commit(&bitmap_mtr); + + goto function_exit; + } + + bits = ibuf_bitmap_page_get_bits(bitmap_page, page_no, zip_size, + IBUF_BITMAP_FREE, &bitmap_mtr); + + if (buffered + entry_size + page_dir_calc_reserved_space(1) + > ibuf_index_page_calc_free_from_bits(zip_size, bits)) { + mtr_commit(&bitmap_mtr); + + /* It may not fit */ + err = DB_STRONG_FAIL; + + do_merge = TRUE; + + ibuf_get_merge_page_nos(FALSE, btr_pcur_get_rec(&pcur), + space_ids, space_versions, + page_nos, &n_stored); + goto function_exit; + } + + /* Set the bitmap bit denoting that the insert buffer contains + buffered entries for this index page, if the bit is not set yet */ + + old_bit_value = ibuf_bitmap_page_get_bits( + bitmap_page, page_no, zip_size, + IBUF_BITMAP_BUFFERED, &bitmap_mtr); + + if (!old_bit_value) { + ibuf_bitmap_page_set_bits(bitmap_page, page_no, zip_size, + IBUF_BITMAP_BUFFERED, TRUE, + &bitmap_mtr); + } + + mtr_commit(&bitmap_mtr); + + cursor = btr_pcur_get_btr_cur(&pcur); + + if (mode == BTR_MODIFY_PREV) { + err = btr_cur_optimistic_insert(BTR_NO_LOCKING_FLAG, cursor, + ibuf_entry, &ins_rec, + &dummy_big_rec, 0, thr, &mtr); + if (err == DB_SUCCESS) { + /* Update the page max trx id field */ + page_update_max_trx_id(btr_cur_get_block(cursor), NULL, + thr_get_trx(thr)->id); + } + } else { + ut_ad(mode == BTR_MODIFY_TREE); + + /* We acquire an x-latch to the root page before the insert, + because a pessimistic insert releases the tree x-latch, + which would cause the x-latching of the root after that to + break the latching order. */ + + root = ibuf_tree_root_get(&mtr); + + err = btr_cur_pessimistic_insert(BTR_NO_LOCKING_FLAG + | BTR_NO_UNDO_LOG_FLAG, + cursor, + ibuf_entry, &ins_rec, + &dummy_big_rec, 0, thr, &mtr); + if (err == DB_SUCCESS) { + /* Update the page max trx id field */ + page_update_max_trx_id(btr_cur_get_block(cursor), NULL, + thr_get_trx(thr)->id); + } + + ibuf_size_update(root, &mtr); + } + +function_exit: +#ifdef UNIV_IBUF_COUNT_DEBUG + if (err == DB_SUCCESS) { + fprintf(stderr, + "Incrementing ibuf count of space %lu page %lu\n" + "from %lu by 1\n", space, page_no, + ibuf_count_get(space, page_no)); + + ibuf_count_set(space, page_no, + ibuf_count_get(space, page_no) + 1); + } +#endif + if (mode == BTR_MODIFY_TREE) { + + mutex_exit(&ibuf_mutex); + mutex_exit(&ibuf_pessimistic_insert_mutex); + } + + mtr_commit(&mtr); + btr_pcur_close(&pcur); + ibuf_exit(); + + mem_heap_free(heap); + + if (err == DB_SUCCESS) { + mutex_enter(&ibuf_mutex); + + ibuf->empty = FALSE; + ibuf->n_inserts++; + + mutex_exit(&ibuf_mutex); + + if (mode == BTR_MODIFY_TREE) { + ibuf_contract_after_insert(entry_size); + } + } + + if (do_merge) { +#ifdef UNIV_IBUF_DEBUG + ut_a(n_stored <= IBUF_MAX_N_PAGES_MERGED); +#endif + buf_read_ibuf_merge_pages(FALSE, space_ids, space_versions, + page_nos, n_stored); + } + + return(err); +} + +/************************************************************************* +Makes an index insert to the insert buffer, instead of directly to the disk +page, if this is possible. Does not do insert if the index is clustered +or unique. */ +UNIV_INTERN +ibool +ibuf_insert( +/*========*/ + /* out: TRUE if success */ + const dtuple_t* entry, /* in: index entry to insert */ + dict_index_t* index, /* in: index where to insert */ + ulint space, /* in: space id where to insert */ + ulint zip_size,/* in: compressed page size in bytes, or 0 */ + ulint page_no,/* in: page number where to insert */ + que_thr_t* thr) /* in: query thread */ +{ + ulint err; + ulint entry_size; + + ut_a(trx_sys_multiple_tablespace_format); + ut_ad(dtuple_check_typed(entry)); + ut_ad(ut_is_2pow(zip_size)); + + ut_a(!dict_index_is_clust(index)); + + switch (UNIV_EXPECT(ibuf_use, IBUF_USE_INSERT)) { + case IBUF_USE_NONE: + return(FALSE); + case IBUF_USE_INSERT: + goto do_insert; + case IBUF_USE_COUNT: + break; + } + + ut_error; /* unknown value of ibuf_use */ + +do_insert: + entry_size = rec_get_converted_size(index, entry, 0); + + if (entry_size + >= (page_get_free_space_of_empty(dict_table_is_comp(index->table)) + / 2)) { + return(FALSE); + } + + err = ibuf_insert_low(BTR_MODIFY_PREV, entry, entry_size, + index, space, zip_size, page_no, thr); + if (err == DB_FAIL) { + err = ibuf_insert_low(BTR_MODIFY_TREE, entry, entry_size, + index, space, zip_size, page_no, thr); + } + + if (err == DB_SUCCESS) { +#ifdef UNIV_IBUF_DEBUG + /* fprintf(stderr, "Ibuf insert for page no %lu of index %s\n", + page_no, index->name); */ +#endif + return(TRUE); + + } else { + ut_a(err == DB_STRONG_FAIL); + + return(FALSE); + } +} + +/************************************************************************ +During merge, inserts to an index page a secondary index entry extracted +from the insert buffer. */ +static +void +ibuf_insert_to_index_page( +/*======================*/ + dtuple_t* entry, /* in: buffered entry to insert */ + buf_block_t* block, /* in/out: index page where the buffered entry + should be placed */ + dict_index_t* index, /* in: record descriptor */ + mtr_t* mtr) /* in: mtr */ +{ + page_cur_t page_cur; + ulint low_match; + page_t* page = buf_block_get_frame(block); + rec_t* rec; + page_t* bitmap_page; + ulint old_bits; + + ut_ad(ibuf_inside()); + ut_ad(dtuple_check_typed(entry)); + + if (UNIV_UNLIKELY(dict_table_is_comp(index->table) + != (ibool)!!page_is_comp(page))) { + fputs("InnoDB: Trying to insert a record from" + " the insert buffer to an index page\n" + "InnoDB: but the 'compact' flag does not match!\n", + stderr); + goto dump; + } + + rec = page_rec_get_next(page_get_infimum_rec(page)); + + if (UNIV_UNLIKELY(rec_get_n_fields(rec, index) + != dtuple_get_n_fields(entry))) { + fputs("InnoDB: Trying to insert a record from" + " the insert buffer to an index page\n" + "InnoDB: but the number of fields does not match!\n", + stderr); +dump: + buf_page_print(page, 0); + + dtuple_print(stderr, entry); + + fputs("InnoDB: The table where where" + " this index record belongs\n" + "InnoDB: is now probably corrupt." + " Please run CHECK TABLE on\n" + "InnoDB: your tables.\n" + "InnoDB: Submit a detailed bug report to" + " http://bugs.mysql.com!\n", stderr); + + return; + } + + low_match = page_cur_search(block, index, entry, + PAGE_CUR_LE, &page_cur); + + if (low_match == dtuple_get_n_fields(entry)) { + page_zip_des_t* page_zip; + + rec = page_cur_get_rec(&page_cur); + page_zip = buf_block_get_page_zip(block); + + btr_cur_del_unmark_for_ibuf(rec, page_zip, mtr); + } else { + rec = page_cur_tuple_insert(&page_cur, entry, index, 0, mtr); + + if (UNIV_LIKELY(rec != NULL)) { + return; + } + + /* If the record did not fit, reorganize */ + + btr_page_reorganize(block, index, mtr); + page_cur_search(block, index, entry, PAGE_CUR_LE, &page_cur); + + /* This time the record must fit */ + if (UNIV_UNLIKELY + (!page_cur_tuple_insert(&page_cur, entry, index, + 0, mtr))) { + ulint space; + ulint page_no; + ulint zip_size; + + ut_print_timestamp(stderr); + + fprintf(stderr, + " InnoDB: Error: Insert buffer insert" + " fails; page free %lu," + " dtuple size %lu\n", + (ulong) page_get_max_insert_size( + page, 1), + (ulong) rec_get_converted_size( + index, entry, 0)); + fputs("InnoDB: Cannot insert index record ", + stderr); + dtuple_print(stderr, entry); + fputs("\nInnoDB: The table where" + " this index record belongs\n" + "InnoDB: is now probably corrupt." + " Please run CHECK TABLE on\n" + "InnoDB: that table.\n", stderr); + + space = page_get_space_id(page); + zip_size = buf_block_get_zip_size(block); + page_no = page_get_page_no(page); + + bitmap_page = ibuf_bitmap_get_map_page( + space, page_no, zip_size, mtr); + old_bits = ibuf_bitmap_page_get_bits( + bitmap_page, page_no, zip_size, + IBUF_BITMAP_FREE, mtr); + + fprintf(stderr, + "InnoDB: space %lu, page %lu," + " zip_size %lu, bitmap bits %lu\n", + (ulong) space, (ulong) page_no, + (ulong) zip_size, (ulong) old_bits); + + fputs("InnoDB: Submit a detailed bug report" + " to http://bugs.mysql.com\n", stderr); + } + } +} + +/************************************************************************* +Deletes from ibuf the record on which pcur is positioned. If we have to +resort to a pessimistic delete, this function commits mtr and closes +the cursor. */ +static +ibool +ibuf_delete_rec( +/*============*/ + /* out: TRUE if mtr was committed and pcur + closed in this operation */ + ulint space, /* in: space id */ + ulint page_no,/* in: index page number where the record + should belong */ + btr_pcur_t* pcur, /* in: pcur positioned on the record to + delete, having latch mode BTR_MODIFY_LEAF */ + const dtuple_t* search_tuple, + /* in: search tuple for entries of page_no */ + mtr_t* mtr) /* in: mtr */ +{ + ibool success; + page_t* root; + ulint err; + + ut_ad(ibuf_inside()); + ut_ad(page_rec_is_user_rec(btr_pcur_get_rec(pcur))); + ut_ad(ibuf_rec_get_page_no(btr_pcur_get_rec(pcur)) == page_no); + ut_ad(ibuf_rec_get_space(btr_pcur_get_rec(pcur)) == space); + + success = btr_cur_optimistic_delete(btr_pcur_get_btr_cur(pcur), mtr); + + if (success) { +#ifdef UNIV_IBUF_COUNT_DEBUG + fprintf(stderr, + "Decrementing ibuf count of space %lu page %lu\n" + "from %lu by 1\n", space, page_no, + ibuf_count_get(space, page_no)); + ibuf_count_set(space, page_no, + ibuf_count_get(space, page_no) - 1); +#endif + return(FALSE); + } + + ut_ad(page_rec_is_user_rec(btr_pcur_get_rec(pcur))); + ut_ad(ibuf_rec_get_page_no(btr_pcur_get_rec(pcur)) == page_no); + ut_ad(ibuf_rec_get_space(btr_pcur_get_rec(pcur)) == space); + + /* We have to resort to a pessimistic delete from ibuf */ + btr_pcur_store_position(pcur, mtr); + + btr_pcur_commit_specify_mtr(pcur, mtr); + + mutex_enter(&ibuf_mutex); + + mtr_start(mtr); + + success = btr_pcur_restore_position(BTR_MODIFY_TREE, pcur, mtr); + + if (!success) { + if (fil_space_get_flags(space) == ULINT_UNDEFINED) { + /* The tablespace has been dropped. It is possible + that another thread has deleted the insert buffer + entry. Do not complain. */ + goto commit_and_exit; + } + + fprintf(stderr, + "InnoDB: ERROR: Submit the output to" + " http://bugs.mysql.com\n" + "InnoDB: ibuf cursor restoration fails!\n" + "InnoDB: ibuf record inserted to page %lu\n", + (ulong) page_no); + fflush(stderr); + + rec_print_old(stderr, btr_pcur_get_rec(pcur)); + rec_print_old(stderr, pcur->old_rec); + dtuple_print(stderr, search_tuple); + + rec_print_old(stderr, + page_rec_get_next(btr_pcur_get_rec(pcur))); + fflush(stderr); + + btr_pcur_commit_specify_mtr(pcur, mtr); + + fputs("InnoDB: Validating insert buffer tree:\n", stderr); + if (!btr_validate_index(ibuf->index, NULL)) { + ut_error; + } + + fprintf(stderr, "InnoDB: ibuf tree ok\n"); + fflush(stderr); + + goto func_exit; + } + + root = ibuf_tree_root_get(mtr); + + btr_cur_pessimistic_delete(&err, TRUE, btr_pcur_get_btr_cur(pcur), + RB_NONE, mtr); + ut_a(err == DB_SUCCESS); + +#ifdef UNIV_IBUF_COUNT_DEBUG + ibuf_count_set(space, page_no, ibuf_count_get(space, page_no) - 1); +#endif + ibuf_size_update(root, mtr); + +commit_and_exit: + btr_pcur_commit_specify_mtr(pcur, mtr); + +func_exit: + btr_pcur_close(pcur); + + mutex_exit(&ibuf_mutex); + + return(TRUE); +} + +/************************************************************************* +When an index page is read from a disk to the buffer pool, this function +inserts to the page the possible index entries buffered in the insert buffer. +The entries are deleted from the insert buffer. If the page is not read, but +created in the buffer pool, this function deletes its buffered entries from +the insert buffer; there can exist entries for such a page if the page +belonged to an index which subsequently was dropped. */ +UNIV_INTERN +void +ibuf_merge_or_delete_for_page( +/*==========================*/ + buf_block_t* block, /* in: if page has been read from + disk, pointer to the page x-latched, + else NULL */ + ulint space, /* in: space id of the index page */ + ulint page_no,/* in: page number of the index page */ + ulint zip_size,/* in: compressed page size in bytes, + or 0 */ + ibool update_ibuf_bitmap)/* in: normally this is set + to TRUE, but if we have deleted or are + deleting the tablespace, then we + naturally do not want to update a + non-existent bitmap page */ +{ + mem_heap_t* heap; + btr_pcur_t pcur; + dtuple_t* search_tuple; + ulint n_inserts; +#ifdef UNIV_IBUF_DEBUG + ulint volume; +#endif + page_zip_des_t* page_zip = NULL; + ibool tablespace_being_deleted = FALSE; + ibool corruption_noticed = FALSE; + mtr_t mtr; + + ut_ad(!block || buf_block_get_space(block) == space); + ut_ad(!block || buf_block_get_page_no(block) == page_no); + ut_ad(!block || buf_block_get_zip_size(block) == zip_size); + + if (srv_force_recovery >= SRV_FORCE_NO_IBUF_MERGE + || trx_sys_hdr_page(space, page_no)) { + return; + } + + /* We cannot refer to zip_size in the following, because + zip_size is passed as ULINT_UNDEFINED (it is unknown) when + buf_read_ibuf_merge_pages() is merging (discarding) changes + for a dropped tablespace. When block != NULL or + update_ibuf_bitmap is specified, the zip_size must be known. + That is why we will repeat the check below, with zip_size in + place of 0. Passing zip_size as 0 assumes that the + uncompressed page size always is a power-of-2 multiple of the + compressed page size. */ + + if (ibuf_fixed_addr_page(space, 0, page_no) + || fsp_descr_page(0, page_no)) { + return; + } + + if (UNIV_LIKELY(update_ibuf_bitmap)) { + ut_a(ut_is_2pow(zip_size)); + + if (ibuf_fixed_addr_page(space, zip_size, page_no) + || fsp_descr_page(zip_size, page_no)) { + return; + } + + /* If the following returns FALSE, we get the counter + incremented, and must decrement it when we leave this + function. When the counter is > 0, that prevents tablespace + from being dropped. */ + + tablespace_being_deleted = fil_inc_pending_ibuf_merges(space); + + if (UNIV_UNLIKELY(tablespace_being_deleted)) { + /* Do not try to read the bitmap page from space; + just delete the ibuf records for the page */ + + block = NULL; + update_ibuf_bitmap = FALSE; + } else { + page_t* bitmap_page; + + mtr_start(&mtr); + + bitmap_page = ibuf_bitmap_get_map_page( + space, page_no, zip_size, &mtr); + + if (!ibuf_bitmap_page_get_bits(bitmap_page, page_no, + zip_size, + IBUF_BITMAP_BUFFERED, + &mtr)) { + /* No inserts buffered for this page */ + mtr_commit(&mtr); + + if (!tablespace_being_deleted) { + fil_decr_pending_ibuf_merges(space); + } + + return; + } + mtr_commit(&mtr); + } + } else if (block + && (ibuf_fixed_addr_page(space, zip_size, page_no) + || fsp_descr_page(zip_size, page_no))) { + + return; + } + + ibuf_enter(); + + heap = mem_heap_create(512); + + if (!trx_sys_multiple_tablespace_format) { + ut_a(trx_doublewrite_must_reset_space_ids); + search_tuple = ibuf_search_tuple_build(space, page_no, heap); + } else { + search_tuple = ibuf_new_search_tuple_build(space, page_no, + heap); + } + + if (block) { + /* Move the ownership of the x-latch on the page to this OS + thread, so that we can acquire a second x-latch on it. This + is needed for the insert operations to the index page to pass + the debug checks. */ + + rw_lock_x_lock_move_ownership(&(block->lock)); + page_zip = buf_block_get_page_zip(block); + + if (UNIV_UNLIKELY(fil_page_get_type(block->frame) + != FIL_PAGE_INDEX) + || UNIV_UNLIKELY(!page_is_leaf(block->frame))) { + + page_t* bitmap_page; + + corruption_noticed = TRUE; + + ut_print_timestamp(stderr); + + mtr_start(&mtr); + + fputs(" InnoDB: Dump of the ibuf bitmap page:\n", + stderr); + + bitmap_page = ibuf_bitmap_get_map_page(space, page_no, + zip_size, &mtr); + buf_page_print(bitmap_page, 0); + + mtr_commit(&mtr); + + fputs("\nInnoDB: Dump of the page:\n", stderr); + + buf_page_print(block->frame, 0); + + fprintf(stderr, + "InnoDB: Error: corruption in the tablespace." + " Bitmap shows insert\n" + "InnoDB: buffer records to page n:o %lu" + " though the page\n" + "InnoDB: type is %lu, which is" + " not an index leaf page!\n" + "InnoDB: We try to resolve the problem" + " by skipping the insert buffer\n" + "InnoDB: merge for this page." + " Please run CHECK TABLE on your tables\n" + "InnoDB: to determine if they are corrupt" + " after this.\n\n" + "InnoDB: Please submit a detailed bug report" + " to http://bugs.mysql.com\n\n", + (ulong) page_no, + (ulong) + fil_page_get_type(block->frame)); + } + } + + n_inserts = 0; +#ifdef UNIV_IBUF_DEBUG + volume = 0; +#endif +loop: + mtr_start(&mtr); + + if (block) { + ibool success; + + success = buf_page_get_known_nowait( + RW_X_LATCH, block, + BUF_KEEP_OLD, __FILE__, __LINE__, &mtr); + + ut_a(success); + + buf_block_dbg_add_level(block, SYNC_TREE_NODE); + } + + /* Position pcur in the insert buffer at the first entry for this + index page */ + btr_pcur_open_on_user_rec( + ibuf->index, search_tuple, PAGE_CUR_GE, BTR_MODIFY_LEAF, + &pcur, &mtr); + + if (!btr_pcur_is_on_user_rec(&pcur)) { + ut_ad(btr_pcur_is_after_last_in_tree(&pcur, &mtr)); + + goto reset_bit; + } + + for (;;) { + rec_t* rec; + + ut_ad(btr_pcur_is_on_user_rec(&pcur)); + + rec = btr_pcur_get_rec(&pcur); + + /* Check if the entry is for this index page */ + if (ibuf_rec_get_page_no(rec) != page_no + || ibuf_rec_get_space(rec) != space) { + + if (block) { + page_header_reset_last_insert( + block->frame, page_zip, &mtr); + } + + goto reset_bit; + } + + if (UNIV_UNLIKELY(corruption_noticed)) { + fputs("InnoDB: Discarding record\n ", stderr); + rec_print_old(stderr, rec); + fputs("\nInnoDB: from the insert buffer!\n\n", stderr); + } else if (block) { + /* Now we have at pcur a record which should be + inserted to the index page; NOTE that the call below + copies pointers to fields in rec, and we must + keep the latch to the rec page until the + insertion is finished! */ + dtuple_t* entry; + dulint max_trx_id; + dict_index_t* dummy_index; + + max_trx_id = page_get_max_trx_id(page_align(rec)); + page_update_max_trx_id(block, page_zip, max_trx_id); + + entry = ibuf_build_entry_from_ibuf_rec( + rec, heap, &dummy_index); +#ifdef UNIV_IBUF_DEBUG + volume += rec_get_converted_size(dummy_index, entry, 0) + + page_dir_calc_reserved_space(1); + ut_a(volume <= 4 * UNIV_PAGE_SIZE + / IBUF_PAGE_SIZE_PER_FREE_SPACE); +#endif + ibuf_insert_to_index_page(entry, block, + dummy_index, &mtr); + ibuf_dummy_index_free(dummy_index); + } + + n_inserts++; + + /* Delete the record from ibuf */ + if (ibuf_delete_rec(space, page_no, &pcur, search_tuple, + &mtr)) { + /* Deletion was pessimistic and mtr was committed: + we start from the beginning again */ + + goto loop; + } else if (btr_pcur_is_after_last_on_page(&pcur)) { + mtr_commit(&mtr); + btr_pcur_close(&pcur); + + goto loop; + } + } + +reset_bit: +#ifdef UNIV_IBUF_COUNT_DEBUG + if (ibuf_count_get(space, page_no) > 0) { + /* btr_print_tree(ibuf_data->index->tree, 100); + ibuf_print(); */ + } +#endif + if (UNIV_LIKELY(update_ibuf_bitmap)) { + page_t* bitmap_page; + + bitmap_page = ibuf_bitmap_get_map_page( + space, page_no, zip_size, &mtr); + + ibuf_bitmap_page_set_bits( + bitmap_page, page_no, zip_size, + IBUF_BITMAP_BUFFERED, FALSE, &mtr); + + if (block) { + ulint old_bits = ibuf_bitmap_page_get_bits( + bitmap_page, page_no, zip_size, + IBUF_BITMAP_FREE, &mtr); + + ulint new_bits = ibuf_index_page_calc_free( + zip_size, block); + + if (old_bits != new_bits) { + ibuf_bitmap_page_set_bits( + bitmap_page, page_no, zip_size, + IBUF_BITMAP_FREE, new_bits, &mtr); + } + } + } + + mtr_commit(&mtr); + btr_pcur_close(&pcur); + mem_heap_free(heap); + + /* Protect our statistics keeping from race conditions */ + mutex_enter(&ibuf_mutex); + + ibuf->n_merges++; + ibuf->n_merged_recs += n_inserts; + + mutex_exit(&ibuf_mutex); + + if (update_ibuf_bitmap && !tablespace_being_deleted) { + + fil_decr_pending_ibuf_merges(space); + } + + ibuf_exit(); + +#ifdef UNIV_IBUF_COUNT_DEBUG + ut_a(ibuf_count_get(space, page_no) == 0); +#endif +} + +/************************************************************************* +Deletes all entries in the insert buffer for a given space id. This is used +in DISCARD TABLESPACE and IMPORT TABLESPACE. +NOTE: this does not update the page free bitmaps in the space. The space will +become CORRUPT when you call this function! */ +UNIV_INTERN +void +ibuf_delete_for_discarded_space( +/*============================*/ + ulint space) /* in: space id */ +{ + mem_heap_t* heap; + btr_pcur_t pcur; + dtuple_t* search_tuple; + rec_t* ibuf_rec; + ulint page_no; + ibool closed; + ulint n_inserts; + mtr_t mtr; + + heap = mem_heap_create(512); + + /* Use page number 0 to build the search tuple so that we get the + cursor positioned at the first entry for this space id */ + + search_tuple = ibuf_new_search_tuple_build(space, 0, heap); + + n_inserts = 0; +loop: + ibuf_enter(); + + mtr_start(&mtr); + + /* Position pcur in the insert buffer at the first entry for the + space */ + btr_pcur_open_on_user_rec( + ibuf->index, search_tuple, PAGE_CUR_GE, BTR_MODIFY_LEAF, + &pcur, &mtr); + + if (!btr_pcur_is_on_user_rec(&pcur)) { + ut_ad(btr_pcur_is_after_last_in_tree(&pcur, &mtr)); + + goto leave_loop; + } + + for (;;) { + ut_ad(btr_pcur_is_on_user_rec(&pcur)); + + ibuf_rec = btr_pcur_get_rec(&pcur); + + /* Check if the entry is for this space */ + if (ibuf_rec_get_space(ibuf_rec) != space) { + + goto leave_loop; + } + + page_no = ibuf_rec_get_page_no(ibuf_rec); + + n_inserts++; + + /* Delete the record from ibuf */ + closed = ibuf_delete_rec(space, page_no, &pcur, search_tuple, + &mtr); + if (closed) { + /* Deletion was pessimistic and mtr was committed: + we start from the beginning again */ + + ibuf_exit(); + + goto loop; + } + + if (btr_pcur_is_after_last_on_page(&pcur)) { + mtr_commit(&mtr); + btr_pcur_close(&pcur); + + ibuf_exit(); + + goto loop; + } + } + +leave_loop: + mtr_commit(&mtr); + btr_pcur_close(&pcur); + + /* Protect our statistics keeping from race conditions */ + mutex_enter(&ibuf_mutex); + + ibuf->n_merges++; + ibuf->n_merged_recs += n_inserts; + + mutex_exit(&ibuf_mutex); + + ibuf_exit(); + + mem_heap_free(heap); +} + +/********************************************************************** +Looks if the insert buffer is empty. */ +UNIV_INTERN +ibool +ibuf_is_empty(void) +/*===============*/ + /* out: TRUE if empty */ +{ + ibool is_empty; + const page_t* root; + mtr_t mtr; + + ibuf_enter(); + + mutex_enter(&ibuf_mutex); + + mtr_start(&mtr); + + root = ibuf_tree_root_get(&mtr); + + if (page_get_n_recs(root) == 0) { + + is_empty = TRUE; + + if (ibuf->empty == FALSE) { + fprintf(stderr, + "InnoDB: Warning: insert buffer tree is empty" + " but the data struct does not\n" + "InnoDB: know it. This condition is legal" + " if the master thread has not yet\n" + "InnoDB: run to completion.\n"); + } + } else { + ut_a(ibuf->empty == FALSE); + + is_empty = FALSE; + } + + mtr_commit(&mtr); + + mutex_exit(&ibuf_mutex); + + ibuf_exit(); + + return(is_empty); +} + +/********************************************************************** +Prints info of ibuf. */ +UNIV_INTERN +void +ibuf_print( +/*=======*/ + FILE* file) /* in: file where to print */ +{ +#ifdef UNIV_IBUF_COUNT_DEBUG + ulint i; + ulint j; +#endif + + mutex_enter(&ibuf_mutex); + + fprintf(file, + "Ibuf: size %lu, free list len %lu, seg size %lu,\n" + "%lu inserts, %lu merged recs, %lu merges\n", + (ulong) ibuf->size, + (ulong) ibuf->free_list_len, + (ulong) ibuf->seg_size, + (ulong) ibuf->n_inserts, + (ulong) ibuf->n_merged_recs, + (ulong) ibuf->n_merges); +#ifdef UNIV_IBUF_COUNT_DEBUG + for (i = 0; i < IBUF_COUNT_N_SPACES; i++) { + for (j = 0; j < IBUF_COUNT_N_PAGES; j++) { + ulint count = ibuf_count_get(i, j); + + if (count > 0) { + fprintf(stderr, + "Ibuf count for space/page %lu/%lu" + " is %lu\n", + (ulong) i, (ulong) j, (ulong) count); + } + } + } +#endif /* UNIV_IBUF_COUNT_DEBUG */ + + mutex_exit(&ibuf_mutex); +} diff --git a/storage/xtradb/include/btr0btr.h b/storage/xtradb/include/btr0btr.h new file mode 100644 index 00000000000..298942bd542 --- /dev/null +++ b/storage/xtradb/include/btr0btr.h @@ -0,0 +1,494 @@ +/***************************************************************************** + +Copyright (c) 1994, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/****************************************************** +The B-tree + +Created 6/2/1994 Heikki Tuuri +*******************************************************/ + +#ifndef btr0btr_h +#define btr0btr_h + +#include "univ.i" + +#include "dict0dict.h" +#include "data0data.h" +#include "page0cur.h" +#include "mtr0mtr.h" +#include "btr0types.h" + +/* Maximum record size which can be stored on a page, without using the +special big record storage structure */ + +#define BTR_PAGE_MAX_REC_SIZE (UNIV_PAGE_SIZE / 2 - 200) + +/* Maximum depth of a B-tree in InnoDB. Note that this isn't a maximum as +such; none of the tree operations avoid producing trees bigger than this. It +is instead a "max depth that other code must work with", useful for e.g. +fixed-size arrays that must store some information about each level in a +tree. In other words: if a B-tree with bigger depth than this is +encountered, it is not acceptable for it to lead to mysterious memory +corruption, but it is acceptable for the program to die with a clear assert +failure. */ +#define BTR_MAX_LEVELS 100 + +/* Latching modes for btr_cur_search_to_nth_level(). */ +#define BTR_SEARCH_LEAF RW_S_LATCH +#define BTR_MODIFY_LEAF RW_X_LATCH +#define BTR_NO_LATCHES RW_NO_LATCH +#define BTR_MODIFY_TREE 33 +#define BTR_CONT_MODIFY_TREE 34 +#define BTR_SEARCH_PREV 35 +#define BTR_MODIFY_PREV 36 + +/* If this is ORed to the latch mode, it means that the search tuple will be +inserted to the index, at the searched position */ +#define BTR_INSERT 512 + +/* This flag ORed to latch mode says that we do the search in query +optimization */ +#define BTR_ESTIMATE 1024 + +/* This flag ORed to latch mode says that we can ignore possible +UNIQUE definition on secondary indexes when we decide if we can use the +insert buffer to speed up inserts */ +#define BTR_IGNORE_SEC_UNIQUE 2048 + +/****************************************************************** +Gets the root node of a tree and x-latches it. */ +UNIV_INTERN +page_t* +btr_root_get( +/*=========*/ + /* out: root page, x-latched */ + dict_index_t* index, /* in: index tree */ + mtr_t* mtr); /* in: mtr */ +/****************************************************************** +Gets a buffer page and declares its latching order level. */ +UNIV_INLINE +buf_block_t* +btr_block_get( +/*==========*/ + ulint space, /* in: space id */ + ulint zip_size, /* in: compressed page size in bytes + or 0 for uncompressed pages */ + ulint page_no, /* in: page number */ + ulint mode, /* in: latch mode */ + mtr_t* mtr); /* in: mtr */ +/****************************************************************** +Gets a buffer page and declares its latching order level. */ +UNIV_INLINE +page_t* +btr_page_get( +/*=========*/ + ulint space, /* in: space id */ + ulint zip_size, /* in: compressed page size in bytes + or 0 for uncompressed pages */ + ulint page_no, /* in: page number */ + ulint mode, /* in: latch mode */ + mtr_t* mtr); /* in: mtr */ +/****************************************************************** +Gets the index id field of a page. */ +UNIV_INLINE +dulint +btr_page_get_index_id( +/*==================*/ + /* out: index id */ + const page_t* page); /* in: index page */ +/************************************************************ +Gets the node level field in an index page. */ +UNIV_INLINE +ulint +btr_page_get_level_low( +/*===================*/ + /* out: level, leaf level == 0 */ + const page_t* page); /* in: index page */ +/************************************************************ +Gets the node level field in an index page. */ +UNIV_INLINE +ulint +btr_page_get_level( +/*===============*/ + /* out: level, leaf level == 0 */ + const page_t* page, /* in: index page */ + mtr_t* mtr); /* in: mini-transaction handle */ +/************************************************************ +Gets the next index page number. */ +UNIV_INLINE +ulint +btr_page_get_next( +/*==============*/ + /* out: next page number */ + const page_t* page, /* in: index page */ + mtr_t* mtr); /* in: mini-transaction handle */ +/************************************************************ +Gets the previous index page number. */ +UNIV_INLINE +ulint +btr_page_get_prev( +/*==============*/ + /* out: prev page number */ + const page_t* page, /* in: index page */ + mtr_t* mtr); /* in: mini-transaction handle */ +/***************************************************************** +Gets pointer to the previous user record in the tree. It is assumed +that the caller has appropriate latches on the page and its neighbor. */ +UNIV_INTERN +rec_t* +btr_get_prev_user_rec( +/*==================*/ + /* out: previous user record, NULL if there is none */ + rec_t* rec, /* in: record on leaf level */ + mtr_t* mtr); /* in: mtr holding a latch on the page, and if + needed, also to the previous page */ +/***************************************************************** +Gets pointer to the next user record in the tree. It is assumed +that the caller has appropriate latches on the page and its neighbor. */ +UNIV_INTERN +rec_t* +btr_get_next_user_rec( +/*==================*/ + /* out: next user record, NULL if there is none */ + rec_t* rec, /* in: record on leaf level */ + mtr_t* mtr); /* in: mtr holding a latch on the page, and if + needed, also to the next page */ +/****************************************************************** +Releases the latch on a leaf page and bufferunfixes it. */ +UNIV_INLINE +void +btr_leaf_page_release( +/*==================*/ + buf_block_t* block, /* in: buffer block */ + ulint latch_mode, /* in: BTR_SEARCH_LEAF or + BTR_MODIFY_LEAF */ + mtr_t* mtr); /* in: mtr */ +/****************************************************************** +Gets the child node file address in a node pointer. */ +UNIV_INLINE +ulint +btr_node_ptr_get_child_page_no( +/*===========================*/ + /* out: child node address */ + const rec_t* rec, /* in: node pointer record */ + const ulint* offsets);/* in: array returned by rec_get_offsets() */ +/**************************************************************** +Creates the root node for a new index tree. */ +UNIV_INTERN +ulint +btr_create( +/*=======*/ + /* out: page number of the created root, + FIL_NULL if did not succeed */ + ulint type, /* in: type of the index */ + ulint space, /* in: space where created */ + ulint zip_size,/* in: compressed page size in bytes + or 0 for uncompressed pages */ + dulint index_id,/* in: index id */ + dict_index_t* index, /* in: index */ + mtr_t* mtr); /* in: mini-transaction handle */ +/**************************************************************** +Frees a B-tree except the root page, which MUST be freed after this +by calling btr_free_root. */ +UNIV_INTERN +void +btr_free_but_not_root( +/*==================*/ + ulint space, /* in: space where created */ + ulint zip_size, /* in: compressed page size in bytes + or 0 for uncompressed pages */ + ulint root_page_no); /* in: root page number */ +/**************************************************************** +Frees the B-tree root page. Other tree MUST already have been freed. */ +UNIV_INTERN +void +btr_free_root( +/*==========*/ + ulint space, /* in: space where created */ + ulint zip_size, /* in: compressed page size in bytes + or 0 for uncompressed pages */ + ulint root_page_no, /* in: root page number */ + mtr_t* mtr); /* in: a mini-transaction which has already + been started */ +/***************************************************************** +Makes tree one level higher by splitting the root, and inserts +the tuple. It is assumed that mtr contains an x-latch on the tree. +NOTE that the operation of this function must always succeed, +we cannot reverse it: therefore enough free disk space must be +guaranteed to be available before this function is called. */ +UNIV_INTERN +rec_t* +btr_root_raise_and_insert( +/*======================*/ + /* out: inserted record */ + btr_cur_t* cursor, /* in: cursor at which to insert: must be + on the root page; when the function returns, + the cursor is positioned on the predecessor + of the inserted record */ + const dtuple_t* tuple, /* in: tuple to insert */ + ulint n_ext, /* in: number of externally stored columns */ + mtr_t* mtr); /* in: mtr */ +/***************************************************************** +Reorganizes an index page. +IMPORTANT: if btr_page_reorganize() is invoked on a compressed leaf +page of a non-clustered index, the caller must update the insert +buffer free bits in the same mini-transaction in such a way that the +modification will be redo-logged. */ +UNIV_INTERN +ibool +btr_page_reorganize( +/*================*/ + /* out: TRUE on success, FALSE on failure */ + buf_block_t* block, /* in: page to be reorganized */ + dict_index_t* index, /* in: record descriptor */ + mtr_t* mtr); /* in: mtr */ +/***************************************************************** +Decides if the page should be split at the convergence point of +inserts converging to left. */ +UNIV_INTERN +ibool +btr_page_get_split_rec_to_left( +/*===========================*/ + /* out: TRUE if split recommended */ + btr_cur_t* cursor, /* in: cursor at which to insert */ + rec_t** split_rec);/* out: if split recommended, + the first record on upper half page, + or NULL if tuple should be first */ +/***************************************************************** +Decides if the page should be split at the convergence point of +inserts converging to right. */ +UNIV_INTERN +ibool +btr_page_get_split_rec_to_right( +/*============================*/ + /* out: TRUE if split recommended */ + btr_cur_t* cursor, /* in: cursor at which to insert */ + rec_t** split_rec);/* out: if split recommended, + the first record on upper half page, + or NULL if tuple should be first */ +/***************************************************************** +Splits an index page to halves and inserts the tuple. It is assumed +that mtr holds an x-latch to the index tree. NOTE: the tree x-latch +is released within this function! NOTE that the operation of this +function must always succeed, we cannot reverse it: therefore +enough free disk space must be guaranteed to be available before +this function is called. */ +UNIV_INTERN +rec_t* +btr_page_split_and_insert( +/*======================*/ + /* out: inserted record; NOTE: the tree + x-latch is released! NOTE: 2 free disk + pages must be available! */ + btr_cur_t* cursor, /* in: cursor at which to insert; when the + function returns, the cursor is positioned + on the predecessor of the inserted record */ + const dtuple_t* tuple, /* in: tuple to insert */ + ulint n_ext, /* in: number of externally stored columns */ + mtr_t* mtr); /* in: mtr */ +/*********************************************************** +Inserts a data tuple to a tree on a non-leaf level. It is assumed +that mtr holds an x-latch on the tree. */ +UNIV_INTERN +void +btr_insert_on_non_leaf_level( +/*=========================*/ + dict_index_t* index, /* in: index */ + ulint level, /* in: level, must be > 0 */ + dtuple_t* tuple, /* in: the record to be inserted */ + mtr_t* mtr); /* in: mtr */ +/******************************************************************** +Sets a record as the predefined minimum record. */ +UNIV_INTERN +void +btr_set_min_rec_mark( +/*=================*/ + rec_t* rec, /* in/out: record */ + mtr_t* mtr); /* in: mtr */ +/***************************************************************** +Deletes on the upper level the node pointer to a page. */ +UNIV_INTERN +void +btr_node_ptr_delete( +/*================*/ + dict_index_t* index, /* in: index tree */ + buf_block_t* block, /* in: page whose node pointer is deleted */ + mtr_t* mtr); /* in: mtr */ +#ifdef UNIV_DEBUG +/**************************************************************** +Checks that the node pointer to a page is appropriate. */ +UNIV_INTERN +ibool +btr_check_node_ptr( +/*===============*/ + /* out: TRUE */ + dict_index_t* index, /* in: index tree */ + buf_block_t* block, /* in: index page */ + mtr_t* mtr); /* in: mtr */ +#endif /* UNIV_DEBUG */ +/***************************************************************** +Tries to merge the page first to the left immediate brother if such a +brother exists, and the node pointers to the current page and to the +brother reside on the same page. If the left brother does not satisfy these +conditions, looks at the right brother. If the page is the only one on that +level lifts the records of the page to the father page, thus reducing the +tree height. It is assumed that mtr holds an x-latch on the tree and on the +page. If cursor is on the leaf level, mtr must also hold x-latches to +the brothers, if they exist. */ +UNIV_INTERN +ibool +btr_compress( +/*=========*/ + /* out: TRUE on success */ + btr_cur_t* cursor, /* in: cursor on the page to merge or lift; + the page must not be empty: in record delete + use btr_discard_page if the page would become + empty */ + mtr_t* mtr); /* in: mtr */ +/***************************************************************** +Discards a page from a B-tree. This is used to remove the last record from +a B-tree page: the whole page must be removed at the same time. This cannot +be used for the root page, which is allowed to be empty. */ +UNIV_INTERN +void +btr_discard_page( +/*=============*/ + btr_cur_t* cursor, /* in: cursor on the page to discard: not on + the root page */ + mtr_t* mtr); /* in: mtr */ +/******************************************************************** +Parses the redo log record for setting an index record as the predefined +minimum record. */ +UNIV_INTERN +byte* +btr_parse_set_min_rec_mark( +/*=======================*/ + /* out: end of log record or NULL */ + byte* ptr, /* in: buffer */ + byte* end_ptr,/* in: buffer end */ + ulint comp, /* in: nonzero=compact page format */ + page_t* page, /* in: page or NULL */ + mtr_t* mtr); /* in: mtr or NULL */ +/*************************************************************** +Parses a redo log record of reorganizing a page. */ +UNIV_INTERN +byte* +btr_parse_page_reorganize( +/*======================*/ + /* out: end of log record or NULL */ + byte* ptr, /* in: buffer */ + byte* end_ptr,/* in: buffer end */ + dict_index_t* index, /* in: record descriptor */ + buf_block_t* block, /* in: page to be reorganized, or NULL */ + mtr_t* mtr); /* in: mtr or NULL */ +/****************************************************************** +Gets the number of pages in a B-tree. */ +UNIV_INTERN +ulint +btr_get_size( +/*=========*/ + /* out: number of pages */ + dict_index_t* index, /* in: index */ + ulint flag); /* in: BTR_N_LEAF_PAGES or BTR_TOTAL_SIZE */ +/****************************************************************** +Allocates a new file page to be used in an index tree. NOTE: we assume +that the caller has made the reservation for free extents! */ +UNIV_INTERN +buf_block_t* +btr_page_alloc( +/*===========*/ + /* out: new allocated block, x-latched; + NULL if out of space */ + dict_index_t* index, /* in: index tree */ + ulint hint_page_no, /* in: hint of a good page */ + byte file_direction, /* in: direction where a possible + page split is made */ + ulint level, /* in: level where the page is placed + in the tree */ + mtr_t* mtr); /* in: mtr */ +/****************************************************************** +Frees a file page used in an index tree. NOTE: cannot free field external +storage pages because the page must contain info on its level. */ +UNIV_INTERN +void +btr_page_free( +/*==========*/ + dict_index_t* index, /* in: index tree */ + buf_block_t* block, /* in: block to be freed, x-latched */ + mtr_t* mtr); /* in: mtr */ +/****************************************************************** +Frees a file page used in an index tree. Can be used also to BLOB +external storage pages, because the page level 0 can be given as an +argument. */ +UNIV_INTERN +void +btr_page_free_low( +/*==============*/ + dict_index_t* index, /* in: index tree */ + buf_block_t* block, /* in: block to be freed, x-latched */ + ulint level, /* in: page level */ + mtr_t* mtr); /* in: mtr */ +#ifdef UNIV_BTR_PRINT +/***************************************************************** +Prints size info of a B-tree. */ +UNIV_INTERN +void +btr_print_size( +/*===========*/ + dict_index_t* index); /* in: index tree */ +/****************************************************************** +Prints directories and other info of all nodes in the index. */ +UNIV_INTERN +void +btr_print_index( +/*============*/ + dict_index_t* index, /* in: index */ + ulint width); /* in: print this many entries from start + and end */ +#endif /* UNIV_BTR_PRINT */ +/**************************************************************** +Checks the size and number of fields in a record based on the definition of +the index. */ +UNIV_INTERN +ibool +btr_index_rec_validate( +/*===================*/ + /* out: TRUE if ok */ + const rec_t* rec, /* in: index record */ + const dict_index_t* index, /* in: index */ + ibool dump_on_error); /* in: TRUE if the function + should print hex dump of record + and page on error */ +/****************************************************************** +Checks the consistency of an index tree. */ +UNIV_INTERN +ibool +btr_validate_index( +/*===============*/ + /* out: TRUE if ok */ + dict_index_t* index, /* in: index */ + trx_t* trx); /* in: transaction or NULL */ + +#define BTR_N_LEAF_PAGES 1 +#define BTR_TOTAL_SIZE 2 + +#ifndef UNIV_NONINL +#include "btr0btr.ic" +#endif + +#endif diff --git a/storage/xtradb/include/btr0btr.ic b/storage/xtradb/include/btr0btr.ic new file mode 100644 index 00000000000..a8d934ecc87 --- /dev/null +++ b/storage/xtradb/include/btr0btr.ic @@ -0,0 +1,301 @@ +/***************************************************************************** + +Copyright (c) 1994, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/****************************************************** +The B-tree + +Created 6/2/1994 Heikki Tuuri +*******************************************************/ + +#include "mach0data.h" +#include "mtr0mtr.h" +#include "mtr0log.h" +#include "page0zip.h" + +#define BTR_MAX_NODE_LEVEL 50 /* used in debug checking */ + +/****************************************************************** +Gets a buffer page and declares its latching order level. */ +UNIV_INLINE +buf_block_t* +btr_block_get( +/*==========*/ + ulint space, /* in: space id */ + ulint zip_size, /* in: compressed page size in bytes + or 0 for uncompressed pages */ + ulint page_no, /* in: page number */ + ulint mode, /* in: latch mode */ + mtr_t* mtr) /* in: mtr */ +{ + buf_block_t* block; + + block = buf_page_get(space, zip_size, page_no, mode, mtr); + + if (mode != RW_NO_LATCH) { + + buf_block_dbg_add_level(block, SYNC_TREE_NODE); + } + + return(block); +} + +/****************************************************************** +Gets a buffer page and declares its latching order level. */ +UNIV_INLINE +page_t* +btr_page_get( +/*=========*/ + ulint space, /* in: space id */ + ulint zip_size, /* in: compressed page size in bytes + or 0 for uncompressed pages */ + ulint page_no, /* in: page number */ + ulint mode, /* in: latch mode */ + mtr_t* mtr) /* in: mtr */ +{ + return(buf_block_get_frame(btr_block_get(space, zip_size, page_no, + mode, mtr))); +} + +/****************************************************************** +Sets the index id field of a page. */ +UNIV_INLINE +void +btr_page_set_index_id( +/*==================*/ + page_t* page, /* in: page to be created */ + page_zip_des_t* page_zip,/* in: compressed page whose uncompressed + part will be updated, or NULL */ + dulint id, /* in: index id */ + mtr_t* mtr) /* in: mtr */ +{ + if (UNIV_LIKELY_NULL(page_zip)) { + mach_write_to_8(page + (PAGE_HEADER + PAGE_INDEX_ID), id); + page_zip_write_header(page_zip, + page + (PAGE_HEADER + PAGE_INDEX_ID), + 8, mtr); + } else { + mlog_write_dulint(page + (PAGE_HEADER + PAGE_INDEX_ID), + id, mtr); + } +} + +/****************************************************************** +Gets the index id field of a page. */ +UNIV_INLINE +dulint +btr_page_get_index_id( +/*==================*/ + /* out: index id */ + const page_t* page) /* in: index page */ +{ + return(mach_read_from_8(page + PAGE_HEADER + PAGE_INDEX_ID)); +} + +/************************************************************ +Gets the node level field in an index page. */ +UNIV_INLINE +ulint +btr_page_get_level_low( +/*===================*/ + /* out: level, leaf level == 0 */ + const page_t* page) /* in: index page */ +{ + ulint level; + + ut_ad(page); + + level = mach_read_from_2(page + PAGE_HEADER + PAGE_LEVEL); + + ut_ad(level <= BTR_MAX_NODE_LEVEL); + + return(level); +} + +/************************************************************ +Gets the node level field in an index page. */ +UNIV_INLINE +ulint +btr_page_get_level( +/*===============*/ + /* out: level, leaf level == 0 */ + const page_t* page, /* in: index page */ + mtr_t* mtr __attribute__((unused))) + /* in: mini-transaction handle */ +{ + ut_ad(page && mtr); + + return(btr_page_get_level_low(page)); +} + +/************************************************************ +Sets the node level field in an index page. */ +UNIV_INLINE +void +btr_page_set_level( +/*===============*/ + page_t* page, /* in: index page */ + page_zip_des_t* page_zip,/* in: compressed page whose uncompressed + part will be updated, or NULL */ + ulint level, /* in: level, leaf level == 0 */ + mtr_t* mtr) /* in: mini-transaction handle */ +{ + ut_ad(page && mtr); + ut_ad(level <= BTR_MAX_NODE_LEVEL); + + if (UNIV_LIKELY_NULL(page_zip)) { + mach_write_to_2(page + (PAGE_HEADER + PAGE_LEVEL), level); + page_zip_write_header(page_zip, + page + (PAGE_HEADER + PAGE_LEVEL), + 2, mtr); + } else { + mlog_write_ulint(page + (PAGE_HEADER + PAGE_LEVEL), level, + MLOG_2BYTES, mtr); + } +} + +/************************************************************ +Gets the next index page number. */ +UNIV_INLINE +ulint +btr_page_get_next( +/*==============*/ + /* out: next page number */ + const page_t* page, /* in: index page */ + mtr_t* mtr __attribute__((unused))) + /* in: mini-transaction handle */ +{ + ut_ad(page && mtr); + ut_ad(mtr_memo_contains_page(mtr, page, MTR_MEMO_PAGE_X_FIX) + || mtr_memo_contains_page(mtr, page, MTR_MEMO_PAGE_S_FIX)); + + return(mach_read_from_4(page + FIL_PAGE_NEXT)); +} + +/************************************************************ +Sets the next index page field. */ +UNIV_INLINE +void +btr_page_set_next( +/*==============*/ + page_t* page, /* in: index page */ + page_zip_des_t* page_zip,/* in: compressed page whose uncompressed + part will be updated, or NULL */ + ulint next, /* in: next page number */ + mtr_t* mtr) /* in: mini-transaction handle */ +{ + ut_ad(page && mtr); + + if (UNIV_LIKELY_NULL(page_zip)) { + mach_write_to_4(page + FIL_PAGE_NEXT, next); + page_zip_write_header(page_zip, page + FIL_PAGE_NEXT, 4, mtr); + } else { + mlog_write_ulint(page + FIL_PAGE_NEXT, next, MLOG_4BYTES, mtr); + } +} + +/************************************************************ +Gets the previous index page number. */ +UNIV_INLINE +ulint +btr_page_get_prev( +/*==============*/ + /* out: prev page number */ + const page_t* page, /* in: index page */ + mtr_t* mtr __attribute__((unused))) /* in: mini-transaction handle */ +{ + ut_ad(page && mtr); + + return(mach_read_from_4(page + FIL_PAGE_PREV)); +} + +/************************************************************ +Sets the previous index page field. */ +UNIV_INLINE +void +btr_page_set_prev( +/*==============*/ + page_t* page, /* in: index page */ + page_zip_des_t* page_zip,/* in: compressed page whose uncompressed + part will be updated, or NULL */ + ulint prev, /* in: previous page number */ + mtr_t* mtr) /* in: mini-transaction handle */ +{ + ut_ad(page && mtr); + + if (UNIV_LIKELY_NULL(page_zip)) { + mach_write_to_4(page + FIL_PAGE_PREV, prev); + page_zip_write_header(page_zip, page + FIL_PAGE_PREV, 4, mtr); + } else { + mlog_write_ulint(page + FIL_PAGE_PREV, prev, MLOG_4BYTES, mtr); + } +} + +/****************************************************************** +Gets the child node file address in a node pointer. */ +UNIV_INLINE +ulint +btr_node_ptr_get_child_page_no( +/*===========================*/ + /* out: child node address */ + const rec_t* rec, /* in: node pointer record */ + const ulint* offsets)/* in: array returned by rec_get_offsets() */ +{ + const byte* field; + ulint len; + ulint page_no; + + ut_ad(!rec_offs_comp(offsets) || rec_get_node_ptr_flag(rec)); + + /* The child address is in the last field */ + field = rec_get_nth_field(rec, offsets, + rec_offs_n_fields(offsets) - 1, &len); + + ut_ad(len == 4); + + page_no = mach_read_from_4(field); + + if (UNIV_UNLIKELY(page_no == 0)) { + fprintf(stderr, + "InnoDB: a nonsensical page number 0" + " in a node ptr record at offset %lu\n", + (ulong) page_offset(rec)); + buf_page_print(page_align(rec), 0); + } + + return(page_no); +} + +/****************************************************************** +Releases the latches on a leaf page and bufferunfixes it. */ +UNIV_INLINE +void +btr_leaf_page_release( +/*==================*/ + buf_block_t* block, /* in: buffer block */ + ulint latch_mode, /* in: BTR_SEARCH_LEAF or + BTR_MODIFY_LEAF */ + mtr_t* mtr) /* in: mtr */ +{ + ut_ad(latch_mode == BTR_SEARCH_LEAF || latch_mode == BTR_MODIFY_LEAF); + ut_ad(!mtr_memo_contains(mtr, block, MTR_MEMO_MODIFY)); + + mtr_memo_release(mtr, block, + latch_mode == BTR_SEARCH_LEAF + ? MTR_MEMO_PAGE_S_FIX + : MTR_MEMO_PAGE_X_FIX); +} diff --git a/storage/xtradb/include/btr0cur.h b/storage/xtradb/include/btr0cur.h new file mode 100644 index 00000000000..c3a478c4bb7 --- /dev/null +++ b/storage/xtradb/include/btr0cur.h @@ -0,0 +1,742 @@ +/***************************************************************************** + +Copyright (c) 1994, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/****************************************************** +The index tree cursor + +Created 10/16/1994 Heikki Tuuri +*******************************************************/ + +#ifndef btr0cur_h +#define btr0cur_h + +#include "univ.i" +#include "dict0dict.h" +#include "page0cur.h" +#include "btr0types.h" +#include "que0types.h" +#include "row0types.h" +#include "ha0ha.h" + +/* Mode flags for btr_cur operations; these can be ORed */ +#define BTR_NO_UNDO_LOG_FLAG 1 /* do no undo logging */ +#define BTR_NO_LOCKING_FLAG 2 /* do no record lock checking */ +#define BTR_KEEP_SYS_FLAG 4 /* sys fields will be found from the + update vector or inserted entry */ + +#define BTR_CUR_ADAPT +#define BTR_CUR_HASH_ADAPT + +#ifdef UNIV_DEBUG +/************************************************************* +Returns the page cursor component of a tree cursor. */ +UNIV_INLINE +page_cur_t* +btr_cur_get_page_cur( +/*=================*/ + /* out: pointer to page cursor + component */ + const btr_cur_t* cursor);/* in: tree cursor */ +#else /* UNIV_DEBUG */ +# define btr_cur_get_page_cur(cursor) (&(cursor)->page_cur) +#endif /* UNIV_DEBUG */ +/************************************************************* +Returns the buffer block on which the tree cursor is positioned. */ +UNIV_INLINE +buf_block_t* +btr_cur_get_block( +/*==============*/ + /* out: pointer to buffer block */ + btr_cur_t* cursor);/* in: tree cursor */ +/************************************************************* +Returns the record pointer of a tree cursor. */ +UNIV_INLINE +rec_t* +btr_cur_get_rec( +/*============*/ + /* out: pointer to record */ + btr_cur_t* cursor);/* in: tree cursor */ +/************************************************************* +Returns the compressed page on which the tree cursor is positioned. */ +UNIV_INLINE +page_zip_des_t* +btr_cur_get_page_zip( +/*=================*/ + /* out: pointer to compressed page, + or NULL if the page is not compressed */ + btr_cur_t* cursor);/* in: tree cursor */ +/************************************************************* +Invalidates a tree cursor by setting record pointer to NULL. */ +UNIV_INLINE +void +btr_cur_invalidate( +/*===============*/ + btr_cur_t* cursor);/* in: tree cursor */ +/************************************************************* +Returns the page of a tree cursor. */ +UNIV_INLINE +page_t* +btr_cur_get_page( +/*=============*/ + /* out: pointer to page */ + btr_cur_t* cursor);/* in: tree cursor */ +/************************************************************* +Returns the index of a cursor. */ +UNIV_INLINE +dict_index_t* +btr_cur_get_index( +/*==============*/ + /* out: index */ + btr_cur_t* cursor);/* in: B-tree cursor */ +/************************************************************* +Positions a tree cursor at a given record. */ +UNIV_INLINE +void +btr_cur_position( +/*=============*/ + dict_index_t* index, /* in: index */ + rec_t* rec, /* in: record in tree */ + buf_block_t* block, /* in: buffer block of rec */ + btr_cur_t* cursor);/* in: cursor */ +/************************************************************************ +Searches an index tree and positions a tree cursor on a given level. +NOTE: n_fields_cmp in tuple must be set so that it cannot be compared +to node pointer page number fields on the upper levels of the tree! +Note that if mode is PAGE_CUR_LE, which is used in inserts, then +cursor->up_match and cursor->low_match both will have sensible values. +If mode is PAGE_CUR_GE, then up_match will a have a sensible value. */ +UNIV_INTERN +void +btr_cur_search_to_nth_level( +/*========================*/ + dict_index_t* index, /* in: index */ + ulint level, /* in: the tree level of search */ + const dtuple_t* tuple, /* in: data tuple; NOTE: n_fields_cmp in + tuple must be set so that it cannot get + compared to the node ptr page number field! */ + ulint mode, /* in: PAGE_CUR_L, ...; + NOTE that if the search is made using a unique + prefix of a record, mode should be PAGE_CUR_LE, + not PAGE_CUR_GE, as the latter may end up on + the previous page of the record! Inserts + should always be made using PAGE_CUR_LE to + search the position! */ + ulint latch_mode, /* in: BTR_SEARCH_LEAF, ..., ORed with + BTR_INSERT and BTR_ESTIMATE; + cursor->left_block is used to store a pointer + to the left neighbor page, in the cases + BTR_SEARCH_PREV and BTR_MODIFY_PREV; + NOTE that if has_search_latch + is != 0, we maybe do not have a latch set + on the cursor page, we assume + the caller uses his search latch + to protect the record! */ + btr_cur_t* cursor, /* in/out: tree cursor; the cursor page is + s- or x-latched, but see also above! */ + ulint has_search_latch,/* in: latch mode the caller + currently has on btr_search_latch: + RW_S_LATCH, or 0 */ + mtr_t* mtr); /* in: mtr */ +/********************************************************************* +Opens a cursor at either end of an index. */ +UNIV_INTERN +void +btr_cur_open_at_index_side( +/*=======================*/ + ibool from_left, /* in: TRUE if open to the low end, + FALSE if to the high end */ + dict_index_t* index, /* in: index */ + ulint latch_mode, /* in: latch mode */ + btr_cur_t* cursor, /* in: cursor */ + mtr_t* mtr); /* in: mtr */ +/************************************************************************** +Positions a cursor at a randomly chosen position within a B-tree. */ +UNIV_INTERN +void +btr_cur_open_at_rnd_pos( +/*====================*/ + dict_index_t* index, /* in: index */ + ulint latch_mode, /* in: BTR_SEARCH_LEAF, ... */ + btr_cur_t* cursor, /* in/out: B-tree cursor */ + mtr_t* mtr); /* in: mtr */ +/***************************************************************** +Tries to perform an insert to a page in an index tree, next to cursor. +It is assumed that mtr holds an x-latch on the page. The operation does +not succeed if there is too little space on the page. If there is just +one record on the page, the insert will always succeed; this is to +prevent trying to split a page with just one record. */ +UNIV_INTERN +ulint +btr_cur_optimistic_insert( +/*======================*/ + /* out: DB_SUCCESS, DB_WAIT_LOCK, + DB_FAIL, or error number */ + ulint flags, /* in: undo logging and locking flags: if not + zero, the parameters index and thr should be + specified */ + btr_cur_t* cursor, /* in: cursor on page after which to insert; + cursor stays valid */ + dtuple_t* entry, /* in/out: entry to insert */ + rec_t** rec, /* out: pointer to inserted record if + succeed */ + big_rec_t** big_rec,/* out: big rec vector whose fields have to + be stored externally by the caller, or + NULL */ + ulint n_ext, /* in: number of externally stored columns */ + que_thr_t* thr, /* in: query thread or NULL */ + mtr_t* mtr); /* in: mtr; if this function returns + DB_SUCCESS on a leaf page of a secondary + index in a compressed tablespace, the + mtr must be committed before latching + any further pages */ +/***************************************************************** +Performs an insert on a page of an index tree. It is assumed that mtr +holds an x-latch on the tree and on the cursor page. If the insert is +made on the leaf level, to avoid deadlocks, mtr must also own x-latches +to brothers of page, if those brothers exist. */ +UNIV_INTERN +ulint +btr_cur_pessimistic_insert( +/*=======================*/ + /* out: DB_SUCCESS or error number */ + ulint flags, /* in: undo logging and locking flags: if not + zero, the parameter thr should be + specified; if no undo logging is specified, + then the caller must have reserved enough + free extents in the file space so that the + insertion will certainly succeed */ + btr_cur_t* cursor, /* in: cursor after which to insert; + cursor stays valid */ + dtuple_t* entry, /* in/out: entry to insert */ + rec_t** rec, /* out: pointer to inserted record if + succeed */ + big_rec_t** big_rec,/* out: big rec vector whose fields have to + be stored externally by the caller, or + NULL */ + ulint n_ext, /* in: number of externally stored columns */ + que_thr_t* thr, /* in: query thread or NULL */ + mtr_t* mtr); /* in: mtr */ +/***************************************************************** +Updates a record when the update causes no size changes in its fields. */ +UNIV_INTERN +ulint +btr_cur_update_in_place( +/*====================*/ + /* out: DB_SUCCESS or error number */ + ulint flags, /* in: undo logging and locking flags */ + btr_cur_t* cursor, /* in: cursor on the record to update; + cursor stays valid and positioned on the + same record */ + const upd_t* update, /* in: update vector */ + ulint cmpl_info,/* in: compiler info on secondary index + updates */ + que_thr_t* thr, /* in: query thread */ + mtr_t* mtr); /* in: mtr; must be committed before + latching any further pages */ +/***************************************************************** +Tries to update a record on a page in an index tree. It is assumed that mtr +holds an x-latch on the page. The operation does not succeed if there is too +little space on the page or if the update would result in too empty a page, +so that tree compression is recommended. */ +UNIV_INTERN +ulint +btr_cur_optimistic_update( +/*======================*/ + /* out: DB_SUCCESS, or DB_OVERFLOW if the + updated record does not fit, DB_UNDERFLOW + if the page would become too empty, or + DB_ZIP_OVERFLOW if there is not enough + space left on the compressed page */ + ulint flags, /* in: undo logging and locking flags */ + btr_cur_t* cursor, /* in: cursor on the record to update; + cursor stays valid and positioned on the + same record */ + const upd_t* update, /* in: update vector; this must also + contain trx id and roll ptr fields */ + ulint cmpl_info,/* in: compiler info on secondary index + updates */ + que_thr_t* thr, /* in: query thread */ + mtr_t* mtr); /* in: mtr; must be committed before + latching any further pages */ +/***************************************************************** +Performs an update of a record on a page of a tree. It is assumed +that mtr holds an x-latch on the tree and on the cursor page. If the +update is made on the leaf level, to avoid deadlocks, mtr must also +own x-latches to brothers of page, if those brothers exist. */ +UNIV_INTERN +ulint +btr_cur_pessimistic_update( +/*=======================*/ + /* out: DB_SUCCESS or error code */ + ulint flags, /* in: undo logging, locking, and rollback + flags */ + btr_cur_t* cursor, /* in: cursor on the record to update */ + mem_heap_t** heap, /* in/out: pointer to memory heap, or NULL */ + big_rec_t** big_rec,/* out: big rec vector whose fields have to + be stored externally by the caller, or NULL */ + const upd_t* update, /* in: update vector; this is allowed also + contain trx id and roll ptr fields, but + the values in update vector have no effect */ + ulint cmpl_info,/* in: compiler info on secondary index + updates */ + que_thr_t* thr, /* in: query thread */ + mtr_t* mtr); /* in: mtr; must be committed before + latching any further pages */ +/*************************************************************** +Marks a clustered index record deleted. Writes an undo log record to +undo log on this delete marking. Writes in the trx id field the id +of the deleting transaction, and in the roll ptr field pointer to the +undo log record created. */ +UNIV_INTERN +ulint +btr_cur_del_mark_set_clust_rec( +/*===========================*/ + /* out: DB_SUCCESS, DB_LOCK_WAIT, or error + number */ + ulint flags, /* in: undo logging and locking flags */ + btr_cur_t* cursor, /* in: cursor */ + ibool val, /* in: value to set */ + que_thr_t* thr, /* in: query thread */ + mtr_t* mtr); /* in: mtr */ +/*************************************************************** +Sets a secondary index record delete mark to TRUE or FALSE. */ +UNIV_INTERN +ulint +btr_cur_del_mark_set_sec_rec( +/*=========================*/ + /* out: DB_SUCCESS, DB_LOCK_WAIT, or error + number */ + ulint flags, /* in: locking flag */ + btr_cur_t* cursor, /* in: cursor */ + ibool val, /* in: value to set */ + que_thr_t* thr, /* in: query thread */ + mtr_t* mtr); /* in: mtr */ +/*************************************************************** +Clear a secondary index record's delete mark. This function is only +used by the insert buffer insert merge mechanism. */ +UNIV_INTERN +void +btr_cur_del_unmark_for_ibuf( +/*========================*/ + rec_t* rec, /* in/out: record to delete unmark */ + page_zip_des_t* page_zip, /* in/out: compressed page + corresponding to rec, or NULL + when the tablespace is + uncompressed */ + mtr_t* mtr); /* in: mtr */ +/***************************************************************** +Tries to compress a page of the tree if it seems useful. It is assumed +that mtr holds an x-latch on the tree and on the cursor page. To avoid +deadlocks, mtr must also own x-latches to brothers of page, if those +brothers exist. NOTE: it is assumed that the caller has reserved enough +free extents so that the compression will always succeed if done! */ +UNIV_INTERN +ibool +btr_cur_compress_if_useful( +/*=======================*/ + /* out: TRUE if compression occurred */ + btr_cur_t* cursor, /* in: cursor on the page to compress; + cursor does not stay valid if compression + occurs */ + mtr_t* mtr); /* in: mtr */ +/*********************************************************** +Removes the record on which the tree cursor is positioned. It is assumed +that the mtr has an x-latch on the page where the cursor is positioned, +but no latch on the whole tree. */ +UNIV_INTERN +ibool +btr_cur_optimistic_delete( +/*======================*/ + /* out: TRUE if success, i.e., the page + did not become too empty */ + btr_cur_t* cursor, /* in: cursor on the record to delete; + cursor stays valid: if deletion succeeds, + on function exit it points to the successor + of the deleted record */ + mtr_t* mtr); /* in: mtr; if this function returns + TRUE on a leaf page of a secondary + index, the mtr must be committed + before latching any further pages */ +/***************************************************************** +Removes the record on which the tree cursor is positioned. Tries +to compress the page if its fillfactor drops below a threshold +or if it is the only page on the level. It is assumed that mtr holds +an x-latch on the tree and on the cursor page. To avoid deadlocks, +mtr must also own x-latches to brothers of page, if those brothers +exist. */ +UNIV_INTERN +ibool +btr_cur_pessimistic_delete( +/*=======================*/ + /* out: TRUE if compression occurred */ + ulint* err, /* out: DB_SUCCESS or DB_OUT_OF_FILE_SPACE; + the latter may occur because we may have + to update node pointers on upper levels, + and in the case of variable length keys + these may actually grow in size */ + ibool has_reserved_extents, /* in: TRUE if the + caller has already reserved enough free + extents so that he knows that the operation + will succeed */ + btr_cur_t* cursor, /* in: cursor on the record to delete; + if compression does not occur, the cursor + stays valid: it points to successor of + deleted record on function exit */ + enum trx_rb_ctx rb_ctx, /* in: rollback context */ + mtr_t* mtr); /* in: mtr */ +/*************************************************************** +Parses a redo log record of updating a record in-place. */ +UNIV_INTERN +byte* +btr_cur_parse_update_in_place( +/*==========================*/ + /* out: end of log record or NULL */ + byte* ptr, /* in: buffer */ + byte* end_ptr,/* in: buffer end */ + page_t* page, /* in/out: page or NULL */ + page_zip_des_t* page_zip,/* in/out: compressed page, or NULL */ + dict_index_t* index); /* in: index corresponding to page */ +/******************************************************************** +Parses the redo log record for delete marking or unmarking of a clustered +index record. */ +UNIV_INTERN +byte* +btr_cur_parse_del_mark_set_clust_rec( +/*=================================*/ + /* out: end of log record or NULL */ + byte* ptr, /* in: buffer */ + byte* end_ptr,/* in: buffer end */ + page_t* page, /* in/out: page or NULL */ + page_zip_des_t* page_zip,/* in/out: compressed page, or NULL */ + dict_index_t* index); /* in: index corresponding to page */ +/******************************************************************** +Parses the redo log record for delete marking or unmarking of a secondary +index record. */ +UNIV_INTERN +byte* +btr_cur_parse_del_mark_set_sec_rec( +/*===============================*/ + /* out: end of log record or NULL */ + byte* ptr, /* in: buffer */ + byte* end_ptr,/* in: buffer end */ + page_t* page, /* in/out: page or NULL */ + page_zip_des_t* page_zip);/* in/out: compressed page, or NULL */ +/*********************************************************************** +Estimates the number of rows in a given index range. */ +UNIV_INTERN +ib_int64_t +btr_estimate_n_rows_in_range( +/*=========================*/ + /* out: estimated number of rows */ + dict_index_t* index, /* in: index */ + const dtuple_t* tuple1, /* in: range start, may also be empty tuple */ + ulint mode1, /* in: search mode for range start */ + const dtuple_t* tuple2, /* in: range end, may also be empty tuple */ + ulint mode2); /* in: search mode for range end */ +/*********************************************************************** +Estimates the number of different key values in a given index, for +each n-column prefix of the index where n <= dict_index_get_n_unique(index). +The estimates are stored in the array index->stat_n_diff_key_vals. */ +UNIV_INTERN +void +btr_estimate_number_of_different_key_vals( +/*======================================*/ + dict_index_t* index); /* in: index */ +/*********************************************************************** +Marks not updated extern fields as not-owned by this record. The ownership +is transferred to the updated record which is inserted elsewhere in the +index tree. In purge only the owner of externally stored field is allowed +to free the field. */ +UNIV_INTERN +void +btr_cur_mark_extern_inherited_fields( +/*=================================*/ + page_zip_des_t* page_zip,/* in/out: compressed page whose uncompressed + part will be updated, or NULL */ + rec_t* rec, /* in/out: record in a clustered index */ + dict_index_t* index, /* in: index of the page */ + const ulint* offsets,/* in: array returned by rec_get_offsets() */ + const upd_t* update, /* in: update vector */ + mtr_t* mtr); /* in: mtr, or NULL if not logged */ +/*********************************************************************** +The complement of the previous function: in an update entry may inherit +some externally stored fields from a record. We must mark them as inherited +in entry, so that they are not freed in a rollback. */ +UNIV_INTERN +void +btr_cur_mark_dtuple_inherited_extern( +/*=================================*/ + dtuple_t* entry, /* in/out: updated entry to be + inserted to clustered index */ + const upd_t* update); /* in: update vector */ +/*********************************************************************** +Marks all extern fields in a dtuple as owned by the record. */ +UNIV_INTERN +void +btr_cur_unmark_dtuple_extern_fields( +/*================================*/ + dtuple_t* entry); /* in/out: clustered index entry */ +/*********************************************************************** +Stores the fields in big_rec_vec to the tablespace and puts pointers to +them in rec. The extern flags in rec will have to be set beforehand. +The fields are stored on pages allocated from leaf node +file segment of the index tree. */ +UNIV_INTERN +ulint +btr_store_big_rec_extern_fields( +/*============================*/ + /* out: DB_SUCCESS or error */ + dict_index_t* index, /* in: index of rec; the index tree + MUST be X-latched */ + buf_block_t* rec_block, /* in/out: block containing rec */ + rec_t* rec, /* in: record */ + const ulint* offsets, /* in: rec_get_offsets(rec, index); + the "external storage" flags in offsets + will not correspond to rec when + this function returns */ + big_rec_t* big_rec_vec, /* in: vector containing fields + to be stored externally */ + mtr_t* local_mtr); /* in: mtr containing the latch to + rec and to the tree */ +/*********************************************************************** +Frees the space in an externally stored field to the file space +management if the field in data is owned the externally stored field, +in a rollback we may have the additional condition that the field must +not be inherited. */ +UNIV_INTERN +void +btr_free_externally_stored_field( +/*=============================*/ + dict_index_t* index, /* in: index of the data, the index + tree MUST be X-latched; if the tree + height is 1, then also the root page + must be X-latched! (this is relevant + in the case this function is called + from purge where 'data' is located on + an undo log page, not an index + page) */ + byte* field_ref, /* in/out: field reference */ + const rec_t* rec, /* in: record containing field_ref, for + page_zip_write_blob_ptr(), or NULL */ + const ulint* offsets, /* in: rec_get_offsets(rec, index), + or NULL */ + page_zip_des_t* page_zip, /* in: compressed page corresponding + to rec, or NULL if rec == NULL */ + ulint i, /* in: field number of field_ref; + ignored if rec == NULL */ + enum trx_rb_ctx rb_ctx, /* in: rollback context */ + mtr_t* local_mtr); /* in: mtr containing the latch to + data an an X-latch to the index + tree */ +/*********************************************************************** +Copies the prefix of an externally stored field of a record. The +clustered index record must be protected by a lock or a page latch. */ +UNIV_INTERN +ulint +btr_copy_externally_stored_field_prefix( +/*====================================*/ + /* out: the length of the copied field, + or 0 if the column is being or has been + deleted */ + byte* buf, /* out: the field, or a prefix of it */ + ulint len, /* in: length of buf, in bytes */ + ulint zip_size,/* in: nonzero=compressed BLOB page size, + zero for uncompressed BLOBs */ + const byte* data, /* in: 'internally' stored part of the + field containing also the reference to + the external part; must be protected by + a lock or a page latch */ + ulint local_len);/* in: length of data, in bytes */ +/*********************************************************************** +Copies an externally stored field of a record to mem heap. */ +UNIV_INTERN +byte* +btr_rec_copy_externally_stored_field( +/*=================================*/ + /* out: the field copied to heap */ + const rec_t* rec, /* in: record in a clustered index; + must be protected by a lock or a page latch */ + const ulint* offsets,/* in: array returned by rec_get_offsets() */ + ulint zip_size,/* in: nonzero=compressed BLOB page size, + zero for uncompressed BLOBs */ + ulint no, /* in: field number */ + ulint* len, /* out: length of the field */ + mem_heap_t* heap); /* in: mem heap */ +/*********************************************************************** +Flags the data tuple fields that are marked as extern storage in the +update vector. We use this function to remember which fields we must +mark as extern storage in a record inserted for an update. */ +UNIV_INTERN +ulint +btr_push_update_extern_fields( +/*==========================*/ + /* out: number of flagged external columns */ + dtuple_t* tuple, /* in/out: data tuple */ + const upd_t* update, /* in: update vector */ + mem_heap_t* heap) /* in: memory heap */ + __attribute__((nonnull)); + +/*######################################################################*/ + +/* In the pessimistic delete, if the page data size drops below this +limit, merging it to a neighbor is tried */ + +#define BTR_CUR_PAGE_COMPRESS_LIMIT (UNIV_PAGE_SIZE / 2) + +/* A slot in the path array. We store here info on a search path down the +tree. Each slot contains data on a single level of the tree. */ + +typedef struct btr_path_struct btr_path_t; +struct btr_path_struct{ + ulint nth_rec; /* index of the record + where the page cursor stopped on + this level (index in alphabetical + order); value ULINT_UNDEFINED + denotes array end */ + ulint n_recs; /* number of records on the page */ +}; + +#define BTR_PATH_ARRAY_N_SLOTS 250 /* size of path array (in slots) */ + +/* The tree cursor: the definition appears here only for the compiler +to know struct size! */ + +struct btr_cur_struct { + dict_index_t* index; /* index where positioned */ + page_cur_t page_cur; /* page cursor */ + buf_block_t* left_block; /* this field is used to store + a pointer to the left neighbor + page, in the cases + BTR_SEARCH_PREV and + BTR_MODIFY_PREV */ + /*------------------------------*/ + que_thr_t* thr; /* this field is only used when + btr_cur_search_... is called for an + index entry insertion: the calling + query thread is passed here to be + used in the insert buffer */ + /*------------------------------*/ + /* The following fields are used in btr_cur_search... to pass + information: */ + ulint flag; /* BTR_CUR_HASH, BTR_CUR_HASH_FAIL, + BTR_CUR_BINARY, or + BTR_CUR_INSERT_TO_IBUF */ + ulint tree_height; /* Tree height if the search is done + for a pessimistic insert or update + operation */ + ulint up_match; /* If the search mode was PAGE_CUR_LE, + the number of matched fields to the + the first user record to the right of + the cursor record after + btr_cur_search_...; + for the mode PAGE_CUR_GE, the matched + fields to the first user record AT THE + CURSOR or to the right of it; + NOTE that the up_match and low_match + values may exceed the correct values + for comparison to the adjacent user + record if that record is on a + different leaf page! (See the note in + row_ins_duplicate_key.) */ + ulint up_bytes; /* number of matched bytes to the + right at the time cursor positioned; + only used internally in searches: not + defined after the search */ + ulint low_match; /* if search mode was PAGE_CUR_LE, + the number of matched fields to the + first user record AT THE CURSOR or + to the left of it after + btr_cur_search_...; + NOT defined for PAGE_CUR_GE or any + other search modes; see also the NOTE + in up_match! */ + ulint low_bytes; /* number of matched bytes to the + right at the time cursor positioned; + only used internally in searches: not + defined after the search */ + ulint n_fields; /* prefix length used in a hash + search if hash_node != NULL */ + ulint n_bytes; /* hash prefix bytes if hash_node != + NULL */ + ulint fold; /* fold value used in the search if + flag is BTR_CUR_HASH */ + /*------------------------------*/ + btr_path_t* path_arr; /* in estimating the number of + rows in range, we store in this array + information of the path through + the tree */ +}; + +/* Values for the flag documenting the used search method */ +#define BTR_CUR_HASH 1 /* successful shortcut using the hash + index */ +#define BTR_CUR_HASH_FAIL 2 /* failure using hash, success using + binary search: the misleading hash + reference is stored in the field + hash_node, and might be necessary to + update */ +#define BTR_CUR_BINARY 3 /* success using the binary search */ +#define BTR_CUR_INSERT_TO_IBUF 4 /* performed the intended insert to + the insert buffer */ + +/* If pessimistic delete fails because of lack of file space, +there is still a good change of success a little later: try this many times, +and sleep this many microseconds in between */ +#define BTR_CUR_RETRY_DELETE_N_TIMES 100 +#define BTR_CUR_RETRY_SLEEP_TIME 50000 + +/* The reference in a field for which data is stored on a different page. +The reference is at the end of the 'locally' stored part of the field. +'Locally' means storage in the index record. +We store locally a long enough prefix of each column so that we can determine +the ordering parts of each index record without looking into the externally +stored part. */ + +/*--------------------------------------*/ +#define BTR_EXTERN_SPACE_ID 0 /* space id where stored */ +#define BTR_EXTERN_PAGE_NO 4 /* page no where stored */ +#define BTR_EXTERN_OFFSET 8 /* offset of BLOB header + on that page */ +#define BTR_EXTERN_LEN 12 /* 8 bytes containing the + length of the externally + stored part of the BLOB. + The 2 highest bits are + reserved to the flags below. */ +/*--------------------------------------*/ +/* #define BTR_EXTERN_FIELD_REF_SIZE 20 // moved to btr0types.h */ + +/* The highest bit of BTR_EXTERN_LEN (i.e., the highest bit of the byte +at lowest address) is set to 1 if this field does not 'own' the externally +stored field; only the owner field is allowed to free the field in purge! +If the 2nd highest bit is 1 then it means that the externally stored field +was inherited from an earlier version of the row. In rollback we are not +allowed to free an inherited external field. */ + +#define BTR_EXTERN_OWNER_FLAG 128 +#define BTR_EXTERN_INHERITED_FLAG 64 + +extern ulint btr_cur_n_non_sea; +extern ulint btr_cur_n_sea; +extern ulint btr_cur_n_non_sea_old; +extern ulint btr_cur_n_sea_old; + +#ifndef UNIV_NONINL +#include "btr0cur.ic" +#endif + +#endif diff --git a/storage/xtradb/include/btr0cur.ic b/storage/xtradb/include/btr0cur.ic new file mode 100644 index 00000000000..84a3a5cba0b --- /dev/null +++ b/storage/xtradb/include/btr0cur.ic @@ -0,0 +1,200 @@ +/***************************************************************************** + +Copyright (c) 1994, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/****************************************************** +The index tree cursor + +Created 10/16/1994 Heikki Tuuri +*******************************************************/ + +#include "btr0btr.h" + +#ifdef UNIV_DEBUG +/************************************************************* +Returns the page cursor component of a tree cursor. */ +UNIV_INLINE +page_cur_t* +btr_cur_get_page_cur( +/*=================*/ + /* out: pointer to page cursor + component */ + const btr_cur_t* cursor) /* in: tree cursor */ +{ + return(&((btr_cur_t*) cursor)->page_cur); +} +#endif /* UNIV_DEBUG */ +/************************************************************* +Returns the buffer block on which the tree cursor is positioned. */ +UNIV_INLINE +buf_block_t* +btr_cur_get_block( +/*==============*/ + /* out: pointer to buffer block */ + btr_cur_t* cursor) /* in: tree cursor */ +{ + return(page_cur_get_block(btr_cur_get_page_cur(cursor))); +} + +/************************************************************* +Returns the record pointer of a tree cursor. */ +UNIV_INLINE +rec_t* +btr_cur_get_rec( +/*============*/ + /* out: pointer to record */ + btr_cur_t* cursor) /* in: tree cursor */ +{ + return(page_cur_get_rec(&(cursor->page_cur))); +} + +/************************************************************* +Returns the compressed page on which the tree cursor is positioned. */ +UNIV_INLINE +page_zip_des_t* +btr_cur_get_page_zip( +/*=================*/ + /* out: pointer to compressed page, + or NULL if the page is not compressed */ + btr_cur_t* cursor) /* in: tree cursor */ +{ + return(buf_block_get_page_zip(btr_cur_get_block(cursor))); +} + +/************************************************************* +Invalidates a tree cursor by setting record pointer to NULL. */ +UNIV_INLINE +void +btr_cur_invalidate( +/*===============*/ + btr_cur_t* cursor) /* in: tree cursor */ +{ + page_cur_invalidate(&(cursor->page_cur)); +} + +/************************************************************* +Returns the page of a tree cursor. */ +UNIV_INLINE +page_t* +btr_cur_get_page( +/*=============*/ + /* out: pointer to page */ + btr_cur_t* cursor) /* in: tree cursor */ +{ + return(page_align(page_cur_get_rec(&(cursor->page_cur)))); +} + +/************************************************************* +Returns the index of a cursor. */ +UNIV_INLINE +dict_index_t* +btr_cur_get_index( +/*==============*/ + /* out: index */ + btr_cur_t* cursor) /* in: B-tree cursor */ +{ + return(cursor->index); +} + +/************************************************************* +Positions a tree cursor at a given record. */ +UNIV_INLINE +void +btr_cur_position( +/*=============*/ + dict_index_t* index, /* in: index */ + rec_t* rec, /* in: record in tree */ + buf_block_t* block, /* in: buffer block of rec */ + btr_cur_t* cursor) /* out: cursor */ +{ + ut_ad(page_align(rec) == block->frame); + + page_cur_position(rec, block, btr_cur_get_page_cur(cursor)); + + cursor->index = index; +} + +/************************************************************************* +Checks if compressing an index page where a btr cursor is placed makes +sense. */ +UNIV_INLINE +ibool +btr_cur_compress_recommendation( +/*============================*/ + /* out: TRUE if compression is recommended */ + btr_cur_t* cursor, /* in: btr cursor */ + mtr_t* mtr) /* in: mtr */ +{ + page_t* page; + + ut_ad(mtr_memo_contains(mtr, btr_cur_get_block(cursor), + MTR_MEMO_PAGE_X_FIX)); + + page = btr_cur_get_page(cursor); + + if ((page_get_data_size(page) < BTR_CUR_PAGE_COMPRESS_LIMIT) + || ((btr_page_get_next(page, mtr) == FIL_NULL) + && (btr_page_get_prev(page, mtr) == FIL_NULL))) { + + /* The page fillfactor has dropped below a predefined + minimum value OR the level in the B-tree contains just + one page: we recommend compression if this is not the + root page. */ + + return(dict_index_get_page(cursor->index) + != page_get_page_no(page)); + } + + return(FALSE); +} + +/************************************************************************* +Checks if the record on which the cursor is placed can be deleted without +making tree compression necessary (or, recommended). */ +UNIV_INLINE +ibool +btr_cur_can_delete_without_compress( +/*================================*/ + /* out: TRUE if can be deleted without + recommended compression */ + btr_cur_t* cursor, /* in: btr cursor */ + ulint rec_size,/* in: rec_get_size(btr_cur_get_rec(cursor))*/ + mtr_t* mtr) /* in: mtr */ +{ + page_t* page; + + ut_ad(mtr_memo_contains(mtr, btr_cur_get_block(cursor), + MTR_MEMO_PAGE_X_FIX)); + + page = btr_cur_get_page(cursor); + + if ((page_get_data_size(page) - rec_size < BTR_CUR_PAGE_COMPRESS_LIMIT) + || ((btr_page_get_next(page, mtr) == FIL_NULL) + && (btr_page_get_prev(page, mtr) == FIL_NULL)) + || (page_get_n_recs(page) < 2)) { + + /* The page fillfactor will drop below a predefined + minimum value, OR the level in the B-tree contains just + one page, OR the page will become empty: we recommend + compression if this is not the root page. */ + + return(dict_index_get_page(cursor->index) + == page_get_page_no(page)); + } + + return(TRUE); +} diff --git a/storage/xtradb/include/btr0pcur.h b/storage/xtradb/include/btr0pcur.h new file mode 100644 index 00000000000..1fdd102d32a --- /dev/null +++ b/storage/xtradb/include/btr0pcur.h @@ -0,0 +1,545 @@ +/***************************************************************************** + +Copyright (c) 1996, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/****************************************************** +The index tree persistent cursor + +Created 2/23/1996 Heikki Tuuri +*******************************************************/ + +#ifndef btr0pcur_h +#define btr0pcur_h + +#include "univ.i" +#include "dict0dict.h" +#include "data0data.h" +#include "mtr0mtr.h" +#include "page0cur.h" +#include "btr0cur.h" +#include "btr0btr.h" +#include "btr0types.h" + +/* Relative positions for a stored cursor position */ +#define BTR_PCUR_ON 1 +#define BTR_PCUR_BEFORE 2 +#define BTR_PCUR_AFTER 3 +/* Note that if the tree is not empty, btr_pcur_store_position does not +use the following, but only uses the above three alternatives, where the +position is stored relative to a specific record: this makes implementation +of a scroll cursor easier */ +#define BTR_PCUR_BEFORE_FIRST_IN_TREE 4 /* in an empty tree */ +#define BTR_PCUR_AFTER_LAST_IN_TREE 5 /* in an empty tree */ + +/****************************************************************** +Allocates memory for a persistent cursor object and initializes the cursor. */ +UNIV_INTERN +btr_pcur_t* +btr_pcur_create_for_mysql(void); +/*============================*/ + /* out, own: persistent cursor */ +/****************************************************************** +Frees the memory for a persistent cursor object. */ +UNIV_INTERN +void +btr_pcur_free_for_mysql( +/*====================*/ + btr_pcur_t* cursor); /* in, own: persistent cursor */ +/****************************************************************** +Copies the stored position of a pcur to another pcur. */ +UNIV_INTERN +void +btr_pcur_copy_stored_position( +/*==========================*/ + btr_pcur_t* pcur_receive, /* in: pcur which will receive the + position info */ + btr_pcur_t* pcur_donate); /* in: pcur from which the info is + copied */ +/****************************************************************** +Sets the old_rec_buf field to NULL. */ +UNIV_INLINE +void +btr_pcur_init( +/*==========*/ + btr_pcur_t* pcur); /* in: persistent cursor */ +/****************************************************************** +Initializes and opens a persistent cursor to an index tree. It should be +closed with btr_pcur_close. */ +UNIV_INLINE +void +btr_pcur_open( +/*==========*/ + dict_index_t* index, /* in: index */ + const dtuple_t* tuple, /* in: tuple on which search done */ + ulint mode, /* in: PAGE_CUR_L, ...; + NOTE that if the search is made using a unique + prefix of a record, mode should be + PAGE_CUR_LE, not PAGE_CUR_GE, as the latter + may end up on the previous page from the + record! */ + ulint latch_mode,/* in: BTR_SEARCH_LEAF, ... */ + btr_pcur_t* cursor, /* in: memory buffer for persistent cursor */ + mtr_t* mtr); /* in: mtr */ +/****************************************************************** +Opens an persistent cursor to an index tree without initializing the +cursor. */ +UNIV_INLINE +void +btr_pcur_open_with_no_init( +/*=======================*/ + dict_index_t* index, /* in: index */ + const dtuple_t* tuple, /* in: tuple on which search done */ + ulint mode, /* in: PAGE_CUR_L, ...; + NOTE that if the search is made using a unique + prefix of a record, mode should be + PAGE_CUR_LE, not PAGE_CUR_GE, as the latter + may end up on the previous page of the + record! */ + ulint latch_mode,/* in: BTR_SEARCH_LEAF, ...; + NOTE that if has_search_latch != 0 then + we maybe do not acquire a latch on the cursor + page, but assume that the caller uses his + btr search latch to protect the record! */ + btr_pcur_t* cursor, /* in: memory buffer for persistent cursor */ + ulint has_search_latch,/* in: latch mode the caller + currently has on btr_search_latch: + RW_S_LATCH, or 0 */ + mtr_t* mtr); /* in: mtr */ +/********************************************************************* +Opens a persistent cursor at either end of an index. */ +UNIV_INLINE +void +btr_pcur_open_at_index_side( +/*========================*/ + ibool from_left, /* in: TRUE if open to the low end, + FALSE if to the high end */ + dict_index_t* index, /* in: index */ + ulint latch_mode, /* in: latch mode */ + btr_pcur_t* pcur, /* in: cursor */ + ibool do_init, /* in: TRUE if should be initialized */ + mtr_t* mtr); /* in: mtr */ +/****************************************************************** +Gets the up_match value for a pcur after a search. */ +UNIV_INLINE +ulint +btr_pcur_get_up_match( +/*==================*/ + /* out: number of matched fields at the cursor + or to the right if search mode was PAGE_CUR_GE, + otherwise undefined */ + btr_pcur_t* cursor); /* in: memory buffer for persistent cursor */ +/****************************************************************** +Gets the low_match value for a pcur after a search. */ +UNIV_INLINE +ulint +btr_pcur_get_low_match( +/*===================*/ + /* out: number of matched fields at the cursor + or to the right if search mode was PAGE_CUR_LE, + otherwise undefined */ + btr_pcur_t* cursor); /* in: memory buffer for persistent cursor */ +/****************************************************************** +If mode is PAGE_CUR_G or PAGE_CUR_GE, opens a persistent cursor on the first +user record satisfying the search condition, in the case PAGE_CUR_L or +PAGE_CUR_LE, on the last user record. If no such user record exists, then +in the first case sets the cursor after last in tree, and in the latter case +before first in tree. The latching mode must be BTR_SEARCH_LEAF or +BTR_MODIFY_LEAF. */ +UNIV_INTERN +void +btr_pcur_open_on_user_rec( +/*======================*/ + dict_index_t* index, /* in: index */ + const dtuple_t* tuple, /* in: tuple on which search done */ + ulint mode, /* in: PAGE_CUR_L, ... */ + ulint latch_mode, /* in: BTR_SEARCH_LEAF or + BTR_MODIFY_LEAF */ + btr_pcur_t* cursor, /* in: memory buffer for persistent + cursor */ + mtr_t* mtr); /* in: mtr */ +/************************************************************************** +Positions a cursor at a randomly chosen position within a B-tree. */ +UNIV_INLINE +void +btr_pcur_open_at_rnd_pos( +/*=====================*/ + dict_index_t* index, /* in: index */ + ulint latch_mode, /* in: BTR_SEARCH_LEAF, ... */ + btr_pcur_t* cursor, /* in/out: B-tree pcur */ + mtr_t* mtr); /* in: mtr */ +/****************************************************************** +Frees the possible old_rec_buf buffer of a persistent cursor and sets the +latch mode of the persistent cursor to BTR_NO_LATCHES. */ +UNIV_INLINE +void +btr_pcur_close( +/*===========*/ + btr_pcur_t* cursor); /* in: persistent cursor */ +/****************************************************************** +The position of the cursor is stored by taking an initial segment of the +record the cursor is positioned on, before, or after, and copying it to the +cursor data structure, or just setting a flag if the cursor id before the +first in an EMPTY tree, or after the last in an EMPTY tree. NOTE that the +page where the cursor is positioned must not be empty if the index tree is +not totally empty! */ +UNIV_INTERN +void +btr_pcur_store_position( +/*====================*/ + btr_pcur_t* cursor, /* in: persistent cursor */ + mtr_t* mtr); /* in: mtr */ +/****************************************************************** +Restores the stored position of a persistent cursor bufferfixing the page and +obtaining the specified latches. If the cursor position was saved when the +(1) cursor was positioned on a user record: this function restores the position +to the last record LESS OR EQUAL to the stored record; +(2) cursor was positioned on a page infimum record: restores the position to +the last record LESS than the user record which was the successor of the page +infimum; +(3) cursor was positioned on the page supremum: restores to the first record +GREATER than the user record which was the predecessor of the supremum. +(4) cursor was positioned before the first or after the last in an empty tree: +restores to before first or after the last in the tree. */ +UNIV_INTERN +ibool +btr_pcur_restore_position( +/*======================*/ + /* out: TRUE if the cursor position + was stored when it was on a user record + and it can be restored on a user record + whose ordering fields are identical to + the ones of the original user record */ + ulint latch_mode, /* in: BTR_SEARCH_LEAF, ... */ + btr_pcur_t* cursor, /* in: detached persistent cursor */ + mtr_t* mtr); /* in: mtr */ +/****************************************************************** +If the latch mode of the cursor is BTR_LEAF_SEARCH or BTR_LEAF_MODIFY, +releases the page latch and bufferfix reserved by the cursor. +NOTE! In the case of BTR_LEAF_MODIFY, there should not exist changes +made by the current mini-transaction to the data protected by the +cursor latch, as then the latch must not be released until mtr_commit. */ +UNIV_INTERN +void +btr_pcur_release_leaf( +/*==================*/ + btr_pcur_t* cursor, /* in: persistent cursor */ + mtr_t* mtr); /* in: mtr */ +/************************************************************* +Gets the rel_pos field for a cursor whose position has been stored. */ +UNIV_INLINE +ulint +btr_pcur_get_rel_pos( +/*=================*/ + /* out: BTR_PCUR_ON, ... */ + const btr_pcur_t* cursor);/* in: persistent cursor */ +/************************************************************* +Sets the mtr field for a pcur. */ +UNIV_INLINE +void +btr_pcur_set_mtr( +/*=============*/ + btr_pcur_t* cursor, /* in: persistent cursor */ + mtr_t* mtr); /* in, own: mtr */ +/************************************************************* +Gets the mtr field for a pcur. */ +UNIV_INLINE +mtr_t* +btr_pcur_get_mtr( +/*=============*/ + /* out: mtr */ + btr_pcur_t* cursor); /* in: persistent cursor */ +/****************************************************************** +Commits the pcur mtr and sets the pcur latch mode to BTR_NO_LATCHES, +that is, the cursor becomes detached. If there have been modifications +to the page where pcur is positioned, this can be used instead of +btr_pcur_release_leaf. Function btr_pcur_store_position should be used +before calling this, if restoration of cursor is wanted later. */ +UNIV_INLINE +void +btr_pcur_commit( +/*============*/ + btr_pcur_t* pcur); /* in: persistent cursor */ +/****************************************************************** +Differs from btr_pcur_commit in that we can specify the mtr to commit. */ +UNIV_INLINE +void +btr_pcur_commit_specify_mtr( +/*========================*/ + btr_pcur_t* pcur, /* in: persistent cursor */ + mtr_t* mtr); /* in: mtr to commit */ +/****************************************************************** +Tests if a cursor is detached: that is the latch mode is BTR_NO_LATCHES. */ +UNIV_INLINE +ibool +btr_pcur_is_detached( +/*=================*/ + /* out: TRUE if detached */ + btr_pcur_t* pcur); /* in: persistent cursor */ +/************************************************************* +Moves the persistent cursor to the next record in the tree. If no records are +left, the cursor stays 'after last in tree'. */ +UNIV_INLINE +ibool +btr_pcur_move_to_next( +/*==================*/ + /* out: TRUE if the cursor was not after last + in tree */ + btr_pcur_t* cursor, /* in: persistent cursor; NOTE that the + function may release the page latch */ + mtr_t* mtr); /* in: mtr */ +/************************************************************* +Moves the persistent cursor to the previous record in the tree. If no records +are left, the cursor stays 'before first in tree'. */ +UNIV_INTERN +ibool +btr_pcur_move_to_prev( +/*==================*/ + /* out: TRUE if the cursor was not before first + in tree */ + btr_pcur_t* cursor, /* in: persistent cursor; NOTE that the + function may release the page latch */ + mtr_t* mtr); /* in: mtr */ +/************************************************************* +Moves the persistent cursor to the last record on the same page. */ +UNIV_INLINE +void +btr_pcur_move_to_last_on_page( +/*==========================*/ + btr_pcur_t* cursor, /* in: persistent cursor */ + mtr_t* mtr); /* in: mtr */ +/************************************************************* +Moves the persistent cursor to the next user record in the tree. If no user +records are left, the cursor ends up 'after last in tree'. */ +UNIV_INLINE +ibool +btr_pcur_move_to_next_user_rec( +/*===========================*/ + /* out: TRUE if the cursor moved forward, + ending on a user record */ + btr_pcur_t* cursor, /* in: persistent cursor; NOTE that the + function may release the page latch */ + mtr_t* mtr); /* in: mtr */ +/************************************************************* +Moves the persistent cursor to the first record on the next page. +Releases the latch on the current page, and bufferunfixes it. +Note that there must not be modifications on the current page, +as then the x-latch can be released only in mtr_commit. */ +UNIV_INTERN +void +btr_pcur_move_to_next_page( +/*=======================*/ + btr_pcur_t* cursor, /* in: persistent cursor; must be on the + last record of the current page */ + mtr_t* mtr); /* in: mtr */ +/************************************************************* +Moves the persistent cursor backward if it is on the first record +of the page. Releases the latch on the current page, and bufferunfixes +it. Note that to prevent a possible deadlock, the operation first +stores the position of the cursor, releases the leaf latch, acquires +necessary latches and restores the cursor position again before returning. +The alphabetical position of the cursor is guaranteed to be sensible +on return, but it may happen that the cursor is not positioned on the +last record of any page, because the structure of the tree may have +changed while the cursor had no latches. */ +UNIV_INTERN +void +btr_pcur_move_backward_from_page( +/*=============================*/ + btr_pcur_t* cursor, /* in: persistent cursor, must be on the + first record of the current page */ + mtr_t* mtr); /* in: mtr */ +#ifdef UNIV_DEBUG +/************************************************************* +Returns the btr cursor component of a persistent cursor. */ +UNIV_INLINE +btr_cur_t* +btr_pcur_get_btr_cur( +/*=================*/ + /* out: pointer to + btr cursor component */ + const btr_pcur_t* cursor); /* in: persistent cursor */ +/************************************************************* +Returns the page cursor component of a persistent cursor. */ +UNIV_INLINE +page_cur_t* +btr_pcur_get_page_cur( +/*==================*/ + /* out: pointer to + page cursor component */ + const btr_pcur_t* cursor); /* in: persistent cursor */ +#else /* UNIV_DEBUG */ +# define btr_pcur_get_btr_cur(cursor) (&(cursor)->btr_cur) +# define btr_pcur_get_page_cur(cursor) (&(cursor)->btr_cur.page_cur) +#endif /* UNIV_DEBUG */ +/************************************************************* +Returns the page of a persistent cursor. */ +UNIV_INLINE +page_t* +btr_pcur_get_page( +/*==============*/ + /* out: pointer to the page */ + btr_pcur_t* cursor);/* in: persistent cursor */ +/************************************************************* +Returns the buffer block of a persistent cursor. */ +UNIV_INLINE +buf_block_t* +btr_pcur_get_block( +/*===============*/ + /* out: pointer to the block */ + btr_pcur_t* cursor);/* in: persistent cursor */ +/************************************************************* +Returns the record of a persistent cursor. */ +UNIV_INLINE +rec_t* +btr_pcur_get_rec( +/*=============*/ + /* out: pointer to the record */ + btr_pcur_t* cursor);/* in: persistent cursor */ +/************************************************************* +Checks if the persistent cursor is on a user record. */ +UNIV_INLINE +ibool +btr_pcur_is_on_user_rec( +/*====================*/ + const btr_pcur_t* cursor);/* in: persistent cursor */ +/************************************************************* +Checks if the persistent cursor is after the last user record on +a page. */ +UNIV_INLINE +ibool +btr_pcur_is_after_last_on_page( +/*===========================*/ + const btr_pcur_t* cursor);/* in: persistent cursor */ +/************************************************************* +Checks if the persistent cursor is before the first user record on +a page. */ +UNIV_INLINE +ibool +btr_pcur_is_before_first_on_page( +/*=============================*/ + const btr_pcur_t* cursor);/* in: persistent cursor */ +/************************************************************* +Checks if the persistent cursor is before the first user record in +the index tree. */ +UNIV_INLINE +ibool +btr_pcur_is_before_first_in_tree( +/*=============================*/ + btr_pcur_t* cursor, /* in: persistent cursor */ + mtr_t* mtr); /* in: mtr */ +/************************************************************* +Checks if the persistent cursor is after the last user record in +the index tree. */ +UNIV_INLINE +ibool +btr_pcur_is_after_last_in_tree( +/*===========================*/ + btr_pcur_t* cursor, /* in: persistent cursor */ + mtr_t* mtr); /* in: mtr */ +/************************************************************* +Moves the persistent cursor to the next record on the same page. */ +UNIV_INLINE +void +btr_pcur_move_to_next_on_page( +/*==========================*/ + btr_pcur_t* cursor);/* in/out: persistent cursor */ +/************************************************************* +Moves the persistent cursor to the previous record on the same page. */ +UNIV_INLINE +void +btr_pcur_move_to_prev_on_page( +/*==========================*/ + btr_pcur_t* cursor);/* in/out: persistent cursor */ + + +/* The persistent B-tree cursor structure. This is used mainly for SQL +selects, updates, and deletes. */ + +struct btr_pcur_struct{ + btr_cur_t btr_cur; /* a B-tree cursor */ + ulint latch_mode; /* see TODO note below! + BTR_SEARCH_LEAF, BTR_MODIFY_LEAF, + BTR_MODIFY_TREE, or BTR_NO_LATCHES, + depending on the latching state of + the page and tree where the cursor is + positioned; the last value means that + the cursor is not currently positioned: + we say then that the cursor is + detached; it can be restored to + attached if the old position was + stored in old_rec */ + ulint old_stored; /* BTR_PCUR_OLD_STORED + or BTR_PCUR_OLD_NOT_STORED */ + rec_t* old_rec; /* if cursor position is stored, + contains an initial segment of the + latest record cursor was positioned + either on, before, or after */ + ulint old_n_fields; /* number of fields in old_rec */ + ulint rel_pos; /* BTR_PCUR_ON, BTR_PCUR_BEFORE, or + BTR_PCUR_AFTER, depending on whether + cursor was on, before, or after the + old_rec record */ + buf_block_t* block_when_stored;/* buffer block when the position was + stored */ + ib_uint64_t modify_clock; /* the modify clock value of the + buffer block when the cursor position + was stored */ + ulint pos_state; /* see TODO note below! + BTR_PCUR_IS_POSITIONED, + BTR_PCUR_WAS_POSITIONED, + BTR_PCUR_NOT_POSITIONED */ + ulint search_mode; /* PAGE_CUR_G, ... */ + trx_t* trx_if_known; /* the transaction, if we know it; + otherwise this field is not defined; + can ONLY BE USED in error prints in + fatal assertion failures! */ + /*-----------------------------*/ + /* NOTE that the following fields may possess dynamically allocated + memory which should be freed if not needed anymore! */ + + mtr_t* mtr; /* NULL, or this field may contain + a mini-transaction which holds the + latch on the cursor page */ + byte* old_rec_buf; /* NULL, or a dynamically allocated + buffer for old_rec */ + ulint buf_size; /* old_rec_buf size if old_rec_buf + is not NULL */ +}; + +#define BTR_PCUR_IS_POSITIONED 1997660512 /* TODO: currently, the state + can be BTR_PCUR_IS_POSITIONED, + though it really should be + BTR_PCUR_WAS_POSITIONED, + because we have no obligation + to commit the cursor with + mtr; similarly latch_mode may + be out of date. This can + lead to problems if btr_pcur + is not used the right way; + all current code should be + ok. */ +#define BTR_PCUR_WAS_POSITIONED 1187549791 +#define BTR_PCUR_NOT_POSITIONED 1328997689 + +#define BTR_PCUR_OLD_STORED 908467085 +#define BTR_PCUR_OLD_NOT_STORED 122766467 + +#ifndef UNIV_NONINL +#include "btr0pcur.ic" +#endif + +#endif diff --git a/storage/xtradb/include/btr0pcur.ic b/storage/xtradb/include/btr0pcur.ic new file mode 100644 index 00000000000..bde7413820a --- /dev/null +++ b/storage/xtradb/include/btr0pcur.ic @@ -0,0 +1,656 @@ +/***************************************************************************** + +Copyright (c) 1996, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/****************************************************** +The index tree persistent cursor + +Created 2/23/1996 Heikki Tuuri +*******************************************************/ + + +/************************************************************* +Gets the rel_pos field for a cursor whose position has been stored. */ +UNIV_INLINE +ulint +btr_pcur_get_rel_pos( +/*=================*/ + /* out: BTR_PCUR_ON, ... */ + const btr_pcur_t* cursor) /* in: persistent cursor */ +{ + ut_ad(cursor); + ut_ad(cursor->old_rec); + ut_ad(cursor->old_stored == BTR_PCUR_OLD_STORED); + ut_ad(cursor->pos_state == BTR_PCUR_WAS_POSITIONED + || cursor->pos_state == BTR_PCUR_IS_POSITIONED); + + return(cursor->rel_pos); +} + +/************************************************************* +Sets the mtr field for a pcur. */ +UNIV_INLINE +void +btr_pcur_set_mtr( +/*=============*/ + btr_pcur_t* cursor, /* in: persistent cursor */ + mtr_t* mtr) /* in, own: mtr */ +{ + ut_ad(cursor); + + cursor->mtr = mtr; +} + +/************************************************************* +Gets the mtr field for a pcur. */ +UNIV_INLINE +mtr_t* +btr_pcur_get_mtr( +/*=============*/ + /* out: mtr */ + btr_pcur_t* cursor) /* in: persistent cursor */ +{ + ut_ad(cursor); + + return(cursor->mtr); +} + +#ifdef UNIV_DEBUG +/************************************************************* +Returns the btr cursor component of a persistent cursor. */ +UNIV_INLINE +btr_cur_t* +btr_pcur_get_btr_cur( +/*=================*/ + /* out: pointer to + btr cursor component */ + const btr_pcur_t* cursor) /* in: persistent cursor */ +{ + const btr_cur_t* btr_cur = &cursor->btr_cur; + return((btr_cur_t*) btr_cur); +} + +/************************************************************* +Returns the page cursor component of a persistent cursor. */ +UNIV_INLINE +page_cur_t* +btr_pcur_get_page_cur( +/*==================*/ + /* out: pointer to page cursor + component */ + const btr_pcur_t* cursor) /* in: persistent cursor */ +{ + return(btr_cur_get_page_cur(btr_pcur_get_btr_cur(cursor))); +} +#endif /* UNIV_DEBUG */ +/************************************************************* +Returns the page of a persistent cursor. */ +UNIV_INLINE +page_t* +btr_pcur_get_page( +/*==============*/ + /* out: pointer to the page */ + btr_pcur_t* cursor) /* in: persistent cursor */ +{ + ut_ad(cursor->pos_state == BTR_PCUR_IS_POSITIONED); + + return(btr_cur_get_page(btr_pcur_get_btr_cur(cursor))); +} + +/************************************************************* +Returns the buffer block of a persistent cursor. */ +UNIV_INLINE +buf_block_t* +btr_pcur_get_block( +/*===============*/ + /* out: pointer to the block */ + btr_pcur_t* cursor) /* in: persistent cursor */ +{ + ut_ad(cursor->pos_state == BTR_PCUR_IS_POSITIONED); + + return(btr_cur_get_block(btr_pcur_get_btr_cur(cursor))); +} + +/************************************************************* +Returns the record of a persistent cursor. */ +UNIV_INLINE +rec_t* +btr_pcur_get_rec( +/*=============*/ + /* out: pointer to the record */ + btr_pcur_t* cursor) /* in: persistent cursor */ +{ + ut_ad(cursor->pos_state == BTR_PCUR_IS_POSITIONED); + ut_ad(cursor->latch_mode != BTR_NO_LATCHES); + + return(btr_cur_get_rec(btr_pcur_get_btr_cur(cursor))); +} + +/****************************************************************** +Gets the up_match value for a pcur after a search. */ +UNIV_INLINE +ulint +btr_pcur_get_up_match( +/*==================*/ + /* out: number of matched fields at the cursor + or to the right if search mode was PAGE_CUR_GE, + otherwise undefined */ + btr_pcur_t* cursor) /* in: memory buffer for persistent cursor */ +{ + btr_cur_t* btr_cursor; + + ut_ad((cursor->pos_state == BTR_PCUR_WAS_POSITIONED) + || (cursor->pos_state == BTR_PCUR_IS_POSITIONED)); + + btr_cursor = btr_pcur_get_btr_cur(cursor); + + ut_ad(btr_cursor->up_match != ULINT_UNDEFINED); + + return(btr_cursor->up_match); +} + +/****************************************************************** +Gets the low_match value for a pcur after a search. */ +UNIV_INLINE +ulint +btr_pcur_get_low_match( +/*===================*/ + /* out: number of matched fields at the cursor + or to the right if search mode was PAGE_CUR_LE, + otherwise undefined */ + btr_pcur_t* cursor) /* in: memory buffer for persistent cursor */ +{ + btr_cur_t* btr_cursor; + + ut_ad((cursor->pos_state == BTR_PCUR_WAS_POSITIONED) + || (cursor->pos_state == BTR_PCUR_IS_POSITIONED)); + + btr_cursor = btr_pcur_get_btr_cur(cursor); + ut_ad(btr_cursor->low_match != ULINT_UNDEFINED); + + return(btr_cursor->low_match); +} + +/************************************************************* +Checks if the persistent cursor is after the last user record on +a page. */ +UNIV_INLINE +ibool +btr_pcur_is_after_last_on_page( +/*===========================*/ + const btr_pcur_t* cursor) /* in: persistent cursor */ +{ + ut_ad(cursor->pos_state == BTR_PCUR_IS_POSITIONED); + ut_ad(cursor->latch_mode != BTR_NO_LATCHES); + + return(page_cur_is_after_last(btr_pcur_get_page_cur(cursor))); +} + +/************************************************************* +Checks if the persistent cursor is before the first user record on +a page. */ +UNIV_INLINE +ibool +btr_pcur_is_before_first_on_page( +/*=============================*/ + const btr_pcur_t* cursor) /* in: persistent cursor */ +{ + ut_ad(cursor->pos_state == BTR_PCUR_IS_POSITIONED); + ut_ad(cursor->latch_mode != BTR_NO_LATCHES); + + return(page_cur_is_before_first(btr_pcur_get_page_cur(cursor))); +} + +/************************************************************* +Checks if the persistent cursor is on a user record. */ +UNIV_INLINE +ibool +btr_pcur_is_on_user_rec( +/*====================*/ + const btr_pcur_t* cursor) /* in: persistent cursor */ +{ + ut_ad(cursor->pos_state == BTR_PCUR_IS_POSITIONED); + ut_ad(cursor->latch_mode != BTR_NO_LATCHES); + + if (btr_pcur_is_before_first_on_page(cursor) + || btr_pcur_is_after_last_on_page(cursor)) { + + return(FALSE); + } + + return(TRUE); +} + +/************************************************************* +Checks if the persistent cursor is before the first user record in +the index tree. */ +UNIV_INLINE +ibool +btr_pcur_is_before_first_in_tree( +/*=============================*/ + btr_pcur_t* cursor, /* in: persistent cursor */ + mtr_t* mtr) /* in: mtr */ +{ + ut_ad(cursor->pos_state == BTR_PCUR_IS_POSITIONED); + ut_ad(cursor->latch_mode != BTR_NO_LATCHES); + + if (btr_page_get_prev(btr_pcur_get_page(cursor), mtr) != FIL_NULL) { + + return(FALSE); + } + + return(page_cur_is_before_first(btr_pcur_get_page_cur(cursor))); +} + +/************************************************************* +Checks if the persistent cursor is after the last user record in +the index tree. */ +UNIV_INLINE +ibool +btr_pcur_is_after_last_in_tree( +/*===========================*/ + btr_pcur_t* cursor, /* in: persistent cursor */ + mtr_t* mtr) /* in: mtr */ +{ + ut_ad(cursor->pos_state == BTR_PCUR_IS_POSITIONED); + ut_ad(cursor->latch_mode != BTR_NO_LATCHES); + + if (btr_page_get_next(btr_pcur_get_page(cursor), mtr) != FIL_NULL) { + + return(FALSE); + } + + return(page_cur_is_after_last(btr_pcur_get_page_cur(cursor))); +} + +/************************************************************* +Moves the persistent cursor to the next record on the same page. */ +UNIV_INLINE +void +btr_pcur_move_to_next_on_page( +/*==========================*/ + btr_pcur_t* cursor) /* in/out: persistent cursor */ +{ + ut_ad(cursor->pos_state == BTR_PCUR_IS_POSITIONED); + ut_ad(cursor->latch_mode != BTR_NO_LATCHES); + + page_cur_move_to_next(btr_pcur_get_page_cur(cursor)); + + cursor->old_stored = BTR_PCUR_OLD_NOT_STORED; +} + +/************************************************************* +Moves the persistent cursor to the previous record on the same page. */ +UNIV_INLINE +void +btr_pcur_move_to_prev_on_page( +/*==========================*/ + btr_pcur_t* cursor) /* in/out: persistent cursor */ +{ + ut_ad(cursor->pos_state == BTR_PCUR_IS_POSITIONED); + ut_ad(cursor->latch_mode != BTR_NO_LATCHES); + + page_cur_move_to_prev(btr_pcur_get_page_cur(cursor)); + + cursor->old_stored = BTR_PCUR_OLD_NOT_STORED; +} + +/************************************************************* +Moves the persistent cursor to the last record on the same page. */ +UNIV_INLINE +void +btr_pcur_move_to_last_on_page( +/*==========================*/ + btr_pcur_t* cursor, /* in: persistent cursor */ + mtr_t* mtr) /* in: mtr */ +{ + UT_NOT_USED(mtr); + ut_ad(cursor->latch_mode != BTR_NO_LATCHES); + + page_cur_set_after_last(btr_pcur_get_block(cursor), + btr_pcur_get_page_cur(cursor)); + + cursor->old_stored = BTR_PCUR_OLD_NOT_STORED; +} + +/************************************************************* +Moves the persistent cursor to the next user record in the tree. If no user +records are left, the cursor ends up 'after last in tree'. */ +UNIV_INLINE +ibool +btr_pcur_move_to_next_user_rec( +/*===========================*/ + /* out: TRUE if the cursor moved forward, + ending on a user record */ + btr_pcur_t* cursor, /* in: persistent cursor; NOTE that the + function may release the page latch */ + mtr_t* mtr) /* in: mtr */ +{ + ut_ad(cursor->pos_state == BTR_PCUR_IS_POSITIONED); + ut_ad(cursor->latch_mode != BTR_NO_LATCHES); + cursor->old_stored = BTR_PCUR_OLD_NOT_STORED; +loop: + if (btr_pcur_is_after_last_on_page(cursor)) { + + if (btr_pcur_is_after_last_in_tree(cursor, mtr)) { + + return(FALSE); + } + + btr_pcur_move_to_next_page(cursor, mtr); + } else { + btr_pcur_move_to_next_on_page(cursor); + } + + if (btr_pcur_is_on_user_rec(cursor)) { + + return(TRUE); + } + + goto loop; +} + +/************************************************************* +Moves the persistent cursor to the next record in the tree. If no records are +left, the cursor stays 'after last in tree'. */ +UNIV_INLINE +ibool +btr_pcur_move_to_next( +/*==================*/ + /* out: TRUE if the cursor was not after last + in tree */ + btr_pcur_t* cursor, /* in: persistent cursor; NOTE that the + function may release the page latch */ + mtr_t* mtr) /* in: mtr */ +{ + ut_ad(cursor->pos_state == BTR_PCUR_IS_POSITIONED); + ut_ad(cursor->latch_mode != BTR_NO_LATCHES); + + cursor->old_stored = BTR_PCUR_OLD_NOT_STORED; + + if (btr_pcur_is_after_last_on_page(cursor)) { + + if (btr_pcur_is_after_last_in_tree(cursor, mtr)) { + + return(FALSE); + } + + btr_pcur_move_to_next_page(cursor, mtr); + + return(TRUE); + } + + btr_pcur_move_to_next_on_page(cursor); + + return(TRUE); +} + +/****************************************************************** +Commits the pcur mtr and sets the pcur latch mode to BTR_NO_LATCHES, +that is, the cursor becomes detached. If there have been modifications +to the page where pcur is positioned, this can be used instead of +btr_pcur_release_leaf. Function btr_pcur_store_position should be used +before calling this, if restoration of cursor is wanted later. */ +UNIV_INLINE +void +btr_pcur_commit( +/*============*/ + btr_pcur_t* pcur) /* in: persistent cursor */ +{ + ut_a(pcur->pos_state == BTR_PCUR_IS_POSITIONED); + + pcur->latch_mode = BTR_NO_LATCHES; + + mtr_commit(pcur->mtr); + + pcur->pos_state = BTR_PCUR_WAS_POSITIONED; +} + +/****************************************************************** +Differs from btr_pcur_commit in that we can specify the mtr to commit. */ +UNIV_INLINE +void +btr_pcur_commit_specify_mtr( +/*========================*/ + btr_pcur_t* pcur, /* in: persistent cursor */ + mtr_t* mtr) /* in: mtr to commit */ +{ + ut_a(pcur->pos_state == BTR_PCUR_IS_POSITIONED); + + pcur->latch_mode = BTR_NO_LATCHES; + + mtr_commit(mtr); + + pcur->pos_state = BTR_PCUR_WAS_POSITIONED; +} + +/****************************************************************** +Sets the pcur latch mode to BTR_NO_LATCHES. */ +UNIV_INLINE +void +btr_pcur_detach( +/*============*/ + btr_pcur_t* pcur) /* in: persistent cursor */ +{ + ut_a(pcur->pos_state == BTR_PCUR_IS_POSITIONED); + + pcur->latch_mode = BTR_NO_LATCHES; + + pcur->pos_state = BTR_PCUR_WAS_POSITIONED; +} + +/****************************************************************** +Tests if a cursor is detached: that is the latch mode is BTR_NO_LATCHES. */ +UNIV_INLINE +ibool +btr_pcur_is_detached( +/*=================*/ + /* out: TRUE if detached */ + btr_pcur_t* pcur) /* in: persistent cursor */ +{ + if (pcur->latch_mode == BTR_NO_LATCHES) { + + return(TRUE); + } + + return(FALSE); +} + +/****************************************************************** +Sets the old_rec_buf field to NULL. */ +UNIV_INLINE +void +btr_pcur_init( +/*==========*/ + btr_pcur_t* pcur) /* in: persistent cursor */ +{ + pcur->old_stored = BTR_PCUR_OLD_NOT_STORED; + pcur->old_rec_buf = NULL; + pcur->old_rec = NULL; +} + +/****************************************************************** +Initializes and opens a persistent cursor to an index tree. It should be +closed with btr_pcur_close. */ +UNIV_INLINE +void +btr_pcur_open( +/*==========*/ + dict_index_t* index, /* in: index */ + const dtuple_t* tuple, /* in: tuple on which search done */ + ulint mode, /* in: PAGE_CUR_L, ...; + NOTE that if the search is made using a unique + prefix of a record, mode should be + PAGE_CUR_LE, not PAGE_CUR_GE, as the latter + may end up on the previous page from the + record! */ + ulint latch_mode,/* in: BTR_SEARCH_LEAF, ... */ + btr_pcur_t* cursor, /* in: memory buffer for persistent cursor */ + mtr_t* mtr) /* in: mtr */ +{ + btr_cur_t* btr_cursor; + + /* Initialize the cursor */ + + btr_pcur_init(cursor); + + cursor->latch_mode = latch_mode; + cursor->search_mode = mode; + + /* Search with the tree cursor */ + + btr_cursor = btr_pcur_get_btr_cur(cursor); + + btr_cur_search_to_nth_level(index, 0, tuple, mode, latch_mode, + btr_cursor, 0, mtr); + cursor->pos_state = BTR_PCUR_IS_POSITIONED; + + cursor->trx_if_known = NULL; +} + +/****************************************************************** +Opens an persistent cursor to an index tree without initializing the +cursor. */ +UNIV_INLINE +void +btr_pcur_open_with_no_init( +/*=======================*/ + dict_index_t* index, /* in: index */ + const dtuple_t* tuple, /* in: tuple on which search done */ + ulint mode, /* in: PAGE_CUR_L, ...; + NOTE that if the search is made using a unique + prefix of a record, mode should be + PAGE_CUR_LE, not PAGE_CUR_GE, as the latter + may end up on the previous page of the + record! */ + ulint latch_mode,/* in: BTR_SEARCH_LEAF, ...; + NOTE that if has_search_latch != 0 then + we maybe do not acquire a latch on the cursor + page, but assume that the caller uses his + btr search latch to protect the record! */ + btr_pcur_t* cursor, /* in: memory buffer for persistent cursor */ + ulint has_search_latch,/* in: latch mode the caller + currently has on btr_search_latch: + RW_S_LATCH, or 0 */ + mtr_t* mtr) /* in: mtr */ +{ + btr_cur_t* btr_cursor; + + cursor->latch_mode = latch_mode; + cursor->search_mode = mode; + + /* Search with the tree cursor */ + + btr_cursor = btr_pcur_get_btr_cur(cursor); + + btr_cur_search_to_nth_level(index, 0, tuple, mode, latch_mode, + btr_cursor, has_search_latch, mtr); + cursor->pos_state = BTR_PCUR_IS_POSITIONED; + + cursor->old_stored = BTR_PCUR_OLD_NOT_STORED; + + cursor->trx_if_known = NULL; +} + +/********************************************************************* +Opens a persistent cursor at either end of an index. */ +UNIV_INLINE +void +btr_pcur_open_at_index_side( +/*========================*/ + ibool from_left, /* in: TRUE if open to the low end, + FALSE if to the high end */ + dict_index_t* index, /* in: index */ + ulint latch_mode, /* in: latch mode */ + btr_pcur_t* pcur, /* in: cursor */ + ibool do_init, /* in: TRUE if should be initialized */ + mtr_t* mtr) /* in: mtr */ +{ + pcur->latch_mode = latch_mode; + + if (from_left) { + pcur->search_mode = PAGE_CUR_G; + } else { + pcur->search_mode = PAGE_CUR_L; + } + + if (do_init) { + btr_pcur_init(pcur); + } + + btr_cur_open_at_index_side(from_left, index, latch_mode, + btr_pcur_get_btr_cur(pcur), mtr); + pcur->pos_state = BTR_PCUR_IS_POSITIONED; + + pcur->old_stored = BTR_PCUR_OLD_NOT_STORED; + + pcur->trx_if_known = NULL; +} + +/************************************************************************** +Positions a cursor at a randomly chosen position within a B-tree. */ +UNIV_INLINE +void +btr_pcur_open_at_rnd_pos( +/*=====================*/ + dict_index_t* index, /* in: index */ + ulint latch_mode, /* in: BTR_SEARCH_LEAF, ... */ + btr_pcur_t* cursor, /* in/out: B-tree pcur */ + mtr_t* mtr) /* in: mtr */ +{ + /* Initialize the cursor */ + + cursor->latch_mode = latch_mode; + cursor->search_mode = PAGE_CUR_G; + + btr_pcur_init(cursor); + + btr_cur_open_at_rnd_pos(index, latch_mode, + btr_pcur_get_btr_cur(cursor), mtr); + cursor->pos_state = BTR_PCUR_IS_POSITIONED; + cursor->old_stored = BTR_PCUR_OLD_NOT_STORED; + + cursor->trx_if_known = NULL; +} + +/****************************************************************** +Frees the possible memory heap of a persistent cursor and sets the latch +mode of the persistent cursor to BTR_NO_LATCHES. */ +UNIV_INLINE +void +btr_pcur_close( +/*===========*/ + btr_pcur_t* cursor) /* in: persistent cursor */ +{ + if (cursor->old_rec_buf != NULL) { + + mem_free(cursor->old_rec_buf); + + cursor->old_rec = NULL; + cursor->old_rec_buf = NULL; + } + + cursor->btr_cur.page_cur.rec = NULL; + cursor->btr_cur.page_cur.block = NULL; + cursor->old_rec = NULL; + cursor->old_stored = BTR_PCUR_OLD_NOT_STORED; + + cursor->latch_mode = BTR_NO_LATCHES; + cursor->pos_state = BTR_PCUR_NOT_POSITIONED; + + cursor->trx_if_known = NULL; +} diff --git a/storage/xtradb/include/btr0sea.h b/storage/xtradb/include/btr0sea.h new file mode 100644 index 00000000000..074e6595258 --- /dev/null +++ b/storage/xtradb/include/btr0sea.h @@ -0,0 +1,298 @@ +/***************************************************************************** + +Copyright (c) 1996, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/************************************************************************ +The index tree adaptive search + +Created 2/17/1996 Heikki Tuuri +*************************************************************************/ + +#ifndef btr0sea_h +#define btr0sea_h + +#include "univ.i" + +#include "rem0rec.h" +#include "dict0dict.h" +#include "btr0types.h" +#include "mtr0mtr.h" +#include "ha0ha.h" + +/********************************************************************* +Creates and initializes the adaptive search system at a database start. */ +UNIV_INTERN +void +btr_search_sys_create( +/*==================*/ + ulint hash_size); /* in: hash index hash table size */ + +/************************************************************************ +Disable the adaptive hash search system and empty the index. */ +UNIV_INTERN +void +btr_search_disable(void); +/*====================*/ +/************************************************************************ +Enable the adaptive hash search system. */ +UNIV_INTERN +void +btr_search_enable(void); +/*====================*/ + +/************************************************************************ +Returns search info for an index. */ +UNIV_INLINE +btr_search_t* +btr_search_get_info( +/*================*/ + /* out: search info; search mutex reserved */ + dict_index_t* index); /* in: index */ +/********************************************************************* +Creates and initializes a search info struct. */ +UNIV_INTERN +btr_search_t* +btr_search_info_create( +/*===================*/ + /* out, own: search info struct */ + mem_heap_t* heap); /* in: heap where created */ +/********************************************************************* +Returns the value of ref_count. The value is protected by +btr_search_latch. */ +UNIV_INTERN +ulint +btr_search_info_get_ref_count( +/*==========================*/ + /* out: ref_count value. */ + btr_search_t* info); /* in: search info. */ +/************************************************************************* +Updates the search info. */ +UNIV_INLINE +void +btr_search_info_update( +/*===================*/ + dict_index_t* index, /* in: index of the cursor */ + btr_cur_t* cursor);/* in: cursor which was just positioned */ +/********************************************************************** +Tries to guess the right search position based on the hash search info +of the index. Note that if mode is PAGE_CUR_LE, which is used in inserts, +and the function returns TRUE, then cursor->up_match and cursor->low_match +both have sensible values. */ +UNIV_INTERN +ibool +btr_search_guess_on_hash( +/*=====================*/ + /* out: TRUE if succeeded */ + dict_index_t* index, /* in: index */ + btr_search_t* info, /* in: index search info */ + const dtuple_t* tuple, /* in: logical record */ + ulint mode, /* in: PAGE_CUR_L, ... */ + ulint latch_mode, /* in: BTR_SEARCH_LEAF, ... */ + btr_cur_t* cursor, /* out: tree cursor */ + ulint has_search_latch,/* in: latch mode the caller + currently has on btr_search_latch: + RW_S_LATCH, RW_X_LATCH, or 0 */ + mtr_t* mtr); /* in: mtr */ +/************************************************************************ +Moves or deletes hash entries for moved records. If new_page is already hashed, +then the hash index for page, if any, is dropped. If new_page is not hashed, +and page is hashed, then a new hash index is built to new_page with the same +parameters as page (this often happens when a page is split). */ +UNIV_INTERN +void +btr_search_move_or_delete_hash_entries( +/*===================================*/ + buf_block_t* new_block, /* in: records are copied + to this page */ + buf_block_t* block, /* in: index page from which + records were copied, and the + copied records will be deleted + from this page */ + dict_index_t* index); /* in: record descriptor */ +/************************************************************************ +Drops a page hash index. */ +UNIV_INTERN +void +btr_search_drop_page_hash_index( +/*============================*/ + buf_block_t* block); /* in: block containing index page, + s- or x-latched, or an index page + for which we know that + block->buf_fix_count == 0 */ +/************************************************************************ +Drops a page hash index when a page is freed from a fseg to the file system. +Drops possible hash index if the page happens to be in the buffer pool. */ +UNIV_INTERN +void +btr_search_drop_page_hash_when_freed( +/*=================================*/ + ulint space, /* in: space id */ + ulint zip_size, /* in: compressed page size in bytes + or 0 for uncompressed pages */ + ulint page_no); /* in: page number */ +/************************************************************************ +Updates the page hash index when a single record is inserted on a page. */ +UNIV_INTERN +void +btr_search_update_hash_node_on_insert( +/*==================================*/ + btr_cur_t* cursor);/* in: cursor which was positioned to the + place to insert using btr_cur_search_..., + and the new record has been inserted next + to the cursor */ +/************************************************************************ +Updates the page hash index when a single record is inserted on a page. */ +UNIV_INTERN +void +btr_search_update_hash_on_insert( +/*=============================*/ + btr_cur_t* cursor);/* in: cursor which was positioned to the + place to insert using btr_cur_search_..., + and the new record has been inserted next + to the cursor */ +/************************************************************************ +Updates the page hash index when a single record is deleted from a page. */ +UNIV_INTERN +void +btr_search_update_hash_on_delete( +/*=============================*/ + btr_cur_t* cursor);/* in: cursor which was positioned on the + record to delete using btr_cur_search_..., + the record is not yet deleted */ +/************************************************************************ +Validates the search system. */ +UNIV_INTERN +ibool +btr_search_validate(void); +/*======================*/ + /* out: TRUE if ok */ + +/* Flag: has the search system been enabled? +Protected by btr_search_latch and btr_search_enabled_mutex. */ +extern char btr_search_enabled; + +/* The search info struct in an index */ + +struct btr_search_struct{ + ulint ref_count; /* Number of blocks in this index tree + that have search index built + i.e. block->index points to this index. + Protected by btr_search_latch except + when during initialization in + btr_search_info_create(). */ + + /* The following fields are not protected by any latch. + Unfortunately, this means that they must be aligned to + the machine word, i.e., they cannot be turned into bit-fields. */ + buf_block_t* root_guess;/* the root page frame when it was last time + fetched, or NULL */ + ulint hash_analysis; /* when this exceeds BTR_SEARCH_HASH_ANALYSIS, + the hash analysis starts; this is reset if no + success noticed */ + ibool last_hash_succ; /* TRUE if the last search would have + succeeded, or did succeed, using the hash + index; NOTE that the value here is not exact: + it is not calculated for every search, and the + calculation itself is not always accurate! */ + ulint n_hash_potential; + /* number of consecutive searches + which would have succeeded, or did succeed, + using the hash index; + the range is 0 .. BTR_SEARCH_BUILD_LIMIT + 5 */ + /*----------------------*/ + ulint n_fields; /* recommended prefix length for hash search: + number of full fields */ + ulint n_bytes; /* recommended prefix: number of bytes in + an incomplete field; + see also BTR_PAGE_MAX_REC_SIZE */ + ibool left_side; /* TRUE or FALSE, depending on whether + the leftmost record of several records with + the same prefix should be indexed in the + hash index */ + /*----------------------*/ +#ifdef UNIV_SEARCH_PERF_STAT + ulint n_hash_succ; /* number of successful hash searches thus + far */ + ulint n_hash_fail; /* number of failed hash searches */ + ulint n_patt_succ; /* number of successful pattern searches thus + far */ + ulint n_searches; /* number of searches */ +#endif /* UNIV_SEARCH_PERF_STAT */ +#ifdef UNIV_DEBUG + ulint magic_n; /* magic number */ +# define BTR_SEARCH_MAGIC_N 1112765 +#endif /* UNIV_DEBUG */ +}; + +/* The hash index system */ + +typedef struct btr_search_sys_struct btr_search_sys_t; + +struct btr_search_sys_struct{ + hash_table_t* hash_index; +}; + +extern btr_search_sys_t* btr_search_sys; + +/* The latch protecting the adaptive search system: this latch protects the +(1) hash index; +(2) columns of a record to which we have a pointer in the hash index; + +but does NOT protect: + +(3) next record offset field in a record; +(4) next or previous records on the same page. + +Bear in mind (3) and (4) when using the hash index. +*/ + +extern rw_lock_t* btr_search_latch_temp; + +#define btr_search_latch (*btr_search_latch_temp) + +#ifdef UNIV_SEARCH_PERF_STAT +extern ulint btr_search_n_succ; +extern ulint btr_search_n_hash_fail; +#endif /* UNIV_SEARCH_PERF_STAT */ + +/* After change in n_fields or n_bytes in info, this many rounds are waited +before starting the hash analysis again: this is to save CPU time when there +is no hope in building a hash index. */ + +#define BTR_SEARCH_HASH_ANALYSIS 17 + +/* Limit of consecutive searches for trying a search shortcut on the search +pattern */ + +#define BTR_SEARCH_ON_PATTERN_LIMIT 3 + +/* Limit of consecutive searches for trying a search shortcut using the hash +index */ + +#define BTR_SEARCH_ON_HASH_LIMIT 3 + +/* We do this many searches before trying to keep the search latch over calls +from MySQL. If we notice someone waiting for the latch, we again set this +much timeout. This is to reduce contention. */ + +#define BTR_SEA_TIMEOUT 10000 + +#ifndef UNIV_NONINL +#include "btr0sea.ic" +#endif + +#endif diff --git a/storage/xtradb/include/btr0sea.ic b/storage/xtradb/include/btr0sea.ic new file mode 100644 index 00000000000..c948d7e92af --- /dev/null +++ b/storage/xtradb/include/btr0sea.ic @@ -0,0 +1,83 @@ +/***************************************************************************** + +Copyright (c) 1996, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/************************************************************************ +The index tree adaptive search + +Created 2/17/1996 Heikki Tuuri +*************************************************************************/ + +#include "dict0mem.h" +#include "btr0cur.h" +#include "buf0buf.h" + +/************************************************************************* +Updates the search info. */ +UNIV_INTERN +void +btr_search_info_update_slow( +/*========================*/ + btr_search_t* info, /* in/out: search info */ + btr_cur_t* cursor);/* in: cursor which was just positioned */ + +/************************************************************************ +Returns search info for an index. */ +UNIV_INLINE +btr_search_t* +btr_search_get_info( +/*================*/ + /* out: search info; search mutex reserved */ + dict_index_t* index) /* in: index */ +{ + ut_ad(index); + + return(index->search_info); +} + +/************************************************************************* +Updates the search info. */ +UNIV_INLINE +void +btr_search_info_update( +/*===================*/ + dict_index_t* index, /* in: index of the cursor */ + btr_cur_t* cursor) /* in: cursor which was just positioned */ +{ + btr_search_t* info; + +#ifdef UNIV_SYNC_DEBUG + ut_ad(!rw_lock_own(&btr_search_latch, RW_LOCK_SHARED)); + ut_ad(!rw_lock_own(&btr_search_latch, RW_LOCK_EX)); +#endif /* UNIV_SYNC_DEBUG */ + + info = btr_search_get_info(index); + + info->hash_analysis++; + + if (info->hash_analysis < BTR_SEARCH_HASH_ANALYSIS) { + + /* Do nothing */ + + return; + + } + + ut_ad(cursor->flag != BTR_CUR_HASH); + + btr_search_info_update_slow(info, cursor); +} diff --git a/storage/xtradb/include/btr0types.h b/storage/xtradb/include/btr0types.h new file mode 100644 index 00000000000..074b15fa68d --- /dev/null +++ b/storage/xtradb/include/btr0types.h @@ -0,0 +1,47 @@ +/***************************************************************************** + +Copyright (c) 1996, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/************************************************************************ +The index tree general types + +Created 2/17/1996 Heikki Tuuri +*************************************************************************/ + +#ifndef btr0types_h +#define btr0types_h + +#include "univ.i" + +#include "rem0types.h" +#include "page0types.h" + +typedef struct btr_pcur_struct btr_pcur_t; +typedef struct btr_cur_struct btr_cur_t; +typedef struct btr_search_struct btr_search_t; + +/* The size of a reference to data stored on a different page. +The reference is stored at the end of the prefix of the field +in the index record. */ +#define BTR_EXTERN_FIELD_REF_SIZE 20 + +/* A BLOB field reference full of zero, for use in assertions and tests. +Initially, BLOB field references are set to zero, in +dtuple_convert_big_rec(). */ +extern const byte field_ref_zero[BTR_EXTERN_FIELD_REF_SIZE]; + +#endif diff --git a/storage/xtradb/include/buf0buddy.h b/storage/xtradb/include/buf0buddy.h new file mode 100644 index 00000000000..2afef7913fc --- /dev/null +++ b/storage/xtradb/include/buf0buddy.h @@ -0,0 +1,91 @@ +/***************************************************************************** + +Copyright (c) 2006, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/****************************************************** +Binary buddy allocator for compressed pages + +Created December 2006 by Marko Makela +*******************************************************/ + +#ifndef buf0buddy_h +#define buf0buddy_h + +#ifdef UNIV_MATERIALIZE +# undef UNIV_INLINE +# define UNIV_INLINE +#endif + +#include "univ.i" +#include "buf0types.h" + +/************************************************************************** +Allocate a block. The thread calling this function must hold +buf_pool_mutex and must not hold buf_pool_zip_mutex or any +block->mutex. The buf_pool_mutex may only be released and reacquired +if lru != NULL. This function should only be used for allocating +compressed page frames or control blocks (buf_page_t). Allocated +control blocks must be properly initialized immediately after +buf_buddy_alloc() has returned the memory, before releasing +buf_pool_mutex. */ +UNIV_INLINE +void* +buf_buddy_alloc( +/*============*/ + /* out: allocated block, + possibly NULL if lru == NULL */ + ulint size, /* in: block size, up to UNIV_PAGE_SIZE */ + ibool* lru, /* in: pointer to a variable that will be assigned + TRUE if storage was allocated from the LRU list + and buf_pool_mutex was temporarily released, + or NULL if the LRU list should not be used */ + ibool have_page_hash_mutex) + __attribute__((malloc)); + +/************************************************************************** +Release a block. */ +UNIV_INLINE +void +buf_buddy_free( +/*===========*/ + void* buf, /* in: block to be freed, must not be + pointed to by the buffer pool */ + ulint size, /* in: block size, up to UNIV_PAGE_SIZE */ + ibool have_page_hash_mutex) + __attribute__((nonnull)); + +/** Statistics of buddy blocks of a given size. */ +struct buf_buddy_stat_struct { + /** Number of blocks allocated from the buddy system. */ + ulint used; + /** Number of blocks relocated by the buddy system. */ + ib_uint64_t relocated; + /** Total duration of block relocations, in microseconds. */ + ib_uint64_t relocated_usec; +}; + +typedef struct buf_buddy_stat_struct buf_buddy_stat_t; + +/** Statistics of the buddy system, indexed by block size. +Protected by buf_pool_mutex. */ +extern buf_buddy_stat_t buf_buddy_stat[BUF_BUDDY_SIZES + 1]; + +#ifndef UNIV_NONINL +# include "buf0buddy.ic" +#endif + +#endif /* buf0buddy_h */ diff --git a/storage/xtradb/include/buf0buddy.ic b/storage/xtradb/include/buf0buddy.ic new file mode 100644 index 00000000000..668959cdba2 --- /dev/null +++ b/storage/xtradb/include/buf0buddy.ic @@ -0,0 +1,145 @@ +/***************************************************************************** + +Copyright (c) 2006, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/****************************************************** +Binary buddy allocator for compressed pages + +Created December 2006 by Marko Makela +*******************************************************/ + +#ifdef UNIV_MATERIALIZE +# undef UNIV_INLINE +# define UNIV_INLINE +#endif + +#include "buf0buf.h" +#include "buf0buddy.h" +#include "ut0ut.h" +#include "sync0sync.h" + +/************************************************************************** +Allocate a block. The thread calling this function must hold +buf_pool_mutex and must not hold buf_pool_zip_mutex or any block->mutex. +The buf_pool_mutex may only be released and reacquired if lru != NULL. */ +UNIV_INTERN +void* +buf_buddy_alloc_low( +/*================*/ + /* out: allocated block, + possibly NULL if lru==NULL */ + ulint i, /* in: index of buf_pool->zip_free[], + or BUF_BUDDY_SIZES */ + ibool* lru, /* in: pointer to a variable that will be assigned + TRUE if storage was allocated from the LRU list + and buf_pool_mutex was temporarily released, + or NULL if the LRU list should not be used */ + ibool have_page_hash_mutex) + __attribute__((malloc)); + +/************************************************************************** +Deallocate a block. */ +UNIV_INTERN +void +buf_buddy_free_low( +/*===============*/ + void* buf, /* in: block to be freed, must not be + pointed to by the buffer pool */ + ulint i, /* in: index of buf_pool->zip_free[], + or BUF_BUDDY_SIZES */ + ibool have_page_hash_mutex) + __attribute__((nonnull)); + +/************************************************************************** +Get the index of buf_pool->zip_free[] for a given block size. */ +UNIV_INLINE +ulint +buf_buddy_get_slot( +/*===============*/ + /* out: index of buf_pool->zip_free[], + or BUF_BUDDY_SIZES */ + ulint size) /* in: block size */ +{ + ulint i; + ulint s; + + for (i = 0, s = BUF_BUDDY_LOW; s < size; i++, s <<= 1) { + } + + ut_ad(i <= BUF_BUDDY_SIZES); + return(i); +} + +/************************************************************************** +Allocate a block. The thread calling this function must hold +buf_pool_mutex and must not hold buf_pool_zip_mutex or any +block->mutex. The buf_pool_mutex may only be released and reacquired +if lru != NULL. This function should only be used for allocating +compressed page frames or control blocks (buf_page_t). Allocated +control blocks must be properly initialized immediately after +buf_buddy_alloc() has returned the memory, before releasing +buf_pool_mutex. */ +UNIV_INLINE +void* +buf_buddy_alloc( +/*============*/ + /* out: allocated block, + possibly NULL if lru == NULL */ + ulint size, /* in: block size, up to UNIV_PAGE_SIZE */ + ibool* lru, /* in: pointer to a variable that will be assigned + TRUE if storage was allocated from the LRU list + and buf_pool_mutex was temporarily released, + or NULL if the LRU list should not be used */ + ibool have_page_hash_mutex) +{ + //ut_ad(buf_pool_mutex_own()); + + return(buf_buddy_alloc_low(buf_buddy_get_slot(size), lru, have_page_hash_mutex)); +} + +/************************************************************************** +Deallocate a block. */ +UNIV_INLINE +void +buf_buddy_free( +/*===========*/ + void* buf, /* in: block to be freed, must not be + pointed to by the buffer pool */ + ulint size, /* in: block size, up to UNIV_PAGE_SIZE */ + ibool have_page_hash_mutex) +{ + //ut_ad(buf_pool_mutex_own()); + + if (!have_page_hash_mutex) { + mutex_enter(&LRU_list_mutex); + rw_lock_x_lock(&page_hash_latch); + } + + mutex_enter(&zip_free_mutex); + buf_buddy_free_low(buf, buf_buddy_get_slot(size), TRUE); + mutex_exit(&zip_free_mutex); + + if (!have_page_hash_mutex) { + mutex_exit(&LRU_list_mutex); + rw_lock_x_unlock(&page_hash_latch); + } +} + +#ifdef UNIV_MATERIALIZE +# undef UNIV_INLINE +# define UNIV_INLINE UNIV_INLINE_ORIGINAL +#endif diff --git a/storage/xtradb/include/buf0buf.h b/storage/xtradb/include/buf0buf.h new file mode 100644 index 00000000000..f89e772513a --- /dev/null +++ b/storage/xtradb/include/buf0buf.h @@ -0,0 +1,1421 @@ +/***************************************************************************** + +Copyright (c) 1995, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/****************************************************** +The database buffer pool high-level routines + +Created 11/5/1995 Heikki Tuuri +*******************************************************/ + +#ifndef buf0buf_h +#define buf0buf_h + +#include "univ.i" +#include "fil0fil.h" +#include "mtr0types.h" +#include "buf0types.h" +#include "sync0rw.h" +#include "hash0hash.h" +#include "ut0byte.h" +#include "os0proc.h" +#include "page0types.h" + +/* Modes for buf_page_get_gen */ +#define BUF_GET 10 /* get always */ +#define BUF_GET_IF_IN_POOL 11 /* get if in pool */ +#define BUF_GET_NO_LATCH 14 /* get and bufferfix, but set no latch; + we have separated this case, because + it is error-prone programming not to + set a latch, and it should be used + with care */ +/* Modes for buf_page_get_known_nowait */ +#define BUF_MAKE_YOUNG 51 +#define BUF_KEEP_OLD 52 +/* Magic value to use instead of checksums when they are disabled */ +#define BUF_NO_CHECKSUM_MAGIC 0xDEADBEEFUL + +extern buf_pool_t* buf_pool; /* The buffer pool of the database */ +#ifdef UNIV_DEBUG +extern ibool buf_debug_prints;/* If this is set TRUE, the program + prints info whenever read or flush + occurs */ +#endif /* UNIV_DEBUG */ +extern ulint srv_buf_pool_write_requests; /* variable to count write request + issued */ + +/* States of a control block (@see buf_page_struct). +The enumeration values must be 0..7. */ +enum buf_page_state { + BUF_BLOCK_ZIP_FREE = 0, /* contains a free compressed page */ + BUF_BLOCK_ZIP_PAGE, /* contains a clean compressed page */ + BUF_BLOCK_ZIP_DIRTY, /* contains a compressed page that is + in the buf_pool->flush_list */ + + /* The constants for compressed-only pages must precede + BUF_BLOCK_NOT_USED; @see buf_block_state_valid() */ + + BUF_BLOCK_NOT_USED, /* is in the free list */ + BUF_BLOCK_READY_FOR_USE, /* when buf_LRU_get_free_block returns + a block, it is in this state */ + BUF_BLOCK_FILE_PAGE, /* contains a buffered file page */ + BUF_BLOCK_MEMORY, /* contains some main memory object */ + BUF_BLOCK_REMOVE_HASH /* hash index should be removed + before putting to the free list */ +}; + +/************************************************************************ +Creates the buffer pool. */ +UNIV_INTERN +buf_pool_t* +buf_pool_init(void); +/*===============*/ + /* out, own: buf_pool object, NULL if not + enough memory or error */ +/************************************************************************ +Frees the buffer pool at shutdown. This must not be invoked before +freeing all mutexes. */ +UNIV_INTERN +void +buf_pool_free(void); +/*===============*/ + +/************************************************************************ +Drops the adaptive hash index. To prevent a livelock, this function +is only to be called while holding btr_search_latch and while +btr_search_enabled == FALSE. */ +UNIV_INTERN +void +buf_pool_drop_hash_index(void); +/*==========================*/ + +/************************************************************************ +Relocate a buffer control block. Relocates the block on the LRU list +and in buf_pool->page_hash. Does not relocate bpage->list. +The caller must take care of relocating bpage->list. */ +UNIV_INTERN +void +buf_relocate( +/*=========*/ + buf_page_t* bpage, /* in/out: control block being relocated; + buf_page_get_state(bpage) must be + BUF_BLOCK_ZIP_DIRTY or BUF_BLOCK_ZIP_PAGE */ + buf_page_t* dpage) /* in/out: destination control block */ + __attribute__((nonnull)); +/************************************************************************ +Resizes the buffer pool. */ +UNIV_INTERN +void +buf_pool_resize(void); +/*=================*/ +/************************************************************************* +Gets the current size of buffer buf_pool in bytes. */ +UNIV_INLINE +ulint +buf_pool_get_curr_size(void); +/*========================*/ + /* out: size in bytes */ +/************************************************************************ +Gets the smallest oldest_modification lsn for any page in the pool. Returns +zero if all modified pages have been flushed to disk. */ +UNIV_INLINE +ib_uint64_t +buf_pool_get_oldest_modification(void); +/*==================================*/ + /* out: oldest modification in pool, + zero if none */ +/************************************************************************ +Allocates a buffer block. */ +UNIV_INLINE +buf_block_t* +buf_block_alloc( +/*============*/ + /* out, own: the allocated block, + in state BUF_BLOCK_MEMORY */ + ulint zip_size); /* in: compressed page size in bytes, + or 0 if uncompressed tablespace */ +/************************************************************************ +Frees a buffer block which does not contain a file page. */ +UNIV_INLINE +void +buf_block_free( +/*===========*/ + buf_block_t* block); /* in, own: block to be freed */ +/************************************************************************* +Copies contents of a buffer frame to a given buffer. */ +UNIV_INLINE +byte* +buf_frame_copy( +/*===========*/ + /* out: buf */ + byte* buf, /* in: buffer to copy to */ + const buf_frame_t* frame); /* in: buffer frame */ +/****************************************************************** +NOTE! The following macros should be used instead of buf_page_get_gen, +to improve debugging. Only values RW_S_LATCH and RW_X_LATCH are allowed +in LA! */ +#define buf_page_get(SP, ZS, OF, LA, MTR) buf_page_get_gen(\ + SP, ZS, OF, LA, NULL,\ + BUF_GET, __FILE__, __LINE__, MTR) +/****************************************************************** +Use these macros to bufferfix a page with no latching. Remember not to +read the contents of the page unless you know it is safe. Do not modify +the contents of the page! We have separated this case, because it is +error-prone programming not to set a latch, and it should be used +with care. */ +#define buf_page_get_with_no_latch(SP, ZS, OF, MTR) buf_page_get_gen(\ + SP, ZS, OF, RW_NO_LATCH, NULL,\ + BUF_GET_NO_LATCH, __FILE__, __LINE__, MTR) +/****************************************************************** +NOTE! The following macros should be used instead of +buf_page_optimistic_get_func, to improve debugging. Only values RW_S_LATCH and +RW_X_LATCH are allowed as LA! */ +#define buf_page_optimistic_get(LA, BL, MC, MTR) \ + buf_page_optimistic_get_func(LA, BL, MC, __FILE__, __LINE__, MTR) +/************************************************************************ +This is the general function used to get optimistic access to a database +page. */ +UNIV_INTERN +ibool +buf_page_optimistic_get_func( +/*=========================*/ + /* out: TRUE if success */ + ulint rw_latch,/* in: RW_S_LATCH, RW_X_LATCH */ + buf_block_t* block, /* in: guessed block */ + ib_uint64_t modify_clock,/* in: modify clock value if mode is + ..._GUESS_ON_CLOCK */ + const char* file, /* in: file name */ + ulint line, /* in: line where called */ + mtr_t* mtr); /* in: mini-transaction */ +/************************************************************************ +This is used to get access to a known database page, when no waiting can be +done. */ +UNIV_INTERN +ibool +buf_page_get_known_nowait( +/*======================*/ + /* out: TRUE if success */ + ulint rw_latch,/* in: RW_S_LATCH, RW_X_LATCH */ + buf_block_t* block, /* in: the known page */ + ulint mode, /* in: BUF_MAKE_YOUNG or BUF_KEEP_OLD */ + const char* file, /* in: file name */ + ulint line, /* in: line where called */ + mtr_t* mtr); /* in: mini-transaction */ + +/*********************************************************************** +Given a tablespace id and page number tries to get that page. If the +page is not in the buffer pool it is not loaded and NULL is returned. +Suitable for using when holding the kernel mutex. */ + +const buf_block_t* +buf_page_try_get_func( +/*==================*/ + ulint space_id,/* in: tablespace id */ + ulint page_no,/* in: page number */ + const char* file, /* in: file name */ + ulint line, /* in: line where called */ + mtr_t* mtr); /* in: mini-transaction */ + +#define buf_page_try_get(space_id, page_no, mtr) \ + buf_page_try_get_func(space_id, page_no, __FILE__, __LINE__, mtr); + +/************************************************************************ +Get read access to a compressed page (usually of type +FIL_PAGE_TYPE_ZBLOB or FIL_PAGE_TYPE_ZBLOB2). +The page must be released with buf_page_release_zip(). +NOTE: the page is not protected by any latch. Mutual exclusion has to +be implemented at a higher level. In other words, all possible +accesses to a given page through this function must be protected by +the same set of mutexes or latches. */ +UNIV_INTERN +buf_page_t* +buf_page_get_zip( +/*=============*/ + /* out: pointer to the block, + or NULL if not compressed */ + ulint space, /* in: space id */ + ulint zip_size,/* in: compressed page size */ + ulint offset);/* in: page number */ +/************************************************************************ +This is the general function used to get access to a database page. */ +UNIV_INTERN +buf_block_t* +buf_page_get_gen( +/*=============*/ + /* out: pointer to the block or NULL */ + ulint space, /* in: space id */ + ulint zip_size,/* in: compressed page size in bytes + or 0 for uncompressed pages */ + ulint offset, /* in: page number */ + ulint rw_latch,/* in: RW_S_LATCH, RW_X_LATCH, RW_NO_LATCH */ + buf_block_t* guess, /* in: guessed block or NULL */ + ulint mode, /* in: BUF_GET, BUF_GET_IF_IN_POOL, + BUF_GET_NO_LATCH */ + const char* file, /* in: file name */ + ulint line, /* in: line where called */ + mtr_t* mtr); /* in: mini-transaction */ +/************************************************************************ +Initializes a page to the buffer buf_pool. The page is usually not read +from a file even if it cannot be found in the buffer buf_pool. This is one +of the functions which perform to a block a state transition NOT_USED => +FILE_PAGE (the other is buf_page_get_gen). */ +UNIV_INTERN +buf_block_t* +buf_page_create( +/*============*/ + /* out: pointer to the block, page bufferfixed */ + ulint space, /* in: space id */ + ulint offset, /* in: offset of the page within space in units of + a page */ + ulint zip_size,/* in: compressed page size, or 0 */ + mtr_t* mtr); /* in: mini-transaction handle */ +#ifdef UNIV_HOTBACKUP +/************************************************************************ +Inits a page to the buffer buf_pool, for use in ibbackup --restore. */ +UNIV_INTERN +void +buf_page_init_for_backup_restore( +/*=============================*/ + ulint space, /* in: space id */ + ulint offset, /* in: offset of the page within space + in units of a page */ + ulint zip_size,/* in: compressed page size in bytes + or 0 for uncompressed pages */ + buf_block_t* block); /* in: block to init */ +#endif /* UNIV_HOTBACKUP */ +/************************************************************************ +Releases a compressed-only page acquired with buf_page_get_zip(). */ +UNIV_INLINE +void +buf_page_release_zip( +/*=================*/ + buf_page_t* bpage); /* in: buffer block */ +/************************************************************************ +Decrements the bufferfix count of a buffer control block and releases +a latch, if specified. */ +UNIV_INLINE +void +buf_page_release( +/*=============*/ + buf_block_t* block, /* in: buffer block */ + ulint rw_latch, /* in: RW_S_LATCH, RW_X_LATCH, + RW_NO_LATCH */ + mtr_t* mtr); /* in: mtr */ +/************************************************************************ +Moves a page to the start of the buffer pool LRU list. This high-level +function can be used to prevent an important page from from slipping out of +the buffer pool. */ +UNIV_INTERN +void +buf_page_make_young( +/*================*/ + buf_page_t* bpage); /* in: buffer block of a file page */ +/************************************************************************ +Returns TRUE if the page can be found in the buffer pool hash table. NOTE +that it is possible that the page is not yet read from disk, though. */ +UNIV_INLINE +ibool +buf_page_peek( +/*==========*/ + /* out: TRUE if found from page hash table, + NOTE that the page is not necessarily yet read + from disk! */ + ulint space, /* in: space id */ + ulint offset);/* in: page number */ +/************************************************************************ +Resets the check_index_page_at_flush field of a page if found in the buffer +pool. */ +UNIV_INTERN +void +buf_reset_check_index_page_at_flush( +/*================================*/ + ulint space, /* in: space id */ + ulint offset);/* in: page number */ +#ifdef UNIV_DEBUG_FILE_ACCESSES +/************************************************************************ +Sets file_page_was_freed TRUE if the page is found in the buffer pool. +This function should be called when we free a file page and want the +debug version to check that it is not accessed any more unless +reallocated. */ +UNIV_INTERN +buf_page_t* +buf_page_set_file_page_was_freed( +/*=============================*/ + /* out: control block if found in page hash table, + otherwise NULL */ + ulint space, /* in: space id */ + ulint offset);/* in: page number */ +/************************************************************************ +Sets file_page_was_freed FALSE if the page is found in the buffer pool. +This function should be called when we free a file page and want the +debug version to check that it is not accessed any more unless +reallocated. */ +UNIV_INTERN +buf_page_t* +buf_page_reset_file_page_was_freed( +/*===============================*/ + /* out: control block if found in page hash table, + otherwise NULL */ + ulint space, /* in: space id */ + ulint offset); /* in: page number */ +#endif /* UNIV_DEBUG_FILE_ACCESSES */ +/************************************************************************ +Reads the freed_page_clock of a buffer block. */ +UNIV_INLINE +ulint +buf_page_get_freed_page_clock( +/*==========================*/ + /* out: freed_page_clock */ + const buf_page_t* bpage) /* in: block */ + __attribute__((pure)); +/************************************************************************ +Reads the freed_page_clock of a buffer block. */ +UNIV_INLINE +ulint +buf_block_get_freed_page_clock( +/*===========================*/ + /* out: freed_page_clock */ + const buf_block_t* block) /* in: block */ + __attribute__((pure)); + +/************************************************************************ +Recommends a move of a block to the start of the LRU list if there is danger +of dropping from the buffer pool. NOTE: does not reserve the buffer pool +mutex. */ +UNIV_INLINE +ibool +buf_page_peek_if_too_old( +/*=====================*/ + /* out: TRUE if should be made + younger */ + const buf_page_t* bpage); /* in: block to make younger */ +/************************************************************************ +Returns the current state of is_hashed of a page. FALSE if the page is +not in the pool. NOTE that this operation does not fix the page in the +pool if it is found there. */ +UNIV_INTERN +ibool +buf_page_peek_if_search_hashed( +/*===========================*/ + /* out: TRUE if page hash index is built in search + system */ + ulint space, /* in: space id */ + ulint offset);/* in: page number */ +/************************************************************************ +Gets the youngest modification log sequence number for a frame. +Returns zero if not file page or no modification occurred yet. */ +UNIV_INLINE +ib_uint64_t +buf_page_get_newest_modification( +/*=============================*/ + /* out: newest modification to page */ + const buf_page_t* bpage); /* in: block containing the + page frame */ +/************************************************************************ +Increments the modify clock of a frame by 1. The caller must (1) own the +buf_pool mutex and block bufferfix count has to be zero, (2) or own an x-lock +on the block. */ +UNIV_INLINE +void +buf_block_modify_clock_inc( +/*=======================*/ + buf_block_t* block); /* in: block */ +/************************************************************************ +Returns the value of the modify clock. The caller must have an s-lock +or x-lock on the block. */ +UNIV_INLINE +ib_uint64_t +buf_block_get_modify_clock( +/*=======================*/ + /* out: value */ + buf_block_t* block); /* in: block */ +/************************************************************************ +Calculates a page checksum which is stored to the page when it is written +to a file. Note that we must be careful to calculate the same value +on 32-bit and 64-bit architectures. */ +UNIV_INTERN +ulint +buf_calc_page_new_checksum( +/*=======================*/ + /* out: checksum */ + const byte* page); /* in: buffer page */ +/************************************************************************ +In versions < 4.0.14 and < 4.1.1 there was a bug that the checksum only +looked at the first few bytes of the page. This calculates that old +checksum. +NOTE: we must first store the new formula checksum to +FIL_PAGE_SPACE_OR_CHKSUM before calculating and storing this old checksum +because this takes that field as an input! */ +UNIV_INTERN +ulint +buf_calc_page_old_checksum( +/*=======================*/ + /* out: checksum */ + const byte* page); /* in: buffer page */ +/************************************************************************ +Checks if a page is corrupt. */ +UNIV_INTERN +ibool +buf_page_is_corrupted( +/*==================*/ + /* out: TRUE if corrupted */ + const byte* read_buf, /* in: a database page */ + ulint zip_size); /* in: size of compressed page; + 0 for uncompressed pages */ +/************************************************************************** +Gets the space id, page offset, and byte offset within page of a +pointer pointing to a buffer frame containing a file page. */ +UNIV_INLINE +void +buf_ptr_get_fsp_addr( +/*=================*/ + const void* ptr, /* in: pointer to a buffer frame */ + ulint* space, /* out: space id */ + fil_addr_t* addr); /* out: page offset and byte offset */ +/************************************************************************** +Gets the hash value of a block. This can be used in searches in the +lock hash table. */ +UNIV_INLINE +ulint +buf_block_get_lock_hash_val( +/*========================*/ + /* out: lock hash value */ + const buf_block_t* block) /* in: block */ + __attribute__((pure)); +#ifdef UNIV_DEBUG +/************************************************************************* +Finds a block in the buffer pool that points to a +given compressed page. */ +UNIV_INTERN +buf_block_t* +buf_pool_contains_zip( +/*==================*/ + /* out: buffer block pointing to + the compressed page, or NULL */ + const void* data); /* in: pointer to compressed page */ +#endif /* UNIV_DEBUG */ +#if defined UNIV_DEBUG || defined UNIV_BUF_DEBUG +/************************************************************************* +Validates the buffer pool data structure. */ +UNIV_INTERN +ibool +buf_validate(void); +/*==============*/ +#endif /* UNIV_DEBUG || UNIV_BUF_DEBUG */ +#if defined UNIV_DEBUG_PRINT || defined UNIV_DEBUG || defined UNIV_BUF_DEBUG +/************************************************************************* +Prints info of the buffer pool data structure. */ +UNIV_INTERN +void +buf_print(void); +/*============*/ +#endif /* UNIV_DEBUG_PRINT || UNIV_DEBUG || UNIV_BUF_DEBUG */ +/************************************************************************ +Prints a page to stderr. */ +UNIV_INTERN +void +buf_page_print( +/*===========*/ + const byte* read_buf, /* in: a database page */ + ulint zip_size); /* in: compressed page size, or + 0 for uncompressed pages */ +#ifdef UNIV_DEBUG +/************************************************************************* +Returns the number of latched pages in the buffer pool. */ +UNIV_INTERN +ulint +buf_get_latched_pages_number(void); +/*==============================*/ +#endif /* UNIV_DEBUG */ +/************************************************************************* +Returns the number of pending buf pool ios. */ +UNIV_INTERN +ulint +buf_get_n_pending_ios(void); +/*=======================*/ +/************************************************************************* +Prints info of the buffer i/o. */ +UNIV_INTERN +void +buf_print_io( +/*=========*/ + FILE* file); /* in: file where to print */ +/************************************************************************* +Returns the ratio in percents of modified pages in the buffer pool / +database pages in the buffer pool. */ +UNIV_INTERN +ulint +buf_get_modified_ratio_pct(void); +/*============================*/ +/************************************************************************** +Refreshes the statistics used to print per-second averages. */ +UNIV_INTERN +void +buf_refresh_io_stats(void); +/*======================*/ +/************************************************************************* +Checks that all file pages in the buffer are in a replaceable state. */ +UNIV_INTERN +ibool +buf_all_freed(void); +/*===============*/ +/************************************************************************* +Checks that there currently are no pending i/o-operations for the buffer +pool. */ +UNIV_INTERN +ibool +buf_pool_check_no_pending_io(void); +/*==============================*/ + /* out: TRUE if there is no pending i/o */ +/************************************************************************* +Invalidates the file pages in the buffer pool when an archive recovery is +completed. All the file pages buffered must be in a replaceable state when +this function is called: not latched and not modified. */ +UNIV_INTERN +void +buf_pool_invalidate(void); +/*=====================*/ + +/*======================================================================== +--------------------------- LOWER LEVEL ROUTINES ------------------------- +=========================================================================*/ + +#ifdef UNIV_SYNC_DEBUG +/************************************************************************* +Adds latch level info for the rw-lock protecting the buffer frame. This +should be called in the debug version after a successful latching of a +page if we know the latching order level of the acquired latch. */ +UNIV_INLINE +void +buf_block_dbg_add_level( +/*====================*/ + buf_block_t* block, /* in: buffer page + where we have acquired latch */ + ulint level); /* in: latching order level */ +#else /* UNIV_SYNC_DEBUG */ +# define buf_block_dbg_add_level(block, level) /* nothing */ +#endif /* UNIV_SYNC_DEBUG */ +/************************************************************************* +Gets the state of a block. */ +UNIV_INLINE +enum buf_page_state +buf_page_get_state( +/*===============*/ + /* out: state */ + const buf_page_t* bpage); /* in: pointer to the control block */ +/************************************************************************* +Gets the state of a block. */ +UNIV_INLINE +enum buf_page_state +buf_block_get_state( +/*================*/ + /* out: state */ + const buf_block_t* block) /* in: pointer to the control block */ + __attribute__((pure)); +/************************************************************************* +Sets the state of a block. */ +UNIV_INLINE +void +buf_page_set_state( +/*===============*/ + buf_page_t* bpage, /* in/out: pointer to control block */ + enum buf_page_state state); /* in: state */ +/************************************************************************* +Sets the state of a block. */ +UNIV_INLINE +void +buf_block_set_state( +/*================*/ + buf_block_t* block, /* in/out: pointer to control block */ + enum buf_page_state state); /* in: state */ +/************************************************************************* +Determines if a block is mapped to a tablespace. */ +UNIV_INLINE +ibool +buf_page_in_file( +/*=============*/ + /* out: TRUE if mapped */ + const buf_page_t* bpage) /* in: pointer to control block */ + __attribute__((pure)); +/************************************************************************* +Determines if a block should be on unzip_LRU list. */ +UNIV_INLINE +ibool +buf_page_belongs_to_unzip_LRU( +/*==========================*/ + /* out: TRUE if block belongs + to unzip_LRU */ + const buf_page_t* bpage) /* in: pointer to control block */ + __attribute__((pure)); +/************************************************************************* +Determine the approximate LRU list position of a block. */ +UNIV_INLINE +ulint +buf_page_get_LRU_position( +/*======================*/ + /* out: LRU list position */ + const buf_page_t* bpage) /* in: control block */ + __attribute__((pure)); + +/************************************************************************* +Gets the mutex of a block. */ +UNIV_INLINE +mutex_t* +buf_page_get_mutex( +/*===============*/ + /* out: pointer to mutex + protecting bpage */ + const buf_page_t* bpage) /* in: pointer to control block */ + __attribute__((pure)); + +/************************************************************************* +Get the flush type of a page. */ +UNIV_INLINE +enum buf_flush +buf_page_get_flush_type( +/*====================*/ + /* out: flush type */ + const buf_page_t* bpage) /* in: buffer page */ + __attribute__((pure)); +/************************************************************************* +Set the flush type of a page. */ +UNIV_INLINE +void +buf_page_set_flush_type( +/*====================*/ + buf_page_t* bpage, /* in: buffer page */ + enum buf_flush flush_type); /* in: flush type */ +/************************************************************************* +Map a block to a file page. */ +UNIV_INLINE +void +buf_block_set_file_page( +/*====================*/ + buf_block_t* block, /* in/out: pointer to control block */ + ulint space, /* in: tablespace id */ + ulint page_no);/* in: page number */ +/************************************************************************* +Gets the io_fix state of a block. */ +UNIV_INLINE +enum buf_io_fix +buf_page_get_io_fix( +/*================*/ + /* out: io_fix state */ + const buf_page_t* bpage) /* in: pointer to the control block */ + __attribute__((pure)); +/************************************************************************* +Gets the io_fix state of a block. */ +UNIV_INLINE +enum buf_io_fix +buf_block_get_io_fix( +/*================*/ + /* out: io_fix state */ + const buf_block_t* block) /* in: pointer to the control block */ + __attribute__((pure)); +/************************************************************************* +Sets the io_fix state of a block. */ +UNIV_INLINE +void +buf_page_set_io_fix( +/*================*/ + buf_page_t* bpage, /* in/out: control block */ + enum buf_io_fix io_fix);/* in: io_fix state */ +/************************************************************************* +Sets the io_fix state of a block. */ +UNIV_INLINE +void +buf_block_set_io_fix( +/*=================*/ + buf_block_t* block, /* in/out: control block */ + enum buf_io_fix io_fix);/* in: io_fix state */ + +/************************************************************************ +Determine if a buffer block can be relocated in memory. The block +can be dirty, but it must not be I/O-fixed or bufferfixed. */ +UNIV_INLINE +ibool +buf_page_can_relocate( +/*==================*/ + const buf_page_t* bpage) /* control block being relocated */ + __attribute__((pure)); + +/************************************************************************* +Determine if a block has been flagged old. */ +UNIV_INLINE +ibool +buf_page_is_old( +/*============*/ + /* out: TRUE if old */ + const buf_page_t* bpage) /* in: control block */ + __attribute__((pure)); +/************************************************************************* +Flag a block old. */ +UNIV_INLINE +void +buf_page_set_old( +/*=============*/ + buf_page_t* bpage, /* in/out: control block */ + ibool old); /* in: old */ +/************************************************************************* +Determine if a block has been accessed in the buffer pool. */ +UNIV_INLINE +ibool +buf_page_is_accessed( +/*=================*/ + /* out: TRUE if accessed */ + const buf_page_t* bpage) /* in: control block */ + __attribute__((pure)); +/************************************************************************* +Flag a block accessed. */ +UNIV_INLINE +void +buf_page_set_accessed( +/*==================*/ + buf_page_t* bpage, /* in/out: control block */ + ibool accessed); /* in: accessed */ +/************************************************************************* +Gets the buf_block_t handle of a buffered file block if an uncompressed +page frame exists, or NULL. */ +UNIV_INLINE +buf_block_t* +buf_page_get_block( +/*===============*/ + /* out: control block, or NULL */ + buf_page_t* bpage) /* in: control block, or NULL */ + __attribute__((pure)); +#ifdef UNIV_DEBUG +/************************************************************************* +Gets a pointer to the memory frame of a block. */ +UNIV_INLINE +buf_frame_t* +buf_block_get_frame( +/*================*/ + /* out: pointer to the frame */ + const buf_block_t* block) /* in: pointer to the control block */ + __attribute__((pure)); +#else /* UNIV_DEBUG */ +# define buf_block_get_frame(block) (block)->frame +#endif /* UNIV_DEBUG */ +/************************************************************************* +Gets the space id of a block. */ +UNIV_INLINE +ulint +buf_page_get_space( +/*===============*/ + /* out: space id */ + const buf_page_t* bpage) /* in: pointer to the control block */ + __attribute__((pure)); +/************************************************************************* +Gets the space id of a block. */ +UNIV_INLINE +ulint +buf_block_get_space( +/*================*/ + /* out: space id */ + const buf_block_t* block) /* in: pointer to the control block */ + __attribute__((pure)); +/************************************************************************* +Gets the page number of a block. */ +UNIV_INLINE +ulint +buf_page_get_page_no( +/*=================*/ + /* out: page number */ + const buf_page_t* bpage) /* in: pointer to the control block */ + __attribute__((pure)); +/************************************************************************* +Gets the page number of a block. */ +UNIV_INLINE +ulint +buf_block_get_page_no( +/*==================*/ + /* out: page number */ + const buf_block_t* block) /* in: pointer to the control block */ + __attribute__((pure)); +/************************************************************************* +Gets the compressed page size of a block. */ +UNIV_INLINE +ulint +buf_page_get_zip_size( +/*==================*/ + /* out: compressed page size, or 0 */ + const buf_page_t* bpage) /* in: pointer to the control block */ + __attribute__((pure)); +/************************************************************************* +Gets the compressed page size of a block. */ +UNIV_INLINE +ulint +buf_block_get_zip_size( +/*===================*/ + /* out: compressed page size, or 0 */ + const buf_block_t* block) /* in: pointer to the control block */ + __attribute__((pure)); +/************************************************************************* +Gets the compressed page descriptor corresponding to an uncompressed page +if applicable. */ +#define buf_block_get_page_zip(block) \ + (UNIV_LIKELY_NULL((block)->page.zip.data) ? &(block)->page.zip : NULL) +/*********************************************************************** +Gets the block to whose frame the pointer is pointing to. */ +UNIV_INTERN +buf_block_t* +buf_block_align( +/*============*/ + /* out: pointer to block, never NULL */ + const byte* ptr); /* in: pointer to a frame */ +#if defined UNIV_DEBUG || defined UNIV_ZIP_DEBUG +/************************************************************************* +Gets the compressed page descriptor corresponding to an uncompressed page +if applicable. */ +UNIV_INLINE +const page_zip_des_t* +buf_frame_get_page_zip( +/*===================*/ + /* out: compressed page descriptor, or NULL */ + const byte* ptr); /* in: pointer to the page */ +#endif /* UNIV_DEBUG || UNIV_ZIP_DEBUG */ +/************************************************************************ +This function is used to get info if there is an io operation +going on on a buffer page. */ +UNIV_INLINE +ibool +buf_page_io_query( +/*==============*/ + /* out: TRUE if io going on */ + buf_page_t* bpage); /* in: pool block, must be bufferfixed */ +/************************************************************************ +Function which inits a page for read to the buffer buf_pool. If the page is +(1) already in buf_pool, or +(2) if we specify to read only ibuf pages and the page is not an ibuf page, or +(3) if the space is deleted or being deleted, +then this function does nothing. +Sets the io_fix flag to BUF_IO_READ and sets a non-recursive exclusive lock +on the buffer frame. The io-handler must take care that the flag is cleared +and the lock released later. */ +UNIV_INTERN +buf_page_t* +buf_page_init_for_read( +/*===================*/ + /* out: pointer to the block or NULL */ + ulint* err, /* out: DB_SUCCESS or DB_TABLESPACE_DELETED */ + ulint mode, /* in: BUF_READ_IBUF_PAGES_ONLY, ... */ + ulint space, /* in: space id */ + ulint zip_size,/* in: compressed page size, or 0 */ + ibool unzip, /* in: TRUE=request uncompressed page */ + ib_int64_t tablespace_version,/* in: prevents reading from a wrong + version of the tablespace in case we have done + DISCARD + IMPORT */ + ulint offset);/* in: page number */ +/************************************************************************ +Completes an asynchronous read or write request of a file page to or from +the buffer pool. */ +UNIV_INTERN +void +buf_page_io_complete( +/*=================*/ + buf_page_t* bpage); /* in: pointer to the block in question */ +/************************************************************************ +Calculates a folded value of a file page address to use in the page hash +table. */ +UNIV_INLINE +ulint +buf_page_address_fold( +/*==================*/ + /* out: the folded value */ + ulint space, /* in: space id */ + ulint offset) /* in: offset of the page within space */ + __attribute__((const)); +/********************************************************************** +Returns the control block of a file page, NULL if not found. */ +UNIV_INLINE +buf_page_t* +buf_page_hash_get( +/*==============*/ + /* out: block, NULL if not found */ + ulint space, /* in: space id */ + ulint offset);/* in: offset of the page within space */ +/********************************************************************** +Returns the control block of a file page, NULL if not found +or an uncompressed page frame does not exist. */ +UNIV_INLINE +buf_block_t* +buf_block_hash_get( +/*===============*/ + /* out: block, NULL if not found */ + ulint space, /* in: space id */ + ulint offset);/* in: offset of the page within space */ +/*********************************************************************** +Increments the pool clock by one and returns its new value. Remember that +in the 32 bit version the clock wraps around at 4 billion! */ +UNIV_INLINE +ulint +buf_pool_clock_tic(void); +/*====================*/ + /* out: new clock value */ +/************************************************************************* +Gets the current length of the free list of buffer blocks. */ +UNIV_INTERN +ulint +buf_get_free_list_len(void); +/*=======================*/ + + + +/* The common buffer control block structure +for compressed and uncompressed frames */ + +struct buf_page_struct{ + /* None of the following bit-fields must be modified without + holding buf_page_get_mutex() [block->mutex or buf_pool_zip_mutex], + since they can be stored in the same machine word. Some of them are + additionally protected by buf_pool_mutex. */ + + unsigned space:32; /* tablespace id; also protected + by buf_pool_mutex. */ + unsigned offset:32; /* page number; also protected + by buf_pool_mutex. */ + + unsigned state:3; /* state of the control block + (@see enum buf_page_state); also + protected by buf_pool_mutex. + State transitions from + BUF_BLOCK_READY_FOR_USE to + BUF_BLOCK_MEMORY need not be + protected by buf_page_get_mutex(). */ + unsigned flush_type:2; /* if this block is currently being + flushed to disk, this tells the + flush_type (@see enum buf_flush) */ + unsigned accessed:1; /* TRUE if the page has been accessed + while in the buffer pool: read-ahead + may read in pages which have not been + accessed yet; a thread is allowed to + read this for heuristic purposes + without holding any mutex or latch */ + unsigned io_fix:2; /* type of pending I/O operation + (@see enum buf_io_fix); also + protected by buf_pool_mutex */ + unsigned buf_fix_count:24;/* count of how manyfold this block + is currently bufferfixed */ + + page_zip_des_t zip; /* compressed page; zip.data + (but not the data it points to) is + also protected by buf_pool_mutex */ + buf_page_t* hash; /* node used in chaining to + buf_pool->page_hash or + buf_pool->zip_hash */ +#ifdef UNIV_DEBUG + ibool in_page_hash; /* TRUE if in buf_pool->page_hash */ + ibool in_zip_hash; /* TRUE if in buf_pool->zip_hash */ +#endif /* UNIV_DEBUG */ + + /* 2. Page flushing fields; protected by buf_pool_mutex */ + + /* UT_LIST_NODE_T(buf_page_t) list; */ + /* based on state, this is a list + node in one of the following lists + in buf_pool: + + BUF_BLOCK_NOT_USED: free + BUF_BLOCK_FILE_PAGE: flush_list + BUF_BLOCK_ZIP_DIRTY: flush_list + BUF_BLOCK_ZIP_PAGE: zip_clean + BUF_BLOCK_ZIP_FREE: zip_free[] */ + /* resplit for optimistic use */ + UT_LIST_NODE_T(buf_page_t) free; + UT_LIST_NODE_T(buf_page_t) flush_list; + UT_LIST_NODE_T(buf_page_t) zip_list; /* zip_clean or zip_free[] */ +#ifdef UNIV_DEBUG + ibool in_flush_list; /* TRUE if in buf_pool->flush_list; + when buf_pool_mutex is free, the + following should hold: in_flush_list + == (state == BUF_BLOCK_FILE_PAGE + || state == BUF_BLOCK_ZIP_DIRTY) */ + ibool in_free_list; /* TRUE if in buf_pool->free; when + buf_pool_mutex is free, the following + should hold: in_free_list + == (state == BUF_BLOCK_NOT_USED) */ +#endif /* UNIV_DEBUG */ + ib_uint64_t newest_modification; + /* log sequence number of the youngest + modification to this block, zero if + not modified */ + ib_uint64_t oldest_modification; + /* log sequence number of the START of + the log entry written of the oldest + modification to this block which has + not yet been flushed on disk; zero if + all modifications are on disk */ + + /* 3. LRU replacement algorithm fields; protected by + buf_pool_mutex only (not buf_pool_zip_mutex or block->mutex) */ + + UT_LIST_NODE_T(buf_page_t) LRU; + /* node of the LRU list */ +//#ifdef UNIV_DEBUG + ibool in_LRU_list; /* TRUE if the page is in the LRU list; + used in debugging */ +//#endif /* UNIV_DEBUG */ + unsigned old:1; /* TRUE if the block is in the old + blocks in the LRU list */ + unsigned LRU_position:31;/* value which monotonically decreases + (or may stay constant if old==TRUE) + toward the end of the LRU list, if + buf_pool->ulint_clock has not wrapped + around: NOTE that this value can only + be used in heuristic algorithms, + because of the possibility of a + wrap-around! */ + unsigned freed_page_clock:32;/* the value of + buf_pool->freed_page_clock when this + block was the last time put to the + head of the LRU list; a thread is + allowed to read this for heuristic + purposes without holding any mutex or + latch */ +#ifdef UNIV_DEBUG_FILE_ACCESSES + ibool file_page_was_freed; + /* this is set to TRUE when fsp + frees a page in buffer pool */ +#endif /* UNIV_DEBUG_FILE_ACCESSES */ +}; + +/* The buffer control block structure */ + +struct buf_block_struct{ + + /* 1. General fields */ + + buf_page_t page; /* page information; this must + be the first field, so that + buf_pool->page_hash can point + to buf_page_t or buf_block_t */ + UT_LIST_NODE_T(buf_block_t) unzip_LRU; + /* node of the decompressed LRU list; + a block is in the unzip_LRU list + if page.state == BUF_BLOCK_FILE_PAGE + and page.zip.data != NULL */ +//#ifdef UNIV_DEBUG + ibool in_unzip_LRU_list;/* TRUE if the page is in the + decompressed LRU list; + used in debugging */ +//#endif /* UNIV_DEBUG */ + byte* frame; /* pointer to buffer frame which + is of size UNIV_PAGE_SIZE, and + aligned to an address divisible by + UNIV_PAGE_SIZE */ + mutex_t mutex; /* mutex protecting this block: + state (also protected by the buffer + pool mutex), io_fix, buf_fix_count, + and accessed; we introduce this new + mutex in InnoDB-5.1 to relieve + contention on the buffer pool mutex */ + rw_lock_t lock; /* read-write lock of the buffer + frame */ + unsigned lock_hash_val:32;/* hashed value of the page address + in the record lock hash table */ + unsigned check_index_page_at_flush:1; + /* TRUE if we know that this is + an index page, and want the database + to check its consistency before flush; + note that there may be pages in the + buffer pool which are index pages, + but this flag is not set because + we do not keep track of all pages */ + + /* 2. Optimistic search field */ + + ib_uint64_t modify_clock; /* this clock is incremented every + time a pointer to a record on the + page may become obsolete; this is + used in the optimistic cursor + positioning: if the modify clock has + not changed, we know that the pointer + is still valid; this field may be + changed if the thread (1) owns the + pool mutex and the page is not + bufferfixed, or (2) the thread has an + x-latch on the block */ + + /* 3. Hash search fields: NOTE that the first 4 fields are NOT + protected by any semaphore! */ + + ulint n_hash_helps; /* counter which controls building + of a new hash index for the page */ + ulint n_fields; /* recommended prefix length for hash + search: number of full fields */ + ulint n_bytes; /* recommended prefix: number of bytes + in an incomplete field */ + ibool left_side; /* TRUE or FALSE, depending on + whether the leftmost record of several + records with the same prefix should be + indexed in the hash index */ + + /* These 6 fields may only be modified when we have + an x-latch on btr_search_latch AND + a) we are holding an s-latch or x-latch on block->lock or + b) we know that block->buf_fix_count == 0. + + An exception to this is when we init or create a page + in the buffer pool in buf0buf.c. */ + +#if defined UNIV_AHI_DEBUG || defined UNIV_DEBUG + ulint n_pointers; /* used in debugging: the number of + pointers in the adaptive hash index + pointing to this frame */ +#endif /* UNIV_AHI_DEBUG || UNIV_DEBUG */ + unsigned is_hashed:1; /* TRUE if hash index has already been + built on this page; note that it does + not guarantee that the index is + complete, though: there may have been + hash collisions, record deletions, + etc. */ + unsigned curr_n_fields:10;/* prefix length for hash indexing: + number of full fields */ + unsigned curr_n_bytes:15;/* number of bytes in hash indexing */ + unsigned curr_left_side:1;/* TRUE or FALSE in hash indexing */ + dict_index_t* index; /* Index for which the adaptive + hash index has been created. */ + /* 4. Debug fields */ +#ifdef UNIV_SYNC_DEBUG + rw_lock_t debug_latch; /* in the debug version, each thread + which bufferfixes the block acquires + an s-latch here; so we can use the + debug utilities in sync0rw */ +#endif +}; + +/* Check if a buf_block_t object is in a valid state. */ +#define buf_block_state_valid(block) \ +(buf_block_get_state(block) >= BUF_BLOCK_NOT_USED \ + && (buf_block_get_state(block) <= BUF_BLOCK_REMOVE_HASH)) + +/************************************************************************** +Compute the hash fold value for blocks in buf_pool->zip_hash. */ +#define BUF_POOL_ZIP_FOLD_PTR(ptr) ((ulint) (ptr) / UNIV_PAGE_SIZE) +#define BUF_POOL_ZIP_FOLD(b) BUF_POOL_ZIP_FOLD_PTR((b)->frame) +#define BUF_POOL_ZIP_FOLD_BPAGE(b) BUF_POOL_ZIP_FOLD((buf_block_t*) (b)) + +/* The buffer pool structure. NOTE! The definition appears here only for +other modules of this directory (buf) to see it. Do not use from outside! */ + +struct buf_pool_struct{ + + /* 1. General fields */ + + ulint n_chunks; /* number of buffer pool chunks */ + buf_chunk_t* chunks; /* buffer pool chunks */ + ulint curr_size; /* current pool size in pages */ + hash_table_t* page_hash; /* hash table of buf_page_t or + buf_block_t file pages, + buf_page_in_file() == TRUE, + indexed by (space_id, offset) */ + hash_table_t* zip_hash; /* hash table of buf_block_t blocks + whose frames are allocated to the + zip buddy system, + indexed by block->frame */ + ulint n_pend_reads; /* number of pending read operations */ + ulint n_pend_unzip; /* number of pending decompressions */ + + time_t last_printout_time; /* when buf_print was last time + called */ + ulint n_pages_read; /* number read operations */ + ulint n_pages_written;/* number write operations */ + ulint n_pages_created;/* number of pages created in the pool + with no read */ + ulint n_page_gets; /* number of page gets performed; + also successful searches through + the adaptive hash index are + counted as page gets; this field + is NOT protected by the buffer + pool mutex */ + ulint n_page_gets_old;/* n_page_gets when buf_print was + last time called: used to calculate + hit rate */ + ulint n_pages_read_old;/* n_pages_read when buf_print was + last time called */ + ulint n_pages_written_old;/* number write operations */ + ulint n_pages_created_old;/* number of pages created in + the pool with no read */ + /* 2. Page flushing algorithm fields */ + + UT_LIST_BASE_NODE_T(buf_page_t) flush_list; + /* base node of the modified block + list */ + ibool init_flush[BUF_FLUSH_N_TYPES]; + /* this is TRUE when a flush of the + given type is being initialized */ + ulint n_flush[BUF_FLUSH_N_TYPES]; + /* this is the number of pending + writes in the given flush type */ + os_event_t no_flush[BUF_FLUSH_N_TYPES]; + /* this is in the set state when there + is no flush batch of the given type + running */ + ulint ulint_clock; /* a sequence number used to count + time. NOTE! This counter wraps + around at 4 billion (if ulint == + 32 bits)! */ + ulint freed_page_clock;/* a sequence number used to count the + number of buffer blocks removed from + the end of the LRU list; NOTE that + this counter may wrap around at 4 + billion! A thread is allowed to + read this for heuristic purposes + without holding any mutex or latch */ + ulint LRU_flush_ended;/* when an LRU flush ends for a page, + this is incremented by one; this is + set to zero when a buffer block is + allocated */ + + /* 3. LRU replacement algorithm fields */ + + UT_LIST_BASE_NODE_T(buf_page_t) free; + /* base node of the free block list */ + UT_LIST_BASE_NODE_T(buf_page_t) LRU; + /* base node of the LRU list */ + buf_page_t* LRU_old; /* pointer to the about 3/8 oldest + blocks in the LRU list; NULL if LRU + length less than BUF_LRU_OLD_MIN_LEN; + NOTE: when LRU_old != NULL, its length + should always equal LRU_old_len */ + ulint LRU_old_len; /* length of the LRU list from + the block to which LRU_old points + onward, including that block; + see buf0lru.c for the restrictions + on this value; not defined if + LRU_old == NULL; + NOTE: LRU_old_len must be adjusted + whenever LRU_old shrinks or grows! */ + + UT_LIST_BASE_NODE_T(buf_block_t) unzip_LRU; + /* base node of the unzip_LRU list */ + + /* 4. Fields for the buddy allocator of compressed pages */ + UT_LIST_BASE_NODE_T(buf_page_t) zip_clean; + /* unmodified compressed pages */ + UT_LIST_BASE_NODE_T(buf_page_t) zip_free[BUF_BUDDY_SIZES]; + /* buddy free lists */ +#if BUF_BUDDY_HIGH != UNIV_PAGE_SIZE +# error "BUF_BUDDY_HIGH != UNIV_PAGE_SIZE" +#endif +#if BUF_BUDDY_LOW > PAGE_ZIP_MIN_SIZE +# error "BUF_BUDDY_LOW > PAGE_ZIP_MIN_SIZE" +#endif +}; + +/* mutex protecting the buffer pool struct and control blocks, except the +read-write lock in them */ +extern mutex_t buf_pool_mutex; +extern mutex_t LRU_list_mutex; +extern mutex_t flush_list_mutex; +extern rw_lock_t page_hash_latch; +extern mutex_t free_list_mutex; +extern mutex_t zip_free_mutex; +extern mutex_t zip_hash_mutex; +/* mutex protecting the control blocks of compressed-only pages +(of type buf_page_t, not buf_block_t) */ +extern mutex_t buf_pool_zip_mutex; + +/* Accessors for buf_pool_mutex. Use these instead of accessing +buf_pool_mutex directly. */ + +/* Test if buf_pool_mutex is owned. */ +#define buf_pool_mutex_own() mutex_own(&buf_pool_mutex) +/* Acquire the buffer pool mutex. */ +#define buf_pool_mutex_enter() do { \ + ut_ad(!mutex_own(&buf_pool_zip_mutex)); \ + mutex_enter(&buf_pool_mutex); \ +} while (0) + +#if defined UNIV_DEBUG || defined UNIV_BUF_DEBUG +/** Flag to forbid the release of the buffer pool mutex. +Protected by buf_pool_mutex. */ +extern ulint buf_pool_mutex_exit_forbidden; +/* Forbid the release of the buffer pool mutex. */ +# define buf_pool_mutex_exit_forbid() do { \ + ut_ad(buf_pool_mutex_own()); \ + buf_pool_mutex_exit_forbidden++; \ +} while (0) +/* Allow the release of the buffer pool mutex. */ +# define buf_pool_mutex_exit_allow() do { \ + ut_ad(buf_pool_mutex_own()); \ + ut_a(buf_pool_mutex_exit_forbidden); \ + buf_pool_mutex_exit_forbidden--; \ +} while (0) +/* Release the buffer pool mutex. */ +# define buf_pool_mutex_exit() do { \ + ut_a(!buf_pool_mutex_exit_forbidden); \ + mutex_exit(&buf_pool_mutex); \ +} while (0) +#else +/* Forbid the release of the buffer pool mutex. */ +# define buf_pool_mutex_exit_forbid() ((void) 0) +/* Allow the release of the buffer pool mutex. */ +# define buf_pool_mutex_exit_allow() ((void) 0) +/* Release the buffer pool mutex. */ +# define buf_pool_mutex_exit() mutex_exit(&buf_pool_mutex) +#endif + +/************************************************************************ +Let us list the consistency conditions for different control block states. + +NOT_USED: is in free list, not in LRU list, not in flush list, nor + page hash table +READY_FOR_USE: is not in free list, LRU list, or flush list, nor page + hash table +MEMORY: is not in free list, LRU list, or flush list, nor page + hash table +FILE_PAGE: space and offset are defined, is in page hash table + if io_fix == BUF_IO_WRITE, + pool: no_flush[flush_type] is in reset state, + pool: n_flush[flush_type] > 0 + + (1) if buf_fix_count == 0, then + is in LRU list, not in free list + is in flush list, + if and only if oldest_modification > 0 + is x-locked, + if and only if io_fix == BUF_IO_READ + is s-locked, + if and only if io_fix == BUF_IO_WRITE + + (2) if buf_fix_count > 0, then + is not in LRU list, not in free list + is in flush list, + if and only if oldest_modification > 0 + if io_fix == BUF_IO_READ, + is x-locked + if io_fix == BUF_IO_WRITE, + is s-locked + +State transitions: + +NOT_USED => READY_FOR_USE +READY_FOR_USE => MEMORY +READY_FOR_USE => FILE_PAGE +MEMORY => NOT_USED +FILE_PAGE => NOT_USED NOTE: This transition is allowed if and only if + (1) buf_fix_count == 0, + (2) oldest_modification == 0, and + (3) io_fix == 0. +*/ + +#ifndef UNIV_NONINL +#include "buf0buf.ic" +#endif + +#endif diff --git a/storage/xtradb/include/buf0buf.ic b/storage/xtradb/include/buf0buf.ic new file mode 100644 index 00000000000..2b7f643f59b --- /dev/null +++ b/storage/xtradb/include/buf0buf.ic @@ -0,0 +1,1110 @@ +/***************************************************************************** + +Copyright (c) 1995, 2009, Innobase Oy. All Rights Reserved. +Copyright (c) 2008, Google Inc. + +Portions of this file contain modifications contributed and copyrighted by +Google, Inc. Those modifications are gratefully acknowledged and are described +briefly in the InnoDB documentation. The contributions by Google are +incorporated with their permission, and subject to the conditions contained in +the file COPYING.Google. + +This program is free software; you can redistribute it and/or modify it under +the terms of the GNU General Public License as published by the Free Software +Foundation; version 2 of the License. + +This program is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + +You 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 + +*****************************************************************************/ + +/****************************************************** +The database buffer buf_pool + +Created 11/5/1995 Heikki Tuuri +*******************************************************/ + +#include "buf0flu.h" +#include "buf0lru.h" +#include "buf0rea.h" +#include "mtr0mtr.h" + +/************************************************************************ +Reads the freed_page_clock of a buffer block. */ +UNIV_INLINE +ulint +buf_page_get_freed_page_clock( +/*==========================*/ + /* out: freed_page_clock */ + const buf_page_t* bpage) /* in: block */ +{ + /* This is sometimes read without holding buf_pool_mutex. */ + return(bpage->freed_page_clock); +} + +/************************************************************************ +Reads the freed_page_clock of a buffer block. */ +UNIV_INLINE +ulint +buf_block_get_freed_page_clock( +/*===========================*/ + /* out: freed_page_clock */ + const buf_block_t* block) /* in: block */ +{ + return(buf_page_get_freed_page_clock(&block->page)); +} + +/************************************************************************ +Recommends a move of a block to the start of the LRU list if there is danger +of dropping from the buffer pool. NOTE: does not reserve the buffer pool +mutex. */ +UNIV_INLINE +ibool +buf_page_peek_if_too_old( +/*=====================*/ + /* out: TRUE if should be made + younger */ + const buf_page_t* bpage) /* in: block to make younger */ +{ + return(buf_pool->freed_page_clock + >= buf_page_get_freed_page_clock(bpage) + + 1 + (buf_pool->curr_size / 4)); +} + +/************************************************************************* +Gets the current size of buffer buf_pool in bytes. */ +UNIV_INLINE +ulint +buf_pool_get_curr_size(void) +/*========================*/ + /* out: size in bytes */ +{ + return(buf_pool->curr_size * UNIV_PAGE_SIZE); +} + +/************************************************************************ +Gets the smallest oldest_modification lsn for any page in the pool. Returns +zero if all modified pages have been flushed to disk. */ +UNIV_INLINE +ib_uint64_t +buf_pool_get_oldest_modification(void) +/*==================================*/ + /* out: oldest modification in pool, + zero if none */ +{ + buf_page_t* bpage; + ib_uint64_t lsn; + +try_again: + //buf_pool_mutex_enter(); + mutex_enter(&flush_list_mutex); + + bpage = UT_LIST_GET_LAST(buf_pool->flush_list); + + if (bpage == NULL) { + lsn = 0; + } else { + ut_ad(bpage->in_flush_list); + lsn = bpage->oldest_modification; + if (lsn == 0) { + mutex_exit(&flush_list_mutex); + goto try_again; + } + } + + //buf_pool_mutex_exit(); + mutex_exit(&flush_list_mutex); + + /* The returned answer may be out of date: the flush_list can + change after the mutex has been released. */ + + return(lsn); +} + +/*********************************************************************** +Increments the buf_pool clock by one and returns its new value. Remember +that in the 32 bit version the clock wraps around at 4 billion! */ +UNIV_INLINE +ulint +buf_pool_clock_tic(void) +/*====================*/ + /* out: new clock value */ +{ + //ut_ad(buf_pool_mutex_own()); + ut_ad(mutex_own(&LRU_list_mutex)); + + buf_pool->ulint_clock++; + + return(buf_pool->ulint_clock); +} + +/************************************************************************* +Gets the state of a block. */ +UNIV_INLINE +enum buf_page_state +buf_page_get_state( +/*===============*/ + /* out: state */ + const buf_page_t* bpage) /* in: pointer to the control block */ +{ + enum buf_page_state state = (enum buf_page_state) bpage->state; + +#ifdef UNIV_DEBUG + switch (state) { + case BUF_BLOCK_ZIP_FREE: + case BUF_BLOCK_ZIP_PAGE: + case BUF_BLOCK_ZIP_DIRTY: + case BUF_BLOCK_NOT_USED: + case BUF_BLOCK_READY_FOR_USE: + case BUF_BLOCK_FILE_PAGE: + case BUF_BLOCK_MEMORY: + case BUF_BLOCK_REMOVE_HASH: + break; + default: + ut_error; + } +#endif /* UNIV_DEBUG */ + + return(state); +} +/************************************************************************* +Gets the state of a block. */ +UNIV_INLINE +enum buf_page_state +buf_block_get_state( +/*================*/ + /* out: state */ + const buf_block_t* block) /* in: pointer to the control block */ +{ + return(buf_page_get_state(&block->page)); +} +/************************************************************************* +Sets the state of a block. */ +UNIV_INLINE +void +buf_page_set_state( +/*===============*/ + buf_page_t* bpage, /* in/out: pointer to control block */ + enum buf_page_state state) /* in: state */ +{ +#ifdef UNIV_DEBUG + enum buf_page_state old_state = buf_page_get_state(bpage); + + switch (old_state) { + case BUF_BLOCK_ZIP_FREE: + ut_error; + break; + case BUF_BLOCK_ZIP_PAGE: + ut_a(state == BUF_BLOCK_ZIP_DIRTY); + break; + case BUF_BLOCK_ZIP_DIRTY: + ut_a(state == BUF_BLOCK_ZIP_PAGE); + break; + case BUF_BLOCK_NOT_USED: + ut_a(state == BUF_BLOCK_READY_FOR_USE); + break; + case BUF_BLOCK_READY_FOR_USE: + ut_a(state == BUF_BLOCK_MEMORY + || state == BUF_BLOCK_FILE_PAGE + || state == BUF_BLOCK_NOT_USED); + break; + case BUF_BLOCK_MEMORY: + ut_a(state == BUF_BLOCK_NOT_USED); + break; + case BUF_BLOCK_FILE_PAGE: + ut_a(state == BUF_BLOCK_NOT_USED + || state == BUF_BLOCK_REMOVE_HASH); + break; + case BUF_BLOCK_REMOVE_HASH: + ut_a(state == BUF_BLOCK_MEMORY); + break; + } +#endif /* UNIV_DEBUG */ + bpage->state = state; + ut_ad(buf_page_get_state(bpage) == state); +} + +/************************************************************************* +Sets the state of a block. */ +UNIV_INLINE +void +buf_block_set_state( +/*================*/ + buf_block_t* block, /* in/out: pointer to control block */ + enum buf_page_state state) /* in: state */ +{ + buf_page_set_state(&block->page, state); +} + +/************************************************************************* +Determines if a block is mapped to a tablespace. */ +UNIV_INLINE +ibool +buf_page_in_file( +/*=============*/ + /* out: TRUE if mapped */ + const buf_page_t* bpage) /* in: pointer to control block */ +{ + switch (buf_page_get_state(bpage)) { + case BUF_BLOCK_ZIP_FREE: + /* This is a free page in buf_pool->zip_free[]. + Such pages should only be accessed by the buddy allocator. */ + /* ut_error; */ /* optimistic */ + break; + case BUF_BLOCK_ZIP_PAGE: + case BUF_BLOCK_ZIP_DIRTY: + case BUF_BLOCK_FILE_PAGE: + return(TRUE); + case BUF_BLOCK_NOT_USED: + case BUF_BLOCK_READY_FOR_USE: + case BUF_BLOCK_MEMORY: + case BUF_BLOCK_REMOVE_HASH: + break; + } + + return(FALSE); +} + +/************************************************************************* +Determines if a block should be on unzip_LRU list. */ +UNIV_INLINE +ibool +buf_page_belongs_to_unzip_LRU( +/*==========================*/ + /* out: TRUE if block belongs + to unzip_LRU */ + const buf_page_t* bpage) /* in: pointer to control block */ +{ + ut_ad(buf_page_in_file(bpage)); + + return(bpage->zip.data + && buf_page_get_state(bpage) == BUF_BLOCK_FILE_PAGE); +} + +/************************************************************************* +Determine the approximate LRU list position of a block. */ +UNIV_INLINE +ulint +buf_page_get_LRU_position( +/*======================*/ + /* out: LRU list position */ + const buf_page_t* bpage) /* in: control block */ +{ + ut_ad(buf_page_in_file(bpage)); + //ut_ad(buf_pool_mutex_own()); /* This is used in optimistic */ + + return(bpage->LRU_position); +} + +/************************************************************************* +Gets the mutex of a block. */ +UNIV_INLINE +mutex_t* +buf_page_get_mutex( +/*===============*/ + /* out: pointer to mutex + protecting bpage */ + const buf_page_t* bpage) /* in: pointer to control block */ +{ + switch (buf_page_get_state(bpage)) { + case BUF_BLOCK_ZIP_FREE: + /* ut_error; */ /* optimistic */ + return(NULL); + case BUF_BLOCK_ZIP_PAGE: + case BUF_BLOCK_ZIP_DIRTY: + return(&buf_pool_zip_mutex); + default: + return(&((buf_block_t*) bpage)->mutex); + } +} + +/************************************************************************* +Get the flush type of a page. */ +UNIV_INLINE +enum buf_flush +buf_page_get_flush_type( +/*====================*/ + /* out: flush type */ + const buf_page_t* bpage) /* in: buffer page */ +{ + enum buf_flush flush_type = (enum buf_flush) bpage->flush_type; + +#ifdef UNIV_DEBUG + switch (flush_type) { + case BUF_FLUSH_LRU: + case BUF_FLUSH_SINGLE_PAGE: + case BUF_FLUSH_LIST: + return(flush_type); + case BUF_FLUSH_N_TYPES: + break; + } + ut_error; +#endif /* UNIV_DEBUG */ + return(flush_type); +} +/************************************************************************* +Set the flush type of a page. */ +UNIV_INLINE +void +buf_page_set_flush_type( +/*====================*/ + buf_page_t* bpage, /* in: buffer page */ + enum buf_flush flush_type) /* in: flush type */ +{ + bpage->flush_type = flush_type; + ut_ad(buf_page_get_flush_type(bpage) == flush_type); +} + +/************************************************************************* +Map a block to a file page. */ +UNIV_INLINE +void +buf_block_set_file_page( +/*====================*/ + buf_block_t* block, /* in/out: pointer to control block */ + ulint space, /* in: tablespace id */ + ulint page_no)/* in: page number */ +{ + buf_block_set_state(block, BUF_BLOCK_FILE_PAGE); + block->page.space = space; + block->page.offset = page_no; +} + +/************************************************************************* +Gets the io_fix state of a block. */ +UNIV_INLINE +enum buf_io_fix +buf_page_get_io_fix( +/*================*/ + /* out: io_fix state */ + const buf_page_t* bpage) /* in: pointer to the control block */ +{ + enum buf_io_fix io_fix = (enum buf_io_fix) bpage->io_fix; +#ifdef UNIV_DEBUG + switch (io_fix) { + case BUF_IO_NONE: + case BUF_IO_READ: + case BUF_IO_WRITE: + return(io_fix); + } + ut_error; +#endif /* UNIV_DEBUG */ + return(io_fix); +} + +/************************************************************************* +Gets the io_fix state of a block. */ +UNIV_INLINE +enum buf_io_fix +buf_block_get_io_fix( +/*================*/ + /* out: io_fix state */ + const buf_block_t* block) /* in: pointer to the control block */ +{ + return(buf_page_get_io_fix(&block->page)); +} + +/************************************************************************* +Sets the io_fix state of a block. */ +UNIV_INLINE +void +buf_page_set_io_fix( +/*================*/ + buf_page_t* bpage, /* in/out: control block */ + enum buf_io_fix io_fix) /* in: io_fix state */ +{ + //ut_ad(buf_pool_mutex_own()); + ut_ad(mutex_own(buf_page_get_mutex(bpage))); + + bpage->io_fix = io_fix; + ut_ad(buf_page_get_io_fix(bpage) == io_fix); +} + +/************************************************************************* +Sets the io_fix state of a block. */ +UNIV_INLINE +void +buf_block_set_io_fix( +/*=================*/ + buf_block_t* block, /* in/out: control block */ + enum buf_io_fix io_fix) /* in: io_fix state */ +{ + buf_page_set_io_fix(&block->page, io_fix); +} + +/************************************************************************ +Determine if a buffer block can be relocated in memory. The block +can be dirty, but it must not be I/O-fixed or bufferfixed. */ +UNIV_INLINE +ibool +buf_page_can_relocate( +/*==================*/ + const buf_page_t* bpage) /* control block being relocated */ +{ + //ut_ad(buf_pool_mutex_own()); + ut_ad(mutex_own(buf_page_get_mutex(bpage))); + ut_ad(buf_page_in_file(bpage)); + /* optimistic */ + //ut_ad(bpage->in_LRU_list); + + return(bpage->in_LRU_list && bpage->io_fix == BUF_IO_NONE + && bpage->buf_fix_count == 0); +} + +/************************************************************************* +Determine if a block has been flagged old. */ +UNIV_INLINE +ibool +buf_page_is_old( +/*============*/ + /* out: TRUE if old */ + const buf_page_t* bpage) /* in: control block */ +{ + ut_ad(buf_page_in_file(bpage)); + //ut_ad(buf_pool_mutex_own()); /* This is used in optimistic */ + + return(bpage->old); +} + +/************************************************************************* +Flag a block old. */ +UNIV_INLINE +void +buf_page_set_old( +/*=============*/ + buf_page_t* bpage, /* in/out: control block */ + ibool old) /* in: old */ +{ + ut_a(buf_page_in_file(bpage)); + //ut_ad(buf_pool_mutex_own()); + ut_ad(mutex_own(&LRU_list_mutex)); + ut_ad(bpage->in_LRU_list); + +#ifdef UNIV_LRU_DEBUG + if (UT_LIST_GET_PREV(LRU, bpage) && UT_LIST_GET_NEXT(LRU, bpage) + && UT_LIST_GET_PREV(LRU, bpage)->old + == UT_LIST_GET_NEXT(LRU, bpage)->old) { + ut_a(UT_LIST_GET_PREV(LRU, bpage)->old == old); + } +#endif /* UNIV_LRU_DEBUG */ + + bpage->old = old; +} + +/************************************************************************* +Determine if a block has been accessed in the buffer pool. */ +UNIV_INLINE +ibool +buf_page_is_accessed( +/*=================*/ + /* out: TRUE if accessed */ + const buf_page_t* bpage) /* in: control block */ +{ + ut_ad(buf_page_in_file(bpage)); + + return(bpage->accessed); +} + +/************************************************************************* +Flag a block accessed. */ +UNIV_INLINE +void +buf_page_set_accessed( +/*==================*/ + buf_page_t* bpage, /* in/out: control block */ + ibool accessed) /* in: accessed */ +{ + ut_a(buf_page_in_file(bpage)); + ut_ad(mutex_own(buf_page_get_mutex(bpage))); + + bpage->accessed = accessed; +} + +/************************************************************************* +Gets the buf_block_t handle of a buffered file block if an uncompressed +page frame exists, or NULL. */ +UNIV_INLINE +buf_block_t* +buf_page_get_block( +/*===============*/ + /* out: control block, or NULL */ + buf_page_t* bpage) /* in: control block, or NULL */ +{ + if (UNIV_LIKELY(bpage != NULL)) { + ut_ad(buf_page_in_file(bpage)); + + if (buf_page_get_state(bpage) == BUF_BLOCK_FILE_PAGE) { + return((buf_block_t*) bpage); + } + } + + return(NULL); +} + +#ifdef UNIV_DEBUG +/************************************************************************* +Gets a pointer to the memory frame of a block. */ +UNIV_INLINE +buf_frame_t* +buf_block_get_frame( +/*================*/ + /* out: pointer to the frame */ + const buf_block_t* block) /* in: pointer to the control block */ +{ + ut_ad(block); + + switch (buf_block_get_state(block)) { + case BUF_BLOCK_ZIP_FREE: + case BUF_BLOCK_ZIP_PAGE: + case BUF_BLOCK_ZIP_DIRTY: + case BUF_BLOCK_NOT_USED: + ut_error; + break; + case BUF_BLOCK_FILE_PAGE: + ut_a(block->page.buf_fix_count > 0); + /* fall through */ + case BUF_BLOCK_READY_FOR_USE: + case BUF_BLOCK_MEMORY: + case BUF_BLOCK_REMOVE_HASH: + goto ok; + } + ut_error; +ok: + return((buf_frame_t*) block->frame); +} +#endif /* UNIV_DEBUG */ + +/************************************************************************* +Gets the space id of a block. */ +UNIV_INLINE +ulint +buf_page_get_space( +/*===============*/ + /* out: space id */ + const buf_page_t* bpage) /* in: pointer to the control block */ +{ + ut_ad(bpage); + ut_a(buf_page_in_file(bpage)); + + return(bpage->space); +} + +/************************************************************************* +Gets the space id of a block. */ +UNIV_INLINE +ulint +buf_block_get_space( +/*================*/ + /* out: space id */ + const buf_block_t* block) /* in: pointer to the control block */ +{ + ut_ad(block); + ut_a(buf_block_get_state(block) == BUF_BLOCK_FILE_PAGE); + + return(block->page.space); +} + +/************************************************************************* +Gets the page number of a block. */ +UNIV_INLINE +ulint +buf_page_get_page_no( +/*=================*/ + /* out: page number */ + const buf_page_t* bpage) /* in: pointer to the control block */ +{ + ut_ad(bpage); + ut_a(buf_page_in_file(bpage)); + + return(bpage->offset); +} + +/************************************************************************* +Gets the page number of a block. */ +UNIV_INLINE +ulint +buf_block_get_page_no( +/*==================*/ + /* out: page number */ + const buf_block_t* block) /* in: pointer to the control block */ +{ + ut_ad(block); + ut_a(buf_block_get_state(block) == BUF_BLOCK_FILE_PAGE); + + return(block->page.offset); +} + +/************************************************************************* +Gets the compressed page size of a block. */ +UNIV_INLINE +ulint +buf_page_get_zip_size( +/*==================*/ + /* out: compressed page size, or 0 */ + const buf_page_t* bpage) /* in: pointer to the control block */ +{ + return(bpage->zip.ssize ? 512 << bpage->zip.ssize : 0); +} + +/************************************************************************* +Gets the compressed page size of a block. */ +UNIV_INLINE +ulint +buf_block_get_zip_size( +/*===================*/ + /* out: compressed page size, or 0 */ + const buf_block_t* block) /* in: pointer to the control block */ +{ + return(block->page.zip.ssize ? 512 << block->page.zip.ssize : 0); +} + +#if defined UNIV_DEBUG || defined UNIV_ZIP_DEBUG +/************************************************************************* +Gets the compressed page descriptor corresponding to an uncompressed page +if applicable. */ +UNIV_INLINE +const page_zip_des_t* +buf_frame_get_page_zip( +/*===================*/ + /* out: compressed page descriptor, or NULL */ + const byte* ptr) /* in: pointer to the page */ +{ + return(buf_block_get_page_zip(buf_block_align(ptr))); +} +#endif /* UNIV_DEBUG || UNIV_ZIP_DEBUG */ + +/************************************************************************** +Gets the space id, page offset, and byte offset within page of a +pointer pointing to a buffer frame containing a file page. */ +UNIV_INLINE +void +buf_ptr_get_fsp_addr( +/*=================*/ + const void* ptr, /* in: pointer to a buffer frame */ + ulint* space, /* out: space id */ + fil_addr_t* addr) /* out: page offset and byte offset */ +{ + const page_t* page = (const page_t*) ut_align_down(ptr, + UNIV_PAGE_SIZE); + + *space = mach_read_from_4(page + FIL_PAGE_ARCH_LOG_NO_OR_SPACE_ID); + addr->page = mach_read_from_4(page + FIL_PAGE_OFFSET); + addr->boffset = ut_align_offset(ptr, UNIV_PAGE_SIZE); +} + +/************************************************************************** +Gets the hash value of the page the pointer is pointing to. This can be used +in searches in the lock hash table. */ +UNIV_INLINE +ulint +buf_block_get_lock_hash_val( +/*========================*/ + /* out: lock hash value */ + const buf_block_t* block) /* in: block */ +{ + return(block->lock_hash_val); +} + +/************************************************************************ +Allocates a buffer block. */ +UNIV_INLINE +buf_block_t* +buf_block_alloc( +/*============*/ + /* out, own: the allocated block, + in state BUF_BLOCK_MEMORY */ + ulint zip_size) /* in: compressed page size in bytes, + or 0 if uncompressed tablespace */ +{ + buf_block_t* block; + + block = buf_LRU_get_free_block(zip_size); + + buf_block_set_state(block, BUF_BLOCK_MEMORY); + + return(block); +} + +/************************************************************************ +Frees a buffer block which does not contain a file page. */ +UNIV_INLINE +void +buf_block_free( +/*===========*/ + buf_block_t* block) /* in, own: block to be freed */ +{ + //buf_pool_mutex_enter(); + + mutex_enter(&block->mutex); + + ut_a(buf_block_get_state(block) != BUF_BLOCK_FILE_PAGE); + + buf_LRU_block_free_non_file_page(block, FALSE); + + mutex_exit(&block->mutex); + + //buf_pool_mutex_exit(); +} + +/************************************************************************* +Copies contents of a buffer frame to a given buffer. */ +UNIV_INLINE +byte* +buf_frame_copy( +/*===========*/ + /* out: buf */ + byte* buf, /* in: buffer to copy to */ + const buf_frame_t* frame) /* in: buffer frame */ +{ + ut_ad(buf && frame); + + ut_memcpy(buf, frame, UNIV_PAGE_SIZE); + + return(buf); +} + +/************************************************************************ +Calculates a folded value of a file page address to use in the page hash +table. */ +UNIV_INLINE +ulint +buf_page_address_fold( +/*==================*/ + /* out: the folded value */ + ulint space, /* in: space id */ + ulint offset) /* in: offset of the page within space */ +{ + return((space << 20) + space + offset); +} + +/************************************************************************ +This function is used to get info if there is an io operation +going on on a buffer page. */ +UNIV_INLINE +ibool +buf_page_io_query( +/*==============*/ + /* out: TRUE if io going on */ + buf_page_t* bpage) /* in: buf_pool block, must be bufferfixed */ +{ + ibool io_fixed; + mutex_t* block_mutex = buf_page_get_mutex(bpage); + + //buf_pool_mutex_enter(); +retry_lock: + mutex_enter(block_mutex); + if (block_mutex != buf_page_get_mutex(bpage)) { + mutex_exit(block_mutex); + block_mutex = buf_page_get_mutex(bpage); + goto retry_lock; + } + + ut_ad(buf_page_in_file(bpage)); + ut_ad(bpage->buf_fix_count > 0); + + io_fixed = buf_page_get_io_fix(bpage) != BUF_IO_NONE; + //buf_pool_mutex_exit(); + mutex_exit(block_mutex); + + return(io_fixed); +} + +/************************************************************************ +Gets the youngest modification log sequence number for a frame. +Returns zero if not file page or no modification occurred yet. */ +UNIV_INLINE +ib_uint64_t +buf_page_get_newest_modification( +/*=============================*/ + /* out: newest modification to page */ + const buf_page_t* bpage) /* in: block containing the + page frame */ +{ + ib_uint64_t lsn; + mutex_t* block_mutex = buf_page_get_mutex(bpage); + +retry_lock: + mutex_enter(block_mutex); + if (block_mutex != buf_page_get_mutex(bpage)) { + mutex_exit(block_mutex); + block_mutex = buf_page_get_mutex(bpage); + goto retry_lock; + } + + if (buf_page_in_file(bpage)) { + lsn = bpage->newest_modification; + } else { + lsn = 0; + } + + mutex_exit(block_mutex); + + return(lsn); +} + +/************************************************************************ +Increments the modify clock of a frame by 1. The caller must (1) own the +buf_pool mutex and block bufferfix count has to be zero, (2) or own an x-lock +on the block. */ +UNIV_INLINE +void +buf_block_modify_clock_inc( +/*=======================*/ + buf_block_t* block) /* in: block */ +{ +#ifdef UNIV_SYNC_DEBUG + ut_ad((mutex_own(&LRU_list_mutex) + && (block->page.buf_fix_count == 0)) + || rw_lock_own(&(block->lock), RW_LOCK_EXCLUSIVE)); +#endif /* UNIV_SYNC_DEBUG */ + + block->modify_clock++; +} + +/************************************************************************ +Returns the value of the modify clock. The caller must have an s-lock +or x-lock on the block. */ +UNIV_INLINE +ib_uint64_t +buf_block_get_modify_clock( +/*=======================*/ + /* out: value */ + buf_block_t* block) /* in: block */ +{ +#ifdef UNIV_SYNC_DEBUG + ut_ad(rw_lock_own(&(block->lock), RW_LOCK_SHARED) + || rw_lock_own(&(block->lock), RW_LOCK_EXCLUSIVE)); +#endif /* UNIV_SYNC_DEBUG */ + + return(block->modify_clock); +} + +/*********************************************************************** +Increments the bufferfix count. */ +UNIV_INLINE +void +buf_block_buf_fix_inc_func( +/*=======================*/ +#ifdef UNIV_SYNC_DEBUG + const char* file, /* in: file name */ + ulint line, /* in: line */ +#endif /* UNIV_SYNC_DEBUG */ + buf_block_t* block) /* in: block to bufferfix */ +{ +#ifdef UNIV_SYNC_DEBUG + ibool ret; + + ret = rw_lock_s_lock_nowait(&(block->debug_latch), file, line); + ut_a(ret); +#endif /* UNIV_SYNC_DEBUG */ + ut_ad(mutex_own(&block->mutex)); + + block->page.buf_fix_count++; +} +#ifdef UNIV_SYNC_DEBUG +# define buf_block_buf_fix_inc(b,f,l) buf_block_buf_fix_inc_func(f,l,b) +#else /* UNIV_SYNC_DEBUG */ +# define buf_block_buf_fix_inc(b,f,l) buf_block_buf_fix_inc_func(b) +#endif /* UNIV_SYNC_DEBUG */ + +/*********************************************************************** +Decrements the bufferfix count. */ +UNIV_INLINE +void +buf_block_buf_fix_dec( +/*==================*/ + buf_block_t* block) /* in: block to bufferunfix */ +{ + ut_ad(mutex_own(&block->mutex)); + + block->page.buf_fix_count--; +#ifdef UNIV_SYNC_DEBUG + rw_lock_s_unlock(&block->debug_latch); +#endif +} + +/********************************************************************** +Returns the control block of a file page, NULL if not found. */ +UNIV_INLINE +buf_page_t* +buf_page_hash_get( +/*==============*/ + /* out: block, NULL if not found */ + ulint space, /* in: space id */ + ulint offset) /* in: offset of the page within space */ +{ + buf_page_t* bpage; + ulint fold; + + ut_ad(buf_pool); + //ut_ad(buf_pool_mutex_own()); +#ifdef UNIV_SYNC_DEBUG + ut_ad(rw_lock_own(&page_hash_latch, RW_LOCK_EX) + || rw_lock_own(&page_hash_latch, RW_LOCK_SHARED)); +#endif + + /* Look for the page in the hash table */ + + fold = buf_page_address_fold(space, offset); + + HASH_SEARCH(hash, buf_pool->page_hash, fold, buf_page_t*, bpage, + ut_ad(bpage->in_page_hash && !bpage->in_zip_hash + && buf_page_in_file(bpage)), + bpage->space == space && bpage->offset == offset); + if (bpage) { + ut_a(buf_page_in_file(bpage)); + ut_ad(bpage->in_page_hash); + ut_ad(!bpage->in_zip_hash); + UNIV_MEM_ASSERT_RW(bpage, sizeof *bpage); + } + + return(bpage); +} + +/********************************************************************** +Returns the control block of a file page, NULL if not found +or an uncompressed page frame does not exist. */ +UNIV_INLINE +buf_block_t* +buf_block_hash_get( +/*===============*/ + /* out: block, NULL if not found */ + ulint space, /* in: space id */ + ulint offset) /* in: offset of the page within space */ +{ + return(buf_page_get_block(buf_page_hash_get(space, offset))); +} + +/************************************************************************ +Returns TRUE if the page can be found in the buffer pool hash table. NOTE +that it is possible that the page is not yet read from disk, though. */ +UNIV_INLINE +ibool +buf_page_peek( +/*==========*/ + /* out: TRUE if found from page hash table, + NOTE that the page is not necessarily yet read + from disk! */ + ulint space, /* in: space id */ + ulint offset) /* in: page number */ +{ + const buf_page_t* bpage; + + //buf_pool_mutex_enter(); + rw_lock_s_lock(&page_hash_latch); + + bpage = buf_page_hash_get(space, offset); + + //buf_pool_mutex_exit(); + rw_lock_s_unlock(&page_hash_latch); + + return(bpage != NULL); +} + +/************************************************************************ +Releases a compressed-only page acquired with buf_page_get_zip(). */ +UNIV_INLINE +void +buf_page_release_zip( +/*=================*/ + buf_page_t* bpage) /* in: buffer block */ +{ + buf_block_t* block; + + ut_ad(bpage); + ut_a(bpage->buf_fix_count > 0); + + switch (buf_page_get_state(bpage)) { + case BUF_BLOCK_ZIP_PAGE: + case BUF_BLOCK_ZIP_DIRTY: + mutex_enter(&buf_pool_zip_mutex); + bpage->buf_fix_count--; + mutex_exit(&buf_pool_zip_mutex); + return; + case BUF_BLOCK_FILE_PAGE: + block = (buf_block_t*) bpage; + mutex_enter(&block->mutex); +#ifdef UNIV_SYNC_DEBUG + rw_lock_s_unlock(&block->debug_latch); +#endif + bpage->buf_fix_count--; + mutex_exit(&block->mutex); + return; + case BUF_BLOCK_ZIP_FREE: + case BUF_BLOCK_NOT_USED: + case BUF_BLOCK_READY_FOR_USE: + case BUF_BLOCK_MEMORY: + case BUF_BLOCK_REMOVE_HASH: + break; + } + + ut_error; +} + +/************************************************************************ +Decrements the bufferfix count of a buffer control block and releases +a latch, if specified. */ +UNIV_INLINE +void +buf_page_release( +/*=============*/ + buf_block_t* block, /* in: buffer block */ + ulint rw_latch, /* in: RW_S_LATCH, RW_X_LATCH, + RW_NO_LATCH */ + mtr_t* mtr) /* in: mtr */ +{ + ut_ad(block); + + ut_a(buf_block_get_state(block) == BUF_BLOCK_FILE_PAGE); + ut_a(block->page.buf_fix_count > 0); + + /* buf_flush_note_modification() should be called before this function. */ +/* + if (rw_latch == RW_X_LATCH && mtr->modifications) { + buf_pool_mutex_enter(); + buf_flush_note_modification(block, mtr); + buf_pool_mutex_exit(); + } +*/ + + mutex_enter(&block->mutex); + +#ifdef UNIV_SYNC_DEBUG + rw_lock_s_unlock(&(block->debug_latch)); +#endif + block->page.buf_fix_count--; + + /* Dirty blocks should be in the flush list. */ + ut_ad(!block->page.oldest_modification + || block->page.in_flush_list); + + mutex_exit(&block->mutex); + + if (rw_latch == RW_S_LATCH) { + rw_lock_s_unlock(&(block->lock)); + } else if (rw_latch == RW_X_LATCH) { + rw_lock_x_unlock(&(block->lock)); + } +} + +#ifdef UNIV_SYNC_DEBUG +/************************************************************************* +Adds latch level info for the rw-lock protecting the buffer frame. This +should be called in the debug version after a successful latching of a +page if we know the latching order level of the acquired latch. */ +UNIV_INLINE +void +buf_block_dbg_add_level( +/*====================*/ + buf_block_t* block, /* in: buffer page + where we have acquired latch */ + ulint level) /* in: latching order level */ +{ + sync_thread_add_level(&block->lock, level); +} +#endif /* UNIV_SYNC_DEBUG */ diff --git a/storage/xtradb/include/buf0flu.h b/storage/xtradb/include/buf0flu.h new file mode 100644 index 00000000000..11a37351479 --- /dev/null +++ b/storage/xtradb/include/buf0flu.h @@ -0,0 +1,151 @@ +/***************************************************************************** + +Copyright (c) 1995, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/****************************************************** +The database buffer pool flush algorithm + +Created 11/5/1995 Heikki Tuuri +*******************************************************/ + +#ifndef buf0flu_h +#define buf0flu_h + +#include "univ.i" +#include "buf0types.h" +#include "ut0byte.h" +#include "mtr0types.h" + +/************************************************************************ +Remove a block from the flush list of modified blocks. */ +UNIV_INTERN +void +buf_flush_remove( +/*=============*/ + buf_page_t* bpage); /* in: pointer to the block in question */ +/************************************************************************ +Updates the flush system data structures when a write is completed. */ +UNIV_INTERN +void +buf_flush_write_complete( +/*=====================*/ + buf_page_t* bpage); /* in: pointer to the block in question */ +/************************************************************************* +Flushes pages from the end of the LRU list if there is too small +a margin of replaceable pages there. */ +UNIV_INTERN +void +buf_flush_free_margin( +/*=======================*/ + ibool wait); +/************************************************************************ +Initializes a page for writing to the tablespace. */ +UNIV_INTERN +void +buf_flush_init_for_writing( +/*=======================*/ + byte* page, /* in/out: page */ + void* page_zip_, /* in/out: compressed page, or NULL */ + ib_uint64_t newest_lsn); /* in: newest modification lsn + to the page */ +/*********************************************************************** +This utility flushes dirty blocks from the end of the LRU list or flush_list. +NOTE 1: in the case of an LRU flush the calling thread may own latches to +pages: to avoid deadlocks, this function must be written so that it cannot +end up waiting for these latches! NOTE 2: in the case of a flush list flush, +the calling thread is not allowed to own any latches on pages! */ +UNIV_INTERN +ulint +buf_flush_batch( +/*============*/ + /* out: number of blocks for which the + write request was queued; + ULINT_UNDEFINED if there was a flush + of the same type already running */ + enum buf_flush flush_type, /* in: BUF_FLUSH_LRU or + BUF_FLUSH_LIST; if BUF_FLUSH_LIST, + then the caller must not own any + latches on pages */ + ulint min_n, /* in: wished minimum mumber of blocks + flushed (it is not guaranteed that the + actual number is that big, though) */ + ib_uint64_t lsn_limit); /* in the case BUF_FLUSH_LIST all + blocks whose oldest_modification is + smaller than this should be flushed + (if their number does not exceed + min_n), otherwise ignored */ +/********************************************************************** +Waits until a flush batch of the given type ends */ +UNIV_INTERN +void +buf_flush_wait_batch_end( +/*=====================*/ + enum buf_flush type); /* in: BUF_FLUSH_LRU or BUF_FLUSH_LIST */ +/************************************************************************ +This function should be called at a mini-transaction commit, if a page was +modified in it. Puts the block to the list of modified blocks, if it not +already in it. */ +UNIV_INLINE +void +buf_flush_note_modification( +/*========================*/ + buf_block_t* block, /* in: block which is modified */ + mtr_t* mtr); /* in: mtr */ +/************************************************************************ +This function should be called when recovery has modified a buffer page. */ +UNIV_INLINE +void +buf_flush_recv_note_modification( +/*=============================*/ + buf_block_t* block, /* in: block which is modified */ + ib_uint64_t start_lsn, /* in: start lsn of the first mtr in a + set of mtr's */ + ib_uint64_t end_lsn); /* in: end lsn of the last mtr in the + set of mtr's */ +/************************************************************************ +Returns TRUE if the file page block is immediately suitable for replacement, +i.e., transition FILE_PAGE => NOT_USED allowed. */ +UNIV_INTERN +ibool +buf_flush_ready_for_replace( +/*========================*/ + /* out: TRUE if can replace immediately */ + buf_page_t* bpage); /* in: buffer control block, must be + buf_page_in_file(bpage) and in the LRU list */ +#if defined UNIV_DEBUG || defined UNIV_BUF_DEBUG +/********************************************************************** +Validates the flush list. */ +UNIV_INTERN +ibool +buf_flush_validate(void); +/*====================*/ + /* out: TRUE if ok */ +#endif /* UNIV_DEBUG || UNIV_BUF_DEBUG */ + +/* When buf_flush_free_margin is called, it tries to make this many blocks +available to replacement in the free list and at the end of the LRU list (to +make sure that a read-ahead batch can be read efficiently in a single +sweep). */ + +#define BUF_FLUSH_FREE_BLOCK_MARGIN (5 + BUF_READ_AHEAD_AREA) +#define BUF_FLUSH_EXTRA_MARGIN (BUF_FLUSH_FREE_BLOCK_MARGIN / 4 + 100) + +#ifndef UNIV_NONINL +#include "buf0flu.ic" +#endif + +#endif diff --git a/storage/xtradb/include/buf0flu.ic b/storage/xtradb/include/buf0flu.ic new file mode 100644 index 00000000000..c466caae347 --- /dev/null +++ b/storage/xtradb/include/buf0flu.ic @@ -0,0 +1,152 @@ +/***************************************************************************** + +Copyright (c) 1995, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/****************************************************** +The database buffer pool flush algorithm + +Created 11/5/1995 Heikki Tuuri +*******************************************************/ + +#include "buf0buf.h" +#include "mtr0mtr.h" + +/************************************************************************ +Inserts a modified block into the flush list. */ +UNIV_INTERN +void +buf_flush_insert_into_flush_list( +/*=============================*/ + buf_block_t* block); /* in/out: block which is modified */ +/************************************************************************ +Inserts a modified block into the flush list in the right sorted position. +This function is used by recovery, because there the modifications do not +necessarily come in the order of lsn's. */ +UNIV_INTERN +void +buf_flush_insert_sorted_into_flush_list( +/*====================================*/ + buf_block_t* block); /* in/out: block which is modified */ + +/************************************************************************ +This function should be called at a mini-transaction commit, if a page was +modified in it. Puts the block to the list of modified blocks, if it is not +already in it. */ +UNIV_INLINE +void +buf_flush_note_modification( +/*========================*/ + buf_block_t* block, /* in: block which is modified */ + mtr_t* mtr) /* in: mtr */ +{ + ibool use_LRU_mutex = FALSE; + + if (UT_LIST_GET_LEN(buf_pool->unzip_LRU)) + use_LRU_mutex = TRUE; + + if (use_LRU_mutex) + mutex_enter(&LRU_list_mutex); + + mutex_enter(&block->mutex); + + ut_ad(block); + ut_ad(buf_block_get_state(block) == BUF_BLOCK_FILE_PAGE); + ut_ad(block->page.buf_fix_count > 0); +#ifdef UNIV_SYNC_DEBUG + ut_ad(rw_lock_own(&(block->lock), RW_LOCK_EX)); +#endif /* UNIV_SYNC_DEBUG */ + //ut_ad(buf_pool_mutex_own()); + + ut_ad(mtr->start_lsn != 0); + ut_ad(mtr->modifications); + ut_ad(block->page.newest_modification <= mtr->end_lsn); + + block->page.newest_modification = mtr->end_lsn; + + if (!block->page.oldest_modification) { + mutex_enter(&flush_list_mutex); + + block->page.oldest_modification = mtr->start_lsn; + ut_ad(block->page.oldest_modification != 0); + + buf_flush_insert_into_flush_list(block); + mutex_exit(&flush_list_mutex); + } else { + ut_ad(block->page.oldest_modification <= mtr->start_lsn); + } + + mutex_exit(&block->mutex); + + ++srv_buf_pool_write_requests; + + if (use_LRU_mutex) + mutex_exit(&LRU_list_mutex); +} + +/************************************************************************ +This function should be called when recovery has modified a buffer page. */ +UNIV_INLINE +void +buf_flush_recv_note_modification( +/*=============================*/ + buf_block_t* block, /* in: block which is modified */ + ib_uint64_t start_lsn, /* in: start lsn of the first mtr in a + set of mtr's */ + ib_uint64_t end_lsn) /* in: end lsn of the last mtr in the + set of mtr's */ +{ + ibool use_LRU_mutex = FALSE; + + if(UT_LIST_GET_LEN(buf_pool->unzip_LRU)) + use_LRU_mutex = TRUE; + + if (use_LRU_mutex) + mutex_enter(&LRU_list_mutex); + + mutex_enter(&(block->mutex)); + + ut_ad(block); + ut_ad(buf_block_get_state(block) == BUF_BLOCK_FILE_PAGE); + ut_ad(block->page.buf_fix_count > 0); +#ifdef UNIV_SYNC_DEBUG + ut_ad(rw_lock_own(&(block->lock), RW_LOCK_EX)); +#endif /* UNIV_SYNC_DEBUG */ + + //buf_pool_mutex_enter(); + + ut_ad(block->page.newest_modification <= end_lsn); + + block->page.newest_modification = end_lsn; + + if (!block->page.oldest_modification) { + mutex_enter(&flush_list_mutex); + + block->page.oldest_modification = start_lsn; + + ut_ad(block->page.oldest_modification != 0); + + buf_flush_insert_sorted_into_flush_list(block); + mutex_exit(&flush_list_mutex); + } else { + ut_ad(block->page.oldest_modification <= start_lsn); + } + + //buf_pool_mutex_exit(); + if (use_LRU_mutex) + mutex_exit(&LRU_list_mutex); + mutex_exit(&(block->mutex)); +} diff --git a/storage/xtradb/include/buf0lru.h b/storage/xtradb/include/buf0lru.h new file mode 100644 index 00000000000..3fd534a215d --- /dev/null +++ b/storage/xtradb/include/buf0lru.h @@ -0,0 +1,267 @@ +/***************************************************************************** + +Copyright (c) 1995, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/****************************************************** +The database buffer pool LRU replacement algorithm + +Created 11/5/1995 Heikki Tuuri +*******************************************************/ + +#ifndef buf0lru_h +#define buf0lru_h + +#include "univ.i" +#include "ut0byte.h" +#include "buf0types.h" + +/** The return type of buf_LRU_free_block() */ +enum buf_lru_free_block_status { + /** freed */ + BUF_LRU_FREED = 0, + /** not freed because the caller asked to remove the + uncompressed frame but the control block cannot be + relocated */ + BUF_LRU_CANNOT_RELOCATE, + /** not freed because of some other reason */ + BUF_LRU_NOT_FREED +}; + +/********************************************************************** +Tries to remove LRU flushed blocks from the end of the LRU list and put them +to the free list. This is beneficial for the efficiency of the insert buffer +operation, as flushed pages from non-unique non-clustered indexes are here +taken out of the buffer pool, and their inserts redirected to the insert +buffer. Otherwise, the flushed blocks could get modified again before read +operations need new buffer blocks, and the i/o work done in flushing would be +wasted. */ +UNIV_INTERN +void +buf_LRU_try_free_flushed_blocks(void); +/*==================================*/ +/********************************************************************** +Returns TRUE if less than 25 % of the buffer pool is available. This can be +used in heuristics to prevent huge transactions eating up the whole buffer +pool for their locks. */ +UNIV_INTERN +ibool +buf_LRU_buf_pool_running_out(void); +/*==============================*/ + /* out: TRUE if less than 25 % of buffer pool + left */ + +/*####################################################################### +These are low-level functions +#########################################################################*/ + +/* Minimum LRU list length for which the LRU_old pointer is defined */ + +#define BUF_LRU_OLD_MIN_LEN 80 + +#define BUF_LRU_FREE_SEARCH_LEN (5 + 2 * BUF_READ_AHEAD_AREA) + +/********************************************************************** +Invalidates all pages belonging to a given tablespace when we are deleting +the data file(s) of that tablespace. A PROBLEM: if readahead is being started, +what guarantees that it will not try to read in pages after this operation has +completed? */ +UNIV_INTERN +void +buf_LRU_invalidate_tablespace( +/*==========================*/ + ulint id); /* in: space id */ +/********************************************************************** +Gets the minimum LRU_position field for the blocks in an initial segment +(determined by BUF_LRU_INITIAL_RATIO) of the LRU list. The limit is not +guaranteed to be precise, because the ulint_clock may wrap around. */ +UNIV_INTERN +ulint +buf_LRU_get_recent_limit(void); +/*==========================*/ + /* out: the limit; zero if could not determine it */ +/************************************************************************ +Insert a compressed block into buf_pool->zip_clean in the LRU order. */ +UNIV_INTERN +void +buf_LRU_insert_zip_clean( +/*=====================*/ + buf_page_t* bpage); /* in: pointer to the block in question */ + +/********************************************************************** +Try to free a block. If bpage is a descriptor of a compressed-only +page, the descriptor object will be freed as well. + +NOTE: If this function returns BUF_LRU_FREED, it will not temporarily +release buf_pool_mutex. Furthermore, the page frame will no longer be +accessible via bpage. + +The caller must hold buf_pool_mutex and buf_page_get_mutex(bpage) and +release these two mutexes after the call. No other +buf_page_get_mutex() may be held when calling this function. */ +UNIV_INTERN +enum buf_lru_free_block_status +buf_LRU_free_block( +/*===============*/ + /* out: BUF_LRU_FREED if freed, + BUF_LRU_CANNOT_RELOCATE or + BUF_LRU_NOT_FREED otherwise. */ + buf_page_t* bpage, /* in: block to be freed */ + ibool zip, /* in: TRUE if should remove also the + compressed page of an uncompressed page */ + ibool* buf_pool_mutex_released, + /* in: pointer to a variable that will + be assigned TRUE if buf_pool_mutex + was temporarily released, or NULL */ + ibool have_LRU_mutex); +/********************************************************************** +Try to free a replaceable block. */ +UNIV_INTERN +ibool +buf_LRU_search_and_free_block( +/*==========================*/ + /* out: TRUE if found and freed */ + ulint n_iterations); /* in: how many times this has been called + repeatedly without result: a high value means + that we should search farther; if + n_iterations < 10, then we search + n_iterations / 10 * buf_pool->curr_size + pages from the end of the LRU list; if + n_iterations < 5, then we will also search + n_iterations / 5 of the unzip_LRU list. */ +/********************************************************************** +Returns a free block from the buf_pool. The block is taken off the +free list. If it is empty, returns NULL. */ +UNIV_INTERN +buf_block_t* +buf_LRU_get_free_only(void); +/*=======================*/ + /* out: a free control block, or NULL + if the buf_block->free list is empty */ +/********************************************************************** +Returns a free block from the buf_pool. The block is taken off the +free list. If it is empty, blocks are moved from the end of the +LRU list to the free list. */ +UNIV_INTERN +buf_block_t* +buf_LRU_get_free_block( +/*===================*/ + /* out: the free control block, + in state BUF_BLOCK_READY_FOR_USE */ + ulint zip_size); /* in: compressed page size in bytes, + or 0 if uncompressed tablespace */ + +/********************************************************************** +Puts a block back to the free list. */ +UNIV_INTERN +void +buf_LRU_block_free_non_file_page( +/*=============================*/ + buf_block_t* block, /* in: block, must not contain a file page */ + ibool have_page_hash_mutex); +/********************************************************************** +Adds a block to the LRU list. */ +UNIV_INTERN +void +buf_LRU_add_block( +/*==============*/ + buf_page_t* bpage, /* in: control block */ + ibool old); /* in: TRUE if should be put to the old + blocks in the LRU list, else put to the + start; if the LRU list is very short, added to + the start regardless of this parameter */ +/********************************************************************** +Adds a block to the LRU list of decompressed zip pages. */ +UNIV_INTERN +void +buf_unzip_LRU_add_block( +/*====================*/ + buf_block_t* block, /* in: control block */ + ibool old); /* in: TRUE if should be put to the end + of the list, else put to the start */ +/********************************************************************** +Moves a block to the start of the LRU list. */ +UNIV_INTERN +void +buf_LRU_make_block_young( +/*=====================*/ + buf_page_t* bpage); /* in: control block */ +/********************************************************************** +Moves a block to the end of the LRU list. */ +UNIV_INTERN +void +buf_LRU_make_block_old( +/*===================*/ + buf_page_t* bpage); /* in: control block */ +/************************************************************************ +Update the historical stats that we are collecting for LRU eviction +policy at the end of each interval. */ +UNIV_INTERN +void +buf_LRU_stat_update(void); +/*=====================*/ + +#if defined UNIV_DEBUG || defined UNIV_BUF_DEBUG +/************************************************************************** +Validates the LRU list. */ +UNIV_INTERN +ibool +buf_LRU_validate(void); +/*==================*/ +#endif /* UNIV_DEBUG || UNIV_BUF_DEBUG */ +#if defined UNIV_DEBUG_PRINT || defined UNIV_DEBUG || defined UNIV_BUF_DEBUG +/************************************************************************** +Prints the LRU list. */ +UNIV_INTERN +void +buf_LRU_print(void); +/*===============*/ +#endif /* UNIV_DEBUG_PRINT || UNIV_DEBUG || UNIV_BUF_DEBUG */ + +/********************************************************************** +These statistics are not 'of' LRU but 'for' LRU. We keep count of I/O +and page_zip_decompress() operations. Based on the statistics we decide +if we want to evict from buf_pool->unzip_LRU or buf_pool->LRU. */ + +/** Statistics for selecting the LRU list for eviction. */ +struct buf_LRU_stat_struct +{ + ulint io; /**< Counter of buffer pool I/O operations. */ + ulint unzip; /**< Counter of page_zip_decompress operations. */ +}; + +typedef struct buf_LRU_stat_struct buf_LRU_stat_t; + +/** Current operation counters. Not protected by any mutex. +Cleared by buf_LRU_stat_update(). */ +extern buf_LRU_stat_t buf_LRU_stat_cur; + +/** Running sum of past values of buf_LRU_stat_cur. +Updated by buf_LRU_stat_update(). Protected by buf_pool_mutex. */ +extern buf_LRU_stat_t buf_LRU_stat_sum; + +/************************************************************************ +Increments the I/O counter in buf_LRU_stat_cur. */ +#define buf_LRU_stat_inc_io() buf_LRU_stat_cur.io++ +/************************************************************************ +Increments the page_zip_decompress() counter in buf_LRU_stat_cur. */ +#define buf_LRU_stat_inc_unzip() buf_LRU_stat_cur.unzip++ + +#ifndef UNIV_NONINL +#include "buf0lru.ic" +#endif + +#endif diff --git a/storage/xtradb/include/buf0lru.ic b/storage/xtradb/include/buf0lru.ic new file mode 100644 index 00000000000..f4c40e0b606 --- /dev/null +++ b/storage/xtradb/include/buf0lru.ic @@ -0,0 +1,24 @@ +/***************************************************************************** + +Copyright (c) 1995, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/****************************************************** +The database buffer replacement algorithm + +Created 11/5/1995 Heikki Tuuri +*******************************************************/ + diff --git a/storage/xtradb/include/buf0rea.h b/storage/xtradb/include/buf0rea.h new file mode 100644 index 00000000000..6d138a3a02b --- /dev/null +++ b/storage/xtradb/include/buf0rea.h @@ -0,0 +1,136 @@ +/***************************************************************************** + +Copyright (c) 1995, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/****************************************************** +The database buffer read + +Created 11/5/1995 Heikki Tuuri +*******************************************************/ + +#ifndef buf0rea_h +#define buf0rea_h + +#include "univ.i" +#include "buf0types.h" + +/************************************************************************ +High-level function which reads a page asynchronously from a file to the +buffer buf_pool if it is not already there. Sets the io_fix flag and sets +an exclusive lock on the buffer frame. The flag is cleared and the x-lock +released by the i/o-handler thread. Does a random read-ahead if it seems +sensible. */ +UNIV_INTERN +ulint +buf_read_page( +/*==========*/ + /* out: number of page read requests issued: this can + be > 1 if read-ahead occurred */ + ulint space, /* in: space id */ + ulint zip_size,/* in: compressed page size in bytes, or 0 */ + ulint offset);/* in: page number */ +/************************************************************************ +Applies linear read-ahead if in the buf_pool the page is a border page of +a linear read-ahead area and all the pages in the area have been accessed. +Does not read any page if the read-ahead mechanism is not activated. Note +that the the algorithm looks at the 'natural' adjacent successor and +predecessor of the page, which on the leaf level of a B-tree are the next +and previous page in the chain of leaves. To know these, the page specified +in (space, offset) must already be present in the buf_pool. Thus, the +natural way to use this function is to call it when a page in the buf_pool +is accessed the first time, calling this function just after it has been +bufferfixed. +NOTE 1: as this function looks at the natural predecessor and successor +fields on the page, what happens, if these are not initialized to any +sensible value? No problem, before applying read-ahead we check that the +area to read is within the span of the space, if not, read-ahead is not +applied. An uninitialized value may result in a useless read operation, but +only very improbably. +NOTE 2: the calling thread may own latches on pages: to avoid deadlocks this +function must be written such that it cannot end up waiting for these +latches! +NOTE 3: the calling thread must want access to the page given: this rule is +set to prevent unintended read-aheads performed by ibuf routines, a situation +which could result in a deadlock if the OS does not support asynchronous io. */ +UNIV_INTERN +ulint +buf_read_ahead_linear( +/*==================*/ + /* out: number of page read requests issued */ + ulint space, /* in: space id */ + ulint zip_size,/* in: compressed page size in bytes, or 0 */ + ulint offset);/* in: page number of a page; NOTE: the current thread + must want access to this page (see NOTE 3 above) */ +/************************************************************************ +Issues read requests for pages which the ibuf module wants to read in, in +order to contract the insert buffer tree. Technically, this function is like +a read-ahead function. */ +UNIV_INTERN +void +buf_read_ibuf_merge_pages( +/*======================*/ + ibool sync, /* in: TRUE if the caller + wants this function to wait + for the highest address page + to get read in, before this + function returns */ + const ulint* space_ids, /* in: array of space ids */ + const ib_int64_t* space_versions,/* in: the spaces must have + this version number + (timestamp), otherwise we + discard the read; we use this + to cancel reads if DISCARD + + IMPORT may have changed the + tablespace size */ + const ulint* page_nos, /* in: array of page numbers + to read, with the highest page + number the last in the + array */ + ulint n_stored); /* in: number of elements + in the arrays */ +/************************************************************************ +Issues read requests for pages which recovery wants to read in. */ +UNIV_INTERN +void +buf_read_recv_pages( +/*================*/ + ibool sync, /* in: TRUE if the caller + wants this function to wait + for the highest address page + to get read in, before this + function returns */ + ulint space, /* in: space id */ + ulint zip_size, /* in: compressed page size in + bytes, or 0 */ + const ulint* page_nos, /* in: array of page numbers + to read, with the highest page + number the last in the + array */ + ulint n_stored); /* in: number of page numbers + in the array */ + +/* The size in pages of the area which the read-ahead algorithms read if +invoked */ + +#define BUF_READ_AHEAD_AREA \ + ut_min(64, ut_2_power_up(buf_pool->curr_size / 32)) + +/* Modes used in read-ahead */ +#define BUF_READ_IBUF_PAGES_ONLY 131 +#define BUF_READ_ANY_PAGE 132 + +#endif diff --git a/storage/xtradb/include/buf0types.h b/storage/xtradb/include/buf0types.h new file mode 100644 index 00000000000..f2721da85f9 --- /dev/null +++ b/storage/xtradb/include/buf0types.h @@ -0,0 +1,70 @@ +/***************************************************************************** + +Copyright (c) 1995, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/****************************************************** +The database buffer pool global types for the directory + +Created 11/17/1995 Heikki Tuuri +*******************************************************/ + +#ifndef buf0types_h +#define buf0types_h + +typedef struct buf_page_struct buf_page_t; +typedef struct buf_block_struct buf_block_t; +typedef struct buf_chunk_struct buf_chunk_t; +typedef struct buf_pool_struct buf_pool_t; + +/* The 'type' used of a buffer frame */ +typedef byte buf_frame_t; + +/* Flags for flush types */ +enum buf_flush { + BUF_FLUSH_LRU = 0, + BUF_FLUSH_SINGLE_PAGE, + BUF_FLUSH_LIST, + BUF_FLUSH_N_TYPES /* index of last element + 1 */ +}; + +/* Flags for io_fix types */ +enum buf_io_fix { + BUF_IO_NONE = 0, /**< no pending I/O */ + BUF_IO_READ, /**< read pending */ + BUF_IO_WRITE /**< write pending */ +}; + +/* Parameters of binary buddy system for compressed pages (buf0buddy.h) */ +#if UNIV_WORD_SIZE <= 4 /* 32-bit system */ +# define BUF_BUDDY_LOW_SHIFT 6 +#else /* 64-bit system */ +# define BUF_BUDDY_LOW_SHIFT 7 +#endif +#define BUF_BUDDY_LOW (1 << BUF_BUDDY_LOW_SHIFT) + /* minimum block size in the binary + buddy system; must be at least + sizeof(buf_page_t) */ +#define BUF_BUDDY_SIZES (UNIV_PAGE_SIZE_SHIFT - BUF_BUDDY_LOW_SHIFT) + /* number of buddy sizes */ + +/* twice the maximum block size of the buddy system; +the underlying memory is aligned by this amount: +this must be equal to UNIV_PAGE_SIZE */ +#define BUF_BUDDY_HIGH (BUF_BUDDY_LOW << BUF_BUDDY_SIZES) + +#endif + diff --git a/storage/xtradb/include/data0data.h b/storage/xtradb/include/data0data.h new file mode 100644 index 00000000000..1190a7ae45a --- /dev/null +++ b/storage/xtradb/include/data0data.h @@ -0,0 +1,480 @@ +/***************************************************************************** + +Copyright (c) 1994, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/************************************************************************ +SQL data field and tuple + +Created 5/30/1994 Heikki Tuuri +*************************************************************************/ + +#ifndef data0data_h +#define data0data_h + +#include "univ.i" + +#include "data0types.h" +#include "data0type.h" +#include "mem0mem.h" +#include "dict0types.h" + +typedef struct big_rec_struct big_rec_t; + +#ifdef UNIV_DEBUG +/************************************************************************* +Gets pointer to the type struct of SQL data field. */ +UNIV_INLINE +dtype_t* +dfield_get_type( +/*============*/ + /* out: pointer to the type struct */ + const dfield_t* field); /* in: SQL data field */ +/************************************************************************* +Gets pointer to the data in a field. */ +UNIV_INLINE +void* +dfield_get_data( +/*============*/ + /* out: pointer to data */ + const dfield_t* field); /* in: field */ +#else /* UNIV_DEBUG */ +# define dfield_get_type(field) (&(field)->type) +# define dfield_get_data(field) ((field)->data) +#endif /* UNIV_DEBUG */ +/************************************************************************* +Sets the type struct of SQL data field. */ +UNIV_INLINE +void +dfield_set_type( +/*============*/ + dfield_t* field, /* in: SQL data field */ + dtype_t* type); /* in: pointer to data type struct */ +/************************************************************************* +Gets length of field data. */ +UNIV_INLINE +ulint +dfield_get_len( +/*===========*/ + /* out: length of data; UNIV_SQL_NULL if + SQL null data */ + const dfield_t* field); /* in: field */ +/************************************************************************* +Sets length in a field. */ +UNIV_INLINE +void +dfield_set_len( +/*===========*/ + dfield_t* field, /* in: field */ + ulint len); /* in: length or UNIV_SQL_NULL */ +/************************************************************************* +Determines if a field is SQL NULL */ +UNIV_INLINE +ulint +dfield_is_null( +/*===========*/ + /* out: nonzero if SQL null data */ + const dfield_t* field); /* in: field */ +/************************************************************************* +Determines if a field is externally stored */ +UNIV_INLINE +ulint +dfield_is_ext( +/*==========*/ + /* out: nonzero if externally stored */ + const dfield_t* field); /* in: field */ +/************************************************************************* +Sets the "external storage" flag */ +UNIV_INLINE +void +dfield_set_ext( +/*===========*/ + dfield_t* field); /* in/out: field */ +/************************************************************************* +Sets pointer to the data and length in a field. */ +UNIV_INLINE +void +dfield_set_data( +/*============*/ + dfield_t* field, /* in: field */ + const void* data, /* in: data */ + ulint len); /* in: length or UNIV_SQL_NULL */ +/************************************************************************* +Sets a data field to SQL NULL. */ +UNIV_INLINE +void +dfield_set_null( +/*============*/ + dfield_t* field); /* in/out: field */ +/************************************************************************** +Writes an SQL null field full of zeros. */ +UNIV_INLINE +void +data_write_sql_null( +/*================*/ + byte* data, /* in: pointer to a buffer of size len */ + ulint len); /* in: SQL null size in bytes */ +/************************************************************************* +Copies the data and len fields. */ +UNIV_INLINE +void +dfield_copy_data( +/*=============*/ + dfield_t* field1, /* out: field to copy to */ + const dfield_t* field2);/* in: field to copy from */ +/************************************************************************* +Copies a data field to another. */ +UNIV_INLINE +void +dfield_copy( +/*========*/ + dfield_t* field1, /* out: field to copy to */ + const dfield_t* field2);/* in: field to copy from */ +/************************************************************************* +Copies the data pointed to by a data field. */ +UNIV_INLINE +void +dfield_dup( +/*=======*/ + dfield_t* field, /* in/out: data field */ + mem_heap_t* heap); /* in: memory heap where allocated */ +/************************************************************************* +Tests if data length and content is equal for two dfields. */ +UNIV_INLINE +ibool +dfield_datas_are_binary_equal( +/*==========================*/ + /* out: TRUE if equal */ + const dfield_t* field1, /* in: field */ + const dfield_t* field2);/* in: field */ +/************************************************************************* +Tests if dfield data length and content is equal to the given. */ +UNIV_INTERN +ibool +dfield_data_is_binary_equal( +/*========================*/ + /* out: TRUE if equal */ + const dfield_t* field, /* in: field */ + ulint len, /* in: data length or UNIV_SQL_NULL */ + const byte* data); /* in: data */ +/************************************************************************* +Gets number of fields in a data tuple. */ +UNIV_INLINE +ulint +dtuple_get_n_fields( +/*================*/ + /* out: number of fields */ + const dtuple_t* tuple); /* in: tuple */ +#ifdef UNIV_DEBUG +/************************************************************************* +Gets nth field of a tuple. */ +UNIV_INLINE +dfield_t* +dtuple_get_nth_field( +/*=================*/ + /* out: nth field */ + const dtuple_t* tuple, /* in: tuple */ + ulint n); /* in: index of field */ +#else /* UNIV_DEBUG */ +# define dtuple_get_nth_field(tuple, n) ((tuple)->fields + (n)) +#endif /* UNIV_DEBUG */ +/************************************************************************* +Gets info bits in a data tuple. */ +UNIV_INLINE +ulint +dtuple_get_info_bits( +/*=================*/ + /* out: info bits */ + const dtuple_t* tuple); /* in: tuple */ +/************************************************************************* +Sets info bits in a data tuple. */ +UNIV_INLINE +void +dtuple_set_info_bits( +/*=================*/ + dtuple_t* tuple, /* in: tuple */ + ulint info_bits); /* in: info bits */ +/************************************************************************* +Gets number of fields used in record comparisons. */ +UNIV_INLINE +ulint +dtuple_get_n_fields_cmp( +/*====================*/ + /* out: number of fields used in comparisons + in rem0cmp.* */ + const dtuple_t* tuple); /* in: tuple */ +/************************************************************************* +Gets number of fields used in record comparisons. */ +UNIV_INLINE +void +dtuple_set_n_fields_cmp( +/*====================*/ + dtuple_t* tuple, /* in: tuple */ + ulint n_fields_cmp); /* in: number of fields used in + comparisons in rem0cmp.* */ +/************************************************************** +Creates a data tuple to a memory heap. The default value for number +of fields used in record comparisons for this tuple is n_fields. */ +UNIV_INLINE +dtuple_t* +dtuple_create( +/*==========*/ + /* out, own: created tuple */ + mem_heap_t* heap, /* in: memory heap where the tuple + is created */ + ulint n_fields); /* in: number of fields */ + +/************************************************************** +Wrap data fields in a tuple. The default value for number +of fields used in record comparisons for this tuple is n_fields. */ +UNIV_INLINE +const dtuple_t* +dtuple_from_fields( +/*===============*/ + /* out: data tuple */ + dtuple_t* tuple, /* in: storage for data tuple */ + const dfield_t* fields, /* in: fields */ + ulint n_fields); /* in: number of fields */ + +/************************************************************************* +Sets number of fields used in a tuple. Normally this is set in +dtuple_create, but if you want later to set it smaller, you can use this. */ +UNIV_INTERN +void +dtuple_set_n_fields( +/*================*/ + dtuple_t* tuple, /* in: tuple */ + ulint n_fields); /* in: number of fields */ +/************************************************************************* +Copies a data tuple to another. This is a shallow copy; if a deep copy +is desired, dfield_dup() will have to be invoked on each field. */ +UNIV_INLINE +dtuple_t* +dtuple_copy( +/*========*/ + /* out, own: copy of tuple */ + const dtuple_t* tuple, /* in: tuple to copy from */ + mem_heap_t* heap); /* in: memory heap + where the tuple is created */ +/************************************************************** +The following function returns the sum of data lengths of a tuple. The space +occupied by the field structs or the tuple struct is not counted. */ +UNIV_INLINE +ulint +dtuple_get_data_size( +/*=================*/ + /* out: sum of data lens */ + const dtuple_t* tuple); /* in: typed data tuple */ +/************************************************************************* +Computes the number of externally stored fields in a data tuple. */ +UNIV_INLINE +ulint +dtuple_get_n_ext( +/*=============*/ + /* out: number of fields */ + const dtuple_t* tuple); /* in: tuple */ +/**************************************************************** +Compare two data tuples, respecting the collation of character fields. */ +UNIV_INTERN +int +dtuple_coll_cmp( +/*============*/ + /* out: 1, 0 , -1 if tuple1 is greater, equal, + less, respectively, than tuple2 */ + const dtuple_t* tuple1, /* in: tuple 1 */ + const dtuple_t* tuple2);/* in: tuple 2 */ +/**************************************************************** +Folds a prefix given as the number of fields of a tuple. */ +UNIV_INLINE +ulint +dtuple_fold( +/*========*/ + /* out: the folded value */ + const dtuple_t* tuple, /* in: the tuple */ + ulint n_fields,/* in: number of complete fields to fold */ + ulint n_bytes,/* in: number of bytes to fold in an + incomplete last field */ + dulint tree_id)/* in: index tree id */ + __attribute__((pure)); +/*********************************************************************** +Sets types of fields binary in a tuple. */ +UNIV_INLINE +void +dtuple_set_types_binary( +/*====================*/ + dtuple_t* tuple, /* in: data tuple */ + ulint n); /* in: number of fields to set */ +/************************************************************************** +Checks if a dtuple contains an SQL null value. */ +UNIV_INLINE +ibool +dtuple_contains_null( +/*=================*/ + /* out: TRUE if some field is SQL null */ + const dtuple_t* tuple); /* in: dtuple */ +/************************************************************** +Checks that a data field is typed. Asserts an error if not. */ +UNIV_INTERN +ibool +dfield_check_typed( +/*===============*/ + /* out: TRUE if ok */ + const dfield_t* field); /* in: data field */ +/************************************************************** +Checks that a data tuple is typed. Asserts an error if not. */ +UNIV_INTERN +ibool +dtuple_check_typed( +/*===============*/ + /* out: TRUE if ok */ + const dtuple_t* tuple); /* in: tuple */ +/************************************************************** +Checks that a data tuple is typed. */ +UNIV_INTERN +ibool +dtuple_check_typed_no_assert( +/*=========================*/ + /* out: TRUE if ok */ + const dtuple_t* tuple); /* in: tuple */ +#ifdef UNIV_DEBUG +/************************************************************** +Validates the consistency of a tuple which must be complete, i.e, +all fields must have been set. */ +UNIV_INTERN +ibool +dtuple_validate( +/*============*/ + /* out: TRUE if ok */ + const dtuple_t* tuple); /* in: tuple */ +#endif /* UNIV_DEBUG */ +/***************************************************************** +Pretty prints a dfield value according to its data type. */ +UNIV_INTERN +void +dfield_print( +/*=========*/ + const dfield_t* dfield);/* in: dfield */ +/***************************************************************** +Pretty prints a dfield value according to its data type. Also the hex string +is printed if a string contains non-printable characters. */ +UNIV_INTERN +void +dfield_print_also_hex( +/*==================*/ + const dfield_t* dfield); /* in: dfield */ +/************************************************************** +The following function prints the contents of a tuple. */ +UNIV_INTERN +void +dtuple_print( +/*=========*/ + FILE* f, /* in: output stream */ + const dtuple_t* tuple); /* in: tuple */ +/****************************************************************** +Moves parts of long fields in entry to the big record vector so that +the size of tuple drops below the maximum record size allowed in the +database. Moves data only from those fields which are not necessary +to determine uniquely the insertion place of the tuple in the index. */ +UNIV_INTERN +big_rec_t* +dtuple_convert_big_rec( +/*===================*/ + /* out, own: created big record vector, + NULL if we are not able to shorten + the entry enough, i.e., if there are + too many fixed-length or short fields + in entry or the index is clustered */ + dict_index_t* index, /* in: index */ + dtuple_t* entry, /* in/out: index entry */ + ulint* n_ext); /* in/out: number of + externally stored columns */ +/****************************************************************** +Puts back to entry the data stored in vector. Note that to ensure the +fields in entry can accommodate the data, vector must have been created +from entry with dtuple_convert_big_rec. */ +UNIV_INTERN +void +dtuple_convert_back_big_rec( +/*========================*/ + dict_index_t* index, /* in: index */ + dtuple_t* entry, /* in: entry whose data was put to vector */ + big_rec_t* vector);/* in, own: big rec vector; it is + freed in this function */ +/****************************************************************** +Frees the memory in a big rec vector. */ +UNIV_INLINE +void +dtuple_big_rec_free( +/*================*/ + big_rec_t* vector); /* in, own: big rec vector; it is + freed in this function */ + +/*######################################################################*/ + +/* Structure for an SQL data field */ +struct dfield_struct{ + void* data; /* pointer to data */ + unsigned ext:1; /* TRUE=externally stored, FALSE=local */ + unsigned len:32; /* data length; UNIV_SQL_NULL if SQL null */ + dtype_t type; /* type of data */ +}; + +struct dtuple_struct { + ulint info_bits; /* info bits of an index record: + the default is 0; this field is used + if an index record is built from + a data tuple */ + ulint n_fields; /* number of fields in dtuple */ + ulint n_fields_cmp; /* number of fields which should + be used in comparison services + of rem0cmp.*; the index search + is performed by comparing only these + fields, others are ignored; the + default value in dtuple creation is + the same value as n_fields */ + dfield_t* fields; /* fields */ + UT_LIST_NODE_T(dtuple_t) tuple_list; + /* data tuples can be linked into a + list using this field */ +#ifdef UNIV_DEBUG + ulint magic_n; +# define DATA_TUPLE_MAGIC_N 65478679 +#endif /* UNIV_DEBUG */ +}; + +/* A slot for a field in a big rec vector */ + +typedef struct big_rec_field_struct big_rec_field_t; +struct big_rec_field_struct { + ulint field_no; /* field number in record */ + ulint len; /* stored data len */ + const void* data; /* stored data */ +}; + +/* Storage format for overflow data in a big record, that is, a record +which needs external storage of data fields */ + +struct big_rec_struct { + mem_heap_t* heap; /* memory heap from which allocated */ + ulint n_fields; /* number of stored fields */ + big_rec_field_t* fields; /* stored fields */ +}; + +#ifndef UNIV_NONINL +#include "data0data.ic" +#endif + +#endif diff --git a/storage/xtradb/include/data0data.ic b/storage/xtradb/include/data0data.ic new file mode 100644 index 00000000000..f11dbd9fce6 --- /dev/null +++ b/storage/xtradb/include/data0data.ic @@ -0,0 +1,608 @@ +/***************************************************************************** + +Copyright (c) 1994, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/************************************************************************ +SQL data field and tuple + +Created 5/30/1994 Heikki Tuuri +*************************************************************************/ + +#include "mem0mem.h" +#include "ut0rnd.h" + +#ifdef UNIV_DEBUG +extern byte data_error; + +/************************************************************************* +Gets pointer to the type struct of SQL data field. */ +UNIV_INLINE +dtype_t* +dfield_get_type( +/*============*/ + /* out: pointer to the type struct */ + const dfield_t* field) /* in: SQL data field */ +{ + ut_ad(field); + + return((dtype_t*) &(field->type)); +} +#endif /* UNIV_DEBUG */ + +/************************************************************************* +Sets the type struct of SQL data field. */ +UNIV_INLINE +void +dfield_set_type( +/*============*/ + dfield_t* field, /* in: SQL data field */ + dtype_t* type) /* in: pointer to data type struct */ +{ + ut_ad(field && type); + + field->type = *type; +} + +#ifdef UNIV_DEBUG +/************************************************************************* +Gets pointer to the data in a field. */ +UNIV_INLINE +void* +dfield_get_data( +/*============*/ + /* out: pointer to data */ + const dfield_t* field) /* in: field */ +{ + ut_ad(field); + ut_ad((field->len == UNIV_SQL_NULL) + || (field->data != &data_error)); + + return((void*) field->data); +} +#endif /* UNIV_DEBUG */ + +/************************************************************************* +Gets length of field data. */ +UNIV_INLINE +ulint +dfield_get_len( +/*===========*/ + /* out: length of data; UNIV_SQL_NULL if + SQL null data */ + const dfield_t* field) /* in: field */ +{ + ut_ad(field); + ut_ad((field->len == UNIV_SQL_NULL) + || (field->data != &data_error)); + + return(field->len); +} + +/************************************************************************* +Sets length in a field. */ +UNIV_INLINE +void +dfield_set_len( +/*===========*/ + dfield_t* field, /* in: field */ + ulint len) /* in: length or UNIV_SQL_NULL */ +{ + ut_ad(field); +#ifdef UNIV_VALGRIND_DEBUG + if (len != UNIV_SQL_NULL) UNIV_MEM_ASSERT_RW(field->data, len); +#endif /* UNIV_VALGRIND_DEBUG */ + + field->ext = 0; + field->len = len; +} + +/************************************************************************* +Determines if a field is SQL NULL */ +UNIV_INLINE +ulint +dfield_is_null( +/*===========*/ + /* out: nonzero if SQL null data */ + const dfield_t* field) /* in: field */ +{ + ut_ad(field); + + return(field->len == UNIV_SQL_NULL); +} + +/************************************************************************* +Determines if a field is externally stored */ +UNIV_INLINE +ulint +dfield_is_ext( +/*==========*/ + /* out: nonzero if externally stored */ + const dfield_t* field) /* in: field */ +{ + ut_ad(field); + + return(UNIV_UNLIKELY(field->ext)); +} + +/************************************************************************* +Sets the "external storage" flag */ +UNIV_INLINE +void +dfield_set_ext( +/*===========*/ + dfield_t* field) /* in/out: field */ +{ + ut_ad(field); + + field->ext = 1; +} + +/************************************************************************* +Sets pointer to the data and length in a field. */ +UNIV_INLINE +void +dfield_set_data( +/*============*/ + dfield_t* field, /* in: field */ + const void* data, /* in: data */ + ulint len) /* in: length or UNIV_SQL_NULL */ +{ + ut_ad(field); + +#ifdef UNIV_VALGRIND_DEBUG + if (len != UNIV_SQL_NULL) UNIV_MEM_ASSERT_RW(data, len); +#endif /* UNIV_VALGRIND_DEBUG */ + field->data = (void*) data; + field->ext = 0; + field->len = len; +} + +/************************************************************************* +Sets a data field to SQL NULL. */ +UNIV_INLINE +void +dfield_set_null( +/*============*/ + dfield_t* field) /* in/out: field */ +{ + dfield_set_data(field, NULL, UNIV_SQL_NULL); +} + +/************************************************************************* +Copies the data and len fields. */ +UNIV_INLINE +void +dfield_copy_data( +/*=============*/ + dfield_t* field1, /* out: field to copy to */ + const dfield_t* field2) /* in: field to copy from */ +{ + ut_ad(field1 && field2); + + field1->data = field2->data; + field1->len = field2->len; + field1->ext = field2->ext; +} + +/************************************************************************* +Copies a data field to another. */ +UNIV_INLINE +void +dfield_copy( +/*========*/ + dfield_t* field1, /* out: field to copy to */ + const dfield_t* field2) /* in: field to copy from */ +{ + *field1 = *field2; +} + +/************************************************************************* +Copies the data pointed to by a data field. */ +UNIV_INLINE +void +dfield_dup( +/*=======*/ + dfield_t* field, /* in/out: data field */ + mem_heap_t* heap) /* in: memory heap where allocated */ +{ + if (!dfield_is_null(field)) { + UNIV_MEM_ASSERT_RW(field->data, field->len); + field->data = mem_heap_dup(heap, field->data, field->len); + } +} + +/************************************************************************* +Tests if data length and content is equal for two dfields. */ +UNIV_INLINE +ibool +dfield_datas_are_binary_equal( +/*==========================*/ + /* out: TRUE if equal */ + const dfield_t* field1, /* in: field */ + const dfield_t* field2) /* in: field */ +{ + ulint len; + + len = field1->len; + + return(len == field2->len + && (len == UNIV_SQL_NULL + || !memcmp(field1->data, field2->data, len))); +} + +/************************************************************************* +Gets info bits in a data tuple. */ +UNIV_INLINE +ulint +dtuple_get_info_bits( +/*=================*/ + /* out: info bits */ + const dtuple_t* tuple) /* in: tuple */ +{ + ut_ad(tuple); + + return(tuple->info_bits); +} + +/************************************************************************* +Sets info bits in a data tuple. */ +UNIV_INLINE +void +dtuple_set_info_bits( +/*=================*/ + dtuple_t* tuple, /* in: tuple */ + ulint info_bits) /* in: info bits */ +{ + ut_ad(tuple); + + tuple->info_bits = info_bits; +} + +/************************************************************************* +Gets number of fields used in record comparisons. */ +UNIV_INLINE +ulint +dtuple_get_n_fields_cmp( +/*====================*/ + /* out: number of fields used in comparisons + in rem0cmp.* */ + const dtuple_t* tuple) /* in: tuple */ +{ + ut_ad(tuple); + + return(tuple->n_fields_cmp); +} + +/************************************************************************* +Sets number of fields used in record comparisons. */ +UNIV_INLINE +void +dtuple_set_n_fields_cmp( +/*====================*/ + dtuple_t* tuple, /* in: tuple */ + ulint n_fields_cmp) /* in: number of fields used in + comparisons in rem0cmp.* */ +{ + ut_ad(tuple); + ut_ad(n_fields_cmp <= tuple->n_fields); + + tuple->n_fields_cmp = n_fields_cmp; +} + +/************************************************************************* +Gets number of fields in a data tuple. */ +UNIV_INLINE +ulint +dtuple_get_n_fields( +/*================*/ + /* out: number of fields */ + const dtuple_t* tuple) /* in: tuple */ +{ + ut_ad(tuple); + + return(tuple->n_fields); +} + +#ifdef UNIV_DEBUG +/************************************************************************* +Gets nth field of a tuple. */ +UNIV_INLINE +dfield_t* +dtuple_get_nth_field( +/*=================*/ + /* out: nth field */ + const dtuple_t* tuple, /* in: tuple */ + ulint n) /* in: index of field */ +{ + ut_ad(tuple); + ut_ad(n < tuple->n_fields); + + return((dfield_t*) tuple->fields + n); +} +#endif /* UNIV_DEBUG */ + +/************************************************************** +Creates a data tuple to a memory heap. The default value for number +of fields used in record comparisons for this tuple is n_fields. */ +UNIV_INLINE +dtuple_t* +dtuple_create( +/*==========*/ + /* out, own: created tuple */ + mem_heap_t* heap, /* in: memory heap where the tuple + is created */ + ulint n_fields) /* in: number of fields */ +{ + dtuple_t* tuple; + + ut_ad(heap); + + tuple = (dtuple_t*) mem_heap_alloc(heap, sizeof(dtuple_t) + + n_fields * sizeof(dfield_t)); + tuple->info_bits = 0; + tuple->n_fields = n_fields; + tuple->n_fields_cmp = n_fields; + tuple->fields = (dfield_t*) &tuple[1]; + +#ifdef UNIV_DEBUG + tuple->magic_n = DATA_TUPLE_MAGIC_N; + + { /* In the debug version, initialize fields to an error value */ + ulint i; + + for (i = 0; i < n_fields; i++) { + dfield_t* field; + + field = dtuple_get_nth_field(tuple, i); + + dfield_set_len(field, UNIV_SQL_NULL); + field->data = &data_error; + dfield_get_type(field)->mtype = DATA_ERROR; + } + } + + UNIV_MEM_INVALID(tuple->fields, n_fields * sizeof *tuple->fields); +#endif + return(tuple); +} + +/************************************************************** +Wrap data fields in a tuple. The default value for number +of fields used in record comparisons for this tuple is n_fields. */ +UNIV_INLINE +const dtuple_t* +dtuple_from_fields( +/*===============*/ + /* out: data tuple */ + dtuple_t* tuple, /* in: storage for data tuple */ + const dfield_t* fields, /* in: fields */ + ulint n_fields) /* in: number of fields */ +{ + tuple->info_bits = 0; + tuple->n_fields = tuple->n_fields_cmp = n_fields; + tuple->fields = (dfield_t*) fields; + ut_d(tuple->magic_n = DATA_TUPLE_MAGIC_N); + + return(tuple); +} + +/************************************************************************* +Copies a data tuple to another. This is a shallow copy; if a deep copy +is desired, dfield_dup() will have to be invoked on each field. */ +UNIV_INLINE +dtuple_t* +dtuple_copy( +/*========*/ + /* out, own: copy of tuple */ + const dtuple_t* tuple, /* in: tuple to copy from */ + mem_heap_t* heap) /* in: memory heap + where the tuple is created */ +{ + ulint n_fields = dtuple_get_n_fields(tuple); + dtuple_t* new_tuple = dtuple_create(heap, n_fields); + ulint i; + + for (i = 0; i < n_fields; i++) { + dfield_copy(dtuple_get_nth_field(new_tuple, i), + dtuple_get_nth_field(tuple, i)); + } + + return(new_tuple); +} + +/************************************************************** +The following function returns the sum of data lengths of a tuple. The space +occupied by the field structs or the tuple struct is not counted. Neither +is possible space in externally stored parts of the field. */ +UNIV_INLINE +ulint +dtuple_get_data_size( +/*=================*/ + /* out: sum of data lengths */ + const dtuple_t* tuple) /* in: typed data tuple */ +{ + const dfield_t* field; + ulint n_fields; + ulint len; + ulint i; + ulint sum = 0; + + ut_ad(tuple); + ut_ad(dtuple_check_typed(tuple)); + ut_ad(tuple->magic_n == DATA_TUPLE_MAGIC_N); + + n_fields = tuple->n_fields; + + for (i = 0; i < n_fields; i++) { + field = dtuple_get_nth_field(tuple, i); + len = dfield_get_len(field); + + if (len == UNIV_SQL_NULL) { + len = dtype_get_sql_null_size(dfield_get_type(field)); + } + + sum += len; + } + + return(sum); +} + +/************************************************************************* +Computes the number of externally stored fields in a data tuple. */ +UNIV_INLINE +ulint +dtuple_get_n_ext( +/*=============*/ + /* out: number of externally stored fields */ + const dtuple_t* tuple) /* in: tuple */ +{ + ulint n_ext = 0; + ulint n_fields = tuple->n_fields; + ulint i; + + ut_ad(tuple); + ut_ad(dtuple_check_typed(tuple)); + ut_ad(tuple->magic_n == DATA_TUPLE_MAGIC_N); + + for (i = 0; i < n_fields; i++) { + n_ext += dtuple_get_nth_field(tuple, i)->ext; + } + + return(n_ext); +} + +/*********************************************************************** +Sets types of fields binary in a tuple. */ +UNIV_INLINE +void +dtuple_set_types_binary( +/*====================*/ + dtuple_t* tuple, /* in: data tuple */ + ulint n) /* in: number of fields to set */ +{ + dtype_t* dfield_type; + ulint i; + + for (i = 0; i < n; i++) { + dfield_type = dfield_get_type(dtuple_get_nth_field(tuple, i)); + dtype_set(dfield_type, DATA_BINARY, 0, 0); + } +} + +/**************************************************************** +Folds a prefix given as the number of fields of a tuple. */ +UNIV_INLINE +ulint +dtuple_fold( +/*========*/ + /* out: the folded value */ + const dtuple_t* tuple, /* in: the tuple */ + ulint n_fields,/* in: number of complete fields to fold */ + ulint n_bytes,/* in: number of bytes to fold in an + incomplete last field */ + dulint tree_id)/* in: index tree id */ +{ + const dfield_t* field; + ulint i; + const byte* data; + ulint len; + ulint fold; + + ut_ad(tuple); + ut_ad(tuple->magic_n == DATA_TUPLE_MAGIC_N); + ut_ad(dtuple_check_typed(tuple)); + + fold = ut_fold_dulint(tree_id); + + for (i = 0; i < n_fields; i++) { + field = dtuple_get_nth_field(tuple, i); + + data = (const byte*) dfield_get_data(field); + len = dfield_get_len(field); + + if (len != UNIV_SQL_NULL) { + fold = ut_fold_ulint_pair(fold, + ut_fold_binary(data, len)); + } + } + + if (n_bytes > 0) { + field = dtuple_get_nth_field(tuple, i); + + data = (const byte*) dfield_get_data(field); + len = dfield_get_len(field); + + if (len != UNIV_SQL_NULL) { + if (len > n_bytes) { + len = n_bytes; + } + + fold = ut_fold_ulint_pair(fold, + ut_fold_binary(data, len)); + } + } + + return(fold); +} + +/************************************************************************** +Writes an SQL null field full of zeros. */ +UNIV_INLINE +void +data_write_sql_null( +/*================*/ + byte* data, /* in: pointer to a buffer of size len */ + ulint len) /* in: SQL null size in bytes */ +{ + memset(data, 0, len); +} + +/************************************************************************** +Checks if a dtuple contains an SQL null value. */ +UNIV_INLINE +ibool +dtuple_contains_null( +/*=================*/ + /* out: TRUE if some field is SQL null */ + const dtuple_t* tuple) /* in: dtuple */ +{ + ulint n; + ulint i; + + n = dtuple_get_n_fields(tuple); + + for (i = 0; i < n; i++) { + if (dfield_is_null(dtuple_get_nth_field(tuple, i))) { + + return(TRUE); + } + } + + return(FALSE); +} + +/****************************************************************** +Frees the memory in a big rec vector. */ +UNIV_INLINE +void +dtuple_big_rec_free( +/*================*/ + big_rec_t* vector) /* in, own: big rec vector; it is + freed in this function */ +{ + mem_heap_free(vector->heap); +} diff --git a/storage/xtradb/include/data0type.h b/storage/xtradb/include/data0type.h new file mode 100644 index 00000000000..1f10878984b --- /dev/null +++ b/storage/xtradb/include/data0type.h @@ -0,0 +1,471 @@ +/***************************************************************************** + +Copyright (c) 1996, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/****************************************************** +Data types + +Created 1/16/1996 Heikki Tuuri +*******************************************************/ + +#ifndef data0type_h +#define data0type_h + +#include "univ.i" + +extern ulint data_mysql_default_charset_coll; +#define DATA_MYSQL_LATIN1_SWEDISH_CHARSET_COLL 8 +#define DATA_MYSQL_BINARY_CHARSET_COLL 63 + +/* SQL data type struct */ +typedef struct dtype_struct dtype_t; + +/*-------------------------------------------*/ +/* The 'MAIN TYPE' of a column */ +#define DATA_VARCHAR 1 /* character varying of the + latin1_swedish_ci charset-collation; note + that the MySQL format for this, DATA_BINARY, + DATA_VARMYSQL, is also affected by whether the + 'precise type' contains + DATA_MYSQL_TRUE_VARCHAR */ +#define DATA_CHAR 2 /* fixed length character of the + latin1_swedish_ci charset-collation */ +#define DATA_FIXBINARY 3 /* binary string of fixed length */ +#define DATA_BINARY 4 /* binary string */ +#define DATA_BLOB 5 /* binary large object, or a TEXT type; + if prtype & DATA_BINARY_TYPE == 0, then this is + actually a TEXT column (or a BLOB created + with < 4.0.14; since column prefix indexes + came only in 4.0.14, the missing flag in BLOBs + created before that does not cause any harm) */ +#define DATA_INT 6 /* integer: can be any size 1 - 8 bytes */ +#define DATA_SYS_CHILD 7 /* address of the child page in node pointer */ +#define DATA_SYS 8 /* system column */ + +/* Data types >= DATA_FLOAT must be compared using the whole field, not as +binary strings */ + +#define DATA_FLOAT 9 +#define DATA_DOUBLE 10 +#define DATA_DECIMAL 11 /* decimal number stored as an ASCII string */ +#define DATA_VARMYSQL 12 /* any charset varying length char */ +#define DATA_MYSQL 13 /* any charset fixed length char */ + /* NOTE that 4.1.1 used DATA_MYSQL and + DATA_VARMYSQL for all character sets, and the + charset-collation for tables created with it + can also be latin1_swedish_ci */ +#define DATA_MTYPE_MAX 63 /* dtype_store_for_order_and_null_size() + requires the values are <= 63 */ +/*-------------------------------------------*/ +/* The 'PRECISE TYPE' of a column */ +/* +Tables created by a MySQL user have the following convention: + +- In the least significant byte in the precise type we store the MySQL type +code (not applicable for system columns). + +- In the second least significant byte we OR flags DATA_NOT_NULL, +DATA_UNSIGNED, DATA_BINARY_TYPE. + +- In the third least significant byte of the precise type of string types we +store the MySQL charset-collation code. In DATA_BLOB columns created with +< 4.0.14 we do not actually know if it is a BLOB or a TEXT column. Since there +are no indexes on prefixes of BLOB or TEXT columns in < 4.0.14, this is no +problem, though. + +Note that versions < 4.1.2 or < 5.0.1 did not store the charset code to the +precise type, since the charset was always the default charset of the MySQL +installation. If the stored charset code is 0 in the system table SYS_COLUMNS +of InnoDB, that means that the default charset of this MySQL installation +should be used. + +When loading a table definition from the system tables to the InnoDB data +dictionary cache in main memory, InnoDB versions >= 4.1.2 and >= 5.0.1 check +if the stored charset-collation is 0, and if that is the case and the type is +a non-binary string, replace that 0 by the default charset-collation code of +this MySQL installation. In short, in old tables, the charset-collation code +in the system tables on disk can be 0, but in in-memory data structures +(dtype_t), the charset-collation code is always != 0 for non-binary string +types. + +In new tables, in binary string types, the charset-collation code is the +MySQL code for the 'binary charset', that is, != 0. + +For binary string types and for DATA_CHAR, DATA_VARCHAR, and for those +DATA_BLOB which are binary or have the charset-collation latin1_swedish_ci, +InnoDB performs all comparisons internally, without resorting to the MySQL +comparison functions. This is to save CPU time. + +InnoDB's own internal system tables have different precise types for their +columns, and for them the precise type is usually not used at all. +*/ + +#define DATA_ENGLISH 4 /* English language character string: this + is a relic from pre-MySQL time and only used + for InnoDB's own system tables */ +#define DATA_ERROR 111 /* another relic from pre-MySQL time */ + +#define DATA_MYSQL_TYPE_MASK 255 /* AND with this mask to extract the MySQL + type from the precise type */ +#define DATA_MYSQL_TRUE_VARCHAR 15 /* MySQL type code for the >= 5.0.3 + format true VARCHAR */ + +/* Precise data types for system columns and the length of those columns; +NOTE: the values must run from 0 up in the order given! All codes must +be less than 256 */ +#define DATA_ROW_ID 0 /* row id: a dulint */ +#define DATA_ROW_ID_LEN 6 /* stored length for row id */ + +#define DATA_TRX_ID 1 /* transaction id: 6 bytes */ +#define DATA_TRX_ID_LEN 6 + +#define DATA_ROLL_PTR 2 /* rollback data pointer: 7 bytes */ +#define DATA_ROLL_PTR_LEN 7 + +#define DATA_N_SYS_COLS 3 /* number of system columns defined above */ + +#define DATA_SYS_PRTYPE_MASK 0xF /* mask to extract the above from prtype */ + +/* Flags ORed to the precise data type */ +#define DATA_NOT_NULL 256 /* this is ORed to the precise type when + the column is declared as NOT NULL */ +#define DATA_UNSIGNED 512 /* this id ORed to the precise type when + we have an unsigned integer type */ +#define DATA_BINARY_TYPE 1024 /* if the data type is a binary character + string, this is ORed to the precise type: + this only holds for tables created with + >= MySQL-4.0.14 */ +/* #define DATA_NONLATIN1 2048 This is a relic from < 4.1.2 and < 5.0.1. + In earlier versions this was set for some + BLOB columns. +*/ +#define DATA_LONG_TRUE_VARCHAR 4096 /* this is ORed to the precise data + type when the column is true VARCHAR where + MySQL uses 2 bytes to store the data len; + for shorter VARCHARs MySQL uses only 1 byte */ +/*-------------------------------------------*/ + +/* This many bytes we need to store the type information affecting the +alphabetical order for a single field and decide the storage size of an +SQL null*/ +#define DATA_ORDER_NULL_TYPE_BUF_SIZE 4 +/* In the >= 4.1.x storage format we add 2 bytes more so that we can also +store the charset-collation number; one byte is left unused, though */ +#define DATA_NEW_ORDER_NULL_TYPE_BUF_SIZE 6 + +/************************************************************************* +Gets the MySQL type code from a dtype. */ +UNIV_INLINE +ulint +dtype_get_mysql_type( +/*=================*/ + /* out: MySQL type code; this is NOT an InnoDB + type code! */ + const dtype_t* type); /* in: type struct */ +/************************************************************************* +Determine how many bytes the first n characters of the given string occupy. +If the string is shorter than n characters, returns the number of bytes +the characters in the string occupy. */ +UNIV_INTERN +ulint +dtype_get_at_most_n_mbchars( +/*========================*/ + /* out: length of the prefix, + in bytes */ + ulint prtype, /* in: precise type */ + ulint mbminlen, /* in: minimum length of a + multi-byte character */ + ulint mbmaxlen, /* in: maximum length of a + multi-byte character */ + ulint prefix_len, /* in: length of the requested + prefix, in characters, multiplied by + dtype_get_mbmaxlen(dtype) */ + ulint data_len, /* in: length of str (in bytes) */ + const char* str); /* in: the string whose prefix + length is being determined */ +/************************************************************************* +Checks if a data main type is a string type. Also a BLOB is considered a +string type. */ +UNIV_INTERN +ibool +dtype_is_string_type( +/*=================*/ + /* out: TRUE if string type */ + ulint mtype); /* in: InnoDB main data type code: DATA_CHAR, ... */ +/************************************************************************* +Checks if a type is a binary string type. Note that for tables created with +< 4.0.14, we do not know if a DATA_BLOB column is a BLOB or a TEXT column. For +those DATA_BLOB columns this function currently returns FALSE. */ +UNIV_INTERN +ibool +dtype_is_binary_string_type( +/*========================*/ + /* out: TRUE if binary string type */ + ulint mtype, /* in: main data type */ + ulint prtype);/* in: precise type */ +/************************************************************************* +Checks if a type is a non-binary string type. That is, dtype_is_string_type is +TRUE and dtype_is_binary_string_type is FALSE. Note that for tables created +with < 4.0.14, we do not know if a DATA_BLOB column is a BLOB or a TEXT column. +For those DATA_BLOB columns this function currently returns TRUE. */ +UNIV_INTERN +ibool +dtype_is_non_binary_string_type( +/*============================*/ + /* out: TRUE if non-binary string type */ + ulint mtype, /* in: main data type */ + ulint prtype);/* in: precise type */ +/************************************************************************* +Sets a data type structure. */ +UNIV_INLINE +void +dtype_set( +/*======*/ + dtype_t* type, /* in: type struct to init */ + ulint mtype, /* in: main data type */ + ulint prtype, /* in: precise type */ + ulint len); /* in: precision of type */ +/************************************************************************* +Copies a data type structure. */ +UNIV_INLINE +void +dtype_copy( +/*=======*/ + dtype_t* type1, /* in: type struct to copy to */ + const dtype_t* type2); /* in: type struct to copy from */ +/************************************************************************* +Gets the SQL main data type. */ +UNIV_INLINE +ulint +dtype_get_mtype( +/*============*/ + const dtype_t* type); +/************************************************************************* +Gets the precise data type. */ +UNIV_INLINE +ulint +dtype_get_prtype( +/*=============*/ + const dtype_t* type); +/************************************************************************* +Compute the mbminlen and mbmaxlen members of a data type structure. */ +UNIV_INLINE +void +dtype_get_mblen( +/*============*/ + ulint mtype, /* in: main type */ + ulint prtype, /* in: precise type (and collation) */ + ulint* mbminlen, /* out: minimum length of a + multi-byte character */ + ulint* mbmaxlen); /* out: maximum length of a + multi-byte character */ +/************************************************************************* +Gets the MySQL charset-collation code for MySQL string types. */ +UNIV_INLINE +ulint +dtype_get_charset_coll( +/*===================*/ + ulint prtype);/* in: precise data type */ +/************************************************************************* +Forms a precise type from the < 4.1.2 format precise type plus the +charset-collation code. */ +UNIV_INTERN +ulint +dtype_form_prtype( +/*==============*/ + ulint old_prtype, /* in: the MySQL type code and the flags + DATA_BINARY_TYPE etc. */ + ulint charset_coll); /* in: MySQL charset-collation code */ +/************************************************************************* +Determines if a MySQL string type is a subset of UTF-8. This function +may return false negatives, in case further character-set collation +codes are introduced in MySQL later. */ +UNIV_INLINE +ibool +dtype_is_utf8( +/*==========*/ + /* out: TRUE if a subset of UTF-8 */ + ulint prtype);/* in: precise data type */ +/************************************************************************* +Gets the type length. */ +UNIV_INLINE +ulint +dtype_get_len( +/*==========*/ + const dtype_t* type); +/************************************************************************* +Gets the minimum length of a character, in bytes. */ +UNIV_INLINE +ulint +dtype_get_mbminlen( +/*===============*/ + /* out: minimum length of a char, in bytes, + or 0 if this is not a character type */ + const dtype_t* type); /* in: type */ +/************************************************************************* +Gets the maximum length of a character, in bytes. */ +UNIV_INLINE +ulint +dtype_get_mbmaxlen( +/*===============*/ + /* out: maximum length of a char, in bytes, + or 0 if this is not a character type */ + const dtype_t* type); /* in: type */ +/************************************************************************* +Gets the padding character code for the type. */ +UNIV_INLINE +ulint +dtype_get_pad_char( +/*===============*/ + /* out: padding character code, or + ULINT_UNDEFINED if no padding specified */ + ulint mtype, /* in: main type */ + ulint prtype); /* in: precise type */ +/*************************************************************************** +Returns the size of a fixed size data type, 0 if not a fixed size type. */ +UNIV_INLINE +ulint +dtype_get_fixed_size_low( +/*=====================*/ + /* out: fixed size, or 0 */ + ulint mtype, /* in: main type */ + ulint prtype, /* in: precise type */ + ulint len, /* in: length */ + ulint mbminlen, /* in: minimum length of a multibyte char */ + ulint mbmaxlen); /* in: maximum length of a multibyte char */ +/*************************************************************************** +Returns the minimum size of a data type. */ +UNIV_INLINE +ulint +dtype_get_min_size_low( +/*===================*/ + /* out: minimum size */ + ulint mtype, /* in: main type */ + ulint prtype, /* in: precise type */ + ulint len, /* in: length */ + ulint mbminlen, /* in: minimum length of a multibyte char */ + ulint mbmaxlen); /* in: maximum length of a multibyte char */ +/*************************************************************************** +Returns the maximum size of a data type. Note: types in system tables may be +incomplete and return incorrect information. */ +UNIV_INLINE +ulint +dtype_get_max_size_low( +/*===================*/ + /* out: maximum size */ + ulint mtype, /* in: main type */ + ulint len); /* in: length */ +/*************************************************************************** +Returns the ROW_FORMAT=REDUNDANT stored SQL NULL size of a type. +For fixed length types it is the fixed length of the type, otherwise 0. */ +UNIV_INLINE +ulint +dtype_get_sql_null_size( +/*====================*/ + /* out: SQL null storage size + in ROW_FORMAT=REDUNDANT */ + const dtype_t* type); /* in: type */ +/************************************************************************** +Reads to a type the stored information which determines its alphabetical +ordering and the storage size of an SQL NULL value. */ +UNIV_INLINE +void +dtype_read_for_order_and_null_size( +/*===============================*/ + dtype_t* type, /* in: type struct */ + const byte* buf); /* in: buffer for the stored order info */ +/************************************************************************** +Stores for a type the information which determines its alphabetical ordering +and the storage size of an SQL NULL value. This is the >= 4.1.x storage +format. */ +UNIV_INLINE +void +dtype_new_store_for_order_and_null_size( +/*====================================*/ + byte* buf, /* in: buffer for + DATA_NEW_ORDER_NULL_TYPE_BUF_SIZE + bytes where we store the info */ + const dtype_t* type, /* in: type struct */ + ulint prefix_len);/* in: prefix length to + replace type->len, or 0 */ +/************************************************************************** +Reads to a type the stored information which determines its alphabetical +ordering and the storage size of an SQL NULL value. This is the 4.1.x storage +format. */ +UNIV_INLINE +void +dtype_new_read_for_order_and_null_size( +/*===================================*/ + dtype_t* type, /* in: type struct */ + const byte* buf); /* in: buffer for stored type order info */ + +/************************************************************************* +Validates a data type structure. */ +UNIV_INTERN +ibool +dtype_validate( +/*===========*/ + /* out: TRUE if ok */ + const dtype_t* type); /* in: type struct to validate */ +/************************************************************************* +Prints a data type structure. */ +UNIV_INTERN +void +dtype_print( +/*========*/ + const dtype_t* type); /* in: type */ + +/* Structure for an SQL data type. +If you add fields to this structure, be sure to initialize them everywhere. +This structure is initialized in the following functions: +dtype_set() +dtype_read_for_order_and_null_size() +dtype_new_read_for_order_and_null_size() +sym_tab_add_null_lit() */ + +struct dtype_struct{ + unsigned mtype:8; /* main data type */ + unsigned prtype:24; /* precise type; MySQL data + type, charset code, flags to + indicate nullability, + signedness, whether this is a + binary string, whether this is + a true VARCHAR where MySQL + uses 2 bytes to store the length */ + + /* the remaining fields do not affect alphabetical ordering: */ + + unsigned len:16; /* length; for MySQL data this + is field->pack_length(), + except that for a >= 5.0.3 + type true VARCHAR this is the + maximum byte length of the + string data (in addition to + the string, MySQL uses 1 or 2 + bytes to store the string length) */ + + unsigned mbminlen:2; /* minimum length of a + character, in bytes */ + unsigned mbmaxlen:3; /* maximum length of a + character, in bytes */ +}; + +#ifndef UNIV_NONINL +#include "data0type.ic" +#endif + +#endif diff --git a/storage/xtradb/include/data0type.ic b/storage/xtradb/include/data0type.ic new file mode 100644 index 00000000000..d4c1080bebe --- /dev/null +++ b/storage/xtradb/include/data0type.ic @@ -0,0 +1,587 @@ +/***************************************************************************** + +Copyright (c) 1996, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/****************************************************** +Data types + +Created 1/16/1996 Heikki Tuuri +*******************************************************/ + +#include "mach0data.h" +#include "ha_prototypes.h" + +/************************************************************************* +Gets the MySQL charset-collation code for MySQL string types. */ +UNIV_INLINE +ulint +dtype_get_charset_coll( +/*===================*/ + ulint prtype) /* in: precise data type */ +{ + return((prtype >> 16) & 0xFFUL); +} + +/************************************************************************* +Determines if a MySQL string type is a subset of UTF-8. This function +may return false negatives, in case further character-set collation +codes are introduced in MySQL later. */ +UNIV_INLINE +ibool +dtype_is_utf8( +/*==========*/ + /* out: TRUE if a subset of UTF-8 */ + ulint prtype) /* in: precise data type */ +{ + /* These codes have been copied from strings/ctype-extra.c + and strings/ctype-utf8.c. */ + switch (dtype_get_charset_coll(prtype)) { + case 11: /* ascii_general_ci */ + case 65: /* ascii_bin */ + case 33: /* utf8_general_ci */ + case 83: /* utf8_bin */ + case 254: /* utf8_general_cs */ + return(TRUE); + } + + return(FALSE); +} + +/************************************************************************* +Gets the MySQL type code from a dtype. */ +UNIV_INLINE +ulint +dtype_get_mysql_type( +/*=================*/ + /* out: MySQL type code; this is NOT an InnoDB + type code! */ + const dtype_t* type) /* in: type struct */ +{ + return(type->prtype & 0xFFUL); +} + +/************************************************************************* +Compute the mbminlen and mbmaxlen members of a data type structure. */ +UNIV_INLINE +void +dtype_get_mblen( +/*============*/ + ulint mtype, /* in: main type */ + ulint prtype, /* in: precise type (and collation) */ + ulint* mbminlen, /* out: minimum length of a + multi-byte character */ + ulint* mbmaxlen) /* out: maximum length of a + multi-byte character */ +{ + if (dtype_is_string_type(mtype)) { +#ifndef UNIV_HOTBACKUP + innobase_get_cset_width(dtype_get_charset_coll(prtype), + mbminlen, mbmaxlen); + ut_ad(*mbminlen <= *mbmaxlen); + ut_ad(*mbminlen <= 2); /* mbminlen in dtype_t is 0..3 */ + ut_ad(*mbmaxlen < 1 << 3); /* mbmaxlen in dtype_t is 0..7 */ +#else /* !UNIV_HOTBACKUP */ + ut_a(mtype <= DATA_BINARY); + *mbminlen = *mbmaxlen = 1; +#endif /* !UNIV_HOTBACKUP */ + } else { + *mbminlen = *mbmaxlen = 0; + } +} + +/************************************************************************* +Compute the mbminlen and mbmaxlen members of a data type structure. */ +UNIV_INLINE +void +dtype_set_mblen( +/*============*/ + dtype_t* type) /* in/out: type */ +{ + ulint mbminlen; + ulint mbmaxlen; + + dtype_get_mblen(type->mtype, type->prtype, &mbminlen, &mbmaxlen); + type->mbminlen = mbminlen; + type->mbmaxlen = mbmaxlen; + + ut_ad(dtype_validate(type)); +} + +/************************************************************************* +Sets a data type structure. */ +UNIV_INLINE +void +dtype_set( +/*======*/ + dtype_t* type, /* in: type struct to init */ + ulint mtype, /* in: main data type */ + ulint prtype, /* in: precise type */ + ulint len) /* in: precision of type */ +{ + ut_ad(type); + ut_ad(mtype <= DATA_MTYPE_MAX); + + type->mtype = mtype; + type->prtype = prtype; + type->len = len; + + dtype_set_mblen(type); +} + +/************************************************************************* +Copies a data type structure. */ +UNIV_INLINE +void +dtype_copy( +/*=======*/ + dtype_t* type1, /* in: type struct to copy to */ + const dtype_t* type2) /* in: type struct to copy from */ +{ + *type1 = *type2; + + ut_ad(dtype_validate(type1)); +} + +/************************************************************************* +Gets the SQL main data type. */ +UNIV_INLINE +ulint +dtype_get_mtype( +/*============*/ + const dtype_t* type) +{ + ut_ad(type); + + return(type->mtype); +} + +/************************************************************************* +Gets the precise data type. */ +UNIV_INLINE +ulint +dtype_get_prtype( +/*=============*/ + const dtype_t* type) +{ + ut_ad(type); + + return(type->prtype); +} + +/************************************************************************* +Gets the type length. */ +UNIV_INLINE +ulint +dtype_get_len( +/*==========*/ + const dtype_t* type) +{ + ut_ad(type); + + return(type->len); +} + +/************************************************************************* +Gets the minimum length of a character, in bytes. */ +UNIV_INLINE +ulint +dtype_get_mbminlen( +/*===============*/ + /* out: minimum length of a char, in bytes, + or 0 if this is not a character type */ + const dtype_t* type) /* in: type */ +{ + ut_ad(type); + return(type->mbminlen); +} +/************************************************************************* +Gets the maximum length of a character, in bytes. */ +UNIV_INLINE +ulint +dtype_get_mbmaxlen( +/*===============*/ + /* out: maximum length of a char, in bytes, + or 0 if this is not a character type */ + const dtype_t* type) /* in: type */ +{ + ut_ad(type); + return(type->mbmaxlen); +} + +/************************************************************************* +Gets the padding character code for a type. */ +UNIV_INLINE +ulint +dtype_get_pad_char( +/*===============*/ + /* out: padding character code, or + ULINT_UNDEFINED if no padding specified */ + ulint mtype, /* in: main type */ + ulint prtype) /* in: precise type */ +{ + switch (mtype) { + case DATA_FIXBINARY: + case DATA_BINARY: + if (UNIV_UNLIKELY(dtype_get_charset_coll(prtype) + == DATA_MYSQL_BINARY_CHARSET_COLL)) { + /* Starting from 5.0.18, do not pad + VARBINARY or BINARY columns. */ + return(ULINT_UNDEFINED); + } + /* Fall through */ + case DATA_CHAR: + case DATA_VARCHAR: + case DATA_MYSQL: + case DATA_VARMYSQL: + /* Space is the padding character for all char and binary + strings, and starting from 5.0.3, also for TEXT strings. */ + + return(0x20); + case DATA_BLOB: + if (!(prtype & DATA_BINARY_TYPE)) { + return(0x20); + } + /* Fall through */ + default: + /* No padding specified */ + return(ULINT_UNDEFINED); + } +} + +/************************************************************************** +Stores for a type the information which determines its alphabetical ordering +and the storage size of an SQL NULL value. This is the >= 4.1.x storage +format. */ +UNIV_INLINE +void +dtype_new_store_for_order_and_null_size( +/*====================================*/ + byte* buf, /* in: buffer for + DATA_NEW_ORDER_NULL_TYPE_BUF_SIZE + bytes where we store the info */ + const dtype_t* type, /* in: type struct */ + ulint prefix_len)/* in: prefix length to + replace type->len, or 0 */ +{ +#if 6 != DATA_NEW_ORDER_NULL_TYPE_BUF_SIZE +#error "6 != DATA_NEW_ORDER_NULL_TYPE_BUF_SIZE" +#endif + ulint len; + + buf[0] = (byte)(type->mtype & 0xFFUL); + + if (type->prtype & DATA_BINARY_TYPE) { + buf[0] = buf[0] | 128; + } + + /* In versions < 4.1.2 we had: if (type->prtype & DATA_NONLATIN1) { + buf[0] = buf[0] | 64; + } + */ + + buf[1] = (byte)(type->prtype & 0xFFUL); + + len = prefix_len ? prefix_len : type->len; + + mach_write_to_2(buf + 2, len & 0xFFFFUL); + + ut_ad(dtype_get_charset_coll(type->prtype) < 256); + mach_write_to_2(buf + 4, dtype_get_charset_coll(type->prtype)); + + if (type->prtype & DATA_NOT_NULL) { + buf[4] |= 128; + } +} + +/************************************************************************** +Reads to a type the stored information which determines its alphabetical +ordering and the storage size of an SQL NULL value. This is the < 4.1.x +storage format. */ +UNIV_INLINE +void +dtype_read_for_order_and_null_size( +/*===============================*/ + dtype_t* type, /* in: type struct */ + const byte* buf) /* in: buffer for stored type order info */ +{ +#if 4 != DATA_ORDER_NULL_TYPE_BUF_SIZE +# error "4 != DATA_ORDER_NULL_TYPE_BUF_SIZE" +#endif + + type->mtype = buf[0] & 63; + type->prtype = buf[1]; + + if (buf[0] & 128) { + type->prtype = type->prtype | DATA_BINARY_TYPE; + } + + type->len = mach_read_from_2(buf + 2); + + type->prtype = dtype_form_prtype(type->prtype, + data_mysql_default_charset_coll); + dtype_set_mblen(type); +} + +/************************************************************************** +Reads to a type the stored information which determines its alphabetical +ordering and the storage size of an SQL NULL value. This is the >= 4.1.x +storage format. */ +UNIV_INLINE +void +dtype_new_read_for_order_and_null_size( +/*===================================*/ + dtype_t* type, /* in: type struct */ + const byte* buf) /* in: buffer for stored type order info */ +{ + ulint charset_coll; + +#if 6 != DATA_NEW_ORDER_NULL_TYPE_BUF_SIZE +#error "6 != DATA_NEW_ORDER_NULL_TYPE_BUF_SIZE" +#endif + + type->mtype = buf[0] & 63; + type->prtype = buf[1]; + + if (buf[0] & 128) { + type->prtype |= DATA_BINARY_TYPE; + } + + if (buf[4] & 128) { + type->prtype |= DATA_NOT_NULL; + } + + type->len = mach_read_from_2(buf + 2); + + charset_coll = mach_read_from_2(buf + 4) & 0x7fff; + + if (dtype_is_string_type(type->mtype)) { + ut_a(charset_coll < 256); + + if (charset_coll == 0) { + /* This insert buffer record was inserted with MySQL + version < 4.1.2, and the charset-collation code was not + explicitly stored to dtype->prtype at that time. It + must be the default charset-collation of this MySQL + installation. */ + + charset_coll = data_mysql_default_charset_coll; + } + + type->prtype = dtype_form_prtype(type->prtype, charset_coll); + } + dtype_set_mblen(type); +} + +/*************************************************************************** +Returns the size of a fixed size data type, 0 if not a fixed size type. */ +UNIV_INLINE +ulint +dtype_get_fixed_size_low( +/*=====================*/ + /* out: fixed size, or 0 */ + ulint mtype, /* in: main type */ + ulint prtype, /* in: precise type */ + ulint len, /* in: length */ + ulint mbminlen, /* in: minimum length of a multibyte char */ + ulint mbmaxlen) /* in: maximum length of a multibyte char */ +{ + switch (mtype) { + case DATA_SYS: +#ifdef UNIV_DEBUG + switch (prtype & DATA_MYSQL_TYPE_MASK) { + case DATA_ROW_ID: + ut_ad(len == DATA_ROW_ID_LEN); + break; + case DATA_TRX_ID: + ut_ad(len == DATA_TRX_ID_LEN); + break; + case DATA_ROLL_PTR: + ut_ad(len == DATA_ROLL_PTR_LEN); + break; + default: + ut_ad(0); + return(0); + } +#endif /* UNIV_DEBUG */ + case DATA_CHAR: + case DATA_FIXBINARY: + case DATA_INT: + case DATA_FLOAT: + case DATA_DOUBLE: + return(len); + case DATA_MYSQL: + if (prtype & DATA_BINARY_TYPE) { + return(len); + } else { +#ifdef UNIV_HOTBACKUP + if (mbminlen == mbmaxlen) { + return(len); + } +#else /* UNIV_HOTBACKUP */ + /* We play it safe here and ask MySQL for + mbminlen and mbmaxlen. Although + mbminlen and mbmaxlen are + initialized if and only if prtype + is (in one of the 3 functions in this file), + it could be that none of these functions + has been called. */ + + ulint i_mbminlen, i_mbmaxlen; + + innobase_get_cset_width( + dtype_get_charset_coll(prtype), + &i_mbminlen, &i_mbmaxlen); + + if (UNIV_UNLIKELY(mbminlen != i_mbminlen) + || UNIV_UNLIKELY(mbmaxlen != i_mbmaxlen)) { + + ut_print_timestamp(stderr); + fprintf(stderr, " InnoDB: " + "mbminlen=%lu, " + "mbmaxlen=%lu, " + "type->mbminlen=%lu, " + "type->mbmaxlen=%lu\n", + (ulong) i_mbminlen, + (ulong) i_mbmaxlen, + (ulong) mbminlen, + (ulong) mbmaxlen); + } + if (mbminlen == mbmaxlen) { + return(len); + } +#endif /* !UNIV_HOTBACKUP */ + } + /* fall through for variable-length charsets */ + case DATA_VARCHAR: + case DATA_BINARY: + case DATA_DECIMAL: + case DATA_VARMYSQL: + case DATA_BLOB: + return(0); + default: + ut_error; + } + + return(0); +} + +/*************************************************************************** +Returns the minimum size of a data type. */ +UNIV_INLINE +ulint +dtype_get_min_size_low( +/*===================*/ + /* out: minimum size */ + ulint mtype, /* in: main type */ + ulint prtype, /* in: precise type */ + ulint len, /* in: length */ + ulint mbminlen, /* in: minimum length of a multibyte char */ + ulint mbmaxlen) /* in: maximum length of a multibyte char */ +{ + switch (mtype) { + case DATA_SYS: +#ifdef UNIV_DEBUG + switch (prtype & DATA_MYSQL_TYPE_MASK) { + case DATA_ROW_ID: + ut_ad(len == DATA_ROW_ID_LEN); + break; + case DATA_TRX_ID: + ut_ad(len == DATA_TRX_ID_LEN); + break; + case DATA_ROLL_PTR: + ut_ad(len == DATA_ROLL_PTR_LEN); + break; + default: + ut_ad(0); + return(0); + } +#endif /* UNIV_DEBUG */ + case DATA_CHAR: + case DATA_FIXBINARY: + case DATA_INT: + case DATA_FLOAT: + case DATA_DOUBLE: + return(len); + case DATA_MYSQL: + if ((prtype & DATA_BINARY_TYPE) || mbminlen == mbmaxlen) { + return(len); + } + /* this is a variable-length character set */ + ut_a(mbminlen > 0); + ut_a(mbmaxlen > mbminlen); + ut_a(len % mbmaxlen == 0); + return(len * mbminlen / mbmaxlen); + case DATA_VARCHAR: + case DATA_BINARY: + case DATA_DECIMAL: + case DATA_VARMYSQL: + case DATA_BLOB: + return(0); + default: + ut_error; + } + + return(0); +} + +/*************************************************************************** +Returns the maximum size of a data type. Note: types in system tables may be +incomplete and return incorrect information. */ +UNIV_INLINE +ulint +dtype_get_max_size_low( +/*===================*/ + /* out: maximum size */ + ulint mtype, /* in: main type */ + ulint len) /* in: length */ +{ + switch (mtype) { + case DATA_SYS: + case DATA_CHAR: + case DATA_FIXBINARY: + case DATA_INT: + case DATA_FLOAT: + case DATA_DOUBLE: + case DATA_MYSQL: + case DATA_VARCHAR: + case DATA_BINARY: + case DATA_DECIMAL: + case DATA_VARMYSQL: + return(len); + case DATA_BLOB: + break; + default: + ut_error; + } + + return(ULINT_MAX); +} + +/*************************************************************************** +Returns the ROW_FORMAT=REDUNDANT stored SQL NULL size of a type. +For fixed length types it is the fixed length of the type, otherwise 0. */ +UNIV_INLINE +ulint +dtype_get_sql_null_size( +/*====================*/ + /* out: SQL null storage size + in ROW_FORMAT=REDUNDANT */ + const dtype_t* type) /* in: type */ +{ + return(dtype_get_fixed_size_low(type->mtype, type->prtype, type->len, + type->mbminlen, type->mbmaxlen)); +} diff --git a/storage/xtradb/include/data0types.h b/storage/xtradb/include/data0types.h new file mode 100644 index 00000000000..9e536478d68 --- /dev/null +++ b/storage/xtradb/include/data0types.h @@ -0,0 +1,35 @@ +/***************************************************************************** + +Copyright (c) 2000, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/************************************************************************ +Some type definitions + +Created 9/21/2000 Heikki Tuuri +*************************************************************************/ + +#ifndef data0types_h +#define data0types_h + +/* SQL data field struct */ +typedef struct dfield_struct dfield_t; + +/* SQL data tuple struct */ +typedef struct dtuple_struct dtuple_t; + +#endif + diff --git a/storage/xtradb/include/db0err.h b/storage/xtradb/include/db0err.h new file mode 100644 index 00000000000..d6d2a9785a5 --- /dev/null +++ b/storage/xtradb/include/db0err.h @@ -0,0 +1,104 @@ +/***************************************************************************** + +Copyright (c) 1996, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/****************************************************** +Global error codes for the database + +Created 5/24/1996 Heikki Tuuri +*******************************************************/ + +#ifndef db0err_h +#define db0err_h + + +enum db_err { + DB_SUCCESS = 10, + + /* The following are error codes */ + DB_ERROR, + DB_OUT_OF_MEMORY, + DB_OUT_OF_FILE_SPACE, + DB_LOCK_WAIT, + DB_DEADLOCK, + DB_ROLLBACK, + DB_DUPLICATE_KEY, + DB_QUE_THR_SUSPENDED, + DB_MISSING_HISTORY, /* required history data has been + deleted due to lack of space in + rollback segment */ + DB_CLUSTER_NOT_FOUND = 30, + DB_TABLE_NOT_FOUND, + DB_MUST_GET_MORE_FILE_SPACE, /* the database has to be stopped + and restarted with more file space */ + DB_TABLE_IS_BEING_USED, + DB_TOO_BIG_RECORD, /* a record in an index would not fit + on a compressed page, or it would + become bigger than 1/2 free space in + an uncompressed page frame */ + DB_LOCK_WAIT_TIMEOUT, /* lock wait lasted too long */ + DB_NO_REFERENCED_ROW, /* referenced key value not found + for a foreign key in an insert or + update of a row */ + DB_ROW_IS_REFERENCED, /* cannot delete or update a row + because it contains a key value + which is referenced */ + DB_CANNOT_ADD_CONSTRAINT, /* adding a foreign key constraint + to a table failed */ + DB_CORRUPTION, /* data structure corruption noticed */ + DB_COL_APPEARS_TWICE_IN_INDEX, /* InnoDB cannot handle an index + where same column appears twice */ + DB_CANNOT_DROP_CONSTRAINT, /* dropping a foreign key constraint + from a table failed */ + DB_NO_SAVEPOINT, /* no savepoint exists with the given + name */ + DB_TABLESPACE_ALREADY_EXISTS, /* we cannot create a new single-table + tablespace because a file of the same + name already exists */ + DB_TABLESPACE_DELETED, /* tablespace does not exist or is + being dropped right now */ + DB_LOCK_TABLE_FULL, /* lock structs have exhausted the + buffer pool (for big transactions, + InnoDB stores the lock structs in the + buffer pool) */ + DB_FOREIGN_DUPLICATE_KEY, /* foreign key constraints + activated by the operation would + lead to a duplicate key in some + table */ + DB_TOO_MANY_CONCURRENT_TRXS, /* when InnoDB runs out of the + preconfigured undo slots, this can + only happen when there are too many + concurrent transactions */ + DB_UNSUPPORTED, /* when InnoDB sees any artefact or + a feature that it can't recoginize or + work with e.g., FT indexes created by + a later version of the engine. */ + + DB_PRIMARY_KEY_IS_NULL, /* a column in the PRIMARY KEY + was found to be NULL */ + + /* The following are partial failure codes */ + DB_FAIL = 1000, + DB_OVERFLOW, + DB_UNDERFLOW, + DB_STRONG_FAIL, + DB_ZIP_OVERFLOW, + DB_RECORD_NOT_FOUND = 1500, + DB_END_OF_INDEX +}; + +#endif diff --git a/storage/xtradb/include/dict0boot.h b/storage/xtradb/include/dict0boot.h new file mode 100644 index 00000000000..e1556bdb16e --- /dev/null +++ b/storage/xtradb/include/dict0boot.h @@ -0,0 +1,150 @@ +/***************************************************************************** + +Copyright (c) 1996, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/****************************************************** +Data dictionary creation and booting + +Created 4/18/1996 Heikki Tuuri +*******************************************************/ + +#ifndef dict0boot_h +#define dict0boot_h + +#include "univ.i" + +#include "mtr0mtr.h" +#include "mtr0log.h" +#include "ut0byte.h" +#include "buf0buf.h" +#include "fsp0fsp.h" +#include "dict0dict.h" + +typedef byte dict_hdr_t; + +/************************************************************************** +Gets a pointer to the dictionary header and x-latches its page. */ +UNIV_INTERN +dict_hdr_t* +dict_hdr_get( +/*=========*/ + /* out: pointer to the dictionary header, + page x-latched */ + mtr_t* mtr); /* in: mtr */ +/************************************************************************** +Returns a new row, table, index, or tree id. */ +UNIV_INTERN +dulint +dict_hdr_get_new_id( +/*================*/ + /* out: the new id */ + ulint type); /* in: DICT_HDR_ROW_ID, ... */ +/************************************************************************** +Returns a new row id. */ +UNIV_INLINE +dulint +dict_sys_get_new_row_id(void); +/*=========================*/ + /* out: the new id */ +/************************************************************************** +Reads a row id from a record or other 6-byte stored form. */ +UNIV_INLINE +dulint +dict_sys_read_row_id( +/*=================*/ + /* out: row id */ + byte* field); /* in: record field */ +/************************************************************************** +Writes a row id to a record or other 6-byte stored form. */ +UNIV_INLINE +void +dict_sys_write_row_id( +/*==================*/ + byte* field, /* in: record field */ + dulint row_id);/* in: row id */ +/********************************************************************* +Initializes the data dictionary memory structures when the database is +started. This function is also called when the data dictionary is created. */ +UNIV_INTERN +void +dict_boot(void); +/*===========*/ +/********************************************************************* +Creates and initializes the data dictionary at the database creation. */ +UNIV_INTERN +void +dict_create(void); +/*=============*/ + + +/* Space id and page no where the dictionary header resides */ +#define DICT_HDR_SPACE 0 /* the SYSTEM tablespace */ +#define DICT_HDR_PAGE_NO FSP_DICT_HDR_PAGE_NO + +/* The ids for the basic system tables and their indexes */ +#define DICT_TABLES_ID ut_dulint_create(0, 1) +#define DICT_COLUMNS_ID ut_dulint_create(0, 2) +#define DICT_INDEXES_ID ut_dulint_create(0, 3) +#define DICT_FIELDS_ID ut_dulint_create(0, 4) +/* The following is a secondary index on SYS_TABLES */ +#define DICT_TABLE_IDS_ID ut_dulint_create(0, 5) + +#define DICT_HDR_FIRST_ID 10 /* the ids for tables etc. start + from this number, except for basic + system tables and their above defined + indexes; ibuf tables and indexes are + assigned as the id the number + DICT_IBUF_ID_MIN plus the space id */ +#define DICT_IBUF_ID_MIN ut_dulint_create(0xFFFFFFFFUL, 0) + +/* The offset of the dictionary header on the page */ +#define DICT_HDR FSEG_PAGE_DATA + +/*-------------------------------------------------------------*/ +/* Dictionary header offsets */ +#define DICT_HDR_ROW_ID 0 /* The latest assigned row id */ +#define DICT_HDR_TABLE_ID 8 /* The latest assigned table id */ +#define DICT_HDR_INDEX_ID 16 /* The latest assigned index id */ +#define DICT_HDR_MIX_ID 24 /* Obsolete, always 0. */ +#define DICT_HDR_TABLES 32 /* Root of the table index tree */ +#define DICT_HDR_TABLE_IDS 36 /* Root of the table index tree */ +#define DICT_HDR_COLUMNS 40 /* Root of the column index tree */ +#define DICT_HDR_INDEXES 44 /* Root of the index index tree */ +#define DICT_HDR_FIELDS 48 /* Root of the index field + index tree */ + +#define DICT_HDR_FSEG_HEADER 56 /* Segment header for the tablespace + segment into which the dictionary + header is created */ +/*-------------------------------------------------------------*/ + +/* The field number of the page number field in the sys_indexes table +clustered index */ +#define DICT_SYS_INDEXES_PAGE_NO_FIELD 8 +#define DICT_SYS_INDEXES_SPACE_NO_FIELD 7 +#define DICT_SYS_INDEXES_TYPE_FIELD 6 + +/* When a row id which is zero modulo this number (which must be a power of +two) is assigned, the field DICT_HDR_ROW_ID on the dictionary header page is +updated */ +#define DICT_HDR_ROW_ID_WRITE_MARGIN 256 + +#ifndef UNIV_NONINL +#include "dict0boot.ic" +#endif + +#endif diff --git a/storage/xtradb/include/dict0boot.ic b/storage/xtradb/include/dict0boot.ic new file mode 100644 index 00000000000..9b45f9e84be --- /dev/null +++ b/storage/xtradb/include/dict0boot.ic @@ -0,0 +1,92 @@ +/***************************************************************************** + +Copyright (c) 1996, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/****************************************************** +Data dictionary creation and booting + +Created 4/18/1996 Heikki Tuuri +*******************************************************/ + +/************************************************************************** +Writes the current value of the row id counter to the dictionary header file +page. */ +UNIV_INTERN +void +dict_hdr_flush_row_id(void); +/*=======================*/ + + +/************************************************************************** +Returns a new row id. */ +UNIV_INLINE +dulint +dict_sys_get_new_row_id(void) +/*=========================*/ + /* out: the new id */ +{ + dulint id; + + mutex_enter(&(dict_sys->mutex)); + + id = dict_sys->row_id; + + if (0 == (ut_dulint_get_low(id) % DICT_HDR_ROW_ID_WRITE_MARGIN)) { + + dict_hdr_flush_row_id(); + } + + UT_DULINT_INC(dict_sys->row_id); + + mutex_exit(&(dict_sys->mutex)); + + return(id); +} + +/************************************************************************** +Reads a row id from a record or other 6-byte stored form. */ +UNIV_INLINE +dulint +dict_sys_read_row_id( +/*=================*/ + /* out: row id */ + byte* field) /* in: record field */ +{ +#if DATA_ROW_ID_LEN != 6 +# error "DATA_ROW_ID_LEN != 6" +#endif + + return(mach_read_from_6(field)); +} + +/************************************************************************** +Writes a row id to a record or other 6-byte stored form. */ +UNIV_INLINE +void +dict_sys_write_row_id( +/*==================*/ + byte* field, /* in: record field */ + dulint row_id) /* in: row id */ +{ +#if DATA_ROW_ID_LEN != 6 +# error "DATA_ROW_ID_LEN != 6" +#endif + + mach_write_to_6(field, row_id); +} + + diff --git a/storage/xtradb/include/dict0crea.h b/storage/xtradb/include/dict0crea.h new file mode 100644 index 00000000000..9ac3e408f1f --- /dev/null +++ b/storage/xtradb/include/dict0crea.h @@ -0,0 +1,199 @@ +/***************************************************************************** + +Copyright (c) 1996, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/****************************************************** +Database object creation + +Created 1/8/1996 Heikki Tuuri +*******************************************************/ + +#ifndef dict0crea_h +#define dict0crea_h + +#include "univ.i" +#include "dict0types.h" +#include "dict0dict.h" +#include "que0types.h" +#include "row0types.h" +#include "mtr0mtr.h" + +/************************************************************************* +Creates a table create graph. */ +UNIV_INTERN +tab_node_t* +tab_create_graph_create( +/*====================*/ + /* out, own: table create node */ + dict_table_t* table, /* in: table to create, built as a memory data + structure */ + mem_heap_t* heap); /* in: heap where created */ +/************************************************************************* +Creates an index create graph. */ +UNIV_INTERN +ind_node_t* +ind_create_graph_create( +/*====================*/ + /* out, own: index create node */ + dict_index_t* index, /* in: index to create, built as a memory data + structure */ + mem_heap_t* heap); /* in: heap where created */ +/*************************************************************** +Creates a table. This is a high-level function used in SQL execution graphs. */ +UNIV_INTERN +que_thr_t* +dict_create_table_step( +/*===================*/ + /* out: query thread to run next or NULL */ + que_thr_t* thr); /* in: query thread */ +/*************************************************************** +Creates an index. This is a high-level function used in SQL execution +graphs. */ +UNIV_INTERN +que_thr_t* +dict_create_index_step( +/*===================*/ + /* out: query thread to run next or NULL */ + que_thr_t* thr); /* in: query thread */ +/*********************************************************************** +Truncates the index tree associated with a row in SYS_INDEXES table. */ +UNIV_INTERN +ulint +dict_truncate_index_tree( +/*=====================*/ + /* out: new root page number, or + FIL_NULL on failure */ + dict_table_t* table, /* in: the table the index belongs to */ + ulint space, /* in: 0=truncate, + nonzero=create the index tree in the + given tablespace */ + btr_pcur_t* pcur, /* in/out: persistent cursor pointing to + record in the clustered index of + SYS_INDEXES table. The cursor may be + repositioned in this call. */ + mtr_t* mtr); /* in: mtr having the latch + on the record page. The mtr may be + committed and restarted in this call. */ +/*********************************************************************** +Drops the index tree associated with a row in SYS_INDEXES table. */ +UNIV_INTERN +void +dict_drop_index_tree( +/*=================*/ + rec_t* rec, /* in/out: record in the clustered index + of SYS_INDEXES table */ + mtr_t* mtr); /* in: mtr having the latch on the record page */ +#ifndef UNIV_HOTBACKUP +/******************************************************************** +Creates the foreign key constraints system tables inside InnoDB +at database creation or database start if they are not found or are +not of the right form. */ +UNIV_INTERN +ulint +dict_create_or_check_foreign_constraint_tables(void); +/*================================================*/ + /* out: DB_SUCCESS or error code */ +/************************************************************************ +Adds foreign key definitions to data dictionary tables in the database. We +look at table->foreign_list, and also generate names to constraints that were +not named by the user. A generated constraint has a name of the format +databasename/tablename_ibfk_<number>, where the numbers start from 1, and are +given locally for this table, that is, the number is not global, as in the +old format constraints < 4.0.18 it used to be. */ +UNIV_INTERN +ulint +dict_create_add_foreigns_to_dictionary( +/*===================================*/ + /* out: error code or DB_SUCCESS */ + ulint start_id,/* in: if we are actually doing ALTER TABLE + ADD CONSTRAINT, we want to generate constraint + numbers which are bigger than in the table so + far; we number the constraints from + start_id + 1 up; start_id should be set to 0 if + we are creating a new table, or if the table + so far has no constraints for which the name + was generated here */ + dict_table_t* table, /* in: table */ + trx_t* trx); /* in: transaction */ +#endif /* !UNIV_HOTBACKUP */ + +/* Table create node structure */ + +struct tab_node_struct{ + que_common_t common; /* node type: QUE_NODE_TABLE_CREATE */ + dict_table_t* table; /* table to create, built as a memory data + structure with dict_mem_... functions */ + ins_node_t* tab_def; /* child node which does the insert of + the table definition; the row to be inserted + is built by the parent node */ + ins_node_t* col_def; /* child node which does the inserts of + the column definitions; the row to be inserted + is built by the parent node */ + commit_node_t* commit_node; + /* child node which performs a commit after + a successful table creation */ + /*----------------------*/ + /* Local storage for this graph node */ + ulint state; /* node execution state */ + ulint col_no; /* next column definition to insert */ + mem_heap_t* heap; /* memory heap used as auxiliary storage */ +}; + +/* Table create node states */ +#define TABLE_BUILD_TABLE_DEF 1 +#define TABLE_BUILD_COL_DEF 2 +#define TABLE_COMMIT_WORK 3 +#define TABLE_ADD_TO_CACHE 4 +#define TABLE_COMPLETED 5 + +/* Index create node struct */ + +struct ind_node_struct{ + que_common_t common; /* node type: QUE_NODE_INDEX_CREATE */ + dict_index_t* index; /* index to create, built as a memory data + structure with dict_mem_... functions */ + ins_node_t* ind_def; /* child node which does the insert of + the index definition; the row to be inserted + is built by the parent node */ + ins_node_t* field_def; /* child node which does the inserts of + the field definitions; the row to be inserted + is built by the parent node */ + commit_node_t* commit_node; + /* child node which performs a commit after + a successful index creation */ + /*----------------------*/ + /* Local storage for this graph node */ + ulint state; /* node execution state */ + ulint page_no;/* root page number of the index */ + dict_table_t* table; /* table which owns the index */ + dtuple_t* ind_row;/* index definition row built */ + ulint field_no;/* next field definition to insert */ + mem_heap_t* heap; /* memory heap used as auxiliary storage */ +}; + +/* Index create node states */ +#define INDEX_BUILD_INDEX_DEF 1 +#define INDEX_BUILD_FIELD_DEF 2 +#define INDEX_CREATE_INDEX_TREE 3 +#define INDEX_COMMIT_WORK 4 +#define INDEX_ADD_TO_CACHE 5 + +#ifndef UNIV_NONINL +#include "dict0crea.ic" +#endif + +#endif diff --git a/storage/xtradb/include/dict0crea.ic b/storage/xtradb/include/dict0crea.ic new file mode 100644 index 00000000000..b05385fa121 --- /dev/null +++ b/storage/xtradb/include/dict0crea.ic @@ -0,0 +1,24 @@ +/***************************************************************************** + +Copyright (c) 1996, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/****************************************************** +Database object creation + +Created 1/8/1996 Heikki Tuuri +*******************************************************/ + diff --git a/storage/xtradb/include/dict0dict.h b/storage/xtradb/include/dict0dict.h new file mode 100644 index 00000000000..7c7b37b7681 --- /dev/null +++ b/storage/xtradb/include/dict0dict.h @@ -0,0 +1,1153 @@ +/***************************************************************************** + +Copyright (c) 1996, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/****************************************************** +Data dictionary system + +Created 1/8/1996 Heikki Tuuri +*******************************************************/ + +#ifndef dict0dict_h +#define dict0dict_h + +#include "univ.i" +#include "dict0types.h" +#include "dict0mem.h" +#include "data0type.h" +#include "data0data.h" +#include "sync0sync.h" +#include "sync0rw.h" +#include "mem0mem.h" +#include "rem0types.h" +#include "ut0mem.h" +#include "ut0lst.h" +#include "hash0hash.h" +#include "ut0rnd.h" +#include "ut0byte.h" +#include "trx0types.h" + +#ifndef UNIV_HOTBACKUP +/********************************************************************** +Makes all characters in a NUL-terminated UTF-8 string lower case. */ +UNIV_INTERN +void +dict_casedn_str( +/*============*/ + char* a); /* in/out: string to put in lower case */ +#endif /* !UNIV_HOTBACKUP */ +/************************************************************************ +Get the database name length in a table name. */ +UNIV_INTERN +ulint +dict_get_db_name_len( +/*=================*/ + /* out: database name length */ + const char* name); /* in: table name in the form + dbname '/' tablename */ +/************************************************************************ +Return the end of table name where we have removed dbname and '/'. */ + +const char* +dict_remove_db_name( +/*================*/ + /* out: table name */ + const char* name); /* in: table name in the form + dbname '/' tablename */ +/************************************************************************** +Returns a table object based on table id. */ +UNIV_INTERN +dict_table_t* +dict_table_get_on_id( +/*=================*/ + /* out: table, NULL if does not exist */ + dulint table_id, /* in: table id */ + trx_t* trx); /* in: transaction handle */ +/************************************************************************ +Decrements the count of open MySQL handles to a table. */ +UNIV_INTERN +void +dict_table_decrement_handle_count( +/*==============================*/ + dict_table_t* table, /* in/out: table */ + ibool dict_locked); /* in: TRUE=data dictionary locked */ +/************************************************************************** +Inits the data dictionary module. */ +UNIV_INTERN +void +dict_init(void); +/*===========*/ +/************************************************************************ +Gets the space id of every table of the data dictionary and makes a linear +list and a hash table of them to the data dictionary cache. This function +can be called at database startup if we did not need to do a crash recovery. +In crash recovery we must scan the space id's from the .ibd files in MySQL +database directories. */ +UNIV_INTERN +void +dict_load_space_id_list(void); +/*=========================*/ +/************************************************************************* +Gets the column data type. */ +UNIV_INLINE +void +dict_col_copy_type( +/*===============*/ + const dict_col_t* col, /* in: column */ + dtype_t* type); /* out: data type */ +#ifdef UNIV_DEBUG +/************************************************************************* +Assert that a column and a data type match. */ +UNIV_INLINE +ibool +dict_col_type_assert_equal( +/*=======================*/ + /* out: TRUE */ + const dict_col_t* col, /* in: column */ + const dtype_t* type); /* in: data type */ +#endif /* UNIV_DEBUG */ +/*************************************************************************** +Returns the minimum size of the column. */ +UNIV_INLINE +ulint +dict_col_get_min_size( +/*==================*/ + /* out: minimum size */ + const dict_col_t* col); /* in: column */ +/*************************************************************************** +Returns the maximum size of the column. */ +UNIV_INLINE +ulint +dict_col_get_max_size( +/*==================*/ + /* out: maximum size */ + const dict_col_t* col); /* in: column */ +/*************************************************************************** +Returns the size of a fixed size column, 0 if not a fixed size column. */ +UNIV_INLINE +ulint +dict_col_get_fixed_size( +/*====================*/ + /* out: fixed size, or 0 */ + const dict_col_t* col); /* in: column */ +/*************************************************************************** +Returns the ROW_FORMAT=REDUNDANT stored SQL NULL size of a column. +For fixed length types it is the fixed length of the type, otherwise 0. */ +UNIV_INLINE +ulint +dict_col_get_sql_null_size( +/*=======================*/ + /* out: SQL null storage size + in ROW_FORMAT=REDUNDANT */ + const dict_col_t* col); /* in: column */ + +/************************************************************************* +Gets the column number. */ +UNIV_INLINE +ulint +dict_col_get_no( +/*============*/ + const dict_col_t* col); +/************************************************************************* +Gets the column position in the clustered index. */ +UNIV_INLINE +ulint +dict_col_get_clust_pos( +/*===================*/ + const dict_col_t* col, /* in: table column */ + const dict_index_t* clust_index); /* in: clustered index */ +/******************************************************************** +If the given column name is reserved for InnoDB system columns, return +TRUE. */ +UNIV_INTERN +ibool +dict_col_name_is_reserved( +/*======================*/ + /* out: TRUE if name is reserved */ + const char* name); /* in: column name */ +/************************************************************************ +Acquire the autoinc lock.*/ +UNIV_INTERN +void +dict_table_autoinc_lock( +/*====================*/ + dict_table_t* table); /* in/out: table */ +/************************************************************************ +Unconditionally set the autoinc counter. */ +UNIV_INTERN +void +dict_table_autoinc_initialize( +/*==========================*/ + dict_table_t* table, /* in/out: table */ + ib_uint64_t value); /* in: next value to assign to a row */ +/************************************************************************ +Reads the next autoinc value (== autoinc counter value), 0 if not yet +initialized. */ +UNIV_INTERN +ib_uint64_t +dict_table_autoinc_read( +/*====================*/ + /* out: value for a new row, or 0 */ + const dict_table_t* table); /* in: table */ +/************************************************************************ +Updates the autoinc counter if the value supplied is greater than the +current value. */ +UNIV_INTERN +void +dict_table_autoinc_update_if_greater( +/*=================================*/ + + dict_table_t* table, /* in/out: table */ + ib_uint64_t value); /* in: value which was assigned to a row */ +/************************************************************************ +Release the autoinc lock.*/ +UNIV_INTERN +void +dict_table_autoinc_unlock( +/*======================*/ + dict_table_t* table); /* in/out: table */ +/************************************************************************** +Adds system columns to a table object. */ +UNIV_INTERN +void +dict_table_add_system_columns( +/*==========================*/ + dict_table_t* table, /* in/out: table */ + mem_heap_t* heap); /* in: temporary heap */ +/************************************************************************** +Adds a table object to the dictionary cache. */ +UNIV_INTERN +void +dict_table_add_to_cache( +/*====================*/ + dict_table_t* table, /* in: table */ + mem_heap_t* heap); /* in: temporary heap */ +/************************************************************************** +Removes a table object from the dictionary cache. */ +UNIV_INTERN +void +dict_table_remove_from_cache( +/*=========================*/ + dict_table_t* table); /* in, own: table */ +/************************************************************************** +Renames a table object. */ +UNIV_INTERN +ibool +dict_table_rename_in_cache( +/*=======================*/ + /* out: TRUE if success */ + dict_table_t* table, /* in/out: table */ + const char* new_name, /* in: new name */ + ibool rename_also_foreigns);/* in: in ALTER TABLE we want + to preserve the original table name + in constraints which reference it */ +/************************************************************************** +Removes an index from the dictionary cache. */ +UNIV_INTERN +void +dict_index_remove_from_cache( +/*=========================*/ + dict_table_t* table, /* in/out: table */ + dict_index_t* index); /* in, own: index */ +/************************************************************************** +Change the id of a table object in the dictionary cache. This is used in +DISCARD TABLESPACE. */ +UNIV_INTERN +void +dict_table_change_id_in_cache( +/*==========================*/ + dict_table_t* table, /* in/out: table object already in cache */ + dulint new_id);/* in: new id to set */ +/************************************************************************** +Adds a foreign key constraint object to the dictionary cache. May free +the object if there already is an object with the same identifier in. +At least one of foreign table or referenced table must already be in +the dictionary cache! */ +UNIV_INTERN +ulint +dict_foreign_add_to_cache( +/*======================*/ + /* out: DB_SUCCESS or error code */ + dict_foreign_t* foreign, /* in, own: foreign key constraint */ + ibool check_charsets);/* in: TRUE=check charset + compatibility */ +/************************************************************************* +Check if the index is referenced by a foreign key, if TRUE return the +matching instance NULL otherwise. */ +UNIV_INTERN +dict_foreign_t* +dict_table_get_referenced_constraint( +/*=================================*/ + /* out: pointer to foreign key struct if index + is defined for foreign key, otherwise NULL */ + dict_table_t* table, /* in: InnoDB table */ + dict_index_t* index); /* in: InnoDB index */ +/************************************************************************* +Checks if a table is referenced by foreign keys. */ +UNIV_INTERN +ibool +dict_table_is_referenced_by_foreign_key( +/*====================================*/ + /* out: TRUE if table is referenced + by a foreign key */ + const dict_table_t* table); /* in: InnoDB table */ +/************************************************************************** +Replace the index in the foreign key list that matches this index's +definition with an equivalent index. */ +UNIV_INTERN +void +dict_table_replace_index_in_foreign_list( +/*=====================================*/ + dict_table_t* table, /* in/out: table */ + dict_index_t* index); /* in: index to be replaced */ +/************************************************************************* +Checks if a index is defined for a foreign key constraint. Index is a part +of a foreign key constraint if the index is referenced by foreign key +or index is a foreign key index */ +UNIV_INTERN +dict_foreign_t* +dict_table_get_foreign_constraint( +/*==============================*/ + /* out: pointer to foreign key struct if index + is defined for foreign key, otherwise NULL */ + dict_table_t* table, /* in: InnoDB table */ + dict_index_t* index); /* in: InnoDB index */ +/************************************************************************* +Scans a table create SQL string and adds to the data dictionary +the foreign key constraints declared in the string. This function +should be called after the indexes for a table have been created. +Each foreign key constraint must be accompanied with indexes in +bot participating tables. The indexes are allowed to contain more +fields than mentioned in the constraint. */ +UNIV_INTERN +ulint +dict_create_foreign_constraints( +/*============================*/ + /* out: error code or DB_SUCCESS */ + trx_t* trx, /* in: transaction */ + const char* sql_string, /* in: table create statement where + foreign keys are declared like: + FOREIGN KEY (a, b) REFERENCES + table2(c, d), table2 can be written + also with the database + name before it: test.table2; the + default database id the database of + parameter name */ + const char* name, /* in: table full name in the + normalized form + database_name/table_name */ + ibool reject_fks); /* in: if TRUE, fail with error + code DB_CANNOT_ADD_CONSTRAINT if + any foreign keys are found. */ +/************************************************************************** +Parses the CONSTRAINT id's to be dropped in an ALTER TABLE statement. */ +UNIV_INTERN +ulint +dict_foreign_parse_drop_constraints( +/*================================*/ + /* out: DB_SUCCESS or + DB_CANNOT_DROP_CONSTRAINT if + syntax error or the constraint + id does not match */ + mem_heap_t* heap, /* in: heap from which we can + allocate memory */ + trx_t* trx, /* in: transaction */ + dict_table_t* table, /* in: table */ + ulint* n, /* out: number of constraints + to drop */ + const char*** constraints_to_drop); /* out: id's of the + constraints to drop */ +/************************************************************************** +Returns a table object and optionally increment its MySQL open handle count. +NOTE! This is a high-level function to be used mainly from outside the +'dict' directory. Inside this directory dict_table_get_low is usually the +appropriate function. */ +UNIV_INTERN +dict_table_t* +dict_table_get( +/*===========*/ + /* out: table, NULL if + does not exist */ + const char* table_name, /* in: table name */ + ibool inc_mysql_count); + /* in: whether to increment the open + handle count on the table */ +/************************************************************************** +Returns a index object, based on table and index id, and memoryfixes it. */ +UNIV_INTERN +dict_index_t* +dict_index_get_on_id_low( +/*=====================*/ + /* out: index, NULL if does not + exist */ + dict_table_t* table, /* in: table */ + dulint index_id); /* in: index id */ +/************************************************************************** +Checks if a table is in the dictionary cache. */ + +UNIV_INLINE +dict_table_t* +dict_table_check_if_in_cache_low( +/*=============================*/ + /* out: table, NULL if not found */ + const char* table_name); /* in: table name */ +/************************************************************************** +Gets a table; loads it to the dictionary cache if necessary. A low-level +function. */ +UNIV_INLINE +dict_table_t* +dict_table_get_low( +/*===============*/ + /* out: table, NULL if not found */ + const char* table_name); /* in: table name */ +/************************************************************************** +Returns a table object based on table id. */ +UNIV_INLINE +dict_table_t* +dict_table_get_on_id_low( +/*=====================*/ + /* out: table, NULL if does not exist */ + dulint table_id); /* in: table id */ +/************************************************************************** +Find an index that is equivalent to the one passed in and is not marked +for deletion. */ +UNIV_INTERN +dict_index_t* +dict_foreign_find_equiv_index( +/*==========================*/ + /* out: index equivalent to + foreign->foreign_index, or NULL */ + dict_foreign_t* foreign);/* in: foreign key */ +/************************************************************************** +Returns an index object by matching on the name and column names and if +more than index is found return the index with the higher id.*/ +UNIV_INTERN +dict_index_t* +dict_table_get_index_by_max_id( +/*===========================*/ + /* out: matching index, NULL if not found */ + dict_table_t* table, /* in: table */ + const char* name, /* in: the index name to find */ + const char** columns,/* in: array of column names */ + ulint n_cols);/* in: number of columns */ +/************************************************************************** +Returns a column's name. */ + +const char* +dict_table_get_col_name( +/*====================*/ + /* out: column name. NOTE: not + guaranteed to stay valid if table is + modified in any way (columns added, + etc.). */ + const dict_table_t* table, /* in: table */ + ulint col_nr);/* in: column number */ + +/************************************************************************** +Prints a table definition. */ +UNIV_INTERN +void +dict_table_print( +/*=============*/ + dict_table_t* table); /* in: table */ +/************************************************************************** +Prints a table data. */ +UNIV_INTERN +void +dict_table_print_low( +/*=================*/ + dict_table_t* table); /* in: table */ +/************************************************************************** +Prints a table data when we know the table name. */ +UNIV_INTERN +void +dict_table_print_by_name( +/*=====================*/ + const char* name); +/************************************************************************** +Outputs info on foreign keys of a table. */ +UNIV_INTERN +void +dict_print_info_on_foreign_keys( +/*============================*/ + ibool create_table_format, /* in: if TRUE then print in + a format suitable to be inserted into + a CREATE TABLE, otherwise in the format + of SHOW TABLE STATUS */ + FILE* file, /* in: file where to print */ + trx_t* trx, /* in: transaction */ + dict_table_t* table); /* in: table */ +/************************************************************************** +Outputs info on a foreign key of a table in a format suitable for +CREATE TABLE. */ +UNIV_INTERN +void +dict_print_info_on_foreign_key_in_create_format( +/*============================================*/ + FILE* file, /* in: file where to print */ + trx_t* trx, /* in: transaction */ + dict_foreign_t* foreign, /* in: foreign key constraint */ + ibool add_newline); /* in: whether to add a newline */ +/************************************************************************ +Displays the names of the index and the table. */ +UNIV_INTERN +void +dict_index_name_print( +/*==================*/ + FILE* file, /* in: output stream */ + trx_t* trx, /* in: transaction */ + const dict_index_t* index); /* in: index to print */ +#ifdef UNIV_DEBUG +/************************************************************************ +Gets the first index on the table (the clustered index). */ +UNIV_INLINE +dict_index_t* +dict_table_get_first_index( +/*=======================*/ + /* out: index, NULL if none exists */ + const dict_table_t* table); /* in: table */ +/************************************************************************ +Gets the next index on the table. */ +UNIV_INLINE +dict_index_t* +dict_table_get_next_index( +/*======================*/ + /* out: index, NULL if none left */ + const dict_index_t* index); /* in: index */ +#else /* UNIV_DEBUG */ +# define dict_table_get_first_index(table) UT_LIST_GET_FIRST((table)->indexes) +# define dict_table_get_next_index(index) UT_LIST_GET_NEXT(indexes, index) +#endif /* UNIV_DEBUG */ +/************************************************************************ +Check whether the index is the clustered index. */ +UNIV_INLINE +ulint +dict_index_is_clust( +/*================*/ + /* out: nonzero for clustered index, + zero for other indexes */ + const dict_index_t* index) /* in: index */ + __attribute__((pure)); +/************************************************************************ +Check whether the index is unique. */ +UNIV_INLINE +ulint +dict_index_is_unique( +/*=================*/ + /* out: nonzero for unique index, + zero for other indexes */ + const dict_index_t* index) /* in: index */ + __attribute__((pure)); +/************************************************************************ +Check whether the index is the insert buffer tree. */ +UNIV_INLINE +ulint +dict_index_is_ibuf( +/*===============*/ + /* out: nonzero for insert buffer, + zero for other indexes */ + const dict_index_t* index) /* in: index */ + __attribute__((pure)); + +/************************************************************************ +Gets the number of user-defined columns in a table in the dictionary +cache. */ +UNIV_INLINE +ulint +dict_table_get_n_user_cols( +/*=======================*/ + /* out: number of user-defined + (e.g., not ROW_ID) + columns of a table */ + const dict_table_t* table); /* in: table */ +/************************************************************************ +Gets the number of system columns in a table in the dictionary cache. */ +UNIV_INLINE +ulint +dict_table_get_n_sys_cols( +/*======================*/ + /* out: number of system (e.g., + ROW_ID) columns of a table */ + const dict_table_t* table); /* in: table */ +/************************************************************************ +Gets the number of all columns (also system) in a table in the dictionary +cache. */ +UNIV_INLINE +ulint +dict_table_get_n_cols( +/*==================*/ + /* out: number of columns of a table */ + const dict_table_t* table); /* in: table */ +#ifdef UNIV_DEBUG +/************************************************************************ +Gets the nth column of a table. */ +UNIV_INLINE +dict_col_t* +dict_table_get_nth_col( +/*===================*/ + /* out: pointer to column object */ + const dict_table_t* table, /* in: table */ + ulint pos); /* in: position of column */ +/************************************************************************ +Gets the given system column of a table. */ +UNIV_INLINE +dict_col_t* +dict_table_get_sys_col( +/*===================*/ + /* out: pointer to column object */ + const dict_table_t* table, /* in: table */ + ulint sys); /* in: DATA_ROW_ID, ... */ +#else /* UNIV_DEBUG */ +#define dict_table_get_nth_col(table, pos) \ +((table)->cols + (pos)) +#define dict_table_get_sys_col(table, sys) \ +((table)->cols + (table)->n_cols + (sys) - DATA_N_SYS_COLS) +#endif /* UNIV_DEBUG */ +/************************************************************************ +Gets the given system column number of a table. */ +UNIV_INLINE +ulint +dict_table_get_sys_col_no( +/*======================*/ + /* out: column number */ + const dict_table_t* table, /* in: table */ + ulint sys); /* in: DATA_ROW_ID, ... */ +/************************************************************************ +Returns the minimum data size of an index record. */ +UNIV_INLINE +ulint +dict_index_get_min_size( +/*====================*/ + /* out: minimum data size in bytes */ + const dict_index_t* index); /* in: index */ +/************************************************************************ +Check whether the table uses the compact page format. */ +UNIV_INLINE +ibool +dict_table_is_comp( +/*===============*/ + /* out: TRUE if table uses the + compact page format */ + const dict_table_t* table); /* in: table */ +/************************************************************************ +Determine the file format of a table. */ +UNIV_INLINE +ulint +dict_table_get_format( +/*==================*/ + /* out: file format version */ + const dict_table_t* table); /* in: table */ +/************************************************************************ +Set the file format of a table. */ +UNIV_INLINE +void +dict_table_set_format( +/*==================*/ + dict_table_t* table, /* in/out: table */ + ulint format);/* in: file format version */ +/************************************************************************ +Extract the compressed page size from table flags. */ +UNIV_INLINE +ulint +dict_table_flags_to_zip_size( +/*=========================*/ + /* out: compressed page size, + or 0 if not compressed */ + ulint flags) /* in: flags */ + __attribute__((const)); +/************************************************************************ +Check whether the table uses the compressed compact page format. */ +UNIV_INLINE +ulint +dict_table_zip_size( +/*================*/ + /* out: compressed page size, + or 0 if not compressed */ + const dict_table_t* table); /* in: table */ +/************************************************************************ +Checks if a column is in the ordering columns of the clustered index of a +table. Column prefixes are treated like whole columns. */ +UNIV_INTERN +ibool +dict_table_col_in_clustered_key( +/*============================*/ + /* out: TRUE if the column, or its + prefix, is in the clustered key */ + const dict_table_t* table, /* in: table */ + ulint n); /* in: column number */ +/*********************************************************************** +Copies types of columns contained in table to tuple and sets all +fields of the tuple to the SQL NULL value. This function should +be called right after dtuple_create(). */ +UNIV_INTERN +void +dict_table_copy_types( +/*==================*/ + dtuple_t* tuple, /* in/out: data tuple */ + const dict_table_t* table); /* in: table */ +/************************************************************************** +Looks for an index with the given id. NOTE that we do not reserve +the dictionary mutex: this function is for emergency purposes like +printing info of a corrupt database page! */ +UNIV_INTERN +dict_index_t* +dict_index_find_on_id_low( +/*======================*/ + /* out: index or NULL if not found from cache */ + dulint id); /* in: index id */ +/************************************************************************** +Adds an index to the dictionary cache. */ +UNIV_INTERN +ulint +dict_index_add_to_cache( +/*====================*/ + /* out: DB_SUCCESS or error code */ + dict_table_t* table, /* in: table on which the index is */ + dict_index_t* index, /* in, own: index; NOTE! The index memory + object is freed in this function! */ + ulint page_no,/* in: root page number of the index */ + ibool strict);/* in: TRUE=refuse to create the index + if records could be too big to fit in + an B-tree page */ +/************************************************************************** +Removes an index from the dictionary cache. */ +UNIV_INTERN +void +dict_index_remove_from_cache( +/*=========================*/ + dict_table_t* table, /* in/out: table */ + dict_index_t* index); /* in, own: index */ +/************************************************************************ +Gets the number of fields in the internal representation of an index, +including fields added by the dictionary system. */ +UNIV_INLINE +ulint +dict_index_get_n_fields( +/*====================*/ + /* out: number of fields */ + const dict_index_t* index); /* in: an internal + representation of index (in + the dictionary cache) */ +/************************************************************************ +Gets the number of fields in the internal representation of an index +that uniquely determine the position of an index entry in the index, if +we do not take multiversioning into account: in the B-tree use the value +returned by dict_index_get_n_unique_in_tree. */ +UNIV_INLINE +ulint +dict_index_get_n_unique( +/*====================*/ + /* out: number of fields */ + const dict_index_t* index); /* in: an internal representation + of index (in the dictionary cache) */ +/************************************************************************ +Gets the number of fields in the internal representation of an index +which uniquely determine the position of an index entry in the index, if +we also take multiversioning into account. */ +UNIV_INLINE +ulint +dict_index_get_n_unique_in_tree( +/*============================*/ + /* out: number of fields */ + const dict_index_t* index); /* in: an internal representation + of index (in the dictionary cache) */ +/************************************************************************ +Gets the number of user-defined ordering fields in the index. In the internal +representation we add the row id to the ordering fields to make all indexes +unique, but this function returns the number of fields the user defined +in the index as ordering fields. */ +UNIV_INLINE +ulint +dict_index_get_n_ordering_defined_by_user( +/*======================================*/ + /* out: number of fields */ + const dict_index_t* index); /* in: an internal representation + of index (in the dictionary cache) */ +#ifdef UNIV_DEBUG +/************************************************************************ +Gets the nth field of an index. */ +UNIV_INLINE +dict_field_t* +dict_index_get_nth_field( +/*=====================*/ + /* out: pointer to field object */ + const dict_index_t* index, /* in: index */ + ulint pos); /* in: position of field */ +#else /* UNIV_DEBUG */ +# define dict_index_get_nth_field(index, pos) ((index)->fields + (pos)) +#endif /* UNIV_DEBUG */ +/************************************************************************ +Gets pointer to the nth column in an index. */ +UNIV_INLINE +const dict_col_t* +dict_index_get_nth_col( +/*===================*/ + /* out: column */ + const dict_index_t* index, /* in: index */ + ulint pos); /* in: position of the field */ +/************************************************************************ +Gets the column number of the nth field in an index. */ +UNIV_INLINE +ulint +dict_index_get_nth_col_no( +/*======================*/ + /* out: column number */ + const dict_index_t* index, /* in: index */ + ulint pos); /* in: position of the field */ +/************************************************************************ +Looks for column n in an index. */ +UNIV_INTERN +ulint +dict_index_get_nth_col_pos( +/*=======================*/ + /* out: position in internal + representation of the index; + if not contained, returns + ULINT_UNDEFINED */ + const dict_index_t* index, /* in: index */ + ulint n); /* in: column number */ +/************************************************************************ +Returns TRUE if the index contains a column or a prefix of that column. */ +UNIV_INTERN +ibool +dict_index_contains_col_or_prefix( +/*==============================*/ + /* out: TRUE if contains the column + or its prefix */ + const dict_index_t* index, /* in: index */ + ulint n); /* in: column number */ +/************************************************************************ +Looks for a matching field in an index. The column has to be the same. The +column in index must be complete, or must contain a prefix longer than the +column in index2. That is, we must be able to construct the prefix in index2 +from the prefix in index. */ +UNIV_INTERN +ulint +dict_index_get_nth_field_pos( +/*=========================*/ + /* out: position in internal + representation of the index; + if not contained, returns + ULINT_UNDEFINED */ + const dict_index_t* index, /* in: index from which to search */ + const dict_index_t* index2, /* in: index */ + ulint n); /* in: field number in index2 */ +/************************************************************************ +Looks for column n position in the clustered index. */ +UNIV_INTERN +ulint +dict_table_get_nth_col_pos( +/*=======================*/ + /* out: position in internal + representation of + the clustered index */ + const dict_table_t* table, /* in: table */ + ulint n); /* in: column number */ +/************************************************************************ +Returns the position of a system column in an index. */ +UNIV_INLINE +ulint +dict_index_get_sys_col_pos( +/*=======================*/ + /* out: position, + ULINT_UNDEFINED if not contained */ + const dict_index_t* index, /* in: index */ + ulint type); /* in: DATA_ROW_ID, ... */ +/*********************************************************************** +Adds a column to index. */ +UNIV_INTERN +void +dict_index_add_col( +/*===============*/ + dict_index_t* index, /* in/out: index */ + const dict_table_t* table, /* in: table */ + dict_col_t* col, /* in: column */ + ulint prefix_len); /* in: column prefix length */ +/*********************************************************************** +Copies types of fields contained in index to tuple. */ +UNIV_INTERN +void +dict_index_copy_types( +/*==================*/ + dtuple_t* tuple, /* in/out: data tuple */ + const dict_index_t* index, /* in: index */ + ulint n_fields); /* in: number of + field types to copy */ +/************************************************************************* +Gets the field column. */ +UNIV_INLINE +const dict_col_t* +dict_field_get_col( +/*===============*/ + const dict_field_t* field); + +/************************************************************************** +Returns an index object if it is found in the dictionary cache. +Assumes that dict_sys->mutex is already being held. */ +UNIV_INTERN +dict_index_t* +dict_index_get_if_in_cache_low( +/*===========================*/ + /* out: index, NULL if not found */ + dulint index_id); /* in: index id */ +#if defined UNIV_DEBUG || defined UNIV_BUF_DEBUG +/************************************************************************** +Returns an index object if it is found in the dictionary cache. */ +UNIV_INTERN +dict_index_t* +dict_index_get_if_in_cache( +/*=======================*/ + /* out: index, NULL if not found */ + dulint index_id); /* in: index id */ +#endif /* UNIV_DEBUG || UNIV_BUF_DEBUG */ +#ifdef UNIV_DEBUG +/************************************************************************** +Checks that a tuple has n_fields_cmp value in a sensible range, so that +no comparison can occur with the page number field in a node pointer. */ +UNIV_INTERN +ibool +dict_index_check_search_tuple( +/*==========================*/ + /* out: TRUE if ok */ + const dict_index_t* index, /* in: index tree */ + const dtuple_t* tuple); /* in: tuple used in a search */ +/************************************************************************** +Check for duplicate index entries in a table [using the index name] */ +UNIV_INTERN +void +dict_table_check_for_dup_indexes( +/*=============================*/ + const dict_table_t* table); /* in: Check for dup indexes + in this table */ + +#endif /* UNIV_DEBUG */ +/************************************************************************** +Builds a node pointer out of a physical record and a page number. */ +UNIV_INTERN +dtuple_t* +dict_index_build_node_ptr( +/*======================*/ + /* out, own: node pointer */ + const dict_index_t* index, /* in: index */ + const rec_t* rec, /* in: record for which to build node + pointer */ + ulint page_no,/* in: page number to put in node + pointer */ + mem_heap_t* heap, /* in: memory heap where pointer + created */ + ulint level); /* in: level of rec in tree: + 0 means leaf level */ +/************************************************************************** +Copies an initial segment of a physical record, long enough to specify an +index entry uniquely. */ +UNIV_INTERN +rec_t* +dict_index_copy_rec_order_prefix( +/*=============================*/ + /* out: pointer to the prefix record */ + const dict_index_t* index, /* in: index */ + const rec_t* rec, /* in: record for which to + copy prefix */ + ulint* n_fields,/* out: number of fields copied */ + byte** buf, /* in/out: memory buffer for the + copied prefix, or NULL */ + ulint* buf_size);/* in/out: buffer size */ +/************************************************************************** +Builds a typed data tuple out of a physical record. */ +UNIV_INTERN +dtuple_t* +dict_index_build_data_tuple( +/*========================*/ + /* out, own: data tuple */ + dict_index_t* index, /* in: index */ + rec_t* rec, /* in: record for which to build data tuple */ + ulint n_fields,/* in: number of data fields */ + mem_heap_t* heap); /* in: memory heap where tuple created */ +/************************************************************************* +Gets the space id of the root of the index tree. */ +UNIV_INLINE +ulint +dict_index_get_space( +/*=================*/ + /* out: space id */ + const dict_index_t* index); /* in: index */ +/************************************************************************* +Sets the space id of the root of the index tree. */ +UNIV_INLINE +void +dict_index_set_space( +/*=================*/ + dict_index_t* index, /* in/out: index */ + ulint space); /* in: space id */ +/************************************************************************* +Gets the page number of the root of the index tree. */ +UNIV_INLINE +ulint +dict_index_get_page( +/*================*/ + /* out: page number */ + const dict_index_t* tree); /* in: index */ +/************************************************************************* +Sets the page number of the root of index tree. */ +UNIV_INLINE +void +dict_index_set_page( +/*================*/ + dict_index_t* index, /* in/out: index */ + ulint page); /* in: page number */ +/************************************************************************* +Gets the read-write lock of the index tree. */ +UNIV_INLINE +rw_lock_t* +dict_index_get_lock( +/*================*/ + /* out: read-write lock */ + dict_index_t* index); /* in: index */ +/************************************************************************ +Returns free space reserved for future updates of records. This is +relevant only in the case of many consecutive inserts, as updates +which make the records bigger might fragment the index. */ +UNIV_INLINE +ulint +dict_index_get_space_reserve(void); +/*==============================*/ + /* out: number of free bytes on page, + reserved for updates */ +/************************************************************************* +Calculates the minimum record length in an index. */ +UNIV_INTERN +ulint +dict_index_calc_min_rec_len( +/*========================*/ + const dict_index_t* index); /* in: index */ +/************************************************************************* +Calculates new estimates for table and index statistics. The statistics +are used in query optimization. */ +UNIV_INTERN +void +dict_update_statistics_low( +/*=======================*/ + dict_table_t* table, /* in/out: table */ + ibool has_dict_mutex);/* in: TRUE if the caller has the + dictionary mutex */ +/************************************************************************* +Calculates new estimates for table and index statistics. The statistics +are used in query optimization. */ +UNIV_INTERN +void +dict_update_statistics( +/*===================*/ + dict_table_t* table); /* in/out: table */ +/************************************************************************ +Reserves the dictionary system mutex for MySQL. */ +UNIV_INTERN +void +dict_mutex_enter_for_mysql(void); +/*============================*/ +/************************************************************************ +Releases the dictionary system mutex for MySQL. */ +UNIV_INTERN +void +dict_mutex_exit_for_mysql(void); +/*===========================*/ +/************************************************************************ +Checks if the database name in two table names is the same. */ +UNIV_INTERN +ibool +dict_tables_have_same_db( +/*=====================*/ + /* out: TRUE if same db name */ + const char* name1, /* in: table name in the form + dbname '/' tablename */ + const char* name2); /* in: table name in the form + dbname '/' tablename */ +/************************************************************************* +Removes an index from the cache */ +UNIV_INTERN +void +dict_index_remove_from_cache( +/*=========================*/ + dict_table_t* table, /* in/out: table */ + dict_index_t* index); /* in, own: index */ +/************************************************************************** +Get index by name */ +UNIV_INTERN +dict_index_t* +dict_table_get_index_on_name( +/*=========================*/ + /* out: index, NULL if does not exist */ + dict_table_t* table, /* in: table */ + const char* name); /* in: name of the index to find */ +/************************************************************************** +In case there is more than one index with the same name return the index +with the min(id). */ +UNIV_INTERN +dict_index_t* +dict_table_get_index_on_name_and_min_id( +/*====================================*/ + /* out: index, NULL if does not exist */ + dict_table_t* table, /* in: table */ + const char* name); /* in: name of the index to find */ + +UNIV_INTERN +void +dict_table_LRU_trim( +/*================*/ + dict_table_t* self); +/* Buffers for storing detailed information about the latest foreign key +and unique key errors */ +extern FILE* dict_foreign_err_file; +extern mutex_t dict_foreign_err_mutex; /* mutex protecting the buffers */ + +extern dict_sys_t* dict_sys; /* the dictionary system */ +extern rw_lock_t dict_operation_lock; + +/* Dictionary system struct */ +struct dict_sys_struct{ + mutex_t mutex; /* mutex protecting the data + dictionary; protects also the + disk-based dictionary system tables; + this mutex serializes CREATE TABLE + and DROP TABLE, as well as reading + the dictionary data for a table from + system tables */ + dulint row_id; /* the next row id to assign; + NOTE that at a checkpoint this + must be written to the dict system + header and flushed to a file; in + recovery this must be derived from + the log records */ + hash_table_t* table_hash; /* hash table of the tables, based + on name */ + hash_table_t* table_id_hash; /* hash table of the tables, based + on id */ + UT_LIST_BASE_NODE_T(dict_table_t) + table_LRU; /* LRU list of tables */ + ulint size; /* varying space in bytes occupied + by the data dictionary table and + index objects */ + dict_table_t* sys_tables; /* SYS_TABLES table */ + dict_table_t* sys_columns; /* SYS_COLUMNS table */ + dict_table_t* sys_indexes; /* SYS_INDEXES table */ + dict_table_t* sys_fields; /* SYS_FIELDS table */ +}; + +#ifndef UNIV_NONINL +#include "dict0dict.ic" +#endif + +#endif diff --git a/storage/xtradb/include/dict0dict.ic b/storage/xtradb/include/dict0dict.ic new file mode 100644 index 00000000000..687e5f9fb13 --- /dev/null +++ b/storage/xtradb/include/dict0dict.ic @@ -0,0 +1,798 @@ +/***************************************************************************** + +Copyright (c) 1996, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/********************************************************************** +Data dictionary system + +Created 1/8/1996 Heikki Tuuri +***********************************************************************/ + +#include "dict0load.h" +#include "rem0types.h" +#include "data0type.h" + +/************************************************************************* +Gets the column data type. */ +UNIV_INLINE +void +dict_col_copy_type( +/*===============*/ + const dict_col_t* col, /* in: column */ + dtype_t* type) /* out: data type */ +{ + ut_ad(col && type); + + type->mtype = col->mtype; + type->prtype = col->prtype; + type->len = col->len; + type->mbminlen = col->mbminlen; + type->mbmaxlen = col->mbmaxlen; +} + +#ifdef UNIV_DEBUG +/************************************************************************* +Assert that a column and a data type match. */ +UNIV_INLINE +ibool +dict_col_type_assert_equal( +/*=======================*/ + /* out: TRUE */ + const dict_col_t* col, /* in: column */ + const dtype_t* type) /* in: data type */ +{ + ut_ad(col); + ut_ad(type); + + ut_ad(col->mtype == type->mtype); + ut_ad(col->prtype == type->prtype); + ut_ad(col->len == type->len); + ut_ad(col->mbminlen == type->mbminlen); + ut_ad(col->mbmaxlen == type->mbmaxlen); + + return(TRUE); +} +#endif /* UNIV_DEBUG */ + +/*************************************************************************** +Returns the minimum size of the column. */ +UNIV_INLINE +ulint +dict_col_get_min_size( +/*==================*/ + /* out: minimum size */ + const dict_col_t* col) /* in: column */ +{ + return(dtype_get_min_size_low(col->mtype, col->prtype, col->len, + col->mbminlen, col->mbmaxlen)); +} +/*************************************************************************** +Returns the maximum size of the column. */ +UNIV_INLINE +ulint +dict_col_get_max_size( +/*==================*/ + /* out: maximum size */ + const dict_col_t* col) /* in: column */ +{ + return(dtype_get_max_size_low(col->mtype, col->len)); +} +/*************************************************************************** +Returns the size of a fixed size column, 0 if not a fixed size column. */ +UNIV_INLINE +ulint +dict_col_get_fixed_size( +/*====================*/ + /* out: fixed size, or 0 */ + const dict_col_t* col) /* in: column */ +{ + return(dtype_get_fixed_size_low(col->mtype, col->prtype, col->len, + col->mbminlen, col->mbmaxlen)); +} +/*************************************************************************** +Returns the ROW_FORMAT=REDUNDANT stored SQL NULL size of a column. +For fixed length types it is the fixed length of the type, otherwise 0. */ +UNIV_INLINE +ulint +dict_col_get_sql_null_size( +/*=======================*/ + /* out: SQL null storage size + in ROW_FORMAT=REDUNDANT */ + const dict_col_t* col) /* in: column */ +{ + return(dict_col_get_fixed_size(col)); +} + +/************************************************************************* +Gets the column number. */ +UNIV_INLINE +ulint +dict_col_get_no( +/*============*/ + const dict_col_t* col) +{ + ut_ad(col); + + return(col->ind); +} + +/************************************************************************* +Gets the column position in the clustered index. */ +UNIV_INLINE +ulint +dict_col_get_clust_pos( +/*===================*/ + const dict_col_t* col, /* in: table column */ + const dict_index_t* clust_index) /* in: clustered index */ +{ + ulint i; + + ut_ad(col); + ut_ad(clust_index); + ut_ad(dict_index_is_clust(clust_index)); + + for (i = 0; i < clust_index->n_def; i++) { + const dict_field_t* field = &clust_index->fields[i]; + + if (!field->prefix_len && field->col == col) { + return(i); + } + } + + return(ULINT_UNDEFINED); +} + +#ifdef UNIV_DEBUG +/************************************************************************ +Gets the first index on the table (the clustered index). */ +UNIV_INLINE +dict_index_t* +dict_table_get_first_index( +/*=======================*/ + /* out: index, NULL if none exists */ + const dict_table_t* table) /* in: table */ +{ + ut_ad(table); + ut_ad(table->magic_n == DICT_TABLE_MAGIC_N); + + return(UT_LIST_GET_FIRST(((dict_table_t*) table)->indexes)); +} + +/************************************************************************ +Gets the next index on the table. */ +UNIV_INLINE +dict_index_t* +dict_table_get_next_index( +/*======================*/ + /* out: index, NULL if none left */ + const dict_index_t* index) /* in: index */ +{ + ut_ad(index); + ut_ad(index->magic_n == DICT_INDEX_MAGIC_N); + + return(UT_LIST_GET_NEXT(indexes, (dict_index_t*) index)); +} +#endif /* UNIV_DEBUG */ + +/************************************************************************ +Check whether the index is the clustered index. */ +UNIV_INLINE +ulint +dict_index_is_clust( +/*================*/ + /* out: nonzero for clustered index, + zero for other indexes */ + const dict_index_t* index) /* in: index */ +{ + ut_ad(index); + ut_ad(index->magic_n == DICT_INDEX_MAGIC_N); + + return(UNIV_UNLIKELY(index->type & DICT_CLUSTERED)); +} +/************************************************************************ +Check whether the index is unique. */ +UNIV_INLINE +ulint +dict_index_is_unique( +/*=================*/ + /* out: nonzero for unique index, + zero for other indexes */ + const dict_index_t* index) /* in: index */ +{ + ut_ad(index); + ut_ad(index->magic_n == DICT_INDEX_MAGIC_N); + + return(UNIV_UNLIKELY(index->type & DICT_UNIQUE)); +} + +/************************************************************************ +Check whether the index is the insert buffer tree. */ +UNIV_INLINE +ulint +dict_index_is_ibuf( +/*===============*/ + /* out: nonzero for insert buffer, + zero for other indexes */ + const dict_index_t* index) /* in: index */ +{ + ut_ad(index); + ut_ad(index->magic_n == DICT_INDEX_MAGIC_N); + + return(UNIV_UNLIKELY(index->type & DICT_IBUF)); +} + +/************************************************************************ +Gets the number of user-defined columns in a table in the dictionary +cache. */ +UNIV_INLINE +ulint +dict_table_get_n_user_cols( +/*=======================*/ + /* out: number of user-defined + (e.g., not ROW_ID) + columns of a table */ + const dict_table_t* table) /* in: table */ +{ + ut_ad(table); + ut_ad(table->magic_n == DICT_TABLE_MAGIC_N); + + return(table->n_cols - DATA_N_SYS_COLS); +} + +/************************************************************************ +Gets the number of system columns in a table in the dictionary cache. */ +UNIV_INLINE +ulint +dict_table_get_n_sys_cols( +/*======================*/ + /* out: number of system (e.g., + ROW_ID) columns of a table */ + const dict_table_t* table __attribute__((unused))) /* in: table */ +{ + ut_ad(table); + ut_ad(table->magic_n == DICT_TABLE_MAGIC_N); + ut_ad(table->cached); + + return(DATA_N_SYS_COLS); +} + +/************************************************************************ +Gets the number of all columns (also system) in a table in the dictionary +cache. */ +UNIV_INLINE +ulint +dict_table_get_n_cols( +/*==================*/ + /* out: number of columns of a table */ + const dict_table_t* table) /* in: table */ +{ + ut_ad(table); + ut_ad(table->magic_n == DICT_TABLE_MAGIC_N); + + return(table->n_cols); +} + +#ifdef UNIV_DEBUG +/************************************************************************ +Gets the nth column of a table. */ +UNIV_INLINE +dict_col_t* +dict_table_get_nth_col( +/*===================*/ + /* out: pointer to column object */ + const dict_table_t* table, /* in: table */ + ulint pos) /* in: position of column */ +{ + ut_ad(table); + ut_ad(pos < table->n_def); + ut_ad(table->magic_n == DICT_TABLE_MAGIC_N); + + return((dict_col_t*) (table->cols) + pos); +} + +/************************************************************************ +Gets the given system column of a table. */ +UNIV_INLINE +dict_col_t* +dict_table_get_sys_col( +/*===================*/ + /* out: pointer to column object */ + const dict_table_t* table, /* in: table */ + ulint sys) /* in: DATA_ROW_ID, ... */ +{ + dict_col_t* col; + + ut_ad(table); + ut_ad(sys < DATA_N_SYS_COLS); + ut_ad(table->magic_n == DICT_TABLE_MAGIC_N); + + col = dict_table_get_nth_col(table, table->n_cols + - DATA_N_SYS_COLS + sys); + ut_ad(col->mtype == DATA_SYS); + ut_ad(col->prtype == (sys | DATA_NOT_NULL)); + + return(col); +} +#endif /* UNIV_DEBUG */ + +/************************************************************************ +Gets the given system column number of a table. */ +UNIV_INLINE +ulint +dict_table_get_sys_col_no( +/*======================*/ + /* out: column number */ + const dict_table_t* table, /* in: table */ + ulint sys) /* in: DATA_ROW_ID, ... */ +{ + ut_ad(table); + ut_ad(sys < DATA_N_SYS_COLS); + ut_ad(table->magic_n == DICT_TABLE_MAGIC_N); + + return(table->n_cols - DATA_N_SYS_COLS + sys); +} + +/************************************************************************ +Check whether the table uses the compact page format. */ +UNIV_INLINE +ibool +dict_table_is_comp( +/*===============*/ + /* out: TRUE if table uses the + compact page format */ + const dict_table_t* table) /* in: table */ +{ + ut_ad(table); + +#if DICT_TF_COMPACT != TRUE +#error +#endif + + return(UNIV_LIKELY(table->flags & DICT_TF_COMPACT)); +} + +/************************************************************************ +Determine the file format of a table. */ +UNIV_INLINE +ulint +dict_table_get_format( +/*==================*/ + /* out: file format version */ + const dict_table_t* table) /* in: table */ +{ + ut_ad(table); + + return((table->flags & DICT_TF_FORMAT_MASK) >> DICT_TF_FORMAT_SHIFT); +} + +/************************************************************************ +Determine the file format of a table. */ +UNIV_INLINE +void +dict_table_set_format( +/*==================*/ + dict_table_t* table, /* in/out: table */ + ulint format) /* in: file format version */ +{ + ut_ad(table); + + table->flags = (table->flags & ~DICT_TF_FORMAT_MASK) + | (format << DICT_TF_FORMAT_SHIFT); +} + +/************************************************************************ +Extract the compressed page size from table flags. */ +UNIV_INLINE +ulint +dict_table_flags_to_zip_size( +/*=========================*/ + /* out: compressed page size, + or 0 if not compressed */ + ulint flags) /* in: flags */ +{ + ulint zip_size = flags & DICT_TF_ZSSIZE_MASK; + + if (UNIV_UNLIKELY(zip_size)) { + zip_size = ((PAGE_ZIP_MIN_SIZE >> 1) + << (zip_size >> DICT_TF_ZSSIZE_SHIFT)); + + ut_ad(zip_size <= UNIV_PAGE_SIZE); + } + + return(zip_size); +} + +/************************************************************************ +Check whether the table uses the compressed compact page format. */ +UNIV_INLINE +ulint +dict_table_zip_size( +/*================*/ + /* out: compressed page size, + or 0 if not compressed */ + const dict_table_t* table) /* in: table */ +{ + ut_ad(table); + + return(dict_table_flags_to_zip_size(table->flags)); +} + +/************************************************************************ +Gets the number of fields in the internal representation of an index, +including fields added by the dictionary system. */ +UNIV_INLINE +ulint +dict_index_get_n_fields( +/*====================*/ + /* out: number of fields */ + const dict_index_t* index) /* in: an internal + representation of index (in + the dictionary cache) */ +{ + ut_ad(index); + ut_ad(index->magic_n == DICT_INDEX_MAGIC_N); + + return(index->n_fields); +} + +/************************************************************************ +Gets the number of fields in the internal representation of an index +that uniquely determine the position of an index entry in the index, if +we do not take multiversioning into account: in the B-tree use the value +returned by dict_index_get_n_unique_in_tree. */ +UNIV_INLINE +ulint +dict_index_get_n_unique( +/*====================*/ + /* out: number of fields */ + const dict_index_t* index) /* in: an internal representation + of index (in the dictionary cache) */ +{ + ut_ad(index); + ut_ad(index->magic_n == DICT_INDEX_MAGIC_N); + ut_ad(index->cached); + + return(index->n_uniq); +} + +/************************************************************************ +Gets the number of fields in the internal representation of an index +which uniquely determine the position of an index entry in the index, if +we also take multiversioning into account. */ +UNIV_INLINE +ulint +dict_index_get_n_unique_in_tree( +/*============================*/ + /* out: number of fields */ + const dict_index_t* index) /* in: an internal representation + of index (in the dictionary cache) */ +{ + ut_ad(index); + ut_ad(index->magic_n == DICT_INDEX_MAGIC_N); + ut_ad(index->cached); + + if (dict_index_is_clust(index)) { + + return(dict_index_get_n_unique(index)); + } + + return(dict_index_get_n_fields(index)); +} + +/************************************************************************ +Gets the number of user-defined ordering fields in the index. In the internal +representation of clustered indexes we add the row id to the ordering fields +to make a clustered index unique, but this function returns the number of +fields the user defined in the index as ordering fields. */ +UNIV_INLINE +ulint +dict_index_get_n_ordering_defined_by_user( +/*======================================*/ + /* out: number of fields */ + const dict_index_t* index) /* in: an internal representation + of index (in the dictionary cache) */ +{ + return(index->n_user_defined_cols); +} + +#ifdef UNIV_DEBUG +/************************************************************************ +Gets the nth field of an index. */ +UNIV_INLINE +dict_field_t* +dict_index_get_nth_field( +/*=====================*/ + /* out: pointer to field object */ + const dict_index_t* index, /* in: index */ + ulint pos) /* in: position of field */ +{ + ut_ad(index); + ut_ad(pos < index->n_def); + ut_ad(index->magic_n == DICT_INDEX_MAGIC_N); + + return((dict_field_t*) (index->fields) + pos); +} +#endif /* UNIV_DEBUG */ + +/************************************************************************ +Returns the position of a system column in an index. */ +UNIV_INLINE +ulint +dict_index_get_sys_col_pos( +/*=======================*/ + /* out: position, + ULINT_UNDEFINED if not contained */ + const dict_index_t* index, /* in: index */ + ulint type) /* in: DATA_ROW_ID, ... */ +{ + ut_ad(index); + ut_ad(index->magic_n == DICT_INDEX_MAGIC_N); + ut_ad(!(index->type & DICT_UNIVERSAL)); + + if (dict_index_is_clust(index)) { + + return(dict_col_get_clust_pos( + dict_table_get_sys_col(index->table, type), + index)); + } + + return(dict_index_get_nth_col_pos( + index, dict_table_get_sys_col_no(index->table, type))); +} + +/************************************************************************* +Gets the field column. */ +UNIV_INLINE +const dict_col_t* +dict_field_get_col( +/*===============*/ + const dict_field_t* field) +{ + ut_ad(field); + + return(field->col); +} + +/************************************************************************ +Gets pointer to the nth column in an index. */ +UNIV_INLINE +const dict_col_t* +dict_index_get_nth_col( +/*===================*/ + /* out: column */ + const dict_index_t* index, /* in: index */ + ulint pos) /* in: position of the field */ +{ + return(dict_field_get_col(dict_index_get_nth_field(index, pos))); +} + +/************************************************************************ +Gets the column number the nth field in an index. */ +UNIV_INLINE +ulint +dict_index_get_nth_col_no( +/*======================*/ + /* out: column number */ + const dict_index_t* index, /* in: index */ + ulint pos) /* in: position of the field */ +{ + return(dict_col_get_no(dict_index_get_nth_col(index, pos))); +} + +/************************************************************************ +Returns the minimum data size of an index record. */ +UNIV_INLINE +ulint +dict_index_get_min_size( +/*====================*/ + /* out: minimum data size in bytes */ + const dict_index_t* index) /* in: index */ +{ + ulint n = dict_index_get_n_fields(index); + ulint size = 0; + + while (n--) { + size += dict_col_get_min_size(dict_index_get_nth_col(index, + n)); + } + + return(size); +} + +/************************************************************************* +Gets the space id of the root of the index tree. */ +UNIV_INLINE +ulint +dict_index_get_space( +/*=================*/ + /* out: space id */ + const dict_index_t* index) /* in: index */ +{ + ut_ad(index); + ut_ad(index->magic_n == DICT_INDEX_MAGIC_N); + + return(index->space); +} + +/************************************************************************* +Sets the space id of the root of the index tree. */ +UNIV_INLINE +void +dict_index_set_space( +/*=================*/ + dict_index_t* index, /* in/out: index */ + ulint space) /* in: space id */ +{ + ut_ad(index); + ut_ad(index->magic_n == DICT_INDEX_MAGIC_N); + + index->space = space; +} + +/************************************************************************* +Gets the page number of the root of the index tree. */ +UNIV_INLINE +ulint +dict_index_get_page( +/*================*/ + /* out: page number */ + const dict_index_t* index) /* in: index */ +{ + ut_ad(index); + ut_ad(index->magic_n == DICT_INDEX_MAGIC_N); + + return(index->page); +} + +/************************************************************************* +Sets the page number of the root of index tree. */ +UNIV_INLINE +void +dict_index_set_page( +/*================*/ + dict_index_t* index, /* in/out: index */ + ulint page) /* in: page number */ +{ + ut_ad(index); + ut_ad(index->magic_n == DICT_INDEX_MAGIC_N); + + index->page = page; +} + +/************************************************************************* +Gets the read-write lock of the index tree. */ +UNIV_INLINE +rw_lock_t* +dict_index_get_lock( +/*================*/ + /* out: read-write lock */ + dict_index_t* index) /* in: index */ +{ + ut_ad(index); + ut_ad(index->magic_n == DICT_INDEX_MAGIC_N); + + return(&(index->lock)); +} + +/************************************************************************ +Returns free space reserved for future updates of records. This is +relevant only in the case of many consecutive inserts, as updates +which make the records bigger might fragment the index. */ +UNIV_INLINE +ulint +dict_index_get_space_reserve(void) +/*==============================*/ + /* out: number of free bytes on page, + reserved for updates */ +{ + return(UNIV_PAGE_SIZE / 16); +} + +/************************************************************************** +Checks if a table is in the dictionary cache. */ +UNIV_INLINE +dict_table_t* +dict_table_check_if_in_cache_low( +/*=============================*/ + /* out: table, NULL if not found */ + const char* table_name) /* in: table name */ +{ + dict_table_t* table; + ulint table_fold; + + ut_ad(table_name); + ut_ad(mutex_own(&(dict_sys->mutex))); + + /* Look for the table name in the hash table */ + table_fold = ut_fold_string(table_name); + + HASH_SEARCH(name_hash, dict_sys->table_hash, table_fold, + dict_table_t*, table, ut_ad(table->cached), + !strcmp(table->name, table_name)); + + /* make young in table_LRU */ + if (table) { + UT_LIST_REMOVE(table_LRU, dict_sys->table_LRU, table); + UT_LIST_ADD_FIRST(table_LRU, dict_sys->table_LRU, table); + } + + return(table); +} + +/************************************************************************** +Gets a table; loads it to the dictionary cache if necessary. A low-level +function. */ +UNIV_INLINE +dict_table_t* +dict_table_get_low( +/*===============*/ + /* out: table, NULL if not found */ + const char* table_name) /* in: table name */ +{ + dict_table_t* table; + + ut_ad(table_name); + ut_ad(mutex_own(&(dict_sys->mutex))); + + table = dict_table_check_if_in_cache_low(table_name); + + if (table == NULL) { + table = dict_load_table(table_name); + } + + ut_ad(!table || table->cached); + + return(table); +} + +/************************************************************************** +Returns a table object based on table id. */ +UNIV_INLINE +dict_table_t* +dict_table_get_on_id_low( +/*=====================*/ + /* out: table, NULL if does not exist */ + dulint table_id) /* in: table id */ +{ + dict_table_t* table; + ulint fold; + + ut_ad(mutex_own(&(dict_sys->mutex))); + + /* Look for the table name in the hash table */ + fold = ut_fold_dulint(table_id); + + HASH_SEARCH(id_hash, dict_sys->table_id_hash, fold, + dict_table_t*, table, ut_ad(table->cached), + !ut_dulint_cmp(table->id, table_id)); + if (table == NULL) { + table = dict_load_table_on_id(table_id); + } + + /* make young in table_LRU */ + if (table) { + UT_LIST_REMOVE(table_LRU, dict_sys->table_LRU, table); + UT_LIST_ADD_FIRST(table_LRU, dict_sys->table_LRU, table); + } + + ut_ad(!table || table->cached); + + /* TODO: should get the type information from MySQL */ + + return(table); +} + diff --git a/storage/xtradb/include/dict0load.h b/storage/xtradb/include/dict0load.h new file mode 100644 index 00000000000..759cbcdb14a --- /dev/null +++ b/storage/xtradb/include/dict0load.h @@ -0,0 +1,119 @@ +/***************************************************************************** + +Copyright (c) 1996, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/****************************************************** +Loads to the memory cache database object definitions +from dictionary tables + +Created 4/24/1996 Heikki Tuuri +*******************************************************/ + +#ifndef dict0load_h +#define dict0load_h + +#include "univ.i" +#include "dict0types.h" +#include "ut0byte.h" +#include "mem0mem.h" + +/************************************************************************ +In a crash recovery we already have all the tablespace objects created. +This function compares the space id information in the InnoDB data dictionary +to what we already read with fil_load_single_table_tablespaces(). + +In a normal startup, we create the tablespace objects for every table in +InnoDB's data dictionary, if the corresponding .ibd file exists. +We also scan the biggest space id, and store it to fil_system. */ +UNIV_INTERN +void +dict_check_tablespaces_and_store_max_id( +/*====================================*/ + ibool in_crash_recovery); /* in: are we doing a crash recovery */ +/************************************************************************ +Finds the first table name in the given database. */ +UNIV_INTERN +char* +dict_get_first_table_name_in_db( +/*============================*/ + /* out, own: table name, NULL if + does not exist; the caller must free + the memory in the string! */ + const char* name); /* in: database name which ends to '/' */ +/************************************************************************ +Loads a table definition and also all its index definitions, and also +the cluster definition if the table is a member in a cluster. Also loads +all foreign key constraints where the foreign key is in the table or where +a foreign key references columns in this table. */ +UNIV_INTERN +dict_table_t* +dict_load_table( +/*============*/ + /* out: table, NULL if does not exist; + if the table is stored in an .ibd file, + but the file does not exist, + then we set the ibd_file_missing flag TRUE + in the table object we return */ + const char* name); /* in: table name in the + databasename/tablename format */ +/*************************************************************************** +Loads a table object based on the table id. */ +UNIV_INTERN +dict_table_t* +dict_load_table_on_id( +/*==================*/ + /* out: table; NULL if table does not exist */ + dulint table_id); /* in: table id */ +/************************************************************************ +This function is called when the database is booted. +Loads system table index definitions except for the clustered index which +is added to the dictionary cache at booting before calling this function. */ +UNIV_INTERN +void +dict_load_sys_table( +/*================*/ + dict_table_t* table); /* in: system table */ +#ifndef UNIV_HOTBACKUP +/*************************************************************************** +Loads foreign key constraints where the table is either the foreign key +holder or where the table is referenced by a foreign key. Adds these +constraints to the data dictionary. Note that we know that the dictionary +cache already contains all constraints where the other relevant table is +already in the dictionary cache. */ +UNIV_INTERN +ulint +dict_load_foreigns( +/*===============*/ + /* out: DB_SUCCESS or error code */ + const char* table_name, /* in: table name */ + ibool check_charsets);/* in: TRUE=check charsets + compatibility */ +#endif /* !UNIV_HOTBACKUP */ +/************************************************************************ +Prints to the standard output information on all tables found in the data +dictionary system table. */ +UNIV_INTERN +void +dict_print(void); +/*============*/ + + +#ifndef UNIV_NONINL +#include "dict0load.ic" +#endif + +#endif diff --git a/storage/xtradb/include/dict0load.ic b/storage/xtradb/include/dict0load.ic new file mode 100644 index 00000000000..72eac2f621a --- /dev/null +++ b/storage/xtradb/include/dict0load.ic @@ -0,0 +1,25 @@ +/***************************************************************************** + +Copyright (c) 1996, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/****************************************************** +Loads to the memory cache database object definitions +from dictionary tables + +Created 4/24/1996 Heikki Tuuri +*******************************************************/ + diff --git a/storage/xtradb/include/dict0mem.h b/storage/xtradb/include/dict0mem.h new file mode 100644 index 00000000000..e2b3cfa3679 --- /dev/null +++ b/storage/xtradb/include/dict0mem.h @@ -0,0 +1,501 @@ +/***************************************************************************** + +Copyright (c) 1996, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/****************************************************** +Data dictionary memory object creation + +Created 1/8/1996 Heikki Tuuri +*******************************************************/ + +#ifndef dict0mem_h +#define dict0mem_h + +#include "univ.i" +#include "dict0types.h" +#include "data0type.h" +#include "mem0mem.h" +#include "rem0types.h" +#include "btr0types.h" +#include "ut0mem.h" +#include "ut0lst.h" +#include "ut0rnd.h" +#include "ut0byte.h" +#include "sync0rw.h" +#include "lock0types.h" +#include "hash0hash.h" +#include "que0types.h" +#include "trx0types.h" + +/* Type flags of an index: OR'ing of the flags is allowed to define a +combination of types */ +#define DICT_CLUSTERED 1 /* clustered index */ +#define DICT_UNIQUE 2 /* unique index */ +#define DICT_UNIVERSAL 4 /* index which can contain records from any + other index */ +#define DICT_IBUF 8 /* insert buffer tree */ + +/* Types for a table object */ +#define DICT_TABLE_ORDINARY 1 +#if 0 /* not implemented */ +#define DICT_TABLE_CLUSTER_MEMBER 2 +#define DICT_TABLE_CLUSTER 3 /* this means that the table is + really a cluster definition */ +#endif + +/* Table flags. All unused bits must be 0. */ +#define DICT_TF_COMPACT 1 /* Compact page format. + This must be set for + new file formats + (later than + DICT_TF_FORMAT_51). */ + +/* compressed page size (0=uncompressed, up to 15 compressed sizes) */ +#define DICT_TF_ZSSIZE_SHIFT 1 +#define DICT_TF_ZSSIZE_MASK (15 << DICT_TF_ZSSIZE_SHIFT) +#define DICT_TF_ZSSIZE_MAX (UNIV_PAGE_SIZE_SHIFT - PAGE_ZIP_MIN_SIZE_SHIFT + 1) + + +#define DICT_TF_FORMAT_SHIFT 5 /* file format */ +#define DICT_TF_FORMAT_MASK (127 << DICT_TF_FORMAT_SHIFT) +#define DICT_TF_FORMAT_51 0 /* InnoDB/MySQL up to 5.1 */ +#define DICT_TF_FORMAT_ZIP 1 /* InnoDB plugin for 5.1: + compressed tables, + new BLOB treatment */ +#define DICT_TF_FORMAT_MAX DICT_TF_FORMAT_ZIP + +#define DICT_TF_BITS 6 /* number of flag bits */ +#if (1 << (DICT_TF_BITS - DICT_TF_FORMAT_SHIFT)) <= DICT_TF_FORMAT_MAX +# error "DICT_TF_BITS is insufficient for DICT_TF_FORMAT_MAX" +#endif + +/************************************************************************** +Creates a table memory object. */ +UNIV_INTERN +dict_table_t* +dict_mem_table_create( +/*==================*/ + /* out, own: table object */ + const char* name, /* in: table name */ + ulint space, /* in: space where the clustered index + of the table is placed; this parameter + is ignored if the table is made + a member of a cluster */ + ulint n_cols, /* in: number of columns */ + ulint flags); /* in: table flags */ +/******************************************************************** +Free a table memory object. */ +UNIV_INTERN +void +dict_mem_table_free( +/*================*/ + dict_table_t* table); /* in: table */ +/************************************************************************** +Adds a column definition to a table. */ +UNIV_INTERN +void +dict_mem_table_add_col( +/*===================*/ + dict_table_t* table, /* in: table */ + mem_heap_t* heap, /* in: temporary memory heap, or NULL */ + const char* name, /* in: column name, or NULL */ + ulint mtype, /* in: main datatype */ + ulint prtype, /* in: precise type */ + ulint len); /* in: precision */ +/************************************************************************** +Creates an index memory object. */ +UNIV_INTERN +dict_index_t* +dict_mem_index_create( +/*==================*/ + /* out, own: index object */ + const char* table_name, /* in: table name */ + const char* index_name, /* in: index name */ + ulint space, /* in: space where the index tree is + placed, ignored if the index is of + the clustered type */ + ulint type, /* in: DICT_UNIQUE, + DICT_CLUSTERED, ... ORed */ + ulint n_fields); /* in: number of fields */ +/************************************************************************** +Adds a field definition to an index. NOTE: does not take a copy +of the column name if the field is a column. The memory occupied +by the column name may be released only after publishing the index. */ +UNIV_INTERN +void +dict_mem_index_add_field( +/*=====================*/ + dict_index_t* index, /* in: index */ + const char* name, /* in: column name */ + ulint prefix_len); /* in: 0 or the column prefix length + in a MySQL index like + INDEX (textcol(25)) */ +/************************************************************************** +Frees an index memory object. */ +UNIV_INTERN +void +dict_mem_index_free( +/*================*/ + dict_index_t* index); /* in: index */ +/************************************************************************** +Creates and initializes a foreign constraint memory object. */ +UNIV_INTERN +dict_foreign_t* +dict_mem_foreign_create(void); +/*=========================*/ + /* out, own: foreign constraint struct */ + +/* Data structure for a column in a table */ +struct dict_col_struct{ + /*----------------------*/ + /* The following are copied from dtype_t, + so that all bit-fields can be packed tightly. */ + unsigned mtype:8; /* main data type */ + unsigned prtype:24; /* precise type; MySQL data + type, charset code, flags to + indicate nullability, + signedness, whether this is a + binary string, whether this is + a true VARCHAR where MySQL + uses 2 bytes to store the length */ + + /* the remaining fields do not affect alphabetical ordering: */ + + unsigned len:16; /* length; for MySQL data this + is field->pack_length(), + except that for a >= 5.0.3 + type true VARCHAR this is the + maximum byte length of the + string data (in addition to + the string, MySQL uses 1 or 2 + bytes to store the string length) */ + + unsigned mbminlen:2; /* minimum length of a + character, in bytes */ + unsigned mbmaxlen:3; /* maximum length of a + character, in bytes */ + /*----------------------*/ + /* End of definitions copied from dtype_t */ + + unsigned ind:10; /* table column position + (starting from 0) */ + unsigned ord_part:1; /* nonzero if this column + appears in the ordering fields + of an index */ +}; + +/* DICT_MAX_INDEX_COL_LEN is measured in bytes and is the maximum +indexed column length (or indexed prefix length). It is set to 3*256, +so that one can create a column prefix index on 256 characters of a +TEXT or VARCHAR column also in the UTF-8 charset. In that charset, +a character may take at most 3 bytes. +This constant MUST NOT BE CHANGED, or the compatibility of InnoDB data +files would be at risk! */ + +#define DICT_MAX_INDEX_COL_LEN REC_MAX_INDEX_COL_LEN + +/* Data structure for a field in an index */ +struct dict_field_struct{ + dict_col_t* col; /* pointer to the table column */ + const char* name; /* name of the column */ + unsigned prefix_len:10; /* 0 or the length of the column + prefix in bytes in a MySQL index of + type, e.g., INDEX (textcol(25)); + must be smaller than + DICT_MAX_INDEX_COL_LEN; NOTE that + in the UTF-8 charset, MySQL sets this + to 3 * the prefix len in UTF-8 chars */ + unsigned fixed_len:10; /* 0 or the fixed length of the + column if smaller than + DICT_MAX_INDEX_COL_LEN */ +}; + +/* Data structure for an index. Most fields will be +initialized to 0, NULL or FALSE in dict_mem_index_create(). */ +struct dict_index_struct{ + dulint id; /* id of the index */ + mem_heap_t* heap; /* memory heap */ + const char* name; /* index name */ + const char* table_name; /* table name */ + dict_table_t* table; /* back pointer to table */ + unsigned space:32; + /* space where the index tree is placed */ + unsigned page:32;/* index tree root page number */ + unsigned type:4; /* index type (DICT_CLUSTERED, DICT_UNIQUE, + DICT_UNIVERSAL, DICT_IBUF) */ + unsigned trx_id_offset:10;/* position of the trx id column + in a clustered index record, if the fields + before it are known to be of a fixed size, + 0 otherwise */ + unsigned n_user_defined_cols:10; + /* number of columns the user defined to + be in the index: in the internal + representation we add more columns */ + unsigned n_uniq:10;/* number of fields from the beginning + which are enough to determine an index + entry uniquely */ + unsigned n_def:10;/* number of fields defined so far */ + unsigned n_fields:10;/* number of fields in the index */ + unsigned n_nullable:10;/* number of nullable fields */ + unsigned cached:1;/* TRUE if the index object is in the + dictionary cache */ + unsigned to_be_dropped:1; + /* TRUE if this index is marked to be + dropped in ha_innobase::prepare_drop_index(), + otherwise FALSE */ + dict_field_t* fields; /* array of field descriptions */ + UT_LIST_NODE_T(dict_index_t) + indexes;/* list of indexes of the table */ + btr_search_t* search_info; /* info used in optimistic searches */ + /*----------------------*/ + ib_int64_t* stat_n_diff_key_vals; + /* approximate number of different key values + for this index, for each n-column prefix + where n <= dict_get_n_unique(index); we + periodically calculate new estimates */ + ulint stat_index_size; + /* approximate index size in database pages */ + ulint stat_n_leaf_pages; + /* approximate number of leaf pages in the + index tree */ + rw_lock_t lock; /* read-write lock protecting the upper levels + of the index tree */ +#ifdef ROW_MERGE_IS_INDEX_USABLE + dulint trx_id; /* id of the transaction that created this + index, or ut_dulint_zero if the index existed + when InnoDB was started up */ +#endif /* ROW_MERGE_IS_INDEX_USABLE */ +#ifdef UNIV_DEBUG + ulint magic_n;/* magic number */ +# define DICT_INDEX_MAGIC_N 76789786 +#endif +}; + +/* Data structure for a foreign key constraint; an example: +FOREIGN KEY (A, B) REFERENCES TABLE2 (C, D). Most fields will be +initialized to 0, NULL or FALSE in dict_mem_foreign_create(). */ + +struct dict_foreign_struct{ + mem_heap_t* heap; /* this object is allocated from + this memory heap */ + char* id; /* id of the constraint as a + null-terminated string */ + unsigned n_fields:10; /* number of indexes' first fields + for which the the foreign key + constraint is defined: we allow the + indexes to contain more fields than + mentioned in the constraint, as long + as the first fields are as mentioned */ + unsigned type:6; /* 0 or DICT_FOREIGN_ON_DELETE_CASCADE + or DICT_FOREIGN_ON_DELETE_SET_NULL */ + char* foreign_table_name;/* foreign table name */ + dict_table_t* foreign_table; /* table where the foreign key is */ + const char** foreign_col_names;/* names of the columns in the + foreign key */ + char* referenced_table_name;/* referenced table name */ + dict_table_t* referenced_table;/* table where the referenced key + is */ + const char** referenced_col_names;/* names of the referenced + columns in the referenced table */ + dict_index_t* foreign_index; /* foreign index; we require that + both tables contain explicitly defined + indexes for the constraint: InnoDB + does not generate new indexes + implicitly */ + dict_index_t* referenced_index;/* referenced index */ + UT_LIST_NODE_T(dict_foreign_t) + foreign_list; /* list node for foreign keys of the + table */ + UT_LIST_NODE_T(dict_foreign_t) + referenced_list;/* list node for referenced keys of the + table */ +}; + +/* The flags for ON_UPDATE and ON_DELETE can be ORed; the default is that +a foreign key constraint is enforced, therefore RESTRICT just means no flag */ +#define DICT_FOREIGN_ON_DELETE_CASCADE 1 +#define DICT_FOREIGN_ON_DELETE_SET_NULL 2 +#define DICT_FOREIGN_ON_UPDATE_CASCADE 4 +#define DICT_FOREIGN_ON_UPDATE_SET_NULL 8 +#define DICT_FOREIGN_ON_DELETE_NO_ACTION 16 +#define DICT_FOREIGN_ON_UPDATE_NO_ACTION 32 + + +/* Data structure for a database table. Most fields will be +initialized to 0, NULL or FALSE in dict_mem_table_create(). */ +struct dict_table_struct{ + dulint id; /* id of the table */ + mem_heap_t* heap; /* memory heap */ + const char* name; /* table name */ + const char* dir_path_of_temp_table;/* NULL or the directory path + where a TEMPORARY table that was explicitly + created by a user should be placed if + innodb_file_per_table is defined in my.cnf; + in Unix this is usually /tmp/..., in Windows + \temp\... */ + unsigned space:32; + /* space where the clustered index of the + table is placed */ + unsigned flags:DICT_TF_BITS;/* DICT_TF_COMPACT, ... */ + unsigned ibd_file_missing:1; + /* TRUE if this is in a single-table + tablespace and the .ibd file is missing; then + we must return in ha_innodb.cc an error if the + user tries to query such an orphaned table */ + unsigned tablespace_discarded:1; + /* this flag is set TRUE when the user + calls DISCARD TABLESPACE on this + table, and reset to FALSE in IMPORT + TABLESPACE */ + unsigned cached:1;/* TRUE if the table object has been added + to the dictionary cache */ + unsigned n_def:10;/* number of columns defined so far */ + unsigned n_cols:10;/* number of columns */ + dict_col_t* cols; /* array of column descriptions */ + const char* col_names; + /* Column names packed in a character string + "name1\0name2\0...nameN\0". Until + the string contains n_cols, it will be + allocated from a temporary heap. The final + string will be allocated from table->heap. */ + hash_node_t name_hash; /* hash chain node */ + hash_node_t id_hash; /* hash chain node */ + UT_LIST_BASE_NODE_T(dict_index_t) + indexes; /* list of indexes of the table */ + UT_LIST_BASE_NODE_T(dict_foreign_t) + foreign_list;/* list of foreign key constraints + in the table; these refer to columns + in other tables */ + UT_LIST_BASE_NODE_T(dict_foreign_t) + referenced_list;/* list of foreign key constraints + which refer to this table */ + UT_LIST_NODE_T(dict_table_t) + table_LRU; /* node of the LRU list of tables */ + ulint n_mysql_handles_opened; + /* count of how many handles MySQL has opened + to this table; dropping of the table is + NOT allowed until this count gets to zero; + MySQL does NOT itself check the number of + open handles at drop */ + ulint n_foreign_key_checks_running; + /* count of how many foreign key check + operations are currently being performed + on the table: we cannot drop the table while + there are foreign key checks running on + it! */ + dulint query_cache_inv_trx_id; + /* transactions whose trx id < than this + number are not allowed to store to the MySQL + query cache or retrieve from it; when a trx + with undo logs commits, it sets this to the + value of the trx id counter for the tables it + had an IX lock on */ + UT_LIST_BASE_NODE_T(lock_t) + locks; /* list of locks on the table */ +#ifdef UNIV_DEBUG + /*----------------------*/ + ibool does_not_fit_in_memory; + /* this field is used to specify in simulations + tables which are so big that disk should be + accessed: disk access is simulated by + putting the thread to sleep for a while; + NOTE that this flag is not stored to the data + dictionary on disk, and the database will + forget about value TRUE if it has to reload + the table definition from disk */ +#endif /* UNIV_DEBUG */ + /*----------------------*/ + unsigned big_rows:1; + /* flag: TRUE if the maximum length of + a single row exceeds BIG_ROW_SIZE; + initialized in dict_table_add_to_cache() */ + unsigned stat_initialized:1; /* TRUE if statistics have + been calculated the first time + after database startup or table creation */ + ib_int64_t stat_n_rows; + /* approximate number of rows in the table; + we periodically calculate new estimates */ + ulint stat_clustered_index_size; + /* approximate clustered index size in + database pages */ + ulint stat_sum_of_other_index_sizes; + /* other indexes in database pages */ + ulint stat_modified_counter; + /* when a row is inserted, updated, or deleted, + we add 1 to this number; we calculate new + estimates for the stat_... values for the + table and the indexes at an interval of 2 GB + or when about 1 / 16 of table has been + modified; also when the estimate operation is + called for MySQL SHOW TABLE STATUS; the + counter is reset to zero at statistics + calculation; this counter is not protected by + any latch, because this is only used for + heuristics */ + /*----------------------*/ + /* The following fields are used by the + AUTOINC code. The actual collection of + tables locked during AUTOINC read/write is + kept in trx_t. In order to quickly determine + whether a transaction has locked the AUTOINC + lock we keep a pointer to the transaction + here in the autoinc_trx variable. This is to + avoid acquiring the kernel mutex and scanning + the vector in trx_t. + + When an AUTOINC lock has to wait, the + corresponding lock instance is created on + the trx lock heap rather than use the + pre-allocated instance in autoinc_lock below.*/ + lock_t* autoinc_lock; + /* a buffer for an AUTOINC lock + for this table: we allocate the memory here + so that individual transactions can get it + and release it without a need to allocate + space from the lock heap of the trx: + otherwise the lock heap would grow rapidly + if we do a large insert from a select */ + mutex_t autoinc_mutex; + /* mutex protecting the autoincrement + counter */ + ib_uint64_t autoinc;/* autoinc counter value to give to the + next inserted row */ + ulong n_waiting_or_granted_auto_inc_locks; + /* This counter is used to track the number + of granted and pending autoinc locks on this + table. This value is set after acquiring the + kernel mutex but we peek the contents to + determine whether other transactions have + acquired the AUTOINC lock or not. Of course + only one transaction can be granted the + lock but there can be multiple waiters. */ + const trx_t* autoinc_trx; + /* The transaction that currently holds the + the AUTOINC lock on this table. */ + /*----------------------*/ + +#ifdef UNIV_DEBUG + ulint magic_n;/* magic number */ +# define DICT_TABLE_MAGIC_N 76333786 +#endif /* UNIV_DEBUG */ +}; + +#ifndef UNIV_NONINL +#include "dict0mem.ic" +#endif + +#endif diff --git a/storage/xtradb/include/dict0mem.ic b/storage/xtradb/include/dict0mem.ic new file mode 100644 index 00000000000..6916393a9cd --- /dev/null +++ b/storage/xtradb/include/dict0mem.ic @@ -0,0 +1,25 @@ +/***************************************************************************** + +Copyright (c) 1996, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/********************************************************************** +Data dictionary memory object creation + +Created 1/8/1996 Heikki Tuuri +***********************************************************************/ + + diff --git a/storage/xtradb/include/dict0types.h b/storage/xtradb/include/dict0types.h new file mode 100644 index 00000000000..b93e995e01b --- /dev/null +++ b/storage/xtradb/include/dict0types.h @@ -0,0 +1,45 @@ +/***************************************************************************** + +Copyright (c) 1996, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/****************************************************** +Data dictionary global types + +Created 1/8/1996 Heikki Tuuri +*******************************************************/ + +#ifndef dict0types_h +#define dict0types_h + +#include "ut0list.h" + +typedef struct dict_sys_struct dict_sys_t; +typedef struct dict_col_struct dict_col_t; +typedef struct dict_field_struct dict_field_t; +typedef struct dict_index_struct dict_index_t; +typedef struct dict_table_struct dict_table_t; +typedef struct dict_foreign_struct dict_foreign_t; + +/* A cluster object is a table object with the type field set to +DICT_CLUSTERED */ + +typedef dict_table_t dict_cluster_t; + +typedef struct ind_node_struct ind_node_t; +typedef struct tab_node_struct tab_node_t; + +#endif diff --git a/storage/xtradb/include/dyn0dyn.h b/storage/xtradb/include/dyn0dyn.h new file mode 100644 index 00000000000..c06d6b88d2f --- /dev/null +++ b/storage/xtradb/include/dyn0dyn.h @@ -0,0 +1,182 @@ +/***************************************************************************** + +Copyright (c) 1996, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/****************************************************** +The dynamically allocated array + +Created 2/5/1996 Heikki Tuuri +*******************************************************/ + +#ifndef dyn0dyn_h +#define dyn0dyn_h + +#include "univ.i" +#include "ut0lst.h" +#include "mem0mem.h" + +typedef struct dyn_block_struct dyn_block_t; +typedef dyn_block_t dyn_array_t; + + +/* This is the initial 'payload' size of a dynamic array; +this must be > MLOG_BUF_MARGIN + 30! */ +#define DYN_ARRAY_DATA_SIZE 512 + +/************************************************************************* +Initializes a dynamic array. */ +UNIV_INLINE +dyn_array_t* +dyn_array_create( +/*=============*/ + /* out: initialized dyn array */ + dyn_array_t* arr); /* in: pointer to a memory buffer of + size sizeof(dyn_array_t) */ +/**************************************************************** +Frees a dynamic array. */ +UNIV_INLINE +void +dyn_array_free( +/*===========*/ + dyn_array_t* arr); /* in: dyn array */ +/************************************************************************* +Makes room on top of a dyn array and returns a pointer to a buffer in it. +After copying the elements, the caller must close the buffer using +dyn_array_close. */ +UNIV_INLINE +byte* +dyn_array_open( +/*===========*/ + /* out: pointer to the buffer */ + dyn_array_t* arr, /* in: dynamic array */ + ulint size); /* in: size in bytes of the buffer; MUST be + smaller than DYN_ARRAY_DATA_SIZE! */ +/************************************************************************* +Closes the buffer returned by dyn_array_open. */ +UNIV_INLINE +void +dyn_array_close( +/*============*/ + dyn_array_t* arr, /* in: dynamic array */ + byte* ptr); /* in: buffer space from ptr up was not used */ +/************************************************************************* +Makes room on top of a dyn array and returns a pointer to +the added element. The caller must copy the element to +the pointer returned. */ +UNIV_INLINE +void* +dyn_array_push( +/*===========*/ + /* out: pointer to the element */ + dyn_array_t* arr, /* in: dynamic array */ + ulint size); /* in: size in bytes of the element */ +/**************************************************************** +Returns pointer to an element in dyn array. */ +UNIV_INLINE +void* +dyn_array_get_element( +/*==================*/ + /* out: pointer to element */ + dyn_array_t* arr, /* in: dyn array */ + ulint pos); /* in: position of element as bytes + from array start */ +/**************************************************************** +Returns the size of stored data in a dyn array. */ +UNIV_INLINE +ulint +dyn_array_get_data_size( +/*====================*/ + /* out: data size in bytes */ + dyn_array_t* arr); /* in: dyn array */ +/**************************************************************** +Gets the first block in a dyn array. */ +UNIV_INLINE +dyn_block_t* +dyn_array_get_first_block( +/*======================*/ + dyn_array_t* arr); /* in: dyn array */ +/**************************************************************** +Gets the last block in a dyn array. */ +UNIV_INLINE +dyn_block_t* +dyn_array_get_last_block( +/*=====================*/ + dyn_array_t* arr); /* in: dyn array */ +/************************************************************************ +Gets the next block in a dyn array. */ +UNIV_INLINE +dyn_block_t* +dyn_array_get_next_block( +/*=====================*/ + /* out: pointer to next, NULL if end of list */ + dyn_array_t* arr, /* in: dyn array */ + dyn_block_t* block); /* in: dyn array block */ +/************************************************************************ +Gets the number of used bytes in a dyn array block. */ +UNIV_INLINE +ulint +dyn_block_get_used( +/*===============*/ + /* out: number of bytes used */ + dyn_block_t* block); /* in: dyn array block */ +/************************************************************************ +Gets pointer to the start of data in a dyn array block. */ +UNIV_INLINE +byte* +dyn_block_get_data( +/*===============*/ + /* out: pointer to data */ + dyn_block_t* block); /* in: dyn array block */ +/************************************************************ +Pushes n bytes to a dyn array. */ +UNIV_INLINE +void +dyn_push_string( +/*============*/ + dyn_array_t* arr, /* in: dyn array */ + const byte* str, /* in: string to write */ + ulint len); /* in: string length */ + +/*#################################################################*/ + +/* NOTE! Do not use the fields of the struct directly: the definition +appears here only for the compiler to know its size! */ +struct dyn_block_struct{ + mem_heap_t* heap; /* in the first block this is != NULL + if dynamic allocation has been needed */ + ulint used; /* number of data bytes used in this block */ + byte data[DYN_ARRAY_DATA_SIZE]; + /* storage for array elements */ + UT_LIST_BASE_NODE_T(dyn_block_t) base; + /* linear list of dyn blocks: this node is + used only in the first block */ + UT_LIST_NODE_T(dyn_block_t) list; + /* linear list node: used in all blocks */ +#ifdef UNIV_DEBUG + ulint buf_end;/* only in the debug version: if dyn array is + opened, this is the buffer end offset, else + this is 0 */ + ulint magic_n; +#endif +}; + + +#ifndef UNIV_NONINL +#include "dyn0dyn.ic" +#endif + +#endif diff --git a/storage/xtradb/include/dyn0dyn.ic b/storage/xtradb/include/dyn0dyn.ic new file mode 100644 index 00000000000..1ef8b284a99 --- /dev/null +++ b/storage/xtradb/include/dyn0dyn.ic @@ -0,0 +1,362 @@ +/***************************************************************************** + +Copyright (c) 1996, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/****************************************************** +The dynamically allocated array + +Created 2/5/1996 Heikki Tuuri +*******************************************************/ + +#define DYN_BLOCK_MAGIC_N 375767 +#define DYN_BLOCK_FULL_FLAG 0x1000000UL + +/**************************************************************** +Adds a new block to a dyn array. */ +UNIV_INTERN +dyn_block_t* +dyn_array_add_block( +/*================*/ + /* out: created block */ + dyn_array_t* arr); /* in: dyn array */ + + +/**************************************************************** +Gets the first block in a dyn array. */ +UNIV_INLINE +dyn_block_t* +dyn_array_get_first_block( +/*======================*/ + dyn_array_t* arr) /* in: dyn array */ +{ + return(arr); +} + +/**************************************************************** +Gets the last block in a dyn array. */ +UNIV_INLINE +dyn_block_t* +dyn_array_get_last_block( +/*=====================*/ + dyn_array_t* arr) /* in: dyn array */ +{ + if (arr->heap == NULL) { + + return(arr); + } + + return(UT_LIST_GET_LAST(arr->base)); +} + +/************************************************************************ +Gets the next block in a dyn array. */ +UNIV_INLINE +dyn_block_t* +dyn_array_get_next_block( +/*=====================*/ + /* out: pointer to next, NULL if end of list */ + dyn_array_t* arr, /* in: dyn array */ + dyn_block_t* block) /* in: dyn array block */ +{ + ut_ad(arr && block); + + if (arr->heap == NULL) { + ut_ad(arr == block); + + return(NULL); + } + + return(UT_LIST_GET_NEXT(list, block)); +} + +/************************************************************************ +Gets the number of used bytes in a dyn array block. */ +UNIV_INLINE +ulint +dyn_block_get_used( +/*===============*/ + /* out: number of bytes used */ + dyn_block_t* block) /* in: dyn array block */ +{ + ut_ad(block); + + return((block->used) & ~DYN_BLOCK_FULL_FLAG); +} + +/************************************************************************ +Gets pointer to the start of data in a dyn array block. */ +UNIV_INLINE +byte* +dyn_block_get_data( +/*===============*/ + /* out: pointer to data */ + dyn_block_t* block) /* in: dyn array block */ +{ + ut_ad(block); + + return(block->data); +} + +/************************************************************************* +Initializes a dynamic array. */ +UNIV_INLINE +dyn_array_t* +dyn_array_create( +/*=============*/ + /* out: initialized dyn array */ + dyn_array_t* arr) /* in: pointer to a memory buffer of + size sizeof(dyn_array_t) */ +{ + ut_ad(arr); +#if DYN_ARRAY_DATA_SIZE >= DYN_BLOCK_FULL_FLAG +# error "DYN_ARRAY_DATA_SIZE >= DYN_BLOCK_FULL_FLAG" +#endif + + arr->heap = NULL; + arr->used = 0; + +#ifdef UNIV_DEBUG + arr->buf_end = 0; + arr->magic_n = DYN_BLOCK_MAGIC_N; +#endif + return(arr); +} + +/**************************************************************** +Frees a dynamic array. */ +UNIV_INLINE +void +dyn_array_free( +/*===========*/ + dyn_array_t* arr) /* in: dyn array */ +{ + if (arr->heap != NULL) { + mem_heap_free(arr->heap); + } + +#ifdef UNIV_DEBUG + arr->magic_n = 0; +#endif +} + +/************************************************************************* +Makes room on top of a dyn array and returns a pointer to the added element. +The caller must copy the element to the pointer returned. */ +UNIV_INLINE +void* +dyn_array_push( +/*===========*/ + /* out: pointer to the element */ + dyn_array_t* arr, /* in: dynamic array */ + ulint size) /* in: size in bytes of the element */ +{ + dyn_block_t* block; + ulint used; + + ut_ad(arr); + ut_ad(arr->magic_n == DYN_BLOCK_MAGIC_N); + ut_ad(size <= DYN_ARRAY_DATA_SIZE); + ut_ad(size); + + block = arr; + used = block->used; + + if (used + size > DYN_ARRAY_DATA_SIZE) { + /* Get the last array block */ + + block = dyn_array_get_last_block(arr); + used = block->used; + + if (used + size > DYN_ARRAY_DATA_SIZE) { + block = dyn_array_add_block(arr); + used = block->used; + } + } + + block->used = used + size; + ut_ad(block->used <= DYN_ARRAY_DATA_SIZE); + + return((block->data) + used); +} + +/************************************************************************* +Makes room on top of a dyn array and returns a pointer to a buffer in it. +After copying the elements, the caller must close the buffer using +dyn_array_close. */ +UNIV_INLINE +byte* +dyn_array_open( +/*===========*/ + /* out: pointer to the buffer */ + dyn_array_t* arr, /* in: dynamic array */ + ulint size) /* in: size in bytes of the buffer; MUST be + smaller than DYN_ARRAY_DATA_SIZE! */ +{ + dyn_block_t* block; + ulint used; + + ut_ad(arr); + ut_ad(arr->magic_n == DYN_BLOCK_MAGIC_N); + ut_ad(size <= DYN_ARRAY_DATA_SIZE); + ut_ad(size); + + block = arr; + used = block->used; + + if (used + size > DYN_ARRAY_DATA_SIZE) { + /* Get the last array block */ + + block = dyn_array_get_last_block(arr); + used = block->used; + + if (used + size > DYN_ARRAY_DATA_SIZE) { + block = dyn_array_add_block(arr); + used = block->used; + ut_a(size <= DYN_ARRAY_DATA_SIZE); + } + } + + ut_ad(block->used <= DYN_ARRAY_DATA_SIZE); +#ifdef UNIV_DEBUG + ut_ad(arr->buf_end == 0); + + arr->buf_end = used + size; +#endif + return((block->data) + used); +} + +/************************************************************************* +Closes the buffer returned by dyn_array_open. */ +UNIV_INLINE +void +dyn_array_close( +/*============*/ + dyn_array_t* arr, /* in: dynamic array */ + byte* ptr) /* in: buffer space from ptr up was not used */ +{ + dyn_block_t* block; + + ut_ad(arr); + ut_ad(arr->magic_n == DYN_BLOCK_MAGIC_N); + + block = dyn_array_get_last_block(arr); + + ut_ad(arr->buf_end + block->data >= ptr); + + block->used = ptr - block->data; + + ut_ad(block->used <= DYN_ARRAY_DATA_SIZE); + +#ifdef UNIV_DEBUG + arr->buf_end = 0; +#endif +} + +/**************************************************************** +Returns pointer to an element in dyn array. */ +UNIV_INLINE +void* +dyn_array_get_element( +/*==================*/ + /* out: pointer to element */ + dyn_array_t* arr, /* in: dyn array */ + ulint pos) /* in: position of element as bytes + from array start */ +{ + dyn_block_t* block; + ulint used; + + ut_ad(arr); + ut_ad(arr->magic_n == DYN_BLOCK_MAGIC_N); + + /* Get the first array block */ + block = dyn_array_get_first_block(arr); + + if (arr->heap != NULL) { + used = dyn_block_get_used(block); + + while (pos >= used) { + pos -= used; + block = UT_LIST_GET_NEXT(list, block); + ut_ad(block); + + used = dyn_block_get_used(block); + } + } + + ut_ad(block); + ut_ad(dyn_block_get_used(block) >= pos); + + return(block->data + pos); +} + +/**************************************************************** +Returns the size of stored data in a dyn array. */ +UNIV_INLINE +ulint +dyn_array_get_data_size( +/*====================*/ + /* out: data size in bytes */ + dyn_array_t* arr) /* in: dyn array */ +{ + dyn_block_t* block; + ulint sum = 0; + + ut_ad(arr); + ut_ad(arr->magic_n == DYN_BLOCK_MAGIC_N); + + if (arr->heap == NULL) { + + return(arr->used); + } + + /* Get the first array block */ + block = dyn_array_get_first_block(arr); + + while (block != NULL) { + sum += dyn_block_get_used(block); + block = dyn_array_get_next_block(arr, block); + } + + return(sum); +} + +/************************************************************ +Pushes n bytes to a dyn array. */ +UNIV_INLINE +void +dyn_push_string( +/*============*/ + dyn_array_t* arr, /* in: dyn array */ + const byte* str, /* in: string to write */ + ulint len) /* in: string length */ +{ + ulint n_copied; + + while (len > 0) { + if (len > DYN_ARRAY_DATA_SIZE) { + n_copied = DYN_ARRAY_DATA_SIZE; + } else { + n_copied = len; + } + + memcpy(dyn_array_push(arr, n_copied), str, n_copied); + + str += n_copied; + len -= n_copied; + } +} diff --git a/storage/xtradb/include/eval0eval.h b/storage/xtradb/include/eval0eval.h new file mode 100644 index 00000000000..75cf9b38c3a --- /dev/null +++ b/storage/xtradb/include/eval0eval.h @@ -0,0 +1,113 @@ +/***************************************************************************** + +Copyright (c) 1997, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/****************************************************** +SQL evaluator: evaluates simple data structures, like expressions, in +a query graph + +Created 12/29/1997 Heikki Tuuri +*******************************************************/ + +#ifndef eval0eval_h +#define eval0eval_h + +#include "univ.i" +#include "que0types.h" +#include "pars0sym.h" +#include "pars0pars.h" + +/********************************************************************* +Free the buffer from global dynamic memory for a value of a que_node, +if it has been allocated in the above function. The freeing for pushed +column values is done in sel_col_prefetch_buf_free. */ +UNIV_INTERN +void +eval_node_free_val_buf( +/*===================*/ + que_node_t* node); /* in: query graph node */ +/********************************************************************* +Evaluates a symbol table symbol. */ +UNIV_INLINE +void +eval_sym( +/*=====*/ + sym_node_t* sym_node); /* in: symbol table node */ +/********************************************************************* +Evaluates an expression. */ +UNIV_INLINE +void +eval_exp( +/*=====*/ + que_node_t* exp_node); /* in: expression */ +/********************************************************************* +Sets an integer value as the value of an expression node. */ +UNIV_INLINE +void +eval_node_set_int_val( +/*==================*/ + que_node_t* node, /* in: expression node */ + lint val); /* in: value to set */ +/********************************************************************* +Gets an integer value from an expression node. */ +UNIV_INLINE +lint +eval_node_get_int_val( +/*==================*/ + /* out: integer value */ + que_node_t* node); /* in: expression node */ +/********************************************************************* +Copies a binary string value as the value of a query graph node. Allocates a +new buffer if necessary. */ +UNIV_INLINE +void +eval_node_copy_and_alloc_val( +/*=========================*/ + que_node_t* node, /* in: query graph node */ + const byte* str, /* in: binary string */ + ulint len); /* in: string length or UNIV_SQL_NULL */ +/********************************************************************* +Copies a query node value to another node. */ +UNIV_INLINE +void +eval_node_copy_val( +/*===============*/ + que_node_t* node1, /* in: node to copy to */ + que_node_t* node2); /* in: node to copy from */ +/********************************************************************* +Gets a iboolean value from a query node. */ +UNIV_INLINE +ibool +eval_node_get_ibool_val( +/*====================*/ + /* out: iboolean value */ + que_node_t* node); /* in: query graph node */ +/********************************************************************* +Evaluates a comparison node. */ +UNIV_INTERN +ibool +eval_cmp( +/*=====*/ + /* out: the result of the comparison */ + func_node_t* cmp_node); /* in: comparison node */ + + +#ifndef UNIV_NONINL +#include "eval0eval.ic" +#endif + +#endif diff --git a/storage/xtradb/include/eval0eval.ic b/storage/xtradb/include/eval0eval.ic new file mode 100644 index 00000000000..a6330ae441f --- /dev/null +++ b/storage/xtradb/include/eval0eval.ic @@ -0,0 +1,250 @@ +/***************************************************************************** + +Copyright (c) 1997, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/****************************************************** +SQL evaluator: evaluates simple data structures, like expressions, in +a query graph + +Created 12/29/1997 Heikki Tuuri +*******************************************************/ + +#include "que0que.h" +#include "rem0cmp.h" +#include "pars0grm.h" + +/********************************************************************* +Evaluates a function node. */ +UNIV_INTERN +void +eval_func( +/*======*/ + func_node_t* func_node); /* in: function node */ +/********************************************************************* +Allocate a buffer from global dynamic memory for a value of a que_node. +NOTE that this memory must be explicitly freed when the query graph is +freed. If the node already has allocated buffer, that buffer is freed +here. NOTE that this is the only function where dynamic memory should be +allocated for a query node val field. */ +UNIV_INTERN +byte* +eval_node_alloc_val_buf( +/*====================*/ + /* out: pointer to allocated buffer */ + que_node_t* node, /* in: query graph node; sets the val field + data field to point to the new buffer, and + len field equal to size */ + ulint size); /* in: buffer size */ + + +/********************************************************************* +Allocates a new buffer if needed. */ +UNIV_INLINE +byte* +eval_node_ensure_val_buf( +/*=====================*/ + /* out: pointer to buffer */ + que_node_t* node, /* in: query graph node; sets the val field + data field to point to the new buffer, and + len field equal to size */ + ulint size) /* in: buffer size */ +{ + dfield_t* dfield; + byte* data; + + dfield = que_node_get_val(node); + dfield_set_len(dfield, size); + + data = dfield_get_data(dfield); + + if (!data || que_node_get_val_buf_size(node) < size) { + + data = eval_node_alloc_val_buf(node, size); + } + + return(data); +} + +/********************************************************************* +Evaluates a symbol table symbol. */ +UNIV_INLINE +void +eval_sym( +/*=====*/ + sym_node_t* sym_node) /* in: symbol table node */ +{ + + ut_ad(que_node_get_type(sym_node) == QUE_NODE_SYMBOL); + + if (sym_node->indirection) { + /* The symbol table node is an alias for a variable or a + column */ + + dfield_copy_data(que_node_get_val(sym_node), + que_node_get_val(sym_node->indirection)); + } +} + +/********************************************************************* +Evaluates an expression. */ +UNIV_INLINE +void +eval_exp( +/*=====*/ + que_node_t* exp_node) /* in: expression */ +{ + if (que_node_get_type(exp_node) == QUE_NODE_SYMBOL) { + + eval_sym((sym_node_t*)exp_node); + + return; + } + + eval_func(exp_node); +} + +/********************************************************************* +Sets an integer value as the value of an expression node. */ +UNIV_INLINE +void +eval_node_set_int_val( +/*==================*/ + que_node_t* node, /* in: expression node */ + lint val) /* in: value to set */ +{ + dfield_t* dfield; + byte* data; + + dfield = que_node_get_val(node); + + data = dfield_get_data(dfield); + + if (data == NULL) { + data = eval_node_alloc_val_buf(node, 4); + } + + ut_ad(dfield_get_len(dfield) == 4); + + mach_write_to_4(data, (ulint)val); +} + +/********************************************************************* +Gets an integer non-SQL null value from an expression node. */ +UNIV_INLINE +lint +eval_node_get_int_val( +/*==================*/ + /* out: integer value */ + que_node_t* node) /* in: expression node */ +{ + dfield_t* dfield; + + dfield = que_node_get_val(node); + + ut_ad(dfield_get_len(dfield) == 4); + + return((int)mach_read_from_4(dfield_get_data(dfield))); +} + +/********************************************************************* +Gets a iboolean value from a query node. */ +UNIV_INLINE +ibool +eval_node_get_ibool_val( +/*====================*/ + /* out: iboolean value */ + que_node_t* node) /* in: query graph node */ +{ + dfield_t* dfield; + byte* data; + + dfield = que_node_get_val(node); + + data = dfield_get_data(dfield); + + ut_ad(data != NULL); + + return(mach_read_from_1(data)); +} + +/********************************************************************* +Sets a iboolean value as the value of a function node. */ +UNIV_INLINE +void +eval_node_set_ibool_val( +/*====================*/ + func_node_t* func_node, /* in: function node */ + ibool val) /* in: value to set */ +{ + dfield_t* dfield; + byte* data; + + dfield = que_node_get_val(func_node); + + data = dfield_get_data(dfield); + + if (data == NULL) { + /* Allocate 1 byte to hold the value */ + + data = eval_node_alloc_val_buf(func_node, 1); + } + + ut_ad(dfield_get_len(dfield) == 1); + + mach_write_to_1(data, val); +} + +/********************************************************************* +Copies a binary string value as the value of a query graph node. Allocates a +new buffer if necessary. */ +UNIV_INLINE +void +eval_node_copy_and_alloc_val( +/*=========================*/ + que_node_t* node, /* in: query graph node */ + const byte* str, /* in: binary string */ + ulint len) /* in: string length or UNIV_SQL_NULL */ +{ + byte* data; + + if (len == UNIV_SQL_NULL) { + dfield_set_len(que_node_get_val(node), len); + + return; + } + + data = eval_node_ensure_val_buf(node, len); + + ut_memcpy(data, str, len); +} + +/********************************************************************* +Copies a query node value to another node. */ +UNIV_INLINE +void +eval_node_copy_val( +/*===============*/ + que_node_t* node1, /* in: node to copy to */ + que_node_t* node2) /* in: node to copy from */ +{ + dfield_t* dfield2; + + dfield2 = que_node_get_val(node2); + + eval_node_copy_and_alloc_val(node1, dfield_get_data(dfield2), + dfield_get_len(dfield2)); +} diff --git a/storage/xtradb/include/eval0proc.h b/storage/xtradb/include/eval0proc.h new file mode 100644 index 00000000000..58937c18124 --- /dev/null +++ b/storage/xtradb/include/eval0proc.h @@ -0,0 +1,103 @@ +/***************************************************************************** + +Copyright (c) 1998, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/****************************************************** +Executes SQL stored procedures and their control structures + +Created 1/20/1998 Heikki Tuuri +*******************************************************/ + +#ifndef eval0proc_h +#define eval0proc_h + +#include "univ.i" +#include "que0types.h" +#include "pars0sym.h" +#include "pars0pars.h" + +/************************************************************************** +Performs an execution step of a procedure node. */ +UNIV_INLINE +que_thr_t* +proc_step( +/*======*/ + /* out: query thread to run next or NULL */ + que_thr_t* thr); /* in: query thread */ +/************************************************************************** +Performs an execution step of an if-statement node. */ +UNIV_INTERN +que_thr_t* +if_step( +/*====*/ + /* out: query thread to run next or NULL */ + que_thr_t* thr); /* in: query thread */ +/************************************************************************** +Performs an execution step of a while-statement node. */ +UNIV_INTERN +que_thr_t* +while_step( +/*=======*/ + /* out: query thread to run next or NULL */ + que_thr_t* thr); /* in: query thread */ +/************************************************************************** +Performs an execution step of a for-loop node. */ +UNIV_INTERN +que_thr_t* +for_step( +/*=====*/ + /* out: query thread to run next or NULL */ + que_thr_t* thr); /* in: query thread */ +/************************************************************************** +Performs an execution step of an assignment statement node. */ +UNIV_INTERN +que_thr_t* +assign_step( +/*========*/ + /* out: query thread to run next or NULL */ + que_thr_t* thr); /* in: query thread */ +/************************************************************************** +Performs an execution step of a procedure call node. */ +UNIV_INLINE +que_thr_t* +proc_eval_step( +/*===========*/ + /* out: query thread to run next or NULL */ + que_thr_t* thr); /* in: query thread */ +/************************************************************************** +Performs an execution step of an exit statement node. */ +UNIV_INTERN +que_thr_t* +exit_step( +/*======*/ + /* out: query thread to run next or NULL */ + que_thr_t* thr); /* in: query thread */ +/************************************************************************** +Performs an execution step of a return-statement node. */ +UNIV_INTERN +que_thr_t* +return_step( +/*========*/ + /* out: query thread to run next or NULL */ + que_thr_t* thr); /* in: query thread */ + + +#ifndef UNIV_NONINL +#include "eval0proc.ic" +#endif + +#endif diff --git a/storage/xtradb/include/eval0proc.ic b/storage/xtradb/include/eval0proc.ic new file mode 100644 index 00000000000..6bd978ad3fc --- /dev/null +++ b/storage/xtradb/include/eval0proc.ic @@ -0,0 +1,87 @@ +/***************************************************************************** + +Copyright (c) 1998, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/****************************************************** +Executes SQL stored procedures and their control structures + +Created 1/20/1998 Heikki Tuuri +*******************************************************/ + +#include "pars0pars.h" +#include "que0que.h" +#include "eval0eval.h" + +/************************************************************************** +Performs an execution step of a procedure node. */ +UNIV_INLINE +que_thr_t* +proc_step( +/*======*/ + /* out: query thread to run next or NULL */ + que_thr_t* thr) /* in: query thread */ +{ + proc_node_t* node; + + ut_ad(thr); + + node = thr->run_node; + ut_ad(que_node_get_type(node) == QUE_NODE_PROC); + + if (thr->prev_node == que_node_get_parent(node)) { + /* Start execution from the first statement in the statement + list */ + + thr->run_node = node->stat_list; + } else { + /* Move to the next statement */ + ut_ad(que_node_get_next(thr->prev_node) == NULL); + + thr->run_node = NULL; + } + + if (thr->run_node == NULL) { + thr->run_node = que_node_get_parent(node); + } + + return(thr); +} + +/************************************************************************** +Performs an execution step of a procedure call node. */ +UNIV_INLINE +que_thr_t* +proc_eval_step( +/*===========*/ + /* out: query thread to run next or NULL */ + que_thr_t* thr) /* in: query thread */ +{ + func_node_t* node; + + ut_ad(thr); + + node = thr->run_node; + ut_ad(que_node_get_type(node) == QUE_NODE_FUNC); + + /* Evaluate the procedure */ + + eval_exp(node); + + thr->run_node = que_node_get_parent(node); + + return(thr); +} diff --git a/storage/xtradb/include/fil0fil.h b/storage/xtradb/include/fil0fil.h new file mode 100644 index 00000000000..587e5ee48a8 --- /dev/null +++ b/storage/xtradb/include/fil0fil.h @@ -0,0 +1,711 @@ +/***************************************************************************** + +Copyright (c) 1995, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/****************************************************** +The low-level file system + +Created 10/25/1995 Heikki Tuuri +*******************************************************/ + +#ifndef fil0fil_h +#define fil0fil_h + +#include "univ.i" +#include "sync0rw.h" +#include "dict0types.h" +#include "ut0byte.h" +#include "os0file.h" + +/* When mysqld is run, the default directory "." is the mysqld datadir, but in +ibbackup we must set it explicitly; the patgh must NOT contain the trailing +'/' or '\' */ +extern const char* fil_path_to_mysql_datadir; + +/* Initial size of a single-table tablespace in pages */ +#define FIL_IBD_FILE_INITIAL_SIZE 4 + +/* 'null' (undefined) page offset in the context of file spaces */ +#define FIL_NULL ULINT32_UNDEFINED + +/* Space address data type; this is intended to be used when +addresses accurate to a byte are stored in file pages. If the page part +of the address is FIL_NULL, the address is considered undefined. */ + +typedef byte fil_faddr_t; /* 'type' definition in C: an address + stored in a file page is a string of bytes */ +#define FIL_ADDR_PAGE 0 /* first in address is the page offset */ +#define FIL_ADDR_BYTE 4 /* then comes 2-byte byte offset within page*/ + +#define FIL_ADDR_SIZE 6 /* address size is 6 bytes */ + +/* A struct for storing a space address FIL_ADDR, when it is used +in C program data structures. */ + +typedef struct fil_addr_struct fil_addr_t; +struct fil_addr_struct{ + ulint page; /* page number within a space */ + ulint boffset; /* byte offset within the page */ +}; + +/* Null file address */ +extern fil_addr_t fil_addr_null; + +/* The byte offsets on a file page for various variables */ +#define FIL_PAGE_SPACE_OR_CHKSUM 0 /* in < MySQL-4.0.14 space id the + page belongs to (== 0) but in later + versions the 'new' checksum of the + page */ +#define FIL_PAGE_OFFSET 4 /* page offset inside space */ +#define FIL_PAGE_PREV 8 /* if there is a 'natural' predecessor + of the page, its offset. + Otherwise FIL_NULL. + This field is not set on BLOB pages, + which are stored as a singly-linked + list. See also FIL_PAGE_NEXT. */ +#define FIL_PAGE_NEXT 12 /* if there is a 'natural' successor + of the page, its offset. + Otherwise FIL_NULL. + B-tree index pages + (FIL_PAGE_TYPE contains FIL_PAGE_INDEX) + on the same PAGE_LEVEL are maintained + as a doubly linked list via + FIL_PAGE_PREV and FIL_PAGE_NEXT + in the collation order of the + smallest user record on each page. */ +#define FIL_PAGE_LSN 16 /* lsn of the end of the newest + modification log record to the page */ +#define FIL_PAGE_TYPE 24 /* file page type: FIL_PAGE_INDEX,..., + 2 bytes. + + The contents of this field can only + be trusted in the following case: + if the page is an uncompressed + B-tree index page, then it is + guaranteed that the value is + FIL_PAGE_INDEX. + The opposite does not hold. + + In tablespaces created by + MySQL/InnoDB 5.1.7 or later, the + contents of this field is valid + for all uncompressed pages. */ +#define FIL_PAGE_FILE_FLUSH_LSN 26 /* this is only defined for the + first page in a data file: the file + has been flushed to disk at least up + to this lsn */ +#define FIL_PAGE_ARCH_LOG_NO_OR_SPACE_ID 34 /* starting from 4.1.x this + contains the space id of the page */ +#define FIL_PAGE_DATA 38 /* start of the data on the page */ + +/* File page trailer */ +#define FIL_PAGE_END_LSN_OLD_CHKSUM 8 /* the low 4 bytes of this are used + to store the page checksum, the + last 4 bytes should be identical + to the last 4 bytes of FIL_PAGE_LSN */ +#define FIL_PAGE_DATA_END 8 + +/* File page types (values of FIL_PAGE_TYPE) */ +#define FIL_PAGE_INDEX 17855 /* B-tree node */ +#define FIL_PAGE_UNDO_LOG 2 /* Undo log page */ +#define FIL_PAGE_INODE 3 /* Index node */ +#define FIL_PAGE_IBUF_FREE_LIST 4 /* Insert buffer free list */ +/* File page types introduced in MySQL/InnoDB 5.1.7 */ +#define FIL_PAGE_TYPE_ALLOCATED 0 /* Freshly allocated page */ +#define FIL_PAGE_IBUF_BITMAP 5 /* Insert buffer bitmap */ +#define FIL_PAGE_TYPE_SYS 6 /* System page */ +#define FIL_PAGE_TYPE_TRX_SYS 7 /* Transaction system data */ +#define FIL_PAGE_TYPE_FSP_HDR 8 /* File space header */ +#define FIL_PAGE_TYPE_XDES 9 /* Extent descriptor page */ +#define FIL_PAGE_TYPE_BLOB 10 /* Uncompressed BLOB page */ +#define FIL_PAGE_TYPE_ZBLOB 11 /* First compressed BLOB page */ +#define FIL_PAGE_TYPE_ZBLOB2 12 /* Subsequent compressed BLOB page */ + +/* Space types */ +#define FIL_TABLESPACE 501 +#define FIL_LOG 502 + +extern ulint fil_n_log_flushes; + +extern ulint fil_n_pending_log_flushes; +extern ulint fil_n_pending_tablespace_flushes; + + +/*********************************************************************** +Returns the version number of a tablespace, -1 if not found. */ +UNIV_INTERN +ib_int64_t +fil_space_get_version( +/*==================*/ + /* out: version number, -1 if the tablespace does not + exist in the memory cache */ + ulint id); /* in: space id */ +/*********************************************************************** +Returns the latch of a file space. */ +UNIV_INTERN +rw_lock_t* +fil_space_get_latch( +/*================*/ + /* out: latch protecting storage allocation */ + ulint id, /* in: space id */ + ulint* zip_size);/* out: compressed page size, or + 0 for uncompressed tablespaces */ +/*********************************************************************** +Returns the type of a file space. */ +UNIV_INTERN +ulint +fil_space_get_type( +/*===============*/ + /* out: FIL_TABLESPACE or FIL_LOG */ + ulint id); /* in: space id */ +/*********************************************************************** +Appends a new file to the chain of files of a space. File must be closed. */ +UNIV_INTERN +void +fil_node_create( +/*============*/ + const char* name, /* in: file name (file must be closed) */ + ulint size, /* in: file size in database blocks, rounded + downwards to an integer */ + ulint id, /* in: space id where to append */ + ibool is_raw);/* in: TRUE if a raw device or + a raw disk partition */ +#ifdef UNIV_LOG_ARCHIVE +/******************************************************************** +Drops files from the start of a file space, so that its size is cut by +the amount given. */ +UNIV_INTERN +void +fil_space_truncate_start( +/*=====================*/ + ulint id, /* in: space id */ + ulint trunc_len); /* in: truncate by this much; it is an error + if this does not equal to the combined size of + some initial files in the space */ +#endif /* UNIV_LOG_ARCHIVE */ +/*********************************************************************** +Creates a space memory object and puts it to the 'fil system' hash table. If +there is an error, prints an error message to the .err log. */ +UNIV_INTERN +ibool +fil_space_create( +/*=============*/ + /* out: TRUE if success */ + const char* name, /* in: space name */ + ulint id, /* in: space id */ + ulint zip_size,/* in: compressed page size, or + 0 for uncompressed tablespaces */ + ulint purpose);/* in: FIL_TABLESPACE, or FIL_LOG if log */ +/*********************************************************************** +Frees a space object from a the tablespace memory cache. Closes the files in +the chain but does not delete them. */ +UNIV_INTERN +ibool +fil_space_free( +/*===========*/ + /* out: TRUE if success */ + ulint id); /* in: space id */ +/*********************************************************************** +Returns the size of the space in pages. The tablespace must be cached in the +memory cache. */ +UNIV_INTERN +ulint +fil_space_get_size( +/*===============*/ + /* out: space size, 0 if space not found */ + ulint id); /* in: space id */ +/*********************************************************************** +Returns the flags of the space. The tablespace must be cached +in the memory cache. */ +UNIV_INTERN +ulint +fil_space_get_flags( +/*================*/ + /* out: flags, ULINT_UNDEFINED if space not found */ + ulint id); /* in: space id */ +/*********************************************************************** +Returns the compressed page size of the space, or 0 if the space +is not compressed. The tablespace must be cached in the memory cache. */ +UNIV_INTERN +ulint +fil_space_get_zip_size( +/*===================*/ + /* out: compressed page size, ULINT_UNDEFINED + if space not found */ + ulint id); /* in: space id */ +/*********************************************************************** +Checks if the pair space, page_no refers to an existing page in a tablespace +file space. The tablespace must be cached in the memory cache. */ +UNIV_INTERN +ibool +fil_check_adress_in_tablespace( +/*===========================*/ + /* out: TRUE if the address is meaningful */ + ulint id, /* in: space id */ + ulint page_no);/* in: page number */ +/******************************************************************** +Initializes the tablespace memory cache. */ +UNIV_INTERN +void +fil_init( +/*=====*/ + ulint max_n_open); /* in: max number of open files */ +/*********************************************************************** +Opens all log files and system tablespace data files. They stay open until the +database server shutdown. This should be called at a server startup after the +space objects for the log and the system tablespace have been created. The +purpose of this operation is to make sure we never run out of file descriptors +if we need to read from the insert buffer or to write to the log. */ +UNIV_INTERN +void +fil_open_log_and_system_tablespace_files(void); +/*==========================================*/ +/*********************************************************************** +Closes all open files. There must not be any pending i/o's or not flushed +modifications in the files. */ +UNIV_INTERN +void +fil_close_all_files(void); +/*=====================*/ +/*********************************************************************** +Sets the max tablespace id counter if the given number is bigger than the +previous value. */ +UNIV_INTERN +void +fil_set_max_space_id_if_bigger( +/*===========================*/ + ulint max_id);/* in: maximum known id */ +/******************************************************************** +Writes the flushed lsn and the latest archived log number to the page +header of the first page of each data file in the system tablespace. */ +UNIV_INTERN +ulint +fil_write_flushed_lsn_to_data_files( +/*================================*/ + /* out: DB_SUCCESS or error number */ + ib_uint64_t lsn, /* in: lsn to write */ + ulint arch_log_no); /* in: latest archived log + file number */ +/*********************************************************************** +Reads the flushed lsn and arch no fields from a data file at database +startup. */ +UNIV_INTERN +void +fil_read_flushed_lsn_and_arch_log_no( +/*=================================*/ + os_file_t data_file, /* in: open data file */ + ibool one_read_already, /* in: TRUE if min and max + parameters below already + contain sensible data */ +#ifdef UNIV_LOG_ARCHIVE + ulint* min_arch_log_no, /* in/out: */ + ulint* max_arch_log_no, /* in/out: */ +#endif /* UNIV_LOG_ARCHIVE */ + ib_uint64_t* min_flushed_lsn, /* in/out: */ + ib_uint64_t* max_flushed_lsn); /* in/out: */ +/*********************************************************************** +Increments the count of pending insert buffer page merges, if space is not +being deleted. */ +UNIV_INTERN +ibool +fil_inc_pending_ibuf_merges( +/*========================*/ + /* out: TRUE if being deleted, and ibuf merges should + be skipped */ + ulint id); /* in: space id */ +/*********************************************************************** +Decrements the count of pending insert buffer page merges. */ +UNIV_INTERN +void +fil_decr_pending_ibuf_merges( +/*=========================*/ + ulint id); /* in: space id */ +/*********************************************************************** +Parses the body of a log record written about an .ibd file operation. That is, +the log record part after the standard (type, space id, page no) header of the +log record. + +If desired, also replays the delete or rename operation if the .ibd file +exists and the space id in it matches. Replays the create operation if a file +at that path does not exist yet. If the database directory for the file to be +created does not exist, then we create the directory, too. + +Note that ibbackup --apply-log sets fil_path_to_mysql_datadir to point to the +datadir that we should use in replaying the file operations. */ +UNIV_INTERN +byte* +fil_op_log_parse_or_replay( +/*=======================*/ + /* out: end of log record, or NULL if the + record was not completely contained between + ptr and end_ptr */ + byte* ptr, /* in: buffer containing the log record body, + or an initial segment of it, if the record does + not fir completely between ptr and end_ptr */ + byte* end_ptr, /* in: buffer end */ + ulint type, /* in: the type of this log record */ + ulint space_id); /* in: the space id of the tablespace in + question, or 0 if the log record should + only be parsed but not replayed */ +/*********************************************************************** +Deletes a single-table tablespace. The tablespace must be cached in the +memory cache. */ +UNIV_INTERN +ibool +fil_delete_tablespace( +/*==================*/ + /* out: TRUE if success */ + ulint id); /* in: space id */ +/*********************************************************************** +Discards a single-table tablespace. The tablespace must be cached in the +memory cache. Discarding is like deleting a tablespace, but +1) we do not drop the table from the data dictionary; +2) we remove all insert buffer entries for the tablespace immediately; in DROP +TABLE they are only removed gradually in the background; +3) when the user does IMPORT TABLESPACE, the tablespace will have the same id +as it originally had. */ +UNIV_INTERN +ibool +fil_discard_tablespace( +/*===================*/ + /* out: TRUE if success */ + ulint id); /* in: space id */ +/*********************************************************************** +Renames a single-table tablespace. The tablespace must be cached in the +tablespace memory cache. */ +UNIV_INTERN +ibool +fil_rename_tablespace( +/*==================*/ + /* out: TRUE if success */ + const char* old_name, /* in: old table name in the standard + databasename/tablename format of + InnoDB, or NULL if we do the rename + based on the space id only */ + ulint id, /* in: space id */ + const char* new_name); /* in: new table name in the standard + databasename/tablename format + of InnoDB */ + +/*********************************************************************** +Creates a new single-table tablespace to a database directory of MySQL. +Database directories are under the 'datadir' of MySQL. The datadir is the +directory of a running mysqld program. We can refer to it by simply the +path '.'. Tables created with CREATE TEMPORARY TABLE we place in the temp +dir of the mysqld server. */ +UNIV_INTERN +ulint +fil_create_new_single_table_tablespace( +/*===================================*/ + /* out: DB_SUCCESS or error code */ + ulint* space_id, /* in/out: space id; if this is != 0, + then this is an input parameter, + otherwise output */ + const char* tablename, /* in: the table name in the usual + databasename/tablename format + of InnoDB, or a dir path to a temp + table */ + ibool is_temp, /* in: TRUE if a table created with + CREATE TEMPORARY TABLE */ + ulint flags, /* in: tablespace flags */ + ulint size); /* in: the initial size of the + tablespace file in pages, + must be >= FIL_IBD_FILE_INITIAL_SIZE */ +/************************************************************************ +Tries to open a single-table tablespace and optionally checks the space id is +right in it. If does not succeed, prints an error message to the .err log. This +function is used to open a tablespace when we start up mysqld, and also in +IMPORT TABLESPACE. +NOTE that we assume this operation is used either at the database startup +or under the protection of the dictionary mutex, so that two users cannot +race here. This operation does not leave the file associated with the +tablespace open, but closes it after we have looked at the space id in it. */ +UNIV_INTERN +ibool +fil_open_single_table_tablespace( +/*=============================*/ + /* out: TRUE if success */ + ibool check_space_id, /* in: should we check that the space + id in the file is right; we assume + that this function runs much faster + if no check is made, since accessing + the file inode probably is much + faster (the OS caches them) than + accessing the first page of the file */ + ulint id, /* in: space id */ + ulint flags, /* in: tablespace flags */ + const char* name); /* in: table name in the + databasename/tablename format */ +/************************************************************************ +It is possible, though very improbable, that the lsn's in the tablespace to be +imported have risen above the current system lsn, if a lengthy purge, ibuf +merge, or rollback was performed on a backup taken with ibbackup. If that is +the case, reset page lsn's in the file. We assume that mysqld was shut down +after it performed these cleanup operations on the .ibd file, so that it at +the shutdown stamped the latest lsn to the FIL_PAGE_FILE_FLUSH_LSN in the +first page of the .ibd file, and we can determine whether we need to reset the +lsn's just by looking at that flush lsn. */ +UNIV_INTERN +ibool +fil_reset_too_high_lsns( +/*====================*/ + /* out: TRUE if success */ + const char* name, /* in: table name in the + databasename/tablename format */ + ib_uint64_t current_lsn); /* in: reset lsn's if the lsn stamped + to FIL_PAGE_FILE_FLUSH_LSN in the + first page is too high */ +/************************************************************************ +At the server startup, if we need crash recovery, scans the database +directories under the MySQL datadir, looking for .ibd files. Those files are +single-table tablespaces. We need to know the space id in each of them so that +we know into which file we should look to check the contents of a page stored +in the doublewrite buffer, also to know where to apply log records where the +space id is != 0. */ +UNIV_INTERN +ulint +fil_load_single_table_tablespaces(void); +/*===================================*/ + /* out: DB_SUCCESS or error number */ +/************************************************************************ +If we need crash recovery, and we have called +fil_load_single_table_tablespaces() and dict_load_single_table_tablespaces(), +we can call this function to print an error message of orphaned .ibd files +for which there is not a data dictionary entry with a matching table name +and space id. */ +UNIV_INTERN +void +fil_print_orphaned_tablespaces(void); +/*================================*/ +/*********************************************************************** +Returns TRUE if a single-table tablespace does not exist in the memory cache, +or is being deleted there. */ +UNIV_INTERN +ibool +fil_tablespace_deleted_or_being_deleted_in_mem( +/*===========================================*/ + /* out: TRUE if does not exist or is being\ + deleted */ + ulint id, /* in: space id */ + ib_int64_t version);/* in: tablespace_version should be this; if + you pass -1 as the value of this, then this + parameter is ignored */ +/*********************************************************************** +Returns TRUE if a single-table tablespace exists in the memory cache. */ +UNIV_INTERN +ibool +fil_tablespace_exists_in_mem( +/*=========================*/ + /* out: TRUE if exists */ + ulint id); /* in: space id */ +/*********************************************************************** +Returns TRUE if a matching tablespace exists in the InnoDB tablespace memory +cache. Note that if we have not done a crash recovery at the database startup, +there may be many tablespaces which are not yet in the memory cache. */ +UNIV_INTERN +ibool +fil_space_for_table_exists_in_mem( +/*==============================*/ + /* out: TRUE if a matching tablespace + exists in the memory cache */ + ulint id, /* in: space id */ + const char* name, /* in: table name in the standard + 'databasename/tablename' format or + the dir path to a temp table */ + ibool is_temp, /* in: TRUE if created with CREATE + TEMPORARY TABLE */ + ibool mark_space, /* in: in crash recovery, at database + startup we mark all spaces which have + an associated table in the InnoDB + data dictionary, so that + we can print a warning about orphaned + tablespaces */ + ibool print_error_if_does_not_exist); + /* in: print detailed error + information to the .err log if a + matching tablespace is not found from + memory */ +/************************************************************************** +Tries to extend a data file so that it would accommodate the number of pages +given. The tablespace must be cached in the memory cache. If the space is big +enough already, does nothing. */ +UNIV_INTERN +ibool +fil_extend_space_to_desired_size( +/*=============================*/ + /* out: TRUE if success */ + ulint* actual_size, /* out: size of the space after extension; + if we ran out of disk space this may be lower + than the desired size */ + ulint space_id, /* in: space id */ + ulint size_after_extend);/* in: desired size in pages after the + extension; if the current space size is bigger + than this already, the function does nothing */ +#ifdef UNIV_HOTBACKUP +/************************************************************************ +Extends all tablespaces to the size stored in the space header. During the +ibbackup --apply-log phase we extended the spaces on-demand so that log records +could be appllied, but that may have left spaces still too small compared to +the size stored in the space header. */ +UNIV_INTERN +void +fil_extend_tablespaces_to_stored_len(void); +/*======================================*/ +#endif +/*********************************************************************** +Tries to reserve free extents in a file space. */ +UNIV_INTERN +ibool +fil_space_reserve_free_extents( +/*===========================*/ + /* out: TRUE if succeed */ + ulint id, /* in: space id */ + ulint n_free_now, /* in: number of free extents now */ + ulint n_to_reserve); /* in: how many one wants to reserve */ +/*********************************************************************** +Releases free extents in a file space. */ +UNIV_INTERN +void +fil_space_release_free_extents( +/*===========================*/ + ulint id, /* in: space id */ + ulint n_reserved); /* in: how many one reserved */ +/*********************************************************************** +Gets the number of reserved extents. If the database is silent, this number +should be zero. */ +UNIV_INTERN +ulint +fil_space_get_n_reserved_extents( +/*=============================*/ + ulint id); /* in: space id */ +/************************************************************************ +Reads or writes data. This operation is asynchronous (aio). */ +UNIV_INTERN +ulint +fil_io( +/*===*/ + /* out: DB_SUCCESS, or DB_TABLESPACE_DELETED + if we are trying to do i/o on a tablespace + which does not exist */ + ulint type, /* in: OS_FILE_READ or OS_FILE_WRITE, + ORed to OS_FILE_LOG, if a log i/o + and ORed to OS_AIO_SIMULATED_WAKE_LATER + if simulated aio and we want to post a + batch of i/os; NOTE that a simulated batch + may introduce hidden chances of deadlocks, + because i/os are not actually handled until + all have been posted: use with great + caution! */ + ibool sync, /* in: TRUE if synchronous aio is desired */ + ulint space_id, /* in: space id */ + ulint zip_size, /* in: compressed page size in bytes; + 0 for uncompressed pages */ + ulint block_offset, /* in: offset in number of blocks */ + ulint byte_offset, /* in: remainder of offset in bytes; in + aio this must be divisible by the OS block + size */ + ulint len, /* in: how many bytes to read or write; this + must not cross a file boundary; in aio this + must be a block size multiple */ + void* buf, /* in/out: buffer where to store read data + or from where to write; in aio this must be + appropriately aligned */ + void* message); /* in: message for aio handler if non-sync + aio used, else ignored */ +/************************************************************************** +Waits for an aio operation to complete. This function is used to write the +handler for completed requests. The aio array of pending requests is divided +into segments (see os0file.c for more info). The thread specifies which +segment it wants to wait for. */ +UNIV_INTERN +void +fil_aio_wait( +/*=========*/ + ulint segment); /* in: the number of the segment in the aio + array to wait for */ +/************************************************************************** +Flushes to disk possible writes cached by the OS. If the space does not exist +or is being dropped, does not do anything. */ +UNIV_INTERN +void +fil_flush( +/*======*/ + ulint space_id); /* in: file space id (this can be a group of + log files or a tablespace of the database) */ +/************************************************************************** +Flushes to disk writes in file spaces of the given type possibly cached by +the OS. */ +UNIV_INTERN +void +fil_flush_file_spaces( +/*==================*/ + ulint purpose); /* in: FIL_TABLESPACE, FIL_LOG */ +/********************************************************************** +Checks the consistency of the tablespace cache. */ +UNIV_INTERN +ibool +fil_validate(void); +/*==============*/ + /* out: TRUE if ok */ +/************************************************************************ +Returns TRUE if file address is undefined. */ +UNIV_INTERN +ibool +fil_addr_is_null( +/*=============*/ + /* out: TRUE if undefined */ + fil_addr_t addr); /* in: address */ +/************************************************************************ +Accessor functions for a file page */ +UNIV_INTERN +ulint +fil_page_get_prev(const byte* page); +ulint +fil_page_get_next(const byte* page); +/************************************************************************* +Sets the file page type. */ +UNIV_INTERN +void +fil_page_set_type( +/*==============*/ + byte* page, /* in: file page */ + ulint type); /* in: type */ +/************************************************************************* +Gets the file page type. */ +UNIV_INTERN +ulint +fil_page_get_type( +/*==============*/ + /* out: type; NOTE that if the type + has not been written to page, the + return value not defined */ + const byte* page); /* in: file page */ + +/************************************************************************* +Return local hash table informations. */ + +ulint +fil_system_hash_cells(void); +/*========================*/ + +ulint +fil_system_hash_nodes(void); +/*========================*/ + +typedef struct fil_space_struct fil_space_t; + +#endif diff --git a/storage/xtradb/include/fsp0fsp.h b/storage/xtradb/include/fsp0fsp.h new file mode 100644 index 00000000000..1f6ae4b614b --- /dev/null +++ b/storage/xtradb/include/fsp0fsp.h @@ -0,0 +1,433 @@ +/***************************************************************************** + +Copyright (c) 1995, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/****************************************************** +File space management + +Created 12/18/1995 Heikki Tuuri +*******************************************************/ + +#ifndef fsp0fsp_h +#define fsp0fsp_h + +#include "univ.i" + +#include "mtr0mtr.h" +#include "fut0lst.h" +#include "ut0byte.h" +#include "page0types.h" + +/* If records are inserted in order, there are the following +flags to tell this (their type is made byte for the compiler +to warn if direction and hint parameters are switched in +fseg_alloc_free_page): */ +#define FSP_UP ((byte)111) /* alphabetically upwards */ +#define FSP_DOWN ((byte)112) /* alphabetically downwards */ +#define FSP_NO_DIR ((byte)113) /* no order */ + +/* File space extent size (one megabyte) in pages */ +#define FSP_EXTENT_SIZE (1 << (20 - UNIV_PAGE_SIZE_SHIFT)) + +/* On a page of any file segment, data may be put starting from this offset: */ +#define FSEG_PAGE_DATA FIL_PAGE_DATA + +/* File segment header which points to the inode describing the file segment */ +typedef byte fseg_header_t; + +#define FSEG_HDR_SPACE 0 /* space id of the inode */ +#define FSEG_HDR_PAGE_NO 4 /* page number of the inode */ +#define FSEG_HDR_OFFSET 8 /* byte offset of the inode */ + +#define FSEG_HEADER_SIZE 10 + +/************************************************************************** +Initializes the file space system. */ +UNIV_INTERN +void +fsp_init(void); +/*==========*/ +/************************************************************************** +Gets the current free limit of the system tablespace. The free limit +means the place of the first page which has never been put to the the +free list for allocation. The space above that address is initialized +to zero. Sets also the global variable log_fsp_current_free_limit. */ +UNIV_INTERN +ulint +fsp_header_get_free_limit(void); +/*===========================*/ + /* out: free limit in megabytes */ +/************************************************************************** +Gets the size of the system tablespace from the tablespace header. If +we do not have an auto-extending data file, this should be equal to +the size of the data files. If there is an auto-extending data file, +this can be smaller. */ +UNIV_INTERN +ulint +fsp_header_get_tablespace_size(void); +/*================================*/ + /* out: size in pages */ +/************************************************************************** +Reads the file space size stored in the header page. */ +UNIV_INTERN +ulint +fsp_get_size_low( +/*=============*/ + /* out: tablespace size stored in the space header */ + page_t* page); /* in: header page (page 0 in the tablespace) */ +/************************************************************************** +Reads the space id from the first page of a tablespace. */ +UNIV_INTERN +ulint +fsp_header_get_space_id( +/*====================*/ + /* out: space id, ULINT UNDEFINED if error */ + const page_t* page); /* in: first page of a tablespace */ +/************************************************************************** +Reads the space flags from the first page of a tablespace. */ +UNIV_INTERN +ulint +fsp_header_get_flags( +/*=================*/ + /* out: flags */ + const page_t* page); /* in: first page of a tablespace */ +/************************************************************************** +Reads the compressed page size from the first page of a tablespace. */ +UNIV_INTERN +ulint +fsp_header_get_zip_size( +/*====================*/ + /* out: compressed page size in bytes, + or 0 if uncompressed */ + const page_t* page); /* in: first page of a tablespace */ +/************************************************************************** +Writes the space id and compressed page size to a tablespace header. +This function is used past the buffer pool when we in fil0fil.c create +a new single-table tablespace. */ +UNIV_INTERN +void +fsp_header_init_fields( +/*===================*/ + page_t* page, /* in/out: first page in the space */ + ulint space_id, /* in: space id */ + ulint flags); /* in: tablespace flags (FSP_SPACE_FLAGS): + 0, or table->flags if newer than COMPACT */ +/************************************************************************** +Initializes the space header of a new created space and creates also the +insert buffer tree root if space == 0. */ +UNIV_INTERN +void +fsp_header_init( +/*============*/ + ulint space, /* in: space id */ + ulint size, /* in: current size in blocks */ + mtr_t* mtr); /* in: mini-transaction handle */ +/************************************************************************** +Increases the space size field of a space. */ +UNIV_INTERN +void +fsp_header_inc_size( +/*================*/ + ulint space, /* in: space id */ + ulint size_inc,/* in: size increment in pages */ + mtr_t* mtr); /* in: mini-transaction handle */ +/************************************************************************** +Creates a new segment. */ +UNIV_INTERN +buf_block_t* +fseg_create( +/*========*/ + /* out: the block where the segment header is placed, + x-latched, NULL if could not create segment + because of lack of space */ + ulint space, /* in: space id */ + ulint page, /* in: page where the segment header is placed: if + this is != 0, the page must belong to another segment, + if this is 0, a new page will be allocated and it + will belong to the created segment */ + ulint byte_offset, /* in: byte offset of the created segment header + on the page */ + mtr_t* mtr); /* in: mtr */ +/************************************************************************** +Creates a new segment. */ +UNIV_INTERN +buf_block_t* +fseg_create_general( +/*================*/ + /* out: the block where the segment header is placed, + x-latched, NULL if could not create segment + because of lack of space */ + ulint space, /* in: space id */ + ulint page, /* in: page where the segment header is placed: if + this is != 0, the page must belong to another segment, + if this is 0, a new page will be allocated and it + will belong to the created segment */ + ulint byte_offset, /* in: byte offset of the created segment header + on the page */ + ibool has_done_reservation, /* in: TRUE if the caller has already + done the reservation for the pages with + fsp_reserve_free_extents (at least 2 extents: one for + the inode and the other for the segment) then there is + no need to do the check for this individual + operation */ + mtr_t* mtr); /* in: mtr */ +/************************************************************************** +Calculates the number of pages reserved by a segment, and how many pages are +currently used. */ +UNIV_INTERN +ulint +fseg_n_reserved_pages( +/*==================*/ + /* out: number of reserved pages */ + fseg_header_t* header, /* in: segment header */ + ulint* used, /* out: number of pages used (<= reserved) */ + mtr_t* mtr); /* in: mtr handle */ +/************************************************************************** +Allocates a single free page from a segment. This function implements +the intelligent allocation strategy which tries to minimize +file space fragmentation. */ +UNIV_INTERN +ulint +fseg_alloc_free_page( +/*=================*/ + /* out: the allocated page offset + FIL_NULL if no page could be allocated */ + fseg_header_t* seg_header, /* in: segment header */ + ulint hint, /* in: hint of which page would be desirable */ + byte direction, /* in: if the new page is needed because + of an index page split, and records are + inserted there in order, into which + direction they go alphabetically: FSP_DOWN, + FSP_UP, FSP_NO_DIR */ + mtr_t* mtr); /* in: mtr handle */ +/************************************************************************** +Allocates a single free page from a segment. This function implements +the intelligent allocation strategy which tries to minimize file space +fragmentation. */ +UNIV_INTERN +ulint +fseg_alloc_free_page_general( +/*=========================*/ + /* out: allocated page offset, FIL_NULL if no + page could be allocated */ + fseg_header_t* seg_header,/* in: segment header */ + ulint hint, /* in: hint of which page would be desirable */ + byte direction,/* in: if the new page is needed because + of an index page split, and records are + inserted there in order, into which + direction they go alphabetically: FSP_DOWN, + FSP_UP, FSP_NO_DIR */ + ibool has_done_reservation, /* in: TRUE if the caller has + already done the reservation for the page + with fsp_reserve_free_extents, then there + is no need to do the check for this individual + page */ + mtr_t* mtr); /* in: mtr handle */ +/************************************************************************** +Reserves free pages from a tablespace. All mini-transactions which may +use several pages from the tablespace should call this function beforehand +and reserve enough free extents so that they certainly will be able +to do their operation, like a B-tree page split, fully. Reservations +must be released with function fil_space_release_free_extents! + +The alloc_type below has the following meaning: FSP_NORMAL means an +operation which will probably result in more space usage, like an +insert in a B-tree; FSP_UNDO means allocation to undo logs: if we are +deleting rows, then this allocation will in the long run result in +less space usage (after a purge); FSP_CLEANING means allocation done +in a physical record delete (like in a purge) or other cleaning operation +which will result in less space usage in the long run. We prefer the latter +two types of allocation: when space is scarce, FSP_NORMAL allocations +will not succeed, but the latter two allocations will succeed, if possible. +The purpose is to avoid dead end where the database is full but the +user cannot free any space because these freeing operations temporarily +reserve some space. + +Single-table tablespaces whose size is < 32 pages are a special case. In this +function we would liberally reserve several 64 page extents for every page +split or merge in a B-tree. But we do not want to waste disk space if the table +only occupies < 32 pages. That is why we apply different rules in that special +case, just ensuring that there are 3 free pages available. */ +UNIV_INTERN +ibool +fsp_reserve_free_extents( +/*=====================*/ + /* out: TRUE if we were able to make the reservation */ + ulint* n_reserved,/* out: number of extents actually reserved; if we + return TRUE and the tablespace size is < 64 pages, + then this can be 0, otherwise it is n_ext */ + ulint space, /* in: space id */ + ulint n_ext, /* in: number of extents to reserve */ + ulint alloc_type,/* in: FSP_NORMAL, FSP_UNDO, or FSP_CLEANING */ + mtr_t* mtr); /* in: mtr */ +/************************************************************************** +This function should be used to get information on how much we still +will be able to insert new data to the database without running out the +tablespace. Only free extents are taken into account and we also subtract +the safety margin required by the above function fsp_reserve_free_extents. */ +UNIV_INTERN +ullint +fsp_get_available_space_in_free_extents( +/*====================================*/ + /* out: available space in kB */ + ulint space); /* in: space id */ +/************************************************************************** +Frees a single page of a segment. */ +UNIV_INTERN +void +fseg_free_page( +/*===========*/ + fseg_header_t* seg_header, /* in: segment header */ + ulint space, /* in: space id */ + ulint page, /* in: page offset */ + mtr_t* mtr); /* in: mtr handle */ +/*********************************************************************** +Frees a segment. The freeing is performed in several mini-transactions, +so that there is no danger of bufferfixing too many buffer pages. */ +UNIV_INTERN +void +fseg_free( +/*======*/ + ulint space, /* in: space id */ + ulint zip_size,/* in: compressed page size in bytes + or 0 for uncompressed pages */ + ulint page_no,/* in: page number where the segment header is + placed */ + ulint offset);/* in: byte offset of the segment header on that + page */ +/************************************************************************** +Frees part of a segment. This function can be used to free a segment +by repeatedly calling this function in different mini-transactions. +Doing the freeing in a single mini-transaction might result in +too big a mini-transaction. */ +UNIV_INTERN +ibool +fseg_free_step( +/*===========*/ + /* out: TRUE if freeing completed */ + fseg_header_t* header, /* in, own: segment header; NOTE: if the header + resides on the first page of the frag list + of the segment, this pointer becomes obsolete + after the last freeing step */ + mtr_t* mtr); /* in: mtr */ +/************************************************************************** +Frees part of a segment. Differs from fseg_free_step because this function +leaves the header page unfreed. */ +UNIV_INTERN +ibool +fseg_free_step_not_header( +/*======================*/ + /* out: TRUE if freeing completed, except the + header page */ + fseg_header_t* header, /* in: segment header which must reside on + the first fragment page of the segment */ + mtr_t* mtr); /* in: mtr */ +/*************************************************************************** +Checks if a page address is an extent descriptor page address. */ +UNIV_INLINE +ibool +fsp_descr_page( +/*===========*/ + /* out: TRUE if a descriptor page */ + ulint zip_size,/* in: compressed page size in bytes; + 0 for uncompressed pages */ + ulint page_no);/* in: page number */ +/*************************************************************** +Parses a redo log record of a file page init. */ +UNIV_INTERN +byte* +fsp_parse_init_file_page( +/*=====================*/ + /* out: end of log record or NULL */ + byte* ptr, /* in: buffer */ + byte* end_ptr, /* in: buffer end */ + buf_block_t* block); /* in: block or NULL */ +/*********************************************************************** +Validates the file space system and its segments. */ +UNIV_INTERN +ibool +fsp_validate( +/*=========*/ + /* out: TRUE if ok */ + ulint space); /* in: space id */ +/*********************************************************************** +Prints info of a file space. */ +UNIV_INTERN +void +fsp_print( +/*======*/ + ulint space); /* in: space id */ +/*********************************************************************** +Validates a segment. */ +UNIV_INTERN +ibool +fseg_validate( +/*==========*/ + /* out: TRUE if ok */ + fseg_header_t* header, /* in: segment header */ + mtr_t* mtr2); /* in: mtr */ +#ifdef UNIV_BTR_PRINT +/*********************************************************************** +Writes info of a segment. */ +UNIV_INTERN +void +fseg_print( +/*=======*/ + fseg_header_t* header, /* in: segment header */ + mtr_t* mtr); /* in: mtr */ +#endif /* UNIV_BTR_PRINT */ + +/* Flags for fsp_reserve_free_extents */ +#define FSP_NORMAL 1000000 +#define FSP_UNDO 2000000 +#define FSP_CLEANING 3000000 + +/* Number of pages described in a single descriptor page: currently each page +description takes less than 1 byte; a descriptor page is repeated every +this many file pages */ +/* #define XDES_DESCRIBED_PER_PAGE UNIV_PAGE_SIZE */ +/* This has been replaced with either UNIV_PAGE_SIZE or page_zip->size. */ + +/* The space low address page map */ +/*--------------------------------------*/ + /* The following two pages are repeated + every XDES_DESCRIBED_PER_PAGE pages in + every tablespace. */ +#define FSP_XDES_OFFSET 0 /* extent descriptor */ +#define FSP_IBUF_BITMAP_OFFSET 1 /* insert buffer bitmap */ + /* The ibuf bitmap pages are the ones whose + page number is the number above plus a + multiple of XDES_DESCRIBED_PER_PAGE */ + +#define FSP_FIRST_INODE_PAGE_NO 2 /* in every tablespace */ + /* The following pages exist + in the system tablespace (space 0). */ +#define FSP_IBUF_HEADER_PAGE_NO 3 /* in tablespace 0 */ +#define FSP_IBUF_TREE_ROOT_PAGE_NO 4 /* in tablespace 0 */ + /* The ibuf tree root page number in + tablespace 0; its fseg inode is on the page + number FSP_FIRST_INODE_PAGE_NO */ +#define FSP_TRX_SYS_PAGE_NO 5 /* in tablespace 0 */ +#define FSP_FIRST_RSEG_PAGE_NO 6 /* in tablespace 0 */ +#define FSP_DICT_HDR_PAGE_NO 7 /* in tablespace 0 */ +/*--------------------------------------*/ + +#ifndef UNIV_NONINL +#include "fsp0fsp.ic" +#endif + +#endif diff --git a/storage/xtradb/include/fsp0fsp.ic b/storage/xtradb/include/fsp0fsp.ic new file mode 100644 index 00000000000..f0301cc5e18 --- /dev/null +++ b/storage/xtradb/include/fsp0fsp.ic @@ -0,0 +1,44 @@ +/***************************************************************************** + +Copyright (c) 1995, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/****************************************************** +File space management + +Created 12/18/1995 Heikki Tuuri +*******************************************************/ + +/*************************************************************************** +Checks if a page address is an extent descriptor page address. */ +UNIV_INLINE +ibool +fsp_descr_page( +/*===========*/ + /* out: TRUE if a descriptor page */ + ulint zip_size,/* in: compressed page size in bytes; + 0 for uncompressed pages */ + ulint page_no)/* in: page number */ +{ + ut_ad(ut_is_2pow(zip_size)); + + if (!zip_size) { + return(UNIV_UNLIKELY((page_no & (UNIV_PAGE_SIZE - 1)) + == FSP_XDES_OFFSET)); + } + + return(UNIV_UNLIKELY((page_no & (zip_size - 1)) == FSP_XDES_OFFSET)); +} diff --git a/storage/xtradb/include/fut0fut.h b/storage/xtradb/include/fut0fut.h new file mode 100644 index 00000000000..4de0c97294c --- /dev/null +++ b/storage/xtradb/include/fut0fut.h @@ -0,0 +1,54 @@ +/***************************************************************************** + +Copyright (c) 1995, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/********************************************************************** +File-based utilities + +Created 12/13/1995 Heikki Tuuri +***********************************************************************/ + + +#ifndef fut0fut_h +#define fut0fut_h + +#include "univ.i" + +#include "fil0fil.h" +#include "mtr0mtr.h" + +/************************************************************************ +Gets a pointer to a file address and latches the page. */ +UNIV_INLINE +byte* +fut_get_ptr( +/*========*/ + /* out: pointer to a byte in a frame; the file + page in the frame is bufferfixed and latched */ + ulint space, /* in: space id */ + ulint zip_size,/* in: compressed page size in bytes + or 0 for uncompressed pages */ + fil_addr_t addr, /* in: file address */ + ulint rw_latch, /* in: RW_S_LATCH, RW_X_LATCH */ + mtr_t* mtr); /* in: mtr handle */ + +#ifndef UNIV_NONINL +#include "fut0fut.ic" +#endif + +#endif + diff --git a/storage/xtradb/include/fut0fut.ic b/storage/xtradb/include/fut0fut.ic new file mode 100644 index 00000000000..f7e820da008 --- /dev/null +++ b/storage/xtradb/include/fut0fut.ic @@ -0,0 +1,55 @@ +/***************************************************************************** + +Copyright (c) 1995, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/********************************************************************** +File-based utilities + +Created 12/13/1995 Heikki Tuuri +***********************************************************************/ + +#include "sync0rw.h" +#include "buf0buf.h" + +/************************************************************************ +Gets a pointer to a file address and latches the page. */ +UNIV_INLINE +byte* +fut_get_ptr( +/*========*/ + /* out: pointer to a byte in a frame; the file + page in the frame is bufferfixed and latched */ + ulint space, /* in: space id */ + ulint zip_size,/* in: compressed page size in bytes + or 0 for uncompressed pages */ + fil_addr_t addr, /* in: file address */ + ulint rw_latch, /* in: RW_S_LATCH, RW_X_LATCH */ + mtr_t* mtr) /* in: mtr handle */ +{ + buf_block_t* block; + byte* ptr; + + ut_ad(addr.boffset < UNIV_PAGE_SIZE); + ut_ad((rw_latch == RW_S_LATCH) || (rw_latch == RW_X_LATCH)); + + block = buf_page_get(space, zip_size, addr.page, rw_latch, mtr); + ptr = buf_block_get_frame(block) + addr.boffset; + + buf_block_dbg_add_level(block, SYNC_NO_ORDER_CHECK); + + return(ptr); +} diff --git a/storage/xtradb/include/fut0lst.h b/storage/xtradb/include/fut0lst.h new file mode 100644 index 00000000000..f812874fe00 --- /dev/null +++ b/storage/xtradb/include/fut0lst.h @@ -0,0 +1,214 @@ +/***************************************************************************** + +Copyright (c) 1995, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/********************************************************************** +File-based list utilities + +Created 11/28/1995 Heikki Tuuri +***********************************************************************/ + +#ifndef fut0lst_h +#define fut0lst_h + +#include "univ.i" + +#include "fil0fil.h" +#include "mtr0mtr.h" + + +/* The C 'types' of base node and list node: these should be used to +write self-documenting code. Of course, the sizeof macro cannot be +applied to these types! */ + +typedef byte flst_base_node_t; +typedef byte flst_node_t; + +/* The physical size of a list base node in bytes */ +#define FLST_BASE_NODE_SIZE (4 + 2 * FIL_ADDR_SIZE) + +/* The physical size of a list node in bytes */ +#define FLST_NODE_SIZE (2 * FIL_ADDR_SIZE) + + +/************************************************************************ +Initializes a list base node. */ +UNIV_INLINE +void +flst_init( +/*======*/ + flst_base_node_t* base, /* in: pointer to base node */ + mtr_t* mtr); /* in: mini-transaction handle */ +/************************************************************************ +Adds a node as the last node in a list. */ +UNIV_INTERN +void +flst_add_last( +/*==========*/ + flst_base_node_t* base, /* in: pointer to base node of list */ + flst_node_t* node, /* in: node to add */ + mtr_t* mtr); /* in: mini-transaction handle */ +/************************************************************************ +Adds a node as the first node in a list. */ +UNIV_INTERN +void +flst_add_first( +/*===========*/ + flst_base_node_t* base, /* in: pointer to base node of list */ + flst_node_t* node, /* in: node to add */ + mtr_t* mtr); /* in: mini-transaction handle */ +/************************************************************************ +Inserts a node after another in a list. */ +UNIV_INTERN +void +flst_insert_after( +/*==============*/ + flst_base_node_t* base, /* in: pointer to base node of list */ + flst_node_t* node1, /* in: node to insert after */ + flst_node_t* node2, /* in: node to add */ + mtr_t* mtr); /* in: mini-transaction handle */ +/************************************************************************ +Inserts a node before another in a list. */ +UNIV_INTERN +void +flst_insert_before( +/*===============*/ + flst_base_node_t* base, /* in: pointer to base node of list */ + flst_node_t* node2, /* in: node to insert */ + flst_node_t* node3, /* in: node to insert before */ + mtr_t* mtr); /* in: mini-transaction handle */ +/************************************************************************ +Removes a node. */ +UNIV_INTERN +void +flst_remove( +/*========*/ + flst_base_node_t* base, /* in: pointer to base node of list */ + flst_node_t* node2, /* in: node to remove */ + mtr_t* mtr); /* in: mini-transaction handle */ +/************************************************************************ +Cuts off the tail of the list, including the node given. The number of +nodes which will be removed must be provided by the caller, as this function +does not measure the length of the tail. */ +UNIV_INTERN +void +flst_cut_end( +/*=========*/ + flst_base_node_t* base, /* in: pointer to base node of list */ + flst_node_t* node2, /* in: first node to remove */ + ulint n_nodes,/* in: number of nodes to remove, + must be >= 1 */ + mtr_t* mtr); /* in: mini-transaction handle */ +/************************************************************************ +Cuts off the tail of the list, not including the given node. The number of +nodes which will be removed must be provided by the caller, as this function +does not measure the length of the tail. */ +UNIV_INTERN +void +flst_truncate_end( +/*==============*/ + flst_base_node_t* base, /* in: pointer to base node of list */ + flst_node_t* node2, /* in: first node not to remove */ + ulint n_nodes,/* in: number of nodes to remove */ + mtr_t* mtr); /* in: mini-transaction handle */ +/************************************************************************ +Gets list length. */ +UNIV_INLINE +ulint +flst_get_len( +/*=========*/ + /* out: length */ + const flst_base_node_t* base, /* in: pointer to base node */ + mtr_t* mtr); /* in: mini-transaction handle */ +/************************************************************************ +Gets list first node address. */ +UNIV_INLINE +fil_addr_t +flst_get_first( +/*===========*/ + /* out: file address */ + const flst_base_node_t* base, /* in: pointer to base node */ + mtr_t* mtr); /* in: mini-transaction handle */ +/************************************************************************ +Gets list last node address. */ +UNIV_INLINE +fil_addr_t +flst_get_last( +/*==========*/ + /* out: file address */ + const flst_base_node_t* base, /* in: pointer to base node */ + mtr_t* mtr); /* in: mini-transaction handle */ +/************************************************************************ +Gets list next node address. */ +UNIV_INLINE +fil_addr_t +flst_get_next_addr( +/*===============*/ + /* out: file address */ + const flst_node_t* node, /* in: pointer to node */ + mtr_t* mtr); /* in: mini-transaction handle */ +/************************************************************************ +Gets list prev node address. */ +UNIV_INLINE +fil_addr_t +flst_get_prev_addr( +/*===============*/ + /* out: file address */ + const flst_node_t* node, /* in: pointer to node */ + mtr_t* mtr); /* in: mini-transaction handle */ +/************************************************************************ +Writes a file address. */ +UNIV_INLINE +void +flst_write_addr( +/*============*/ + fil_faddr_t* faddr, /* in: pointer to file faddress */ + fil_addr_t addr, /* in: file address */ + mtr_t* mtr); /* in: mini-transaction handle */ +/************************************************************************ +Reads a file address. */ +UNIV_INLINE +fil_addr_t +flst_read_addr( +/*===========*/ + /* out: file address */ + const fil_faddr_t* faddr, /* in: pointer to file faddress */ + mtr_t* mtr); /* in: mini-transaction handle */ +/************************************************************************ +Validates a file-based list. */ +UNIV_INTERN +ibool +flst_validate( +/*==========*/ + /* out: TRUE if ok */ + const flst_base_node_t* base, /* in: pointer to base node of list */ + mtr_t* mtr1); /* in: mtr */ +/************************************************************************ +Prints info of a file-based list. */ +UNIV_INTERN +void +flst_print( +/*=======*/ + const flst_base_node_t* base, /* in: pointer to base node of list */ + mtr_t* mtr); /* in: mtr */ + + +#ifndef UNIV_NONINL +#include "fut0lst.ic" +#endif + +#endif diff --git a/storage/xtradb/include/fut0lst.ic b/storage/xtradb/include/fut0lst.ic new file mode 100644 index 00000000000..5899e996059 --- /dev/null +++ b/storage/xtradb/include/fut0lst.ic @@ -0,0 +1,166 @@ +/***************************************************************************** + +Copyright (c) 1995, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/********************************************************************** +File-based list utilities + +Created 11/28/1995 Heikki Tuuri +***********************************************************************/ + +#include "fut0fut.h" +#include "mtr0log.h" +#include "buf0buf.h" + +/* We define the field offsets of a node for the list */ +#define FLST_PREV 0 /* 6-byte address of the previous list element; + the page part of address is FIL_NULL, if no + previous element */ +#define FLST_NEXT FIL_ADDR_SIZE /* 6-byte address of the next + list element; the page part of address + is FIL_NULL, if no next element */ + +/* We define the field offsets of a base node for the list */ +#define FLST_LEN 0 /* 32-bit list length field */ +#define FLST_FIRST 4 /* 6-byte address of the first element + of the list; undefined if empty list */ +#define FLST_LAST (4 + FIL_ADDR_SIZE) /* 6-byte address of the + last element of the list; undefined + if empty list */ + +/************************************************************************ +Writes a file address. */ +UNIV_INLINE +void +flst_write_addr( +/*============*/ + fil_faddr_t* faddr, /* in: pointer to file faddress */ + fil_addr_t addr, /* in: file address */ + mtr_t* mtr) /* in: mini-transaction handle */ +{ + ut_ad(faddr && mtr); + ut_ad(mtr_memo_contains_page(mtr, faddr, MTR_MEMO_PAGE_X_FIX)); + ut_a(addr.page == FIL_NULL || addr.boffset >= FIL_PAGE_DATA); + ut_a(ut_align_offset(faddr, UNIV_PAGE_SIZE) >= FIL_PAGE_DATA); + + mlog_write_ulint(faddr + FIL_ADDR_PAGE, addr.page, MLOG_4BYTES, mtr); + mlog_write_ulint(faddr + FIL_ADDR_BYTE, addr.boffset, + MLOG_2BYTES, mtr); +} + +/************************************************************************ +Reads a file address. */ +UNIV_INLINE +fil_addr_t +flst_read_addr( +/*===========*/ + /* out: file address */ + const fil_faddr_t* faddr, /* in: pointer to file faddress */ + mtr_t* mtr) /* in: mini-transaction handle */ +{ + fil_addr_t addr; + + ut_ad(faddr && mtr); + + addr.page = mtr_read_ulint(faddr + FIL_ADDR_PAGE, MLOG_4BYTES, mtr); + addr.boffset = mtr_read_ulint(faddr + FIL_ADDR_BYTE, MLOG_2BYTES, + mtr); + ut_a(addr.page == FIL_NULL || addr.boffset >= FIL_PAGE_DATA); + ut_a(ut_align_offset(faddr, UNIV_PAGE_SIZE) >= FIL_PAGE_DATA); + return(addr); +} + +/************************************************************************ +Initializes a list base node. */ +UNIV_INLINE +void +flst_init( +/*======*/ + flst_base_node_t* base, /* in: pointer to base node */ + mtr_t* mtr) /* in: mini-transaction handle */ +{ + ut_ad(mtr_memo_contains_page(mtr, base, MTR_MEMO_PAGE_X_FIX)); + + mlog_write_ulint(base + FLST_LEN, 0, MLOG_4BYTES, mtr); + flst_write_addr(base + FLST_FIRST, fil_addr_null, mtr); + flst_write_addr(base + FLST_LAST, fil_addr_null, mtr); +} + +/************************************************************************ +Gets list length. */ +UNIV_INLINE +ulint +flst_get_len( +/*=========*/ + /* out: length */ + const flst_base_node_t* base, /* in: pointer to base node */ + mtr_t* mtr) /* in: mini-transaction handle */ +{ + return(mtr_read_ulint(base + FLST_LEN, MLOG_4BYTES, mtr)); +} + +/************************************************************************ +Gets list first node address. */ +UNIV_INLINE +fil_addr_t +flst_get_first( +/*===========*/ + /* out: file address */ + const flst_base_node_t* base, /* in: pointer to base node */ + mtr_t* mtr) /* in: mini-transaction handle */ +{ + return(flst_read_addr(base + FLST_FIRST, mtr)); +} + +/************************************************************************ +Gets list last node address. */ +UNIV_INLINE +fil_addr_t +flst_get_last( +/*==========*/ + /* out: file address */ + const flst_base_node_t* base, /* in: pointer to base node */ + mtr_t* mtr) /* in: mini-transaction handle */ +{ + return(flst_read_addr(base + FLST_LAST, mtr)); +} + +/************************************************************************ +Gets list next node address. */ +UNIV_INLINE +fil_addr_t +flst_get_next_addr( +/*===============*/ + /* out: file address */ + const flst_node_t* node, /* in: pointer to node */ + mtr_t* mtr) /* in: mini-transaction handle */ +{ + return(flst_read_addr(node + FLST_NEXT, mtr)); +} + +/************************************************************************ +Gets list prev node address. */ +UNIV_INLINE +fil_addr_t +flst_get_prev_addr( +/*===============*/ + /* out: file address */ + const flst_node_t* node, /* in: pointer to node */ + mtr_t* mtr) /* in: mini-transaction handle */ +{ + return(flst_read_addr(node + FLST_PREV, mtr)); +} diff --git a/storage/xtradb/include/ha0ha.h b/storage/xtradb/include/ha0ha.h new file mode 100644 index 00000000000..768f3d7aca3 --- /dev/null +++ b/storage/xtradb/include/ha0ha.h @@ -0,0 +1,188 @@ +/***************************************************************************** + +Copyright (c) 1994, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/****************************************************** +The hash table with external chains + +Created 8/18/1994 Heikki Tuuri +*******************************************************/ + +#ifndef ha0ha_h +#define ha0ha_h + +#include "univ.i" + +#include "hash0hash.h" +#include "page0types.h" +#include "buf0types.h" + +/***************************************************************** +Looks for an element in a hash table. */ +UNIV_INLINE +void* +ha_search_and_get_data( +/*===================*/ + /* out: pointer to the data of the first hash + table node in chain having the fold number, + NULL if not found */ + hash_table_t* table, /* in: hash table */ + ulint fold); /* in: folded value of the searched data */ +/************************************************************* +Looks for an element when we know the pointer to the data and updates +the pointer to data if found. */ +UNIV_INTERN +void +ha_search_and_update_if_found_func( +/*===============================*/ + hash_table_t* table, /* in: hash table */ + ulint fold, /* in: folded value of the searched data */ + void* data, /* in: pointer to the data */ +#if defined UNIV_AHI_DEBUG || defined UNIV_DEBUG + buf_block_t* new_block,/* in: block containing new_data */ +#endif /* UNIV_AHI_DEBUG || UNIV_DEBUG */ + void* new_data);/* in: new pointer to the data */ + +#if defined UNIV_AHI_DEBUG || defined UNIV_DEBUG +# define ha_search_and_update_if_found(table,fold,data,new_block,new_data) \ + ha_search_and_update_if_found_func(table,fold,data,new_block,new_data) +#else /* UNIV_AHI_DEBUG || UNIV_DEBUG */ +# define ha_search_and_update_if_found(table,fold,data,new_block,new_data) \ + ha_search_and_update_if_found_func(table,fold,data,new_data) +#endif /* UNIV_AHI_DEBUG || UNIV_DEBUG */ +/***************************************************************** +Creates a hash table with >= n array cells. The actual number of cells is +chosen to be a prime number slightly bigger than n. */ +UNIV_INTERN +hash_table_t* +ha_create_func( +/*===========*/ + /* out, own: created table */ + ulint n, /* in: number of array cells */ +#ifdef UNIV_SYNC_DEBUG + ulint mutex_level, /* in: level of the mutexes in the latching + order: this is used in the debug version */ +#endif /* UNIV_SYNC_DEBUG */ + ulint n_mutexes); /* in: number of mutexes to protect the + hash table: must be a power of 2 */ +#ifdef UNIV_SYNC_DEBUG +# define ha_create(n_c,n_m,level) ha_create_func(n_c,level,n_m) +#else /* UNIV_SYNC_DEBUG */ +# define ha_create(n_c,n_m,level) ha_create_func(n_c,n_m) +#endif /* UNIV_SYNC_DEBUG */ + +/***************************************************************** +Empties a hash table and frees the memory heaps. */ +UNIV_INTERN +void +ha_clear( +/*=====*/ + hash_table_t* table); /* in, own: hash table */ + +/***************************************************************** +Inserts an entry into a hash table. If an entry with the same fold number +is found, its node is updated to point to the new data, and no new node +is inserted. */ +UNIV_INTERN +ibool +ha_insert_for_fold_func( +/*====================*/ + /* out: TRUE if succeed, FALSE if no more + memory could be allocated */ + hash_table_t* table, /* in: hash table */ + ulint fold, /* in: folded value of data; if a node with + the same fold value already exists, it is + updated to point to the same data, and no new + node is created! */ +#if defined UNIV_AHI_DEBUG || defined UNIV_DEBUG + buf_block_t* block, /* in: buffer block containing the data */ +#endif /* UNIV_AHI_DEBUG || UNIV_DEBUG */ + void* data); /* in: data, must not be NULL */ + +#if defined UNIV_AHI_DEBUG || defined UNIV_DEBUG +# define ha_insert_for_fold(t,f,b,d) ha_insert_for_fold_func(t,f,b,d) +#else /* UNIV_AHI_DEBUG || UNIV_DEBUG */ +# define ha_insert_for_fold(t,f,b,d) ha_insert_for_fold_func(t,f,d) +#endif /* UNIV_AHI_DEBUG || UNIV_DEBUG */ + +/***************************************************************** +Deletes an entry from a hash table. */ +UNIV_INTERN +void +ha_delete( +/*======*/ + hash_table_t* table, /* in: hash table */ + ulint fold, /* in: folded value of data */ + void* data); /* in: data, must not be NULL and must exist + in the hash table */ +/************************************************************* +Looks for an element when we know the pointer to the data and deletes +it from the hash table if found. */ +UNIV_INLINE +ibool +ha_search_and_delete_if_found( +/*==========================*/ + /* out: TRUE if found */ + hash_table_t* table, /* in: hash table */ + ulint fold, /* in: folded value of the searched data */ + void* data); /* in: pointer to the data */ +/********************************************************************* +Removes from the chain determined by fold all nodes whose data pointer +points to the page given. */ +UNIV_INTERN +void +ha_remove_all_nodes_to_page( +/*========================*/ + hash_table_t* table, /* in: hash table */ + ulint fold, /* in: fold value */ + const page_t* page); /* in: buffer page */ +/***************************************************************** +Validates a given range of the cells in hash table. */ +UNIV_INTERN +ibool +ha_validate( +/*========*/ + /* out: TRUE if ok */ + hash_table_t* table, /* in: hash table */ + ulint start_index, /* in: start index */ + ulint end_index); /* in: end index */ +/***************************************************************** +Prints info of a hash table. */ +UNIV_INTERN +void +ha_print_info( +/*==========*/ + FILE* file, /* in: file where to print */ + hash_table_t* table); /* in: hash table */ + +/* The hash table external chain node */ + +typedef struct ha_node_struct ha_node_t; +struct ha_node_struct { + ha_node_t* next; /* next chain node or NULL if none */ +#if defined UNIV_AHI_DEBUG || defined UNIV_DEBUG + buf_block_t* block; /* buffer block containing the data, or NULL */ +#endif /* UNIV_AHI_DEBUG || UNIV_DEBUG */ + void* data; /* pointer to the data */ + ulint fold; /* fold value for the data */ +}; + +#ifndef UNIV_NONINL +#include "ha0ha.ic" +#endif + +#endif diff --git a/storage/xtradb/include/ha0ha.ic b/storage/xtradb/include/ha0ha.ic new file mode 100644 index 00000000000..35fd802eaef --- /dev/null +++ b/storage/xtradb/include/ha0ha.ic @@ -0,0 +1,214 @@ +/***************************************************************************** + +Copyright (c) 1994, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/************************************************************************ +The hash table with external chains + +Created 8/18/1994 Heikki Tuuri +*************************************************************************/ + +#include "ut0rnd.h" +#include "mem0mem.h" + +/*************************************************************** +Deletes a hash node. */ +UNIV_INTERN +void +ha_delete_hash_node( +/*================*/ + hash_table_t* table, /* in: hash table */ + ha_node_t* del_node); /* in: node to be deleted */ + +/********************************************************************** +Gets a hash node data. */ +UNIV_INLINE +void* +ha_node_get_data( +/*=============*/ + /* out: pointer to the data */ + ha_node_t* node) /* in: hash chain node */ +{ + return(node->data); +} + +/********************************************************************** +Sets hash node data. */ +UNIV_INLINE +void +ha_node_set_data_func( +/*==================*/ + ha_node_t* node, /* in: hash chain node */ +#if defined UNIV_AHI_DEBUG || defined UNIV_DEBUG + buf_block_t* block, /* in: buffer block containing the data */ +#endif /* UNIV_AHI_DEBUG || UNIV_DEBUG */ + void* data) /* in: pointer to the data */ +{ +#if defined UNIV_AHI_DEBUG || defined UNIV_DEBUG + node->block = block; +#endif /* UNIV_AHI_DEBUG || UNIV_DEBUG */ + node->data = data; +} + +#if defined UNIV_AHI_DEBUG || defined UNIV_DEBUG +# define ha_node_set_data(n,b,d) ha_node_set_data_func(n,b,d) +#else /* UNIV_AHI_DEBUG || UNIV_DEBUG */ +# define ha_node_set_data(n,b,d) ha_node_set_data_func(n,d) +#endif /* UNIV_AHI_DEBUG || UNIV_DEBUG */ + +/********************************************************************** +Gets the next node in a hash chain. */ +UNIV_INLINE +ha_node_t* +ha_chain_get_next( +/*==============*/ + /* out: next node, NULL if none */ + ha_node_t* node) /* in: hash chain node */ +{ + return(node->next); +} + +/********************************************************************** +Gets the first node in a hash chain. */ +UNIV_INLINE +ha_node_t* +ha_chain_get_first( +/*===============*/ + /* out: first node, NULL if none */ + hash_table_t* table, /* in: hash table */ + ulint fold) /* in: fold value determining the chain */ +{ + return((ha_node_t*) + hash_get_nth_cell(table, hash_calc_hash(fold, table))->node); +} + +/***************************************************************** +Looks for an element in a hash table. */ +UNIV_INLINE +ha_node_t* +ha_search( +/*======*/ + /* out: pointer to the first hash table node + in chain having the fold number, NULL if not + found */ + hash_table_t* table, /* in: hash table */ + ulint fold) /* in: folded value of the searched data */ +{ + ha_node_t* node; + + ut_ad(!table->mutexes || mutex_own(hash_get_mutex(table, fold))); + + node = ha_chain_get_first(table, fold); + + while (node) { + if (node->fold == fold) { + + return(node); + } + + node = ha_chain_get_next(node); + } + + return(NULL); +} + +/***************************************************************** +Looks for an element in a hash table. */ +UNIV_INLINE +void* +ha_search_and_get_data( +/*===================*/ + /* out: pointer to the data of the first hash + table node in chain having the fold number, + NULL if not found */ + hash_table_t* table, /* in: hash table */ + ulint fold) /* in: folded value of the searched data */ +{ + ha_node_t* node; + + ut_ad(!table->mutexes || mutex_own(hash_get_mutex(table, fold))); + + node = ha_chain_get_first(table, fold); + + while (node) { + if (node->fold == fold) { + + return(node->data); + } + + node = ha_chain_get_next(node); + } + + return(NULL); +} + +/************************************************************* +Looks for an element when we know the pointer to the data. */ +UNIV_INLINE +ha_node_t* +ha_search_with_data( +/*================*/ + /* out: pointer to the hash table node, NULL + if not found in the table */ + hash_table_t* table, /* in: hash table */ + ulint fold, /* in: folded value of the searched data */ + void* data) /* in: pointer to the data */ +{ + ha_node_t* node; + + ut_ad(!table->mutexes || mutex_own(hash_get_mutex(table, fold))); + + node = ha_chain_get_first(table, fold); + + while (node) { + if (node->data == data) { + + return(node); + } + + node = ha_chain_get_next(node); + } + + return(NULL); +} + +/************************************************************* +Looks for an element when we know the pointer to the data, and deletes +it from the hash table, if found. */ +UNIV_INLINE +ibool +ha_search_and_delete_if_found( +/*==========================*/ + /* out: TRUE if found */ + hash_table_t* table, /* in: hash table */ + ulint fold, /* in: folded value of the searched data */ + void* data) /* in: pointer to the data */ +{ + ha_node_t* node; + + ut_ad(!table->mutexes || mutex_own(hash_get_mutex(table, fold))); + + node = ha_search_with_data(table, fold, data); + + if (node) { + ha_delete_hash_node(table, node); + + return(TRUE); + } + + return(FALSE); +} diff --git a/storage/xtradb/include/ha0storage.h b/storage/xtradb/include/ha0storage.h new file mode 100644 index 00000000000..f5a3938f434 --- /dev/null +++ b/storage/xtradb/include/ha0storage.h @@ -0,0 +1,129 @@ +/***************************************************************************** + +Copyright (c) 2007, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/****************************************************** +Hash storage. +Provides a data structure that stores chunks of data in +its own storage, avoiding duplicates. + +Created September 22, 2007 Vasil Dimov +*******************************************************/ + +#ifndef ha0storage_h +#define ha0storage_h + +#include "univ.i" + +/* This value is used by default by ha_storage_create(). More memory +is allocated later when/if it is needed. */ +#define HA_STORAGE_DEFAULT_HEAP_BYTES 1024 + +/* This value is used by default by ha_storage_create(). It is a +constant per ha_storage's lifetime. */ +#define HA_STORAGE_DEFAULT_HASH_CELLS 4096 + +typedef struct ha_storage_struct ha_storage_t; + +/*********************************************************************** +Creates a hash storage. If any of the parameters is 0, then a default +value is used. */ +UNIV_INLINE +ha_storage_t* +ha_storage_create( +/*==============*/ + /* out, own: hash storage */ + ulint initial_heap_bytes, /* in: initial heap's size */ + ulint initial_hash_cells); /* in: initial number of cells + in the hash table */ + +/*********************************************************************** +Copies data into the storage and returns a pointer to the copy. If the +same data chunk is already present, then pointer to it is returned. +Data chunks are considered to be equal if len1 == len2 and +memcmp(data1, data2, len1) == 0. If "data" is not present (and thus +data_len bytes need to be allocated) and the size of storage is going to +become more than "memlim" then "data" is not added and NULL is returned. +To disable this behavior "memlim" can be set to 0, which stands for +"no limit". */ + +const void* +ha_storage_put_memlim( +/*==================*/ + /* out: pointer to the copy */ + ha_storage_t* storage, /* in/out: hash storage */ + const void* data, /* in: data to store */ + ulint data_len, /* in: data length */ + ulint memlim); /* in: memory limit to obey */ + +/*********************************************************************** +Same as ha_storage_put_memlim() but without memory limit. */ + +#define ha_storage_put(storage, data, data_len) \ + ha_storage_put_memlim((storage), (data), (data_len), 0) + +/*********************************************************************** +Copies string into the storage and returns a pointer to the copy. If the +same string is already present, then pointer to it is returned. +Strings are considered to be equal if strcmp(str1, str2) == 0. */ + +#define ha_storage_put_str(storage, str) \ + ((const char*) ha_storage_put((storage), (str), strlen(str) + 1)) + +/*********************************************************************** +Copies string into the storage and returns a pointer to the copy obeying +a memory limit. */ + +#define ha_storage_put_str_memlim(storage, str, memlim) \ + ((const char*) ha_storage_put_memlim((storage), (str), \ + strlen(str) + 1, (memlim))) + +/*********************************************************************** +Empties a hash storage, freeing memory occupied by data chunks. +This invalidates any pointers previously returned by ha_storage_put(). +The hash storage is not invalidated itself and can be used again. */ +UNIV_INLINE +void +ha_storage_empty( +/*=============*/ + ha_storage_t** storage); /* in/out: hash storage */ + +/*********************************************************************** +Frees a hash storage and everything it contains, it cannot be used after +this call. +This invalidates any pointers previously returned by ha_storage_put(). +*/ +UNIV_INLINE +void +ha_storage_free( +/*============*/ + ha_storage_t* storage); /* in/out: hash storage */ + +/*********************************************************************** +Gets the size of the memory used by a storage. */ +UNIV_INLINE +ulint +ha_storage_get_size( +/*================*/ + /* out: bytes used */ + const ha_storage_t* storage); /* in: hash storage */ + +#ifndef UNIV_NONINL +#include "ha0storage.ic" +#endif + +#endif /* ha0storage_h */ diff --git a/storage/xtradb/include/ha0storage.ic b/storage/xtradb/include/ha0storage.ic new file mode 100644 index 00000000000..7ab43bc00ba --- /dev/null +++ b/storage/xtradb/include/ha0storage.ic @@ -0,0 +1,146 @@ +/***************************************************************************** + +Copyright (c) 2007, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/****************************************************** +Hash storage. +Provides a data structure that stores chunks of data in +its own storage, avoiding duplicates. + +Created September 24, 2007 Vasil Dimov +*******************************************************/ + +#include "univ.i" +#include "ha0storage.h" +#include "hash0hash.h" +#include "mem0mem.h" + +struct ha_storage_struct { + mem_heap_t* heap; /* storage from which memory is + allocated */ + hash_table_t* hash; /* hash table used to avoid + duplicates */ +}; + +/* Objects of this type are put in the hash */ +typedef struct ha_storage_node_struct ha_storage_node_t; +struct ha_storage_node_struct { + ulint data_len;/* length of the data */ + const void* data; /* pointer to data */ + ha_storage_node_t* next; /* next node in hash chain */ +}; + +/*********************************************************************** +Creates a hash storage. If any of the parameters is 0, then a default +value is used. */ +UNIV_INLINE +ha_storage_t* +ha_storage_create( +/*==============*/ + /* out, own: hash storage */ + ulint initial_heap_bytes, /* in: initial heap's size */ + ulint initial_hash_cells) /* in: initial number of cells + in the hash table */ +{ + ha_storage_t* storage; + mem_heap_t* heap; + + if (initial_heap_bytes == 0) { + + initial_heap_bytes = HA_STORAGE_DEFAULT_HEAP_BYTES; + } + + if (initial_hash_cells == 0) { + + initial_hash_cells = HA_STORAGE_DEFAULT_HASH_CELLS; + } + + /* we put "storage" within "storage->heap" */ + + heap = mem_heap_create(sizeof(ha_storage_t) + + initial_heap_bytes); + + storage = (ha_storage_t*) mem_heap_alloc(heap, + sizeof(ha_storage_t)); + + storage->heap = heap; + storage->hash = hash_create(initial_hash_cells); + + return(storage); +} + +/*********************************************************************** +Empties a hash storage, freeing memory occupied by data chunks. +This invalidates any pointers previously returned by ha_storage_put(). +The hash storage is not invalidated itself and can be used again. */ +UNIV_INLINE +void +ha_storage_empty( +/*=============*/ + ha_storage_t** storage) /* in/out: hash storage */ +{ + ha_storage_t temp_storage; + + temp_storage.heap = (*storage)->heap; + temp_storage.hash = (*storage)->hash; + + hash_table_clear(temp_storage.hash); + mem_heap_empty(temp_storage.heap); + + *storage = (ha_storage_t*) mem_heap_alloc(temp_storage.heap, + sizeof(ha_storage_t)); + + (*storage)->heap = temp_storage.heap; + (*storage)->hash = temp_storage.hash; +} + +/*********************************************************************** +Frees a hash storage and everything it contains, it cannot be used after +this call. +This invalidates any pointers previously returned by ha_storage_put(). +*/ +UNIV_INLINE +void +ha_storage_free( +/*============*/ + ha_storage_t* storage) /* in/out: hash storage */ +{ + /* order is important because the pointer storage->hash is + within the heap */ + hash_table_free(storage->hash); + mem_heap_free(storage->heap); +} + +/*********************************************************************** +Gets the size of the memory used by a storage. */ +UNIV_INLINE +ulint +ha_storage_get_size( +/*================*/ + /* out: bytes used */ + const ha_storage_t* storage) /* in: hash storage */ +{ + ulint ret; + + ret = mem_heap_get_size(storage->heap); + + /* this assumes hash->heap and hash->heaps are NULL */ + ret += sizeof(hash_table_t); + ret += sizeof(hash_cell_t) * hash_get_n_cells(storage->hash); + + return(ret); +} diff --git a/storage/xtradb/include/ha_prototypes.h b/storage/xtradb/include/ha_prototypes.h new file mode 100644 index 00000000000..116242b32e4 --- /dev/null +++ b/storage/xtradb/include/ha_prototypes.h @@ -0,0 +1,249 @@ +/***************************************************************************** + +Copyright (c) 2006, 2009, Innobase Oy. 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 HA_INNODB_PROTOTYPES_H +#define HA_INNODB_PROTOTYPES_H + +#ifndef UNIV_HOTBACKUP + +#include "univ.i" /* ulint, uint */ +#include "m_ctype.h" /* CHARSET_INFO */ + +/* Prototypes for global functions in ha_innodb.cc that are called by +InnoDB's C-code. */ + +/************************************************************************* +Wrapper around MySQL's copy_and_convert function, see it for +documentation. */ +UNIV_INTERN +ulint +innobase_convert_string( +/*====================*/ + void* to, + ulint to_length, + CHARSET_INFO* to_cs, + const void* from, + ulint from_length, + CHARSET_INFO* from_cs, + uint* errors); + +/*********************************************************************** +Formats the raw data in "data" (in InnoDB on-disk format) that is of +type DATA_(CHAR|VARCHAR|MYSQL|VARMYSQL) using "charset_coll" and writes +the result to "buf". The result is converted to "system_charset_info". +Not more than "buf_size" bytes are written to "buf". +The result is always '\0'-terminated (provided buf_size > 0) and the +number of bytes that were written to "buf" is returned (including the +terminating '\0'). */ +UNIV_INTERN +ulint +innobase_raw_format( +/*================*/ + /* out: number of bytes + that were written */ + const char* data, /* in: raw data */ + ulint data_len, /* in: raw data length + in bytes */ + ulint charset_coll, /* in: charset collation */ + char* buf, /* out: output buffer */ + ulint buf_size); /* in: output buffer size + in bytes */ + +/********************************************************************* +Convert a table or index name to the MySQL system_charset_info (UTF-8) +and quote it if needed. */ +UNIV_INTERN +char* +innobase_convert_name( +/*==================*/ + /* out: pointer to the end of buf */ + char* buf, /* out: buffer for converted identifier */ + ulint buflen, /* in: length of buf, in bytes */ + const char* id, /* in: identifier to convert */ + ulint idlen, /* in: length of id, in bytes */ + void* thd, /* in: MySQL connection thread, or NULL */ + ibool table_id);/* in: TRUE=id is a table or database name; + FALSE=id is an index name */ + +/********************************************************************** +Returns true if the thread is the replication thread on the slave +server. Used in srv_conc_enter_innodb() to determine if the thread +should be allowed to enter InnoDB - the replication thread is treated +differently than other threads. Also used in +srv_conc_force_exit_innodb(). */ +UNIV_INTERN +ibool +thd_is_replication_slave_thread( +/*============================*/ + /* out: true if thd is the replication thread */ + void* thd); /* in: thread handle (THD*) */ + +/********************************************************************** +Returns true if the transaction this thread is processing has edited +non-transactional tables. Used by the deadlock detector when deciding +which transaction to rollback in case of a deadlock - we try to avoid +rolling back transactions that have edited non-transactional tables. */ +UNIV_INTERN +ibool +thd_has_edited_nontrans_tables( +/*===========================*/ + /* out: true if non-transactional tables have + been edited */ + void* thd); /* in: thread handle (THD*) */ + +/***************************************************************** +Prints info of a THD object (== user session thread) to the given file. */ +UNIV_INTERN +void +innobase_mysql_print_thd( +/*=====================*/ + FILE* f, /* in: output stream */ + void* thd, /* in: pointer to a MySQL THD object */ + uint max_query_len); /* in: max query length to print, or 0 to + use the default max length */ + +/****************************************************************** +Converts a MySQL type to an InnoDB type. Note that this function returns +the 'mtype' of InnoDB. InnoDB differentiates between MySQL's old <= 4.1 +VARCHAR and the new true VARCHAR in >= 5.0.3 by the 'prtype'. */ +UNIV_INTERN +ulint +get_innobase_type_from_mysql_type( +/*==============================*/ + /* out: DATA_BINARY, + DATA_VARCHAR, ... */ + ulint* unsigned_flag, /* out: DATA_UNSIGNED if an + 'unsigned type'; + at least ENUM and SET, + and unsigned integer + types are 'unsigned types' */ + const void* field) /* in: MySQL Field */ + __attribute__((nonnull)); + +/***************************************************************** +If you want to print a thd that is not associated with the current thread, +you must call this function before reserving the InnoDB kernel_mutex, to +protect MySQL from setting thd->query NULL. If you print a thd of the current +thread, we know that MySQL cannot modify thd->query, and it is not necessary +to call this. Call innobase_mysql_end_print_arbitrary_thd() after you release +the kernel_mutex. */ +UNIV_INTERN +void +innobase_mysql_prepare_print_arbitrary_thd(void); +/*============================================*/ + +/***************************************************************** +Releases the mutex reserved by innobase_mysql_prepare_print_arbitrary_thd(). +In the InnoDB latching order, the mutex sits right above the +kernel_mutex. In debug builds, we assert that the kernel_mutex is +released before this function is invoked. */ +UNIV_INTERN +void +innobase_mysql_end_print_arbitrary_thd(void); +/*========================================*/ + +/********************************************************************** +Get the variable length bounds of the given character set. */ +UNIV_INTERN +void +innobase_get_cset_width( +/*====================*/ + ulint cset, /* in: MySQL charset-collation code */ + ulint* mbminlen, /* out: minimum length of a char (in bytes) */ + ulint* mbmaxlen); /* out: maximum length of a char (in bytes) */ + +/********************************************************************** +Compares NUL-terminated UTF-8 strings case insensitively. */ +UNIV_INTERN +int +innobase_strcasecmp( +/*================*/ + /* out: 0 if a=b, <0 if a<b, >1 if a>b */ + const char* a, /* in: first string to compare */ + const char* b); /* in: second string to compare */ + +/********************************************************************** +Returns true if the thread is executing a SELECT statement. */ + +ibool +thd_is_select( +/*==========*/ + /* out: true if thd is executing SELECT */ + const void* thd); /* in: thread handle (THD*) */ + +/********************************************************************** +Converts an identifier to a table name. */ +UNIV_INTERN +void +innobase_convert_from_table_id( +/*===========================*/ + struct charset_info_st* cs, /* in: the 'from' character set */ + char* to, /* out: converted identifier */ + const char* from, /* in: identifier to convert */ + ulint len); /* in: length of 'to', in bytes; should + be at least 5 * strlen(to) + 1 */ +/********************************************************************** +Converts an identifier to UTF-8. */ +UNIV_INTERN +void +innobase_convert_from_id( +/*=====================*/ + struct charset_info_st* cs, /* in: the 'from' character set */ + char* to, /* out: converted identifier */ + const char* from, /* in: identifier to convert */ + ulint len); /* in: length of 'to', in bytes; should + be at least 3 * strlen(to) + 1 */ +/********************************************************************** +Makes all characters in a NUL-terminated UTF-8 string lower case. */ +UNIV_INTERN +void +innobase_casedn_str( +/*================*/ + char* a); /* in/out: string to put in lower case */ + +/************************************************************************** +Determines the connection character set. */ +struct charset_info_st* +innobase_get_charset( +/*=================*/ + /* out: connection character set */ + void* mysql_thd); /* in: MySQL thread handle */ + +/********************************************************************** +Returns true if the thread supports XA, +global value of innodb_supports_xa if thd is NULL. */ + +ibool +thd_supports_xa( +/*============*/ + /* out: true if thd supports XA */ + void* thd); /* in: thread handle (THD*), or NULL to query + the global innodb_supports_xa */ + +/********************************************************************** +Returns the lock wait timeout for the current connection. */ + +ulong +thd_lock_wait_timeout( +/*==================*/ + /* out: the lock wait timeout, in seconds */ + void* thd); /* in: thread handle (THD*), or NULL to query + the global innodb_lock_wait_timeout */ + +#endif +#endif diff --git a/storage/xtradb/include/handler0alter.h b/storage/xtradb/include/handler0alter.h new file mode 100644 index 00000000000..69488b67b2b --- /dev/null +++ b/storage/xtradb/include/handler0alter.h @@ -0,0 +1,41 @@ +/***************************************************************************** + +Copyright (c) 2005, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/****************************************************** +Smart ALTER TABLE +*******************************************************/ + +/***************************************************************** +Copies an InnoDB record to table->record[0]. */ +UNIV_INTERN +void +innobase_rec_to_mysql( +/*==================*/ + TABLE* table, /* in/out: MySQL table */ + const rec_t* rec, /* in: record */ + const dict_index_t* index, /* in: index */ + const ulint* offsets); /* in: rec_get_offsets( + rec, index, ...) */ + +/***************************************************************** +Resets table->record[0]. */ +UNIV_INTERN +void +innobase_rec_reset( +/*===============*/ + TABLE* table); /* in/out: MySQL table */ diff --git a/storage/xtradb/include/hash0hash.h b/storage/xtradb/include/hash0hash.h new file mode 100644 index 00000000000..2b3eea62754 --- /dev/null +++ b/storage/xtradb/include/hash0hash.h @@ -0,0 +1,429 @@ +/***************************************************************************** + +Copyright (c) 1997, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/****************************************************** +The simple hash table utility + +Created 5/20/1997 Heikki Tuuri +*******************************************************/ + +#ifndef hash0hash_h +#define hash0hash_h + +#include "univ.i" +#include "mem0mem.h" +#include "sync0sync.h" + +typedef struct hash_table_struct hash_table_t; +typedef struct hash_cell_struct hash_cell_t; + +typedef void* hash_node_t; + +/* Fix Bug #13859: symbol collision between imap/mysql */ +#define hash_create hash0_create + +/***************************************************************** +Creates a hash table with >= n array cells. The actual number +of cells is chosen to be a prime number slightly bigger than n. */ +UNIV_INTERN +hash_table_t* +hash_create( +/*========*/ + /* out, own: created table */ + ulint n); /* in: number of array cells */ +/***************************************************************** +Creates a mutex array to protect a hash table. */ +UNIV_INTERN +void +hash_create_mutexes_func( +/*=====================*/ + hash_table_t* table, /* in: hash table */ +#ifdef UNIV_SYNC_DEBUG + ulint sync_level, /* in: latching order level of the + mutexes: used in the debug version */ +#endif /* UNIV_SYNC_DEBUG */ + ulint n_mutexes); /* in: number of mutexes */ +#ifdef UNIV_SYNC_DEBUG +# define hash_create_mutexes(t,n,level) hash_create_mutexes_func(t,level,n) +#else /* UNIV_SYNC_DEBUG */ +# define hash_create_mutexes(t,n,level) hash_create_mutexes_func(t,n) +#endif /* UNIV_SYNC_DEBUG */ + +/***************************************************************** +Frees a hash table. */ +UNIV_INTERN +void +hash_table_free( +/*============*/ + hash_table_t* table); /* in, own: hash table */ +/****************************************************************** +Calculates the hash value from a folded value. */ +UNIV_INLINE +ulint +hash_calc_hash( +/*===========*/ + /* out: hashed value */ + ulint fold, /* in: folded value */ + hash_table_t* table); /* in: hash table */ +/************************************************************************ +Assert that the mutex for the table in a hash operation is owned. */ +#define HASH_ASSERT_OWNED(TABLE, FOLD) \ +ut_ad(!(TABLE)->mutexes || mutex_own(hash_get_mutex(TABLE, FOLD))); + +/*********************************************************************** +Inserts a struct to a hash table. */ + +#define HASH_INSERT(TYPE, NAME, TABLE, FOLD, DATA)\ +do {\ + hash_cell_t* cell3333;\ + TYPE* struct3333;\ +\ + HASH_ASSERT_OWNED(TABLE, FOLD)\ +\ + (DATA)->NAME = NULL;\ +\ + cell3333 = hash_get_nth_cell(TABLE, hash_calc_hash(FOLD, TABLE));\ +\ + if (cell3333->node == NULL) {\ + cell3333->node = DATA;\ + } else {\ + struct3333 = (TYPE*) cell3333->node;\ +\ + while (struct3333->NAME != NULL) {\ +\ + struct3333 = (TYPE*) struct3333->NAME;\ + }\ +\ + struct3333->NAME = DATA;\ + }\ +} while (0) + +#ifdef UNIV_HASH_DEBUG +# define HASH_ASSERT_VALID(DATA) ut_a((void*) (DATA) != (void*) -1) +# define HASH_INVALIDATE(DATA, NAME) DATA->NAME = (void*) -1 +#else +# define HASH_ASSERT_VALID(DATA) do {} while (0) +# define HASH_INVALIDATE(DATA, NAME) do {} while (0) +#endif + +/*********************************************************************** +Deletes a struct from a hash table. */ + +#define HASH_DELETE(TYPE, NAME, TABLE, FOLD, DATA)\ +do {\ + hash_cell_t* cell3333;\ + TYPE* struct3333;\ +\ + HASH_ASSERT_OWNED(TABLE, FOLD)\ +\ + cell3333 = hash_get_nth_cell(TABLE, hash_calc_hash(FOLD, TABLE));\ +\ + if (cell3333->node == DATA) {\ + HASH_ASSERT_VALID(DATA->NAME);\ + cell3333->node = DATA->NAME;\ + } else {\ + struct3333 = (TYPE*) cell3333->node;\ +\ + while (struct3333->NAME != DATA) {\ +\ + struct3333 = (TYPE*) struct3333->NAME;\ + ut_a(struct3333);\ + }\ +\ + struct3333->NAME = DATA->NAME;\ + }\ + HASH_INVALIDATE(DATA, NAME);\ +} while (0) + +/*********************************************************************** +Gets the first struct in a hash chain, NULL if none. */ + +#define HASH_GET_FIRST(TABLE, HASH_VAL)\ + (hash_get_nth_cell(TABLE, HASH_VAL)->node) + +/*********************************************************************** +Gets the next struct in a hash chain, NULL if none. */ + +#define HASH_GET_NEXT(NAME, DATA) ((DATA)->NAME) + +/************************************************************************ +Looks for a struct in a hash table. */ +#define HASH_SEARCH(NAME, TABLE, FOLD, TYPE, DATA, ASSERTION, TEST)\ +{\ +\ + HASH_ASSERT_OWNED(TABLE, FOLD)\ +\ + (DATA) = (TYPE) HASH_GET_FIRST(TABLE, hash_calc_hash(FOLD, TABLE));\ + HASH_ASSERT_VALID(DATA);\ +\ + while ((DATA) != NULL) {\ + ASSERTION;\ + if (TEST) {\ + break;\ + } else {\ + HASH_ASSERT_VALID(HASH_GET_NEXT(NAME, DATA));\ + (DATA) = (TYPE) HASH_GET_NEXT(NAME, DATA);\ + }\ + }\ +} + +/************************************************************************ +Looks for an item in all hash buckets. */ +#define HASH_SEARCH_ALL(NAME, TABLE, TYPE, DATA, ASSERTION, TEST) \ +do { \ + ulint i3333; \ + \ + for (i3333 = (TABLE)->n_cells; i3333--; ) { \ + (DATA) = (TYPE) HASH_GET_FIRST(TABLE, i3333); \ + \ + while ((DATA) != NULL) { \ + HASH_ASSERT_VALID(DATA); \ + ASSERTION; \ + \ + if (TEST) { \ + break; \ + } \ + \ + (DATA) = (TYPE) HASH_GET_NEXT(NAME, DATA); \ + } \ + \ + if ((DATA) != NULL) { \ + break; \ + } \ + } \ +} while (0) + +/**************************************************************** +Gets the nth cell in a hash table. */ +UNIV_INLINE +hash_cell_t* +hash_get_nth_cell( +/*==============*/ + /* out: pointer to cell */ + hash_table_t* table, /* in: hash table */ + ulint n); /* in: cell index */ + +/***************************************************************** +Clears a hash table so that all the cells become empty. */ +UNIV_INLINE +void +hash_table_clear( +/*=============*/ + hash_table_t* table); /* in/out: hash table */ + +/***************************************************************** +Returns the number of cells in a hash table. */ +UNIV_INLINE +ulint +hash_get_n_cells( +/*=============*/ + /* out: number of cells */ + hash_table_t* table); /* in: table */ +/*********************************************************************** +Deletes a struct which is stored in the heap of the hash table, and compacts +the heap. The fold value must be stored in the struct NODE in a field named +'fold'. */ + +#define HASH_DELETE_AND_COMPACT(TYPE, NAME, TABLE, NODE)\ +do {\ + TYPE* node111;\ + TYPE* top_node111;\ + hash_cell_t* cell111;\ + ulint fold111;\ +\ + fold111 = (NODE)->fold;\ +\ + HASH_DELETE(TYPE, NAME, TABLE, fold111, NODE);\ +\ + top_node111 = (TYPE*)mem_heap_get_top(\ + hash_get_heap(TABLE, fold111),\ + sizeof(TYPE));\ +\ + /* If the node to remove is not the top node in the heap, compact the\ + heap of nodes by moving the top node in the place of NODE. */\ +\ + if (NODE != top_node111) {\ +\ + /* Copy the top node in place of NODE */\ +\ + *(NODE) = *top_node111;\ +\ + cell111 = hash_get_nth_cell(TABLE,\ + hash_calc_hash(top_node111->fold, TABLE));\ +\ + /* Look for the pointer to the top node, to update it */\ +\ + if (cell111->node == top_node111) {\ + /* The top node is the first in the chain */\ +\ + cell111->node = NODE;\ + } else {\ + /* We have to look for the predecessor of the top\ + node */\ + node111 = cell111->node;\ +\ + while (top_node111 != HASH_GET_NEXT(NAME, node111)) {\ +\ + node111 = HASH_GET_NEXT(NAME, node111);\ + }\ +\ + /* Now we have the predecessor node */\ +\ + node111->NAME = NODE;\ + }\ + }\ +\ + /* Free the space occupied by the top node */\ +\ + mem_heap_free_top(hash_get_heap(TABLE, fold111), sizeof(TYPE));\ +} while (0) + +/******************************************************************** +Move all hash table entries from OLD_TABLE to NEW_TABLE.*/ + +#define HASH_MIGRATE(OLD_TABLE, NEW_TABLE, NODE_TYPE, PTR_NAME, FOLD_FUNC) \ +do {\ + ulint i2222;\ + ulint cell_count2222;\ +\ + cell_count2222 = hash_get_n_cells(OLD_TABLE);\ +\ + for (i2222 = 0; i2222 < cell_count2222; i2222++) {\ + NODE_TYPE* node2222 = HASH_GET_FIRST((OLD_TABLE), i2222);\ +\ + while (node2222) {\ + NODE_TYPE* next2222 = node2222->PTR_NAME;\ + ulint fold2222 = FOLD_FUNC(node2222);\ +\ + HASH_INSERT(NODE_TYPE, PTR_NAME, (NEW_TABLE),\ + fold2222, node2222);\ +\ + node2222 = next2222;\ + }\ + }\ +} while (0) + + +/**************************************************************** +Gets the mutex index for a fold value in a hash table. */ +UNIV_INLINE +ulint +hash_get_mutex_no( +/*==============*/ + /* out: mutex number */ + hash_table_t* table, /* in: hash table */ + ulint fold); /* in: fold */ +/**************************************************************** +Gets the nth heap in a hash table. */ +UNIV_INLINE +mem_heap_t* +hash_get_nth_heap( +/*==============*/ + /* out: mem heap */ + hash_table_t* table, /* in: hash table */ + ulint i); /* in: index of the heap */ +/**************************************************************** +Gets the heap for a fold value in a hash table. */ +UNIV_INLINE +mem_heap_t* +hash_get_heap( +/*==========*/ + /* out: mem heap */ + hash_table_t* table, /* in: hash table */ + ulint fold); /* in: fold */ +/**************************************************************** +Gets the nth mutex in a hash table. */ +UNIV_INLINE +mutex_t* +hash_get_nth_mutex( +/*===============*/ + /* out: mutex */ + hash_table_t* table, /* in: hash table */ + ulint i); /* in: index of the mutex */ +/**************************************************************** +Gets the mutex for a fold value in a hash table. */ +UNIV_INLINE +mutex_t* +hash_get_mutex( +/*===========*/ + /* out: mutex */ + hash_table_t* table, /* in: hash table */ + ulint fold); /* in: fold */ +/**************************************************************** +Reserves the mutex for a fold value in a hash table. */ +UNIV_INTERN +void +hash_mutex_enter( +/*=============*/ + hash_table_t* table, /* in: hash table */ + ulint fold); /* in: fold */ +/**************************************************************** +Releases the mutex for a fold value in a hash table. */ +UNIV_INTERN +void +hash_mutex_exit( +/*============*/ + hash_table_t* table, /* in: hash table */ + ulint fold); /* in: fold */ +/**************************************************************** +Reserves all the mutexes of a hash table, in an ascending order. */ +UNIV_INTERN +void +hash_mutex_enter_all( +/*=================*/ + hash_table_t* table); /* in: hash table */ +/**************************************************************** +Releases all the mutexes of a hash table. */ +UNIV_INTERN +void +hash_mutex_exit_all( +/*================*/ + hash_table_t* table); /* in: hash table */ + + +struct hash_cell_struct{ + void* node; /* hash chain node, NULL if none */ +}; + +/* The hash table structure */ +struct hash_table_struct { +#if defined UNIV_AHI_DEBUG || defined UNIV_DEBUG + ibool adaptive;/* TRUE if this is the hash table of the + adaptive hash index */ +#endif /* UNIV_AHI_DEBUG || UNIV_DEBUG */ + ulint n_cells;/* number of cells in the hash table */ + hash_cell_t* array; /* pointer to cell array */ + ulint n_mutexes;/* if mutexes != NULL, then the number of + mutexes, must be a power of 2 */ + mutex_t* mutexes;/* NULL, or an array of mutexes used to + protect segments of the hash table */ + mem_heap_t** heaps; /* if this is non-NULL, hash chain nodes for + external chaining can be allocated from these + memory heaps; there are then n_mutexes many of + these heaps */ + mem_heap_t* heap; + ulint magic_n; +}; + +#define HASH_TABLE_MAGIC_N 76561114 + +#ifndef UNIV_NONINL +#include "hash0hash.ic" +#endif + +#endif diff --git a/storage/xtradb/include/hash0hash.ic b/storage/xtradb/include/hash0hash.ic new file mode 100644 index 00000000000..792fdcbf4f8 --- /dev/null +++ b/storage/xtradb/include/hash0hash.ic @@ -0,0 +1,160 @@ +/***************************************************************************** + +Copyright (c) 1997, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/****************************************************** +The simple hash table utility + +Created 5/20/1997 Heikki Tuuri +*******************************************************/ + +#include "ut0rnd.h" + +/**************************************************************** +Gets the nth cell in a hash table. */ +UNIV_INLINE +hash_cell_t* +hash_get_nth_cell( +/*==============*/ + /* out: pointer to cell */ + hash_table_t* table, /* in: hash table */ + ulint n) /* in: cell index */ +{ + ut_ad(n < table->n_cells); + + return(table->array + n); +} + +/***************************************************************** +Clears a hash table so that all the cells become empty. */ +UNIV_INLINE +void +hash_table_clear( +/*=============*/ + hash_table_t* table) /* in/out: hash table */ +{ + memset(table->array, 0x0, + table->n_cells * sizeof(*table->array)); +} + +/***************************************************************** +Returns the number of cells in a hash table. */ +UNIV_INLINE +ulint +hash_get_n_cells( +/*=============*/ + /* out: number of cells */ + hash_table_t* table) /* in: table */ +{ + return(table->n_cells); +} + +/****************************************************************** +Calculates the hash value from a folded value. */ +UNIV_INLINE +ulint +hash_calc_hash( +/*===========*/ + /* out: hashed value */ + ulint fold, /* in: folded value */ + hash_table_t* table) /* in: hash table */ +{ + return(ut_hash_ulint(fold, table->n_cells)); +} + +/**************************************************************** +Gets the mutex index for a fold value in a hash table. */ +UNIV_INLINE +ulint +hash_get_mutex_no( +/*==============*/ + /* out: mutex number */ + hash_table_t* table, /* in: hash table */ + ulint fold) /* in: fold */ +{ + ut_ad(ut_is_2pow(table->n_mutexes)); + return(ut_2pow_remainder(hash_calc_hash(fold, table), + table->n_mutexes)); +} + +/**************************************************************** +Gets the nth heap in a hash table. */ +UNIV_INLINE +mem_heap_t* +hash_get_nth_heap( +/*==============*/ + /* out: mem heap */ + hash_table_t* table, /* in: hash table */ + ulint i) /* in: index of the heap */ +{ + ut_ad(i < table->n_mutexes); + + return(table->heaps[i]); +} + +/**************************************************************** +Gets the heap for a fold value in a hash table. */ +UNIV_INLINE +mem_heap_t* +hash_get_heap( +/*==========*/ + /* out: mem heap */ + hash_table_t* table, /* in: hash table */ + ulint fold) /* in: fold */ +{ + ulint i; + + if (table->heap) { + return(table->heap); + } + + i = hash_get_mutex_no(table, fold); + + return(hash_get_nth_heap(table, i)); +} + +/**************************************************************** +Gets the nth mutex in a hash table. */ +UNIV_INLINE +mutex_t* +hash_get_nth_mutex( +/*===============*/ + /* out: mutex */ + hash_table_t* table, /* in: hash table */ + ulint i) /* in: index of the mutex */ +{ + ut_ad(i < table->n_mutexes); + + return(table->mutexes + i); +} + +/**************************************************************** +Gets the mutex for a fold value in a hash table. */ +UNIV_INLINE +mutex_t* +hash_get_mutex( +/*===========*/ + /* out: mutex */ + hash_table_t* table, /* in: hash table */ + ulint fold) /* in: fold */ +{ + ulint i; + + i = hash_get_mutex_no(table, fold); + + return(hash_get_nth_mutex(table, i)); +} diff --git a/storage/xtradb/include/ibuf0ibuf.h b/storage/xtradb/include/ibuf0ibuf.h new file mode 100644 index 00000000000..41e2392cc4a --- /dev/null +++ b/storage/xtradb/include/ibuf0ibuf.h @@ -0,0 +1,369 @@ +/***************************************************************************** + +Copyright (c) 1997, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/****************************************************** +Insert buffer + +Created 7/19/1997 Heikki Tuuri +*******************************************************/ + +#ifndef ibuf0ibuf_h +#define ibuf0ibuf_h + +#include "univ.i" + +#include "dict0mem.h" +#include "mtr0mtr.h" +#include "que0types.h" +#include "ibuf0types.h" +#include "fsp0fsp.h" + +/** Combinations of operations that can be buffered. Because the enum +values are used for indexing innobase_change_buffering_values[], they +should start at 0 and there should not be any gaps. */ +typedef enum { + IBUF_USE_NONE = 0, + IBUF_USE_INSERT, /* insert */ + + IBUF_USE_COUNT /* number of entries in ibuf_use_t */ +} ibuf_use_t; + +/** Operations that can currently be buffered. */ +extern ibuf_use_t ibuf_use; + +/** The insert buffer control structure */ +extern ibuf_t* ibuf; + +/* The purpose of the insert buffer is to reduce random disk access. +When we wish to insert a record into a non-unique secondary index and +the B-tree leaf page where the record belongs to is not in the buffer +pool, we insert the record into the insert buffer B-tree, indexed by +(space_id, page_no). When the page is eventually read into the buffer +pool, we look up the insert buffer B-tree for any modifications to the +page, and apply these upon the completion of the read operation. This +is called the insert buffer merge. */ + +/* The insert buffer merge must always succeed. To guarantee this, +the insert buffer subsystem keeps track of the free space in pages for +which it can buffer operations. Two bits per page in the insert +buffer bitmap indicate the available space in coarse increments. The +free bits in the insert buffer bitmap must never exceed the free space +on a page. It is safe to decrement or reset the bits in the bitmap in +a mini-transaction that is committed before the mini-transaction that +affects the free space. It is unsafe to increment the bits in a +separately committed mini-transaction, because in crash recovery, the +free bits could momentarily be set too high. */ + +/********************************************************************** +Creates the insert buffer data structure at a database startup and +initializes the data structures for the insert buffer of each tablespace. */ +UNIV_INTERN +void +ibuf_init_at_db_start(void); +/*=======================*/ +/************************************************************************* +Reads the biggest tablespace id from the high end of the insert buffer +tree and updates the counter in fil_system. */ +UNIV_INTERN +void +ibuf_update_max_tablespace_id(void); +/*===============================*/ +/************************************************************************* +Initializes an ibuf bitmap page. */ +UNIV_INTERN +void +ibuf_bitmap_page_init( +/*==================*/ + buf_block_t* block, /* in: bitmap page */ + mtr_t* mtr); /* in: mtr */ +/**************************************************************************** +Resets the free bits of the page in the ibuf bitmap. This is done in a +separate mini-transaction, hence this operation does not restrict +further work to only ibuf bitmap operations, which would result if the +latch to the bitmap page were kept. NOTE: The free bits in the insert +buffer bitmap must never exceed the free space on a page. It is safe +to decrement or reset the bits in the bitmap in a mini-transaction +that is committed before the mini-transaction that affects the free +space. */ +UNIV_INTERN +void +ibuf_reset_free_bits( +/*=================*/ + buf_block_t* block); /* in: index page; free bits are set to 0 + if the index is a non-clustered + non-unique, and page level is 0 */ +/**************************************************************************** +Updates the free bits of an uncompressed page in the ibuf bitmap if +there is not enough free on the page any more. This is done in a +separate mini-transaction, hence this operation does not restrict +further work to only ibuf bitmap operations, which would result if the +latch to the bitmap page were kept. NOTE: The free bits in the insert +buffer bitmap must never exceed the free space on a page. It is +unsafe to increment the bits in a separately committed +mini-transaction, because in crash recovery, the free bits could +momentarily be set too high. It is only safe to use this function for +decrementing the free bits. Should more free space become available, +we must not update the free bits here, because that would break crash +recovery. */ +UNIV_INLINE +void +ibuf_update_free_bits_if_full( +/*==========================*/ + buf_block_t* block, /* in: index page to which we have added new + records; the free bits are updated if the + index is non-clustered and non-unique and + the page level is 0, and the page becomes + fuller */ + ulint max_ins_size,/* in: value of maximum insert size with + reorganize before the latest operation + performed to the page */ + ulint increase);/* in: upper limit for the additional space + used in the latest operation, if known, or + ULINT_UNDEFINED */ +/************************************************************************** +Updates the free bits for an uncompressed page to reflect the present +state. Does this in the mtr given, which means that the latching +order rules virtually prevent any further operations for this OS +thread until mtr is committed. NOTE: The free bits in the insert +buffer bitmap must never exceed the free space on a page. It is safe +to set the free bits in the same mini-transaction that updated the +page. */ +UNIV_INTERN +void +ibuf_update_free_bits_low( +/*======================*/ + const buf_block_t* block, /* in: index page */ + ulint max_ins_size, /* in: value of + maximum insert size + with reorganize before + the latest operation + performed to the page */ + mtr_t* mtr); /* in/out: mtr */ +/************************************************************************** +Updates the free bits for a compressed page to reflect the present +state. Does this in the mtr given, which means that the latching +order rules virtually prevent any further operations for this OS +thread until mtr is committed. NOTE: The free bits in the insert +buffer bitmap must never exceed the free space on a page. It is safe +to set the free bits in the same mini-transaction that updated the +page. */ +UNIV_INTERN +void +ibuf_update_free_bits_zip( +/*======================*/ + buf_block_t* block, /* in/out: index page */ + mtr_t* mtr); /* in/out: mtr */ +/************************************************************************** +Updates the free bits for the two pages to reflect the present state. +Does this in the mtr given, which means that the latching order rules +virtually prevent any further operations until mtr is committed. +NOTE: The free bits in the insert buffer bitmap must never exceed the +free space on a page. It is safe to set the free bits in the same +mini-transaction that updated the pages. */ +UNIV_INTERN +void +ibuf_update_free_bits_for_two_pages_low( +/*====================================*/ + ulint zip_size,/* in: compressed page size in bytes; + 0 for uncompressed pages */ + buf_block_t* block1, /* in: index page */ + buf_block_t* block2, /* in: index page */ + mtr_t* mtr); /* in: mtr */ +/************************************************************************** +A basic partial test if an insert to the insert buffer could be possible and +recommended. */ +UNIV_INLINE +ibool +ibuf_should_try( +/*============*/ + dict_index_t* index, /* in: index where to insert */ + ulint ignore_sec_unique); /* in: if != 0, we should + ignore UNIQUE constraint on + a secondary index when we + decide */ +/********************************************************************** +Returns TRUE if the current OS thread is performing an insert buffer +routine. */ +UNIV_INTERN +ibool +ibuf_inside(void); +/*=============*/ + /* out: TRUE if inside an insert buffer routine: for instance, + a read-ahead of non-ibuf pages is then forbidden */ +/*************************************************************************** +Checks if a page address is an ibuf bitmap page (level 3 page) address. */ +UNIV_INLINE +ibool +ibuf_bitmap_page( +/*=============*/ + /* out: TRUE if a bitmap page */ + ulint zip_size,/* in: compressed page size in bytes; + 0 for uncompressed pages */ + ulint page_no);/* in: page number */ +/*************************************************************************** +Checks if a page is a level 2 or 3 page in the ibuf hierarchy of pages. +Must not be called when recv_no_ibuf_operations==TRUE. */ +UNIV_INTERN +ibool +ibuf_page( +/*======*/ + /* out: TRUE if level 2 or level 3 page */ + ulint space, /* in: space id */ + ulint zip_size,/* in: compressed page size in bytes, or 0 */ + ulint page_no,/* in: page number */ + mtr_t* mtr); /* in: mtr which will contain an x-latch to the + bitmap page if the page is not one of the fixed + address ibuf pages, or NULL, in which case a new + transaction is created. */ +/*************************************************************************** +Frees excess pages from the ibuf free list. This function is called when an OS +thread calls fsp services to allocate a new file segment, or a new page to a +file segment, and the thread did not own the fsp latch before this call. */ +UNIV_INTERN +void +ibuf_free_excess_pages(void); +/*========================*/ +/************************************************************************* +Makes an index insert to the insert buffer, instead of directly to the disk +page, if this is possible. Does not do insert if the index is clustered +or unique. */ +UNIV_INTERN +ibool +ibuf_insert( +/*========*/ + /* out: TRUE if success */ + const dtuple_t* entry, /* in: index entry to insert */ + dict_index_t* index, /* in: index where to insert */ + ulint space, /* in: space id where to insert */ + ulint zip_size,/* in: compressed page size in bytes, or 0 */ + ulint page_no,/* in: page number where to insert */ + que_thr_t* thr); /* in: query thread */ +/************************************************************************* +When an index page is read from a disk to the buffer pool, this function +inserts to the page the possible index entries buffered in the insert buffer. +The entries are deleted from the insert buffer. If the page is not read, but +created in the buffer pool, this function deletes its buffered entries from +the insert buffer; there can exist entries for such a page if the page +belonged to an index which subsequently was dropped. */ +UNIV_INTERN +void +ibuf_merge_or_delete_for_page( +/*==========================*/ + buf_block_t* block, /* in: if page has been read from + disk, pointer to the page x-latched, + else NULL */ + ulint space, /* in: space id of the index page */ + ulint page_no,/* in: page number of the index page */ + ulint zip_size,/* in: compressed page size in bytes, + or 0 */ + ibool update_ibuf_bitmap);/* in: normally this is set + to TRUE, but if we have deleted or are + deleting the tablespace, then we + naturally do not want to update a + non-existent bitmap page */ +/************************************************************************* +Deletes all entries in the insert buffer for a given space id. This is used +in DISCARD TABLESPACE and IMPORT TABLESPACE. +NOTE: this does not update the page free bitmaps in the space. The space will +become CORRUPT when you call this function! */ +UNIV_INTERN +void +ibuf_delete_for_discarded_space( +/*============================*/ + ulint space); /* in: space id */ +/************************************************************************* +Contracts insert buffer trees by reading pages to the buffer pool. */ +UNIV_INTERN +ulint +ibuf_contract( +/*==========*/ + /* out: a lower limit for the combined size in bytes + of entries which will be merged from ibuf trees to the + pages read, 0 if ibuf is empty */ + ibool sync); /* in: TRUE if the caller wants to wait for the + issued read with the highest tablespace address + to complete */ +/************************************************************************* +Contracts insert buffer trees by reading pages to the buffer pool. */ +UNIV_INTERN +ulint +ibuf_contract_for_n_pages( +/*======================*/ + /* out: a lower limit for the combined size in bytes + of entries which will be merged from ibuf trees to the + pages read, 0 if ibuf is empty */ + ibool sync, /* in: TRUE if the caller wants to wait for the + issued read with the highest tablespace address + to complete */ + ulint n_pages);/* in: try to read at least this many pages to + the buffer pool and merge the ibuf contents to + them */ +/************************************************************************* +Parses a redo log record of an ibuf bitmap page init. */ +UNIV_INTERN +byte* +ibuf_parse_bitmap_init( +/*===================*/ + /* out: end of log record or NULL */ + byte* ptr, /* in: buffer */ + byte* end_ptr,/* in: buffer end */ + buf_block_t* block, /* in: block or NULL */ + mtr_t* mtr); /* in: mtr or NULL */ +#ifdef UNIV_IBUF_COUNT_DEBUG +/********************************************************************** +Gets the ibuf count for a given page. */ +UNIV_INTERN +ulint +ibuf_count_get( +/*===========*/ + /* out: number of entries in the insert buffer + currently buffered for this page */ + ulint space, /* in: space id */ + ulint page_no);/* in: page number */ +#endif +/********************************************************************** +Looks if the insert buffer is empty. */ +UNIV_INTERN +ibool +ibuf_is_empty(void); +/*===============*/ + /* out: TRUE if empty */ +/********************************************************************** +Prints info of ibuf. */ +UNIV_INTERN +void +ibuf_print( +/*=======*/ + FILE* file); /* in: file where to print */ + +#define IBUF_HEADER_PAGE_NO FSP_IBUF_HEADER_PAGE_NO +#define IBUF_TREE_ROOT_PAGE_NO FSP_IBUF_TREE_ROOT_PAGE_NO + +/* The ibuf header page currently contains only the file segment header +for the file segment from which the pages for the ibuf tree are allocated */ +#define IBUF_HEADER PAGE_DATA +#define IBUF_TREE_SEG_HEADER 0 /* fseg header for ibuf tree */ + +/* The insert buffer tree itself is always located in space 0. */ +#define IBUF_SPACE_ID 0 + +#ifndef UNIV_NONINL +#include "ibuf0ibuf.ic" +#endif + +#endif diff --git a/storage/xtradb/include/ibuf0ibuf.ic b/storage/xtradb/include/ibuf0ibuf.ic new file mode 100644 index 00000000000..170e5dba473 --- /dev/null +++ b/storage/xtradb/include/ibuf0ibuf.ic @@ -0,0 +1,325 @@ +/***************************************************************************** + +Copyright (c) 1997, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/****************************************************** +Insert buffer + +Created 7/19/1997 Heikki Tuuri +*******************************************************/ + +#include "buf0lru.h" +#include "page0page.h" +#include "page0zip.h" + +extern ulint ibuf_flush_count; + +/* If this number is n, an index page must contain at least the page size +per n bytes of free space for ibuf to try to buffer inserts to this page. +If there is this much of free space, the corresponding bits are set in the +ibuf bitmap. */ +#define IBUF_PAGE_SIZE_PER_FREE_SPACE 32 + +/* Insert buffer struct */ + +struct ibuf_struct{ + ulint size; /* current size of the ibuf index + tree, in pages */ + ulint max_size; /* recommended maximum size of the + ibuf index tree, in pages */ + ulint seg_size; /* allocated pages of the file + segment containing ibuf header and + tree */ + ibool empty; /* after an insert to the ibuf tree + is performed, this is set to FALSE, + and if a contract operation finds + the tree empty, this is set to + TRUE */ + ulint free_list_len; /* length of the free list */ + ulint height; /* tree height */ + dict_index_t* index; /* insert buffer index */ + + ulint n_inserts; /* number of inserts made to + the insert buffer */ + ulint n_merges; /* number of pages merged */ + ulint n_merged_recs; /* number of records merged */ +}; + +/**************************************************************************** +Sets the free bit of the page in the ibuf bitmap. This is done in a separate +mini-transaction, hence this operation does not restrict further work to only +ibuf bitmap operations, which would result if the latch to the bitmap page +were kept. */ +UNIV_INTERN +void +ibuf_set_free_bits_func( +/*====================*/ + buf_block_t* block, /* in: index page of a non-clustered index; + free bit is reset if page level is 0 */ +#ifdef UNIV_IBUF_DEBUG + ulint max_val,/* in: ULINT_UNDEFINED or a maximum + value which the bits must have before + setting; this is for debugging */ +#endif /* UNIV_IBUF_DEBUG */ + ulint val); /* in: value to set: < 4 */ +#ifdef UNIV_IBUF_DEBUG +# define ibuf_set_free_bits(b,v,max) ibuf_set_free_bits_func(b,max,v) +#else /* UNIV_IBUF_DEBUG */ +# define ibuf_set_free_bits(b,v,max) ibuf_set_free_bits_func(b,v) +#endif /* UNIV_IBUF_DEBUG */ + +/************************************************************************** +A basic partial test if an insert to the insert buffer could be possible and +recommended. */ +UNIV_INLINE +ibool +ibuf_should_try( +/*============*/ + dict_index_t* index, /* in: index where to insert */ + ulint ignore_sec_unique) /* in: if != 0, we should + ignore UNIQUE constraint on + a secondary index when we + decide */ +{ + if (ibuf_use != IBUF_USE_NONE + && !dict_index_is_clust(index) + && (ignore_sec_unique || !dict_index_is_unique(index))) { + + ibuf_flush_count++; + + if (ibuf_flush_count % 4 == 0) { + + buf_LRU_try_free_flushed_blocks(); + } + + return(TRUE); + } + + return(FALSE); +} + +/*************************************************************************** +Checks if a page address is an ibuf bitmap page address. */ +UNIV_INLINE +ibool +ibuf_bitmap_page( +/*=============*/ + /* out: TRUE if a bitmap page */ + ulint zip_size,/* in: compressed page size in bytes; + 0 for uncompressed pages */ + ulint page_no)/* in: page number */ +{ + ut_ad(ut_is_2pow(zip_size)); + + if (!zip_size) { + return(UNIV_UNLIKELY((page_no & (UNIV_PAGE_SIZE - 1)) + == FSP_IBUF_BITMAP_OFFSET)); + } + + return(UNIV_UNLIKELY((page_no & (zip_size - 1)) + == FSP_IBUF_BITMAP_OFFSET)); +} + +/************************************************************************* +Translates the free space on a page to a value in the ibuf bitmap.*/ +UNIV_INLINE +ulint +ibuf_index_page_calc_free_bits( +/*===========================*/ + /* out: value for ibuf bitmap bits */ + ulint zip_size, /* in: compressed page size in bytes; + 0 for uncompressed pages */ + ulint max_ins_size) /* in: maximum insert size after reorganize + for the page */ +{ + ulint n; + ut_ad(ut_is_2pow(zip_size)); + ut_ad(!zip_size || zip_size > IBUF_PAGE_SIZE_PER_FREE_SPACE); + ut_ad(zip_size <= UNIV_PAGE_SIZE); + + if (zip_size) { + n = max_ins_size + / (zip_size / IBUF_PAGE_SIZE_PER_FREE_SPACE); + } else { + n = max_ins_size + / (UNIV_PAGE_SIZE / IBUF_PAGE_SIZE_PER_FREE_SPACE); + } + + if (n == 3) { + n = 2; + } + + if (n > 3) { + n = 3; + } + + return(n); +} + +/************************************************************************* +Translates the ibuf free bits to the free space on a page in bytes. */ +UNIV_INLINE +ulint +ibuf_index_page_calc_free_from_bits( +/*================================*/ + /* out: maximum insert size after reorganize for the + page */ + ulint zip_size,/* in: compressed page size in bytes; + 0 for uncompressed pages */ + ulint bits) /* in: value for ibuf bitmap bits */ +{ + ut_ad(bits < 4); + ut_ad(ut_is_2pow(zip_size)); + ut_ad(!zip_size || zip_size > IBUF_PAGE_SIZE_PER_FREE_SPACE); + ut_ad(zip_size <= UNIV_PAGE_SIZE); + + if (zip_size) { + if (bits == 3) { + return(4 * zip_size / IBUF_PAGE_SIZE_PER_FREE_SPACE); + } + + return(bits * zip_size / IBUF_PAGE_SIZE_PER_FREE_SPACE); + } + + if (bits == 3) { + return(4 * UNIV_PAGE_SIZE / IBUF_PAGE_SIZE_PER_FREE_SPACE); + } + + return(bits * (UNIV_PAGE_SIZE / IBUF_PAGE_SIZE_PER_FREE_SPACE)); +} + +/************************************************************************* +Translates the free space on a compressed page to a value in the ibuf bitmap.*/ +UNIV_INLINE +ulint +ibuf_index_page_calc_free_zip( +/*==========================*/ + /* out: value for ibuf bitmap bits */ + ulint zip_size, + /* in: compressed page size in bytes */ + const buf_block_t* block) /* in: buffer block */ +{ + ulint max_ins_size; + const page_zip_des_t* page_zip; + lint zip_max_ins; + + ut_ad(zip_size == buf_block_get_zip_size(block)); + ut_ad(zip_size); + + max_ins_size = page_get_max_insert_size_after_reorganize( + buf_block_get_frame(block), 1); + + page_zip = buf_block_get_page_zip(block); + zip_max_ins = page_zip_max_ins_size(page_zip, + FALSE/* not clustered */); + + if (UNIV_UNLIKELY(zip_max_ins < 0)) { + return(0); + } else if (UNIV_LIKELY(max_ins_size > (ulint) zip_max_ins)) { + max_ins_size = (ulint) zip_max_ins; + } + + return(ibuf_index_page_calc_free_bits(zip_size, max_ins_size)); +} + +/************************************************************************* +Translates the free space on a page to a value in the ibuf bitmap.*/ +UNIV_INLINE +ulint +ibuf_index_page_calc_free( +/*======================*/ + /* out: value for ibuf bitmap bits */ + ulint zip_size,/* in: compressed page size in bytes; + 0 for uncompressed pages */ + const buf_block_t* block) /* in: buffer block */ +{ + ut_ad(zip_size == buf_block_get_zip_size(block)); + + if (!zip_size) { + ulint max_ins_size; + + max_ins_size = page_get_max_insert_size_after_reorganize( + buf_block_get_frame(block), 1); + + return(ibuf_index_page_calc_free_bits(0, max_ins_size)); + } else { + return(ibuf_index_page_calc_free_zip(zip_size, block)); + } +} + +/**************************************************************************** +Updates the free bits of an uncompressed page in the ibuf bitmap if +there is not enough free on the page any more. This is done in a +separate mini-transaction, hence this operation does not restrict +further work to only ibuf bitmap operations, which would result if the +latch to the bitmap page were kept. NOTE: The free bits in the insert +buffer bitmap must never exceed the free space on a page. It is +unsafe to increment the bits in a separately committed +mini-transaction, because in crash recovery, the free bits could +momentarily be set too high. It is only safe to use this function for +decrementing the free bits. Should more free space become available, +we must not update the free bits here, because that would break crash +recovery. */ +UNIV_INLINE +void +ibuf_update_free_bits_if_full( +/*==========================*/ + buf_block_t* block, /* in: index page to which we have added new + records; the free bits are updated if the + index is non-clustered and non-unique and + the page level is 0, and the page becomes + fuller */ + ulint max_ins_size,/* in: value of maximum insert size with + reorganize before the latest operation + performed to the page */ + ulint increase)/* in: upper limit for the additional space + used in the latest operation, if known, or + ULINT_UNDEFINED */ +{ + ulint before; + ulint after; + + ut_ad(!buf_block_get_page_zip(block)); + + before = ibuf_index_page_calc_free_bits(0, max_ins_size); + + if (max_ins_size >= increase) { +#if ULINT32_UNDEFINED <= UNIV_PAGE_SIZE +# error "ULINT32_UNDEFINED <= UNIV_PAGE_SIZE" +#endif + after = ibuf_index_page_calc_free_bits(0, max_ins_size + - increase); +#ifdef UNIV_IBUF_DEBUG + ut_a(after <= ibuf_index_page_calc_free(0, block)); +#endif + } else { + after = ibuf_index_page_calc_free(0, block); + } + + if (after == 0) { + /* We move the page to the front of the buffer pool LRU list: + the purpose of this is to prevent those pages to which we + cannot make inserts using the insert buffer from slipping + out of the buffer pool */ + + buf_page_make_young(&block->page); + } + + if (before > after) { + ibuf_set_free_bits(block, after, before); + } +} diff --git a/storage/xtradb/include/ibuf0types.h b/storage/xtradb/include/ibuf0types.h new file mode 100644 index 00000000000..264415196a1 --- /dev/null +++ b/storage/xtradb/include/ibuf0types.h @@ -0,0 +1,30 @@ +/***************************************************************************** + +Copyright (c) 1997, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/****************************************************** +Insert buffer global types + +Created 7/29/1997 Heikki Tuuri +*******************************************************/ + +#ifndef ibuf0types_h +#define ibuf0types_h + +typedef struct ibuf_struct ibuf_t; + +#endif diff --git a/storage/xtradb/include/lock0iter.h b/storage/xtradb/include/lock0iter.h new file mode 100644 index 00000000000..3cd47bb95d2 --- /dev/null +++ b/storage/xtradb/include/lock0iter.h @@ -0,0 +1,68 @@ +/***************************************************************************** + +Copyright (c) 2007, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/****************************************************** +Lock queue iterator type and function prototypes. + +Created July 16, 2007 Vasil Dimov +*******************************************************/ + +#ifndef lock0iter_h +#define lock0iter_h + +#include "univ.i" +#include "lock0types.h" + +typedef struct lock_queue_iterator_struct { + const lock_t* current_lock; + /* In case this is a record lock queue (not table lock queue) + then bit_no is the record number within the heap in which the + record is stored. */ + ulint bit_no; +} lock_queue_iterator_t; + +/*********************************************************************** +Initialize lock queue iterator so that it starts to iterate from +"lock". bit_no specifies the record number within the heap where the +record is stored. It can be undefined (ULINT_UNDEFINED) in two cases: +1. If the lock is a table lock, thus we have a table lock queue; +2. If the lock is a record lock and it is a wait lock. In this case + bit_no is calculated in this function by using + lock_rec_find_set_bit(). There is exactly one bit set in the bitmap + of a wait lock. */ +UNIV_INTERN +void +lock_queue_iterator_reset( +/*======================*/ + lock_queue_iterator_t* iter, /* out: iterator */ + const lock_t* lock, /* in: lock to start from */ + ulint bit_no);/* in: record number in the + heap */ + +/*********************************************************************** +Gets the previous lock in the lock queue, returns NULL if there are no +more locks (i.e. the current lock is the first one). The iterator is +receded (if not-NULL is returned). */ + +const lock_t* +lock_queue_iterator_get_prev( +/*=========================*/ + /* out: previous lock or NULL */ + lock_queue_iterator_t* iter); /* in/out: iterator */ + +#endif /* lock0iter_h */ diff --git a/storage/xtradb/include/lock0lock.h b/storage/xtradb/include/lock0lock.h new file mode 100644 index 00000000000..2deeb804737 --- /dev/null +++ b/storage/xtradb/include/lock0lock.h @@ -0,0 +1,838 @@ +/***************************************************************************** + +Copyright (c) 1996, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/****************************************************** +The transaction lock system + +Created 5/7/1996 Heikki Tuuri +*******************************************************/ + +#ifndef lock0lock_h +#define lock0lock_h + +#include "univ.i" +#include "buf0types.h" +#include "trx0types.h" +#include "rem0types.h" +#include "dict0types.h" +#include "que0types.h" +#include "lock0types.h" +#include "read0types.h" +#include "hash0hash.h" +#include "ut0vec.h" + +#ifdef UNIV_DEBUG +extern ibool lock_print_waits; +#endif /* UNIV_DEBUG */ +/* Buffer for storing information about the most recent deadlock error */ +extern FILE* lock_latest_err_file; + +/************************************************************************* +Gets the size of a lock struct. */ +UNIV_INTERN +ulint +lock_get_size(void); +/*===============*/ + /* out: size in bytes */ +/************************************************************************* +Creates the lock system at database start. */ +UNIV_INTERN +void +lock_sys_create( +/*============*/ + ulint n_cells); /* in: number of slots in lock hash table */ +/************************************************************************* +Checks if some transaction has an implicit x-lock on a record in a clustered +index. */ +UNIV_INLINE +trx_t* +lock_clust_rec_some_has_impl( +/*=========================*/ + /* out: transaction which has the x-lock, or + NULL */ + const rec_t* rec, /* in: user record */ + dict_index_t* index, /* in: clustered index */ + const ulint* offsets);/* in: rec_get_offsets(rec, index) */ +/************************************************************************* +Gets the heap_no of the smallest user record on a page. */ +UNIV_INLINE +ulint +lock_get_min_heap_no( +/*=================*/ + /* out: heap_no of smallest + user record, or + PAGE_HEAP_NO_SUPREMUM */ + const buf_block_t* block); /* in: buffer block */ +/***************************************************************** +Updates the lock table when we have reorganized a page. NOTE: we copy +also the locks set on the infimum of the page; the infimum may carry +locks if an update of a record is occurring on the page, and its locks +were temporarily stored on the infimum. */ +UNIV_INTERN +void +lock_move_reorganize_page( +/*======================*/ + const buf_block_t* block, /* in: old index page, now + reorganized */ + const buf_block_t* oblock);/* in: copy of the old, not + reorganized page */ +/***************************************************************** +Moves the explicit locks on user records to another page if a record +list end is moved to another page. */ +UNIV_INTERN +void +lock_move_rec_list_end( +/*===================*/ + const buf_block_t* new_block, /* in: index page to move to */ + const buf_block_t* block, /* in: index page */ + const rec_t* rec); /* in: record on page: this + is the first record moved */ +/***************************************************************** +Moves the explicit locks on user records to another page if a record +list start is moved to another page. */ +UNIV_INTERN +void +lock_move_rec_list_start( +/*=====================*/ + const buf_block_t* new_block, /* in: index page to move to */ + const buf_block_t* block, /* in: index page */ + const rec_t* rec, /* in: record on page: + this is the first + record NOT copied */ + const rec_t* old_end); /* in: old + previous-to-last + record on new_page + before the records + were copied */ +/***************************************************************** +Updates the lock table when a page is split to the right. */ +UNIV_INTERN +void +lock_update_split_right( +/*====================*/ + const buf_block_t* right_block, /* in: right page */ + const buf_block_t* left_block); /* in: left page */ +/***************************************************************** +Updates the lock table when a page is merged to the right. */ +UNIV_INTERN +void +lock_update_merge_right( +/*====================*/ + const buf_block_t* right_block, /* in: right page to + which merged */ + const rec_t* orig_succ, /* in: original + successor of infimum + on the right page + before merge */ + const buf_block_t* left_block); /* in: merged index + page which will be + discarded */ +/***************************************************************** +Updates the lock table when the root page is copied to another in +btr_root_raise_and_insert. Note that we leave lock structs on the +root page, even though they do not make sense on other than leaf +pages: the reason is that in a pessimistic update the infimum record +of the root page will act as a dummy carrier of the locks of the record +to be updated. */ +UNIV_INTERN +void +lock_update_root_raise( +/*===================*/ + const buf_block_t* block, /* in: index page to which copied */ + const buf_block_t* root); /* in: root page */ +/***************************************************************** +Updates the lock table when a page is copied to another and the original page +is removed from the chain of leaf pages, except if page is the root! */ +UNIV_INTERN +void +lock_update_copy_and_discard( +/*=========================*/ + const buf_block_t* new_block, /* in: index page to + which copied */ + const buf_block_t* block); /* in: index page; + NOT the root! */ +/***************************************************************** +Updates the lock table when a page is split to the left. */ +UNIV_INTERN +void +lock_update_split_left( +/*===================*/ + const buf_block_t* right_block, /* in: right page */ + const buf_block_t* left_block); /* in: left page */ +/***************************************************************** +Updates the lock table when a page is merged to the left. */ +UNIV_INTERN +void +lock_update_merge_left( +/*===================*/ + const buf_block_t* left_block, /* in: left page to + which merged */ + const rec_t* orig_pred, /* in: original predecessor + of supremum on the left page + before merge */ + const buf_block_t* right_block); /* in: merged index page + which will be discarded */ +/***************************************************************** +Resets the original locks on heir and replaces them with gap type locks +inherited from rec. */ +UNIV_INTERN +void +lock_rec_reset_and_inherit_gap_locks( +/*=================================*/ + const buf_block_t* heir_block, /* in: block containing the + record which inherits */ + const buf_block_t* block, /* in: block containing the + record from which inherited; + does NOT reset the locks on + this record */ + ulint heir_heap_no, /* in: heap_no of the + inheriting record */ + ulint heap_no); /* in: heap_no of the + donating record */ +/***************************************************************** +Updates the lock table when a page is discarded. */ +UNIV_INTERN +void +lock_update_discard( +/*================*/ + const buf_block_t* heir_block, /* in: index page + which will inherit the locks */ + ulint heir_heap_no, /* in: heap_no of the record + which will inherit the locks */ + const buf_block_t* block); /* in: index page + which will be discarded */ +/***************************************************************** +Updates the lock table when a new user record is inserted. */ +UNIV_INTERN +void +lock_update_insert( +/*===============*/ + const buf_block_t* block, /* in: buffer block containing rec */ + const rec_t* rec); /* in: the inserted record */ +/***************************************************************** +Updates the lock table when a record is removed. */ +UNIV_INTERN +void +lock_update_delete( +/*===============*/ + const buf_block_t* block, /* in: buffer block containing rec */ + const rec_t* rec); /* in: the record to be removed */ +/************************************************************************* +Stores on the page infimum record the explicit locks of another record. +This function is used to store the lock state of a record when it is +updated and the size of the record changes in the update. The record +is in such an update moved, perhaps to another page. The infimum record +acts as a dummy carrier record, taking care of lock releases while the +actual record is being moved. */ +UNIV_INTERN +void +lock_rec_store_on_page_infimum( +/*===========================*/ + const buf_block_t* block, /* in: buffer block containing rec */ + const rec_t* rec); /* in: record whose lock state + is stored on the infimum + record of the same page; lock + bits are reset on the + record */ +/************************************************************************* +Restores the state of explicit lock requests on a single record, where the +state was stored on the infimum of the page. */ +UNIV_INTERN +void +lock_rec_restore_from_page_infimum( +/*===============================*/ + const buf_block_t* block, /* in: buffer block containing rec */ + const rec_t* rec, /* in: record whose lock state + is restored */ + const buf_block_t* donator);/* in: page (rec is not + necessarily on this page) + whose infimum stored the lock + state; lock bits are reset on + the infimum */ +/************************************************************************* +Returns TRUE if there are explicit record locks on a page. */ +UNIV_INTERN +ibool +lock_rec_expl_exist_on_page( +/*========================*/ + /* out: TRUE if there are explicit record locks on + the page */ + ulint space, /* in: space id */ + ulint page_no);/* in: page number */ +/************************************************************************* +Checks if locks of other transactions prevent an immediate insert of +a record. If they do, first tests if the query thread should anyway +be suspended for some reason; if not, then puts the transaction and +the query thread to the lock wait state and inserts a waiting request +for a gap x-lock to the lock queue. */ +UNIV_INTERN +ulint +lock_rec_insert_check_and_lock( +/*===========================*/ + /* out: DB_SUCCESS, DB_LOCK_WAIT, + DB_DEADLOCK, or DB_QUE_THR_SUSPENDED */ + ulint flags, /* in: if BTR_NO_LOCKING_FLAG bit is + set, does nothing */ + rec_t* rec, /* in: record after which to insert */ + buf_block_t* block, /* in/out: buffer block of rec */ + dict_index_t* index, /* in: index */ + que_thr_t* thr, /* in: query thread */ + ibool* inherit);/* out: set to TRUE if the new + inserted record maybe should inherit + LOCK_GAP type locks from the successor + record */ +/************************************************************************* +Checks if locks of other transactions prevent an immediate modify (update, +delete mark, or delete unmark) of a clustered index record. If they do, +first tests if the query thread should anyway be suspended for some +reason; if not, then puts the transaction and the query thread to the +lock wait state and inserts a waiting request for a record x-lock to the +lock queue. */ +UNIV_INTERN +ulint +lock_clust_rec_modify_check_and_lock( +/*=================================*/ + /* out: DB_SUCCESS, + DB_LOCK_WAIT, DB_DEADLOCK, or + DB_QUE_THR_SUSPENDED */ + ulint flags, /* in: if BTR_NO_LOCKING_FLAG + bit is set, does nothing */ + const buf_block_t* block, /* in: buffer block of rec */ + const rec_t* rec, /* in: record which should be + modified */ + dict_index_t* index, /* in: clustered index */ + const ulint* offsets,/* in: rec_get_offsets(rec, index) */ + que_thr_t* thr); /* in: query thread */ +/************************************************************************* +Checks if locks of other transactions prevent an immediate modify +(delete mark or delete unmark) of a secondary index record. */ +UNIV_INTERN +ulint +lock_sec_rec_modify_check_and_lock( +/*===============================*/ + /* out: DB_SUCCESS, DB_LOCK_WAIT, + DB_DEADLOCK, or DB_QUE_THR_SUSPENDED */ + ulint flags, /* in: if BTR_NO_LOCKING_FLAG + bit is set, does nothing */ + buf_block_t* block, /* in/out: buffer block of rec */ + rec_t* rec, /* in: record which should be + modified; NOTE: as this is a secondary + index, we always have to modify the + clustered index record first: see the + comment below */ + dict_index_t* index, /* in: secondary index */ + que_thr_t* thr); /* in: query thread */ +/************************************************************************* +Like the counterpart for a clustered index below, but now we read a +secondary index record. */ +UNIV_INTERN +ulint +lock_sec_rec_read_check_and_lock( +/*=============================*/ + /* out: DB_SUCCESS, + DB_LOCK_WAIT, DB_DEADLOCK, or + DB_QUE_THR_SUSPENDED */ + ulint flags, /* in: if BTR_NO_LOCKING_FLAG + bit is set, does nothing */ + const buf_block_t* block, /* in: buffer block of rec */ + const rec_t* rec, /* in: user record or page + supremum record which should + be read or passed over by a + read cursor */ + dict_index_t* index, /* in: secondary index */ + const ulint* offsets,/* in: rec_get_offsets(rec, index) */ + enum lock_mode mode, /* in: mode of the lock which + the read cursor should set on + records: LOCK_S or LOCK_X; the + latter is possible in + SELECT FOR UPDATE */ + ulint gap_mode,/* in: LOCK_ORDINARY, LOCK_GAP, or + LOCK_REC_NOT_GAP */ + que_thr_t* thr); /* in: query thread */ +/************************************************************************* +Checks if locks of other transactions prevent an immediate read, or passing +over by a read cursor, of a clustered index record. If they do, first tests +if the query thread should anyway be suspended for some reason; if not, then +puts the transaction and the query thread to the lock wait state and inserts a +waiting request for a record lock to the lock queue. Sets the requested mode +lock on the record. */ +UNIV_INTERN +ulint +lock_clust_rec_read_check_and_lock( +/*===============================*/ + /* out: DB_SUCCESS, + DB_LOCK_WAIT, DB_DEADLOCK, or + DB_QUE_THR_SUSPENDED */ + ulint flags, /* in: if BTR_NO_LOCKING_FLAG + bit is set, does nothing */ + const buf_block_t* block, /* in: buffer block of rec */ + const rec_t* rec, /* in: user record or page + supremum record which should + be read or passed over by a + read cursor */ + dict_index_t* index, /* in: clustered index */ + const ulint* offsets,/* in: rec_get_offsets(rec, index) */ + enum lock_mode mode, /* in: mode of the lock which + the read cursor should set on + records: LOCK_S or LOCK_X; the + latter is possible in + SELECT FOR UPDATE */ + ulint gap_mode,/* in: LOCK_ORDINARY, LOCK_GAP, or + LOCK_REC_NOT_GAP */ + que_thr_t* thr); /* in: query thread */ +/************************************************************************* +Checks if locks of other transactions prevent an immediate read, or passing +over by a read cursor, of a clustered index record. If they do, first tests +if the query thread should anyway be suspended for some reason; if not, then +puts the transaction and the query thread to the lock wait state and inserts a +waiting request for a record lock to the lock queue. Sets the requested mode +lock on the record. This is an alternative version of +lock_clust_rec_read_check_and_lock() that does not require the parameter +"offsets". */ +UNIV_INTERN +ulint +lock_clust_rec_read_check_and_lock_alt( +/*===================================*/ + /* out: DB_SUCCESS, + DB_LOCK_WAIT, DB_DEADLOCK, or + DB_QUE_THR_SUSPENDED */ + ulint flags, /* in: if BTR_NO_LOCKING_FLAG + bit is set, does nothing */ + const buf_block_t* block, /* in: buffer block of rec */ + const rec_t* rec, /* in: user record or page + supremum record which should + be read or passed over by a + read cursor */ + dict_index_t* index, /* in: clustered index */ + enum lock_mode mode, /* in: mode of the lock which + the read cursor should set on + records: LOCK_S or LOCK_X; the + latter is possible in + SELECT FOR UPDATE */ + ulint gap_mode,/* in: LOCK_ORDINARY, LOCK_GAP, or + LOCK_REC_NOT_GAP */ + que_thr_t* thr); /* in: query thread */ +/************************************************************************* +Checks that a record is seen in a consistent read. */ +UNIV_INTERN +ibool +lock_clust_rec_cons_read_sees( +/*==========================*/ + /* out: TRUE if sees, or FALSE if an earlier + version of the record should be retrieved */ + const rec_t* rec, /* in: user record which should be read or + passed over by a read cursor */ + dict_index_t* index, /* in: clustered index */ + const ulint* offsets,/* in: rec_get_offsets(rec, index) */ + read_view_t* view); /* in: consistent read view */ +/************************************************************************* +Checks that a non-clustered index record is seen in a consistent read. */ +UNIV_INTERN +ulint +lock_sec_rec_cons_read_sees( +/*========================*/ + /* out: TRUE if certainly + sees, or FALSE if an earlier + version of the clustered index + record might be needed: NOTE + that a non-clustered index + page contains so little + information on its + modifications that also in the + case FALSE, the present + version of rec may be the + right, but we must check this + from the clustered index + record */ + const rec_t* rec, /* in: user record which + should be read or passed over + by a read cursor */ + const read_view_t* view); /* in: consistent read view */ +/************************************************************************* +Locks the specified database table in the mode given. If the lock cannot +be granted immediately, the query thread is put to wait. */ +UNIV_INTERN +ulint +lock_table( +/*=======*/ + /* out: DB_SUCCESS, DB_LOCK_WAIT, + DB_DEADLOCK, or DB_QUE_THR_SUSPENDED */ + ulint flags, /* in: if BTR_NO_LOCKING_FLAG bit is set, + does nothing */ + dict_table_t* table, /* in: database table in dictionary cache */ + enum lock_mode mode, /* in: lock mode */ + que_thr_t* thr); /* in: query thread */ +/************************************************************************* +Checks if there are any locks set on the table. */ +UNIV_INTERN +ibool +lock_is_on_table( +/*=============*/ + /* out: TRUE if there are lock(s) */ + dict_table_t* table); /* in: database table in dictionary cache */ +/***************************************************************** +Removes a granted record lock of a transaction from the queue and grants +locks to other transactions waiting in the queue if they now are entitled +to a lock. */ +UNIV_INTERN +void +lock_rec_unlock( +/*============*/ + trx_t* trx, /* in: transaction that has + set a record lock */ + const buf_block_t* block, /* in: buffer block containing rec */ + const rec_t* rec, /* in: record */ + enum lock_mode lock_mode);/* in: LOCK_S or LOCK_X */ +/************************************************************************* +Releases a table lock. +Releases possible other transactions waiting for this lock. */ +UNIV_INTERN +void +lock_table_unlock( +/*==============*/ + lock_t* lock); /* in: lock */ +/************************************************************************* +Releases transaction locks, and releases possible other transactions waiting +because of these locks. */ +UNIV_INTERN +void +lock_release_off_kernel( +/*====================*/ + trx_t* trx); /* in: transaction */ +/************************************************************************* +Cancels a waiting lock request and releases possible other transactions +waiting behind it. */ +UNIV_INTERN +void +lock_cancel_waiting_and_release( +/*============================*/ + lock_t* lock); /* in: waiting lock request */ + +/************************************************************************* +Removes locks on a table to be dropped or truncated. +If remove_also_table_sx_locks is TRUE then table-level S and X locks are +also removed in addition to other table-level and record-level locks. +No lock, that is going to be removed, is allowed to be a wait lock. */ +UNIV_INTERN +void +lock_remove_all_on_table( +/*=====================*/ + dict_table_t* table, /* in: table to be dropped + or truncated */ + ibool remove_also_table_sx_locks);/* in: also removes + table S and X locks */ + +/************************************************************************* +Calculates the fold value of a page file address: used in inserting or +searching for a lock in the hash table. */ +UNIV_INLINE +ulint +lock_rec_fold( +/*==========*/ + /* out: folded value */ + ulint space, /* in: space */ + ulint page_no)/* in: page number */ + __attribute__((const)); +/************************************************************************* +Calculates the hash value of a page file address: used in inserting or +searching for a lock in the hash table. */ +UNIV_INLINE +ulint +lock_rec_hash( +/*==========*/ + /* out: hashed value */ + ulint space, /* in: space */ + ulint page_no);/* in: page number */ + +/************************************************************************** +Looks for a set bit in a record lock bitmap. Returns ULINT_UNDEFINED, +if none found. */ +UNIV_INTERN +ulint +lock_rec_find_set_bit( +/*==================*/ + /* out: bit index == heap number of + the record, or ULINT_UNDEFINED if none found */ + const lock_t* lock); /* in: record lock with at least one bit set */ + +/************************************************************************* +Gets the source table of an ALTER TABLE transaction. The table must be +covered by an IX or IS table lock. */ +UNIV_INTERN +dict_table_t* +lock_get_src_table( +/*===============*/ + /* out: the source table of transaction, + if it is covered by an IX or IS table lock; + dest if there is no source table, and + NULL if the transaction is locking more than + two tables or an inconsistency is found */ + trx_t* trx, /* in: transaction */ + dict_table_t* dest, /* in: destination of ALTER TABLE */ + enum lock_mode* mode); /* out: lock mode of the source table */ +/************************************************************************* +Determine if the given table is exclusively "owned" by the given +transaction, i.e., transaction holds LOCK_IX and possibly LOCK_AUTO_INC +on the table. */ +UNIV_INTERN +ibool +lock_is_table_exclusive( +/*====================*/ + /* out: TRUE if table is only locked by trx, + with LOCK_IX, and possibly LOCK_AUTO_INC */ + dict_table_t* table, /* in: table */ + trx_t* trx); /* in: transaction */ +/************************************************************************* +Checks if a lock request lock1 has to wait for request lock2. */ +UNIV_INTERN +ibool +lock_has_to_wait( +/*=============*/ + /* out: TRUE if lock1 has to wait for + lock2 to be removed */ + const lock_t* lock1, /* in: waiting lock */ + const lock_t* lock2); /* in: another lock; NOTE that it is + assumed that this has a lock bit set + on the same record as in lock1 if the + locks are record locks */ +/************************************************************************* +Checks that a transaction id is sensible, i.e., not in the future. */ +UNIV_INTERN +ibool +lock_check_trx_id_sanity( +/*=====================*/ + /* out: TRUE if ok */ + dulint trx_id, /* in: trx id */ + const rec_t* rec, /* in: user record */ + dict_index_t* index, /* in: clustered index */ + const ulint* offsets, /* in: rec_get_offsets(rec, index) */ + ibool has_kernel_mutex);/* in: TRUE if the caller owns the + kernel mutex */ +/************************************************************************* +Prints info of a table lock. */ +UNIV_INTERN +void +lock_table_print( +/*=============*/ + FILE* file, /* in: file where to print */ + const lock_t* lock); /* in: table type lock */ +/************************************************************************* +Prints info of a record lock. */ +UNIV_INTERN +void +lock_rec_print( +/*===========*/ + FILE* file, /* in: file where to print */ + const lock_t* lock); /* in: record type lock */ +/************************************************************************* +Prints info of locks for all transactions. */ +UNIV_INTERN +void +lock_print_info_summary( +/*====================*/ + FILE* file); /* in: file where to print */ +/************************************************************************* +Prints info of locks for each transaction. */ +UNIV_INTERN +void +lock_print_info_all_transactions( +/*=============================*/ + FILE* file); /* in: file where to print */ +/************************************************************************* +Return approximate number or record locks (bits set in the bitmap) for +this transaction. Since delete-marked records may be removed, the +record count will not be precise. */ +UNIV_INTERN +ulint +lock_number_of_rows_locked( +/*=======================*/ + trx_t* trx); /* in: transaction */ +/*********************************************************************** +Release all the transaction's autoinc locks. */ +UNIV_INTERN +void +lock_release_autoinc_locks( +/*=======================*/ + trx_t* trx); /* in/out: transaction */ + +/*********************************************************************** +Gets the type of a lock. Non-inline version for using outside of the +lock module. */ +UNIV_INTERN +ulint +lock_get_type( +/*==========*/ + /* out: LOCK_TABLE or LOCK_REC */ + const lock_t* lock); /* in: lock */ + +/*********************************************************************** +Gets the id of the transaction owning a lock. */ +UNIV_INTERN +ullint +lock_get_trx_id( +/*============*/ + /* out: transaction id */ + const lock_t* lock); /* in: lock */ + +/*********************************************************************** +Gets the mode of a lock in a human readable string. +The string should not be free()'d or modified. */ + +const char* +lock_get_mode_str( +/*==============*/ + /* out: lock mode */ + const lock_t* lock); /* in: lock */ + +/*********************************************************************** +Gets the type of a lock in a human readable string. +The string should not be free()'d or modified. */ + +const char* +lock_get_type_str( +/*==============*/ + /* out: lock type */ + const lock_t* lock); /* in: lock */ + +/*********************************************************************** +Gets the id of the table on which the lock is. */ +UNIV_INTERN +ullint +lock_get_table_id( +/*==============*/ + /* out: id of the table */ + const lock_t* lock); /* in: lock */ + +/*********************************************************************** +Gets the name of the table on which the lock is. +The string should not be free()'d or modified. */ + +const char* +lock_get_table_name( +/*================*/ + /* out: name of the table */ + const lock_t* lock); /* in: lock */ + +/*********************************************************************** +For a record lock, gets the index on which the lock is. */ + +const dict_index_t* +lock_rec_get_index( +/*===============*/ + /* out: index */ + const lock_t* lock); /* in: lock */ + +/*********************************************************************** +For a record lock, gets the name of the index on which the lock is. +The string should not be free()'d or modified. */ + +const char* +lock_rec_get_index_name( +/*====================*/ + /* out: name of the index */ + const lock_t* lock); /* in: lock */ + +/*********************************************************************** +For a record lock, gets the tablespace number on which the lock is. */ +UNIV_INTERN +ulint +lock_rec_get_space_id( +/*==================*/ + /* out: tablespace number */ + const lock_t* lock); /* in: lock */ + +/*********************************************************************** +For a record lock, gets the page number on which the lock is. */ +UNIV_INTERN +ulint +lock_rec_get_page_no( +/*=================*/ + /* out: page number */ + const lock_t* lock); /* in: lock */ + +/* Lock modes and types */ +#define LOCK_MODE_MASK 0xFUL /* mask used to extract mode from the + type_mode field in a lock */ +/* Lock types */ +#define LOCK_TABLE 16 /* these type values should be so high that */ +#define LOCK_REC 32 /* they can be ORed to the lock mode */ +#define LOCK_TYPE_MASK 0xF0UL /* mask used to extract lock type from the + type_mode field in a lock */ +/* Waiting lock flag */ +#define LOCK_WAIT 256 /* this wait bit should be so high that + it can be ORed to the lock mode and type; + when this bit is set, it means that the + lock has not yet been granted, it is just + waiting for its turn in the wait queue */ +/* Precise modes */ +#define LOCK_ORDINARY 0 /* this flag denotes an ordinary next-key lock + in contrast to LOCK_GAP or LOCK_REC_NOT_GAP */ +#define LOCK_GAP 512 /* this gap bit should be so high that + it can be ORed to the other flags; + when this bit is set, it means that the + lock holds only on the gap before the record; + for instance, an x-lock on the gap does not + give permission to modify the record on which + the bit is set; locks of this type are created + when records are removed from the index chain + of records */ +#define LOCK_REC_NOT_GAP 1024 /* this bit means that the lock is only on + the index record and does NOT block inserts + to the gap before the index record; this is + used in the case when we retrieve a record + with a unique key, and is also used in + locking plain SELECTs (not part of UPDATE + or DELETE) when the user has set the READ + COMMITTED isolation level */ +#define LOCK_INSERT_INTENTION 2048 /* this bit is set when we place a waiting + gap type record lock request in order to let + an insert of an index record to wait until + there are no conflicting locks by other + transactions on the gap; note that this flag + remains set when the waiting lock is granted, + or if the lock is inherited to a neighboring + record */ + +/* When lock bits are reset, the following flags are available: */ +#define LOCK_RELEASE_WAIT 1 +#define LOCK_NOT_RELEASE_WAIT 2 + +/* Lock operation struct */ +typedef struct lock_op_struct lock_op_t; +struct lock_op_struct{ + dict_table_t* table; /* table to be locked */ + enum lock_mode mode; /* lock mode */ +}; + +#define LOCK_OP_START 1 +#define LOCK_OP_COMPLETE 2 + +/* The lock system struct */ +struct lock_sys_struct{ + hash_table_t* rec_hash; /* hash table of the record locks */ +}; + +/* The lock system */ +extern lock_sys_t* lock_sys; + + +#ifndef UNIV_NONINL +#include "lock0lock.ic" +#endif + +#endif diff --git a/storage/xtradb/include/lock0lock.ic b/storage/xtradb/include/lock0lock.ic new file mode 100644 index 00000000000..f978cc70678 --- /dev/null +++ b/storage/xtradb/include/lock0lock.ic @@ -0,0 +1,123 @@ +/***************************************************************************** + +Copyright (c) 1996, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/****************************************************** +The transaction lock system + +Created 5/7/1996 Heikki Tuuri +*******************************************************/ + +#include "sync0sync.h" +#include "srv0srv.h" +#include "dict0dict.h" +#include "row0row.h" +#include "trx0sys.h" +#include "trx0trx.h" +#include "buf0buf.h" +#include "page0page.h" +#include "page0cur.h" +#include "row0vers.h" +#include "que0que.h" +#include "btr0cur.h" +#include "read0read.h" +#include "log0recv.h" + +/************************************************************************* +Calculates the fold value of a page file address: used in inserting or +searching for a lock in the hash table. */ +UNIV_INLINE +ulint +lock_rec_fold( +/*==========*/ + /* out: folded value */ + ulint space, /* in: space */ + ulint page_no)/* in: page number */ +{ + return(ut_fold_ulint_pair(space, page_no)); +} + +/************************************************************************* +Calculates the hash value of a page file address: used in inserting or +searching for a lock in the hash table. */ +UNIV_INLINE +ulint +lock_rec_hash( +/*==========*/ + /* out: hashed value */ + ulint space, /* in: space */ + ulint page_no)/* in: page number */ +{ + return(hash_calc_hash(lock_rec_fold(space, page_no), + lock_sys->rec_hash)); +} + +/************************************************************************* +Checks if some transaction has an implicit x-lock on a record in a clustered +index. */ +UNIV_INLINE +trx_t* +lock_clust_rec_some_has_impl( +/*=========================*/ + /* out: transaction which has the x-lock, or + NULL */ + const rec_t* rec, /* in: user record */ + dict_index_t* index, /* in: clustered index */ + const ulint* offsets)/* in: rec_get_offsets(rec, index) */ +{ + dulint trx_id; + + ut_ad(mutex_own(&kernel_mutex)); + ut_ad(dict_index_is_clust(index)); + ut_ad(page_rec_is_user_rec(rec)); + + trx_id = row_get_rec_trx_id(rec, index, offsets); + + if (trx_is_active(trx_id)) { + /* The modifying or inserting transaction is active */ + + return(trx_get_on_id(trx_id)); + } + + return(NULL); +} + +/************************************************************************* +Gets the heap_no of the smallest user record on a page. */ +UNIV_INLINE +ulint +lock_get_min_heap_no( +/*=================*/ + /* out: heap_no of smallest + user record, or + PAGE_HEAP_NO_SUPREMUM */ + const buf_block_t* block) /* in: buffer block */ +{ + const page_t* page = block->frame; + + if (page_is_comp(page)) { + return(rec_get_heap_no_new( + page + + rec_get_next_offs(page + PAGE_NEW_INFIMUM, + TRUE))); + } else { + return(rec_get_heap_no_old( + page + + rec_get_next_offs(page + PAGE_OLD_INFIMUM, + FALSE))); + } +} diff --git a/storage/xtradb/include/lock0priv.h b/storage/xtradb/include/lock0priv.h new file mode 100644 index 00000000000..0a0d41e6aaa --- /dev/null +++ b/storage/xtradb/include/lock0priv.h @@ -0,0 +1,106 @@ +/***************************************************************************** + +Copyright (c) 2007, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/****************************************************** +Lock module internal structures and methods. + +Created July 12, 2007 Vasil Dimov +*******************************************************/ + +#ifndef lock0priv_h +#define lock0priv_h + +#ifndef LOCK_MODULE_IMPLEMENTATION +/* If you need to access members of the structures defined in this +file, please write appropriate functions that retrieve them and put +those functions in lock/ */ +#error Do not include lock0priv.h outside of the lock/ module +#endif + +#include "univ.i" +#include "dict0types.h" +#include "hash0hash.h" +#include "trx0types.h" +#include "ut0lst.h" + +/* A table lock */ +typedef struct lock_table_struct lock_table_t; +struct lock_table_struct { + dict_table_t* table; /* database table in dictionary + cache */ + UT_LIST_NODE_T(lock_t) + locks; /* list of locks on the same + table */ +}; + +/* Record lock for a page */ +typedef struct lock_rec_struct lock_rec_t; +struct lock_rec_struct { + ulint space; /* space id */ + ulint page_no; /* page number */ + ulint n_bits; /* number of bits in the lock + bitmap; NOTE: the lock bitmap is + placed immediately after the + lock struct */ +}; + +/* Lock struct */ +struct lock_struct { + trx_t* trx; /* transaction owning the + lock */ + UT_LIST_NODE_T(lock_t) + trx_locks; /* list of the locks of the + transaction */ + ulint type_mode; /* lock type, mode, LOCK_GAP or + LOCK_REC_NOT_GAP, + LOCK_INSERT_INTENTION, + wait flag, ORed */ + hash_node_t hash; /* hash chain node for a record + lock */ + dict_index_t* index; /* index for a record lock */ + union { + lock_table_t tab_lock;/* table lock */ + lock_rec_t rec_lock;/* record lock */ + } un_member; +}; + +/************************************************************************* +Gets the type of a lock. */ +UNIV_INLINE +ulint +lock_get_type_low( +/*==============*/ + /* out: LOCK_TABLE or LOCK_REC */ + const lock_t* lock); /* in: lock */ + +/************************************************************************* +Gets the previous record lock set on a record. */ + +const lock_t* +lock_rec_get_prev( +/*==============*/ + /* out: previous lock on the same + record, NULL if none exists */ + const lock_t* in_lock,/* in: record lock */ + ulint heap_no);/* in: heap number of the record */ + +#ifndef UNIV_NONINL +#include "lock0priv.ic" +#endif + +#endif /* lock0priv_h */ diff --git a/storage/xtradb/include/lock0priv.ic b/storage/xtradb/include/lock0priv.ic new file mode 100644 index 00000000000..ae633a4fc61 --- /dev/null +++ b/storage/xtradb/include/lock0priv.ic @@ -0,0 +1,48 @@ +/***************************************************************************** + +Copyright (c) 2007, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/****************************************************** +Lock module internal inline methods. + +Created July 16, 2007 Vasil Dimov +*******************************************************/ + +/* This file contains only methods which are used in +lock/lock0* files, other than lock/lock0lock.c. +I.e. lock/lock0lock.c contains more internal inline +methods but they are used only in that file. */ + +#ifndef LOCK_MODULE_IMPLEMENTATION +#error Do not include lock0priv.ic outside of the lock/ module +#endif + +/************************************************************************* +Gets the type of a lock. */ +UNIV_INLINE +ulint +lock_get_type_low( +/*==============*/ + /* out: LOCK_TABLE or LOCK_REC */ + const lock_t* lock) /* in: lock */ +{ + ut_ad(lock); + + return(lock->type_mode & LOCK_TYPE_MASK); +} + +/* vim: set filetype=c: */ diff --git a/storage/xtradb/include/lock0types.h b/storage/xtradb/include/lock0types.h new file mode 100644 index 00000000000..52631b56532 --- /dev/null +++ b/storage/xtradb/include/lock0types.h @@ -0,0 +1,44 @@ +/***************************************************************************** + +Copyright (c) 1996, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/****************************************************** +The transaction lock system global types + +Created 5/7/1996 Heikki Tuuri +*******************************************************/ + +#ifndef lock0types_h +#define lock0types_h + +#define lock_t ib_lock_t +typedef struct lock_struct lock_t; +typedef struct lock_sys_struct lock_sys_t; + +/* Basic lock modes */ +enum lock_mode { + LOCK_IS = 0, /* intention shared */ + LOCK_IX, /* intention exclusive */ + LOCK_S, /* shared */ + LOCK_X, /* exclusive */ + LOCK_AUTO_INC, /* locks the auto-inc counter of a table + in an exclusive mode */ + LOCK_NONE, /* this is used elsewhere to note consistent read */ + LOCK_NUM = LOCK_NONE/* number of lock modes */ +}; + +#endif diff --git a/storage/xtradb/include/log0log.h b/storage/xtradb/include/log0log.h new file mode 100644 index 00000000000..2aaf74b4190 --- /dev/null +++ b/storage/xtradb/include/log0log.h @@ -0,0 +1,900 @@ +/***************************************************************************** + +Copyright (c) 1995, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/****************************************************** +Database log + +Created 12/9/1995 Heikki Tuuri +*******************************************************/ + +#ifndef log0log_h +#define log0log_h + +#include "univ.i" +#include "ut0byte.h" +#include "sync0sync.h" +#include "sync0rw.h" + +typedef struct log_struct log_t; +typedef struct log_group_struct log_group_t; + +#ifdef UNIV_DEBUG +extern ibool log_do_write; +extern ibool log_debug_writes; +#else /* UNIV_DEBUG */ +# define log_do_write TRUE +#endif /* UNIV_DEBUG */ + +/* Wait modes for log_write_up_to */ +#define LOG_NO_WAIT 91 +#define LOG_WAIT_ONE_GROUP 92 +#define LOG_WAIT_ALL_GROUPS 93 +#define LOG_MAX_N_GROUPS 32 + +/******************************************************************** +Sets the global variable log_fsp_current_free_limit. Also makes a checkpoint, +so that we know that the limit has been written to a log checkpoint field +on disk. */ +UNIV_INTERN +void +log_fsp_current_free_limit_set_and_checkpoint( +/*==========================================*/ + ulint limit); /* in: limit to set */ +/*********************************************************************** +Calculates where in log files we find a specified lsn. */ +UNIV_INTERN +ulint +log_calc_where_lsn_is( +/*==================*/ + /* out: log file number */ + ib_int64_t* log_file_offset, /* out: offset in that file + (including the header) */ + ib_uint64_t first_header_lsn, /* in: first log file start + lsn */ + ib_uint64_t lsn, /* in: lsn whose position to + determine */ + ulint n_log_files, /* in: total number of log + files */ + ib_int64_t log_file_size); /* in: log file size + (including the header) */ +/**************************************************************** +Writes to the log the string given. The log must be released with +log_release. */ +UNIV_INLINE +ib_uint64_t +log_reserve_and_write_fast( +/*=======================*/ + /* out: end lsn of the log record, + zero if did not succeed */ + byte* str, /* in: string */ + ulint len, /* in: string length */ + ib_uint64_t* start_lsn,/* out: start lsn of the log record */ + ibool* success);/* out: TRUE if success */ +/*************************************************************************** +Releases the log mutex. */ +UNIV_INLINE +void +log_release(void); +/*=============*/ +/*************************************************************************** +Checks if there is need for a log buffer flush or a new checkpoint, and does +this if yes. Any database operation should call this when it has modified +more than about 4 pages. NOTE that this function may only be called when the +OS thread owns no synchronization objects except the dictionary mutex. */ +UNIV_INLINE +void +log_free_check(void); +/*================*/ +/**************************************************************** +Opens the log for log_write_low. The log must be closed with log_close and +released with log_release. */ +UNIV_INTERN +ib_uint64_t +log_reserve_and_open( +/*=================*/ + /* out: start lsn of the log record */ + ulint len); /* in: length of data to be catenated */ +/**************************************************************** +Writes to the log the string given. It is assumed that the caller holds the +log mutex. */ +UNIV_INTERN +void +log_write_low( +/*==========*/ + byte* str, /* in: string */ + ulint str_len); /* in: string length */ +/**************************************************************** +Closes the log. */ +UNIV_INTERN +ib_uint64_t +log_close(void); +/*===========*/ + /* out: lsn */ +/**************************************************************** +Gets the current lsn. */ +UNIV_INLINE +ib_uint64_t +log_get_lsn(void); +/*=============*/ + /* out: current lsn */ +/********************************************************** +Initializes the log. */ +UNIV_INTERN +void +log_init(void); +/*==========*/ +/********************************************************************** +Inits a log group to the log system. */ +UNIV_INTERN +void +log_group_init( +/*===========*/ + ulint id, /* in: group id */ + ulint n_files, /* in: number of log files */ + ulint file_size, /* in: log file size in bytes */ + ulint space_id, /* in: space id of the file space + which contains the log files of this + group */ + ulint archive_space_id); /* in: space id of the file space + which contains some archived log + files for this group; currently, only + for the first log group this is + used */ +/********************************************************** +Completes an i/o to a log file. */ +UNIV_INTERN +void +log_io_complete( +/*============*/ + log_group_t* group); /* in: log group */ +/********************************************************** +This function is called, e.g., when a transaction wants to commit. It checks +that the log has been written to the log file up to the last log entry written +by the transaction. If there is a flush running, it waits and checks if the +flush flushed enough. If not, starts a new flush. */ +UNIV_INTERN +void +log_write_up_to( +/*============*/ + ib_uint64_t lsn, /* in: log sequence number up to which + the log should be written, + IB_ULONGLONG_MAX if not specified */ + ulint wait, /* in: LOG_NO_WAIT, LOG_WAIT_ONE_GROUP, + or LOG_WAIT_ALL_GROUPS */ + ibool flush_to_disk); + /* in: TRUE if we want the written log + also to be flushed to disk */ +/******************************************************************** +Does a syncronous flush of the log buffer to disk. */ +UNIV_INTERN +void +log_buffer_flush_to_disk(void); +/*==========================*/ +/******************************************************************** +Flushes the log buffer. Forces it to disk depending on the value of +the configuration parameter innodb_flush_log_at_trx_commit. */ +UNIV_INTERN +void +log_buffer_flush_maybe_sync(void); +/*=============================*/ +/******************************************************************** +Advances the smallest lsn for which there are unflushed dirty blocks in the +buffer pool and also may make a new checkpoint. NOTE: this function may only +be called if the calling thread owns no synchronization objects! */ +UNIV_INTERN +ibool +log_preflush_pool_modified_pages( +/*=============================*/ + /* out: FALSE if there was a + flush batch of the same type + running, which means that we + could not start this flush + batch */ + ib_uint64_t new_oldest, /* in: try to advance + oldest_modified_lsn at least + to this lsn */ + ibool sync); /* in: TRUE if synchronous + operation is desired */ +/********************************************************** +Makes a checkpoint. Note that this function does not flush dirty +blocks from the buffer pool: it only checks what is lsn of the oldest +modification in the pool, and writes information about the lsn in +log files. Use log_make_checkpoint_at to flush also the pool. */ +UNIV_INTERN +ibool +log_checkpoint( +/*===========*/ + /* out: TRUE if success, FALSE if a checkpoint + write was already running */ + ibool sync, /* in: TRUE if synchronous operation is + desired */ + ibool write_always); /* in: the function normally checks if the + the new checkpoint would have a greater + lsn than the previous one: if not, then no + physical write is done; by setting this + parameter TRUE, a physical write will always be + made to log files */ +/******************************************************************** +Makes a checkpoint at a given lsn or later. */ +UNIV_INTERN +void +log_make_checkpoint_at( +/*===================*/ + ib_uint64_t lsn, /* in: make a checkpoint at this or a + later lsn, if IB_ULONGLONG_MAX, makes + a checkpoint at the latest lsn */ + ibool write_always); /* in: the function normally checks if + the the new checkpoint would have a + greater lsn than the previous one: if + not, then no physical write is done; + by setting this parameter TRUE, a + physical write will always be made to + log files */ +/******************************************************************** +Makes a checkpoint at the latest lsn and writes it to first page of each +data file in the database, so that we know that the file spaces contain +all modifications up to that lsn. This can only be called at database +shutdown. This function also writes all log in log files to the log archive. */ +UNIV_INTERN +void +logs_empty_and_mark_files_at_shutdown(void); +/*=======================================*/ +/********************************************************** +Reads a checkpoint info from a log group header to log_sys->checkpoint_buf. */ +UNIV_INTERN +void +log_group_read_checkpoint_info( +/*===========================*/ + log_group_t* group, /* in: log group */ + ulint field); /* in: LOG_CHECKPOINT_1 or LOG_CHECKPOINT_2 */ +/*********************************************************************** +Gets info from a checkpoint about a log group. */ +UNIV_INTERN +void +log_checkpoint_get_nth_group_info( +/*==============================*/ + byte* buf, /* in: buffer containing checkpoint info */ + ulint n, /* in: nth slot */ + ulint* file_no,/* out: archived file number */ + ulint* offset);/* out: archived file offset */ +/********************************************************** +Writes checkpoint info to groups. */ +UNIV_INTERN +void +log_groups_write_checkpoint_info(void); +/*==================================*/ +#ifdef UNIV_HOTBACKUP +/********************************************************** +Writes info to a buffer of a log group when log files are created in +backup restoration. */ +UNIV_INTERN +void +log_reset_first_header_and_checkpoint( +/*==================================*/ + byte* hdr_buf,/* in: buffer which will be written to the + start of the first log file */ + ib_uint64_t start); /* in: lsn of the start of the first log file; + we pretend that there is a checkpoint at + start + LOG_BLOCK_HDR_SIZE */ +#endif /* UNIV_HOTBACKUP */ +/************************************************************************ +Starts an archiving operation. */ +UNIV_INTERN +ibool +log_archive_do( +/*===========*/ + /* out: TRUE if succeed, FALSE if an archiving + operation was already running */ + ibool sync, /* in: TRUE if synchronous operation is desired */ + ulint* n_bytes);/* out: archive log buffer size, 0 if nothing to + archive */ +/******************************************************************** +Writes the log contents to the archive up to the lsn when this function was +called, and stops the archiving. When archiving is started again, the archived +log file numbers start from a number one higher, so that the archiving will +not write again to the archived log files which exist when this function +returns. */ +UNIV_INTERN +ulint +log_archive_stop(void); +/*==================*/ + /* out: DB_SUCCESS or DB_ERROR */ +/******************************************************************** +Starts again archiving which has been stopped. */ +UNIV_INTERN +ulint +log_archive_start(void); +/*===================*/ + /* out: DB_SUCCESS or DB_ERROR */ +/******************************************************************** +Stop archiving the log so that a gap may occur in the archived log files. */ +UNIV_INTERN +ulint +log_archive_noarchivelog(void); +/*==========================*/ + /* out: DB_SUCCESS or DB_ERROR */ +/******************************************************************** +Start archiving the log so that a gap may occur in the archived log files. */ +UNIV_INTERN +ulint +log_archive_archivelog(void); +/*========================*/ + /* out: DB_SUCCESS or DB_ERROR */ +/********************************************************** +Generates an archived log file name. */ +UNIV_INTERN +void +log_archived_file_name_gen( +/*=======================*/ + char* buf, /* in: buffer where to write */ + ulint id, /* in: group id */ + ulint file_no);/* in: file number */ +/************************************************************************ +Checks that there is enough free space in the log to start a new query step. +Flushes the log buffer or makes a new checkpoint if necessary. NOTE: this +function may only be called if the calling thread owns no synchronization +objects! */ +UNIV_INTERN +void +log_check_margins(void); +/*===================*/ +/********************************************************** +Reads a specified log segment to a buffer. */ +UNIV_INTERN +void +log_group_read_log_seg( +/*===================*/ + ulint type, /* in: LOG_ARCHIVE or LOG_RECOVER */ + byte* buf, /* in: buffer where to read */ + log_group_t* group, /* in: log group */ + ib_uint64_t start_lsn, /* in: read area start */ + ib_uint64_t end_lsn); /* in: read area end */ +/********************************************************** +Writes a buffer to a log file group. */ +UNIV_INTERN +void +log_group_write_buf( +/*================*/ + log_group_t* group, /* in: log group */ + byte* buf, /* in: buffer */ + ulint len, /* in: buffer len; must be divisible + by OS_FILE_LOG_BLOCK_SIZE */ + ib_uint64_t start_lsn, /* in: start lsn of the buffer; must + be divisible by + OS_FILE_LOG_BLOCK_SIZE */ + ulint new_data_offset);/* in: start offset of new data in + buf: this parameter is used to decide + if we have to write a new log file + header */ +/************************************************************ +Sets the field values in group to correspond to a given lsn. For this function +to work, the values must already be correctly initialized to correspond to +some lsn, for instance, a checkpoint lsn. */ +UNIV_INTERN +void +log_group_set_fields( +/*=================*/ + log_group_t* group, /* in: group */ + ib_uint64_t lsn); /* in: lsn for which the values should be + set */ +/********************************************************** +Calculates the data capacity of a log group, when the log file headers are not +included. */ +UNIV_INTERN +ulint +log_group_get_capacity( +/*===================*/ + /* out: capacity in bytes */ + log_group_t* group); /* in: log group */ +/**************************************************************** +Gets a log block flush bit. */ +UNIV_INLINE +ibool +log_block_get_flush_bit( +/*====================*/ + /* out: TRUE if this block was the first + to be written in a log flush */ + byte* log_block); /* in: log block */ +/**************************************************************** +Gets a log block number stored in the header. */ +UNIV_INLINE +ulint +log_block_get_hdr_no( +/*=================*/ + /* out: log block number stored in the block + header */ + byte* log_block); /* in: log block */ +/**************************************************************** +Gets a log block data length. */ +UNIV_INLINE +ulint +log_block_get_data_len( +/*===================*/ + /* out: log block data length measured as a + byte offset from the block start */ + byte* log_block); /* in: log block */ +/**************************************************************** +Sets the log block data length. */ +UNIV_INLINE +void +log_block_set_data_len( +/*===================*/ + byte* log_block, /* in: log block */ + ulint len); /* in: data length */ +/**************************************************************** +Calculates the checksum for a log block. */ +UNIV_INLINE +ulint +log_block_calc_checksum( +/*====================*/ + /* out: checksum */ + const byte* block); /* in: log block */ +/**************************************************************** +Gets a log block checksum field value. */ +UNIV_INLINE +ulint +log_block_get_checksum( +/*===================*/ + /* out: checksum */ + const byte* log_block); /* in: log block */ +/**************************************************************** +Sets a log block checksum field value. */ +UNIV_INLINE +void +log_block_set_checksum( +/*===================*/ + byte* log_block, /* in: log block */ + ulint checksum); /* in: checksum */ +/**************************************************************** +Gets a log block first mtr log record group offset. */ +UNIV_INLINE +ulint +log_block_get_first_rec_group( +/*==========================*/ + /* out: first mtr log record group byte offset + from the block start, 0 if none */ + byte* log_block); /* in: log block */ +/**************************************************************** +Sets the log block first mtr log record group offset. */ +UNIV_INLINE +void +log_block_set_first_rec_group( +/*==========================*/ + byte* log_block, /* in: log block */ + ulint offset); /* in: offset, 0 if none */ +/**************************************************************** +Gets a log block checkpoint number field (4 lowest bytes). */ +UNIV_INLINE +ulint +log_block_get_checkpoint_no( +/*========================*/ + /* out: checkpoint no (4 lowest bytes) */ + byte* log_block); /* in: log block */ +/**************************************************************** +Initializes a log block in the log buffer. */ +UNIV_INLINE +void +log_block_init( +/*===========*/ + byte* log_block, /* in: pointer to the log buffer */ + ib_uint64_t lsn); /* in: lsn within the log block */ +/**************************************************************** +Initializes a log block in the log buffer in the old, < 3.23.52 format, where +there was no checksum yet. */ +UNIV_INLINE +void +log_block_init_in_old_format( +/*=========================*/ + byte* log_block, /* in: pointer to the log buffer */ + ib_uint64_t lsn); /* in: lsn within the log block */ +/**************************************************************** +Converts a lsn to a log block number. */ +UNIV_INLINE +ulint +log_block_convert_lsn_to_no( +/*========================*/ + /* out: log block number, + it is > 0 and <= 1G */ + ib_uint64_t lsn); /* in: lsn of a byte within the block */ +/********************************************************** +Prints info of the log. */ +UNIV_INTERN +void +log_print( +/*======*/ + FILE* file); /* in: file where to print */ +/********************************************************** +Peeks the current lsn. */ +UNIV_INTERN +ibool +log_peek_lsn( +/*=========*/ + /* out: TRUE if success, FALSE if + could not get the log system mutex */ + ib_uint64_t* lsn); /* out: if returns TRUE, current lsn is here */ +/************************************************************************** +Refreshes the statistics used to print per-second averages. */ +UNIV_INTERN +void +log_refresh_stats(void); +/*===================*/ + +extern log_t* log_sys; + +/* Values used as flags */ +#define LOG_FLUSH 7652559 +#define LOG_CHECKPOINT 78656949 +#ifdef UNIV_LOG_ARCHIVE +# define LOG_ARCHIVE 11122331 +#endif /* UNIV_LOG_ARCHIVE */ +#define LOG_RECOVER 98887331 + +/* The counting of lsn's starts from this value: this must be non-zero */ +#define LOG_START_LSN ((ib_uint64_t) (16 * OS_FILE_LOG_BLOCK_SIZE)) + +#define LOG_BUFFER_SIZE (srv_log_buffer_size * UNIV_PAGE_SIZE) +#define LOG_ARCHIVE_BUF_SIZE (srv_log_buffer_size * UNIV_PAGE_SIZE / 4) + +/* Offsets of a log block header */ +#define LOG_BLOCK_HDR_NO 0 /* block number which must be > 0 and + is allowed to wrap around at 2G; the + highest bit is set to 1 if this is the + first log block in a log flush write + segment */ +#define LOG_BLOCK_FLUSH_BIT_MASK 0x80000000UL + /* mask used to get the highest bit in + the preceding field */ +#define LOG_BLOCK_HDR_DATA_LEN 4 /* number of bytes of log written to + this block */ +#define LOG_BLOCK_FIRST_REC_GROUP 6 /* offset of the first start of an + mtr log record group in this log block, + 0 if none; if the value is the same + as LOG_BLOCK_HDR_DATA_LEN, it means + that the first rec group has not yet + been catenated to this log block, but + if it will, it will start at this + offset; an archive recovery can + start parsing the log records starting + from this offset in this log block, + if value not 0 */ +#define LOG_BLOCK_CHECKPOINT_NO 8 /* 4 lower bytes of the value of + log_sys->next_checkpoint_no when the + log block was last written to: if the + block has not yet been written full, + this value is only updated before a + log buffer flush */ +#define LOG_BLOCK_HDR_SIZE 12 /* size of the log block header in + bytes */ + +/* Offsets of a log block trailer from the end of the block */ +#define LOG_BLOCK_CHECKSUM 4 /* 4 byte checksum of the log block + contents; in InnoDB versions + < 3.23.52 this did not contain the + checksum but the same value as + .._HDR_NO */ +#define LOG_BLOCK_TRL_SIZE 4 /* trailer size in bytes */ + +/* Offsets for a checkpoint field */ +#define LOG_CHECKPOINT_NO 0 +#define LOG_CHECKPOINT_LSN 8 +#define LOG_CHECKPOINT_OFFSET 16 +#define LOG_CHECKPOINT_LOG_BUF_SIZE 20 +#define LOG_CHECKPOINT_ARCHIVED_LSN 24 +#define LOG_CHECKPOINT_GROUP_ARRAY 32 + +/* For each value < LOG_MAX_N_GROUPS the following 8 bytes: */ + +#define LOG_CHECKPOINT_ARCHIVED_FILE_NO 0 +#define LOG_CHECKPOINT_ARCHIVED_OFFSET 4 + +#define LOG_CHECKPOINT_ARRAY_END (LOG_CHECKPOINT_GROUP_ARRAY\ + + LOG_MAX_N_GROUPS * 8) +#define LOG_CHECKPOINT_CHECKSUM_1 LOG_CHECKPOINT_ARRAY_END +#define LOG_CHECKPOINT_CHECKSUM_2 (4 + LOG_CHECKPOINT_ARRAY_END) +#define LOG_CHECKPOINT_FSP_FREE_LIMIT (8 + LOG_CHECKPOINT_ARRAY_END) + /* current fsp free limit in + tablespace 0, in units of one + megabyte; this information is only used + by ibbackup to decide if it can + truncate unused ends of + non-auto-extending data files in space + 0 */ +#define LOG_CHECKPOINT_FSP_MAGIC_N (12 + LOG_CHECKPOINT_ARRAY_END) + /* this magic number tells if the + checkpoint contains the above field: + the field was added to + InnoDB-3.23.50 */ +#define LOG_CHECKPOINT_SIZE (16 + LOG_CHECKPOINT_ARRAY_END) + +#define LOG_CHECKPOINT_FSP_MAGIC_N_VAL 1441231243 + +/* Offsets of a log file header */ +#define LOG_GROUP_ID 0 /* log group number */ +#define LOG_FILE_START_LSN 4 /* lsn of the start of data in this + log file */ +#define LOG_FILE_NO 12 /* 4-byte archived log file number; + this field is only defined in an + archived log file */ +#define LOG_FILE_WAS_CREATED_BY_HOT_BACKUP 16 + /* a 32-byte field which contains + the string 'ibbackup' and the + creation time if the log file was + created by ibbackup --restore; + when mysqld is first time started + on the restored database, it can + print helpful info for the user */ +#define LOG_FILE_ARCH_COMPLETED OS_FILE_LOG_BLOCK_SIZE + /* this 4-byte field is TRUE when + the writing of an archived log file + has been completed; this field is + only defined in an archived log file */ +#define LOG_FILE_END_LSN (OS_FILE_LOG_BLOCK_SIZE + 4) + /* lsn where the archived log file + at least extends: actually the + archived log file may extend to a + later lsn, as long as it is within the + same log block as this lsn; this field + is defined only when an archived log + file has been completely written */ +#define LOG_CHECKPOINT_1 OS_FILE_LOG_BLOCK_SIZE + /* first checkpoint field in the log + header; we write alternately to the + checkpoint fields when we make new + checkpoints; this field is only defined + in the first log file of a log group */ +#define LOG_CHECKPOINT_2 (3 * OS_FILE_LOG_BLOCK_SIZE) + /* second checkpoint field in the log + header */ +#define LOG_FILE_HDR_SIZE (4 * OS_FILE_LOG_BLOCK_SIZE) + +#define LOG_GROUP_OK 301 +#define LOG_GROUP_CORRUPTED 302 + +/* Log group consists of a number of log files, each of the same size; a log +group is implemented as a space in the sense of the module fil0fil. */ + +struct log_group_struct{ + /* The following fields are protected by log_sys->mutex */ + ulint id; /* log group id */ + ulint n_files; /* number of files in the group */ + ulint file_size; /* individual log file size in bytes, + including the log file header */ + ulint space_id; /* file space which implements the log + group */ + ulint state; /* LOG_GROUP_OK or + LOG_GROUP_CORRUPTED */ + ib_uint64_t lsn; /* lsn used to fix coordinates within + the log group */ + ulint lsn_offset; /* the offset of the above lsn */ + ulint n_pending_writes;/* number of currently pending flush + writes for this log group */ + byte** file_header_bufs;/* buffers for each file header in the + group */ + /*-----------------------------*/ + byte** archive_file_header_bufs;/* buffers for each file + header in the group */ + ulint archive_space_id;/* file space which implements the log + group archive */ + ulint archived_file_no;/* file number corresponding to + log_sys->archived_lsn */ + ulint archived_offset;/* file offset corresponding to + log_sys->archived_lsn, 0 if we have + not yet written to the archive file + number archived_file_no */ + ulint next_archived_file_no;/* during an archive write, + until the write is completed, we + store the next value for + archived_file_no here: the write + completion function then sets the new + value to ..._file_no */ + ulint next_archived_offset; /* like the preceding field */ + /*-----------------------------*/ + ib_uint64_t scanned_lsn; /* used only in recovery: recovery scan + succeeded up to this lsn in this log + group */ + byte* checkpoint_buf; /* checkpoint header is written from + this buffer to the group */ + UT_LIST_NODE_T(log_group_t) + log_groups; /* list of log groups */ +}; + +struct log_struct{ + byte pad[64]; /* padding to prevent other memory + update hotspots from residing on the + same memory cache line */ + ib_uint64_t lsn; /* log sequence number */ + ulint buf_free; /* first free offset within the log + buffer */ + mutex_t mutex; /* mutex protecting the log */ + byte* buf; /* log buffer */ + ulint buf_size; /* log buffer size in bytes */ + ulint max_buf_free; /* recommended maximum value of + buf_free, after which the buffer is + flushed */ + ulint old_buf_free; /* value of buf free when log was + last time opened; only in the debug + version */ + ib_uint64_t old_lsn; /* value of lsn when log was last time + opened; only in the debug version */ + ibool check_flush_or_checkpoint; + /* this is set to TRUE when there may + be need to flush the log buffer, or + preflush buffer pool pages, or make + a checkpoint; this MUST be TRUE when + lsn - last_checkpoint_lsn > + max_checkpoint_age; this flag is + peeked at by log_free_check(), which + does not reserve the log mutex */ + UT_LIST_BASE_NODE_T(log_group_t) + log_groups; /* log groups */ + + /* The fields involved in the log buffer flush */ + + ulint buf_next_to_write;/* first offset in the log buffer + where the byte content may not exist + written to file, e.g., the start + offset of a log record catenated + later; this is advanced when a flush + operation is completed to all the log + groups */ + ib_uint64_t written_to_some_lsn; + /* first log sequence number not yet + written to any log group; for this to + be advanced, it is enough that the + write i/o has been completed for any + one log group */ + ib_uint64_t written_to_all_lsn; + /* first log sequence number not yet + written to some log group; for this to + be advanced, it is enough that the + write i/o has been completed for all + log groups */ + ib_uint64_t write_lsn; /* end lsn for the current running + write */ + ulint write_end_offset;/* the data in buffer has been written + up to this offset when the current + write ends: this field will then + be copied to buf_next_to_write */ + ib_uint64_t current_flush_lsn;/* end lsn for the current running + write + flush operation */ + ib_uint64_t flushed_to_disk_lsn; + /* how far we have written the log + AND flushed to disk */ + ulint n_pending_writes;/* number of currently pending flushes + or writes */ + /* NOTE on the 'flush' in names of the fields below: starting from + 4.0.14, we separate the write of the log file and the actual fsync() + or other method to flush it to disk. The names below shhould really + be 'flush_or_write'! */ + os_event_t no_flush_event; /* this event is in the reset state + when a flush or a write is running; + a thread should wait for this without + owning the log mutex, but NOTE that + to set or reset this event, the + thread MUST own the log mutex! */ + ibool one_flushed; /* during a flush, this is first FALSE + and becomes TRUE when one log group + has been written or flushed */ + os_event_t one_flushed_event;/* this event is reset when the + flush or write has not yet completed + for any log group; e.g., this means + that a transaction has been committed + when this is set; a thread should wait + for this without owning the log mutex, + but NOTE that to set or reset this + event, the thread MUST own the log + mutex! */ + ulint n_log_ios; /* number of log i/os initiated thus + far */ + ulint n_log_ios_old; /* number of log i/o's at the + previous printout */ + time_t last_printout_time;/* when log_print was last time + called */ + + /* Fields involved in checkpoints */ + ulint log_group_capacity; /* capacity of the log group; if + the checkpoint age exceeds this, it is + a serious error because it is possible + we will then overwrite log and spoil + crash recovery */ + ulint max_modified_age_async; + /* when this recommended value for lsn + - buf_pool_get_oldest_modification() + is exceeded, we start an asynchronous + preflush of pool pages */ + ulint max_modified_age_sync; + /* when this recommended value for lsn + - buf_pool_get_oldest_modification() + is exceeded, we start a synchronous + preflush of pool pages */ + ulint adm_checkpoint_interval; + /* administrator-specified checkpoint + interval in terms of log growth in + bytes; the interval actually used by + the database can be smaller */ + ulint max_checkpoint_age_async; + /* when this checkpoint age is exceeded + we start an asynchronous writing of a + new checkpoint */ + ulint max_checkpoint_age; + /* this is the maximum allowed value + for lsn - last_checkpoint_lsn when a + new query step is started */ + ib_uint64_t next_checkpoint_no; + /* next checkpoint number */ + ib_uint64_t last_checkpoint_lsn; + /* latest checkpoint lsn */ + ib_uint64_t next_checkpoint_lsn; + /* next checkpoint lsn */ + ulint n_pending_checkpoint_writes; + /* number of currently pending + checkpoint writes */ + rw_lock_t checkpoint_lock;/* this latch is x-locked when a + checkpoint write is running; a thread + should wait for this without owning + the log mutex */ + byte* checkpoint_buf; /* checkpoint header is read to this + buffer */ +#ifdef UNIV_LOG_ARCHIVE + /* Fields involved in archiving */ + ulint archiving_state;/* LOG_ARCH_ON, LOG_ARCH_STOPPING + LOG_ARCH_STOPPED, LOG_ARCH_OFF */ + ib_uint64_t archived_lsn; /* archiving has advanced to this + lsn */ + ulint max_archived_lsn_age_async; + /* recommended maximum age of + archived_lsn, before we start + asynchronous copying to the archive */ + ulint max_archived_lsn_age; + /* maximum allowed age for + archived_lsn */ + ib_uint64_t next_archived_lsn;/* during an archive write, + until the write is completed, we + store the next value for + archived_lsn here: the write + completion function then sets the new + value to archived_lsn */ + ulint archiving_phase;/* LOG_ARCHIVE_READ or + LOG_ARCHIVE_WRITE */ + ulint n_pending_archive_ios; + /* number of currently pending reads + or writes in archiving */ + rw_lock_t archive_lock; /* this latch is x-locked when an + archive write is running; a thread + should wait for this without owning + the log mutex */ + ulint archive_buf_size;/* size of archive_buf */ + byte* archive_buf; /* log segment is written to the + archive from this buffer */ + os_event_t archiving_on; /* if archiving has been stopped, + a thread can wait for this event to + become signaled */ +#endif /* UNIV_LOG_ARCHIVE */ +}; + +#define LOG_ARCH_ON 71 +#define LOG_ARCH_STOPPING 72 +#define LOG_ARCH_STOPPING2 73 +#define LOG_ARCH_STOPPED 74 +#define LOG_ARCH_OFF 75 + +#ifndef UNIV_NONINL +#include "log0log.ic" +#endif + +#endif diff --git a/storage/xtradb/include/log0log.ic b/storage/xtradb/include/log0log.ic new file mode 100644 index 00000000000..85eebda4942 --- /dev/null +++ b/storage/xtradb/include/log0log.ic @@ -0,0 +1,407 @@ +/***************************************************************************** + +Copyright (c) 1995, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/****************************************************** +Database log + +Created 12/9/1995 Heikki Tuuri +*******************************************************/ + +#include "os0file.h" +#include "mach0data.h" +#include "mtr0mtr.h" + +/********************************************************** +Checks by parsing that the catenated log segment for a single mtr is +consistent. */ +UNIV_INTERN +ibool +log_check_log_recs( +/*===============*/ + byte* buf, /* in: pointer to the start of + the log segment in the + log_sys->buf log buffer */ + ulint len, /* in: segment length in bytes */ + ib_uint64_t buf_start_lsn); /* in: buffer start lsn */ + +/**************************************************************** +Gets a log block flush bit. */ +UNIV_INLINE +ibool +log_block_get_flush_bit( +/*====================*/ + /* out: TRUE if this block was the first + to be written in a log flush */ + byte* log_block) /* in: log block */ +{ + if (LOG_BLOCK_FLUSH_BIT_MASK + & mach_read_from_4(log_block + LOG_BLOCK_HDR_NO)) { + + return(TRUE); + } + + return(FALSE); +} + +/**************************************************************** +Sets the log block flush bit. */ +UNIV_INLINE +void +log_block_set_flush_bit( +/*====================*/ + byte* log_block, /* in: log block */ + ibool val) /* in: value to set */ +{ + ulint field; + + field = mach_read_from_4(log_block + LOG_BLOCK_HDR_NO); + + if (val) { + field = field | LOG_BLOCK_FLUSH_BIT_MASK; + } else { + field = field & ~LOG_BLOCK_FLUSH_BIT_MASK; + } + + mach_write_to_4(log_block + LOG_BLOCK_HDR_NO, field); +} + +/**************************************************************** +Gets a log block number stored in the header. */ +UNIV_INLINE +ulint +log_block_get_hdr_no( +/*=================*/ + /* out: log block number stored in the block + header */ + byte* log_block) /* in: log block */ +{ + return(~LOG_BLOCK_FLUSH_BIT_MASK + & mach_read_from_4(log_block + LOG_BLOCK_HDR_NO)); +} + +/**************************************************************** +Sets the log block number stored in the header; NOTE that this must be set +before the flush bit! */ +UNIV_INLINE +void +log_block_set_hdr_no( +/*=================*/ + byte* log_block, /* in: log block */ + ulint n) /* in: log block number: must be > 0 and + < LOG_BLOCK_FLUSH_BIT_MASK */ +{ + ut_ad(n > 0); + ut_ad(n < LOG_BLOCK_FLUSH_BIT_MASK); + + mach_write_to_4(log_block + LOG_BLOCK_HDR_NO, n); +} + +/**************************************************************** +Gets a log block data length. */ +UNIV_INLINE +ulint +log_block_get_data_len( +/*===================*/ + /* out: log block data length measured as a + byte offset from the block start */ + byte* log_block) /* in: log block */ +{ + return(mach_read_from_2(log_block + LOG_BLOCK_HDR_DATA_LEN)); +} + +/**************************************************************** +Sets the log block data length. */ +UNIV_INLINE +void +log_block_set_data_len( +/*===================*/ + byte* log_block, /* in: log block */ + ulint len) /* in: data length */ +{ + mach_write_to_2(log_block + LOG_BLOCK_HDR_DATA_LEN, len); +} + +/**************************************************************** +Gets a log block first mtr log record group offset. */ +UNIV_INLINE +ulint +log_block_get_first_rec_group( +/*==========================*/ + /* out: first mtr log record group byte offset + from the block start, 0 if none */ + byte* log_block) /* in: log block */ +{ + return(mach_read_from_2(log_block + LOG_BLOCK_FIRST_REC_GROUP)); +} + +/**************************************************************** +Sets the log block first mtr log record group offset. */ +UNIV_INLINE +void +log_block_set_first_rec_group( +/*==========================*/ + byte* log_block, /* in: log block */ + ulint offset) /* in: offset, 0 if none */ +{ + mach_write_to_2(log_block + LOG_BLOCK_FIRST_REC_GROUP, offset); +} + +/**************************************************************** +Gets a log block checkpoint number field (4 lowest bytes). */ +UNIV_INLINE +ulint +log_block_get_checkpoint_no( +/*========================*/ + /* out: checkpoint no (4 lowest bytes) */ + byte* log_block) /* in: log block */ +{ + return(mach_read_from_4(log_block + LOG_BLOCK_CHECKPOINT_NO)); +} + +/**************************************************************** +Sets a log block checkpoint number field (4 lowest bytes). */ +UNIV_INLINE +void +log_block_set_checkpoint_no( +/*========================*/ + byte* log_block, /* in: log block */ + ib_uint64_t no) /* in: checkpoint no */ +{ + mach_write_to_4(log_block + LOG_BLOCK_CHECKPOINT_NO, (ulint) no); +} + +/**************************************************************** +Converts a lsn to a log block number. */ +UNIV_INLINE +ulint +log_block_convert_lsn_to_no( +/*========================*/ + /* out: log block number, + it is > 0 and <= 1G */ + ib_uint64_t lsn) /* in: lsn of a byte within the block */ +{ + return(((ulint) (lsn / OS_FILE_LOG_BLOCK_SIZE) & 0x3FFFFFFFUL) + 1); +} + +/**************************************************************** +Calculates the checksum for a log block. */ +UNIV_INLINE +ulint +log_block_calc_checksum( +/*====================*/ + /* out: checksum */ + const byte* block) /* in: log block */ +{ + ulint sum; + ulint sh; + ulint i; + + sum = 1; + sh = 0; + + for (i = 0; i < OS_FILE_LOG_BLOCK_SIZE - LOG_BLOCK_TRL_SIZE; i++) { + ulint b = (ulint) block[i]; + sum &= 0x7FFFFFFFUL; + sum += b; + sum += b << sh; + sh++; + if (sh > 24) { + sh = 0; + } + } + + return(sum); +} + +/**************************************************************** +Gets a log block checksum field value. */ +UNIV_INLINE +ulint +log_block_get_checksum( +/*===================*/ + /* out: checksum */ + const byte* log_block) /* in: log block */ +{ + return(mach_read_from_4(log_block + OS_FILE_LOG_BLOCK_SIZE + - LOG_BLOCK_CHECKSUM)); +} + +/**************************************************************** +Sets a log block checksum field value. */ +UNIV_INLINE +void +log_block_set_checksum( +/*===================*/ + byte* log_block, /* in: log block */ + ulint checksum) /* in: checksum */ +{ + mach_write_to_4(log_block + OS_FILE_LOG_BLOCK_SIZE + - LOG_BLOCK_CHECKSUM, + checksum); +} + +/**************************************************************** +Initializes a log block in the log buffer. */ +UNIV_INLINE +void +log_block_init( +/*===========*/ + byte* log_block, /* in: pointer to the log buffer */ + ib_uint64_t lsn) /* in: lsn within the log block */ +{ + ulint no; + + ut_ad(mutex_own(&(log_sys->mutex))); + + no = log_block_convert_lsn_to_no(lsn); + + log_block_set_hdr_no(log_block, no); + + log_block_set_data_len(log_block, LOG_BLOCK_HDR_SIZE); + log_block_set_first_rec_group(log_block, 0); +} + +/**************************************************************** +Initializes a log block in the log buffer in the old format, where there +was no checksum yet. */ +UNIV_INLINE +void +log_block_init_in_old_format( +/*=========================*/ + byte* log_block, /* in: pointer to the log buffer */ + ib_uint64_t lsn) /* in: lsn within the log block */ +{ + ulint no; + + ut_ad(mutex_own(&(log_sys->mutex))); + + no = log_block_convert_lsn_to_no(lsn); + + log_block_set_hdr_no(log_block, no); + mach_write_to_4(log_block + OS_FILE_LOG_BLOCK_SIZE + - LOG_BLOCK_CHECKSUM, no); + log_block_set_data_len(log_block, LOG_BLOCK_HDR_SIZE); + log_block_set_first_rec_group(log_block, 0); +} + +/**************************************************************** +Writes to the log the string given. The log must be released with +log_release. */ +UNIV_INLINE +ib_uint64_t +log_reserve_and_write_fast( +/*=======================*/ + /* out: end lsn of the log record, + zero if did not succeed */ + byte* str, /* in: string */ + ulint len, /* in: string length */ + ib_uint64_t* start_lsn,/* out: start lsn of the log record */ + ibool* success)/* out: TRUE if success */ +{ + log_t* log = log_sys; + ulint data_len; + ib_uint64_t lsn; + + *success = TRUE; + + mutex_enter(&(log->mutex)); + + data_len = len + log->buf_free % OS_FILE_LOG_BLOCK_SIZE; + + if (data_len >= OS_FILE_LOG_BLOCK_SIZE - LOG_BLOCK_TRL_SIZE) { + + /* The string does not fit within the current log block + or the log block would become full */ + + *success = FALSE; + + mutex_exit(&(log->mutex)); + + return(0); + } + + *start_lsn = log->lsn; + + ut_memcpy(log->buf + log->buf_free, str, len); + + log_block_set_data_len((byte*) ut_align_down(log->buf + log->buf_free, + OS_FILE_LOG_BLOCK_SIZE), + data_len); +#ifdef UNIV_LOG_DEBUG + log->old_buf_free = log->buf_free; + log->old_lsn = log->lsn; +#endif + log->buf_free += len; + + ut_ad(log->buf_free <= log->buf_size); + + lsn = log->lsn += len; + +#ifdef UNIV_LOG_DEBUG + log_check_log_recs(log->buf + log->old_buf_free, + log->buf_free - log->old_buf_free, log->old_lsn); +#endif + return(lsn); +} + +/*************************************************************************** +Releases the log mutex. */ +UNIV_INLINE +void +log_release(void) +/*=============*/ +{ + mutex_exit(&(log_sys->mutex)); +} + +/**************************************************************** +Gets the current lsn. */ +UNIV_INLINE +ib_uint64_t +log_get_lsn(void) +/*=============*/ + /* out: current lsn */ +{ + ib_uint64_t lsn; + + mutex_enter(&(log_sys->mutex)); + + lsn = log_sys->lsn; + + mutex_exit(&(log_sys->mutex)); + + return(lsn); +} + +/*************************************************************************** +Checks if there is need for a log buffer flush or a new checkpoint, and does +this if yes. Any database operation should call this when it has modified +more than about 4 pages. NOTE that this function may only be called when the +OS thread owns no synchronization objects except the dictionary mutex. */ +UNIV_INLINE +void +log_free_check(void) +/*================*/ +{ + /* ut_ad(sync_thread_levels_empty()); */ + + if (log_sys->check_flush_or_checkpoint) { + + log_check_margins(); + } +} diff --git a/storage/xtradb/include/log0recv.h b/storage/xtradb/include/log0recv.h new file mode 100644 index 00000000000..e3fe9ed330a --- /dev/null +++ b/storage/xtradb/include/log0recv.h @@ -0,0 +1,392 @@ +/***************************************************************************** + +Copyright (c) 1997, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/****************************************************** +Recovery + +Created 9/20/1997 Heikki Tuuri +*******************************************************/ + +#ifndef log0recv_h +#define log0recv_h + +#include "univ.i" +#include "ut0byte.h" +#include "buf0types.h" +#include "hash0hash.h" +#include "log0log.h" + +#ifdef UNIV_HOTBACKUP +extern ibool recv_replay_file_ops; + +/*********************************************************************** +Reads the checkpoint info needed in hot backup. */ +UNIV_INTERN +ibool +recv_read_cp_info_for_backup( +/*=========================*/ + /* out: TRUE if success */ + byte* hdr, /* in: buffer containing the log group + header */ + ib_uint64_t* lsn, /* out: checkpoint lsn */ + ulint* offset, /* out: checkpoint offset in the log group */ + ulint* fsp_limit,/* out: fsp limit of space 0, + 1000000000 if the database is running + with < version 3.23.50 of InnoDB */ + ib_uint64_t* cp_no, /* out: checkpoint number */ + ib_uint64_t* first_header_lsn); + /* out: lsn of of the start of the + first log file */ +/*********************************************************************** +Scans the log segment and n_bytes_scanned is set to the length of valid +log scanned. */ +UNIV_INTERN +void +recv_scan_log_seg_for_backup( +/*=========================*/ + byte* buf, /* in: buffer containing log data */ + ulint buf_len, /* in: data length in that buffer */ + ib_uint64_t* scanned_lsn, /* in/out: lsn of buffer start, + we return scanned lsn */ + ulint* scanned_checkpoint_no, + /* in/out: 4 lowest bytes of the + highest scanned checkpoint number so + far */ + ulint* n_bytes_scanned);/* out: how much we were able to + scan, smaller than buf_len if log + data ended here */ +#endif /* UNIV_HOTBACKUP */ +/*********************************************************************** +Returns TRUE if recovery is currently running. */ +UNIV_INLINE +ibool +recv_recovery_is_on(void); +/*=====================*/ +/*********************************************************************** +Returns TRUE if recovery from backup is currently running. */ +UNIV_INLINE +ibool +recv_recovery_from_backup_is_on(void); +/*=================================*/ +/**************************************************************************** +Applies the hashed log records to the page, if the page lsn is less than the +lsn of a log record. This can be called when a buffer page has just been +read in, or also for a page already in the buffer pool. */ +UNIV_INTERN +void +recv_recover_page( +/*==============*/ + ibool recover_backup, + /* in: TRUE if we are recovering a backup + page: then we do not acquire any latches + since the page was read in outside the + buffer pool */ + ibool just_read_in, + /* in: TRUE if the i/o-handler calls this for + a freshly read page */ + buf_block_t* block); /* in: buffer block */ +/************************************************************ +Recovers from a checkpoint. When this function returns, the database is able +to start processing of new user transactions, but the function +recv_recovery_from_checkpoint_finish should be called later to complete +the recovery and free the resources used in it. */ +UNIV_INTERN +ulint +recv_recovery_from_checkpoint_start_func( +/*=====================================*/ + /* out: error code or DB_SUCCESS */ +#ifdef UNIV_LOG_ARCHIVE + ulint type, /* in: LOG_CHECKPOINT or LOG_ARCHIVE */ + ib_uint64_t limit_lsn, /* in: recover up to this lsn + if possible */ +#endif /* UNIV_LOG_ARCHIVE */ + ib_uint64_t min_flushed_lsn,/* in: min flushed lsn from + data files */ + ib_uint64_t max_flushed_lsn);/* in: max flushed lsn from + data files */ +#ifdef UNIV_LOG_ARCHIVE +# define recv_recovery_from_checkpoint_start(type,lim,min,max) \ + recv_recovery_from_checkpoint_start_func(type,lim,min,max) +#else /* UNIV_LOG_ARCHIVE */ +# define recv_recovery_from_checkpoint_start(type,lim,min,max) \ + recv_recovery_from_checkpoint_start_func(min,max) +#endif /* UNIV_LOG_ARCHIVE */ +/************************************************************ +Completes recovery from a checkpoint. */ +UNIV_INTERN +void +recv_recovery_from_checkpoint_finish(void); +/*======================================*/ +/*********************************************************** +Scans log from a buffer and stores new log data to the parsing buffer. Parses +and hashes the log records if new data found. */ +UNIV_INTERN +ibool +recv_scan_log_recs( +/*===============*/ + /* out: TRUE if limit_lsn has been + reached, or not able to scan any more + in this log group */ + ibool apply_automatically,/* in: TRUE if we want this + function to apply log records + automatically when the hash table + becomes full; in the hot backup tool + the tool does the applying, not this + function */ + ulint available_memory,/* in: we let the hash table of recs + to grow to this size, at the maximum */ + ibool store_to_hash, /* in: TRUE if the records should be + stored to the hash table; this is set + to FALSE if just debug checking is + needed */ + byte* buf, /* in: buffer containing a log segment + or garbage */ + ulint len, /* in: buffer length */ + ib_uint64_t start_lsn, /* in: buffer start lsn */ + ib_uint64_t* contiguous_lsn, /* in/out: it is known that all log + groups contain contiguous log data up + to this lsn */ + ib_uint64_t* group_scanned_lsn);/* out: scanning succeeded up to + this lsn */ +/********************************************************** +Resets the logs. The contents of log files will be lost! */ +UNIV_INTERN +void +recv_reset_logs( +/*============*/ + ib_uint64_t lsn, /* in: reset to this lsn + rounded up to be divisible by + OS_FILE_LOG_BLOCK_SIZE, after + which we add + LOG_BLOCK_HDR_SIZE */ +#ifdef UNIV_LOG_ARCHIVE + ulint arch_log_no, /* in: next archived log file number */ +#endif /* UNIV_LOG_ARCHIVE */ + ibool new_logs_created);/* in: TRUE if resetting logs + is done at the log creation; + FALSE if it is done after + archive recovery */ +#ifdef UNIV_HOTBACKUP +/********************************************************** +Creates new log files after a backup has been restored. */ +UNIV_INTERN +void +recv_reset_log_files_for_backup( +/*============================*/ + const char* log_dir, /* in: log file directory path */ + ulint n_log_files, /* in: number of log files */ + ulint log_file_size, /* in: log file size */ + ib_uint64_t lsn); /* in: new start lsn, must be + divisible by OS_FILE_LOG_BLOCK_SIZE */ +#endif /* UNIV_HOTBACKUP */ +/************************************************************ +Creates the recovery system. */ +UNIV_INTERN +void +recv_sys_create(void); +/*=================*/ +/************************************************************ +Inits the recovery system for a recovery operation. */ +UNIV_INTERN +void +recv_sys_init( +/*==========*/ + ibool recover_from_backup, /* in: TRUE if this is called + to recover from a hot backup */ + ulint available_memory); /* in: available memory in bytes */ +/*********************************************************************** +Empties the hash table of stored log records, applying them to appropriate +pages. */ +UNIV_INTERN +void +recv_apply_hashed_log_recs( +/*=======================*/ + ibool allow_ibuf); /* in: if TRUE, also ibuf operations are + allowed during the application; if FALSE, + no ibuf operations are allowed, and after + the application all file pages are flushed to + disk and invalidated in buffer pool: this + alternative means that no new log records + can be generated during the application */ +#ifdef UNIV_HOTBACKUP +/*********************************************************************** +Applies log records in the hash table to a backup. */ +UNIV_INTERN +void +recv_apply_log_recs_for_backup(void); +/*================================*/ +#endif +#ifdef UNIV_LOG_ARCHIVE +/************************************************************ +Recovers from archived log files, and also from log files, if they exist. */ +UNIV_INTERN +ulint +recv_recovery_from_archive_start( +/*=============================*/ + /* out: error code or DB_SUCCESS */ + ib_uint64_t min_flushed_lsn,/* in: min flushed lsn field from the + data files */ + ib_uint64_t limit_lsn, /* in: recover up to this lsn if + possible */ + ulint first_log_no); /* in: number of the first archived + log file to use in the recovery; the + file will be searched from + INNOBASE_LOG_ARCH_DIR specified in + server config file */ +/************************************************************ +Completes recovery from archive. */ +UNIV_INTERN +void +recv_recovery_from_archive_finish(void); +/*===================================*/ +#endif /* UNIV_LOG_ARCHIVE */ + +/* Block of log record data */ +typedef struct recv_data_struct recv_data_t; +struct recv_data_struct{ + recv_data_t* next; /* pointer to the next block or NULL */ + /* the log record data is stored physically + immediately after this struct, max amount + RECV_DATA_BLOCK_SIZE bytes of it */ +}; + +/* Stored log record struct */ +typedef struct recv_struct recv_t; +struct recv_struct{ + byte type; /* log record type */ + ulint len; /* log record body length in bytes */ + recv_data_t* data; /* chain of blocks containing the log record + body */ + ib_uint64_t start_lsn;/* start lsn of the log segment written by + the mtr which generated this log record: NOTE + that this is not necessarily the start lsn of + this log record */ + ib_uint64_t end_lsn;/* end lsn of the log segment written by + the mtr which generated this log record: NOTE + that this is not necessarily the end lsn of + this log record */ + UT_LIST_NODE_T(recv_t) + rec_list;/* list of log records for this page */ +}; + +/* Hashed page file address struct */ +typedef struct recv_addr_struct recv_addr_t; +struct recv_addr_struct{ + ulint state; /* RECV_NOT_PROCESSED, RECV_BEING_PROCESSED, + or RECV_PROCESSED */ + ulint space; /* space id */ + ulint page_no;/* page number */ + UT_LIST_BASE_NODE_T(recv_t) + rec_list;/* list of log records for this page */ + hash_node_t addr_hash; +}; + +/* Recovery system data structure */ +typedef struct recv_sys_struct recv_sys_t; +struct recv_sys_struct{ + mutex_t mutex; /* mutex protecting the fields apply_log_recs, + n_addrs, and the state field in each recv_addr + struct */ + ibool apply_log_recs; + /* this is TRUE when log rec application to + pages is allowed; this flag tells the + i/o-handler if it should do log record + application */ + ibool apply_batch_on; + /* this is TRUE when a log rec application + batch is running */ + ib_uint64_t lsn; /* log sequence number */ + ulint last_log_buf_size; + /* size of the log buffer when the database + last time wrote to the log */ + byte* last_block; + /* possible incomplete last recovered log + block */ + byte* last_block_buf_start; + /* the nonaligned start address of the + preceding buffer */ + byte* buf; /* buffer for parsing log records */ + ulint len; /* amount of data in buf */ + ib_uint64_t parse_start_lsn; + /* this is the lsn from which we were able to + start parsing log records and adding them to + the hash table; zero if a suitable + start point not found yet */ + ib_uint64_t scanned_lsn; + /* the log data has been scanned up to this + lsn */ + ulint scanned_checkpoint_no; + /* the log data has been scanned up to this + checkpoint number (lowest 4 bytes) */ + ulint recovered_offset; + /* start offset of non-parsed log records in + buf */ + ib_uint64_t recovered_lsn; + /* the log records have been parsed up to + this lsn */ + ib_uint64_t limit_lsn;/* recovery should be made at most up to this + lsn */ + ibool found_corrupt_log; + /* this is set to TRUE if we during log + scan find a corrupt log block, or a corrupt + log record, or there is a log parsing + buffer overflow */ +#ifdef UNIV_LOG_ARCHIVE + log_group_t* archive_group; + /* in archive recovery: the log group whose + archive is read */ +#endif /* !UNIV_LOG_ARCHIVE */ + mem_heap_t* heap; /* memory heap of log records and file + addresses*/ + hash_table_t* addr_hash;/* hash table of file addresses of pages */ + ulint n_addrs;/* number of not processed hashed file + addresses in the hash table */ +}; + +extern recv_sys_t* recv_sys; +extern ibool recv_recovery_on; +extern ibool recv_no_ibuf_operations; +extern ibool recv_needed_recovery; + +extern ibool recv_lsn_checks_on; +#ifdef UNIV_HOTBACKUP +extern ibool recv_is_making_a_backup; +#endif /* UNIV_HOTBACKUP */ +extern ulint recv_max_parsed_page_no; + +/* Size of the parsing buffer; it must accommodate RECV_SCAN_SIZE many +times! */ +#define RECV_PARSING_BUF_SIZE (2 * 1024 * 1024) + +/* Size of block reads when the log groups are scanned forward to do a +roll-forward */ +#define RECV_SCAN_SIZE (4 * UNIV_PAGE_SIZE) + +/* States of recv_addr_struct */ +#define RECV_NOT_PROCESSED 71 +#define RECV_BEING_READ 72 +#define RECV_BEING_PROCESSED 73 +#define RECV_PROCESSED 74 + +extern ulint recv_n_pool_free_frames; + +#ifndef UNIV_NONINL +#include "log0recv.ic" +#endif + +#endif diff --git a/storage/xtradb/include/log0recv.ic b/storage/xtradb/include/log0recv.ic new file mode 100644 index 00000000000..e114bede38f --- /dev/null +++ b/storage/xtradb/include/log0recv.ic @@ -0,0 +1,48 @@ +/***************************************************************************** + +Copyright (c) 1997, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/****************************************************** +Recovery + +Created 9/20/1997 Heikki Tuuri +*******************************************************/ + +#include "univ.i" + +extern ibool recv_recovery_from_backup_on; + +/*********************************************************************** +Returns TRUE if recovery is currently running. */ +UNIV_INLINE +ibool +recv_recovery_is_on(void) +/*=====================*/ +{ + return(UNIV_UNLIKELY(recv_recovery_on)); +} + +/*********************************************************************** +Returns TRUE if recovery from backup is currently running. */ +UNIV_INLINE +ibool +recv_recovery_from_backup_is_on(void) +/*=================================*/ +{ + return(recv_recovery_from_backup_on); +} + diff --git a/storage/xtradb/include/mach0data.h b/storage/xtradb/include/mach0data.h new file mode 100644 index 00000000000..78b48af0836 --- /dev/null +++ b/storage/xtradb/include/mach0data.h @@ -0,0 +1,398 @@ +/***************************************************************************** + +Copyright (c) 1995, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/********************************************************************** +Utilities for converting data from the database file +to the machine format. + +Created 11/28/1995 Heikki Tuuri +***********************************************************************/ + +#ifndef mach0data_h +#define mach0data_h + +#include "univ.i" +#include "ut0byte.h" + +/* The data and all fields are always stored in a database file +in the same format: ascii, big-endian, ... . +All data in the files MUST be accessed using the functions in this +module. */ + +/*********************************************************** +The following function is used to store data in one byte. */ +UNIV_INLINE +void +mach_write_to_1( +/*============*/ + byte* b, /* in: pointer to byte where to store */ + ulint n); /* in: ulint integer to be stored, >= 0, < 256 */ +/************************************************************ +The following function is used to fetch data from one byte. */ +UNIV_INLINE +ulint +mach_read_from_1( +/*=============*/ + /* out: ulint integer, >= 0, < 256 */ + const byte* b) /* in: pointer to byte */ + __attribute__((nonnull, pure)); +/*********************************************************** +The following function is used to store data in two consecutive +bytes. We store the most significant byte to the lower address. */ +UNIV_INLINE +void +mach_write_to_2( +/*============*/ + byte* b, /* in: pointer to two bytes where to store */ + ulint n); /* in: ulint integer to be stored, >= 0, < 64k */ +/************************************************************ +The following function is used to fetch data from two consecutive +bytes. The most significant byte is at the lowest address. */ +UNIV_INLINE +ulint +mach_read_from_2( +/*=============*/ + /* out: ulint integer, >= 0, < 64k */ + const byte* b) /* in: pointer to two bytes */ + __attribute__((nonnull, pure)); + +/************************************************************ +The following function is used to convert a 16-bit data item +to the canonical format, for fast bytewise equality test +against memory. */ +UNIV_INLINE +uint16 +mach_encode_2( +/*==========*/ + /* out: 16-bit integer in canonical format */ + ulint n) /* in: integer in machine-dependent format */ + __attribute__((const)); +/************************************************************ +The following function is used to convert a 16-bit data item +from the canonical format, for fast bytewise equality test +against memory. */ +UNIV_INLINE +ulint +mach_decode_2( +/*==========*/ + /* out: integer in machine-dependent format */ + uint16 n) /* in: 16-bit integer in canonical format */ + __attribute__((const)); +/*********************************************************** +The following function is used to store data in 3 consecutive +bytes. We store the most significant byte to the lowest address. */ +UNIV_INLINE +void +mach_write_to_3( +/*============*/ + byte* b, /* in: pointer to 3 bytes where to store */ + ulint n); /* in: ulint integer to be stored */ +/************************************************************ +The following function is used to fetch data from 3 consecutive +bytes. The most significant byte is at the lowest address. */ +UNIV_INLINE +ulint +mach_read_from_3( +/*=============*/ + /* out: ulint integer */ + const byte* b) /* in: pointer to 3 bytes */ + __attribute__((nonnull, pure)); +/*********************************************************** +The following function is used to store data in four consecutive +bytes. We store the most significant byte to the lowest address. */ +UNIV_INLINE +void +mach_write_to_4( +/*============*/ + byte* b, /* in: pointer to four bytes where to store */ + ulint n); /* in: ulint integer to be stored */ +/************************************************************ +The following function is used to fetch data from 4 consecutive +bytes. The most significant byte is at the lowest address. */ +UNIV_INLINE +ulint +mach_read_from_4( +/*=============*/ + /* out: ulint integer */ + const byte* b) /* in: pointer to four bytes */ + __attribute__((nonnull, pure)); +/************************************************************* +Writes a ulint in a compressed form (1..5 bytes). */ +UNIV_INLINE +ulint +mach_write_compressed( +/*==================*/ + /* out: stored size in bytes */ + byte* b, /* in: pointer to memory where to store */ + ulint n); /* in: ulint integer to be stored */ +/************************************************************* +Returns the size of an ulint when written in the compressed form. */ +UNIV_INLINE +ulint +mach_get_compressed_size( +/*=====================*/ + /* out: compressed size in bytes */ + ulint n) /* in: ulint integer to be stored */ + __attribute__((const)); +/************************************************************* +Reads a ulint in a compressed form. */ +UNIV_INLINE +ulint +mach_read_compressed( +/*=================*/ + /* out: read integer */ + const byte* b) /* in: pointer to memory from where to read */ + __attribute__((nonnull, pure)); +/*********************************************************** +The following function is used to store data in 6 consecutive +bytes. We store the most significant byte to the lowest address. */ +UNIV_INLINE +void +mach_write_to_6( +/*============*/ + byte* b, /* in: pointer to 6 bytes where to store */ + dulint n); /* in: dulint integer to be stored */ +/************************************************************ +The following function is used to fetch data from 6 consecutive +bytes. The most significant byte is at the lowest address. */ +UNIV_INLINE +dulint +mach_read_from_6( +/*=============*/ + /* out: dulint integer */ + const byte* b) /* in: pointer to 6 bytes */ + __attribute__((nonnull, pure)); +/*********************************************************** +The following function is used to store data in 7 consecutive +bytes. We store the most significant byte to the lowest address. */ +UNIV_INLINE +void +mach_write_to_7( +/*============*/ + byte* b, /* in: pointer to 7 bytes where to store */ + dulint n); /* in: dulint integer to be stored */ +/************************************************************ +The following function is used to fetch data from 7 consecutive +bytes. The most significant byte is at the lowest address. */ +UNIV_INLINE +dulint +mach_read_from_7( +/*=============*/ + /* out: dulint integer */ + const byte* b) /* in: pointer to 7 bytes */ + __attribute__((nonnull, pure)); +/*********************************************************** +The following function is used to store data in 8 consecutive +bytes. We store the most significant byte to the lowest address. */ +UNIV_INLINE +void +mach_write_to_8( +/*============*/ + byte* b, /* in: pointer to 8 bytes where to store */ + dulint n); /* in: dulint integer to be stored */ +/*********************************************************** +The following function is used to store data in 8 consecutive +bytes. We store the most significant byte to the lowest address. */ +UNIV_INLINE +void +mach_write_ull( +/*===========*/ + byte* b, /* in: pointer to 8 bytes where to store */ + ib_uint64_t n); /* in: 64-bit integer to be stored */ +/************************************************************ +The following function is used to fetch data from 8 consecutive +bytes. The most significant byte is at the lowest address. */ +UNIV_INLINE +dulint +mach_read_from_8( +/*=============*/ + /* out: dulint integer */ + const byte* b) /* in: pointer to 8 bytes */ + __attribute__((nonnull, pure)); +/************************************************************ +The following function is used to fetch data from 8 consecutive +bytes. The most significant byte is at the lowest address. */ +UNIV_INLINE +ib_uint64_t +mach_read_ull( +/*==========*/ + /* out: 64-bit integer */ + const byte* b) /* in: pointer to 8 bytes */ + __attribute__((nonnull, pure)); +/************************************************************* +Writes a dulint in a compressed form (5..9 bytes). */ +UNIV_INLINE +ulint +mach_dulint_write_compressed( +/*=========================*/ + /* out: size in bytes */ + byte* b, /* in: pointer to memory where to store */ + dulint n); /* in: dulint integer to be stored */ +/************************************************************* +Returns the size of a dulint when written in the compressed form. */ +UNIV_INLINE +ulint +mach_dulint_get_compressed_size( +/*============================*/ + /* out: compressed size in bytes */ + dulint n); /* in: dulint integer to be stored */ +/************************************************************* +Reads a dulint in a compressed form. */ +UNIV_INLINE +dulint +mach_dulint_read_compressed( +/*========================*/ + /* out: read dulint */ + const byte* b) /* in: pointer to memory from where to read */ + __attribute__((nonnull, pure)); +/************************************************************* +Writes a dulint in a compressed form (1..11 bytes). */ +UNIV_INLINE +ulint +mach_dulint_write_much_compressed( +/*==============================*/ + /* out: size in bytes */ + byte* b, /* in: pointer to memory where to store */ + dulint n); /* in: dulint integer to be stored */ +/************************************************************* +Returns the size of a dulint when written in the compressed form. */ +UNIV_INLINE +ulint +mach_dulint_get_much_compressed_size( +/*=================================*/ + /* out: compressed size in bytes */ + dulint n) /* in: dulint integer to be stored */ + __attribute__((const)); +/************************************************************* +Reads a dulint in a compressed form. */ +UNIV_INLINE +dulint +mach_dulint_read_much_compressed( +/*=============================*/ + /* out: read dulint */ + const byte* b) /* in: pointer to memory from where to read */ + __attribute__((nonnull, pure)); +/************************************************************* +Reads a ulint in a compressed form if the log record fully contains it. */ +UNIV_INTERN +byte* +mach_parse_compressed( +/*==================*/ + /* out: pointer to end of the stored field, NULL if + not complete */ + byte* ptr, /* in: pointer to buffer from where to read */ + byte* end_ptr,/* in: pointer to end of the buffer */ + ulint* val); /* out: read value */ +/************************************************************* +Reads a dulint in a compressed form if the log record fully contains it. */ +UNIV_INTERN +byte* +mach_dulint_parse_compressed( +/*=========================*/ + /* out: pointer to end of the stored field, NULL if + not complete */ + byte* ptr, /* in: pointer to buffer from where to read */ + byte* end_ptr,/* in: pointer to end of the buffer */ + dulint* val); /* out: read value */ +/************************************************************* +Reads a double. It is stored in a little-endian format. */ +UNIV_INLINE +double +mach_double_read( +/*=============*/ + /* out: double read */ + const byte* b) /* in: pointer to memory from where to read */ + __attribute__((nonnull, pure)); +/************************************************************* +Writes a double. It is stored in a little-endian format. */ +UNIV_INLINE +void +mach_double_write( +/*==============*/ + byte* b, /* in: pointer to memory where to write */ + double d); /* in: double */ +/************************************************************* +Reads a float. It is stored in a little-endian format. */ +UNIV_INLINE +float +mach_float_read( +/*============*/ + /* out: float read */ + const byte* b) /* in: pointer to memory from where to read */ + __attribute__((nonnull, pure)); +/************************************************************* +Writes a float. It is stored in a little-endian format. */ +UNIV_INLINE +void +mach_float_write( +/*=============*/ + byte* b, /* in: pointer to memory where to write */ + float d); /* in: float */ +/************************************************************* +Reads a ulint stored in the little-endian format. */ +UNIV_INLINE +ulint +mach_read_from_n_little_endian( +/*===========================*/ + /* out: unsigned long int */ + const byte* buf, /* in: from where to read */ + ulint buf_size) /* in: from how many bytes to read */ + __attribute__((nonnull, pure)); +/************************************************************* +Writes a ulint in the little-endian format. */ +UNIV_INLINE +void +mach_write_to_n_little_endian( +/*==========================*/ + byte* dest, /* in: where to write */ + ulint dest_size, /* in: into how many bytes to write */ + ulint n); /* in: unsigned long int to write */ +/************************************************************* +Reads a ulint stored in the little-endian format. */ +UNIV_INLINE +ulint +mach_read_from_2_little_endian( +/*===========================*/ + /* out: unsigned long int */ + const byte* buf) /* in: from where to read */ + __attribute__((nonnull, pure)); +/************************************************************* +Writes a ulint in the little-endian format. */ +UNIV_INLINE +void +mach_write_to_2_little_endian( +/*==========================*/ + byte* dest, /* in: where to write */ + ulint n); /* in: unsigned long int to write */ + +/************************************************************* +Convert integral type from storage byte order (big endian) to +host byte order. */ +UNIV_INLINE +ullint +mach_read_int_type( +/*===============*/ + /* out: integer value */ + const byte* src, /* in: where to read from */ + ulint len, /* in: length of src */ + ibool unsigned_type); /* in: signed or unsigned flag */ +#ifndef UNIV_NONINL +#include "mach0data.ic" +#endif + +#endif diff --git a/storage/xtradb/include/mach0data.ic b/storage/xtradb/include/mach0data.ic new file mode 100644 index 00000000000..5dda9aece2f --- /dev/null +++ b/storage/xtradb/include/mach0data.ic @@ -0,0 +1,784 @@ +/***************************************************************************** + +Copyright (c) 1995, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/********************************************************************** +Utilities for converting data from the database file +to the machine format. + +Created 11/28/1995 Heikki Tuuri +***********************************************************************/ + +#include "ut0mem.h" + +/*********************************************************** +The following function is used to store data in one byte. */ +UNIV_INLINE +void +mach_write_to_1( +/*============*/ + byte* b, /* in: pointer to byte where to store */ + ulint n) /* in: ulint integer to be stored, >= 0, < 256 */ +{ + ut_ad(b); + ut_ad(n <= 0xFFUL); + + b[0] = (byte)n; +} + +/************************************************************ +The following function is used to fetch data from one byte. */ +UNIV_INLINE +ulint +mach_read_from_1( +/*=============*/ + /* out: ulint integer, >= 0, < 256 */ + const byte* b) /* in: pointer to byte */ +{ + ut_ad(b); + return((ulint)(b[0])); +} + +/*********************************************************** +The following function is used to store data in two consecutive +bytes. We store the most significant byte to the lowest address. */ +UNIV_INLINE +void +mach_write_to_2( +/*============*/ + byte* b, /* in: pointer to two bytes where to store */ + ulint n) /* in: ulint integer to be stored */ +{ + ut_ad(b); + ut_ad(n <= 0xFFFFUL); + + b[0] = (byte)(n >> 8); + b[1] = (byte)(n); +} + +/************************************************************ +The following function is used to fetch data from 2 consecutive +bytes. The most significant byte is at the lowest address. */ +UNIV_INLINE +ulint +mach_read_from_2( +/*=============*/ + /* out: ulint integer */ + const byte* b) /* in: pointer to 2 bytes */ +{ + ut_ad(b); + return( ((ulint)(b[0]) << 8) + + (ulint)(b[1]) + ); +} + +/************************************************************ +The following function is used to convert a 16-bit data item +to the canonical format, for fast bytewise equality test +against memory. */ +UNIV_INLINE +uint16 +mach_encode_2( +/*==========*/ + /* out: 16-bit integer in canonical format */ + ulint n) /* in: integer in machine-dependent format */ +{ + uint16 ret; + ut_ad(2 == sizeof ret); + mach_write_to_2((byte*) &ret, n); + return(ret); +} +/************************************************************ +The following function is used to convert a 16-bit data item +from the canonical format, for fast bytewise equality test +against memory. */ +UNIV_INLINE +ulint +mach_decode_2( +/*==========*/ + /* out: integer in machine-dependent format */ + uint16 n) /* in: 16-bit integer in canonical format */ +{ + ut_ad(2 == sizeof n); + return(mach_read_from_2((const byte*) &n)); +} + +/*********************************************************** +The following function is used to store data in 3 consecutive +bytes. We store the most significant byte to the lowest address. */ +UNIV_INLINE +void +mach_write_to_3( +/*============*/ + byte* b, /* in: pointer to 3 bytes where to store */ + ulint n) /* in: ulint integer to be stored */ +{ + ut_ad(b); + ut_ad(n <= 0xFFFFFFUL); + + b[0] = (byte)(n >> 16); + b[1] = (byte)(n >> 8); + b[2] = (byte)(n); +} + +/************************************************************ +The following function is used to fetch data from 3 consecutive +bytes. The most significant byte is at the lowest address. */ +UNIV_INLINE +ulint +mach_read_from_3( +/*=============*/ + /* out: ulint integer */ + const byte* b) /* in: pointer to 3 bytes */ +{ + ut_ad(b); + return( ((ulint)(b[0]) << 16) + + ((ulint)(b[1]) << 8) + + (ulint)(b[2]) + ); +} + +/*********************************************************** +The following function is used to store data in four consecutive +bytes. We store the most significant byte to the lowest address. */ +UNIV_INLINE +void +mach_write_to_4( +/*============*/ + byte* b, /* in: pointer to four bytes where to store */ + ulint n) /* in: ulint integer to be stored */ +{ + ut_ad(b); + + b[0] = (byte)(n >> 24); + b[1] = (byte)(n >> 16); + b[2] = (byte)(n >> 8); + b[3] = (byte)n; +} + +/************************************************************ +The following function is used to fetch data from 4 consecutive +bytes. The most significant byte is at the lowest address. */ +UNIV_INLINE +ulint +mach_read_from_4( +/*=============*/ + /* out: ulint integer */ + const byte* b) /* in: pointer to four bytes */ +{ + ut_ad(b); + return( ((ulint)(b[0]) << 24) + + ((ulint)(b[1]) << 16) + + ((ulint)(b[2]) << 8) + + (ulint)(b[3]) + ); +} + +/************************************************************* +Writes a ulint in a compressed form where the first byte codes the +length of the stored ulint. We look at the most significant bits of +the byte. If the most significant bit is zero, it means 1-byte storage, +else if the 2nd bit is 0, it means 2-byte storage, else if 3rd is 0, +it means 3-byte storage, else if 4th is 0, it means 4-byte storage, +else the storage is 5-byte. */ +UNIV_INLINE +ulint +mach_write_compressed( +/*==================*/ + /* out: compressed size in bytes */ + byte* b, /* in: pointer to memory where to store */ + ulint n) /* in: ulint integer (< 2^32) to be stored */ +{ + ut_ad(b); + + if (n < 0x80UL) { + mach_write_to_1(b, n); + return(1); + } else if (n < 0x4000UL) { + mach_write_to_2(b, n | 0x8000UL); + return(2); + } else if (n < 0x200000UL) { + mach_write_to_3(b, n | 0xC00000UL); + return(3); + } else if (n < 0x10000000UL) { + mach_write_to_4(b, n | 0xE0000000UL); + return(4); + } else { + mach_write_to_1(b, 0xF0UL); + mach_write_to_4(b + 1, n); + return(5); + } +} + +/************************************************************* +Returns the size of a ulint when written in the compressed form. */ +UNIV_INLINE +ulint +mach_get_compressed_size( +/*=====================*/ + /* out: compressed size in bytes */ + ulint n) /* in: ulint integer (< 2^32) to be stored */ +{ + if (n < 0x80UL) { + return(1); + } else if (n < 0x4000UL) { + return(2); + } else if (n < 0x200000UL) { + return(3); + } else if (n < 0x10000000UL) { + return(4); + } else { + return(5); + } +} + +/************************************************************* +Reads a ulint in a compressed form. */ +UNIV_INLINE +ulint +mach_read_compressed( +/*=================*/ + /* out: read integer (< 2^32) */ + const byte* b) /* in: pointer to memory from where to read */ +{ + ulint flag; + + ut_ad(b); + + flag = mach_read_from_1(b); + + if (flag < 0x80UL) { + return(flag); + } else if (flag < 0xC0UL) { + return(mach_read_from_2(b) & 0x7FFFUL); + } else if (flag < 0xE0UL) { + return(mach_read_from_3(b) & 0x3FFFFFUL); + } else if (flag < 0xF0UL) { + return(mach_read_from_4(b) & 0x1FFFFFFFUL); + } else { + ut_ad(flag == 0xF0UL); + return(mach_read_from_4(b + 1)); + } +} + +/*********************************************************** +The following function is used to store data in 8 consecutive +bytes. We store the most significant byte to the lowest address. */ +UNIV_INLINE +void +mach_write_to_8( +/*============*/ + byte* b, /* in: pointer to 8 bytes where to store */ + dulint n) /* in: dulint integer to be stored */ +{ + ut_ad(b); + + mach_write_to_4(b, ut_dulint_get_high(n)); + mach_write_to_4(b + 4, ut_dulint_get_low(n)); +} + +/*********************************************************** +The following function is used to store data in 8 consecutive +bytes. We store the most significant byte to the lowest address. */ +UNIV_INLINE +void +mach_write_ull( +/*===========*/ + byte* b, /* in: pointer to 8 bytes where to store */ + ib_uint64_t n) /* in: 64-bit integer to be stored */ +{ + ut_ad(b); + + mach_write_to_4(b, (ulint) (n >> 32)); + mach_write_to_4(b + 4, (ulint) n); +} + +/************************************************************ +The following function is used to fetch data from 8 consecutive +bytes. The most significant byte is at the lowest address. */ +UNIV_INLINE +dulint +mach_read_from_8( +/*=============*/ + /* out: dulint integer */ + const byte* b) /* in: pointer to 8 bytes */ +{ + ulint high; + ulint low; + + ut_ad(b); + + high = mach_read_from_4(b); + low = mach_read_from_4(b + 4); + + return(ut_dulint_create(high, low)); +} + +/************************************************************ +The following function is used to fetch data from 8 consecutive +bytes. The most significant byte is at the lowest address. */ +UNIV_INLINE +ib_uint64_t +mach_read_ull( +/*==========*/ + /* out: 64-bit integer */ + const byte* b) /* in: pointer to 8 bytes */ +{ + ib_uint64_t ull; + + ull = ((ib_uint64_t) mach_read_from_4(b)) << 32; + ull |= (ib_uint64_t) mach_read_from_4(b + 4); + + return(ull); +} + +/*********************************************************** +The following function is used to store data in 7 consecutive +bytes. We store the most significant byte to the lowest address. */ +UNIV_INLINE +void +mach_write_to_7( +/*============*/ + byte* b, /* in: pointer to 7 bytes where to store */ + dulint n) /* in: dulint integer to be stored */ +{ + ut_ad(b); + + mach_write_to_3(b, ut_dulint_get_high(n)); + mach_write_to_4(b + 3, ut_dulint_get_low(n)); +} + +/************************************************************ +The following function is used to fetch data from 7 consecutive +bytes. The most significant byte is at the lowest address. */ +UNIV_INLINE +dulint +mach_read_from_7( +/*=============*/ + /* out: dulint integer */ + const byte* b) /* in: pointer to 7 bytes */ +{ + ulint high; + ulint low; + + ut_ad(b); + + high = mach_read_from_3(b); + low = mach_read_from_4(b + 3); + + return(ut_dulint_create(high, low)); +} + +/*********************************************************** +The following function is used to store data in 6 consecutive +bytes. We store the most significant byte to the lowest address. */ +UNIV_INLINE +void +mach_write_to_6( +/*============*/ + byte* b, /* in: pointer to 6 bytes where to store */ + dulint n) /* in: dulint integer to be stored */ +{ + ut_ad(b); + + mach_write_to_2(b, ut_dulint_get_high(n)); + mach_write_to_4(b + 2, ut_dulint_get_low(n)); +} + +/************************************************************ +The following function is used to fetch data from 6 consecutive +bytes. The most significant byte is at the lowest address. */ +UNIV_INLINE +dulint +mach_read_from_6( +/*=============*/ + /* out: dulint integer */ + const byte* b) /* in: pointer to 6 bytes */ +{ + ulint high; + ulint low; + + ut_ad(b); + + high = mach_read_from_2(b); + low = mach_read_from_4(b + 2); + + return(ut_dulint_create(high, low)); +} + +/************************************************************* +Writes a dulint in a compressed form (5..9 bytes). */ +UNIV_INLINE +ulint +mach_dulint_write_compressed( +/*=========================*/ + /* out: size in bytes */ + byte* b, /* in: pointer to memory where to store */ + dulint n) /* in: dulint integer to be stored */ +{ + ulint size; + + ut_ad(b); + + size = mach_write_compressed(b, ut_dulint_get_high(n)); + mach_write_to_4(b + size, ut_dulint_get_low(n)); + + return(size + 4); +} + +/************************************************************* +Returns the size of a dulint when written in the compressed form. */ +UNIV_INLINE +ulint +mach_dulint_get_compressed_size( +/*============================*/ + /* out: compressed size in bytes */ + dulint n) /* in: dulint integer to be stored */ +{ + return(4 + mach_get_compressed_size(ut_dulint_get_high(n))); +} + +/************************************************************* +Reads a dulint in a compressed form. */ +UNIV_INLINE +dulint +mach_dulint_read_compressed( +/*========================*/ + /* out: read dulint */ + const byte* b) /* in: pointer to memory from where to read */ +{ + ulint high; + ulint low; + ulint size; + + ut_ad(b); + + high = mach_read_compressed(b); + + size = mach_get_compressed_size(high); + + low = mach_read_from_4(b + size); + + return(ut_dulint_create(high, low)); +} + +/************************************************************* +Writes a dulint in a compressed form (1..11 bytes). */ +UNIV_INLINE +ulint +mach_dulint_write_much_compressed( +/*==============================*/ + /* out: size in bytes */ + byte* b, /* in: pointer to memory where to store */ + dulint n) /* in: dulint integer to be stored */ +{ + ulint size; + + ut_ad(b); + + if (ut_dulint_get_high(n) == 0) { + return(mach_write_compressed(b, ut_dulint_get_low(n))); + } + + *b = (byte)0xFF; + size = 1 + mach_write_compressed(b + 1, ut_dulint_get_high(n)); + + size += mach_write_compressed(b + size, ut_dulint_get_low(n)); + + return(size); +} + +/************************************************************* +Returns the size of a dulint when written in the compressed form. */ +UNIV_INLINE +ulint +mach_dulint_get_much_compressed_size( +/*=================================*/ + /* out: compressed size in bytes */ + dulint n) /* in: dulint integer to be stored */ +{ + if (0 == ut_dulint_get_high(n)) { + return(mach_get_compressed_size(ut_dulint_get_low(n))); + } + + return(1 + mach_get_compressed_size(ut_dulint_get_high(n)) + + mach_get_compressed_size(ut_dulint_get_low(n))); +} + +/************************************************************* +Reads a dulint in a compressed form. */ +UNIV_INLINE +dulint +mach_dulint_read_much_compressed( +/*=============================*/ + /* out: read dulint */ + const byte* b) /* in: pointer to memory from where to read */ +{ + ulint high; + ulint low; + ulint size; + + ut_ad(b); + + if (*b != (byte)0xFF) { + high = 0; + size = 0; + } else { + high = mach_read_compressed(b + 1); + + size = 1 + mach_get_compressed_size(high); + } + + low = mach_read_compressed(b + size); + + return(ut_dulint_create(high, low)); +} + +/************************************************************* +Reads a double. It is stored in a little-endian format. */ +UNIV_INLINE +double +mach_double_read( +/*=============*/ + /* out: double read */ + const byte* b) /* in: pointer to memory from where to read */ +{ + double d; + ulint i; + byte* ptr; + + ptr = (byte*)&d; + + for (i = 0; i < sizeof(double); i++) { +#ifdef WORDS_BIGENDIAN + ptr[sizeof(double) - i - 1] = b[i]; +#else + ptr[i] = b[i]; +#endif + } + + return(d); +} + +/************************************************************* +Writes a double. It is stored in a little-endian format. */ +UNIV_INLINE +void +mach_double_write( +/*==============*/ + byte* b, /* in: pointer to memory where to write */ + double d) /* in: double */ +{ + ulint i; + byte* ptr; + + ptr = (byte*)&d; + + for (i = 0; i < sizeof(double); i++) { +#ifdef WORDS_BIGENDIAN + b[i] = ptr[sizeof(double) - i - 1]; +#else + b[i] = ptr[i]; +#endif + } +} + +/************************************************************* +Reads a float. It is stored in a little-endian format. */ +UNIV_INLINE +float +mach_float_read( +/*============*/ + /* out: float read */ + const byte* b) /* in: pointer to memory from where to read */ +{ + float d; + ulint i; + byte* ptr; + + ptr = (byte*)&d; + + for (i = 0; i < sizeof(float); i++) { +#ifdef WORDS_BIGENDIAN + ptr[sizeof(float) - i - 1] = b[i]; +#else + ptr[i] = b[i]; +#endif + } + + return(d); +} + +/************************************************************* +Writes a float. It is stored in a little-endian format. */ +UNIV_INLINE +void +mach_float_write( +/*=============*/ + byte* b, /* in: pointer to memory where to write */ + float d) /* in: float */ +{ + ulint i; + byte* ptr; + + ptr = (byte*)&d; + + for (i = 0; i < sizeof(float); i++) { +#ifdef WORDS_BIGENDIAN + b[i] = ptr[sizeof(float) - i - 1]; +#else + b[i] = ptr[i]; +#endif + } +} + +/************************************************************* +Reads a ulint stored in the little-endian format. */ +UNIV_INLINE +ulint +mach_read_from_n_little_endian( +/*===========================*/ + /* out: unsigned long int */ + const byte* buf, /* in: from where to read */ + ulint buf_size) /* in: from how many bytes to read */ +{ + ulint n = 0; + const byte* ptr; + + ut_ad(buf_size <= sizeof(ulint)); + ut_ad(buf_size > 0); + + ptr = buf + buf_size; + + for (;;) { + ptr--; + + n = n << 8; + + n += (ulint)(*ptr); + + if (ptr == buf) { + break; + } + } + + return(n); +} + +/************************************************************* +Writes a ulint in the little-endian format. */ +UNIV_INLINE +void +mach_write_to_n_little_endian( +/*==========================*/ + byte* dest, /* in: where to write */ + ulint dest_size, /* in: into how many bytes to write */ + ulint n) /* in: unsigned long int to write */ +{ + byte* end; + + ut_ad(dest_size <= sizeof(ulint)); + ut_ad(dest_size > 0); + + end = dest + dest_size; + + for (;;) { + *dest = (byte)(n & 0xFF); + + n = n >> 8; + + dest++; + + if (dest == end) { + break; + } + } + + ut_ad(n == 0); +} + +/************************************************************* +Reads a ulint stored in the little-endian format. */ +UNIV_INLINE +ulint +mach_read_from_2_little_endian( +/*===========================*/ + /* out: unsigned long int */ + const byte* buf) /* in: from where to read */ +{ + return((ulint)(*buf) + ((ulint)(*(buf + 1))) * 256); +} + +/************************************************************* +Writes a ulint in the little-endian format. */ +UNIV_INLINE +void +mach_write_to_2_little_endian( +/*==========================*/ + byte* dest, /* in: where to write */ + ulint n) /* in: unsigned long int to write */ +{ + ut_ad(n < 256 * 256); + + *dest = (byte)(n & 0xFFUL); + + n = n >> 8; + dest++; + + *dest = (byte)(n & 0xFFUL); +} + +/************************************************************* +Convert integral type from storage byte order (big endian) to +host byte order. */ +UNIV_INLINE +ullint +mach_read_int_type( +/*===============*/ + /* out: integer value */ + const byte* src, /* in: where to read from */ + ulint len, /* in: length of src */ + ibool unsigned_type) /* in: signed or unsigned flag */ +{ + /* XXX this can be optimized on big-endian machines */ + + ullint ret; + uint i; + + if (unsigned_type || (src[0] & 0x80)) { + + ret = 0x0000000000000000ULL; + } else { + + ret = 0xFFFFFFFFFFFFFF00ULL; + } + + if (unsigned_type) { + + ret |= src[0]; + } else { + + ret |= src[0] ^ 0x80; + } + + for (i = 1; i < len; i++) { + ret <<= 8; + ret |= src[i]; + } + + return(ret); +} diff --git a/storage/xtradb/include/mem0dbg.h b/storage/xtradb/include/mem0dbg.h new file mode 100644 index 00000000000..0568a595d06 --- /dev/null +++ b/storage/xtradb/include/mem0dbg.h @@ -0,0 +1,142 @@ +/***************************************************************************** + +Copyright (c) 1994, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/****************************************************** +The memory management: the debug code. This is not a compilation module, +but is included in mem0mem.* ! + +Created 6/9/1994 Heikki Tuuri +*******************************************************/ + +/* In the debug version each allocated field is surrounded with +check fields whose sizes are given below */ + +#ifdef UNIV_MEM_DEBUG +#define MEM_FIELD_HEADER_SIZE ut_calc_align(2 * sizeof(ulint),\ + UNIV_MEM_ALIGNMENT) +#define MEM_FIELD_TRAILER_SIZE sizeof(ulint) +#else +#define MEM_FIELD_HEADER_SIZE 0 +#endif + + +/* Space needed when allocating for a user a field of +length N. The space is allocated only in multiples of +UNIV_MEM_ALIGNMENT. In the debug version there are also +check fields at the both ends of the field. */ +#ifdef UNIV_MEM_DEBUG +#define MEM_SPACE_NEEDED(N) ut_calc_align((N) + MEM_FIELD_HEADER_SIZE\ + + MEM_FIELD_TRAILER_SIZE, UNIV_MEM_ALIGNMENT) +#else +#define MEM_SPACE_NEEDED(N) ut_calc_align((N), UNIV_MEM_ALIGNMENT) +#endif + +#if defined UNIV_MEM_DEBUG || defined UNIV_DEBUG +/******************************************************************* +Checks a memory heap for consistency and prints the contents if requested. +Outputs the sum of sizes of buffers given to the user (only in +the debug version), the physical size of the heap and the number of +blocks in the heap. In case of error returns 0 as sizes and number +of blocks. */ +UNIV_INTERN +void +mem_heap_validate_or_print( +/*=======================*/ + mem_heap_t* heap, /* in: memory heap */ + byte* top, /* in: calculate and validate only until + this top pointer in the heap is reached, + if this pointer is NULL, ignored */ + ibool print, /* in: if TRUE, prints the contents + of the heap; works only in + the debug version */ + ibool* error, /* out: TRUE if error */ + ulint* us_size,/* out: allocated memory + (for the user) in the heap, + if a NULL pointer is passed as this + argument, it is ignored; in the + non-debug version this is always -1 */ + ulint* ph_size,/* out: physical size of the heap, + if a NULL pointer is passed as this + argument, it is ignored */ + ulint* n_blocks); /* out: number of blocks in the heap, + if a NULL pointer is passed as this + argument, it is ignored */ +/****************************************************************** +Validates the contents of a memory heap. */ +UNIV_INTERN +ibool +mem_heap_validate( +/*==============*/ + /* out: TRUE if ok */ + mem_heap_t* heap); /* in: memory heap */ +#endif /* UNIV_MEM_DEBUG || UNIV_DEBUG */ +#ifdef UNIV_DEBUG +/****************************************************************** +Checks that an object is a memory heap (or a block of it) */ +UNIV_INTERN +ibool +mem_heap_check( +/*===========*/ + /* out: TRUE if ok */ + mem_heap_t* heap); /* in: memory heap */ +#endif /* UNIV_DEBUG */ +#ifdef UNIV_MEM_DEBUG +/********************************************************************* +TRUE if no memory is currently allocated. */ +UNIV_INTERN +ibool +mem_all_freed(void); +/*===============*/ + /* out: TRUE if no heaps exist */ +/********************************************************************* +Validates the dynamic memory */ +UNIV_INTERN +ibool +mem_validate_no_assert(void); +/*=========================*/ + /* out: TRUE if error */ +/**************************************************************** +Validates the dynamic memory */ +UNIV_INTERN +ibool +mem_validate(void); +/*===============*/ + /* out: TRUE if ok */ +#endif /* UNIV_MEM_DEBUG */ +/**************************************************************** +Tries to find neigboring memory allocation blocks and dumps to stderr +the neighborhood of a given pointer. */ +UNIV_INTERN +void +mem_analyze_corruption( +/*===================*/ + void* ptr); /* in: pointer to place of possible corruption */ +/********************************************************************* +Prints information of dynamic memory usage and currently allocated memory +heaps or buffers. Can only be used in the debug version. */ +UNIV_INTERN +void +mem_print_info(void); +/*================*/ +/********************************************************************* +Prints information of dynamic memory usage and currently allocated memory +heaps or buffers since the last ..._print_info or..._print_new_info. */ +UNIV_INTERN +void +mem_print_new_info(void); +/*====================*/ diff --git a/storage/xtradb/include/mem0dbg.ic b/storage/xtradb/include/mem0dbg.ic new file mode 100644 index 00000000000..bf695fee785 --- /dev/null +++ b/storage/xtradb/include/mem0dbg.ic @@ -0,0 +1,109 @@ +/***************************************************************************** + +Copyright (c) 1994, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/************************************************************************ +The memory management: the debug code. This is not an independent +compilation module but is included in mem0mem.*. + +Created 6/8/1994 Heikki Tuuri +*************************************************************************/ + +#ifdef UNIV_MEM_DEBUG +extern mutex_t mem_hash_mutex; +extern ulint mem_current_allocated_memory; + +/********************************************************************** +Initializes an allocated memory field in the debug version. */ +UNIV_INTERN +void +mem_field_init( +/*===========*/ + byte* buf, /* in: memory field */ + ulint n); /* in: how many bytes the user requested */ +/********************************************************************** +Erases an allocated memory field in the debug version. */ +UNIV_INTERN +void +mem_field_erase( +/*============*/ + byte* buf, /* in: memory field */ + ulint n); /* in: how many bytes the user requested */ +/******************************************************************* +Initializes a buffer to a random combination of hex BA and BE. +Used to initialize allocated memory. */ +UNIV_INTERN +void +mem_init_buf( +/*=========*/ + byte* buf, /* in: pointer to buffer */ + ulint n); /* in: length of buffer */ +/******************************************************************* +Initializes a buffer to a random combination of hex DE and AD. +Used to erase freed memory.*/ +UNIV_INTERN +void +mem_erase_buf( +/*==========*/ + byte* buf, /* in: pointer to buffer */ + ulint n); /* in: length of buffer */ +/******************************************************************* +Inserts a created memory heap to the hash table of +current allocated memory heaps. +Initializes the hash table when first called. */ +UNIV_INTERN +void +mem_hash_insert( +/*============*/ + mem_heap_t* heap, /* in: the created heap */ + const char* file_name, /* in: file name of creation */ + ulint line); /* in: line where created */ +/******************************************************************* +Removes a memory heap (which is going to be freed by the caller) +from the list of live memory heaps. Returns the size of the heap +in terms of how much memory in bytes was allocated for the user of +the heap (not the total space occupied by the heap). +Also validates the heap. +NOTE: This function does not free the storage occupied by the +heap itself, only the node in the list of heaps. */ +UNIV_INTERN +void +mem_hash_remove( +/*============*/ + mem_heap_t* heap, /* in: the heap to be freed */ + const char* file_name, /* in: file name of freeing */ + ulint line); /* in: line where freed */ + + +void +mem_field_header_set_len(byte* field, ulint len); + +ulint +mem_field_header_get_len(byte* field); + +void +mem_field_header_set_check(byte* field, ulint check); + +ulint +mem_field_header_get_check(byte* field); + +void +mem_field_trailer_set_check(byte* field, ulint check); + +ulint +mem_field_trailer_get_check(byte* field); +#endif /* UNIV_MEM_DEBUG */ diff --git a/storage/xtradb/include/mem0mem.h b/storage/xtradb/include/mem0mem.h new file mode 100644 index 00000000000..c20e7815001 --- /dev/null +++ b/storage/xtradb/include/mem0mem.h @@ -0,0 +1,406 @@ +/***************************************************************************** + +Copyright (c) 1994, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/****************************************************** +The memory management + +Created 6/9/1994 Heikki Tuuri +*******************************************************/ + +#ifndef mem0mem_h +#define mem0mem_h + +#include "univ.i" +#include "ut0mem.h" +#include "ut0byte.h" +#include "ut0ut.h" +#include "ut0rnd.h" +#include "sync0sync.h" +#include "ut0lst.h" +#include "mach0data.h" + +/* -------------------- MEMORY HEAPS ----------------------------- */ + +/* The info structure stored at the beginning of a heap block */ +typedef struct mem_block_info_struct mem_block_info_t; + +/* A block of a memory heap consists of the info structure +followed by an area of memory */ +typedef mem_block_info_t mem_block_t; + +/* A memory heap is a nonempty linear list of memory blocks */ +typedef mem_block_t mem_heap_t; + +/* Types of allocation for memory heaps: DYNAMIC means allocation from the +dynamic memory pool of the C compiler, BUFFER means allocation from the +buffer pool; the latter method is used for very big heaps */ + +#define MEM_HEAP_DYNAMIC 0 /* the most common type */ +#define MEM_HEAP_BUFFER 1 +#define MEM_HEAP_BTR_SEARCH 2 /* this flag can optionally be + ORed to MEM_HEAP_BUFFER, in which + case heap->free_block is used in + some cases for memory allocations, + and if it's NULL, the memory + allocation functions can return + NULL. */ + +/* The following start size is used for the first block in the memory heap if +the size is not specified, i.e., 0 is given as the parameter in the call of +create. The standard size is the maximum (payload) size of the blocks used for +allocations of small buffers. */ + +#define MEM_BLOCK_START_SIZE 64 +#define MEM_BLOCK_STANDARD_SIZE \ + (UNIV_PAGE_SIZE >= 16384 ? 8000 : MEM_MAX_ALLOC_IN_BUF) + +/* If a memory heap is allowed to grow into the buffer pool, the following +is the maximum size for a single allocated buffer: */ +#define MEM_MAX_ALLOC_IN_BUF (UNIV_PAGE_SIZE - 200) + +/********************************************************************** +Initializes the memory system. */ +UNIV_INTERN +void +mem_init( +/*=====*/ + ulint size); /* in: common pool size in bytes */ +/****************************************************************** +Use this macro instead of the corresponding function! Macro for memory +heap creation. */ + +#define mem_heap_create(N) mem_heap_create_func(\ + (N), MEM_HEAP_DYNAMIC, __FILE__, __LINE__) +/****************************************************************** +Use this macro instead of the corresponding function! Macro for memory +heap creation. */ + +#define mem_heap_create_in_buffer(N) mem_heap_create_func(\ + (N), MEM_HEAP_BUFFER, __FILE__, __LINE__) +/****************************************************************** +Use this macro instead of the corresponding function! Macro for memory +heap creation. */ + +#define mem_heap_create_in_btr_search(N) mem_heap_create_func(\ + (N), MEM_HEAP_BTR_SEARCH | MEM_HEAP_BUFFER,\ + __FILE__, __LINE__) + +/****************************************************************** +Use this macro instead of the corresponding function! Macro for memory +heap freeing. */ + +#define mem_heap_free(heap) mem_heap_free_func(\ + (heap), __FILE__, __LINE__) +/********************************************************************* +NOTE: Use the corresponding macros instead of this function. Creates a +memory heap. For debugging purposes, takes also the file name and line as +arguments. */ +UNIV_INLINE +mem_heap_t* +mem_heap_create_func( +/*=================*/ + /* out, own: memory heap, NULL if + did not succeed (only possible for + MEM_HEAP_BTR_SEARCH type heaps)*/ + ulint n, /* in: desired start block size, + this means that a single user buffer + of size n will fit in the block, + 0 creates a default size block */ + ulint type, /* in: heap type */ + const char* file_name, /* in: file name where created */ + ulint line); /* in: line where created */ +/********************************************************************* +NOTE: Use the corresponding macro instead of this function. Frees the space +occupied by a memory heap. In the debug version erases the heap memory +blocks. */ +UNIV_INLINE +void +mem_heap_free_func( +/*===============*/ + mem_heap_t* heap, /* in, own: heap to be freed */ + const char* file_name, /* in: file name where freed */ + ulint line); /* in: line where freed */ +/******************************************************************* +Allocates and zero-fills n bytes of memory from a memory heap. */ +UNIV_INLINE +void* +mem_heap_zalloc( +/*============*/ + /* out: allocated, zero-filled storage */ + mem_heap_t* heap, /* in: memory heap */ + ulint n); /* in: number of bytes; if the heap is allowed + to grow into the buffer pool, this must be + <= MEM_MAX_ALLOC_IN_BUF */ +/******************************************************************* +Allocates n bytes of memory from a memory heap. */ +UNIV_INLINE +void* +mem_heap_alloc( +/*===========*/ + /* out: allocated storage, NULL if did not + succeed (only possible for + MEM_HEAP_BTR_SEARCH type heaps) */ + mem_heap_t* heap, /* in: memory heap */ + ulint n); /* in: number of bytes; if the heap is allowed + to grow into the buffer pool, this must be + <= MEM_MAX_ALLOC_IN_BUF */ +/********************************************************************* +Returns a pointer to the heap top. */ +UNIV_INLINE +byte* +mem_heap_get_heap_top( +/*==================*/ + /* out: pointer to the heap top */ + mem_heap_t* heap); /* in: memory heap */ +/********************************************************************* +Frees the space in a memory heap exceeding the pointer given. The +pointer must have been acquired from mem_heap_get_heap_top. The first +memory block of the heap is not freed. */ +UNIV_INLINE +void +mem_heap_free_heap_top( +/*===================*/ + mem_heap_t* heap, /* in: heap from which to free */ + byte* old_top);/* in: pointer to old top of heap */ +/********************************************************************* +Empties a memory heap. The first memory block of the heap is not freed. */ +UNIV_INLINE +void +mem_heap_empty( +/*===========*/ + mem_heap_t* heap); /* in: heap to empty */ +/********************************************************************* +Returns a pointer to the topmost element in a memory heap. +The size of the element must be given. */ +UNIV_INLINE +void* +mem_heap_get_top( +/*=============*/ + /* out: pointer to the topmost element */ + mem_heap_t* heap, /* in: memory heap */ + ulint n); /* in: size of the topmost element */ +/********************************************************************* +Frees the topmost element in a memory heap. +The size of the element must be given. */ +UNIV_INLINE +void +mem_heap_free_top( +/*==============*/ + mem_heap_t* heap, /* in: memory heap */ + ulint n); /* in: size of the topmost element */ +/********************************************************************* +Returns the space in bytes occupied by a memory heap. */ +UNIV_INLINE +ulint +mem_heap_get_size( +/*==============*/ + mem_heap_t* heap); /* in: heap */ +/****************************************************************** +Use this macro instead of the corresponding function! +Macro for memory buffer allocation */ + +#define mem_zalloc(N) memset(mem_alloc(N), 0, (N)); + +#define mem_alloc(N) mem_alloc_func((N), NULL, __FILE__, __LINE__) +#define mem_alloc2(N,S) mem_alloc_func((N), (S), __FILE__, __LINE__) +/******************************************************************* +NOTE: Use the corresponding macro instead of this function. +Allocates a single buffer of memory from the dynamic memory of +the C compiler. Is like malloc of C. The buffer must be freed +with mem_free. */ +UNIV_INLINE +void* +mem_alloc_func( +/*===========*/ + /* out, own: free storage */ + ulint n, /* in: requested size in bytes */ + ulint* size, /* out: allocated size in bytes, + or NULL */ + const char* file_name, /* in: file name where created */ + ulint line); /* in: line where created */ + +/****************************************************************** +Use this macro instead of the corresponding function! +Macro for memory buffer freeing */ + +#define mem_free(PTR) mem_free_func((PTR), __FILE__, __LINE__) +/******************************************************************* +NOTE: Use the corresponding macro instead of this function. +Frees a single buffer of storage from +the dynamic memory of C compiler. Similar to free of C. */ +UNIV_INLINE +void +mem_free_func( +/*==========*/ + void* ptr, /* in, own: buffer to be freed */ + const char* file_name, /* in: file name where created */ + ulint line /* in: line where created */ +); + +/************************************************************************** +Duplicates a NUL-terminated string. */ +UNIV_INLINE +char* +mem_strdup( +/*=======*/ + /* out, own: a copy of the string, + must be deallocated with mem_free */ + const char* str); /* in: string to be copied */ +/************************************************************************** +Makes a NUL-terminated copy of a nonterminated string. */ +UNIV_INLINE +char* +mem_strdupl( +/*========*/ + /* out, own: a copy of the string, + must be deallocated with mem_free */ + const char* str, /* in: string to be copied */ + ulint len); /* in: length of str, in bytes */ + +/************************************************************************** +Duplicates a NUL-terminated string, allocated from a memory heap. */ +UNIV_INTERN +char* +mem_heap_strdup( +/*============*/ + /* out, own: a copy of the string */ + mem_heap_t* heap, /* in: memory heap where string is allocated */ + const char* str); /* in: string to be copied */ +/************************************************************************** +Makes a NUL-terminated copy of a nonterminated string, +allocated from a memory heap. */ +UNIV_INLINE +char* +mem_heap_strdupl( +/*=============*/ + /* out, own: a copy of the string */ + mem_heap_t* heap, /* in: memory heap where string is allocated */ + const char* str, /* in: string to be copied */ + ulint len); /* in: length of str, in bytes */ + +/************************************************************************** +Concatenate two strings and return the result, using a memory heap. */ +UNIV_INTERN +char* +mem_heap_strcat( +/*============*/ + /* out, own: the result */ + mem_heap_t* heap, /* in: memory heap where string is allocated */ + const char* s1, /* in: string 1 */ + const char* s2); /* in: string 2 */ + +/************************************************************************** +Duplicate a block of data, allocated from a memory heap. */ +UNIV_INTERN +void* +mem_heap_dup( +/*=========*/ + /* out, own: a copy of the data */ + mem_heap_t* heap, /* in: memory heap where copy is allocated */ + const void* data, /* in: data to be copied */ + ulint len); /* in: length of data, in bytes */ + +/************************************************************************** +Concatenate two memory blocks and return the result, using a memory heap. */ +UNIV_INTERN +void* +mem_heap_cat( +/*=========*/ + /* out, own: the result */ + mem_heap_t* heap, /* in: memory heap where result is allocated */ + const void* b1, /* in: block 1 */ + ulint len1, /* in: length of b1, in bytes */ + const void* b2, /* in: block 2 */ + ulint len2); /* in: length of b2, in bytes */ + +/******************************************************************** +A simple (s)printf replacement that dynamically allocates the space for the +formatted string from the given heap. This supports a very limited set of +the printf syntax: types 's' and 'u' and length modifier 'l' (which is +required for the 'u' type). */ +UNIV_INTERN +char* +mem_heap_printf( +/*============*/ + /* out: heap-allocated formatted string */ + mem_heap_t* heap, /* in: memory heap */ + const char* format, /* in: format string */ + ...) __attribute__ ((format (printf, 2, 3))); + +#ifdef MEM_PERIODIC_CHECK +/********************************************************************** +Goes through the list of all allocated mem blocks, checks their magic +numbers, and reports possible corruption. */ +UNIV_INTERN +void +mem_validate_all_blocks(void); +/*=========================*/ +#endif + +/*#######################################################################*/ + +/* The info header of a block in a memory heap */ + +struct mem_block_info_struct { + ulint magic_n;/* magic number for debugging */ + char file_name[8];/* file name where the mem heap was created */ + ulint line; /* line number where the mem heap was created */ + UT_LIST_BASE_NODE_T(mem_block_t) base; /* In the first block in the + the list this is the base node of the list of blocks; + in subsequent blocks this is undefined */ + UT_LIST_NODE_T(mem_block_t) list; /* This contains pointers to next + and prev in the list. The first block allocated + to the heap is also the first block in this list, + though it also contains the base node of the list. */ + ulint len; /* physical length of this block in bytes */ + ulint type; /* type of heap: MEM_HEAP_DYNAMIC, or + MEM_HEAP_BUF possibly ORed to MEM_HEAP_BTR_SEARCH */ + ulint free; /* offset in bytes of the first free position for + user data in the block */ + ulint start; /* the value of the struct field 'free' at the + creation of the block */ + void* free_block; + /* if the MEM_HEAP_BTR_SEARCH bit is set in type, + and this is the heap root, this can contain an + allocated buffer frame, which can be appended as a + free block to the heap, if we need more space; + otherwise, this is NULL */ + void* buf_block; + /* if this block has been allocated from the buffer + pool, this contains the buf_block_t handle; + otherwise, this is NULL */ +#ifdef MEM_PERIODIC_CHECK + UT_LIST_NODE_T(mem_block_t) mem_block_list; + /* List of all mem blocks allocated; protected + by the mem_comm_pool mutex */ +#endif +}; + +#define MEM_BLOCK_MAGIC_N 764741555 +#define MEM_FREED_BLOCK_MAGIC_N 547711122 + +/* Header size for a memory heap block */ +#define MEM_BLOCK_HEADER_SIZE ut_calc_align(sizeof(mem_block_info_t),\ + UNIV_MEM_ALIGNMENT) +#include "mem0dbg.h" + +#ifndef UNIV_NONINL +#include "mem0mem.ic" +#endif + +#endif diff --git a/storage/xtradb/include/mem0mem.ic b/storage/xtradb/include/mem0mem.ic new file mode 100644 index 00000000000..04b4234904a --- /dev/null +++ b/storage/xtradb/include/mem0mem.ic @@ -0,0 +1,646 @@ +/***************************************************************************** + +Copyright (c) 1994, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/************************************************************************ +The memory management + +Created 6/8/1994 Heikki Tuuri +*************************************************************************/ + +#include "mem0dbg.ic" + +#include "mem0pool.h" + +/******************************************************************* +Creates a memory heap block where data can be allocated. */ +UNIV_INTERN +mem_block_t* +mem_heap_create_block( +/*==================*/ + /* out, own: memory heap block, NULL if + did not succeed (only possible for + MEM_HEAP_BTR_SEARCH type heaps) */ + mem_heap_t* heap, /* in: memory heap or NULL if first block + should be created */ + ulint n, /* in: number of bytes needed for user data */ + ulint type, /* in: type of heap: MEM_HEAP_DYNAMIC or + MEM_HEAP_BUFFER */ + const char* file_name,/* in: file name where created */ + ulint line); /* in: line where created */ +/********************************************************************** +Frees a block from a memory heap. */ +UNIV_INTERN +void +mem_heap_block_free( +/*================*/ + mem_heap_t* heap, /* in: heap */ + mem_block_t* block); /* in: block to free */ +/********************************************************************** +Frees the free_block field from a memory heap. */ +UNIV_INTERN +void +mem_heap_free_block_free( +/*=====================*/ + mem_heap_t* heap); /* in: heap */ +/******************************************************************* +Adds a new block to a memory heap. */ +UNIV_INTERN +mem_block_t* +mem_heap_add_block( +/*===============*/ + /* out: created block, NULL if did not + succeed (only possible for + MEM_HEAP_BTR_SEARCH type heaps)*/ + mem_heap_t* heap, /* in: memory heap */ + ulint n); /* in: number of bytes user needs */ + +UNIV_INLINE +void +mem_block_set_len(mem_block_t* block, ulint len) +{ + ut_ad(len > 0); + + block->len = len; +} + +UNIV_INLINE +ulint +mem_block_get_len(mem_block_t* block) +{ + return(block->len); +} + +UNIV_INLINE +void +mem_block_set_type(mem_block_t* block, ulint type) +{ + ut_ad((type == MEM_HEAP_DYNAMIC) || (type == MEM_HEAP_BUFFER) + || (type == MEM_HEAP_BUFFER + MEM_HEAP_BTR_SEARCH)); + + block->type = type; +} + +UNIV_INLINE +ulint +mem_block_get_type(mem_block_t* block) +{ + return(block->type); +} + +UNIV_INLINE +void +mem_block_set_free(mem_block_t* block, ulint free) +{ + ut_ad(free > 0); + ut_ad(free <= mem_block_get_len(block)); + + block->free = free; +} + +UNIV_INLINE +ulint +mem_block_get_free(mem_block_t* block) +{ + return(block->free); +} + +UNIV_INLINE +void +mem_block_set_start(mem_block_t* block, ulint start) +{ + ut_ad(start > 0); + + block->start = start; +} + +UNIV_INLINE +ulint +mem_block_get_start(mem_block_t* block) +{ + return(block->start); +} + +/******************************************************************* +Allocates and zero-fills n bytes of memory from a memory heap. */ +UNIV_INLINE +void* +mem_heap_zalloc( +/*============*/ + /* out: allocated, zero-filled storage */ + mem_heap_t* heap, /* in: memory heap */ + ulint n) /* in: number of bytes; if the heap is allowed + to grow into the buffer pool, this must be + <= MEM_MAX_ALLOC_IN_BUF */ +{ + ut_ad(heap); + ut_ad(!(heap->type & MEM_HEAP_BTR_SEARCH)); + return(memset(mem_heap_alloc(heap, n), 0, n)); +} + +/******************************************************************* +Allocates n bytes of memory from a memory heap. */ +UNIV_INLINE +void* +mem_heap_alloc( +/*===========*/ + /* out: allocated storage, NULL if did not + succeed (only possible for + MEM_HEAP_BTR_SEARCH type heaps) */ + mem_heap_t* heap, /* in: memory heap */ + ulint n) /* in: number of bytes; if the heap is allowed + to grow into the buffer pool, this must be + <= MEM_MAX_ALLOC_IN_BUF */ +{ + mem_block_t* block; + void* buf; + ulint free; + + ut_ad(mem_heap_check(heap)); + + block = UT_LIST_GET_LAST(heap->base); + + ut_ad(!(block->type & MEM_HEAP_BUFFER) || (n <= MEM_MAX_ALLOC_IN_BUF)); + + /* Check if there is enough space in block. If not, create a new + block to the heap */ + + if (mem_block_get_len(block) + < mem_block_get_free(block) + MEM_SPACE_NEEDED(n)) { + + block = mem_heap_add_block(heap, n); + + if (block == NULL) { + + return(NULL); + } + } + + free = mem_block_get_free(block); + + buf = (byte*)block + free; + + mem_block_set_free(block, free + MEM_SPACE_NEEDED(n)); + +#ifdef UNIV_MEM_DEBUG + UNIV_MEM_ALLOC(buf, + n + MEM_FIELD_HEADER_SIZE + MEM_FIELD_TRAILER_SIZE); + + /* In the debug version write debugging info to the field */ + mem_field_init((byte*)buf, n); + + /* Advance buf to point at the storage which will be given to the + caller */ + buf = (byte*)buf + MEM_FIELD_HEADER_SIZE; + +#endif +#ifdef UNIV_SET_MEM_TO_ZERO + UNIV_MEM_ALLOC(buf, n); + memset(buf, '\0', n); +#endif + UNIV_MEM_ALLOC(buf, n); + return(buf); +} + +/********************************************************************* +Returns a pointer to the heap top. */ +UNIV_INLINE +byte* +mem_heap_get_heap_top( +/*==================*/ + /* out: pointer to the heap top */ + mem_heap_t* heap) /* in: memory heap */ +{ + mem_block_t* block; + byte* buf; + + ut_ad(mem_heap_check(heap)); + + block = UT_LIST_GET_LAST(heap->base); + + buf = (byte*)block + mem_block_get_free(block); + + return(buf); +} + +/********************************************************************* +Frees the space in a memory heap exceeding the pointer given. The +pointer must have been acquired from mem_heap_get_heap_top. The first +memory block of the heap is not freed. */ +UNIV_INLINE +void +mem_heap_free_heap_top( +/*===================*/ + mem_heap_t* heap, /* in: heap from which to free */ + byte* old_top)/* in: pointer to old top of heap */ +{ + mem_block_t* block; + mem_block_t* prev_block; +#ifdef UNIV_MEM_DEBUG + ibool error; + ulint total_size; + ulint size; +#endif + + ut_ad(mem_heap_check(heap)); + +#ifdef UNIV_MEM_DEBUG + + /* Validate the heap and get its total allocated size */ + mem_heap_validate_or_print(heap, NULL, FALSE, &error, &total_size, + NULL, NULL); + ut_a(!error); + + /* Get the size below top pointer */ + mem_heap_validate_or_print(heap, old_top, FALSE, &error, &size, NULL, + NULL); + ut_a(!error); + +#endif + + block = UT_LIST_GET_LAST(heap->base); + + while (block != NULL) { + if (((byte*)block + mem_block_get_free(block) >= old_top) + && ((byte*)block <= old_top)) { + /* Found the right block */ + + break; + } + + /* Store prev_block value before freeing the current block + (the current block will be erased in freeing) */ + + prev_block = UT_LIST_GET_PREV(list, block); + + mem_heap_block_free(heap, block); + + block = prev_block; + } + + ut_ad(block); + + /* Set the free field of block */ + mem_block_set_free(block, old_top - (byte*)block); + +#ifdef UNIV_MEM_DEBUG + ut_ad(mem_block_get_start(block) <= mem_block_get_free(block)); + + /* In the debug version erase block from top up */ + mem_erase_buf(old_top, (byte*)block + block->len - old_top); + + /* Update allocated memory count */ + mutex_enter(&mem_hash_mutex); + mem_current_allocated_memory -= (total_size - size); + mutex_exit(&mem_hash_mutex); +#else /* UNIV_MEM_DEBUG */ + UNIV_MEM_ASSERT_W(old_top, (byte*)block + block->len - old_top); +#endif /* UNIV_MEM_DEBUG */ + UNIV_MEM_ALLOC(old_top, (byte*)block + block->len - old_top); + + /* If free == start, we may free the block if it is not the first + one */ + + if ((heap != block) && (mem_block_get_free(block) + == mem_block_get_start(block))) { + mem_heap_block_free(heap, block); + } +} + +/********************************************************************* +Empties a memory heap. The first memory block of the heap is not freed. */ +UNIV_INLINE +void +mem_heap_empty( +/*===========*/ + mem_heap_t* heap) /* in: heap to empty */ +{ + mem_heap_free_heap_top(heap, (byte*)heap + mem_block_get_start(heap)); + + if (heap->free_block) { + mem_heap_free_block_free(heap); + } +} + +/********************************************************************* +Returns a pointer to the topmost element in a memory heap. The size of the +element must be given. */ +UNIV_INLINE +void* +mem_heap_get_top( +/*=============*/ + /* out: pointer to the topmost element */ + mem_heap_t* heap, /* in: memory heap */ + ulint n) /* in: size of the topmost element */ +{ + mem_block_t* block; + void* buf; + + ut_ad(mem_heap_check(heap)); + + block = UT_LIST_GET_LAST(heap->base); + + buf = (byte*)block + mem_block_get_free(block) - MEM_SPACE_NEEDED(n); + +#ifdef UNIV_MEM_DEBUG + ut_ad(mem_block_get_start(block) <=(ulint)((byte*)buf - (byte*)block)); + + /* In the debug version, advance buf to point at the storage which + was given to the caller in the allocation*/ + + buf = (byte*)buf + MEM_FIELD_HEADER_SIZE; + + /* Check that the field lengths agree */ + ut_ad(n == (ulint)mem_field_header_get_len(buf)); +#endif + + return(buf); +} + +/********************************************************************* +Frees the topmost element in a memory heap. The size of the element must be +given. */ +UNIV_INLINE +void +mem_heap_free_top( +/*==============*/ + mem_heap_t* heap, /* in: memory heap */ + ulint n) /* in: size of the topmost element */ +{ + mem_block_t* block; + + ut_ad(mem_heap_check(heap)); + + block = UT_LIST_GET_LAST(heap->base); + + /* Subtract the free field of block */ + mem_block_set_free(block, mem_block_get_free(block) + - MEM_SPACE_NEEDED(n)); + UNIV_MEM_ASSERT_W((byte*) block + mem_block_get_free(block), n); +#ifdef UNIV_MEM_DEBUG + + ut_ad(mem_block_get_start(block) <= mem_block_get_free(block)); + + /* In the debug version check the consistency, and erase field */ + mem_field_erase((byte*)block + mem_block_get_free(block), n); +#endif + + /* If free == start, we may free the block if it is not the first + one */ + + if ((heap != block) && (mem_block_get_free(block) + == mem_block_get_start(block))) { + mem_heap_block_free(heap, block); + } else { + /* Avoid a bogus UNIV_MEM_ASSERT_W() warning in a + subsequent invocation of mem_heap_free_top(). + Originally, this was UNIV_MEM_FREE(), to catch writes + to freed memory. */ + UNIV_MEM_ALLOC((byte*) block + mem_block_get_free(block), n); + } +} + +/********************************************************************* +NOTE: Use the corresponding macros instead of this function. Creates a +memory heap. For debugging purposes, takes also the file name and line as +argument. */ +UNIV_INLINE +mem_heap_t* +mem_heap_create_func( +/*=================*/ + /* out, own: memory heap, NULL if + did not succeed (only possible for + MEM_HEAP_BTR_SEARCH type heaps)*/ + ulint n, /* in: desired start block size, + this means that a single user buffer + of size n will fit in the block, + 0 creates a default size block */ + ulint type, /* in: heap type */ + const char* file_name, /* in: file name where created */ + ulint line) /* in: line where created */ +{ + mem_block_t* block; + + if (!n) { + n = MEM_BLOCK_START_SIZE; + } + + block = mem_heap_create_block(NULL, n, type, file_name, line); + + if (block == NULL) { + + return(NULL); + } + + UT_LIST_INIT(block->base); + + /* Add the created block itself as the first block in the list */ + UT_LIST_ADD_FIRST(list, block->base, block); + +#ifdef UNIV_MEM_DEBUG + + mem_hash_insert(block, file_name, line); + +#endif + + return(block); +} + +/********************************************************************* +NOTE: Use the corresponding macro instead of this function. Frees the space +occupied by a memory heap. In the debug version erases the heap memory +blocks. */ +UNIV_INLINE +void +mem_heap_free_func( +/*===============*/ + mem_heap_t* heap, /* in, own: heap to be freed */ + const char* file_name __attribute__((unused)), + /* in: file name where freed */ + ulint line __attribute__((unused))) +{ + mem_block_t* block; + mem_block_t* prev_block; + + ut_ad(mem_heap_check(heap)); + + block = UT_LIST_GET_LAST(heap->base); + +#ifdef UNIV_MEM_DEBUG + + /* In the debug version remove the heap from the hash table of heaps + and check its consistency */ + + mem_hash_remove(heap, file_name, line); + +#endif + + if (heap->free_block) { + mem_heap_free_block_free(heap); + } + + while (block != NULL) { + /* Store the contents of info before freeing current block + (it is erased in freeing) */ + + prev_block = UT_LIST_GET_PREV(list, block); + + mem_heap_block_free(heap, block); + + block = prev_block; + } +} + +/******************************************************************* +NOTE: Use the corresponding macro instead of this function. +Allocates a single buffer of memory from the dynamic memory of +the C compiler. Is like malloc of C. The buffer must be freed +with mem_free. */ +UNIV_INLINE +void* +mem_alloc_func( +/*===========*/ + /* out, own: free storage */ + ulint n, /* in: desired number of bytes */ + ulint* size, /* out: allocated size in bytes, + or NULL */ + const char* file_name, /* in: file name where created */ + ulint line) /* in: line where created */ +{ + mem_heap_t* heap; + void* buf; + + heap = mem_heap_create_func(n, MEM_HEAP_DYNAMIC, file_name, line); + + /* Note that as we created the first block in the heap big enough + for the buffer requested by the caller, the buffer will be in the + first block and thus we can calculate the pointer to the heap from + the pointer to the buffer when we free the memory buffer. */ + + if (UNIV_LIKELY_NULL(size)) { + /* Adjust the allocation to the actual size of the + memory block. */ + ulint m = mem_block_get_len(heap) + - mem_block_get_free(heap); +#ifdef UNIV_MEM_DEBUG + m -= MEM_FIELD_HEADER_SIZE + MEM_FIELD_TRAILER_SIZE; +#endif /* UNIV_MEM_DEBUG */ + ut_ad(m >= n); + *size = n = m; + } + + buf = mem_heap_alloc(heap, n); + + ut_a((byte*)heap == (byte*)buf - MEM_BLOCK_HEADER_SIZE + - MEM_FIELD_HEADER_SIZE); + return(buf); +} + +/******************************************************************* +NOTE: Use the corresponding macro instead of this function. Frees a single +buffer of storage from the dynamic memory of the C compiler. Similar to the +free of C. */ +UNIV_INLINE +void +mem_free_func( +/*==========*/ + void* ptr, /* in, own: buffer to be freed */ + const char* file_name, /* in: file name where created */ + ulint line /* in: line where created */ + ) +{ + mem_heap_t* heap; + + heap = (mem_heap_t*)((byte*)ptr - MEM_BLOCK_HEADER_SIZE + - MEM_FIELD_HEADER_SIZE); + mem_heap_free_func(heap, file_name, line); +} + +/********************************************************************* +Returns the space in bytes occupied by a memory heap. */ +UNIV_INLINE +ulint +mem_heap_get_size( +/*==============*/ + mem_heap_t* heap) /* in: heap */ +{ + mem_block_t* block; + ulint size = 0; + + ut_ad(mem_heap_check(heap)); + + block = heap; + + while (block != NULL) { + + size += mem_block_get_len(block); + block = UT_LIST_GET_NEXT(list, block); + } + + if (heap->free_block) { + size += UNIV_PAGE_SIZE; + } + + return(size); +} + +/************************************************************************** +Duplicates a NUL-terminated string. */ +UNIV_INLINE +char* +mem_strdup( +/*=======*/ + /* out, own: a copy of the string, + must be deallocated with mem_free */ + const char* str) /* in: string to be copied */ +{ + ulint len = strlen(str) + 1; + return((char*) memcpy(mem_alloc(len), str, len)); +} + +/************************************************************************** +Makes a NUL-terminated copy of a nonterminated string. */ +UNIV_INLINE +char* +mem_strdupl( +/*========*/ + /* out, own: a copy of the string, + must be deallocated with mem_free */ + const char* str, /* in: string to be copied */ + ulint len) /* in: length of str, in bytes */ +{ + char* s = (char*) mem_alloc(len + 1); + s[len] = 0; + return((char*) memcpy(s, str, len)); +} + +/************************************************************************** +Makes a NUL-terminated copy of a nonterminated string, +allocated from a memory heap. */ +UNIV_INLINE +char* +mem_heap_strdupl( +/*=============*/ + /* out, own: a copy of the string */ + mem_heap_t* heap, /* in: memory heap where string is allocated */ + const char* str, /* in: string to be copied */ + ulint len) /* in: length of str, in bytes */ +{ + char* s = (char*) mem_heap_alloc(heap, len + 1); + s[len] = 0; + return((char*) memcpy(s, str, len)); +} diff --git a/storage/xtradb/include/mem0pool.h b/storage/xtradb/include/mem0pool.h new file mode 100644 index 00000000000..7e51b07bfe0 --- /dev/null +++ b/storage/xtradb/include/mem0pool.h @@ -0,0 +1,126 @@ +/***************************************************************************** + +Copyright (c) 1994, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/****************************************************** +The lowest-level memory management + +Created 6/9/1994 Heikki Tuuri +*******************************************************/ + +#ifndef mem0pool_h +#define mem0pool_h + +#include "univ.i" +#include "os0file.h" +#include "ut0lst.h" + +typedef struct mem_area_struct mem_area_t; +typedef struct mem_pool_struct mem_pool_t; + +/* The common memory pool */ +extern mem_pool_t* mem_comm_pool; + +/* Memory area header */ + +struct mem_area_struct{ + ulint size_and_free; /* memory area size is obtained by + anding with ~MEM_AREA_FREE; area in + a free list if ANDing with + MEM_AREA_FREE results in nonzero */ + UT_LIST_NODE_T(mem_area_t) + free_list; /* free list node */ +}; + +/* Each memory area takes this many extra bytes for control information */ +#define MEM_AREA_EXTRA_SIZE (ut_calc_align(sizeof(struct mem_area_struct),\ + UNIV_MEM_ALIGNMENT)) + +/************************************************************************ +Creates a memory pool. */ +UNIV_INTERN +mem_pool_t* +mem_pool_create( +/*============*/ + /* out: memory pool */ + ulint size); /* in: pool size in bytes */ +/************************************************************************ +Allocates memory from a pool. NOTE: This low-level function should only be +used in mem0mem.*! */ +UNIV_INTERN +void* +mem_area_alloc( +/*===========*/ + /* out, own: allocated memory buffer */ + ulint* psize, /* in: requested size in bytes; for optimum + space usage, the size should be a power of 2 + minus MEM_AREA_EXTRA_SIZE; + out: allocated size in bytes (greater than + or equal to the requested size) */ + mem_pool_t* pool); /* in: memory pool */ +/************************************************************************ +Frees memory to a pool. */ +UNIV_INTERN +void +mem_area_free( +/*==========*/ + void* ptr, /* in, own: pointer to allocated memory + buffer */ + mem_pool_t* pool); /* in: memory pool */ +/************************************************************************ +Returns the amount of reserved memory. */ +UNIV_INTERN +ulint +mem_pool_get_reserved( +/*==================*/ + /* out: reserved mmeory in bytes */ + mem_pool_t* pool); /* in: memory pool */ +/************************************************************************ +Reserves the mem pool mutex. */ +UNIV_INTERN +void +mem_pool_mutex_enter(void); +/*======================*/ +/************************************************************************ +Releases the mem pool mutex. */ +UNIV_INTERN +void +mem_pool_mutex_exit(void); +/*=====================*/ +/************************************************************************ +Validates a memory pool. */ +UNIV_INTERN +ibool +mem_pool_validate( +/*==============*/ + /* out: TRUE if ok */ + mem_pool_t* pool); /* in: memory pool */ +/************************************************************************ +Prints info of a memory pool. */ +UNIV_INTERN +void +mem_pool_print_info( +/*================*/ + FILE* outfile,/* in: output file to write to */ + mem_pool_t* pool); /* in: memory pool */ + + +#ifndef UNIV_NONINL +#include "mem0pool.ic" +#endif + +#endif diff --git a/storage/xtradb/include/mem0pool.ic b/storage/xtradb/include/mem0pool.ic new file mode 100644 index 00000000000..4cc65e754ce --- /dev/null +++ b/storage/xtradb/include/mem0pool.ic @@ -0,0 +1,23 @@ +/***************************************************************************** + +Copyright (c) 1994, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/************************************************************************ +The lowest-level memory management + +Created 6/8/1994 Heikki Tuuri +*************************************************************************/ diff --git a/storage/xtradb/include/mtr0log.h b/storage/xtradb/include/mtr0log.h new file mode 100644 index 00000000000..44374cdf1a4 --- /dev/null +++ b/storage/xtradb/include/mtr0log.h @@ -0,0 +1,247 @@ +/***************************************************************************** + +Copyright (c) 1995, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/****************************************************** +Mini-transaction logging routines + +Created 12/7/1995 Heikki Tuuri +*******************************************************/ + +#ifndef mtr0log_h +#define mtr0log_h + +#include "univ.i" +#include "mtr0mtr.h" +#include "dict0types.h" + +/************************************************************ +Writes 1 - 4 bytes to a file page buffered in the buffer pool. +Writes the corresponding log record to the mini-transaction log. */ +UNIV_INTERN +void +mlog_write_ulint( +/*=============*/ + byte* ptr, /* in: pointer where to write */ + ulint val, /* in: value to write */ + byte type, /* in: MLOG_1BYTE, MLOG_2BYTES, MLOG_4BYTES */ + mtr_t* mtr); /* in: mini-transaction handle */ +/************************************************************ +Writes 8 bytes to a file page buffered in the buffer pool. +Writes the corresponding log record to the mini-transaction log. */ +UNIV_INTERN +void +mlog_write_dulint( +/*==============*/ + byte* ptr, /* in: pointer where to write */ + dulint val, /* in: value to write */ + mtr_t* mtr); /* in: mini-transaction handle */ +/************************************************************ +Writes a string to a file page buffered in the buffer pool. Writes the +corresponding log record to the mini-transaction log. */ +UNIV_INTERN +void +mlog_write_string( +/*==============*/ + byte* ptr, /* in: pointer where to write */ + const byte* str, /* in: string to write */ + ulint len, /* in: string length */ + mtr_t* mtr); /* in: mini-transaction handle */ +/************************************************************ +Logs a write of a string to a file page buffered in the buffer pool. +Writes the corresponding log record to the mini-transaction log. */ +UNIV_INTERN +void +mlog_log_string( +/*============*/ + byte* ptr, /* in: pointer written to */ + ulint len, /* in: string length */ + mtr_t* mtr); /* in: mini-transaction handle */ +/************************************************************ +Writes initial part of a log record consisting of one-byte item +type and four-byte space and page numbers. */ +UNIV_INTERN +void +mlog_write_initial_log_record( +/*==========================*/ + const byte* ptr, /* in: pointer to (inside) a buffer + frame holding the file page where + modification is made */ + byte type, /* in: log item type: MLOG_1BYTE, ... */ + mtr_t* mtr); /* in: mini-transaction handle */ +/************************************************************ +Writes a log record about an .ibd file create/delete/rename. */ +UNIV_INLINE +byte* +mlog_write_initial_log_record_for_file_op( +/*======================================*/ + /* out: new value of log_ptr */ + ulint type, /* in: MLOG_FILE_CREATE, MLOG_FILE_DELETE, or + MLOG_FILE_RENAME */ + ulint space_id,/* in: space id, if applicable */ + ulint page_no,/* in: page number (not relevant currently) */ + byte* log_ptr,/* in: pointer to mtr log which has been opened */ + mtr_t* mtr); /* in: mtr */ +/************************************************************ +Catenates 1 - 4 bytes to the mtr log. */ +UNIV_INLINE +void +mlog_catenate_ulint( +/*================*/ + mtr_t* mtr, /* in: mtr */ + ulint val, /* in: value to write */ + ulint type); /* in: MLOG_1BYTE, MLOG_2BYTES, MLOG_4BYTES */ +/************************************************************ +Catenates n bytes to the mtr log. */ +UNIV_INTERN +void +mlog_catenate_string( +/*=================*/ + mtr_t* mtr, /* in: mtr */ + const byte* str, /* in: string to write */ + ulint len); /* in: string length */ +/************************************************************ +Catenates a compressed ulint to mlog. */ +UNIV_INLINE +void +mlog_catenate_ulint_compressed( +/*===========================*/ + mtr_t* mtr, /* in: mtr */ + ulint val); /* in: value to write */ +/************************************************************ +Catenates a compressed dulint to mlog. */ +UNIV_INLINE +void +mlog_catenate_dulint_compressed( +/*============================*/ + mtr_t* mtr, /* in: mtr */ + dulint val); /* in: value to write */ +/************************************************************ +Opens a buffer to mlog. It must be closed with mlog_close. */ +UNIV_INLINE +byte* +mlog_open( +/*======*/ + /* out: buffer, NULL if log mode MTR_LOG_NONE */ + mtr_t* mtr, /* in: mtr */ + ulint size); /* in: buffer size in bytes; MUST be + smaller than DYN_ARRAY_DATA_SIZE! */ +/************************************************************ +Closes a buffer opened to mlog. */ +UNIV_INLINE +void +mlog_close( +/*=======*/ + mtr_t* mtr, /* in: mtr */ + byte* ptr); /* in: buffer space from ptr up was not used */ +/************************************************************ +Writes the initial part of a log record (3..11 bytes). +If the implementation of this function is changed, all +size parameters to mlog_open() should be adjusted accordingly! */ +UNIV_INLINE +byte* +mlog_write_initial_log_record_fast( +/*===============================*/ + /* out: new value of log_ptr */ + const byte* ptr, /* in: pointer to (inside) a buffer + frame holding the file page where + modification is made */ + byte type, /* in: log item type: MLOG_1BYTE, ... */ + byte* log_ptr,/* in: pointer to mtr log which has + been opened */ + mtr_t* mtr); /* in: mtr */ +/************************************************************ +Parses an initial log record written by mlog_write_initial_log_record. */ +UNIV_INTERN +byte* +mlog_parse_initial_log_record( +/*==========================*/ + /* out: parsed record end, NULL if not a complete + record */ + byte* ptr, /* in: buffer */ + byte* end_ptr,/* in: buffer end */ + byte* type, /* out: log record type: MLOG_1BYTE, ... */ + ulint* space, /* out: space id */ + ulint* page_no);/* out: page number */ +/************************************************************ +Parses a log record written by mlog_write_ulint or mlog_write_dulint. */ +UNIV_INTERN +byte* +mlog_parse_nbytes( +/*==============*/ + /* out: parsed record end, NULL if not a complete + record */ + ulint type, /* in: log record type: MLOG_1BYTE, ... */ + byte* ptr, /* in: buffer */ + byte* end_ptr,/* in: buffer end */ + byte* page, /* in: page where to apply the log record, or NULL */ + void* page_zip);/* in/out: compressed page, or NULL */ +/************************************************************ +Parses a log record written by mlog_write_string. */ +UNIV_INTERN +byte* +mlog_parse_string( +/*==============*/ + /* out: parsed record end, NULL if not a complete + record */ + byte* ptr, /* in: buffer */ + byte* end_ptr,/* in: buffer end */ + byte* page, /* in: page where to apply the log record, or NULL */ + void* page_zip);/* in/out: compressed page, or NULL */ + + +/************************************************************ +Opens a buffer for mlog, writes the initial log record and, +if needed, the field lengths of an index. Reserves space +for further log entries. The log entry must be closed with +mtr_close(). */ +UNIV_INTERN +byte* +mlog_open_and_write_index( +/*======================*/ + /* out: buffer, NULL if log mode + MTR_LOG_NONE */ + mtr_t* mtr, /* in: mtr */ + byte* rec, /* in: index record or page */ + dict_index_t* index, /* in: record descriptor */ + byte type, /* in: log item type */ + ulint size); /* in: requested buffer size in bytes + (if 0, calls mlog_close() and returns NULL) */ + +/************************************************************ +Parses a log record written by mlog_open_and_write_index. */ +UNIV_INTERN +byte* +mlog_parse_index( +/*=============*/ + /* out: parsed record end, + NULL if not a complete record */ + byte* ptr, /* in: buffer */ + byte* end_ptr,/* in: buffer end */ + /* out: new value of log_ptr */ + ibool comp, /* in: TRUE=compact record format */ + dict_index_t** index); /* out, own: dummy index */ + +/* Insert, update, and maybe other functions may use this value to define an +extra mlog buffer size for variable size data */ +#define MLOG_BUF_MARGIN 256 + +#ifndef UNIV_NONINL +#include "mtr0log.ic" +#endif + +#endif diff --git a/storage/xtradb/include/mtr0log.ic b/storage/xtradb/include/mtr0log.ic new file mode 100644 index 00000000000..5f05befb9cc --- /dev/null +++ b/storage/xtradb/include/mtr0log.ic @@ -0,0 +1,247 @@ +/***************************************************************************** + +Copyright (c) 1995, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/****************************************************** +Mini-transaction logging routines + +Created 12/7/1995 Heikki Tuuri +*******************************************************/ + +#include "mach0data.h" +#include "ut0lst.h" +#include "buf0buf.h" + +/************************************************************ +Opens a buffer to mlog. It must be closed with mlog_close. */ +UNIV_INLINE +byte* +mlog_open( +/*======*/ + /* out: buffer, NULL if log mode MTR_LOG_NONE */ + mtr_t* mtr, /* in: mtr */ + ulint size) /* in: buffer size in bytes; MUST be + smaller than DYN_ARRAY_DATA_SIZE! */ +{ + dyn_array_t* mlog; + + mtr->modifications = TRUE; + + if (mtr_get_log_mode(mtr) == MTR_LOG_NONE) { + + return(NULL); + } + + mlog = &(mtr->log); + + return(dyn_array_open(mlog, size)); +} + +/************************************************************ +Closes a buffer opened to mlog. */ +UNIV_INLINE +void +mlog_close( +/*=======*/ + mtr_t* mtr, /* in: mtr */ + byte* ptr) /* in: buffer space from ptr up was not used */ +{ + dyn_array_t* mlog; + + ut_ad(mtr_get_log_mode(mtr) != MTR_LOG_NONE); + + mlog = &(mtr->log); + + dyn_array_close(mlog, ptr); +} + +/************************************************************ +Catenates 1 - 4 bytes to the mtr log. The value is not compressed. */ +UNIV_INLINE +void +mlog_catenate_ulint( +/*================*/ + mtr_t* mtr, /* in: mtr */ + ulint val, /* in: value to write */ + ulint type) /* in: MLOG_1BYTE, MLOG_2BYTES, MLOG_4BYTES */ +{ + dyn_array_t* mlog; + byte* ptr; + + if (mtr_get_log_mode(mtr) == MTR_LOG_NONE) { + + return; + } + + mlog = &(mtr->log); + +#if MLOG_1BYTE != 1 +# error "MLOG_1BYTE != 1" +#endif +#if MLOG_2BYTES != 2 +# error "MLOG_2BYTES != 2" +#endif +#if MLOG_4BYTES != 4 +# error "MLOG_4BYTES != 4" +#endif +#if MLOG_8BYTES != 8 +# error "MLOG_8BYTES != 8" +#endif + ptr = (byte*) dyn_array_push(mlog, type); + + if (type == MLOG_4BYTES) { + mach_write_to_4(ptr, val); + } else if (type == MLOG_2BYTES) { + mach_write_to_2(ptr, val); + } else { + ut_ad(type == MLOG_1BYTE); + mach_write_to_1(ptr, val); + } +} + +/************************************************************ +Catenates a compressed ulint to mlog. */ +UNIV_INLINE +void +mlog_catenate_ulint_compressed( +/*===========================*/ + mtr_t* mtr, /* in: mtr */ + ulint val) /* in: value to write */ +{ + byte* log_ptr; + + log_ptr = mlog_open(mtr, 10); + + /* If no logging is requested, we may return now */ + if (log_ptr == NULL) { + + return; + } + + log_ptr += mach_write_compressed(log_ptr, val); + + mlog_close(mtr, log_ptr); +} + +/************************************************************ +Catenates a compressed dulint to mlog. */ +UNIV_INLINE +void +mlog_catenate_dulint_compressed( +/*============================*/ + mtr_t* mtr, /* in: mtr */ + dulint val) /* in: value to write */ +{ + byte* log_ptr; + + log_ptr = mlog_open(mtr, 15); + + /* If no logging is requested, we may return now */ + if (log_ptr == NULL) { + + return; + } + + log_ptr += mach_dulint_write_compressed(log_ptr, val); + + mlog_close(mtr, log_ptr); +} + +/************************************************************ +Writes the initial part of a log record (3..11 bytes). +If the implementation of this function is changed, all +size parameters to mlog_open() should be adjusted accordingly! */ +UNIV_INLINE +byte* +mlog_write_initial_log_record_fast( +/*===============================*/ + /* out: new value of log_ptr */ + const byte* ptr, /* in: pointer to (inside) a buffer + frame holding the file page where + modification is made */ + byte type, /* in: log item type: MLOG_1BYTE, ... */ + byte* log_ptr,/* in: pointer to mtr log which has + been opened */ + mtr_t* mtr) /* in: mtr */ +{ +#ifdef UNIV_DEBUG + buf_block_t* block; +#endif + const byte* page; + ulint space; + ulint offset; + + ut_ad(mtr_memo_contains_page(mtr, ptr, MTR_MEMO_PAGE_X_FIX)); + ut_ad(type <= MLOG_BIGGEST_TYPE); + ut_ad(ptr && log_ptr); + + page = (const byte*) ut_align_down(ptr, UNIV_PAGE_SIZE); + space = mach_read_from_4(page + FIL_PAGE_ARCH_LOG_NO_OR_SPACE_ID); + offset = mach_read_from_4(page + FIL_PAGE_OFFSET); + + mach_write_to_1(log_ptr, type); + log_ptr++; + log_ptr += mach_write_compressed(log_ptr, space); + log_ptr += mach_write_compressed(log_ptr, offset); + + mtr->n_log_recs++; + +#ifdef UNIV_LOG_DEBUG + fprintf(stderr, + "Adding to mtr log record type %lu space %lu page no %lu\n", + (ulong) type, space, offset); +#endif + +#ifdef UNIV_DEBUG + /* We now assume that all x-latched pages have been modified! */ + block = (buf_block_t*) buf_block_align(ptr); + + if (!mtr_memo_contains(mtr, block, MTR_MEMO_MODIFY)) { + + mtr_memo_push(mtr, block, MTR_MEMO_MODIFY); + } +#endif + return(log_ptr); +} + +/************************************************************ +Writes a log record about an .ibd file create/delete/rename. */ +UNIV_INLINE +byte* +mlog_write_initial_log_record_for_file_op( +/*======================================*/ + /* out: new value of log_ptr */ + ulint type, /* in: MLOG_FILE_CREATE, MLOG_FILE_DELETE, or + MLOG_FILE_RENAME */ + ulint space_id,/* in: space id, if applicable */ + ulint page_no,/* in: page number (not relevant currently) */ + byte* log_ptr,/* in: pointer to mtr log which has been opened */ + mtr_t* mtr) /* in: mtr */ +{ + ut_ad(log_ptr); + + mach_write_to_1(log_ptr, type); + log_ptr++; + + /* We write dummy space id and page number */ + log_ptr += mach_write_compressed(log_ptr, space_id); + log_ptr += mach_write_compressed(log_ptr, page_no); + + mtr->n_log_recs++; + + return(log_ptr); +} diff --git a/storage/xtradb/include/mtr0mtr.h b/storage/xtradb/include/mtr0mtr.h new file mode 100644 index 00000000000..a29f6c73141 --- /dev/null +++ b/storage/xtradb/include/mtr0mtr.h @@ -0,0 +1,380 @@ +/***************************************************************************** + +Copyright (c) 1995, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/****************************************************** +Mini-transaction buffer + +Created 11/26/1995 Heikki Tuuri +*******************************************************/ + +#ifndef mtr0mtr_h +#define mtr0mtr_h + +#include "univ.i" +#include "mem0mem.h" +#include "dyn0dyn.h" +#include "buf0types.h" +#include "sync0rw.h" +#include "ut0byte.h" +#include "mtr0types.h" +#include "page0types.h" + +/* Logging modes for a mini-transaction */ +#define MTR_LOG_ALL 21 /* default mode: log all operations + modifying disk-based data */ +#define MTR_LOG_NONE 22 /* log no operations */ +/*#define MTR_LOG_SPACE 23 */ /* log only operations modifying + file space page allocation data + (operations in fsp0fsp.* ) */ +#define MTR_LOG_SHORT_INSERTS 24 /* inserts are logged in a shorter + form */ + +/* Types for the mlock objects to store in the mtr memo; NOTE that the +first 3 values must be RW_S_LATCH, RW_X_LATCH, RW_NO_LATCH */ +#define MTR_MEMO_PAGE_S_FIX RW_S_LATCH +#define MTR_MEMO_PAGE_X_FIX RW_X_LATCH +#define MTR_MEMO_BUF_FIX RW_NO_LATCH +#define MTR_MEMO_MODIFY 54 +#define MTR_MEMO_S_LOCK 55 +#define MTR_MEMO_X_LOCK 56 + +/* Log item types: we have made them to be of the type 'byte' +for the compiler to warn if val and type parameters are switched +in a call to mlog_write_ulint. NOTE! For 1 - 8 bytes, the +flag value must give the length also! */ +#define MLOG_SINGLE_REC_FLAG 128 /* if the mtr contains only + one log record for one page, + i.e., write_initial_log_record + has been called only once, + this flag is ORed to the type + of that first log record */ +#define MLOG_1BYTE (1) /* one byte is written */ +#define MLOG_2BYTES (2) /* 2 bytes ... */ +#define MLOG_4BYTES (4) /* 4 bytes ... */ +#define MLOG_8BYTES (8) /* 8 bytes ... */ +#define MLOG_REC_INSERT ((byte)9) /* record insert */ +#define MLOG_REC_CLUST_DELETE_MARK ((byte)10) /* mark clustered index record + deleted */ +#define MLOG_REC_SEC_DELETE_MARK ((byte)11) /* mark secondary index record + deleted */ +#define MLOG_REC_UPDATE_IN_PLACE ((byte)13) /* update of a record, + preserves record field sizes */ +#define MLOG_REC_DELETE ((byte)14) /* delete a record from a + page */ +#define MLOG_LIST_END_DELETE ((byte)15) /* delete record list end on + index page */ +#define MLOG_LIST_START_DELETE ((byte)16) /* delete record list start on + index page */ +#define MLOG_LIST_END_COPY_CREATED ((byte)17) /* copy record list end to a + new created index page */ +#define MLOG_PAGE_REORGANIZE ((byte)18) /* reorganize an index page */ +#define MLOG_PAGE_CREATE ((byte)19) /* create an index page */ +#define MLOG_UNDO_INSERT ((byte)20) /* insert entry in an undo + log */ +#define MLOG_UNDO_ERASE_END ((byte)21) /* erase an undo log + page end */ +#define MLOG_UNDO_INIT ((byte)22) /* initialize a page in an + undo log */ +#define MLOG_UNDO_HDR_DISCARD ((byte)23) /* discard an update undo log + header */ +#define MLOG_UNDO_HDR_REUSE ((byte)24) /* reuse an insert undo log + header */ +#define MLOG_UNDO_HDR_CREATE ((byte)25) /* create an undo log header */ +#define MLOG_REC_MIN_MARK ((byte)26) /* mark an index record as the + predefined minimum record */ +#define MLOG_IBUF_BITMAP_INIT ((byte)27) /* initialize an ibuf bitmap + page */ +/*#define MLOG_FULL_PAGE ((byte)28) full contents of a page */ +#define MLOG_INIT_FILE_PAGE ((byte)29) /* this means that a file page + is taken into use and the prior + contents of the page should be + ignored: in recovery we must + not trust the lsn values stored + to the file page */ +#define MLOG_WRITE_STRING ((byte)30) /* write a string to a page */ +#define MLOG_MULTI_REC_END ((byte)31) /* if a single mtr writes + log records for several pages, + this log record ends the + sequence of these records */ +#define MLOG_DUMMY_RECORD ((byte)32) /* dummy log record used to + pad a log block full */ +#define MLOG_FILE_CREATE ((byte)33) /* log record about an .ibd + file creation */ +#define MLOG_FILE_RENAME ((byte)34) /* log record about an .ibd + file rename */ +#define MLOG_FILE_DELETE ((byte)35) /* log record about an .ibd + file deletion */ +#define MLOG_COMP_REC_MIN_MARK ((byte)36) /* mark a compact index record + as the predefined minimum + record */ +#define MLOG_COMP_PAGE_CREATE ((byte)37) /* create a compact + index page */ +#define MLOG_COMP_REC_INSERT ((byte)38) /* compact record insert */ +#define MLOG_COMP_REC_CLUST_DELETE_MARK ((byte)39) + /* mark compact clustered index + record deleted */ +#define MLOG_COMP_REC_SEC_DELETE_MARK ((byte)40)/* mark compact secondary index + record deleted; this log + record type is redundant, as + MLOG_REC_SEC_DELETE_MARK is + independent of the record + format. */ +#define MLOG_COMP_REC_UPDATE_IN_PLACE ((byte)41)/* update of a compact record, + preserves record field sizes */ +#define MLOG_COMP_REC_DELETE ((byte)42) /* delete a compact record + from a page */ +#define MLOG_COMP_LIST_END_DELETE ((byte)43) /* delete compact record list + end on index page */ +#define MLOG_COMP_LIST_START_DELETE ((byte)44) /* delete compact record list + start on index page */ +#define MLOG_COMP_LIST_END_COPY_CREATED ((byte)45) + /* copy compact record list end + to a new created index page */ +#define MLOG_COMP_PAGE_REORGANIZE ((byte)46) /* reorganize an index page */ +#define MLOG_FILE_CREATE2 ((byte)47) /* log record about creating + an .ibd file, with format */ +#define MLOG_ZIP_WRITE_NODE_PTR ((byte)48) /* write the node pointer of + a record on a compressed + non-leaf B-tree page */ +#define MLOG_ZIP_WRITE_BLOB_PTR ((byte)49) /* write the BLOB pointer + of an externally stored column + on a compressed page */ +#define MLOG_ZIP_WRITE_HEADER ((byte)50) /* write to compressed page + header */ +#define MLOG_ZIP_PAGE_COMPRESS ((byte)51) /* compress an index page */ +#define MLOG_BIGGEST_TYPE ((byte)51) /* biggest value (used in + asserts) */ + +/******************************************************************* +Starts a mini-transaction and creates a mini-transaction handle +and buffer in the memory buffer given by the caller. */ +UNIV_INLINE +mtr_t* +mtr_start( +/*======*/ + /* out: mtr buffer which also acts as + the mtr handle */ + mtr_t* mtr); /* in: memory buffer for the mtr buffer */ +/******************************************************************* +Commits a mini-transaction. */ +UNIV_INTERN +void +mtr_commit( +/*=======*/ + mtr_t* mtr); /* in: mini-transaction */ +/************************************************************** +Sets and returns a savepoint in mtr. */ +UNIV_INLINE +ulint +mtr_set_savepoint( +/*==============*/ + /* out: savepoint */ + mtr_t* mtr); /* in: mtr */ +/************************************************************** +Releases the latches stored in an mtr memo down to a savepoint. +NOTE! The mtr must not have made changes to buffer pages after the +savepoint, as these can be handled only by mtr_commit. */ +UNIV_INTERN +void +mtr_rollback_to_savepoint( +/*======================*/ + mtr_t* mtr, /* in: mtr */ + ulint savepoint); /* in: savepoint */ +/************************************************************** +Releases the (index tree) s-latch stored in an mtr memo after a +savepoint. */ +UNIV_INLINE +void +mtr_release_s_latch_at_savepoint( +/*=============================*/ + mtr_t* mtr, /* in: mtr */ + ulint savepoint, /* in: savepoint */ + rw_lock_t* lock); /* in: latch to release */ +/******************************************************************* +Gets the logging mode of a mini-transaction. */ +UNIV_INLINE +ulint +mtr_get_log_mode( +/*=============*/ + /* out: logging mode: MTR_LOG_NONE, ... */ + mtr_t* mtr); /* in: mtr */ +/******************************************************************* +Changes the logging mode of a mini-transaction. */ +UNIV_INLINE +ulint +mtr_set_log_mode( +/*=============*/ + /* out: old mode */ + mtr_t* mtr, /* in: mtr */ + ulint mode); /* in: logging mode: MTR_LOG_NONE, ... */ +/************************************************************ +Reads 1 - 4 bytes from a file page buffered in the buffer pool. */ +UNIV_INTERN +ulint +mtr_read_ulint( +/*===========*/ + /* out: value read */ + const byte* ptr, /* in: pointer from where to read */ + ulint type, /* in: MLOG_1BYTE, MLOG_2BYTES, MLOG_4BYTES */ + mtr_t* mtr); /* in: mini-transaction handle */ +/************************************************************ +Reads 8 bytes from a file page buffered in the buffer pool. */ +UNIV_INTERN +dulint +mtr_read_dulint( +/*============*/ + /* out: value read */ + const byte* ptr, /* in: pointer from where to read */ + mtr_t* mtr); /* in: mini-transaction handle */ +/************************************************************************* +This macro locks an rw-lock in s-mode. */ +#define mtr_s_lock(B, MTR) mtr_s_lock_func((B), __FILE__, __LINE__,\ + (MTR)) +/************************************************************************* +This macro locks an rw-lock in x-mode. */ +#define mtr_x_lock(B, MTR) mtr_x_lock_func((B), __FILE__, __LINE__,\ + (MTR)) +/************************************************************************* +NOTE! Use the macro above! +Locks a lock in s-mode. */ +UNIV_INLINE +void +mtr_s_lock_func( +/*============*/ + rw_lock_t* lock, /* in: rw-lock */ + const char* file, /* in: file name */ + ulint line, /* in: line number */ + mtr_t* mtr); /* in: mtr */ +/************************************************************************* +NOTE! Use the macro above! +Locks a lock in x-mode. */ +UNIV_INLINE +void +mtr_x_lock_func( +/*============*/ + rw_lock_t* lock, /* in: rw-lock */ + const char* file, /* in: file name */ + ulint line, /* in: line number */ + mtr_t* mtr); /* in: mtr */ + +/******************************************************* +Releases an object in the memo stack. */ +UNIV_INTERN +void +mtr_memo_release( +/*=============*/ + mtr_t* mtr, /* in: mtr */ + void* object, /* in: object */ + ulint type); /* in: object type: MTR_MEMO_S_LOCK, ... */ +#ifdef UNIV_DEBUG +/************************************************************** +Checks if memo contains the given item. */ +UNIV_INLINE +ibool +mtr_memo_contains( +/*==============*/ + /* out: TRUE if contains */ + mtr_t* mtr, /* in: mtr */ + const void* object, /* in: object to search */ + ulint type); /* in: type of object */ + +/************************************************************** +Checks if memo contains the given page. */ +UNIV_INTERN +ibool +mtr_memo_contains_page( +/*===================*/ + /* out: TRUE if contains */ + mtr_t* mtr, /* in: mtr */ + const byte* ptr, /* in: pointer to buffer frame */ + ulint type); /* in: type of object */ +/************************************************************* +Prints info of an mtr handle. */ +UNIV_INTERN +void +mtr_print( +/*======*/ + mtr_t* mtr); /* in: mtr */ +#endif /* UNIV_DEBUG */ +/*######################################################################*/ + +#define MTR_BUF_MEMO_SIZE 200 /* number of slots in memo */ + +/******************************************************************* +Returns the log object of a mini-transaction buffer. */ +UNIV_INLINE +dyn_array_t* +mtr_get_log( +/*========*/ + /* out: log */ + mtr_t* mtr); /* in: mini-transaction */ +/******************************************************* +Pushes an object to an mtr memo stack. */ +UNIV_INLINE +void +mtr_memo_push( +/*==========*/ + mtr_t* mtr, /* in: mtr */ + void* object, /* in: object */ + ulint type); /* in: object type: MTR_MEMO_S_LOCK, ... */ + + +/* Type definition of a mini-transaction memo stack slot. */ +typedef struct mtr_memo_slot_struct mtr_memo_slot_t; +struct mtr_memo_slot_struct{ + ulint type; /* type of the stored object (MTR_MEMO_S_LOCK, ...) */ + void* object; /* pointer to the object */ +}; + +/* Mini-transaction handle and buffer */ +struct mtr_struct{ +#ifdef UNIV_DEBUG + ulint state; /* MTR_ACTIVE, MTR_COMMITTING, MTR_COMMITTED */ +#endif + dyn_array_t memo; /* memo stack for locks etc. */ + dyn_array_t log; /* mini-transaction log */ + ibool modifications; + /* TRUE if the mtr made modifications to + buffer pool pages */ + ulint n_log_recs; + /* count of how many page initial log records + have been written to the mtr log */ + ulint log_mode; /* specifies which operations should be + logged; default value MTR_LOG_ALL */ + ib_uint64_t start_lsn;/* start lsn of the possible log entry for + this mtr */ + ib_uint64_t end_lsn;/* end lsn of the possible log entry for + this mtr */ +#ifdef UNIV_DEBUG + ulint magic_n; +#endif /* UNIV_DEBUG */ +}; + +#ifdef UNIV_DEBUG +# define MTR_MAGIC_N 54551 +#endif /* UNIV_DEBUG */ + +#define MTR_ACTIVE 12231 +#define MTR_COMMITTING 56456 +#define MTR_COMMITTED 34676 + +#ifndef UNIV_NONINL +#include "mtr0mtr.ic" +#endif + +#endif diff --git a/storage/xtradb/include/mtr0mtr.ic b/storage/xtradb/include/mtr0mtr.ic new file mode 100644 index 00000000000..7d6d99917b7 --- /dev/null +++ b/storage/xtradb/include/mtr0mtr.ic @@ -0,0 +1,266 @@ +/***************************************************************************** + +Copyright (c) 1995, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/****************************************************** +Mini-transaction buffer + +Created 11/26/1995 Heikki Tuuri +*******************************************************/ + +#include "sync0sync.h" +#include "sync0rw.h" +#include "mach0data.h" + +/******************************************************************* +Starts a mini-transaction and creates a mini-transaction handle +and a buffer in the memory buffer given by the caller. */ +UNIV_INLINE +mtr_t* +mtr_start( +/*======*/ + /* out: mtr buffer which also acts as + the mtr handle */ + mtr_t* mtr) /* in: memory buffer for the mtr buffer */ +{ + dyn_array_create(&(mtr->memo)); + dyn_array_create(&(mtr->log)); + + mtr->log_mode = MTR_LOG_ALL; + mtr->modifications = FALSE; + mtr->n_log_recs = 0; + + ut_d(mtr->state = MTR_ACTIVE); + ut_d(mtr->magic_n = MTR_MAGIC_N); + + return(mtr); +} + +/******************************************************* +Pushes an object to an mtr memo stack. */ +UNIV_INLINE +void +mtr_memo_push( +/*==========*/ + mtr_t* mtr, /* in: mtr */ + void* object, /* in: object */ + ulint type) /* in: object type: MTR_MEMO_S_LOCK, ... */ +{ + dyn_array_t* memo; + mtr_memo_slot_t* slot; + + ut_ad(object); + ut_ad(type >= MTR_MEMO_PAGE_S_FIX); + ut_ad(type <= MTR_MEMO_X_LOCK); + ut_ad(mtr); + ut_ad(mtr->magic_n == MTR_MAGIC_N); + + memo = &(mtr->memo); + + slot = (mtr_memo_slot_t*) dyn_array_push(memo, sizeof *slot); + + slot->object = object; + slot->type = type; +} + +/************************************************************** +Sets and returns a savepoint in mtr. */ +UNIV_INLINE +ulint +mtr_set_savepoint( +/*==============*/ + /* out: savepoint */ + mtr_t* mtr) /* in: mtr */ +{ + dyn_array_t* memo; + + ut_ad(mtr); + ut_ad(mtr->magic_n == MTR_MAGIC_N); + + memo = &(mtr->memo); + + return(dyn_array_get_data_size(memo)); +} + +/************************************************************** +Releases the (index tree) s-latch stored in an mtr memo after a +savepoint. */ +UNIV_INLINE +void +mtr_release_s_latch_at_savepoint( +/*=============================*/ + mtr_t* mtr, /* in: mtr */ + ulint savepoint, /* in: savepoint */ + rw_lock_t* lock) /* in: latch to release */ +{ + mtr_memo_slot_t* slot; + dyn_array_t* memo; + + ut_ad(mtr); + ut_ad(mtr->magic_n == MTR_MAGIC_N); + ut_ad(mtr->state == MTR_ACTIVE); + + memo = &(mtr->memo); + + ut_ad(dyn_array_get_data_size(memo) > savepoint); + + slot = (mtr_memo_slot_t*) dyn_array_get_element(memo, savepoint); + + ut_ad(slot->object == lock); + ut_ad(slot->type == MTR_MEMO_S_LOCK); + + rw_lock_s_unlock(lock); + + slot->object = NULL; +} + +#ifdef UNIV_DEBUG +/************************************************************** +Checks if memo contains the given item. */ +UNIV_INLINE +ibool +mtr_memo_contains( +/*==============*/ + /* out: TRUE if contains */ + mtr_t* mtr, /* in: mtr */ + const void* object, /* in: object to search */ + ulint type) /* in: type of object */ +{ + mtr_memo_slot_t* slot; + dyn_array_t* memo; + ulint offset; + + ut_ad(mtr); + ut_ad(mtr->magic_n == MTR_MAGIC_N); + + memo = &(mtr->memo); + + offset = dyn_array_get_data_size(memo); + + while (offset > 0) { + offset -= sizeof(mtr_memo_slot_t); + + slot = dyn_array_get_element(memo, offset); + + if ((object == slot->object) && (type == slot->type)) { + + return(TRUE); + } + } + + return(FALSE); +} +#endif /* UNIV_DEBUG */ + +/******************************************************************* +Returns the log object of a mini-transaction buffer. */ +UNIV_INLINE +dyn_array_t* +mtr_get_log( +/*========*/ + /* out: log */ + mtr_t* mtr) /* in: mini-transaction */ +{ + ut_ad(mtr); + ut_ad(mtr->magic_n == MTR_MAGIC_N); + + return(&(mtr->log)); +} + +/******************************************************************* +Gets the logging mode of a mini-transaction. */ +UNIV_INLINE +ulint +mtr_get_log_mode( +/*=============*/ + /* out: logging mode: MTR_LOG_NONE, ... */ + mtr_t* mtr) /* in: mtr */ +{ + ut_ad(mtr); + ut_ad(mtr->log_mode >= MTR_LOG_ALL); + ut_ad(mtr->log_mode <= MTR_LOG_SHORT_INSERTS); + + return(mtr->log_mode); +} + +/******************************************************************* +Changes the logging mode of a mini-transaction. */ +UNIV_INLINE +ulint +mtr_set_log_mode( +/*=============*/ + /* out: old mode */ + mtr_t* mtr, /* in: mtr */ + ulint mode) /* in: logging mode: MTR_LOG_NONE, ... */ +{ + ulint old_mode; + + ut_ad(mtr); + ut_ad(mode >= MTR_LOG_ALL); + ut_ad(mode <= MTR_LOG_SHORT_INSERTS); + + old_mode = mtr->log_mode; + + if ((mode == MTR_LOG_SHORT_INSERTS) && (old_mode == MTR_LOG_NONE)) { + /* Do nothing */ + } else { + mtr->log_mode = mode; + } + + ut_ad(old_mode >= MTR_LOG_ALL); + ut_ad(old_mode <= MTR_LOG_SHORT_INSERTS); + + return(old_mode); +} + +/************************************************************************* +Locks a lock in s-mode. */ +UNIV_INLINE +void +mtr_s_lock_func( +/*============*/ + rw_lock_t* lock, /* in: rw-lock */ + const char* file, /* in: file name */ + ulint line, /* in: line number */ + mtr_t* mtr) /* in: mtr */ +{ + ut_ad(mtr); + ut_ad(lock); + + rw_lock_s_lock_func(lock, 0, file, line); + + mtr_memo_push(mtr, lock, MTR_MEMO_S_LOCK); +} + +/************************************************************************* +Locks a lock in x-mode. */ +UNIV_INLINE +void +mtr_x_lock_func( +/*============*/ + rw_lock_t* lock, /* in: rw-lock */ + const char* file, /* in: file name */ + ulint line, /* in: line number */ + mtr_t* mtr) /* in: mtr */ +{ + ut_ad(mtr); + ut_ad(lock); + + rw_lock_x_lock_func(lock, 0, file, line); + + mtr_memo_push(mtr, lock, MTR_MEMO_X_LOCK); +} diff --git a/storage/xtradb/include/mtr0types.h b/storage/xtradb/include/mtr0types.h new file mode 100644 index 00000000000..23634c98827 --- /dev/null +++ b/storage/xtradb/include/mtr0types.h @@ -0,0 +1,30 @@ +/***************************************************************************** + +Copyright (c) 1995, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/****************************************************** +Mini-transaction buffer global types + +Created 11/26/1995 Heikki Tuuri +*******************************************************/ + +#ifndef mtr0types_h +#define mtr0types_h + +typedef struct mtr_struct mtr_t; + +#endif diff --git a/storage/xtradb/include/mysql_addons.h b/storage/xtradb/include/mysql_addons.h new file mode 100644 index 00000000000..2e8c87f5962 --- /dev/null +++ b/storage/xtradb/include/mysql_addons.h @@ -0,0 +1,32 @@ +/***************************************************************************** + +Copyright (c) 2007, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/****************************************************** +This file contains functions that need to be added to +MySQL code but have not been added yet. + +Whenever you add a function here submit a MySQL bug +report (feature request) with the implementation. Then +write the bug number in the comment before the +function in this file. + +When MySQL commits the function it can be deleted from +here. In a perfect world this file exists but is empty. + +Created November 07, 2007 Vasil Dimov +*******************************************************/ diff --git a/storage/xtradb/include/os0file.h b/storage/xtradb/include/os0file.h new file mode 100644 index 00000000000..26897226ff4 --- /dev/null +++ b/storage/xtradb/include/os0file.h @@ -0,0 +1,758 @@ +/***************************************************************************** + +Copyright (c) 1995, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/****************************************************** +The interface to the operating system file io + +Created 10/21/1995 Heikki Tuuri +*******************************************************/ + +#ifndef os0file_h +#define os0file_h + +#include "univ.i" + +#ifndef __WIN__ +#include <dirent.h> +#include <sys/stat.h> +#include <time.h> +#endif + +typedef struct fil_node_struct fil_node_t; + +#ifdef UNIV_DO_FLUSH +extern ibool os_do_not_call_flush_at_each_write; +#endif /* UNIV_DO_FLUSH */ +extern ibool os_has_said_disk_full; +extern ibool os_aio_print_debug; + +extern ulint os_file_n_pending_preads; +extern ulint os_file_n_pending_pwrites; + +extern ulint os_n_pending_reads; +extern ulint os_n_pending_writes; + +#ifdef __WIN__ + +/* We define always WIN_ASYNC_IO, and check at run-time whether + the OS actually supports it: Win 95 does not, NT does. */ +#define WIN_ASYNC_IO + +#define UNIV_NON_BUFFERED_IO + +#endif + +#ifdef __WIN__ +#define os_file_t HANDLE +#define OS_FILE_FROM_FD(fd) (HANDLE) _get_osfhandle(fd) +#else +typedef int os_file_t; +#define OS_FILE_FROM_FD(fd) fd +#endif + +extern ulint os_innodb_umask; + +/* If this flag is TRUE, then we will use the native aio of the +OS (provided we compiled Innobase with it in), otherwise we will +use simulated aio we build below with threads */ + +extern ibool os_aio_use_native_aio; + +#define OS_FILE_SECTOR_SIZE 512 + +/* The next value should be smaller or equal to the smallest sector size used +on any disk. A log block is required to be a portion of disk which is written +so that if the start and the end of a block get written to disk, then the +whole block gets written. This should be true even in most cases of a crash: +if this fails for a log block, then it is equivalent to a media failure in the +log. */ + +#define OS_FILE_LOG_BLOCK_SIZE 512 + +/* Options for file_create */ +#define OS_FILE_OPEN 51 +#define OS_FILE_CREATE 52 +#define OS_FILE_OVERWRITE 53 +#define OS_FILE_OPEN_RAW 54 +#define OS_FILE_CREATE_PATH 55 +#define OS_FILE_OPEN_RETRY 56 /* for os_file_create() on + the first ibdata file */ + +#define OS_FILE_READ_ONLY 333 +#define OS_FILE_READ_WRITE 444 +#define OS_FILE_READ_ALLOW_DELETE 555 /* for ibbackup */ + +/* Options for file_create */ +#define OS_FILE_AIO 61 +#define OS_FILE_NORMAL 62 + +/* Types for file create */ +#define OS_DATA_FILE 100 +#define OS_LOG_FILE 101 + +/* Error codes from os_file_get_last_error */ +#define OS_FILE_NOT_FOUND 71 +#define OS_FILE_DISK_FULL 72 +#define OS_FILE_ALREADY_EXISTS 73 +#define OS_FILE_PATH_ERROR 74 +#define OS_FILE_AIO_RESOURCES_RESERVED 75 /* wait for OS aio resources + to become available again */ +#define OS_FILE_SHARING_VIOLATION 76 +#define OS_FILE_ERROR_NOT_SPECIFIED 77 + +/* Types for aio operations */ +#define OS_FILE_READ 10 +#define OS_FILE_WRITE 11 + +#define OS_FILE_LOG 256 /* This can be ORed to type */ + +#define OS_AIO_N_PENDING_IOS_PER_THREAD 32 /* Win NT does not allow more + than 64 */ + +/* Modes for aio operations */ +#define OS_AIO_NORMAL 21 /* Normal asynchronous i/o not for ibuf + pages or ibuf bitmap pages */ +#define OS_AIO_IBUF 22 /* Asynchronous i/o for ibuf pages or ibuf + bitmap pages */ +#define OS_AIO_LOG 23 /* Asynchronous i/o for the log */ +#define OS_AIO_SYNC 24 /* Asynchronous i/o where the calling thread + will itself wait for the i/o to complete, + doing also the job of the i/o-handler thread; + can be used for any pages, ibuf or non-ibuf. + This is used to save CPU time, as we can do + with fewer thread switches. Plain synchronous + i/o is not as good, because it must serialize + the file seek and read or write, causing a + bottleneck for parallelism. */ + +#define OS_AIO_SIMULATED_WAKE_LATER 512 /* This can be ORed to mode + in the call of os_aio(...), + if the caller wants to post several i/o + requests in a batch, and only after that + wake the i/o-handler thread; this has + effect only in simulated aio */ +#define OS_WIN31 1 +#define OS_WIN95 2 +#define OS_WINNT 3 +#define OS_WIN2000 4 + +extern ulint os_n_file_reads; +extern ulint os_n_file_writes; +extern ulint os_n_fsyncs; + +/* File types for directory entry data type */ + +enum os_file_type_enum{ + OS_FILE_TYPE_UNKNOWN = 0, + OS_FILE_TYPE_FILE, /* regular file */ + OS_FILE_TYPE_DIR, /* directory */ + OS_FILE_TYPE_LINK /* symbolic link */ +}; +typedef enum os_file_type_enum os_file_type_t; + +/* Maximum path string length in bytes when referring to tables with in the +'./databasename/tablename.ibd' path format; we can allocate at least 2 buffers +of this size from the thread stack; that is why this should not be made much +bigger than 4000 bytes */ +#define OS_FILE_MAX_PATH 4000 + +/* Struct used in fetching information of a file in a directory */ +struct os_file_stat_struct{ + char name[OS_FILE_MAX_PATH]; /* path to a file */ + os_file_type_t type; /* file type */ + ib_int64_t size; /* file size */ + time_t ctime; /* creation time */ + time_t mtime; /* modification time */ + time_t atime; /* access time */ +}; +typedef struct os_file_stat_struct os_file_stat_t; + +#ifdef __WIN__ +typedef HANDLE os_file_dir_t; /* directory stream */ +#else +typedef DIR* os_file_dir_t; /* directory stream */ +#endif + +/*************************************************************************** +Gets the operating system version. Currently works only on Windows. */ +UNIV_INTERN +ulint +os_get_os_version(void); +/*===================*/ + /* out: OS_WIN95, OS_WIN31, OS_WINNT, or OS_WIN2000 */ +/******************************************************************** +Creates the seek mutexes used in positioned reads and writes. */ +UNIV_INTERN +void +os_io_init_simple(void); +/*===================*/ +/*************************************************************************** +Creates a temporary file. This function is like tmpfile(3), but +the temporary file is created in the MySQL temporary directory. +On Netware, this function is like tmpfile(3), because the C run-time +library of Netware does not expose the delete-on-close flag. */ + +FILE* +os_file_create_tmpfile(void); +/*========================*/ + /* out: temporary file handle, or NULL on error */ +/*************************************************************************** +The os_file_opendir() function opens a directory stream corresponding to the +directory named by the dirname argument. The directory stream is positioned +at the first entry. In both Unix and Windows we automatically skip the '.' +and '..' items at the start of the directory listing. */ +UNIV_INTERN +os_file_dir_t +os_file_opendir( +/*============*/ + /* out: directory stream, NULL if + error */ + const char* dirname, /* in: directory name; it must not + contain a trailing '\' or '/' */ + ibool error_is_fatal);/* in: TRUE if we should treat an + error as a fatal error; if we try to + open symlinks then we do not wish a + fatal error if it happens not to be + a directory */ +/*************************************************************************** +Closes a directory stream. */ +UNIV_INTERN +int +os_file_closedir( +/*=============*/ + /* out: 0 if success, -1 if failure */ + os_file_dir_t dir); /* in: directory stream */ +/*************************************************************************** +This function returns information of the next file in the directory. We jump +over the '.' and '..' entries in the directory. */ +UNIV_INTERN +int +os_file_readdir_next_file( +/*======================*/ + /* out: 0 if ok, -1 if error, 1 if at the end + of the directory */ + const char* dirname,/* in: directory name or path */ + os_file_dir_t dir, /* in: directory stream */ + os_file_stat_t* info); /* in/out: buffer where the info is returned */ +/********************************************************************* +This function attempts to create a directory named pathname. The new directory +gets default permissions. On Unix, the permissions are (0770 & ~umask). If the +directory exists already, nothing is done and the call succeeds, unless the +fail_if_exists arguments is true. */ +UNIV_INTERN +ibool +os_file_create_directory( +/*=====================*/ + /* out: TRUE if call succeeds, + FALSE on error */ + const char* pathname, /* in: directory name as + null-terminated string */ + ibool fail_if_exists);/* in: if TRUE, pre-existing directory + is treated as an error. */ +/******************************************************************** +A simple function to open or create a file. */ +UNIV_INTERN +os_file_t +os_file_create_simple( +/*==================*/ + /* out, own: handle to the file, not defined + if error, error number can be retrieved with + os_file_get_last_error */ + const char* name, /* in: name of the file or path as a + null-terminated string */ + ulint create_mode,/* in: OS_FILE_OPEN if an existing file is + opened (if does not exist, error), or + OS_FILE_CREATE if a new file is created + (if exists, error), or + OS_FILE_CREATE_PATH if new file + (if exists, error) and subdirectories along + its path are created (if needed)*/ + ulint access_type,/* in: OS_FILE_READ_ONLY or + OS_FILE_READ_WRITE */ + ibool* success);/* out: TRUE if succeed, FALSE if error */ +/******************************************************************** +A simple function to open or create a file. */ +UNIV_INTERN +os_file_t +os_file_create_simple_no_error_handling( +/*====================================*/ + /* out, own: handle to the file, not defined + if error, error number can be retrieved with + os_file_get_last_error */ + const char* name, /* in: name of the file or path as a + null-terminated string */ + ulint create_mode,/* in: OS_FILE_OPEN if an existing file + is opened (if does not exist, error), or + OS_FILE_CREATE if a new file is created + (if exists, error) */ + ulint access_type,/* in: OS_FILE_READ_ONLY, + OS_FILE_READ_WRITE, or + OS_FILE_READ_ALLOW_DELETE; the last option is + used by a backup program reading the file */ + ibool* success);/* out: TRUE if succeed, FALSE if error */ +/******************************************************************** +Tries to disable OS caching on an opened file descriptor. */ +UNIV_INTERN +void +os_file_set_nocache( +/*================*/ + int fd, /* in: file descriptor to alter */ + const char* file_name, /* in: file name, used in the + diagnostic message */ + const char* operation_name);/* in: "open" or "create"; used in the + diagnostic message */ +/******************************************************************** +Opens an existing file or creates a new. */ +UNIV_INTERN +os_file_t +os_file_create( +/*===========*/ + /* out, own: handle to the file, not defined + if error, error number can be retrieved with + os_file_get_last_error */ + const char* name, /* in: name of the file or path as a + null-terminated string */ + ulint create_mode,/* in: OS_FILE_OPEN if an existing file + is opened (if does not exist, error), or + OS_FILE_CREATE if a new file is created + (if exists, error), + OS_FILE_OVERWRITE if a new file is created + or an old overwritten; + OS_FILE_OPEN_RAW, if a raw device or disk + partition should be opened */ + ulint purpose,/* in: OS_FILE_AIO, if asynchronous, + non-buffered i/o is desired, + OS_FILE_NORMAL, if any normal file; + NOTE that it also depends on type, os_aio_.. + and srv_.. variables whether we really use + async i/o or unbuffered i/o: look in the + function source code for the exact rules */ + ulint type, /* in: OS_DATA_FILE or OS_LOG_FILE */ + ibool* success);/* out: TRUE if succeed, FALSE if error */ +/*************************************************************************** +Deletes a file. The file has to be closed before calling this. */ +UNIV_INTERN +ibool +os_file_delete( +/*===========*/ + /* out: TRUE if success */ + const char* name); /* in: file path as a null-terminated string */ + +/*************************************************************************** +Deletes a file if it exists. The file has to be closed before calling this. */ +UNIV_INTERN +ibool +os_file_delete_if_exists( +/*=====================*/ + /* out: TRUE if success */ + const char* name); /* in: file path as a null-terminated string */ +/*************************************************************************** +Renames a file (can also move it to another directory). It is safest that the +file is closed before calling this function. */ +UNIV_INTERN +ibool +os_file_rename( +/*===========*/ + /* out: TRUE if success */ + const char* oldpath, /* in: old file path as a + null-terminated string */ + const char* newpath); /* in: new file path */ +/*************************************************************************** +Closes a file handle. In case of error, error number can be retrieved with +os_file_get_last_error. */ +UNIV_INTERN +ibool +os_file_close( +/*==========*/ + /* out: TRUE if success */ + os_file_t file); /* in, own: handle to a file */ +/*************************************************************************** +Closes a file handle. */ +UNIV_INTERN +ibool +os_file_close_no_error_handling( +/*============================*/ + /* out: TRUE if success */ + os_file_t file); /* in, own: handle to a file */ +/*************************************************************************** +Gets a file size. */ +UNIV_INTERN +ibool +os_file_get_size( +/*=============*/ + /* out: TRUE if success */ + os_file_t file, /* in: handle to a file */ + ulint* size, /* out: least significant 32 bits of file + size */ + ulint* size_high);/* out: most significant 32 bits of size */ +/*************************************************************************** +Gets file size as a 64-bit integer ib_int64_t. */ +UNIV_INTERN +ib_int64_t +os_file_get_size_as_iblonglong( +/*===========================*/ + /* out: size in bytes, -1 if error */ + os_file_t file); /* in: handle to a file */ +/*************************************************************************** +Write the specified number of zeros to a newly created file. */ +UNIV_INTERN +ibool +os_file_set_size( +/*=============*/ + /* out: TRUE if success */ + const char* name, /* in: name of the file or path as a + null-terminated string */ + os_file_t file, /* in: handle to a file */ + ulint size, /* in: least significant 32 bits of file + size */ + ulint size_high);/* in: most significant 32 bits of size */ +/*************************************************************************** +Truncates a file at its current position. */ +UNIV_INTERN +ibool +os_file_set_eof( +/*============*/ + /* out: TRUE if success */ + FILE* file); /* in: file to be truncated */ +/*************************************************************************** +Flushes the write buffers of a given file to the disk. */ +UNIV_INTERN +ibool +os_file_flush( +/*==========*/ + /* out: TRUE if success */ + os_file_t file); /* in, own: handle to a file */ +/*************************************************************************** +Retrieves the last error number if an error occurs in a file io function. +The number should be retrieved before any other OS calls (because they may +overwrite the error number). If the number is not known to this program, +the OS error number + 100 is returned. */ +UNIV_INTERN +ulint +os_file_get_last_error( +/*===================*/ + /* out: error number, or OS error + number + 100 */ + ibool report_all_errors); /* in: TRUE if we want an error message + printed of all errors */ +/*********************************************************************** +Requests a synchronous read operation. */ +UNIV_INTERN +ibool +os_file_read( +/*=========*/ + /* out: TRUE if request was + successful, FALSE if fail */ + os_file_t file, /* in: handle to a file */ + void* buf, /* in: buffer where to read */ + ulint offset, /* in: least significant 32 bits of file + offset where to read */ + ulint offset_high,/* in: most significant 32 bits of + offset */ + ulint n); /* in: number of bytes to read */ +/*********************************************************************** +Rewind file to its start, read at most size - 1 bytes from it to str, and +NUL-terminate str. All errors are silently ignored. This function is +mostly meant to be used with temporary files. */ +UNIV_INTERN +void +os_file_read_string( +/*================*/ + FILE* file, /* in: file to read from */ + char* str, /* in: buffer where to read */ + ulint size); /* in: size of buffer */ +/*********************************************************************** +Requests a synchronous positioned read operation. This function does not do +any error handling. In case of error it returns FALSE. */ +UNIV_INTERN +ibool +os_file_read_no_error_handling( +/*===========================*/ + /* out: TRUE if request was + successful, FALSE if fail */ + os_file_t file, /* in: handle to a file */ + void* buf, /* in: buffer where to read */ + ulint offset, /* in: least significant 32 bits of file + offset where to read */ + ulint offset_high,/* in: most significant 32 bits of + offset */ + ulint n); /* in: number of bytes to read */ + +/*********************************************************************** +Requests a synchronous write operation. */ +UNIV_INTERN +ibool +os_file_write( +/*==========*/ + /* out: TRUE if request was + successful, FALSE if fail */ + const char* name, /* in: name of the file or path as a + null-terminated string */ + os_file_t file, /* in: handle to a file */ + const void* buf, /* in: buffer from which to write */ + ulint offset, /* in: least significant 32 bits of file + offset where to write */ + ulint offset_high,/* in: most significant 32 bits of + offset */ + ulint n); /* in: number of bytes to write */ +/*********************************************************************** +Check the existence and type of the given file. */ +UNIV_INTERN +ibool +os_file_status( +/*===========*/ + /* out: TRUE if call succeeded */ + const char* path, /* in: pathname of the file */ + ibool* exists, /* out: TRUE if file exists */ + os_file_type_t* type); /* out: type of the file (if it exists) */ +/******************************************************************** +The function os_file_dirname returns a directory component of a +null-terminated pathname string. In the usual case, dirname returns +the string up to, but not including, the final '/', and basename +is the component following the final '/'. Trailing '/' charac +ters are not counted as part of the pathname. + +If path does not contain a slash, dirname returns the string ".". + +Concatenating the string returned by dirname, a "/", and the basename +yields a complete pathname. + +The return value is a copy of the directory component of the pathname. +The copy is allocated from heap. It is the caller responsibility +to free it after it is no longer needed. + +The following list of examples (taken from SUSv2) shows the strings +returned by dirname and basename for different paths: + + path dirname basename + "/usr/lib" "/usr" "lib" + "/usr/" "/" "usr" + "usr" "." "usr" + "/" "/" "/" + "." "." "." + ".." "." ".." +*/ +UNIV_INTERN +char* +os_file_dirname( +/*============*/ + /* out, own: directory component of the + pathname */ + const char* path); /* in: pathname */ +/******************************************************************** +Creates all missing subdirectories along the given path. */ +UNIV_INTERN +ibool +os_file_create_subdirs_if_needed( +/*=============================*/ + /* out: TRUE if call succeeded + FALSE otherwise */ + const char* path); /* in: path name */ +/**************************************************************************** +Initializes the asynchronous io system. Creates separate aio array for +non-ibuf read and write, a third aio array for the ibuf i/o, with just one +segment, two aio arrays for log reads and writes with one segment, and a +synchronous aio array of the specified size. The combined number of segments +in the three first aio arrays is the parameter n_segments given to the +function. The caller must create an i/o handler thread for each segment in +the four first arrays, but not for the sync aio array. */ +UNIV_INTERN +void +os_aio_init( +/*========*/ + ulint n, /* in: maximum number of pending aio operations + allowed; n must be divisible by n_segments */ +// ulint n_segments, /* in: combined number of segments in the four +// first aio arrays; must be >= 4 */ + ulint n_read_threads, /* n_segments == 2 + n_read_threads + n_write_threads */ + ulint n_write_threads, /**/ + ulint n_slots_sync); /* in: number of slots in the sync aio array */ +/*********************************************************************** +Requests an asynchronous i/o operation. */ +UNIV_INTERN +ibool +os_aio( +/*===*/ + /* out: TRUE if request was queued + successfully, FALSE if fail */ + ulint type, /* in: OS_FILE_READ or OS_FILE_WRITE */ + ulint mode, /* in: OS_AIO_NORMAL, ..., possibly ORed + to OS_AIO_SIMULATED_WAKE_LATER: the + last flag advises this function not to wake + i/o-handler threads, but the caller will + do the waking explicitly later, in this + way the caller can post several requests in + a batch; NOTE that the batch must not be + so big that it exhausts the slots in aio + arrays! NOTE that a simulated batch + may introduce hidden chances of deadlocks, + because i/os are not actually handled until + all have been posted: use with great + caution! */ + const char* name, /* in: name of the file or path as a + null-terminated string */ + os_file_t file, /* in: handle to a file */ + void* buf, /* in: buffer where to read or from which + to write */ + ulint offset, /* in: least significant 32 bits of file + offset where to read or write */ + ulint offset_high, /* in: most significant 32 bits of + offset */ + ulint n, /* in: number of bytes to read or write */ + fil_node_t* message1,/* in: messages for the aio handler (these + can be used to identify a completed aio + operation); if mode is OS_AIO_SYNC, these + are ignored */ + void* message2); +/**************************************************************************** +Wakes up all async i/o threads so that they know to exit themselves in +shutdown. */ +UNIV_INTERN +void +os_aio_wake_all_threads_at_shutdown(void); +/*=====================================*/ +/**************************************************************************** +Waits until there are no pending writes in os_aio_write_array. There can +be other, synchronous, pending writes. */ +UNIV_INTERN +void +os_aio_wait_until_no_pending_writes(void); +/*=====================================*/ +/************************************************************************** +Wakes up simulated aio i/o-handler threads if they have something to do. */ +UNIV_INTERN +void +os_aio_simulated_wake_handler_threads(void); +/*=======================================*/ +/************************************************************************** +This function can be called if one wants to post a batch of reads and +prefers an i/o-handler thread to handle them all at once later. You must +call os_aio_simulated_wake_handler_threads later to ensure the threads +are not left sleeping! */ +UNIV_INTERN +void +os_aio_simulated_put_read_threads_to_sleep(void); +/*============================================*/ + +#ifdef WIN_ASYNC_IO +/************************************************************************** +This function is only used in Windows asynchronous i/o. +Waits for an aio operation to complete. This function is used to wait the +for completed requests. The aio array of pending requests is divided +into segments. The thread specifies which segment or slot it wants to wait +for. NOTE: this function will also take care of freeing the aio slot, +therefore no other thread is allowed to do the freeing! */ +UNIV_INTERN +ibool +os_aio_windows_handle( +/*==================*/ + /* out: TRUE if the aio operation succeeded */ + ulint segment, /* in: the number of the segment in the aio + arrays to wait for; segment 0 is the ibuf + i/o thread, segment 1 the log i/o thread, + then follow the non-ibuf read threads, and as + the last are the non-ibuf write threads; if + this is ULINT_UNDEFINED, then it means that + sync aio is used, and this parameter is + ignored */ + ulint pos, /* this parameter is used only in sync aio: + wait for the aio slot at this position */ + fil_node_t**message1, /* out: the messages passed with the aio + request; note that also in the case where + the aio operation failed, these output + parameters are valid and can be used to + restart the operation, for example */ + void** message2, + ulint* type); /* out: OS_FILE_WRITE or ..._READ */ +#endif + +/************************************************************************** +Does simulated aio. This function should be called by an i/o-handler +thread. */ +UNIV_INTERN +ibool +os_aio_simulated_handle( +/*====================*/ + /* out: TRUE if the aio operation succeeded */ + ulint segment, /* in: the number of the segment in the aio + arrays to wait for; segment 0 is the ibuf + i/o thread, segment 1 the log i/o thread, + then follow the non-ibuf read threads, and as + the last are the non-ibuf write threads */ + fil_node_t**message1, /* out: the messages passed with the aio + request; note that also in the case where + the aio operation failed, these output + parameters are valid and can be used to + restart the operation, for example */ + void** message2, + ulint* type); /* out: OS_FILE_WRITE or ..._READ */ +/************************************************************************** +Validates the consistency of the aio system. */ +UNIV_INTERN +ibool +os_aio_validate(void); +/*=================*/ + /* out: TRUE if ok */ +/************************************************************************** +Prints info of the aio arrays. */ +UNIV_INTERN +void +os_aio_print( +/*=========*/ + FILE* file); /* in: file where to print */ +/************************************************************************** +Refreshes the statistics used to print per-second averages. */ +UNIV_INTERN +void +os_aio_refresh_stats(void); +/*======================*/ + +#ifdef UNIV_DEBUG +/************************************************************************** +Checks that all slots in the system have been freed, that is, there are +no pending io operations. */ +UNIV_INTERN +ibool +os_aio_all_slots_free(void); +/*=======================*/ +#endif /* UNIV_DEBUG */ + +/*********************************************************************** +This function returns information about the specified file */ +UNIV_INTERN +ibool +os_file_get_status( +/*===============*/ + /* out: TRUE if stat + information found */ + const char* path, /* in: pathname of the file */ + os_file_stat_t* stat_info); /* information of a file in a + directory */ + +#if !defined(UNIV_HOTBACKUP) && !defined(__NETWARE__) +/************************************************************************* +Creates a temporary file that will be deleted on close. +This function is defined in ha_innodb.cc. */ +UNIV_INTERN +int +innobase_mysql_tmpfile(void); +/*========================*/ + /* out: temporary file descriptor, or < 0 on error */ +#endif /* !UNIV_HOTBACKUP && !__NETWARE__ */ + +#endif diff --git a/storage/xtradb/include/os0proc.h b/storage/xtradb/include/os0proc.h new file mode 100644 index 00000000000..19b0b112638 --- /dev/null +++ b/storage/xtradb/include/os0proc.h @@ -0,0 +1,84 @@ +/***************************************************************************** + +Copyright (c) 1995, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/****************************************************** +The interface to the operating system +process control primitives + +Created 9/30/1995 Heikki Tuuri +*******************************************************/ + +#ifndef os0proc_h +#define os0proc_h + +#include "univ.i" + +#ifdef UNIV_LINUX +#include <sys/ipc.h> +#include <sys/shm.h> +#endif + +typedef void* os_process_t; +typedef unsigned long int os_process_id_t; + +extern ibool os_use_large_pages; +/* Large page size. This may be a boot-time option on some platforms */ +extern ulint os_large_page_size; + +/******************************************************************** +Converts the current process id to a number. It is not guaranteed that the +number is unique. In Linux returns the 'process number' of the current +thread. That number is the same as one sees in 'top', for example. In Linux +the thread id is not the same as one sees in 'top'. */ +UNIV_INTERN +ulint +os_proc_get_number(void); +/*====================*/ +/******************************************************************** +Allocates large pages memory. */ +UNIV_INTERN +void* +os_mem_alloc_large( +/*===============*/ + /* out: allocated memory */ + ulint* n); /* in/out: number of bytes */ +/******************************************************************** +Frees large pages memory. */ +UNIV_INTERN +void +os_mem_free_large( +/*==============*/ + void *ptr, /* in: pointer returned by + os_mem_alloc_large() */ + ulint size); /* in: size returned by + os_mem_alloc_large() */ +/******************************************************************** +Sets the priority boost for threads released from waiting within the current +process. */ +UNIV_INTERN +void +os_process_set_priority_boost( +/*==========================*/ + ibool do_boost); /* in: TRUE if priority boost should be done, + FALSE if not */ + +#ifndef UNIV_NONINL +#include "os0proc.ic" +#endif + +#endif diff --git a/storage/xtradb/include/os0proc.ic b/storage/xtradb/include/os0proc.ic new file mode 100644 index 00000000000..9f1fb01866d --- /dev/null +++ b/storage/xtradb/include/os0proc.ic @@ -0,0 +1,26 @@ +/***************************************************************************** + +Copyright (c) 1995, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/****************************************************** +The interface to the operating system +process control primitives + +Created 9/30/1995 Heikki Tuuri +*******************************************************/ + + diff --git a/storage/xtradb/include/os0sync.h b/storage/xtradb/include/os0sync.h new file mode 100644 index 00000000000..7e058266762 --- /dev/null +++ b/storage/xtradb/include/os0sync.h @@ -0,0 +1,309 @@ +/***************************************************************************** + +Copyright (c) 1995, 2009, Innobase Oy. All Rights Reserved. +Copyright (c) 2008, Google Inc. + +Portions of this file contain modifications contributed and copyrighted by +Google, Inc. Those modifications are gratefully acknowledged and are described +briefly in the InnoDB documentation. The contributions by Google are +incorporated with their permission, and subject to the conditions contained in +the file COPYING.Google. + +This program is free software; you can redistribute it and/or modify it under +the terms of the GNU General Public License as published by the Free Software +Foundation; version 2 of the License. + +This program is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + +You 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 + +*****************************************************************************/ + +/****************************************************** +The interface to the operating system +synchronization primitives. + +Created 9/6/1995 Heikki Tuuri +*******************************************************/ + +#ifndef os0sync_h +#define os0sync_h + +#include "univ.i" +#include "ut0lst.h" + +#ifdef __WIN__ + +#define os_fast_mutex_t CRITICAL_SECTION + +typedef HANDLE os_native_event_t; + +typedef struct os_event_struct os_event_struct_t; +typedef os_event_struct_t* os_event_t; + +struct os_event_struct { + os_native_event_t handle; + /* Windows event */ + UT_LIST_NODE_T(os_event_struct_t) os_event_list; + /* list of all created events */ +}; +#else +typedef pthread_mutex_t os_fast_mutex_t; + +typedef struct os_event_struct os_event_struct_t; +typedef os_event_struct_t* os_event_t; + +struct os_event_struct { + os_fast_mutex_t os_mutex; /* this mutex protects the next + fields */ + ibool is_set; /* this is TRUE when the event is + in the signaled state, i.e., a thread + does not stop if it tries to wait for + this event */ + ib_int64_t signal_count; /* this is incremented each time + the event becomes signaled */ + pthread_cond_t cond_var; /* condition variable is used in + waiting for the event */ + UT_LIST_NODE_T(os_event_struct_t) os_event_list; + /* list of all created events */ +}; +#endif + +typedef struct os_mutex_struct os_mutex_str_t; +typedef os_mutex_str_t* os_mutex_t; + +#define OS_SYNC_INFINITE_TIME ((ulint)(-1)) + +#define OS_SYNC_TIME_EXCEEDED 1 + +/* Mutex protecting counts and the event and OS 'slow' mutex lists */ +extern os_mutex_t os_sync_mutex; + +/* This is incremented by 1 in os_thread_create and decremented by 1 in +os_thread_exit */ +extern ulint os_thread_count; + +extern ulint os_event_count; +extern ulint os_mutex_count; +extern ulint os_fast_mutex_count; + +/************************************************************* +Initializes global event and OS 'slow' mutex lists. */ +UNIV_INTERN +void +os_sync_init(void); +/*==============*/ +/************************************************************* +Frees created events and OS 'slow' mutexes. */ +UNIV_INTERN +void +os_sync_free(void); +/*==============*/ +/************************************************************* +Creates an event semaphore, i.e., a semaphore which may just have two states: +signaled and nonsignaled. The created event is manual reset: it must be reset +explicitly by calling sync_os_reset_event. */ +UNIV_INTERN +os_event_t +os_event_create( +/*============*/ + /* out: the event handle */ + const char* name); /* in: the name of the event, if NULL + the event is created without a name */ +#ifdef __WIN__ +/************************************************************* +Creates an auto-reset event semaphore, i.e., an event which is automatically +reset when a single thread is released. Works only in Windows. */ +UNIV_INTERN +os_event_t +os_event_create_auto( +/*=================*/ + /* out: the event handle */ + const char* name); /* in: the name of the event, if NULL + the event is created without a name */ +#endif +/************************************************************** +Sets an event semaphore to the signaled state: lets waiting threads +proceed. */ +UNIV_INTERN +void +os_event_set( +/*=========*/ + os_event_t event); /* in: event to set */ +/************************************************************** +Resets an event semaphore to the nonsignaled state. Waiting threads will +stop to wait for the event. +The return value should be passed to os_even_wait_low() if it is desired +that this thread should not wait in case of an intervening call to +os_event_set() between this os_event_reset() and the +os_event_wait_low() call. See comments for os_event_wait_low(). */ +UNIV_INTERN +ib_int64_t +os_event_reset( +/*===========*/ + os_event_t event); /* in: event to reset */ +/************************************************************** +Frees an event object. */ +UNIV_INTERN +void +os_event_free( +/*==========*/ + os_event_t event); /* in: event to free */ + +/************************************************************** +Waits for an event object until it is in the signaled state. If +srv_shutdown_state == SRV_SHUTDOWN_EXIT_THREADS this also exits the +waiting thread when the event becomes signaled (or immediately if the +event is already in the signaled state). + +Typically, if the event has been signalled after the os_event_reset() +we'll return immediately because event->is_set == TRUE. +There are, however, situations (e.g.: sync_array code) where we may +lose this information. For example: + +thread A calls os_event_reset() +thread B calls os_event_set() [event->is_set == TRUE] +thread C calls os_event_reset() [event->is_set == FALSE] +thread A calls os_event_wait() [infinite wait!] +thread C calls os_event_wait() [infinite wait!] + +Where such a scenario is possible, to avoid infinite wait, the +value returned by os_event_reset() should be passed in as +reset_sig_count. */ +UNIV_INTERN +void +os_event_wait_low( +/*==============*/ + os_event_t event, /* in: event to wait */ + ib_int64_t reset_sig_count);/* in: zero or the value + returned by previous call of + os_event_reset(). */ + +#define os_event_wait(event) os_event_wait_low(event, 0) + +/************************************************************** +Waits for an event object until it is in the signaled state or +a timeout is exceeded. In Unix the timeout is always infinite. */ +UNIV_INTERN +ulint +os_event_wait_time( +/*===============*/ + /* out: 0 if success, + OS_SYNC_TIME_EXCEEDED if timeout + was exceeded */ + os_event_t event, /* in: event to wait */ + ulint time); /* in: timeout in microseconds, or + OS_SYNC_INFINITE_TIME */ +#ifdef __WIN__ +/************************************************************** +Waits for any event in an OS native event array. Returns if even a single +one is signaled or becomes signaled. */ +UNIV_INTERN +ulint +os_event_wait_multiple( +/*===================*/ + /* out: index of the event + which was signaled */ + ulint n, /* in: number of events in the + array */ + os_native_event_t* native_event_array); + /* in: pointer to an array of event + handles */ +#endif +/************************************************************* +Creates an operating system mutex semaphore. Because these are slow, the +mutex semaphore of InnoDB itself (mutex_t) should be used where possible. */ +UNIV_INTERN +os_mutex_t +os_mutex_create( +/*============*/ + /* out: the mutex handle */ + const char* name); /* in: the name of the mutex, if NULL + the mutex is created without a name */ +/************************************************************** +Acquires ownership of a mutex semaphore. */ +UNIV_INTERN +void +os_mutex_enter( +/*===========*/ + os_mutex_t mutex); /* in: mutex to acquire */ +/************************************************************** +Releases ownership of a mutex. */ +UNIV_INTERN +void +os_mutex_exit( +/*==========*/ + os_mutex_t mutex); /* in: mutex to release */ +/************************************************************** +Frees an mutex object. */ +UNIV_INTERN +void +os_mutex_free( +/*==========*/ + os_mutex_t mutex); /* in: mutex to free */ +/************************************************************** +Acquires ownership of a fast mutex. Currently in Windows this is the same +as os_fast_mutex_lock! */ +UNIV_INLINE +ulint +os_fast_mutex_trylock( +/*==================*/ + /* out: 0 if success, != 0 if + was reserved by another + thread */ + os_fast_mutex_t* fast_mutex); /* in: mutex to acquire */ +/************************************************************** +Releases ownership of a fast mutex. */ +UNIV_INTERN +void +os_fast_mutex_unlock( +/*=================*/ + os_fast_mutex_t* fast_mutex); /* in: mutex to release */ +/************************************************************* +Initializes an operating system fast mutex semaphore. */ +UNIV_INTERN +void +os_fast_mutex_init( +/*===============*/ + os_fast_mutex_t* fast_mutex); /* in: fast mutex */ +/************************************************************** +Acquires ownership of a fast mutex. */ +UNIV_INTERN +void +os_fast_mutex_lock( +/*===============*/ + os_fast_mutex_t* fast_mutex); /* in: mutex to acquire */ +/************************************************************** +Frees an mutex object. */ +UNIV_INTERN +void +os_fast_mutex_free( +/*===============*/ + os_fast_mutex_t* fast_mutex); /* in: mutex to free */ + +#ifdef HAVE_GCC_ATOMIC_BUILTINS +/************************************************************** +Atomic compare-and-swap for InnoDB. Currently requires GCC atomic builtins. +Returns true if swapped, ptr is pointer to target, old_val is value to +compare to, new_val is the value to swap in. */ +#define os_compare_and_swap(ptr, old_val, new_val) \ + __sync_bool_compare_and_swap(ptr, old_val, new_val) + +/************************************************************** +Atomic increment for InnoDB. Currently requires GCC atomic builtins. +Returns the resulting value, ptr is pointer to target, amount is the +amount of increment. */ +#define os_atomic_increment(ptr, amount) \ + __sync_add_and_fetch(ptr, amount) + +#endif /* HAVE_GCC_ATOMIC_BUILTINS */ + +#ifndef UNIV_NONINL +#include "os0sync.ic" +#endif + +#endif diff --git a/storage/xtradb/include/os0sync.ic b/storage/xtradb/include/os0sync.ic new file mode 100644 index 00000000000..5c03d184c7c --- /dev/null +++ b/storage/xtradb/include/os0sync.ic @@ -0,0 +1,62 @@ +/***************************************************************************** + +Copyright (c) 1995, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/****************************************************** +The interface to the operating system synchronization primitives. + +Created 9/6/1995 Heikki Tuuri +*******************************************************/ + +#ifdef __WIN__ +#include <winbase.h> +#endif + +/************************************************************** +Acquires ownership of a fast mutex. Currently in Windows this is the same +as os_fast_mutex_lock! */ +UNIV_INLINE +ulint +os_fast_mutex_trylock( +/*==================*/ + /* out: 0 if success, != 0 if + was reserved by another + thread */ + os_fast_mutex_t* fast_mutex) /* in: mutex to acquire */ +{ +#ifdef __WIN__ + EnterCriticalSection(fast_mutex); + + return(0); +#else +#if defined(UNIV_HOTBACKUP) && defined(UNIV_HPUX10) + /* Since the hot backup version is standalone, MySQL does not redefine + pthread_mutex_trylock for HP-UX-10.20, and consequently we must invert + the return value here */ + + return((ulint) (1 - pthread_mutex_trylock(fast_mutex))); +#else + /* NOTE that the MySQL my_pthread.h redefines pthread_mutex_trylock + so that it returns 0 on success. In the operating system + libraries, HP-UX-10.20 follows the old Posix 1003.4a Draft 4 and + returns 1 on success (but MySQL remaps that to 0), while Linux, + FreeBSD, Solaris, AIX, Tru64 Unix, HP-UX-11.0 return 0 on success. */ + + return((ulint) pthread_mutex_trylock(fast_mutex)); +#endif +#endif +} diff --git a/storage/xtradb/include/os0thread.h b/storage/xtradb/include/os0thread.h new file mode 100644 index 00000000000..863596bfa84 --- /dev/null +++ b/storage/xtradb/include/os0thread.h @@ -0,0 +1,158 @@ +/***************************************************************************** + +Copyright (c) 1995, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/****************************************************** +The interface to the operating system +process and thread control primitives + +Created 9/8/1995 Heikki Tuuri +*******************************************************/ + +#ifndef os0thread_h +#define os0thread_h + +#include "univ.i" + +/* Maximum number of threads which can be created in the program; +this is also the size of the wait slot array for MySQL threads which +can wait inside InnoDB */ + +#define OS_THREAD_MAX_N srv_max_n_threads + + +/* Possible fixed priorities for threads */ +#define OS_THREAD_PRIORITY_NONE 100 +#define OS_THREAD_PRIORITY_BACKGROUND 1 +#define OS_THREAD_PRIORITY_NORMAL 2 +#define OS_THREAD_PRIORITY_ABOVE_NORMAL 3 + +#ifdef __WIN__ +typedef void* os_thread_t; +typedef ulint os_thread_id_t; /* In Windows the thread id + is an unsigned long int */ +#else +typedef pthread_t os_thread_t; +typedef os_thread_t os_thread_id_t; /* In Unix we use the thread + handle itself as the id of + the thread */ +#endif + +/* Define a function pointer type to use in a typecast */ +typedef void* (*os_posix_f_t) (void*); + +/******************************************************************* +Compares two thread ids for equality. */ +UNIV_INTERN +ibool +os_thread_eq( +/*=========*/ + /* out: TRUE if equal */ + os_thread_id_t a, /* in: OS thread or thread id */ + os_thread_id_t b); /* in: OS thread or thread id */ +/******************************************************************** +Converts an OS thread id to a ulint. It is NOT guaranteed that the ulint is +unique for the thread though! */ +UNIV_INTERN +ulint +os_thread_pf( +/*=========*/ + /* out: unsigned long int */ + os_thread_id_t a); /* in: thread or thread id */ +/******************************************************************** +Creates a new thread of execution. The execution starts from +the function given. The start function takes a void* parameter +and returns a ulint. +NOTE: We count the number of threads in os_thread_exit(). A created +thread should always use that to exit and not use return() to exit. */ +UNIV_INTERN +os_thread_t +os_thread_create( +/*=============*/ + /* out: handle to the thread */ +#ifndef __WIN__ + os_posix_f_t start_f, +#else + ulint (*start_f)(void*), /* in: pointer to function + from which to start */ +#endif + void* arg, /* in: argument to start + function */ + os_thread_id_t* thread_id); /* out: id of the created + thread, or NULL */ + +/********************************************************************* +Exits the current thread. */ +UNIV_INTERN +void +os_thread_exit( +/*===========*/ + void* exit_value); /* in: exit value; in Windows this void* + is cast as a DWORD */ +/********************************************************************* +Returns the thread identifier of current thread. */ +UNIV_INTERN +os_thread_id_t +os_thread_get_curr_id(void); +/*========================*/ +/********************************************************************* +Returns handle to the current thread. */ +UNIV_INTERN +os_thread_t +os_thread_get_curr(void); +/*====================*/ +/********************************************************************* +Advises the os to give up remainder of the thread's time slice. */ +UNIV_INTERN +void +os_thread_yield(void); +/*=================*/ +/********************************************************************* +The thread sleeps at least the time given in microseconds. */ +UNIV_INTERN +void +os_thread_sleep( +/*============*/ + ulint tm); /* in: time in microseconds */ +/********************************************************************** +Gets a thread priority. */ +UNIV_INTERN +ulint +os_thread_get_priority( +/*===================*/ + /* out: priority */ + os_thread_t handle);/* in: OS handle to the thread */ +/********************************************************************** +Sets a thread priority. */ +UNIV_INTERN +void +os_thread_set_priority( +/*===================*/ + os_thread_t handle, /* in: OS handle to the thread */ + ulint pri); /* in: priority: one of OS_PRIORITY_... */ +/********************************************************************** +Gets the last operating system error code for the calling thread. */ +UNIV_INTERN +ulint +os_thread_get_last_error(void); +/*==========================*/ + +#ifndef UNIV_NONINL +#include "os0thread.ic" +#endif + +#endif diff --git a/storage/xtradb/include/os0thread.ic b/storage/xtradb/include/os0thread.ic new file mode 100644 index 00000000000..a86b203809c --- /dev/null +++ b/storage/xtradb/include/os0thread.ic @@ -0,0 +1,24 @@ +/***************************************************************************** + +Copyright (c) 1995, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/****************************************************** +The interface to the operating system +process and thread control primitives + +Created 9/8/1995 Heikki Tuuri +*******************************************************/ diff --git a/storage/xtradb/include/page0cur.h b/storage/xtradb/include/page0cur.h new file mode 100644 index 00000000000..960ecdddf4e --- /dev/null +++ b/storage/xtradb/include/page0cur.h @@ -0,0 +1,346 @@ +/***************************************************************************** + +Copyright (c) 1994, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/************************************************************************ +The page cursor + +Created 10/4/1994 Heikki Tuuri +*************************************************************************/ + +#ifndef page0cur_h +#define page0cur_h + +#include "univ.i" + +#include "buf0types.h" +#include "page0page.h" +#include "rem0rec.h" +#include "data0data.h" +#include "mtr0mtr.h" + + +#define PAGE_CUR_ADAPT + +/* Page cursor search modes; the values must be in this order! */ + +#define PAGE_CUR_UNSUPP 0 +#define PAGE_CUR_G 1 +#define PAGE_CUR_GE 2 +#define PAGE_CUR_L 3 +#define PAGE_CUR_LE 4 +/*#define PAGE_CUR_LE_OR_EXTENDS 5*/ /* This is a search mode used in + "column LIKE 'abc%' ORDER BY column DESC"; + we have to find strings which are <= 'abc' or + which extend it */ +#ifdef UNIV_SEARCH_DEBUG +# define PAGE_CUR_DBG 6 /* As PAGE_CUR_LE, but skips search shortcut */ +#endif /* UNIV_SEARCH_DEBUG */ + +#ifdef UNIV_DEBUG +/************************************************************* +Gets pointer to the page frame where the cursor is positioned. */ +UNIV_INLINE +page_t* +page_cur_get_page( +/*==============*/ + /* out: page */ + page_cur_t* cur); /* in: page cursor */ +/************************************************************* +Gets pointer to the buffer block where the cursor is positioned. */ +UNIV_INLINE +buf_block_t* +page_cur_get_block( +/*===============*/ + /* out: page */ + page_cur_t* cur); /* in: page cursor */ +/************************************************************* +Gets pointer to the page frame where the cursor is positioned. */ +UNIV_INLINE +page_zip_des_t* +page_cur_get_page_zip( +/*==================*/ + /* out: page */ + page_cur_t* cur); /* in: page cursor */ +/************************************************************* +Gets the record where the cursor is positioned. */ +UNIV_INLINE +rec_t* +page_cur_get_rec( +/*=============*/ + /* out: record */ + page_cur_t* cur); /* in: page cursor */ +#else /* UNIV_DEBUG */ +# define page_cur_get_page(cur) page_align((cur)->rec) +# define page_cur_get_block(cur) (cur)->block +# define page_cur_get_page_zip(cur) buf_block_get_page_zip((cur)->block) +# define page_cur_get_rec(cur) (cur)->rec +#endif /* UNIV_DEBUG */ +/************************************************************* +Sets the cursor object to point before the first user record +on the page. */ +UNIV_INLINE +void +page_cur_set_before_first( +/*======================*/ + const buf_block_t* block, /* in: index page */ + page_cur_t* cur); /* in: cursor */ +/************************************************************* +Sets the cursor object to point after the last user record on +the page. */ +UNIV_INLINE +void +page_cur_set_after_last( +/*====================*/ + const buf_block_t* block, /* in: index page */ + page_cur_t* cur); /* in: cursor */ +/************************************************************* +Returns TRUE if the cursor is before first user record on page. */ +UNIV_INLINE +ibool +page_cur_is_before_first( +/*=====================*/ + /* out: TRUE if at start */ + const page_cur_t* cur); /* in: cursor */ +/************************************************************* +Returns TRUE if the cursor is after last user record. */ +UNIV_INLINE +ibool +page_cur_is_after_last( +/*===================*/ + /* out: TRUE if at end */ + const page_cur_t* cur); /* in: cursor */ +/************************************************************** +Positions the cursor on the given record. */ +UNIV_INLINE +void +page_cur_position( +/*==============*/ + const rec_t* rec, /* in: record on a page */ + const buf_block_t* block, /* in: buffer block containing + the record */ + page_cur_t* cur); /* out: page cursor */ +/************************************************************** +Invalidates a page cursor by setting the record pointer NULL. */ +UNIV_INLINE +void +page_cur_invalidate( +/*================*/ + page_cur_t* cur); /* out: page cursor */ +/************************************************************** +Moves the cursor to the next record on page. */ +UNIV_INLINE +void +page_cur_move_to_next( +/*==================*/ + page_cur_t* cur); /* in/out: cursor; must not be after last */ +/************************************************************** +Moves the cursor to the previous record on page. */ +UNIV_INLINE +void +page_cur_move_to_prev( +/*==================*/ + page_cur_t* cur); /* in/out: cursor; not before first */ +/*************************************************************** +Inserts a record next to page cursor. Returns pointer to inserted record if +succeed, i.e., enough space available, NULL otherwise. The cursor stays at +the same logical position, but the physical position may change if it is +pointing to a compressed page that was reorganized. */ +UNIV_INLINE +rec_t* +page_cur_tuple_insert( +/*==================*/ + /* out: pointer to record if succeed, NULL + otherwise */ + page_cur_t* cursor, /* in/out: a page cursor */ + const dtuple_t* tuple, /* in: pointer to a data tuple */ + dict_index_t* index, /* in: record descriptor */ + ulint n_ext, /* in: number of externally stored columns */ + mtr_t* mtr); /* in: mini-transaction handle, or NULL */ +/*************************************************************** +Inserts a record next to page cursor. Returns pointer to inserted record if +succeed, i.e., enough space available, NULL otherwise. The cursor stays at +the same logical position, but the physical position may change if it is +pointing to a compressed page that was reorganized. */ +UNIV_INLINE +rec_t* +page_cur_rec_insert( +/*================*/ + /* out: pointer to record if succeed, NULL + otherwise */ + page_cur_t* cursor, /* in/out: a page cursor */ + const rec_t* rec, /* in: record to insert */ + dict_index_t* index, /* in: record descriptor */ + ulint* offsets,/* in/out: rec_get_offsets(rec, index) */ + mtr_t* mtr); /* in: mini-transaction handle, or NULL */ +/*************************************************************** +Inserts a record next to page cursor on an uncompressed page. +Returns pointer to inserted record if succeed, i.e., enough +space available, NULL otherwise. The cursor stays at the same position. */ +UNIV_INTERN +rec_t* +page_cur_insert_rec_low( +/*====================*/ + /* out: pointer to record if succeed, NULL + otherwise */ + rec_t* current_rec,/* in: pointer to current record after + which the new record is inserted */ + dict_index_t* index, /* in: record descriptor */ + const rec_t* rec, /* in: pointer to a physical record */ + ulint* offsets,/* in/out: rec_get_offsets(rec, index) */ + mtr_t* mtr); /* in: mini-transaction handle, or NULL */ +/*************************************************************** +Inserts a record next to page cursor on a compressed and uncompressed +page. Returns pointer to inserted record if succeed, i.e., +enough space available, NULL otherwise. +The cursor stays at the same position. */ +UNIV_INTERN +rec_t* +page_cur_insert_rec_zip( +/*====================*/ + /* out: pointer to record if succeed, NULL + otherwise */ + rec_t** current_rec,/* in/out: pointer to current record after + which the new record is inserted */ + buf_block_t* block, /* in: buffer block of *current_rec */ + dict_index_t* index, /* in: record descriptor */ + const rec_t* rec, /* in: pointer to a physical record */ + ulint* offsets,/* in/out: rec_get_offsets(rec, index) */ + mtr_t* mtr); /* in: mini-transaction handle, or NULL */ +/***************************************************************** +Copies records from page to a newly created page, from a given record onward, +including that record. Infimum and supremum records are not copied. */ +UNIV_INTERN +void +page_copy_rec_list_end_to_created_page( +/*===================================*/ + page_t* new_page, /* in/out: index page to copy to */ + rec_t* rec, /* in: first record to copy */ + dict_index_t* index, /* in: record descriptor */ + mtr_t* mtr); /* in: mtr */ +/*************************************************************** +Deletes a record at the page cursor. The cursor is moved to the +next record after the deleted one. */ +UNIV_INTERN +void +page_cur_delete_rec( +/*================*/ + page_cur_t* cursor, /* in/out: a page cursor */ + dict_index_t* index, /* in: record descriptor */ + const ulint* offsets,/* in: rec_get_offsets(cursor->rec, index) */ + mtr_t* mtr); /* in: mini-transaction handle */ +/******************************************************************** +Searches the right position for a page cursor. */ +UNIV_INLINE +ulint +page_cur_search( +/*============*/ + /* out: number of matched + fields on the left */ + const buf_block_t* block, /* in: buffer block */ + const dict_index_t* index, /* in: record descriptor */ + const dtuple_t* tuple, /* in: data tuple */ + ulint mode, /* in: PAGE_CUR_L, + PAGE_CUR_LE, PAGE_CUR_G, or + PAGE_CUR_GE */ + page_cur_t* cursor);/* out: page cursor */ +/******************************************************************** +Searches the right position for a page cursor. */ +UNIV_INTERN +void +page_cur_search_with_match( +/*=======================*/ + const buf_block_t* block, /* in: buffer block */ + const dict_index_t* index, /* in: record descriptor */ + const dtuple_t* tuple, /* in: data tuple */ + ulint mode, /* in: PAGE_CUR_L, + PAGE_CUR_LE, PAGE_CUR_G, or + PAGE_CUR_GE */ + ulint* iup_matched_fields, + /* in/out: already matched + fields in upper limit record */ + ulint* iup_matched_bytes, + /* in/out: already matched + bytes in a field not yet + completely matched */ + ulint* ilow_matched_fields, + /* in/out: already matched + fields in lower limit record */ + ulint* ilow_matched_bytes, + /* in/out: already matched + bytes in a field not yet + completely matched */ + page_cur_t* cursor);/* out: page cursor */ +/*************************************************************** +Positions a page cursor on a randomly chosen user record on a page. If there +are no user records, sets the cursor on the infimum record. */ +UNIV_INTERN +void +page_cur_open_on_rnd_user_rec( +/*==========================*/ + buf_block_t* block, /* in: page */ + page_cur_t* cursor);/* out: page cursor */ +/*************************************************************** +Parses a log record of a record insert on a page. */ +UNIV_INTERN +byte* +page_cur_parse_insert_rec( +/*======================*/ + /* out: end of log record or NULL */ + ibool is_short,/* in: TRUE if short inserts */ + byte* ptr, /* in: buffer */ + byte* end_ptr,/* in: buffer end */ + buf_block_t* block, /* in: page or NULL */ + dict_index_t* index, /* in: record descriptor */ + mtr_t* mtr); /* in: mtr or NULL */ +/************************************************************** +Parses a log record of copying a record list end to a new created page. */ +UNIV_INTERN +byte* +page_parse_copy_rec_list_to_created_page( +/*=====================================*/ + /* out: end of log record or NULL */ + byte* ptr, /* in: buffer */ + byte* end_ptr,/* in: buffer end */ + buf_block_t* block, /* in: page or NULL */ + dict_index_t* index, /* in: record descriptor */ + mtr_t* mtr); /* in: mtr or NULL */ +/*************************************************************** +Parses log record of a record delete on a page. */ +UNIV_INTERN +byte* +page_cur_parse_delete_rec( +/*======================*/ + /* out: pointer to record end or NULL */ + byte* ptr, /* in: buffer */ + byte* end_ptr,/* in: buffer end */ + buf_block_t* block, /* in: page or NULL */ + dict_index_t* index, /* in: record descriptor */ + mtr_t* mtr); /* in: mtr or NULL */ + +/* Index page cursor */ + +struct page_cur_struct{ + byte* rec; /* pointer to a record on page */ + buf_block_t* block; /* pointer to the block containing rec */ +}; + +#ifndef UNIV_NONINL +#include "page0cur.ic" +#endif + +#endif diff --git a/storage/xtradb/include/page0cur.ic b/storage/xtradb/include/page0cur.ic new file mode 100644 index 00000000000..9cf10ea5e3f --- /dev/null +++ b/storage/xtradb/include/page0cur.ic @@ -0,0 +1,300 @@ +/***************************************************************************** + +Copyright (c) 1994, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/************************************************************************ +The page cursor + +Created 10/4/1994 Heikki Tuuri +*************************************************************************/ + +#include "page0page.h" +#include "buf0types.h" + +#ifdef UNIV_DEBUG +/************************************************************* +Gets pointer to the page frame where the cursor is positioned. */ +UNIV_INLINE +page_t* +page_cur_get_page( +/*==============*/ + /* out: page */ + page_cur_t* cur) /* in: page cursor */ +{ + ut_ad(cur); + ut_ad(page_align(cur->rec) == cur->block->frame); + + return(page_align(cur->rec)); +} + +/************************************************************* +Gets pointer to the buffer block where the cursor is positioned. */ +UNIV_INLINE +buf_block_t* +page_cur_get_block( +/*===============*/ + /* out: page */ + page_cur_t* cur) /* in: page cursor */ +{ + ut_ad(cur); + ut_ad(page_align(cur->rec) == cur->block->frame); + return(cur->block); +} + +/************************************************************* +Gets pointer to the page frame where the cursor is positioned. */ +UNIV_INLINE +page_zip_des_t* +page_cur_get_page_zip( +/*==================*/ + /* out: page */ + page_cur_t* cur) /* in: page cursor */ +{ + return(buf_block_get_page_zip(page_cur_get_block(cur))); +} + +/************************************************************* +Gets the record where the cursor is positioned. */ +UNIV_INLINE +rec_t* +page_cur_get_rec( +/*=============*/ + /* out: record */ + page_cur_t* cur) /* in: page cursor */ +{ + ut_ad(cur); + ut_ad(page_align(cur->rec) == cur->block->frame); + + return(cur->rec); +} +#endif /* UNIV_DEBUG */ + +/************************************************************* +Sets the cursor object to point before the first user record +on the page. */ +UNIV_INLINE +void +page_cur_set_before_first( +/*======================*/ + const buf_block_t* block, /* in: index page */ + page_cur_t* cur) /* in: cursor */ +{ + cur->block = (buf_block_t*) block; + cur->rec = page_get_infimum_rec(buf_block_get_frame(cur->block)); +} + +/************************************************************* +Sets the cursor object to point after the last user record on +the page. */ +UNIV_INLINE +void +page_cur_set_after_last( +/*====================*/ + const buf_block_t* block, /* in: index page */ + page_cur_t* cur) /* in: cursor */ +{ + cur->block = (buf_block_t*) block; + cur->rec = page_get_supremum_rec(buf_block_get_frame(cur->block)); +} + +/************************************************************* +Returns TRUE if the cursor is before first user record on page. */ +UNIV_INLINE +ibool +page_cur_is_before_first( +/*=====================*/ + /* out: TRUE if at start */ + const page_cur_t* cur) /* in: cursor */ +{ + ut_ad(cur); + ut_ad(page_align(cur->rec) == cur->block->frame); + return(page_rec_is_infimum(cur->rec)); +} + +/************************************************************* +Returns TRUE if the cursor is after last user record. */ +UNIV_INLINE +ibool +page_cur_is_after_last( +/*===================*/ + /* out: TRUE if at end */ + const page_cur_t* cur) /* in: cursor */ +{ + ut_ad(cur); + ut_ad(page_align(cur->rec) == cur->block->frame); + return(page_rec_is_supremum(cur->rec)); +} + +/************************************************************** +Positions the cursor on the given record. */ +UNIV_INLINE +void +page_cur_position( +/*==============*/ + const rec_t* rec, /* in: record on a page */ + const buf_block_t* block, /* in: buffer block containing + the record */ + page_cur_t* cur) /* out: page cursor */ +{ + ut_ad(rec && block && cur); + ut_ad(page_align(rec) == block->frame); + + cur->rec = (rec_t*) rec; + cur->block = (buf_block_t*) block; +} + +/************************************************************** +Invalidates a page cursor by setting the record pointer NULL. */ +UNIV_INLINE +void +page_cur_invalidate( +/*================*/ + page_cur_t* cur) /* out: page cursor */ +{ + ut_ad(cur); + + cur->rec = NULL; + cur->block = NULL; +} + +/************************************************************** +Moves the cursor to the next record on page. */ +UNIV_INLINE +void +page_cur_move_to_next( +/*==================*/ + page_cur_t* cur) /* in/out: cursor; must not be after last */ +{ + ut_ad(!page_cur_is_after_last(cur)); + + cur->rec = page_rec_get_next(cur->rec); +} + +/************************************************************** +Moves the cursor to the previous record on page. */ +UNIV_INLINE +void +page_cur_move_to_prev( +/*==================*/ + page_cur_t* cur) /* in/out: page cursor, not before first */ +{ + ut_ad(!page_cur_is_before_first(cur)); + + cur->rec = page_rec_get_prev(cur->rec); +} + +/******************************************************************** +Searches the right position for a page cursor. */ +UNIV_INLINE +ulint +page_cur_search( +/*============*/ + /* out: number of matched + fields on the left */ + const buf_block_t* block, /* in: buffer block */ + const dict_index_t* index, /* in: record descriptor */ + const dtuple_t* tuple, /* in: data tuple */ + ulint mode, /* in: PAGE_CUR_L, + PAGE_CUR_LE, PAGE_CUR_G, or + PAGE_CUR_GE */ + page_cur_t* cursor) /* out: page cursor */ +{ + ulint low_matched_fields = 0; + ulint low_matched_bytes = 0; + ulint up_matched_fields = 0; + ulint up_matched_bytes = 0; + + ut_ad(dtuple_check_typed(tuple)); + + page_cur_search_with_match(block, index, tuple, mode, + &up_matched_fields, + &up_matched_bytes, + &low_matched_fields, + &low_matched_bytes, + cursor); + return(low_matched_fields); +} + +/*************************************************************** +Inserts a record next to page cursor. Returns pointer to inserted record if +succeed, i.e., enough space available, NULL otherwise. The cursor stays at +the same logical position, but the physical position may change if it is +pointing to a compressed page that was reorganized. */ +UNIV_INLINE +rec_t* +page_cur_tuple_insert( +/*==================*/ + /* out: pointer to record if succeed, NULL + otherwise */ + page_cur_t* cursor, /* in/out: a page cursor */ + const dtuple_t* tuple, /* in: pointer to a data tuple */ + dict_index_t* index, /* in: record descriptor */ + ulint n_ext, /* in: number of externally stored columns */ + mtr_t* mtr) /* in: mini-transaction handle, or NULL */ +{ + mem_heap_t* heap; + ulint* offsets; + ulint size + = rec_get_converted_size(index, tuple, n_ext); + rec_t* rec; + + heap = mem_heap_create(size + + (4 + REC_OFFS_HEADER_SIZE + + dtuple_get_n_fields(tuple)) + * sizeof *offsets); + rec = rec_convert_dtuple_to_rec((byte*) mem_heap_alloc(heap, size), + index, tuple, n_ext); + offsets = rec_get_offsets(rec, index, NULL, ULINT_UNDEFINED, &heap); + + if (buf_block_get_page_zip(cursor->block)) { + rec = page_cur_insert_rec_zip(&cursor->rec, cursor->block, + index, rec, offsets, mtr); + } else { + rec = page_cur_insert_rec_low(cursor->rec, + index, rec, offsets, mtr); + } + + mem_heap_free(heap); + return(rec); +} + +/*************************************************************** +Inserts a record next to page cursor. Returns pointer to inserted record if +succeed, i.e., enough space available, NULL otherwise. The cursor stays at +the same logical position, but the physical position may change if it is +pointing to a compressed page that was reorganized. */ +UNIV_INLINE +rec_t* +page_cur_rec_insert( +/*================*/ + /* out: pointer to record if succeed, NULL + otherwise */ + page_cur_t* cursor, /* in/out: a page cursor */ + const rec_t* rec, /* in: record to insert */ + dict_index_t* index, /* in: record descriptor */ + ulint* offsets,/* in/out: rec_get_offsets(rec, index) */ + mtr_t* mtr) /* in: mini-transaction handle, or NULL */ +{ + if (buf_block_get_page_zip(cursor->block)) { + return(page_cur_insert_rec_zip(&cursor->rec, cursor->block, + index, rec, offsets, mtr)); + } else { + return(page_cur_insert_rec_low(cursor->rec, + index, rec, offsets, mtr)); + } +} + diff --git a/storage/xtradb/include/page0page.h b/storage/xtradb/include/page0page.h new file mode 100644 index 00000000000..e3de6901ee1 --- /dev/null +++ b/storage/xtradb/include/page0page.h @@ -0,0 +1,1019 @@ +/***************************************************************************** + +Copyright (c) 1994, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/****************************************************** +Index page routines + +Created 2/2/1994 Heikki Tuuri +*******************************************************/ + +#ifndef page0page_h +#define page0page_h + +#include "univ.i" + +#include "page0types.h" +#include "fil0fil.h" +#include "buf0buf.h" +#include "data0data.h" +#include "dict0dict.h" +#include "rem0rec.h" +#include "fsp0fsp.h" +#include "mtr0mtr.h" + +#ifdef UNIV_MATERIALIZE +#undef UNIV_INLINE +#define UNIV_INLINE +#endif + +/* PAGE HEADER + =========== + +Index page header starts at the first offset left free by the FIL-module */ + +typedef byte page_header_t; + +#define PAGE_HEADER FSEG_PAGE_DATA /* index page header starts at this + offset */ +/*-----------------------------*/ +#define PAGE_N_DIR_SLOTS 0 /* number of slots in page directory */ +#define PAGE_HEAP_TOP 2 /* pointer to record heap top */ +#define PAGE_N_HEAP 4 /* number of records in the heap, + bit 15=flag: new-style compact page format */ +#define PAGE_FREE 6 /* pointer to start of page free record list */ +#define PAGE_GARBAGE 8 /* number of bytes in deleted records */ +#define PAGE_LAST_INSERT 10 /* pointer to the last inserted record, or + NULL if this info has been reset by a delete, + for example */ +#define PAGE_DIRECTION 12 /* last insert direction: PAGE_LEFT, ... */ +#define PAGE_N_DIRECTION 14 /* number of consecutive inserts to the same + direction */ +#define PAGE_N_RECS 16 /* number of user records on the page */ +#define PAGE_MAX_TRX_ID 18 /* highest id of a trx which may have modified + a record on the page; a dulint; defined only + in secondary indexes; specifically, not in an + ibuf tree; NOTE: this may be modified only + when the thread has an x-latch to the page, + and ALSO an x-latch to btr_search_latch + if there is a hash index to the page! */ +#define PAGE_HEADER_PRIV_END 26 /* end of private data structure of the page + header which are set in a page create */ +/*----*/ +#define PAGE_LEVEL 26 /* level of the node in an index tree; the + leaf level is the level 0 */ +#define PAGE_INDEX_ID 28 /* index id where the page belongs */ +#define PAGE_BTR_SEG_LEAF 36 /* file segment header for the leaf pages in + a B-tree: defined only on the root page of a + B-tree, but not in the root of an ibuf tree */ +#define PAGE_BTR_IBUF_FREE_LIST PAGE_BTR_SEG_LEAF +#define PAGE_BTR_IBUF_FREE_LIST_NODE PAGE_BTR_SEG_LEAF + /* in the place of PAGE_BTR_SEG_LEAF and _TOP + there is a free list base node if the page is + the root page of an ibuf tree, and at the same + place is the free list node if the page is in + a free list */ +#define PAGE_BTR_SEG_TOP (36 + FSEG_HEADER_SIZE) + /* file segment header for the non-leaf pages + in a B-tree: defined only on the root page of + a B-tree, but not in the root of an ibuf + tree */ +/*----*/ +#define PAGE_DATA (PAGE_HEADER + 36 + 2 * FSEG_HEADER_SIZE) + /* start of data on the page */ + +#define PAGE_OLD_INFIMUM (PAGE_DATA + 1 + REC_N_OLD_EXTRA_BYTES) + /* offset of the page infimum record on an + old-style page */ +#define PAGE_OLD_SUPREMUM (PAGE_DATA + 2 + 2 * REC_N_OLD_EXTRA_BYTES + 8) + /* offset of the page supremum record on an + old-style page */ +#define PAGE_OLD_SUPREMUM_END (PAGE_OLD_SUPREMUM + 9) + /* offset of the page supremum record end on + an old-style page */ +#define PAGE_NEW_INFIMUM (PAGE_DATA + REC_N_NEW_EXTRA_BYTES) + /* offset of the page infimum record on a + new-style compact page */ +#define PAGE_NEW_SUPREMUM (PAGE_DATA + 2 * REC_N_NEW_EXTRA_BYTES + 8) + /* offset of the page supremum record on a + new-style compact page */ +#define PAGE_NEW_SUPREMUM_END (PAGE_NEW_SUPREMUM + 8) + /* offset of the page supremum record end on + a new-style compact page */ +/*-----------------------------*/ + +/* Heap numbers */ +#define PAGE_HEAP_NO_INFIMUM 0 /* page infimum */ +#define PAGE_HEAP_NO_SUPREMUM 1 /* page supremum */ +#define PAGE_HEAP_NO_USER_LOW 2 /* first user record in + creation (insertion) order, + not necessarily collation order; + this record may have been deleted */ + +/* Directions of cursor movement */ +#define PAGE_LEFT 1 +#define PAGE_RIGHT 2 +#define PAGE_SAME_REC 3 +#define PAGE_SAME_PAGE 4 +#define PAGE_NO_DIRECTION 5 + +/* PAGE DIRECTORY + ============== +*/ + +typedef byte page_dir_slot_t; +typedef page_dir_slot_t page_dir_t; + +/* Offset of the directory start down from the page end. We call the +slot with the highest file address directory start, as it points to +the first record in the list of records. */ +#define PAGE_DIR FIL_PAGE_DATA_END + +/* We define a slot in the page directory as two bytes */ +#define PAGE_DIR_SLOT_SIZE 2 + +/* The offset of the physically lower end of the directory, counted from +page end, when the page is empty */ +#define PAGE_EMPTY_DIR_START (PAGE_DIR + 2 * PAGE_DIR_SLOT_SIZE) + +/* The maximum and minimum number of records owned by a directory slot. The +number may drop below the minimum in the first and the last slot in the +directory. */ +#define PAGE_DIR_SLOT_MAX_N_OWNED 8 +#define PAGE_DIR_SLOT_MIN_N_OWNED 4 + +/**************************************************************** +Gets the start of a page. */ +UNIV_INLINE +page_t* +page_align( +/*=======*/ + /* out: start of the page */ + const void* ptr) /* in: pointer to page frame */ + __attribute__((const)); +/**************************************************************** +Gets the offset within a page. */ +UNIV_INLINE +ulint +page_offset( +/*========*/ + /* out: offset from the start of the page */ + const void* ptr) /* in: pointer to page frame */ + __attribute__((const)); +/***************************************************************** +Returns the max trx id field value. */ +UNIV_INLINE +dulint +page_get_max_trx_id( +/*================*/ + const page_t* page); /* in: page */ +/***************************************************************** +Sets the max trx id field value. */ +UNIV_INTERN +void +page_set_max_trx_id( +/*================*/ + buf_block_t* block, /* in/out: page */ + page_zip_des_t* page_zip,/* in/out: compressed page, or NULL */ + dulint trx_id);/* in: transaction id */ +/***************************************************************** +Sets the max trx id field value if trx_id is bigger than the previous +value. */ +UNIV_INLINE +void +page_update_max_trx_id( +/*===================*/ + buf_block_t* block, /* in/out: page */ + page_zip_des_t* page_zip,/* in/out: compressed page whose + uncompressed part will be updated, or NULL */ + dulint trx_id);/* in: transaction id */ +/***************************************************************** +Reads the given header field. */ +UNIV_INLINE +ulint +page_header_get_field( +/*==================*/ + const page_t* page, /* in: page */ + ulint field); /* in: PAGE_N_DIR_SLOTS, ... */ +/***************************************************************** +Sets the given header field. */ +UNIV_INLINE +void +page_header_set_field( +/*==================*/ + page_t* page, /* in/out: page */ + page_zip_des_t* page_zip,/* in/out: compressed page whose + uncompressed part will be updated, or NULL */ + ulint field, /* in: PAGE_N_DIR_SLOTS, ... */ + ulint val); /* in: value */ +/***************************************************************** +Returns the offset stored in the given header field. */ +UNIV_INLINE +ulint +page_header_get_offs( +/*=================*/ + /* out: offset from the start of the page, + or 0 */ + const page_t* page, /* in: page */ + ulint field) /* in: PAGE_FREE, ... */ + __attribute__((nonnull, pure)); + +/***************************************************************** +Returns the pointer stored in the given header field, or NULL. */ +#define page_header_get_ptr(page, field) \ + (page_header_get_offs(page, field) \ + ? page + page_header_get_offs(page, field) : NULL) +/***************************************************************** +Sets the pointer stored in the given header field. */ +UNIV_INLINE +void +page_header_set_ptr( +/*================*/ + page_t* page, /* in/out: page */ + page_zip_des_t* page_zip,/* in/out: compressed page whose + uncompressed part will be updated, or NULL */ + ulint field, /* in/out: PAGE_FREE, ... */ + const byte* ptr); /* in: pointer or NULL*/ +/***************************************************************** +Resets the last insert info field in the page header. Writes to mlog +about this operation. */ +UNIV_INLINE +void +page_header_reset_last_insert( +/*==========================*/ + page_t* page, /* in: page */ + page_zip_des_t* page_zip,/* in/out: compressed page whose + uncompressed part will be updated, or NULL */ + mtr_t* mtr); /* in: mtr */ +/**************************************************************** +Gets the offset of the first record on the page. */ +UNIV_INLINE +ulint +page_get_infimum_offset( +/*====================*/ + /* out: offset of the first record + in record list, relative from page */ + const page_t* page); /* in: page which must have record(s) */ +/**************************************************************** +Gets the offset of the last record on the page. */ +UNIV_INLINE +ulint +page_get_supremum_offset( +/*=====================*/ + /* out: offset of the last record in + record list, relative from page */ + const page_t* page); /* in: page which must have record(s) */ +#define page_get_infimum_rec(page) ((page) + page_get_infimum_offset(page)) +#define page_get_supremum_rec(page) ((page) + page_get_supremum_offset(page)) +/**************************************************************** +Returns the middle record of record list. If there are an even number +of records in the list, returns the first record of upper half-list. */ +UNIV_INTERN +rec_t* +page_get_middle_rec( +/*================*/ + /* out: middle record */ + page_t* page); /* in: page */ +/***************************************************************** +Compares a data tuple to a physical record. Differs from the function +cmp_dtuple_rec_with_match in the way that the record must reside on an +index page, and also page infimum and supremum records can be given in +the parameter rec. These are considered as the negative infinity and +the positive infinity in the alphabetical order. */ +UNIV_INLINE +int +page_cmp_dtuple_rec_with_match( +/*===========================*/ + /* out: 1, 0, -1, if dtuple is greater, equal, + less than rec, respectively, when only the + common first fields are compared */ + const dtuple_t* dtuple, /* in: data tuple */ + const rec_t* rec, /* in: physical record on a page; may also + be page infimum or supremum, in which case + matched-parameter values below are not + affected */ + const ulint* offsets,/* in: array returned by rec_get_offsets() */ + ulint* matched_fields, /* in/out: number of already completely + matched fields; when function returns + contains the value for current comparison */ + ulint* matched_bytes); /* in/out: number of already matched + bytes within the first field not completely + matched; when function returns contains the + value for current comparison */ +/***************************************************************** +Gets the page number. */ +UNIV_INLINE +ulint +page_get_page_no( +/*=============*/ + /* out: page number */ + const page_t* page); /* in: page */ +/***************************************************************** +Gets the tablespace identifier. */ +UNIV_INLINE +ulint +page_get_space_id( +/*==============*/ + /* out: space id */ + const page_t* page); /* in: page */ +/***************************************************************** +Gets the number of user records on page (the infimum and supremum records +are not user records). */ +UNIV_INLINE +ulint +page_get_n_recs( +/*============*/ + /* out: number of user records */ + const page_t* page); /* in: index page */ +/******************************************************************* +Returns the number of records before the given record in chain. +The number includes infimum and supremum records. */ +UNIV_INTERN +ulint +page_rec_get_n_recs_before( +/*=======================*/ + /* out: number of records */ + const rec_t* rec); /* in: the physical record */ +/***************************************************************** +Gets the number of records in the heap. */ +UNIV_INLINE +ulint +page_dir_get_n_heap( +/*================*/ + /* out: number of user records */ + const page_t* page); /* in: index page */ +/***************************************************************** +Sets the number of records in the heap. */ +UNIV_INLINE +void +page_dir_set_n_heap( +/*================*/ + page_t* page, /* in/out: index page */ + page_zip_des_t* page_zip,/* in/out: compressed page whose + uncompressed part will be updated, or NULL. + Note that the size of the dense page directory + in the compressed page trailer is + n_heap * PAGE_ZIP_DIR_SLOT_SIZE. */ + ulint n_heap);/* in: number of records */ +/***************************************************************** +Gets the number of dir slots in directory. */ +UNIV_INLINE +ulint +page_dir_get_n_slots( +/*=================*/ + /* out: number of slots */ + const page_t* page); /* in: index page */ +/***************************************************************** +Sets the number of dir slots in directory. */ +UNIV_INLINE +void +page_dir_set_n_slots( +/*=================*/ + page_t* page, /* in/out: page */ + page_zip_des_t* page_zip,/* in/out: compressed page whose + uncompressed part will be updated, or NULL */ + ulint n_slots);/* in: number of slots */ +#ifdef UNIV_DEBUG +/***************************************************************** +Gets pointer to nth directory slot. */ +UNIV_INLINE +page_dir_slot_t* +page_dir_get_nth_slot( +/*==================*/ + /* out: pointer to dir slot */ + const page_t* page, /* in: index page */ + ulint n); /* in: position */ +#else /* UNIV_DEBUG */ +# define page_dir_get_nth_slot(page, n) \ + ((page) + UNIV_PAGE_SIZE - PAGE_DIR \ + - (n + 1) * PAGE_DIR_SLOT_SIZE) +#endif /* UNIV_DEBUG */ +/****************************************************************** +Used to check the consistency of a record on a page. */ +UNIV_INLINE +ibool +page_rec_check( +/*===========*/ + /* out: TRUE if succeed */ + const rec_t* rec); /* in: record */ +/******************************************************************* +Gets the record pointed to by a directory slot. */ +UNIV_INLINE +const rec_t* +page_dir_slot_get_rec( +/*==================*/ + /* out: pointer to record */ + const page_dir_slot_t* slot); /* in: directory slot */ +/******************************************************************* +This is used to set the record offset in a directory slot. */ +UNIV_INLINE +void +page_dir_slot_set_rec( +/*==================*/ + page_dir_slot_t* slot, /* in: directory slot */ + rec_t* rec); /* in: record on the page */ +/******************************************************************* +Gets the number of records owned by a directory slot. */ +UNIV_INLINE +ulint +page_dir_slot_get_n_owned( +/*======================*/ + /* out: number of records */ + const page_dir_slot_t* slot); /* in: page directory slot */ +/******************************************************************* +This is used to set the owned records field of a directory slot. */ +UNIV_INLINE +void +page_dir_slot_set_n_owned( +/*======================*/ + page_dir_slot_t*slot, /* in/out: directory slot */ + page_zip_des_t* page_zip,/* in/out: compressed page, or NULL */ + ulint n); /* in: number of records owned by the slot */ +/**************************************************************** +Calculates the space reserved for directory slots of a given +number of records. The exact value is a fraction number +n * PAGE_DIR_SLOT_SIZE / PAGE_DIR_SLOT_MIN_N_OWNED, and it is +rounded upwards to an integer. */ +UNIV_INLINE +ulint +page_dir_calc_reserved_space( +/*=========================*/ + ulint n_recs); /* in: number of records */ +/******************************************************************* +Looks for the directory slot which owns the given record. */ +UNIV_INTERN +ulint +page_dir_find_owner_slot( +/*=====================*/ + /* out: the directory slot number */ + const rec_t* rec); /* in: the physical record */ +/**************************************************************** +Determine whether the page is in new-style compact format. */ +UNIV_INLINE +ulint +page_is_comp( +/*=========*/ + /* out: nonzero if the page is in compact + format, zero if it is in old-style format */ + const page_t* page); /* in: index page */ +/**************************************************************** +TRUE if the record is on a page in compact format. */ +UNIV_INLINE +ulint +page_rec_is_comp( +/*=============*/ + /* out: nonzero if in compact format */ + const rec_t* rec); /* in: record */ +/******************************************************************* +Returns the heap number of a record. */ +UNIV_INLINE +ulint +page_rec_get_heap_no( +/*=================*/ + /* out: heap number */ + const rec_t* rec); /* in: the physical record */ +/**************************************************************** +Determine whether the page is a B-tree leaf. */ +UNIV_INLINE +ibool +page_is_leaf( +/*=========*/ + /* out: TRUE if the page is a B-tree leaf */ + const page_t* page) /* in: page */ + __attribute__((nonnull, pure)); +/**************************************************************** +Gets the pointer to the next record on the page. */ +UNIV_INLINE +const rec_t* +page_rec_get_next_low( +/*==================*/ + /* out: pointer to next record */ + const rec_t* rec, /* in: pointer to record */ + ulint comp); /* in: nonzero=compact page layout */ +/**************************************************************** +Gets the pointer to the next record on the page. */ +UNIV_INLINE +rec_t* +page_rec_get_next( +/*==============*/ + /* out: pointer to next record */ + rec_t* rec); /* in: pointer to record */ +/**************************************************************** +Gets the pointer to the next record on the page. */ +UNIV_INLINE +const rec_t* +page_rec_get_next_const( +/*====================*/ + /* out: pointer to next record */ + const rec_t* rec); /* in: pointer to record */ +/**************************************************************** +Sets the pointer to the next record on the page. */ +UNIV_INLINE +void +page_rec_set_next( +/*==============*/ + rec_t* rec, /* in: pointer to record, + must not be page supremum */ + rec_t* next); /* in: pointer to next record, + must not be page infimum */ +/**************************************************************** +Gets the pointer to the previous record. */ +UNIV_INLINE +const rec_t* +page_rec_get_prev_const( +/*====================*/ + /* out: pointer to previous record */ + const rec_t* rec); /* in: pointer to record, must not be page + infimum */ +/**************************************************************** +Gets the pointer to the previous record. */ +UNIV_INLINE +rec_t* +page_rec_get_prev( +/*==============*/ + /* out: pointer to previous record */ + rec_t* rec); /* in: pointer to record, + must not be page infimum */ +/**************************************************************** +TRUE if the record is a user record on the page. */ +UNIV_INLINE +ibool +page_rec_is_user_rec_low( +/*=====================*/ + /* out: TRUE if a user record */ + ulint offset) /* in: record offset on page */ + __attribute__((const)); +/**************************************************************** +TRUE if the record is the supremum record on a page. */ +UNIV_INLINE +ibool +page_rec_is_supremum_low( +/*=====================*/ + /* out: TRUE if the supremum record */ + ulint offset) /* in: record offset on page */ + __attribute__((const)); +/**************************************************************** +TRUE if the record is the infimum record on a page. */ +UNIV_INLINE +ibool +page_rec_is_infimum_low( +/*====================*/ + /* out: TRUE if the infimum record */ + ulint offset) /* in: record offset on page */ + __attribute__((const)); + +/**************************************************************** +TRUE if the record is a user record on the page. */ +UNIV_INLINE +ibool +page_rec_is_user_rec( +/*=================*/ + /* out: TRUE if a user record */ + const rec_t* rec) /* in: record */ + __attribute__((const)); +/**************************************************************** +TRUE if the record is the supremum record on a page. */ +UNIV_INLINE +ibool +page_rec_is_supremum( +/*=================*/ + /* out: TRUE if the supremum record */ + const rec_t* rec) /* in: record */ + __attribute__((const)); + +/**************************************************************** +TRUE if the record is the infimum record on a page. */ +UNIV_INLINE +ibool +page_rec_is_infimum( +/*================*/ + /* out: TRUE if the infimum record */ + const rec_t* rec) /* in: record */ + __attribute__((const)); +/******************************************************************* +Looks for the record which owns the given record. */ +UNIV_INLINE +rec_t* +page_rec_find_owner_rec( +/*====================*/ + /* out: the owner record */ + rec_t* rec); /* in: the physical record */ +/*************************************************************************** +This is a low-level operation which is used in a database index creation +to update the page number of a created B-tree to a data dictionary +record. */ +UNIV_INTERN +void +page_rec_write_index_page_no( +/*=========================*/ + rec_t* rec, /* in: record to update */ + ulint i, /* in: index of the field to update */ + ulint page_no,/* in: value to write */ + mtr_t* mtr); /* in: mtr */ +/**************************************************************** +Returns the maximum combined size of records which can be inserted on top +of record heap. */ +UNIV_INLINE +ulint +page_get_max_insert_size( +/*=====================*/ + /* out: maximum combined size for + inserted records */ + const page_t* page, /* in: index page */ + ulint n_recs);/* in: number of records */ +/**************************************************************** +Returns the maximum combined size of records which can be inserted on top +of record heap if page is first reorganized. */ +UNIV_INLINE +ulint +page_get_max_insert_size_after_reorganize( +/*======================================*/ + /* out: maximum combined size for + inserted records */ + const page_t* page, /* in: index page */ + ulint n_recs);/* in: number of records */ +/***************************************************************** +Calculates free space if a page is emptied. */ +UNIV_INLINE +ulint +page_get_free_space_of_empty( +/*=========================*/ + /* out: free space */ + ulint comp) /* in: nonzero=compact page format */ + __attribute__((const)); +/************************************************************** +Returns the base extra size of a physical record. This is the +size of the fixed header, independent of the record size. */ +UNIV_INLINE +ulint +page_rec_get_base_extra_size( +/*=========================*/ + /* out: REC_N_NEW_EXTRA_BYTES + or REC_N_OLD_EXTRA_BYTES */ + const rec_t* rec); /* in: physical record */ +/**************************************************************** +Returns the sum of the sizes of the records in the record list +excluding the infimum and supremum records. */ +UNIV_INLINE +ulint +page_get_data_size( +/*===============*/ + /* out: data in bytes */ + const page_t* page); /* in: index page */ +/**************************************************************** +Allocates a block of memory from the head of the free list +of an index page. */ +UNIV_INLINE +void +page_mem_alloc_free( +/*================*/ + page_t* page, /* in/out: index page */ + page_zip_des_t* page_zip,/* in/out: compressed page with enough + space available for inserting the record, + or NULL */ + rec_t* next_rec,/* in: pointer to the new head of the + free record list */ + ulint need); /* in: number of bytes allocated */ +/**************************************************************** +Allocates a block of memory from the heap of an index page. */ +UNIV_INTERN +byte* +page_mem_alloc_heap( +/*================*/ + /* out: pointer to start of allocated + buffer, or NULL if allocation fails */ + page_t* page, /* in/out: index page */ + page_zip_des_t* page_zip,/* in/out: compressed page with enough + space available for inserting the record, + or NULL */ + ulint need, /* in: total number of bytes needed */ + ulint* heap_no);/* out: this contains the heap number + of the allocated record + if allocation succeeds */ +/**************************************************************** +Puts a record to free list. */ +UNIV_INLINE +void +page_mem_free( +/*==========*/ + page_t* page, /* in/out: index page */ + page_zip_des_t* page_zip,/* in/out: compressed page, or NULL */ + rec_t* rec, /* in: pointer to the (origin of) record */ + dict_index_t* index, /* in: index of rec */ + const ulint* offsets);/* in: array returned by rec_get_offsets() */ +/************************************************************** +Create an uncompressed B-tree index page. */ +UNIV_INTERN +page_t* +page_create( +/*========*/ + /* out: pointer to the page */ + buf_block_t* block, /* in: a buffer block where the + page is created */ + mtr_t* mtr, /* in: mini-transaction handle */ + ulint comp); /* in: nonzero=compact page format */ +/************************************************************** +Create a compressed B-tree index page. */ +UNIV_INTERN +page_t* +page_create_zip( +/*============*/ + /* out: pointer to the page */ + buf_block_t* block, /* in/out: a buffer frame where the + page is created */ + dict_index_t* index, /* in: the index of the page */ + ulint level, /* in: the B-tree level of the page */ + mtr_t* mtr); /* in: mini-transaction handle */ + +/***************************************************************** +Differs from page_copy_rec_list_end, because this function does not +touch the lock table and max trx id on page or compress the page. */ +UNIV_INTERN +void +page_copy_rec_list_end_no_locks( +/*============================*/ + buf_block_t* new_block, /* in: index page to copy to */ + buf_block_t* block, /* in: index page of rec */ + rec_t* rec, /* in: record on page */ + dict_index_t* index, /* in: record descriptor */ + mtr_t* mtr); /* in: mtr */ +/***************************************************************** +Copies records from page to new_page, from the given record onward, +including that record. Infimum and supremum records are not copied. +The records are copied to the start of the record list on new_page. */ +UNIV_INTERN +rec_t* +page_copy_rec_list_end( +/*===================*/ + /* out: pointer to the original + successor of the infimum record + on new_page, or NULL on zip overflow + (new_block will be decompressed) */ + buf_block_t* new_block, /* in/out: index page to copy to */ + buf_block_t* block, /* in: index page containing rec */ + rec_t* rec, /* in: record on page */ + dict_index_t* index, /* in: record descriptor */ + mtr_t* mtr) /* in: mtr */ + __attribute__((nonnull)); +/***************************************************************** +Copies records from page to new_page, up to the given record, NOT +including that record. Infimum and supremum records are not copied. +The records are copied to the end of the record list on new_page. */ +UNIV_INTERN +rec_t* +page_copy_rec_list_start( +/*=====================*/ + /* out: pointer to the original + predecessor of the supremum record + on new_page, or NULL on zip overflow + (new_block will be decompressed) */ + buf_block_t* new_block, /* in/out: index page to copy to */ + buf_block_t* block, /* in: index page containing rec */ + rec_t* rec, /* in: record on page */ + dict_index_t* index, /* in: record descriptor */ + mtr_t* mtr) /* in: mtr */ + __attribute__((nonnull)); +/***************************************************************** +Deletes records from a page from a given record onward, including that record. +The infimum and supremum records are not deleted. */ +UNIV_INTERN +void +page_delete_rec_list_end( +/*=====================*/ + rec_t* rec, /* in: pointer to record on page */ + buf_block_t* block, /* in: buffer block of the page */ + dict_index_t* index, /* in: record descriptor */ + ulint n_recs, /* in: number of records to delete, + or ULINT_UNDEFINED if not known */ + ulint size, /* in: the sum of the sizes of the + records in the end of the chain to + delete, or ULINT_UNDEFINED if not known */ + mtr_t* mtr) /* in: mtr */ + __attribute__((nonnull)); +/***************************************************************** +Deletes records from page, up to the given record, NOT including +that record. Infimum and supremum records are not deleted. */ +UNIV_INTERN +void +page_delete_rec_list_start( +/*=======================*/ + rec_t* rec, /* in: record on page */ + buf_block_t* block, /* in: buffer block of the page */ + dict_index_t* index, /* in: record descriptor */ + mtr_t* mtr) /* in: mtr */ + __attribute__((nonnull)); +/***************************************************************** +Moves record list end to another page. Moved records include +split_rec. */ +UNIV_INTERN +ibool +page_move_rec_list_end( +/*===================*/ + /* out: TRUE on success; FALSE on + compression failure + (new_block will be decompressed) */ + buf_block_t* new_block, /* in/out: index page where to move */ + buf_block_t* block, /* in: index page from where to move */ + rec_t* split_rec, /* in: first record to move */ + dict_index_t* index, /* in: record descriptor */ + mtr_t* mtr) /* in: mtr */ + __attribute__((nonnull(1, 2, 4, 5))); +/***************************************************************** +Moves record list start to another page. Moved records do not include +split_rec. */ +UNIV_INTERN +ibool +page_move_rec_list_start( +/*=====================*/ + /* out: TRUE on success; FALSE on + compression failure */ + buf_block_t* new_block, /* in/out: index page where to move */ + buf_block_t* block, /* in/out: page containing split_rec */ + rec_t* split_rec, /* in: first record not to move */ + dict_index_t* index, /* in: record descriptor */ + mtr_t* mtr) /* in: mtr */ + __attribute__((nonnull(1, 2, 4, 5))); +/******************************************************************** +Splits a directory slot which owns too many records. */ +UNIV_INTERN +void +page_dir_split_slot( +/*================*/ + page_t* page, /* in: index page */ + page_zip_des_t* page_zip,/* in/out: compressed page whose + uncompressed part will be written, or NULL */ + ulint slot_no)/* in: the directory slot */ + __attribute__((nonnull(1))); +/***************************************************************** +Tries to balance the given directory slot with too few records +with the upper neighbor, so that there are at least the minimum number +of records owned by the slot; this may result in the merging of +two slots. */ +UNIV_INTERN +void +page_dir_balance_slot( +/*==================*/ + page_t* page, /* in/out: index page */ + page_zip_des_t* page_zip,/* in/out: compressed page, or NULL */ + ulint slot_no)/* in: the directory slot */ + __attribute__((nonnull(1))); +/************************************************************** +Parses a log record of a record list end or start deletion. */ +UNIV_INTERN +byte* +page_parse_delete_rec_list( +/*=======================*/ + /* out: end of log record or NULL */ + byte type, /* in: MLOG_LIST_END_DELETE, + MLOG_LIST_START_DELETE, + MLOG_COMP_LIST_END_DELETE or + MLOG_COMP_LIST_START_DELETE */ + byte* ptr, /* in: buffer */ + byte* end_ptr,/* in: buffer end */ + buf_block_t* block, /* in/out: buffer block or NULL */ + dict_index_t* index, /* in: record descriptor */ + mtr_t* mtr); /* in: mtr or NULL */ +/*************************************************************** +Parses a redo log record of creating a page. */ +UNIV_INTERN +byte* +page_parse_create( +/*==============*/ + /* out: end of log record or NULL */ + byte* ptr, /* in: buffer */ + byte* end_ptr,/* in: buffer end */ + ulint comp, /* in: nonzero=compact page format */ + buf_block_t* block, /* in: block or NULL */ + mtr_t* mtr); /* in: mtr or NULL */ +/**************************************************************** +Prints record contents including the data relevant only in +the index page context. */ +UNIV_INTERN +void +page_rec_print( +/*===========*/ + const rec_t* rec, /* in: physical record */ + const ulint* offsets);/* in: record descriptor */ +/******************************************************************* +This is used to print the contents of the directory for +debugging purposes. */ +UNIV_INTERN +void +page_dir_print( +/*===========*/ + page_t* page, /* in: index page */ + ulint pr_n); /* in: print n first and n last entries */ +/******************************************************************* +This is used to print the contents of the page record list for +debugging purposes. */ +UNIV_INTERN +void +page_print_list( +/*============*/ + buf_block_t* block, /* in: index page */ + dict_index_t* index, /* in: dictionary index of the page */ + ulint pr_n); /* in: print n first and n last entries */ +/******************************************************************* +Prints the info in a page header. */ +UNIV_INTERN +void +page_header_print( +/*==============*/ + const page_t* page); +/******************************************************************* +This is used to print the contents of the page for +debugging purposes. */ +UNIV_INTERN +void +page_print( +/*=======*/ + buf_block_t* block, /* in: index page */ + dict_index_t* index, /* in: dictionary index of the page */ + ulint dn, /* in: print dn first and last entries + in directory */ + ulint rn); /* in: print rn first and last records + in directory */ +/******************************************************************* +The following is used to validate a record on a page. This function +differs from rec_validate as it can also check the n_owned field and +the heap_no field. */ +UNIV_INTERN +ibool +page_rec_validate( +/*==============*/ + /* out: TRUE if ok */ + rec_t* rec, /* in: physical record */ + const ulint* offsets);/* in: array returned by rec_get_offsets() */ +/******************************************************************* +Checks that the first directory slot points to the infimum record and +the last to the supremum. This function is intended to track if the +bug fixed in 4.0.14 has caused corruption to users' databases. */ +UNIV_INTERN +void +page_check_dir( +/*===========*/ + const page_t* page); /* in: index page */ +/******************************************************************* +This function checks the consistency of an index page when we do not +know the index. This is also resilient so that this should never crash +even if the page is total garbage. */ +UNIV_INTERN +ibool +page_simple_validate_old( +/*=====================*/ + /* out: TRUE if ok */ + page_t* page); /* in: old-style index page */ +/******************************************************************* +This function checks the consistency of an index page when we do not +know the index. This is also resilient so that this should never crash +even if the page is total garbage. */ +UNIV_INTERN +ibool +page_simple_validate_new( +/*=====================*/ + /* out: TRUE if ok */ + page_t* block); /* in: new-style index page */ +/******************************************************************* +This function checks the consistency of an index page. */ +UNIV_INTERN +ibool +page_validate( +/*==========*/ + /* out: TRUE if ok */ + page_t* page, /* in: index page */ + dict_index_t* index); /* in: data dictionary index containing + the page record type definition */ +/******************************************************************* +Looks in the page record list for a record with the given heap number. */ + +const rec_t* +page_find_rec_with_heap_no( +/*=======================*/ + /* out: record, NULL if not found */ + const page_t* page, /* in: index page */ + ulint heap_no);/* in: heap number */ + +#ifdef UNIV_MATERIALIZE +#undef UNIV_INLINE +#define UNIV_INLINE UNIV_INLINE_ORIGINAL +#endif + +#ifndef UNIV_NONINL +#include "page0page.ic" +#endif + +#endif diff --git a/storage/xtradb/include/page0page.ic b/storage/xtradb/include/page0page.ic new file mode 100644 index 00000000000..df0f6f8b360 --- /dev/null +++ b/storage/xtradb/include/page0page.ic @@ -0,0 +1,1060 @@ +/***************************************************************************** + +Copyright (c) 1994, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/****************************************************** +Index page routines + +Created 2/2/1994 Heikki Tuuri +*******************************************************/ + +#include "mach0data.h" +#include "rem0cmp.h" +#include "mtr0log.h" +#include "page0zip.h" + +#ifdef UNIV_MATERIALIZE +#undef UNIV_INLINE +#define UNIV_INLINE +#endif + +/**************************************************************** +Gets the start of a page. */ +UNIV_INLINE +page_t* +page_align( +/*=======*/ + /* out: start of the page */ + const void* ptr) /* in: pointer to page frame */ +{ + return((page_t*) ut_align_down(ptr, UNIV_PAGE_SIZE)); +} +/**************************************************************** +Gets the offset within a page. */ +UNIV_INLINE +ulint +page_offset( +/*========*/ + /* out: offset from the start of the page */ + const void* ptr) /* in: pointer to page frame */ +{ + return(ut_align_offset(ptr, UNIV_PAGE_SIZE)); +} +/***************************************************************** +Returns the max trx id field value. */ +UNIV_INLINE +dulint +page_get_max_trx_id( +/*================*/ + const page_t* page) /* in: page */ +{ + ut_ad(page); + + return(mach_read_from_8(page + PAGE_HEADER + PAGE_MAX_TRX_ID)); +} + +/***************************************************************** +Sets the max trx id field value if trx_id is bigger than the previous +value. */ +UNIV_INLINE +void +page_update_max_trx_id( +/*===================*/ + buf_block_t* block, /* in/out: page */ + page_zip_des_t* page_zip,/* in/out: compressed page whose + uncompressed part will be updated, or NULL */ + dulint trx_id) /* in: transaction id */ +{ + ut_ad(block); + + if (ut_dulint_cmp(page_get_max_trx_id(buf_block_get_frame(block)), + trx_id) < 0) { + + page_set_max_trx_id(block, page_zip, trx_id); + } +} + +/***************************************************************** +Reads the given header field. */ +UNIV_INLINE +ulint +page_header_get_field( +/*==================*/ + const page_t* page, /* in: page */ + ulint field) /* in: PAGE_LEVEL, ... */ +{ + ut_ad(page); + ut_ad(field <= PAGE_INDEX_ID); + + return(mach_read_from_2(page + PAGE_HEADER + field)); +} + +/***************************************************************** +Sets the given header field. */ +UNIV_INLINE +void +page_header_set_field( +/*==================*/ + page_t* page, /* in/out: page */ + page_zip_des_t* page_zip,/* in/out: compressed page whose + uncompressed part will be updated, or NULL */ + ulint field, /* in: PAGE_N_DIR_SLOTS, ... */ + ulint val) /* in: value */ +{ + ut_ad(page); + ut_ad(field <= PAGE_N_RECS); + ut_ad(field == PAGE_N_HEAP || val < UNIV_PAGE_SIZE); + ut_ad(field != PAGE_N_HEAP || (val & 0x7fff) < UNIV_PAGE_SIZE); + + mach_write_to_2(page + PAGE_HEADER + field, val); + if (UNIV_LIKELY_NULL(page_zip)) { + page_zip_write_header(page_zip, + page + PAGE_HEADER + field, 2, NULL); + } +} + +/***************************************************************** +Returns the offset stored in the given header field. */ +UNIV_INLINE +ulint +page_header_get_offs( +/*=================*/ + /* out: offset from the start of the page, + or 0 */ + const page_t* page, /* in: page */ + ulint field) /* in: PAGE_FREE, ... */ +{ + ulint offs; + + ut_ad(page); + ut_ad((field == PAGE_FREE) + || (field == PAGE_LAST_INSERT) + || (field == PAGE_HEAP_TOP)); + + offs = page_header_get_field(page, field); + + ut_ad((field != PAGE_HEAP_TOP) || offs); + + return(offs); +} + +/***************************************************************** +Sets the pointer stored in the given header field. */ +UNIV_INLINE +void +page_header_set_ptr( +/*================*/ + page_t* page, /* in: page */ + page_zip_des_t* page_zip,/* in/out: compressed page whose + uncompressed part will be updated, or NULL */ + ulint field, /* in: PAGE_FREE, ... */ + const byte* ptr) /* in: pointer or NULL*/ +{ + ulint offs; + + ut_ad(page); + ut_ad((field == PAGE_FREE) + || (field == PAGE_LAST_INSERT) + || (field == PAGE_HEAP_TOP)); + + if (ptr == NULL) { + offs = 0; + } else { + offs = ptr - page; + } + + ut_ad((field != PAGE_HEAP_TOP) || offs); + + page_header_set_field(page, page_zip, field, offs); +} + +/***************************************************************** +Resets the last insert info field in the page header. Writes to mlog +about this operation. */ +UNIV_INLINE +void +page_header_reset_last_insert( +/*==========================*/ + page_t* page, /* in/out: page */ + page_zip_des_t* page_zip,/* in/out: compressed page whose + uncompressed part will be updated, or NULL */ + mtr_t* mtr) /* in: mtr */ +{ + ut_ad(page && mtr); + + if (UNIV_LIKELY_NULL(page_zip)) { + mach_write_to_2(page + (PAGE_HEADER + PAGE_LAST_INSERT), 0); + page_zip_write_header(page_zip, + page + (PAGE_HEADER + PAGE_LAST_INSERT), + 2, mtr); + } else { + mlog_write_ulint(page + (PAGE_HEADER + PAGE_LAST_INSERT), 0, + MLOG_2BYTES, mtr); + } +} + +/**************************************************************** +Determine whether the page is in new-style compact format. */ +UNIV_INLINE +ulint +page_is_comp( +/*=========*/ + /* out: nonzero if the page is in compact + format, zero if it is in old-style format */ + const page_t* page) /* in: index page */ +{ + return(UNIV_EXPECT(page_header_get_field(page, PAGE_N_HEAP) & 0x8000, + 0x8000)); +} + +/**************************************************************** +TRUE if the record is on a page in compact format. */ +UNIV_INLINE +ulint +page_rec_is_comp( +/*=============*/ + /* out: nonzero if in compact format */ + const rec_t* rec) /* in: record */ +{ + return(page_is_comp(page_align(rec))); +} + +/******************************************************************* +Returns the heap number of a record. */ +UNIV_INLINE +ulint +page_rec_get_heap_no( +/*=================*/ + /* out: heap number */ + const rec_t* rec) /* in: the physical record */ +{ + if (page_rec_is_comp(rec)) { + return(rec_get_heap_no_new(rec)); + } else { + return(rec_get_heap_no_old(rec)); + } +} + +/**************************************************************** +Determine whether the page is a B-tree leaf. */ +UNIV_INLINE +ibool +page_is_leaf( +/*=========*/ + /* out: TRUE if the page is a B-tree leaf */ + const page_t* page) /* in: page */ +{ + return(!*(const uint16*) (page + (PAGE_HEADER + PAGE_LEVEL))); +} + +/**************************************************************** +Gets the offset of the first record on the page. */ +UNIV_INLINE +ulint +page_get_infimum_offset( +/*====================*/ + /* out: offset of the first record + in record list, relative from page */ + const page_t* page) /* in: page which must have record(s) */ +{ + ut_ad(page); + ut_ad(!page_offset(page)); + + if (page_is_comp(page)) { + return(PAGE_NEW_INFIMUM); + } else { + return(PAGE_OLD_INFIMUM); + } +} + +/**************************************************************** +Gets the offset of the last record on the page. */ +UNIV_INLINE +ulint +page_get_supremum_offset( +/*=====================*/ + /* out: offset of the last record in + record list, relative from page */ + const page_t* page) /* in: page which must have record(s) */ +{ + ut_ad(page); + ut_ad(!page_offset(page)); + + if (page_is_comp(page)) { + return(PAGE_NEW_SUPREMUM); + } else { + return(PAGE_OLD_SUPREMUM); + } +} + +/**************************************************************** +TRUE if the record is a user record on the page. */ +UNIV_INLINE +ibool +page_rec_is_user_rec_low( +/*=====================*/ + /* out: TRUE if a user record */ + ulint offset) /* in: record offset on page */ +{ + ut_ad(offset >= PAGE_NEW_INFIMUM); +#if PAGE_OLD_INFIMUM < PAGE_NEW_INFIMUM +# error "PAGE_OLD_INFIMUM < PAGE_NEW_INFIMUM" +#endif +#if PAGE_OLD_SUPREMUM < PAGE_NEW_SUPREMUM +# error "PAGE_OLD_SUPREMUM < PAGE_NEW_SUPREMUM" +#endif +#if PAGE_NEW_INFIMUM > PAGE_OLD_SUPREMUM +# error "PAGE_NEW_INFIMUM > PAGE_OLD_SUPREMUM" +#endif +#if PAGE_OLD_INFIMUM > PAGE_NEW_SUPREMUM +# error "PAGE_OLD_INFIMUM > PAGE_NEW_SUPREMUM" +#endif +#if PAGE_NEW_SUPREMUM > PAGE_OLD_SUPREMUM_END +# error "PAGE_NEW_SUPREMUM > PAGE_OLD_SUPREMUM_END" +#endif +#if PAGE_OLD_SUPREMUM > PAGE_NEW_SUPREMUM_END +# error "PAGE_OLD_SUPREMUM > PAGE_NEW_SUPREMUM_END" +#endif + ut_ad(offset <= UNIV_PAGE_SIZE - PAGE_EMPTY_DIR_START); + + return(UNIV_LIKELY(offset != PAGE_NEW_SUPREMUM) + && UNIV_LIKELY(offset != PAGE_NEW_INFIMUM) + && UNIV_LIKELY(offset != PAGE_OLD_INFIMUM) + && UNIV_LIKELY(offset != PAGE_OLD_SUPREMUM)); +} + +/**************************************************************** +TRUE if the record is the supremum record on a page. */ +UNIV_INLINE +ibool +page_rec_is_supremum_low( +/*=====================*/ + /* out: TRUE if the supremum record */ + ulint offset) /* in: record offset on page */ +{ + ut_ad(offset >= PAGE_NEW_INFIMUM); + ut_ad(offset <= UNIV_PAGE_SIZE - PAGE_EMPTY_DIR_START); + + return(UNIV_UNLIKELY(offset == PAGE_NEW_SUPREMUM) + || UNIV_UNLIKELY(offset == PAGE_OLD_SUPREMUM)); +} + +/**************************************************************** +TRUE if the record is the infimum record on a page. */ +UNIV_INLINE +ibool +page_rec_is_infimum_low( +/*====================*/ + /* out: TRUE if the infimum record */ + ulint offset) /* in: record offset on page */ +{ + ut_ad(offset >= PAGE_NEW_INFIMUM); + ut_ad(offset <= UNIV_PAGE_SIZE - PAGE_EMPTY_DIR_START); + + return(UNIV_UNLIKELY(offset == PAGE_NEW_INFIMUM) + || UNIV_UNLIKELY(offset == PAGE_OLD_INFIMUM)); +} + +/**************************************************************** +TRUE if the record is a user record on the page. */ +UNIV_INLINE +ibool +page_rec_is_user_rec( +/*=================*/ + /* out: TRUE if a user record */ + const rec_t* rec) /* in: record */ +{ + return(page_rec_is_user_rec_low(page_offset(rec))); +} + +/**************************************************************** +TRUE if the record is the supremum record on a page. */ +UNIV_INLINE +ibool +page_rec_is_supremum( +/*=================*/ + /* out: TRUE if the supremum record */ + const rec_t* rec) /* in: record */ +{ + return(page_rec_is_supremum_low(page_offset(rec))); +} + +/**************************************************************** +TRUE if the record is the infimum record on a page. */ +UNIV_INLINE +ibool +page_rec_is_infimum( +/*================*/ + /* out: TRUE if the infimum record */ + const rec_t* rec) /* in: record */ +{ + return(page_rec_is_infimum_low(page_offset(rec))); +} + +/***************************************************************** +Compares a data tuple to a physical record. Differs from the function +cmp_dtuple_rec_with_match in the way that the record must reside on an +index page, and also page infimum and supremum records can be given in +the parameter rec. These are considered as the negative infinity and +the positive infinity in the alphabetical order. */ +UNIV_INLINE +int +page_cmp_dtuple_rec_with_match( +/*===========================*/ + /* out: 1, 0, -1, if dtuple is greater, equal, + less than rec, respectively, when only the + common first fields are compared */ + const dtuple_t* dtuple, /* in: data tuple */ + const rec_t* rec, /* in: physical record on a page; may also + be page infimum or supremum, in which case + matched-parameter values below are not + affected */ + const ulint* offsets,/* in: array returned by rec_get_offsets() */ + ulint* matched_fields, /* in/out: number of already completely + matched fields; when function returns + contains the value for current comparison */ + ulint* matched_bytes) /* in/out: number of already matched + bytes within the first field not completely + matched; when function returns contains the + value for current comparison */ +{ + ulint rec_offset; + + ut_ad(dtuple_check_typed(dtuple)); + ut_ad(rec_offs_validate(rec, NULL, offsets)); + ut_ad(!rec_offs_comp(offsets) == !page_rec_is_comp(rec)); + + rec_offset = page_offset(rec); + + if (UNIV_UNLIKELY(rec_offset == PAGE_NEW_INFIMUM) + || UNIV_UNLIKELY(rec_offset == PAGE_OLD_INFIMUM)) { + return(1); + } + if (UNIV_UNLIKELY(rec_offset == PAGE_NEW_SUPREMUM) + || UNIV_UNLIKELY(rec_offset == PAGE_OLD_SUPREMUM)) { + return(-1); + } + + return(cmp_dtuple_rec_with_match(dtuple, rec, offsets, + matched_fields, + matched_bytes)); +} + +/***************************************************************** +Gets the page number. */ +UNIV_INLINE +ulint +page_get_page_no( +/*=============*/ + /* out: page number */ + const page_t* page) /* in: page */ +{ + ut_ad(page == page_align((page_t*) page)); + return(mach_read_from_4(page + FIL_PAGE_OFFSET)); +} + +/***************************************************************** +Gets the tablespace identifier. */ +UNIV_INLINE +ulint +page_get_space_id( +/*==============*/ + /* out: space id */ + const page_t* page) /* in: page */ +{ + ut_ad(page == page_align((page_t*) page)); + return(mach_read_from_4(page + FIL_PAGE_ARCH_LOG_NO_OR_SPACE_ID)); +} + +/***************************************************************** +Gets the number of user records on page (infimum and supremum records +are not user records). */ +UNIV_INLINE +ulint +page_get_n_recs( +/*============*/ + /* out: number of user records */ + const page_t* page) /* in: index page */ +{ + return(page_header_get_field(page, PAGE_N_RECS)); +} + +/***************************************************************** +Gets the number of dir slots in directory. */ +UNIV_INLINE +ulint +page_dir_get_n_slots( +/*=================*/ + /* out: number of slots */ + const page_t* page) /* in: index page */ +{ + return(page_header_get_field(page, PAGE_N_DIR_SLOTS)); +} +/***************************************************************** +Sets the number of dir slots in directory. */ +UNIV_INLINE +void +page_dir_set_n_slots( +/*=================*/ + page_t* page, /* in/out: page */ + page_zip_des_t* page_zip,/* in/out: compressed page whose + uncompressed part will be updated, or NULL */ + ulint n_slots)/* in: number of slots */ +{ + page_header_set_field(page, page_zip, PAGE_N_DIR_SLOTS, n_slots); +} + +/***************************************************************** +Gets the number of records in the heap. */ +UNIV_INLINE +ulint +page_dir_get_n_heap( +/*================*/ + /* out: number of user records */ + const page_t* page) /* in: index page */ +{ + return(page_header_get_field(page, PAGE_N_HEAP) & 0x7fff); +} + +/***************************************************************** +Sets the number of records in the heap. */ +UNIV_INLINE +void +page_dir_set_n_heap( +/*================*/ + page_t* page, /* in/out: index page */ + page_zip_des_t* page_zip,/* in/out: compressed page whose + uncompressed part will be updated, or NULL. + Note that the size of the dense page directory + in the compressed page trailer is + n_heap * PAGE_ZIP_DIR_SLOT_SIZE. */ + ulint n_heap) /* in: number of records */ +{ + ut_ad(n_heap < 0x8000); + ut_ad(!page_zip || n_heap + == (page_header_get_field(page, PAGE_N_HEAP) & 0x7fff) + 1); + + page_header_set_field(page, page_zip, PAGE_N_HEAP, n_heap + | (0x8000 + & page_header_get_field(page, PAGE_N_HEAP))); +} + +#ifdef UNIV_DEBUG +/***************************************************************** +Gets pointer to nth directory slot. */ +UNIV_INLINE +page_dir_slot_t* +page_dir_get_nth_slot( +/*==================*/ + /* out: pointer to dir slot */ + const page_t* page, /* in: index page */ + ulint n) /* in: position */ +{ + ut_ad(page_dir_get_n_slots(page) > n); + + return((page_dir_slot_t*) + page + UNIV_PAGE_SIZE - PAGE_DIR + - (n + 1) * PAGE_DIR_SLOT_SIZE); +} +#endif /* UNIV_DEBUG */ + +/****************************************************************** +Used to check the consistency of a record on a page. */ +UNIV_INLINE +ibool +page_rec_check( +/*===========*/ + /* out: TRUE if succeed */ + const rec_t* rec) /* in: record */ +{ + const page_t* page = page_align(rec); + + ut_a(rec); + + ut_a(page_offset(rec) <= page_header_get_field(page, PAGE_HEAP_TOP)); + ut_a(page_offset(rec) >= PAGE_DATA); + + return(TRUE); +} + +/******************************************************************* +Gets the record pointed to by a directory slot. */ +UNIV_INLINE +const rec_t* +page_dir_slot_get_rec( +/*==================*/ + /* out: pointer to record */ + const page_dir_slot_t* slot) /* in: directory slot */ +{ + return(page_align(slot) + mach_read_from_2(slot)); +} + +/******************************************************************* +This is used to set the record offset in a directory slot. */ +UNIV_INLINE +void +page_dir_slot_set_rec( +/*==================*/ + page_dir_slot_t* slot, /* in: directory slot */ + rec_t* rec) /* in: record on the page */ +{ + ut_ad(page_rec_check(rec)); + + mach_write_to_2(slot, page_offset(rec)); +} + +/******************************************************************* +Gets the number of records owned by a directory slot. */ +UNIV_INLINE +ulint +page_dir_slot_get_n_owned( +/*======================*/ + /* out: number of records */ + const page_dir_slot_t* slot) /* in: page directory slot */ +{ + const rec_t* rec = page_dir_slot_get_rec(slot); + if (page_rec_is_comp(slot)) { + return(rec_get_n_owned_new(rec)); + } else { + return(rec_get_n_owned_old(rec)); + } +} + +/******************************************************************* +This is used to set the owned records field of a directory slot. */ +UNIV_INLINE +void +page_dir_slot_set_n_owned( +/*======================*/ + page_dir_slot_t*slot, /* in/out: directory slot */ + page_zip_des_t* page_zip,/* in/out: compressed page, or NULL */ + ulint n) /* in: number of records owned by the slot */ +{ + rec_t* rec = (rec_t*) page_dir_slot_get_rec(slot); + if (page_rec_is_comp(slot)) { + rec_set_n_owned_new(rec, page_zip, n); + } else { + ut_ad(!page_zip); + rec_set_n_owned_old(rec, n); + } +} + +/**************************************************************** +Calculates the space reserved for directory slots of a given number of +records. The exact value is a fraction number n * PAGE_DIR_SLOT_SIZE / +PAGE_DIR_SLOT_MIN_N_OWNED, and it is rounded upwards to an integer. */ +UNIV_INLINE +ulint +page_dir_calc_reserved_space( +/*=========================*/ + ulint n_recs) /* in: number of records */ +{ + return((PAGE_DIR_SLOT_SIZE * n_recs + PAGE_DIR_SLOT_MIN_N_OWNED - 1) + / PAGE_DIR_SLOT_MIN_N_OWNED); +} + +/**************************************************************** +Gets the pointer to the next record on the page. */ +UNIV_INLINE +const rec_t* +page_rec_get_next_low( +/*==================*/ + /* out: pointer to next record */ + const rec_t* rec, /* in: pointer to record */ + ulint comp) /* in: nonzero=compact page layout */ +{ + ulint offs; + const page_t* page; + + ut_ad(page_rec_check(rec)); + + page = page_align(rec); + + offs = rec_get_next_offs(rec, comp); + + if (UNIV_UNLIKELY(offs >= UNIV_PAGE_SIZE)) { + fprintf(stderr, + "InnoDB: Next record offset is nonsensical %lu" + " in record at offset %lu\n" + "InnoDB: rec address %p, space id %lu, page %lu\n", + (ulong)offs, (ulong) page_offset(rec), + (void*) rec, + (ulong) page_get_space_id(page), + (ulong) page_get_page_no(page)); + buf_page_print(page, 0); + + ut_error; + } + + if (UNIV_UNLIKELY(offs == 0)) { + + return(NULL); + } + + return(page + offs); +} + +/**************************************************************** +Gets the pointer to the next record on the page. */ +UNIV_INLINE +rec_t* +page_rec_get_next( +/*==============*/ + /* out: pointer to next record */ + rec_t* rec) /* in: pointer to record */ +{ + return((rec_t*) page_rec_get_next_low(rec, page_rec_is_comp(rec))); +} + +/**************************************************************** +Gets the pointer to the next record on the page. */ +UNIV_INLINE +const rec_t* +page_rec_get_next_const( +/*====================*/ + /* out: pointer to next record */ + const rec_t* rec) /* in: pointer to record */ +{ + return(page_rec_get_next_low(rec, page_rec_is_comp(rec))); +} + +/**************************************************************** +Sets the pointer to the next record on the page. */ +UNIV_INLINE +void +page_rec_set_next( +/*==============*/ + rec_t* rec, /* in: pointer to record, + must not be page supremum */ + rec_t* next) /* in: pointer to next record, + must not be page infimum */ +{ + ulint offs; + + ut_ad(page_rec_check(rec)); + ut_ad(!page_rec_is_supremum(rec)); + ut_ad(rec != next); + + ut_ad(!next || !page_rec_is_infimum(next)); + ut_ad(!next || page_align(rec) == page_align(next)); + + if (UNIV_LIKELY(next != NULL)) { + offs = page_offset(next); + } else { + offs = 0; + } + + if (page_rec_is_comp(rec)) { + rec_set_next_offs_new(rec, offs); + } else { + rec_set_next_offs_old(rec, offs); + } +} + +/**************************************************************** +Gets the pointer to the previous record. */ +UNIV_INLINE +const rec_t* +page_rec_get_prev_const( +/*====================*/ + /* out: pointer to previous record */ + const rec_t* rec) /* in: pointer to record, must not be page + infimum */ +{ + const page_dir_slot_t* slot; + ulint slot_no; + const rec_t* rec2; + const rec_t* prev_rec = NULL; + const page_t* page; + + ut_ad(page_rec_check(rec)); + + page = page_align(rec); + + ut_ad(!page_rec_is_infimum(rec)); + + slot_no = page_dir_find_owner_slot(rec); + + ut_a(slot_no != 0); + + slot = page_dir_get_nth_slot(page, slot_no - 1); + + rec2 = page_dir_slot_get_rec(slot); + + if (page_is_comp(page)) { + while (rec != rec2) { + prev_rec = rec2; + rec2 = page_rec_get_next_low(rec2, TRUE); + } + } else { + while (rec != rec2) { + prev_rec = rec2; + rec2 = page_rec_get_next_low(rec2, FALSE); + } + } + + ut_a(prev_rec); + + return(prev_rec); +} + +/**************************************************************** +Gets the pointer to the previous record. */ +UNIV_INLINE +rec_t* +page_rec_get_prev( +/*==============*/ + /* out: pointer to previous record */ + rec_t* rec) /* in: pointer to record, must not be page + infimum */ +{ + return((rec_t*) page_rec_get_prev_const(rec)); +} + +/******************************************************************* +Looks for the record which owns the given record. */ +UNIV_INLINE +rec_t* +page_rec_find_owner_rec( +/*====================*/ + /* out: the owner record */ + rec_t* rec) /* in: the physical record */ +{ + ut_ad(page_rec_check(rec)); + + if (page_rec_is_comp(rec)) { + while (rec_get_n_owned_new(rec) == 0) { + rec = page_rec_get_next(rec); + } + } else { + while (rec_get_n_owned_old(rec) == 0) { + rec = page_rec_get_next(rec); + } + } + + return(rec); +} + +/************************************************************** +Returns the base extra size of a physical record. This is the +size of the fixed header, independent of the record size. */ +UNIV_INLINE +ulint +page_rec_get_base_extra_size( +/*=========================*/ + /* out: REC_N_NEW_EXTRA_BYTES + or REC_N_OLD_EXTRA_BYTES */ + const rec_t* rec) /* in: physical record */ +{ +#if REC_N_NEW_EXTRA_BYTES + 1 != REC_N_OLD_EXTRA_BYTES +# error "REC_N_NEW_EXTRA_BYTES + 1 != REC_N_OLD_EXTRA_BYTES" +#endif + return(REC_N_NEW_EXTRA_BYTES + (ulint) !page_rec_is_comp(rec)); +} + +/**************************************************************** +Returns the sum of the sizes of the records in the record list, excluding +the infimum and supremum records. */ +UNIV_INLINE +ulint +page_get_data_size( +/*===============*/ + /* out: data in bytes */ + const page_t* page) /* in: index page */ +{ + ulint ret; + + ret = (ulint)(page_header_get_field(page, PAGE_HEAP_TOP) + - (page_is_comp(page) + ? PAGE_NEW_SUPREMUM_END + : PAGE_OLD_SUPREMUM_END) + - page_header_get_field(page, PAGE_GARBAGE)); + + ut_ad(ret < UNIV_PAGE_SIZE); + + return(ret); +} + + +/**************************************************************** +Allocates a block of memory from the free list of an index page. */ +UNIV_INTERN +void +page_mem_alloc_free( +/*================*/ + page_t* page, /* in/out: index page */ + page_zip_des_t* page_zip,/* in/out: compressed page with enough + space available for inserting the record, + or NULL */ + rec_t* next_rec,/* in: pointer to the new head of the + free record list */ + ulint need) /* in: number of bytes allocated */ +{ + ulint garbage; + +#ifdef UNIV_DEBUG + const rec_t* old_rec = page_header_get_ptr(page, PAGE_FREE); + ulint next_offs; + + ut_ad(old_rec); + next_offs = rec_get_next_offs(old_rec, page_is_comp(page)); + ut_ad(next_rec == (next_offs ? page + next_offs : NULL)); +#endif + + page_header_set_ptr(page, page_zip, PAGE_FREE, next_rec); + + garbage = page_header_get_field(page, PAGE_GARBAGE); + ut_ad(garbage >= need); + + page_header_set_field(page, page_zip, PAGE_GARBAGE, garbage - need); +} + +/***************************************************************** +Calculates free space if a page is emptied. */ +UNIV_INLINE +ulint +page_get_free_space_of_empty( +/*=========================*/ + /* out: free space */ + ulint comp) /* in: nonzero=compact page layout */ +{ + if (UNIV_LIKELY(comp)) { + return((ulint)(UNIV_PAGE_SIZE + - PAGE_NEW_SUPREMUM_END + - PAGE_DIR + - 2 * PAGE_DIR_SLOT_SIZE)); + } + + return((ulint)(UNIV_PAGE_SIZE + - PAGE_OLD_SUPREMUM_END + - PAGE_DIR + - 2 * PAGE_DIR_SLOT_SIZE)); +} + +/**************************************************************** +Each user record on a page, and also the deleted user records in the heap +takes its size plus the fraction of the dir cell size / +PAGE_DIR_SLOT_MIN_N_OWNED bytes for it. If the sum of these exceeds the +value of page_get_free_space_of_empty, the insert is impossible, otherwise +it is allowed. This function returns the maximum combined size of records +which can be inserted on top of the record heap. */ +UNIV_INLINE +ulint +page_get_max_insert_size( +/*=====================*/ + /* out: maximum combined size for + inserted records */ + const page_t* page, /* in: index page */ + ulint n_recs) /* in: number of records */ +{ + ulint occupied; + ulint free_space; + + if (page_is_comp(page)) { + occupied = page_header_get_field(page, PAGE_HEAP_TOP) + - PAGE_NEW_SUPREMUM_END + + page_dir_calc_reserved_space( + n_recs + page_dir_get_n_heap(page) - 2); + + free_space = page_get_free_space_of_empty(TRUE); + } else { + occupied = page_header_get_field(page, PAGE_HEAP_TOP) + - PAGE_OLD_SUPREMUM_END + + page_dir_calc_reserved_space( + n_recs + page_dir_get_n_heap(page) - 2); + + free_space = page_get_free_space_of_empty(FALSE); + } + + /* Above the 'n_recs +' part reserves directory space for the new + inserted records; the '- 2' excludes page infimum and supremum + records */ + + if (occupied > free_space) { + + return(0); + } + + return(free_space - occupied); +} + +/**************************************************************** +Returns the maximum combined size of records which can be inserted on top +of the record heap if a page is first reorganized. */ +UNIV_INLINE +ulint +page_get_max_insert_size_after_reorganize( +/*======================================*/ + /* out: maximum combined size for + inserted records */ + const page_t* page, /* in: index page */ + ulint n_recs) /* in: number of records */ +{ + ulint occupied; + ulint free_space; + + occupied = page_get_data_size(page) + + page_dir_calc_reserved_space(n_recs + page_get_n_recs(page)); + + free_space = page_get_free_space_of_empty(page_is_comp(page)); + + if (occupied > free_space) { + + return(0); + } + + return(free_space - occupied); +} + +/**************************************************************** +Puts a record to free list. */ +UNIV_INLINE +void +page_mem_free( +/*==========*/ + page_t* page, /* in/out: index page */ + page_zip_des_t* page_zip,/* in/out: compressed page, or NULL */ + rec_t* rec, /* in: pointer to the (origin of) record */ + dict_index_t* index, /* in: index of rec */ + const ulint* offsets)/* in: array returned by rec_get_offsets() */ +{ + rec_t* free; + ulint garbage; + + ut_ad(rec_offs_validate(rec, index, offsets)); + free = page_header_get_ptr(page, PAGE_FREE); + + page_rec_set_next(rec, free); + page_header_set_ptr(page, page_zip, PAGE_FREE, rec); + + garbage = page_header_get_field(page, PAGE_GARBAGE); + + page_header_set_field(page, page_zip, PAGE_GARBAGE, + garbage + rec_offs_size(offsets)); + + if (UNIV_LIKELY_NULL(page_zip)) { + page_zip_dir_delete(page_zip, rec, index, offsets, free); + } else { + page_header_set_field(page, page_zip, PAGE_N_RECS, + page_get_n_recs(page) - 1); + } +} + +#ifdef UNIV_MATERIALIZE +#undef UNIV_INLINE +#define UNIV_INLINE UNIV_INLINE_ORIGINAL +#endif diff --git a/storage/xtradb/include/page0types.h b/storage/xtradb/include/page0types.h new file mode 100644 index 00000000000..06af7a63d58 --- /dev/null +++ b/storage/xtradb/include/page0types.h @@ -0,0 +1,142 @@ +/***************************************************************************** + +Copyright (c) 1994, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/****************************************************** +Index page routines + +Created 2/2/1994 Heikki Tuuri +*******************************************************/ + +#ifndef page0types_h +#define page0types_h + +#include "univ.i" +#include "dict0types.h" +#include "mtr0types.h" + +/* Type of the index page */ +/* The following define eliminates a name collision on HP-UX */ +#define page_t ib_page_t +typedef byte page_t; +typedef struct page_search_struct page_search_t; +typedef struct page_cur_struct page_cur_t; + +typedef byte page_zip_t; +typedef struct page_zip_des_struct page_zip_des_t; + +/* The following definitions would better belong to page0zip.h, +but we cannot include page0zip.h from rem0rec.ic, because +page0*.h includes rem0rec.h and may include rem0rec.ic. */ + +#define PAGE_ZIP_SSIZE_BITS 3 + +#define PAGE_ZIP_MIN_SIZE_SHIFT 10 /* log2 of smallest compressed size */ +#define PAGE_ZIP_MIN_SIZE (1 << PAGE_ZIP_MIN_SIZE_SHIFT) + +#define PAGE_ZIP_NUM_SSIZE (UNIV_PAGE_SIZE_SHIFT - PAGE_ZIP_MIN_SIZE_SHIFT + 2) +#if PAGE_ZIP_NUM_SSIZE > (1 << PAGE_ZIP_SSIZE_BITS) +# error "PAGE_ZIP_NUM_SSIZE > (1 << PAGE_ZIP_SSIZE_BITS)" +#endif + +/* Compressed page descriptor */ +struct page_zip_des_struct +{ + page_zip_t* data; /* compressed page data */ + +#ifdef UNIV_DEBUG + unsigned m_start:16; /* start offset of modification log */ +#endif /* UNIV_DEBUG */ + unsigned m_end:16; /* end offset of modification log */ + unsigned m_nonempty:1; /* TRUE if the modification log + is not empty */ + unsigned n_blobs:12; /* number of externally stored + columns on the page; the maximum + is 744 on a 16 KiB page */ + unsigned ssize:PAGE_ZIP_SSIZE_BITS; + /* 0 or compressed page size; + the size in bytes is + PAGE_ZIP_MIN_SIZE << (ssize - 1). */ +}; + +/** Compression statistics for a given page size */ +struct page_zip_stat_struct { + /** Number of page compressions */ + ulint compressed; + /** Number of successful page compressions */ + ulint compressed_ok; + /** Number of page decompressions */ + ulint decompressed; + /** Duration of page compressions in microseconds */ + ib_uint64_t compressed_usec; + /** Duration of page decompressions in microseconds */ + ib_uint64_t decompressed_usec; +}; + +typedef struct page_zip_stat_struct page_zip_stat_t; + +/** Statistics on compression, indexed by page_zip_des_t::ssize - 1 */ +extern page_zip_stat_t page_zip_stat[PAGE_ZIP_NUM_SSIZE - 1]; + +/************************************************************************** +Write the "deleted" flag of a record on a compressed page. The flag must +already have been written on the uncompressed page. */ +UNIV_INTERN +void +page_zip_rec_set_deleted( +/*=====================*/ + page_zip_des_t* page_zip,/* in/out: compressed page */ + const byte* rec, /* in: record on the uncompressed page */ + ulint flag) /* in: the deleted flag (nonzero=TRUE) */ + __attribute__((nonnull)); + +/************************************************************************** +Write the "owned" flag of a record on a compressed page. The n_owned field +must already have been written on the uncompressed page. */ +UNIV_INTERN +void +page_zip_rec_set_owned( +/*===================*/ + page_zip_des_t* page_zip,/* in/out: compressed page */ + const byte* rec, /* in: record on the uncompressed page */ + ulint flag) /* in: the owned flag (nonzero=TRUE) */ + __attribute__((nonnull)); + +/************************************************************************** +Shift the dense page directory when a record is deleted. */ +UNIV_INTERN +void +page_zip_dir_delete( +/*================*/ + page_zip_des_t* page_zip,/* in/out: compressed page */ + byte* rec, /* in: deleted record */ + dict_index_t* index, /* in: index of rec */ + const ulint* offsets,/* in: rec_get_offsets(rec) */ + const byte* free) /* in: previous start of the free list */ + __attribute__((nonnull(1,2,3,4))); + +/************************************************************************** +Add a slot to the dense page directory. */ +UNIV_INTERN +void +page_zip_dir_add_slot( +/*==================*/ + page_zip_des_t* page_zip, /* in/out: compressed page */ + ulint is_clustered) /* in: nonzero for clustered index, + zero for others */ + __attribute__((nonnull)); +#endif diff --git a/storage/xtradb/include/page0zip.h b/storage/xtradb/include/page0zip.h new file mode 100644 index 00000000000..0183e013d05 --- /dev/null +++ b/storage/xtradb/include/page0zip.h @@ -0,0 +1,455 @@ +/***************************************************************************** + +Copyright (c) 2005, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/****************************************************** +Compressed page interface + +Created June 2005 by Marko Makela +*******************************************************/ + +#ifndef page0zip_h +#define page0zip_h + +#ifdef UNIV_MATERIALIZE +# undef UNIV_INLINE +# define UNIV_INLINE +#endif + +#include "mtr0types.h" +#include "page0types.h" +#include "buf0types.h" +#include "dict0types.h" +#include "mem0mem.h" + +/************************************************************************** +Determine the size of a compressed page in bytes. */ +UNIV_INLINE +ulint +page_zip_get_size( +/*==============*/ + /* out: size in bytes */ + const page_zip_des_t* page_zip) /* in: compressed page */ + __attribute__((nonnull, pure)); +/************************************************************************** +Set the size of a compressed page in bytes. */ +UNIV_INLINE +void +page_zip_set_size( +/*==============*/ + page_zip_des_t* page_zip, /* in/out: compressed page */ + ulint size); /* in: size in bytes */ + +/************************************************************************** +Determine if a record is so big that it needs to be stored externally. */ +UNIV_INLINE +ibool +page_zip_rec_needs_ext( +/*===================*/ + /* out: FALSE if the entire record + can be stored locally on the page */ + ulint rec_size, /* in: length of the record in bytes */ + ulint comp, /* in: nonzero=compact format */ + ulint n_fields, /* in: number of fields in the record; + ignored if zip_size == 0 */ + ulint zip_size) /* in: compressed page size in bytes, or 0 */ + __attribute__((const)); + +/************************************************************************** +Determine the guaranteed free space on an empty page. */ +UNIV_INTERN +ulint +page_zip_empty_size( +/*================*/ + /* out: minimum payload size on the page */ + ulint n_fields, /* in: number of columns in the index */ + ulint zip_size) /* in: compressed page size in bytes */ + __attribute__((const)); + +/************************************************************************** +Initialize a compressed page descriptor. */ +UNIV_INLINE +void +page_zip_des_init( +/*==============*/ + page_zip_des_t* page_zip); /* in/out: compressed page + descriptor */ + +/************************************************************************** +Configure the zlib allocator to use the given memory heap. */ +UNIV_INTERN +void +page_zip_set_alloc( +/*===============*/ + void* stream, /* in/out: zlib stream */ + mem_heap_t* heap); /* in: memory heap to use */ + +/************************************************************************** +Compress a page. */ +UNIV_INTERN +ibool +page_zip_compress( +/*==============*/ + /* out: TRUE on success, FALSE on failure; + page_zip will be left intact on failure. */ + page_zip_des_t* page_zip,/* in: size; out: data, n_blobs, + m_start, m_end, m_nonempty */ + const page_t* page, /* in: uncompressed page */ + dict_index_t* index, /* in: index of the B-tree node */ + mtr_t* mtr) /* in: mini-transaction, or NULL */ + __attribute__((nonnull(1,2,3))); + +/************************************************************************** +Decompress a page. This function should tolerate errors on the compressed +page. Instead of letting assertions fail, it will return FALSE if an +inconsistency is detected. */ +UNIV_INTERN +ibool +page_zip_decompress( +/*================*/ + /* out: TRUE on success, FALSE on failure */ + page_zip_des_t* page_zip,/* in: data, ssize; + out: m_start, m_end, m_nonempty, n_blobs */ + page_t* page) /* out: uncompressed page, may be trashed */ + __attribute__((nonnull)); + +#ifdef UNIV_DEBUG +/************************************************************************** +Validate a compressed page descriptor. */ +UNIV_INLINE +ibool +page_zip_simple_validate( +/*=====================*/ + /* out: TRUE if ok */ + const page_zip_des_t* page_zip); /* in: compressed page + descriptor */ +#endif /* UNIV_DEBUG */ + +#ifdef UNIV_ZIP_DEBUG +/************************************************************************** +Check that the compressed and decompressed pages match. */ +UNIV_INTERN +ibool +page_zip_validate_low( +/*==================*/ + /* out: TRUE if valid, FALSE if not */ + const page_zip_des_t* page_zip,/* in: compressed page */ + const page_t* page, /* in: uncompressed page */ + ibool sloppy) /* in: FALSE=strict, + TRUE=ignore the MIN_REC_FLAG */ + __attribute__((nonnull)); +/************************************************************************** +Check that the compressed and decompressed pages match. */ +UNIV_INTERN +ibool +page_zip_validate( +/*==============*/ + const page_zip_des_t* page_zip,/* in: compressed page */ + const page_t* page) /* in: uncompressed page */ + __attribute__((nonnull)); +#endif /* UNIV_ZIP_DEBUG */ + +/************************************************************************** +Determine how big record can be inserted without recompressing the page. */ +UNIV_INLINE +lint +page_zip_max_ins_size( +/*==================*/ + /* out: a positive number + indicating the maximum size of + a record whose insertion is + guaranteed to succeed, or + zero or negative */ + const page_zip_des_t* page_zip,/* in: compressed page */ + ibool is_clust)/* in: TRUE if clustered index */ + __attribute__((nonnull, pure)); + +/************************************************************************** +Determine if enough space is available in the modification log. */ +UNIV_INLINE +ibool +page_zip_available( +/*===============*/ + /* out: TRUE if page_zip_write_rec() + will succeed */ + const page_zip_des_t* page_zip,/* in: compressed page */ + ibool is_clust,/* in: TRUE if clustered index */ + ulint length, /* in: combined size of the record */ + ulint create) /* in: nonzero=add the record to + the heap */ + __attribute__((nonnull, pure)); + +/************************************************************************** +Write data to the uncompressed header portion of a page. The data must +already have been written to the uncompressed page. */ +UNIV_INLINE +void +page_zip_write_header( +/*==================*/ + page_zip_des_t* page_zip,/* in/out: compressed page */ + const byte* str, /* in: address on the uncompressed page */ + ulint length, /* in: length of the data */ + mtr_t* mtr) /* in: mini-transaction, or NULL */ + __attribute__((nonnull(1,2))); + +/************************************************************************** +Write an entire record on the compressed page. The data must already +have been written to the uncompressed page. */ +UNIV_INTERN +void +page_zip_write_rec( +/*===============*/ + page_zip_des_t* page_zip,/* in/out: compressed page */ + const byte* rec, /* in: record being written */ + dict_index_t* index, /* in: the index the record belongs to */ + const ulint* offsets,/* in: rec_get_offsets(rec, index) */ + ulint create) /* in: nonzero=insert, zero=update */ + __attribute__((nonnull)); + +/*************************************************************** +Parses a log record of writing a BLOB pointer of a record. */ +UNIV_INTERN +byte* +page_zip_parse_write_blob_ptr( +/*==========================*/ + /* out: end of log record or NULL */ + byte* ptr, /* in: redo log buffer */ + byte* end_ptr,/* in: redo log buffer end */ + page_t* page, /* in/out: uncompressed page */ + page_zip_des_t* page_zip);/* in/out: compressed page */ + +/************************************************************************** +Write a BLOB pointer of a record on the leaf page of a clustered index. +The information must already have been updated on the uncompressed page. */ +UNIV_INTERN +void +page_zip_write_blob_ptr( +/*====================*/ + page_zip_des_t* page_zip,/* in/out: compressed page */ + const byte* rec, /* in/out: record whose data is being + written */ + dict_index_t* index, /* in: index of the page */ + const ulint* offsets,/* in: rec_get_offsets(rec, index) */ + ulint n, /* in: column index */ + mtr_t* mtr) /* in: mini-transaction handle, + or NULL if no logging is needed */ + __attribute__((nonnull(1,2,3,4))); + +/*************************************************************** +Parses a log record of writing the node pointer of a record. */ +UNIV_INTERN +byte* +page_zip_parse_write_node_ptr( +/*==========================*/ + /* out: end of log record or NULL */ + byte* ptr, /* in: redo log buffer */ + byte* end_ptr,/* in: redo log buffer end */ + page_t* page, /* in/out: uncompressed page */ + page_zip_des_t* page_zip);/* in/out: compressed page */ + +/************************************************************************** +Write the node pointer of a record on a non-leaf compressed page. */ +UNIV_INTERN +void +page_zip_write_node_ptr( +/*====================*/ + page_zip_des_t* page_zip,/* in/out: compressed page */ + byte* rec, /* in/out: record */ + ulint size, /* in: data size of rec */ + ulint ptr, /* in: node pointer */ + mtr_t* mtr) /* in: mini-transaction, or NULL */ + __attribute__((nonnull(1,2))); + +/************************************************************************** +Write the trx_id and roll_ptr of a record on a B-tree leaf node page. */ +UNIV_INTERN +void +page_zip_write_trx_id_and_roll_ptr( +/*===============================*/ + page_zip_des_t* page_zip,/* in/out: compressed page */ + byte* rec, /* in/out: record */ + const ulint* offsets,/* in: rec_get_offsets(rec, index) */ + ulint trx_id_col,/* in: column number of TRX_ID in rec */ + dulint trx_id, /* in: transaction identifier */ + dulint roll_ptr)/* in: roll_ptr */ + __attribute__((nonnull)); + +/************************************************************************** +Write the "deleted" flag of a record on a compressed page. The flag must +already have been written on the uncompressed page. */ +UNIV_INTERN +void +page_zip_rec_set_deleted( +/*=====================*/ + page_zip_des_t* page_zip,/* in/out: compressed page */ + const byte* rec, /* in: record on the uncompressed page */ + ulint flag) /* in: the deleted flag (nonzero=TRUE) */ + __attribute__((nonnull)); + +/************************************************************************** +Write the "owned" flag of a record on a compressed page. The n_owned field +must already have been written on the uncompressed page. */ +UNIV_INTERN +void +page_zip_rec_set_owned( +/*===================*/ + page_zip_des_t* page_zip,/* in/out: compressed page */ + const byte* rec, /* in: record on the uncompressed page */ + ulint flag) /* in: the owned flag (nonzero=TRUE) */ + __attribute__((nonnull)); + +/************************************************************************** +Insert a record to the dense page directory. */ +UNIV_INTERN +void +page_zip_dir_insert( +/*================*/ + page_zip_des_t* page_zip,/* in/out: compressed page */ + const byte* prev_rec,/* in: record after which to insert */ + const byte* free_rec,/* in: record from which rec was + allocated, or NULL */ + byte* rec); /* in: record to insert */ + +/************************************************************************** +Shift the dense page directory and the array of BLOB pointers +when a record is deleted. */ +UNIV_INTERN +void +page_zip_dir_delete( +/*================*/ + page_zip_des_t* page_zip,/* in/out: compressed page */ + byte* rec, /* in: deleted record */ + dict_index_t* index, /* in: index of rec */ + const ulint* offsets,/* in: rec_get_offsets(rec) */ + const byte* free) /* in: previous start of the free list */ + __attribute__((nonnull(1,2,3,4))); + +/************************************************************************** +Add a slot to the dense page directory. */ +UNIV_INTERN +void +page_zip_dir_add_slot( +/*==================*/ + page_zip_des_t* page_zip, /* in/out: compressed page */ + ulint is_clustered) /* in: nonzero for clustered index, + zero for others */ + __attribute__((nonnull)); + +/*************************************************************** +Parses a log record of writing to the header of a page. */ +UNIV_INTERN +byte* +page_zip_parse_write_header( +/*========================*/ + /* out: end of log record or NULL */ + byte* ptr, /* in: redo log buffer */ + byte* end_ptr,/* in: redo log buffer end */ + page_t* page, /* in/out: uncompressed page */ + page_zip_des_t* page_zip);/* in/out: compressed page */ + +/************************************************************************** +Write data to the uncompressed header portion of a page. The data must +already have been written to the uncompressed page. +However, the data portion of the uncompressed page may differ from +the compressed page when a record is being inserted in +page_cur_insert_rec_low(). */ +UNIV_INLINE +void +page_zip_write_header( +/*==================*/ + page_zip_des_t* page_zip,/* in/out: compressed page */ + const byte* str, /* in: address on the uncompressed page */ + ulint length, /* in: length of the data */ + mtr_t* mtr) /* in: mini-transaction, or NULL */ + __attribute__((nonnull(1,2))); + +/************************************************************************** +Reorganize and compress a page. This is a low-level operation for +compressed pages, to be used when page_zip_compress() fails. +On success, a redo log entry MLOG_ZIP_PAGE_COMPRESS will be written. +The function btr_page_reorganize() should be preferred whenever possible. +IMPORTANT: if page_zip_reorganize() is invoked on a leaf page of a +non-clustered index, the caller must update the insert buffer free +bits in the same mini-transaction in such a way that the modification +will be redo-logged. */ +UNIV_INTERN +ibool +page_zip_reorganize( +/*================*/ + /* out: TRUE on success, FALSE on failure; + page and page_zip will be left intact + on failure. */ + buf_block_t* block, /* in/out: page with compressed page; + on the compressed page, in: size; + out: data, n_blobs, + m_start, m_end, m_nonempty */ + dict_index_t* index, /* in: index of the B-tree node */ + mtr_t* mtr) /* in: mini-transaction */ + __attribute__((nonnull)); +/************************************************************************** +Copy the records of a page byte for byte. Do not copy the page header +or trailer, except those B-tree header fields that are directly +related to the storage of records. Also copy PAGE_MAX_TRX_ID. +NOTE: The caller must update the lock table and the adaptive hash index. */ +UNIV_INTERN +void +page_zip_copy_recs( +/*===============*/ + page_zip_des_t* page_zip, /* out: copy of src_zip + (n_blobs, m_start, m_end, + m_nonempty, data[0..size-1]) */ + page_t* page, /* out: copy of src */ + const page_zip_des_t* src_zip, /* in: compressed page */ + const page_t* src, /* in: page */ + dict_index_t* index, /* in: index of the B-tree */ + mtr_t* mtr) /* in: mini-transaction */ + __attribute__((nonnull(1,2,3,4))); + +/************************************************************************** +Parses a log record of compressing an index page. */ +UNIV_INTERN +byte* +page_zip_parse_compress( +/*====================*/ + /* out: end of log record or NULL */ + byte* ptr, /* in: buffer */ + byte* end_ptr,/* in: buffer end */ + page_t* page, /* out: uncompressed page */ + page_zip_des_t* page_zip)/* out: compressed page */ + __attribute__((nonnull(1,2))); + +/************************************************************************** +Calculate the compressed page checksum. */ +UNIV_INTERN +ulint +page_zip_calc_checksum( +/*===================*/ + /* out: page checksum */ + const void* data, /* in: compressed page */ + ulint size) /* in: size of compressed page */ + __attribute__((nonnull)); + +#ifdef UNIV_MATERIALIZE +# undef UNIV_INLINE +# define UNIV_INLINE UNIV_INLINE_ORIGINAL +#endif + +#ifndef UNIV_NONINL +# include "page0zip.ic" +#endif + +#endif /* page0zip_h */ diff --git a/storage/xtradb/include/page0zip.ic b/storage/xtradb/include/page0zip.ic new file mode 100644 index 00000000000..3db5f025c31 --- /dev/null +++ b/storage/xtradb/include/page0zip.ic @@ -0,0 +1,398 @@ +/***************************************************************************** + +Copyright (c) 2005, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/****************************************************** +Compressed page interface + +Created June 2005 by Marko Makela +*******************************************************/ + +#ifdef UNIV_MATERIALIZE +# undef UNIV_INLINE +# define UNIV_INLINE +#endif + +#include "page0zip.h" +#include "page0page.h" + +/* The format of compressed pages is as follows. + +The header and trailer of the uncompressed pages, excluding the page +directory in the trailer, are copied as is to the header and trailer +of the compressed page. + +At the end of the compressed page, there is a dense page directory +pointing to every user record contained on the page, including deleted +records on the free list. The dense directory is indexed in the +collation order, i.e., in the order in which the record list is +linked on the uncompressed page. The infimum and supremum records are +excluded. The two most significant bits of the entries are allocated +for the delete-mark and an n_owned flag indicating the last record in +a chain of records pointed to from the sparse page directory on the +uncompressed page. + +The data between PAGE_ZIP_START and the last page directory entry will +be written in compressed format, starting at offset PAGE_DATA. +Infimum and supremum records are not stored. We exclude the +REC_N_NEW_EXTRA_BYTES in every record header. These can be recovered +from the dense page directory stored at the end of the compressed +page. + +The fields node_ptr (in non-leaf B-tree nodes; level>0), trx_id and +roll_ptr (in leaf B-tree nodes; level=0), and BLOB pointers of +externally stored columns are stored separately, in ascending order of +heap_no and column index, starting backwards from the dense page +directory. + +The compressed data stream may be followed by a modification log +covering the compressed portion of the page, as follows. + +MODIFICATION LOG ENTRY FORMAT +- write record: + - (heap_no - 1) << 1 (1..2 bytes) + - extra bytes backwards + - data bytes +- clear record: + - (heap_no - 1) << 1 | 1 (1..2 bytes) + +The integer values are stored in a variable-length format: +- 0xxxxxxx: 0..127 +- 1xxxxxxx xxxxxxxx: 0..32767 + +The end of the modification log is marked by a 0 byte. + +In summary, the compressed page looks like this: + +(1) Uncompressed page header (PAGE_DATA bytes) +(2) Compressed index information +(3) Compressed page data +(4) Page modification log (page_zip->m_start..page_zip->m_end) +(5) Empty zero-filled space +(6) BLOB pointers (on leaf pages) + - BTR_EXTERN_FIELD_REF_SIZE for each externally stored column + - in descending collation order +(7) Uncompressed columns of user records, n_dense * uncompressed_size bytes, + - indexed by heap_no + - DATA_TRX_ID_LEN + DATA_ROLL_PTR_LEN for leaf pages of clustered indexes + - REC_NODE_PTR_SIZE for non-leaf pages + - 0 otherwise +(8) dense page directory, stored backwards + - n_dense = n_heap - 2 + - existing records in ascending collation order + - deleted records (free list) in link order +*/ + +/* Start offset of the area that will be compressed */ +#define PAGE_ZIP_START PAGE_NEW_SUPREMUM_END +/* Size of an compressed page directory entry */ +#define PAGE_ZIP_DIR_SLOT_SIZE 2 +/* Mask of record offsets */ +#define PAGE_ZIP_DIR_SLOT_MASK 0x3fff +/* 'owned' flag */ +#define PAGE_ZIP_DIR_SLOT_OWNED 0x4000 +/* 'deleted' flag */ +#define PAGE_ZIP_DIR_SLOT_DEL 0x8000 + +/************************************************************************** +Determine the size of a compressed page in bytes. */ +UNIV_INLINE +ulint +page_zip_get_size( +/*==============*/ + /* out: size in bytes */ + const page_zip_des_t* page_zip) /* in: compressed page */ +{ + ulint size; + + if (UNIV_UNLIKELY(!page_zip->ssize)) { + return(0); + } + + size = (PAGE_ZIP_MIN_SIZE >> 1) << page_zip->ssize; + + ut_ad(size >= PAGE_ZIP_MIN_SIZE); + ut_ad(size <= UNIV_PAGE_SIZE); + + return(size); +} +/************************************************************************** +Set the size of a compressed page in bytes. */ +UNIV_INLINE +void +page_zip_set_size( +/*==============*/ + page_zip_des_t* page_zip, /* in/out: compressed page */ + ulint size) /* in: size in bytes */ +{ + if (size) { + int ssize; + + ut_ad(ut_is_2pow(size)); + + for (ssize = 1; size > (ulint) (512 << ssize); ssize++) { + } + + page_zip->ssize = ssize; + } else { + page_zip->ssize = 0; + } + + ut_ad(page_zip_get_size(page_zip) == size); +} + +/************************************************************************** +Determine if a record is so big that it needs to be stored externally. */ +UNIV_INLINE +ibool +page_zip_rec_needs_ext( +/*===================*/ + /* out: FALSE if the entire record + can be stored locally on the page */ + ulint rec_size, /* in: length of the record in bytes */ + ulint comp, /* in: nonzero=compact format */ + ulint n_fields, /* in: number of fields in the record; + ignored if zip_size == 0 */ + ulint zip_size) /* in: compressed page size in bytes, or 0 */ +{ + ut_ad(rec_size > comp ? REC_N_NEW_EXTRA_BYTES : REC_N_OLD_EXTRA_BYTES); + ut_ad(ut_is_2pow(zip_size)); + ut_ad(comp || !zip_size); + +#if UNIV_PAGE_SIZE > REC_MAX_DATA_SIZE + if (UNIV_UNLIKELY(rec_size >= REC_MAX_DATA_SIZE)) { + return(TRUE); + } +#endif + + if (UNIV_UNLIKELY(zip_size)) { + ut_ad(comp); + /* On a compressed page, there is a two-byte entry in + the dense page directory for every record. But there + is no record header. There should be enough room for + one record on an empty leaf page. Subtract 1 byte for + the encoded heap number. Check also the available space + on the uncompressed page. */ + return(rec_size - (REC_N_NEW_EXTRA_BYTES - 2) + >= (page_zip_empty_size(n_fields, zip_size) - 1) + || rec_size >= page_get_free_space_of_empty(TRUE) / 2); + } + + return(rec_size >= page_get_free_space_of_empty(comp) / 2); +} + +#ifdef UNIV_DEBUG +/************************************************************************** +Validate a compressed page descriptor. */ +UNIV_INLINE +ibool +page_zip_simple_validate( +/*=====================*/ + /* out: TRUE if ok */ + const page_zip_des_t* page_zip)/* in: compressed page descriptor */ +{ + ut_ad(page_zip); + ut_ad(page_zip->data); + ut_ad(page_zip->ssize < PAGE_ZIP_NUM_SSIZE); + ut_ad(page_zip_get_size(page_zip) + > PAGE_DATA + PAGE_ZIP_DIR_SLOT_SIZE); + ut_ad(page_zip->m_start <= page_zip->m_end); + ut_ad(page_zip->m_end < page_zip_get_size(page_zip)); + ut_ad(page_zip->n_blobs + < page_zip_get_size(page_zip) / BTR_EXTERN_FIELD_REF_SIZE); + return(TRUE); +} +#endif /* UNIV_DEBUG */ + +/************************************************************************** +Determine if the length of the page trailer. */ +UNIV_INLINE +ibool +page_zip_get_trailer_len( +/*=====================*/ + /* out: length of the page trailer, + in bytes, not including the terminating + zero byte of the modification log */ + const page_zip_des_t* page_zip,/* in: compressed page */ + ibool is_clust,/* in: TRUE if clustered index */ + ulint* entry_size)/* out: size of the uncompressed + portion of a user record */ +{ + ulint uncompressed_size; + + ut_ad(page_zip_simple_validate(page_zip)); + UNIV_MEM_ASSERT_RW(page_zip->data, page_zip_get_size(page_zip)); + + if (UNIV_UNLIKELY(!page_is_leaf(page_zip->data))) { + uncompressed_size = PAGE_ZIP_DIR_SLOT_SIZE + + REC_NODE_PTR_SIZE; + ut_ad(!page_zip->n_blobs); + } else if (UNIV_UNLIKELY(is_clust)) { + uncompressed_size = PAGE_ZIP_DIR_SLOT_SIZE + + DATA_TRX_ID_LEN + DATA_ROLL_PTR_LEN; + } else { + uncompressed_size = PAGE_ZIP_DIR_SLOT_SIZE; + ut_ad(!page_zip->n_blobs); + } + + if (entry_size) { + *entry_size = uncompressed_size; + } + + return((page_dir_get_n_heap(page_zip->data) - 2) + * uncompressed_size + + page_zip->n_blobs * BTR_EXTERN_FIELD_REF_SIZE); +} + +/************************************************************************** +Determine how big record can be inserted without recompressing the page. */ +UNIV_INLINE +lint +page_zip_max_ins_size( +/*==================*/ + /* out: a positive number + indicating the maximum size of + a record whose insertion is + guaranteed to succeed, or + zero or negative */ + const page_zip_des_t* page_zip,/* in: compressed page */ + ibool is_clust)/* in: TRUE if clustered index */ +{ + ulint uncompressed_size; + ulint trailer_len; + + trailer_len = page_zip_get_trailer_len(page_zip, is_clust, + &uncompressed_size); + + /* When a record is created, a pointer may be added to + the dense directory. + Likewise, space for the columns that will not be + compressed will be allocated from the page trailer. + Also the BLOB pointers will be allocated from there, but + we may as well count them in the length of the record. */ + + trailer_len += uncompressed_size; + + return((lint) page_zip_get_size(page_zip) + - trailer_len - page_zip->m_end + - (REC_N_NEW_EXTRA_BYTES - 2)); +} + +/************************************************************************** +Determine if enough space is available in the modification log. */ +UNIV_INLINE +ibool +page_zip_available( +/*===============*/ + /* out: TRUE if enough space + is available */ + const page_zip_des_t* page_zip,/* in: compressed page */ + ibool is_clust,/* in: TRUE if clustered index */ + ulint length, /* in: combined size of the record */ + ulint create) /* in: nonzero=add the record to + the heap */ +{ + ulint uncompressed_size; + ulint trailer_len; + + ut_ad(length > REC_N_NEW_EXTRA_BYTES); + + trailer_len = page_zip_get_trailer_len(page_zip, is_clust, + &uncompressed_size); + + /* Subtract the fixed extra bytes and add the maximum + space needed for identifying the record (encoded heap_no). */ + length -= REC_N_NEW_EXTRA_BYTES - 2; + + if (UNIV_UNLIKELY(create)) { + /* When a record is created, a pointer may be added to + the dense directory. + Likewise, space for the columns that will not be + compressed will be allocated from the page trailer. + Also the BLOB pointers will be allocated from there, but + we may as well count them in the length of the record. */ + + trailer_len += uncompressed_size; + } + + return(UNIV_LIKELY(length + + trailer_len + + page_zip->m_end + < page_zip_get_size(page_zip))); +} + +/************************************************************************** +Initialize a compressed page descriptor. */ +UNIV_INLINE +void +page_zip_des_init( +/*==============*/ + page_zip_des_t* page_zip) /* in/out: compressed page + descriptor */ +{ + memset(page_zip, 0, sizeof *page_zip); +} + +/************************************************************************** +Write a log record of writing to the uncompressed header portion of a page. */ +UNIV_INTERN +void +page_zip_write_header_log( +/*======================*/ + const byte* data,/* in: data on the uncompressed page */ + ulint length, /* in: length of the data */ + mtr_t* mtr); /* in: mini-transaction */ + +/************************************************************************** +Write data to the uncompressed header portion of a page. The data must +already have been written to the uncompressed page. +However, the data portion of the uncompressed page may differ from +the compressed page when a record is being inserted in +page_cur_insert_rec_zip(). */ +UNIV_INLINE +void +page_zip_write_header( +/*==================*/ + page_zip_des_t* page_zip,/* in/out: compressed page */ + const byte* str, /* in: address on the uncompressed page */ + ulint length, /* in: length of the data */ + mtr_t* mtr) /* in: mini-transaction, or NULL */ +{ + ulint pos; + + ut_ad(buf_frame_get_page_zip(str) == page_zip); + ut_ad(page_zip_simple_validate(page_zip)); + UNIV_MEM_ASSERT_RW(page_zip->data, page_zip_get_size(page_zip)); + + pos = page_offset(str); + + ut_ad(pos < PAGE_DATA); + + memcpy(page_zip->data + pos, str, length); + + /* The following would fail in page_cur_insert_rec_zip(). */ + /* ut_ad(page_zip_validate(page_zip, str - pos)); */ + + if (UNIV_LIKELY_NULL(mtr)) { + page_zip_write_header_log(str, length, mtr); + } +} + +#ifdef UNIV_MATERIALIZE +# undef UNIV_INLINE +# define UNIV_INLINE UNIV_INLINE_ORIGINAL +#endif diff --git a/storage/xtradb/include/pars0grm.h b/storage/xtradb/include/pars0grm.h new file mode 100644 index 00000000000..3de233eed3a --- /dev/null +++ b/storage/xtradb/include/pars0grm.h @@ -0,0 +1,236 @@ +/***************************************************************************** + +Copyright (c) 1995, 2009, Innobase Oy. All Rights Reserved. +Copyright (c) 1984, 1989, 1990, 2000, 2001, 2002, 2003, 2004 Free Software +Foundation, Inc. + +As a special exception, when this file is copied by Bison into a +Bison output file, you may use that output file without restriction. +This special exception was added by the Free Software Foundation +in version 1.24 of Bison. + +This program is free software; you can redistribute it and/or modify it under +the terms of the GNU General Public License as published by the Free Software +Foundation; version 2 of the License. + +This program is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + +You 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 + +*****************************************************************************/ + +/* A Bison parser, made by GNU Bison 1.875d. */ + +/* Tokens. */ +#ifndef YYTOKENTYPE +# define YYTOKENTYPE + /* Put the tokens into the symbol table, so that GDB and other debuggers + know about them. */ + enum yytokentype { + PARS_INT_LIT = 258, + PARS_FLOAT_LIT = 259, + PARS_STR_LIT = 260, + PARS_FIXBINARY_LIT = 261, + PARS_BLOB_LIT = 262, + PARS_NULL_LIT = 263, + PARS_ID_TOKEN = 264, + PARS_AND_TOKEN = 265, + PARS_OR_TOKEN = 266, + PARS_NOT_TOKEN = 267, + PARS_GE_TOKEN = 268, + PARS_LE_TOKEN = 269, + PARS_NE_TOKEN = 270, + PARS_PROCEDURE_TOKEN = 271, + PARS_IN_TOKEN = 272, + PARS_OUT_TOKEN = 273, + PARS_BINARY_TOKEN = 274, + PARS_BLOB_TOKEN = 275, + PARS_INT_TOKEN = 276, + PARS_INTEGER_TOKEN = 277, + PARS_FLOAT_TOKEN = 278, + PARS_CHAR_TOKEN = 279, + PARS_IS_TOKEN = 280, + PARS_BEGIN_TOKEN = 281, + PARS_END_TOKEN = 282, + PARS_IF_TOKEN = 283, + PARS_THEN_TOKEN = 284, + PARS_ELSE_TOKEN = 285, + PARS_ELSIF_TOKEN = 286, + PARS_LOOP_TOKEN = 287, + PARS_WHILE_TOKEN = 288, + PARS_RETURN_TOKEN = 289, + PARS_SELECT_TOKEN = 290, + PARS_SUM_TOKEN = 291, + PARS_COUNT_TOKEN = 292, + PARS_DISTINCT_TOKEN = 293, + PARS_FROM_TOKEN = 294, + PARS_WHERE_TOKEN = 295, + PARS_FOR_TOKEN = 296, + PARS_DDOT_TOKEN = 297, + PARS_READ_TOKEN = 298, + PARS_ORDER_TOKEN = 299, + PARS_BY_TOKEN = 300, + PARS_ASC_TOKEN = 301, + PARS_DESC_TOKEN = 302, + PARS_INSERT_TOKEN = 303, + PARS_INTO_TOKEN = 304, + PARS_VALUES_TOKEN = 305, + PARS_UPDATE_TOKEN = 306, + PARS_SET_TOKEN = 307, + PARS_DELETE_TOKEN = 308, + PARS_CURRENT_TOKEN = 309, + PARS_OF_TOKEN = 310, + PARS_CREATE_TOKEN = 311, + PARS_TABLE_TOKEN = 312, + PARS_INDEX_TOKEN = 313, + PARS_UNIQUE_TOKEN = 314, + PARS_CLUSTERED_TOKEN = 315, + PARS_DOES_NOT_FIT_IN_MEM_TOKEN = 316, + PARS_ON_TOKEN = 317, + PARS_ASSIGN_TOKEN = 318, + PARS_DECLARE_TOKEN = 319, + PARS_CURSOR_TOKEN = 320, + PARS_SQL_TOKEN = 321, + PARS_OPEN_TOKEN = 322, + PARS_FETCH_TOKEN = 323, + PARS_CLOSE_TOKEN = 324, + PARS_NOTFOUND_TOKEN = 325, + PARS_TO_CHAR_TOKEN = 326, + PARS_TO_NUMBER_TOKEN = 327, + PARS_TO_BINARY_TOKEN = 328, + PARS_BINARY_TO_NUMBER_TOKEN = 329, + PARS_SUBSTR_TOKEN = 330, + PARS_REPLSTR_TOKEN = 331, + PARS_CONCAT_TOKEN = 332, + PARS_INSTR_TOKEN = 333, + PARS_LENGTH_TOKEN = 334, + PARS_SYSDATE_TOKEN = 335, + PARS_PRINTF_TOKEN = 336, + PARS_ASSERT_TOKEN = 337, + PARS_RND_TOKEN = 338, + PARS_RND_STR_TOKEN = 339, + PARS_ROW_PRINTF_TOKEN = 340, + PARS_COMMIT_TOKEN = 341, + PARS_ROLLBACK_TOKEN = 342, + PARS_WORK_TOKEN = 343, + PARS_UNSIGNED_TOKEN = 344, + PARS_EXIT_TOKEN = 345, + PARS_FUNCTION_TOKEN = 346, + PARS_LOCK_TOKEN = 347, + PARS_SHARE_TOKEN = 348, + PARS_MODE_TOKEN = 349, + NEG = 350 + }; +#endif +#define PARS_INT_LIT 258 +#define PARS_FLOAT_LIT 259 +#define PARS_STR_LIT 260 +#define PARS_FIXBINARY_LIT 261 +#define PARS_BLOB_LIT 262 +#define PARS_NULL_LIT 263 +#define PARS_ID_TOKEN 264 +#define PARS_AND_TOKEN 265 +#define PARS_OR_TOKEN 266 +#define PARS_NOT_TOKEN 267 +#define PARS_GE_TOKEN 268 +#define PARS_LE_TOKEN 269 +#define PARS_NE_TOKEN 270 +#define PARS_PROCEDURE_TOKEN 271 +#define PARS_IN_TOKEN 272 +#define PARS_OUT_TOKEN 273 +#define PARS_BINARY_TOKEN 274 +#define PARS_BLOB_TOKEN 275 +#define PARS_INT_TOKEN 276 +#define PARS_INTEGER_TOKEN 277 +#define PARS_FLOAT_TOKEN 278 +#define PARS_CHAR_TOKEN 279 +#define PARS_IS_TOKEN 280 +#define PARS_BEGIN_TOKEN 281 +#define PARS_END_TOKEN 282 +#define PARS_IF_TOKEN 283 +#define PARS_THEN_TOKEN 284 +#define PARS_ELSE_TOKEN 285 +#define PARS_ELSIF_TOKEN 286 +#define PARS_LOOP_TOKEN 287 +#define PARS_WHILE_TOKEN 288 +#define PARS_RETURN_TOKEN 289 +#define PARS_SELECT_TOKEN 290 +#define PARS_SUM_TOKEN 291 +#define PARS_COUNT_TOKEN 292 +#define PARS_DISTINCT_TOKEN 293 +#define PARS_FROM_TOKEN 294 +#define PARS_WHERE_TOKEN 295 +#define PARS_FOR_TOKEN 296 +#define PARS_DDOT_TOKEN 297 +#define PARS_READ_TOKEN 298 +#define PARS_ORDER_TOKEN 299 +#define PARS_BY_TOKEN 300 +#define PARS_ASC_TOKEN 301 +#define PARS_DESC_TOKEN 302 +#define PARS_INSERT_TOKEN 303 +#define PARS_INTO_TOKEN 304 +#define PARS_VALUES_TOKEN 305 +#define PARS_UPDATE_TOKEN 306 +#define PARS_SET_TOKEN 307 +#define PARS_DELETE_TOKEN 308 +#define PARS_CURRENT_TOKEN 309 +#define PARS_OF_TOKEN 310 +#define PARS_CREATE_TOKEN 311 +#define PARS_TABLE_TOKEN 312 +#define PARS_INDEX_TOKEN 313 +#define PARS_UNIQUE_TOKEN 314 +#define PARS_CLUSTERED_TOKEN 315 +#define PARS_DOES_NOT_FIT_IN_MEM_TOKEN 316 +#define PARS_ON_TOKEN 317 +#define PARS_ASSIGN_TOKEN 318 +#define PARS_DECLARE_TOKEN 319 +#define PARS_CURSOR_TOKEN 320 +#define PARS_SQL_TOKEN 321 +#define PARS_OPEN_TOKEN 322 +#define PARS_FETCH_TOKEN 323 +#define PARS_CLOSE_TOKEN 324 +#define PARS_NOTFOUND_TOKEN 325 +#define PARS_TO_CHAR_TOKEN 326 +#define PARS_TO_NUMBER_TOKEN 327 +#define PARS_TO_BINARY_TOKEN 328 +#define PARS_BINARY_TO_NUMBER_TOKEN 329 +#define PARS_SUBSTR_TOKEN 330 +#define PARS_REPLSTR_TOKEN 331 +#define PARS_CONCAT_TOKEN 332 +#define PARS_INSTR_TOKEN 333 +#define PARS_LENGTH_TOKEN 334 +#define PARS_SYSDATE_TOKEN 335 +#define PARS_PRINTF_TOKEN 336 +#define PARS_ASSERT_TOKEN 337 +#define PARS_RND_TOKEN 338 +#define PARS_RND_STR_TOKEN 339 +#define PARS_ROW_PRINTF_TOKEN 340 +#define PARS_COMMIT_TOKEN 341 +#define PARS_ROLLBACK_TOKEN 342 +#define PARS_WORK_TOKEN 343 +#define PARS_UNSIGNED_TOKEN 344 +#define PARS_EXIT_TOKEN 345 +#define PARS_FUNCTION_TOKEN 346 +#define PARS_LOCK_TOKEN 347 +#define PARS_SHARE_TOKEN 348 +#define PARS_MODE_TOKEN 349 +#define NEG 350 + + + + +#if ! defined (YYSTYPE) && ! defined (YYSTYPE_IS_DECLARED) +typedef int YYSTYPE; +# define yystype YYSTYPE /* obsolescent; will be withdrawn */ +# define YYSTYPE_IS_DECLARED 1 +# define YYSTYPE_IS_TRIVIAL 1 +#endif + +extern YYSTYPE yylval; + + + diff --git a/storage/xtradb/include/pars0opt.h b/storage/xtradb/include/pars0opt.h new file mode 100644 index 00000000000..02524e9d893 --- /dev/null +++ b/storage/xtradb/include/pars0opt.h @@ -0,0 +1,74 @@ +/***************************************************************************** + +Copyright (c) 1997, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/****************************************************** +Simple SQL optimizer + +Created 12/21/1997 Heikki Tuuri +*******************************************************/ + +#ifndef pars0opt_h +#define pars0opt_h + +#include "univ.i" +#include "que0types.h" +#include "usr0types.h" +#include "pars0sym.h" +#include "dict0types.h" +#include "row0sel.h" + +/*********************************************************************** +Optimizes a select. Decides which indexes to tables to use. The tables +are accessed in the order that they were written to the FROM part in the +select statement. */ +UNIV_INTERN +void +opt_search_plan( +/*============*/ + sel_node_t* sel_node); /* in: parsed select node */ +/*********************************************************************** +Looks for occurrences of the columns of the table in the query subgraph and +adds them to the list of columns if an occurrence of the same column does not +already exist in the list. If the column is already in the list, puts a value +indirection to point to the occurrence in the column list, except if the +column occurrence we are looking at is in the column list, in which case +nothing is done. */ +UNIV_INTERN +void +opt_find_all_cols( +/*==============*/ + ibool copy_val, /* in: if TRUE, new found columns are + added as columns to copy */ + dict_index_t* index, /* in: index to use */ + sym_node_list_t* col_list, /* in: base node of a list where + to add new found columns */ + plan_t* plan, /* in: plan or NULL */ + que_node_t* exp); /* in: expression or condition */ +/************************************************************************ +Prints info of a query plan. */ +UNIV_INTERN +void +opt_print_query_plan( +/*=================*/ + sel_node_t* sel_node); /* in: select node */ + +#ifndef UNIV_NONINL +#include "pars0opt.ic" +#endif + +#endif diff --git a/storage/xtradb/include/pars0opt.ic b/storage/xtradb/include/pars0opt.ic new file mode 100644 index 00000000000..35653453b30 --- /dev/null +++ b/storage/xtradb/include/pars0opt.ic @@ -0,0 +1,23 @@ +/***************************************************************************** + +Copyright (c) 1997, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/****************************************************** +Simple SQL optimizer + +Created 12/21/1997 Heikki Tuuri +*******************************************************/ diff --git a/storage/xtradb/include/pars0pars.h b/storage/xtradb/include/pars0pars.h new file mode 100644 index 00000000000..865f24f7bf4 --- /dev/null +++ b/storage/xtradb/include/pars0pars.h @@ -0,0 +1,747 @@ +/***************************************************************************** + +Copyright (c) 1996, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/****************************************************** +SQL parser + +Created 11/19/1996 Heikki Tuuri +*******************************************************/ + +#ifndef pars0pars_h +#define pars0pars_h + +#include "univ.i" +#include "que0types.h" +#include "usr0types.h" +#include "pars0types.h" +#include "row0types.h" +#include "trx0types.h" +#include "ut0vec.h" + +/* Type of the user functions. The first argument is always InnoDB-supplied +and varies in type, while 'user_arg' is a user-supplied argument. The +meaning of the return type also varies. See the individual use cases, e.g. +the FETCH statement, for details on them. */ +typedef void* (*pars_user_func_cb_t)(void* arg, void* user_arg); + +extern int yydebug; + +/* If the following is set TRUE, the lexer will print the SQL string +as it tokenizes it */ + +#ifdef UNIV_SQL_DEBUG +extern ibool pars_print_lexed; +#endif /* UNIV_SQL_DEBUG */ + +/* Global variable used while parsing a single procedure or query : the code is +NOT re-entrant */ +extern sym_tab_t* pars_sym_tab_global; + +extern pars_res_word_t pars_to_char_token; +extern pars_res_word_t pars_to_number_token; +extern pars_res_word_t pars_to_binary_token; +extern pars_res_word_t pars_binary_to_number_token; +extern pars_res_word_t pars_substr_token; +extern pars_res_word_t pars_replstr_token; +extern pars_res_word_t pars_concat_token; +extern pars_res_word_t pars_length_token; +extern pars_res_word_t pars_instr_token; +extern pars_res_word_t pars_sysdate_token; +extern pars_res_word_t pars_printf_token; +extern pars_res_word_t pars_assert_token; +extern pars_res_word_t pars_rnd_token; +extern pars_res_word_t pars_rnd_str_token; +extern pars_res_word_t pars_count_token; +extern pars_res_word_t pars_sum_token; +extern pars_res_word_t pars_distinct_token; +extern pars_res_word_t pars_binary_token; +extern pars_res_word_t pars_blob_token; +extern pars_res_word_t pars_int_token; +extern pars_res_word_t pars_char_token; +extern pars_res_word_t pars_float_token; +extern pars_res_word_t pars_update_token; +extern pars_res_word_t pars_asc_token; +extern pars_res_word_t pars_desc_token; +extern pars_res_word_t pars_open_token; +extern pars_res_word_t pars_close_token; +extern pars_res_word_t pars_share_token; +extern pars_res_word_t pars_unique_token; +extern pars_res_word_t pars_clustered_token; + +extern ulint pars_star_denoter; + +/* Procedure parameter types */ +#define PARS_INPUT 0 +#define PARS_OUTPUT 1 +#define PARS_NOT_PARAM 2 + +int +yyparse(void); + +/***************************************************************** +Parses an SQL string returning the query graph. */ +UNIV_INTERN +que_t* +pars_sql( +/*=====*/ + /* out, own: the query graph */ + pars_info_t* info, /* in: extra information, or NULL */ + const char* str); /* in: SQL string */ +/***************************************************************** +Retrieves characters to the lexical analyzer. */ +UNIV_INTERN +void +pars_get_lex_chars( +/*===============*/ + char* buf, /* in/out: buffer where to copy */ + int* result, /* out: number of characters copied or EOF */ + int max_size); /* in: maximum number of characters which fit + in the buffer */ +/***************************************************************** +Called by yyparse on error. */ +UNIV_INTERN +void +yyerror( +/*====*/ + const char* s); /* in: error message string */ +/************************************************************************* +Parses a variable declaration. */ +UNIV_INTERN +sym_node_t* +pars_variable_declaration( +/*======================*/ + /* out, own: symbol table node of type + SYM_VAR */ + sym_node_t* node, /* in: symbol table node allocated for the + id of the variable */ + pars_res_word_t* type); /* in: pointer to a type token */ +/************************************************************************* +Parses a function expression. */ +UNIV_INTERN +func_node_t* +pars_func( +/*======*/ + /* out, own: function node in a query tree */ + que_node_t* res_word,/* in: function name reserved word */ + que_node_t* arg); /* in: first argument in the argument list */ +/************************************************************************* +Parses an operator expression. */ +UNIV_INTERN +func_node_t* +pars_op( +/*====*/ + /* out, own: function node in a query tree */ + int func, /* in: operator token code */ + que_node_t* arg1, /* in: first argument */ + que_node_t* arg2); /* in: second argument or NULL for an unary + operator */ +/************************************************************************* +Parses an ORDER BY clause. Order by a single column only is supported. */ +UNIV_INTERN +order_node_t* +pars_order_by( +/*==========*/ + /* out, own: order-by node in a query tree */ + sym_node_t* column, /* in: column name */ + pars_res_word_t* asc); /* in: &pars_asc_token or pars_desc_token */ +/************************************************************************* +Parses a select list; creates a query graph node for the whole SELECT +statement. */ +UNIV_INTERN +sel_node_t* +pars_select_list( +/*=============*/ + /* out, own: select node in a query + tree */ + que_node_t* select_list, /* in: select list */ + sym_node_t* into_list); /* in: variables list or NULL */ +/************************************************************************* +Parses a cursor declaration. */ +UNIV_INTERN +que_node_t* +pars_cursor_declaration( +/*====================*/ + /* out: sym_node */ + sym_node_t* sym_node, /* in: cursor id node in the symbol + table */ + sel_node_t* select_node); /* in: select node */ +/************************************************************************* +Parses a function declaration. */ +UNIV_INTERN +que_node_t* +pars_function_declaration( +/*======================*/ + /* out: sym_node */ + sym_node_t* sym_node); /* in: function id node in the symbol + table */ +/************************************************************************* +Parses a select statement. */ +UNIV_INTERN +sel_node_t* +pars_select_statement( +/*==================*/ + /* out, own: select node in a query + tree */ + sel_node_t* select_node, /* in: select node already containing + the select list */ + sym_node_t* table_list, /* in: table list */ + que_node_t* search_cond, /* in: search condition or NULL */ + pars_res_word_t* for_update, /* in: NULL or &pars_update_token */ + pars_res_word_t* consistent_read,/* in: NULL or + &pars_consistent_token */ + order_node_t* order_by); /* in: NULL or an order-by node */ +/************************************************************************* +Parses a column assignment in an update. */ +UNIV_INTERN +col_assign_node_t* +pars_column_assignment( +/*===================*/ + /* out: column assignment node */ + sym_node_t* column, /* in: column to assign */ + que_node_t* exp); /* in: value to assign */ +/************************************************************************* +Parses a delete or update statement start. */ +UNIV_INTERN +upd_node_t* +pars_update_statement_start( +/*========================*/ + /* out, own: update node in a query + tree */ + ibool is_delete, /* in: TRUE if delete */ + sym_node_t* table_sym, /* in: table name node */ + col_assign_node_t* col_assign_list);/* in: column assignment list, NULL + if delete */ +/************************************************************************* +Parses an update or delete statement. */ +UNIV_INTERN +upd_node_t* +pars_update_statement( +/*==================*/ + /* out, own: update node in a query + tree */ + upd_node_t* node, /* in: update node */ + sym_node_t* cursor_sym, /* in: pointer to a cursor entry in + the symbol table or NULL */ + que_node_t* search_cond); /* in: search condition or NULL */ +/************************************************************************* +Parses an insert statement. */ +UNIV_INTERN +ins_node_t* +pars_insert_statement( +/*==================*/ + /* out, own: update node in a query + tree */ + sym_node_t* table_sym, /* in: table name node */ + que_node_t* values_list, /* in: value expression list or NULL */ + sel_node_t* select); /* in: select condition or NULL */ +/************************************************************************* +Parses a procedure parameter declaration. */ +UNIV_INTERN +sym_node_t* +pars_parameter_declaration( +/*=======================*/ + /* out, own: symbol table node of type + SYM_VAR */ + sym_node_t* node, /* in: symbol table node allocated for the + id of the parameter */ + ulint param_type, + /* in: PARS_INPUT or PARS_OUTPUT */ + pars_res_word_t* type); /* in: pointer to a type token */ +/************************************************************************* +Parses an elsif element. */ +UNIV_INTERN +elsif_node_t* +pars_elsif_element( +/*===============*/ + /* out: elsif node */ + que_node_t* cond, /* in: if-condition */ + que_node_t* stat_list); /* in: statement list */ +/************************************************************************* +Parses an if-statement. */ +UNIV_INTERN +if_node_t* +pars_if_statement( +/*==============*/ + /* out: if-statement node */ + que_node_t* cond, /* in: if-condition */ + que_node_t* stat_list, /* in: statement list */ + que_node_t* else_part); /* in: else-part statement list */ +/************************************************************************* +Parses a for-loop-statement. */ +UNIV_INTERN +for_node_t* +pars_for_statement( +/*===============*/ + /* out: for-statement node */ + sym_node_t* loop_var, /* in: loop variable */ + que_node_t* loop_start_limit,/* in: loop start expression */ + que_node_t* loop_end_limit, /* in: loop end expression */ + que_node_t* stat_list); /* in: statement list */ +/************************************************************************* +Parses a while-statement. */ +UNIV_INTERN +while_node_t* +pars_while_statement( +/*=================*/ + /* out: while-statement node */ + que_node_t* cond, /* in: while-condition */ + que_node_t* stat_list); /* in: statement list */ +/************************************************************************* +Parses an exit statement. */ +UNIV_INTERN +exit_node_t* +pars_exit_statement(void); +/*=====================*/ + /* out: exit statement node */ +/************************************************************************* +Parses a return-statement. */ +UNIV_INTERN +return_node_t* +pars_return_statement(void); +/*=======================*/ + /* out: return-statement node */ +/************************************************************************* +Parses a procedure call. */ +UNIV_INTERN +func_node_t* +pars_procedure_call( +/*================*/ + /* out: function node */ + que_node_t* res_word,/* in: procedure name reserved word */ + que_node_t* args); /* in: argument list */ +/************************************************************************* +Parses an assignment statement. */ +UNIV_INTERN +assign_node_t* +pars_assignment_statement( +/*======================*/ + /* out: assignment statement node */ + sym_node_t* var, /* in: variable to assign */ + que_node_t* val); /* in: value to assign */ +/************************************************************************* +Parses a fetch statement. into_list or user_func (but not both) must be +non-NULL. */ +UNIV_INTERN +fetch_node_t* +pars_fetch_statement( +/*=================*/ + /* out: fetch statement node */ + sym_node_t* cursor, /* in: cursor node */ + sym_node_t* into_list, /* in: variables to set, or NULL */ + sym_node_t* user_func); /* in: user function name, or NULL */ +/************************************************************************* +Parses an open or close cursor statement. */ +UNIV_INTERN +open_node_t* +pars_open_statement( +/*================*/ + /* out: fetch statement node */ + ulint type, /* in: ROW_SEL_OPEN_CURSOR + or ROW_SEL_CLOSE_CURSOR */ + sym_node_t* cursor); /* in: cursor node */ +/************************************************************************* +Parses a row_printf-statement. */ +UNIV_INTERN +row_printf_node_t* +pars_row_printf_statement( +/*======================*/ + /* out: row_printf-statement node */ + sel_node_t* sel_node); /* in: select node */ +/************************************************************************* +Parses a commit statement. */ +UNIV_INTERN +commit_node_t* +pars_commit_statement(void); +/*=======================*/ +/************************************************************************* +Parses a rollback statement. */ +UNIV_INTERN +roll_node_t* +pars_rollback_statement(void); +/*=========================*/ +/************************************************************************* +Parses a column definition at a table creation. */ +UNIV_INTERN +sym_node_t* +pars_column_def( +/*============*/ + /* out: column sym table + node */ + sym_node_t* sym_node, /* in: column node in the + symbol table */ + pars_res_word_t* type, /* in: data type */ + sym_node_t* len, /* in: length of column, or + NULL */ + void* is_unsigned, /* in: if not NULL, column + is of type UNSIGNED. */ + void* is_not_null); /* in: if not NULL, column + is of type NOT NULL. */ +/************************************************************************* +Parses a table creation operation. */ +UNIV_INTERN +tab_node_t* +pars_create_table( +/*==============*/ + /* out: table create subgraph */ + sym_node_t* table_sym, /* in: table name node in the symbol + table */ + sym_node_t* column_defs, /* in: list of column names */ + void* not_fit_in_memory);/* in: a non-NULL pointer means that + this is a table which in simulations + should be simulated as not fitting + in memory; thread is put to sleep + to simulate disk accesses; NOTE that + this flag is not stored to the data + dictionary on disk, and the database + will forget about non-NULL value if + it has to reload the table definition + from disk */ +/************************************************************************* +Parses an index creation operation. */ +UNIV_INTERN +ind_node_t* +pars_create_index( +/*==============*/ + /* out: index create subgraph */ + pars_res_word_t* unique_def, /* in: not NULL if a unique index */ + pars_res_word_t* clustered_def, /* in: not NULL if a clustered index */ + sym_node_t* index_sym, /* in: index name node in the symbol + table */ + sym_node_t* table_sym, /* in: table name node in the symbol + table */ + sym_node_t* column_list); /* in: list of column names */ +/************************************************************************* +Parses a procedure definition. */ +UNIV_INTERN +que_fork_t* +pars_procedure_definition( +/*======================*/ + /* out: query fork node */ + sym_node_t* sym_node, /* in: procedure id node in the symbol + table */ + sym_node_t* param_list, /* in: parameter declaration list */ + que_node_t* stat_list); /* in: statement list */ + +/***************************************************************** +Parses a stored procedure call, when this is not within another stored +procedure, that is, the client issues a procedure call directly. +In MySQL/InnoDB, stored InnoDB procedures are invoked via the +parsed procedure tree, not via InnoDB SQL, so this function is not used. */ +UNIV_INTERN +que_fork_t* +pars_stored_procedure_call( +/*=======================*/ + /* out: query graph */ + sym_node_t* sym_node); /* in: stored procedure name */ +/********************************************************************** +Completes a query graph by adding query thread and fork nodes +above it and prepares the graph for running. The fork created is of +type QUE_FORK_MYSQL_INTERFACE. */ +UNIV_INTERN +que_thr_t* +pars_complete_graph_for_exec( +/*=========================*/ + /* out: query thread node to run */ + que_node_t* node, /* in: root node for an incomplete + query graph */ + trx_t* trx, /* in: transaction handle */ + mem_heap_t* heap); /* in: memory heap from which allocated */ + +/******************************************************************** +Create parser info struct.*/ +UNIV_INTERN +pars_info_t* +pars_info_create(void); +/*==================*/ + /* out, own: info struct */ + +/******************************************************************** +Free info struct and everything it contains.*/ +UNIV_INTERN +void +pars_info_free( +/*===========*/ + pars_info_t* info); /* in: info struct */ + +/******************************************************************** +Add bound literal. */ +UNIV_INTERN +void +pars_info_add_literal( +/*==================*/ + pars_info_t* info, /* in: info struct */ + const char* name, /* in: name */ + const void* address, /* in: address */ + ulint length, /* in: length of data */ + ulint type, /* in: type, e.g. DATA_FIXBINARY */ + ulint prtype); /* in: precise type, e.g. + DATA_UNSIGNED */ + +/******************************************************************** +Equivalent to pars_info_add_literal(info, name, str, strlen(str), +DATA_VARCHAR, DATA_ENGLISH). */ +UNIV_INTERN +void +pars_info_add_str_literal( +/*======================*/ + pars_info_t* info, /* in: info struct */ + const char* name, /* in: name */ + const char* str); /* in: string */ + +/******************************************************************** +Equivalent to: + +char buf[4]; +mach_write_to_4(buf, val); +pars_info_add_literal(info, name, buf, 4, DATA_INT, 0); + +except that the buffer is dynamically allocated from the info struct's +heap. */ +UNIV_INTERN +void +pars_info_add_int4_literal( +/*=======================*/ + pars_info_t* info, /* in: info struct */ + const char* name, /* in: name */ + lint val); /* in: value */ + +/******************************************************************** +Equivalent to: + +char buf[8]; +mach_write_to_8(buf, val); +pars_info_add_literal(info, name, buf, 8, DATA_BINARY, 0); + +except that the buffer is dynamically allocated from the info struct's +heap. */ +UNIV_INTERN +void +pars_info_add_dulint_literal( +/*=========================*/ + pars_info_t* info, /* in: info struct */ + const char* name, /* in: name */ + dulint val); /* in: value */ +/******************************************************************** +Add user function. */ +UNIV_INTERN +void +pars_info_add_function( +/*===================*/ + pars_info_t* info, /* in: info struct */ + const char* name, /* in: function name */ + pars_user_func_cb_t func, /* in: function address */ + void* arg); /* in: user-supplied argument */ + +/******************************************************************** +Add bound id. */ +UNIV_INTERN +void +pars_info_add_id( +/*=============*/ + pars_info_t* info, /* in: info struct */ + const char* name, /* in: name */ + const char* id); /* in: id */ + +/******************************************************************** +Get user function with the given name.*/ +UNIV_INTERN +pars_user_func_t* +pars_info_get_user_func( +/*====================*/ + /* out: user func, or NULL if not + found */ + pars_info_t* info, /* in: info struct */ + const char* name); /* in: function name to find*/ + +/******************************************************************** +Get bound literal with the given name.*/ +UNIV_INTERN +pars_bound_lit_t* +pars_info_get_bound_lit( +/*====================*/ + /* out: bound literal, or NULL if + not found */ + pars_info_t* info, /* in: info struct */ + const char* name); /* in: bound literal name to find */ + +/******************************************************************** +Get bound id with the given name.*/ +UNIV_INTERN +pars_bound_id_t* +pars_info_get_bound_id( +/*===================*/ + /* out: bound id, or NULL if not + found */ + pars_info_t* info, /* in: info struct */ + const char* name); /* in: bound id name to find */ + + +/* Extra information supplied for pars_sql(). */ +struct pars_info_struct { + mem_heap_t* heap; /* our own memory heap */ + + ib_vector_t* funcs; /* user functions, or NUll + (pars_user_func_t*) */ + ib_vector_t* bound_lits; /* bound literals, or NULL + (pars_bound_lit_t*) */ + ib_vector_t* bound_ids; /* bound ids, or NULL + (pars_bound_id_t*) */ + + ibool graph_owns_us; /* if TRUE (which is the default), + que_graph_free() will free us */ +}; + +/* User-supplied function and argument. */ +struct pars_user_func_struct { + const char* name; /* function name */ + pars_user_func_cb_t func; /* function address */ + void* arg; /* user-supplied argument */ +}; + +/* Bound literal. */ +struct pars_bound_lit_struct { + const char* name; /* name */ + const void* address; /* address */ + ulint length; /* length of data */ + ulint type; /* type, e.g. DATA_FIXBINARY */ + ulint prtype; /* precise type, e.g. DATA_UNSIGNED */ +}; + +/* Bound id. */ +struct pars_bound_id_struct { + const char* name; /* name */ + const char* id; /* id */ +}; + +/* Struct used to denote a reserved word in a parsing tree */ +struct pars_res_word_struct{ + int code; /* the token code for the reserved word from + pars0grm.h */ +}; + +/* A predefined function or operator node in a parsing tree; this construct +is also used for some non-functions like the assignment ':=' */ +struct func_node_struct{ + que_common_t common; /* type: QUE_NODE_FUNC */ + int func; /* token code of the function name */ + ulint class; /* class of the function */ + que_node_t* args; /* argument(s) of the function */ + UT_LIST_NODE_T(func_node_t) cond_list; + /* list of comparison conditions; defined + only for comparison operator nodes except, + presently, for OPT_SCROLL_TYPE ones */ + UT_LIST_NODE_T(func_node_t) func_node_list; + /* list of function nodes in a parsed + query graph */ +}; + +/* An order-by node in a select */ +struct order_node_struct{ + que_common_t common; /* type: QUE_NODE_ORDER */ + sym_node_t* column; /* order-by column */ + ibool asc; /* TRUE if ascending, FALSE if descending */ +}; + +/* Procedure definition node */ +struct proc_node_struct{ + que_common_t common; /* type: QUE_NODE_PROC */ + sym_node_t* proc_id; /* procedure name symbol in the symbol + table of this same procedure */ + sym_node_t* param_list; /* input and output parameters */ + que_node_t* stat_list; /* statement list */ + sym_tab_t* sym_tab; /* symbol table of this procedure */ +}; + +/* elsif-element node */ +struct elsif_node_struct{ + que_common_t common; /* type: QUE_NODE_ELSIF */ + que_node_t* cond; /* if condition */ + que_node_t* stat_list; /* statement list */ +}; + +/* if-statement node */ +struct if_node_struct{ + que_common_t common; /* type: QUE_NODE_IF */ + que_node_t* cond; /* if condition */ + que_node_t* stat_list; /* statement list */ + que_node_t* else_part; /* else-part statement list */ + elsif_node_t* elsif_list; /* elsif element list */ +}; + +/* while-statement node */ +struct while_node_struct{ + que_common_t common; /* type: QUE_NODE_WHILE */ + que_node_t* cond; /* while condition */ + que_node_t* stat_list; /* statement list */ +}; + +/* for-loop-statement node */ +struct for_node_struct{ + que_common_t common; /* type: QUE_NODE_FOR */ + sym_node_t* loop_var; /* loop variable: this is the + dereferenced symbol from the + variable declarations, not the + symbol occurrence in the for loop + definition */ + que_node_t* loop_start_limit;/* initial value of loop variable */ + que_node_t* loop_end_limit; /* end value of loop variable */ + lint loop_end_value; /* evaluated value for the end value: + it is calculated only when the loop + is entered, and will not change within + the loop */ + que_node_t* stat_list; /* statement list */ +}; + +/* exit statement node */ +struct exit_node_struct{ + que_common_t common; /* type: QUE_NODE_EXIT */ +}; + +/* return-statement node */ +struct return_node_struct{ + que_common_t common; /* type: QUE_NODE_RETURN */ +}; + +/* Assignment statement node */ +struct assign_node_struct{ + que_common_t common; /* type: QUE_NODE_ASSIGNMENT */ + sym_node_t* var; /* variable to set */ + que_node_t* val; /* value to assign */ +}; + +/* Column assignment node */ +struct col_assign_node_struct{ + que_common_t common; /* type: QUE_NODE_COL_ASSIGN */ + sym_node_t* col; /* column to set */ + que_node_t* val; /* value to assign */ +}; + +/* Classes of functions */ +#define PARS_FUNC_ARITH 1 /* +, -, *, / */ +#define PARS_FUNC_LOGICAL 2 +#define PARS_FUNC_CMP 3 +#define PARS_FUNC_PREDEFINED 4 /* TO_NUMBER, SUBSTR, ... */ +#define PARS_FUNC_AGGREGATE 5 /* COUNT, DISTINCT, SUM */ +#define PARS_FUNC_OTHER 6 /* these are not real functions, + e.g., := */ + +#ifndef UNIV_NONINL +#include "pars0pars.ic" +#endif + +#endif diff --git a/storage/xtradb/include/pars0pars.ic b/storage/xtradb/include/pars0pars.ic new file mode 100644 index 00000000000..3a55ad86f48 --- /dev/null +++ b/storage/xtradb/include/pars0pars.ic @@ -0,0 +1,23 @@ +/***************************************************************************** + +Copyright (c) 1996, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/****************************************************** +SQL parser + +Created 11/19/1996 Heikki Tuuri +*******************************************************/ diff --git a/storage/xtradb/include/pars0sym.h b/storage/xtradb/include/pars0sym.h new file mode 100644 index 00000000000..69227a2917e --- /dev/null +++ b/storage/xtradb/include/pars0sym.h @@ -0,0 +1,239 @@ +/***************************************************************************** + +Copyright (c) 1997, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/****************************************************** +SQL parser symbol table + +Created 12/15/1997 Heikki Tuuri +*******************************************************/ + +#ifndef pars0sym_h +#define pars0sym_h + +#include "univ.i" +#include "que0types.h" +#include "usr0types.h" +#include "dict0types.h" +#include "pars0types.h" +#include "row0types.h" + +/********************************************************************** +Creates a symbol table for a single stored procedure or query. */ +UNIV_INTERN +sym_tab_t* +sym_tab_create( +/*===========*/ + /* out, own: symbol table */ + mem_heap_t* heap); /* in: memory heap where to create */ +/********************************************************************** +Frees the memory allocated dynamically AFTER parsing phase for variables +etc. in the symbol table. Does not free the mem heap where the table was +originally created. Frees also SQL explicit cursor definitions. */ +UNIV_INTERN +void +sym_tab_free_private( +/*=================*/ + sym_tab_t* sym_tab); /* in, own: symbol table */ +/********************************************************************** +Adds an integer literal to a symbol table. */ +UNIV_INTERN +sym_node_t* +sym_tab_add_int_lit( +/*================*/ + /* out: symbol table node */ + sym_tab_t* sym_tab, /* in: symbol table */ + ulint val); /* in: integer value */ +/********************************************************************** +Adds an string literal to a symbol table. */ +UNIV_INTERN +sym_node_t* +sym_tab_add_str_lit( +/*================*/ + /* out: symbol table node */ + sym_tab_t* sym_tab, /* in: symbol table */ + byte* str, /* in: string with no quotes around + it */ + ulint len); /* in: string length */ +/********************************************************************** +Add a bound literal to a symbol table. */ +UNIV_INTERN +sym_node_t* +sym_tab_add_bound_lit( +/*==================*/ + /* out: symbol table node */ + sym_tab_t* sym_tab, /* in: symbol table */ + const char* name, /* in: name of bound literal */ + ulint* lit_type); /* out: type of literal (PARS_*_LIT) */ +/********************************************************************** +Adds an SQL null literal to a symbol table. */ +UNIV_INTERN +sym_node_t* +sym_tab_add_null_lit( +/*=================*/ + /* out: symbol table node */ + sym_tab_t* sym_tab); /* in: symbol table */ +/********************************************************************** +Adds an identifier to a symbol table. */ +UNIV_INTERN +sym_node_t* +sym_tab_add_id( +/*===========*/ + /* out: symbol table node */ + sym_tab_t* sym_tab, /* in: symbol table */ + byte* name, /* in: identifier name */ + ulint len); /* in: identifier length */ + +/********************************************************************** +Add a bound identifier to a symbol table. */ +UNIV_INTERN +sym_node_t* +sym_tab_add_bound_id( +/*===========*/ + /* out: symbol table node */ + sym_tab_t* sym_tab, /* in: symbol table */ + const char* name); /* in: name of bound id */ + +#define SYM_CLUST_FIELD_NO 0 +#define SYM_SEC_FIELD_NO 1 + +struct sym_node_struct{ + que_common_t common; /* node type: + QUE_NODE_SYMBOL */ + /* NOTE: if the data field in 'common.val' is not NULL and the symbol + table node is not for a temporary column, the memory for the value has + been allocated from dynamic memory and it should be freed when the + symbol table is discarded */ + + /* 'alias' and 'indirection' are almost the same, but not quite. + 'alias' always points to the primary instance of the variable, while + 'indirection' does the same only if we should use the primary + instance's values for the node's data. This is usually the case, but + when initializing a cursor (e.g., "DECLARE CURSOR c IS SELECT * FROM + t WHERE id = x;"), we copy the values from the primary instance to + the cursor's instance so that they are fixed for the duration of the + cursor, and set 'indirection' to NULL. If we did not, the value of + 'x' could change between fetches and things would break horribly. + + TODO: It would be cleaner to make 'indirection' a boolean field and + always use 'alias' to refer to the primary node. */ + + sym_node_t* indirection; /* pointer to + another symbol table + node which contains + the value for this + node, NULL otherwise */ + sym_node_t* alias; /* pointer to + another symbol table + node for which this + node is an alias, + NULL otherwise */ + UT_LIST_NODE_T(sym_node_t) col_var_list; /* list of table + columns or a list of + input variables for an + explicit cursor */ + ibool copy_val; /* TRUE if a column + and its value should + be copied to dynamic + memory when fetched */ + ulint field_nos[2]; /* if a column, in + the position + SYM_CLUST_FIELD_NO is + the field number in the + clustered index; in + the position + SYM_SEC_FIELD_NO + the field number in the + non-clustered index to + use first; if not found + from the index, then + ULINT_UNDEFINED */ + ibool resolved; /* TRUE if the + meaning of a variable + or a column has been + resolved; for literals + this is always TRUE */ + ulint token_type; /* SYM_VAR, SYM_COLUMN, + SYM_IMPLICIT_VAR, + SYM_LIT, SYM_TABLE, + SYM_CURSOR, ... */ + const char* name; /* name of an id */ + ulint name_len; /* id name length */ + dict_table_t* table; /* table definition + if a table id or a + column id */ + ulint col_no; /* column number if a + column */ + sel_buf_t* prefetch_buf; /* NULL, or a buffer + for cached column + values for prefetched + rows */ + sel_node_t* cursor_def; /* cursor definition + select node if a + named cursor */ + ulint param_type; /* PARS_INPUT, + PARS_OUTPUT, or + PARS_NOT_PARAM if not a + procedure parameter */ + sym_tab_t* sym_table; /* back pointer to + the symbol table */ + UT_LIST_NODE_T(sym_node_t) sym_list; /* list of symbol + nodes */ +}; + +struct sym_tab_struct{ + que_t* query_graph; + /* query graph generated by the + parser */ + const char* sql_string; + /* SQL string to parse */ + size_t string_len; + /* SQL string length */ + int next_char_pos; + /* position of the next character in + sql_string to give to the lexical + analyzer */ + pars_info_t* info; /* extra information, or NULL */ + sym_node_list_t sym_list; + /* list of symbol nodes in the symbol + table */ + UT_LIST_BASE_NODE_T(func_node_t) + func_node_list; + /* list of function nodes in the + parsed query graph */ + mem_heap_t* heap; /* memory heap from which we can + allocate space */ +}; + +/* Types of a symbol table entry */ +#define SYM_VAR 91 /* declared parameter or local + variable of a procedure */ +#define SYM_IMPLICIT_VAR 92 /* storage for a intermediate result + of a calculation */ +#define SYM_LIT 93 /* literal */ +#define SYM_TABLE 94 /* database table name */ +#define SYM_COLUMN 95 /* database table name */ +#define SYM_CURSOR 96 /* named cursor */ +#define SYM_PROCEDURE_NAME 97 /* stored procedure name */ +#define SYM_INDEX 98 /* database index name */ +#define SYM_FUNCTION 99 /* user function name */ + +#ifndef UNIV_NONINL +#include "pars0sym.ic" +#endif + +#endif diff --git a/storage/xtradb/include/pars0sym.ic b/storage/xtradb/include/pars0sym.ic new file mode 100644 index 00000000000..235d6819ae9 --- /dev/null +++ b/storage/xtradb/include/pars0sym.ic @@ -0,0 +1,23 @@ +/***************************************************************************** + +Copyright (c) 1997, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/****************************************************** +SQL parser symbol table + +Created 12/15/1997 Heikki Tuuri +*******************************************************/ diff --git a/storage/xtradb/include/pars0types.h b/storage/xtradb/include/pars0types.h new file mode 100644 index 00000000000..e0902d0611a --- /dev/null +++ b/storage/xtradb/include/pars0types.h @@ -0,0 +1,49 @@ +/***************************************************************************** + +Copyright (c) 1998, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/****************************************************** +SQL parser global types + +Created 1/11/1998 Heikki Tuuri +*******************************************************/ + +#ifndef pars0types_h +#define pars0types_h + +typedef struct pars_info_struct pars_info_t; +typedef struct pars_user_func_struct pars_user_func_t; +typedef struct pars_bound_lit_struct pars_bound_lit_t; +typedef struct pars_bound_id_struct pars_bound_id_t; +typedef struct sym_node_struct sym_node_t; +typedef struct sym_tab_struct sym_tab_t; +typedef struct pars_res_word_struct pars_res_word_t; +typedef struct func_node_struct func_node_t; +typedef struct order_node_struct order_node_t; +typedef struct proc_node_struct proc_node_t; +typedef struct elsif_node_struct elsif_node_t; +typedef struct if_node_struct if_node_t; +typedef struct while_node_struct while_node_t; +typedef struct for_node_struct for_node_t; +typedef struct exit_node_struct exit_node_t; +typedef struct return_node_struct return_node_t; +typedef struct assign_node_struct assign_node_t; +typedef struct col_assign_node_struct col_assign_node_t; + +typedef UT_LIST_BASE_NODE_T(sym_node_t) sym_node_list_t; + +#endif diff --git a/storage/xtradb/include/que0que.h b/storage/xtradb/include/que0que.h new file mode 100644 index 00000000000..a534cb7e464 --- /dev/null +++ b/storage/xtradb/include/que0que.h @@ -0,0 +1,526 @@ +/***************************************************************************** + +Copyright (c) 1996, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/****************************************************** +Query graph + +Created 5/27/1996 Heikki Tuuri +*******************************************************/ + +#ifndef que0que_h +#define que0que_h + +#include "univ.i" +#include "data0data.h" +#include "dict0types.h" +#include "trx0trx.h" +#include "srv0srv.h" +#include "usr0types.h" +#include "que0types.h" +#include "row0types.h" +#include "pars0types.h" + +/* If the following flag is set TRUE, the module will print trace info +of SQL execution in the UNIV_SQL_DEBUG version */ +extern ibool que_trace_on; + +/*************************************************************************** +Adds a query graph to the session's list of graphs. */ +UNIV_INTERN +void +que_graph_publish( +/*==============*/ + que_t* graph, /* in: graph */ + sess_t* sess); /* in: session */ +/*************************************************************************** +Creates a query graph fork node. */ +UNIV_INTERN +que_fork_t* +que_fork_create( +/*============*/ + /* out, own: fork node */ + que_t* graph, /* in: graph, if NULL then this + fork node is assumed to be the + graph root */ + que_node_t* parent, /* in: parent node */ + ulint fork_type, /* in: fork type */ + mem_heap_t* heap); /* in: memory heap where created */ +/*************************************************************************** +Gets the first thr in a fork. */ +UNIV_INLINE +que_thr_t* +que_fork_get_first_thr( +/*===================*/ + que_fork_t* fork); /* in: query fork */ +/*************************************************************************** +Gets the child node of the first thr in a fork. */ +UNIV_INLINE +que_node_t* +que_fork_get_child( +/*===============*/ + que_fork_t* fork); /* in: query fork */ +/*************************************************************************** +Sets the parent of a graph node. */ +UNIV_INLINE +void +que_node_set_parent( +/*================*/ + que_node_t* node, /* in: graph node */ + que_node_t* parent);/* in: parent */ +/*************************************************************************** +Creates a query graph thread node. */ +UNIV_INTERN +que_thr_t* +que_thr_create( +/*===========*/ + /* out, own: query thread node */ + que_fork_t* parent, /* in: parent node, i.e., a fork node */ + mem_heap_t* heap); /* in: memory heap where created */ +/************************************************************************** +Checks if the query graph is in a state where it should be freed, and +frees it in that case. If the session is in a state where it should be +closed, also this is done. */ +UNIV_INTERN +ibool +que_graph_try_free( +/*===============*/ + /* out: TRUE if freed */ + que_t* graph); /* in: query graph */ +/************************************************************************** +Frees a query graph, but not the heap where it was created. Does not free +explicit cursor declarations, they are freed in que_graph_free. */ +UNIV_INTERN +void +que_graph_free_recursive( +/*=====================*/ + que_node_t* node); /* in: query graph node */ +/************************************************************************** +Frees a query graph. */ +UNIV_INTERN +void +que_graph_free( +/*===========*/ + que_t* graph); /* in: query graph; we assume that the memory + heap where this graph was created is private + to this graph: if not, then use + que_graph_free_recursive and free the heap + afterwards! */ +/************************************************************************** +Stops a query thread if graph or trx is in a state requiring it. The +conditions are tested in the order (1) graph, (2) trx. The kernel mutex has +to be reserved. */ +UNIV_INTERN +ibool +que_thr_stop( +/*=========*/ + /* out: TRUE if stopped */ + que_thr_t* thr); /* in: query thread */ +/************************************************************************** +Moves a thread from another state to the QUE_THR_RUNNING state. Increments +the n_active_thrs counters of the query graph and transaction. */ +UNIV_INTERN +void +que_thr_move_to_run_state_for_mysql( +/*================================*/ + que_thr_t* thr, /* in: an query thread */ + trx_t* trx); /* in: transaction */ +/************************************************************************** +A patch for MySQL used to 'stop' a dummy query thread used in MySQL +select, when there is no error or lock wait. */ +UNIV_INTERN +void +que_thr_stop_for_mysql_no_error( +/*============================*/ + que_thr_t* thr, /* in: query thread */ + trx_t* trx); /* in: transaction */ +/************************************************************************** +A patch for MySQL used to 'stop' a dummy query thread used in MySQL. The +query thread is stopped and made inactive, except in the case where +it was put to the lock wait state in lock0lock.c, but the lock has already +been granted or the transaction chosen as a victim in deadlock resolution. */ +UNIV_INTERN +void +que_thr_stop_for_mysql( +/*===================*/ + que_thr_t* thr); /* in: query thread */ +/************************************************************************** +Run a query thread. Handles lock waits. */ +UNIV_INTERN +void +que_run_threads( +/*============*/ + que_thr_t* thr); /* in: query thread */ +/************************************************************************** +After signal handling is finished, returns control to a query graph error +handling routine. (Currently, just returns the control to the root of the +graph so that the graph can communicate an error message to the client.) */ +UNIV_INTERN +void +que_fork_error_handle( +/*==================*/ + trx_t* trx, /* in: trx */ + que_t* fork); /* in: query graph which was run before signal + handling started, NULL not allowed */ +/************************************************************************** +Moves a suspended query thread to the QUE_THR_RUNNING state and releases +a single worker thread to execute it. This function should be used to end +the wait state of a query thread waiting for a lock or a stored procedure +completion. */ +UNIV_INTERN +void +que_thr_end_wait( +/*=============*/ + que_thr_t* thr, /* in: query thread in the + QUE_THR_LOCK_WAIT, + or QUE_THR_PROCEDURE_WAIT, or + QUE_THR_SIG_REPLY_WAIT state */ + que_thr_t** next_thr); /* in/out: next query thread to run; + if the value which is passed in is + a pointer to a NULL pointer, then the + calling function can start running + a new query thread */ +/************************************************************************** +Same as que_thr_end_wait, but no parameter next_thr available. */ +UNIV_INTERN +void +que_thr_end_wait_no_next_thr( +/*=========================*/ + que_thr_t* thr); /* in: query thread in the + QUE_THR_LOCK_WAIT, + or QUE_THR_PROCEDURE_WAIT, or + QUE_THR_SIG_REPLY_WAIT state */ +/************************************************************************** +Starts execution of a command in a query fork. Picks a query thread which +is not in the QUE_THR_RUNNING state and moves it to that state. If none +can be chosen, a situation which may arise in parallelized fetches, NULL +is returned. */ +UNIV_INTERN +que_thr_t* +que_fork_start_command( +/*===================*/ + /* out: a query thread of the graph moved to + QUE_THR_RUNNING state, or NULL; the query + thread should be executed by que_run_threads + by the caller */ + que_fork_t* fork); /* in: a query fork */ +/*************************************************************************** +Gets the trx of a query thread. */ +UNIV_INLINE +trx_t* +thr_get_trx( +/*========*/ + que_thr_t* thr); /* in: query thread */ +/*************************************************************************** +Gets the type of a graph node. */ +UNIV_INLINE +ulint +que_node_get_type( +/*==============*/ + que_node_t* node); /* in: graph node */ +/*************************************************************************** +Gets pointer to the value data type field of a graph node. */ +UNIV_INLINE +dtype_t* +que_node_get_data_type( +/*===================*/ + que_node_t* node); /* in: graph node */ +/*************************************************************************** +Gets pointer to the value dfield of a graph node. */ +UNIV_INLINE +dfield_t* +que_node_get_val( +/*=============*/ + que_node_t* node); /* in: graph node */ +/*************************************************************************** +Gets the value buffer size of a graph node. */ +UNIV_INLINE +ulint +que_node_get_val_buf_size( +/*======================*/ + /* out: val buffer size, not defined if + val.data == NULL in node */ + que_node_t* node); /* in: graph node */ +/*************************************************************************** +Sets the value buffer size of a graph node. */ +UNIV_INLINE +void +que_node_set_val_buf_size( +/*======================*/ + que_node_t* node, /* in: graph node */ + ulint size); /* in: size */ +/************************************************************************* +Gets the next list node in a list of query graph nodes. */ +UNIV_INLINE +que_node_t* +que_node_get_next( +/*==============*/ + que_node_t* node); /* in: node in a list */ +/************************************************************************* +Gets the parent node of a query graph node. */ +UNIV_INLINE +que_node_t* +que_node_get_parent( +/*================*/ + /* out: parent node or NULL */ + que_node_t* node); /* in: node */ +/******************************************************************** +Get the first containing loop node (e.g. while_node_t or for_node_t) for the +given node, or NULL if the node is not within a loop. */ +UNIV_INTERN +que_node_t* +que_node_get_containing_loop_node( +/*==============================*/ + /* out: containing loop node, or NULL. */ + que_node_t* node); /* in: node */ +/************************************************************************* +Catenates a query graph node to a list of them, possible empty list. */ +UNIV_INLINE +que_node_t* +que_node_list_add_last( +/*===================*/ + /* out: one-way list of nodes */ + que_node_t* node_list, /* in: node list, or NULL */ + que_node_t* node); /* in: node */ +/************************************************************************* +Gets a query graph node list length. */ +UNIV_INLINE +ulint +que_node_list_get_len( +/*==================*/ + /* out: length, for NULL list 0 */ + que_node_t* node_list); /* in: node list, or NULL */ +/************************************************************************** +Checks if graph, trx, or session is in a state where the query thread should +be stopped. */ +UNIV_INLINE +ibool +que_thr_peek_stop( +/*==============*/ + /* out: TRUE if should be stopped; NOTE that + if the peek is made without reserving the + kernel mutex, then another peek with the + mutex reserved is necessary before deciding + the actual stopping */ + que_thr_t* thr); /* in: query thread */ +/*************************************************************************** +Returns TRUE if the query graph is for a SELECT statement. */ +UNIV_INLINE +ibool +que_graph_is_select( +/*================*/ + /* out: TRUE if a select */ + que_t* graph); /* in: graph */ +/************************************************************************** +Prints info of an SQL query graph node. */ +UNIV_INTERN +void +que_node_print_info( +/*================*/ + que_node_t* node); /* in: query graph node */ +/************************************************************************* +Evaluate the given SQL */ +UNIV_INTERN +ulint +que_eval_sql( +/*=========*/ + /* out: error code or DB_SUCCESS */ + pars_info_t* info, /* in: info struct, or NULL */ + const char* sql, /* in: SQL string */ + ibool reserve_dict_mutex, + /* in: if TRUE, acquire/release + dict_sys->mutex around call to pars_sql. */ + trx_t* trx); /* in: trx */ + +/* Query graph query thread node: the fields are protected by the kernel +mutex with the exceptions named below */ + +struct que_thr_struct{ + que_common_t common; /* type: QUE_NODE_THR */ + ulint magic_n; /* magic number to catch memory + corruption */ + que_node_t* child; /* graph child node */ + que_t* graph; /* graph where this node belongs */ + ibool is_active; /* TRUE if the thread has been set + to the run state in + que_thr_move_to_run_state, but not + deactivated in + que_thr_dec_reference_count */ + ulint state; /* state of the query thread */ + UT_LIST_NODE_T(que_thr_t) + thrs; /* list of thread nodes of the fork + node */ + UT_LIST_NODE_T(que_thr_t) + trx_thrs; /* lists of threads in wait list of + the trx */ + UT_LIST_NODE_T(que_thr_t) + queue; /* list of runnable thread nodes in + the server task queue */ + /*------------------------------*/ + /* The following fields are private to the OS thread executing the + query thread, and are not protected by the kernel mutex: */ + + que_node_t* run_node; /* pointer to the node where the + subgraph down from this node is + currently executed */ + que_node_t* prev_node; /* pointer to the node from which + the control came */ + ulint resource; /* resource usage of the query thread + thus far */ + ulint lock_state; /* lock state of thread (table or + row) */ +}; + +#define QUE_THR_MAGIC_N 8476583 +#define QUE_THR_MAGIC_FREED 123461526 + +/* Query graph fork node: its fields are protected by the kernel mutex */ +struct que_fork_struct{ + que_common_t common; /* type: QUE_NODE_FORK */ + que_t* graph; /* query graph of this node */ + ulint fork_type; /* fork type */ + ulint n_active_thrs; /* if this is the root of a graph, the + number query threads that have been + started in que_thr_move_to_run_state + but for which que_thr_dec_refer_count + has not yet been called */ + trx_t* trx; /* transaction: this is set only in + the root node */ + ulint state; /* state of the fork node */ + que_thr_t* caller; /* pointer to a possible calling query + thread */ + UT_LIST_BASE_NODE_T(que_thr_t) + thrs; /* list of query threads */ + /*------------------------------*/ + /* The fields in this section are defined only in the root node */ + sym_tab_t* sym_tab; /* symbol table of the query, + generated by the parser, or NULL + if the graph was created 'by hand' */ + pars_info_t* info; /* in: info struct, or NULL */ + /* The following cur_... fields are relevant only in a select graph */ + + ulint cur_end; /* QUE_CUR_NOT_DEFINED, QUE_CUR_START, + QUE_CUR_END */ + ulint cur_pos; /* if there are n rows in the result + set, values 0 and n + 1 mean before + first row, or after last row, depending + on cur_end; values 1...n mean a row + index */ + ibool cur_on_row; /* TRUE if cursor is on a row, i.e., + it is not before the first row or + after the last row */ + dulint n_inserts; /* number of rows inserted */ + dulint n_updates; /* number of rows updated */ + dulint n_deletes; /* number of rows deleted */ + sel_node_t* last_sel_node; /* last executed select node, or NULL + if none */ + UT_LIST_NODE_T(que_fork_t) + graphs; /* list of query graphs of a session + or a stored procedure */ + /*------------------------------*/ + mem_heap_t* heap; /* memory heap where the fork was + created */ + +}; + +/* Query fork (or graph) types */ +#define QUE_FORK_SELECT_NON_SCROLL 1 /* forward-only cursor */ +#define QUE_FORK_SELECT_SCROLL 2 /* scrollable cursor */ +#define QUE_FORK_INSERT 3 +#define QUE_FORK_UPDATE 4 +#define QUE_FORK_ROLLBACK 5 + /* This is really the undo graph used in rollback, + no signal-sending roll_node in this graph */ +#define QUE_FORK_PURGE 6 +#define QUE_FORK_EXECUTE 7 +#define QUE_FORK_PROCEDURE 8 +#define QUE_FORK_PROCEDURE_CALL 9 +#define QUE_FORK_MYSQL_INTERFACE 10 +#define QUE_FORK_RECOVERY 11 + +/* Query fork (or graph) states */ +#define QUE_FORK_ACTIVE 1 +#define QUE_FORK_COMMAND_WAIT 2 +#define QUE_FORK_INVALID 3 +#define QUE_FORK_BEING_FREED 4 + +/* Flag which is ORed to control structure statement node types */ +#define QUE_NODE_CONTROL_STAT 1024 + +/* Query graph node types */ +#define QUE_NODE_LOCK 1 +#define QUE_NODE_INSERT 2 +#define QUE_NODE_UPDATE 4 +#define QUE_NODE_CURSOR 5 +#define QUE_NODE_SELECT 6 +#define QUE_NODE_AGGREGATE 7 +#define QUE_NODE_FORK 8 +#define QUE_NODE_THR 9 +#define QUE_NODE_UNDO 10 +#define QUE_NODE_COMMIT 11 +#define QUE_NODE_ROLLBACK 12 +#define QUE_NODE_PURGE 13 +#define QUE_NODE_CREATE_TABLE 14 +#define QUE_NODE_CREATE_INDEX 15 +#define QUE_NODE_SYMBOL 16 +#define QUE_NODE_RES_WORD 17 +#define QUE_NODE_FUNC 18 +#define QUE_NODE_ORDER 19 +#define QUE_NODE_PROC (20 + QUE_NODE_CONTROL_STAT) +#define QUE_NODE_IF (21 + QUE_NODE_CONTROL_STAT) +#define QUE_NODE_WHILE (22 + QUE_NODE_CONTROL_STAT) +#define QUE_NODE_ASSIGNMENT 23 +#define QUE_NODE_FETCH 24 +#define QUE_NODE_OPEN 25 +#define QUE_NODE_COL_ASSIGNMENT 26 +#define QUE_NODE_FOR (27 + QUE_NODE_CONTROL_STAT) +#define QUE_NODE_RETURN 28 +#define QUE_NODE_ROW_PRINTF 29 +#define QUE_NODE_ELSIF 30 +#define QUE_NODE_CALL 31 +#define QUE_NODE_EXIT 32 + +/* Query thread states */ +#define QUE_THR_RUNNING 1 +#define QUE_THR_PROCEDURE_WAIT 2 +#define QUE_THR_COMPLETED 3 /* in selects this means that the + thread is at the end of its result set + (or start, in case of a scroll cursor); + in other statements, this means the + thread has done its task */ +#define QUE_THR_COMMAND_WAIT 4 +#define QUE_THR_LOCK_WAIT 5 +#define QUE_THR_SIG_REPLY_WAIT 6 +#define QUE_THR_SUSPENDED 7 +#define QUE_THR_ERROR 8 + +/* Query thread lock states */ +#define QUE_THR_LOCK_NOLOCK 0 +#define QUE_THR_LOCK_ROW 1 +#define QUE_THR_LOCK_TABLE 2 + +/* From where the cursor position is counted */ +#define QUE_CUR_NOT_DEFINED 1 +#define QUE_CUR_START 2 +#define QUE_CUR_END 3 + + +#ifndef UNIV_NONINL +#include "que0que.ic" +#endif + +#endif diff --git a/storage/xtradb/include/que0que.ic b/storage/xtradb/include/que0que.ic new file mode 100644 index 00000000000..e9a6b00b9ab --- /dev/null +++ b/storage/xtradb/include/que0que.ic @@ -0,0 +1,275 @@ +/***************************************************************************** + +Copyright (c) 1996, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/****************************************************** +Query graph + +Created 5/27/1996 Heikki Tuuri +*******************************************************/ + +#include "usr0sess.h" + +/*************************************************************************** +Gets the trx of a query thread. */ +UNIV_INLINE +trx_t* +thr_get_trx( +/*========*/ + que_thr_t* thr) /* in: query thread */ +{ + ut_ad(thr); + + return(thr->graph->trx); +} + +/*************************************************************************** +Gets the first thr in a fork. */ +UNIV_INLINE +que_thr_t* +que_fork_get_first_thr( +/*===================*/ + que_fork_t* fork) /* in: query fork */ +{ + return(UT_LIST_GET_FIRST(fork->thrs)); +} + +/*************************************************************************** +Gets the child node of the first thr in a fork. */ +UNIV_INLINE +que_node_t* +que_fork_get_child( +/*===============*/ + que_fork_t* fork) /* in: query fork */ +{ + que_thr_t* thr; + + thr = UT_LIST_GET_FIRST(fork->thrs); + + return(thr->child); +} + +/*************************************************************************** +Gets the type of a graph node. */ +UNIV_INLINE +ulint +que_node_get_type( +/*==============*/ + que_node_t* node) /* in: graph node */ +{ + ut_ad(node); + + return(((que_common_t*)node)->type); +} + +/*************************************************************************** +Gets pointer to the value dfield of a graph node. */ +UNIV_INLINE +dfield_t* +que_node_get_val( +/*=============*/ + que_node_t* node) /* in: graph node */ +{ + ut_ad(node); + + return(&(((que_common_t*)node)->val)); +} + +/*************************************************************************** +Gets the value buffer size of a graph node. */ +UNIV_INLINE +ulint +que_node_get_val_buf_size( +/*======================*/ + /* out: val buffer size, not defined if + val.data == NULL in node */ + que_node_t* node) /* in: graph node */ +{ + ut_ad(node); + + return(((que_common_t*)node)->val_buf_size); +} + +/*************************************************************************** +Sets the value buffer size of a graph node. */ +UNIV_INLINE +void +que_node_set_val_buf_size( +/*======================*/ + que_node_t* node, /* in: graph node */ + ulint size) /* in: size */ +{ + ut_ad(node); + + ((que_common_t*)node)->val_buf_size = size; +} + +/*************************************************************************** +Sets the parent of a graph node. */ +UNIV_INLINE +void +que_node_set_parent( +/*================*/ + que_node_t* node, /* in: graph node */ + que_node_t* parent) /* in: parent */ +{ + ut_ad(node); + + ((que_common_t*)node)->parent = parent; +} + +/*************************************************************************** +Gets pointer to the value data type field of a graph node. */ +UNIV_INLINE +dtype_t* +que_node_get_data_type( +/*===================*/ + que_node_t* node) /* in: graph node */ +{ + ut_ad(node); + + return(dfield_get_type(&((que_common_t*) node)->val)); +} + +/************************************************************************* +Catenates a query graph node to a list of them, possible empty list. */ +UNIV_INLINE +que_node_t* +que_node_list_add_last( +/*===================*/ + /* out: one-way list of nodes */ + que_node_t* node_list, /* in: node list, or NULL */ + que_node_t* node) /* in: node */ +{ + que_common_t* cnode; + que_common_t* cnode2; + + cnode = (que_common_t*) node; + + cnode->brother = NULL; + + if (node_list == NULL) { + + return(node); + } + + cnode2 = (que_common_t*) node_list; + + while (cnode2->brother != NULL) { + cnode2 = (que_common_t*) cnode2->brother; + } + + cnode2->brother = node; + + return(node_list); +} + +/************************************************************************* +Gets the next list node in a list of query graph nodes. */ +UNIV_INLINE +que_node_t* +que_node_get_next( +/*==============*/ + /* out: next node in a list of nodes */ + que_node_t* node) /* in: node in a list */ +{ + return(((que_common_t*)node)->brother); +} + +/************************************************************************* +Gets a query graph node list length. */ +UNIV_INLINE +ulint +que_node_list_get_len( +/*==================*/ + /* out: length, for NULL list 0 */ + que_node_t* node_list) /* in: node list, or NULL */ +{ + const que_common_t* cnode; + ulint len; + + cnode = (const que_common_t*) node_list; + len = 0; + + while (cnode != NULL) { + len++; + cnode = (const que_common_t*) cnode->brother; + } + + return(len); +} + +/************************************************************************* +Gets the parent node of a query graph node. */ +UNIV_INLINE +que_node_t* +que_node_get_parent( +/*================*/ + /* out: parent node or NULL */ + que_node_t* node) /* in: node */ +{ + return(((que_common_t*)node)->parent); +} + +/************************************************************************** +Checks if graph, trx, or session is in a state where the query thread should +be stopped. */ +UNIV_INLINE +ibool +que_thr_peek_stop( +/*==============*/ + /* out: TRUE if should be stopped; NOTE that + if the peek is made without reserving the + kernel mutex, then another peek with the + mutex reserved is necessary before deciding + the actual stopping */ + que_thr_t* thr) /* in: query thread */ +{ + trx_t* trx; + que_t* graph; + + graph = thr->graph; + trx = graph->trx; + + if (graph->state != QUE_FORK_ACTIVE + || trx->que_state == TRX_QUE_LOCK_WAIT + || (UT_LIST_GET_LEN(trx->signals) > 0 + && trx->que_state == TRX_QUE_RUNNING)) { + + return(TRUE); + } + + return(FALSE); +} + +/*************************************************************************** +Returns TRUE if the query graph is for a SELECT statement. */ +UNIV_INLINE +ibool +que_graph_is_select( +/*================*/ + /* out: TRUE if a select */ + que_t* graph) /* in: graph */ +{ + if (graph->fork_type == QUE_FORK_SELECT_SCROLL + || graph->fork_type == QUE_FORK_SELECT_NON_SCROLL) { + + return(TRUE); + } + + return(FALSE); +} diff --git a/storage/xtradb/include/que0types.h b/storage/xtradb/include/que0types.h new file mode 100644 index 00000000000..1d3217fb491 --- /dev/null +++ b/storage/xtradb/include/que0types.h @@ -0,0 +1,59 @@ +/***************************************************************************** + +Copyright (c) 1996, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/****************************************************** +Query graph global types + +Created 5/27/1996 Heikki Tuuri +*******************************************************/ + +#ifndef que0types_h +#define que0types_h + +#include "data0data.h" +#include "dict0types.h" + +/* Pseudotype for all graph nodes */ +typedef void que_node_t; + +typedef struct que_fork_struct que_fork_t; + +/* Query graph root is a fork node */ +typedef que_fork_t que_t; + +typedef struct que_thr_struct que_thr_t; +typedef struct que_common_struct que_common_t; + +/* Common struct at the beginning of each query graph node; the name of this +substruct must be 'common' */ + +struct que_common_struct{ + ulint type; /* query node type */ + que_node_t* parent; /* back pointer to parent node, or NULL */ + que_node_t* brother;/* pointer to a possible brother node */ + dfield_t val; /* evaluated value for an expression */ + ulint val_buf_size; + /* buffer size for the evaluated value data, + if the buffer has been allocated dynamically: + if this field is != 0, and the node is a + symbol node or a function node, then we + have to free the data field in val + explicitly */ +}; + +#endif diff --git a/storage/xtradb/include/read0read.h b/storage/xtradb/include/read0read.h new file mode 100644 index 00000000000..7ea8bdaf8dd --- /dev/null +++ b/storage/xtradb/include/read0read.h @@ -0,0 +1,181 @@ +/***************************************************************************** + +Copyright (c) 1997, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/****************************************************** +Cursor read + +Created 2/16/1997 Heikki Tuuri +*******************************************************/ + +#ifndef read0read_h +#define read0read_h + +#include "univ.i" + + +#include "ut0byte.h" +#include "ut0lst.h" +#include "trx0trx.h" +#include "read0types.h" + +/************************************************************************* +Opens a read view where exactly the transactions serialized before this +point in time are seen in the view. */ +UNIV_INTERN +read_view_t* +read_view_open_now( +/*===============*/ + /* out, own: read view struct */ + dulint cr_trx_id, /* in: trx_id of creating + transaction, or (0, 0) used in + purge */ + mem_heap_t* heap); /* in: memory heap from which + allocated */ +/************************************************************************* +Makes a copy of the oldest existing read view, or opens a new. The view +must be closed with ..._close. */ +UNIV_INTERN +read_view_t* +read_view_oldest_copy_or_open_new( +/*==============================*/ + /* out, own: read view struct */ + dulint cr_trx_id, /* in: trx_id of creating + transaction, or (0, 0) used in + purge */ + mem_heap_t* heap); /* in: memory heap from which + allocated */ +/************************************************************************* +Closes a read view. */ +UNIV_INTERN +void +read_view_close( +/*============*/ + read_view_t* view); /* in: read view */ +/************************************************************************* +Closes a consistent read view for MySQL. This function is called at an SQL +statement end if the trx isolation level is <= TRX_ISO_READ_COMMITTED. */ +UNIV_INTERN +void +read_view_close_for_mysql( +/*======================*/ + trx_t* trx); /* in: trx which has a read view */ +/************************************************************************* +Checks if a read view sees the specified transaction. */ +UNIV_INLINE +ibool +read_view_sees_trx_id( +/*==================*/ + /* out: TRUE if sees */ + read_view_t* view, /* in: read view */ + dulint trx_id);/* in: trx id */ +/************************************************************************* +Prints a read view to stderr. */ +UNIV_INTERN +void +read_view_print( +/*============*/ + read_view_t* view); /* in: read view */ +/************************************************************************* +Create a consistent cursor view for mysql to be used in cursors. In this +consistent read view modifications done by the creating transaction or future +transactions are not visible. */ +UNIV_INTERN +cursor_view_t* +read_cursor_view_create_for_mysql( +/*==============================*/ + trx_t* cr_trx);/* in: trx where cursor view is created */ +/************************************************************************* +Close a given consistent cursor view for mysql and restore global read view +back to a transaction read view. */ +UNIV_INTERN +void +read_cursor_view_close_for_mysql( +/*=============================*/ + trx_t* trx, /* in: trx */ + cursor_view_t* curview); /* in: cursor view to be closed */ +/************************************************************************* +This function sets a given consistent cursor view to a transaction +read view if given consistent cursor view is not NULL. Otherwise, function +restores a global read view to a transaction read view. */ +UNIV_INTERN +void +read_cursor_set_for_mysql( +/*======================*/ + trx_t* trx, /* in: transaction where cursor is set */ + cursor_view_t* curview);/* in: consistent cursor view to be set */ + +/* Read view lists the trx ids of those transactions for which a consistent +read should not see the modifications to the database. */ + +struct read_view_struct{ + ulint type; /* VIEW_NORMAL, VIEW_HIGH_GRANULARITY */ + dulint undo_no; /* (0, 0) or if type is VIEW_HIGH_GRANULARITY + transaction undo_no when this high-granularity + consistent read view was created */ + dulint low_limit_no; /* The view does not need to see the undo + logs for transactions whose transaction number + is strictly smaller (<) than this value: they + can be removed in purge if not needed by other + views */ + dulint low_limit_id; /* The read should not see any transaction + with trx id >= this value */ + dulint up_limit_id; /* The read should see all trx ids which + are strictly smaller (<) than this value */ + ulint n_trx_ids; /* Number of cells in the trx_ids array */ + dulint* trx_ids; /* Additional trx ids which the read should + not see: typically, these are the active + transactions at the time when the read is + serialized, except the reading transaction + itself; the trx ids in this array are in a + descending order */ + dulint creator_trx_id; /* trx id of creating transaction, or + (0, 0) used in purge */ + UT_LIST_NODE_T(read_view_t) view_list; + /* List of read views in trx_sys */ +}; + +/* Read view types */ +#define VIEW_NORMAL 1 /* Normal consistent read view + where transaction does not see changes + made by active transactions except + creating transaction. */ +#define VIEW_HIGH_GRANULARITY 2 /* High-granularity read view where + transaction does not see changes + made by active transactions and own + changes after a point in time when this + read view was created. */ + +/* Implement InnoDB framework to support consistent read views in +cursors. This struct holds both heap where consistent read view +is allocated and pointer to a read view. */ + +struct cursor_view_struct{ + mem_heap_t* heap; + /* Memory heap for the cursor view */ + read_view_t* read_view; + /* Consistent read view of the cursor*/ + ulint n_mysql_tables_in_use; + /* number of Innobase tables used in the + processing of this cursor */ +}; + +#ifndef UNIV_NONINL +#include "read0read.ic" +#endif + +#endif diff --git a/storage/xtradb/include/read0read.ic b/storage/xtradb/include/read0read.ic new file mode 100644 index 00000000000..9fc6af04e88 --- /dev/null +++ b/storage/xtradb/include/read0read.ic @@ -0,0 +1,97 @@ +/***************************************************************************** + +Copyright (c) 1997, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/****************************************************** +Cursor read + +Created 2/16/1997 Heikki Tuuri +*******************************************************/ + +/************************************************************************* +Gets the nth trx id in a read view. */ +UNIV_INLINE +dulint +read_view_get_nth_trx_id( +/*=====================*/ + /* out: trx id */ + read_view_t* view, /* in: read view */ + ulint n) /* in: position */ +{ + ut_ad(n < view->n_trx_ids); + + return(*(view->trx_ids + n)); +} + +/************************************************************************* +Sets the nth trx id in a read view. */ +UNIV_INLINE +void +read_view_set_nth_trx_id( +/*=====================*/ + read_view_t* view, /* in: read view */ + ulint n, /* in: position */ + dulint trx_id) /* in: trx id to set */ +{ + ut_ad(n < view->n_trx_ids); + + *(view->trx_ids + n) = trx_id; +} + +/************************************************************************* +Checks if a read view sees the specified transaction. */ +UNIV_INLINE +ibool +read_view_sees_trx_id( +/*==================*/ + /* out: TRUE if sees */ + read_view_t* view, /* in: read view */ + dulint trx_id) /* in: trx id */ +{ + ulint n_ids; + int cmp; + ulint i; + + if (ut_dulint_cmp(trx_id, view->up_limit_id) < 0) { + + return(TRUE); + } + + if (ut_dulint_cmp(trx_id, view->low_limit_id) >= 0) { + + return(FALSE); + } + + /* We go through the trx ids in the array smallest first: this order + may save CPU time, because if there was a very long running + transaction in the trx id array, its trx id is looked at first, and + the first two comparisons may well decide the visibility of trx_id. */ + + n_ids = view->n_trx_ids; + + for (i = 0; i < n_ids; i++) { + + cmp = ut_dulint_cmp( + trx_id, + read_view_get_nth_trx_id(view, n_ids - i - 1)); + if (cmp <= 0) { + return(cmp < 0); + } + } + + return(TRUE); +} diff --git a/storage/xtradb/include/read0types.h b/storage/xtradb/include/read0types.h new file mode 100644 index 00000000000..44849cbb498 --- /dev/null +++ b/storage/xtradb/include/read0types.h @@ -0,0 +1,31 @@ +/***************************************************************************** + +Copyright (c) 1997, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/****************************************************** +Cursor read + +Created 2/16/1997 Heikki Tuuri +*******************************************************/ + +#ifndef read0types_h +#define read0types_h + +typedef struct read_view_struct read_view_t; +typedef struct cursor_view_struct cursor_view_t; + +#endif diff --git a/storage/xtradb/include/rem0cmp.h b/storage/xtradb/include/rem0cmp.h new file mode 100644 index 00000000000..4687e0ee45e --- /dev/null +++ b/storage/xtradb/include/rem0cmp.h @@ -0,0 +1,206 @@ +/***************************************************************************** + +Copyright (c) 1994, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/*********************************************************************** +Comparison services for records + +Created 7/1/1994 Heikki Tuuri +************************************************************************/ + +#ifndef rem0cmp_h +#define rem0cmp_h + +#include "univ.i" +#include "data0data.h" +#include "data0type.h" +#include "dict0dict.h" +#include "rem0rec.h" + +/***************************************************************** +Returns TRUE if two columns are equal for comparison purposes. */ +UNIV_INTERN +ibool +cmp_cols_are_equal( +/*===============*/ + /* out: TRUE if the columns are + considered equal in comparisons */ + const dict_col_t* col1, /* in: column 1 */ + const dict_col_t* col2, /* in: column 2 */ + ibool check_charsets); + /* in: whether to check charsets */ +/***************************************************************** +This function is used to compare two data fields for which we know the +data type. */ +UNIV_INLINE +int +cmp_data_data( +/*==========*/ + /* out: 1, 0, -1, if data1 is greater, equal, + less than data2, respectively */ + ulint mtype, /* in: main type */ + ulint prtype, /* in: precise type */ + const byte* data1, /* in: data field (== a pointer to a memory + buffer) */ + ulint len1, /* in: data field length or UNIV_SQL_NULL */ + const byte* data2, /* in: data field (== a pointer to a memory + buffer) */ + ulint len2); /* in: data field length or UNIV_SQL_NULL */ +/***************************************************************** +This function is used to compare two data fields for which we know the +data type. */ +UNIV_INTERN +int +cmp_data_data_slow( +/*===============*/ + /* out: 1, 0, -1, if data1 is greater, equal, + less than data2, respectively */ + ulint mtype, /* in: main type */ + ulint prtype, /* in: precise type */ + const byte* data1, /* in: data field (== a pointer to a memory + buffer) */ + ulint len1, /* in: data field length or UNIV_SQL_NULL */ + const byte* data2, /* in: data field (== a pointer to a memory + buffer) */ + ulint len2); /* in: data field length or UNIV_SQL_NULL */ +/***************************************************************** +This function is used to compare two dfields where at least the first +has its data type field set. */ +UNIV_INLINE +int +cmp_dfield_dfield( +/*==============*/ + /* out: 1, 0, -1, if dfield1 is greater, equal, + less than dfield2, respectively */ + const dfield_t* dfield1,/* in: data field; must have type field set */ + const dfield_t* dfield2);/* in: data field */ +/***************************************************************** +This function is used to compare a data tuple to a physical record. +Only dtuple->n_fields_cmp first fields are taken into account for +the the data tuple! If we denote by n = n_fields_cmp, then rec must +have either m >= n fields, or it must differ from dtuple in some of +the m fields rec has. If rec has an externally stored field we do not +compare it but return with value 0 if such a comparison should be +made. */ +UNIV_INTERN +int +cmp_dtuple_rec_with_match( +/*======================*/ + /* out: 1, 0, -1, if dtuple is greater, equal, + less than rec, respectively, when only the + common first fields are compared, or + until the first externally stored field in + rec */ + const dtuple_t* dtuple, /* in: data tuple */ + const rec_t* rec, /* in: physical record which differs from + dtuple in some of the common fields, or which + has an equal number or more fields than + dtuple */ + const ulint* offsets,/* in: array returned by rec_get_offsets() */ + ulint* matched_fields, /* in/out: number of already completely + matched fields; when function returns, + contains the value for current comparison */ + ulint* matched_bytes); /* in/out: number of already matched + bytes within the first field not completely + matched; when function returns, contains the + value for current comparison */ +/****************************************************************** +Compares a data tuple to a physical record. */ +UNIV_INTERN +int +cmp_dtuple_rec( +/*===========*/ + /* out: 1, 0, -1, if dtuple is greater, equal, + less than rec, respectively; see the comments + for cmp_dtuple_rec_with_match */ + const dtuple_t* dtuple, /* in: data tuple */ + const rec_t* rec, /* in: physical record */ + const ulint* offsets);/* in: array returned by rec_get_offsets() */ +/****************************************************************** +Checks if a dtuple is a prefix of a record. The last field in dtuple +is allowed to be a prefix of the corresponding field in the record. */ +UNIV_INTERN +ibool +cmp_dtuple_is_prefix_of_rec( +/*========================*/ + /* out: TRUE if prefix */ + const dtuple_t* dtuple, /* in: data tuple */ + const rec_t* rec, /* in: physical record */ + const ulint* offsets);/* in: array returned by rec_get_offsets() */ +#ifndef UNIV_HOTBACKUP +/***************************************************************** +Compare two physical records that contain the same number of columns, +none of which are stored externally. */ +UNIV_INTERN +int +cmp_rec_rec_simple( +/*===============*/ + /* out: 1, 0 , -1 if rec1 is greater, + equal, less, respectively, than rec2 */ + const rec_t* rec1, /* in: physical record */ + const rec_t* rec2, /* in: physical record */ + const ulint* offsets1,/* in: rec_get_offsets(rec1, index) */ + const ulint* offsets2,/* in: rec_get_offsets(rec2, index) */ + const dict_index_t* index); /* in: data dictionary index */ +#endif /* !UNIV_HOTBACKUP */ +/***************************************************************** +This function is used to compare two physical records. Only the common +first fields are compared, and if an externally stored field is +encountered, then 0 is returned. */ +UNIV_INTERN +int +cmp_rec_rec_with_match( +/*===================*/ + /* out: 1, 0 , -1 if rec1 is greater, equal, + less, respectively, than rec2; only the common + first fields are compared */ + const rec_t* rec1, /* in: physical record */ + const rec_t* rec2, /* in: physical record */ + const ulint* offsets1,/* in: rec_get_offsets(rec1, index) */ + const ulint* offsets2,/* in: rec_get_offsets(rec2, index) */ + dict_index_t* index, /* in: data dictionary index */ + ulint* matched_fields, /* in/out: number of already completely + matched fields; when the function returns, + contains the value the for current + comparison */ + ulint* matched_bytes, /* in/out: number of already matched + bytes within the first field not completely + matched; when the function returns, contains + the value for the current comparison */ + ulint stats_method); +/***************************************************************** +This function is used to compare two physical records. Only the common +first fields are compared. */ +UNIV_INLINE +int +cmp_rec_rec( +/*========*/ + /* out: 1, 0 , -1 if rec1 is greater, equal, + less, respectively, than rec2; only the common + first fields are compared */ + const rec_t* rec1, /* in: physical record */ + const rec_t* rec2, /* in: physical record */ + const ulint* offsets1,/* in: rec_get_offsets(rec1, index) */ + const ulint* offsets2,/* in: rec_get_offsets(rec2, index) */ + dict_index_t* index); /* in: data dictionary index */ + + +#ifndef UNIV_NONINL +#include "rem0cmp.ic" +#endif + +#endif diff --git a/storage/xtradb/include/rem0cmp.ic b/storage/xtradb/include/rem0cmp.ic new file mode 100644 index 00000000000..1abf4142531 --- /dev/null +++ b/storage/xtradb/include/rem0cmp.ic @@ -0,0 +1,92 @@ +/***************************************************************************** + +Copyright (c) 1994, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/*********************************************************************** +Comparison services for records + +Created 7/1/1994 Heikki Tuuri +************************************************************************/ + +/***************************************************************** +This function is used to compare two data fields for which we know the +data type. */ +UNIV_INLINE +int +cmp_data_data( +/*==========*/ + /* out: 1, 0, -1, if data1 is greater, equal, + less than data2, respectively */ + ulint mtype, /* in: main type */ + ulint prtype, /* in: precise type */ + const byte* data1, /* in: data field (== a pointer to a memory + buffer) */ + ulint len1, /* in: data field length or UNIV_SQL_NULL */ + const byte* data2, /* in: data field (== a pointer to a memory + buffer) */ + ulint len2) /* in: data field length or UNIV_SQL_NULL */ +{ + return(cmp_data_data_slow(mtype, prtype, data1, len1, data2, len2)); +} + +/***************************************************************** +This function is used to compare two dfields where at least the first +has its data type field set. */ +UNIV_INLINE +int +cmp_dfield_dfield( +/*==============*/ + /* out: 1, 0, -1, if dfield1 is greater, equal, + less than dfield2, respectively */ + const dfield_t* dfield1,/* in: data field; must have type field set */ + const dfield_t* dfield2)/* in: data field */ +{ + const dtype_t* type; + + ut_ad(dfield_check_typed(dfield1)); + + type = dfield_get_type(dfield1); + + return(cmp_data_data(type->mtype, type->prtype, + (const byte*) dfield_get_data(dfield1), + dfield_get_len(dfield1), + (const byte*) dfield_get_data(dfield2), + dfield_get_len(dfield2))); +} + +/***************************************************************** +This function is used to compare two physical records. Only the common +first fields are compared. */ +UNIV_INLINE +int +cmp_rec_rec( +/*========*/ + /* out: 1, 0 , -1 if rec1 is greater, equal, + less, respectively, than rec2; only the common + first fields are compared */ + const rec_t* rec1, /* in: physical record */ + const rec_t* rec2, /* in: physical record */ + const ulint* offsets1,/* in: rec_get_offsets(rec1, index) */ + const ulint* offsets2,/* in: rec_get_offsets(rec2, index) */ + dict_index_t* index) /* in: data dictionary index */ +{ + ulint match_f = 0; + ulint match_b = 0; + + return(cmp_rec_rec_with_match(rec1, rec2, offsets1, offsets2, index, + &match_f, &match_b, 0)); +} diff --git a/storage/xtradb/include/rem0rec.h b/storage/xtradb/include/rem0rec.h new file mode 100644 index 00000000000..cb72a5fa25b --- /dev/null +++ b/storage/xtradb/include/rem0rec.h @@ -0,0 +1,822 @@ +/***************************************************************************** + +Copyright (c) 1994, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/************************************************************************ +Record manager + +Created 5/30/1994 Heikki Tuuri +*************************************************************************/ + +#ifndef rem0rec_h +#define rem0rec_h + +#include "univ.i" +#include "data0data.h" +#include "rem0types.h" +#include "mtr0types.h" +#include "page0types.h" + +/* Info bit denoting the predefined minimum record: this bit is set +if and only if the record is the first user record on a non-leaf +B-tree page that is the leftmost page on its level +(PAGE_LEVEL is nonzero and FIL_PAGE_PREV is FIL_NULL). */ +#define REC_INFO_MIN_REC_FLAG 0x10UL +/* The deleted flag in info bits */ +#define REC_INFO_DELETED_FLAG 0x20UL /* when bit is set to 1, it means the + record has been delete marked */ + +/* Number of extra bytes in an old-style record, +in addition to the data and the offsets */ +#define REC_N_OLD_EXTRA_BYTES 6 +/* Number of extra bytes in a new-style record, +in addition to the data and the offsets */ +#define REC_N_NEW_EXTRA_BYTES 5 + +/* Record status values */ +#define REC_STATUS_ORDINARY 0 +#define REC_STATUS_NODE_PTR 1 +#define REC_STATUS_INFIMUM 2 +#define REC_STATUS_SUPREMUM 3 + +/* The following four constants are needed in page0zip.c in order to +efficiently compress and decompress pages. */ + +/* The offset of heap_no in a compact record */ +#define REC_NEW_HEAP_NO 4 +/* The shift of heap_no in a compact record. +The status is stored in the low-order bits. */ +#define REC_HEAP_NO_SHIFT 3 + +/* Length of a B-tree node pointer, in bytes */ +#define REC_NODE_PTR_SIZE 4 + +#ifdef UNIV_DEBUG +/* Length of the rec_get_offsets() header */ +# define REC_OFFS_HEADER_SIZE 4 +#else /* UNIV_DEBUG */ +/* Length of the rec_get_offsets() header */ +# define REC_OFFS_HEADER_SIZE 2 +#endif /* UNIV_DEBUG */ + +/* Number of elements that should be initially allocated for the +offsets[] array, first passed to rec_get_offsets() */ +#define REC_OFFS_NORMAL_SIZE 100 +#define REC_OFFS_SMALL_SIZE 10 + +/********************************************************** +The following function is used to get the pointer of the next chained record +on the same page. */ +UNIV_INLINE +const rec_t* +rec_get_next_ptr_const( +/*===================*/ + /* out: pointer to the next chained record, or + NULL if none */ + const rec_t* rec, /* in: physical record */ + ulint comp); /* in: nonzero=compact page format */ +/********************************************************** +The following function is used to get the pointer of the next chained record +on the same page. */ +UNIV_INLINE +rec_t* +rec_get_next_ptr( +/*=============*/ + /* out: pointer to the next chained record, or + NULL if none */ + rec_t* rec, /* in: physical record */ + ulint comp); /* in: nonzero=compact page format */ +/********************************************************** +The following function is used to get the offset of the +next chained record on the same page. */ +UNIV_INLINE +ulint +rec_get_next_offs( +/*==============*/ + /* out: the page offset of the next + chained record, or 0 if none */ + const rec_t* rec, /* in: physical record */ + ulint comp); /* in: nonzero=compact page format */ +/********************************************************** +The following function is used to set the next record offset field +of an old-style record. */ +UNIV_INLINE +void +rec_set_next_offs_old( +/*==================*/ + rec_t* rec, /* in: old-style physical record */ + ulint next); /* in: offset of the next record */ +/********************************************************** +The following function is used to set the next record offset field +of a new-style record. */ +UNIV_INLINE +void +rec_set_next_offs_new( +/*==================*/ + rec_t* rec, /* in/out: new-style physical record */ + ulint next); /* in: offset of the next record */ +/********************************************************** +The following function is used to get the number of fields +in an old-style record. */ +UNIV_INLINE +ulint +rec_get_n_fields_old( +/*=================*/ + /* out: number of data fields */ + const rec_t* rec); /* in: physical record */ +/********************************************************** +The following function is used to get the number of fields +in a record. */ +UNIV_INLINE +ulint +rec_get_n_fields( +/*=============*/ + /* out: number of data fields */ + const rec_t* rec, /* in: physical record */ + const dict_index_t* index); /* in: record descriptor */ +/********************************************************** +The following function is used to get the number of records owned by the +previous directory record. */ +UNIV_INLINE +ulint +rec_get_n_owned_old( +/*================*/ + /* out: number of owned records */ + const rec_t* rec); /* in: old-style physical record */ +/********************************************************** +The following function is used to set the number of owned records. */ +UNIV_INLINE +void +rec_set_n_owned_old( +/*================*/ + /* out: TRUE on success */ + rec_t* rec, /* in: old-style physical record */ + ulint n_owned); /* in: the number of owned */ +/********************************************************** +The following function is used to get the number of records owned by the +previous directory record. */ +UNIV_INLINE +ulint +rec_get_n_owned_new( +/*================*/ + /* out: number of owned records */ + const rec_t* rec); /* in: new-style physical record */ +/********************************************************** +The following function is used to set the number of owned records. */ +UNIV_INLINE +void +rec_set_n_owned_new( +/*================*/ + rec_t* rec, /* in/out: new-style physical record */ + page_zip_des_t* page_zip,/* in/out: compressed page, or NULL */ + ulint n_owned);/* in: the number of owned */ +/********************************************************** +The following function is used to retrieve the info bits of +a record. */ +UNIV_INLINE +ulint +rec_get_info_bits( +/*==============*/ + /* out: info bits */ + const rec_t* rec, /* in: physical record */ + ulint comp); /* in: nonzero=compact page format */ +/********************************************************** +The following function is used to set the info bits of a record. */ +UNIV_INLINE +void +rec_set_info_bits_old( +/*==================*/ + rec_t* rec, /* in: old-style physical record */ + ulint bits); /* in: info bits */ +/********************************************************** +The following function is used to set the info bits of a record. */ +UNIV_INLINE +void +rec_set_info_bits_new( +/*==================*/ + rec_t* rec, /* in/out: new-style physical record */ + ulint bits); /* in: info bits */ +/********************************************************** +The following function retrieves the status bits of a new-style record. */ +UNIV_INLINE +ulint +rec_get_status( +/*===========*/ + /* out: status bits */ + const rec_t* rec); /* in: physical record */ + +/********************************************************** +The following function is used to set the status bits of a new-style record. */ +UNIV_INLINE +void +rec_set_status( +/*===========*/ + rec_t* rec, /* in/out: physical record */ + ulint bits); /* in: info bits */ + +/********************************************************** +The following function is used to retrieve the info and status +bits of a record. (Only compact records have status bits.) */ +UNIV_INLINE +ulint +rec_get_info_and_status_bits( +/*=========================*/ + /* out: info bits */ + const rec_t* rec, /* in: physical record */ + ulint comp); /* in: nonzero=compact page format */ +/********************************************************** +The following function is used to set the info and status +bits of a record. (Only compact records have status bits.) */ +UNIV_INLINE +void +rec_set_info_and_status_bits( +/*=========================*/ + rec_t* rec, /* in/out: compact physical record */ + ulint bits); /* in: info bits */ + +/********************************************************** +The following function tells if record is delete marked. */ +UNIV_INLINE +ulint +rec_get_deleted_flag( +/*=================*/ + /* out: nonzero if delete marked */ + const rec_t* rec, /* in: physical record */ + ulint comp); /* in: nonzero=compact page format */ +/********************************************************** +The following function is used to set the deleted bit. */ +UNIV_INLINE +void +rec_set_deleted_flag_old( +/*=====================*/ + rec_t* rec, /* in: old-style physical record */ + ulint flag); /* in: nonzero if delete marked */ +/********************************************************** +The following function is used to set the deleted bit. */ +UNIV_INLINE +void +rec_set_deleted_flag_new( +/*=====================*/ + rec_t* rec, /* in/out: new-style physical record */ + page_zip_des_t* page_zip,/* in/out: compressed page, or NULL */ + ulint flag); /* in: nonzero if delete marked */ +/********************************************************** +The following function tells if a new-style record is a node pointer. */ +UNIV_INLINE +ibool +rec_get_node_ptr_flag( +/*==================*/ + /* out: TRUE if node pointer */ + const rec_t* rec); /* in: physical record */ +/********************************************************** +The following function is used to get the order number +of an old-style record in the heap of the index page. */ +UNIV_INLINE +ulint +rec_get_heap_no_old( +/*================*/ + /* out: heap order number */ + const rec_t* rec); /* in: physical record */ +/********************************************************** +The following function is used to set the heap number +field in an old-style record. */ +UNIV_INLINE +void +rec_set_heap_no_old( +/*================*/ + rec_t* rec, /* in: physical record */ + ulint heap_no);/* in: the heap number */ +/********************************************************** +The following function is used to get the order number +of a new-style record in the heap of the index page. */ +UNIV_INLINE +ulint +rec_get_heap_no_new( +/*================*/ + /* out: heap order number */ + const rec_t* rec); /* in: physical record */ +/********************************************************** +The following function is used to set the heap number +field in a new-style record. */ +UNIV_INLINE +void +rec_set_heap_no_new( +/*================*/ + rec_t* rec, /* in/out: physical record */ + ulint heap_no);/* in: the heap number */ +/********************************************************** +The following function is used to test whether the data offsets +in the record are stored in one-byte or two-byte format. */ +UNIV_INLINE +ibool +rec_get_1byte_offs_flag( +/*====================*/ + /* out: TRUE if 1-byte form */ + const rec_t* rec); /* in: physical record */ + +/********************************************************** +Determine how many of the first n columns in a compact +physical record are stored externally. */ +UNIV_INTERN +ulint +rec_get_n_extern_new( +/*=================*/ + /* out: number of externally stored columns */ + const rec_t* rec, /* in: compact physical record */ + dict_index_t* index, /* in: record descriptor */ + ulint n); /* in: number of columns to scan */ + +/********************************************************** +The following function determines the offsets to each field +in the record. It can reuse a previously allocated array. */ +UNIV_INTERN +ulint* +rec_get_offsets_func( +/*=================*/ + /* out: the new offsets */ + const rec_t* rec, /* in: physical record */ + const dict_index_t* index, /* in: record descriptor */ + ulint* offsets,/* in/out: array consisting of + offsets[0] allocated elements, + or an array from rec_get_offsets(), + or NULL */ + ulint n_fields,/* in: maximum number of + initialized fields + (ULINT_UNDEFINED if all fields) */ + mem_heap_t** heap, /* in/out: memory heap */ + const char* file, /* in: file name where called */ + ulint line); /* in: line number where called */ + +#define rec_get_offsets(rec,index,offsets,n,heap) \ + rec_get_offsets_func(rec,index,offsets,n,heap,__FILE__,__LINE__) + +/********************************************************** +Determine the offset to each field in a leaf-page record +in ROW_FORMAT=COMPACT. This is a special case of +rec_init_offsets() and rec_get_offsets_func(). */ +UNIV_INTERN +void +rec_init_offsets_comp_ordinary( +/*===========================*/ + const rec_t* rec, /* in: physical record in + ROW_FORMAT=COMPACT */ + ulint extra, /* in: number of bytes to reserve + between the record header and + the data payload + (usually REC_N_NEW_EXTRA_BYTES) */ + const dict_index_t* index, /* in: record descriptor */ + ulint* offsets);/* in/out: array of offsets; + in: n=rec_offs_n_fields(offsets) */ + +/********************************************************** +The following function determines the offsets to each field +in the record. It can reuse a previously allocated array. */ +UNIV_INTERN +void +rec_get_offsets_reverse( +/*====================*/ + const byte* extra, /* in: the extra bytes of a + compact record in reverse order, + excluding the fixed-size + REC_N_NEW_EXTRA_BYTES */ + const dict_index_t* index, /* in: record descriptor */ + ulint node_ptr,/* in: nonzero=node pointer, + 0=leaf node */ + ulint* offsets);/* in/out: array consisting of + offsets[0] allocated elements */ + +/**************************************************************** +Validates offsets returned by rec_get_offsets(). */ +UNIV_INLINE +ibool +rec_offs_validate( +/*==============*/ + /* out: TRUE if valid */ + const rec_t* rec, /* in: record or NULL */ + const dict_index_t* index, /* in: record descriptor or NULL */ + const ulint* offsets);/* in: array returned by + rec_get_offsets() */ +#ifdef UNIV_DEBUG +/**************************************************************** +Updates debug data in offsets, in order to avoid bogus +rec_offs_validate() failures. */ +UNIV_INLINE +void +rec_offs_make_valid( +/*================*/ + const rec_t* rec, /* in: record */ + const dict_index_t* index, /* in: record descriptor */ + ulint* offsets);/* in: array returned by + rec_get_offsets() */ +#else +# define rec_offs_make_valid(rec, index, offsets) ((void) 0) +#endif /* UNIV_DEBUG */ + +/**************************************************************** +The following function is used to get the offset to the nth +data field in an old-style record. */ +UNIV_INTERN +ulint +rec_get_nth_field_offs_old( +/*=======================*/ + /* out: offset to the field */ + const rec_t* rec, /* in: record */ + ulint n, /* in: index of the field */ + ulint* len); /* out: length of the field; UNIV_SQL_NULL + if SQL null */ +#define rec_get_nth_field_old(rec, n, len) \ +((rec) + rec_get_nth_field_offs_old(rec, n, len)) +/**************************************************************** +Gets the physical size of an old-style field. +Also an SQL null may have a field of size > 0, +if the data type is of a fixed size. */ +UNIV_INLINE +ulint +rec_get_nth_field_size( +/*===================*/ + /* out: field size in bytes */ + const rec_t* rec, /* in: record */ + ulint n); /* in: index of the field */ +/**************************************************************** +The following function is used to get an offset to the nth +data field in a record. */ +UNIV_INLINE +ulint +rec_get_nth_field_offs( +/*===================*/ + /* out: offset from the origin of rec */ + const ulint* offsets,/* in: array returned by rec_get_offsets() */ + ulint n, /* in: index of the field */ + ulint* len); /* out: length of the field; UNIV_SQL_NULL + if SQL null */ +#define rec_get_nth_field(rec, offsets, n, len) \ +((rec) + rec_get_nth_field_offs(offsets, n, len)) +/********************************************************** +Determine if the offsets are for a record in the new +compact format. */ +UNIV_INLINE +ulint +rec_offs_comp( +/*==========*/ + /* out: nonzero if compact format */ + const ulint* offsets);/* in: array returned by rec_get_offsets() */ +/********************************************************** +Determine if the offsets are for a record containing +externally stored columns. */ +UNIV_INLINE +ulint +rec_offs_any_extern( +/*================*/ + /* out: nonzero if externally stored */ + const ulint* offsets);/* in: array returned by rec_get_offsets() */ +/********************************************************** +Returns nonzero if the extern bit is set in nth field of rec. */ +UNIV_INLINE +ulint +rec_offs_nth_extern( +/*================*/ + /* out: nonzero if externally stored */ + const ulint* offsets,/* in: array returned by rec_get_offsets() */ + ulint n); /* in: nth field */ +/********************************************************** +Returns nonzero if the SQL NULL bit is set in nth field of rec. */ +UNIV_INLINE +ulint +rec_offs_nth_sql_null( +/*==================*/ + /* out: nonzero if SQL NULL */ + const ulint* offsets,/* in: array returned by rec_get_offsets() */ + ulint n); /* in: nth field */ +/********************************************************** +Gets the physical size of a field. */ +UNIV_INLINE +ulint +rec_offs_nth_size( +/*==============*/ + /* out: length of field */ + const ulint* offsets,/* in: array returned by rec_get_offsets() */ + ulint n); /* in: nth field */ + +/********************************************************** +Returns the number of extern bits set in a record. */ +UNIV_INLINE +ulint +rec_offs_n_extern( +/*==============*/ + /* out: number of externally stored fields */ + const ulint* offsets);/* in: array returned by rec_get_offsets() */ +/*************************************************************** +This is used to modify the value of an already existing field in a record. +The previous value must have exactly the same size as the new value. If len +is UNIV_SQL_NULL then the field is treated as an SQL null. +For records in ROW_FORMAT=COMPACT (new-style records), len must not be +UNIV_SQL_NULL unless the field already is SQL null. */ +UNIV_INLINE +void +rec_set_nth_field( +/*==============*/ + rec_t* rec, /* in: record */ + const ulint* offsets,/* in: array returned by rec_get_offsets() */ + ulint n, /* in: index number of the field */ + const void* data, /* in: pointer to the data if not SQL null */ + ulint len); /* in: length of the data or UNIV_SQL_NULL */ +/************************************************************** +The following function returns the data size of an old-style physical +record, that is the sum of field lengths. SQL null fields +are counted as length 0 fields. The value returned by the function +is the distance from record origin to record end in bytes. */ +UNIV_INLINE +ulint +rec_get_data_size_old( +/*==================*/ + /* out: size */ + const rec_t* rec); /* in: physical record */ +/************************************************************** +The following function returns the number of allocated elements +for an array of offsets. */ +UNIV_INLINE +ulint +rec_offs_get_n_alloc( +/*=================*/ + /* out: number of elements */ + const ulint* offsets);/* in: array for rec_get_offsets() */ +/************************************************************** +The following function sets the number of allocated elements +for an array of offsets. */ +UNIV_INLINE +void +rec_offs_set_n_alloc( +/*=================*/ + ulint* offsets, /* out: array for rec_get_offsets(), + must be allocated */ + ulint n_alloc); /* in: number of elements */ +#define rec_offs_init(offsets) \ + rec_offs_set_n_alloc(offsets, (sizeof offsets) / sizeof *offsets) +/************************************************************** +The following function returns the number of fields in a record. */ +UNIV_INLINE +ulint +rec_offs_n_fields( +/*==============*/ + /* out: number of fields */ + const ulint* offsets);/* in: array returned by rec_get_offsets() */ +/************************************************************** +The following function returns the data size of a physical +record, that is the sum of field lengths. SQL null fields +are counted as length 0 fields. The value returned by the function +is the distance from record origin to record end in bytes. */ +UNIV_INLINE +ulint +rec_offs_data_size( +/*===============*/ + /* out: size */ + const ulint* offsets);/* in: array returned by rec_get_offsets() */ +/************************************************************** +Returns the total size of record minus data size of record. +The value returned by the function is the distance from record +start to record origin in bytes. */ +UNIV_INLINE +ulint +rec_offs_extra_size( +/*================*/ + /* out: size */ + const ulint* offsets);/* in: array returned by rec_get_offsets() */ +/************************************************************** +Returns the total size of a physical record. */ +UNIV_INLINE +ulint +rec_offs_size( +/*==========*/ + /* out: size */ + const ulint* offsets);/* in: array returned by rec_get_offsets() */ +/************************************************************** +Returns a pointer to the start of the record. */ +UNIV_INLINE +byte* +rec_get_start( +/*==========*/ + /* out: pointer to start */ + rec_t* rec, /* in: pointer to record */ + const ulint* offsets);/* in: array returned by rec_get_offsets() */ +/************************************************************** +Returns a pointer to the end of the record. */ +UNIV_INLINE +byte* +rec_get_end( +/*========*/ + /* out: pointer to end */ + rec_t* rec, /* in: pointer to record */ + const ulint* offsets);/* in: array returned by rec_get_offsets() */ +/******************************************************************* +Copies a physical record to a buffer. */ +UNIV_INLINE +rec_t* +rec_copy( +/*=====*/ + /* out: pointer to the origin of the copy */ + void* buf, /* in: buffer */ + const rec_t* rec, /* in: physical record */ + const ulint* offsets);/* in: array returned by rec_get_offsets() */ +/****************************************************************** +Copies the first n fields of a physical record to a new physical record in +a buffer. */ +UNIV_INTERN +rec_t* +rec_copy_prefix_to_buf( +/*===================*/ + /* out, own: copied record */ + const rec_t* rec, /* in: physical record */ + const dict_index_t* index, /* in: record descriptor */ + ulint n_fields, /* in: number of fields + to copy */ + byte** buf, /* in/out: memory buffer + for the copied prefix, + or NULL */ + ulint* buf_size); /* in/out: buffer size */ +/**************************************************************** +Folds a prefix of a physical record to a ulint. */ +UNIV_INLINE +ulint +rec_fold( +/*=====*/ + /* out: the folded value */ + const rec_t* rec, /* in: the physical record */ + const ulint* offsets, /* in: array returned by + rec_get_offsets() */ + ulint n_fields, /* in: number of complete + fields to fold */ + ulint n_bytes, /* in: number of bytes to fold + in an incomplete last field */ + dulint tree_id) /* in: index tree id */ + __attribute__((pure)); +/************************************************************* +Builds a ROW_FORMAT=COMPACT record out of a data tuple. */ +UNIV_INTERN +void +rec_convert_dtuple_to_rec_comp( +/*===========================*/ + rec_t* rec, /* in: origin of record */ + ulint extra, /* in: number of bytes to + reserve between the record + header and the data payload + (normally REC_N_NEW_EXTRA_BYTES) */ + const dict_index_t* index, /* in: record descriptor */ + ulint status, /* in: status bits of the record */ + const dfield_t* fields, /* in: array of data fields */ + ulint n_fields);/* in: number of data fields */ +/************************************************************* +Builds a physical record out of a data tuple and +stores it into the given buffer. */ +UNIV_INTERN +rec_t* +rec_convert_dtuple_to_rec( +/*======================*/ + /* out: pointer to the origin + of physical record */ + byte* buf, /* in: start address of the + physical record */ + const dict_index_t* index, /* in: record descriptor */ + const dtuple_t* dtuple, /* in: data tuple */ + ulint n_ext); /* in: number of + externally stored columns */ +/************************************************************** +Returns the extra size of an old-style physical record if we know its +data size and number of fields. */ +UNIV_INLINE +ulint +rec_get_converted_extra_size( +/*=========================*/ + /* out: extra size */ + ulint data_size, /* in: data size */ + ulint n_fields, /* in: number of fields */ + ulint n_ext) /* in: number of externally stored columns */ + __attribute__((const)); +/************************************************************** +Determines the size of a data tuple prefix in ROW_FORMAT=COMPACT. */ +UNIV_INTERN +ulint +rec_get_converted_size_comp_prefix( +/*===============================*/ + /* out: total size */ + const dict_index_t* index, /* in: record descriptor; + dict_table_is_comp() is + assumed to hold, even if + it does not */ + const dfield_t* fields, /* in: array of data fields */ + ulint n_fields,/* in: number of data fields */ + ulint* extra); /* out: extra size */ +/************************************************************** +Determines the size of a data tuple in ROW_FORMAT=COMPACT. */ +UNIV_INTERN +ulint +rec_get_converted_size_comp( +/*========================*/ + /* out: total size */ + const dict_index_t* index, /* in: record descriptor; + dict_table_is_comp() is + assumed to hold, even if + it does not */ + ulint status, /* in: status bits of the record */ + const dfield_t* fields, /* in: array of data fields */ + ulint n_fields,/* in: number of data fields */ + ulint* extra); /* out: extra size */ +/************************************************************** +The following function returns the size of a data tuple when converted to +a physical record. */ +UNIV_INLINE +ulint +rec_get_converted_size( +/*===================*/ + /* out: size */ + dict_index_t* index, /* in: record descriptor */ + const dtuple_t* dtuple, /* in: data tuple */ + ulint n_ext); /* in: number of externally stored columns */ +/****************************************************************** +Copies the first n fields of a physical record to a data tuple. +The fields are copied to the memory heap. */ +UNIV_INTERN +void +rec_copy_prefix_to_dtuple( +/*======================*/ + dtuple_t* tuple, /* out: data tuple */ + const rec_t* rec, /* in: physical record */ + const dict_index_t* index, /* in: record descriptor */ + ulint n_fields, /* in: number of fields + to copy */ + mem_heap_t* heap); /* in: memory heap */ +/******************************************************************* +Validates the consistency of a physical record. */ +UNIV_INTERN +ibool +rec_validate( +/*=========*/ + /* out: TRUE if ok */ + const rec_t* rec, /* in: physical record */ + const ulint* offsets);/* in: array returned by rec_get_offsets() */ +/******************************************************************* +Prints an old-style physical record. */ +UNIV_INTERN +void +rec_print_old( +/*==========*/ + FILE* file, /* in: file where to print */ + const rec_t* rec); /* in: physical record */ +/******************************************************************* +Prints a physical record in ROW_FORMAT=COMPACT. Ignores the +record header. */ +UNIV_INTERN +void +rec_print_comp( +/*===========*/ + FILE* file, /* in: file where to print */ + const rec_t* rec, /* in: physical record */ + const ulint* offsets);/* in: array returned by rec_get_offsets() */ +/******************************************************************* +Prints a physical record. */ +UNIV_INTERN +void +rec_print_new( +/*==========*/ + FILE* file, /* in: file where to print */ + const rec_t* rec, /* in: physical record */ + const ulint* offsets);/* in: array returned by rec_get_offsets() */ +/******************************************************************* +Prints a physical record. */ +UNIV_INTERN +void +rec_print( +/*======*/ + FILE* file, /* in: file where to print */ + const rec_t* rec, /* in: physical record */ + dict_index_t* index); /* in: record descriptor */ + +#define REC_INFO_BITS 6 /* This is single byte bit-field */ + +/* Maximum lengths for the data in a physical record if the offsets +are given in one byte (resp. two byte) format. */ +#define REC_1BYTE_OFFS_LIMIT 0x7FUL +#define REC_2BYTE_OFFS_LIMIT 0x7FFFUL + +/* The data size of record must be smaller than this because we reserve +two upmost bits in a two byte offset for special purposes */ +#define REC_MAX_DATA_SIZE (16 * 1024) + +#ifndef UNIV_NONINL +#include "rem0rec.ic" +#endif + +#endif diff --git a/storage/xtradb/include/rem0rec.ic b/storage/xtradb/include/rem0rec.ic new file mode 100644 index 00000000000..0b2b9f4a685 --- /dev/null +++ b/storage/xtradb/include/rem0rec.ic @@ -0,0 +1,1652 @@ +/***************************************************************************** + +Copyright (c) 1994, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/************************************************************************ +Record manager + +Created 5/30/1994 Heikki Tuuri +*************************************************************************/ + +#include "mach0data.h" +#include "ut0byte.h" +#include "dict0dict.h" + +/* Compact flag ORed to the extra size returned by rec_get_offsets() */ +#define REC_OFFS_COMPACT ((ulint) 1 << 31) +/* SQL NULL flag in offsets returned by rec_get_offsets() */ +#define REC_OFFS_SQL_NULL ((ulint) 1 << 31) +/* External flag in offsets returned by rec_get_offsets() */ +#define REC_OFFS_EXTERNAL ((ulint) 1 << 30) +/* Mask for offsets returned by rec_get_offsets() */ +#define REC_OFFS_MASK (REC_OFFS_EXTERNAL - 1) + +/* Offsets of the bit-fields in an old-style record. NOTE! In the table the +most significant bytes and bits are written below less significant. + + (1) byte offset (2) bit usage within byte + downward from + origin -> 1 8 bits pointer to next record + 2 8 bits pointer to next record + 3 1 bit short flag + 7 bits number of fields + 4 3 bits number of fields + 5 bits heap number + 5 8 bits heap number + 6 4 bits n_owned + 4 bits info bits +*/ + +/* Offsets of the bit-fields in a new-style record. NOTE! In the table the +most significant bytes and bits are written below less significant. + + (1) byte offset (2) bit usage within byte + downward from + origin -> 1 8 bits relative offset of next record + 2 8 bits relative offset of next record + the relative offset is an unsigned 16-bit + integer: + (offset_of_next_record + - offset_of_this_record) mod 64Ki, + where mod is the modulo as a non-negative + number; + we can calculate the the offset of the next + record with the formula: + relative_offset + offset_of_this_record + mod UNIV_PAGE_SIZE + 3 3 bits status: + 000=conventional record + 001=node pointer record (inside B-tree) + 010=infimum record + 011=supremum record + 1xx=reserved + 5 bits heap number + 4 8 bits heap number + 5 4 bits n_owned + 4 bits info bits +*/ + +/* We list the byte offsets from the origin of the record, the mask, +and the shift needed to obtain each bit-field of the record. */ + +#define REC_NEXT 2 +#define REC_NEXT_MASK 0xFFFFUL +#define REC_NEXT_SHIFT 0 + +#define REC_OLD_SHORT 3 /* This is single byte bit-field */ +#define REC_OLD_SHORT_MASK 0x1UL +#define REC_OLD_SHORT_SHIFT 0 + +#define REC_OLD_N_FIELDS 4 +#define REC_OLD_N_FIELDS_MASK 0x7FEUL +#define REC_OLD_N_FIELDS_SHIFT 1 + +#define REC_NEW_STATUS 3 /* This is single byte bit-field */ +#define REC_NEW_STATUS_MASK 0x7UL +#define REC_NEW_STATUS_SHIFT 0 + +#define REC_OLD_HEAP_NO 5 +#define REC_HEAP_NO_MASK 0xFFF8UL +#if 0 /* defined in rem0rec.h for use of page0zip.c */ +#define REC_NEW_HEAP_NO 4 +#define REC_HEAP_NO_SHIFT 3 +#endif + +#define REC_OLD_N_OWNED 6 /* This is single byte bit-field */ +#define REC_NEW_N_OWNED 5 /* This is single byte bit-field */ +#define REC_N_OWNED_MASK 0xFUL +#define REC_N_OWNED_SHIFT 0 + +#define REC_OLD_INFO_BITS 6 /* This is single byte bit-field */ +#define REC_NEW_INFO_BITS 5 /* This is single byte bit-field */ +#define REC_INFO_BITS_MASK 0xF0UL +#define REC_INFO_BITS_SHIFT 0 + +/* The following masks are used to filter the SQL null bit from +one-byte and two-byte offsets */ + +#define REC_1BYTE_SQL_NULL_MASK 0x80UL +#define REC_2BYTE_SQL_NULL_MASK 0x8000UL + +/* In a 2-byte offset the second most significant bit denotes +a field stored to another page: */ + +#define REC_2BYTE_EXTERN_MASK 0x4000UL + +#if REC_OLD_SHORT_MASK << (8 * (REC_OLD_SHORT - 3)) \ + ^ REC_OLD_N_FIELDS_MASK << (8 * (REC_OLD_N_FIELDS - 4)) \ + ^ REC_HEAP_NO_MASK << (8 * (REC_OLD_HEAP_NO - 4)) \ + ^ REC_N_OWNED_MASK << (8 * (REC_OLD_N_OWNED - 3)) \ + ^ REC_INFO_BITS_MASK << (8 * (REC_OLD_INFO_BITS - 3)) \ + ^ 0xFFFFFFFFUL +# error "sum of old-style masks != 0xFFFFFFFFUL" +#endif +#if REC_NEW_STATUS_MASK << (8 * (REC_NEW_STATUS - 3)) \ + ^ REC_HEAP_NO_MASK << (8 * (REC_NEW_HEAP_NO - 4)) \ + ^ REC_N_OWNED_MASK << (8 * (REC_NEW_N_OWNED - 3)) \ + ^ REC_INFO_BITS_MASK << (8 * (REC_NEW_INFO_BITS - 3)) \ + ^ 0xFFFFFFUL +# error "sum of new-style masks != 0xFFFFFFUL" +#endif + +/*************************************************************** +Sets the value of the ith field SQL null bit of an old-style record. */ +UNIV_INTERN +void +rec_set_nth_field_null_bit( +/*=======================*/ + rec_t* rec, /* in: record */ + ulint i, /* in: ith field */ + ibool val); /* in: value to set */ +/*************************************************************** +Sets an old-style record field to SQL null. +The physical size of the field is not changed. */ +UNIV_INTERN +void +rec_set_nth_field_sql_null( +/*=======================*/ + rec_t* rec, /* in: record */ + ulint n); /* in: index of the field */ + +/********************************************************** +Gets a bit field from within 1 byte. */ +UNIV_INLINE +ulint +rec_get_bit_field_1( +/*================*/ + const rec_t* rec, /* in: pointer to record origin */ + ulint offs, /* in: offset from the origin down */ + ulint mask, /* in: mask used to filter bits */ + ulint shift) /* in: shift right applied after masking */ +{ + ut_ad(rec); + + return((mach_read_from_1(rec - offs) & mask) >> shift); +} + +/********************************************************** +Sets a bit field within 1 byte. */ +UNIV_INLINE +void +rec_set_bit_field_1( +/*================*/ + rec_t* rec, /* in: pointer to record origin */ + ulint val, /* in: value to set */ + ulint offs, /* in: offset from the origin down */ + ulint mask, /* in: mask used to filter bits */ + ulint shift) /* in: shift right applied after masking */ +{ + ut_ad(rec); + ut_ad(offs <= REC_N_OLD_EXTRA_BYTES); + ut_ad(mask); + ut_ad(mask <= 0xFFUL); + ut_ad(((mask >> shift) << shift) == mask); + ut_ad(((val << shift) & mask) == (val << shift)); + + mach_write_to_1(rec - offs, + (mach_read_from_1(rec - offs) & ~mask) + | (val << shift)); +} + +/********************************************************** +Gets a bit field from within 2 bytes. */ +UNIV_INLINE +ulint +rec_get_bit_field_2( +/*================*/ + const rec_t* rec, /* in: pointer to record origin */ + ulint offs, /* in: offset from the origin down */ + ulint mask, /* in: mask used to filter bits */ + ulint shift) /* in: shift right applied after masking */ +{ + ut_ad(rec); + + return((mach_read_from_2(rec - offs) & mask) >> shift); +} + +/********************************************************** +Sets a bit field within 2 bytes. */ +UNIV_INLINE +void +rec_set_bit_field_2( +/*================*/ + rec_t* rec, /* in: pointer to record origin */ + ulint val, /* in: value to set */ + ulint offs, /* in: offset from the origin down */ + ulint mask, /* in: mask used to filter bits */ + ulint shift) /* in: shift right applied after masking */ +{ + ut_ad(rec); + ut_ad(offs <= REC_N_OLD_EXTRA_BYTES); + ut_ad(mask > 0xFFUL); + ut_ad(mask <= 0xFFFFUL); + ut_ad((mask >> shift) & 1); + ut_ad(0 == ((mask >> shift) & ((mask >> shift) + 1))); + ut_ad(((mask >> shift) << shift) == mask); + ut_ad(((val << shift) & mask) == (val << shift)); + + mach_write_to_2(rec - offs, + (mach_read_from_2(rec - offs) & ~mask) + | (val << shift)); +} + +/********************************************************** +The following function is used to get the pointer of the next chained record +on the same page. */ +UNIV_INLINE +const rec_t* +rec_get_next_ptr_const( +/*===================*/ + /* out: pointer to the next chained record, or + NULL if none */ + const rec_t* rec, /* in: physical record */ + ulint comp) /* in: nonzero=compact page format */ +{ + ulint field_value; + + ut_ad(REC_NEXT_MASK == 0xFFFFUL); + ut_ad(REC_NEXT_SHIFT == 0); + + field_value = mach_read_from_2(rec - REC_NEXT); + + if (UNIV_UNLIKELY(field_value == 0)) { + + return(NULL); + } + + if (UNIV_EXPECT(comp, REC_OFFS_COMPACT)) { +#if UNIV_PAGE_SIZE <= 32768 + /* Note that for 64 KiB pages, field_value can 'wrap around' + and the debug assertion is not valid */ + + /* In the following assertion, field_value is interpreted + as signed 16-bit integer in 2's complement arithmetics. + If all platforms defined int16_t in the standard headers, + the expression could be written simpler as + (int16_t) field_value + ut_align_offset(...) < UNIV_PAGE_SIZE + */ + ut_ad((field_value >= 32768 + ? field_value - 65536 + : field_value) + + ut_align_offset(rec, UNIV_PAGE_SIZE) + < UNIV_PAGE_SIZE); +#endif + /* There must be at least REC_N_NEW_EXTRA_BYTES + 1 + between each record. */ + ut_ad((field_value > REC_N_NEW_EXTRA_BYTES + && field_value < 32768) + || field_value < (uint16) -REC_N_NEW_EXTRA_BYTES); + + return((byte*) ut_align_down(rec, UNIV_PAGE_SIZE) + + ut_align_offset(rec + field_value, UNIV_PAGE_SIZE)); + } else { + ut_ad(field_value < UNIV_PAGE_SIZE); + + return((byte*) ut_align_down(rec, UNIV_PAGE_SIZE) + + field_value); + } +} + +/********************************************************** +The following function is used to get the pointer of the next chained record +on the same page. */ +UNIV_INLINE +rec_t* +rec_get_next_ptr( +/*=============*/ + /* out: pointer to the next chained record, or + NULL if none */ + rec_t* rec, /* in: physical record */ + ulint comp) /* in: nonzero=compact page format */ +{ + return((rec_t*) rec_get_next_ptr_const(rec, comp)); +} + +/********************************************************** +The following function is used to get the offset of the next chained record +on the same page. */ +UNIV_INLINE +ulint +rec_get_next_offs( +/*==============*/ + /* out: the page offset of the next + chained record, or 0 if none */ + const rec_t* rec, /* in: physical record */ + ulint comp) /* in: nonzero=compact page format */ +{ + ulint field_value; +#if REC_NEXT_MASK != 0xFFFFUL +# error "REC_NEXT_MASK != 0xFFFFUL" +#endif +#if REC_NEXT_SHIFT +# error "REC_NEXT_SHIFT != 0" +#endif + + field_value = mach_read_from_2(rec - REC_NEXT); + + if (UNIV_EXPECT(comp, REC_OFFS_COMPACT)) { +#if UNIV_PAGE_SIZE <= 32768 + /* Note that for 64 KiB pages, field_value can 'wrap around' + and the debug assertion is not valid */ + + /* In the following assertion, field_value is interpreted + as signed 16-bit integer in 2's complement arithmetics. + If all platforms defined int16_t in the standard headers, + the expression could be written simpler as + (int16_t) field_value + ut_align_offset(...) < UNIV_PAGE_SIZE + */ + ut_ad((field_value >= 32768 + ? field_value - 65536 + : field_value) + + ut_align_offset(rec, UNIV_PAGE_SIZE) + < UNIV_PAGE_SIZE); +#endif + if (UNIV_UNLIKELY(field_value == 0)) { + + return(0); + } + + /* There must be at least REC_N_NEW_EXTRA_BYTES + 1 + between each record. */ + ut_ad((field_value > REC_N_NEW_EXTRA_BYTES + && field_value < 32768) + || field_value < (uint16) -REC_N_NEW_EXTRA_BYTES); + + return(ut_align_offset(rec + field_value, UNIV_PAGE_SIZE)); + } else { + ut_ad(field_value < UNIV_PAGE_SIZE); + + return(field_value); + } +} + +/********************************************************** +The following function is used to set the next record offset field +of an old-style record. */ +UNIV_INLINE +void +rec_set_next_offs_old( +/*==================*/ + rec_t* rec, /* in: old-style physical record */ + ulint next) /* in: offset of the next record */ +{ + ut_ad(rec); + ut_ad(UNIV_PAGE_SIZE > next); +#if REC_NEXT_MASK != 0xFFFFUL +# error "REC_NEXT_MASK != 0xFFFFUL" +#endif +#if REC_NEXT_SHIFT +# error "REC_NEXT_SHIFT != 0" +#endif + + mach_write_to_2(rec - REC_NEXT, next); +} + +/********************************************************** +The following function is used to set the next record offset field +of a new-style record. */ +UNIV_INLINE +void +rec_set_next_offs_new( +/*==================*/ + rec_t* rec, /* in/out: new-style physical record */ + ulint next) /* in: offset of the next record */ +{ + ulint field_value; + + ut_ad(rec); + ut_ad(UNIV_PAGE_SIZE > next); + + if (UNIV_UNLIKELY(!next)) { + field_value = 0; + } else { + /* The following two statements calculate + next - offset_of_rec mod 64Ki, where mod is the modulo + as a non-negative number */ + + field_value = (ulint) + ((lint) next + - (lint) ut_align_offset(rec, UNIV_PAGE_SIZE)); + field_value &= REC_NEXT_MASK; + } + + mach_write_to_2(rec - REC_NEXT, field_value); +} + +/********************************************************** +The following function is used to get the number of fields +in an old-style record. */ +UNIV_INLINE +ulint +rec_get_n_fields_old( +/*=================*/ + /* out: number of data fields */ + const rec_t* rec) /* in: physical record */ +{ + ulint ret; + + ut_ad(rec); + + ret = rec_get_bit_field_2(rec, REC_OLD_N_FIELDS, + REC_OLD_N_FIELDS_MASK, + REC_OLD_N_FIELDS_SHIFT); + ut_ad(ret <= REC_MAX_N_FIELDS); + ut_ad(ret > 0); + + return(ret); +} + +/********************************************************** +The following function is used to set the number of fields +in an old-style record. */ +UNIV_INLINE +void +rec_set_n_fields_old( +/*=================*/ + rec_t* rec, /* in: physical record */ + ulint n_fields) /* in: the number of fields */ +{ + ut_ad(rec); + ut_ad(n_fields <= REC_MAX_N_FIELDS); + ut_ad(n_fields > 0); + + rec_set_bit_field_2(rec, n_fields, REC_OLD_N_FIELDS, + REC_OLD_N_FIELDS_MASK, REC_OLD_N_FIELDS_SHIFT); +} + +/********************************************************** +The following function retrieves the status bits of a new-style record. */ +UNIV_INLINE +ulint +rec_get_status( +/*===========*/ + /* out: status bits */ + const rec_t* rec) /* in: physical record */ +{ + ulint ret; + + ut_ad(rec); + + ret = rec_get_bit_field_1(rec, REC_NEW_STATUS, + REC_NEW_STATUS_MASK, REC_NEW_STATUS_SHIFT); + ut_ad((ret & ~REC_NEW_STATUS_MASK) == 0); + + return(ret); +} + +/********************************************************** +The following function is used to get the number of fields +in a record. */ +UNIV_INLINE +ulint +rec_get_n_fields( +/*=============*/ + /* out: number of data fields */ + const rec_t* rec, /* in: physical record */ + const dict_index_t* index) /* in: record descriptor */ +{ + ut_ad(rec); + ut_ad(index); + + if (!dict_table_is_comp(index->table)) { + return(rec_get_n_fields_old(rec)); + } + + switch (rec_get_status(rec)) { + case REC_STATUS_ORDINARY: + return(dict_index_get_n_fields(index)); + case REC_STATUS_NODE_PTR: + return(dict_index_get_n_unique_in_tree(index) + 1); + case REC_STATUS_INFIMUM: + case REC_STATUS_SUPREMUM: + return(1); + default: + ut_error; + return(ULINT_UNDEFINED); + } +} + +/********************************************************** +The following function is used to get the number of records owned by the +previous directory record. */ +UNIV_INLINE +ulint +rec_get_n_owned_old( +/*================*/ + /* out: number of owned records */ + const rec_t* rec) /* in: old-style physical record */ +{ + return(rec_get_bit_field_1(rec, REC_OLD_N_OWNED, + REC_N_OWNED_MASK, REC_N_OWNED_SHIFT)); +} + +/********************************************************** +The following function is used to set the number of owned records. */ +UNIV_INLINE +void +rec_set_n_owned_old( +/*================*/ + /* out: TRUE on success */ + rec_t* rec, /* in: old-style physical record */ + ulint n_owned) /* in: the number of owned */ +{ + rec_set_bit_field_1(rec, n_owned, REC_OLD_N_OWNED, + REC_N_OWNED_MASK, REC_N_OWNED_SHIFT); +} + +/********************************************************** +The following function is used to get the number of records owned by the +previous directory record. */ +UNIV_INLINE +ulint +rec_get_n_owned_new( +/*================*/ + /* out: number of owned records */ + const rec_t* rec) /* in: new-style physical record */ +{ + return(rec_get_bit_field_1(rec, REC_NEW_N_OWNED, + REC_N_OWNED_MASK, REC_N_OWNED_SHIFT)); +} + +/********************************************************** +The following function is used to set the number of owned records. */ +UNIV_INLINE +void +rec_set_n_owned_new( +/*================*/ + rec_t* rec, /* in/out: new-style physical record */ + page_zip_des_t* page_zip,/* in/out: compressed page, or NULL */ + ulint n_owned)/* in: the number of owned */ +{ + rec_set_bit_field_1(rec, n_owned, REC_NEW_N_OWNED, + REC_N_OWNED_MASK, REC_N_OWNED_SHIFT); + if (UNIV_LIKELY_NULL(page_zip) + && UNIV_LIKELY(rec_get_status(rec) + != REC_STATUS_SUPREMUM)) { + page_zip_rec_set_owned(page_zip, rec, n_owned); + } +} + +/********************************************************** +The following function is used to retrieve the info bits of a record. */ +UNIV_INLINE +ulint +rec_get_info_bits( +/*==============*/ + /* out: info bits */ + const rec_t* rec, /* in: physical record */ + ulint comp) /* in: nonzero=compact page format */ +{ + return(rec_get_bit_field_1( + rec, comp ? REC_NEW_INFO_BITS : REC_OLD_INFO_BITS, + REC_INFO_BITS_MASK, REC_INFO_BITS_SHIFT)); +} + +/********************************************************** +The following function is used to set the info bits of a record. */ +UNIV_INLINE +void +rec_set_info_bits_old( +/*==================*/ + rec_t* rec, /* in: old-style physical record */ + ulint bits) /* in: info bits */ +{ + rec_set_bit_field_1(rec, bits, REC_OLD_INFO_BITS, + REC_INFO_BITS_MASK, REC_INFO_BITS_SHIFT); +} +/********************************************************** +The following function is used to set the info bits of a record. */ +UNIV_INLINE +void +rec_set_info_bits_new( +/*==================*/ + rec_t* rec, /* in/out: new-style physical record */ + ulint bits) /* in: info bits */ +{ + rec_set_bit_field_1(rec, bits, REC_NEW_INFO_BITS, + REC_INFO_BITS_MASK, REC_INFO_BITS_SHIFT); +} + +/********************************************************** +The following function is used to set the status bits of a new-style record. */ +UNIV_INLINE +void +rec_set_status( +/*===========*/ + rec_t* rec, /* in/out: physical record */ + ulint bits) /* in: info bits */ +{ + rec_set_bit_field_1(rec, bits, REC_NEW_STATUS, + REC_NEW_STATUS_MASK, REC_NEW_STATUS_SHIFT); +} + +/********************************************************** +The following function is used to retrieve the info and status +bits of a record. (Only compact records have status bits.) */ +UNIV_INLINE +ulint +rec_get_info_and_status_bits( +/*=========================*/ + /* out: info bits */ + const rec_t* rec, /* in: physical record */ + ulint comp) /* in: nonzero=compact page format */ +{ + ulint bits; +#if (REC_NEW_STATUS_MASK >> REC_NEW_STATUS_SHIFT) \ +& (REC_INFO_BITS_MASK >> REC_INFO_BITS_SHIFT) +# error "REC_NEW_STATUS_MASK and REC_INFO_BITS_MASK overlap" +#endif + if (UNIV_EXPECT(comp, REC_OFFS_COMPACT)) { + bits = rec_get_info_bits(rec, TRUE) | rec_get_status(rec); + } else { + bits = rec_get_info_bits(rec, FALSE); + ut_ad(!(bits & ~(REC_INFO_BITS_MASK >> REC_INFO_BITS_SHIFT))); + } + return(bits); +} +/********************************************************** +The following function is used to set the info and status +bits of a record. (Only compact records have status bits.) */ +UNIV_INLINE +void +rec_set_info_and_status_bits( +/*=========================*/ + rec_t* rec, /* in/out: physical record */ + ulint bits) /* in: info bits */ +{ +#if (REC_NEW_STATUS_MASK >> REC_NEW_STATUS_SHIFT) \ +& (REC_INFO_BITS_MASK >> REC_INFO_BITS_SHIFT) +# error "REC_NEW_STATUS_MASK and REC_INFO_BITS_MASK overlap" +#endif + rec_set_status(rec, bits & REC_NEW_STATUS_MASK); + rec_set_info_bits_new(rec, bits & ~REC_NEW_STATUS_MASK); +} + +/********************************************************** +The following function tells if record is delete marked. */ +UNIV_INLINE +ulint +rec_get_deleted_flag( +/*=================*/ + /* out: nonzero if delete marked */ + const rec_t* rec, /* in: physical record */ + ulint comp) /* in: nonzero=compact page format */ +{ + if (UNIV_EXPECT(comp, REC_OFFS_COMPACT)) { + return(UNIV_UNLIKELY( + rec_get_bit_field_1(rec, REC_NEW_INFO_BITS, + REC_INFO_DELETED_FLAG, + REC_INFO_BITS_SHIFT))); + } else { + return(UNIV_UNLIKELY( + rec_get_bit_field_1(rec, REC_OLD_INFO_BITS, + REC_INFO_DELETED_FLAG, + REC_INFO_BITS_SHIFT))); + } +} + +/********************************************************** +The following function is used to set the deleted bit. */ +UNIV_INLINE +void +rec_set_deleted_flag_old( +/*=====================*/ + rec_t* rec, /* in: old-style physical record */ + ulint flag) /* in: nonzero if delete marked */ +{ + ulint val; + + val = rec_get_info_bits(rec, FALSE); + + if (flag) { + val |= REC_INFO_DELETED_FLAG; + } else { + val &= ~REC_INFO_DELETED_FLAG; + } + + rec_set_info_bits_old(rec, val); +} + +/********************************************************** +The following function is used to set the deleted bit. */ +UNIV_INLINE +void +rec_set_deleted_flag_new( +/*=====================*/ + rec_t* rec, /* in/out: new-style physical record */ + page_zip_des_t* page_zip,/* in/out: compressed page, or NULL */ + ulint flag) /* in: nonzero if delete marked */ +{ + ulint val; + + val = rec_get_info_bits(rec, TRUE); + + if (flag) { + val |= REC_INFO_DELETED_FLAG; + } else { + val &= ~REC_INFO_DELETED_FLAG; + } + + rec_set_info_bits_new(rec, val); + + if (UNIV_LIKELY_NULL(page_zip)) { + page_zip_rec_set_deleted(page_zip, rec, flag); + } +} + +/********************************************************** +The following function tells if a new-style record is a node pointer. */ +UNIV_INLINE +ibool +rec_get_node_ptr_flag( +/*==================*/ + /* out: TRUE if node pointer */ + const rec_t* rec) /* in: physical record */ +{ + return(REC_STATUS_NODE_PTR == rec_get_status(rec)); +} + +/********************************************************** +The following function is used to get the order number +of an old-style record in the heap of the index page. */ +UNIV_INLINE +ulint +rec_get_heap_no_old( +/*================*/ + /* out: heap order number */ + const rec_t* rec) /* in: physical record */ +{ + return(rec_get_bit_field_2(rec, REC_OLD_HEAP_NO, + REC_HEAP_NO_MASK, REC_HEAP_NO_SHIFT)); +} + +/********************************************************** +The following function is used to set the heap number +field in an old-style record. */ +UNIV_INLINE +void +rec_set_heap_no_old( +/*================*/ + rec_t* rec, /* in: physical record */ + ulint heap_no)/* in: the heap number */ +{ + rec_set_bit_field_2(rec, heap_no, REC_OLD_HEAP_NO, + REC_HEAP_NO_MASK, REC_HEAP_NO_SHIFT); +} + +/********************************************************** +The following function is used to get the order number +of a new-style record in the heap of the index page. */ +UNIV_INLINE +ulint +rec_get_heap_no_new( +/*================*/ + /* out: heap order number */ + const rec_t* rec) /* in: physical record */ +{ + return(rec_get_bit_field_2(rec, REC_NEW_HEAP_NO, + REC_HEAP_NO_MASK, REC_HEAP_NO_SHIFT)); +} + +/********************************************************** +The following function is used to set the heap number +field in a new-style record. */ +UNIV_INLINE +void +rec_set_heap_no_new( +/*================*/ + rec_t* rec, /* in/out: physical record */ + ulint heap_no)/* in: the heap number */ +{ + rec_set_bit_field_2(rec, heap_no, REC_NEW_HEAP_NO, + REC_HEAP_NO_MASK, REC_HEAP_NO_SHIFT); +} + +/********************************************************** +The following function is used to test whether the data offsets in the record +are stored in one-byte or two-byte format. */ +UNIV_INLINE +ibool +rec_get_1byte_offs_flag( +/*====================*/ + /* out: TRUE if 1-byte form */ + const rec_t* rec) /* in: physical record */ +{ +#if TRUE != 1 +#error "TRUE != 1" +#endif + + return(rec_get_bit_field_1(rec, REC_OLD_SHORT, REC_OLD_SHORT_MASK, + REC_OLD_SHORT_SHIFT)); +} + +/********************************************************** +The following function is used to set the 1-byte offsets flag. */ +UNIV_INLINE +void +rec_set_1byte_offs_flag( +/*====================*/ + rec_t* rec, /* in: physical record */ + ibool flag) /* in: TRUE if 1byte form */ +{ +#if TRUE != 1 +#error "TRUE != 1" +#endif + ut_ad(flag <= TRUE); + + rec_set_bit_field_1(rec, flag, REC_OLD_SHORT, REC_OLD_SHORT_MASK, + REC_OLD_SHORT_SHIFT); +} + +/********************************************************** +Returns the offset of nth field end if the record is stored in the 1-byte +offsets form. If the field is SQL null, the flag is ORed in the returned +value. */ +UNIV_INLINE +ulint +rec_1_get_field_end_info( +/*=====================*/ + /* out: offset of the start of the + field, SQL null flag ORed */ + const rec_t* rec, /* in: record */ + ulint n) /* in: field index */ +{ + ut_ad(rec_get_1byte_offs_flag(rec)); + ut_ad(n < rec_get_n_fields_old(rec)); + + return(mach_read_from_1(rec - (REC_N_OLD_EXTRA_BYTES + n + 1))); +} + +/********************************************************** +Returns the offset of nth field end if the record is stored in the 2-byte +offsets form. If the field is SQL null, the flag is ORed in the returned +value. */ +UNIV_INLINE +ulint +rec_2_get_field_end_info( +/*=====================*/ + /* out: offset of the start of the + field, SQL null flag and extern + storage flag ORed */ + const rec_t* rec, /* in: record */ + ulint n) /* in: field index */ +{ + ut_ad(!rec_get_1byte_offs_flag(rec)); + ut_ad(n < rec_get_n_fields_old(rec)); + + return(mach_read_from_2(rec - (REC_N_OLD_EXTRA_BYTES + 2 * n + 2))); +} + +/* Get the base address of offsets. The extra_size is stored at +this position, and following positions hold the end offsets of +the fields. */ +#define rec_offs_base(offsets) (offsets + REC_OFFS_HEADER_SIZE) + +/************************************************************** +The following function returns the number of allocated elements +for an array of offsets. */ +UNIV_INLINE +ulint +rec_offs_get_n_alloc( +/*=================*/ + /* out: number of elements */ + const ulint* offsets)/* in: array for rec_get_offsets() */ +{ + ulint n_alloc; + ut_ad(offsets); + n_alloc = offsets[0]; + ut_ad(n_alloc > REC_OFFS_HEADER_SIZE); + UNIV_MEM_ASSERT_W(offsets, n_alloc * sizeof *offsets); + return(n_alloc); +} + +/************************************************************** +The following function sets the number of allocated elements +for an array of offsets. */ +UNIV_INLINE +void +rec_offs_set_n_alloc( +/*=================*/ + ulint* offsets, /* out: array for rec_get_offsets(), + must be allocated */ + ulint n_alloc) /* in: number of elements */ +{ + ut_ad(offsets); + ut_ad(n_alloc > REC_OFFS_HEADER_SIZE); + UNIV_MEM_ASSERT_AND_ALLOC(offsets, n_alloc * sizeof *offsets); + offsets[0] = n_alloc; +} + +/************************************************************** +The following function returns the number of fields in a record. */ +UNIV_INLINE +ulint +rec_offs_n_fields( +/*==============*/ + /* out: number of fields */ + const ulint* offsets)/* in: array returned by rec_get_offsets() */ +{ + ulint n_fields; + ut_ad(offsets); + n_fields = offsets[1]; + ut_ad(n_fields > 0); + ut_ad(n_fields <= REC_MAX_N_FIELDS); + ut_ad(n_fields + REC_OFFS_HEADER_SIZE + <= rec_offs_get_n_alloc(offsets)); + return(n_fields); +} + +/**************************************************************** +Validates offsets returned by rec_get_offsets(). */ +UNIV_INLINE +ibool +rec_offs_validate( +/*==============*/ + /* out: TRUE if valid */ + const rec_t* rec, /* in: record or NULL */ + const dict_index_t* index, /* in: record descriptor or NULL */ + const ulint* offsets)/* in: array returned by + rec_get_offsets() */ +{ + ulint i = rec_offs_n_fields(offsets); + ulint last = ULINT_MAX; + ulint comp = *rec_offs_base(offsets) & REC_OFFS_COMPACT; + + if (rec) { + ut_ad((ulint) rec == offsets[2]); + if (!comp) { + ut_a(rec_get_n_fields_old(rec) >= i); + } + } + if (index) { + ulint max_n_fields; + ut_ad((ulint) index == offsets[3]); + max_n_fields = ut_max( + dict_index_get_n_fields(index), + dict_index_get_n_unique_in_tree(index) + 1); + if (comp && rec) { + switch (rec_get_status(rec)) { + case REC_STATUS_ORDINARY: + break; + case REC_STATUS_NODE_PTR: + max_n_fields = dict_index_get_n_unique_in_tree( + index) + 1; + break; + case REC_STATUS_INFIMUM: + case REC_STATUS_SUPREMUM: + max_n_fields = 1; + break; + default: + ut_error; + } + } + /* index->n_def == 0 for dummy indexes if !comp */ + ut_a(!comp || index->n_def); + ut_a(!index->n_def || i <= max_n_fields); + } + while (i--) { + ulint curr = rec_offs_base(offsets)[1 + i] & REC_OFFS_MASK; + ut_a(curr <= last); + last = curr; + } + return(TRUE); +} +#ifdef UNIV_DEBUG +/**************************************************************** +Updates debug data in offsets, in order to avoid bogus +rec_offs_validate() failures. */ +UNIV_INLINE +void +rec_offs_make_valid( +/*================*/ + const rec_t* rec, /* in: record */ + const dict_index_t* index, /* in: record descriptor */ + ulint* offsets)/* in: array returned by + rec_get_offsets() */ +{ + ut_ad(rec); + ut_ad(index); + ut_ad(offsets); + ut_ad(rec_get_n_fields(rec, index) >= rec_offs_n_fields(offsets)); + offsets[2] = (ulint) rec; + offsets[3] = (ulint) index; +} +#endif /* UNIV_DEBUG */ + +/**************************************************************** +The following function is used to get an offset to the nth +data field in a record. */ +UNIV_INLINE +ulint +rec_get_nth_field_offs( +/*===================*/ + /* out: offset from the origin of rec */ + const ulint* offsets,/* in: array returned by rec_get_offsets() */ + ulint n, /* in: index of the field */ + ulint* len) /* out: length of the field; UNIV_SQL_NULL + if SQL null */ +{ + ulint offs; + ulint length; + ut_ad(n < rec_offs_n_fields(offsets)); + ut_ad(len); + + if (UNIV_UNLIKELY(n == 0)) { + offs = 0; + } else { + offs = rec_offs_base(offsets)[n] & REC_OFFS_MASK; + } + + length = rec_offs_base(offsets)[1 + n]; + + if (length & REC_OFFS_SQL_NULL) { + length = UNIV_SQL_NULL; + } else { + length &= REC_OFFS_MASK; + length -= offs; + } + + *len = length; + return(offs); +} + +/********************************************************** +Determine if the offsets are for a record in the new +compact format. */ +UNIV_INLINE +ulint +rec_offs_comp( +/*==========*/ + /* out: nonzero if compact format */ + const ulint* offsets)/* in: array returned by rec_get_offsets() */ +{ + ut_ad(rec_offs_validate(NULL, NULL, offsets)); + return(*rec_offs_base(offsets) & REC_OFFS_COMPACT); +} + +/********************************************************** +Determine if the offsets are for a record containing +externally stored columns. */ +UNIV_INLINE +ulint +rec_offs_any_extern( +/*================*/ + /* out: nonzero if externally stored */ + const ulint* offsets)/* in: array returned by rec_get_offsets() */ +{ + ut_ad(rec_offs_validate(NULL, NULL, offsets)); + return(UNIV_UNLIKELY(*rec_offs_base(offsets) & REC_OFFS_EXTERNAL)); +} + +/********************************************************** +Returns nonzero if the extern bit is set in nth field of rec. */ +UNIV_INLINE +ulint +rec_offs_nth_extern( +/*================*/ + /* out: nonzero if externally stored */ + const ulint* offsets,/* in: array returned by rec_get_offsets() */ + ulint n) /* in: nth field */ +{ + ut_ad(rec_offs_validate(NULL, NULL, offsets)); + ut_ad(n < rec_offs_n_fields(offsets)); + return(UNIV_UNLIKELY(rec_offs_base(offsets)[1 + n] + & REC_OFFS_EXTERNAL)); +} + +/********************************************************** +Returns nonzero if the SQL NULL bit is set in nth field of rec. */ +UNIV_INLINE +ulint +rec_offs_nth_sql_null( +/*==================*/ + /* out: nonzero if SQL NULL */ + const ulint* offsets,/* in: array returned by rec_get_offsets() */ + ulint n) /* in: nth field */ +{ + ut_ad(rec_offs_validate(NULL, NULL, offsets)); + ut_ad(n < rec_offs_n_fields(offsets)); + return(UNIV_UNLIKELY(rec_offs_base(offsets)[1 + n] + & REC_OFFS_SQL_NULL)); +} + +/********************************************************** +Gets the physical size of a field. */ +UNIV_INLINE +ulint +rec_offs_nth_size( +/*==============*/ + /* out: length of field */ + const ulint* offsets,/* in: array returned by rec_get_offsets() */ + ulint n) /* in: nth field */ +{ + ut_ad(rec_offs_validate(NULL, NULL, offsets)); + ut_ad(n < rec_offs_n_fields(offsets)); + if (!n) { + return(rec_offs_base(offsets)[1 + n] & REC_OFFS_MASK); + } + return((rec_offs_base(offsets)[1 + n] - rec_offs_base(offsets)[n]) + & REC_OFFS_MASK); +} + +/********************************************************** +Returns the number of extern bits set in a record. */ +UNIV_INLINE +ulint +rec_offs_n_extern( +/*==============*/ + /* out: number of externally stored fields */ + const ulint* offsets)/* in: array returned by rec_get_offsets() */ +{ + ulint n = 0; + + if (rec_offs_any_extern(offsets)) { + ulint i; + + for (i = rec_offs_n_fields(offsets); i--; ) { + if (rec_offs_nth_extern(offsets, i)) { + n++; + } + } + } + + return(n); +} + +/********************************************************** +Returns the offset of n - 1th field end if the record is stored in the 1-byte +offsets form. If the field is SQL null, the flag is ORed in the returned +value. This function and the 2-byte counterpart are defined here because the +C-compiler was not able to sum negative and positive constant offsets, and +warned of constant arithmetic overflow within the compiler. */ +UNIV_INLINE +ulint +rec_1_get_prev_field_end_info( +/*==========================*/ + /* out: offset of the start of the + PREVIOUS field, SQL null flag ORed */ + const rec_t* rec, /* in: record */ + ulint n) /* in: field index */ +{ + ut_ad(rec_get_1byte_offs_flag(rec)); + ut_ad(n <= rec_get_n_fields_old(rec)); + + return(mach_read_from_1(rec - (REC_N_OLD_EXTRA_BYTES + n))); +} + +/********************************************************** +Returns the offset of n - 1th field end if the record is stored in the 2-byte +offsets form. If the field is SQL null, the flag is ORed in the returned +value. */ +UNIV_INLINE +ulint +rec_2_get_prev_field_end_info( +/*==========================*/ + /* out: offset of the start of the + PREVIOUS field, SQL null flag ORed */ + const rec_t* rec, /* in: record */ + ulint n) /* in: field index */ +{ + ut_ad(!rec_get_1byte_offs_flag(rec)); + ut_ad(n <= rec_get_n_fields_old(rec)); + + return(mach_read_from_2(rec - (REC_N_OLD_EXTRA_BYTES + 2 * n))); +} + +/********************************************************** +Sets the field end info for the nth field if the record is stored in the +1-byte format. */ +UNIV_INLINE +void +rec_1_set_field_end_info( +/*=====================*/ + rec_t* rec, /* in: record */ + ulint n, /* in: field index */ + ulint info) /* in: value to set */ +{ + ut_ad(rec_get_1byte_offs_flag(rec)); + ut_ad(n < rec_get_n_fields_old(rec)); + + mach_write_to_1(rec - (REC_N_OLD_EXTRA_BYTES + n + 1), info); +} + +/********************************************************** +Sets the field end info for the nth field if the record is stored in the +2-byte format. */ +UNIV_INLINE +void +rec_2_set_field_end_info( +/*=====================*/ + rec_t* rec, /* in: record */ + ulint n, /* in: field index */ + ulint info) /* in: value to set */ +{ + ut_ad(!rec_get_1byte_offs_flag(rec)); + ut_ad(n < rec_get_n_fields_old(rec)); + + mach_write_to_2(rec - (REC_N_OLD_EXTRA_BYTES + 2 * n + 2), info); +} + +/********************************************************** +Returns the offset of nth field start if the record is stored in the 1-byte +offsets form. */ +UNIV_INLINE +ulint +rec_1_get_field_start_offs( +/*=======================*/ + /* out: offset of the start of the field */ + const rec_t* rec, /* in: record */ + ulint n) /* in: field index */ +{ + ut_ad(rec_get_1byte_offs_flag(rec)); + ut_ad(n <= rec_get_n_fields_old(rec)); + + if (n == 0) { + + return(0); + } + + return(rec_1_get_prev_field_end_info(rec, n) + & ~REC_1BYTE_SQL_NULL_MASK); +} + +/********************************************************** +Returns the offset of nth field start if the record is stored in the 2-byte +offsets form. */ +UNIV_INLINE +ulint +rec_2_get_field_start_offs( +/*=======================*/ + /* out: offset of the start of the field */ + const rec_t* rec, /* in: record */ + ulint n) /* in: field index */ +{ + ut_ad(!rec_get_1byte_offs_flag(rec)); + ut_ad(n <= rec_get_n_fields_old(rec)); + + if (n == 0) { + + return(0); + } + + return(rec_2_get_prev_field_end_info(rec, n) + & ~(REC_2BYTE_SQL_NULL_MASK | REC_2BYTE_EXTERN_MASK)); +} + +/********************************************************** +The following function is used to read the offset of the start of a data field +in the record. The start of an SQL null field is the end offset of the +previous non-null field, or 0, if none exists. If n is the number of the last +field + 1, then the end offset of the last field is returned. */ +UNIV_INLINE +ulint +rec_get_field_start_offs( +/*=====================*/ + /* out: offset of the start of the field */ + const rec_t* rec, /* in: record */ + ulint n) /* in: field index */ +{ + ut_ad(rec); + ut_ad(n <= rec_get_n_fields_old(rec)); + + if (n == 0) { + + return(0); + } + + if (rec_get_1byte_offs_flag(rec)) { + + return(rec_1_get_field_start_offs(rec, n)); + } + + return(rec_2_get_field_start_offs(rec, n)); +} + +/**************************************************************** +Gets the physical size of an old-style field. +Also an SQL null may have a field of size > 0, +if the data type is of a fixed size. */ +UNIV_INLINE +ulint +rec_get_nth_field_size( +/*===================*/ + /* out: field size in bytes */ + const rec_t* rec, /* in: record */ + ulint n) /* in: index of the field */ +{ + ulint os; + ulint next_os; + + os = rec_get_field_start_offs(rec, n); + next_os = rec_get_field_start_offs(rec, n + 1); + + ut_ad(next_os - os < UNIV_PAGE_SIZE); + + return(next_os - os); +} + +/*************************************************************** +This is used to modify the value of an already existing field in a record. +The previous value must have exactly the same size as the new value. If len +is UNIV_SQL_NULL then the field is treated as an SQL null. +For records in ROW_FORMAT=COMPACT (new-style records), len must not be +UNIV_SQL_NULL unless the field already is SQL null. */ +UNIV_INLINE +void +rec_set_nth_field( +/*==============*/ + rec_t* rec, /* in: record */ + const ulint* offsets,/* in: array returned by rec_get_offsets() */ + ulint n, /* in: index number of the field */ + const void* data, /* in: pointer to the data + if not SQL null */ + ulint len) /* in: length of the data or UNIV_SQL_NULL */ +{ + byte* data2; + ulint len2; + + ut_ad(rec); + ut_ad(rec_offs_validate(rec, NULL, offsets)); + + if (UNIV_UNLIKELY(len == UNIV_SQL_NULL)) { + if (!rec_offs_nth_sql_null(offsets, n)) { + ut_a(!rec_offs_comp(offsets)); + rec_set_nth_field_sql_null(rec, n); + } + + return; + } + + data2 = rec_get_nth_field(rec, offsets, n, &len2); + if (len2 == UNIV_SQL_NULL) { + ut_ad(!rec_offs_comp(offsets)); + rec_set_nth_field_null_bit(rec, n, FALSE); + ut_ad(len == rec_get_nth_field_size(rec, n)); + } else { + ut_ad(len2 == len); + } + + ut_memcpy(data2, data, len); +} + +/************************************************************** +The following function returns the data size of an old-style physical +record, that is the sum of field lengths. SQL null fields +are counted as length 0 fields. The value returned by the function +is the distance from record origin to record end in bytes. */ +UNIV_INLINE +ulint +rec_get_data_size_old( +/*==================*/ + /* out: size */ + const rec_t* rec) /* in: physical record */ +{ + ut_ad(rec); + + return(rec_get_field_start_offs(rec, rec_get_n_fields_old(rec))); +} + +/************************************************************** +The following function sets the number of fields in offsets. */ +UNIV_INLINE +void +rec_offs_set_n_fields( +/*==================*/ + ulint* offsets, /* in/out: array returned by + rec_get_offsets() */ + ulint n_fields) /* in: number of fields */ +{ + ut_ad(offsets); + ut_ad(n_fields > 0); + ut_ad(n_fields <= REC_MAX_N_FIELDS); + ut_ad(n_fields + REC_OFFS_HEADER_SIZE + <= rec_offs_get_n_alloc(offsets)); + offsets[1] = n_fields; +} + +/************************************************************** +The following function returns the data size of a physical +record, that is the sum of field lengths. SQL null fields +are counted as length 0 fields. The value returned by the function +is the distance from record origin to record end in bytes. */ +UNIV_INLINE +ulint +rec_offs_data_size( +/*===============*/ + /* out: size */ + const ulint* offsets)/* in: array returned by rec_get_offsets() */ +{ + ulint size; + + ut_ad(rec_offs_validate(NULL, NULL, offsets)); + size = rec_offs_base(offsets)[rec_offs_n_fields(offsets)] + & REC_OFFS_MASK; + ut_ad(size < UNIV_PAGE_SIZE); + return(size); +} + +/************************************************************** +Returns the total size of record minus data size of record. The value +returned by the function is the distance from record start to record origin +in bytes. */ +UNIV_INLINE +ulint +rec_offs_extra_size( +/*================*/ + /* out: size */ + const ulint* offsets)/* in: array returned by rec_get_offsets() */ +{ + ulint size; + ut_ad(rec_offs_validate(NULL, NULL, offsets)); + size = *rec_offs_base(offsets) & ~(REC_OFFS_COMPACT | REC_OFFS_EXTERNAL); + ut_ad(size < UNIV_PAGE_SIZE); + return(size); +} + +/************************************************************** +Returns the total size of a physical record. */ +UNIV_INLINE +ulint +rec_offs_size( +/*==========*/ + /* out: size */ + const ulint* offsets)/* in: array returned by rec_get_offsets() */ +{ + return(rec_offs_data_size(offsets) + rec_offs_extra_size(offsets)); +} + +/************************************************************** +Returns a pointer to the end of the record. */ +UNIV_INLINE +byte* +rec_get_end( +/*========*/ + /* out: pointer to end */ + rec_t* rec, /* in: pointer to record */ + const ulint* offsets)/* in: array returned by rec_get_offsets() */ +{ + ut_ad(rec_offs_validate(rec, NULL, offsets)); + return(rec + rec_offs_data_size(offsets)); +} + +/************************************************************** +Returns a pointer to the start of the record. */ +UNIV_INLINE +byte* +rec_get_start( +/*==========*/ + /* out: pointer to start */ + rec_t* rec, /* in: pointer to record */ + const ulint* offsets)/* in: array returned by rec_get_offsets() */ +{ + ut_ad(rec_offs_validate(rec, NULL, offsets)); + return(rec - rec_offs_extra_size(offsets)); +} + +/******************************************************************* +Copies a physical record to a buffer. */ +UNIV_INLINE +rec_t* +rec_copy( +/*=====*/ + /* out: pointer to the origin of the copy */ + void* buf, /* in: buffer */ + const rec_t* rec, /* in: physical record */ + const ulint* offsets)/* in: array returned by rec_get_offsets() */ +{ + ulint extra_len; + ulint data_len; + + ut_ad(rec && buf); + ut_ad(rec_offs_validate((rec_t*) rec, NULL, offsets)); + ut_ad(rec_validate(rec, offsets)); + + extra_len = rec_offs_extra_size(offsets); + data_len = rec_offs_data_size(offsets); + + ut_memcpy(buf, rec - extra_len, extra_len + data_len); + + return((byte*)buf + extra_len); +} + +/************************************************************** +Returns the extra size of an old-style physical record if we know its +data size and number of fields. */ +UNIV_INLINE +ulint +rec_get_converted_extra_size( +/*=========================*/ + /* out: extra size */ + ulint data_size, /* in: data size */ + ulint n_fields, /* in: number of fields */ + ulint n_ext) /* in: number of externally stored columns */ +{ + if (!n_ext && data_size <= REC_1BYTE_OFFS_LIMIT) { + + return(REC_N_OLD_EXTRA_BYTES + n_fields); + } + + return(REC_N_OLD_EXTRA_BYTES + 2 * n_fields); +} + +/************************************************************** +The following function returns the size of a data tuple when converted to +a physical record. */ +UNIV_INLINE +ulint +rec_get_converted_size( +/*===================*/ + /* out: size */ + dict_index_t* index, /* in: record descriptor */ + const dtuple_t* dtuple, /* in: data tuple */ + ulint n_ext) /* in: number of externally stored columns */ +{ + ulint data_size; + ulint extra_size; + + ut_ad(index); + ut_ad(dtuple); + ut_ad(dtuple_check_typed(dtuple)); + + ut_ad(index->type & DICT_UNIVERSAL + || dtuple_get_n_fields(dtuple) + == (((dtuple_get_info_bits(dtuple) & REC_NEW_STATUS_MASK) + == REC_STATUS_NODE_PTR) + ? dict_index_get_n_unique_in_tree(index) + 1 + : dict_index_get_n_fields(index))); + + if (dict_table_is_comp(index->table)) { + return(rec_get_converted_size_comp(index, + dtuple_get_info_bits(dtuple) + & REC_NEW_STATUS_MASK, + dtuple->fields, + dtuple->n_fields, NULL)); + } + + data_size = dtuple_get_data_size(dtuple); + + extra_size = rec_get_converted_extra_size( + data_size, dtuple_get_n_fields(dtuple), n_ext); + + return(data_size + extra_size); +} + +/**************************************************************** +Folds a prefix of a physical record to a ulint. Folds only existing fields, +that is, checks that we do not run out of the record. */ +UNIV_INLINE +ulint +rec_fold( +/*=====*/ + /* out: the folded value */ + const rec_t* rec, /* in: the physical record */ + const ulint* offsets, /* in: array returned by + rec_get_offsets() */ + ulint n_fields, /* in: number of complete + fields to fold */ + ulint n_bytes, /* in: number of bytes to fold + in an incomplete last field */ + dulint tree_id) /* in: index tree id */ +{ + ulint i; + const byte* data; + ulint len; + ulint fold; + ulint n_fields_rec; + + ut_ad(rec_offs_validate(rec, NULL, offsets)); + ut_ad(rec_validate(rec, offsets)); + ut_ad(n_fields + n_bytes > 0); + + n_fields_rec = rec_offs_n_fields(offsets); + ut_ad(n_fields <= n_fields_rec); + ut_ad(n_fields < n_fields_rec || n_bytes == 0); + + if (n_fields > n_fields_rec) { + n_fields = n_fields_rec; + } + + if (n_fields == n_fields_rec) { + n_bytes = 0; + } + + fold = ut_fold_dulint(tree_id); + + for (i = 0; i < n_fields; i++) { + data = rec_get_nth_field(rec, offsets, i, &len); + + if (len != UNIV_SQL_NULL) { + fold = ut_fold_ulint_pair(fold, + ut_fold_binary(data, len)); + } + } + + if (n_bytes > 0) { + data = rec_get_nth_field(rec, offsets, i, &len); + + if (len != UNIV_SQL_NULL) { + if (len > n_bytes) { + len = n_bytes; + } + + fold = ut_fold_ulint_pair(fold, + ut_fold_binary(data, len)); + } + } + + return(fold); +} diff --git a/storage/xtradb/include/rem0types.h b/storage/xtradb/include/rem0types.h new file mode 100644 index 00000000000..d0b11b92495 --- /dev/null +++ b/storage/xtradb/include/rem0types.h @@ -0,0 +1,45 @@ +/***************************************************************************** + +Copyright (c) 1994, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/************************************************************************ +Record manager global types + +Created 5/30/1994 Heikki Tuuri +*************************************************************************/ + +#ifndef rem0types_h +#define rem0types_h + +/* We define the physical record simply as an array of bytes */ +typedef byte rec_t; + +/* Maximum values for various fields (for non-blob tuples) */ +#define REC_MAX_N_FIELDS (1024 - 1) +#define REC_MAX_HEAP_NO (2 * 8192 - 1) +#define REC_MAX_N_OWNED (16 - 1) + +/* REC_MAX_INDEX_COL_LEN is measured in bytes and is the maximum +indexed column length (or indexed prefix length). It is set to 3*256, +so that one can create a column prefix index on 256 characters of a +TEXT or VARCHAR column also in the UTF-8 charset. In that charset, +a character may take at most 3 bytes. +This constant MUST NOT BE CHANGED, or the compatibility of InnoDB data +files would be at risk! */ +#define REC_MAX_INDEX_COL_LEN 768 + +#endif diff --git a/storage/xtradb/include/row0ext.h b/storage/xtradb/include/row0ext.h new file mode 100644 index 00000000000..08ebafa4d98 --- /dev/null +++ b/storage/xtradb/include/row0ext.h @@ -0,0 +1,98 @@ +/***************************************************************************** + +Copyright (c) 2006, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/****************************************************** +Caching of externally stored column prefixes + +Created September 2006 Marko Makela +*******************************************************/ + +#ifndef row0ext_h +#define row0ext_h + +#include "univ.i" +#include "row0types.h" +#include "data0types.h" +#include "mem0mem.h" + +/************************************************************************ +Creates a cache of column prefixes of externally stored columns. */ +UNIV_INTERN +row_ext_t* +row_ext_create( +/*===========*/ + /* out,own: column prefix cache */ + ulint n_ext, /* in: number of externally stored columns */ + const ulint* ext, /* in: col_no's of externally stored columns + in the InnoDB table object, as reported by + dict_col_get_no(); NOT relative to the records + in the clustered index */ + const dtuple_t* tuple, /* in: data tuple containing the field + references of the externally stored + columns; must be indexed by col_no; + the clustered index record must be + covered by a lock or a page latch + to prevent deletion (rollback or purge). */ + ulint zip_size,/* compressed page size in bytes, or 0 */ + mem_heap_t* heap); /* in: heap where created */ + +/************************************************************************ +Looks up a column prefix of an externally stored column. */ +UNIV_INLINE +const byte* +row_ext_lookup_ith( +/*===============*/ + /* out: column prefix, or NULL if + the column is not stored externally, + or pointer to field_ref_zero + if the BLOB pointer is unset */ + const row_ext_t* ext, /* in/out: column prefix cache */ + ulint i, /* in: index of ext->ext[] */ + ulint* len); /* out: length of prefix, in bytes, + at most REC_MAX_INDEX_COL_LEN */ +/************************************************************************ +Looks up a column prefix of an externally stored column. */ +UNIV_INLINE +const byte* +row_ext_lookup( +/*===========*/ + /* out: column prefix, or NULL if + the column is not stored externally, + or pointer to field_ref_zero + if the BLOB pointer is unset */ + const row_ext_t* ext, /* in: column prefix cache */ + ulint col, /* in: column number in the InnoDB + table object, as reported by + dict_col_get_no(); NOT relative to the + records in the clustered index */ + ulint* len); /* out: length of prefix, in bytes, + at most REC_MAX_INDEX_COL_LEN */ + +/* Prefixes of externally stored columns */ +struct row_ext_struct{ + ulint n_ext; /* number of externally stored columns */ + const ulint* ext; /* col_no's of externally stored columns */ + byte* buf; /* backing store of the column prefix cache */ + ulint len[1]; /* prefix lengths; 0 if not cached */ +}; + +#ifndef UNIV_NONINL +#include "row0ext.ic" +#endif + +#endif diff --git a/storage/xtradb/include/row0ext.ic b/storage/xtradb/include/row0ext.ic new file mode 100644 index 00000000000..e56fc175764 --- /dev/null +++ b/storage/xtradb/include/row0ext.ic @@ -0,0 +1,87 @@ +/***************************************************************************** + +Copyright (c) 2006, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/****************************************************** +Caching of externally stored column prefixes + +Created September 2006 Marko Makela +*******************************************************/ + +#include "rem0types.h" +#include "btr0types.h" + +/************************************************************************ +Looks up a column prefix of an externally stored column. */ +UNIV_INLINE +const byte* +row_ext_lookup_ith( +/*===============*/ + /* out: column prefix, or NULL if + the column is not stored externally, + or pointer to field_ref_zero + if the BLOB pointer is unset */ + const row_ext_t* ext, /* in/out: column prefix cache */ + ulint i, /* in: index of ext->ext[] */ + ulint* len) /* out: length of prefix, in bytes, + at most REC_MAX_INDEX_COL_LEN */ +{ + ut_ad(ext); + ut_ad(len); + ut_ad(i < ext->n_ext); + + *len = ext->len[i]; + + if (UNIV_UNLIKELY(*len == 0)) { + /* The BLOB could not be fetched to the cache. */ + return(field_ref_zero); + } else { + return(ext->buf + i * REC_MAX_INDEX_COL_LEN); + } +} + +/************************************************************************ +Looks up a column prefix of an externally stored column. */ +UNIV_INLINE +const byte* +row_ext_lookup( +/*===========*/ + /* out: column prefix, or NULL if + the column is not stored externally, + or pointer to field_ref_zero + if the BLOB pointer is unset */ + const row_ext_t* ext, /* in: column prefix cache */ + ulint col, /* in: column number in the InnoDB + table object, as reported by + dict_col_get_no(); NOT relative to the + records in the clustered index */ + ulint* len) /* out: length of prefix, in bytes, + at most REC_MAX_INDEX_COL_LEN */ +{ + ulint i; + + ut_ad(ext); + ut_ad(len); + + for (i = 0; i < ext->n_ext; i++) { + if (col == ext->ext[i]) { + return(row_ext_lookup_ith(ext, i, len)); + } + } + + return(NULL); +} diff --git a/storage/xtradb/include/row0ins.h b/storage/xtradb/include/row0ins.h new file mode 100644 index 00000000000..6aa83bed0f6 --- /dev/null +++ b/storage/xtradb/include/row0ins.h @@ -0,0 +1,157 @@ +/***************************************************************************** + +Copyright (c) 1996, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/****************************************************** +Insert into a table + +Created 4/20/1996 Heikki Tuuri +*******************************************************/ + +#ifndef row0ins_h +#define row0ins_h + +#include "univ.i" +#include "data0data.h" +#include "que0types.h" +#include "dict0types.h" +#include "trx0types.h" +#include "row0types.h" + +/******************************************************************* +Checks if foreign key constraint fails for an index entry. Sets shared locks +which lock either the success or the failure of the constraint. NOTE that +the caller must have a shared latch on dict_foreign_key_check_lock. */ +UNIV_INTERN +ulint +row_ins_check_foreign_constraint( +/*=============================*/ + /* out: DB_SUCCESS, DB_LOCK_WAIT, + DB_NO_REFERENCED_ROW, + or DB_ROW_IS_REFERENCED */ + ibool check_ref,/* in: TRUE If we want to check that + the referenced table is ok, FALSE if we + want to to check the foreign key table */ + dict_foreign_t* foreign,/* in: foreign constraint; NOTE that the + tables mentioned in it must be in the + dictionary cache if they exist at all */ + dict_table_t* table, /* in: if check_ref is TRUE, then the foreign + table, else the referenced table */ + dtuple_t* entry, /* in: index entry for index */ + que_thr_t* thr); /* in: query thread */ +/************************************************************************* +Creates an insert node struct. */ +UNIV_INTERN +ins_node_t* +ins_node_create( +/*============*/ + /* out, own: insert node struct */ + ulint ins_type, /* in: INS_VALUES, ... */ + dict_table_t* table, /* in: table where to insert */ + mem_heap_t* heap); /* in: mem heap where created */ +/************************************************************************* +Sets a new row to insert for an INS_DIRECT node. This function is only used +if we have constructed the row separately, which is a rare case; this +function is quite slow. */ +UNIV_INTERN +void +ins_node_set_new_row( +/*=================*/ + ins_node_t* node, /* in: insert node */ + dtuple_t* row); /* in: new row (or first row) for the node */ +/******************************************************************* +Inserts an index entry to index. Tries first optimistic, then pessimistic +descent down the tree. If the entry matches enough to a delete marked record, +performs the insert by updating or delete unmarking the delete marked +record. */ +UNIV_INTERN +ulint +row_ins_index_entry( +/*================*/ + /* out: DB_SUCCESS, DB_LOCK_WAIT, + DB_DUPLICATE_KEY, or some other error code */ + dict_index_t* index, /* in: index */ + dtuple_t* entry, /* in: index entry to insert */ + ulint n_ext, /* in: number of externally stored columns */ + ibool foreign,/* in: TRUE=check foreign key constraints */ + que_thr_t* thr); /* in: query thread */ +/*************************************************************** +Inserts a row to a table. This is a high-level function used in +SQL execution graphs. */ +UNIV_INTERN +que_thr_t* +row_ins_step( +/*=========*/ + /* out: query thread to run next or NULL */ + que_thr_t* thr); /* in: query thread */ +/*************************************************************** +Creates an entry template for each index of a table. */ +UNIV_INTERN +void +ins_node_create_entry_list( +/*=======================*/ + ins_node_t* node); /* in: row insert node */ + +/* Insert node structure */ + +struct ins_node_struct{ + que_common_t common; /* node type: QUE_NODE_INSERT */ + ulint ins_type;/* INS_VALUES, INS_SEARCHED, or INS_DIRECT */ + dtuple_t* row; /* row to insert */ + dict_table_t* table; /* table where to insert */ + sel_node_t* select; /* select in searched insert */ + que_node_t* values_list;/* list of expressions to evaluate and + insert in an INS_VALUES insert */ + ulint state; /* node execution state */ + dict_index_t* index; /* NULL, or the next index where the index + entry should be inserted */ + dtuple_t* entry; /* NULL, or entry to insert in the index; + after a successful insert of the entry, + this should be reset to NULL */ + UT_LIST_BASE_NODE_T(dtuple_t) + entry_list;/* list of entries, one for each index */ + byte* row_id_buf;/* buffer for the row id sys field in row */ + dulint trx_id; /* trx id or the last trx which executed the + node */ + byte* trx_id_buf;/* buffer for the trx id sys field in row */ + mem_heap_t* entry_sys_heap; + /* memory heap used as auxiliary storage; + entry_list and sys fields are stored here; + if this is NULL, entry list should be created + and buffers for sys fields in row allocated */ + ulint magic_n; +}; + +#define INS_NODE_MAGIC_N 15849075 + +/* Insert node types */ +#define INS_SEARCHED 0 /* INSERT INTO ... SELECT ... */ +#define INS_VALUES 1 /* INSERT INTO ... VALUES ... */ +#define INS_DIRECT 2 /* this is for internal use in dict0crea: + insert the row directly */ + +/* Node execution states */ +#define INS_NODE_SET_IX_LOCK 1 /* we should set an IX lock on table */ +#define INS_NODE_ALLOC_ROW_ID 2 /* row id should be allocated */ +#define INS_NODE_INSERT_ENTRIES 3 /* index entries should be built and + inserted */ + +#ifndef UNIV_NONINL +#include "row0ins.ic" +#endif + +#endif diff --git a/storage/xtradb/include/row0ins.ic b/storage/xtradb/include/row0ins.ic new file mode 100644 index 00000000000..b7aeaf97834 --- /dev/null +++ b/storage/xtradb/include/row0ins.ic @@ -0,0 +1,25 @@ +/***************************************************************************** + +Copyright (c) 1996, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/****************************************************** +Insert into a table + +Created 4/20/1996 Heikki Tuuri +*******************************************************/ + + diff --git a/storage/xtradb/include/row0merge.h b/storage/xtradb/include/row0merge.h new file mode 100644 index 00000000000..9975497cbeb --- /dev/null +++ b/storage/xtradb/include/row0merge.h @@ -0,0 +1,198 @@ +/***************************************************************************** + +Copyright (c) 2005, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/****************************************************** +Index build routines using a merge sort + +Created 13/06/2005 Jan Lindstrom +*******************************************************/ + +#ifndef row0merge_h +#define row0merge_h + +#include "univ.i" +#include "data0data.h" +#include "dict0types.h" +#include "trx0types.h" +#include "que0types.h" +#include "mtr0mtr.h" +#include "rem0types.h" +#include "rem0rec.h" +#include "read0types.h" +#include "btr0types.h" +#include "row0mysql.h" +#include "lock0types.h" + +/* This structure holds index field definitions */ + +struct merge_index_field_struct { + ulint prefix_len; /* Prefix len */ + const char* field_name; /* Field name */ +}; + +typedef struct merge_index_field_struct merge_index_field_t; + +/* This structure holds index definitions */ + +struct merge_index_def_struct { + const char* name; /* Index name */ + ulint ind_type; /* 0, DICT_UNIQUE, + or DICT_CLUSTERED */ + ulint n_fields; /* Number of fields in index */ + merge_index_field_t* fields; /* Field definitions */ +}; + +typedef struct merge_index_def_struct merge_index_def_t; + +/************************************************************************* +Sets an exclusive lock on a table, for the duration of creating indexes. */ +UNIV_INTERN +ulint +row_merge_lock_table( +/*=================*/ + /* out: error code or DB_SUCCESS */ + trx_t* trx, /* in/out: transaction */ + dict_table_t* table, /* in: table to lock */ + enum lock_mode mode); /* in: LOCK_X or LOCK_S */ +/************************************************************************* +Drop an index from the InnoDB system tables. The data dictionary must +have been locked exclusively by the caller, because the transaction +will not be committed. */ +UNIV_INTERN +void +row_merge_drop_index( +/*=================*/ + dict_index_t* index, /* in: index to be removed */ + dict_table_t* table, /* in: table */ + trx_t* trx); /* in: transaction handle */ +/************************************************************************* +Drop those indexes which were created before an error occurred when +building an index. The data dictionary must have been locked +exclusively by the caller, because the transaction will not be +committed. */ +UNIV_INTERN +void +row_merge_drop_indexes( +/*===================*/ + trx_t* trx, /* in: transaction */ + dict_table_t* table, /* in: table containing the indexes */ + dict_index_t** index, /* in: indexes to drop */ + ulint num_created); /* in: number of elements in index[] */ +/************************************************************************* +Drop all partially created indexes during crash recovery. */ +UNIV_INTERN +void +row_merge_drop_temp_indexes(void); +/*=============================*/ +/************************************************************************* +Rename the tables in the data dictionary. The data dictionary must +have been locked exclusively by the caller, because the transaction +will not be committed. */ +UNIV_INTERN +ulint +row_merge_rename_tables( +/*====================*/ + /* out: error code or DB_SUCCESS */ + dict_table_t* old_table, /* in/out: old table, renamed to + tmp_name */ + dict_table_t* new_table, /* in/out: new table, renamed to + old_table->name */ + const char* tmp_name, /* in: new name for old_table */ + trx_t* trx); /* in: transaction handle */ + +/************************************************************************* +Create a temporary table for creating a primary key, using the definition +of an existing table. */ +UNIV_INTERN +dict_table_t* +row_merge_create_temporary_table( +/*=============================*/ + /* out: table, + or NULL on error */ + const char* table_name, /* in: new table name */ + const merge_index_def_t*index_def, /* in: the index definition + of the primary key */ + const dict_table_t* table, /* in: old table definition */ + trx_t* trx); /* in/out: transaction + (sets error_state) */ +/************************************************************************* +Rename the temporary indexes in the dictionary to permanent ones. The +data dictionary must have been locked exclusively by the caller, +because the transaction will not be committed. */ +UNIV_INTERN +ulint +row_merge_rename_indexes( +/*=====================*/ + /* out: DB_SUCCESS if all OK */ + trx_t* trx, /* in/out: transaction */ + dict_table_t* table); /* in/out: table with new indexes */ +/************************************************************************* +Create the index and load in to the dictionary. */ +UNIV_INTERN +dict_index_t* +row_merge_create_index( +/*===================*/ + /* out: index, or NULL on error */ + trx_t* trx, /* in/out: trx (sets error_state) */ + dict_table_t* table, /* in: the index is on this table */ + const merge_index_def_t* /* in: the index definition */ + index_def); +#ifdef ROW_MERGE_IS_INDEX_USABLE +/************************************************************************* +Check if a transaction can use an index. */ +UNIV_INTERN +ibool +row_merge_is_index_usable( +/*======================*/ + /* out: TRUE if index can be used by + the transaction else FALSE*/ + const trx_t* trx, /* in: transaction */ + const dict_index_t* index); /* in: index to check */ +#endif /* ROW_MERGE_IS_INDEX_USABLE */ +/************************************************************************* +If there are views that refer to the old table name then we "attach" to +the new instance of the table else we drop it immediately. */ +UNIV_INTERN +ulint +row_merge_drop_table( +/*=================*/ + /* out: DB_SUCCESS or error code */ + trx_t* trx, /* in: transaction */ + dict_table_t* table); /* in: table instance to drop */ + +/************************************************************************* +Build indexes on a table by reading a clustered index, +creating a temporary file containing index entries, merge sorting +these index entries and inserting sorted index entries to indexes. */ +UNIV_INTERN +ulint +row_merge_build_indexes( +/*====================*/ + /* out: DB_SUCCESS or error code */ + trx_t* trx, /* in: transaction */ + dict_table_t* old_table, /* in: table where rows are + read from */ + dict_table_t* new_table, /* in: table where indexes are + created; identical to old_table + unless creating a PRIMARY KEY */ + dict_index_t** indexes, /* in: indexes to be created */ + ulint n_indexes, /* in: size of indexes[] */ + TABLE* table); /* in/out: MySQL table, for + reporting erroneous key value + if applicable */ +#endif /* row0merge.h */ diff --git a/storage/xtradb/include/row0mysql.h b/storage/xtradb/include/row0mysql.h new file mode 100644 index 00000000000..c1e11124a5d --- /dev/null +++ b/storage/xtradb/include/row0mysql.h @@ -0,0 +1,769 @@ +/***************************************************************************** + +Copyright (c) 2000, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/****************************************************** +Interface between Innobase row operations and MySQL. +Contains also create table and other data dictionary operations. + +Created 9/17/2000 Heikki Tuuri +*******************************************************/ + +#ifndef row0mysql_h +#define row0mysql_h + +#include "univ.i" +#include "data0data.h" +#include "que0types.h" +#include "dict0types.h" +#include "trx0types.h" +#include "row0types.h" +#include "btr0pcur.h" +#include "trx0types.h" + +extern ibool row_rollback_on_timeout; + +typedef struct row_prebuilt_struct row_prebuilt_t; + +/*********************************************************************** +Frees the blob heap in prebuilt when no longer needed. */ +UNIV_INTERN +void +row_mysql_prebuilt_free_blob_heap( +/*==============================*/ + row_prebuilt_t* prebuilt); /* in: prebuilt struct of a + ha_innobase:: table handle */ +/*********************************************************************** +Stores a >= 5.0.3 format true VARCHAR length to dest, in the MySQL row +format. */ +UNIV_INTERN +byte* +row_mysql_store_true_var_len( +/*=========================*/ + /* out: pointer to the data, we skip the 1 or 2 bytes + at the start that are used to store the len */ + byte* dest, /* in: where to store */ + ulint len, /* in: length, must fit in two bytes */ + ulint lenlen);/* in: storage length of len: either 1 or 2 bytes */ +/*********************************************************************** +Reads a >= 5.0.3 format true VARCHAR length, in the MySQL row format, and +returns a pointer to the data. */ + +const byte* +row_mysql_read_true_varchar( +/*========================*/ + /* out: pointer to the data, we skip + the 1 or 2 bytes at the start that are + used to store the len */ + ulint* len, /* out: variable-length field length */ + const byte* field, /* in: field in the MySQL format */ + ulint lenlen);/* in: storage length of len: either 1 + or 2 bytes */ +/*********************************************************************** +Stores a reference to a BLOB in the MySQL format. */ +UNIV_INTERN +void +row_mysql_store_blob_ref( +/*=====================*/ + byte* dest, /* in: where to store */ + ulint col_len,/* in: dest buffer size: determines into + how many bytes the BLOB length is stored, + the space for the length may vary from 1 + to 4 bytes */ + const void* data, /* in: BLOB data; if the value to store + is SQL NULL this should be NULL pointer */ + ulint len); /* in: BLOB length; if the value to store + is SQL NULL this should be 0; remember + also to set the NULL bit in the MySQL record + header! */ +/*********************************************************************** +Reads a reference to a BLOB in the MySQL format. */ + +const byte* +row_mysql_read_blob_ref( +/*====================*/ + /* out: pointer to BLOB data */ + ulint* len, /* out: BLOB length */ + const byte* ref, /* in: BLOB reference in the + MySQL format */ + ulint col_len); /* in: BLOB reference length + (not BLOB length) */ +/****************************************************************** +Stores a non-SQL-NULL field given in the MySQL format in the InnoDB format. +The counterpart of this function is row_sel_field_store_in_mysql_format() in +row0sel.c. */ +UNIV_INTERN +byte* +row_mysql_store_col_in_innobase_format( +/*===================================*/ + /* out: up to which byte we used + buf in the conversion */ + dfield_t* dfield, /* in/out: dfield where dtype + information must be already set when + this function is called! */ + byte* buf, /* in/out: buffer for a converted + integer value; this must be at least + col_len long then! */ + ibool row_format_col, /* TRUE if the mysql_data is from + a MySQL row, FALSE if from a MySQL + key value; + in MySQL, a true VARCHAR storage + format differs in a row and in a + key value: in a key value the length + is always stored in 2 bytes! */ + const byte* mysql_data, /* in: MySQL column value, not + SQL NULL; NOTE that dfield may also + get a pointer to mysql_data, + therefore do not discard this as long + as dfield is used! */ + ulint col_len, /* in: MySQL column length; NOTE that + this is the storage length of the + column in the MySQL format row, not + necessarily the length of the actual + payload data; if the column is a true + VARCHAR then this is irrelevant */ + ulint comp); /* in: nonzero=compact format */ +/******************************************************************** +Handles user errors and lock waits detected by the database engine. */ +UNIV_INTERN +ibool +row_mysql_handle_errors( +/*====================*/ + /* out: TRUE if it was a lock wait and + we should continue running the query thread */ + ulint* new_err,/* out: possible new error encountered in + rollback, or the old error which was + during the function entry */ + trx_t* trx, /* in: transaction */ + que_thr_t* thr, /* in: query thread */ + trx_savept_t* savept);/* in: savepoint */ +/************************************************************************ +Create a prebuilt struct for a MySQL table handle. */ +UNIV_INTERN +row_prebuilt_t* +row_create_prebuilt( +/*================*/ + /* out, own: a prebuilt struct */ + dict_table_t* table); /* in: Innobase table handle */ +/************************************************************************ +Free a prebuilt struct for a MySQL table handle. */ +UNIV_INTERN +void +row_prebuilt_free( +/*==============*/ + row_prebuilt_t* prebuilt, /* in, own: prebuilt struct */ + ibool dict_locked); /* in: TRUE=data dictionary locked */ +/************************************************************************* +Updates the transaction pointers in query graphs stored in the prebuilt +struct. */ +UNIV_INTERN +void +row_update_prebuilt_trx( +/*====================*/ + /* out: prebuilt dtuple */ + row_prebuilt_t* prebuilt, /* in: prebuilt struct in MySQL + handle */ + trx_t* trx); /* in: transaction handle */ +/************************************************************************* +Unlocks AUTO_INC type locks that were possibly reserved by a trx. */ +UNIV_INTERN +void +row_unlock_table_autoinc_for_mysql( +/*===============================*/ + trx_t* trx); /* in/out: transaction */ +/************************************************************************* +Sets an AUTO_INC type lock on the table mentioned in prebuilt. The +AUTO_INC lock gives exclusive access to the auto-inc counter of the +table. The lock is reserved only for the duration of an SQL statement. +It is not compatible with another AUTO_INC or exclusive lock on the +table. */ +UNIV_INTERN +int +row_lock_table_autoinc_for_mysql( +/*=============================*/ + /* out: error code or DB_SUCCESS */ + row_prebuilt_t* prebuilt); /* in: prebuilt struct in the MySQL + table handle */ +/************************************************************************* +Sets a table lock on the table mentioned in prebuilt. */ +UNIV_INTERN +int +row_lock_table_for_mysql( +/*=====================*/ + /* out: error code or DB_SUCCESS */ + row_prebuilt_t* prebuilt, /* in: prebuilt struct in the MySQL + table handle */ + dict_table_t* table, /* in: table to lock, or NULL + if prebuilt->table should be + locked as + prebuilt->select_lock_type */ + ulint mode); /* in: lock mode of table + (ignored if table==NULL) */ + +/************************************************************************* +Does an insert for MySQL. */ +UNIV_INTERN +int +row_insert_for_mysql( +/*=================*/ + /* out: error code or DB_SUCCESS */ + byte* mysql_rec, /* in: row in the MySQL format */ + row_prebuilt_t* prebuilt); /* in: prebuilt struct in MySQL + handle */ +/************************************************************************* +Builds a dummy query graph used in selects. */ +UNIV_INTERN +void +row_prebuild_sel_graph( +/*===================*/ + row_prebuilt_t* prebuilt); /* in: prebuilt struct in MySQL + handle */ +/************************************************************************* +Gets pointer to a prebuilt update vector used in updates. If the update +graph has not yet been built in the prebuilt struct, then this function +first builds it. */ +UNIV_INTERN +upd_t* +row_get_prebuilt_update_vector( +/*===========================*/ + /* out: prebuilt update vector */ + row_prebuilt_t* prebuilt); /* in: prebuilt struct in MySQL + handle */ +/************************************************************************* +Checks if a table is such that we automatically created a clustered +index on it (on row id). */ +UNIV_INTERN +ibool +row_table_got_default_clust_index( +/*==============================*/ + const dict_table_t* table); +/************************************************************************* +Calculates the key number used inside MySQL for an Innobase index. We have +to take into account if we generated a default clustered index for the table */ +UNIV_INTERN +ulint +row_get_mysql_key_number_for_index( +/*===============================*/ + const dict_index_t* index); +/************************************************************************* +Does an update or delete of a row for MySQL. */ +UNIV_INTERN +int +row_update_for_mysql( +/*=================*/ + /* out: error code or DB_SUCCESS */ + byte* mysql_rec, /* in: the row to be updated, in + the MySQL format */ + row_prebuilt_t* prebuilt); /* in: prebuilt struct in MySQL + handle */ +/************************************************************************* +This can only be used when srv_locks_unsafe_for_binlog is TRUE or +session is using a READ COMMITTED isolation level. Before +calling this function we must use trx_reset_new_rec_lock_info() and +trx_register_new_rec_lock() to store the information which new record locks +really were set. This function removes a newly set lock under prebuilt->pcur, +and also under prebuilt->clust_pcur. Currently, this is only used and tested +in the case of an UPDATE or a DELETE statement, where the row lock is of the +LOCK_X type. +Thus, this implements a 'mini-rollback' that releases the latest record +locks we set. */ +UNIV_INTERN +int +row_unlock_for_mysql( +/*=================*/ + /* out: error code or DB_SUCCESS */ + row_prebuilt_t* prebuilt, /* in: prebuilt struct in MySQL + handle */ + ibool has_latches_on_recs);/* TRUE if called so that we have + the latches on the records under pcur + and clust_pcur, and we do not need to + reposition the cursors. */ +/************************************************************************* +Creates an query graph node of 'update' type to be used in the MySQL +interface. */ +UNIV_INTERN +upd_node_t* +row_create_update_node_for_mysql( +/*=============================*/ + /* out, own: update node */ + dict_table_t* table, /* in: table to update */ + mem_heap_t* heap); /* in: mem heap from which allocated */ +/************************************************************************** +Does a cascaded delete or set null in a foreign key operation. */ +UNIV_INTERN +ulint +row_update_cascade_for_mysql( +/*=========================*/ + /* out: error code or DB_SUCCESS */ + que_thr_t* thr, /* in: query thread */ + upd_node_t* node, /* in: update node used in the cascade + or set null operation */ + dict_table_t* table); /* in: table where we do the operation */ +/************************************************************************* +Locks the data dictionary exclusively for performing a table create or other +data dictionary modification operation. */ +UNIV_INTERN +void +row_mysql_lock_data_dictionary_func( +/*================================*/ + trx_t* trx, /* in/out: transaction */ + const char* file, /* in: file name */ + ulint line); /* in: line number */ +#define row_mysql_lock_data_dictionary(trx) \ + row_mysql_lock_data_dictionary_func(trx, __FILE__, __LINE__) +/************************************************************************* +Unlocks the data dictionary exclusive lock. */ +UNIV_INTERN +void +row_mysql_unlock_data_dictionary( +/*=============================*/ + trx_t* trx); /* in/out: transaction */ +/************************************************************************* +Locks the data dictionary in shared mode from modifications, for performing +foreign key check, rollback, or other operation invisible to MySQL. */ +UNIV_INTERN +void +row_mysql_freeze_data_dictionary_func( +/*==================================*/ + trx_t* trx, /* in/out: transaction */ + const char* file, /* in: file name */ + ulint line); /* in: line number */ +#define row_mysql_freeze_data_dictionary(trx) \ + row_mysql_freeze_data_dictionary_func(trx, __FILE__, __LINE__) +/************************************************************************* +Unlocks the data dictionary shared lock. */ +UNIV_INTERN +void +row_mysql_unfreeze_data_dictionary( +/*===============================*/ + trx_t* trx); /* in/out: transaction */ +#ifndef UNIV_HOTBACKUP +/************************************************************************* +Creates a table for MySQL. If the name of the table ends in +one of "innodb_monitor", "innodb_lock_monitor", "innodb_tablespace_monitor", +"innodb_table_monitor", then this will also start the printing of monitor +output by the master thread. If the table name ends in "innodb_mem_validate", +InnoDB will try to invoke mem_validate(). */ +UNIV_INTERN +int +row_create_table_for_mysql( +/*=======================*/ + /* out: error code or DB_SUCCESS */ + dict_table_t* table, /* in, own: table definition + (will be freed) */ + trx_t* trx); /* in: transaction handle */ +/************************************************************************* +Does an index creation operation for MySQL. TODO: currently failure +to create an index results in dropping the whole table! This is no problem +currently as all indexes must be created at the same time as the table. */ +UNIV_INTERN +int +row_create_index_for_mysql( +/*=======================*/ + /* out: error number or DB_SUCCESS */ + dict_index_t* index, /* in, own: index definition + (will be freed) */ + trx_t* trx, /* in: transaction handle */ + const ulint* field_lengths); /* in: if not NULL, must contain + dict_index_get_n_fields(index) + actual field lengths for the + index columns, which are + then checked for not being too + large. */ +/************************************************************************* +Scans a table create SQL string and adds to the data dictionary +the foreign key constraints declared in the string. This function +should be called after the indexes for a table have been created. +Each foreign key constraint must be accompanied with indexes in +bot participating tables. The indexes are allowed to contain more +fields than mentioned in the constraint. */ +UNIV_INTERN +int +row_table_add_foreign_constraints( +/*==============================*/ + /* out: error code or DB_SUCCESS */ + trx_t* trx, /* in: transaction */ + const char* sql_string, /* in: table create statement where + foreign keys are declared like: + FOREIGN KEY (a, b) REFERENCES table2(c, d), + table2 can be written also with the + database name before it: test.table2 */ + const char* name, /* in: table full name in the + normalized form + database_name/table_name */ + ibool reject_fks); /* in: if TRUE, fail with error + code DB_CANNOT_ADD_CONSTRAINT if + any foreign keys are found. */ + +/************************************************************************* +The master thread in srv0srv.c calls this regularly to drop tables which +we must drop in background after queries to them have ended. Such lazy +dropping of tables is needed in ALTER TABLE on Unix. */ +UNIV_INTERN +ulint +row_drop_tables_for_mysql_in_background(void); +/*=========================================*/ + /* out: how many tables dropped + + remaining tables in list */ +/************************************************************************* +Get the background drop list length. NOTE: the caller must own the kernel +mutex! */ +UNIV_INTERN +ulint +row_get_background_drop_list_len_low(void); +/*======================================*/ + /* out: how many tables in list */ +/************************************************************************* +Truncates a table for MySQL. */ +UNIV_INTERN +int +row_truncate_table_for_mysql( +/*=========================*/ + /* out: error code or DB_SUCCESS */ + dict_table_t* table, /* in: table handle */ + trx_t* trx); /* in: transaction handle */ +/************************************************************************* +Drops a table for MySQL. If the name of the dropped table ends in +one of "innodb_monitor", "innodb_lock_monitor", "innodb_tablespace_monitor", +"innodb_table_monitor", then this will also stop the printing of monitor +output by the master thread. If the data dictionary was not already locked +by the transaction, the transaction will be committed. Otherwise, the +data dictionary will remain locked. */ +UNIV_INTERN +int +row_drop_table_for_mysql( +/*=====================*/ + /* out: error code or DB_SUCCESS */ + const char* name, /* in: table name */ + trx_t* trx, /* in: transaction handle */ + ibool drop_db);/* in: TRUE=dropping whole database */ + +/************************************************************************* +Discards the tablespace of a table which stored in an .ibd file. Discarding +means that this function deletes the .ibd file and assigns a new table id for +the table. Also the flag table->ibd_file_missing is set TRUE. */ +UNIV_INTERN +int +row_discard_tablespace_for_mysql( +/*=============================*/ + /* out: error code or DB_SUCCESS */ + const char* name, /* in: table name */ + trx_t* trx); /* in: transaction handle */ +/********************************************************************* +Imports a tablespace. The space id in the .ibd file must match the space id +of the table in the data dictionary. */ +UNIV_INTERN +int +row_import_tablespace_for_mysql( +/*============================*/ + /* out: error code or DB_SUCCESS */ + const char* name, /* in: table name */ + trx_t* trx); /* in: transaction handle */ +/************************************************************************* +Drops a database for MySQL. */ +UNIV_INTERN +int +row_drop_database_for_mysql( +/*========================*/ + /* out: error code or DB_SUCCESS */ + const char* name, /* in: database name which ends to '/' */ + trx_t* trx); /* in: transaction handle */ +/************************************************************************* +Renames a table for MySQL. */ +UNIV_INTERN +ulint +row_rename_table_for_mysql( +/*=======================*/ + /* out: error code or DB_SUCCESS */ + const char* old_name, /* in: old table name */ + const char* new_name, /* in: new table name */ + trx_t* trx, /* in: transaction handle */ + ibool commit); /* in: if TRUE then commit trx */ +/************************************************************************* +Checks a table for corruption. */ +UNIV_INTERN +ulint +row_check_table_for_mysql( +/*======================*/ + /* out: DB_ERROR or DB_SUCCESS */ + row_prebuilt_t* prebuilt); /* in: prebuilt struct in MySQL + handle */ +#endif /* !UNIV_HOTBACKUP */ + +/************************************************************************* +Determines if a table is a magic monitor table. */ +UNIV_INTERN +ibool +row_is_magic_monitor_table( +/*=======================*/ + /* out: TRUE if monitor table */ + const char* table_name); /* in: name of the table, in the + form database/table_name */ + +/* A struct describing a place for an individual column in the MySQL +row format which is presented to the table handler in ha_innobase. +This template struct is used to speed up row transformations between +Innobase and MySQL. */ + +typedef struct mysql_row_templ_struct mysql_row_templ_t; +struct mysql_row_templ_struct { + ulint col_no; /* column number of the column */ + ulint rec_field_no; /* field number of the column in an + Innobase record in the current index; + not defined if template_type is + ROW_MYSQL_WHOLE_ROW */ + ulint mysql_col_offset; /* offset of the column in the MySQL + row format */ + ulint mysql_col_len; /* length of the column in the MySQL + row format */ + ulint mysql_null_byte_offset; /* MySQL NULL bit byte offset in a + MySQL record */ + ulint mysql_null_bit_mask; /* bit mask to get the NULL bit, + zero if column cannot be NULL */ + ulint type; /* column type in Innobase mtype + numbers DATA_CHAR... */ + ulint mysql_type; /* MySQL type code; this is always + < 256 */ + ulint mysql_length_bytes; /* if mysql_type + == DATA_MYSQL_TRUE_VARCHAR, this tells + whether we should use 1 or 2 bytes to + store the MySQL true VARCHAR data + length at the start of row in the MySQL + format (NOTE that the MySQL key value + format always uses 2 bytes for the data + len) */ + ulint charset; /* MySQL charset-collation code + of the column, or zero */ + ulint mbminlen; /* minimum length of a char, in bytes, + or zero if not a char type */ + ulint mbmaxlen; /* maximum length of a char, in bytes, + or zero if not a char type */ + ulint is_unsigned; /* if a column type is an integer + type and this field is != 0, then + it is an unsigned integer type */ +}; + +#define MYSQL_FETCH_CACHE_SIZE 8 +/* After fetching this many rows, we start caching them in fetch_cache */ +#define MYSQL_FETCH_CACHE_THRESHOLD 4 + +#define ROW_PREBUILT_ALLOCATED 78540783 +#define ROW_PREBUILT_FREED 26423527 + +/* A struct for (sometimes lazily) prebuilt structures in an Innobase table +handle used within MySQL; these are used to save CPU time. */ + +struct row_prebuilt_struct { + ulint magic_n; /* this magic number is set to + ROW_PREBUILT_ALLOCATED when created, + or ROW_PREBUILT_FREED when the + struct has been freed */ + dict_table_t* table; /* Innobase table handle */ + trx_t* trx; /* current transaction handle */ + ibool sql_stat_start; /* TRUE when we start processing of + an SQL statement: we may have to set + an intention lock on the table, + create a consistent read view etc. */ + ibool mysql_has_locked; /* this is set TRUE when MySQL + calls external_lock on this handle + with a lock flag, and set FALSE when + with the F_UNLOCK flag */ + ibool clust_index_was_generated; + /* if the user did not define a + primary key in MySQL, then Innobase + automatically generated a clustered + index where the ordering column is + the row id: in this case this flag + is set to TRUE */ + dict_index_t* index; /* current index for a search, if + any */ + ulint read_just_key; /* set to 1 when MySQL calls + ha_innobase::extra with the + argument HA_EXTRA_KEYREAD; it is enough + to read just columns defined in + the index (i.e., no read of the + clustered index record necessary) */ + ibool used_in_HANDLER;/* TRUE if we have been using this + handle in a MySQL HANDLER low level + index cursor command: then we must + store the pcur position even in a + unique search from a clustered index, + because HANDLER allows NEXT and PREV + in such a situation */ + ulint template_type; /* ROW_MYSQL_WHOLE_ROW, + ROW_MYSQL_REC_FIELDS, + ROW_MYSQL_DUMMY_TEMPLATE, or + ROW_MYSQL_NO_TEMPLATE */ + ulint n_template; /* number of elements in the + template */ + ulint null_bitmap_len;/* number of bytes in the SQL NULL + bitmap at the start of a row in the + MySQL format */ + ibool need_to_access_clustered; /* if we are fetching + columns through a secondary index + and at least one column is not in + the secondary index, then this is + set to TRUE */ + ibool templ_contains_blob;/* TRUE if the template contains + BLOB column(s) */ + mysql_row_templ_t* mysql_template;/* template used to transform + rows fast between MySQL and Innobase + formats; memory for this template + is not allocated from 'heap' */ + mem_heap_t* heap; /* memory heap from which + these auxiliary structures are + allocated when needed */ + ins_node_t* ins_node; /* Innobase SQL insert node + used to perform inserts + to the table */ + byte* ins_upd_rec_buff;/* buffer for storing data converted + to the Innobase format from the MySQL + format */ + const byte* default_rec; /* the default values of all columns + (a "default row") in MySQL format */ + ulint hint_need_to_fetch_extra_cols; + /* normally this is set to 0; if this + is set to ROW_RETRIEVE_PRIMARY_KEY, + then we should at least retrieve all + columns in the primary key; if this + is set to ROW_RETRIEVE_ALL_COLS, then + we must retrieve all columns in the + key (if read_just_key == 1), or all + columns in the table */ + upd_node_t* upd_node; /* Innobase SQL update node used + to perform updates and deletes */ + que_fork_t* ins_graph; /* Innobase SQL query graph used + in inserts */ + que_fork_t* upd_graph; /* Innobase SQL query graph used + in updates or deletes */ + btr_pcur_t* pcur; /* persistent cursor used in selects + and updates */ + btr_pcur_t* clust_pcur; /* persistent cursor used in + some selects and updates */ + que_fork_t* sel_graph; /* dummy query graph used in + selects */ + dtuple_t* search_tuple; /* prebuilt dtuple used in selects */ + byte row_id[DATA_ROW_ID_LEN]; + /* if the clustered index was + generated, the row id of the + last row fetched is stored + here */ + dtuple_t* clust_ref; /* prebuilt dtuple used in + sel/upd/del */ + ulint select_lock_type;/* LOCK_NONE, LOCK_S, or LOCK_X */ + ulint stored_select_lock_type;/* this field is used to + remember the original select_lock_type + that was decided in ha_innodb.cc, + ::store_lock(), ::external_lock(), + etc. */ + ulint row_read_type; /* ROW_READ_WITH_LOCKS if row locks + should be the obtained for records + under an UPDATE or DELETE cursor. + If innodb_locks_unsafe_for_binlog + is TRUE, this can be set to + ROW_READ_TRY_SEMI_CONSISTENT, so that + if the row under an UPDATE or DELETE + cursor was locked by another + transaction, InnoDB will resort + to reading the last committed value + ('semi-consistent read'). Then, + this field will be set to + ROW_READ_DID_SEMI_CONSISTENT to + indicate that. If the row does not + match the WHERE condition, MySQL will + invoke handler::unlock_row() to + clear the flag back to + ROW_READ_TRY_SEMI_CONSISTENT and + to simply skip the row. If + the row matches, the next call to + row_search_for_mysql() will lock + the row. + This eliminates lock waits in some + cases; note that this breaks + serializability. */ + ulint mysql_prefix_len;/* byte offset of the end of + the last requested column */ + ulint mysql_row_len; /* length in bytes of a row in the + MySQL format */ + ulint n_rows_fetched; /* number of rows fetched after + positioning the current cursor */ + ulint fetch_direction;/* ROW_SEL_NEXT or ROW_SEL_PREV */ + byte* fetch_cache[MYSQL_FETCH_CACHE_SIZE]; + /* a cache for fetched rows if we + fetch many rows from the same cursor: + it saves CPU time to fetch them in a + batch; we reserve mysql_row_len + bytes for each such row; these + pointers point 4 bytes past the + allocated mem buf start, because + there is a 4 byte magic number at the + start and at the end */ + ibool keep_other_fields_on_keyread; /* when using fetch + cache with HA_EXTRA_KEYREAD, don't + overwrite other fields in mysql row + row buffer.*/ + ulint fetch_cache_first;/* position of the first not yet + fetched row in fetch_cache */ + ulint n_fetch_cached; /* number of not yet fetched rows + in fetch_cache */ + mem_heap_t* blob_heap; /* in SELECTS BLOB fields are copied + to this heap */ + mem_heap_t* old_vers_heap; /* memory heap where a previous + version is built in consistent read */ + /*----------------------*/ + ulonglong autoinc_last_value;/* last value of AUTO-INC interval */ + ulonglong autoinc_increment;/* The increment step of the auto + increment column. Value must be + greater than or equal to 1. Required to + calculate the next value */ + ulonglong autoinc_offset; /* The offset passed to + get_auto_increment() by MySQL. Required + to calculate the next value */ + ulint autoinc_error; /* The actual error code encountered + while trying to init or read the + autoinc value from the table. We + store it here so that we can return + it to MySQL */ + /*----------------------*/ + UT_LIST_NODE_T(row_prebuilt_t) prebuilts; + /* list node of table->prebuilts */ + ulint magic_n2; /* this should be the same as + magic_n */ +}; + +#define ROW_PREBUILT_FETCH_MAGIC_N 465765687 + +#define ROW_MYSQL_WHOLE_ROW 0 +#define ROW_MYSQL_REC_FIELDS 1 +#define ROW_MYSQL_NO_TEMPLATE 2 +#define ROW_MYSQL_DUMMY_TEMPLATE 3 /* dummy template used in + row_scan_and_check_index */ + +/* Values for hint_need_to_fetch_extra_cols */ +#define ROW_RETRIEVE_PRIMARY_KEY 1 +#define ROW_RETRIEVE_ALL_COLS 2 + +/* Values for row_read_type */ +#define ROW_READ_WITH_LOCKS 0 +#define ROW_READ_TRY_SEMI_CONSISTENT 1 +#define ROW_READ_DID_SEMI_CONSISTENT 2 + +#ifndef UNIV_NONINL +#include "row0mysql.ic" +#endif + +#endif diff --git a/storage/xtradb/include/row0mysql.ic b/storage/xtradb/include/row0mysql.ic new file mode 100644 index 00000000000..5260ae17924 --- /dev/null +++ b/storage/xtradb/include/row0mysql.ic @@ -0,0 +1,23 @@ +/***************************************************************************** + +Copyright (c) 2001, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/****************************************************** +MySQL interface for Innobase + +Created 1/23/2001 Heikki Tuuri +*******************************************************/ diff --git a/storage/xtradb/include/row0purge.h b/storage/xtradb/include/row0purge.h new file mode 100644 index 00000000000..fbc12f8d389 --- /dev/null +++ b/storage/xtradb/include/row0purge.h @@ -0,0 +1,95 @@ +/***************************************************************************** + +Copyright (c) 1997, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/****************************************************** +Purge obsolete records + +Created 3/14/1997 Heikki Tuuri +*******************************************************/ + +#ifndef row0purge_h +#define row0purge_h + +#include "univ.i" +#include "data0data.h" +#include "btr0types.h" +#include "btr0pcur.h" +#include "dict0types.h" +#include "trx0types.h" +#include "que0types.h" +#include "row0types.h" + +/************************************************************************ +Creates a purge node to a query graph. */ +UNIV_INTERN +purge_node_t* +row_purge_node_create( +/*==================*/ + /* out, own: purge node */ + que_thr_t* parent, /* in: parent node, i.e., a thr node */ + mem_heap_t* heap); /* in: memory heap where created */ +/*************************************************************** +Does the purge operation for a single undo log record. This is a high-level +function used in an SQL execution graph. */ +UNIV_INTERN +que_thr_t* +row_purge_step( +/*===========*/ + /* out: query thread to run next or NULL */ + que_thr_t* thr); /* in: query thread */ + +/* Purge node structure */ + +struct purge_node_struct{ + que_common_t common; /* node type: QUE_NODE_PURGE */ + /*----------------------*/ + /* Local storage for this graph node */ + dulint roll_ptr;/* roll pointer to undo log record */ + trx_undo_rec_t* undo_rec;/* undo log record */ + trx_undo_inf_t* reservation;/* reservation for the undo log record in + the purge array */ + dulint undo_no;/* undo number of the record */ + ulint rec_type;/* undo log record type: TRX_UNDO_INSERT_REC, + ... */ + btr_pcur_t pcur; /* persistent cursor used in searching the + clustered index record */ + ibool found_clust;/* TRUE if the clustered index record + determined by ref was found in the clustered + index, and we were able to position pcur on + it */ + dict_table_t* table; /* table where purge is done */ + ulint cmpl_info;/* compiler analysis info of an update */ + upd_t* update; /* update vector for a clustered index + record */ + dtuple_t* ref; /* NULL, or row reference to the next row to + handle */ + dtuple_t* row; /* NULL, or a copy (also fields copied to + heap) of the indexed fields of the row to + handle */ + dict_index_t* index; /* NULL, or the next index whose record should + be handled */ + mem_heap_t* heap; /* memory heap used as auxiliary storage for + row; this must be emptied after a successful + purge of a row */ +}; + +#ifndef UNIV_NONINL +#include "row0purge.ic" +#endif + +#endif diff --git a/storage/xtradb/include/row0purge.ic b/storage/xtradb/include/row0purge.ic new file mode 100644 index 00000000000..5fc665e9d20 --- /dev/null +++ b/storage/xtradb/include/row0purge.ic @@ -0,0 +1,24 @@ +/***************************************************************************** + +Copyright (c) 1997, 2009, Innobase Oy. 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 + +*****************************************************************************/ + + +/****************************************************** +Purge obsolete records + +Created 3/14/1997 Heikki Tuuri +*******************************************************/ diff --git a/storage/xtradb/include/row0row.h b/storage/xtradb/include/row0row.h new file mode 100644 index 00000000000..26c4b5e4e71 --- /dev/null +++ b/storage/xtradb/include/row0row.h @@ -0,0 +1,331 @@ +/***************************************************************************** + +Copyright (c) 1996, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/****************************************************** +General row routines + +Created 4/20/1996 Heikki Tuuri +*******************************************************/ + +#ifndef row0row_h +#define row0row_h + +#include "univ.i" +#include "data0data.h" +#include "dict0types.h" +#include "trx0types.h" +#include "que0types.h" +#include "mtr0mtr.h" +#include "rem0types.h" +#include "read0types.h" +#include "row0types.h" +#include "btr0types.h" + +/************************************************************************* +Gets the offset of the trx id field, in bytes relative to the origin of +a clustered index record. */ +UNIV_INTERN +ulint +row_get_trx_id_offset( +/*==================*/ + /* out: offset of DATA_TRX_ID */ + const rec_t* rec, /* in: record */ + dict_index_t* index, /* in: clustered index */ + const ulint* offsets);/* in: rec_get_offsets(rec, index) */ +/************************************************************************* +Reads the trx id field from a clustered index record. */ +UNIV_INLINE +dulint +row_get_rec_trx_id( +/*===============*/ + /* out: value of the field */ + const rec_t* rec, /* in: record */ + dict_index_t* index, /* in: clustered index */ + const ulint* offsets);/* in: rec_get_offsets(rec, index) */ +/************************************************************************* +Reads the roll pointer field from a clustered index record. */ +UNIV_INLINE +dulint +row_get_rec_roll_ptr( +/*=================*/ + /* out: value of the field */ + const rec_t* rec, /* in: record */ + dict_index_t* index, /* in: clustered index */ + const ulint* offsets);/* in: rec_get_offsets(rec, index) */ +/********************************************************************* +When an insert or purge to a table is performed, this function builds +the entry to be inserted into or purged from an index on the table. */ +UNIV_INTERN +dtuple_t* +row_build_index_entry( +/*==================*/ + /* out: index entry which should be + inserted or purged, or NULL if the + externally stored columns in the + clustered index record are unavailable + and ext != NULL */ + const dtuple_t* row, /* in: row which should be + inserted or purged */ + row_ext_t* ext, /* in: externally stored column prefixes, + or NULL */ + dict_index_t* index, /* in: index on the table */ + mem_heap_t* heap); /* in: memory heap from which the memory for + the index entry is allocated */ +/*********************************************************************** +An inverse function to row_build_index_entry. Builds a row from a +record in a clustered index. */ +UNIV_INTERN +dtuple_t* +row_build( +/*======*/ + /* out, own: row built; + see the NOTE below! */ + ulint type, /* in: ROW_COPY_POINTERS or + ROW_COPY_DATA; the latter + copies also the data fields to + heap while the first only + places pointers to data fields + on the index page, and thus is + more efficient */ + const dict_index_t* index, /* in: clustered index */ + const rec_t* rec, /* in: record in the clustered + index; NOTE: in the case + ROW_COPY_POINTERS the data + fields in the row will point + directly into this record, + therefore, the buffer page of + this record must be at least + s-latched and the latch held + as long as the row dtuple is used! */ + const ulint* offsets,/* in: rec_get_offsets(rec,index) + or NULL, in which case this function + will invoke rec_get_offsets() */ + const dict_table_t* col_table, + /* in: table, to check which + externally stored columns + occur in the ordering columns + of an index, or NULL if + index->table should be + consulted instead; the user + columns in this table should be + the same columns as in index->table */ + row_ext_t** ext, /* out, own: cache of + externally stored column + prefixes, or NULL */ + mem_heap_t* heap); /* in: memory heap from which + the memory needed is allocated */ +/*********************************************************************** +Converts an index record to a typed data tuple. */ +UNIV_INTERN +dtuple_t* +row_rec_to_index_entry_low( +/*=======================*/ + /* out: index entry built; does not + set info_bits, and the data fields in + the entry will point directly to rec */ + const rec_t* rec, /* in: record in the index */ + const dict_index_t* index, /* in: index */ + const ulint* offsets,/* in: rec_get_offsets(rec, index) */ + ulint* n_ext, /* out: number of externally + stored columns */ + mem_heap_t* heap); /* in: memory heap from which + the memory needed is allocated */ +/*********************************************************************** +Converts an index record to a typed data tuple. NOTE that externally +stored (often big) fields are NOT copied to heap. */ +UNIV_INTERN +dtuple_t* +row_rec_to_index_entry( +/*===================*/ + /* out, own: index entry + built; see the NOTE below! */ + ulint type, /* in: ROW_COPY_DATA, or + ROW_COPY_POINTERS: the former + copies also the data fields to + heap as the latter only places + pointers to data fields on the + index page */ + const rec_t* rec, /* in: record in the index; + NOTE: in the case + ROW_COPY_POINTERS the data + fields in the row will point + directly into this record, + therefore, the buffer page of + this record must be at least + s-latched and the latch held + as long as the dtuple is used! */ + const dict_index_t* index, /* in: index */ + ulint* offsets,/* in/out: rec_get_offsets(rec) */ + ulint* n_ext, /* out: number of externally + stored columns */ + mem_heap_t* heap); /* in: memory heap from which + the memory needed is allocated */ +/*********************************************************************** +Builds from a secondary index record a row reference with which we can +search the clustered index record. */ +UNIV_INTERN +dtuple_t* +row_build_row_ref( +/*==============*/ + /* out, own: row reference built; see the + NOTE below! */ + ulint type, /* in: ROW_COPY_DATA, or ROW_COPY_POINTERS: + the former copies also the data fields to + heap, whereas the latter only places pointers + to data fields on the index page */ + dict_index_t* index, /* in: secondary index */ + const rec_t* rec, /* in: record in the index; + NOTE: in the case ROW_COPY_POINTERS + the data fields in the row will point + directly into this record, therefore, + the buffer page of this record must be + at least s-latched and the latch held + as long as the row reference is used! */ + mem_heap_t* heap); /* in: memory heap from which the memory + needed is allocated */ +/*********************************************************************** +Builds from a secondary index record a row reference with which we can +search the clustered index record. */ +UNIV_INTERN +void +row_build_row_ref_in_tuple( +/*=======================*/ + dtuple_t* ref, /* in/out: row reference built; + see the NOTE below! */ + const rec_t* rec, /* in: record in the index; + NOTE: the data fields in ref + will point directly into this + record, therefore, the buffer + page of this record must be at + least s-latched and the latch + held as long as the row + reference is used! */ + const dict_index_t* index, /* in: secondary index */ + ulint* offsets,/* in: rec_get_offsets(rec, index) + or NULL */ + trx_t* trx); /* in: transaction */ +/*********************************************************************** +From a row build a row reference with which we can search the clustered +index record. */ +UNIV_INTERN +void +row_build_row_ref_from_row( +/*=======================*/ + dtuple_t* ref, /* in/out: row reference built; + see the NOTE below! + ref must have the right number + of fields! */ + const dict_table_t* table, /* in: table */ + const dtuple_t* row); /* in: row + NOTE: the data fields in ref will point + directly into data of this row */ +/*********************************************************************** +Builds from a secondary index record a row reference with which we can +search the clustered index record. */ +UNIV_INLINE +void +row_build_row_ref_fast( +/*===================*/ + dtuple_t* ref, /* in/out: typed data tuple where the + reference is built */ + const ulint* map, /* in: array of field numbers in rec + telling how ref should be built from + the fields of rec */ + const rec_t* rec, /* in: record in the index; must be + preserved while ref is used, as we do + not copy field values to heap */ + const ulint* offsets);/* in: array returned by rec_get_offsets() */ +/******************************************************************* +Searches the clustered index record for a row, if we have the row +reference. */ +UNIV_INTERN +ibool +row_search_on_row_ref( +/*==================*/ + /* out: TRUE if found */ + btr_pcur_t* pcur, /* out: persistent cursor, which must + be closed by the caller */ + ulint mode, /* in: BTR_MODIFY_LEAF, ... */ + const dict_table_t* table, /* in: table */ + const dtuple_t* ref, /* in: row reference */ + mtr_t* mtr); /* in/out: mtr */ +/************************************************************************* +Fetches the clustered index record for a secondary index record. The latches +on the secondary index record are preserved. */ +UNIV_INTERN +rec_t* +row_get_clust_rec( +/*==============*/ + /* out: record or NULL, if no record found */ + ulint mode, /* in: BTR_MODIFY_LEAF, ... */ + const rec_t* rec, /* in: record in a secondary index */ + dict_index_t* index, /* in: secondary index */ + dict_index_t** clust_index,/* out: clustered index */ + mtr_t* mtr); /* in: mtr */ +/******************************************************************* +Searches an index record. */ +UNIV_INTERN +ibool +row_search_index_entry( +/*===================*/ + /* out: TRUE if found */ + dict_index_t* index, /* in: index */ + const dtuple_t* entry, /* in: index entry */ + ulint mode, /* in: BTR_MODIFY_LEAF, ... */ + btr_pcur_t* pcur, /* in/out: persistent cursor, which must + be closed by the caller */ + mtr_t* mtr); /* in: mtr */ + + +#define ROW_COPY_DATA 1 +#define ROW_COPY_POINTERS 2 + +/* The allowed latching order of index records is the following: +(1) a secondary index record -> +(2) the clustered index record -> +(3) rollback segment data for the clustered index record. + +No new latches may be obtained while the kernel mutex is reserved. +However, the kernel mutex can be reserved while latches are owned. */ + +/*********************************************************************** +Formats the raw data in "data" (in InnoDB on-disk format) using +"dict_field" and writes the result to "buf". +Not more than "buf_size" bytes are written to "buf". +The result is always '\0'-terminated (provided buf_size > 0) and the +number of bytes that were written to "buf" is returned (including the +terminating '\0'). */ +UNIV_INTERN +ulint +row_raw_format( +/*===========*/ + /* out: number of bytes + that were written */ + const char* data, /* in: raw data */ + ulint data_len, /* in: raw data length + in bytes */ + const dict_field_t* dict_field, /* in: index field */ + char* buf, /* out: output buffer */ + ulint buf_size); /* in: output buffer size + in bytes */ + +#ifndef UNIV_NONINL +#include "row0row.ic" +#endif + +#endif diff --git a/storage/xtradb/include/row0row.ic b/storage/xtradb/include/row0row.ic new file mode 100644 index 00000000000..9947dd43257 --- /dev/null +++ b/storage/xtradb/include/row0row.ic @@ -0,0 +1,119 @@ +/***************************************************************************** + +Copyright (c) 1996, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/****************************************************** +General row routines + +Created 4/20/1996 Heikki Tuuri +*******************************************************/ + +#include "dict0dict.h" +#include "rem0rec.h" +#include "trx0undo.h" + +/************************************************************************* +Reads the trx id field from a clustered index record. */ +UNIV_INLINE +dulint +row_get_rec_trx_id( +/*===============*/ + /* out: value of the field */ + const rec_t* rec, /* in: record */ + dict_index_t* index, /* in: clustered index */ + const ulint* offsets)/* in: rec_get_offsets(rec, index) */ +{ + ulint offset; + + ut_ad(dict_index_is_clust(index)); + ut_ad(rec_offs_validate(rec, index, offsets)); + + offset = index->trx_id_offset; + + if (!offset) { + offset = row_get_trx_id_offset(rec, index, offsets); + } + + return(trx_read_trx_id(rec + offset)); +} + +/************************************************************************* +Reads the roll pointer field from a clustered index record. */ +UNIV_INLINE +dulint +row_get_rec_roll_ptr( +/*=================*/ + /* out: value of the field */ + const rec_t* rec, /* in: record */ + dict_index_t* index, /* in: clustered index */ + const ulint* offsets)/* in: rec_get_offsets(rec, index) */ +{ + ulint offset; + + ut_ad(dict_index_is_clust(index)); + ut_ad(rec_offs_validate(rec, index, offsets)); + + offset = index->trx_id_offset; + + if (!offset) { + offset = row_get_trx_id_offset(rec, index, offsets); + } + + return(trx_read_roll_ptr(rec + offset + DATA_TRX_ID_LEN)); +} + +/*********************************************************************** +Builds from a secondary index record a row reference with which we can +search the clustered index record. */ +UNIV_INLINE +void +row_build_row_ref_fast( +/*===================*/ + dtuple_t* ref, /* in/out: typed data tuple where the + reference is built */ + const ulint* map, /* in: array of field numbers in rec + telling how ref should be built from + the fields of rec */ + const rec_t* rec, /* in: record in the index; must be + preserved while ref is used, as we do + not copy field values to heap */ + const ulint* offsets)/* in: array returned by rec_get_offsets() */ +{ + dfield_t* dfield; + const byte* field; + ulint len; + ulint ref_len; + ulint field_no; + ulint i; + + ut_ad(rec_offs_validate(rec, NULL, offsets)); + ut_ad(!rec_offs_any_extern(offsets)); + ref_len = dtuple_get_n_fields(ref); + + for (i = 0; i < ref_len; i++) { + dfield = dtuple_get_nth_field(ref, i); + + field_no = *(map + i); + + if (field_no != ULINT_UNDEFINED) { + + field = rec_get_nth_field(rec, offsets, + field_no, &len); + dfield_set_data(dfield, field, len); + } + } +} diff --git a/storage/xtradb/include/row0sel.h b/storage/xtradb/include/row0sel.h new file mode 100644 index 00000000000..2f8574d0691 --- /dev/null +++ b/storage/xtradb/include/row0sel.h @@ -0,0 +1,401 @@ +/***************************************************************************** + +Copyright (c) 1997, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/****************************************************** +Select + +Created 12/19/1997 Heikki Tuuri +*******************************************************/ + +#ifndef row0sel_h +#define row0sel_h + +#include "univ.i" +#include "data0data.h" +#include "que0types.h" +#include "dict0types.h" +#include "trx0types.h" +#include "row0types.h" +#include "que0types.h" +#include "pars0sym.h" +#include "btr0pcur.h" +#include "read0read.h" +#include "row0mysql.h" + +/************************************************************************* +Creates a select node struct. */ +UNIV_INTERN +sel_node_t* +sel_node_create( +/*============*/ + /* out, own: select node struct */ + mem_heap_t* heap); /* in: memory heap where created */ +/************************************************************************* +Frees the memory private to a select node when a query graph is freed, +does not free the heap where the node was originally created. */ +UNIV_INTERN +void +sel_node_free_private( +/*==================*/ + sel_node_t* node); /* in: select node struct */ +/************************************************************************* +Frees a prefetch buffer for a column, including the dynamically allocated +memory for data stored there. */ +UNIV_INTERN +void +sel_col_prefetch_buf_free( +/*======================*/ + sel_buf_t* prefetch_buf); /* in, own: prefetch buffer */ +/************************************************************************* +Gets the plan node for the nth table in a join. */ +UNIV_INLINE +plan_t* +sel_node_get_nth_plan( +/*==================*/ + sel_node_t* node, + ulint i); +/************************************************************************** +Performs a select step. This is a high-level function used in SQL execution +graphs. */ +UNIV_INTERN +que_thr_t* +row_sel_step( +/*=========*/ + /* out: query thread to run next or NULL */ + que_thr_t* thr); /* in: query thread */ +/************************************************************************** +Performs an execution step of an open or close cursor statement node. */ +UNIV_INLINE +que_thr_t* +open_step( +/*======*/ + /* out: query thread to run next or NULL */ + que_thr_t* thr); /* in: query thread */ +/************************************************************************** +Performs a fetch for a cursor. */ +UNIV_INTERN +que_thr_t* +fetch_step( +/*=======*/ + /* out: query thread to run next or NULL */ + que_thr_t* thr); /* in: query thread */ +/******************************************************************** +Sample callback function for fetch that prints each row.*/ +UNIV_INTERN +void* +row_fetch_print( +/*============*/ + /* out: always returns non-NULL */ + void* row, /* in: sel_node_t* */ + void* user_arg); /* in: not used */ +/******************************************************************** +Callback function for fetch that stores an unsigned 4 byte integer to the +location pointed. The column's type must be DATA_INT, DATA_UNSIGNED, length += 4. */ +UNIV_INTERN +void* +row_fetch_store_uint4( +/*==================*/ + /* out: always returns NULL */ + void* row, /* in: sel_node_t* */ + void* user_arg); /* in: data pointer */ +/*************************************************************** +Prints a row in a select result. */ +UNIV_INTERN +que_thr_t* +row_printf_step( +/*============*/ + /* out: query thread to run next or NULL */ + que_thr_t* thr); /* in: query thread */ +/******************************************************************** +Converts a key value stored in MySQL format to an Innobase dtuple. The last +field of the key value may be just a prefix of a fixed length field: hence +the parameter key_len. But currently we do not allow search keys where the +last field is only a prefix of the full key field len and print a warning if +such appears. */ +UNIV_INTERN +void +row_sel_convert_mysql_key_to_innobase( +/*==================================*/ + dtuple_t* tuple, /* in/out: tuple where to build; + NOTE: we assume that the type info + in the tuple is already according + to index! */ + byte* buf, /* in: buffer to use in field + conversions */ + ulint buf_len, /* in: buffer length */ + dict_index_t* index, /* in: index of the key value */ + const byte* key_ptr, /* in: MySQL key value */ + ulint key_len, /* in: MySQL key value length */ + trx_t* trx); /* in: transaction */ +/************************************************************************ +Searches for rows in the database. This is used in the interface to +MySQL. This function opens a cursor, and also implements fetch next +and fetch prev. NOTE that if we do a search with a full key value +from a unique index (ROW_SEL_EXACT), then we will not store the cursor +position and fetch next or fetch prev must not be tried to the cursor! */ +UNIV_INTERN +ulint +row_search_for_mysql( +/*=================*/ + /* out: DB_SUCCESS, + DB_RECORD_NOT_FOUND, + DB_END_OF_INDEX, DB_DEADLOCK, + DB_LOCK_TABLE_FULL, + or DB_TOO_BIG_RECORD */ + byte* buf, /* in/out: buffer for the fetched + row in the MySQL format */ + ulint mode, /* in: search mode PAGE_CUR_L, ... */ + row_prebuilt_t* prebuilt, /* in: prebuilt struct for the + table handle; this contains the info + of search_tuple, index; if search + tuple contains 0 fields then we + position the cursor at the start or + the end of the index, depending on + 'mode' */ + ulint match_mode, /* in: 0 or ROW_SEL_EXACT or + ROW_SEL_EXACT_PREFIX */ + ulint direction); /* in: 0 or ROW_SEL_NEXT or + ROW_SEL_PREV; NOTE: if this is != 0, + then prebuilt must have a pcur + with stored position! In opening of a + cursor 'direction' should be 0. */ +/*********************************************************************** +Checks if MySQL at the moment is allowed for this table to retrieve a +consistent read result, or store it to the query cache. */ +UNIV_INTERN +ibool +row_search_check_if_query_cache_permitted( +/*======================================*/ + /* out: TRUE if storing or retrieving + from the query cache is permitted */ + trx_t* trx, /* in: transaction object */ + const char* norm_name); /* in: concatenation of database name, + '/' char, table name */ +/*********************************************************************** +Read the max AUTOINC value from an index. */ +UNIV_INTERN +ulint +row_search_max_autoinc( +/*===================*/ + /* out: DB_SUCCESS if all OK else + error code */ + dict_index_t* index, /* in: index to search */ + const char* col_name, /* in: autoinc column name */ + ib_uint64_t* value); /* out: AUTOINC value read */ + +/* A structure for caching column values for prefetched rows */ +struct sel_buf_struct{ + byte* data; /* data, or NULL; if not NULL, this field + has allocated memory which must be explicitly + freed; can be != NULL even when len is + UNIV_SQL_NULL */ + ulint len; /* data length or UNIV_SQL_NULL */ + ulint val_buf_size; + /* size of memory buffer allocated for data: + this can be more than len; this is defined + when data != NULL */ +}; + +struct plan_struct{ + dict_table_t* table; /* table struct in the dictionary + cache */ + dict_index_t* index; /* table index used in the search */ + btr_pcur_t pcur; /* persistent cursor used to search + the index */ + ibool asc; /* TRUE if cursor traveling upwards */ + ibool pcur_is_open; /* TRUE if pcur has been positioned + and we can try to fetch new rows */ + ibool cursor_at_end; /* TRUE if the cursor is open but + we know that there are no more + qualifying rows left to retrieve from + the index tree; NOTE though, that + there may still be unprocessed rows in + the prefetch stack; always FALSE when + pcur_is_open is FALSE */ + ibool stored_cursor_rec_processed; + /* TRUE if the pcur position has been + stored and the record it is positioned + on has already been processed */ + que_node_t** tuple_exps; /* array of expressions which are used + to calculate the field values in the + search tuple: there is one expression + for each field in the search tuple */ + dtuple_t* tuple; /* search tuple */ + ulint mode; /* search mode: PAGE_CUR_G, ... */ + ulint n_exact_match; /* number of first fields in the search + tuple which must be exactly matched */ + ibool unique_search; /* TRUE if we are searching an + index record with a unique key */ + ulint n_rows_fetched; /* number of rows fetched using pcur + after it was opened */ + ulint n_rows_prefetched;/* number of prefetched rows cached + for fetch: fetching several rows in + the same mtr saves CPU time */ + ulint first_prefetched;/* index of the first cached row in + select buffer arrays for each column */ + ibool no_prefetch; /* no prefetch for this table */ + sym_node_list_t columns; /* symbol table nodes for the columns + to retrieve from the table */ + UT_LIST_BASE_NODE_T(func_node_t) + end_conds; /* conditions which determine the + fetch limit of the index segment we + have to look at: when one of these + fails, the result set has been + exhausted for the cursor in this + index; these conditions are normalized + so that in a comparison the column + for this table is the first argument */ + UT_LIST_BASE_NODE_T(func_node_t) + other_conds; /* the rest of search conditions we can + test at this table in a join */ + ibool must_get_clust; /* TRUE if index is a non-clustered + index and we must also fetch the + clustered index record; this is the + case if the non-clustered record does + not contain all the needed columns, or + if this is a single-table explicit + cursor, or a searched update or + delete */ + ulint* clust_map; /* map telling how clust_ref is built + from the fields of a non-clustered + record */ + dtuple_t* clust_ref; /* the reference to the clustered + index entry is built here if index is + a non-clustered index */ + btr_pcur_t clust_pcur; /* if index is non-clustered, we use + this pcur to search the clustered + index */ + mem_heap_t* old_vers_heap; /* memory heap used in building an old + version of a row, or NULL */ +}; + +struct sel_node_struct{ + que_common_t common; /* node type: QUE_NODE_SELECT */ + ulint state; /* node state */ + que_node_t* select_list; /* select list */ + sym_node_t* into_list; /* variables list or NULL */ + sym_node_t* table_list; /* table list */ + ibool asc; /* TRUE if the rows should be fetched + in an ascending order */ + ibool set_x_locks; /* TRUE if the cursor is for update or + delete, which means that a row x-lock + should be placed on the cursor row */ + ulint row_lock_mode; /* LOCK_X or LOCK_S */ + ulint n_tables; /* number of tables */ + ulint fetch_table; /* number of the next table to access + in the join */ + plan_t* plans; /* array of n_tables many plan nodes + containing the search plan and the + search data structures */ + que_node_t* search_cond; /* search condition */ + read_view_t* read_view; /* if the query is a non-locking + consistent read, its read view is + placed here, otherwise NULL */ + ibool consistent_read;/* TRUE if the select is a consistent, + non-locking read */ + order_node_t* order_by; /* order by column definition, or + NULL */ + ibool is_aggregate; /* TRUE if the select list consists of + aggregate functions */ + ibool aggregate_already_fetched; + /* TRUE if the aggregate row has + already been fetched for the current + cursor */ + ibool can_get_updated;/* this is TRUE if the select + is in a single-table explicit + cursor which can get updated + within the stored procedure, + or in a searched update or + delete; NOTE that to determine + of an explicit cursor if it + can get updated, the parser + checks from a stored procedure + if it contains positioned + update or delete statements */ + sym_node_t* explicit_cursor;/* not NULL if an explicit cursor */ + UT_LIST_BASE_NODE_T(sym_node_t) + copy_variables; /* variables whose values we have to + copy when an explicit cursor is opened, + so that they do not change between + fetches */ +}; + +/* Select node states */ +#define SEL_NODE_CLOSED 0 /* it is a declared cursor which is not + currently open */ +#define SEL_NODE_OPEN 1 /* intention locks not yet set on + tables */ +#define SEL_NODE_FETCH 2 /* intention locks have been set */ +#define SEL_NODE_NO_MORE_ROWS 3 /* cursor has reached the result set + end */ + +/* Fetch statement node */ +struct fetch_node_struct{ + que_common_t common; /* type: QUE_NODE_FETCH */ + sel_node_t* cursor_def; /* cursor definition */ + sym_node_t* into_list; /* variables to set */ + + pars_user_func_t* + func; /* User callback function or NULL. + The first argument to the function + is a sel_node_t*, containing the + results of the SELECT operation for + one row. If the function returns + NULL, it is not interested in + further rows and the cursor is + modified so (cursor % NOTFOUND) is + true. If it returns not-NULL, + continue normally. See + row_fetch_print() for an example + (and a useful debugging tool). */ +}; + +/* Open or close cursor statement node */ +struct open_node_struct{ + que_common_t common; /* type: QUE_NODE_OPEN */ + ulint op_type; /* ROW_SEL_OPEN_CURSOR or + ROW_SEL_CLOSE_CURSOR */ + sel_node_t* cursor_def; /* cursor definition */ +}; + +/* Row printf statement node */ +struct row_printf_node_struct{ + que_common_t common; /* type: QUE_NODE_ROW_PRINTF */ + sel_node_t* sel_node; /* select */ +}; + +#define ROW_SEL_OPEN_CURSOR 0 +#define ROW_SEL_CLOSE_CURSOR 1 + +/* Flags for the MySQL interface */ +#define ROW_SEL_NEXT 1 +#define ROW_SEL_PREV 2 + +#define ROW_SEL_EXACT 1 /* search using a complete key value */ +#define ROW_SEL_EXACT_PREFIX 2 /* search using a key prefix which + must match to rows: the prefix may + contain an incomplete field (the + last field in prefix may be just + a prefix of a fixed length column) */ + +#ifndef UNIV_NONINL +#include "row0sel.ic" +#endif + +#endif diff --git a/storage/xtradb/include/row0sel.ic b/storage/xtradb/include/row0sel.ic new file mode 100644 index 00000000000..a21181e3237 --- /dev/null +++ b/storage/xtradb/include/row0sel.ic @@ -0,0 +1,104 @@ +/***************************************************************************** + +Copyright (c) 1997, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/****************************************************** +Select + +Created 12/19/1997 Heikki Tuuri +*******************************************************/ + +#include "que0que.h" + +/************************************************************************* +Gets the plan node for the nth table in a join. */ +UNIV_INLINE +plan_t* +sel_node_get_nth_plan( +/*==================*/ + /* out: plan node */ + sel_node_t* node, /* in: select node */ + ulint i) /* in: get ith plan node */ +{ + ut_ad(i < node->n_tables); + + return(node->plans + i); +} + +/************************************************************************* +Resets the cursor defined by sel_node to the SEL_NODE_OPEN state, which means +that it will start fetching from the start of the result set again, regardless +of where it was before, and it will set intention locks on the tables. */ +UNIV_INLINE +void +sel_node_reset_cursor( +/*==================*/ + sel_node_t* node) /* in: select node */ +{ + node->state = SEL_NODE_OPEN; +} + +/************************************************************************** +Performs an execution step of an open or close cursor statement node. */ +UNIV_INLINE +que_thr_t* +open_step( +/*======*/ + /* out: query thread to run next or NULL */ + que_thr_t* thr) /* in: query thread */ +{ + sel_node_t* sel_node; + open_node_t* node; + ulint err; + + ut_ad(thr); + + node = (open_node_t*) thr->run_node; + ut_ad(que_node_get_type(node) == QUE_NODE_OPEN); + + sel_node = node->cursor_def; + + err = DB_SUCCESS; + + if (node->op_type == ROW_SEL_OPEN_CURSOR) { + + /* if (sel_node->state == SEL_NODE_CLOSED) { */ + + sel_node_reset_cursor(sel_node); + /* } else { + err = DB_ERROR; + } */ + } else { + if (sel_node->state != SEL_NODE_CLOSED) { + + sel_node->state = SEL_NODE_CLOSED; + } else { + err = DB_ERROR; + } + } + + if (UNIV_EXPECT(err, DB_SUCCESS) != DB_SUCCESS) { + /* SQL error detected */ + fprintf(stderr, "SQL error %lu\n", (ulong) err); + + ut_error; + } + + thr->run_node = que_node_get_parent(node); + + return(thr); +} diff --git a/storage/xtradb/include/row0types.h b/storage/xtradb/include/row0types.h new file mode 100644 index 00000000000..f0af7c2bf53 --- /dev/null +++ b/storage/xtradb/include/row0types.h @@ -0,0 +1,58 @@ +/***************************************************************************** + +Copyright (c) 1996, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/****************************************************** +Row operation global types + +Created 12/27/1996 Heikki Tuuri +*******************************************************/ + +#ifndef row0types_h +#define row0types_h + +typedef struct plan_struct plan_t; + +typedef struct upd_struct upd_t; + +typedef struct upd_field_struct upd_field_t; + +typedef struct upd_node_struct upd_node_t; + +typedef struct del_node_struct del_node_t; + +typedef struct ins_node_struct ins_node_t; + +typedef struct sel_node_struct sel_node_t; + +typedef struct open_node_struct open_node_t; + +typedef struct fetch_node_struct fetch_node_t; + +typedef struct row_printf_node_struct row_printf_node_t; +typedef struct sel_buf_struct sel_buf_t; + +typedef struct undo_node_struct undo_node_t; + +typedef struct purge_node_struct purge_node_t; + +typedef struct row_ext_struct row_ext_t; + +/* MySQL data types */ +typedef struct st_table TABLE; + +#endif diff --git a/storage/xtradb/include/row0uins.h b/storage/xtradb/include/row0uins.h new file mode 100644 index 00000000000..16bbbbd0d12 --- /dev/null +++ b/storage/xtradb/include/row0uins.h @@ -0,0 +1,53 @@ +/***************************************************************************** + +Copyright (c) 1997, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/****************************************************** +Fresh insert undo + +Created 2/25/1997 Heikki Tuuri +*******************************************************/ + +#ifndef row0uins_h +#define row0uins_h + +#include "univ.i" +#include "data0data.h" +#include "dict0types.h" +#include "trx0types.h" +#include "que0types.h" +#include "row0types.h" +#include "mtr0mtr.h" + +/*************************************************************** +Undoes a fresh insert of a row to a table. A fresh insert means that +the same clustered index unique key did not have any record, even delete +marked, at the time of the insert. InnoDB is eager in a rollback: +if it figures out that an index record will be removed in the purge +anyway, it will remove it in the rollback. */ +UNIV_INTERN +ulint +row_undo_ins( +/*=========*/ + /* out: DB_SUCCESS */ + undo_node_t* node); /* in: row undo node */ + +#ifndef UNIV_NONINL +#include "row0uins.ic" +#endif + +#endif diff --git a/storage/xtradb/include/row0uins.ic b/storage/xtradb/include/row0uins.ic new file mode 100644 index 00000000000..75bef8431eb --- /dev/null +++ b/storage/xtradb/include/row0uins.ic @@ -0,0 +1,24 @@ +/***************************************************************************** + +Copyright (c) 1997, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/****************************************************** +Fresh insert undo + +Created 2/25/1997 Heikki Tuuri +*******************************************************/ + diff --git a/storage/xtradb/include/row0umod.h b/storage/xtradb/include/row0umod.h new file mode 100644 index 00000000000..3a4e8c2f9a3 --- /dev/null +++ b/storage/xtradb/include/row0umod.h @@ -0,0 +1,51 @@ +/***************************************************************************** + +Copyright (c) 1997, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/****************************************************** +Undo modify of a row + +Created 2/27/1997 Heikki Tuuri +*******************************************************/ + +#ifndef row0umod_h +#define row0umod_h + +#include "univ.i" +#include "data0data.h" +#include "dict0types.h" +#include "trx0types.h" +#include "que0types.h" +#include "row0types.h" +#include "mtr0mtr.h" + +/*************************************************************** +Undoes a modify operation on a row of a table. */ +UNIV_INTERN +ulint +row_undo_mod( +/*=========*/ + /* out: DB_SUCCESS or error code */ + undo_node_t* node, /* in: row undo node */ + que_thr_t* thr); /* in: query thread */ + + +#ifndef UNIV_NONINL +#include "row0umod.ic" +#endif + +#endif diff --git a/storage/xtradb/include/row0umod.ic b/storage/xtradb/include/row0umod.ic new file mode 100644 index 00000000000..7ac7bc2fea7 --- /dev/null +++ b/storage/xtradb/include/row0umod.ic @@ -0,0 +1,23 @@ +/***************************************************************************** + +Copyright (c) 1997, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/****************************************************** +Undo modify of a row + +Created 2/27/1997 Heikki Tuuri +*******************************************************/ diff --git a/storage/xtradb/include/row0undo.h b/storage/xtradb/include/row0undo.h new file mode 100644 index 00000000000..a17cfb1babd --- /dev/null +++ b/storage/xtradb/include/row0undo.h @@ -0,0 +1,136 @@ +/***************************************************************************** + +Copyright (c) 1997, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/****************************************************** +Row undo + +Created 1/8/1997 Heikki Tuuri +*******************************************************/ + +#ifndef row0undo_h +#define row0undo_h + +#include "univ.i" +#include "mtr0mtr.h" +#include "trx0sys.h" +#include "btr0types.h" +#include "btr0pcur.h" +#include "dict0types.h" +#include "trx0types.h" +#include "que0types.h" +#include "row0types.h" + +/************************************************************************ +Creates a row undo node to a query graph. */ +UNIV_INTERN +undo_node_t* +row_undo_node_create( +/*=================*/ + /* out, own: undo node */ + trx_t* trx, /* in: transaction */ + que_thr_t* parent, /* in: parent node, i.e., a thr node */ + mem_heap_t* heap); /* in: memory heap where created */ +/*************************************************************** +Looks for the clustered index record when node has the row reference. +The pcur in node is used in the search. If found, stores the row to node, +and stores the position of pcur, and detaches it. The pcur must be closed +by the caller in any case. */ +UNIV_INTERN +ibool +row_undo_search_clust_to_pcur( +/*==========================*/ + /* out: TRUE if found; NOTE the node->pcur + must be closed by the caller, regardless of + the return value */ + undo_node_t* node); /* in: row undo node */ +/*************************************************************** +Undoes a row operation in a table. This is a high-level function used +in SQL execution graphs. */ +UNIV_INTERN +que_thr_t* +row_undo_step( +/*==========*/ + /* out: query thread to run next or NULL */ + que_thr_t* thr); /* in: query thread */ + +/* A single query thread will try to perform the undo for all successive +versions of a clustered index record, if the transaction has modified it +several times during the execution which is rolled back. It may happen +that the task is transferred to another query thread, if the other thread +is assigned to handle an undo log record in the chain of different versions +of the record, and the other thread happens to get the x-latch to the +clustered index record at the right time. + If a query thread notices that the clustered index record it is looking +for is missing, or the roll ptr field in the record doed not point to the +undo log record the thread was assigned to handle, then it gives up the undo +task for that undo log record, and fetches the next. This situation can occur +just in the case where the transaction modified the same record several times +and another thread is currently doing the undo for successive versions of +that index record. */ + +/* Undo node structure */ + +struct undo_node_struct{ + que_common_t common; /* node type: QUE_NODE_UNDO */ + ulint state; /* node execution state */ + trx_t* trx; /* trx for which undo is done */ + dulint roll_ptr;/* roll pointer to undo log record */ + trx_undo_rec_t* undo_rec;/* undo log record */ + dulint undo_no;/* undo number of the record */ + ulint rec_type;/* undo log record type: TRX_UNDO_INSERT_REC, + ... */ + dulint new_roll_ptr; /* roll ptr to restore to clustered index + record */ + dulint new_trx_id; /* trx id to restore to clustered index + record */ + btr_pcur_t pcur; /* persistent cursor used in searching the + clustered index record */ + dict_table_t* table; /* table where undo is done */ + ulint cmpl_info;/* compiler analysis of an update */ + upd_t* update; /* update vector for a clustered index + record */ + dtuple_t* ref; /* row reference to the next row to handle */ + dtuple_t* row; /* a copy (also fields copied to heap) of the + row to handle */ + row_ext_t* ext; /* NULL, or prefixes of the externally + stored columns of the row */ + dtuple_t* undo_row;/* NULL, or the row after undo */ + row_ext_t* undo_ext;/* NULL, or prefixes of the externally + stored columns of undo_row */ + dict_index_t* index; /* the next index whose record should be + handled */ + mem_heap_t* heap; /* memory heap used as auxiliary storage for + row; this must be emptied after undo is tried + on a row */ +}; + +/* Execution states for an undo node */ +#define UNDO_NODE_FETCH_NEXT 1 /* we should fetch the next undo log + record */ +#define UNDO_NODE_PREV_VERS 2 /* the roll ptr to previous version of + a row is stored in node, and undo + should be done based on it */ +#define UNDO_NODE_INSERT 3 +#define UNDO_NODE_MODIFY 4 + + +#ifndef UNIV_NONINL +#include "row0undo.ic" +#endif + +#endif diff --git a/storage/xtradb/include/row0undo.ic b/storage/xtradb/include/row0undo.ic new file mode 100644 index 00000000000..921e3633b10 --- /dev/null +++ b/storage/xtradb/include/row0undo.ic @@ -0,0 +1,23 @@ +/***************************************************************************** + +Copyright (c) 1997, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/****************************************************** +Row undo + +Created 1/8/1997 Heikki Tuuri +*******************************************************/ diff --git a/storage/xtradb/include/row0upd.h b/storage/xtradb/include/row0upd.h new file mode 100644 index 00000000000..71aa20d158c --- /dev/null +++ b/storage/xtradb/include/row0upd.h @@ -0,0 +1,475 @@ +/***************************************************************************** + +Copyright (c) 1996, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/****************************************************** +Update of a row + +Created 12/27/1996 Heikki Tuuri +*******************************************************/ + +#ifndef row0upd_h +#define row0upd_h + +#include "univ.i" +#include "data0data.h" +#include "btr0types.h" +#include "btr0pcur.h" +#include "dict0types.h" +#include "trx0types.h" +#include "que0types.h" +#include "row0types.h" +#include "pars0types.h" + +/************************************************************************* +Creates an update vector object. */ +UNIV_INLINE +upd_t* +upd_create( +/*=======*/ + /* out, own: update vector object */ + ulint n, /* in: number of fields */ + mem_heap_t* heap); /* in: heap from which memory allocated */ +/************************************************************************* +Returns the number of fields in the update vector == number of columns +to be updated by an update vector. */ +UNIV_INLINE +ulint +upd_get_n_fields( +/*=============*/ + /* out: number of fields */ + const upd_t* update); /* in: update vector */ +#ifdef UNIV_DEBUG +/************************************************************************* +Returns the nth field of an update vector. */ +UNIV_INLINE +upd_field_t* +upd_get_nth_field( +/*==============*/ + /* out: update vector field */ + const upd_t* update, /* in: update vector */ + ulint n); /* in: field position in update vector */ +#else +# define upd_get_nth_field(update, n) ((update)->fields + (n)) +#endif +/************************************************************************* +Sets an index field number to be updated by an update vector field. */ +UNIV_INLINE +void +upd_field_set_field_no( +/*===================*/ + upd_field_t* upd_field, /* in: update vector field */ + ulint field_no, /* in: field number in a clustered + index */ + dict_index_t* index, /* in: index */ + trx_t* trx); /* in: transaction */ +/************************************************************************* +Returns a field of an update vector by field_no. */ +UNIV_INLINE +const upd_field_t* +upd_get_field_by_field_no( +/*======================*/ + /* out: update vector field, or NULL */ + const upd_t* update, /* in: update vector */ + ulint no) /* in: field_no */ + __attribute__((nonnull, pure)); +/************************************************************************* +Writes into the redo log the values of trx id and roll ptr and enough info +to determine their positions within a clustered index record. */ +UNIV_INTERN +byte* +row_upd_write_sys_vals_to_log( +/*==========================*/ + /* out: new pointer to mlog */ + dict_index_t* index, /* in: clustered index */ + trx_t* trx, /* in: transaction */ + dulint roll_ptr,/* in: roll ptr of the undo log record */ + byte* log_ptr,/* pointer to a buffer of size > 20 opened + in mlog */ + mtr_t* mtr); /* in: mtr */ +/************************************************************************* +Updates the trx id and roll ptr field in a clustered index record when +a row is updated or marked deleted. */ +UNIV_INLINE +void +row_upd_rec_sys_fields( +/*===================*/ + rec_t* rec, /* in/out: record */ + page_zip_des_t* page_zip,/* in/out: compressed page whose + uncompressed part will be updated, or NULL */ + dict_index_t* index, /* in: clustered index */ + const ulint* offsets,/* in: rec_get_offsets(rec, index) */ + trx_t* trx, /* in: transaction */ + dulint roll_ptr);/* in: roll ptr of the undo log record */ +/************************************************************************* +Sets the trx id or roll ptr field of a clustered index entry. */ +UNIV_INTERN +void +row_upd_index_entry_sys_field( +/*==========================*/ + const dtuple_t* entry, /* in: index entry, where the memory buffers + for sys fields are already allocated: + the function just copies the new values to + them */ + dict_index_t* index, /* in: clustered index */ + ulint type, /* in: DATA_TRX_ID or DATA_ROLL_PTR */ + dulint val); /* in: value to write */ +/************************************************************************* +Creates an update node for a query graph. */ +UNIV_INTERN +upd_node_t* +upd_node_create( +/*============*/ + /* out, own: update node */ + mem_heap_t* heap); /* in: mem heap where created */ +/*************************************************************** +Writes to the redo log the new values of the fields occurring in the index. */ +UNIV_INTERN +void +row_upd_index_write_log( +/*====================*/ + const upd_t* update, /* in: update vector */ + byte* log_ptr,/* in: pointer to mlog buffer: must + contain at least MLOG_BUF_MARGIN bytes + of free space; the buffer is closed + within this function */ + mtr_t* mtr); /* in: mtr into whose log to write */ +/*************************************************************** +Returns TRUE if row update changes size of some field in index or if some +field to be updated is stored externally in rec or update. */ +UNIV_INTERN +ibool +row_upd_changes_field_size_or_external( +/*===================================*/ + /* out: TRUE if the update changes the size of + some field in index or the field is external + in rec or update */ + dict_index_t* index, /* in: index */ + const ulint* offsets,/* in: rec_get_offsets(rec, index) */ + const upd_t* update);/* in: update vector */ +/*************************************************************** +Replaces the new column values stored in the update vector to the record +given. No field size changes are allowed. */ +UNIV_INTERN +void +row_upd_rec_in_place( +/*=================*/ + rec_t* rec, /* in/out: record where replaced */ + dict_index_t* index, /* in: the index the record belongs to */ + const ulint* offsets,/* in: array returned by rec_get_offsets() */ + const upd_t* update, /* in: update vector */ + page_zip_des_t* page_zip);/* in: compressed page with enough space + available, or NULL */ +/******************************************************************* +Builds an update vector from those fields which in a secondary index entry +differ from a record that has the equal ordering fields. NOTE: we compare +the fields as binary strings! */ +UNIV_INTERN +upd_t* +row_upd_build_sec_rec_difference_binary( +/*====================================*/ + /* out, own: update vector of differing + fields */ + dict_index_t* index, /* in: index */ + const dtuple_t* entry, /* in: entry to insert */ + const rec_t* rec, /* in: secondary index record */ + trx_t* trx, /* in: transaction */ + mem_heap_t* heap); /* in: memory heap from which allocated */ +/******************************************************************* +Builds an update vector from those fields, excluding the roll ptr and +trx id fields, which in an index entry differ from a record that has +the equal ordering fields. NOTE: we compare the fields as binary strings! */ +UNIV_INTERN +upd_t* +row_upd_build_difference_binary( +/*============================*/ + /* out, own: update vector of differing + fields, excluding roll ptr and trx id */ + dict_index_t* index, /* in: clustered index */ + const dtuple_t* entry, /* in: entry to insert */ + const rec_t* rec, /* in: clustered index record */ + trx_t* trx, /* in: transaction */ + mem_heap_t* heap); /* in: memory heap from which allocated */ +/*************************************************************** +Replaces the new column values stored in the update vector to the index entry +given. */ +UNIV_INTERN +void +row_upd_index_replace_new_col_vals_index_pos( +/*=========================================*/ + dtuple_t* entry, /* in/out: index entry where replaced; + the clustered index record must be + covered by a lock or a page latch to + prevent deletion (rollback or purge) */ + dict_index_t* index, /* in: index; NOTE that this may also be a + non-clustered index */ + const upd_t* update, /* in: an update vector built for the index so + that the field number in an upd_field is the + index position */ + ibool order_only, + /* in: if TRUE, limit the replacement to + ordering fields of index; note that this + does not work for non-clustered indexes. */ + mem_heap_t* heap) /* in: memory heap for allocating and + copying the new values */ + __attribute__((nonnull)); +/*************************************************************** +Replaces the new column values stored in the update vector to the index entry +given. */ +UNIV_INTERN +void +row_upd_index_replace_new_col_vals( +/*===============================*/ + dtuple_t* entry, /* in/out: index entry where replaced; + the clustered index record must be + covered by a lock or a page latch to + prevent deletion (rollback or purge) */ + dict_index_t* index, /* in: index; NOTE that this may also be a + non-clustered index */ + const upd_t* update, /* in: an update vector built for the + CLUSTERED index so that the field number in + an upd_field is the clustered index position */ + mem_heap_t* heap) /* in: memory heap for allocating and + copying the new values */ + __attribute__((nonnull)); +/*************************************************************** +Replaces the new column values stored in the update vector. */ +UNIV_INTERN +void +row_upd_replace( +/*============*/ + dtuple_t* row, /* in/out: row where replaced, + indexed by col_no; + the clustered index record must be + covered by a lock or a page latch to + prevent deletion (rollback or purge) */ + row_ext_t** ext, /* out, own: NULL, or externally + stored column prefixes */ + const dict_index_t* index, /* in: clustered index */ + const upd_t* update, /* in: an update vector built for the + clustered index */ + mem_heap_t* heap); /* in: memory heap */ +/*************************************************************** +Checks if an update vector changes an ordering field of an index record. +This function is fast if the update vector is short or the number of ordering +fields in the index is small. Otherwise, this can be quadratic. +NOTE: we compare the fields as binary strings! */ +UNIV_INTERN +ibool +row_upd_changes_ord_field_binary( +/*=============================*/ + /* out: TRUE if update vector changes + an ordering field in the index record; + NOTE: the fields are compared as binary + strings */ + const dtuple_t* row, /* in: old value of row, or NULL if the + row and the data values in update are not + known when this function is called, e.g., at + compile time */ + dict_index_t* index, /* in: index of the record */ + const upd_t* update);/* in: update vector for the row; NOTE: the + field numbers in this MUST be clustered index + positions! */ +/*************************************************************** +Checks if an update vector changes an ordering field of an index record. +This function is fast if the update vector is short or the number of ordering +fields in the index is small. Otherwise, this can be quadratic. +NOTE: we compare the fields as binary strings! */ +UNIV_INTERN +ibool +row_upd_changes_some_index_ord_field_binary( +/*========================================*/ + /* out: TRUE if update vector + may change an ordering field + in an index record */ + const dict_table_t* table, /* in: table */ + const upd_t* update);/* in: update vector for the row */ +/*************************************************************** +Updates a row in a table. This is a high-level function used +in SQL execution graphs. */ +UNIV_INTERN +que_thr_t* +row_upd_step( +/*=========*/ + /* out: query thread to run next or NULL */ + que_thr_t* thr); /* in: query thread */ +/************************************************************************* +Parses the log data of system field values. */ +UNIV_INTERN +byte* +row_upd_parse_sys_vals( +/*===================*/ + /* out: log data end or NULL */ + byte* ptr, /* in: buffer */ + byte* end_ptr,/* in: buffer end */ + ulint* pos, /* out: TRX_ID position in record */ + dulint* trx_id, /* out: trx id */ + dulint* roll_ptr);/* out: roll ptr */ +/************************************************************************* +Updates the trx id and roll ptr field in a clustered index record in database +recovery. */ +UNIV_INTERN +void +row_upd_rec_sys_fields_in_recovery( +/*===============================*/ + rec_t* rec, /* in/out: record */ + page_zip_des_t* page_zip,/* in/out: compressed page, or NULL */ + const ulint* offsets,/* in: array returned by rec_get_offsets() */ + ulint pos, /* in: TRX_ID position in rec */ + dulint trx_id, /* in: transaction id */ + dulint roll_ptr);/* in: roll ptr of the undo log record */ +/************************************************************************* +Parses the log data written by row_upd_index_write_log. */ +UNIV_INTERN +byte* +row_upd_index_parse( +/*================*/ + /* out: log data end or NULL */ + byte* ptr, /* in: buffer */ + byte* end_ptr,/* in: buffer end */ + mem_heap_t* heap, /* in: memory heap where update vector is + built */ + upd_t** update_out);/* out: update vector */ + + +/* Update vector field */ +struct upd_field_struct{ + unsigned field_no:16; /* field number in an index, usually + the clustered index, but in updating + a secondary index record in btr0cur.c + this is the position in the secondary + index */ + unsigned orig_len:16; /* original length of the locally + stored part of an externally stored + column, or 0 */ + que_node_t* exp; /* expression for calculating a new + value: it refers to column values and + constants in the symbol table of the + query graph */ + dfield_t new_val; /* new value for the column */ +}; + +/* Update vector structure */ +struct upd_struct{ + ulint info_bits; /* new value of info bits to record; + default is 0 */ + ulint n_fields; /* number of update fields */ + upd_field_t* fields; /* array of update fields */ +}; + +/* Update node structure which also implements the delete operation +of a row */ + +struct upd_node_struct{ + que_common_t common; /* node type: QUE_NODE_UPDATE */ + ibool is_delete;/* TRUE if delete, FALSE if update */ + ibool searched_update; + /* TRUE if searched update, FALSE if + positioned */ + ibool in_mysql_interface; + /* TRUE if the update node was created + for the MySQL interface */ + dict_foreign_t* foreign;/* NULL or pointer to a foreign key + constraint if this update node is used in + doing an ON DELETE or ON UPDATE operation */ + upd_node_t* cascade_node;/* NULL or an update node template which + is used to implement ON DELETE/UPDATE CASCADE + or ... SET NULL for foreign keys */ + mem_heap_t* cascade_heap;/* NULL or a mem heap where the cascade + node is created */ + sel_node_t* select; /* query graph subtree implementing a base + table cursor: the rows returned will be + updated */ + btr_pcur_t* pcur; /* persistent cursor placed on the clustered + index record which should be updated or + deleted; the cursor is stored in the graph + of 'select' field above, except in the case + of the MySQL interface */ + dict_table_t* table; /* table where updated */ + upd_t* update; /* update vector for the row */ + ulint update_n_fields; + /* when this struct is used to implement + a cascade operation for foreign keys, we store + here the size of the buffer allocated for use + as the update vector */ + sym_node_list_t columns;/* symbol table nodes for the columns + to retrieve from the table */ + ibool has_clust_rec_x_lock; + /* TRUE if the select which retrieves the + records to update already sets an x-lock on + the clustered record; note that it must always + set at least an s-lock */ + ulint cmpl_info;/* information extracted during query + compilation; speeds up execution: + UPD_NODE_NO_ORD_CHANGE and + UPD_NODE_NO_SIZE_CHANGE, ORed */ + /*----------------------*/ + /* Local storage for this graph node */ + ulint state; /* node execution state */ + dict_index_t* index; /* NULL, or the next index whose record should + be updated */ + dtuple_t* row; /* NULL, or a copy (also fields copied to + heap) of the row to update; this must be reset + to NULL after a successful update */ + row_ext_t* ext; /* NULL, or prefixes of the externally + stored columns in the old row */ + dtuple_t* upd_row;/* NULL, or a copy of the updated row */ + row_ext_t* upd_ext;/* NULL, or prefixes of the externally + stored columns in upd_row */ + mem_heap_t* heap; /* memory heap used as auxiliary storage; + this must be emptied after a successful + update */ + /*----------------------*/ + sym_node_t* table_sym;/* table node in symbol table */ + que_node_t* col_assign_list; + /* column assignment list */ + ulint magic_n; +}; + +#define UPD_NODE_MAGIC_N 1579975 + +/* Node execution states */ +#define UPD_NODE_SET_IX_LOCK 1 /* execution came to the node from + a node above and if the field + has_clust_rec_x_lock is FALSE, we + should set an intention x-lock on + the table */ +#define UPD_NODE_UPDATE_CLUSTERED 2 /* clustered index record should be + updated */ +#define UPD_NODE_INSERT_CLUSTERED 3 /* clustered index record should be + inserted, old record is already delete + marked */ +#define UPD_NODE_UPDATE_ALL_SEC 4 /* an ordering field of the clustered + index record was changed, or this is + a delete operation: should update + all the secondary index records */ +#define UPD_NODE_UPDATE_SOME_SEC 5 /* secondary index entries should be + looked at and updated if an ordering + field changed */ + +/* Compilation info flags: these must fit within 3 bits; see trx0rec.h */ +#define UPD_NODE_NO_ORD_CHANGE 1 /* no secondary index record will be + changed in the update and no ordering + field of the clustered index */ +#define UPD_NODE_NO_SIZE_CHANGE 2 /* no record field size will be + changed in the update */ + +#ifndef UNIV_NONINL +#include "row0upd.ic" +#endif + +#endif diff --git a/storage/xtradb/include/row0upd.ic b/storage/xtradb/include/row0upd.ic new file mode 100644 index 00000000000..a0c23aa6b07 --- /dev/null +++ b/storage/xtradb/include/row0upd.ic @@ -0,0 +1,179 @@ +/***************************************************************************** + +Copyright (c) 1996, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/****************************************************** +Update of a row + +Created 12/27/1996 Heikki Tuuri +*******************************************************/ + +#include "mtr0log.h" +#include "trx0trx.h" +#include "trx0undo.h" +#include "row0row.h" +#include "btr0sea.h" +#include "page0zip.h" + +/************************************************************************* +Creates an update vector object. */ +UNIV_INLINE +upd_t* +upd_create( +/*=======*/ + /* out, own: update vector object */ + ulint n, /* in: number of fields */ + mem_heap_t* heap) /* in: heap from which memory allocated */ +{ + upd_t* update; + + update = (upd_t*) mem_heap_alloc(heap, sizeof(upd_t)); + + update->info_bits = 0; + update->n_fields = n; + update->fields = (upd_field_t*) + mem_heap_alloc(heap, sizeof(upd_field_t) * n); + + return(update); +} + +/************************************************************************* +Returns the number of fields in the update vector == number of columns +to be updated by an update vector. */ +UNIV_INLINE +ulint +upd_get_n_fields( +/*=============*/ + /* out: number of fields */ + const upd_t* update) /* in: update vector */ +{ + ut_ad(update); + + return(update->n_fields); +} + +#ifdef UNIV_DEBUG +/************************************************************************* +Returns the nth field of an update vector. */ +UNIV_INLINE +upd_field_t* +upd_get_nth_field( +/*==============*/ + /* out: update vector field */ + const upd_t* update, /* in: update vector */ + ulint n) /* in: field position in update vector */ +{ + ut_ad(update); + ut_ad(n < update->n_fields); + + return((upd_field_t*) update->fields + n); +} +#endif /* UNIV_DEBUG */ + +/************************************************************************* +Sets an index field number to be updated by an update vector field. */ +UNIV_INLINE +void +upd_field_set_field_no( +/*===================*/ + upd_field_t* upd_field, /* in: update vector field */ + ulint field_no, /* in: field number in a clustered + index */ + dict_index_t* index, /* in: index */ + trx_t* trx) /* in: transaction */ +{ + upd_field->field_no = field_no; + upd_field->orig_len = 0; + + if (UNIV_UNLIKELY(field_no >= dict_index_get_n_fields(index))) { + fprintf(stderr, + "InnoDB: Error: trying to access field %lu in ", + (ulong) field_no); + dict_index_name_print(stderr, trx, index); + fprintf(stderr, "\n" + "InnoDB: but index only has %lu fields\n", + (ulong) dict_index_get_n_fields(index)); + } + + dict_col_copy_type(dict_index_get_nth_col(index, field_no), + dfield_get_type(&upd_field->new_val)); +} + +/************************************************************************* +Returns a field of an update vector by field_no. */ +UNIV_INLINE +const upd_field_t* +upd_get_field_by_field_no( +/*======================*/ + /* out: update vector field, or NULL */ + const upd_t* update, /* in: update vector */ + ulint no) /* in: field_no */ +{ + ulint i; + for (i = 0; i < upd_get_n_fields(update); i++) { + const upd_field_t* uf = upd_get_nth_field(update, i); + + if (uf->field_no == no) { + + return(uf); + } + } + + return(NULL); +} + +/************************************************************************* +Updates the trx id and roll ptr field in a clustered index record when +a row is updated or marked deleted. */ +UNIV_INLINE +void +row_upd_rec_sys_fields( +/*===================*/ + rec_t* rec, /* in/out: record */ + page_zip_des_t* page_zip,/* in/out: compressed page whose + uncompressed part will be updated, or NULL */ + dict_index_t* index, /* in: clustered index */ + const ulint* offsets,/* in: rec_get_offsets(rec, index) */ + trx_t* trx, /* in: transaction */ + dulint roll_ptr)/* in: roll ptr of the undo log record */ +{ + ut_ad(dict_index_is_clust(index)); + ut_ad(rec_offs_validate(rec, index, offsets)); +#ifdef UNIV_SYNC_DEBUG + if (!rw_lock_own(&btr_search_latch, RW_LOCK_EX)) { + ut_ad(!buf_block_align(rec)->is_hashed); + } +#endif /* UNIV_SYNC_DEBUG */ + + if (UNIV_LIKELY_NULL(page_zip)) { + ulint pos = dict_index_get_sys_col_pos(index, DATA_TRX_ID); + page_zip_write_trx_id_and_roll_ptr(page_zip, rec, offsets, + pos, trx->id, roll_ptr); + } else { + ulint offset = index->trx_id_offset; + + if (!offset) { + offset = row_get_trx_id_offset(rec, index, offsets); + } + +#if DATA_TRX_ID + 1 != DATA_ROLL_PTR +# error "DATA_TRX_ID + 1 != DATA_ROLL_PTR" +#endif + trx_write_trx_id(rec + offset, trx->id); + trx_write_roll_ptr(rec + offset + DATA_TRX_ID_LEN, roll_ptr); + } +} diff --git a/storage/xtradb/include/row0vers.h b/storage/xtradb/include/row0vers.h new file mode 100644 index 00000000000..0feae77e8b5 --- /dev/null +++ b/storage/xtradb/include/row0vers.h @@ -0,0 +1,142 @@ +/***************************************************************************** + +Copyright (c) 1997, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/****************************************************** +Row versions + +Created 2/6/1997 Heikki Tuuri +*******************************************************/ + +#ifndef row0vers_h +#define row0vers_h + +#include "univ.i" +#include "data0data.h" +#include "dict0types.h" +#include "trx0types.h" +#include "que0types.h" +#include "rem0types.h" +#include "mtr0mtr.h" +#include "read0types.h" + +/********************************************************************* +Finds out if an active transaction has inserted or modified a secondary +index record. NOTE: the kernel mutex is temporarily released in this +function! */ +UNIV_INTERN +trx_t* +row_vers_impl_x_locked_off_kernel( +/*==============================*/ + /* out: NULL if committed, else the active + transaction; NOTE that the kernel mutex is + temporarily released! */ + const rec_t* rec, /* in: record in a secondary index */ + dict_index_t* index, /* in: the secondary index */ + const ulint* offsets);/* in: rec_get_offsets(rec, index) */ +/********************************************************************* +Finds out if we must preserve a delete marked earlier version of a clustered +index record, because it is >= the purge view. */ +UNIV_INTERN +ibool +row_vers_must_preserve_del_marked( +/*==============================*/ + /* out: TRUE if earlier version should be preserved */ + dulint trx_id, /* in: transaction id in the version */ + mtr_t* mtr); /* in: mtr holding the latch on the clustered index + record; it will also hold the latch on purge_view */ +/********************************************************************* +Finds out if a version of the record, where the version >= the current +purge view, should have ientry as its secondary index entry. We check +if there is any not delete marked version of the record where the trx +id >= purge view, and the secondary index entry == ientry; exactly in +this case we return TRUE. */ +UNIV_INTERN +ibool +row_vers_old_has_index_entry( +/*=========================*/ + /* out: TRUE if earlier version should have */ + ibool also_curr,/* in: TRUE if also rec is included in the + versions to search; otherwise only versions + prior to it are searched */ + const rec_t* rec, /* in: record in the clustered index; the + caller must have a latch on the page */ + mtr_t* mtr, /* in: mtr holding the latch on rec; it will + also hold the latch on purge_view */ + dict_index_t* index, /* in: the secondary index */ + const dtuple_t* ientry);/* in: the secondary index entry */ +/********************************************************************* +Constructs the version of a clustered index record which a consistent +read should see. We assume that the trx id stored in rec is such that +the consistent read should not see rec in its present version. */ +UNIV_INTERN +ulint +row_vers_build_for_consistent_read( +/*===============================*/ + /* out: DB_SUCCESS or DB_MISSING_HISTORY */ + const rec_t* rec, /* in: record in a clustered index; the + caller must have a latch on the page; this + latch locks the top of the stack of versions + of this records */ + mtr_t* mtr, /* in: mtr holding the latch on rec; it will + also hold the latch on purge_view */ + dict_index_t* index, /* in: the clustered index */ + ulint** offsets,/* in/out: offsets returned by + rec_get_offsets(rec, index) */ + read_view_t* view, /* in: the consistent read view */ + mem_heap_t** offset_heap,/* in/out: memory heap from which + the offsets are allocated */ + mem_heap_t* in_heap,/* in: memory heap from which the memory for + *old_vers is allocated; memory for possible + intermediate versions is allocated and freed + locally within the function */ + rec_t** old_vers);/* out, own: old version, or NULL if the + record does not exist in the view, that is, + it was freshly inserted afterwards */ + +/********************************************************************* +Constructs the last committed version of a clustered index record, +which should be seen by a semi-consistent read. */ +UNIV_INTERN +ulint +row_vers_build_for_semi_consistent_read( +/*====================================*/ + /* out: DB_SUCCESS or DB_MISSING_HISTORY */ + const rec_t* rec, /* in: record in a clustered index; the + caller must have a latch on the page; this + latch locks the top of the stack of versions + of this records */ + mtr_t* mtr, /* in: mtr holding the latch on rec */ + dict_index_t* index, /* in: the clustered index */ + ulint** offsets,/* in/out: offsets returned by + rec_get_offsets(rec, index) */ + mem_heap_t** offset_heap,/* in/out: memory heap from which + the offsets are allocated */ + mem_heap_t* in_heap,/* in: memory heap from which the memory for + *old_vers is allocated; memory for possible + intermediate versions is allocated and freed + locally within the function */ + const rec_t** old_vers);/* out: rec, old version, or NULL if the + record does not exist in the view, that is, + it was freshly inserted afterwards */ + + +#ifndef UNIV_NONINL +#include "row0vers.ic" +#endif + +#endif diff --git a/storage/xtradb/include/row0vers.ic b/storage/xtradb/include/row0vers.ic new file mode 100644 index 00000000000..aac95ea6593 --- /dev/null +++ b/storage/xtradb/include/row0vers.ic @@ -0,0 +1,29 @@ +/***************************************************************************** + +Copyright (c) 1997, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/****************************************************** +Row versions + +Created 2/6/1997 Heikki Tuuri +*******************************************************/ + +#include "row0row.h" +#include "dict0dict.h" +#include "read0read.h" +#include "page0page.h" +#include "log0recv.h" diff --git a/storage/xtradb/include/srv0que.h b/storage/xtradb/include/srv0que.h new file mode 100644 index 00000000000..88db1a013f6 --- /dev/null +++ b/storage/xtradb/include/srv0que.h @@ -0,0 +1,68 @@ +/***************************************************************************** + +Copyright (c) 1996, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/****************************************************** +Server query execution + +Created 6/5/1996 Heikki Tuuri +*******************************************************/ + +#ifndef srv0que_h +#define srv0que_h + +#include "univ.i" +#include "que0types.h" + +/************************************************************************** +Checks if there is work to do in the server task queue. If there is, the +thread starts processing a task. Before leaving, it again checks the task +queue and picks a new task if any exists. This is called by a SRV_WORKER +thread. */ +UNIV_INTERN +void +srv_que_task_queue_check(void); +/*==========================*/ +/************************************************************************** +Performs round-robin on the server tasks. This is called by a SRV_WORKER +thread every second or so. */ +UNIV_INTERN +que_thr_t* +srv_que_round_robin( +/*================*/ + /* out: the new (may be == thr) query thread + to run */ + que_thr_t* thr); /* in: query thread */ +/************************************************************************** +Enqueues a task to server task queue and releases a worker thread, if +there exists one suspended. */ +UNIV_INTERN +void +srv_que_task_enqueue( +/*=================*/ + que_thr_t* thr); /* in: query thread */ +/************************************************************************** +Enqueues a task to server task queue and releases a worker thread, if +there exists one suspended. */ +UNIV_INTERN +void +srv_que_task_enqueue_low( +/*=====================*/ + que_thr_t* thr); /* in: query thread */ + +#endif + diff --git a/storage/xtradb/include/srv0srv.h b/storage/xtradb/include/srv0srv.h new file mode 100644 index 00000000000..c16c8e33057 --- /dev/null +++ b/storage/xtradb/include/srv0srv.h @@ -0,0 +1,618 @@ +/***************************************************************************** + +Copyright (c) 1995, 2009, Innobase Oy. All Rights Reserved. +Copyright (c) 2008, Google Inc. + +Portions of this file contain modifications contributed and copyrighted by +Google, Inc. Those modifications are gratefully acknowledged and are described +briefly in the InnoDB documentation. The contributions by Google are +incorporated with their permission, and subject to the conditions contained in +the file COPYING.Google. + +This program is free software; you can redistribute it and/or modify it under +the terms of the GNU General Public License as published by the Free Software +Foundation; version 2 of the License. + +This program is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + +You 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 + +*****************************************************************************/ + +/****************************************************** +The server main program + +Created 10/10/1995 Heikki Tuuri +*******************************************************/ + +#ifndef srv0srv_h +#define srv0srv_h + +#include "univ.i" +#include "sync0sync.h" +#include "os0sync.h" +#include "que0types.h" +#include "trx0types.h" + +extern const char* srv_main_thread_op_info; + +/* Prefix used by MySQL to indicate pre-5.1 table name encoding */ +extern const char srv_mysql50_table_name_prefix[9]; + +/* When this event is set the lock timeout and InnoDB monitor +thread starts running */ +extern os_event_t srv_lock_timeout_thread_event; + +/* If the last data file is auto-extended, we add this many pages to it +at a time */ +#define SRV_AUTO_EXTEND_INCREMENT \ + (srv_auto_extend_increment * ((1024 * 1024) / UNIV_PAGE_SIZE)) + +/* This is set to TRUE if the MySQL user has set it in MySQL */ +extern ibool srv_lower_case_table_names; + +/* Mutex for locking srv_monitor_file */ +extern mutex_t srv_monitor_file_mutex; +/* Temporary file for innodb monitor output */ +extern FILE* srv_monitor_file; +/* Mutex for locking srv_dict_tmpfile. +This mutex has a very high rank; threads reserving it should not +be holding any InnoDB latches. */ +extern mutex_t srv_dict_tmpfile_mutex; +/* Temporary file for output from the data dictionary */ +extern FILE* srv_dict_tmpfile; +/* Mutex for locking srv_misc_tmpfile. +This mutex has a very low rank; threads reserving it should not +acquire any further latches or sleep before releasing this one. */ +extern mutex_t srv_misc_tmpfile_mutex; +/* Temporary file for miscellanous diagnostic output */ +extern FILE* srv_misc_tmpfile; + +/* Server parameters which are read from the initfile */ + +extern char* srv_data_home; +#ifdef UNIV_LOG_ARCHIVE +extern char* srv_arch_dir; +#endif /* UNIV_LOG_ARCHIVE */ + +/* store to its own file each table created by an user; data +dictionary tables are in the system tablespace 0 */ +extern my_bool srv_file_per_table; +/* The file format to use on new *.ibd files. */ +extern ulint srv_file_format; +/* Whether to check file format during startup.*/ +extern ulint srv_check_file_format_at_startup; +/* Place locks to records only i.e. do not use next-key locking except +on duplicate key checking and foreign key checking */ +extern ibool srv_locks_unsafe_for_binlog; + +extern ulint srv_n_data_files; +extern char** srv_data_file_names; +extern ulint* srv_data_file_sizes; +extern ulint* srv_data_file_is_raw_partition; + +extern ibool srv_extra_undoslots; + +extern ibool srv_auto_extend_last_data_file; +extern ulint srv_last_file_size_max; +extern ulong srv_auto_extend_increment; + +extern ibool srv_created_new_raw; + +#define SRV_NEW_RAW 1 +#define SRV_OLD_RAW 2 + +extern char** srv_log_group_home_dirs; + +extern ulint srv_n_log_groups; +extern ulint srv_n_log_files; +extern ulint srv_log_file_size; +extern ulint srv_log_buffer_size; +extern ulong srv_flush_log_at_trx_commit; + +extern ulint srv_show_locks_held; +extern ulint srv_show_verbose_locks; + +/* The sort order table of the MySQL latin1_swedish_ci character set +collation */ +extern const byte* srv_latin1_ordering; +extern my_bool srv_use_sys_malloc; +extern ulint srv_buf_pool_size; /* requested size in bytes */ +extern ulint srv_buf_pool_old_size; /* previously requested size */ +extern ulint srv_buf_pool_curr_size; /* current size in bytes */ +extern ulint srv_mem_pool_size; +extern ulint srv_lock_table_size; + +extern ibool srv_thread_concurrency_timer_based; + +extern ulint srv_n_file_io_threads; +extern ulint srv_n_read_io_threads; +extern ulint srv_n_write_io_threads; + +#ifdef UNIV_LOG_ARCHIVE +extern ibool srv_log_archive_on; +extern ibool srv_archive_recovery; +extern dulint srv_archive_recovery_limit_lsn; +#endif /* UNIV_LOG_ARCHIVE */ + +extern char* srv_file_flush_method_str; +extern ulint srv_unix_file_flush_method; +extern ulint srv_win_file_flush_method; + +extern ulint srv_max_n_open_files; + +extern ulint srv_max_dirty_pages_pct; + +extern ulint srv_force_recovery; +extern ulong srv_thread_concurrency; +extern ulong srv_commit_concurrency; + +extern ulint srv_max_n_threads; + +extern lint srv_conc_n_threads; + +extern ulint srv_fast_shutdown; /* If this is 1, do not do a + purge and index buffer merge. + If this 2, do not even flush the + buffer pool to data files at the + shutdown: we effectively 'crash' + InnoDB (but lose no committed + transactions). */ +extern ibool srv_innodb_status; + +extern unsigned long long srv_stats_sample_pages; +extern ulint srv_stats_method; +#define SRV_STATS_METHOD_NULLS_EQUAL 0 +#define SRV_STATS_METHOD_NULLS_NOT_EQUAL 1 +#define SRV_STATS_METHOD_IGNORE_NULLS 2 +extern ulint srv_stats_auto_update; + +extern ibool srv_use_doublewrite_buf; +extern ibool srv_use_checksums; + +extern ibool srv_set_thread_priorities; +extern int srv_query_thread_priority; + +extern ulong srv_max_buf_pool_modified_pct; +extern ulong srv_max_purge_lag; + +extern ulong srv_replication_delay; + +extern ulint srv_io_capacity; +extern long long srv_ibuf_max_size; +extern ulint srv_ibuf_active_contract; +extern ulint srv_ibuf_accel_rate; +extern ulint srv_flush_neighbor_pages; +extern ulint srv_enable_unsafe_group_commit; +extern ulint srv_read_ahead; +extern ulint srv_adaptive_checkpoint; + +extern ulint srv_expand_import; + +extern ulint srv_extra_rsegments; +extern ulint srv_dict_size_limit; +/*-------------------------------------------*/ + +extern ulint srv_n_rows_inserted; +extern ulint srv_n_rows_updated; +extern ulint srv_n_rows_deleted; +extern ulint srv_n_rows_read; + +extern ibool srv_print_innodb_monitor; +extern ibool srv_print_innodb_lock_monitor; +extern ibool srv_print_innodb_tablespace_monitor; +extern ibool srv_print_verbose_log; +extern ibool srv_print_innodb_table_monitor; + +extern ibool srv_lock_timeout_and_monitor_active; +extern ibool srv_error_monitor_active; + +extern ulong srv_n_spin_wait_rounds; +extern ulong srv_n_free_tickets_to_enter; +extern ulong srv_thread_sleep_delay; +extern ulint srv_spin_wait_delay; +extern ibool srv_priority_boost; + +extern ulint srv_mem_pool_size; +extern ulint srv_lock_table_size; + +#ifdef UNIV_DEBUG +extern ibool srv_print_thread_releases; +extern ibool srv_print_lock_waits; +extern ibool srv_print_buf_io; +extern ibool srv_print_log_io; +extern ibool srv_print_latch_waits; +#else /* UNIV_DEBUG */ +# define srv_print_thread_releases FALSE +# define srv_print_lock_waits FALSE +# define srv_print_buf_io FALSE +# define srv_print_log_io FALSE +# define srv_print_latch_waits FALSE +#endif /* UNIV_DEBUG */ + +extern ulint srv_activity_count; +extern ulint srv_fatal_semaphore_wait_threshold; +extern ulint srv_dml_needed_delay; + +extern mutex_t* kernel_mutex_temp;/* mutex protecting the server, trx structs, + query threads, and lock table: we allocate + it from dynamic memory to get it to the + same DRAM page as other hotspot semaphores */ +#define kernel_mutex (*kernel_mutex_temp) + +#define SRV_MAX_N_IO_THREADS 100 + +/* Array of English strings describing the current state of an +i/o handler thread */ +extern const char* srv_io_thread_op_info[]; +extern const char* srv_io_thread_function[]; + +/* the number of the log write requests done */ +extern ulint srv_log_write_requests; + +/* the number of physical writes to the log performed */ +extern ulint srv_log_writes; + +/* amount of data written to the log files in bytes */ +extern ulint srv_os_log_written; + +/* amount of writes being done to the log files */ +extern ulint srv_os_log_pending_writes; + +/* we increase this counter, when there we don't have enough space in the +log buffer and have to flush it */ +extern ulint srv_log_waits; + +/* variable that counts amount of data read in total (in bytes) */ +extern ulint srv_data_read; + +/* here we count the amount of data written in total (in bytes) */ +extern ulint srv_data_written; + +/* this variable counts the amount of times, when the doublewrite buffer +was flushed */ +extern ulint srv_dblwr_writes; + +/* here we store the number of pages that have been flushed to the +doublewrite buffer */ +extern ulint srv_dblwr_pages_written; + +/* in this variable we store the number of write requests issued */ +extern ulint srv_buf_pool_write_requests; + +/* here we store the number of times when we had to wait for a free page +in the buffer pool. It happens when the buffer pool is full and we need +to make a flush, in order to be able to read or create a page. */ +extern ulint srv_buf_pool_wait_free; + +/* variable to count the number of pages that were written from the +buffer pool to disk */ +extern ulint srv_buf_pool_flushed; + +/* variable to count the number of buffer pool reads that led to the +reading of a disk page */ +extern ulint srv_buf_pool_reads; + +/* variable to count the number of sequential read-aheads were done */ +extern ulint srv_read_ahead_seq; + +/* variable to count the number of random read-aheads were done */ +extern ulint srv_read_ahead_rnd; + +/* In this structure we store status variables to be passed to MySQL */ +typedef struct export_var_struct export_struc; + +extern export_struc export_vars; + +typedef struct srv_sys_struct srv_sys_t; + +/* The server system */ +extern srv_sys_t* srv_sys; + +/* Alternatives for the file flush option in Unix; see the InnoDB manual +about what these mean */ +#define SRV_UNIX_FSYNC 1 /* This is the default */ +#define SRV_UNIX_O_DSYNC 2 +#define SRV_UNIX_LITTLESYNC 3 +#define SRV_UNIX_NOSYNC 4 +#define SRV_UNIX_O_DIRECT 5 + +/* Alternatives for file i/o in Windows */ +#define SRV_WIN_IO_NORMAL 1 +#define SRV_WIN_IO_UNBUFFERED 2 /* This is the default */ + +/* Alternatives for srv_force_recovery. Non-zero values are intended +to help the user get a damaged database up so that he can dump intact +tables and rows with SELECT INTO OUTFILE. The database must not otherwise +be used with these options! A bigger number below means that all precautions +of lower numbers are included. */ + +#define SRV_FORCE_IGNORE_CORRUPT 1 /* let the server run even if it + detects a corrupt page */ +#define SRV_FORCE_NO_BACKGROUND 2 /* prevent the main thread from + running: if a crash would occur + in purge, this prevents it */ +#define SRV_FORCE_NO_TRX_UNDO 3 /* do not run trx rollback after + recovery */ +#define SRV_FORCE_NO_IBUF_MERGE 4 /* prevent also ibuf operations: + if they would cause a crash, better + not do them */ +#define SRV_FORCE_NO_UNDO_LOG_SCAN 5 /* do not look at undo logs when + starting the database: InnoDB will + treat even incomplete transactions + as committed */ +#define SRV_FORCE_NO_LOG_REDO 6 /* do not do the log roll-forward + in connection with recovery */ + +/** Types of threads existing in the system. */ +enum srv_thread_type { + SRV_COM = 1, /**< threads serving communication and queries */ + SRV_CONSOLE, /**< thread serving console */ + SRV_WORKER, /**< threads serving parallelized queries and + queries released from lock wait */ +#if 0 + /* Utility threads */ + SRV_BUFFER, /**< thread flushing dirty buffer blocks */ + SRV_RECOVERY, /**< threads finishing a recovery */ + SRV_INSERT, /**< thread flushing the insert buffer to disk */ +#endif + SRV_MASTER /**< the master thread, (whose type number must + be biggest) */ +}; + +/************************************************************************* +Boots Innobase server. */ +UNIV_INTERN +ulint +srv_boot(void); +/*==========*/ + /* out: DB_SUCCESS or error code */ +/************************************************************************* +Initializes the server. */ +UNIV_INTERN +void +srv_init(void); +/*==========*/ +/************************************************************************* +Frees the OS fast mutex created in srv_boot(). */ +UNIV_INTERN +void +srv_free(void); +/*==========*/ +/************************************************************************* +Initializes the synchronization primitives, memory system, and the thread +local storage. */ +UNIV_INTERN +void +srv_general_init(void); +/*==================*/ +/************************************************************************* +Gets the number of threads in the system. */ +UNIV_INTERN +ulint +srv_get_n_threads(void); +/*===================*/ +/************************************************************************* +Returns the calling thread type. */ + +enum srv_thread_type +srv_get_thread_type(void); +/*=====================*/ + /* out: SRV_COM, ... */ +/************************************************************************* +Sets the info describing an i/o thread current state. */ +UNIV_INTERN +void +srv_set_io_thread_op_info( +/*======================*/ + ulint i, /* in: the 'segment' of the i/o thread */ + const char* str); /* in: constant char string describing the + state */ +/************************************************************************* +Releases threads of the type given from suspension in the thread table. +NOTE! The server mutex has to be reserved by the caller! */ +UNIV_INTERN +ulint +srv_release_threads( +/*================*/ + /* out: number of threads + released: this may be < n if + not enough threads were + suspended at the moment */ + enum srv_thread_type type, /* in: thread type */ + ulint n); /* in: number of threads to release */ +/************************************************************************* +The master thread controlling the server. */ +UNIV_INTERN +os_thread_ret_t +srv_master_thread( +/*==============*/ + /* out: a dummy parameter */ + void* arg); /* in: a dummy parameter required by + os_thread_create */ +/*********************************************************************** +Tells the Innobase server that there has been activity in the database +and wakes up the master thread if it is suspended (not sleeping). Used +in the MySQL interface. Note that there is a small chance that the master +thread stays suspended (we do not protect our operation with the kernel +mutex, for performace reasons). */ +UNIV_INTERN +void +srv_active_wake_master_thread(void); +/*===============================*/ +/*********************************************************************** +Wakes up the master thread if it is suspended or being suspended. */ +UNIV_INTERN +void +srv_wake_master_thread(void); +/*========================*/ +/************************************************************************* +Puts an OS thread to wait if there are too many concurrent threads +(>= srv_thread_concurrency) inside InnoDB. The threads wait in a FIFO queue. */ +UNIV_INTERN +void +srv_conc_enter_innodb( +/*==================*/ + trx_t* trx); /* in: transaction object associated with the + thread */ +/************************************************************************* +This lets a thread enter InnoDB regardless of the number of threads inside +InnoDB. This must be called when a thread ends a lock wait. */ +UNIV_INTERN +void +srv_conc_force_enter_innodb( +/*========================*/ + trx_t* trx); /* in: transaction object associated with the + thread */ +/************************************************************************* +This must be called when a thread exits InnoDB in a lock wait or at the +end of an SQL statement. */ +UNIV_INTERN +void +srv_conc_force_exit_innodb( +/*=======================*/ + trx_t* trx); /* in: transaction object associated with the + thread */ +/************************************************************************* +This must be called when a thread exits InnoDB. */ +UNIV_INTERN +void +srv_conc_exit_innodb( +/*=================*/ + trx_t* trx); /* in: transaction object associated with the + thread */ +/******************************************************************* +Puts a MySQL OS thread to wait for a lock to be released. If an error +occurs during the wait trx->error_state associated with thr is +!= DB_SUCCESS when we return. DB_LOCK_WAIT_TIMEOUT and DB_DEADLOCK +are possible errors. DB_DEADLOCK is returned if selective deadlock +resolution chose this transaction as a victim. */ +UNIV_INTERN +void +srv_suspend_mysql_thread( +/*=====================*/ + que_thr_t* thr); /* in: query thread associated with the MySQL + OS thread */ +/************************************************************************ +Releases a MySQL OS thread waiting for a lock to be released, if the +thread is already suspended. */ +UNIV_INTERN +void +srv_release_mysql_thread_if_suspended( +/*==================================*/ + que_thr_t* thr); /* in: query thread associated with the + MySQL OS thread */ +/************************************************************************* +A thread which wakes up threads whose lock wait may have lasted too long. +This also prints the info output by various InnoDB monitors. */ +UNIV_INTERN +os_thread_ret_t +srv_lock_timeout_and_monitor_thread( +/*================================*/ + /* out: a dummy parameter */ + void* arg); /* in: a dummy parameter required by + os_thread_create */ +/************************************************************************* +A thread which prints warnings about semaphore waits which have lasted +too long. These can be used to track bugs which cause hangs. */ +UNIV_INTERN +os_thread_ret_t +srv_error_monitor_thread( +/*=====================*/ + /* out: a dummy parameter */ + void* arg); /* in: a dummy parameter required by + os_thread_create */ +/********************************************************************** +Outputs to a file the output of the InnoDB Monitor. */ +UNIV_INTERN +void +srv_printf_innodb_monitor( +/*======================*/ + FILE* file, /* in: output stream */ + ulint* trx_start, /* out: file position of the start of + the list of active transactions */ + ulint* trx_end); /* out: file position of the end of + the list of active transactions */ + +/********************************************************************** +Function to pass InnoDB status variables to MySQL */ +UNIV_INTERN +void +srv_export_innodb_status(void); +/*=====================*/ + +/* Thread slot in the thread table */ +typedef struct srv_slot_struct srv_slot_t; + +/* Thread table is an array of slots */ +typedef srv_slot_t srv_table_t; + +/* In this structure we store status variables to be passed to MySQL */ +struct export_var_struct{ + ulint innodb_data_pending_reads; + ulint innodb_data_pending_writes; + ulint innodb_data_pending_fsyncs; + ulint innodb_data_fsyncs; + ulint innodb_data_read; + ulint innodb_data_writes; + ulint innodb_data_written; + ulint innodb_data_reads; + ulint innodb_dict_tables; + ulint innodb_buffer_pool_pages_total; + ulint innodb_buffer_pool_pages_data; + ulint innodb_buffer_pool_pages_dirty; + ulint innodb_buffer_pool_pages_misc; + ulint innodb_buffer_pool_pages_free; +#ifdef UNIV_DEBUG + ulint innodb_buffer_pool_pages_latched; +#endif /* UNIV_DEBUG */ + ulint innodb_buffer_pool_read_requests; + ulint innodb_buffer_pool_reads; + ulint innodb_buffer_pool_wait_free; + ulint innodb_buffer_pool_pages_flushed; + ulint innodb_buffer_pool_write_requests; + ulint innodb_buffer_pool_read_ahead_seq; + ulint innodb_buffer_pool_read_ahead_rnd; + ulint innodb_dblwr_pages_written; + ulint innodb_dblwr_writes; + ibool innodb_have_atomic_builtins; + ulint innodb_log_waits; + ulint innodb_log_write_requests; + ulint innodb_log_writes; + ulint innodb_os_log_written; + ulint innodb_os_log_fsyncs; + ulint innodb_os_log_pending_writes; + ulint innodb_os_log_pending_fsyncs; + ulint innodb_page_size; + ulint innodb_pages_created; + ulint innodb_pages_read; + ulint innodb_pages_written; + ulint innodb_row_lock_waits; + ulint innodb_row_lock_current_waits; + ib_int64_t innodb_row_lock_time; + ulint innodb_row_lock_time_avg; + ulint innodb_row_lock_time_max; + ulint innodb_rows_read; + ulint innodb_rows_inserted; + ulint innodb_rows_updated; + ulint innodb_rows_deleted; +}; + +/* The server system struct */ +struct srv_sys_struct{ + srv_table_t* threads; /* server thread table */ + UT_LIST_BASE_NODE_T(que_thr_t) + tasks; /* task queue */ + dict_index_t* dummy_ind1; /* dummy index for old-style + supremum and infimum records */ + dict_index_t* dummy_ind2; /* dummy index for new-style + supremum and infimum records */ +}; + +extern ulint srv_n_threads_active[]; + +#endif diff --git a/storage/xtradb/include/srv0srv.ic b/storage/xtradb/include/srv0srv.ic new file mode 100644 index 00000000000..93d675f1dca --- /dev/null +++ b/storage/xtradb/include/srv0srv.ic @@ -0,0 +1,23 @@ +/***************************************************************************** + +Copyright (c) 1995, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/****************************************************** +Server main program + +Created 10/4/1995 Heikki Tuuri +*******************************************************/ diff --git a/storage/xtradb/include/srv0start.h b/storage/xtradb/include/srv0start.h new file mode 100644 index 00000000000..15fa3b8f95f --- /dev/null +++ b/storage/xtradb/include/srv0start.h @@ -0,0 +1,118 @@ +/***************************************************************************** + +Copyright (c) 1995, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/****************************************************** +Starts the Innobase database server + +Created 10/10/1995 Heikki Tuuri +*******************************************************/ + +#ifndef srv0start_h +#define srv0start_h + +#include "univ.i" +#include "ut0byte.h" + +/************************************************************************* +Normalizes a directory path for Windows: converts slashes to backslashes. */ +UNIV_INTERN +void +srv_normalize_path_for_win( +/*=======================*/ + char* str); /* in/out: null-terminated character string */ +/************************************************************************* +Reads the data files and their sizes from a character string given in +the .cnf file. */ +UNIV_INTERN +ibool +srv_parse_data_file_paths_and_sizes( +/*================================*/ + /* out: TRUE if ok, FALSE on parse error */ + char* str); /* in/out: the data file path string */ +/************************************************************************* +Reads log group home directories from a character string given in +the .cnf file. */ +UNIV_INTERN +ibool +srv_parse_log_group_home_dirs( +/*==========================*/ + /* out: TRUE if ok, FALSE on parse error */ + char* str); /* in/out: character string */ +/************************************************************************* +Frees the memory allocated by srv_parse_data_file_paths_and_sizes() +and srv_parse_log_group_home_dirs(). */ +UNIV_INTERN +void +srv_free_paths_and_sizes(void); +/*==========================*/ +/************************************************************************* +Adds a slash or a backslash to the end of a string if it is missing +and the string is not empty. */ +UNIV_INTERN +char* +srv_add_path_separator_if_needed( +/*=============================*/ + /* out: string which has the separator if the + string is not empty */ + char* str); /* in: null-terminated character string */ +/******************************************************************** +Starts Innobase and creates a new database if database files +are not found and the user wants. */ +UNIV_INTERN +int +innobase_start_or_create_for_mysql(void); +/*====================================*/ + /* out: DB_SUCCESS or error code */ +/******************************************************************** +Shuts down the Innobase database. */ +UNIV_INTERN +int +innobase_shutdown_for_mysql(void); +/*=============================*/ + /* out: DB_SUCCESS or error code */ +extern ib_uint64_t srv_shutdown_lsn; +extern ib_uint64_t srv_start_lsn; + +#ifdef __NETWARE__ +void set_panic_flag_for_netware(void); +#endif + +#ifdef HAVE_DARWIN_THREADS +extern ibool srv_have_fullfsync; +#endif + +extern ibool srv_is_being_started; +extern ibool srv_was_started; +extern ibool srv_startup_is_before_trx_rollback_phase; +extern ibool srv_is_being_shut_down; + +extern ibool srv_start_raw_disk_in_use; + +/* At a shutdown the value first climbs from 0 to SRV_SHUTDOWN_CLEANUP +and then to SRV_SHUTDOWN_LAST_PHASE, and so on */ + +extern ulint srv_shutdown_state; + +#define SRV_SHUTDOWN_CLEANUP 1 +#define SRV_SHUTDOWN_LAST_PHASE 2 +#define SRV_SHUTDOWN_EXIT_THREADS 3 + +/* Log 'spaces' have id's >= this */ +#define SRV_LOG_SPACE_FIRST_ID 0xFFFFFFF0UL + +#endif diff --git a/storage/xtradb/include/sync0arr.h b/storage/xtradb/include/sync0arr.h new file mode 100644 index 00000000000..cc01c9ac5c8 --- /dev/null +++ b/storage/xtradb/include/sync0arr.h @@ -0,0 +1,138 @@ +/***************************************************************************** + +Copyright (c) 1995, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/****************************************************** +The wait array used in synchronization primitives + +Created 9/5/1995 Heikki Tuuri +*******************************************************/ + +#ifndef sync0arr_h +#define sync0arr_h + +#include "univ.i" +#include "ut0lst.h" +#include "ut0mem.h" +#include "os0thread.h" + +typedef struct sync_cell_struct sync_cell_t; +typedef struct sync_array_struct sync_array_t; + +#define SYNC_ARRAY_OS_MUTEX 1 +#define SYNC_ARRAY_MUTEX 2 + +/*********************************************************************** +Creates a synchronization wait array. It is protected by a mutex +which is automatically reserved when the functions operating on it +are called. */ +UNIV_INTERN +sync_array_t* +sync_array_create( +/*==============*/ + /* out, own: created wait array */ + ulint n_cells, /* in: number of cells in the array + to create */ + ulint protection); /* in: either SYNC_ARRAY_OS_MUTEX or + SYNC_ARRAY_MUTEX: determines the type + of mutex protecting the data structure */ +/********************************************************************** +Frees the resources in a wait array. */ +UNIV_INTERN +void +sync_array_free( +/*============*/ + sync_array_t* arr); /* in, own: sync wait array */ +/********************************************************************** +Reserves a wait array cell for waiting for an object. +The event of the cell is reset to nonsignalled state. */ +UNIV_INTERN +void +sync_array_reserve_cell( +/*====================*/ + sync_array_t* arr, /* in: wait array */ + void* object, /* in: pointer to the object to wait for */ + ulint type, /* in: lock request type */ + const char* file, /* in: file where requested */ + ulint line, /* in: line where requested */ + ulint* index); /* out: index of the reserved cell */ +/********************************************************************** +This function should be called when a thread starts to wait on +a wait array cell. In the debug version this function checks +if the wait for a semaphore will result in a deadlock, in which +case prints info and asserts. */ +UNIV_INTERN +void +sync_array_wait_event( +/*==================*/ + sync_array_t* arr, /* in: wait array */ + ulint index); /* in: index of the reserved cell */ +/********************************************************************** +Frees the cell. NOTE! sync_array_wait_event frees the cell +automatically! */ +UNIV_INTERN +void +sync_array_free_cell( +/*=================*/ + sync_array_t* arr, /* in: wait array */ + ulint index); /* in: index of the cell in array */ +/************************************************************************** +Note that one of the wait objects was signalled. */ +UNIV_INTERN +void +sync_array_object_signalled( +/*========================*/ + sync_array_t* arr); /* in: wait array */ +/************************************************************************** +If the wakeup algorithm does not work perfectly at semaphore relases, +this function will do the waking (see the comment in mutex_exit). This +function should be called about every 1 second in the server. */ +UNIV_INTERN +void +sync_arr_wake_threads_if_sema_free(void); +/*====================================*/ +/************************************************************************** +Prints warnings of long semaphore waits to stderr. */ +UNIV_INTERN +ibool +sync_array_print_long_waits(void); +/*=============================*/ + /* out: TRUE if fatal semaphore wait threshold + was exceeded */ +/************************************************************************ +Validates the integrity of the wait array. Checks +that the number of reserved cells equals the count variable. */ +UNIV_INTERN +void +sync_array_validate( +/*================*/ + sync_array_t* arr); /* in: sync wait array */ +/************************************************************************** +Prints info of the wait array. */ +UNIV_INTERN +void +sync_array_print_info( +/*==================*/ + FILE* file, /* in: file where to print */ + sync_array_t* arr); /* in: wait array */ + + +#ifndef UNIV_NONINL +#include "sync0arr.ic" +#endif + +#endif diff --git a/storage/xtradb/include/sync0arr.ic b/storage/xtradb/include/sync0arr.ic new file mode 100644 index 00000000000..09a562a4723 --- /dev/null +++ b/storage/xtradb/include/sync0arr.ic @@ -0,0 +1,26 @@ +/***************************************************************************** + +Copyright (c) 1995, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/****************************************************** +The wait array for synchronization primitives + +Inline code + +Created 9/5/1995 Heikki Tuuri +*******************************************************/ + diff --git a/storage/xtradb/include/sync0rw.h b/storage/xtradb/include/sync0rw.h new file mode 100644 index 00000000000..e3fe0dc9ccc --- /dev/null +++ b/storage/xtradb/include/sync0rw.h @@ -0,0 +1,567 @@ +/***************************************************************************** + +Copyright (c) 1995, 2009, Innobase Oy. All Rights Reserved. +Copyright (c) 2008, Google Inc. + +Portions of this file contain modifications contributed and copyrighted by +Google, Inc. Those modifications are gratefully acknowledged and are described +briefly in the InnoDB documentation. The contributions by Google are +incorporated with their permission, and subject to the conditions contained in +the file COPYING.Google. + +This program is free software; you can redistribute it and/or modify it under +the terms of the GNU General Public License as published by the Free Software +Foundation; version 2 of the License. + +This program is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + +You 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 + +*****************************************************************************/ + +/****************************************************** +The read-write lock (for threads, not for database transactions) + +Created 9/11/1995 Heikki Tuuri +*******************************************************/ + +#ifndef sync0rw_h +#define sync0rw_h + +#include "univ.i" +#include "ut0lst.h" +#include "sync0sync.h" +#include "os0sync.h" + +/* The following undef is to prevent a name conflict with a macro +in MySQL: */ +#undef rw_lock_t + +/* Latch types; these are used also in btr0btr.h: keep the numerical values +smaller than 30 and the order of the numerical values like below! */ +#define RW_S_LATCH 1 +#define RW_X_LATCH 2 +#define RW_NO_LATCH 3 + +/* We decrement lock_word by this amount for each x_lock. It is also the +start value for the lock_word, meaning that it limits the maximum number +of concurrent read locks before the rw_lock breaks. The current value of +0x00100000 allows 1,048,575 concurrent readers and 2047 recursive writers.*/ +#define X_LOCK_DECR 0x00100000 + +typedef struct rw_lock_struct rw_lock_t; +#ifdef UNIV_SYNC_DEBUG +typedef struct rw_lock_debug_struct rw_lock_debug_t; +#endif /* UNIV_SYNC_DEBUG */ + +typedef UT_LIST_BASE_NODE_T(rw_lock_t) rw_lock_list_t; + +extern rw_lock_list_t rw_lock_list; +extern mutex_t rw_lock_list_mutex; + +#ifdef UNIV_SYNC_DEBUG +/* The global mutex which protects debug info lists of all rw-locks. +To modify the debug info list of an rw-lock, this mutex has to be + +acquired in addition to the mutex protecting the lock. */ +extern mutex_t rw_lock_debug_mutex; +extern os_event_t rw_lock_debug_event; /* If deadlock detection does + not get immediately the mutex it + may wait for this event */ +extern ibool rw_lock_debug_waiters; /* This is set to TRUE, if + there may be waiters for the event */ +#endif /* UNIV_SYNC_DEBUG */ + +extern ib_int64_t rw_s_spin_wait_count; +extern ib_int64_t rw_s_spin_round_count; +extern ib_int64_t rw_s_exit_count; +extern ib_int64_t rw_s_os_wait_count; +extern ib_int64_t rw_x_spin_wait_count; +extern ib_int64_t rw_x_spin_round_count; +extern ib_int64_t rw_x_os_wait_count; +extern ib_int64_t rw_x_exit_count; + +/********************************************************************** +Creates, or rather, initializes an rw-lock object in a specified memory +location (which must be appropriately aligned). The rw-lock is initialized +to the non-locked state. Explicit freeing of the rw-lock with rw_lock_free +is necessary only if the memory block containing it is freed. */ +#ifdef UNIV_DEBUG +# ifdef UNIV_SYNC_DEBUG +# define rw_lock_create(L, level) \ + rw_lock_create_func((L), (level), #L, __FILE__, __LINE__) +# else /* UNIV_SYNC_DEBUG */ +# define rw_lock_create(L, level) \ + rw_lock_create_func((L), #L, __FILE__, __LINE__) +# endif /* UNIV_SYNC_DEBUG */ +#else /* UNIV_DEBUG */ +# define rw_lock_create(L, level) \ + rw_lock_create_func((L), __FILE__, __LINE__) +#endif /* UNIV_DEBUG */ + +/********************************************************************** +Creates, or rather, initializes an rw-lock object in a specified memory +location (which must be appropriately aligned). The rw-lock is initialized +to the non-locked state. Explicit freeing of the rw-lock with rw_lock_free +is necessary only if the memory block containing it is freed. */ +UNIV_INTERN +void +rw_lock_create_func( +/*================*/ + rw_lock_t* lock, /* in: pointer to memory */ +#ifdef UNIV_DEBUG +# ifdef UNIV_SYNC_DEBUG + ulint level, /* in: level */ +# endif /* UNIV_SYNC_DEBUG */ + const char* cmutex_name, /* in: mutex name */ +#endif /* UNIV_DEBUG */ + const char* cfile_name, /* in: file name where created */ + ulint cline); /* in: file line where created */ +/********************************************************************** +Calling this function is obligatory only if the memory buffer containing +the rw-lock is freed. Removes an rw-lock object from the global list. The +rw-lock is checked to be in the non-locked state. */ +UNIV_INTERN +void +rw_lock_free( +/*=========*/ + rw_lock_t* lock); /* in: rw-lock */ +#ifdef UNIV_DEBUG +/********************************************************************** +Checks that the rw-lock has been initialized and that there are no +simultaneous shared and exclusive locks. */ +UNIV_INTERN +ibool +rw_lock_validate( +/*=============*/ + rw_lock_t* lock); +#endif /* UNIV_DEBUG */ +/****************************************************************** +NOTE! The following macros should be used in rw s-locking, not the +corresponding function. */ + +#define rw_lock_s_lock(M) rw_lock_s_lock_func(\ + (M), 0, __FILE__, __LINE__) +/****************************************************************** +NOTE! The following macros should be used in rw s-locking, not the +corresponding function. */ + +#define rw_lock_s_lock_gen(M, P) rw_lock_s_lock_func(\ + (M), (P), __FILE__, __LINE__) +/****************************************************************** +NOTE! The following macros should be used in rw s-locking, not the +corresponding function. */ + +#define rw_lock_s_lock_nowait(M, F, L) rw_lock_s_lock_low(\ + (M), 0, (F), (L)) +/********************************************************************** +Low-level function which tries to lock an rw-lock in s-mode. Performs no +spinning. */ +UNIV_INLINE +ibool +rw_lock_s_lock_low( +/*===============*/ + /* out: TRUE if success */ + rw_lock_t* lock, /* in: pointer to rw-lock */ + ulint pass __attribute__((unused)), + /* in: pass value; != 0, if the lock will be + passed to another thread to unlock */ + const char* file_name, /* in: file name where lock requested */ + ulint line); /* in: line where requested */ +/********************************************************************** +NOTE! Use the corresponding macro, not directly this function, except if +you supply the file name and line number. Lock an rw-lock in shared mode +for the current thread. If the rw-lock is locked in exclusive mode, or +there is an exclusive lock request waiting, the function spins a preset +time (controlled by SYNC_SPIN_ROUNDS), waiting for the lock, before +suspending the thread. */ +UNIV_INLINE +void +rw_lock_s_lock_func( +/*================*/ + rw_lock_t* lock, /* in: pointer to rw-lock */ + ulint pass, /* in: pass value; != 0, if the lock will + be passed to another thread to unlock */ + const char* file_name,/* in: file name where lock requested */ + ulint line); /* in: line where requested */ +/********************************************************************** +NOTE! Use the corresponding macro, not directly this function! Lock an +rw-lock in exclusive mode for the current thread if the lock can be +obtained immediately. */ +UNIV_INLINE +ibool +rw_lock_x_lock_func_nowait( +/*=======================*/ + /* out: TRUE if success */ + rw_lock_t* lock, /* in: pointer to rw-lock */ + const char* file_name,/* in: file name where lock requested */ + ulint line); /* in: line where requested */ +/********************************************************************** +Releases a shared mode lock. */ +UNIV_INLINE +void +rw_lock_s_unlock_func( +/*==================*/ + rw_lock_t* lock /* in: rw-lock */ +#ifdef UNIV_SYNC_DEBUG + ,ulint pass /* in: pass value; != 0, if the lock may have + been passed to another thread to unlock */ +#endif + ); +/*********************************************************************** +Releases a shared mode lock. */ + +#ifdef UNIV_SYNC_DEBUG +#define rw_lock_s_unlock(L) rw_lock_s_unlock_func(L, 0) +#else +#define rw_lock_s_unlock(L) rw_lock_s_unlock_func(L) +#endif +/*********************************************************************** +Releases a shared mode lock. */ + +#ifdef UNIV_SYNC_DEBUG +#define rw_lock_s_unlock_gen(L, P) rw_lock_s_unlock_func(L, P) +#else +#define rw_lock_s_unlock_gen(L, P) rw_lock_s_unlock_func(L) +#endif +/****************************************************************** +NOTE! The following macro should be used in rw x-locking, not the +corresponding function. */ + +#define rw_lock_x_lock(M) rw_lock_x_lock_func(\ + (M), 0, __FILE__, __LINE__) +/****************************************************************** +NOTE! The following macro should be used in rw x-locking, not the +corresponding function. */ + +#define rw_lock_x_lock_gen(M, P) rw_lock_x_lock_func(\ + (M), (P), __FILE__, __LINE__) +/****************************************************************** +NOTE! The following macros should be used in rw x-locking, not the +corresponding function. */ + +#define rw_lock_x_lock_nowait(M) rw_lock_x_lock_func_nowait(\ + (M), __FILE__, __LINE__) +/********************************************************************** +NOTE! Use the corresponding macro, not directly this function! Lock an +rw-lock in exclusive mode for the current thread. If the rw-lock is locked +in shared or exclusive mode, or there is an exclusive lock request waiting, +the function spins a preset time (controlled by SYNC_SPIN_ROUNDS), waiting +for the lock, before suspending the thread. If the same thread has an x-lock +on the rw-lock, locking succeed, with the following exception: if pass != 0, +only a single x-lock may be taken on the lock. NOTE: If the same thread has +an s-lock, locking does not succeed! */ +UNIV_INTERN +void +rw_lock_x_lock_func( +/*================*/ + rw_lock_t* lock, /* in: pointer to rw-lock */ + ulint pass, /* in: pass value; != 0, if the lock will + be passed to another thread to unlock */ + const char* file_name,/* in: file name where lock requested */ + ulint line); /* in: line where requested */ +/********************************************************************** +Releases an exclusive mode lock. */ +UNIV_INLINE +void +rw_lock_x_unlock_func( +/*==================*/ + rw_lock_t* lock /* in: rw-lock */ +#ifdef UNIV_SYNC_DEBUG + ,ulint pass /* in: pass value; != 0, if the lock may have + been passed to another thread to unlock */ +#endif + ); +/*********************************************************************** +Releases an exclusive mode lock. */ + +#ifdef UNIV_SYNC_DEBUG +#define rw_lock_x_unlock(L) rw_lock_x_unlock_func(L, 0) +#else +#define rw_lock_x_unlock(L) rw_lock_x_unlock_func(L) +#endif +/*********************************************************************** +Releases an exclusive mode lock. */ + +#ifdef UNIV_SYNC_DEBUG +#define rw_lock_x_unlock_gen(L, P) rw_lock_x_unlock_func(L, P) +#else +#define rw_lock_x_unlock_gen(L, P) rw_lock_x_unlock_func(L) +#endif +/********************************************************************** +Low-level function which locks an rw-lock in s-mode when we know that it +is possible and none else is currently accessing the rw-lock structure. +Then we can do the locking without reserving the mutex. */ +UNIV_INLINE +void +rw_lock_s_lock_direct( +/*==================*/ + rw_lock_t* lock, /* in: pointer to rw-lock */ + const char* file_name, /* in: file name where requested */ + ulint line /* in: line where lock requested */ +); +/********************************************************************** +Low-level function which locks an rw-lock in x-mode when we know that it +is not locked and none else is currently accessing the rw-lock structure. +Then we can do the locking without reserving the mutex. */ +UNIV_INLINE +void +rw_lock_x_lock_direct( +/*==================*/ + rw_lock_t* lock, /* in: pointer to rw-lock */ + const char* file_name, /* in: file name where requested */ + ulint line /* in: line where lock requested */ +); +/********************************************************************** +This function is used in the insert buffer to move the ownership of an +x-latch on a buffer frame to the current thread. The x-latch was set by +the buffer read operation and it protected the buffer frame while the +read was done. The ownership is moved because we want that the current +thread is able to acquire a second x-latch which is stored in an mtr. +This, in turn, is needed to pass the debug checks of index page +operations. */ +UNIV_INTERN +void +rw_lock_x_lock_move_ownership( +/*==========================*/ + rw_lock_t* lock); /* in: lock which was x-locked in the + buffer read */ +/********************************************************************** +Releases a shared mode lock when we know there are no waiters and none +else will access the lock during the time this function is executed. */ +UNIV_INLINE +void +rw_lock_s_unlock_direct( +/*====================*/ + rw_lock_t* lock); /* in: rw-lock */ +/********************************************************************** +Releases an exclusive mode lock when we know there are no waiters, and +none else will access the lock durint the time this function is executed. */ +UNIV_INLINE +void +rw_lock_x_unlock_direct( +/*====================*/ + rw_lock_t* lock); /* in: rw-lock */ +/********************************************************************** +Returns the value of writer_count for the lock. Does not reserve the lock +mutex, so the caller must be sure it is not changed during the call. */ +UNIV_INLINE +ulint +rw_lock_get_x_lock_count( +/*=====================*/ + /* out: value of writer_count */ + rw_lock_t* lock); /* in: rw-lock */ +/************************************************************************ +Accessor functions for rw lock. */ +UNIV_INLINE +ulint +rw_lock_get_waiters( +/*================*/ + rw_lock_t* lock); +UNIV_INLINE +ulint +rw_lock_get_writer( +/*===============*/ + rw_lock_t* lock); +UNIV_INLINE +ulint +rw_lock_get_reader_count( +/*=====================*/ + rw_lock_t* lock); +/********************************************************************** +Decrements lock_word the specified amount if it is greater than 0. +This is used by both s_lock and x_lock operations. */ +UNIV_INLINE +ibool +rw_lock_lock_word_decr( +/*===================*/ + /* out: TRUE if decr occurs */ + rw_lock_t* lock, /* in: rw-lock */ + ulint amount); /* in: amount to decrement */ +/********************************************************************** +Increments lock_word the specified amount and returns new value. */ +UNIV_INLINE +lint +rw_lock_lock_word_incr( +/*===================*/ + /* out: TRUE if decr occurs */ + rw_lock_t* lock, + ulint amount); /* in: rw-lock */ +/********************************************************************** +This function sets the lock->writer_thread and lock->recursive fields. +For platforms where we are using atomic builtins instead of lock->mutex +it sets the lock->writer_thread field using atomics to ensure memory +ordering. Note that it is assumed that the caller of this function +effectively owns the lock i.e.: nobody else is allowed to modify +lock->writer_thread at this point in time. +The protocol is that lock->writer_thread MUST be updated BEFORE the +lock->recursive flag is set. */ +UNIV_INLINE +void +rw_lock_set_writer_id_and_recursion_flag( +/*=====================================*/ + rw_lock_t* lock, /* in/out: lock to work on */ + ibool recursive); /* in: TRUE if recursion + allowed */ +#ifdef UNIV_SYNC_DEBUG +/********************************************************************** +Checks if the thread has locked the rw-lock in the specified mode, with +the pass value == 0. */ +UNIV_INTERN +ibool +rw_lock_own( +/*========*/ + rw_lock_t* lock, /* in: rw-lock */ + ulint lock_type); /* in: lock type: RW_LOCK_SHARED, + RW_LOCK_EX */ +#endif /* UNIV_SYNC_DEBUG */ +/********************************************************************** +Checks if somebody has locked the rw-lock in the specified mode. */ +UNIV_INTERN +ibool +rw_lock_is_locked( +/*==============*/ + rw_lock_t* lock, /* in: rw-lock */ + ulint lock_type); /* in: lock type: RW_LOCK_SHARED, + RW_LOCK_EX */ +#ifdef UNIV_SYNC_DEBUG +/******************************************************************* +Prints debug info of an rw-lock. */ +UNIV_INTERN +void +rw_lock_print( +/*==========*/ + rw_lock_t* lock); /* in: rw-lock */ +/******************************************************************* +Prints debug info of currently locked rw-locks. */ +UNIV_INTERN +void +rw_lock_list_print_info( +/*====================*/ + FILE* file); /* in: file where to print */ +/******************************************************************* +Returns the number of currently locked rw-locks. +Works only in the debug version. */ +UNIV_INTERN +ulint +rw_lock_n_locked(void); +/*==================*/ + +/*#####################################################################*/ + +/********************************************************************** +Acquires the debug mutex. We cannot use the mutex defined in sync0sync, +because the debug mutex is also acquired in sync0arr while holding the OS +mutex protecting the sync array, and the ordinary mutex_enter might +recursively call routines in sync0arr, leading to a deadlock on the OS +mutex. */ +UNIV_INTERN +void +rw_lock_debug_mutex_enter(void); +/*==========================*/ +/********************************************************************** +Releases the debug mutex. */ +UNIV_INTERN +void +rw_lock_debug_mutex_exit(void); +/*==========================*/ +/************************************************************************* +Prints info of a debug struct. */ +UNIV_INTERN +void +rw_lock_debug_print( +/*================*/ + rw_lock_debug_t* info); /* in: debug struct */ +#endif /* UNIV_SYNC_DEBUG */ + +/* NOTE! The structure appears here only for the compiler to know its size. +Do not use its fields directly! The structure used in the spin lock +implementation of a read-write lock. Several threads may have a shared lock +simultaneously in this lock, but only one writer may have an exclusive lock, +in which case no shared locks are allowed. To prevent starving of a writer +blocked by readers, a writer may queue for x-lock by decrementing lock_word: +no new readers will be let in while the thread waits for readers to exit. */ + +struct rw_lock_struct { + volatile lint lock_word; + /* Holds the state of the lock. */ + volatile ulint waiters;/* 1: there are waiters */ + volatile ibool recursive;/* Default value FALSE which means the lock + is non-recursive. The value is typically set + to TRUE making normal rw_locks recursive. In + case of asynchronous IO, when a non-zero + value of 'pass' is passed then we keep the + lock non-recursive. + This flag also tells us about the state of + writer_thread field. If this flag is set + then writer_thread MUST contain the thread + id of the current x-holder or wait-x thread. + This flag must be reset in x_unlock + functions before incrementing the lock_word */ + volatile os_thread_id_t writer_thread; + /* Thread id of writer thread. Is only + guaranteed to have sane and non-stale + value iff recursive flag is set. */ + os_event_t event; /* Used by sync0arr.c for thread queueing */ + os_event_t wait_ex_event; + /* Event for next-writer to wait on. A thread + must decrement lock_word before waiting. */ +#ifndef INNODB_RW_LOCKS_USE_ATOMICS + mutex_t mutex; /* The mutex protecting rw_lock_struct */ +#endif /* INNODB_RW_LOCKS_USE_ATOMICS */ + + UT_LIST_NODE_T(rw_lock_t) list; + /* All allocated rw locks are put into a + list */ +#ifdef UNIV_SYNC_DEBUG + UT_LIST_BASE_NODE_T(rw_lock_debug_t) debug_list; + /* In the debug version: pointer to the debug + info list of the lock */ + ulint level; /* Level in the global latching order. */ +#endif /* UNIV_SYNC_DEBUG */ + ulint count_os_wait; /* Count of os_waits. May not be accurate */ + const char* cfile_name;/* File name where lock created */ + /* last s-lock file/line is not guaranteed to be correct */ + const char* last_s_file_name;/* File name where last s-locked */ + const char* last_x_file_name;/* File name where last x-locked */ + ibool writer_is_wait_ex; + /* This is TRUE if the writer field is + RW_LOCK_WAIT_EX; this field is located far + from the memory update hotspot fields which + are at the start of this struct, thus we can + peek this field without causing much memory + bus traffic */ + unsigned cline:14; /* Line where created */ + unsigned last_s_line:14; /* Line number where last time s-locked */ + unsigned last_x_line:14; /* Line number where last time x-locked */ + ulint magic_n; +}; + +#define RW_LOCK_MAGIC_N 22643 + +#ifdef UNIV_SYNC_DEBUG +/* The structure for storing debug info of an rw-lock */ +struct rw_lock_debug_struct { + + os_thread_id_t thread_id; /* The thread id of the thread which + locked the rw-lock */ + ulint pass; /* Pass value given in the lock operation */ + ulint lock_type; /* Type of the lock: RW_LOCK_EX, + RW_LOCK_SHARED, RW_LOCK_WAIT_EX */ + const char* file_name;/* File name where the lock was obtained */ + ulint line; /* Line where the rw-lock was locked */ + UT_LIST_NODE_T(rw_lock_debug_t) list; + /* Debug structs are linked in a two-way + list */ +}; +#endif /* UNIV_SYNC_DEBUG */ + +#ifndef UNIV_NONINL +#include "sync0rw.ic" +#endif + +#endif diff --git a/storage/xtradb/include/sync0rw.ic b/storage/xtradb/include/sync0rw.ic new file mode 100644 index 00000000000..9e7e4dc9bd8 --- /dev/null +++ b/storage/xtradb/include/sync0rw.ic @@ -0,0 +1,635 @@ +/***************************************************************************** + +Copyright (c) 1995, 2009, Innobase Oy. All Rights Reserved. +Copyright (c) 2008, Google Inc. + +Portions of this file contain modifications contributed and copyrighted by +Google, Inc. Those modifications are gratefully acknowledged and are described +briefly in the InnoDB documentation. The contributions by Google are +incorporated with their permission, and subject to the conditions contained in +the file COPYING.Google. + +This program is free software; you can redistribute it and/or modify it under +the terms of the GNU General Public License as published by the Free Software +Foundation; version 2 of the License. + +This program is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + +You 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 + +*****************************************************************************/ + +/****************************************************** +The read-write lock (for threads) + +Created 9/11/1995 Heikki Tuuri +*******************************************************/ + +/********************************************************************** +Lock an rw-lock in shared mode for the current thread. If the rw-lock is +locked in exclusive mode, or there is an exclusive lock request waiting, +the function spins a preset time (controlled by SYNC_SPIN_ROUNDS), +waiting for the lock before suspending the thread. */ +UNIV_INTERN +void +rw_lock_s_lock_spin( +/*================*/ + rw_lock_t* lock, /* in: pointer to rw-lock */ + ulint pass, /* in: pass value; != 0, if the lock will + be passed to another thread to unlock */ + const char* file_name,/* in: file name where lock requested */ + ulint line); /* in: line where requested */ +#ifdef UNIV_SYNC_DEBUG +/********************************************************************** +Inserts the debug information for an rw-lock. */ +UNIV_INTERN +void +rw_lock_add_debug_info( +/*===================*/ + rw_lock_t* lock, /* in: rw-lock */ + ulint pass, /* in: pass value */ + ulint lock_type, /* in: lock type */ + const char* file_name, /* in: file where requested */ + ulint line); /* in: line where requested */ +/********************************************************************** +Removes a debug information struct for an rw-lock. */ +UNIV_INTERN +void +rw_lock_remove_debug_info( +/*======================*/ + rw_lock_t* lock, /* in: rw-lock */ + ulint pass, /* in: pass value */ + ulint lock_type); /* in: lock type */ +#endif /* UNIV_SYNC_DEBUG */ + +/************************************************************************ +Accessor functions for rw lock. */ +UNIV_INLINE +ulint +rw_lock_get_waiters( +/*================*/ + /* out: 1 if waiters, 0 otherwise */ + rw_lock_t* lock) /* in: rw-lock */ +{ + return(lock->waiters); +} + +/************************************************************************ +Sets lock->waiters to 1. It is not an error if lock->waiters is already +1. On platforms where ATOMIC builtins are used this function enforces a +memory barrier. */ +UNIV_INLINE +void +rw_lock_set_waiter_flag( +/*====================*/ + rw_lock_t* lock) /* in: rw-lock */ +{ +#ifdef INNODB_RW_LOCKS_USE_ATOMICS + os_compare_and_swap(&lock->waiters, 0, 1); +#else /* INNODB_RW_LOCKS_USE_ATOMICS */ + lock->waiters = 1; +#endif /* INNODB_RW_LOCKS_USE_ATOMICS */ +} + +/************************************************************************ +Resets lock->waiters to 0. It is not an error if lock->waiters is already +0. On platforms where ATOMIC builtins are used this function enforces a +memory barrier. */ +UNIV_INLINE +void +rw_lock_reset_waiter_flag( +/*======================*/ + rw_lock_t* lock) /* in: rw-lock */ +{ +#ifdef INNODB_RW_LOCKS_USE_ATOMICS + os_compare_and_swap(&lock->waiters, 1, 0); +#else /* INNODB_RW_LOCKS_USE_ATOMICS */ + lock->waiters = 0; +#endif /* INNODB_RW_LOCKS_USE_ATOMICS */ +} + +/********************************************************************** +Returns the write-status of the lock - this function made more sense +with the old rw_lock implementation. */ +UNIV_INLINE +ulint +rw_lock_get_writer( +/*===============*/ + rw_lock_t* lock) +{ + lint lock_word = lock->lock_word; + if(lock_word > 0) { + /* return NOT_LOCKED in s-lock state, like the writer + member of the old lock implementation. */ + return(RW_LOCK_NOT_LOCKED); + } else if (((-lock_word) % X_LOCK_DECR) == 0) { + return(RW_LOCK_EX); + } else { + ut_ad(lock_word > -X_LOCK_DECR); + return(RW_LOCK_WAIT_EX); + } +} + +/********************************************************************** +Returns number of readers. */ +UNIV_INLINE +ulint +rw_lock_get_reader_count( +/*=====================*/ + rw_lock_t* lock) +{ + lint lock_word = lock->lock_word; + if(lock_word > 0) { + /* s-locked, no x-waiters */ + return(X_LOCK_DECR - lock_word); + } else if (lock_word < 0 && lock_word > -X_LOCK_DECR) { + /* s-locked, with x-waiters */ + return((ulint)(-lock_word)); + } + return(0); +} + +#ifndef INNODB_RW_LOCKS_USE_ATOMICS +UNIV_INLINE +mutex_t* +rw_lock_get_mutex( +/*==============*/ + rw_lock_t* lock) +{ + return(&(lock->mutex)); +} +#endif + +/********************************************************************** +Returns the value of writer_count for the lock. Does not reserve the lock +mutex, so the caller must be sure it is not changed during the call. */ +UNIV_INLINE +ulint +rw_lock_get_x_lock_count( +/*=====================*/ + /* out: value of writer_count */ + rw_lock_t* lock) /* in: rw-lock */ +{ + lint lock_copy = lock->lock_word; + /* If there is a reader, lock_word is not divisible by X_LOCK_DECR */ + if(lock_copy > 0 || (-lock_copy) % X_LOCK_DECR != 0) { + return(0); + } + return(((-lock_copy) / X_LOCK_DECR) + 1); +} + +/********************************************************************** +Two different implementations for decrementing the lock_word of a rw_lock: +one for systems supporting atomic operations, one for others. This does +does not support recusive x-locks: they should be handled by the caller and +need not be atomic since they are performed by the current lock holder. +Returns true if the decrement was made, false if not. */ +UNIV_INLINE +ibool +rw_lock_lock_word_decr( +/*===================*/ + /* out: TRUE if decr occurs */ + rw_lock_t* lock, /* in: rw-lock */ + ulint amount) /* in: amount of decrement */ +{ + +#ifdef INNODB_RW_LOCKS_USE_ATOMICS + + lint local_lock_word = lock->lock_word; + while (local_lock_word > 0) { + if(os_compare_and_swap(&(lock->lock_word), + local_lock_word, + local_lock_word - amount)) { + return(TRUE); + } + local_lock_word = lock->lock_word; + } + return(FALSE); + +#else /* INNODB_RW_LOCKS_USE_ATOMICS */ + + ibool success = FALSE; + mutex_enter(&(lock->mutex)); + if(lock->lock_word > 0) { + lock->lock_word -= amount; + success = TRUE; + } + mutex_exit(&(lock->mutex)); + return(success); + +#endif /* INNODB_RW_LOCKS_USE_ATOMICS */ +} + +/********************************************************************** +Two different implementations for incrementing the lock_word of a rw_lock: +one for systems supporting atomic operations, one for others. +Returns the value of lock_word after increment. */ +UNIV_INLINE +lint +rw_lock_lock_word_incr( +/*===================*/ + /* out: lock->lock_word after increment */ + rw_lock_t* lock, /* in: rw-lock */ + ulint amount) /* in: amount of increment */ +{ + +#ifdef INNODB_RW_LOCKS_USE_ATOMICS + + return(os_atomic_increment(&(lock->lock_word), amount)); + +#else /* INNODB_RW_LOCKS_USE_ATOMICS */ + + lint local_lock_word; + + mutex_enter(&(lock->mutex)); + + lock->lock_word += amount; + local_lock_word = lock->lock_word; + + mutex_exit(&(lock->mutex)); + + return(local_lock_word); + +#endif /* INNODB_RW_LOCKS_USE_ATOMICS */ +} + +/********************************************************************** +This function sets the lock->writer_thread and lock->recursive fields. +For platforms where we are using atomic builtins instead of lock->mutex +it sets the lock->writer_thread field using atomics to ensure memory +ordering. Note that it is assumed that the caller of this function +effectively owns the lock i.e.: nobody else is allowed to modify +lock->writer_thread at this point in time. +The protocol is that lock->writer_thread MUST be updated BEFORE the +lock->recursive flag is set. */ +UNIV_INLINE +void +rw_lock_set_writer_id_and_recursion_flag( +/*=====================================*/ + rw_lock_t* lock, /* in/out: lock to work on */ + ibool recursive) /* in: TRUE if recursion + allowed */ +{ + os_thread_id_t curr_thread = os_thread_get_curr_id(); + +#ifdef INNODB_RW_LOCKS_USE_ATOMICS + os_thread_id_t local_thread; + ibool success; + + /* Prevent Valgrind warnings about writer_thread being + uninitialized. It does not matter if writer_thread is + uninitialized, because we are comparing writer_thread against + itself, and the operation should always succeed. */ + UNIV_MEM_VALID(&lock->writer_thread, sizeof lock->writer_thread); + + local_thread = lock->writer_thread; + success = os_compare_and_swap(&lock->writer_thread, + local_thread, curr_thread); + ut_a(success); + lock->recursive = recursive; + +#else /* INNODB_RW_LOCKS_USE_ATOMICS */ + + mutex_enter(&lock->mutex); + lock->writer_thread = curr_thread; + lock->recursive = recursive; + mutex_exit(&lock->mutex); + +#endif /* INNODB_RW_LOCKS_USE_ATOMICS */ +} + +/********************************************************************** +Low-level function which tries to lock an rw-lock in s-mode. Performs no +spinning. */ +UNIV_INLINE +ibool +rw_lock_s_lock_low( +/*===============*/ + /* out: TRUE if success */ + rw_lock_t* lock, /* in: pointer to rw-lock */ + ulint pass __attribute__((unused)), + /* in: pass value; != 0, if the lock will be + passed to another thread to unlock */ + const char* file_name, /* in: file name where lock requested */ + ulint line) /* in: line where requested */ +{ + /* TODO: study performance of UNIV_LIKELY branch prediction hints. */ + if (!rw_lock_lock_word_decr(lock, 1)) { + /* Locking did not succeed */ + return(FALSE); + } + +#ifdef UNIV_SYNC_DEBUG + rw_lock_add_debug_info(lock, pass, RW_LOCK_SHARED, file_name, line); +#endif + /* These debugging values are not set safely: they may be incorrect + or even refer to a line that is invalid for the file name. */ + lock->last_s_file_name = file_name; + lock->last_s_line = line; + + return(TRUE); /* locking succeeded */ +} + +/********************************************************************** +Low-level function which locks an rw-lock in s-mode when we know that it +is possible and none else is currently accessing the rw-lock structure. +Then we can do the locking without reserving the mutex. */ +UNIV_INLINE +void +rw_lock_s_lock_direct( +/*==================*/ + rw_lock_t* lock, /* in: pointer to rw-lock */ + const char* file_name, /* in: file name where requested */ + ulint line) /* in: line where lock requested */ +{ + ut_ad(lock->lock_word == X_LOCK_DECR); + + /* Indicate there is a new reader by decrementing lock_word */ + lock->lock_word--; + + lock->last_s_file_name = file_name; + lock->last_s_line = line; + +#ifdef UNIV_SYNC_DEBUG + rw_lock_add_debug_info(lock, 0, RW_LOCK_SHARED, file_name, line); +#endif +} + +/********************************************************************** +Low-level function which locks an rw-lock in x-mode when we know that it +is not locked and none else is currently accessing the rw-lock structure. +Then we can do the locking without reserving the mutex. */ +UNIV_INLINE +void +rw_lock_x_lock_direct( +/*==================*/ + rw_lock_t* lock, /* in: pointer to rw-lock */ + const char* file_name, /* in: file name where requested */ + ulint line) /* in: line where lock requested */ +{ + ut_ad(rw_lock_validate(lock)); + ut_ad(lock->lock_word == X_LOCK_DECR); + + lock->lock_word -= X_LOCK_DECR; + lock->writer_thread = os_thread_get_curr_id(); + lock->recursive = TRUE; + + lock->last_x_file_name = file_name; + lock->last_x_line = line; + +#ifdef UNIV_SYNC_DEBUG + rw_lock_add_debug_info(lock, 0, RW_LOCK_EX, file_name, line); +#endif +} + +/********************************************************************** +NOTE! Use the corresponding macro, not directly this function! Lock an +rw-lock in shared mode for the current thread. If the rw-lock is locked +in exclusive mode, or there is an exclusive lock request waiting, the +function spins a preset time (controlled by SYNC_SPIN_ROUNDS), waiting for +the lock, before suspending the thread. */ +UNIV_INLINE +void +rw_lock_s_lock_func( +/*================*/ + rw_lock_t* lock, /* in: pointer to rw-lock */ + ulint pass, /* in: pass value; != 0, if the lock will + be passed to another thread to unlock */ + const char* file_name,/* in: file name where lock requested */ + ulint line) /* in: line where requested */ +{ + /* NOTE: As we do not know the thread ids for threads which have + s-locked a latch, and s-lockers will be served only after waiting + x-lock requests have been fulfilled, then if this thread already + owns an s-lock here, it may end up in a deadlock with another thread + which requests an x-lock here. Therefore, we will forbid recursive + s-locking of a latch: the following assert will warn the programmer + of the possibility of this kind of a deadlock. If we want to implement + safe recursive s-locking, we should keep in a list the thread ids of + the threads which have s-locked a latch. This would use some CPU + time. */ + +#ifdef UNIV_SYNC_DEBUG + ut_ad(!rw_lock_own(lock, RW_LOCK_SHARED)); /* see NOTE above */ +#endif /* UNIV_SYNC_DEBUG */ + + /* TODO: study performance of UNIV_LIKELY branch prediction hints. */ + if (rw_lock_s_lock_low(lock, pass, file_name, line)) { + + return; /* Success */ + } else { + /* Did not succeed, try spin wait */ + + rw_lock_s_lock_spin(lock, pass, file_name, line); + + return; + } +} + +/********************************************************************** +NOTE! Use the corresponding macro, not directly this function! Lock an +rw-lock in exclusive mode for the current thread if the lock can be +obtained immediately. */ +UNIV_INLINE +ibool +rw_lock_x_lock_func_nowait( +/*=======================*/ + /* out: TRUE if success */ + rw_lock_t* lock, /* in: pointer to rw-lock */ + const char* file_name,/* in: file name where lock requested */ + ulint line) /* in: line where requested */ +{ + os_thread_id_t curr_thread = os_thread_get_curr_id(); + + ibool success; + +#ifdef INNODB_RW_LOCKS_USE_ATOMICS + success = os_compare_and_swap(&(lock->lock_word), X_LOCK_DECR, 0); +#else + + success = FALSE; + mutex_enter(&(lock->mutex)); + if (lock->lock_word == X_LOCK_DECR) { + lock->lock_word = 0; + success = TRUE; + } + mutex_exit(&(lock->mutex)); + +#endif + if (success) { + rw_lock_set_writer_id_and_recursion_flag(lock, TRUE); + + } else if (lock->recursive + && os_thread_eq(lock->writer_thread, curr_thread)) { + /* Relock: this lock_word modification is safe since no other + threads can modify (lock, unlock, or reserve) lock_word while + there is an exclusive writer and this is the writer thread. */ + lock->lock_word -= X_LOCK_DECR; + + ut_ad(((-lock->lock_word) % X_LOCK_DECR) == 0); + + } else { + /* Failure */ + return(FALSE); + } +#ifdef UNIV_SYNC_DEBUG + rw_lock_add_debug_info(lock, 0, RW_LOCK_EX, file_name, line); +#endif + + lock->last_x_file_name = file_name; + lock->last_x_line = line; + + ut_ad(rw_lock_validate(lock)); + + return(TRUE); +} + +/********************************************************************** +Releases a shared mode lock. */ +UNIV_INLINE +void +rw_lock_s_unlock_func( +/*==================*/ + rw_lock_t* lock /* in: rw-lock */ +#ifdef UNIV_SYNC_DEBUG + ,ulint pass /* in: pass value; != 0, if the lock may have + been passed to another thread to unlock */ +#endif + ) +{ + ut_ad((lock->lock_word % X_LOCK_DECR) != 0); + +#ifdef UNIV_SYNC_DEBUG + rw_lock_remove_debug_info(lock, pass, RW_LOCK_SHARED); +#endif + + /* Increment lock_word to indicate 1 less reader */ + if (rw_lock_lock_word_incr(lock, 1) == 0) { + + /* wait_ex waiter exists. It may not be asleep, but we signal + anyway. We do not wake other waiters, because they can't + exist without wait_ex waiter and wait_ex waiter goes first.*/ + os_event_set(lock->wait_ex_event); + sync_array_object_signalled(sync_primary_wait_array); + + } + + ut_ad(rw_lock_validate(lock)); + +#ifdef UNIV_SYNC_PERF_STAT + rw_s_exit_count++; +#endif +} + +/********************************************************************** +Releases a shared mode lock when we know there are no waiters and none +else will access the lock during the time this function is executed. */ +UNIV_INLINE +void +rw_lock_s_unlock_direct( +/*====================*/ + rw_lock_t* lock) /* in: rw-lock */ +{ + ut_ad(lock->lock_word < X_LOCK_DECR); + +#ifdef UNIV_SYNC_DEBUG + rw_lock_remove_debug_info(lock, 0, RW_LOCK_SHARED); +#endif + + /* Decrease reader count by incrementing lock_word */ + lock->lock_word++; + + ut_ad(!lock->waiters); + ut_ad(rw_lock_validate(lock)); +#ifdef UNIV_SYNC_PERF_STAT + rw_s_exit_count++; +#endif +} + +/********************************************************************** +Releases an exclusive mode lock. */ +UNIV_INLINE +void +rw_lock_x_unlock_func( +/*==================*/ + rw_lock_t* lock /* in: rw-lock */ +#ifdef UNIV_SYNC_DEBUG + ,ulint pass /* in: pass value; != 0, if the lock may have + been passed to another thread to unlock */ +#endif + ) +{ + ut_ad((lock->lock_word % X_LOCK_DECR) == 0); + + /* lock->recursive flag also indicates if lock->writer_thread is + valid or stale. If we are the last of the recursive callers + then we must unset lock->recursive flag to indicate that the + lock->writer_thread is now stale. + Note that since we still hold the x-lock we can safely read the + lock_word. */ + if (lock->lock_word == 0) { + /* Last caller in a possible recursive chain. */ + lock->recursive = FALSE; + UNIV_MEM_INVALID(&lock->writer_thread, + sizeof lock->writer_thread); + } + +#ifdef UNIV_SYNC_DEBUG + rw_lock_remove_debug_info(lock, pass, RW_LOCK_EX); +#endif + + if (rw_lock_lock_word_incr(lock, X_LOCK_DECR) == X_LOCK_DECR) { + /* Lock is now free. May have to signal read/write waiters. + We do not need to signal wait_ex waiters, since they cannot + exist when there is a writer. */ + if (lock->waiters) { + rw_lock_reset_waiter_flag(lock); + os_event_set(lock->event); + sync_array_object_signalled(sync_primary_wait_array); + } + } + + ut_ad(rw_lock_validate(lock)); + +#ifdef UNIV_SYNC_PERF_STAT + rw_x_exit_count++; +#endif +} + +/********************************************************************** +Releases an exclusive mode lock when we know there are no waiters, and +none else will access the lock during the time this function is executed. */ +UNIV_INLINE +void +rw_lock_x_unlock_direct( +/*====================*/ + rw_lock_t* lock) /* in: rw-lock */ +{ + /* Reset the exclusive lock if this thread no longer has an x-mode + lock */ + + ut_ad((lock->lock_word % X_LOCK_DECR) == 0); + +#ifdef UNIV_SYNC_DEBUG + rw_lock_remove_debug_info(lock, 0, RW_LOCK_EX); +#endif + + if (lock->lock_word == 0) { + lock->recursive = FALSE; + UNIV_MEM_INVALID(&lock->writer_thread, + sizeof lock->writer_thread); + } + + lock->lock_word += X_LOCK_DECR; + + ut_ad(!lock->waiters); + ut_ad(rw_lock_validate(lock)); + +#ifdef UNIV_SYNC_PERF_STAT + rw_x_exit_count++; +#endif +} diff --git a/storage/xtradb/include/sync0sync.h b/storage/xtradb/include/sync0sync.h new file mode 100644 index 00000000000..dccfc020681 --- /dev/null +++ b/storage/xtradb/include/sync0sync.h @@ -0,0 +1,575 @@ +/***************************************************************************** + +Copyright (c) 1995, 2009, Innobase Oy. All Rights Reserved. +Copyright (c) 2008, Google Inc. + +Portions of this file contain modifications contributed and copyrighted by +Google, Inc. Those modifications are gratefully acknowledged and are described +briefly in the InnoDB documentation. The contributions by Google are +incorporated with their permission, and subject to the conditions contained in +the file COPYING.Google. + +This program is free software; you can redistribute it and/or modify it under +the terms of the GNU General Public License as published by the Free Software +Foundation; version 2 of the License. + +This program is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + +You 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 + +*****************************************************************************/ + +/****************************************************** +Mutex, the basic synchronization primitive + +Created 9/5/1995 Heikki Tuuri +*******************************************************/ + +#ifndef sync0sync_h +#define sync0sync_h + +#include "univ.i" +#include "sync0types.h" +#include "ut0lst.h" +#include "ut0mem.h" +#include "os0thread.h" +#include "os0sync.h" +#include "sync0arr.h" + +#ifndef UNIV_HOTBACKUP +extern my_bool timed_mutexes; +#endif /* UNIV_HOTBACKUP */ + +/********************************************************************** +Initializes the synchronization data structures. */ +UNIV_INTERN +void +sync_init(void); +/*===========*/ +/********************************************************************** +Frees the resources in synchronization data structures. */ +UNIV_INTERN +void +sync_close(void); +/*===========*/ +/********************************************************************** +Creates, or rather, initializes a mutex object to a specified memory +location (which must be appropriately aligned). The mutex is initialized +in the reset state. Explicit freeing of the mutex with mutex_free is +necessary only if the memory block containing it is freed. */ + +#ifdef UNIV_DEBUG +# ifdef UNIV_SYNC_DEBUG +# define mutex_create(M, level) \ + mutex_create_func((M), #M, (level), __FILE__, __LINE__) +# else +# define mutex_create(M, level) \ + mutex_create_func((M), #M, __FILE__, __LINE__) +# endif +#else +# define mutex_create(M, level) \ + mutex_create_func((M), __FILE__, __LINE__) +#endif + +/********************************************************************** +Creates, or rather, initializes a mutex object in a specified memory +location (which must be appropriately aligned). The mutex is initialized +in the reset state. Explicit freeing of the mutex with mutex_free is +necessary only if the memory block containing it is freed. */ +UNIV_INTERN +void +mutex_create_func( +/*==============*/ + mutex_t* mutex, /* in: pointer to memory */ +#ifdef UNIV_DEBUG + const char* cmutex_name, /* in: mutex name */ +# ifdef UNIV_SYNC_DEBUG + ulint level, /* in: level */ +# endif /* UNIV_SYNC_DEBUG */ +#endif /* UNIV_DEBUG */ + const char* cfile_name, /* in: file name where created */ + ulint cline); /* in: file line where created */ + +#undef mutex_free /* Fix for MacOS X */ + +/********************************************************************** +Calling this function is obligatory only if the memory buffer containing +the mutex is freed. Removes a mutex object from the mutex list. The mutex +is checked to be in the reset state. */ +UNIV_INTERN +void +mutex_free( +/*=======*/ + mutex_t* mutex); /* in: mutex */ +/****************************************************************** +NOTE! The following macro should be used in mutex locking, not the +corresponding function. */ + +#define mutex_enter(M) mutex_enter_func((M), __FILE__, __LINE__) +/****************************************************************** +NOTE! The following macro should be used in mutex locking, not the +corresponding function. */ + +/* NOTE! currently same as mutex_enter! */ + +#define mutex_enter_fast(M) mutex_enter_func((M), __FILE__, __LINE__) +/********************************************************************** +NOTE! Use the corresponding macro in the header file, not this function +directly. Locks a mutex for the current thread. If the mutex is reserved +the function spins a preset time (controlled by SYNC_SPIN_ROUNDS) waiting +for the mutex before suspending the thread. */ +UNIV_INLINE +void +mutex_enter_func( +/*=============*/ + mutex_t* mutex, /* in: pointer to mutex */ + const char* file_name, /* in: file name where locked */ + ulint line); /* in: line where locked */ +/****************************************************************** +NOTE! The following macro should be used in mutex locking, not the +corresponding function. */ + +#define mutex_enter_nowait(M) \ + mutex_enter_nowait_func((M), __FILE__, __LINE__) +/************************************************************************ +NOTE! Use the corresponding macro in the header file, not this function +directly. Tries to lock the mutex for the current thread. If the lock is not +acquired immediately, returns with return value 1. */ +UNIV_INTERN +ulint +mutex_enter_nowait_func( +/*====================*/ + /* out: 0 if succeed, 1 if not */ + mutex_t* mutex, /* in: pointer to mutex */ + const char* file_name, /* in: file name where mutex + requested */ + ulint line); /* in: line where requested */ +/********************************************************************** +Unlocks a mutex owned by the current thread. */ +UNIV_INLINE +void +mutex_exit( +/*=======*/ + mutex_t* mutex); /* in: pointer to mutex */ +/********************************************************************** +Returns TRUE if no mutex or rw-lock is currently locked. +Works only in the debug version. */ +UNIV_INTERN +ibool +sync_all_freed(void); +/*================*/ +/*##################################################################### +FUNCTION PROTOTYPES FOR DEBUGGING */ +/*********************************************************************** +Prints wait info of the sync system. */ +UNIV_INTERN +void +sync_print_wait_info( +/*=================*/ + FILE* file); /* in: file where to print */ +/*********************************************************************** +Prints info of the sync system. */ +UNIV_INTERN +void +sync_print( +/*=======*/ + FILE* file); /* in: file where to print */ +#ifdef UNIV_DEBUG +/********************************************************************** +Checks that the mutex has been initialized. */ +UNIV_INTERN +ibool +mutex_validate( +/*===========*/ + const mutex_t* mutex); +/********************************************************************** +Checks that the current thread owns the mutex. Works only +in the debug version. */ +UNIV_INTERN +ibool +mutex_own( +/*======*/ + /* out: TRUE if owns */ + const mutex_t* mutex); /* in: mutex */ +#endif /* UNIV_DEBUG */ +#ifdef UNIV_SYNC_DEBUG +/********************************************************************** +Adds a latch and its level in the thread level array. Allocates the memory +for the array if called first time for this OS thread. Makes the checks +against other latch levels stored in the array for this thread. */ +UNIV_INTERN +void +sync_thread_add_level( +/*==================*/ + void* latch, /* in: pointer to a mutex or an rw-lock */ + ulint level); /* in: level in the latching order; if + SYNC_LEVEL_VARYING, nothing is done */ +/********************************************************************** +Removes a latch from the thread level array if it is found there. */ +UNIV_INTERN +ibool +sync_thread_reset_level( +/*====================*/ + /* out: TRUE if found from the array; it is no error + if the latch is not found, as we presently are not + able to determine the level for every latch + reservation the program does */ + void* latch); /* in: pointer to a mutex or an rw-lock */ +/********************************************************************** +Checks that the level array for the current thread is empty. */ +UNIV_INTERN +ibool +sync_thread_levels_empty(void); +/*==========================*/ + /* out: TRUE if empty */ +/********************************************************************** +Checks that the level array for the current thread is empty. */ +UNIV_INTERN +ibool +sync_thread_levels_empty_gen( +/*=========================*/ + /* out: TRUE if empty except the + exceptions specified below */ + ibool dict_mutex_allowed); /* in: TRUE if dictionary mutex is + allowed to be owned by the thread, + also purge_is_running mutex is + allowed */ +/********************************************************************** +Gets the debug information for a reserved mutex. */ +UNIV_INTERN +void +mutex_get_debug_info( +/*=================*/ + mutex_t* mutex, /* in: mutex */ + const char** file_name, /* out: file where requested */ + ulint* line, /* out: line where requested */ + os_thread_id_t* thread_id); /* out: id of the thread which owns + the mutex */ +/********************************************************************** +Counts currently reserved mutexes. Works only in the debug version. */ +UNIV_INTERN +ulint +mutex_n_reserved(void); +/*==================*/ +#endif /* UNIV_SYNC_DEBUG */ +/********************************************************************** +NOT to be used outside this module except in debugging! Gets the value +of the lock word. */ +UNIV_INLINE +byte +mutex_get_lock_word( +/*================*/ + const mutex_t* mutex); /* in: mutex */ +#ifdef UNIV_SYNC_DEBUG +/********************************************************************** +NOT to be used outside this module except in debugging! Gets the waiters +field in a mutex. */ +UNIV_INLINE +ulint +mutex_get_waiters( +/*==============*/ + /* out: value to set */ + const mutex_t* mutex); /* in: mutex */ +#endif /* UNIV_SYNC_DEBUG */ + +/* + LATCHING ORDER WITHIN THE DATABASE + ================================== + +The mutex or latch in the central memory object, for instance, a rollback +segment object, must be acquired before acquiring the latch or latches to +the corresponding file data structure. In the latching order below, these +file page object latches are placed immediately below the corresponding +central memory object latch or mutex. + +Synchronization object Notes +---------------------- ----- + +Dictionary mutex If we have a pointer to a dictionary +| object, e.g., a table, it can be +| accessed without reserving the +| dictionary mutex. We must have a +| reservation, a memoryfix, to the +| appropriate table object in this case, +| and the table must be explicitly +| released later. +V +Dictionary header +| +V +Secondary index tree latch The tree latch protects also all +| the B-tree non-leaf pages. These +V can be read with the page only +Secondary index non-leaf bufferfixed to save CPU time, +| no s-latch is needed on the page. +| Modification of a page requires an +| x-latch on the page, however. If a +| thread owns an x-latch to the tree, +| it is allowed to latch non-leaf pages +| even after it has acquired the fsp +| latch. +V +Secondary index leaf The latch on the secondary index leaf +| can be kept while accessing the +| clustered index, to save CPU time. +V +Clustered index tree latch To increase concurrency, the tree +| latch is usually released when the +| leaf page latch has been acquired. +V +Clustered index non-leaf +| +V +Clustered index leaf +| +V +Transaction system header +| +V +Transaction undo mutex The undo log entry must be written +| before any index page is modified. +| Transaction undo mutex is for the undo +| logs the analogue of the tree latch +| for a B-tree. If a thread has the +| trx undo mutex reserved, it is allowed +| to latch the undo log pages in any +| order, and also after it has acquired +| the fsp latch. +V +Rollback segment mutex The rollback segment mutex must be +| reserved, if, e.g., a new page must +| be added to an undo log. The rollback +| segment and the undo logs in its +| history list can be seen as an +| analogue of a B-tree, and the latches +| reserved similarly, using a version of +| lock-coupling. If an undo log must be +| extended by a page when inserting an +| undo log record, this corresponds to +| a pessimistic insert in a B-tree. +V +Rollback segment header +| +V +Purge system latch +| +V +Undo log pages If a thread owns the trx undo mutex, +| or for a log in the history list, the +| rseg mutex, it is allowed to latch +| undo log pages in any order, and even +| after it has acquired the fsp latch. +| If a thread does not have the +| appropriate mutex, it is allowed to +| latch only a single undo log page in +| a mini-transaction. +V +File space management latch If a mini-transaction must allocate +| several file pages, it can do that, +| because it keeps the x-latch to the +| file space management in its memo. +V +File system pages +| +V +Kernel mutex If a kernel operation needs a file +| page allocation, it must reserve the +| fsp x-latch before acquiring the kernel +| mutex. +V +Search system mutex +| +V +Buffer pool mutex +| +V +Log mutex +| +Any other latch +| +V +Memory pool mutex */ + +/* Latching order levels */ + +/* User transaction locks are higher than any of the latch levels below: +no latches are allowed when a thread goes to wait for a normal table +or row lock! */ +#define SYNC_USER_TRX_LOCK 9999 +#define SYNC_NO_ORDER_CHECK 3000 /* this can be used to suppress + latching order checking */ +#define SYNC_LEVEL_VARYING 2000 /* Level is varying. Only used with + buffer pool page locks, which do not + have a fixed level, but instead have + their level set after the page is + locked; see e.g. + ibuf_bitmap_get_map_page(). */ +#define SYNC_TRX_I_S_RWLOCK 1910 /* Used for + trx_i_s_cache_t::rw_lock */ +#define SYNC_TRX_I_S_LAST_READ 1900 /* Used for + trx_i_s_cache_t::last_read_mutex */ +#define SYNC_FILE_FORMAT_TAG 1200 /* Used to serialize access to the + file format tag */ +#define SYNC_DICT_OPERATION 1001 /* table create, drop, etc. reserve + this in X-mode, implicit or backround + operations purge, rollback, foreign + key checks reserve this in S-mode */ +#define SYNC_DICT 1000 +#define SYNC_DICT_AUTOINC_MUTEX 999 +#define SYNC_DICT_HEADER 995 +#define SYNC_IBUF_HEADER 914 +#define SYNC_IBUF_PESS_INSERT_MUTEX 912 +#define SYNC_IBUF_MUTEX 910 /* ibuf mutex is really below + SYNC_FSP_PAGE: we assign a value this + high only to make the program to pass + the debug checks */ +/*-------------------------------*/ +#define SYNC_INDEX_TREE 900 +#define SYNC_TREE_NODE_NEW 892 +#define SYNC_TREE_NODE_FROM_HASH 891 +#define SYNC_TREE_NODE 890 +#define SYNC_PURGE_SYS 810 +#define SYNC_PURGE_LATCH 800 +#define SYNC_TRX_UNDO 700 +#define SYNC_RSEG 600 +#define SYNC_RSEG_HEADER_NEW 591 +#define SYNC_RSEG_HEADER 590 +#define SYNC_TRX_UNDO_PAGE 570 +#define SYNC_EXTERN_STORAGE 500 +#define SYNC_FSP 400 +#define SYNC_FSP_PAGE 395 +/*------------------------------------- Insert buffer headers */ +/*------------------------------------- ibuf_mutex */ +/*------------------------------------- Insert buffer tree */ +#define SYNC_IBUF_BITMAP_MUTEX 351 +#define SYNC_IBUF_BITMAP 350 +/*------------------------------------- MySQL query cache mutex */ +/*------------------------------------- MySQL binlog mutex */ +/*-------------------------------*/ +#define SYNC_KERNEL 300 +#define SYNC_REC_LOCK 299 +#define SYNC_TRX_LOCK_HEAP 298 +#define SYNC_TRX_SYS_HEADER 290 +#define SYNC_LOG 170 +#define SYNC_RECV 168 +#define SYNC_WORK_QUEUE 162 +#define SYNC_SEARCH_SYS_CONF 161 /* for assigning btr_search_enabled */ +#define SYNC_SEARCH_SYS 160 /* NOTE that if we have a memory + heap that can be extended to the + buffer pool, its logical level is + SYNC_SEARCH_SYS, as memory allocation + can call routines there! Otherwise + the level is SYNC_MEM_HASH. */ +#define SYNC_BUF_LRU_LIST 157 +#define SYNC_BUF_PAGE_HASH 156 +#define SYNC_BUF_BLOCK 155 +#define SYNC_BUF_FREE_LIST 153 +#define SYNC_BUF_ZIP_FREE 152 +#define SYNC_BUF_ZIP_HASH 151 +#define SYNC_BUF_POOL 150 +#define SYNC_BUF_FLUSH_LIST 149 +#define SYNC_DOUBLEWRITE 140 +#define SYNC_ANY_LATCH 135 +#define SYNC_THR_LOCAL 133 +#define SYNC_MEM_HASH 131 +#define SYNC_MEM_POOL 130 + +/* Codes used to designate lock operations */ +#define RW_LOCK_NOT_LOCKED 350 +#define RW_LOCK_EX 351 +#define RW_LOCK_EXCLUSIVE 351 +#define RW_LOCK_SHARED 352 +#define RW_LOCK_WAIT_EX 353 +#define SYNC_MUTEX 354 + +/* NOTE! The structure appears here only for the compiler to know its size. +Do not use its fields directly! The structure used in the spin lock +implementation of a mutual exclusion semaphore. */ + +struct mutex_struct { + os_event_t event; /* Used by sync0arr.c for the wait queue */ + byte lock_word; /* This byte is the target of the atomic + test-and-set instruction in Win32 and + x86 32/64 with GCC 4.1.0 or later version */ +#if defined(_WIN32) && defined(UNIV_CAN_USE_X86_ASSEMBLER) +#elif defined(HAVE_GCC_ATOMIC_BUILTINS) +#else + os_fast_mutex_t + os_fast_mutex; /* In other systems we use this OS mutex + in place of lock_word */ +#endif + ulint waiters; /* This ulint is set to 1 if there are (or + may be) threads waiting in the global wait + array for this mutex to be released. + Otherwise, this is 0. */ + UT_LIST_NODE_T(mutex_t) list; /* All allocated mutexes are put into + a list. Pointers to the next and prev. */ +#ifdef UNIV_SYNC_DEBUG + const char* file_name; /* File where the mutex was locked */ + ulint line; /* Line where the mutex was locked */ + ulint level; /* Level in the global latching order */ +#endif /* UNIV_SYNC_DEBUG */ + const char* cfile_name;/* File name where mutex created */ + ulint cline; /* Line where created */ +#ifdef UNIV_DEBUG + os_thread_id_t thread_id; /* The thread id of the thread + which locked the mutex. */ + ulint magic_n; +# define MUTEX_MAGIC_N (ulint)979585 +#endif /* UNIV_DEBUG */ +#ifndef UNIV_HOTBACKUP + ulong count_os_wait; /* count of os_wait */ +# ifdef UNIV_DEBUG + ulong count_using; /* count of times mutex used */ + ulong count_spin_loop; /* count of spin loops */ + ulong count_spin_rounds; /* count of spin rounds */ + ulong count_os_yield; /* count of os_wait */ + ulonglong lspent_time; /* mutex os_wait timer msec */ + ulonglong lmax_spent_time; /* mutex os_wait timer msec */ + const char* cmutex_name;/* mutex name */ + ulint mutex_type;/* 0 - usual mutex 1 - rw_lock mutex */ +# endif /* UNIV_DEBUG */ +#endif /* !UNIV_HOTBACKUP */ +}; + +/* The global array of wait cells for implementation of the databases own +mutexes and read-write locks. Appears here for debugging purposes only! */ + +extern sync_array_t* sync_primary_wait_array; + +/* Constant determining how long spin wait is continued before suspending +the thread. A value 600 rounds on a 1995 100 MHz Pentium seems to correspond +to 20 microseconds. */ + +#define SYNC_SPIN_ROUNDS srv_n_spin_wait_rounds + +/* The number of system calls made in this module. Intended for performance +monitoring. */ + +extern ib_int64_t mutex_exit_count; + +#ifdef UNIV_SYNC_DEBUG +/* Latching order checks start when this is set TRUE */ +extern ibool sync_order_checks_on; +#endif /* UNIV_SYNC_DEBUG */ + +/* This variable is set to TRUE when sync_init is called */ +extern ibool sync_initialized; + +/* Global list of database mutexes (not OS mutexes) created. */ +typedef UT_LIST_BASE_NODE_T(mutex_t) ut_list_base_node_t; +extern ut_list_base_node_t mutex_list; + +/* Mutex protecting the mutex_list variable */ +extern mutex_t mutex_list_mutex; + + +#ifndef UNIV_NONINL +#include "sync0sync.ic" +#endif + +#endif diff --git a/storage/xtradb/include/sync0sync.ic b/storage/xtradb/include/sync0sync.ic new file mode 100644 index 00000000000..c43121ebd0b --- /dev/null +++ b/storage/xtradb/include/sync0sync.ic @@ -0,0 +1,270 @@ +/***************************************************************************** + +Copyright (c) 1995, 2009, Innobase Oy. All Rights Reserved. +Copyright (c) 2008, Google Inc. + +Portions of this file contain modifications contributed and copyrighted by +Google, Inc. Those modifications are gratefully acknowledged and are described +briefly in the InnoDB documentation. The contributions by Google are +incorporated with their permission, and subject to the conditions contained in +the file COPYING.Google. + +This program is free software; you can redistribute it and/or modify it under +the terms of the GNU General Public License as published by the Free Software +Foundation; version 2 of the License. + +This program is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + +You 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 + +*****************************************************************************/ + +/****************************************************** +Mutex, the basic synchronization primitive + +Created 9/5/1995 Heikki Tuuri +*******************************************************/ + +/********************************************************************** +Sets the waiters field in a mutex. */ +UNIV_INTERN +void +mutex_set_waiters( +/*==============*/ + mutex_t* mutex, /* in: mutex */ + ulint n); /* in: value to set */ +/********************************************************************** +Reserves a mutex for the current thread. If the mutex is reserved, the +function spins a preset time (controlled by SYNC_SPIN_ROUNDS) waiting +for the mutex before suspending the thread. */ +UNIV_INTERN +void +mutex_spin_wait( +/*============*/ + mutex_t* mutex, /* in: pointer to mutex */ + const char* file_name, /* in: file name where mutex + requested */ + ulint line); /* in: line where requested */ +#ifdef UNIV_SYNC_DEBUG +/********************************************************************** +Sets the debug information for a reserved mutex. */ +UNIV_INTERN +void +mutex_set_debug_info( +/*=================*/ + mutex_t* mutex, /* in: mutex */ + const char* file_name, /* in: file where requested */ + ulint line); /* in: line where requested */ +#endif /* UNIV_SYNC_DEBUG */ +/********************************************************************** +Releases the threads waiting in the primary wait array for this mutex. */ +UNIV_INTERN +void +mutex_signal_object( +/*================*/ + mutex_t* mutex); /* in: mutex */ + +/********************************************************************** +Performs an atomic test-and-set instruction to the lock_word field of a +mutex. */ +UNIV_INLINE +byte +mutex_test_and_set( +/*===============*/ + /* out: the previous value of lock_word: 0 or + 1 */ + mutex_t* mutex) /* in: mutex */ +{ +#if defined(_WIN32) && defined(UNIV_CAN_USE_X86_ASSEMBLER) + byte res; + byte* lw; /* assembler code is used to ensure that + lock_word is loaded from memory */ + ut_ad(mutex); + ut_ad(sizeof(byte) == 1); + + lw = &(mutex->lock_word); + + __asm MOV ECX, lw + __asm MOV EDX, 1 + __asm XCHG DL, BYTE PTR [ECX] + __asm MOV res, DL + + /* The fence below would prevent this thread from + reading the data structure protected by the mutex + before the test-and-set operation is committed, but + the fence is apparently not needed: + + In a posting to comp.arch newsgroup (August 10, 1997) + Andy Glew said that in P6 a LOCKed instruction like + XCHG establishes a fence with respect to memory reads + and writes and thus an explicit fence is not + needed. In P5 he seemed to agree with a previous + newsgroup poster that LOCKed instructions serialize + all instruction execution, and, consequently, also + memory operations. This is confirmed in Intel Software + Dev. Manual, Vol. 3. */ + + /* mutex_fence(); */ + + return(res); +#elif defined(HAVE_GCC_ATOMIC_BUILTINS) + return __sync_lock_test_and_set(&(mutex->lock_word), 1); +#else + ibool ret; + + ret = os_fast_mutex_trylock(&(mutex->os_fast_mutex)); + + if (ret == 0) { + /* We check that os_fast_mutex_trylock does not leak + and allow race conditions */ + ut_a(mutex->lock_word == 0); + + mutex->lock_word = 1; + } + + return((byte)ret); +#endif +} + +/********************************************************************** +Performs a reset instruction to the lock_word field of a mutex. This +instruction also serializes memory operations to the program order. */ +UNIV_INLINE +void +mutex_reset_lock_word( +/*==================*/ + mutex_t* mutex) /* in: mutex */ +{ +#if defined(_WIN32) && defined(UNIV_CAN_USE_X86_ASSEMBLER) + byte* lw; /* assembler code is used to ensure that + lock_word is loaded from memory */ + ut_ad(mutex); + + lw = &(mutex->lock_word); + + __asm MOV EDX, 0 + __asm MOV ECX, lw + __asm XCHG DL, BYTE PTR [ECX] +#elif defined(HAVE_GCC_ATOMIC_BUILTINS) + /* In theory __sync_lock_release should be used to release the lock. + Unfortunately, it does not work properly alone. The workaround is + that more conservative __sync_lock_test_and_set is used instead. */ + __sync_lock_test_and_set(&(mutex->lock_word), 0); +#else + mutex->lock_word = 0; + + os_fast_mutex_unlock(&(mutex->os_fast_mutex)); +#endif +} + +/********************************************************************** +Gets the value of the lock word. */ +UNIV_INLINE +byte +mutex_get_lock_word( +/*================*/ + const mutex_t* mutex) /* in: mutex */ +{ + const volatile byte* ptr; /* declared volatile to ensure that + lock_word is loaded from memory */ + ut_ad(mutex); + + ptr = &(mutex->lock_word); + + return(*ptr); +} + +/********************************************************************** +Gets the waiters field in a mutex. */ +UNIV_INLINE +ulint +mutex_get_waiters( +/*==============*/ + /* out: value to set */ + const mutex_t* mutex) /* in: mutex */ +{ + const volatile ulint* ptr; /* declared volatile to ensure that + the value is read from memory */ + ut_ad(mutex); + + ptr = &(mutex->waiters); + + return(*ptr); /* Here we assume that the read of a single + word from memory is atomic */ +} + +/********************************************************************** +Unlocks a mutex owned by the current thread. */ +UNIV_INLINE +void +mutex_exit( +/*=======*/ + mutex_t* mutex) /* in: pointer to mutex */ +{ + ut_ad(mutex_own(mutex)); + + ut_d(mutex->thread_id = (os_thread_id_t) ULINT_UNDEFINED); + +#ifdef UNIV_SYNC_DEBUG + sync_thread_reset_level(mutex); +#endif + mutex_reset_lock_word(mutex); + + /* A problem: we assume that mutex_reset_lock word + is a memory barrier, that is when we read the waiters + field next, the read must be serialized in memory + after the reset. A speculative processor might + perform the read first, which could leave a waiting + thread hanging indefinitely. + + Our current solution call every second + sync_arr_wake_threads_if_sema_free() + to wake up possible hanging threads if + they are missed in mutex_signal_object. */ + + if (mutex_get_waiters(mutex) != 0) { + + mutex_signal_object(mutex); + } + +#ifdef UNIV_SYNC_PERF_STAT + mutex_exit_count++; +#endif +} + +/********************************************************************** +Locks a mutex for the current thread. If the mutex is reserved, the function +spins a preset time (controlled by SYNC_SPIN_ROUNDS), waiting for the mutex +before suspending the thread. */ +UNIV_INLINE +void +mutex_enter_func( +/*=============*/ + mutex_t* mutex, /* in: pointer to mutex */ + const char* file_name, /* in: file name where locked */ + ulint line) /* in: line where locked */ +{ + ut_ad(mutex_validate(mutex)); + ut_ad(!mutex_own(mutex)); + + /* Note that we do not peek at the value of lock_word before trying + the atomic test_and_set; we could peek, and possibly save time. */ + +#if defined UNIV_DEBUG && !defined UNIV_HOTBACKUP + mutex->count_using++; +#endif /* UNIV_DEBUG && !UNIV_HOTBACKUP */ + + if (!mutex_test_and_set(mutex)) { + ut_d(mutex->thread_id = os_thread_get_curr_id()); +#ifdef UNIV_SYNC_DEBUG + mutex_set_debug_info(mutex, file_name, line); +#endif + return; /* Succeeded! */ + } + + mutex_spin_wait(mutex, file_name, line); +} diff --git a/storage/xtradb/include/sync0types.h b/storage/xtradb/include/sync0types.h new file mode 100644 index 00000000000..3c1021b1a30 --- /dev/null +++ b/storage/xtradb/include/sync0types.h @@ -0,0 +1,31 @@ +/***************************************************************************** + +Copyright (c) 1995, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/****************************************************** +Global types for sync + +Created 9/5/1995 Heikki Tuuri +*******************************************************/ + +#ifndef sync0types_h +#define sync0types_h + +#define mutex_t ib_mutex_t +typedef struct mutex_struct mutex_t; + +#endif diff --git a/storage/xtradb/include/thr0loc.h b/storage/xtradb/include/thr0loc.h new file mode 100644 index 00000000000..96ec13cc8e4 --- /dev/null +++ b/storage/xtradb/include/thr0loc.h @@ -0,0 +1,94 @@ +/***************************************************************************** + +Copyright (c) 1995, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/****************************************************** +The thread local storage + +Created 10/5/1995 Heikki Tuuri +*******************************************************/ + +/* This module implements storage private to each thread, +a capability useful in some situations like storing the +OS handle to the current thread, or its priority. */ + +#ifndef thr0loc_h +#define thr0loc_h + +#include "univ.i" +#include "os0thread.h" + +/******************************************************************** +Initializes the thread local storage module. */ +UNIV_INTERN +void +thr_local_init(void); +/*================*/ +/*********************************************************************** +Creates a local storage struct for the calling new thread. */ +UNIV_INTERN +void +thr_local_create(void); +/*==================*/ +/*********************************************************************** +Frees the local storage struct for the specified thread. */ +UNIV_INTERN +void +thr_local_free( +/*===========*/ + os_thread_id_t id); /* in: thread id */ +/*********************************************************************** +Gets the slot number in the thread table of a thread. */ +UNIV_INTERN +ulint +thr_local_get_slot_no( +/*==================*/ + /* out: slot number */ + os_thread_id_t id); /* in: thread id of the thread */ +/*********************************************************************** +Sets in the local storage the slot number in the thread table of a thread. */ +UNIV_INTERN +void +thr_local_set_slot_no( +/*==================*/ + os_thread_id_t id, /* in: thread id of the thread */ + ulint slot_no);/* in: slot number */ +/*********************************************************************** +Returns pointer to the 'in_ibuf' field within the current thread local +storage. */ +UNIV_INTERN +ibool* +thr_local_get_in_ibuf_field(void); +/*=============================*/ + /* out: pointer to the in_ibuf field */ + +/************************************************************************* +Return local hash table informations. */ + +ulint +thr_local_hash_cells(void); +/*=======================*/ + +ulint +thr_local_hash_nodes(void); +/*=======================*/ + +#ifndef UNIV_NONINL +#include "thr0loc.ic" +#endif + +#endif diff --git a/storage/xtradb/include/thr0loc.ic b/storage/xtradb/include/thr0loc.ic new file mode 100644 index 00000000000..6de183fd857 --- /dev/null +++ b/storage/xtradb/include/thr0loc.ic @@ -0,0 +1,23 @@ +/***************************************************************************** + +Copyright (c) 1995, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/****************************************************** +Thread local storage + +Created 10/4/1995 Heikki Tuuri +*******************************************************/ diff --git a/storage/xtradb/include/trx0i_s.h b/storage/xtradb/include/trx0i_s.h new file mode 100644 index 00000000000..cf2865af127 --- /dev/null +++ b/storage/xtradb/include/trx0i_s.h @@ -0,0 +1,212 @@ +/***************************************************************************** + +Copyright (c) 2007, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/****************************************************** +INFORMATION SCHEMA innodb_trx, innodb_locks and +innodb_lock_waits tables cache structures and public +functions. + +Created July 17, 2007 Vasil Dimov +*******************************************************/ + +#ifndef trx0i_s_h +#define trx0i_s_h + +#include "univ.i" +#include "trx0types.h" +#include "ut0ut.h" + +/* the maximum amount of memory that can be consumed by innodb_trx, +innodb_locks and innodb_lock_waits information schema tables. */ +#define TRX_I_S_MEM_LIMIT 16777216 /* 16 MiB */ + +/* the maximum length of a string that can be stored in +i_s_locks_row_t::lock_data */ +#define TRX_I_S_LOCK_DATA_MAX_LEN 8192 + +/* the maximum length of a string that can be stored in +i_s_trx_row_t::trx_query */ +#define TRX_I_S_TRX_QUERY_MAX_LEN 1024 + +typedef struct i_s_locks_row_struct i_s_locks_row_t; +typedef struct i_s_hash_chain_struct i_s_hash_chain_t; + +/* Objects of this type are added to the hash table +trx_i_s_cache_t::locks_hash */ +struct i_s_hash_chain_struct { + i_s_locks_row_t* value; + i_s_hash_chain_t* next; +}; + +/* This structure represents INFORMATION_SCHEMA.innodb_locks row */ +struct i_s_locks_row_struct { + ullint lock_trx_id; + const char* lock_mode; + const char* lock_type; + const char* lock_table; + const char* lock_index; + ulint lock_space; + ulint lock_page; + ulint lock_rec; + const char* lock_data; + + /* The following are auxiliary and not included in the table */ + ullint lock_table_id; + i_s_hash_chain_t hash_chain; /* this object is added to the hash + table + trx_i_s_cache_t::locks_hash */ +}; + +/* This structure represents INFORMATION_SCHEMA.innodb_trx row */ +typedef struct i_s_trx_row_struct { + ullint trx_id; + const char* trx_state; + ib_time_t trx_started; + const i_s_locks_row_t* requested_lock_row; + ib_time_t trx_wait_started; + ullint trx_weight; + ulint trx_mysql_thread_id; + const char* trx_query; +} i_s_trx_row_t; + +/* This structure represents INFORMATION_SCHEMA.innodb_lock_waits row */ +typedef struct i_s_lock_waits_row_struct { + const i_s_locks_row_t* requested_lock_row; + const i_s_locks_row_t* blocking_lock_row; +} i_s_lock_waits_row_t; + +/* This type is opaque and is defined in trx/trx0i_s.c */ +typedef struct trx_i_s_cache_struct trx_i_s_cache_t; + +/* Auxiliary enum used by functions that need to select one of the +INFORMATION_SCHEMA tables */ +enum i_s_table { + I_S_INNODB_TRX, + I_S_INNODB_LOCKS, + I_S_INNODB_LOCK_WAITS +}; + +/* This is the intermediate buffer where data needed to fill the +INFORMATION SCHEMA tables is fetched and later retrieved by the C++ +code in handler/i_s.cc. */ +extern trx_i_s_cache_t* trx_i_s_cache; + +/*********************************************************************** +Initialize INFORMATION SCHEMA trx related cache. */ +UNIV_INTERN +void +trx_i_s_cache_init( +/*===============*/ + trx_i_s_cache_t* cache); /* out: cache to init */ + +/*********************************************************************** +Issue a shared/read lock on the tables cache. */ +UNIV_INTERN +void +trx_i_s_cache_start_read( +/*=====================*/ + trx_i_s_cache_t* cache); /* in: cache */ + +/*********************************************************************** +Release a shared/read lock on the tables cache. */ +UNIV_INTERN +void +trx_i_s_cache_end_read( +/*===================*/ + trx_i_s_cache_t* cache); /* in: cache */ + +/*********************************************************************** +Issue an exclusive/write lock on the tables cache. */ +UNIV_INTERN +void +trx_i_s_cache_start_write( +/*======================*/ + trx_i_s_cache_t* cache); /* in: cache */ + +/*********************************************************************** +Release an exclusive/write lock on the tables cache. */ +UNIV_INTERN +void +trx_i_s_cache_end_write( +/*====================*/ + trx_i_s_cache_t* cache); /* in: cache */ + + +/*********************************************************************** +Retrieves the number of used rows in the cache for a given +INFORMATION SCHEMA table. */ +UNIV_INTERN +ulint +trx_i_s_cache_get_rows_used( +/*========================*/ + /* out: number of rows */ + trx_i_s_cache_t* cache, /* in: cache */ + enum i_s_table table); /* in: which table */ + +/*********************************************************************** +Retrieves the nth row in the cache for a given INFORMATION SCHEMA +table. */ +UNIV_INTERN +void* +trx_i_s_cache_get_nth_row( +/*======================*/ + /* out: row */ + trx_i_s_cache_t* cache, /* in: cache */ + enum i_s_table table, /* in: which table */ + ulint n); /* in: row number */ + +/*********************************************************************** +Update the transactions cache if it has not been read for some time. */ +UNIV_INTERN +int +trx_i_s_possibly_fetch_data_into_cache( +/*===================================*/ + /* out: 0 - fetched, 1 - not */ + trx_i_s_cache_t* cache); /* in/out: cache */ + +/*********************************************************************** +Returns TRUE if the data in the cache is truncated due to the memory +limit posed by TRX_I_S_MEM_LIMIT. */ +UNIV_INTERN +ibool +trx_i_s_cache_is_truncated( +/*=======================*/ + /* out: TRUE if truncated */ + trx_i_s_cache_t* cache); /* in: cache */ + +/* The maximum length of a resulting lock_id_size in +trx_i_s_create_lock_id(), not including the terminating '\0'. +":%lu:%lu:%lu" -> 63 chars */ +#define TRX_I_S_LOCK_ID_MAX_LEN (TRX_ID_MAX_LEN + 63) + +/*********************************************************************** +Crafts a lock id string from a i_s_locks_row_t object. Returns its +second argument. This function aborts if there is not enough space in +lock_id. Be sure to provide at least TRX_I_S_LOCK_ID_MAX_LEN + 1 if you +want to be 100% sure that it will not abort. */ +UNIV_INTERN +char* +trx_i_s_create_lock_id( +/*===================*/ + /* out: resulting lock id */ + const i_s_locks_row_t* row, /* in: innodb_locks row */ + char* lock_id,/* out: resulting lock_id */ + ulint lock_id_size);/* in: size of the lock id + buffer */ + +#endif /* trx0i_s_h */ diff --git a/storage/xtradb/include/trx0purge.h b/storage/xtradb/include/trx0purge.h new file mode 100644 index 00000000000..4921b860485 --- /dev/null +++ b/storage/xtradb/include/trx0purge.h @@ -0,0 +1,185 @@ +/***************************************************************************** + +Copyright (c) 1996, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/****************************************************** +Purge old versions + +Created 3/26/1996 Heikki Tuuri +*******************************************************/ + +#ifndef trx0purge_h +#define trx0purge_h + +#include "univ.i" +#include "trx0types.h" +#include "mtr0mtr.h" +#include "trx0sys.h" +#include "que0types.h" +#include "page0page.h" +#include "usr0sess.h" +#include "fil0fil.h" + +/* The global data structure coordinating a purge */ +extern trx_purge_t* purge_sys; + +/* A dummy undo record used as a return value when we have a whole undo log +which needs no purge */ +extern trx_undo_rec_t trx_purge_dummy_rec; + +/************************************************************************ +Calculates the file address of an undo log header when we have the file +address of its history list node. */ +UNIV_INLINE +fil_addr_t +trx_purge_get_log_from_hist( +/*========================*/ + /* out: file address of the log */ + fil_addr_t node_addr); /* in: file address of the history + list node of the log */ +/********************************************************************* +Checks if trx_id is >= purge_view: then it is guaranteed that its update +undo log still exists in the system. */ +UNIV_INTERN +ibool +trx_purge_update_undo_must_exist( +/*=============================*/ + /* out: TRUE if is sure that it is preserved, also + if the function returns FALSE, it is possible that + the undo log still exists in the system */ + dulint trx_id);/* in: transaction id */ +/************************************************************************ +Creates the global purge system control structure and inits the history +mutex. */ +UNIV_INTERN +void +trx_purge_sys_create(void); +/*======================*/ +/************************************************************************ +Adds the update undo log as the first log in the history list. Removes the +update undo log segment from the rseg slot if it is too big for reuse. */ +UNIV_INTERN +void +trx_purge_add_update_undo_to_history( +/*=================================*/ + trx_t* trx, /* in: transaction */ + page_t* undo_page, /* in: update undo log header page, + x-latched */ + mtr_t* mtr); /* in: mtr */ +/************************************************************************ +Fetches the next undo log record from the history list to purge. It must be +released with the corresponding release function. */ +UNIV_INTERN +trx_undo_rec_t* +trx_purge_fetch_next_rec( +/*=====================*/ + /* out: copy of an undo log record, or + pointer to the dummy undo log record + &trx_purge_dummy_rec if the whole undo log + can skipped in purge; NULL if none left */ + dulint* roll_ptr,/* out: roll pointer to undo record */ + trx_undo_inf_t** cell, /* out: storage cell for the record in the + purge array */ + mem_heap_t* heap); /* in: memory heap where copied */ +/*********************************************************************** +Releases a reserved purge undo record. */ +UNIV_INTERN +void +trx_purge_rec_release( +/*==================*/ + trx_undo_inf_t* cell); /* in: storage cell */ +/*********************************************************************** +This function runs a purge batch. */ +UNIV_INTERN +ulint +trx_purge(void); +/*===========*/ + /* out: number of undo log pages handled in + the batch */ +/********************************************************************** +Prints information of the purge system to stderr. */ +UNIV_INTERN +void +trx_purge_sys_print(void); +/*======================*/ + +/* The control structure used in the purge operation */ +struct trx_purge_struct{ + ulint state; /* Purge system state */ + sess_t* sess; /* System session running the purge + query */ + trx_t* trx; /* System transaction running the purge + query: this trx is not in the trx list + of the trx system and it never ends */ + que_t* query; /* The query graph which will do the + parallelized purge operation */ + rw_lock_t latch; /* The latch protecting the purge view. + A purge operation must acquire an + x-latch here for the instant at which + it changes the purge view: an undo + log operation can prevent this by + obtaining an s-latch here. */ + read_view_t* view; /* The purge will not remove undo logs + which are >= this view (purge view) */ + mutex_t mutex; /* Mutex protecting the fields below */ + ulint n_pages_handled;/* Approximate number of undo log + pages processed in purge */ + ulint handle_limit; /* Target of how many pages to get + processed in the current purge */ + /*------------------------------*/ + /* The following two fields form the 'purge pointer' which advances + during a purge, and which is used in history list truncation */ + + dulint purge_trx_no; /* Purge has advanced past all + transactions whose number is less + than this */ + dulint purge_undo_no; /* Purge has advanced past all records + whose undo number is less than this */ + /*-----------------------------*/ + ibool next_stored; /* TRUE if the info of the next record + to purge is stored below: if yes, then + the transaction number and the undo + number of the record are stored in + purge_trx_no and purge_undo_no above */ + trx_rseg_t* rseg; /* Rollback segment for the next undo + record to purge */ + ulint page_no; /* Page number for the next undo + record to purge, page number of the + log header, if dummy record */ + ulint offset; /* Page offset for the next undo + record to purge, 0 if the dummy + record */ + ulint hdr_page_no; /* Header page of the undo log where + the next record to purge belongs */ + ulint hdr_offset; /* Header byte offset on the page */ + /*-----------------------------*/ + trx_undo_arr_t* arr; /* Array of transaction numbers and + undo numbers of the undo records + currently under processing in purge */ + mem_heap_t* heap; /* Temporary storage used during a + purge: can be emptied after purge + completes */ +}; + +#define TRX_PURGE_ON 1 /* purge operation is running */ +#define TRX_STOP_PURGE 2 /* purge operation is stopped, or + it should be stopped */ +#ifndef UNIV_NONINL +#include "trx0purge.ic" +#endif + +#endif diff --git a/storage/xtradb/include/trx0purge.ic b/storage/xtradb/include/trx0purge.ic new file mode 100644 index 00000000000..2c1d2ac75af --- /dev/null +++ b/storage/xtradb/include/trx0purge.ic @@ -0,0 +1,42 @@ +/***************************************************************************** + +Copyright (c) 1996, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/****************************************************** +Purge old versions + +Created 3/26/1996 Heikki Tuuri +*******************************************************/ + +#include "trx0undo.h" + +/************************************************************************ +Calculates the file address of an undo log header when we have the file +address of its history list node. */ +UNIV_INLINE +fil_addr_t +trx_purge_get_log_from_hist( +/*========================*/ + /* out: file address of the log */ + fil_addr_t node_addr) /* in: file address of the history + list node of the log */ +{ + node_addr.boffset -= TRX_UNDO_HISTORY_NODE; + + return(node_addr); +} + diff --git a/storage/xtradb/include/trx0rec.h b/storage/xtradb/include/trx0rec.h new file mode 100644 index 00000000000..444d39e39db --- /dev/null +++ b/storage/xtradb/include/trx0rec.h @@ -0,0 +1,333 @@ +/***************************************************************************** + +Copyright (c) 1996, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/****************************************************** +Transaction undo log record + +Created 3/26/1996 Heikki Tuuri +*******************************************************/ + +#ifndef trx0rec_h +#define trx0rec_h + +#include "univ.i" +#include "trx0types.h" +#include "row0types.h" +#include "mtr0mtr.h" +#include "dict0types.h" +#include "que0types.h" +#include "data0data.h" +#include "rem0types.h" + +/*************************************************************************** +Copies the undo record to the heap. */ +UNIV_INLINE +trx_undo_rec_t* +trx_undo_rec_copy( +/*==============*/ + /* out, own: copy of undo log record */ + trx_undo_rec_t* undo_rec, /* in: undo log record */ + mem_heap_t* heap); /* in: heap where copied */ +/************************************************************************** +Reads the undo log record type. */ +UNIV_INLINE +ulint +trx_undo_rec_get_type( +/*==================*/ + /* out: record type */ + trx_undo_rec_t* undo_rec); /* in: undo log record */ +/************************************************************************** +Reads from an undo log record the record compiler info. */ +UNIV_INLINE +ulint +trx_undo_rec_get_cmpl_info( +/*=======================*/ + /* out: compiler info */ + trx_undo_rec_t* undo_rec); /* in: undo log record */ +/************************************************************************** +Returns TRUE if an undo log record contains an extern storage field. */ +UNIV_INLINE +ibool +trx_undo_rec_get_extern_storage( +/*============================*/ + /* out: TRUE if extern */ + trx_undo_rec_t* undo_rec); /* in: undo log record */ +/************************************************************************** +Reads the undo log record number. */ +UNIV_INLINE +dulint +trx_undo_rec_get_undo_no( +/*=====================*/ + /* out: undo no */ + trx_undo_rec_t* undo_rec); /* in: undo log record */ +/************************************************************************** + * Returns the start of the undo record data area. */ + +UNIV_INLINE +byte* +trx_undo_rec_get_ptr( +/*==================*/ + /* out: compiler info */ + trx_undo_rec_t* undo_rec, /* in: undo log record */ + dulint undo_no); /* in: undo no read from node */ + +/************************************************************************** +Reads from an undo log record the general parameters. */ +UNIV_INTERN +byte* +trx_undo_rec_get_pars( +/*==================*/ + /* out: remaining part of undo log + record after reading these values */ + trx_undo_rec_t* undo_rec, /* in: undo log record */ + ulint* type, /* out: undo record type: + TRX_UNDO_INSERT_REC, ... */ + ulint* cmpl_info, /* out: compiler info, relevant only + for update type records */ + ibool* updated_extern, /* out: TRUE if we updated an + externally stored fild */ + dulint* undo_no, /* out: undo log record number */ + dulint* table_id); /* out: table id */ +/*********************************************************************** +Builds a row reference from an undo log record. */ +UNIV_INTERN +byte* +trx_undo_rec_get_row_ref( +/*=====================*/ + /* out: pointer to remaining part of undo + record */ + byte* ptr, /* in: remaining part of a copy of an undo log + record, at the start of the row reference; + NOTE that this copy of the undo log record must + be preserved as long as the row reference is + used, as we do NOT copy the data in the + record! */ + dict_index_t* index, /* in: clustered index */ + dtuple_t** ref, /* out, own: row reference */ + mem_heap_t* heap); /* in: memory heap from which the memory + needed is allocated */ +/*********************************************************************** +Skips a row reference from an undo log record. */ +UNIV_INTERN +byte* +trx_undo_rec_skip_row_ref( +/*======================*/ + /* out: pointer to remaining part of undo + record */ + byte* ptr, /* in: remaining part in update undo log + record, at the start of the row reference */ + dict_index_t* index); /* in: clustered index */ +/************************************************************************** +Reads from an undo log update record the system field values of the old +version. */ +UNIV_INTERN +byte* +trx_undo_update_rec_get_sys_cols( +/*=============================*/ + /* out: remaining part of undo log + record after reading these values */ + byte* ptr, /* in: remaining part of undo log + record after reading general + parameters */ + dulint* trx_id, /* out: trx id */ + dulint* roll_ptr, /* out: roll ptr */ + ulint* info_bits); /* out: info bits state */ +/*********************************************************************** +Builds an update vector based on a remaining part of an undo log record. */ +UNIV_INTERN +byte* +trx_undo_update_rec_get_update( +/*===========================*/ + /* out: remaining part of the record, + NULL if an error detected, which means that + the record is corrupted */ + byte* ptr, /* in: remaining part in update undo log + record, after reading the row reference + NOTE that this copy of the undo log record must + be preserved as long as the update vector is + used, as we do NOT copy the data in the + record! */ + dict_index_t* index, /* in: clustered index */ + ulint type, /* in: TRX_UNDO_UPD_EXIST_REC, + TRX_UNDO_UPD_DEL_REC, or + TRX_UNDO_DEL_MARK_REC; in the last case, + only trx id and roll ptr fields are added to + the update vector */ + dulint trx_id, /* in: transaction id from this undorecord */ + dulint roll_ptr,/* in: roll pointer from this undo record */ + ulint info_bits,/* in: info bits from this undo record */ + trx_t* trx, /* in: transaction */ + mem_heap_t* heap, /* in: memory heap from which the memory + needed is allocated */ + upd_t** upd); /* out, own: update vector */ +/*********************************************************************** +Builds a partial row from an update undo log record. It contains the +columns which occur as ordering in any index of the table. */ +UNIV_INTERN +byte* +trx_undo_rec_get_partial_row( +/*=========================*/ + /* out: pointer to remaining part of undo + record */ + byte* ptr, /* in: remaining part in update undo log + record of a suitable type, at the start of + the stored index columns; + NOTE that this copy of the undo log record must + be preserved as long as the partial row is + used, as we do NOT copy the data in the + record! */ + dict_index_t* index, /* in: clustered index */ + dtuple_t** row, /* out, own: partial row */ + ibool ignore_prefix, /* in: flag to indicate if we + expect blob prefixes in undo. Used + only in the assertion. */ + mem_heap_t* heap); /* in: memory heap from which the memory + needed is allocated */ +/*************************************************************************** +Writes information to an undo log about an insert, update, or a delete marking +of a clustered index record. This information is used in a rollback of the +transaction and in consistent reads that must look to the history of this +transaction. */ +UNIV_INTERN +ulint +trx_undo_report_row_operation( +/*==========================*/ + /* out: DB_SUCCESS or error code */ + ulint flags, /* in: if BTR_NO_UNDO_LOG_FLAG bit is + set, does nothing */ + ulint op_type, /* in: TRX_UNDO_INSERT_OP or + TRX_UNDO_MODIFY_OP */ + que_thr_t* thr, /* in: query thread */ + dict_index_t* index, /* in: clustered index */ + const dtuple_t* clust_entry, /* in: in the case of an insert, + index entry to insert into the + clustered index, otherwise NULL */ + const upd_t* update, /* in: in the case of an update, + the update vector, otherwise NULL */ + ulint cmpl_info, /* in: compiler info on secondary + index updates */ + const rec_t* rec, /* in: case of an update or delete + marking, the record in the clustered + index, otherwise NULL */ + dulint* roll_ptr); /* out: rollback pointer to the + inserted undo log record, + ut_dulint_zero if BTR_NO_UNDO_LOG + flag was specified */ +/********************************************************************** +Copies an undo record to heap. This function can be called if we know that +the undo log record exists. */ +UNIV_INTERN +trx_undo_rec_t* +trx_undo_get_undo_rec_low( +/*======================*/ + /* out, own: copy of the record */ + dulint roll_ptr, /* in: roll pointer to record */ + mem_heap_t* heap); /* in: memory heap where copied */ +/********************************************************************** +Copies an undo record to heap. */ +UNIV_INTERN +ulint +trx_undo_get_undo_rec( +/*==================*/ + /* out: DB_SUCCESS, or + DB_MISSING_HISTORY if the undo log + has been truncated and we cannot + fetch the old version; NOTE: the + caller must have latches on the + clustered index page and purge_view */ + dulint roll_ptr, /* in: roll pointer to record */ + dulint trx_id, /* in: id of the trx that generated + the roll pointer: it points to an + undo log of this transaction */ + trx_undo_rec_t** undo_rec, /* out, own: copy of the record */ + mem_heap_t* heap); /* in: memory heap where copied */ +/*********************************************************************** +Build a previous version of a clustered index record. This function checks +that the caller has a latch on the index page of the clustered index record +and an s-latch on the purge_view. This guarantees that the stack of versions +is locked. */ +UNIV_INTERN +ulint +trx_undo_prev_version_build( +/*========================*/ + /* out: DB_SUCCESS, or DB_MISSING_HISTORY if + the previous version is not >= purge_view, + which means that it may have been removed, + DB_ERROR if corrupted record */ + const rec_t* index_rec,/* in: clustered index record in the + index tree */ + mtr_t* index_mtr,/* in: mtr which contains the latch to + index_rec page and purge_view */ + const rec_t* rec, /* in: version of a clustered index record */ + dict_index_t* index, /* in: clustered index */ + ulint* offsets,/* in: rec_get_offsets(rec, index) */ + mem_heap_t* heap, /* in: memory heap from which the memory + needed is allocated */ + rec_t** old_vers);/* out, own: previous version, or NULL if + rec is the first inserted version, or if + history data has been deleted */ +/*************************************************************** +Parses a redo log record of adding an undo log record. */ +UNIV_INTERN +byte* +trx_undo_parse_add_undo_rec( +/*========================*/ + /* out: end of log record or NULL */ + byte* ptr, /* in: buffer */ + byte* end_ptr,/* in: buffer end */ + page_t* page); /* in: page or NULL */ +/*************************************************************** +Parses a redo log record of erasing of an undo page end. */ +UNIV_INTERN +byte* +trx_undo_parse_erase_page_end( +/*==========================*/ + /* out: end of log record or NULL */ + byte* ptr, /* in: buffer */ + byte* end_ptr,/* in: buffer end */ + page_t* page, /* in: page or NULL */ + mtr_t* mtr); /* in: mtr or NULL */ + +/* Types of an undo log record: these have to be smaller than 16, as the +compilation info multiplied by 16 is ORed to this value in an undo log +record */ + +#define TRX_UNDO_INSERT_REC 11 /* fresh insert into clustered index */ +#define TRX_UNDO_UPD_EXIST_REC 12 /* update of a non-delete-marked + record */ +#define TRX_UNDO_UPD_DEL_REC 13 /* update of a delete marked record to + a not delete marked record; also the + fields of the record can change */ +#define TRX_UNDO_DEL_MARK_REC 14 /* delete marking of a record; fields + do not change */ +#define TRX_UNDO_CMPL_INFO_MULT 16 /* compilation info is multiplied by + this and ORed to the type above */ +#define TRX_UNDO_UPD_EXTERN 128 /* This bit can be ORed to type_cmpl + to denote that we updated external + storage fields: used by purge to + free the external storage */ + +/* Operation type flags used in trx_undo_report_row_operation */ +#define TRX_UNDO_INSERT_OP 1 +#define TRX_UNDO_MODIFY_OP 2 + +#ifndef UNIV_NONINL +#include "trx0rec.ic" +#endif + +#endif diff --git a/storage/xtradb/include/trx0rec.ic b/storage/xtradb/include/trx0rec.ic new file mode 100644 index 00000000000..bfd74eb9dfb --- /dev/null +++ b/storage/xtradb/include/trx0rec.ic @@ -0,0 +1,116 @@ +/***************************************************************************** + +Copyright (c) 1996, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/****************************************************** +Transaction undo log record + +Created 3/26/1996 Heikki Tuuri +*******************************************************/ + +/************************************************************************** +Reads from an undo log record the record type. */ +UNIV_INLINE +ulint +trx_undo_rec_get_type( +/*==================*/ + /* out: record type */ + trx_undo_rec_t* undo_rec) /* in: undo log record */ +{ + return(mach_read_from_1(undo_rec + 2) & (TRX_UNDO_CMPL_INFO_MULT - 1)); +} + +/************************************************************************** +Reads from an undo log record the record compiler info. */ +UNIV_INLINE +ulint +trx_undo_rec_get_cmpl_info( +/*=======================*/ + /* out: compiler info */ + trx_undo_rec_t* undo_rec) /* in: undo log record */ +{ + return(mach_read_from_1(undo_rec + 2) / TRX_UNDO_CMPL_INFO_MULT); +} + +/************************************************************************** +Returns TRUE if an undo log record contains an extern storage field. */ +UNIV_INLINE +ibool +trx_undo_rec_get_extern_storage( +/*============================*/ + /* out: TRUE if extern */ + trx_undo_rec_t* undo_rec) /* in: undo log record */ +{ + if (mach_read_from_1(undo_rec + 2) & TRX_UNDO_UPD_EXTERN) { + + return(TRUE); + } + + return(FALSE); +} + +/************************************************************************** +Reads the undo log record number. */ +UNIV_INLINE +dulint +trx_undo_rec_get_undo_no( +/*=====================*/ + /* out: undo no */ + trx_undo_rec_t* undo_rec) /* in: undo log record */ +{ + byte* ptr; + + ptr = undo_rec + 3; + + return(mach_dulint_read_much_compressed(ptr)); +} + +/************************************************************************** +Returns the start of the undo record data area. */ +UNIV_INLINE +byte* +trx_undo_rec_get_ptr( +/*=================*/ + /* out: compiler info */ + trx_undo_rec_t* undo_rec, /* in: undo log record */ + dulint undo_no) /* in: undo no read from node */ +{ + return (((byte*) undo_rec) + 3 + + mach_dulint_get_much_compressed_size(undo_no)); +} + +/*************************************************************************** +Copies the undo record to the heap. */ +UNIV_INLINE +trx_undo_rec_t* +trx_undo_rec_copy( +/*==============*/ + /* out, own: copy of undo log record */ + trx_undo_rec_t* undo_rec, /* in: undo log record */ + mem_heap_t* heap) /* in: heap where copied */ +{ + ulint len; + trx_undo_rec_t* rec_copy; + + len = mach_read_from_2(undo_rec) + - ut_align_offset(undo_rec, UNIV_PAGE_SIZE); + rec_copy = mem_heap_alloc(heap, len); + + ut_memcpy(rec_copy, undo_rec, len); + + return(rec_copy); +} diff --git a/storage/xtradb/include/trx0roll.h b/storage/xtradb/include/trx0roll.h new file mode 100644 index 00000000000..3318a5985d7 --- /dev/null +++ b/storage/xtradb/include/trx0roll.h @@ -0,0 +1,340 @@ +/***************************************************************************** + +Copyright (c) 1996, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/****************************************************** +Transaction rollback + +Created 3/26/1996 Heikki Tuuri +*******************************************************/ + +#ifndef trx0roll_h +#define trx0roll_h + +#include "univ.i" +#include "trx0trx.h" +#include "trx0types.h" +#include "mtr0mtr.h" +#include "trx0sys.h" + +#define trx_roll_free_all_savepoints(s) trx_roll_savepoints_free((s), NULL) + +/*********************************************************************** +Determines if this transaction is rolling back an incomplete transaction +in crash recovery. */ +UNIV_INTERN +ibool +trx_is_recv( +/*========*/ + /* out: TRUE if trx is an incomplete + transaction that is being rolled back + in crash recovery */ + const trx_t* trx); /* in: transaction */ +/*********************************************************************** +Returns a transaction savepoint taken at this point in time. */ +UNIV_INTERN +trx_savept_t +trx_savept_take( +/*============*/ + /* out: savepoint */ + trx_t* trx); /* in: transaction */ +/*********************************************************************** +Creates an undo number array. */ +UNIV_INTERN +trx_undo_arr_t* +trx_undo_arr_create(void); +/*=====================*/ +/*********************************************************************** +Frees an undo number array. */ +UNIV_INTERN +void +trx_undo_arr_free( +/*==============*/ + trx_undo_arr_t* arr); /* in: undo number array */ +/*********************************************************************** +Returns pointer to nth element in an undo number array. */ +UNIV_INLINE +trx_undo_inf_t* +trx_undo_arr_get_nth_info( +/*======================*/ + /* out: pointer to the nth element */ + trx_undo_arr_t* arr, /* in: undo number array */ + ulint n); /* in: position */ +/*************************************************************************** +Tries truncate the undo logs. */ +UNIV_INTERN +void +trx_roll_try_truncate( +/*==================*/ + trx_t* trx); /* in: transaction */ +/************************************************************************ +Pops the topmost record when the two undo logs of a transaction are seen +as a single stack of records ordered by their undo numbers. Inserts the +undo number of the popped undo record to the array of currently processed +undo numbers in the transaction. When the query thread finishes processing +of this undo record, it must be released with trx_undo_rec_release. */ +UNIV_INTERN +trx_undo_rec_t* +trx_roll_pop_top_rec_of_trx( +/*========================*/ + /* out: undo log record copied to heap, NULL + if none left, or if the undo number of the + top record would be less than the limit */ + trx_t* trx, /* in: transaction */ + dulint limit, /* in: least undo number we need */ + dulint* roll_ptr,/* out: roll pointer to undo record */ + mem_heap_t* heap); /* in: memory heap where copied */ +/************************************************************************ +Reserves an undo log record for a query thread to undo. This should be +called if the query thread gets the undo log record not using the pop +function above. */ +UNIV_INTERN +ibool +trx_undo_rec_reserve( +/*=================*/ + /* out: TRUE if succeeded */ + trx_t* trx, /* in: transaction */ + dulint undo_no);/* in: undo number of the record */ +/*********************************************************************** +Releases a reserved undo record. */ +UNIV_INTERN +void +trx_undo_rec_release( +/*=================*/ + trx_t* trx, /* in: transaction */ + dulint undo_no);/* in: undo number */ +/************************************************************************* +Starts a rollback operation. */ +UNIV_INTERN +void +trx_rollback( +/*=========*/ + trx_t* trx, /* in: transaction */ + trx_sig_t* sig, /* in: signal starting the rollback */ + que_thr_t** next_thr);/* in/out: next query thread to run; + if the value which is passed in is + a pointer to a NULL pointer, then the + calling function can start running + a new query thread */ +/*********************************************************************** +Rollback or clean up any incomplete transactions which were +encountered in crash recovery. If the transaction already was +committed, then we clean up a possible insert undo log. If the +transaction was not yet committed, then we roll it back. +Note: this is done in a background thread. */ +UNIV_INTERN +os_thread_ret_t +trx_rollback_or_clean_all_recovered( +/*================================*/ + /* out: a dummy parameter */ + void* arg __attribute__((unused))); + /* in: a dummy parameter required by + os_thread_create */ +/******************************************************************** +Finishes a transaction rollback. */ +UNIV_INTERN +void +trx_finish_rollback_off_kernel( +/*===========================*/ + que_t* graph, /* in: undo graph which can now be freed */ + trx_t* trx, /* in: transaction */ + que_thr_t** next_thr);/* in/out: next query thread to run; + if the value which is passed in is + a pointer to a NULL pointer, then the + calling function can start running + a new query thread; if this parameter is + NULL, it is ignored */ +/******************************************************************** +Builds an undo 'query' graph for a transaction. The actual rollback is +performed by executing this query graph like a query subprocedure call. +The reply about the completion of the rollback will be sent by this +graph. */ +UNIV_INTERN +que_t* +trx_roll_graph_build( +/*=================*/ + /* out, own: the query graph */ + trx_t* trx); /* in: trx handle */ +/************************************************************************* +Creates a rollback command node struct. */ +UNIV_INTERN +roll_node_t* +roll_node_create( +/*=============*/ + /* out, own: rollback node struct */ + mem_heap_t* heap); /* in: mem heap where created */ +/*************************************************************** +Performs an execution step for a rollback command node in a query graph. */ +UNIV_INTERN +que_thr_t* +trx_rollback_step( +/*==============*/ + /* out: query thread to run next, or NULL */ + que_thr_t* thr); /* in: query thread */ +/*********************************************************************** +Rollback a transaction used in MySQL. */ +UNIV_INTERN +int +trx_rollback_for_mysql( +/*===================*/ + /* out: error code or DB_SUCCESS */ + trx_t* trx); /* in: transaction handle */ +/*********************************************************************** +Rollback the latest SQL statement for MySQL. */ +UNIV_INTERN +int +trx_rollback_last_sql_stat_for_mysql( +/*=================================*/ + /* out: error code or DB_SUCCESS */ + trx_t* trx); /* in: transaction handle */ +/*********************************************************************** +Rollback a transaction used in MySQL. */ +UNIV_INTERN +int +trx_general_rollback_for_mysql( +/*===========================*/ + /* out: error code or DB_SUCCESS */ + trx_t* trx, /* in: transaction handle */ + ibool partial,/* in: TRUE if partial rollback requested */ + trx_savept_t* savept);/* in: pointer to savepoint undo number, if + partial rollback requested */ +/*********************************************************************** +Rolls back a transaction back to a named savepoint. Modifications after the +savepoint are undone but InnoDB does NOT release the corresponding locks +which are stored in memory. If a lock is 'implicit', that is, a new inserted +row holds a lock where the lock information is carried by the trx id stored in +the row, these locks are naturally released in the rollback. Savepoints which +were set after this savepoint are deleted. */ +UNIV_INTERN +ulint +trx_rollback_to_savepoint_for_mysql( +/*================================*/ + /* out: if no savepoint + of the name found then + DB_NO_SAVEPOINT, + otherwise DB_SUCCESS */ + trx_t* trx, /* in: transaction handle */ + const char* savepoint_name, /* in: savepoint name */ + ib_int64_t* mysql_binlog_cache_pos);/* out: the MySQL binlog cache + position corresponding to this + savepoint; MySQL needs this + information to remove the + binlog entries of the queries + executed after the savepoint */ +/*********************************************************************** +Creates a named savepoint. If the transaction is not yet started, starts it. +If there is already a savepoint of the same name, this call erases that old +savepoint and replaces it with a new. Savepoints are deleted in a transaction +commit or rollback. */ +UNIV_INTERN +ulint +trx_savepoint_for_mysql( +/*====================*/ + /* out: always DB_SUCCESS */ + trx_t* trx, /* in: transaction handle */ + const char* savepoint_name, /* in: savepoint name */ + ib_int64_t binlog_cache_pos); /* in: MySQL binlog cache + position corresponding to this + connection at the time of the + savepoint */ + +/*********************************************************************** +Releases a named savepoint. Savepoints which +were set after this savepoint are deleted. */ +UNIV_INTERN +ulint +trx_release_savepoint_for_mysql( +/*============================*/ + /* out: if no savepoint + of the name found then + DB_NO_SAVEPOINT, + otherwise DB_SUCCESS */ + trx_t* trx, /* in: transaction handle */ + const char* savepoint_name); /* in: savepoint name */ + +/*********************************************************************** +Frees a single savepoint struct. */ +UNIV_INTERN +void +trx_roll_savepoint_free( +/*=====================*/ + trx_t* trx, /* in: transaction handle */ + trx_named_savept_t* savep); /* in: savepoint to free */ + +/*********************************************************************** +Frees savepoint structs starting from savep, if savep == NULL then +free all savepoints. */ + +void +trx_roll_savepoints_free( +/*=====================*/ + trx_t* trx, /* in: transaction handle */ + trx_named_savept_t* savep); /* in: free all savepoints > this one; + if this is NULL, free all savepoints + of trx */ + +/* A cell in the array used during a rollback and a purge */ +struct trx_undo_inf_struct{ + dulint trx_no; /* transaction number: not defined during + a rollback */ + dulint undo_no; /* undo number of an undo record */ + ibool in_use; /* TRUE if the cell is in use */ +}; + +/* During a rollback and a purge, undo numbers of undo records currently being +processed are stored in this array */ + +struct trx_undo_arr_struct{ + ulint n_cells; /* number of cells in the array */ + ulint n_used; /* number of cells currently in use */ + trx_undo_inf_t* infos; /* the array of undo infos */ + mem_heap_t* heap; /* memory heap from which allocated */ +}; + +/* Rollback command node in a query graph */ +struct roll_node_struct{ + que_common_t common; /* node type: QUE_NODE_ROLLBACK */ + ulint state; /* node execution state */ + ibool partial;/* TRUE if we want a partial rollback */ + trx_savept_t savept; /* savepoint to which to roll back, in the + case of a partial rollback */ +}; + +/* A savepoint set with SQL's "SAVEPOINT savepoint_id" command */ +struct trx_named_savept_struct{ + char* name; /* savepoint name */ + trx_savept_t savept; /* the undo number corresponding to + the savepoint */ + ib_int64_t mysql_binlog_cache_pos; + /* the MySQL binlog cache position + corresponding to this savepoint, not + defined if the MySQL binlogging is not + enabled */ + UT_LIST_NODE_T(trx_named_savept_t) + trx_savepoints; /* the list of savepoints of a + transaction */ +}; + +/* Rollback node states */ +#define ROLL_NODE_SEND 1 +#define ROLL_NODE_WAIT 2 + +#ifndef UNIV_NONINL +#include "trx0roll.ic" +#endif + +#endif diff --git a/storage/xtradb/include/trx0roll.ic b/storage/xtradb/include/trx0roll.ic new file mode 100644 index 00000000000..513b8b44847 --- /dev/null +++ b/storage/xtradb/include/trx0roll.ic @@ -0,0 +1,39 @@ +/***************************************************************************** + +Copyright (c) 1996, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/****************************************************** +Transaction rollback + +Created 3/26/1996 Heikki Tuuri +*******************************************************/ + +/*********************************************************************** +Returns pointer to nth element in an undo number array. */ +UNIV_INLINE +trx_undo_inf_t* +trx_undo_arr_get_nth_info( +/*======================*/ + /* out: pointer to the nth element */ + trx_undo_arr_t* arr, /* in: undo number array */ + ulint n) /* in: position */ +{ + ut_ad(arr); + ut_ad(n < arr->n_cells); + + return(arr->infos + n); +} diff --git a/storage/xtradb/include/trx0rseg.h b/storage/xtradb/include/trx0rseg.h new file mode 100644 index 00000000000..f3aa736f788 --- /dev/null +++ b/storage/xtradb/include/trx0rseg.h @@ -0,0 +1,220 @@ +/***************************************************************************** + +Copyright (c) 1996, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/****************************************************** +Rollback segment + +Created 3/26/1996 Heikki Tuuri +*******************************************************/ + +#ifndef trx0rseg_h +#define trx0rseg_h + +#include "univ.i" +#include "trx0types.h" +#include "trx0sys.h" + +/********************************************************************** +Gets a rollback segment header. */ +UNIV_INLINE +trx_rsegf_t* +trx_rsegf_get( +/*==========*/ + /* out: rollback segment header, page + x-latched */ + ulint space, /* in: space where placed */ + ulint zip_size, /* in: compressed page size in bytes + or 0 for uncompressed pages */ + ulint page_no, /* in: page number of the header */ + mtr_t* mtr); /* in: mtr */ +/********************************************************************** +Gets a newly created rollback segment header. */ +UNIV_INLINE +trx_rsegf_t* +trx_rsegf_get_new( +/*==============*/ + /* out: rollback segment header, page + x-latched */ + ulint space, /* in: space where placed */ + ulint zip_size, /* in: compressed page size in bytes + or 0 for uncompressed pages */ + ulint page_no, /* in: page number of the header */ + mtr_t* mtr); /* in: mtr */ +/******************************************************************* +Gets the file page number of the nth undo log slot. */ +UNIV_INLINE +ulint +trx_rsegf_get_nth_undo( +/*===================*/ + /* out: page number of the undo log segment */ + trx_rsegf_t* rsegf, /* in: rollback segment header */ + ulint n, /* in: index of slot */ + mtr_t* mtr); /* in: mtr */ +/******************************************************************* +Sets the file page number of the nth undo log slot. */ +UNIV_INLINE +void +trx_rsegf_set_nth_undo( +/*===================*/ + trx_rsegf_t* rsegf, /* in: rollback segment header */ + ulint n, /* in: index of slot */ + ulint page_no,/* in: page number of the undo log segment */ + mtr_t* mtr); /* in: mtr */ +/******************************************************************** +Looks for a free slot for an undo log segment. */ +UNIV_INLINE +ulint +trx_rsegf_undo_find_free( +/*=====================*/ + /* out: slot index or ULINT_UNDEFINED if not + found */ + trx_rsegf_t* rsegf, /* in: rollback segment header */ + mtr_t* mtr); /* in: mtr */ +/********************************************************************** +Looks for a rollback segment, based on the rollback segment id. */ +UNIV_INTERN +trx_rseg_t* +trx_rseg_get_on_id( +/*===============*/ + /* out: rollback segment */ + ulint id); /* in: rollback segment id */ +/******************************************************************** +Creates a rollback segment header. This function is called only when +a new rollback segment is created in the database. */ +UNIV_INTERN +ulint +trx_rseg_header_create( +/*===================*/ + /* out: page number of the created segment, + FIL_NULL if fail */ + ulint space, /* in: space id */ + ulint zip_size, /* in: compressed page size in bytes + or 0 for uncompressed pages */ + ulint max_size, /* in: max size in pages */ + ulint* slot_no, /* out: rseg id == slot number in trx sys */ + mtr_t* mtr); /* in: mtr */ +/************************************************************************* +Creates the memory copies for rollback segments and initializes the +rseg list and array in trx_sys at a database startup. */ +UNIV_INTERN +void +trx_rseg_list_and_array_init( +/*=========================*/ + trx_sysf_t* sys_header, /* in: trx system header */ + mtr_t* mtr); /* in: mtr */ +/******************************************************************** +Creates a new rollback segment to the database. */ +UNIV_INTERN +trx_rseg_t* +trx_rseg_create( +/*============*/ + /* out: the created segment object, NULL if + fail */ + ulint space, /* in: space id */ + ulint max_size, /* in: max size in pages */ + ulint* id, /* out: rseg id */ + mtr_t* mtr); /* in: mtr */ + + +/* Real max value may be 4076 in usual. But reserve 4 slot for safety or etc... */ +#define TRX_RSEG_N_EXTRA_SLOTS (((UNIV_PAGE_SIZE - (FIL_PAGE_DATA + FIL_PAGE_DATA_END + TRX_RSEG_UNDO_SLOTS)) / TRX_RSEG_SLOT_SIZE) - 4) + +/* Number of undo log slots in a rollback segment file copy */ +#define TRX_RSEG_N_SLOTS (srv_extra_undoslots ? TRX_RSEG_N_EXTRA_SLOTS : (UNIV_PAGE_SIZE / 16)) + +/* Maximum number of transactions supported by a single rollback segment */ +#define TRX_RSEG_MAX_N_TRXS (TRX_RSEG_N_SLOTS / 2) + +/* The rollback segment memory object */ +struct trx_rseg_struct{ + /*--------------------------------------------------------*/ + ulint id; /* rollback segment id == the index of + its slot in the trx system file copy */ + mutex_t mutex; /* mutex protecting the fields in this + struct except id; NOTE that the latching + order must always be kernel mutex -> + rseg mutex */ + ulint space; /* space where the rollback segment is + header is placed */ + ulint zip_size;/* in: compressed page size of space + in bytes, or 0 for uncompressed spaces */ + ulint page_no;/* page number of the rollback segment + header */ + ulint max_size;/* maximum allowed size in pages */ + ulint curr_size;/* current size in pages */ + /*--------------------------------------------------------*/ + /* Fields for update undo logs */ + UT_LIST_BASE_NODE_T(trx_undo_t) update_undo_list; + /* List of update undo logs */ + UT_LIST_BASE_NODE_T(trx_undo_t) update_undo_cached; + /* List of update undo log segments + cached for fast reuse */ + /*--------------------------------------------------------*/ + /* Fields for insert undo logs */ + UT_LIST_BASE_NODE_T(trx_undo_t) insert_undo_list; + /* List of insert undo logs */ + UT_LIST_BASE_NODE_T(trx_undo_t) insert_undo_cached; + /* List of insert undo log segments + cached for fast reuse */ + /*--------------------------------------------------------*/ + ulint last_page_no; /* Page number of the last not yet + purged log header in the history list; + FIL_NULL if all list purged */ + ulint last_offset; /* Byte offset of the last not yet + purged log header */ + dulint last_trx_no; /* Transaction number of the last not + yet purged log */ + ibool last_del_marks; /* TRUE if the last not yet purged log + needs purging */ + /*--------------------------------------------------------*/ + UT_LIST_NODE_T(trx_rseg_t) rseg_list; + /* the list of the rollback segment + memory objects */ +}; + +/* Undo log segment slot in a rollback segment header */ +/*-------------------------------------------------------------*/ +#define TRX_RSEG_SLOT_PAGE_NO 0 /* Page number of the header page of + an undo log segment */ +/*-------------------------------------------------------------*/ +/* Slot size */ +#define TRX_RSEG_SLOT_SIZE 4 + +/* The offset of the rollback segment header on its page */ +#define TRX_RSEG FSEG_PAGE_DATA + +/* Transaction rollback segment header */ +/*-------------------------------------------------------------*/ +#define TRX_RSEG_MAX_SIZE 0 /* Maximum allowed size for rollback + segment in pages */ +#define TRX_RSEG_HISTORY_SIZE 4 /* Number of file pages occupied + by the logs in the history list */ +#define TRX_RSEG_HISTORY 8 /* The update undo logs for committed + transactions */ +#define TRX_RSEG_FSEG_HEADER (8 + FLST_BASE_NODE_SIZE) + /* Header for the file segment where + this page is placed */ +#define TRX_RSEG_UNDO_SLOTS (8 + FLST_BASE_NODE_SIZE + FSEG_HEADER_SIZE) + /* Undo log segment slots */ +/*-------------------------------------------------------------*/ + +#ifndef UNIV_NONINL +#include "trx0rseg.ic" +#endif + +#endif diff --git a/storage/xtradb/include/trx0rseg.ic b/storage/xtradb/include/trx0rseg.ic new file mode 100644 index 00000000000..e665a40fa8b --- /dev/null +++ b/storage/xtradb/include/trx0rseg.ic @@ -0,0 +1,146 @@ +/***************************************************************************** + +Copyright (c) 1996, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/****************************************************** +Rollback segment + +Created 3/26/1996 Heikki Tuuri +*******************************************************/ + +#include "srv0srv.h" + +/********************************************************************** +Gets a rollback segment header. */ +UNIV_INLINE +trx_rsegf_t* +trx_rsegf_get( +/*==========*/ + /* out: rollback segment header, page + x-latched */ + ulint space, /* in: space where placed */ + ulint zip_size, /* in: compressed page size in bytes + or 0 for uncompressed pages */ + ulint page_no, /* in: page number of the header */ + mtr_t* mtr) /* in: mtr */ +{ + buf_block_t* block; + trx_rsegf_t* header; + + block = buf_page_get(space, zip_size, page_no, RW_X_LATCH, mtr); + buf_block_dbg_add_level(block, SYNC_RSEG_HEADER); + + header = TRX_RSEG + buf_block_get_frame(block); + + return(header); +} + +/********************************************************************** +Gets a newly created rollback segment header. */ +UNIV_INLINE +trx_rsegf_t* +trx_rsegf_get_new( +/*==============*/ + /* out: rollback segment header, page + x-latched */ + ulint space, /* in: space where placed */ + ulint zip_size, /* in: compressed page size in bytes + or 0 for uncompressed pages */ + ulint page_no, /* in: page number of the header */ + mtr_t* mtr) /* in: mtr */ +{ + buf_block_t* block; + trx_rsegf_t* header; + + block = buf_page_get(space, zip_size, page_no, RW_X_LATCH, mtr); + buf_block_dbg_add_level(block, SYNC_RSEG_HEADER_NEW); + + header = TRX_RSEG + buf_block_get_frame(block); + + return(header); +} + +/******************************************************************* +Gets the file page number of the nth undo log slot. */ +UNIV_INLINE +ulint +trx_rsegf_get_nth_undo( +/*===================*/ + /* out: page number of the undo log segment */ + trx_rsegf_t* rsegf, /* in: rollback segment header */ + ulint n, /* in: index of slot */ + mtr_t* mtr) /* in: mtr */ +{ + if (UNIV_UNLIKELY(n >= TRX_RSEG_N_SLOTS)) { + fprintf(stderr, + "InnoDB: Error: trying to get slot %lu of rseg\n", + (ulong) n); + ut_error; + } + + return(mtr_read_ulint(rsegf + TRX_RSEG_UNDO_SLOTS + + n * TRX_RSEG_SLOT_SIZE, MLOG_4BYTES, mtr)); +} + +/******************************************************************* +Sets the file page number of the nth undo log slot. */ +UNIV_INLINE +void +trx_rsegf_set_nth_undo( +/*===================*/ + trx_rsegf_t* rsegf, /* in: rollback segment header */ + ulint n, /* in: index of slot */ + ulint page_no,/* in: page number of the undo log segment */ + mtr_t* mtr) /* in: mtr */ +{ + if (UNIV_UNLIKELY(n >= TRX_RSEG_N_SLOTS)) { + fprintf(stderr, + "InnoDB: Error: trying to set slot %lu of rseg\n", + (ulong) n); + ut_error; + } + + mlog_write_ulint(rsegf + TRX_RSEG_UNDO_SLOTS + n * TRX_RSEG_SLOT_SIZE, + page_no, MLOG_4BYTES, mtr); +} + +/******************************************************************** +Looks for a free slot for an undo log segment. */ +UNIV_INLINE +ulint +trx_rsegf_undo_find_free( +/*=====================*/ + /* out: slot index or ULINT_UNDEFINED if not + found */ + trx_rsegf_t* rsegf, /* in: rollback segment header */ + mtr_t* mtr) /* in: mtr */ +{ + ulint i; + ulint page_no; + + for (i = 0; i < TRX_RSEG_N_SLOTS; i++) { + + page_no = trx_rsegf_get_nth_undo(rsegf, i, mtr); + + if (page_no == FIL_NULL) { + + return(i); + } + } + + return(ULINT_UNDEFINED); +} diff --git a/storage/xtradb/include/trx0sys.h b/storage/xtradb/include/trx0sys.h new file mode 100644 index 00000000000..f7e7e082278 --- /dev/null +++ b/storage/xtradb/include/trx0sys.h @@ -0,0 +1,560 @@ +/***************************************************************************** + +Copyright (c) 1996, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/****************************************************** +Transaction system + +Created 3/26/1996 Heikki Tuuri +*******************************************************/ + +#ifndef trx0sys_h +#define trx0sys_h + +#include "univ.i" + +#include "trx0types.h" +#include "mtr0mtr.h" +#include "mtr0log.h" +#include "ut0byte.h" +#include "mem0mem.h" +#include "sync0sync.h" +#include "ut0lst.h" +#include "buf0buf.h" +#include "fil0fil.h" +#include "fut0lst.h" +#include "fsp0fsp.h" +#include "read0types.h" +#include "page0types.h" + +/* In a MySQL replication slave, in crash recovery we store the master log +file name and position here. We have successfully got the updates to InnoDB +up to this position. If .._pos is -1, it means no crash recovery was needed, +or there was no master log position info inside InnoDB. */ + +extern char trx_sys_mysql_master_log_name[]; +extern ib_int64_t trx_sys_mysql_master_log_pos; + +extern char trx_sys_mysql_relay_log_name[]; +extern ib_int64_t trx_sys_mysql_relay_log_pos; + +/* If this MySQL server uses binary logging, after InnoDB has been inited +and if it has done a crash recovery, we store the binlog file name and position +here. If .._pos is -1, it means there was no binlog position info inside +InnoDB. */ + +extern char trx_sys_mysql_bin_log_name[]; +extern ib_int64_t trx_sys_mysql_bin_log_pos; + +/* The transaction system */ +extern trx_sys_t* trx_sys; + +/* Doublewrite system */ +extern trx_doublewrite_t* trx_doublewrite; +extern ibool trx_doublewrite_must_reset_space_ids; +extern ibool trx_sys_multiple_tablespace_format; + +/******************************************************************** +Creates the doublewrite buffer to a new InnoDB installation. The header of the +doublewrite buffer is placed on the trx system header page. */ +UNIV_INTERN +void +trx_sys_create_doublewrite_buf(void); +/*================================*/ +/******************************************************************** +At a database startup initializes the doublewrite buffer memory structure if +we already have a doublewrite buffer created in the data files. If we are +upgrading to an InnoDB version which supports multiple tablespaces, then this +function performs the necessary update operations. If we are in a crash +recovery, this function uses a possible doublewrite buffer to restore +half-written pages in the data files. */ +UNIV_INTERN +void +trx_sys_doublewrite_init_or_restore_pages( +/*======================================*/ + ibool restore_corrupt_pages); +/******************************************************************** +Marks the trx sys header when we have successfully upgraded to the >= 4.1.x +multiple tablespace format. */ +UNIV_INTERN +void +trx_sys_mark_upgraded_to_multiple_tablespaces(void); +/*===============================================*/ +/******************************************************************** +Determines if a page number is located inside the doublewrite buffer. */ +UNIV_INTERN +ibool +trx_doublewrite_page_inside( +/*========================*/ + /* out: TRUE if the location is inside + the two blocks of the doublewrite buffer */ + ulint page_no); /* in: page number */ +/******************************************************************* +Checks if a page address is the trx sys header page. */ +UNIV_INLINE +ibool +trx_sys_hdr_page( +/*=============*/ + /* out: TRUE if trx sys header page */ + ulint space, /* in: space */ + ulint page_no);/* in: page number */ +/********************************************************************* +Creates and initializes the central memory structures for the transaction +system. This is called when the database is started. */ +UNIV_INTERN +void +trx_sys_init_at_db_start(void); +/*==========================*/ +/********************************************************************* +Creates and initializes the transaction system at the database creation. */ +UNIV_INTERN +void +trx_sys_create(void); +/*================*/ +/********************************************************************* +Create extra rollback segments when create_new_db */ +UNIV_INTERN +void +trx_sys_create_extra_rseg( +/*======================*/ + ulint num); /* in: number of extra user rollback segments */ +/******************************************************************** +Looks for a free slot for a rollback segment in the trx system file copy. */ +UNIV_INTERN +ulint +trx_sysf_rseg_find_free( +/*====================*/ + /* out: slot index or ULINT_UNDEFINED + if not found */ + mtr_t* mtr); /* in: mtr */ +/******************************************************************* +Gets the pointer in the nth slot of the rseg array. */ +UNIV_INLINE +trx_rseg_t* +trx_sys_get_nth_rseg( +/*=================*/ + /* out: pointer to rseg object, NULL if slot + not in use */ + trx_sys_t* sys, /* in: trx system */ + ulint n); /* in: index of slot */ +/******************************************************************* +Sets the pointer in the nth slot of the rseg array. */ +UNIV_INLINE +void +trx_sys_set_nth_rseg( +/*=================*/ + trx_sys_t* sys, /* in: trx system */ + ulint n, /* in: index of slot */ + trx_rseg_t* rseg); /* in: pointer to rseg object, NULL if slot + not in use */ +/************************************************************************** +Gets a pointer to the transaction system file copy and x-locks its page. */ +UNIV_INLINE +trx_sysf_t* +trx_sysf_get( +/*=========*/ + /* out: pointer to system file copy, page x-locked */ + mtr_t* mtr); /* in: mtr */ +/********************************************************************* +Gets the space of the nth rollback segment slot in the trx system +file copy. */ +UNIV_INLINE +ulint +trx_sysf_rseg_get_space( +/*====================*/ + /* out: space id */ + trx_sysf_t* sys_header, /* in: trx sys file copy */ + ulint i, /* in: slot index == rseg id */ + mtr_t* mtr); /* in: mtr */ +/********************************************************************* +Gets the page number of the nth rollback segment slot in the trx system +file copy. */ +UNIV_INLINE +ulint +trx_sysf_rseg_get_page_no( +/*======================*/ + /* out: page number, FIL_NULL + if slot unused */ + trx_sysf_t* sys_header, /* in: trx sys file copy */ + ulint i, /* in: slot index == rseg id */ + mtr_t* mtr); /* in: mtr */ +/********************************************************************* +Sets the space id of the nth rollback segment slot in the trx system +file copy. */ +UNIV_INLINE +void +trx_sysf_rseg_set_space( +/*====================*/ + trx_sysf_t* sys_header, /* in: trx sys file copy */ + ulint i, /* in: slot index == rseg id */ + ulint space, /* in: space id */ + mtr_t* mtr); /* in: mtr */ +/********************************************************************* +Sets the page number of the nth rollback segment slot in the trx system +file copy. */ +UNIV_INLINE +void +trx_sysf_rseg_set_page_no( +/*======================*/ + trx_sysf_t* sys_header, /* in: trx sys file copy */ + ulint i, /* in: slot index == rseg id */ + ulint page_no, /* in: page number, FIL_NULL if + the slot is reset to unused */ + mtr_t* mtr); /* in: mtr */ +/********************************************************************* +Allocates a new transaction id. */ +UNIV_INLINE +dulint +trx_sys_get_new_trx_id(void); +/*========================*/ + /* out: new, allocated trx id */ +/********************************************************************* +Allocates a new transaction number. */ +UNIV_INLINE +dulint +trx_sys_get_new_trx_no(void); +/*========================*/ + /* out: new, allocated trx number */ +/********************************************************************* +Writes a trx id to an index page. In case that the id size changes in +some future version, this function should be used instead of +mach_write_... */ +UNIV_INLINE +void +trx_write_trx_id( +/*=============*/ + byte* ptr, /* in: pointer to memory where written */ + dulint id); /* in: id */ +/********************************************************************* +Reads a trx id from an index page. In case that the id size changes in +some future version, this function should be used instead of +mach_read_... */ +UNIV_INLINE +dulint +trx_read_trx_id( +/*============*/ + /* out: id */ + const byte* ptr); /* in: pointer to memory from where to read */ +/******************************************************************** +Looks for the trx handle with the given id in trx_list. */ +UNIV_INLINE +trx_t* +trx_get_on_id( +/*==========*/ + /* out: the trx handle or NULL if not found */ + dulint trx_id); /* in: trx id to search for */ +/******************************************************************** +Returns the minumum trx id in trx list. This is the smallest id for which +the trx can possibly be active. (But, you must look at the trx->conc_state to +find out if the minimum trx id transaction itself is active, or already +committed.) */ +UNIV_INLINE +dulint +trx_list_get_min_trx_id(void); +/*=========================*/ + /* out: the minimum trx id, or trx_sys->max_trx_id + if the trx list is empty */ +/******************************************************************** +Checks if a transaction with the given id is active. */ +UNIV_INLINE +ibool +trx_is_active( +/*==========*/ + /* out: TRUE if active */ + dulint trx_id);/* in: trx id of the transaction */ +/******************************************************************** +Checks that trx is in the trx list. */ +UNIV_INTERN +ibool +trx_in_trx_list( +/*============*/ + /* out: TRUE if is in */ + trx_t* in_trx);/* in: trx */ +/********************************************************************* +Updates the offset information about the end of the MySQL binlog entry +which corresponds to the transaction just being committed. In a MySQL +replication slave updates the latest master binlog position up to which +replication has proceeded. */ +UNIV_INTERN +void +trx_sys_update_mysql_binlog_offset( +/*===============================*/ + const char* file_name_in,/* in: MySQL log file name */ + ib_int64_t offset, /* in: position in that log file */ + ulint field, /* in: offset of the MySQL log info field in + the trx sys header */ + mtr_t* mtr); /* in: mtr */ +/********************************************************************* +Prints to stderr the MySQL binlog offset info in the trx system header if +the magic number shows it valid. */ +UNIV_INTERN +void +trx_sys_print_mysql_binlog_offset(void); +/*===================================*/ +#ifdef UNIV_HOTBACKUP +/********************************************************************* +Prints to stderr the MySQL binlog info in the system header if the +magic number shows it valid. */ +UNIV_INTERN +void +trx_sys_print_mysql_binlog_offset_from_page( +/*========================================*/ + const byte* page); /* in: buffer containing the trx + system header page, i.e., page number + TRX_SYS_PAGE_NO in the tablespace */ +#endif /* UNIV_HOTBACKUP */ +/********************************************************************* +Prints to stderr the MySQL master log offset info in the trx system header if +the magic number shows it valid. */ +UNIV_INTERN +void +trx_sys_print_mysql_master_log_pos(void); +/*====================================*/ +/********************************************************************* +Initializes the tablespace tag system. */ +UNIV_INTERN +void +trx_sys_file_format_init(void); +/*==========================*/ +/********************************************************************* +Closes the tablespace tag system. */ +UNIV_INTERN +void +trx_sys_file_format_close(void); +/*===========================*/ +/************************************************************************ +Tags the system table space with minimum format id if it has not been +tagged yet. +WARNING: This function is only called during the startup and AFTER the +redo log application during recovery has finished. */ +UNIV_INTERN +void +trx_sys_file_format_tag_init(void); +/*==============================*/ +/********************************************************************* +Get the name representation of the file format from its id. */ +UNIV_INTERN +const char* +trx_sys_file_format_id_to_name( +/*===========================*/ + /* out: pointer to the name */ + const ulint id); /* in: id of the file format */ +/********************************************************************* +Set the file format id unconditionally except if it's already the +same value. */ +UNIV_INTERN +ibool +trx_sys_file_format_max_set( +/*========================*/ + /* out: TRUE if value updated */ + ulint format_id, /* in: file format id */ + const char** name); /* out: max file format name or + NULL if not needed. */ +/********************************************************************* +Get the name representation of the file format from its id. */ +UNIV_INTERN +const char* +trx_sys_file_format_max_get(void); +/*=============================*/ + /* out: pointer to the max format name */ +/********************************************************************* +Check for the max file format tag stored on disk. */ +UNIV_INTERN +ulint +trx_sys_file_format_max_check( +/*==========================*/ + /* out: DB_SUCCESS or error code */ + ulint max_format_id); /* in: the max format id to check */ +/************************************************************************ +Update the file format tag in the system tablespace only if the given +format id is greater than the known max id. */ +UNIV_INTERN +ibool +trx_sys_file_format_max_upgrade( +/*============================*/ + /* out: TRUE if format_id was + bigger than the known max id */ + const char** name, /* out: max file format name */ + ulint format_id); /* in: file format identifier */ +/* The automatically created system rollback segment has this id */ +#define TRX_SYS_SYSTEM_RSEG_ID 0 + +/* Space id and page no where the trx system file copy resides */ +#define TRX_SYS_SPACE 0 /* the SYSTEM tablespace */ +#define TRX_SYS_PAGE_NO FSP_TRX_SYS_PAGE_NO + +/* The offset of the transaction system header on the page */ +#define TRX_SYS FSEG_PAGE_DATA + +/* Transaction system header */ +/*-------------------------------------------------------------*/ +#define TRX_SYS_TRX_ID_STORE 0 /* the maximum trx id or trx number + modulo TRX_SYS_TRX_ID_UPDATE_MARGIN + written to a file page by any + transaction; the assignment of + transaction ids continues from this + number rounded up by .._MARGIN plus + .._MARGIN when the database is + started */ +#define TRX_SYS_FSEG_HEADER 8 /* segment header for the tablespace + segment the trx system is created + into */ +#define TRX_SYS_RSEGS (8 + FSEG_HEADER_SIZE) + /* the start of the array of rollback + segment specification slots */ +/*-------------------------------------------------------------*/ + +/* Max number of rollback segments: the number of segment specification slots +in the transaction system array; rollback segment id must fit in one byte, +therefore 256; each slot is currently 8 bytes in size */ +#define TRX_SYS_N_RSEGS 256 + +#define TRX_SYS_MYSQL_LOG_NAME_LEN 512 +#define TRX_SYS_MYSQL_MASTER_LOG_NAME_LEN 480 /* (500 - 12) is dead line. */ +#define TRX_SYS_MYSQL_LOG_MAGIC_N 873422344 + +#if UNIV_PAGE_SIZE < 4096 +# error "UNIV_PAGE_SIZE < 4096" +#endif +/* The offset of the MySQL replication info in the trx system header; +this contains the same fields as TRX_SYS_MYSQL_LOG_INFO below */ +#define TRX_SYS_MYSQL_MASTER_LOG_INFO (UNIV_PAGE_SIZE - 2000) +#define TRX_SYS_MYSQL_RELAY_LOG_INFO (UNIV_PAGE_SIZE - 1500) + +/* The offset of the MySQL binlog offset info in the trx system header */ +#define TRX_SYS_MYSQL_LOG_INFO (UNIV_PAGE_SIZE - 1000) +#define TRX_SYS_MYSQL_LOG_MAGIC_N_FLD 0 /* magic number which shows + if we have valid data in the + MySQL binlog info; the value + is ..._MAGIC_N if yes */ +#define TRX_SYS_MYSQL_LOG_OFFSET_HIGH 4 /* high 4 bytes of the offset + within that file */ +#define TRX_SYS_MYSQL_LOG_OFFSET_LOW 8 /* low 4 bytes of the offset + within that file */ +#define TRX_SYS_MYSQL_LOG_NAME 12 /* MySQL log file name */ + +/* The offset of the doublewrite buffer header on the trx system header page */ +#define TRX_SYS_DOUBLEWRITE (UNIV_PAGE_SIZE - 200) +/*-------------------------------------------------------------*/ +#define TRX_SYS_DOUBLEWRITE_FSEG 0 /* fseg header of the fseg + containing the doublewrite + buffer */ +#define TRX_SYS_DOUBLEWRITE_MAGIC FSEG_HEADER_SIZE + /* 4-byte magic number which + shows if we already have + created the doublewrite + buffer */ +#define TRX_SYS_DOUBLEWRITE_BLOCK1 (4 + FSEG_HEADER_SIZE) + /* page number of the + first page in the first + sequence of 64 + (= FSP_EXTENT_SIZE) consecutive + pages in the doublewrite + buffer */ +#define TRX_SYS_DOUBLEWRITE_BLOCK2 (8 + FSEG_HEADER_SIZE) + /* page number of the + first page in the second + sequence of 64 consecutive + pages in the doublewrite + buffer */ +#define TRX_SYS_DOUBLEWRITE_REPEAT 12 /* we repeat the above 3 + numbers so that if the trx + sys header is half-written + to disk, we still may be able + to recover the information */ +#define TRX_SYS_DOUBLEWRITE_SPACE_ID_STORED (24 + FSEG_HEADER_SIZE) + /* If this is not yet set to + .._N, we must reset the + doublewrite buffer, because + starting from 4.1.x the space + id of a data page is stored to + FIL_PAGE_ARCH_LOG_NO_OR_SPACE_NO */ +/*-------------------------------------------------------------*/ +#define TRX_SYS_DOUBLEWRITE_MAGIC_N 536853855 +#define TRX_SYS_DOUBLEWRITE_SPACE_ID_STORED_N 1783657386 + + +#define TRX_SYS_DOUBLEWRITE_BLOCK_SIZE FSP_EXTENT_SIZE + +/* The offset of the file format tag on the trx system header page */ +#define TRX_SYS_FILE_FORMAT_TAG (UNIV_PAGE_SIZE - 16) + +/* We use these random constants to reduce the probability of reading +garbage (from previous versions) that maps to an actual format id. We +use these as bit masks at the time of reading and writing from/to disk. */ +#define TRX_SYS_FILE_FORMAT_TAG_MAGIC_N_LOW 3645922177UL +#define TRX_SYS_FILE_FORMAT_TAG_MAGIC_N_HIGH 2745987765UL + +/* Doublewrite control struct */ +struct trx_doublewrite_struct{ + mutex_t mutex; /* mutex protecting the first_free field and + write_buf */ + ulint block1; /* the page number of the first + doublewrite block (64 pages) */ + ulint block2; /* page number of the second block */ + ulint first_free; /* first free position in write_buf measured + in units of UNIV_PAGE_SIZE */ + byte* write_buf; /* write buffer used in writing to the + doublewrite buffer, aligned to an + address divisible by UNIV_PAGE_SIZE + (which is required by Windows aio) */ + byte* write_buf_unaligned; /* pointer to write_buf, but unaligned */ + buf_page_t** + buf_block_arr; /* array to store pointers to the buffer + blocks which have been cached to write_buf */ +}; + +/* The transaction system central memory data structure; protected by the +kernel mutex */ +struct trx_sys_struct{ + dulint max_trx_id; /* The smallest number not yet + assigned as a transaction id or + transaction number */ + UT_LIST_BASE_NODE_T(trx_t) trx_list; + /* List of active and committed in + memory transactions, sorted on trx id, + biggest first */ + UT_LIST_BASE_NODE_T(trx_t) mysql_trx_list; + /* List of transactions created + for MySQL */ + UT_LIST_BASE_NODE_T(trx_rseg_t) rseg_list; + /* List of rollback segment objects */ + trx_rseg_t* latest_rseg; /* Latest rollback segment in the + round-robin assignment of rollback + segments to transactions */ + trx_rseg_t* rseg_array[TRX_SYS_N_RSEGS]; + /* Pointer array to rollback segments; + NULL if slot not in use */ + ulint rseg_history_len;/* Length of the TRX_RSEG_HISTORY + list (update undo logs for committed + transactions), protected by + rseg->mutex */ + UT_LIST_BASE_NODE_T(read_view_t) view_list; + /* List of read views sorted on trx no, + biggest first */ +}; + +/* When a trx id which is zero modulo this number (which must be a power of +two) is assigned, the field TRX_SYS_TRX_ID_STORE on the transaction system +page is updated */ +#define TRX_SYS_TRX_ID_WRITE_MARGIN 256 + +#ifndef UNIV_NONINL +#include "trx0sys.ic" +#endif + +#endif diff --git a/storage/xtradb/include/trx0sys.ic b/storage/xtradb/include/trx0sys.ic new file mode 100644 index 00000000000..4437133188f --- /dev/null +++ b/storage/xtradb/include/trx0sys.ic @@ -0,0 +1,383 @@ +/***************************************************************************** + +Copyright (c) 1996, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/****************************************************** +Transaction system + +Created 3/26/1996 Heikki Tuuri +*******************************************************/ + +#include "srv0srv.h" +#include "trx0trx.h" + +/* The typedef for rseg slot in the file copy */ +typedef byte trx_sysf_rseg_t; + +/* Rollback segment specification slot offsets */ +/*-------------------------------------------------------------*/ +#define TRX_SYS_RSEG_SPACE 0 /* space where the the segment + header is placed; starting with + MySQL/InnoDB 5.1.7, this is + UNIV_UNDEFINED if the slot is unused */ +#define TRX_SYS_RSEG_PAGE_NO 4 /* page number where the the segment + header is placed; this is FIL_NULL + if the slot is unused */ +/*-------------------------------------------------------------*/ +/* Size of a rollback segment specification slot */ +#define TRX_SYS_RSEG_SLOT_SIZE 8 + +/********************************************************************* +Writes the value of max_trx_id to the file based trx system header. */ +UNIV_INTERN +void +trx_sys_flush_max_trx_id(void); +/*==========================*/ + +/******************************************************************* +Checks if a page address is the trx sys header page. */ +UNIV_INLINE +ibool +trx_sys_hdr_page( +/*=============*/ + /* out: TRUE if trx sys header page */ + ulint space, /* in: space */ + ulint page_no)/* in: page number */ +{ + if ((space == TRX_SYS_SPACE) && (page_no == TRX_SYS_PAGE_NO)) { + + return(TRUE); + } + + return(FALSE); +} + +/******************************************************************* +Gets the pointer in the nth slot of the rseg array. */ +UNIV_INLINE +trx_rseg_t* +trx_sys_get_nth_rseg( +/*=================*/ + /* out: pointer to rseg object, NULL if slot + not in use */ + trx_sys_t* sys, /* in: trx system */ + ulint n) /* in: index of slot */ +{ + ut_ad(mutex_own(&(kernel_mutex))); + ut_ad(n < TRX_SYS_N_RSEGS); + + return(sys->rseg_array[n]); +} + +/******************************************************************* +Sets the pointer in the nth slot of the rseg array. */ +UNIV_INLINE +void +trx_sys_set_nth_rseg( +/*=================*/ + trx_sys_t* sys, /* in: trx system */ + ulint n, /* in: index of slot */ + trx_rseg_t* rseg) /* in: pointer to rseg object, NULL if slot + not in use */ +{ + ut_ad(n < TRX_SYS_N_RSEGS); + + sys->rseg_array[n] = rseg; +} + +/************************************************************************** +Gets a pointer to the transaction system header and x-latches its page. */ +UNIV_INLINE +trx_sysf_t* +trx_sysf_get( +/*=========*/ + /* out: pointer to system header, page x-latched. */ + mtr_t* mtr) /* in: mtr */ +{ + buf_block_t* block; + trx_sysf_t* header; + + ut_ad(mtr); + + block = buf_page_get(TRX_SYS_SPACE, 0, TRX_SYS_PAGE_NO, + RW_X_LATCH, mtr); + buf_block_dbg_add_level(block, SYNC_TRX_SYS_HEADER); + + header = TRX_SYS + buf_block_get_frame(block); + + return(header); +} + +/********************************************************************* +Gets the space of the nth rollback segment slot in the trx system +file copy. */ +UNIV_INLINE +ulint +trx_sysf_rseg_get_space( +/*====================*/ + /* out: space id */ + trx_sysf_t* sys_header, /* in: trx sys header */ + ulint i, /* in: slot index == rseg id */ + mtr_t* mtr) /* in: mtr */ +{ + ut_ad(mutex_own(&(kernel_mutex))); + ut_ad(sys_header); + ut_ad(i < TRX_SYS_N_RSEGS); + + return(mtr_read_ulint(sys_header + TRX_SYS_RSEGS + + i * TRX_SYS_RSEG_SLOT_SIZE + + TRX_SYS_RSEG_SPACE, MLOG_4BYTES, mtr)); +} + +/********************************************************************* +Gets the page number of the nth rollback segment slot in the trx system +header. */ +UNIV_INLINE +ulint +trx_sysf_rseg_get_page_no( +/*======================*/ + /* out: page number, FIL_NULL + if slot unused */ + trx_sysf_t* sys_header, /* in: trx system header */ + ulint i, /* in: slot index == rseg id */ + mtr_t* mtr) /* in: mtr */ +{ + ut_ad(sys_header); + ut_ad(mutex_own(&(kernel_mutex))); + ut_ad(i < TRX_SYS_N_RSEGS); + + return(mtr_read_ulint(sys_header + TRX_SYS_RSEGS + + i * TRX_SYS_RSEG_SLOT_SIZE + + TRX_SYS_RSEG_PAGE_NO, MLOG_4BYTES, mtr)); +} + +/********************************************************************* +Sets the space id of the nth rollback segment slot in the trx system +file copy. */ +UNIV_INLINE +void +trx_sysf_rseg_set_space( +/*====================*/ + trx_sysf_t* sys_header, /* in: trx sys file copy */ + ulint i, /* in: slot index == rseg id */ + ulint space, /* in: space id */ + mtr_t* mtr) /* in: mtr */ +{ + ut_ad(mutex_own(&(kernel_mutex))); + ut_ad(sys_header); + ut_ad(i < TRX_SYS_N_RSEGS); + + mlog_write_ulint(sys_header + TRX_SYS_RSEGS + + i * TRX_SYS_RSEG_SLOT_SIZE + + TRX_SYS_RSEG_SPACE, + space, + MLOG_4BYTES, mtr); +} + +/********************************************************************* +Sets the page number of the nth rollback segment slot in the trx system +header. */ +UNIV_INLINE +void +trx_sysf_rseg_set_page_no( +/*======================*/ + trx_sysf_t* sys_header, /* in: trx sys header */ + ulint i, /* in: slot index == rseg id */ + ulint page_no, /* in: page number, FIL_NULL if the + slot is reset to unused */ + mtr_t* mtr) /* in: mtr */ +{ + ut_ad(mutex_own(&(kernel_mutex))); + ut_ad(sys_header); + ut_ad(i < TRX_SYS_N_RSEGS); + + mlog_write_ulint(sys_header + TRX_SYS_RSEGS + + i * TRX_SYS_RSEG_SLOT_SIZE + + TRX_SYS_RSEG_PAGE_NO, + page_no, + MLOG_4BYTES, mtr); +} + +/********************************************************************* +Writes a trx id to an index page. In case that the id size changes in +some future version, this function should be used instead of +mach_write_... */ +UNIV_INLINE +void +trx_write_trx_id( +/*=============*/ + byte* ptr, /* in: pointer to memory where written */ + dulint id) /* in: id */ +{ +#if DATA_TRX_ID_LEN != 6 +# error "DATA_TRX_ID_LEN != 6" +#endif + mach_write_to_6(ptr, id); +} + +/********************************************************************* +Reads a trx id from an index page. In case that the id size changes in +some future version, this function should be used instead of +mach_read_... */ +UNIV_INLINE +dulint +trx_read_trx_id( +/*============*/ + /* out: id */ + const byte* ptr) /* in: pointer to memory from where to read */ +{ +#if DATA_TRX_ID_LEN != 6 +# error "DATA_TRX_ID_LEN != 6" +#endif + return(mach_read_from_6(ptr)); +} + +/******************************************************************** +Looks for the trx handle with the given id in trx_list. */ +UNIV_INLINE +trx_t* +trx_get_on_id( +/*==========*/ + /* out: the trx handle or NULL if not found */ + dulint trx_id) /* in: trx id to search for */ +{ + trx_t* trx; + + ut_ad(mutex_own(&(kernel_mutex))); + + trx = UT_LIST_GET_FIRST(trx_sys->trx_list); + + while (trx != NULL) { + if (0 == ut_dulint_cmp(trx_id, trx->id)) { + + return(trx); + } + + trx = UT_LIST_GET_NEXT(trx_list, trx); + } + + return(NULL); +} + +/******************************************************************** +Returns the minumum trx id in trx list. This is the smallest id for which +the trx can possibly be active. (But, you must look at the trx->conc_state to +find out if the minimum trx id transaction itself is active, or already +committed.) */ +UNIV_INLINE +dulint +trx_list_get_min_trx_id(void) +/*=========================*/ + /* out: the minimum trx id, or trx_sys->max_trx_id + if the trx list is empty */ +{ + trx_t* trx; + + ut_ad(mutex_own(&(kernel_mutex))); + + trx = UT_LIST_GET_LAST(trx_sys->trx_list); + + if (trx == NULL) { + + return(trx_sys->max_trx_id); + } + + return(trx->id); +} + +/******************************************************************** +Checks if a transaction with the given id is active. */ +UNIV_INLINE +ibool +trx_is_active( +/*==========*/ + /* out: TRUE if active */ + dulint trx_id) /* in: trx id of the transaction */ +{ + trx_t* trx; + + ut_ad(mutex_own(&(kernel_mutex))); + + if (ut_dulint_cmp(trx_id, trx_list_get_min_trx_id()) < 0) { + + return(FALSE); + } + + if (ut_dulint_cmp(trx_id, trx_sys->max_trx_id) >= 0) { + + /* There must be corruption: we return TRUE because this + function is only called by lock_clust_rec_some_has_impl() + and row_vers_impl_x_locked_off_kernel() and they have + diagnostic prints in this case */ + + return(TRUE); + } + + trx = trx_get_on_id(trx_id); + if (trx && (trx->conc_state == TRX_ACTIVE + || trx->conc_state == TRX_PREPARED)) { + + return(TRUE); + } + + return(FALSE); +} + +/********************************************************************* +Allocates a new transaction id. */ +UNIV_INLINE +dulint +trx_sys_get_new_trx_id(void) +/*========================*/ + /* out: new, allocated trx id */ +{ + dulint id; + + ut_ad(mutex_own(&kernel_mutex)); + + /* VERY important: after the database is started, max_trx_id value is + divisible by TRX_SYS_TRX_ID_WRITE_MARGIN, and the following if + will evaluate to TRUE when this function is first time called, + and the value for trx id will be written to disk-based header! + Thus trx id values will not overlap when the database is + repeatedly started! */ + + if (ut_dulint_get_low(trx_sys->max_trx_id) + % TRX_SYS_TRX_ID_WRITE_MARGIN == 0) { + + trx_sys_flush_max_trx_id(); + } + + id = trx_sys->max_trx_id; + + UT_DULINT_INC(trx_sys->max_trx_id); + + return(id); +} + +/********************************************************************* +Allocates a new transaction number. */ +UNIV_INLINE +dulint +trx_sys_get_new_trx_no(void) +/*========================*/ + /* out: new, allocated trx number */ +{ + ut_ad(mutex_own(&kernel_mutex)); + + return(trx_sys_get_new_trx_id()); +} diff --git a/storage/xtradb/include/trx0trx.h b/storage/xtradb/include/trx0trx.h new file mode 100644 index 00000000000..e7f7539f9ee --- /dev/null +++ b/storage/xtradb/include/trx0trx.h @@ -0,0 +1,860 @@ +/***************************************************************************** + +Copyright (c) 1996, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/****************************************************** +The transaction + +Created 3/26/1996 Heikki Tuuri +*******************************************************/ + +#ifndef trx0trx_h +#define trx0trx_h + +#include "univ.i" +#include "trx0types.h" +#include "lock0types.h" +#include "usr0types.h" +#include "que0types.h" +#include "mem0mem.h" +#include "read0types.h" +#include "dict0types.h" +#include "trx0xa.h" +#include "ut0vec.h" + +/* Dummy session used currently in MySQL interface */ +extern sess_t* trx_dummy_sess; + +/* Number of transactions currently allocated for MySQL: protected by +the kernel mutex */ +extern ulint trx_n_mysql_transactions; + +/***************************************************************** +Resets the new record lock info in a transaction struct. */ +UNIV_INLINE +void +trx_reset_new_rec_lock_info( +/*========================*/ + trx_t* trx); /* in: transaction struct */ +/***************************************************************** +Registers that we have set a new record lock on an index. We only have space +to store 2 indexes! If this is called to store more than 2 indexes after +trx_reset_new_rec_lock_info(), then this function does nothing. */ +UNIV_INLINE +void +trx_register_new_rec_lock( +/*======================*/ + trx_t* trx, /* in: transaction struct */ + dict_index_t* index); /* in: trx sets a new record lock on this + index */ +/***************************************************************** +Checks if trx has set a new record lock on an index. */ +UNIV_INLINE +ibool +trx_new_rec_locks_contain( +/*======================*/ + /* out: TRUE if trx has set a new record lock + on index */ + trx_t* trx, /* in: transaction struct */ + dict_index_t* index); /* in: index */ +/************************************************************************ +Releases the search latch if trx has reserved it. */ +UNIV_INTERN +void +trx_search_latch_release_if_reserved( +/*=================================*/ + trx_t* trx); /* in: transaction */ +/********************************************************************** +Set detailed error message for the transaction. */ +UNIV_INTERN +void +trx_set_detailed_error( +/*===================*/ + trx_t* trx, /* in: transaction struct */ + const char* msg); /* in: detailed error message */ +/***************************************************************** +Set detailed error message for the transaction from a file. Note that the +file is rewinded before reading from it. */ +UNIV_INTERN +void +trx_set_detailed_error_from_file( +/*=============================*/ + trx_t* trx, /* in: transaction struct */ + FILE* file); /* in: file to read message from */ +/******************************************************************** +Retrieves the error_info field from a trx. */ +UNIV_INLINE +const dict_index_t* +trx_get_error_info( +/*===============*/ + /* out: the error info */ + const trx_t* trx); /* in: trx object */ +/******************************************************************** +Creates and initializes a transaction object. */ +UNIV_INTERN +trx_t* +trx_create( +/*=======*/ + /* out, own: the transaction */ + sess_t* sess) /* in: session */ + __attribute__((nonnull)); +/************************************************************************ +Creates a transaction object for MySQL. */ +UNIV_INTERN +trx_t* +trx_allocate_for_mysql(void); +/*========================*/ + /* out, own: transaction object */ +/************************************************************************ +Creates a transaction object for background operations by the master thread. */ +UNIV_INTERN +trx_t* +trx_allocate_for_background(void); +/*=============================*/ + /* out, own: transaction object */ +/************************************************************************ +Frees a transaction object. */ +UNIV_INTERN +void +trx_free( +/*=====*/ + trx_t* trx); /* in, own: trx object */ +/************************************************************************ +Frees a transaction object for MySQL. */ +UNIV_INTERN +void +trx_free_for_mysql( +/*===============*/ + trx_t* trx); /* in, own: trx object */ +/************************************************************************ +Frees a transaction object of a background operation of the master thread. */ +UNIV_INTERN +void +trx_free_for_background( +/*====================*/ + trx_t* trx); /* in, own: trx object */ +/******************************************************************** +Creates trx objects for transactions and initializes the trx list of +trx_sys at database start. Rollback segment and undo log lists must +already exist when this function is called, because the lists of +transactions to be rolled back or cleaned up are built based on the +undo log lists. */ +UNIV_INTERN +void +trx_lists_init_at_db_start(void); +/*============================*/ +/******************************************************************** +Starts a new transaction. */ +UNIV_INTERN +ibool +trx_start( +/*======*/ + /* out: TRUE if success, FALSE if the rollback + segment could not support this many transactions */ + trx_t* trx, /* in: transaction */ + ulint rseg_id);/* in: rollback segment id; if ULINT_UNDEFINED + is passed, the system chooses the rollback segment + automatically in a round-robin fashion */ +/******************************************************************** +Starts a new transaction. */ +UNIV_INTERN +ibool +trx_start_low( +/*==========*/ + /* out: TRUE */ + trx_t* trx, /* in: transaction */ + ulint rseg_id);/* in: rollback segment id; if ULINT_UNDEFINED + is passed, the system chooses the rollback segment + automatically in a round-robin fashion */ +/***************************************************************** +Starts the transaction if it is not yet started. */ +UNIV_INLINE +void +trx_start_if_not_started( +/*=====================*/ + trx_t* trx); /* in: transaction */ +/***************************************************************** +Starts the transaction if it is not yet started. Assumes we have reserved +the kernel mutex! */ +UNIV_INLINE +void +trx_start_if_not_started_low( +/*=========================*/ + trx_t* trx); /* in: transaction */ +/******************************************************************** +Commits a transaction. */ +UNIV_INTERN +void +trx_commit_off_kernel( +/*==================*/ + trx_t* trx); /* in: transaction */ +/******************************************************************** +Cleans up a transaction at database startup. The cleanup is needed if +the transaction already got to the middle of a commit when the database +crashed, andf we cannot roll it back. */ +UNIV_INTERN +void +trx_cleanup_at_db_startup( +/*======================*/ + trx_t* trx); /* in: transaction */ +/************************************************************************** +Does the transaction commit for MySQL. */ +UNIV_INTERN +ulint +trx_commit_for_mysql( +/*=================*/ + /* out: DB_SUCCESS or error number */ + trx_t* trx); /* in: trx handle */ +/************************************************************************** +Does the transaction prepare for MySQL. */ +UNIV_INTERN +ulint +trx_prepare_for_mysql( +/*==================*/ + /* out: 0 or error number */ + trx_t* trx); /* in: trx handle */ +/************************************************************************** +This function is used to find number of prepared transactions and +their transaction objects for a recovery. */ +UNIV_INTERN +int +trx_recover_for_mysql( +/*==================*/ + /* out: number of prepared transactions */ + XID* xid_list, /* in/out: prepared transactions */ + ulint len); /* in: number of slots in xid_list */ +/*********************************************************************** +This function is used to find one X/Open XA distributed transaction +which is in the prepared state */ +UNIV_INTERN +trx_t * +trx_get_trx_by_xid( +/*===============*/ + /* out: trx or NULL */ + XID* xid); /* in: X/Open XA transaction identification */ +/************************************************************************** +If required, flushes the log to disk if we called trx_commit_for_mysql() +with trx->flush_log_later == TRUE. */ +UNIV_INTERN +ulint +trx_commit_complete_for_mysql( +/*==========================*/ + /* out: 0 or error number */ + trx_t* trx); /* in: trx handle */ +/************************************************************************** +Marks the latest SQL statement ended. */ +UNIV_INTERN +void +trx_mark_sql_stat_end( +/*==================*/ + trx_t* trx); /* in: trx handle */ +/************************************************************************ +Assigns a read view for a consistent read query. All the consistent reads +within the same transaction will get the same read view, which is created +when this function is first called for a new started transaction. */ +UNIV_INTERN +read_view_t* +trx_assign_read_view( +/*=================*/ + /* out: consistent read view */ + trx_t* trx); /* in: active transaction */ +/*************************************************************** +The transaction must be in the TRX_QUE_LOCK_WAIT state. Puts it to +the TRX_QUE_RUNNING state and releases query threads which were +waiting for a lock in the wait_thrs list. */ +UNIV_INTERN +void +trx_end_lock_wait( +/*==============*/ + trx_t* trx); /* in: transaction */ +/******************************************************************** +Sends a signal to a trx object. */ +UNIV_INTERN +void +trx_sig_send( +/*=========*/ + trx_t* trx, /* in: trx handle */ + ulint type, /* in: signal type */ + ulint sender, /* in: TRX_SIG_SELF or + TRX_SIG_OTHER_SESS */ + que_thr_t* receiver_thr, /* in: query thread which wants the + reply, or NULL; if type is + TRX_SIG_END_WAIT, this must be NULL */ + trx_savept_t* savept, /* in: possible rollback savepoint, or + NULL */ + que_thr_t** next_thr); /* in/out: next query thread to run; + if the value which is passed in is + a pointer to a NULL pointer, then the + calling function can start running + a new query thread; if the parameter + is NULL, it is ignored */ +/******************************************************************** +Send the reply message when a signal in the queue of the trx has +been handled. */ +UNIV_INTERN +void +trx_sig_reply( +/*==========*/ + trx_sig_t* sig, /* in: signal */ + que_thr_t** next_thr); /* in/out: next query thread to run; + if the value which is passed in is + a pointer to a NULL pointer, then the + calling function can start running + a new query thread */ +/******************************************************************** +Removes the signal object from a trx signal queue. */ +UNIV_INTERN +void +trx_sig_remove( +/*===========*/ + trx_t* trx, /* in: trx handle */ + trx_sig_t* sig); /* in, own: signal */ +/******************************************************************** +Starts handling of a trx signal. */ +UNIV_INTERN +void +trx_sig_start_handle( +/*=================*/ + trx_t* trx, /* in: trx handle */ + que_thr_t** next_thr); /* in/out: next query thread to run; + if the value which is passed in is + a pointer to a NULL pointer, then the + calling function can start running + a new query thread */ +/******************************************************************** +Ends signal handling. If the session is in the error state, and +trx->graph_before_signal_handling != NULL, returns control to the error +handling routine of the graph (currently only returns the control to the +graph root which then sends an error message to the client). */ +UNIV_INTERN +void +trx_end_signal_handling( +/*====================*/ + trx_t* trx); /* in: trx */ +/************************************************************************* +Creates a commit command node struct. */ +UNIV_INTERN +commit_node_t* +commit_node_create( +/*===============*/ + /* out, own: commit node struct */ + mem_heap_t* heap); /* in: mem heap where created */ +/*************************************************************** +Performs an execution step for a commit type node in a query graph. */ +UNIV_INTERN +que_thr_t* +trx_commit_step( +/*============*/ + /* out: query thread to run next, or NULL */ + que_thr_t* thr); /* in: query thread */ + +/************************************************************************** +Prints info about a transaction to the given file. The caller must own the +kernel mutex and must have called +innobase_mysql_prepare_print_arbitrary_thd(), unless he knows that MySQL +or InnoDB cannot meanwhile change the info printed here. */ +UNIV_INTERN +void +trx_print( +/*======*/ + FILE* f, /* in: output stream */ + trx_t* trx, /* in: transaction */ + ulint max_query_len); /* in: max query length to print, or 0 to + use the default max length */ + +/** Type of data dictionary operation */ +enum trx_dict_op { + /** The transaction is not modifying the data dictionary. */ + TRX_DICT_OP_NONE = 0, + /** The transaction is creating a table or an index, or + dropping a table. The table must be dropped in crash + recovery. This and TRX_DICT_OP_NONE are the only possible + operation modes in crash recovery. */ + TRX_DICT_OP_TABLE = 1, + /** The transaction is creating or dropping an index in an + existing table. In crash recovery, the the data dictionary + must be locked, but the table must not be dropped. */ + TRX_DICT_OP_INDEX = 2 +}; + +/************************************************************************** +Determine if a transaction is a dictionary operation. */ +UNIV_INLINE +enum trx_dict_op +trx_get_dict_operation( +/*===================*/ + /* out: dictionary operation mode */ + const trx_t* trx) /* in: transaction */ + __attribute__((pure)); +/************************************************************************** +Flag a transaction a dictionary operation. */ +UNIV_INLINE +void +trx_set_dict_operation( +/*===================*/ + trx_t* trx, /* in/out: transaction */ + enum trx_dict_op op); /* in: operation, not + TRX_DICT_OP_NONE */ + +#ifndef UNIV_HOTBACKUP +/************************************************************************** +Determines if the currently running transaction has been interrupted. */ +UNIV_INTERN +ibool +trx_is_interrupted( +/*===============*/ + /* out: TRUE if interrupted */ + trx_t* trx); /* in: transaction */ +#else /* !UNIV_HOTBACKUP */ +#define trx_is_interrupted(trx) FALSE +#endif /* !UNIV_HOTBACKUP */ + +/*********************************************************************** +Calculates the "weight" of a transaction. The weight of one transaction +is estimated as the number of altered rows + the number of locked rows. +*/ + +#define TRX_WEIGHT(t) \ + ut_dulint_add((t)->undo_no, UT_LIST_GET_LEN((t)->trx_locks)) + +/*********************************************************************** +Compares the "weight" (or size) of two transactions. Transactions that +have edited non-transactional tables are considered heavier than ones +that have not. */ +UNIV_INTERN +int +trx_weight_cmp( +/*===========*/ + /* out: <0, 0 or >0; similar to strcmp(3) */ + const trx_t* a, /* in: the first transaction to be compared */ + const trx_t* b); /* in: the second transaction to be compared */ + +/*********************************************************************** +Retrieves transacion's id, represented as unsigned long long. */ +UNIV_INLINE +ullint +trx_get_id( +/*=======*/ + /* out: transaction's id */ + const trx_t* trx); /* in: transaction */ + +/* Maximum length of a string that can be returned by +trx_get_que_state_str(). */ +#define TRX_QUE_STATE_STR_MAX_LEN 12 /* "ROLLING BACK" */ + +/*********************************************************************** +Retrieves transaction's que state in a human readable string. The string +should not be free()'d or modified. */ +UNIV_INLINE +const char* +trx_get_que_state_str( +/*==================*/ + /* out: string in the data segment */ + const trx_t* trx); /* in: transaction */ + +/* Signal to a transaction */ +struct trx_sig_struct{ + unsigned type:3; /* signal type */ + unsigned sender:1; /* TRX_SIG_SELF or + TRX_SIG_OTHER_SESS */ + que_thr_t* receiver; /* non-NULL if the sender of the signal + wants reply after the operation induced + by the signal is completed */ + trx_savept_t savept; /* possible rollback savepoint */ + UT_LIST_NODE_T(trx_sig_t) + signals; /* queue of pending signals to the + transaction */ + UT_LIST_NODE_T(trx_sig_t) + reply_signals; /* list of signals for which the sender + transaction is waiting a reply */ +}; + +#define TRX_MAGIC_N 91118598 + +/* The transaction handle; every session has a trx object which is freed only +when the session is freed; in addition there may be session-less transactions +rolling back after a database recovery */ + +struct trx_struct{ + ulint magic_n; + /* All the next fields are protected by the kernel mutex, except the + undo logs which are protected by undo_mutex */ + const char* op_info; /* English text describing the + current operation, or an empty + string */ + unsigned is_purge:1; /* 0=user transaction, 1=purge */ + unsigned is_recovered:1; /* 0=normal transaction, + 1=recovered, must be rolled back */ + unsigned conc_state:2; /* state of the trx from the point + of view of concurrency control: + TRX_ACTIVE, TRX_COMMITTED_IN_MEMORY, + ... */ + unsigned que_state:2; /* valid when conc_state == TRX_ACTIVE: + TRX_QUE_RUNNING, TRX_QUE_LOCK_WAIT, + ... */ + unsigned isolation_level:2;/* TRX_ISO_REPEATABLE_READ, ... */ + unsigned check_foreigns:1;/* normally TRUE, but if the user + wants to suppress foreign key checks, + (in table imports, for example) we + set this FALSE */ + unsigned check_unique_secondary:1; + /* normally TRUE, but if the user + wants to speed up inserts by + suppressing unique key checks + for secondary indexes when we decide + if we can use the insert buffer for + them, we set this FALSE */ + unsigned support_xa:1; /* normally we do the XA two-phase + commit steps, but by setting this to + FALSE, one can save CPU time and about + 150 bytes in the undo log size as then + we skip XA steps */ + unsigned flush_log_later:1;/* when we commit the transaction + in MySQL's binlog write, we will + flush the log to disk later in + a separate call */ + unsigned must_flush_log_later:1;/* this flag is set to TRUE in + trx_commit_off_kernel() if + flush_log_later was TRUE, and there + were modifications by the transaction; + in that case we must flush the log + in trx_commit_complete_for_mysql() */ + unsigned dict_operation:2;/**< @see enum trx_dict_op */ + unsigned duplicates:2; /* TRX_DUP_IGNORE | TRX_DUP_REPLACE */ + unsigned active_trans:2; /* 1 - if a transaction in MySQL + is active. 2 - if prepare_commit_mutex + was taken */ + unsigned has_search_latch:1; + /* TRUE if this trx has latched the + search system latch in S-mode */ + unsigned declared_to_be_inside_innodb:1; + /* this is TRUE if we have declared + this transaction in + srv_conc_enter_innodb to be inside the + InnoDB engine */ + unsigned handling_signals:1;/* this is TRUE as long as the trx + is handling signals */ + unsigned dict_operation_lock_mode:2; + /* 0, RW_S_LATCH, or RW_X_LATCH: + the latch mode trx currently holds + on dict_operation_lock */ + time_t start_time; /* time the trx object was created + or the state last time became + TRX_ACTIVE */ + dulint id; /* transaction id */ + XID xid; /* X/Open XA transaction + identification to identify a + transaction branch */ + dulint no; /* transaction serialization number == + max trx id when the transaction is + moved to COMMITTED_IN_MEMORY state */ + ib_uint64_t commit_lsn; /* lsn at the time of the commit */ + dulint table_id; /* Table to drop iff dict_operation + is TRUE, or ut_dulint_zero. */ + /*------------------------------*/ + void* mysql_thd; /* MySQL thread handle corresponding + to this trx, or NULL */ + char** mysql_query_str;/* pointer to the field in mysqld_thd + which contains the pointer to the + current SQL query string */ + const char* mysql_log_file_name; + /* if MySQL binlog is used, this field + contains a pointer to the latest file + name; this is NULL if binlog is not + used */ + ib_int64_t mysql_log_offset;/* if MySQL binlog is used, this field + contains the end offset of the binlog + entry */ + const char* mysql_master_log_file_name; + /* if the database server is a MySQL + replication slave, we have here the + master binlog name up to which + replication has processed; otherwise + this is a pointer to a null + character */ + ib_int64_t mysql_master_log_pos; + /* if the database server is a MySQL + replication slave, this is the + position in the log file up to which + replication has processed */ + const char* mysql_relay_log_file_name; + ib_int64_t mysql_relay_log_pos; + + os_thread_id_t mysql_thread_id;/* id of the MySQL thread associated + with this transaction object */ + ulint mysql_process_no;/* since in Linux, 'top' reports + process id's and not thread id's, we + store the process number too */ + /*------------------------------*/ + ulint n_mysql_tables_in_use; /* number of Innobase tables + used in the processing of the current + SQL statement in MySQL */ + ulint mysql_n_tables_locked; + /* how many tables the current SQL + statement uses, except those + in consistent read */ + ulint search_latch_timeout; + /* If we notice that someone is + waiting for our S-lock on the search + latch to be released, we wait in + row0sel.c for BTR_SEA_TIMEOUT new + searches until we try to keep + the search latch again over + calls from MySQL; this is intended + to reduce contention on the search + latch */ + /*------------------------------*/ + ulint n_tickets_to_enter_innodb; + /* this can be > 0 only when + declared_to_... is TRUE; when we come + to srv_conc_innodb_enter, if the value + here is > 0, we decrement this by 1 */ + /*------------------------------*/ + dict_index_t* new_rec_locks[2];/* these are normally NULL; if + srv_locks_unsafe_for_binlog is TRUE + or session is using READ COMMITTED + isolation level, + in a cursor search, if we set a new + record lock on an index, this is set + to point to the index; this is + used in releasing the locks under the + cursors if we are performing an UPDATE + and we determine after retrieving + the row that it does not need to be + locked; thus, these can be used to + implement a 'mini-rollback' that + releases the latest record locks */ + UT_LIST_NODE_T(trx_t) + trx_list; /* list of transactions */ + UT_LIST_NODE_T(trx_t) + mysql_trx_list; /* list of transactions created for + MySQL */ + /*------------------------------*/ + ulint error_state; /* 0 if no error, otherwise error + number; NOTE That ONLY the thread + doing the transaction is allowed to + set this field: this is NOT protected + by the kernel mutex */ + const dict_index_t*error_info; /* if the error number indicates a + duplicate key error, a pointer to + the problematic index is stored here */ + ulint error_key_num; /* if the index creation fails to a + duplicate key error, a mysql key + number of that index is stored here */ + sess_t* sess; /* session of the trx, NULL if none */ + que_t* graph; /* query currently run in the session, + or NULL if none; NOTE that the query + belongs to the session, and it can + survive over a transaction commit, if + it is a stored procedure with a COMMIT + WORK statement, for instance */ + ulint n_active_thrs; /* number of active query threads */ + que_t* graph_before_signal_handling; + /* value of graph when signal handling + for this trx started: this is used to + return control to the original query + graph for error processing */ + trx_sig_t sig; /* one signal object can be allocated + in this space, avoiding mem_alloc */ + UT_LIST_BASE_NODE_T(trx_sig_t) + signals; /* queue of processed or pending + signals to the trx */ + UT_LIST_BASE_NODE_T(trx_sig_t) + reply_signals; /* list of signals sent by the query + threads of this trx for which a thread + is waiting for a reply; if this trx is + killed, the reply requests in the list + must be canceled */ + /*------------------------------*/ + lock_t* wait_lock; /* if trx execution state is + TRX_QUE_LOCK_WAIT, this points to + the lock request, otherwise this is + NULL */ + ibool was_chosen_as_deadlock_victim; + /* when the transaction decides to wait + for a lock, it sets this to FALSE; + if another transaction chooses this + transaction as a victim in deadlock + resolution, it sets this to TRUE */ + time_t wait_started; /* lock wait started at this time */ + UT_LIST_BASE_NODE_T(que_thr_t) + wait_thrs; /* query threads belonging to this + trx that are in the QUE_THR_LOCK_WAIT + state */ + ulint deadlock_mark; /* a mark field used in deadlock + checking algorithm. This must be + in its own machine word, because + it can be changed by other + threads while holding kernel_mutex. */ + /*------------------------------*/ + mem_heap_t* lock_heap; /* memory heap for the locks of the + transaction */ + UT_LIST_BASE_NODE_T(lock_t) + trx_locks; /* locks reserved by the transaction */ + /*------------------------------*/ + mem_heap_t* global_read_view_heap; + /* memory heap for the global read + view */ + read_view_t* global_read_view; + /* consistent read view associated + to a transaction or NULL */ + read_view_t* read_view; /* consistent read view used in the + transaction or NULL, this read view + if defined can be normal read view + associated to a transaction (i.e. + same as global_read_view) or read view + associated to a cursor */ + /*------------------------------*/ + UT_LIST_BASE_NODE_T(trx_named_savept_t) + trx_savepoints; /* savepoints set with SAVEPOINT ..., + oldest first */ + /*------------------------------*/ + mutex_t undo_mutex; /* mutex protecting the fields in this + section (down to undo_no_arr), EXCEPT + last_sql_stat_start, which can be + accessed only when we know that there + cannot be any activity in the undo + logs! */ + dulint undo_no; /* next undo log record number to + assign; since the undo log is + private for a transaction, this + is a simple ascending sequence + with no gaps; thus it represents + the number of modified/inserted + rows in a transaction */ + trx_savept_t last_sql_stat_start; + /* undo_no when the last sql statement + was started: in case of an error, trx + is rolled back down to this undo + number; see note at undo_mutex! */ + trx_rseg_t* rseg; /* rollback segment assigned to the + transaction, or NULL if not assigned + yet */ + trx_undo_t* insert_undo; /* pointer to the insert undo log, or + NULL if no inserts performed yet */ + trx_undo_t* update_undo; /* pointer to the update undo log, or + NULL if no update performed yet */ + dulint roll_limit; /* least undo number to undo during + a rollback */ + ulint pages_undone; /* number of undo log pages undone + since the last undo log truncation */ + trx_undo_arr_t* undo_no_arr; /* array of undo numbers of undo log + records which are currently processed + by a rollback operation */ + /*------------------------------*/ + ulint n_autoinc_rows; /* no. of AUTO-INC rows required for + an SQL statement. This is useful for + multi-row INSERTs */ + ib_vector_t* autoinc_locks; /* AUTOINC locks held by this + transaction. Note that these are + also in the lock list trx_locks. This + vector needs to be freed explicitly + when the trx_t instance is desrtoyed */ + /*------------------------------*/ + char detailed_error[256]; /* detailed error message for last + error, or empty. */ +}; + +#define TRX_MAX_N_THREADS 32 /* maximum number of + concurrent threads running a + single operation of a + transaction, e.g., a parallel + query */ +/* Transaction concurrency states (trx->conc_state) */ +#define TRX_NOT_STARTED 0 +#define TRX_ACTIVE 1 +#define TRX_COMMITTED_IN_MEMORY 2 +#define TRX_PREPARED 3 /* Support for 2PC/XA */ + +/* Transaction execution states when trx->conc_state == TRX_ACTIVE */ +#define TRX_QUE_RUNNING 0 /* transaction is running */ +#define TRX_QUE_LOCK_WAIT 1 /* transaction is waiting for a lock */ +#define TRX_QUE_ROLLING_BACK 2 /* transaction is rolling back */ +#define TRX_QUE_COMMITTING 3 /* transaction is committing */ + +/* Transaction isolation levels (trx->isolation_level) */ +#define TRX_ISO_READ_UNCOMMITTED 0 /* dirty read: non-locking + SELECTs are performed so that + we do not look at a possible + earlier version of a record; + thus they are not 'consistent' + reads under this isolation + level; otherwise like level + 2 */ + +#define TRX_ISO_READ_COMMITTED 1 /* somewhat Oracle-like + isolation, except that in + range UPDATE and DELETE we + must block phantom rows + with next-key locks; + SELECT ... FOR UPDATE and ... + LOCK IN SHARE MODE only lock + the index records, NOT the + gaps before them, and thus + allow free inserting; + each consistent read reads its + own snapshot */ + +#define TRX_ISO_REPEATABLE_READ 2 /* this is the default; + all consistent reads in the + same trx read the same + snapshot; + full next-key locking used + in locking reads to block + insertions into gaps */ + +#define TRX_ISO_SERIALIZABLE 3 /* all plain SELECTs are + converted to LOCK IN SHARE + MODE reads */ + +/* Treatment of duplicate values (trx->duplicates; for example, in inserts). +Multiple flags can be combined with bitwise OR. */ +#define TRX_DUP_IGNORE 1 /* duplicate rows are to be updated */ +#define TRX_DUP_REPLACE 2 /* duplicate rows are to be replaced */ + + +/* Types of a trx signal */ +#define TRX_SIG_NO_SIGNAL 0 +#define TRX_SIG_TOTAL_ROLLBACK 1 +#define TRX_SIG_ROLLBACK_TO_SAVEPT 2 +#define TRX_SIG_COMMIT 3 +#define TRX_SIG_ERROR_OCCURRED 4 +#define TRX_SIG_BREAK_EXECUTION 5 + +/* Sender types of a signal */ +#define TRX_SIG_SELF 0 /* sent by the session itself, or + by an error occurring within this + session */ +#define TRX_SIG_OTHER_SESS 1 /* sent by another session (which + must hold rights to this) */ + +/* Commit command node in a query graph */ +struct commit_node_struct{ + que_common_t common; /* node type: QUE_NODE_COMMIT */ + ulint state; /* node execution state */ +}; + +/* Commit node states */ +#define COMMIT_NODE_SEND 1 +#define COMMIT_NODE_WAIT 2 + + +#ifndef UNIV_NONINL +#include "trx0trx.ic" +#endif + +#endif diff --git a/storage/xtradb/include/trx0trx.ic b/storage/xtradb/include/trx0trx.ic new file mode 100644 index 00000000000..6da89f002fe --- /dev/null +++ b/storage/xtradb/include/trx0trx.ic @@ -0,0 +1,221 @@ +/***************************************************************************** + +Copyright (c) 1996, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/****************************************************** +The transaction + +Created 3/26/1996 Heikki Tuuri +*******************************************************/ + +/***************************************************************** +Starts the transaction if it is not yet started. */ +UNIV_INLINE +void +trx_start_if_not_started( +/*=====================*/ + trx_t* trx) /* in: transaction */ +{ + ut_ad(trx->conc_state != TRX_COMMITTED_IN_MEMORY); + + if (trx->conc_state == TRX_NOT_STARTED) { + + trx_start(trx, ULINT_UNDEFINED); + } +} + +/***************************************************************** +Starts the transaction if it is not yet started. Assumes we have reserved +the kernel mutex! */ +UNIV_INLINE +void +trx_start_if_not_started_low( +/*=========================*/ + trx_t* trx) /* in: transaction */ +{ + ut_ad(trx->conc_state != TRX_COMMITTED_IN_MEMORY); + + if (trx->conc_state == TRX_NOT_STARTED) { + + trx_start_low(trx, ULINT_UNDEFINED); + } +} + +/***************************************************************** +Resets the new record lock info in a transaction struct. */ +UNIV_INLINE +void +trx_reset_new_rec_lock_info( +/*========================*/ + trx_t* trx) /* in: transaction struct */ +{ + trx->new_rec_locks[0] = NULL; + trx->new_rec_locks[1] = NULL; +} + +/***************************************************************** +Registers that we have set a new record lock on an index. We only have space +to store 2 indexes! If this is called to store more than 2 indexes after +trx_reset_new_rec_lock_info(), then this function does nothing. */ +UNIV_INLINE +void +trx_register_new_rec_lock( +/*======================*/ + trx_t* trx, /* in: transaction struct */ + dict_index_t* index) /* in: trx sets a new record lock on this + index */ +{ + if (trx->new_rec_locks[0] == NULL) { + trx->new_rec_locks[0] = index; + + return; + } + + if (trx->new_rec_locks[0] == index) { + + return; + } + + if (trx->new_rec_locks[1] != NULL) { + + return; + } + + trx->new_rec_locks[1] = index; +} + +/***************************************************************** +Checks if trx has set a new record lock on an index. */ +UNIV_INLINE +ibool +trx_new_rec_locks_contain( +/*======================*/ + /* out: TRUE if trx has set a new record lock + on index */ + trx_t* trx, /* in: transaction struct */ + dict_index_t* index) /* in: index */ +{ + return(trx->new_rec_locks[0] == index + || trx->new_rec_locks[1] == index); +} + +/******************************************************************** +Retrieves the error_info field from a trx. */ +UNIV_INLINE +const dict_index_t* +trx_get_error_info( +/*===============*/ + /* out: the error info */ + const trx_t* trx) /* in: trx object */ +{ + return(trx->error_info); +} + +/*********************************************************************** +Retrieves transacion's id, represented as unsigned long long. */ +UNIV_INLINE +ullint +trx_get_id( +/*=======*/ + /* out: transaction's id */ + const trx_t* trx) /* in: transaction */ +{ + return((ullint)ut_conv_dulint_to_longlong(trx->id)); +} + +/*********************************************************************** +Retrieves transaction's que state in a human readable string. The string +should not be free()'d or modified. */ +UNIV_INLINE +const char* +trx_get_que_state_str( +/*==================*/ + /* out: string in the data segment */ + const trx_t* trx) /* in: transaction */ +{ + /* be sure to adjust TRX_QUE_STATE_STR_MAX_LEN if you change this */ + switch (trx->que_state) { + case TRX_QUE_RUNNING: + return("RUNNING"); + case TRX_QUE_LOCK_WAIT: + return("LOCK WAIT"); + case TRX_QUE_ROLLING_BACK: + return("ROLLING BACK"); + case TRX_QUE_COMMITTING: + return("COMMITTING"); + default: + return("UNKNOWN"); + } +} + +/************************************************************************** +Determine if a transaction is a dictionary operation. */ +UNIV_INLINE +enum trx_dict_op +trx_get_dict_operation( +/*===================*/ + /* out: dictionary operation mode */ + const trx_t* trx) /* in: transaction */ +{ + enum trx_dict_op op = (enum trx_dict_op) trx->dict_operation; + +#ifdef UNIV_DEBUG + switch (op) { + case TRX_DICT_OP_NONE: + case TRX_DICT_OP_TABLE: + case TRX_DICT_OP_INDEX: + return(op); + } + ut_error; +#endif /* UNIV_DEBUG */ + return((enum trx_dict_op) UNIV_EXPECT(op, TRX_DICT_OP_NONE)); +} +/************************************************************************** +Flag a transaction a dictionary operation. */ +UNIV_INLINE +void +trx_set_dict_operation( +/*===================*/ + trx_t* trx, /* in/out: transaction */ + enum trx_dict_op op) /* in: operation, not + TRX_DICT_OP_NONE */ +{ +#ifdef UNIV_DEBUG + enum trx_dict_op old_op = trx_get_dict_operation(trx); + + switch (op) { + case TRX_DICT_OP_NONE: + ut_error; + break; + case TRX_DICT_OP_TABLE: + switch (old_op) { + case TRX_DICT_OP_NONE: + case TRX_DICT_OP_INDEX: + case TRX_DICT_OP_TABLE: + goto ok; + } + ut_error; + break; + case TRX_DICT_OP_INDEX: + ut_ad(old_op == TRX_DICT_OP_NONE); + break; + } +ok: +#endif /* UNIV_DEBUG */ + + trx->dict_operation = op; +} diff --git a/storage/xtradb/include/trx0types.h b/storage/xtradb/include/trx0types.h new file mode 100644 index 00000000000..896f4e8c0a2 --- /dev/null +++ b/storage/xtradb/include/trx0types.h @@ -0,0 +1,78 @@ +/***************************************************************************** + +Copyright (c) 1996, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/****************************************************** +Transaction system global type definitions + +Created 3/26/1996 Heikki Tuuri +*******************************************************/ + +#ifndef trx0types_h +#define trx0types_h + +#include "ut0byte.h" + +/* prepare trx_t::id for being printed via printf(3) */ +#define TRX_ID_PREP_PRINTF(id) (ullint) ut_conv_dulint_to_longlong(id) + +/* printf(3) format used for printing TRX_ID_PRINTF_PREP() */ +#define TRX_ID_FMT "%llX" + +/* maximum length that a formatted trx_t::id could take, not including +the terminating '\0'. */ +#define TRX_ID_MAX_LEN 17 + +/* Memory objects */ +typedef struct trx_struct trx_t; +typedef struct trx_sys_struct trx_sys_t; +typedef struct trx_doublewrite_struct trx_doublewrite_t; +typedef struct trx_sig_struct trx_sig_t; +typedef struct trx_rseg_struct trx_rseg_t; +typedef struct trx_undo_struct trx_undo_t; +typedef struct trx_undo_arr_struct trx_undo_arr_t; +typedef struct trx_undo_inf_struct trx_undo_inf_t; +typedef struct trx_purge_struct trx_purge_t; +typedef struct roll_node_struct roll_node_t; +typedef struct commit_node_struct commit_node_t; +typedef struct trx_named_savept_struct trx_named_savept_t; + +/* Rollback contexts */ +enum trx_rb_ctx { + RB_NONE = 0, /* no rollback */ + RB_NORMAL, /* normal rollback */ + RB_RECOVERY, /* rolling back an incomplete transaction, + in crash recovery */ +}; + +/* Transaction savepoint */ +typedef struct trx_savept_struct trx_savept_t; +struct trx_savept_struct{ + dulint least_undo_no; /* least undo number to undo */ +}; + +/* File objects */ +typedef byte trx_sysf_t; +typedef byte trx_rsegf_t; +typedef byte trx_usegf_t; +typedef byte trx_ulogf_t; +typedef byte trx_upagef_t; + +/* Undo log record */ +typedef byte trx_undo_rec_t; + +#endif diff --git a/storage/xtradb/include/trx0undo.h b/storage/xtradb/include/trx0undo.h new file mode 100644 index 00000000000..7f7408931da --- /dev/null +++ b/storage/xtradb/include/trx0undo.h @@ -0,0 +1,526 @@ +/***************************************************************************** + +Copyright (c) 1996, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/****************************************************** +Transaction undo log + +Created 3/26/1996 Heikki Tuuri +*******************************************************/ + +#ifndef trx0undo_h +#define trx0undo_h + +#include "univ.i" +#include "trx0types.h" +#include "mtr0mtr.h" +#include "trx0sys.h" +#include "page0types.h" +#include "trx0xa.h" + +/*************************************************************************** +Builds a roll pointer dulint. */ +UNIV_INLINE +dulint +trx_undo_build_roll_ptr( +/*====================*/ + /* out: roll pointer */ + ibool is_insert, /* in: TRUE if insert undo log */ + ulint rseg_id, /* in: rollback segment id */ + ulint page_no, /* in: page number */ + ulint offset); /* in: offset of the undo entry within page */ +/*************************************************************************** +Decodes a roll pointer dulint. */ +UNIV_INLINE +void +trx_undo_decode_roll_ptr( +/*=====================*/ + dulint roll_ptr, /* in: roll pointer */ + ibool* is_insert, /* out: TRUE if insert undo log */ + ulint* rseg_id, /* out: rollback segment id */ + ulint* page_no, /* out: page number */ + ulint* offset); /* out: offset of the undo entry within page */ +/*************************************************************************** +Returns TRUE if the roll pointer is of the insert type. */ +UNIV_INLINE +ibool +trx_undo_roll_ptr_is_insert( +/*========================*/ + /* out: TRUE if insert undo log */ + dulint roll_ptr); /* in: roll pointer */ +/********************************************************************* +Writes a roll ptr to an index page. In case that the size changes in +some future version, this function should be used instead of +mach_write_... */ +UNIV_INLINE +void +trx_write_roll_ptr( +/*===============*/ + byte* ptr, /* in: pointer to memory where written */ + dulint roll_ptr); /* in: roll ptr */ +/********************************************************************* +Reads a roll ptr from an index page. In case that the roll ptr size +changes in some future version, this function should be used instead of +mach_read_... */ +UNIV_INLINE +dulint +trx_read_roll_ptr( +/*==============*/ + /* out: roll ptr */ + const byte* ptr); /* in: pointer to memory from where to read */ +/********************************************************************** +Gets an undo log page and x-latches it. */ +UNIV_INLINE +page_t* +trx_undo_page_get( +/*==============*/ + /* out: pointer to page x-latched */ + ulint space, /* in: space where placed */ + ulint zip_size, /* in: compressed page size in bytes + or 0 for uncompressed pages */ + ulint page_no, /* in: page number */ + mtr_t* mtr); /* in: mtr */ +/********************************************************************** +Gets an undo log page and s-latches it. */ +UNIV_INLINE +page_t* +trx_undo_page_get_s_latched( +/*========================*/ + /* out: pointer to page s-latched */ + ulint space, /* in: space where placed */ + ulint zip_size, /* in: compressed page size in bytes + or 0 for uncompressed pages */ + ulint page_no, /* in: page number */ + mtr_t* mtr); /* in: mtr */ +/********************************************************************** +Returns the previous undo record on the page in the specified log, or +NULL if none exists. */ +UNIV_INLINE +trx_undo_rec_t* +trx_undo_page_get_prev_rec( +/*=======================*/ + /* out: pointer to record, NULL if none */ + trx_undo_rec_t* rec, /* in: undo log record */ + ulint page_no,/* in: undo log header page number */ + ulint offset);/* in: undo log header offset on page */ +/********************************************************************** +Returns the next undo log record on the page in the specified log, or +NULL if none exists. */ +UNIV_INLINE +trx_undo_rec_t* +trx_undo_page_get_next_rec( +/*=======================*/ + /* out: pointer to record, NULL if none */ + trx_undo_rec_t* rec, /* in: undo log record */ + ulint page_no,/* in: undo log header page number */ + ulint offset);/* in: undo log header offset on page */ +/********************************************************************** +Returns the last undo record on the page in the specified undo log, or +NULL if none exists. */ +UNIV_INLINE +trx_undo_rec_t* +trx_undo_page_get_last_rec( +/*=======================*/ + /* out: pointer to record, NULL if none */ + page_t* undo_page,/* in: undo log page */ + ulint page_no,/* in: undo log header page number */ + ulint offset); /* in: undo log header offset on page */ +/********************************************************************** +Returns the first undo record on the page in the specified undo log, or +NULL if none exists. */ +UNIV_INLINE +trx_undo_rec_t* +trx_undo_page_get_first_rec( +/*========================*/ + /* out: pointer to record, NULL if none */ + page_t* undo_page,/* in: undo log page */ + ulint page_no,/* in: undo log header page number */ + ulint offset);/* in: undo log header offset on page */ +/*************************************************************************** +Gets the previous record in an undo log. */ +UNIV_INTERN +trx_undo_rec_t* +trx_undo_get_prev_rec( +/*==================*/ + /* out: undo log record, the page s-latched, + NULL if none */ + trx_undo_rec_t* rec, /* in: undo record */ + ulint page_no,/* in: undo log header page number */ + ulint offset, /* in: undo log header offset on page */ + mtr_t* mtr); /* in: mtr */ +/*************************************************************************** +Gets the next record in an undo log. */ +UNIV_INTERN +trx_undo_rec_t* +trx_undo_get_next_rec( +/*==================*/ + /* out: undo log record, the page s-latched, + NULL if none */ + trx_undo_rec_t* rec, /* in: undo record */ + ulint page_no,/* in: undo log header page number */ + ulint offset, /* in: undo log header offset on page */ + mtr_t* mtr); /* in: mtr */ +/*************************************************************************** +Gets the first record in an undo log. */ +UNIV_INTERN +trx_undo_rec_t* +trx_undo_get_first_rec( +/*===================*/ + /* out: undo log record, the page latched, NULL if + none */ + ulint space, /* in: undo log header space */ + ulint zip_size,/* in: compressed page size in bytes + or 0 for uncompressed pages */ + ulint page_no,/* in: undo log header page number */ + ulint offset, /* in: undo log header offset on page */ + ulint mode, /* in: latching mode: RW_S_LATCH or RW_X_LATCH */ + mtr_t* mtr); /* in: mtr */ +/************************************************************************ +Tries to add a page to the undo log segment where the undo log is placed. */ +UNIV_INTERN +ulint +trx_undo_add_page( +/*==============*/ + /* out: page number if success, else + FIL_NULL */ + trx_t* trx, /* in: transaction */ + trx_undo_t* undo, /* in: undo log memory object */ + mtr_t* mtr); /* in: mtr which does not have a latch to any + undo log page; the caller must have reserved + the rollback segment mutex */ +/*************************************************************************** +Truncates an undo log from the end. This function is used during a rollback +to free space from an undo log. */ +UNIV_INTERN +void +trx_undo_truncate_end( +/*==================*/ + trx_t* trx, /* in: transaction whose undo log it is */ + trx_undo_t* undo, /* in: undo log */ + dulint limit); /* in: all undo records with undo number + >= this value should be truncated */ +/*************************************************************************** +Truncates an undo log from the start. This function is used during a purge +operation. */ +UNIV_INTERN +void +trx_undo_truncate_start( +/*====================*/ + trx_rseg_t* rseg, /* in: rollback segment */ + ulint space, /* in: space id of the log */ + ulint hdr_page_no, /* in: header page number */ + ulint hdr_offset, /* in: header offset on the page */ + dulint limit); /* in: all undo pages with undo numbers < + this value should be truncated; NOTE that + the function only frees whole pages; the + header page is not freed, but emptied, if + all the records there are < limit */ +/************************************************************************ +Initializes the undo log lists for a rollback segment memory copy. +This function is only called when the database is started or a new +rollback segment created. */ +UNIV_INTERN +ulint +trx_undo_lists_init( +/*================*/ + /* out: the combined size of undo log segments + in pages */ + trx_rseg_t* rseg); /* in: rollback segment memory object */ +/************************************************************************** +Assigns an undo log for a transaction. A new undo log is created or a cached +undo log reused. */ +UNIV_INTERN +ulint +trx_undo_assign_undo( +/*=================*/ + /* out: DB_SUCCESS if undo log assign + successful, possible error codes are: + DB_TOO_MANY_CONCURRENT_TRXS + DB_OUT_OF_FILE_SPACE DB_OUT_OF_MEMORY*/ + trx_t* trx, /* in: transaction */ + ulint type); /* in: TRX_UNDO_INSERT or TRX_UNDO_UPDATE */ +/********************************************************************** +Sets the state of the undo log segment at a transaction finish. */ +UNIV_INTERN +page_t* +trx_undo_set_state_at_finish( +/*=========================*/ + /* out: undo log segment header page, + x-latched */ + trx_rseg_t* rseg, /* in: rollback segment memory object */ + trx_t* trx, /* in: transaction */ + trx_undo_t* undo, /* in: undo log memory copy */ + mtr_t* mtr); /* in: mtr */ +/********************************************************************** +Sets the state of the undo log segment at a transaction prepare. */ +UNIV_INTERN +page_t* +trx_undo_set_state_at_prepare( +/*==========================*/ + /* out: undo log segment header page, + x-latched */ + trx_t* trx, /* in: transaction */ + trx_undo_t* undo, /* in: undo log memory copy */ + mtr_t* mtr); /* in: mtr */ + +/************************************************************************** +Adds the update undo log header as the first in the history list, and +frees the memory object, or puts it to the list of cached update undo log +segments. */ +UNIV_INTERN +void +trx_undo_update_cleanup( +/*====================*/ + trx_t* trx, /* in: trx owning the update undo log */ + page_t* undo_page, /* in: update undo log header page, + x-latched */ + mtr_t* mtr); /* in: mtr */ +/********************************************************************** +Frees or caches an insert undo log after a transaction commit or rollback. +Knowledge of inserts is not needed after a commit or rollback, therefore +the data can be discarded. */ +UNIV_INTERN +void +trx_undo_insert_cleanup( +/*====================*/ + trx_t* trx); /* in: transaction handle */ +/*************************************************************** +Parses the redo log entry of an undo log page initialization. */ +UNIV_INTERN +byte* +trx_undo_parse_page_init( +/*=====================*/ + /* out: end of log record or NULL */ + byte* ptr, /* in: buffer */ + byte* end_ptr,/* in: buffer end */ + page_t* page, /* in: page or NULL */ + mtr_t* mtr); /* in: mtr or NULL */ +/*************************************************************** +Parses the redo log entry of an undo log page header create or reuse. */ +UNIV_INTERN +byte* +trx_undo_parse_page_header( +/*=======================*/ + /* out: end of log record or NULL */ + ulint type, /* in: MLOG_UNDO_HDR_CREATE or MLOG_UNDO_HDR_REUSE */ + byte* ptr, /* in: buffer */ + byte* end_ptr,/* in: buffer end */ + page_t* page, /* in: page or NULL */ + mtr_t* mtr); /* in: mtr or NULL */ +/*************************************************************** +Parses the redo log entry of an undo log page header discard. */ +UNIV_INTERN +byte* +trx_undo_parse_discard_latest( +/*==========================*/ + /* out: end of log record or NULL */ + byte* ptr, /* in: buffer */ + byte* end_ptr,/* in: buffer end */ + page_t* page, /* in: page or NULL */ + mtr_t* mtr); /* in: mtr or NULL */ + +/* Types of an undo log segment */ +#define TRX_UNDO_INSERT 1 /* contains undo entries for inserts */ +#define TRX_UNDO_UPDATE 2 /* contains undo entries for updates + and delete markings: in short, + modifys (the name 'UPDATE' is a + historical relic) */ +/* States of an undo log segment */ +#define TRX_UNDO_ACTIVE 1 /* contains an undo log of an active + transaction */ +#define TRX_UNDO_CACHED 2 /* cached for quick reuse */ +#define TRX_UNDO_TO_FREE 3 /* insert undo segment can be freed */ +#define TRX_UNDO_TO_PURGE 4 /* update undo segment will not be + reused: it can be freed in purge when + all undo data in it is removed */ +#define TRX_UNDO_PREPARED 5 /* contains an undo log of an + prepared transaction */ + +/* Transaction undo log memory object; this is protected by the undo_mutex +in the corresponding transaction object */ + +struct trx_undo_struct{ + /*-----------------------------*/ + ulint id; /* undo log slot number within the + rollback segment */ + ulint type; /* TRX_UNDO_INSERT or + TRX_UNDO_UPDATE */ + ulint state; /* state of the corresponding undo log + segment */ + ibool del_marks; /* relevant only in an update undo log: + this is TRUE if the transaction may + have delete marked records, because of + a delete of a row or an update of an + indexed field; purge is then + necessary; also TRUE if the transaction + has updated an externally stored + field */ + dulint trx_id; /* id of the trx assigned to the undo + log */ + XID xid; /* X/Open XA transaction + identification */ + ibool dict_operation; /* TRUE if a dict operation trx */ + dulint table_id; /* if a dict operation, then the table + id */ + trx_rseg_t* rseg; /* rseg where the undo log belongs */ + /*-----------------------------*/ + ulint space; /* space id where the undo log + placed */ + ulint zip_size; /* in: compressed page size of space + in bytes, or 0 for uncompressed */ + ulint hdr_page_no; /* page number of the header page in + the undo log */ + ulint hdr_offset; /* header offset of the undo log on the + page */ + ulint last_page_no; /* page number of the last page in the + undo log; this may differ from + top_page_no during a rollback */ + ulint size; /* current size in pages */ + /*-----------------------------*/ + ulint empty; /* TRUE if the stack of undo log + records is currently empty */ + ulint top_page_no; /* page number where the latest undo + log record was catenated; during + rollback the page from which the latest + undo record was chosen */ + ulint top_offset; /* offset of the latest undo record, + i.e., the topmost element in the undo + log if we think of it as a stack */ + dulint top_undo_no; /* undo number of the latest record */ + buf_block_t* guess_block; /* guess for the buffer block where + the top page might reside */ + /*-----------------------------*/ + UT_LIST_NODE_T(trx_undo_t) undo_list; + /* undo log objects in the rollback + segment are chained into lists */ +}; + +/* The offset of the undo log page header on pages of the undo log */ +#define TRX_UNDO_PAGE_HDR FSEG_PAGE_DATA +/*-------------------------------------------------------------*/ +/* Transaction undo log page header offsets */ +#define TRX_UNDO_PAGE_TYPE 0 /* TRX_UNDO_INSERT or + TRX_UNDO_UPDATE */ +#define TRX_UNDO_PAGE_START 2 /* Byte offset where the undo log + records for the LATEST transaction + start on this page (remember that + in an update undo log, the first page + can contain several undo logs) */ +#define TRX_UNDO_PAGE_FREE 4 /* On each page of the undo log this + field contains the byte offset of the + first free byte on the page */ +#define TRX_UNDO_PAGE_NODE 6 /* The file list node in the chain + of undo log pages */ +/*-------------------------------------------------------------*/ +#define TRX_UNDO_PAGE_HDR_SIZE (6 + FLST_NODE_SIZE) + +/* An update undo segment with just one page can be reused if it has +< this number bytes used; we must leave space at least for one new undo +log header on the page */ + +#define TRX_UNDO_PAGE_REUSE_LIMIT (3 * UNIV_PAGE_SIZE / 4) + +/* An update undo log segment may contain several undo logs on its first page +if the undo logs took so little space that the segment could be cached and +reused. All the undo log headers are then on the first page, and the last one +owns the undo log records on subsequent pages if the segment is bigger than +one page. If an undo log is stored in a segment, then on the first page it is +allowed to have zero undo records, but if the segment extends to several +pages, then all the rest of the pages must contain at least one undo log +record. */ + +/* The offset of the undo log segment header on the first page of the undo +log segment */ + +#define TRX_UNDO_SEG_HDR (TRX_UNDO_PAGE_HDR + TRX_UNDO_PAGE_HDR_SIZE) +/*-------------------------------------------------------------*/ +#define TRX_UNDO_STATE 0 /* TRX_UNDO_ACTIVE, ... */ +#define TRX_UNDO_LAST_LOG 2 /* Offset of the last undo log header + on the segment header page, 0 if + none */ +#define TRX_UNDO_FSEG_HEADER 4 /* Header for the file segment which + the undo log segment occupies */ +#define TRX_UNDO_PAGE_LIST (4 + FSEG_HEADER_SIZE) + /* Base node for the list of pages in + the undo log segment; defined only on + the undo log segment's first page */ +/*-------------------------------------------------------------*/ +/* Size of the undo log segment header */ +#define TRX_UNDO_SEG_HDR_SIZE (4 + FSEG_HEADER_SIZE + FLST_BASE_NODE_SIZE) + + +/* The undo log header. There can be several undo log headers on the first +page of an update undo log segment. */ +/*-------------------------------------------------------------*/ +#define TRX_UNDO_TRX_ID 0 /* Transaction id */ +#define TRX_UNDO_TRX_NO 8 /* Transaction number of the + transaction; defined only if the log + is in a history list */ +#define TRX_UNDO_DEL_MARKS 16 /* Defined only in an update undo + log: TRUE if the transaction may have + done delete markings of records, and + thus purge is necessary */ +#define TRX_UNDO_LOG_START 18 /* Offset of the first undo log record + of this log on the header page; purge + may remove undo log record from the + log start, and therefore this is not + necessarily the same as this log + header end offset */ +#define TRX_UNDO_XID_EXISTS 20 /* TRUE if undo log header includes + X/Open XA transaction identification + XID */ +#define TRX_UNDO_DICT_TRANS 21 /* TRUE if the transaction is a table + create, index create, or drop + transaction: in recovery + the transaction cannot be rolled back + in the usual way: a 'rollback' rather + means dropping the created or dropped + table, if it still exists */ +#define TRX_UNDO_TABLE_ID 22 /* Id of the table if the preceding + field is TRUE */ +#define TRX_UNDO_NEXT_LOG 30 /* Offset of the next undo log header + on this page, 0 if none */ +#define TRX_UNDO_PREV_LOG 32 /* Offset of the previous undo log + header on this page, 0 if none */ +#define TRX_UNDO_HISTORY_NODE 34 /* If the log is put to the history + list, the file list node is here */ +/*-------------------------------------------------------------*/ +#define TRX_UNDO_LOG_OLD_HDR_SIZE (34 + FLST_NODE_SIZE) + +/* Note: the writing of the undo log old header is coded by a log record +MLOG_UNDO_HDR_CREATE or MLOG_UNDO_HDR_REUSE. The appending of an XID to the +header is logged separately. In this sense, the XID is not really a member +of the undo log header. TODO: do not append the XID to the log header if XA +is not needed by the user. The XID wastes about 150 bytes of space in every +undo log. In the history list we may have millions of undo logs, which means +quite a large overhead. */ + +/* X/Open XA Transaction Identification (XID) */ + +#define TRX_UNDO_XA_FORMAT (TRX_UNDO_LOG_OLD_HDR_SIZE) +#define TRX_UNDO_XA_TRID_LEN (TRX_UNDO_XA_FORMAT + 4) +#define TRX_UNDO_XA_BQUAL_LEN (TRX_UNDO_XA_TRID_LEN + 4) +#define TRX_UNDO_XA_XID (TRX_UNDO_XA_BQUAL_LEN + 4) +/*--------------------------------------------------------------*/ +#define TRX_UNDO_LOG_XA_HDR_SIZE (TRX_UNDO_XA_XID + XIDDATASIZE) + /* Total size of the header with the XA XID */ + +#ifndef UNIV_NONINL +#include "trx0undo.ic" +#endif + +#endif diff --git a/storage/xtradb/include/trx0undo.ic b/storage/xtradb/include/trx0undo.ic new file mode 100644 index 00000000000..0bd8b79414b --- /dev/null +++ b/storage/xtradb/include/trx0undo.ic @@ -0,0 +1,344 @@ +/***************************************************************************** + +Copyright (c) 1996, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/****************************************************** +Transaction undo log + +Created 3/26/1996 Heikki Tuuri +*******************************************************/ + +#include "data0type.h" +#include "page0page.h" + +/*************************************************************************** +Builds a roll pointer dulint. */ +UNIV_INLINE +dulint +trx_undo_build_roll_ptr( +/*====================*/ + /* out: roll pointer */ + ibool is_insert, /* in: TRUE if insert undo log */ + ulint rseg_id, /* in: rollback segment id */ + ulint page_no, /* in: page number */ + ulint offset) /* in: offset of the undo entry within page */ +{ +#if DATA_ROLL_PTR_LEN != 7 +# error "DATA_ROLL_PTR_LEN != 7" +#endif + ut_ad(rseg_id < 128); + + return(ut_dulint_create(is_insert * 128 * 256 * 256 + + rseg_id * 256 * 256 + + (page_no / 256) / 256, + (page_no % (256 * 256)) * 256 * 256 + + offset)); +} + +/*************************************************************************** +Decodes a roll pointer dulint. */ +UNIV_INLINE +void +trx_undo_decode_roll_ptr( +/*=====================*/ + dulint roll_ptr, /* in: roll pointer */ + ibool* is_insert, /* out: TRUE if insert undo log */ + ulint* rseg_id, /* out: rollback segment id */ + ulint* page_no, /* out: page number */ + ulint* offset) /* out: offset of the undo entry within page */ +{ + ulint low; + ulint high; +#if DATA_ROLL_PTR_LEN != 7 +# error "DATA_ROLL_PTR_LEN != 7" +#endif +#if TRUE != 1 +# error "TRUE != 1" +#endif + high = ut_dulint_get_high(roll_ptr); + low = ut_dulint_get_low(roll_ptr); + + *offset = low % (256 * 256); + + *is_insert = high / (256 * 256 * 128); /* TRUE == 1 */ + *rseg_id = (high / (256 * 256)) % 128; + + *page_no = (high % (256 * 256)) * 256 * 256 + + (low / 256) / 256; +} + +/*************************************************************************** +Returns TRUE if the roll pointer is of the insert type. */ +UNIV_INLINE +ibool +trx_undo_roll_ptr_is_insert( +/*========================*/ + /* out: TRUE if insert undo log */ + dulint roll_ptr) /* in: roll pointer */ +{ + ulint high; +#if DATA_ROLL_PTR_LEN != 7 +# error "DATA_ROLL_PTR_LEN != 7" +#endif +#if TRUE != 1 +# error "TRUE != 1" +#endif + high = ut_dulint_get_high(roll_ptr); + + return(high / (256 * 256 * 128)); +} + +/********************************************************************* +Writes a roll ptr to an index page. In case that the size changes in +some future version, this function should be used instead of +mach_write_... */ +UNIV_INLINE +void +trx_write_roll_ptr( +/*===============*/ + byte* ptr, /* in: pointer to memory where written */ + dulint roll_ptr) /* in: roll ptr */ +{ +#if DATA_ROLL_PTR_LEN != 7 +# error "DATA_ROLL_PTR_LEN != 7" +#endif + mach_write_to_7(ptr, roll_ptr); +} + +/********************************************************************* +Reads a roll ptr from an index page. In case that the roll ptr size +changes in some future version, this function should be used instead of +mach_read_... */ +UNIV_INLINE +dulint +trx_read_roll_ptr( +/*==============*/ + /* out: roll ptr */ + const byte* ptr) /* in: pointer to memory from where to read */ +{ +#if DATA_ROLL_PTR_LEN != 7 +# error "DATA_ROLL_PTR_LEN != 7" +#endif + return(mach_read_from_7(ptr)); +} + +/********************************************************************** +Gets an undo log page and x-latches it. */ +UNIV_INLINE +page_t* +trx_undo_page_get( +/*==============*/ + /* out: pointer to page x-latched */ + ulint space, /* in: space where placed */ + ulint zip_size, /* in: compressed page size in bytes + or 0 for uncompressed pages */ + ulint page_no, /* in: page number */ + mtr_t* mtr) /* in: mtr */ +{ + buf_block_t* block = buf_page_get(space, zip_size, page_no, + RW_X_LATCH, mtr); + buf_block_dbg_add_level(block, SYNC_TRX_UNDO_PAGE); + + return(buf_block_get_frame(block)); +} + +/********************************************************************** +Gets an undo log page and s-latches it. */ +UNIV_INLINE +page_t* +trx_undo_page_get_s_latched( +/*========================*/ + /* out: pointer to page s-latched */ + ulint space, /* in: space where placed */ + ulint zip_size, /* in: compressed page size in bytes + or 0 for uncompressed pages */ + ulint page_no, /* in: page number */ + mtr_t* mtr) /* in: mtr */ +{ + buf_block_t* block = buf_page_get(space, zip_size, page_no, + RW_S_LATCH, mtr); + buf_block_dbg_add_level(block, SYNC_TRX_UNDO_PAGE); + + return(buf_block_get_frame(block)); +} + +/********************************************************************** +Returns the start offset of the undo log records of the specified undo +log on the page. */ +UNIV_INLINE +ulint +trx_undo_page_get_start( +/*====================*/ + /* out: start offset */ + page_t* undo_page,/* in: undo log page */ + ulint page_no,/* in: undo log header page number */ + ulint offset) /* in: undo log header offset on page */ +{ + ulint start; + + if (page_no == page_get_page_no(undo_page)) { + + start = mach_read_from_2(offset + undo_page + + TRX_UNDO_LOG_START); + } else { + start = TRX_UNDO_PAGE_HDR + TRX_UNDO_PAGE_HDR_SIZE; + } + + return(start); +} + +/********************************************************************** +Returns the end offset of the undo log records of the specified undo +log on the page. */ +UNIV_INLINE +ulint +trx_undo_page_get_end( +/*==================*/ + /* out: end offset */ + page_t* undo_page,/* in: undo log page */ + ulint page_no,/* in: undo log header page number */ + ulint offset) /* in: undo log header offset on page */ +{ + trx_ulogf_t* log_hdr; + ulint end; + + if (page_no == page_get_page_no(undo_page)) { + + log_hdr = undo_page + offset; + + end = mach_read_from_2(log_hdr + TRX_UNDO_NEXT_LOG); + + if (end == 0) { + end = mach_read_from_2(undo_page + TRX_UNDO_PAGE_HDR + + TRX_UNDO_PAGE_FREE); + } + } else { + end = mach_read_from_2(undo_page + TRX_UNDO_PAGE_HDR + + TRX_UNDO_PAGE_FREE); + } + + return(end); +} + +/********************************************************************** +Returns the previous undo record on the page in the specified log, or +NULL if none exists. */ +UNIV_INLINE +trx_undo_rec_t* +trx_undo_page_get_prev_rec( +/*=======================*/ + /* out: pointer to record, NULL if none */ + trx_undo_rec_t* rec, /* in: undo log record */ + ulint page_no,/* in: undo log header page number */ + ulint offset) /* in: undo log header offset on page */ +{ + page_t* undo_page; + ulint start; + + undo_page = (page_t*) ut_align_down(rec, UNIV_PAGE_SIZE); + + start = trx_undo_page_get_start(undo_page, page_no, offset); + + if (start + undo_page == rec) { + + return(NULL); + } + + return(undo_page + mach_read_from_2(rec - 2)); +} + +/********************************************************************** +Returns the next undo log record on the page in the specified log, or +NULL if none exists. */ +UNIV_INLINE +trx_undo_rec_t* +trx_undo_page_get_next_rec( +/*=======================*/ + /* out: pointer to record, NULL if none */ + trx_undo_rec_t* rec, /* in: undo log record */ + ulint page_no,/* in: undo log header page number */ + ulint offset) /* in: undo log header offset on page */ +{ + page_t* undo_page; + ulint end; + ulint next; + + undo_page = (page_t*) ut_align_down(rec, UNIV_PAGE_SIZE); + + end = trx_undo_page_get_end(undo_page, page_no, offset); + + next = mach_read_from_2(rec); + + if (next == end) { + + return(NULL); + } + + return(undo_page + next); +} + +/********************************************************************** +Returns the last undo record on the page in the specified undo log, or +NULL if none exists. */ +UNIV_INLINE +trx_undo_rec_t* +trx_undo_page_get_last_rec( +/*=======================*/ + /* out: pointer to record, NULL if none */ + page_t* undo_page,/* in: undo log page */ + ulint page_no,/* in: undo log header page number */ + ulint offset) /* in: undo log header offset on page */ +{ + ulint start; + ulint end; + + start = trx_undo_page_get_start(undo_page, page_no, offset); + end = trx_undo_page_get_end(undo_page, page_no, offset); + + if (start == end) { + + return(NULL); + } + + return(undo_page + mach_read_from_2(undo_page + end - 2)); +} + +/********************************************************************** +Returns the first undo record on the page in the specified undo log, or +NULL if none exists. */ +UNIV_INLINE +trx_undo_rec_t* +trx_undo_page_get_first_rec( +/*========================*/ + /* out: pointer to record, NULL if none */ + page_t* undo_page,/* in: undo log page */ + ulint page_no,/* in: undo log header page number */ + ulint offset) /* in: undo log header offset on page */ +{ + ulint start; + ulint end; + + start = trx_undo_page_get_start(undo_page, page_no, offset); + end = trx_undo_page_get_end(undo_page, page_no, offset); + + if (start == end) { + + return(NULL); + } + + return(undo_page + start); +} diff --git a/storage/xtradb/include/trx0xa.h b/storage/xtradb/include/trx0xa.h new file mode 100644 index 00000000000..0e040b8d8e5 --- /dev/null +++ b/storage/xtradb/include/trx0xa.h @@ -0,0 +1,61 @@ +/***************************************************************************** + +Copyright (c) 1995, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/* + * Start of xa.h header + * + * Define a symbol to prevent multiple inclusions of this header file + */ +#ifndef XA_H +#define XA_H + +/* + * Transaction branch identification: XID and NULLXID: + */ +#ifndef XIDDATASIZE + +#define XIDDATASIZE 128 /* size in bytes */ +#define MAXGTRIDSIZE 64 /* maximum size in bytes of gtrid */ +#define MAXBQUALSIZE 64 /* maximum size in bytes of bqual */ + +struct xid_t { + long formatID; /* format identifier; -1 + means that the XID is null */ + long gtrid_length; /* value from 1 through 64 */ + long bqual_length; /* value from 1 through 64 */ + char data[XIDDATASIZE]; +}; +typedef struct xid_t XID; +#endif +#define XA_OK 0 /* normal execution */ +#define XAER_ASYNC -2 /* asynchronous operation already + outstanding */ +#define XAER_RMERR -3 /* a resource manager error occurred in + the transaction branch */ +#define XAER_NOTA -4 /* the XID is not valid */ +#define XAER_INVAL -5 /* invalid arguments were given */ +#define XAER_PROTO -6 /* routine invoked in an improper + context */ +#define XAER_RMFAIL -7 /* resource manager unavailable */ +#define XAER_DUPID -8 /* the XID already exists */ +#define XAER_OUTSIDE -9 /* resource manager doing work outside + transaction */ +#endif /* ifndef XA_H */ +/* + * End of xa.h header + */ diff --git a/storage/xtradb/include/univ.i b/storage/xtradb/include/univ.i new file mode 100644 index 00000000000..3fc12503031 --- /dev/null +++ b/storage/xtradb/include/univ.i @@ -0,0 +1,466 @@ +/***************************************************************************** + +Copyright (c) 1994, 2009, Innobase Oy. All Rights Reserved. +Copyright (c) 2008, Google Inc. + +Portions of this file contain modifications contributed and copyrighted by +Google, Inc. Those modifications are gratefully acknowledged and are described +briefly in the InnoDB documentation. The contributions by Google are +incorporated with their permission, and subject to the conditions contained in +the file COPYING.Google. + +This program is free software; you can redistribute it and/or modify it under +the terms of the GNU General Public License as published by the Free Software +Foundation; version 2 of the License. + +This program is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + +You 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 + +*****************************************************************************/ + +/*************************************************************************** +Version control for database, common definitions, and include files + +Created 1/20/1994 Heikki Tuuri +****************************************************************************/ + +#ifndef univ_i +#define univ_i + +#define INNODB_VERSION_MAJOR 1 +#define INNODB_VERSION_MINOR 0 +#define INNODB_VERSION_BUGFIX 3 +#define PERCONA_INNODB_VERSION 6a + +/* The following is the InnoDB version as shown in +SELECT plugin_version FROM information_schema.plugins; +calculated in in make_version_string() in sql/sql_show.cc like this: +"version >> 8" . "version & 0xff" +because the version is shown with only one dot, we skip the last +component, i.e. we show M.N.P as M.N */ +#define INNODB_VERSION_SHORT \ + (INNODB_VERSION_MAJOR << 8 | INNODB_VERSION_MINOR) + +/* auxiliary macros to help creating the version as string */ +#define __INNODB_VERSION(a, b, c, d) (#a "." #b "." #c "-" #d) +#define _INNODB_VERSION(a, b, c, d) __INNODB_VERSION(a, b, c, d) + +#define INNODB_VERSION_STR \ + _INNODB_VERSION(INNODB_VERSION_MAJOR, \ + INNODB_VERSION_MINOR, \ + INNODB_VERSION_BUGFIX, \ + PERCONA_INNODB_VERSION) + +#ifdef MYSQL_DYNAMIC_PLUGIN +/* In the dynamic plugin, redefine some externally visible symbols +in order not to conflict with the symbols of a builtin InnoDB. */ + +/* Rename all C++ classes that contain virtual functions, because we +have not figured out how to apply the visibility=hidden attribute to +the virtual method table (vtable) in GCC 3. */ +# define ha_innobase ha_innodb +#endif /* MYSQL_DYNAMIC_PLUGIN */ + +#if (defined(WIN32) || defined(_WIN32) || defined(WIN64) || defined(_WIN64)) && !defined(MYSQL_SERVER) && !defined(__WIN__) +# undef __WIN__ +# define __WIN__ + +# include <windows.h> + +# if !defined(WIN64) && !defined(_WIN64) +# define UNIV_CAN_USE_X86_ASSEMBLER +# endif + +# ifdef _NT_ +# define __NT__ +# endif + +#else +/* The defines used with MySQL */ + +/* Include two header files from MySQL to make the Unix flavor used +in compiling more Posix-compatible. These headers also define __WIN__ +if we are compiling on Windows. */ + +# include <my_global.h> +# include <my_pthread.h> + +/* Include <sys/stat.h> to get S_I... macros defined for os0file.c */ +# include <sys/stat.h> +# if !defined(__NETWARE__) && !defined(__WIN__) +# include <sys/mman.h> /* mmap() for os0proc.c */ +# endif + +# undef PACKAGE +# undef VERSION + +/* Include the header file generated by GNU autoconf */ +# ifndef __WIN__ +# include "config.h" +# endif + +# ifdef HAVE_SCHED_H +# include <sched.h> +# endif + +/* When compiling for Itanium IA64, undefine the flag below to prevent use +of the 32-bit x86 assembler in mutex operations. */ + +# if defined(__WIN__) && !defined(WIN64) && !defined(_WIN64) +# define UNIV_CAN_USE_X86_ASSEMBLER +# endif + +/* For InnoDB rw_locks to work with atomics we need the thread_id +to be no more than machine word wide. The following enables using +atomics for InnoDB rw_locks where these conditions are met. */ +#ifdef HAVE_GCC_ATOMIC_BUILTINS +/* if HAVE_ATOMIC_PTHREAD_T is defined at this point that means that +the code from plug.in has defined it and we do not need to include +ut0auxconf.h which would either define HAVE_ATOMIC_PTHREAD_T or will +be empty */ +# ifndef HAVE_ATOMIC_PTHREAD_T +# include "ut0auxconf.h" +# endif /* HAVE_ATOMIC_PTHREAD_T */ +/* now HAVE_ATOMIC_PTHREAD_T is eventually defined either by plug.in or +from Makefile.in->ut0auxconf.h */ +# ifdef HAVE_ATOMIC_PTHREAD_T +# define INNODB_RW_LOCKS_USE_ATOMICS +# endif /* HAVE_ATOMIC_PTHREAD_T */ +#endif /* HAVE_GCC_ATOMIC_BUILTINS */ + +/* Enable explicit inlining of functions only for compilers known to +support it. */ + +# if !defined(__GNUC__) && !defined(__SUNPRO_C) +# undef UNIV_MUST_NOT_INLINE /* Remove compiler warning */ +# define UNIV_MUST_NOT_INLINE +# endif + +# ifdef HAVE_PREAD +# define HAVE_PWRITE +# endif + +#endif /* #if (defined(WIN32) || ... */ + +/* DEBUG VERSION CONTROL + ===================== */ + +/* The following flag will make InnoDB to initialize +all memory it allocates to zero. It hides Purify +warnings about reading unallocated memory unless +memory is read outside the allocated blocks. */ +/* +#define UNIV_INIT_MEM_TO_ZERO +*/ + +/* When this macro is defined then additional test functions will be +compiled. These functions live at the end of each relevant source file +and have "test_" prefix. These functions are not called from anywhere in +the code, they can be called from gdb after +innobase_start_or_create_for_mysql() has executed using the call +command. Not tested on Windows. */ +/* +#define UNIV_COMPILE_TEST_FUNCS +*/ + +#if 0 +#define UNIV_DEBUG_VALGRIND /* Enable extra + Valgrind instrumentation */ +#define UNIV_DEBUG_PRINT /* Enable the compilation of + some debug print functions */ +#define UNIV_AHI_DEBUG /* Enable adaptive hash index + debugging without UNIV_DEBUG */ +#define UNIV_BUF_DEBUG /* Enable buffer pool + debugging without UNIV_DEBUG */ +#define UNIV_DEBUG /* Enable ut_ad() assertions + and disable UNIV_INLINE */ +#define UNIV_DEBUG_FILE_ACCESSES /* Debug .ibd file access + (field file_page_was_freed + in buf_page_t) */ +#define UNIV_LRU_DEBUG /* debug the buffer pool LRU */ +#define UNIV_HASH_DEBUG /* debug HASH_ macros */ +#define UNIV_LIST_DEBUG /* debug UT_LIST_ macros */ +#define UNIV_MEM_DEBUG /* detect memory leaks etc */ +#define UNIV_IBUF_DEBUG /* debug the insert buffer */ +#define UNIV_IBUF_COUNT_DEBUG /* debug the insert buffer; +this limits the database to IBUF_COUNT_N_SPACES and IBUF_COUNT_N_PAGES, +and the insert buffer must be empty when the database is started */ +#define UNIV_SYNC_DEBUG /* debug mutex and latch +operations (very slow); also UNIV_DEBUG must be defined */ +#define UNIV_SEARCH_DEBUG /* debug B-tree comparisons */ +#define UNIV_SYNC_PERF_STAT /* operation counts for + rw-locks and mutexes */ +#define UNIV_SEARCH_PERF_STAT /* statistics for the + adaptive hash index */ +#define UNIV_SRV_PRINT_LATCH_WAITS /* enable diagnostic output + in sync0sync.c */ +#define UNIV_BTR_PRINT /* enable functions for + printing B-trees */ +#define UNIV_ZIP_DEBUG /* extensive consistency checks + for compressed pages */ +#define UNIV_ZIP_COPY /* call page_zip_copy_recs() + more often */ +#endif + +#define UNIV_BTR_DEBUG /* check B-tree links */ +#define UNIV_LIGHT_MEM_DEBUG /* light memory debugging */ + +#ifdef HAVE_valgrind +/* The following sets all new allocated memory to zero before use: +this can be used to eliminate unnecessary Purify warnings, but note that +it also masks many bugs Purify could detect. For detailed Purify analysis it +is best to remove the define below and look through the warnings one +by one. */ +#define UNIV_SET_MEM_TO_ZERO +#endif + +/* +#define UNIV_SQL_DEBUG +#define UNIV_LOG_DEBUG +*/ + /* the above option prevents forcing of log to disk + at a buffer page write: it should be tested with this + option off; also some ibuf tests are suppressed */ +/* +#define UNIV_BASIC_LOG_DEBUG +*/ + /* the above option enables basic recovery debugging: + new allocated file pages are reset */ + +/* Linkage specifier for non-static InnoDB symbols (variables and functions) +that are only referenced from within InnoDB, not from MySQL */ +#ifdef __WIN__ +# define UNIV_INTERN +#else +# define UNIV_INTERN __attribute__((visibility ("hidden"))) +#endif + +#if (!defined(UNIV_DEBUG) && !defined(UNIV_MUST_NOT_INLINE)) +/* Definition for inline version */ + +#ifdef __WIN__ +#define UNIV_INLINE __inline +#else +#define UNIV_INLINE static __inline__ +#endif + +#else +/* If we want to compile a noninlined version we use the following macro +definitions: */ + +#define UNIV_NONINL +#define UNIV_INLINE UNIV_INTERN + +#endif /* UNIV_DEBUG */ + +#ifdef _WIN32 +#define UNIV_WORD_SIZE 4 +#elif defined(_WIN64) +#define UNIV_WORD_SIZE 8 +#else +/* MySQL config.h generated by GNU autoconf will define SIZEOF_LONG in Posix */ +#define UNIV_WORD_SIZE SIZEOF_LONG +#endif + +/* The following alignment is used in memory allocations in memory heap +management to ensure correct alignment for doubles etc. */ +#define UNIV_MEM_ALIGNMENT 8 + +/* The following alignment is used in aligning lints etc. */ +#define UNIV_WORD_ALIGNMENT UNIV_WORD_SIZE + +/* + DATABASE VERSION CONTROL + ======================== +*/ + +/* The 2-logarithm of UNIV_PAGE_SIZE: */ +#define UNIV_PAGE_SIZE_SHIFT 14 +/* The universal page size of the database */ +#define UNIV_PAGE_SIZE (1 << UNIV_PAGE_SIZE_SHIFT) + +/* Maximum number of parallel threads in a parallelized operation */ +#define UNIV_MAX_PARALLELISM 32 + +/* + UNIVERSAL TYPE DEFINITIONS + ========================== +*/ + +/* Note that inside MySQL 'byte' is defined as char on Linux! */ +#define byte unsigned char + +/* Define an unsigned integer type that is exactly 32 bits. */ + +#if SIZEOF_INT == 4 +typedef unsigned int ib_uint32_t; +#elif SIZEOF_LONG == 4 +typedef unsigned long ib_uint32_t; +#else +#error "Neither int or long is 4 bytes" +#endif + +/* Another basic type we use is unsigned long integer which should be equal to +the word size of the machine, that is on a 32-bit platform 32 bits, and on a +64-bit platform 64 bits. We also give the printf format for the type as a +macro ULINTPF. */ + +#ifdef _WIN64 +typedef unsigned __int64 ulint; +#define ULINTPF "%I64u" +typedef __int64 lint; +#else +typedef unsigned long int ulint; +#define ULINTPF "%lu" +typedef long int lint; +#endif + +#ifdef __WIN__ +typedef __int64 ib_int64_t; +typedef unsigned __int64 ib_uint64_t; +#else +/* Note: longlong and ulonglong come from MySQL headers. */ +typedef longlong ib_int64_t; +typedef ulonglong ib_uint64_t; +#endif + +typedef unsigned long long int ullint; + +#ifndef __WIN__ +#if SIZEOF_LONG != SIZEOF_VOIDP +#error "Error: InnoDB's ulint must be of the same size as void*" +#endif +#endif + +/* The 'undefined' value for a ulint */ +#define ULINT_UNDEFINED ((ulint)(-1)) + +/* The undefined 32-bit unsigned integer */ +#define ULINT32_UNDEFINED 0xFFFFFFFF + +/* Maximum value for a ulint */ +#define ULINT_MAX ((ulint)(-2)) + +/* Maximum value for ib_uint64_t */ +#define IB_ULONGLONG_MAX ((ib_uint64_t) (~0ULL)) + +/* This 'ibool' type is used within Innobase. Remember that different included +headers may define 'bool' differently. Do not assume that 'bool' is a ulint! */ +#define ibool ulint + +#ifndef TRUE + +#define TRUE 1 +#define FALSE 0 + +#endif + +/* The following number as the length of a logical field means that the field +has the SQL NULL as its value. NOTE that because we assume that the length +of a field is a 32-bit integer when we store it, for example, to an undo log +on disk, we must have also this number fit in 32 bits, also in 64-bit +computers! */ + +#define UNIV_SQL_NULL ULINT32_UNDEFINED + +/* Lengths which are not UNIV_SQL_NULL, but bigger than the following +number indicate that a field contains a reference to an externally +stored part of the field in the tablespace. The length field then +contains the sum of the following flag and the locally stored len. */ + +#define UNIV_EXTERN_STORAGE_FIELD (UNIV_SQL_NULL - UNIV_PAGE_SIZE) + +/* Some macros to improve branch prediction and reduce cache misses */ +#if defined(__GNUC__) && (__GNUC__ > 2) && ! defined(__INTEL_COMPILER) +/* Tell the compiler that 'expr' probably evaluates to 'constant'. */ +# define UNIV_EXPECT(expr,constant) __builtin_expect(expr, constant) +/* Tell the compiler that a pointer is likely to be NULL */ +# define UNIV_LIKELY_NULL(ptr) __builtin_expect((ulint) ptr, 0) +/* Minimize cache-miss latency by moving data at addr into a cache before +it is read. */ +# define UNIV_PREFETCH_R(addr) __builtin_prefetch(addr, 0, 3) +/* Minimize cache-miss latency by moving data at addr into a cache before +it is read or written. */ +# define UNIV_PREFETCH_RW(addr) __builtin_prefetch(addr, 1, 3) +#else +/* Dummy versions of the macros */ +# define UNIV_EXPECT(expr,value) (expr) +# define UNIV_LIKELY_NULL(expr) (expr) +# define UNIV_PREFETCH_R(addr) ((void) 0) +# define UNIV_PREFETCH_RW(addr) ((void) 0) +#endif +/* Tell the compiler that cond is likely to hold */ +#define UNIV_LIKELY(cond) UNIV_EXPECT(cond, TRUE) +/* Tell the compiler that cond is unlikely to hold */ +#define UNIV_UNLIKELY(cond) UNIV_EXPECT(cond, FALSE) + +/* Compile-time constant of the given array's size. */ +#define UT_ARR_SIZE(a) (sizeof(a) / sizeof((a)[0])) + +/* The return type from a thread's start function differs between Unix and +Windows, so define a typedef for it and a macro to use at the end of such +functions. */ + +#ifdef __WIN__ +typedef ulint os_thread_ret_t; +#define OS_THREAD_DUMMY_RETURN return(0) +#else +typedef void* os_thread_ret_t; +#define OS_THREAD_DUMMY_RETURN return(NULL) +#endif + +#include <stdio.h> +#include "ut0dbg.h" +#include "ut0ut.h" +#include "db0err.h" +#ifdef UNIV_DEBUG_VALGRIND +# include <valgrind/memcheck.h> +# define UNIV_MEM_VALID(addr, size) VALGRIND_MAKE_MEM_DEFINED(addr, size) +# define UNIV_MEM_INVALID(addr, size) VALGRIND_MAKE_MEM_UNDEFINED(addr, size) +# define UNIV_MEM_FREE(addr, size) VALGRIND_MAKE_MEM_NOACCESS(addr, size) +# define UNIV_MEM_ALLOC(addr, size) VALGRIND_MAKE_MEM_UNDEFINED(addr, size) +# define UNIV_MEM_DESC(addr, size, b) VALGRIND_CREATE_BLOCK(addr, size, b) +# define UNIV_MEM_UNDESC(b) VALGRIND_DISCARD(b) +# define UNIV_MEM_ASSERT_RW(addr, size) do { \ + const void* _p = (const void*) (ulint) \ + VALGRIND_CHECK_MEM_IS_DEFINED(addr, size); \ + if (UNIV_LIKELY_NULL(_p)) \ + fprintf(stderr, "%s:%d: %p[%u] undefined at %ld\n", \ + __FILE__, __LINE__, \ + (const void*) (addr), (unsigned) (size), (long) \ + (((const char*) _p) - ((const char*) (addr)))); \ + } while (0) +# define UNIV_MEM_ASSERT_W(addr, size) do { \ + const void* _p = (const void*) (ulint) \ + VALGRIND_CHECK_MEM_IS_ADDRESSABLE(addr, size); \ + if (UNIV_LIKELY_NULL(_p)) \ + fprintf(stderr, "%s:%d: %p[%u] unwritable at %ld\n", \ + __FILE__, __LINE__, \ + (const void*) (addr), (unsigned) (size), (long) \ + (((const char*) _p) - ((const char*) (addr)))); \ + } while (0) +#else +# define UNIV_MEM_VALID(addr, size) do {} while(0) +# define UNIV_MEM_INVALID(addr, size) do {} while(0) +# define UNIV_MEM_FREE(addr, size) do {} while(0) +# define UNIV_MEM_ALLOC(addr, size) do {} while(0) +# define UNIV_MEM_DESC(addr, size, b) do {} while(0) +# define UNIV_MEM_UNDESC(b) do {} while(0) +# define UNIV_MEM_ASSERT_RW(addr, size) do {} while(0) +# define UNIV_MEM_ASSERT_W(addr, size) do {} while(0) +#endif +#define UNIV_MEM_ASSERT_AND_FREE(addr, size) do { \ + UNIV_MEM_ASSERT_W(addr, size); \ + UNIV_MEM_FREE(addr, size); \ +} while (0) +#define UNIV_MEM_ASSERT_AND_ALLOC(addr, size) do { \ + UNIV_MEM_ASSERT_W(addr, size); \ + UNIV_MEM_ALLOC(addr, size); \ +} while (0) + +#endif diff --git a/storage/xtradb/include/usr0sess.h b/storage/xtradb/include/usr0sess.h new file mode 100644 index 00000000000..08c6c70066f --- /dev/null +++ b/storage/xtradb/include/usr0sess.h @@ -0,0 +1,77 @@ +/***************************************************************************** + +Copyright (c) 1996, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/****************************************************** +Sessions + +Created 6/25/1996 Heikki Tuuri +*******************************************************/ + +#ifndef usr0sess_h +#define usr0sess_h + +#include "univ.i" +#include "ut0byte.h" +#include "trx0types.h" +#include "srv0srv.h" +#include "trx0types.h" +#include "usr0types.h" +#include "que0types.h" +#include "data0data.h" +#include "rem0rec.h" + +/************************************************************************* +Opens a session. */ +UNIV_INTERN +sess_t* +sess_open(void); +/*============*/ + /* out, own: session object */ +/************************************************************************* +Closes a session, freeing the memory occupied by it, if it is in a state +where it should be closed. */ +UNIV_INTERN +ibool +sess_try_close( +/*===========*/ + /* out: TRUE if closed */ + sess_t* sess); /* in, own: session object */ + +/* The session handle. All fields are protected by the kernel mutex */ +struct sess_struct{ + ulint state; /* state of the session */ + trx_t* trx; /* transaction object permanently + assigned for the session: the + transaction instance designated by the + trx id changes, but the memory + structure is preserved */ + UT_LIST_BASE_NODE_T(que_t) + graphs; /* query graphs belonging to this + session */ +}; + +/* Session states */ +#define SESS_ACTIVE 1 +#define SESS_ERROR 2 /* session contains an error message + which has not yet been communicated + to the client */ +#ifndef UNIV_NONINL +#include "usr0sess.ic" +#endif + +#endif diff --git a/storage/xtradb/include/usr0sess.ic b/storage/xtradb/include/usr0sess.ic new file mode 100644 index 00000000000..5eefed382da --- /dev/null +++ b/storage/xtradb/include/usr0sess.ic @@ -0,0 +1,23 @@ +/***************************************************************************** + +Copyright (c) 1996, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/****************************************************** +Sessions + +Created 6/25/1996 Heikki Tuuri +*******************************************************/ diff --git a/storage/xtradb/include/usr0types.h b/storage/xtradb/include/usr0types.h new file mode 100644 index 00000000000..7f7d12f7bf5 --- /dev/null +++ b/storage/xtradb/include/usr0types.h @@ -0,0 +1,30 @@ +/***************************************************************************** + +Copyright (c) 1996, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/****************************************************** +Users and sessions global types + +Created 6/25/1996 Heikki Tuuri +*******************************************************/ + +#ifndef usr0types_h +#define usr0types_h + +typedef struct sess_struct sess_t; + +#endif diff --git a/storage/xtradb/include/ut0auxconf.h b/storage/xtradb/include/ut0auxconf.h new file mode 100644 index 00000000000..fb5fae9b399 --- /dev/null +++ b/storage/xtradb/include/ut0auxconf.h @@ -0,0 +1,19 @@ +/* Do not remove this file even though it is empty. +This file is included in univ.i and will cause compilation failure +if not present. +A custom check has been added in the generated +storage/innobase/Makefile.in that is shipped with with the InnoDB Plugin +source archive. This check tries to compile a test program and if +successful then adds "#define HAVE_ATOMIC_PTHREAD_T" to this file. +This is a hack that has been developed in order to check for pthread_t +atomicity without the need to regenerate the ./configure script that is +distributed in the MySQL 5.1 official source archives. +If by any chance Makefile.in and ./configure are regenerated and thus +the hack from Makefile.in wiped away then the "real" check from plug.in +will take over. +*/ +/* This is temprary fix for http://bugs.mysql.com/43740 */ +/* force to enable */ +#ifdef HAVE_GCC_ATOMIC_BUILTINS +#define HAVE_ATOMIC_PTHREAD_T +#endif diff --git a/storage/xtradb/include/ut0byte.h b/storage/xtradb/include/ut0byte.h new file mode 100644 index 00000000000..24aac1678b3 --- /dev/null +++ b/storage/xtradb/include/ut0byte.h @@ -0,0 +1,268 @@ +/***************************************************************************** + +Copyright (c) 1994, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/********************************************************************** +Utilities for byte operations + +Created 1/20/1994 Heikki Tuuri +***********************************************************************/ + +#ifndef ut0byte_h +#define ut0byte_h + + +#include "univ.i" + +/* Type definition for a 64-bit unsigned integer, which works also +in 32-bit machines. NOTE! Access the fields only with the accessor +functions. This definition appears here only for the compiler to +know the size of a dulint. */ + +typedef struct dulint_struct dulint; +struct dulint_struct{ + ulint high; /* most significant 32 bits */ + ulint low; /* least significant 32 bits */ +}; + +/* Zero value for a dulint */ +extern const dulint ut_dulint_zero; + +/* Maximum value for a dulint */ +extern const dulint ut_dulint_max; + +/*********************************************************** +Creates a 64-bit dulint out of two ulints. */ +UNIV_INLINE +dulint +ut_dulint_create( +/*=============*/ + /* out: created dulint */ + ulint high, /* in: high-order 32 bits */ + ulint low); /* in: low-order 32 bits */ +/*********************************************************** +Gets the high-order 32 bits of a dulint. */ +UNIV_INLINE +ulint +ut_dulint_get_high( +/*===============*/ + /* out: 32 bits in ulint */ + dulint d); /* in: dulint */ +/*********************************************************** +Gets the low-order 32 bits of a dulint. */ +UNIV_INLINE +ulint +ut_dulint_get_low( +/*==============*/ + /* out: 32 bits in ulint */ + dulint d); /* in: dulint */ +/*********************************************************** +Converts a dulint (a struct of 2 ulints) to ib_int64_t, which is a 64-bit +integer type. */ +UNIV_INLINE +ib_int64_t +ut_conv_dulint_to_longlong( +/*=======================*/ + /* out: value in ib_int64_t type */ + dulint d); /* in: dulint */ +/*********************************************************** +Tests if a dulint is zero. */ +UNIV_INLINE +ibool +ut_dulint_is_zero( +/*==============*/ + /* out: TRUE if zero */ + dulint a); /* in: dulint */ +/*********************************************************** +Compares two dulints. */ +UNIV_INLINE +int +ut_dulint_cmp( +/*==========*/ + /* out: -1 if a < b, 0 if a == b, + 1 if a > b */ + dulint a, /* in: dulint */ + dulint b); /* in: dulint */ +/*********************************************************** +Calculates the max of two dulints. */ +UNIV_INLINE +dulint +ut_dulint_get_max( +/*==============*/ + /* out: max(a, b) */ + dulint a, /* in: dulint */ + dulint b); /* in: dulint */ +/*********************************************************** +Calculates the min of two dulints. */ +UNIV_INLINE +dulint +ut_dulint_get_min( +/*==============*/ + /* out: min(a, b) */ + dulint a, /* in: dulint */ + dulint b); /* in: dulint */ +/*********************************************************** +Adds a ulint to a dulint. */ +UNIV_INLINE +dulint +ut_dulint_add( +/*==========*/ + /* out: sum a + b */ + dulint a, /* in: dulint */ + ulint b); /* in: ulint */ +/*********************************************************** +Subtracts a ulint from a dulint. */ +UNIV_INLINE +dulint +ut_dulint_subtract( +/*===============*/ + /* out: a - b */ + dulint a, /* in: dulint */ + ulint b); /* in: ulint, b <= a */ +/*********************************************************** +Subtracts a dulint from another. NOTE that the difference must be positive +and smaller that 4G. */ +UNIV_INLINE +ulint +ut_dulint_minus( +/*============*/ + /* out: a - b */ + dulint a, /* in: dulint; NOTE a must be >= b and at most + 2 to power 32 - 1 greater */ + dulint b); /* in: dulint */ +/************************************************************ +Rounds a dulint downward to a multiple of a power of 2. */ +UNIV_INLINE +dulint +ut_dulint_align_down( +/*=================*/ + /* out: rounded value */ + dulint n, /* in: number to be rounded */ + ulint align_no); /* in: align by this number which must be a + power of 2 */ +/************************************************************ +Rounds a dulint upward to a multiple of a power of 2. */ +UNIV_INLINE +dulint +ut_dulint_align_up( +/*===============*/ + /* out: rounded value */ + dulint n, /* in: number to be rounded */ + ulint align_no); /* in: align by this number which must be a + power of 2 */ +/************************************************************ +Rounds a dulint downward to a multiple of a power of 2. */ +UNIV_INLINE +ib_uint64_t +ut_uint64_align_down( +/*=================*/ + /* out: rounded value */ + ib_uint64_t n, /* in: number to be rounded */ + ulint align_no); /* in: align by this number + which must be a power of 2 */ +/************************************************************ +Rounds ib_uint64_t upward to a multiple of a power of 2. */ +UNIV_INLINE +ib_uint64_t +ut_uint64_align_up( +/*===============*/ + /* out: rounded value */ + ib_uint64_t n, /* in: number to be rounded */ + ulint align_no); /* in: align by this number + which must be a power of 2 */ +/*********************************************************** +Increments a dulint variable by 1. */ +#define UT_DULINT_INC(D)\ +{\ + if ((D).low == 0xFFFFFFFFUL) {\ + (D).high = (D).high + 1;\ + (D).low = 0;\ + } else {\ + (D).low = (D).low + 1;\ + }\ +} +/*********************************************************** +Tests if two dulints are equal. */ +#define UT_DULINT_EQ(D1, D2) (((D1).low == (D2).low)\ + && ((D1).high == (D2).high)) +#ifdef notdefined +/**************************************************************** +Sort function for dulint arrays. */ +UNIV_INTERN +void +ut_dulint_sort(dulint* arr, dulint* aux_arr, ulint low, ulint high); +/*===============================================================*/ +#endif /* notdefined */ + +/************************************************************* +The following function rounds up a pointer to the nearest aligned address. */ +UNIV_INLINE +void* +ut_align( +/*=====*/ + /* out: aligned pointer */ + void* ptr, /* in: pointer */ + ulint align_no); /* in: align by this number */ +/************************************************************* +The following function rounds down a pointer to the nearest +aligned address. */ +UNIV_INLINE +void* +ut_align_down( +/*==========*/ + /* out: aligned pointer */ + const void* ptr, /* in: pointer */ + ulint align_no) /* in: align by this number */ + __attribute__((const)); +/************************************************************* +The following function computes the offset of a pointer from the nearest +aligned address. */ +UNIV_INLINE +ulint +ut_align_offset( +/*============*/ + /* out: distance from aligned + pointer */ + const void* ptr, /* in: pointer */ + ulint align_no) /* in: align by this number */ + __attribute__((const)); +/********************************************************************* +Gets the nth bit of a ulint. */ +UNIV_INLINE +ibool +ut_bit_get_nth( +/*===========*/ + /* out: TRUE if nth bit is 1; 0th bit is defined to + be the least significant */ + ulint a, /* in: ulint */ + ulint n); /* in: nth bit requested */ +/********************************************************************* +Sets the nth bit of a ulint. */ +UNIV_INLINE +ulint +ut_bit_set_nth( +/*===========*/ + /* out: the ulint with the bit set as requested */ + ulint a, /* in: ulint */ + ulint n, /* in: nth bit requested */ + ibool val); /* in: value for the bit to set */ + +#ifndef UNIV_NONINL +#include "ut0byte.ic" +#endif + +#endif diff --git a/storage/xtradb/include/ut0byte.ic b/storage/xtradb/include/ut0byte.ic new file mode 100644 index 00000000000..021a3a15009 --- /dev/null +++ b/storage/xtradb/include/ut0byte.ic @@ -0,0 +1,413 @@ +/***************************************************************************** + +Copyright (c) 1994, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/****************************************************************** +Utilities for byte operations + +Created 5/30/1994 Heikki Tuuri +*******************************************************************/ + +/*********************************************************** +Creates a 64-bit dulint out of two ulints. */ +UNIV_INLINE +dulint +ut_dulint_create( +/*=============*/ + /* out: created dulint */ + ulint high, /* in: high-order 32 bits */ + ulint low) /* in: low-order 32 bits */ +{ + dulint res; + + ut_ad(high <= 0xFFFFFFFF); + ut_ad(low <= 0xFFFFFFFF); + + res.high = high; + res.low = low; + + return(res); +} + +/*********************************************************** +Gets the high-order 32 bits of a dulint. */ +UNIV_INLINE +ulint +ut_dulint_get_high( +/*===============*/ + /* out: 32 bits in ulint */ + dulint d) /* in: dulint */ +{ + return(d.high); +} + +/*********************************************************** +Gets the low-order 32 bits of a dulint. */ +UNIV_INLINE +ulint +ut_dulint_get_low( +/*==============*/ + /* out: 32 bits in ulint */ + dulint d) /* in: dulint */ +{ + return(d.low); +} + +/*********************************************************** +Converts a dulint (a struct of 2 ulints) to ib_int64_t, which is a 64-bit +integer type. */ +UNIV_INLINE +ib_int64_t +ut_conv_dulint_to_longlong( +/*=======================*/ + /* out: value in ib_int64_t type */ + dulint d) /* in: dulint */ +{ + return((ib_int64_t)d.low + + (((ib_int64_t)d.high) << 32)); +} + +/*********************************************************** +Tests if a dulint is zero. */ +UNIV_INLINE +ibool +ut_dulint_is_zero( +/*==============*/ + /* out: TRUE if zero */ + dulint a) /* in: dulint */ +{ + if ((a.low == 0) && (a.high == 0)) { + + return(TRUE); + } + + return(FALSE); +} + +/*********************************************************** +Compares two dulints. */ +UNIV_INLINE +int +ut_dulint_cmp( +/*==========*/ + /* out: -1 if a < b, 0 if a == b, + 1 if a > b */ + dulint a, /* in: dulint */ + dulint b) /* in: dulint */ +{ + if (a.high > b.high) { + return(1); + } else if (a.high < b.high) { + return(-1); + } else if (a.low > b.low) { + return(1); + } else if (a.low < b.low) { + return(-1); + } else { + return(0); + } +} + +/*********************************************************** +Calculates the max of two dulints. */ +UNIV_INLINE +dulint +ut_dulint_get_max( +/*==============*/ + /* out: max(a, b) */ + dulint a, /* in: dulint */ + dulint b) /* in: dulint */ +{ + if (ut_dulint_cmp(a, b) > 0) { + + return(a); + } + + return(b); +} + +/*********************************************************** +Calculates the min of two dulints. */ +UNIV_INLINE +dulint +ut_dulint_get_min( +/*==============*/ + /* out: min(a, b) */ + dulint a, /* in: dulint */ + dulint b) /* in: dulint */ +{ + if (ut_dulint_cmp(a, b) > 0) { + + return(b); + } + + return(a); +} + +/*********************************************************** +Adds a ulint to a dulint. */ +UNIV_INLINE +dulint +ut_dulint_add( +/*==========*/ + /* out: sum a + b */ + dulint a, /* in: dulint */ + ulint b) /* in: ulint */ +{ + if (0xFFFFFFFFUL - b >= a.low) { + a.low += b; + + return(a); + } + + a.low = a.low - (0xFFFFFFFFUL - b) - 1; + + a.high++; + + return(a); +} + +/*********************************************************** +Subtracts a ulint from a dulint. */ +UNIV_INLINE +dulint +ut_dulint_subtract( +/*===============*/ + /* out: a - b */ + dulint a, /* in: dulint */ + ulint b) /* in: ulint, b <= a */ +{ + if (a.low >= b) { + a.low -= b; + + return(a); + } + + b -= a.low + 1; + + a.low = 0xFFFFFFFFUL - b; + + ut_ad(a.high > 0); + + a.high--; + + return(a); +} + +/*********************************************************** +Subtracts a dulint from another. NOTE that the difference must be positive +and smaller that 4G. */ +UNIV_INLINE +ulint +ut_dulint_minus( +/*============*/ + /* out: a - b */ + dulint a, /* in: dulint; NOTE a must be >= b and at most + 2 to power 32 - 1 greater */ + dulint b) /* in: dulint */ +{ + ulint diff; + + if (a.high == b.high) { + ut_ad(a.low >= b.low); + + return(a.low - b.low); + } + + ut_ad(a.high == b.high + 1); + + diff = (ulint)(0xFFFFFFFFUL - b.low); + diff += 1 + a.low; + + ut_ad(diff > a.low); + + return(diff); +} + +/************************************************************ +Rounds a dulint downward to a multiple of a power of 2. */ +UNIV_INLINE +dulint +ut_dulint_align_down( +/*=================*/ + /* out: rounded value */ + dulint n, /* in: number to be rounded */ + ulint align_no) /* in: align by this number which must be a + power of 2 */ +{ + ulint low, high; + + ut_ad(align_no > 0); + ut_ad(((align_no - 1) & align_no) == 0); + + low = ut_dulint_get_low(n); + high = ut_dulint_get_high(n); + + low = low & ~(align_no - 1); + + return(ut_dulint_create(high, low)); +} + +/************************************************************ +Rounds a dulint upward to a multiple of a power of 2. */ +UNIV_INLINE +dulint +ut_dulint_align_up( +/*===============*/ + /* out: rounded value */ + dulint n, /* in: number to be rounded */ + ulint align_no) /* in: align by this number which must be a + power of 2 */ +{ + return(ut_dulint_align_down(ut_dulint_add(n, align_no - 1), align_no)); +} + +/************************************************************ +Rounds ib_uint64_t downward to a multiple of a power of 2. */ +UNIV_INLINE +ib_uint64_t +ut_uint64_align_down( +/*=================*/ + /* out: rounded value */ + ib_uint64_t n, /* in: number to be rounded */ + ulint align_no) /* in: align by this number + which must be a power of 2 */ +{ + ut_ad(align_no > 0); + ut_ad(ut_is_2pow(align_no)); + + return(n & ~((ib_uint64_t) align_no - 1)); +} + +/************************************************************ +Rounds ib_uint64_t upward to a multiple of a power of 2. */ +UNIV_INLINE +ib_uint64_t +ut_uint64_align_up( +/*===============*/ + /* out: rounded value */ + ib_uint64_t n, /* in: number to be rounded */ + ulint align_no) /* in: align by this number + which must be a power of 2 */ +{ + ib_uint64_t align_1 = (ib_uint64_t) align_no - 1; + + ut_ad(align_no > 0); + ut_ad(ut_is_2pow(align_no)); + + return((n + align_1) & ~align_1); +} + +/************************************************************* +The following function rounds up a pointer to the nearest aligned address. */ +UNIV_INLINE +void* +ut_align( +/*=====*/ + /* out: aligned pointer */ + void* ptr, /* in: pointer */ + ulint align_no) /* in: align by this number */ +{ + ut_ad(align_no > 0); + ut_ad(((align_no - 1) & align_no) == 0); + ut_ad(ptr); + + ut_ad(sizeof(void*) == sizeof(ulint)); + + return((void*)((((ulint)ptr) + align_no - 1) & ~(align_no - 1))); +} + +/************************************************************* +The following function rounds down a pointer to the nearest +aligned address. */ +UNIV_INLINE +void* +ut_align_down( +/*==========*/ + /* out: aligned pointer */ + const void* ptr, /* in: pointer */ + ulint align_no) /* in: align by this number */ +{ + ut_ad(align_no > 0); + ut_ad(((align_no - 1) & align_no) == 0); + ut_ad(ptr); + + ut_ad(sizeof(void*) == sizeof(ulint)); + + return((void*)((((ulint)ptr)) & ~(align_no - 1))); +} + +/************************************************************* +The following function computes the offset of a pointer from the nearest +aligned address. */ +UNIV_INLINE +ulint +ut_align_offset( +/*============*/ + /* out: distance from + aligned pointer */ + const void* ptr, /* in: pointer */ + ulint align_no) /* in: align by this number */ +{ + ut_ad(align_no > 0); + ut_ad(((align_no - 1) & align_no) == 0); + ut_ad(ptr); + + ut_ad(sizeof(void*) == sizeof(ulint)); + + return(((ulint)ptr) & (align_no - 1)); +} + +/********************************************************************* +Gets the nth bit of a ulint. */ +UNIV_INLINE +ibool +ut_bit_get_nth( +/*===========*/ + /* out: TRUE if nth bit is 1; 0th bit is defined to + be the least significant */ + ulint a, /* in: ulint */ + ulint n) /* in: nth bit requested */ +{ + ut_ad(n < 8 * sizeof(ulint)); +#if TRUE != 1 +# error "TRUE != 1" +#endif + return(1 & (a >> n)); +} + +/********************************************************************* +Sets the nth bit of a ulint. */ +UNIV_INLINE +ulint +ut_bit_set_nth( +/*===========*/ + /* out: the ulint with the bit set as requested */ + ulint a, /* in: ulint */ + ulint n, /* in: nth bit requested */ + ibool val) /* in: value for the bit to set */ +{ + ut_ad(n < 8 * sizeof(ulint)); +#if TRUE != 1 +# error "TRUE != 1" +#endif + if (val) { + return(((ulint) 1 << n) | a); + } else { + return(~((ulint) 1 << n) & a); + } +} diff --git a/storage/xtradb/include/ut0dbg.h b/storage/xtradb/include/ut0dbg.h new file mode 100644 index 00000000000..a206789fd4c --- /dev/null +++ b/storage/xtradb/include/ut0dbg.h @@ -0,0 +1,160 @@ +/***************************************************************************** + +Copyright (c) 1994, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/********************************************************************* +Debug utilities for Innobase + +Created 1/30/1994 Heikki Tuuri +**********************************************************************/ + +#ifndef ut0dbg_h +#define ut0dbg_h + +#include "univ.i" +#include <stdlib.h> +#include "os0thread.h" + +#if defined(__GNUC__) && (__GNUC__ > 2) +# define UT_DBG_FAIL(EXPR) UNIV_UNLIKELY(!((ulint)(EXPR))) +#else +extern ulint ut_dbg_zero; /* This is used to eliminate + compiler warnings */ +# define UT_DBG_FAIL(EXPR) !((ulint)(EXPR) + ut_dbg_zero) +#endif + +/***************************************************************** +Report a failed assertion. */ +UNIV_INTERN +void +ut_dbg_assertion_failed( +/*====================*/ + const char* expr, /* in: the failed assertion */ + const char* file, /* in: source file containing the assertion */ + ulint line); /* in: line number of the assertion */ + +#ifdef __NETWARE__ +/* Flag for ignoring further assertion failures. +On NetWare, have a graceful exit rather than a segfault to avoid abends. */ +extern ibool panic_shutdown; +/* Abort the execution. */ +void ut_dbg_panic(void); +# define UT_DBG_PANIC ut_dbg_panic() +/* Stop threads in ut_a(). */ +# define UT_DBG_STOP do {} while (0) /* We do not do this on NetWare */ +#else /* __NETWARE__ */ +# if defined(__WIN__) || defined(__INTEL_COMPILER) +# undef UT_DBG_USE_ABORT +# elif defined(__GNUC__) && (__GNUC__ > 2) +# define UT_DBG_USE_ABORT +# endif + +# ifndef UT_DBG_USE_ABORT +/* A null pointer that will be dereferenced to trigger a memory trap */ +extern ulint* ut_dbg_null_ptr; +# endif + +# if defined(UNIV_SYNC_DEBUG) || !defined(UT_DBG_USE_ABORT) +/* Flag for indicating that all threads should stop. This will be set +by ut_dbg_assertion_failed(). */ +extern ibool ut_dbg_stop_threads; + +/***************************************************************** +Stop a thread after assertion failure. */ +UNIV_INTERN +void +ut_dbg_stop_thread( +/*===============*/ + const char* file, + ulint line); +# endif + +# ifdef UT_DBG_USE_ABORT +/* Abort the execution. */ +# define UT_DBG_PANIC abort() +/* Stop threads (null operation) */ +# define UT_DBG_STOP do {} while (0) +# else /* UT_DBG_USE_ABORT */ +/* Abort the execution. */ +# define UT_DBG_PANIC \ + if (*(ut_dbg_null_ptr)) ut_dbg_null_ptr = NULL +/* Stop threads in ut_a(). */ +# define UT_DBG_STOP do \ + if (UNIV_UNLIKELY(ut_dbg_stop_threads)) { \ + ut_dbg_stop_thread(__FILE__, (ulint) __LINE__); \ + } while (0) +# endif /* UT_DBG_USE_ABORT */ +#endif /* __NETWARE__ */ + +/* Abort execution if EXPR does not evaluate to nonzero. */ +#define ut_a(EXPR) do { \ + if (UT_DBG_FAIL(EXPR)) { \ + ut_dbg_assertion_failed(#EXPR, \ + __FILE__, (ulint) __LINE__); \ + UT_DBG_PANIC; \ + } \ + UT_DBG_STOP; \ +} while (0) + +/* Abort execution. */ +#define ut_error do { \ + ut_dbg_assertion_failed(0, __FILE__, (ulint) __LINE__); \ + UT_DBG_PANIC; \ +} while (0) + +#ifdef UNIV_DEBUG +#define ut_ad(EXPR) ut_a(EXPR) +#define ut_d(EXPR) do {EXPR;} while (0) +#else +#define ut_ad(EXPR) +#define ut_d(EXPR) +#endif + +#define UT_NOT_USED(A) A = A + +#ifdef UNIV_COMPILE_TEST_FUNCS + +#include <sys/types.h> +#include <sys/time.h> +#include <sys/resource.h> + +/* structure used for recording usage statistics */ +typedef struct speedo_struct { + struct rusage ru; + struct timeval tv; +} speedo_t; + +/*********************************************************************** +Resets a speedo (records the current time in it). */ +UNIV_INTERN +void +speedo_reset( +/*=========*/ + speedo_t* speedo); /* out: speedo */ + +/*********************************************************************** +Shows the time elapsed and usage statistics since the last reset of a +speedo. */ +UNIV_INTERN +void +speedo_show( +/*========*/ + const speedo_t* speedo); /* in: speedo */ + +#endif /* UNIV_COMPILE_TEST_FUNCS */ + +#endif diff --git a/storage/xtradb/include/ut0list.h b/storage/xtradb/include/ut0list.h new file mode 100644 index 00000000000..034aa400af9 --- /dev/null +++ b/storage/xtradb/include/ut0list.h @@ -0,0 +1,165 @@ +/***************************************************************************** + +Copyright (c) 2006, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/*********************************************************************** +A double-linked list. This differs from the one in ut0lst.h in that in this +one, each list node contains a pointer to the data, whereas the one in +ut0lst.h uses a strategy where the list pointers are embedded in the data +items themselves. + +Use this one when you need to store arbitrary data in the list where you +can't embed the list pointers in the data, if a data item needs to be +stored in multiple lists, etc. + +Note about the memory management: ib_list_t is a fixed-size struct whose +allocation/deallocation is done through ib_list_create/ib_list_free, but the +memory for the list nodes is allocated through a user-given memory heap, +which can either be the same for all nodes or vary per node. Most users will +probably want to create a memory heap to store the item-specific data, and +pass in this same heap to the list node creation functions, thus +automatically freeing the list node when the item's heap is freed. + +************************************************************************/ + +#ifndef IB_LIST_H +#define IB_LIST_H + +#include "mem0mem.h" + +typedef struct ib_list_struct ib_list_t; +typedef struct ib_list_node_struct ib_list_node_t; +typedef struct ib_list_helper_struct ib_list_helper_t; + +/******************************************************************** +Create a new list using mem_alloc. Lists created with this function must be +freed with ib_list_free. */ +UNIV_INTERN +ib_list_t* +ib_list_create(void); +/*=================*/ + /* out: list */ + + +/******************************************************************** +Create a new list using the given heap. ib_list_free MUST NOT BE CALLED for +lists created with this function. */ +UNIV_INTERN +ib_list_t* +ib_list_create_heap( +/*================*/ + /* out: list */ + mem_heap_t* heap); /* in: memory heap to use */ + +/******************************************************************** +Free a list. */ +UNIV_INTERN +void +ib_list_free( +/*=========*/ + ib_list_t* list); /* in: list */ + +/******************************************************************** +Add the data to the start of the list. */ +UNIV_INTERN +ib_list_node_t* +ib_list_add_first( +/*==============*/ + /* out: new list node*/ + ib_list_t* list, /* in: list */ + void* data, /* in: data */ + mem_heap_t* heap); /* in: memory heap to use */ + +/******************************************************************** +Add the data to the end of the list. */ +UNIV_INTERN +ib_list_node_t* +ib_list_add_last( +/*=============*/ + /* out: new list node*/ + ib_list_t* list, /* in: list */ + void* data, /* in: data */ + mem_heap_t* heap); /* in: memory heap to use */ + +/******************************************************************** +Add the data after the indicated node. */ +UNIV_INTERN +ib_list_node_t* +ib_list_add_after( +/*==============*/ + /* out: new list node*/ + ib_list_t* list, /* in: list */ + ib_list_node_t* prev_node, /* in: node preceding new node (can + be NULL) */ + void* data, /* in: data */ + mem_heap_t* heap); /* in: memory heap to use */ + +/******************************************************************** +Remove the node from the list. */ +UNIV_INTERN +void +ib_list_remove( +/*===========*/ + ib_list_t* list, /* in: list */ + ib_list_node_t* node); /* in: node to remove */ + +/******************************************************************** +Get the first node in the list. */ +UNIV_INLINE +ib_list_node_t* +ib_list_get_first( +/*==============*/ + /* out: first node, or NULL */ + ib_list_t* list); /* in: list */ + +/******************************************************************** +Get the last node in the list. */ +UNIV_INLINE +ib_list_node_t* +ib_list_get_last( +/*=============*/ + /* out: last node, or NULL */ + ib_list_t* list); /* in: list */ + +/* List. */ +struct ib_list_struct { + ib_list_node_t* first; /* first node */ + ib_list_node_t* last; /* last node */ + ibool is_heap_list; /* TRUE if this list was + allocated through a heap */ +}; + +/* A list node. */ +struct ib_list_node_struct { + ib_list_node_t* prev; /* previous node */ + ib_list_node_t* next; /* next node */ + void* data; /* user data */ +}; + +/* Quite often, the only additional piece of data you need is the per-item +memory heap, so we have this generic struct available to use in those +cases. */ +struct ib_list_helper_struct { + mem_heap_t* heap; /* memory heap */ + void* data; /* user data */ +}; + +#ifndef UNIV_NONINL +#include "ut0list.ic" +#endif + +#endif diff --git a/storage/xtradb/include/ut0list.ic b/storage/xtradb/include/ut0list.ic new file mode 100644 index 00000000000..c79a0cf18dc --- /dev/null +++ b/storage/xtradb/include/ut0list.ic @@ -0,0 +1,41 @@ +/***************************************************************************** + +Copyright (c) 2006, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/******************************************************************** +Get the first node in the list. */ +UNIV_INLINE +ib_list_node_t* +ib_list_get_first( +/*==============*/ + /* out: first node, or NULL */ + ib_list_t* list) /* in: list */ +{ + return(list->first); +} + +/******************************************************************** +Get the last node in the list. */ +UNIV_INLINE +ib_list_node_t* +ib_list_get_last( +/*=============*/ + /* out: last node, or NULL */ + ib_list_t* list) /* in: list */ +{ + return(list->last); +} diff --git a/storage/xtradb/include/ut0lst.h b/storage/xtradb/include/ut0lst.h new file mode 100644 index 00000000000..46ee23a2538 --- /dev/null +++ b/storage/xtradb/include/ut0lst.h @@ -0,0 +1,243 @@ +/***************************************************************************** + +Copyright (c) 1995, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/********************************************************************** +List utilities + +Created 9/10/1995 Heikki Tuuri +***********************************************************************/ + +#ifndef ut0lst_h +#define ut0lst_h + +#include "univ.i" + +/* This module implements the two-way linear list which should be used +if a list is used in the database. Note that a single struct may belong +to two or more lists, provided that the list are given different names. +An example of the usage of the lists can be found in fil0fil.c. */ + +/*********************************************************************** +This macro expands to the unnamed type definition of a struct which acts +as the two-way list base node. The base node contains pointers +to both ends of the list and a count of nodes in the list (excluding +the base node from the count). TYPE should be the list node type name. */ + +#define UT_LIST_BASE_NODE_T(TYPE)\ +struct {\ + ulint count; /* count of nodes in list */\ + TYPE * start; /* pointer to list start, NULL if empty */\ + TYPE * end; /* pointer to list end, NULL if empty */\ +}\ + +/*********************************************************************** +This macro expands to the unnamed type definition of a struct which +should be embedded in the nodes of the list, the node type must be a struct. +This struct contains the pointers to next and previous nodes in the list. +The name of the field in the node struct should be the name given +to the list. TYPE should be the list node type name. Example of usage: + +typedef struct LRU_node_struct LRU_node_t; +struct LRU_node_struct { + UT_LIST_NODE_T(LRU_node_t) LRU_list; + ... +} +The example implements an LRU list of name LRU_list. Its nodes are of type +LRU_node_t. +*/ + +#define UT_LIST_NODE_T(TYPE)\ +struct {\ + TYPE * prev; /* pointer to the previous node,\ + NULL if start of list */\ + TYPE * next; /* pointer to next node, NULL if end of list */\ +}\ + +/*********************************************************************** +Initializes the base node of a two-way list. */ + +#define UT_LIST_INIT(BASE)\ +{\ + (BASE).count = 0;\ + (BASE).start = NULL;\ + (BASE).end = NULL;\ +}\ + +/*********************************************************************** +Adds the node as the first element in a two-way linked list. +BASE has to be the base node (not a pointer to it). N has to be +the pointer to the node to be added to the list. NAME is the list name. */ + +#define UT_LIST_ADD_FIRST(NAME, BASE, N)\ +{\ + ut_ad(N);\ + ((BASE).count)++;\ + ((N)->NAME).next = (BASE).start;\ + ((N)->NAME).prev = NULL;\ + if (UNIV_LIKELY((BASE).start != NULL)) {\ + ut_ad((BASE).start != (N));\ + (((BASE).start)->NAME).prev = (N);\ + }\ + (BASE).start = (N);\ + if (UNIV_UNLIKELY((BASE).end == NULL)) {\ + (BASE).end = (N);\ + }\ +}\ + +/*********************************************************************** +Adds the node as the last element in a two-way linked list. +BASE has to be the base node (not a pointer to it). N has to be +the pointer to the node to be added to the list. NAME is the list name. */ + +#define UT_LIST_ADD_LAST(NAME, BASE, N)\ +{\ + ut_ad(N);\ + ((BASE).count)++;\ + ((N)->NAME).prev = (BASE).end;\ + ((N)->NAME).next = NULL;\ + if ((BASE).end != NULL) {\ + ut_ad((BASE).end != (N));\ + (((BASE).end)->NAME).next = (N);\ + }\ + (BASE).end = (N);\ + if ((BASE).start == NULL) {\ + (BASE).start = (N);\ + }\ +}\ + +/*********************************************************************** +Inserts a NODE2 after NODE1 in a list. +BASE has to be the base node (not a pointer to it). NAME is the list +name, NODE1 and NODE2 are pointers to nodes. */ + +#define UT_LIST_INSERT_AFTER(NAME, BASE, NODE1, NODE2)\ +{\ + ut_ad(NODE1);\ + ut_ad(NODE2);\ + ut_ad((NODE1) != (NODE2));\ + ((BASE).count)++;\ + ((NODE2)->NAME).prev = (NODE1);\ + ((NODE2)->NAME).next = ((NODE1)->NAME).next;\ + if (((NODE1)->NAME).next != NULL) {\ + ((((NODE1)->NAME).next)->NAME).prev = (NODE2);\ + }\ + ((NODE1)->NAME).next = (NODE2);\ + if ((BASE).end == (NODE1)) {\ + (BASE).end = (NODE2);\ + }\ +}\ + +/* Invalidate the pointers in a list node. */ +#ifdef UNIV_LIST_DEBUG +# define UT_LIST_REMOVE_CLEAR(NAME, N) \ +((N)->NAME.prev = (N)->NAME.next = (void*) -1) +#else +# define UT_LIST_REMOVE_CLEAR(NAME, N) while (0) +#endif + +/*********************************************************************** +Removes a node from a two-way linked list. BASE has to be the base node +(not a pointer to it). N has to be the pointer to the node to be removed +from the list. NAME is the list name. */ + +#define UT_LIST_REMOVE(NAME, BASE, N) \ +do { \ + ut_ad(N); \ + ut_a((BASE).count > 0); \ + ((BASE).count)--; \ + if (((N)->NAME).next != NULL) { \ + ((((N)->NAME).next)->NAME).prev = ((N)->NAME).prev; \ + } else { \ + (BASE).end = ((N)->NAME).prev; \ + } \ + if (((N)->NAME).prev != NULL) { \ + ((((N)->NAME).prev)->NAME).next = ((N)->NAME).next; \ + } else { \ + (BASE).start = ((N)->NAME).next; \ + } \ + UT_LIST_REMOVE_CLEAR(NAME, N); \ +} while (0) + +/************************************************************************ +Gets the next node in a two-way list. NAME is the name of the list +and N is pointer to a node. */ + +#define UT_LIST_GET_NEXT(NAME, N)\ + (((N)->NAME).next) + +/************************************************************************ +Gets the previous node in a two-way list. NAME is the name of the list +and N is pointer to a node. */ + +#define UT_LIST_GET_PREV(NAME, N)\ + (((N)->NAME).prev) + +/************************************************************************ +Alternative macro to get the number of nodes in a two-way list, i.e., +its length. BASE is the base node (not a pointer to it). */ + +#define UT_LIST_GET_LEN(BASE)\ + (BASE).count + +/************************************************************************ +Gets the first node in a two-way list, or returns NULL, +if the list is empty. BASE is the base node (not a pointer to it). */ + +#define UT_LIST_GET_FIRST(BASE)\ + (BASE).start + +/************************************************************************ +Gets the last node in a two-way list, or returns NULL, +if the list is empty. BASE is the base node (not a pointer to it). */ + +#define UT_LIST_GET_LAST(BASE)\ + (BASE).end + +/************************************************************************ +Checks the consistency of a two-way list. NAME is the name of the list, +TYPE is the node type, and BASE is the base node (not a pointer to it). */ + +#define UT_LIST_VALIDATE(NAME, TYPE, BASE)\ +{\ + ulint ut_list_i_313;\ + TYPE * ut_list_node_313;\ +\ + ut_list_node_313 = (BASE).start;\ +\ + for (ut_list_i_313 = 0; ut_list_i_313 < (BASE).count;\ + ut_list_i_313++) {\ + ut_a(ut_list_node_313);\ + ut_list_node_313 = (ut_list_node_313->NAME).next;\ + }\ +\ + ut_a(ut_list_node_313 == NULL);\ +\ + ut_list_node_313 = (BASE).end;\ +\ + for (ut_list_i_313 = 0; ut_list_i_313 < (BASE).count;\ + ut_list_i_313++) {\ + ut_a(ut_list_node_313);\ + ut_list_node_313 = (ut_list_node_313->NAME).prev;\ + }\ +\ + ut_a(ut_list_node_313 == NULL);\ +}\ + + +#endif + diff --git a/storage/xtradb/include/ut0mem.h b/storage/xtradb/include/ut0mem.h new file mode 100644 index 00000000000..f8dec99ed4a --- /dev/null +++ b/storage/xtradb/include/ut0mem.h @@ -0,0 +1,271 @@ +/***************************************************************************** + +Copyright (c) 1994, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/*********************************************************************** +Memory primitives + +Created 5/30/1994 Heikki Tuuri +************************************************************************/ + +#ifndef ut0mem_h +#define ut0mem_h + +#include "univ.i" +#include "os0sync.h" +#include <string.h> + +/* The total amount of memory currently allocated from the operating +system with os_mem_alloc_large() or malloc(). Does not count malloc() +if srv_use_sys_malloc is set. Protected by ut_list_mutex. */ +extern ulint ut_total_allocated_memory; + +/* Mutex protecting ut_total_allocated_memory and ut_mem_block_list */ +extern os_fast_mutex_t ut_list_mutex; + +UNIV_INLINE +void* +ut_memcpy(void* dest, const void* sour, ulint n); + +UNIV_INLINE +void* +ut_memmove(void* dest, const void* sour, ulint n); + +UNIV_INLINE +int +ut_memcmp(const void* str1, const void* str2, ulint n); + +/************************************************************************** +Initializes the mem block list at database startup. */ +UNIV_INTERN +void +ut_mem_init(void); +/*=============*/ + +/************************************************************************** +Allocates memory. Sets it also to zero if UNIV_SET_MEM_TO_ZERO is +defined and set_to_zero is TRUE. */ +UNIV_INTERN +void* +ut_malloc_low( +/*==========*/ + /* out, own: allocated memory */ + ulint n, /* in: number of bytes to allocate */ + ibool set_to_zero, /* in: TRUE if allocated memory + should be set to zero if + UNIV_SET_MEM_TO_ZERO is defined */ + ibool assert_on_error); /* in: if TRUE, we crash mysqld if + the memory cannot be allocated */ +/************************************************************************** +Allocates memory. Sets it also to zero if UNIV_SET_MEM_TO_ZERO is +defined. */ +UNIV_INTERN +void* +ut_malloc( +/*======*/ + /* out, own: allocated memory */ + ulint n); /* in: number of bytes to allocate */ +/************************************************************************** +Tests if malloc of n bytes would succeed. ut_malloc() asserts if memory runs +out. It cannot be used if we want to return an error message. Prints to +stderr a message if fails. */ +UNIV_INTERN +ibool +ut_test_malloc( +/*===========*/ + /* out: TRUE if succeeded */ + ulint n); /* in: try to allocate this many bytes */ +/************************************************************************** +Frees a memory block allocated with ut_malloc. */ +UNIV_INTERN +void +ut_free( +/*====*/ + void* ptr); /* in, own: memory block */ +/************************************************************************** +Implements realloc. This is needed by /pars/lexyy.c. Otherwise, you should not +use this function because the allocation functions in mem0mem.h are the +recommended ones in InnoDB. + +man realloc in Linux, 2004: + + realloc() changes the size of the memory block pointed to + by ptr to size bytes. The contents will be unchanged to + the minimum of the old and new sizes; newly allocated mem + ory will be uninitialized. If ptr is NULL, the call is + equivalent to malloc(size); if size is equal to zero, the + call is equivalent to free(ptr). Unless ptr is NULL, it + must have been returned by an earlier call to malloc(), + calloc() or realloc(). + +RETURN VALUE + realloc() returns a pointer to the newly allocated memory, + which is suitably aligned for any kind of variable and may + be different from ptr, or NULL if the request fails. If + size was equal to 0, either NULL or a pointer suitable to + be passed to free() is returned. If realloc() fails the + original block is left untouched - it is not freed or + moved. */ +UNIV_INTERN +void* +ut_realloc( +/*=======*/ + /* out, own: pointer to new mem block or NULL */ + void* ptr, /* in: pointer to old block or NULL */ + ulint size); /* in: desired size */ +/************************************************************************** +Frees in shutdown all allocated memory not freed yet. */ +UNIV_INTERN +void +ut_free_all_mem(void); +/*=================*/ + +UNIV_INLINE +char* +ut_strcpy(char* dest, const char* sour); + +UNIV_INLINE +ulint +ut_strlen(const char* str); + +UNIV_INLINE +int +ut_strcmp(const char* str1, const char* str2); + +/************************************************************************** +Copies up to size - 1 characters from the NUL-terminated string src to +dst, NUL-terminating the result. Returns strlen(src), so truncation +occurred if the return value >= size. */ +UNIV_INTERN +ulint +ut_strlcpy( +/*=======*/ + /* out: strlen(src) */ + char* dst, /* in: destination buffer */ + const char* src, /* in: source buffer */ + ulint size); /* in: size of destination buffer */ + +/************************************************************************** +Like ut_strlcpy, but if src doesn't fit in dst completely, copies the last +(size - 1) bytes of src, not the first. */ +UNIV_INTERN +ulint +ut_strlcpy_rev( +/*===========*/ + /* out: strlen(src) */ + char* dst, /* in: destination buffer */ + const char* src, /* in: source buffer */ + ulint size); /* in: size of destination buffer */ + +/************************************************************************** +Compute strlen(ut_strcpyq(str, q)). */ +UNIV_INLINE +ulint +ut_strlenq( +/*=======*/ + /* out: length of the string when quoted */ + const char* str, /* in: null-terminated string */ + char q); /* in: the quote character */ + +/************************************************************************** +Make a quoted copy of a NUL-terminated string. Leading and trailing +quotes will not be included; only embedded quotes will be escaped. +See also ut_strlenq() and ut_memcpyq(). */ +UNIV_INTERN +char* +ut_strcpyq( +/*=======*/ + /* out: pointer to end of dest */ + char* dest, /* in: output buffer */ + char q, /* in: the quote character */ + const char* src); /* in: null-terminated string */ + +/************************************************************************** +Make a quoted copy of a fixed-length string. Leading and trailing +quotes will not be included; only embedded quotes will be escaped. +See also ut_strlenq() and ut_strcpyq(). */ +UNIV_INTERN +char* +ut_memcpyq( +/*=======*/ + /* out: pointer to end of dest */ + char* dest, /* in: output buffer */ + char q, /* in: the quote character */ + const char* src, /* in: string to be quoted */ + ulint len); /* in: length of src */ + +/************************************************************************** +Return the number of times s2 occurs in s1. Overlapping instances of s2 +are only counted once. */ +UNIV_INTERN +ulint +ut_strcount( +/*========*/ + /* out: the number of times s2 occurs in s1 */ + const char* s1, /* in: string to search in */ + const char* s2); /* in: string to search for */ + +/************************************************************************** +Replace every occurrence of s1 in str with s2. Overlapping instances of s1 +are only replaced once. */ +UNIV_INTERN +char* +ut_strreplace( +/*==========*/ + /* out, own: modified string, must be + freed with mem_free() */ + const char* str, /* in: string to operate on */ + const char* s1, /* in: string to replace */ + const char* s2); /* in: string to replace s1 with */ + +/************************************************************************** +Converts a raw binary data to a '\0'-terminated hex string. The output is +truncated if there is not enough space in "hex", make sure "hex_size" is at +least (2 * raw_size + 1) if you do not want this to happen. Returns the +actual number of characters written to "hex" (including the '\0'). */ +UNIV_INLINE +ulint +ut_raw_to_hex( +/*==========*/ + /* out: number of chars written */ + const void* raw, /* in: raw data */ + ulint raw_size, /* in: "raw" length in bytes */ + char* hex, /* out: hex string */ + ulint hex_size); /* in: "hex" size in bytes */ + +/*********************************************************************** +Adds single quotes to the start and end of string and escapes any quotes +by doubling them. Returns the number of bytes that were written to "buf" +(including the terminating '\0'). If buf_size is too small then the +trailing bytes from "str" are discarded. */ +UNIV_INLINE +ulint +ut_str_sql_format( +/*==============*/ + /* out: number of bytes + that were written */ + const char* str, /* in: string */ + ulint str_len, /* in: string length in bytes */ + char* buf, /* out: output buffer */ + ulint buf_size); /* in: output buffer size + in bytes */ + +#ifndef UNIV_NONINL +#include "ut0mem.ic" +#endif + +#endif diff --git a/storage/xtradb/include/ut0mem.ic b/storage/xtradb/include/ut0mem.ic new file mode 100644 index 00000000000..5078c721706 --- /dev/null +++ b/storage/xtradb/include/ut0mem.ic @@ -0,0 +1,308 @@ +/***************************************************************************** + +Copyright (c) 1994, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/*********************************************************************** +Memory primitives + +Created 5/30/1994 Heikki Tuuri +************************************************************************/ + +#include "ut0byte.h" +#include "mach0data.h" + +UNIV_INLINE +void* +ut_memcpy(void* dest, const void* sour, ulint n) +{ + return(memcpy(dest, sour, n)); +} + +UNIV_INLINE +void* +ut_memmove(void* dest, const void* sour, ulint n) +{ + return(memmove(dest, sour, n)); +} + +UNIV_INLINE +int +ut_memcmp(const void* str1, const void* str2, ulint n) +{ + return(memcmp(str1, str2, n)); +} + +UNIV_INLINE +char* +ut_strcpy(char* dest, const char* sour) +{ + return(strcpy(dest, sour)); +} + +UNIV_INLINE +ulint +ut_strlen(const char* str) +{ + return(strlen(str)); +} + +UNIV_INLINE +int +ut_strcmp(const char* str1, const char* str2) +{ + return(strcmp(str1, str2)); +} + +/************************************************************************** +Compute strlen(ut_strcpyq(str, q)). */ +UNIV_INLINE +ulint +ut_strlenq( +/*=======*/ + /* out: length of the string when quoted */ + const char* str, /* in: null-terminated string */ + char q) /* in: the quote character */ +{ + ulint len; + + for (len = 0; *str; len++, str++) { + if (*str == q) { + len++; + } + } + + return(len); +} + +/************************************************************************** +Converts a raw binary data to a '\0'-terminated hex string. The output is +truncated if there is not enough space in "hex", make sure "hex_size" is at +least (2 * raw_size + 1) if you do not want this to happen. Returns the +actual number of characters written to "hex" (including the '\0'). */ +UNIV_INLINE +ulint +ut_raw_to_hex( +/*==========*/ + /* out: number of chars written */ + const void* raw, /* in: raw data */ + ulint raw_size, /* in: "raw" length in bytes */ + char* hex, /* out: hex string */ + ulint hex_size) /* in: "hex" size in bytes */ +{ + +#ifdef WORDS_BIGENDIAN + +#define MK_UINT16(a, b) (((uint16) (a)) << 8 | (uint16) (b)) + +#define UINT16_GET_A(u) ((unsigned char) ((u) >> 8)) +#define UINT16_GET_B(u) ((unsigned char) ((u) & 0xFF)) + +#else /* WORDS_BIGENDIAN */ + +#define MK_UINT16(a, b) (((uint16) (b)) << 8 | (uint16) (a)) + +#define UINT16_GET_A(u) ((unsigned char) ((u) & 0xFF)) +#define UINT16_GET_B(u) ((unsigned char) ((u) >> 8)) + +#endif /* WORDS_BIGENDIAN */ + +#define MK_ALL_UINT16_WITH_A(a) \ + MK_UINT16(a, '0'), \ + MK_UINT16(a, '1'), \ + MK_UINT16(a, '2'), \ + MK_UINT16(a, '3'), \ + MK_UINT16(a, '4'), \ + MK_UINT16(a, '5'), \ + MK_UINT16(a, '6'), \ + MK_UINT16(a, '7'), \ + MK_UINT16(a, '8'), \ + MK_UINT16(a, '9'), \ + MK_UINT16(a, 'A'), \ + MK_UINT16(a, 'B'), \ + MK_UINT16(a, 'C'), \ + MK_UINT16(a, 'D'), \ + MK_UINT16(a, 'E'), \ + MK_UINT16(a, 'F') + + static const uint16 hex_map[256] = { + MK_ALL_UINT16_WITH_A('0'), + MK_ALL_UINT16_WITH_A('1'), + MK_ALL_UINT16_WITH_A('2'), + MK_ALL_UINT16_WITH_A('3'), + MK_ALL_UINT16_WITH_A('4'), + MK_ALL_UINT16_WITH_A('5'), + MK_ALL_UINT16_WITH_A('6'), + MK_ALL_UINT16_WITH_A('7'), + MK_ALL_UINT16_WITH_A('8'), + MK_ALL_UINT16_WITH_A('9'), + MK_ALL_UINT16_WITH_A('A'), + MK_ALL_UINT16_WITH_A('B'), + MK_ALL_UINT16_WITH_A('C'), + MK_ALL_UINT16_WITH_A('D'), + MK_ALL_UINT16_WITH_A('E'), + MK_ALL_UINT16_WITH_A('F') + }; + const unsigned char* rawc; + ulint read_bytes; + ulint write_bytes; + ulint i; + + rawc = (const unsigned char*) raw; + + if (hex_size == 0) { + + return(0); + } + + if (hex_size <= 2 * raw_size) { + + read_bytes = hex_size / 2; + write_bytes = hex_size; + } else { + + read_bytes = raw_size; + write_bytes = 2 * raw_size + 1; + } + +#define LOOP_READ_BYTES(ASSIGN) \ + for (i = 0; i < read_bytes; i++) { \ + ASSIGN; \ + hex += 2; \ + rawc++; \ + } + + if (ut_align_offset(hex, 2) == 0) { + + LOOP_READ_BYTES( + *(uint16*) hex = hex_map[*rawc] + ); + } else { + + LOOP_READ_BYTES( + *hex = UINT16_GET_A(hex_map[*rawc]); + *(hex + 1) = UINT16_GET_B(hex_map[*rawc]) + ); + } + + if (hex_size <= 2 * raw_size && hex_size % 2 == 0) { + + hex--; + } + + *hex = '\0'; + + return(write_bytes); +} + +/*********************************************************************** +Adds single quotes to the start and end of string and escapes any quotes +by doubling them. Returns the number of bytes that were written to "buf" +(including the terminating '\0'). If buf_size is too small then the +trailing bytes from "str" are discarded. */ +UNIV_INLINE +ulint +ut_str_sql_format( +/*==============*/ + /* out: number of bytes + that were written */ + const char* str, /* in: string */ + ulint str_len, /* in: string length in bytes */ + char* buf, /* out: output buffer */ + ulint buf_size) /* in: output buffer size + in bytes */ +{ + ulint str_i; + ulint buf_i; + + buf_i = 0; + + switch (buf_size) { + case 3: + + if (str_len == 0) { + + buf[buf_i] = '\''; + buf_i++; + buf[buf_i] = '\''; + buf_i++; + } + /* FALLTHROUGH */ + case 2: + case 1: + + buf[buf_i] = '\0'; + buf_i++; + /* FALLTHROUGH */ + case 0: + + return(buf_i); + } + + /* buf_size >= 4 */ + + buf[0] = '\''; + buf_i = 1; + + for (str_i = 0; str_i < str_len; str_i++) { + + char ch; + + if (buf_size - buf_i == 2) { + + break; + } + + ch = str[str_i]; + + switch (ch) { + case '\0': + + if (UNIV_UNLIKELY(buf_size - buf_i < 4)) { + + goto func_exit; + } + buf[buf_i] = '\\'; + buf_i++; + buf[buf_i] = '0'; + buf_i++; + break; + case '\'': + case '\\': + + if (UNIV_UNLIKELY(buf_size - buf_i < 4)) { + + goto func_exit; + } + buf[buf_i] = ch; + buf_i++; + /* FALLTHROUGH */ + default: + + buf[buf_i] = ch; + buf_i++; + } + } + +func_exit: + + buf[buf_i] = '\''; + buf_i++; + buf[buf_i] = '\0'; + buf_i++; + + return(buf_i); +} diff --git a/storage/xtradb/include/ut0rnd.h b/storage/xtradb/include/ut0rnd.h new file mode 100644 index 00000000000..b9e23d7cd14 --- /dev/null +++ b/storage/xtradb/include/ut0rnd.h @@ -0,0 +1,142 @@ +/***************************************************************************** + +Copyright (c) 1994, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/********************************************************************** +Random numbers and hashing + +Created 1/20/1994 Heikki Tuuri +***********************************************************************/ + +#ifndef ut0rnd_h +#define ut0rnd_h + +#include "univ.i" + +#include "ut0byte.h" + +/* The 'character code' for end of field or string (used +in folding records */ +#define UT_END_OF_FIELD 257 + +/************************************************************ +This is used to set the random number seed. */ +UNIV_INLINE +void +ut_rnd_set_seed( +/*============*/ + ulint seed); /* in: seed */ +/************************************************************ +The following function generates a series of 'random' ulint integers. */ +UNIV_INLINE +ulint +ut_rnd_gen_next_ulint( +/*==================*/ + /* out: the next 'random' number */ + ulint rnd); /* in: the previous random number value */ +/************************************************************* +The following function generates 'random' ulint integers which +enumerate the value space (let there be N of them) of ulint integers +in a pseudo-random fashion. Note that the same integer is repeated +always after N calls to the generator. */ +UNIV_INLINE +ulint +ut_rnd_gen_ulint(void); +/*==================*/ + /* out: the 'random' number */ +/************************************************************ +Generates a random integer from a given interval. */ +UNIV_INLINE +ulint +ut_rnd_interval( +/*============*/ + /* out: the 'random' number */ + ulint low, /* in: low limit; can generate also this value */ + ulint high); /* in: high limit; can generate also this value */ +/************************************************************* +Generates a random iboolean value. */ +UNIV_INLINE +ibool +ut_rnd_gen_ibool(void); +/*=================*/ + /* out: the random value */ +/*********************************************************** +The following function generates a hash value for a ulint integer +to a hash table of size table_size, which should be a prime or some +random number to work reliably. */ +UNIV_INLINE +ulint +ut_hash_ulint( +/*==========*/ + /* out: hash value */ + ulint key, /* in: value to be hashed */ + ulint table_size); /* in: hash table size */ +/***************************************************************** +Folds a pair of ulints. */ +UNIV_INLINE +ulint +ut_fold_ulint_pair( +/*===============*/ + /* out: folded value */ + ulint n1, /* in: ulint */ + ulint n2) /* in: ulint */ + __attribute__((const)); +/***************************************************************** +Folds a dulint. */ +UNIV_INLINE +ulint +ut_fold_dulint( +/*===========*/ + /* out: folded value */ + dulint d) /* in: dulint */ + __attribute__((const)); +/***************************************************************** +Folds a character string ending in the null character. */ +UNIV_INLINE +ulint +ut_fold_string( +/*===========*/ + /* out: folded value */ + const char* str) /* in: null-terminated string */ + __attribute__((pure)); +/***************************************************************** +Folds a binary string. */ +UNIV_INLINE +ulint +ut_fold_binary( +/*===========*/ + /* out: folded value */ + const byte* str, /* in: string of bytes */ + ulint len) /* in: length */ + __attribute__((pure)); +/*************************************************************** +Looks for a prime number slightly greater than the given argument. +The prime is chosen so that it is not near any power of 2. */ +UNIV_INTERN +ulint +ut_find_prime( +/*==========*/ + /* out: prime */ + ulint n) /* in: positive number > 100 */ + __attribute__((const)); + + +#ifndef UNIV_NONINL +#include "ut0rnd.ic" +#endif + +#endif diff --git a/storage/xtradb/include/ut0rnd.ic b/storage/xtradb/include/ut0rnd.ic new file mode 100644 index 00000000000..d72100d16a1 --- /dev/null +++ b/storage/xtradb/include/ut0rnd.ic @@ -0,0 +1,228 @@ +/***************************************************************************** + +Copyright (c) 1994, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/****************************************************************** +Random numbers and hashing + +Created 5/30/1994 Heikki Tuuri +*******************************************************************/ + +#define UT_HASH_RANDOM_MASK 1463735687 +#define UT_HASH_RANDOM_MASK2 1653893711 +#define UT_RND1 151117737 +#define UT_RND2 119785373 +#define UT_RND3 85689495 +#define UT_RND4 76595339 +#define UT_SUM_RND2 98781234 +#define UT_SUM_RND3 126792457 +#define UT_SUM_RND4 63498502 +#define UT_XOR_RND1 187678878 +#define UT_XOR_RND2 143537923 + +extern ulint ut_rnd_ulint_counter; + +/************************************************************ +This is used to set the random number seed. */ +UNIV_INLINE +void +ut_rnd_set_seed( +/*============*/ + ulint seed) /* in: seed */ +{ + ut_rnd_ulint_counter = seed; +} + +/************************************************************ +The following function generates a series of 'random' ulint integers. */ +UNIV_INLINE +ulint +ut_rnd_gen_next_ulint( +/*==================*/ + /* out: the next 'random' number */ + ulint rnd) /* in: the previous random number value */ +{ + ulint n_bits; + + n_bits = 8 * sizeof(ulint); + + rnd = UT_RND2 * rnd + UT_SUM_RND3; + rnd = UT_XOR_RND1 ^ rnd; + rnd = (rnd << 20) + (rnd >> (n_bits - 20)); + rnd = UT_RND3 * rnd + UT_SUM_RND4; + rnd = UT_XOR_RND2 ^ rnd; + rnd = (rnd << 20) + (rnd >> (n_bits - 20)); + rnd = UT_RND1 * rnd + UT_SUM_RND2; + + return(rnd); +} + +/************************************************************ +The following function generates 'random' ulint integers which +enumerate the value space of ulint integers in a pseudo random +fashion. Note that the same integer is repeated always after +2 to power 32 calls to the generator (if ulint is 32-bit). */ +UNIV_INLINE +ulint +ut_rnd_gen_ulint(void) +/*==================*/ + /* out: the 'random' number */ +{ + ulint rnd; + ulint n_bits; + + n_bits = 8 * sizeof(ulint); + + ut_rnd_ulint_counter = UT_RND1 * ut_rnd_ulint_counter + UT_RND2; + + rnd = ut_rnd_gen_next_ulint(ut_rnd_ulint_counter); + + return(rnd); +} + +/************************************************************ +Generates a random integer from a given interval. */ +UNIV_INLINE +ulint +ut_rnd_interval( +/*============*/ + /* out: the 'random' number */ + ulint low, /* in: low limit; can generate also this value */ + ulint high) /* in: high limit; can generate also this value */ +{ + ulint rnd; + + ut_ad(high >= low); + + if (low == high) { + + return(low); + } + + rnd = ut_rnd_gen_ulint(); + + return(low + (rnd % (high - low + 1))); +} + +/************************************************************* +Generates a random iboolean value. */ +UNIV_INLINE +ibool +ut_rnd_gen_ibool(void) +/*=================*/ + /* out: the random value */ +{ + ulint x; + + x = ut_rnd_gen_ulint(); + + if (((x >> 20) + (x >> 15)) & 1) { + + return(TRUE); + } + + return(FALSE); +} + +/*********************************************************** +The following function generates a hash value for a ulint integer +to a hash table of size table_size, which should be a prime +or some random number for the hash table to work reliably. */ +UNIV_INLINE +ulint +ut_hash_ulint( +/*==========*/ + /* out: hash value */ + ulint key, /* in: value to be hashed */ + ulint table_size) /* in: hash table size */ +{ + key = key ^ UT_HASH_RANDOM_MASK2; + + return(key % table_size); +} + +/***************************************************************** +Folds a pair of ulints. */ +UNIV_INLINE +ulint +ut_fold_ulint_pair( +/*===============*/ + /* out: folded value */ + ulint n1, /* in: ulint */ + ulint n2) /* in: ulint */ +{ + return(((((n1 ^ n2 ^ UT_HASH_RANDOM_MASK2) << 8) + n1) + ^ UT_HASH_RANDOM_MASK) + n2); +} + +/***************************************************************** +Folds a dulint. */ +UNIV_INLINE +ulint +ut_fold_dulint( +/*===========*/ + /* out: folded value */ + dulint d) /* in: dulint */ +{ + return(ut_fold_ulint_pair(ut_dulint_get_low(d), + ut_dulint_get_high(d))); +} + +/***************************************************************** +Folds a character string ending in the null character. */ +UNIV_INLINE +ulint +ut_fold_string( +/*===========*/ + /* out: folded value */ + const char* str) /* in: null-terminated string */ +{ + ulint fold = 0; + + ut_ad(str); + + while (*str != '\0') { + fold = ut_fold_ulint_pair(fold, (ulint)(*str)); + str++; + } + + return(fold); +} + +/***************************************************************** +Folds a binary string. */ +UNIV_INLINE +ulint +ut_fold_binary( +/*===========*/ + /* out: folded value */ + const byte* str, /* in: string of bytes */ + ulint len) /* in: length */ +{ + const byte* str_end = str + len; + ulint fold = 0; + + ut_ad(str || !len); + + while (str < str_end) { + fold = ut_fold_ulint_pair(fold, (ulint)(*str)); + + str++; + } + + return(fold); +} diff --git a/storage/xtradb/include/ut0sort.h b/storage/xtradb/include/ut0sort.h new file mode 100644 index 00000000000..5fd5db54832 --- /dev/null +++ b/storage/xtradb/include/ut0sort.h @@ -0,0 +1,105 @@ +/***************************************************************************** + +Copyright (c) 1995, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/********************************************************************** +Sort utility + +Created 11/9/1995 Heikki Tuuri +***********************************************************************/ + +#ifndef ut0sort_h +#define ut0sort_h + +#include "univ.i" + +/* This module gives a macro definition of the body of +a standard sort function for an array of elements of any +type. The comparison function is given as a parameter to +the macro. The sort algorithm is mergesort which has logarithmic +worst case. +*/ + +/*********************************************************************** +This macro expands to the body of a standard sort function. +The sort function uses mergesort and must be defined separately +for each type of array. +Also the comparison function has to be defined individually +for each array cell type. SORT_FUN is the sort function name. +The function takes the array to be sorted (ARR), +the array of auxiliary space (AUX_ARR) of same size, +and the low (LOW), inclusive, and high (HIGH), noninclusive, +limits for the sort interval as arguments. +CMP_FUN is the comparison function name. It takes as arguments +two elements from the array and returns 1, if the first is bigger, +0 if equal, and -1 if the second bigger. */ + +#define UT_SORT_FUNCTION_BODY(SORT_FUN, ARR, AUX_ARR, LOW, HIGH, CMP_FUN)\ +{\ + ulint ut_sort_mid77;\ + ulint ut_sort_i77;\ + ulint ut_sort_low77;\ + ulint ut_sort_high77;\ +\ + ut_ad((LOW) < (HIGH));\ + ut_ad(ARR);\ + ut_ad(AUX_ARR);\ +\ + if ((LOW) == (HIGH) - 1) {\ + return;\ + } else if ((LOW) == (HIGH) - 2) {\ + if (CMP_FUN((ARR)[LOW], (ARR)[(HIGH) - 1]) > 0) {\ + (AUX_ARR)[LOW] = (ARR)[LOW];\ + (ARR)[LOW] = (ARR)[(HIGH) - 1];\ + (ARR)[(HIGH) - 1] = (AUX_ARR)[LOW];\ + }\ + return;\ + }\ +\ + ut_sort_mid77 = ((LOW) + (HIGH)) / 2;\ +\ + SORT_FUN((ARR), (AUX_ARR), (LOW), ut_sort_mid77);\ + SORT_FUN((ARR), (AUX_ARR), ut_sort_mid77, (HIGH));\ +\ + ut_sort_low77 = (LOW);\ + ut_sort_high77 = ut_sort_mid77;\ +\ + for (ut_sort_i77 = (LOW); ut_sort_i77 < (HIGH); ut_sort_i77++) {\ +\ + if (ut_sort_low77 >= ut_sort_mid77) {\ + (AUX_ARR)[ut_sort_i77] = (ARR)[ut_sort_high77];\ + ut_sort_high77++;\ + } else if (ut_sort_high77 >= (HIGH)) {\ + (AUX_ARR)[ut_sort_i77] = (ARR)[ut_sort_low77];\ + ut_sort_low77++;\ + } else if (CMP_FUN((ARR)[ut_sort_low77],\ + (ARR)[ut_sort_high77]) > 0) {\ + (AUX_ARR)[ut_sort_i77] = (ARR)[ut_sort_high77];\ + ut_sort_high77++;\ + } else {\ + (AUX_ARR)[ut_sort_i77] = (ARR)[ut_sort_low77];\ + ut_sort_low77++;\ + }\ + }\ +\ + memcpy((void*) ((ARR) + (LOW)), (AUX_ARR) + (LOW),\ + ((HIGH) - (LOW)) * sizeof *(ARR));\ +}\ + + +#endif + diff --git a/storage/xtradb/include/ut0ut.h b/storage/xtradb/include/ut0ut.h new file mode 100644 index 00000000000..3ca14acd2ef --- /dev/null +++ b/storage/xtradb/include/ut0ut.h @@ -0,0 +1,328 @@ +/***************************************************************************** + +Copyright (c) 1994, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/********************************************************************** +Various utilities + +Created 1/20/1994 Heikki Tuuri +***********************************************************************/ + +#ifndef ut0ut_h +#define ut0ut_h + +#include "univ.i" +#include <time.h> +#ifndef MYSQL_SERVER +#include <ctype.h> +#endif + +#define TEMP_INDEX_PREFIX '\377' /* Index name prefix in fast index + creation */ + +typedef time_t ib_time_t; + +/************************************************************************* +Delays execution for at most max_wait_us microseconds or returns earlier +if cond becomes true; cond is evaluated every 2 ms. */ + +#define UT_WAIT_FOR(cond, max_wait_us) \ +do { \ + ullint start_us; \ + start_us = ut_time_us(NULL); \ + while (!(cond) \ + && ut_time_us(NULL) - start_us < (max_wait_us)) {\ + \ + os_thread_sleep(2000 /* 2 ms */); \ + } \ +} while (0) + +/************************************************************ +Gets the high 32 bits in a ulint. That is makes a shift >> 32, +but since there seem to be compiler bugs in both gcc and Visual C++, +we do this by a special conversion. */ +UNIV_INTERN +ulint +ut_get_high32( +/*==========*/ + /* out: a >> 32 */ + ulint a); /* in: ulint */ +/********************************************************** +Calculates the minimum of two ulints. */ +UNIV_INLINE +ulint +ut_min( +/*===*/ + /* out: minimum */ + ulint n1, /* in: first number */ + ulint n2); /* in: second number */ +/********************************************************** +Calculates the maximum of two ulints. */ +UNIV_INLINE +ulint +ut_max( +/*===*/ + /* out: maximum */ + ulint n1, /* in: first number */ + ulint n2); /* in: second number */ +/******************************************************************** +Calculates minimum of two ulint-pairs. */ +UNIV_INLINE +void +ut_pair_min( +/*========*/ + ulint* a, /* out: more significant part of minimum */ + ulint* b, /* out: less significant part of minimum */ + ulint a1, /* in: more significant part of first pair */ + ulint b1, /* in: less significant part of first pair */ + ulint a2, /* in: more significant part of second pair */ + ulint b2); /* in: less significant part of second pair */ +/********************************************************** +Compares two ulints. */ +UNIV_INLINE +int +ut_ulint_cmp( +/*=========*/ + /* out: 1 if a > b, 0 if a == b, -1 if a < b */ + ulint a, /* in: ulint */ + ulint b); /* in: ulint */ +/*********************************************************** +Compares two pairs of ulints. */ +UNIV_INLINE +int +ut_pair_cmp( +/*========*/ + /* out: -1 if a < b, 0 if a == b, + 1 if a > b */ + ulint a1, /* in: more significant part of first pair */ + ulint a2, /* in: less significant part of first pair */ + ulint b1, /* in: more significant part of second pair */ + ulint b2); /* in: less significant part of second pair */ +/***************************************************************** +Determines if a number is zero or a power of two. */ +#define ut_is_2pow(n) UNIV_LIKELY(!((n) & ((n) - 1))) +/***************************************************************** +Calculates fast the remainder of n/m when m is a power of two. */ +#define ut_2pow_remainder(n, m) ((n) & ((m) - 1)) +/***************************************************************** +Calculates the biggest multiple of m that is not bigger than n +when m is a power of two. In other words, rounds n down to m * k. */ +#define ut_2pow_round(n, m) ((n) & ~((m) - 1)) +#define ut_calc_align_down(n, m) ut_2pow_round(n, m) +/************************************************************ +Calculates the smallest multiple of m that is not smaller than n +when m is a power of two. In other words, rounds n up to m * k. */ +#define ut_calc_align(n, m) (((n) + ((m) - 1)) & ~((m) - 1)) +/***************************************************************** +Calculates fast the 2-logarithm of a number, rounded upward to an +integer. */ +UNIV_INLINE +ulint +ut_2_log( +/*=====*/ + /* out: logarithm in the base 2, rounded upward */ + ulint n); /* in: number */ +/***************************************************************** +Calculates 2 to power n. */ +UNIV_INLINE +ulint +ut_2_exp( +/*=====*/ + /* out: 2 to power n */ + ulint n); /* in: number */ +/***************************************************************** +Calculates fast the number rounded up to the nearest power of 2. */ +UNIV_INTERN +ulint +ut_2_power_up( +/*==========*/ + /* out: first power of 2 which is >= n */ + ulint n) /* in: number != 0 */ + __attribute__((const)); + +/* Determine how many bytes (groups of 8 bits) are needed to +store the given number of bits. */ +#define UT_BITS_IN_BYTES(b) (((b) + 7) / 8) + +/************************************************************** +Returns system time. We do not specify the format of the time returned: +the only way to manipulate it is to use the function ut_difftime. */ +UNIV_INTERN +ib_time_t +ut_time(void); +/*=========*/ +/************************************************************** +Returns system time. +Upon successful completion, the value 0 is returned; otherwise the +value -1 is returned and the global variable errno is set to indicate the +error. */ +UNIV_INTERN +int +ut_usectime( +/*========*/ + /* out: 0 on success, -1 otherwise */ + ulint* sec, /* out: seconds since the Epoch */ + ulint* ms); /* out: microseconds since the Epoch+*sec */ + +/************************************************************** +Returns the number of microseconds since epoch. Similar to +time(3), the return value is also stored in *tloc, provided +that tloc is non-NULL. */ +UNIV_INTERN +ullint +ut_time_us( +/*=======*/ + /* out: us since epoch */ + ullint* tloc); /* out: us since epoch, if non-NULL */ + +/************************************************************** +Returns the difference of two times in seconds. */ +UNIV_INTERN +double +ut_difftime( +/*========*/ + /* out: time2 - time1 expressed in seconds */ + ib_time_t time2, /* in: time */ + ib_time_t time1); /* in: time */ +/************************************************************** +Prints a timestamp to a file. */ +UNIV_INTERN +void +ut_print_timestamp( +/*===============*/ + FILE* file); /* in: file where to print */ +/************************************************************** +Sprintfs a timestamp to a buffer, 13..14 chars plus terminating NUL. */ +UNIV_INTERN +void +ut_sprintf_timestamp( +/*=================*/ + char* buf); /* in: buffer where to sprintf */ +#ifdef UNIV_HOTBACKUP +/************************************************************** +Sprintfs a timestamp to a buffer with no spaces and with ':' characters +replaced by '_'. */ +UNIV_INTERN +void +ut_sprintf_timestamp_without_extra_chars( +/*=====================================*/ + char* buf); /* in: buffer where to sprintf */ +/************************************************************** +Returns current year, month, day. */ +UNIV_INTERN +void +ut_get_year_month_day( +/*==================*/ + ulint* year, /* out: current year */ + ulint* month, /* out: month */ + ulint* day); /* out: day */ +#endif /* UNIV_HOTBACKUP */ +/***************************************************************** +Runs an idle loop on CPU. The argument gives the desired delay +in microseconds on 100 MHz Pentium + Visual C++. */ +UNIV_INTERN +ulint +ut_delay( +/*=====*/ + /* out: dummy value */ + ulint delay); /* in: delay in microseconds on 100 MHz Pentium */ +/***************************************************************** +Prints the contents of a memory buffer in hex and ascii. */ +UNIV_INTERN +void +ut_print_buf( +/*=========*/ + FILE* file, /* in: file where to print */ + const void* buf, /* in: memory buffer */ + ulint len); /* in: length of the buffer */ + +/************************************************************************** +Outputs a NUL-terminated file name, quoted with apostrophes. */ +UNIV_INTERN +void +ut_print_filename( +/*==============*/ + FILE* f, /* in: output stream */ + const char* name); /* in: name to print */ + +/* Forward declaration of transaction handle */ +struct trx_struct; + +/************************************************************************** +Outputs a fixed-length string, quoted as an SQL identifier. +If the string contains a slash '/', the string will be +output as two identifiers separated by a period (.), +as in SQL database_name.identifier. */ +UNIV_INTERN +void +ut_print_name( +/*==========*/ + FILE* f, /* in: output stream */ + struct trx_struct*trx, /* in: transaction */ + ibool table_id,/* in: TRUE=print a table name, + FALSE=print other identifier */ + const char* name); /* in: name to print */ + +/************************************************************************** +Outputs a fixed-length string, quoted as an SQL identifier. +If the string contains a slash '/', the string will be +output as two identifiers separated by a period (.), +as in SQL database_name.identifier. */ +UNIV_INTERN +void +ut_print_namel( +/*===========*/ + FILE* f, /* in: output stream */ + struct trx_struct*trx, /* in: transaction (NULL=no quotes) */ + ibool table_id,/* in: TRUE=print a table name, + FALSE=print other identifier */ + const char* name, /* in: name to print */ + ulint namelen);/* in: length of name */ + +/************************************************************************** +Catenate files. */ +UNIV_INTERN +void +ut_copy_file( +/*=========*/ + FILE* dest, /* in: output file */ + FILE* src); /* in: input file to be appended to output */ + +/************************************************************************** +snprintf(). */ + +#ifdef __WIN__ +int +ut_snprintf( + /* out: number of characters that would + have been printed if the size were + unlimited, not including the terminating + '\0'. */ + char* str, /* out: string */ + size_t size, /* in: str size */ + const char* fmt, /* in: format */ + ...); /* in: format values */ +#else +#define ut_snprintf snprintf +#endif /* __WIN__ */ + +#ifndef UNIV_NONINL +#include "ut0ut.ic" +#endif + +#endif + diff --git a/storage/xtradb/include/ut0ut.ic b/storage/xtradb/include/ut0ut.ic new file mode 100644 index 00000000000..e4e0a2acce6 --- /dev/null +++ b/storage/xtradb/include/ut0ut.ic @@ -0,0 +1,161 @@ +/***************************************************************************** + +Copyright (c) 1994, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/****************************************************************** +Various utilities + +Created 5/30/1994 Heikki Tuuri +*******************************************************************/ + +/********************************************************** +Calculates the minimum of two ulints. */ +UNIV_INLINE +ulint +ut_min( +/*===*/ + /* out: minimum */ + ulint n1, /* in: first number */ + ulint n2) /* in: second number */ +{ + return((n1 <= n2) ? n1 : n2); +} + +/********************************************************** +Calculates the maximum of two ulints. */ +UNIV_INLINE +ulint +ut_max( +/*===*/ + /* out: maximum */ + ulint n1, /* in: first number */ + ulint n2) /* in: second number */ +{ + return((n1 <= n2) ? n2 : n1); +} + +/******************************************************************** +Calculates minimum of two ulint-pairs. */ +UNIV_INLINE +void +ut_pair_min( +/*========*/ + ulint* a, /* out: more significant part of minimum */ + ulint* b, /* out: less significant part of minimum */ + ulint a1, /* in: more significant part of first pair */ + ulint b1, /* in: less significant part of first pair */ + ulint a2, /* in: more significant part of second pair */ + ulint b2) /* in: less significant part of second pair */ +{ + if (a1 == a2) { + *a = a1; + *b = ut_min(b1, b2); + } else if (a1 < a2) { + *a = a1; + *b = b1; + } else { + *a = a2; + *b = b2; + } +} + +/********************************************************** +Compares two ulints. */ +UNIV_INLINE +int +ut_ulint_cmp( +/*=========*/ + /* out: 1 if a > b, 0 if a == b, -1 if a < b */ + ulint a, /* in: ulint */ + ulint b) /* in: ulint */ +{ + if (a < b) { + return(-1); + } else if (a == b) { + return(0); + } else { + return(1); + } +} + +/*********************************************************** +Compares two pairs of ulints. */ +UNIV_INLINE +int +ut_pair_cmp( +/*========*/ + /* out: -1 if a < b, 0 if a == b, 1 if a > b */ + ulint a1, /* in: more significant part of first pair */ + ulint a2, /* in: less significant part of first pair */ + ulint b1, /* in: more significant part of second pair */ + ulint b2) /* in: less significant part of second pair */ +{ + if (a1 > b1) { + return(1); + } else if (a1 < b1) { + return(-1); + } else if (a2 > b2) { + return(1); + } else if (a2 < b2) { + return(-1); + } else { + return(0); + } +} + +/***************************************************************** +Calculates fast the 2-logarithm of a number, rounded upward to an +integer. */ +UNIV_INLINE +ulint +ut_2_log( +/*=====*/ + /* out: logarithm in the base 2, rounded upward */ + ulint n) /* in: number != 0 */ +{ + ulint res; + + res = 0; + + ut_ad(n > 0); + + n = n - 1; + + for (;;) { + n = n / 2; + + if (n == 0) { + break; + } + + res++; + } + + return(res + 1); +} + +/***************************************************************** +Calculates 2 to power n. */ +UNIV_INLINE +ulint +ut_2_exp( +/*=====*/ + /* out: 2 to power n */ + ulint n) /* in: number */ +{ + return((ulint) 1 << n); +} diff --git a/storage/xtradb/include/ut0vec.h b/storage/xtradb/include/ut0vec.h new file mode 100644 index 00000000000..aeb7e168dc6 --- /dev/null +++ b/storage/xtradb/include/ut0vec.h @@ -0,0 +1,117 @@ +/***************************************************************************** + +Copyright (c) 2006, 2009, Innobase Oy. 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 IB_VECTOR_H +#define IB_VECTOR_H + +#include "univ.i" +#include "mem0mem.h" + +typedef struct ib_vector_struct ib_vector_t; + +/* An automatically resizing vector datatype with the following properties: + + -Contains void* items. + + -The items are owned by the caller. + + -All memory allocation is done through a heap owned by the caller, who is + responsible for freeing it when done with the vector. + + -When the vector is resized, the old memory area is left allocated since it + uses the same heap as the new memory area, so this is best used for + relatively small or short-lived uses. +*/ + +/******************************************************************** +Create a new vector with the given initial size. */ +UNIV_INTERN +ib_vector_t* +ib_vector_create( +/*=============*/ + /* out: vector */ + mem_heap_t* heap, /* in: heap */ + ulint size); /* in: initial size */ + +/******************************************************************** +Push a new element to the vector, increasing its size if necessary. */ +UNIV_INTERN +void +ib_vector_push( +/*===========*/ + ib_vector_t* vec, /* in: vector */ + void* elem); /* in: data element */ + +/******************************************************************** +Get the number of elements in the vector. */ +UNIV_INLINE +ulint +ib_vector_size( +/*===========*/ + /* out: number of elements in vector */ + const ib_vector_t* vec); /* in: vector */ + +/******************************************************************** +Test whether a vector is empty or not. */ +UNIV_INLINE +ibool +ib_vector_is_empty( +/*===============*/ + /* out: TRUE if empty */ + const ib_vector_t* vec); /* in: vector */ + +/******************************************************************** +Get the n'th element. */ +UNIV_INLINE +void* +ib_vector_get( +/*==========*/ + /* out: n'th element */ + ib_vector_t* vec, /* in: vector */ + ulint n); /* in: element index to get */ + +/******************************************************************** +Remove the last element from the vector. */ +UNIV_INLINE +void* +ib_vector_pop( +/*==========*/ + ib_vector_t* vec); /* in: vector */ + +/******************************************************************** +Free the underlying heap of the vector. Note that vec is invalid +after this call. */ +UNIV_INLINE +void +ib_vector_free( +/*===========*/ + ib_vector_t* vec); /* in,own: vector */ + +/* See comment at beginning of file. */ +struct ib_vector_struct { + mem_heap_t* heap; /* heap */ + void** data; /* data elements */ + ulint used; /* number of elements currently used */ + ulint total; /* number of elements allocated */ +}; + +#ifndef UNIV_NONINL +#include "ut0vec.ic" +#endif + +#endif diff --git a/storage/xtradb/include/ut0vec.ic b/storage/xtradb/include/ut0vec.ic new file mode 100644 index 00000000000..b0e853717e3 --- /dev/null +++ b/storage/xtradb/include/ut0vec.ic @@ -0,0 +1,88 @@ +/***************************************************************************** + +Copyright (c) 2006, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/******************************************************************** +Get number of elements in vector. */ +UNIV_INLINE +ulint +ib_vector_size( +/*===========*/ + /* out: number of elements in vector */ + const ib_vector_t* vec) /* in: vector */ +{ + return(vec->used); +} + +/******************************************************************** +Get n'th element. */ +UNIV_INLINE +void* +ib_vector_get( +/*==========*/ + /* out: n'th element */ + ib_vector_t* vec, /* in: vector */ + ulint n) /* in: element index to get */ +{ + ut_a(n < vec->used); + + return(vec->data[n]); +} + +/******************************************************************** +Remove the last element from the vector. */ +UNIV_INLINE +void* +ib_vector_pop( +/*==========*/ + /* out: last vector element */ + ib_vector_t* vec) /* in/out: vector */ +{ + void* elem; + + ut_a(vec->used > 0); + --vec->used; + elem = vec->data[vec->used]; + + ut_d(vec->data[vec->used] = NULL); + UNIV_MEM_INVALID(&vec->data[vec->used], sizeof(*vec->data)); + + return(elem); +} + +/******************************************************************** +Free the underlying heap of the vector. Note that vec is invalid +after this call. */ +UNIV_INLINE +void +ib_vector_free( +/*===========*/ + ib_vector_t* vec) /* in, own: vector */ +{ + mem_heap_free(vec->heap); +} + +/******************************************************************** +Test whether a vector is empty or not. */ +UNIV_INLINE +ibool +ib_vector_is_empty( +/*===============*/ /* out: TRUE if empty else FALSE */ + const ib_vector_t* vec) /* in vector to test */ +{ + return(ib_vector_size(vec) == 0); +} diff --git a/storage/xtradb/include/ut0wqueue.h b/storage/xtradb/include/ut0wqueue.h new file mode 100644 index 00000000000..6bb80dad532 --- /dev/null +++ b/storage/xtradb/include/ut0wqueue.h @@ -0,0 +1,77 @@ +/***************************************************************************** + +Copyright (c) 2006, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/*********************************************************************** +A Work queue. Threads can add work items to the queue and other threads can +wait for work items to be available and take them off the queue for +processing. +************************************************************************/ + +#ifndef IB_WORK_QUEUE_H +#define IB_WORK_QUEUE_H + +#include "ut0list.h" +#include "mem0mem.h" +#include "os0sync.h" +#include "sync0types.h" + +typedef struct ib_wqueue_struct ib_wqueue_t; + +/******************************************************************** +Create a new work queue. */ +UNIV_INTERN +ib_wqueue_t* +ib_wqueue_create(void); +/*===================*/ + /* out: work queue */ + +/******************************************************************** +Free a work queue. */ +UNIV_INTERN +void +ib_wqueue_free( +/*===========*/ + ib_wqueue_t* wq); /* in: work queue */ + +/******************************************************************** +Add a work item to the queue. */ +UNIV_INTERN +void +ib_wqueue_add( +/*==========*/ + ib_wqueue_t* wq, /* in: work queue */ + void* item, /* in: work item */ + mem_heap_t* heap); /* in: memory heap to use for allocating the + list node */ + +/******************************************************************** +Wait for a work item to appear in the queue. */ +UNIV_INTERN +void* +ib_wqueue_wait( + /* out: work item */ + ib_wqueue_t* wq); /* in: work queue */ + +/* Work queue. */ +struct ib_wqueue_struct { + mutex_t mutex; /* mutex protecting everything */ + ib_list_t* items; /* work item list */ + os_event_t event; /* event we use to signal additions to list */ +}; + +#endif diff --git a/storage/xtradb/lock/lock0iter.c b/storage/xtradb/lock/lock0iter.c new file mode 100644 index 00000000000..e7a128d0db3 --- /dev/null +++ b/storage/xtradb/lock/lock0iter.c @@ -0,0 +1,113 @@ +/***************************************************************************** + +Copyright (c) 2007, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/****************************************************** +Lock queue iterator. Can iterate over table and record +lock queues. + +Created July 16, 2007 Vasil Dimov +*******************************************************/ + +#define LOCK_MODULE_IMPLEMENTATION + +#include "univ.i" +#include "lock0iter.h" +#include "lock0lock.h" +#include "lock0priv.h" +#include "ut0dbg.h" +#include "ut0lst.h" +#ifdef UNIV_DEBUG +# include "srv0srv.h" /* kernel_mutex */ +#endif /* UNIV_DEBUG */ + +/*********************************************************************** +Initialize lock queue iterator so that it starts to iterate from +"lock". bit_no specifies the record number within the heap where the +record is stored. It can be undefined (ULINT_UNDEFINED) in two cases: +1. If the lock is a table lock, thus we have a table lock queue; +2. If the lock is a record lock and it is a wait lock. In this case + bit_no is calculated in this function by using + lock_rec_find_set_bit(). There is exactly one bit set in the bitmap + of a wait lock. */ +UNIV_INTERN +void +lock_queue_iterator_reset( +/*======================*/ + lock_queue_iterator_t* iter, /* out: iterator */ + const lock_t* lock, /* in: lock to start from */ + ulint bit_no) /* in: record number in the + heap */ +{ + ut_ad(mutex_own(&kernel_mutex)); + + iter->current_lock = lock; + + if (bit_no != ULINT_UNDEFINED) { + + iter->bit_no = bit_no; + } else { + + switch (lock_get_type_low(lock)) { + case LOCK_TABLE: + iter->bit_no = ULINT_UNDEFINED; + break; + case LOCK_REC: + iter->bit_no = lock_rec_find_set_bit(lock); + ut_a(iter->bit_no != ULINT_UNDEFINED); + break; + default: + ut_error; + } + } +} + +/*********************************************************************** +Gets the previous lock in the lock queue, returns NULL if there are no +more locks (i.e. the current lock is the first one). The iterator is +receded (if not-NULL is returned). */ +UNIV_INTERN +const lock_t* +lock_queue_iterator_get_prev( +/*=========================*/ + /* out: previous lock or NULL */ + lock_queue_iterator_t* iter) /* in/out: iterator */ +{ + const lock_t* prev_lock; + + ut_ad(mutex_own(&kernel_mutex)); + + switch (lock_get_type_low(iter->current_lock)) { + case LOCK_REC: + prev_lock = lock_rec_get_prev( + iter->current_lock, iter->bit_no); + break; + case LOCK_TABLE: + prev_lock = UT_LIST_GET_PREV( + un_member.tab_lock.locks, iter->current_lock); + break; + default: + ut_error; + } + + if (prev_lock != NULL) { + + iter->current_lock = prev_lock; + } + + return(prev_lock); +} diff --git a/storage/xtradb/lock/lock0lock.c b/storage/xtradb/lock/lock0lock.c new file mode 100644 index 00000000000..3730c66313d --- /dev/null +++ b/storage/xtradb/lock/lock0lock.c @@ -0,0 +1,5680 @@ +/***************************************************************************** + +Copyright (c) 1996, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/****************************************************** +The transaction lock system + +Created 5/7/1996 Heikki Tuuri +*******************************************************/ + +#define LOCK_MODULE_IMPLEMENTATION + +#include "lock0lock.h" +#include "lock0priv.h" + +#ifdef UNIV_NONINL +#include "lock0lock.ic" +#include "lock0priv.ic" +#endif + +#include "ha_prototypes.h" +#include "usr0sess.h" +#include "trx0purge.h" +#include "dict0mem.h" +#include "trx0sys.h" + +/* Restricts the length of search we will do in the waits-for +graph of transactions */ +#define LOCK_MAX_N_STEPS_IN_DEADLOCK_CHECK 1000000 + +/* Restricts the recursion depth of the search we will do in the waits-for +graph of transactions */ +#define LOCK_MAX_DEPTH_IN_DEADLOCK_CHECK 200 + +/* When releasing transaction locks, this specifies how often we release +the kernel mutex for a moment to give also others access to it */ + +#define LOCK_RELEASE_KERNEL_INTERVAL 1000 + +/* Safety margin when creating a new record lock: this many extra records +can be inserted to the page without need to create a lock with a bigger +bitmap */ + +#define LOCK_PAGE_BITMAP_MARGIN 64 + +/* An explicit record lock affects both the record and the gap before it. +An implicit x-lock does not affect the gap, it only locks the index +record from read or update. + +If a transaction has modified or inserted an index record, then +it owns an implicit x-lock on the record. On a secondary index record, +a transaction has an implicit x-lock also if it has modified the +clustered index record, the max trx id of the page where the secondary +index record resides is >= trx id of the transaction (or database recovery +is running), and there are no explicit non-gap lock requests on the +secondary index record. + +This complicated definition for a secondary index comes from the +implementation: we want to be able to determine if a secondary index +record has an implicit x-lock, just by looking at the present clustered +index record, not at the historical versions of the record. The +complicated definition can be explained to the user so that there is +nondeterminism in the access path when a query is answered: we may, +or may not, access the clustered index record and thus may, or may not, +bump into an x-lock set there. + +Different transaction can have conflicting locks set on the gap at the +same time. The locks on the gap are purely inhibitive: an insert cannot +be made, or a select cursor may have to wait if a different transaction +has a conflicting lock on the gap. An x-lock on the gap does not give +the right to insert into the gap. + +An explicit lock can be placed on a user record or the supremum record of +a page. The locks on the supremum record are always thought to be of the gap +type, though the gap bit is not set. When we perform an update of a record +where the size of the record changes, we may temporarily store its explicit +locks on the infimum record of the page, though the infimum otherwise never +carries locks. + +A waiting record lock can also be of the gap type. A waiting lock request +can be granted when there is no conflicting mode lock request by another +transaction ahead of it in the explicit lock queue. + +In version 4.0.5 we added yet another explicit lock type: LOCK_REC_NOT_GAP. +It only locks the record it is placed on, not the gap before the record. +This lock type is necessary to emulate an Oracle-like READ COMMITTED isolation +level. + +------------------------------------------------------------------------- +RULE 1: If there is an implicit x-lock on a record, and there are non-gap +------- +lock requests waiting in the queue, then the transaction holding the implicit +x-lock also has an explicit non-gap record x-lock. Therefore, as locks are +released, we can grant locks to waiting lock requests purely by looking at +the explicit lock requests in the queue. + +RULE 3: Different transactions cannot have conflicting granted non-gap locks +------- +on a record at the same time. However, they can have conflicting granted gap +locks. +RULE 4: If a there is a waiting lock request in a queue, no lock request, +------- +gap or not, can be inserted ahead of it in the queue. In record deletes +and page splits new gap type locks can be created by the database manager +for a transaction, and without rule 4, the waits-for graph of transactions +might become cyclic without the database noticing it, as the deadlock check +is only performed when a transaction itself requests a lock! +------------------------------------------------------------------------- + +An insert is allowed to a gap if there are no explicit lock requests by +other transactions on the next record. It does not matter if these lock +requests are granted or waiting, gap bit set or not, with the exception +that a gap type request set by another transaction to wait for +its turn to do an insert is ignored. On the other hand, an +implicit x-lock by another transaction does not prevent an insert, which +allows for more concurrency when using an Oracle-style sequence number +generator for the primary key with many transactions doing inserts +concurrently. + +A modify of a record is allowed if the transaction has an x-lock on the +record, or if other transactions do not have any non-gap lock requests on the +record. + +A read of a single user record with a cursor is allowed if the transaction +has a non-gap explicit, or an implicit lock on the record, or if the other +transactions have no x-lock requests on the record. At a page supremum a +read is always allowed. + +In summary, an implicit lock is seen as a granted x-lock only on the +record, not on the gap. An explicit lock with no gap bit set is a lock +both on the record and the gap. If the gap bit is set, the lock is only +on the gap. Different transaction cannot own conflicting locks on the +record at the same time, but they may own conflicting locks on the gap. +Granted locks on a record give an access right to the record, but gap type +locks just inhibit operations. + +NOTE: Finding out if some transaction has an implicit x-lock on a secondary +index record can be cumbersome. We may have to look at previous versions of +the corresponding clustered index record to find out if a delete marked +secondary index record was delete marked by an active transaction, not by +a committed one. + +FACT A: If a transaction has inserted a row, it can delete it any time +without need to wait for locks. + +PROOF: The transaction has an implicit x-lock on every index record inserted +for the row, and can thus modify each record without the need to wait. Q.E.D. + +FACT B: If a transaction has read some result set with a cursor, it can read +it again, and retrieves the same result set, if it has not modified the +result set in the meantime. Hence, there is no phantom problem. If the +biggest record, in the alphabetical order, touched by the cursor is removed, +a lock wait may occur, otherwise not. + +PROOF: When a read cursor proceeds, it sets an s-lock on each user record +it passes, and a gap type s-lock on each page supremum. The cursor must +wait until it has these locks granted. Then no other transaction can +have a granted x-lock on any of the user records, and therefore cannot +modify the user records. Neither can any other transaction insert into +the gaps which were passed over by the cursor. Page splits and merges, +and removal of obsolete versions of records do not affect this, because +when a user record or a page supremum is removed, the next record inherits +its locks as gap type locks, and therefore blocks inserts to the same gap. +Also, if a page supremum is inserted, it inherits its locks from the successor +record. When the cursor is positioned again at the start of the result set, +the records it will touch on its course are either records it touched +during the last pass or new inserted page supremums. It can immediately +access all these records, and when it arrives at the biggest record, it +notices that the result set is complete. If the biggest record was removed, +lock wait can occur because the next record only inherits a gap type lock, +and a wait may be needed. Q.E.D. */ + +/* If an index record should be changed or a new inserted, we must check +the lock on the record or the next. When a read cursor starts reading, +we will set a record level s-lock on each record it passes, except on the +initial record on which the cursor is positioned before we start to fetch +records. Our index tree search has the convention that the B-tree +cursor is positioned BEFORE the first possibly matching record in +the search. Optimizations are possible here: if the record is searched +on an equality condition to a unique key, we could actually set a special +lock on the record, a lock which would not prevent any insert before +this record. In the next key locking an x-lock set on a record also +prevents inserts just before that record. + There are special infimum and supremum records on each page. +A supremum record can be locked by a read cursor. This records cannot be +updated but the lock prevents insert of a user record to the end of +the page. + Next key locks will prevent the phantom problem where new rows +could appear to SELECT result sets after the select operation has been +performed. Prevention of phantoms ensures the serilizability of +transactions. + What should we check if an insert of a new record is wanted? +Only the lock on the next record on the same page, because also the +supremum record can carry a lock. An s-lock prevents insertion, but +what about an x-lock? If it was set by a searched update, then there +is implicitly an s-lock, too, and the insert should be prevented. +What if our transaction owns an x-lock to the next record, but there is +a waiting s-lock request on the next record? If this s-lock was placed +by a read cursor moving in the ascending order in the index, we cannot +do the insert immediately, because when we finally commit our transaction, +the read cursor should see also the new inserted record. So we should +move the read cursor backward from the the next record for it to pass over +the new inserted record. This move backward may be too cumbersome to +implement. If we in this situation just enqueue a second x-lock request +for our transaction on the next record, then the deadlock mechanism +notices a deadlock between our transaction and the s-lock request +transaction. This seems to be an ok solution. + We could have the convention that granted explicit record locks, +lock the corresponding records from changing, and also lock the gaps +before them from inserting. A waiting explicit lock request locks the gap +before from inserting. Implicit record x-locks, which we derive from the +transaction id in the clustered index record, only lock the record itself +from modification, not the gap before it from inserting. + How should we store update locks? If the search is done by a unique +key, we could just modify the record trx id. Otherwise, we could put a record +x-lock on the record. If the update changes ordering fields of the +clustered index record, the inserted new record needs no record lock in +lock table, the trx id is enough. The same holds for a secondary index +record. Searched delete is similar to update. + +PROBLEM: +What about waiting lock requests? If a transaction is waiting to make an +update to a record which another modified, how does the other transaction +know to send the end-lock-wait signal to the waiting transaction? If we have +the convention that a transaction may wait for just one lock at a time, how +do we preserve it if lock wait ends? + +PROBLEM: +Checking the trx id label of a secondary index record. In the case of a +modification, not an insert, is this necessary? A secondary index record +is modified only by setting or resetting its deleted flag. A secondary index +record contains fields to uniquely determine the corresponding clustered +index record. A secondary index record is therefore only modified if we +also modify the clustered index record, and the trx id checking is done +on the clustered index record, before we come to modify the secondary index +record. So, in the case of delete marking or unmarking a secondary index +record, we do not have to care about trx ids, only the locks in the lock +table must be checked. In the case of a select from a secondary index, the +trx id is relevant, and in this case we may have to search the clustered +index record. + +PROBLEM: How to update record locks when page is split or merged, or +-------------------------------------------------------------------- +a record is deleted or updated? +If the size of fields in a record changes, we perform the update by +a delete followed by an insert. How can we retain the locks set or +waiting on the record? Because a record lock is indexed in the bitmap +by the heap number of the record, when we remove the record from the +record list, it is possible still to keep the lock bits. If the page +is reorganized, we could make a table of old and new heap numbers, +and permute the bitmaps in the locks accordingly. We can add to the +table a row telling where the updated record ended. If the update does +not require a reorganization of the page, we can simply move the lock +bits for the updated record to the position determined by its new heap +number (we may have to allocate a new lock, if we run out of the bitmap +in the old one). + A more complicated case is the one where the reinsertion of the +updated record is done pessimistically, because the structure of the +tree may change. + +PROBLEM: If a supremum record is removed in a page merge, or a record +--------------------------------------------------------------------- +removed in a purge, what to do to the waiting lock requests? In a split to +the right, we just move the lock requests to the new supremum. If a record +is removed, we could move the waiting lock request to its inheritor, the +next record in the index. But, the next record may already have lock +requests on its own queue. A new deadlock check should be made then. Maybe +it is easier just to release the waiting transactions. They can then enqueue +new lock requests on appropriate records. + +PROBLEM: When a record is inserted, what locks should it inherit from the +------------------------------------------------------------------------- +upper neighbor? An insert of a new supremum record in a page split is +always possible, but an insert of a new user record requires that the upper +neighbor does not have any lock requests by other transactions, granted or +waiting, in its lock queue. Solution: We can copy the locks as gap type +locks, so that also the waiting locks are transformed to granted gap type +locks on the inserted record. */ + +/* LOCK COMPATIBILITY MATRIX + * IS IX S X AI + * IS + + + - + + * IX + + - - + + * S + - + - - + * X - - - - - + * AI + + - - - + * + * Note that for rows, InnoDB only acquires S or X locks. + * For tables, InnoDB normally acquires IS or IX locks. + * S or X table locks are only acquired for LOCK TABLES. + * Auto-increment (AI) locks are needed because of + * statement-level MySQL binlog. + * See also lock_mode_compatible(). + */ +#define LK(a,b) (1 << ((a) * LOCK_NUM + (b))) +#define LKS(a,b) LK(a,b) | LK(b,a) + +/* Define the lock compatibility matrix in a ulint. The first line below +defines the diagonal entries. The following lines define the compatibility +for LOCK_IX, LOCK_S, and LOCK_AUTO_INC using LKS(), since the matrix +is symmetric. */ +#define LOCK_MODE_COMPATIBILITY 0 \ + | LK(LOCK_IS, LOCK_IS) | LK(LOCK_IX, LOCK_IX) | LK(LOCK_S, LOCK_S) \ + | LKS(LOCK_IX, LOCK_IS) | LKS(LOCK_IS, LOCK_AUTO_INC) \ + | LKS(LOCK_S, LOCK_IS) \ + | LKS(LOCK_AUTO_INC, LOCK_IS) | LKS(LOCK_AUTO_INC, LOCK_IX) + +/* STRONGER-OR-EQUAL RELATION (mode1=row, mode2=column) + * IS IX S X AI + * IS + - - - - + * IX + + - - - + * S + - + - - + * X + + + + + + * AI - - - - + + * See lock_mode_stronger_or_eq(). + */ + +/* Define the stronger-or-equal lock relation in a ulint. This relation +contains all pairs LK(mode1, mode2) where mode1 is stronger than or +equal to mode2. */ +#define LOCK_MODE_STRONGER_OR_EQ 0 \ + | LK(LOCK_IS, LOCK_IS) \ + | LK(LOCK_IX, LOCK_IS) | LK(LOCK_IX, LOCK_IX) \ + | LK(LOCK_S, LOCK_IS) | LK(LOCK_S, LOCK_S) \ + | LK(LOCK_AUTO_INC, LOCK_AUTO_INC) \ + | LK(LOCK_X, LOCK_IS) | LK(LOCK_X, LOCK_IX) | LK(LOCK_X, LOCK_S) \ + | LK(LOCK_X, LOCK_AUTO_INC) | LK(LOCK_X, LOCK_X) + +#ifdef UNIV_DEBUG +UNIV_INTERN ibool lock_print_waits = FALSE; + +/************************************************************************* +Validates the lock system. */ +static +ibool +lock_validate(void); +/*===============*/ + /* out: TRUE if ok */ + +/************************************************************************* +Validates the record lock queues on a page. */ +static +ibool +lock_rec_validate_page( +/*===================*/ + /* out: TRUE if ok */ + ulint space, /* in: space id */ + ulint page_no);/* in: page number */ + +/* Define the following in order to enable lock_rec_validate_page() checks. */ +# undef UNIV_DEBUG_LOCK_VALIDATE +#endif /* UNIV_DEBUG */ + +/* The lock system */ +UNIV_INTERN lock_sys_t* lock_sys = NULL; + +/* We store info on the latest deadlock error to this buffer. InnoDB +Monitor will then fetch it and print */ +UNIV_INTERN ibool lock_deadlock_found = FALSE; +UNIV_INTERN FILE* lock_latest_err_file; + +/* Flags for recursive deadlock search */ +#define LOCK_VICTIM_IS_START 1 +#define LOCK_VICTIM_IS_OTHER 2 + +/************************************************************************ +Checks if a lock request results in a deadlock. */ +static +ibool +lock_deadlock_occurs( +/*=================*/ + /* out: TRUE if a deadlock was detected and we + chose trx as a victim; FALSE if no deadlock, or + there was a deadlock, but we chose other + transaction(s) as victim(s) */ + lock_t* lock, /* in: lock the transaction is requesting */ + trx_t* trx); /* in: transaction */ +/************************************************************************ +Looks recursively for a deadlock. */ +static +ulint +lock_deadlock_recursive( +/*====================*/ + /* out: 0 if no deadlock found, + LOCK_VICTIM_IS_START if there was a deadlock + and we chose 'start' as the victim, + LOCK_VICTIM_IS_OTHER if a deadlock + was found and we chose some other trx as a + victim: we must do the search again in this + last case because there may be another + deadlock! */ + trx_t* start, /* in: recursion starting point */ + trx_t* trx, /* in: a transaction waiting for a lock */ + lock_t* wait_lock, /* in: the lock trx is waiting to be granted */ + ulint* cost, /* in/out: number of calculation steps thus + far: if this exceeds LOCK_MAX_N_STEPS_... + we return LOCK_VICTIM_IS_START */ + ulint depth); /* in: recursion depth: if this exceeds + LOCK_MAX_DEPTH_IN_DEADLOCK_CHECK, we + return LOCK_VICTIM_IS_START */ + +/************************************************************************* +Gets the nth bit of a record lock. */ +UNIV_INLINE +ibool +lock_rec_get_nth_bit( +/*=================*/ + /* out: TRUE if bit set */ + const lock_t* lock, /* in: record lock */ + ulint i) /* in: index of the bit */ +{ + ulint byte_index; + ulint bit_index; + + ut_ad(lock); + ut_ad(lock_get_type_low(lock) == LOCK_REC); + + if (i >= lock->un_member.rec_lock.n_bits) { + + return(FALSE); + } + + byte_index = i / 8; + bit_index = i % 8; + + return(1 & ((const byte*) &lock[1])[byte_index] >> bit_index); +} + +/*************************************************************************/ + +#define lock_mutex_enter_kernel() mutex_enter(&kernel_mutex) +#define lock_mutex_exit_kernel() mutex_exit(&kernel_mutex) + +/************************************************************************* +Checks that a transaction id is sensible, i.e., not in the future. */ +UNIV_INTERN +ibool +lock_check_trx_id_sanity( +/*=====================*/ + /* out: TRUE if ok */ + dulint trx_id, /* in: trx id */ + const rec_t* rec, /* in: user record */ + dict_index_t* index, /* in: index */ + const ulint* offsets, /* in: rec_get_offsets(rec, index) */ + ibool has_kernel_mutex)/* in: TRUE if the caller owns the + kernel mutex */ +{ + ibool is_ok = TRUE; + + ut_ad(rec_offs_validate(rec, index, offsets)); + + if (!has_kernel_mutex) { + mutex_enter(&kernel_mutex); + } + + /* A sanity check: the trx_id in rec must be smaller than the global + trx id counter */ + + if (ut_dulint_cmp(trx_id, trx_sys->max_trx_id) >= 0) { + ut_print_timestamp(stderr); + fputs(" InnoDB: Error: transaction id associated" + " with record\n", + stderr); + rec_print_new(stderr, rec, offsets); + fputs("InnoDB: in ", stderr); + dict_index_name_print(stderr, NULL, index); + fprintf(stderr, "\n" + "InnoDB: is " TRX_ID_FMT " which is higher than the" + " global trx id counter " TRX_ID_FMT "!\n" + "InnoDB: The table is corrupt. You have to do" + " dump + drop + reimport.\n", + TRX_ID_PREP_PRINTF(trx_id), + TRX_ID_PREP_PRINTF(trx_sys->max_trx_id)); + + is_ok = FALSE; + } + + if (!has_kernel_mutex) { + mutex_exit(&kernel_mutex); + } + + return(is_ok); +} + +/************************************************************************* +Checks that a record is seen in a consistent read. */ +UNIV_INTERN +ibool +lock_clust_rec_cons_read_sees( +/*==========================*/ + /* out: TRUE if sees, or FALSE if an earlier + version of the record should be retrieved */ + const rec_t* rec, /* in: user record which should be read or + passed over by a read cursor */ + dict_index_t* index, /* in: clustered index */ + const ulint* offsets,/* in: rec_get_offsets(rec, index) */ + read_view_t* view) /* in: consistent read view */ +{ + dulint trx_id; + + ut_ad(dict_index_is_clust(index)); + ut_ad(page_rec_is_user_rec(rec)); + ut_ad(rec_offs_validate(rec, index, offsets)); + + /* NOTE that we call this function while holding the search + system latch. To obey the latching order we must NOT reserve the + kernel mutex here! */ + + trx_id = row_get_rec_trx_id(rec, index, offsets); + + return(read_view_sees_trx_id(view, trx_id)); +} + +/************************************************************************* +Checks that a non-clustered index record is seen in a consistent read. */ +UNIV_INTERN +ulint +lock_sec_rec_cons_read_sees( +/*========================*/ + /* out: TRUE if certainly + sees, or FALSE if an earlier + version of the clustered index + record might be needed: NOTE + that a non-clustered index + page contains so little + information on its + modifications that also in the + case FALSE, the present + version of rec may be the + right, but we must check this + from the clustered index + record */ + const rec_t* rec, /* in: user record which + should be read or passed over + by a read cursor */ + const read_view_t* view) /* in: consistent read view */ +{ + dulint max_trx_id; + + ut_ad(page_rec_is_user_rec(rec)); + + /* NOTE that we might call this function while holding the search + system latch. To obey the latching order we must NOT reserve the + kernel mutex here! */ + + if (recv_recovery_is_on()) { + + return(FALSE); + } + + max_trx_id = page_get_max_trx_id(page_align(rec)); + + return(ut_dulint_cmp(max_trx_id, view->up_limit_id) < 0); +} + +/************************************************************************* +Creates the lock system at database start. */ +UNIV_INTERN +void +lock_sys_create( +/*============*/ + ulint n_cells) /* in: number of slots in lock hash table */ +{ + lock_sys = mem_alloc(sizeof(lock_sys_t)); + + lock_sys->rec_hash = hash_create(n_cells); + + /* hash_create_mutexes(lock_sys->rec_hash, 2, SYNC_REC_LOCK); */ + + lock_latest_err_file = os_file_create_tmpfile(); + ut_a(lock_latest_err_file); +} + +/************************************************************************* +Gets the size of a lock struct. */ +UNIV_INTERN +ulint +lock_get_size(void) +/*===============*/ + /* out: size in bytes */ +{ + return((ulint)sizeof(lock_t)); +} + +/************************************************************************* +Gets the mode of a lock. */ +UNIV_INLINE +enum lock_mode +lock_get_mode( +/*==========*/ + /* out: mode */ + const lock_t* lock) /* in: lock */ +{ + ut_ad(lock); + + return(lock->type_mode & LOCK_MODE_MASK); +} + +/************************************************************************* +Gets the wait flag of a lock. */ +UNIV_INLINE +ibool +lock_get_wait( +/*==========*/ + /* out: TRUE if waiting */ + const lock_t* lock) /* in: lock */ +{ + ut_ad(lock); + + if (UNIV_UNLIKELY(lock->type_mode & LOCK_WAIT)) { + + return(TRUE); + } + + return(FALSE); +} + +/************************************************************************* +Gets the source table of an ALTER TABLE transaction. The table must be +covered by an IX or IS table lock. */ +UNIV_INTERN +dict_table_t* +lock_get_src_table( +/*===============*/ + /* out: the source table of transaction, + if it is covered by an IX or IS table lock; + dest if there is no source table, and + NULL if the transaction is locking more than + two tables or an inconsistency is found */ + trx_t* trx, /* in: transaction */ + dict_table_t* dest, /* in: destination of ALTER TABLE */ + enum lock_mode* mode) /* out: lock mode of the source table */ +{ + dict_table_t* src; + lock_t* lock; + + src = NULL; + *mode = LOCK_NONE; + + for (lock = UT_LIST_GET_FIRST(trx->trx_locks); + lock; + lock = UT_LIST_GET_NEXT(trx_locks, lock)) { + lock_table_t* tab_lock; + enum lock_mode lock_mode; + if (!(lock_get_type_low(lock) & LOCK_TABLE)) { + /* We are only interested in table locks. */ + continue; + } + tab_lock = &lock->un_member.tab_lock; + if (dest == tab_lock->table) { + /* We are not interested in the destination table. */ + continue; + } else if (!src) { + /* This presumably is the source table. */ + src = tab_lock->table; + if (UT_LIST_GET_LEN(src->locks) != 1 + || UT_LIST_GET_FIRST(src->locks) != lock) { + /* We only support the case when + there is only one lock on this table. */ + return(NULL); + } + } else if (src != tab_lock->table) { + /* The transaction is locking more than + two tables (src and dest): abort */ + return(NULL); + } + + /* Check that the source table is locked by + LOCK_IX or LOCK_IS. */ + lock_mode = lock_get_mode(lock); + if (lock_mode == LOCK_IX || lock_mode == LOCK_IS) { + if (*mode != LOCK_NONE && *mode != lock_mode) { + /* There are multiple locks on src. */ + return(NULL); + } + *mode = lock_mode; + } + } + + if (!src) { + /* No source table lock found: flag the situation to caller */ + src = dest; + } + + return(src); +} + +/************************************************************************* +Determine if the given table is exclusively "owned" by the given +transaction, i.e., transaction holds LOCK_IX and possibly LOCK_AUTO_INC +on the table. */ +UNIV_INTERN +ibool +lock_is_table_exclusive( +/*====================*/ + /* out: TRUE if table is only locked by trx, + with LOCK_IX, and possibly LOCK_AUTO_INC */ + dict_table_t* table, /* in: table */ + trx_t* trx) /* in: transaction */ +{ + const lock_t* lock; + ibool ok = FALSE; + + ut_ad(table); + ut_ad(trx); + + lock_mutex_enter_kernel(); + + for (lock = UT_LIST_GET_FIRST(table->locks); + lock; + lock = UT_LIST_GET_NEXT(locks, &lock->un_member.tab_lock)) { + if (lock->trx != trx) { + /* A lock on the table is held + by some other transaction. */ + goto not_ok; + } + + if (!(lock_get_type_low(lock) & LOCK_TABLE)) { + /* We are interested in table locks only. */ + continue; + } + + switch (lock_get_mode(lock)) { + case LOCK_IX: + ok = TRUE; + break; + case LOCK_AUTO_INC: + /* It is allowed for trx to hold an + auto_increment lock. */ + break; + default: +not_ok: + /* Other table locks than LOCK_IX are not allowed. */ + ok = FALSE; + goto func_exit; + } + } + +func_exit: + lock_mutex_exit_kernel(); + + return(ok); +} + +/************************************************************************* +Sets the wait flag of a lock and the back pointer in trx to lock. */ +UNIV_INLINE +void +lock_set_lock_and_trx_wait( +/*=======================*/ + lock_t* lock, /* in: lock */ + trx_t* trx) /* in: trx */ +{ + ut_ad(lock); + ut_ad(trx->wait_lock == NULL); + + trx->wait_lock = lock; + lock->type_mode |= LOCK_WAIT; +} + +/************************************************************************** +The back pointer to a waiting lock request in the transaction is set to NULL +and the wait bit in lock type_mode is reset. */ +UNIV_INLINE +void +lock_reset_lock_and_trx_wait( +/*=========================*/ + lock_t* lock) /* in: record lock */ +{ + ut_ad((lock->trx)->wait_lock == lock); + ut_ad(lock_get_wait(lock)); + + /* Reset the back pointer in trx to this waiting lock request */ + + (lock->trx)->wait_lock = NULL; + lock->type_mode &= ~LOCK_WAIT; +} + +/************************************************************************* +Gets the gap flag of a record lock. */ +UNIV_INLINE +ibool +lock_rec_get_gap( +/*=============*/ + /* out: TRUE if gap flag set */ + const lock_t* lock) /* in: record lock */ +{ + ut_ad(lock); + ut_ad(lock_get_type_low(lock) == LOCK_REC); + + if (lock->type_mode & LOCK_GAP) { + + return(TRUE); + } + + return(FALSE); +} + +/************************************************************************* +Gets the LOCK_REC_NOT_GAP flag of a record lock. */ +UNIV_INLINE +ibool +lock_rec_get_rec_not_gap( +/*=====================*/ + /* out: TRUE if LOCK_REC_NOT_GAP flag set */ + const lock_t* lock) /* in: record lock */ +{ + ut_ad(lock); + ut_ad(lock_get_type_low(lock) == LOCK_REC); + + if (lock->type_mode & LOCK_REC_NOT_GAP) { + + return(TRUE); + } + + return(FALSE); +} + +/************************************************************************* +Gets the waiting insert flag of a record lock. */ +UNIV_INLINE +ibool +lock_rec_get_insert_intention( +/*==========================*/ + /* out: TRUE if gap flag set */ + const lock_t* lock) /* in: record lock */ +{ + ut_ad(lock); + ut_ad(lock_get_type_low(lock) == LOCK_REC); + + if (lock->type_mode & LOCK_INSERT_INTENTION) { + + return(TRUE); + } + + return(FALSE); +} + +/************************************************************************* +Calculates if lock mode 1 is stronger or equal to lock mode 2. */ +UNIV_INLINE +ulint +lock_mode_stronger_or_eq( +/*=====================*/ + /* out: nonzero + if mode1 stronger or equal to mode2 */ + enum lock_mode mode1, /* in: lock mode */ + enum lock_mode mode2) /* in: lock mode */ +{ + ut_ad(mode1 == LOCK_X || mode1 == LOCK_S || mode1 == LOCK_IX + || mode1 == LOCK_IS || mode1 == LOCK_AUTO_INC); + ut_ad(mode2 == LOCK_X || mode2 == LOCK_S || mode2 == LOCK_IX + || mode2 == LOCK_IS || mode2 == LOCK_AUTO_INC); + + return((LOCK_MODE_STRONGER_OR_EQ) & LK(mode1, mode2)); +} + +/************************************************************************* +Calculates if lock mode 1 is compatible with lock mode 2. */ +UNIV_INLINE +ulint +lock_mode_compatible( +/*=================*/ + /* out: nonzero if mode1 compatible with mode2 */ + enum lock_mode mode1, /* in: lock mode */ + enum lock_mode mode2) /* in: lock mode */ +{ + ut_ad(mode1 == LOCK_X || mode1 == LOCK_S || mode1 == LOCK_IX + || mode1 == LOCK_IS || mode1 == LOCK_AUTO_INC); + ut_ad(mode2 == LOCK_X || mode2 == LOCK_S || mode2 == LOCK_IX + || mode2 == LOCK_IS || mode2 == LOCK_AUTO_INC); + + return((LOCK_MODE_COMPATIBILITY) & LK(mode1, mode2)); +} + +/************************************************************************* +Checks if a lock request for a new lock has to wait for request lock2. */ +UNIV_INLINE +ibool +lock_rec_has_to_wait( +/*=================*/ + /* out: TRUE if new lock has to wait + for lock2 to be removed */ + const trx_t* trx, /* in: trx of new lock */ + ulint type_mode,/* in: precise mode of the new lock + to set: LOCK_S or LOCK_X, possibly + ORed to LOCK_GAP or LOCK_REC_NOT_GAP, + LOCK_INSERT_INTENTION */ + const lock_t* lock2, /* in: another record lock; NOTE that + it is assumed that this has a lock bit + set on the same record as in the new + lock we are setting */ + ibool lock_is_on_supremum) /* in: TRUE if we are setting the + lock on the 'supremum' record of an + index page: we know then that the lock + request is really for a 'gap' type lock */ +{ + ut_ad(trx && lock2); + ut_ad(lock_get_type_low(lock2) == LOCK_REC); + + if (trx != lock2->trx + && !lock_mode_compatible(LOCK_MODE_MASK & type_mode, + lock_get_mode(lock2))) { + + /* We have somewhat complex rules when gap type record locks + cause waits */ + + if ((lock_is_on_supremum || (type_mode & LOCK_GAP)) + && !(type_mode & LOCK_INSERT_INTENTION)) { + + /* Gap type locks without LOCK_INSERT_INTENTION flag + do not need to wait for anything. This is because + different users can have conflicting lock types + on gaps. */ + + return(FALSE); + } + + if (!(type_mode & LOCK_INSERT_INTENTION) + && lock_rec_get_gap(lock2)) { + + /* Record lock (LOCK_ORDINARY or LOCK_REC_NOT_GAP + does not need to wait for a gap type lock */ + + return(FALSE); + } + + if ((type_mode & LOCK_GAP) + && lock_rec_get_rec_not_gap(lock2)) { + + /* Lock on gap does not need to wait for + a LOCK_REC_NOT_GAP type lock */ + + return(FALSE); + } + + if (lock_rec_get_insert_intention(lock2)) { + + /* No lock request needs to wait for an insert + intention lock to be removed. This is ok since our + rules allow conflicting locks on gaps. This eliminates + a spurious deadlock caused by a next-key lock waiting + for an insert intention lock; when the insert + intention lock was granted, the insert deadlocked on + the waiting next-key lock. + + Also, insert intention locks do not disturb each + other. */ + + return(FALSE); + } + + return(TRUE); + } + + return(FALSE); +} + +/************************************************************************* +Checks if a lock request lock1 has to wait for request lock2. */ +UNIV_INTERN +ibool +lock_has_to_wait( +/*=============*/ + /* out: TRUE if lock1 has to wait for + lock2 to be removed */ + const lock_t* lock1, /* in: waiting lock */ + const lock_t* lock2) /* in: another lock; NOTE that it is + assumed that this has a lock bit set + on the same record as in lock1 if the + locks are record locks */ +{ + ut_ad(lock1 && lock2); + + if (lock1->trx != lock2->trx + && !lock_mode_compatible(lock_get_mode(lock1), + lock_get_mode(lock2))) { + if (lock_get_type_low(lock1) == LOCK_REC) { + ut_ad(lock_get_type_low(lock2) == LOCK_REC); + + /* If this lock request is for a supremum record + then the second bit on the lock bitmap is set */ + + return(lock_rec_has_to_wait(lock1->trx, + lock1->type_mode, lock2, + lock_rec_get_nth_bit( + lock1, 1))); + } + + return(TRUE); + } + + return(FALSE); +} + +/*============== RECORD LOCK BASIC FUNCTIONS ============================*/ + +/************************************************************************* +Gets the number of bits in a record lock bitmap. */ +UNIV_INLINE +ulint +lock_rec_get_n_bits( +/*================*/ + /* out: number of bits */ + const lock_t* lock) /* in: record lock */ +{ + return(lock->un_member.rec_lock.n_bits); +} + +/************************************************************************** +Sets the nth bit of a record lock to TRUE. */ +UNIV_INLINE +void +lock_rec_set_nth_bit( +/*=================*/ + lock_t* lock, /* in: record lock */ + ulint i) /* in: index of the bit */ +{ + ulint byte_index; + ulint bit_index; + + ut_ad(lock); + ut_ad(lock_get_type_low(lock) == LOCK_REC); + ut_ad(i < lock->un_member.rec_lock.n_bits); + + byte_index = i / 8; + bit_index = i % 8; + + ((byte*) &lock[1])[byte_index] |= 1 << bit_index; +} + +/************************************************************************** +Looks for a set bit in a record lock bitmap. Returns ULINT_UNDEFINED, +if none found. */ +UNIV_INTERN +ulint +lock_rec_find_set_bit( +/*==================*/ + /* out: bit index == heap number of + the record, or ULINT_UNDEFINED if none found */ + const lock_t* lock) /* in: record lock with at least one bit set */ +{ + ulint i; + + for (i = 0; i < lock_rec_get_n_bits(lock); i++) { + + if (lock_rec_get_nth_bit(lock, i)) { + + return(i); + } + } + + return(ULINT_UNDEFINED); +} + +/************************************************************************** +Resets the nth bit of a record lock. */ +UNIV_INLINE +void +lock_rec_reset_nth_bit( +/*===================*/ + lock_t* lock, /* in: record lock */ + ulint i) /* in: index of the bit which must be set to TRUE + when this function is called */ +{ + ulint byte_index; + ulint bit_index; + + ut_ad(lock); + ut_ad(lock_get_type_low(lock) == LOCK_REC); + ut_ad(i < lock->un_member.rec_lock.n_bits); + + byte_index = i / 8; + bit_index = i % 8; + + ((byte*) &lock[1])[byte_index] &= ~(1 << bit_index); +} + +/************************************************************************* +Gets the first or next record lock on a page. */ +UNIV_INLINE +lock_t* +lock_rec_get_next_on_page( +/*======================*/ + /* out: next lock, NULL if none exists */ + lock_t* lock) /* in: a record lock */ +{ + ulint space; + ulint page_no; + + ut_ad(mutex_own(&kernel_mutex)); + ut_ad(lock_get_type_low(lock) == LOCK_REC); + + space = lock->un_member.rec_lock.space; + page_no = lock->un_member.rec_lock.page_no; + + for (;;) { + lock = HASH_GET_NEXT(hash, lock); + + if (!lock) { + + break; + } + + if ((lock->un_member.rec_lock.space == space) + && (lock->un_member.rec_lock.page_no == page_no)) { + + break; + } + } + + return(lock); +} + +/************************************************************************* +Gets the first record lock on a page, where the page is identified by its +file address. */ +UNIV_INLINE +lock_t* +lock_rec_get_first_on_page_addr( +/*============================*/ + /* out: first lock, NULL if none exists */ + ulint space, /* in: space */ + ulint page_no)/* in: page number */ +{ + lock_t* lock; + + ut_ad(mutex_own(&kernel_mutex)); + + lock = HASH_GET_FIRST(lock_sys->rec_hash, + lock_rec_hash(space, page_no)); + while (lock) { + if ((lock->un_member.rec_lock.space == space) + && (lock->un_member.rec_lock.page_no == page_no)) { + + break; + } + + lock = HASH_GET_NEXT(hash, lock); + } + + return(lock); +} + +/************************************************************************* +Returns TRUE if there are explicit record locks on a page. */ +UNIV_INTERN +ibool +lock_rec_expl_exist_on_page( +/*========================*/ + /* out: TRUE if there are explicit record locks on + the page */ + ulint space, /* in: space id */ + ulint page_no)/* in: page number */ +{ + ibool ret; + + mutex_enter(&kernel_mutex); + + if (lock_rec_get_first_on_page_addr(space, page_no)) { + ret = TRUE; + } else { + ret = FALSE; + } + + mutex_exit(&kernel_mutex); + + return(ret); +} + +/************************************************************************* +Gets the first record lock on a page, where the page is identified by a +pointer to it. */ +UNIV_INLINE +lock_t* +lock_rec_get_first_on_page( +/*=======================*/ + /* out: first lock, NULL if + none exists */ + const buf_block_t* block) /* in: buffer block */ +{ + ulint hash; + lock_t* lock; + ulint space = buf_block_get_space(block); + ulint page_no = buf_block_get_page_no(block); + + ut_ad(mutex_own(&kernel_mutex)); + + hash = buf_block_get_lock_hash_val(block); + + lock = HASH_GET_FIRST(lock_sys->rec_hash, hash); + + while (lock) { + if ((lock->un_member.rec_lock.space == space) + && (lock->un_member.rec_lock.page_no == page_no)) { + + break; + } + + lock = HASH_GET_NEXT(hash, lock); + } + + return(lock); +} + +/************************************************************************* +Gets the next explicit lock request on a record. */ +UNIV_INLINE +lock_t* +lock_rec_get_next( +/*==============*/ + /* out: next lock, NULL if none exists */ + ulint heap_no,/* in: heap number of the record */ + lock_t* lock) /* in: lock */ +{ + ut_ad(mutex_own(&kernel_mutex)); + + do { + ut_ad(lock_get_type_low(lock) == LOCK_REC); + lock = lock_rec_get_next_on_page(lock); + } while (lock && !lock_rec_get_nth_bit(lock, heap_no)); + + return(lock); +} + +/************************************************************************* +Gets the first explicit lock request on a record. */ +UNIV_INLINE +lock_t* +lock_rec_get_first( +/*===============*/ + /* out: first lock, NULL if + none exists */ + const buf_block_t* block, /* in: block containing the record */ + ulint heap_no)/* in: heap number of the record */ +{ + lock_t* lock; + + ut_ad(mutex_own(&kernel_mutex)); + + for (lock = lock_rec_get_first_on_page(block); lock; + lock = lock_rec_get_next_on_page(lock)) { + if (lock_rec_get_nth_bit(lock, heap_no)) { + break; + } + } + + return(lock); +} + +/************************************************************************* +Resets the record lock bitmap to zero. NOTE: does not touch the wait_lock +pointer in the transaction! This function is used in lock object creation +and resetting. */ +static +void +lock_rec_bitmap_reset( +/*==================*/ + lock_t* lock) /* in: record lock */ +{ + ulint n_bytes; + + ut_ad(lock_get_type_low(lock) == LOCK_REC); + + /* Reset to zero the bitmap which resides immediately after the lock + struct */ + + n_bytes = lock_rec_get_n_bits(lock) / 8; + + ut_ad((lock_rec_get_n_bits(lock) % 8) == 0); + + memset(&lock[1], 0, n_bytes); +} + +/************************************************************************* +Copies a record lock to heap. */ +static +lock_t* +lock_rec_copy( +/*==========*/ + /* out: copy of lock */ + const lock_t* lock, /* in: record lock */ + mem_heap_t* heap) /* in: memory heap */ +{ + ulint size; + + ut_ad(lock_get_type_low(lock) == LOCK_REC); + + size = sizeof(lock_t) + lock_rec_get_n_bits(lock) / 8; + + return(mem_heap_dup(heap, lock, size)); +} + +/************************************************************************* +Gets the previous record lock set on a record. */ +UNIV_INTERN +const lock_t* +lock_rec_get_prev( +/*==============*/ + /* out: previous lock on the same + record, NULL if none exists */ + const lock_t* in_lock,/* in: record lock */ + ulint heap_no)/* in: heap number of the record */ +{ + lock_t* lock; + ulint space; + ulint page_no; + lock_t* found_lock = NULL; + + ut_ad(mutex_own(&kernel_mutex)); + ut_ad(lock_get_type_low(in_lock) == LOCK_REC); + + space = in_lock->un_member.rec_lock.space; + page_no = in_lock->un_member.rec_lock.page_no; + + lock = lock_rec_get_first_on_page_addr(space, page_no); + + for (;;) { + ut_ad(lock); + + if (lock == in_lock) { + + return(found_lock); + } + + if (lock_rec_get_nth_bit(lock, heap_no)) { + + found_lock = lock; + } + + lock = lock_rec_get_next_on_page(lock); + } +} + +/*============= FUNCTIONS FOR ANALYZING TABLE LOCK QUEUE ================*/ + +/************************************************************************* +Checks if a transaction has the specified table lock, or stronger. */ +UNIV_INLINE +lock_t* +lock_table_has( +/*===========*/ + /* out: lock or NULL */ + trx_t* trx, /* in: transaction */ + dict_table_t* table, /* in: table */ + enum lock_mode mode) /* in: lock mode */ +{ + lock_t* lock; + + ut_ad(mutex_own(&kernel_mutex)); + + /* Look for stronger locks the same trx already has on the table */ + + lock = UT_LIST_GET_LAST(table->locks); + + while (lock != NULL) { + + if (lock->trx == trx + && lock_mode_stronger_or_eq(lock_get_mode(lock), mode)) { + + /* The same trx already has locked the table in + a mode stronger or equal to the mode given */ + + ut_ad(!lock_get_wait(lock)); + + return(lock); + } + + lock = UT_LIST_GET_PREV(un_member.tab_lock.locks, lock); + } + + return(NULL); +} + +/*============= FUNCTIONS FOR ANALYZING RECORD LOCK QUEUE ================*/ + +/************************************************************************* +Checks if a transaction has a GRANTED explicit lock on rec stronger or equal +to precise_mode. */ +UNIV_INLINE +lock_t* +lock_rec_has_expl( +/*==============*/ + /* out: lock or NULL */ + ulint precise_mode,/* in: LOCK_S or LOCK_X + possibly ORed to LOCK_GAP or + LOCK_REC_NOT_GAP, for a + supremum record we regard this + always a gap type request */ + const buf_block_t* block, /* in: buffer block containing + the record */ + ulint heap_no,/* in: heap number of the record */ + trx_t* trx) /* in: transaction */ +{ + lock_t* lock; + + ut_ad(mutex_own(&kernel_mutex)); + ut_ad((precise_mode & LOCK_MODE_MASK) == LOCK_S + || (precise_mode & LOCK_MODE_MASK) == LOCK_X); + ut_ad(!(precise_mode & LOCK_INSERT_INTENTION)); + + lock = lock_rec_get_first(block, heap_no); + + while (lock) { + if (lock->trx == trx + && lock_mode_stronger_or_eq(lock_get_mode(lock), + precise_mode & LOCK_MODE_MASK) + && !lock_get_wait(lock) + && (!lock_rec_get_rec_not_gap(lock) + || (precise_mode & LOCK_REC_NOT_GAP) + || heap_no == PAGE_HEAP_NO_SUPREMUM) + && (!lock_rec_get_gap(lock) + || (precise_mode & LOCK_GAP) + || heap_no == PAGE_HEAP_NO_SUPREMUM) + && (!lock_rec_get_insert_intention(lock))) { + + return(lock); + } + + lock = lock_rec_get_next(heap_no, lock); + } + + return(NULL); +} + +#ifdef UNIV_DEBUG +# ifndef UNIV_HOTBACKUP +/************************************************************************* +Checks if some other transaction has a lock request in the queue. */ +static +lock_t* +lock_rec_other_has_expl_req( +/*========================*/ + /* out: lock or NULL */ + enum lock_mode mode, /* in: LOCK_S or LOCK_X */ + ulint gap, /* in: LOCK_GAP if also gap + locks are taken into account, + or 0 if not */ + ulint wait, /* in: LOCK_WAIT if also + waiting locks are taken into + account, or 0 if not */ + const buf_block_t* block, /* in: buffer block containing + the record */ + ulint heap_no,/* in: heap number of the record */ + const trx_t* trx) /* in: transaction, or NULL if + requests by all transactions + are taken into account */ +{ + lock_t* lock; + + ut_ad(mutex_own(&kernel_mutex)); + ut_ad(mode == LOCK_X || mode == LOCK_S); + ut_ad(gap == 0 || gap == LOCK_GAP); + ut_ad(wait == 0 || wait == LOCK_WAIT); + + lock = lock_rec_get_first(block, heap_no); + + while (lock) { + if (lock->trx != trx + && (gap + || !(lock_rec_get_gap(lock) + || heap_no == PAGE_HEAP_NO_SUPREMUM)) + && (wait || !lock_get_wait(lock)) + && lock_mode_stronger_or_eq(lock_get_mode(lock), mode)) { + + return(lock); + } + + lock = lock_rec_get_next(heap_no, lock); + } + + return(NULL); +} +# endif /* !UNIV_HOTBACKUP */ +#endif /* UNIV_DEBUG */ + +/************************************************************************* +Checks if some other transaction has a conflicting explicit lock request +in the queue, so that we have to wait. */ +static +lock_t* +lock_rec_other_has_conflicting( +/*===========================*/ + /* out: lock or NULL */ + enum lock_mode mode, /* in: LOCK_S or LOCK_X, + possibly ORed to LOCK_GAP or + LOC_REC_NOT_GAP, + LOCK_INSERT_INTENTION */ + const buf_block_t* block, /* in: buffer block containing + the record */ + ulint heap_no,/* in: heap number of the record */ + trx_t* trx) /* in: our transaction */ +{ + lock_t* lock; + + ut_ad(mutex_own(&kernel_mutex)); + + lock = lock_rec_get_first(block, heap_no); + + if (UNIV_LIKELY_NULL(lock)) { + if (UNIV_UNLIKELY(heap_no == PAGE_HEAP_NO_SUPREMUM)) { + + do { + if (lock_rec_has_to_wait(trx, mode, lock, + TRUE)) { + return(lock); + } + + lock = lock_rec_get_next(heap_no, lock); + } while (lock); + } else { + + do { + if (lock_rec_has_to_wait(trx, mode, lock, + FALSE)) { + return(lock); + } + + lock = lock_rec_get_next(heap_no, lock); + } while (lock); + } + } + + return(NULL); +} + +/************************************************************************* +Looks for a suitable type record lock struct by the same trx on the same page. +This can be used to save space when a new record lock should be set on a page: +no new struct is needed, if a suitable old is found. */ +UNIV_INLINE +lock_t* +lock_rec_find_similar_on_page( +/*==========================*/ + /* out: lock or NULL */ + ulint type_mode, /* in: lock type_mode field */ + ulint heap_no, /* in: heap number of the record */ + lock_t* lock, /* in: lock_rec_get_first_on_page() */ + const trx_t* trx) /* in: transaction */ +{ + ut_ad(mutex_own(&kernel_mutex)); + + while (lock != NULL) { + if (lock->trx == trx + && lock->type_mode == type_mode + && lock_rec_get_n_bits(lock) > heap_no) { + + return(lock); + } + + lock = lock_rec_get_next_on_page(lock); + } + + return(NULL); +} + +/************************************************************************* +Checks if some transaction has an implicit x-lock on a record in a secondary +index. */ +static +trx_t* +lock_sec_rec_some_has_impl_off_kernel( +/*==================================*/ + /* out: transaction which has the x-lock, or + NULL */ + const rec_t* rec, /* in: user record */ + dict_index_t* index, /* in: secondary index */ + const ulint* offsets)/* in: rec_get_offsets(rec, index) */ +{ + const page_t* page = page_align(rec); + + ut_ad(mutex_own(&kernel_mutex)); + ut_ad(!dict_index_is_clust(index)); + ut_ad(page_rec_is_user_rec(rec)); + ut_ad(rec_offs_validate(rec, index, offsets)); + + /* Some transaction may have an implicit x-lock on the record only + if the max trx id for the page >= min trx id for the trx list, or + database recovery is running. We do not write the changes of a page + max trx id to the log, and therefore during recovery, this value + for a page may be incorrect. */ + + if (!(ut_dulint_cmp(page_get_max_trx_id(page), + trx_list_get_min_trx_id()) >= 0) + && !recv_recovery_is_on()) { + + return(NULL); + } + + /* Ok, in this case it is possible that some transaction has an + implicit x-lock. We have to look in the clustered index. */ + + if (!lock_check_trx_id_sanity(page_get_max_trx_id(page), + rec, index, offsets, TRUE)) { + buf_page_print(page, 0); + + /* The page is corrupt: try to avoid a crash by returning + NULL */ + return(NULL); + } + + return(row_vers_impl_x_locked_off_kernel(rec, index, offsets)); +} + +/************************************************************************* +Return approximate number or record locks (bits set in the bitmap) for +this transaction. Since delete-marked records may be removed, the +record count will not be precise. */ +UNIV_INTERN +ulint +lock_number_of_rows_locked( +/*=======================*/ + trx_t* trx) /* in: transaction */ +{ + lock_t* lock; + ulint n_records = 0; + ulint n_bits; + ulint n_bit; + + lock = UT_LIST_GET_FIRST(trx->trx_locks); + + while (lock) { + if (lock_get_type_low(lock) == LOCK_REC) { + n_bits = lock_rec_get_n_bits(lock); + + for (n_bit = 0; n_bit < n_bits; n_bit++) { + if (lock_rec_get_nth_bit(lock, n_bit)) { + n_records++; + } + } + } + + lock = UT_LIST_GET_NEXT(trx_locks, lock); + } + + return (n_records); +} + +/*============== RECORD LOCK CREATION AND QUEUE MANAGEMENT =============*/ + +/************************************************************************* +Creates a new record lock and inserts it to the lock queue. Does NOT check +for deadlocks or lock compatibility! */ +static +lock_t* +lock_rec_create( +/*============*/ + /* out: created lock */ + ulint type_mode,/* in: lock mode and wait + flag, type is ignored and + replaced by LOCK_REC */ + const buf_block_t* block, /* in: buffer block containing + the record */ + ulint heap_no,/* in: heap number of the record */ + dict_index_t* index, /* in: index of record */ + trx_t* trx) /* in: transaction */ +{ + lock_t* lock; + ulint page_no; + ulint space; + ulint n_bits; + ulint n_bytes; + const page_t* page; + + ut_ad(mutex_own(&kernel_mutex)); + + space = buf_block_get_space(block); + page_no = buf_block_get_page_no(block); + page = block->frame; + + ut_ad(!!page_is_comp(page) == dict_table_is_comp(index->table)); + + /* If rec is the supremum record, then we reset the gap and + LOCK_REC_NOT_GAP bits, as all locks on the supremum are + automatically of the gap type */ + + if (UNIV_UNLIKELY(heap_no == PAGE_HEAP_NO_SUPREMUM)) { + ut_ad(!(type_mode & LOCK_REC_NOT_GAP)); + + type_mode = type_mode & ~(LOCK_GAP | LOCK_REC_NOT_GAP); + } + + /* Make lock bitmap bigger by a safety margin */ + n_bits = page_dir_get_n_heap(page) + LOCK_PAGE_BITMAP_MARGIN; + n_bytes = 1 + n_bits / 8; + + lock = mem_heap_alloc(trx->lock_heap, sizeof(lock_t) + n_bytes); + + UT_LIST_ADD_LAST(trx_locks, trx->trx_locks, lock); + + lock->trx = trx; + + lock->type_mode = (type_mode & ~LOCK_TYPE_MASK) | LOCK_REC; + lock->index = index; + + lock->un_member.rec_lock.space = space; + lock->un_member.rec_lock.page_no = page_no; + lock->un_member.rec_lock.n_bits = n_bytes * 8; + + /* Reset to zero the bitmap which resides immediately after the + lock struct */ + + lock_rec_bitmap_reset(lock); + + /* Set the bit corresponding to rec */ + lock_rec_set_nth_bit(lock, heap_no); + + HASH_INSERT(lock_t, hash, lock_sys->rec_hash, + lock_rec_fold(space, page_no), lock); + if (UNIV_UNLIKELY(type_mode & LOCK_WAIT)) { + + lock_set_lock_and_trx_wait(lock, trx); + } + + return(lock); +} + +/************************************************************************* +Enqueues a waiting request for a lock which cannot be granted immediately. +Checks for deadlocks. */ +static +ulint +lock_rec_enqueue_waiting( +/*=====================*/ + /* out: DB_LOCK_WAIT, + DB_DEADLOCK, or + DB_QUE_THR_SUSPENDED, or + DB_SUCCESS; DB_SUCCESS means + that there was a deadlock, but + another transaction was chosen + as a victim, and we got the + lock immediately: no need to + wait then */ + ulint type_mode,/* in: lock mode this + transaction is requesting: + LOCK_S or LOCK_X, possibly + ORed with LOCK_GAP or + LOCK_REC_NOT_GAP, ORed with + LOCK_INSERT_INTENTION if this + waiting lock request is set + when performing an insert of + an index record */ + const buf_block_t* block, /* in: buffer block containing + the record */ + ulint heap_no,/* in: heap number of the record */ + dict_index_t* index, /* in: index of record */ + que_thr_t* thr) /* in: query thread */ +{ + lock_t* lock; + trx_t* trx; + + ut_ad(mutex_own(&kernel_mutex)); + + /* Test if there already is some other reason to suspend thread: + we do not enqueue a lock request if the query thread should be + stopped anyway */ + + if (UNIV_UNLIKELY(que_thr_stop(thr))) { + + ut_error; + + return(DB_QUE_THR_SUSPENDED); + } + + trx = thr_get_trx(thr); + + switch (trx_get_dict_operation(trx)) { + case TRX_DICT_OP_NONE: + break; + case TRX_DICT_OP_TABLE: + case TRX_DICT_OP_INDEX: + ut_print_timestamp(stderr); + fputs(" InnoDB: Error: a record lock wait happens" + " in a dictionary operation!\n" + "InnoDB: ", stderr); + dict_index_name_print(stderr, trx, index); + fputs(".\n" + "InnoDB: Submit a detailed bug report" + " to http://bugs.mysql.com\n", + stderr); + } + + /* Enqueue the lock request that will wait to be granted */ + lock = lock_rec_create(type_mode | LOCK_WAIT, + block, heap_no, index, trx); + + /* Check if a deadlock occurs: if yes, remove the lock request and + return an error code */ + + if (UNIV_UNLIKELY(lock_deadlock_occurs(lock, trx))) { + + lock_reset_lock_and_trx_wait(lock); + lock_rec_reset_nth_bit(lock, heap_no); + + return(DB_DEADLOCK); + } + + /* If there was a deadlock but we chose another transaction as a + victim, it is possible that we already have the lock now granted! */ + + if (trx->wait_lock == NULL) { + + return(DB_SUCCESS); + } + + trx->que_state = TRX_QUE_LOCK_WAIT; + trx->was_chosen_as_deadlock_victim = FALSE; + trx->wait_started = time(NULL); + + ut_a(que_thr_stop(thr)); + +#ifdef UNIV_DEBUG + if (lock_print_waits) { + fprintf(stderr, "Lock wait for trx %lu in index ", + (ulong) ut_dulint_get_low(trx->id)); + ut_print_name(stderr, trx, FALSE, index->name); + } +#endif /* UNIV_DEBUG */ + + return(DB_LOCK_WAIT); +} + +/************************************************************************* +Adds a record lock request in the record queue. The request is normally +added as the last in the queue, but if there are no waiting lock requests +on the record, and the request to be added is not a waiting request, we +can reuse a suitable record lock object already existing on the same page, +just setting the appropriate bit in its bitmap. This is a low-level function +which does NOT check for deadlocks or lock compatibility! */ +static +lock_t* +lock_rec_add_to_queue( +/*==================*/ + /* out: lock where the bit was set */ + ulint type_mode,/* in: lock mode, wait, gap + etc. flags; type is ignored + and replaced by LOCK_REC */ + const buf_block_t* block, /* in: buffer block containing + the record */ + ulint heap_no,/* in: heap number of the record */ + dict_index_t* index, /* in: index of record */ + trx_t* trx) /* in: transaction */ +{ + lock_t* lock; + + ut_ad(mutex_own(&kernel_mutex)); +#ifdef UNIV_DEBUG + switch (type_mode & LOCK_MODE_MASK) { + case LOCK_X: + case LOCK_S: + break; + default: + ut_error; + } + + if (!(type_mode & (LOCK_WAIT | LOCK_GAP))) { + enum lock_mode mode = (type_mode & LOCK_MODE_MASK) == LOCK_S + ? LOCK_X + : LOCK_S; + lock_t* other_lock + = lock_rec_other_has_expl_req(mode, 0, LOCK_WAIT, + block, heap_no, trx); + ut_a(!other_lock); + } +#endif /* UNIV_DEBUG */ + + type_mode |= LOCK_REC; + + /* If rec is the supremum record, then we can reset the gap bit, as + all locks on the supremum are automatically of the gap type, and we + try to avoid unnecessary memory consumption of a new record lock + struct for a gap type lock */ + + if (UNIV_UNLIKELY(heap_no == PAGE_HEAP_NO_SUPREMUM)) { + ut_ad(!(type_mode & LOCK_REC_NOT_GAP)); + + /* There should never be LOCK_REC_NOT_GAP on a supremum + record, but let us play safe */ + + type_mode = type_mode & ~(LOCK_GAP | LOCK_REC_NOT_GAP); + } + + /* Look for a waiting lock request on the same record or on a gap */ + + lock = lock_rec_get_first_on_page(block); + + while (lock != NULL) { + if (lock_get_wait(lock) + && (lock_rec_get_nth_bit(lock, heap_no))) { + + goto somebody_waits; + } + + lock = lock_rec_get_next_on_page(lock); + } + + if (UNIV_LIKELY(!(type_mode & LOCK_WAIT))) { + + /* Look for a similar record lock on the same page: + if one is found and there are no waiting lock requests, + we can just set the bit */ + + lock = lock_rec_find_similar_on_page( + type_mode, heap_no, + lock_rec_get_first_on_page(block), trx); + + if (lock) { + + lock_rec_set_nth_bit(lock, heap_no); + + return(lock); + } + } + +somebody_waits: + return(lock_rec_create(type_mode, block, heap_no, index, trx)); +} + +/************************************************************************* +This is a fast routine for locking a record in the most common cases: +there are no explicit locks on the page, or there is just one lock, owned +by this transaction, and of the right type_mode. This is a low-level function +which does NOT look at implicit locks! Checks lock compatibility within +explicit locks. This function sets a normal next-key lock, or in the case of +a page supremum record, a gap type lock. */ +UNIV_INLINE +ibool +lock_rec_lock_fast( +/*===============*/ + /* out: TRUE if locking succeeded */ + ibool impl, /* in: if TRUE, no lock is set + if no wait is necessary: we + assume that the caller will + set an implicit lock */ + ulint mode, /* in: lock mode: LOCK_X or + LOCK_S possibly ORed to either + LOCK_GAP or LOCK_REC_NOT_GAP */ + const buf_block_t* block, /* in: buffer block containing + the record */ + ulint heap_no,/* in: heap number of record */ + dict_index_t* index, /* in: index of record */ + que_thr_t* thr) /* in: query thread */ +{ + lock_t* lock; + trx_t* trx; + + ut_ad(mutex_own(&kernel_mutex)); + ut_ad((LOCK_MODE_MASK & mode) != LOCK_S + || lock_table_has(thr_get_trx(thr), index->table, LOCK_IS)); + ut_ad((LOCK_MODE_MASK & mode) != LOCK_X + || lock_table_has(thr_get_trx(thr), index->table, LOCK_IX)); + ut_ad((LOCK_MODE_MASK & mode) == LOCK_S + || (LOCK_MODE_MASK & mode) == LOCK_X); + ut_ad(mode - (LOCK_MODE_MASK & mode) == LOCK_GAP + || mode - (LOCK_MODE_MASK & mode) == 0 + || mode - (LOCK_MODE_MASK & mode) == LOCK_REC_NOT_GAP); + + lock = lock_rec_get_first_on_page(block); + + trx = thr_get_trx(thr); + + if (lock == NULL) { + if (!impl) { + lock_rec_create(mode, block, heap_no, index, trx); + + if (srv_locks_unsafe_for_binlog + || trx->isolation_level + == TRX_ISO_READ_COMMITTED) { + trx_register_new_rec_lock(trx, index); + } + } + + return(TRUE); + } + + if (lock_rec_get_next_on_page(lock)) { + + return(FALSE); + } + + if (lock->trx != trx + || lock->type_mode != (mode | LOCK_REC) + || lock_rec_get_n_bits(lock) <= heap_no) { + + return(FALSE); + } + + if (!impl) { + /* If the nth bit of the record lock is already set then we + do not set a new lock bit, otherwise we do set */ + + if (!lock_rec_get_nth_bit(lock, heap_no)) { + lock_rec_set_nth_bit(lock, heap_no); + if (srv_locks_unsafe_for_binlog + || trx->isolation_level + == TRX_ISO_READ_COMMITTED) { + trx_register_new_rec_lock(trx, index); + } + } + } + + return(TRUE); +} + +/************************************************************************* +This is the general, and slower, routine for locking a record. This is a +low-level function which does NOT look at implicit locks! Checks lock +compatibility within explicit locks. This function sets a normal next-key +lock, or in the case of a page supremum record, a gap type lock. */ +static +ulint +lock_rec_lock_slow( +/*===============*/ + /* out: DB_SUCCESS, + DB_LOCK_WAIT, or error code */ + ibool impl, /* in: if TRUE, no lock is set + if no wait is necessary: we + assume that the caller will + set an implicit lock */ + ulint mode, /* in: lock mode: LOCK_X or + LOCK_S possibly ORed to either + LOCK_GAP or LOCK_REC_NOT_GAP */ + const buf_block_t* block, /* in: buffer block containing + the record */ + ulint heap_no,/* in: heap number of record */ + dict_index_t* index, /* in: index of record */ + que_thr_t* thr) /* in: query thread */ +{ + trx_t* trx; + ulint err; + + ut_ad(mutex_own(&kernel_mutex)); + ut_ad((LOCK_MODE_MASK & mode) != LOCK_S + || lock_table_has(thr_get_trx(thr), index->table, LOCK_IS)); + ut_ad((LOCK_MODE_MASK & mode) != LOCK_X + || lock_table_has(thr_get_trx(thr), index->table, LOCK_IX)); + ut_ad((LOCK_MODE_MASK & mode) == LOCK_S + || (LOCK_MODE_MASK & mode) == LOCK_X); + ut_ad(mode - (LOCK_MODE_MASK & mode) == LOCK_GAP + || mode - (LOCK_MODE_MASK & mode) == 0 + || mode - (LOCK_MODE_MASK & mode) == LOCK_REC_NOT_GAP); + + trx = thr_get_trx(thr); + + if (lock_rec_has_expl(mode, block, heap_no, trx)) { + /* The trx already has a strong enough lock on rec: do + nothing */ + + err = DB_SUCCESS; + } else if (lock_rec_other_has_conflicting(mode, block, heap_no, trx)) { + + /* If another transaction has a non-gap conflicting request in + the queue, as this transaction does not have a lock strong + enough already granted on the record, we have to wait. */ + + err = lock_rec_enqueue_waiting(mode, block, heap_no, + index, thr); + + if (srv_locks_unsafe_for_binlog + || trx->isolation_level == TRX_ISO_READ_COMMITTED) { + trx_register_new_rec_lock(trx, index); + } + } else { + if (!impl) { + /* Set the requested lock on the record */ + + lock_rec_add_to_queue(LOCK_REC | mode, block, + heap_no, index, trx); + if (srv_locks_unsafe_for_binlog + || trx->isolation_level + == TRX_ISO_READ_COMMITTED) { + trx_register_new_rec_lock(trx, index); + } + } + + err = DB_SUCCESS; + } + + return(err); +} + +/************************************************************************* +Tries to lock the specified record in the mode requested. If not immediately +possible, enqueues a waiting lock request. This is a low-level function +which does NOT look at implicit locks! Checks lock compatibility within +explicit locks. This function sets a normal next-key lock, or in the case +of a page supremum record, a gap type lock. */ +static +ulint +lock_rec_lock( +/*==========*/ + /* out: DB_SUCCESS, + DB_LOCK_WAIT, or error code */ + ibool impl, /* in: if TRUE, no lock is set + if no wait is necessary: we + assume that the caller will + set an implicit lock */ + ulint mode, /* in: lock mode: LOCK_X or + LOCK_S possibly ORed to either + LOCK_GAP or LOCK_REC_NOT_GAP */ + const buf_block_t* block, /* in: buffer block containing + the record */ + ulint heap_no,/* in: heap number of record */ + dict_index_t* index, /* in: index of record */ + que_thr_t* thr) /* in: query thread */ +{ + ulint err; + + ut_ad(mutex_own(&kernel_mutex)); + ut_ad((LOCK_MODE_MASK & mode) != LOCK_S + || lock_table_has(thr_get_trx(thr), index->table, LOCK_IS)); + ut_ad((LOCK_MODE_MASK & mode) != LOCK_X + || lock_table_has(thr_get_trx(thr), index->table, LOCK_IX)); + ut_ad((LOCK_MODE_MASK & mode) == LOCK_S + || (LOCK_MODE_MASK & mode) == LOCK_X); + ut_ad(mode - (LOCK_MODE_MASK & mode) == LOCK_GAP + || mode - (LOCK_MODE_MASK & mode) == LOCK_REC_NOT_GAP + || mode - (LOCK_MODE_MASK & mode) == 0); + + if (lock_rec_lock_fast(impl, mode, block, heap_no, index, thr)) { + + /* We try a simplified and faster subroutine for the most + common cases */ + + err = DB_SUCCESS; + } else { + err = lock_rec_lock_slow(impl, mode, block, + heap_no, index, thr); + } + + return(err); +} + +/************************************************************************* +Checks if a waiting record lock request still has to wait in a queue. */ +static +ibool +lock_rec_has_to_wait_in_queue( +/*==========================*/ + /* out: TRUE if still has to wait */ + lock_t* wait_lock) /* in: waiting record lock */ +{ + lock_t* lock; + ulint space; + ulint page_no; + ulint heap_no; + + ut_ad(mutex_own(&kernel_mutex)); + ut_ad(lock_get_wait(wait_lock)); + ut_ad(lock_get_type_low(wait_lock) == LOCK_REC); + + space = wait_lock->un_member.rec_lock.space; + page_no = wait_lock->un_member.rec_lock.page_no; + heap_no = lock_rec_find_set_bit(wait_lock); + + lock = lock_rec_get_first_on_page_addr(space, page_no); + + while (lock != wait_lock) { + + if (lock_rec_get_nth_bit(lock, heap_no) + && lock_has_to_wait(wait_lock, lock)) { + + return(TRUE); + } + + lock = lock_rec_get_next_on_page(lock); + } + + return(FALSE); +} + +/***************************************************************** +Grants a lock to a waiting lock request and releases the waiting +transaction. */ +static +void +lock_grant( +/*=======*/ + lock_t* lock) /* in/out: waiting lock request */ +{ + ut_ad(mutex_own(&kernel_mutex)); + + lock_reset_lock_and_trx_wait(lock); + + if (lock_get_mode(lock) == LOCK_AUTO_INC) { + trx_t* trx = lock->trx; + dict_table_t* table = lock->un_member.tab_lock.table; + + if (table->autoinc_trx == trx) { + fprintf(stderr, + "InnoDB: Error: trx already had" + " an AUTO-INC lock!\n"); + } else { + table->autoinc_trx = trx; + + ib_vector_push(trx->autoinc_locks, lock); + } + } + +#ifdef UNIV_DEBUG + if (lock_print_waits) { + fprintf(stderr, "Lock wait for trx %lu ends\n", + (ulong) ut_dulint_get_low(lock->trx->id)); + } +#endif /* UNIV_DEBUG */ + + /* If we are resolving a deadlock by choosing another transaction + as a victim, then our original transaction may not be in the + TRX_QUE_LOCK_WAIT state, and there is no need to end the lock wait + for it */ + + if (lock->trx->que_state == TRX_QUE_LOCK_WAIT) { + trx_end_lock_wait(lock->trx); + } +} + +/***************************************************************** +Cancels a waiting record lock request and releases the waiting transaction +that requested it. NOTE: does NOT check if waiting lock requests behind this +one can now be granted! */ +static +void +lock_rec_cancel( +/*============*/ + lock_t* lock) /* in: waiting record lock request */ +{ + ut_ad(mutex_own(&kernel_mutex)); + ut_ad(lock_get_type_low(lock) == LOCK_REC); + + /* Reset the bit (there can be only one set bit) in the lock bitmap */ + lock_rec_reset_nth_bit(lock, lock_rec_find_set_bit(lock)); + + /* Reset the wait flag and the back pointer to lock in trx */ + + lock_reset_lock_and_trx_wait(lock); + + /* The following function releases the trx from lock wait */ + + trx_end_lock_wait(lock->trx); +} + +/***************************************************************** +Removes a record lock request, waiting or granted, from the queue and +grants locks to other transactions in the queue if they now are entitled +to a lock. NOTE: all record locks contained in in_lock are removed. */ +static +void +lock_rec_dequeue_from_page( +/*=======================*/ + lock_t* in_lock)/* in: record lock object: all record locks which + are contained in this lock object are removed; + transactions waiting behind will get their lock + requests granted, if they are now qualified to it */ +{ + ulint space; + ulint page_no; + lock_t* lock; + trx_t* trx; + + ut_ad(mutex_own(&kernel_mutex)); + ut_ad(lock_get_type_low(in_lock) == LOCK_REC); + + trx = in_lock->trx; + + space = in_lock->un_member.rec_lock.space; + page_no = in_lock->un_member.rec_lock.page_no; + + HASH_DELETE(lock_t, hash, lock_sys->rec_hash, + lock_rec_fold(space, page_no), in_lock); + + UT_LIST_REMOVE(trx_locks, trx->trx_locks, in_lock); + + /* Check if waiting locks in the queue can now be granted: grant + locks if there are no conflicting locks ahead. */ + + lock = lock_rec_get_first_on_page_addr(space, page_no); + + while (lock != NULL) { + if (lock_get_wait(lock) + && !lock_rec_has_to_wait_in_queue(lock)) { + + /* Grant the lock */ + lock_grant(lock); + } + + lock = lock_rec_get_next_on_page(lock); + } +} + +/***************************************************************** +Removes a record lock request, waiting or granted, from the queue. */ +static +void +lock_rec_discard( +/*=============*/ + lock_t* in_lock)/* in: record lock object: all record locks which + are contained in this lock object are removed */ +{ + ulint space; + ulint page_no; + trx_t* trx; + + ut_ad(mutex_own(&kernel_mutex)); + ut_ad(lock_get_type_low(in_lock) == LOCK_REC); + + trx = in_lock->trx; + + space = in_lock->un_member.rec_lock.space; + page_no = in_lock->un_member.rec_lock.page_no; + + HASH_DELETE(lock_t, hash, lock_sys->rec_hash, + lock_rec_fold(space, page_no), in_lock); + + UT_LIST_REMOVE(trx_locks, trx->trx_locks, in_lock); +} + +/***************************************************************** +Removes record lock objects set on an index page which is discarded. This +function does not move locks, or check for waiting locks, therefore the +lock bitmaps must already be reset when this function is called. */ +static +void +lock_rec_free_all_from_discard_page( +/*================================*/ + const buf_block_t* block) /* in: page to be discarded */ +{ + ulint space; + ulint page_no; + lock_t* lock; + lock_t* next_lock; + + ut_ad(mutex_own(&kernel_mutex)); + + space = buf_block_get_space(block); + page_no = buf_block_get_page_no(block); + + lock = lock_rec_get_first_on_page_addr(space, page_no); + + while (lock != NULL) { + ut_ad(lock_rec_find_set_bit(lock) == ULINT_UNDEFINED); + ut_ad(!lock_get_wait(lock)); + + next_lock = lock_rec_get_next_on_page(lock); + + lock_rec_discard(lock); + + lock = next_lock; + } +} + +/*============= RECORD LOCK MOVING AND INHERITING ===================*/ + +/***************************************************************** +Resets the lock bits for a single record. Releases transactions waiting for +lock requests here. */ +static +void +lock_rec_reset_and_release_wait( +/*============================*/ + const buf_block_t* block, /* in: buffer block containing + the record */ + ulint heap_no)/* in: heap number of record */ +{ + lock_t* lock; + + ut_ad(mutex_own(&kernel_mutex)); + + lock = lock_rec_get_first(block, heap_no); + + while (lock != NULL) { + if (lock_get_wait(lock)) { + lock_rec_cancel(lock); + } else { + lock_rec_reset_nth_bit(lock, heap_no); + } + + lock = lock_rec_get_next(heap_no, lock); + } +} + +/***************************************************************** +Makes a record to inherit the locks (except LOCK_INSERT_INTENTION type) +of another record as gap type locks, but does not reset the lock bits of +the other record. Also waiting lock requests on rec are inherited as +GRANTED gap locks. */ +static +void +lock_rec_inherit_to_gap( +/*====================*/ + const buf_block_t* heir_block, /* in: block containing the + record which inherits */ + const buf_block_t* block, /* in: block containing the + record from which inherited; + does NOT reset the locks on + this record */ + ulint heir_heap_no, /* in: heap_no of the + inheriting record */ + ulint heap_no) /* in: heap_no of the + donating record */ +{ + lock_t* lock; + + ut_ad(mutex_own(&kernel_mutex)); + + lock = lock_rec_get_first(block, heap_no); + + /* If srv_locks_unsafe_for_binlog is TRUE or session is using + READ COMMITTED isolation level, we do not want locks set + by an UPDATE or a DELETE to be inherited as gap type locks. But we + DO want S-locks set by a consistency constraint to be inherited also + then. */ + + while (lock != NULL) { + if (!lock_rec_get_insert_intention(lock) + && !((srv_locks_unsafe_for_binlog + || lock->trx->isolation_level + == TRX_ISO_READ_COMMITTED) + && lock_get_mode(lock) == LOCK_X)) { + + lock_rec_add_to_queue(LOCK_REC | LOCK_GAP + | lock_get_mode(lock), + heir_block, heir_heap_no, + lock->index, lock->trx); + } + + lock = lock_rec_get_next(heap_no, lock); + } +} + +/***************************************************************** +Makes a record to inherit the gap locks (except LOCK_INSERT_INTENTION type) +of another record as gap type locks, but does not reset the lock bits of the +other record. Also waiting lock requests are inherited as GRANTED gap locks. */ +static +void +lock_rec_inherit_to_gap_if_gap_lock( +/*================================*/ + const buf_block_t* block, /* in: buffer block */ + ulint heir_heap_no, /* in: heap_no of + record which inherits */ + ulint heap_no) /* in: heap_no of record + from which inherited; + does NOT reset the locks + on this record */ +{ + lock_t* lock; + + ut_ad(mutex_own(&kernel_mutex)); + + lock = lock_rec_get_first(block, heap_no); + + while (lock != NULL) { + if (!lock_rec_get_insert_intention(lock) + && (heap_no == PAGE_HEAP_NO_SUPREMUM + || !lock_rec_get_rec_not_gap(lock))) { + + lock_rec_add_to_queue(LOCK_REC | LOCK_GAP + | lock_get_mode(lock), + block, heir_heap_no, + lock->index, lock->trx); + } + + lock = lock_rec_get_next(heap_no, lock); + } +} + +/***************************************************************** +Moves the locks of a record to another record and resets the lock bits of +the donating record. */ +static +void +lock_rec_move( +/*==========*/ + const buf_block_t* receiver, /* in: buffer block containing + the receiving record */ + const buf_block_t* donator, /* in: buffer block containing + the donating record */ + ulint receiver_heap_no,/* in: heap_no of the record + which gets the locks; there + must be no lock requests + on it! */ + ulint donator_heap_no)/* in: heap_no of the record + which gives the locks */ +{ + lock_t* lock; + + ut_ad(mutex_own(&kernel_mutex)); + + lock = lock_rec_get_first(donator, donator_heap_no); + + ut_ad(lock_rec_get_first(receiver, receiver_heap_no) == NULL); + + while (lock != NULL) { + const ulint type_mode = lock->type_mode; + + lock_rec_reset_nth_bit(lock, donator_heap_no); + + if (UNIV_UNLIKELY(type_mode & LOCK_WAIT)) { + lock_reset_lock_and_trx_wait(lock); + } + + /* Note that we FIRST reset the bit, and then set the lock: + the function works also if donator == receiver */ + + lock_rec_add_to_queue(type_mode, receiver, receiver_heap_no, + lock->index, lock->trx); + lock = lock_rec_get_next(donator_heap_no, lock); + } + + ut_ad(lock_rec_get_first(donator, donator_heap_no) == NULL); +} + +/***************************************************************** +Updates the lock table when we have reorganized a page. NOTE: we copy +also the locks set on the infimum of the page; the infimum may carry +locks if an update of a record is occurring on the page, and its locks +were temporarily stored on the infimum. */ +UNIV_INTERN +void +lock_move_reorganize_page( +/*======================*/ + const buf_block_t* block, /* in: old index page, now + reorganized */ + const buf_block_t* oblock) /* in: copy of the old, not + reorganized page */ +{ + lock_t* lock; + UT_LIST_BASE_NODE_T(lock_t) old_locks; + mem_heap_t* heap = NULL; + ulint comp; + + lock_mutex_enter_kernel(); + + lock = lock_rec_get_first_on_page(block); + + if (lock == NULL) { + lock_mutex_exit_kernel(); + + return; + } + + heap = mem_heap_create(256); + + /* Copy first all the locks on the page to heap and reset the + bitmaps in the original locks; chain the copies of the locks + using the trx_locks field in them. */ + + UT_LIST_INIT(old_locks); + + do { + /* Make a copy of the lock */ + lock_t* old_lock = lock_rec_copy(lock, heap); + + UT_LIST_ADD_LAST(trx_locks, old_locks, old_lock); + + /* Reset bitmap of lock */ + lock_rec_bitmap_reset(lock); + + if (lock_get_wait(lock)) { + lock_reset_lock_and_trx_wait(lock); + } + + lock = lock_rec_get_next_on_page(lock); + } while (lock != NULL); + + comp = page_is_comp(block->frame); + ut_ad(comp == page_is_comp(oblock->frame)); + + for (lock = UT_LIST_GET_FIRST(old_locks); lock; + lock = UT_LIST_GET_NEXT(trx_locks, lock)) { + /* NOTE: we copy also the locks set on the infimum and + supremum of the page; the infimum may carry locks if an + update of a record is occurring on the page, and its locks + were temporarily stored on the infimum */ + page_cur_t cur1; + page_cur_t cur2; + + page_cur_set_before_first(block, &cur1); + page_cur_set_before_first(oblock, &cur2); + + /* Set locks according to old locks */ + for (;;) { + ulint old_heap_no; + ulint new_heap_no; + + ut_ad(comp || !memcmp(page_cur_get_rec(&cur1), + page_cur_get_rec(&cur2), + rec_get_data_size_old( + page_cur_get_rec( + &cur2)))); + if (UNIV_LIKELY(comp)) { + old_heap_no = rec_get_heap_no_new( + page_cur_get_rec(&cur2)); + new_heap_no = rec_get_heap_no_new( + page_cur_get_rec(&cur1)); + } else { + old_heap_no = rec_get_heap_no_old( + page_cur_get_rec(&cur2)); + new_heap_no = rec_get_heap_no_old( + page_cur_get_rec(&cur1)); + } + + if (lock_rec_get_nth_bit(lock, old_heap_no)) { + + /* Clear the bit in old_lock. */ + ut_d(lock_rec_reset_nth_bit(lock, + old_heap_no)); + + /* NOTE that the old lock bitmap could be too + small for the new heap number! */ + + lock_rec_add_to_queue(lock->type_mode, block, + new_heap_no, + lock->index, lock->trx); + + /* if (new_heap_no == PAGE_HEAP_NO_SUPREMUM + && lock_get_wait(lock)) { + fprintf(stderr, + "---\n--\n!!!Lock reorg: supr type %lu\n", + lock->type_mode); + } */ + } + + if (UNIV_UNLIKELY + (new_heap_no == PAGE_HEAP_NO_SUPREMUM)) { + + ut_ad(old_heap_no == PAGE_HEAP_NO_SUPREMUM); + break; + } + + page_cur_move_to_next(&cur1); + page_cur_move_to_next(&cur2); + } + +#ifdef UNIV_DEBUG + { + ulint i = lock_rec_find_set_bit(lock); + + /* Check that all locks were moved. */ + if (UNIV_UNLIKELY(i != ULINT_UNDEFINED)) { + fprintf(stderr, + "lock_move_reorganize_page():" + " %lu not moved in %p\n", + (ulong) i, (void*) lock); + ut_error; + } + } +#endif /* UNIV_DEBUG */ + } + + lock_mutex_exit_kernel(); + + mem_heap_free(heap); + +#ifdef UNIV_DEBUG_LOCK_VALIDATE + ut_ad(lock_rec_validate_page(buf_block_get_space(block), + buf_block_get_page_no(block))); +#endif +} + +/***************************************************************** +Moves the explicit locks on user records to another page if a record +list end is moved to another page. */ +UNIV_INTERN +void +lock_move_rec_list_end( +/*===================*/ + const buf_block_t* new_block, /* in: index page to move to */ + const buf_block_t* block, /* in: index page */ + const rec_t* rec) /* in: record on page: this + is the first record moved */ +{ + lock_t* lock; + const ulint comp = page_rec_is_comp(rec); + + lock_mutex_enter_kernel(); + + /* Note: when we move locks from record to record, waiting locks + and possible granted gap type locks behind them are enqueued in + the original order, because new elements are inserted to a hash + table to the end of the hash chain, and lock_rec_add_to_queue + does not reuse locks if there are waiters in the queue. */ + + for (lock = lock_rec_get_first_on_page(block); lock; + lock = lock_rec_get_next_on_page(lock)) { + page_cur_t cur1; + page_cur_t cur2; + const ulint type_mode = lock->type_mode; + + page_cur_position(rec, block, &cur1); + + if (page_cur_is_before_first(&cur1)) { + page_cur_move_to_next(&cur1); + } + + page_cur_set_before_first(new_block, &cur2); + page_cur_move_to_next(&cur2); + + /* Copy lock requests on user records to new page and + reset the lock bits on the old */ + + while (!page_cur_is_after_last(&cur1)) { + ulint heap_no; + + if (comp) { + heap_no = rec_get_heap_no_new( + page_cur_get_rec(&cur1)); + } else { + heap_no = rec_get_heap_no_old( + page_cur_get_rec(&cur1)); + ut_ad(!memcmp(page_cur_get_rec(&cur1), + page_cur_get_rec(&cur2), + rec_get_data_size_old( + page_cur_get_rec(&cur2)))); + } + + if (lock_rec_get_nth_bit(lock, heap_no)) { + lock_rec_reset_nth_bit(lock, heap_no); + + if (UNIV_UNLIKELY(type_mode & LOCK_WAIT)) { + lock_reset_lock_and_trx_wait(lock); + } + + if (comp) { + heap_no = rec_get_heap_no_new( + page_cur_get_rec(&cur2)); + } else { + heap_no = rec_get_heap_no_old( + page_cur_get_rec(&cur2)); + } + + lock_rec_add_to_queue(type_mode, + new_block, heap_no, + lock->index, lock->trx); + } + + page_cur_move_to_next(&cur1); + page_cur_move_to_next(&cur2); + } + } + + lock_mutex_exit_kernel(); + +#ifdef UNIV_DEBUG_LOCK_VALIDATE + ut_ad(lock_rec_validate_page(buf_block_get_space(block), + buf_block_get_page_no(block))); + ut_ad(lock_rec_validate_page(buf_block_get_space(new_block), + buf_block_get_page_no(new_block))); +#endif +} + +/***************************************************************** +Moves the explicit locks on user records to another page if a record +list start is moved to another page. */ +UNIV_INTERN +void +lock_move_rec_list_start( +/*=====================*/ + const buf_block_t* new_block, /* in: index page to move to */ + const buf_block_t* block, /* in: index page */ + const rec_t* rec, /* in: record on page: + this is the first + record NOT copied */ + const rec_t* old_end) /* in: old + previous-to-last + record on new_page + before the records + were copied */ +{ + lock_t* lock; + const ulint comp = page_rec_is_comp(rec); + + ut_ad(block->frame == page_align(rec)); + ut_ad(new_block->frame == page_align(old_end)); + + lock_mutex_enter_kernel(); + + for (lock = lock_rec_get_first_on_page(block); lock; + lock = lock_rec_get_next_on_page(lock)) { + page_cur_t cur1; + page_cur_t cur2; + const ulint type_mode = lock->type_mode; + + page_cur_set_before_first(block, &cur1); + page_cur_move_to_next(&cur1); + + page_cur_position(old_end, new_block, &cur2); + page_cur_move_to_next(&cur2); + + /* Copy lock requests on user records to new page and + reset the lock bits on the old */ + + while (page_cur_get_rec(&cur1) != rec) { + ulint heap_no; + + if (comp) { + heap_no = rec_get_heap_no_new( + page_cur_get_rec(&cur1)); + } else { + heap_no = rec_get_heap_no_old( + page_cur_get_rec(&cur1)); + ut_ad(!memcmp(page_cur_get_rec(&cur1), + page_cur_get_rec(&cur2), + rec_get_data_size_old( + page_cur_get_rec( + &cur2)))); + } + + if (lock_rec_get_nth_bit(lock, heap_no)) { + lock_rec_reset_nth_bit(lock, heap_no); + + if (UNIV_UNLIKELY(type_mode & LOCK_WAIT)) { + lock_reset_lock_and_trx_wait(lock); + } + + if (comp) { + heap_no = rec_get_heap_no_new( + page_cur_get_rec(&cur2)); + } else { + heap_no = rec_get_heap_no_old( + page_cur_get_rec(&cur2)); + } + + lock_rec_add_to_queue(type_mode, + new_block, heap_no, + lock->index, lock->trx); + } + + page_cur_move_to_next(&cur1); + page_cur_move_to_next(&cur2); + } + +#ifdef UNIV_DEBUG + if (page_rec_is_supremum(rec)) { + ulint i; + + for (i = PAGE_HEAP_NO_USER_LOW; + i < lock_rec_get_n_bits(lock); i++) { + if (UNIV_UNLIKELY + (lock_rec_get_nth_bit(lock, i))) { + + fprintf(stderr, + "lock_move_rec_list_start():" + " %lu not moved in %p\n", + (ulong) i, (void*) lock); + ut_error; + } + } + } +#endif /* UNIV_DEBUG */ + } + + lock_mutex_exit_kernel(); + +#ifdef UNIV_DEBUG_LOCK_VALIDATE + ut_ad(lock_rec_validate_page(buf_block_get_space(block), + buf_block_get_page_no(block))); +#endif +} + +/***************************************************************** +Updates the lock table when a page is split to the right. */ +UNIV_INTERN +void +lock_update_split_right( +/*====================*/ + const buf_block_t* right_block, /* in: right page */ + const buf_block_t* left_block) /* in: left page */ +{ + ulint heap_no = lock_get_min_heap_no(right_block); + + lock_mutex_enter_kernel(); + + /* Move the locks on the supremum of the left page to the supremum + of the right page */ + + lock_rec_move(right_block, left_block, + PAGE_HEAP_NO_SUPREMUM, PAGE_HEAP_NO_SUPREMUM); + + /* Inherit the locks to the supremum of left page from the successor + of the infimum on right page */ + + lock_rec_inherit_to_gap(left_block, right_block, + PAGE_HEAP_NO_SUPREMUM, heap_no); + + lock_mutex_exit_kernel(); +} + +/***************************************************************** +Updates the lock table when a page is merged to the right. */ +UNIV_INTERN +void +lock_update_merge_right( +/*====================*/ + const buf_block_t* right_block, /* in: right page to + which merged */ + const rec_t* orig_succ, /* in: original + successor of infimum + on the right page + before merge */ + const buf_block_t* left_block) /* in: merged index + page which will be + discarded */ +{ + lock_mutex_enter_kernel(); + + /* Inherit the locks from the supremum of the left page to the + original successor of infimum on the right page, to which the left + page was merged */ + + lock_rec_inherit_to_gap(right_block, left_block, + page_rec_get_heap_no(orig_succ), + PAGE_HEAP_NO_SUPREMUM); + + /* Reset the locks on the supremum of the left page, releasing + waiting transactions */ + + lock_rec_reset_and_release_wait(left_block, + PAGE_HEAP_NO_SUPREMUM); + + lock_rec_free_all_from_discard_page(left_block); + + lock_mutex_exit_kernel(); +} + +/***************************************************************** +Updates the lock table when the root page is copied to another in +btr_root_raise_and_insert. Note that we leave lock structs on the +root page, even though they do not make sense on other than leaf +pages: the reason is that in a pessimistic update the infimum record +of the root page will act as a dummy carrier of the locks of the record +to be updated. */ +UNIV_INTERN +void +lock_update_root_raise( +/*===================*/ + const buf_block_t* block, /* in: index page to which copied */ + const buf_block_t* root) /* in: root page */ +{ + lock_mutex_enter_kernel(); + + /* Move the locks on the supremum of the root to the supremum + of block */ + + lock_rec_move(block, root, + PAGE_HEAP_NO_SUPREMUM, PAGE_HEAP_NO_SUPREMUM); + lock_mutex_exit_kernel(); +} + +/***************************************************************** +Updates the lock table when a page is copied to another and the original page +is removed from the chain of leaf pages, except if page is the root! */ +UNIV_INTERN +void +lock_update_copy_and_discard( +/*=========================*/ + const buf_block_t* new_block, /* in: index page to + which copied */ + const buf_block_t* block) /* in: index page; + NOT the root! */ +{ + lock_mutex_enter_kernel(); + + /* Move the locks on the supremum of the old page to the supremum + of new_page */ + + lock_rec_move(new_block, block, + PAGE_HEAP_NO_SUPREMUM, PAGE_HEAP_NO_SUPREMUM); + lock_rec_free_all_from_discard_page(block); + + lock_mutex_exit_kernel(); +} + +/***************************************************************** +Updates the lock table when a page is split to the left. */ +UNIV_INTERN +void +lock_update_split_left( +/*===================*/ + const buf_block_t* right_block, /* in: right page */ + const buf_block_t* left_block) /* in: left page */ +{ + ulint heap_no = lock_get_min_heap_no(right_block); + + lock_mutex_enter_kernel(); + + /* Inherit the locks to the supremum of the left page from the + successor of the infimum on the right page */ + + lock_rec_inherit_to_gap(left_block, right_block, + PAGE_HEAP_NO_SUPREMUM, heap_no); + + lock_mutex_exit_kernel(); +} + +/***************************************************************** +Updates the lock table when a page is merged to the left. */ +UNIV_INTERN +void +lock_update_merge_left( +/*===================*/ + const buf_block_t* left_block, /* in: left page to + which merged */ + const rec_t* orig_pred, /* in: original predecessor + of supremum on the left page + before merge */ + const buf_block_t* right_block) /* in: merged index page + which will be discarded */ +{ + const rec_t* left_next_rec; + + ut_ad(left_block->frame == page_align(orig_pred)); + + lock_mutex_enter_kernel(); + + left_next_rec = page_rec_get_next_const(orig_pred); + + if (!page_rec_is_supremum(left_next_rec)) { + + /* Inherit the locks on the supremum of the left page to the + first record which was moved from the right page */ + + lock_rec_inherit_to_gap(left_block, left_block, + page_rec_get_heap_no(left_next_rec), + PAGE_HEAP_NO_SUPREMUM); + + /* Reset the locks on the supremum of the left page, + releasing waiting transactions */ + + lock_rec_reset_and_release_wait(left_block, + PAGE_HEAP_NO_SUPREMUM); + } + + /* Move the locks from the supremum of right page to the supremum + of the left page */ + + lock_rec_move(left_block, right_block, + PAGE_HEAP_NO_SUPREMUM, PAGE_HEAP_NO_SUPREMUM); + + lock_rec_free_all_from_discard_page(right_block); + + lock_mutex_exit_kernel(); +} + +/***************************************************************** +Resets the original locks on heir and replaces them with gap type locks +inherited from rec. */ +UNIV_INTERN +void +lock_rec_reset_and_inherit_gap_locks( +/*=================================*/ + const buf_block_t* heir_block, /* in: block containing the + record which inherits */ + const buf_block_t* block, /* in: block containing the + record from which inherited; + does NOT reset the locks on + this record */ + ulint heir_heap_no, /* in: heap_no of the + inheriting record */ + ulint heap_no) /* in: heap_no of the + donating record */ +{ + mutex_enter(&kernel_mutex); + + lock_rec_reset_and_release_wait(heir_block, heir_heap_no); + + lock_rec_inherit_to_gap(heir_block, block, heir_heap_no, heap_no); + + mutex_exit(&kernel_mutex); +} + +/***************************************************************** +Updates the lock table when a page is discarded. */ +UNIV_INTERN +void +lock_update_discard( +/*================*/ + const buf_block_t* heir_block, /* in: index page + which will inherit the locks */ + ulint heir_heap_no, /* in: heap_no of the record + which will inherit the locks */ + const buf_block_t* block) /* in: index page + which will be discarded */ +{ + const page_t* page = block->frame; + const rec_t* rec; + ulint heap_no; + + lock_mutex_enter_kernel(); + + if (!lock_rec_get_first_on_page(block)) { + /* No locks exist on page, nothing to do */ + + lock_mutex_exit_kernel(); + + return; + } + + /* Inherit all the locks on the page to the record and reset all + the locks on the page */ + + if (page_is_comp(page)) { + rec = page + PAGE_NEW_INFIMUM; + + do { + heap_no = rec_get_heap_no_new(rec); + + lock_rec_inherit_to_gap(heir_block, block, + heir_heap_no, heap_no); + + lock_rec_reset_and_release_wait(block, heap_no); + + rec = page + rec_get_next_offs(rec, TRUE); + } while (heap_no != PAGE_HEAP_NO_SUPREMUM); + } else { + rec = page + PAGE_OLD_INFIMUM; + + do { + heap_no = rec_get_heap_no_old(rec); + + lock_rec_inherit_to_gap(heir_block, block, + heir_heap_no, heap_no); + + lock_rec_reset_and_release_wait(block, heap_no); + + rec = page + rec_get_next_offs(rec, FALSE); + } while (heap_no != PAGE_HEAP_NO_SUPREMUM); + } + + lock_rec_free_all_from_discard_page(block); + + lock_mutex_exit_kernel(); +} + +/***************************************************************** +Updates the lock table when a new user record is inserted. */ +UNIV_INTERN +void +lock_update_insert( +/*===============*/ + const buf_block_t* block, /* in: buffer block containing rec */ + const rec_t* rec) /* in: the inserted record */ +{ + ulint receiver_heap_no; + ulint donator_heap_no; + + ut_ad(block->frame == page_align(rec)); + + /* Inherit the gap-locking locks for rec, in gap mode, from the next + record */ + + if (page_rec_is_comp(rec)) { + receiver_heap_no = rec_get_heap_no_new(rec); + donator_heap_no = rec_get_heap_no_new( + page_rec_get_next_low(rec, TRUE)); + } else { + receiver_heap_no = rec_get_heap_no_old(rec); + donator_heap_no = rec_get_heap_no_old( + page_rec_get_next_low(rec, FALSE)); + } + + lock_mutex_enter_kernel(); + lock_rec_inherit_to_gap_if_gap_lock(block, + receiver_heap_no, donator_heap_no); + lock_mutex_exit_kernel(); +} + +/***************************************************************** +Updates the lock table when a record is removed. */ +UNIV_INTERN +void +lock_update_delete( +/*===============*/ + const buf_block_t* block, /* in: buffer block containing rec */ + const rec_t* rec) /* in: the record to be removed */ +{ + const page_t* page = block->frame; + ulint heap_no; + ulint next_heap_no; + + ut_ad(page == page_align(rec)); + + if (page_is_comp(page)) { + heap_no = rec_get_heap_no_new(rec); + next_heap_no = rec_get_heap_no_new(page + + rec_get_next_offs(rec, + TRUE)); + } else { + heap_no = rec_get_heap_no_old(rec); + next_heap_no = rec_get_heap_no_old(page + + rec_get_next_offs(rec, + FALSE)); + } + + lock_mutex_enter_kernel(); + + /* Let the next record inherit the locks from rec, in gap mode */ + + lock_rec_inherit_to_gap(block, block, next_heap_no, heap_no); + + /* Reset the lock bits on rec and release waiting transactions */ + + lock_rec_reset_and_release_wait(block, heap_no); + + lock_mutex_exit_kernel(); +} + +/************************************************************************* +Stores on the page infimum record the explicit locks of another record. +This function is used to store the lock state of a record when it is +updated and the size of the record changes in the update. The record +is moved in such an update, perhaps to another page. The infimum record +acts as a dummy carrier record, taking care of lock releases while the +actual record is being moved. */ +UNIV_INTERN +void +lock_rec_store_on_page_infimum( +/*===========================*/ + const buf_block_t* block, /* in: buffer block containing rec */ + const rec_t* rec) /* in: record whose lock state + is stored on the infimum + record of the same page; lock + bits are reset on the + record */ +{ + ulint heap_no = page_rec_get_heap_no(rec); + + ut_ad(block->frame == page_align(rec)); + + lock_mutex_enter_kernel(); + + lock_rec_move(block, block, PAGE_HEAP_NO_INFIMUM, heap_no); + + lock_mutex_exit_kernel(); +} + +/************************************************************************* +Restores the state of explicit lock requests on a single record, where the +state was stored on the infimum of the page. */ +UNIV_INTERN +void +lock_rec_restore_from_page_infimum( +/*===============================*/ + const buf_block_t* block, /* in: buffer block containing rec */ + const rec_t* rec, /* in: record whose lock state + is restored */ + const buf_block_t* donator)/* in: page (rec is not + necessarily on this page) + whose infimum stored the lock + state; lock bits are reset on + the infimum */ +{ + ulint heap_no = page_rec_get_heap_no(rec); + + lock_mutex_enter_kernel(); + + lock_rec_move(block, donator, heap_no, PAGE_HEAP_NO_INFIMUM); + + lock_mutex_exit_kernel(); +} + +/*=========== DEADLOCK CHECKING ======================================*/ + +/************************************************************************ +Checks if a lock request results in a deadlock. */ +static +ibool +lock_deadlock_occurs( +/*=================*/ + /* out: TRUE if a deadlock was detected and we + chose trx as a victim; FALSE if no deadlock, or + there was a deadlock, but we chose other + transaction(s) as victim(s) */ + lock_t* lock, /* in: lock the transaction is requesting */ + trx_t* trx) /* in: transaction */ +{ + dict_table_t* table; + dict_index_t* index; + trx_t* mark_trx; + ulint ret; + ulint cost = 0; + + ut_ad(trx); + ut_ad(lock); + ut_ad(mutex_own(&kernel_mutex)); +retry: + /* We check that adding this trx to the waits-for graph + does not produce a cycle. First mark all active transactions + with 0: */ + + mark_trx = UT_LIST_GET_FIRST(trx_sys->trx_list); + + while (mark_trx) { + mark_trx->deadlock_mark = 0; + mark_trx = UT_LIST_GET_NEXT(trx_list, mark_trx); + } + + ret = lock_deadlock_recursive(trx, trx, lock, &cost, 0); + + if (ret == LOCK_VICTIM_IS_OTHER) { + /* We chose some other trx as a victim: retry if there still + is a deadlock */ + + goto retry; + } + + if (UNIV_UNLIKELY(ret == LOCK_VICTIM_IS_START)) { + if (lock_get_type_low(lock) & LOCK_TABLE) { + table = lock->un_member.tab_lock.table; + index = NULL; + } else { + index = lock->index; + table = index->table; + } + + lock_deadlock_found = TRUE; + + fputs("*** WE ROLL BACK TRANSACTION (2)\n", + lock_latest_err_file); + + return(TRUE); + } + + return(FALSE); +} + +/************************************************************************ +Looks recursively for a deadlock. */ +static +ulint +lock_deadlock_recursive( +/*====================*/ + /* out: 0 if no deadlock found, + LOCK_VICTIM_IS_START if there was a deadlock + and we chose 'start' as the victim, + LOCK_VICTIM_IS_OTHER if a deadlock + was found and we chose some other trx as a + victim: we must do the search again in this + last case because there may be another + deadlock! */ + trx_t* start, /* in: recursion starting point */ + trx_t* trx, /* in: a transaction waiting for a lock */ + lock_t* wait_lock, /* in: the lock trx is waiting to be granted */ + ulint* cost, /* in/out: number of calculation steps thus + far: if this exceeds LOCK_MAX_N_STEPS_... + we return LOCK_VICTIM_IS_START */ + ulint depth) /* in: recursion depth: if this exceeds + LOCK_MAX_DEPTH_IN_DEADLOCK_CHECK, we + return LOCK_VICTIM_IS_START */ +{ + lock_t* lock; + ulint bit_no = ULINT_UNDEFINED; + trx_t* lock_trx; + ulint ret; + + ut_a(trx); + ut_a(start); + ut_a(wait_lock); + ut_ad(mutex_own(&kernel_mutex)); + + if (trx->deadlock_mark == 1) { + /* We have already exhaustively searched the subtree starting + from this trx */ + + return(0); + } + + *cost = *cost + 1; + + lock = wait_lock; + + if (lock_get_type_low(wait_lock) == LOCK_REC) { + + bit_no = lock_rec_find_set_bit(wait_lock); + + ut_a(bit_no != ULINT_UNDEFINED); + } + + /* Look at the locks ahead of wait_lock in the lock queue */ + + for (;;) { + if (lock_get_type_low(lock) & LOCK_TABLE) { + + lock = UT_LIST_GET_PREV(un_member.tab_lock.locks, + lock); + } else { + ut_ad(lock_get_type_low(lock) == LOCK_REC); + ut_a(bit_no != ULINT_UNDEFINED); + + lock = (lock_t*) lock_rec_get_prev(lock, bit_no); + } + + if (lock == NULL) { + /* We can mark this subtree as searched */ + trx->deadlock_mark = 1; + + return(FALSE); + } + + if (lock_has_to_wait(wait_lock, lock)) { + + ibool too_far + = depth > LOCK_MAX_DEPTH_IN_DEADLOCK_CHECK + || *cost > LOCK_MAX_N_STEPS_IN_DEADLOCK_CHECK; + + lock_trx = lock->trx; + + if (lock_trx == start || too_far) { + + /* We came back to the recursion starting + point: a deadlock detected; or we have + searched the waits-for graph too long */ + + FILE* ef = lock_latest_err_file; + + rewind(ef); + ut_print_timestamp(ef); + + fputs("\n*** (1) TRANSACTION:\n", ef); + + trx_print(ef, wait_lock->trx, 3000); + + fputs("*** (1) WAITING FOR THIS LOCK" + " TO BE GRANTED:\n", ef); + + if (lock_get_type_low(wait_lock) == LOCK_REC) { + lock_rec_print(ef, wait_lock); + } else { + lock_table_print(ef, wait_lock); + } + + fputs("*** (2) TRANSACTION:\n", ef); + + trx_print(ef, lock->trx, 3000); + + fputs("*** (2) HOLDS THE LOCK(S):\n", ef); + + if (lock_get_type_low(lock) == LOCK_REC) { + lock_rec_print(ef, lock); + } else { + lock_table_print(ef, lock); + } + + fputs("*** (2) WAITING FOR THIS LOCK" + " TO BE GRANTED:\n", ef); + + if (lock_get_type_low(start->wait_lock) + == LOCK_REC) { + lock_rec_print(ef, start->wait_lock); + } else { + lock_table_print(ef, start->wait_lock); + } +#ifdef UNIV_DEBUG + if (lock_print_waits) { + fputs("Deadlock detected" + " or too long search\n", + stderr); + } +#endif /* UNIV_DEBUG */ + if (too_far) { + + fputs("TOO DEEP OR LONG SEARCH" + " IN THE LOCK TABLE" + " WAITS-FOR GRAPH\n", ef); + + return(LOCK_VICTIM_IS_START); + } + + if (trx_weight_cmp(wait_lock->trx, + start) >= 0) { + /* Our recursion starting point + transaction is 'smaller', let us + choose 'start' as the victim and roll + back it */ + + return(LOCK_VICTIM_IS_START); + } + + lock_deadlock_found = TRUE; + + /* Let us choose the transaction of wait_lock + as a victim to try to avoid deadlocking our + recursion starting point transaction */ + + fputs("*** WE ROLL BACK TRANSACTION (1)\n", + ef); + + wait_lock->trx->was_chosen_as_deadlock_victim + = TRUE; + + lock_cancel_waiting_and_release(wait_lock); + + /* Since trx and wait_lock are no longer + in the waits-for graph, we can return FALSE; + note that our selective algorithm can choose + several transactions as victims, but still + we may end up rolling back also the recursion + starting point transaction! */ + + return(LOCK_VICTIM_IS_OTHER); + } + + if (lock_trx->que_state == TRX_QUE_LOCK_WAIT) { + + /* Another trx ahead has requested lock in an + incompatible mode, and is itself waiting for + a lock */ + + ret = lock_deadlock_recursive( + start, lock_trx, + lock_trx->wait_lock, cost, depth + 1); + if (ret != 0) { + + return(ret); + } + } + } + }/* end of the 'for (;;)'-loop */ +} + +/*========================= TABLE LOCKS ==============================*/ + +/************************************************************************* +Creates a table lock object and adds it as the last in the lock queue +of the table. Does NOT check for deadlocks or lock compatibility. */ +UNIV_INLINE +lock_t* +lock_table_create( +/*==============*/ + /* out, own: new lock object */ + dict_table_t* table, /* in: database table in dictionary cache */ + ulint type_mode,/* in: lock mode possibly ORed with + LOCK_WAIT */ + trx_t* trx) /* in: trx */ +{ + lock_t* lock; + + ut_ad(table && trx); + ut_ad(mutex_own(&kernel_mutex)); + + if ((type_mode & LOCK_MODE_MASK) == LOCK_AUTO_INC) { + ++table->n_waiting_or_granted_auto_inc_locks; + } + + /* For AUTOINC locking we reuse the lock instance only if + there is no wait involved else we allocate the waiting lock + from the transaction lock heap. */ + if (type_mode == LOCK_AUTO_INC) { + + lock = table->autoinc_lock; + + table->autoinc_trx = trx; + + ib_vector_push(trx->autoinc_locks, lock); + } else { + lock = mem_heap_alloc(trx->lock_heap, sizeof(lock_t)); + } + + UT_LIST_ADD_LAST(trx_locks, trx->trx_locks, lock); + + lock->type_mode = type_mode | LOCK_TABLE; + lock->trx = trx; + + lock->un_member.tab_lock.table = table; + + UT_LIST_ADD_LAST(un_member.tab_lock.locks, table->locks, lock); + + if (UNIV_UNLIKELY(type_mode & LOCK_WAIT)) { + + lock_set_lock_and_trx_wait(lock, trx); + } + + return(lock); +} + +/***************************************************************** +Removes a table lock request from the queue and the trx list of locks; +this is a low-level function which does NOT check if waiting requests +can now be granted. */ +UNIV_INLINE +void +lock_table_remove_low( +/*==================*/ + lock_t* lock) /* in: table lock */ +{ + trx_t* trx; + dict_table_t* table; + + ut_ad(mutex_own(&kernel_mutex)); + + trx = lock->trx; + table = lock->un_member.tab_lock.table; + + /* Remove the table from the transaction's AUTOINC vector, if + the lock that is being release is an AUTOINC lock. */ + if (lock_get_mode(lock) == LOCK_AUTO_INC) { + + /* The table's AUTOINC lock can get transferred to + another transaction before we get here. */ + if (table->autoinc_trx == trx) { + table->autoinc_trx = NULL; + } + + /* The locks must be freed in the reverse order from + the one in which they were acquired. This is to avoid + traversing the AUTOINC lock vector unnecessarily. + + We only store locks that were granted in the + trx->autoinc_locks vector (see lock_table_create() + and lock_grant()). Therefore it can be empty and we + need to check for that. */ + + if (!ib_vector_is_empty(trx->autoinc_locks)) { + lock_t* autoinc_lock; + + autoinc_lock = ib_vector_pop(trx->autoinc_locks); + ut_a(autoinc_lock == lock); + } + + ut_a(table->n_waiting_or_granted_auto_inc_locks > 0); + --table->n_waiting_or_granted_auto_inc_locks; + } + + UT_LIST_REMOVE(trx_locks, trx->trx_locks, lock); + UT_LIST_REMOVE(un_member.tab_lock.locks, table->locks, lock); +} + +/************************************************************************* +Enqueues a waiting request for a table lock which cannot be granted +immediately. Checks for deadlocks. */ +static +ulint +lock_table_enqueue_waiting( +/*=======================*/ + /* out: DB_LOCK_WAIT, DB_DEADLOCK, or + DB_QUE_THR_SUSPENDED, or DB_SUCCESS; + DB_SUCCESS means that there was a deadlock, + but another transaction was chosen as a + victim, and we got the lock immediately: + no need to wait then */ + ulint mode, /* in: lock mode this transaction is + requesting */ + dict_table_t* table, /* in: table */ + que_thr_t* thr) /* in: query thread */ +{ + lock_t* lock; + trx_t* trx; + + ut_ad(mutex_own(&kernel_mutex)); + + /* Test if there already is some other reason to suspend thread: + we do not enqueue a lock request if the query thread should be + stopped anyway */ + + if (que_thr_stop(thr)) { + ut_error; + + return(DB_QUE_THR_SUSPENDED); + } + + trx = thr_get_trx(thr); + + switch (trx_get_dict_operation(trx)) { + case TRX_DICT_OP_NONE: + break; + case TRX_DICT_OP_TABLE: + case TRX_DICT_OP_INDEX: + ut_print_timestamp(stderr); + fputs(" InnoDB: Error: a table lock wait happens" + " in a dictionary operation!\n" + "InnoDB: Table name ", stderr); + ut_print_name(stderr, trx, TRUE, table->name); + fputs(".\n" + "InnoDB: Submit a detailed bug report" + " to http://bugs.mysql.com\n", + stderr); + } + + /* Enqueue the lock request that will wait to be granted */ + + lock = lock_table_create(table, mode | LOCK_WAIT, trx); + + /* Check if a deadlock occurs: if yes, remove the lock request and + return an error code */ + + if (lock_deadlock_occurs(lock, trx)) { + + lock_reset_lock_and_trx_wait(lock); + lock_table_remove_low(lock); + + return(DB_DEADLOCK); + } + + if (trx->wait_lock == NULL) { + /* Deadlock resolution chose another transaction as a victim, + and we accidentally got our lock granted! */ + + return(DB_SUCCESS); + } + + trx->que_state = TRX_QUE_LOCK_WAIT; + trx->was_chosen_as_deadlock_victim = FALSE; + trx->wait_started = time(NULL); + + ut_a(que_thr_stop(thr)); + + return(DB_LOCK_WAIT); +} + +/************************************************************************* +Checks if other transactions have an incompatible mode lock request in +the lock queue. */ +UNIV_INLINE +ibool +lock_table_other_has_incompatible( +/*==============================*/ + trx_t* trx, /* in: transaction, or NULL if all + transactions should be included */ + ulint wait, /* in: LOCK_WAIT if also waiting locks are + taken into account, or 0 if not */ + dict_table_t* table, /* in: table */ + enum lock_mode mode) /* in: lock mode */ +{ + lock_t* lock; + + ut_ad(mutex_own(&kernel_mutex)); + + lock = UT_LIST_GET_LAST(table->locks); + + while (lock != NULL) { + + if ((lock->trx != trx) + && (!lock_mode_compatible(lock_get_mode(lock), mode)) + && (wait || !(lock_get_wait(lock)))) { + + return(TRUE); + } + + lock = UT_LIST_GET_PREV(un_member.tab_lock.locks, lock); + } + + return(FALSE); +} + +/************************************************************************* +Locks the specified database table in the mode given. If the lock cannot +be granted immediately, the query thread is put to wait. */ +UNIV_INTERN +ulint +lock_table( +/*=======*/ + /* out: DB_SUCCESS, DB_LOCK_WAIT, + DB_DEADLOCK, or DB_QUE_THR_SUSPENDED */ + ulint flags, /* in: if BTR_NO_LOCKING_FLAG bit is set, + does nothing */ + dict_table_t* table, /* in: database table in dictionary cache */ + enum lock_mode mode, /* in: lock mode */ + que_thr_t* thr) /* in: query thread */ +{ + trx_t* trx; + ulint err; + + ut_ad(table && thr); + + if (flags & BTR_NO_LOCKING_FLAG) { + + return(DB_SUCCESS); + } + + ut_a(flags == 0); + + trx = thr_get_trx(thr); + + lock_mutex_enter_kernel(); + + /* Look for stronger locks the same trx already has on the table */ + + if (lock_table_has(trx, table, mode)) { + + lock_mutex_exit_kernel(); + + return(DB_SUCCESS); + } + + /* We have to check if the new lock is compatible with any locks + other transactions have in the table lock queue. */ + + if (lock_table_other_has_incompatible(trx, LOCK_WAIT, table, mode)) { + + /* Another trx has a request on the table in an incompatible + mode: this trx may have to wait */ + + err = lock_table_enqueue_waiting(mode | flags, table, thr); + + lock_mutex_exit_kernel(); + + return(err); + } + + lock_table_create(table, mode | flags, trx); + + ut_a(!flags || mode == LOCK_S || mode == LOCK_X); + + lock_mutex_exit_kernel(); + + return(DB_SUCCESS); +} + +/************************************************************************* +Checks if there are any locks set on the table. */ +UNIV_INTERN +ibool +lock_is_on_table( +/*=============*/ + /* out: TRUE if there are lock(s) */ + dict_table_t* table) /* in: database table in dictionary cache */ +{ + ibool ret; + + ut_ad(table); + + lock_mutex_enter_kernel(); + + if (UT_LIST_GET_LAST(table->locks)) { + ret = TRUE; + } else { + ret = FALSE; + } + + lock_mutex_exit_kernel(); + + return(ret); +} + +/************************************************************************* +Checks if a waiting table lock request still has to wait in a queue. */ +static +ibool +lock_table_has_to_wait_in_queue( +/*============================*/ + /* out: TRUE if still has to wait */ + lock_t* wait_lock) /* in: waiting table lock */ +{ + dict_table_t* table; + lock_t* lock; + + ut_ad(mutex_own(&kernel_mutex)); + ut_ad(lock_get_wait(wait_lock)); + + table = wait_lock->un_member.tab_lock.table; + + lock = UT_LIST_GET_FIRST(table->locks); + + while (lock != wait_lock) { + + if (lock_has_to_wait(wait_lock, lock)) { + + return(TRUE); + } + + lock = UT_LIST_GET_NEXT(un_member.tab_lock.locks, lock); + } + + return(FALSE); +} + +/***************************************************************** +Removes a table lock request, waiting or granted, from the queue and grants +locks to other transactions in the queue, if they now are entitled to a +lock. */ +static +void +lock_table_dequeue( +/*===============*/ + lock_t* in_lock)/* in: table lock object; transactions waiting + behind will get their lock requests granted, if + they are now qualified to it */ +{ + lock_t* lock; + + ut_ad(mutex_own(&kernel_mutex)); + ut_a(lock_get_type_low(in_lock) == LOCK_TABLE); + + lock = UT_LIST_GET_NEXT(un_member.tab_lock.locks, in_lock); + + lock_table_remove_low(in_lock); + + /* Check if waiting locks in the queue can now be granted: grant + locks if there are no conflicting locks ahead. */ + + while (lock != NULL) { + + if (lock_get_wait(lock) + && !lock_table_has_to_wait_in_queue(lock)) { + + /* Grant the lock */ + lock_grant(lock); + } + + lock = UT_LIST_GET_NEXT(un_member.tab_lock.locks, lock); + } +} + +/*=========================== LOCK RELEASE ==============================*/ + +/***************************************************************** +Removes a granted record lock of a transaction from the queue and grants +locks to other transactions waiting in the queue if they now are entitled +to a lock. */ +UNIV_INTERN +void +lock_rec_unlock( +/*============*/ + trx_t* trx, /* in: transaction that has + set a record lock */ + const buf_block_t* block, /* in: buffer block containing rec */ + const rec_t* rec, /* in: record */ + enum lock_mode lock_mode)/* in: LOCK_S or LOCK_X */ +{ + lock_t* lock; + lock_t* release_lock = NULL; + ulint heap_no; + + ut_ad(trx && rec); + ut_ad(block->frame == page_align(rec)); + + heap_no = page_rec_get_heap_no(rec); + + mutex_enter(&kernel_mutex); + + lock = lock_rec_get_first(block, heap_no); + + /* Find the last lock with the same lock_mode and transaction + from the record. */ + + while (lock != NULL) { + if (lock->trx == trx && lock_get_mode(lock) == lock_mode) { + release_lock = lock; + ut_a(!lock_get_wait(lock)); + } + + lock = lock_rec_get_next(heap_no, lock); + } + + /* If a record lock is found, release the record lock */ + + if (UNIV_LIKELY(release_lock != NULL)) { + lock_rec_reset_nth_bit(release_lock, heap_no); + } else { + mutex_exit(&kernel_mutex); + ut_print_timestamp(stderr); + fprintf(stderr, + " InnoDB: Error: unlock row could not" + " find a %lu mode lock on the record\n", + (ulong) lock_mode); + + return; + } + + /* Check if we can now grant waiting lock requests */ + + lock = lock_rec_get_first(block, heap_no); + + while (lock != NULL) { + if (lock_get_wait(lock) + && !lock_rec_has_to_wait_in_queue(lock)) { + + /* Grant the lock */ + lock_grant(lock); + } + + lock = lock_rec_get_next(heap_no, lock); + } + + mutex_exit(&kernel_mutex); +} + +/************************************************************************* +Releases a table lock. +Releases possible other transactions waiting for this lock. */ +UNIV_INTERN +void +lock_table_unlock( +/*==============*/ + lock_t* lock) /* in: lock */ +{ + mutex_enter(&kernel_mutex); + + lock_table_dequeue(lock); + + mutex_exit(&kernel_mutex); +} + +/************************************************************************* +Releases transaction locks, and releases possible other transactions waiting +because of these locks. */ +UNIV_INTERN +void +lock_release_off_kernel( +/*====================*/ + trx_t* trx) /* in: transaction */ +{ + dict_table_t* table; + ulint count; + lock_t* lock; + + ut_ad(mutex_own(&kernel_mutex)); + + lock = UT_LIST_GET_LAST(trx->trx_locks); + + count = 0; + + while (lock != NULL) { + + count++; + + if (lock_get_type_low(lock) == LOCK_REC) { + + lock_rec_dequeue_from_page(lock); + } else { + ut_ad(lock_get_type_low(lock) & LOCK_TABLE); + + if (lock_get_mode(lock) != LOCK_IS + && !ut_dulint_is_zero(trx->undo_no)) { + + /* The trx may have modified the table. We + block the use of the MySQL query cache for + all currently active transactions. */ + + table = lock->un_member.tab_lock.table; + + table->query_cache_inv_trx_id + = trx_sys->max_trx_id; + } + + lock_table_dequeue(lock); + } + + if (count == LOCK_RELEASE_KERNEL_INTERVAL) { + /* Release the kernel mutex for a while, so that we + do not monopolize it */ + + lock_mutex_exit_kernel(); + + lock_mutex_enter_kernel(); + + count = 0; + } + + lock = UT_LIST_GET_LAST(trx->trx_locks); + } + + ut_a(ib_vector_size(trx->autoinc_locks) == 0); + + mem_heap_empty(trx->lock_heap); +} + +/************************************************************************* +Cancels a waiting lock request and releases possible other transactions +waiting behind it. */ +UNIV_INTERN +void +lock_cancel_waiting_and_release( +/*============================*/ + lock_t* lock) /* in: waiting lock request */ +{ + ut_ad(mutex_own(&kernel_mutex)); + + if (lock_get_type_low(lock) == LOCK_REC) { + + lock_rec_dequeue_from_page(lock); + } else { + ut_ad(lock_get_type_low(lock) & LOCK_TABLE); + + if (lock->trx->autoinc_locks != NULL) { + /* Release the transaction's AUTOINC locks/ */ + lock_release_autoinc_locks(lock->trx); + } + + lock_table_dequeue(lock); + } + + /* Reset the wait flag and the back pointer to lock in trx */ + + lock_reset_lock_and_trx_wait(lock); + + /* The following function releases the trx from lock wait */ + + trx_end_lock_wait(lock->trx); +} + +/* True if a lock mode is S or X */ +#define IS_LOCK_S_OR_X(lock) \ + (lock_get_mode(lock) == LOCK_S \ + || lock_get_mode(lock) == LOCK_X) + + +/************************************************************************* +Removes locks of a transaction on a table to be dropped. +If remove_also_table_sx_locks is TRUE then table-level S and X locks are +also removed in addition to other table-level and record-level locks. +No lock, that is going to be removed, is allowed to be a wait lock. */ +static +void +lock_remove_all_on_table_for_trx( +/*=============================*/ + dict_table_t* table, /* in: table to be dropped */ + trx_t* trx, /* in: a transaction */ + ibool remove_also_table_sx_locks)/* in: also removes + table S and X locks */ +{ + lock_t* lock; + lock_t* prev_lock; + + ut_ad(mutex_own(&kernel_mutex)); + + lock = UT_LIST_GET_LAST(trx->trx_locks); + + while (lock != NULL) { + prev_lock = UT_LIST_GET_PREV(trx_locks, lock); + + if (lock_get_type_low(lock) == LOCK_REC + && lock->index->table == table) { + ut_a(!lock_get_wait(lock)); + + lock_rec_discard(lock); + } else if (lock_get_type_low(lock) & LOCK_TABLE + && lock->un_member.tab_lock.table == table + && (remove_also_table_sx_locks + || !IS_LOCK_S_OR_X(lock))) { + + ut_a(!lock_get_wait(lock)); + + lock_table_remove_low(lock); + } + + lock = prev_lock; + } +} + +/************************************************************************* +Removes locks on a table to be dropped or truncated. +If remove_also_table_sx_locks is TRUE then table-level S and X locks are +also removed in addition to other table-level and record-level locks. +No lock, that is going to be removed, is allowed to be a wait lock. */ +UNIV_INTERN +void +lock_remove_all_on_table( +/*=====================*/ + dict_table_t* table, /* in: table to be dropped + or truncated */ + ibool remove_also_table_sx_locks)/* in: also removes + table S and X locks */ +{ + lock_t* lock; + lock_t* prev_lock; + + mutex_enter(&kernel_mutex); + + lock = UT_LIST_GET_FIRST(table->locks); + + while (lock != NULL) { + + prev_lock = UT_LIST_GET_PREV(un_member.tab_lock.locks, + lock); + + /* If we should remove all locks (remove_also_table_sx_locks + is TRUE), or if the lock is not table-level S or X lock, + then check we are not going to remove a wait lock. */ + if (remove_also_table_sx_locks + || !(lock_get_type(lock) == LOCK_TABLE + && IS_LOCK_S_OR_X(lock))) { + + ut_a(!lock_get_wait(lock)); + } + + lock_remove_all_on_table_for_trx(table, lock->trx, + remove_also_table_sx_locks); + + if (prev_lock == NULL) { + if (lock == UT_LIST_GET_FIRST(table->locks)) { + /* lock was not removed, pick its successor */ + lock = UT_LIST_GET_NEXT( + un_member.tab_lock.locks, lock); + } else { + /* lock was removed, pick the first one */ + lock = UT_LIST_GET_FIRST(table->locks); + } + } else if (UT_LIST_GET_NEXT(un_member.tab_lock.locks, + prev_lock) != lock) { + /* If lock was removed by + lock_remove_all_on_table_for_trx() then pick the + successor of prev_lock ... */ + lock = UT_LIST_GET_NEXT( + un_member.tab_lock.locks, prev_lock); + } else { + /* ... otherwise pick the successor of lock. */ + lock = UT_LIST_GET_NEXT( + un_member.tab_lock.locks, lock); + } + } + + mutex_exit(&kernel_mutex); +} + +/*===================== VALIDATION AND DEBUGGING ====================*/ + +/************************************************************************* +Prints info of a table lock. */ +UNIV_INTERN +void +lock_table_print( +/*=============*/ + FILE* file, /* in: file where to print */ + const lock_t* lock) /* in: table type lock */ +{ + ut_ad(mutex_own(&kernel_mutex)); + ut_a(lock_get_type_low(lock) == LOCK_TABLE); + + fputs("TABLE LOCK table ", file); + ut_print_name(file, lock->trx, TRUE, + lock->un_member.tab_lock.table->name); + fprintf(file, " trx id " TRX_ID_FMT, + TRX_ID_PREP_PRINTF(lock->trx->id)); + + if (lock_get_mode(lock) == LOCK_S) { + fputs(" lock mode S", file); + } else if (lock_get_mode(lock) == LOCK_X) { + fputs(" lock mode X", file); + } else if (lock_get_mode(lock) == LOCK_IS) { + fputs(" lock mode IS", file); + } else if (lock_get_mode(lock) == LOCK_IX) { + fputs(" lock mode IX", file); + } else if (lock_get_mode(lock) == LOCK_AUTO_INC) { + fputs(" lock mode AUTO-INC", file); + } else { + fprintf(file, " unknown lock mode %lu", + (ulong) lock_get_mode(lock)); + } + + if (lock_get_wait(lock)) { + fputs(" waiting", file); + } + + putc('\n', file); +} + +/************************************************************************* +Prints info of a record lock. */ +UNIV_INTERN +void +lock_rec_print( +/*===========*/ + FILE* file, /* in: file where to print */ + const lock_t* lock) /* in: record type lock */ +{ + const buf_block_t* block; + ulint space; + ulint page_no; + ulint i; + mtr_t mtr; + mem_heap_t* heap = NULL; + ulint offsets_[REC_OFFS_NORMAL_SIZE]; + ulint* offsets = offsets_; + rec_offs_init(offsets_); + + ut_ad(mutex_own(&kernel_mutex)); + ut_a(lock_get_type_low(lock) == LOCK_REC); + + space = lock->un_member.rec_lock.space; + page_no = lock->un_member.rec_lock.page_no; + + fprintf(file, "RECORD LOCKS space id %lu page no %lu n bits %lu ", + (ulong) space, (ulong) page_no, + (ulong) lock_rec_get_n_bits(lock)); + dict_index_name_print(file, lock->trx, lock->index); + fprintf(file, " trx id " TRX_ID_FMT, + TRX_ID_PREP_PRINTF(lock->trx->id)); + + if (lock_get_mode(lock) == LOCK_S) { + fputs(" lock mode S", file); + } else if (lock_get_mode(lock) == LOCK_X) { + fputs(" lock_mode X", file); + } else { + ut_error; + } + + if (lock_rec_get_gap(lock)) { + fputs(" locks gap before rec", file); + } + + if (lock_rec_get_rec_not_gap(lock)) { + fputs(" locks rec but not gap", file); + } + + if (lock_rec_get_insert_intention(lock)) { + fputs(" insert intention", file); + } + + if (lock_get_wait(lock)) { + fputs(" waiting", file); + } + + mtr_start(&mtr); + + putc('\n', file); + + if ( srv_show_verbose_locks ) { + block = buf_page_try_get(space, page_no, &mtr); + if (block) { + for (i = 0; i < lock_rec_get_n_bits(lock); i++) { + + if (lock_rec_get_nth_bit(lock, i)) { + + const rec_t* rec + = page_find_rec_with_heap_no( + buf_block_get_frame(block), i); + offsets = rec_get_offsets( + rec, lock->index, offsets, + ULINT_UNDEFINED, &heap); + + fprintf(file, "Record lock, heap no %lu ", + (ulong) i); + rec_print_new(file, rec, offsets); + putc('\n', file); + } + } + } else { + for (i = 0; i < lock_rec_get_n_bits(lock); i++) { + fprintf(file, "Record lock, heap no %lu\n", (ulong) i); + } + } + } + mtr_commit(&mtr); + if (UNIV_LIKELY_NULL(heap)) { + mem_heap_free(heap); + } +} + +#ifndef UNIV_HOTBACKUP + +#ifdef UNIV_DEBUG +/* Print the number of lock structs from lock_print_info_summary() only +in non-production builds for performance reasons, see +http://bugs.mysql.com/36942 */ +#define PRINT_NUM_OF_LOCK_STRUCTS +#endif /* UNIV_DEBUG */ + +#ifdef PRINT_NUM_OF_LOCK_STRUCTS +/************************************************************************* +Calculates the number of record lock structs in the record lock hash table. */ +static +ulint +lock_get_n_rec_locks(void) +/*======================*/ +{ + lock_t* lock; + ulint n_locks = 0; + ulint i; + + ut_ad(mutex_own(&kernel_mutex)); + + for (i = 0; i < hash_get_n_cells(lock_sys->rec_hash); i++) { + + lock = HASH_GET_FIRST(lock_sys->rec_hash, i); + + while (lock) { + n_locks++; + + lock = HASH_GET_NEXT(hash, lock); + } + } + + return(n_locks); +} +#endif /* PRINT_NUM_OF_LOCK_STRUCTS */ + +/************************************************************************* +Prints info of locks for all transactions. */ +UNIV_INTERN +void +lock_print_info_summary( +/*====================*/ + FILE* file) /* in: file where to print */ +{ + /* We must protect the MySQL thd->query field with a MySQL mutex, and + because the MySQL mutex must be reserved before the kernel_mutex of + InnoDB, we call innobase_mysql_prepare_print_arbitrary_thd() here. */ + + innobase_mysql_prepare_print_arbitrary_thd(); + lock_mutex_enter_kernel(); + + if (lock_deadlock_found) { + fputs("------------------------\n" + "LATEST DETECTED DEADLOCK\n" + "------------------------\n", file); + + ut_copy_file(file, lock_latest_err_file); + } + + fputs("------------\n" + "TRANSACTIONS\n" + "------------\n", file); + + fprintf(file, "Trx id counter " TRX_ID_FMT "\n", + TRX_ID_PREP_PRINTF(trx_sys->max_trx_id)); + + fprintf(file, + "Purge done for trx's n:o < " TRX_ID_FMT + " undo n:o < " TRX_ID_FMT "\n", + TRX_ID_PREP_PRINTF(purge_sys->purge_trx_no), + TRX_ID_PREP_PRINTF(purge_sys->purge_undo_no)); + + fprintf(file, + "History list length %lu\n", + (ulong) trx_sys->rseg_history_len); + +#ifdef PRINT_NUM_OF_LOCK_STRUCTS + fprintf(file, + "Total number of lock structs in row lock hash table %lu\n", + (ulong) lock_get_n_rec_locks()); +#endif /* PRINT_NUM_OF_LOCK_STRUCTS */ +} + +/************************************************************************* +Prints info of locks for each transaction. */ +UNIV_INTERN +void +lock_print_info_all_transactions( +/*=============================*/ + FILE* file) /* in: file where to print */ +{ + lock_t* lock; + ibool load_page_first = TRUE; + ulint nth_trx = 0; + ulint nth_lock = 0; + ulint i; + mtr_t mtr; + trx_t* trx; + + fprintf(file, "LIST OF TRANSACTIONS FOR EACH SESSION:\n"); + + /* First print info on non-active transactions */ + + trx = UT_LIST_GET_FIRST(trx_sys->mysql_trx_list); + + while (trx) { + if (trx->conc_state == TRX_NOT_STARTED) { + fputs("---", file); + trx_print(file, trx, 600); + } + + trx = UT_LIST_GET_NEXT(mysql_trx_list, trx); + } + +loop: + trx = UT_LIST_GET_FIRST(trx_sys->trx_list); + + i = 0; + + /* Since we temporarily release the kernel mutex when + reading a database page in below, variable trx may be + obsolete now and we must loop through the trx list to + get probably the same trx, or some other trx. */ + + while (trx && (i < nth_trx)) { + trx = UT_LIST_GET_NEXT(trx_list, trx); + i++; + } + + if (trx == NULL) { + lock_mutex_exit_kernel(); + innobase_mysql_end_print_arbitrary_thd(); + + ut_ad(lock_validate()); + + return; + } + + if (nth_lock == 0) { + fputs("---", file); + trx_print(file, trx, 600); + + if (trx->read_view) { + fprintf(file, + "Trx read view will not see trx with" + " id >= " TRX_ID_FMT + ", sees < " TRX_ID_FMT "\n", + TRX_ID_PREP_PRINTF( + trx->read_view->low_limit_id), + TRX_ID_PREP_PRINTF( + trx->read_view->up_limit_id)); + } + + if (trx->que_state == TRX_QUE_LOCK_WAIT) { + fprintf(file, + "------- TRX HAS BEEN WAITING %lu SEC" + " FOR THIS LOCK TO BE GRANTED:\n", + (ulong) difftime(time(NULL), + trx->wait_started)); + + if (lock_get_type_low(trx->wait_lock) == LOCK_REC) { + lock_rec_print(file, trx->wait_lock); + } else { + lock_table_print(file, trx->wait_lock); + } + + fputs("------------------\n", file); + } + } + + if (!srv_print_innodb_lock_monitor && !srv_show_locks_held) { + nth_trx++; + goto loop; + } + + i = 0; + + /* Look at the note about the trx loop above why we loop here: + lock may be an obsolete pointer now. */ + + lock = UT_LIST_GET_FIRST(trx->trx_locks); + + while (lock && (i < nth_lock)) { + lock = UT_LIST_GET_NEXT(trx_locks, lock); + i++; + } + + if (lock == NULL) { + nth_trx++; + nth_lock = 0; + + goto loop; + } + + if (lock_get_type_low(lock) == LOCK_REC) { + if (load_page_first) { + ulint space = lock->un_member.rec_lock.space; + ulint zip_size= fil_space_get_zip_size(space); + ulint page_no = lock->un_member.rec_lock.page_no; + + lock_mutex_exit_kernel(); + innobase_mysql_end_print_arbitrary_thd(); + + mtr_start(&mtr); + + buf_page_get_with_no_latch(space, zip_size, + page_no, &mtr); + + mtr_commit(&mtr); + + load_page_first = FALSE; + + innobase_mysql_prepare_print_arbitrary_thd(); + lock_mutex_enter_kernel(); + + goto loop; + } + + lock_rec_print(file, lock); + } else { + ut_ad(lock_get_type_low(lock) & LOCK_TABLE); + + lock_table_print(file, lock); + } + + load_page_first = TRUE; + + nth_lock++; + + if (nth_lock >= srv_show_locks_held) { + fputs("TOO LOCKS PRINTED FOR THIS TRX:" + " SUPPRESSING FURTHER PRINTS\n", + file); + + nth_trx++; + nth_lock = 0; + + goto loop; + } + + goto loop; +} + +# ifdef UNIV_DEBUG +/************************************************************************* +Validates the lock queue on a table. */ +static +ibool +lock_table_queue_validate( +/*======================*/ + /* out: TRUE if ok */ + dict_table_t* table) /* in: table */ +{ + lock_t* lock; + + ut_ad(mutex_own(&kernel_mutex)); + + lock = UT_LIST_GET_FIRST(table->locks); + + while (lock) { + ut_a(((lock->trx)->conc_state == TRX_ACTIVE) + || ((lock->trx)->conc_state == TRX_PREPARED) + || ((lock->trx)->conc_state == TRX_COMMITTED_IN_MEMORY)); + + if (!lock_get_wait(lock)) { + + ut_a(!lock_table_other_has_incompatible( + lock->trx, 0, table, + lock_get_mode(lock))); + } else { + + ut_a(lock_table_has_to_wait_in_queue(lock)); + } + + lock = UT_LIST_GET_NEXT(un_member.tab_lock.locks, lock); + } + + return(TRUE); +} + +/************************************************************************* +Validates the lock queue on a single record. */ +static +ibool +lock_rec_queue_validate( +/*====================*/ + /* out: TRUE if ok */ + const buf_block_t* block, /* in: buffer block containing rec */ + const rec_t* rec, /* in: record to look at */ + dict_index_t* index, /* in: index, or NULL if not known */ + const ulint* offsets)/* in: rec_get_offsets(rec, index) */ +{ + trx_t* impl_trx; + lock_t* lock; + ulint heap_no; + + ut_a(rec); + ut_a(block->frame == page_align(rec)); + ut_ad(rec_offs_validate(rec, index, offsets)); + ut_ad(!page_rec_is_comp(rec) == !rec_offs_comp(offsets)); + + heap_no = page_rec_get_heap_no(rec); + + lock_mutex_enter_kernel(); + + if (!page_rec_is_user_rec(rec)) { + + lock = lock_rec_get_first(block, heap_no); + + while (lock) { + switch(lock->trx->conc_state) { + case TRX_ACTIVE: + case TRX_PREPARED: + case TRX_COMMITTED_IN_MEMORY: + break; + default: + ut_error; + } + + ut_a(trx_in_trx_list(lock->trx)); + + if (lock_get_wait(lock)) { + ut_a(lock_rec_has_to_wait_in_queue(lock)); + } + + if (index) { + ut_a(lock->index == index); + } + + lock = lock_rec_get_next(heap_no, lock); + } + + lock_mutex_exit_kernel(); + + return(TRUE); + } + + if (!index); + else if (dict_index_is_clust(index)) { + + impl_trx = lock_clust_rec_some_has_impl(rec, index, offsets); + + if (impl_trx + && lock_rec_other_has_expl_req(LOCK_S, 0, LOCK_WAIT, + block, heap_no, impl_trx)) { + + ut_a(lock_rec_has_expl(LOCK_X | LOCK_REC_NOT_GAP, + block, heap_no, impl_trx)); + } + } else { + + /* The kernel mutex may get released temporarily in the + next function call: we have to release lock table mutex + to obey the latching order */ + + impl_trx = lock_sec_rec_some_has_impl_off_kernel( + rec, index, offsets); + + if (impl_trx + && lock_rec_other_has_expl_req(LOCK_S, 0, LOCK_WAIT, + block, heap_no, impl_trx)) { + + ut_a(lock_rec_has_expl(LOCK_X | LOCK_REC_NOT_GAP, + block, heap_no, impl_trx)); + } + } + + lock = lock_rec_get_first(block, heap_no); + + while (lock) { + ut_a(lock->trx->conc_state == TRX_ACTIVE + || lock->trx->conc_state == TRX_PREPARED + || lock->trx->conc_state == TRX_COMMITTED_IN_MEMORY); + ut_a(trx_in_trx_list(lock->trx)); + + if (index) { + ut_a(lock->index == index); + } + + if (!lock_rec_get_gap(lock) && !lock_get_wait(lock)) { + + enum lock_mode mode; + + if (lock_get_mode(lock) == LOCK_S) { + mode = LOCK_X; + } else { + mode = LOCK_S; + } + ut_a(!lock_rec_other_has_expl_req( + mode, 0, 0, block, heap_no, lock->trx)); + + } else if (lock_get_wait(lock) && !lock_rec_get_gap(lock)) { + + ut_a(lock_rec_has_to_wait_in_queue(lock)); + } + + lock = lock_rec_get_next(heap_no, lock); + } + + lock_mutex_exit_kernel(); + + return(TRUE); +} + +/************************************************************************* +Validates the record lock queues on a page. */ +static +ibool +lock_rec_validate_page( +/*===================*/ + /* out: TRUE if ok */ + ulint space, /* in: space id */ + ulint page_no)/* in: page number */ +{ + dict_index_t* index; + buf_block_t* block; + const page_t* page; + lock_t* lock; + const rec_t* rec; + ulint nth_lock = 0; + ulint nth_bit = 0; + ulint i; + mtr_t mtr; + mem_heap_t* heap = NULL; + ulint offsets_[REC_OFFS_NORMAL_SIZE]; + ulint* offsets = offsets_; + rec_offs_init(offsets_); + + ut_ad(!mutex_own(&kernel_mutex)); + + mtr_start(&mtr); + + block = buf_page_get(space, fil_space_get_zip_size(space), + page_no, RW_X_LATCH, &mtr); + buf_block_dbg_add_level(block, SYNC_NO_ORDER_CHECK); + + page = block->frame; + + lock_mutex_enter_kernel(); +loop: + lock = lock_rec_get_first_on_page_addr(space, page_no); + + if (!lock) { + goto function_exit; + } + + for (i = 0; i < nth_lock; i++) { + + lock = lock_rec_get_next_on_page(lock); + + if (!lock) { + goto function_exit; + } + } + + ut_a(trx_in_trx_list(lock->trx)); + ut_a(lock->trx->conc_state == TRX_ACTIVE + || lock->trx->conc_state == TRX_PREPARED + || lock->trx->conc_state == TRX_COMMITTED_IN_MEMORY); + + for (i = nth_bit; i < lock_rec_get_n_bits(lock); i++) { + + if (i == 1 || lock_rec_get_nth_bit(lock, i)) { + + index = lock->index; + rec = page_find_rec_with_heap_no(page, i); + ut_a(rec); + offsets = rec_get_offsets(rec, index, offsets, + ULINT_UNDEFINED, &heap); + + fprintf(stderr, + "Validating %lu %lu\n", + (ulong) space, (ulong) page_no); + + lock_mutex_exit_kernel(); + + lock_rec_queue_validate(block, rec, index, offsets); + + lock_mutex_enter_kernel(); + + nth_bit = i + 1; + + goto loop; + } + } + + nth_bit = 0; + nth_lock++; + + goto loop; + +function_exit: + lock_mutex_exit_kernel(); + + mtr_commit(&mtr); + + if (UNIV_LIKELY_NULL(heap)) { + mem_heap_free(heap); + } + return(TRUE); +} + +/************************************************************************* +Validates the lock system. */ +static +ibool +lock_validate(void) +/*===============*/ + /* out: TRUE if ok */ +{ + lock_t* lock; + trx_t* trx; + dulint limit; + ulint space; + ulint page_no; + ulint i; + + lock_mutex_enter_kernel(); + + trx = UT_LIST_GET_FIRST(trx_sys->trx_list); + + while (trx) { + lock = UT_LIST_GET_FIRST(trx->trx_locks); + + while (lock) { + if (lock_get_type_low(lock) & LOCK_TABLE) { + + lock_table_queue_validate( + lock->un_member.tab_lock.table); + } + + lock = UT_LIST_GET_NEXT(trx_locks, lock); + } + + trx = UT_LIST_GET_NEXT(trx_list, trx); + } + + for (i = 0; i < hash_get_n_cells(lock_sys->rec_hash); i++) { + + limit = ut_dulint_zero; + + for (;;) { + lock = HASH_GET_FIRST(lock_sys->rec_hash, i); + + while (lock) { + ut_a(trx_in_trx_list(lock->trx)); + + space = lock->un_member.rec_lock.space; + page_no = lock->un_member.rec_lock.page_no; + + if (ut_dulint_cmp( + ut_dulint_create(space, page_no), + limit) >= 0) { + break; + } + + lock = HASH_GET_NEXT(hash, lock); + } + + if (!lock) { + + break; + } + + lock_mutex_exit_kernel(); + + lock_rec_validate_page(space, page_no); + + lock_mutex_enter_kernel(); + + limit = ut_dulint_create(space, page_no + 1); + } + } + + lock_mutex_exit_kernel(); + + return(TRUE); +} +# endif /* UNIV_DEBUG */ +#endif /* !UNIV_HOTBACKUP */ +/*============ RECORD LOCK CHECKS FOR ROW OPERATIONS ====================*/ + +/************************************************************************* +Checks if locks of other transactions prevent an immediate insert of +a record. If they do, first tests if the query thread should anyway +be suspended for some reason; if not, then puts the transaction and +the query thread to the lock wait state and inserts a waiting request +for a gap x-lock to the lock queue. */ +UNIV_INTERN +ulint +lock_rec_insert_check_and_lock( +/*===========================*/ + /* out: DB_SUCCESS, DB_LOCK_WAIT, + DB_DEADLOCK, or DB_QUE_THR_SUSPENDED */ + ulint flags, /* in: if BTR_NO_LOCKING_FLAG bit is + set, does nothing */ + rec_t* rec, /* in: record after which to insert */ + buf_block_t* block, /* in/out: buffer block of rec */ + dict_index_t* index, /* in: index */ + que_thr_t* thr, /* in: query thread */ + ibool* inherit)/* out: set to TRUE if the new + inserted record maybe should inherit + LOCK_GAP type locks from the successor + record */ +{ + const rec_t* next_rec; + trx_t* trx; + lock_t* lock; + ulint err; + ulint next_rec_heap_no; + + ut_ad(block->frame == page_align(rec)); + + if (flags & BTR_NO_LOCKING_FLAG) { + + return(DB_SUCCESS); + } + + trx = thr_get_trx(thr); + next_rec = page_rec_get_next(rec); + next_rec_heap_no = page_rec_get_heap_no(next_rec); + + lock_mutex_enter_kernel(); + + /* When inserting a record into an index, the table must be at + least IX-locked or we must be building an index, in which case + the table must be at least S-locked. */ + ut_ad(lock_table_has(trx, index->table, LOCK_IX) + || (*index->name == TEMP_INDEX_PREFIX + && lock_table_has(trx, index->table, LOCK_S))); + + lock = lock_rec_get_first(block, next_rec_heap_no); + + if (UNIV_LIKELY(lock == NULL)) { + /* We optimize CPU time usage in the simplest case */ + + lock_mutex_exit_kernel(); + + if (!dict_index_is_clust(index)) { + /* Update the page max trx id field */ + page_update_max_trx_id(block, + buf_block_get_page_zip(block), + trx->id); + } + + *inherit = FALSE; + + return(DB_SUCCESS); + } + + *inherit = TRUE; + + /* If another transaction has an explicit lock request which locks + the gap, waiting or granted, on the successor, the insert has to wait. + + An exception is the case where the lock by the another transaction + is a gap type lock which it placed to wait for its turn to insert. We + do not consider that kind of a lock conflicting with our insert. This + eliminates an unnecessary deadlock which resulted when 2 transactions + had to wait for their insert. Both had waiting gap type lock requests + on the successor, which produced an unnecessary deadlock. */ + + if (lock_rec_other_has_conflicting( + LOCK_X | LOCK_GAP | LOCK_INSERT_INTENTION, + block, next_rec_heap_no, trx)) { + + /* Note that we may get DB_SUCCESS also here! */ + err = lock_rec_enqueue_waiting(LOCK_X | LOCK_GAP + | LOCK_INSERT_INTENTION, + block, next_rec_heap_no, + index, thr); + } else { + err = DB_SUCCESS; + } + + lock_mutex_exit_kernel(); + + if ((err == DB_SUCCESS) && !dict_index_is_clust(index)) { + /* Update the page max trx id field */ + page_update_max_trx_id(block, + buf_block_get_page_zip(block), + trx->id); + } + +#ifdef UNIV_DEBUG + { + mem_heap_t* heap = NULL; + ulint offsets_[REC_OFFS_NORMAL_SIZE]; + const ulint* offsets; + rec_offs_init(offsets_); + + offsets = rec_get_offsets(next_rec, index, offsets_, + ULINT_UNDEFINED, &heap); + ut_ad(lock_rec_queue_validate(block, + next_rec, index, offsets)); + if (UNIV_LIKELY_NULL(heap)) { + mem_heap_free(heap); + } + } +#endif /* UNIV_DEBUG */ + + return(err); +} + +/************************************************************************* +If a transaction has an implicit x-lock on a record, but no explicit x-lock +set on the record, sets one for it. NOTE that in the case of a secondary +index, the kernel mutex may get temporarily released. */ +static +void +lock_rec_convert_impl_to_expl( +/*==========================*/ + const buf_block_t* block, /* in: buffer block of rec */ + const rec_t* rec, /* in: user record on page */ + dict_index_t* index, /* in: index of record */ + const ulint* offsets)/* in: rec_get_offsets(rec, index) */ +{ + trx_t* impl_trx; + + ut_ad(mutex_own(&kernel_mutex)); + ut_ad(page_rec_is_user_rec(rec)); + ut_ad(rec_offs_validate(rec, index, offsets)); + ut_ad(!page_rec_is_comp(rec) == !rec_offs_comp(offsets)); + + if (dict_index_is_clust(index)) { + impl_trx = lock_clust_rec_some_has_impl(rec, index, offsets); + } else { + impl_trx = lock_sec_rec_some_has_impl_off_kernel( + rec, index, offsets); + } + + if (impl_trx) { + ulint heap_no = page_rec_get_heap_no(rec); + + /* If the transaction has no explicit x-lock set on the + record, set one for it */ + + if (!lock_rec_has_expl(LOCK_X | LOCK_REC_NOT_GAP, block, + heap_no, impl_trx)) { + + lock_rec_add_to_queue( + LOCK_REC | LOCK_X | LOCK_REC_NOT_GAP, + block, heap_no, index, impl_trx); + } + } +} + +/************************************************************************* +Checks if locks of other transactions prevent an immediate modify (update, +delete mark, or delete unmark) of a clustered index record. If they do, +first tests if the query thread should anyway be suspended for some +reason; if not, then puts the transaction and the query thread to the +lock wait state and inserts a waiting request for a record x-lock to the +lock queue. */ +UNIV_INTERN +ulint +lock_clust_rec_modify_check_and_lock( +/*=================================*/ + /* out: DB_SUCCESS, + DB_LOCK_WAIT, DB_DEADLOCK, or + DB_QUE_THR_SUSPENDED */ + ulint flags, /* in: if BTR_NO_LOCKING_FLAG + bit is set, does nothing */ + const buf_block_t* block, /* in: buffer block of rec */ + const rec_t* rec, /* in: record which should be + modified */ + dict_index_t* index, /* in: clustered index */ + const ulint* offsets,/* in: rec_get_offsets(rec, index) */ + que_thr_t* thr) /* in: query thread */ +{ + ulint err; + ulint heap_no; + + ut_ad(rec_offs_validate(rec, index, offsets)); + ut_ad(dict_index_is_clust(index)); + ut_ad(block->frame == page_align(rec)); + + if (flags & BTR_NO_LOCKING_FLAG) { + + return(DB_SUCCESS); + } + + heap_no = rec_offs_comp(offsets) + ? rec_get_heap_no_new(rec) + : rec_get_heap_no_old(rec); + + lock_mutex_enter_kernel(); + + ut_ad(lock_table_has(thr_get_trx(thr), index->table, LOCK_IX)); + + /* If a transaction has no explicit x-lock set on the record, set one + for it */ + + lock_rec_convert_impl_to_expl(block, rec, index, offsets); + + err = lock_rec_lock(TRUE, LOCK_X | LOCK_REC_NOT_GAP, + block, heap_no, index, thr); + + lock_mutex_exit_kernel(); + + ut_ad(lock_rec_queue_validate(block, rec, index, offsets)); + + return(err); +} + +/************************************************************************* +Checks if locks of other transactions prevent an immediate modify (delete +mark or delete unmark) of a secondary index record. */ +UNIV_INTERN +ulint +lock_sec_rec_modify_check_and_lock( +/*===============================*/ + /* out: DB_SUCCESS, DB_LOCK_WAIT, + DB_DEADLOCK, or DB_QUE_THR_SUSPENDED */ + ulint flags, /* in: if BTR_NO_LOCKING_FLAG + bit is set, does nothing */ + buf_block_t* block, /* in/out: buffer block of rec */ + rec_t* rec, /* in: record which should be + modified; NOTE: as this is a secondary + index, we always have to modify the + clustered index record first: see the + comment below */ + dict_index_t* index, /* in: secondary index */ + que_thr_t* thr) /* in: query thread */ +{ + ulint err; + ulint heap_no; + + ut_ad(!dict_index_is_clust(index)); + ut_ad(block->frame == page_align(rec)); + + if (flags & BTR_NO_LOCKING_FLAG) { + + return(DB_SUCCESS); + } + + heap_no = page_rec_get_heap_no(rec); + + /* Another transaction cannot have an implicit lock on the record, + because when we come here, we already have modified the clustered + index record, and this would not have been possible if another active + transaction had modified this secondary index record. */ + + lock_mutex_enter_kernel(); + + ut_ad(lock_table_has(thr_get_trx(thr), index->table, LOCK_IX)); + + err = lock_rec_lock(TRUE, LOCK_X | LOCK_REC_NOT_GAP, + block, heap_no, index, thr); + + lock_mutex_exit_kernel(); + +#ifdef UNIV_DEBUG + { + mem_heap_t* heap = NULL; + ulint offsets_[REC_OFFS_NORMAL_SIZE]; + const ulint* offsets; + rec_offs_init(offsets_); + + offsets = rec_get_offsets(rec, index, offsets_, + ULINT_UNDEFINED, &heap); + ut_ad(lock_rec_queue_validate(block, rec, index, offsets)); + if (UNIV_LIKELY_NULL(heap)) { + mem_heap_free(heap); + } + } +#endif /* UNIV_DEBUG */ + + if (err == DB_SUCCESS) { + /* Update the page max trx id field */ + page_update_max_trx_id(block, + buf_block_get_page_zip(block), + thr_get_trx(thr)->id); + } + + return(err); +} + +/************************************************************************* +Like the counterpart for a clustered index below, but now we read a +secondary index record. */ +UNIV_INTERN +ulint +lock_sec_rec_read_check_and_lock( +/*=============================*/ + /* out: DB_SUCCESS, + DB_LOCK_WAIT, DB_DEADLOCK, or + DB_QUE_THR_SUSPENDED */ + ulint flags, /* in: if BTR_NO_LOCKING_FLAG + bit is set, does nothing */ + const buf_block_t* block, /* in: buffer block of rec */ + const rec_t* rec, /* in: user record or page + supremum record which should + be read or passed over by a + read cursor */ + dict_index_t* index, /* in: secondary index */ + const ulint* offsets,/* in: rec_get_offsets(rec, index) */ + enum lock_mode mode, /* in: mode of the lock which + the read cursor should set on + records: LOCK_S or LOCK_X; the + latter is possible in + SELECT FOR UPDATE */ + ulint gap_mode,/* in: LOCK_ORDINARY, LOCK_GAP, or + LOCK_REC_NOT_GAP */ + que_thr_t* thr) /* in: query thread */ +{ + ulint err; + ulint heap_no; + + ut_ad(!dict_index_is_clust(index)); + ut_ad(block->frame == page_align(rec)); + ut_ad(page_rec_is_user_rec(rec) || page_rec_is_supremum(rec)); + ut_ad(rec_offs_validate(rec, index, offsets)); + ut_ad(mode == LOCK_X || mode == LOCK_S); + + if (flags & BTR_NO_LOCKING_FLAG) { + + return(DB_SUCCESS); + } + + heap_no = page_rec_get_heap_no(rec); + + lock_mutex_enter_kernel(); + + ut_ad(mode != LOCK_X + || lock_table_has(thr_get_trx(thr), index->table, LOCK_IX)); + ut_ad(mode != LOCK_S + || lock_table_has(thr_get_trx(thr), index->table, LOCK_IS)); + + /* Some transaction may have an implicit x-lock on the record only + if the max trx id for the page >= min trx id for the trx list or a + database recovery is running. */ + + if (((ut_dulint_cmp(page_get_max_trx_id(block->frame), + trx_list_get_min_trx_id()) >= 0) + || recv_recovery_is_on()) + && !page_rec_is_supremum(rec)) { + + lock_rec_convert_impl_to_expl(block, rec, index, offsets); + } + + err = lock_rec_lock(FALSE, mode | gap_mode, + block, heap_no, index, thr); + + lock_mutex_exit_kernel(); + + ut_ad(lock_rec_queue_validate(block, rec, index, offsets)); + + return(err); +} + +/************************************************************************* +Checks if locks of other transactions prevent an immediate read, or passing +over by a read cursor, of a clustered index record. If they do, first tests +if the query thread should anyway be suspended for some reason; if not, then +puts the transaction and the query thread to the lock wait state and inserts a +waiting request for a record lock to the lock queue. Sets the requested mode +lock on the record. */ +UNIV_INTERN +ulint +lock_clust_rec_read_check_and_lock( +/*===============================*/ + /* out: DB_SUCCESS, + DB_LOCK_WAIT, DB_DEADLOCK, or + DB_QUE_THR_SUSPENDED */ + ulint flags, /* in: if BTR_NO_LOCKING_FLAG + bit is set, does nothing */ + const buf_block_t* block, /* in: buffer block of rec */ + const rec_t* rec, /* in: user record or page + supremum record which should + be read or passed over by a + read cursor */ + dict_index_t* index, /* in: clustered index */ + const ulint* offsets,/* in: rec_get_offsets(rec, index) */ + enum lock_mode mode, /* in: mode of the lock which + the read cursor should set on + records: LOCK_S or LOCK_X; the + latter is possible in + SELECT FOR UPDATE */ + ulint gap_mode,/* in: LOCK_ORDINARY, LOCK_GAP, or + LOCK_REC_NOT_GAP */ + que_thr_t* thr) /* in: query thread */ +{ + ulint err; + ulint heap_no; + + ut_ad(dict_index_is_clust(index)); + ut_ad(block->frame == page_align(rec)); + ut_ad(page_rec_is_user_rec(rec) || page_rec_is_supremum(rec)); + ut_ad(gap_mode == LOCK_ORDINARY || gap_mode == LOCK_GAP + || gap_mode == LOCK_REC_NOT_GAP); + ut_ad(rec_offs_validate(rec, index, offsets)); + + if (flags & BTR_NO_LOCKING_FLAG) { + + return(DB_SUCCESS); + } + + heap_no = page_rec_get_heap_no(rec); + + lock_mutex_enter_kernel(); + + ut_ad(mode != LOCK_X + || lock_table_has(thr_get_trx(thr), index->table, LOCK_IX)); + ut_ad(mode != LOCK_S + || lock_table_has(thr_get_trx(thr), index->table, LOCK_IS)); + + if (UNIV_LIKELY(heap_no != PAGE_HEAP_NO_SUPREMUM)) { + + lock_rec_convert_impl_to_expl(block, rec, index, offsets); + } + + err = lock_rec_lock(FALSE, mode | gap_mode, + block, heap_no, index, thr); + + lock_mutex_exit_kernel(); + + ut_ad(lock_rec_queue_validate(block, rec, index, offsets)); + + return(err); +} +/************************************************************************* +Checks if locks of other transactions prevent an immediate read, or passing +over by a read cursor, of a clustered index record. If they do, first tests +if the query thread should anyway be suspended for some reason; if not, then +puts the transaction and the query thread to the lock wait state and inserts a +waiting request for a record lock to the lock queue. Sets the requested mode +lock on the record. This is an alternative version of +lock_clust_rec_read_check_and_lock() that does not require the parameter +"offsets". */ +UNIV_INTERN +ulint +lock_clust_rec_read_check_and_lock_alt( +/*===================================*/ + /* out: DB_SUCCESS, + DB_LOCK_WAIT, DB_DEADLOCK, or + DB_QUE_THR_SUSPENDED */ + ulint flags, /* in: if BTR_NO_LOCKING_FLAG + bit is set, does nothing */ + const buf_block_t* block, /* in: buffer block of rec */ + const rec_t* rec, /* in: user record or page + supremum record which should + be read or passed over by a + read cursor */ + dict_index_t* index, /* in: clustered index */ + enum lock_mode mode, /* in: mode of the lock which + the read cursor should set on + records: LOCK_S or LOCK_X; the + latter is possible in + SELECT FOR UPDATE */ + ulint gap_mode,/* in: LOCK_ORDINARY, LOCK_GAP, or + LOCK_REC_NOT_GAP */ + que_thr_t* thr) /* in: query thread */ +{ + mem_heap_t* tmp_heap = NULL; + ulint offsets_[REC_OFFS_NORMAL_SIZE]; + ulint* offsets = offsets_; + ulint ret; + rec_offs_init(offsets_); + + offsets = rec_get_offsets(rec, index, offsets, + ULINT_UNDEFINED, &tmp_heap); + ret = lock_clust_rec_read_check_and_lock(flags, block, rec, index, + offsets, mode, gap_mode, thr); + if (tmp_heap) { + mem_heap_free(tmp_heap); + } + return(ret); +} + +/*********************************************************************** +Release the last lock from the transaction's autoinc locks. */ +UNIV_INLINE +void +lock_release_autoinc_last_lock( +/*===========================*/ + ib_vector_t* autoinc_locks) /* in/out: vector of AUTOINC locks */ +{ + ulint last; + lock_t* lock; + + ut_ad(mutex_own(&kernel_mutex)); + ut_a(!ib_vector_is_empty(autoinc_locks)); + + /* The lock to be release must be the last lock acquired. */ + last = ib_vector_size(autoinc_locks) - 1; + lock = ib_vector_get(autoinc_locks, last); + + /* Should have only AUTOINC locks in the vector. */ + ut_a(lock_get_mode(lock) == LOCK_AUTO_INC); + ut_a(lock_get_type(lock) == LOCK_TABLE); + + ut_a(lock->un_member.tab_lock.table != NULL); + + /* This will remove the lock from the trx autoinc_locks too. */ + lock_table_dequeue(lock); +} + +/*********************************************************************** +Release all the transaction's autoinc locks. */ +UNIV_INTERN +void +lock_release_autoinc_locks( +/*=======================*/ + trx_t* trx) /* in/out: transaction */ +{ + ut_ad(mutex_own(&kernel_mutex)); + + ut_a(trx->autoinc_locks != NULL); + + /* We release the locks in the reverse order. This is to + avoid searching the vector for the element to delete at + the lower level. See (lock_table_remove_low()) for details. */ + while (!ib_vector_is_empty(trx->autoinc_locks)) { + + /* lock_table_remove_low() will also remove the lock from + the transaction's autoinc_locks vector. */ + lock_release_autoinc_last_lock(trx->autoinc_locks); + } + + /* Should release all locks. */ + ut_a(ib_vector_is_empty(trx->autoinc_locks)); +} + +/*********************************************************************** +Gets the type of a lock. Non-inline version for using outside of the +lock module. */ +UNIV_INTERN +ulint +lock_get_type( +/*==========*/ + /* out: LOCK_TABLE or LOCK_REC */ + const lock_t* lock) /* in: lock */ +{ + return(lock_get_type_low(lock)); +} + +/*********************************************************************** +Gets the id of the transaction owning a lock. */ +UNIV_INTERN +ullint +lock_get_trx_id( +/*============*/ + /* out: transaction id */ + const lock_t* lock) /* in: lock */ +{ + return(trx_get_id(lock->trx)); +} + +/*********************************************************************** +Gets the mode of a lock in a human readable string. +The string should not be free()'d or modified. */ +UNIV_INTERN +const char* +lock_get_mode_str( +/*==============*/ + /* out: lock mode */ + const lock_t* lock) /* in: lock */ +{ + ibool is_gap_lock; + + is_gap_lock = lock_get_type_low(lock) == LOCK_REC + && lock_rec_get_gap(lock); + + switch (lock_get_mode(lock)) { + case LOCK_S: + if (is_gap_lock) { + return("S,GAP"); + } else { + return("S"); + } + case LOCK_X: + if (is_gap_lock) { + return("X,GAP"); + } else { + return("X"); + } + case LOCK_IS: + if (is_gap_lock) { + return("IS,GAP"); + } else { + return("IS"); + } + case LOCK_IX: + if (is_gap_lock) { + return("IX,GAP"); + } else { + return("IX"); + } + case LOCK_AUTO_INC: + return("AUTO_INC"); + default: + return("UNKNOWN"); + } +} + +/*********************************************************************** +Gets the type of a lock in a human readable string. +The string should not be free()'d or modified. */ +UNIV_INTERN +const char* +lock_get_type_str( +/*==============*/ + /* out: lock type */ + const lock_t* lock) /* in: lock */ +{ + switch (lock_get_type_low(lock)) { + case LOCK_REC: + return("RECORD"); + case LOCK_TABLE: + return("TABLE"); + default: + return("UNKNOWN"); + } +} + +/*********************************************************************** +Gets the table on which the lock is. */ +UNIV_INLINE +dict_table_t* +lock_get_table( +/*===========*/ + /* out: table */ + const lock_t* lock) /* in: lock */ +{ + switch (lock_get_type_low(lock)) { + case LOCK_REC: + return(lock->index->table); + case LOCK_TABLE: + return(lock->un_member.tab_lock.table); + default: + ut_error; + return(NULL); + } +} + +/*********************************************************************** +Gets the id of the table on which the lock is. */ +UNIV_INTERN +ullint +lock_get_table_id( +/*==============*/ + /* out: id of the table */ + const lock_t* lock) /* in: lock */ +{ + dict_table_t* table; + + table = lock_get_table(lock); + + return((ullint)ut_conv_dulint_to_longlong(table->id)); +} + +/*********************************************************************** +Gets the name of the table on which the lock is. +The string should not be free()'d or modified. */ +UNIV_INTERN +const char* +lock_get_table_name( +/*================*/ + /* out: name of the table */ + const lock_t* lock) /* in: lock */ +{ + dict_table_t* table; + + table = lock_get_table(lock); + + return(table->name); +} + +/*********************************************************************** +For a record lock, gets the index on which the lock is. */ +UNIV_INTERN +const dict_index_t* +lock_rec_get_index( +/*===============*/ + /* out: index */ + const lock_t* lock) /* in: lock */ +{ + ut_a(lock_get_type_low(lock) == LOCK_REC); + + return(lock->index); +} + +/*********************************************************************** +For a record lock, gets the name of the index on which the lock is. +The string should not be free()'d or modified. */ +UNIV_INTERN +const char* +lock_rec_get_index_name( +/*====================*/ + /* out: name of the index */ + const lock_t* lock) /* in: lock */ +{ + ut_a(lock_get_type_low(lock) == LOCK_REC); + + return(lock->index->name); +} + +/*********************************************************************** +For a record lock, gets the tablespace number on which the lock is. */ +UNIV_INTERN +ulint +lock_rec_get_space_id( +/*==================*/ + /* out: tablespace number */ + const lock_t* lock) /* in: lock */ +{ + ut_a(lock_get_type_low(lock) == LOCK_REC); + + return(lock->un_member.rec_lock.space); +} + +/*********************************************************************** +For a record lock, gets the page number on which the lock is. */ +UNIV_INTERN +ulint +lock_rec_get_page_no( +/*=================*/ + /* out: page number */ + const lock_t* lock) /* in: lock */ +{ + ut_a(lock_get_type_low(lock) == LOCK_REC); + + return(lock->un_member.rec_lock.page_no); +} diff --git a/storage/xtradb/log/log0log.c b/storage/xtradb/log/log0log.c new file mode 100644 index 00000000000..a41b02c32fa --- /dev/null +++ b/storage/xtradb/log/log0log.c @@ -0,0 +1,3336 @@ +/***************************************************************************** + +Copyright (c) 1995, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/****************************************************** +Database log + +Created 12/9/1995 Heikki Tuuri +*******************************************************/ + +#include "log0log.h" + +#ifdef UNIV_NONINL +#include "log0log.ic" +#endif + +#include "mem0mem.h" +#include "buf0buf.h" +#include "buf0flu.h" +#include "srv0srv.h" +#include "log0recv.h" +#include "fil0fil.h" +#include "dict0boot.h" +#include "srv0srv.h" +#include "srv0start.h" +#include "trx0sys.h" +#include "trx0trx.h" + +/* +General philosophy of InnoDB redo-logs: + +1) Every change to a contents of a data page must be done +through mtr, which in mtr_commit() writes log records +to the InnoDB redo log. + +2) Normally these changes are performed using a mlog_write_ulint() +or similar function. + +3) In some page level operations only a code number of a +c-function and its parameters are written to the log to +reduce the size of the log. + + 3a) You should not add parameters to these kind of functions + (e.g. trx_undo_header_create(), trx_undo_insert_header_reuse()) + + 3b) You should not add such functionality which either change + working when compared with the old or are dependent on data + outside of the page. These kind of functions should implement + self-contained page transformation and it should be unchanged + if you don't have very essential reasons to change log + semantics or format. + +*/ + +/* Current free limit of space 0; protected by the log sys mutex; 0 means +uninitialized */ +UNIV_INTERN ulint log_fsp_current_free_limit = 0; + +/* Global log system variable */ +UNIV_INTERN log_t* log_sys = NULL; + +#ifdef UNIV_DEBUG +UNIV_INTERN ibool log_do_write = TRUE; + +UNIV_INTERN ibool log_debug_writes = FALSE; +#endif /* UNIV_DEBUG */ + +/* These control how often we print warnings if the last checkpoint is too +old */ +UNIV_INTERN ibool log_has_printed_chkp_warning = FALSE; +UNIV_INTERN time_t log_last_warning_time; + +#ifdef UNIV_LOG_ARCHIVE +/* Pointer to this variable is used as the i/o-message when we do i/o to an +archive */ +UNIV_INTERN byte log_archive_io; +#endif /* UNIV_LOG_ARCHIVE */ + +/* A margin for free space in the log buffer before a log entry is catenated */ +#define LOG_BUF_WRITE_MARGIN (4 * OS_FILE_LOG_BLOCK_SIZE) + +/* Margins for free space in the log buffer after a log entry is catenated */ +#define LOG_BUF_FLUSH_RATIO 2 +#define LOG_BUF_FLUSH_MARGIN (LOG_BUF_WRITE_MARGIN + 4 * UNIV_PAGE_SIZE) + +/* Margin for the free space in the smallest log group, before a new query +step which modifies the database, is started */ + +#define LOG_CHECKPOINT_FREE_PER_THREAD (4 * UNIV_PAGE_SIZE) +#define LOG_CHECKPOINT_EXTRA_FREE (8 * UNIV_PAGE_SIZE) + +/* This parameter controls asynchronous making of a new checkpoint; the value +should be bigger than LOG_POOL_PREFLUSH_RATIO_SYNC */ + +#define LOG_POOL_CHECKPOINT_RATIO_ASYNC 32 + +/* This parameter controls synchronous preflushing of modified buffer pages */ +#define LOG_POOL_PREFLUSH_RATIO_SYNC 16 + +/* The same ratio for asynchronous preflushing; this value should be less than +the previous */ +#define LOG_POOL_PREFLUSH_RATIO_ASYNC 8 + +/* Extra margin, in addition to one log file, used in archiving */ +#define LOG_ARCHIVE_EXTRA_MARGIN (4 * UNIV_PAGE_SIZE) + +/* This parameter controls asynchronous writing to the archive */ +#define LOG_ARCHIVE_RATIO_ASYNC 16 + +/* Codes used in unlocking flush latches */ +#define LOG_UNLOCK_NONE_FLUSHED_LOCK 1 +#define LOG_UNLOCK_FLUSH_LOCK 2 + +/* States of an archiving operation */ +#define LOG_ARCHIVE_READ 1 +#define LOG_ARCHIVE_WRITE 2 + +/********************************************************** +Completes a checkpoint write i/o to a log file. */ +static +void +log_io_complete_checkpoint(void); +/*============================*/ +#ifdef UNIV_LOG_ARCHIVE +/********************************************************** +Completes an archiving i/o. */ +static +void +log_io_complete_archive(void); +/*=========================*/ +#endif /* UNIV_LOG_ARCHIVE */ + +/******************************************************************** +Sets the global variable log_fsp_current_free_limit. Also makes a checkpoint, +so that we know that the limit has been written to a log checkpoint field +on disk. */ +UNIV_INTERN +void +log_fsp_current_free_limit_set_and_checkpoint( +/*==========================================*/ + ulint limit) /* in: limit to set */ +{ + ibool success; + + mutex_enter(&(log_sys->mutex)); + + log_fsp_current_free_limit = limit; + + mutex_exit(&(log_sys->mutex)); + + /* Try to make a synchronous checkpoint */ + + success = FALSE; + + while (!success) { + success = log_checkpoint(TRUE, TRUE); + } +} + +/******************************************************************** +Returns the oldest modified block lsn in the pool, or log_sys->lsn if none +exists. */ +static +ib_uint64_t +log_buf_pool_get_oldest_modification(void) +/*======================================*/ +{ + ib_uint64_t lsn; + + ut_ad(mutex_own(&(log_sys->mutex))); + + lsn = buf_pool_get_oldest_modification(); + + if (!lsn) { + + lsn = log_sys->lsn; + } + + return(lsn); +} + +/**************************************************************** +Opens the log for log_write_low. The log must be closed with log_close and +released with log_release. */ +UNIV_INTERN +ib_uint64_t +log_reserve_and_open( +/*=================*/ + /* out: start lsn of the log record */ + ulint len) /* in: length of data to be catenated */ +{ + log_t* log = log_sys; + ulint len_upper_limit; +#ifdef UNIV_LOG_ARCHIVE + ulint archived_lsn_age; + ulint dummy; +#endif /* UNIV_LOG_ARCHIVE */ +#ifdef UNIV_DEBUG + ulint count = 0; +#endif /* UNIV_DEBUG */ + + ut_a(len < log->buf_size / 2); +loop: + mutex_enter(&(log->mutex)); + + /* Calculate an upper limit for the space the string may take in the + log buffer */ + + len_upper_limit = LOG_BUF_WRITE_MARGIN + (5 * len) / 4; + + if (log->buf_free + len_upper_limit > log->buf_size) { + + mutex_exit(&(log->mutex)); + + /* Not enough free space, do a syncronous flush of the log + buffer */ + + log_buffer_flush_to_disk(); + + srv_log_waits++; + + ut_ad(++count < 50); + + goto loop; + } + +#ifdef UNIV_LOG_ARCHIVE + if (log->archiving_state != LOG_ARCH_OFF) { + + archived_lsn_age = log->lsn - log->archived_lsn; + if (archived_lsn_age + len_upper_limit + > log->max_archived_lsn_age) { + /* Not enough free archived space in log groups: do a + synchronous archive write batch: */ + + mutex_exit(&(log->mutex)); + + ut_ad(len_upper_limit <= log->max_archived_lsn_age); + + log_archive_do(TRUE, &dummy); + + ut_ad(++count < 50); + + goto loop; + } + } +#endif /* UNIV_LOG_ARCHIVE */ + +#ifdef UNIV_LOG_DEBUG + log->old_buf_free = log->buf_free; + log->old_lsn = log->lsn; +#endif + return(log->lsn); +} + +/**************************************************************** +Writes to the log the string given. It is assumed that the caller holds the +log mutex. */ +UNIV_INTERN +void +log_write_low( +/*==========*/ + byte* str, /* in: string */ + ulint str_len) /* in: string length */ +{ + log_t* log = log_sys; + ulint len; + ulint data_len; + byte* log_block; + + ut_ad(mutex_own(&(log->mutex))); +part_loop: + /* Calculate a part length */ + + data_len = (log->buf_free % OS_FILE_LOG_BLOCK_SIZE) + str_len; + + if (data_len <= OS_FILE_LOG_BLOCK_SIZE - LOG_BLOCK_TRL_SIZE) { + + /* The string fits within the current log block */ + + len = str_len; + } else { + data_len = OS_FILE_LOG_BLOCK_SIZE - LOG_BLOCK_TRL_SIZE; + + len = OS_FILE_LOG_BLOCK_SIZE + - (log->buf_free % OS_FILE_LOG_BLOCK_SIZE) + - LOG_BLOCK_TRL_SIZE; + } + + ut_memcpy(log->buf + log->buf_free, str, len); + + str_len -= len; + str = str + len; + + log_block = ut_align_down(log->buf + log->buf_free, + OS_FILE_LOG_BLOCK_SIZE); + log_block_set_data_len(log_block, data_len); + + if (data_len == OS_FILE_LOG_BLOCK_SIZE - LOG_BLOCK_TRL_SIZE) { + /* This block became full */ + log_block_set_data_len(log_block, OS_FILE_LOG_BLOCK_SIZE); + log_block_set_checkpoint_no(log_block, + log_sys->next_checkpoint_no); + len += LOG_BLOCK_HDR_SIZE + LOG_BLOCK_TRL_SIZE; + + log->lsn += len; + + /* Initialize the next block header */ + log_block_init(log_block + OS_FILE_LOG_BLOCK_SIZE, log->lsn); + } else { + log->lsn += len; + } + + log->buf_free += len; + + ut_ad(log->buf_free <= log->buf_size); + + if (str_len > 0) { + goto part_loop; + } + + srv_log_write_requests++; +} + +/**************************************************************** +Closes the log. */ +UNIV_INTERN +ib_uint64_t +log_close(void) +/*===========*/ + /* out: lsn */ +{ + byte* log_block; + ulint first_rec_group; + ib_uint64_t oldest_lsn; + ib_uint64_t lsn; + log_t* log = log_sys; + ib_uint64_t checkpoint_age; + + ut_ad(mutex_own(&(log->mutex))); + + lsn = log->lsn; + + log_block = ut_align_down(log->buf + log->buf_free, + OS_FILE_LOG_BLOCK_SIZE); + first_rec_group = log_block_get_first_rec_group(log_block); + + if (first_rec_group == 0) { + /* We initialized a new log block which was not written + full by the current mtr: the next mtr log record group + will start within this block at the offset data_len */ + + log_block_set_first_rec_group( + log_block, log_block_get_data_len(log_block)); + } + + if (log->buf_free > log->max_buf_free) { + + log->check_flush_or_checkpoint = TRUE; + } + + checkpoint_age = lsn - log->last_checkpoint_lsn; + + if (checkpoint_age >= log->log_group_capacity) { + /* TODO: split btr_store_big_rec_extern_fields() into small + steps so that we can release all latches in the middle, and + call log_free_check() to ensure we never write over log written + after the latest checkpoint. In principle, we should split all + big_rec operations, but other operations are smaller. */ + + if (!log_has_printed_chkp_warning + || difftime(time(NULL), log_last_warning_time) > 15) { + + log_has_printed_chkp_warning = TRUE; + log_last_warning_time = time(NULL); + + ut_print_timestamp(stderr); + fprintf(stderr, + " InnoDB: ERROR: the age of the last" + " checkpoint is %lu,\n" + "InnoDB: which exceeds the log group" + " capacity %lu.\n" + "InnoDB: If you are using big" + " BLOB or TEXT rows, you must set the\n" + "InnoDB: combined size of log files" + " at least 10 times bigger than the\n" + "InnoDB: largest such row.\n", + (ulong) checkpoint_age, + (ulong) log->log_group_capacity); + } + } + + if (checkpoint_age <= log->max_modified_age_async) { + + goto function_exit; + } + + oldest_lsn = buf_pool_get_oldest_modification(); + + if (!oldest_lsn + || lsn - oldest_lsn > log->max_modified_age_async + || checkpoint_age > log->max_checkpoint_age_async) { + + log->check_flush_or_checkpoint = TRUE; + } +function_exit: + +#ifdef UNIV_LOG_DEBUG + log_check_log_recs(log->buf + log->old_buf_free, + log->buf_free - log->old_buf_free, log->old_lsn); +#endif + + return(lsn); +} + +#ifdef UNIV_LOG_ARCHIVE +/********************************************************** +Pads the current log block full with dummy log records. Used in producing +consistent archived log files. */ +static +void +log_pad_current_log_block(void) +/*===========================*/ +{ + byte b = MLOG_DUMMY_RECORD; + ulint pad_length; + ulint i; + ib_uint64_t lsn; + + /* We retrieve lsn only because otherwise gcc crashed on HP-UX */ + lsn = log_reserve_and_open(OS_FILE_LOG_BLOCK_SIZE); + + pad_length = OS_FILE_LOG_BLOCK_SIZE + - (log_sys->buf_free % OS_FILE_LOG_BLOCK_SIZE) + - LOG_BLOCK_TRL_SIZE; + + for (i = 0; i < pad_length; i++) { + log_write_low(&b, 1); + } + + lsn = log_sys->lsn; + + log_close(); + log_release(); + + ut_a(lsn % OS_FILE_LOG_BLOCK_SIZE == LOG_BLOCK_HDR_SIZE); +} +#endif /* UNIV_LOG_ARCHIVE */ + +/********************************************************** +Calculates the data capacity of a log group, when the log file headers are not +included. */ +UNIV_INTERN +ulint +log_group_get_capacity( +/*===================*/ + /* out: capacity in bytes */ + log_group_t* group) /* in: log group */ +{ + ut_ad(mutex_own(&(log_sys->mutex))); + + return((group->file_size - LOG_FILE_HDR_SIZE) * group->n_files); +} + +/********************************************************** +Calculates the offset within a log group, when the log file headers are not +included. */ +UNIV_INLINE +ulint +log_group_calc_size_offset( +/*=======================*/ + /* out: size offset (<= offset) */ + ulint offset, /* in: real offset within the log group */ + log_group_t* group) /* in: log group */ +{ + ut_ad(mutex_own(&(log_sys->mutex))); + + return(offset - LOG_FILE_HDR_SIZE * (1 + offset / group->file_size)); +} + +/********************************************************** +Calculates the offset within a log group, when the log file headers are +included. */ +UNIV_INLINE +ulint +log_group_calc_real_offset( +/*=======================*/ + /* out: real offset (>= offset) */ + ulint offset, /* in: size offset within the log group */ + log_group_t* group) /* in: log group */ +{ + ut_ad(mutex_own(&(log_sys->mutex))); + + return(offset + LOG_FILE_HDR_SIZE + * (1 + offset / (group->file_size - LOG_FILE_HDR_SIZE))); +} + +/********************************************************** +Calculates the offset of an lsn within a log group. */ +static +ulint +log_group_calc_lsn_offset( +/*======================*/ + /* out: offset within the log group */ + ib_uint64_t lsn, /* in: lsn, must be within 4 GB of + group->lsn */ + log_group_t* group) /* in: log group */ +{ + ib_uint64_t gr_lsn; + ib_int64_t gr_lsn_size_offset; + ib_int64_t difference; + ib_int64_t group_size; + ib_int64_t offset; + + ut_ad(mutex_own(&(log_sys->mutex))); + + /* If total log file size is > 2 GB we can easily get overflows + with 32-bit integers. Use 64-bit integers instead. */ + + gr_lsn = group->lsn; + + gr_lsn_size_offset = (ib_int64_t) + log_group_calc_size_offset(group->lsn_offset, group); + + group_size = (ib_int64_t) log_group_get_capacity(group); + + if (lsn >= gr_lsn) { + + difference = (ib_int64_t) (lsn - gr_lsn); + } else { + difference = (ib_int64_t) (gr_lsn - lsn); + + difference = difference % group_size; + + difference = group_size - difference; + } + + offset = (gr_lsn_size_offset + difference) % group_size; + + ut_a(offset < (((ib_int64_t) 1) << 32)); /* offset must be < 4 GB */ + + /* fprintf(stderr, + "Offset is %lu gr_lsn_offset is %lu difference is %lu\n", + (ulint)offset,(ulint)gr_lsn_size_offset, (ulint)difference); + */ + + return(log_group_calc_real_offset((ulint)offset, group)); +} + +/*********************************************************************** +Calculates where in log files we find a specified lsn. */ +UNIV_INTERN +ulint +log_calc_where_lsn_is( +/*==================*/ + /* out: log file number */ + ib_int64_t* log_file_offset, /* out: offset in that file + (including the header) */ + ib_uint64_t first_header_lsn, /* in: first log file start + lsn */ + ib_uint64_t lsn, /* in: lsn whose position to + determine */ + ulint n_log_files, /* in: total number of log + files */ + ib_int64_t log_file_size) /* in: log file size + (including the header) */ +{ + ib_int64_t capacity = log_file_size - LOG_FILE_HDR_SIZE; + ulint file_no; + ib_int64_t add_this_many; + + if (lsn < first_header_lsn) { + add_this_many = 1 + (first_header_lsn - lsn) + / (capacity * (ib_int64_t)n_log_files); + lsn += add_this_many + * capacity * (ib_int64_t)n_log_files; + } + + ut_a(lsn >= first_header_lsn); + + file_no = ((ulint)((lsn - first_header_lsn) / capacity)) + % n_log_files; + *log_file_offset = (lsn - first_header_lsn) % capacity; + + *log_file_offset = *log_file_offset + LOG_FILE_HDR_SIZE; + + return(file_no); +} + +/************************************************************ +Sets the field values in group to correspond to a given lsn. For this function +to work, the values must already be correctly initialized to correspond to +some lsn, for instance, a checkpoint lsn. */ +UNIV_INTERN +void +log_group_set_fields( +/*=================*/ + log_group_t* group, /* in: group */ + ib_uint64_t lsn) /* in: lsn for which the values should be + set */ +{ + group->lsn_offset = log_group_calc_lsn_offset(lsn, group); + group->lsn = lsn; +} + +/********************************************************************* +Calculates the recommended highest values for lsn - last_checkpoint_lsn, +lsn - buf_get_oldest_modification(), and lsn - max_archive_lsn_age. */ +static +ibool +log_calc_max_ages(void) +/*===================*/ + /* out: error value FALSE if the smallest log group is + too small to accommodate the number of OS threads in + the database server */ +{ + log_group_t* group; + ulint margin; + ulint free; + ibool success = TRUE; + ulint smallest_capacity; + ulint archive_margin; + ulint smallest_archive_margin; + + ut_ad(!mutex_own(&(log_sys->mutex))); + + mutex_enter(&(log_sys->mutex)); + + group = UT_LIST_GET_FIRST(log_sys->log_groups); + + ut_ad(group); + + smallest_capacity = ULINT_MAX; + smallest_archive_margin = ULINT_MAX; + + while (group) { + if (log_group_get_capacity(group) < smallest_capacity) { + + smallest_capacity = log_group_get_capacity(group); + } + + archive_margin = log_group_get_capacity(group) + - (group->file_size - LOG_FILE_HDR_SIZE) + - LOG_ARCHIVE_EXTRA_MARGIN; + + if (archive_margin < smallest_archive_margin) { + + smallest_archive_margin = archive_margin; + } + + group = UT_LIST_GET_NEXT(log_groups, group); + } + + /* Add extra safety */ + smallest_capacity = smallest_capacity - smallest_capacity / 10; + + /* For each OS thread we must reserve so much free space in the + smallest log group that it can accommodate the log entries produced + by single query steps: running out of free log space is a serious + system error which requires rebooting the database. */ + + free = LOG_CHECKPOINT_FREE_PER_THREAD * (10 + srv_thread_concurrency) + + LOG_CHECKPOINT_EXTRA_FREE; + if (free >= smallest_capacity / 2) { + success = FALSE; + + goto failure; + } else { + margin = smallest_capacity - free; + } + + margin = ut_min(margin, log_sys->adm_checkpoint_interval); + + margin = margin - margin / 10; /* Add still some extra safety */ + + log_sys->log_group_capacity = smallest_capacity; + + log_sys->max_modified_age_async = margin + - margin / LOG_POOL_PREFLUSH_RATIO_ASYNC; + log_sys->max_modified_age_sync = margin + - margin / LOG_POOL_PREFLUSH_RATIO_SYNC; + + log_sys->max_checkpoint_age_async = margin - margin + / LOG_POOL_CHECKPOINT_RATIO_ASYNC; + log_sys->max_checkpoint_age = margin; + +#ifdef UNIV_LOG_ARCHIVE + log_sys->max_archived_lsn_age = smallest_archive_margin; + + log_sys->max_archived_lsn_age_async = smallest_archive_margin + - smallest_archive_margin / LOG_ARCHIVE_RATIO_ASYNC; +#endif /* UNIV_LOG_ARCHIVE */ +failure: + mutex_exit(&(log_sys->mutex)); + + if (!success) { + fprintf(stderr, + "InnoDB: Error: ib_logfiles are too small" + " for innodb_thread_concurrency %lu.\n" + "InnoDB: The combined size of ib_logfiles" + " should be bigger than\n" + "InnoDB: 200 kB * innodb_thread_concurrency.\n" + "InnoDB: To get mysqld to start up, set" + " innodb_thread_concurrency in my.cnf\n" + "InnoDB: to a lower value, for example, to 8." + " After an ERROR-FREE shutdown\n" + "InnoDB: of mysqld you can adjust the size of" + " ib_logfiles, as explained in\n" + "InnoDB: http://dev.mysql.com/doc/refman/5.1/en/" + "adding-and-removing.html\n" + "InnoDB: Cannot continue operation." + " Calling exit(1).\n", + (ulong)srv_thread_concurrency); + + exit(1); + } + + return(success); +} + +/********************************************************** +Initializes the log. */ +UNIV_INTERN +void +log_init(void) +/*==========*/ +{ + byte* buf; + + log_sys = mem_alloc(sizeof(log_t)); + + mutex_create(&log_sys->mutex, SYNC_LOG); + + mutex_enter(&(log_sys->mutex)); + + /* Start the lsn from one log block from zero: this way every + log record has a start lsn != zero, a fact which we will use */ + + log_sys->lsn = LOG_START_LSN; + + ut_a(LOG_BUFFER_SIZE >= 16 * OS_FILE_LOG_BLOCK_SIZE); + ut_a(LOG_BUFFER_SIZE >= 4 * UNIV_PAGE_SIZE); + + buf = mem_alloc(LOG_BUFFER_SIZE + OS_FILE_LOG_BLOCK_SIZE); + log_sys->buf = ut_align(buf, OS_FILE_LOG_BLOCK_SIZE); + + log_sys->buf_size = LOG_BUFFER_SIZE; + + memset(log_sys->buf, '\0', LOG_BUFFER_SIZE); + + log_sys->max_buf_free = log_sys->buf_size / LOG_BUF_FLUSH_RATIO + - LOG_BUF_FLUSH_MARGIN; + log_sys->check_flush_or_checkpoint = TRUE; + UT_LIST_INIT(log_sys->log_groups); + + log_sys->n_log_ios = 0; + + log_sys->n_log_ios_old = log_sys->n_log_ios; + log_sys->last_printout_time = time(NULL); + /*----------------------------*/ + + log_sys->buf_next_to_write = 0; + + log_sys->write_lsn = 0; + log_sys->current_flush_lsn = 0; + log_sys->flushed_to_disk_lsn = 0; + + log_sys->written_to_some_lsn = log_sys->lsn; + log_sys->written_to_all_lsn = log_sys->lsn; + + log_sys->n_pending_writes = 0; + + log_sys->no_flush_event = os_event_create(NULL); + + os_event_set(log_sys->no_flush_event); + + log_sys->one_flushed_event = os_event_create(NULL); + + os_event_set(log_sys->one_flushed_event); + + /*----------------------------*/ + log_sys->adm_checkpoint_interval = ULINT_MAX; + + log_sys->next_checkpoint_no = 0; + log_sys->last_checkpoint_lsn = log_sys->lsn; + log_sys->n_pending_checkpoint_writes = 0; + + rw_lock_create(&log_sys->checkpoint_lock, SYNC_NO_ORDER_CHECK); + + log_sys->checkpoint_buf + = ut_align(mem_alloc(2 * OS_FILE_LOG_BLOCK_SIZE), + OS_FILE_LOG_BLOCK_SIZE); + memset(log_sys->checkpoint_buf, '\0', OS_FILE_LOG_BLOCK_SIZE); + /*----------------------------*/ + +#ifdef UNIV_LOG_ARCHIVE + /* Under MySQL, log archiving is always off */ + log_sys->archiving_state = LOG_ARCH_OFF; + log_sys->archived_lsn = log_sys->lsn; + log_sys->next_archived_lsn = 0; + + log_sys->n_pending_archive_ios = 0; + + rw_lock_create(&log_sys->archive_lock, SYNC_NO_ORDER_CHECK); + + log_sys->archive_buf = NULL; + + /* ut_align( + ut_malloc(LOG_ARCHIVE_BUF_SIZE + + OS_FILE_LOG_BLOCK_SIZE), + OS_FILE_LOG_BLOCK_SIZE); */ + log_sys->archive_buf_size = 0; + + /* memset(log_sys->archive_buf, '\0', LOG_ARCHIVE_BUF_SIZE); */ + + log_sys->archiving_on = os_event_create(NULL); +#endif /* UNIV_LOG_ARCHIVE */ + + /*----------------------------*/ + + log_block_init(log_sys->buf, log_sys->lsn); + log_block_set_first_rec_group(log_sys->buf, LOG_BLOCK_HDR_SIZE); + + log_sys->buf_free = LOG_BLOCK_HDR_SIZE; + log_sys->lsn = LOG_START_LSN + LOG_BLOCK_HDR_SIZE; + + mutex_exit(&(log_sys->mutex)); + +#ifdef UNIV_LOG_DEBUG + recv_sys_create(); + recv_sys_init(FALSE, buf_pool_get_curr_size()); + + recv_sys->parse_start_lsn = log_sys->lsn; + recv_sys->scanned_lsn = log_sys->lsn; + recv_sys->scanned_checkpoint_no = 0; + recv_sys->recovered_lsn = log_sys->lsn; + recv_sys->limit_lsn = IB_ULONGLONG_MAX; +#endif +} + +/********************************************************************** +Inits a log group to the log system. */ +UNIV_INTERN +void +log_group_init( +/*===========*/ + ulint id, /* in: group id */ + ulint n_files, /* in: number of log files */ + ulint file_size, /* in: log file size in bytes */ + ulint space_id, /* in: space id of the file space + which contains the log files of this + group */ + ulint archive_space_id __attribute__((unused))) + /* in: space id of the file space + which contains some archived log + files for this group; currently, only + for the first log group this is + used */ +{ + ulint i; + + log_group_t* group; + + group = mem_alloc(sizeof(log_group_t)); + + group->id = id; + group->n_files = n_files; + group->file_size = file_size; + group->space_id = space_id; + group->state = LOG_GROUP_OK; + group->lsn = LOG_START_LSN; + group->lsn_offset = LOG_FILE_HDR_SIZE; + group->n_pending_writes = 0; + + group->file_header_bufs = mem_alloc(sizeof(byte*) * n_files); +#ifdef UNIV_LOG_ARCHIVE + group->archive_file_header_bufs = mem_alloc(sizeof(byte*) * n_files); +#endif /* UNIV_LOG_ARCHIVE */ + + for (i = 0; i < n_files; i++) { + *(group->file_header_bufs + i) = ut_align( + mem_alloc(LOG_FILE_HDR_SIZE + OS_FILE_LOG_BLOCK_SIZE), + OS_FILE_LOG_BLOCK_SIZE); + + memset(*(group->file_header_bufs + i), '\0', + LOG_FILE_HDR_SIZE); + +#ifdef UNIV_LOG_ARCHIVE + *(group->archive_file_header_bufs + i) = ut_align( + mem_alloc(LOG_FILE_HDR_SIZE + OS_FILE_LOG_BLOCK_SIZE), + OS_FILE_LOG_BLOCK_SIZE); + memset(*(group->archive_file_header_bufs + i), '\0', + LOG_FILE_HDR_SIZE); +#endif /* UNIV_LOG_ARCHIVE */ + } + +#ifdef UNIV_LOG_ARCHIVE + group->archive_space_id = archive_space_id; + + group->archived_file_no = 0; + group->archived_offset = 0; +#endif /* UNIV_LOG_ARCHIVE */ + + group->checkpoint_buf = ut_align( + mem_alloc(2 * OS_FILE_LOG_BLOCK_SIZE), OS_FILE_LOG_BLOCK_SIZE); + + memset(group->checkpoint_buf, '\0', OS_FILE_LOG_BLOCK_SIZE); + + UT_LIST_ADD_LAST(log_groups, log_sys->log_groups, group); + + ut_a(log_calc_max_ages()); +} + +/********************************************************************** +Does the unlockings needed in flush i/o completion. */ +UNIV_INLINE +void +log_flush_do_unlocks( +/*=================*/ + ulint code) /* in: any ORed combination of LOG_UNLOCK_FLUSH_LOCK + and LOG_UNLOCK_NONE_FLUSHED_LOCK */ +{ + ut_ad(mutex_own(&(log_sys->mutex))); + + /* NOTE that we must own the log mutex when doing the setting of the + events: this is because transactions will wait for these events to + be set, and at that moment the log flush they were waiting for must + have ended. If the log mutex were not reserved here, the i/o-thread + calling this function might be preempted for a while, and when it + resumed execution, it might be that a new flush had been started, and + this function would erroneously signal the NEW flush as completed. + Thus, the changes in the state of these events are performed + atomically in conjunction with the changes in the state of + log_sys->n_pending_writes etc. */ + + if (code & LOG_UNLOCK_NONE_FLUSHED_LOCK) { + os_event_set(log_sys->one_flushed_event); + } + + if (code & LOG_UNLOCK_FLUSH_LOCK) { + os_event_set(log_sys->no_flush_event); + } +} + +/********************************************************************** +Checks if a flush is completed for a log group and does the completion +routine if yes. */ +UNIV_INLINE +ulint +log_group_check_flush_completion( +/*=============================*/ + /* out: LOG_UNLOCK_NONE_FLUSHED_LOCK or 0 */ + log_group_t* group) /* in: log group */ +{ + ut_ad(mutex_own(&(log_sys->mutex))); + + if (!log_sys->one_flushed && group->n_pending_writes == 0) { +#ifdef UNIV_DEBUG + if (log_debug_writes) { + fprintf(stderr, + "Log flushed first to group %lu\n", + (ulong) group->id); + } +#endif /* UNIV_DEBUG */ + log_sys->written_to_some_lsn = log_sys->write_lsn; + log_sys->one_flushed = TRUE; + + return(LOG_UNLOCK_NONE_FLUSHED_LOCK); + } + +#ifdef UNIV_DEBUG + if (log_debug_writes && (group->n_pending_writes == 0)) { + + fprintf(stderr, "Log flushed to group %lu\n", + (ulong) group->id); + } +#endif /* UNIV_DEBUG */ + return(0); +} + +/********************************************************** +Checks if a flush is completed and does the completion routine if yes. */ +static +ulint +log_sys_check_flush_completion(void) +/*================================*/ + /* out: LOG_UNLOCK_FLUSH_LOCK or 0 */ +{ + ulint move_start; + ulint move_end; + + ut_ad(mutex_own(&(log_sys->mutex))); + + if (log_sys->n_pending_writes == 0) { + + log_sys->written_to_all_lsn = log_sys->write_lsn; + log_sys->buf_next_to_write = log_sys->write_end_offset; + + if (log_sys->write_end_offset > log_sys->max_buf_free / 2) { + /* Move the log buffer content to the start of the + buffer */ + + move_start = ut_calc_align_down( + log_sys->write_end_offset, + OS_FILE_LOG_BLOCK_SIZE); + move_end = ut_calc_align(log_sys->buf_free, + OS_FILE_LOG_BLOCK_SIZE); + + ut_memmove(log_sys->buf, log_sys->buf + move_start, + move_end - move_start); + log_sys->buf_free -= move_start; + + log_sys->buf_next_to_write -= move_start; + } + + return(LOG_UNLOCK_FLUSH_LOCK); + } + + return(0); +} + +/********************************************************** +Completes an i/o to a log file. */ +UNIV_INTERN +void +log_io_complete( +/*============*/ + log_group_t* group) /* in: log group or a dummy pointer */ +{ + ulint unlock; + +#ifdef UNIV_LOG_ARCHIVE + if ((byte*)group == &log_archive_io) { + /* It was an archive write */ + + log_io_complete_archive(); + + return; + } +#endif /* UNIV_LOG_ARCHIVE */ + + if ((ulint)group & 0x1UL) { + /* It was a checkpoint write */ + group = (log_group_t*)((ulint)group - 1); + + if (srv_unix_file_flush_method != SRV_UNIX_O_DSYNC + && srv_unix_file_flush_method != SRV_UNIX_NOSYNC) { + + fil_flush(group->space_id); + } + +#ifdef UNIV_DEBUG + if (log_debug_writes) { + fprintf(stderr, + "Checkpoint info written to group %lu\n", + group->id); + } +#endif /* UNIV_DEBUG */ + log_io_complete_checkpoint(); + + return; + } + + ut_error; /* We currently use synchronous writing of the + logs and cannot end up here! */ + + if (srv_unix_file_flush_method != SRV_UNIX_O_DSYNC + && srv_unix_file_flush_method != SRV_UNIX_NOSYNC + && srv_flush_log_at_trx_commit != 2) { + + fil_flush(group->space_id); + } + + mutex_enter(&(log_sys->mutex)); + + ut_a(group->n_pending_writes > 0); + ut_a(log_sys->n_pending_writes > 0); + + group->n_pending_writes--; + log_sys->n_pending_writes--; + + unlock = log_group_check_flush_completion(group); + unlock = unlock | log_sys_check_flush_completion(); + + log_flush_do_unlocks(unlock); + + mutex_exit(&(log_sys->mutex)); +} + +/********************************************************** +Writes a log file header to a log file space. */ +static +void +log_group_file_header_flush( +/*========================*/ + log_group_t* group, /* in: log group */ + ulint nth_file, /* in: header to the nth file in the + log file space */ + ib_uint64_t start_lsn) /* in: log file data starts at this + lsn */ +{ + byte* buf; + ulint dest_offset; + + ut_ad(mutex_own(&(log_sys->mutex))); + ut_a(nth_file < group->n_files); + + buf = *(group->file_header_bufs + nth_file); + + mach_write_to_4(buf + LOG_GROUP_ID, group->id); + mach_write_ull(buf + LOG_FILE_START_LSN, start_lsn); + + /* Wipe over possible label of ibbackup --restore */ + memcpy(buf + LOG_FILE_WAS_CREATED_BY_HOT_BACKUP, " ", 4); + + dest_offset = nth_file * group->file_size; + +#ifdef UNIV_DEBUG + if (log_debug_writes) { + fprintf(stderr, + "Writing log file header to group %lu file %lu\n", + (ulong) group->id, (ulong) nth_file); + } +#endif /* UNIV_DEBUG */ + if (log_do_write) { + log_sys->n_log_ios++; + + srv_os_log_pending_writes++; + + fil_io(OS_FILE_WRITE | OS_FILE_LOG, TRUE, group->space_id, 0, + dest_offset / UNIV_PAGE_SIZE, + dest_offset % UNIV_PAGE_SIZE, + OS_FILE_LOG_BLOCK_SIZE, + buf, group); + + srv_os_log_pending_writes--; + } +} + +/********************************************************** +Stores a 4-byte checksum to the trailer checksum field of a log block +before writing it to a log file. This checksum is used in recovery to +check the consistency of a log block. */ +static +void +log_block_store_checksum( +/*=====================*/ + byte* block) /* in/out: pointer to a log block */ +{ + log_block_set_checksum(block, log_block_calc_checksum(block)); +} + +/********************************************************** +Writes a buffer to a log file group. */ +UNIV_INTERN +void +log_group_write_buf( +/*================*/ + log_group_t* group, /* in: log group */ + byte* buf, /* in: buffer */ + ulint len, /* in: buffer len; must be divisible + by OS_FILE_LOG_BLOCK_SIZE */ + ib_uint64_t start_lsn, /* in: start lsn of the buffer; must + be divisible by + OS_FILE_LOG_BLOCK_SIZE */ + ulint new_data_offset)/* in: start offset of new data in + buf: this parameter is used to decide + if we have to write a new log file + header */ +{ + ulint write_len; + ibool write_header; + ulint next_offset; + ulint i; + + ut_ad(mutex_own(&(log_sys->mutex))); + ut_a(len % OS_FILE_LOG_BLOCK_SIZE == 0); + ut_a(((ulint) start_lsn) % OS_FILE_LOG_BLOCK_SIZE == 0); + + if (new_data_offset == 0) { + write_header = TRUE; + } else { + write_header = FALSE; + } +loop: + if (len == 0) { + + return; + } + + next_offset = log_group_calc_lsn_offset(start_lsn, group); + + if ((next_offset % group->file_size == LOG_FILE_HDR_SIZE) + && write_header) { + /* We start to write a new log file instance in the group */ + + log_group_file_header_flush(group, + next_offset / group->file_size, + start_lsn); + srv_os_log_written+= OS_FILE_LOG_BLOCK_SIZE; + srv_log_writes++; + } + + if ((next_offset % group->file_size) + len > group->file_size) { + + write_len = group->file_size + - (next_offset % group->file_size); + } else { + write_len = len; + } + +#ifdef UNIV_DEBUG + if (log_debug_writes) { + + fprintf(stderr, + "Writing log file segment to group %lu" + " offset %lu len %lu\n" + "start lsn %llu\n" + "First block n:o %lu last block n:o %lu\n", + (ulong) group->id, (ulong) next_offset, + (ulong) write_len, + start_lsn, + (ulong) log_block_get_hdr_no(buf), + (ulong) log_block_get_hdr_no( + buf + write_len - OS_FILE_LOG_BLOCK_SIZE)); + ut_a(log_block_get_hdr_no(buf) + == log_block_convert_lsn_to_no(start_lsn)); + + for (i = 0; i < write_len / OS_FILE_LOG_BLOCK_SIZE; i++) { + + ut_a(log_block_get_hdr_no(buf) + i + == log_block_get_hdr_no( + buf + i * OS_FILE_LOG_BLOCK_SIZE)); + } + } +#endif /* UNIV_DEBUG */ + /* Calculate the checksums for each log block and write them to + the trailer fields of the log blocks */ + + for (i = 0; i < write_len / OS_FILE_LOG_BLOCK_SIZE; i++) { + log_block_store_checksum(buf + i * OS_FILE_LOG_BLOCK_SIZE); + } + + if (log_do_write) { + log_sys->n_log_ios++; + + srv_os_log_pending_writes++; + + fil_io(OS_FILE_WRITE | OS_FILE_LOG, TRUE, group->space_id, 0, + next_offset / UNIV_PAGE_SIZE, + next_offset % UNIV_PAGE_SIZE, write_len, buf, group); + + srv_os_log_pending_writes--; + + srv_os_log_written+= write_len; + srv_log_writes++; + } + + if (write_len < len) { + start_lsn += write_len; + len -= write_len; + buf += write_len; + + write_header = TRUE; + + goto loop; + } +} + +/********************************************************** +This function is called, e.g., when a transaction wants to commit. It checks +that the log has been written to the log file up to the last log entry written +by the transaction. If there is a flush running, it waits and checks if the +flush flushed enough. If not, starts a new flush. */ +UNIV_INTERN +void +log_write_up_to( +/*============*/ + ib_uint64_t lsn, /* in: log sequence number up to which + the log should be written, + IB_ULONGLONG_MAX if not specified */ + ulint wait, /* in: LOG_NO_WAIT, LOG_WAIT_ONE_GROUP, + or LOG_WAIT_ALL_GROUPS */ + ibool flush_to_disk) + /* in: TRUE if we want the written log + also to be flushed to disk */ +{ + log_group_t* group; + ulint start_offset; + ulint end_offset; + ulint area_start; + ulint area_end; +#ifdef UNIV_DEBUG + ulint loop_count = 0; +#endif /* UNIV_DEBUG */ + ulint unlock; + + if (recv_no_ibuf_operations) { + /* Recovery is running and no operations on the log files are + allowed yet (the variable name .._no_ibuf_.. is misleading) */ + + return; + } + +loop: +#ifdef UNIV_DEBUG + loop_count++; + + ut_ad(loop_count < 5); + +# if 0 + if (loop_count > 2) { + fprintf(stderr, "Log loop count %lu\n", loop_count); + } +# endif +#endif + + mutex_enter(&(log_sys->mutex)); + + if (flush_to_disk + && log_sys->flushed_to_disk_lsn >= lsn) { + + mutex_exit(&(log_sys->mutex)); + + return; + } + + if (!flush_to_disk + && (log_sys->written_to_all_lsn >= lsn + || (log_sys->written_to_some_lsn >= lsn + && wait != LOG_WAIT_ALL_GROUPS))) { + + mutex_exit(&(log_sys->mutex)); + + return; + } + + if (log_sys->n_pending_writes > 0) { + /* A write (+ possibly flush to disk) is running */ + + if (flush_to_disk + && log_sys->current_flush_lsn >= lsn) { + /* The write + flush will write enough: wait for it to + complete */ + + goto do_waits; + } + + if (!flush_to_disk + && log_sys->write_lsn >= lsn) { + /* The write will write enough: wait for it to + complete */ + + goto do_waits; + } + + mutex_exit(&(log_sys->mutex)); + + /* Wait for the write to complete and try to start a new + write */ + + os_event_wait(log_sys->no_flush_event); + + goto loop; + } + + if (!flush_to_disk + && log_sys->buf_free == log_sys->buf_next_to_write) { + /* Nothing to write and no flush to disk requested */ + + mutex_exit(&(log_sys->mutex)); + + return; + } + +#ifdef UNIV_DEBUG + if (log_debug_writes) { + fprintf(stderr, + "Writing log from %llu up to lsn %llu\n", + log_sys->written_to_all_lsn, + log_sys->lsn); + } +#endif /* UNIV_DEBUG */ + log_sys->n_pending_writes++; + + group = UT_LIST_GET_FIRST(log_sys->log_groups); + group->n_pending_writes++; /* We assume here that we have only + one log group! */ + + os_event_reset(log_sys->no_flush_event); + os_event_reset(log_sys->one_flushed_event); + + start_offset = log_sys->buf_next_to_write; + end_offset = log_sys->buf_free; + + area_start = ut_calc_align_down(start_offset, OS_FILE_LOG_BLOCK_SIZE); + area_end = ut_calc_align(end_offset, OS_FILE_LOG_BLOCK_SIZE); + + ut_ad(area_end - area_start > 0); + + log_sys->write_lsn = log_sys->lsn; + + if (flush_to_disk) { + log_sys->current_flush_lsn = log_sys->lsn; + } + + log_sys->one_flushed = FALSE; + + log_block_set_flush_bit(log_sys->buf + area_start, TRUE); + log_block_set_checkpoint_no( + log_sys->buf + area_end - OS_FILE_LOG_BLOCK_SIZE, + log_sys->next_checkpoint_no); + + /* Copy the last, incompletely written, log block a log block length + up, so that when the flush operation writes from the log buffer, the + segment to write will not be changed by writers to the log */ + + ut_memcpy(log_sys->buf + area_end, + log_sys->buf + area_end - OS_FILE_LOG_BLOCK_SIZE, + OS_FILE_LOG_BLOCK_SIZE); + + log_sys->buf_free += OS_FILE_LOG_BLOCK_SIZE; + log_sys->write_end_offset = log_sys->buf_free; + + group = UT_LIST_GET_FIRST(log_sys->log_groups); + + /* Do the write to the log files */ + + while (group) { + log_group_write_buf( + group, log_sys->buf + area_start, + area_end - area_start, + ut_uint64_align_down(log_sys->written_to_all_lsn, + OS_FILE_LOG_BLOCK_SIZE), + start_offset - area_start); + + log_group_set_fields(group, log_sys->write_lsn); + + group = UT_LIST_GET_NEXT(log_groups, group); + } + + mutex_exit(&(log_sys->mutex)); + + if (srv_unix_file_flush_method == SRV_UNIX_O_DSYNC) { + /* O_DSYNC means the OS did not buffer the log file at all: + so we have also flushed to disk what we have written */ + + log_sys->flushed_to_disk_lsn = log_sys->write_lsn; + + } else if (flush_to_disk) { + + group = UT_LIST_GET_FIRST(log_sys->log_groups); + + fil_flush(group->space_id); + log_sys->flushed_to_disk_lsn = log_sys->write_lsn; + } + + mutex_enter(&(log_sys->mutex)); + + group = UT_LIST_GET_FIRST(log_sys->log_groups); + + ut_a(group->n_pending_writes == 1); + ut_a(log_sys->n_pending_writes == 1); + + group->n_pending_writes--; + log_sys->n_pending_writes--; + + unlock = log_group_check_flush_completion(group); + unlock = unlock | log_sys_check_flush_completion(); + + log_flush_do_unlocks(unlock); + + mutex_exit(&(log_sys->mutex)); + + return; + +do_waits: + mutex_exit(&(log_sys->mutex)); + + switch (wait) { + case LOG_WAIT_ONE_GROUP: + os_event_wait(log_sys->one_flushed_event); + break; + case LOG_WAIT_ALL_GROUPS: + os_event_wait(log_sys->no_flush_event); + break; +#ifdef UNIV_DEBUG + case LOG_NO_WAIT: + break; + default: + ut_error; +#endif /* UNIV_DEBUG */ + } +} + +/******************************************************************** +Does a syncronous flush of the log buffer to disk. */ +UNIV_INTERN +void +log_buffer_flush_to_disk(void) +/*==========================*/ +{ + ib_uint64_t lsn; + + mutex_enter(&(log_sys->mutex)); + + lsn = log_sys->lsn; + + mutex_exit(&(log_sys->mutex)); + + log_write_up_to(lsn, LOG_WAIT_ALL_GROUPS, TRUE); +} + +/******************************************************************** +Flush the log buffer. Force it to disk depending on the value of +innodb_flush_log_at_trx_commit. */ +UNIV_INTERN +void +log_buffer_flush_maybe_sync(void) +/*=============================*/ +{ + ib_uint64_t lsn; + + mutex_enter(&(log_sys->mutex)); + + lsn = log_sys->lsn; + + mutex_exit(&(log_sys->mutex)); + + /* Force log buffer to disk when innodb_flush_log_at_trx_commit = 1. */ + log_write_up_to(lsn, LOG_WAIT_ALL_GROUPS, + srv_flush_log_at_trx_commit == 1 ? TRUE : FALSE); +} +/******************************************************************** +Tries to establish a big enough margin of free space in the log buffer, such +that a new log entry can be catenated without an immediate need for a flush. */ +static +void +log_flush_margin(void) +/*==================*/ +{ + log_t* log = log_sys; + ib_uint64_t lsn = 0; + + mutex_enter(&(log->mutex)); + + if (log->buf_free > log->max_buf_free) { + + if (log->n_pending_writes > 0) { + /* A flush is running: hope that it will provide enough + free space */ + } else { + lsn = log->lsn; + } + } + + mutex_exit(&(log->mutex)); + + if (lsn) { + log_write_up_to(lsn, LOG_NO_WAIT, FALSE); + } +} + +/******************************************************************** +Advances the smallest lsn for which there are unflushed dirty blocks in the +buffer pool. NOTE: this function may only be called if the calling thread owns +no synchronization objects! */ +UNIV_INTERN +ibool +log_preflush_pool_modified_pages( +/*=============================*/ + /* out: FALSE if there was a + flush batch of the same type + running, which means that we + could not start this flush + batch */ + ib_uint64_t new_oldest, /* in: try to advance + oldest_modified_lsn at least + to this lsn */ + ibool sync) /* in: TRUE if synchronous + operation is desired */ +{ + ulint n_pages; + + if (recv_recovery_on) { + /* If the recovery is running, we must first apply all + log records to their respective file pages to get the + right modify lsn values to these pages: otherwise, there + might be pages on disk which are not yet recovered to the + current lsn, and even after calling this function, we could + not know how up-to-date the disk version of the database is, + and we could not make a new checkpoint on the basis of the + info on the buffer pool only. */ + + recv_apply_hashed_log_recs(TRUE); + } + + n_pages = buf_flush_batch(BUF_FLUSH_LIST, ULINT_MAX, new_oldest); + + if (sync) { + buf_flush_wait_batch_end(BUF_FLUSH_LIST); + } + + if (n_pages == ULINT_UNDEFINED) { + + return(FALSE); + } + + return(TRUE); +} + +/********************************************************** +Completes a checkpoint. */ +static +void +log_complete_checkpoint(void) +/*=========================*/ +{ + ut_ad(mutex_own(&(log_sys->mutex))); + ut_ad(log_sys->n_pending_checkpoint_writes == 0); + + log_sys->next_checkpoint_no++; + + log_sys->last_checkpoint_lsn = log_sys->next_checkpoint_lsn; + + rw_lock_x_unlock_gen(&(log_sys->checkpoint_lock), LOG_CHECKPOINT); +} + +/********************************************************** +Completes an asynchronous checkpoint info write i/o to a log file. */ +static +void +log_io_complete_checkpoint(void) +/*============================*/ +{ + mutex_enter(&(log_sys->mutex)); + + ut_ad(log_sys->n_pending_checkpoint_writes > 0); + + log_sys->n_pending_checkpoint_writes--; + + if (log_sys->n_pending_checkpoint_writes == 0) { + log_complete_checkpoint(); + } + + mutex_exit(&(log_sys->mutex)); +} + +/*********************************************************************** +Writes info to a checkpoint about a log group. */ +static +void +log_checkpoint_set_nth_group_info( +/*==============================*/ + byte* buf, /* in: buffer for checkpoint info */ + ulint n, /* in: nth slot */ + ulint file_no,/* in: archived file number */ + ulint offset) /* in: archived file offset */ +{ + ut_ad(n < LOG_MAX_N_GROUPS); + + mach_write_to_4(buf + LOG_CHECKPOINT_GROUP_ARRAY + + 8 * n + LOG_CHECKPOINT_ARCHIVED_FILE_NO, file_no); + mach_write_to_4(buf + LOG_CHECKPOINT_GROUP_ARRAY + + 8 * n + LOG_CHECKPOINT_ARCHIVED_OFFSET, offset); +} + +/*********************************************************************** +Gets info from a checkpoint about a log group. */ +UNIV_INTERN +void +log_checkpoint_get_nth_group_info( +/*==============================*/ + byte* buf, /* in: buffer containing checkpoint info */ + ulint n, /* in: nth slot */ + ulint* file_no,/* out: archived file number */ + ulint* offset) /* out: archived file offset */ +{ + ut_ad(n < LOG_MAX_N_GROUPS); + + *file_no = mach_read_from_4(buf + LOG_CHECKPOINT_GROUP_ARRAY + + 8 * n + LOG_CHECKPOINT_ARCHIVED_FILE_NO); + *offset = mach_read_from_4(buf + LOG_CHECKPOINT_GROUP_ARRAY + + 8 * n + LOG_CHECKPOINT_ARCHIVED_OFFSET); +} + +/********************************************************** +Writes the checkpoint info to a log group header. */ +static +void +log_group_checkpoint( +/*=================*/ + log_group_t* group) /* in: log group */ +{ + log_group_t* group2; +#ifdef UNIV_LOG_ARCHIVE + ib_uint64_t archived_lsn; + ib_uint64_t next_archived_lsn; +#endif /* UNIV_LOG_ARCHIVE */ + ulint write_offset; + ulint fold; + byte* buf; + ulint i; + + ut_ad(mutex_own(&(log_sys->mutex))); +#if LOG_CHECKPOINT_SIZE > OS_FILE_LOG_BLOCK_SIZE +# error "LOG_CHECKPOINT_SIZE > OS_FILE_LOG_BLOCK_SIZE" +#endif + + buf = group->checkpoint_buf; + + mach_write_ull(buf + LOG_CHECKPOINT_NO, log_sys->next_checkpoint_no); + mach_write_ull(buf + LOG_CHECKPOINT_LSN, log_sys->next_checkpoint_lsn); + + mach_write_to_4(buf + LOG_CHECKPOINT_OFFSET, + log_group_calc_lsn_offset( + log_sys->next_checkpoint_lsn, group)); + + mach_write_to_4(buf + LOG_CHECKPOINT_LOG_BUF_SIZE, log_sys->buf_size); + +#ifdef UNIV_LOG_ARCHIVE + if (log_sys->archiving_state == LOG_ARCH_OFF) { + archived_lsn = IB_ULONGLONG_MAX; + } else { + archived_lsn = log_sys->archived_lsn; + + if (archived_lsn != log_sys->next_archived_lsn) { + next_archived_lsn = log_sys->next_archived_lsn; + /* For debugging only */ + } + } + + mach_write_ull(buf + LOG_CHECKPOINT_ARCHIVED_LSN, archived_lsn); +#else /* UNIV_LOG_ARCHIVE */ + mach_write_ull(buf + LOG_CHECKPOINT_ARCHIVED_LSN, IB_ULONGLONG_MAX); +#endif /* UNIV_LOG_ARCHIVE */ + + for (i = 0; i < LOG_MAX_N_GROUPS; i++) { + log_checkpoint_set_nth_group_info(buf, i, 0, 0); + } + + group2 = UT_LIST_GET_FIRST(log_sys->log_groups); + + while (group2) { + log_checkpoint_set_nth_group_info(buf, group2->id, +#ifdef UNIV_LOG_ARCHIVE + group2->archived_file_no, + group2->archived_offset +#else /* UNIV_LOG_ARCHIVE */ + 0, 0 +#endif /* UNIV_LOG_ARCHIVE */ + ); + + group2 = UT_LIST_GET_NEXT(log_groups, group2); + } + + fold = ut_fold_binary(buf, LOG_CHECKPOINT_CHECKSUM_1); + mach_write_to_4(buf + LOG_CHECKPOINT_CHECKSUM_1, fold); + + fold = ut_fold_binary(buf + LOG_CHECKPOINT_LSN, + LOG_CHECKPOINT_CHECKSUM_2 - LOG_CHECKPOINT_LSN); + mach_write_to_4(buf + LOG_CHECKPOINT_CHECKSUM_2, fold); + + /* Starting from InnoDB-3.23.50, we also write info on allocated + size in the tablespace */ + + mach_write_to_4(buf + LOG_CHECKPOINT_FSP_FREE_LIMIT, + log_fsp_current_free_limit); + + mach_write_to_4(buf + LOG_CHECKPOINT_FSP_MAGIC_N, + LOG_CHECKPOINT_FSP_MAGIC_N_VAL); + + /* We alternate the physical place of the checkpoint info in the first + log file */ + + if ((log_sys->next_checkpoint_no & 1) == 0) { + write_offset = LOG_CHECKPOINT_1; + } else { + write_offset = LOG_CHECKPOINT_2; + } + + if (log_do_write) { + if (log_sys->n_pending_checkpoint_writes == 0) { + + rw_lock_x_lock_gen(&(log_sys->checkpoint_lock), + LOG_CHECKPOINT); + } + + log_sys->n_pending_checkpoint_writes++; + + log_sys->n_log_ios++; + + /* We send as the last parameter the group machine address + added with 1, as we want to distinguish between a normal log + file write and a checkpoint field write */ + + fil_io(OS_FILE_WRITE | OS_FILE_LOG, FALSE, group->space_id, 0, + write_offset / UNIV_PAGE_SIZE, + write_offset % UNIV_PAGE_SIZE, + OS_FILE_LOG_BLOCK_SIZE, + buf, ((byte*)group + 1)); + + ut_ad(((ulint)group & 0x1UL) == 0); + } +} + +#ifdef UNIV_HOTBACKUP +/********************************************************** +Writes info to a buffer of a log group when log files are created in +backup restoration. */ +UNIV_INTERN +void +log_reset_first_header_and_checkpoint( +/*==================================*/ + byte* hdr_buf,/* in: buffer which will be written to the + start of the first log file */ + ib_uint64_t start) /* in: lsn of the start of the first log file; + we pretend that there is a checkpoint at + start + LOG_BLOCK_HDR_SIZE */ +{ + ulint fold; + byte* buf; + ib_uint64_t lsn; + + mach_write_to_4(hdr_buf + LOG_GROUP_ID, 0); + mach_write_ull(hdr_buf + LOG_FILE_START_LSN, start); + + lsn = start + LOG_BLOCK_HDR_SIZE; + + /* Write the label of ibbackup --restore */ + strcpy((char*) hdr_buf + LOG_FILE_WAS_CREATED_BY_HOT_BACKUP, + "ibbackup "); + ut_sprintf_timestamp((char*) hdr_buf + + (LOG_FILE_WAS_CREATED_BY_HOT_BACKUP + + (sizeof "ibbackup ") - 1)); + buf = hdr_buf + LOG_CHECKPOINT_1; + + mach_write_ull(buf + LOG_CHECKPOINT_NO, 0); + mach_write_ull(buf + LOG_CHECKPOINT_LSN, lsn); + + mach_write_to_4(buf + LOG_CHECKPOINT_OFFSET, + LOG_FILE_HDR_SIZE + LOG_BLOCK_HDR_SIZE); + + mach_write_to_4(buf + LOG_CHECKPOINT_LOG_BUF_SIZE, 2 * 1024 * 1024); + + mach_write_ull(buf + LOG_CHECKPOINT_ARCHIVED_LSN, IB_ULONGLONG_MAX); + + fold = ut_fold_binary(buf, LOG_CHECKPOINT_CHECKSUM_1); + mach_write_to_4(buf + LOG_CHECKPOINT_CHECKSUM_1, fold); + + fold = ut_fold_binary(buf + LOG_CHECKPOINT_LSN, + LOG_CHECKPOINT_CHECKSUM_2 - LOG_CHECKPOINT_LSN); + mach_write_to_4(buf + LOG_CHECKPOINT_CHECKSUM_2, fold); + + /* Starting from InnoDB-3.23.50, we should also write info on + allocated size in the tablespace, but unfortunately we do not + know it here */ +} +#endif /* UNIV_HOTBACKUP */ + +/********************************************************** +Reads a checkpoint info from a log group header to log_sys->checkpoint_buf. */ +UNIV_INTERN +void +log_group_read_checkpoint_info( +/*===========================*/ + log_group_t* group, /* in: log group */ + ulint field) /* in: LOG_CHECKPOINT_1 or LOG_CHECKPOINT_2 */ +{ + ut_ad(mutex_own(&(log_sys->mutex))); + + log_sys->n_log_ios++; + + fil_io(OS_FILE_READ | OS_FILE_LOG, TRUE, group->space_id, 0, + field / UNIV_PAGE_SIZE, field % UNIV_PAGE_SIZE, + OS_FILE_LOG_BLOCK_SIZE, log_sys->checkpoint_buf, NULL); +} + +/********************************************************** +Writes checkpoint info to groups. */ +UNIV_INTERN +void +log_groups_write_checkpoint_info(void) +/*==================================*/ +{ + log_group_t* group; + + ut_ad(mutex_own(&(log_sys->mutex))); + + group = UT_LIST_GET_FIRST(log_sys->log_groups); + + while (group) { + log_group_checkpoint(group); + + group = UT_LIST_GET_NEXT(log_groups, group); + } +} + +/********************************************************** +Makes a checkpoint. Note that this function does not flush dirty +blocks from the buffer pool: it only checks what is lsn of the oldest +modification in the pool, and writes information about the lsn in +log files. Use log_make_checkpoint_at to flush also the pool. */ +UNIV_INTERN +ibool +log_checkpoint( +/*===========*/ + /* out: TRUE if success, FALSE if a checkpoint + write was already running */ + ibool sync, /* in: TRUE if synchronous operation is + desired */ + ibool write_always) /* in: the function normally checks if the + the new checkpoint would have a greater + lsn than the previous one: if not, then no + physical write is done; by setting this + parameter TRUE, a physical write will always be + made to log files */ +{ + ib_uint64_t oldest_lsn; + + if (recv_recovery_is_on()) { + recv_apply_hashed_log_recs(TRUE); + } + + if (srv_unix_file_flush_method != SRV_UNIX_NOSYNC) { + fil_flush_file_spaces(FIL_TABLESPACE); + } + + mutex_enter(&(log_sys->mutex)); + + oldest_lsn = log_buf_pool_get_oldest_modification(); + + mutex_exit(&(log_sys->mutex)); + + /* Because log also contains headers and dummy log records, + if the buffer pool contains no dirty buffers, oldest_lsn + gets the value log_sys->lsn from the previous function, + and we must make sure that the log is flushed up to that + lsn. If there are dirty buffers in the buffer pool, then our + write-ahead-logging algorithm ensures that the log has been flushed + up to oldest_lsn. */ + + log_write_up_to(oldest_lsn, LOG_WAIT_ALL_GROUPS, TRUE); + + mutex_enter(&(log_sys->mutex)); + + if (!write_always + && log_sys->last_checkpoint_lsn >= oldest_lsn) { + + mutex_exit(&(log_sys->mutex)); + + return(TRUE); + } + + ut_ad(log_sys->written_to_all_lsn >= oldest_lsn); + + if (log_sys->n_pending_checkpoint_writes > 0) { + /* A checkpoint write is running */ + + mutex_exit(&(log_sys->mutex)); + + if (sync) { + /* Wait for the checkpoint write to complete */ + rw_lock_s_lock(&(log_sys->checkpoint_lock)); + rw_lock_s_unlock(&(log_sys->checkpoint_lock)); + } + + return(FALSE); + } + + log_sys->next_checkpoint_lsn = oldest_lsn; + +#ifdef UNIV_DEBUG + if (log_debug_writes) { + fprintf(stderr, "Making checkpoint no %lu at lsn %llu\n", + (ulong) log_sys->next_checkpoint_no, + oldest_lsn); + } +#endif /* UNIV_DEBUG */ + + log_groups_write_checkpoint_info(); + + mutex_exit(&(log_sys->mutex)); + + if (sync) { + /* Wait for the checkpoint write to complete */ + rw_lock_s_lock(&(log_sys->checkpoint_lock)); + rw_lock_s_unlock(&(log_sys->checkpoint_lock)); + } + + return(TRUE); +} + +/******************************************************************** +Makes a checkpoint at a given lsn or later. */ +UNIV_INTERN +void +log_make_checkpoint_at( +/*===================*/ + ib_uint64_t lsn, /* in: make a checkpoint at this or a + later lsn, if IB_ULONGLONG_MAX, makes + a checkpoint at the latest lsn */ + ibool write_always) /* in: the function normally checks if + the the new checkpoint would have a + greater lsn than the previous one: if + not, then no physical write is done; + by setting this parameter TRUE, a + physical write will always be made to + log files */ +{ + /* Preflush pages synchronously */ + + while (!log_preflush_pool_modified_pages(lsn, TRUE)); + + while (!log_checkpoint(TRUE, write_always)); +} + +/******************************************************************** +Tries to establish a big enough margin of free space in the log groups, such +that a new log entry can be catenated without an immediate need for a +checkpoint. NOTE: this function may only be called if the calling thread +owns no synchronization objects! */ +static +void +log_checkpoint_margin(void) +/*=======================*/ +{ + log_t* log = log_sys; + ib_uint64_t age; + ib_uint64_t checkpoint_age; + ib_uint64_t advance; + ib_uint64_t oldest_lsn; + ibool sync; + ibool checkpoint_sync; + ibool do_checkpoint; + ibool success; +loop: + sync = FALSE; + checkpoint_sync = FALSE; + do_checkpoint = FALSE; + + mutex_enter(&(log->mutex)); + + if (log->check_flush_or_checkpoint == FALSE) { + mutex_exit(&(log->mutex)); + + return; + } + + oldest_lsn = log_buf_pool_get_oldest_modification(); + + age = log->lsn - oldest_lsn; + + if (age > log->max_modified_age_sync) { + + /* A flush is urgent: we have to do a synchronous preflush */ + + sync = TRUE; + advance = 2 * (age - log->max_modified_age_sync); + } else if (age > log->max_modified_age_async) { + + /* A flush is not urgent: we do an asynchronous preflush */ + advance = age - log->max_modified_age_async; + } else { + advance = 0; + } + + checkpoint_age = log->lsn - log->last_checkpoint_lsn; + + if (checkpoint_age > log->max_checkpoint_age) { + /* A checkpoint is urgent: we do it synchronously */ + + checkpoint_sync = TRUE; + + do_checkpoint = TRUE; + + } else if (checkpoint_age > log->max_checkpoint_age_async) { + /* A checkpoint is not urgent: do it asynchronously */ + + do_checkpoint = TRUE; + + log->check_flush_or_checkpoint = FALSE; + } else { + log->check_flush_or_checkpoint = FALSE; + } + + mutex_exit(&(log->mutex)); + + if (advance) { + ib_uint64_t new_oldest = oldest_lsn + advance; + + success = log_preflush_pool_modified_pages(new_oldest, sync); + + /* If the flush succeeded, this thread has done its part + and can proceed. If it did not succeed, there was another + thread doing a flush at the same time. If sync was FALSE, + the flush was not urgent, and we let this thread proceed. + Otherwise, we let it start from the beginning again. */ + + if (sync && !success) { + mutex_enter(&(log->mutex)); + + log->check_flush_or_checkpoint = TRUE; + + mutex_exit(&(log->mutex)); + goto loop; + } + } + + if (do_checkpoint) { + log_checkpoint(checkpoint_sync, FALSE); + + if (checkpoint_sync) { + + goto loop; + } + } +} + +/********************************************************** +Reads a specified log segment to a buffer. */ +UNIV_INTERN +void +log_group_read_log_seg( +/*===================*/ + ulint type, /* in: LOG_ARCHIVE or LOG_RECOVER */ + byte* buf, /* in: buffer where to read */ + log_group_t* group, /* in: log group */ + ib_uint64_t start_lsn, /* in: read area start */ + ib_uint64_t end_lsn) /* in: read area end */ +{ + ulint len; + ulint source_offset; + ibool sync; + + ut_ad(mutex_own(&(log_sys->mutex))); + + sync = (type == LOG_RECOVER); +loop: + source_offset = log_group_calc_lsn_offset(start_lsn, group); + + len = (ulint) (end_lsn - start_lsn); + + ut_ad(len != 0); + + if ((source_offset % group->file_size) + len > group->file_size) { + + len = group->file_size - (source_offset % group->file_size); + } + +#ifdef UNIV_LOG_ARCHIVE + if (type == LOG_ARCHIVE) { + + log_sys->n_pending_archive_ios++; + } +#endif /* UNIV_LOG_ARCHIVE */ + + log_sys->n_log_ios++; + + fil_io(OS_FILE_READ | OS_FILE_LOG, sync, group->space_id, 0, + source_offset / UNIV_PAGE_SIZE, source_offset % UNIV_PAGE_SIZE, + len, buf, NULL); + + start_lsn += len; + buf += len; + + if (start_lsn != end_lsn) { + + goto loop; + } +} + +#ifdef UNIV_LOG_ARCHIVE +/********************************************************** +Generates an archived log file name. */ +UNIV_INTERN +void +log_archived_file_name_gen( +/*=======================*/ + char* buf, /* in: buffer where to write */ + ulint id __attribute__((unused)), + /* in: group id; + currently we only archive the first group */ + ulint file_no)/* in: file number */ +{ + sprintf(buf, "%sib_arch_log_%010lu", srv_arch_dir, (ulong) file_no); +} + +/********************************************************** +Writes a log file header to a log file space. */ +static +void +log_group_archive_file_header_write( +/*================================*/ + log_group_t* group, /* in: log group */ + ulint nth_file, /* in: header to the nth file in the + archive log file space */ + ulint file_no, /* in: archived file number */ + ib_uint64_t start_lsn) /* in: log file data starts at this + lsn */ +{ + byte* buf; + ulint dest_offset; + + ut_ad(mutex_own(&(log_sys->mutex))); + + ut_a(nth_file < group->n_files); + + buf = *(group->archive_file_header_bufs + nth_file); + + mach_write_to_4(buf + LOG_GROUP_ID, group->id); + mach_write_ull(buf + LOG_FILE_START_LSN, start_lsn); + mach_write_to_4(buf + LOG_FILE_NO, file_no); + + mach_write_to_4(buf + LOG_FILE_ARCH_COMPLETED, FALSE); + + dest_offset = nth_file * group->file_size; + + log_sys->n_log_ios++; + + fil_io(OS_FILE_WRITE | OS_FILE_LOG, TRUE, group->archive_space_id, + dest_offset / UNIV_PAGE_SIZE, + dest_offset % UNIV_PAGE_SIZE, + 2 * OS_FILE_LOG_BLOCK_SIZE, + buf, &log_archive_io); +} + +/********************************************************** +Writes a log file header to a completed archived log file. */ +static +void +log_group_archive_completed_header_write( +/*=====================================*/ + log_group_t* group, /* in: log group */ + ulint nth_file, /* in: header to the nth file in the + archive log file space */ + ib_uint64_t end_lsn) /* in: end lsn of the file */ +{ + byte* buf; + ulint dest_offset; + + ut_ad(mutex_own(&(log_sys->mutex))); + ut_a(nth_file < group->n_files); + + buf = *(group->archive_file_header_bufs + nth_file); + + mach_write_to_4(buf + LOG_FILE_ARCH_COMPLETED, TRUE); + mach_write_ull(buf + LOG_FILE_END_LSN, end_lsn); + + dest_offset = nth_file * group->file_size + LOG_FILE_ARCH_COMPLETED; + + log_sys->n_log_ios++; + + fil_io(OS_FILE_WRITE | OS_FILE_LOG, TRUE, group->archive_space_id, + dest_offset / UNIV_PAGE_SIZE, + dest_offset % UNIV_PAGE_SIZE, + OS_FILE_LOG_BLOCK_SIZE, + buf + LOG_FILE_ARCH_COMPLETED, + &log_archive_io); +} + +/********************************************************** +Does the archive writes for a single log group. */ +static +void +log_group_archive( +/*==============*/ + log_group_t* group) /* in: log group */ +{ + os_file_t file_handle; + ib_uint64_t start_lsn; + ib_uint64_t end_lsn; + char name[1024]; + byte* buf; + ulint len; + ibool ret; + ulint next_offset; + ulint n_files; + ulint open_mode; + + ut_ad(mutex_own(&(log_sys->mutex))); + + start_lsn = log_sys->archived_lsn; + + ut_a(start_lsn % OS_FILE_LOG_BLOCK_SIZE == 0); + + end_lsn = log_sys->next_archived_lsn; + + ut_a(end_lsn % OS_FILE_LOG_BLOCK_SIZE == 0); + + buf = log_sys->archive_buf; + + n_files = 0; + + next_offset = group->archived_offset; +loop: + if ((next_offset % group->file_size == 0) + || (fil_space_get_size(group->archive_space_id) == 0)) { + + /* Add the file to the archive file space; create or open the + file */ + + if (next_offset % group->file_size == 0) { + open_mode = OS_FILE_CREATE; + } else { + open_mode = OS_FILE_OPEN; + } + + log_archived_file_name_gen(name, group->id, + group->archived_file_no + n_files); + + file_handle = os_file_create(name, open_mode, OS_FILE_AIO, + OS_DATA_FILE, &ret); + + if (!ret && (open_mode == OS_FILE_CREATE)) { + file_handle = os_file_create( + name, OS_FILE_OPEN, OS_FILE_AIO, + OS_DATA_FILE, &ret); + } + + if (!ret) { + fprintf(stderr, + "InnoDB: Cannot create or open" + " archive log file %s.\n" + "InnoDB: Cannot continue operation.\n" + "InnoDB: Check that the log archive" + " directory exists,\n" + "InnoDB: you have access rights to it, and\n" + "InnoDB: there is space available.\n", name); + exit(1); + } + +#ifdef UNIV_DEBUG + if (log_debug_writes) { + fprintf(stderr, "Created archive file %s\n", name); + } +#endif /* UNIV_DEBUG */ + + ret = os_file_close(file_handle); + + ut_a(ret); + + /* Add the archive file as a node to the space */ + + fil_node_create(name, group->file_size / UNIV_PAGE_SIZE, + group->archive_space_id, FALSE); + + if (next_offset % group->file_size == 0) { + log_group_archive_file_header_write( + group, n_files, + group->archived_file_no + n_files, + start_lsn); + + next_offset += LOG_FILE_HDR_SIZE; + } + } + + len = end_lsn - start_lsn; + + if (group->file_size < (next_offset % group->file_size) + len) { + + len = group->file_size - (next_offset % group->file_size); + } + +#ifdef UNIV_DEBUG + if (log_debug_writes) { + fprintf(stderr, + "Archiving starting at lsn %llu, len %lu" + " to group %lu\n", + start_lsn, + (ulong) len, (ulong) group->id); + } +#endif /* UNIV_DEBUG */ + + log_sys->n_pending_archive_ios++; + + log_sys->n_log_ios++; + + fil_io(OS_FILE_WRITE | OS_FILE_LOG, FALSE, group->archive_space_id, + next_offset / UNIV_PAGE_SIZE, next_offset % UNIV_PAGE_SIZE, + ut_calc_align(len, OS_FILE_LOG_BLOCK_SIZE), buf, + &log_archive_io); + + start_lsn += len; + next_offset += len; + buf += len; + + if (next_offset % group->file_size == 0) { + n_files++; + } + + if (end_lsn != start_lsn) { + + goto loop; + } + + group->next_archived_file_no = group->archived_file_no + n_files; + group->next_archived_offset = next_offset % group->file_size; + + ut_a(group->next_archived_offset % OS_FILE_LOG_BLOCK_SIZE == 0); +} + +/********************************************************* +(Writes to the archive of each log group.) Currently, only the first +group is archived. */ +static +void +log_archive_groups(void) +/*====================*/ +{ + log_group_t* group; + + ut_ad(mutex_own(&(log_sys->mutex))); + + group = UT_LIST_GET_FIRST(log_sys->log_groups); + + log_group_archive(group); +} + +/********************************************************* +Completes the archiving write phase for (each log group), currently, +the first log group. */ +static +void +log_archive_write_complete_groups(void) +/*===================================*/ +{ + log_group_t* group; + ulint end_offset; + ulint trunc_files; + ulint n_files; + ib_uint64_t start_lsn; + ib_uint64_t end_lsn; + ulint i; + + ut_ad(mutex_own(&(log_sys->mutex))); + + group = UT_LIST_GET_FIRST(log_sys->log_groups); + + group->archived_file_no = group->next_archived_file_no; + group->archived_offset = group->next_archived_offset; + + /* Truncate from the archive file space all but the last + file, or if it has been written full, all files */ + + n_files = (UNIV_PAGE_SIZE + * fil_space_get_size(group->archive_space_id)) + / group->file_size; + ut_ad(n_files > 0); + + end_offset = group->archived_offset; + + if (end_offset % group->file_size == 0) { + + trunc_files = n_files; + } else { + trunc_files = n_files - 1; + } + +#ifdef UNIV_DEBUG + if (log_debug_writes && trunc_files) { + fprintf(stderr, + "Complete file(s) archived to group %lu\n", + (ulong) group->id); + } +#endif /* UNIV_DEBUG */ + + /* Calculate the archive file space start lsn */ + start_lsn = log_sys->next_archived_lsn + - (end_offset - LOG_FILE_HDR_SIZE + trunc_files + * (group->file_size - LOG_FILE_HDR_SIZE)); + end_lsn = start_lsn; + + for (i = 0; i < trunc_files; i++) { + + end_lsn += group->file_size - LOG_FILE_HDR_SIZE; + + /* Write a notice to the headers of archived log + files that the file write has been completed */ + + log_group_archive_completed_header_write(group, i, end_lsn); + } + + fil_space_truncate_start(group->archive_space_id, + trunc_files * group->file_size); + +#ifdef UNIV_DEBUG + if (log_debug_writes) { + fputs("Archiving writes completed\n", stderr); + } +#endif /* UNIV_DEBUG */ +} + +/********************************************************** +Completes an archiving i/o. */ +static +void +log_archive_check_completion_low(void) +/*==================================*/ +{ + ut_ad(mutex_own(&(log_sys->mutex))); + + if (log_sys->n_pending_archive_ios == 0 + && log_sys->archiving_phase == LOG_ARCHIVE_READ) { + +#ifdef UNIV_DEBUG + if (log_debug_writes) { + fputs("Archiving read completed\n", stderr); + } +#endif /* UNIV_DEBUG */ + + /* Archive buffer has now been read in: start archive writes */ + + log_sys->archiving_phase = LOG_ARCHIVE_WRITE; + + log_archive_groups(); + } + + if (log_sys->n_pending_archive_ios == 0 + && log_sys->archiving_phase == LOG_ARCHIVE_WRITE) { + + log_archive_write_complete_groups(); + + log_sys->archived_lsn = log_sys->next_archived_lsn; + + rw_lock_x_unlock_gen(&(log_sys->archive_lock), LOG_ARCHIVE); + } +} + +/********************************************************** +Completes an archiving i/o. */ +static +void +log_io_complete_archive(void) +/*=========================*/ +{ + log_group_t* group; + + mutex_enter(&(log_sys->mutex)); + + group = UT_LIST_GET_FIRST(log_sys->log_groups); + + mutex_exit(&(log_sys->mutex)); + + fil_flush(group->archive_space_id); + + mutex_enter(&(log_sys->mutex)); + + ut_ad(log_sys->n_pending_archive_ios > 0); + + log_sys->n_pending_archive_ios--; + + log_archive_check_completion_low(); + + mutex_exit(&(log_sys->mutex)); +} + +/************************************************************************ +Starts an archiving operation. */ +UNIV_INTERN +ibool +log_archive_do( +/*===========*/ + /* out: TRUE if succeed, FALSE if an archiving + operation was already running */ + ibool sync, /* in: TRUE if synchronous operation is desired */ + ulint* n_bytes)/* out: archive log buffer size, 0 if nothing to + archive */ +{ + ibool calc_new_limit; + ib_uint64_t start_lsn; + ib_uint64_t limit_lsn; + + calc_new_limit = TRUE; +loop: + mutex_enter(&(log_sys->mutex)); + + switch (log_sys->archiving_state) { + case LOG_ARCH_OFF: +arch_none: + mutex_exit(&(log_sys->mutex)); + + *n_bytes = 0; + + return(TRUE); + case LOG_ARCH_STOPPED: + case LOG_ARCH_STOPPING2: + mutex_exit(&(log_sys->mutex)); + + os_event_wait(log_sys->archiving_on); + + goto loop; + } + + start_lsn = log_sys->archived_lsn; + + if (calc_new_limit) { + ut_a(log_sys->archive_buf_size % OS_FILE_LOG_BLOCK_SIZE == 0); + limit_lsn = start_lsn + log_sys->archive_buf_size; + + *n_bytes = log_sys->archive_buf_size; + + if (limit_lsn >= log_sys->lsn) { + + limit_lsn = ut_uint64_align_down( + log_sys->lsn, OS_FILE_LOG_BLOCK_SIZE); + } + } + + if (log_sys->archived_lsn >= limit_lsn) { + + goto arch_none; + } + + if (log_sys->written_to_all_lsn < limit_lsn) { + + mutex_exit(&(log_sys->mutex)); + + log_write_up_to(limit_lsn, LOG_WAIT_ALL_GROUPS, TRUE); + + calc_new_limit = FALSE; + + goto loop; + } + + if (log_sys->n_pending_archive_ios > 0) { + /* An archiving operation is running */ + + mutex_exit(&(log_sys->mutex)); + + if (sync) { + rw_lock_s_lock(&(log_sys->archive_lock)); + rw_lock_s_unlock(&(log_sys->archive_lock)); + } + + *n_bytes = log_sys->archive_buf_size; + + return(FALSE); + } + + rw_lock_x_lock_gen(&(log_sys->archive_lock), LOG_ARCHIVE); + + log_sys->archiving_phase = LOG_ARCHIVE_READ; + + log_sys->next_archived_lsn = limit_lsn; + +#ifdef UNIV_DEBUG + if (log_debug_writes) { + fprintf(stderr, + "Archiving from lsn %llu to lsn %llu\n", + log_sys->archived_lsn, limit_lsn); + } +#endif /* UNIV_DEBUG */ + + /* Read the log segment to the archive buffer */ + + log_group_read_log_seg(LOG_ARCHIVE, log_sys->archive_buf, + UT_LIST_GET_FIRST(log_sys->log_groups), + start_lsn, limit_lsn); + + mutex_exit(&(log_sys->mutex)); + + if (sync) { + rw_lock_s_lock(&(log_sys->archive_lock)); + rw_lock_s_unlock(&(log_sys->archive_lock)); + } + + *n_bytes = log_sys->archive_buf_size; + + return(TRUE); +} + +/******************************************************************** +Writes the log contents to the archive at least up to the lsn when this +function was called. */ +static +void +log_archive_all(void) +/*=================*/ +{ + ib_uint64_t present_lsn; + ulint dummy; + + mutex_enter(&(log_sys->mutex)); + + if (log_sys->archiving_state == LOG_ARCH_OFF) { + mutex_exit(&(log_sys->mutex)); + + return; + } + + present_lsn = log_sys->lsn; + + mutex_exit(&(log_sys->mutex)); + + log_pad_current_log_block(); + + for (;;) { + mutex_enter(&(log_sys->mutex)); + + if (present_lsn <= log_sys->archived_lsn) { + + mutex_exit(&(log_sys->mutex)); + + return; + } + + mutex_exit(&(log_sys->mutex)); + + log_archive_do(TRUE, &dummy); + } +} + +/********************************************************* +Closes the possible open archive log file (for each group) the first group, +and if it was open, increments the group file count by 2, if desired. */ +static +void +log_archive_close_groups( +/*=====================*/ + ibool increment_file_count) /* in: TRUE if we want to increment + the file count */ +{ + log_group_t* group; + ulint trunc_len; + + ut_ad(mutex_own(&(log_sys->mutex))); + + if (log_sys->archiving_state == LOG_ARCH_OFF) { + + return; + } + + group = UT_LIST_GET_FIRST(log_sys->log_groups); + + trunc_len = UNIV_PAGE_SIZE + * fil_space_get_size(group->archive_space_id); + if (trunc_len > 0) { + ut_a(trunc_len == group->file_size); + + /* Write a notice to the headers of archived log + files that the file write has been completed */ + + log_group_archive_completed_header_write( + group, 0, log_sys->archived_lsn); + + fil_space_truncate_start(group->archive_space_id, + trunc_len); + if (increment_file_count) { + group->archived_offset = 0; + group->archived_file_no += 2; + } + +#ifdef UNIV_DEBUG + if (log_debug_writes) { + fprintf(stderr, + "Incrementing arch file no to %lu" + " in log group %lu\n", + (ulong) group->archived_file_no + 2, + (ulong) group->id); + } +#endif /* UNIV_DEBUG */ + } +} + +/******************************************************************** +Writes the log contents to the archive up to the lsn when this function was +called, and stops the archiving. When archiving is started again, the archived +log file numbers start from 2 higher, so that the archiving will not write +again to the archived log files which exist when this function returns. */ +UNIV_INTERN +ulint +log_archive_stop(void) +/*==================*/ + /* out: DB_SUCCESS or DB_ERROR */ +{ + ibool success; + + mutex_enter(&(log_sys->mutex)); + + if (log_sys->archiving_state != LOG_ARCH_ON) { + + mutex_exit(&(log_sys->mutex)); + + return(DB_ERROR); + } + + log_sys->archiving_state = LOG_ARCH_STOPPING; + + mutex_exit(&(log_sys->mutex)); + + log_archive_all(); + + mutex_enter(&(log_sys->mutex)); + + log_sys->archiving_state = LOG_ARCH_STOPPING2; + os_event_reset(log_sys->archiving_on); + + mutex_exit(&(log_sys->mutex)); + + /* Wait for a possible archiving operation to end */ + + rw_lock_s_lock(&(log_sys->archive_lock)); + rw_lock_s_unlock(&(log_sys->archive_lock)); + + mutex_enter(&(log_sys->mutex)); + + /* Close all archived log files, incrementing the file count by 2, + if appropriate */ + + log_archive_close_groups(TRUE); + + mutex_exit(&(log_sys->mutex)); + + /* Make a checkpoint, so that if recovery is needed, the file numbers + of new archived log files will start from the right value */ + + success = FALSE; + + while (!success) { + success = log_checkpoint(TRUE, TRUE); + } + + mutex_enter(&(log_sys->mutex)); + + log_sys->archiving_state = LOG_ARCH_STOPPED; + + mutex_exit(&(log_sys->mutex)); + + return(DB_SUCCESS); +} + +/******************************************************************** +Starts again archiving which has been stopped. */ +UNIV_INTERN +ulint +log_archive_start(void) +/*===================*/ + /* out: DB_SUCCESS or DB_ERROR */ +{ + mutex_enter(&(log_sys->mutex)); + + if (log_sys->archiving_state != LOG_ARCH_STOPPED) { + + mutex_exit(&(log_sys->mutex)); + + return(DB_ERROR); + } + + log_sys->archiving_state = LOG_ARCH_ON; + + os_event_set(log_sys->archiving_on); + + mutex_exit(&(log_sys->mutex)); + + return(DB_SUCCESS); +} + +/******************************************************************** +Stop archiving the log so that a gap may occur in the archived log files. */ +UNIV_INTERN +ulint +log_archive_noarchivelog(void) +/*==========================*/ + /* out: DB_SUCCESS or DB_ERROR */ +{ +loop: + mutex_enter(&(log_sys->mutex)); + + if (log_sys->archiving_state == LOG_ARCH_STOPPED + || log_sys->archiving_state == LOG_ARCH_OFF) { + + log_sys->archiving_state = LOG_ARCH_OFF; + + os_event_set(log_sys->archiving_on); + + mutex_exit(&(log_sys->mutex)); + + return(DB_SUCCESS); + } + + mutex_exit(&(log_sys->mutex)); + + log_archive_stop(); + + os_thread_sleep(500000); + + goto loop; +} + +/******************************************************************** +Start archiving the log so that a gap may occur in the archived log files. */ +UNIV_INTERN +ulint +log_archive_archivelog(void) +/*========================*/ + /* out: DB_SUCCESS or DB_ERROR */ +{ + mutex_enter(&(log_sys->mutex)); + + if (log_sys->archiving_state == LOG_ARCH_OFF) { + + log_sys->archiving_state = LOG_ARCH_ON; + + log_sys->archived_lsn + = ut_uint64_align_down(log_sys->lsn, + OS_FILE_LOG_BLOCK_SIZE); + mutex_exit(&(log_sys->mutex)); + + return(DB_SUCCESS); + } + + mutex_exit(&(log_sys->mutex)); + + return(DB_ERROR); +} + +/******************************************************************** +Tries to establish a big enough margin of free space in the log groups, such +that a new log entry can be catenated without an immediate need for +archiving. */ +static +void +log_archive_margin(void) +/*====================*/ +{ + log_t* log = log_sys; + ulint age; + ibool sync; + ulint dummy; +loop: + mutex_enter(&(log->mutex)); + + if (log->archiving_state == LOG_ARCH_OFF) { + mutex_exit(&(log->mutex)); + + return; + } + + age = log->lsn - log->archived_lsn; + + if (age > log->max_archived_lsn_age) { + + /* An archiving is urgent: we have to do synchronous i/o */ + + sync = TRUE; + + } else if (age > log->max_archived_lsn_age_async) { + + /* An archiving is not urgent: we do asynchronous i/o */ + + sync = FALSE; + } else { + /* No archiving required yet */ + + mutex_exit(&(log->mutex)); + + return; + } + + mutex_exit(&(log->mutex)); + + log_archive_do(sync, &dummy); + + if (sync == TRUE) { + /* Check again that enough was written to the archive */ + + goto loop; + } +} +#endif /* UNIV_LOG_ARCHIVE */ + +/************************************************************************ +Checks that there is enough free space in the log to start a new query step. +Flushes the log buffer or makes a new checkpoint if necessary. NOTE: this +function may only be called if the calling thread owns no synchronization +objects! */ +UNIV_INTERN +void +log_check_margins(void) +/*===================*/ +{ +loop: + log_flush_margin(); + + log_checkpoint_margin(); + +#ifdef UNIV_LOG_ARCHIVE + log_archive_margin(); +#endif /* UNIV_LOG_ARCHIVE */ + + mutex_enter(&(log_sys->mutex)); + + if (log_sys->check_flush_or_checkpoint) { + + mutex_exit(&(log_sys->mutex)); + + goto loop; + } + + mutex_exit(&(log_sys->mutex)); +} + +/******************************************************************** +Makes a checkpoint at the latest lsn and writes it to first page of each +data file in the database, so that we know that the file spaces contain +all modifications up to that lsn. This can only be called at database +shutdown. This function also writes all log in log files to the log archive. */ +UNIV_INTERN +void +logs_empty_and_mark_files_at_shutdown(void) +/*=======================================*/ +{ + ib_uint64_t lsn; + ulint arch_log_no; + + if (srv_print_verbose_log) { + ut_print_timestamp(stderr); + fprintf(stderr, " InnoDB: Starting shutdown...\n"); + } + /* Wait until the master thread and all other operations are idle: our + algorithm only works if the server is idle at shutdown */ + + srv_shutdown_state = SRV_SHUTDOWN_CLEANUP; +loop: + os_thread_sleep(100000); + + mutex_enter(&kernel_mutex); + + /* We need the monitor threads to stop before we proceed with a + normal shutdown. In case of very fast shutdown, however, we can + proceed without waiting for monitor threads. */ + + if (srv_fast_shutdown < 2 + && (srv_error_monitor_active + || srv_lock_timeout_and_monitor_active)) { + + mutex_exit(&kernel_mutex); + + goto loop; + } + + /* Check that there are no longer transactions. We need this wait even + for the 'very fast' shutdown, because the InnoDB layer may have + committed or prepared transactions and we don't want to lose them. */ + + if (trx_n_mysql_transactions > 0 + || UT_LIST_GET_LEN(trx_sys->trx_list) > 0) { + + mutex_exit(&kernel_mutex); + + goto loop; + } + + if (srv_fast_shutdown == 2) { + /* In this fastest shutdown we do not flush the buffer pool: + it is essentially a 'crash' of the InnoDB server. Make sure + that the log is all flushed to disk, so that we can recover + all committed transactions in a crash recovery. We must not + write the lsn stamps to the data files, since at a startup + InnoDB deduces from the stamps if the previous shutdown was + clean. */ + + log_buffer_flush_to_disk(); + + return; /* We SKIP ALL THE REST !! */ + } + + /* Check that the master thread is suspended */ + + if (srv_n_threads_active[SRV_MASTER] != 0) { + + mutex_exit(&kernel_mutex); + + goto loop; + } + + mutex_exit(&kernel_mutex); + + mutex_enter(&(log_sys->mutex)); + + if (log_sys->n_pending_checkpoint_writes +#ifdef UNIV_LOG_ARCHIVE + || log_sys->n_pending_archive_ios +#endif /* UNIV_LOG_ARCHIVE */ + || log_sys->n_pending_writes) { + + mutex_exit(&(log_sys->mutex)); + + goto loop; + } + + mutex_exit(&(log_sys->mutex)); + + if (!buf_pool_check_no_pending_io()) { + + goto loop; + } + +#ifdef UNIV_LOG_ARCHIVE + log_archive_all(); +#endif /* UNIV_LOG_ARCHIVE */ + + log_make_checkpoint_at(IB_ULONGLONG_MAX, TRUE); + + mutex_enter(&(log_sys->mutex)); + + lsn = log_sys->lsn; + + if (lsn != log_sys->last_checkpoint_lsn +#ifdef UNIV_LOG_ARCHIVE + || (srv_log_archive_on + && lsn != log_sys->archived_lsn + LOG_BLOCK_HDR_SIZE) +#endif /* UNIV_LOG_ARCHIVE */ + ) { + + mutex_exit(&(log_sys->mutex)); + + goto loop; + } + + arch_log_no = 0; + +#ifdef UNIV_LOG_ARCHIVE + UT_LIST_GET_FIRST(log_sys->log_groups)->archived_file_no; + + if (0 == UT_LIST_GET_FIRST(log_sys->log_groups)->archived_offset) { + + arch_log_no--; + } + + log_archive_close_groups(TRUE); +#endif /* UNIV_LOG_ARCHIVE */ + + mutex_exit(&(log_sys->mutex)); + + mutex_enter(&kernel_mutex); + /* Check that the master thread has stayed suspended */ + if (srv_n_threads_active[SRV_MASTER] != 0) { + fprintf(stderr, + "InnoDB: Warning: the master thread woke up" + " during shutdown\n"); + + mutex_exit(&kernel_mutex); + + goto loop; + } + mutex_exit(&kernel_mutex); + + fil_flush_file_spaces(FIL_TABLESPACE); + fil_flush_file_spaces(FIL_LOG); + + /* The call fil_write_flushed_lsn_to_data_files() will pass the buffer + pool: therefore it is essential that the buffer pool has been + completely flushed to disk! (We do not call fil_write... if the + 'very fast' shutdown is enabled.) */ + + if (!buf_all_freed()) { + + goto loop; + } + + srv_shutdown_state = SRV_SHUTDOWN_LAST_PHASE; + + /* Make some checks that the server really is quiet */ + ut_a(srv_n_threads_active[SRV_MASTER] == 0); + ut_a(buf_all_freed()); + ut_a(lsn == log_sys->lsn); + + if (lsn < srv_start_lsn) { + fprintf(stderr, + "InnoDB: Error: log sequence number" + " at shutdown %llu\n" + "InnoDB: is lower than at startup %llu!\n", + lsn, srv_start_lsn); + } + + srv_shutdown_lsn = lsn; + + fil_write_flushed_lsn_to_data_files(lsn, arch_log_no); + + fil_flush_file_spaces(FIL_TABLESPACE); + + fil_close_all_files(); + + /* Make some checks that the server really is quiet */ + ut_a(srv_n_threads_active[SRV_MASTER] == 0); + ut_a(buf_all_freed()); + ut_a(lsn == log_sys->lsn); +} + +/********************************************************** +Checks by parsing that the catenated log segment for a single mtr is +consistent. */ +UNIV_INTERN +ibool +log_check_log_recs( +/*===============*/ + byte* buf, /* in: pointer to the start of + the log segment in the + log_sys->buf log buffer */ + ulint len, /* in: segment length in bytes */ + ib_uint64_t buf_start_lsn) /* in: buffer start lsn */ +{ + ib_uint64_t contiguous_lsn; + ib_uint64_t scanned_lsn; + byte* start; + byte* end; + byte* buf1; + byte* scan_buf; + + ut_ad(mutex_own(&(log_sys->mutex))); + + if (len == 0) { + + return(TRUE); + } + + start = ut_align_down(buf, OS_FILE_LOG_BLOCK_SIZE); + end = ut_align(buf + len, OS_FILE_LOG_BLOCK_SIZE); + + buf1 = mem_alloc((end - start) + OS_FILE_LOG_BLOCK_SIZE); + scan_buf = ut_align(buf1, OS_FILE_LOG_BLOCK_SIZE); + + ut_memcpy(scan_buf, start, end - start); + + recv_scan_log_recs(TRUE, + (buf_pool->curr_size + - recv_n_pool_free_frames) * UNIV_PAGE_SIZE, + FALSE, scan_buf, end - start, + ut_uint64_align_down(buf_start_lsn, + OS_FILE_LOG_BLOCK_SIZE), + &contiguous_lsn, &scanned_lsn); + + ut_a(scanned_lsn == buf_start_lsn + len); + ut_a(recv_sys->recovered_lsn == scanned_lsn); + + mem_free(buf1); + + return(TRUE); +} + +/********************************************************** +Peeks the current lsn. */ +UNIV_INTERN +ibool +log_peek_lsn( +/*=========*/ + /* out: TRUE if success, FALSE if + could not get the log system mutex */ + ib_uint64_t* lsn) /* out: if returns TRUE, current lsn is here */ +{ + if (0 == mutex_enter_nowait(&(log_sys->mutex))) { + *lsn = log_sys->lsn; + + mutex_exit(&(log_sys->mutex)); + + return(TRUE); + } + + return(FALSE); +} + +/********************************************************** +Prints info of the log. */ +UNIV_INTERN +void +log_print( +/*======*/ + FILE* file) /* in: file where to print */ +{ + double time_elapsed; + time_t current_time; + + mutex_enter(&(log_sys->mutex)); + + fprintf(file, + "Log sequence number %llu\n" + "Log flushed up to %llu\n" + "Last checkpoint at %llu\n", + log_sys->lsn, + log_sys->flushed_to_disk_lsn, + log_sys->last_checkpoint_lsn); + + fprintf(file, + "Max checkpoint age %lu\n" + "Modified age %lu\n" + "Checkpoint age %lu\n", + (ulong) log_sys->max_checkpoint_age, + (ulong) (log_sys->lsn - + log_buf_pool_get_oldest_modification()), + (ulong) (log_sys->lsn - log_sys->last_checkpoint_lsn)); + + current_time = time(NULL); + + time_elapsed = 0.001 + difftime(current_time, + log_sys->last_printout_time); + fprintf(file, + "%lu pending log writes, %lu pending chkp writes\n" + "%lu log i/o's done, %.2f log i/o's/second\n", + (ulong) log_sys->n_pending_writes, + (ulong) log_sys->n_pending_checkpoint_writes, + (ulong) log_sys->n_log_ios, + ((log_sys->n_log_ios - log_sys->n_log_ios_old) + / time_elapsed)); + + log_sys->n_log_ios_old = log_sys->n_log_ios; + log_sys->last_printout_time = current_time; + + mutex_exit(&(log_sys->mutex)); +} + +/************************************************************************** +Refreshes the statistics used to print per-second averages. */ +UNIV_INTERN +void +log_refresh_stats(void) +/*===================*/ +{ + log_sys->n_log_ios_old = log_sys->n_log_ios; + log_sys->last_printout_time = time(NULL); +} diff --git a/storage/xtradb/log/log0recv.c b/storage/xtradb/log/log0recv.c new file mode 100644 index 00000000000..f0f5ae8d6cc --- /dev/null +++ b/storage/xtradb/log/log0recv.c @@ -0,0 +1,3478 @@ +/***************************************************************************** + +Copyright (c) 1997, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/****************************************************** +Recovery + +Created 9/20/1997 Heikki Tuuri +*******************************************************/ + +#include "log0recv.h" + +#ifdef UNIV_NONINL +#include "log0recv.ic" +#endif + +#include "mem0mem.h" +#include "buf0buf.h" +#include "buf0flu.h" +#include "buf0rea.h" +#include "srv0srv.h" +#include "srv0start.h" +#include "mtr0log.h" +#include "page0cur.h" +#include "page0zip.h" +#include "btr0cur.h" +#include "ibuf0ibuf.h" +#include "trx0undo.h" +#include "trx0rec.h" +#include "trx0roll.h" +#include "row0merge.h" + +#ifdef UNIV_HOTBACKUP +/* This is set to FALSE if the backup was originally taken with the +ibbackup --include regexp option: then we do not want to create tables in +directories which were not included */ +UNIV_INTERN ibool recv_replay_file_ops = TRUE; +#endif /* UNIV_HOTBACKUP */ + +/* Log records are stored in the hash table in chunks at most of this size; +this must be less than UNIV_PAGE_SIZE as it is stored in the buffer pool */ +#define RECV_DATA_BLOCK_SIZE (MEM_MAX_ALLOC_IN_BUF - sizeof(recv_data_t)) + +/* Read-ahead area in applying log records to file pages */ +#define RECV_READ_AHEAD_AREA 32 + +UNIV_INTERN recv_sys_t* recv_sys = NULL; +UNIV_INTERN ibool recv_recovery_on = FALSE; +UNIV_INTERN ibool recv_recovery_from_backup_on = FALSE; + +UNIV_INTERN ibool recv_needed_recovery = FALSE; + +UNIV_INTERN ibool recv_lsn_checks_on = FALSE; + +/* There are two conditions under which we scan the logs, the first +is normal startup and the second is when we do a recovery from an +archive. +This flag is set if we are doing a scan from the last checkpoint during +startup. If we find log entries that were written after the last checkpoint +we know that the server was not cleanly shutdown. We must then initialize +the crash recovery environment before attempting to store these entries in +the log hash table. */ +UNIV_INTERN ibool recv_log_scan_is_startup_type = FALSE; + +/* If the following is TRUE, the buffer pool file pages must be invalidated +after recovery and no ibuf operations are allowed; this becomes TRUE if +the log record hash table becomes too full, and log records must be merged +to file pages already before the recovery is finished: in this case no +ibuf operations are allowed, as they could modify the pages read in the +buffer pool before the pages have been recovered to the up-to-date state */ + +/* Recovery is running and no operations on the log files are allowed +yet: the variable name is misleading */ + +UNIV_INTERN ibool recv_no_ibuf_operations = FALSE; + +/* The following counter is used to decide when to print info on +log scan */ +UNIV_INTERN ulint recv_scan_print_counter = 0; + +UNIV_INTERN ibool recv_is_from_backup = FALSE; +#ifdef UNIV_HOTBACKUP +UNIV_INTERN ibool recv_is_making_a_backup = FALSE; +#else +# define recv_is_making_a_backup FALSE +#endif /* UNIV_HOTBACKUP */ + +UNIV_INTERN ulint recv_previous_parsed_rec_type = 999999; +UNIV_INTERN ulint recv_previous_parsed_rec_offset = 0; +UNIV_INTERN ulint recv_previous_parsed_rec_is_multi = 0; + +UNIV_INTERN ulint recv_max_parsed_page_no = 0; + +/* This many frames must be left free in the buffer pool when we scan +the log and store the scanned log records in the buffer pool: we will +use these free frames to read in pages when we start applying the +log records to the database. */ + +UNIV_INTERN ulint recv_n_pool_free_frames = 1024; + +/* The maximum lsn we see for a page during the recovery process. If this +is bigger than the lsn we are able to scan up to, that is an indication that +the recovery failed and the database may be corrupt. */ + +UNIV_INTERN ib_uint64_t recv_max_page_lsn; + +/* prototypes */ + +/*********************************************************** +Initialize crash recovery environment. Can be called iff +recv_needed_recovery == FALSE. */ +static +void +recv_init_crash_recovery(void); +/*===========================*/ + +/************************************************************ +Creates the recovery system. */ +UNIV_INTERN +void +recv_sys_create(void) +/*=================*/ +{ + if (recv_sys != NULL) { + + return; + } + + recv_sys = mem_alloc(sizeof(recv_sys_t)); + + mutex_create(&recv_sys->mutex, SYNC_RECV); + + recv_sys->heap = NULL; + recv_sys->addr_hash = NULL; +} + +/************************************************************ +Inits the recovery system for a recovery operation. */ +UNIV_INTERN +void +recv_sys_init( +/*==========*/ + ibool recover_from_backup, /* in: TRUE if this is called + to recover from a hot backup */ + ulint available_memory) /* in: available memory in bytes */ +{ + if (recv_sys->heap != NULL) { + + return; + } + + mutex_enter(&(recv_sys->mutex)); + + if (!recover_from_backup) { + recv_sys->heap = mem_heap_create_in_buffer(256); + } else { + recv_sys->heap = mem_heap_create(256); + recv_is_from_backup = TRUE; + } + + recv_sys->buf = ut_malloc(RECV_PARSING_BUF_SIZE); + recv_sys->len = 0; + recv_sys->recovered_offset = 0; + + recv_sys->addr_hash = hash_create(available_memory / 64); + recv_sys->n_addrs = 0; + + recv_sys->apply_log_recs = FALSE; + recv_sys->apply_batch_on = FALSE; + + recv_sys->last_block_buf_start = mem_alloc(2 * OS_FILE_LOG_BLOCK_SIZE); + + recv_sys->last_block = ut_align(recv_sys->last_block_buf_start, + OS_FILE_LOG_BLOCK_SIZE); + recv_sys->found_corrupt_log = FALSE; + + recv_max_page_lsn = 0; + + mutex_exit(&(recv_sys->mutex)); +} + +/************************************************************ +Empties the hash table when it has been fully processed. */ +static +void +recv_sys_empty_hash(void) +/*=====================*/ +{ + ut_ad(mutex_own(&(recv_sys->mutex))); + + if (recv_sys->n_addrs != 0) { + fprintf(stderr, + "InnoDB: Error: %lu pages with log records" + " were left unprocessed!\n" + "InnoDB: Maximum page number with" + " log records on it %lu\n", + (ulong) recv_sys->n_addrs, + (ulong) recv_max_parsed_page_no); + ut_error; + } + + hash_table_free(recv_sys->addr_hash); + mem_heap_empty(recv_sys->heap); + + recv_sys->addr_hash = hash_create(buf_pool_get_curr_size() / 256); +} + +#ifndef UNIV_LOG_DEBUG +/************************************************************ +Frees the recovery system. */ +static +void +recv_sys_free(void) +/*===============*/ +{ + mutex_enter(&(recv_sys->mutex)); + + hash_table_free(recv_sys->addr_hash); + mem_heap_free(recv_sys->heap); + ut_free(recv_sys->buf); + mem_free(recv_sys->last_block_buf_start); + + recv_sys->addr_hash = NULL; + recv_sys->heap = NULL; + + mutex_exit(&(recv_sys->mutex)); +} +#endif /* UNIV_LOG_DEBUG */ + +/************************************************************ +Truncates possible corrupted or extra records from a log group. */ +static +void +recv_truncate_group( +/*================*/ + log_group_t* group, /* in: log group */ + ib_uint64_t recovered_lsn, /* in: recovery succeeded up to this + lsn */ + ib_uint64_t limit_lsn, /* in: this was the limit for + recovery */ + ib_uint64_t checkpoint_lsn, /* in: recovery was started from this + checkpoint */ + ib_uint64_t archived_lsn) /* in: the log has been archived up to + this lsn */ +{ + ib_uint64_t start_lsn; + ib_uint64_t end_lsn; + ib_uint64_t finish_lsn1; + ib_uint64_t finish_lsn2; + ib_uint64_t finish_lsn; + ulint len; + ulint i; + + if (archived_lsn == IB_ULONGLONG_MAX) { + /* Checkpoint was taken in the NOARCHIVELOG mode */ + archived_lsn = checkpoint_lsn; + } + + finish_lsn1 = ut_uint64_align_down(archived_lsn, + OS_FILE_LOG_BLOCK_SIZE) + + log_group_get_capacity(group); + + finish_lsn2 = ut_uint64_align_up(recovered_lsn, + OS_FILE_LOG_BLOCK_SIZE) + + recv_sys->last_log_buf_size; + + if (limit_lsn != IB_ULONGLONG_MAX) { + /* We do not know how far we should erase log records: erase + as much as possible */ + + finish_lsn = finish_lsn1; + } else { + /* It is enough to erase the length of the log buffer */ + finish_lsn = finish_lsn1 < finish_lsn2 + ? finish_lsn1 : finish_lsn2; + } + + ut_a(RECV_SCAN_SIZE <= log_sys->buf_size); + + /* Write the log buffer full of zeros */ + for (i = 0; i < RECV_SCAN_SIZE; i++) { + + *(log_sys->buf + i) = '\0'; + } + + start_lsn = ut_uint64_align_down(recovered_lsn, + OS_FILE_LOG_BLOCK_SIZE); + + if (start_lsn != recovered_lsn) { + /* Copy the last incomplete log block to the log buffer and + edit its data length: */ + + ut_memcpy(log_sys->buf, recv_sys->last_block, + OS_FILE_LOG_BLOCK_SIZE); + log_block_set_data_len(log_sys->buf, + (ulint) (recovered_lsn - start_lsn)); + } + + if (start_lsn >= finish_lsn) { + + return; + } + + for (;;) { + end_lsn = start_lsn + RECV_SCAN_SIZE; + + if (end_lsn > finish_lsn) { + + end_lsn = finish_lsn; + } + + len = (ulint) (end_lsn - start_lsn); + + log_group_write_buf(group, log_sys->buf, len, start_lsn, 0); + if (end_lsn >= finish_lsn) { + + return; + } + + /* Write the log buffer full of zeros */ + for (i = 0; i < RECV_SCAN_SIZE; i++) { + + *(log_sys->buf + i) = '\0'; + } + + start_lsn = end_lsn; + } +} + +/************************************************************ +Copies the log segment between group->recovered_lsn and recovered_lsn from the +most up-to-date log group to group, so that it contains the latest log data. */ +static +void +recv_copy_group( +/*============*/ + log_group_t* up_to_date_group, /* in: the most up-to-date log + group */ + log_group_t* group, /* in: copy to this log + group */ + ib_uint64_t recovered_lsn) /* in: recovery succeeded up + to this lsn */ +{ + ib_uint64_t start_lsn; + ib_uint64_t end_lsn; + ulint len; + + if (group->scanned_lsn >= recovered_lsn) { + + return; + } + + ut_a(RECV_SCAN_SIZE <= log_sys->buf_size); + + start_lsn = ut_uint64_align_down(group->scanned_lsn, + OS_FILE_LOG_BLOCK_SIZE); + for (;;) { + end_lsn = start_lsn + RECV_SCAN_SIZE; + + if (end_lsn > recovered_lsn) { + end_lsn = ut_uint64_align_up(recovered_lsn, + OS_FILE_LOG_BLOCK_SIZE); + } + + log_group_read_log_seg(LOG_RECOVER, log_sys->buf, + up_to_date_group, start_lsn, end_lsn); + + len = (ulint) (end_lsn - start_lsn); + + log_group_write_buf(group, log_sys->buf, len, start_lsn, 0); + + if (end_lsn >= recovered_lsn) { + + return; + } + + start_lsn = end_lsn; + } +} + +/************************************************************ +Copies a log segment from the most up-to-date log group to the other log +groups, so that they all contain the latest log data. Also writes the info +about the latest checkpoint to the groups, and inits the fields in the group +memory structs to up-to-date values. */ +static +void +recv_synchronize_groups( +/*====================*/ + log_group_t* up_to_date_group) /* in: the most up-to-date + log group */ +{ + log_group_t* group; + ib_uint64_t start_lsn; + ib_uint64_t end_lsn; + ib_uint64_t recovered_lsn; + ib_uint64_t limit_lsn; + + recovered_lsn = recv_sys->recovered_lsn; + limit_lsn = recv_sys->limit_lsn; + + /* Read the last recovered log block to the recovery system buffer: + the block is always incomplete */ + + start_lsn = ut_uint64_align_down(recovered_lsn, + OS_FILE_LOG_BLOCK_SIZE); + end_lsn = ut_uint64_align_up(recovered_lsn, OS_FILE_LOG_BLOCK_SIZE); + + ut_a(start_lsn != end_lsn); + + log_group_read_log_seg(LOG_RECOVER, recv_sys->last_block, + up_to_date_group, start_lsn, end_lsn); + + group = UT_LIST_GET_FIRST(log_sys->log_groups); + + while (group) { + if (group != up_to_date_group) { + + /* Copy log data if needed */ + + recv_copy_group(group, up_to_date_group, + recovered_lsn); + } + + /* Update the fields in the group struct to correspond to + recovered_lsn */ + + log_group_set_fields(group, recovered_lsn); + + group = UT_LIST_GET_NEXT(log_groups, group); + } + + /* Copy the checkpoint info to the groups; remember that we have + incremented checkpoint_no by one, and the info will not be written + over the max checkpoint info, thus making the preservation of max + checkpoint info on disk certain */ + + log_groups_write_checkpoint_info(); + + mutex_exit(&(log_sys->mutex)); + + /* Wait for the checkpoint write to complete */ + rw_lock_s_lock(&(log_sys->checkpoint_lock)); + rw_lock_s_unlock(&(log_sys->checkpoint_lock)); + + mutex_enter(&(log_sys->mutex)); +} + +/*************************************************************************** +Checks the consistency of the checkpoint info */ +static +ibool +recv_check_cp_is_consistent( +/*========================*/ + /* out: TRUE if ok */ + byte* buf) /* in: buffer containing checkpoint info */ +{ + ulint fold; + + fold = ut_fold_binary(buf, LOG_CHECKPOINT_CHECKSUM_1); + + if ((fold & 0xFFFFFFFFUL) != mach_read_from_4( + buf + LOG_CHECKPOINT_CHECKSUM_1)) { + return(FALSE); + } + + fold = ut_fold_binary(buf + LOG_CHECKPOINT_LSN, + LOG_CHECKPOINT_CHECKSUM_2 - LOG_CHECKPOINT_LSN); + + if ((fold & 0xFFFFFFFFUL) != mach_read_from_4( + buf + LOG_CHECKPOINT_CHECKSUM_2)) { + return(FALSE); + } + + return(TRUE); +} + +/************************************************************ +Looks for the maximum consistent checkpoint from the log groups. */ +static +ulint +recv_find_max_checkpoint( +/*=====================*/ + /* out: error code or DB_SUCCESS */ + log_group_t** max_group, /* out: max group */ + ulint* max_field) /* out: LOG_CHECKPOINT_1 or + LOG_CHECKPOINT_2 */ +{ + log_group_t* group; + ib_uint64_t max_no; + ib_uint64_t checkpoint_no; + ulint field; + byte* buf; + + group = UT_LIST_GET_FIRST(log_sys->log_groups); + + max_no = 0; + *max_group = NULL; + *max_field = 0; + + buf = log_sys->checkpoint_buf; + + while (group) { + group->state = LOG_GROUP_CORRUPTED; + + for (field = LOG_CHECKPOINT_1; field <= LOG_CHECKPOINT_2; + field += LOG_CHECKPOINT_2 - LOG_CHECKPOINT_1) { + + log_group_read_checkpoint_info(group, field); + + if (!recv_check_cp_is_consistent(buf)) { +#ifdef UNIV_DEBUG + if (log_debug_writes) { + fprintf(stderr, + "InnoDB: Checkpoint in group" + " %lu at %lu invalid, %lu\n", + (ulong) group->id, + (ulong) field, + (ulong) mach_read_from_4( + buf + + LOG_CHECKPOINT_CHECKSUM_1)); + + } +#endif /* UNIV_DEBUG */ + goto not_consistent; + } + + group->state = LOG_GROUP_OK; + + group->lsn = mach_read_ull( + buf + LOG_CHECKPOINT_LSN); + group->lsn_offset = mach_read_from_4( + buf + LOG_CHECKPOINT_OFFSET); + checkpoint_no = mach_read_ull( + buf + LOG_CHECKPOINT_NO); + +#ifdef UNIV_DEBUG + if (log_debug_writes) { + fprintf(stderr, + "InnoDB: Checkpoint number %lu" + " found in group %lu\n", + (ulong) checkpoint_no, + (ulong) group->id); + } +#endif /* UNIV_DEBUG */ + + if (checkpoint_no >= max_no) { + *max_group = group; + *max_field = field; + max_no = checkpoint_no; + } + +not_consistent: + ; + } + + group = UT_LIST_GET_NEXT(log_groups, group); + } + + if (*max_group == NULL) { + + fprintf(stderr, + "InnoDB: No valid checkpoint found.\n" + "InnoDB: If this error appears when you are" + " creating an InnoDB database,\n" + "InnoDB: the problem may be that during" + " an earlier attempt you managed\n" + "InnoDB: to create the InnoDB data files," + " but log file creation failed.\n" + "InnoDB: If that is the case, please refer to\n" + "InnoDB: http://dev.mysql.com/doc/refman/5.1/en/" + "error-creating-innodb.html\n"); + return(DB_ERROR); + } + + return(DB_SUCCESS); +} + +#ifdef UNIV_HOTBACKUP +/*********************************************************************** +Reads the checkpoint info needed in hot backup. */ +UNIV_INTERN +ibool +recv_read_cp_info_for_backup( +/*=========================*/ + /* out: TRUE if success */ + byte* hdr, /* in: buffer containing the log group + header */ + ib_uint64_t* lsn, /* out: checkpoint lsn */ + ulint* offset, /* out: checkpoint offset in the log group */ + ulint* fsp_limit,/* out: fsp limit of space 0, + 1000000000 if the database is running + with < version 3.23.50 of InnoDB */ + ib_uint64_t* cp_no, /* out: checkpoint number */ + ib_uint64_t* first_header_lsn) + /* out: lsn of of the start of the + first log file */ +{ + ulint max_cp = 0; + ib_uint64_t max_cp_no = 0; + byte* cp_buf; + + cp_buf = hdr + LOG_CHECKPOINT_1; + + if (recv_check_cp_is_consistent(cp_buf)) { + max_cp_no = mach_read_ull(cp_buf + LOG_CHECKPOINT_NO); + max_cp = LOG_CHECKPOINT_1; + } + + cp_buf = hdr + LOG_CHECKPOINT_2; + + if (recv_check_cp_is_consistent(cp_buf)) { + if (mach_read_ull(cp_buf + LOG_CHECKPOINT_NO) > max_cp_no) { + max_cp = LOG_CHECKPOINT_2; + } + } + + if (max_cp == 0) { + return(FALSE); + } + + cp_buf = hdr + max_cp; + + *lsn = mach_read_ull(cp_buf + LOG_CHECKPOINT_LSN); + *offset = mach_read_from_4(cp_buf + LOG_CHECKPOINT_OFFSET); + + /* If the user is running a pre-3.23.50 version of InnoDB, its + checkpoint data does not contain the fsp limit info */ + if (mach_read_from_4(cp_buf + LOG_CHECKPOINT_FSP_MAGIC_N) + == LOG_CHECKPOINT_FSP_MAGIC_N_VAL) { + + *fsp_limit = mach_read_from_4( + cp_buf + LOG_CHECKPOINT_FSP_FREE_LIMIT); + + if (*fsp_limit == 0) { + *fsp_limit = 1000000000; + } + } else { + *fsp_limit = 1000000000; + } + + /* fprintf(stderr, "fsp limit %lu MB\n", *fsp_limit); */ + + *cp_no = mach_read_ull(cp_buf + LOG_CHECKPOINT_NO); + + *first_header_lsn = mach_read_ull(hdr + LOG_FILE_START_LSN); + + return(TRUE); +} +#endif /* UNIV_HOTBACKUP */ + +/********************************************************** +Checks the 4-byte checksum to the trailer checksum field of a log block. +We also accept a log block in the old format < InnoDB-3.23.52 where the +checksum field contains the log block number. */ +static +ibool +log_block_checksum_is_ok_or_old_format( +/*===================================*/ + /* out: TRUE if ok, or if the log block may be in the + format of InnoDB version < 3.23.52 */ + byte* block) /* in: pointer to a log block */ +{ +#ifdef UNIV_LOG_DEBUG + return(TRUE); +#endif /* UNIV_LOG_DEBUG */ + if (log_block_calc_checksum(block) == log_block_get_checksum(block)) { + + return(TRUE); + } + + if (log_block_get_hdr_no(block) == log_block_get_checksum(block)) { + + /* We assume the log block is in the format of + InnoDB version < 3.23.52 and the block is ok */ +#if 0 + fprintf(stderr, + "InnoDB: Scanned old format < InnoDB-3.23.52" + " log block number %lu\n", + log_block_get_hdr_no(block)); +#endif + return(TRUE); + } + + return(FALSE); +} + +#ifdef UNIV_HOTBACKUP +/*********************************************************************** +Scans the log segment and n_bytes_scanned is set to the length of valid +log scanned. */ +UNIV_INTERN +void +recv_scan_log_seg_for_backup( +/*=========================*/ + byte* buf, /* in: buffer containing log data */ + ulint buf_len, /* in: data length in that buffer */ + ib_uint64_t* scanned_lsn, /* in/out: lsn of buffer start, + we return scanned lsn */ + ulint* scanned_checkpoint_no, + /* in/out: 4 lowest bytes of the + highest scanned checkpoint number so + far */ + ulint* n_bytes_scanned)/* out: how much we were able to + scan, smaller than buf_len if log + data ended here */ +{ + ulint data_len; + byte* log_block; + ulint no; + + *n_bytes_scanned = 0; + + for (log_block = buf; log_block < buf + buf_len; + log_block += OS_FILE_LOG_BLOCK_SIZE) { + + no = log_block_get_hdr_no(log_block); + +#if 0 + fprintf(stderr, "Log block header no %lu\n", no); +#endif + + if (no != log_block_convert_lsn_to_no(*scanned_lsn) + || !log_block_checksum_is_ok_or_old_format(log_block)) { +#if 0 + fprintf(stderr, + "Log block n:o %lu, scanned lsn n:o %lu\n", + no, log_block_convert_lsn_to_no(*scanned_lsn)); +#endif + /* Garbage or an incompletely written log block */ + + log_block += OS_FILE_LOG_BLOCK_SIZE; +#if 0 + fprintf(stderr, + "Next log block n:o %lu\n", + log_block_get_hdr_no(log_block)); +#endif + break; + } + + if (*scanned_checkpoint_no > 0 + && log_block_get_checkpoint_no(log_block) + < *scanned_checkpoint_no + && *scanned_checkpoint_no + - log_block_get_checkpoint_no(log_block) + > 0x80000000UL) { + + /* Garbage from a log buffer flush which was made + before the most recent database recovery */ +#if 0 + fprintf(stderr, + "Scanned cp n:o %lu, block cp n:o %lu\n", + *scanned_checkpoint_no, + log_block_get_checkpoint_no(log_block)); +#endif + break; + } + + data_len = log_block_get_data_len(log_block); + + *scanned_checkpoint_no + = log_block_get_checkpoint_no(log_block); + *scanned_lsn += data_len; + + *n_bytes_scanned += data_len; + + if (data_len < OS_FILE_LOG_BLOCK_SIZE) { + /* Log data ends here */ + +#if 0 + fprintf(stderr, "Log block data len %lu\n", + data_len); +#endif + break; + } + } +} +#endif /* UNIV_HOTBACKUP */ + +/*********************************************************************** +Tries to parse a single log record body and also applies it to a page if +specified. File ops are parsed, but not applied in this function. */ +static +byte* +recv_parse_or_apply_log_rec_body( +/*=============================*/ + /* out: log record end, NULL if not a + complete record */ + byte type, /* in: type */ + byte* ptr, /* in: pointer to a buffer */ + byte* end_ptr,/* in: pointer to the buffer end */ + buf_block_t* block, /* in/out: buffer block or NULL; if + not NULL, then the log record is + applied to the page, and the log + record should be complete then */ + mtr_t* mtr) /* in: mtr or NULL; should be non-NULL + if and only if block is non-NULL */ +{ + dict_index_t* index = NULL; + page_t* page; + page_zip_des_t* page_zip; + + ut_ad(!block == !mtr); + + if (block) { + page = block->frame; + page_zip = buf_block_get_page_zip(block); + } else { + page = NULL; + page_zip = NULL; + } + + switch (type) { + case MLOG_1BYTE: case MLOG_2BYTES: case MLOG_4BYTES: case MLOG_8BYTES: + ptr = mlog_parse_nbytes(type, ptr, end_ptr, page, page_zip); + break; + case MLOG_REC_INSERT: case MLOG_COMP_REC_INSERT: + if (NULL != (ptr = mlog_parse_index( + ptr, end_ptr, + type == MLOG_COMP_REC_INSERT, + &index))) { + ut_a(!page + || (ibool)!!page_is_comp(page) + == dict_table_is_comp(index->table)); + ptr = page_cur_parse_insert_rec(FALSE, ptr, end_ptr, + block, index, mtr); + } + break; + case MLOG_REC_CLUST_DELETE_MARK: case MLOG_COMP_REC_CLUST_DELETE_MARK: + if (NULL != (ptr = mlog_parse_index( + ptr, end_ptr, + type == MLOG_COMP_REC_CLUST_DELETE_MARK, + &index))) { + ut_a(!page + || (ibool)!!page_is_comp(page) + == dict_table_is_comp(index->table)); + ptr = btr_cur_parse_del_mark_set_clust_rec( + ptr, end_ptr, page, page_zip, index); + } + break; + case MLOG_COMP_REC_SEC_DELETE_MARK: + /* This log record type is obsolete, but we process it for + backward compatibility with MySQL 5.0.3 and 5.0.4. */ + ut_a(!page || page_is_comp(page)); + ut_a(!page_zip); + ptr = mlog_parse_index(ptr, end_ptr, TRUE, &index); + if (!ptr) { + break; + } + /* Fall through */ + case MLOG_REC_SEC_DELETE_MARK: + ptr = btr_cur_parse_del_mark_set_sec_rec(ptr, end_ptr, + page, page_zip); + break; + case MLOG_REC_UPDATE_IN_PLACE: case MLOG_COMP_REC_UPDATE_IN_PLACE: + if (NULL != (ptr = mlog_parse_index( + ptr, end_ptr, + type == MLOG_COMP_REC_UPDATE_IN_PLACE, + &index))) { + ut_a(!page + || (ibool)!!page_is_comp(page) + == dict_table_is_comp(index->table)); + ptr = btr_cur_parse_update_in_place(ptr, end_ptr, page, + page_zip, index); + } + break; + case MLOG_LIST_END_DELETE: case MLOG_COMP_LIST_END_DELETE: + case MLOG_LIST_START_DELETE: case MLOG_COMP_LIST_START_DELETE: + if (NULL != (ptr = mlog_parse_index( + ptr, end_ptr, + type == MLOG_COMP_LIST_END_DELETE + || type == MLOG_COMP_LIST_START_DELETE, + &index))) { + ut_a(!page + || (ibool)!!page_is_comp(page) + == dict_table_is_comp(index->table)); + ptr = page_parse_delete_rec_list(type, ptr, end_ptr, + block, index, mtr); + } + break; + case MLOG_LIST_END_COPY_CREATED: case MLOG_COMP_LIST_END_COPY_CREATED: + if (NULL != (ptr = mlog_parse_index( + ptr, end_ptr, + type == MLOG_COMP_LIST_END_COPY_CREATED, + &index))) { + ut_a(!page + || (ibool)!!page_is_comp(page) + == dict_table_is_comp(index->table)); + ptr = page_parse_copy_rec_list_to_created_page( + ptr, end_ptr, block, index, mtr); + } + break; + case MLOG_PAGE_REORGANIZE: case MLOG_COMP_PAGE_REORGANIZE: + if (NULL != (ptr = mlog_parse_index( + ptr, end_ptr, + type == MLOG_COMP_PAGE_REORGANIZE, + &index))) { + ut_a(!page + || (ibool)!!page_is_comp(page) + == dict_table_is_comp(index->table)); + ptr = btr_parse_page_reorganize(ptr, end_ptr, index, + block, mtr); + } + break; + case MLOG_PAGE_CREATE: case MLOG_COMP_PAGE_CREATE: + ut_a(!page_zip); + ptr = page_parse_create(ptr, end_ptr, + type == MLOG_COMP_PAGE_CREATE, + block, mtr); + break; + case MLOG_UNDO_INSERT: + ptr = trx_undo_parse_add_undo_rec(ptr, end_ptr, page); + break; + case MLOG_UNDO_ERASE_END: + ptr = trx_undo_parse_erase_page_end(ptr, end_ptr, page, mtr); + break; + case MLOG_UNDO_INIT: + ptr = trx_undo_parse_page_init(ptr, end_ptr, page, mtr); + break; + case MLOG_UNDO_HDR_DISCARD: + ptr = trx_undo_parse_discard_latest(ptr, end_ptr, page, mtr); + break; + case MLOG_UNDO_HDR_CREATE: + case MLOG_UNDO_HDR_REUSE: + ptr = trx_undo_parse_page_header(type, ptr, end_ptr, + page, mtr); + break; + case MLOG_REC_MIN_MARK: case MLOG_COMP_REC_MIN_MARK: + /* On a compressed page, MLOG_COMP_REC_MIN_MARK + will be followed by MLOG_COMP_REC_DELETE + or MLOG_ZIP_WRITE_HEADER(FIL_PAGE_PREV, FIL_NULL) + in the same mini-transaction. */ + ut_a(type == MLOG_COMP_REC_MIN_MARK || !page_zip); + ptr = btr_parse_set_min_rec_mark( + ptr, end_ptr, type == MLOG_COMP_REC_MIN_MARK, + page, mtr); + break; + case MLOG_REC_DELETE: case MLOG_COMP_REC_DELETE: + if (NULL != (ptr = mlog_parse_index( + ptr, end_ptr, + type == MLOG_COMP_REC_DELETE, + &index))) { + ut_a(!page + || (ibool)!!page_is_comp(page) + == dict_table_is_comp(index->table)); + ptr = page_cur_parse_delete_rec(ptr, end_ptr, + block, index, mtr); + } + break; + case MLOG_IBUF_BITMAP_INIT: + ptr = ibuf_parse_bitmap_init(ptr, end_ptr, block, mtr); + break; + case MLOG_INIT_FILE_PAGE: + ptr = fsp_parse_init_file_page(ptr, end_ptr, block); + break; + case MLOG_WRITE_STRING: + ptr = mlog_parse_string(ptr, end_ptr, page, page_zip); + break; + case MLOG_FILE_CREATE: + case MLOG_FILE_RENAME: + case MLOG_FILE_DELETE: + case MLOG_FILE_CREATE2: + ptr = fil_op_log_parse_or_replay(ptr, end_ptr, type, 0); + break; + case MLOG_ZIP_WRITE_NODE_PTR: + ptr = page_zip_parse_write_node_ptr(ptr, end_ptr, + page, page_zip); + break; + case MLOG_ZIP_WRITE_BLOB_PTR: + ptr = page_zip_parse_write_blob_ptr(ptr, end_ptr, + page, page_zip); + break; + case MLOG_ZIP_WRITE_HEADER: + ptr = page_zip_parse_write_header(ptr, end_ptr, + page, page_zip); + break; + case MLOG_ZIP_PAGE_COMPRESS: + ptr = page_zip_parse_compress(ptr, end_ptr, + page, page_zip); + break; + default: + ptr = NULL; + recv_sys->found_corrupt_log = TRUE; + } + + if (index) { + dict_table_t* table = index->table; + + dict_mem_index_free(index); + dict_mem_table_free(table); + } + + return(ptr); +} + +/************************************************************************* +Calculates the fold value of a page file address: used in inserting or +searching for a log record in the hash table. */ +UNIV_INLINE +ulint +recv_fold( +/*======*/ + /* out: folded value */ + ulint space, /* in: space */ + ulint page_no)/* in: page number */ +{ + return(ut_fold_ulint_pair(space, page_no)); +} + +/************************************************************************* +Calculates the hash value of a page file address: used in inserting or +searching for a log record in the hash table. */ +UNIV_INLINE +ulint +recv_hash( +/*======*/ + /* out: folded value */ + ulint space, /* in: space */ + ulint page_no)/* in: page number */ +{ + return(hash_calc_hash(recv_fold(space, page_no), recv_sys->addr_hash)); +} + +/************************************************************************* +Gets the hashed file address struct for a page. */ +static +recv_addr_t* +recv_get_fil_addr_struct( +/*=====================*/ + /* out: file address struct, NULL if not found from + the hash table */ + ulint space, /* in: space id */ + ulint page_no)/* in: page number */ +{ + recv_addr_t* recv_addr; + + recv_addr = HASH_GET_FIRST(recv_sys->addr_hash, + recv_hash(space, page_no)); + while (recv_addr) { + if ((recv_addr->space == space) + && (recv_addr->page_no == page_no)) { + + break; + } + + recv_addr = HASH_GET_NEXT(addr_hash, recv_addr); + } + + return(recv_addr); +} + +/*********************************************************************** +Adds a new log record to the hash table of log records. */ +static +void +recv_add_to_hash_table( +/*===================*/ + byte type, /* in: log record type */ + ulint space, /* in: space id */ + ulint page_no, /* in: page number */ + byte* body, /* in: log record body */ + byte* rec_end, /* in: log record end */ + ib_uint64_t start_lsn, /* in: start lsn of the mtr */ + ib_uint64_t end_lsn) /* in: end lsn of the mtr */ +{ + recv_t* recv; + ulint len; + recv_data_t* recv_data; + recv_data_t** prev_field; + recv_addr_t* recv_addr; + + if (fil_tablespace_deleted_or_being_deleted_in_mem(space, -1)) { + /* The tablespace does not exist any more: do not store the + log record */ + + return; + } + + len = rec_end - body; + + recv = mem_heap_alloc(recv_sys->heap, sizeof(recv_t)); + recv->type = type; + recv->len = rec_end - body; + recv->start_lsn = start_lsn; + recv->end_lsn = end_lsn; + + recv_addr = recv_get_fil_addr_struct(space, page_no); + + if (recv_addr == NULL) { + recv_addr = mem_heap_alloc(recv_sys->heap, + sizeof(recv_addr_t)); + recv_addr->space = space; + recv_addr->page_no = page_no; + recv_addr->state = RECV_NOT_PROCESSED; + + UT_LIST_INIT(recv_addr->rec_list); + + HASH_INSERT(recv_addr_t, addr_hash, recv_sys->addr_hash, + recv_fold(space, page_no), recv_addr); + recv_sys->n_addrs++; +#if 0 + fprintf(stderr, "Inserting log rec for space %lu, page %lu\n", + space, page_no); +#endif + } + + UT_LIST_ADD_LAST(rec_list, recv_addr->rec_list, recv); + + prev_field = &(recv->data); + + /* Store the log record body in chunks of less than UNIV_PAGE_SIZE: + recv_sys->heap grows into the buffer pool, and bigger chunks could not + be allocated */ + + while (rec_end > body) { + + len = rec_end - body; + + if (len > RECV_DATA_BLOCK_SIZE) { + len = RECV_DATA_BLOCK_SIZE; + } + + recv_data = mem_heap_alloc(recv_sys->heap, + sizeof(recv_data_t) + len); + *prev_field = recv_data; + + ut_memcpy(((byte*)recv_data) + sizeof(recv_data_t), body, len); + + prev_field = &(recv_data->next); + + body += len; + } + + *prev_field = NULL; +} + +/************************************************************************* +Copies the log record body from recv to buf. */ +static +void +recv_data_copy_to_buf( +/*==================*/ + byte* buf, /* in: buffer of length at least recv->len */ + recv_t* recv) /* in: log record */ +{ + recv_data_t* recv_data; + ulint part_len; + ulint len; + + len = recv->len; + recv_data = recv->data; + + while (len > 0) { + if (len > RECV_DATA_BLOCK_SIZE) { + part_len = RECV_DATA_BLOCK_SIZE; + } else { + part_len = len; + } + + ut_memcpy(buf, ((byte*)recv_data) + sizeof(recv_data_t), + part_len); + buf += part_len; + len -= part_len; + + recv_data = recv_data->next; + } +} + +/**************************************************************************** +Applies the hashed log records to the page, if the page lsn is less than the +lsn of a log record. This can be called when a buffer page has just been +read in, or also for a page already in the buffer pool. */ +UNIV_INTERN +void +recv_recover_page( +/*==============*/ + ibool recover_backup, + /* in: TRUE if we are recovering a backup + page: then we do not acquire any latches + since the page was read in outside the + buffer pool */ + ibool just_read_in, + /* in: TRUE if the i/o-handler calls this for + a freshly read page */ + buf_block_t* block) /* in: buffer block */ +{ + page_t* page; + recv_addr_t* recv_addr; + recv_t* recv; + byte* buf; + ib_uint64_t start_lsn; + ib_uint64_t end_lsn; + ib_uint64_t page_lsn; + ib_uint64_t page_newest_lsn; + ibool modification_to_page; + ibool success; + mtr_t mtr; + + mutex_enter(&(recv_sys->mutex)); + + if (recv_sys->apply_log_recs == FALSE) { + + /* Log records should not be applied now */ + + mutex_exit(&(recv_sys->mutex)); + + return; + } + + recv_addr = recv_get_fil_addr_struct(buf_block_get_space(block), + buf_block_get_page_no(block)); + + if ((recv_addr == NULL) + /* bugfix: http://bugs.mysql.com/bug.php?id=44140 */ + || (recv_addr->state == RECV_BEING_READ && !just_read_in) + || (recv_addr->state == RECV_BEING_PROCESSED) + || (recv_addr->state == RECV_PROCESSED)) { + + mutex_exit(&(recv_sys->mutex)); + + return; + } + +#if 0 + fprintf(stderr, "Recovering space %lu, page %lu\n", + buf_block_get_space(block), buf_block_get_page_no(block)); +#endif + + recv_addr->state = RECV_BEING_PROCESSED; + + mutex_exit(&(recv_sys->mutex)); + + mtr_start(&mtr); + mtr_set_log_mode(&mtr, MTR_LOG_NONE); + + page = block->frame; + + if (!recover_backup) { + if (just_read_in) { + /* Move the ownership of the x-latch on the + page to this OS thread, so that we can acquire + a second x-latch on it. This is needed for the + operations to the page to pass the debug + checks. */ + + rw_lock_x_lock_move_ownership(&(block->lock)); + } + + success = buf_page_get_known_nowait(RW_X_LATCH, block, + BUF_KEEP_OLD, + __FILE__, __LINE__, + &mtr); + ut_a(success); + + buf_block_dbg_add_level(block, SYNC_NO_ORDER_CHECK); + } + + /* Read the newest modification lsn from the page */ + page_lsn = mach_read_ull(page + FIL_PAGE_LSN); + + if (!recover_backup) { + /* It may be that the page has been modified in the buffer + pool: read the newest modification lsn there */ + + page_newest_lsn + = buf_page_get_newest_modification(&block->page); + + if (page_newest_lsn) { + + page_lsn = page_newest_lsn; + } + } else { + /* In recovery from a backup we do not really use the buffer + pool */ + + page_newest_lsn = 0; + } + + modification_to_page = FALSE; + start_lsn = end_lsn = 0; + + recv = UT_LIST_GET_FIRST(recv_addr->rec_list); + + while (recv) { + end_lsn = recv->end_lsn; + + if (recv->len > RECV_DATA_BLOCK_SIZE) { + /* We have to copy the record body to a separate + buffer */ + + buf = mem_alloc(recv->len); + + recv_data_copy_to_buf(buf, recv); + } else { + buf = ((byte*)(recv->data)) + sizeof(recv_data_t); + } + + if (recv->type == MLOG_INIT_FILE_PAGE) { + page_lsn = page_newest_lsn; + + mach_write_ull(page + UNIV_PAGE_SIZE + - FIL_PAGE_END_LSN_OLD_CHKSUM, 0); + mach_write_ull(page + FIL_PAGE_LSN, 0); + } + + if (recv->start_lsn >= page_lsn) { + + if (!modification_to_page) { + + modification_to_page = TRUE; + start_lsn = recv->start_lsn; + } + +#ifdef UNIV_DEBUG + if (log_debug_writes) { + fprintf(stderr, + "InnoDB: Applying log rec" + " type %lu len %lu" + " to space %lu page no %lu\n", + (ulong) recv->type, (ulong) recv->len, + (ulong) recv_addr->space, + (ulong) recv_addr->page_no); + } +#endif /* UNIV_DEBUG */ + + recv_parse_or_apply_log_rec_body(recv->type, buf, + buf + recv->len, + block, &mtr); + mach_write_ull(page + UNIV_PAGE_SIZE + - FIL_PAGE_END_LSN_OLD_CHKSUM, + recv->start_lsn + recv->len); + mach_write_ull(page + FIL_PAGE_LSN, + recv->start_lsn + recv->len); + } + + if (recv->len > RECV_DATA_BLOCK_SIZE) { + mem_free(buf); + } + + recv = UT_LIST_GET_NEXT(rec_list, recv); + } + +#ifdef UNIV_ZIP_DEBUG + if (fil_page_get_type(page) == FIL_PAGE_INDEX) { + page_zip_des_t* page_zip = buf_block_get_page_zip(block); + + if (page_zip) { + ut_a(page_zip_validate_low(page_zip, page, FALSE)); + } + } +#endif /* UNIV_ZIP_DEBUG */ + + mutex_enter(&(recv_sys->mutex)); + + if (recv_max_page_lsn < page_lsn) { + recv_max_page_lsn = page_lsn; + } + + recv_addr->state = RECV_PROCESSED; + + ut_a(recv_sys->n_addrs); + recv_sys->n_addrs--; + + mutex_exit(&(recv_sys->mutex)); + + if (!recover_backup && modification_to_page) { + ut_a(block); + + buf_flush_recv_note_modification(block, start_lsn, end_lsn); + } + + /* Make sure that committing mtr does not change the modification + lsn values of page */ + + mtr.modifications = FALSE; + + mtr_commit(&mtr); +} + +/*********************************************************************** +Reads in pages which have hashed log records, from an area around a given +page number. */ +static +ulint +recv_read_in_area( +/*==============*/ + /* out: number of pages found */ + ulint space, /* in: space */ + ulint zip_size,/* in: compressed page size in bytes, or 0 */ + ulint page_no)/* in: page number */ +{ + recv_addr_t* recv_addr; + ulint page_nos[RECV_READ_AHEAD_AREA]; + ulint low_limit; + ulint n; + + low_limit = page_no - (page_no % RECV_READ_AHEAD_AREA); + + n = 0; + + for (page_no = low_limit; page_no < low_limit + RECV_READ_AHEAD_AREA; + page_no++) { + recv_addr = recv_get_fil_addr_struct(space, page_no); + + if (recv_addr && !buf_page_peek(space, page_no)) { + + mutex_enter(&(recv_sys->mutex)); + + if (recv_addr->state == RECV_NOT_PROCESSED) { + recv_addr->state = RECV_BEING_READ; + + page_nos[n] = page_no; + + n++; + } + + mutex_exit(&(recv_sys->mutex)); + } + } + + buf_read_recv_pages(FALSE, space, zip_size, page_nos, n); + /* + fprintf(stderr, "Recv pages at %lu n %lu\n", page_nos[0], n); + */ + return(n); +} + +/*********************************************************************** +Empties the hash table of stored log records, applying them to appropriate +pages. */ +UNIV_INTERN +void +recv_apply_hashed_log_recs( +/*=======================*/ + ibool allow_ibuf) /* in: if TRUE, also ibuf operations are + allowed during the application; if FALSE, + no ibuf operations are allowed, and after + the application all file pages are flushed to + disk and invalidated in buffer pool: this + alternative means that no new log records + can be generated during the application; + the caller must in this case own the log + mutex */ +{ + recv_addr_t* recv_addr; + ulint i; + ulint n_pages; + ibool has_printed = FALSE; + mtr_t mtr; +loop: + mutex_enter(&(recv_sys->mutex)); + + if (recv_sys->apply_batch_on) { + + mutex_exit(&(recv_sys->mutex)); + + os_thread_sleep(500000); + + goto loop; + } + + ut_ad(!allow_ibuf == mutex_own(&log_sys->mutex)); + + if (!allow_ibuf) { + recv_no_ibuf_operations = TRUE; + } + + recv_sys->apply_log_recs = TRUE; + recv_sys->apply_batch_on = TRUE; + + for (i = 0; i < hash_get_n_cells(recv_sys->addr_hash); i++) { + + recv_addr = HASH_GET_FIRST(recv_sys->addr_hash, i); + + while (recv_addr) { + ulint space = recv_addr->space; + ulint zip_size = fil_space_get_zip_size(space); + ulint page_no = recv_addr->page_no; + + if (recv_addr->state == RECV_NOT_PROCESSED) { + if (!has_printed) { + ut_print_timestamp(stderr); + fputs(" InnoDB: Starting an" + " apply batch of log records" + " to the database...\n" + "InnoDB: Progress in percents: ", + stderr); + has_printed = TRUE; + } + + mutex_exit(&(recv_sys->mutex)); + + if (buf_page_peek(space, page_no)) { + buf_block_t* block; + + mtr_start(&mtr); + + block = buf_page_get( + space, zip_size, page_no, + RW_X_LATCH, &mtr); + buf_block_dbg_add_level( + block, SYNC_NO_ORDER_CHECK); + + recv_recover_page(FALSE, FALSE, block); + mtr_commit(&mtr); + } else { + recv_read_in_area(space, zip_size, + page_no); + } + + mutex_enter(&(recv_sys->mutex)); + } + + recv_addr = HASH_GET_NEXT(addr_hash, recv_addr); + } + + if (has_printed + && (i * 100) / hash_get_n_cells(recv_sys->addr_hash) + != ((i + 1) * 100) + / hash_get_n_cells(recv_sys->addr_hash)) { + + fprintf(stderr, "%lu ", (ulong) + ((i * 100) + / hash_get_n_cells(recv_sys->addr_hash))); + } + } + + /* Wait until all the pages have been processed */ + + while (recv_sys->n_addrs != 0) { + + mutex_exit(&(recv_sys->mutex)); + + os_thread_sleep(500000); + + mutex_enter(&(recv_sys->mutex)); + } + + if (has_printed) { + + fprintf(stderr, "\n"); + } + + if (!allow_ibuf) { + /* Flush all the file pages to disk and invalidate them in + the buffer pool */ + + mutex_exit(&(recv_sys->mutex)); + mutex_exit(&(log_sys->mutex)); + + n_pages = buf_flush_batch(BUF_FLUSH_LIST, ULINT_MAX, + IB_ULONGLONG_MAX); + ut_a(n_pages != ULINT_UNDEFINED); + + buf_flush_wait_batch_end(BUF_FLUSH_LIST); + + buf_pool_invalidate(); + + mutex_enter(&(log_sys->mutex)); + mutex_enter(&(recv_sys->mutex)); + + recv_no_ibuf_operations = FALSE; + } + + recv_sys->apply_log_recs = FALSE; + recv_sys->apply_batch_on = FALSE; + + recv_sys_empty_hash(); + + if (has_printed) { + fprintf(stderr, "InnoDB: Apply batch completed\n"); + } + + mutex_exit(&(recv_sys->mutex)); +} + +#ifdef UNIV_HOTBACKUP +/*********************************************************************** +Applies log records in the hash table to a backup. */ +UNIV_INTERN +void +recv_apply_log_recs_for_backup(void) +/*================================*/ +{ + recv_addr_t* recv_addr; + ulint n_hash_cells; + buf_block_t* block; + ulint actual_size; + ibool success; + ulint error; + ulint i; + + recv_sys->apply_log_recs = TRUE; + recv_sys->apply_batch_on = TRUE; + + block = buf_LRU_get_free_block(UNIV_PAGE_SIZE); + + fputs("InnoDB: Starting an apply batch of log records" + " to the database...\n" + "InnoDB: Progress in percents: ", stderr); + + n_hash_cells = hash_get_n_cells(recv_sys->addr_hash); + + for (i = 0; i < n_hash_cells; i++) { + /* The address hash table is externally chained */ + recv_addr = hash_get_nth_cell(recv_sys->addr_hash, i)->node; + + while (recv_addr != NULL) { + + ulint zip_size + = fil_space_get_zip_size(recv_addr->space); + + if (zip_size == ULINT_UNDEFINED) { +#if 0 + fprintf(stderr, + "InnoDB: Warning: cannot apply" + " log record to" + " tablespace %lu page %lu,\n" + "InnoDB: because tablespace with" + " that id does not exist.\n", + recv_addr->space, recv_addr->page_no); +#endif + recv_addr->state = RECV_PROCESSED; + + ut_a(recv_sys->n_addrs); + recv_sys->n_addrs--; + + goto skip_this_recv_addr; + } + + /* We simulate a page read made by the buffer pool, to + make sure the recovery apparatus works ok. We must init + the block. */ + + buf_page_init_for_backup_restore( + recv_addr->space, recv_addr->page_no, + zip_size, block); + + /* Extend the tablespace's last file if the page_no + does not fall inside its bounds; we assume the last + file is auto-extending, and ibbackup copied the file + when it still was smaller */ + + success = fil_extend_space_to_desired_size( + &actual_size, + recv_addr->space, recv_addr->page_no + 1); + if (!success) { + fprintf(stderr, + "InnoDB: Fatal error: cannot extend" + " tablespace %lu to hold %lu pages\n", + recv_addr->space, recv_addr->page_no); + + exit(1); + } + + /* Read the page from the tablespace file using the + fil0fil.c routines */ + + if (zip_size) { + error = fil_io(OS_FILE_READ, TRUE, + recv_addr->space, zip_size, + recv_addr->page_no, 0, zip_size, + block->page.zip.data, NULL); + } else { + error = fil_io(OS_FILE_READ, TRUE, + recv_addr->space, 0, + recv_addr->page_no, 0, + UNIV_PAGE_SIZE, + block->frame, NULL); + } + + if (error != DB_SUCCESS) { + fprintf(stderr, + "InnoDB: Fatal error: cannot read" + " from tablespace" + " %lu page number %lu\n", + (ulong) recv_addr->space, + (ulong) recv_addr->page_no); + + exit(1); + } + + /* Apply the log records to this page */ + recv_recover_page(TRUE, FALSE, block); + + /* Write the page back to the tablespace file using the + fil0fil.c routines */ + + buf_flush_init_for_writing( + block->frame, buf_block_get_page_zip(block), + mach_read_ull(block->frame + FIL_PAGE_LSN)); + + if (zip_size) { + error = fil_io(OS_FILE_WRITE, TRUE, + recv_addr->space, zip_size, + recv_addr->page_no, 0, + zip_size, + block->page.zip.data, NULL); + } else { + error = fil_io(OS_FILE_WRITE, TRUE, + recv_addr->space, 0, + recv_addr->page_no, 0, + UNIV_PAGE_SIZE, + block->frame, NULL); + } +skip_this_recv_addr: + recv_addr = HASH_GET_NEXT(addr_hash, recv_addr); + } + + if ((100 * i) / n_hash_cells + != (100 * (i + 1)) / n_hash_cells) { + fprintf(stderr, "%lu ", + (ulong) ((100 * i) / n_hash_cells)); + fflush(stderr); + } + } + + buf_block_free(block); + recv_sys_empty_hash(); +} +#endif /* UNIV_HOTBACKUP */ + +/*********************************************************************** +Tries to parse a single log record and returns its length. */ +static +ulint +recv_parse_log_rec( +/*===============*/ + /* out: length of the record, or 0 if the record was + not complete */ + byte* ptr, /* in: pointer to a buffer */ + byte* end_ptr,/* in: pointer to the buffer end */ + byte* type, /* out: type */ + ulint* space, /* out: space id */ + ulint* page_no,/* out: page number */ + byte** body) /* out: log record body start */ +{ + byte* new_ptr; + + *body = NULL; + + if (ptr == end_ptr) { + + return(0); + } + + if (*ptr == MLOG_MULTI_REC_END) { + + *type = *ptr; + + return(1); + } + + if (*ptr == MLOG_DUMMY_RECORD) { + *type = *ptr; + + *space = ULINT_UNDEFINED - 1; /* For debugging */ + + return(1); + } + + new_ptr = mlog_parse_initial_log_record(ptr, end_ptr, type, space, + page_no); + *body = new_ptr; + + if (UNIV_UNLIKELY(!new_ptr)) { + + return(0); + } + + /* Check that page_no is sensible */ + + if (UNIV_UNLIKELY(*page_no > 0x8FFFFFFFUL)) { + + recv_sys->found_corrupt_log = TRUE; + + return(0); + } + + new_ptr = recv_parse_or_apply_log_rec_body(*type, new_ptr, end_ptr, + NULL, NULL); + if (UNIV_UNLIKELY(new_ptr == NULL)) { + + return(0); + } + + if (*page_no > recv_max_parsed_page_no) { + recv_max_parsed_page_no = *page_no; + } + + return(new_ptr - ptr); +} + +/*********************************************************** +Calculates the new value for lsn when more data is added to the log. */ +static +ib_uint64_t +recv_calc_lsn_on_data_add( +/*======================*/ + ib_uint64_t lsn, /* in: old lsn */ + ib_uint64_t len) /* in: this many bytes of data is + added, log block headers not included */ +{ + ulint frag_len; + ulint lsn_len; + + frag_len = (((ulint) lsn) % OS_FILE_LOG_BLOCK_SIZE) + - LOG_BLOCK_HDR_SIZE; + ut_ad(frag_len < OS_FILE_LOG_BLOCK_SIZE - LOG_BLOCK_HDR_SIZE + - LOG_BLOCK_TRL_SIZE); + lsn_len = (ulint) len; + lsn_len += (lsn_len + frag_len) + / (OS_FILE_LOG_BLOCK_SIZE - LOG_BLOCK_HDR_SIZE + - LOG_BLOCK_TRL_SIZE) + * (LOG_BLOCK_HDR_SIZE + LOG_BLOCK_TRL_SIZE); + + return(lsn + lsn_len); +} + +#ifdef UNIV_LOG_DEBUG +/*********************************************************** +Checks that the parser recognizes incomplete initial segments of a log +record as incomplete. */ +static +void +recv_check_incomplete_log_recs( +/*===========================*/ + byte* ptr, /* in: pointer to a complete log record */ + ulint len) /* in: length of the log record */ +{ + ulint i; + byte type; + ulint space; + ulint page_no; + byte* body; + + for (i = 0; i < len; i++) { + ut_a(0 == recv_parse_log_rec(ptr, ptr + i, &type, &space, + &page_no, &body)); + } +} +#endif /* UNIV_LOG_DEBUG */ + +/*********************************************************** +Prints diagnostic info of corrupt log. */ +static +void +recv_report_corrupt_log( +/*====================*/ + byte* ptr, /* in: pointer to corrupt log record */ + byte type, /* in: type of the record */ + ulint space, /* in: space id, this may also be garbage */ + ulint page_no)/* in: page number, this may also be garbage */ +{ + fprintf(stderr, + "InnoDB: ############### CORRUPT LOG RECORD FOUND\n" + "InnoDB: Log record type %lu, space id %lu, page number %lu\n" + "InnoDB: Log parsing proceeded successfully up to %llu\n" + "InnoDB: Previous log record type %lu, is multi %lu\n" + "InnoDB: Recv offset %lu, prev %lu\n", + (ulong) type, (ulong) space, (ulong) page_no, + recv_sys->recovered_lsn, + (ulong) recv_previous_parsed_rec_type, + (ulong) recv_previous_parsed_rec_is_multi, + (ulong) (ptr - recv_sys->buf), + (ulong) recv_previous_parsed_rec_offset); + + if ((ulint)(ptr - recv_sys->buf + 100) + > recv_previous_parsed_rec_offset + && (ulint)(ptr - recv_sys->buf + 100 + - recv_previous_parsed_rec_offset) + < 200000) { + fputs("InnoDB: Hex dump of corrupt log starting" + " 100 bytes before the start\n" + "InnoDB: of the previous log rec,\n" + "InnoDB: and ending 100 bytes after the start" + " of the corrupt rec:\n", + stderr); + + ut_print_buf(stderr, + recv_sys->buf + + recv_previous_parsed_rec_offset - 100, + ptr - recv_sys->buf + 200 + - recv_previous_parsed_rec_offset); + putc('\n', stderr); + } + + fputs("InnoDB: WARNING: the log file may have been corrupt and it\n" + "InnoDB: is possible that the log scan did not proceed\n" + "InnoDB: far enough in recovery! Please run CHECK TABLE\n" + "InnoDB: on your InnoDB tables to check that they are ok!\n" + "InnoDB: If mysqld crashes after this recovery, look at\n" + "InnoDB: http://dev.mysql.com/doc/refman/5.1/en/" + "forcing-recovery.html\n" + "InnoDB: about forcing recovery.\n", stderr); + + fflush(stderr); +} + +/*********************************************************** +Parses log records from a buffer and stores them to a hash table to wait +merging to file pages. */ +static +ibool +recv_parse_log_recs( +/*================*/ + /* out: currently always returns FALSE */ + ibool store_to_hash) /* in: TRUE if the records should be stored + to the hash table; this is set to FALSE if just + debug checking is needed */ +{ + byte* ptr; + byte* end_ptr; + ulint single_rec; + ulint len; + ulint total_len; + ib_uint64_t new_recovered_lsn; + ib_uint64_t old_lsn; + byte type; + ulint space; + ulint page_no; + byte* body; + ulint n_recs; + + ut_ad(mutex_own(&(log_sys->mutex))); + ut_ad(recv_sys->parse_start_lsn != 0); +loop: + ptr = recv_sys->buf + recv_sys->recovered_offset; + + end_ptr = recv_sys->buf + recv_sys->len; + + if (ptr == end_ptr) { + + return(FALSE); + } + + single_rec = (ulint)*ptr & MLOG_SINGLE_REC_FLAG; + + if (single_rec || *ptr == MLOG_DUMMY_RECORD) { + /* The mtr only modified a single page, or this is a file op */ + + old_lsn = recv_sys->recovered_lsn; + + /* Try to parse a log record, fetching its type, space id, + page no, and a pointer to the body of the log record */ + + len = recv_parse_log_rec(ptr, end_ptr, &type, &space, + &page_no, &body); + + if (len == 0 || recv_sys->found_corrupt_log) { + if (recv_sys->found_corrupt_log) { + + recv_report_corrupt_log(ptr, + type, space, page_no); + } + + return(FALSE); + } + + new_recovered_lsn = recv_calc_lsn_on_data_add(old_lsn, len); + + if (new_recovered_lsn > recv_sys->scanned_lsn) { + /* The log record filled a log block, and we require + that also the next log block should have been scanned + in */ + + return(FALSE); + } + + recv_previous_parsed_rec_type = (ulint)type; + recv_previous_parsed_rec_offset = recv_sys->recovered_offset; + recv_previous_parsed_rec_is_multi = 0; + + recv_sys->recovered_offset += len; + recv_sys->recovered_lsn = new_recovered_lsn; + +#ifdef UNIV_DEBUG + if (log_debug_writes) { + fprintf(stderr, + "InnoDB: Parsed a single log rec" + " type %lu len %lu space %lu page no %lu\n", + (ulong) type, (ulong) len, (ulong) space, + (ulong) page_no); + } +#endif /* UNIV_DEBUG */ + + if (type == MLOG_DUMMY_RECORD) { + /* Do nothing */ + + } else if (!store_to_hash) { + /* In debug checking, update a replicate page + according to the log record, and check that it + becomes identical with the original page */ +#ifdef UNIV_LOG_DEBUG + recv_check_incomplete_log_recs(ptr, len); +#endif/* UNIV_LOG_DEBUG */ + + } else if (type == MLOG_FILE_CREATE + || type == MLOG_FILE_CREATE2 + || type == MLOG_FILE_RENAME + || type == MLOG_FILE_DELETE) { + ut_a(space); +#ifdef UNIV_HOTBACKUP + if (recv_replay_file_ops) { + + /* In ibbackup --apply-log, replay an .ibd file + operation, if possible; note that + fil_path_to_mysql_datadir is set in ibbackup to + point to the datadir we should use there */ + + if (NULL == fil_op_log_parse_or_replay( + body, end_ptr, type, space)) { + fprintf(stderr, + "InnoDB: Error: file op" + " log record of type %lu" + " space %lu not complete in\n" + "InnoDB: the replay phase." + " Path %s\n", + (ulint)type, space, + (char*)(body + 2)); + + ut_error; + } + } +#endif + /* In normal mysqld crash recovery we do not try to + replay file operations */ + } else { + recv_add_to_hash_table(type, space, page_no, body, + ptr + len, old_lsn, + recv_sys->recovered_lsn); + } + } else { + /* Check that all the records associated with the single mtr + are included within the buffer */ + + total_len = 0; + n_recs = 0; + + for (;;) { + len = recv_parse_log_rec(ptr, end_ptr, &type, &space, + &page_no, &body); + if (len == 0 || recv_sys->found_corrupt_log) { + + if (recv_sys->found_corrupt_log) { + + recv_report_corrupt_log( + ptr, type, space, page_no); + } + + return(FALSE); + } + + recv_previous_parsed_rec_type = (ulint)type; + recv_previous_parsed_rec_offset + = recv_sys->recovered_offset + total_len; + recv_previous_parsed_rec_is_multi = 1; + + if ((!store_to_hash) && (type != MLOG_MULTI_REC_END)) { +#ifdef UNIV_LOG_DEBUG + recv_check_incomplete_log_recs(ptr, len); +#endif /* UNIV_LOG_DEBUG */ + } + +#ifdef UNIV_DEBUG + if (log_debug_writes) { + fprintf(stderr, + "InnoDB: Parsed a multi log rec" + " type %lu len %lu" + " space %lu page no %lu\n", + (ulong) type, (ulong) len, + (ulong) space, (ulong) page_no); + } +#endif /* UNIV_DEBUG */ + + total_len += len; + n_recs++; + + ptr += len; + + if (type == MLOG_MULTI_REC_END) { + + /* Found the end mark for the records */ + + break; + } + } + + new_recovered_lsn = recv_calc_lsn_on_data_add( + recv_sys->recovered_lsn, total_len); + + if (new_recovered_lsn > recv_sys->scanned_lsn) { + /* The log record filled a log block, and we require + that also the next log block should have been scanned + in */ + + return(FALSE); + } + + /* Add all the records to the hash table */ + + ptr = recv_sys->buf + recv_sys->recovered_offset; + + for (;;) { + old_lsn = recv_sys->recovered_lsn; + len = recv_parse_log_rec(ptr, end_ptr, &type, &space, + &page_no, &body); + if (recv_sys->found_corrupt_log) { + + recv_report_corrupt_log(ptr, + type, space, page_no); + } + + ut_a(len != 0); + ut_a(0 == ((ulint)*ptr & MLOG_SINGLE_REC_FLAG)); + + recv_sys->recovered_offset += len; + recv_sys->recovered_lsn + = recv_calc_lsn_on_data_add(old_lsn, len); + if (type == MLOG_MULTI_REC_END) { + + /* Found the end mark for the records */ + + break; + } + + if (store_to_hash) { + recv_add_to_hash_table(type, space, page_no, + body, ptr + len, + old_lsn, + new_recovered_lsn); + } + + ptr += len; + } + } + + goto loop; +} + +/*********************************************************** +Adds data from a new log block to the parsing buffer of recv_sys if +recv_sys->parse_start_lsn is non-zero. */ +static +ibool +recv_sys_add_to_parsing_buf( +/*========================*/ + /* out: TRUE if more data added */ + byte* log_block, /* in: log block */ + ib_uint64_t scanned_lsn) /* in: lsn of how far we were able + to find data in this log block */ +{ + ulint more_len; + ulint data_len; + ulint start_offset; + ulint end_offset; + + ut_ad(scanned_lsn >= recv_sys->scanned_lsn); + + if (!recv_sys->parse_start_lsn) { + /* Cannot start parsing yet because no start point for + it found */ + + return(FALSE); + } + + data_len = log_block_get_data_len(log_block); + + if (recv_sys->parse_start_lsn >= scanned_lsn) { + + return(FALSE); + + } else if (recv_sys->scanned_lsn >= scanned_lsn) { + + return(FALSE); + + } else if (recv_sys->parse_start_lsn > recv_sys->scanned_lsn) { + more_len = (ulint) (scanned_lsn - recv_sys->parse_start_lsn); + } else { + more_len = (ulint) (scanned_lsn - recv_sys->scanned_lsn); + } + + if (more_len == 0) { + + return(FALSE); + } + + ut_ad(data_len >= more_len); + + start_offset = data_len - more_len; + + if (start_offset < LOG_BLOCK_HDR_SIZE) { + start_offset = LOG_BLOCK_HDR_SIZE; + } + + end_offset = data_len; + + if (end_offset > OS_FILE_LOG_BLOCK_SIZE - LOG_BLOCK_TRL_SIZE) { + end_offset = OS_FILE_LOG_BLOCK_SIZE - LOG_BLOCK_TRL_SIZE; + } + + ut_ad(start_offset <= end_offset); + + if (start_offset < end_offset) { + ut_memcpy(recv_sys->buf + recv_sys->len, + log_block + start_offset, end_offset - start_offset); + + recv_sys->len += end_offset - start_offset; + + ut_a(recv_sys->len <= RECV_PARSING_BUF_SIZE); + } + + return(TRUE); +} + +/*********************************************************** +Moves the parsing buffer data left to the buffer start. */ +static +void +recv_sys_justify_left_parsing_buf(void) +/*===================================*/ +{ + ut_memmove(recv_sys->buf, recv_sys->buf + recv_sys->recovered_offset, + recv_sys->len - recv_sys->recovered_offset); + + recv_sys->len -= recv_sys->recovered_offset; + + recv_sys->recovered_offset = 0; +} + +/*********************************************************** +Scans log from a buffer and stores new log data to the parsing buffer. Parses +and hashes the log records if new data found. */ +UNIV_INTERN +ibool +recv_scan_log_recs( +/*===============*/ + /* out: TRUE if limit_lsn has been + reached, or not able to scan any more + in this log group */ + ibool apply_automatically,/* in: TRUE if we want this + function to apply log records + automatically when the hash table + becomes full; in the hot backup tool + the tool does the applying, not this + function */ + ulint available_memory,/* in: we let the hash table of recs + to grow to this size, at the maximum */ + ibool store_to_hash, /* in: TRUE if the records should be + stored to the hash table; this is set + to FALSE if just debug checking is + needed */ + byte* buf, /* in: buffer containing a log segment + or garbage */ + ulint len, /* in: buffer length */ + ib_uint64_t start_lsn, /* in: buffer start lsn */ + ib_uint64_t* contiguous_lsn, /* in/out: it is known that all log + groups contain contiguous log data up + to this lsn */ + ib_uint64_t* group_scanned_lsn)/* out: scanning succeeded up to + this lsn */ +{ + byte* log_block; + ulint no; + ib_uint64_t scanned_lsn; + ibool finished; + ulint data_len; + ibool more_data; + + ut_ad(start_lsn % OS_FILE_LOG_BLOCK_SIZE == 0); + ut_ad(len % OS_FILE_LOG_BLOCK_SIZE == 0); + ut_ad(len > 0); + ut_a(apply_automatically <= TRUE); + ut_a(store_to_hash <= TRUE); + + finished = FALSE; + + log_block = buf; + scanned_lsn = start_lsn; + more_data = FALSE; + + while (log_block < buf + len && !finished) { + + no = log_block_get_hdr_no(log_block); + /* + fprintf(stderr, "Log block header no %lu\n", no); + + fprintf(stderr, "Scanned lsn no %lu\n", + log_block_convert_lsn_to_no(scanned_lsn)); + */ + if (no != log_block_convert_lsn_to_no(scanned_lsn) + || !log_block_checksum_is_ok_or_old_format(log_block)) { + + if (no == log_block_convert_lsn_to_no(scanned_lsn) + && !log_block_checksum_is_ok_or_old_format( + log_block)) { + fprintf(stderr, + "InnoDB: Log block no %lu at" + " lsn %llu has\n" + "InnoDB: ok header, but checksum field" + " contains %lu, should be %lu\n", + (ulong) no, + scanned_lsn, + (ulong) log_block_get_checksum( + log_block), + (ulong) log_block_calc_checksum( + log_block)); + } + + /* Garbage or an incompletely written log block */ + + finished = TRUE; + + break; + } + + if (log_block_get_flush_bit(log_block)) { + /* This block was a start of a log flush operation: + we know that the previous flush operation must have + been completed for all log groups before this block + can have been flushed to any of the groups. Therefore, + we know that log data is contiguous up to scanned_lsn + in all non-corrupt log groups. */ + + if (scanned_lsn > *contiguous_lsn) { + *contiguous_lsn = scanned_lsn; + } + } + + data_len = log_block_get_data_len(log_block); + + if ((store_to_hash || (data_len == OS_FILE_LOG_BLOCK_SIZE)) + && scanned_lsn + data_len > recv_sys->scanned_lsn + && (recv_sys->scanned_checkpoint_no > 0) + && (log_block_get_checkpoint_no(log_block) + < recv_sys->scanned_checkpoint_no) + && (recv_sys->scanned_checkpoint_no + - log_block_get_checkpoint_no(log_block) + > 0x80000000UL)) { + + /* Garbage from a log buffer flush which was made + before the most recent database recovery */ + + finished = TRUE; +#ifdef UNIV_LOG_DEBUG + /* This is not really an error, but currently + we stop here in the debug version: */ + + ut_error; +#endif + break; + } + + if (!recv_sys->parse_start_lsn + && (log_block_get_first_rec_group(log_block) > 0)) { + + /* We found a point from which to start the parsing + of log records */ + + recv_sys->parse_start_lsn = scanned_lsn + + log_block_get_first_rec_group(log_block); + recv_sys->scanned_lsn = recv_sys->parse_start_lsn; + recv_sys->recovered_lsn = recv_sys->parse_start_lsn; + } + + scanned_lsn += data_len; + + if (scanned_lsn > recv_sys->scanned_lsn) { + + /* We have found more entries. If this scan is + of startup type, we must initiate crash recovery + environment before parsing these log records. */ + + if (recv_log_scan_is_startup_type + && !recv_needed_recovery) { + + fprintf(stderr, + "InnoDB: Log scan progressed" + " past the checkpoint lsn %llu\n", + recv_sys->scanned_lsn); + recv_init_crash_recovery(); + } + + /* We were able to find more log data: add it to the + parsing buffer if parse_start_lsn is already + non-zero */ + + if (recv_sys->len + 4 * OS_FILE_LOG_BLOCK_SIZE + >= RECV_PARSING_BUF_SIZE) { + fprintf(stderr, + "InnoDB: Error: log parsing" + " buffer overflow." + " Recovery may have failed!\n"); + + recv_sys->found_corrupt_log = TRUE; + + } else if (!recv_sys->found_corrupt_log) { + more_data = recv_sys_add_to_parsing_buf( + log_block, scanned_lsn); + } + + recv_sys->scanned_lsn = scanned_lsn; + recv_sys->scanned_checkpoint_no + = log_block_get_checkpoint_no(log_block); + } + + if (data_len < OS_FILE_LOG_BLOCK_SIZE) { + /* Log data for this group ends here */ + + finished = TRUE; + } else { + log_block += OS_FILE_LOG_BLOCK_SIZE; + } + } + + *group_scanned_lsn = scanned_lsn; + + if (recv_needed_recovery + || (recv_is_from_backup && !recv_is_making_a_backup)) { + recv_scan_print_counter++; + + if (finished || (recv_scan_print_counter % 80 == 0)) { + + fprintf(stderr, + "InnoDB: Doing recovery: scanned up to" + " log sequence number %llu\n", + *group_scanned_lsn); + } + } + + if (more_data && !recv_sys->found_corrupt_log) { + /* Try to parse more log records */ + + recv_parse_log_recs(store_to_hash); + + if (store_to_hash && mem_heap_get_size(recv_sys->heap) + > available_memory + && apply_automatically) { + + /* Hash table of log records has grown too big: + empty it; FALSE means no ibuf operations + allowed, as we cannot add new records to the + log yet: they would be produced by ibuf + operations */ + + recv_apply_hashed_log_recs(FALSE); + } + + if (recv_sys->recovered_offset > RECV_PARSING_BUF_SIZE / 4) { + /* Move parsing buffer data to the buffer start */ + + recv_sys_justify_left_parsing_buf(); + } + } + + return(finished); +} + +/*********************************************************** +Scans log from a buffer and stores new log data to the parsing buffer. Parses +and hashes the log records if new data found. */ +static +void +recv_group_scan_log_recs( +/*=====================*/ + log_group_t* group, /* in: log group */ + ib_uint64_t* contiguous_lsn, /* in/out: it is known that all log + groups contain contiguous log data up + to this lsn */ + ib_uint64_t* group_scanned_lsn)/* out: scanning succeeded up to + this lsn */ +{ + ibool finished; + ib_uint64_t start_lsn; + ib_uint64_t end_lsn; + + finished = FALSE; + + start_lsn = *contiguous_lsn; + + while (!finished) { + end_lsn = start_lsn + RECV_SCAN_SIZE; + + log_group_read_log_seg(LOG_RECOVER, log_sys->buf, + group, start_lsn, end_lsn); + + finished = recv_scan_log_recs( + TRUE, (buf_pool->curr_size - recv_n_pool_free_frames) + * UNIV_PAGE_SIZE, TRUE, log_sys->buf, RECV_SCAN_SIZE, + start_lsn, contiguous_lsn, group_scanned_lsn); + start_lsn = end_lsn; + } + +#ifdef UNIV_DEBUG + if (log_debug_writes) { + fprintf(stderr, + "InnoDB: Scanned group %lu up to" + " log sequence number %llu\n", + (ulong) group->id, + *group_scanned_lsn); + } +#endif /* UNIV_DEBUG */ +} + +/*********************************************************** +Initialize crash recovery environment. Can be called iff +recv_needed_recovery == FALSE. */ +static +void +recv_init_crash_recovery(void) +/*==========================*/ +{ + ut_a(!recv_needed_recovery); + + recv_needed_recovery = TRUE; + + ut_print_timestamp(stderr); + + fprintf(stderr, + " InnoDB: Database was not" + " shut down normally!\n" + "InnoDB: Starting crash recovery.\n"); + + fprintf(stderr, + "InnoDB: Reading tablespace information" + " from the .ibd files...\n"); + + fil_load_single_table_tablespaces(); + + /* If we are using the doublewrite method, we will + check if there are half-written pages in data files, + and restore them from the doublewrite buffer if + possible */ + + if (srv_force_recovery < SRV_FORCE_NO_LOG_REDO) { + + fprintf(stderr, + "InnoDB: Restoring possible" + " half-written data pages from" + " the doublewrite\n" + "InnoDB: buffer...\n"); + trx_sys_doublewrite_init_or_restore_pages(TRUE); + } +} + +/************************************************************ +Recovers from a checkpoint. When this function returns, the database is able +to start processing of new user transactions, but the function +recv_recovery_from_checkpoint_finish should be called later to complete +the recovery and free the resources used in it. */ +UNIV_INTERN +ulint +recv_recovery_from_checkpoint_start_func( +/*=====================================*/ + /* out: error code or DB_SUCCESS */ +#ifdef UNIV_LOG_ARCHIVE + ulint type, /* in: LOG_CHECKPOINT or LOG_ARCHIVE */ + ib_uint64_t limit_lsn, /* in: recover up to this lsn + if possible */ +#endif /* UNIV_LOG_ARCHIVE */ + ib_uint64_t min_flushed_lsn,/* in: min flushed lsn from + data files */ + ib_uint64_t max_flushed_lsn)/* in: max flushed lsn from + data files */ +{ + log_group_t* group; + log_group_t* max_cp_group; + log_group_t* up_to_date_group; + ulint max_cp_field; + ib_uint64_t checkpoint_lsn; + ib_uint64_t checkpoint_no; + ib_uint64_t old_scanned_lsn; + ib_uint64_t group_scanned_lsn; + ib_uint64_t contiguous_lsn; + ib_uint64_t archived_lsn; + byte* buf; + byte log_hdr_buf[LOG_FILE_HDR_SIZE]; + ulint err; + +#ifdef UNIV_LOG_ARCHIVE + ut_ad(type != LOG_CHECKPOINT || limit_lsn == IB_ULONGLONG_MAX); +# define TYPE_CHECKPOINT (type == LOG_CHECKPOINT) +# define LIMIT_LSN limit_lsn +#else /* UNIV_LOG_ARCHIVE */ +# define TYPE_CHECKPOINT 1 +# define LIMIT_LSN IB_ULONGLONG_MAX +#endif /* UNIV_LOG_ARCHIVE */ + + if (TYPE_CHECKPOINT) { + recv_sys_create(); + recv_sys_init(FALSE, buf_pool_get_curr_size()); + } + + if (srv_force_recovery >= SRV_FORCE_NO_LOG_REDO) { + fprintf(stderr, + "InnoDB: The user has set SRV_FORCE_NO_LOG_REDO on\n"); + fprintf(stderr, + "InnoDB: Skipping log redo\n"); + + return(DB_SUCCESS); + } + + recv_recovery_on = TRUE; + + recv_sys->limit_lsn = LIMIT_LSN; + + mutex_enter(&(log_sys->mutex)); + + /* Look for the latest checkpoint from any of the log groups */ + + err = recv_find_max_checkpoint(&max_cp_group, &max_cp_field); + + if (err != DB_SUCCESS) { + + mutex_exit(&(log_sys->mutex)); + + return(err); + } + + log_group_read_checkpoint_info(max_cp_group, max_cp_field); + + buf = log_sys->checkpoint_buf; + + checkpoint_lsn = mach_read_ull(buf + LOG_CHECKPOINT_LSN); + checkpoint_no = mach_read_ull(buf + LOG_CHECKPOINT_NO); + archived_lsn = mach_read_ull(buf + LOG_CHECKPOINT_ARCHIVED_LSN); + + /* Read the first log file header to print a note if this is + a recovery from a restored InnoDB Hot Backup */ + + fil_io(OS_FILE_READ | OS_FILE_LOG, TRUE, max_cp_group->space_id, 0, + 0, 0, LOG_FILE_HDR_SIZE, + log_hdr_buf, max_cp_group); + + if (0 == ut_memcmp(log_hdr_buf + LOG_FILE_WAS_CREATED_BY_HOT_BACKUP, + (byte*)"ibbackup", (sizeof "ibbackup") - 1)) { + /* This log file was created by ibbackup --restore: print + a note to the user about it */ + + fprintf(stderr, + "InnoDB: The log file was created by" + " ibbackup --apply-log at\n" + "InnoDB: %s\n", + log_hdr_buf + LOG_FILE_WAS_CREATED_BY_HOT_BACKUP); + fprintf(stderr, + "InnoDB: NOTE: the following crash recovery" + " is part of a normal restore.\n"); + + /* Wipe over the label now */ + + memset(log_hdr_buf + LOG_FILE_WAS_CREATED_BY_HOT_BACKUP, + ' ', 4); + /* Write to the log file to wipe over the label */ + fil_io(OS_FILE_WRITE | OS_FILE_LOG, TRUE, + max_cp_group->space_id, 0, + 0, 0, OS_FILE_LOG_BLOCK_SIZE, + log_hdr_buf, max_cp_group); + } + +#ifdef UNIV_LOG_ARCHIVE + group = UT_LIST_GET_FIRST(log_sys->log_groups); + + while (group) { + log_checkpoint_get_nth_group_info(buf, group->id, + &(group->archived_file_no), + &(group->archived_offset)); + + group = UT_LIST_GET_NEXT(log_groups, group); + } +#endif /* UNIV_LOG_ARCHIVE */ + + if (TYPE_CHECKPOINT) { + /* Start reading the log groups from the checkpoint lsn up. The + variable contiguous_lsn contains an lsn up to which the log is + known to be contiguously written to all log groups. */ + + recv_sys->parse_start_lsn = checkpoint_lsn; + recv_sys->scanned_lsn = checkpoint_lsn; + recv_sys->scanned_checkpoint_no = 0; + recv_sys->recovered_lsn = checkpoint_lsn; + + srv_start_lsn = checkpoint_lsn; + } + + contiguous_lsn = ut_uint64_align_down(recv_sys->scanned_lsn, + OS_FILE_LOG_BLOCK_SIZE); + if (TYPE_CHECKPOINT) { + up_to_date_group = max_cp_group; +#ifdef UNIV_LOG_ARCHIVE + } else { + ulint capacity; + + /* Try to recover the remaining part from logs: first from + the logs of the archived group */ + + group = recv_sys->archive_group; + capacity = log_group_get_capacity(group); + + if (recv_sys->scanned_lsn > checkpoint_lsn + capacity + || checkpoint_lsn > recv_sys->scanned_lsn + capacity) { + + mutex_exit(&(log_sys->mutex)); + + /* The group does not contain enough log: probably + an archived log file was missing or corrupt */ + + return(DB_ERROR); + } + + recv_group_scan_log_recs(group, &contiguous_lsn, + &group_scanned_lsn); + if (recv_sys->scanned_lsn < checkpoint_lsn) { + + mutex_exit(&(log_sys->mutex)); + + /* The group did not contain enough log: an archived + log file was missing or invalid, or the log group + was corrupt */ + + return(DB_ERROR); + } + + group->scanned_lsn = group_scanned_lsn; + up_to_date_group = group; +#endif /* UNIV_LOG_ARCHIVE */ + } + + ut_ad(RECV_SCAN_SIZE <= log_sys->buf_size); + + group = UT_LIST_GET_FIRST(log_sys->log_groups); + +#ifdef UNIV_LOG_ARCHIVE + if ((type == LOG_ARCHIVE) && (group == recv_sys->archive_group)) { + group = UT_LIST_GET_NEXT(log_groups, group); + } +#endif /* UNIV_LOG_ARCHIVE */ + + /* Set the flag to publish that we are doing startup scan. */ + recv_log_scan_is_startup_type = TYPE_CHECKPOINT; + while (group) { + old_scanned_lsn = recv_sys->scanned_lsn; + + recv_group_scan_log_recs(group, &contiguous_lsn, + &group_scanned_lsn); + group->scanned_lsn = group_scanned_lsn; + + if (old_scanned_lsn < group_scanned_lsn) { + /* We found a more up-to-date group */ + + up_to_date_group = group; + } + +#ifdef UNIV_LOG_ARCHIVE + if ((type == LOG_ARCHIVE) + && (group == recv_sys->archive_group)) { + group = UT_LIST_GET_NEXT(log_groups, group); + } +#endif /* UNIV_LOG_ARCHIVE */ + + group = UT_LIST_GET_NEXT(log_groups, group); + } + + /* Done with startup scan. Clear the flag. */ + recv_log_scan_is_startup_type = FALSE; + if (TYPE_CHECKPOINT) { + /* NOTE: we always do a 'recovery' at startup, but only if + there is something wrong we will print a message to the + user about recovery: */ + + if (checkpoint_lsn != max_flushed_lsn + || checkpoint_lsn != min_flushed_lsn) { + + if (checkpoint_lsn < max_flushed_lsn) { + fprintf(stderr, + "InnoDB: #########################" + "#################################\n" + "InnoDB: " + "WARNING!\n" + "InnoDB: The log sequence number" + " in ibdata files is higher\n" + "InnoDB: than the log sequence number" + " in the ib_logfiles! Are you sure\n" + "InnoDB: you are using the right" + " ib_logfiles to start up" + " the database?\n" + "InnoDB: Log sequence number in" + " ib_logfiles is %llu, log\n" + "InnoDB: sequence numbers stamped" + " to ibdata file headers are between\n" + "InnoDB: %llu and %llu.\n" + "InnoDB: #########################" + "#################################\n", + checkpoint_lsn, + min_flushed_lsn, + max_flushed_lsn); + } + + if (!recv_needed_recovery) { + fprintf(stderr, + "InnoDB: The log sequence number" + " in ibdata files does not match\n" + "InnoDB: the log sequence number" + " in the ib_logfiles!\n"); + recv_init_crash_recovery(); + } + } + + if (!recv_needed_recovery) { + /* Init the doublewrite buffer memory structure */ + trx_sys_doublewrite_init_or_restore_pages(FALSE); + } + } + + /* We currently have only one log group */ + if (group_scanned_lsn < checkpoint_lsn) { + ut_print_timestamp(stderr); + fprintf(stderr, + " InnoDB: ERROR: We were only able to scan the log" + " up to\n" + "InnoDB: %llu, but a checkpoint was at %llu.\n" + "InnoDB: It is possible that" + " the database is now corrupt!\n", + group_scanned_lsn, + checkpoint_lsn); + } + + if (group_scanned_lsn < recv_max_page_lsn) { + ut_print_timestamp(stderr); + fprintf(stderr, + " InnoDB: ERROR: We were only able to scan the log" + " up to %llu\n" + "InnoDB: but a database page a had an lsn %llu." + " It is possible that the\n" + "InnoDB: database is now corrupt!\n", + group_scanned_lsn, + recv_max_page_lsn); + } + + if (recv_sys->recovered_lsn < checkpoint_lsn) { + + mutex_exit(&(log_sys->mutex)); + + if (recv_sys->recovered_lsn >= LIMIT_LSN) { + + return(DB_SUCCESS); + } + + ut_error; + + return(DB_ERROR); + } + + /* Synchronize the uncorrupted log groups to the most up-to-date log + group; we also copy checkpoint info to groups */ + + log_sys->next_checkpoint_lsn = checkpoint_lsn; + log_sys->next_checkpoint_no = checkpoint_no + 1; + +#ifdef UNIV_LOG_ARCHIVE + log_sys->archived_lsn = archived_lsn; +#endif /* UNIV_LOG_ARCHIVE */ + + recv_synchronize_groups(up_to_date_group); + + if (!recv_needed_recovery) { + ut_a(checkpoint_lsn == recv_sys->recovered_lsn); + } else { + srv_start_lsn = recv_sys->recovered_lsn; + } + + log_sys->lsn = recv_sys->recovered_lsn; + + ut_memcpy(log_sys->buf, recv_sys->last_block, OS_FILE_LOG_BLOCK_SIZE); + + log_sys->buf_free = (ulint) log_sys->lsn % OS_FILE_LOG_BLOCK_SIZE; + log_sys->buf_next_to_write = log_sys->buf_free; + log_sys->written_to_some_lsn = log_sys->lsn; + log_sys->written_to_all_lsn = log_sys->lsn; + + log_sys->last_checkpoint_lsn = checkpoint_lsn; + + log_sys->next_checkpoint_no = checkpoint_no + 1; + +#ifdef UNIV_LOG_ARCHIVE + if (archived_lsn == IB_ULONGLONG_MAX) { + + log_sys->archiving_state = LOG_ARCH_OFF; + } +#endif /* UNIV_LOG_ARCHIVE */ + + mutex_enter(&(recv_sys->mutex)); + + recv_sys->apply_log_recs = TRUE; + + mutex_exit(&(recv_sys->mutex)); + + mutex_exit(&(log_sys->mutex)); + + recv_lsn_checks_on = TRUE; + + /* The database is now ready to start almost normal processing of user + transactions: transaction rollbacks and the application of the log + records in the hash table can be run in background. */ + + return(DB_SUCCESS); + +#undef TYPE_CHECKPOINT +#undef LIMIT_LSN +} + +/************************************************************ +Completes recovery from a checkpoint. */ +UNIV_INTERN +void +recv_recovery_from_checkpoint_finish(void) +/*======================================*/ +{ + int i; + + /* Apply the hashed log records to the respective file pages */ + + if (srv_force_recovery < SRV_FORCE_NO_LOG_REDO) { + + recv_apply_hashed_log_recs(TRUE); + } + +#ifdef UNIV_DEBUG + if (log_debug_writes) { + fprintf(stderr, + "InnoDB: Log records applied to the database\n"); + } +#endif /* UNIV_DEBUG */ + + if (recv_needed_recovery) { + trx_sys_print_mysql_master_log_pos(); + trx_sys_print_mysql_binlog_offset(); + } + + if (recv_sys->found_corrupt_log) { + + fprintf(stderr, + "InnoDB: WARNING: the log file may have been" + " corrupt and it\n" + "InnoDB: is possible that the log scan or parsing" + " did not proceed\n" + "InnoDB: far enough in recovery. Please run" + " CHECK TABLE\n" + "InnoDB: on your InnoDB tables to check that" + " they are ok!\n" + "InnoDB: It may be safest to recover your" + " InnoDB database from\n" + "InnoDB: a backup!\n"); + } + + /* Free the resources of the recovery system */ + + recv_recovery_on = FALSE; + +#ifndef UNIV_LOG_DEBUG + recv_sys_free(); +#endif + + /* Drop partially created indexes. */ + row_merge_drop_temp_indexes(); + +#ifdef UNIV_SYNC_DEBUG + /* Wait for a while so that created threads have time to suspend + themselves before we switch the latching order checks on */ + os_thread_sleep(1000000); + + /* Switch latching order checks on in sync0sync.c */ + sync_order_checks_on = TRUE; +#endif + if (srv_force_recovery < SRV_FORCE_NO_TRX_UNDO) { + /* Rollback the uncommitted transactions which have no user + session */ + + os_thread_create(trx_rollback_or_clean_all_recovered, + (void *)&i, NULL); + } +} + +/********************************************************** +Resets the logs. The contents of log files will be lost! */ +UNIV_INTERN +void +recv_reset_logs( +/*============*/ + ib_uint64_t lsn, /* in: reset to this lsn + rounded up to be divisible by + OS_FILE_LOG_BLOCK_SIZE, after + which we add + LOG_BLOCK_HDR_SIZE */ +#ifdef UNIV_LOG_ARCHIVE + ulint arch_log_no, /* in: next archived log file number */ +#endif /* UNIV_LOG_ARCHIVE */ + ibool new_logs_created)/* in: TRUE if resetting logs + is done at the log creation; + FALSE if it is done after + archive recovery */ +{ + log_group_t* group; + + ut_ad(mutex_own(&(log_sys->mutex))); + + log_sys->lsn = ut_uint64_align_up(lsn, OS_FILE_LOG_BLOCK_SIZE); + + group = UT_LIST_GET_FIRST(log_sys->log_groups); + + while (group) { + group->lsn = log_sys->lsn; + group->lsn_offset = LOG_FILE_HDR_SIZE; +#ifdef UNIV_LOG_ARCHIVE + group->archived_file_no = arch_log_no; + group->archived_offset = 0; +#endif /* UNIV_LOG_ARCHIVE */ + + if (!new_logs_created) { + recv_truncate_group(group, group->lsn, group->lsn, + group->lsn, group->lsn); + } + + group = UT_LIST_GET_NEXT(log_groups, group); + } + + log_sys->buf_next_to_write = 0; + log_sys->written_to_some_lsn = log_sys->lsn; + log_sys->written_to_all_lsn = log_sys->lsn; + + log_sys->next_checkpoint_no = 0; + log_sys->last_checkpoint_lsn = 0; + +#ifdef UNIV_LOG_ARCHIVE + log_sys->archived_lsn = log_sys->lsn; +#endif /* UNIV_LOG_ARCHIVE */ + + log_block_init(log_sys->buf, log_sys->lsn); + log_block_set_first_rec_group(log_sys->buf, LOG_BLOCK_HDR_SIZE); + + log_sys->buf_free = LOG_BLOCK_HDR_SIZE; + log_sys->lsn += LOG_BLOCK_HDR_SIZE; + + mutex_exit(&(log_sys->mutex)); + + /* Reset the checkpoint fields in logs */ + + log_make_checkpoint_at(IB_ULONGLONG_MAX, TRUE); + log_make_checkpoint_at(IB_ULONGLONG_MAX, TRUE); + + mutex_enter(&(log_sys->mutex)); +} + +#ifdef UNIV_HOTBACKUP +/********************************************************** +Creates new log files after a backup has been restored. */ +UNIV_INTERN +void +recv_reset_log_files_for_backup( +/*============================*/ + const char* log_dir, /* in: log file directory path */ + ulint n_log_files, /* in: number of log files */ + ulint log_file_size, /* in: log file size */ + ib_uint64_t lsn) /* in: new start lsn, must be + divisible by OS_FILE_LOG_BLOCK_SIZE */ +{ + os_file_t log_file; + ibool success; + byte* buf; + ulint i; + ulint log_dir_len; + char name[5000]; + static const char ib_logfile_basename[] = "ib_logfile"; + + log_dir_len = strlen(log_dir); + /* full path name of ib_logfile consists of log dir path + basename + + number. This must fit in the name buffer. + */ + ut_a(log_dir_len + strlen(ib_logfile_basename) + 11 < sizeof(name)); + + buf = ut_malloc(LOG_FILE_HDR_SIZE + OS_FILE_LOG_BLOCK_SIZE); + memset(buf, '\0', LOG_FILE_HDR_SIZE + OS_FILE_LOG_BLOCK_SIZE); + + for (i = 0; i < n_log_files; i++) { + + sprintf(name, "%s%s%lu", log_dir, + ib_logfile_basename, (ulong)i); + + log_file = os_file_create_simple(name, OS_FILE_CREATE, + OS_FILE_READ_WRITE, &success); + if (!success) { + fprintf(stderr, + "InnoDB: Cannot create %s. Check that" + " the file does not exist yet.\n", name); + + exit(1); + } + + fprintf(stderr, + "Setting log file size to %lu %lu\n", + (ulong) ut_get_high32(log_file_size), + (ulong) log_file_size & 0xFFFFFFFFUL); + + success = os_file_set_size(name, log_file, + log_file_size & 0xFFFFFFFFUL, + ut_get_high32(log_file_size)); + + if (!success) { + fprintf(stderr, + "InnoDB: Cannot set %s size to %lu %lu\n", + name, (ulong) ut_get_high32(log_file_size), + (ulong) (log_file_size & 0xFFFFFFFFUL)); + exit(1); + } + + os_file_flush(log_file); + os_file_close(log_file); + } + + /* We pretend there is a checkpoint at lsn + LOG_BLOCK_HDR_SIZE */ + + log_reset_first_header_and_checkpoint(buf, lsn); + + log_block_init_in_old_format(buf + LOG_FILE_HDR_SIZE, lsn); + log_block_set_first_rec_group(buf + LOG_FILE_HDR_SIZE, + LOG_BLOCK_HDR_SIZE); + sprintf(name, "%s%s%lu", log_dir, ib_logfile_basename, (ulong)0); + + log_file = os_file_create_simple(name, OS_FILE_OPEN, + OS_FILE_READ_WRITE, &success); + if (!success) { + fprintf(stderr, "InnoDB: Cannot open %s.\n", name); + + exit(1); + } + + os_file_write(name, log_file, buf, 0, 0, + LOG_FILE_HDR_SIZE + OS_FILE_LOG_BLOCK_SIZE); + os_file_flush(log_file); + os_file_close(log_file); + + ut_free(buf); +} +#endif /* UNIV_HOTBACKUP */ + +#ifdef UNIV_LOG_ARCHIVE +/********************************************************** +Reads from the archive of a log group and performs recovery. */ +static +ibool +log_group_recover_from_archive_file( +/*================================*/ + /* out: TRUE if no more complete + consistent archive files */ + log_group_t* group) /* in: log group */ +{ + os_file_t file_handle; + ib_uint64_t start_lsn; + ib_uint64_t file_end_lsn; + ib_uint64_t dummy_lsn; + ib_uint64_t scanned_lsn; + ulint len; + ibool ret; + byte* buf; + ulint read_offset; + ulint file_size; + ulint file_size_high; + int input_char; + char name[10000]; + + ut_a(0); + +try_open_again: + buf = log_sys->buf; + + /* Add the file to the archive file space; open the file */ + + log_archived_file_name_gen(name, group->id, group->archived_file_no); + + file_handle = os_file_create(name, OS_FILE_OPEN, + OS_FILE_LOG, OS_FILE_AIO, &ret); + + if (ret == FALSE) { +ask_again: + fprintf(stderr, + "InnoDB: Do you want to copy additional" + " archived log files\n" + "InnoDB: to the directory\n"); + fprintf(stderr, + "InnoDB: or were these all the files needed" + " in recovery?\n"); + fprintf(stderr, + "InnoDB: (Y == copy more files; N == this is all)?"); + + input_char = getchar(); + + if (input_char == (int) 'N') { + + return(TRUE); + } else if (input_char == (int) 'Y') { + + goto try_open_again; + } else { + goto ask_again; + } + } + + ret = os_file_get_size(file_handle, &file_size, &file_size_high); + ut_a(ret); + + ut_a(file_size_high == 0); + + fprintf(stderr, "InnoDB: Opened archived log file %s\n", name); + + ret = os_file_close(file_handle); + + if (file_size < LOG_FILE_HDR_SIZE) { + fprintf(stderr, + "InnoDB: Archive file header incomplete %s\n", name); + + return(TRUE); + } + + ut_a(ret); + + /* Add the archive file as a node to the space */ + + fil_node_create(name, 1 + file_size / UNIV_PAGE_SIZE, + group->archive_space_id, FALSE); +#if RECV_SCAN_SIZE < LOG_FILE_HDR_SIZE +# error "RECV_SCAN_SIZE < LOG_FILE_HDR_SIZE" +#endif + + /* Read the archive file header */ + fil_io(OS_FILE_READ | OS_FILE_LOG, TRUE, group->archive_space_id, 0, 0, + LOG_FILE_HDR_SIZE, buf, NULL); + + /* Check if the archive file header is consistent */ + + if (mach_read_from_4(buf + LOG_GROUP_ID) != group->id + || mach_read_from_4(buf + LOG_FILE_NO) + != group->archived_file_no) { + fprintf(stderr, + "InnoDB: Archive file header inconsistent %s\n", name); + + return(TRUE); + } + + if (!mach_read_from_4(buf + LOG_FILE_ARCH_COMPLETED)) { + fprintf(stderr, + "InnoDB: Archive file not completely written %s\n", + name); + + return(TRUE); + } + + start_lsn = mach_read_ull(buf + LOG_FILE_START_LSN); + file_end_lsn = mach_read_ull(buf + LOG_FILE_END_LSN); + + if (!recv_sys->scanned_lsn) { + + if (recv_sys->parse_start_lsn < start_lsn) { + fprintf(stderr, + "InnoDB: Archive log file %s" + " starts from too big a lsn\n", + name); + return(TRUE); + } + + recv_sys->scanned_lsn = start_lsn; + } + + if (recv_sys->scanned_lsn != start_lsn) { + + fprintf(stderr, + "InnoDB: Archive log file %s starts from" + " a wrong lsn\n", + name); + return(TRUE); + } + + read_offset = LOG_FILE_HDR_SIZE; + + for (;;) { + len = RECV_SCAN_SIZE; + + if (read_offset + len > file_size) { + len = ut_calc_align_down(file_size - read_offset, + OS_FILE_LOG_BLOCK_SIZE); + } + + if (len == 0) { + + break; + } + +#ifdef UNIV_DEBUG + if (log_debug_writes) { + fprintf(stderr, + "InnoDB: Archive read starting at" + " lsn %llu, len %lu from file %s\n", + start_lsn, + (ulong) len, name); + } +#endif /* UNIV_DEBUG */ + + fil_io(OS_FILE_READ | OS_FILE_LOG, TRUE, + group->archive_space_id, read_offset / UNIV_PAGE_SIZE, + read_offset % UNIV_PAGE_SIZE, len, buf, NULL); + + ret = recv_scan_log_recs( + TRUE, (buf_pool->n_frames - recv_n_pool_free_frames) + * UNIV_PAGE_SIZE, TRUE, buf, len, start_lsn, + &dummy_lsn, &scanned_lsn); + + if (scanned_lsn == file_end_lsn) { + + return(FALSE); + } + + if (ret) { + fprintf(stderr, + "InnoDB: Archive log file %s" + " does not scan right\n", + name); + return(TRUE); + } + + read_offset += len; + start_lsn += len; + + ut_ad(start_lsn == scanned_lsn); + } + + return(FALSE); +} + +/************************************************************ +Recovers from archived log files, and also from log files, if they exist. */ +UNIV_INTERN +ulint +recv_recovery_from_archive_start( +/*=============================*/ + /* out: error code or DB_SUCCESS */ + ib_uint64_t min_flushed_lsn,/* in: min flushed lsn field from the + data files */ + ib_uint64_t limit_lsn, /* in: recover up to this lsn if + possible */ + ulint first_log_no) /* in: number of the first archived + log file to use in the recovery; the + file will be searched from + INNOBASE_LOG_ARCH_DIR specified in + server config file */ +{ + log_group_t* group; + ulint group_id; + ulint trunc_len; + ibool ret; + ulint err; + + ut_a(0); + + recv_sys_create(); + recv_sys_init(FALSE, buf_pool_get_curr_size()); + + recv_recovery_on = TRUE; + recv_recovery_from_backup_on = TRUE; + + recv_sys->limit_lsn = limit_lsn; + + group_id = 0; + + group = UT_LIST_GET_FIRST(log_sys->log_groups); + + while (group) { + if (group->id == group_id) { + + break; + } + + group = UT_LIST_GET_NEXT(log_groups, group); + } + + if (!group) { + fprintf(stderr, + "InnoDB: There is no log group defined with id %lu!\n", + (ulong) group_id); + return(DB_ERROR); + } + + group->archived_file_no = first_log_no; + + recv_sys->parse_start_lsn = min_flushed_lsn; + + recv_sys->scanned_lsn = 0; + recv_sys->scanned_checkpoint_no = 0; + recv_sys->recovered_lsn = recv_sys->parse_start_lsn; + + recv_sys->archive_group = group; + + ret = FALSE; + + mutex_enter(&(log_sys->mutex)); + + while (!ret) { + ret = log_group_recover_from_archive_file(group); + + /* Close and truncate a possible processed archive file + from the file space */ + + trunc_len = UNIV_PAGE_SIZE + * fil_space_get_size(group->archive_space_id); + if (trunc_len > 0) { + fil_space_truncate_start(group->archive_space_id, + trunc_len); + } + + group->archived_file_no++; + } + + if (recv_sys->recovered_lsn < limit_lsn) { + + if (!recv_sys->scanned_lsn) { + + recv_sys->scanned_lsn = recv_sys->parse_start_lsn; + } + + mutex_exit(&(log_sys->mutex)); + + err = recv_recovery_from_checkpoint_start(LOG_ARCHIVE, + limit_lsn, + IB_ULONGLONG_MAX, + IB_ULONGLONG_MAX); + if (err != DB_SUCCESS) { + + return(err); + } + + mutex_enter(&(log_sys->mutex)); + } + + if (limit_lsn != IB_ULONGLONG_MAX) { + + recv_apply_hashed_log_recs(FALSE); + + recv_reset_logs(recv_sys->recovered_lsn, 0, FALSE); + } + + mutex_exit(&(log_sys->mutex)); + + return(DB_SUCCESS); +} + +/************************************************************ +Completes recovery from archive. */ +UNIV_INTERN +void +recv_recovery_from_archive_finish(void) +/*===================================*/ +{ + recv_recovery_from_checkpoint_finish(); + + recv_recovery_from_backup_on = FALSE; +} +#endif /* UNIV_LOG_ARCHIVE */ diff --git a/storage/xtradb/mach/mach0data.c b/storage/xtradb/mach/mach0data.c new file mode 100644 index 00000000000..5deb475318d --- /dev/null +++ b/storage/xtradb/mach/mach0data.c @@ -0,0 +1,135 @@ +/***************************************************************************** + +Copyright (c) 1995, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/********************************************************************** +Utilities for converting data from the database file +to the machine format. + +Created 11/28/1995 Heikki Tuuri +***********************************************************************/ + +#include "mach0data.h" + +#ifdef UNIV_NONINL +#include "mach0data.ic" +#endif + +/************************************************************* +Reads a ulint in a compressed form if the log record fully contains it. */ +UNIV_INTERN +byte* +mach_parse_compressed( +/*==================*/ + /* out: pointer to end of the stored field, NULL if + not complete */ + byte* ptr, /* in: pointer to buffer from where to read */ + byte* end_ptr,/* in: pointer to end of the buffer */ + ulint* val) /* out: read value (< 2^32) */ +{ + ulint flag; + + ut_ad(ptr && end_ptr && val); + + if (ptr >= end_ptr) { + + return(NULL); + } + + flag = mach_read_from_1(ptr); + + if (flag < 0x80UL) { + *val = flag; + return(ptr + 1); + + } else if (flag < 0xC0UL) { + if (end_ptr < ptr + 2) { + return(NULL); + } + + *val = mach_read_from_2(ptr) & 0x7FFFUL; + + return(ptr + 2); + + } else if (flag < 0xE0UL) { + if (end_ptr < ptr + 3) { + return(NULL); + } + + *val = mach_read_from_3(ptr) & 0x3FFFFFUL; + + return(ptr + 3); + } else if (flag < 0xF0UL) { + if (end_ptr < ptr + 4) { + return(NULL); + } + + *val = mach_read_from_4(ptr) & 0x1FFFFFFFUL; + + return(ptr + 4); + } else { + ut_ad(flag == 0xF0UL); + + if (end_ptr < ptr + 5) { + return(NULL); + } + + *val = mach_read_from_4(ptr + 1); + return(ptr + 5); + } +} + +/************************************************************* +Reads a dulint in a compressed form if the log record fully contains it. */ +UNIV_INTERN +byte* +mach_dulint_parse_compressed( +/*=========================*/ + /* out: pointer to end of the stored field, NULL if + not complete */ + byte* ptr, /* in: pointer to buffer from where to read */ + byte* end_ptr,/* in: pointer to end of the buffer */ + dulint* val) /* out: read value */ +{ + ulint high; + ulint low; + ulint size; + + ut_ad(ptr && end_ptr && val); + + if (end_ptr < ptr + 5) { + + return(NULL); + } + + high = mach_read_compressed(ptr); + + size = mach_get_compressed_size(high); + + ptr += size; + + if (end_ptr < ptr + 4) { + + return(NULL); + } + + low = mach_read_from_4(ptr); + + *val = ut_dulint_create(high, low); + + return(ptr + 4); +} diff --git a/storage/xtradb/mem/mem0dbg.c b/storage/xtradb/mem/mem0dbg.c new file mode 100644 index 00000000000..a1647462922 --- /dev/null +++ b/storage/xtradb/mem/mem0dbg.c @@ -0,0 +1,1014 @@ +/***************************************************************************** + +Copyright (c) 1994, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/************************************************************************ +The memory management: the debug code. This is not a compilation module, +but is included in mem0mem.* ! + +Created 6/9/1994 Heikki Tuuri +*************************************************************************/ + +#ifdef UNIV_MEM_DEBUG +/* The mutex which protects in the debug version the hash table +containing the list of live memory heaps, and also the global +variables below. */ +UNIV_INTERN mutex_t mem_hash_mutex; + +/* The following variables contain information about the +extent of memory allocations. Only used in the debug version. +Protected by mem_hash_mutex above. */ + +static ulint mem_n_created_heaps = 0; +static ulint mem_n_allocations = 0; +static ulint mem_total_allocated_memory = 0; +UNIV_INTERN ulint mem_current_allocated_memory = 0; +static ulint mem_max_allocated_memory = 0; +static ulint mem_last_print_info = 0; + +/* Size of the hash table for memory management tracking */ +#define MEM_HASH_SIZE 997 + +/* The node of the list containing currently allocated memory heaps */ + +typedef struct mem_hash_node_struct mem_hash_node_t; +struct mem_hash_node_struct { + UT_LIST_NODE_T(mem_hash_node_t) + list; /* hash list node */ + mem_heap_t* heap; /* memory heap */ + const char* file_name;/* file where heap was created*/ + ulint line; /* file line of creation */ + ulint nth_heap;/* this is the nth heap created */ + UT_LIST_NODE_T(mem_hash_node_t) + all_list;/* list of all created heaps */ +}; + +typedef UT_LIST_BASE_NODE_T(mem_hash_node_t) mem_hash_cell_t; + +/* The hash table of allocated heaps */ +static mem_hash_cell_t mem_hash_table[MEM_HASH_SIZE]; + +/* The base node of the list of all allocated heaps */ +static mem_hash_cell_t mem_all_list_base; + +static ibool mem_hash_initialized = FALSE; + + +UNIV_INLINE +mem_hash_cell_t* +mem_hash_get_nth_cell(ulint i); + +/* Accessor function for the hash table. Returns a pointer to the +table cell. */ +UNIV_INLINE +mem_hash_cell_t* +mem_hash_get_nth_cell(ulint i) +{ + ut_a(i < MEM_HASH_SIZE); + + return(&(mem_hash_table[i])); +} + +/* Accessor functions for a memory field in the debug version */ +UNIV_INTERN +void +mem_field_header_set_len(byte* field, ulint len) +{ + mach_write_to_4(field - 2 * sizeof(ulint), len); +} + +UNIV_INTERN +ulint +mem_field_header_get_len(byte* field) +{ + return(mach_read_from_4(field - 2 * sizeof(ulint))); +} + +UNIV_INTERN +void +mem_field_header_set_check(byte* field, ulint check) +{ + mach_write_to_4(field - sizeof(ulint), check); +} + +UNIV_INTERN +ulint +mem_field_header_get_check(byte* field) +{ + return(mach_read_from_4(field - sizeof(ulint))); +} + +UNIV_INTERN +void +mem_field_trailer_set_check(byte* field, ulint check) +{ + mach_write_to_4(field + mem_field_header_get_len(field), check); +} + +UNIV_INTERN +ulint +mem_field_trailer_get_check(byte* field) +{ + return(mach_read_from_4(field + + mem_field_header_get_len(field))); +} +#endif /* UNIV_MEM_DEBUG */ + +/********************************************************************** +Initializes the memory system. */ +UNIV_INTERN +void +mem_init( +/*=====*/ + ulint size) /* in: common pool size in bytes */ +{ +#ifdef UNIV_MEM_DEBUG + + ulint i; + + /* Initialize the hash table */ + ut_a(FALSE == mem_hash_initialized); + + mutex_create(&mem_hash_mutex, SYNC_MEM_HASH); + + for (i = 0; i < MEM_HASH_SIZE; i++) { + UT_LIST_INIT(*mem_hash_get_nth_cell(i)); + } + + UT_LIST_INIT(mem_all_list_base); + + mem_hash_initialized = TRUE; +#endif + + if (UNIV_LIKELY(srv_use_sys_malloc)) { + /* When innodb_use_sys_malloc is set, the + mem_comm_pool won't be used for any allocations. We + create a dummy mem_comm_pool, because some statistics + and debugging code relies on it being initialized. */ + size = 1; + } + + mem_comm_pool = mem_pool_create(size); +} + +#ifdef UNIV_MEM_DEBUG +/********************************************************************** +Initializes an allocated memory field in the debug version. */ +UNIV_INTERN +void +mem_field_init( +/*===========*/ + byte* buf, /* in: memory field */ + ulint n) /* in: how many bytes the user requested */ +{ + ulint rnd; + byte* usr_buf; + + usr_buf = buf + MEM_FIELD_HEADER_SIZE; + + /* In the debug version write the length field and the + check fields to the start and the end of the allocated storage. + The field header consists of a length field and + a random number field, in this order. The field trailer contains + the same random number as a check field. */ + + mem_field_header_set_len(usr_buf, n); + + rnd = ut_rnd_gen_ulint(); + + mem_field_header_set_check(usr_buf, rnd); + mem_field_trailer_set_check(usr_buf, rnd); + + /* Update the memory allocation information */ + + mutex_enter(&mem_hash_mutex); + + mem_total_allocated_memory += n; + mem_current_allocated_memory += n; + mem_n_allocations++; + + if (mem_current_allocated_memory > mem_max_allocated_memory) { + mem_max_allocated_memory = mem_current_allocated_memory; + } + + mutex_exit(&mem_hash_mutex); + + /* In the debug version set the buffer to a random + combination of 0xBA and 0xBE */ + + mem_init_buf(usr_buf, n); +} + +/********************************************************************** +Erases an allocated memory field in the debug version. */ +UNIV_INTERN +void +mem_field_erase( +/*============*/ + byte* buf, /* in: memory field */ + ulint n __attribute__((unused))) + /* in: how many bytes the user requested */ +{ + byte* usr_buf; + + usr_buf = buf + MEM_FIELD_HEADER_SIZE; + + mutex_enter(&mem_hash_mutex); + mem_current_allocated_memory -= n; + mutex_exit(&mem_hash_mutex); + + /* Check that the field lengths agree */ + ut_ad(n == (ulint)mem_field_header_get_len(usr_buf)); + + /* In the debug version, set the freed space to a random + combination of 0xDE and 0xAD */ + + mem_erase_buf(buf, MEM_SPACE_NEEDED(n)); +} + +/******************************************************************* +Initializes a buffer to a random combination of hex BA and BE. +Used to initialize allocated memory. */ +UNIV_INTERN +void +mem_init_buf( +/*=========*/ + byte* buf, /* in: pointer to buffer */ + ulint n) /* in: length of buffer */ +{ + byte* ptr; + + UNIV_MEM_ASSERT_W(buf, n); + + for (ptr = buf; ptr < buf + n; ptr++) { + + if (ut_rnd_gen_ibool()) { + *ptr = 0xBA; + } else { + *ptr = 0xBE; + } + } + + UNIV_MEM_INVALID(buf, n); +} + +/******************************************************************* +Initializes a buffer to a random combination of hex DE and AD. +Used to erase freed memory.*/ +UNIV_INTERN +void +mem_erase_buf( +/*==========*/ + byte* buf, /* in: pointer to buffer */ + ulint n) /* in: length of buffer */ +{ + byte* ptr; + + UNIV_MEM_ASSERT_W(buf, n); + + for (ptr = buf; ptr < buf + n; ptr++) { + if (ut_rnd_gen_ibool()) { + *ptr = 0xDE; + } else { + *ptr = 0xAD; + } + } + + UNIV_MEM_FREE(buf, n); +} + +/******************************************************************* +Inserts a created memory heap to the hash table of current allocated +memory heaps. */ +UNIV_INTERN +void +mem_hash_insert( +/*============*/ + mem_heap_t* heap, /* in: the created heap */ + const char* file_name, /* in: file name of creation */ + ulint line) /* in: line where created */ +{ + mem_hash_node_t* new_node; + ulint cell_no ; + + ut_ad(mem_heap_check(heap)); + + mutex_enter(&mem_hash_mutex); + + cell_no = ut_hash_ulint((ulint)heap, MEM_HASH_SIZE); + + /* Allocate a new node to the list */ + new_node = ut_malloc(sizeof(mem_hash_node_t)); + + new_node->heap = heap; + new_node->file_name = file_name; + new_node->line = line; + new_node->nth_heap = mem_n_created_heaps; + + /* Insert into lists */ + UT_LIST_ADD_FIRST(list, *mem_hash_get_nth_cell(cell_no), new_node); + + UT_LIST_ADD_LAST(all_list, mem_all_list_base, new_node); + + mem_n_created_heaps++; + + mutex_exit(&mem_hash_mutex); +} + +/******************************************************************* +Removes a memory heap (which is going to be freed by the caller) +from the list of live memory heaps. Returns the size of the heap +in terms of how much memory in bytes was allocated for the user of +the heap (not the total space occupied by the heap). +Also validates the heap. +NOTE: This function does not free the storage occupied by the +heap itself, only the node in the list of heaps. */ +UNIV_INTERN +void +mem_hash_remove( +/*============*/ + mem_heap_t* heap, /* in: the heap to be freed */ + const char* file_name, /* in: file name of freeing */ + ulint line) /* in: line where freed */ +{ + mem_hash_node_t* node; + ulint cell_no; + ibool error; + ulint size; + + ut_ad(mem_heap_check(heap)); + + mutex_enter(&mem_hash_mutex); + + cell_no = ut_hash_ulint((ulint)heap, MEM_HASH_SIZE); + + /* Look for the heap in the hash table list */ + node = UT_LIST_GET_FIRST(*mem_hash_get_nth_cell(cell_no)); + + while (node != NULL) { + if (node->heap == heap) { + + break; + } + + node = UT_LIST_GET_NEXT(list, node); + } + + if (node == NULL) { + fprintf(stderr, + "Memory heap or buffer freed in %s line %lu" + " did not exist.\n", + file_name, (ulong) line); + ut_error; + } + + /* Remove from lists */ + UT_LIST_REMOVE(list, *mem_hash_get_nth_cell(cell_no), node); + + UT_LIST_REMOVE(all_list, mem_all_list_base, node); + + /* Validate the heap which will be freed */ + mem_heap_validate_or_print(node->heap, NULL, FALSE, &error, &size, + NULL, NULL); + if (error) { + fprintf(stderr, + "Inconsistency in memory heap or" + " buffer n:o %lu created\n" + "in %s line %lu and tried to free in %s line %lu.\n" + "Hex dump of 400 bytes around memory heap" + " first block start:\n", + node->nth_heap, node->file_name, (ulong) node->line, + file_name, (ulong) line); + ut_print_buf(stderr, (byte*)node->heap - 200, 400); + fputs("\nDump of the mem heap:\n", stderr); + mem_heap_validate_or_print(node->heap, NULL, TRUE, &error, + &size, NULL, NULL); + ut_error; + } + + /* Free the memory occupied by the node struct */ + ut_free(node); + + mem_current_allocated_memory -= size; + + mutex_exit(&mem_hash_mutex); +} +#endif /* UNIV_MEM_DEBUG */ + +#if defined UNIV_MEM_DEBUG || defined UNIV_DEBUG +/******************************************************************* +Checks a memory heap for consistency and prints the contents if requested. +Outputs the sum of sizes of buffers given to the user (only in +the debug version), the physical size of the heap and the number of +blocks in the heap. In case of error returns 0 as sizes and number +of blocks. */ +UNIV_INTERN +void +mem_heap_validate_or_print( +/*=======================*/ + mem_heap_t* heap, /* in: memory heap */ + byte* top __attribute__((unused)), + /* in: calculate and validate only until + this top pointer in the heap is reached, + if this pointer is NULL, ignored */ + ibool print, /* in: if TRUE, prints the contents + of the heap; works only in + the debug version */ + ibool* error, /* out: TRUE if error */ + ulint* us_size,/* out: allocated memory + (for the user) in the heap, + if a NULL pointer is passed as this + argument, it is ignored; in the + non-debug version this is always -1 */ + ulint* ph_size,/* out: physical size of the heap, + if a NULL pointer is passed as this + argument, it is ignored */ + ulint* n_blocks) /* out: number of blocks in the heap, + if a NULL pointer is passed as this + argument, it is ignored */ +{ + mem_block_t* block; + ulint total_len = 0; + ulint block_count = 0; + ulint phys_len = 0; +#ifdef UNIV_MEM_DEBUG + ulint len; + byte* field; + byte* user_field; + ulint check_field; +#endif + + /* Pessimistically, we set the parameters to error values */ + if (us_size != NULL) { + *us_size = 0; + } + if (ph_size != NULL) { + *ph_size = 0; + } + if (n_blocks != NULL) { + *n_blocks = 0; + } + *error = TRUE; + + block = heap; + + if (block->magic_n != MEM_BLOCK_MAGIC_N) { + return; + } + + if (print) { + fputs("Memory heap:", stderr); + } + + while (block != NULL) { + phys_len += mem_block_get_len(block); + + if ((block->type == MEM_HEAP_BUFFER) + && (mem_block_get_len(block) > UNIV_PAGE_SIZE)) { + + fprintf(stderr, + "InnoDB: Error: mem block %p" + " length %lu > UNIV_PAGE_SIZE\n", + (void*) block, + (ulong) mem_block_get_len(block)); + /* error */ + + return; + } + +#ifdef UNIV_MEM_DEBUG + /* We can trace the fields of the block only in the debug + version */ + if (print) { + fprintf(stderr, " Block %ld:", block_count); + } + + field = (byte*)block + mem_block_get_start(block); + + if (top && (field == top)) { + + goto completed; + } + + while (field < (byte*)block + mem_block_get_free(block)) { + + /* Calculate the pointer to the storage + which was given to the user */ + + user_field = field + MEM_FIELD_HEADER_SIZE; + + len = mem_field_header_get_len(user_field); + + if (print) { + ut_print_buf(stderr, user_field, len); + putc('\n', stderr); + } + + total_len += len; + check_field = mem_field_header_get_check(user_field); + + if (check_field + != mem_field_trailer_get_check(user_field)) { + /* error */ + + fprintf(stderr, + "InnoDB: Error: block %lx mem" + " field %lx len %lu\n" + "InnoDB: header check field is" + " %lx but trailer %lx\n", + (ulint)block, + (ulint)field, len, check_field, + mem_field_trailer_get_check( + user_field)); + + return; + } + + /* Move to next field */ + field = field + MEM_SPACE_NEEDED(len); + + if (top && (field == top)) { + + goto completed; + } + + } + + /* At the end check that we have arrived to the first free + position */ + + if (field != (byte*)block + mem_block_get_free(block)) { + /* error */ + + fprintf(stderr, + "InnoDB: Error: block %lx end of" + " mem fields %lx\n" + "InnoDB: but block free at %lx\n", + (ulint)block, (ulint)field, + (ulint)((byte*)block + + mem_block_get_free(block))); + + return; + } + +#endif + + block = UT_LIST_GET_NEXT(list, block); + block_count++; + } +#ifdef UNIV_MEM_DEBUG +completed: +#endif + if (us_size != NULL) { + *us_size = total_len; + } + if (ph_size != NULL) { + *ph_size = phys_len; + } + if (n_blocks != NULL) { + *n_blocks = block_count; + } + *error = FALSE; +} + +/****************************************************************** +Prints the contents of a memory heap. */ +static +void +mem_heap_print( +/*===========*/ + mem_heap_t* heap) /* in: memory heap */ +{ + ibool error; + ulint us_size; + ulint phys_size; + ulint n_blocks; + + ut_ad(mem_heap_check(heap)); + + mem_heap_validate_or_print(heap, NULL, TRUE, &error, + &us_size, &phys_size, &n_blocks); + fprintf(stderr, + "\nheap type: %lu; size: user size %lu;" + " physical size %lu; blocks %lu.\n", + (ulong) heap->type, (ulong) us_size, + (ulong) phys_size, (ulong) n_blocks); + ut_a(!error); +} + +/****************************************************************** +Validates the contents of a memory heap. */ +UNIV_INTERN +ibool +mem_heap_validate( +/*==============*/ + /* out: TRUE if ok */ + mem_heap_t* heap) /* in: memory heap */ +{ + ibool error; + ulint us_size; + ulint phys_size; + ulint n_blocks; + + ut_ad(mem_heap_check(heap)); + + mem_heap_validate_or_print(heap, NULL, FALSE, &error, &us_size, + &phys_size, &n_blocks); + if (error) { + mem_heap_print(heap); + } + + ut_a(!error); + + return(TRUE); +} +#endif /* UNIV_MEM_DEBUG || UNIV_DEBUG */ + +#ifdef UNIV_DEBUG +/****************************************************************** +Checks that an object is a memory heap (or a block of it). */ +UNIV_INTERN +ibool +mem_heap_check( +/*===========*/ + /* out: TRUE if ok */ + mem_heap_t* heap) /* in: memory heap */ +{ + ut_a(heap->magic_n == MEM_BLOCK_MAGIC_N); + + return(TRUE); +} +#endif /* UNIV_DEBUG */ + +#ifdef UNIV_MEM_DEBUG +/********************************************************************* +TRUE if no memory is currently allocated. */ +UNIV_INTERN +ibool +mem_all_freed(void) +/*===============*/ + /* out: TRUE if no heaps exist */ +{ + mem_hash_node_t* node; + ulint heap_count = 0; + ulint i; + + mem_validate(); + + mutex_enter(&mem_hash_mutex); + + for (i = 0; i < MEM_HASH_SIZE; i++) { + + node = UT_LIST_GET_FIRST(*mem_hash_get_nth_cell(i)); + while (node != NULL) { + heap_count++; + node = UT_LIST_GET_NEXT(list, node); + } + } + + mutex_exit(&mem_hash_mutex); + + if (heap_count == 0) { + + ut_a(mem_pool_get_reserved(mem_comm_pool) == 0); + + return(TRUE); + } else { + return(FALSE); + } +} + +/********************************************************************* +Validates the dynamic memory allocation system. */ +UNIV_INTERN +ibool +mem_validate_no_assert(void) +/*========================*/ + /* out: TRUE if error */ +{ + mem_hash_node_t* node; + ulint n_heaps = 0; + ulint allocated_mem; + ulint ph_size; + ulint total_allocated_mem = 0; + ibool error = FALSE; + ulint n_blocks; + ulint i; + + mem_pool_validate(mem_comm_pool); + + mutex_enter(&mem_hash_mutex); + + for (i = 0; i < MEM_HASH_SIZE; i++) { + + node = UT_LIST_GET_FIRST(*mem_hash_get_nth_cell(i)); + + while (node != NULL) { + n_heaps++; + + mem_heap_validate_or_print(node->heap, NULL, + FALSE, &error, + &allocated_mem, + &ph_size, &n_blocks); + + if (error) { + fprintf(stderr, + "\nERROR!!!!!!!!!!!!!!!!!!!" + "!!!!!!!!!!!!!!!!!!!!!!!\n\n" + "Inconsistency in memory heap" + " or buffer created\n" + "in %s line %lu.\n", + node->file_name, node->line); + + mutex_exit(&mem_hash_mutex); + + return(TRUE); + } + + total_allocated_mem += allocated_mem; + node = UT_LIST_GET_NEXT(list, node); + } + } + + if ((n_heaps == 0) && (mem_current_allocated_memory != 0)) { + error = TRUE; + } + + if (mem_total_allocated_memory < mem_current_allocated_memory) { + error = TRUE; + } + + if (mem_max_allocated_memory > mem_total_allocated_memory) { + error = TRUE; + } + + if (mem_n_created_heaps < n_heaps) { + error = TRUE; + } + + mutex_exit(&mem_hash_mutex); + + return(error); +} + +/**************************************************************** +Validates the dynamic memory */ +UNIV_INTERN +ibool +mem_validate(void) +/*==============*/ + /* out: TRUE if ok */ +{ + ut_a(!mem_validate_no_assert()); + + return(TRUE); +} +#endif /* UNIV_MEM_DEBUG */ + +/**************************************************************** +Tries to find neigboring memory allocation blocks and dumps to stderr +the neighborhood of a given pointer. */ +UNIV_INTERN +void +mem_analyze_corruption( +/*===================*/ + void* ptr) /* in: pointer to place of possible corruption */ +{ + byte* p; + ulint i; + ulint dist; + + fputs("InnoDB: Apparent memory corruption: mem dump ", stderr); + ut_print_buf(stderr, (byte*)ptr - 250, 500); + + fputs("\nInnoDB: Scanning backward trying to find" + " previous allocated mem blocks\n", stderr); + + p = (byte*)ptr; + dist = 0; + + for (i = 0; i < 10; i++) { + for (;;) { + if (((ulint)p) % 4 == 0) { + + if (*((ulint*)p) == MEM_BLOCK_MAGIC_N) { + fprintf(stderr, + "Mem block at - %lu," + " file %s, line %lu\n", + (ulong) dist, + (p + sizeof(ulint)), + (ulong) + (*(ulint*)(p + 8 + + sizeof(ulint)))); + + break; + } + + if (*((ulint*)p) == MEM_FREED_BLOCK_MAGIC_N) { + fprintf(stderr, + "Freed mem block at - %lu," + " file %s, line %lu\n", + (ulong) dist, + (p + sizeof(ulint)), + (ulong) + (*(ulint*)(p + 8 + + sizeof(ulint)))); + + break; + } + } + + p--; + dist++; + } + + p--; + dist++; + } + + fprintf(stderr, + "InnoDB: Scanning forward trying to find next" + " allocated mem blocks\n"); + + p = (byte*)ptr; + dist = 0; + + for (i = 0; i < 10; i++) { + for (;;) { + if (((ulint)p) % 4 == 0) { + + if (*((ulint*)p) == MEM_BLOCK_MAGIC_N) { + fprintf(stderr, + "Mem block at + %lu, file %s," + " line %lu\n", + (ulong) dist, + (p + sizeof(ulint)), + (ulong) + (*(ulint*)(p + 8 + + sizeof(ulint)))); + + break; + } + + if (*((ulint*)p) == MEM_FREED_BLOCK_MAGIC_N) { + fprintf(stderr, + "Freed mem block at + %lu," + " file %s, line %lu\n", + (ulong) dist, + (p + sizeof(ulint)), + (ulong) + (*(ulint*)(p + 8 + + sizeof(ulint)))); + + break; + } + } + + p++; + dist++; + } + + p++; + dist++; + } +} + +/********************************************************************* +Prints information of dynamic memory usage and currently allocated +memory heaps or buffers. Can only be used in the debug version. */ +static +void +mem_print_info_low( +/*===============*/ + ibool print_all) /* in: if TRUE, all heaps are printed, + else only the heaps allocated after the + previous call of this function */ +{ +#ifdef UNIV_MEM_DEBUG + mem_hash_node_t* node; + ulint n_heaps = 0; + ulint allocated_mem; + ulint ph_size; + ulint total_allocated_mem = 0; + ibool error; + ulint n_blocks; +#endif + FILE* outfile; + + /* outfile = fopen("ibdebug", "a"); */ + + outfile = stdout; + + fprintf(outfile, "\n"); + fprintf(outfile, + "________________________________________________________\n"); + fprintf(outfile, "MEMORY ALLOCATION INFORMATION\n\n"); + +#ifndef UNIV_MEM_DEBUG + + UT_NOT_USED(print_all); + + mem_pool_print_info(outfile, mem_comm_pool); + + fprintf(outfile, + "Sorry, non-debug version cannot give more memory info\n"); + + /* fclose(outfile); */ + + return; +#else + mutex_enter(&mem_hash_mutex); + + fprintf(outfile, "LIST OF CREATED HEAPS AND ALLOCATED BUFFERS: \n\n"); + + if (!print_all) { + fprintf(outfile, "AFTER THE LAST PRINT INFO\n"); + } + + node = UT_LIST_GET_FIRST(mem_all_list_base); + + while (node != NULL) { + n_heaps++; + + if (!print_all && node->nth_heap < mem_last_print_info) { + + goto next_heap; + } + + mem_heap_validate_or_print(node->heap, NULL, + FALSE, &error, &allocated_mem, + &ph_size, &n_blocks); + total_allocated_mem += allocated_mem; + + fprintf(outfile, + "%lu: file %s line %lu of size %lu phys.size %lu" + " with %lu blocks, type %lu\n", + node->nth_heap, node->file_name, node->line, + allocated_mem, ph_size, n_blocks, + (node->heap)->type); +next_heap: + node = UT_LIST_GET_NEXT(all_list, node); + } + + fprintf(outfile, "\n"); + + fprintf(outfile, "Current allocated memory : %lu\n", + mem_current_allocated_memory); + fprintf(outfile, "Current allocated heaps and buffers : %lu\n", + n_heaps); + fprintf(outfile, "Cumulative allocated memory : %lu\n", + mem_total_allocated_memory); + fprintf(outfile, "Maximum allocated memory : %lu\n", + mem_max_allocated_memory); + fprintf(outfile, "Cumulative created heaps and buffers : %lu\n", + mem_n_created_heaps); + fprintf(outfile, "Cumulative number of allocations : %lu\n", + mem_n_allocations); + + mem_last_print_info = mem_n_created_heaps; + + mutex_exit(&mem_hash_mutex); + + mem_pool_print_info(outfile, mem_comm_pool); + + /* mem_validate(); */ + + /* fclose(outfile); */ +#endif +} + +/********************************************************************* +Prints information of dynamic memory usage and currently allocated memory +heaps or buffers. Can only be used in the debug version. */ +UNIV_INTERN +void +mem_print_info(void) +/*================*/ +{ + mem_print_info_low(TRUE); +} + +/********************************************************************* +Prints information of dynamic memory usage and currently allocated memory +heaps or buffers since the last ..._print_info or..._print_new_info. */ +UNIV_INTERN +void +mem_print_new_info(void) +/*====================*/ +{ + mem_print_info_low(FALSE); +} diff --git a/storage/xtradb/mem/mem0mem.c b/storage/xtradb/mem/mem0mem.c new file mode 100644 index 00000000000..b7345f5846b --- /dev/null +++ b/storage/xtradb/mem/mem0mem.c @@ -0,0 +1,553 @@ +/***************************************************************************** + +Copyright (c) 1994, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/************************************************************************ +The memory management + +Created 6/9/1994 Heikki Tuuri +*************************************************************************/ + +#include "mem0mem.h" +#ifdef UNIV_NONINL +#include "mem0mem.ic" +#endif + +#include "buf0buf.h" +#include "srv0srv.h" +#include "mem0dbg.c" +#include <stdarg.h> + +/* + THE MEMORY MANAGEMENT + ===================== + +The basic element of the memory management is called a memory +heap. A memory heap is conceptually a +stack from which memory can be allocated. The stack may grow infinitely. +The top element of the stack may be freed, or +the whole stack can be freed at one time. The advantage of the +memory heap concept is that we can avoid using the malloc and free +functions of C which are quite expensive, for example, on the Solaris + GCC +system (50 MHz Sparc, 1993) the pair takes 3 microseconds, +on Win NT + 100MHz Pentium, 2.5 microseconds. +When we use a memory heap, +we can allocate larger blocks of memory at a time and thus +reduce overhead. Slightly more efficient the method is when we +allocate the memory from the index page buffer pool, as we can +claim a new page fast. This is called buffer allocation. +When we allocate the memory from the dynamic memory of the +C environment, that is called dynamic allocation. + +The default way of operation of the memory heap is the following. +First, when the heap is created, an initial block of memory is +allocated. In dynamic allocation this may be about 50 bytes. +If more space is needed, additional blocks are allocated +and they are put into a linked list. +After the initial block, each allocated block is twice the size of the +previous, until a threshold is attained, after which the sizes +of the blocks stay the same. An exception is, of course, the case +where the caller requests a memory buffer whose size is +bigger than the threshold. In that case a block big enough must +be allocated. + +The heap is physically arranged so that if the current block +becomes full, a new block is allocated and always inserted in the +chain of blocks as the last block. + +In the debug version of the memory management, all the allocated +heaps are kept in a list (which is implemented as a hash table). +Thus we can notice if the caller tries to free an already freed +heap. In addition, each buffer given to the caller contains +start field at the start and a trailer field at the end of the buffer. + +The start field has the following content: +A. sizeof(ulint) bytes of field length (in the standard byte order) +B. sizeof(ulint) bytes of check field (a random number) + +The trailer field contains: +A. sizeof(ulint) bytes of check field (the same random number as at the start) + +Thus we can notice if something has been copied over the +borders of the buffer, which is illegal. +The memory in the buffers is initialized to a random byte sequence. +After freeing, all the blocks in the heap are set to random bytes +to help us discover errors which result from the use of +buffers in an already freed heap. */ + +#ifdef MEM_PERIODIC_CHECK + +ibool mem_block_list_inited; +/* List of all mem blocks allocated; protected by the mem_comm_pool mutex */ +UT_LIST_BASE_NODE_T(mem_block_t) mem_block_list; + +#endif + +/************************************************************************** +Duplicates a NUL-terminated string, allocated from a memory heap. */ +UNIV_INTERN +char* +mem_heap_strdup( +/*============*/ + /* out, own: a copy of the string */ + mem_heap_t* heap, /* in: memory heap where string is allocated */ + const char* str) /* in: string to be copied */ +{ + return(mem_heap_dup(heap, str, strlen(str) + 1)); +} + +/************************************************************************** +Duplicate a block of data, allocated from a memory heap. */ +UNIV_INTERN +void* +mem_heap_dup( +/*=========*/ + /* out, own: a copy of the data */ + mem_heap_t* heap, /* in: memory heap where copy is allocated */ + const void* data, /* in: data to be copied */ + ulint len) /* in: length of data, in bytes */ +{ + return(memcpy(mem_heap_alloc(heap, len), data, len)); +} + +/************************************************************************** +Concatenate two memory blocks and return the result, using a memory heap. */ +UNIV_INTERN +void* +mem_heap_cat( +/*=========*/ + /* out, own: the result */ + mem_heap_t* heap, /* in: memory heap where result is allocated */ + const void* b1, /* in: block 1 */ + ulint len1, /* in: length of b1, in bytes */ + const void* b2, /* in: block 2 */ + ulint len2) /* in: length of b2, in bytes */ +{ + void* res = mem_heap_alloc(heap, len1 + len2); + + memcpy(res, b1, len1); + memcpy((char*)res + len1, b2, len2); + + return(res); +} + +/************************************************************************** +Concatenate two strings and return the result, using a memory heap. */ +UNIV_INTERN +char* +mem_heap_strcat( +/*============*/ + /* out, own: the result */ + mem_heap_t* heap, /* in: memory heap where string is allocated */ + const char* s1, /* in: string 1 */ + const char* s2) /* in: string 2 */ +{ + char* s; + ulint s1_len = strlen(s1); + ulint s2_len = strlen(s2); + + s = mem_heap_alloc(heap, s1_len + s2_len + 1); + + memcpy(s, s1, s1_len); + memcpy(s + s1_len, s2, s2_len); + + s[s1_len + s2_len] = '\0'; + + return(s); +} + + +/******************************************************************** +Helper function for mem_heap_printf. */ +static +ulint +mem_heap_printf_low( +/*================*/ + /* out: length of formatted string, + including terminating NUL */ + char* buf, /* in/out: buffer to store formatted string + in, or NULL to just calculate length */ + const char* format, /* in: format string */ + va_list ap) /* in: arguments */ +{ + ulint len = 0; + + while (*format) { + + /* Does this format specifier have the 'l' length modifier. */ + ibool is_long = FALSE; + + /* Length of one parameter. */ + size_t plen; + + if (*format++ != '%') { + /* Non-format character. */ + + len++; + + if (buf) { + *buf++ = *(format - 1); + } + + continue; + } + + if (*format == 'l') { + is_long = TRUE; + format++; + } + + switch (*format++) { + case 's': + /* string */ + { + char* s = va_arg(ap, char*); + + /* "%ls" is a non-sensical format specifier. */ + ut_a(!is_long); + + plen = strlen(s); + len += plen; + + if (buf) { + memcpy(buf, s, plen); + buf += plen; + } + } + + break; + + case 'u': + /* unsigned int */ + { + char tmp[32]; + unsigned long val; + + /* We only support 'long' values for now. */ + ut_a(is_long); + + val = va_arg(ap, unsigned long); + + plen = sprintf(tmp, "%lu", val); + len += plen; + + if (buf) { + memcpy(buf, tmp, plen); + buf += plen; + } + } + + break; + + case '%': + + /* "%l%" is a non-sensical format specifier. */ + ut_a(!is_long); + + len++; + + if (buf) { + *buf++ = '%'; + } + + break; + + default: + ut_error; + } + } + + /* For the NUL character. */ + len++; + + if (buf) { + *buf = '\0'; + } + + return(len); +} + +/******************************************************************** +A simple (s)printf replacement that dynamically allocates the space for the +formatted string from the given heap. This supports a very limited set of +the printf syntax: types 's' and 'u' and length modifier 'l' (which is +required for the 'u' type). */ +UNIV_INTERN +char* +mem_heap_printf( +/*============*/ + /* out: heap-allocated formatted string */ + mem_heap_t* heap, /* in: memory heap */ + const char* format, /* in: format string */ + ...) +{ + va_list ap; + char* str; + ulint len; + + /* Calculate length of string */ + len = 0; + va_start(ap, format); + len = mem_heap_printf_low(NULL, format, ap); + va_end(ap); + + /* Now create it for real. */ + str = mem_heap_alloc(heap, len); + va_start(ap, format); + mem_heap_printf_low(str, format, ap); + va_end(ap); + + return(str); +} + +/******************************************************************* +Creates a memory heap block where data can be allocated. */ +UNIV_INTERN +mem_block_t* +mem_heap_create_block( +/*==================*/ + /* out, own: memory heap block, NULL if + did not succeed (only possible for + MEM_HEAP_BTR_SEARCH type heaps) */ + mem_heap_t* heap, /* in: memory heap or NULL if first block + should be created */ + ulint n, /* in: number of bytes needed for user data */ + ulint type, /* in: type of heap: MEM_HEAP_DYNAMIC or + MEM_HEAP_BUFFER */ + const char* file_name,/* in: file name where created */ + ulint line) /* in: line where created */ +{ + buf_block_t* buf_block = NULL; + mem_block_t* block; + ulint len; + + ut_ad((type == MEM_HEAP_DYNAMIC) || (type == MEM_HEAP_BUFFER) + || (type == MEM_HEAP_BUFFER + MEM_HEAP_BTR_SEARCH)); + + if (heap && heap->magic_n != MEM_BLOCK_MAGIC_N) { + mem_analyze_corruption(heap); + } + + /* In dynamic allocation, calculate the size: block header + data. */ + len = MEM_BLOCK_HEADER_SIZE + MEM_SPACE_NEEDED(n); + + if (type == MEM_HEAP_DYNAMIC || len < UNIV_PAGE_SIZE / 2) { + + ut_ad(type == MEM_HEAP_DYNAMIC || n <= MEM_MAX_ALLOC_IN_BUF); + + block = mem_area_alloc(&len, mem_comm_pool); + } else { + len = UNIV_PAGE_SIZE; + + if ((type & MEM_HEAP_BTR_SEARCH) && heap) { + /* We cannot allocate the block from the + buffer pool, but must get the free block from + the heap header free block field */ + + buf_block = heap->free_block; + heap->free_block = NULL; + + if (UNIV_UNLIKELY(!buf_block)) { + + return(NULL); + } + } else { + buf_block = buf_block_alloc(0); + } + + block = (mem_block_t*) buf_block->frame; + } + + ut_ad(block); + block->buf_block = buf_block; + block->magic_n = MEM_BLOCK_MAGIC_N; + ut_strlcpy_rev(block->file_name, file_name, sizeof(block->file_name)); + block->line = line; + +#ifdef MEM_PERIODIC_CHECK + mem_pool_mutex_enter(); + + if (!mem_block_list_inited) { + mem_block_list_inited = TRUE; + UT_LIST_INIT(mem_block_list); + } + + UT_LIST_ADD_LAST(mem_block_list, mem_block_list, block); + + mem_pool_mutex_exit(); +#endif + mem_block_set_len(block, len); + mem_block_set_type(block, type); + mem_block_set_free(block, MEM_BLOCK_HEADER_SIZE); + mem_block_set_start(block, MEM_BLOCK_HEADER_SIZE); + + block->free_block = NULL; + + ut_ad((ulint)MEM_BLOCK_HEADER_SIZE < len); + + return(block); +} + +/******************************************************************* +Adds a new block to a memory heap. */ +UNIV_INTERN +mem_block_t* +mem_heap_add_block( +/*===============*/ + /* out: created block, NULL if did not + succeed (only possible for + MEM_HEAP_BTR_SEARCH type heaps)*/ + mem_heap_t* heap, /* in: memory heap */ + ulint n) /* in: number of bytes user needs */ +{ + mem_block_t* block; + mem_block_t* new_block; + ulint new_size; + + ut_ad(mem_heap_check(heap)); + + block = UT_LIST_GET_LAST(heap->base); + + /* We have to allocate a new block. The size is always at least + doubled until the standard size is reached. After that the size + stays the same, except in cases where the caller needs more space. */ + + new_size = 2 * mem_block_get_len(block); + + if (heap->type != MEM_HEAP_DYNAMIC) { + /* From the buffer pool we allocate buffer frames */ + ut_a(n <= MEM_MAX_ALLOC_IN_BUF); + + if (new_size > MEM_MAX_ALLOC_IN_BUF) { + new_size = MEM_MAX_ALLOC_IN_BUF; + } + } else if (new_size > MEM_BLOCK_STANDARD_SIZE) { + + new_size = MEM_BLOCK_STANDARD_SIZE; + } + + if (new_size < n) { + new_size = n; + } + + new_block = mem_heap_create_block(heap, new_size, heap->type, + heap->file_name, heap->line); + if (new_block == NULL) { + + return(NULL); + } + + /* Add the new block as the last block */ + + UT_LIST_INSERT_AFTER(list, heap->base, block, new_block); + + return(new_block); +} + +/********************************************************************** +Frees a block from a memory heap. */ +UNIV_INTERN +void +mem_heap_block_free( +/*================*/ + mem_heap_t* heap, /* in: heap */ + mem_block_t* block) /* in: block to free */ +{ + ulint type; + ulint len; + buf_block_t* buf_block; + + if (block->magic_n != MEM_BLOCK_MAGIC_N) { + mem_analyze_corruption(block); + } + + UT_LIST_REMOVE(list, heap->base, block); + +#ifdef MEM_PERIODIC_CHECK + mem_pool_mutex_enter(); + + UT_LIST_REMOVE(mem_block_list, mem_block_list, block); + + mem_pool_mutex_exit(); +#endif + type = heap->type; + len = block->len; + buf_block = block->buf_block; + block->magic_n = MEM_FREED_BLOCK_MAGIC_N; + +#ifdef UNIV_MEM_DEBUG + /* In the debug version we set the memory to a random combination + of hex 0xDE and 0xAD. */ + + mem_erase_buf((byte*)block, len); +#else /* UNIV_MEM_DEBUG */ + UNIV_MEM_ASSERT_AND_FREE(block, len); +#endif /* UNIV_MEM_DEBUG */ + + if (type == MEM_HEAP_DYNAMIC || len < UNIV_PAGE_SIZE / 2) { + + ut_ad(!buf_block); + mem_area_free(block, mem_comm_pool); + } else { + ut_ad(type & MEM_HEAP_BUFFER); + + buf_block_free(buf_block); + } +} + +/********************************************************************** +Frees the free_block field from a memory heap. */ +UNIV_INTERN +void +mem_heap_free_block_free( +/*=====================*/ + mem_heap_t* heap) /* in: heap */ +{ + if (UNIV_LIKELY_NULL(heap->free_block)) { + + buf_block_free(heap->free_block); + + heap->free_block = NULL; + } +} + +#ifdef MEM_PERIODIC_CHECK +/********************************************************************** +Goes through the list of all allocated mem blocks, checks their magic +numbers, and reports possible corruption. */ +UNIV_INTERN +void +mem_validate_all_blocks(void) +/*=========================*/ +{ + mem_block_t* block; + + mem_pool_mutex_enter(); + + block = UT_LIST_GET_FIRST(mem_block_list); + + while (block) { + if (block->magic_n != MEM_BLOCK_MAGIC_N) { + mem_analyze_corruption(block); + } + + block = UT_LIST_GET_NEXT(mem_block_list, block); + } + + mem_pool_mutex_exit(); +} +#endif diff --git a/storage/xtradb/mem/mem0pool.c b/storage/xtradb/mem/mem0pool.c new file mode 100644 index 00000000000..34de6b2a706 --- /dev/null +++ b/storage/xtradb/mem/mem0pool.c @@ -0,0 +1,704 @@ +/***************************************************************************** + +Copyright (c) 1997, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/************************************************************************ +The lowest-level memory management + +Created 5/12/1997 Heikki Tuuri +*************************************************************************/ + +#include "mem0pool.h" +#ifdef UNIV_NONINL +#include "mem0pool.ic" +#endif + +#include "srv0srv.h" +#include "sync0sync.h" +#include "ut0mem.h" +#include "ut0lst.h" +#include "ut0byte.h" +#include "mem0mem.h" + +/* We would like to use also the buffer frames to allocate memory. This +would be desirable, because then the memory consumption of the database +would be fixed, and we might even lock the buffer pool to the main memory. +The problem here is that the buffer management routines can themselves call +memory allocation, while the buffer pool mutex is reserved. + +The main components of the memory consumption are: + +1. buffer pool, +2. parsed and optimized SQL statements, +3. data dictionary cache, +4. log buffer, +5. locks for each transaction, +6. hash table for the adaptive index, +7. state and buffers for each SQL query currently being executed, +8. session for each user, and +9. stack for each OS thread. + +Items 1 and 2 are managed by an LRU algorithm. Items 5 and 6 can potentially +consume very much memory. Items 7 and 8 should consume quite little memory, +and the OS should take care of item 9, which too should consume little memory. + +A solution to the memory management: + +1. the buffer pool size is set separately; +2. log buffer size is set separately; +3. the common pool size for all the other entries, except 8, is set separately. + +Problems: we may waste memory if the common pool is set too big. Another +problem is the locks, which may take very much space in big transactions. +Then the shared pool size should be set very big. We can allow locks to take +space from the buffer pool, but the SQL optimizer is then unaware of the +usable size of the buffer pool. We could also combine the objects in the +common pool and the buffers in the buffer pool into a single LRU list and +manage it uniformly, but this approach does not take into account the parsing +and other costs unique to SQL statements. + +The locks for a transaction can be seen as a part of the state of the +transaction. Hence, they should be stored in the common pool. We still +have the problem of a very big update transaction, for example, which +will set very many x-locks on rows, and the locks will consume a lot +of memory, say, half of the buffer pool size. + +Another problem is what to do if we are not able to malloc a requested +block of memory from the common pool. Then we can request memory from +the operating system. If it does not help, a system error results. + +Because 5 and 6 may potentially consume very much memory, we let them grow +into the buffer pool. We may let the locks of a transaction take frames +from the buffer pool, when the corresponding memory heap block has grown to +the size of a buffer frame. Similarly for the hash node cells of the locks, +and for the adaptive index. Thus, for each individual transaction, its locks +can occupy at most about the size of the buffer frame of memory in the common +pool, and after that its locks will grow into the buffer pool. */ + +/* Mask used to extract the free bit from area->size */ +#define MEM_AREA_FREE 1 + +/* The smallest memory area total size */ +#define MEM_AREA_MIN_SIZE (2 * MEM_AREA_EXTRA_SIZE) + + +/* Data structure for a memory pool. The space is allocated using the buddy +algorithm, where free list i contains areas of size 2 to power i. */ +struct mem_pool_struct{ + byte* buf; /* memory pool */ + ulint size; /* memory common pool size */ + ulint reserved; /* amount of currently allocated + memory */ + mutex_t mutex; /* mutex protecting this struct */ + UT_LIST_BASE_NODE_T(mem_area_t) + free_list[64]; /* lists of free memory areas: an + area is put to the list whose number + is the 2-logarithm of the area size */ +}; + +/* The common memory pool */ +UNIV_INTERN mem_pool_t* mem_comm_pool = NULL; + +/* We use this counter to check that the mem pool mutex does not leak; +this is to track a strange assertion failure reported at +mysql@lists.mysql.com */ + +UNIV_INTERN ulint mem_n_threads_inside = 0; + +/************************************************************************ +Reserves the mem pool mutex. */ +UNIV_INTERN +void +mem_pool_mutex_enter(void) +/*======================*/ +{ + mutex_enter(&(mem_comm_pool->mutex)); +} + +/************************************************************************ +Releases the mem pool mutex. */ +UNIV_INTERN +void +mem_pool_mutex_exit(void) +/*=====================*/ +{ + mutex_exit(&(mem_comm_pool->mutex)); +} + +/************************************************************************ +Returns memory area size. */ +UNIV_INLINE +ulint +mem_area_get_size( +/*==============*/ + /* out: size */ + mem_area_t* area) /* in: area */ +{ + return(area->size_and_free & ~MEM_AREA_FREE); +} + +/************************************************************************ +Sets memory area size. */ +UNIV_INLINE +void +mem_area_set_size( +/*==============*/ + mem_area_t* area, /* in: area */ + ulint size) /* in: size */ +{ + area->size_and_free = (area->size_and_free & MEM_AREA_FREE) + | size; +} + +/************************************************************************ +Returns memory area free bit. */ +UNIV_INLINE +ibool +mem_area_get_free( +/*==============*/ + /* out: TRUE if free */ + mem_area_t* area) /* in: area */ +{ +#if TRUE != MEM_AREA_FREE +# error "TRUE != MEM_AREA_FREE" +#endif + return(area->size_and_free & MEM_AREA_FREE); +} + +/************************************************************************ +Sets memory area free bit. */ +UNIV_INLINE +void +mem_area_set_free( +/*==============*/ + mem_area_t* area, /* in: area */ + ibool free) /* in: free bit value */ +{ +#if TRUE != MEM_AREA_FREE +# error "TRUE != MEM_AREA_FREE" +#endif + area->size_and_free = (area->size_and_free & ~MEM_AREA_FREE) + | free; +} + +/************************************************************************ +Creates a memory pool. */ +UNIV_INTERN +mem_pool_t* +mem_pool_create( +/*============*/ + /* out: memory pool */ + ulint size) /* in: pool size in bytes */ +{ + mem_pool_t* pool; + mem_area_t* area; + ulint i; + ulint used; + + pool = ut_malloc(sizeof(mem_pool_t)); + + /* We do not set the memory to zero (FALSE) in the pool, + but only when allocated at a higher level in mem0mem.c. + This is to avoid masking useful Purify warnings. */ + + pool->buf = ut_malloc_low(size, FALSE, TRUE); + pool->size = size; + + mutex_create(&pool->mutex, SYNC_MEM_POOL); + + /* Initialize the free lists */ + + for (i = 0; i < 64; i++) { + + UT_LIST_INIT(pool->free_list[i]); + } + + used = 0; + + while (size - used >= MEM_AREA_MIN_SIZE) { + + i = ut_2_log(size - used); + + if (ut_2_exp(i) > size - used) { + + /* ut_2_log rounds upward */ + + i--; + } + + area = (mem_area_t*)(pool->buf + used); + + mem_area_set_size(area, ut_2_exp(i)); + mem_area_set_free(area, TRUE); + UNIV_MEM_FREE(MEM_AREA_EXTRA_SIZE + (byte*) area, + ut_2_exp(i) - MEM_AREA_EXTRA_SIZE); + + UT_LIST_ADD_FIRST(free_list, pool->free_list[i], area); + + used = used + ut_2_exp(i); + } + + ut_ad(size >= used); + + pool->reserved = 0; + + return(pool); +} + +/************************************************************************ +Fills the specified free list. */ +static +ibool +mem_pool_fill_free_list( +/*====================*/ + /* out: TRUE if we were able to insert a + block to the free list */ + ulint i, /* in: free list index */ + mem_pool_t* pool) /* in: memory pool */ +{ + mem_area_t* area; + mem_area_t* area2; + ibool ret; + + ut_ad(mutex_own(&(pool->mutex))); + + if (UNIV_UNLIKELY(i >= 63)) { + /* We come here when we have run out of space in the + memory pool: */ + + return(FALSE); + } + + area = UT_LIST_GET_FIRST(pool->free_list[i + 1]); + + if (area == NULL) { + if (UT_LIST_GET_LEN(pool->free_list[i + 1]) > 0) { + ut_print_timestamp(stderr); + + fprintf(stderr, + " InnoDB: Error: mem pool free list %lu" + " length is %lu\n" + "InnoDB: though the list is empty!\n", + (ulong) i + 1, + (ulong) + UT_LIST_GET_LEN(pool->free_list[i + 1])); + } + + ret = mem_pool_fill_free_list(i + 1, pool); + + if (ret == FALSE) { + + return(FALSE); + } + + area = UT_LIST_GET_FIRST(pool->free_list[i + 1]); + } + + if (UNIV_UNLIKELY(UT_LIST_GET_LEN(pool->free_list[i + 1]) == 0)) { + mem_analyze_corruption(area); + + ut_error; + } + + UT_LIST_REMOVE(free_list, pool->free_list[i + 1], area); + + area2 = (mem_area_t*)(((byte*)area) + ut_2_exp(i)); + UNIV_MEM_ALLOC(area2, MEM_AREA_EXTRA_SIZE); + + mem_area_set_size(area2, ut_2_exp(i)); + mem_area_set_free(area2, TRUE); + + UT_LIST_ADD_FIRST(free_list, pool->free_list[i], area2); + + mem_area_set_size(area, ut_2_exp(i)); + + UT_LIST_ADD_FIRST(free_list, pool->free_list[i], area); + + return(TRUE); +} + +/************************************************************************ +Allocates memory from a pool. NOTE: This low-level function should only be +used in mem0mem.*! */ +UNIV_INTERN +void* +mem_area_alloc( +/*===========*/ + /* out, own: allocated memory buffer */ + ulint* psize, /* in: requested size in bytes; for optimum + space usage, the size should be a power of 2 + minus MEM_AREA_EXTRA_SIZE; + out: allocated size in bytes (greater than + or equal to the requested size) */ + mem_pool_t* pool) /* in: memory pool */ +{ + mem_area_t* area; + ulint size; + ulint n; + ibool ret; + + /* If we are using os allocator just make a simple call + to malloc */ + if (UNIV_LIKELY(srv_use_sys_malloc)) { + return(malloc(*psize)); + } + + size = *psize; + n = ut_2_log(ut_max(size + MEM_AREA_EXTRA_SIZE, MEM_AREA_MIN_SIZE)); + + mutex_enter(&(pool->mutex)); + mem_n_threads_inside++; + + ut_a(mem_n_threads_inside == 1); + + area = UT_LIST_GET_FIRST(pool->free_list[n]); + + if (area == NULL) { + ret = mem_pool_fill_free_list(n, pool); + + if (ret == FALSE) { + /* Out of memory in memory pool: we try to allocate + from the operating system with the regular malloc: */ + + mem_n_threads_inside--; + mutex_exit(&(pool->mutex)); + + return(ut_malloc(size)); + } + + area = UT_LIST_GET_FIRST(pool->free_list[n]); + } + + if (!mem_area_get_free(area)) { + fprintf(stderr, + "InnoDB: Error: Removing element from mem pool" + " free list %lu though the\n" + "InnoDB: element is not marked free!\n", + (ulong) n); + + mem_analyze_corruption(area); + + /* Try to analyze a strange assertion failure reported at + mysql@lists.mysql.com where the free bit IS 1 in the + hex dump above */ + + if (mem_area_get_free(area)) { + fprintf(stderr, + "InnoDB: Probably a race condition" + " because now the area is marked free!\n"); + } + + ut_error; + } + + if (UT_LIST_GET_LEN(pool->free_list[n]) == 0) { + fprintf(stderr, + "InnoDB: Error: Removing element from mem pool" + " free list %lu\n" + "InnoDB: though the list length is 0!\n", + (ulong) n); + mem_analyze_corruption(area); + + ut_error; + } + + ut_ad(mem_area_get_size(area) == ut_2_exp(n)); + + mem_area_set_free(area, FALSE); + + UT_LIST_REMOVE(free_list, pool->free_list[n], area); + + pool->reserved += mem_area_get_size(area); + + mem_n_threads_inside--; + mutex_exit(&(pool->mutex)); + + ut_ad(mem_pool_validate(pool)); + + *psize = ut_2_exp(n) - MEM_AREA_EXTRA_SIZE; + UNIV_MEM_ALLOC(MEM_AREA_EXTRA_SIZE + (byte*)area, *psize); + + return((void*)(MEM_AREA_EXTRA_SIZE + ((byte*)area))); +} + +/************************************************************************ +Gets the buddy of an area, if it exists in pool. */ +UNIV_INLINE +mem_area_t* +mem_area_get_buddy( +/*===============*/ + /* out: the buddy, NULL if no buddy in pool */ + mem_area_t* area, /* in: memory area */ + ulint size, /* in: memory area size */ + mem_pool_t* pool) /* in: memory pool */ +{ + mem_area_t* buddy; + + ut_ad(size != 0); + + if (((((byte*)area) - pool->buf) % (2 * size)) == 0) { + + /* The buddy is in a higher address */ + + buddy = (mem_area_t*)(((byte*)area) + size); + + if ((((byte*)buddy) - pool->buf) + size > pool->size) { + + /* The buddy is not wholly contained in the pool: + there is no buddy */ + + buddy = NULL; + } + } else { + /* The buddy is in a lower address; NOTE that area cannot + be at the pool lower end, because then we would end up to + the upper branch in this if-clause: the remainder would be + 0 */ + + buddy = (mem_area_t*)(((byte*)area) - size); + } + + return(buddy); +} + +/************************************************************************ +Frees memory to a pool. */ +UNIV_INTERN +void +mem_area_free( +/*==========*/ + void* ptr, /* in, own: pointer to allocated memory + buffer */ + mem_pool_t* pool) /* in: memory pool */ +{ + mem_area_t* area; + mem_area_t* buddy; + void* new_ptr; + ulint size; + ulint n; + + if (UNIV_LIKELY(srv_use_sys_malloc)) { + free(ptr); + + return; + } + + /* It may be that the area was really allocated from the OS with + regular malloc: check if ptr points within our memory pool */ + + if ((byte*)ptr < pool->buf || (byte*)ptr >= pool->buf + pool->size) { + ut_free(ptr); + + return; + } + + area = (mem_area_t*) (((byte*)ptr) - MEM_AREA_EXTRA_SIZE); + + if (mem_area_get_free(area)) { + fprintf(stderr, + "InnoDB: Error: Freeing element to mem pool" + " free list though the\n" + "InnoDB: element is marked free!\n"); + + mem_analyze_corruption(area); + ut_error; + } + + size = mem_area_get_size(area); + UNIV_MEM_FREE(ptr, size - MEM_AREA_EXTRA_SIZE); + + if (size == 0) { + fprintf(stderr, + "InnoDB: Error: Mem area size is 0. Possibly a" + " memory overrun of the\n" + "InnoDB: previous allocated area!\n"); + + mem_analyze_corruption(area); + ut_error; + } + +#ifdef UNIV_LIGHT_MEM_DEBUG + if (((byte*)area) + size < pool->buf + pool->size) { + + ulint next_size; + + next_size = mem_area_get_size( + (mem_area_t*)(((byte*)area) + size)); + if (UNIV_UNLIKELY(!next_size || !ut_is_2pow(next_size))) { + fprintf(stderr, + "InnoDB: Error: Memory area size %lu," + " next area size %lu not a power of 2!\n" + "InnoDB: Possibly a memory overrun of" + " the buffer being freed here.\n", + (ulong) size, (ulong) next_size); + mem_analyze_corruption(area); + + ut_error; + } + } +#endif + buddy = mem_area_get_buddy(area, size, pool); + + n = ut_2_log(size); + + mutex_enter(&(pool->mutex)); + mem_n_threads_inside++; + + ut_a(mem_n_threads_inside == 1); + + if (buddy && mem_area_get_free(buddy) + && (size == mem_area_get_size(buddy))) { + + /* The buddy is in a free list */ + + if ((byte*)buddy < (byte*)area) { + new_ptr = ((byte*)buddy) + MEM_AREA_EXTRA_SIZE; + + mem_area_set_size(buddy, 2 * size); + mem_area_set_free(buddy, FALSE); + } else { + new_ptr = ptr; + + mem_area_set_size(area, 2 * size); + } + + /* Remove the buddy from its free list and merge it to area */ + + UT_LIST_REMOVE(free_list, pool->free_list[n], buddy); + + pool->reserved += ut_2_exp(n); + + mem_n_threads_inside--; + mutex_exit(&(pool->mutex)); + + mem_area_free(new_ptr, pool); + + return; + } else { + UT_LIST_ADD_FIRST(free_list, pool->free_list[n], area); + + mem_area_set_free(area, TRUE); + + ut_ad(pool->reserved >= size); + + pool->reserved -= size; + } + + mem_n_threads_inside--; + mutex_exit(&(pool->mutex)); + + ut_ad(mem_pool_validate(pool)); +} + +/************************************************************************ +Validates a memory pool. */ +UNIV_INTERN +ibool +mem_pool_validate( +/*==============*/ + /* out: TRUE if ok */ + mem_pool_t* pool) /* in: memory pool */ +{ + mem_area_t* area; + mem_area_t* buddy; + ulint free; + ulint i; + + mutex_enter(&(pool->mutex)); + + free = 0; + + for (i = 0; i < 64; i++) { + + UT_LIST_VALIDATE(free_list, mem_area_t, pool->free_list[i]); + + area = UT_LIST_GET_FIRST(pool->free_list[i]); + + while (area != NULL) { + ut_a(mem_area_get_free(area)); + ut_a(mem_area_get_size(area) == ut_2_exp(i)); + + buddy = mem_area_get_buddy(area, ut_2_exp(i), pool); + + ut_a(!buddy || !mem_area_get_free(buddy) + || (ut_2_exp(i) != mem_area_get_size(buddy))); + + area = UT_LIST_GET_NEXT(free_list, area); + + free += ut_2_exp(i); + } + } + + ut_a(free + pool->reserved == pool->size); + + mutex_exit(&(pool->mutex)); + + return(TRUE); +} + +/************************************************************************ +Prints info of a memory pool. */ +UNIV_INTERN +void +mem_pool_print_info( +/*================*/ + FILE* outfile,/* in: output file to write to */ + mem_pool_t* pool) /* in: memory pool */ +{ + ulint i; + + mem_pool_validate(pool); + + fprintf(outfile, "INFO OF A MEMORY POOL\n"); + + mutex_enter(&(pool->mutex)); + + for (i = 0; i < 64; i++) { + if (UT_LIST_GET_LEN(pool->free_list[i]) > 0) { + + fprintf(outfile, + "Free list length %lu for" + " blocks of size %lu\n", + (ulong) UT_LIST_GET_LEN(pool->free_list[i]), + (ulong) ut_2_exp(i)); + } + } + + fprintf(outfile, "Pool size %lu, reserved %lu.\n", (ulong) pool->size, + (ulong) pool->reserved); + mutex_exit(&(pool->mutex)); +} + +/************************************************************************ +Returns the amount of reserved memory. */ +UNIV_INTERN +ulint +mem_pool_get_reserved( +/*==================*/ + /* out: reserved memory in bytes */ + mem_pool_t* pool) /* in: memory pool */ +{ + ulint reserved; + + mutex_enter(&(pool->mutex)); + + reserved = pool->reserved; + + mutex_exit(&(pool->mutex)); + + return(reserved); +} diff --git a/storage/xtradb/mtr/mtr0log.c b/storage/xtradb/mtr/mtr0log.c new file mode 100644 index 00000000000..0fe66d08c05 --- /dev/null +++ b/storage/xtradb/mtr/mtr0log.c @@ -0,0 +1,609 @@ +/***************************************************************************** + +Copyright (c) 1995, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/****************************************************** +Mini-transaction log routines + +Created 12/7/1995 Heikki Tuuri +*******************************************************/ + +#include "mtr0log.h" + +#ifdef UNIV_NONINL +#include "mtr0log.ic" +#endif + +#include "buf0buf.h" +#include "dict0boot.h" +#include "log0recv.h" +#include "page0page.h" + +/************************************************************ +Catenates n bytes to the mtr log. */ +UNIV_INTERN +void +mlog_catenate_string( +/*=================*/ + mtr_t* mtr, /* in: mtr */ + const byte* str, /* in: string to write */ + ulint len) /* in: string length */ +{ + dyn_array_t* mlog; + + if (mtr_get_log_mode(mtr) == MTR_LOG_NONE) { + + return; + } + + mlog = &(mtr->log); + + dyn_push_string(mlog, str, len); +} + +/************************************************************ +Writes the initial part of a log record consisting of one-byte item +type and four-byte space and page numbers. Also pushes info +to the mtr memo that a buffer page has been modified. */ +UNIV_INTERN +void +mlog_write_initial_log_record( +/*==========================*/ + const byte* ptr, /* in: pointer to (inside) a buffer + frame holding the file page where + modification is made */ + byte type, /* in: log item type: MLOG_1BYTE, ... */ + mtr_t* mtr) /* in: mini-transaction handle */ +{ + byte* log_ptr; + + ut_ad(type <= MLOG_BIGGEST_TYPE); + ut_ad(type > MLOG_8BYTES); + + log_ptr = mlog_open(mtr, 11); + + /* If no logging is requested, we may return now */ + if (log_ptr == NULL) { + + return; + } + + log_ptr = mlog_write_initial_log_record_fast(ptr, type, log_ptr, mtr); + + mlog_close(mtr, log_ptr); +} + +/************************************************************ +Parses an initial log record written by mlog_write_initial_log_record. */ +UNIV_INTERN +byte* +mlog_parse_initial_log_record( +/*==========================*/ + /* out: parsed record end, NULL if not a complete + record */ + byte* ptr, /* in: buffer */ + byte* end_ptr,/* in: buffer end */ + byte* type, /* out: log record type: MLOG_1BYTE, ... */ + ulint* space, /* out: space id */ + ulint* page_no)/* out: page number */ +{ + if (end_ptr < ptr + 1) { + + return(NULL); + } + + *type = (byte)((ulint)*ptr & ~MLOG_SINGLE_REC_FLAG); + ut_ad(*type <= MLOG_BIGGEST_TYPE); + + ptr++; + + if (end_ptr < ptr + 2) { + + return(NULL); + } + + ptr = mach_parse_compressed(ptr, end_ptr, space); + + if (ptr == NULL) { + + return(NULL); + } + + ptr = mach_parse_compressed(ptr, end_ptr, page_no); + + return(ptr); +} + +/************************************************************ +Parses a log record written by mlog_write_ulint or mlog_write_dulint. */ +UNIV_INTERN +byte* +mlog_parse_nbytes( +/*==============*/ + /* out: parsed record end, NULL if not a complete + record or a corrupt record */ + ulint type, /* in: log record type: MLOG_1BYTE, ... */ + byte* ptr, /* in: buffer */ + byte* end_ptr,/* in: buffer end */ + byte* page, /* in: page where to apply the log record, or NULL */ + void* page_zip)/* in/out: compressed page, or NULL */ +{ + ulint offset; + ulint val; + dulint dval; + + ut_a(type <= MLOG_8BYTES); + ut_a(!page || !page_zip || fil_page_get_type(page) != FIL_PAGE_INDEX); + + if (end_ptr < ptr + 2) { + + return(NULL); + } + + offset = mach_read_from_2(ptr); + ptr += 2; + + if (offset >= UNIV_PAGE_SIZE) { + recv_sys->found_corrupt_log = TRUE; + + return(NULL); + } + + if (type == MLOG_8BYTES) { + ptr = mach_dulint_parse_compressed(ptr, end_ptr, &dval); + + if (ptr == NULL) { + + return(NULL); + } + + if (page) { + if (UNIV_LIKELY_NULL(page_zip)) { + mach_write_to_8 + (((page_zip_des_t*) page_zip)->data + + offset, dval); + } + mach_write_to_8(page + offset, dval); + } + + return(ptr); + } + + ptr = mach_parse_compressed(ptr, end_ptr, &val); + + if (ptr == NULL) { + + return(NULL); + } + + switch (type) { + case MLOG_1BYTE: + if (UNIV_UNLIKELY(val > 0xFFUL)) { + goto corrupt; + } + if (page) { + if (UNIV_LIKELY_NULL(page_zip)) { + mach_write_to_1 + (((page_zip_des_t*) page_zip)->data + + offset, val); + } + mach_write_to_1(page + offset, val); + } + break; + case MLOG_2BYTES: + if (UNIV_UNLIKELY(val > 0xFFFFUL)) { + goto corrupt; + } + if (page) { + if (UNIV_LIKELY_NULL(page_zip)) { + mach_write_to_2 + (((page_zip_des_t*) page_zip)->data + + offset, val); + } + mach_write_to_2(page + offset, val); + } + break; + case MLOG_4BYTES: + if (page) { + if (UNIV_LIKELY_NULL(page_zip)) { + mach_write_to_4 + (((page_zip_des_t*) page_zip)->data + + offset, val); + } + mach_write_to_4(page + offset, val); + } + break; + default: + corrupt: + recv_sys->found_corrupt_log = TRUE; + ptr = NULL; + } + + return(ptr); +} + +/************************************************************ +Writes 1 - 4 bytes to a file page buffered in the buffer pool. +Writes the corresponding log record to the mini-transaction log. */ +UNIV_INTERN +void +mlog_write_ulint( +/*=============*/ + byte* ptr, /* in: pointer where to write */ + ulint val, /* in: value to write */ + byte type, /* in: MLOG_1BYTE, MLOG_2BYTES, MLOG_4BYTES */ + mtr_t* mtr) /* in: mini-transaction handle */ +{ + byte* log_ptr; + + switch (type) { + case MLOG_1BYTE: + mach_write_to_1(ptr, val); + break; + case MLOG_2BYTES: + mach_write_to_2(ptr, val); + break; + case MLOG_4BYTES: + mach_write_to_4(ptr, val); + break; + default: + ut_error; + } + + log_ptr = mlog_open(mtr, 11 + 2 + 5); + + /* If no logging is requested, we may return now */ + if (log_ptr == NULL) { + + return; + } + + log_ptr = mlog_write_initial_log_record_fast(ptr, type, log_ptr, mtr); + + mach_write_to_2(log_ptr, page_offset(ptr)); + log_ptr += 2; + + log_ptr += mach_write_compressed(log_ptr, val); + + mlog_close(mtr, log_ptr); +} + +/************************************************************ +Writes 8 bytes to a file page buffered in the buffer pool. +Writes the corresponding log record to the mini-transaction log. */ +UNIV_INTERN +void +mlog_write_dulint( +/*==============*/ + byte* ptr, /* in: pointer where to write */ + dulint val, /* in: value to write */ + mtr_t* mtr) /* in: mini-transaction handle */ +{ + byte* log_ptr; + + ut_ad(ptr && mtr); + + mach_write_to_8(ptr, val); + + log_ptr = mlog_open(mtr, 11 + 2 + 9); + + /* If no logging is requested, we may return now */ + if (log_ptr == NULL) { + + return; + } + + log_ptr = mlog_write_initial_log_record_fast(ptr, MLOG_8BYTES, + log_ptr, mtr); + + mach_write_to_2(log_ptr, page_offset(ptr)); + log_ptr += 2; + + log_ptr += mach_dulint_write_compressed(log_ptr, val); + + mlog_close(mtr, log_ptr); +} + +/************************************************************ +Writes a string to a file page buffered in the buffer pool. Writes the +corresponding log record to the mini-transaction log. */ +UNIV_INTERN +void +mlog_write_string( +/*==============*/ + byte* ptr, /* in: pointer where to write */ + const byte* str, /* in: string to write */ + ulint len, /* in: string length */ + mtr_t* mtr) /* in: mini-transaction handle */ +{ + ut_ad(ptr && mtr); + ut_a(len < UNIV_PAGE_SIZE); + + memcpy(ptr, str, len); + + mlog_log_string(ptr, len, mtr); +} + +/************************************************************ +Logs a write of a string to a file page buffered in the buffer pool. +Writes the corresponding log record to the mini-transaction log. */ +UNIV_INTERN +void +mlog_log_string( +/*============*/ + byte* ptr, /* in: pointer written to */ + ulint len, /* in: string length */ + mtr_t* mtr) /* in: mini-transaction handle */ +{ + byte* log_ptr; + + ut_ad(ptr && mtr); + ut_ad(len <= UNIV_PAGE_SIZE); + + log_ptr = mlog_open(mtr, 30); + + /* If no logging is requested, we may return now */ + if (log_ptr == NULL) { + + return; + } + + log_ptr = mlog_write_initial_log_record_fast(ptr, MLOG_WRITE_STRING, + log_ptr, mtr); + mach_write_to_2(log_ptr, page_offset(ptr)); + log_ptr += 2; + + mach_write_to_2(log_ptr, len); + log_ptr += 2; + + mlog_close(mtr, log_ptr); + + mlog_catenate_string(mtr, ptr, len); +} + +/************************************************************ +Parses a log record written by mlog_write_string. */ +UNIV_INTERN +byte* +mlog_parse_string( +/*==============*/ + /* out: parsed record end, NULL if not a complete + record */ + byte* ptr, /* in: buffer */ + byte* end_ptr,/* in: buffer end */ + byte* page, /* in: page where to apply the log record, or NULL */ + void* page_zip)/* in/out: compressed page, or NULL */ +{ + ulint offset; + ulint len; + + ut_a(!page || !page_zip || fil_page_get_type(page) != FIL_PAGE_INDEX); + + if (end_ptr < ptr + 4) { + + return(NULL); + } + + offset = mach_read_from_2(ptr); + ptr += 2; + len = mach_read_from_2(ptr); + ptr += 2; + + if (UNIV_UNLIKELY(offset >= UNIV_PAGE_SIZE) + || UNIV_UNLIKELY(len + offset) > UNIV_PAGE_SIZE) { + recv_sys->found_corrupt_log = TRUE; + + return(NULL); + } + + if (end_ptr < ptr + len) { + + return(NULL); + } + + if (page) { + if (UNIV_LIKELY_NULL(page_zip)) { + memcpy(((page_zip_des_t*) page_zip)->data + + offset, ptr, len); + } + memcpy(page + offset, ptr, len); + } + + return(ptr + len); +} + +/************************************************************ +Opens a buffer for mlog, writes the initial log record and, +if needed, the field lengths of an index. */ +UNIV_INTERN +byte* +mlog_open_and_write_index( +/*======================*/ + /* out: buffer, NULL if log mode + MTR_LOG_NONE */ + mtr_t* mtr, /* in: mtr */ + byte* rec, /* in: index record or page */ + dict_index_t* index, /* in: record descriptor */ + byte type, /* in: log item type */ + ulint size) /* in: requested buffer size in bytes + (if 0, calls mlog_close() and returns NULL) */ +{ + byte* log_ptr; + const byte* log_start; + const byte* log_end; + + ut_ad(!!page_rec_is_comp(rec) == dict_table_is_comp(index->table)); + + if (!page_rec_is_comp(rec)) { + log_start = log_ptr = mlog_open(mtr, 11 + size); + if (!log_ptr) { + return(NULL); /* logging is disabled */ + } + log_ptr = mlog_write_initial_log_record_fast(rec, type, + log_ptr, mtr); + log_end = log_ptr + 11 + size; + } else { + ulint i; + ulint n = dict_index_get_n_fields(index); + /* total size needed */ + ulint total = 11 + size + (n + 2) * 2; + ulint alloc = total; + /* allocate at most DYN_ARRAY_DATA_SIZE at a time */ + if (alloc > DYN_ARRAY_DATA_SIZE) { + alloc = DYN_ARRAY_DATA_SIZE; + } + log_start = log_ptr = mlog_open(mtr, alloc); + if (!log_ptr) { + return(NULL); /* logging is disabled */ + } + log_end = log_ptr + alloc; + log_ptr = mlog_write_initial_log_record_fast(rec, type, + log_ptr, mtr); + mach_write_to_2(log_ptr, n); + log_ptr += 2; + mach_write_to_2(log_ptr, + dict_index_get_n_unique_in_tree(index)); + log_ptr += 2; + for (i = 0; i < n; i++) { + dict_field_t* field; + const dict_col_t* col; + ulint len; + + field = dict_index_get_nth_field(index, i); + col = dict_field_get_col(field); + len = field->fixed_len; + ut_ad(len < 0x7fff); + if (len == 0 + && (col->len > 255 || col->mtype == DATA_BLOB)) { + /* variable-length field + with maximum length > 255 */ + len = 0x7fff; + } + if (col->prtype & DATA_NOT_NULL) { + len |= 0x8000; + } + if (log_ptr + 2 > log_end) { + mlog_close(mtr, log_ptr); + ut_a(total > (ulint) (log_ptr - log_start)); + total -= log_ptr - log_start; + alloc = total; + if (alloc > DYN_ARRAY_DATA_SIZE) { + alloc = DYN_ARRAY_DATA_SIZE; + } + log_start = log_ptr = mlog_open(mtr, alloc); + if (!log_ptr) { + return(NULL); /* logging is disabled */ + } + log_end = log_ptr + alloc; + } + mach_write_to_2(log_ptr, len); + log_ptr += 2; + } + } + if (size == 0) { + mlog_close(mtr, log_ptr); + log_ptr = NULL; + } else if (log_ptr + size > log_end) { + mlog_close(mtr, log_ptr); + log_ptr = mlog_open(mtr, size); + } + return(log_ptr); +} + +/************************************************************ +Parses a log record written by mlog_open_and_write_index. */ +UNIV_INTERN +byte* +mlog_parse_index( +/*=============*/ + /* out: parsed record end, + NULL if not a complete record */ + byte* ptr, /* in: buffer */ + byte* end_ptr,/* in: buffer end */ + /* out: new value of log_ptr */ + ibool comp, /* in: TRUE=compact record format */ + dict_index_t** index) /* out, own: dummy index */ +{ + ulint i, n, n_uniq; + dict_table_t* table; + dict_index_t* ind; + + ut_ad(comp == FALSE || comp == TRUE); + + if (comp) { + if (end_ptr < ptr + 4) { + return(NULL); + } + n = mach_read_from_2(ptr); + ptr += 2; + n_uniq = mach_read_from_2(ptr); + ptr += 2; + ut_ad(n_uniq <= n); + if (end_ptr < ptr + n * 2) { + return(NULL); + } + } else { + n = n_uniq = 1; + } + table = dict_mem_table_create("LOG_DUMMY", DICT_HDR_SPACE, n, + comp ? DICT_TF_COMPACT : 0); + ind = dict_mem_index_create("LOG_DUMMY", "LOG_DUMMY", + DICT_HDR_SPACE, 0, n); + ind->table = table; + ind->n_uniq = (unsigned int) n_uniq; + if (n_uniq != n) { + ut_a(n_uniq + DATA_ROLL_PTR <= n); + ind->type = DICT_CLUSTERED; + } + if (comp) { + for (i = 0; i < n; i++) { + ulint len = mach_read_from_2(ptr); + ptr += 2; + /* The high-order bit of len is the NOT NULL flag; + the rest is 0 or 0x7fff for variable-length fields, + and 1..0x7ffe for fixed-length fields. */ + dict_mem_table_add_col( + table, NULL, NULL, + ((len + 1) & 0x7fff) <= 1 + ? DATA_BINARY : DATA_FIXBINARY, + len & 0x8000 ? DATA_NOT_NULL : 0, + len & 0x7fff); + + dict_index_add_col(ind, table, + dict_table_get_nth_col(table, i), + 0); + } + dict_table_add_system_columns(table, table->heap); + if (n_uniq != n) { + /* Identify DB_TRX_ID and DB_ROLL_PTR in the index. */ + ut_a(DATA_TRX_ID_LEN + == dict_index_get_nth_col(ind, DATA_TRX_ID - 1 + + n_uniq)->len); + ut_a(DATA_ROLL_PTR_LEN + == dict_index_get_nth_col(ind, DATA_ROLL_PTR - 1 + + n_uniq)->len); + ind->fields[DATA_TRX_ID - 1 + n_uniq].col + = &table->cols[n + DATA_TRX_ID]; + ind->fields[DATA_ROLL_PTR - 1 + n_uniq].col + = &table->cols[n + DATA_ROLL_PTR]; + } + } + /* avoid ut_ad(index->cached) in dict_index_get_n_unique_in_tree */ + ind->cached = TRUE; + *index = ind; + return(ptr); +} diff --git a/storage/xtradb/mtr/mtr0mtr.c b/storage/xtradb/mtr/mtr0mtr.c new file mode 100644 index 00000000000..703f9b18eed --- /dev/null +++ b/storage/xtradb/mtr/mtr0mtr.c @@ -0,0 +1,386 @@ +/***************************************************************************** + +Copyright (c) 1995, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/****************************************************** +Mini-transaction buffer + +Created 11/26/1995 Heikki Tuuri +*******************************************************/ + +#include "mtr0mtr.h" + +#ifdef UNIV_NONINL +#include "mtr0mtr.ic" +#endif + +#include "buf0buf.h" +#include "page0types.h" +#include "mtr0log.h" +#include "log0log.h" + +/********************************************************************* +Releases the item in the slot given. */ +UNIV_INLINE +void +mtr_memo_slot_release( +/*==================*/ + mtr_t* mtr, /* in: mtr */ + mtr_memo_slot_t* slot) /* in: memo slot */ +{ + void* object; + ulint type; + + ut_ad(mtr && slot); + + object = slot->object; + type = slot->type; + + if (UNIV_LIKELY(object != NULL)) { + if (type <= MTR_MEMO_BUF_FIX) { + buf_page_release((buf_block_t*)object, type, mtr); + } else if (type == MTR_MEMO_S_LOCK) { + rw_lock_s_unlock((rw_lock_t*)object); +#ifdef UNIV_DEBUG + } else if (type != MTR_MEMO_X_LOCK) { + ut_ad(type == MTR_MEMO_MODIFY); + ut_ad(mtr_memo_contains(mtr, object, + MTR_MEMO_PAGE_X_FIX)); +#endif /* UNIV_DEBUG */ + } else { + rw_lock_x_unlock((rw_lock_t*)object); + } + } + + slot->object = NULL; +} + +/************************************************************** +Releases the mlocks and other objects stored in an mtr memo. They are released +in the order opposite to which they were pushed to the memo. NOTE! It is +essential that the x-rw-lock on a modified buffer page is not released before +buf_page_note_modification is called for that page! Otherwise, some thread +might race to modify it, and the flush list sort order on lsn would be +destroyed. */ +UNIV_INLINE +void +mtr_memo_pop_all( +/*=============*/ + mtr_t* mtr) /* in: mtr */ +{ + mtr_memo_slot_t* slot; + dyn_array_t* memo; + ulint offset; + + ut_ad(mtr); + ut_ad(mtr->magic_n == MTR_MAGIC_N); + ut_ad(mtr->state == MTR_COMMITTING); /* Currently only used in + commit */ + memo = &(mtr->memo); + + offset = dyn_array_get_data_size(memo); + + while (offset > 0) { + offset -= sizeof(mtr_memo_slot_t); + slot = dyn_array_get_element(memo, offset); + + mtr_memo_slot_release(mtr, slot); + } +} + +UNIV_INLINE +void +mtr_memo_note_modification_all( +/*===========================*/ + mtr_t* mtr) /* in: mtr */ +{ + mtr_memo_slot_t* slot; + dyn_array_t* memo; + ulint offset; + + ut_ad(mtr); + ut_ad(mtr->magic_n == MTR_MAGIC_N); + ut_ad(mtr->state == MTR_COMMITTING); /* Currently only used in + commit */ + ut_ad(mtr->modifications); + + memo = &(mtr->memo); + + offset = dyn_array_get_data_size(memo); + + while (offset > 0) { + offset -= sizeof(mtr_memo_slot_t); + slot = dyn_array_get_element(memo, offset); + + if (UNIV_LIKELY(slot->object != NULL) && + slot->type == MTR_MEMO_PAGE_X_FIX) { + buf_flush_note_modification( + (buf_block_t*)slot->object, mtr); + } + } +} + +/**************************************************************** +Writes the contents of a mini-transaction log, if any, to the database log. */ +static +void +mtr_log_reserve_and_write( +/*======================*/ + mtr_t* mtr) /* in: mtr */ +{ + dyn_array_t* mlog; + dyn_block_t* block; + ulint data_size; + ibool success; + byte* first_data; + + ut_ad(mtr); + + mlog = &(mtr->log); + + first_data = dyn_block_get_data(mlog); + + if (mtr->n_log_recs > 1) { + mlog_catenate_ulint(mtr, MLOG_MULTI_REC_END, MLOG_1BYTE); + } else { + *first_data = (byte)((ulint)*first_data + | MLOG_SINGLE_REC_FLAG); + } + + if (mlog->heap == NULL) { + mtr->end_lsn = log_reserve_and_write_fast( + first_data, dyn_block_get_used(mlog), + &(mtr->start_lsn), &success); + if (success) { + + return; + } + } + + data_size = dyn_array_get_data_size(mlog); + + /* Open the database log for log_write_low */ + mtr->start_lsn = log_reserve_and_open(data_size); + + if (mtr->log_mode == MTR_LOG_ALL) { + + block = mlog; + + while (block != NULL) { + log_write_low(dyn_block_get_data(block), + dyn_block_get_used(block)); + block = dyn_array_get_next_block(mlog, block); + } + } else { + ut_ad(mtr->log_mode == MTR_LOG_NONE); + /* Do nothing */ + } + + mtr->end_lsn = log_close(); +} + +/******************************************************************* +Commits a mini-transaction. */ +UNIV_INTERN +void +mtr_commit( +/*=======*/ + mtr_t* mtr) /* in: mini-transaction */ +{ + ibool write_log; + + ut_ad(mtr); + ut_ad(mtr->magic_n == MTR_MAGIC_N); + ut_ad(mtr->state == MTR_ACTIVE); + ut_d(mtr->state = MTR_COMMITTING); + + write_log = mtr->modifications && mtr->n_log_recs; + + if (write_log) { + mtr_log_reserve_and_write(mtr); + + mtr_memo_note_modification_all(mtr); + } + + /* We first update the modification info to buffer pages, and only + after that release the log mutex: this guarantees that when the log + mutex is free, all buffer pages contain an up-to-date info of their + modifications. This fact is used in making a checkpoint when we look + at the oldest modification of any page in the buffer pool. It is also + required when we insert modified buffer pages in to the flush list + which must be sorted on oldest_modification. */ + + if (write_log) { + log_release(); + } + + /* All unlocking has been moved here, after log_sys mutex release. */ + mtr_memo_pop_all(mtr); + + ut_d(mtr->state = MTR_COMMITTED); + dyn_array_free(&(mtr->memo)); + dyn_array_free(&(mtr->log)); +} + +/************************************************************** +Releases the latches stored in an mtr memo down to a savepoint. +NOTE! The mtr must not have made changes to buffer pages after the +savepoint, as these can be handled only by mtr_commit. */ +UNIV_INTERN +void +mtr_rollback_to_savepoint( +/*======================*/ + mtr_t* mtr, /* in: mtr */ + ulint savepoint) /* in: savepoint */ +{ + mtr_memo_slot_t* slot; + dyn_array_t* memo; + ulint offset; + + ut_ad(mtr); + ut_ad(mtr->magic_n == MTR_MAGIC_N); + ut_ad(mtr->state == MTR_ACTIVE); + + memo = &(mtr->memo); + + offset = dyn_array_get_data_size(memo); + ut_ad(offset >= savepoint); + + while (offset > savepoint) { + offset -= sizeof(mtr_memo_slot_t); + + slot = dyn_array_get_element(memo, offset); + + ut_ad(slot->type != MTR_MEMO_MODIFY); + mtr_memo_slot_release(mtr, slot); + } +} + +/******************************************************* +Releases an object in the memo stack. */ +UNIV_INTERN +void +mtr_memo_release( +/*=============*/ + mtr_t* mtr, /* in: mtr */ + void* object, /* in: object */ + ulint type) /* in: object type: MTR_MEMO_S_LOCK, ... */ +{ + mtr_memo_slot_t* slot; + dyn_array_t* memo; + ulint offset; + + ut_ad(mtr); + ut_ad(mtr->magic_n == MTR_MAGIC_N); + ut_ad(mtr->state == MTR_ACTIVE); + + memo = &(mtr->memo); + + offset = dyn_array_get_data_size(memo); + + while (offset > 0) { + offset -= sizeof(mtr_memo_slot_t); + + slot = dyn_array_get_element(memo, offset); + + if ((object == slot->object) && (type == slot->type)) { + if (mtr->modifications && + UNIV_LIKELY(slot->object != NULL) && + slot->type == MTR_MEMO_PAGE_X_FIX) { + buf_flush_note_modification( + (buf_block_t*)slot->object, mtr); + } + + mtr_memo_slot_release(mtr, slot); + + break; + } + } +} + +/************************************************************ +Reads 1 - 4 bytes from a file page buffered in the buffer pool. */ +UNIV_INTERN +ulint +mtr_read_ulint( +/*===========*/ + /* out: value read */ + const byte* ptr, /* in: pointer from where to read */ + ulint type, /* in: MLOG_1BYTE, MLOG_2BYTES, MLOG_4BYTES */ + mtr_t* mtr __attribute__((unused))) + /* in: mini-transaction handle */ +{ + ut_ad(mtr->state == MTR_ACTIVE); + ut_ad(mtr_memo_contains_page(mtr, ptr, MTR_MEMO_PAGE_S_FIX) + || mtr_memo_contains_page(mtr, ptr, MTR_MEMO_PAGE_X_FIX)); + if (type == MLOG_1BYTE) { + return(mach_read_from_1(ptr)); + } else if (type == MLOG_2BYTES) { + return(mach_read_from_2(ptr)); + } else { + ut_ad(type == MLOG_4BYTES); + return(mach_read_from_4(ptr)); + } +} + +/************************************************************ +Reads 8 bytes from a file page buffered in the buffer pool. */ +UNIV_INTERN +dulint +mtr_read_dulint( +/*============*/ + /* out: value read */ + const byte* ptr, /* in: pointer from where to read */ + mtr_t* mtr __attribute__((unused))) + /* in: mini-transaction handle */ +{ + ut_ad(mtr->state == MTR_ACTIVE); + ut_ad(mtr_memo_contains_page(mtr, ptr, MTR_MEMO_PAGE_S_FIX) + || mtr_memo_contains_page(mtr, ptr, MTR_MEMO_PAGE_X_FIX)); + return(mach_read_from_8(ptr)); +} + +#ifdef UNIV_DEBUG +/************************************************************** +Checks if memo contains the given page. */ +UNIV_INTERN +ibool +mtr_memo_contains_page( +/*===================*/ + /* out: TRUE if contains */ + mtr_t* mtr, /* in: mtr */ + const byte* ptr, /* in: pointer to buffer frame */ + ulint type) /* in: type of object */ +{ + return(mtr_memo_contains(mtr, buf_block_align(ptr), type)); +} + +/************************************************************* +Prints info of an mtr handle. */ +UNIV_INTERN +void +mtr_print( +/*======*/ + mtr_t* mtr) /* in: mtr */ +{ + fprintf(stderr, + "Mini-transaction handle: memo size %lu bytes" + " log size %lu bytes\n", + (ulong) dyn_array_get_data_size(&(mtr->memo)), + (ulong) dyn_array_get_data_size(&(mtr->log))); +} +#endif /* UNIV_DEBUG */ diff --git a/storage/xtradb/os/os0file.c b/storage/xtradb/os/os0file.c new file mode 100644 index 00000000000..efef608b88f --- /dev/null +++ b/storage/xtradb/os/os0file.c @@ -0,0 +1,4315 @@ +/***************************************************************************** + +Copyright (c) 1995, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/****************************************************** +The interface to the operating system file i/o primitives + +Created 10/21/1995 Heikki Tuuri +*******************************************************/ + +#include "os0file.h" +#include "os0sync.h" +#include "os0thread.h" +#include "ut0mem.h" +#include "srv0srv.h" +#include "srv0start.h" +#include "fil0fil.h" +#include "buf0buf.h" + +#if defined(UNIV_HOTBACKUP) && defined(__WIN__) +/* Add includes for the _stat() call to compile on Windows */ +#include <sys/types.h> +#include <sys/stat.h> +#include <errno.h> +#endif /* UNIV_HOTBACKUP */ + +/* This specifies the file permissions InnoDB uses when it creates files in +Unix; the value of os_innodb_umask is initialized in ha_innodb.cc to +my_umask */ + +#ifndef __WIN__ +UNIV_INTERN ulint os_innodb_umask + = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP; +#else +UNIV_INTERN ulint os_innodb_umask = 0; +#endif + +#ifdef UNIV_DO_FLUSH +/* If the following is set to TRUE, we do not call os_file_flush in every +os_file_write. We can set this TRUE when the doublewrite buffer is used. */ +UNIV_INTERN ibool os_do_not_call_flush_at_each_write = FALSE; +#else +/* We do not call os_file_flush in every os_file_write. */ +#endif /* UNIV_DO_FLUSH */ + +/* We use these mutexes to protect lseek + file i/o operation, if the +OS does not provide an atomic pread or pwrite, or similar */ +#define OS_FILE_N_SEEK_MUTEXES 16 +UNIV_INTERN os_mutex_t os_file_seek_mutexes[OS_FILE_N_SEEK_MUTEXES]; + +/* In simulated aio, merge at most this many consecutive i/os */ +#define OS_AIO_MERGE_N_CONSECUTIVE 64 + +/* If this flag is TRUE, then we will use the native aio of the +OS (provided we compiled Innobase with it in), otherwise we will +use simulated aio we build below with threads */ + +UNIV_INTERN ibool os_aio_use_native_aio = FALSE; + +UNIV_INTERN ibool os_aio_print_debug = FALSE; + +/* State for the state of an IO request in simulated AIO. + Protocol for simulated aio: + client requests IO: find slot with reserved = FALSE. Add entry with + status = OS_AIO_NOT_ISSUED. + IO thread wakes: find adjacent slots with reserved = TRUE and status = + OS_AIO_NOT_ISSUED. Change status for slots to + OS_AIO_ISSUED. + IO operation completes: set status for slots to OS_AIO_DONE. set status + for the first slot to OS_AIO_CLAIMED and return + result for that slot. + When there are multiple read and write threads, they all compete to execute + the requests in the array (os_aio_array_t). This avoids the need to load + balance requests at the time the request is made at the cost of waking all + threads when a request is available. +*/ +typedef enum { + OS_AIO_NOT_ISSUED, /* Available to be processed by an IO thread. */ + OS_AIO_ISSUED, /* Being processed by an IO thread. */ + OS_AIO_DONE, /* Request processed. */ + OS_AIO_CLAIMED /* Result being returned to client. */ +} os_aio_status; + +/* The aio array slot structure */ +typedef struct os_aio_slot_struct os_aio_slot_t; + +struct os_aio_slot_struct{ + ibool is_read; /* TRUE if a read operation */ + ulint pos; /* index of the slot in the aio + array */ + ibool reserved; /* TRUE if this slot is reserved */ + os_aio_status status; /* Status for current request. Valid when reserved + is TRUE. Used only in simulated aio. */ + time_t reservation_time;/* time when reserved */ + ulint len; /* length of the block to read or + write */ + byte* buf; /* buffer used in i/o */ + ulint type; /* OS_FILE_READ or OS_FILE_WRITE */ + ulint offset; /* 32 low bits of file offset in + bytes */ + ulint offset_high; /* 32 high bits of file offset */ + os_file_t file; /* file where to read or write */ + const char* name; /* file name or path */ +// ibool io_already_done;/* used only in simulated aio: +// TRUE if the physical i/o already +// made and only the slot message +// needs to be passed to the caller +// of os_aio_simulated_handle */ + fil_node_t* message1; /* message which is given by the */ + void* message2; /* the requester of an aio operation + and which can be used to identify + which pending aio operation was + completed */ +#ifdef WIN_ASYNC_IO + os_event_t event; /* event object we need in the + OVERLAPPED struct */ + OVERLAPPED control; /* Windows control block for the + aio request */ +#endif +}; + +/* The aio array structure */ +typedef struct os_aio_array_struct os_aio_array_t; + +struct os_aio_array_struct{ + os_mutex_t mutex; /* the mutex protecting the aio array */ + os_event_t not_full; /* The event which is set to the signaled + state when there is space in the aio + outside the ibuf segment */ + os_event_t is_empty; /* The event which is set to the signaled + state when there are no pending i/os + in this array */ + ulint n_slots; /* Total number of slots in the aio array. + This must be divisible by n_threads. */ + ulint n_segments;/* Number of segments in the aio array of + pending aio requests. A thread can wait + separately for any one of the segments. */ + ulint n_reserved;/* Number of reserved slots in the + aio array outside the ibuf segment */ + os_aio_slot_t* slots; /* Pointer to the slots in the array */ +#ifdef __WIN__ + os_native_event_t* native_events; + /* Pointer to an array of OS native event + handles where we copied the handles from + slots, in the same order. This can be used + in WaitForMultipleObjects; used only in + Windows */ +#endif +}; + +/* Array of events used in simulated aio */ +static os_event_t* os_aio_segment_wait_events = NULL; + +/* Number for the first global segment for reading. */ +const ulint os_aio_first_read_segment = 2; + +/* Number for the first global segment for writing. Set to +2 + os_aio_read_write_threads. */ +ulint os_aio_first_write_segment = 0; + +/* The aio arrays for non-ibuf i/o and ibuf i/o, as well as sync aio. These +are NULL when the module has not yet been initialized. */ +static os_aio_array_t* os_aio_read_array = NULL; +static os_aio_array_t* os_aio_write_array = NULL; +static os_aio_array_t* os_aio_ibuf_array = NULL; +static os_aio_array_t* os_aio_log_array = NULL; +static os_aio_array_t* os_aio_sync_array = NULL; + +/* Per thread buffer used for merged IO requests. Used by +os_aio_simulated_handle so that a buffer doesn't have to be allocated +for each request. */ +static char* os_aio_thread_buffer[SRV_MAX_N_IO_THREADS]; +static ulint os_aio_thread_buffer_size[SRV_MAX_N_IO_THREADS]; + +static ulint os_aio_n_segments = ULINT_UNDEFINED; + +/* If the following is TRUE, read i/o handler threads try to +wait until a batch of new read requests have been posted */ +static volatile ibool os_aio_recommend_sleep_for_read_threads = FALSE; + +UNIV_INTERN ulint os_n_file_reads = 0; +UNIV_INTERN ulint os_bytes_read_since_printout = 0; +UNIV_INTERN ulint os_n_file_writes = 0; +UNIV_INTERN ulint os_n_fsyncs = 0; +UNIV_INTERN ulint os_n_file_reads_old = 0; +UNIV_INTERN ulint os_n_file_writes_old = 0; +UNIV_INTERN ulint os_n_fsyncs_old = 0; +UNIV_INTERN time_t os_last_printout; + +UNIV_INTERN ibool os_has_said_disk_full = FALSE; + +/* The mutex protecting the following counts of pending I/O operations */ +static os_mutex_t os_file_count_mutex; +UNIV_INTERN ulint os_file_n_pending_preads = 0; +UNIV_INTERN ulint os_file_n_pending_pwrites = 0; +UNIV_INTERN ulint os_n_pending_writes = 0; +UNIV_INTERN ulint os_n_pending_reads = 0; + +/*************************************************************************** +Gets the operating system version. Currently works only on Windows. */ +UNIV_INTERN +ulint +os_get_os_version(void) +/*===================*/ + /* out: OS_WIN95, OS_WIN31, OS_WINNT, OS_WIN2000 */ +{ +#ifdef __WIN__ + OSVERSIONINFO os_info; + + os_info.dwOSVersionInfoSize = sizeof(OSVERSIONINFO); + + ut_a(GetVersionEx(&os_info)); + + if (os_info.dwPlatformId == VER_PLATFORM_WIN32s) { + return(OS_WIN31); + } else if (os_info.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS) { + return(OS_WIN95); + } else if (os_info.dwPlatformId == VER_PLATFORM_WIN32_NT) { + if (os_info.dwMajorVersion <= 4) { + return(OS_WINNT); + } else { + return(OS_WIN2000); + } + } else { + ut_error; + return(0); + } +#else + ut_error; + + return(0); +#endif +} + +/*************************************************************************** +Retrieves the last error number if an error occurs in a file io function. +The number should be retrieved before any other OS calls (because they may +overwrite the error number). If the number is not known to this program, +the OS error number + 100 is returned. */ +UNIV_INTERN +ulint +os_file_get_last_error( +/*===================*/ + /* out: error number, or OS error + number + 100 */ + ibool report_all_errors) /* in: TRUE if we want an error message + printed of all errors */ +{ + ulint err; + +#ifdef __WIN__ + + err = (ulint) GetLastError(); + + if (report_all_errors + || (err != ERROR_DISK_FULL && err != ERROR_FILE_EXISTS)) { + + ut_print_timestamp(stderr); + fprintf(stderr, + " InnoDB: Operating system error number %lu" + " in a file operation.\n", (ulong) err); + + if (err == ERROR_PATH_NOT_FOUND) { + fprintf(stderr, + "InnoDB: The error means the system" + " cannot find the path specified.\n"); + + if (srv_is_being_started) { + fprintf(stderr, + "InnoDB: If you are installing InnoDB," + " remember that you must create\n" + "InnoDB: directories yourself, InnoDB" + " does not create them.\n"); + } + } else if (err == ERROR_ACCESS_DENIED) { + fprintf(stderr, + "InnoDB: The error means mysqld does not have" + " the access rights to\n" + "InnoDB: the directory. It may also be" + " you have created a subdirectory\n" + "InnoDB: of the same name as a data file.\n"); + } else if (err == ERROR_SHARING_VIOLATION + || err == ERROR_LOCK_VIOLATION) { + fprintf(stderr, + "InnoDB: The error means that another program" + " is using InnoDB's files.\n" + "InnoDB: This might be a backup or antivirus" + " software or another instance\n" + "InnoDB: of MySQL." + " Please close it to get rid of this error.\n"); + } else { + fprintf(stderr, + "InnoDB: Some operating system error numbers" + " are described at\n" + "InnoDB: " + "http://dev.mysql.com/doc/refman/5.1/en/" + "operating-system-error-codes.html\n"); + } + } + + fflush(stderr); + + if (err == ERROR_FILE_NOT_FOUND) { + return(OS_FILE_NOT_FOUND); + } else if (err == ERROR_DISK_FULL) { + return(OS_FILE_DISK_FULL); + } else if (err == ERROR_FILE_EXISTS) { + return(OS_FILE_ALREADY_EXISTS); + } else if (err == ERROR_SHARING_VIOLATION + || err == ERROR_LOCK_VIOLATION) { + return(OS_FILE_SHARING_VIOLATION); + } else { + return(100 + err); + } +#else + err = (ulint) errno; + + if (report_all_errors + || (err != ENOSPC && err != EEXIST)) { + + ut_print_timestamp(stderr); + fprintf(stderr, + " InnoDB: Operating system error number %lu" + " in a file operation.\n", (ulong) err); + + if (err == ENOENT) { + fprintf(stderr, + "InnoDB: The error means the system" + " cannot find the path specified.\n"); + + if (srv_is_being_started) { + fprintf(stderr, + "InnoDB: If you are installing InnoDB," + " remember that you must create\n" + "InnoDB: directories yourself, InnoDB" + " does not create them.\n"); + } + } else if (err == EACCES) { + fprintf(stderr, + "InnoDB: The error means mysqld does not have" + " the access rights to\n" + "InnoDB: the directory.\n"); + } else { + if (strerror((int)err) != NULL) { + fprintf(stderr, + "InnoDB: Error number %lu" + " means '%s'.\n", + err, strerror((int)err)); + } + + fprintf(stderr, + "InnoDB: Some operating system" + " error numbers are described at\n" + "InnoDB: " + "http://dev.mysql.com/doc/refman/5.1/en/" + "operating-system-error-codes.html\n"); + } + } + + fflush(stderr); + + if (err == ENOSPC) { + return(OS_FILE_DISK_FULL); + } else if (err == ENOENT) { + return(OS_FILE_NOT_FOUND); + } else if (err == EEXIST) { + return(OS_FILE_ALREADY_EXISTS); + } else if (err == EXDEV || err == ENOTDIR || err == EISDIR) { + return(OS_FILE_PATH_ERROR); + } else { + return(100 + err); + } +#endif +} + +/******************************************************************** +Does error handling when a file operation fails. +Conditionally exits (calling exit(3)) based on should_exit value and the +error type */ +static +ibool +os_file_handle_error_cond_exit( +/*===========================*/ + /* out: TRUE if we should retry the + operation */ + const char* name, /* in: name of a file or NULL */ + const char* operation, /* in: operation */ + ibool should_exit) /* in: call exit(3) if unknown error + and this parameter is TRUE */ +{ + ulint err; + + err = os_file_get_last_error(FALSE); + + if (err == OS_FILE_DISK_FULL) { + /* We only print a warning about disk full once */ + + if (os_has_said_disk_full) { + + return(FALSE); + } + + if (name) { + ut_print_timestamp(stderr); + fprintf(stderr, + " InnoDB: Encountered a problem with" + " file %s\n", name); + } + + ut_print_timestamp(stderr); + fprintf(stderr, + " InnoDB: Disk is full. Try to clean the disk" + " to free space.\n"); + + os_has_said_disk_full = TRUE; + + fflush(stderr); + + return(FALSE); + } else if (err == OS_FILE_AIO_RESOURCES_RESERVED) { + + return(TRUE); + } else if (err == OS_FILE_ALREADY_EXISTS + || err == OS_FILE_PATH_ERROR) { + + return(FALSE); + } else if (err == OS_FILE_SHARING_VIOLATION) { + + os_thread_sleep(10000000); /* 10 sec */ + return(TRUE); + } else { + if (name) { + fprintf(stderr, "InnoDB: File name %s\n", name); + } + + fprintf(stderr, "InnoDB: File operation call: '%s'.\n", + operation); + + if (should_exit) { + fprintf(stderr, "InnoDB: Cannot continue operation.\n"); + + fflush(stderr); + + exit(1); + } + } + + return(FALSE); +} + +/******************************************************************** +Does error handling when a file operation fails. */ +static +ibool +os_file_handle_error( +/*=================*/ + /* out: TRUE if we should retry the + operation */ + const char* name, /* in: name of a file or NULL */ + const char* operation)/* in: operation */ +{ + /* exit in case of unknown error */ + return(os_file_handle_error_cond_exit(name, operation, TRUE)); +} + +/******************************************************************** +Does error handling when a file operation fails. */ +static +ibool +os_file_handle_error_no_exit( +/*=========================*/ + /* out: TRUE if we should retry the + operation */ + const char* name, /* in: name of a file or NULL */ + const char* operation)/* in: operation */ +{ + /* don't exit in case of unknown error */ + return(os_file_handle_error_cond_exit(name, operation, FALSE)); +} + +#undef USE_FILE_LOCK +#define USE_FILE_LOCK +#if defined(UNIV_HOTBACKUP) || defined(__WIN__) || defined(__NETWARE__) +/* InnoDB Hot Backup does not lock the data files. + * On Windows, mandatory locking is used. + */ +# undef USE_FILE_LOCK +#endif +#ifdef USE_FILE_LOCK +/******************************************************************** +Obtain an exclusive lock on a file. */ +static +int +os_file_lock( +/*=========*/ + /* out: 0 on success */ + int fd, /* in: file descriptor */ + const char* name) /* in: file name */ +{ + struct flock lk; + lk.l_type = F_WRLCK; + lk.l_whence = SEEK_SET; + lk.l_start = lk.l_len = 0; + if (fcntl(fd, F_SETLK, &lk) == -1) { + fprintf(stderr, + "InnoDB: Unable to lock %s, error: %d\n", name, errno); + + if (errno == EAGAIN || errno == EACCES) { + fprintf(stderr, + "InnoDB: Check that you do not already have" + " another mysqld process\n" + "InnoDB: using the same InnoDB data" + " or log files.\n"); + } + + return(-1); + } + + return(0); +} +#endif /* USE_FILE_LOCK */ + +/******************************************************************** +Creates the seek mutexes used in positioned reads and writes. */ +UNIV_INTERN +void +os_io_init_simple(void) +/*===================*/ +{ + ulint i; + + os_file_count_mutex = os_mutex_create(NULL); + + for (i = 0; i < OS_FILE_N_SEEK_MUTEXES; i++) { + os_file_seek_mutexes[i] = os_mutex_create(NULL); + } +} + +/*************************************************************************** +Creates a temporary file. This function is like tmpfile(3), but +the temporary file is created in the MySQL temporary directory. +On Netware, this function is like tmpfile(3), because the C run-time +library of Netware does not expose the delete-on-close flag. */ +UNIV_INTERN +FILE* +os_file_create_tmpfile(void) +/*========================*/ + /* out: temporary file handle, or NULL on error */ +{ +#ifdef UNIV_HOTBACKUP + ut_error; + + return(NULL); +#else +# ifdef __NETWARE__ + FILE* file = tmpfile(); +# else /* __NETWARE__ */ + FILE* file = NULL; + int fd = innobase_mysql_tmpfile(); + + if (fd >= 0) { + file = fdopen(fd, "w+b"); + } +# endif /* __NETWARE__ */ + + if (!file) { + ut_print_timestamp(stderr); + fprintf(stderr, + " InnoDB: Error: unable to create temporary file;" + " errno: %d\n", errno); +# ifndef __NETWARE__ + if (fd >= 0) { + close(fd); + } +# endif /* !__NETWARE__ */ + } + + return(file); +#endif /* UNIV_HOTBACKUP */ +} + +/*************************************************************************** +The os_file_opendir() function opens a directory stream corresponding to the +directory named by the dirname argument. The directory stream is positioned +at the first entry. In both Unix and Windows we automatically skip the '.' +and '..' items at the start of the directory listing. */ +UNIV_INTERN +os_file_dir_t +os_file_opendir( +/*============*/ + /* out: directory stream, NULL if + error */ + const char* dirname, /* in: directory name; it must not + contain a trailing '\' or '/' */ + ibool error_is_fatal) /* in: TRUE if we should treat an + error as a fatal error; if we try to + open symlinks then we do not wish a + fatal error if it happens not to be + a directory */ +{ + os_file_dir_t dir; +#ifdef __WIN__ + LPWIN32_FIND_DATA lpFindFileData; + char path[OS_FILE_MAX_PATH + 3]; + + ut_a(strlen(dirname) < OS_FILE_MAX_PATH); + + strcpy(path, dirname); + strcpy(path + strlen(path), "\\*"); + + /* Note that in Windows opening the 'directory stream' also retrieves + the first entry in the directory. Since it is '.', that is no problem, + as we will skip over the '.' and '..' entries anyway. */ + + lpFindFileData = ut_malloc(sizeof(WIN32_FIND_DATA)); + + dir = FindFirstFile((LPCTSTR) path, lpFindFileData); + + ut_free(lpFindFileData); + + if (dir == INVALID_HANDLE_VALUE) { + + if (error_is_fatal) { + os_file_handle_error(dirname, "opendir"); + } + + return(NULL); + } + + return(dir); +#else + dir = opendir(dirname); + + if (dir == NULL && error_is_fatal) { + os_file_handle_error(dirname, "opendir"); + } + + return(dir); +#endif +} + +/*************************************************************************** +Closes a directory stream. */ +UNIV_INTERN +int +os_file_closedir( +/*=============*/ + /* out: 0 if success, -1 if failure */ + os_file_dir_t dir) /* in: directory stream */ +{ +#ifdef __WIN__ + BOOL ret; + + ret = FindClose(dir); + + if (!ret) { + os_file_handle_error_no_exit(NULL, "closedir"); + + return(-1); + } + + return(0); +#else + int ret; + + ret = closedir(dir); + + if (ret) { + os_file_handle_error_no_exit(NULL, "closedir"); + } + + return(ret); +#endif +} + +/*************************************************************************** +This function returns information of the next file in the directory. We jump +over the '.' and '..' entries in the directory. */ +UNIV_INTERN +int +os_file_readdir_next_file( +/*======================*/ + /* out: 0 if ok, -1 if error, 1 if at the end + of the directory */ + const char* dirname,/* in: directory name or path */ + os_file_dir_t dir, /* in: directory stream */ + os_file_stat_t* info) /* in/out: buffer where the info is returned */ +{ +#ifdef __WIN__ + LPWIN32_FIND_DATA lpFindFileData; + BOOL ret; + + lpFindFileData = ut_malloc(sizeof(WIN32_FIND_DATA)); +next_file: + ret = FindNextFile(dir, lpFindFileData); + + if (ret) { + ut_a(strlen((char *) lpFindFileData->cFileName) + < OS_FILE_MAX_PATH); + + if (strcmp((char *) lpFindFileData->cFileName, ".") == 0 + || strcmp((char *) lpFindFileData->cFileName, "..") == 0) { + + goto next_file; + } + + strcpy(info->name, (char *) lpFindFileData->cFileName); + + info->size = (ib_int64_t)(lpFindFileData->nFileSizeLow) + + (((ib_int64_t)(lpFindFileData->nFileSizeHigh)) + << 32); + + if (lpFindFileData->dwFileAttributes + & FILE_ATTRIBUTE_REPARSE_POINT) { + /* TODO: test Windows symlinks */ + /* TODO: MySQL has apparently its own symlink + implementation in Windows, dbname.sym can + redirect a database directory: + http://dev.mysql.com/doc/refman/5.1/en/ + windows-symbolic-links.html */ + info->type = OS_FILE_TYPE_LINK; + } else if (lpFindFileData->dwFileAttributes + & FILE_ATTRIBUTE_DIRECTORY) { + info->type = OS_FILE_TYPE_DIR; + } else { + /* It is probably safest to assume that all other + file types are normal. Better to check them rather + than blindly skip them. */ + + info->type = OS_FILE_TYPE_FILE; + } + } + + ut_free(lpFindFileData); + + if (ret) { + return(0); + } else if (GetLastError() == ERROR_NO_MORE_FILES) { + + return(1); + } else { + os_file_handle_error_no_exit(dirname, + "readdir_next_file"); + return(-1); + } +#else + struct dirent* ent; + char* full_path; + int ret; + struct stat statinfo; +#ifdef HAVE_READDIR_R + char dirent_buf[sizeof(struct dirent) + + _POSIX_PATH_MAX + 100]; + /* In /mysys/my_lib.c, _POSIX_PATH_MAX + 1 is used as + the max file name len; but in most standards, the + length is NAME_MAX; we add 100 to be even safer */ +#endif + +next_file: + +#ifdef HAVE_READDIR_R + ret = readdir_r(dir, (struct dirent*)dirent_buf, &ent); + + if (ret != 0) { + fprintf(stderr, + "InnoDB: cannot read directory %s, error %lu\n", + dirname, (ulong)ret); + + return(-1); + } + + if (ent == NULL) { + /* End of directory */ + + return(1); + } + + ut_a(strlen(ent->d_name) < _POSIX_PATH_MAX + 100 - 1); +#else + ent = readdir(dir); + + if (ent == NULL) { + + return(1); + } +#endif + ut_a(strlen(ent->d_name) < OS_FILE_MAX_PATH); + + if (strcmp(ent->d_name, ".") == 0 || strcmp(ent->d_name, "..") == 0) { + + goto next_file; + } + + strcpy(info->name, ent->d_name); + + full_path = ut_malloc(strlen(dirname) + strlen(ent->d_name) + 10); + + sprintf(full_path, "%s/%s", dirname, ent->d_name); + + ret = stat(full_path, &statinfo); + + if (ret) { + os_file_handle_error_no_exit(full_path, "stat"); + + ut_free(full_path); + + return(-1); + } + + info->size = (ib_int64_t)statinfo.st_size; + + if (S_ISDIR(statinfo.st_mode)) { + info->type = OS_FILE_TYPE_DIR; + } else if (S_ISLNK(statinfo.st_mode)) { + info->type = OS_FILE_TYPE_LINK; + } else if (S_ISREG(statinfo.st_mode)) { + info->type = OS_FILE_TYPE_FILE; + } else { + info->type = OS_FILE_TYPE_UNKNOWN; + } + + ut_free(full_path); + + return(0); +#endif +} + +/********************************************************************* +This function attempts to create a directory named pathname. The new directory +gets default permissions. On Unix the permissions are (0770 & ~umask). If the +directory exists already, nothing is done and the call succeeds, unless the +fail_if_exists arguments is true. */ +UNIV_INTERN +ibool +os_file_create_directory( +/*=====================*/ + /* out: TRUE if call succeeds, + FALSE on error */ + const char* pathname, /* in: directory name as + null-terminated string */ + ibool fail_if_exists) /* in: if TRUE, pre-existing directory + is treated as an error. */ +{ +#ifdef __WIN__ + BOOL rcode; + + rcode = CreateDirectory((LPCTSTR) pathname, NULL); + if (!(rcode != 0 + || (GetLastError() == ERROR_ALREADY_EXISTS + && !fail_if_exists))) { + /* failure */ + os_file_handle_error(pathname, "CreateDirectory"); + + return(FALSE); + } + + return (TRUE); +#else + int rcode; + + rcode = mkdir(pathname, 0770); + + if (!(rcode == 0 || (errno == EEXIST && !fail_if_exists))) { + /* failure */ + os_file_handle_error(pathname, "mkdir"); + + return(FALSE); + } + + return (TRUE); +#endif +} + +/******************************************************************** +A simple function to open or create a file. */ +UNIV_INTERN +os_file_t +os_file_create_simple( +/*==================*/ + /* out, own: handle to the file, not defined + if error, error number can be retrieved with + os_file_get_last_error */ + const char* name, /* in: name of the file or path as a + null-terminated string */ + ulint create_mode,/* in: OS_FILE_OPEN if an existing file is + opened (if does not exist, error), or + OS_FILE_CREATE if a new file is created + (if exists, error), or + OS_FILE_CREATE_PATH if new file + (if exists, error) and subdirectories along + its path are created (if needed)*/ + ulint access_type,/* in: OS_FILE_READ_ONLY or + OS_FILE_READ_WRITE */ + ibool* success)/* out: TRUE if succeed, FALSE if error */ +{ +#ifdef __WIN__ + os_file_t file; + DWORD create_flag; + DWORD access; + DWORD attributes = 0; + ibool retry; + +try_again: + ut_a(name); + + if (create_mode == OS_FILE_OPEN) { + create_flag = OPEN_EXISTING; + } else if (create_mode == OS_FILE_CREATE) { + create_flag = CREATE_NEW; + } else if (create_mode == OS_FILE_CREATE_PATH) { + /* create subdirs along the path if needed */ + *success = os_file_create_subdirs_if_needed(name); + if (!*success) { + ut_error; + } + create_flag = CREATE_NEW; + create_mode = OS_FILE_CREATE; + } else { + create_flag = 0; + ut_error; + } + + if (access_type == OS_FILE_READ_ONLY) { + access = GENERIC_READ; + } else if (access_type == OS_FILE_READ_WRITE) { + access = GENERIC_READ | GENERIC_WRITE; + } else { + access = 0; + ut_error; + } + + file = CreateFile((LPCTSTR) name, + access, + FILE_SHARE_READ | FILE_SHARE_WRITE, + /* file can be read and written also + by other processes */ + NULL, /* default security attributes */ + create_flag, + attributes, + NULL); /* no template file */ + + if (file == INVALID_HANDLE_VALUE) { + *success = FALSE; + + retry = os_file_handle_error(name, + create_mode == OS_FILE_OPEN ? + "open" : "create"); + if (retry) { + goto try_again; + } + } else { + *success = TRUE; + } + + return(file); +#else /* __WIN__ */ + os_file_t file; + int create_flag; + ibool retry; + +try_again: + ut_a(name); + + if (create_mode == OS_FILE_OPEN) { + if (access_type == OS_FILE_READ_ONLY) { + create_flag = O_RDONLY; + } else { + create_flag = O_RDWR; + } + } else if (create_mode == OS_FILE_CREATE) { + create_flag = O_RDWR | O_CREAT | O_EXCL; + } else if (create_mode == OS_FILE_CREATE_PATH) { + /* create subdirs along the path if needed */ + *success = os_file_create_subdirs_if_needed(name); + if (!*success) { + return (-1); + } + create_flag = O_RDWR | O_CREAT | O_EXCL; + create_mode = OS_FILE_CREATE; + } else { + create_flag = 0; + ut_error; + } + + if (create_mode == OS_FILE_CREATE) { + file = open(name, create_flag, S_IRUSR | S_IWUSR + | S_IRGRP | S_IWGRP); + } else { + file = open(name, create_flag); + } + + if (file == -1) { + *success = FALSE; + + retry = os_file_handle_error(name, + create_mode == OS_FILE_OPEN ? + "open" : "create"); + if (retry) { + goto try_again; + } +#ifdef USE_FILE_LOCK + } else if (access_type == OS_FILE_READ_WRITE + && os_file_lock(file, name)) { + *success = FALSE; + close(file); + file = -1; +#endif + } else { + *success = TRUE; + } + + return(file); +#endif /* __WIN__ */ +} + +/******************************************************************** +A simple function to open or create a file. */ +UNIV_INTERN +os_file_t +os_file_create_simple_no_error_handling( +/*====================================*/ + /* out, own: handle to the file, not defined + if error, error number can be retrieved with + os_file_get_last_error */ + const char* name, /* in: name of the file or path as a + null-terminated string */ + ulint create_mode,/* in: OS_FILE_OPEN if an existing file + is opened (if does not exist, error), or + OS_FILE_CREATE if a new file is created + (if exists, error) */ + ulint access_type,/* in: OS_FILE_READ_ONLY, + OS_FILE_READ_WRITE, or + OS_FILE_READ_ALLOW_DELETE; the last option is + used by a backup program reading the file */ + ibool* success)/* out: TRUE if succeed, FALSE if error */ +{ +#ifdef __WIN__ + os_file_t file; + DWORD create_flag; + DWORD access; + DWORD attributes = 0; + DWORD share_mode = FILE_SHARE_READ | FILE_SHARE_WRITE; + + ut_a(name); + + if (create_mode == OS_FILE_OPEN) { + create_flag = OPEN_EXISTING; + } else if (create_mode == OS_FILE_CREATE) { + create_flag = CREATE_NEW; + } else { + create_flag = 0; + ut_error; + } + + if (access_type == OS_FILE_READ_ONLY) { + access = GENERIC_READ; + } else if (access_type == OS_FILE_READ_WRITE) { + access = GENERIC_READ | GENERIC_WRITE; + } else if (access_type == OS_FILE_READ_ALLOW_DELETE) { + access = GENERIC_READ; + share_mode = FILE_SHARE_DELETE | FILE_SHARE_READ + | FILE_SHARE_WRITE; /* A backup program has to give + mysqld the maximum freedom to + do what it likes with the + file */ + } else { + access = 0; + ut_error; + } + + file = CreateFile((LPCTSTR) name, + access, + share_mode, + NULL, /* default security attributes */ + create_flag, + attributes, + NULL); /* no template file */ + + if (file == INVALID_HANDLE_VALUE) { + *success = FALSE; + } else { + *success = TRUE; + } + + return(file); +#else /* __WIN__ */ + os_file_t file; + int create_flag; + + ut_a(name); + + if (create_mode == OS_FILE_OPEN) { + if (access_type == OS_FILE_READ_ONLY) { + create_flag = O_RDONLY; + } else { + create_flag = O_RDWR; + } + } else if (create_mode == OS_FILE_CREATE) { + create_flag = O_RDWR | O_CREAT | O_EXCL; + } else { + create_flag = 0; + ut_error; + } + + if (create_mode == OS_FILE_CREATE) { + file = open(name, create_flag, S_IRUSR | S_IWUSR + | S_IRGRP | S_IWGRP); + } else { + file = open(name, create_flag); + } + + if (file == -1) { + *success = FALSE; +#ifdef USE_FILE_LOCK + } else if (access_type == OS_FILE_READ_WRITE + && os_file_lock(file, name)) { + *success = FALSE; + close(file); + file = -1; +#endif + } else { + *success = TRUE; + } + + return(file); +#endif /* __WIN__ */ +} + +/******************************************************************** +Tries to disable OS caching on an opened file descriptor. */ +UNIV_INTERN +void +os_file_set_nocache( +/*================*/ + int fd, /* in: file descriptor to alter */ + const char* file_name, /* in: file name, used in the + diagnostic message */ + const char* operation_name) /* in: "open" or "create"; used in the + diagnostic message */ +{ + /* some versions of Solaris may not have DIRECTIO_ON */ +#if defined(UNIV_SOLARIS) && defined(DIRECTIO_ON) + if (directio(fd, DIRECTIO_ON) == -1) { + int errno_save; + errno_save = (int)errno; + ut_print_timestamp(stderr); + fprintf(stderr, + " InnoDB: Failed to set DIRECTIO_ON " + "on file %s: %s: %s, continuing anyway\n", + file_name, operation_name, strerror(errno_save)); + } +#elif defined(O_DIRECT) + if (fcntl(fd, F_SETFL, O_DIRECT) == -1) { + int errno_save; + errno_save = (int)errno; + ut_print_timestamp(stderr); + fprintf(stderr, + " InnoDB: Failed to set O_DIRECT " + "on file %s: %s: %s, continuing anyway\n", + file_name, operation_name, strerror(errno_save)); + if (errno_save == EINVAL) { + ut_print_timestamp(stderr); + fprintf(stderr, + " InnoDB: O_DIRECT is known to result in " + "'Invalid argument' on Linux on tmpfs, " + "see MySQL Bug#26662\n"); + } + } +#endif +} + +/******************************************************************** +Opens an existing file or creates a new. */ +UNIV_INTERN +os_file_t +os_file_create( +/*===========*/ + /* out, own: handle to the file, not defined + if error, error number can be retrieved with + os_file_get_last_error */ + const char* name, /* in: name of the file or path as a + null-terminated string */ + ulint create_mode,/* in: OS_FILE_OPEN if an existing file + is opened (if does not exist, error), or + OS_FILE_CREATE if a new file is created + (if exists, error), + OS_FILE_OVERWRITE if a new file is created + or an old overwritten; + OS_FILE_OPEN_RAW, if a raw device or disk + partition should be opened */ + ulint purpose,/* in: OS_FILE_AIO, if asynchronous, + non-buffered i/o is desired, + OS_FILE_NORMAL, if any normal file; + NOTE that it also depends on type, os_aio_.. + and srv_.. variables whether we really use + async i/o or unbuffered i/o: look in the + function source code for the exact rules */ + ulint type, /* in: OS_DATA_FILE or OS_LOG_FILE */ + ibool* success)/* out: TRUE if succeed, FALSE if error */ +{ +#ifdef __WIN__ + os_file_t file; + DWORD share_mode = FILE_SHARE_READ; + DWORD create_flag; + DWORD attributes; + ibool retry; +try_again: + ut_a(name); + + if (create_mode == OS_FILE_OPEN_RAW) { + create_flag = OPEN_EXISTING; + share_mode = FILE_SHARE_WRITE; + } else if (create_mode == OS_FILE_OPEN + || create_mode == OS_FILE_OPEN_RETRY) { + create_flag = OPEN_EXISTING; + } else if (create_mode == OS_FILE_CREATE) { + create_flag = CREATE_NEW; + } else if (create_mode == OS_FILE_OVERWRITE) { + create_flag = CREATE_ALWAYS; + } else { + create_flag = 0; + ut_error; + } + + if (purpose == OS_FILE_AIO) { + /* If specified, use asynchronous (overlapped) io and no + buffering of writes in the OS */ + attributes = 0; +#ifdef WIN_ASYNC_IO + if (os_aio_use_native_aio) { + attributes = attributes | FILE_FLAG_OVERLAPPED; + } +#endif +#ifdef UNIV_NON_BUFFERED_IO + if (type == OS_LOG_FILE && srv_flush_log_at_trx_commit == 2) { + /* Do not use unbuffered i/o to log files because + value 2 denotes that we do not flush the log at every + commit, but only once per second */ + } else if (srv_win_file_flush_method + == SRV_WIN_IO_UNBUFFERED) { + attributes = attributes | FILE_FLAG_NO_BUFFERING; + } +#endif + } else if (purpose == OS_FILE_NORMAL) { + attributes = 0; +#ifdef UNIV_NON_BUFFERED_IO + if (type == OS_LOG_FILE && srv_flush_log_at_trx_commit == 2) { + /* Do not use unbuffered i/o to log files because + value 2 denotes that we do not flush the log at every + commit, but only once per second */ + } else if (srv_win_file_flush_method + == SRV_WIN_IO_UNBUFFERED) { + attributes = attributes | FILE_FLAG_NO_BUFFERING; + } +#endif + } else { + attributes = 0; + ut_error; + } + + file = CreateFile((LPCTSTR) name, + GENERIC_READ | GENERIC_WRITE, /* read and write + access */ + share_mode, /* File can be read also by other + processes; we must give the read + permission because of ibbackup. We do + not give the write permission to + others because if one would succeed to + start 2 instances of mysqld on the + SAME files, that could cause severe + database corruption! When opening + raw disk partitions, Microsoft manuals + say that we must give also the write + permission. */ + NULL, /* default security attributes */ + create_flag, + attributes, + NULL); /* no template file */ + + if (file == INVALID_HANDLE_VALUE) { + *success = FALSE; + + /* When srv_file_per_table is on, file creation failure may not + be critical to the whole instance. Do not crash the server in + case of unknown errors. */ + if (srv_file_per_table) { + retry = os_file_handle_error_no_exit(name, + create_mode == OS_FILE_CREATE ? + "create" : "open"); + } else { + retry = os_file_handle_error(name, + create_mode == OS_FILE_CREATE ? + "create" : "open"); + } + + if (retry) { + goto try_again; + } + } else { + *success = TRUE; + } + + return(file); +#else /* __WIN__ */ + os_file_t file; + int create_flag; + ibool retry; + const char* mode_str = NULL; + const char* type_str = NULL; + const char* purpose_str = NULL; + +try_again: + ut_a(name); + + if (create_mode == OS_FILE_OPEN || create_mode == OS_FILE_OPEN_RAW + || create_mode == OS_FILE_OPEN_RETRY) { + mode_str = "OPEN"; + create_flag = O_RDWR; + } else if (create_mode == OS_FILE_CREATE) { + mode_str = "CREATE"; + create_flag = O_RDWR | O_CREAT | O_EXCL; + } else if (create_mode == OS_FILE_OVERWRITE) { + mode_str = "OVERWRITE"; + create_flag = O_RDWR | O_CREAT | O_TRUNC; + } else { + create_flag = 0; + ut_error; + } + + if (type == OS_LOG_FILE) { + type_str = "LOG"; + } else if (type == OS_DATA_FILE) { + type_str = "DATA"; + } else { + ut_error; + } + + if (purpose == OS_FILE_AIO) { + purpose_str = "AIO"; + } else if (purpose == OS_FILE_NORMAL) { + purpose_str = "NORMAL"; + } else { + ut_error; + } + +#if 0 + fprintf(stderr, "Opening file %s, mode %s, type %s, purpose %s\n", + name, mode_str, type_str, purpose_str); +#endif +#ifdef O_SYNC + /* We let O_SYNC only affect log files; note that we map O_DSYNC to + O_SYNC because the datasync options seemed to corrupt files in 2001 + in both Linux and Solaris */ + if (type == OS_LOG_FILE + && srv_unix_file_flush_method == SRV_UNIX_O_DSYNC) { + +# if 0 + fprintf(stderr, "Using O_SYNC for file %s\n", name); +# endif + + create_flag = create_flag | O_SYNC; + } +#endif /* O_SYNC */ + + file = open(name, create_flag, os_innodb_umask); + + if (file == -1) { + *success = FALSE; + + /* When srv_file_per_table is on, file creation failure may not + be critical to the whole instance. Do not crash the server in + case of unknown errors. */ + if (srv_file_per_table) { + retry = os_file_handle_error_no_exit(name, + create_mode == OS_FILE_CREATE ? + "create" : "open"); + } else { + retry = os_file_handle_error(name, + create_mode == OS_FILE_CREATE ? + "create" : "open"); + } + + if (retry) { + goto try_again; + } else { + return(file /* -1 */); + } + } + /* else */ + + *success = TRUE; + + /* We disable OS caching (O_DIRECT) only on data files */ + if (type != OS_LOG_FILE + && srv_unix_file_flush_method == SRV_UNIX_O_DIRECT) { + + os_file_set_nocache(file, name, mode_str); + } + +#ifdef USE_FILE_LOCK + if (create_mode != OS_FILE_OPEN_RAW && os_file_lock(file, name)) { + + if (create_mode == OS_FILE_OPEN_RETRY) { + int i; + ut_print_timestamp(stderr); + fputs(" InnoDB: Retrying to lock" + " the first data file\n", + stderr); + for (i = 0; i < 100; i++) { + os_thread_sleep(1000000); + if (!os_file_lock(file, name)) { + *success = TRUE; + return(file); + } + } + ut_print_timestamp(stderr); + fputs(" InnoDB: Unable to open the first data file\n", + stderr); + } + + *success = FALSE; + close(file); + file = -1; + } +#endif /* USE_FILE_LOCK */ + + return(file); +#endif /* __WIN__ */ +} + +/*************************************************************************** +Deletes a file if it exists. The file has to be closed before calling this. */ +UNIV_INTERN +ibool +os_file_delete_if_exists( +/*=====================*/ + /* out: TRUE if success */ + const char* name) /* in: file path as a null-terminated string */ +{ +#ifdef __WIN__ + BOOL ret; + ulint count = 0; +loop: + /* In Windows, deleting an .ibd file may fail if ibbackup is copying + it */ + + ret = DeleteFile((LPCTSTR)name); + + if (ret) { + return(TRUE); + } + + if (GetLastError() == ERROR_FILE_NOT_FOUND) { + /* the file does not exist, this not an error */ + + return(TRUE); + } + + count++; + + if (count > 100 && 0 == (count % 10)) { + fprintf(stderr, + "InnoDB: Warning: cannot delete file %s\n" + "InnoDB: Are you running ibbackup" + " to back up the file?\n", name); + + os_file_get_last_error(TRUE); /* print error information */ + } + + os_thread_sleep(1000000); /* sleep for a second */ + + if (count > 2000) { + + return(FALSE); + } + + goto loop; +#else + int ret; + + ret = unlink(name); + + if (ret != 0 && errno != ENOENT) { + os_file_handle_error_no_exit(name, "delete"); + + return(FALSE); + } + + return(TRUE); +#endif +} + +/*************************************************************************** +Deletes a file. The file has to be closed before calling this. */ +UNIV_INTERN +ibool +os_file_delete( +/*===========*/ + /* out: TRUE if success */ + const char* name) /* in: file path as a null-terminated string */ +{ +#ifdef __WIN__ + BOOL ret; + ulint count = 0; +loop: + /* In Windows, deleting an .ibd file may fail if ibbackup is copying + it */ + + ret = DeleteFile((LPCTSTR)name); + + if (ret) { + return(TRUE); + } + + if (GetLastError() == ERROR_FILE_NOT_FOUND) { + /* If the file does not exist, we classify this as a 'mild' + error and return */ + + return(FALSE); + } + + count++; + + if (count > 100 && 0 == (count % 10)) { + fprintf(stderr, + "InnoDB: Warning: cannot delete file %s\n" + "InnoDB: Are you running ibbackup" + " to back up the file?\n", name); + + os_file_get_last_error(TRUE); /* print error information */ + } + + os_thread_sleep(1000000); /* sleep for a second */ + + if (count > 2000) { + + return(FALSE); + } + + goto loop; +#else + int ret; + + ret = unlink(name); + + if (ret != 0) { + os_file_handle_error_no_exit(name, "delete"); + + return(FALSE); + } + + return(TRUE); +#endif +} + +/*************************************************************************** +Renames a file (can also move it to another directory). It is safest that the +file is closed before calling this function. */ +UNIV_INTERN +ibool +os_file_rename( +/*===========*/ + /* out: TRUE if success */ + const char* oldpath,/* in: old file path as a null-terminated + string */ + const char* newpath)/* in: new file path */ +{ +#ifdef __WIN__ + BOOL ret; + + ret = MoveFile((LPCTSTR)oldpath, (LPCTSTR)newpath); + + if (ret) { + return(TRUE); + } + + os_file_handle_error_no_exit(oldpath, "rename"); + + return(FALSE); +#else + int ret; + + ret = rename(oldpath, newpath); + + if (ret != 0) { + os_file_handle_error_no_exit(oldpath, "rename"); + + return(FALSE); + } + + return(TRUE); +#endif +} + +/*************************************************************************** +Closes a file handle. In case of error, error number can be retrieved with +os_file_get_last_error. */ +UNIV_INTERN +ibool +os_file_close( +/*==========*/ + /* out: TRUE if success */ + os_file_t file) /* in, own: handle to a file */ +{ +#ifdef __WIN__ + BOOL ret; + + ut_a(file); + + ret = CloseHandle(file); + + if (ret) { + return(TRUE); + } + + os_file_handle_error(NULL, "close"); + + return(FALSE); +#else + int ret; + + ret = close(file); + + if (ret == -1) { + os_file_handle_error(NULL, "close"); + + return(FALSE); + } + + return(TRUE); +#endif +} + +/*************************************************************************** +Closes a file handle. */ +UNIV_INTERN +ibool +os_file_close_no_error_handling( +/*============================*/ + /* out: TRUE if success */ + os_file_t file) /* in, own: handle to a file */ +{ +#ifdef __WIN__ + BOOL ret; + + ut_a(file); + + ret = CloseHandle(file); + + if (ret) { + return(TRUE); + } + + return(FALSE); +#else + int ret; + + ret = close(file); + + if (ret == -1) { + + return(FALSE); + } + + return(TRUE); +#endif +} + +/*************************************************************************** +Gets a file size. */ +UNIV_INTERN +ibool +os_file_get_size( +/*=============*/ + /* out: TRUE if success */ + os_file_t file, /* in: handle to a file */ + ulint* size, /* out: least significant 32 bits of file + size */ + ulint* size_high)/* out: most significant 32 bits of size */ +{ +#ifdef __WIN__ + DWORD high; + DWORD low; + + low = GetFileSize(file, &high); + + if ((low == 0xFFFFFFFF) && (GetLastError() != NO_ERROR)) { + return(FALSE); + } + + *size = low; + *size_high = high; + + return(TRUE); +#else + off_t offs; + + offs = lseek(file, 0, SEEK_END); + + if (offs == ((off_t)-1)) { + + return(FALSE); + } + + if (sizeof(off_t) > 4) { + *size = (ulint)(offs & 0xFFFFFFFFUL); + *size_high = (ulint)(offs >> 32); + } else { + *size = (ulint) offs; + *size_high = 0; + } + + return(TRUE); +#endif +} + +/*************************************************************************** +Gets file size as a 64-bit integer ib_int64_t. */ +UNIV_INTERN +ib_int64_t +os_file_get_size_as_iblonglong( +/*===========================*/ + /* out: size in bytes, -1 if error */ + os_file_t file) /* in: handle to a file */ +{ + ulint size; + ulint size_high; + ibool success; + + success = os_file_get_size(file, &size, &size_high); + + if (!success) { + + return(-1); + } + + return((((ib_int64_t)size_high) << 32) + (ib_int64_t)size); +} + +/*************************************************************************** +Write the specified number of zeros to a newly created file. */ +UNIV_INTERN +ibool +os_file_set_size( +/*=============*/ + /* out: TRUE if success */ + const char* name, /* in: name of the file or path as a + null-terminated string */ + os_file_t file, /* in: handle to a file */ + ulint size, /* in: least significant 32 bits of file + size */ + ulint size_high)/* in: most significant 32 bits of size */ +{ + ib_int64_t current_size; + ib_int64_t desired_size; + ibool ret; + byte* buf; + byte* buf2; + ulint buf_size; + + ut_a(size == (size & 0xFFFFFFFF)); + + current_size = 0; + desired_size = (ib_int64_t)size + (((ib_int64_t)size_high) << 32); + + /* Write up to 1 megabyte at a time. */ + buf_size = ut_min(64, (ulint) (desired_size / UNIV_PAGE_SIZE)) + * UNIV_PAGE_SIZE; + buf2 = ut_malloc(buf_size + UNIV_PAGE_SIZE); + + /* Align the buffer for possible raw i/o */ + buf = ut_align(buf2, UNIV_PAGE_SIZE); + + /* Write buffer full of zeros */ + memset(buf, 0, buf_size); + + if (desired_size >= (ib_int64_t)(100 * 1024 * 1024)) { + + fprintf(stderr, "InnoDB: Progress in MB:"); + } + + while (current_size < desired_size) { + ulint n_bytes; + + if (desired_size - current_size < (ib_int64_t) buf_size) { + n_bytes = (ulint) (desired_size - current_size); + } else { + n_bytes = buf_size; + } + + ret = os_file_write(name, file, buf, + (ulint)(current_size & 0xFFFFFFFF), + (ulint)(current_size >> 32), + n_bytes); + if (!ret) { + ut_free(buf2); + goto error_handling; + } + + /* Print about progress for each 100 MB written */ + if ((ib_int64_t) (current_size + n_bytes) / (ib_int64_t)(100 * 1024 * 1024) + != current_size / (ib_int64_t)(100 * 1024 * 1024)) { + + fprintf(stderr, " %lu00", + (ulong) ((current_size + n_bytes) + / (ib_int64_t)(100 * 1024 * 1024))); + } + + current_size += n_bytes; + } + + if (desired_size >= (ib_int64_t)(100 * 1024 * 1024)) { + + fprintf(stderr, "\n"); + } + + ut_free(buf2); + + ret = os_file_flush(file); + + if (ret) { + return(TRUE); + } + +error_handling: + return(FALSE); +} + +/*************************************************************************** +Truncates a file at its current position. */ +UNIV_INTERN +ibool +os_file_set_eof( +/*============*/ + /* out: TRUE if success */ + FILE* file) /* in: file to be truncated */ +{ +#ifdef __WIN__ + HANDLE h = (HANDLE) _get_osfhandle(fileno(file)); + return(SetEndOfFile(h)); +#else /* __WIN__ */ + return(!ftruncate(fileno(file), ftell(file))); +#endif /* __WIN__ */ +} + +#ifndef __WIN__ +/*************************************************************************** +Wrapper to fsync(2) that retries the call on some errors. +Returns the value 0 if successful; otherwise the value -1 is returned and +the global variable errno is set to indicate the error. */ + +static +int +os_file_fsync( +/*==========*/ + /* out: 0 if success, -1 otherwise */ + os_file_t file) /* in: handle to a file */ +{ + int ret; + int failures; + ibool retry; + + failures = 0; + + do { + ret = fsync(file); + + os_n_fsyncs++; + + if (ret == -1 && errno == ENOLCK) { + + if (failures % 100 == 0) { + + ut_print_timestamp(stderr); + fprintf(stderr, + " InnoDB: fsync(): " + "No locks available; retrying\n"); + } + + os_thread_sleep(200000 /* 0.2 sec */); + + failures++; + + retry = TRUE; + } else { + + retry = FALSE; + } + } while (retry); + + return(ret); +} +#endif /* !__WIN__ */ + +/*************************************************************************** +Flushes the write buffers of a given file to the disk. */ +UNIV_INTERN +ibool +os_file_flush( +/*==========*/ + /* out: TRUE if success */ + os_file_t file) /* in, own: handle to a file */ +{ +#ifdef __WIN__ + BOOL ret; + + ut_a(file); + + os_n_fsyncs++; + + ret = FlushFileBuffers(file); + + if (ret) { + return(TRUE); + } + + /* Since Windows returns ERROR_INVALID_FUNCTION if the 'file' is + actually a raw device, we choose to ignore that error if we are using + raw disks */ + + if (srv_start_raw_disk_in_use && GetLastError() + == ERROR_INVALID_FUNCTION) { + return(TRUE); + } + + os_file_handle_error(NULL, "flush"); + + /* It is a fatal error if a file flush does not succeed, because then + the database can get corrupt on disk */ + ut_error; + + return(FALSE); +#else + int ret; + +#if defined(HAVE_DARWIN_THREADS) +# ifndef F_FULLFSYNC + /* The following definition is from the Mac OS X 10.3 <sys/fcntl.h> */ +# define F_FULLFSYNC 51 /* fsync + ask the drive to flush to the media */ +# elif F_FULLFSYNC != 51 +# error "F_FULLFSYNC != 51: ABI incompatibility with Mac OS X 10.3" +# endif + /* Apple has disabled fsync() for internal disk drives in OS X. That + caused corruption for a user when he tested a power outage. Let us in + OS X use a nonstandard flush method recommended by an Apple + engineer. */ + + if (!srv_have_fullfsync) { + /* If we are not on an operating system that supports this, + then fall back to a plain fsync. */ + + ret = os_file_fsync(file); + } else { + ret = fcntl(file, F_FULLFSYNC, NULL); + + if (ret) { + /* If we are not on a file system that supports this, + then fall back to a plain fsync. */ + ret = os_file_fsync(file); + } + } +#else + ret = os_file_fsync(file); +#endif + + if (ret == 0) { + return(TRUE); + } + + /* Since Linux returns EINVAL if the 'file' is actually a raw device, + we choose to ignore that error if we are using raw disks */ + + if (srv_start_raw_disk_in_use && errno == EINVAL) { + + return(TRUE); + } + + ut_print_timestamp(stderr); + + fprintf(stderr, + " InnoDB: Error: the OS said file flush did not succeed\n"); + + os_file_handle_error(NULL, "flush"); + + /* It is a fatal error if a file flush does not succeed, because then + the database can get corrupt on disk */ + ut_error; + + return(FALSE); +#endif +} + +#ifndef __WIN__ +/*********************************************************************** +Does a synchronous read operation in Posix. */ +static +ssize_t +os_file_pread( +/*==========*/ + /* out: number of bytes read, -1 if error */ + os_file_t file, /* in: handle to a file */ + void* buf, /* in: buffer where to read */ + ulint n, /* in: number of bytes to read */ + ulint offset, /* in: least significant 32 bits of file + offset from where to read */ + ulint offset_high) /* in: most significant 32 bits of + offset */ +{ + off_t offs; + ssize_t n_bytes; + + ut_a((offset & 0xFFFFFFFFUL) == offset); + + /* If off_t is > 4 bytes in size, then we assume we can pass a + 64-bit address */ + + if (sizeof(off_t) > 4) { + offs = (off_t)offset + (((off_t)offset_high) << 32); + + } else { + offs = (off_t)offset; + + if (offset_high > 0) { + fprintf(stderr, + "InnoDB: Error: file read at offset > 4 GB\n"); + } + } + + os_n_file_reads++; + +#if defined(HAVE_PREAD) && !defined(HAVE_BROKEN_PREAD) + os_mutex_enter(os_file_count_mutex); + os_file_n_pending_preads++; + os_n_pending_reads++; + os_mutex_exit(os_file_count_mutex); + + n_bytes = pread(file, buf, (ssize_t)n, offs); + + os_mutex_enter(os_file_count_mutex); + os_file_n_pending_preads--; + os_n_pending_reads--; + os_mutex_exit(os_file_count_mutex); + + return(n_bytes); +#else + { + off_t ret_offset; + ssize_t ret; + ulint i; + + os_mutex_enter(os_file_count_mutex); + os_n_pending_reads++; + os_mutex_exit(os_file_count_mutex); + + /* Protect the seek / read operation with a mutex */ + i = ((ulint) file) % OS_FILE_N_SEEK_MUTEXES; + + os_mutex_enter(os_file_seek_mutexes[i]); + + ret_offset = lseek(file, offs, SEEK_SET); + + if (ret_offset < 0) { + ret = -1; + } else { + ret = read(file, buf, (ssize_t)n); + } + + os_mutex_exit(os_file_seek_mutexes[i]); + + os_mutex_enter(os_file_count_mutex); + os_n_pending_reads--; + os_mutex_exit(os_file_count_mutex); + + return(ret); + } +#endif +} + +/*********************************************************************** +Does a synchronous write operation in Posix. */ +static +ssize_t +os_file_pwrite( +/*===========*/ + /* out: number of bytes written, -1 if error */ + os_file_t file, /* in: handle to a file */ + const void* buf, /* in: buffer from where to write */ + ulint n, /* in: number of bytes to write */ + ulint offset, /* in: least significant 32 bits of file + offset where to write */ + ulint offset_high) /* in: most significant 32 bits of + offset */ +{ + ssize_t ret; + off_t offs; + + ut_a((offset & 0xFFFFFFFFUL) == offset); + + /* If off_t is > 4 bytes in size, then we assume we can pass a + 64-bit address */ + + if (sizeof(off_t) > 4) { + offs = (off_t)offset + (((off_t)offset_high) << 32); + } else { + offs = (off_t)offset; + + if (offset_high > 0) { + fprintf(stderr, + "InnoDB: Error: file write" + " at offset > 4 GB\n"); + } + } + + os_n_file_writes++; + +#if defined(HAVE_PWRITE) && !defined(HAVE_BROKEN_PREAD) + os_mutex_enter(os_file_count_mutex); + os_file_n_pending_pwrites++; + os_n_pending_writes++; + os_mutex_exit(os_file_count_mutex); + + ret = pwrite(file, buf, (ssize_t)n, offs); + + os_mutex_enter(os_file_count_mutex); + os_file_n_pending_pwrites--; + os_n_pending_writes--; + os_mutex_exit(os_file_count_mutex); + +# ifdef UNIV_DO_FLUSH + if (srv_unix_file_flush_method != SRV_UNIX_LITTLESYNC + && srv_unix_file_flush_method != SRV_UNIX_NOSYNC + && !os_do_not_call_flush_at_each_write) { + + /* Always do fsync to reduce the probability that when + the OS crashes, a database page is only partially + physically written to disk. */ + + ut_a(TRUE == os_file_flush(file)); + } +# endif /* UNIV_DO_FLUSH */ + + return(ret); +#else + { + off_t ret_offset; + ulint i; + + os_mutex_enter(os_file_count_mutex); + os_n_pending_writes++; + os_mutex_exit(os_file_count_mutex); + + /* Protect the seek / write operation with a mutex */ + i = ((ulint) file) % OS_FILE_N_SEEK_MUTEXES; + + os_mutex_enter(os_file_seek_mutexes[i]); + + ret_offset = lseek(file, offs, SEEK_SET); + + if (ret_offset < 0) { + ret = -1; + + goto func_exit; + } + + ret = write(file, buf, (ssize_t)n); + +# ifdef UNIV_DO_FLUSH + if (srv_unix_file_flush_method != SRV_UNIX_LITTLESYNC + && srv_unix_file_flush_method != SRV_UNIX_NOSYNC + && !os_do_not_call_flush_at_each_write) { + + /* Always do fsync to reduce the probability that when + the OS crashes, a database page is only partially + physically written to disk. */ + + ut_a(TRUE == os_file_flush(file)); + } +# endif /* UNIV_DO_FLUSH */ + +func_exit: + os_mutex_exit(os_file_seek_mutexes[i]); + + os_mutex_enter(os_file_count_mutex); + os_n_pending_writes--; + os_mutex_exit(os_file_count_mutex); + + return(ret); + } +#endif +} +#endif + +/*********************************************************************** +Requests a synchronous positioned read operation. */ +UNIV_INTERN +ibool +os_file_read( +/*=========*/ + /* out: TRUE if request was + successful, FALSE if fail */ + os_file_t file, /* in: handle to a file */ + void* buf, /* in: buffer where to read */ + ulint offset, /* in: least significant 32 bits of file + offset where to read */ + ulint offset_high, /* in: most significant 32 bits of + offset */ + ulint n) /* in: number of bytes to read */ +{ +#ifdef __WIN__ + BOOL ret; + DWORD len; + DWORD ret2; + DWORD low; + DWORD high; + ibool retry; + ulint i; + + ut_a((offset & 0xFFFFFFFFUL) == offset); + + os_n_file_reads++; + os_bytes_read_since_printout += n; + +try_again: + ut_ad(file); + ut_ad(buf); + ut_ad(n > 0); + + low = (DWORD) offset; + high = (DWORD) offset_high; + + os_mutex_enter(os_file_count_mutex); + os_n_pending_reads++; + os_mutex_exit(os_file_count_mutex); + + /* Protect the seek / read operation with a mutex */ + i = ((ulint) file) % OS_FILE_N_SEEK_MUTEXES; + + os_mutex_enter(os_file_seek_mutexes[i]); + + ret2 = SetFilePointer(file, low, &high, FILE_BEGIN); + + if (ret2 == 0xFFFFFFFF && GetLastError() != NO_ERROR) { + + os_mutex_exit(os_file_seek_mutexes[i]); + + os_mutex_enter(os_file_count_mutex); + os_n_pending_reads--; + os_mutex_exit(os_file_count_mutex); + + goto error_handling; + } + + ret = ReadFile(file, buf, (DWORD) n, &len, NULL); + + os_mutex_exit(os_file_seek_mutexes[i]); + + os_mutex_enter(os_file_count_mutex); + os_n_pending_reads--; + os_mutex_exit(os_file_count_mutex); + + if (ret && len == n) { + return(TRUE); + } +#else + ibool retry; + ssize_t ret; + + os_bytes_read_since_printout += n; + +try_again: + ret = os_file_pread(file, buf, n, offset, offset_high); + + if ((ulint)ret == n) { + + return(TRUE); + } + + fprintf(stderr, + "InnoDB: Error: tried to read %lu bytes at offset %lu %lu.\n" + "InnoDB: Was only able to read %ld.\n", + (ulong)n, (ulong)offset_high, + (ulong)offset, (long)ret); +#endif +#ifdef __WIN__ +error_handling: +#endif + retry = os_file_handle_error(NULL, "read"); + + if (retry) { + goto try_again; + } + + fprintf(stderr, + "InnoDB: Fatal error: cannot read from file." + " OS error number %lu.\n", +#ifdef __WIN__ + (ulong) GetLastError() +#else + (ulong) errno +#endif + ); + fflush(stderr); + + ut_error; + + return(FALSE); +} + +/*********************************************************************** +Requests a synchronous positioned read operation. This function does not do +any error handling. In case of error it returns FALSE. */ +UNIV_INTERN +ibool +os_file_read_no_error_handling( +/*===========================*/ + /* out: TRUE if request was + successful, FALSE if fail */ + os_file_t file, /* in: handle to a file */ + void* buf, /* in: buffer where to read */ + ulint offset, /* in: least significant 32 bits of file + offset where to read */ + ulint offset_high, /* in: most significant 32 bits of + offset */ + ulint n) /* in: number of bytes to read */ +{ +#ifdef __WIN__ + BOOL ret; + DWORD len; + DWORD ret2; + DWORD low; + DWORD high; + ibool retry; + ulint i; + + ut_a((offset & 0xFFFFFFFFUL) == offset); + + os_n_file_reads++; + os_bytes_read_since_printout += n; + +try_again: + ut_ad(file); + ut_ad(buf); + ut_ad(n > 0); + + low = (DWORD) offset; + high = (DWORD) offset_high; + + os_mutex_enter(os_file_count_mutex); + os_n_pending_reads++; + os_mutex_exit(os_file_count_mutex); + + /* Protect the seek / read operation with a mutex */ + i = ((ulint) file) % OS_FILE_N_SEEK_MUTEXES; + + os_mutex_enter(os_file_seek_mutexes[i]); + + ret2 = SetFilePointer(file, low, &high, FILE_BEGIN); + + if (ret2 == 0xFFFFFFFF && GetLastError() != NO_ERROR) { + + os_mutex_exit(os_file_seek_mutexes[i]); + + os_mutex_enter(os_file_count_mutex); + os_n_pending_reads--; + os_mutex_exit(os_file_count_mutex); + + goto error_handling; + } + + ret = ReadFile(file, buf, (DWORD) n, &len, NULL); + + os_mutex_exit(os_file_seek_mutexes[i]); + + os_mutex_enter(os_file_count_mutex); + os_n_pending_reads--; + os_mutex_exit(os_file_count_mutex); + + if (ret && len == n) { + return(TRUE); + } +#else + ibool retry; + ssize_t ret; + + os_bytes_read_since_printout += n; + +try_again: + ret = os_file_pread(file, buf, n, offset, offset_high); + + if ((ulint)ret == n) { + + return(TRUE); + } +#endif +#ifdef __WIN__ +error_handling: +#endif + retry = os_file_handle_error_no_exit(NULL, "read"); + + if (retry) { + goto try_again; + } + + return(FALSE); +} + +/*********************************************************************** +Rewind file to its start, read at most size - 1 bytes from it to str, and +NUL-terminate str. All errors are silently ignored. This function is +mostly meant to be used with temporary files. */ +UNIV_INTERN +void +os_file_read_string( +/*================*/ + FILE* file, /* in: file to read from */ + char* str, /* in: buffer where to read */ + ulint size) /* in: size of buffer */ +{ + size_t flen; + + if (size == 0) { + return; + } + + rewind(file); + flen = fread(str, 1, size - 1, file); + str[flen] = '\0'; +} + +/*********************************************************************** +Requests a synchronous write operation. */ +UNIV_INTERN +ibool +os_file_write( +/*==========*/ + /* out: TRUE if request was + successful, FALSE if fail */ + const char* name, /* in: name of the file or path as a + null-terminated string */ + os_file_t file, /* in: handle to a file */ + const void* buf, /* in: buffer from which to write */ + ulint offset, /* in: least significant 32 bits of file + offset where to write */ + ulint offset_high, /* in: most significant 32 bits of + offset */ + ulint n) /* in: number of bytes to write */ +{ +#ifdef __WIN__ + BOOL ret; + DWORD len; + DWORD ret2; + DWORD low; + DWORD high; + ulint i; + ulint n_retries = 0; + ulint err; + + ut_a((offset & 0xFFFFFFFF) == offset); + + os_n_file_writes++; + + ut_ad(file); + ut_ad(buf); + ut_ad(n > 0); +retry: + low = (DWORD) offset; + high = (DWORD) offset_high; + + os_mutex_enter(os_file_count_mutex); + os_n_pending_writes++; + os_mutex_exit(os_file_count_mutex); + + /* Protect the seek / write operation with a mutex */ + i = ((ulint) file) % OS_FILE_N_SEEK_MUTEXES; + + os_mutex_enter(os_file_seek_mutexes[i]); + + ret2 = SetFilePointer(file, low, &high, FILE_BEGIN); + + if (ret2 == 0xFFFFFFFF && GetLastError() != NO_ERROR) { + + os_mutex_exit(os_file_seek_mutexes[i]); + + os_mutex_enter(os_file_count_mutex); + os_n_pending_writes--; + os_mutex_exit(os_file_count_mutex); + + ut_print_timestamp(stderr); + + fprintf(stderr, + " InnoDB: Error: File pointer positioning to" + " file %s failed at\n" + "InnoDB: offset %lu %lu. Operating system" + " error number %lu.\n" + "InnoDB: Some operating system error numbers" + " are described at\n" + "InnoDB: " + "http://dev.mysql.com/doc/refman/5.1/en/" + "operating-system-error-codes.html\n", + name, (ulong) offset_high, (ulong) offset, + (ulong) GetLastError()); + + return(FALSE); + } + + ret = WriteFile(file, buf, (DWORD) n, &len, NULL); + + /* Always do fsync to reduce the probability that when the OS crashes, + a database page is only partially physically written to disk. */ + +# ifdef UNIV_DO_FLUSH + if (!os_do_not_call_flush_at_each_write) { + ut_a(TRUE == os_file_flush(file)); + } +# endif /* UNIV_DO_FLUSH */ + + os_mutex_exit(os_file_seek_mutexes[i]); + + os_mutex_enter(os_file_count_mutex); + os_n_pending_writes--; + os_mutex_exit(os_file_count_mutex); + + if (ret && len == n) { + + return(TRUE); + } + + /* If some background file system backup tool is running, then, at + least in Windows 2000, we may get here a specific error. Let us + retry the operation 100 times, with 1 second waits. */ + + if (GetLastError() == ERROR_LOCK_VIOLATION && n_retries < 100) { + + os_thread_sleep(1000000); + + n_retries++; + + goto retry; + } + + if (!os_has_said_disk_full) { + + err = (ulint)GetLastError(); + + ut_print_timestamp(stderr); + + fprintf(stderr, + " InnoDB: Error: Write to file %s failed" + " at offset %lu %lu.\n" + "InnoDB: %lu bytes should have been written," + " only %lu were written.\n" + "InnoDB: Operating system error number %lu.\n" + "InnoDB: Check that your OS and file system" + " support files of this size.\n" + "InnoDB: Check also that the disk is not full" + " or a disk quota exceeded.\n", + name, (ulong) offset_high, (ulong) offset, + (ulong) n, (ulong) len, (ulong) err); + + if (strerror((int)err) != NULL) { + fprintf(stderr, + "InnoDB: Error number %lu means '%s'.\n", + (ulong) err, strerror((int)err)); + } + + fprintf(stderr, + "InnoDB: Some operating system error numbers" + " are described at\n" + "InnoDB: " + "http://dev.mysql.com/doc/refman/5.1/en/" + "operating-system-error-codes.html\n"); + + os_has_said_disk_full = TRUE; + } + + return(FALSE); +#else + ssize_t ret; + + ret = os_file_pwrite(file, buf, n, offset, offset_high); + + if ((ulint)ret == n) { + + return(TRUE); + } + + if (!os_has_said_disk_full) { + + ut_print_timestamp(stderr); + + fprintf(stderr, + " InnoDB: Error: Write to file %s failed" + " at offset %lu %lu.\n" + "InnoDB: %lu bytes should have been written," + " only %ld were written.\n" + "InnoDB: Operating system error number %lu.\n" + "InnoDB: Check that your OS and file system" + " support files of this size.\n" + "InnoDB: Check also that the disk is not full" + " or a disk quota exceeded.\n", + name, offset_high, offset, n, (long int)ret, + (ulint)errno); + if (strerror(errno) != NULL) { + fprintf(stderr, + "InnoDB: Error number %lu means '%s'.\n", + (ulint)errno, strerror(errno)); + } + + fprintf(stderr, + "InnoDB: Some operating system error numbers" + " are described at\n" + "InnoDB: " + "http://dev.mysql.com/doc/refman/5.1/en/" + "operating-system-error-codes.html\n"); + + os_has_said_disk_full = TRUE; + } + + return(FALSE); +#endif +} + +/*********************************************************************** +Check the existence and type of the given file. */ +UNIV_INTERN +ibool +os_file_status( +/*===========*/ + /* out: TRUE if call succeeded */ + const char* path, /* in: pathname of the file */ + ibool* exists, /* out: TRUE if file exists */ + os_file_type_t* type) /* out: type of the file (if it exists) */ +{ +#ifdef __WIN__ + int ret; + struct _stat statinfo; + + ret = _stat(path, &statinfo); + if (ret && (errno == ENOENT || errno == ENOTDIR)) { + /* file does not exist */ + *exists = FALSE; + return(TRUE); + } else if (ret) { + /* file exists, but stat call failed */ + + os_file_handle_error_no_exit(path, "stat"); + + return(FALSE); + } + + if (_S_IFDIR & statinfo.st_mode) { + *type = OS_FILE_TYPE_DIR; + } else if (_S_IFREG & statinfo.st_mode) { + *type = OS_FILE_TYPE_FILE; + } else { + *type = OS_FILE_TYPE_UNKNOWN; + } + + *exists = TRUE; + + return(TRUE); +#else + int ret; + struct stat statinfo; + + ret = stat(path, &statinfo); + if (ret && (errno == ENOENT || errno == ENOTDIR)) { + /* file does not exist */ + *exists = FALSE; + return(TRUE); + } else if (ret) { + /* file exists, but stat call failed */ + + os_file_handle_error_no_exit(path, "stat"); + + return(FALSE); + } + + if (S_ISDIR(statinfo.st_mode)) { + *type = OS_FILE_TYPE_DIR; + } else if (S_ISLNK(statinfo.st_mode)) { + *type = OS_FILE_TYPE_LINK; + } else if (S_ISREG(statinfo.st_mode)) { + *type = OS_FILE_TYPE_FILE; + } else { + *type = OS_FILE_TYPE_UNKNOWN; + } + + *exists = TRUE; + + return(TRUE); +#endif +} + +/*********************************************************************** +This function returns information about the specified file */ +UNIV_INTERN +ibool +os_file_get_status( +/*===============*/ + /* out: TRUE if stat + information found */ + const char* path, /* in: pathname of the file */ + os_file_stat_t* stat_info) /* information of a file in a + directory */ +{ +#ifdef __WIN__ + int ret; + struct _stat statinfo; + + ret = _stat(path, &statinfo); + if (ret && (errno == ENOENT || errno == ENOTDIR)) { + /* file does not exist */ + + return(FALSE); + } else if (ret) { + /* file exists, but stat call failed */ + + os_file_handle_error_no_exit(path, "stat"); + + return(FALSE); + } + if (_S_IFDIR & statinfo.st_mode) { + stat_info->type = OS_FILE_TYPE_DIR; + } else if (_S_IFREG & statinfo.st_mode) { + stat_info->type = OS_FILE_TYPE_FILE; + } else { + stat_info->type = OS_FILE_TYPE_UNKNOWN; + } + + stat_info->ctime = statinfo.st_ctime; + stat_info->atime = statinfo.st_atime; + stat_info->mtime = statinfo.st_mtime; + stat_info->size = statinfo.st_size; + + return(TRUE); +#else + int ret; + struct stat statinfo; + + ret = stat(path, &statinfo); + + if (ret && (errno == ENOENT || errno == ENOTDIR)) { + /* file does not exist */ + + return(FALSE); + } else if (ret) { + /* file exists, but stat call failed */ + + os_file_handle_error_no_exit(path, "stat"); + + return(FALSE); + } + + if (S_ISDIR(statinfo.st_mode)) { + stat_info->type = OS_FILE_TYPE_DIR; + } else if (S_ISLNK(statinfo.st_mode)) { + stat_info->type = OS_FILE_TYPE_LINK; + } else if (S_ISREG(statinfo.st_mode)) { + stat_info->type = OS_FILE_TYPE_FILE; + } else { + stat_info->type = OS_FILE_TYPE_UNKNOWN; + } + + stat_info->ctime = statinfo.st_ctime; + stat_info->atime = statinfo.st_atime; + stat_info->mtime = statinfo.st_mtime; + stat_info->size = statinfo.st_size; + + return(TRUE); +#endif +} + +/* path name separator character */ +#ifdef __WIN__ +# define OS_FILE_PATH_SEPARATOR '\\' +#else +# define OS_FILE_PATH_SEPARATOR '/' +#endif + +/******************************************************************** +The function os_file_dirname returns a directory component of a +null-terminated pathname string. In the usual case, dirname returns +the string up to, but not including, the final '/', and basename +is the component following the final '/'. Trailing '/' charac +ters are not counted as part of the pathname. + +If path does not contain a slash, dirname returns the string ".". + +Concatenating the string returned by dirname, a "/", and the basename +yields a complete pathname. + +The return value is a copy of the directory component of the pathname. +The copy is allocated from heap. It is the caller responsibility +to free it after it is no longer needed. + +The following list of examples (taken from SUSv2) shows the strings +returned by dirname and basename for different paths: + + path dirname basename + "/usr/lib" "/usr" "lib" + "/usr/" "/" "usr" + "usr" "." "usr" + "/" "/" "/" + "." "." "." + ".." "." ".." +*/ +UNIV_INTERN +char* +os_file_dirname( +/*============*/ + /* out, own: directory component of the + pathname */ + const char* path) /* in: pathname */ +{ + /* Find the offset of the last slash */ + const char* last_slash = strrchr(path, OS_FILE_PATH_SEPARATOR); + if (!last_slash) { + /* No slash in the path, return "." */ + + return(mem_strdup(".")); + } + + /* Ok, there is a slash */ + + if (last_slash == path) { + /* last slash is the first char of the path */ + + return(mem_strdup("/")); + } + + /* Non-trivial directory component */ + + return(mem_strdupl(path, last_slash - path)); +} + +/******************************************************************** +Creates all missing subdirectories along the given path. */ +UNIV_INTERN +ibool +os_file_create_subdirs_if_needed( +/*=============================*/ + /* out: TRUE if call succeeded + FALSE otherwise */ + const char* path) /* in: path name */ +{ + char* subdir; + ibool success, subdir_exists; + os_file_type_t type; + + subdir = os_file_dirname(path); + if (strlen(subdir) == 1 + && (*subdir == OS_FILE_PATH_SEPARATOR || *subdir == '.')) { + /* subdir is root or cwd, nothing to do */ + mem_free(subdir); + + return(TRUE); + } + + /* Test if subdir exists */ + success = os_file_status(subdir, &subdir_exists, &type); + if (success && !subdir_exists) { + /* subdir does not exist, create it */ + success = os_file_create_subdirs_if_needed(subdir); + if (!success) { + mem_free(subdir); + + return(FALSE); + } + success = os_file_create_directory(subdir, FALSE); + } + + mem_free(subdir); + + return(success); +} + +/******************************************************************** +Returns a pointer to the nth slot in the aio array. */ +static +os_aio_slot_t* +os_aio_array_get_nth_slot( +/*======================*/ + /* out: pointer to slot */ + os_aio_array_t* array, /* in: aio array */ + ulint index) /* in: index of the slot */ +{ + ut_a(index < array->n_slots); + + return((array->slots) + index); +} + +/**************************************************************************** +Creates an aio wait array. */ +static +os_aio_array_t* +os_aio_array_create( +/*================*/ + /* out, own: aio array */ + ulint n, /* in: maximum number of pending aio operations + allowed; n must be divisible by n_segments */ + ulint n_segments) /* in: number of segments in the aio array */ +{ + os_aio_array_t* array; + ulint i; + os_aio_slot_t* slot; +#ifdef WIN_ASYNC_IO + OVERLAPPED* over; +#endif + ut_a(n > 0); + ut_a(n_segments > 0); + + array = ut_malloc(sizeof(os_aio_array_t)); + + array->mutex = os_mutex_create(NULL); + array->not_full = os_event_create(NULL); + array->is_empty = os_event_create(NULL); + + os_event_set(array->is_empty); + + array->n_slots = n; + array->n_segments = n_segments; + array->n_reserved = 0; + array->slots = ut_malloc(n * sizeof(os_aio_slot_t)); +#ifdef __WIN__ + array->native_events = ut_malloc(n * sizeof(os_native_event_t)); +#endif + for (i = 0; i < n; i++) { + slot = os_aio_array_get_nth_slot(array, i); + + slot->pos = i; + slot->reserved = FALSE; +#ifdef WIN_ASYNC_IO + slot->event = os_event_create(NULL); + + over = &(slot->control); + + over->hEvent = slot->event->handle; + + *((array->native_events) + i) = over->hEvent; +#endif + } + + return(array); +} + +/**************************************************************************** +Initializes the asynchronous io system. Calls also os_io_init_simple. +Creates a separate aio array for +non-ibuf read and write, a third aio array for the ibuf i/o, with just one +segment, two aio arrays for log reads and writes with one segment, and a +synchronous aio array of the specified size. The combined number of segments +in the three first aio arrays is the parameter n_segments given to the +function. The caller must create an i/o handler thread for each segment in +the four first arrays, but not for the sync aio array. */ +UNIV_INTERN +void +os_aio_init( +/*========*/ + ulint n, /* in: maximum number of pending aio operations + allowed; n must be divisible by n_segments */ +// ulint n_segments, /* in: combined number of segments in the four +// first aio arrays; must be >= 4 */ + ulint n_read_threads, /* n_segments == 2 + n_read_threads + n_write_threads*/ + ulint n_write_threads, /**/ + ulint n_slots_sync) /* in: number of slots in the sync aio array */ +{ + ulint n_read_segs; + ulint n_write_segs; + ulint n_per_seg; + ulint i; + + ulint n_segments = 2 + n_read_threads + n_write_threads; + + ut_ad(n % n_segments == 0); + ut_ad(n_segments >= 4); + + os_io_init_simple(); + + for (i = 0; i < n_segments; i++) { + srv_set_io_thread_op_info(i, "not started yet"); + os_aio_thread_buffer[i] = 0; + os_aio_thread_buffer_size[i] = 0; + } + + n_per_seg = n / n_segments; + n_write_segs = n_write_threads; + n_read_segs = n_read_threads; + + /* fprintf(stderr, "Array n per seg %lu\n", n_per_seg); */ + + os_aio_first_write_segment = os_aio_first_read_segment + n_read_threads; + os_aio_ibuf_array = os_aio_array_create(n_per_seg, 1); + + srv_io_thread_function[0] = "insert buffer thread"; + + os_aio_log_array = os_aio_array_create(n_per_seg, 1); + + srv_io_thread_function[1] = "log thread"; + + os_aio_read_array = os_aio_array_create(n_per_seg, + n_read_segs); + for (i = 2; i < 2 + n_read_segs; i++) { + ut_a(i < SRV_MAX_N_IO_THREADS); + srv_io_thread_function[i] = "read thread"; + } + + os_aio_write_array = os_aio_array_create(n_per_seg, + n_write_segs); + for (i = 2 + n_read_segs; i < n_segments; i++) { + ut_a(i < SRV_MAX_N_IO_THREADS); + srv_io_thread_function[i] = "write thread"; + } + + os_aio_sync_array = os_aio_array_create(n_slots_sync, 1); + + os_aio_n_segments = n_segments; + + os_aio_validate(); + + os_aio_segment_wait_events = ut_malloc(n_segments * sizeof(void*)); + + for (i = 0; i < n_segments; i++) { + os_aio_segment_wait_events[i] = os_event_create(NULL); + } + + os_last_printout = time(NULL); + +} + +#ifdef WIN_ASYNC_IO +/**************************************************************************** +Wakes up all async i/o threads in the array in Windows async i/o at +shutdown. */ +static +void +os_aio_array_wake_win_aio_at_shutdown( +/*==================================*/ + os_aio_array_t* array) /* in: aio array */ +{ + ulint i; + + for (i = 0; i < array->n_slots; i++) { + + os_event_set((array->slots + i)->event); + } +} +#endif + +/**************************************************************************** +Wakes up all async i/o threads so that they know to exit themselves in +shutdown. */ +UNIV_INTERN +void +os_aio_wake_all_threads_at_shutdown(void) +/*=====================================*/ +{ + ulint i; + +#ifdef WIN_ASYNC_IO + /* This code wakes up all ai/o threads in Windows native aio */ + os_aio_array_wake_win_aio_at_shutdown(os_aio_read_array); + os_aio_array_wake_win_aio_at_shutdown(os_aio_write_array); + os_aio_array_wake_win_aio_at_shutdown(os_aio_ibuf_array); + os_aio_array_wake_win_aio_at_shutdown(os_aio_log_array); +#endif + /* This loop wakes up all simulated ai/o threads */ + + for (i = 0; i < os_aio_n_segments; i++) { + + os_event_set(os_aio_segment_wait_events[i]); + } +} + +/**************************************************************************** +Waits until there are no pending writes in os_aio_write_array. There can +be other, synchronous, pending writes. */ +UNIV_INTERN +void +os_aio_wait_until_no_pending_writes(void) +/*=====================================*/ +{ + os_event_wait(os_aio_write_array->is_empty); +} + +/************************************************************************** +Calculates segment number for a slot. */ +static +ulint +os_aio_get_segment_no_from_slot( +/*============================*/ + /* out: segment number (which is the number + used by, for example, i/o-handler threads) */ + os_aio_array_t* array, /* in: aio wait array */ + os_aio_slot_t* slot) /* in: slot in this array */ +{ + ulint segment; + ulint seg_len; + + if (array == os_aio_ibuf_array) { + segment = 0; + + } else if (array == os_aio_log_array) { + segment = 1; + + } else if (array == os_aio_read_array) { + seg_len = os_aio_read_array->n_slots + / os_aio_read_array->n_segments; + + segment = 2 + slot->pos / seg_len; + } else { + ut_a(array == os_aio_write_array); + seg_len = os_aio_write_array->n_slots + / os_aio_write_array->n_segments; + + segment = os_aio_read_array->n_segments + 2 + + slot->pos / seg_len; + } + + return(segment); +} + +/************************************************************************** +Calculates local segment number and aio array from global segment number. */ +static +ulint +os_aio_get_array_and_local_segment( +/*===============================*/ + /* out: local segment number within + the aio array */ + os_aio_array_t** array, /* out: aio wait array */ + ulint global_segment)/* in: global segment number */ +{ + ulint segment; + + ut_a(global_segment < os_aio_n_segments); + + if (global_segment == 0) { + *array = os_aio_ibuf_array; + segment = 0; + + } else if (global_segment == 1) { + *array = os_aio_log_array; + segment = 0; + + } else if (global_segment < os_aio_read_array->n_segments + 2) { + *array = os_aio_read_array; + + segment = global_segment - 2; + } else { + *array = os_aio_write_array; + + segment = global_segment - (os_aio_read_array->n_segments + 2); + } + + return(segment); +} + +/*********************************************************************** +Requests for a slot in the aio array. If no slot is available, waits until +not_full-event becomes signaled. */ +static +os_aio_slot_t* +os_aio_array_reserve_slot( +/*======================*/ + /* out: pointer to slot */ + ulint type, /* in: OS_FILE_READ or OS_FILE_WRITE */ + os_aio_array_t* array, /* in: aio array */ + fil_node_t* message1,/* in: message to be passed along with + the aio operation */ + void* message2,/* in: message to be passed along with + the aio operation */ + os_file_t file, /* in: file handle */ + const char* name, /* in: name of the file or path as a + null-terminated string */ + void* buf, /* in: buffer where to read or from which + to write */ + ulint offset, /* in: least significant 32 bits of file + offset */ + ulint offset_high, /* in: most significant 32 bits of + offset */ + ulint len) /* in: length of the block to read or write */ +{ + os_aio_slot_t* slot; +#ifdef WIN_ASYNC_IO + OVERLAPPED* control; +#endif + ulint i; + ulint prim_segment; + ulint n; + + n = array->n_slots / array->n_segments; + /* 64 blocks' striping ( aligning max(BUF_READ_AHEAD_AREA) ) */ + prim_segment = ( offset >> (UNIV_PAGE_SIZE_SHIFT + 6) ) % (array->n_segments); + +loop: + os_mutex_enter(array->mutex); + + if (array->n_reserved == array->n_slots) { + os_mutex_exit(array->mutex); + + if (!os_aio_use_native_aio) { + /* If the handler threads are suspended, wake them + so that we get more slots */ + + os_aio_simulated_wake_handler_threads(); + } + + os_event_wait(array->not_full); + + goto loop; + } + + for (i = prim_segment * n; i < array->n_slots; i++) { + slot = os_aio_array_get_nth_slot(array, i); + + if (slot->reserved == FALSE) { + break; + } + } + + if (slot->reserved == TRUE){ + /* Not found after the intended segment. So we should search before. */ + for (i = 0;; i++) { + slot = os_aio_array_get_nth_slot(array, i); + + if (slot->reserved == FALSE) { + break; + } + } + } + + array->n_reserved++; + + if (array->n_reserved == 1) { + os_event_reset(array->is_empty); + } + + if (array->n_reserved == array->n_slots) { + os_event_reset(array->not_full); + } + + slot->reserved = TRUE; + slot->reservation_time = time(NULL); + slot->message1 = message1; + slot->message2 = message2; + slot->file = file; + slot->name = name; + slot->len = len; + slot->type = type; + slot->buf = buf; + slot->offset = offset; + slot->offset_high = offset_high; +// slot->io_already_done = FALSE; + slot->status = OS_AIO_NOT_ISSUED; + +#ifdef WIN_ASYNC_IO + control = &(slot->control); + control->Offset = (DWORD)offset; + control->OffsetHigh = (DWORD)offset_high; + os_event_reset(slot->event); +#endif + + os_mutex_exit(array->mutex); + + return(slot); +} + +/*********************************************************************** +Frees a slot in the aio array. */ +static +void +os_aio_array_free_slot( +/*===================*/ + os_aio_array_t* array, /* in: aio array */ + os_aio_slot_t* slot) /* in: pointer to slot */ +{ + ut_ad(array); + ut_ad(slot); + + os_mutex_enter(array->mutex); + + ut_ad(slot->reserved); + + slot->reserved = FALSE; + slot->status = OS_AIO_NOT_ISSUED; + + array->n_reserved--; + + if (array->n_reserved == array->n_slots - 1) { + os_event_set(array->not_full); + } + + if (array->n_reserved == 0) { + os_event_set(array->is_empty); + } + +#ifdef WIN_ASYNC_IO + os_event_reset(slot->event); +#endif + os_mutex_exit(array->mutex); +} + +/************************************************************************** +Wakes up a simulated aio i/o-handler thread if it has something to do. */ +static +void +os_aio_simulated_wake_handler_thread( +/*=================================*/ + ulint global_segment) /* in: the number of the segment in the aio + arrays */ +{ + os_aio_array_t* array; + os_aio_slot_t* slot; + ulint segment; + ulint n; + ulint i; + + ut_ad(!os_aio_use_native_aio); + + segment = os_aio_get_array_and_local_segment(&array, global_segment); + + n = array->n_slots; + + /* Look through n slots after the segment * n'th slot */ + + os_mutex_enter(array->mutex); + + for (i = 0; i < n; i++) { + slot = os_aio_array_get_nth_slot(array, i); + + if (slot->reserved && + (slot->status == OS_AIO_NOT_ISSUED || + slot->status == OS_AIO_DONE)) { + /* Found an i/o request */ + + break; + } + } + + os_mutex_exit(array->mutex); + + if (i < n) { + if (array == os_aio_ibuf_array) { + os_event_set(os_aio_segment_wait_events[0]); + + } else if (array == os_aio_log_array) { + os_event_set(os_aio_segment_wait_events[1]); + + } else if (array == os_aio_read_array) { + ulint x; + for (x = os_aio_first_read_segment; x < os_aio_first_write_segment; x++) + os_event_set(os_aio_segment_wait_events[x]); + + } else if (array == os_aio_write_array) { + ulint x; + for (x = os_aio_first_write_segment; x < os_aio_n_segments; x++) + os_event_set(os_aio_segment_wait_events[x]); + + } else { + ut_a(0); + } + } +} + +/************************************************************************** +Wakes up simulated aio i/o-handler threads if they have something to do. */ +UNIV_INTERN +void +os_aio_simulated_wake_handler_threads(void) +/*=======================================*/ +{ + if (os_aio_use_native_aio) { + /* We do not use simulated aio: do nothing */ + + return; + } + + os_aio_recommend_sleep_for_read_threads = FALSE; + + os_aio_simulated_wake_handler_thread(0); + os_aio_simulated_wake_handler_thread(1); + os_aio_simulated_wake_handler_thread(os_aio_first_read_segment); + os_aio_simulated_wake_handler_thread(os_aio_first_write_segment); +} + +/************************************************************************** +This function can be called if one wants to post a batch of reads and +prefers an i/o-handler thread to handle them all at once later. You must +call os_aio_simulated_wake_handler_threads later to ensure the threads +are not left sleeping! */ +UNIV_INTERN +void +os_aio_simulated_put_read_threads_to_sleep(void) +/*============================================*/ +{ + os_aio_array_t* array; + ulint g; + + os_aio_recommend_sleep_for_read_threads = TRUE; + + for (g = 0; g < os_aio_n_segments; g++) { + os_aio_get_array_and_local_segment(&array, g); + + if (array == os_aio_read_array) { + + os_event_reset(os_aio_segment_wait_events[g]); + } + } +} + +/*********************************************************************** +Requests an asynchronous i/o operation. */ +UNIV_INTERN +ibool +os_aio( +/*===*/ + /* out: TRUE if request was queued + successfully, FALSE if fail */ + ulint type, /* in: OS_FILE_READ or OS_FILE_WRITE */ + ulint mode, /* in: OS_AIO_NORMAL, ..., possibly ORed + to OS_AIO_SIMULATED_WAKE_LATER: the + last flag advises this function not to wake + i/o-handler threads, but the caller will + do the waking explicitly later, in this + way the caller can post several requests in + a batch; NOTE that the batch must not be + so big that it exhausts the slots in aio + arrays! NOTE that a simulated batch + may introduce hidden chances of deadlocks, + because i/os are not actually handled until + all have been posted: use with great + caution! */ + const char* name, /* in: name of the file or path as a + null-terminated string */ + os_file_t file, /* in: handle to a file */ + void* buf, /* in: buffer where to read or from which + to write */ + ulint offset, /* in: least significant 32 bits of file + offset where to read or write */ + ulint offset_high, /* in: most significant 32 bits of + offset */ + ulint n, /* in: number of bytes to read or write */ + fil_node_t* message1,/* in: messages for the aio handler (these + can be used to identify a completed aio + operation); if mode is OS_AIO_SYNC, these + are ignored */ + void* message2) +{ + os_aio_array_t* array; + os_aio_slot_t* slot; +#ifdef WIN_ASYNC_IO + ibool retval; + BOOL ret = TRUE; + DWORD len = (DWORD) n; + struct fil_node_struct * dummy_mess1; + void* dummy_mess2; + ulint dummy_type; +#endif + ulint err = 0; + ibool retry; + ulint wake_later; + + ut_ad(file); + ut_ad(buf); + ut_ad(n > 0); + ut_ad(n % OS_FILE_LOG_BLOCK_SIZE == 0); + ut_ad(offset % OS_FILE_LOG_BLOCK_SIZE == 0); + ut_ad(os_aio_validate()); + + wake_later = mode & OS_AIO_SIMULATED_WAKE_LATER; + mode = mode & (~OS_AIO_SIMULATED_WAKE_LATER); + + if (mode == OS_AIO_SYNC +#ifdef WIN_ASYNC_IO + && !os_aio_use_native_aio +#endif + ) { + /* This is actually an ordinary synchronous read or write: + no need to use an i/o-handler thread. NOTE that if we use + Windows async i/o, Windows does not allow us to use + ordinary synchronous os_file_read etc. on the same file, + therefore we have built a special mechanism for synchronous + wait in the Windows case. */ + + if (type == OS_FILE_READ) { + return(os_file_read(file, buf, offset, + offset_high, n)); + } + + ut_a(type == OS_FILE_WRITE); + + return(os_file_write(name, file, buf, offset, offset_high, n)); + } + +try_again: + if (mode == OS_AIO_NORMAL) { + if (type == OS_FILE_READ) { + array = os_aio_read_array; + } else { + array = os_aio_write_array; + } + } else if (mode == OS_AIO_IBUF) { + ut_ad(type == OS_FILE_READ); + /* Reduce probability of deadlock bugs in connection with ibuf: + do not let the ibuf i/o handler sleep */ + + wake_later = FALSE; + + array = os_aio_ibuf_array; + } else if (mode == OS_AIO_LOG) { + + array = os_aio_log_array; + } else if (mode == OS_AIO_SYNC) { + array = os_aio_sync_array; + } else { + array = NULL; /* Eliminate compiler warning */ + ut_error; + } + + slot = os_aio_array_reserve_slot(type, array, message1, message2, file, + name, buf, offset, offset_high, n); + if (type == OS_FILE_READ) { + if (os_aio_use_native_aio) { +#ifdef WIN_ASYNC_IO + os_n_file_reads++; + os_bytes_read_since_printout += len; + + ret = ReadFile(file, buf, (DWORD)n, &len, + &(slot->control)); +#endif + } else { + if (!wake_later) { + os_aio_simulated_wake_handler_thread( + os_aio_get_segment_no_from_slot( + array, slot)); + } + } + } else if (type == OS_FILE_WRITE) { + if (os_aio_use_native_aio) { +#ifdef WIN_ASYNC_IO + os_n_file_writes++; + ret = WriteFile(file, buf, (DWORD)n, &len, + &(slot->control)); +#endif + } else { + if (!wake_later) { + os_aio_simulated_wake_handler_thread( + os_aio_get_segment_no_from_slot( + array, slot)); + } + } + } else { + ut_error; + } + +#ifdef WIN_ASYNC_IO + if (os_aio_use_native_aio) { + if ((ret && len == n) + || (!ret && GetLastError() == ERROR_IO_PENDING)) { + /* aio was queued successfully! */ + + if (mode == OS_AIO_SYNC) { + /* We want a synchronous i/o operation on a + file where we also use async i/o: in Windows + we must use the same wait mechanism as for + async i/o */ + + retval = os_aio_windows_handle(ULINT_UNDEFINED, + slot->pos, + &dummy_mess1, + &dummy_mess2, + &dummy_type); + + return(retval); + } + + return(TRUE); + } + + err = 1; /* Fall through the next if */ + } +#endif + if (err == 0) { + /* aio was queued successfully! */ + + return(TRUE); + } + + os_aio_array_free_slot(array, slot); + + retry = os_file_handle_error(name, + type == OS_FILE_READ + ? "aio read" : "aio write"); + if (retry) { + + goto try_again; + } + + return(FALSE); +} + +#ifdef WIN_ASYNC_IO +/************************************************************************** +This function is only used in Windows asynchronous i/o. +Waits for an aio operation to complete. This function is used to wait the +for completed requests. The aio array of pending requests is divided +into segments. The thread specifies which segment or slot it wants to wait +for. NOTE: this function will also take care of freeing the aio slot, +therefore no other thread is allowed to do the freeing! */ +UNIV_INTERN +ibool +os_aio_windows_handle( +/*==================*/ + /* out: TRUE if the aio operation succeeded */ + ulint segment, /* in: the number of the segment in the aio + arrays to wait for; segment 0 is the ibuf + i/o thread, segment 1 the log i/o thread, + then follow the non-ibuf read threads, and as + the last are the non-ibuf write threads; if + this is ULINT_UNDEFINED, then it means that + sync aio is used, and this parameter is + ignored */ + ulint pos, /* this parameter is used only in sync aio: + wait for the aio slot at this position */ + fil_node_t**message1, /* out: the messages passed with the aio + request; note that also in the case where + the aio operation failed, these output + parameters are valid and can be used to + restart the operation, for example */ + void** message2, + ulint* type) /* out: OS_FILE_WRITE or ..._READ */ +{ + ulint orig_seg = segment; + os_aio_array_t* array; + os_aio_slot_t* slot; + ulint n; + ulint i; + ibool ret_val; + BOOL ret; + DWORD len; + + if (segment == ULINT_UNDEFINED) { + array = os_aio_sync_array; + segment = 0; + } else { + segment = os_aio_get_array_and_local_segment(&array, segment); + } + + /* NOTE! We only access constant fields in os_aio_array. Therefore + we do not have to acquire the protecting mutex yet */ + + ut_ad(os_aio_validate()); + ut_ad(segment < array->n_segments); + + n = array->n_slots; + + if (array == os_aio_sync_array) { + os_event_wait(os_aio_array_get_nth_slot(array, pos)->event); + i = pos; + } else { + srv_set_io_thread_op_info(orig_seg, "wait Windows aio"); + i = os_event_wait_multiple(n, + (array->native_events) + ); + } + + os_mutex_enter(array->mutex); + + slot = os_aio_array_get_nth_slot(array, i); + + ut_a(slot->reserved); + + if (orig_seg != ULINT_UNDEFINED) { + srv_set_io_thread_op_info(orig_seg, + "get windows aio return value"); + } + + ret = GetOverlappedResult(slot->file, &(slot->control), &len, TRUE); + + *message1 = slot->message1; + *message2 = slot->message2; + + *type = slot->type; + + if (ret && len == slot->len) { + ret_val = TRUE; + +#ifdef UNIV_DO_FLUSH + if (slot->type == OS_FILE_WRITE + && !os_do_not_call_flush_at_each_write) { + ut_a(TRUE == os_file_flush(slot->file)); + } +#endif /* UNIV_DO_FLUSH */ + } else { + os_file_handle_error(slot->name, "Windows aio"); + + ret_val = FALSE; + } + + os_mutex_exit(array->mutex); + + os_aio_array_free_slot(array, slot); + + return(ret_val); +} +#endif + +/************************************************************************** +Does simulated aio. This function should be called by an i/o-handler +thread. */ +UNIV_INTERN +ibool +os_aio_simulated_handle( +/*====================*/ + /* out: TRUE if the aio operation succeeded */ + ulint global_segment, /* in: the number of the segment in the aio + arrays to wait for; segment 0 is the ibuf + i/o thread, segment 1 the log i/o thread, + then follow the non-ibuf read threads, and as + the last are the non-ibuf write threads */ + fil_node_t**message1, /* out: the messages passed with the aio + request; note that also in the case where + the aio operation failed, these output + parameters are valid and can be used to + restart the operation, for example */ + void** message2, + ulint* type) /* out: OS_FILE_WRITE or ..._READ */ +{ + os_aio_array_t* array; + ulint segment; + os_aio_slot_t* slot; + os_aio_slot_t* slot2; + os_aio_slot_t* consecutive_ios[OS_AIO_MERGE_N_CONSECUTIVE]; + os_aio_slot_t* lowest_request; + os_aio_slot_t* oldest_request; + ulint n_consecutive; + ulint total_len; + ulint offs; + ulint lowest_offset; + ulint oldest_offset; + ulint biggest_age; + ulint age; + byte* combined_buf; + byte* combined_buf2; + ibool ret; + ulint n; + ulint i; + time_t now; + + segment = os_aio_get_array_and_local_segment(&array, global_segment); + +restart: + /* NOTE! We only access constant fields in os_aio_array. Therefore + we do not have to acquire the protecting mutex yet */ + + srv_set_io_thread_op_info(global_segment, + "looking for i/o requests (a)"); + ut_ad(os_aio_validate()); + ut_ad(segment < array->n_segments); + + n = array->n_slots; + + /* Look through n slots after the segment * n'th slot */ + + if (array == os_aio_read_array + && os_aio_recommend_sleep_for_read_threads) { + + /* Give other threads chance to add several i/os to the array + at once. */ + + goto recommended_sleep; + } + + os_mutex_enter(array->mutex); + + srv_set_io_thread_op_info(global_segment, + "looking for i/o requests (b)"); + + /* Check if there is a slot for which the i/o has already been + done */ + + for (i = 0; i < n; i++) { + slot = os_aio_array_get_nth_slot(array, i); + + if (slot->reserved && slot->status == OS_AIO_DONE) { + + if (os_aio_print_debug) { + fprintf(stderr, + "InnoDB: i/o for slot %lu" + " already done, returning\n", + (ulong) i); + } + + ret = TRUE; + + goto slot_io_done; + } + } + + n_consecutive = 0; + + /* If there are at least 2 seconds old requests, then pick the oldest + one to prevent starvation. If several requests have the same age, + then pick the one at the lowest offset. */ + + biggest_age = 0; + now = time(NULL); + oldest_request = lowest_request = NULL; + oldest_offset = lowest_offset = ULINT_MAX; + + /* Find the oldest request and the request with the smallest offset */ + for (i = 0; i < n; i++) { + slot = os_aio_array_get_nth_slot(array, i); + + if (slot->reserved && slot->status == OS_AIO_NOT_ISSUED) { + age = (ulint)difftime(now, slot->reservation_time); + + if ((age >= 2 && age > biggest_age) + || (age >= 2 && age == biggest_age + && slot->offset < oldest_offset)) { + + /* Found an i/o request */ + biggest_age = age; + oldest_request = slot; + oldest_offset = slot->offset; + } + + /* Look for an i/o request at the lowest offset in the array + * (we ignore the high 32 bits of the offset) */ + if (slot->offset < lowest_offset) { + /* Found an i/o request */ + lowest_request = slot; + lowest_offset = slot->offset; + } + } + } + + if (!lowest_request && !oldest_request) { + + /* No i/o requested at the moment */ + + goto wait_for_io; + } + + if (oldest_request) { + slot = oldest_request; + } else { + slot = lowest_request; + } + consecutive_ios[0] = slot; + n_consecutive = 1; + + /* Check if there are several consecutive blocks to read or write */ + +consecutive_loop: + for (i = 0; i < n; i++) { + slot2 = os_aio_array_get_nth_slot(array, i); + + if (slot2->reserved && slot2 != slot + && slot2->offset == slot->offset + slot->len + /* check that sum does not wrap over */ + && slot->offset + slot->len > slot->offset + && slot2->offset_high == slot->offset_high + && slot2->type == slot->type + && slot2->file == slot->file + && slot2->status == OS_AIO_NOT_ISSUED) { + + /* Found a consecutive i/o request */ + + consecutive_ios[n_consecutive] = slot2; + n_consecutive++; + + slot = slot2; + + if (n_consecutive < OS_AIO_MERGE_N_CONSECUTIVE) { + + goto consecutive_loop; + } else { + break; + } + } + } + + srv_set_io_thread_op_info(global_segment, "consecutive i/o requests"); + + /* We have now collected n_consecutive i/o requests in the array; + allocate a single buffer which can hold all data, and perform the + i/o */ + + total_len = 0; + slot = consecutive_ios[0]; + + for (i = 0; i < n_consecutive; i++) { + total_len += consecutive_ios[i]->len; + ut_a(consecutive_ios[i]->status == OS_AIO_NOT_ISSUED); + consecutive_ios[i]->status = OS_AIO_ISSUED; + } + + if (n_consecutive == 1) { + /* We can use the buffer of the i/o request */ + combined_buf = slot->buf; + combined_buf2 = NULL; + } else { + if ((total_len + UNIV_PAGE_SIZE) > os_aio_thread_buffer_size[global_segment]) { + if (os_aio_thread_buffer[global_segment]) + ut_free(os_aio_thread_buffer[global_segment]); + + os_aio_thread_buffer[global_segment] = ut_malloc(total_len + UNIV_PAGE_SIZE); + os_aio_thread_buffer_size[global_segment] = total_len + UNIV_PAGE_SIZE; + } + combined_buf2 = os_aio_thread_buffer[global_segment]; + + ut_a(combined_buf2); + + combined_buf = ut_align(combined_buf2, UNIV_PAGE_SIZE); + } + + /* We release the array mutex for the time of the i/o: NOTE that + this assumes that there is just one i/o-handler thread serving + a single segment of slots! */ + + ut_a(slot->reserved); + ut_a(slot->status == OS_AIO_ISSUED); + + os_mutex_exit(array->mutex); + + if (slot->type == OS_FILE_WRITE && n_consecutive > 1) { + /* Copy the buffers to the combined buffer */ + offs = 0; + + for (i = 0; i < n_consecutive; i++) { + + ut_memcpy(combined_buf + offs, consecutive_ios[i]->buf, + consecutive_ios[i]->len); + offs += consecutive_ios[i]->len; + } + } + + srv_set_io_thread_op_info(global_segment, "doing file i/o"); + + if (os_aio_print_debug) { + fprintf(stderr, + "InnoDB: doing i/o of type %lu at offset %lu %lu," + " length %lu\n", + (ulong) slot->type, (ulong) slot->offset_high, + (ulong) slot->offset, (ulong) total_len); + } + + /* Do the i/o with ordinary, synchronous i/o functions: */ + if (slot->type == OS_FILE_WRITE) { + ret = os_file_write(slot->name, slot->file, combined_buf, + slot->offset, slot->offset_high, + total_len); + } else { + ret = os_file_read(slot->file, combined_buf, + slot->offset, slot->offset_high, total_len); + } + + ut_a(ret); + srv_set_io_thread_op_info(global_segment, "file i/o done"); + +#if 0 + fprintf(stderr, + "aio: %lu consecutive %lu:th segment, first offs %lu blocks\n", + n_consecutive, global_segment, slot->offset / UNIV_PAGE_SIZE); +#endif + + if (slot->type == OS_FILE_READ && n_consecutive > 1) { + /* Copy the combined buffer to individual buffers */ + offs = 0; + + for (i = 0; i < n_consecutive; i++) { + + ut_memcpy(consecutive_ios[i]->buf, combined_buf + offs, + consecutive_ios[i]->len); + offs += consecutive_ios[i]->len; + } + } + + os_mutex_enter(array->mutex); + + /* Mark the i/os done in slots */ + + for (i = 0; i < n_consecutive; i++) { + ut_a(consecutive_ios[i]->status == OS_AIO_ISSUED); + consecutive_ios[i]->status = OS_AIO_DONE; + } + + /* We return the messages for the first slot now, and if there were + several slots, the messages will be returned with subsequent calls + of this function */ + +slot_io_done: + + ut_a(slot->reserved); + ut_a(slot->status == OS_AIO_DONE); + slot->status = OS_AIO_CLAIMED; + + *message1 = slot->message1; + *message2 = slot->message2; + + *type = slot->type; + + os_mutex_exit(array->mutex); + + os_aio_array_free_slot(array, slot); + + return(ret); + +wait_for_io: + srv_set_io_thread_op_info(global_segment, "resetting wait event"); + + /* We wait here until there again can be i/os in the segment + of this thread */ + + os_event_reset(os_aio_segment_wait_events[global_segment]); + + os_mutex_exit(array->mutex); + +recommended_sleep: + srv_set_io_thread_op_info(global_segment, "waiting for i/o request"); + + os_event_wait(os_aio_segment_wait_events[global_segment]); + + if (os_aio_print_debug) { + fprintf(stderr, + "InnoDB: i/o handler thread for i/o" + " segment %lu wakes up\n", + (ulong) global_segment); + } + + goto restart; +} + +/************************************************************************** +Validates the consistency of an aio array. */ +static +ibool +os_aio_array_validate( +/*==================*/ + /* out: TRUE if ok */ + os_aio_array_t* array) /* in: aio wait array */ +{ + os_aio_slot_t* slot; + ulint n_reserved = 0; + ulint i; + + ut_a(array); + + os_mutex_enter(array->mutex); + + ut_a(array->n_slots > 0); + ut_a(array->n_segments > 0); + + for (i = 0; i < array->n_slots; i++) { + slot = os_aio_array_get_nth_slot(array, i); + + if (slot->reserved) { + n_reserved++; + ut_a(slot->len > 0); + } + } + + ut_a(array->n_reserved == n_reserved); + + os_mutex_exit(array->mutex); + + return(TRUE); +} + +/************************************************************************** +Validates the consistency the aio system. */ +UNIV_INTERN +ibool +os_aio_validate(void) +/*=================*/ + /* out: TRUE if ok */ +{ + os_aio_array_validate(os_aio_read_array); + os_aio_array_validate(os_aio_write_array); + os_aio_array_validate(os_aio_ibuf_array); + os_aio_array_validate(os_aio_log_array); + os_aio_array_validate(os_aio_sync_array); + + return(TRUE); +} + +/************************************************************************** +Prints info of the aio arrays. */ +UNIV_INTERN +void +os_aio_print( +/*=========*/ + FILE* file) /* in: file where to print */ +{ + os_aio_array_t* array; + os_aio_slot_t* slot; + ulint n_reserved; + time_t current_time; + double time_elapsed; + double avg_bytes_read; + ulint i; + + for (i = 0; i < srv_n_file_io_threads; i++) { + fprintf(file, "I/O thread %lu state: %s (%s)", (ulong) i, + srv_io_thread_op_info[i], + srv_io_thread_function[i]); + +#ifndef __WIN__ + if (os_aio_segment_wait_events[i]->is_set) { + fprintf(file, " ev set"); + } +#endif + + fprintf(file, "\n"); + } + + fputs("Pending normal aio reads:", file); + + array = os_aio_read_array; +loop: + ut_a(array); + + os_mutex_enter(array->mutex); + + ut_a(array->n_slots > 0); + ut_a(array->n_segments > 0); + + n_reserved = 0; + + for (i = 0; i < array->n_slots; i++) { + slot = os_aio_array_get_nth_slot(array, i); + + if (slot->reserved) { + n_reserved++; +#if 0 + fprintf(stderr, "Reserved slot, messages %p %p\n", + (void*) slot->message1, + (void*) slot->message2); +#endif + ut_a(slot->len > 0); + } + } + + ut_a(array->n_reserved == n_reserved); + + fprintf(file, " %lu", (ulong) n_reserved); + + os_mutex_exit(array->mutex); + + if (array == os_aio_read_array) { + fputs(", aio writes:", file); + + array = os_aio_write_array; + + goto loop; + } + + if (array == os_aio_write_array) { + fputs(",\n ibuf aio reads:", file); + array = os_aio_ibuf_array; + + goto loop; + } + + if (array == os_aio_ibuf_array) { + fputs(", log i/o's:", file); + array = os_aio_log_array; + + goto loop; + } + + if (array == os_aio_log_array) { + fputs(", sync i/o's:", file); + array = os_aio_sync_array; + + goto loop; + } + + putc('\n', file); + current_time = time(NULL); + time_elapsed = 0.001 + difftime(current_time, os_last_printout); + + fprintf(file, + "Pending flushes (fsync) log: %lu; buffer pool: %lu\n" + "%lu OS file reads, %lu OS file writes, %lu OS fsyncs\n", + (ulong) fil_n_pending_log_flushes, + (ulong) fil_n_pending_tablespace_flushes, + (ulong) os_n_file_reads, (ulong) os_n_file_writes, + (ulong) os_n_fsyncs); + + if (os_file_n_pending_preads != 0 || os_file_n_pending_pwrites != 0) { + fprintf(file, + "%lu pending preads, %lu pending pwrites\n", + (ulong) os_file_n_pending_preads, + (ulong) os_file_n_pending_pwrites); + } + + if (os_n_file_reads == os_n_file_reads_old) { + avg_bytes_read = 0.0; + } else { + avg_bytes_read = (double) os_bytes_read_since_printout + / (os_n_file_reads - os_n_file_reads_old); + } + + fprintf(file, + "%.2f reads/s, %lu avg bytes/read," + " %.2f writes/s, %.2f fsyncs/s\n", + (os_n_file_reads - os_n_file_reads_old) + / time_elapsed, + (ulong)avg_bytes_read, + (os_n_file_writes - os_n_file_writes_old) + / time_elapsed, + (os_n_fsyncs - os_n_fsyncs_old) + / time_elapsed); + + os_n_file_reads_old = os_n_file_reads; + os_n_file_writes_old = os_n_file_writes; + os_n_fsyncs_old = os_n_fsyncs; + os_bytes_read_since_printout = 0; + + os_last_printout = current_time; +} + +/************************************************************************** +Refreshes the statistics used to print per-second averages. */ +UNIV_INTERN +void +os_aio_refresh_stats(void) +/*======================*/ +{ + os_n_file_reads_old = os_n_file_reads; + os_n_file_writes_old = os_n_file_writes; + os_n_fsyncs_old = os_n_fsyncs; + os_bytes_read_since_printout = 0; + + os_last_printout = time(NULL); +} + +#ifdef UNIV_DEBUG +/************************************************************************** +Checks that all slots in the system have been freed, that is, there are +no pending io operations. */ +UNIV_INTERN +ibool +os_aio_all_slots_free(void) +/*=======================*/ + /* out: TRUE if all free */ +{ + os_aio_array_t* array; + ulint n_res = 0; + + array = os_aio_read_array; + + os_mutex_enter(array->mutex); + + n_res += array->n_reserved; + + os_mutex_exit(array->mutex); + + array = os_aio_write_array; + + os_mutex_enter(array->mutex); + + n_res += array->n_reserved; + + os_mutex_exit(array->mutex); + + array = os_aio_ibuf_array; + + os_mutex_enter(array->mutex); + + n_res += array->n_reserved; + + os_mutex_exit(array->mutex); + + array = os_aio_log_array; + + os_mutex_enter(array->mutex); + + n_res += array->n_reserved; + + os_mutex_exit(array->mutex); + + array = os_aio_sync_array; + + os_mutex_enter(array->mutex); + + n_res += array->n_reserved; + + os_mutex_exit(array->mutex); + + if (n_res == 0) { + + return(TRUE); + } + + return(FALSE); +} +#endif /* UNIV_DEBUG */ diff --git a/storage/xtradb/os/os0proc.c b/storage/xtradb/os/os0proc.c new file mode 100644 index 00000000000..8d4a71f8c4e --- /dev/null +++ b/storage/xtradb/os/os0proc.c @@ -0,0 +1,262 @@ +/***************************************************************************** + +Copyright (c) 1995, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/****************************************************** +The interface to the operating system +process control primitives + +Created 9/30/1995 Heikki Tuuri +*******************************************************/ + +#include "os0proc.h" +#ifdef UNIV_NONINL +#include "os0proc.ic" +#endif + +#include "ut0mem.h" +#include "ut0byte.h" + +/* FreeBSD for example has only MAP_ANON, Linux has MAP_ANONYMOUS and +MAP_ANON but MAP_ANON is marked as deprecated */ +#if defined(MAP_ANONYMOUS) +#define OS_MAP_ANON MAP_ANONYMOUS +#elif defined(MAP_ANON) +#define OS_MAP_ANON MAP_ANON +#endif + +UNIV_INTERN ibool os_use_large_pages; +/* Large page size. This may be a boot-time option on some platforms */ +UNIV_INTERN ulint os_large_page_size; + +/******************************************************************** +Converts the current process id to a number. It is not guaranteed that the +number is unique. In Linux returns the 'process number' of the current +thread. That number is the same as one sees in 'top', for example. In Linux +the thread id is not the same as one sees in 'top'. */ +UNIV_INTERN +ulint +os_proc_get_number(void) +/*====================*/ +{ +#ifdef __WIN__ + return((ulint)GetCurrentProcessId()); +#else + return((ulint)getpid()); +#endif +} + +/******************************************************************** +Allocates large pages memory. */ +UNIV_INTERN +void* +os_mem_alloc_large( +/*===============*/ + /* out: allocated memory */ + ulint* n) /* in/out: number of bytes */ +{ + void* ptr; + ulint size; +#if defined HAVE_LARGE_PAGES && defined UNIV_LINUX + int shmid; + struct shmid_ds buf; + + if (!os_use_large_pages || !os_large_page_size) { + goto skip; + } + + /* Align block size to os_large_page_size */ + ut_ad(ut_is_2pow(os_large_page_size)); + size = ut_2pow_round(*n + (os_large_page_size - 1), + os_large_page_size); + + shmid = shmget(IPC_PRIVATE, (size_t)size, SHM_HUGETLB | SHM_R | SHM_W); + if (shmid < 0) { + fprintf(stderr, "InnoDB: HugeTLB: Warning: Failed to allocate" + " %lu bytes. errno %d\n", size, errno); + ptr = NULL; + } else { + ptr = shmat(shmid, NULL, 0); + if (ptr == (void *)-1) { + fprintf(stderr, "InnoDB: HugeTLB: Warning: Failed to" + " attach shared memory segment, errno %d\n", + errno); + } + + /* Remove the shared memory segment so that it will be + automatically freed after memory is detached or + process exits */ + shmctl(shmid, IPC_RMID, &buf); + } + + if (ptr) { + *n = size; + os_fast_mutex_lock(&ut_list_mutex); + ut_total_allocated_memory += size; + os_fast_mutex_unlock(&ut_list_mutex); +# ifdef UNIV_SET_MEM_TO_ZERO + memset(ptr, '\0', size); +# endif + UNIV_MEM_ALLOC(ptr, size); + return(ptr); + } + + fprintf(stderr, "InnoDB HugeTLB: Warning: Using conventional" + " memory pool\n"); +skip: +#endif /* HAVE_LARGE_PAGES && UNIV_LINUX */ + +#ifdef __WIN__ + SYSTEM_INFO system_info; + GetSystemInfo(&system_info); + + /* Align block size to system page size */ + ut_ad(ut_is_2pow(system_info.dwPageSize)); + /* system_info.dwPageSize is only 32-bit. Casting to ulint is required + on 64-bit Windows. */ + size = *n = ut_2pow_round(*n + (system_info.dwPageSize - 1), + (ulint) system_info.dwPageSize); + ptr = VirtualAlloc(NULL, size, MEM_COMMIT | MEM_RESERVE, + PAGE_READWRITE); + if (!ptr) { + fprintf(stderr, "InnoDB: VirtualAlloc(%lu bytes) failed;" + " Windows error %lu\n", + (ulong) size, (ulong) GetLastError()); + } else { + os_fast_mutex_lock(&ut_list_mutex); + ut_total_allocated_memory += size; + os_fast_mutex_unlock(&ut_list_mutex); + UNIV_MEM_ALLOC(ptr, size); + } +#elif defined __NETWARE__ || !defined OS_MAP_ANON + size = *n; + ptr = ut_malloc_low(size, TRUE, FALSE); +#else +# ifdef HAVE_GETPAGESIZE + size = getpagesize(); +# else + size = UNIV_PAGE_SIZE; +# endif + /* Align block size to system page size */ + ut_ad(ut_is_2pow(size)); + size = *n = ut_2pow_round(*n + (size - 1), size); + ptr = mmap(NULL, size, PROT_READ | PROT_WRITE, + MAP_PRIVATE | OS_MAP_ANON, -1, 0); + if (UNIV_UNLIKELY(ptr == (void*) -1)) { + fprintf(stderr, "InnoDB: mmap(%lu bytes) failed;" + " errno %lu\n", + (ulong) size, (ulong) errno); + ptr = NULL; + } else { + os_fast_mutex_lock(&ut_list_mutex); + ut_total_allocated_memory += size; + os_fast_mutex_unlock(&ut_list_mutex); + UNIV_MEM_ALLOC(ptr, size); + } +#endif + return(ptr); +} + +/******************************************************************** +Frees large pages memory. */ +UNIV_INTERN +void +os_mem_free_large( +/*==============*/ + void *ptr, /* in: pointer returned by + os_mem_alloc_large() */ + ulint size) /* in: size returned by + os_mem_alloc_large() */ +{ + os_fast_mutex_lock(&ut_list_mutex); + ut_a(ut_total_allocated_memory >= size); + os_fast_mutex_unlock(&ut_list_mutex); + +#if defined HAVE_LARGE_PAGES && defined UNIV_LINUX + if (os_use_large_pages && os_large_page_size && !shmdt(ptr)) { + os_fast_mutex_lock(&ut_list_mutex); + ut_a(ut_total_allocated_memory >= size); + ut_total_allocated_memory -= size; + os_fast_mutex_unlock(&ut_list_mutex); + UNIV_MEM_FREE(ptr, size); + return; + } +#endif /* HAVE_LARGE_PAGES && UNIV_LINUX */ +#ifdef __WIN__ + /* When RELEASE memory, the size parameter must be 0. + Do not use MEM_RELEASE with MEM_DECOMMIT. */ + if (!VirtualFree(ptr, 0, MEM_RELEASE)) { + fprintf(stderr, "InnoDB: VirtualFree(%p, %lu) failed;" + " Windows error %lu\n", + ptr, (ulong) size, (ulong) GetLastError()); + } else { + os_fast_mutex_lock(&ut_list_mutex); + ut_a(ut_total_allocated_memory >= size); + ut_total_allocated_memory -= size; + os_fast_mutex_unlock(&ut_list_mutex); + UNIV_MEM_FREE(ptr, size); + } +#elif defined __NETWARE__ || !defined OS_MAP_ANON + ut_free(ptr); +#else + if (munmap(ptr, size)) { + fprintf(stderr, "InnoDB: munmap(%p, %lu) failed;" + " errno %lu\n", + ptr, (ulong) size, (ulong) errno); + } else { + os_fast_mutex_lock(&ut_list_mutex); + ut_a(ut_total_allocated_memory >= size); + ut_total_allocated_memory -= size; + os_fast_mutex_unlock(&ut_list_mutex); + UNIV_MEM_FREE(ptr, size); + } +#endif +} + +/******************************************************************** +Sets the priority boost for threads released from waiting within the current +process. */ +UNIV_INTERN +void +os_process_set_priority_boost( +/*==========================*/ + ibool do_boost) /* in: TRUE if priority boost should be done, + FALSE if not */ +{ +#ifdef __WIN__ + ibool no_boost; + + if (do_boost) { + no_boost = FALSE; + } else { + no_boost = TRUE; + } + +#if TRUE != 1 +# error "TRUE != 1" +#endif + + /* Does not do anything currently! + SetProcessPriorityBoost(GetCurrentProcess(), no_boost); + */ + fputs("Warning: process priority boost setting" + " currently not functional!\n", + stderr); +#else + UT_NOT_USED(do_boost); +#endif +} diff --git a/storage/xtradb/os/os0sync.c b/storage/xtradb/os/os0sync.c new file mode 100644 index 00000000000..78ff74059f8 --- /dev/null +++ b/storage/xtradb/os/os0sync.c @@ -0,0 +1,769 @@ +/***************************************************************************** + +Copyright (c) 1995, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/****************************************************** +The interface to the operating system +synchronization primitives. + +Created 9/6/1995 Heikki Tuuri +*******************************************************/ + +#include "os0sync.h" +#ifdef UNIV_NONINL +#include "os0sync.ic" +#endif + +#ifdef __WIN__ +#include <windows.h> +#endif + +#include "ut0mem.h" +#include "srv0start.h" + +/* Type definition for an operating system mutex struct */ +struct os_mutex_struct{ + os_event_t event; /* Used by sync0arr.c for queing threads */ + void* handle; /* OS handle to mutex */ + ulint count; /* we use this counter to check + that the same thread does not + recursively lock the mutex: we + do not assume that the OS mutex + supports recursive locking, though + NT seems to do that */ + UT_LIST_NODE_T(os_mutex_str_t) os_mutex_list; + /* list of all 'slow' OS mutexes created */ +}; + +/* Mutex protecting counts and the lists of OS mutexes and events */ +UNIV_INTERN os_mutex_t os_sync_mutex; +static ibool os_sync_mutex_inited = FALSE; +static ibool os_sync_free_called = FALSE; + +/* This is incremented by 1 in os_thread_create and decremented by 1 in +os_thread_exit */ +UNIV_INTERN ulint os_thread_count = 0; + +/* The list of all events created */ +static UT_LIST_BASE_NODE_T(os_event_struct_t) os_event_list; + +/* The list of all OS 'slow' mutexes */ +static UT_LIST_BASE_NODE_T(os_mutex_str_t) os_mutex_list; + +UNIV_INTERN ulint os_event_count = 0; +UNIV_INTERN ulint os_mutex_count = 0; +UNIV_INTERN ulint os_fast_mutex_count = 0; + +/* Because a mutex is embedded inside an event and there is an +event embedded inside a mutex, on free, this generates a recursive call. +This version of the free event function doesn't acquire the global lock */ +static void os_event_free_internal(os_event_t event); + +/************************************************************* +Initializes global event and OS 'slow' mutex lists. */ +UNIV_INTERN +void +os_sync_init(void) +/*==============*/ +{ + UT_LIST_INIT(os_event_list); + UT_LIST_INIT(os_mutex_list); + + os_sync_mutex = os_mutex_create(NULL); + + os_sync_mutex_inited = TRUE; +} + +/************************************************************* +Frees created events and OS 'slow' mutexes. */ +UNIV_INTERN +void +os_sync_free(void) +/*==============*/ +{ + os_event_t event; + os_mutex_t mutex; + + os_sync_free_called = TRUE; + event = UT_LIST_GET_FIRST(os_event_list); + + while (event) { + + os_event_free(event); + + event = UT_LIST_GET_FIRST(os_event_list); + } + + mutex = UT_LIST_GET_FIRST(os_mutex_list); + + while (mutex) { + if (mutex == os_sync_mutex) { + /* Set the flag to FALSE so that we do not try to + reserve os_sync_mutex any more in remaining freeing + operations in shutdown */ + os_sync_mutex_inited = FALSE; + } + + os_mutex_free(mutex); + + mutex = UT_LIST_GET_FIRST(os_mutex_list); + } + os_sync_free_called = FALSE; +} + +/************************************************************* +Creates an event semaphore, i.e., a semaphore which may just have two +states: signaled and nonsignaled. The created event is manual reset: it +must be reset explicitly by calling sync_os_reset_event. */ +UNIV_INTERN +os_event_t +os_event_create( +/*============*/ + /* out: the event handle */ + const char* name) /* in: the name of the event, if NULL + the event is created without a name */ +{ +#ifdef __WIN__ + os_event_t event; + + event = ut_malloc(sizeof(struct os_event_struct)); + + event->handle = CreateEvent(NULL, /* No security attributes */ + TRUE, /* Manual reset */ + FALSE, /* Initial state nonsignaled */ + (LPCTSTR) name); + if (!event->handle) { + fprintf(stderr, + "InnoDB: Could not create a Windows event semaphore;" + " Windows error %lu\n", + (ulong) GetLastError()); + } +#else /* Unix */ + os_event_t event; + + UT_NOT_USED(name); + + event = ut_malloc(sizeof(struct os_event_struct)); + + os_fast_mutex_init(&(event->os_mutex)); + +#if defined(UNIV_HOTBACKUP) && defined(UNIV_HPUX10) + ut_a(0 == pthread_cond_init(&(event->cond_var), + pthread_condattr_default)); +#else + ut_a(0 == pthread_cond_init(&(event->cond_var), NULL)); +#endif + event->is_set = FALSE; + + /* We return this value in os_event_reset(), which can then be + be used to pass to the os_event_wait_low(). The value of zero + is reserved in os_event_wait_low() for the case when the + caller does not want to pass any signal_count value. To + distinguish between the two cases we initialize signal_count + to 1 here. */ + event->signal_count = 1; +#endif /* __WIN__ */ + + /* The os_sync_mutex can be NULL because during startup an event + can be created [ because it's embedded in the mutex/rwlock ] before + this module has been initialized */ + if (os_sync_mutex != NULL) { + os_mutex_enter(os_sync_mutex); + } + + /* Put to the list of events */ + UT_LIST_ADD_FIRST(os_event_list, os_event_list, event); + + os_event_count++; + + if (os_sync_mutex != NULL) { + os_mutex_exit(os_sync_mutex); + } + + return(event); +} + +#ifdef __WIN__ +/************************************************************* +Creates an auto-reset event semaphore, i.e., an event which is automatically +reset when a single thread is released. Works only in Windows. */ +UNIV_INTERN +os_event_t +os_event_create_auto( +/*=================*/ + /* out: the event handle */ + const char* name) /* in: the name of the event, if NULL + the event is created without a name */ +{ + os_event_t event; + + event = ut_malloc(sizeof(struct os_event_struct)); + + event->handle = CreateEvent(NULL, /* No security attributes */ + FALSE, /* Auto-reset */ + FALSE, /* Initial state nonsignaled */ + (LPCTSTR) name); + + if (!event->handle) { + fprintf(stderr, + "InnoDB: Could not create a Windows auto" + " event semaphore; Windows error %lu\n", + (ulong) GetLastError()); + } + + /* Put to the list of events */ + os_mutex_enter(os_sync_mutex); + + UT_LIST_ADD_FIRST(os_event_list, os_event_list, event); + + os_event_count++; + + os_mutex_exit(os_sync_mutex); + + return(event); +} +#endif + +/************************************************************** +Sets an event semaphore to the signaled state: lets waiting threads +proceed. */ +UNIV_INTERN +void +os_event_set( +/*=========*/ + os_event_t event) /* in: event to set */ +{ +#ifdef __WIN__ + ut_a(event); + ut_a(SetEvent(event->handle)); +#else + ut_a(event); + + os_fast_mutex_lock(&(event->os_mutex)); + + if (event->is_set) { + /* Do nothing */ + } else { + event->is_set = TRUE; + event->signal_count += 1; + ut_a(0 == pthread_cond_broadcast(&(event->cond_var))); + } + + os_fast_mutex_unlock(&(event->os_mutex)); +#endif +} + +/************************************************************** +Resets an event semaphore to the nonsignaled state. Waiting threads will +stop to wait for the event. +The return value should be passed to os_even_wait_low() if it is desired +that this thread should not wait in case of an intervening call to +os_event_set() between this os_event_reset() and the +os_event_wait_low() call. See comments for os_event_wait_low(). */ +UNIV_INTERN +ib_int64_t +os_event_reset( +/*===========*/ + /* out: current signal_count. */ + os_event_t event) /* in: event to reset */ +{ + ib_int64_t ret = 0; + +#ifdef __WIN__ + ut_a(event); + + ut_a(ResetEvent(event->handle)); +#else + ut_a(event); + + os_fast_mutex_lock(&(event->os_mutex)); + + if (!event->is_set) { + /* Do nothing */ + } else { + event->is_set = FALSE; + } + ret = event->signal_count; + + os_fast_mutex_unlock(&(event->os_mutex)); +#endif + return(ret); +} + +/************************************************************** +Frees an event object, without acquiring the global lock. */ +static +void +os_event_free_internal( +/*===================*/ + os_event_t event) /* in: event to free */ +{ +#ifdef __WIN__ + ut_a(event); + + ut_a(CloseHandle(event->handle)); +#else + ut_a(event); + + /* This is to avoid freeing the mutex twice */ + os_fast_mutex_free(&(event->os_mutex)); + + ut_a(0 == pthread_cond_destroy(&(event->cond_var))); +#endif + /* Remove from the list of events */ + + UT_LIST_REMOVE(os_event_list, os_event_list, event); + + os_event_count--; + + ut_free(event); +} + +/************************************************************** +Frees an event object. */ +UNIV_INTERN +void +os_event_free( +/*==========*/ + os_event_t event) /* in: event to free */ + +{ +#ifdef __WIN__ + ut_a(event); + + ut_a(CloseHandle(event->handle)); +#else + ut_a(event); + + os_fast_mutex_free(&(event->os_mutex)); + ut_a(0 == pthread_cond_destroy(&(event->cond_var))); +#endif + /* Remove from the list of events */ + + os_mutex_enter(os_sync_mutex); + + UT_LIST_REMOVE(os_event_list, os_event_list, event); + + os_event_count--; + + os_mutex_exit(os_sync_mutex); + + ut_free(event); +} + +/************************************************************** +Waits for an event object until it is in the signaled state. If +srv_shutdown_state == SRV_SHUTDOWN_EXIT_THREADS this also exits the +waiting thread when the event becomes signaled (or immediately if the +event is already in the signaled state). + +Typically, if the event has been signalled after the os_event_reset() +we'll return immediately because event->is_set == TRUE. +There are, however, situations (e.g.: sync_array code) where we may +lose this information. For example: + +thread A calls os_event_reset() +thread B calls os_event_set() [event->is_set == TRUE] +thread C calls os_event_reset() [event->is_set == FALSE] +thread A calls os_event_wait() [infinite wait!] +thread C calls os_event_wait() [infinite wait!] + +Where such a scenario is possible, to avoid infinite wait, the +value returned by os_event_reset() should be passed in as +reset_sig_count. */ +UNIV_INTERN +void +os_event_wait_low( +/*==============*/ + os_event_t event, /* in: event to wait */ + ib_int64_t reset_sig_count)/* in: zero or the value + returned by previous call of + os_event_reset(). */ +{ +#ifdef __WIN__ + DWORD err; + + ut_a(event); + + UT_NOT_USED(reset_sig_count); + + /* Specify an infinite time limit for waiting */ + err = WaitForSingleObject(event->handle, INFINITE); + + ut_a(err == WAIT_OBJECT_0); + + if (srv_shutdown_state == SRV_SHUTDOWN_EXIT_THREADS) { + os_thread_exit(NULL); + } +#else + ib_int64_t old_signal_count; + + os_fast_mutex_lock(&(event->os_mutex)); + + if (reset_sig_count) { + old_signal_count = reset_sig_count; + } else { + old_signal_count = event->signal_count; + } + + for (;;) { + if (event->is_set == TRUE + || event->signal_count != old_signal_count) { + + os_fast_mutex_unlock(&(event->os_mutex)); + + if (srv_shutdown_state == SRV_SHUTDOWN_EXIT_THREADS) { + + os_thread_exit(NULL); + } + /* Ok, we may return */ + + return; + } + + pthread_cond_wait(&(event->cond_var), &(event->os_mutex)); + + /* Solaris manual said that spurious wakeups may occur: we + have to check if the event really has been signaled after + we came here to wait */ + } +#endif +} + +/************************************************************** +Waits for an event object until it is in the signaled state or +a timeout is exceeded. In Unix the timeout is always infinite. */ +UNIV_INTERN +ulint +os_event_wait_time( +/*===============*/ + /* out: 0 if success, OS_SYNC_TIME_EXCEEDED if + timeout was exceeded */ + os_event_t event, /* in: event to wait */ + ulint time) /* in: timeout in microseconds, or + OS_SYNC_INFINITE_TIME */ +{ +#ifdef __WIN__ + DWORD err; + + ut_a(event); + + if (time != OS_SYNC_INFINITE_TIME) { + err = WaitForSingleObject(event->handle, (DWORD) time / 1000); + } else { + err = WaitForSingleObject(event->handle, INFINITE); + } + + if (err == WAIT_OBJECT_0) { + + return(0); + } else if (err == WAIT_TIMEOUT) { + + return(OS_SYNC_TIME_EXCEEDED); + } else { + ut_error; + return(1000000); /* dummy value to eliminate compiler warn. */ + } +#else + UT_NOT_USED(time); + + /* In Posix this is just an ordinary, infinite wait */ + + os_event_wait(event); + + return(0); +#endif +} + +#ifdef __WIN__ +/************************************************************** +Waits for any event in an OS native event array. Returns if even a single +one is signaled or becomes signaled. */ +UNIV_INTERN +ulint +os_event_wait_multiple( +/*===================*/ + /* out: index of the event + which was signaled */ + ulint n, /* in: number of events in the + array */ + os_native_event_t* native_event_array) + /* in: pointer to an array of event + handles */ +{ + DWORD index; + + ut_a(native_event_array); + ut_a(n > 0); + + index = WaitForMultipleObjects((DWORD) n, native_event_array, + FALSE, /* Wait for any 1 event */ + INFINITE); /* Infinite wait time + limit */ + ut_a(index >= WAIT_OBJECT_0); /* NOTE: Pointless comparision */ + ut_a(index < WAIT_OBJECT_0 + n); + + if (srv_shutdown_state == SRV_SHUTDOWN_EXIT_THREADS) { + os_thread_exit(NULL); + } + + return(index - WAIT_OBJECT_0); +} +#endif + +/************************************************************* +Creates an operating system mutex semaphore. Because these are slow, the +mutex semaphore of InnoDB itself (mutex_t) should be used where possible. */ +UNIV_INTERN +os_mutex_t +os_mutex_create( +/*============*/ + /* out: the mutex handle */ + const char* name) /* in: the name of the mutex, if NULL + the mutex is created without a name */ +{ +#ifdef __WIN__ + HANDLE mutex; + os_mutex_t mutex_str; + + mutex = CreateMutex(NULL, /* No security attributes */ + FALSE, /* Initial state: no owner */ + (LPCTSTR) name); + ut_a(mutex); +#else + os_fast_mutex_t* mutex; + os_mutex_t mutex_str; + + UT_NOT_USED(name); + + mutex = ut_malloc(sizeof(os_fast_mutex_t)); + + os_fast_mutex_init(mutex); +#endif + mutex_str = ut_malloc(sizeof(os_mutex_str_t)); + + mutex_str->handle = mutex; + mutex_str->count = 0; + mutex_str->event = os_event_create(NULL); + + if (UNIV_LIKELY(os_sync_mutex_inited)) { + /* When creating os_sync_mutex itself we cannot reserve it */ + os_mutex_enter(os_sync_mutex); + } + + UT_LIST_ADD_FIRST(os_mutex_list, os_mutex_list, mutex_str); + + os_mutex_count++; + + if (UNIV_LIKELY(os_sync_mutex_inited)) { + os_mutex_exit(os_sync_mutex); + } + + return(mutex_str); +} + +/************************************************************** +Acquires ownership of a mutex semaphore. */ +UNIV_INTERN +void +os_mutex_enter( +/*===========*/ + os_mutex_t mutex) /* in: mutex to acquire */ +{ +#ifdef __WIN__ + DWORD err; + + ut_a(mutex); + + /* Specify infinite time limit for waiting */ + err = WaitForSingleObject(mutex->handle, INFINITE); + + ut_a(err == WAIT_OBJECT_0); + + (mutex->count)++; + ut_a(mutex->count == 1); +#else + os_fast_mutex_lock(mutex->handle); + + (mutex->count)++; + + ut_a(mutex->count == 1); +#endif +} + +/************************************************************** +Releases ownership of a mutex. */ +UNIV_INTERN +void +os_mutex_exit( +/*==========*/ + os_mutex_t mutex) /* in: mutex to release */ +{ + ut_a(mutex); + + ut_a(mutex->count == 1); + + (mutex->count)--; +#ifdef __WIN__ + ut_a(ReleaseMutex(mutex->handle)); +#else + os_fast_mutex_unlock(mutex->handle); +#endif +} + +/************************************************************** +Frees a mutex object. */ +UNIV_INTERN +void +os_mutex_free( +/*==========*/ + os_mutex_t mutex) /* in: mutex to free */ +{ + ut_a(mutex); + + if (UNIV_LIKELY(!os_sync_free_called)) { + os_event_free_internal(mutex->event); + } + + if (UNIV_LIKELY(os_sync_mutex_inited)) { + os_mutex_enter(os_sync_mutex); + } + + UT_LIST_REMOVE(os_mutex_list, os_mutex_list, mutex); + + os_mutex_count--; + + if (UNIV_LIKELY(os_sync_mutex_inited)) { + os_mutex_exit(os_sync_mutex); + } + +#ifdef __WIN__ + ut_a(CloseHandle(mutex->handle)); + + ut_free(mutex); +#else + os_fast_mutex_free(mutex->handle); + ut_free(mutex->handle); + ut_free(mutex); +#endif +} + +/************************************************************* +Initializes an operating system fast mutex semaphore. */ +UNIV_INTERN +void +os_fast_mutex_init( +/*===============*/ + os_fast_mutex_t* fast_mutex) /* in: fast mutex */ +{ +#ifdef __WIN__ + ut_a(fast_mutex); + + InitializeCriticalSection((LPCRITICAL_SECTION) fast_mutex); +#else +#if defined(UNIV_HOTBACKUP) && defined(UNIV_HPUX10) + ut_a(0 == pthread_mutex_init(fast_mutex, pthread_mutexattr_default)); +#else + ut_a(0 == pthread_mutex_init(fast_mutex, MY_MUTEX_INIT_FAST)); +#endif +#endif + if (UNIV_LIKELY(os_sync_mutex_inited)) { + /* When creating os_sync_mutex itself (in Unix) we cannot + reserve it */ + + os_mutex_enter(os_sync_mutex); + } + + os_fast_mutex_count++; + + if (UNIV_LIKELY(os_sync_mutex_inited)) { + os_mutex_exit(os_sync_mutex); + } +} + +/************************************************************** +Acquires ownership of a fast mutex. */ +UNIV_INTERN +void +os_fast_mutex_lock( +/*===============*/ + os_fast_mutex_t* fast_mutex) /* in: mutex to acquire */ +{ +#ifdef __WIN__ + EnterCriticalSection((LPCRITICAL_SECTION) fast_mutex); +#else + pthread_mutex_lock(fast_mutex); +#endif +} + +/************************************************************** +Releases ownership of a fast mutex. */ +UNIV_INTERN +void +os_fast_mutex_unlock( +/*=================*/ + os_fast_mutex_t* fast_mutex) /* in: mutex to release */ +{ +#ifdef __WIN__ + LeaveCriticalSection(fast_mutex); +#else + pthread_mutex_unlock(fast_mutex); +#endif +} + +/************************************************************** +Frees a mutex object. */ +UNIV_INTERN +void +os_fast_mutex_free( +/*===============*/ + os_fast_mutex_t* fast_mutex) /* in: mutex to free */ +{ +#ifdef __WIN__ + ut_a(fast_mutex); + + DeleteCriticalSection((LPCRITICAL_SECTION) fast_mutex); +#else + int ret; + + ret = pthread_mutex_destroy(fast_mutex); + + if (UNIV_UNLIKELY(ret != 0)) { + ut_print_timestamp(stderr); + fprintf(stderr, + " InnoDB: error: return value %lu when calling\n" + "InnoDB: pthread_mutex_destroy().\n", (ulint)ret); + fprintf(stderr, + "InnoDB: Byte contents of the pthread mutex at %p:\n", + (void*) fast_mutex); + ut_print_buf(stderr, fast_mutex, sizeof(os_fast_mutex_t)); + putc('\n', stderr); + } +#endif + if (UNIV_LIKELY(os_sync_mutex_inited)) { + /* When freeing the last mutexes, we have + already freed os_sync_mutex */ + + os_mutex_enter(os_sync_mutex); + } + + os_fast_mutex_count--; + + if (UNIV_LIKELY(os_sync_mutex_inited)) { + os_mutex_exit(os_sync_mutex); + } +} diff --git a/storage/xtradb/os/os0thread.c b/storage/xtradb/os/os0thread.c new file mode 100644 index 00000000000..7d0a57ae17c --- /dev/null +++ b/storage/xtradb/os/os0thread.c @@ -0,0 +1,365 @@ +/***************************************************************************** + +Copyright (c) 1995, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/****************************************************** +The interface to the operating system thread control primitives + +Created 9/8/1995 Heikki Tuuri +*******************************************************/ + +#include "os0thread.h" +#ifdef UNIV_NONINL +#include "os0thread.ic" +#endif + +#ifdef __WIN__ +#include <windows.h> +#endif + +#include "srv0srv.h" +#include "os0sync.h" + +/******************************************************************* +Compares two thread ids for equality. */ +UNIV_INTERN +ibool +os_thread_eq( +/*=========*/ + /* out: TRUE if equal */ + os_thread_id_t a, /* in: OS thread or thread id */ + os_thread_id_t b) /* in: OS thread or thread id */ +{ +#ifdef __WIN__ + if (a == b) { + return(TRUE); + } + + return(FALSE); +#else + if (pthread_equal(a, b)) { + return(TRUE); + } + + return(FALSE); +#endif +} + +/******************************************************************** +Converts an OS thread id to a ulint. It is NOT guaranteed that the ulint is +unique for the thread though! */ +UNIV_INTERN +ulint +os_thread_pf( +/*=========*/ + os_thread_id_t a) +{ +#ifdef UNIV_HPUX10 + /* In HP-UX-10.20 a pthread_t is a struct of 3 fields: field1, field2, + field3. We do not know if field1 determines the thread uniquely. */ + + return((ulint)(a.field1)); +#else + return((ulint)a); +#endif +} + +/********************************************************************* +Returns the thread identifier of current thread. Currently the thread +identifier in Unix is the thread handle itself. Note that in HP-UX +pthread_t is a struct of 3 fields. */ +UNIV_INTERN +os_thread_id_t +os_thread_get_curr_id(void) +/*=======================*/ +{ +#ifdef __WIN__ + return(GetCurrentThreadId()); +#else + return(pthread_self()); +#endif +} + +/******************************************************************** +Creates a new thread of execution. The execution starts from +the function given. The start function takes a void* parameter +and returns an ulint. */ +UNIV_INTERN +os_thread_t +os_thread_create( +/*=============*/ + /* out: handle to the thread */ +#ifndef __WIN__ + os_posix_f_t start_f, +#else + ulint (*start_f)(void*), /* in: pointer to function + from which to start */ +#endif + void* arg, /* in: argument to start + function */ + os_thread_id_t* thread_id) /* out: id of the created + thread, or NULL */ +{ +#ifdef __WIN__ + os_thread_t thread; + DWORD win_thread_id; + + os_mutex_enter(os_sync_mutex); + os_thread_count++; + os_mutex_exit(os_sync_mutex); + + thread = CreateThread(NULL, /* no security attributes */ + 0, /* default size stack */ + (LPTHREAD_START_ROUTINE)start_f, + arg, + 0, /* thread runs immediately */ + &win_thread_id); + + if (srv_set_thread_priorities) { + + /* Set created thread priority the same as a normal query + in MYSQL: we try to prevent starvation of threads by + assigning same priority QUERY_PRIOR to all */ + + ut_a(SetThreadPriority(thread, srv_query_thread_priority)); + } + + if (thread_id) { + *thread_id = win_thread_id; + } + + return(thread); +#else + int ret; + os_thread_t pthread; + pthread_attr_t attr; + +#if !(defined(UNIV_HOTBACKUP) && defined(UNIV_HPUX10)) + pthread_attr_init(&attr); +#endif + +#ifdef UNIV_AIX + /* We must make sure a thread stack is at least 32 kB, otherwise + InnoDB might crash; we do not know if the default stack size on + AIX is always big enough. An empirical test on AIX-4.3 suggested + the size was 96 kB, though. */ + + ret = pthread_attr_setstacksize(&attr, + (size_t)(PTHREAD_STACK_MIN + + 32 * 1024)); + if (ret) { + fprintf(stderr, + "InnoDB: Error: pthread_attr_setstacksize" + " returned %d\n", ret); + exit(1); + } +#endif +#ifdef __NETWARE__ + ret = pthread_attr_setstacksize(&attr, + (size_t) NW_THD_STACKSIZE); + if (ret) { + fprintf(stderr, + "InnoDB: Error: pthread_attr_setstacksize" + " returned %d\n", ret); + exit(1); + } +#endif + os_mutex_enter(os_sync_mutex); + os_thread_count++; + os_mutex_exit(os_sync_mutex); + +#if defined(UNIV_HOTBACKUP) && defined(UNIV_HPUX10) + ret = pthread_create(&pthread, pthread_attr_default, start_f, arg); +#else + ret = pthread_create(&pthread, &attr, start_f, arg); +#endif + if (ret) { + fprintf(stderr, + "InnoDB: Error: pthread_create returned %d\n", ret); + exit(1); + } + +#if !(defined(UNIV_HOTBACKUP) && defined(UNIV_HPUX10)) + pthread_attr_destroy(&attr); +#endif + if (srv_set_thread_priorities) { + + my_pthread_setprio(pthread, srv_query_thread_priority); + } + + if (thread_id) { + *thread_id = pthread; + } + + return(pthread); +#endif +} + +/********************************************************************* +Exits the current thread. */ +UNIV_INTERN +void +os_thread_exit( +/*===========*/ + void* exit_value) /* in: exit value; in Windows this void* + is cast as a DWORD */ +{ +#ifdef UNIV_DEBUG_THREAD_CREATION + fprintf(stderr, "Thread exits, id %lu\n", + os_thread_pf(os_thread_get_curr_id())); +#endif + os_mutex_enter(os_sync_mutex); + os_thread_count--; + os_mutex_exit(os_sync_mutex); + +#ifdef __WIN__ + ExitThread((DWORD)exit_value); +#else + pthread_exit(exit_value); +#endif +} + +/********************************************************************* +Returns handle to the current thread. */ +UNIV_INTERN +os_thread_t +os_thread_get_curr(void) +/*====================*/ +{ +#ifdef __WIN__ + return(GetCurrentThread()); +#else + return(pthread_self()); +#endif +} + +/********************************************************************* +Advises the os to give up remainder of the thread's time slice. */ +UNIV_INTERN +void +os_thread_yield(void) +/*=================*/ +{ +#if defined(__WIN__) + Sleep(0); +#elif (defined(HAVE_SCHED_YIELD) && defined(HAVE_SCHED_H)) + sched_yield(); +#elif defined(HAVE_PTHREAD_YIELD_ZERO_ARG) + pthread_yield(); +#elif defined(HAVE_PTHREAD_YIELD_ONE_ARG) + pthread_yield(0); +#else + os_thread_sleep(0); +#endif +} + +/********************************************************************* +The thread sleeps at least the time given in microseconds. */ +UNIV_INTERN +void +os_thread_sleep( +/*============*/ + ulint tm) /* in: time in microseconds */ +{ +#ifdef __WIN__ + Sleep((DWORD) tm / 1000); +#elif defined(__NETWARE__) + delay(tm / 1000); +#else + struct timeval t; + + t.tv_sec = tm / 1000000; + t.tv_usec = tm % 1000000; + + select(0, NULL, NULL, NULL, &t); +#endif +} + +/********************************************************************** +Sets a thread priority. */ +UNIV_INTERN +void +os_thread_set_priority( +/*===================*/ + os_thread_t handle, /* in: OS handle to the thread */ + ulint pri) /* in: priority */ +{ +#ifdef __WIN__ + int os_pri; + + if (pri == OS_THREAD_PRIORITY_BACKGROUND) { + os_pri = THREAD_PRIORITY_BELOW_NORMAL; + } else if (pri == OS_THREAD_PRIORITY_NORMAL) { + os_pri = THREAD_PRIORITY_NORMAL; + } else if (pri == OS_THREAD_PRIORITY_ABOVE_NORMAL) { + os_pri = THREAD_PRIORITY_HIGHEST; + } else { + ut_error; + } + + ut_a(SetThreadPriority(handle, os_pri)); +#else + UT_NOT_USED(handle); + UT_NOT_USED(pri); +#endif +} + +/********************************************************************** +Gets a thread priority. */ +UNIV_INTERN +ulint +os_thread_get_priority( +/*===================*/ + /* out: priority */ + os_thread_t handle __attribute__((unused))) + /* in: OS handle to the thread */ +{ +#ifdef __WIN__ + int os_pri; + ulint pri; + + os_pri = GetThreadPriority(handle); + + if (os_pri == THREAD_PRIORITY_BELOW_NORMAL) { + pri = OS_THREAD_PRIORITY_BACKGROUND; + } else if (os_pri == THREAD_PRIORITY_NORMAL) { + pri = OS_THREAD_PRIORITY_NORMAL; + } else if (os_pri == THREAD_PRIORITY_HIGHEST) { + pri = OS_THREAD_PRIORITY_ABOVE_NORMAL; + } else { + ut_error; + } + + return(pri); +#else + return(0); +#endif +} + +/********************************************************************** +Gets the last operating system error code for the calling thread. */ +UNIV_INTERN +ulint +os_thread_get_last_error(void) +/*==========================*/ +{ +#ifdef __WIN__ + return(GetLastError()); +#else + return(0); +#endif +} diff --git a/storage/xtradb/page/page0cur.c b/storage/xtradb/page/page0cur.c new file mode 100644 index 00000000000..e810756c1e4 --- /dev/null +++ b/storage/xtradb/page/page0cur.c @@ -0,0 +1,1922 @@ +/***************************************************************************** + +Copyright (c) 1994, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/************************************************************************ +The page cursor + +Created 10/4/1994 Heikki Tuuri +*************************************************************************/ + +#include "page0cur.h" +#ifdef UNIV_NONINL +#include "page0cur.ic" +#endif + +#include "page0zip.h" +#include "mtr0log.h" +#include "log0recv.h" +#include "rem0cmp.h" + +static ulint page_rnd = 976722341; + +#ifdef PAGE_CUR_ADAPT +# ifdef UNIV_SEARCH_PERF_STAT +static ulint page_cur_short_succ = 0; +# endif /* UNIV_SEARCH_PERF_STAT */ + +/******************************************************************** +Tries a search shortcut based on the last insert. */ +UNIV_INLINE +ibool +page_cur_try_search_shortcut( +/*=========================*/ + /* out: TRUE on success */ + const buf_block_t* block, /* in: index page */ + const dict_index_t* index, /* in: record descriptor */ + const dtuple_t* tuple, /* in: data tuple */ + ulint* iup_matched_fields, + /* in/out: already matched + fields in upper limit record */ + ulint* iup_matched_bytes, + /* in/out: already matched + bytes in a field not yet + completely matched */ + ulint* ilow_matched_fields, + /* in/out: already matched + fields in lower limit record */ + ulint* ilow_matched_bytes, + /* in/out: already matched + bytes in a field not yet + completely matched */ + page_cur_t* cursor) /* out: page cursor */ +{ + const rec_t* rec; + const rec_t* next_rec; + ulint low_match; + ulint low_bytes; + ulint up_match; + ulint up_bytes; +#ifdef UNIV_SEARCH_DEBUG + page_cur_t cursor2; +#endif + ibool success = FALSE; + const page_t* page = buf_block_get_frame(block); + mem_heap_t* heap = NULL; + ulint offsets_[REC_OFFS_NORMAL_SIZE]; + ulint* offsets = offsets_; + rec_offs_init(offsets_); + + ut_ad(dtuple_check_typed(tuple)); + + rec = page_header_get_ptr(page, PAGE_LAST_INSERT); + offsets = rec_get_offsets(rec, index, offsets, + dtuple_get_n_fields(tuple), &heap); + + ut_ad(rec); + ut_ad(page_rec_is_user_rec(rec)); + + ut_pair_min(&low_match, &low_bytes, + *ilow_matched_fields, *ilow_matched_bytes, + *iup_matched_fields, *iup_matched_bytes); + + up_match = low_match; + up_bytes = low_bytes; + + if (page_cmp_dtuple_rec_with_match(tuple, rec, offsets, + &low_match, &low_bytes) < 0) { + goto exit_func; + } + + next_rec = page_rec_get_next_const(rec); + offsets = rec_get_offsets(next_rec, index, offsets, + dtuple_get_n_fields(tuple), &heap); + + if (page_cmp_dtuple_rec_with_match(tuple, next_rec, offsets, + &up_match, &up_bytes) >= 0) { + goto exit_func; + } + + page_cur_position(rec, block, cursor); + +#ifdef UNIV_SEARCH_DEBUG + page_cur_search_with_match(block, index, tuple, PAGE_CUR_DBG, + iup_matched_fields, + iup_matched_bytes, + ilow_matched_fields, + ilow_matched_bytes, + &cursor2); + ut_a(cursor2.rec == cursor->rec); + + if (!page_rec_is_supremum(next_rec)) { + + ut_a(*iup_matched_fields == up_match); + ut_a(*iup_matched_bytes == up_bytes); + } + + ut_a(*ilow_matched_fields == low_match); + ut_a(*ilow_matched_bytes == low_bytes); +#endif + if (!page_rec_is_supremum(next_rec)) { + + *iup_matched_fields = up_match; + *iup_matched_bytes = up_bytes; + } + + *ilow_matched_fields = low_match; + *ilow_matched_bytes = low_bytes; + +#ifdef UNIV_SEARCH_PERF_STAT + page_cur_short_succ++; +#endif + success = TRUE; +exit_func: + if (UNIV_LIKELY_NULL(heap)) { + mem_heap_free(heap); + } + return(success); +} + +#endif + +#ifdef PAGE_CUR_LE_OR_EXTENDS +/******************************************************************** +Checks if the nth field in a record is a character type field which extends +the nth field in tuple, i.e., the field is longer or equal in length and has +common first characters. */ +static +ibool +page_cur_rec_field_extends( +/*=======================*/ + /* out: TRUE if rec field + extends tuple field */ + const dtuple_t* tuple, /* in: data tuple */ + const rec_t* rec, /* in: record */ + const ulint* offsets,/* in: array returned by rec_get_offsets() */ + ulint n) /* in: compare nth field */ +{ + const dtype_t* type; + const dfield_t* dfield; + const byte* rec_f; + ulint rec_f_len; + + ut_ad(rec_offs_validate(rec, NULL, offsets)); + dfield = dtuple_get_nth_field(tuple, n); + + type = dfield_get_type(dfield); + + rec_f = rec_get_nth_field(rec, offsets, n, &rec_f_len); + + if (type->mtype == DATA_VARCHAR + || type->mtype == DATA_CHAR + || type->mtype == DATA_FIXBINARY + || type->mtype == DATA_BINARY + || type->mtype == DATA_BLOB + || type->mtype == DATA_VARMYSQL + || type->mtype == DATA_MYSQL) { + + if (dfield_get_len(dfield) != UNIV_SQL_NULL + && rec_f_len != UNIV_SQL_NULL + && rec_f_len >= dfield_get_len(dfield) + && !cmp_data_data_slow(type->mtype, type->prtype, + dfield_get_data(dfield), + dfield_get_len(dfield), + rec_f, dfield_get_len(dfield))) { + + return(TRUE); + } + } + + return(FALSE); +} +#endif /* PAGE_CUR_LE_OR_EXTENDS */ + +/******************************************************************** +Searches the right position for a page cursor. */ +UNIV_INTERN +void +page_cur_search_with_match( +/*=======================*/ + const buf_block_t* block, /* in: buffer block */ + const dict_index_t* index, /* in: record descriptor */ + const dtuple_t* tuple, /* in: data tuple */ + ulint mode, /* in: PAGE_CUR_L, + PAGE_CUR_LE, PAGE_CUR_G, or + PAGE_CUR_GE */ + ulint* iup_matched_fields, + /* in/out: already matched + fields in upper limit record */ + ulint* iup_matched_bytes, + /* in/out: already matched + bytes in a field not yet + completely matched */ + ulint* ilow_matched_fields, + /* in/out: already matched + fields in lower limit record */ + ulint* ilow_matched_bytes, + /* in/out: already matched + bytes in a field not yet + completely matched */ + page_cur_t* cursor) /* out: page cursor */ +{ + ulint up; + ulint low; + ulint mid; + const page_t* page; + const page_dir_slot_t* slot; + const rec_t* up_rec; + const rec_t* low_rec; + const rec_t* mid_rec; + ulint up_matched_fields; + ulint up_matched_bytes; + ulint low_matched_fields; + ulint low_matched_bytes; + ulint cur_matched_fields; + ulint cur_matched_bytes; + int cmp; +#ifdef UNIV_SEARCH_DEBUG + int dbg_cmp; + ulint dbg_matched_fields; + ulint dbg_matched_bytes; +#endif +#ifdef UNIV_ZIP_DEBUG + const page_zip_des_t* page_zip = buf_block_get_page_zip(block); +#endif /* UNIV_ZIP_DEBUG */ + mem_heap_t* heap = NULL; + ulint offsets_[REC_OFFS_NORMAL_SIZE]; + ulint* offsets = offsets_; + rec_offs_init(offsets_); + + ut_ad(block && tuple && iup_matched_fields && iup_matched_bytes + && ilow_matched_fields && ilow_matched_bytes && cursor); + ut_ad(dtuple_validate(tuple)); +#ifdef UNIV_DEBUG +# ifdef PAGE_CUR_DBG + if (mode != PAGE_CUR_DBG) +# endif /* PAGE_CUR_DBG */ +# ifdef PAGE_CUR_LE_OR_EXTENDS + if (mode != PAGE_CUR_LE_OR_EXTENDS) +# endif /* PAGE_CUR_LE_OR_EXTENDS */ + ut_ad(mode == PAGE_CUR_L || mode == PAGE_CUR_LE + || mode == PAGE_CUR_G || mode == PAGE_CUR_GE); +#endif /* UNIV_DEBUG */ + page = buf_block_get_frame(block); +#ifdef UNIV_ZIP_DEBUG + ut_a(!page_zip || page_zip_validate(page_zip, page)); +#endif /* UNIV_ZIP_DEBUG */ + + page_check_dir(page); + +#ifdef PAGE_CUR_ADAPT + if (page_is_leaf(page) + && (mode == PAGE_CUR_LE) + && (page_header_get_field(page, PAGE_N_DIRECTION) > 3) + && (page_header_get_ptr(page, PAGE_LAST_INSERT)) + && (page_header_get_field(page, PAGE_DIRECTION) == PAGE_RIGHT)) { + + if (page_cur_try_search_shortcut( + block, index, tuple, + iup_matched_fields, iup_matched_bytes, + ilow_matched_fields, ilow_matched_bytes, + cursor)) { + return; + } + } +# ifdef PAGE_CUR_DBG + if (mode == PAGE_CUR_DBG) { + mode = PAGE_CUR_LE; + } +# endif +#endif + + /* The following flag does not work for non-latin1 char sets because + cmp_full_field does not tell how many bytes matched */ +#ifdef PAGE_CUR_LE_OR_EXTENDS + ut_a(mode != PAGE_CUR_LE_OR_EXTENDS); +#endif /* PAGE_CUR_LE_OR_EXTENDS */ + + /* If mode PAGE_CUR_G is specified, we are trying to position the + cursor to answer a query of the form "tuple < X", where tuple is + the input parameter, and X denotes an arbitrary physical record on + the page. We want to position the cursor on the first X which + satisfies the condition. */ + + up_matched_fields = *iup_matched_fields; + up_matched_bytes = *iup_matched_bytes; + low_matched_fields = *ilow_matched_fields; + low_matched_bytes = *ilow_matched_bytes; + + /* Perform binary search. First the search is done through the page + directory, after that as a linear search in the list of records + owned by the upper limit directory slot. */ + + low = 0; + up = page_dir_get_n_slots(page) - 1; + + /* Perform binary search until the lower and upper limit directory + slots come to the distance 1 of each other */ + + while (up - low > 1) { + mid = (low + up) / 2; + slot = page_dir_get_nth_slot(page, mid); + mid_rec = page_dir_slot_get_rec(slot); + + ut_pair_min(&cur_matched_fields, &cur_matched_bytes, + low_matched_fields, low_matched_bytes, + up_matched_fields, up_matched_bytes); + + offsets = rec_get_offsets(mid_rec, index, offsets, + dtuple_get_n_fields_cmp(tuple), + &heap); + + cmp = cmp_dtuple_rec_with_match(tuple, mid_rec, offsets, + &cur_matched_fields, + &cur_matched_bytes); + if (UNIV_LIKELY(cmp > 0)) { +low_slot_match: + low = mid; + low_matched_fields = cur_matched_fields; + low_matched_bytes = cur_matched_bytes; + + } else if (UNIV_EXPECT(cmp, -1)) { +#ifdef PAGE_CUR_LE_OR_EXTENDS + if (mode == PAGE_CUR_LE_OR_EXTENDS + && page_cur_rec_field_extends( + tuple, mid_rec, offsets, + cur_matched_fields)) { + + goto low_slot_match; + } +#endif /* PAGE_CUR_LE_OR_EXTENDS */ +up_slot_match: + up = mid; + up_matched_fields = cur_matched_fields; + up_matched_bytes = cur_matched_bytes; + + } else if (mode == PAGE_CUR_G || mode == PAGE_CUR_LE +#ifdef PAGE_CUR_LE_OR_EXTENDS + || mode == PAGE_CUR_LE_OR_EXTENDS +#endif /* PAGE_CUR_LE_OR_EXTENDS */ + ) { + + goto low_slot_match; + } else { + + goto up_slot_match; + } + } + + slot = page_dir_get_nth_slot(page, low); + low_rec = page_dir_slot_get_rec(slot); + slot = page_dir_get_nth_slot(page, up); + up_rec = page_dir_slot_get_rec(slot); + + /* Perform linear search until the upper and lower records come to + distance 1 of each other. */ + + while (page_rec_get_next_const(low_rec) != up_rec) { + + mid_rec = page_rec_get_next_const(low_rec); + + ut_pair_min(&cur_matched_fields, &cur_matched_bytes, + low_matched_fields, low_matched_bytes, + up_matched_fields, up_matched_bytes); + + offsets = rec_get_offsets(mid_rec, index, offsets, + dtuple_get_n_fields_cmp(tuple), + &heap); + + cmp = cmp_dtuple_rec_with_match(tuple, mid_rec, offsets, + &cur_matched_fields, + &cur_matched_bytes); + if (UNIV_LIKELY(cmp > 0)) { +low_rec_match: + low_rec = mid_rec; + low_matched_fields = cur_matched_fields; + low_matched_bytes = cur_matched_bytes; + + } else if (UNIV_EXPECT(cmp, -1)) { +#ifdef PAGE_CUR_LE_OR_EXTENDS + if (mode == PAGE_CUR_LE_OR_EXTENDS + && page_cur_rec_field_extends( + tuple, mid_rec, offsets, + cur_matched_fields)) { + + goto low_rec_match; + } +#endif /* PAGE_CUR_LE_OR_EXTENDS */ +up_rec_match: + up_rec = mid_rec; + up_matched_fields = cur_matched_fields; + up_matched_bytes = cur_matched_bytes; + } else if (mode == PAGE_CUR_G || mode == PAGE_CUR_LE +#ifdef PAGE_CUR_LE_OR_EXTENDS + || mode == PAGE_CUR_LE_OR_EXTENDS +#endif /* PAGE_CUR_LE_OR_EXTENDS */ + ) { + + goto low_rec_match; + } else { + + goto up_rec_match; + } + } + +#ifdef UNIV_SEARCH_DEBUG + + /* Check that the lower and upper limit records have the + right alphabetical order compared to tuple. */ + dbg_matched_fields = 0; + dbg_matched_bytes = 0; + + offsets = rec_get_offsets(low_rec, index, offsets, + ULINT_UNDEFINED, &heap); + dbg_cmp = page_cmp_dtuple_rec_with_match(tuple, low_rec, offsets, + &dbg_matched_fields, + &dbg_matched_bytes); + if (mode == PAGE_CUR_G) { + ut_a(dbg_cmp >= 0); + } else if (mode == PAGE_CUR_GE) { + ut_a(dbg_cmp == 1); + } else if (mode == PAGE_CUR_L) { + ut_a(dbg_cmp == 1); + } else if (mode == PAGE_CUR_LE) { + ut_a(dbg_cmp >= 0); + } + + if (!page_rec_is_infimum(low_rec)) { + + ut_a(low_matched_fields == dbg_matched_fields); + ut_a(low_matched_bytes == dbg_matched_bytes); + } + + dbg_matched_fields = 0; + dbg_matched_bytes = 0; + + offsets = rec_get_offsets(up_rec, index, offsets, + ULINT_UNDEFINED, &heap); + dbg_cmp = page_cmp_dtuple_rec_with_match(tuple, up_rec, offsets, + &dbg_matched_fields, + &dbg_matched_bytes); + if (mode == PAGE_CUR_G) { + ut_a(dbg_cmp == -1); + } else if (mode == PAGE_CUR_GE) { + ut_a(dbg_cmp <= 0); + } else if (mode == PAGE_CUR_L) { + ut_a(dbg_cmp <= 0); + } else if (mode == PAGE_CUR_LE) { + ut_a(dbg_cmp == -1); + } + + if (!page_rec_is_supremum(up_rec)) { + + ut_a(up_matched_fields == dbg_matched_fields); + ut_a(up_matched_bytes == dbg_matched_bytes); + } +#endif + if (mode <= PAGE_CUR_GE) { + page_cur_position(up_rec, block, cursor); + } else { + page_cur_position(low_rec, block, cursor); + } + + *iup_matched_fields = up_matched_fields; + *iup_matched_bytes = up_matched_bytes; + *ilow_matched_fields = low_matched_fields; + *ilow_matched_bytes = low_matched_bytes; + if (UNIV_LIKELY_NULL(heap)) { + mem_heap_free(heap); + } +} + +/*************************************************************** +Positions a page cursor on a randomly chosen user record on a page. If there +are no user records, sets the cursor on the infimum record. */ +UNIV_INTERN +void +page_cur_open_on_rnd_user_rec( +/*==========================*/ + buf_block_t* block, /* in: page */ + page_cur_t* cursor) /* out: page cursor */ +{ + ulint rnd; + ulint n_recs = page_get_n_recs(buf_block_get_frame(block)); + + page_cur_set_before_first(block, cursor); + + if (UNIV_UNLIKELY(n_recs == 0)) { + + return; + } + + page_rnd += 87584577; + + rnd = page_rnd % n_recs; + + do { + page_cur_move_to_next(cursor); + } while (rnd--); +} + +/*************************************************************** +Writes the log record of a record insert on a page. */ +static +void +page_cur_insert_rec_write_log( +/*==========================*/ + rec_t* insert_rec, /* in: inserted physical record */ + ulint rec_size, /* in: insert_rec size */ + rec_t* cursor_rec, /* in: record the + cursor is pointing to */ + dict_index_t* index, /* in: record descriptor */ + mtr_t* mtr) /* in: mini-transaction handle */ +{ + ulint cur_rec_size; + ulint extra_size; + ulint cur_extra_size; + const byte* ins_ptr; + byte* log_ptr; + const byte* log_end; + ulint i; + + ut_a(rec_size < UNIV_PAGE_SIZE); + ut_ad(page_align(insert_rec) == page_align(cursor_rec)); + ut_ad(!page_rec_is_comp(insert_rec) + == !dict_table_is_comp(index->table)); + + { + mem_heap_t* heap = NULL; + ulint cur_offs_[REC_OFFS_NORMAL_SIZE]; + ulint ins_offs_[REC_OFFS_NORMAL_SIZE]; + + ulint* cur_offs; + ulint* ins_offs; + + rec_offs_init(cur_offs_); + rec_offs_init(ins_offs_); + + cur_offs = rec_get_offsets(cursor_rec, index, cur_offs_, + ULINT_UNDEFINED, &heap); + ins_offs = rec_get_offsets(insert_rec, index, ins_offs_, + ULINT_UNDEFINED, &heap); + + extra_size = rec_offs_extra_size(ins_offs); + cur_extra_size = rec_offs_extra_size(cur_offs); + ut_ad(rec_size == rec_offs_size(ins_offs)); + cur_rec_size = rec_offs_size(cur_offs); + + if (UNIV_LIKELY_NULL(heap)) { + mem_heap_free(heap); + } + } + + ins_ptr = insert_rec - extra_size; + + i = 0; + + if (cur_extra_size == extra_size) { + ulint min_rec_size = ut_min(cur_rec_size, rec_size); + + const byte* cur_ptr = cursor_rec - cur_extra_size; + + /* Find out the first byte in insert_rec which differs from + cursor_rec; skip the bytes in the record info */ + + do { + if (*ins_ptr == *cur_ptr) { + i++; + ins_ptr++; + cur_ptr++; + } else if ((i < extra_size) + && (i >= extra_size + - page_rec_get_base_extra_size + (insert_rec))) { + i = extra_size; + ins_ptr = insert_rec; + cur_ptr = cursor_rec; + } else { + break; + } + } while (i < min_rec_size); + } + + if (mtr_get_log_mode(mtr) != MTR_LOG_SHORT_INSERTS) { + + if (page_rec_is_comp(insert_rec)) { + log_ptr = mlog_open_and_write_index( + mtr, insert_rec, index, MLOG_COMP_REC_INSERT, + 2 + 5 + 1 + 5 + 5 + MLOG_BUF_MARGIN); + if (UNIV_UNLIKELY(!log_ptr)) { + /* Logging in mtr is switched off + during crash recovery: in that case + mlog_open returns NULL */ + return; + } + } else { + log_ptr = mlog_open(mtr, 11 + + 2 + 5 + 1 + 5 + 5 + + MLOG_BUF_MARGIN); + if (UNIV_UNLIKELY(!log_ptr)) { + /* Logging in mtr is switched off + during crash recovery: in that case + mlog_open returns NULL */ + return; + } + + log_ptr = mlog_write_initial_log_record_fast( + insert_rec, MLOG_REC_INSERT, log_ptr, mtr); + } + + log_end = &log_ptr[2 + 5 + 1 + 5 + 5 + MLOG_BUF_MARGIN]; + /* Write the cursor rec offset as a 2-byte ulint */ + mach_write_to_2(log_ptr, page_offset(cursor_rec)); + log_ptr += 2; + } else { + log_ptr = mlog_open(mtr, 5 + 1 + 5 + 5 + MLOG_BUF_MARGIN); + if (!log_ptr) { + /* Logging in mtr is switched off during crash + recovery: in that case mlog_open returns NULL */ + return; + } + log_end = &log_ptr[5 + 1 + 5 + 5 + MLOG_BUF_MARGIN]; + } + + if (page_rec_is_comp(insert_rec)) { + if (UNIV_UNLIKELY + (rec_get_info_and_status_bits(insert_rec, TRUE) + != rec_get_info_and_status_bits(cursor_rec, TRUE))) { + + goto need_extra_info; + } + } else { + if (UNIV_UNLIKELY + (rec_get_info_and_status_bits(insert_rec, FALSE) + != rec_get_info_and_status_bits(cursor_rec, FALSE))) { + + goto need_extra_info; + } + } + + if (extra_size != cur_extra_size || rec_size != cur_rec_size) { +need_extra_info: + /* Write the record end segment length + and the extra info storage flag */ + log_ptr += mach_write_compressed(log_ptr, + 2 * (rec_size - i) + 1); + + /* Write the info bits */ + mach_write_to_1(log_ptr, + rec_get_info_and_status_bits( + insert_rec, + page_rec_is_comp(insert_rec))); + log_ptr++; + + /* Write the record origin offset */ + log_ptr += mach_write_compressed(log_ptr, extra_size); + + /* Write the mismatch index */ + log_ptr += mach_write_compressed(log_ptr, i); + + ut_a(i < UNIV_PAGE_SIZE); + ut_a(extra_size < UNIV_PAGE_SIZE); + } else { + /* Write the record end segment length + and the extra info storage flag */ + log_ptr += mach_write_compressed(log_ptr, 2 * (rec_size - i)); + } + + /* Write to the log the inserted index record end segment which + differs from the cursor record */ + + rec_size -= i; + + if (log_ptr + rec_size <= log_end) { + memcpy(log_ptr, ins_ptr, rec_size); + mlog_close(mtr, log_ptr + rec_size); + } else { + mlog_close(mtr, log_ptr); + ut_a(rec_size < UNIV_PAGE_SIZE); + mlog_catenate_string(mtr, ins_ptr, rec_size); + } +} + +/*************************************************************** +Parses a log record of a record insert on a page. */ +UNIV_INTERN +byte* +page_cur_parse_insert_rec( +/*======================*/ + /* out: end of log record or NULL */ + ibool is_short,/* in: TRUE if short inserts */ + byte* ptr, /* in: buffer */ + byte* end_ptr,/* in: buffer end */ + buf_block_t* block, /* in: page or NULL */ + dict_index_t* index, /* in: record descriptor */ + mtr_t* mtr) /* in: mtr or NULL */ +{ + ulint origin_offset; + ulint end_seg_len; + ulint mismatch_index; + page_t* page; + rec_t* cursor_rec; + byte buf1[1024]; + byte* buf; + byte* ptr2 = ptr; + ulint info_and_status_bits = 0; /* remove warning */ + page_cur_t cursor; + mem_heap_t* heap = NULL; + ulint offsets_[REC_OFFS_NORMAL_SIZE]; + ulint* offsets = offsets_; + rec_offs_init(offsets_); + + page = block ? buf_block_get_frame(block) : NULL; + + if (is_short) { + cursor_rec = page_rec_get_prev(page_get_supremum_rec(page)); + } else { + ulint offset; + + /* Read the cursor rec offset as a 2-byte ulint */ + + if (UNIV_UNLIKELY(end_ptr < ptr + 2)) { + + return(NULL); + } + + offset = mach_read_from_2(ptr); + ptr += 2; + + cursor_rec = page + offset; + + if (UNIV_UNLIKELY(offset >= UNIV_PAGE_SIZE)) { + + recv_sys->found_corrupt_log = TRUE; + + return(NULL); + } + } + + ptr = mach_parse_compressed(ptr, end_ptr, &end_seg_len); + + if (ptr == NULL) { + + return(NULL); + } + + if (UNIV_UNLIKELY(end_seg_len >= UNIV_PAGE_SIZE << 1)) { + recv_sys->found_corrupt_log = TRUE; + + return(NULL); + } + + if (end_seg_len & 0x1UL) { + /* Read the info bits */ + + if (end_ptr < ptr + 1) { + + return(NULL); + } + + info_and_status_bits = mach_read_from_1(ptr); + ptr++; + + ptr = mach_parse_compressed(ptr, end_ptr, &origin_offset); + + if (ptr == NULL) { + + return(NULL); + } + + ut_a(origin_offset < UNIV_PAGE_SIZE); + + ptr = mach_parse_compressed(ptr, end_ptr, &mismatch_index); + + if (ptr == NULL) { + + return(NULL); + } + + ut_a(mismatch_index < UNIV_PAGE_SIZE); + } + + if (UNIV_UNLIKELY(end_ptr < ptr + (end_seg_len >> 1))) { + + return(NULL); + } + + if (!block) { + + return(ptr + (end_seg_len >> 1)); + } + + ut_ad(!!page_is_comp(page) == dict_table_is_comp(index->table)); + ut_ad(!buf_block_get_page_zip(block) || page_is_comp(page)); + + /* Read from the log the inserted index record end segment which + differs from the cursor record */ + + offsets = rec_get_offsets(cursor_rec, index, offsets, + ULINT_UNDEFINED, &heap); + + if (!(end_seg_len & 0x1UL)) { + info_and_status_bits = rec_get_info_and_status_bits( + cursor_rec, page_is_comp(page)); + origin_offset = rec_offs_extra_size(offsets); + mismatch_index = rec_offs_size(offsets) - (end_seg_len >> 1); + } + + end_seg_len >>= 1; + + if (mismatch_index + end_seg_len < sizeof buf1) { + buf = buf1; + } else { + buf = mem_alloc(mismatch_index + end_seg_len); + } + + /* Build the inserted record to buf */ + + if (UNIV_UNLIKELY(mismatch_index >= UNIV_PAGE_SIZE)) { + fprintf(stderr, + "Is short %lu, info_and_status_bits %lu, offset %lu, " + "o_offset %lu\n" + "mismatch index %lu, end_seg_len %lu\n" + "parsed len %lu\n", + (ulong) is_short, (ulong) info_and_status_bits, + (ulong) page_offset(cursor_rec), + (ulong) origin_offset, + (ulong) mismatch_index, (ulong) end_seg_len, + (ulong) (ptr - ptr2)); + + fputs("Dump of 300 bytes of log:\n", stderr); + ut_print_buf(stderr, ptr2, 300); + putc('\n', stderr); + + buf_page_print(page, 0); + + ut_error; + } + + ut_memcpy(buf, rec_get_start(cursor_rec, offsets), mismatch_index); + ut_memcpy(buf + mismatch_index, ptr, end_seg_len); + + if (page_is_comp(page)) { + rec_set_info_and_status_bits(buf + origin_offset, + info_and_status_bits); + } else { + rec_set_info_bits_old(buf + origin_offset, + info_and_status_bits); + } + + page_cur_position(cursor_rec, block, &cursor); + + offsets = rec_get_offsets(buf + origin_offset, index, offsets, + ULINT_UNDEFINED, &heap); + if (UNIV_UNLIKELY(!page_cur_rec_insert(&cursor, + buf + origin_offset, + index, offsets, mtr))) { + /* The redo log record should only have been written + after the write was successful. */ + ut_error; + } + + if (buf != buf1) { + + mem_free(buf); + } + + if (UNIV_LIKELY_NULL(heap)) { + mem_heap_free(heap); + } + + return(ptr + end_seg_len); +} + +/*************************************************************** +Inserts a record next to page cursor on an uncompressed page. +Returns pointer to inserted record if succeed, i.e., enough +space available, NULL otherwise. The cursor stays at the same position. */ +UNIV_INTERN +rec_t* +page_cur_insert_rec_low( +/*====================*/ + /* out: pointer to record if succeed, NULL + otherwise */ + rec_t* current_rec,/* in: pointer to current record after + which the new record is inserted */ + dict_index_t* index, /* in: record descriptor */ + const rec_t* rec, /* in: pointer to a physical record */ + ulint* offsets,/* in/out: rec_get_offsets(rec, index) */ + mtr_t* mtr) /* in: mini-transaction handle, or NULL */ +{ + byte* insert_buf; + ulint rec_size; + page_t* page; /* the relevant page */ + rec_t* last_insert; /* cursor position at previous + insert */ + rec_t* free_rec; /* a free record that was reused, + or NULL */ + rec_t* insert_rec; /* inserted record */ + ulint heap_no; /* heap number of the inserted + record */ + + ut_ad(rec_offs_validate(rec, index, offsets)); + + page = page_align(current_rec); + ut_ad(dict_table_is_comp(index->table) + == (ibool) !!page_is_comp(page)); + + ut_ad(!page_rec_is_supremum(current_rec)); + + /* 1. Get the size of the physical record in the page */ + rec_size = rec_offs_size(offsets); + +#ifdef UNIV_DEBUG_VALGRIND + { + const void* rec_start + = rec - rec_offs_extra_size(offsets); + ulint extra_size + = rec_offs_extra_size(offsets) + - (rec_offs_comp(offsets) + ? REC_N_NEW_EXTRA_BYTES + : REC_N_OLD_EXTRA_BYTES); + + /* All data bytes of the record must be valid. */ + UNIV_MEM_ASSERT_RW(rec, rec_offs_data_size(offsets)); + /* The variable-length header must be valid. */ + UNIV_MEM_ASSERT_RW(rec_start, extra_size); + } +#endif /* UNIV_DEBUG_VALGRIND */ + + /* 2. Try to find suitable space from page memory management */ + + free_rec = page_header_get_ptr(page, PAGE_FREE); + if (UNIV_LIKELY_NULL(free_rec)) { + /* Try to allocate from the head of the free list. */ + ulint foffsets_[REC_OFFS_NORMAL_SIZE]; + ulint* foffsets = foffsets_; + mem_heap_t* heap = NULL; + + rec_offs_init(foffsets_); + + foffsets = rec_get_offsets(free_rec, index, foffsets, + ULINT_UNDEFINED, &heap); + if (rec_offs_size(foffsets) < rec_size) { + if (UNIV_LIKELY_NULL(heap)) { + mem_heap_free(heap); + } + + goto use_heap; + } + + insert_buf = free_rec - rec_offs_extra_size(foffsets); + + if (page_is_comp(page)) { + heap_no = rec_get_heap_no_new(free_rec); + page_mem_alloc_free(page, NULL, + rec_get_next_ptr(free_rec, TRUE), + rec_size); + } else { + heap_no = rec_get_heap_no_old(free_rec); + page_mem_alloc_free(page, NULL, + rec_get_next_ptr(free_rec, FALSE), + rec_size); + } + + if (UNIV_LIKELY_NULL(heap)) { + mem_heap_free(heap); + } + } else { +use_heap: + free_rec = NULL; + insert_buf = page_mem_alloc_heap(page, NULL, + rec_size, &heap_no); + + if (UNIV_UNLIKELY(insert_buf == NULL)) { + return(NULL); + } + } + + /* 3. Create the record */ + insert_rec = rec_copy(insert_buf, rec, offsets); + rec_offs_make_valid(insert_rec, index, offsets); + + /* 4. Insert the record in the linked list of records */ + ut_ad(current_rec != insert_rec); + + { + /* next record after current before the insertion */ + rec_t* next_rec = page_rec_get_next(current_rec); +#ifdef UNIV_DEBUG + if (page_is_comp(page)) { + ut_ad(rec_get_status(current_rec) + <= REC_STATUS_INFIMUM); + ut_ad(rec_get_status(insert_rec) < REC_STATUS_INFIMUM); + ut_ad(rec_get_status(next_rec) != REC_STATUS_INFIMUM); + } +#endif + page_rec_set_next(insert_rec, next_rec); + page_rec_set_next(current_rec, insert_rec); + } + + page_header_set_field(page, NULL, PAGE_N_RECS, + 1 + page_get_n_recs(page)); + + /* 5. Set the n_owned field in the inserted record to zero, + and set the heap_no field */ + if (page_is_comp(page)) { + rec_set_n_owned_new(insert_rec, NULL, 0); + rec_set_heap_no_new(insert_rec, heap_no); + } else { + rec_set_n_owned_old(insert_rec, 0); + rec_set_heap_no_old(insert_rec, heap_no); + } + + UNIV_MEM_ASSERT_RW(rec_get_start(insert_rec, offsets), + rec_offs_size(offsets)); + /* 6. Update the last insertion info in page header */ + + last_insert = page_header_get_ptr(page, PAGE_LAST_INSERT); + ut_ad(!last_insert || !page_is_comp(page) + || rec_get_node_ptr_flag(last_insert) + == rec_get_node_ptr_flag(insert_rec)); + + if (UNIV_UNLIKELY(last_insert == NULL)) { + page_header_set_field(page, NULL, PAGE_DIRECTION, + PAGE_NO_DIRECTION); + page_header_set_field(page, NULL, PAGE_N_DIRECTION, 0); + + } else if ((last_insert == current_rec) + && (page_header_get_field(page, PAGE_DIRECTION) + != PAGE_LEFT)) { + + page_header_set_field(page, NULL, PAGE_DIRECTION, + PAGE_RIGHT); + page_header_set_field(page, NULL, PAGE_N_DIRECTION, + page_header_get_field( + page, PAGE_N_DIRECTION) + 1); + + } else if ((page_rec_get_next(insert_rec) == last_insert) + && (page_header_get_field(page, PAGE_DIRECTION) + != PAGE_RIGHT)) { + + page_header_set_field(page, NULL, PAGE_DIRECTION, + PAGE_LEFT); + page_header_set_field(page, NULL, PAGE_N_DIRECTION, + page_header_get_field( + page, PAGE_N_DIRECTION) + 1); + } else { + page_header_set_field(page, NULL, PAGE_DIRECTION, + PAGE_NO_DIRECTION); + page_header_set_field(page, NULL, PAGE_N_DIRECTION, 0); + } + + page_header_set_ptr(page, NULL, PAGE_LAST_INSERT, insert_rec); + + /* 7. It remains to update the owner record. */ + { + rec_t* owner_rec = page_rec_find_owner_rec(insert_rec); + ulint n_owned; + if (page_is_comp(page)) { + n_owned = rec_get_n_owned_new(owner_rec); + rec_set_n_owned_new(owner_rec, NULL, n_owned + 1); + } else { + n_owned = rec_get_n_owned_old(owner_rec); + rec_set_n_owned_old(owner_rec, n_owned + 1); + } + + /* 8. Now we have incremented the n_owned field of the owner + record. If the number exceeds PAGE_DIR_SLOT_MAX_N_OWNED, + we have to split the corresponding directory slot in two. */ + + if (UNIV_UNLIKELY(n_owned == PAGE_DIR_SLOT_MAX_N_OWNED)) { + page_dir_split_slot( + page, NULL, + page_dir_find_owner_slot(owner_rec)); + } + } + + /* 9. Write log record of the insert */ + if (UNIV_LIKELY(mtr != NULL)) { + page_cur_insert_rec_write_log(insert_rec, rec_size, + current_rec, index, mtr); + } + + return(insert_rec); +} + +/*************************************************************** +Compresses or reorganizes a page after an optimistic insert. */ +static +rec_t* +page_cur_insert_rec_zip_reorg( +/*==========================*/ + /* out: rec if succeed, NULL otherwise */ + rec_t** current_rec,/* in/out: pointer to current record after + which the new record is inserted */ + buf_block_t* block, /* in: buffer block */ + dict_index_t* index, /* in: record descriptor */ + rec_t* rec, /* in: inserted record */ + page_t* page, /* in: uncompressed page */ + page_zip_des_t* page_zip,/* in: compressed page */ + mtr_t* mtr) /* in: mini-transaction, or NULL */ +{ + ulint pos; + + /* Recompress or reorganize and recompress the page. */ + if (UNIV_LIKELY(page_zip_compress(page_zip, page, index, mtr))) { + return(rec); + } + + /* Before trying to reorganize the page, + store the number of preceding records on the page. */ + pos = page_rec_get_n_recs_before(rec); + + if (page_zip_reorganize(block, index, mtr)) { + /* The page was reorganized: Find rec by seeking to pos, + and update *current_rec. */ + rec = page + PAGE_NEW_INFIMUM; + + while (--pos) { + rec = page + rec_get_next_offs(rec, TRUE); + } + + *current_rec = rec; + rec = page + rec_get_next_offs(rec, TRUE); + + return(rec); + } + + /* Out of space: restore the page */ + if (!page_zip_decompress(page_zip, page)) { + ut_error; /* Memory corrupted? */ + } + ut_ad(page_validate(page, index)); + return(NULL); +} + +/*************************************************************** +Inserts a record next to page cursor on a compressed and uncompressed +page. Returns pointer to inserted record if succeed, i.e., +enough space available, NULL otherwise. +The cursor stays at the same position. */ +UNIV_INTERN +rec_t* +page_cur_insert_rec_zip( +/*====================*/ + /* out: pointer to record if succeed, NULL + otherwise */ + rec_t** current_rec,/* in/out: pointer to current record after + which the new record is inserted */ + buf_block_t* block, /* in: buffer block of *current_rec */ + dict_index_t* index, /* in: record descriptor */ + const rec_t* rec, /* in: pointer to a physical record */ + ulint* offsets,/* in/out: rec_get_offsets(rec, index) */ + mtr_t* mtr) /* in: mini-transaction handle, or NULL */ +{ + byte* insert_buf; + ulint rec_size; + page_t* page; /* the relevant page */ + rec_t* last_insert; /* cursor position at previous + insert */ + rec_t* free_rec; /* a free record that was reused, + or NULL */ + rec_t* insert_rec; /* inserted record */ + ulint heap_no; /* heap number of the inserted + record */ + page_zip_des_t* page_zip; + + page_zip = buf_block_get_page_zip(block); + ut_ad(page_zip); + + ut_ad(rec_offs_validate(rec, index, offsets)); + + page = page_align(*current_rec); + ut_ad(dict_table_is_comp(index->table)); + ut_ad(page_is_comp(page)); + + ut_ad(!page_rec_is_supremum(*current_rec)); +#ifdef UNIV_ZIP_DEBUG + ut_a(page_zip_validate(page_zip, page)); +#endif /* UNIV_ZIP_DEBUG */ + + /* 1. Get the size of the physical record in the page */ + rec_size = rec_offs_size(offsets); + +#ifdef UNIV_DEBUG_VALGRIND + { + const void* rec_start + = rec - rec_offs_extra_size(offsets); + ulint extra_size + = rec_offs_extra_size(offsets) + - (rec_offs_comp(offsets) + ? REC_N_NEW_EXTRA_BYTES + : REC_N_OLD_EXTRA_BYTES); + + /* All data bytes of the record must be valid. */ + UNIV_MEM_ASSERT_RW(rec, rec_offs_data_size(offsets)); + /* The variable-length header must be valid. */ + UNIV_MEM_ASSERT_RW(rec_start, extra_size); + } +#endif /* UNIV_DEBUG_VALGRIND */ + + /* 2. Try to find suitable space from page memory management */ + if (!page_zip_available(page_zip, dict_index_is_clust(index), + rec_size, 1)) { + + /* Try compressing the whole page afterwards. */ + insert_rec = page_cur_insert_rec_low(*current_rec, + index, rec, offsets, + NULL); + + if (UNIV_LIKELY(insert_rec != NULL)) { + insert_rec = page_cur_insert_rec_zip_reorg( + current_rec, block, index, insert_rec, + page, page_zip, mtr); + } + + return(insert_rec); + } + + free_rec = page_header_get_ptr(page, PAGE_FREE); + if (UNIV_LIKELY_NULL(free_rec)) { + /* Try to allocate from the head of the free list. */ + lint extra_size_diff; + ulint foffsets_[REC_OFFS_NORMAL_SIZE]; + ulint* foffsets = foffsets_; + mem_heap_t* heap = NULL; + + rec_offs_init(foffsets_); + + foffsets = rec_get_offsets(free_rec, index, foffsets, + ULINT_UNDEFINED, &heap); + if (rec_offs_size(foffsets) < rec_size) { +too_small: + if (UNIV_LIKELY_NULL(heap)) { + mem_heap_free(heap); + } + + goto use_heap; + } + + insert_buf = free_rec - rec_offs_extra_size(foffsets); + + /* On compressed pages, do not relocate records from + the free list. If extra_size would grow, use the heap. */ + extra_size_diff + = rec_offs_extra_size(offsets) + - rec_offs_extra_size(foffsets); + + if (UNIV_UNLIKELY(extra_size_diff < 0)) { + /* Add an offset to the extra_size. */ + if (rec_offs_size(foffsets) + < rec_size - extra_size_diff) { + + goto too_small; + } + + insert_buf -= extra_size_diff; + } else if (UNIV_UNLIKELY(extra_size_diff)) { + /* Do not allow extra_size to grow */ + + goto too_small; + } + + heap_no = rec_get_heap_no_new(free_rec); + page_mem_alloc_free(page, page_zip, + rec_get_next_ptr(free_rec, TRUE), + rec_size); + + if (!page_is_leaf(page)) { + /* Zero out the node pointer of free_rec, + in case it will not be overwritten by + insert_rec. */ + + ut_ad(rec_size > REC_NODE_PTR_SIZE); + + if (rec_offs_extra_size(foffsets) + + rec_offs_data_size(foffsets) > rec_size) { + + memset(rec_get_end(free_rec, foffsets) + - REC_NODE_PTR_SIZE, 0, + REC_NODE_PTR_SIZE); + } + } else if (dict_index_is_clust(index)) { + /* Zero out the DB_TRX_ID and DB_ROLL_PTR + columns of free_rec, in case it will not be + overwritten by insert_rec. */ + + ulint trx_id_col; + ulint trx_id_offs; + ulint len; + + trx_id_col = dict_index_get_sys_col_pos(index, + DATA_TRX_ID); + ut_ad(trx_id_col > 0); + ut_ad(trx_id_col != ULINT_UNDEFINED); + + trx_id_offs = rec_get_nth_field_offs(foffsets, + trx_id_col, &len); + ut_ad(len == DATA_TRX_ID_LEN); + + if (DATA_TRX_ID_LEN + DATA_ROLL_PTR_LEN + trx_id_offs + + rec_offs_extra_size(foffsets) > rec_size) { + /* We will have to zero out the + DB_TRX_ID and DB_ROLL_PTR, because + they will not be fully overwritten by + insert_rec. */ + + memset(free_rec + trx_id_offs, 0, + DATA_TRX_ID_LEN + DATA_ROLL_PTR_LEN); + } + + ut_ad(free_rec + trx_id_offs + DATA_TRX_ID_LEN + == rec_get_nth_field(free_rec, foffsets, + trx_id_col + 1, &len)); + ut_ad(len == DATA_ROLL_PTR_LEN); + } + + if (UNIV_LIKELY_NULL(heap)) { + mem_heap_free(heap); + } + } else { +use_heap: + free_rec = NULL; + insert_buf = page_mem_alloc_heap(page, page_zip, + rec_size, &heap_no); + + if (UNIV_UNLIKELY(insert_buf == NULL)) { + return(NULL); + } + + page_zip_dir_add_slot(page_zip, dict_index_is_clust(index)); + } + + /* 3. Create the record */ + insert_rec = rec_copy(insert_buf, rec, offsets); + rec_offs_make_valid(insert_rec, index, offsets); + + /* 4. Insert the record in the linked list of records */ + ut_ad(*current_rec != insert_rec); + + { + /* next record after current before the insertion */ + rec_t* next_rec = page_rec_get_next(*current_rec); + ut_ad(rec_get_status(*current_rec) + <= REC_STATUS_INFIMUM); + ut_ad(rec_get_status(insert_rec) < REC_STATUS_INFIMUM); + ut_ad(rec_get_status(next_rec) != REC_STATUS_INFIMUM); + + page_rec_set_next(insert_rec, next_rec); + page_rec_set_next(*current_rec, insert_rec); + } + + page_header_set_field(page, page_zip, PAGE_N_RECS, + 1 + page_get_n_recs(page)); + + /* 5. Set the n_owned field in the inserted record to zero, + and set the heap_no field */ + rec_set_n_owned_new(insert_rec, NULL, 0); + rec_set_heap_no_new(insert_rec, heap_no); + + UNIV_MEM_ASSERT_RW(rec_get_start(insert_rec, offsets), + rec_offs_size(offsets)); + + page_zip_dir_insert(page_zip, *current_rec, free_rec, insert_rec); + + /* 6. Update the last insertion info in page header */ + + last_insert = page_header_get_ptr(page, PAGE_LAST_INSERT); + ut_ad(!last_insert + || rec_get_node_ptr_flag(last_insert) + == rec_get_node_ptr_flag(insert_rec)); + + if (UNIV_UNLIKELY(last_insert == NULL)) { + page_header_set_field(page, page_zip, PAGE_DIRECTION, + PAGE_NO_DIRECTION); + page_header_set_field(page, page_zip, PAGE_N_DIRECTION, 0); + + } else if ((last_insert == *current_rec) + && (page_header_get_field(page, PAGE_DIRECTION) + != PAGE_LEFT)) { + + page_header_set_field(page, page_zip, PAGE_DIRECTION, + PAGE_RIGHT); + page_header_set_field(page, page_zip, PAGE_N_DIRECTION, + page_header_get_field( + page, PAGE_N_DIRECTION) + 1); + + } else if ((page_rec_get_next(insert_rec) == last_insert) + && (page_header_get_field(page, PAGE_DIRECTION) + != PAGE_RIGHT)) { + + page_header_set_field(page, page_zip, PAGE_DIRECTION, + PAGE_LEFT); + page_header_set_field(page, page_zip, PAGE_N_DIRECTION, + page_header_get_field( + page, PAGE_N_DIRECTION) + 1); + } else { + page_header_set_field(page, page_zip, PAGE_DIRECTION, + PAGE_NO_DIRECTION); + page_header_set_field(page, page_zip, PAGE_N_DIRECTION, 0); + } + + page_header_set_ptr(page, page_zip, PAGE_LAST_INSERT, insert_rec); + + /* 7. It remains to update the owner record. */ + { + rec_t* owner_rec = page_rec_find_owner_rec(insert_rec); + ulint n_owned; + + n_owned = rec_get_n_owned_new(owner_rec); + rec_set_n_owned_new(owner_rec, page_zip, n_owned + 1); + + /* 8. Now we have incremented the n_owned field of the owner + record. If the number exceeds PAGE_DIR_SLOT_MAX_N_OWNED, + we have to split the corresponding directory slot in two. */ + + if (UNIV_UNLIKELY(n_owned == PAGE_DIR_SLOT_MAX_N_OWNED)) { + page_dir_split_slot( + page, page_zip, + page_dir_find_owner_slot(owner_rec)); + } + } + + page_zip_write_rec(page_zip, insert_rec, index, offsets, 1); + + /* 9. Write log record of the insert */ + if (UNIV_LIKELY(mtr != NULL)) { + page_cur_insert_rec_write_log(insert_rec, rec_size, + *current_rec, index, mtr); + } + + return(insert_rec); +} + +/************************************************************** +Writes a log record of copying a record list end to a new created page. */ +UNIV_INLINE +byte* +page_copy_rec_list_to_created_page_write_log( +/*=========================================*/ + /* out: 4-byte field where to + write the log data length, + or NULL if logging is disabled */ + page_t* page, /* in: index page */ + dict_index_t* index, /* in: record descriptor */ + mtr_t* mtr) /* in: mtr */ +{ + byte* log_ptr; + + ut_ad(!!page_is_comp(page) == dict_table_is_comp(index->table)); + + log_ptr = mlog_open_and_write_index(mtr, page, index, + page_is_comp(page) + ? MLOG_COMP_LIST_END_COPY_CREATED + : MLOG_LIST_END_COPY_CREATED, 4); + if (UNIV_LIKELY(log_ptr != NULL)) { + mlog_close(mtr, log_ptr + 4); + } + + return(log_ptr); +} + +/************************************************************** +Parses a log record of copying a record list end to a new created page. */ +UNIV_INTERN +byte* +page_parse_copy_rec_list_to_created_page( +/*=====================================*/ + /* out: end of log record or NULL */ + byte* ptr, /* in: buffer */ + byte* end_ptr,/* in: buffer end */ + buf_block_t* block, /* in: page or NULL */ + dict_index_t* index, /* in: record descriptor */ + mtr_t* mtr) /* in: mtr or NULL */ +{ + byte* rec_end; + ulint log_data_len; + page_t* page; + page_zip_des_t* page_zip; + + if (ptr + 4 > end_ptr) { + + return(NULL); + } + + log_data_len = mach_read_from_4(ptr); + ptr += 4; + + rec_end = ptr + log_data_len; + + if (rec_end > end_ptr) { + + return(NULL); + } + + if (!block) { + + return(rec_end); + } + + while (ptr < rec_end) { + ptr = page_cur_parse_insert_rec(TRUE, ptr, end_ptr, + block, index, mtr); + } + + ut_a(ptr == rec_end); + + page = buf_block_get_frame(block); + page_zip = buf_block_get_page_zip(block); + + page_header_set_ptr(page, page_zip, PAGE_LAST_INSERT, NULL); + page_header_set_field(page, page_zip, PAGE_DIRECTION, + PAGE_NO_DIRECTION); + page_header_set_field(page, page_zip, PAGE_N_DIRECTION, 0); + + return(rec_end); +} + +/***************************************************************** +Copies records from page to a newly created page, from a given record onward, +including that record. Infimum and supremum records are not copied. */ +UNIV_INTERN +void +page_copy_rec_list_end_to_created_page( +/*===================================*/ + page_t* new_page, /* in/out: index page to copy to */ + rec_t* rec, /* in: first record to copy */ + dict_index_t* index, /* in: record descriptor */ + mtr_t* mtr) /* in: mtr */ +{ + page_dir_slot_t* slot = 0; /* remove warning */ + byte* heap_top; + rec_t* insert_rec = 0; /* remove warning */ + rec_t* prev_rec; + ulint count; + ulint n_recs; + ulint slot_index; + ulint rec_size; + ulint log_mode; + byte* log_ptr; + ulint log_data_len; + mem_heap_t* heap = NULL; + ulint offsets_[REC_OFFS_NORMAL_SIZE]; + ulint* offsets = offsets_; + rec_offs_init(offsets_); + + ut_ad(page_dir_get_n_heap(new_page) == PAGE_HEAP_NO_USER_LOW); + ut_ad(page_align(rec) != new_page); + ut_ad(page_rec_is_comp(rec) == page_is_comp(new_page)); + + if (page_rec_is_infimum(rec)) { + + rec = page_rec_get_next(rec); + } + + if (page_rec_is_supremum(rec)) { + + return; + } + +#ifdef UNIV_DEBUG + /* To pass the debug tests we have to set these dummy values + in the debug version */ + page_dir_set_n_slots(new_page, NULL, UNIV_PAGE_SIZE / 2); + page_header_set_ptr(new_page, NULL, PAGE_HEAP_TOP, + new_page + UNIV_PAGE_SIZE - 1); +#endif + + log_ptr = page_copy_rec_list_to_created_page_write_log(new_page, + index, mtr); + + log_data_len = dyn_array_get_data_size(&(mtr->log)); + + /* Individual inserts are logged in a shorter form */ + + log_mode = mtr_set_log_mode(mtr, MTR_LOG_SHORT_INSERTS); + + prev_rec = page_get_infimum_rec(new_page); + if (page_is_comp(new_page)) { + heap_top = new_page + PAGE_NEW_SUPREMUM_END; + } else { + heap_top = new_page + PAGE_OLD_SUPREMUM_END; + } + count = 0; + slot_index = 0; + n_recs = 0; + + do { + offsets = rec_get_offsets(rec, index, offsets, + ULINT_UNDEFINED, &heap); + insert_rec = rec_copy(heap_top, rec, offsets); + + if (page_is_comp(new_page)) { + rec_set_next_offs_new(prev_rec, + page_offset(insert_rec)); + + rec_set_n_owned_new(insert_rec, NULL, 0); + rec_set_heap_no_new(insert_rec, + PAGE_HEAP_NO_USER_LOW + n_recs); + } else { + rec_set_next_offs_old(prev_rec, + page_offset(insert_rec)); + + rec_set_n_owned_old(insert_rec, 0); + rec_set_heap_no_old(insert_rec, + PAGE_HEAP_NO_USER_LOW + n_recs); + } + + count++; + n_recs++; + + if (UNIV_UNLIKELY + (count == (PAGE_DIR_SLOT_MAX_N_OWNED + 1) / 2)) { + + slot_index++; + + slot = page_dir_get_nth_slot(new_page, slot_index); + + page_dir_slot_set_rec(slot, insert_rec); + page_dir_slot_set_n_owned(slot, NULL, count); + + count = 0; + } + + rec_size = rec_offs_size(offsets); + + ut_ad(heap_top < new_page + UNIV_PAGE_SIZE); + + heap_top += rec_size; + + page_cur_insert_rec_write_log(insert_rec, rec_size, prev_rec, + index, mtr); + prev_rec = insert_rec; + rec = page_rec_get_next(rec); + } while (!page_rec_is_supremum(rec)); + + if ((slot_index > 0) && (count + 1 + + (PAGE_DIR_SLOT_MAX_N_OWNED + 1) / 2 + <= PAGE_DIR_SLOT_MAX_N_OWNED)) { + /* We can merge the two last dir slots. This operation is + here to make this function imitate exactly the equivalent + task made using page_cur_insert_rec, which we use in database + recovery to reproduce the task performed by this function. + To be able to check the correctness of recovery, it is good + that it imitates exactly. */ + + count += (PAGE_DIR_SLOT_MAX_N_OWNED + 1) / 2; + + page_dir_slot_set_n_owned(slot, NULL, 0); + + slot_index--; + } + + if (UNIV_LIKELY_NULL(heap)) { + mem_heap_free(heap); + } + + log_data_len = dyn_array_get_data_size(&(mtr->log)) - log_data_len; + + ut_a(log_data_len < 100 * UNIV_PAGE_SIZE); + + if (UNIV_LIKELY(log_ptr != NULL)) { + mach_write_to_4(log_ptr, log_data_len); + } + + if (page_is_comp(new_page)) { + rec_set_next_offs_new(insert_rec, PAGE_NEW_SUPREMUM); + } else { + rec_set_next_offs_old(insert_rec, PAGE_OLD_SUPREMUM); + } + + slot = page_dir_get_nth_slot(new_page, 1 + slot_index); + + page_dir_slot_set_rec(slot, page_get_supremum_rec(new_page)); + page_dir_slot_set_n_owned(slot, NULL, count + 1); + + page_dir_set_n_slots(new_page, NULL, 2 + slot_index); + page_header_set_ptr(new_page, NULL, PAGE_HEAP_TOP, heap_top); + page_dir_set_n_heap(new_page, NULL, PAGE_HEAP_NO_USER_LOW + n_recs); + page_header_set_field(new_page, NULL, PAGE_N_RECS, n_recs); + + page_header_set_ptr(new_page, NULL, PAGE_LAST_INSERT, NULL); + page_header_set_field(new_page, NULL, PAGE_DIRECTION, + PAGE_NO_DIRECTION); + page_header_set_field(new_page, NULL, PAGE_N_DIRECTION, 0); + + /* Restore the log mode */ + + mtr_set_log_mode(mtr, log_mode); +} + +/*************************************************************** +Writes log record of a record delete on a page. */ +UNIV_INLINE +void +page_cur_delete_rec_write_log( +/*==========================*/ + rec_t* rec, /* in: record to be deleted */ + dict_index_t* index, /* in: record descriptor */ + mtr_t* mtr) /* in: mini-transaction handle */ +{ + byte* log_ptr; + + ut_ad(!!page_rec_is_comp(rec) == dict_table_is_comp(index->table)); + + log_ptr = mlog_open_and_write_index(mtr, rec, index, + page_rec_is_comp(rec) + ? MLOG_COMP_REC_DELETE + : MLOG_REC_DELETE, 2); + + if (!log_ptr) { + /* Logging in mtr is switched off during crash recovery: + in that case mlog_open returns NULL */ + return; + } + + /* Write the cursor rec offset as a 2-byte ulint */ + mach_write_to_2(log_ptr, page_offset(rec)); + + mlog_close(mtr, log_ptr + 2); +} + +/*************************************************************** +Parses log record of a record delete on a page. */ +UNIV_INTERN +byte* +page_cur_parse_delete_rec( +/*======================*/ + /* out: pointer to record end or NULL */ + byte* ptr, /* in: buffer */ + byte* end_ptr,/* in: buffer end */ + buf_block_t* block, /* in: page or NULL */ + dict_index_t* index, /* in: record descriptor */ + mtr_t* mtr) /* in: mtr or NULL */ +{ + ulint offset; + page_cur_t cursor; + + if (end_ptr < ptr + 2) { + + return(NULL); + } + + /* Read the cursor rec offset as a 2-byte ulint */ + offset = mach_read_from_2(ptr); + ptr += 2; + + ut_a(offset <= UNIV_PAGE_SIZE); + + if (block) { + page_t* page = buf_block_get_frame(block); + mem_heap_t* heap = NULL; + ulint offsets_[REC_OFFS_NORMAL_SIZE]; + rec_t* rec = page + offset; + rec_offs_init(offsets_); + + page_cur_position(rec, block, &cursor); + ut_ad(!buf_block_get_page_zip(block) || page_is_comp(page)); + + page_cur_delete_rec(&cursor, index, + rec_get_offsets(rec, index, offsets_, + ULINT_UNDEFINED, &heap), + mtr); + if (UNIV_LIKELY_NULL(heap)) { + mem_heap_free(heap); + } + } + + return(ptr); +} + +/*************************************************************** +Deletes a record at the page cursor. The cursor is moved to the next +record after the deleted one. */ +UNIV_INTERN +void +page_cur_delete_rec( +/*================*/ + page_cur_t* cursor, /* in/out: a page cursor */ + dict_index_t* index, /* in: record descriptor */ + const ulint* offsets,/* in: rec_get_offsets(cursor->rec, index) */ + mtr_t* mtr) /* in: mini-transaction handle */ +{ + page_dir_slot_t* cur_dir_slot; + page_dir_slot_t* prev_slot; + page_t* page; + page_zip_des_t* page_zip; + rec_t* current_rec; + rec_t* prev_rec = NULL; + rec_t* next_rec; + ulint cur_slot_no; + ulint cur_n_owned; + rec_t* rec; + + ut_ad(cursor && mtr); + + page = page_cur_get_page(cursor); + page_zip = page_cur_get_page_zip(cursor); + + /* page_zip_validate() will fail here when + btr_cur_pessimistic_delete() invokes btr_set_min_rec_mark(). + Then, both "page_zip" and "page" would have the min-rec-mark + set on the smallest user record, but "page" would additionally + have it set on the smallest-but-one record. Because sloppy + page_zip_validate_low() only ignores min-rec-flag differences + in the smallest user record, it cannot be used here either. */ + + current_rec = cursor->rec; + ut_ad(rec_offs_validate(current_rec, index, offsets)); + ut_ad(!!page_is_comp(page) == dict_table_is_comp(index->table)); + + /* The record must not be the supremum or infimum record. */ + ut_ad(page_rec_is_user_rec(current_rec)); + + /* Save to local variables some data associated with current_rec */ + cur_slot_no = page_dir_find_owner_slot(current_rec); + cur_dir_slot = page_dir_get_nth_slot(page, cur_slot_no); + cur_n_owned = page_dir_slot_get_n_owned(cur_dir_slot); + + /* 0. Write the log record */ + page_cur_delete_rec_write_log(current_rec, index, mtr); + + /* 1. Reset the last insert info in the page header and increment + the modify clock for the frame */ + + page_header_set_ptr(page, page_zip, PAGE_LAST_INSERT, NULL); + + /* The page gets invalid for optimistic searches: increment the + frame modify clock */ + + buf_block_modify_clock_inc(page_cur_get_block(cursor)); + + /* 2. Find the next and the previous record. Note that the cursor is + left at the next record. */ + + ut_ad(cur_slot_no > 0); + prev_slot = page_dir_get_nth_slot(page, cur_slot_no - 1); + + rec = (rec_t*) page_dir_slot_get_rec(prev_slot); + + /* rec now points to the record of the previous directory slot. Look + for the immediate predecessor of current_rec in a loop. */ + + while(current_rec != rec) { + prev_rec = rec; + rec = page_rec_get_next(rec); + } + + page_cur_move_to_next(cursor); + next_rec = cursor->rec; + + /* 3. Remove the record from the linked list of records */ + + page_rec_set_next(prev_rec, next_rec); + + /* 4. If the deleted record is pointed to by a dir slot, update the + record pointer in slot. In the following if-clause we assume that + prev_rec is owned by the same slot, i.e., PAGE_DIR_SLOT_MIN_N_OWNED + >= 2. */ + +#if PAGE_DIR_SLOT_MIN_N_OWNED < 2 +# error "PAGE_DIR_SLOT_MIN_N_OWNED < 2" +#endif + ut_ad(cur_n_owned > 1); + + if (current_rec == page_dir_slot_get_rec(cur_dir_slot)) { + page_dir_slot_set_rec(cur_dir_slot, prev_rec); + } + + /* 5. Update the number of owned records of the slot */ + + page_dir_slot_set_n_owned(cur_dir_slot, page_zip, cur_n_owned - 1); + + /* 6. Free the memory occupied by the record */ + page_mem_free(page, page_zip, current_rec, index, offsets); + + /* 7. Now we have decremented the number of owned records of the slot. + If the number drops below PAGE_DIR_SLOT_MIN_N_OWNED, we balance the + slots. */ + + if (UNIV_UNLIKELY(cur_n_owned <= PAGE_DIR_SLOT_MIN_N_OWNED)) { + page_dir_balance_slot(page, page_zip, cur_slot_no); + } + +#ifdef UNIV_ZIP_DEBUG + ut_a(!page_zip || page_zip_validate(page_zip, page)); +#endif /* UNIV_ZIP_DEBUG */ +} diff --git a/storage/xtradb/page/page0page.c b/storage/xtradb/page/page0page.c new file mode 100644 index 00000000000..0ce532068ce --- /dev/null +++ b/storage/xtradb/page/page0page.c @@ -0,0 +1,2568 @@ +/***************************************************************************** + +Copyright (c) 1994, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/****************************************************** +Index page routines + +Created 2/2/1994 Heikki Tuuri +*******************************************************/ + +#define THIS_MODULE +#include "page0page.h" +#ifdef UNIV_NONINL +#include "page0page.ic" +#endif +#undef THIS_MODULE + +#include "page0cur.h" +#include "page0zip.h" +#include "lock0lock.h" +#include "fut0lst.h" +#include "btr0sea.h" +#include "buf0buf.h" +#include "srv0srv.h" +#include "btr0btr.h" + +/* THE INDEX PAGE + ============== + +The index page consists of a page header which contains the page's +id and other information. On top of it are the the index records +in a heap linked into a one way linear list according to alphabetic order. + +Just below page end is an array of pointers which we call page directory, +to about every sixth record in the list. The pointers are placed in +the directory in the alphabetical order of the records pointed to, +enabling us to make binary search using the array. Each slot n:o I +in the directory points to a record, where a 4-bit field contains a count +of those records which are in the linear list between pointer I and +the pointer I - 1 in the directory, including the record +pointed to by pointer I and not including the record pointed to by I - 1. +We say that the record pointed to by slot I, or that slot I, owns +these records. The count is always kept in the range 4 to 8, with +the exception that it is 1 for the first slot, and 1--8 for the second slot. + +An essentially binary search can be performed in the list of index +records, like we could do if we had pointer to every record in the +page directory. The data structure is, however, more efficient when +we are doing inserts, because most inserts are just pushed on a heap. +Only every 8th insert requires block move in the directory pointer +table, which itself is quite small. A record is deleted from the page +by just taking it off the linear list and updating the number of owned +records-field of the record which owns it, and updating the page directory, +if necessary. A special case is the one when the record owns itself. +Because the overhead of inserts is so small, we may also increase the +page size from the projected default of 8 kB to 64 kB without too +much loss of efficiency in inserts. Bigger page becomes actual +when the disk transfer rate compared to seek and latency time rises. +On the present system, the page size is set so that the page transfer +time (3 ms) is 20 % of the disk random access time (15 ms). + +When the page is split, merged, or becomes full but contains deleted +records, we have to reorganize the page. + +Assuming a page size of 8 kB, a typical index page of a secondary +index contains 300 index entries, and the size of the page directory +is 50 x 4 bytes = 200 bytes. */ + +/******************************************************************* +Looks for the directory slot which owns the given record. */ +UNIV_INTERN +ulint +page_dir_find_owner_slot( +/*=====================*/ + /* out: the directory slot number */ + const rec_t* rec) /* in: the physical record */ +{ + const page_t* page; + register uint16 rec_offs_bytes; + register const page_dir_slot_t* slot; + register const page_dir_slot_t* first_slot; + register const rec_t* r = rec; + + ut_ad(page_rec_check(rec)); + + page = page_align(rec); + first_slot = page_dir_get_nth_slot(page, 0); + slot = page_dir_get_nth_slot(page, page_dir_get_n_slots(page) - 1); + + if (page_is_comp(page)) { + while (rec_get_n_owned_new(r) == 0) { + r = rec_get_next_ptr_const(r, TRUE); + ut_ad(r >= page + PAGE_NEW_SUPREMUM); + ut_ad(r < page + (UNIV_PAGE_SIZE - PAGE_DIR)); + } + } else { + while (rec_get_n_owned_old(r) == 0) { + r = rec_get_next_ptr_const(r, FALSE); + ut_ad(r >= page + PAGE_OLD_SUPREMUM); + ut_ad(r < page + (UNIV_PAGE_SIZE - PAGE_DIR)); + } + } + + rec_offs_bytes = mach_encode_2(r - page); + + while (UNIV_LIKELY(*(uint16*) slot != rec_offs_bytes)) { + + if (UNIV_UNLIKELY(slot == first_slot)) { + fprintf(stderr, + "InnoDB: Probable data corruption on" + " page %lu\n" + "InnoDB: Original record ", + (ulong) page_get_page_no(page)); + + if (page_is_comp(page)) { + fputs("(compact record)", stderr); + } else { + rec_print_old(stderr, rec); + } + + fputs("\n" + "InnoDB: on that page.\n" + "InnoDB: Cannot find the dir slot for record ", + stderr); + if (page_is_comp(page)) { + fputs("(compact record)", stderr); + } else { + rec_print_old(stderr, page + + mach_decode_2(rec_offs_bytes)); + } + fputs("\n" + "InnoDB: on that page!\n", stderr); + + buf_page_print(page, 0); + + ut_error; + } + + slot += PAGE_DIR_SLOT_SIZE; + } + + return(((ulint) (first_slot - slot)) / PAGE_DIR_SLOT_SIZE); +} + +/****************************************************************** +Used to check the consistency of a directory slot. */ +static +ibool +page_dir_slot_check( +/*================*/ + /* out: TRUE if succeed */ + page_dir_slot_t* slot) /* in: slot */ +{ + page_t* page; + ulint n_slots; + ulint n_owned; + + ut_a(slot); + + page = page_align(slot); + + n_slots = page_dir_get_n_slots(page); + + ut_a(slot <= page_dir_get_nth_slot(page, 0)); + ut_a(slot >= page_dir_get_nth_slot(page, n_slots - 1)); + + ut_a(page_rec_check(page_dir_slot_get_rec(slot))); + + if (page_is_comp(page)) { + n_owned = rec_get_n_owned_new(page_dir_slot_get_rec(slot)); + } else { + n_owned = rec_get_n_owned_old(page_dir_slot_get_rec(slot)); + } + + if (slot == page_dir_get_nth_slot(page, 0)) { + ut_a(n_owned == 1); + } else if (slot == page_dir_get_nth_slot(page, n_slots - 1)) { + ut_a(n_owned >= 1); + ut_a(n_owned <= PAGE_DIR_SLOT_MAX_N_OWNED); + } else { + ut_a(n_owned >= PAGE_DIR_SLOT_MIN_N_OWNED); + ut_a(n_owned <= PAGE_DIR_SLOT_MAX_N_OWNED); + } + + return(TRUE); +} + +/***************************************************************** +Sets the max trx id field value. */ +UNIV_INTERN +void +page_set_max_trx_id( +/*================*/ + buf_block_t* block, /* in/out: page */ + page_zip_des_t* page_zip,/* in/out: compressed page, or NULL */ + dulint trx_id) /* in: transaction id */ +{ + const ibool is_hashed = block->is_hashed; + page_t* page = buf_block_get_frame(block); + + if (is_hashed) { + rw_lock_x_lock(&btr_search_latch); + } + + /* It is not necessary to write this change to the redo log, as + during a database recovery we assume that the max trx id of every + page is the maximum trx id assigned before the crash. */ + + mach_write_to_8(page + (PAGE_HEADER + PAGE_MAX_TRX_ID), trx_id); + if (UNIV_LIKELY_NULL(page_zip)) { + page_zip_write_header(page_zip, + page + (PAGE_HEADER + PAGE_MAX_TRX_ID), + 8, NULL); + } + + if (is_hashed) { + rw_lock_x_unlock(&btr_search_latch); + } +} + +/**************************************************************** +Allocates a block of memory from the heap of an index page. */ +UNIV_INTERN +byte* +page_mem_alloc_heap( +/*================*/ + /* out: pointer to start of allocated + buffer, or NULL if allocation fails */ + page_t* page, /* in/out: index page */ + page_zip_des_t* page_zip,/* in/out: compressed page with enough + space available for inserting the record, + or NULL */ + ulint need, /* in: total number of bytes needed */ + ulint* heap_no)/* out: this contains the heap number + of the allocated record + if allocation succeeds */ +{ + byte* block; + ulint avl_space; + + ut_ad(page && heap_no); + + avl_space = page_get_max_insert_size(page, 1); + + if (avl_space >= need) { + block = page_header_get_ptr(page, PAGE_HEAP_TOP); + + page_header_set_ptr(page, page_zip, PAGE_HEAP_TOP, + block + need); + *heap_no = page_dir_get_n_heap(page); + + page_dir_set_n_heap(page, page_zip, 1 + *heap_no); + + return(block); + } + + return(NULL); +} + +/************************************************************** +Writes a log record of page creation. */ +UNIV_INLINE +void +page_create_write_log( +/*==================*/ + buf_frame_t* frame, /* in: a buffer frame where the page is + created */ + mtr_t* mtr, /* in: mini-transaction handle */ + ibool comp) /* in: TRUE=compact page format */ +{ + mlog_write_initial_log_record(frame, comp + ? MLOG_COMP_PAGE_CREATE + : MLOG_PAGE_CREATE, mtr); +} + +/*************************************************************** +Parses a redo log record of creating a page. */ +UNIV_INTERN +byte* +page_parse_create( +/*==============*/ + /* out: end of log record or NULL */ + byte* ptr, /* in: buffer */ + byte* end_ptr __attribute__((unused)), /* in: buffer end */ + ulint comp, /* in: nonzero=compact page format */ + buf_block_t* block, /* in: block or NULL */ + mtr_t* mtr) /* in: mtr or NULL */ +{ + ut_ad(ptr && end_ptr); + + /* The record is empty, except for the record initial part */ + + if (block) { + page_create(block, mtr, comp); + } + + return(ptr); +} + +/************************************************************** +The index page creation function. */ +static +page_t* +page_create_low( +/*============*/ + /* out: pointer to the page */ + buf_block_t* block, /* in: a buffer block where the + page is created */ + ulint comp) /* in: nonzero=compact page format */ +{ + page_dir_slot_t* slot; + mem_heap_t* heap; + dtuple_t* tuple; + dfield_t* field; + byte* heap_top; + rec_t* infimum_rec; + rec_t* supremum_rec; + page_t* page; + dict_index_t* index; + ulint* offsets; + + ut_ad(block); +#if PAGE_BTR_IBUF_FREE_LIST + FLST_BASE_NODE_SIZE > PAGE_DATA +# error "PAGE_BTR_IBUF_FREE_LIST + FLST_BASE_NODE_SIZE > PAGE_DATA" +#endif +#if PAGE_BTR_IBUF_FREE_LIST_NODE + FLST_NODE_SIZE > PAGE_DATA +# error "PAGE_BTR_IBUF_FREE_LIST_NODE + FLST_NODE_SIZE > PAGE_DATA" +#endif + + /* The infimum and supremum records use a dummy index. */ + if (UNIV_LIKELY(comp)) { + index = srv_sys->dummy_ind2; + } else { + index = srv_sys->dummy_ind1; + } + + /* 1. INCREMENT MODIFY CLOCK */ + buf_block_modify_clock_inc(block); + + page = buf_block_get_frame(block); + + fil_page_set_type(page, FIL_PAGE_INDEX); + + heap = mem_heap_create(200); + + /* 3. CREATE THE INFIMUM AND SUPREMUM RECORDS */ + + /* Create first a data tuple for infimum record */ + tuple = dtuple_create(heap, 1); + dtuple_set_info_bits(tuple, REC_STATUS_INFIMUM); + field = dtuple_get_nth_field(tuple, 0); + + dfield_set_data(field, "infimum", 8); + dtype_set(dfield_get_type(field), + DATA_VARCHAR, DATA_ENGLISH | DATA_NOT_NULL, 8); + /* Set the corresponding physical record to its place in the page + record heap */ + + heap_top = page + PAGE_DATA; + + infimum_rec = rec_convert_dtuple_to_rec(heap_top, index, tuple, 0); + + if (UNIV_LIKELY(comp)) { + ut_a(infimum_rec == page + PAGE_NEW_INFIMUM); + + rec_set_n_owned_new(infimum_rec, NULL, 1); + rec_set_heap_no_new(infimum_rec, 0); + } else { + ut_a(infimum_rec == page + PAGE_OLD_INFIMUM); + + rec_set_n_owned_old(infimum_rec, 1); + rec_set_heap_no_old(infimum_rec, 0); + } + + offsets = rec_get_offsets(infimum_rec, index, NULL, + ULINT_UNDEFINED, &heap); + + heap_top = rec_get_end(infimum_rec, offsets); + + /* Create then a tuple for supremum */ + + tuple = dtuple_create(heap, 1); + dtuple_set_info_bits(tuple, REC_STATUS_SUPREMUM); + field = dtuple_get_nth_field(tuple, 0); + + dfield_set_data(field, "supremum", comp ? 8 : 9); + dtype_set(dfield_get_type(field), + DATA_VARCHAR, DATA_ENGLISH | DATA_NOT_NULL, comp ? 8 : 9); + + supremum_rec = rec_convert_dtuple_to_rec(heap_top, index, tuple, 0); + + if (UNIV_LIKELY(comp)) { + ut_a(supremum_rec == page + PAGE_NEW_SUPREMUM); + + rec_set_n_owned_new(supremum_rec, NULL, 1); + rec_set_heap_no_new(supremum_rec, 1); + } else { + ut_a(supremum_rec == page + PAGE_OLD_SUPREMUM); + + rec_set_n_owned_old(supremum_rec, 1); + rec_set_heap_no_old(supremum_rec, 1); + } + + offsets = rec_get_offsets(supremum_rec, index, offsets, + ULINT_UNDEFINED, &heap); + heap_top = rec_get_end(supremum_rec, offsets); + + ut_ad(heap_top == page + + (comp ? PAGE_NEW_SUPREMUM_END : PAGE_OLD_SUPREMUM_END)); + + mem_heap_free(heap); + + /* 4. INITIALIZE THE PAGE */ + + page_header_set_field(page, NULL, PAGE_N_DIR_SLOTS, 2); + page_header_set_ptr(page, NULL, PAGE_HEAP_TOP, heap_top); + page_header_set_field(page, NULL, PAGE_N_HEAP, comp + ? 0x8000 | PAGE_HEAP_NO_USER_LOW + : PAGE_HEAP_NO_USER_LOW); + page_header_set_ptr(page, NULL, PAGE_FREE, NULL); + page_header_set_field(page, NULL, PAGE_GARBAGE, 0); + page_header_set_ptr(page, NULL, PAGE_LAST_INSERT, NULL); + page_header_set_field(page, NULL, PAGE_DIRECTION, PAGE_NO_DIRECTION); + page_header_set_field(page, NULL, PAGE_N_DIRECTION, 0); + page_header_set_field(page, NULL, PAGE_N_RECS, 0); + page_set_max_trx_id(block, NULL, ut_dulint_zero); + memset(heap_top, 0, UNIV_PAGE_SIZE - PAGE_EMPTY_DIR_START + - page_offset(heap_top)); + + /* 5. SET POINTERS IN RECORDS AND DIR SLOTS */ + + /* Set the slots to point to infimum and supremum. */ + + slot = page_dir_get_nth_slot(page, 0); + page_dir_slot_set_rec(slot, infimum_rec); + + slot = page_dir_get_nth_slot(page, 1); + page_dir_slot_set_rec(slot, supremum_rec); + + /* Set the next pointers in infimum and supremum */ + + if (UNIV_LIKELY(comp)) { + rec_set_next_offs_new(infimum_rec, PAGE_NEW_SUPREMUM); + rec_set_next_offs_new(supremum_rec, 0); + } else { + rec_set_next_offs_old(infimum_rec, PAGE_OLD_SUPREMUM); + rec_set_next_offs_old(supremum_rec, 0); + } + + return(page); +} + +/************************************************************** +Create an uncompressed B-tree index page. */ +UNIV_INTERN +page_t* +page_create( +/*========*/ + /* out: pointer to the page */ + buf_block_t* block, /* in: a buffer block where the + page is created */ + mtr_t* mtr, /* in: mini-transaction handle */ + ulint comp) /* in: nonzero=compact page format */ +{ + page_create_write_log(buf_block_get_frame(block), mtr, comp); + return(page_create_low(block, comp)); +} + +/************************************************************** +Create a compressed B-tree index page. */ +UNIV_INTERN +page_t* +page_create_zip( +/*============*/ + /* out: pointer to the page */ + buf_block_t* block, /* in/out: a buffer frame where the + page is created */ + dict_index_t* index, /* in: the index of the page */ + ulint level, /* in: the B-tree level of the page */ + mtr_t* mtr) /* in: mini-transaction handle */ +{ + page_t* page; + page_zip_des_t* page_zip = buf_block_get_page_zip(block); + + ut_ad(block); + ut_ad(page_zip); + ut_ad(index); + ut_ad(dict_table_is_comp(index->table)); + + page = page_create_low(block, TRUE); + mach_write_to_2(page + PAGE_HEADER + PAGE_LEVEL, level); + + if (UNIV_UNLIKELY(!page_zip_compress(page_zip, page, index, mtr))) { + /* The compression of a newly created page + should always succeed. */ + ut_error; + } + + return(page); +} + +/***************************************************************** +Differs from page_copy_rec_list_end, because this function does not +touch the lock table and max trx id on page or compress the page. */ +UNIV_INTERN +void +page_copy_rec_list_end_no_locks( +/*============================*/ + buf_block_t* new_block, /* in: index page to copy to */ + buf_block_t* block, /* in: index page of rec */ + rec_t* rec, /* in: record on page */ + dict_index_t* index, /* in: record descriptor */ + mtr_t* mtr) /* in: mtr */ +{ + page_t* new_page = buf_block_get_frame(new_block); + page_cur_t cur1; + rec_t* cur2; + mem_heap_t* heap = NULL; + ulint offsets_[REC_OFFS_NORMAL_SIZE]; + ulint* offsets = offsets_; + rec_offs_init(offsets_); + + page_cur_position(rec, block, &cur1); + + if (page_cur_is_before_first(&cur1)) { + + page_cur_move_to_next(&cur1); + } + + ut_a((ibool)!!page_is_comp(new_page) + == dict_table_is_comp(index->table)); + ut_a(page_is_comp(new_page) == page_rec_is_comp(rec)); + ut_a(mach_read_from_2(new_page + UNIV_PAGE_SIZE - 10) == (ulint) + (page_is_comp(new_page) ? PAGE_NEW_INFIMUM : PAGE_OLD_INFIMUM)); + + cur2 = page_get_infimum_rec(buf_block_get_frame(new_block)); + + /* Copy records from the original page to the new page */ + + while (!page_cur_is_after_last(&cur1)) { + rec_t* cur1_rec = page_cur_get_rec(&cur1); + rec_t* ins_rec; + offsets = rec_get_offsets(cur1_rec, index, offsets, + ULINT_UNDEFINED, &heap); + ins_rec = page_cur_insert_rec_low(cur2, index, + cur1_rec, offsets, mtr); + if (UNIV_UNLIKELY(!ins_rec)) { + /* Track an assertion failure reported on the mailing + list on June 18th, 2003 */ + + buf_page_print(new_page, 0); + buf_page_print(page_align(rec), 0); + ut_print_timestamp(stderr); + + fprintf(stderr, + "InnoDB: rec offset %lu, cur1 offset %lu," + " cur2 offset %lu\n", + (ulong) page_offset(rec), + (ulong) page_offset(page_cur_get_rec(&cur1)), + (ulong) page_offset(cur2)); + ut_error; + } + + page_cur_move_to_next(&cur1); + cur2 = ins_rec; + } + + if (UNIV_LIKELY_NULL(heap)) { + mem_heap_free(heap); + } +} + +/***************************************************************** +Copies records from page to new_page, from a given record onward, +including that record. Infimum and supremum records are not copied. +The records are copied to the start of the record list on new_page. */ +UNIV_INTERN +rec_t* +page_copy_rec_list_end( +/*===================*/ + /* out: pointer to the original + successor of the infimum record + on new_page, or NULL on zip overflow + (new_block will be decompressed) */ + buf_block_t* new_block, /* in/out: index page to copy to */ + buf_block_t* block, /* in: index page containing rec */ + rec_t* rec, /* in: record on page */ + dict_index_t* index, /* in: record descriptor */ + mtr_t* mtr) /* in: mtr */ +{ + page_t* new_page = buf_block_get_frame(new_block); + page_zip_des_t* new_page_zip = buf_block_get_page_zip(new_block); + page_t* page = page_align(rec); + rec_t* ret = page_rec_get_next( + page_get_infimum_rec(new_page)); + ulint log_mode = 0; /* remove warning */ + +#ifdef UNIV_ZIP_DEBUG + if (new_page_zip) { + page_zip_des_t* page_zip = buf_block_get_page_zip(block); + ut_a(page_zip); + + /* Strict page_zip_validate() may fail here. + Furthermore, btr_compress() may set FIL_PAGE_PREV to + FIL_NULL on new_page while leaving it intact on + new_page_zip. So, we cannot validate new_page_zip. */ + ut_a(page_zip_validate_low(page_zip, page, TRUE)); + } +#endif /* UNIV_ZIP_DEBUG */ + ut_ad(buf_block_get_frame(block) == page); + ut_ad(page_is_leaf(page) == page_is_leaf(new_page)); + ut_ad(page_is_comp(page) == page_is_comp(new_page)); + /* Here, "ret" may be pointing to a user record or the + predefined supremum record. */ + + if (UNIV_LIKELY_NULL(new_page_zip)) { + log_mode = mtr_set_log_mode(mtr, MTR_LOG_NONE); + } + + if (page_dir_get_n_heap(new_page) == PAGE_HEAP_NO_USER_LOW) { + page_copy_rec_list_end_to_created_page(new_page, rec, + index, mtr); + } else { + page_copy_rec_list_end_no_locks(new_block, block, rec, + index, mtr); + } + + if (UNIV_LIKELY_NULL(new_page_zip)) { + mtr_set_log_mode(mtr, log_mode); + + if (UNIV_UNLIKELY + (!page_zip_compress(new_page_zip, new_page, index, mtr))) { + /* Before trying to reorganize the page, + store the number of preceding records on the page. */ + ulint ret_pos + = page_rec_get_n_recs_before(ret); + /* Before copying, "ret" was the successor of + the predefined infimum record. It must still + have at least one predecessor (the predefined + infimum record, or a freshly copied record + that is smaller than "ret"). */ + ut_a(ret_pos > 0); + + if (UNIV_UNLIKELY + (!page_zip_reorganize(new_block, index, mtr))) { + + if (UNIV_UNLIKELY + (!page_zip_decompress(new_page_zip, + new_page))) { + ut_error; + } + ut_ad(page_validate(new_page, index)); + return(NULL); + } else { + /* The page was reorganized: + Seek to ret_pos. */ + ret = new_page + PAGE_NEW_INFIMUM; + + do { + ret = rec_get_next_ptr(ret, TRUE); + } while (--ret_pos); + } + } + } + + /* Update the lock table, MAX_TRX_ID, and possible hash index */ + + lock_move_rec_list_end(new_block, block, rec); + + page_update_max_trx_id(new_block, new_page_zip, + page_get_max_trx_id(page)); + + btr_search_move_or_delete_hash_entries(new_block, block, index); + + return(ret); +} + +/***************************************************************** +Copies records from page to new_page, up to the given record, +NOT including that record. Infimum and supremum records are not copied. +The records are copied to the end of the record list on new_page. */ +UNIV_INTERN +rec_t* +page_copy_rec_list_start( +/*=====================*/ + /* out: pointer to the original + predecessor of the supremum record + on new_page, or NULL on zip overflow + (new_block will be decompressed) */ + buf_block_t* new_block, /* in/out: index page to copy to */ + buf_block_t* block, /* in: index page containing rec */ + rec_t* rec, /* in: record on page */ + dict_index_t* index, /* in: record descriptor */ + mtr_t* mtr) /* in: mtr */ +{ + page_t* new_page = buf_block_get_frame(new_block); + page_zip_des_t* new_page_zip = buf_block_get_page_zip(new_block); + page_cur_t cur1; + rec_t* cur2; + ulint log_mode = 0 /* remove warning */; + mem_heap_t* heap = NULL; + rec_t* ret + = page_rec_get_prev(page_get_supremum_rec(new_page)); + ulint offsets_[REC_OFFS_NORMAL_SIZE]; + ulint* offsets = offsets_; + rec_offs_init(offsets_); + + /* Here, "ret" may be pointing to a user record or the + predefined infimum record. */ + + if (page_rec_is_infimum(rec)) { + + return(ret); + } + + if (UNIV_LIKELY_NULL(new_page_zip)) { + log_mode = mtr_set_log_mode(mtr, MTR_LOG_NONE); + } + + page_cur_set_before_first(block, &cur1); + page_cur_move_to_next(&cur1); + + cur2 = ret; + + /* Copy records from the original page to the new page */ + + while (page_cur_get_rec(&cur1) != rec) { + rec_t* cur1_rec = page_cur_get_rec(&cur1); + offsets = rec_get_offsets(cur1_rec, index, offsets, + ULINT_UNDEFINED, &heap); + cur2 = page_cur_insert_rec_low(cur2, index, + cur1_rec, offsets, mtr); + ut_a(cur2); + + page_cur_move_to_next(&cur1); + } + + if (UNIV_LIKELY_NULL(heap)) { + mem_heap_free(heap); + } + + if (UNIV_LIKELY_NULL(new_page_zip)) { + mtr_set_log_mode(mtr, log_mode); + + if (UNIV_UNLIKELY + (!page_zip_compress(new_page_zip, new_page, index, mtr))) { + /* Before trying to reorganize the page, + store the number of preceding records on the page. */ + ulint ret_pos + = page_rec_get_n_recs_before(ret); + /* Before copying, "ret" was the predecessor + of the predefined supremum record. If it was + the predefined infimum record, then it would + still be the infimum. Thus, the assertion + ut_a(ret_pos > 0) would fail here. */ + + if (UNIV_UNLIKELY + (!page_zip_reorganize(new_block, index, mtr))) { + + if (UNIV_UNLIKELY + (!page_zip_decompress(new_page_zip, + new_page))) { + ut_error; + } + ut_ad(page_validate(new_page, index)); + return(NULL); + } else { + /* The page was reorganized: + Seek to ret_pos. */ + ret = new_page + PAGE_NEW_INFIMUM; + + do { + ret = rec_get_next_ptr(ret, TRUE); + } while (--ret_pos); + } + } + } + + /* Update MAX_TRX_ID, the lock table, and possible hash index */ + + page_update_max_trx_id(new_block, new_page_zip, + page_get_max_trx_id(page_align(rec))); + + lock_move_rec_list_start(new_block, block, rec, ret); + + btr_search_move_or_delete_hash_entries(new_block, block, index); + + return(ret); +} + +/************************************************************** +Writes a log record of a record list end or start deletion. */ +UNIV_INLINE +void +page_delete_rec_list_write_log( +/*===========================*/ + rec_t* rec, /* in: record on page */ + dict_index_t* index, /* in: record descriptor */ + byte type, /* in: operation type: + MLOG_LIST_END_DELETE, ... */ + mtr_t* mtr) /* in: mtr */ +{ + byte* log_ptr; + ut_ad(type == MLOG_LIST_END_DELETE + || type == MLOG_LIST_START_DELETE + || type == MLOG_COMP_LIST_END_DELETE + || type == MLOG_COMP_LIST_START_DELETE); + + log_ptr = mlog_open_and_write_index(mtr, rec, index, type, 2); + if (log_ptr) { + /* Write the parameter as a 2-byte ulint */ + mach_write_to_2(log_ptr, page_offset(rec)); + mlog_close(mtr, log_ptr + 2); + } +} + +/************************************************************** +Parses a log record of a record list end or start deletion. */ +UNIV_INTERN +byte* +page_parse_delete_rec_list( +/*=======================*/ + /* out: end of log record or NULL */ + byte type, /* in: MLOG_LIST_END_DELETE, + MLOG_LIST_START_DELETE, + MLOG_COMP_LIST_END_DELETE or + MLOG_COMP_LIST_START_DELETE */ + byte* ptr, /* in: buffer */ + byte* end_ptr,/* in: buffer end */ + buf_block_t* block, /* in/out: buffer block or NULL */ + dict_index_t* index, /* in: record descriptor */ + mtr_t* mtr) /* in: mtr or NULL */ +{ + page_t* page; + ulint offset; + + ut_ad(type == MLOG_LIST_END_DELETE + || type == MLOG_LIST_START_DELETE + || type == MLOG_COMP_LIST_END_DELETE + || type == MLOG_COMP_LIST_START_DELETE); + + /* Read the record offset as a 2-byte ulint */ + + if (end_ptr < ptr + 2) { + + return(NULL); + } + + offset = mach_read_from_2(ptr); + ptr += 2; + + if (!block) { + + return(ptr); + } + + page = buf_block_get_frame(block); + + ut_ad(!!page_is_comp(page) == dict_table_is_comp(index->table)); + + if (type == MLOG_LIST_END_DELETE + || type == MLOG_COMP_LIST_END_DELETE) { + page_delete_rec_list_end(page + offset, block, index, + ULINT_UNDEFINED, ULINT_UNDEFINED, + mtr); + } else { + page_delete_rec_list_start(page + offset, block, index, mtr); + } + + return(ptr); +} + +/***************************************************************** +Deletes records from a page from a given record onward, including that record. +The infimum and supremum records are not deleted. */ +UNIV_INTERN +void +page_delete_rec_list_end( +/*=====================*/ + rec_t* rec, /* in: pointer to record on page */ + buf_block_t* block, /* in: buffer block of the page */ + dict_index_t* index, /* in: record descriptor */ + ulint n_recs, /* in: number of records to delete, + or ULINT_UNDEFINED if not known */ + ulint size, /* in: the sum of the sizes of the + records in the end of the chain to + delete, or ULINT_UNDEFINED if not known */ + mtr_t* mtr) /* in: mtr */ +{ + page_dir_slot_t*slot; + ulint slot_index; + rec_t* last_rec; + rec_t* prev_rec; + ulint n_owned; + page_zip_des_t* page_zip = buf_block_get_page_zip(block); + page_t* page = page_align(rec); + mem_heap_t* heap = NULL; + ulint offsets_[REC_OFFS_NORMAL_SIZE]; + ulint* offsets = offsets_; + rec_offs_init(offsets_); + + ut_ad(size == ULINT_UNDEFINED || size < UNIV_PAGE_SIZE); + ut_ad(!page_zip || page_rec_is_comp(rec)); +#ifdef UNIV_ZIP_DEBUG + ut_a(!page_zip || page_zip_validate(page_zip, page)); +#endif /* UNIV_ZIP_DEBUG */ + + if (page_rec_is_infimum(rec)) { + rec = page_rec_get_next(rec); + } + + if (page_rec_is_supremum(rec)) { + + return; + } + + /* Reset the last insert info in the page header and increment + the modify clock for the frame */ + + page_header_set_ptr(page, page_zip, PAGE_LAST_INSERT, NULL); + + /* The page gets invalid for optimistic searches: increment the + frame modify clock */ + + buf_block_modify_clock_inc(block); + + page_delete_rec_list_write_log(rec, index, page_is_comp(page) + ? MLOG_COMP_LIST_END_DELETE + : MLOG_LIST_END_DELETE, mtr); + + if (UNIV_LIKELY_NULL(page_zip)) { + ulint log_mode; + + ut_a(page_is_comp(page)); + /* Individual deletes are not logged */ + + log_mode = mtr_set_log_mode(mtr, MTR_LOG_NONE); + + do { + page_cur_t cur; + page_cur_position(rec, block, &cur); + + offsets = rec_get_offsets(rec, index, offsets, + ULINT_UNDEFINED, &heap); + rec = rec_get_next_ptr(rec, TRUE); +#ifdef UNIV_ZIP_DEBUG + ut_a(page_zip_validate(page_zip, page)); +#endif /* UNIV_ZIP_DEBUG */ + page_cur_delete_rec(&cur, index, offsets, mtr); + } while (page_offset(rec) != PAGE_NEW_SUPREMUM); + + if (UNIV_LIKELY_NULL(heap)) { + mem_heap_free(heap); + } + + /* Restore log mode */ + + mtr_set_log_mode(mtr, log_mode); + return; + } + + prev_rec = page_rec_get_prev(rec); + + last_rec = page_rec_get_prev(page_get_supremum_rec(page)); + + if ((size == ULINT_UNDEFINED) || (n_recs == ULINT_UNDEFINED)) { + rec_t* rec2 = rec; + /* Calculate the sum of sizes and the number of records */ + size = 0; + n_recs = 0; + + do { + ulint s; + offsets = rec_get_offsets(rec2, index, offsets, + ULINT_UNDEFINED, &heap); + s = rec_offs_size(offsets); + ut_ad(rec2 - page + s - rec_offs_extra_size(offsets) + < UNIV_PAGE_SIZE); + ut_ad(size + s < UNIV_PAGE_SIZE); + size += s; + n_recs++; + + rec2 = page_rec_get_next(rec2); + } while (!page_rec_is_supremum(rec2)); + + if (UNIV_LIKELY_NULL(heap)) { + mem_heap_free(heap); + } + } + + ut_ad(size < UNIV_PAGE_SIZE); + + /* Update the page directory; there is no need to balance the number + of the records owned by the supremum record, as it is allowed to be + less than PAGE_DIR_SLOT_MIN_N_OWNED */ + + if (page_is_comp(page)) { + rec_t* rec2 = rec; + ulint count = 0; + + while (rec_get_n_owned_new(rec2) == 0) { + count++; + + rec2 = rec_get_next_ptr(rec2, TRUE); + } + + ut_ad(rec_get_n_owned_new(rec2) > count); + + n_owned = rec_get_n_owned_new(rec2) - count; + slot_index = page_dir_find_owner_slot(rec2); + slot = page_dir_get_nth_slot(page, slot_index); + } else { + rec_t* rec2 = rec; + ulint count = 0; + + while (rec_get_n_owned_old(rec2) == 0) { + count++; + + rec2 = rec_get_next_ptr(rec2, FALSE); + } + + ut_ad(rec_get_n_owned_old(rec2) > count); + + n_owned = rec_get_n_owned_old(rec2) - count; + slot_index = page_dir_find_owner_slot(rec2); + slot = page_dir_get_nth_slot(page, slot_index); + } + + page_dir_slot_set_rec(slot, page_get_supremum_rec(page)); + page_dir_slot_set_n_owned(slot, NULL, n_owned); + + page_dir_set_n_slots(page, NULL, slot_index + 1); + + /* Remove the record chain segment from the record chain */ + page_rec_set_next(prev_rec, page_get_supremum_rec(page)); + + /* Catenate the deleted chain segment to the page free list */ + + page_rec_set_next(last_rec, page_header_get_ptr(page, PAGE_FREE)); + page_header_set_ptr(page, NULL, PAGE_FREE, rec); + + page_header_set_field(page, NULL, PAGE_GARBAGE, size + + page_header_get_field(page, PAGE_GARBAGE)); + + page_header_set_field(page, NULL, PAGE_N_RECS, + (ulint)(page_get_n_recs(page) - n_recs)); +} + +/***************************************************************** +Deletes records from page, up to the given record, NOT including +that record. Infimum and supremum records are not deleted. */ +UNIV_INTERN +void +page_delete_rec_list_start( +/*=======================*/ + rec_t* rec, /* in: record on page */ + buf_block_t* block, /* in: buffer block of the page */ + dict_index_t* index, /* in: record descriptor */ + mtr_t* mtr) /* in: mtr */ +{ + page_cur_t cur1; + ulint log_mode; + ulint offsets_[REC_OFFS_NORMAL_SIZE]; + ulint* offsets = offsets_; + mem_heap_t* heap = NULL; + byte type; + + rec_offs_init(offsets_); + + ut_ad((ibool) !!page_rec_is_comp(rec) + == dict_table_is_comp(index->table)); +#ifdef UNIV_ZIP_DEBUG + { + page_zip_des_t* page_zip= buf_block_get_page_zip(block); + page_t* page = buf_block_get_frame(block); + + /* page_zip_validate() would detect a min_rec_mark mismatch + in btr_page_split_and_insert() + between btr_attach_half_pages() and insert_page = ... + when btr_page_get_split_rec_to_left() holds + (direction == FSP_DOWN). */ + ut_a(!page_zip || page_zip_validate_low(page_zip, page, TRUE)); + } +#endif /* UNIV_ZIP_DEBUG */ + + if (page_rec_is_infimum(rec)) { + + return; + } + + if (page_rec_is_comp(rec)) { + type = MLOG_COMP_LIST_START_DELETE; + } else { + type = MLOG_LIST_START_DELETE; + } + + page_delete_rec_list_write_log(rec, index, type, mtr); + + page_cur_set_before_first(block, &cur1); + page_cur_move_to_next(&cur1); + + /* Individual deletes are not logged */ + + log_mode = mtr_set_log_mode(mtr, MTR_LOG_NONE); + + while (page_cur_get_rec(&cur1) != rec) { + offsets = rec_get_offsets(page_cur_get_rec(&cur1), index, + offsets, ULINT_UNDEFINED, &heap); + page_cur_delete_rec(&cur1, index, offsets, mtr); + } + + if (UNIV_LIKELY_NULL(heap)) { + mem_heap_free(heap); + } + + /* Restore log mode */ + + mtr_set_log_mode(mtr, log_mode); +} + +/***************************************************************** +Moves record list end to another page. Moved records include +split_rec. */ +UNIV_INTERN +ibool +page_move_rec_list_end( +/*===================*/ + /* out: TRUE on success; FALSE on + compression failure + (new_block will be decompressed) */ + buf_block_t* new_block, /* in/out: index page where to move */ + buf_block_t* block, /* in: index page from where to move */ + rec_t* split_rec, /* in: first record to move */ + dict_index_t* index, /* in: record descriptor */ + mtr_t* mtr) /* in: mtr */ +{ + page_t* new_page = buf_block_get_frame(new_block); + ulint old_data_size; + ulint new_data_size; + ulint old_n_recs; + ulint new_n_recs; + + old_data_size = page_get_data_size(new_page); + old_n_recs = page_get_n_recs(new_page); +#ifdef UNIV_ZIP_DEBUG + { + page_zip_des_t* new_page_zip + = buf_block_get_page_zip(new_block); + page_zip_des_t* page_zip + = buf_block_get_page_zip(block); + ut_a(!new_page_zip == !page_zip); + ut_a(!new_page_zip + || page_zip_validate(new_page_zip, new_page)); + ut_a(!page_zip + || page_zip_validate(page_zip, page_align(split_rec))); + } +#endif /* UNIV_ZIP_DEBUG */ + + if (UNIV_UNLIKELY(!page_copy_rec_list_end(new_block, block, + split_rec, index, mtr))) { + return(FALSE); + } + + new_data_size = page_get_data_size(new_page); + new_n_recs = page_get_n_recs(new_page); + + ut_ad(new_data_size >= old_data_size); + + page_delete_rec_list_end(split_rec, block, index, + new_n_recs - old_n_recs, + new_data_size - old_data_size, mtr); + + return(TRUE); +} + +/***************************************************************** +Moves record list start to another page. Moved records do not include +split_rec. */ +UNIV_INTERN +ibool +page_move_rec_list_start( +/*=====================*/ + /* out: TRUE on success; FALSE on + compression failure */ + buf_block_t* new_block, /* in/out: index page where to move */ + buf_block_t* block, /* in/out: page containing split_rec */ + rec_t* split_rec, /* in: first record not to move */ + dict_index_t* index, /* in: record descriptor */ + mtr_t* mtr) /* in: mtr */ +{ + if (UNIV_UNLIKELY(!page_copy_rec_list_start(new_block, block, + split_rec, index, mtr))) { + return(FALSE); + } + + page_delete_rec_list_start(split_rec, block, index, mtr); + + return(TRUE); +} + +/*************************************************************************** +This is a low-level operation which is used in a database index creation +to update the page number of a created B-tree to a data dictionary record. */ +UNIV_INTERN +void +page_rec_write_index_page_no( +/*=========================*/ + rec_t* rec, /* in: record to update */ + ulint i, /* in: index of the field to update */ + ulint page_no,/* in: value to write */ + mtr_t* mtr) /* in: mtr */ +{ + byte* data; + ulint len; + + data = rec_get_nth_field_old(rec, i, &len); + + ut_ad(len == 4); + + mlog_write_ulint(data, page_no, MLOG_4BYTES, mtr); +} + +/****************************************************************** +Used to delete n slots from the directory. This function updates +also n_owned fields in the records, so that the first slot after +the deleted ones inherits the records of the deleted slots. */ +UNIV_INLINE +void +page_dir_delete_slot( +/*=================*/ + page_t* page, /* in/out: the index page */ + page_zip_des_t* page_zip,/* in/out: compressed page, or NULL */ + ulint slot_no)/* in: slot to be deleted */ +{ + page_dir_slot_t* slot; + ulint n_owned; + ulint i; + ulint n_slots; + + ut_ad(!page_zip || page_is_comp(page)); + ut_ad(slot_no > 0); + ut_ad(slot_no + 1 < page_dir_get_n_slots(page)); + + n_slots = page_dir_get_n_slots(page); + + /* 1. Reset the n_owned fields of the slots to be + deleted */ + slot = page_dir_get_nth_slot(page, slot_no); + n_owned = page_dir_slot_get_n_owned(slot); + page_dir_slot_set_n_owned(slot, page_zip, 0); + + /* 2. Update the n_owned value of the first non-deleted slot */ + + slot = page_dir_get_nth_slot(page, slot_no + 1); + page_dir_slot_set_n_owned(slot, page_zip, + n_owned + page_dir_slot_get_n_owned(slot)); + + /* 3. Destroy the slot by copying slots */ + for (i = slot_no + 1; i < n_slots; i++) { + rec_t* rec = (rec_t*) + page_dir_slot_get_rec(page_dir_get_nth_slot(page, i)); + page_dir_slot_set_rec(page_dir_get_nth_slot(page, i - 1), rec); + } + + /* 4. Zero out the last slot, which will be removed */ + mach_write_to_2(page_dir_get_nth_slot(page, n_slots - 1), 0); + + /* 5. Update the page header */ + page_header_set_field(page, page_zip, PAGE_N_DIR_SLOTS, n_slots - 1); +} + +/****************************************************************** +Used to add n slots to the directory. Does not set the record pointers +in the added slots or update n_owned values: this is the responsibility +of the caller. */ +UNIV_INLINE +void +page_dir_add_slot( +/*==============*/ + page_t* page, /* in/out: the index page */ + page_zip_des_t* page_zip,/* in/out: comprssed page, or NULL */ + ulint start) /* in: the slot above which the new slots + are added */ +{ + page_dir_slot_t* slot; + ulint n_slots; + + n_slots = page_dir_get_n_slots(page); + + ut_ad(start < n_slots - 1); + + /* Update the page header */ + page_dir_set_n_slots(page, page_zip, n_slots + 1); + + /* Move slots up */ + slot = page_dir_get_nth_slot(page, n_slots); + memmove(slot, slot + PAGE_DIR_SLOT_SIZE, + (n_slots - 1 - start) * PAGE_DIR_SLOT_SIZE); +} + +/******************************************************************** +Splits a directory slot which owns too many records. */ +UNIV_INTERN +void +page_dir_split_slot( +/*================*/ + page_t* page, /* in/out: index page */ + page_zip_des_t* page_zip,/* in/out: compressed page whose + uncompressed part will be written, or NULL */ + ulint slot_no)/* in: the directory slot */ +{ + rec_t* rec; + page_dir_slot_t* new_slot; + page_dir_slot_t* prev_slot; + page_dir_slot_t* slot; + ulint i; + ulint n_owned; + + ut_ad(page); + ut_ad(!page_zip || page_is_comp(page)); + ut_ad(slot_no > 0); + + slot = page_dir_get_nth_slot(page, slot_no); + + n_owned = page_dir_slot_get_n_owned(slot); + ut_ad(n_owned == PAGE_DIR_SLOT_MAX_N_OWNED + 1); + + /* 1. We loop to find a record approximately in the middle of the + records owned by the slot. */ + + prev_slot = page_dir_get_nth_slot(page, slot_no - 1); + rec = (rec_t*) page_dir_slot_get_rec(prev_slot); + + for (i = 0; i < n_owned / 2; i++) { + rec = page_rec_get_next(rec); + } + + ut_ad(n_owned / 2 >= PAGE_DIR_SLOT_MIN_N_OWNED); + + /* 2. We add one directory slot immediately below the slot to be + split. */ + + page_dir_add_slot(page, page_zip, slot_no - 1); + + /* The added slot is now number slot_no, and the old slot is + now number slot_no + 1 */ + + new_slot = page_dir_get_nth_slot(page, slot_no); + slot = page_dir_get_nth_slot(page, slot_no + 1); + + /* 3. We store the appropriate values to the new slot. */ + + page_dir_slot_set_rec(new_slot, rec); + page_dir_slot_set_n_owned(new_slot, page_zip, n_owned / 2); + + /* 4. Finally, we update the number of records field of the + original slot */ + + page_dir_slot_set_n_owned(slot, page_zip, n_owned - (n_owned / 2)); +} + +/***************************************************************** +Tries to balance the given directory slot with too few records with the upper +neighbor, so that there are at least the minimum number of records owned by +the slot; this may result in the merging of two slots. */ +UNIV_INTERN +void +page_dir_balance_slot( +/*==================*/ + page_t* page, /* in/out: index page */ + page_zip_des_t* page_zip,/* in/out: compressed page, or NULL */ + ulint slot_no)/* in: the directory slot */ +{ + page_dir_slot_t* slot; + page_dir_slot_t* up_slot; + ulint n_owned; + ulint up_n_owned; + rec_t* old_rec; + rec_t* new_rec; + + ut_ad(page); + ut_ad(!page_zip || page_is_comp(page)); + ut_ad(slot_no > 0); + + slot = page_dir_get_nth_slot(page, slot_no); + + /* The last directory slot cannot be balanced with the upper + neighbor, as there is none. */ + + if (UNIV_UNLIKELY(slot_no == page_dir_get_n_slots(page) - 1)) { + + return; + } + + up_slot = page_dir_get_nth_slot(page, slot_no + 1); + + n_owned = page_dir_slot_get_n_owned(slot); + up_n_owned = page_dir_slot_get_n_owned(up_slot); + + ut_ad(n_owned == PAGE_DIR_SLOT_MIN_N_OWNED - 1); + + /* If the upper slot has the minimum value of n_owned, we will merge + the two slots, therefore we assert: */ + ut_ad(2 * PAGE_DIR_SLOT_MIN_N_OWNED - 1 <= PAGE_DIR_SLOT_MAX_N_OWNED); + + if (up_n_owned > PAGE_DIR_SLOT_MIN_N_OWNED) { + + /* In this case we can just transfer one record owned + by the upper slot to the property of the lower slot */ + old_rec = (rec_t*) page_dir_slot_get_rec(slot); + + if (page_is_comp(page)) { + new_rec = rec_get_next_ptr(old_rec, TRUE); + + rec_set_n_owned_new(old_rec, page_zip, 0); + rec_set_n_owned_new(new_rec, page_zip, n_owned + 1); + } else { + new_rec = rec_get_next_ptr(old_rec, FALSE); + + rec_set_n_owned_old(old_rec, 0); + rec_set_n_owned_old(new_rec, n_owned + 1); + } + + page_dir_slot_set_rec(slot, new_rec); + + page_dir_slot_set_n_owned(up_slot, page_zip, up_n_owned -1); + } else { + /* In this case we may merge the two slots */ + page_dir_delete_slot(page, page_zip, slot_no); + } +} + +/**************************************************************** +Returns the middle record of the record list. If there are an even number +of records in the list, returns the first record of the upper half-list. */ +UNIV_INTERN +rec_t* +page_get_middle_rec( +/*================*/ + /* out: middle record */ + page_t* page) /* in: page */ +{ + page_dir_slot_t* slot; + ulint middle; + ulint i; + ulint n_owned; + ulint count; + rec_t* rec; + + /* This many records we must leave behind */ + middle = (page_get_n_recs(page) + PAGE_HEAP_NO_USER_LOW) / 2; + + count = 0; + + for (i = 0;; i++) { + + slot = page_dir_get_nth_slot(page, i); + n_owned = page_dir_slot_get_n_owned(slot); + + if (count + n_owned > middle) { + break; + } else { + count += n_owned; + } + } + + ut_ad(i > 0); + slot = page_dir_get_nth_slot(page, i - 1); + rec = (rec_t*) page_dir_slot_get_rec(slot); + rec = page_rec_get_next(rec); + + /* There are now count records behind rec */ + + for (i = 0; i < middle - count; i++) { + rec = page_rec_get_next(rec); + } + + return(rec); +} + +/******************************************************************* +Returns the number of records before the given record in chain. +The number includes infimum and supremum records. */ +UNIV_INTERN +ulint +page_rec_get_n_recs_before( +/*=======================*/ + /* out: number of records */ + const rec_t* rec) /* in: the physical record */ +{ + const page_dir_slot_t* slot; + const rec_t* slot_rec; + const page_t* page; + ulint i; + lint n = 0; + + ut_ad(page_rec_check(rec)); + + page = page_align(rec); + if (page_is_comp(page)) { + while (rec_get_n_owned_new(rec) == 0) { + + rec = rec_get_next_ptr_const(rec, TRUE); + n--; + } + + for (i = 0; ; i++) { + slot = page_dir_get_nth_slot(page, i); + slot_rec = page_dir_slot_get_rec(slot); + + n += rec_get_n_owned_new(slot_rec); + + if (rec == slot_rec) { + + break; + } + } + } else { + while (rec_get_n_owned_old(rec) == 0) { + + rec = rec_get_next_ptr_const(rec, FALSE); + n--; + } + + for (i = 0; ; i++) { + slot = page_dir_get_nth_slot(page, i); + slot_rec = page_dir_slot_get_rec(slot); + + n += rec_get_n_owned_old(slot_rec); + + if (rec == slot_rec) { + + break; + } + } + } + + n--; + + ut_ad(n >= 0); + + return((ulint) n); +} + +/**************************************************************** +Prints record contents including the data relevant only in +the index page context. */ +UNIV_INTERN +void +page_rec_print( +/*===========*/ + const rec_t* rec, /* in: physical record */ + const ulint* offsets)/* in: record descriptor */ +{ + ut_a(!page_rec_is_comp(rec) == !rec_offs_comp(offsets)); + rec_print_new(stderr, rec, offsets); + if (page_rec_is_comp(rec)) { + fprintf(stderr, + " n_owned: %lu; heap_no: %lu; next rec: %lu\n", + (ulong) rec_get_n_owned_new(rec), + (ulong) rec_get_heap_no_new(rec), + (ulong) rec_get_next_offs(rec, TRUE)); + } else { + fprintf(stderr, + " n_owned: %lu; heap_no: %lu; next rec: %lu\n", + (ulong) rec_get_n_owned_old(rec), + (ulong) rec_get_heap_no_old(rec), + (ulong) rec_get_next_offs(rec, TRUE)); + } + + page_rec_check(rec); + rec_validate(rec, offsets); +} + +/******************************************************************* +This is used to print the contents of the directory for +debugging purposes. */ +UNIV_INTERN +void +page_dir_print( +/*===========*/ + page_t* page, /* in: index page */ + ulint pr_n) /* in: print n first and n last entries */ +{ + ulint n; + ulint i; + page_dir_slot_t* slot; + + n = page_dir_get_n_slots(page); + + fprintf(stderr, "--------------------------------\n" + "PAGE DIRECTORY\n" + "Page address %p\n" + "Directory stack top at offs: %lu; number of slots: %lu\n", + page, (ulong) page_offset(page_dir_get_nth_slot(page, n - 1)), + (ulong) n); + for (i = 0; i < n; i++) { + slot = page_dir_get_nth_slot(page, i); + if ((i == pr_n) && (i < n - pr_n)) { + fputs(" ... \n", stderr); + } + if ((i < pr_n) || (i >= n - pr_n)) { + fprintf(stderr, + "Contents of slot: %lu: n_owned: %lu," + " rec offs: %lu\n", + (ulong) i, + (ulong) page_dir_slot_get_n_owned(slot), + (ulong) + page_offset(page_dir_slot_get_rec(slot))); + } + } + fprintf(stderr, "Total of %lu records\n" + "--------------------------------\n", + (ulong) (PAGE_HEAP_NO_USER_LOW + page_get_n_recs(page))); +} + +/******************************************************************* +This is used to print the contents of the page record list for +debugging purposes. */ +UNIV_INTERN +void +page_print_list( +/*============*/ + buf_block_t* block, /* in: index page */ + dict_index_t* index, /* in: dictionary index of the page */ + ulint pr_n) /* in: print n first and n last entries */ +{ + page_t* page = block->frame; + page_cur_t cur; + ulint count; + ulint n_recs; + mem_heap_t* heap = NULL; + ulint offsets_[REC_OFFS_NORMAL_SIZE]; + ulint* offsets = offsets_; + rec_offs_init(offsets_); + + ut_a((ibool)!!page_is_comp(page) == dict_table_is_comp(index->table)); + + fprintf(stderr, + "--------------------------------\n" + "PAGE RECORD LIST\n" + "Page address %p\n", page); + + n_recs = page_get_n_recs(page); + + page_cur_set_before_first(block, &cur); + count = 0; + for (;;) { + offsets = rec_get_offsets(cur.rec, index, offsets, + ULINT_UNDEFINED, &heap); + page_rec_print(cur.rec, offsets); + + if (count == pr_n) { + break; + } + if (page_cur_is_after_last(&cur)) { + break; + } + page_cur_move_to_next(&cur); + count++; + } + + if (n_recs > 2 * pr_n) { + fputs(" ... \n", stderr); + } + + while (!page_cur_is_after_last(&cur)) { + page_cur_move_to_next(&cur); + + if (count + pr_n >= n_recs) { + offsets = rec_get_offsets(cur.rec, index, offsets, + ULINT_UNDEFINED, &heap); + page_rec_print(cur.rec, offsets); + } + count++; + } + + fprintf(stderr, + "Total of %lu records \n" + "--------------------------------\n", + (ulong) (count + 1)); + + if (UNIV_LIKELY_NULL(heap)) { + mem_heap_free(heap); + } +} + +/******************************************************************* +Prints the info in a page header. */ +UNIV_INTERN +void +page_header_print( +/*==============*/ + const page_t* page) +{ + fprintf(stderr, + "--------------------------------\n" + "PAGE HEADER INFO\n" + "Page address %p, n records %lu (%s)\n" + "n dir slots %lu, heap top %lu\n" + "Page n heap %lu, free %lu, garbage %lu\n" + "Page last insert %lu, direction %lu, n direction %lu\n", + page, (ulong) page_header_get_field(page, PAGE_N_RECS), + page_is_comp(page) ? "compact format" : "original format", + (ulong) page_header_get_field(page, PAGE_N_DIR_SLOTS), + (ulong) page_header_get_field(page, PAGE_HEAP_TOP), + (ulong) page_dir_get_n_heap(page), + (ulong) page_header_get_field(page, PAGE_FREE), + (ulong) page_header_get_field(page, PAGE_GARBAGE), + (ulong) page_header_get_field(page, PAGE_LAST_INSERT), + (ulong) page_header_get_field(page, PAGE_DIRECTION), + (ulong) page_header_get_field(page, PAGE_N_DIRECTION)); +} + +/******************************************************************* +This is used to print the contents of the page for +debugging purposes. */ +UNIV_INTERN +void +page_print( +/*=======*/ + buf_block_t* block, /* in: index page */ + dict_index_t* index, /* in: dictionary index of the page */ + ulint dn, /* in: print dn first and last entries + in directory */ + ulint rn) /* in: print rn first and last records + in directory */ +{ + page_t* page = block->frame; + + page_header_print(page); + page_dir_print(page, dn); + page_print_list(block, index, rn); +} + +/******************************************************************* +The following is used to validate a record on a page. This function +differs from rec_validate as it can also check the n_owned field and +the heap_no field. */ +UNIV_INTERN +ibool +page_rec_validate( +/*==============*/ + /* out: TRUE if ok */ + rec_t* rec, /* in: physical record */ + const ulint* offsets)/* in: array returned by rec_get_offsets() */ +{ + ulint n_owned; + ulint heap_no; + page_t* page; + + page = page_align(rec); + ut_a(!page_is_comp(page) == !rec_offs_comp(offsets)); + + page_rec_check(rec); + rec_validate(rec, offsets); + + if (page_rec_is_comp(rec)) { + n_owned = rec_get_n_owned_new(rec); + heap_no = rec_get_heap_no_new(rec); + } else { + n_owned = rec_get_n_owned_old(rec); + heap_no = rec_get_heap_no_old(rec); + } + + if (UNIV_UNLIKELY(!(n_owned <= PAGE_DIR_SLOT_MAX_N_OWNED))) { + fprintf(stderr, + "InnoDB: Dir slot of rec %lu, n owned too big %lu\n", + (ulong) page_offset(rec), (ulong) n_owned); + return(FALSE); + } + + if (UNIV_UNLIKELY(!(heap_no < page_dir_get_n_heap(page)))) { + fprintf(stderr, + "InnoDB: Heap no of rec %lu too big %lu %lu\n", + (ulong) page_offset(rec), (ulong) heap_no, + (ulong) page_dir_get_n_heap(page)); + return(FALSE); + } + + return(TRUE); +} + +/******************************************************************* +Checks that the first directory slot points to the infimum record and +the last to the supremum. This function is intended to track if the +bug fixed in 4.0.14 has caused corruption to users' databases. */ +UNIV_INTERN +void +page_check_dir( +/*===========*/ + const page_t* page) /* in: index page */ +{ + ulint n_slots; + ulint infimum_offs; + ulint supremum_offs; + + n_slots = page_dir_get_n_slots(page); + infimum_offs = mach_read_from_2(page_dir_get_nth_slot(page, 0)); + supremum_offs = mach_read_from_2(page_dir_get_nth_slot(page, + n_slots - 1)); + + if (UNIV_UNLIKELY(!page_rec_is_infimum_low(infimum_offs))) { + + fprintf(stderr, + "InnoDB: Page directory corruption:" + " infimum not pointed to\n"); + buf_page_print(page, 0); + } + + if (UNIV_UNLIKELY(!page_rec_is_supremum_low(supremum_offs))) { + + fprintf(stderr, + "InnoDB: Page directory corruption:" + " supremum not pointed to\n"); + buf_page_print(page, 0); + } +} + +/******************************************************************* +This function checks the consistency of an index page when we do not +know the index. This is also resilient so that this should never crash +even if the page is total garbage. */ +UNIV_INTERN +ibool +page_simple_validate_old( +/*=====================*/ + /* out: TRUE if ok */ + page_t* page) /* in: old-style index page */ +{ + page_dir_slot_t* slot; + ulint slot_no; + ulint n_slots; + rec_t* rec; + byte* rec_heap_top; + ulint count; + ulint own_count; + ibool ret = FALSE; + + ut_a(!page_is_comp(page)); + + /* Check first that the record heap and the directory do not + overlap. */ + + n_slots = page_dir_get_n_slots(page); + + if (UNIV_UNLIKELY(n_slots > UNIV_PAGE_SIZE / 4)) { + fprintf(stderr, + "InnoDB: Nonsensical number %lu of page dir slots\n", + (ulong) n_slots); + + goto func_exit; + } + + rec_heap_top = page_header_get_ptr(page, PAGE_HEAP_TOP); + + if (UNIV_UNLIKELY(rec_heap_top + > page_dir_get_nth_slot(page, n_slots - 1))) { + + fprintf(stderr, + "InnoDB: Record heap and dir overlap on a page," + " heap top %lu, dir %lu\n", + (ulong) page_header_get_field(page, PAGE_HEAP_TOP), + (ulong) + page_offset(page_dir_get_nth_slot(page, n_slots - 1))); + + goto func_exit; + } + + /* Validate the record list in a loop checking also that it is + consistent with the page record directory. */ + + count = 0; + own_count = 1; + slot_no = 0; + slot = page_dir_get_nth_slot(page, slot_no); + + rec = page_get_infimum_rec(page); + + for (;;) { + if (UNIV_UNLIKELY(rec > rec_heap_top)) { + fprintf(stderr, + "InnoDB: Record %lu is above" + " rec heap top %lu\n", + (ulong)(rec - page), + (ulong)(rec_heap_top - page)); + + goto func_exit; + } + + if (UNIV_UNLIKELY(rec_get_n_owned_old(rec))) { + /* This is a record pointed to by a dir slot */ + if (UNIV_UNLIKELY(rec_get_n_owned_old(rec) + != own_count)) { + + fprintf(stderr, + "InnoDB: Wrong owned count %lu, %lu," + " rec %lu\n", + (ulong) rec_get_n_owned_old(rec), + (ulong) own_count, + (ulong)(rec - page)); + + goto func_exit; + } + + if (UNIV_UNLIKELY + (page_dir_slot_get_rec(slot) != rec)) { + fprintf(stderr, + "InnoDB: Dir slot does not point" + " to right rec %lu\n", + (ulong)(rec - page)); + + goto func_exit; + } + + own_count = 0; + + if (!page_rec_is_supremum(rec)) { + slot_no++; + slot = page_dir_get_nth_slot(page, slot_no); + } + } + + if (page_rec_is_supremum(rec)) { + + break; + } + + if (UNIV_UNLIKELY + (rec_get_next_offs(rec, FALSE) < FIL_PAGE_DATA + || rec_get_next_offs(rec, FALSE) >= UNIV_PAGE_SIZE)) { + fprintf(stderr, + "InnoDB: Next record offset" + " nonsensical %lu for rec %lu\n", + (ulong) rec_get_next_offs(rec, FALSE), + (ulong) (rec - page)); + + goto func_exit; + } + + count++; + + if (UNIV_UNLIKELY(count > UNIV_PAGE_SIZE)) { + fprintf(stderr, + "InnoDB: Page record list appears" + " to be circular %lu\n", + (ulong) count); + goto func_exit; + } + + rec = page_rec_get_next(rec); + own_count++; + } + + if (UNIV_UNLIKELY(rec_get_n_owned_old(rec) == 0)) { + fprintf(stderr, "InnoDB: n owned is zero in a supremum rec\n"); + + goto func_exit; + } + + if (UNIV_UNLIKELY(slot_no != n_slots - 1)) { + fprintf(stderr, "InnoDB: n slots wrong %lu, %lu\n", + (ulong) slot_no, (ulong) (n_slots - 1)); + goto func_exit; + } + + if (UNIV_UNLIKELY(page_header_get_field(page, PAGE_N_RECS) + + PAGE_HEAP_NO_USER_LOW + != count + 1)) { + fprintf(stderr, "InnoDB: n recs wrong %lu %lu\n", + (ulong) page_header_get_field(page, PAGE_N_RECS) + + PAGE_HEAP_NO_USER_LOW, + (ulong) (count + 1)); + + goto func_exit; + } + + /* Check then the free list */ + rec = page_header_get_ptr(page, PAGE_FREE); + + while (rec != NULL) { + if (UNIV_UNLIKELY(rec < page + FIL_PAGE_DATA + || rec >= page + UNIV_PAGE_SIZE)) { + fprintf(stderr, + "InnoDB: Free list record has" + " a nonsensical offset %lu\n", + (ulong) (rec - page)); + + goto func_exit; + } + + if (UNIV_UNLIKELY(rec > rec_heap_top)) { + fprintf(stderr, + "InnoDB: Free list record %lu" + " is above rec heap top %lu\n", + (ulong) (rec - page), + (ulong) (rec_heap_top - page)); + + goto func_exit; + } + + count++; + + if (UNIV_UNLIKELY(count > UNIV_PAGE_SIZE)) { + fprintf(stderr, + "InnoDB: Page free list appears" + " to be circular %lu\n", + (ulong) count); + goto func_exit; + } + + rec = page_rec_get_next(rec); + } + + if (UNIV_UNLIKELY(page_dir_get_n_heap(page) != count + 1)) { + + fprintf(stderr, "InnoDB: N heap is wrong %lu, %lu\n", + (ulong) page_dir_get_n_heap(page), + (ulong) (count + 1)); + + goto func_exit; + } + + ret = TRUE; + +func_exit: + return(ret); +} + +/******************************************************************* +This function checks the consistency of an index page when we do not +know the index. This is also resilient so that this should never crash +even if the page is total garbage. */ +UNIV_INTERN +ibool +page_simple_validate_new( +/*=====================*/ + /* out: TRUE if ok */ + page_t* page) /* in: new-style index page */ +{ + page_dir_slot_t* slot; + ulint slot_no; + ulint n_slots; + rec_t* rec; + byte* rec_heap_top; + ulint count; + ulint own_count; + ibool ret = FALSE; + + ut_a(page_is_comp(page)); + + /* Check first that the record heap and the directory do not + overlap. */ + + n_slots = page_dir_get_n_slots(page); + + if (UNIV_UNLIKELY(n_slots > UNIV_PAGE_SIZE / 4)) { + fprintf(stderr, + "InnoDB: Nonsensical number %lu" + " of page dir slots\n", (ulong) n_slots); + + goto func_exit; + } + + rec_heap_top = page_header_get_ptr(page, PAGE_HEAP_TOP); + + if (UNIV_UNLIKELY(rec_heap_top + > page_dir_get_nth_slot(page, n_slots - 1))) { + + fprintf(stderr, + "InnoDB: Record heap and dir overlap on a page," + " heap top %lu, dir %lu\n", + (ulong) page_header_get_field(page, PAGE_HEAP_TOP), + (ulong) + page_offset(page_dir_get_nth_slot(page, n_slots - 1))); + + goto func_exit; + } + + /* Validate the record list in a loop checking also that it is + consistent with the page record directory. */ + + count = 0; + own_count = 1; + slot_no = 0; + slot = page_dir_get_nth_slot(page, slot_no); + + rec = page_get_infimum_rec(page); + + for (;;) { + if (UNIV_UNLIKELY(rec > rec_heap_top)) { + fprintf(stderr, + "InnoDB: Record %lu is above rec" + " heap top %lu\n", + (ulong) page_offset(rec), + (ulong) page_offset(rec_heap_top)); + + goto func_exit; + } + + if (UNIV_UNLIKELY(rec_get_n_owned_new(rec))) { + /* This is a record pointed to by a dir slot */ + if (UNIV_UNLIKELY(rec_get_n_owned_new(rec) + != own_count)) { + + fprintf(stderr, + "InnoDB: Wrong owned count %lu, %lu," + " rec %lu\n", + (ulong) rec_get_n_owned_new(rec), + (ulong) own_count, + (ulong) page_offset(rec)); + + goto func_exit; + } + + if (UNIV_UNLIKELY + (page_dir_slot_get_rec(slot) != rec)) { + fprintf(stderr, + "InnoDB: Dir slot does not point" + " to right rec %lu\n", + (ulong) page_offset(rec)); + + goto func_exit; + } + + own_count = 0; + + if (!page_rec_is_supremum(rec)) { + slot_no++; + slot = page_dir_get_nth_slot(page, slot_no); + } + } + + if (page_rec_is_supremum(rec)) { + + break; + } + + if (UNIV_UNLIKELY + (rec_get_next_offs(rec, TRUE) < FIL_PAGE_DATA + || rec_get_next_offs(rec, TRUE) >= UNIV_PAGE_SIZE)) { + fprintf(stderr, + "InnoDB: Next record offset nonsensical %lu" + " for rec %lu\n", + (ulong) rec_get_next_offs(rec, TRUE), + (ulong) page_offset(rec)); + + goto func_exit; + } + + count++; + + if (UNIV_UNLIKELY(count > UNIV_PAGE_SIZE)) { + fprintf(stderr, + "InnoDB: Page record list appears" + " to be circular %lu\n", + (ulong) count); + goto func_exit; + } + + rec = page_rec_get_next(rec); + own_count++; + } + + if (UNIV_UNLIKELY(rec_get_n_owned_new(rec) == 0)) { + fprintf(stderr, "InnoDB: n owned is zero" + " in a supremum rec\n"); + + goto func_exit; + } + + if (UNIV_UNLIKELY(slot_no != n_slots - 1)) { + fprintf(stderr, "InnoDB: n slots wrong %lu, %lu\n", + (ulong) slot_no, (ulong) (n_slots - 1)); + goto func_exit; + } + + if (UNIV_UNLIKELY(page_header_get_field(page, PAGE_N_RECS) + + PAGE_HEAP_NO_USER_LOW + != count + 1)) { + fprintf(stderr, "InnoDB: n recs wrong %lu %lu\n", + (ulong) page_header_get_field(page, PAGE_N_RECS) + + PAGE_HEAP_NO_USER_LOW, + (ulong) (count + 1)); + + goto func_exit; + } + + /* Check then the free list */ + rec = page_header_get_ptr(page, PAGE_FREE); + + while (rec != NULL) { + if (UNIV_UNLIKELY(rec < page + FIL_PAGE_DATA + || rec >= page + UNIV_PAGE_SIZE)) { + fprintf(stderr, + "InnoDB: Free list record has" + " a nonsensical offset %lu\n", + (ulong) page_offset(rec)); + + goto func_exit; + } + + if (UNIV_UNLIKELY(rec > rec_heap_top)) { + fprintf(stderr, + "InnoDB: Free list record %lu" + " is above rec heap top %lu\n", + (ulong) page_offset(rec), + (ulong) page_offset(rec_heap_top)); + + goto func_exit; + } + + count++; + + if (UNIV_UNLIKELY(count > UNIV_PAGE_SIZE)) { + fprintf(stderr, + "InnoDB: Page free list appears" + " to be circular %lu\n", + (ulong) count); + goto func_exit; + } + + rec = page_rec_get_next(rec); + } + + if (UNIV_UNLIKELY(page_dir_get_n_heap(page) != count + 1)) { + + fprintf(stderr, "InnoDB: N heap is wrong %lu, %lu\n", + (ulong) page_dir_get_n_heap(page), + (ulong) (count + 1)); + + goto func_exit; + } + + ret = TRUE; + +func_exit: + return(ret); +} + +/******************************************************************* +This function checks the consistency of an index page. */ +UNIV_INTERN +ibool +page_validate( +/*==========*/ + /* out: TRUE if ok */ + page_t* page, /* in: index page */ + dict_index_t* index) /* in: data dictionary index containing + the page record type definition */ +{ + page_dir_slot_t*slot; + mem_heap_t* heap; + byte* buf; + ulint count; + ulint own_count; + ulint rec_own_count; + ulint slot_no; + ulint data_size; + rec_t* rec; + rec_t* old_rec = NULL; + ulint offs; + ulint n_slots; + ibool ret = FALSE; + ulint i; + ulint* offsets = NULL; + ulint* old_offsets = NULL; + + if (UNIV_UNLIKELY((ibool) !!page_is_comp(page) + != dict_table_is_comp(index->table))) { + fputs("InnoDB: 'compact format' flag mismatch\n", stderr); + goto func_exit2; + } + if (page_is_comp(page)) { + if (UNIV_UNLIKELY(!page_simple_validate_new(page))) { + goto func_exit2; + } + } else { + if (UNIV_UNLIKELY(!page_simple_validate_old(page))) { + goto func_exit2; + } + } + + heap = mem_heap_create(UNIV_PAGE_SIZE + 200); + + /* The following buffer is used to check that the + records in the page record heap do not overlap */ + + buf = mem_heap_zalloc(heap, UNIV_PAGE_SIZE); + + /* Check first that the record heap and the directory do not + overlap. */ + + n_slots = page_dir_get_n_slots(page); + + if (UNIV_UNLIKELY(!(page_header_get_ptr(page, PAGE_HEAP_TOP) + <= page_dir_get_nth_slot(page, n_slots - 1)))) { + + fputs("InnoDB: Record heap and dir overlap on a page ", + stderr); + dict_index_name_print(stderr, NULL, index); + fprintf(stderr, ", %p, %p\n", + page_header_get_ptr(page, PAGE_HEAP_TOP), + page_dir_get_nth_slot(page, n_slots - 1)); + + goto func_exit; + } + + /* Validate the record list in a loop checking also that + it is consistent with the directory. */ + count = 0; + data_size = 0; + own_count = 1; + slot_no = 0; + slot = page_dir_get_nth_slot(page, slot_no); + + rec = page_get_infimum_rec(page); + + for (;;) { + offsets = rec_get_offsets(rec, index, offsets, + ULINT_UNDEFINED, &heap); + + if (page_is_comp(page) && page_rec_is_user_rec(rec) + && UNIV_UNLIKELY(rec_get_node_ptr_flag(rec) + == page_is_leaf(page))) { + fputs("InnoDB: node_ptr flag mismatch\n", stderr); + goto func_exit; + } + + if (UNIV_UNLIKELY(!page_rec_validate(rec, offsets))) { + goto func_exit; + } + + /* Check that the records are in the ascending order */ + if (UNIV_LIKELY(count >= PAGE_HEAP_NO_USER_LOW) + && !page_rec_is_supremum(rec)) { + if (UNIV_UNLIKELY + (1 != cmp_rec_rec(rec, old_rec, + offsets, old_offsets, index))) { + fprintf(stderr, + "InnoDB: Records in wrong order" + " on page %lu ", + (ulong) page_get_page_no(page)); + dict_index_name_print(stderr, NULL, index); + fputs("\nInnoDB: previous record ", stderr); + rec_print_new(stderr, old_rec, old_offsets); + fputs("\nInnoDB: record ", stderr); + rec_print_new(stderr, rec, offsets); + putc('\n', stderr); + + goto func_exit; + } + } + + if (page_rec_is_user_rec(rec)) { + + data_size += rec_offs_size(offsets); + } + + offs = page_offset(rec_get_start(rec, offsets)); + + for (i = rec_offs_size(offsets); i--; ) { + if (UNIV_UNLIKELY(buf[offs + i])) { + /* No other record may overlap this */ + + fputs("InnoDB: Record overlaps another\n", + stderr); + goto func_exit; + } + + buf[offs + i] = 1; + } + + if (page_is_comp(page)) { + rec_own_count = rec_get_n_owned_new(rec); + } else { + rec_own_count = rec_get_n_owned_old(rec); + } + + if (UNIV_UNLIKELY(rec_own_count)) { + /* This is a record pointed to by a dir slot */ + if (UNIV_UNLIKELY(rec_own_count != own_count)) { + fprintf(stderr, + "InnoDB: Wrong owned count %lu, %lu\n", + (ulong) rec_own_count, + (ulong) own_count); + goto func_exit; + } + + if (page_dir_slot_get_rec(slot) != rec) { + fputs("InnoDB: Dir slot does not" + " point to right rec\n", + stderr); + goto func_exit; + } + + page_dir_slot_check(slot); + + own_count = 0; + if (!page_rec_is_supremum(rec)) { + slot_no++; + slot = page_dir_get_nth_slot(page, slot_no); + } + } + + if (page_rec_is_supremum(rec)) { + break; + } + + count++; + own_count++; + old_rec = rec; + rec = page_rec_get_next(rec); + + /* set old_offsets to offsets; recycle offsets */ + { + ulint* offs = old_offsets; + old_offsets = offsets; + offsets = offs; + } + } + + if (page_is_comp(page)) { + if (UNIV_UNLIKELY(rec_get_n_owned_new(rec) == 0)) { + + goto n_owned_zero; + } + } else if (UNIV_UNLIKELY(rec_get_n_owned_old(rec) == 0)) { +n_owned_zero: + fputs("InnoDB: n owned is zero\n", stderr); + goto func_exit; + } + + if (UNIV_UNLIKELY(slot_no != n_slots - 1)) { + fprintf(stderr, "InnoDB: n slots wrong %lu %lu\n", + (ulong) slot_no, (ulong) (n_slots - 1)); + goto func_exit; + } + + if (UNIV_UNLIKELY(page_header_get_field(page, PAGE_N_RECS) + + PAGE_HEAP_NO_USER_LOW + != count + 1)) { + fprintf(stderr, "InnoDB: n recs wrong %lu %lu\n", + (ulong) page_header_get_field(page, PAGE_N_RECS) + + PAGE_HEAP_NO_USER_LOW, + (ulong) (count + 1)); + goto func_exit; + } + + if (UNIV_UNLIKELY(data_size != page_get_data_size(page))) { + fprintf(stderr, + "InnoDB: Summed data size %lu, returned by func %lu\n", + (ulong) data_size, (ulong) page_get_data_size(page)); + goto func_exit; + } + + /* Check then the free list */ + rec = page_header_get_ptr(page, PAGE_FREE); + + while (rec != NULL) { + offsets = rec_get_offsets(rec, index, offsets, + ULINT_UNDEFINED, &heap); + if (UNIV_UNLIKELY(!page_rec_validate(rec, offsets))) { + + goto func_exit; + } + + count++; + offs = page_offset(rec_get_start(rec, offsets)); + + for (i = rec_offs_size(offsets); i--; ) { + + if (UNIV_UNLIKELY(buf[offs + i])) { + fputs("InnoDB: Record overlaps another" + " in free list\n", stderr); + goto func_exit; + } + + buf[offs + i] = 1; + } + + rec = page_rec_get_next(rec); + } + + if (UNIV_UNLIKELY(page_dir_get_n_heap(page) != count + 1)) { + fprintf(stderr, "InnoDB: N heap is wrong %lu %lu\n", + (ulong) page_dir_get_n_heap(page), + (ulong) count + 1); + goto func_exit; + } + + ret = TRUE; + +func_exit: + mem_heap_free(heap); + + if (UNIV_UNLIKELY(ret == FALSE)) { +func_exit2: + fprintf(stderr, "InnoDB: Apparent corruption in page %lu in ", + (ulong) page_get_page_no(page)); + dict_index_name_print(stderr, NULL, index); + putc('\n', stderr); + buf_page_print(page, 0); + } + + return(ret); +} + +/******************************************************************* +Looks in the page record list for a record with the given heap number. */ +UNIV_INTERN +const rec_t* +page_find_rec_with_heap_no( +/*=======================*/ + /* out: record, NULL if not found */ + const page_t* page, /* in: index page */ + ulint heap_no)/* in: heap number */ +{ + const rec_t* rec; + + if (page_is_comp(page)) { + rec = page + PAGE_NEW_INFIMUM; + + for(;;) { + ulint rec_heap_no = rec_get_heap_no_new(rec); + + if (rec_heap_no == heap_no) { + + return(rec); + } else if (rec_heap_no == PAGE_HEAP_NO_SUPREMUM) { + + return(NULL); + } + + rec = page + rec_get_next_offs(rec, TRUE); + } + } else { + rec = page + PAGE_OLD_INFIMUM; + + for (;;) { + ulint rec_heap_no = rec_get_heap_no_old(rec); + + if (rec_heap_no == heap_no) { + + return(rec); + } else if (rec_heap_no == PAGE_HEAP_NO_SUPREMUM) { + + return(NULL); + } + + rec = page + rec_get_next_offs(rec, FALSE); + } + } +} diff --git a/storage/xtradb/page/page0zip.c b/storage/xtradb/page/page0zip.c new file mode 100644 index 00000000000..56189ce3bad --- /dev/null +++ b/storage/xtradb/page/page0zip.c @@ -0,0 +1,4575 @@ +/***************************************************************************** + +Copyright (c) 2005, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/****************************************************** +Compressed page interface + +Created June 2005 by Marko Makela +*******************************************************/ + +#define THIS_MODULE +#include "page0zip.h" +#ifdef UNIV_NONINL +# include "page0zip.ic" +#endif +#undef THIS_MODULE +#include "page0page.h" +#include "mtr0log.h" +#include "ut0sort.h" +#include "dict0boot.h" +#include "dict0dict.h" +#include "btr0sea.h" +#include "btr0cur.h" +#include "page0types.h" +#include "lock0lock.h" +#include "log0recv.h" +#include "zlib.h" +#include "buf0lru.h" + +/** Statistics on compression, indexed by page_zip_des_t::ssize - 1 */ +UNIV_INTERN page_zip_stat_t page_zip_stat[PAGE_ZIP_NUM_SSIZE - 1]; + +/* Please refer to ../include/page0zip.ic for a description of the +compressed page format. */ + +/* The infimum and supremum records are omitted from the compressed page. +On compress, we compare that the records are there, and on uncompress we +restore the records. */ +static const byte infimum_extra[] = { + 0x01, /* info_bits=0, n_owned=1 */ + 0x00, 0x02 /* heap_no=0, status=2 */ + /* ?, ? */ /* next=(first user rec, or supremum) */ +}; +static const byte infimum_data[] = { + 0x69, 0x6e, 0x66, 0x69, + 0x6d, 0x75, 0x6d, 0x00 /* "infimum\0" */ +}; +static const byte supremum_extra_data[] = { + /* 0x0?, */ /* info_bits=0, n_owned=1..8 */ + 0x00, 0x0b, /* heap_no=1, status=3 */ + 0x00, 0x00, /* next=0 */ + 0x73, 0x75, 0x70, 0x72, + 0x65, 0x6d, 0x75, 0x6d /* "supremum" */ +}; + +/** Assert that a block of memory is filled with zero bytes. +Compare at most sizeof(field_ref_zero) bytes. */ +#define ASSERT_ZERO(b, s) \ + ut_ad(!memcmp(b, field_ref_zero, ut_min(s, sizeof field_ref_zero))) +/** Assert that a BLOB pointer is filled with zero bytes. */ +#define ASSERT_ZERO_BLOB(b) \ + ut_ad(!memcmp(b, field_ref_zero, sizeof field_ref_zero)) + +/* Enable some extra debugging output. This code can be enabled +independently of any UNIV_ debugging conditions. */ +#if defined UNIV_DEBUG || defined UNIV_ZIP_DEBUG +# include <stdarg.h> +__attribute__((format (printf, 1, 2))) +/************************************************************************** +Report a failure to decompress or compress. */ +static +int +page_zip_fail_func( +/*===============*/ + /* out: number of characters printed */ + const char* fmt, /* in: printf(3) format string */ + ...) /* in: arguments corresponding to fmt */ +{ + int res; + va_list ap; + + ut_print_timestamp(stderr); + fputs(" InnoDB: ", stderr); + va_start(ap, fmt); + res = vfprintf(stderr, fmt, ap); + va_end(ap); + + return(res); +} +# define page_zip_fail(fmt_args) page_zip_fail_func fmt_args +#else /* UNIV_DEBUG || UNIV_ZIP_DEBUG */ +# define page_zip_fail(fmt_args) /* empty */ +#endif /* UNIV_DEBUG || UNIV_ZIP_DEBUG */ + +/************************************************************************** +Determine the guaranteed free space on an empty page. */ +UNIV_INTERN +ulint +page_zip_empty_size( +/*================*/ + /* out: minimum payload size on the page */ + ulint n_fields, /* in: number of columns in the index */ + ulint zip_size) /* in: compressed page size in bytes */ +{ + lint size = zip_size + /* subtract the page header and the longest + uncompressed data needed for one record */ + - (PAGE_DATA + + PAGE_ZIP_DIR_SLOT_SIZE + + DATA_TRX_ID_LEN + DATA_ROLL_PTR_LEN + + 1/* encoded heap_no==2 in page_zip_write_rec() */ + + 1/* end of modification log */ + - REC_N_NEW_EXTRA_BYTES/* omitted bytes */) + /* subtract the space for page_zip_fields_encode() */ + - compressBound(2 * (n_fields + 1)); + return(size > 0 ? (ulint) size : 0); +} + +/***************************************************************** +Gets the size of the compressed page trailer (the dense page directory), +including deleted records (the free list). */ +UNIV_INLINE +ulint +page_zip_dir_size( +/*==============*/ + /* out: length of dense page + directory, in bytes */ + const page_zip_des_t* page_zip) /* in: compressed page */ +{ + /* Exclude the page infimum and supremum from the record count. */ + ulint size = PAGE_ZIP_DIR_SLOT_SIZE + * (page_dir_get_n_heap(page_zip->data) + - PAGE_HEAP_NO_USER_LOW); + return(size); +} + +/***************************************************************** +Gets the size of the compressed page trailer (the dense page directory), +only including user records (excluding the free list). */ +UNIV_INLINE +ulint +page_zip_dir_user_size( +/*===================*/ + /* out: length of dense page + directory comprising existing + records, in bytes */ + const page_zip_des_t* page_zip) /* in: compressed page */ +{ + ulint size = PAGE_ZIP_DIR_SLOT_SIZE + * page_get_n_recs(page_zip->data); + ut_ad(size <= page_zip_dir_size(page_zip)); + return(size); +} + +/***************************************************************** +Find the slot of the given record in the dense page directory. */ +UNIV_INLINE +byte* +page_zip_dir_find_low( +/*==================*/ + /* out: dense directory slot, + or NULL if record not found */ + byte* slot, /* in: start of records */ + byte* end, /* in: end of records */ + ulint offset) /* in: offset of user record */ +{ + ut_ad(slot <= end); + + for (; slot < end; slot += PAGE_ZIP_DIR_SLOT_SIZE) { + if ((mach_read_from_2(slot) & PAGE_ZIP_DIR_SLOT_MASK) + == offset) { + return(slot); + } + } + + return(NULL); +} + +/***************************************************************** +Find the slot of the given non-free record in the dense page directory. */ +UNIV_INLINE +byte* +page_zip_dir_find( +/*==============*/ + /* out: dense directory slot, + or NULL if record not found */ + page_zip_des_t* page_zip, /* in: compressed page */ + ulint offset) /* in: offset of user record */ +{ + byte* end = page_zip->data + page_zip_get_size(page_zip); + + ut_ad(page_zip_simple_validate(page_zip)); + + return(page_zip_dir_find_low(end - page_zip_dir_user_size(page_zip), + end, + offset)); +} + +/***************************************************************** +Find the slot of the given free record in the dense page directory. */ +UNIV_INLINE +byte* +page_zip_dir_find_free( +/*===================*/ + /* out: dense directory slot, + or NULL if record not found */ + page_zip_des_t* page_zip, /* in: compressed page */ + ulint offset) /* in: offset of user record */ +{ + byte* end = page_zip->data + page_zip_get_size(page_zip); + + ut_ad(page_zip_simple_validate(page_zip)); + + return(page_zip_dir_find_low(end - page_zip_dir_size(page_zip), + end - page_zip_dir_user_size(page_zip), + offset)); +} + +/***************************************************************** +Read a given slot in the dense page directory. */ +UNIV_INLINE +ulint +page_zip_dir_get( +/*=============*/ + /* out: record offset + on the uncompressed page, + possibly ORed with + PAGE_ZIP_DIR_SLOT_DEL or + PAGE_ZIP_DIR_SLOT_OWNED */ + const page_zip_des_t* page_zip, /* in: compressed page */ + ulint slot) /* in: slot + (0=first user record) */ +{ + ut_ad(page_zip_simple_validate(page_zip)); + ut_ad(slot < page_zip_dir_size(page_zip) / PAGE_ZIP_DIR_SLOT_SIZE); + return(mach_read_from_2(page_zip->data + page_zip_get_size(page_zip) + - PAGE_ZIP_DIR_SLOT_SIZE * (slot + 1))); +} + +/************************************************************************** +Write a log record of compressing an index page. */ +static +void +page_zip_compress_write_log( +/*========================*/ + const page_zip_des_t* page_zip,/* in: compressed page */ + const page_t* page, /* in: uncompressed page */ + dict_index_t* index, /* in: index of the B-tree node */ + mtr_t* mtr) /* in: mini-transaction */ +{ + byte* log_ptr; + ulint trailer_size; + + log_ptr = mlog_open(mtr, 11 + 2 + 2); + + if (!log_ptr) { + + return; + } + + /* Read the number of user records. */ + trailer_size = page_dir_get_n_heap(page_zip->data) + - PAGE_HEAP_NO_USER_LOW; + /* Multiply by uncompressed of size stored per record */ + if (!page_is_leaf(page)) { + trailer_size *= PAGE_ZIP_DIR_SLOT_SIZE + REC_NODE_PTR_SIZE; + } else if (dict_index_is_clust(index)) { + trailer_size *= PAGE_ZIP_DIR_SLOT_SIZE + + DATA_TRX_ID_LEN + DATA_ROLL_PTR_LEN; + } else { + trailer_size *= PAGE_ZIP_DIR_SLOT_SIZE; + } + /* Add the space occupied by BLOB pointers. */ + trailer_size += page_zip->n_blobs * BTR_EXTERN_FIELD_REF_SIZE; + ut_a(page_zip->m_end > PAGE_DATA); +#if FIL_PAGE_DATA > PAGE_DATA +# error "FIL_PAGE_DATA > PAGE_DATA" +#endif + ut_a(page_zip->m_end + trailer_size <= page_zip_get_size(page_zip)); + + log_ptr = mlog_write_initial_log_record_fast((page_t*) page, + MLOG_ZIP_PAGE_COMPRESS, + log_ptr, mtr); + mach_write_to_2(log_ptr, page_zip->m_end - FIL_PAGE_TYPE); + log_ptr += 2; + mach_write_to_2(log_ptr, trailer_size); + log_ptr += 2; + mlog_close(mtr, log_ptr); + + /* Write FIL_PAGE_PREV and FIL_PAGE_NEXT */ + mlog_catenate_string(mtr, page_zip->data + FIL_PAGE_PREV, 4); + mlog_catenate_string(mtr, page_zip->data + FIL_PAGE_NEXT, 4); + /* Write most of the page header, the compressed stream and + the modification log. */ + mlog_catenate_string(mtr, page_zip->data + FIL_PAGE_TYPE, + page_zip->m_end - FIL_PAGE_TYPE); + /* Write the uncompressed trailer of the compressed page. */ + mlog_catenate_string(mtr, page_zip->data + page_zip_get_size(page_zip) + - trailer_size, trailer_size); +} + +/********************************************************** +Determine how many externally stored columns are contained +in existing records with smaller heap_no than rec. */ +static +ulint +page_zip_get_n_prev_extern( +/*=======================*/ + const page_zip_des_t* page_zip,/* in: dense page directory on + compressed page */ + const rec_t* rec, /* in: compact physical record + on a B-tree leaf page */ + dict_index_t* index) /* in: record descriptor */ +{ + const page_t* page = page_align(rec); + ulint n_ext = 0; + ulint i; + ulint left; + ulint heap_no; + ulint n_recs = page_get_n_recs(page_zip->data); + + ut_ad(page_is_leaf(page)); + ut_ad(page_is_comp(page)); + ut_ad(dict_table_is_comp(index->table)); + ut_ad(dict_index_is_clust(index)); + + heap_no = rec_get_heap_no_new(rec); + ut_ad(heap_no >= PAGE_HEAP_NO_USER_LOW); + left = heap_no - PAGE_HEAP_NO_USER_LOW; + if (UNIV_UNLIKELY(!left)) { + return(0); + } + + for (i = 0; i < n_recs; i++) { + const rec_t* r = page + (page_zip_dir_get(page_zip, i) + & PAGE_ZIP_DIR_SLOT_MASK); + + if (rec_get_heap_no_new(r) < heap_no) { + n_ext += rec_get_n_extern_new(r, index, + ULINT_UNDEFINED); + if (!--left) { + break; + } + } + } + + return(n_ext); +} + +/************************************************************************** +Encode the length of a fixed-length column. */ +static +byte* +page_zip_fixed_field_encode( +/*========================*/ + /* out: buf + length of encoded val */ + byte* buf, /* in: pointer to buffer where to write */ + ulint val) /* in: value to write */ +{ + ut_ad(val >= 2); + + if (UNIV_LIKELY(val < 126)) { + /* + 0 = nullable variable field of at most 255 bytes length; + 1 = not null variable field of at most 255 bytes length; + 126 = nullable variable field with maximum length >255; + 127 = not null variable field with maximum length >255 + */ + *buf++ = (byte) val; + } else { + *buf++ = (byte) (0x80 | val >> 8); + *buf++ = (byte) val; + } + + return(buf); +} + +/************************************************************************** +Write the index information for the compressed page. */ +static +ulint +page_zip_fields_encode( +/*===================*/ + /* out: used size of buf */ + ulint n, /* in: number of fields to compress */ + dict_index_t* index, /* in: index comprising at least n fields */ + ulint trx_id_pos,/* in: position of the trx_id column + in the index, or ULINT_UNDEFINED if + this is a non-leaf page */ + byte* buf) /* out: buffer of (n + 1) * 2 bytes */ +{ + const byte* buf_start = buf; + ulint i; + ulint col; + ulint trx_id_col = 0; + /* sum of lengths of preceding non-nullable fixed fields, or 0 */ + ulint fixed_sum = 0; + + ut_ad(trx_id_pos == ULINT_UNDEFINED || trx_id_pos < n); + + for (i = col = 0; i < n; i++) { + dict_field_t* field = dict_index_get_nth_field(index, i); + ulint val; + + if (dict_field_get_col(field)->prtype & DATA_NOT_NULL) { + val = 1; /* set the "not nullable" flag */ + } else { + val = 0; /* nullable field */ + } + + if (!field->fixed_len) { + /* variable-length field */ + const dict_col_t* column + = dict_field_get_col(field); + + if (UNIV_UNLIKELY(column->len > 255) + || UNIV_UNLIKELY(column->mtype == DATA_BLOB)) { + val |= 0x7e; /* max > 255 bytes */ + } + + if (fixed_sum) { + /* write out the length of any + preceding non-nullable fields */ + buf = page_zip_fixed_field_encode( + buf, fixed_sum << 1 | 1); + fixed_sum = 0; + col++; + } + + *buf++ = (byte) val; + col++; + } else if (val) { + /* fixed-length non-nullable field */ + + if (fixed_sum && UNIV_UNLIKELY + (fixed_sum + field->fixed_len + > DICT_MAX_INDEX_COL_LEN)) { + /* Write out the length of the + preceding non-nullable fields, + to avoid exceeding the maximum + length of a fixed-length column. */ + buf = page_zip_fixed_field_encode( + buf, fixed_sum << 1 | 1); + fixed_sum = 0; + col++; + } + + if (i && UNIV_UNLIKELY(i == trx_id_pos)) { + if (fixed_sum) { + /* Write out the length of any + preceding non-nullable fields, + and start a new trx_id column. */ + buf = page_zip_fixed_field_encode( + buf, fixed_sum << 1 | 1); + col++; + } + + trx_id_col = col; + fixed_sum = field->fixed_len; + } else { + /* add to the sum */ + fixed_sum += field->fixed_len; + } + } else { + /* fixed-length nullable field */ + + if (fixed_sum) { + /* write out the length of any + preceding non-nullable fields */ + buf = page_zip_fixed_field_encode( + buf, fixed_sum << 1 | 1); + fixed_sum = 0; + col++; + } + + buf = page_zip_fixed_field_encode( + buf, field->fixed_len << 1); + col++; + } + } + + if (fixed_sum) { + /* Write out the lengths of last fixed-length columns. */ + buf = page_zip_fixed_field_encode(buf, fixed_sum << 1 | 1); + } + + if (trx_id_pos != ULINT_UNDEFINED) { + /* Write out the position of the trx_id column */ + i = trx_id_col; + } else { + /* Write out the number of nullable fields */ + i = index->n_nullable; + } + + if (i < 128) { + *buf++ = (byte) i; + } else { + *buf++ = (byte) (0x80 | i >> 8); + *buf++ = (byte) i; + } + + ut_ad((ulint) (buf - buf_start) <= (n + 2) * 2); + return((ulint) (buf - buf_start)); +} + +/************************************************************************** +Populate the dense page directory from the sparse directory. */ +static +void +page_zip_dir_encode( +/*================*/ + const page_t* page, /* in: compact page */ + byte* buf, /* in: pointer to dense page directory[-1]; + out: dense directory on compressed page */ + const rec_t** recs) /* in: pointer to an array of 0, or NULL; + out: dense page directory sorted by ascending + address (and heap_no) */ +{ + const byte* rec; + ulint status; + ulint min_mark; + ulint heap_no; + ulint i; + ulint n_heap; + ulint offs; + + min_mark = 0; + + if (page_is_leaf(page)) { + status = REC_STATUS_ORDINARY; + } else { + status = REC_STATUS_NODE_PTR; + if (UNIV_UNLIKELY + (mach_read_from_4(page + FIL_PAGE_PREV) == FIL_NULL)) { + min_mark = REC_INFO_MIN_REC_FLAG; + } + } + + n_heap = page_dir_get_n_heap(page); + + /* Traverse the list of stored records in the collation order, + starting from the first user record. */ + + rec = page + PAGE_NEW_INFIMUM, TRUE; + + i = 0; + + for (;;) { + ulint info_bits; + offs = rec_get_next_offs(rec, TRUE); + if (UNIV_UNLIKELY(offs == PAGE_NEW_SUPREMUM)) { + break; + } + rec = page + offs; + heap_no = rec_get_heap_no_new(rec); + ut_a(heap_no >= PAGE_HEAP_NO_USER_LOW); + ut_a(heap_no < n_heap); + ut_a(offs < UNIV_PAGE_SIZE - PAGE_DIR); + ut_a(offs >= PAGE_ZIP_START); +#if PAGE_ZIP_DIR_SLOT_MASK & (PAGE_ZIP_DIR_SLOT_MASK + 1) +# error "PAGE_ZIP_DIR_SLOT_MASK is not 1 less than a power of 2" +#endif +#if PAGE_ZIP_DIR_SLOT_MASK < UNIV_PAGE_SIZE - 1 +# error "PAGE_ZIP_DIR_SLOT_MASK < UNIV_PAGE_SIZE - 1" +#endif + if (UNIV_UNLIKELY(rec_get_n_owned_new(rec))) { + offs |= PAGE_ZIP_DIR_SLOT_OWNED; + } + + info_bits = rec_get_info_bits(rec, TRUE); + if (UNIV_UNLIKELY(info_bits & REC_INFO_DELETED_FLAG)) { + info_bits &= ~REC_INFO_DELETED_FLAG; + offs |= PAGE_ZIP_DIR_SLOT_DEL; + } + ut_a(info_bits == min_mark); + /* Only the smallest user record can have + REC_INFO_MIN_REC_FLAG set. */ + min_mark = 0; + + mach_write_to_2(buf - PAGE_ZIP_DIR_SLOT_SIZE * ++i, offs); + + if (UNIV_LIKELY_NULL(recs)) { + /* Ensure that each heap_no occurs at most once. */ + ut_a(!recs[heap_no - PAGE_HEAP_NO_USER_LOW]); + /* exclude infimum and supremum */ + recs[heap_no - PAGE_HEAP_NO_USER_LOW] = rec; + } + + ut_a(rec_get_status(rec) == status); + } + + offs = page_header_get_field(page, PAGE_FREE); + + /* Traverse the free list (of deleted records). */ + while (offs) { + ut_ad(!(offs & ~PAGE_ZIP_DIR_SLOT_MASK)); + rec = page + offs; + + heap_no = rec_get_heap_no_new(rec); + ut_a(heap_no >= PAGE_HEAP_NO_USER_LOW); + ut_a(heap_no < n_heap); + + ut_a(!rec[-REC_N_NEW_EXTRA_BYTES]); /* info_bits and n_owned */ + ut_a(rec_get_status(rec) == status); + + mach_write_to_2(buf - PAGE_ZIP_DIR_SLOT_SIZE * ++i, offs); + + if (UNIV_LIKELY_NULL(recs)) { + /* Ensure that each heap_no occurs at most once. */ + ut_a(!recs[heap_no - PAGE_HEAP_NO_USER_LOW]); + /* exclude infimum and supremum */ + recs[heap_no - PAGE_HEAP_NO_USER_LOW] = rec; + } + + offs = rec_get_next_offs(rec, TRUE); + } + + /* Ensure that each heap no occurs at least once. */ + ut_a(i + PAGE_HEAP_NO_USER_LOW == n_heap); +} + +/************************************************************************** +Allocate memory for zlib. */ +static +void* +page_zip_malloc( +/*============*/ + void* opaque, + uInt items, + uInt size) +{ + return(mem_heap_alloc(opaque, items * size)); +} + +/************************************************************************** +Deallocate memory for zlib. */ +static +void +page_zip_free( +/*==========*/ + void* opaque __attribute__((unused)), + void* address __attribute__((unused))) +{ +} + +/************************************************************************** +Configure the zlib allocator to use the given memory heap. */ +UNIV_INTERN +void +page_zip_set_alloc( +/*===============*/ + void* stream, /* in/out: zlib stream */ + mem_heap_t* heap) /* in: memory heap to use */ +{ + z_stream* strm = stream; + + strm->zalloc = page_zip_malloc; + strm->zfree = page_zip_free; + strm->opaque = heap; +} + +#if 0 || defined UNIV_DEBUG || defined UNIV_ZIP_DEBUG +# define PAGE_ZIP_COMPRESS_DBG +#endif + +#ifdef PAGE_ZIP_COMPRESS_DBG +/* Set this variable in a debugger to enable +excessive logging in page_zip_compress(). */ +UNIV_INTERN ibool page_zip_compress_dbg; +/* Set this variable in a debugger to enable +binary logging of the data passed to deflate(). +When this variable is nonzero, it will act +as a log file name generator. */ +UNIV_INTERN unsigned page_zip_compress_log; + +/************************************************************************** +Wrapper for deflate(). Log the operation if page_zip_compress_dbg is set. */ +static +ibool +page_zip_compress_deflate( +/*======================*/ + FILE* logfile,/* in: log file, or NULL */ + z_streamp strm, /* in/out: compressed stream for deflate() */ + int flush) /* in: deflate() flushing method */ +{ + int status; + if (UNIV_UNLIKELY(page_zip_compress_dbg)) { + ut_print_buf(stderr, strm->next_in, strm->avail_in); + } + if (UNIV_LIKELY_NULL(logfile)) { + fwrite(strm->next_in, 1, strm->avail_in, logfile); + } + status = deflate(strm, flush); + if (UNIV_UNLIKELY(page_zip_compress_dbg)) { + fprintf(stderr, " -> %d\n", status); + } + return(status); +} + +/* Redefine deflate(). */ +# undef deflate +# define deflate(strm, flush) page_zip_compress_deflate(logfile, strm, flush) +# define FILE_LOGFILE FILE* logfile, +# define LOGFILE logfile, +#else /* PAGE_ZIP_COMPRESS_DBG */ +# define FILE_LOGFILE +# define LOGFILE +#endif /* PAGE_ZIP_COMPRESS_DBG */ + +/************************************************************************** +Compress the records of a node pointer page. */ +static +int +page_zip_compress_node_ptrs( +/*========================*/ + /* out: Z_OK, or a zlib error code */ + FILE_LOGFILE + z_stream* c_stream, /* in/out: compressed page stream */ + const rec_t** recs, /* in: dense page directory + sorted by address */ + ulint n_dense, /* in: size of recs[] */ + dict_index_t* index, /* in: the index of the page */ + byte* storage, /* in: end of dense page directory */ + mem_heap_t* heap) /* in: temporary memory heap */ +{ + int err = Z_OK; + ulint* offsets = NULL; + + do { + const rec_t* rec = *recs++; + + offsets = rec_get_offsets(rec, index, offsets, + ULINT_UNDEFINED, &heap); + /* Only leaf nodes may contain externally stored columns. */ + ut_ad(!rec_offs_any_extern(offsets)); + + UNIV_MEM_ASSERT_RW(rec, rec_offs_data_size(offsets)); + UNIV_MEM_ASSERT_RW(rec - rec_offs_extra_size(offsets), + rec_offs_extra_size(offsets)); + + /* Compress the extra bytes. */ + c_stream->avail_in = rec - REC_N_NEW_EXTRA_BYTES + - c_stream->next_in; + + if (c_stream->avail_in) { + err = deflate(c_stream, Z_NO_FLUSH); + if (UNIV_UNLIKELY(err != Z_OK)) { + break; + } + } + ut_ad(!c_stream->avail_in); + + /* Compress the data bytes, except node_ptr. */ + c_stream->next_in = (byte*) rec; + c_stream->avail_in = rec_offs_data_size(offsets) + - REC_NODE_PTR_SIZE; + ut_ad(c_stream->avail_in); + + err = deflate(c_stream, Z_NO_FLUSH); + if (UNIV_UNLIKELY(err != Z_OK)) { + break; + } + + ut_ad(!c_stream->avail_in); + + memcpy(storage - REC_NODE_PTR_SIZE + * (rec_get_heap_no_new(rec) - 1), + c_stream->next_in, REC_NODE_PTR_SIZE); + c_stream->next_in += REC_NODE_PTR_SIZE; + } while (--n_dense); + + return(err); +} + +/************************************************************************** +Compress the records of a leaf node of a secondary index. */ +static +int +page_zip_compress_sec( +/*==================*/ + /* out: Z_OK, or a zlib error code */ + FILE_LOGFILE + z_stream* c_stream, /* in/out: compressed page stream */ + const rec_t** recs, /* in: dense page directory + sorted by address */ + ulint n_dense) /* in: size of recs[] */ +{ + int err = Z_OK; + + ut_ad(n_dense > 0); + + do { + const rec_t* rec = *recs++; + + /* Compress everything up to this record. */ + c_stream->avail_in = rec - REC_N_NEW_EXTRA_BYTES + - c_stream->next_in; + + if (UNIV_LIKELY(c_stream->avail_in)) { + UNIV_MEM_ASSERT_RW(c_stream->next_in, + c_stream->avail_in); + err = deflate(c_stream, Z_NO_FLUSH); + if (UNIV_UNLIKELY(err != Z_OK)) { + break; + } + } + + ut_ad(!c_stream->avail_in); + ut_ad(c_stream->next_in == rec - REC_N_NEW_EXTRA_BYTES); + + /* Skip the REC_N_NEW_EXTRA_BYTES. */ + + c_stream->next_in = (byte*) rec; + } while (--n_dense); + + return(err); +} + +/************************************************************************** +Compress a record of a leaf node of a clustered index that contains +externally stored columns. */ +static +int +page_zip_compress_clust_ext( +/*========================*/ + /* out: Z_OK, or a zlib error code */ + FILE_LOGFILE + z_stream* c_stream, /* in/out: compressed page stream */ + const rec_t* rec, /* in: record */ + const ulint* offsets, /* in: rec_get_offsets(rec) */ + ulint trx_id_col, /* in: position of of DB_TRX_ID */ + byte* deleted, /* in: dense directory entry pointing + to the head of the free list */ + byte* storage, /* in: end of dense page directory */ + byte** externs, /* in/out: pointer to the next + available BLOB pointer */ + ulint* n_blobs) /* in/out: number of + externally stored columns */ +{ + int err; + ulint i; + + UNIV_MEM_ASSERT_RW(rec, rec_offs_data_size(offsets)); + UNIV_MEM_ASSERT_RW(rec - rec_offs_extra_size(offsets), + rec_offs_extra_size(offsets)); + + for (i = 0; i < rec_offs_n_fields(offsets); i++) { + ulint len; + const byte* src; + + if (UNIV_UNLIKELY(i == trx_id_col)) { + ut_ad(!rec_offs_nth_extern(offsets, i)); + /* Store trx_id and roll_ptr + in uncompressed form. */ + src = rec_get_nth_field(rec, offsets, i, &len); + ut_ad(src + DATA_TRX_ID_LEN + == rec_get_nth_field(rec, offsets, + i + 1, &len)); + ut_ad(len == DATA_ROLL_PTR_LEN); + + /* Compress any preceding bytes. */ + c_stream->avail_in + = src - c_stream->next_in; + + if (c_stream->avail_in) { + err = deflate(c_stream, Z_NO_FLUSH); + if (UNIV_UNLIKELY(err != Z_OK)) { + + return(err); + } + } + + ut_ad(!c_stream->avail_in); + ut_ad(c_stream->next_in == src); + + memcpy(storage + - (DATA_TRX_ID_LEN + DATA_ROLL_PTR_LEN) + * (rec_get_heap_no_new(rec) - 1), + c_stream->next_in, + DATA_TRX_ID_LEN + DATA_ROLL_PTR_LEN); + + c_stream->next_in + += DATA_TRX_ID_LEN + DATA_ROLL_PTR_LEN; + + /* Skip also roll_ptr */ + i++; + } else if (rec_offs_nth_extern(offsets, i)) { + src = rec_get_nth_field(rec, offsets, i, &len); + ut_ad(len >= BTR_EXTERN_FIELD_REF_SIZE); + src += len - BTR_EXTERN_FIELD_REF_SIZE; + + c_stream->avail_in = src + - c_stream->next_in; + if (UNIV_LIKELY(c_stream->avail_in)) { + err = deflate(c_stream, Z_NO_FLUSH); + if (UNIV_UNLIKELY(err != Z_OK)) { + + return(err); + } + } + + ut_ad(!c_stream->avail_in); + ut_ad(c_stream->next_in == src); + + /* Reserve space for the data at + the end of the space reserved for + the compressed data and the page + modification log. */ + + if (UNIV_UNLIKELY + (c_stream->avail_out + <= BTR_EXTERN_FIELD_REF_SIZE)) { + /* out of space */ + return(Z_BUF_ERROR); + } + + ut_ad(*externs == c_stream->next_out + + c_stream->avail_out + + 1/* end of modif. log */); + + c_stream->next_in + += BTR_EXTERN_FIELD_REF_SIZE; + + /* Skip deleted records. */ + if (UNIV_LIKELY_NULL + (page_zip_dir_find_low( + storage, deleted, + page_offset(rec)))) { + continue; + } + + (*n_blobs)++; + c_stream->avail_out + -= BTR_EXTERN_FIELD_REF_SIZE; + *externs -= BTR_EXTERN_FIELD_REF_SIZE; + + /* Copy the BLOB pointer */ + memcpy(*externs, c_stream->next_in + - BTR_EXTERN_FIELD_REF_SIZE, + BTR_EXTERN_FIELD_REF_SIZE); + } + } + + return(Z_OK); +} + +/************************************************************************** +Compress the records of a leaf node of a clustered index. */ +static +int +page_zip_compress_clust( +/*====================*/ + /* out: Z_OK, or a zlib error code */ + FILE_LOGFILE + z_stream* c_stream, /* in/out: compressed page stream */ + const rec_t** recs, /* in: dense page directory + sorted by address */ + ulint n_dense, /* in: size of recs[] */ + dict_index_t* index, /* in: the index of the page */ + ulint* n_blobs, /* in: 0; out: number of + externally stored columns */ + ulint trx_id_col, /* index of the trx_id column */ + byte* deleted, /* in: dense directory entry pointing + to the head of the free list */ + byte* storage, /* in: end of dense page directory */ + mem_heap_t* heap) /* in: temporary memory heap */ +{ + int err = Z_OK; + ulint* offsets = NULL; + /* BTR_EXTERN_FIELD_REF storage */ + byte* externs = storage - n_dense + * (DATA_TRX_ID_LEN + DATA_ROLL_PTR_LEN); + + ut_ad(*n_blobs == 0); + + do { + const rec_t* rec = *recs++; + + offsets = rec_get_offsets(rec, index, offsets, + ULINT_UNDEFINED, &heap); + ut_ad(rec_offs_n_fields(offsets) + == dict_index_get_n_fields(index)); + UNIV_MEM_ASSERT_RW(rec, rec_offs_data_size(offsets)); + UNIV_MEM_ASSERT_RW(rec - rec_offs_extra_size(offsets), + rec_offs_extra_size(offsets)); + + /* Compress the extra bytes. */ + c_stream->avail_in = rec - REC_N_NEW_EXTRA_BYTES + - c_stream->next_in; + + if (c_stream->avail_in) { + err = deflate(c_stream, Z_NO_FLUSH); + if (UNIV_UNLIKELY(err != Z_OK)) { + + goto func_exit; + } + } + ut_ad(!c_stream->avail_in); + ut_ad(c_stream->next_in == rec - REC_N_NEW_EXTRA_BYTES); + + /* Compress the data bytes. */ + + c_stream->next_in = (byte*) rec; + + /* Check if there are any externally stored columns. + For each externally stored column, store the + BTR_EXTERN_FIELD_REF separately. */ + if (UNIV_UNLIKELY(rec_offs_any_extern(offsets))) { + ut_ad(dict_index_is_clust(index)); + + err = page_zip_compress_clust_ext( + LOGFILE + c_stream, rec, offsets, trx_id_col, + deleted, storage, &externs, n_blobs); + + if (UNIV_UNLIKELY(err != Z_OK)) { + + goto func_exit; + } + } else { + ulint len; + const byte* src; + + /* Store trx_id and roll_ptr in uncompressed form. */ + src = rec_get_nth_field(rec, offsets, + trx_id_col, &len); + ut_ad(src + DATA_TRX_ID_LEN + == rec_get_nth_field(rec, offsets, + trx_id_col + 1, &len)); + ut_ad(len == DATA_ROLL_PTR_LEN); + UNIV_MEM_ASSERT_RW(rec, rec_offs_data_size(offsets)); + UNIV_MEM_ASSERT_RW(rec - rec_offs_extra_size(offsets), + rec_offs_extra_size(offsets)); + + /* Compress any preceding bytes. */ + c_stream->avail_in = src - c_stream->next_in; + + if (c_stream->avail_in) { + err = deflate(c_stream, Z_NO_FLUSH); + if (UNIV_UNLIKELY(err != Z_OK)) { + + return(err); + } + } + + ut_ad(!c_stream->avail_in); + ut_ad(c_stream->next_in == src); + + memcpy(storage + - (DATA_TRX_ID_LEN + DATA_ROLL_PTR_LEN) + * (rec_get_heap_no_new(rec) - 1), + c_stream->next_in, + DATA_TRX_ID_LEN + DATA_ROLL_PTR_LEN); + + c_stream->next_in + += DATA_TRX_ID_LEN + DATA_ROLL_PTR_LEN; + + /* Skip also roll_ptr */ + ut_ad(trx_id_col + 1 < rec_offs_n_fields(offsets)); + } + + /* Compress the last bytes of the record. */ + c_stream->avail_in = rec + rec_offs_data_size(offsets) + - c_stream->next_in; + + if (c_stream->avail_in) { + err = deflate(c_stream, Z_NO_FLUSH); + if (UNIV_UNLIKELY(err != Z_OK)) { + + goto func_exit; + } + } + ut_ad(!c_stream->avail_in); + } while (--n_dense); + +func_exit: + return(err); +} + +/************************************************************************** +Compress a page. */ +UNIV_INTERN +ibool +page_zip_compress( +/*==============*/ + /* out: TRUE on success, FALSE on failure; + page_zip will be left intact on failure. */ + page_zip_des_t* page_zip,/* in: size; out: data, n_blobs, + m_start, m_end, m_nonempty */ + const page_t* page, /* in: uncompressed page */ + dict_index_t* index, /* in: index of the B-tree node */ + mtr_t* mtr) /* in: mini-transaction, or NULL */ +{ + z_stream c_stream; + int err; + ulint n_fields;/* number of index fields needed */ + byte* fields; /* index field information */ + byte* buf; /* compressed payload of the page */ + byte* buf_end;/* end of buf */ + ulint n_dense; + ulint slot_size;/* amount of uncompressed bytes per record */ + const rec_t** recs; /* dense page directory, sorted by address */ + mem_heap_t* heap; + ulint trx_id_col; + ulint* offsets = NULL; + ulint n_blobs = 0; + byte* storage;/* storage of uncompressed columns */ + ullint usec = ut_time_us(NULL); +#ifdef PAGE_ZIP_COMPRESS_DBG + FILE* logfile = NULL; +#endif + + ut_a(page_is_comp(page)); + ut_a(fil_page_get_type(page) == FIL_PAGE_INDEX); + ut_ad(page_simple_validate_new((page_t*) page)); + ut_ad(page_zip_simple_validate(page_zip)); + + UNIV_MEM_ASSERT_RW(page, UNIV_PAGE_SIZE); + + /* Check the data that will be omitted. */ + ut_a(!memcmp(page + (PAGE_NEW_INFIMUM - REC_N_NEW_EXTRA_BYTES), + infimum_extra, sizeof infimum_extra)); + ut_a(!memcmp(page + PAGE_NEW_INFIMUM, + infimum_data, sizeof infimum_data)); + ut_a(page[PAGE_NEW_SUPREMUM - REC_N_NEW_EXTRA_BYTES] + /* info_bits == 0, n_owned <= max */ + <= PAGE_DIR_SLOT_MAX_N_OWNED); + ut_a(!memcmp(page + (PAGE_NEW_SUPREMUM - REC_N_NEW_EXTRA_BYTES + 1), + supremum_extra_data, sizeof supremum_extra_data)); + + if (UNIV_UNLIKELY(!page_get_n_recs(page))) { + ut_a(rec_get_next_offs(page + PAGE_NEW_INFIMUM, TRUE) + == PAGE_NEW_SUPREMUM); + } + + if (page_is_leaf(page)) { + n_fields = dict_index_get_n_fields(index); + } else { + n_fields = dict_index_get_n_unique_in_tree(index); + } + + /* The dense directory excludes the infimum and supremum records. */ + n_dense = page_dir_get_n_heap(page) - PAGE_HEAP_NO_USER_LOW; +#ifdef PAGE_ZIP_COMPRESS_DBG + if (UNIV_UNLIKELY(page_zip_compress_dbg)) { + fprintf(stderr, "compress %p %p %lu %lu %lu\n", + (void*) page_zip, (void*) page, + page_is_leaf(page), + n_fields, n_dense); + } + if (UNIV_UNLIKELY(page_zip_compress_log)) { + /* Create a log file for every compression attempt. */ + char logfilename[9]; + ut_snprintf(logfilename, sizeof logfilename, + "%08x", page_zip_compress_log++); + logfile = fopen(logfilename, "wb"); + + if (logfile) { + /* Write the uncompressed page to the log. */ + fwrite(page, 1, UNIV_PAGE_SIZE, logfile); + /* Record the compressed size as zero. + This will be overwritten at successful exit. */ + putc(0, logfile); + putc(0, logfile); + putc(0, logfile); + putc(0, logfile); + } + } +#endif /* PAGE_ZIP_COMPRESS_DBG */ + page_zip_stat[page_zip->ssize - 1].compressed++; + + if (UNIV_UNLIKELY(n_dense * PAGE_ZIP_DIR_SLOT_SIZE + >= page_zip_get_size(page_zip))) { + + goto err_exit; + } + + heap = mem_heap_create(page_zip_get_size(page_zip) + + n_fields * (2 + sizeof *offsets) + + n_dense * ((sizeof *recs) + - PAGE_ZIP_DIR_SLOT_SIZE) + + UNIV_PAGE_SIZE * 4 + + (512 << MAX_MEM_LEVEL)); + + recs = mem_heap_zalloc(heap, n_dense * sizeof *recs); + + fields = mem_heap_alloc(heap, (n_fields + 1) * 2); + + buf = mem_heap_alloc(heap, page_zip_get_size(page_zip) - PAGE_DATA); + buf_end = buf + page_zip_get_size(page_zip) - PAGE_DATA; + + /* Compress the data payload. */ + page_zip_set_alloc(&c_stream, heap); + + err = deflateInit2(&c_stream, Z_DEFAULT_COMPRESSION, + Z_DEFLATED, UNIV_PAGE_SIZE_SHIFT, + MAX_MEM_LEVEL, Z_DEFAULT_STRATEGY); + ut_a(err == Z_OK); + + c_stream.next_out = buf; + /* Subtract the space reserved for uncompressed data. */ + /* Page header and the end marker of the modification log */ + c_stream.avail_out = buf_end - buf - 1; + /* Dense page directory and uncompressed columns, if any */ + if (page_is_leaf(page)) { + if (dict_index_is_clust(index)) { + trx_id_col = dict_index_get_sys_col_pos( + index, DATA_TRX_ID); + ut_ad(trx_id_col > 0); + ut_ad(trx_id_col != ULINT_UNDEFINED); + + slot_size = PAGE_ZIP_DIR_SLOT_SIZE + + DATA_TRX_ID_LEN + DATA_ROLL_PTR_LEN; + } else { + /* Signal the absence of trx_id + in page_zip_fields_encode() */ + ut_ad(dict_index_get_sys_col_pos(index, DATA_TRX_ID) + == ULINT_UNDEFINED); + trx_id_col = 0; + slot_size = PAGE_ZIP_DIR_SLOT_SIZE; + } + } else { + slot_size = PAGE_ZIP_DIR_SLOT_SIZE + REC_NODE_PTR_SIZE; + trx_id_col = ULINT_UNDEFINED; + } + + if (UNIV_UNLIKELY(c_stream.avail_out <= n_dense * slot_size + + 6/* sizeof(zlib header and footer) */)) { + goto zlib_error; + } + + c_stream.avail_out -= n_dense * slot_size; + c_stream.avail_in = page_zip_fields_encode(n_fields, index, + trx_id_col, fields); + c_stream.next_in = fields; + if (UNIV_LIKELY(!trx_id_col)) { + trx_id_col = ULINT_UNDEFINED; + } + + UNIV_MEM_ASSERT_RW(c_stream.next_in, c_stream.avail_in); + err = deflate(&c_stream, Z_FULL_FLUSH); + if (err != Z_OK) { + goto zlib_error; + } + + ut_ad(!c_stream.avail_in); + + page_zip_dir_encode(page, buf_end, recs); + + c_stream.next_in = (byte*) page + PAGE_ZIP_START; + + storage = buf_end - n_dense * PAGE_ZIP_DIR_SLOT_SIZE; + + /* Compress the records in heap_no order. */ + if (UNIV_UNLIKELY(!n_dense)) { + } else if (!page_is_leaf(page)) { + /* This is a node pointer page. */ + err = page_zip_compress_node_ptrs(LOGFILE + &c_stream, recs, n_dense, + index, storage, heap); + if (UNIV_UNLIKELY(err != Z_OK)) { + goto zlib_error; + } + } else if (UNIV_LIKELY(trx_id_col == ULINT_UNDEFINED)) { + /* This is a leaf page in a secondary index. */ + err = page_zip_compress_sec(LOGFILE + &c_stream, recs, n_dense); + if (UNIV_UNLIKELY(err != Z_OK)) { + goto zlib_error; + } + } else { + /* This is a leaf page in a clustered index. */ + err = page_zip_compress_clust(LOGFILE + &c_stream, recs, n_dense, + index, &n_blobs, trx_id_col, + buf_end - PAGE_ZIP_DIR_SLOT_SIZE + * page_get_n_recs(page), + storage, heap); + if (UNIV_UNLIKELY(err != Z_OK)) { + goto zlib_error; + } + } + + /* Finish the compression. */ + ut_ad(!c_stream.avail_in); + /* Compress any trailing garbage, in case the last record was + allocated from an originally longer space on the free list, + or the data of the last record from page_zip_compress_sec(). */ + c_stream.avail_in + = page_header_get_field(page, PAGE_HEAP_TOP) + - (c_stream.next_in - page); + ut_a(c_stream.avail_in <= UNIV_PAGE_SIZE - PAGE_ZIP_START - PAGE_DIR); + + UNIV_MEM_ASSERT_RW(c_stream.next_in, c_stream.avail_in); + err = deflate(&c_stream, Z_FINISH); + + if (UNIV_UNLIKELY(err != Z_STREAM_END)) { +zlib_error: + deflateEnd(&c_stream); + mem_heap_free(heap); +err_exit: +#ifdef PAGE_ZIP_COMPRESS_DBG + if (logfile) { + fclose(logfile); + } +#endif /* PAGE_ZIP_COMPRESS_DBG */ + page_zip_stat[page_zip->ssize - 1].compressed_usec + += ut_time_us(NULL) - usec; + return(FALSE); + } + + err = deflateEnd(&c_stream); + ut_a(err == Z_OK); + + ut_ad(buf + c_stream.total_out == c_stream.next_out); + ut_ad((ulint) (storage - c_stream.next_out) >= c_stream.avail_out); + + /* Valgrind believes that zlib does not initialize some bits + in the last 7 or 8 bytes of the stream. Make Valgrind happy. */ + UNIV_MEM_VALID(buf, c_stream.total_out); + + /* Zero out the area reserved for the modification log. + Space for the end marker of the modification log is not + included in avail_out. */ + memset(c_stream.next_out, 0, c_stream.avail_out + 1/* end marker */); + +#ifdef UNIV_DEBUG + page_zip->m_start = +#endif /* UNIV_DEBUG */ + page_zip->m_end = PAGE_DATA + c_stream.total_out; + page_zip->m_nonempty = FALSE; + page_zip->n_blobs = n_blobs; + /* Copy those header fields that will not be written + in buf_flush_init_for_writing() */ + memcpy(page_zip->data + FIL_PAGE_PREV, page + FIL_PAGE_PREV, + FIL_PAGE_LSN - FIL_PAGE_PREV); + memcpy(page_zip->data + FIL_PAGE_TYPE, page + FIL_PAGE_TYPE, 2); + memcpy(page_zip->data + FIL_PAGE_DATA, page + FIL_PAGE_DATA, + PAGE_DATA - FIL_PAGE_DATA); + /* Copy the rest of the compressed page */ + memcpy(page_zip->data + PAGE_DATA, buf, + page_zip_get_size(page_zip) - PAGE_DATA); + mem_heap_free(heap); +#ifdef UNIV_ZIP_DEBUG + ut_a(page_zip_validate(page_zip, page)); +#endif /* UNIV_ZIP_DEBUG */ + + if (mtr) { + page_zip_compress_write_log(page_zip, page, index, mtr); + } + + UNIV_MEM_ASSERT_RW(page_zip->data, page_zip_get_size(page_zip)); + +#ifdef PAGE_ZIP_COMPRESS_DBG + if (logfile) { + /* Record the compressed size of the block. */ + byte sz[4]; + mach_write_to_4(sz, c_stream.total_out); + fseek(logfile, UNIV_PAGE_SIZE, SEEK_SET); + fwrite(sz, 1, sizeof sz, logfile); + fclose(logfile); + } +#endif /* PAGE_ZIP_COMPRESS_DBG */ + { + page_zip_stat_t* zip_stat + = &page_zip_stat[page_zip->ssize - 1]; + zip_stat->compressed_ok++; + zip_stat->compressed_usec += ut_time_us(NULL) - usec; + } + + return(TRUE); +} + +/************************************************************************** +Compare two page directory entries. */ +UNIV_INLINE +ibool +page_zip_dir_cmp( +/*=============*/ + /* out: positive if rec1 > rec2 */ + const rec_t* rec1, /* in: rec1 */ + const rec_t* rec2) /* in: rec2 */ +{ + return(rec1 > rec2); +} + +/************************************************************************** +Sort the dense page directory by address (heap_no). */ +static +void +page_zip_dir_sort( +/*==============*/ + rec_t** arr, /* in/out: dense page directory */ + rec_t** aux_arr,/* in/out: work area */ + ulint low, /* in: lower bound of the sorting area, inclusive */ + ulint high) /* in: upper bound of the sorting area, exclusive */ +{ + UT_SORT_FUNCTION_BODY(page_zip_dir_sort, arr, aux_arr, low, high, + page_zip_dir_cmp); +} + +/************************************************************************** +Deallocate the index information initialized by page_zip_fields_decode(). */ +static +void +page_zip_fields_free( +/*=================*/ + dict_index_t* index) /* in: dummy index to be freed */ +{ + if (index) { + dict_table_t* table = index->table; + mem_heap_free(index->heap); + mutex_free(&(table->autoinc_mutex)); + mem_heap_free(table->heap); + } +} + +/************************************************************************** +Read the index information for the compressed page. */ +static +dict_index_t* +page_zip_fields_decode( +/*===================*/ + /* out,own: dummy index describing the page, + or NULL on error */ + const byte* buf, /* in: index information */ + const byte* end, /* in: end of buf */ + ulint* trx_id_col)/* in: NULL for non-leaf pages; + for leaf pages, pointer to where to store + the position of the trx_id column */ +{ + const byte* b; + ulint n; + ulint i; + ulint val; + dict_table_t* table; + dict_index_t* index; + + /* Determine the number of fields. */ + for (b = buf, n = 0; b < end; n++) { + if (*b++ & 0x80) { + b++; /* skip the second byte */ + } + } + + n--; /* n_nullable or trx_id */ + + if (UNIV_UNLIKELY(n > REC_MAX_N_FIELDS)) { + + page_zip_fail(("page_zip_fields_decode: n = %lu\n", + (ulong) n)); + return(NULL); + } + + if (UNIV_UNLIKELY(b > end)) { + + page_zip_fail(("page_zip_fields_decode: %p > %p\n", + (const void*) b, (const void*) end)); + return(NULL); + } + + table = dict_mem_table_create("ZIP_DUMMY", DICT_HDR_SPACE, n, + DICT_TF_COMPACT); + index = dict_mem_index_create("ZIP_DUMMY", "ZIP_DUMMY", + DICT_HDR_SPACE, 0, n); + index->table = table; + index->n_uniq = n; + /* avoid ut_ad(index->cached) in dict_index_get_n_unique_in_tree */ + index->cached = TRUE; + + /* Initialize the fields. */ + for (b = buf, i = 0; i < n; i++) { + ulint mtype; + ulint len; + + val = *b++; + + if (UNIV_UNLIKELY(val & 0x80)) { + /* fixed length > 62 bytes */ + val = (val & 0x7f) << 8 | *b++; + len = val >> 1; + mtype = DATA_FIXBINARY; + } else if (UNIV_UNLIKELY(val >= 126)) { + /* variable length with max > 255 bytes */ + len = 0x7fff; + mtype = DATA_BINARY; + } else if (val <= 1) { + /* variable length with max <= 255 bytes */ + len = 0; + mtype = DATA_BINARY; + } else { + /* fixed length < 62 bytes */ + len = val >> 1; + mtype = DATA_FIXBINARY; + } + + dict_mem_table_add_col(table, NULL, NULL, mtype, + val & 1 ? DATA_NOT_NULL : 0, len); + dict_index_add_col(index, table, + dict_table_get_nth_col(table, i), 0); + } + + val = *b++; + if (UNIV_UNLIKELY(val & 0x80)) { + val = (val & 0x7f) << 8 | *b++; + } + + /* Decode the position of the trx_id column. */ + if (trx_id_col) { + if (!val) { + val = ULINT_UNDEFINED; + } else if (UNIV_UNLIKELY(val >= n)) { + page_zip_fields_free(index); + index = NULL; + } else { + index->type = DICT_CLUSTERED; + } + + *trx_id_col = val; + } else { + /* Decode the number of nullable fields. */ + if (UNIV_UNLIKELY(index->n_nullable > val)) { + page_zip_fields_free(index); + index = NULL; + } else { + index->n_nullable = val; + } + } + + ut_ad(b == end); + + return(index); +} + +/************************************************************************** +Populate the sparse page directory from the dense directory. */ +static +ibool +page_zip_dir_decode( +/*================*/ + /* out: TRUE on success, + FALSE on failure */ + const page_zip_des_t* page_zip,/* in: dense page directory on + compressed page */ + page_t* page, /* in: compact page with valid header; + out: trailer and sparse page directory + filled in */ + rec_t** recs, /* out: dense page directory sorted by + ascending address (and heap_no) */ + rec_t** recs_aux,/* in/out: scratch area */ + ulint n_dense)/* in: number of user records, and + size of recs[] and recs_aux[] */ +{ + ulint i; + ulint n_recs; + byte* slot; + + n_recs = page_get_n_recs(page); + + if (UNIV_UNLIKELY(n_recs > n_dense)) { + page_zip_fail(("page_zip_dir_decode 1: %lu > %lu\n", + (ulong) n_recs, (ulong) n_dense)); + return(FALSE); + } + + /* Traverse the list of stored records in the sorting order, + starting from the first user record. */ + + slot = page + (UNIV_PAGE_SIZE - PAGE_DIR - PAGE_DIR_SLOT_SIZE); + UNIV_PREFETCH_RW(slot); + + /* Zero out the page trailer. */ + memset(slot + PAGE_DIR_SLOT_SIZE, 0, PAGE_DIR); + + mach_write_to_2(slot, PAGE_NEW_INFIMUM); + slot -= PAGE_DIR_SLOT_SIZE; + UNIV_PREFETCH_RW(slot); + + /* Initialize the sparse directory and copy the dense directory. */ + for (i = 0; i < n_recs; i++) { + ulint offs = page_zip_dir_get(page_zip, i); + + if (offs & PAGE_ZIP_DIR_SLOT_OWNED) { + mach_write_to_2(slot, offs & PAGE_ZIP_DIR_SLOT_MASK); + slot -= PAGE_DIR_SLOT_SIZE; + UNIV_PREFETCH_RW(slot); + } + + if (UNIV_UNLIKELY((offs & PAGE_ZIP_DIR_SLOT_MASK) + < PAGE_ZIP_START + REC_N_NEW_EXTRA_BYTES)) { + page_zip_fail(("page_zip_dir_decode 2: %u %u %lx\n", + (unsigned) i, (unsigned) n_recs, + (ulong) offs)); + return(FALSE); + } + + recs[i] = page + (offs & PAGE_ZIP_DIR_SLOT_MASK); + } + + mach_write_to_2(slot, PAGE_NEW_SUPREMUM); + { + const page_dir_slot_t* last_slot = page_dir_get_nth_slot( + page, page_dir_get_n_slots(page) - 1); + + if (UNIV_UNLIKELY(slot != last_slot)) { + page_zip_fail(("page_zip_dir_decode 3: %p != %p\n", + (const void*) slot, + (const void*) last_slot)); + return(FALSE); + } + } + + /* Copy the rest of the dense directory. */ + for (; i < n_dense; i++) { + ulint offs = page_zip_dir_get(page_zip, i); + + if (UNIV_UNLIKELY(offs & ~PAGE_ZIP_DIR_SLOT_MASK)) { + page_zip_fail(("page_zip_dir_decode 4: %u %u %lx\n", + (unsigned) i, (unsigned) n_dense, + (ulong) offs)); + return(FALSE); + } + + recs[i] = page + offs; + } + + if (UNIV_LIKELY(n_dense > 1)) { + page_zip_dir_sort(recs, recs_aux, 0, n_dense); + } + return(TRUE); +} + +/************************************************************************** +Initialize the REC_N_NEW_EXTRA_BYTES of each record. */ +static +ibool +page_zip_set_extra_bytes( +/*=====================*/ + /* out: TRUE on success, + FALSE on failure */ + const page_zip_des_t* page_zip,/* in: compressed page */ + page_t* page, /* in/out: uncompressed page */ + ulint info_bits)/* in: REC_INFO_MIN_REC_FLAG or 0 */ +{ + ulint n; + ulint i; + ulint n_owned = 1; + ulint offs; + rec_t* rec; + + n = page_get_n_recs(page); + rec = page + PAGE_NEW_INFIMUM; + + for (i = 0; i < n; i++) { + offs = page_zip_dir_get(page_zip, i); + + if (UNIV_UNLIKELY(offs & PAGE_ZIP_DIR_SLOT_DEL)) { + info_bits |= REC_INFO_DELETED_FLAG; + } + if (UNIV_UNLIKELY(offs & PAGE_ZIP_DIR_SLOT_OWNED)) { + info_bits |= n_owned; + n_owned = 1; + } else { + n_owned++; + } + offs &= PAGE_ZIP_DIR_SLOT_MASK; + if (UNIV_UNLIKELY(offs < PAGE_ZIP_START + + REC_N_NEW_EXTRA_BYTES)) { + page_zip_fail(("page_zip_set_extra_bytes 1:" + " %u %u %lx\n", + (unsigned) i, (unsigned) n, + (ulong) offs)); + return(FALSE); + } + + rec_set_next_offs_new(rec, offs); + rec = page + offs; + rec[-REC_N_NEW_EXTRA_BYTES] = (byte) info_bits; + info_bits = 0; + } + + /* Set the next pointer of the last user record. */ + rec_set_next_offs_new(rec, PAGE_NEW_SUPREMUM); + + /* Set n_owned of the supremum record. */ + page[PAGE_NEW_SUPREMUM - REC_N_NEW_EXTRA_BYTES] = (byte) n_owned; + + /* The dense directory excludes the infimum and supremum records. */ + n = page_dir_get_n_heap(page) - PAGE_HEAP_NO_USER_LOW; + + if (i >= n) { + if (UNIV_LIKELY(i == n)) { + return(TRUE); + } + + page_zip_fail(("page_zip_set_extra_bytes 2: %u != %u\n", + (unsigned) i, (unsigned) n)); + return(FALSE); + } + + offs = page_zip_dir_get(page_zip, i); + + /* Set the extra bytes of deleted records on the free list. */ + for (;;) { + if (UNIV_UNLIKELY(!offs) + || UNIV_UNLIKELY(offs & ~PAGE_ZIP_DIR_SLOT_MASK)) { + + page_zip_fail(("page_zip_set_extra_bytes 3: %lx\n", + (ulong) offs)); + return(FALSE); + } + + rec = page + offs; + rec[-REC_N_NEW_EXTRA_BYTES] = 0; /* info_bits and n_owned */ + + if (++i == n) { + break; + } + + offs = page_zip_dir_get(page_zip, i); + rec_set_next_offs_new(rec, offs); + } + + /* Terminate the free list. */ + rec[-REC_N_NEW_EXTRA_BYTES] = 0; /* info_bits and n_owned */ + rec_set_next_offs_new(rec, 0); + + return(TRUE); +} + +/************************************************************************** +Apply the modification log to a record containing externally stored +columns. Do not copy the fields that are stored separately. */ +static +const byte* +page_zip_apply_log_ext( +/*===================*/ + /* out: pointer to modification log, + or NULL on failure */ + rec_t* rec, /* in/out: record */ + const ulint* offsets, /* in: rec_get_offsets(rec) */ + ulint trx_id_col, /* in: position of of DB_TRX_ID */ + const byte* data, /* in: modification log */ + const byte* end) /* in: end of modification log */ +{ + ulint i; + ulint len; + byte* next_out = rec; + + /* Check if there are any externally stored columns. + For each externally stored column, skip the + BTR_EXTERN_FIELD_REF. */ + + for (i = 0; i < rec_offs_n_fields(offsets); i++) { + byte* dst; + + if (UNIV_UNLIKELY(i == trx_id_col)) { + /* Skip trx_id and roll_ptr */ + dst = rec_get_nth_field(rec, offsets, + i, &len); + if (UNIV_UNLIKELY(dst - next_out >= end - data) + || UNIV_UNLIKELY + (len < (DATA_TRX_ID_LEN + DATA_ROLL_PTR_LEN)) + || rec_offs_nth_extern(offsets, i)) { + page_zip_fail(("page_zip_apply_log_ext:" + " trx_id len %lu," + " %p - %p >= %p - %p\n", + (ulong) len, + (const void*) dst, + (const void*) next_out, + (const void*) end, + (const void*) data)); + return(NULL); + } + + memcpy(next_out, data, dst - next_out); + data += dst - next_out; + next_out = dst + (DATA_TRX_ID_LEN + + DATA_ROLL_PTR_LEN); + } else if (rec_offs_nth_extern(offsets, i)) { + dst = rec_get_nth_field(rec, offsets, + i, &len); + ut_ad(len + >= BTR_EXTERN_FIELD_REF_SIZE); + + len += dst - next_out + - BTR_EXTERN_FIELD_REF_SIZE; + + if (UNIV_UNLIKELY(data + len >= end)) { + page_zip_fail(("page_zip_apply_log_ext: " + "ext %p+%lu >= %p\n", + (const void*) data, + (ulong) len, + (const void*) end)); + return(NULL); + } + + memcpy(next_out, data, len); + data += len; + next_out += len + + BTR_EXTERN_FIELD_REF_SIZE; + } + } + + /* Copy the last bytes of the record. */ + len = rec_get_end(rec, offsets) - next_out; + if (UNIV_UNLIKELY(data + len >= end)) { + page_zip_fail(("page_zip_apply_log_ext: " + "last %p+%lu >= %p\n", + (const void*) data, + (ulong) len, + (const void*) end)); + return(NULL); + } + memcpy(next_out, data, len); + data += len; + + return(data); +} + +/************************************************************************** +Apply the modification log to an uncompressed page. +Do not copy the fields that are stored separately. */ +static +const byte* +page_zip_apply_log( +/*===============*/ + /* out: pointer to end of modification log, + or NULL on failure */ + const byte* data, /* in: modification log */ + ulint size, /* in: maximum length of the log, in bytes */ + rec_t** recs, /* in: dense page directory, + sorted by address (indexed by + heap_no - PAGE_HEAP_NO_USER_LOW) */ + ulint n_dense,/* in: size of recs[] */ + ulint trx_id_col,/* in: column number of trx_id in the index, + or ULINT_UNDEFINED if none */ + ulint heap_status, + /* in: heap_no and status bits for + the next record to uncompress */ + dict_index_t* index, /* in: index of the page */ + ulint* offsets)/* in/out: work area for + rec_get_offsets_reverse() */ +{ + const byte* const end = data + size; + + for (;;) { + ulint val; + rec_t* rec; + ulint len; + ulint hs; + + val = *data++; + if (UNIV_UNLIKELY(!val)) { + return(data - 1); + } + if (val & 0x80) { + val = (val & 0x7f) << 8 | *data++; + if (UNIV_UNLIKELY(!val)) { + page_zip_fail(("page_zip_apply_log:" + " invalid val %x%x\n", + data[-2], data[-1])); + return(NULL); + } + } + if (UNIV_UNLIKELY(data >= end)) { + page_zip_fail(("page_zip_apply_log: %p >= %p\n", + (const void*) data, + (const void*) end)); + return(NULL); + } + if (UNIV_UNLIKELY((val >> 1) > n_dense)) { + page_zip_fail(("page_zip_apply_log: %lu>>1 > %lu\n", + (ulong) val, (ulong) n_dense)); + return(NULL); + } + + /* Determine the heap number and status bits of the record. */ + rec = recs[(val >> 1) - 1]; + + hs = ((val >> 1) + 1) << REC_HEAP_NO_SHIFT; + hs |= heap_status & ((1 << REC_HEAP_NO_SHIFT) - 1); + + /* This may either be an old record that is being + overwritten (updated in place, or allocated from + the free list), or a new record, with the next + available_heap_no. */ + if (UNIV_UNLIKELY(hs > heap_status)) { + page_zip_fail(("page_zip_apply_log: %lu > %lu\n", + (ulong) hs, (ulong) heap_status)); + return(NULL); + } else if (hs == heap_status) { + /* A new record was allocated from the heap. */ + if (UNIV_UNLIKELY(val & 1)) { + /* Only existing records may be cleared. */ + page_zip_fail(("page_zip_apply_log:" + " attempting to create" + " deleted rec %lu\n", + (ulong) hs)); + return(NULL); + } + heap_status += 1 << REC_HEAP_NO_SHIFT; + } + + mach_write_to_2(rec - REC_NEW_HEAP_NO, hs); + + if (val & 1) { + /* Clear the data bytes of the record. */ + mem_heap_t* heap = NULL; + ulint* offs; + offs = rec_get_offsets(rec, index, offsets, + ULINT_UNDEFINED, &heap); + memset(rec, 0, rec_offs_data_size(offs)); + + if (UNIV_LIKELY_NULL(heap)) { + mem_heap_free(heap); + } + continue; + } + +#if REC_STATUS_NODE_PTR != TRUE +# error "REC_STATUS_NODE_PTR != TRUE" +#endif + rec_get_offsets_reverse(data, index, + hs & REC_STATUS_NODE_PTR, + offsets); + rec_offs_make_valid(rec, index, offsets); + + /* Copy the extra bytes (backwards). */ + { + byte* start = rec_get_start(rec, offsets); + byte* b = rec - REC_N_NEW_EXTRA_BYTES; + while (b != start) { + *--b = *data++; + } + } + + /* Copy the data bytes. */ + if (UNIV_UNLIKELY(rec_offs_any_extern(offsets))) { + /* Non-leaf nodes should not contain any + externally stored columns. */ + if (UNIV_UNLIKELY(hs & REC_STATUS_NODE_PTR)) { + page_zip_fail(("page_zip_apply_log: " + "%lu&REC_STATUS_NODE_PTR\n", + (ulong) hs)); + return(NULL); + } + + data = page_zip_apply_log_ext( + rec, offsets, trx_id_col, data, end); + + if (UNIV_UNLIKELY(!data)) { + return(NULL); + } + } else if (UNIV_UNLIKELY(hs & REC_STATUS_NODE_PTR)) { + len = rec_offs_data_size(offsets) + - REC_NODE_PTR_SIZE; + /* Copy the data bytes, except node_ptr. */ + if (UNIV_UNLIKELY(data + len >= end)) { + page_zip_fail(("page_zip_apply_log: " + "node_ptr %p+%lu >= %p\n", + (const void*) data, + (ulong) len, + (const void*) end)); + return(NULL); + } + memcpy(rec, data, len); + data += len; + } else if (UNIV_LIKELY(trx_id_col == ULINT_UNDEFINED)) { + len = rec_offs_data_size(offsets); + + /* Copy all data bytes of + a record in a secondary index. */ + if (UNIV_UNLIKELY(data + len >= end)) { + page_zip_fail(("page_zip_apply_log: " + "sec %p+%lu >= %p\n", + (const void*) data, + (ulong) len, + (const void*) end)); + return(NULL); + } + + memcpy(rec, data, len); + data += len; + } else { + /* Skip DB_TRX_ID and DB_ROLL_PTR. */ + ulint l = rec_get_nth_field_offs(offsets, + trx_id_col, &len); + byte* b; + + if (UNIV_UNLIKELY(data + l >= end) + || UNIV_UNLIKELY(len < (DATA_TRX_ID_LEN + + DATA_ROLL_PTR_LEN))) { + page_zip_fail(("page_zip_apply_log: " + "trx_id %p+%lu >= %p\n", + (const void*) data, + (ulong) l, + (const void*) end)); + return(NULL); + } + + /* Copy any preceding data bytes. */ + memcpy(rec, data, l); + data += l; + + /* Copy any bytes following DB_TRX_ID, DB_ROLL_PTR. */ + b = rec + l + (DATA_TRX_ID_LEN + DATA_ROLL_PTR_LEN); + len = rec_get_end(rec, offsets) - b; + if (UNIV_UNLIKELY(data + len >= end)) { + page_zip_fail(("page_zip_apply_log: " + "clust %p+%lu >= %p\n", + (const void*) data, + (ulong) len, + (const void*) end)); + return(NULL); + } + memcpy(b, data, len); + data += len; + } + } +} + +/************************************************************************** +Decompress the records of a node pointer page. */ +static +ibool +page_zip_decompress_node_ptrs( +/*==========================*/ + /* out: TRUE on success, + FALSE on failure */ + page_zip_des_t* page_zip, /* in/out: compressed page */ + z_stream* d_stream, /* in/out: compressed page stream */ + rec_t** recs, /* in: dense page directory + sorted by address */ + ulint n_dense, /* in: size of recs[] */ + dict_index_t* index, /* in: the index of the page */ + ulint* offsets, /* in/out: temporary offsets */ + mem_heap_t* heap) /* in: temporary memory heap */ +{ + ulint heap_status = REC_STATUS_NODE_PTR + | PAGE_HEAP_NO_USER_LOW << REC_HEAP_NO_SHIFT; + ulint slot; + const byte* storage; + + /* Subtract the space reserved for uncompressed data. */ + d_stream->avail_in -= n_dense + * (PAGE_ZIP_DIR_SLOT_SIZE + REC_NODE_PTR_SIZE); + + /* Decompress the records in heap_no order. */ + for (slot = 0; slot < n_dense; slot++) { + rec_t* rec = recs[slot]; + + d_stream->avail_out = rec - REC_N_NEW_EXTRA_BYTES + - d_stream->next_out; + + ut_ad(d_stream->avail_out < UNIV_PAGE_SIZE + - PAGE_ZIP_START - PAGE_DIR); + switch (inflate(d_stream, Z_SYNC_FLUSH)) { + case Z_STREAM_END: + /* Apparently, n_dense has grown + since the time the page was last compressed. */ + goto zlib_done; + case Z_OK: + case Z_BUF_ERROR: + if (!d_stream->avail_out) { + break; + } + /* fall through */ + default: + page_zip_fail(("page_zip_decompress_node_ptrs:" + " 1 inflate(Z_SYNC_FLUSH)=%s\n", + d_stream->msg)); + goto zlib_error; + } + + ut_ad(d_stream->next_out == rec - REC_N_NEW_EXTRA_BYTES); + /* Prepare to decompress the data bytes. */ + d_stream->next_out = rec; + /* Set heap_no and the status bits. */ + mach_write_to_2(rec - REC_NEW_HEAP_NO, heap_status); + heap_status += 1 << REC_HEAP_NO_SHIFT; + + /* Read the offsets. The status bits are needed here. */ + offsets = rec_get_offsets(rec, index, offsets, + ULINT_UNDEFINED, &heap); + + /* Non-leaf nodes should not have any externally + stored columns. */ + ut_ad(!rec_offs_any_extern(offsets)); + + /* Decompress the data bytes, except node_ptr. */ + d_stream->avail_out = rec_offs_data_size(offsets) + - REC_NODE_PTR_SIZE; + + switch (inflate(d_stream, Z_SYNC_FLUSH)) { + case Z_STREAM_END: + goto zlib_done; + case Z_OK: + case Z_BUF_ERROR: + if (!d_stream->avail_out) { + break; + } + /* fall through */ + default: + page_zip_fail(("page_zip_decompress_node_ptrs:" + " 2 inflate(Z_SYNC_FLUSH)=%s\n", + d_stream->msg)); + goto zlib_error; + } + + /* Clear the node pointer in case the record + will be deleted and the space will be reallocated + to a smaller record. */ + memset(d_stream->next_out, 0, REC_NODE_PTR_SIZE); + d_stream->next_out += REC_NODE_PTR_SIZE; + + ut_ad(d_stream->next_out == rec_get_end(rec, offsets)); + } + + /* Decompress any trailing garbage, in case the last record was + allocated from an originally longer space on the free list. */ + d_stream->avail_out = page_header_get_field(page_zip->data, + PAGE_HEAP_TOP) + - page_offset(d_stream->next_out); + if (UNIV_UNLIKELY(d_stream->avail_out > UNIV_PAGE_SIZE + - PAGE_ZIP_START - PAGE_DIR)) { + + page_zip_fail(("page_zip_decompress_node_ptrs:" + " avail_out = %u\n", + d_stream->avail_out)); + goto zlib_error; + } + + if (UNIV_UNLIKELY(inflate(d_stream, Z_FINISH) != Z_STREAM_END)) { + page_zip_fail(("page_zip_decompress_node_ptrs:" + " inflate(Z_FINISH)=%s\n", + d_stream->msg)); +zlib_error: + inflateEnd(d_stream); + return(FALSE); + } + + /* Note that d_stream->avail_out > 0 may hold here + if the modification log is nonempty. */ + +zlib_done: + if (UNIV_UNLIKELY(inflateEnd(d_stream) != Z_OK)) { + ut_error; + } + + { + page_t* page = page_align(d_stream->next_out); + + /* Clear the unused heap space on the uncompressed page. */ + memset(d_stream->next_out, 0, + page_dir_get_nth_slot(page, + page_dir_get_n_slots(page) - 1) + - d_stream->next_out); + } + +#ifdef UNIV_DEBUG + page_zip->m_start = PAGE_DATA + d_stream->total_in; +#endif /* UNIV_DEBUG */ + + /* Apply the modification log. */ + { + const byte* mod_log_ptr; + mod_log_ptr = page_zip_apply_log(d_stream->next_in, + d_stream->avail_in + 1, + recs, n_dense, + ULINT_UNDEFINED, heap_status, + index, offsets); + + if (UNIV_UNLIKELY(!mod_log_ptr)) { + return(FALSE); + } + page_zip->m_end = mod_log_ptr - page_zip->data; + page_zip->m_nonempty = mod_log_ptr != d_stream->next_in; + } + + if (UNIV_UNLIKELY + (page_zip_get_trailer_len(page_zip, + dict_index_is_clust(index), NULL) + + page_zip->m_end >= page_zip_get_size(page_zip))) { + page_zip_fail(("page_zip_decompress_node_ptrs:" + " %lu + %lu >= %lu, %lu\n", + (ulong) page_zip_get_trailer_len( + page_zip, dict_index_is_clust(index), + NULL), + (ulong) page_zip->m_end, + (ulong) page_zip_get_size(page_zip), + (ulong) dict_index_is_clust(index))); + return(FALSE); + } + + /* Restore the uncompressed columns in heap_no order. */ + storage = page_zip->data + page_zip_get_size(page_zip) + - n_dense * PAGE_ZIP_DIR_SLOT_SIZE; + + for (slot = 0; slot < n_dense; slot++) { + rec_t* rec = recs[slot]; + + offsets = rec_get_offsets(rec, index, offsets, + ULINT_UNDEFINED, &heap); + /* Non-leaf nodes should not have any externally + stored columns. */ + ut_ad(!rec_offs_any_extern(offsets)); + storage -= REC_NODE_PTR_SIZE; + + memcpy(rec_get_end(rec, offsets) - REC_NODE_PTR_SIZE, + storage, REC_NODE_PTR_SIZE); + } + + return(TRUE); +} + +/************************************************************************** +Decompress the records of a leaf node of a secondary index. */ +static +ibool +page_zip_decompress_sec( +/*====================*/ + /* out: TRUE on success, + FALSE on failure */ + page_zip_des_t* page_zip, /* in/out: compressed page */ + z_stream* d_stream, /* in/out: compressed page stream */ + rec_t** recs, /* in: dense page directory + sorted by address */ + ulint n_dense, /* in: size of recs[] */ + dict_index_t* index, /* in: the index of the page */ + ulint* offsets) /* in/out: temporary offsets */ +{ + ulint heap_status = REC_STATUS_ORDINARY + | PAGE_HEAP_NO_USER_LOW << REC_HEAP_NO_SHIFT; + ulint slot; + + ut_a(!dict_index_is_clust(index)); + + /* Subtract the space reserved for uncompressed data. */ + d_stream->avail_in -= n_dense * PAGE_ZIP_DIR_SLOT_SIZE; + + for (slot = 0; slot < n_dense; slot++) { + rec_t* rec = recs[slot]; + + /* Decompress everything up to this record. */ + d_stream->avail_out = rec - REC_N_NEW_EXTRA_BYTES + - d_stream->next_out; + + if (UNIV_LIKELY(d_stream->avail_out)) { + switch (inflate(d_stream, Z_SYNC_FLUSH)) { + case Z_STREAM_END: + /* Apparently, n_dense has grown + since the time the page was last compressed. */ + goto zlib_done; + case Z_OK: + case Z_BUF_ERROR: + if (!d_stream->avail_out) { + break; + } + /* fall through */ + default: + page_zip_fail(("page_zip_decompress_sec:" + " inflate(Z_SYNC_FLUSH)=%s\n", + d_stream->msg)); + goto zlib_error; + } + } + + ut_ad(d_stream->next_out == rec - REC_N_NEW_EXTRA_BYTES); + + /* Skip the REC_N_NEW_EXTRA_BYTES. */ + + d_stream->next_out = rec; + + /* Set heap_no and the status bits. */ + mach_write_to_2(rec - REC_NEW_HEAP_NO, heap_status); + heap_status += 1 << REC_HEAP_NO_SHIFT; + } + + /* Decompress the data of the last record and any trailing garbage, + in case the last record was allocated from an originally longer space + on the free list. */ + d_stream->avail_out = page_header_get_field(page_zip->data, + PAGE_HEAP_TOP) + - page_offset(d_stream->next_out); + if (UNIV_UNLIKELY(d_stream->avail_out > UNIV_PAGE_SIZE + - PAGE_ZIP_START - PAGE_DIR)) { + + page_zip_fail(("page_zip_decompress_sec:" + " avail_out = %u\n", + d_stream->avail_out)); + goto zlib_error; + } + + if (UNIV_UNLIKELY(inflate(d_stream, Z_FINISH) != Z_STREAM_END)) { + page_zip_fail(("page_zip_decompress_sec:" + " inflate(Z_FINISH)=%s\n", + d_stream->msg)); +zlib_error: + inflateEnd(d_stream); + return(FALSE); + } + + /* Note that d_stream->avail_out > 0 may hold here + if the modification log is nonempty. */ + +zlib_done: + if (UNIV_UNLIKELY(inflateEnd(d_stream) != Z_OK)) { + ut_error; + } + + { + page_t* page = page_align(d_stream->next_out); + + /* Clear the unused heap space on the uncompressed page. */ + memset(d_stream->next_out, 0, + page_dir_get_nth_slot(page, + page_dir_get_n_slots(page) - 1) + - d_stream->next_out); + } + +#ifdef UNIV_DEBUG + page_zip->m_start = PAGE_DATA + d_stream->total_in; +#endif /* UNIV_DEBUG */ + + /* Apply the modification log. */ + { + const byte* mod_log_ptr; + mod_log_ptr = page_zip_apply_log(d_stream->next_in, + d_stream->avail_in + 1, + recs, n_dense, + ULINT_UNDEFINED, heap_status, + index, offsets); + + if (UNIV_UNLIKELY(!mod_log_ptr)) { + return(FALSE); + } + page_zip->m_end = mod_log_ptr - page_zip->data; + page_zip->m_nonempty = mod_log_ptr != d_stream->next_in; + } + + if (UNIV_UNLIKELY(page_zip_get_trailer_len(page_zip, FALSE, NULL) + + page_zip->m_end >= page_zip_get_size(page_zip))) { + + page_zip_fail(("page_zip_decompress_sec: %lu + %lu >= %lu\n", + (ulong) page_zip_get_trailer_len( + page_zip, FALSE, NULL), + (ulong) page_zip->m_end, + (ulong) page_zip_get_size(page_zip))); + return(FALSE); + } + + /* There are no uncompressed columns on leaf pages of + secondary indexes. */ + + return(TRUE); +} + +/************************************************************************** +Decompress a record of a leaf node of a clustered index that contains +externally stored columns. */ +static +ibool +page_zip_decompress_clust_ext( +/*==========================*/ + /* out: TRUE on success */ + z_stream* d_stream, /* in/out: compressed page stream */ + rec_t* rec, /* in/out: record */ + const ulint* offsets, /* in: rec_get_offsets(rec) */ + ulint trx_id_col) /* in: position of of DB_TRX_ID */ +{ + ulint i; + + for (i = 0; i < rec_offs_n_fields(offsets); i++) { + ulint len; + byte* dst; + + if (UNIV_UNLIKELY(i == trx_id_col)) { + /* Skip trx_id and roll_ptr */ + dst = rec_get_nth_field(rec, offsets, i, &len); + if (UNIV_UNLIKELY(len < DATA_TRX_ID_LEN + + DATA_ROLL_PTR_LEN)) { + + page_zip_fail(("page_zip_decompress_clust_ext:" + " len[%lu] = %lu\n", + (ulong) i, (ulong) len)); + return(FALSE); + } + + if (rec_offs_nth_extern(offsets, i)) { + + page_zip_fail(("page_zip_decompress_clust_ext:" + " DB_TRX_ID at %lu is ext\n", + (ulong) i)); + return(FALSE); + } + + d_stream->avail_out = dst - d_stream->next_out; + + switch (inflate(d_stream, Z_SYNC_FLUSH)) { + case Z_STREAM_END: + case Z_OK: + case Z_BUF_ERROR: + if (!d_stream->avail_out) { + break; + } + /* fall through */ + default: + page_zip_fail(("page_zip_decompress_clust_ext:" + " 1 inflate(Z_SYNC_FLUSH)=%s\n", + d_stream->msg)); + return(FALSE); + } + + ut_ad(d_stream->next_out == dst); + + /* Clear DB_TRX_ID and DB_ROLL_PTR in order to + avoid uninitialized bytes in case the record + is affected by page_zip_apply_log(). */ + memset(dst, 0, DATA_TRX_ID_LEN + DATA_ROLL_PTR_LEN); + + d_stream->next_out += DATA_TRX_ID_LEN + + DATA_ROLL_PTR_LEN; + } else if (rec_offs_nth_extern(offsets, i)) { + dst = rec_get_nth_field(rec, offsets, i, &len); + ut_ad(len >= BTR_EXTERN_FIELD_REF_SIZE); + dst += len - BTR_EXTERN_FIELD_REF_SIZE; + + d_stream->avail_out = dst - d_stream->next_out; + switch (inflate(d_stream, Z_SYNC_FLUSH)) { + case Z_STREAM_END: + case Z_OK: + case Z_BUF_ERROR: + if (!d_stream->avail_out) { + break; + } + /* fall through */ + default: + page_zip_fail(("page_zip_decompress_clust_ext:" + " 2 inflate(Z_SYNC_FLUSH)=%s\n", + d_stream->msg)); + return(FALSE); + } + + ut_ad(d_stream->next_out == dst); + + /* Clear the BLOB pointer in case + the record will be deleted and the + space will not be reused. Note that + the final initialization of the BLOB + pointers (copying from "externs" + or clearing) will have to take place + only after the page modification log + has been applied. Otherwise, we + could end up with an uninitialized + BLOB pointer when a record is deleted, + reallocated and deleted. */ + memset(d_stream->next_out, 0, + BTR_EXTERN_FIELD_REF_SIZE); + d_stream->next_out + += BTR_EXTERN_FIELD_REF_SIZE; + } + } + + return(TRUE); +} + +/************************************************************************** +Compress the records of a leaf node of a clustered index. */ +static +ibool +page_zip_decompress_clust( +/*======================*/ + /* out: TRUE on success, + FALSE on failure */ + page_zip_des_t* page_zip, /* in/out: compressed page */ + z_stream* d_stream, /* in/out: compressed page stream */ + rec_t** recs, /* in: dense page directory + sorted by address */ + ulint n_dense, /* in: size of recs[] */ + dict_index_t* index, /* in: the index of the page */ + ulint trx_id_col, /* index of the trx_id column */ + ulint* offsets, /* in/out: temporary offsets */ + mem_heap_t* heap) /* in: temporary memory heap */ +{ + int err; + ulint slot; + ulint heap_status = REC_STATUS_ORDINARY + | PAGE_HEAP_NO_USER_LOW << REC_HEAP_NO_SHIFT; + const byte* storage; + const byte* externs; + + ut_a(dict_index_is_clust(index)); + + /* Subtract the space reserved for uncompressed data. */ + d_stream->avail_in -= n_dense * (PAGE_ZIP_DIR_SLOT_SIZE + + DATA_TRX_ID_LEN + + DATA_ROLL_PTR_LEN); + + /* Decompress the records in heap_no order. */ + for (slot = 0; slot < n_dense; slot++) { + rec_t* rec = recs[slot]; + + d_stream->avail_out = rec - REC_N_NEW_EXTRA_BYTES + - d_stream->next_out; + + ut_ad(d_stream->avail_out < UNIV_PAGE_SIZE + - PAGE_ZIP_START - PAGE_DIR); + err = inflate(d_stream, Z_SYNC_FLUSH); + switch (err) { + case Z_STREAM_END: + /* Apparently, n_dense has grown + since the time the page was last compressed. */ + goto zlib_done; + case Z_OK: + case Z_BUF_ERROR: + if (UNIV_LIKELY(!d_stream->avail_out)) { + break; + } + /* fall through */ + default: + page_zip_fail(("page_zip_decompress_clust:" + " 1 inflate(Z_SYNC_FLUSH)=%s\n", + d_stream->msg)); + goto zlib_error; + } + + ut_ad(d_stream->next_out == rec - REC_N_NEW_EXTRA_BYTES); + /* Prepare to decompress the data bytes. */ + d_stream->next_out = rec; + /* Set heap_no and the status bits. */ + mach_write_to_2(rec - REC_NEW_HEAP_NO, heap_status); + heap_status += 1 << REC_HEAP_NO_SHIFT; + + /* Read the offsets. The status bits are needed here. */ + offsets = rec_get_offsets(rec, index, offsets, + ULINT_UNDEFINED, &heap); + + /* This is a leaf page in a clustered index. */ + + /* Check if there are any externally stored columns. + For each externally stored column, restore the + BTR_EXTERN_FIELD_REF separately. */ + + if (UNIV_UNLIKELY(rec_offs_any_extern(offsets))) { + if (UNIV_UNLIKELY + (!page_zip_decompress_clust_ext( + d_stream, rec, offsets, trx_id_col))) { + + goto zlib_error; + } + } else { + /* Skip trx_id and roll_ptr */ + ulint len; + byte* dst = rec_get_nth_field(rec, offsets, + trx_id_col, &len); + if (UNIV_UNLIKELY(len < DATA_TRX_ID_LEN + + DATA_ROLL_PTR_LEN)) { + + page_zip_fail(("page_zip_decompress_clust:" + " len = %lu\n", (ulong) len)); + goto zlib_error; + } + + d_stream->avail_out = dst - d_stream->next_out; + + switch (inflate(d_stream, Z_SYNC_FLUSH)) { + case Z_STREAM_END: + case Z_OK: + case Z_BUF_ERROR: + if (!d_stream->avail_out) { + break; + } + /* fall through */ + default: + page_zip_fail(("page_zip_decompress_clust:" + " 2 inflate(Z_SYNC_FLUSH)=%s\n", + d_stream->msg)); + goto zlib_error; + } + + ut_ad(d_stream->next_out == dst); + + /* Clear DB_TRX_ID and DB_ROLL_PTR in order to + avoid uninitialized bytes in case the record + is affected by page_zip_apply_log(). */ + memset(dst, 0, DATA_TRX_ID_LEN + DATA_ROLL_PTR_LEN); + + d_stream->next_out += DATA_TRX_ID_LEN + + DATA_ROLL_PTR_LEN; + } + + /* Decompress the last bytes of the record. */ + d_stream->avail_out = rec_get_end(rec, offsets) + - d_stream->next_out; + + switch (inflate(d_stream, Z_SYNC_FLUSH)) { + case Z_STREAM_END: + case Z_OK: + case Z_BUF_ERROR: + if (!d_stream->avail_out) { + break; + } + /* fall through */ + default: + page_zip_fail(("page_zip_decompress_clust:" + " 3 inflate(Z_SYNC_FLUSH)=%s\n", + d_stream->msg)); + goto zlib_error; + } + } + + /* Decompress any trailing garbage, in case the last record was + allocated from an originally longer space on the free list. */ + d_stream->avail_out = page_header_get_field(page_zip->data, + PAGE_HEAP_TOP) + - page_offset(d_stream->next_out); + if (UNIV_UNLIKELY(d_stream->avail_out > UNIV_PAGE_SIZE + - PAGE_ZIP_START - PAGE_DIR)) { + + page_zip_fail(("page_zip_decompress_clust:" + " avail_out = %u\n", + d_stream->avail_out)); + goto zlib_error; + } + + if (UNIV_UNLIKELY(inflate(d_stream, Z_FINISH) != Z_STREAM_END)) { + page_zip_fail(("page_zip_decompress_clust:" + " inflate(Z_FINISH)=%s\n", + d_stream->msg)); +zlib_error: + inflateEnd(d_stream); + return(FALSE); + } + + /* Note that d_stream->avail_out > 0 may hold here + if the modification log is nonempty. */ + +zlib_done: + if (UNIV_UNLIKELY(inflateEnd(d_stream) != Z_OK)) { + ut_error; + } + + { + page_t* page = page_align(d_stream->next_out); + + /* Clear the unused heap space on the uncompressed page. */ + memset(d_stream->next_out, 0, + page_dir_get_nth_slot(page, + page_dir_get_n_slots(page) - 1) + - d_stream->next_out); + } + +#ifdef UNIV_DEBUG + page_zip->m_start = PAGE_DATA + d_stream->total_in; +#endif /* UNIV_DEBUG */ + + /* Apply the modification log. */ + { + const byte* mod_log_ptr; + mod_log_ptr = page_zip_apply_log(d_stream->next_in, + d_stream->avail_in + 1, + recs, n_dense, + trx_id_col, heap_status, + index, offsets); + + if (UNIV_UNLIKELY(!mod_log_ptr)) { + return(FALSE); + } + page_zip->m_end = mod_log_ptr - page_zip->data; + page_zip->m_nonempty = mod_log_ptr != d_stream->next_in; + } + + if (UNIV_UNLIKELY(page_zip_get_trailer_len(page_zip, TRUE, NULL) + + page_zip->m_end >= page_zip_get_size(page_zip))) { + + page_zip_fail(("page_zip_decompress_clust: %lu + %lu >= %lu\n", + (ulong) page_zip_get_trailer_len( + page_zip, TRUE, NULL), + (ulong) page_zip->m_end, + (ulong) page_zip_get_size(page_zip))); + return(FALSE); + } + + storage = page_zip->data + page_zip_get_size(page_zip) + - n_dense * PAGE_ZIP_DIR_SLOT_SIZE; + + externs = storage - n_dense + * (DATA_TRX_ID_LEN + DATA_ROLL_PTR_LEN); + + /* Restore the uncompressed columns in heap_no order. */ + + for (slot = 0; slot < n_dense; slot++) { + ulint i; + ulint len; + byte* dst; + rec_t* rec = recs[slot]; + ibool exists = !page_zip_dir_find_free( + page_zip, page_offset(rec)); + offsets = rec_get_offsets(rec, index, offsets, + ULINT_UNDEFINED, &heap); + + dst = rec_get_nth_field(rec, offsets, + trx_id_col, &len); + ut_ad(len >= DATA_TRX_ID_LEN + DATA_ROLL_PTR_LEN); + storage -= DATA_TRX_ID_LEN + DATA_ROLL_PTR_LEN; + memcpy(dst, storage, + DATA_TRX_ID_LEN + DATA_ROLL_PTR_LEN); + + /* Check if there are any externally stored + columns in this record. For each externally + stored column, restore or clear the + BTR_EXTERN_FIELD_REF. */ + if (!rec_offs_any_extern(offsets)) { + continue; + } + + for (i = 0; i < rec_offs_n_fields(offsets); i++) { + if (!rec_offs_nth_extern(offsets, i)) { + continue; + } + dst = rec_get_nth_field(rec, offsets, i, &len); + + if (UNIV_UNLIKELY(len < BTR_EXTERN_FIELD_REF_SIZE)) { + page_zip_fail(("page_zip_decompress_clust:" + " %lu < 20\n", + (ulong) len)); + return(FALSE); + } + + dst += len - BTR_EXTERN_FIELD_REF_SIZE; + + if (UNIV_LIKELY(exists)) { + /* Existing record: + restore the BLOB pointer */ + externs -= BTR_EXTERN_FIELD_REF_SIZE; + + if (UNIV_UNLIKELY + (externs < page_zip->data + + page_zip->m_end)) { + page_zip_fail(("page_zip_" + "decompress_clust: " + "%p < %p + %lu\n", + (const void*) externs, + (const void*) + page_zip->data, + (ulong) + page_zip->m_end)); + return(FALSE); + } + + memcpy(dst, externs, + BTR_EXTERN_FIELD_REF_SIZE); + + page_zip->n_blobs++; + } else { + /* Deleted record: + clear the BLOB pointer */ + memset(dst, 0, + BTR_EXTERN_FIELD_REF_SIZE); + } + } + } + + return(TRUE); +} + +/************************************************************************** +Decompress a page. This function should tolerate errors on the compressed +page. Instead of letting assertions fail, it will return FALSE if an +inconsistency is detected. */ +UNIV_INTERN +ibool +page_zip_decompress( +/*================*/ + /* out: TRUE on success, FALSE on failure */ + page_zip_des_t* page_zip,/* in: data, ssize; + out: m_start, m_end, m_nonempty, n_blobs */ + page_t* page) /* out: uncompressed page, may be trashed */ +{ + z_stream d_stream; + dict_index_t* index = NULL; + rec_t** recs; /* dense page directory, sorted by address */ + ulint n_dense;/* number of user records on the page */ + ulint trx_id_col = ULINT_UNDEFINED; + mem_heap_t* heap; + ulint* offsets; + ullint usec = ut_time_us(NULL); + + ut_ad(page_zip_simple_validate(page_zip)); + UNIV_MEM_ASSERT_W(page, UNIV_PAGE_SIZE); + UNIV_MEM_ASSERT_RW(page_zip->data, page_zip_get_size(page_zip)); + + /* The dense directory excludes the infimum and supremum records. */ + n_dense = page_dir_get_n_heap(page_zip->data) - PAGE_HEAP_NO_USER_LOW; + if (UNIV_UNLIKELY(n_dense * PAGE_ZIP_DIR_SLOT_SIZE + >= page_zip_get_size(page_zip))) { + page_zip_fail(("page_zip_decompress 1: %lu %lu\n", + (ulong) n_dense, + (ulong) page_zip_get_size(page_zip))); + return(FALSE); + } + + heap = mem_heap_create(n_dense * (3 * sizeof *recs) + UNIV_PAGE_SIZE); + recs = mem_heap_alloc(heap, n_dense * (2 * sizeof *recs)); + +#ifdef UNIV_ZIP_DEBUG + /* Clear the page. */ + memset(page, 0x55, UNIV_PAGE_SIZE); +#endif /* UNIV_ZIP_DEBUG */ + UNIV_MEM_INVALID(page, UNIV_PAGE_SIZE); + /* Copy the page header. */ + memcpy(page, page_zip->data, PAGE_DATA); + + /* Copy the page directory. */ + if (UNIV_UNLIKELY(!page_zip_dir_decode(page_zip, page, recs, + recs + n_dense, n_dense))) { +zlib_error: + mem_heap_free(heap); + return(FALSE); + } + + /* Copy the infimum and supremum records. */ + memcpy(page + (PAGE_NEW_INFIMUM - REC_N_NEW_EXTRA_BYTES), + infimum_extra, sizeof infimum_extra); + if (UNIV_UNLIKELY(!page_get_n_recs(page))) { + rec_set_next_offs_new(page + PAGE_NEW_INFIMUM, + PAGE_NEW_SUPREMUM); + } else { + rec_set_next_offs_new(page + PAGE_NEW_INFIMUM, + page_zip_dir_get(page_zip, 0) + & PAGE_ZIP_DIR_SLOT_MASK); + } + memcpy(page + PAGE_NEW_INFIMUM, infimum_data, sizeof infimum_data); + memcpy(page + (PAGE_NEW_SUPREMUM - REC_N_NEW_EXTRA_BYTES + 1), + supremum_extra_data, sizeof supremum_extra_data); + + page_zip_set_alloc(&d_stream, heap); + + if (UNIV_UNLIKELY(inflateInit2(&d_stream, UNIV_PAGE_SIZE_SHIFT) + != Z_OK)) { + ut_error; + } + + d_stream.next_in = page_zip->data + PAGE_DATA; + /* Subtract the space reserved for + the page header and the end marker of the modification log. */ + d_stream.avail_in = page_zip_get_size(page_zip) - (PAGE_DATA + 1); + + d_stream.next_out = page + PAGE_ZIP_START; + d_stream.avail_out = UNIV_PAGE_SIZE - PAGE_ZIP_START; + + /* Decode the zlib header and the index information. */ + if (UNIV_UNLIKELY(inflate(&d_stream, Z_BLOCK) != Z_OK)) { + + page_zip_fail(("page_zip_decompress:" + " 1 inflate(Z_BLOCK)=%s\n", d_stream.msg)); + goto zlib_error; + } + + if (UNIV_UNLIKELY(inflate(&d_stream, Z_BLOCK) != Z_OK)) { + + page_zip_fail(("page_zip_decompress:" + " 2 inflate(Z_BLOCK)=%s\n", d_stream.msg)); + goto zlib_error; + } + + index = page_zip_fields_decode( + page + PAGE_ZIP_START, d_stream.next_out, + page_is_leaf(page) ? &trx_id_col : NULL); + + if (UNIV_UNLIKELY(!index)) { + + goto zlib_error; + } + + /* Decompress the user records. */ + page_zip->n_blobs = 0; + d_stream.next_out = page + PAGE_ZIP_START; + + { + /* Pre-allocate the offsets for rec_get_offsets_reverse(). */ + ulint n = 1 + 1/* node ptr */ + REC_OFFS_HEADER_SIZE + + dict_index_get_n_fields(index); + offsets = mem_heap_alloc(heap, n * sizeof(ulint)); + *offsets = n; + } + + /* Decompress the records in heap_no order. */ + if (!page_is_leaf(page)) { + /* This is a node pointer page. */ + ulint info_bits; + + if (UNIV_UNLIKELY + (!page_zip_decompress_node_ptrs(page_zip, &d_stream, + recs, n_dense, index, + offsets, heap))) { + goto err_exit; + } + + info_bits = mach_read_from_4(page + FIL_PAGE_PREV) == FIL_NULL + ? REC_INFO_MIN_REC_FLAG : 0; + + if (UNIV_UNLIKELY(!page_zip_set_extra_bytes(page_zip, page, + info_bits))) { + goto err_exit; + } + } else if (UNIV_LIKELY(trx_id_col == ULINT_UNDEFINED)) { + /* This is a leaf page in a secondary index. */ + if (UNIV_UNLIKELY(!page_zip_decompress_sec(page_zip, &d_stream, + recs, n_dense, + index, offsets))) { + goto err_exit; + } + + if (UNIV_UNLIKELY(!page_zip_set_extra_bytes(page_zip, + page, 0))) { +err_exit: + page_zip_fields_free(index); + mem_heap_free(heap); + return(FALSE); + } + } else { + /* This is a leaf page in a clustered index. */ + if (UNIV_UNLIKELY(!page_zip_decompress_clust(page_zip, + &d_stream, recs, + n_dense, index, + trx_id_col, + offsets, heap))) { + goto err_exit; + } + + if (UNIV_UNLIKELY(!page_zip_set_extra_bytes(page_zip, + page, 0))) { + goto err_exit; + } + } + + ut_a(page_is_comp(page)); + UNIV_MEM_ASSERT_RW(page, UNIV_PAGE_SIZE); + + page_zip_fields_free(index); + mem_heap_free(heap); + { + page_zip_stat_t* zip_stat + = &page_zip_stat[page_zip->ssize - 1]; + zip_stat->decompressed++; + zip_stat->decompressed_usec += ut_time_us(NULL) - usec; + } + + /* Update the stat counter for LRU policy. */ + buf_LRU_stat_inc_unzip(); + + return(TRUE); +} + +#ifdef UNIV_ZIP_DEBUG +/************************************************************************** +Dump a block of memory on the standard error stream. */ +static +void +page_zip_hexdump_func( +/*==================*/ + const char* name, /* in: name of the data structure */ + const void* buf, /* in: data */ + ulint size) /* in: length of the data, in bytes */ +{ + const byte* s = buf; + ulint addr; + const ulint width = 32; /* bytes per line */ + + fprintf(stderr, "%s:\n", name); + + for (addr = 0; addr < size; addr += width) { + ulint i; + + fprintf(stderr, "%04lx ", (ulong) addr); + + i = ut_min(width, size - addr); + + while (i--) { + fprintf(stderr, "%02x", *s++); + } + + putc('\n', stderr); + } +} + +#define page_zip_hexdump(buf, size) page_zip_hexdump_func(#buf, buf, size) + +/* Flag: make page_zip_validate() compare page headers only */ +UNIV_INTERN ibool page_zip_validate_header_only = FALSE; + +/************************************************************************** +Check that the compressed and decompressed pages match. */ +UNIV_INTERN +ibool +page_zip_validate_low( +/*==================*/ + /* out: TRUE if valid, FALSE if not */ + const page_zip_des_t* page_zip,/* in: compressed page */ + const page_t* page, /* in: uncompressed page */ + ibool sloppy) /* in: FALSE=strict, + TRUE=ignore the MIN_REC_FLAG */ +{ + page_zip_des_t temp_page_zip; + byte* temp_page_buf; + page_t* temp_page; + ibool valid; + + if (memcmp(page_zip->data + FIL_PAGE_PREV, page + FIL_PAGE_PREV, + FIL_PAGE_LSN - FIL_PAGE_PREV) + || memcmp(page_zip->data + FIL_PAGE_TYPE, page + FIL_PAGE_TYPE, 2) + || memcmp(page_zip->data + FIL_PAGE_DATA, page + FIL_PAGE_DATA, + PAGE_DATA - FIL_PAGE_DATA)) { + page_zip_fail(("page_zip_validate: page header\n")); + page_zip_hexdump(page_zip, sizeof *page_zip); + page_zip_hexdump(page_zip->data, page_zip_get_size(page_zip)); + page_zip_hexdump(page, UNIV_PAGE_SIZE); + return(FALSE); + } + + ut_a(page_is_comp(page)); + + if (page_zip_validate_header_only) { + return(TRUE); + } + + /* page_zip_decompress() expects the uncompressed page to be + UNIV_PAGE_SIZE aligned. */ + temp_page_buf = ut_malloc(2 * UNIV_PAGE_SIZE); + temp_page = ut_align(temp_page_buf, UNIV_PAGE_SIZE); + +#ifdef UNIV_DEBUG_VALGRIND + /* Get detailed information on the valid bits in case the + UNIV_MEM_ASSERT_RW() checks fail. The v-bits of page[], + page_zip->data[] or page_zip could be viewed at temp_page[] or + temp_page_zip in a debugger when running valgrind --db-attach. */ + VALGRIND_GET_VBITS(page, temp_page, UNIV_PAGE_SIZE); + UNIV_MEM_ASSERT_RW(page, UNIV_PAGE_SIZE); + VALGRIND_GET_VBITS(page_zip, &temp_page_zip, sizeof temp_page_zip); + UNIV_MEM_ASSERT_RW(page_zip, sizeof *page_zip); + VALGRIND_GET_VBITS(page_zip->data, temp_page, + page_zip_get_size(page_zip)); + UNIV_MEM_ASSERT_RW(page_zip->data, page_zip_get_size(page_zip)); +#endif /* UNIV_DEBUG_VALGRIND */ + + temp_page_zip = *page_zip; + valid = page_zip_decompress(&temp_page_zip, temp_page); + if (!valid) { + fputs("page_zip_validate(): failed to decompress\n", stderr); + goto func_exit; + } + if (page_zip->n_blobs != temp_page_zip.n_blobs) { + page_zip_fail(("page_zip_validate: n_blobs: %u!=%u\n", + page_zip->n_blobs, temp_page_zip.n_blobs)); + valid = FALSE; + } +#ifdef UNIV_DEBUG + if (page_zip->m_start != temp_page_zip.m_start) { + page_zip_fail(("page_zip_validate: m_start: %u!=%u\n", + page_zip->m_start, temp_page_zip.m_start)); + valid = FALSE; + } +#endif /* UNIV_DEBUG */ + if (page_zip->m_end != temp_page_zip.m_end) { + page_zip_fail(("page_zip_validate: m_end: %u!=%u\n", + page_zip->m_end, temp_page_zip.m_end)); + valid = FALSE; + } + if (page_zip->m_nonempty != temp_page_zip.m_nonempty) { + page_zip_fail(("page_zip_validate(): m_nonempty: %u!=%u\n", + page_zip->m_nonempty, + temp_page_zip.m_nonempty)); + valid = FALSE; + } + if (memcmp(page + PAGE_HEADER, temp_page + PAGE_HEADER, + UNIV_PAGE_SIZE - PAGE_HEADER - FIL_PAGE_DATA_END)) { + + /* In crash recovery, the "minimum record" flag may be + set incorrectly until the mini-transaction is + committed. Let us tolerate that difference when we + are performing a sloppy validation. */ + + if (sloppy) { + byte info_bits_diff; + ulint offset + = rec_get_next_offs(page + PAGE_NEW_INFIMUM, + TRUE); + ut_a(offset >= PAGE_NEW_SUPREMUM); + offset -= 5 /* REC_NEW_INFO_BITS */; + + info_bits_diff = page[offset] ^ temp_page[offset]; + + if (info_bits_diff == REC_INFO_MIN_REC_FLAG) { + temp_page[offset] = page[offset]; + + if (!memcmp(page + PAGE_HEADER, + temp_page + PAGE_HEADER, + UNIV_PAGE_SIZE - PAGE_HEADER + - FIL_PAGE_DATA_END)) { + + /* Only the minimum record flag + differed. Let us ignore it. */ + page_zip_fail(("page_zip_validate: " + "min_rec_flag " + "(ignored, " + "%lu,%lu,0x%02lx)\n", + page_get_space_id(page), + page_get_page_no(page), + (ulong) page[offset])); + goto func_exit; + } + } + } + page_zip_fail(("page_zip_validate: content\n")); + valid = FALSE; + } + +func_exit: + if (!valid) { + page_zip_hexdump(page_zip, sizeof *page_zip); + page_zip_hexdump(page_zip->data, page_zip_get_size(page_zip)); + page_zip_hexdump(page, UNIV_PAGE_SIZE); + page_zip_hexdump(temp_page, UNIV_PAGE_SIZE); + } + ut_free(temp_page_buf); + return(valid); +} + +/************************************************************************** +Check that the compressed and decompressed pages match. */ +UNIV_INTERN +ibool +page_zip_validate( +/*==============*/ + /* out: TRUE if valid, FALSE if not */ + const page_zip_des_t* page_zip,/* in: compressed page */ + const page_t* page) /* in: uncompressed page */ +{ + return(page_zip_validate_low(page_zip, page, + recv_recovery_is_on())); +} +#endif /* UNIV_ZIP_DEBUG */ + +#ifdef UNIV_DEBUG +static +ibool +page_zip_header_cmp( +/*================*/ + /* out: TRUE */ + const page_zip_des_t* page_zip,/* in: compressed page */ + const byte* page) /* in: uncompressed page */ +{ + ut_ad(!memcmp(page_zip->data + FIL_PAGE_PREV, page + FIL_PAGE_PREV, + FIL_PAGE_LSN - FIL_PAGE_PREV)); + ut_ad(!memcmp(page_zip->data + FIL_PAGE_TYPE, page + FIL_PAGE_TYPE, + 2)); + ut_ad(!memcmp(page_zip->data + FIL_PAGE_DATA, page + FIL_PAGE_DATA, + PAGE_DATA - FIL_PAGE_DATA)); + + return(TRUE); +} +#endif /* UNIV_DEBUG */ + +/************************************************************************** +Write a record on the compressed page that contains externally stored +columns. The data must already have been written to the uncompressed page. */ +static +byte* +page_zip_write_rec_ext( +/*===================*/ + /* out: end of modification log */ + page_zip_des_t* page_zip, /* in/out: compressed page */ + const page_t* page, /* in: page containing rec */ + const byte* rec, /* in: record being written */ + dict_index_t* index, /* in: record descriptor */ + const ulint* offsets, /* in: rec_get_offsets(rec, index) */ + ulint create, /* in: nonzero=insert, zero=update */ + ulint trx_id_col, /* in: position of DB_TRX_ID */ + ulint heap_no, /* in: heap number of rec */ + byte* storage, /* in: end of dense page directory */ + byte* data) /* in: end of modification log */ +{ + const byte* start = rec; + ulint i; + ulint len; + byte* externs = storage; + ulint n_ext = rec_offs_n_extern(offsets); + + ut_ad(rec_offs_validate(rec, index, offsets)); + UNIV_MEM_ASSERT_RW(rec, rec_offs_data_size(offsets)); + UNIV_MEM_ASSERT_RW(rec - rec_offs_extra_size(offsets), + rec_offs_extra_size(offsets)); + + externs -= (DATA_TRX_ID_LEN + DATA_ROLL_PTR_LEN) + * (page_dir_get_n_heap(page) - PAGE_HEAP_NO_USER_LOW); + + /* Note that this will not take into account + the BLOB columns of rec if create==TRUE. */ + ut_ad(data + rec_offs_data_size(offsets) + - (DATA_TRX_ID_LEN + DATA_ROLL_PTR_LEN) + - n_ext * BTR_EXTERN_FIELD_REF_SIZE + < externs - BTR_EXTERN_FIELD_REF_SIZE * page_zip->n_blobs); + + { + ulint blob_no = page_zip_get_n_prev_extern( + page_zip, rec, index); + byte* ext_end = externs - page_zip->n_blobs + * BTR_EXTERN_FIELD_REF_SIZE; + ut_ad(blob_no <= page_zip->n_blobs); + externs -= blob_no * BTR_EXTERN_FIELD_REF_SIZE; + + if (create) { + page_zip->n_blobs += n_ext; + ASSERT_ZERO_BLOB(ext_end - n_ext + * BTR_EXTERN_FIELD_REF_SIZE); + memmove(ext_end - n_ext + * BTR_EXTERN_FIELD_REF_SIZE, + ext_end, + externs - ext_end); + } + + ut_a(blob_no + n_ext <= page_zip->n_blobs); + } + + for (i = 0; i < rec_offs_n_fields(offsets); i++) { + const byte* src; + + if (UNIV_UNLIKELY(i == trx_id_col)) { + ut_ad(!rec_offs_nth_extern(offsets, + i)); + ut_ad(!rec_offs_nth_extern(offsets, + i + 1)); + /* Locate trx_id and roll_ptr. */ + src = rec_get_nth_field(rec, offsets, + i, &len); + ut_ad(len == DATA_TRX_ID_LEN); + ut_ad(src + DATA_TRX_ID_LEN + == rec_get_nth_field( + rec, offsets, + i + 1, &len)); + ut_ad(len == DATA_ROLL_PTR_LEN); + + /* Log the preceding fields. */ + ASSERT_ZERO(data, src - start); + memcpy(data, start, src - start); + data += src - start; + start = src + (DATA_TRX_ID_LEN + + DATA_ROLL_PTR_LEN); + + /* Store trx_id and roll_ptr. */ + memcpy(storage - (DATA_TRX_ID_LEN + DATA_ROLL_PTR_LEN) + * (heap_no - 1), + src, DATA_TRX_ID_LEN + DATA_ROLL_PTR_LEN); + i++; /* skip also roll_ptr */ + } else if (rec_offs_nth_extern(offsets, i)) { + src = rec_get_nth_field(rec, offsets, + i, &len); + + ut_ad(dict_index_is_clust(index)); + ut_ad(len + >= BTR_EXTERN_FIELD_REF_SIZE); + src += len - BTR_EXTERN_FIELD_REF_SIZE; + + ASSERT_ZERO(data, src - start); + memcpy(data, start, src - start); + data += src - start; + start = src + BTR_EXTERN_FIELD_REF_SIZE; + + /* Store the BLOB pointer. */ + externs -= BTR_EXTERN_FIELD_REF_SIZE; + ut_ad(data < externs); + memcpy(externs, src, BTR_EXTERN_FIELD_REF_SIZE); + } + } + + /* Log the last bytes of the record. */ + len = rec_offs_data_size(offsets) - (start - rec); + + ASSERT_ZERO(data, len); + memcpy(data, start, len); + data += len; + + return(data); +} + +/************************************************************************** +Write an entire record on the compressed page. The data must already +have been written to the uncompressed page. */ +UNIV_INTERN +void +page_zip_write_rec( +/*===============*/ + page_zip_des_t* page_zip,/* in/out: compressed page */ + const byte* rec, /* in: record being written */ + dict_index_t* index, /* in: the index the record belongs to */ + const ulint* offsets,/* in: rec_get_offsets(rec, index) */ + ulint create) /* in: nonzero=insert, zero=update */ +{ + const page_t* page; + byte* data; + byte* storage; + ulint heap_no; + byte* slot; + + ut_ad(buf_frame_get_page_zip(rec) == page_zip); + ut_ad(page_zip_simple_validate(page_zip)); + ut_ad(page_zip_get_size(page_zip) + > PAGE_DATA + page_zip_dir_size(page_zip)); + ut_ad(rec_offs_comp(offsets)); + ut_ad(rec_offs_validate(rec, index, offsets)); + + ut_ad(page_zip->m_start >= PAGE_DATA); + + page = page_align(rec); + + ut_ad(page_zip_header_cmp(page_zip, page)); + ut_ad(page_simple_validate_new((page_t*) page)); + + UNIV_MEM_ASSERT_RW(page_zip->data, page_zip_get_size(page_zip)); + UNIV_MEM_ASSERT_RW(rec, rec_offs_data_size(offsets)); + UNIV_MEM_ASSERT_RW(rec - rec_offs_extra_size(offsets), + rec_offs_extra_size(offsets)); + + slot = page_zip_dir_find(page_zip, page_offset(rec)); + ut_a(slot); + /* Copy the delete mark. */ + if (rec_get_deleted_flag(rec, TRUE)) { + *slot |= PAGE_ZIP_DIR_SLOT_DEL >> 8; + } else { + *slot &= ~(PAGE_ZIP_DIR_SLOT_DEL >> 8); + } + + ut_ad(rec_get_start((rec_t*) rec, offsets) >= page + PAGE_ZIP_START); + ut_ad(rec_get_end((rec_t*) rec, offsets) <= page + UNIV_PAGE_SIZE + - PAGE_DIR - PAGE_DIR_SLOT_SIZE + * page_dir_get_n_slots(page)); + + heap_no = rec_get_heap_no_new(rec); + ut_ad(heap_no >= PAGE_HEAP_NO_USER_LOW); /* not infimum or supremum */ + ut_ad(heap_no < page_dir_get_n_heap(page)); + + /* Append to the modification log. */ + data = page_zip->data + page_zip->m_end; + ut_ad(!*data); + + /* Identify the record by writing its heap number - 1. + 0 is reserved to indicate the end of the modification log. */ + + if (UNIV_UNLIKELY(heap_no - 1 >= 64)) { + *data++ = (byte) (0x80 | (heap_no - 1) >> 7); + ut_ad(!*data); + } + *data++ = (byte) ((heap_no - 1) << 1); + ut_ad(!*data); + + { + const byte* start = rec - rec_offs_extra_size(offsets); + const byte* b = rec - REC_N_NEW_EXTRA_BYTES; + + /* Write the extra bytes backwards, so that + rec_offs_extra_size() can be easily computed in + page_zip_apply_log() by invoking + rec_get_offsets_reverse(). */ + + while (b != start) { + *data++ = *--b; + ut_ad(!*data); + } + } + + /* Write the data bytes. Store the uncompressed bytes separately. */ + storage = page_zip->data + page_zip_get_size(page_zip) + - (page_dir_get_n_heap(page) - PAGE_HEAP_NO_USER_LOW) + * PAGE_ZIP_DIR_SLOT_SIZE; + + if (page_is_leaf(page)) { + ulint len; + + if (dict_index_is_clust(index)) { + ulint trx_id_col; + + trx_id_col = dict_index_get_sys_col_pos(index, + DATA_TRX_ID); + ut_ad(trx_id_col != ULINT_UNDEFINED); + + /* Store separately trx_id, roll_ptr and + the BTR_EXTERN_FIELD_REF of each BLOB column. */ + if (rec_offs_any_extern(offsets)) { + data = page_zip_write_rec_ext( + page_zip, page, + rec, index, offsets, create, + trx_id_col, heap_no, storage, data); + } else { + /* Locate trx_id and roll_ptr. */ + const byte* src + = rec_get_nth_field(rec, offsets, + trx_id_col, &len); + ut_ad(len == DATA_TRX_ID_LEN); + ut_ad(src + DATA_TRX_ID_LEN + == rec_get_nth_field( + rec, offsets, + trx_id_col + 1, &len)); + ut_ad(len == DATA_ROLL_PTR_LEN); + + /* Log the preceding fields. */ + ASSERT_ZERO(data, src - rec); + memcpy(data, rec, src - rec); + data += src - rec; + + /* Store trx_id and roll_ptr. */ + memcpy(storage + - (DATA_TRX_ID_LEN + DATA_ROLL_PTR_LEN) + * (heap_no - 1), + src, + DATA_TRX_ID_LEN + DATA_ROLL_PTR_LEN); + + src += DATA_TRX_ID_LEN + DATA_ROLL_PTR_LEN; + + /* Log the last bytes of the record. */ + len = rec_offs_data_size(offsets) + - (src - rec); + + ASSERT_ZERO(data, len); + memcpy(data, src, len); + data += len; + } + } else { + /* Leaf page of a secondary index: + no externally stored columns */ + ut_ad(dict_index_get_sys_col_pos(index, DATA_TRX_ID) + == ULINT_UNDEFINED); + ut_ad(!rec_offs_any_extern(offsets)); + + /* Log the entire record. */ + len = rec_offs_data_size(offsets); + + ASSERT_ZERO(data, len); + memcpy(data, rec, len); + data += len; + } + } else { + /* This is a node pointer page. */ + ulint len; + + /* Non-leaf nodes should not have any externally + stored columns. */ + ut_ad(!rec_offs_any_extern(offsets)); + + /* Copy the data bytes, except node_ptr. */ + len = rec_offs_data_size(offsets) - REC_NODE_PTR_SIZE; + ut_ad(data + len < storage - REC_NODE_PTR_SIZE + * (page_dir_get_n_heap(page) - PAGE_HEAP_NO_USER_LOW)); + ASSERT_ZERO(data, len); + memcpy(data, rec, len); + data += len; + + /* Copy the node pointer to the uncompressed area. */ + memcpy(storage - REC_NODE_PTR_SIZE + * (heap_no - 1), + rec + len, + REC_NODE_PTR_SIZE); + } + + ut_a(!*data); + ut_ad((ulint) (data - page_zip->data) < page_zip_get_size(page_zip)); + page_zip->m_end = data - page_zip->data; + page_zip->m_nonempty = TRUE; + +#ifdef UNIV_ZIP_DEBUG + ut_a(page_zip_validate(page_zip, page_align(rec))); +#endif /* UNIV_ZIP_DEBUG */ +} + +/*************************************************************** +Parses a log record of writing a BLOB pointer of a record. */ +UNIV_INTERN +byte* +page_zip_parse_write_blob_ptr( +/*==========================*/ + /* out: end of log record or NULL */ + byte* ptr, /* in: redo log buffer */ + byte* end_ptr,/* in: redo log buffer end */ + page_t* page, /* in/out: uncompressed page */ + page_zip_des_t* page_zip)/* in/out: compressed page */ +{ + ulint offset; + ulint z_offset; + + ut_ad(!page == !page_zip); + + if (UNIV_UNLIKELY + (end_ptr < ptr + (2 + 2 + BTR_EXTERN_FIELD_REF_SIZE))) { + + return(NULL); + } + + offset = mach_read_from_2(ptr); + z_offset = mach_read_from_2(ptr + 2); + + if (UNIV_UNLIKELY(offset < PAGE_ZIP_START) + || UNIV_UNLIKELY(offset >= UNIV_PAGE_SIZE) + || UNIV_UNLIKELY(z_offset >= UNIV_PAGE_SIZE)) { +corrupt: + recv_sys->found_corrupt_log = TRUE; + + return(NULL); + } + + if (page) { + if (UNIV_UNLIKELY(!page_zip) + || UNIV_UNLIKELY(!page_is_leaf(page))) { + + goto corrupt; + } + +#ifdef UNIV_ZIP_DEBUG + ut_a(page_zip_validate(page_zip, page)); +#endif /* UNIV_ZIP_DEBUG */ + + memcpy(page + offset, + ptr + 4, BTR_EXTERN_FIELD_REF_SIZE); + memcpy(page_zip->data + z_offset, + ptr + 4, BTR_EXTERN_FIELD_REF_SIZE); + +#ifdef UNIV_ZIP_DEBUG + ut_a(page_zip_validate(page_zip, page)); +#endif /* UNIV_ZIP_DEBUG */ + } + + return(ptr + (2 + 2 + BTR_EXTERN_FIELD_REF_SIZE)); +} + +/************************************************************************** +Write a BLOB pointer of a record on the leaf page of a clustered index. +The information must already have been updated on the uncompressed page. */ +UNIV_INTERN +void +page_zip_write_blob_ptr( +/*====================*/ + page_zip_des_t* page_zip,/* in/out: compressed page */ + const byte* rec, /* in/out: record whose data is being + written */ + dict_index_t* index, /* in: index of the page */ + const ulint* offsets,/* in: rec_get_offsets(rec, index) */ + ulint n, /* in: column index */ + mtr_t* mtr) /* in: mini-transaction handle, + or NULL if no logging is needed */ +{ + const byte* field; + byte* externs; + const page_t* page = page_align(rec); + ulint blob_no; + ulint len; + + ut_ad(buf_frame_get_page_zip(rec) == page_zip); + ut_ad(page_simple_validate_new((page_t*) page)); + ut_ad(page_zip_simple_validate(page_zip)); + ut_ad(page_zip_get_size(page_zip) + > PAGE_DATA + page_zip_dir_size(page_zip)); + ut_ad(rec_offs_comp(offsets)); + ut_ad(rec_offs_validate(rec, NULL, offsets)); + ut_ad(rec_offs_any_extern(offsets)); + ut_ad(rec_offs_nth_extern(offsets, n)); + + ut_ad(page_zip->m_start >= PAGE_DATA); + ut_ad(page_zip_header_cmp(page_zip, page)); + + ut_ad(page_is_leaf(page)); + ut_ad(dict_index_is_clust(index)); + + UNIV_MEM_ASSERT_RW(page_zip->data, page_zip_get_size(page_zip)); + UNIV_MEM_ASSERT_RW(rec, rec_offs_data_size(offsets)); + UNIV_MEM_ASSERT_RW(rec - rec_offs_extra_size(offsets), + rec_offs_extra_size(offsets)); + + blob_no = page_zip_get_n_prev_extern(page_zip, rec, index) + + rec_get_n_extern_new(rec, index, n); + ut_a(blob_no < page_zip->n_blobs); + + externs = page_zip->data + page_zip_get_size(page_zip) + - (page_dir_get_n_heap(page) - PAGE_HEAP_NO_USER_LOW) + * (PAGE_ZIP_DIR_SLOT_SIZE + + DATA_TRX_ID_LEN + DATA_ROLL_PTR_LEN); + + field = rec_get_nth_field(rec, offsets, n, &len); + + externs -= (blob_no + 1) * BTR_EXTERN_FIELD_REF_SIZE; + field += len - BTR_EXTERN_FIELD_REF_SIZE; + + memcpy(externs, field, BTR_EXTERN_FIELD_REF_SIZE); + +#ifdef UNIV_ZIP_DEBUG + ut_a(page_zip_validate(page_zip, page)); +#endif /* UNIV_ZIP_DEBUG */ + + if (mtr) { + byte* log_ptr = mlog_open( + mtr, 11 + 2 + 2 + BTR_EXTERN_FIELD_REF_SIZE); + if (UNIV_UNLIKELY(!log_ptr)) { + return; + } + + log_ptr = mlog_write_initial_log_record_fast( + (byte*) field, MLOG_ZIP_WRITE_BLOB_PTR, log_ptr, mtr); + mach_write_to_2(log_ptr, page_offset(field)); + log_ptr += 2; + mach_write_to_2(log_ptr, externs - page_zip->data); + log_ptr += 2; + memcpy(log_ptr, externs, BTR_EXTERN_FIELD_REF_SIZE); + log_ptr += BTR_EXTERN_FIELD_REF_SIZE; + mlog_close(mtr, log_ptr); + } +} + +/*************************************************************** +Parses a log record of writing the node pointer of a record. */ +UNIV_INTERN +byte* +page_zip_parse_write_node_ptr( +/*==========================*/ + /* out: end of log record or NULL */ + byte* ptr, /* in: redo log buffer */ + byte* end_ptr,/* in: redo log buffer end */ + page_t* page, /* in/out: uncompressed page */ + page_zip_des_t* page_zip)/* in/out: compressed page */ +{ + ulint offset; + ulint z_offset; + + ut_ad(!page == !page_zip); + + if (UNIV_UNLIKELY(end_ptr < ptr + (2 + 2 + REC_NODE_PTR_SIZE))) { + + return(NULL); + } + + offset = mach_read_from_2(ptr); + z_offset = mach_read_from_2(ptr + 2); + + if (UNIV_UNLIKELY(offset < PAGE_ZIP_START) + || UNIV_UNLIKELY(offset >= UNIV_PAGE_SIZE) + || UNIV_UNLIKELY(z_offset >= UNIV_PAGE_SIZE)) { +corrupt: + recv_sys->found_corrupt_log = TRUE; + + return(NULL); + } + + if (page) { + byte* storage_end; + byte* field; + byte* storage; + ulint heap_no; + + if (UNIV_UNLIKELY(!page_zip) + || UNIV_UNLIKELY(page_is_leaf(page))) { + + goto corrupt; + } + +#ifdef UNIV_ZIP_DEBUG + ut_a(page_zip_validate(page_zip, page)); +#endif /* UNIV_ZIP_DEBUG */ + + field = page + offset; + storage = page_zip->data + z_offset; + + storage_end = page_zip->data + page_zip_get_size(page_zip) + - (page_dir_get_n_heap(page) - PAGE_HEAP_NO_USER_LOW) + * PAGE_ZIP_DIR_SLOT_SIZE; + + heap_no = 1 + (storage_end - storage) / REC_NODE_PTR_SIZE; + + if (UNIV_UNLIKELY((storage_end - storage) % REC_NODE_PTR_SIZE) + || UNIV_UNLIKELY(heap_no < PAGE_HEAP_NO_USER_LOW) + || UNIV_UNLIKELY(heap_no >= page_dir_get_n_heap(page))) { + + goto corrupt; + } + + memcpy(field, ptr + 4, REC_NODE_PTR_SIZE); + memcpy(storage, ptr + 4, REC_NODE_PTR_SIZE); + +#ifdef UNIV_ZIP_DEBUG + ut_a(page_zip_validate(page_zip, page)); +#endif /* UNIV_ZIP_DEBUG */ + } + + return(ptr + (2 + 2 + REC_NODE_PTR_SIZE)); +} + +/************************************************************************** +Write the node pointer of a record on a non-leaf compressed page. */ +UNIV_INTERN +void +page_zip_write_node_ptr( +/*====================*/ + page_zip_des_t* page_zip,/* in/out: compressed page */ + byte* rec, /* in/out: record */ + ulint size, /* in: data size of rec */ + ulint ptr, /* in: node pointer */ + mtr_t* mtr) /* in: mini-transaction, or NULL */ +{ + byte* field; + byte* storage; + page_t* page = page_align(rec); + + ut_ad(buf_frame_get_page_zip(rec) == page_zip); + ut_ad(page_simple_validate_new(page)); + ut_ad(page_zip_simple_validate(page_zip)); + ut_ad(page_zip_get_size(page_zip) + > PAGE_DATA + page_zip_dir_size(page_zip)); + ut_ad(page_rec_is_comp(rec)); + + ut_ad(page_zip->m_start >= PAGE_DATA); + ut_ad(page_zip_header_cmp(page_zip, page)); + + ut_ad(!page_is_leaf(page)); + + UNIV_MEM_ASSERT_RW(page_zip->data, page_zip_get_size(page_zip)); + UNIV_MEM_ASSERT_RW(rec, size); + + storage = page_zip->data + page_zip_get_size(page_zip) + - (page_dir_get_n_heap(page) - PAGE_HEAP_NO_USER_LOW) + * PAGE_ZIP_DIR_SLOT_SIZE + - (rec_get_heap_no_new(rec) - 1) * REC_NODE_PTR_SIZE; + field = rec + size - REC_NODE_PTR_SIZE; + +#if defined UNIV_DEBUG || defined UNIV_ZIP_DEBUG + ut_a(!memcmp(storage, field, REC_NODE_PTR_SIZE)); +#endif /* UNIV_DEBUG || UNIV_ZIP_DEBUG */ +#if REC_NODE_PTR_SIZE != 4 +# error "REC_NODE_PTR_SIZE != 4" +#endif + mach_write_to_4(field, ptr); + memcpy(storage, field, REC_NODE_PTR_SIZE); + + if (mtr) { + byte* log_ptr = mlog_open(mtr, + 11 + 2 + 2 + REC_NODE_PTR_SIZE); + if (UNIV_UNLIKELY(!log_ptr)) { + return; + } + + log_ptr = mlog_write_initial_log_record_fast( + field, MLOG_ZIP_WRITE_NODE_PTR, log_ptr, mtr); + mach_write_to_2(log_ptr, page_offset(field)); + log_ptr += 2; + mach_write_to_2(log_ptr, storage - page_zip->data); + log_ptr += 2; + memcpy(log_ptr, field, REC_NODE_PTR_SIZE); + log_ptr += REC_NODE_PTR_SIZE; + mlog_close(mtr, log_ptr); + } +} + +/************************************************************************** +Write the trx_id and roll_ptr of a record on a B-tree leaf node page. */ +UNIV_INTERN +void +page_zip_write_trx_id_and_roll_ptr( +/*===============================*/ + page_zip_des_t* page_zip,/* in/out: compressed page */ + byte* rec, /* in/out: record */ + const ulint* offsets,/* in: rec_get_offsets(rec, index) */ + ulint trx_id_col,/* in: column number of TRX_ID in rec */ + dulint trx_id, /* in: transaction identifier */ + dulint roll_ptr)/* in: roll_ptr */ +{ + byte* field; + byte* storage; + page_t* page = page_align(rec); + ulint len; + + ut_ad(buf_frame_get_page_zip(rec) == page_zip); + ut_ad(page_simple_validate_new(page)); + ut_ad(page_zip_simple_validate(page_zip)); + ut_ad(page_zip_get_size(page_zip) + > PAGE_DATA + page_zip_dir_size(page_zip)); + ut_ad(rec_offs_validate(rec, NULL, offsets)); + ut_ad(rec_offs_comp(offsets)); + + ut_ad(page_zip->m_start >= PAGE_DATA); + ut_ad(page_zip_header_cmp(page_zip, page)); + + ut_ad(page_is_leaf(page)); + + UNIV_MEM_ASSERT_RW(page_zip->data, page_zip_get_size(page_zip)); + + storage = page_zip->data + page_zip_get_size(page_zip) + - (page_dir_get_n_heap(page) - PAGE_HEAP_NO_USER_LOW) + * PAGE_ZIP_DIR_SLOT_SIZE + - (rec_get_heap_no_new(rec) - 1) + * (DATA_TRX_ID_LEN + DATA_ROLL_PTR_LEN); + +#if DATA_TRX_ID + 1 != DATA_ROLL_PTR +# error "DATA_TRX_ID + 1 != DATA_ROLL_PTR" +#endif + field = rec_get_nth_field(rec, offsets, trx_id_col, &len); + ut_ad(len == DATA_TRX_ID_LEN); + ut_ad(field + DATA_TRX_ID_LEN + == rec_get_nth_field(rec, offsets, trx_id_col + 1, &len)); + ut_ad(len == DATA_ROLL_PTR_LEN); +#if defined UNIV_DEBUG || defined UNIV_ZIP_DEBUG + ut_a(!memcmp(storage, field, DATA_TRX_ID_LEN + DATA_ROLL_PTR_LEN)); +#endif /* UNIV_DEBUG || UNIV_ZIP_DEBUG */ +#if DATA_TRX_ID_LEN != 6 +# error "DATA_TRX_ID_LEN != 6" +#endif + mach_write_to_6(field, trx_id); +#if DATA_ROLL_PTR_LEN != 7 +# error "DATA_ROLL_PTR_LEN != 7" +#endif + mach_write_to_7(field + DATA_TRX_ID_LEN, roll_ptr); + memcpy(storage, field, DATA_TRX_ID_LEN + DATA_ROLL_PTR_LEN); + + UNIV_MEM_ASSERT_RW(rec, rec_offs_data_size(offsets)); + UNIV_MEM_ASSERT_RW(rec - rec_offs_extra_size(offsets), + rec_offs_extra_size(offsets)); + UNIV_MEM_ASSERT_RW(page_zip->data, page_zip_get_size(page_zip)); +} + +#ifdef UNIV_ZIP_DEBUG +/* Set this variable in a debugger to disable page_zip_clear_rec(). +The only observable effect should be the compression ratio due to +deleted records not being zeroed out. In rare cases, there can be +page_zip_validate() failures on the node_ptr, trx_id and roll_ptr +columns if the space is reallocated for a smaller record. */ +UNIV_INTERN ibool page_zip_clear_rec_disable; +#endif /* UNIV_ZIP_DEBUG */ + +/************************************************************************** +Clear an area on the uncompressed and compressed page, if possible. */ +static +void +page_zip_clear_rec( +/*===============*/ + page_zip_des_t* page_zip,/* in/out: compressed page */ + byte* rec, /* in: record to clear */ + dict_index_t* index, /* in: index of rec */ + const ulint* offsets)/* in: rec_get_offsets(rec, index) */ +{ + ulint heap_no; + page_t* page = page_align(rec); + /* page_zip_validate() would fail here if a record + containing externally stored columns is being deleted. */ + ut_ad(rec_offs_validate(rec, index, offsets)); + ut_ad(!page_zip_dir_find(page_zip, page_offset(rec))); + ut_ad(page_zip_dir_find_free(page_zip, page_offset(rec))); + ut_ad(page_zip_header_cmp(page_zip, page)); + + heap_no = rec_get_heap_no_new(rec); + ut_ad(heap_no >= PAGE_HEAP_NO_USER_LOW); + + UNIV_MEM_ASSERT_RW(page_zip->data, page_zip_get_size(page_zip)); + UNIV_MEM_ASSERT_RW(rec, rec_offs_data_size(offsets)); + UNIV_MEM_ASSERT_RW(rec - rec_offs_extra_size(offsets), + rec_offs_extra_size(offsets)); + + if ( +#ifdef UNIV_ZIP_DEBUG + !page_zip_clear_rec_disable && +#endif /* UNIV_ZIP_DEBUG */ + page_zip->m_end + + 1 + ((heap_no - 1) >= 64)/* size of the log entry */ + + page_zip_get_trailer_len(page_zip, + dict_index_is_clust(index), NULL) + < page_zip_get_size(page_zip)) { + byte* data; + + /* Clear only the data bytes, because the allocator and + the decompressor depend on the extra bytes. */ + memset(rec, 0, rec_offs_data_size(offsets)); + + if (!page_is_leaf(page)) { + /* Clear node_ptr on the compressed page. */ + byte* storage = page_zip->data + + page_zip_get_size(page_zip) + - (page_dir_get_n_heap(page) + - PAGE_HEAP_NO_USER_LOW) + * PAGE_ZIP_DIR_SLOT_SIZE; + + memset(storage - (heap_no - 1) * REC_NODE_PTR_SIZE, + 0, REC_NODE_PTR_SIZE); + } else if (dict_index_is_clust(index)) { + /* Clear trx_id and roll_ptr on the compressed page. */ + byte* storage = page_zip->data + + page_zip_get_size(page_zip) + - (page_dir_get_n_heap(page) + - PAGE_HEAP_NO_USER_LOW) + * PAGE_ZIP_DIR_SLOT_SIZE; + + memset(storage - (heap_no - 1) + * (DATA_TRX_ID_LEN + DATA_ROLL_PTR_LEN), + 0, DATA_TRX_ID_LEN + DATA_ROLL_PTR_LEN); + } + + /* Log that the data was zeroed out. */ + data = page_zip->data + page_zip->m_end; + ut_ad(!*data); + if (UNIV_UNLIKELY(heap_no - 1 >= 64)) { + *data++ = (byte) (0x80 | (heap_no - 1) >> 7); + ut_ad(!*data); + } + *data++ = (byte) ((heap_no - 1) << 1 | 1); + ut_ad(!*data); + ut_ad((ulint) (data - page_zip->data) + < page_zip_get_size(page_zip)); + page_zip->m_end = data - page_zip->data; + page_zip->m_nonempty = TRUE; + } else if (page_is_leaf(page) && dict_index_is_clust(index)) { + /* Do not clear the record, because there is not enough space + to log the operation. */ + + if (rec_offs_any_extern(offsets)) { + ulint i; + + for (i = rec_offs_n_fields(offsets); i--; ) { + /* Clear all BLOB pointers in order to make + page_zip_validate() pass. */ + if (rec_offs_nth_extern(offsets, i)) { + ulint len; + byte* field = rec_get_nth_field( + rec, offsets, i, &len); + memset(field + len + - BTR_EXTERN_FIELD_REF_SIZE, + 0, BTR_EXTERN_FIELD_REF_SIZE); + } + } + } + } + +#ifdef UNIV_ZIP_DEBUG + ut_a(page_zip_validate(page_zip, page)); +#endif /* UNIV_ZIP_DEBUG */ +} + +/************************************************************************** +Write the "deleted" flag of a record on a compressed page. The flag must +already have been written on the uncompressed page. */ +UNIV_INTERN +void +page_zip_rec_set_deleted( +/*=====================*/ + page_zip_des_t* page_zip,/* in/out: compressed page */ + const byte* rec, /* in: record on the uncompressed page */ + ulint flag) /* in: the deleted flag (nonzero=TRUE) */ +{ + byte* slot = page_zip_dir_find(page_zip, page_offset(rec)); + ut_a(slot); + UNIV_MEM_ASSERT_RW(page_zip->data, page_zip_get_size(page_zip)); + if (flag) { + *slot |= (PAGE_ZIP_DIR_SLOT_DEL >> 8); + } else { + *slot &= ~(PAGE_ZIP_DIR_SLOT_DEL >> 8); + } +#ifdef UNIV_ZIP_DEBUG + ut_a(page_zip_validate(page_zip, page_align(rec))); +#endif /* UNIV_ZIP_DEBUG */ +} + +/************************************************************************** +Write the "owned" flag of a record on a compressed page. The n_owned field +must already have been written on the uncompressed page. */ +UNIV_INTERN +void +page_zip_rec_set_owned( +/*===================*/ + page_zip_des_t* page_zip,/* in/out: compressed page */ + const byte* rec, /* in: record on the uncompressed page */ + ulint flag) /* in: the owned flag (nonzero=TRUE) */ +{ + byte* slot = page_zip_dir_find(page_zip, page_offset(rec)); + ut_a(slot); + UNIV_MEM_ASSERT_RW(page_zip->data, page_zip_get_size(page_zip)); + if (flag) { + *slot |= (PAGE_ZIP_DIR_SLOT_OWNED >> 8); + } else { + *slot &= ~(PAGE_ZIP_DIR_SLOT_OWNED >> 8); + } +} + +/************************************************************************** +Insert a record to the dense page directory. */ +UNIV_INTERN +void +page_zip_dir_insert( +/*================*/ + page_zip_des_t* page_zip,/* in/out: compressed page */ + const byte* prev_rec,/* in: record after which to insert */ + const byte* free_rec,/* in: record from which rec was + allocated, or NULL */ + byte* rec) /* in: record to insert */ +{ + ulint n_dense; + byte* slot_rec; + byte* slot_free; + + ut_ad(prev_rec != rec); + ut_ad(page_rec_get_next((rec_t*) prev_rec) == rec); + ut_ad(page_zip_simple_validate(page_zip)); + + UNIV_MEM_ASSERT_RW(page_zip->data, page_zip_get_size(page_zip)); + + if (page_rec_is_infimum(prev_rec)) { + /* Use the first slot. */ + slot_rec = page_zip->data + page_zip_get_size(page_zip); + } else { + byte* end = page_zip->data + page_zip_get_size(page_zip); + byte* start = end - page_zip_dir_user_size(page_zip); + + if (UNIV_LIKELY(!free_rec)) { + /* PAGE_N_RECS was already incremented + in page_cur_insert_rec_zip(), but the + dense directory slot at that position + contains garbage. Skip it. */ + start += PAGE_ZIP_DIR_SLOT_SIZE; + } + + slot_rec = page_zip_dir_find_low(start, end, + page_offset(prev_rec)); + ut_a(slot_rec); + } + + /* Read the old n_dense (n_heap may have been incremented). */ + n_dense = page_dir_get_n_heap(page_zip->data) + - (PAGE_HEAP_NO_USER_LOW + 1); + + if (UNIV_LIKELY_NULL(free_rec)) { + /* The record was allocated from the free list. + Shift the dense directory only up to that slot. + Note that in this case, n_dense is actually + off by one, because page_cur_insert_rec_zip() + did not increment n_heap. */ + ut_ad(rec_get_heap_no_new(rec) < n_dense + 1 + + PAGE_HEAP_NO_USER_LOW); + ut_ad(rec >= free_rec); + slot_free = page_zip_dir_find(page_zip, page_offset(free_rec)); + ut_ad(slot_free); + slot_free += PAGE_ZIP_DIR_SLOT_SIZE; + } else { + /* The record was allocated from the heap. + Shift the entire dense directory. */ + ut_ad(rec_get_heap_no_new(rec) == n_dense + + PAGE_HEAP_NO_USER_LOW); + + /* Shift to the end of the dense page directory. */ + slot_free = page_zip->data + page_zip_get_size(page_zip) + - PAGE_ZIP_DIR_SLOT_SIZE * n_dense; + } + + /* Shift the dense directory to allocate place for rec. */ + memmove(slot_free - PAGE_ZIP_DIR_SLOT_SIZE, slot_free, + slot_rec - slot_free); + + /* Write the entry for the inserted record. + The "owned" and "deleted" flags must be zero. */ + mach_write_to_2(slot_rec - PAGE_ZIP_DIR_SLOT_SIZE, page_offset(rec)); +} + +/************************************************************************** +Shift the dense page directory and the array of BLOB pointers +when a record is deleted. */ +UNIV_INTERN +void +page_zip_dir_delete( +/*================*/ + page_zip_des_t* page_zip,/* in/out: compressed page */ + byte* rec, /* in: record to delete */ + dict_index_t* index, /* in: index of rec */ + const ulint* offsets,/* in: rec_get_offsets(rec) */ + const byte* free) /* in: previous start of the free list */ +{ + byte* slot_rec; + byte* slot_free; + ulint n_ext; + page_t* page = page_align(rec); + + ut_ad(rec_offs_validate(rec, index, offsets)); + ut_ad(rec_offs_comp(offsets)); + + UNIV_MEM_ASSERT_RW(page_zip->data, page_zip_get_size(page_zip)); + UNIV_MEM_ASSERT_RW(rec, rec_offs_data_size(offsets)); + UNIV_MEM_ASSERT_RW(rec - rec_offs_extra_size(offsets), + rec_offs_extra_size(offsets)); + + slot_rec = page_zip_dir_find(page_zip, page_offset(rec)); + + ut_a(slot_rec); + + /* This could not be done before page_zip_dir_find(). */ + page_header_set_field(page, page_zip, PAGE_N_RECS, + (ulint)(page_get_n_recs(page) - 1)); + + if (UNIV_UNLIKELY(!free)) { + /* Make the last slot the start of the free list. */ + slot_free = page_zip->data + page_zip_get_size(page_zip) + - PAGE_ZIP_DIR_SLOT_SIZE + * (page_dir_get_n_heap(page_zip->data) + - PAGE_HEAP_NO_USER_LOW); + } else { + slot_free = page_zip_dir_find_free(page_zip, + page_offset(free)); + ut_a(slot_free < slot_rec); + /* Grow the free list by one slot by moving the start. */ + slot_free += PAGE_ZIP_DIR_SLOT_SIZE; + } + + if (UNIV_LIKELY(slot_rec > slot_free)) { + memmove(slot_free + PAGE_ZIP_DIR_SLOT_SIZE, + slot_free, + slot_rec - slot_free); + } + + /* Write the entry for the deleted record. + The "owned" and "deleted" flags will be cleared. */ + mach_write_to_2(slot_free, page_offset(rec)); + + if (!page_is_leaf(page) || !dict_index_is_clust(index)) { + ut_ad(!rec_offs_any_extern(offsets)); + goto skip_blobs; + } + + n_ext = rec_offs_n_extern(offsets); + if (UNIV_UNLIKELY(n_ext)) { + /* Shift and zero fill the array of BLOB pointers. */ + ulint blob_no; + byte* externs; + byte* ext_end; + + blob_no = page_zip_get_n_prev_extern(page_zip, rec, index); + ut_a(blob_no + n_ext <= page_zip->n_blobs); + + externs = page_zip->data + page_zip_get_size(page_zip) + - (page_dir_get_n_heap(page) - PAGE_HEAP_NO_USER_LOW) + * (PAGE_ZIP_DIR_SLOT_SIZE + + DATA_TRX_ID_LEN + DATA_ROLL_PTR_LEN); + + ext_end = externs - page_zip->n_blobs + * BTR_EXTERN_FIELD_REF_SIZE; + externs -= blob_no * BTR_EXTERN_FIELD_REF_SIZE; + + page_zip->n_blobs -= n_ext; + /* Shift and zero fill the array. */ + memmove(ext_end + n_ext * BTR_EXTERN_FIELD_REF_SIZE, ext_end, + (page_zip->n_blobs - blob_no) + * BTR_EXTERN_FIELD_REF_SIZE); + memset(ext_end, 0, n_ext * BTR_EXTERN_FIELD_REF_SIZE); + } + +skip_blobs: + /* The compression algorithm expects info_bits and n_owned + to be 0 for deleted records. */ + rec[-REC_N_NEW_EXTRA_BYTES] = 0; /* info_bits and n_owned */ + + page_zip_clear_rec(page_zip, rec, index, offsets); +} + +/************************************************************************** +Add a slot to the dense page directory. */ +UNIV_INTERN +void +page_zip_dir_add_slot( +/*==================*/ + page_zip_des_t* page_zip, /* in/out: compressed page */ + ulint is_clustered) /* in: nonzero for clustered index, + zero for others */ +{ + ulint n_dense; + byte* dir; + byte* stored; + + ut_ad(page_is_comp(page_zip->data)); + UNIV_MEM_ASSERT_RW(page_zip->data, page_zip_get_size(page_zip)); + + /* Read the old n_dense (n_heap has already been incremented). */ + n_dense = page_dir_get_n_heap(page_zip->data) + - (PAGE_HEAP_NO_USER_LOW + 1); + + dir = page_zip->data + page_zip_get_size(page_zip) + - PAGE_ZIP_DIR_SLOT_SIZE * n_dense; + + if (!page_is_leaf(page_zip->data)) { + ut_ad(!page_zip->n_blobs); + stored = dir - n_dense * REC_NODE_PTR_SIZE; + } else if (UNIV_UNLIKELY(is_clustered)) { + /* Move the BLOB pointer array backwards to make space for the + roll_ptr and trx_id columns and the dense directory slot. */ + byte* externs; + + stored = dir - n_dense + * (DATA_TRX_ID_LEN + DATA_ROLL_PTR_LEN); + externs = stored + - page_zip->n_blobs * BTR_EXTERN_FIELD_REF_SIZE; + ASSERT_ZERO(externs + - (PAGE_ZIP_DIR_SLOT_SIZE + + DATA_TRX_ID_LEN + DATA_ROLL_PTR_LEN), + PAGE_ZIP_DIR_SLOT_SIZE + + DATA_TRX_ID_LEN + DATA_ROLL_PTR_LEN); + memmove(externs - (PAGE_ZIP_DIR_SLOT_SIZE + + DATA_TRX_ID_LEN + DATA_ROLL_PTR_LEN), + externs, stored - externs); + } else { + stored = dir + - page_zip->n_blobs * BTR_EXTERN_FIELD_REF_SIZE; + ASSERT_ZERO(stored - PAGE_ZIP_DIR_SLOT_SIZE, + PAGE_ZIP_DIR_SLOT_SIZE); + } + + /* Move the uncompressed area backwards to make space + for one directory slot. */ + memmove(stored - PAGE_ZIP_DIR_SLOT_SIZE, stored, dir - stored); +} + +/*************************************************************** +Parses a log record of writing to the header of a page. */ +UNIV_INTERN +byte* +page_zip_parse_write_header( +/*========================*/ + /* out: end of log record or NULL */ + byte* ptr, /* in: redo log buffer */ + byte* end_ptr,/* in: redo log buffer end */ + page_t* page, /* in/out: uncompressed page */ + page_zip_des_t* page_zip)/* in/out: compressed page */ +{ + ulint offset; + ulint len; + + ut_ad(ptr && end_ptr); + ut_ad(!page == !page_zip); + + if (UNIV_UNLIKELY(end_ptr < ptr + (1 + 1))) { + + return(NULL); + } + + offset = (ulint) *ptr++; + len = (ulint) *ptr++; + + if (UNIV_UNLIKELY(!len) || UNIV_UNLIKELY(offset + len >= PAGE_DATA)) { +corrupt: + recv_sys->found_corrupt_log = TRUE; + + return(NULL); + } + + if (UNIV_UNLIKELY(end_ptr < ptr + len)) { + + return(NULL); + } + + if (page) { + if (UNIV_UNLIKELY(!page_zip)) { + + goto corrupt; + } +#ifdef UNIV_ZIP_DEBUG + ut_a(page_zip_validate(page_zip, page)); +#endif /* UNIV_ZIP_DEBUG */ + + memcpy(page + offset, ptr, len); + memcpy(page_zip->data + offset, ptr, len); + +#ifdef UNIV_ZIP_DEBUG + ut_a(page_zip_validate(page_zip, page)); +#endif /* UNIV_ZIP_DEBUG */ + } + + return(ptr + len); +} + +/************************************************************************** +Write a log record of writing to the uncompressed header portion of a page. */ +UNIV_INTERN +void +page_zip_write_header_log( +/*======================*/ + const byte* data, /* in: data on the uncompressed page */ + ulint length, /* in: length of the data */ + mtr_t* mtr) /* in: mini-transaction */ +{ + byte* log_ptr = mlog_open(mtr, 11 + 1 + 1); + ulint offset = page_offset(data); + + ut_ad(offset < PAGE_DATA); + ut_ad(offset + length < PAGE_DATA); +#if PAGE_DATA > 255 +# error "PAGE_DATA > 255" +#endif + ut_ad(length < 256); + + /* If no logging is requested, we may return now */ + if (UNIV_UNLIKELY(!log_ptr)) { + + return; + } + + log_ptr = mlog_write_initial_log_record_fast( + (byte*) data, MLOG_ZIP_WRITE_HEADER, log_ptr, mtr); + *log_ptr++ = (byte) offset; + *log_ptr++ = (byte) length; + mlog_close(mtr, log_ptr); + + mlog_catenate_string(mtr, data, length); +} + +/************************************************************************** +Reorganize and compress a page. This is a low-level operation for +compressed pages, to be used when page_zip_compress() fails. +On success, a redo log entry MLOG_ZIP_PAGE_COMPRESS will be written. +The function btr_page_reorganize() should be preferred whenever possible. +IMPORTANT: if page_zip_reorganize() is invoked on a leaf page of a +non-clustered index, the caller must update the insert buffer free +bits in the same mini-transaction in such a way that the modification +will be redo-logged. */ +UNIV_INTERN +ibool +page_zip_reorganize( +/*================*/ + /* out: TRUE on success, FALSE on failure; + page and page_zip will be left intact + on failure. */ + buf_block_t* block, /* in/out: page with compressed page; + on the compressed page, in: size; + out: data, n_blobs, + m_start, m_end, m_nonempty */ + dict_index_t* index, /* in: index of the B-tree node */ + mtr_t* mtr) /* in: mini-transaction */ +{ + page_zip_des_t* page_zip = buf_block_get_page_zip(block); + page_t* page = buf_block_get_frame(block); + buf_block_t* temp_block; + page_t* temp_page; + ulint log_mode; + + ut_ad(mtr_memo_contains(mtr, block, MTR_MEMO_PAGE_X_FIX)); + ut_ad(page_is_comp(page)); + /* Note that page_zip_validate(page_zip, page) may fail here. */ + UNIV_MEM_ASSERT_RW(page, UNIV_PAGE_SIZE); + UNIV_MEM_ASSERT_RW(page_zip->data, page_zip_get_size(page_zip)); + + /* Disable logging */ + log_mode = mtr_set_log_mode(mtr, MTR_LOG_NONE); + + temp_block = buf_block_alloc(0); + temp_page = temp_block->frame; + + btr_search_drop_page_hash_index(block); + + /* Copy the old page to temporary space */ + buf_frame_copy(temp_page, page); + + /* Recreate the page: note that global data on page (possible + segment headers, next page-field, etc.) is preserved intact */ + + page_create(block, mtr, TRUE); + block->check_index_page_at_flush = TRUE; + + /* Copy the records from the temporary space to the recreated page; + do not copy the lock bits yet */ + + page_copy_rec_list_end_no_locks(block, temp_block, + page_get_infimum_rec(temp_page), + index, mtr); + /* Copy max trx id to recreated page */ + page_set_max_trx_id(block, NULL, page_get_max_trx_id(temp_page)); + + /* Restore logging. */ + mtr_set_log_mode(mtr, log_mode); + + if (UNIV_UNLIKELY(!page_zip_compress(page_zip, page, index, mtr))) { + + /* Restore the old page and exit. */ + buf_frame_copy(page, temp_page); + + buf_block_free(temp_block); + return(FALSE); + } + + lock_move_reorganize_page(block, temp_block); + + buf_block_free(temp_block); + return(TRUE); +} + +/************************************************************************** +Copy the records of a page byte for byte. Do not copy the page header +or trailer, except those B-tree header fields that are directly +related to the storage of records. Also copy PAGE_MAX_TRX_ID. +NOTE: The caller must update the lock table and the adaptive hash index. */ +UNIV_INTERN +void +page_zip_copy_recs( +/*===============*/ + page_zip_des_t* page_zip, /* out: copy of src_zip + (n_blobs, m_start, m_end, + m_nonempty, data[0..size-1]) */ + page_t* page, /* out: copy of src */ + const page_zip_des_t* src_zip, /* in: compressed page */ + const page_t* src, /* in: page */ + dict_index_t* index, /* in: index of the B-tree */ + mtr_t* mtr) /* in: mini-transaction */ +{ + ut_ad(mtr_memo_contains_page(mtr, page, MTR_MEMO_PAGE_X_FIX)); + ut_ad(mtr_memo_contains_page(mtr, (page_t*) src, MTR_MEMO_PAGE_X_FIX)); +#ifdef UNIV_ZIP_DEBUG + /* The B-tree operations that call this function may set + FIL_PAGE_PREV or PAGE_LEVEL, causing a temporary min_rec_flag + mismatch. A strict page_zip_validate() will be executed later + during the B-tree operations. */ + ut_a(page_zip_validate_low(src_zip, src, TRUE)); +#endif /* UNIV_ZIP_DEBUG */ + ut_a(page_zip_get_size(page_zip) == page_zip_get_size(src_zip)); + if (UNIV_UNLIKELY(src_zip->n_blobs)) { + ut_a(page_is_leaf(src)); + ut_a(dict_index_is_clust(index)); + } + + UNIV_MEM_ASSERT_W(page, UNIV_PAGE_SIZE); + UNIV_MEM_ASSERT_W(page_zip->data, page_zip_get_size(page_zip)); + UNIV_MEM_ASSERT_RW(src, UNIV_PAGE_SIZE); + UNIV_MEM_ASSERT_RW(src_zip->data, page_zip_get_size(page_zip)); + + /* Copy those B-tree page header fields that are related to + the records stored in the page. Also copy the field + PAGE_MAX_TRX_ID. Skip the rest of the page header and + trailer. On the compressed page, there is no trailer. */ +#if PAGE_MAX_TRX_ID + 8 != PAGE_HEADER_PRIV_END +# error "PAGE_MAX_TRX_ID + 8 != PAGE_HEADER_PRIV_END" +#endif + memcpy(PAGE_HEADER + page, PAGE_HEADER + src, + PAGE_HEADER_PRIV_END); + memcpy(PAGE_DATA + page, PAGE_DATA + src, + UNIV_PAGE_SIZE - PAGE_DATA - FIL_PAGE_DATA_END); + memcpy(PAGE_HEADER + page_zip->data, PAGE_HEADER + src_zip->data, + PAGE_HEADER_PRIV_END); + memcpy(PAGE_DATA + page_zip->data, PAGE_DATA + src_zip->data, + page_zip_get_size(page_zip) - PAGE_DATA); + + /* Copy all fields of src_zip to page_zip, except the pointer + to the compressed data page. */ + { + page_zip_t* data = page_zip->data; + memcpy(page_zip, src_zip, sizeof *page_zip); + page_zip->data = data; + } + ut_ad(page_zip_get_trailer_len(page_zip, + dict_index_is_clust(index), NULL) + + page_zip->m_end < page_zip_get_size(page_zip)); + + if (!page_is_leaf(src) + && UNIV_UNLIKELY(mach_read_from_4(src + FIL_PAGE_PREV) == FIL_NULL) + && UNIV_LIKELY(mach_read_from_4(page + + FIL_PAGE_PREV) != FIL_NULL)) { + /* Clear the REC_INFO_MIN_REC_FLAG of the first user record. */ + ulint offs = rec_get_next_offs(page + PAGE_NEW_INFIMUM, + TRUE); + if (UNIV_LIKELY(offs != PAGE_NEW_SUPREMUM)) { + rec_t* rec = page + offs; + ut_a(rec[-REC_N_NEW_EXTRA_BYTES] + & REC_INFO_MIN_REC_FLAG); + rec[-REC_N_NEW_EXTRA_BYTES] &= ~ REC_INFO_MIN_REC_FLAG; + } + } + +#ifdef UNIV_ZIP_DEBUG + ut_a(page_zip_validate(page_zip, page)); +#endif /* UNIV_ZIP_DEBUG */ + + page_zip_compress_write_log(page_zip, page, index, mtr); +} + +/************************************************************************** +Parses a log record of compressing an index page. */ +UNIV_INTERN +byte* +page_zip_parse_compress( +/*====================*/ + /* out: end of log record or NULL */ + byte* ptr, /* in: buffer */ + byte* end_ptr,/* in: buffer end */ + page_t* page, /* out: uncompressed page */ + page_zip_des_t* page_zip)/* out: compressed page */ +{ + ulint size; + ulint trailer_size; + + ut_ad(ptr && end_ptr); + ut_ad(!page == !page_zip); + + if (UNIV_UNLIKELY(ptr + (2 + 2) > end_ptr)) { + + return(NULL); + } + + size = mach_read_from_2(ptr); + ptr += 2; + trailer_size = mach_read_from_2(ptr); + ptr += 2; + + if (UNIV_UNLIKELY(ptr + 8 + size + trailer_size > end_ptr)) { + + return(NULL); + } + + if (page) { + if (UNIV_UNLIKELY(!page_zip) + || UNIV_UNLIKELY(page_zip_get_size(page_zip) < size)) { +corrupt: + recv_sys->found_corrupt_log = TRUE; + + return(NULL); + } + + memcpy(page_zip->data + FIL_PAGE_PREV, ptr, 4); + memcpy(page_zip->data + FIL_PAGE_NEXT, ptr + 4, 4); + memcpy(page_zip->data + FIL_PAGE_TYPE, ptr + 8, size); + memset(page_zip->data + FIL_PAGE_TYPE + size, 0, + page_zip_get_size(page_zip) - trailer_size + - (FIL_PAGE_TYPE + size)); + memcpy(page_zip->data + page_zip_get_size(page_zip) + - trailer_size, ptr + 8 + size, trailer_size); + + if (UNIV_UNLIKELY(!page_zip_decompress(page_zip, page))) { + + goto corrupt; + } + } + + return(ptr + 8 + size + trailer_size); +} + +/************************************************************************** +Calculate the compressed page checksum. */ +UNIV_INTERN +ulint +page_zip_calc_checksum( +/*===================*/ + /* out: page checksum */ + const void* data, /* in: compressed page */ + ulint size) /* in: size of compressed page */ +{ + /* Exclude FIL_PAGE_SPACE_OR_CHKSUM, FIL_PAGE_LSN, + and FIL_PAGE_FILE_FLUSH_LSN from the checksum. */ + + const Bytef* s = data; + uLong adler; + + ut_ad(size > FIL_PAGE_ARCH_LOG_NO_OR_SPACE_ID); + + adler = adler32(0L, s + FIL_PAGE_OFFSET, + FIL_PAGE_LSN - FIL_PAGE_OFFSET); + adler = adler32(adler, s + FIL_PAGE_TYPE, 2); + adler = adler32(adler, s + FIL_PAGE_ARCH_LOG_NO_OR_SPACE_ID, + size - FIL_PAGE_ARCH_LOG_NO_OR_SPACE_ID); + + return((ulint) adler); +} diff --git a/storage/xtradb/pars/lexyy.c b/storage/xtradb/pars/lexyy.c new file mode 100644 index 00000000000..489752a1900 --- /dev/null +++ b/storage/xtradb/pars/lexyy.c @@ -0,0 +1,2780 @@ +/***************************************************************************** + +Copyright (c) 1995, 2009, Innobase Oy. 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 "univ.i" +#line 2 "lexyy.c" + +#line 4 "lexyy.c" + +#define YY_INT_ALIGNED short int + +/* A lexical scanner generated by flex */ + +#define FLEX_SCANNER +#define YY_FLEX_MAJOR_VERSION 2 +#define YY_FLEX_MINOR_VERSION 5 +#define YY_FLEX_SUBMINOR_VERSION 31 +#if YY_FLEX_SUBMINOR_VERSION > 0 +#define FLEX_BETA +#endif + +/* First, we deal with platform-specific or compiler-specific issues. */ + +/* begin standard C headers. */ +#include <stdio.h> +#include <string.h> +#include <errno.h> +#include <stdlib.h> + +/* end standard C headers. */ + +/* flex integer type definitions */ + +#ifndef FLEXINT_H +#define FLEXINT_H + +/* C99 systems have <inttypes.h>. Non-C99 systems may or may not. */ + +#if defined __STDC_VERSION__ && __STDC_VERSION__ >= 199901L +#include <inttypes.h> +typedef int8_t flex_int8_t; +typedef uint8_t flex_uint8_t; +typedef int16_t flex_int16_t; +typedef uint16_t flex_uint16_t; +typedef int32_t flex_int32_t; +typedef uint32_t flex_uint32_t; +#else +typedef signed char flex_int8_t; +typedef short int flex_int16_t; +typedef int flex_int32_t; +typedef unsigned char flex_uint8_t; +typedef unsigned short int flex_uint16_t; +typedef unsigned int flex_uint32_t; +#endif /* ! C99 */ + +/* Limits of integral types. */ +#ifndef INT8_MIN +#define INT8_MIN (-128) +#endif +#ifndef INT16_MIN +#define INT16_MIN (-32767-1) +#endif +#ifndef INT32_MIN +#define INT32_MIN (-2147483647-1) +#endif +#ifndef INT8_MAX +#define INT8_MAX (127) +#endif +#ifndef INT16_MAX +#define INT16_MAX (32767) +#endif +#ifndef INT32_MAX +#define INT32_MAX (2147483647) +#endif +#ifndef UINT8_MAX +#define UINT8_MAX (255U) +#endif +#ifndef UINT16_MAX +#define UINT16_MAX (65535U) +#endif +#ifndef UINT32_MAX +#define UINT32_MAX (4294967295U) +#endif + +#endif /* ! FLEXINT_H */ + +#ifdef __cplusplus + +/* The "const" storage-class-modifier is valid. */ +#define YY_USE_CONST + +#else /* ! __cplusplus */ + +#if __STDC__ + +#define YY_USE_CONST + +#endif /* __STDC__ */ +#endif /* ! __cplusplus */ + +#ifdef YY_USE_CONST +#define yyconst const +#else +#define yyconst +#endif + +/* Returned upon end-of-file. */ +#define YY_NULL 0 + +/* Promotes a possibly negative, possibly signed char to an unsigned + * integer for use as an array index. If the signed char is negative, + * we want to instead treat it as an 8-bit unsigned char, hence the + * double cast. + */ +#define YY_SC_TO_UI(c) ((unsigned int) (unsigned char) c) + +/* Enter a start condition. This macro really ought to take a parameter, + * but we do it the disgusting crufty way forced on us by the ()-less + * definition of BEGIN. + */ +#define BEGIN (yy_start) = 1 + 2 * + +/* Translate the current start state into a value that can be later handed + * to BEGIN to return to the state. The YYSTATE alias is for lex + * compatibility. + */ +#define YY_START (((yy_start) - 1) / 2) +#define YYSTATE YY_START + +/* Action number for EOF rule of a given start state. */ +#define YY_STATE_EOF(state) (YY_END_OF_BUFFER + state + 1) + +/* Special action meaning "start processing a new file". */ +#define YY_NEW_FILE yyrestart(yyin ) + +#define YY_END_OF_BUFFER_CHAR 0 + +/* Size of default input buffer. */ +#ifndef YY_BUF_SIZE +#define YY_BUF_SIZE 16384 +#endif + +#ifndef YY_TYPEDEF_YY_BUFFER_STATE +#define YY_TYPEDEF_YY_BUFFER_STATE +typedef struct yy_buffer_state *YY_BUFFER_STATE; +#endif + +static int yyleng; + +static FILE *yyin, *yyout; + +#define EOB_ACT_CONTINUE_SCAN 0 +#define EOB_ACT_END_OF_FILE 1 +#define EOB_ACT_LAST_MATCH 2 + + #define YY_LESS_LINENO(n) + +/* Return all but the first "n" matched characters back to the input stream. */ +#define yyless(n) \ + do \ + { \ + /* Undo effects of setting up yytext. */ \ + int yyless_macro_arg = (n); \ + YY_LESS_LINENO(yyless_macro_arg);\ + *yy_cp = (yy_hold_char); \ + YY_RESTORE_YY_MORE_OFFSET \ + (yy_c_buf_p) = yy_cp = yy_bp + yyless_macro_arg - YY_MORE_ADJ; \ + YY_DO_BEFORE_ACTION; /* set up yytext again */ \ + } \ + while ( 0 ) + +#define unput(c) yyunput( c, (yytext_ptr) ) + +/* The following is because we cannot portably get our hands on size_t + * (without autoconf's help, which isn't available because we want + * flex-generated scanners to compile on their own). + */ + +#ifndef YY_TYPEDEF_YY_SIZE_T +#define YY_TYPEDEF_YY_SIZE_T +typedef unsigned int yy_size_t; +#endif + +#ifndef YY_STRUCT_YY_BUFFER_STATE +#define YY_STRUCT_YY_BUFFER_STATE +struct yy_buffer_state + { + FILE *yy_input_file; + + char *yy_ch_buf; /* input buffer */ + char *yy_buf_pos; /* current position in input buffer */ + + /* Size of input buffer in bytes, not including room for EOB + * characters. + */ + yy_size_t yy_buf_size; + + /* Number of characters read into yy_ch_buf, not including EOB + * characters. + */ + int yy_n_chars; + + /* Whether we "own" the buffer - i.e., we know we created it, + * and can realloc() it to grow it, and should free() it to + * delete it. + */ + int yy_is_our_buffer; + + /* Whether this is an "interactive" input source; if so, and + * if we're using stdio for input, then we want to use getc() + * instead of fread(), to make sure we stop fetching input after + * each newline. + */ + int yy_is_interactive; + + /* Whether we're considered to be at the beginning of a line. + * If so, '^' rules will be active on the next match, otherwise + * not. + */ + int yy_at_bol; + + int yy_bs_lineno; /**< The line count. */ + int yy_bs_column; /**< The column count. */ + + /* Whether to try to fill the input buffer when we reach the + * end of it. + */ + int yy_fill_buffer; + + int yy_buffer_status; + +#define YY_BUFFER_NEW 0 +#define YY_BUFFER_NORMAL 1 + /* When an EOF's been seen but there's still some text to process + * then we mark the buffer as YY_EOF_PENDING, to indicate that we + * shouldn't try reading from the input source any more. We might + * still have a bunch of tokens to match, though, because of + * possible backing-up. + * + * When we actually see the EOF, we change the status to "new" + * (via yyrestart()), so that the user can continue scanning by + * just pointing yyin at a new input file. + */ +#define YY_BUFFER_EOF_PENDING 2 + + }; +#endif /* !YY_STRUCT_YY_BUFFER_STATE */ + +/* Stack of input buffers. */ +static size_t yy_buffer_stack_top = 0; /**< index of top of stack. */ +static size_t yy_buffer_stack_max = 0; /**< capacity of stack. */ +static YY_BUFFER_STATE * yy_buffer_stack = 0; /**< Stack as an array. */ + +/* We provide macros for accessing buffer states in case in the + * future we want to put the buffer states in a more general + * "scanner state". + * + * Returns the top of the stack, or NULL. + */ +#define YY_CURRENT_BUFFER ( (yy_buffer_stack) \ + ? (yy_buffer_stack)[(yy_buffer_stack_top)] \ + : NULL) + +/* Same as previous macro, but useful when we know that the buffer stack is not + * NULL or when we need an lvalue. For internal use only. + */ +#define YY_CURRENT_BUFFER_LVALUE (yy_buffer_stack)[(yy_buffer_stack_top)] + +/* yy_hold_char holds the character lost when yytext is formed. */ +static char yy_hold_char; +static int yy_n_chars; /* number of characters read into yy_ch_buf */ +static int yyleng; + +/* Points to current character in buffer. */ +static char *yy_c_buf_p = (char *) 0; +static int yy_init = 1; /* whether we need to initialize */ +static int yy_start = 0; /* start state number */ + +/* Flag which is used to allow yywrap()'s to do buffer switches + * instead of setting up a fresh yyin. A bit of a hack ... + */ +static int yy_did_buffer_switch_on_eof; + +static void yyrestart (FILE *input_file ); +__attribute__((unused)) static void yy_switch_to_buffer (YY_BUFFER_STATE new_buffer ); +static YY_BUFFER_STATE yy_create_buffer (FILE *file,int size ); +static void yy_delete_buffer (YY_BUFFER_STATE b ); +static void yy_flush_buffer (YY_BUFFER_STATE b ); +__attribute__((unused)) static void yypush_buffer_state (YY_BUFFER_STATE new_buffer ); +__attribute__((unused)) static void yypop_buffer_state (void ); + +static void yyensure_buffer_stack (void ); +static void yy_load_buffer_state (void ); +static void yy_init_buffer (YY_BUFFER_STATE b,FILE *file ); + +#define YY_FLUSH_BUFFER yy_flush_buffer(YY_CURRENT_BUFFER ) + +YY_BUFFER_STATE yy_scan_buffer (char *base,yy_size_t size ); +YY_BUFFER_STATE yy_scan_string (yyconst char *yy_str ); +YY_BUFFER_STATE yy_scan_bytes (yyconst char *bytes,int len ); + +static void *yyalloc (yy_size_t ); +static void *yyrealloc (void *,yy_size_t ); +static void yyfree (void * ); + +#define yy_new_buffer yy_create_buffer + +#define yy_set_interactive(is_interactive) \ + { \ + if ( ! YY_CURRENT_BUFFER ){ \ + yyensure_buffer_stack (); \ + YY_CURRENT_BUFFER_LVALUE = \ + yy_create_buffer(yyin,YY_BUF_SIZE ); \ + } \ + YY_CURRENT_BUFFER_LVALUE->yy_is_interactive = is_interactive; \ + } + +#define yy_set_bol(at_bol) \ + { \ + if ( ! YY_CURRENT_BUFFER ){\ + yyensure_buffer_stack (); \ + YY_CURRENT_BUFFER_LVALUE = \ + yy_create_buffer(yyin,YY_BUF_SIZE ); \ + } \ + YY_CURRENT_BUFFER_LVALUE->yy_at_bol = at_bol; \ + } + +#define YY_AT_BOL() (YY_CURRENT_BUFFER_LVALUE->yy_at_bol) + +/* Begin user sect3 */ + +#define yywrap(n) 1 +#define YY_SKIP_YYWRAP + +typedef unsigned char YY_CHAR; + +static FILE *yyin = (FILE *) 0, *yyout = (FILE *) 0; + +typedef int yy_state_type; + +static int yylineno; + +static int yylineno = 1; + +static char *yytext; +#define yytext_ptr yytext + +static yy_state_type yy_get_previous_state (void ); +static yy_state_type yy_try_NUL_trans (yy_state_type current_state ); +static int yy_get_next_buffer (void ); +static void yy_fatal_error (yyconst char msg[] ); + +/* Done after the current pattern has been matched and before the + * corresponding action - sets up yytext. + */ +#define YY_DO_BEFORE_ACTION \ + (yytext_ptr) = yy_bp; \ + yyleng = (size_t) (yy_cp - yy_bp); \ + (yy_hold_char) = *yy_cp; \ + *yy_cp = '\0'; \ + (yy_c_buf_p) = yy_cp; + +#define YY_NUM_RULES 119 +#define YY_END_OF_BUFFER 120 +/* This struct is not used in this scanner, + but its presence is necessary. */ +struct yy_trans_info + { + flex_int32_t yy_verify; + flex_int32_t yy_nxt; + }; +static yyconst flex_int16_t yy_accept[399] = + { 0, + 0, 0, 114, 114, 0, 0, 0, 0, 120, 118, + 117, 117, 8, 118, 109, 5, 98, 104, 107, 105, + 102, 106, 118, 108, 1, 118, 103, 101, 99, 100, + 112, 92, 92, 92, 92, 92, 92, 92, 92, 92, + 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, + 110, 111, 114, 115, 6, 7, 9, 10, 117, 4, + 93, 113, 2, 1, 3, 94, 95, 97, 96, 92, + 92, 92, 92, 92, 92, 44, 92, 92, 92, 92, + 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, + 92, 92, 28, 17, 25, 92, 92, 92, 92, 92, + + 54, 61, 92, 14, 92, 92, 92, 92, 92, 92, + 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, + 92, 92, 114, 115, 115, 116, 6, 7, 9, 10, + 2, 13, 45, 92, 92, 92, 92, 92, 92, 92, + 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, + 92, 27, 92, 92, 92, 41, 92, 92, 92, 92, + 21, 92, 92, 92, 92, 15, 92, 92, 92, 18, + 92, 92, 92, 92, 92, 80, 92, 92, 92, 51, + 92, 12, 92, 36, 92, 92, 92, 92, 92, 92, + 92, 92, 92, 92, 92, 92, 92, 92, 20, 24, + + 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, + 46, 92, 92, 30, 92, 87, 92, 92, 39, 92, + 92, 92, 92, 92, 48, 92, 89, 32, 91, 92, + 11, 64, 92, 92, 92, 42, 92, 92, 92, 92, + 92, 92, 92, 92, 92, 92, 29, 92, 92, 92, + 92, 92, 92, 92, 92, 92, 85, 92, 26, 92, + 66, 92, 92, 92, 37, 92, 92, 92, 92, 92, + 92, 92, 31, 65, 23, 92, 57, 92, 75, 92, + 92, 92, 43, 92, 92, 92, 92, 92, 92, 92, + 92, 90, 92, 92, 56, 92, 92, 92, 92, 92, + + 92, 92, 40, 33, 79, 19, 92, 83, 74, 55, + 92, 63, 92, 52, 92, 92, 92, 47, 92, 76, + 92, 78, 92, 92, 34, 92, 92, 92, 35, 72, + 92, 92, 92, 92, 58, 92, 50, 49, 92, 92, + 53, 62, 92, 92, 92, 22, 92, 92, 73, 81, + 92, 92, 77, 92, 68, 92, 92, 92, 92, 38, + 92, 88, 67, 92, 84, 92, 92, 92, 86, 92, + 59, 92, 16, 92, 70, 69, 92, 92, 82, 92, + 92, 92, 92, 92, 92, 92, 92, 92, 92, 71, + 92, 92, 92, 92, 92, 92, 60, 0 + + } ; + +static yyconst flex_int32_t yy_ec[256] = + { 0, + 1, 1, 1, 1, 1, 1, 1, 1, 2, 3, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 2, 1, 4, 1, 5, 6, 1, 7, 8, + 9, 10, 11, 12, 13, 14, 15, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 17, 18, 19, + 20, 21, 22, 1, 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, 32, + 1, 1, 1, 1, 48, 1, 32, 32, 32, 32, + + 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, + 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, + 32, 32, 49, 1, 50, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1 + } ; + +static yyconst flex_int32_t yy_meta[51] = + { 0, + 1, 1, 1, 2, 1, 1, 3, 1, 1, 4, + 1, 1, 1, 1, 1, 5, 1, 1, 1, 6, + 1, 1, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 1, 1 + } ; + +static yyconst flex_int16_t yy_base[409] = + { 0, + 0, 0, 437, 436, 438, 437, 439, 438, 441, 448, + 49, 51, 448, 0, 448, 448, 448, 448, 448, 448, + 448, 448, 426, 429, 41, 418, 448, 38, 448, 417, + 448, 20, 33, 32, 46, 40, 44, 0, 54, 52, + 399, 48, 60, 395, 65, 67, 81, 27, 411, 75, + 448, 448, 0, 98, 0, 426, 0, 428, 113, 0, + 448, 448, 415, 54, 410, 448, 448, 448, 448, 0, + 403, 68, 399, 391, 389, 0, 402, 80, 84, 397, + 383, 96, 381, 394, 379, 393, 387, 375, 379, 375, + 377, 377, 0, 98, 0, 376, 97, 385, 368, 375, + + 0, 0, 381, 381, 364, 94, 103, 379, 98, 65, + 381, 369, 109, 361, 377, 373, 351, 97, 372, 363, + 115, 356, 0, 137, 138, 448, 0, 388, 0, 390, + 377, 0, 0, 365, 360, 367, 365, 348, 346, 345, + 350, 359, 347, 359, 95, 347, 353, 354, 336, 336, + 123, 0, 334, 350, 351, 0, 338, 347, 344, 122, + 124, 341, 336, 330, 340, 338, 331, 328, 336, 0, + 326, 336, 334, 325, 315, 309, 322, 307, 327, 0, + 313, 0, 311, 0, 325, 316, 313, 131, 309, 316, + 323, 302, 304, 309, 309, 301, 304, 299, 0, 0, + + 311, 295, 305, 312, 292, 291, 305, 294, 307, 287, + 0, 297, 279, 0, 298, 0, 295, 282, 0, 281, + 276, 281, 280, 290, 0, 276, 0, 0, 0, 280, + 0, 0, 276, 273, 287, 0, 272, 272, 270, 286, + 271, 283, 280, 264, 282, 277, 0, 272, 272, 258, + 257, 270, 256, 270, 269, 268, 0, 252, 0, 246, + 0, 265, 249, 248, 0, 262, 252, 247, 246, 258, + 248, 247, 0, 0, 0, 251, 0, 239, 0, 253, + 249, 235, 0, 249, 250, 233, 238, 231, 249, 231, + 228, 0, 229, 226, 0, 231, 243, 230, 237, 227, + + 235, 220, 0, 0, 0, 212, 219, 0, 0, 0, + 216, 0, 230, 0, 231, 218, 217, 0, 213, 0, + 216, 0, 208, 210, 0, 209, 223, 216, 0, 0, + 219, 222, 204, 219, 0, 215, 0, 0, 199, 213, + 0, 0, 197, 196, 201, 0, 210, 195, 0, 0, + 201, 197, 0, 192, 0, 204, 204, 192, 202, 0, + 179, 0, 0, 199, 0, 183, 177, 183, 0, 174, + 0, 193, 0, 192, 0, 0, 183, 187, 0, 174, + 174, 180, 166, 189, 181, 180, 166, 151, 118, 0, + 130, 136, 127, 123, 119, 111, 0, 448, 167, 173, + + 179, 152, 181, 124, 187, 193, 199, 205 + } ; + +static yyconst flex_int16_t yy_def[409] = + { 0, + 398, 1, 399, 399, 400, 400, 401, 401, 398, 398, + 398, 398, 398, 402, 398, 398, 398, 398, 398, 398, + 398, 398, 398, 398, 398, 403, 398, 398, 398, 398, + 398, 404, 404, 404, 404, 404, 404, 404, 404, 404, + 404, 404, 404, 404, 404, 404, 404, 404, 404, 404, + 398, 398, 405, 406, 407, 398, 408, 398, 398, 402, + 398, 398, 398, 398, 403, 398, 398, 398, 398, 404, + 404, 404, 404, 404, 404, 404, 404, 404, 404, 404, + 404, 404, 404, 404, 404, 404, 404, 404, 404, 404, + 404, 404, 404, 404, 404, 404, 404, 404, 404, 404, + + 404, 404, 404, 404, 404, 404, 404, 404, 404, 404, + 404, 404, 404, 404, 404, 404, 404, 404, 404, 404, + 404, 404, 405, 406, 406, 398, 407, 398, 408, 398, + 398, 404, 404, 404, 404, 404, 404, 404, 404, 404, + 404, 404, 404, 404, 404, 404, 404, 404, 404, 404, + 404, 404, 404, 404, 404, 404, 404, 404, 404, 404, + 404, 404, 404, 404, 404, 404, 404, 404, 404, 404, + 404, 404, 404, 404, 404, 404, 404, 404, 404, 404, + 404, 404, 404, 404, 404, 404, 404, 404, 404, 404, + 404, 404, 404, 404, 404, 404, 404, 404, 404, 404, + + 404, 404, 404, 404, 404, 404, 404, 404, 404, 404, + 404, 404, 404, 404, 404, 404, 404, 404, 404, 404, + 404, 404, 404, 404, 404, 404, 404, 404, 404, 404, + 404, 404, 404, 404, 404, 404, 404, 404, 404, 404, + 404, 404, 404, 404, 404, 404, 404, 404, 404, 404, + 404, 404, 404, 404, 404, 404, 404, 404, 404, 404, + 404, 404, 404, 404, 404, 404, 404, 404, 404, 404, + 404, 404, 404, 404, 404, 404, 404, 404, 404, 404, + 404, 404, 404, 404, 404, 404, 404, 404, 404, 404, + 404, 404, 404, 404, 404, 404, 404, 404, 404, 404, + + 404, 404, 404, 404, 404, 404, 404, 404, 404, 404, + 404, 404, 404, 404, 404, 404, 404, 404, 404, 404, + 404, 404, 404, 404, 404, 404, 404, 404, 404, 404, + 404, 404, 404, 404, 404, 404, 404, 404, 404, 404, + 404, 404, 404, 404, 404, 404, 404, 404, 404, 404, + 404, 404, 404, 404, 404, 404, 404, 404, 404, 404, + 404, 404, 404, 404, 404, 404, 404, 404, 404, 404, + 404, 404, 404, 404, 404, 404, 404, 404, 404, 404, + 404, 404, 404, 404, 404, 404, 404, 404, 404, 404, + 404, 404, 404, 404, 404, 404, 404, 0, 398, 398, + + 398, 398, 398, 398, 398, 398, 398, 398 + } ; + +static yyconst flex_int16_t yy_nxt[499] = + { 0, + 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, 38, + 39, 38, 38, 40, 41, 42, 43, 44, 38, 45, + 46, 47, 48, 49, 50, 38, 38, 38, 51, 52, + 59, 59, 59, 59, 63, 71, 64, 67, 68, 73, + 72, 77, 118, 74, 119, 78, 75, 63, 79, 64, + 88, 80, 82, 85, 81, 86, 83, 89, 96, 76, + 90, 93, 84, 91, 99, 87, 92, 101, 97, 94, + 100, 107, 133, 110, 95, 102, 111, 103, 179, 104, + + 108, 109, 105, 115, 121, 112, 180, 125, 134, 113, + 116, 122, 126, 114, 59, 59, 139, 117, 141, 142, + 146, 163, 140, 159, 171, 173, 143, 189, 70, 147, + 172, 177, 183, 164, 207, 208, 148, 190, 160, 161, + 174, 193, 178, 184, 175, 194, 398, 125, 222, 214, + 224, 398, 126, 215, 248, 249, 60, 397, 396, 395, + 225, 394, 393, 223, 392, 391, 250, 53, 53, 53, + 53, 53, 53, 55, 55, 55, 55, 55, 55, 57, + 57, 57, 57, 57, 57, 65, 65, 123, 123, 123, + 390, 123, 123, 124, 124, 124, 124, 124, 124, 127, + + 127, 389, 127, 127, 127, 129, 388, 129, 129, 129, + 129, 387, 386, 385, 384, 383, 382, 381, 380, 379, + 378, 377, 376, 375, 374, 373, 372, 371, 370, 369, + 368, 367, 366, 365, 364, 363, 362, 361, 360, 359, + 358, 357, 356, 355, 354, 353, 352, 351, 350, 349, + 348, 347, 346, 345, 344, 343, 342, 341, 340, 339, + 338, 337, 336, 335, 334, 333, 332, 331, 330, 329, + 328, 327, 326, 325, 324, 323, 322, 321, 320, 319, + 318, 317, 316, 315, 314, 313, 312, 311, 310, 309, + 308, 307, 306, 305, 304, 303, 302, 301, 300, 299, + + 298, 297, 296, 295, 294, 293, 292, 291, 290, 289, + 288, 287, 286, 285, 284, 283, 282, 281, 280, 279, + 278, 277, 276, 275, 274, 273, 272, 271, 270, 269, + 268, 267, 266, 265, 264, 263, 262, 261, 260, 259, + 258, 257, 256, 255, 254, 253, 252, 251, 247, 246, + 245, 244, 243, 242, 241, 240, 239, 238, 237, 236, + 235, 234, 233, 232, 231, 230, 229, 228, 227, 226, + 221, 220, 219, 218, 217, 216, 213, 212, 211, 210, + 209, 206, 205, 204, 203, 202, 201, 200, 199, 198, + 197, 196, 131, 130, 128, 195, 192, 191, 188, 187, + + 186, 185, 182, 181, 176, 170, 169, 168, 167, 166, + 165, 162, 158, 157, 156, 155, 154, 153, 152, 151, + 150, 149, 145, 144, 138, 137, 136, 135, 132, 398, + 131, 130, 128, 120, 106, 98, 69, 66, 62, 61, + 398, 58, 58, 56, 56, 54, 54, 9, 398, 398, + 398, 398, 398, 398, 398, 398, 398, 398, 398, 398, + 398, 398, 398, 398, 398, 398, 398, 398, 398, 398, + 398, 398, 398, 398, 398, 398, 398, 398, 398, 398, + 398, 398, 398, 398, 398, 398, 398, 398, 398, 398, + 398, 398, 398, 398, 398, 398, 398, 398 + + } ; + +static yyconst flex_int16_t yy_chk[499] = + { 0, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 11, 11, 12, 12, 25, 32, 25, 28, 28, 33, + 32, 34, 48, 33, 48, 34, 33, 64, 34, 64, + 37, 34, 35, 36, 34, 36, 35, 37, 40, 33, + 37, 39, 35, 37, 42, 36, 37, 43, 40, 39, + 42, 45, 72, 46, 39, 43, 46, 43, 110, 43, + + 45, 45, 43, 47, 50, 46, 110, 54, 72, 46, + 47, 50, 54, 46, 59, 59, 78, 47, 79, 79, + 82, 97, 78, 94, 106, 107, 79, 118, 404, 82, + 106, 109, 113, 97, 145, 145, 82, 118, 94, 94, + 107, 121, 109, 113, 107, 121, 124, 125, 160, 151, + 161, 124, 125, 151, 188, 188, 402, 396, 395, 394, + 161, 393, 392, 160, 391, 389, 188, 399, 399, 399, + 399, 399, 399, 400, 400, 400, 400, 400, 400, 401, + 401, 401, 401, 401, 401, 403, 403, 405, 405, 405, + 388, 405, 405, 406, 406, 406, 406, 406, 406, 407, + + 407, 387, 407, 407, 407, 408, 386, 408, 408, 408, + 408, 385, 384, 383, 382, 381, 380, 378, 377, 374, + 372, 370, 368, 367, 366, 364, 361, 359, 358, 357, + 356, 354, 352, 351, 348, 347, 345, 344, 343, 340, + 339, 336, 334, 333, 332, 331, 328, 327, 326, 324, + 323, 321, 319, 317, 316, 315, 313, 311, 307, 306, + 302, 301, 300, 299, 298, 297, 296, 294, 293, 291, + 290, 289, 288, 287, 286, 285, 284, 282, 281, 280, + 278, 276, 272, 271, 270, 269, 268, 267, 266, 264, + 263, 262, 260, 258, 256, 255, 254, 253, 252, 251, + + 250, 249, 248, 246, 245, 244, 243, 242, 241, 240, + 239, 238, 237, 235, 234, 233, 230, 226, 224, 223, + 222, 221, 220, 218, 217, 215, 213, 212, 210, 209, + 208, 207, 206, 205, 204, 203, 202, 201, 198, 197, + 196, 195, 194, 193, 192, 191, 190, 189, 187, 186, + 185, 183, 181, 179, 178, 177, 176, 175, 174, 173, + 172, 171, 169, 168, 167, 166, 165, 164, 163, 162, + 159, 158, 157, 155, 154, 153, 150, 149, 148, 147, + 146, 144, 143, 142, 141, 140, 139, 138, 137, 136, + 135, 134, 131, 130, 128, 122, 120, 119, 117, 116, + + 115, 114, 112, 111, 108, 105, 104, 103, 100, 99, + 98, 96, 92, 91, 90, 89, 88, 87, 86, 85, + 84, 83, 81, 80, 77, 75, 74, 73, 71, 65, + 63, 58, 56, 49, 44, 41, 30, 26, 24, 23, + 9, 8, 7, 6, 5, 4, 3, 398, 398, 398, + 398, 398, 398, 398, 398, 398, 398, 398, 398, 398, + 398, 398, 398, 398, 398, 398, 398, 398, 398, 398, + 398, 398, 398, 398, 398, 398, 398, 398, 398, 398, + 398, 398, 398, 398, 398, 398, 398, 398, 398, 398, + 398, 398, 398, 398, 398, 398, 398, 398 + + } ; + +static yy_state_type yy_last_accepting_state; +static char *yy_last_accepting_cpos; + +static int yy_flex_debug; +static int yy_flex_debug = 0; + +/* The intent behind this definition is that it'll catch + * any uses of REJECT which flex missed. + */ +#define REJECT reject_used_but_not_detected +#define yymore() yymore_used_but_not_detected +#define YY_MORE_ADJ 0 +#define YY_RESTORE_YY_MORE_OFFSET +static char *yytext; +#line 1 "pars0lex.l" +/****************************************************** +SQL parser lexical analyzer: input file for the GNU Flex lexer generator + +(c) 1997 Innobase Oy + +Created 12/14/1997 Heikki Tuuri +Published under the GPL version 2 + +The InnoDB parser is frozen because MySQL takes care of SQL parsing. +Therefore we normally keep the InnoDB parser C files as they are, and do +not automatically generate them from pars0grm.y and pars0lex.l. + +How to make the InnoDB parser and lexer C files: + +1. Run ./make_flex.sh to generate lexer files. + +2. Run ./make_bison.sh to generate parser files. + +These instructions seem to work at least with bison-1.875d and flex-2.5.31 on +Linux. +*******************************************************/ +#define YY_NO_INPUT 1 +#define YY_NO_UNISTD_H 1 +#line 38 "pars0lex.l" +#define YYSTYPE que_node_t* + +#include "univ.i" +#include "pars0pars.h" +#include "pars0grm.h" +#include "pars0sym.h" +#include "mem0mem.h" +#include "os0proc.h" + +#define malloc(A) ut_malloc(A) +#define free(A) ut_free(A) +#define realloc(P, A) ut_realloc(P, A) +#define exit(A) ut_error + +#define YY_INPUT(buf, result, max_size) pars_get_lex_chars(buf, &result, max_size) + +/* String buffer for removing quotes */ +static ulint stringbuf_len_alloc = 0; /* Allocated length */ +static ulint stringbuf_len = 0; /* Current length */ +static char* stringbuf; /* Start of buffer */ +/* Appends a string to the buffer. */ +static +void +string_append( +/*==========*/ + const char* str, /* in: string to be appended */ + ulint len) /* in: length of the string */ +{ + if (stringbuf == NULL) { + stringbuf = malloc(1); + stringbuf_len_alloc = 1; + } + + if (stringbuf_len + len > stringbuf_len_alloc) { + while (stringbuf_len + len > stringbuf_len_alloc) { + stringbuf_len_alloc <<= 1; + } + stringbuf = realloc(stringbuf, stringbuf_len_alloc); + } + + memcpy(stringbuf + stringbuf_len, str, len); + stringbuf_len += len; +} + + + + +#line 759 "lexyy.c" + +#define INITIAL 0 +#define comment 1 +#define quoted 2 +#define id 3 + +#ifndef YY_NO_UNISTD_H +/* Special case for "unistd.h", since it is non-ANSI. We include it way + * down here because we want the user's section 1 to have been scanned first. + * The user has a chance to override it with an option. + */ +#include <unistd.h> +#endif + +#ifndef YY_EXTRA_TYPE +#define YY_EXTRA_TYPE void * +#endif + +/* Macros after this point can all be overridden by user definitions in + * section 1. + */ + +#ifndef YY_SKIP_YYWRAP +#ifdef __cplusplus +extern "C" int yywrap (void ); +#else +extern int yywrap (void ); +#endif +#endif + +#ifndef yytext_ptr +static void yy_flex_strncpy (char *,yyconst char *,int ); +#endif + +#ifdef YY_NEED_STRLEN +static int yy_flex_strlen (yyconst char * ); +#endif + +#ifndef YY_NO_INPUT + +#ifdef __cplusplus +static int yyinput (void ); +#else +static int input (void ); +#endif + +#endif + +/* Amount of stuff to slurp up with each read. */ +#ifndef YY_READ_BUF_SIZE +#define YY_READ_BUF_SIZE 8192 +#endif + +/* Copy whatever the last rule matched to the standard output. */ +#ifndef ECHO +/* This used to be an fputs(), but since the string might contain NUL's, + * we now use fwrite(). + */ +#define ECHO (void) fwrite( yytext, yyleng, 1, yyout ) +#endif + +/* Gets input and stuffs it into "buf". number of characters read, or YY_NULL, + * is returned in "result". + */ +#ifndef YY_INPUT +#define YY_INPUT(buf,result,max_size) \ + if ( YY_CURRENT_BUFFER_LVALUE->yy_is_interactive ) \ + { \ + int c = '*'; \ + size_t n; \ + for ( n = 0; n < max_size && \ + (c = getc( yyin )) != EOF && c != '\n'; ++n ) \ + buf[n] = (char) c; \ + if ( c == '\n' ) \ + buf[n++] = (char) c; \ + if ( c == EOF && ferror( yyin ) ) \ + YY_FATAL_ERROR( "input in flex scanner failed" ); \ + result = n; \ + } \ + else \ + { \ + errno=0; \ + while ( (result = fread(buf, 1, max_size, yyin))==0 && ferror(yyin)) \ + { \ + if( errno != EINTR) \ + { \ + YY_FATAL_ERROR( "input in flex scanner failed" ); \ + break; \ + } \ + errno=0; \ + clearerr(yyin); \ + } \ + }\ +\ + +#endif + +/* No semi-colon after return; correct usage is to write "yyterminate();" - + * we don't want an extra ';' after the "return" because that will cause + * some compilers to complain about unreachable statements. + */ +#ifndef yyterminate +#define yyterminate() return YY_NULL +#endif + +/* Number of entries by which start-condition stack grows. */ +#ifndef YY_START_STACK_INCR +#define YY_START_STACK_INCR 25 +#endif + +/* Report a fatal error. */ +#ifndef YY_FATAL_ERROR +#define YY_FATAL_ERROR(msg) yy_fatal_error( msg ) +#endif + +/* end tables serialization structures and prototypes */ + +/* Default declaration of generated scanner - a define so the user can + * easily add parameters. + */ +#ifndef YY_DECL +#define YY_DECL_IS_OURS 1 + +UNIV_INTERN int yylex (void); + +#define YY_DECL UNIV_INTERN int yylex (void) +#endif /* !YY_DECL */ + +/* Code executed at the beginning of each rule, after yytext and yyleng + * have been set up. + */ +#ifndef YY_USER_ACTION +#define YY_USER_ACTION +#endif + +/* Code executed at the end of each rule. */ +#ifndef YY_BREAK +#define YY_BREAK break; +#endif + +#define YY_RULE_SETUP \ + YY_USER_ACTION + +/** The main scanner function which does all the work. + */ +YY_DECL +{ + register yy_state_type yy_current_state; + register char *yy_cp, *yy_bp; + register int yy_act; + +#line 92 "pars0lex.l" + + +#line 914 "lexyy.c" + + if ( (yy_init) ) + { + (yy_init) = 0; + +#ifdef YY_USER_INIT + YY_USER_INIT; +#endif + + if ( ! (yy_start) ) + (yy_start) = 1; /* first start state */ + + if ( ! yyin ) + yyin = stdin; + + if ( ! yyout ) + yyout = stdout; + + if ( ! YY_CURRENT_BUFFER ) { + yyensure_buffer_stack (); + YY_CURRENT_BUFFER_LVALUE = + yy_create_buffer(yyin,YY_BUF_SIZE ); + } + + yy_load_buffer_state( ); + } + + while ( 1 ) /* loops until end-of-file is reached */ + { + yy_cp = (yy_c_buf_p); + + /* Support of yytext. */ + *yy_cp = (yy_hold_char); + + /* yy_bp points to the position in yy_ch_buf of the start of + * the current run. + */ + yy_bp = yy_cp; + + yy_current_state = (yy_start); +yy_match: + do + { + register YY_CHAR yy_c = yy_ec[YY_SC_TO_UI(*yy_cp)]; + if ( yy_accept[yy_current_state] ) + { + (yy_last_accepting_state) = yy_current_state; + (yy_last_accepting_cpos) = yy_cp; + } + while ( yy_chk[yy_base[yy_current_state] + yy_c] != yy_current_state ) + { + yy_current_state = (int) yy_def[yy_current_state]; + if ( yy_current_state >= 399 ) + yy_c = yy_meta[(unsigned int) yy_c]; + } + yy_current_state = yy_nxt[yy_base[yy_current_state] + (unsigned int) yy_c]; + ++yy_cp; + } + while ( yy_current_state != 398 ); + yy_cp = (yy_last_accepting_cpos); + yy_current_state = (yy_last_accepting_state); + +yy_find_action: + yy_act = yy_accept[yy_current_state]; + + YY_DO_BEFORE_ACTION; + +do_action: /* This label is used only to access EOF actions. */ + + switch ( yy_act ) + { /* beginning of action switch */ + case 0: /* must back up */ + /* undo the effects of YY_DO_BEFORE_ACTION */ + *yy_cp = (yy_hold_char); + yy_cp = (yy_last_accepting_cpos); + yy_current_state = (yy_last_accepting_state); + goto yy_find_action; + +case 1: +YY_RULE_SETUP +#line 94 "pars0lex.l" +{ + yylval = sym_tab_add_int_lit(pars_sym_tab_global, + atoi(yytext)); + return(PARS_INT_LIT); +} + YY_BREAK +case 2: +YY_RULE_SETUP +#line 100 "pars0lex.l" +{ + ut_error; /* not implemented */ + + return(PARS_FLOAT_LIT); +} + YY_BREAK +case 3: +YY_RULE_SETUP +#line 106 "pars0lex.l" +{ + ulint type; + + yylval = sym_tab_add_bound_lit(pars_sym_tab_global, + yytext + 1, &type); + + return((int) type); +} + YY_BREAK +case 4: +YY_RULE_SETUP +#line 115 "pars0lex.l" +{ + yylval = sym_tab_add_bound_id(pars_sym_tab_global, + yytext + 1); + + return(PARS_ID_TOKEN); +} + YY_BREAK +case 5: +YY_RULE_SETUP +#line 122 "pars0lex.l" +{ +/* Quoted character string literals are handled in an explicit +start state 'quoted'. This state is entered and the buffer for +the scanned string is emptied upon encountering a starting quote. + +In the state 'quoted', only two actions are possible (defined below). */ + BEGIN(quoted); + stringbuf_len = 0; +} + YY_BREAK +case 6: +/* rule 6 can match eol */ +YY_RULE_SETUP +#line 131 "pars0lex.l" +{ + /* Got a sequence of characters other than "'": + append to string buffer */ + string_append(yytext, yyleng); +} + YY_BREAK +case 7: +YY_RULE_SETUP +#line 136 "pars0lex.l" +{ + /* Got a sequence of "'" characters: + append half of them to string buffer, + as "''" represents a single "'". + We apply truncating division, + so that "'''" will result in "'". */ + + string_append(yytext, yyleng / 2); + + /* If we got an odd number of quotes, then the + last quote we got is the terminating quote. + At the end of the string, we return to the + initial start state and report the scanned + string literal. */ + + if (yyleng % 2) { + BEGIN(INITIAL); + yylval = sym_tab_add_str_lit( + pars_sym_tab_global, + (byte*) stringbuf, stringbuf_len); + return(PARS_STR_LIT); + } +} + YY_BREAK +case 8: +YY_RULE_SETUP +#line 160 "pars0lex.l" +{ +/* Quoted identifiers are handled in an explicit start state 'id'. +This state is entered and the buffer for the scanned string is emptied +upon encountering a starting quote. + +In the state 'id', only two actions are possible (defined below). */ + BEGIN(id); + stringbuf_len = 0; +} + YY_BREAK +case 9: +/* rule 9 can match eol */ +YY_RULE_SETUP +#line 169 "pars0lex.l" +{ + /* Got a sequence of characters other than '"': + append to string buffer */ + string_append(yytext, yyleng); +} + YY_BREAK +case 10: +YY_RULE_SETUP +#line 174 "pars0lex.l" +{ + /* Got a sequence of '"' characters: + append half of them to string buffer, + as '""' represents a single '"'. + We apply truncating division, + so that '"""' will result in '"'. */ + + string_append(yytext, yyleng / 2); + + /* If we got an odd number of quotes, then the + last quote we got is the terminating quote. + At the end of the string, we return to the + initial start state and report the scanned + identifier. */ + + if (yyleng % 2) { + BEGIN(INITIAL); + yylval = sym_tab_add_id( + pars_sym_tab_global, + (byte*) stringbuf, stringbuf_len); + + return(PARS_ID_TOKEN); + } +} + YY_BREAK +case 11: +YY_RULE_SETUP +#line 199 "pars0lex.l" +{ + yylval = sym_tab_add_null_lit(pars_sym_tab_global); + + return(PARS_NULL_LIT); +} + YY_BREAK +case 12: +YY_RULE_SETUP +#line 205 "pars0lex.l" +{ + /* Implicit cursor name */ + yylval = sym_tab_add_str_lit(pars_sym_tab_global, + (byte*) yytext, yyleng); + return(PARS_SQL_TOKEN); +} + YY_BREAK +case 13: +YY_RULE_SETUP +#line 212 "pars0lex.l" +{ + return(PARS_AND_TOKEN); +} + YY_BREAK +case 14: +YY_RULE_SETUP +#line 216 "pars0lex.l" +{ + return(PARS_OR_TOKEN); +} + YY_BREAK +case 15: +YY_RULE_SETUP +#line 220 "pars0lex.l" +{ + return(PARS_NOT_TOKEN); +} + YY_BREAK +case 16: +YY_RULE_SETUP +#line 224 "pars0lex.l" +{ + return(PARS_PROCEDURE_TOKEN); +} + YY_BREAK +case 17: +YY_RULE_SETUP +#line 228 "pars0lex.l" +{ + return(PARS_IN_TOKEN); +} + YY_BREAK +case 18: +YY_RULE_SETUP +#line 232 "pars0lex.l" +{ + return(PARS_OUT_TOKEN); +} + YY_BREAK +case 19: +YY_RULE_SETUP +#line 236 "pars0lex.l" +{ + return(PARS_BINARY_TOKEN); +} + YY_BREAK +case 20: +YY_RULE_SETUP +#line 240 "pars0lex.l" +{ + return(PARS_BLOB_TOKEN); +} + YY_BREAK +case 21: +YY_RULE_SETUP +#line 244 "pars0lex.l" +{ + return(PARS_INT_TOKEN); +} + YY_BREAK +case 22: +YY_RULE_SETUP +#line 248 "pars0lex.l" +{ + return(PARS_INT_TOKEN); +} + YY_BREAK +case 23: +YY_RULE_SETUP +#line 252 "pars0lex.l" +{ + return(PARS_FLOAT_TOKEN); +} + YY_BREAK +case 24: +YY_RULE_SETUP +#line 256 "pars0lex.l" +{ + return(PARS_CHAR_TOKEN); +} + YY_BREAK +case 25: +YY_RULE_SETUP +#line 260 "pars0lex.l" +{ + return(PARS_IS_TOKEN); +} + YY_BREAK +case 26: +YY_RULE_SETUP +#line 264 "pars0lex.l" +{ + return(PARS_BEGIN_TOKEN); +} + YY_BREAK +case 27: +YY_RULE_SETUP +#line 268 "pars0lex.l" +{ + return(PARS_END_TOKEN); +} + YY_BREAK +case 28: +YY_RULE_SETUP +#line 272 "pars0lex.l" +{ + return(PARS_IF_TOKEN); +} + YY_BREAK +case 29: +YY_RULE_SETUP +#line 276 "pars0lex.l" +{ + return(PARS_THEN_TOKEN); +} + YY_BREAK +case 30: +YY_RULE_SETUP +#line 280 "pars0lex.l" +{ + return(PARS_ELSE_TOKEN); +} + YY_BREAK +case 31: +YY_RULE_SETUP +#line 284 "pars0lex.l" +{ + return(PARS_ELSIF_TOKEN); +} + YY_BREAK +case 32: +YY_RULE_SETUP +#line 288 "pars0lex.l" +{ + return(PARS_LOOP_TOKEN); +} + YY_BREAK +case 33: +YY_RULE_SETUP +#line 292 "pars0lex.l" +{ + return(PARS_WHILE_TOKEN); +} + YY_BREAK +case 34: +YY_RULE_SETUP +#line 296 "pars0lex.l" +{ + return(PARS_RETURN_TOKEN); +} + YY_BREAK +case 35: +YY_RULE_SETUP +#line 300 "pars0lex.l" +{ + return(PARS_SELECT_TOKEN); +} + YY_BREAK +case 36: +YY_RULE_SETUP +#line 304 "pars0lex.l" +{ + return(PARS_SUM_TOKEN); +} + YY_BREAK +case 37: +YY_RULE_SETUP +#line 308 "pars0lex.l" +{ + return(PARS_COUNT_TOKEN); +} + YY_BREAK +case 38: +YY_RULE_SETUP +#line 312 "pars0lex.l" +{ + return(PARS_DISTINCT_TOKEN); +} + YY_BREAK +case 39: +YY_RULE_SETUP +#line 316 "pars0lex.l" +{ + return(PARS_FROM_TOKEN); +} + YY_BREAK +case 40: +YY_RULE_SETUP +#line 320 "pars0lex.l" +{ + return(PARS_WHERE_TOKEN); +} + YY_BREAK +case 41: +YY_RULE_SETUP +#line 324 "pars0lex.l" +{ + return(PARS_FOR_TOKEN); +} + YY_BREAK +case 42: +YY_RULE_SETUP +#line 328 "pars0lex.l" +{ + return(PARS_READ_TOKEN); +} + YY_BREAK +case 43: +YY_RULE_SETUP +#line 332 "pars0lex.l" +{ + return(PARS_ORDER_TOKEN); +} + YY_BREAK +case 44: +YY_RULE_SETUP +#line 336 "pars0lex.l" +{ + return(PARS_BY_TOKEN); +} + YY_BREAK +case 45: +YY_RULE_SETUP +#line 340 "pars0lex.l" +{ + return(PARS_ASC_TOKEN); +} + YY_BREAK +case 46: +YY_RULE_SETUP +#line 344 "pars0lex.l" +{ + return(PARS_DESC_TOKEN); +} + YY_BREAK +case 47: +YY_RULE_SETUP +#line 348 "pars0lex.l" +{ + return(PARS_INSERT_TOKEN); +} + YY_BREAK +case 48: +YY_RULE_SETUP +#line 352 "pars0lex.l" +{ + return(PARS_INTO_TOKEN); +} + YY_BREAK +case 49: +YY_RULE_SETUP +#line 356 "pars0lex.l" +{ + return(PARS_VALUES_TOKEN); +} + YY_BREAK +case 50: +YY_RULE_SETUP +#line 360 "pars0lex.l" +{ + return(PARS_UPDATE_TOKEN); +} + YY_BREAK +case 51: +YY_RULE_SETUP +#line 364 "pars0lex.l" +{ + return(PARS_SET_TOKEN); +} + YY_BREAK +case 52: +YY_RULE_SETUP +#line 368 "pars0lex.l" +{ + return(PARS_DELETE_TOKEN); +} + YY_BREAK +case 53: +YY_RULE_SETUP +#line 372 "pars0lex.l" +{ + return(PARS_CURRENT_TOKEN); +} + YY_BREAK +case 54: +YY_RULE_SETUP +#line 376 "pars0lex.l" +{ + return(PARS_OF_TOKEN); +} + YY_BREAK +case 55: +YY_RULE_SETUP +#line 380 "pars0lex.l" +{ + return(PARS_CREATE_TOKEN); +} + YY_BREAK +case 56: +YY_RULE_SETUP +#line 384 "pars0lex.l" +{ + return(PARS_TABLE_TOKEN); +} + YY_BREAK +case 57: +YY_RULE_SETUP +#line 388 "pars0lex.l" +{ + return(PARS_INDEX_TOKEN); +} + YY_BREAK +case 58: +YY_RULE_SETUP +#line 392 "pars0lex.l" +{ + return(PARS_UNIQUE_TOKEN); +} + YY_BREAK +case 59: +YY_RULE_SETUP +#line 396 "pars0lex.l" +{ + return(PARS_CLUSTERED_TOKEN); +} + YY_BREAK +case 60: +YY_RULE_SETUP +#line 400 "pars0lex.l" +{ + return(PARS_DOES_NOT_FIT_IN_MEM_TOKEN); +} + YY_BREAK +case 61: +YY_RULE_SETUP +#line 404 "pars0lex.l" +{ + return(PARS_ON_TOKEN); +} + YY_BREAK +case 62: +YY_RULE_SETUP +#line 408 "pars0lex.l" +{ + return(PARS_DECLARE_TOKEN); +} + YY_BREAK +case 63: +YY_RULE_SETUP +#line 412 "pars0lex.l" +{ + return(PARS_CURSOR_TOKEN); +} + YY_BREAK +case 64: +YY_RULE_SETUP +#line 416 "pars0lex.l" +{ + return(PARS_OPEN_TOKEN); +} + YY_BREAK +case 65: +YY_RULE_SETUP +#line 420 "pars0lex.l" +{ + return(PARS_FETCH_TOKEN); +} + YY_BREAK +case 66: +YY_RULE_SETUP +#line 424 "pars0lex.l" +{ + return(PARS_CLOSE_TOKEN); +} + YY_BREAK +case 67: +YY_RULE_SETUP +#line 428 "pars0lex.l" +{ + return(PARS_NOTFOUND_TOKEN); +} + YY_BREAK +case 68: +YY_RULE_SETUP +#line 432 "pars0lex.l" +{ + return(PARS_TO_CHAR_TOKEN); +} + YY_BREAK +case 69: +YY_RULE_SETUP +#line 436 "pars0lex.l" +{ + return(PARS_TO_NUMBER_TOKEN); +} + YY_BREAK +case 70: +YY_RULE_SETUP +#line 440 "pars0lex.l" +{ + return(PARS_TO_BINARY_TOKEN); +} + YY_BREAK +case 71: +YY_RULE_SETUP +#line 444 "pars0lex.l" +{ + return(PARS_BINARY_TO_NUMBER_TOKEN); +} + YY_BREAK +case 72: +YY_RULE_SETUP +#line 448 "pars0lex.l" +{ + return(PARS_SUBSTR_TOKEN); +} + YY_BREAK +case 73: +YY_RULE_SETUP +#line 452 "pars0lex.l" +{ + return(PARS_REPLSTR_TOKEN); +} + YY_BREAK +case 74: +YY_RULE_SETUP +#line 456 "pars0lex.l" +{ + return(PARS_CONCAT_TOKEN); +} + YY_BREAK +case 75: +YY_RULE_SETUP +#line 460 "pars0lex.l" +{ + return(PARS_INSTR_TOKEN); +} + YY_BREAK +case 76: +YY_RULE_SETUP +#line 464 "pars0lex.l" +{ + return(PARS_LENGTH_TOKEN); +} + YY_BREAK +case 77: +YY_RULE_SETUP +#line 468 "pars0lex.l" +{ + return(PARS_SYSDATE_TOKEN); +} + YY_BREAK +case 78: +YY_RULE_SETUP +#line 472 "pars0lex.l" +{ + return(PARS_PRINTF_TOKEN); +} + YY_BREAK +case 79: +YY_RULE_SETUP +#line 476 "pars0lex.l" +{ + return(PARS_ASSERT_TOKEN); +} + YY_BREAK +case 80: +YY_RULE_SETUP +#line 480 "pars0lex.l" +{ + return(PARS_RND_TOKEN); +} + YY_BREAK +case 81: +YY_RULE_SETUP +#line 484 "pars0lex.l" +{ + return(PARS_RND_STR_TOKEN); +} + YY_BREAK +case 82: +YY_RULE_SETUP +#line 488 "pars0lex.l" +{ + return(PARS_ROW_PRINTF_TOKEN); +} + YY_BREAK +case 83: +YY_RULE_SETUP +#line 492 "pars0lex.l" +{ + return(PARS_COMMIT_TOKEN); +} + YY_BREAK +case 84: +YY_RULE_SETUP +#line 496 "pars0lex.l" +{ + return(PARS_ROLLBACK_TOKEN); +} + YY_BREAK +case 85: +YY_RULE_SETUP +#line 500 "pars0lex.l" +{ + return(PARS_WORK_TOKEN); +} + YY_BREAK +case 86: +YY_RULE_SETUP +#line 504 "pars0lex.l" +{ + return(PARS_UNSIGNED_TOKEN); +} + YY_BREAK +case 87: +YY_RULE_SETUP +#line 508 "pars0lex.l" +{ + return(PARS_EXIT_TOKEN); +} + YY_BREAK +case 88: +YY_RULE_SETUP +#line 512 "pars0lex.l" +{ + return(PARS_FUNCTION_TOKEN); +} + YY_BREAK +case 89: +YY_RULE_SETUP +#line 516 "pars0lex.l" +{ + return(PARS_LOCK_TOKEN); +} + YY_BREAK +case 90: +YY_RULE_SETUP +#line 520 "pars0lex.l" +{ + return(PARS_SHARE_TOKEN); +} + YY_BREAK +case 91: +YY_RULE_SETUP +#line 524 "pars0lex.l" +{ + return(PARS_MODE_TOKEN); +} + YY_BREAK +case 92: +YY_RULE_SETUP +#line 528 "pars0lex.l" +{ + yylval = sym_tab_add_id(pars_sym_tab_global, + (byte*)yytext, + ut_strlen(yytext)); + return(PARS_ID_TOKEN); +} + YY_BREAK +case 93: +YY_RULE_SETUP +#line 535 "pars0lex.l" +{ + return(PARS_DDOT_TOKEN); +} + YY_BREAK +case 94: +YY_RULE_SETUP +#line 539 "pars0lex.l" +{ + return(PARS_ASSIGN_TOKEN); +} + YY_BREAK +case 95: +YY_RULE_SETUP +#line 543 "pars0lex.l" +{ + return(PARS_LE_TOKEN); +} + YY_BREAK +case 96: +YY_RULE_SETUP +#line 547 "pars0lex.l" +{ + return(PARS_GE_TOKEN); +} + YY_BREAK +case 97: +YY_RULE_SETUP +#line 551 "pars0lex.l" +{ + return(PARS_NE_TOKEN); +} + YY_BREAK +case 98: +YY_RULE_SETUP +#line 555 "pars0lex.l" +{ + + return((int)(*yytext)); +} + YY_BREAK +case 99: +YY_RULE_SETUP +#line 560 "pars0lex.l" +{ + + return((int)(*yytext)); +} + YY_BREAK +case 100: +YY_RULE_SETUP +#line 565 "pars0lex.l" +{ + + return((int)(*yytext)); +} + YY_BREAK +case 101: +YY_RULE_SETUP +#line 570 "pars0lex.l" +{ + + return((int)(*yytext)); +} + YY_BREAK +case 102: +YY_RULE_SETUP +#line 575 "pars0lex.l" +{ + + return((int)(*yytext)); +} + YY_BREAK +case 103: +YY_RULE_SETUP +#line 580 "pars0lex.l" +{ + + return((int)(*yytext)); +} + YY_BREAK +case 104: +YY_RULE_SETUP +#line 585 "pars0lex.l" +{ + + return((int)(*yytext)); +} + YY_BREAK +case 105: +YY_RULE_SETUP +#line 590 "pars0lex.l" +{ + + return((int)(*yytext)); +} + YY_BREAK +case 106: +YY_RULE_SETUP +#line 595 "pars0lex.l" +{ + + return((int)(*yytext)); +} + YY_BREAK +case 107: +YY_RULE_SETUP +#line 600 "pars0lex.l" +{ + + return((int)(*yytext)); +} + YY_BREAK +case 108: +YY_RULE_SETUP +#line 605 "pars0lex.l" +{ + + return((int)(*yytext)); +} + YY_BREAK +case 109: +YY_RULE_SETUP +#line 610 "pars0lex.l" +{ + + return((int)(*yytext)); +} + YY_BREAK +case 110: +YY_RULE_SETUP +#line 615 "pars0lex.l" +{ + + return((int)(*yytext)); +} + YY_BREAK +case 111: +YY_RULE_SETUP +#line 620 "pars0lex.l" +{ + + return((int)(*yytext)); +} + YY_BREAK +case 112: +YY_RULE_SETUP +#line 625 "pars0lex.l" +{ + + return((int)(*yytext)); +} + YY_BREAK +case 113: +YY_RULE_SETUP +#line 630 "pars0lex.l" +BEGIN(comment); /* eat up comment */ + YY_BREAK +case 114: +/* rule 114 can match eol */ +YY_RULE_SETUP +#line 632 "pars0lex.l" + + YY_BREAK +case 115: +/* rule 115 can match eol */ +YY_RULE_SETUP +#line 633 "pars0lex.l" + + YY_BREAK +case 116: +YY_RULE_SETUP +#line 634 "pars0lex.l" +BEGIN(INITIAL); + YY_BREAK +case 117: +/* rule 117 can match eol */ +YY_RULE_SETUP +#line 636 "pars0lex.l" +/* eat up whitespace */ + YY_BREAK +case 118: +YY_RULE_SETUP +#line 639 "pars0lex.l" +{ + fprintf(stderr,"Unrecognized character: %02x\n", + *yytext); + + ut_error; + + return(0); +} + YY_BREAK +case 119: +YY_RULE_SETUP +#line 648 "pars0lex.l" +YY_FATAL_ERROR( "flex scanner jammed" ); + YY_BREAK +#line 1916 "lexyy.c" +case YY_STATE_EOF(INITIAL): +case YY_STATE_EOF(comment): +case YY_STATE_EOF(quoted): +case YY_STATE_EOF(id): + yyterminate(); + + case YY_END_OF_BUFFER: + { + /* Amount of text matched not including the EOB char. */ + int yy_amount_of_matched_text = (int) (yy_cp - (yytext_ptr)) - 1; + + /* Undo the effects of YY_DO_BEFORE_ACTION. */ + *yy_cp = (yy_hold_char); + YY_RESTORE_YY_MORE_OFFSET + + if ( YY_CURRENT_BUFFER_LVALUE->yy_buffer_status == YY_BUFFER_NEW ) + { + /* We're scanning a new file or input source. It's + * possible that this happened because the user + * just pointed yyin at a new source and called + * yylex(). If so, then we have to assure + * consistency between YY_CURRENT_BUFFER and our + * globals. Here is the right place to do so, because + * this is the first action (other than possibly a + * back-up) that will match for the new input source. + */ + (yy_n_chars) = YY_CURRENT_BUFFER_LVALUE->yy_n_chars; + YY_CURRENT_BUFFER_LVALUE->yy_input_file = yyin; + YY_CURRENT_BUFFER_LVALUE->yy_buffer_status = YY_BUFFER_NORMAL; + } + + /* Note that here we test for yy_c_buf_p "<=" to the position + * of the first EOB in the buffer, since yy_c_buf_p will + * already have been incremented past the NUL character + * (since all states make transitions on EOB to the + * end-of-buffer state). Contrast this with the test + * in input(). + */ + if ( (yy_c_buf_p) <= &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[(yy_n_chars)] ) + { /* This was really a NUL. */ + yy_state_type yy_next_state; + + (yy_c_buf_p) = (yytext_ptr) + yy_amount_of_matched_text; + + yy_current_state = yy_get_previous_state( ); + + /* Okay, we're now positioned to make the NUL + * transition. We couldn't have + * yy_get_previous_state() go ahead and do it + * for us because it doesn't know how to deal + * with the possibility of jamming (and we don't + * want to build jamming into it because then it + * will run more slowly). + */ + + yy_next_state = yy_try_NUL_trans( yy_current_state ); + + yy_bp = (yytext_ptr) + YY_MORE_ADJ; + + if ( yy_next_state ) + { + /* Consume the NUL. */ + yy_cp = ++(yy_c_buf_p); + yy_current_state = yy_next_state; + goto yy_match; + } + + else + { + yy_cp = (yy_last_accepting_cpos); + yy_current_state = (yy_last_accepting_state); + goto yy_find_action; + } + } + + else switch ( yy_get_next_buffer( ) ) + { + case EOB_ACT_END_OF_FILE: + { + (yy_did_buffer_switch_on_eof) = 0; + + if ( yywrap( ) ) + { + /* Note: because we've taken care in + * yy_get_next_buffer() to have set up + * yytext, we can now set up + * yy_c_buf_p so that if some total + * hoser (like flex itself) wants to + * call the scanner after we return the + * YY_NULL, it'll still work - another + * YY_NULL will get returned. + */ + (yy_c_buf_p) = (yytext_ptr) + YY_MORE_ADJ; + + yy_act = YY_STATE_EOF(YY_START); + goto do_action; + } + + else + { + if ( ! (yy_did_buffer_switch_on_eof) ) + YY_NEW_FILE; + } + break; + } + + case EOB_ACT_CONTINUE_SCAN: + (yy_c_buf_p) = + (yytext_ptr) + yy_amount_of_matched_text; + + yy_current_state = yy_get_previous_state( ); + + yy_cp = (yy_c_buf_p); + yy_bp = (yytext_ptr) + YY_MORE_ADJ; + goto yy_match; + + case EOB_ACT_LAST_MATCH: + (yy_c_buf_p) = + &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[(yy_n_chars)]; + + yy_current_state = yy_get_previous_state( ); + + yy_cp = (yy_c_buf_p); + yy_bp = (yytext_ptr) + YY_MORE_ADJ; + goto yy_find_action; + } + break; + } + + default: + YY_FATAL_ERROR( + "fatal flex scanner internal error--no action found" ); + } /* end of action switch */ + } /* end of scanning one token */ +} /* end of yylex */ + +/* yy_get_next_buffer - try to read in a new buffer + * + * Returns a code representing an action: + * EOB_ACT_LAST_MATCH - + * EOB_ACT_CONTINUE_SCAN - continue scanning from current position + * EOB_ACT_END_OF_FILE - end of file + */ +static int yy_get_next_buffer (void) +{ + register char *dest = YY_CURRENT_BUFFER_LVALUE->yy_ch_buf; + register char *source = (yytext_ptr); + register int number_to_move, i; + int ret_val; + + if ( (yy_c_buf_p) > &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[(yy_n_chars) + 1] ) + YY_FATAL_ERROR( + "fatal flex scanner internal error--end of buffer missed" ); + + if ( YY_CURRENT_BUFFER_LVALUE->yy_fill_buffer == 0 ) + { /* Don't try to fill the buffer, so this is an EOF. */ + if ( (yy_c_buf_p) - (yytext_ptr) - YY_MORE_ADJ == 1 ) + { + /* We matched a single character, the EOB, so + * treat this as a final EOF. + */ + return EOB_ACT_END_OF_FILE; + } + + else + { + /* We matched some text prior to the EOB, first + * process it. + */ + return EOB_ACT_LAST_MATCH; + } + } + + /* Try to read more data. */ + + /* First move last chars to start of buffer. */ + number_to_move = (int) ((yy_c_buf_p) - (yytext_ptr)) - 1; + + for ( i = 0; i < number_to_move; ++i ) + *(dest++) = *(source++); + + if ( YY_CURRENT_BUFFER_LVALUE->yy_buffer_status == YY_BUFFER_EOF_PENDING ) + /* don't do the read, it's not guaranteed to return an EOF, + * just force an EOF + */ + YY_CURRENT_BUFFER_LVALUE->yy_n_chars = (yy_n_chars) = 0; + + else + { + size_t num_to_read = + YY_CURRENT_BUFFER_LVALUE->yy_buf_size - number_to_move - 1; + + while ( num_to_read <= 0 ) + { /* Not enough room in the buffer - grow it. */ + + /* just a shorter name for the current buffer */ + YY_BUFFER_STATE b = YY_CURRENT_BUFFER; + + int yy_c_buf_p_offset = + (int) ((yy_c_buf_p) - b->yy_ch_buf); + + if ( b->yy_is_our_buffer ) + { + int new_size = b->yy_buf_size * 2; + + if ( new_size <= 0 ) + b->yy_buf_size += b->yy_buf_size / 8; + else + b->yy_buf_size *= 2; + + b->yy_ch_buf = (char *) + /* Include room in for 2 EOB chars. */ + yyrealloc((void *) b->yy_ch_buf,b->yy_buf_size + 2 ); + } + else + /* Can't grow it, we don't own it. */ + b->yy_ch_buf = 0; + + if ( ! b->yy_ch_buf ) + YY_FATAL_ERROR( + "fatal error - scanner input buffer overflow" ); + + (yy_c_buf_p) = &b->yy_ch_buf[yy_c_buf_p_offset]; + + num_to_read = YY_CURRENT_BUFFER_LVALUE->yy_buf_size - + number_to_move - 1; + + } + + if ( num_to_read > YY_READ_BUF_SIZE ) + num_to_read = YY_READ_BUF_SIZE; + + /* Read in more data. */ + YY_INPUT( (&YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[number_to_move]), + (yy_n_chars), num_to_read ); + + YY_CURRENT_BUFFER_LVALUE->yy_n_chars = (yy_n_chars); + } + + if ( (yy_n_chars) == 0 ) + { + if ( number_to_move == YY_MORE_ADJ ) + { + ret_val = EOB_ACT_END_OF_FILE; + yyrestart(yyin ); + } + + else + { + ret_val = EOB_ACT_LAST_MATCH; + YY_CURRENT_BUFFER_LVALUE->yy_buffer_status = + YY_BUFFER_EOF_PENDING; + } + } + + else + ret_val = EOB_ACT_CONTINUE_SCAN; + + (yy_n_chars) += number_to_move; + YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[(yy_n_chars)] = YY_END_OF_BUFFER_CHAR; + YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[(yy_n_chars) + 1] = YY_END_OF_BUFFER_CHAR; + + (yytext_ptr) = &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[0]; + + return ret_val; +} + +/* yy_get_previous_state - get the state just before the EOB char was reached */ + + static yy_state_type yy_get_previous_state (void) +{ + register yy_state_type yy_current_state; + register char *yy_cp; + + yy_current_state = (yy_start); + + for ( yy_cp = (yytext_ptr) + YY_MORE_ADJ; yy_cp < (yy_c_buf_p); ++yy_cp ) + { + register YY_CHAR yy_c = (*yy_cp ? yy_ec[YY_SC_TO_UI(*yy_cp)] : 1); + if ( yy_accept[yy_current_state] ) + { + (yy_last_accepting_state) = yy_current_state; + (yy_last_accepting_cpos) = yy_cp; + } + while ( yy_chk[yy_base[yy_current_state] + yy_c] != yy_current_state ) + { + yy_current_state = (int) yy_def[yy_current_state]; + if ( yy_current_state >= 399 ) + yy_c = yy_meta[(unsigned int) yy_c]; + } + yy_current_state = yy_nxt[yy_base[yy_current_state] + (unsigned int) yy_c]; + } + + return yy_current_state; +} + +/* yy_try_NUL_trans - try to make a transition on the NUL character + * + * synopsis + * next_state = yy_try_NUL_trans( current_state ); + */ + static yy_state_type yy_try_NUL_trans (yy_state_type yy_current_state ) +{ + register int yy_is_jam; + register char *yy_cp = (yy_c_buf_p); + + register YY_CHAR yy_c = 1; + if ( yy_accept[yy_current_state] ) + { + (yy_last_accepting_state) = yy_current_state; + (yy_last_accepting_cpos) = yy_cp; + } + while ( yy_chk[yy_base[yy_current_state] + yy_c] != yy_current_state ) + { + yy_current_state = (int) yy_def[yy_current_state]; + if ( yy_current_state >= 399 ) + yy_c = yy_meta[(unsigned int) yy_c]; + } + yy_current_state = yy_nxt[yy_base[yy_current_state] + (unsigned int) yy_c]; + yy_is_jam = (yy_current_state == 398); + + return yy_is_jam ? 0 : yy_current_state; +} + +#ifndef YY_NO_INPUT +#ifdef __cplusplus + static int yyinput (void) +#else + static int input (void) +#endif + +{ + int c; + + *(yy_c_buf_p) = (yy_hold_char); + + if ( *(yy_c_buf_p) == YY_END_OF_BUFFER_CHAR ) + { + /* yy_c_buf_p now points to the character we want to return. + * If this occurs *before* the EOB characters, then it's a + * valid NUL; if not, then we've hit the end of the buffer. + */ + if ( (yy_c_buf_p) < &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[(yy_n_chars)] ) + /* This was really a NUL. */ + *(yy_c_buf_p) = '\0'; + + else + { /* need more input */ + int offset = (int)((yy_c_buf_p) - (yytext_ptr)); + ++(yy_c_buf_p); + + switch ( yy_get_next_buffer( ) ) + { + case EOB_ACT_LAST_MATCH: + /* This happens because yy_g_n_b() + * sees that we've accumulated a + * token and flags that we need to + * try matching the token before + * proceeding. But for input(), + * there's no matching to consider. + * So convert the EOB_ACT_LAST_MATCH + * to EOB_ACT_END_OF_FILE. + */ + + /* Reset buffer status. */ + yyrestart(yyin ); + + /*FALLTHROUGH*/ + + case EOB_ACT_END_OF_FILE: + { + if ( yywrap( ) ) + return EOF; + + if ( ! (yy_did_buffer_switch_on_eof) ) + YY_NEW_FILE; +#ifdef __cplusplus + return yyinput(); +#else + return input(); +#endif + } + + case EOB_ACT_CONTINUE_SCAN: + (yy_c_buf_p) = (yytext_ptr) + offset; + break; + } + } + } + + c = *(unsigned char *) (yy_c_buf_p); /* cast for 8-bit char's */ + *(yy_c_buf_p) = '\0'; /* preserve yytext */ + (yy_hold_char) = *++(yy_c_buf_p); + + return c; +} +#endif /* ifndef YY_NO_INPUT */ + +/** Immediately switch to a different input stream. + * @param input_file A readable stream. + * + * @note This function does not reset the start condition to @c INITIAL . + */ + static void yyrestart (FILE * input_file ) +{ + + if ( ! YY_CURRENT_BUFFER ){ + yyensure_buffer_stack (); + YY_CURRENT_BUFFER_LVALUE = + yy_create_buffer(yyin,YY_BUF_SIZE ); + } + + yy_init_buffer(YY_CURRENT_BUFFER,input_file ); + yy_load_buffer_state( ); +} + +/** Switch to a different input buffer. + * @param new_buffer The new input buffer. + * + */ + __attribute__((unused)) static void yy_switch_to_buffer (YY_BUFFER_STATE new_buffer ) +{ + + /* TODO. We should be able to replace this entire function body + * with + * yypop_buffer_state(); + * yypush_buffer_state(new_buffer); + */ + yyensure_buffer_stack (); + if ( YY_CURRENT_BUFFER == new_buffer ) + return; + + if ( YY_CURRENT_BUFFER ) + { + /* Flush out information for old buffer. */ + *(yy_c_buf_p) = (yy_hold_char); + YY_CURRENT_BUFFER_LVALUE->yy_buf_pos = (yy_c_buf_p); + YY_CURRENT_BUFFER_LVALUE->yy_n_chars = (yy_n_chars); + } + + YY_CURRENT_BUFFER_LVALUE = new_buffer; + yy_load_buffer_state( ); + + /* We don't actually know whether we did this switch during + * EOF (yywrap()) processing, but the only time this flag + * is looked at is after yywrap() is called, so it's safe + * to go ahead and always set it. + */ + (yy_did_buffer_switch_on_eof) = 1; +} + +static void yy_load_buffer_state (void) +{ + (yy_n_chars) = YY_CURRENT_BUFFER_LVALUE->yy_n_chars; + (yytext_ptr) = (yy_c_buf_p) = YY_CURRENT_BUFFER_LVALUE->yy_buf_pos; + yyin = YY_CURRENT_BUFFER_LVALUE->yy_input_file; + (yy_hold_char) = *(yy_c_buf_p); +} + +/** Allocate and initialize an input buffer state. + * @param file A readable stream. + * @param size The character buffer size in bytes. When in doubt, use @c YY_BUF_SIZE. + * + * @return the allocated buffer state. + */ + static YY_BUFFER_STATE yy_create_buffer (FILE * file, int size ) +{ + YY_BUFFER_STATE b; + + b = (YY_BUFFER_STATE) yyalloc(sizeof( struct yy_buffer_state ) ); + if ( ! b ) + YY_FATAL_ERROR( "out of dynamic memory in yy_create_buffer()" ); + + b->yy_buf_size = size; + + /* yy_ch_buf has to be 2 characters longer than the size given because + * we need to put in 2 end-of-buffer characters. + */ + b->yy_ch_buf = (char *) yyalloc(b->yy_buf_size + 2 ); + if ( ! b->yy_ch_buf ) + YY_FATAL_ERROR( "out of dynamic memory in yy_create_buffer()" ); + + b->yy_is_our_buffer = 1; + + yy_init_buffer(b,file ); + + return b; +} + +/** Destroy the buffer. + * @param b a buffer created with yy_create_buffer() + * + */ + static void yy_delete_buffer (YY_BUFFER_STATE b ) +{ + + if ( ! b ) + return; + + if ( b == YY_CURRENT_BUFFER ) /* Not sure if we should pop here. */ + YY_CURRENT_BUFFER_LVALUE = (YY_BUFFER_STATE) 0; + + if ( b->yy_is_our_buffer ) + yyfree((void *) b->yy_ch_buf ); + + yyfree((void *) b ); +} + +/* Initializes or reinitializes a buffer. + * This function is sometimes called more than once on the same buffer, + * such as during a yyrestart() or at EOF. + */ + static void yy_init_buffer (YY_BUFFER_STATE b, FILE * file ) + +{ + int oerrno = errno; + + yy_flush_buffer(b ); + + b->yy_input_file = file; + b->yy_fill_buffer = 1; + + /* If b is the current buffer, then yy_init_buffer was _probably_ + * called from yyrestart() or through yy_get_next_buffer. + * In that case, we don't want to reset the lineno or column. + */ + if (b != YY_CURRENT_BUFFER){ + b->yy_bs_lineno = 1; + b->yy_bs_column = 0; + } + + b->yy_is_interactive = 0; + + errno = oerrno; +} + +/** Discard all buffered characters. On the next scan, YY_INPUT will be called. + * @param b the buffer state to be flushed, usually @c YY_CURRENT_BUFFER. + * + */ + static void yy_flush_buffer (YY_BUFFER_STATE b ) +{ + if ( ! b ) + return; + + b->yy_n_chars = 0; + + /* We always need two end-of-buffer characters. The first causes + * a transition to the end-of-buffer state. The second causes + * a jam in that state. + */ + b->yy_ch_buf[0] = YY_END_OF_BUFFER_CHAR; + b->yy_ch_buf[1] = YY_END_OF_BUFFER_CHAR; + + b->yy_buf_pos = &b->yy_ch_buf[0]; + + b->yy_at_bol = 1; + b->yy_buffer_status = YY_BUFFER_NEW; + + if ( b == YY_CURRENT_BUFFER ) + yy_load_buffer_state( ); +} + +/** Pushes the new state onto the stack. The new state becomes + * the current state. This function will allocate the stack + * if necessary. + * @param new_buffer The new state. + * + */ +__attribute__((unused)) static void yypush_buffer_state (YY_BUFFER_STATE new_buffer ) +{ + if (new_buffer == NULL) + return; + + yyensure_buffer_stack(); + + /* This block is copied from yy_switch_to_buffer. */ + if ( YY_CURRENT_BUFFER ) + { + /* Flush out information for old buffer. */ + *(yy_c_buf_p) = (yy_hold_char); + YY_CURRENT_BUFFER_LVALUE->yy_buf_pos = (yy_c_buf_p); + YY_CURRENT_BUFFER_LVALUE->yy_n_chars = (yy_n_chars); + } + + /* Only push if top exists. Otherwise, replace top. */ + if (YY_CURRENT_BUFFER) + (yy_buffer_stack_top)++; + YY_CURRENT_BUFFER_LVALUE = new_buffer; + + /* copied from yy_switch_to_buffer. */ + yy_load_buffer_state( ); + (yy_did_buffer_switch_on_eof) = 1; +} + +/** Removes and deletes the top of the stack, if present. + * The next element becomes the new top. + * + */ +__attribute__((unused)) static void yypop_buffer_state (void) +{ + if (!YY_CURRENT_BUFFER) + return; + + yy_delete_buffer(YY_CURRENT_BUFFER ); + YY_CURRENT_BUFFER_LVALUE = NULL; + if ((yy_buffer_stack_top) > 0) + --(yy_buffer_stack_top); + + if (YY_CURRENT_BUFFER) { + yy_load_buffer_state( ); + (yy_did_buffer_switch_on_eof) = 1; + } +} + +/* Allocates the stack if it does not exist. + * Guarantees space for at least one push. + */ +static void yyensure_buffer_stack (void) +{ + int num_to_alloc; + + if (!(yy_buffer_stack)) { + + /* First allocation is just for 2 elements, since we don't know if this + * scanner will even need a stack. We use 2 instead of 1 to avoid an + * immediate realloc on the next call. + */ + num_to_alloc = 1; + (yy_buffer_stack) = (struct yy_buffer_state**)yyalloc + (num_to_alloc * sizeof(struct yy_buffer_state*) + ); + + memset((yy_buffer_stack), 0, num_to_alloc * sizeof(struct yy_buffer_state*)); + + (yy_buffer_stack_max) = num_to_alloc; + (yy_buffer_stack_top) = 0; + return; + } + + if ((yy_buffer_stack_top) >= ((yy_buffer_stack_max)) - 1){ + + /* Increase the buffer to prepare for a possible push. */ + int grow_size = 8 /* arbitrary grow size */; + + num_to_alloc = (yy_buffer_stack_max) + grow_size; + (yy_buffer_stack) = (struct yy_buffer_state**)yyrealloc + ((yy_buffer_stack), + num_to_alloc * sizeof(struct yy_buffer_state*) + ); + + /* zero only the new slots.*/ + memset((yy_buffer_stack) + (yy_buffer_stack_max), 0, grow_size * sizeof(struct yy_buffer_state*)); + (yy_buffer_stack_max) = num_to_alloc; + } +} + +#ifndef YY_EXIT_FAILURE +#define YY_EXIT_FAILURE 2 +#endif + +static void yy_fatal_error (yyconst char* msg ) +{ + (void) fprintf( stderr, "%s\n", msg ); + exit( YY_EXIT_FAILURE ); +} + +/* Redefine yyless() so it works in section 3 code. */ + +#undef yyless +#define yyless(n) \ + do \ + { \ + /* Undo effects of setting up yytext. */ \ + int yyless_macro_arg = (n); \ + YY_LESS_LINENO(yyless_macro_arg);\ + yytext[yyleng] = (yy_hold_char); \ + (yy_c_buf_p) = yytext + yyless_macro_arg; \ + (yy_hold_char) = *(yy_c_buf_p); \ + *(yy_c_buf_p) = '\0'; \ + yyleng = yyless_macro_arg; \ + } \ + while ( 0 ) + +/* Accessor methods (get/set functions) to struct members. */ + +/** Get the current line number. + * + */ +__attribute__((unused)) static int yyget_lineno (void) +{ + + return yylineno; +} + +/** Get the input stream. + * + */ +__attribute__((unused)) static FILE *yyget_in (void) +{ + return yyin; +} + +/** Get the output stream. + * + */ +__attribute__((unused)) static FILE *yyget_out (void) +{ + return yyout; +} + +/** Get the length of the current token. + * + */ +__attribute__((unused)) static int yyget_leng (void) +{ + return yyleng; +} + +/** Get the current token. + * + */ + +__attribute__((unused)) static char *yyget_text (void) +{ + return yytext; +} + +/** Set the current line number. + * @param line_number + * + */ +__attribute__((unused)) static void yyset_lineno (int line_number ) +{ + + yylineno = line_number; +} + +/** Set the input stream. This does not discard the current + * input buffer. + * @param in_str A readable stream. + * + * @see yy_switch_to_buffer + */ +__attribute__((unused)) static void yyset_in (FILE * in_str ) +{ + yyin = in_str ; +} + +__attribute__((unused)) static void yyset_out (FILE * out_str ) +{ + yyout = out_str ; +} + +__attribute__((unused)) static int yyget_debug (void) +{ + return yy_flex_debug; +} + +__attribute__((unused)) static void yyset_debug (int bdebug ) +{ + yy_flex_debug = bdebug ; +} + +/* yylex_destroy is for both reentrant and non-reentrant scanners. */ +__attribute__((unused)) static int yylex_destroy (void) +{ + + /* Pop the buffer stack, destroying each element. */ + while(YY_CURRENT_BUFFER){ + yy_delete_buffer(YY_CURRENT_BUFFER ); + YY_CURRENT_BUFFER_LVALUE = NULL; + yypop_buffer_state(); + } + + /* Destroy the stack itself. */ + yyfree((yy_buffer_stack) ); + (yy_buffer_stack) = NULL; + + return 0; +} + +/* + * Internal utility routines. + */ + +#ifndef yytext_ptr +static void yy_flex_strncpy (char* s1, yyconst char * s2, int n ) +{ + register int i; + for ( i = 0; i < n; ++i ) + s1[i] = s2[i]; +} +#endif + +#ifdef YY_NEED_STRLEN +static int yy_flex_strlen (yyconst char * s ) +{ + register int n; + for ( n = 0; s[n]; ++n ) + ; + + return n; +} +#endif + +static void *yyalloc (yy_size_t size ) +{ + return (void *) malloc( size ); +} + +static void *yyrealloc (void * ptr, yy_size_t size ) +{ + /* The cast to (char *) in the following accommodates both + * implementations that use char* generic pointers, and those + * that use void* generic pointers. It works with the latter + * because both ANSI C and C++ allow castless assignment from + * any pointer type to void*, and deal with argument conversions + * as though doing an assignment. + */ + return (void *) realloc( (char *) ptr, size ); +} + +static void yyfree (void * ptr ) +{ + free( (char *) ptr ); /* see yyrealloc() for (char *) cast */ +} + +#define YYTABLES_NAME "yytables" + +#undef YY_NEW_FILE +#undef YY_FLUSH_BUFFER +#undef yy_set_bol +#undef yy_new_buffer +#undef yy_set_interactive +#undef yytext_ptr +#undef YY_DO_BEFORE_ACTION + +#ifdef YY_DECL_IS_OURS +#undef YY_DECL_IS_OURS +#undef YY_DECL +#endif +#line 648 "pars0lex.l" + + + diff --git a/storage/xtradb/pars/make_bison.sh b/storage/xtradb/pars/make_bison.sh new file mode 100755 index 00000000000..09bb86e3106 --- /dev/null +++ b/storage/xtradb/pars/make_bison.sh @@ -0,0 +1,32 @@ +#!/bin/bash +# +# Copyright (c) 2006, 2009, Innobase Oy. 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 +# +# generate parser files from bison input files. + +set -eu +TMPFILE=pars0grm.tab.c +OUTFILE=pars0grm.c + +bison -d pars0grm.y +mv pars0grm.tab.h ../include/pars0grm.h + +sed -e ' +s/'"$TMPFILE"'/'"$OUTFILE"'/; +s/^\(\(YYSTYPE\|int\) yy\(char\|nerrs\)\)/static \1/; +s/\(\(YYSTYPE\|int\) yy\(lval\|parse\)\)/UNIV_INTERN \1/; +' < "$TMPFILE" > "$OUTFILE" + +rm "$TMPFILE" diff --git a/storage/xtradb/pars/make_flex.sh b/storage/xtradb/pars/make_flex.sh new file mode 100755 index 00000000000..89308a6636f --- /dev/null +++ b/storage/xtradb/pars/make_flex.sh @@ -0,0 +1,48 @@ +#!/bin/bash +# +# Copyright (c) 2006, 2009, Innobase Oy. 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 +# +# generate lexer files from flex input files. + +set -eu + +TMPFILE=_flex_tmp.c +OUTFILE=lexyy.c + +flex -o $TMPFILE pars0lex.l + +# AIX needs its includes done in a certain order, so include "univ.i" first +# to be sure we get it right. +echo '#include "univ.i"' > $OUTFILE + +# flex assigns a pointer to an int in one place without a cast, resulting in +# a warning on Win64. Add the cast. Also define some symbols as static. +sed -e ' +s/'"$TMPFILE"'/'"$OUTFILE"'/; +s/\(int offset = \)\((yy_c_buf_p) - (yytext_ptr)\);/\1(int)(\2);/; +s/\(void yy\(restart\|_\(delete\|flush\)_buffer\)\)/static \1/; +s/\(void yy_switch_to_buffer\)/__attribute__((unused)) static \1/; +s/\(void yy\(push\|pop\)_buffer_state\)/__attribute__((unused)) static \1/; +s/\(YY_BUFFER_STATE yy_create_buffer\)/static \1/; +s/\(\(int\|void\) yy[gs]et_\)/__attribute__((unused)) static \1/; +s/\(void \*\?yy\(\(re\)\?alloc\|free\)\)/static \1/; +s/\(extern \)\?\(int yy\(leng\|lineno\|_flex_debug\)\)/static \2/; +s/\(int yylex_destroy\)/__attribute__((unused)) static \1/; +s/\(extern \)\?\(int yylex \)/UNIV_INTERN \2/; +s/^\(\(FILE\|char\) *\* *yyget\)/__attribute__((unused)) static \1/; +s/^\(extern \)\?\(\(FILE\|char\) *\* *yy\)/static \2/; +' < $TMPFILE >> $OUTFILE + +rm $TMPFILE diff --git a/storage/xtradb/pars/pars0grm.c b/storage/xtradb/pars/pars0grm.c new file mode 100644 index 00000000000..d667970735e --- /dev/null +++ b/storage/xtradb/pars/pars0grm.c @@ -0,0 +1,2601 @@ +/***************************************************************************** + +Copyright (c) 1995, 2009, Innobase Oy. All Rights Reserved. +Copyright (c) 1984, 1989, 1990, 2000, 2001, 2002, 2003, 2004 Free Software +Foundation, Inc. + +As a special exception, when this file is copied by Bison into a +Bison output file, you may use that output file without restriction. +This special exception was added by the Free Software Foundation +in version 1.24 of Bison. + +This program is free software; you can redistribute it and/or modify it under +the terms of the GNU General Public License as published by the Free Software +Foundation; version 2 of the License. + +This program is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + +You 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 + +*****************************************************************************/ + +/* A Bison parser, made by GNU Bison 2.0. */ + +/* Written by Richard Stallman by simplifying the original so called + ``semantic'' parser. */ + +/* All symbols defined below should begin with yy or YY, to avoid + infringing on user name space. This should be done even for local + variables, as they might otherwise be expanded by user macros. + There are some unavoidable exceptions within include files to + define necessary library symbols; they are noted "INFRINGES ON + USER NAME SPACE" below. */ + +/* Identify Bison output. */ +#define YYBISON 1 + +/* Skeleton name. */ +#define YYSKELETON_NAME "yacc.c" + +/* Pure parsers. */ +#define YYPURE 0 + +/* Using locations. */ +#define YYLSP_NEEDED 0 + + + +/* Tokens. */ +#ifndef YYTOKENTYPE +# define YYTOKENTYPE + /* Put the tokens into the symbol table, so that GDB and other debuggers + know about them. */ + enum yytokentype { + PARS_INT_LIT = 258, + PARS_FLOAT_LIT = 259, + PARS_STR_LIT = 260, + PARS_FIXBINARY_LIT = 261, + PARS_BLOB_LIT = 262, + PARS_NULL_LIT = 263, + PARS_ID_TOKEN = 264, + PARS_AND_TOKEN = 265, + PARS_OR_TOKEN = 266, + PARS_NOT_TOKEN = 267, + PARS_GE_TOKEN = 268, + PARS_LE_TOKEN = 269, + PARS_NE_TOKEN = 270, + PARS_PROCEDURE_TOKEN = 271, + PARS_IN_TOKEN = 272, + PARS_OUT_TOKEN = 273, + PARS_BINARY_TOKEN = 274, + PARS_BLOB_TOKEN = 275, + PARS_INT_TOKEN = 276, + PARS_INTEGER_TOKEN = 277, + PARS_FLOAT_TOKEN = 278, + PARS_CHAR_TOKEN = 279, + PARS_IS_TOKEN = 280, + PARS_BEGIN_TOKEN = 281, + PARS_END_TOKEN = 282, + PARS_IF_TOKEN = 283, + PARS_THEN_TOKEN = 284, + PARS_ELSE_TOKEN = 285, + PARS_ELSIF_TOKEN = 286, + PARS_LOOP_TOKEN = 287, + PARS_WHILE_TOKEN = 288, + PARS_RETURN_TOKEN = 289, + PARS_SELECT_TOKEN = 290, + PARS_SUM_TOKEN = 291, + PARS_COUNT_TOKEN = 292, + PARS_DISTINCT_TOKEN = 293, + PARS_FROM_TOKEN = 294, + PARS_WHERE_TOKEN = 295, + PARS_FOR_TOKEN = 296, + PARS_DDOT_TOKEN = 297, + PARS_READ_TOKEN = 298, + PARS_ORDER_TOKEN = 299, + PARS_BY_TOKEN = 300, + PARS_ASC_TOKEN = 301, + PARS_DESC_TOKEN = 302, + PARS_INSERT_TOKEN = 303, + PARS_INTO_TOKEN = 304, + PARS_VALUES_TOKEN = 305, + PARS_UPDATE_TOKEN = 306, + PARS_SET_TOKEN = 307, + PARS_DELETE_TOKEN = 308, + PARS_CURRENT_TOKEN = 309, + PARS_OF_TOKEN = 310, + PARS_CREATE_TOKEN = 311, + PARS_TABLE_TOKEN = 312, + PARS_INDEX_TOKEN = 313, + PARS_UNIQUE_TOKEN = 314, + PARS_CLUSTERED_TOKEN = 315, + PARS_DOES_NOT_FIT_IN_MEM_TOKEN = 316, + PARS_ON_TOKEN = 317, + PARS_ASSIGN_TOKEN = 318, + PARS_DECLARE_TOKEN = 319, + PARS_CURSOR_TOKEN = 320, + PARS_SQL_TOKEN = 321, + PARS_OPEN_TOKEN = 322, + PARS_FETCH_TOKEN = 323, + PARS_CLOSE_TOKEN = 324, + PARS_NOTFOUND_TOKEN = 325, + PARS_TO_CHAR_TOKEN = 326, + PARS_TO_NUMBER_TOKEN = 327, + PARS_TO_BINARY_TOKEN = 328, + PARS_BINARY_TO_NUMBER_TOKEN = 329, + PARS_SUBSTR_TOKEN = 330, + PARS_REPLSTR_TOKEN = 331, + PARS_CONCAT_TOKEN = 332, + PARS_INSTR_TOKEN = 333, + PARS_LENGTH_TOKEN = 334, + PARS_SYSDATE_TOKEN = 335, + PARS_PRINTF_TOKEN = 336, + PARS_ASSERT_TOKEN = 337, + PARS_RND_TOKEN = 338, + PARS_RND_STR_TOKEN = 339, + PARS_ROW_PRINTF_TOKEN = 340, + PARS_COMMIT_TOKEN = 341, + PARS_ROLLBACK_TOKEN = 342, + PARS_WORK_TOKEN = 343, + PARS_UNSIGNED_TOKEN = 344, + PARS_EXIT_TOKEN = 345, + PARS_FUNCTION_TOKEN = 346, + PARS_LOCK_TOKEN = 347, + PARS_SHARE_TOKEN = 348, + PARS_MODE_TOKEN = 349, + NEG = 350 + }; +#endif +#define PARS_INT_LIT 258 +#define PARS_FLOAT_LIT 259 +#define PARS_STR_LIT 260 +#define PARS_FIXBINARY_LIT 261 +#define PARS_BLOB_LIT 262 +#define PARS_NULL_LIT 263 +#define PARS_ID_TOKEN 264 +#define PARS_AND_TOKEN 265 +#define PARS_OR_TOKEN 266 +#define PARS_NOT_TOKEN 267 +#define PARS_GE_TOKEN 268 +#define PARS_LE_TOKEN 269 +#define PARS_NE_TOKEN 270 +#define PARS_PROCEDURE_TOKEN 271 +#define PARS_IN_TOKEN 272 +#define PARS_OUT_TOKEN 273 +#define PARS_BINARY_TOKEN 274 +#define PARS_BLOB_TOKEN 275 +#define PARS_INT_TOKEN 276 +#define PARS_INTEGER_TOKEN 277 +#define PARS_FLOAT_TOKEN 278 +#define PARS_CHAR_TOKEN 279 +#define PARS_IS_TOKEN 280 +#define PARS_BEGIN_TOKEN 281 +#define PARS_END_TOKEN 282 +#define PARS_IF_TOKEN 283 +#define PARS_THEN_TOKEN 284 +#define PARS_ELSE_TOKEN 285 +#define PARS_ELSIF_TOKEN 286 +#define PARS_LOOP_TOKEN 287 +#define PARS_WHILE_TOKEN 288 +#define PARS_RETURN_TOKEN 289 +#define PARS_SELECT_TOKEN 290 +#define PARS_SUM_TOKEN 291 +#define PARS_COUNT_TOKEN 292 +#define PARS_DISTINCT_TOKEN 293 +#define PARS_FROM_TOKEN 294 +#define PARS_WHERE_TOKEN 295 +#define PARS_FOR_TOKEN 296 +#define PARS_DDOT_TOKEN 297 +#define PARS_READ_TOKEN 298 +#define PARS_ORDER_TOKEN 299 +#define PARS_BY_TOKEN 300 +#define PARS_ASC_TOKEN 301 +#define PARS_DESC_TOKEN 302 +#define PARS_INSERT_TOKEN 303 +#define PARS_INTO_TOKEN 304 +#define PARS_VALUES_TOKEN 305 +#define PARS_UPDATE_TOKEN 306 +#define PARS_SET_TOKEN 307 +#define PARS_DELETE_TOKEN 308 +#define PARS_CURRENT_TOKEN 309 +#define PARS_OF_TOKEN 310 +#define PARS_CREATE_TOKEN 311 +#define PARS_TABLE_TOKEN 312 +#define PARS_INDEX_TOKEN 313 +#define PARS_UNIQUE_TOKEN 314 +#define PARS_CLUSTERED_TOKEN 315 +#define PARS_DOES_NOT_FIT_IN_MEM_TOKEN 316 +#define PARS_ON_TOKEN 317 +#define PARS_ASSIGN_TOKEN 318 +#define PARS_DECLARE_TOKEN 319 +#define PARS_CURSOR_TOKEN 320 +#define PARS_SQL_TOKEN 321 +#define PARS_OPEN_TOKEN 322 +#define PARS_FETCH_TOKEN 323 +#define PARS_CLOSE_TOKEN 324 +#define PARS_NOTFOUND_TOKEN 325 +#define PARS_TO_CHAR_TOKEN 326 +#define PARS_TO_NUMBER_TOKEN 327 +#define PARS_TO_BINARY_TOKEN 328 +#define PARS_BINARY_TO_NUMBER_TOKEN 329 +#define PARS_SUBSTR_TOKEN 330 +#define PARS_REPLSTR_TOKEN 331 +#define PARS_CONCAT_TOKEN 332 +#define PARS_INSTR_TOKEN 333 +#define PARS_LENGTH_TOKEN 334 +#define PARS_SYSDATE_TOKEN 335 +#define PARS_PRINTF_TOKEN 336 +#define PARS_ASSERT_TOKEN 337 +#define PARS_RND_TOKEN 338 +#define PARS_RND_STR_TOKEN 339 +#define PARS_ROW_PRINTF_TOKEN 340 +#define PARS_COMMIT_TOKEN 341 +#define PARS_ROLLBACK_TOKEN 342 +#define PARS_WORK_TOKEN 343 +#define PARS_UNSIGNED_TOKEN 344 +#define PARS_EXIT_TOKEN 345 +#define PARS_FUNCTION_TOKEN 346 +#define PARS_LOCK_TOKEN 347 +#define PARS_SHARE_TOKEN 348 +#define PARS_MODE_TOKEN 349 +#define NEG 350 + + + + +/* Copy the first part of user declarations. */ +#line 13 "pars0grm.y" + +/* The value of the semantic attribute is a pointer to a query tree node +que_node_t */ + +#include "univ.i" +#include <math.h> /* Can't be before univ.i */ +#include "pars0pars.h" +#include "mem0mem.h" +#include "que0types.h" +#include "que0que.h" +#include "row0sel.h" + +#define YYSTYPE que_node_t* + +/* #define __STDC__ */ + +int +yylex(void); + + +/* Enabling traces. */ +#ifndef YYDEBUG +# define YYDEBUG 0 +#endif + +/* Enabling verbose error messages. */ +#ifdef YYERROR_VERBOSE +# undef YYERROR_VERBOSE +# define YYERROR_VERBOSE 1 +#else +# define YYERROR_VERBOSE 0 +#endif + +#if ! defined (YYSTYPE) && ! defined (YYSTYPE_IS_DECLARED) +typedef int YYSTYPE; +# define yystype YYSTYPE /* obsolescent; will be withdrawn */ +# define YYSTYPE_IS_DECLARED 1 +# define YYSTYPE_IS_TRIVIAL 1 +#endif + + + +/* Copy the second part of user declarations. */ + + +/* Line 213 of yacc.c. */ +#line 297 "pars0grm.c" + +#if ! defined (yyoverflow) || YYERROR_VERBOSE + +# ifndef YYFREE +# define YYFREE free +# endif +# ifndef YYMALLOC +# define YYMALLOC malloc +# endif + +/* The parser invokes alloca or malloc; define the necessary symbols. */ + +# ifdef YYSTACK_USE_ALLOCA +# if YYSTACK_USE_ALLOCA +# ifdef __GNUC__ +# define YYSTACK_ALLOC __builtin_alloca +# else +# define YYSTACK_ALLOC alloca +# endif +# endif +# endif + +# ifdef YYSTACK_ALLOC + /* Pacify GCC's `empty if-body' warning. */ +# define YYSTACK_FREE(Ptr) do { /* empty */; } while (0) +# else +# if defined (__STDC__) || defined (__cplusplus) +# include <stdlib.h> /* INFRINGES ON USER NAME SPACE */ +# define YYSIZE_T size_t +# endif +# define YYSTACK_ALLOC YYMALLOC +# define YYSTACK_FREE YYFREE +# endif +#endif /* ! defined (yyoverflow) || YYERROR_VERBOSE */ + + +#if (! defined (yyoverflow) \ + && (! defined (__cplusplus) \ + || (defined (YYSTYPE_IS_TRIVIAL) && YYSTYPE_IS_TRIVIAL))) + +/* A type that is properly aligned for any stack member. */ +union yyalloc +{ + short int yyss; + YYSTYPE yyvs; + }; + +/* The size of the maximum gap between one aligned stack and the next. */ +# define YYSTACK_GAP_MAXIMUM (sizeof (union yyalloc) - 1) + +/* The size of an array large to enough to hold all stacks, each with + N elements. */ +# define YYSTACK_BYTES(N) \ + ((N) * (sizeof (short int) + sizeof (YYSTYPE)) \ + + YYSTACK_GAP_MAXIMUM) + +/* Copy COUNT objects from FROM to TO. The source and destination do + not overlap. */ +# ifndef YYCOPY +# if defined (__GNUC__) && 1 < __GNUC__ +# define YYCOPY(To, From, Count) \ + __builtin_memcpy (To, From, (Count) * sizeof (*(From))) +# else +# define YYCOPY(To, From, Count) \ + do \ + { \ + register YYSIZE_T yyi; \ + for (yyi = 0; yyi < (Count); yyi++) \ + (To)[yyi] = (From)[yyi]; \ + } \ + while (0) +# endif +# endif + +/* Relocate STACK from its old location to the new one. The + local variables YYSIZE and YYSTACKSIZE give the old and new number of + elements in the stack, and YYPTR gives the new location of the + stack. Advance YYPTR to a properly aligned location for the next + stack. */ +# define YYSTACK_RELOCATE(Stack) \ + do \ + { \ + YYSIZE_T yynewbytes; \ + YYCOPY (&yyptr->Stack, Stack, yysize); \ + Stack = &yyptr->Stack; \ + yynewbytes = yystacksize * sizeof (*Stack) + YYSTACK_GAP_MAXIMUM; \ + yyptr += yynewbytes / sizeof (*yyptr); \ + } \ + while (0) + +#endif + +#if defined (__STDC__) || defined (__cplusplus) + typedef signed char yysigned_char; +#else + typedef short int yysigned_char; +#endif + +/* YYFINAL -- State number of the termination state. */ +#define YYFINAL 5 +/* YYLAST -- Last index in YYTABLE. */ +#define YYLAST 752 + +/* YYNTOKENS -- Number of terminals. */ +#define YYNTOKENS 111 +/* YYNNTS -- Number of nonterminals. */ +#define YYNNTS 70 +/* YYNRULES -- Number of rules. */ +#define YYNRULES 175 +/* YYNRULES -- Number of states. */ +#define YYNSTATES 339 + +/* YYTRANSLATE(YYLEX) -- Bison symbol number corresponding to YYLEX. */ +#define YYUNDEFTOK 2 +#define YYMAXUTOK 350 + +#define YYTRANSLATE(YYX) \ + ((unsigned int) (YYX) <= YYMAXUTOK ? yytranslate[YYX] : YYUNDEFTOK) + +/* YYTRANSLATE[YYLEX] -- Bison symbol number corresponding to YYLEX. */ +static const unsigned char yytranslate[] = +{ + 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 103, 2, 2, + 105, 106, 100, 99, 108, 98, 2, 101, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 104, + 96, 95, 97, 107, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 109, 2, 110, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 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, + 102 +}; + +#if YYDEBUG +/* YYPRHS[YYN] -- Index of the first RHS symbol of rule number YYN in + YYRHS. */ +static const unsigned short int yyprhs[] = +{ + 0, 0, 3, 6, 8, 11, 14, 17, 20, 23, + 26, 29, 32, 35, 38, 41, 44, 47, 50, 53, + 56, 59, 62, 65, 68, 71, 73, 76, 78, 83, + 85, 87, 89, 91, 93, 95, 97, 101, 105, 109, + 113, 116, 120, 124, 128, 132, 136, 140, 144, 148, + 152, 155, 159, 163, 165, 167, 169, 171, 173, 175, + 177, 179, 181, 183, 185, 186, 188, 192, 199, 204, + 206, 208, 210, 214, 216, 220, 221, 223, 227, 228, + 230, 234, 236, 241, 247, 252, 253, 255, 259, 261, + 265, 267, 268, 271, 272, 275, 276, 281, 282, 284, + 286, 287, 292, 301, 305, 311, 314, 318, 320, 324, + 329, 334, 337, 340, 344, 347, 350, 353, 357, 362, + 364, 367, 368, 371, 373, 381, 388, 399, 401, 403, + 406, 409, 414, 419, 425, 427, 431, 432, 436, 437, + 439, 440, 443, 444, 446, 454, 456, 460, 461, 463, + 464, 466, 477, 480, 483, 485, 487, 489, 491, 493, + 497, 501, 502, 504, 508, 512, 513, 515, 518, 525, + 530, 532, 534, 535, 537, 540 +}; + +/* YYRHS -- A `-1'-separated list of the rules' RHS. */ +static const short int yyrhs[] = +{ + 112, 0, -1, 180, 104, -1, 118, -1, 119, 104, + -1, 151, 104, -1, 152, 104, -1, 153, 104, -1, + 150, 104, -1, 154, 104, -1, 146, 104, -1, 133, + 104, -1, 135, 104, -1, 145, 104, -1, 143, 104, + -1, 144, 104, -1, 140, 104, -1, 141, 104, -1, + 155, 104, -1, 157, 104, -1, 156, 104, -1, 169, + 104, -1, 170, 104, -1, 164, 104, -1, 168, 104, + -1, 113, -1, 114, 113, -1, 9, -1, 116, 105, + 124, 106, -1, 3, -1, 4, -1, 5, -1, 6, + -1, 7, -1, 8, -1, 66, -1, 115, 99, 115, + -1, 115, 98, 115, -1, 115, 100, 115, -1, 115, + 101, 115, -1, 98, 115, -1, 105, 115, 106, -1, + 115, 95, 115, -1, 115, 96, 115, -1, 115, 97, + 115, -1, 115, 13, 115, -1, 115, 14, 115, -1, + 115, 15, 115, -1, 115, 10, 115, -1, 115, 11, + 115, -1, 12, 115, -1, 9, 103, 70, -1, 66, + 103, 70, -1, 71, -1, 72, -1, 73, -1, 74, + -1, 75, -1, 77, -1, 78, -1, 79, -1, 80, + -1, 83, -1, 84, -1, -1, 107, -1, 117, 108, + 107, -1, 109, 9, 105, 117, 106, 110, -1, 120, + 105, 124, 106, -1, 76, -1, 81, -1, 82, -1, + 9, 105, 106, -1, 9, -1, 122, 108, 9, -1, + -1, 9, -1, 123, 108, 9, -1, -1, 115, -1, + 124, 108, 115, -1, 115, -1, 37, 105, 100, 106, + -1, 37, 105, 38, 9, 106, -1, 36, 105, 115, + 106, -1, -1, 125, -1, 126, 108, 125, -1, 100, + -1, 126, 49, 123, -1, 126, -1, -1, 40, 115, + -1, -1, 41, 51, -1, -1, 92, 17, 93, 94, + -1, -1, 46, -1, 47, -1, -1, 44, 45, 9, + 131, -1, 35, 127, 39, 122, 128, 129, 130, 132, + -1, 48, 49, 9, -1, 134, 50, 105, 124, 106, + -1, 134, 133, -1, 9, 95, 115, -1, 136, -1, + 137, 108, 136, -1, 40, 54, 55, 9, -1, 51, + 9, 52, 137, -1, 139, 128, -1, 139, 138, -1, + 53, 39, 9, -1, 142, 128, -1, 142, 138, -1, + 85, 133, -1, 9, 63, 115, -1, 31, 115, 29, + 114, -1, 147, -1, 148, 147, -1, -1, 30, 114, + -1, 148, -1, 28, 115, 29, 114, 149, 27, 28, + -1, 33, 115, 32, 114, 27, 32, -1, 41, 9, + 17, 115, 42, 115, 32, 114, 27, 32, -1, 90, + -1, 34, -1, 67, 9, -1, 69, 9, -1, 68, + 9, 49, 123, -1, 68, 9, 49, 121, -1, 9, + 171, 160, 161, 162, -1, 158, -1, 159, 108, 158, + -1, -1, 105, 3, 106, -1, -1, 89, -1, -1, + 12, 8, -1, -1, 61, -1, 56, 57, 9, 105, + 159, 106, 163, -1, 9, -1, 165, 108, 9, -1, + -1, 59, -1, -1, 60, -1, 56, 166, 167, 58, + 9, 62, 9, 105, 165, 106, -1, 86, 88, -1, + 87, 88, -1, 21, -1, 22, -1, 24, -1, 19, + -1, 20, -1, 9, 17, 171, -1, 9, 18, 171, + -1, -1, 172, -1, 173, 108, 172, -1, 9, 171, + 104, -1, -1, 174, -1, 175, 174, -1, 64, 65, + 9, 25, 133, 104, -1, 64, 91, 9, 104, -1, + 176, -1, 177, -1, -1, 178, -1, 179, 178, -1, + 16, 9, 105, 173, 106, 25, 175, 179, 26, 114, + 27, -1 +}; + +/* YYRLINE[YYN] -- source line where rule number YYN was defined. */ +static const unsigned short int yyrline[] = +{ + 0, 138, 138, 141, 142, 143, 144, 145, 146, 147, + 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, + 158, 159, 160, 161, 162, 166, 167, 172, 173, 175, + 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, + 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, + 196, 197, 199, 204, 205, 206, 207, 209, 210, 211, + 212, 213, 214, 215, 218, 220, 221, 225, 230, 235, + 236, 237, 241, 245, 246, 251, 252, 253, 258, 259, + 260, 264, 265, 270, 276, 283, 284, 285, 290, 292, + 294, 298, 299, 303, 304, 309, 310, 315, 316, 317, + 321, 322, 327, 337, 342, 344, 349, 353, 354, 359, + 365, 372, 377, 382, 388, 393, 398, 403, 408, 414, + 415, 420, 421, 423, 427, 434, 440, 448, 452, 456, + 462, 468, 470, 475, 480, 481, 486, 487, 492, 493, + 499, 500, 506, 507, 513, 519, 520, 525, 526, 530, + 531, 535, 543, 548, 553, 554, 555, 556, 557, 561, + 564, 570, 571, 572, 577, 581, 583, 584, 588, 594, + 599, 600, 603, 605, 606, 610 +}; +#endif + +#if YYDEBUG || YYERROR_VERBOSE +/* YYTNME[SYMBOL-NUM] -- String name of the symbol SYMBOL-NUM. + First, the terminals, then, starting at YYNTOKENS, nonterminals. */ +static const char *const yytname[] = +{ + "$end", "error", "$undefined", "PARS_INT_LIT", "PARS_FLOAT_LIT", + "PARS_STR_LIT", "PARS_FIXBINARY_LIT", "PARS_BLOB_LIT", "PARS_NULL_LIT", + "PARS_ID_TOKEN", "PARS_AND_TOKEN", "PARS_OR_TOKEN", "PARS_NOT_TOKEN", + "PARS_GE_TOKEN", "PARS_LE_TOKEN", "PARS_NE_TOKEN", + "PARS_PROCEDURE_TOKEN", "PARS_IN_TOKEN", "PARS_OUT_TOKEN", + "PARS_BINARY_TOKEN", "PARS_BLOB_TOKEN", "PARS_INT_TOKEN", + "PARS_INTEGER_TOKEN", "PARS_FLOAT_TOKEN", "PARS_CHAR_TOKEN", + "PARS_IS_TOKEN", "PARS_BEGIN_TOKEN", "PARS_END_TOKEN", "PARS_IF_TOKEN", + "PARS_THEN_TOKEN", "PARS_ELSE_TOKEN", "PARS_ELSIF_TOKEN", + "PARS_LOOP_TOKEN", "PARS_WHILE_TOKEN", "PARS_RETURN_TOKEN", + "PARS_SELECT_TOKEN", "PARS_SUM_TOKEN", "PARS_COUNT_TOKEN", + "PARS_DISTINCT_TOKEN", "PARS_FROM_TOKEN", "PARS_WHERE_TOKEN", + "PARS_FOR_TOKEN", "PARS_DDOT_TOKEN", "PARS_READ_TOKEN", + "PARS_ORDER_TOKEN", "PARS_BY_TOKEN", "PARS_ASC_TOKEN", "PARS_DESC_TOKEN", + "PARS_INSERT_TOKEN", "PARS_INTO_TOKEN", "PARS_VALUES_TOKEN", + "PARS_UPDATE_TOKEN", "PARS_SET_TOKEN", "PARS_DELETE_TOKEN", + "PARS_CURRENT_TOKEN", "PARS_OF_TOKEN", "PARS_CREATE_TOKEN", + "PARS_TABLE_TOKEN", "PARS_INDEX_TOKEN", "PARS_UNIQUE_TOKEN", + "PARS_CLUSTERED_TOKEN", "PARS_DOES_NOT_FIT_IN_MEM_TOKEN", + "PARS_ON_TOKEN", "PARS_ASSIGN_TOKEN", "PARS_DECLARE_TOKEN", + "PARS_CURSOR_TOKEN", "PARS_SQL_TOKEN", "PARS_OPEN_TOKEN", + "PARS_FETCH_TOKEN", "PARS_CLOSE_TOKEN", "PARS_NOTFOUND_TOKEN", + "PARS_TO_CHAR_TOKEN", "PARS_TO_NUMBER_TOKEN", "PARS_TO_BINARY_TOKEN", + "PARS_BINARY_TO_NUMBER_TOKEN", "PARS_SUBSTR_TOKEN", "PARS_REPLSTR_TOKEN", + "PARS_CONCAT_TOKEN", "PARS_INSTR_TOKEN", "PARS_LENGTH_TOKEN", + "PARS_SYSDATE_TOKEN", "PARS_PRINTF_TOKEN", "PARS_ASSERT_TOKEN", + "PARS_RND_TOKEN", "PARS_RND_STR_TOKEN", "PARS_ROW_PRINTF_TOKEN", + "PARS_COMMIT_TOKEN", "PARS_ROLLBACK_TOKEN", "PARS_WORK_TOKEN", + "PARS_UNSIGNED_TOKEN", "PARS_EXIT_TOKEN", "PARS_FUNCTION_TOKEN", + "PARS_LOCK_TOKEN", "PARS_SHARE_TOKEN", "PARS_MODE_TOKEN", "'='", "'<'", + "'>'", "'-'", "'+'", "'*'", "'/'", "NEG", "'%'", "';'", "'('", "')'", + "'?'", "','", "'{'", "'}'", "$accept", "top_statement", "statement", + "statement_list", "exp", "function_name", "question_mark_list", + "stored_procedure_call", "predefined_procedure_call", + "predefined_procedure_name", "user_function_call", "table_list", + "variable_list", "exp_list", "select_item", "select_item_list", + "select_list", "search_condition", "for_update_clause", + "lock_shared_clause", "order_direction", "order_by_clause", + "select_statement", "insert_statement_start", "insert_statement", + "column_assignment", "column_assignment_list", "cursor_positioned", + "update_statement_start", "update_statement_searched", + "update_statement_positioned", "delete_statement_start", + "delete_statement_searched", "delete_statement_positioned", + "row_printf_statement", "assignment_statement", "elsif_element", + "elsif_list", "else_part", "if_statement", "while_statement", + "for_statement", "exit_statement", "return_statement", + "open_cursor_statement", "close_cursor_statement", "fetch_statement", + "column_def", "column_def_list", "opt_column_len", "opt_unsigned", + "opt_not_null", "not_fit_in_memory", "create_table", "column_list", + "unique_def", "clustered_def", "create_index", "commit_statement", + "rollback_statement", "type_name", "parameter_declaration", + "parameter_declaration_list", "variable_declaration", + "variable_declaration_list", "cursor_declaration", + "function_declaration", "declaration", "declaration_list", + "procedure_definition", 0 +}; +#endif + +# ifdef YYPRINT +/* YYTOKNUM[YYLEX-NUM] -- Internal token number corresponding to + token YYLEX-NUM. */ +static const unsigned short int yytoknum[] = +{ + 0, 256, 257, 258, 259, 260, 261, 262, 263, 264, + 265, 266, 267, 268, 269, 270, 271, 272, 273, 274, + 275, 276, 277, 278, 279, 280, 281, 282, 283, 284, + 285, 286, 287, 288, 289, 290, 291, 292, 293, 294, + 295, 296, 297, 298, 299, 300, 301, 302, 303, 304, + 305, 306, 307, 308, 309, 310, 311, 312, 313, 314, + 315, 316, 317, 318, 319, 320, 321, 322, 323, 324, + 325, 326, 327, 328, 329, 330, 331, 332, 333, 334, + 335, 336, 337, 338, 339, 340, 341, 342, 343, 344, + 345, 346, 347, 348, 349, 61, 60, 62, 45, 43, + 42, 47, 350, 37, 59, 40, 41, 63, 44, 123, + 125 +}; +# endif + +/* YYR1[YYN] -- Symbol number of symbol that rule YYN derives. */ +static const unsigned char yyr1[] = +{ + 0, 111, 112, 113, 113, 113, 113, 113, 113, 113, + 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, + 113, 113, 113, 113, 113, 114, 114, 115, 115, 115, + 115, 115, 115, 115, 115, 115, 115, 115, 115, 115, + 115, 115, 115, 115, 115, 115, 115, 115, 115, 115, + 115, 115, 115, 116, 116, 116, 116, 116, 116, 116, + 116, 116, 116, 116, 117, 117, 117, 118, 119, 120, + 120, 120, 121, 122, 122, 123, 123, 123, 124, 124, + 124, 125, 125, 125, 125, 126, 126, 126, 127, 127, + 127, 128, 128, 129, 129, 130, 130, 131, 131, 131, + 132, 132, 133, 134, 135, 135, 136, 137, 137, 138, + 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, + 148, 149, 149, 149, 150, 151, 152, 153, 154, 155, + 156, 157, 157, 158, 159, 159, 160, 160, 161, 161, + 162, 162, 163, 163, 164, 165, 165, 166, 166, 167, + 167, 168, 169, 170, 171, 171, 171, 171, 171, 172, + 172, 173, 173, 173, 174, 175, 175, 175, 176, 177, + 178, 178, 179, 179, 179, 180 +}; + +/* YYR2[YYN] -- Number of symbols composing right hand side of rule YYN. */ +static const unsigned char yyr2[] = +{ + 0, 2, 2, 1, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 1, 2, 1, 4, 1, + 1, 1, 1, 1, 1, 1, 3, 3, 3, 3, + 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 2, 3, 3, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 0, 1, 3, 6, 4, 1, + 1, 1, 3, 1, 3, 0, 1, 3, 0, 1, + 3, 1, 4, 5, 4, 0, 1, 3, 1, 3, + 1, 0, 2, 0, 2, 0, 4, 0, 1, 1, + 0, 4, 8, 3, 5, 2, 3, 1, 3, 4, + 4, 2, 2, 3, 2, 2, 2, 3, 4, 1, + 2, 0, 2, 1, 7, 6, 10, 1, 1, 2, + 2, 4, 4, 5, 1, 3, 0, 3, 0, 1, + 0, 2, 0, 1, 7, 1, 3, 0, 1, 0, + 1, 10, 2, 2, 1, 1, 1, 1, 1, 3, + 3, 0, 1, 3, 3, 0, 1, 2, 6, 4, + 1, 1, 0, 1, 2, 11 +}; + +/* YYDEFACT[STATE-NAME] -- Default rule to reduce with in state + STATE-NUM when YYTABLE doesn't specify something else to do. Zero + means the default is an error. */ +static const unsigned char yydefact[] = +{ + 0, 0, 0, 0, 0, 1, 2, 161, 0, 162, + 0, 0, 0, 0, 0, 157, 158, 154, 155, 156, + 159, 160, 165, 163, 0, 166, 172, 0, 0, 167, + 170, 171, 173, 0, 164, 0, 0, 0, 174, 0, + 0, 0, 0, 0, 128, 85, 0, 0, 0, 0, + 147, 0, 0, 0, 69, 70, 71, 0, 0, 0, + 127, 0, 25, 0, 3, 0, 0, 0, 0, 0, + 91, 0, 0, 91, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 169, 0, 29, 30, 31, 32, 33, 34, 27, + 0, 35, 53, 54, 55, 56, 57, 58, 59, 60, + 61, 62, 63, 0, 0, 0, 0, 0, 0, 0, + 88, 81, 86, 90, 0, 0, 0, 0, 0, 0, + 148, 149, 129, 0, 130, 116, 152, 153, 0, 175, + 26, 4, 78, 11, 0, 105, 12, 0, 111, 112, + 16, 17, 114, 115, 14, 15, 13, 10, 8, 5, + 6, 7, 9, 18, 20, 19, 23, 24, 21, 22, + 0, 117, 0, 50, 0, 40, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 78, 0, 0, 0, 75, 0, 0, 0, 103, 0, + 113, 0, 150, 0, 75, 64, 79, 0, 78, 0, + 92, 168, 51, 52, 41, 48, 49, 45, 46, 47, + 121, 42, 43, 44, 37, 36, 38, 39, 0, 0, + 0, 0, 0, 76, 89, 87, 73, 91, 0, 0, + 107, 110, 0, 0, 76, 132, 131, 65, 0, 68, + 0, 0, 0, 0, 0, 119, 123, 0, 28, 0, + 84, 0, 82, 0, 0, 0, 93, 0, 0, 0, + 0, 134, 0, 0, 0, 0, 0, 80, 104, 109, + 122, 0, 120, 0, 125, 83, 77, 74, 0, 95, + 0, 106, 108, 136, 142, 0, 0, 72, 67, 66, + 0, 124, 94, 0, 100, 0, 0, 138, 143, 144, + 135, 0, 118, 0, 0, 102, 0, 0, 139, 140, + 0, 0, 0, 0, 137, 0, 133, 145, 0, 96, + 97, 126, 141, 151, 0, 98, 99, 101, 146 +}; + +/* YYDEFGOTO[NTERM-NUM]. */ +static const short int yydefgoto[] = +{ + -1, 2, 62, 63, 206, 116, 248, 64, 65, 66, + 245, 237, 234, 207, 122, 123, 124, 148, 289, 304, + 337, 315, 67, 68, 69, 240, 241, 149, 70, 71, + 72, 73, 74, 75, 76, 77, 255, 256, 257, 78, + 79, 80, 81, 82, 83, 84, 85, 271, 272, 307, + 319, 326, 309, 86, 328, 131, 203, 87, 88, 89, + 20, 9, 10, 25, 26, 30, 31, 32, 33, 3 +}; + +/* YYPACT[STATE-NUM] -- Index in YYTABLE of the portion describing + STATE-NUM. */ +#define YYPACT_NINF -177 +static const short int yypact[] = +{ + 28, 38, 54, -46, -29, -177, -177, 56, 50, -177, + -75, 8, 8, 46, 56, -177, -177, -177, -177, -177, + -177, -177, 63, -177, 8, -177, 2, -26, -51, -177, + -177, -177, -177, -13, -177, 71, 72, 587, -177, 57, + -21, 26, 272, 272, -177, 13, 91, 55, 96, 67, + -22, 99, 100, 103, -177, -177, -177, 75, 29, 35, + -177, 116, -177, 396, -177, 22, 23, 27, -9, 30, + 87, 31, 32, 87, 47, 49, 52, 58, 59, 60, + 61, 62, 65, 66, 74, 77, 78, 86, 89, 102, + 75, -177, 272, -177, -177, -177, -177, -177, -177, 39, + 272, 51, -177, -177, -177, -177, -177, -177, -177, -177, + -177, -177, -177, 272, 272, 361, 25, 489, 45, 90, + -177, 651, -177, -39, 93, 142, 124, 108, 152, 170, + -177, 131, -177, 143, -177, -177, -177, -177, 98, -177, + -177, -177, 272, -177, 110, -177, -177, 256, -177, -177, + -177, -177, -177, -177, -177, -177, -177, -177, -177, -177, + -177, -177, -177, -177, -177, -177, -177, -177, -177, -177, + 112, 651, 137, 101, 147, 204, 88, 272, 272, 272, + 272, 272, 587, 272, 272, 272, 272, 272, 272, 272, + 272, 587, 272, -30, 211, 168, 212, 272, -177, 213, + -177, 118, -177, 167, 217, 122, 651, -63, 272, 175, + 651, -177, -177, -177, -177, 101, 101, 21, 21, 651, + 332, 21, 21, 21, -6, -6, 204, 204, -60, 460, + 198, 222, 126, -177, 125, -177, -177, -33, 584, 140, + -177, 128, 228, 229, 139, -177, 125, -177, -53, -177, + 272, -49, 240, 587, 272, -177, 224, 226, -177, 225, + -177, 150, -177, 258, 272, 260, 230, 272, 272, 213, + 8, -177, -45, 208, 166, 164, 176, 651, -177, -177, + 587, 631, -177, 254, -177, -177, -177, -177, 234, 194, + 638, 651, -177, 182, 227, 228, 280, -177, -177, -177, + 587, -177, -177, 273, 247, 587, 289, 214, -177, -177, + -177, 195, 587, 209, 261, -177, 524, 199, -177, 295, + 292, 215, 299, 279, -177, 304, -177, -177, -44, -177, + -8, -177, -177, -177, 305, -177, -177, -177, -177 +}; + +/* YYPGOTO[NTERM-NUM]. */ +static const short int yypgoto[] = +{ + -177, -177, -62, -176, -40, -177, -177, -177, -177, -177, + -177, -177, 109, -166, 120, -177, -177, -69, -177, -177, + -177, -177, -34, -177, -177, 48, -177, 243, -177, -177, + -177, -177, -177, -177, -177, -177, 64, -177, -177, -177, + -177, -177, -177, -177, -177, -177, -177, 24, -177, -177, + -177, -177, -177, -177, -177, -177, -177, -177, -177, -177, + -12, 307, -177, 297, -177, -177, -177, 285, -177, -177 +}; + +/* YYTABLE[YYPACT[STATE-NUM]]. What to do in state STATE-NUM. If + positive, shift that token. If negative, reduce the rule which + number is the opposite. If zero, do what YYDEFACT says. + If YYTABLE_NINF, syntax error. */ +#define YYTABLE_NINF -1 +static const unsigned short int yytable[] = +{ + 21, 140, 115, 117, 152, 121, 220, 264, 231, 181, + 194, 24, 27, 37, 35, 229, 93, 94, 95, 96, + 97, 98, 99, 135, 228, 100, 45, 15, 16, 17, + 18, 13, 19, 14, 145, 129, 181, 130, 335, 336, + 36, 144, 251, 249, 1, 250, 258, 4, 250, 118, + 119, 28, 171, 275, 5, 276, 170, 278, 6, 250, + 173, 294, 333, 295, 334, 8, 28, 11, 12, 195, + 232, 22, 24, 175, 176, 265, 7, 280, 34, 101, + 39, 40, 90, 91, 102, 103, 104, 105, 106, 92, + 107, 108, 109, 110, 188, 189, 111, 112, 177, 178, + 125, 179, 180, 181, 126, 127, 128, 210, 132, 133, + 45, 113, 134, 120, 179, 180, 181, 136, 114, 186, + 187, 188, 189, 137, 312, 138, 141, 147, 142, 316, + 190, 143, 196, 198, 146, 150, 151, 215, 216, 217, + 218, 219, 172, 221, 222, 223, 224, 225, 226, 227, + 192, 154, 230, 155, 174, 121, 156, 238, 140, 197, + 199, 200, 157, 158, 159, 160, 161, 140, 266, 162, + 163, 93, 94, 95, 96, 97, 98, 99, 164, 201, + 100, 165, 166, 183, 184, 185, 186, 187, 188, 189, + 167, 202, 204, 168, 214, 193, 183, 184, 185, 186, + 187, 188, 189, 205, 118, 119, 169, 212, 177, 178, + 277, 179, 180, 181, 281, 208, 211, 213, 140, 181, + 233, 236, 239, 242, 210, 243, 244, 290, 291, 247, + 252, 261, 262, 263, 101, 268, 269, 270, 273, 102, + 103, 104, 105, 106, 274, 107, 108, 109, 110, 279, + 140, 111, 112, 283, 140, 254, 285, 284, 293, 93, + 94, 95, 96, 97, 98, 99, 113, 286, 100, 287, + 296, 288, 297, 114, 298, 93, 94, 95, 96, 97, + 98, 99, 301, 299, 100, 302, 303, 306, 308, 311, + 313, 314, 317, 183, 184, 185, 186, 187, 188, 189, + 320, 327, 321, 318, 260, 324, 322, 325, 330, 329, + 209, 331, 332, 246, 338, 235, 153, 292, 38, 310, + 282, 23, 101, 29, 0, 0, 0, 102, 103, 104, + 105, 106, 0, 107, 108, 109, 110, 0, 101, 111, + 112, 41, 0, 102, 103, 104, 105, 106, 0, 107, + 108, 109, 110, 0, 113, 111, 112, 0, 0, 0, + 42, 114, 253, 254, 0, 43, 44, 45, 0, 0, + 113, 177, 178, 46, 179, 180, 181, 114, 0, 0, + 47, 0, 0, 48, 0, 49, 0, 0, 50, 0, + 182, 0, 0, 0, 0, 0, 0, 0, 0, 51, + 52, 53, 0, 0, 0, 41, 0, 0, 54, 0, + 0, 0, 0, 55, 56, 0, 0, 57, 58, 59, + 0, 0, 60, 139, 42, 0, 0, 0, 0, 43, + 44, 45, 0, 0, 0, 0, 0, 46, 0, 0, + 0, 61, 0, 0, 47, 0, 0, 48, 0, 49, + 0, 0, 50, 0, 0, 0, 183, 184, 185, 186, + 187, 188, 189, 51, 52, 53, 0, 0, 0, 41, + 0, 0, 54, 0, 0, 0, 0, 55, 56, 0, + 0, 57, 58, 59, 0, 0, 60, 259, 42, 0, + 0, 0, 0, 43, 44, 45, 0, 0, 0, 177, + 178, 46, 179, 180, 181, 61, 0, 0, 47, 0, + 0, 48, 0, 49, 0, 0, 50, 0, 0, 0, + 0, 191, 0, 0, 0, 0, 0, 51, 52, 53, + 0, 0, 0, 41, 0, 0, 54, 0, 0, 0, + 0, 55, 56, 0, 0, 57, 58, 59, 0, 0, + 60, 323, 42, 0, 0, 0, 0, 43, 44, 45, + 0, 0, 0, 0, 0, 46, 0, 0, 0, 61, + 0, 0, 47, 0, 0, 48, 0, 49, 0, 0, + 50, 0, 0, 0, 183, 184, 185, 186, 187, 188, + 189, 51, 52, 53, 177, 178, 41, 179, 180, 181, + 54, 0, 0, 0, 0, 55, 56, 0, 0, 57, + 58, 59, 0, 0, 60, 42, 0, 0, 0, 0, + 43, 44, 45, 0, 0, 0, 267, 0, 46, 0, + 0, 0, 0, 61, 0, 47, 0, 0, 48, 0, + 49, 177, 178, 50, 179, 180, 181, 0, 177, 178, + 0, 179, 180, 181, 51, 52, 53, 0, 0, 0, + 300, 177, 178, 54, 179, 180, 181, 0, 55, 56, + 305, 0, 57, 58, 59, 0, 0, 60, 0, 183, + 184, 185, 186, 187, 188, 189, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 61, 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, 183, 184, 185, 186, + 187, 188, 189, 183, 184, 185, 186, 187, 188, 189, + 0, 0, 0, 0, 0, 0, 183, 184, 185, 186, + 187, 188, 189 +}; + +static const short int yycheck[] = +{ + 12, 63, 42, 43, 73, 45, 182, 40, 38, 15, + 49, 9, 24, 26, 65, 191, 3, 4, 5, 6, + 7, 8, 9, 57, 190, 12, 35, 19, 20, 21, + 22, 106, 24, 108, 68, 57, 15, 59, 46, 47, + 91, 50, 208, 106, 16, 108, 106, 9, 108, 36, + 37, 64, 92, 106, 0, 108, 90, 106, 104, 108, + 100, 106, 106, 108, 108, 9, 64, 17, 18, 108, + 100, 25, 9, 113, 114, 108, 105, 253, 104, 66, + 9, 9, 25, 104, 71, 72, 73, 74, 75, 63, + 77, 78, 79, 80, 100, 101, 83, 84, 10, 11, + 9, 13, 14, 15, 49, 9, 39, 147, 9, 9, + 35, 98, 9, 100, 13, 14, 15, 88, 105, 98, + 99, 100, 101, 88, 300, 9, 104, 40, 105, 305, + 105, 104, 39, 9, 104, 104, 104, 177, 178, 179, + 180, 181, 103, 183, 184, 185, 186, 187, 188, 189, + 105, 104, 192, 104, 103, 195, 104, 197, 220, 17, + 52, 9, 104, 104, 104, 104, 104, 229, 237, 104, + 104, 3, 4, 5, 6, 7, 8, 9, 104, 9, + 12, 104, 104, 95, 96, 97, 98, 99, 100, 101, + 104, 60, 49, 104, 106, 105, 95, 96, 97, 98, + 99, 100, 101, 105, 36, 37, 104, 70, 10, 11, + 250, 13, 14, 15, 254, 105, 104, 70, 280, 15, + 9, 9, 9, 105, 264, 58, 9, 267, 268, 107, + 55, 9, 106, 108, 66, 95, 108, 9, 9, 71, + 72, 73, 74, 75, 105, 77, 78, 79, 80, 9, + 312, 83, 84, 27, 316, 31, 106, 32, 270, 3, + 4, 5, 6, 7, 8, 9, 98, 9, 12, 9, + 62, 41, 106, 105, 110, 3, 4, 5, 6, 7, + 8, 9, 28, 107, 12, 51, 92, 105, 61, 9, + 17, 44, 3, 95, 96, 97, 98, 99, 100, 101, + 105, 9, 93, 89, 106, 106, 45, 12, 9, 94, + 54, 32, 8, 204, 9, 195, 73, 269, 33, 295, + 256, 14, 66, 26, -1, -1, -1, 71, 72, 73, + 74, 75, -1, 77, 78, 79, 80, -1, 66, 83, + 84, 9, -1, 71, 72, 73, 74, 75, -1, 77, + 78, 79, 80, -1, 98, 83, 84, -1, -1, -1, + 28, 105, 30, 31, -1, 33, 34, 35, -1, -1, + 98, 10, 11, 41, 13, 14, 15, 105, -1, -1, + 48, -1, -1, 51, -1, 53, -1, -1, 56, -1, + 29, -1, -1, -1, -1, -1, -1, -1, -1, 67, + 68, 69, -1, -1, -1, 9, -1, -1, 76, -1, + -1, -1, -1, 81, 82, -1, -1, 85, 86, 87, + -1, -1, 90, 27, 28, -1, -1, -1, -1, 33, + 34, 35, -1, -1, -1, -1, -1, 41, -1, -1, + -1, 109, -1, -1, 48, -1, -1, 51, -1, 53, + -1, -1, 56, -1, -1, -1, 95, 96, 97, 98, + 99, 100, 101, 67, 68, 69, -1, -1, -1, 9, + -1, -1, 76, -1, -1, -1, -1, 81, 82, -1, + -1, 85, 86, 87, -1, -1, 90, 27, 28, -1, + -1, -1, -1, 33, 34, 35, -1, -1, -1, 10, + 11, 41, 13, 14, 15, 109, -1, -1, 48, -1, + -1, 51, -1, 53, -1, -1, 56, -1, -1, -1, + -1, 32, -1, -1, -1, -1, -1, 67, 68, 69, + -1, -1, -1, 9, -1, -1, 76, -1, -1, -1, + -1, 81, 82, -1, -1, 85, 86, 87, -1, -1, + 90, 27, 28, -1, -1, -1, -1, 33, 34, 35, + -1, -1, -1, -1, -1, 41, -1, -1, -1, 109, + -1, -1, 48, -1, -1, 51, -1, 53, -1, -1, + 56, -1, -1, -1, 95, 96, 97, 98, 99, 100, + 101, 67, 68, 69, 10, 11, 9, 13, 14, 15, + 76, -1, -1, -1, -1, 81, 82, -1, -1, 85, + 86, 87, -1, -1, 90, 28, -1, -1, -1, -1, + 33, 34, 35, -1, -1, -1, 42, -1, 41, -1, + -1, -1, -1, 109, -1, 48, -1, -1, 51, -1, + 53, 10, 11, 56, 13, 14, 15, -1, 10, 11, + -1, 13, 14, 15, 67, 68, 69, -1, -1, -1, + 29, 10, 11, 76, 13, 14, 15, -1, 81, 82, + 32, -1, 85, 86, 87, -1, -1, 90, -1, 95, + 96, 97, 98, 99, 100, 101, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, 109, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, 95, 96, 97, 98, + 99, 100, 101, 95, 96, 97, 98, 99, 100, 101, + -1, -1, -1, -1, -1, -1, 95, 96, 97, 98, + 99, 100, 101 +}; + +/* YYSTOS[STATE-NUM] -- The (internal number of the) accessing + symbol of state STATE-NUM. */ +static const unsigned char yystos[] = +{ + 0, 16, 112, 180, 9, 0, 104, 105, 9, 172, + 173, 17, 18, 106, 108, 19, 20, 21, 22, 24, + 171, 171, 25, 172, 9, 174, 175, 171, 64, 174, + 176, 177, 178, 179, 104, 65, 91, 26, 178, 9, + 9, 9, 28, 33, 34, 35, 41, 48, 51, 53, + 56, 67, 68, 69, 76, 81, 82, 85, 86, 87, + 90, 109, 113, 114, 118, 119, 120, 133, 134, 135, + 139, 140, 141, 142, 143, 144, 145, 146, 150, 151, + 152, 153, 154, 155, 156, 157, 164, 168, 169, 170, + 25, 104, 63, 3, 4, 5, 6, 7, 8, 9, + 12, 66, 71, 72, 73, 74, 75, 77, 78, 79, + 80, 83, 84, 98, 105, 115, 116, 115, 36, 37, + 100, 115, 125, 126, 127, 9, 49, 9, 39, 57, + 59, 166, 9, 9, 9, 133, 88, 88, 9, 27, + 113, 104, 105, 104, 50, 133, 104, 40, 128, 138, + 104, 104, 128, 138, 104, 104, 104, 104, 104, 104, + 104, 104, 104, 104, 104, 104, 104, 104, 104, 104, + 133, 115, 103, 115, 103, 115, 115, 10, 11, 13, + 14, 15, 29, 95, 96, 97, 98, 99, 100, 101, + 105, 32, 105, 105, 49, 108, 39, 17, 9, 52, + 9, 9, 60, 167, 49, 105, 115, 124, 105, 54, + 115, 104, 70, 70, 106, 115, 115, 115, 115, 115, + 114, 115, 115, 115, 115, 115, 115, 115, 124, 114, + 115, 38, 100, 9, 123, 125, 9, 122, 115, 9, + 136, 137, 105, 58, 9, 121, 123, 107, 117, 106, + 108, 124, 55, 30, 31, 147, 148, 149, 106, 27, + 106, 9, 106, 108, 40, 108, 128, 42, 95, 108, + 9, 158, 159, 9, 105, 106, 108, 115, 106, 9, + 114, 115, 147, 27, 32, 106, 9, 9, 41, 129, + 115, 115, 136, 171, 106, 108, 62, 106, 110, 107, + 29, 28, 51, 92, 130, 32, 105, 160, 61, 163, + 158, 9, 114, 17, 44, 132, 114, 3, 89, 161, + 105, 93, 45, 27, 106, 12, 162, 9, 165, 94, + 9, 32, 8, 106, 108, 46, 47, 131, 9 +}; + +#if ! defined (YYSIZE_T) && defined (__SIZE_TYPE__) +# define YYSIZE_T __SIZE_TYPE__ +#endif +#if ! defined (YYSIZE_T) && defined (size_t) +# define YYSIZE_T size_t +#endif +#if ! defined (YYSIZE_T) +# if defined (__STDC__) || defined (__cplusplus) +# include <stddef.h> /* INFRINGES ON USER NAME SPACE */ +# define YYSIZE_T size_t +# endif +#endif +#if ! defined (YYSIZE_T) +# define YYSIZE_T unsigned int +#endif + +#define yyerrok (yyerrstatus = 0) +#define yyclearin (yychar = YYEMPTY) +#define YYEMPTY (-2) +#define YYEOF 0 + +#define YYACCEPT goto yyacceptlab +#define YYABORT goto yyabortlab +#define YYERROR goto yyerrorlab + + +/* Like YYERROR except do call yyerror. This remains here temporarily + to ease the transition to the new meaning of YYERROR, for GCC. + Once GCC version 2 has supplanted version 1, this can go. */ + +#define YYFAIL goto yyerrlab + +#define YYRECOVERING() (!!yyerrstatus) + +#define YYBACKUP(Token, Value) \ +do \ + if (yychar == YYEMPTY && yylen == 1) \ + { \ + yychar = (Token); \ + yylval = (Value); \ + yytoken = YYTRANSLATE (yychar); \ + YYPOPSTACK; \ + goto yybackup; \ + } \ + else \ + { \ + yyerror ("syntax error: cannot back up");\ + YYERROR; \ + } \ +while (0) + + +#define YYTERROR 1 +#define YYERRCODE 256 + + +/* YYLLOC_DEFAULT -- Set CURRENT to span from RHS[1] to RHS[N]. + If N is 0, then set CURRENT to the empty location which ends + the previous symbol: RHS[0] (always defined). */ + +#define YYRHSLOC(Rhs, K) ((Rhs)[K]) +#ifndef YYLLOC_DEFAULT +# define YYLLOC_DEFAULT(Current, Rhs, N) \ + do \ + if (N) \ + { \ + (Current).first_line = YYRHSLOC (Rhs, 1).first_line; \ + (Current).first_column = YYRHSLOC (Rhs, 1).first_column; \ + (Current).last_line = YYRHSLOC (Rhs, N).last_line; \ + (Current).last_column = YYRHSLOC (Rhs, N).last_column; \ + } \ + else \ + { \ + (Current).first_line = (Current).last_line = \ + YYRHSLOC (Rhs, 0).last_line; \ + (Current).first_column = (Current).last_column = \ + YYRHSLOC (Rhs, 0).last_column; \ + } \ + while (0) +#endif + + +/* YY_LOCATION_PRINT -- Print the location on the stream. + This macro was not mandated originally: define only if we know + we won't break user code: when these are the locations we know. */ + +#ifndef YY_LOCATION_PRINT +# if YYLTYPE_IS_TRIVIAL +# define YY_LOCATION_PRINT(File, Loc) \ + fprintf (File, "%d.%d-%d.%d", \ + (Loc).first_line, (Loc).first_column, \ + (Loc).last_line, (Loc).last_column) +# else +# define YY_LOCATION_PRINT(File, Loc) ((void) 0) +# endif +#endif + + +/* YYLEX -- calling `yylex' with the right arguments. */ + +#ifdef YYLEX_PARAM +# define YYLEX yylex (YYLEX_PARAM) +#else +# define YYLEX yylex () +#endif + +/* Enable debugging if requested. */ +#if YYDEBUG + +# ifndef YYFPRINTF +# include <stdio.h> /* INFRINGES ON USER NAME SPACE */ +# define YYFPRINTF fprintf +# endif + +# define YYDPRINTF(Args) \ +do { \ + if (yydebug) \ + YYFPRINTF Args; \ +} while (0) + +# define YY_SYMBOL_PRINT(Title, Type, Value, Location) \ +do { \ + if (yydebug) \ + { \ + YYFPRINTF (stderr, "%s ", Title); \ + yysymprint (stderr, \ + Type, Value); \ + YYFPRINTF (stderr, "\n"); \ + } \ +} while (0) + +/*------------------------------------------------------------------. +| yy_stack_print -- Print the state stack from its BOTTOM up to its | +| TOP (included). | +`------------------------------------------------------------------*/ + +#if defined (__STDC__) || defined (__cplusplus) +static void +yy_stack_print (short int *bottom, short int *top) +#else +static void +yy_stack_print (bottom, top) + short int *bottom; + short int *top; +#endif +{ + YYFPRINTF (stderr, "Stack now"); + for (/* Nothing. */; bottom <= top; ++bottom) + YYFPRINTF (stderr, " %d", *bottom); + YYFPRINTF (stderr, "\n"); +} + +# define YY_STACK_PRINT(Bottom, Top) \ +do { \ + if (yydebug) \ + yy_stack_print ((Bottom), (Top)); \ +} while (0) + + +/*------------------------------------------------. +| Report that the YYRULE is going to be reduced. | +`------------------------------------------------*/ + +#if defined (__STDC__) || defined (__cplusplus) +static void +yy_reduce_print (int yyrule) +#else +static void +yy_reduce_print (yyrule) + int yyrule; +#endif +{ + int yyi; + unsigned int yylno = yyrline[yyrule]; + YYFPRINTF (stderr, "Reducing stack by rule %d (line %u), ", + yyrule - 1, yylno); + /* Print the symbols being reduced, and their result. */ + for (yyi = yyprhs[yyrule]; 0 <= yyrhs[yyi]; yyi++) + YYFPRINTF (stderr, "%s ", yytname [yyrhs[yyi]]); + YYFPRINTF (stderr, "-> %s\n", yytname [yyr1[yyrule]]); +} + +# define YY_REDUCE_PRINT(Rule) \ +do { \ + if (yydebug) \ + yy_reduce_print (Rule); \ +} while (0) + +/* Nonzero means print parse trace. It is left uninitialized so that + multiple parsers can coexist. */ +int yydebug; +#else /* !YYDEBUG */ +# define YYDPRINTF(Args) +# define YY_SYMBOL_PRINT(Title, Type, Value, Location) +# define YY_STACK_PRINT(Bottom, Top) +# define YY_REDUCE_PRINT(Rule) +#endif /* !YYDEBUG */ + + +/* YYINITDEPTH -- initial size of the parser's stacks. */ +#ifndef YYINITDEPTH +# define YYINITDEPTH 200 +#endif + +/* YYMAXDEPTH -- maximum size the stacks can grow to (effective only + if the built-in stack extension method is used). + + Do not make this value too large; the results are undefined if + SIZE_MAX < YYSTACK_BYTES (YYMAXDEPTH) + evaluated with infinite-precision integer arithmetic. */ + +#ifndef YYMAXDEPTH +# define YYMAXDEPTH 10000 +#endif + + + +#if YYERROR_VERBOSE + +# ifndef yystrlen +# if defined (__GLIBC__) && defined (_STRING_H) +# define yystrlen strlen +# else +/* Return the length of YYSTR. */ +static YYSIZE_T +# if defined (__STDC__) || defined (__cplusplus) +yystrlen (const char *yystr) +# else +yystrlen (yystr) + const char *yystr; +# endif +{ + register const char *yys = yystr; + + while (*yys++ != '\0') + continue; + + return yys - yystr - 1; +} +# endif +# endif + +# ifndef yystpcpy +# if defined (__GLIBC__) && defined (_STRING_H) && defined (_GNU_SOURCE) +# define yystpcpy stpcpy +# else +/* Copy YYSRC to YYDEST, returning the address of the terminating '\0' in + YYDEST. */ +static char * +# if defined (__STDC__) || defined (__cplusplus) +yystpcpy (char *yydest, const char *yysrc) +# else +yystpcpy (yydest, yysrc) + char *yydest; + const char *yysrc; +# endif +{ + register char *yyd = yydest; + register const char *yys = yysrc; + + while ((*yyd++ = *yys++) != '\0') + continue; + + return yyd - 1; +} +# endif +# endif + +#endif /* !YYERROR_VERBOSE */ + + + +#if YYDEBUG +/*--------------------------------. +| Print this symbol on YYOUTPUT. | +`--------------------------------*/ + +#if defined (__STDC__) || defined (__cplusplus) +static void +yysymprint (FILE *yyoutput, int yytype, YYSTYPE *yyvaluep) +#else +static void +yysymprint (yyoutput, yytype, yyvaluep) + FILE *yyoutput; + int yytype; + YYSTYPE *yyvaluep; +#endif +{ + /* Pacify ``unused variable'' warnings. */ + (void) yyvaluep; + + if (yytype < YYNTOKENS) + YYFPRINTF (yyoutput, "token %s (", yytname[yytype]); + else + YYFPRINTF (yyoutput, "nterm %s (", yytname[yytype]); + + +# ifdef YYPRINT + if (yytype < YYNTOKENS) + YYPRINT (yyoutput, yytoknum[yytype], *yyvaluep); +# endif + switch (yytype) + { + default: + break; + } + YYFPRINTF (yyoutput, ")"); +} + +#endif /* ! YYDEBUG */ +/*-----------------------------------------------. +| Release the memory associated to this symbol. | +`-----------------------------------------------*/ + +#if defined (__STDC__) || defined (__cplusplus) +static void +yydestruct (const char *yymsg, int yytype, YYSTYPE *yyvaluep) +#else +static void +yydestruct (yymsg, yytype, yyvaluep) + const char *yymsg; + int yytype; + YYSTYPE *yyvaluep; +#endif +{ + /* Pacify ``unused variable'' warnings. */ + (void) yyvaluep; + + if (!yymsg) + yymsg = "Deleting"; + YY_SYMBOL_PRINT (yymsg, yytype, yyvaluep, yylocationp); + + switch (yytype) + { + + default: + break; + } +} + + +/* Prevent warnings from -Wmissing-prototypes. */ + +#ifdef YYPARSE_PARAM +# if defined (__STDC__) || defined (__cplusplus) +UNIV_INTERN int yyparse (void *YYPARSE_PARAM); +# else +UNIV_INTERN int yyparse (); +# endif +#else /* ! YYPARSE_PARAM */ +#if defined (__STDC__) || defined (__cplusplus) +UNIV_INTERN int yyparse (void); +#else +UNIV_INTERN int yyparse (); +#endif +#endif /* ! YYPARSE_PARAM */ + + + +/* The look-ahead symbol. */ +static int yychar; + +/* The semantic value of the look-ahead symbol. */ +UNIV_INTERN YYSTYPE yylval; + +/* Number of syntax errors so far. */ +static int yynerrs; + + + +/*----------. +| yyparse. | +`----------*/ + +#ifdef YYPARSE_PARAM +# if defined (__STDC__) || defined (__cplusplus) +UNIV_INTERN int yyparse (void *YYPARSE_PARAM) +# else +UNIV_INTERN int yyparse (YYPARSE_PARAM) + void *YYPARSE_PARAM; +# endif +#else /* ! YYPARSE_PARAM */ +#if defined (__STDC__) || defined (__cplusplus) +int +yyparse (void) +#else +int +yyparse () + +#endif +#endif +{ + + register int yystate; + register int yyn; + int yyresult; + /* Number of tokens to shift before error messages enabled. */ + int yyerrstatus; + /* Look-ahead token as an internal (translated) token number. */ + int yytoken = 0; + + /* Three stacks and their tools: + `yyss': related to states, + `yyvs': related to semantic values, + `yyls': related to locations. + + Refer to the stacks thru separate pointers, to allow yyoverflow + to reallocate them elsewhere. */ + + /* The state stack. */ + short int yyssa[YYINITDEPTH]; + short int *yyss = yyssa; + register short int *yyssp; + + /* The semantic value stack. */ + YYSTYPE yyvsa[YYINITDEPTH]; + YYSTYPE *yyvs = yyvsa; + register YYSTYPE *yyvsp; + + + +#define YYPOPSTACK (yyvsp--, yyssp--) + + YYSIZE_T yystacksize = YYINITDEPTH; + + /* The variables used to return semantic value and location from the + action routines. */ + YYSTYPE yyval; + + + /* When reducing, the number of symbols on the RHS of the reduced + rule. */ + int yylen; + + YYDPRINTF ((stderr, "Starting parse\n")); + + yystate = 0; + yyerrstatus = 0; + yynerrs = 0; + yychar = YYEMPTY; /* Cause a token to be read. */ + + /* Initialize stack pointers. + Waste one element of value and location stack + so that they stay on the same level as the state stack. + The wasted elements are never initialized. */ + + yyssp = yyss; + yyvsp = yyvs; + + + yyvsp[0] = yylval; + + goto yysetstate; + +/*------------------------------------------------------------. +| yynewstate -- Push a new state, which is found in yystate. | +`------------------------------------------------------------*/ + yynewstate: + /* In all cases, when you get here, the value and location stacks + have just been pushed. so pushing a state here evens the stacks. + */ + yyssp++; + + yysetstate: + *yyssp = yystate; + + if (yyss + yystacksize - 1 <= yyssp) + { + /* Get the current used size of the three stacks, in elements. */ + YYSIZE_T yysize = yyssp - yyss + 1; + +#ifdef yyoverflow + { + /* Give user a chance to reallocate the stack. Use copies of + these so that the &'s don't force the real ones into + memory. */ + YYSTYPE *yyvs1 = yyvs; + short int *yyss1 = yyss; + + + /* Each stack pointer address is followed by the size of the + data in use in that stack, in bytes. This used to be a + conditional around just the two extra args, but that might + be undefined if yyoverflow is a macro. */ + yyoverflow ("parser stack overflow", + &yyss1, yysize * sizeof (*yyssp), + &yyvs1, yysize * sizeof (*yyvsp), + + &yystacksize); + + yyss = yyss1; + yyvs = yyvs1; + } +#else /* no yyoverflow */ +# ifndef YYSTACK_RELOCATE + goto yyoverflowlab; +# else + /* Extend the stack our own way. */ + if (YYMAXDEPTH <= yystacksize) + goto yyoverflowlab; + yystacksize *= 2; + if (YYMAXDEPTH < yystacksize) + yystacksize = YYMAXDEPTH; + + { + short int *yyss1 = yyss; + union yyalloc *yyptr = + (union yyalloc *) YYSTACK_ALLOC (YYSTACK_BYTES (yystacksize)); + if (! yyptr) + goto yyoverflowlab; + YYSTACK_RELOCATE (yyss); + YYSTACK_RELOCATE (yyvs); + +# undef YYSTACK_RELOCATE + if (yyss1 != yyssa) + YYSTACK_FREE (yyss1); + } +# endif +#endif /* no yyoverflow */ + + yyssp = yyss + yysize - 1; + yyvsp = yyvs + yysize - 1; + + + YYDPRINTF ((stderr, "Stack size increased to %lu\n", + (unsigned long int) yystacksize)); + + if (yyss + yystacksize - 1 <= yyssp) + YYABORT; + } + + YYDPRINTF ((stderr, "Entering state %d\n", yystate)); + + goto yybackup; + +/*-----------. +| yybackup. | +`-----------*/ +yybackup: + +/* Do appropriate processing given the current state. */ +/* Read a look-ahead token if we need one and don't already have one. */ +/* yyresume: */ + + /* First try to decide what to do without reference to look-ahead token. */ + + yyn = yypact[yystate]; + if (yyn == YYPACT_NINF) + goto yydefault; + + /* Not known => get a look-ahead token if don't already have one. */ + + /* YYCHAR is either YYEMPTY or YYEOF or a valid look-ahead symbol. */ + if (yychar == YYEMPTY) + { + YYDPRINTF ((stderr, "Reading a token: ")); + yychar = YYLEX; + } + + if (yychar <= YYEOF) + { + yychar = yytoken = YYEOF; + YYDPRINTF ((stderr, "Now at end of input.\n")); + } + else + { + yytoken = YYTRANSLATE (yychar); + YY_SYMBOL_PRINT ("Next token is", yytoken, &yylval, &yylloc); + } + + /* If the proper action on seeing token YYTOKEN is to reduce or to + detect an error, take that action. */ + yyn += yytoken; + if (yyn < 0 || YYLAST < yyn || yycheck[yyn] != yytoken) + goto yydefault; + yyn = yytable[yyn]; + if (yyn <= 0) + { + if (yyn == 0 || yyn == YYTABLE_NINF) + goto yyerrlab; + yyn = -yyn; + goto yyreduce; + } + + if (yyn == YYFINAL) + YYACCEPT; + + /* Shift the look-ahead token. */ + YY_SYMBOL_PRINT ("Shifting", yytoken, &yylval, &yylloc); + + /* Discard the token being shifted unless it is eof. */ + if (yychar != YYEOF) + yychar = YYEMPTY; + + *++yyvsp = yylval; + + + /* Count tokens shifted since error; after three, turn off error + status. */ + if (yyerrstatus) + yyerrstatus--; + + yystate = yyn; + goto yynewstate; + + +/*-----------------------------------------------------------. +| yydefault -- do the default action for the current state. | +`-----------------------------------------------------------*/ +yydefault: + yyn = yydefact[yystate]; + if (yyn == 0) + goto yyerrlab; + goto yyreduce; + + +/*-----------------------------. +| yyreduce -- Do a reduction. | +`-----------------------------*/ +yyreduce: + /* yyn is the number of a rule to reduce with. */ + yylen = yyr2[yyn]; + + /* If YYLEN is nonzero, implement the default value of the action: + `$$ = $1'. + + Otherwise, the following line sets YYVAL to garbage. + This behavior is undocumented and Bison + users should not rely upon it. Assigning to YYVAL + unconditionally makes the parser a bit smaller, and it avoids a + GCC warning that YYVAL may be used uninitialized. */ + yyval = yyvsp[1-yylen]; + + + YY_REDUCE_PRINT (yyn); + switch (yyn) + { + case 25: +#line 166 "pars0grm.y" + { (yyval) = que_node_list_add_last(NULL, (yyvsp[0])); ;} + break; + + case 26: +#line 168 "pars0grm.y" + { (yyval) = que_node_list_add_last((yyvsp[-1]), (yyvsp[0])); ;} + break; + + case 27: +#line 172 "pars0grm.y" + { (yyval) = (yyvsp[0]);;} + break; + + case 28: +#line 174 "pars0grm.y" + { (yyval) = pars_func((yyvsp[-3]), (yyvsp[-1])); ;} + break; + + case 29: +#line 175 "pars0grm.y" + { (yyval) = (yyvsp[0]);;} + break; + + case 30: +#line 176 "pars0grm.y" + { (yyval) = (yyvsp[0]);;} + break; + + case 31: +#line 177 "pars0grm.y" + { (yyval) = (yyvsp[0]);;} + break; + + case 32: +#line 178 "pars0grm.y" + { (yyval) = (yyvsp[0]);;} + break; + + case 33: +#line 179 "pars0grm.y" + { (yyval) = (yyvsp[0]);;} + break; + + case 34: +#line 180 "pars0grm.y" + { (yyval) = (yyvsp[0]);;} + break; + + case 35: +#line 181 "pars0grm.y" + { (yyval) = (yyvsp[0]);;} + break; + + case 36: +#line 182 "pars0grm.y" + { (yyval) = pars_op('+', (yyvsp[-2]), (yyvsp[0])); ;} + break; + + case 37: +#line 183 "pars0grm.y" + { (yyval) = pars_op('-', (yyvsp[-2]), (yyvsp[0])); ;} + break; + + case 38: +#line 184 "pars0grm.y" + { (yyval) = pars_op('*', (yyvsp[-2]), (yyvsp[0])); ;} + break; + + case 39: +#line 185 "pars0grm.y" + { (yyval) = pars_op('/', (yyvsp[-2]), (yyvsp[0])); ;} + break; + + case 40: +#line 186 "pars0grm.y" + { (yyval) = pars_op('-', (yyvsp[0]), NULL); ;} + break; + + case 41: +#line 187 "pars0grm.y" + { (yyval) = (yyvsp[-1]); ;} + break; + + case 42: +#line 188 "pars0grm.y" + { (yyval) = pars_op('=', (yyvsp[-2]), (yyvsp[0])); ;} + break; + + case 43: +#line 189 "pars0grm.y" + { (yyval) = pars_op('<', (yyvsp[-2]), (yyvsp[0])); ;} + break; + + case 44: +#line 190 "pars0grm.y" + { (yyval) = pars_op('>', (yyvsp[-2]), (yyvsp[0])); ;} + break; + + case 45: +#line 191 "pars0grm.y" + { (yyval) = pars_op(PARS_GE_TOKEN, (yyvsp[-2]), (yyvsp[0])); ;} + break; + + case 46: +#line 192 "pars0grm.y" + { (yyval) = pars_op(PARS_LE_TOKEN, (yyvsp[-2]), (yyvsp[0])); ;} + break; + + case 47: +#line 193 "pars0grm.y" + { (yyval) = pars_op(PARS_NE_TOKEN, (yyvsp[-2]), (yyvsp[0])); ;} + break; + + case 48: +#line 194 "pars0grm.y" + { (yyval) = pars_op(PARS_AND_TOKEN, (yyvsp[-2]), (yyvsp[0])); ;} + break; + + case 49: +#line 195 "pars0grm.y" + { (yyval) = pars_op(PARS_OR_TOKEN, (yyvsp[-2]), (yyvsp[0])); ;} + break; + + case 50: +#line 196 "pars0grm.y" + { (yyval) = pars_op(PARS_NOT_TOKEN, (yyvsp[0]), NULL); ;} + break; + + case 51: +#line 198 "pars0grm.y" + { (yyval) = pars_op(PARS_NOTFOUND_TOKEN, (yyvsp[-2]), NULL); ;} + break; + + case 52: +#line 200 "pars0grm.y" + { (yyval) = pars_op(PARS_NOTFOUND_TOKEN, (yyvsp[-2]), NULL); ;} + break; + + case 53: +#line 204 "pars0grm.y" + { (yyval) = &pars_to_char_token; ;} + break; + + case 54: +#line 205 "pars0grm.y" + { (yyval) = &pars_to_number_token; ;} + break; + + case 55: +#line 206 "pars0grm.y" + { (yyval) = &pars_to_binary_token; ;} + break; + + case 56: +#line 208 "pars0grm.y" + { (yyval) = &pars_binary_to_number_token; ;} + break; + + case 57: +#line 209 "pars0grm.y" + { (yyval) = &pars_substr_token; ;} + break; + + case 58: +#line 210 "pars0grm.y" + { (yyval) = &pars_concat_token; ;} + break; + + case 59: +#line 211 "pars0grm.y" + { (yyval) = &pars_instr_token; ;} + break; + + case 60: +#line 212 "pars0grm.y" + { (yyval) = &pars_length_token; ;} + break; + + case 61: +#line 213 "pars0grm.y" + { (yyval) = &pars_sysdate_token; ;} + break; + + case 62: +#line 214 "pars0grm.y" + { (yyval) = &pars_rnd_token; ;} + break; + + case 63: +#line 215 "pars0grm.y" + { (yyval) = &pars_rnd_str_token; ;} + break; + + case 67: +#line 226 "pars0grm.y" + { (yyval) = pars_stored_procedure_call((yyvsp[-4])); ;} + break; + + case 68: +#line 231 "pars0grm.y" + { (yyval) = pars_procedure_call((yyvsp[-3]), (yyvsp[-1])); ;} + break; + + case 69: +#line 235 "pars0grm.y" + { (yyval) = &pars_replstr_token; ;} + break; + + case 70: +#line 236 "pars0grm.y" + { (yyval) = &pars_printf_token; ;} + break; + + case 71: +#line 237 "pars0grm.y" + { (yyval) = &pars_assert_token; ;} + break; + + case 72: +#line 241 "pars0grm.y" + { (yyval) = (yyvsp[-2]); ;} + break; + + case 73: +#line 245 "pars0grm.y" + { (yyval) = que_node_list_add_last(NULL, (yyvsp[0])); ;} + break; + + case 74: +#line 247 "pars0grm.y" + { (yyval) = que_node_list_add_last((yyvsp[-2]), (yyvsp[0])); ;} + break; + + case 75: +#line 251 "pars0grm.y" + { (yyval) = NULL; ;} + break; + + case 76: +#line 252 "pars0grm.y" + { (yyval) = que_node_list_add_last(NULL, (yyvsp[0])); ;} + break; + + case 77: +#line 254 "pars0grm.y" + { (yyval) = que_node_list_add_last((yyvsp[-2]), (yyvsp[0])); ;} + break; + + case 78: +#line 258 "pars0grm.y" + { (yyval) = NULL; ;} + break; + + case 79: +#line 259 "pars0grm.y" + { (yyval) = que_node_list_add_last(NULL, (yyvsp[0]));;} + break; + + case 80: +#line 260 "pars0grm.y" + { (yyval) = que_node_list_add_last((yyvsp[-2]), (yyvsp[0])); ;} + break; + + case 81: +#line 264 "pars0grm.y" + { (yyval) = (yyvsp[0]); ;} + break; + + case 82: +#line 266 "pars0grm.y" + { (yyval) = pars_func(&pars_count_token, + que_node_list_add_last(NULL, + sym_tab_add_int_lit( + pars_sym_tab_global, 1))); ;} + break; + + case 83: +#line 271 "pars0grm.y" + { (yyval) = pars_func(&pars_count_token, + que_node_list_add_last(NULL, + pars_func(&pars_distinct_token, + que_node_list_add_last( + NULL, (yyvsp[-1]))))); ;} + break; + + case 84: +#line 277 "pars0grm.y" + { (yyval) = pars_func(&pars_sum_token, + que_node_list_add_last(NULL, + (yyvsp[-1]))); ;} + break; + + case 85: +#line 283 "pars0grm.y" + { (yyval) = NULL; ;} + break; + + case 86: +#line 284 "pars0grm.y" + { (yyval) = que_node_list_add_last(NULL, (yyvsp[0])); ;} + break; + + case 87: +#line 286 "pars0grm.y" + { (yyval) = que_node_list_add_last((yyvsp[-2]), (yyvsp[0])); ;} + break; + + case 88: +#line 290 "pars0grm.y" + { (yyval) = pars_select_list(&pars_star_denoter, + NULL); ;} + break; + + case 89: +#line 293 "pars0grm.y" + { (yyval) = pars_select_list((yyvsp[-2]), (yyvsp[0])); ;} + break; + + case 90: +#line 294 "pars0grm.y" + { (yyval) = pars_select_list((yyvsp[0]), NULL); ;} + break; + + case 91: +#line 298 "pars0grm.y" + { (yyval) = NULL; ;} + break; + + case 92: +#line 299 "pars0grm.y" + { (yyval) = (yyvsp[0]); ;} + break; + + case 93: +#line 303 "pars0grm.y" + { (yyval) = NULL; ;} + break; + + case 94: +#line 305 "pars0grm.y" + { (yyval) = &pars_update_token; ;} + break; + + case 95: +#line 309 "pars0grm.y" + { (yyval) = NULL; ;} + break; + + case 96: +#line 311 "pars0grm.y" + { yyval = &pars_share_token; ;} + break; + + case 97: +#line 315 "pars0grm.y" + { (yyval) = &pars_asc_token; ;} + break; + + case 98: +#line 316 "pars0grm.y" + { (yyval) = &pars_asc_token; ;} + break; + + case 99: +#line 317 "pars0grm.y" + { (yyval) = &pars_desc_token; ;} + break; + + case 100: +#line 321 "pars0grm.y" + { (yyval) = NULL; ;} + break; + + case 101: +#line 323 "pars0grm.y" + { (yyval) = pars_order_by((yyvsp[-1]), (yyvsp[0])); ;} + break; + + case 102: +#line 332 "pars0grm.y" + { (yyval) = pars_select_statement((yyvsp[-6]), (yyvsp[-4]), (yyvsp[-3]), + (yyvsp[-2]), (yyvsp[-1]), (yyvsp[0])); ;} + break; + + case 103: +#line 338 "pars0grm.y" + { (yyval) = (yyvsp[0]); ;} + break; + + case 104: +#line 343 "pars0grm.y" + { (yyval) = pars_insert_statement((yyvsp[-4]), (yyvsp[-1]), NULL); ;} + break; + + case 105: +#line 345 "pars0grm.y" + { (yyval) = pars_insert_statement((yyvsp[-1]), NULL, (yyvsp[0])); ;} + break; + + case 106: +#line 349 "pars0grm.y" + { (yyval) = pars_column_assignment((yyvsp[-2]), (yyvsp[0])); ;} + break; + + case 107: +#line 353 "pars0grm.y" + { (yyval) = que_node_list_add_last(NULL, (yyvsp[0])); ;} + break; + + case 108: +#line 355 "pars0grm.y" + { (yyval) = que_node_list_add_last((yyvsp[-2]), (yyvsp[0])); ;} + break; + + case 109: +#line 361 "pars0grm.y" + { (yyval) = (yyvsp[0]); ;} + break; + + case 110: +#line 367 "pars0grm.y" + { (yyval) = pars_update_statement_start(FALSE, + (yyvsp[-2]), (yyvsp[0])); ;} + break; + + case 111: +#line 373 "pars0grm.y" + { (yyval) = pars_update_statement((yyvsp[-1]), NULL, (yyvsp[0])); ;} + break; + + case 112: +#line 378 "pars0grm.y" + { (yyval) = pars_update_statement((yyvsp[-1]), (yyvsp[0]), NULL); ;} + break; + + case 113: +#line 383 "pars0grm.y" + { (yyval) = pars_update_statement_start(TRUE, + (yyvsp[0]), NULL); ;} + break; + + case 114: +#line 389 "pars0grm.y" + { (yyval) = pars_update_statement((yyvsp[-1]), NULL, (yyvsp[0])); ;} + break; + + case 115: +#line 394 "pars0grm.y" + { (yyval) = pars_update_statement((yyvsp[-1]), (yyvsp[0]), NULL); ;} + break; + + case 116: +#line 399 "pars0grm.y" + { (yyval) = pars_row_printf_statement((yyvsp[0])); ;} + break; + + case 117: +#line 404 "pars0grm.y" + { (yyval) = pars_assignment_statement((yyvsp[-2]), (yyvsp[0])); ;} + break; + + case 118: +#line 410 "pars0grm.y" + { (yyval) = pars_elsif_element((yyvsp[-2]), (yyvsp[0])); ;} + break; + + case 119: +#line 414 "pars0grm.y" + { (yyval) = que_node_list_add_last(NULL, (yyvsp[0])); ;} + break; + + case 120: +#line 416 "pars0grm.y" + { (yyval) = que_node_list_add_last((yyvsp[-1]), (yyvsp[0])); ;} + break; + + case 121: +#line 420 "pars0grm.y" + { (yyval) = NULL; ;} + break; + + case 122: +#line 422 "pars0grm.y" + { (yyval) = (yyvsp[0]); ;} + break; + + case 123: +#line 423 "pars0grm.y" + { (yyval) = (yyvsp[0]); ;} + break; + + case 124: +#line 430 "pars0grm.y" + { (yyval) = pars_if_statement((yyvsp[-5]), (yyvsp[-3]), (yyvsp[-2])); ;} + break; + + case 125: +#line 436 "pars0grm.y" + { (yyval) = pars_while_statement((yyvsp[-4]), (yyvsp[-2])); ;} + break; + + case 126: +#line 444 "pars0grm.y" + { (yyval) = pars_for_statement((yyvsp[-8]), (yyvsp[-6]), (yyvsp[-4]), (yyvsp[-2])); ;} + break; + + case 127: +#line 448 "pars0grm.y" + { (yyval) = pars_exit_statement(); ;} + break; + + case 128: +#line 452 "pars0grm.y" + { (yyval) = pars_return_statement(); ;} + break; + + case 129: +#line 457 "pars0grm.y" + { (yyval) = pars_open_statement( + ROW_SEL_OPEN_CURSOR, (yyvsp[0])); ;} + break; + + case 130: +#line 463 "pars0grm.y" + { (yyval) = pars_open_statement( + ROW_SEL_CLOSE_CURSOR, (yyvsp[0])); ;} + break; + + case 131: +#line 469 "pars0grm.y" + { (yyval) = pars_fetch_statement((yyvsp[-2]), (yyvsp[0]), NULL); ;} + break; + + case 132: +#line 471 "pars0grm.y" + { (yyval) = pars_fetch_statement((yyvsp[-2]), NULL, (yyvsp[0])); ;} + break; + + case 133: +#line 476 "pars0grm.y" + { (yyval) = pars_column_def((yyvsp[-4]), (yyvsp[-3]), (yyvsp[-2]), (yyvsp[-1]), (yyvsp[0])); ;} + break; + + case 134: +#line 480 "pars0grm.y" + { (yyval) = que_node_list_add_last(NULL, (yyvsp[0])); ;} + break; + + case 135: +#line 482 "pars0grm.y" + { (yyval) = que_node_list_add_last((yyvsp[-2]), (yyvsp[0])); ;} + break; + + case 136: +#line 486 "pars0grm.y" + { (yyval) = NULL; ;} + break; + + case 137: +#line 488 "pars0grm.y" + { (yyval) = (yyvsp[-1]); ;} + break; + + case 138: +#line 492 "pars0grm.y" + { (yyval) = NULL; ;} + break; + + case 139: +#line 494 "pars0grm.y" + { (yyval) = &pars_int_token; + /* pass any non-NULL pointer */ ;} + break; + + case 140: +#line 499 "pars0grm.y" + { (yyval) = NULL; ;} + break; + + case 141: +#line 501 "pars0grm.y" + { (yyval) = &pars_int_token; + /* pass any non-NULL pointer */ ;} + break; + + case 142: +#line 506 "pars0grm.y" + { (yyval) = NULL; ;} + break; + + case 143: +#line 508 "pars0grm.y" + { (yyval) = &pars_int_token; + /* pass any non-NULL pointer */ ;} + break; + + case 144: +#line 515 "pars0grm.y" + { (yyval) = pars_create_table((yyvsp[-4]), (yyvsp[-2]), (yyvsp[0])); ;} + break; + + case 145: +#line 519 "pars0grm.y" + { (yyval) = que_node_list_add_last(NULL, (yyvsp[0])); ;} + break; + + case 146: +#line 521 "pars0grm.y" + { (yyval) = que_node_list_add_last((yyvsp[-2]), (yyvsp[0])); ;} + break; + + case 147: +#line 525 "pars0grm.y" + { (yyval) = NULL; ;} + break; + + case 148: +#line 526 "pars0grm.y" + { (yyval) = &pars_unique_token; ;} + break; + + case 149: +#line 530 "pars0grm.y" + { (yyval) = NULL; ;} + break; + + case 150: +#line 531 "pars0grm.y" + { (yyval) = &pars_clustered_token; ;} + break; + + case 151: +#line 539 "pars0grm.y" + { (yyval) = pars_create_index((yyvsp[-8]), (yyvsp[-7]), (yyvsp[-5]), (yyvsp[-3]), (yyvsp[-1])); ;} + break; + + case 152: +#line 544 "pars0grm.y" + { (yyval) = pars_commit_statement(); ;} + break; + + case 153: +#line 549 "pars0grm.y" + { (yyval) = pars_rollback_statement(); ;} + break; + + case 154: +#line 553 "pars0grm.y" + { (yyval) = &pars_int_token; ;} + break; + + case 155: +#line 554 "pars0grm.y" + { (yyval) = &pars_int_token; ;} + break; + + case 156: +#line 555 "pars0grm.y" + { (yyval) = &pars_char_token; ;} + break; + + case 157: +#line 556 "pars0grm.y" + { (yyval) = &pars_binary_token; ;} + break; + + case 158: +#line 557 "pars0grm.y" + { (yyval) = &pars_blob_token; ;} + break; + + case 159: +#line 562 "pars0grm.y" + { (yyval) = pars_parameter_declaration((yyvsp[-2]), + PARS_INPUT, (yyvsp[0])); ;} + break; + + case 160: +#line 565 "pars0grm.y" + { (yyval) = pars_parameter_declaration((yyvsp[-2]), + PARS_OUTPUT, (yyvsp[0])); ;} + break; + + case 161: +#line 570 "pars0grm.y" + { (yyval) = NULL; ;} + break; + + case 162: +#line 571 "pars0grm.y" + { (yyval) = que_node_list_add_last(NULL, (yyvsp[0])); ;} + break; + + case 163: +#line 573 "pars0grm.y" + { (yyval) = que_node_list_add_last((yyvsp[-2]), (yyvsp[0])); ;} + break; + + case 164: +#line 578 "pars0grm.y" + { (yyval) = pars_variable_declaration((yyvsp[-2]), (yyvsp[-1])); ;} + break; + + case 168: +#line 590 "pars0grm.y" + { (yyval) = pars_cursor_declaration((yyvsp[-3]), (yyvsp[-1])); ;} + break; + + case 169: +#line 595 "pars0grm.y" + { (yyval) = pars_function_declaration((yyvsp[-1])); ;} + break; + + case 175: +#line 616 "pars0grm.y" + { (yyval) = pars_procedure_definition((yyvsp[-9]), (yyvsp[-7]), + (yyvsp[-1])); ;} + break; + + + } + +/* Line 1010 of yacc.c. */ +#line 2345 "pars0grm.c" + + yyvsp -= yylen; + yyssp -= yylen; + + + YY_STACK_PRINT (yyss, yyssp); + + *++yyvsp = yyval; + + + /* Now `shift' the result of the reduction. Determine what state + that goes to, based on the state we popped back to and the rule + number reduced by. */ + + yyn = yyr1[yyn]; + + yystate = yypgoto[yyn - YYNTOKENS] + *yyssp; + if (0 <= yystate && yystate <= YYLAST && yycheck[yystate] == *yyssp) + yystate = yytable[yystate]; + else + yystate = yydefgoto[yyn - YYNTOKENS]; + + goto yynewstate; + + +/*------------------------------------. +| yyerrlab -- here on detecting error | +`------------------------------------*/ +yyerrlab: + /* If not already recovering from an error, report this error. */ + if (!yyerrstatus) + { + ++yynerrs; +#if YYERROR_VERBOSE + yyn = yypact[yystate]; + + if (YYPACT_NINF < yyn && yyn < YYLAST) + { + YYSIZE_T yysize = 0; + int yytype = YYTRANSLATE (yychar); + const char* yyprefix; + char *yymsg; + int yyx; + + /* Start YYX at -YYN if negative to avoid negative indexes in + YYCHECK. */ + int yyxbegin = yyn < 0 ? -yyn : 0; + + /* Stay within bounds of both yycheck and yytname. */ + int yychecklim = YYLAST - yyn; + int yyxend = yychecklim < YYNTOKENS ? yychecklim : YYNTOKENS; + int yycount = 0; + + yyprefix = ", expecting "; + for (yyx = yyxbegin; yyx < yyxend; ++yyx) + if (yycheck[yyx + yyn] == yyx && yyx != YYTERROR) + { + yysize += yystrlen (yyprefix) + yystrlen (yytname [yyx]); + yycount += 1; + if (yycount == 5) + { + yysize = 0; + break; + } + } + yysize += (sizeof ("syntax error, unexpected ") + + yystrlen (yytname[yytype])); + yymsg = (char *) YYSTACK_ALLOC (yysize); + if (yymsg != 0) + { + char *yyp = yystpcpy (yymsg, "syntax error, unexpected "); + yyp = yystpcpy (yyp, yytname[yytype]); + + if (yycount < 5) + { + yyprefix = ", expecting "; + for (yyx = yyxbegin; yyx < yyxend; ++yyx) + if (yycheck[yyx + yyn] == yyx && yyx != YYTERROR) + { + yyp = yystpcpy (yyp, yyprefix); + yyp = yystpcpy (yyp, yytname[yyx]); + yyprefix = " or "; + } + } + yyerror (yymsg); + YYSTACK_FREE (yymsg); + } + else + yyerror ("syntax error; also virtual memory exhausted"); + } + else +#endif /* YYERROR_VERBOSE */ + yyerror ("syntax error"); + } + + + + if (yyerrstatus == 3) + { + /* If just tried and failed to reuse look-ahead token after an + error, discard it. */ + + if (yychar <= YYEOF) + { + /* If at end of input, pop the error token, + then the rest of the stack, then return failure. */ + if (yychar == YYEOF) + for (;;) + { + + YYPOPSTACK; + if (yyssp == yyss) + YYABORT; + yydestruct ("Error: popping", + yystos[*yyssp], yyvsp); + } + } + else + { + yydestruct ("Error: discarding", yytoken, &yylval); + yychar = YYEMPTY; + } + } + + /* Else will try to reuse look-ahead token after shifting the error + token. */ + goto yyerrlab1; + + +/*---------------------------------------------------. +| yyerrorlab -- error raised explicitly by YYERROR. | +`---------------------------------------------------*/ +yyerrorlab: + +#ifdef __GNUC__ + /* Pacify GCC when the user code never invokes YYERROR and the label + yyerrorlab therefore never appears in user code. */ + if (0) + goto yyerrorlab; +#endif + +yyvsp -= yylen; + yyssp -= yylen; + yystate = *yyssp; + goto yyerrlab1; + + +/*-------------------------------------------------------------. +| yyerrlab1 -- common code for both syntax error and YYERROR. | +`-------------------------------------------------------------*/ +yyerrlab1: + yyerrstatus = 3; /* Each real token shifted decrements this. */ + + for (;;) + { + yyn = yypact[yystate]; + if (yyn != YYPACT_NINF) + { + yyn += YYTERROR; + if (0 <= yyn && yyn <= YYLAST && yycheck[yyn] == YYTERROR) + { + yyn = yytable[yyn]; + if (0 < yyn) + break; + } + } + + /* Pop the current state because it cannot handle the error token. */ + if (yyssp == yyss) + YYABORT; + + + yydestruct ("Error: popping", yystos[yystate], yyvsp); + YYPOPSTACK; + yystate = *yyssp; + YY_STACK_PRINT (yyss, yyssp); + } + + if (yyn == YYFINAL) + YYACCEPT; + + *++yyvsp = yylval; + + + /* Shift the error token. */ + YY_SYMBOL_PRINT ("Shifting", yystos[yyn], yyvsp, yylsp); + + yystate = yyn; + goto yynewstate; + + +/*-------------------------------------. +| yyacceptlab -- YYACCEPT comes here. | +`-------------------------------------*/ +yyacceptlab: + yyresult = 0; + goto yyreturn; + +/*-----------------------------------. +| yyabortlab -- YYABORT comes here. | +`-----------------------------------*/ +yyabortlab: + yydestruct ("Error: discarding lookahead", + yytoken, &yylval); + yychar = YYEMPTY; + yyresult = 1; + goto yyreturn; + +#ifndef yyoverflow +/*----------------------------------------------. +| yyoverflowlab -- parser overflow comes here. | +`----------------------------------------------*/ +yyoverflowlab: + yyerror ("parser stack overflow"); + yyresult = 2; + /* Fall through. */ +#endif + +yyreturn: +#ifndef yyoverflow + if (yyss != yyssa) + YYSTACK_FREE (yyss); +#endif + return yyresult; +} + + +#line 620 "pars0grm.y" + + diff --git a/storage/xtradb/pars/pars0grm.y b/storage/xtradb/pars/pars0grm.y new file mode 100644 index 00000000000..14d64f1826f --- /dev/null +++ b/storage/xtradb/pars/pars0grm.y @@ -0,0 +1,635 @@ +/***************************************************************************** + +Copyright (c) 1997, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/****************************************************** +SQL parser: input file for the GNU Bison parser generator + +Look from pars0lex.l for instructions how to generate the C files for +the InnoDB parser. + +Created 12/14/1997 Heikki Tuuri +*******************************************************/ + +%{ +/* The value of the semantic attribute is a pointer to a query tree node +que_node_t */ + +#include "univ.i" +#include <math.h> /* Can't be before univ.i */ +#include "pars0pars.h" +#include "mem0mem.h" +#include "que0types.h" +#include "que0que.h" +#include "row0sel.h" + +#define YYSTYPE que_node_t* + +/* #define __STDC__ */ + +int +yylex(void); +%} + +%token PARS_INT_LIT +%token PARS_FLOAT_LIT +%token PARS_STR_LIT +%token PARS_FIXBINARY_LIT +%token PARS_BLOB_LIT +%token PARS_NULL_LIT +%token PARS_ID_TOKEN +%token PARS_AND_TOKEN +%token PARS_OR_TOKEN +%token PARS_NOT_TOKEN +%token PARS_GE_TOKEN +%token PARS_LE_TOKEN +%token PARS_NE_TOKEN +%token PARS_PROCEDURE_TOKEN +%token PARS_IN_TOKEN +%token PARS_OUT_TOKEN +%token PARS_BINARY_TOKEN +%token PARS_BLOB_TOKEN +%token PARS_INT_TOKEN +%token PARS_INTEGER_TOKEN +%token PARS_FLOAT_TOKEN +%token PARS_CHAR_TOKEN +%token PARS_IS_TOKEN +%token PARS_BEGIN_TOKEN +%token PARS_END_TOKEN +%token PARS_IF_TOKEN +%token PARS_THEN_TOKEN +%token PARS_ELSE_TOKEN +%token PARS_ELSIF_TOKEN +%token PARS_LOOP_TOKEN +%token PARS_WHILE_TOKEN +%token PARS_RETURN_TOKEN +%token PARS_SELECT_TOKEN +%token PARS_SUM_TOKEN +%token PARS_COUNT_TOKEN +%token PARS_DISTINCT_TOKEN +%token PARS_FROM_TOKEN +%token PARS_WHERE_TOKEN +%token PARS_FOR_TOKEN +%token PARS_DDOT_TOKEN +%token PARS_READ_TOKEN +%token PARS_ORDER_TOKEN +%token PARS_BY_TOKEN +%token PARS_ASC_TOKEN +%token PARS_DESC_TOKEN +%token PARS_INSERT_TOKEN +%token PARS_INTO_TOKEN +%token PARS_VALUES_TOKEN +%token PARS_UPDATE_TOKEN +%token PARS_SET_TOKEN +%token PARS_DELETE_TOKEN +%token PARS_CURRENT_TOKEN +%token PARS_OF_TOKEN +%token PARS_CREATE_TOKEN +%token PARS_TABLE_TOKEN +%token PARS_INDEX_TOKEN +%token PARS_UNIQUE_TOKEN +%token PARS_CLUSTERED_TOKEN +%token PARS_DOES_NOT_FIT_IN_MEM_TOKEN +%token PARS_ON_TOKEN +%token PARS_ASSIGN_TOKEN +%token PARS_DECLARE_TOKEN +%token PARS_CURSOR_TOKEN +%token PARS_SQL_TOKEN +%token PARS_OPEN_TOKEN +%token PARS_FETCH_TOKEN +%token PARS_CLOSE_TOKEN +%token PARS_NOTFOUND_TOKEN +%token PARS_TO_CHAR_TOKEN +%token PARS_TO_NUMBER_TOKEN +%token PARS_TO_BINARY_TOKEN +%token PARS_BINARY_TO_NUMBER_TOKEN +%token PARS_SUBSTR_TOKEN +%token PARS_REPLSTR_TOKEN +%token PARS_CONCAT_TOKEN +%token PARS_INSTR_TOKEN +%token PARS_LENGTH_TOKEN +%token PARS_SYSDATE_TOKEN +%token PARS_PRINTF_TOKEN +%token PARS_ASSERT_TOKEN +%token PARS_RND_TOKEN +%token PARS_RND_STR_TOKEN +%token PARS_ROW_PRINTF_TOKEN +%token PARS_COMMIT_TOKEN +%token PARS_ROLLBACK_TOKEN +%token PARS_WORK_TOKEN +%token PARS_UNSIGNED_TOKEN +%token PARS_EXIT_TOKEN +%token PARS_FUNCTION_TOKEN +%token PARS_LOCK_TOKEN +%token PARS_SHARE_TOKEN +%token PARS_MODE_TOKEN + +%left PARS_AND_TOKEN PARS_OR_TOKEN +%left PARS_NOT_TOKEN +%left '=' '<' '>' PARS_GE_TOKEN PARS_LE_TOKEN +%left '-' '+' +%left '*' '/' +%left NEG /* negation--unary minus */ +%left '%' + +/* Grammar follows */ +%% + +top_statement: + procedure_definition ';' + +statement: + stored_procedure_call + | predefined_procedure_call ';' + | while_statement ';' + | for_statement ';' + | exit_statement ';' + | if_statement ';' + | return_statement ';' + | assignment_statement ';' + | select_statement ';' + | insert_statement ';' + | row_printf_statement ';' + | delete_statement_searched ';' + | delete_statement_positioned ';' + | update_statement_searched ';' + | update_statement_positioned ';' + | open_cursor_statement ';' + | fetch_statement ';' + | close_cursor_statement ';' + | commit_statement ';' + | rollback_statement ';' + | create_table ';' + | create_index ';' +; + +statement_list: + statement { $$ = que_node_list_add_last(NULL, $1); } + | statement_list statement + { $$ = que_node_list_add_last($1, $2); } +; + +exp: + PARS_ID_TOKEN { $$ = $1;} + | function_name '(' exp_list ')' + { $$ = pars_func($1, $3); } + | PARS_INT_LIT { $$ = $1;} + | PARS_FLOAT_LIT { $$ = $1;} + | PARS_STR_LIT { $$ = $1;} + | PARS_FIXBINARY_LIT { $$ = $1;} + | PARS_BLOB_LIT { $$ = $1;} + | PARS_NULL_LIT { $$ = $1;} + | PARS_SQL_TOKEN { $$ = $1;} + | exp '+' exp { $$ = pars_op('+', $1, $3); } + | exp '-' exp { $$ = pars_op('-', $1, $3); } + | exp '*' exp { $$ = pars_op('*', $1, $3); } + | exp '/' exp { $$ = pars_op('/', $1, $3); } + | '-' exp %prec NEG { $$ = pars_op('-', $2, NULL); } + | '(' exp ')' { $$ = $2; } + | exp '=' exp { $$ = pars_op('=', $1, $3); } + | exp '<' exp { $$ = pars_op('<', $1, $3); } + | exp '>' exp { $$ = pars_op('>', $1, $3); } + | exp PARS_GE_TOKEN exp { $$ = pars_op(PARS_GE_TOKEN, $1, $3); } + | exp PARS_LE_TOKEN exp { $$ = pars_op(PARS_LE_TOKEN, $1, $3); } + | exp PARS_NE_TOKEN exp { $$ = pars_op(PARS_NE_TOKEN, $1, $3); } + | exp PARS_AND_TOKEN exp{ $$ = pars_op(PARS_AND_TOKEN, $1, $3); } + | exp PARS_OR_TOKEN exp { $$ = pars_op(PARS_OR_TOKEN, $1, $3); } + | PARS_NOT_TOKEN exp { $$ = pars_op(PARS_NOT_TOKEN, $2, NULL); } + | PARS_ID_TOKEN '%' PARS_NOTFOUND_TOKEN + { $$ = pars_op(PARS_NOTFOUND_TOKEN, $1, NULL); } + | PARS_SQL_TOKEN '%' PARS_NOTFOUND_TOKEN + { $$ = pars_op(PARS_NOTFOUND_TOKEN, $1, NULL); } +; + +function_name: + PARS_TO_CHAR_TOKEN { $$ = &pars_to_char_token; } + | PARS_TO_NUMBER_TOKEN { $$ = &pars_to_number_token; } + | PARS_TO_BINARY_TOKEN { $$ = &pars_to_binary_token; } + | PARS_BINARY_TO_NUMBER_TOKEN + { $$ = &pars_binary_to_number_token; } + | PARS_SUBSTR_TOKEN { $$ = &pars_substr_token; } + | PARS_CONCAT_TOKEN { $$ = &pars_concat_token; } + | PARS_INSTR_TOKEN { $$ = &pars_instr_token; } + | PARS_LENGTH_TOKEN { $$ = &pars_length_token; } + | PARS_SYSDATE_TOKEN { $$ = &pars_sysdate_token; } + | PARS_RND_TOKEN { $$ = &pars_rnd_token; } + | PARS_RND_STR_TOKEN { $$ = &pars_rnd_str_token; } +; + +question_mark_list: + /* Nothing */ + | '?' + | question_mark_list ',' '?' +; + +stored_procedure_call: + '{' PARS_ID_TOKEN '(' question_mark_list ')' '}' + { $$ = pars_stored_procedure_call($2); } +; + +predefined_procedure_call: + predefined_procedure_name '(' exp_list ')' + { $$ = pars_procedure_call($1, $3); } +; + +predefined_procedure_name: + PARS_REPLSTR_TOKEN { $$ = &pars_replstr_token; } + | PARS_PRINTF_TOKEN { $$ = &pars_printf_token; } + | PARS_ASSERT_TOKEN { $$ = &pars_assert_token; } +; + +user_function_call: + PARS_ID_TOKEN '(' ')' { $$ = $1; } +; + +table_list: + PARS_ID_TOKEN { $$ = que_node_list_add_last(NULL, $1); } + | table_list ',' PARS_ID_TOKEN + { $$ = que_node_list_add_last($1, $3); } +; + +variable_list: + /* Nothing */ { $$ = NULL; } + | PARS_ID_TOKEN { $$ = que_node_list_add_last(NULL, $1); } + | variable_list ',' PARS_ID_TOKEN + { $$ = que_node_list_add_last($1, $3); } +; + +exp_list: + /* Nothing */ { $$ = NULL; } + | exp { $$ = que_node_list_add_last(NULL, $1);} + | exp_list ',' exp { $$ = que_node_list_add_last($1, $3); } +; + +select_item: + exp { $$ = $1; } + | PARS_COUNT_TOKEN '(' '*' ')' + { $$ = pars_func(&pars_count_token, + que_node_list_add_last(NULL, + sym_tab_add_int_lit( + pars_sym_tab_global, 1))); } + | PARS_COUNT_TOKEN '(' PARS_DISTINCT_TOKEN PARS_ID_TOKEN ')' + { $$ = pars_func(&pars_count_token, + que_node_list_add_last(NULL, + pars_func(&pars_distinct_token, + que_node_list_add_last( + NULL, $4)))); } + | PARS_SUM_TOKEN '(' exp ')' + { $$ = pars_func(&pars_sum_token, + que_node_list_add_last(NULL, + $3)); } +; + +select_item_list: + /* Nothing */ { $$ = NULL; } + | select_item { $$ = que_node_list_add_last(NULL, $1); } + | select_item_list ',' select_item + { $$ = que_node_list_add_last($1, $3); } +; + +select_list: + '*' { $$ = pars_select_list(&pars_star_denoter, + NULL); } + | select_item_list PARS_INTO_TOKEN variable_list + { $$ = pars_select_list($1, $3); } + | select_item_list { $$ = pars_select_list($1, NULL); } +; + +search_condition: + /* Nothing */ { $$ = NULL; } + | PARS_WHERE_TOKEN exp { $$ = $2; } +; + +for_update_clause: + /* Nothing */ { $$ = NULL; } + | PARS_FOR_TOKEN PARS_UPDATE_TOKEN + { $$ = &pars_update_token; } +; + +lock_shared_clause: + /* Nothing */ { $$ = NULL; } + | PARS_LOCK_TOKEN PARS_IN_TOKEN PARS_SHARE_TOKEN PARS_MODE_TOKEN + { $$ = &pars_share_token; } +; + +order_direction: + /* Nothing */ { $$ = &pars_asc_token; } + | PARS_ASC_TOKEN { $$ = &pars_asc_token; } + | PARS_DESC_TOKEN { $$ = &pars_desc_token; } +; + +order_by_clause: + /* Nothing */ { $$ = NULL; } + | PARS_ORDER_TOKEN PARS_BY_TOKEN PARS_ID_TOKEN order_direction + { $$ = pars_order_by($3, $4); } +; + +select_statement: + PARS_SELECT_TOKEN select_list + PARS_FROM_TOKEN table_list + search_condition + for_update_clause + lock_shared_clause + order_by_clause { $$ = pars_select_statement($2, $4, $5, + $6, $7, $8); } +; + +insert_statement_start: + PARS_INSERT_TOKEN PARS_INTO_TOKEN + PARS_ID_TOKEN { $$ = $3; } +; + +insert_statement: + insert_statement_start PARS_VALUES_TOKEN '(' exp_list ')' + { $$ = pars_insert_statement($1, $4, NULL); } + | insert_statement_start select_statement + { $$ = pars_insert_statement($1, NULL, $2); } +; + +column_assignment: + PARS_ID_TOKEN '=' exp { $$ = pars_column_assignment($1, $3); } +; + +column_assignment_list: + column_assignment { $$ = que_node_list_add_last(NULL, $1); } + | column_assignment_list ',' column_assignment + { $$ = que_node_list_add_last($1, $3); } +; + +cursor_positioned: + PARS_WHERE_TOKEN + PARS_CURRENT_TOKEN PARS_OF_TOKEN + PARS_ID_TOKEN { $$ = $4; } +; + +update_statement_start: + PARS_UPDATE_TOKEN PARS_ID_TOKEN + PARS_SET_TOKEN + column_assignment_list { $$ = pars_update_statement_start(FALSE, + $2, $4); } +; + +update_statement_searched: + update_statement_start + search_condition { $$ = pars_update_statement($1, NULL, $2); } +; + +update_statement_positioned: + update_statement_start + cursor_positioned { $$ = pars_update_statement($1, $2, NULL); } +; + +delete_statement_start: + PARS_DELETE_TOKEN PARS_FROM_TOKEN + PARS_ID_TOKEN { $$ = pars_update_statement_start(TRUE, + $3, NULL); } +; + +delete_statement_searched: + delete_statement_start + search_condition { $$ = pars_update_statement($1, NULL, $2); } +; + +delete_statement_positioned: + delete_statement_start + cursor_positioned { $$ = pars_update_statement($1, $2, NULL); } +; + +row_printf_statement: + PARS_ROW_PRINTF_TOKEN select_statement + { $$ = pars_row_printf_statement($2); } +; + +assignment_statement: + PARS_ID_TOKEN PARS_ASSIGN_TOKEN exp + { $$ = pars_assignment_statement($1, $3); } +; + +elsif_element: + PARS_ELSIF_TOKEN + exp PARS_THEN_TOKEN statement_list + { $$ = pars_elsif_element($2, $4); } +; + +elsif_list: + elsif_element { $$ = que_node_list_add_last(NULL, $1); } + | elsif_list elsif_element + { $$ = que_node_list_add_last($1, $2); } +; + +else_part: + /* Nothing */ { $$ = NULL; } + | PARS_ELSE_TOKEN statement_list + { $$ = $2; } + | elsif_list { $$ = $1; } +; + +if_statement: + PARS_IF_TOKEN exp PARS_THEN_TOKEN statement_list + else_part + PARS_END_TOKEN PARS_IF_TOKEN + { $$ = pars_if_statement($2, $4, $5); } +; + +while_statement: + PARS_WHILE_TOKEN exp PARS_LOOP_TOKEN statement_list + PARS_END_TOKEN PARS_LOOP_TOKEN + { $$ = pars_while_statement($2, $4); } +; + +for_statement: + PARS_FOR_TOKEN PARS_ID_TOKEN PARS_IN_TOKEN + exp PARS_DDOT_TOKEN exp + PARS_LOOP_TOKEN statement_list + PARS_END_TOKEN PARS_LOOP_TOKEN + { $$ = pars_for_statement($2, $4, $6, $8); } +; + +exit_statement: + PARS_EXIT_TOKEN { $$ = pars_exit_statement(); } +; + +return_statement: + PARS_RETURN_TOKEN { $$ = pars_return_statement(); } +; + +open_cursor_statement: + PARS_OPEN_TOKEN PARS_ID_TOKEN + { $$ = pars_open_statement( + ROW_SEL_OPEN_CURSOR, $2); } +; + +close_cursor_statement: + PARS_CLOSE_TOKEN PARS_ID_TOKEN + { $$ = pars_open_statement( + ROW_SEL_CLOSE_CURSOR, $2); } +; + +fetch_statement: + PARS_FETCH_TOKEN PARS_ID_TOKEN PARS_INTO_TOKEN variable_list + { $$ = pars_fetch_statement($2, $4, NULL); } + | PARS_FETCH_TOKEN PARS_ID_TOKEN PARS_INTO_TOKEN user_function_call + { $$ = pars_fetch_statement($2, NULL, $4); } +; + +column_def: + PARS_ID_TOKEN type_name opt_column_len opt_unsigned opt_not_null + { $$ = pars_column_def($1, $2, $3, $4, $5); } +; + +column_def_list: + column_def { $$ = que_node_list_add_last(NULL, $1); } + | column_def_list ',' column_def + { $$ = que_node_list_add_last($1, $3); } +; + +opt_column_len: + /* Nothing */ { $$ = NULL; } + | '(' PARS_INT_LIT ')' + { $$ = $2; } +; + +opt_unsigned: + /* Nothing */ { $$ = NULL; } + | PARS_UNSIGNED_TOKEN + { $$ = &pars_int_token; + /* pass any non-NULL pointer */ } +; + +opt_not_null: + /* Nothing */ { $$ = NULL; } + | PARS_NOT_TOKEN PARS_NULL_LIT + { $$ = &pars_int_token; + /* pass any non-NULL pointer */ } +; + +not_fit_in_memory: + /* Nothing */ { $$ = NULL; } + | PARS_DOES_NOT_FIT_IN_MEM_TOKEN + { $$ = &pars_int_token; + /* pass any non-NULL pointer */ } +; + +create_table: + PARS_CREATE_TOKEN PARS_TABLE_TOKEN + PARS_ID_TOKEN '(' column_def_list ')' + not_fit_in_memory { $$ = pars_create_table($3, $5, $7); } +; + +column_list: + PARS_ID_TOKEN { $$ = que_node_list_add_last(NULL, $1); } + | column_list ',' PARS_ID_TOKEN + { $$ = que_node_list_add_last($1, $3); } +; + +unique_def: + /* Nothing */ { $$ = NULL; } + | PARS_UNIQUE_TOKEN { $$ = &pars_unique_token; } +; + +clustered_def: + /* Nothing */ { $$ = NULL; } + | PARS_CLUSTERED_TOKEN { $$ = &pars_clustered_token; } +; + +create_index: + PARS_CREATE_TOKEN unique_def + clustered_def + PARS_INDEX_TOKEN + PARS_ID_TOKEN PARS_ON_TOKEN PARS_ID_TOKEN + '(' column_list ')' { $$ = pars_create_index($2, $3, $5, $7, $9); } +; + +commit_statement: + PARS_COMMIT_TOKEN PARS_WORK_TOKEN + { $$ = pars_commit_statement(); } +; + +rollback_statement: + PARS_ROLLBACK_TOKEN PARS_WORK_TOKEN + { $$ = pars_rollback_statement(); } +; + +type_name: + PARS_INT_TOKEN { $$ = &pars_int_token; } + | PARS_INTEGER_TOKEN { $$ = &pars_int_token; } + | PARS_CHAR_TOKEN { $$ = &pars_char_token; } + | PARS_BINARY_TOKEN { $$ = &pars_binary_token; } + | PARS_BLOB_TOKEN { $$ = &pars_blob_token; } +; + +parameter_declaration: + PARS_ID_TOKEN PARS_IN_TOKEN type_name + { $$ = pars_parameter_declaration($1, + PARS_INPUT, $3); } + | PARS_ID_TOKEN PARS_OUT_TOKEN type_name + { $$ = pars_parameter_declaration($1, + PARS_OUTPUT, $3); } +; + +parameter_declaration_list: + /* Nothing */ { $$ = NULL; } + | parameter_declaration { $$ = que_node_list_add_last(NULL, $1); } + | parameter_declaration_list ',' parameter_declaration + { $$ = que_node_list_add_last($1, $3); } +; + +variable_declaration: + PARS_ID_TOKEN type_name ';' + { $$ = pars_variable_declaration($1, $2); } +; + +variable_declaration_list: + /* Nothing */ + | variable_declaration + | variable_declaration_list variable_declaration +; + +cursor_declaration: + PARS_DECLARE_TOKEN PARS_CURSOR_TOKEN PARS_ID_TOKEN + PARS_IS_TOKEN select_statement ';' + { $$ = pars_cursor_declaration($3, $5); } +; + +function_declaration: + PARS_DECLARE_TOKEN PARS_FUNCTION_TOKEN PARS_ID_TOKEN ';' + { $$ = pars_function_declaration($3); } +; + +declaration: + cursor_declaration + | function_declaration +; + +declaration_list: + /* Nothing */ + | declaration + | declaration_list declaration +; + +procedure_definition: + PARS_PROCEDURE_TOKEN PARS_ID_TOKEN '(' parameter_declaration_list ')' + PARS_IS_TOKEN + variable_declaration_list + declaration_list + PARS_BEGIN_TOKEN + statement_list + PARS_END_TOKEN { $$ = pars_procedure_definition($2, $4, + $10); } +; + +%% diff --git a/storage/xtradb/pars/pars0lex.l b/storage/xtradb/pars/pars0lex.l new file mode 100644 index 00000000000..38cb744bd44 --- /dev/null +++ b/storage/xtradb/pars/pars0lex.l @@ -0,0 +1,663 @@ +/***************************************************************************** + +Copyright (c) 1997, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/****************************************************** +SQL parser lexical analyzer: input file for the GNU Flex lexer generator + +The InnoDB parser is frozen because MySQL takes care of SQL parsing. +Therefore we normally keep the InnoDB parser C files as they are, and do +not automatically generate them from pars0grm.y and pars0lex.l. + +How to make the InnoDB parser and lexer C files: + +1. Run ./make_flex.sh to generate lexer files. + +2. Run ./make_bison.sh to generate parser files. + +These instructions seem to work at least with bison-1.875d and flex-2.5.31 on +Linux. + +Created 12/14/1997 Heikki Tuuri +*******************************************************/ + +%option nostdinit +%option 8bit +%option warn +%option pointer +%option never-interactive +%option nodefault +%option noinput +%option nounput +%option noyywrap +%option noyy_scan_buffer +%option noyy_scan_bytes +%option noyy_scan_string +%option nounistd + +%{ +#define YYSTYPE que_node_t* + +#include "univ.i" +#include "pars0pars.h" +#include "pars0grm.h" +#include "pars0sym.h" +#include "mem0mem.h" +#include "os0proc.h" + +#define malloc(A) ut_malloc(A) +#define free(A) ut_free(A) +#define realloc(P, A) ut_realloc(P, A) +#define exit(A) ut_error + +#define YY_INPUT(buf, result, max_size) pars_get_lex_chars(buf, &result, max_size) + +/* String buffer for removing quotes */ +static ulint stringbuf_len_alloc = 0; /* Allocated length */ +static ulint stringbuf_len = 0; /* Current length */ +static char* stringbuf; /* Start of buffer */ +/* Appends a string to the buffer. */ +static +void +string_append( +/*==========*/ + const char* str, /* in: string to be appended */ + ulint len) /* in: length of the string */ +{ + if (stringbuf == NULL) { + stringbuf = malloc(1); + stringbuf_len_alloc = 1; + } + + if (stringbuf_len + len > stringbuf_len_alloc) { + while (stringbuf_len + len > stringbuf_len_alloc) { + stringbuf_len_alloc <<= 1; + } + stringbuf = realloc(stringbuf, stringbuf_len_alloc); + } + + memcpy(stringbuf + stringbuf_len, str, len); + stringbuf_len += len; +} + +%} + +DIGIT [0-9] +ID [a-z_A-Z][a-z_A-Z0-9]* +BOUND_LIT \:[a-z_A-Z0-9]+ +BOUND_ID \$[a-z_A-Z0-9]+ + +%x comment +%x quoted +%x id +%% + +{DIGIT}+ { + yylval = sym_tab_add_int_lit(pars_sym_tab_global, + atoi(yytext)); + return(PARS_INT_LIT); +} + +{DIGIT}+"."{DIGIT}* { + ut_error; /* not implemented */ + + return(PARS_FLOAT_LIT); +} + +{BOUND_LIT} { + ulint type; + + yylval = sym_tab_add_bound_lit(pars_sym_tab_global, + yytext + 1, &type); + + return((int) type); +} + +{BOUND_ID} { + yylval = sym_tab_add_bound_id(pars_sym_tab_global, + yytext + 1); + + return(PARS_ID_TOKEN); +} + +"'" { +/* Quoted character string literals are handled in an explicit +start state 'quoted'. This state is entered and the buffer for +the scanned string is emptied upon encountering a starting quote. + +In the state 'quoted', only two actions are possible (defined below). */ + BEGIN(quoted); + stringbuf_len = 0; +} +<quoted>[^\']+ { + /* Got a sequence of characters other than "'": + append to string buffer */ + string_append(yytext, yyleng); +} +<quoted>"'"+ { + /* Got a sequence of "'" characters: + append half of them to string buffer, + as "''" represents a single "'". + We apply truncating division, + so that "'''" will result in "'". */ + + string_append(yytext, yyleng / 2); + + /* If we got an odd number of quotes, then the + last quote we got is the terminating quote. + At the end of the string, we return to the + initial start state and report the scanned + string literal. */ + + if (yyleng % 2) { + BEGIN(INITIAL); + yylval = sym_tab_add_str_lit( + pars_sym_tab_global, + (byte*) stringbuf, stringbuf_len); + return(PARS_STR_LIT); + } +} + +\" { +/* Quoted identifiers are handled in an explicit start state 'id'. +This state is entered and the buffer for the scanned string is emptied +upon encountering a starting quote. + +In the state 'id', only two actions are possible (defined below). */ + BEGIN(id); + stringbuf_len = 0; +} +<id>[^\"]+ { + /* Got a sequence of characters other than '"': + append to string buffer */ + string_append(yytext, yyleng); +} +<id>\"+ { + /* Got a sequence of '"' characters: + append half of them to string buffer, + as '""' represents a single '"'. + We apply truncating division, + so that '"""' will result in '"'. */ + + string_append(yytext, yyleng / 2); + + /* If we got an odd number of quotes, then the + last quote we got is the terminating quote. + At the end of the string, we return to the + initial start state and report the scanned + identifier. */ + + if (yyleng % 2) { + BEGIN(INITIAL); + yylval = sym_tab_add_id( + pars_sym_tab_global, + (byte*) stringbuf, stringbuf_len); + + return(PARS_ID_TOKEN); + } +} + +"NULL" { + yylval = sym_tab_add_null_lit(pars_sym_tab_global); + + return(PARS_NULL_LIT); +} + +"SQL" { + /* Implicit cursor name */ + yylval = sym_tab_add_str_lit(pars_sym_tab_global, + (byte*) yytext, yyleng); + return(PARS_SQL_TOKEN); +} + +"AND" { + return(PARS_AND_TOKEN); +} + +"OR" { + return(PARS_OR_TOKEN); +} + +"NOT" { + return(PARS_NOT_TOKEN); +} + +"PROCEDURE" { + return(PARS_PROCEDURE_TOKEN); +} + +"IN" { + return(PARS_IN_TOKEN); +} + +"OUT" { + return(PARS_OUT_TOKEN); +} + +"BINARY" { + return(PARS_BINARY_TOKEN); +} + +"BLOB" { + return(PARS_BLOB_TOKEN); +} + +"INT" { + return(PARS_INT_TOKEN); +} + +"INTEGER" { + return(PARS_INT_TOKEN); +} + +"FLOAT" { + return(PARS_FLOAT_TOKEN); +} + +"CHAR" { + return(PARS_CHAR_TOKEN); +} + +"IS" { + return(PARS_IS_TOKEN); +} + +"BEGIN" { + return(PARS_BEGIN_TOKEN); +} + +"END" { + return(PARS_END_TOKEN); +} + +"IF" { + return(PARS_IF_TOKEN); +} + +"THEN" { + return(PARS_THEN_TOKEN); +} + +"ELSE" { + return(PARS_ELSE_TOKEN); +} + +"ELSIF" { + return(PARS_ELSIF_TOKEN); +} + +"LOOP" { + return(PARS_LOOP_TOKEN); +} + +"WHILE" { + return(PARS_WHILE_TOKEN); +} + +"RETURN" { + return(PARS_RETURN_TOKEN); +} + +"SELECT" { + return(PARS_SELECT_TOKEN); +} + +"SUM" { + return(PARS_SUM_TOKEN); +} + +"COUNT" { + return(PARS_COUNT_TOKEN); +} + +"DISTINCT" { + return(PARS_DISTINCT_TOKEN); +} + +"FROM" { + return(PARS_FROM_TOKEN); +} + +"WHERE" { + return(PARS_WHERE_TOKEN); +} + +"FOR" { + return(PARS_FOR_TOKEN); +} + +"READ" { + return(PARS_READ_TOKEN); +} + +"ORDER" { + return(PARS_ORDER_TOKEN); +} + +"BY" { + return(PARS_BY_TOKEN); +} + +"ASC" { + return(PARS_ASC_TOKEN); +} + +"DESC" { + return(PARS_DESC_TOKEN); +} + +"INSERT" { + return(PARS_INSERT_TOKEN); +} + +"INTO" { + return(PARS_INTO_TOKEN); +} + +"VALUES" { + return(PARS_VALUES_TOKEN); +} + +"UPDATE" { + return(PARS_UPDATE_TOKEN); +} + +"SET" { + return(PARS_SET_TOKEN); +} + +"DELETE" { + return(PARS_DELETE_TOKEN); +} + +"CURRENT" { + return(PARS_CURRENT_TOKEN); +} + +"OF" { + return(PARS_OF_TOKEN); +} + +"CREATE" { + return(PARS_CREATE_TOKEN); +} + +"TABLE" { + return(PARS_TABLE_TOKEN); +} + +"INDEX" { + return(PARS_INDEX_TOKEN); +} + +"UNIQUE" { + return(PARS_UNIQUE_TOKEN); +} + +"CLUSTERED" { + return(PARS_CLUSTERED_TOKEN); +} + +"DOES_NOT_FIT_IN_MEMORY" { + return(PARS_DOES_NOT_FIT_IN_MEM_TOKEN); +} + +"ON" { + return(PARS_ON_TOKEN); +} + +"DECLARE" { + return(PARS_DECLARE_TOKEN); +} + +"CURSOR" { + return(PARS_CURSOR_TOKEN); +} + +"OPEN" { + return(PARS_OPEN_TOKEN); +} + +"FETCH" { + return(PARS_FETCH_TOKEN); +} + +"CLOSE" { + return(PARS_CLOSE_TOKEN); +} + +"NOTFOUND" { + return(PARS_NOTFOUND_TOKEN); +} + +"TO_CHAR" { + return(PARS_TO_CHAR_TOKEN); +} + +"TO_NUMBER" { + return(PARS_TO_NUMBER_TOKEN); +} + +"TO_BINARY" { + return(PARS_TO_BINARY_TOKEN); +} + +"BINARY_TO_NUMBER" { + return(PARS_BINARY_TO_NUMBER_TOKEN); +} + +"SUBSTR" { + return(PARS_SUBSTR_TOKEN); +} + +"REPLSTR" { + return(PARS_REPLSTR_TOKEN); +} + +"CONCAT" { + return(PARS_CONCAT_TOKEN); +} + +"INSTR" { + return(PARS_INSTR_TOKEN); +} + +"LENGTH" { + return(PARS_LENGTH_TOKEN); +} + +"SYSDATE" { + return(PARS_SYSDATE_TOKEN); +} + +"PRINTF" { + return(PARS_PRINTF_TOKEN); +} + +"ASSERT" { + return(PARS_ASSERT_TOKEN); +} + +"RND" { + return(PARS_RND_TOKEN); +} + +"RND_STR" { + return(PARS_RND_STR_TOKEN); +} + +"ROW_PRINTF" { + return(PARS_ROW_PRINTF_TOKEN); +} + +"COMMIT" { + return(PARS_COMMIT_TOKEN); +} + +"ROLLBACK" { + return(PARS_ROLLBACK_TOKEN); +} + +"WORK" { + return(PARS_WORK_TOKEN); +} + +"UNSIGNED" { + return(PARS_UNSIGNED_TOKEN); +} + +"EXIT" { + return(PARS_EXIT_TOKEN); +} + +"FUNCTION" { + return(PARS_FUNCTION_TOKEN); +} + +"LOCK" { + return(PARS_LOCK_TOKEN); +} + +"SHARE" { + return(PARS_SHARE_TOKEN); +} + +"MODE" { + return(PARS_MODE_TOKEN); +} + +{ID} { + yylval = sym_tab_add_id(pars_sym_tab_global, + (byte*)yytext, + ut_strlen(yytext)); + return(PARS_ID_TOKEN); +} + +".." { + return(PARS_DDOT_TOKEN); +} + +":=" { + return(PARS_ASSIGN_TOKEN); +} + +"<=" { + return(PARS_LE_TOKEN); +} + +">=" { + return(PARS_GE_TOKEN); +} + +"<>" { + return(PARS_NE_TOKEN); +} + +"(" { + + return((int)(*yytext)); +} + +"=" { + + return((int)(*yytext)); +} + +">" { + + return((int)(*yytext)); +} + +"<" { + + return((int)(*yytext)); +} + +"," { + + return((int)(*yytext)); +} + +";" { + + return((int)(*yytext)); +} + +")" { + + return((int)(*yytext)); +} + +"+" { + + return((int)(*yytext)); +} + +"-" { + + return((int)(*yytext)); +} + +"*" { + + return((int)(*yytext)); +} + +"/" { + + return((int)(*yytext)); +} + +"%" { + + return((int)(*yytext)); +} + +"{" { + + return((int)(*yytext)); +} + +"}" { + + return((int)(*yytext)); +} + +"?" { + + return((int)(*yytext)); +} + +"/*" BEGIN(comment); /* eat up comment */ + +<comment>[^*]* +<comment>"*"+[^*/]* +<comment>"*"+"/" BEGIN(INITIAL); + +[ \t\n]+ /* eat up whitespace */ + + +. { + fprintf(stderr,"Unrecognized character: %02x\n", + *yytext); + + ut_error; + + return(0); +} + +%% diff --git a/storage/xtradb/pars/pars0opt.c b/storage/xtradb/pars/pars0opt.c new file mode 100644 index 00000000000..34246929c53 --- /dev/null +++ b/storage/xtradb/pars/pars0opt.c @@ -0,0 +1,1224 @@ +/***************************************************************************** + +Copyright (c) 1997, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/****************************************************** +Simple SQL optimizer + +Created 12/21/1997 Heikki Tuuri +*******************************************************/ + +#include "pars0opt.h" + +#ifdef UNIV_NONINL +#include "pars0opt.ic" +#endif + +#include "row0sel.h" +#include "row0ins.h" +#include "row0upd.h" +#include "dict0dict.h" +#include "dict0mem.h" +#include "que0que.h" +#include "pars0grm.h" +#include "pars0pars.h" +#include "lock0lock.h" + +#define OPT_EQUAL 1 /* comparison by = */ +#define OPT_COMPARISON 2 /* comparison by <, >, <=, or >= */ + +#define OPT_NOT_COND 1 +#define OPT_END_COND 2 +#define OPT_TEST_COND 3 +#define OPT_SCROLL_COND 4 + + +/*********************************************************************** +Inverts a comparison operator. */ +static +int +opt_invert_cmp_op( +/*==============*/ + /* out: the equivalent operator when the order of + the arguments is switched */ + int op) /* in: operator */ +{ + if (op == '<') { + return('>'); + } else if (op == '>') { + return('<'); + } else if (op == '=') { + return('='); + } else if (op == PARS_LE_TOKEN) { + return(PARS_GE_TOKEN); + } else if (op == PARS_GE_TOKEN) { + return(PARS_LE_TOKEN); + } else { + ut_error; + } + + return(0); +} + +/*********************************************************************** +Checks if the value of an expression can be calculated BEFORE the nth table +in a join is accessed. If this is the case, it can possibly be used in an +index search for the nth table. */ +static +ibool +opt_check_exp_determined_before( +/*============================*/ + /* out: TRUE if already determined */ + que_node_t* exp, /* in: expression */ + sel_node_t* sel_node, /* in: select node */ + ulint nth_table) /* in: nth table will be accessed */ +{ + func_node_t* func_node; + sym_node_t* sym_node; + dict_table_t* table; + que_node_t* arg; + ulint i; + + ut_ad(exp && sel_node); + + if (que_node_get_type(exp) == QUE_NODE_FUNC) { + func_node = exp; + + arg = func_node->args; + + while (arg) { + if (!opt_check_exp_determined_before(arg, sel_node, + nth_table)) { + return(FALSE); + } + + arg = que_node_get_next(arg); + } + + return(TRUE); + } + + ut_a(que_node_get_type(exp) == QUE_NODE_SYMBOL); + + sym_node = exp; + + if (sym_node->token_type != SYM_COLUMN) { + + return(TRUE); + } + + for (i = 0; i < nth_table; i++) { + + table = sel_node_get_nth_plan(sel_node, i)->table; + + if (sym_node->table == table) { + + return(TRUE); + } + } + + return(FALSE); +} + +/*********************************************************************** +Looks in a comparison condition if a column value is already restricted by +it BEFORE the nth table is accessed. */ +static +que_node_t* +opt_look_for_col_in_comparison_before( +/*==================================*/ + /* out: expression restricting the + value of the column, or NULL if not + known */ + ulint cmp_type, /* in: OPT_EQUAL, OPT_COMPARISON */ + ulint col_no, /* in: column number */ + func_node_t* search_cond, /* in: comparison condition */ + sel_node_t* sel_node, /* in: select node */ + ulint nth_table, /* in: nth table in a join (a query + from a single table is considered a + join of 1 table) */ + ulint* op) /* out: comparison operator ('=', + PARS_GE_TOKEN, ... ); this is inverted + if the column appears on the right + side */ +{ + sym_node_t* sym_node; + dict_table_t* table; + que_node_t* exp; + que_node_t* arg; + + ut_ad(search_cond); + + ut_a((search_cond->func == '<') + || (search_cond->func == '>') + || (search_cond->func == '=') + || (search_cond->func == PARS_GE_TOKEN) + || (search_cond->func == PARS_LE_TOKEN)); + + table = sel_node_get_nth_plan(sel_node, nth_table)->table; + + if ((cmp_type == OPT_EQUAL) && (search_cond->func != '=')) { + + return(NULL); + + } else if ((cmp_type == OPT_COMPARISON) + && (search_cond->func != '<') + && (search_cond->func != '>') + && (search_cond->func != PARS_GE_TOKEN) + && (search_cond->func != PARS_LE_TOKEN)) { + + return(NULL); + } + + arg = search_cond->args; + + if (que_node_get_type(arg) == QUE_NODE_SYMBOL) { + sym_node = arg; + + if ((sym_node->token_type == SYM_COLUMN) + && (sym_node->table == table) + && (sym_node->col_no == col_no)) { + + /* sym_node contains the desired column id */ + + /* Check if the expression on the right side of the + operator is already determined */ + + exp = que_node_get_next(arg); + + if (opt_check_exp_determined_before(exp, sel_node, + nth_table)) { + *op = search_cond->func; + + return(exp); + } + } + } + + exp = search_cond->args; + arg = que_node_get_next(arg); + + if (que_node_get_type(arg) == QUE_NODE_SYMBOL) { + sym_node = arg; + + if ((sym_node->token_type == SYM_COLUMN) + && (sym_node->table == table) + && (sym_node->col_no == col_no)) { + + if (opt_check_exp_determined_before(exp, sel_node, + nth_table)) { + *op = opt_invert_cmp_op(search_cond->func); + + return(exp); + } + } + } + + return(NULL); +} + +/*********************************************************************** +Looks in a search condition if a column value is already restricted by the +search condition BEFORE the nth table is accessed. Takes into account that +if we will fetch in an ascending order, we cannot utilize an upper limit for +a column value; in a descending order, respectively, a lower limit. */ +static +que_node_t* +opt_look_for_col_in_cond_before( +/*============================*/ + /* out: expression restricting the + value of the column, or NULL if not + known */ + ulint cmp_type, /* in: OPT_EQUAL, OPT_COMPARISON */ + ulint col_no, /* in: column number */ + func_node_t* search_cond, /* in: search condition or NULL */ + sel_node_t* sel_node, /* in: select node */ + ulint nth_table, /* in: nth table in a join (a query + from a single table is considered a + join of 1 table) */ + ulint* op) /* out: comparison operator ('=', + PARS_GE_TOKEN, ... ) */ +{ + func_node_t* new_cond; + que_node_t* exp; + + if (search_cond == NULL) { + + return(NULL); + } + + ut_a(que_node_get_type(search_cond) == QUE_NODE_FUNC); + ut_a(search_cond->func != PARS_OR_TOKEN); + ut_a(search_cond->func != PARS_NOT_TOKEN); + + if (search_cond->func == PARS_AND_TOKEN) { + new_cond = search_cond->args; + + exp = opt_look_for_col_in_cond_before(cmp_type, col_no, + new_cond, sel_node, + nth_table, op); + if (exp) { + + return(exp); + } + + new_cond = que_node_get_next(new_cond); + + exp = opt_look_for_col_in_cond_before(cmp_type, col_no, + new_cond, sel_node, + nth_table, op); + return(exp); + } + + exp = opt_look_for_col_in_comparison_before(cmp_type, col_no, + search_cond, sel_node, + nth_table, op); + if (exp == NULL) { + + return(NULL); + } + + /* If we will fetch in an ascending order, we cannot utilize an upper + limit for a column value; in a descending order, respectively, a lower + limit */ + + if (sel_node->asc && ((*op == '<') || (*op == PARS_LE_TOKEN))) { + + return(NULL); + + } else if (!sel_node->asc + && ((*op == '>') || (*op == PARS_GE_TOKEN))) { + + return(NULL); + } + + return(exp); +} + +/*********************************************************************** +Calculates the goodness for an index according to a select node. The +goodness is 4 times the number of first fields in index whose values we +already know exactly in the query. If we have a comparison condition for +an additional field, 2 point are added. If the index is unique, and we know +all the unique fields for the index we add 1024 points. For a clustered index +we add 1 point. */ +static +ulint +opt_calc_index_goodness( +/*====================*/ + /* out: goodness */ + dict_index_t* index, /* in: index */ + sel_node_t* sel_node, /* in: parsed select node */ + ulint nth_table, /* in: nth table in a join */ + que_node_t** index_plan, /* in/out: comparison expressions for + this index */ + ulint* last_op) /* out: last comparison operator, if + goodness > 1 */ +{ + que_node_t* exp; + ulint goodness; + ulint n_fields; + ulint col_no; + ulint op; + ulint j; + + goodness = 0; + + /* Note that as higher level node pointers in the B-tree contain + page addresses as the last field, we must not put more fields in + the search tuple than dict_index_get_n_unique_in_tree(index); see + the note in btr_cur_search_to_nth_level. */ + + n_fields = dict_index_get_n_unique_in_tree(index); + + for (j = 0; j < n_fields; j++) { + + col_no = dict_index_get_nth_col_no(index, j); + + exp = opt_look_for_col_in_cond_before( + OPT_EQUAL, col_no, sel_node->search_cond, + sel_node, nth_table, &op); + if (exp) { + /* The value for this column is exactly known already + at this stage of the join */ + + index_plan[j] = exp; + *last_op = op; + goodness += 4; + } else { + /* Look for non-equality comparisons */ + + exp = opt_look_for_col_in_cond_before( + OPT_COMPARISON, col_no, sel_node->search_cond, + sel_node, nth_table, &op); + if (exp) { + index_plan[j] = exp; + *last_op = op; + goodness += 2; + } + + break; + } + } + + if (goodness >= 4 * dict_index_get_n_unique(index)) { + goodness += 1024; + + if (dict_index_is_clust(index)) { + + goodness += 1024; + } + } + + /* We have to test for goodness here, as last_op may note be set */ + if (goodness && dict_index_is_clust(index)) { + + goodness++; + } + + return(goodness); +} + +/*********************************************************************** +Calculates the number of matched fields based on an index goodness. */ +UNIV_INLINE +ulint +opt_calc_n_fields_from_goodness( +/*============================*/ + /* out: number of excatly or partially matched + fields */ + ulint goodness) /* in: goodness */ +{ + return(((goodness % 1024) + 2) / 4); +} + +/*********************************************************************** +Converts a comparison operator to the corresponding search mode PAGE_CUR_GE, +... */ +UNIV_INLINE +ulint +opt_op_to_search_mode( +/*==================*/ + /* out: search mode */ + ibool asc, /* in: TRUE if the rows should be fetched in an + ascending order */ + ulint op) /* in: operator '=', PARS_GE_TOKEN, ... */ +{ + if (op == '=') { + if (asc) { + return(PAGE_CUR_GE); + } else { + return(PAGE_CUR_LE); + } + } else if (op == '<') { + ut_a(!asc); + return(PAGE_CUR_L); + } else if (op == '>') { + ut_a(asc); + return(PAGE_CUR_G); + } else if (op == PARS_GE_TOKEN) { + ut_a(asc); + return(PAGE_CUR_GE); + } else if (op == PARS_LE_TOKEN) { + ut_a(!asc); + return(PAGE_CUR_LE); + } else { + ut_error; + } + + return(0); +} + +/*********************************************************************** +Determines if a node is an argument node of a function node. */ +static +ibool +opt_is_arg( +/*=======*/ + /* out: TRUE if is an argument */ + que_node_t* arg_node, /* in: possible argument node */ + func_node_t* func_node) /* in: function node */ +{ + que_node_t* arg; + + arg = func_node->args; + + while (arg) { + if (arg == arg_node) { + + return(TRUE); + } + + arg = que_node_get_next(arg); + } + + return(FALSE); +} + +/*********************************************************************** +Decides if the fetching of rows should be made in a descending order, and +also checks that the chosen query plan produces a result which satisfies +the order-by. */ +static +void +opt_check_order_by( +/*===============*/ + sel_node_t* sel_node) /* in: select node; asserts an error + if the plan does not agree with the + order-by */ +{ + order_node_t* order_node; + dict_table_t* order_table; + ulint order_col_no; + plan_t* plan; + ulint i; + + if (!sel_node->order_by) { + + return; + } + + order_node = sel_node->order_by; + order_col_no = order_node->column->col_no; + order_table = order_node->column->table; + + /* If there is an order-by clause, the first non-exactly matched field + in the index used for the last table in the table list should be the + column defined in the order-by clause, and for all the other tables + we should get only at most a single row, otherwise we cannot presently + calculate the order-by, as we have no sort utility */ + + for (i = 0; i < sel_node->n_tables; i++) { + + plan = sel_node_get_nth_plan(sel_node, i); + + if (i < sel_node->n_tables - 1) { + ut_a(dict_index_get_n_unique(plan->index) + <= plan->n_exact_match); + } else { + ut_a(plan->table == order_table); + + ut_a((dict_index_get_n_unique(plan->index) + <= plan->n_exact_match) + || (dict_index_get_nth_col_no(plan->index, + plan->n_exact_match) + == order_col_no)); + } + } +} + +/*********************************************************************** +Optimizes a select. Decides which indexes to tables to use. The tables +are accessed in the order that they were written to the FROM part in the +select statement. */ +static +void +opt_search_plan_for_table( +/*======================*/ + sel_node_t* sel_node, /* in: parsed select node */ + ulint i, /* in: this is the ith table */ + dict_table_t* table) /* in: table */ +{ + plan_t* plan; + dict_index_t* index; + dict_index_t* best_index; + ulint n_fields; + ulint goodness; + ulint last_op = 75946965; /* Eliminate a Purify + warning */ + ulint best_goodness; + ulint best_last_op = 0; /* remove warning */ + que_node_t* index_plan[256]; + que_node_t* best_index_plan[256]; + + plan = sel_node_get_nth_plan(sel_node, i); + + plan->table = table; + plan->asc = sel_node->asc; + plan->pcur_is_open = FALSE; + plan->cursor_at_end = FALSE; + + /* Calculate goodness for each index of the table */ + + index = dict_table_get_first_index(table); + best_index = index; /* Eliminate compiler warning */ + best_goodness = 0; + + /* should be do ... until ? comment by Jani */ + while (index) { + goodness = opt_calc_index_goodness(index, sel_node, i, + index_plan, &last_op); + if (goodness > best_goodness) { + + best_index = index; + best_goodness = goodness; + n_fields = opt_calc_n_fields_from_goodness(goodness); + + ut_memcpy(best_index_plan, index_plan, + n_fields * sizeof(void*)); + best_last_op = last_op; + } + + index = dict_table_get_next_index(index); + } + + plan->index = best_index; + + n_fields = opt_calc_n_fields_from_goodness(best_goodness); + + if (n_fields == 0) { + plan->tuple = NULL; + plan->n_exact_match = 0; + } else { + plan->tuple = dtuple_create(pars_sym_tab_global->heap, + n_fields); + dict_index_copy_types(plan->tuple, plan->index, n_fields); + + plan->tuple_exps = mem_heap_alloc(pars_sym_tab_global->heap, + n_fields * sizeof(void*)); + + ut_memcpy(plan->tuple_exps, best_index_plan, + n_fields * sizeof(void*)); + if (best_last_op == '=') { + plan->n_exact_match = n_fields; + } else { + plan->n_exact_match = n_fields - 1; + } + + plan->mode = opt_op_to_search_mode(sel_node->asc, + best_last_op); + } + + if (dict_index_is_clust(best_index) + && (plan->n_exact_match >= dict_index_get_n_unique(best_index))) { + + plan->unique_search = TRUE; + } else { + plan->unique_search = FALSE; + } + + plan->old_vers_heap = NULL; + + btr_pcur_init(&(plan->pcur)); + btr_pcur_init(&(plan->clust_pcur)); +} + +/*********************************************************************** +Looks at a comparison condition and decides if it can, and need, be tested for +a table AFTER the table has been accessed. */ +static +ulint +opt_classify_comparison( +/*====================*/ + /* out: OPT_NOT_COND if not for this + table, else OPT_END_COND, + OPT_TEST_COND, or OPT_SCROLL_COND, + where the last means that the + condition need not be tested, except + when scroll cursors are used */ + sel_node_t* sel_node, /* in: select node */ + ulint i, /* in: ith table in the join */ + func_node_t* cond) /* in: comparison condition */ +{ + plan_t* plan; + ulint n_fields; + ulint op; + ulint j; + + ut_ad(cond && sel_node); + + plan = sel_node_get_nth_plan(sel_node, i); + + /* Check if the condition is determined after the ith table has been + accessed, but not after the i - 1:th */ + + if (!opt_check_exp_determined_before(cond, sel_node, i + 1)) { + + return(OPT_NOT_COND); + } + + if ((i > 0) && opt_check_exp_determined_before(cond, sel_node, i)) { + + return(OPT_NOT_COND); + } + + /* If the condition is an exact match condition used in constructing + the search tuple, it is classified as OPT_END_COND */ + + if (plan->tuple) { + n_fields = dtuple_get_n_fields(plan->tuple); + } else { + n_fields = 0; + } + + for (j = 0; j < plan->n_exact_match; j++) { + + if (opt_is_arg(plan->tuple_exps[j], cond)) { + + return(OPT_END_COND); + } + } + + /* If the condition is an non-exact match condition used in + constructing the search tuple, it is classified as OPT_SCROLL_COND. + When the cursor is positioned, and if a non-scroll cursor is used, + there is no need to test this condition; if a scroll cursor is used + the testing is necessary when the cursor is reversed. */ + + if ((n_fields > plan->n_exact_match) + && opt_is_arg(plan->tuple_exps[n_fields - 1], cond)) { + + return(OPT_SCROLL_COND); + } + + /* If the condition is a non-exact match condition on the first field + in index for which there is no exact match, and it limits the search + range from the opposite side of the search tuple already BEFORE we + access the table, it is classified as OPT_END_COND */ + + if ((dict_index_get_n_fields(plan->index) > plan->n_exact_match) + && opt_look_for_col_in_comparison_before( + OPT_COMPARISON, + dict_index_get_nth_col_no(plan->index, + plan->n_exact_match), + cond, sel_node, i, &op)) { + + if (sel_node->asc && ((op == '<') || (op == PARS_LE_TOKEN))) { + + return(OPT_END_COND); + } + + if (!sel_node->asc && ((op == '>') || (op == PARS_GE_TOKEN))) { + + return(OPT_END_COND); + } + } + + /* Otherwise, cond is classified as OPT_TEST_COND */ + + return(OPT_TEST_COND); +} + +/*********************************************************************** +Recursively looks for test conditions for a table in a join. */ +static +void +opt_find_test_conds( +/*================*/ + sel_node_t* sel_node, /* in: select node */ + ulint i, /* in: ith table in the join */ + func_node_t* cond) /* in: conjunction of search + conditions or NULL */ +{ + func_node_t* new_cond; + ulint class; + plan_t* plan; + + if (cond == NULL) { + + return; + } + + if (cond->func == PARS_AND_TOKEN) { + new_cond = cond->args; + + opt_find_test_conds(sel_node, i, new_cond); + + new_cond = que_node_get_next(new_cond); + + opt_find_test_conds(sel_node, i, new_cond); + + return; + } + + plan = sel_node_get_nth_plan(sel_node, i); + + class = opt_classify_comparison(sel_node, i, cond); + + if (class == OPT_END_COND) { + UT_LIST_ADD_LAST(cond_list, plan->end_conds, cond); + + } else if (class == OPT_TEST_COND) { + UT_LIST_ADD_LAST(cond_list, plan->other_conds, cond); + + } +} + +/*********************************************************************** +Normalizes a list of comparison conditions so that a column of the table +appears on the left side of the comparison if possible. This is accomplished +by switching the arguments of the operator. */ +static +void +opt_normalize_cmp_conds( +/*====================*/ + func_node_t* cond, /* in: first in a list of comparison + conditions, or NULL */ + dict_table_t* table) /* in: table */ +{ + que_node_t* arg1; + que_node_t* arg2; + sym_node_t* sym_node; + + while (cond) { + arg1 = cond->args; + arg2 = que_node_get_next(arg1); + + if (que_node_get_type(arg2) == QUE_NODE_SYMBOL) { + + sym_node = arg2; + + if ((sym_node->token_type == SYM_COLUMN) + && (sym_node->table == table)) { + + /* Switch the order of the arguments */ + + cond->args = arg2; + que_node_list_add_last(NULL, arg2); + que_node_list_add_last(arg2, arg1); + + /* Invert the operator */ + cond->func = opt_invert_cmp_op(cond->func); + } + } + + cond = UT_LIST_GET_NEXT(cond_list, cond); + } +} + +/*********************************************************************** +Finds out the search condition conjuncts we can, and need, to test as the ith +table in a join is accessed. The search tuple can eliminate the need to test +some conjuncts. */ +static +void +opt_determine_and_normalize_test_conds( +/*===================================*/ + sel_node_t* sel_node, /* in: select node */ + ulint i) /* in: ith table in the join */ +{ + plan_t* plan; + + plan = sel_node_get_nth_plan(sel_node, i); + + UT_LIST_INIT(plan->end_conds); + UT_LIST_INIT(plan->other_conds); + + /* Recursively go through the conjuncts and classify them */ + + opt_find_test_conds(sel_node, i, sel_node->search_cond); + + opt_normalize_cmp_conds(UT_LIST_GET_FIRST(plan->end_conds), + plan->table); + + ut_a(UT_LIST_GET_LEN(plan->end_conds) >= plan->n_exact_match); +} + +/*********************************************************************** +Looks for occurrences of the columns of the table in the query subgraph and +adds them to the list of columns if an occurrence of the same column does not +already exist in the list. If the column is already in the list, puts a value +indirection to point to the occurrence in the column list, except if the +column occurrence we are looking at is in the column list, in which case +nothing is done. */ +UNIV_INTERN +void +opt_find_all_cols( +/*==============*/ + ibool copy_val, /* in: if TRUE, new found columns are + added as columns to copy */ + dict_index_t* index, /* in: index of the table to use */ + sym_node_list_t* col_list, /* in: base node of a list where + to add new found columns */ + plan_t* plan, /* in: plan or NULL */ + que_node_t* exp) /* in: expression or condition or + NULL */ +{ + func_node_t* func_node; + que_node_t* arg; + sym_node_t* sym_node; + sym_node_t* col_node; + ulint col_pos; + + if (exp == NULL) { + + return; + } + + if (que_node_get_type(exp) == QUE_NODE_FUNC) { + func_node = exp; + + arg = func_node->args; + + while (arg) { + opt_find_all_cols(copy_val, index, col_list, plan, + arg); + arg = que_node_get_next(arg); + } + + return; + } + + ut_a(que_node_get_type(exp) == QUE_NODE_SYMBOL); + + sym_node = exp; + + if (sym_node->token_type != SYM_COLUMN) { + + return; + } + + if (sym_node->table != index->table) { + + return; + } + + /* Look for an occurrence of the same column in the plan column + list */ + + col_node = UT_LIST_GET_FIRST(*col_list); + + while (col_node) { + if (col_node->col_no == sym_node->col_no) { + + if (col_node == sym_node) { + /* sym_node was already in a list: do + nothing */ + + return; + } + + /* Put an indirection */ + sym_node->indirection = col_node; + sym_node->alias = col_node; + + return; + } + + col_node = UT_LIST_GET_NEXT(col_var_list, col_node); + } + + /* The same column did not occur in the list: add it */ + + UT_LIST_ADD_LAST(col_var_list, *col_list, sym_node); + + sym_node->copy_val = copy_val; + + /* Fill in the field_no fields in sym_node */ + + sym_node->field_nos[SYM_CLUST_FIELD_NO] = dict_index_get_nth_col_pos( + dict_table_get_first_index(index->table), sym_node->col_no); + if (!dict_index_is_clust(index)) { + + ut_a(plan); + + col_pos = dict_index_get_nth_col_pos(index, sym_node->col_no); + + if (col_pos == ULINT_UNDEFINED) { + + plan->must_get_clust = TRUE; + } + + sym_node->field_nos[SYM_SEC_FIELD_NO] = col_pos; + } +} + +/*********************************************************************** +Looks for occurrences of the columns of the table in conditions which are +not yet determined AFTER the join operation has fetched a row in the ith +table. The values for these column must be copied to dynamic memory for +later use. */ +static +void +opt_find_copy_cols( +/*===============*/ + sel_node_t* sel_node, /* in: select node */ + ulint i, /* in: ith table in the join */ + func_node_t* search_cond) /* in: search condition or NULL */ +{ + func_node_t* new_cond; + plan_t* plan; + + if (search_cond == NULL) { + + return; + } + + ut_ad(que_node_get_type(search_cond) == QUE_NODE_FUNC); + + if (search_cond->func == PARS_AND_TOKEN) { + new_cond = search_cond->args; + + opt_find_copy_cols(sel_node, i, new_cond); + + new_cond = que_node_get_next(new_cond); + + opt_find_copy_cols(sel_node, i, new_cond); + + return; + } + + if (!opt_check_exp_determined_before(search_cond, sel_node, i + 1)) { + + /* Any ith table columns occurring in search_cond should be + copied, as this condition cannot be tested already on the + fetch from the ith table */ + + plan = sel_node_get_nth_plan(sel_node, i); + + opt_find_all_cols(TRUE, plan->index, &(plan->columns), plan, + search_cond); + } +} + +/*********************************************************************** +Classifies the table columns according to whether we use the column only while +holding the latch on the page, or whether we have to copy the column value to +dynamic memory. Puts the first occurrence of a column to either list in the +plan node, and puts indirections to later occurrences of the column. */ +static +void +opt_classify_cols( +/*==============*/ + sel_node_t* sel_node, /* in: select node */ + ulint i) /* in: ith table in the join */ +{ + plan_t* plan; + que_node_t* exp; + + plan = sel_node_get_nth_plan(sel_node, i); + + /* The final value of the following field will depend on the + environment of the select statement: */ + + plan->must_get_clust = FALSE; + + UT_LIST_INIT(plan->columns); + + /* All select list columns should be copied: therefore TRUE as the + first argument */ + + exp = sel_node->select_list; + + while (exp) { + opt_find_all_cols(TRUE, plan->index, &(plan->columns), plan, + exp); + exp = que_node_get_next(exp); + } + + opt_find_copy_cols(sel_node, i, sel_node->search_cond); + + /* All remaining columns in the search condition are temporary + columns: therefore FALSE */ + + opt_find_all_cols(FALSE, plan->index, &(plan->columns), plan, + sel_node->search_cond); +} + +/*********************************************************************** +Fills in the info in plan which is used in accessing a clustered index +record. The columns must already be classified for the plan node. */ +static +void +opt_clust_access( +/*=============*/ + sel_node_t* sel_node, /* in: select node */ + ulint n) /* in: nth table in select */ +{ + plan_t* plan; + dict_table_t* table; + dict_index_t* clust_index; + dict_index_t* index; + mem_heap_t* heap; + ulint n_fields; + ulint pos; + ulint i; + + plan = sel_node_get_nth_plan(sel_node, n); + + index = plan->index; + + /* The final value of the following field depends on the environment + of the select statement: */ + + plan->no_prefetch = FALSE; + + if (dict_index_is_clust(index)) { + plan->clust_map = NULL; + plan->clust_ref = NULL; + + return; + } + + table = index->table; + + clust_index = dict_table_get_first_index(table); + + n_fields = dict_index_get_n_unique(clust_index); + + heap = pars_sym_tab_global->heap; + + plan->clust_ref = dtuple_create(heap, n_fields); + + dict_index_copy_types(plan->clust_ref, clust_index, n_fields); + + plan->clust_map = mem_heap_alloc(heap, n_fields * sizeof(ulint)); + + for (i = 0; i < n_fields; i++) { + pos = dict_index_get_nth_field_pos(index, clust_index, i); + + ut_a(pos != ULINT_UNDEFINED); + + /* We optimize here only queries to InnoDB's internal system + tables, and they should not contain column prefix indexes. */ + + if (dict_index_get_nth_field(index, pos)->prefix_len != 0 + || dict_index_get_nth_field(clust_index, i) + ->prefix_len != 0) { + fprintf(stderr, + "InnoDB: Error in pars0opt.c:" + " table %s has prefix_len != 0\n", + index->table_name); + } + + *(plan->clust_map + i) = pos; + + ut_ad(pos != ULINT_UNDEFINED); + } +} + +/*********************************************************************** +Optimizes a select. Decides which indexes to tables to use. The tables +are accessed in the order that they were written to the FROM part in the +select statement. */ +UNIV_INTERN +void +opt_search_plan( +/*============*/ + sel_node_t* sel_node) /* in: parsed select node */ +{ + sym_node_t* table_node; + dict_table_t* table; + order_node_t* order_by; + ulint i; + + sel_node->plans = mem_heap_alloc(pars_sym_tab_global->heap, + sel_node->n_tables * sizeof(plan_t)); + + /* Analyze the search condition to find out what we know at each + join stage about the conditions that the columns of a table should + satisfy */ + + table_node = sel_node->table_list; + + if (sel_node->order_by == NULL) { + sel_node->asc = TRUE; + } else { + order_by = sel_node->order_by; + + sel_node->asc = order_by->asc; + } + + for (i = 0; i < sel_node->n_tables; i++) { + + table = table_node->table; + + /* Choose index through which to access the table */ + + opt_search_plan_for_table(sel_node, i, table); + + /* Determine the search condition conjuncts we can test at + this table; normalize the end conditions */ + + opt_determine_and_normalize_test_conds(sel_node, i); + + table_node = que_node_get_next(table_node); + } + + table_node = sel_node->table_list; + + for (i = 0; i < sel_node->n_tables; i++) { + + /* Classify the table columns into those we only need to access + but not copy, and to those we must copy to dynamic memory */ + + opt_classify_cols(sel_node, i); + + /* Calculate possible info for accessing the clustered index + record */ + + opt_clust_access(sel_node, i); + + table_node = que_node_get_next(table_node); + } + + /* Check that the plan obeys a possible order-by clause: if not, + an assertion error occurs */ + + opt_check_order_by(sel_node); + +#ifdef UNIV_SQL_DEBUG + opt_print_query_plan(sel_node); +#endif +} + +/************************************************************************ +Prints info of a query plan. */ +UNIV_INTERN +void +opt_print_query_plan( +/*=================*/ + sel_node_t* sel_node) /* in: select node */ +{ + plan_t* plan; + ulint n_fields; + ulint i; + + fputs("QUERY PLAN FOR A SELECT NODE\n", stderr); + + fputs(sel_node->asc ? "Asc. search; " : "Desc. search; ", stderr); + + if (sel_node->set_x_locks) { + fputs("sets row x-locks; ", stderr); + ut_a(sel_node->row_lock_mode == LOCK_X); + ut_a(!sel_node->consistent_read); + } else if (sel_node->consistent_read) { + fputs("consistent read; ", stderr); + } else { + ut_a(sel_node->row_lock_mode == LOCK_S); + fputs("sets row s-locks; ", stderr); + } + + putc('\n', stderr); + + for (i = 0; i < sel_node->n_tables; i++) { + plan = sel_node_get_nth_plan(sel_node, i); + + if (plan->tuple) { + n_fields = dtuple_get_n_fields(plan->tuple); + } else { + n_fields = 0; + } + + fputs("Table ", stderr); + dict_index_name_print(stderr, NULL, plan->index); + fprintf(stderr,"; exact m. %lu, match %lu, end conds %lu\n", + (unsigned long) plan->n_exact_match, + (unsigned long) n_fields, + (unsigned long) UT_LIST_GET_LEN(plan->end_conds)); + } +} diff --git a/storage/xtradb/pars/pars0pars.c b/storage/xtradb/pars/pars0pars.c new file mode 100644 index 00000000000..62ae3b3d09b --- /dev/null +++ b/storage/xtradb/pars/pars0pars.c @@ -0,0 +1,2205 @@ +/***************************************************************************** + +Copyright (c) 1996, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/****************************************************** +SQL parser + +Created 11/19/1996 Heikki Tuuri +*******************************************************/ + +/* Historical note: Innobase executed its first SQL string (CREATE TABLE) +on 1/27/1998 */ + +#include "pars0pars.h" + +#ifdef UNIV_NONINL +#include "pars0pars.ic" +#endif + +#include "row0sel.h" +#include "row0ins.h" +#include "row0upd.h" +#include "dict0dict.h" +#include "dict0mem.h" +#include "dict0crea.h" +#include "que0que.h" +#include "pars0grm.h" +#include "pars0opt.h" +#include "data0data.h" +#include "data0type.h" +#include "trx0trx.h" +#include "trx0roll.h" +#include "lock0lock.h" +#include "eval0eval.h" + +#ifdef UNIV_SQL_DEBUG +/* If the following is set TRUE, the lexer will print the SQL string +as it tokenizes it */ + +ibool pars_print_lexed = FALSE; +#endif /* UNIV_SQL_DEBUG */ + +/* Global variable used while parsing a single procedure or query : the code is +NOT re-entrant */ +UNIV_INTERN sym_tab_t* pars_sym_tab_global; + +/* Global variables used to denote certain reserved words, used in +constructing the parsing tree */ + +UNIV_INTERN pars_res_word_t pars_to_char_token = {PARS_TO_CHAR_TOKEN}; +UNIV_INTERN pars_res_word_t pars_to_number_token = {PARS_TO_NUMBER_TOKEN}; +UNIV_INTERN pars_res_word_t pars_to_binary_token = {PARS_TO_BINARY_TOKEN}; +UNIV_INTERN pars_res_word_t pars_binary_to_number_token = {PARS_BINARY_TO_NUMBER_TOKEN}; +UNIV_INTERN pars_res_word_t pars_substr_token = {PARS_SUBSTR_TOKEN}; +UNIV_INTERN pars_res_word_t pars_replstr_token = {PARS_REPLSTR_TOKEN}; +UNIV_INTERN pars_res_word_t pars_concat_token = {PARS_CONCAT_TOKEN}; +UNIV_INTERN pars_res_word_t pars_instr_token = {PARS_INSTR_TOKEN}; +UNIV_INTERN pars_res_word_t pars_length_token = {PARS_LENGTH_TOKEN}; +UNIV_INTERN pars_res_word_t pars_sysdate_token = {PARS_SYSDATE_TOKEN}; +UNIV_INTERN pars_res_word_t pars_printf_token = {PARS_PRINTF_TOKEN}; +UNIV_INTERN pars_res_word_t pars_assert_token = {PARS_ASSERT_TOKEN}; +UNIV_INTERN pars_res_word_t pars_rnd_token = {PARS_RND_TOKEN}; +UNIV_INTERN pars_res_word_t pars_rnd_str_token = {PARS_RND_STR_TOKEN}; +UNIV_INTERN pars_res_word_t pars_count_token = {PARS_COUNT_TOKEN}; +UNIV_INTERN pars_res_word_t pars_sum_token = {PARS_SUM_TOKEN}; +UNIV_INTERN pars_res_word_t pars_distinct_token = {PARS_DISTINCT_TOKEN}; +UNIV_INTERN pars_res_word_t pars_binary_token = {PARS_BINARY_TOKEN}; +UNIV_INTERN pars_res_word_t pars_blob_token = {PARS_BLOB_TOKEN}; +UNIV_INTERN pars_res_word_t pars_int_token = {PARS_INT_TOKEN}; +UNIV_INTERN pars_res_word_t pars_char_token = {PARS_CHAR_TOKEN}; +UNIV_INTERN pars_res_word_t pars_float_token = {PARS_FLOAT_TOKEN}; +UNIV_INTERN pars_res_word_t pars_update_token = {PARS_UPDATE_TOKEN}; +UNIV_INTERN pars_res_word_t pars_asc_token = {PARS_ASC_TOKEN}; +UNIV_INTERN pars_res_word_t pars_desc_token = {PARS_DESC_TOKEN}; +UNIV_INTERN pars_res_word_t pars_open_token = {PARS_OPEN_TOKEN}; +UNIV_INTERN pars_res_word_t pars_close_token = {PARS_CLOSE_TOKEN}; +UNIV_INTERN pars_res_word_t pars_share_token = {PARS_SHARE_TOKEN}; +UNIV_INTERN pars_res_word_t pars_unique_token = {PARS_UNIQUE_TOKEN}; +UNIV_INTERN pars_res_word_t pars_clustered_token = {PARS_CLUSTERED_TOKEN}; + +/* Global variable used to denote the '*' in SELECT * FROM.. */ +#define PARS_STAR_DENOTER 12345678 +UNIV_INTERN ulint pars_star_denoter = PARS_STAR_DENOTER; + + +/************************************************************************* +Determines the class of a function code. */ +static +ulint +pars_func_get_class( +/*================*/ + /* out: function class: PARS_FUNC_ARITH, ... */ + int func) /* in: function code: '=', PARS_GE_TOKEN, ... */ +{ + switch (func) { + case '+': case '-': case '*': case '/': + return(PARS_FUNC_ARITH); + + case '=': case '<': case '>': + case PARS_GE_TOKEN: case PARS_LE_TOKEN: case PARS_NE_TOKEN: + return(PARS_FUNC_CMP); + + case PARS_AND_TOKEN: case PARS_OR_TOKEN: case PARS_NOT_TOKEN: + return(PARS_FUNC_LOGICAL); + + case PARS_COUNT_TOKEN: case PARS_SUM_TOKEN: + return(PARS_FUNC_AGGREGATE); + + case PARS_TO_CHAR_TOKEN: + case PARS_TO_NUMBER_TOKEN: + case PARS_TO_BINARY_TOKEN: + case PARS_BINARY_TO_NUMBER_TOKEN: + case PARS_SUBSTR_TOKEN: + case PARS_CONCAT_TOKEN: + case PARS_LENGTH_TOKEN: + case PARS_INSTR_TOKEN: + case PARS_SYSDATE_TOKEN: + case PARS_NOTFOUND_TOKEN: + case PARS_PRINTF_TOKEN: + case PARS_ASSERT_TOKEN: + case PARS_RND_TOKEN: + case PARS_RND_STR_TOKEN: + case PARS_REPLSTR_TOKEN: + return(PARS_FUNC_PREDEFINED); + + default: + return(PARS_FUNC_OTHER); + } +} + +/************************************************************************* +Parses an operator or predefined function expression. */ +static +func_node_t* +pars_func_low( +/*==========*/ + /* out, own: function node in a query tree */ + int func, /* in: function token code */ + que_node_t* arg) /* in: first argument in the argument list */ +{ + func_node_t* node; + + node = mem_heap_alloc(pars_sym_tab_global->heap, sizeof(func_node_t)); + + node->common.type = QUE_NODE_FUNC; + dfield_set_data(&(node->common.val), NULL, 0); + node->common.val_buf_size = 0; + + node->func = func; + + node->class = pars_func_get_class(func); + + node->args = arg; + + UT_LIST_ADD_LAST(func_node_list, pars_sym_tab_global->func_node_list, + node); + return(node); +} + +/************************************************************************* +Parses a function expression. */ +UNIV_INTERN +func_node_t* +pars_func( +/*======*/ + /* out, own: function node in a query tree */ + que_node_t* res_word,/* in: function name reserved word */ + que_node_t* arg) /* in: first argument in the argument list */ +{ + return(pars_func_low(((pars_res_word_t*)res_word)->code, arg)); +} + +/************************************************************************* +Parses an operator expression. */ +UNIV_INTERN +func_node_t* +pars_op( +/*====*/ + /* out, own: function node in a query tree */ + int func, /* in: operator token code */ + que_node_t* arg1, /* in: first argument */ + que_node_t* arg2) /* in: second argument or NULL for an unary + operator */ +{ + que_node_list_add_last(NULL, arg1); + + if (arg2) { + que_node_list_add_last(arg1, arg2); + } + + return(pars_func_low(func, arg1)); +} + +/************************************************************************* +Parses an ORDER BY clause. Order by a single column only is supported. */ +UNIV_INTERN +order_node_t* +pars_order_by( +/*==========*/ + /* out, own: order-by node in a query tree */ + sym_node_t* column, /* in: column name */ + pars_res_word_t* asc) /* in: &pars_asc_token or pars_desc_token */ +{ + order_node_t* node; + + node = mem_heap_alloc(pars_sym_tab_global->heap, sizeof(order_node_t)); + + node->common.type = QUE_NODE_ORDER; + + node->column = column; + + if (asc == &pars_asc_token) { + node->asc = TRUE; + } else { + ut_a(asc == &pars_desc_token); + node->asc = FALSE; + } + + return(node); +} + +/************************************************************************* +Determine if a data type is a built-in string data type of the InnoDB +SQL parser. */ +static +ibool +pars_is_string_type( +/*================*/ + /* out: TRUE if string data type */ + ulint mtype) /* in: main data type */ +{ + switch (mtype) { + case DATA_VARCHAR: case DATA_CHAR: + case DATA_FIXBINARY: case DATA_BINARY: + return(TRUE); + } + + return(FALSE); +} + +/************************************************************************* +Resolves the data type of a function in an expression. The argument data +types must already be resolved. */ +static +void +pars_resolve_func_data_type( +/*========================*/ + func_node_t* node) /* in: function node */ +{ + que_node_t* arg; + + ut_a(que_node_get_type(node) == QUE_NODE_FUNC); + + arg = node->args; + + switch (node->func) { + case PARS_SUM_TOKEN: + case '+': case '-': case '*': case '/': + /* Inherit the data type from the first argument (which must + not be the SQL null literal whose type is DATA_ERROR) */ + + dtype_copy(que_node_get_data_type(node), + que_node_get_data_type(arg)); + + ut_a(dtype_get_mtype(que_node_get_data_type(node)) + == DATA_INT); + break; + + case PARS_COUNT_TOKEN: + ut_a(arg); + dtype_set(que_node_get_data_type(node), DATA_INT, 0, 4); + break; + + case PARS_TO_CHAR_TOKEN: + case PARS_RND_STR_TOKEN: + ut_a(dtype_get_mtype(que_node_get_data_type(arg)) == DATA_INT); + dtype_set(que_node_get_data_type(node), DATA_VARCHAR, + DATA_ENGLISH, 0); + break; + + case PARS_TO_BINARY_TOKEN: + if (dtype_get_mtype(que_node_get_data_type(arg)) == DATA_INT) { + dtype_set(que_node_get_data_type(node), DATA_VARCHAR, + DATA_ENGLISH, 0); + } else { + dtype_set(que_node_get_data_type(node), DATA_BINARY, + 0, 0); + } + break; + + case PARS_TO_NUMBER_TOKEN: + case PARS_BINARY_TO_NUMBER_TOKEN: + case PARS_LENGTH_TOKEN: + case PARS_INSTR_TOKEN: + ut_a(pars_is_string_type(que_node_get_data_type(arg)->mtype)); + dtype_set(que_node_get_data_type(node), DATA_INT, 0, 4); + break; + + case PARS_SYSDATE_TOKEN: + ut_a(arg == NULL); + dtype_set(que_node_get_data_type(node), DATA_INT, 0, 4); + break; + + case PARS_SUBSTR_TOKEN: + case PARS_CONCAT_TOKEN: + ut_a(pars_is_string_type(que_node_get_data_type(arg)->mtype)); + dtype_set(que_node_get_data_type(node), DATA_VARCHAR, + DATA_ENGLISH, 0); + break; + + case '>': case '<': case '=': + case PARS_GE_TOKEN: + case PARS_LE_TOKEN: + case PARS_NE_TOKEN: + case PARS_AND_TOKEN: + case PARS_OR_TOKEN: + case PARS_NOT_TOKEN: + case PARS_NOTFOUND_TOKEN: + + /* We currently have no iboolean type: use integer type */ + dtype_set(que_node_get_data_type(node), DATA_INT, 0, 4); + break; + + case PARS_RND_TOKEN: + ut_a(dtype_get_mtype(que_node_get_data_type(arg)) == DATA_INT); + dtype_set(que_node_get_data_type(node), DATA_INT, 0, 4); + break; + + default: + ut_error; + } +} + +/************************************************************************* +Resolves the meaning of variables in an expression and the data types of +functions. It is an error if some identifier cannot be resolved here. */ +static +void +pars_resolve_exp_variables_and_types( +/*=================================*/ + sel_node_t* select_node, /* in: select node or NULL; if + this is not NULL then the variable + sym nodes are added to the + copy_variables list of select_node */ + que_node_t* exp_node) /* in: expression */ +{ + func_node_t* func_node; + que_node_t* arg; + sym_node_t* sym_node; + sym_node_t* node; + + ut_a(exp_node); + + if (que_node_get_type(exp_node) == QUE_NODE_FUNC) { + func_node = exp_node; + + arg = func_node->args; + + while (arg) { + pars_resolve_exp_variables_and_types(select_node, arg); + + arg = que_node_get_next(arg); + } + + pars_resolve_func_data_type(func_node); + + return; + } + + ut_a(que_node_get_type(exp_node) == QUE_NODE_SYMBOL); + + sym_node = exp_node; + + if (sym_node->resolved) { + + return; + } + + /* Not resolved yet: look in the symbol table for a variable + or a cursor or a function with the same name */ + + node = UT_LIST_GET_FIRST(pars_sym_tab_global->sym_list); + + while (node) { + if (node->resolved + && ((node->token_type == SYM_VAR) + || (node->token_type == SYM_CURSOR) + || (node->token_type == SYM_FUNCTION)) + && node->name + && (sym_node->name_len == node->name_len) + && (ut_memcmp(sym_node->name, node->name, + node->name_len) == 0)) { + + /* Found a variable or a cursor declared with + the same name */ + + break; + } + + node = UT_LIST_GET_NEXT(sym_list, node); + } + + if (!node) { + fprintf(stderr, "PARSER ERROR: Unresolved identifier %s\n", + sym_node->name); + } + + ut_a(node); + + sym_node->resolved = TRUE; + sym_node->token_type = SYM_IMPLICIT_VAR; + sym_node->alias = node; + sym_node->indirection = node; + + if (select_node) { + UT_LIST_ADD_LAST(col_var_list, select_node->copy_variables, + sym_node); + } + + dfield_set_type(que_node_get_val(sym_node), + que_node_get_data_type(node)); +} + +/************************************************************************* +Resolves the meaning of variables in an expression list. It is an error if +some identifier cannot be resolved here. Resolves also the data types of +functions. */ +static +void +pars_resolve_exp_list_variables_and_types( +/*======================================*/ + sel_node_t* select_node, /* in: select node or NULL */ + que_node_t* exp_node) /* in: expression list first node, or + NULL */ +{ + while (exp_node) { + pars_resolve_exp_variables_and_types(select_node, exp_node); + + exp_node = que_node_get_next(exp_node); + } +} + +/************************************************************************* +Resolves the columns in an expression. */ +static +void +pars_resolve_exp_columns( +/*=====================*/ + sym_node_t* table_node, /* in: first node in a table list */ + que_node_t* exp_node) /* in: expression */ +{ + func_node_t* func_node; + que_node_t* arg; + sym_node_t* sym_node; + dict_table_t* table; + sym_node_t* t_node; + ulint n_cols; + ulint i; + + ut_a(exp_node); + + if (que_node_get_type(exp_node) == QUE_NODE_FUNC) { + func_node = exp_node; + + arg = func_node->args; + + while (arg) { + pars_resolve_exp_columns(table_node, arg); + + arg = que_node_get_next(arg); + } + + return; + } + + ut_a(que_node_get_type(exp_node) == QUE_NODE_SYMBOL); + + sym_node = exp_node; + + if (sym_node->resolved) { + + return; + } + + /* Not resolved yet: look in the table list for a column with the + same name */ + + t_node = table_node; + + while (t_node) { + table = t_node->table; + + n_cols = dict_table_get_n_cols(table); + + for (i = 0; i < n_cols; i++) { + const dict_col_t* col + = dict_table_get_nth_col(table, i); + const char* col_name + = dict_table_get_col_name(table, i); + + if ((sym_node->name_len == ut_strlen(col_name)) + && (0 == ut_memcmp(sym_node->name, col_name, + sym_node->name_len))) { + /* Found */ + sym_node->resolved = TRUE; + sym_node->token_type = SYM_COLUMN; + sym_node->table = table; + sym_node->col_no = i; + sym_node->prefetch_buf = NULL; + + dict_col_copy_type( + col, + dfield_get_type(&sym_node + ->common.val)); + + return; + } + } + + t_node = que_node_get_next(t_node); + } +} + +/************************************************************************* +Resolves the meaning of columns in an expression list. */ +static +void +pars_resolve_exp_list_columns( +/*==========================*/ + sym_node_t* table_node, /* in: first node in a table list */ + que_node_t* exp_node) /* in: expression list first node, or + NULL */ +{ + while (exp_node) { + pars_resolve_exp_columns(table_node, exp_node); + + exp_node = que_node_get_next(exp_node); + } +} + +/************************************************************************* +Retrieves the table definition for a table name id. */ +static +void +pars_retrieve_table_def( +/*====================*/ + sym_node_t* sym_node) /* in: table node */ +{ + const char* table_name; + + ut_a(sym_node); + ut_a(que_node_get_type(sym_node) == QUE_NODE_SYMBOL); + + sym_node->resolved = TRUE; + sym_node->token_type = SYM_TABLE; + + table_name = (const char*) sym_node->name; + + sym_node->table = dict_table_get_low(table_name); + + ut_a(sym_node->table); +} + +/************************************************************************* +Retrieves the table definitions for a list of table name ids. */ +static +ulint +pars_retrieve_table_list_defs( +/*==========================*/ + /* out: number of tables */ + sym_node_t* sym_node) /* in: first table node in list */ +{ + ulint count = 0; + + if (sym_node == NULL) { + + return(count); + } + + while (sym_node) { + pars_retrieve_table_def(sym_node); + + count++; + + sym_node = que_node_get_next(sym_node); + } + + return(count); +} + +/************************************************************************* +Adds all columns to the select list if the query is SELECT * FROM ... */ +static +void +pars_select_all_columns( +/*====================*/ + sel_node_t* select_node) /* in: select node already containing + the table list */ +{ + sym_node_t* col_node; + sym_node_t* table_node; + dict_table_t* table; + ulint i; + + select_node->select_list = NULL; + + table_node = select_node->table_list; + + while (table_node) { + table = table_node->table; + + for (i = 0; i < dict_table_get_n_user_cols(table); i++) { + const char* col_name = dict_table_get_col_name( + table, i); + + col_node = sym_tab_add_id(pars_sym_tab_global, + (byte*)col_name, + ut_strlen(col_name)); + + select_node->select_list = que_node_list_add_last( + select_node->select_list, col_node); + } + + table_node = que_node_get_next(table_node); + } +} + +/************************************************************************* +Parses a select list; creates a query graph node for the whole SELECT +statement. */ +UNIV_INTERN +sel_node_t* +pars_select_list( +/*=============*/ + /* out, own: select node in a query + tree */ + que_node_t* select_list, /* in: select list */ + sym_node_t* into_list) /* in: variables list or NULL */ +{ + sel_node_t* node; + + node = sel_node_create(pars_sym_tab_global->heap); + + node->select_list = select_list; + node->into_list = into_list; + + pars_resolve_exp_list_variables_and_types(NULL, into_list); + + return(node); +} + +/************************************************************************* +Checks if the query is an aggregate query, in which case the selct list must +contain only aggregate function items. */ +static +void +pars_check_aggregate( +/*=================*/ + sel_node_t* select_node) /* in: select node already containing + the select list */ +{ + que_node_t* exp_node; + func_node_t* func_node; + ulint n_nodes = 0; + ulint n_aggregate_nodes = 0; + + exp_node = select_node->select_list; + + while (exp_node) { + + n_nodes++; + + if (que_node_get_type(exp_node) == QUE_NODE_FUNC) { + + func_node = exp_node; + + if (func_node->class == PARS_FUNC_AGGREGATE) { + + n_aggregate_nodes++; + } + } + + exp_node = que_node_get_next(exp_node); + } + + if (n_aggregate_nodes > 0) { + ut_a(n_nodes == n_aggregate_nodes); + + select_node->is_aggregate = TRUE; + } else { + select_node->is_aggregate = FALSE; + } +} + +/************************************************************************* +Parses a select statement. */ +UNIV_INTERN +sel_node_t* +pars_select_statement( +/*==================*/ + /* out, own: select node in a query + tree */ + sel_node_t* select_node, /* in: select node already containing + the select list */ + sym_node_t* table_list, /* in: table list */ + que_node_t* search_cond, /* in: search condition or NULL */ + pars_res_word_t* for_update, /* in: NULL or &pars_update_token */ + pars_res_word_t* lock_shared, /* in: NULL or &pars_share_token */ + order_node_t* order_by) /* in: NULL or an order-by node */ +{ + select_node->state = SEL_NODE_OPEN; + + select_node->table_list = table_list; + select_node->n_tables = pars_retrieve_table_list_defs(table_list); + + if (select_node->select_list == &pars_star_denoter) { + + /* SELECT * FROM ... */ + pars_select_all_columns(select_node); + } + + if (select_node->into_list) { + ut_a(que_node_list_get_len(select_node->into_list) + == que_node_list_get_len(select_node->select_list)); + } + + UT_LIST_INIT(select_node->copy_variables); + + pars_resolve_exp_list_columns(table_list, select_node->select_list); + pars_resolve_exp_list_variables_and_types(select_node, + select_node->select_list); + pars_check_aggregate(select_node); + + select_node->search_cond = search_cond; + + if (search_cond) { + pars_resolve_exp_columns(table_list, search_cond); + pars_resolve_exp_variables_and_types(select_node, search_cond); + } + + if (for_update) { + ut_a(!lock_shared); + + select_node->set_x_locks = TRUE; + select_node->row_lock_mode = LOCK_X; + + select_node->consistent_read = FALSE; + select_node->read_view = NULL; + } else if (lock_shared){ + select_node->set_x_locks = FALSE; + select_node->row_lock_mode = LOCK_S; + + select_node->consistent_read = FALSE; + select_node->read_view = NULL; + } else { + select_node->set_x_locks = FALSE; + select_node->row_lock_mode = LOCK_S; + + select_node->consistent_read = TRUE; + } + + select_node->order_by = order_by; + + if (order_by) { + pars_resolve_exp_columns(table_list, order_by->column); + } + + /* The final value of the following fields depend on the environment + where the select statement appears: */ + + select_node->can_get_updated = FALSE; + select_node->explicit_cursor = NULL; + + opt_search_plan(select_node); + + return(select_node); +} + +/************************************************************************* +Parses a cursor declaration. */ +UNIV_INTERN +que_node_t* +pars_cursor_declaration( +/*====================*/ + /* out: sym_node */ + sym_node_t* sym_node, /* in: cursor id node in the symbol + table */ + sel_node_t* select_node) /* in: select node */ +{ + sym_node->resolved = TRUE; + sym_node->token_type = SYM_CURSOR; + sym_node->cursor_def = select_node; + + select_node->state = SEL_NODE_CLOSED; + select_node->explicit_cursor = sym_node; + + return(sym_node); +} + +/************************************************************************* +Parses a function declaration. */ +UNIV_INTERN +que_node_t* +pars_function_declaration( +/*======================*/ + /* out: sym_node */ + sym_node_t* sym_node) /* in: function id node in the symbol + table */ +{ + sym_node->resolved = TRUE; + sym_node->token_type = SYM_FUNCTION; + + /* Check that the function exists. */ + ut_a(pars_info_get_user_func(pars_sym_tab_global->info, + sym_node->name)); + + return(sym_node); +} + +/************************************************************************* +Parses a delete or update statement start. */ +UNIV_INTERN +upd_node_t* +pars_update_statement_start( +/*========================*/ + /* out, own: update node in a query + tree */ + ibool is_delete, /* in: TRUE if delete */ + sym_node_t* table_sym, /* in: table name node */ + col_assign_node_t* col_assign_list)/* in: column assignment list, NULL + if delete */ +{ + upd_node_t* node; + + node = upd_node_create(pars_sym_tab_global->heap); + + node->is_delete = is_delete; + + node->table_sym = table_sym; + node->col_assign_list = col_assign_list; + + return(node); +} + +/************************************************************************* +Parses a column assignment in an update. */ +UNIV_INTERN +col_assign_node_t* +pars_column_assignment( +/*===================*/ + /* out: column assignment node */ + sym_node_t* column, /* in: column to assign */ + que_node_t* exp) /* in: value to assign */ +{ + col_assign_node_t* node; + + node = mem_heap_alloc(pars_sym_tab_global->heap, + sizeof(col_assign_node_t)); + node->common.type = QUE_NODE_COL_ASSIGNMENT; + + node->col = column; + node->val = exp; + + return(node); +} + +/************************************************************************* +Processes an update node assignment list. */ +static +void +pars_process_assign_list( +/*=====================*/ + upd_node_t* node) /* in: update node */ +{ + col_assign_node_t* col_assign_list; + sym_node_t* table_sym; + col_assign_node_t* assign_node; + upd_field_t* upd_field; + dict_index_t* clust_index; + sym_node_t* col_sym; + ulint changes_ord_field; + ulint changes_field_size; + ulint n_assigns; + ulint i; + + table_sym = node->table_sym; + col_assign_list = node->col_assign_list; + clust_index = dict_table_get_first_index(node->table); + + assign_node = col_assign_list; + n_assigns = 0; + + while (assign_node) { + pars_resolve_exp_columns(table_sym, assign_node->col); + pars_resolve_exp_columns(table_sym, assign_node->val); + pars_resolve_exp_variables_and_types(NULL, assign_node->val); +#if 0 + ut_a(dtype_get_mtype( + dfield_get_type(que_node_get_val( + assign_node->col))) + == dtype_get_mtype( + dfield_get_type(que_node_get_val( + assign_node->val)))); +#endif + + /* Add to the update node all the columns found in assignment + values as columns to copy: therefore, TRUE */ + + opt_find_all_cols(TRUE, clust_index, &(node->columns), NULL, + assign_node->val); + n_assigns++; + + assign_node = que_node_get_next(assign_node); + } + + node->update = upd_create(n_assigns, pars_sym_tab_global->heap); + + assign_node = col_assign_list; + + changes_field_size = UPD_NODE_NO_SIZE_CHANGE; + + for (i = 0; i < n_assigns; i++) { + upd_field = upd_get_nth_field(node->update, i); + + col_sym = assign_node->col; + + upd_field_set_field_no(upd_field, dict_index_get_nth_col_pos( + clust_index, col_sym->col_no), + clust_index, NULL); + upd_field->exp = assign_node->val; + + if (!dict_col_get_fixed_size( + dict_index_get_nth_col(clust_index, + upd_field->field_no))) { + changes_field_size = 0; + } + + assign_node = que_node_get_next(assign_node); + } + + /* Find out if the update can modify an ordering field in any index */ + + changes_ord_field = UPD_NODE_NO_ORD_CHANGE; + + if (row_upd_changes_some_index_ord_field_binary(node->table, + node->update)) { + changes_ord_field = 0; + } + + node->cmpl_info = changes_ord_field | changes_field_size; +} + +/************************************************************************* +Parses an update or delete statement. */ +UNIV_INTERN +upd_node_t* +pars_update_statement( +/*==================*/ + /* out, own: update node in a query + tree */ + upd_node_t* node, /* in: update node */ + sym_node_t* cursor_sym, /* in: pointer to a cursor entry in + the symbol table or NULL */ + que_node_t* search_cond) /* in: search condition or NULL */ +{ + sym_node_t* table_sym; + sel_node_t* sel_node; + plan_t* plan; + + table_sym = node->table_sym; + + pars_retrieve_table_def(table_sym); + node->table = table_sym->table; + + UT_LIST_INIT(node->columns); + + /* Make the single table node into a list of table nodes of length 1 */ + + que_node_list_add_last(NULL, table_sym); + + if (cursor_sym) { + pars_resolve_exp_variables_and_types(NULL, cursor_sym); + + sel_node = cursor_sym->alias->cursor_def; + + node->searched_update = FALSE; + } else { + sel_node = pars_select_list(NULL, NULL); + + pars_select_statement(sel_node, table_sym, search_cond, NULL, + &pars_share_token, NULL); + node->searched_update = TRUE; + sel_node->common.parent = node; + } + + node->select = sel_node; + + ut_a(!node->is_delete || (node->col_assign_list == NULL)); + ut_a(node->is_delete || (node->col_assign_list != NULL)); + + if (node->is_delete) { + node->cmpl_info = 0; + } else { + pars_process_assign_list(node); + } + + if (node->searched_update) { + node->has_clust_rec_x_lock = TRUE; + sel_node->set_x_locks = TRUE; + sel_node->row_lock_mode = LOCK_X; + } else { + node->has_clust_rec_x_lock = sel_node->set_x_locks; + } + + ut_a(sel_node->n_tables == 1); + ut_a(sel_node->consistent_read == FALSE); + ut_a(sel_node->order_by == NULL); + ut_a(sel_node->is_aggregate == FALSE); + + sel_node->can_get_updated = TRUE; + + node->state = UPD_NODE_UPDATE_CLUSTERED; + + plan = sel_node_get_nth_plan(sel_node, 0); + + plan->no_prefetch = TRUE; + + if (!dict_index_is_clust(plan->index)) { + + plan->must_get_clust = TRUE; + + node->pcur = &(plan->clust_pcur); + } else { + node->pcur = &(plan->pcur); + } + + return(node); +} + +/************************************************************************* +Parses an insert statement. */ +UNIV_INTERN +ins_node_t* +pars_insert_statement( +/*==================*/ + /* out, own: update node in a query + tree */ + sym_node_t* table_sym, /* in: table name node */ + que_node_t* values_list, /* in: value expression list or NULL */ + sel_node_t* select) /* in: select condition or NULL */ +{ + ins_node_t* node; + dtuple_t* row; + ulint ins_type; + + ut_a(values_list || select); + ut_a(!values_list || !select); + + if (values_list) { + ins_type = INS_VALUES; + } else { + ins_type = INS_SEARCHED; + } + + pars_retrieve_table_def(table_sym); + + node = ins_node_create(ins_type, table_sym->table, + pars_sym_tab_global->heap); + + row = dtuple_create(pars_sym_tab_global->heap, + dict_table_get_n_cols(node->table)); + + dict_table_copy_types(row, table_sym->table); + + ins_node_set_new_row(node, row); + + node->select = select; + + if (select) { + select->common.parent = node; + + ut_a(que_node_list_get_len(select->select_list) + == dict_table_get_n_user_cols(table_sym->table)); + } + + node->values_list = values_list; + + if (node->values_list) { + pars_resolve_exp_list_variables_and_types(NULL, values_list); + + ut_a(que_node_list_get_len(values_list) + == dict_table_get_n_user_cols(table_sym->table)); + } + + return(node); +} + +/************************************************************************* +Set the type of a dfield. */ +static +void +pars_set_dfield_type( +/*=================*/ + dfield_t* dfield, /* in: dfield */ + pars_res_word_t* type, /* in: pointer to a type + token */ + ulint len, /* in: length, or 0 */ + ibool is_unsigned, /* in: if TRUE, column is + UNSIGNED. */ + ibool is_not_null) /* in: if TRUE, column is + NOT NULL. */ +{ + ulint flags = 0; + + if (is_not_null) { + flags |= DATA_NOT_NULL; + } + + if (is_unsigned) { + flags |= DATA_UNSIGNED; + } + + if (type == &pars_int_token) { + ut_a(len == 0); + + dtype_set(dfield_get_type(dfield), DATA_INT, flags, 4); + + } else if (type == &pars_char_token) { + ut_a(len == 0); + + dtype_set(dfield_get_type(dfield), DATA_VARCHAR, + DATA_ENGLISH | flags, 0); + } else if (type == &pars_binary_token) { + ut_a(len != 0); + + dtype_set(dfield_get_type(dfield), DATA_FIXBINARY, + DATA_BINARY_TYPE | flags, len); + } else if (type == &pars_blob_token) { + ut_a(len == 0); + + dtype_set(dfield_get_type(dfield), DATA_BLOB, + DATA_BINARY_TYPE | flags, 0); + } else { + ut_error; + } +} + +/************************************************************************* +Parses a variable declaration. */ +UNIV_INTERN +sym_node_t* +pars_variable_declaration( +/*======================*/ + /* out, own: symbol table node of type + SYM_VAR */ + sym_node_t* node, /* in: symbol table node allocated for the + id of the variable */ + pars_res_word_t* type) /* in: pointer to a type token */ +{ + node->resolved = TRUE; + node->token_type = SYM_VAR; + + node->param_type = PARS_NOT_PARAM; + + pars_set_dfield_type(que_node_get_val(node), type, 0, FALSE, FALSE); + + return(node); +} + +/************************************************************************* +Parses a procedure parameter declaration. */ +UNIV_INTERN +sym_node_t* +pars_parameter_declaration( +/*=======================*/ + /* out, own: symbol table node of type + SYM_VAR */ + sym_node_t* node, /* in: symbol table node allocated for the + id of the parameter */ + ulint param_type, + /* in: PARS_INPUT or PARS_OUTPUT */ + pars_res_word_t* type) /* in: pointer to a type token */ +{ + ut_a((param_type == PARS_INPUT) || (param_type == PARS_OUTPUT)); + + pars_variable_declaration(node, type); + + node->param_type = param_type; + + return(node); +} + +/************************************************************************* +Sets the parent field in a query node list. */ +static +void +pars_set_parent_in_list( +/*====================*/ + que_node_t* node_list, /* in: first node in a list */ + que_node_t* parent) /* in: parent value to set in all + nodes of the list */ +{ + que_common_t* common; + + common = node_list; + + while (common) { + common->parent = parent; + + common = que_node_get_next(common); + } +} + +/************************************************************************* +Parses an elsif element. */ +UNIV_INTERN +elsif_node_t* +pars_elsif_element( +/*===============*/ + /* out: elsif node */ + que_node_t* cond, /* in: if-condition */ + que_node_t* stat_list) /* in: statement list */ +{ + elsif_node_t* node; + + node = mem_heap_alloc(pars_sym_tab_global->heap, sizeof(elsif_node_t)); + + node->common.type = QUE_NODE_ELSIF; + + node->cond = cond; + + pars_resolve_exp_variables_and_types(NULL, cond); + + node->stat_list = stat_list; + + return(node); +} + +/************************************************************************* +Parses an if-statement. */ +UNIV_INTERN +if_node_t* +pars_if_statement( +/*==============*/ + /* out: if-statement node */ + que_node_t* cond, /* in: if-condition */ + que_node_t* stat_list, /* in: statement list */ + que_node_t* else_part) /* in: else-part statement list + or elsif element list */ +{ + if_node_t* node; + elsif_node_t* elsif_node; + + node = mem_heap_alloc(pars_sym_tab_global->heap, sizeof(if_node_t)); + + node->common.type = QUE_NODE_IF; + + node->cond = cond; + + pars_resolve_exp_variables_and_types(NULL, cond); + + node->stat_list = stat_list; + + if (else_part && (que_node_get_type(else_part) == QUE_NODE_ELSIF)) { + + /* There is a list of elsif conditions */ + + node->else_part = NULL; + node->elsif_list = else_part; + + elsif_node = else_part; + + while (elsif_node) { + pars_set_parent_in_list(elsif_node->stat_list, node); + + elsif_node = que_node_get_next(elsif_node); + } + } else { + node->else_part = else_part; + node->elsif_list = NULL; + + pars_set_parent_in_list(else_part, node); + } + + pars_set_parent_in_list(stat_list, node); + + return(node); +} + +/************************************************************************* +Parses a while-statement. */ +UNIV_INTERN +while_node_t* +pars_while_statement( +/*=================*/ + /* out: while-statement node */ + que_node_t* cond, /* in: while-condition */ + que_node_t* stat_list) /* in: statement list */ +{ + while_node_t* node; + + node = mem_heap_alloc(pars_sym_tab_global->heap, sizeof(while_node_t)); + + node->common.type = QUE_NODE_WHILE; + + node->cond = cond; + + pars_resolve_exp_variables_and_types(NULL, cond); + + node->stat_list = stat_list; + + pars_set_parent_in_list(stat_list, node); + + return(node); +} + +/************************************************************************* +Parses a for-loop-statement. */ +UNIV_INTERN +for_node_t* +pars_for_statement( +/*===============*/ + /* out: for-statement node */ + sym_node_t* loop_var, /* in: loop variable */ + que_node_t* loop_start_limit,/* in: loop start expression */ + que_node_t* loop_end_limit, /* in: loop end expression */ + que_node_t* stat_list) /* in: statement list */ +{ + for_node_t* node; + + node = mem_heap_alloc(pars_sym_tab_global->heap, sizeof(for_node_t)); + + node->common.type = QUE_NODE_FOR; + + pars_resolve_exp_variables_and_types(NULL, loop_var); + pars_resolve_exp_variables_and_types(NULL, loop_start_limit); + pars_resolve_exp_variables_and_types(NULL, loop_end_limit); + + node->loop_var = loop_var->indirection; + + ut_a(loop_var->indirection); + + node->loop_start_limit = loop_start_limit; + node->loop_end_limit = loop_end_limit; + + node->stat_list = stat_list; + + pars_set_parent_in_list(stat_list, node); + + return(node); +} + +/************************************************************************* +Parses an exit statement. */ +UNIV_INTERN +exit_node_t* +pars_exit_statement(void) +/*=====================*/ + /* out: exit statement node */ +{ + exit_node_t* node; + + node = mem_heap_alloc(pars_sym_tab_global->heap, sizeof(exit_node_t)); + node->common.type = QUE_NODE_EXIT; + + return(node); +} + +/************************************************************************* +Parses a return-statement. */ +UNIV_INTERN +return_node_t* +pars_return_statement(void) +/*=======================*/ + /* out: return-statement node */ +{ + return_node_t* node; + + node = mem_heap_alloc(pars_sym_tab_global->heap, + sizeof(return_node_t)); + node->common.type = QUE_NODE_RETURN; + + return(node); +} + +/************************************************************************* +Parses an assignment statement. */ +UNIV_INTERN +assign_node_t* +pars_assignment_statement( +/*======================*/ + /* out: assignment statement node */ + sym_node_t* var, /* in: variable to assign */ + que_node_t* val) /* in: value to assign */ +{ + assign_node_t* node; + + node = mem_heap_alloc(pars_sym_tab_global->heap, + sizeof(assign_node_t)); + node->common.type = QUE_NODE_ASSIGNMENT; + + node->var = var; + node->val = val; + + pars_resolve_exp_variables_and_types(NULL, var); + pars_resolve_exp_variables_and_types(NULL, val); + + ut_a(dtype_get_mtype(dfield_get_type(que_node_get_val(var))) + == dtype_get_mtype(dfield_get_type(que_node_get_val(val)))); + + return(node); +} + +/************************************************************************* +Parses a procedure call. */ +UNIV_INTERN +func_node_t* +pars_procedure_call( +/*================*/ + /* out: function node */ + que_node_t* res_word,/* in: procedure name reserved word */ + que_node_t* args) /* in: argument list */ +{ + func_node_t* node; + + node = pars_func(res_word, args); + + pars_resolve_exp_list_variables_and_types(NULL, args); + + return(node); +} + +/************************************************************************* +Parses a fetch statement. into_list or user_func (but not both) must be +non-NULL. */ +UNIV_INTERN +fetch_node_t* +pars_fetch_statement( +/*=================*/ + /* out: fetch statement node */ + sym_node_t* cursor, /* in: cursor node */ + sym_node_t* into_list, /* in: variables to set, or NULL */ + sym_node_t* user_func) /* in: user function name, or NULL */ +{ + sym_node_t* cursor_decl; + fetch_node_t* node; + + /* Logical XOR. */ + ut_a(!into_list != !user_func); + + node = mem_heap_alloc(pars_sym_tab_global->heap, sizeof(fetch_node_t)); + + node->common.type = QUE_NODE_FETCH; + + pars_resolve_exp_variables_and_types(NULL, cursor); + + if (into_list) { + pars_resolve_exp_list_variables_and_types(NULL, into_list); + node->into_list = into_list; + node->func = NULL; + } else { + pars_resolve_exp_variables_and_types(NULL, user_func); + + node->func = pars_info_get_user_func(pars_sym_tab_global->info, + user_func->name); + ut_a(node->func); + + node->into_list = NULL; + } + + cursor_decl = cursor->alias; + + ut_a(cursor_decl->token_type == SYM_CURSOR); + + node->cursor_def = cursor_decl->cursor_def; + + if (into_list) { + ut_a(que_node_list_get_len(into_list) + == que_node_list_get_len(node->cursor_def->select_list)); + } + + return(node); +} + +/************************************************************************* +Parses an open or close cursor statement. */ +UNIV_INTERN +open_node_t* +pars_open_statement( +/*================*/ + /* out: fetch statement node */ + ulint type, /* in: ROW_SEL_OPEN_CURSOR + or ROW_SEL_CLOSE_CURSOR */ + sym_node_t* cursor) /* in: cursor node */ +{ + sym_node_t* cursor_decl; + open_node_t* node; + + node = mem_heap_alloc(pars_sym_tab_global->heap, sizeof(open_node_t)); + + node->common.type = QUE_NODE_OPEN; + + pars_resolve_exp_variables_and_types(NULL, cursor); + + cursor_decl = cursor->alias; + + ut_a(cursor_decl->token_type == SYM_CURSOR); + + node->op_type = type; + node->cursor_def = cursor_decl->cursor_def; + + return(node); +} + +/************************************************************************* +Parses a row_printf-statement. */ +UNIV_INTERN +row_printf_node_t* +pars_row_printf_statement( +/*======================*/ + /* out: row_printf-statement node */ + sel_node_t* sel_node) /* in: select node */ +{ + row_printf_node_t* node; + + node = mem_heap_alloc(pars_sym_tab_global->heap, + sizeof(row_printf_node_t)); + node->common.type = QUE_NODE_ROW_PRINTF; + + node->sel_node = sel_node; + + sel_node->common.parent = node; + + return(node); +} + +/************************************************************************* +Parses a commit statement. */ +UNIV_INTERN +commit_node_t* +pars_commit_statement(void) +/*=======================*/ +{ + return(commit_node_create(pars_sym_tab_global->heap)); +} + +/************************************************************************* +Parses a rollback statement. */ +UNIV_INTERN +roll_node_t* +pars_rollback_statement(void) +/*=========================*/ +{ + return(roll_node_create(pars_sym_tab_global->heap)); +} + +/************************************************************************* +Parses a column definition at a table creation. */ +UNIV_INTERN +sym_node_t* +pars_column_def( +/*============*/ + /* out: column sym table + node */ + sym_node_t* sym_node, /* in: column node in the + symbol table */ + pars_res_word_t* type, /* in: data type */ + sym_node_t* len, /* in: length of column, or + NULL */ + void* is_unsigned, /* in: if not NULL, column + is of type UNSIGNED. */ + void* is_not_null) /* in: if not NULL, column + is of type NOT NULL. */ +{ + ulint len2; + + if (len) { + len2 = eval_node_get_int_val(len); + } else { + len2 = 0; + } + + pars_set_dfield_type(que_node_get_val(sym_node), type, len2, + is_unsigned != NULL, is_not_null != NULL); + + return(sym_node); +} + +/************************************************************************* +Parses a table creation operation. */ +UNIV_INTERN +tab_node_t* +pars_create_table( +/*==============*/ + /* out: table create subgraph */ + sym_node_t* table_sym, /* in: table name node in the symbol + table */ + sym_node_t* column_defs, /* in: list of column names */ + void* not_fit_in_memory __attribute__((unused))) + /* in: a non-NULL pointer means that + this is a table which in simulations + should be simulated as not fitting + in memory; thread is put to sleep + to simulate disk accesses; NOTE that + this flag is not stored to the data + dictionary on disk, and the database + will forget about non-NULL value if + it has to reload the table definition + from disk */ +{ + dict_table_t* table; + sym_node_t* column; + tab_node_t* node; + const dtype_t* dtype; + ulint n_cols; + + n_cols = que_node_list_get_len(column_defs); + + /* As the InnoDB SQL parser is for internal use only, + for creating some system tables, this function will only + create tables in the old (not compact) record format. */ + table = dict_mem_table_create(table_sym->name, 0, n_cols, 0); + +#ifdef UNIV_DEBUG + if (not_fit_in_memory != NULL) { + table->does_not_fit_in_memory = TRUE; + } +#endif /* UNIV_DEBUG */ + column = column_defs; + + while (column) { + dtype = dfield_get_type(que_node_get_val(column)); + + dict_mem_table_add_col(table, table->heap, + column->name, dtype->mtype, + dtype->prtype, dtype->len); + column->resolved = TRUE; + column->token_type = SYM_COLUMN; + + column = que_node_get_next(column); + } + + node = tab_create_graph_create(table, pars_sym_tab_global->heap); + + table_sym->resolved = TRUE; + table_sym->token_type = SYM_TABLE; + + return(node); +} + +/************************************************************************* +Parses an index creation operation. */ +UNIV_INTERN +ind_node_t* +pars_create_index( +/*==============*/ + /* out: index create subgraph */ + pars_res_word_t* unique_def, /* in: not NULL if a unique index */ + pars_res_word_t* clustered_def, /* in: not NULL if a clustered index */ + sym_node_t* index_sym, /* in: index name node in the symbol + table */ + sym_node_t* table_sym, /* in: table name node in the symbol + table */ + sym_node_t* column_list) /* in: list of column names */ +{ + dict_index_t* index; + sym_node_t* column; + ind_node_t* node; + ulint n_fields; + ulint ind_type; + + n_fields = que_node_list_get_len(column_list); + + ind_type = 0; + + if (unique_def) { + ind_type = ind_type | DICT_UNIQUE; + } + + if (clustered_def) { + ind_type = ind_type | DICT_CLUSTERED; + } + + index = dict_mem_index_create(table_sym->name, index_sym->name, 0, + ind_type, n_fields); + column = column_list; + + while (column) { + dict_mem_index_add_field(index, column->name, 0); + + column->resolved = TRUE; + column->token_type = SYM_COLUMN; + + column = que_node_get_next(column); + } + + node = ind_create_graph_create(index, pars_sym_tab_global->heap); + + table_sym->resolved = TRUE; + table_sym->token_type = SYM_TABLE; + + index_sym->resolved = TRUE; + index_sym->token_type = SYM_TABLE; + + return(node); +} + +/************************************************************************* +Parses a procedure definition. */ +UNIV_INTERN +que_fork_t* +pars_procedure_definition( +/*======================*/ + /* out: query fork node */ + sym_node_t* sym_node, /* in: procedure id node in the symbol + table */ + sym_node_t* param_list, /* in: parameter declaration list */ + que_node_t* stat_list) /* in: statement list */ +{ + proc_node_t* node; + que_fork_t* fork; + que_thr_t* thr; + mem_heap_t* heap; + + heap = pars_sym_tab_global->heap; + + fork = que_fork_create(NULL, NULL, QUE_FORK_PROCEDURE, heap); + fork->trx = NULL; + + thr = que_thr_create(fork, heap); + + node = mem_heap_alloc(heap, sizeof(proc_node_t)); + + node->common.type = QUE_NODE_PROC; + node->common.parent = thr; + + sym_node->token_type = SYM_PROCEDURE_NAME; + sym_node->resolved = TRUE; + + node->proc_id = sym_node; + node->param_list = param_list; + node->stat_list = stat_list; + + pars_set_parent_in_list(stat_list, node); + + node->sym_tab = pars_sym_tab_global; + + thr->child = node; + + pars_sym_tab_global->query_graph = fork; + + return(fork); +} + +/***************************************************************** +Parses a stored procedure call, when this is not within another stored +procedure, that is, the client issues a procedure call directly. +In MySQL/InnoDB, stored InnoDB procedures are invoked via the +parsed procedure tree, not via InnoDB SQL, so this function is not used. */ +UNIV_INTERN +que_fork_t* +pars_stored_procedure_call( +/*=======================*/ + /* out: query graph */ + sym_node_t* sym_node __attribute__((unused))) + /* in: stored procedure name */ +{ + ut_error; + return(NULL); +} + +/***************************************************************** +Retrieves characters to the lexical analyzer. */ +UNIV_INTERN +void +pars_get_lex_chars( +/*===============*/ + char* buf, /* in/out: buffer where to copy */ + int* result, /* out: number of characters copied or EOF */ + int max_size) /* in: maximum number of characters which fit + in the buffer */ +{ + int len; + + len = pars_sym_tab_global->string_len + - pars_sym_tab_global->next_char_pos; + if (len == 0) { +#ifdef YYDEBUG + /* fputs("SQL string ends\n", stderr); */ +#endif + *result = 0; + + return; + } + + if (len > max_size) { + len = max_size; + } + +#ifdef UNIV_SQL_DEBUG + if (pars_print_lexed) { + + if (len >= 5) { + len = 5; + } + + fwrite(pars_sym_tab_global->sql_string + + pars_sym_tab_global->next_char_pos, + 1, len, stderr); + } +#endif /* UNIV_SQL_DEBUG */ + + ut_memcpy(buf, pars_sym_tab_global->sql_string + + pars_sym_tab_global->next_char_pos, len); + *result = len; + + pars_sym_tab_global->next_char_pos += len; +} + +/***************************************************************** +Called by yyparse on error. */ +UNIV_INTERN +void +yyerror( +/*====*/ + const char* s __attribute__((unused))) + /* in: error message string */ +{ + ut_ad(s); + + fputs("PARSER ERROR: Syntax error in SQL string\n", stderr); + + ut_error; +} + +/***************************************************************** +Parses an SQL string returning the query graph. */ +UNIV_INTERN +que_t* +pars_sql( +/*=====*/ + /* out, own: the query graph */ + pars_info_t* info, /* in: extra information, or NULL */ + const char* str) /* in: SQL string */ +{ + sym_node_t* sym_node; + mem_heap_t* heap; + que_t* graph; + + ut_ad(str); + + heap = mem_heap_create(256); + + /* Currently, the parser is not reentrant: */ + ut_ad(mutex_own(&(dict_sys->mutex))); + + pars_sym_tab_global = sym_tab_create(heap); + + pars_sym_tab_global->string_len = strlen(str); + pars_sym_tab_global->sql_string = mem_heap_dup( + heap, str, pars_sym_tab_global->string_len + 1); + pars_sym_tab_global->next_char_pos = 0; + pars_sym_tab_global->info = info; + + yyparse(); + + sym_node = UT_LIST_GET_FIRST(pars_sym_tab_global->sym_list); + + while (sym_node) { + ut_a(sym_node->resolved); + + sym_node = UT_LIST_GET_NEXT(sym_list, sym_node); + } + + graph = pars_sym_tab_global->query_graph; + + graph->sym_tab = pars_sym_tab_global; + graph->info = info; + + /* fprintf(stderr, "SQL graph size %lu\n", mem_heap_get_size(heap)); */ + + return(graph); +} + +/********************************************************************** +Completes a query graph by adding query thread and fork nodes +above it and prepares the graph for running. The fork created is of +type QUE_FORK_MYSQL_INTERFACE. */ +UNIV_INTERN +que_thr_t* +pars_complete_graph_for_exec( +/*=========================*/ + /* out: query thread node to run */ + que_node_t* node, /* in: root node for an incomplete + query graph */ + trx_t* trx, /* in: transaction handle */ + mem_heap_t* heap) /* in: memory heap from which allocated */ +{ + que_fork_t* fork; + que_thr_t* thr; + + fork = que_fork_create(NULL, NULL, QUE_FORK_MYSQL_INTERFACE, heap); + fork->trx = trx; + + thr = que_thr_create(fork, heap); + + thr->child = node; + + que_node_set_parent(node, thr); + + trx->graph = NULL; + + return(thr); +} + +/******************************************************************** +Create parser info struct.*/ +UNIV_INTERN +pars_info_t* +pars_info_create(void) +/*==================*/ + /* out, own: info struct */ +{ + pars_info_t* info; + mem_heap_t* heap; + + heap = mem_heap_create(512); + + info = mem_heap_alloc(heap, sizeof(*info)); + + info->heap = heap; + info->funcs = NULL; + info->bound_lits = NULL; + info->bound_ids = NULL; + info->graph_owns_us = TRUE; + + return(info); +} + +/******************************************************************** +Free info struct and everything it contains.*/ +UNIV_INTERN +void +pars_info_free( +/*===========*/ + pars_info_t* info) /* in: info struct */ +{ + mem_heap_free(info->heap); +} + +/******************************************************************** +Add bound literal. */ +UNIV_INTERN +void +pars_info_add_literal( +/*==================*/ + pars_info_t* info, /* in: info struct */ + const char* name, /* in: name */ + const void* address, /* in: address */ + ulint length, /* in: length of data */ + ulint type, /* in: type, e.g. DATA_FIXBINARY */ + ulint prtype) /* in: precise type, e.g. + DATA_UNSIGNED */ +{ + pars_bound_lit_t* pbl; + + ut_ad(!pars_info_get_bound_lit(info, name)); + + pbl = mem_heap_alloc(info->heap, sizeof(*pbl)); + + pbl->name = name; + pbl->address = address; + pbl->length = length; + pbl->type = type; + pbl->prtype = prtype; + + if (!info->bound_lits) { + info->bound_lits = ib_vector_create(info->heap, 8); + } + + ib_vector_push(info->bound_lits, pbl); +} + +/******************************************************************** +Equivalent to pars_info_add_literal(info, name, str, strlen(str), +DATA_VARCHAR, DATA_ENGLISH). */ +UNIV_INTERN +void +pars_info_add_str_literal( +/*======================*/ + pars_info_t* info, /* in: info struct */ + const char* name, /* in: name */ + const char* str) /* in: string */ +{ + pars_info_add_literal(info, name, str, strlen(str), + DATA_VARCHAR, DATA_ENGLISH); +} + +/******************************************************************** +Equivalent to: + +char buf[4]; +mach_write_to_4(buf, val); +pars_info_add_literal(info, name, buf, 4, DATA_INT, 0); + +except that the buffer is dynamically allocated from the info struct's +heap. */ +UNIV_INTERN +void +pars_info_add_int4_literal( +/*=======================*/ + pars_info_t* info, /* in: info struct */ + const char* name, /* in: name */ + lint val) /* in: value */ +{ + byte* buf = mem_heap_alloc(info->heap, 4); + + mach_write_to_4(buf, val); + pars_info_add_literal(info, name, buf, 4, DATA_INT, 0); +} + +/******************************************************************** +Equivalent to: + +char buf[8]; +mach_write_to_8(buf, val); +pars_info_add_literal(info, name, buf, 8, DATA_FIXBINARY, 0); + +except that the buffer is dynamically allocated from the info struct's +heap. */ +UNIV_INTERN +void +pars_info_add_dulint_literal( +/*=========================*/ + pars_info_t* info, /* in: info struct */ + const char* name, /* in: name */ + dulint val) /* in: value */ +{ + byte* buf = mem_heap_alloc(info->heap, 8); + + mach_write_to_8(buf, val); + + pars_info_add_literal(info, name, buf, 8, DATA_FIXBINARY, 0); +} + +/******************************************************************** +Add user function. */ +UNIV_INTERN +void +pars_info_add_function( +/*===================*/ + pars_info_t* info, /* in: info struct */ + const char* name, /* in: function name */ + pars_user_func_cb_t func, /* in: function address */ + void* arg) /* in: user-supplied argument */ +{ + pars_user_func_t* puf; + + ut_ad(!pars_info_get_user_func(info, name)); + + puf = mem_heap_alloc(info->heap, sizeof(*puf)); + + puf->name = name; + puf->func = func; + puf->arg = arg; + + if (!info->funcs) { + info->funcs = ib_vector_create(info->heap, 8); + } + + ib_vector_push(info->funcs, puf); +} + +/******************************************************************** +Add bound id. */ +UNIV_INTERN +void +pars_info_add_id( +/*=============*/ + pars_info_t* info, /* in: info struct */ + const char* name, /* in: name */ + const char* id) /* in: id */ +{ + pars_bound_id_t* bid; + + ut_ad(!pars_info_get_bound_id(info, name)); + + bid = mem_heap_alloc(info->heap, sizeof(*bid)); + + bid->name = name; + bid->id = id; + + if (!info->bound_ids) { + info->bound_ids = ib_vector_create(info->heap, 8); + } + + ib_vector_push(info->bound_ids, bid); +} + +/******************************************************************** +Get user function with the given name.*/ +UNIV_INTERN +pars_user_func_t* +pars_info_get_user_func( +/*====================*/ + /* out: user func, or NULL if not + found */ + pars_info_t* info, /* in: info struct */ + const char* name) /* in: function name to find*/ +{ + ulint i; + ib_vector_t* vec; + + if (!info || !info->funcs) { + return(NULL); + } + + vec = info->funcs; + + for (i = 0; i < ib_vector_size(vec); i++) { + pars_user_func_t* puf = ib_vector_get(vec, i); + + if (strcmp(puf->name, name) == 0) { + return(puf); + } + } + + return(NULL); +} + +/******************************************************************** +Get bound literal with the given name.*/ +UNIV_INTERN +pars_bound_lit_t* +pars_info_get_bound_lit( +/*====================*/ + /* out: bound literal, or NULL if + not found */ + pars_info_t* info, /* in: info struct */ + const char* name) /* in: bound literal name to find */ +{ + ulint i; + ib_vector_t* vec; + + if (!info || !info->bound_lits) { + return(NULL); + } + + vec = info->bound_lits; + + for (i = 0; i < ib_vector_size(vec); i++) { + pars_bound_lit_t* pbl = ib_vector_get(vec, i); + + if (strcmp(pbl->name, name) == 0) { + return(pbl); + } + } + + return(NULL); +} + +/******************************************************************** +Get bound id with the given name.*/ +UNIV_INTERN +pars_bound_id_t* +pars_info_get_bound_id( +/*===================*/ + /* out: bound id, or NULL if not + found */ + pars_info_t* info, /* in: info struct */ + const char* name) /* in: bound id name to find */ +{ + ulint i; + ib_vector_t* vec; + + if (!info || !info->bound_ids) { + return(NULL); + } + + vec = info->bound_ids; + + for (i = 0; i < ib_vector_size(vec); i++) { + pars_bound_id_t* bid = ib_vector_get(vec, i); + + if (strcmp(bid->name, name) == 0) { + return(bid); + } + } + + return(NULL); +} diff --git a/storage/xtradb/pars/pars0sym.c b/storage/xtradb/pars/pars0sym.c new file mode 100644 index 00000000000..fb23547e767 --- /dev/null +++ b/storage/xtradb/pars/pars0sym.c @@ -0,0 +1,370 @@ +/***************************************************************************** + +Copyright (c) 1997, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/****************************************************** +SQL parser symbol table + +Created 12/15/1997 Heikki Tuuri +*******************************************************/ + +#include "pars0sym.h" + +#ifdef UNIV_NONINL +#include "pars0sym.ic" +#endif + +#include "mem0mem.h" +#include "data0type.h" +#include "data0data.h" +#include "pars0grm.h" +#include "pars0pars.h" +#include "que0que.h" +#include "eval0eval.h" +#include "row0sel.h" + +/********************************************************************** +Creates a symbol table for a single stored procedure or query. */ +UNIV_INTERN +sym_tab_t* +sym_tab_create( +/*===========*/ + /* out, own: symbol table */ + mem_heap_t* heap) /* in: memory heap where to create */ +{ + sym_tab_t* sym_tab; + + sym_tab = mem_heap_alloc(heap, sizeof(sym_tab_t)); + + UT_LIST_INIT(sym_tab->sym_list); + UT_LIST_INIT(sym_tab->func_node_list); + + sym_tab->heap = heap; + + return(sym_tab); +} + +/********************************************************************** +Frees the memory allocated dynamically AFTER parsing phase for variables +etc. in the symbol table. Does not free the mem heap where the table was +originally created. Frees also SQL explicit cursor definitions. */ +UNIV_INTERN +void +sym_tab_free_private( +/*=================*/ + sym_tab_t* sym_tab) /* in, own: symbol table */ +{ + sym_node_t* sym; + func_node_t* func; + + sym = UT_LIST_GET_FIRST(sym_tab->sym_list); + + while (sym) { + eval_node_free_val_buf(sym); + + if (sym->prefetch_buf) { + sel_col_prefetch_buf_free(sym->prefetch_buf); + } + + if (sym->cursor_def) { + que_graph_free_recursive(sym->cursor_def); + } + + sym = UT_LIST_GET_NEXT(sym_list, sym); + } + + func = UT_LIST_GET_FIRST(sym_tab->func_node_list); + + while (func) { + eval_node_free_val_buf(func); + + func = UT_LIST_GET_NEXT(func_node_list, func); + } +} + +/********************************************************************** +Adds an integer literal to a symbol table. */ +UNIV_INTERN +sym_node_t* +sym_tab_add_int_lit( +/*================*/ + /* out: symbol table node */ + sym_tab_t* sym_tab, /* in: symbol table */ + ulint val) /* in: integer value */ +{ + sym_node_t* node; + byte* data; + + node = mem_heap_alloc(sym_tab->heap, sizeof(sym_node_t)); + + node->common.type = QUE_NODE_SYMBOL; + + node->resolved = TRUE; + node->token_type = SYM_LIT; + + node->indirection = NULL; + + dtype_set(dfield_get_type(&node->common.val), DATA_INT, 0, 4); + + data = mem_heap_alloc(sym_tab->heap, 4); + mach_write_to_4(data, val); + + dfield_set_data(&(node->common.val), data, 4); + + node->common.val_buf_size = 0; + node->prefetch_buf = NULL; + node->cursor_def = NULL; + + UT_LIST_ADD_LAST(sym_list, sym_tab->sym_list, node); + + node->sym_table = sym_tab; + + return(node); +} + +/********************************************************************** +Adds a string literal to a symbol table. */ +UNIV_INTERN +sym_node_t* +sym_tab_add_str_lit( +/*================*/ + /* out: symbol table node */ + sym_tab_t* sym_tab, /* in: symbol table */ + byte* str, /* in: string with no quotes around + it */ + ulint len) /* in: string length */ +{ + sym_node_t* node; + byte* data; + + node = mem_heap_alloc(sym_tab->heap, sizeof(sym_node_t)); + + node->common.type = QUE_NODE_SYMBOL; + + node->resolved = TRUE; + node->token_type = SYM_LIT; + + node->indirection = NULL; + + dtype_set(dfield_get_type(&node->common.val), + DATA_VARCHAR, DATA_ENGLISH, 0); + + if (len) { + data = mem_heap_alloc(sym_tab->heap, len); + ut_memcpy(data, str, len); + } else { + data = NULL; + } + + dfield_set_data(&(node->common.val), data, len); + + node->common.val_buf_size = 0; + node->prefetch_buf = NULL; + node->cursor_def = NULL; + + UT_LIST_ADD_LAST(sym_list, sym_tab->sym_list, node); + + node->sym_table = sym_tab; + + return(node); +} + +/********************************************************************** +Add a bound literal to a symbol table. */ +UNIV_INTERN +sym_node_t* +sym_tab_add_bound_lit( +/*==================*/ + /* out: symbol table node */ + sym_tab_t* sym_tab, /* in: symbol table */ + const char* name, /* in: name of bound literal */ + ulint* lit_type) /* out: type of literal (PARS_*_LIT) */ +{ + sym_node_t* node; + pars_bound_lit_t* blit; + ulint len = 0; + + blit = pars_info_get_bound_lit(sym_tab->info, name); + ut_a(blit); + + node = mem_heap_alloc(sym_tab->heap, sizeof(sym_node_t)); + + node->common.type = QUE_NODE_SYMBOL; + + node->resolved = TRUE; + node->token_type = SYM_LIT; + + node->indirection = NULL; + + switch (blit->type) { + case DATA_FIXBINARY: + len = blit->length; + *lit_type = PARS_FIXBINARY_LIT; + break; + + case DATA_BLOB: + *lit_type = PARS_BLOB_LIT; + break; + + case DATA_VARCHAR: + *lit_type = PARS_STR_LIT; + break; + + case DATA_CHAR: + ut_a(blit->length > 0); + + len = blit->length; + *lit_type = PARS_STR_LIT; + break; + + case DATA_INT: + ut_a(blit->length > 0); + ut_a(blit->length <= 8); + + len = blit->length; + *lit_type = PARS_INT_LIT; + break; + + default: + ut_error; + } + + dtype_set(dfield_get_type(&node->common.val), + blit->type, blit->prtype, len); + + dfield_set_data(&(node->common.val), blit->address, blit->length); + + node->common.val_buf_size = 0; + node->prefetch_buf = NULL; + node->cursor_def = NULL; + + UT_LIST_ADD_LAST(sym_list, sym_tab->sym_list, node); + + node->sym_table = sym_tab; + + return(node); +} + +/********************************************************************** +Adds an SQL null literal to a symbol table. */ +UNIV_INTERN +sym_node_t* +sym_tab_add_null_lit( +/*=================*/ + /* out: symbol table node */ + sym_tab_t* sym_tab) /* in: symbol table */ +{ + sym_node_t* node; + + node = mem_heap_alloc(sym_tab->heap, sizeof(sym_node_t)); + + node->common.type = QUE_NODE_SYMBOL; + + node->resolved = TRUE; + node->token_type = SYM_LIT; + + node->indirection = NULL; + + dfield_get_type(&node->common.val)->mtype = DATA_ERROR; + + dfield_set_null(&node->common.val); + + node->common.val_buf_size = 0; + node->prefetch_buf = NULL; + node->cursor_def = NULL; + + UT_LIST_ADD_LAST(sym_list, sym_tab->sym_list, node); + + node->sym_table = sym_tab; + + return(node); +} + +/********************************************************************** +Adds an identifier to a symbol table. */ +UNIV_INTERN +sym_node_t* +sym_tab_add_id( +/*===========*/ + /* out: symbol table node */ + sym_tab_t* sym_tab, /* in: symbol table */ + byte* name, /* in: identifier name */ + ulint len) /* in: identifier length */ +{ + sym_node_t* node; + + node = mem_heap_alloc(sym_tab->heap, sizeof(sym_node_t)); + + node->common.type = QUE_NODE_SYMBOL; + + node->resolved = FALSE; + node->indirection = NULL; + + node->name = mem_heap_strdupl(sym_tab->heap, (char*) name, len); + node->name_len = len; + + UT_LIST_ADD_LAST(sym_list, sym_tab->sym_list, node); + + dfield_set_null(&node->common.val); + + node->common.val_buf_size = 0; + node->prefetch_buf = NULL; + node->cursor_def = NULL; + + node->sym_table = sym_tab; + + return(node); +} + +/********************************************************************** +Add a bound identifier to a symbol table. */ +UNIV_INTERN +sym_node_t* +sym_tab_add_bound_id( +/*===========*/ + /* out: symbol table node */ + sym_tab_t* sym_tab, /* in: symbol table */ + const char* name) /* in: name of bound id */ +{ + sym_node_t* node; + pars_bound_id_t* bid; + + bid = pars_info_get_bound_id(sym_tab->info, name); + ut_a(bid); + + node = mem_heap_alloc(sym_tab->heap, sizeof(sym_node_t)); + + node->common.type = QUE_NODE_SYMBOL; + + node->resolved = FALSE; + node->indirection = NULL; + + node->name = mem_heap_strdup(sym_tab->heap, bid->id); + node->name_len = strlen(node->name); + + UT_LIST_ADD_LAST(sym_list, sym_tab->sym_list, node); + + dfield_set_null(&node->common.val); + + node->common.val_buf_size = 0; + node->prefetch_buf = NULL; + node->cursor_def = NULL; + + node->sym_table = sym_tab; + + return(node); +} diff --git a/storage/xtradb/plug.in b/storage/xtradb/plug.in new file mode 100644 index 00000000000..4d5e792bfb1 --- /dev/null +++ b/storage/xtradb/plug.in @@ -0,0 +1,80 @@ +# +# Copyright (c) 2006, 2009, Innobase Oy. 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 +# + +MYSQL_STORAGE_ENGINE(innobase, innodb, [InnoDB Storage Engine], + [Transactional Tables using InnoDB], [max,max-no-ndb]) +MYSQL_PLUGIN_DIRECTORY(innobase, [storage/xtradb]) +MYSQL_PLUGIN_STATIC(innobase, [libinnobase.a]) +MYSQL_PLUGIN_DYNAMIC(innobase, [ha_innodb.la]) +MYSQL_PLUGIN_ACTIONS(innobase, [ + AC_CHECK_HEADERS(sched.h) + AC_CHECK_SIZEOF(int, 4) + AC_CHECK_SIZEOF(long, 4) + AC_CHECK_SIZEOF(void*, 4) + AC_CHECK_FUNCS(sched_yield fdatasync localtime_r) + AC_C_BIGENDIAN + case "$target_os" in + lin*) + CFLAGS="$CFLAGS -DUNIV_LINUX";; + hpux10*) + CFLAGS="$CFLAGS -DUNIV_MUST_NOT_INLINE -DUNIV_HPUX -DUNIV_HPUX10";; + hp*) + CFLAGS="$CFLAGS -DUNIV_MUST_NOT_INLINE -DUNIV_HPUX";; + aix*) + CFLAGS="$CFLAGS -DUNIV_AIX";; + irix*|osf*|sysv5uw7*|openbsd*) + CFLAGS="$CFLAGS -DUNIV_MUST_NOT_INLINE";; + *solaris*|*SunOS*) + CFLAGS="$CFLAGS -DUNIV_SOLARIS";; + esac + INNODB_DYNAMIC_CFLAGS="-DMYSQL_DYNAMIC_PLUGIN" + case "$target_cpu" in + x86_64) + # The AMD64 ABI forbids absolute addresses in shared libraries + ;; + *86) + # Use absolute addresses on IA-32 + INNODB_DYNAMIC_CFLAGS="$INNODB_DYNAMIC_CFLAGS -prefer-non-pic" + ;; + esac + AC_SUBST(INNODB_DYNAMIC_CFLAGS) + AC_MSG_CHECKING(whether pthread_t can be used by GCC atomic builtins) + AC_TRY_RUN( + [ + #include <pthread.h> + + int main(int argc, char** argv) { + pthread_t x1; + pthread_t x2; + pthread_t x3; + + __sync_bool_compare_and_swap(&x1, x2, x3); + + return(0); + } + ], + [ + AC_DEFINE([HAVE_ATOMIC_PTHREAD_T], [1], + [pthread_t can be used by GCC atomic builtins]) + AC_MSG_RESULT(yes) + ], + [ + AC_MSG_RESULT(no) + ] + ) + ]) + +# vim: set ft=config: diff --git a/storage/xtradb/que/que0que.c b/storage/xtradb/que/que0que.c new file mode 100644 index 00000000000..91a9d30ec4c --- /dev/null +++ b/storage/xtradb/que/que0que.c @@ -0,0 +1,1461 @@ +/***************************************************************************** + +Copyright (c) 1996, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/****************************************************** +Query graph + +Created 5/27/1996 Heikki Tuuri +*******************************************************/ + +#include "que0que.h" + +#ifdef UNIV_NONINL +#include "que0que.ic" +#endif + +#include "srv0que.h" +#include "usr0sess.h" +#include "trx0trx.h" +#include "trx0roll.h" +#include "row0undo.h" +#include "row0ins.h" +#include "row0upd.h" +#include "row0sel.h" +#include "row0purge.h" +#include "dict0crea.h" +#include "log0log.h" +#include "eval0proc.h" +#include "eval0eval.h" +#include "pars0types.h" + +#define QUE_PARALLELIZE_LIMIT (64 * 256 * 256 * 256) +#define QUE_ROUND_ROBIN_LIMIT (64 * 256 * 256 * 256) +#define QUE_MAX_LOOPS_WITHOUT_CHECK 16 + +#ifdef UNIV_DEBUG +/* If the following flag is set TRUE, the module will print trace info +of SQL execution in the UNIV_SQL_DEBUG version */ +UNIV_INTERN ibool que_trace_on = FALSE; +#endif /* UNIV_DEBUG */ + +/* Short introduction to query graphs + ================================== + +A query graph consists of nodes linked to each other in various ways. The +execution starts at que_run_threads() which takes a que_thr_t parameter. +que_thr_t contains two fields that control query graph execution: run_node +and prev_node. run_node is the next node to execute and prev_node is the +last node executed. + +Each node has a pointer to a 'next' statement, i.e., its brother, and a +pointer to its parent node. The next pointer is NULL in the last statement +of a block. + +Loop nodes contain a link to the first statement of the enclosed statement +list. While the loop runs, que_thr_step() checks if execution to the loop +node came from its parent or from one of the statement nodes in the loop. If +it came from the parent of the loop node it starts executing the first +statement node in the loop. If it came from one of the statement nodes in +the loop, then it checks if the statement node has another statement node +following it, and runs it if so. + +To signify loop ending, the loop statements (see e.g. while_step()) set +que_thr_t->run_node to the loop node's parent node. This is noticed on the +next call of que_thr_step() and execution proceeds to the node pointed to by +the loop node's 'next' pointer. + +For example, the code: + +X := 1; +WHILE X < 5 LOOP + X := X + 1; + X := X + 1; +X := 5 + +will result in the following node hierarchy, with the X-axis indicating +'next' links and the Y-axis indicating parent/child links: + +A - W - A + | + | + A - A + +A = assign_node_t, W = while_node_t. */ + +/* How a stored procedure containing COMMIT or ROLLBACK commands +is executed? + +The commit or rollback can be seen as a subprocedure call. +The problem is that if there are several query threads +currently running within the transaction, their action could +mess the commit or rollback operation. Or, at the least, the +operation would be difficult to visualize and keep in control. + +Therefore the query thread requesting a commit or a rollback +sends to the transaction a signal, which moves the transaction +to TRX_QUE_SIGNALED state. All running query threads of the +transaction will eventually notice that the transaction is now in +this state and voluntarily suspend themselves. Only the last +query thread which suspends itself will trigger handling of +the signal. + +When the transaction starts to handle a rollback or commit +signal, it builds a query graph which, when executed, will +roll back or commit the incomplete transaction. The transaction +is moved to the TRX_QUE_ROLLING_BACK or TRX_QUE_COMMITTING state. +If specified, the SQL cursors opened by the transaction are closed. +When the execution of the graph completes, it is like returning +from a subprocedure: the query thread which requested the operation +starts running again. */ + +/************************************************************************** +Moves a thread from another state to the QUE_THR_RUNNING state. Increments +the n_active_thrs counters of the query graph and transaction. +***NOTE***: This is the only function in which such a transition is allowed +to happen! */ +static +void +que_thr_move_to_run_state( +/*======================*/ + que_thr_t* thr); /* in: an query thread */ + +/*************************************************************************** +Adds a query graph to the session's list of graphs. */ +UNIV_INTERN +void +que_graph_publish( +/*==============*/ + que_t* graph, /* in: graph */ + sess_t* sess) /* in: session */ +{ + ut_ad(mutex_own(&kernel_mutex)); + + UT_LIST_ADD_LAST(graphs, sess->graphs, graph); +} + +/*************************************************************************** +Creates a query graph fork node. */ +UNIV_INTERN +que_fork_t* +que_fork_create( +/*============*/ + /* out, own: fork node */ + que_t* graph, /* in: graph, if NULL then this + fork node is assumed to be the + graph root */ + que_node_t* parent, /* in: parent node */ + ulint fork_type, /* in: fork type */ + mem_heap_t* heap) /* in: memory heap where created */ +{ + que_fork_t* fork; + + ut_ad(heap); + + fork = mem_heap_alloc(heap, sizeof(que_fork_t)); + + fork->common.type = QUE_NODE_FORK; + fork->n_active_thrs = 0; + + fork->state = QUE_FORK_COMMAND_WAIT; + + if (graph != NULL) { + fork->graph = graph; + } else { + fork->graph = fork; + } + + fork->common.parent = parent; + fork->fork_type = fork_type; + + fork->caller = NULL; + + UT_LIST_INIT(fork->thrs); + + fork->sym_tab = NULL; + fork->info = NULL; + + fork->heap = heap; + + return(fork); +} + +/*************************************************************************** +Creates a query graph thread node. */ +UNIV_INTERN +que_thr_t* +que_thr_create( +/*===========*/ + /* out, own: query thread node */ + que_fork_t* parent, /* in: parent node, i.e., a fork node */ + mem_heap_t* heap) /* in: memory heap where created */ +{ + que_thr_t* thr; + + ut_ad(parent && heap); + + thr = mem_heap_alloc(heap, sizeof(que_thr_t)); + + thr->common.type = QUE_NODE_THR; + thr->common.parent = parent; + + thr->magic_n = QUE_THR_MAGIC_N; + + thr->graph = parent->graph; + + thr->state = QUE_THR_COMMAND_WAIT; + + thr->is_active = FALSE; + + thr->run_node = NULL; + thr->resource = 0; + thr->lock_state = QUE_THR_LOCK_NOLOCK; + + UT_LIST_ADD_LAST(thrs, parent->thrs, thr); + + return(thr); +} + +/************************************************************************** +Moves a suspended query thread to the QUE_THR_RUNNING state and may release +a single worker thread to execute it. This function should be used to end +the wait state of a query thread waiting for a lock or a stored procedure +completion. */ +UNIV_INTERN +void +que_thr_end_wait( +/*=============*/ + que_thr_t* thr, /* in: query thread in the + QUE_THR_LOCK_WAIT, + or QUE_THR_PROCEDURE_WAIT, or + QUE_THR_SIG_REPLY_WAIT state */ + que_thr_t** next_thr) /* in/out: next query thread to run; + if the value which is passed in is + a pointer to a NULL pointer, then the + calling function can start running + a new query thread; if NULL is passed + as the parameter, it is ignored */ +{ + ibool was_active; + + ut_ad(mutex_own(&kernel_mutex)); + ut_ad(thr); + ut_ad((thr->state == QUE_THR_LOCK_WAIT) + || (thr->state == QUE_THR_PROCEDURE_WAIT) + || (thr->state == QUE_THR_SIG_REPLY_WAIT)); + ut_ad(thr->run_node); + + thr->prev_node = thr->run_node; + + was_active = thr->is_active; + + que_thr_move_to_run_state(thr); + + if (was_active) { + + return; + } + + if (next_thr && *next_thr == NULL) { + *next_thr = thr; + } else { + ut_a(0); + srv_que_task_enqueue_low(thr); + } +} + +/************************************************************************** +Same as que_thr_end_wait, but no parameter next_thr available. */ +UNIV_INTERN +void +que_thr_end_wait_no_next_thr( +/*=========================*/ + que_thr_t* thr) /* in: query thread in the QUE_THR_LOCK_WAIT, + or QUE_THR_PROCEDURE_WAIT, or + QUE_THR_SIG_REPLY_WAIT state */ +{ + ibool was_active; + + ut_a(thr->state == QUE_THR_LOCK_WAIT); /* In MySQL this is the + only possible state here */ + ut_ad(mutex_own(&kernel_mutex)); + ut_ad(thr); + ut_ad((thr->state == QUE_THR_LOCK_WAIT) + || (thr->state == QUE_THR_PROCEDURE_WAIT) + || (thr->state == QUE_THR_SIG_REPLY_WAIT)); + + was_active = thr->is_active; + + que_thr_move_to_run_state(thr); + + if (was_active) { + + return; + } + + /* In MySQL we let the OS thread (not just the query thread) to wait + for the lock to be released: */ + + srv_release_mysql_thread_if_suspended(thr); + + /* srv_que_task_enqueue_low(thr); */ +} + +/************************************************************************** +Inits a query thread for a command. */ +UNIV_INLINE +void +que_thr_init_command( +/*=================*/ + que_thr_t* thr) /* in: query thread */ +{ + thr->run_node = thr; + thr->prev_node = thr->common.parent; + + que_thr_move_to_run_state(thr); +} + +/************************************************************************** +Starts execution of a command in a query fork. Picks a query thread which +is not in the QUE_THR_RUNNING state and moves it to that state. If none +can be chosen, a situation which may arise in parallelized fetches, NULL +is returned. */ +UNIV_INTERN +que_thr_t* +que_fork_start_command( +/*===================*/ + /* out: a query thread of the graph moved to + QUE_THR_RUNNING state, or NULL; the query + thread should be executed by que_run_threads + by the caller */ + que_fork_t* fork) /* in: a query fork */ +{ + que_thr_t* thr; + que_thr_t* suspended_thr = NULL; + que_thr_t* completed_thr = NULL; + + fork->state = QUE_FORK_ACTIVE; + + fork->last_sel_node = NULL; + + suspended_thr = NULL; + completed_thr = NULL; + + /* Choose the query thread to run: usually there is just one thread, + but in a parallelized select, which necessarily is non-scrollable, + there may be several to choose from */ + + /* First we try to find a query thread in the QUE_THR_COMMAND_WAIT + state. Then we try to find a query thread in the QUE_THR_SUSPENDED + state, finally we try to find a query thread in the QUE_THR_COMPLETED + state */ + + thr = UT_LIST_GET_FIRST(fork->thrs); + + /* We make a single pass over the thr list within which we note which + threads are ready to run. */ + while (thr) { + switch (thr->state) { + case QUE_THR_COMMAND_WAIT: + + /* We have to send the initial message to query thread + to start it */ + + que_thr_init_command(thr); + + return(thr); + + case QUE_THR_SUSPENDED: + /* In this case the execution of the thread was + suspended: no initial message is needed because + execution can continue from where it was left */ + if (!suspended_thr) { + suspended_thr = thr; + } + + break; + + case QUE_THR_COMPLETED: + if (!completed_thr) { + completed_thr = thr; + } + + break; + + case QUE_THR_LOCK_WAIT: + ut_error; + + } + + thr = UT_LIST_GET_NEXT(thrs, thr); + } + + if (suspended_thr) { + + thr = suspended_thr; + que_thr_move_to_run_state(thr); + + } else if (completed_thr) { + + thr = completed_thr; + que_thr_init_command(thr); + } + + return(thr); +} + +/************************************************************************** +After signal handling is finished, returns control to a query graph error +handling routine. (Currently, just returns the control to the root of the +graph so that the graph can communicate an error message to the client.) */ +UNIV_INTERN +void +que_fork_error_handle( +/*==================*/ + trx_t* trx __attribute__((unused)), /* in: trx */ + que_t* fork) /* in: query graph which was run before signal + handling started, NULL not allowed */ +{ + que_thr_t* thr; + + ut_ad(mutex_own(&kernel_mutex)); + ut_ad(trx->sess->state == SESS_ERROR); + ut_ad(UT_LIST_GET_LEN(trx->reply_signals) == 0); + ut_ad(UT_LIST_GET_LEN(trx->wait_thrs) == 0); + + thr = UT_LIST_GET_FIRST(fork->thrs); + + while (thr != NULL) { + ut_ad(!thr->is_active); + ut_ad(thr->state != QUE_THR_SIG_REPLY_WAIT); + ut_ad(thr->state != QUE_THR_LOCK_WAIT); + + thr->run_node = thr; + thr->prev_node = thr->child; + thr->state = QUE_THR_COMPLETED; + + thr = UT_LIST_GET_NEXT(thrs, thr); + } + + thr = UT_LIST_GET_FIRST(fork->thrs); + + que_thr_move_to_run_state(thr); + + ut_a(0); + srv_que_task_enqueue_low(thr); +} + +/******************************************************************** +Tests if all the query threads in the same fork have a given state. */ +UNIV_INLINE +ibool +que_fork_all_thrs_in_state( +/*=======================*/ + /* out: TRUE if all the query threads in the + same fork were in the given state */ + que_fork_t* fork, /* in: query fork */ + ulint state) /* in: state */ +{ + que_thr_t* thr_node; + + thr_node = UT_LIST_GET_FIRST(fork->thrs); + + while (thr_node != NULL) { + if (thr_node->state != state) { + + return(FALSE); + } + + thr_node = UT_LIST_GET_NEXT(thrs, thr_node); + } + + return(TRUE); +} + +/************************************************************************** +Calls que_graph_free_recursive for statements in a statement list. */ +static +void +que_graph_free_stat_list( +/*=====================*/ + que_node_t* node) /* in: first query graph node in the list */ +{ + while (node) { + que_graph_free_recursive(node); + + node = que_node_get_next(node); + } +} + +/************************************************************************** +Frees a query graph, but not the heap where it was created. Does not free +explicit cursor declarations, they are freed in que_graph_free. */ +UNIV_INTERN +void +que_graph_free_recursive( +/*=====================*/ + que_node_t* node) /* in: query graph node */ +{ + que_fork_t* fork; + que_thr_t* thr; + undo_node_t* undo; + sel_node_t* sel; + ins_node_t* ins; + upd_node_t* upd; + tab_node_t* cre_tab; + ind_node_t* cre_ind; + + if (node == NULL) { + + return; + } + + switch (que_node_get_type(node)) { + + case QUE_NODE_FORK: + fork = node; + + thr = UT_LIST_GET_FIRST(fork->thrs); + + while (thr) { + que_graph_free_recursive(thr); + + thr = UT_LIST_GET_NEXT(thrs, thr); + } + + break; + case QUE_NODE_THR: + + thr = node; + + if (thr->magic_n != QUE_THR_MAGIC_N) { + fprintf(stderr, + "que_thr struct appears corrupt;" + " magic n %lu\n", + (unsigned long) thr->magic_n); + mem_analyze_corruption(thr); + ut_error; + } + + thr->magic_n = QUE_THR_MAGIC_FREED; + + que_graph_free_recursive(thr->child); + + break; + case QUE_NODE_UNDO: + + undo = node; + + mem_heap_free(undo->heap); + + break; + case QUE_NODE_SELECT: + + sel = node; + + sel_node_free_private(sel); + + break; + case QUE_NODE_INSERT: + + ins = node; + + que_graph_free_recursive(ins->select); + + mem_heap_free(ins->entry_sys_heap); + + break; + case QUE_NODE_UPDATE: + + upd = node; + + if (upd->in_mysql_interface) { + + btr_pcur_free_for_mysql(upd->pcur); + } + + que_graph_free_recursive(upd->cascade_node); + + if (upd->cascade_heap) { + mem_heap_free(upd->cascade_heap); + } + + que_graph_free_recursive(upd->select); + + mem_heap_free(upd->heap); + + break; + case QUE_NODE_CREATE_TABLE: + cre_tab = node; + + que_graph_free_recursive(cre_tab->tab_def); + que_graph_free_recursive(cre_tab->col_def); + que_graph_free_recursive(cre_tab->commit_node); + + mem_heap_free(cre_tab->heap); + + break; + case QUE_NODE_CREATE_INDEX: + cre_ind = node; + + que_graph_free_recursive(cre_ind->ind_def); + que_graph_free_recursive(cre_ind->field_def); + que_graph_free_recursive(cre_ind->commit_node); + + mem_heap_free(cre_ind->heap); + + break; + case QUE_NODE_PROC: + que_graph_free_stat_list(((proc_node_t*)node)->stat_list); + + break; + case QUE_NODE_IF: + que_graph_free_stat_list(((if_node_t*)node)->stat_list); + que_graph_free_stat_list(((if_node_t*)node)->else_part); + que_graph_free_stat_list(((if_node_t*)node)->elsif_list); + + break; + case QUE_NODE_ELSIF: + que_graph_free_stat_list(((elsif_node_t*)node)->stat_list); + + break; + case QUE_NODE_WHILE: + que_graph_free_stat_list(((while_node_t*)node)->stat_list); + + break; + case QUE_NODE_FOR: + que_graph_free_stat_list(((for_node_t*)node)->stat_list); + + break; + + case QUE_NODE_ASSIGNMENT: + case QUE_NODE_EXIT: + case QUE_NODE_RETURN: + case QUE_NODE_COMMIT: + case QUE_NODE_ROLLBACK: + case QUE_NODE_LOCK: + case QUE_NODE_FUNC: + case QUE_NODE_ORDER: + case QUE_NODE_ROW_PRINTF: + case QUE_NODE_OPEN: + case QUE_NODE_FETCH: + /* No need to do anything */ + + break; + default: + fprintf(stderr, + "que_node struct appears corrupt; type %lu\n", + (unsigned long) que_node_get_type(node)); + mem_analyze_corruption(node); + ut_error; + } +} + +/************************************************************************** +Frees a query graph. */ +UNIV_INTERN +void +que_graph_free( +/*===========*/ + que_t* graph) /* in: query graph; we assume that the memory + heap where this graph was created is private + to this graph: if not, then use + que_graph_free_recursive and free the heap + afterwards! */ +{ + ut_ad(graph); + + if (graph->sym_tab) { + /* The following call frees dynamic memory allocated + for variables etc. during execution. Frees also explicit + cursor definitions. */ + + sym_tab_free_private(graph->sym_tab); + } + + if (graph->info && graph->info->graph_owns_us) { + pars_info_free(graph->info); + } + + que_graph_free_recursive(graph); + + mem_heap_free(graph->heap); +} + +/************************************************************************** +Checks if the query graph is in a state where it should be freed, and +frees it in that case. If the session is in a state where it should be +closed, also this is done. */ +UNIV_INTERN +ibool +que_graph_try_free( +/*===============*/ + /* out: TRUE if freed */ + que_t* graph) /* in: query graph */ +{ + sess_t* sess; + + ut_ad(mutex_own(&kernel_mutex)); + + sess = (graph->trx)->sess; + + if ((graph->state == QUE_FORK_BEING_FREED) + && (graph->n_active_thrs == 0)) { + + UT_LIST_REMOVE(graphs, sess->graphs, graph); + que_graph_free(graph); + + sess_try_close(sess); + + return(TRUE); + } + + return(FALSE); +} + +/******************************************************************** +Performs an execution step on a thr node. */ +static +que_thr_t* +que_thr_node_step( +/*==============*/ + /* out: query thread to run next, or NULL + if none */ + que_thr_t* thr) /* in: query thread where run_node must + be the thread node itself */ +{ + ut_ad(thr->run_node == thr); + + if (thr->prev_node == thr->common.parent) { + /* If control to the node came from above, it is just passed + on */ + + thr->run_node = thr->child; + + return(thr); + } + + mutex_enter(&kernel_mutex); + + if (que_thr_peek_stop(thr)) { + + mutex_exit(&kernel_mutex); + + return(thr); + } + + /* Thread execution completed */ + + thr->state = QUE_THR_COMPLETED; + + mutex_exit(&kernel_mutex); + + return(NULL); +} + +/************************************************************************** +Moves a thread from another state to the QUE_THR_RUNNING state. Increments +the n_active_thrs counters of the query graph and transaction if thr was +not active. +***NOTE***: This and ..._mysql are the only functions in which such a +transition is allowed to happen! */ +static +void +que_thr_move_to_run_state( +/*======================*/ + que_thr_t* thr) /* in: an query thread */ +{ + trx_t* trx; + + ut_ad(thr->state != QUE_THR_RUNNING); + + trx = thr_get_trx(thr); + + if (!thr->is_active) { + + (thr->graph)->n_active_thrs++; + + trx->n_active_thrs++; + + thr->is_active = TRUE; + + ut_ad((thr->graph)->n_active_thrs == 1); + ut_ad(trx->n_active_thrs == 1); + } + + thr->state = QUE_THR_RUNNING; +} + +/************************************************************************** +Decrements the query thread reference counts in the query graph and the +transaction. May start signal handling, e.g., a rollback. +*** NOTE ***: +This and que_thr_stop_for_mysql are the only functions where the reference +count can be decremented and this function may only be called from inside +que_run_threads or que_thr_check_if_switch! These restrictions exist to make +the rollback code easier to maintain. */ +static +void +que_thr_dec_refer_count( +/*====================*/ + que_thr_t* thr, /* in: query thread */ + que_thr_t** next_thr) /* in/out: next query thread to run; + if the value which is passed in is + a pointer to a NULL pointer, then the + calling function can start running + a new query thread */ +{ + que_fork_t* fork; + trx_t* trx; + ulint fork_type; + ibool stopped; + + fork = thr->common.parent; + trx = thr_get_trx(thr); + + mutex_enter(&kernel_mutex); + + ut_a(thr->is_active); + + if (thr->state == QUE_THR_RUNNING) { + + stopped = que_thr_stop(thr); + + if (!stopped) { + /* The reason for the thr suspension or wait was + already canceled before we came here: continue + running the thread */ + + /* fputs("!!!!!!!! Wait already ended: continue thr\n", + stderr); */ + + if (next_thr && *next_thr == NULL) { + /* Normally srv_suspend_mysql_thread resets + the state to DB_SUCCESS before waiting, but + in this case we have to do it here, + otherwise nobody does it. */ + trx->error_state = DB_SUCCESS; + + *next_thr = thr; + } else { + ut_error; + srv_que_task_enqueue_low(thr); + } + + mutex_exit(&kernel_mutex); + + return; + } + } + + ut_ad(fork->n_active_thrs == 1); + ut_ad(trx->n_active_thrs == 1); + + fork->n_active_thrs--; + trx->n_active_thrs--; + + thr->is_active = FALSE; + + if (trx->n_active_thrs > 0) { + + mutex_exit(&kernel_mutex); + + return; + } + + fork_type = fork->fork_type; + + /* Check if all query threads in the same fork are completed */ + + if (que_fork_all_thrs_in_state(fork, QUE_THR_COMPLETED)) { + + switch (fork_type) { + case QUE_FORK_ROLLBACK: + /* This is really the undo graph used in rollback, + no roll_node in this graph */ + + ut_ad(UT_LIST_GET_LEN(trx->signals) > 0); + ut_ad(trx->handling_signals == TRUE); + + trx_finish_rollback_off_kernel(fork, trx, next_thr); + break; + + case QUE_FORK_PURGE: + case QUE_FORK_RECOVERY: + case QUE_FORK_MYSQL_INTERFACE: + + /* Do nothing */ + break; + + default: + ut_error; /* not used in MySQL */ + } + } + + if (UT_LIST_GET_LEN(trx->signals) > 0 && trx->n_active_thrs == 0) { + + /* If the trx is signaled and its query thread count drops to + zero, then we start processing a signal; from it we may get + a new query thread to run */ + + trx_sig_start_handle(trx, next_thr); + } + + if (trx->handling_signals && UT_LIST_GET_LEN(trx->signals) == 0) { + + trx_end_signal_handling(trx); + } + + mutex_exit(&kernel_mutex); +} + +/************************************************************************** +Stops a query thread if graph or trx is in a state requiring it. The +conditions are tested in the order (1) graph, (2) trx. The kernel mutex has +to be reserved. */ +UNIV_INTERN +ibool +que_thr_stop( +/*=========*/ + /* out: TRUE if stopped */ + que_thr_t* thr) /* in: query thread */ +{ + trx_t* trx; + que_t* graph; + ibool ret = TRUE; + + ut_ad(mutex_own(&kernel_mutex)); + + graph = thr->graph; + trx = graph->trx; + + if (graph->state == QUE_FORK_COMMAND_WAIT) { + thr->state = QUE_THR_SUSPENDED; + + } else if (trx->que_state == TRX_QUE_LOCK_WAIT) { + + UT_LIST_ADD_FIRST(trx_thrs, trx->wait_thrs, thr); + thr->state = QUE_THR_LOCK_WAIT; + + } else if (trx->error_state != DB_SUCCESS + && trx->error_state != DB_LOCK_WAIT) { + + /* Error handling built for the MySQL interface */ + thr->state = QUE_THR_COMPLETED; + + } else if (UT_LIST_GET_LEN(trx->signals) > 0 + && graph->fork_type != QUE_FORK_ROLLBACK) { + + thr->state = QUE_THR_SUSPENDED; + } else { + ut_ad(graph->state == QUE_FORK_ACTIVE); + + ret = FALSE; + } + + return(ret); +} + +/************************************************************************** +A patch for MySQL used to 'stop' a dummy query thread used in MySQL. The +query thread is stopped and made inactive, except in the case where +it was put to the lock wait state in lock0lock.c, but the lock has already +been granted or the transaction chosen as a victim in deadlock resolution. */ +UNIV_INTERN +void +que_thr_stop_for_mysql( +/*===================*/ + que_thr_t* thr) /* in: query thread */ +{ + trx_t* trx; + + trx = thr_get_trx(thr); + + mutex_enter(&kernel_mutex); + + if (thr->state == QUE_THR_RUNNING) { + + if (trx->error_state != DB_SUCCESS + && trx->error_state != DB_LOCK_WAIT) { + + /* Error handling built for the MySQL interface */ + thr->state = QUE_THR_COMPLETED; + } else { + /* It must have been a lock wait but the lock was + already released, or this transaction was chosen + as a victim in selective deadlock resolution */ + + mutex_exit(&kernel_mutex); + + return; + } + } + + ut_ad(thr->is_active == TRUE); + ut_ad(trx->n_active_thrs == 1); + ut_ad(thr->graph->n_active_thrs == 1); + + thr->is_active = FALSE; + (thr->graph)->n_active_thrs--; + + trx->n_active_thrs--; + + mutex_exit(&kernel_mutex); +} + +/************************************************************************** +Moves a thread from another state to the QUE_THR_RUNNING state. Increments +the n_active_thrs counters of the query graph and transaction if thr was +not active. */ +UNIV_INTERN +void +que_thr_move_to_run_state_for_mysql( +/*================================*/ + que_thr_t* thr, /* in: an query thread */ + trx_t* trx) /* in: transaction */ +{ + if (thr->magic_n != QUE_THR_MAGIC_N) { + fprintf(stderr, + "que_thr struct appears corrupt; magic n %lu\n", + (unsigned long) thr->magic_n); + + mem_analyze_corruption(thr); + + ut_error; + } + + if (!thr->is_active) { + + thr->graph->n_active_thrs++; + + trx->n_active_thrs++; + + thr->is_active = TRUE; + } + + thr->state = QUE_THR_RUNNING; +} + +/************************************************************************** +A patch for MySQL used to 'stop' a dummy query thread used in MySQL +select, when there is no error or lock wait. */ +UNIV_INTERN +void +que_thr_stop_for_mysql_no_error( +/*============================*/ + que_thr_t* thr, /* in: query thread */ + trx_t* trx) /* in: transaction */ +{ + ut_ad(thr->state == QUE_THR_RUNNING); + ut_ad(thr->is_active == TRUE); + ut_ad(trx->n_active_thrs == 1); + ut_ad(thr->graph->n_active_thrs == 1); + + if (thr->magic_n != QUE_THR_MAGIC_N) { + fprintf(stderr, + "que_thr struct appears corrupt; magic n %lu\n", + (unsigned long) thr->magic_n); + + mem_analyze_corruption(thr); + + ut_error; + } + + thr->state = QUE_THR_COMPLETED; + + thr->is_active = FALSE; + (thr->graph)->n_active_thrs--; + + trx->n_active_thrs--; +} + +/******************************************************************** +Get the first containing loop node (e.g. while_node_t or for_node_t) for the +given node, or NULL if the node is not within a loop. */ +UNIV_INTERN +que_node_t* +que_node_get_containing_loop_node( +/*==============================*/ + /* out: containing loop node, or NULL. */ + que_node_t* node) /* in: node */ +{ + ut_ad(node); + + for (;;) { + ulint type; + + node = que_node_get_parent(node); + + if (!node) { + break; + } + + type = que_node_get_type(node); + + if ((type == QUE_NODE_FOR) || (type == QUE_NODE_WHILE)) { + break; + } + } + + return(node); +} + +/************************************************************************** +Prints info of an SQL query graph node. */ +UNIV_INTERN +void +que_node_print_info( +/*================*/ + que_node_t* node) /* in: query graph node */ +{ + ulint type; + const char* str; + + type = que_node_get_type(node); + + if (type == QUE_NODE_SELECT) { + str = "SELECT"; + } else if (type == QUE_NODE_INSERT) { + str = "INSERT"; + } else if (type == QUE_NODE_UPDATE) { + str = "UPDATE"; + } else if (type == QUE_NODE_WHILE) { + str = "WHILE"; + } else if (type == QUE_NODE_ASSIGNMENT) { + str = "ASSIGNMENT"; + } else if (type == QUE_NODE_IF) { + str = "IF"; + } else if (type == QUE_NODE_FETCH) { + str = "FETCH"; + } else if (type == QUE_NODE_OPEN) { + str = "OPEN"; + } else if (type == QUE_NODE_PROC) { + str = "STORED PROCEDURE"; + } else if (type == QUE_NODE_FUNC) { + str = "FUNCTION"; + } else if (type == QUE_NODE_LOCK) { + str = "LOCK"; + } else if (type == QUE_NODE_THR) { + str = "QUERY THREAD"; + } else if (type == QUE_NODE_COMMIT) { + str = "COMMIT"; + } else if (type == QUE_NODE_UNDO) { + str = "UNDO ROW"; + } else if (type == QUE_NODE_PURGE) { + str = "PURGE ROW"; + } else if (type == QUE_NODE_ROLLBACK) { + str = "ROLLBACK"; + } else if (type == QUE_NODE_CREATE_TABLE) { + str = "CREATE TABLE"; + } else if (type == QUE_NODE_CREATE_INDEX) { + str = "CREATE INDEX"; + } else if (type == QUE_NODE_FOR) { + str = "FOR LOOP"; + } else if (type == QUE_NODE_RETURN) { + str = "RETURN"; + } else if (type == QUE_NODE_EXIT) { + str = "EXIT"; + } else { + str = "UNKNOWN NODE TYPE"; + } + + fprintf(stderr, "Node type %lu: %s, address %p\n", + (ulong) type, str, (void*) node); +} + +/************************************************************************** +Performs an execution step on a query thread. */ +UNIV_INLINE +que_thr_t* +que_thr_step( +/*=========*/ + /* out: query thread to run next: it may + differ from the input parameter if, e.g., a + subprocedure call is made */ + que_thr_t* thr) /* in: query thread */ +{ + que_node_t* node; + que_thr_t* old_thr; + trx_t* trx; + ulint type; + + trx = thr_get_trx(thr); + + ut_ad(thr->state == QUE_THR_RUNNING); + ut_a(trx->error_state == DB_SUCCESS); + + thr->resource++; + + node = thr->run_node; + type = que_node_get_type(node); + + old_thr = thr; + +#ifdef UNIV_DEBUG + if (que_trace_on) { + fputs("To execute: ", stderr); + que_node_print_info(node); + } +#endif + if (type & QUE_NODE_CONTROL_STAT) { + if ((thr->prev_node != que_node_get_parent(node)) + && que_node_get_next(thr->prev_node)) { + + /* The control statements, like WHILE, always pass the + control to the next child statement if there is any + child left */ + + thr->run_node = que_node_get_next(thr->prev_node); + + } else if (type == QUE_NODE_IF) { + if_step(thr); + } else if (type == QUE_NODE_FOR) { + for_step(thr); + } else if (type == QUE_NODE_PROC) { + + /* We can access trx->undo_no without reserving + trx->undo_mutex, because there cannot be active query + threads doing updating or inserting at the moment! */ + + if (thr->prev_node == que_node_get_parent(node)) { + trx->last_sql_stat_start.least_undo_no + = trx->undo_no; + } + + proc_step(thr); + } else if (type == QUE_NODE_WHILE) { + while_step(thr); + } else { + ut_error; + } + } else if (type == QUE_NODE_ASSIGNMENT) { + assign_step(thr); + } else if (type == QUE_NODE_SELECT) { + thr = row_sel_step(thr); + } else if (type == QUE_NODE_INSERT) { + thr = row_ins_step(thr); + } else if (type == QUE_NODE_UPDATE) { + thr = row_upd_step(thr); + } else if (type == QUE_NODE_FETCH) { + thr = fetch_step(thr); + } else if (type == QUE_NODE_OPEN) { + thr = open_step(thr); + } else if (type == QUE_NODE_FUNC) { + proc_eval_step(thr); + + } else if (type == QUE_NODE_LOCK) { + + ut_error; + /* + thr = que_lock_step(thr); + */ + } else if (type == QUE_NODE_THR) { + thr = que_thr_node_step(thr); + } else if (type == QUE_NODE_COMMIT) { + thr = trx_commit_step(thr); + } else if (type == QUE_NODE_UNDO) { + thr = row_undo_step(thr); + } else if (type == QUE_NODE_PURGE) { + thr = row_purge_step(thr); + } else if (type == QUE_NODE_RETURN) { + thr = return_step(thr); + } else if (type == QUE_NODE_EXIT) { + thr = exit_step(thr); + } else if (type == QUE_NODE_ROLLBACK) { + thr = trx_rollback_step(thr); + } else if (type == QUE_NODE_CREATE_TABLE) { + thr = dict_create_table_step(thr); + } else if (type == QUE_NODE_CREATE_INDEX) { + thr = dict_create_index_step(thr); + } else if (type == QUE_NODE_ROW_PRINTF) { + thr = row_printf_step(thr); + } else { + ut_error; + } + + if (type == QUE_NODE_EXIT) { + old_thr->prev_node = que_node_get_containing_loop_node(node); + } else { + old_thr->prev_node = node; + } + + if (thr) { + ut_a(thr_get_trx(thr)->error_state == DB_SUCCESS); + } + + return(thr); +} + +/************************************************************************** +Run a query thread until it finishes or encounters e.g. a lock wait. */ +static +void +que_run_threads_low( +/*================*/ + que_thr_t* thr) /* in: query thread */ +{ + que_thr_t* next_thr; + ulint cumul_resource; + ulint loop_count; + + ut_ad(thr->state == QUE_THR_RUNNING); + ut_a(thr_get_trx(thr)->error_state == DB_SUCCESS); + ut_ad(!mutex_own(&kernel_mutex)); + + /* cumul_resource counts how much resources the OS thread (NOT the + query thread) has spent in this function */ + + loop_count = QUE_MAX_LOOPS_WITHOUT_CHECK; + cumul_resource = 0; +loop: + /* Check that there is enough space in the log to accommodate + possible log entries by this query step; if the operation can touch + more than about 4 pages, checks must be made also within the query + step! */ + + log_free_check(); + + /* Perform the actual query step: note that the query thread + may change if, e.g., a subprocedure call is made */ + + /*-------------------------*/ + next_thr = que_thr_step(thr); + /*-------------------------*/ + + ut_a(!next_thr || (thr_get_trx(next_thr)->error_state == DB_SUCCESS)); + + loop_count++; + + if (next_thr != thr) { + ut_a(next_thr == NULL); + + /* This can change next_thr to a non-NULL value if there was + a lock wait that already completed. */ + que_thr_dec_refer_count(thr, &next_thr); + + if (next_thr == NULL) { + + return; + } + + loop_count = QUE_MAX_LOOPS_WITHOUT_CHECK; + + thr = next_thr; + } + + goto loop; +} + +/************************************************************************** +Run a query thread. Handles lock waits. */ +UNIV_INTERN +void +que_run_threads( +/*============*/ + que_thr_t* thr) /* in: query thread */ +{ +loop: + ut_a(thr_get_trx(thr)->error_state == DB_SUCCESS); + que_run_threads_low(thr); + + mutex_enter(&kernel_mutex); + + switch (thr->state) { + + case QUE_THR_RUNNING: + /* There probably was a lock wait, but it already ended + before we came here: continue running thr */ + + mutex_exit(&kernel_mutex); + + goto loop; + + case QUE_THR_LOCK_WAIT: + mutex_exit(&kernel_mutex); + + /* The ..._mysql_... function works also for InnoDB's + internal threads. Let us wait that the lock wait ends. */ + + srv_suspend_mysql_thread(thr); + + if (thr_get_trx(thr)->error_state != DB_SUCCESS) { + /* thr was chosen as a deadlock victim or there was + a lock wait timeout */ + + que_thr_dec_refer_count(thr, NULL); + + return; + } + + goto loop; + + case QUE_THR_COMPLETED: + case QUE_THR_COMMAND_WAIT: + /* Do nothing */ + break; + + default: + ut_error; + } + + mutex_exit(&kernel_mutex); +} + +/************************************************************************* +Evaluate the given SQL. */ +UNIV_INTERN +ulint +que_eval_sql( +/*=========*/ + /* out: error code or DB_SUCCESS */ + pars_info_t* info, /* in: info struct, or NULL */ + const char* sql, /* in: SQL string */ + ibool reserve_dict_mutex, + /* in: if TRUE, acquire/release + dict_sys->mutex around call to pars_sql. */ + trx_t* trx) /* in: trx */ +{ + que_thr_t* thr; + que_t* graph; + + ut_a(trx->error_state == DB_SUCCESS); + + if (reserve_dict_mutex) { + mutex_enter(&dict_sys->mutex); + } + + graph = pars_sql(info, sql); + + if (reserve_dict_mutex) { + mutex_exit(&dict_sys->mutex); + } + + ut_a(graph); + + graph->trx = trx; + trx->graph = NULL; + + graph->fork_type = QUE_FORK_MYSQL_INTERFACE; + + ut_a(thr = que_fork_start_command(graph)); + + que_run_threads(thr); + + que_graph_free(graph); + + return(trx->error_state); +} diff --git a/storage/xtradb/read/read0read.c b/storage/xtradb/read/read0read.c new file mode 100644 index 00000000000..e3e5ee5d623 --- /dev/null +++ b/storage/xtradb/read/read0read.c @@ -0,0 +1,538 @@ +/***************************************************************************** + +Copyright (c) 1997, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/****************************************************** +Cursor read + +Created 2/16/1997 Heikki Tuuri +*******************************************************/ + +#include "read0read.h" + +#ifdef UNIV_NONINL +#include "read0read.ic" +#endif + +#include "srv0srv.h" +#include "trx0sys.h" + +/* +------------------------------------------------------------------------------- +FACT A: Cursor read view on a secondary index sees only committed versions +------- +of the records in the secondary index or those versions of rows created +by transaction which created a cursor before cursor was created even +if transaction which created the cursor has changed that clustered index page. + +PROOF: We must show that read goes always to the clustered index record +to see that record is visible in the cursor read view. Consider e.g. +following table and SQL-clauses: + +create table t1(a int not null, b int, primary key(a), index(b)); +insert into t1 values (1,1),(2,2); +commit; + +Now consider that we have a cursor for a query + +select b from t1 where b >= 1; + +This query will use secondary key on the table t1. Now after the first fetch +on this cursor if we do a update: + +update t1 set b = 5 where b = 2; + +Now second fetch of the cursor should not see record (2,5) instead it should +see record (2,2). + +We also should show that if we have delete t1 where b = 5; we still +can see record (2,2). + +When we access a secondary key record maximum transaction id is fetched +from this record and this trx_id is compared to up_limit_id in the view. +If trx_id in the record is greater or equal than up_limit_id in the view +cluster record is accessed. Because trx_id of the creating +transaction is stored when this view was created to the list of +trx_ids not seen by this read view previous version of the +record is requested to be built. This is build using clustered record. +If the secondary key record is delete marked it's corresponding +clustered record can be already be purged only if records +trx_id < low_limit_no. Purge can't remove any record deleted by a +transaction which was active when cursor was created. But, we still +may have a deleted secondary key record but no clustered record. But, +this is not a problem because this case is handled in +row_sel_get_clust_rec() function which is called +whenever we note that this read view does not see trx_id in the +record. Thus, we see correct version. Q. E. D. + +------------------------------------------------------------------------------- +FACT B: Cursor read view on a clustered index sees only committed versions +------- +of the records in the clustered index or those versions of rows created +by transaction which created a cursor before cursor was created even +if transaction which created the cursor has changed that clustered index page. + +PROOF: Consider e.g.following table and SQL-clauses: + +create table t1(a int not null, b int, primary key(a)); +insert into t1 values (1),(2); +commit; + +Now consider that we have a cursor for a query + +select a from t1 where a >= 1; + +This query will use clustered key on the table t1. Now after the first fetch +on this cursor if we do a update: + +update t1 set a = 5 where a = 2; + +Now second fetch of the cursor should not see record (5) instead it should +see record (2). + +We also should show that if we have execute delete t1 where a = 5; after +the cursor is opened we still can see record (2). + +When accessing clustered record we always check if this read view sees +trx_id stored to clustered record. By default we don't see any changes +if record trx_id >= low_limit_id i.e. change was made transaction +which started after transaction which created the cursor. If row +was changed by the future transaction a previous version of the +clustered record is created. Thus we see only committed version in +this case. We see all changes made by committed transactions i.e. +record trx_id < up_limit_id. In this case we don't need to do anything, +we already see correct version of the record. We don't see any changes +made by active transaction except creating transaction. We have stored +trx_id of creating transaction to list of trx_ids when this view was +created. Thus we can easily see if this record was changed by the +creating transaction. Because we already have clustered record we can +access roll_ptr. Using this roll_ptr we can fetch undo record. +We can now check that undo_no of the undo record is less than undo_no of the +trancaction which created a view when cursor was created. We see this +clustered record only in case when record undo_no is less than undo_no +in the view. If this is not true we build based on undo_rec previous +version of the record. This record is found because purge can't remove +records accessed by active transaction. Thus we see correct version. Q. E. D. +------------------------------------------------------------------------------- +FACT C: Purge does not remove any delete marked row that is visible +------- +to cursor view. + +TODO: proof this + +*/ + +/************************************************************************* +Creates a read view object. */ +UNIV_INLINE +read_view_t* +read_view_create_low( +/*=================*/ + /* out, own: read view struct */ + ulint n, /* in: number of cells in the trx_ids array */ + mem_heap_t* heap) /* in: memory heap from which allocated */ +{ + read_view_t* view; + + view = mem_heap_alloc(heap, sizeof(read_view_t)); + + view->n_trx_ids = n; + view->trx_ids = mem_heap_alloc(heap, n * sizeof(dulint)); + + return(view); +} + +/************************************************************************* +Makes a copy of the oldest existing read view, with the exception that also +the creating trx of the oldest view is set as not visible in the 'copied' +view. Opens a new view if no views currently exist. The view must be closed +with ..._close. This is used in purge. */ +UNIV_INTERN +read_view_t* +read_view_oldest_copy_or_open_new( +/*==============================*/ + /* out, own: read view struct */ + dulint cr_trx_id, /* in: trx_id of creating + transaction, or (0, 0) used in purge*/ + mem_heap_t* heap) /* in: memory heap from which + allocated */ +{ + read_view_t* old_view; + read_view_t* view_copy; + ibool needs_insert = TRUE; + ulint insert_done = 0; + ulint n; + ulint i; + + ut_ad(mutex_own(&kernel_mutex)); + + old_view = UT_LIST_GET_LAST(trx_sys->view_list); + + if (old_view == NULL) { + + return(read_view_open_now(cr_trx_id, heap)); + } + + n = old_view->n_trx_ids; + + if (!ut_dulint_is_zero(old_view->creator_trx_id)) { + n++; + } else { + needs_insert = FALSE; + } + + view_copy = read_view_create_low(n, heap); + + /* Insert the id of the creator in the right place of the descending + array of ids, if needs_insert is TRUE: */ + + i = 0; + while (i < n) { + if (needs_insert + && (i >= old_view->n_trx_ids + || ut_dulint_cmp(old_view->creator_trx_id, + read_view_get_nth_trx_id(old_view, i)) + > 0)) { + + read_view_set_nth_trx_id(view_copy, i, + old_view->creator_trx_id); + needs_insert = FALSE; + insert_done = 1; + } else { + read_view_set_nth_trx_id(view_copy, i, + read_view_get_nth_trx_id( + old_view, + i - insert_done)); + } + + i++; + } + + view_copy->creator_trx_id = cr_trx_id; + + view_copy->low_limit_no = old_view->low_limit_no; + view_copy->low_limit_id = old_view->low_limit_id; + + + if (n > 0) { + /* The last active transaction has the smallest id: */ + view_copy->up_limit_id = read_view_get_nth_trx_id( + view_copy, n - 1); + } else { + view_copy->up_limit_id = old_view->up_limit_id; + } + + UT_LIST_ADD_LAST(view_list, trx_sys->view_list, view_copy); + + return(view_copy); +} + +/************************************************************************* +Opens a read view where exactly the transactions serialized before this +point in time are seen in the view. */ +UNIV_INTERN +read_view_t* +read_view_open_now( +/*===============*/ + /* out, own: read view struct */ + dulint cr_trx_id, /* in: trx_id of creating + transaction, or (0, 0) used in + purge */ + mem_heap_t* heap) /* in: memory heap from which + allocated */ +{ + read_view_t* view; + trx_t* trx; + ulint n; + + ut_ad(mutex_own(&kernel_mutex)); + + view = read_view_create_low(UT_LIST_GET_LEN(trx_sys->trx_list), heap); + + view->creator_trx_id = cr_trx_id; + view->type = VIEW_NORMAL; + view->undo_no = ut_dulint_zero; + + /* No future transactions should be visible in the view */ + + view->low_limit_no = trx_sys->max_trx_id; + view->low_limit_id = view->low_limit_no; + + n = 0; + trx = UT_LIST_GET_FIRST(trx_sys->trx_list); + + /* No active transaction should be visible, except cr_trx */ + + while (trx) { + if (ut_dulint_cmp(trx->id, cr_trx_id) != 0 + && (trx->conc_state == TRX_ACTIVE + || trx->conc_state == TRX_PREPARED)) { + + read_view_set_nth_trx_id(view, n, trx->id); + + n++; + + /* NOTE that a transaction whose trx number is < + trx_sys->max_trx_id can still be active, if it is + in the middle of its commit! Note that when a + transaction starts, we initialize trx->no to + ut_dulint_max. */ + + if (ut_dulint_cmp(view->low_limit_no, trx->no) > 0) { + + view->low_limit_no = trx->no; + } + } + + trx = UT_LIST_GET_NEXT(trx_list, trx); + } + + view->n_trx_ids = n; + + if (n > 0) { + /* The last active transaction has the smallest id: */ + view->up_limit_id = read_view_get_nth_trx_id(view, n - 1); + } else { + view->up_limit_id = view->low_limit_id; + } + + + UT_LIST_ADD_FIRST(view_list, trx_sys->view_list, view); + + return(view); +} + +/************************************************************************* +Closes a read view. */ +UNIV_INTERN +void +read_view_close( +/*============*/ + read_view_t* view) /* in: read view */ +{ + ut_ad(mutex_own(&kernel_mutex)); + + UT_LIST_REMOVE(view_list, trx_sys->view_list, view); +} + +/************************************************************************* +Closes a consistent read view for MySQL. This function is called at an SQL +statement end if the trx isolation level is <= TRX_ISO_READ_COMMITTED. */ +UNIV_INTERN +void +read_view_close_for_mysql( +/*======================*/ + trx_t* trx) /* in: trx which has a read view */ +{ + ut_a(trx->global_read_view); + + mutex_enter(&kernel_mutex); + + read_view_close(trx->global_read_view); + + mem_heap_empty(trx->global_read_view_heap); + + trx->read_view = NULL; + trx->global_read_view = NULL; + + mutex_exit(&kernel_mutex); +} + +/************************************************************************* +Prints a read view to stderr. */ +UNIV_INTERN +void +read_view_print( +/*============*/ + read_view_t* view) /* in: read view */ +{ + ulint n_ids; + ulint i; + + if (view->type == VIEW_HIGH_GRANULARITY) { + fprintf(stderr, + "High-granularity read view undo_n:o %lu %lu\n", + (ulong) ut_dulint_get_high(view->undo_no), + (ulong) ut_dulint_get_low(view->undo_no)); + } else { + fprintf(stderr, "Normal read view\n"); + } + + fprintf(stderr, "Read view low limit trx n:o %lu %lu\n", + (ulong) ut_dulint_get_high(view->low_limit_no), + (ulong) ut_dulint_get_low(view->low_limit_no)); + + fprintf(stderr, "Read view up limit trx id " TRX_ID_FMT "\n", + TRX_ID_PREP_PRINTF(view->up_limit_id)); + + fprintf(stderr, "Read view low limit trx id " TRX_ID_FMT "\n", + TRX_ID_PREP_PRINTF(view->low_limit_id)); + + fprintf(stderr, "Read view individually stored trx ids:\n"); + + n_ids = view->n_trx_ids; + + for (i = 0; i < n_ids; i++) { + fprintf(stderr, "Read view trx id " TRX_ID_FMT "\n", + TRX_ID_PREP_PRINTF( + read_view_get_nth_trx_id(view, i))); + } +} + +/************************************************************************* +Create a high-granularity consistent cursor view for mysql to be used +in cursors. In this consistent read view modifications done by the +creating transaction after the cursor is created or future transactions +are not visible. */ +UNIV_INTERN +cursor_view_t* +read_cursor_view_create_for_mysql( +/*==============================*/ + trx_t* cr_trx) /* in: trx where cursor view is created */ +{ + cursor_view_t* curview; + read_view_t* view; + mem_heap_t* heap; + trx_t* trx; + ulint n; + + ut_a(cr_trx); + + /* Use larger heap than in trx_create when creating a read_view + because cursors are quite long. */ + + heap = mem_heap_create(512); + + curview = (cursor_view_t*) mem_heap_alloc(heap, sizeof(cursor_view_t)); + curview->heap = heap; + + /* Drop cursor tables from consideration when evaluating the need of + auto-commit */ + curview->n_mysql_tables_in_use = cr_trx->n_mysql_tables_in_use; + cr_trx->n_mysql_tables_in_use = 0; + + mutex_enter(&kernel_mutex); + + curview->read_view = read_view_create_low( + UT_LIST_GET_LEN(trx_sys->trx_list), curview->heap); + + view = curview->read_view; + view->creator_trx_id = cr_trx->id; + view->type = VIEW_HIGH_GRANULARITY; + view->undo_no = cr_trx->undo_no; + + /* No future transactions should be visible in the view */ + + view->low_limit_no = trx_sys->max_trx_id; + view->low_limit_id = view->low_limit_no; + + n = 0; + trx = UT_LIST_GET_FIRST(trx_sys->trx_list); + + /* No active transaction should be visible */ + + while (trx) { + + if (trx->conc_state == TRX_ACTIVE + || trx->conc_state == TRX_PREPARED) { + + read_view_set_nth_trx_id(view, n, trx->id); + + n++; + + /* NOTE that a transaction whose trx number is < + trx_sys->max_trx_id can still be active, if it is + in the middle of its commit! Note that when a + transaction starts, we initialize trx->no to + ut_dulint_max. */ + + if (ut_dulint_cmp(view->low_limit_no, trx->no) > 0) { + + view->low_limit_no = trx->no; + } + } + + trx = UT_LIST_GET_NEXT(trx_list, trx); + } + + view->n_trx_ids = n; + + if (n > 0) { + /* The last active transaction has the smallest id: */ + view->up_limit_id = read_view_get_nth_trx_id(view, n - 1); + } else { + view->up_limit_id = view->low_limit_id; + } + + UT_LIST_ADD_FIRST(view_list, trx_sys->view_list, view); + + mutex_exit(&kernel_mutex); + + return(curview); +} + +/************************************************************************* +Close a given consistent cursor view for mysql and restore global read view +back to a transaction read view. */ +UNIV_INTERN +void +read_cursor_view_close_for_mysql( +/*=============================*/ + trx_t* trx, /* in: trx */ + cursor_view_t* curview)/* in: cursor view to be closed */ +{ + ut_a(curview); + ut_a(curview->read_view); + ut_a(curview->heap); + + /* Add cursor's tables to the global count of active tables that + belong to this transaction */ + trx->n_mysql_tables_in_use += curview->n_mysql_tables_in_use; + + mutex_enter(&kernel_mutex); + + read_view_close(curview->read_view); + trx->read_view = trx->global_read_view; + + mutex_exit(&kernel_mutex); + + mem_heap_free(curview->heap); +} + +/************************************************************************* +This function sets a given consistent cursor view to a transaction +read view if given consistent cursor view is not NULL. Otherwise, function +restores a global read view to a transaction read view. */ +UNIV_INTERN +void +read_cursor_set_for_mysql( +/*======================*/ + trx_t* trx, /* in: transaction where cursor is set */ + cursor_view_t* curview)/* in: consistent cursor view to be set */ +{ + ut_a(trx); + + mutex_enter(&kernel_mutex); + + if (UNIV_LIKELY(curview != NULL)) { + trx->read_view = curview->read_view; + } else { + trx->read_view = trx->global_read_view; + } + + mutex_exit(&kernel_mutex); +} diff --git a/storage/xtradb/rem/rem0cmp.c b/storage/xtradb/rem/rem0cmp.c new file mode 100644 index 00000000000..f8e5a6b88ff --- /dev/null +++ b/storage/xtradb/rem/rem0cmp.c @@ -0,0 +1,1239 @@ +/***************************************************************************** + +Copyright (c) 1994, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/*********************************************************************** +Comparison services for records + +Created 7/1/1994 Heikki Tuuri +************************************************************************/ + +#include "rem0cmp.h" + +#ifdef UNIV_NONINL +#include "rem0cmp.ic" +#endif + +#include "srv0srv.h" + +/* ALPHABETICAL ORDER + ================== + +The records are put into alphabetical order in the following +way: let F be the first field where two records disagree. +If there is a character in some position n where the the +records disagree, the order is determined by comparison of +the characters at position n, possibly after +collating transformation. If there is no such character, +but the corresponding fields have different lengths, then +if the data type of the fields is paddable, +shorter field is padded with a padding character. If the +data type is not paddable, longer field is considered greater. +Finally, the SQL null is bigger than any other value. + +At the present, the comparison functions return 0 in the case, +where two records disagree only in the way that one +has more fields than the other. */ + +#ifdef UNIV_DEBUG +/***************************************************************** +Used in debug checking of cmp_dtuple_... . +This function is used to compare a data tuple to a physical record. If +dtuple has n fields then rec must have either m >= n fields, or it must +differ from dtuple in some of the m fields rec has. */ +static +int +cmp_debug_dtuple_rec_with_match( +/*============================*/ + /* out: 1, 0, -1, if dtuple is greater, equal, + less than rec, respectively, when only the + common first fields are compared */ + const dtuple_t* dtuple, /* in: data tuple */ + const rec_t* rec, /* in: physical record which differs from + dtuple in some of the common fields, or which + has an equal number or more fields than + dtuple */ + const ulint* offsets,/* in: array returned by rec_get_offsets() */ + ulint* matched_fields);/* in/out: number of already + completely matched fields; when function + returns, contains the value for current + comparison */ +#endif /* UNIV_DEBUG */ +#ifndef UNIV_HOTBACKUP +/***************************************************************** +This function is used to compare two data fields for which the data type +is such that we must use MySQL code to compare them. The prototype here +must be a copy of the the one in ha_innobase.cc! */ +extern +int +innobase_mysql_cmp( +/*===============*/ + /* out: 1, 0, -1, if a is greater, + equal, less than b, respectively */ + int mysql_type, /* in: MySQL type */ + uint charset_number, /* in: number of the charset */ + const unsigned char* a, /* in: data field */ + unsigned int a_length, /* in: data field length, + not UNIV_SQL_NULL */ + const unsigned char* b, /* in: data field */ + unsigned int b_length); /* in: data field length, + not UNIV_SQL_NULL */ +#endif /* !UNIV_HOTBACKUP */ +/************************************************************************* +Transforms the character code so that it is ordered appropriately for the +language. This is only used for the latin1 char set. MySQL does the +comparisons for other char sets. */ +UNIV_INLINE +ulint +cmp_collate( +/*========*/ + /* out: collation order position */ + ulint code) /* in: code of a character stored in database record */ +{ + return((ulint) srv_latin1_ordering[code]); +} + +/***************************************************************** +Returns TRUE if two columns are equal for comparison purposes. */ +UNIV_INTERN +ibool +cmp_cols_are_equal( +/*===============*/ + /* out: TRUE if the columns are + considered equal in comparisons */ + const dict_col_t* col1, /* in: column 1 */ + const dict_col_t* col2, /* in: column 2 */ + ibool check_charsets) + /* in: whether to check charsets */ +{ + if (dtype_is_non_binary_string_type(col1->mtype, col1->prtype) + && dtype_is_non_binary_string_type(col2->mtype, col2->prtype)) { + + /* Both are non-binary string types: they can be compared if + and only if the charset-collation is the same */ + + if (check_charsets) { + return(dtype_get_charset_coll(col1->prtype) + == dtype_get_charset_coll(col2->prtype)); + } else { + return(TRUE); + } + } + + if (dtype_is_binary_string_type(col1->mtype, col1->prtype) + && dtype_is_binary_string_type(col2->mtype, col2->prtype)) { + + /* Both are binary string types: they can be compared */ + + return(TRUE); + } + + if (col1->mtype != col2->mtype) { + + return(FALSE); + } + + if (col1->mtype == DATA_INT + && (col1->prtype & DATA_UNSIGNED) + != (col2->prtype & DATA_UNSIGNED)) { + + /* The storage format of an unsigned integer is different + from a signed integer: in a signed integer we OR + 0x8000... to the value of positive integers. */ + + return(FALSE); + } + + return(col1->mtype != DATA_INT || col1->len == col2->len); +} + +#ifndef UNIV_HOTBACKUP +/***************************************************************** +Innobase uses this function to compare two data fields for which the data type +is such that we must compare whole fields or call MySQL to do the comparison */ +static +int +cmp_whole_field( +/*============*/ + /* out: 1, 0, -1, if a is greater, + equal, less than b, respectively */ + ulint mtype, /* in: main type */ + ulint prtype, /* in: precise type */ + const byte* a, /* in: data field */ + unsigned int a_length, /* in: data field length, + not UNIV_SQL_NULL */ + const byte* b, /* in: data field */ + unsigned int b_length) /* in: data field length, + not UNIV_SQL_NULL */ +{ + float f_1; + float f_2; + double d_1; + double d_2; + int swap_flag = 1; + + switch (mtype) { + + case DATA_DECIMAL: + /* Remove preceding spaces */ + for (; a_length && *a == ' '; a++, a_length--); + for (; b_length && *b == ' '; b++, b_length--); + + if (*a == '-') { + if (*b != '-') { + return(-1); + } + + a++; b++; + a_length--; + b_length--; + + swap_flag = -1; + + } else if (*b == '-') { + + return(1); + } + + while (a_length > 0 && (*a == '+' || *a == '0')) { + a++; a_length--; + } + + while (b_length > 0 && (*b == '+' || *b == '0')) { + b++; b_length--; + } + + if (a_length != b_length) { + if (a_length < b_length) { + return(-swap_flag); + } + + return(swap_flag); + } + + while (a_length > 0 && *a == *b) { + + a++; b++; a_length--; + } + + if (a_length == 0) { + + return(0); + } + + if (*a > *b) { + return(swap_flag); + } + + return(-swap_flag); + case DATA_DOUBLE: + d_1 = mach_double_read(a); + d_2 = mach_double_read(b); + + if (d_1 > d_2) { + return(1); + } else if (d_2 > d_1) { + return(-1); + } + + return(0); + + case DATA_FLOAT: + f_1 = mach_float_read(a); + f_2 = mach_float_read(b); + + if (f_1 > f_2) { + return(1); + } else if (f_2 > f_1) { + return(-1); + } + + return(0); + case DATA_BLOB: + if (prtype & DATA_BINARY_TYPE) { + + ut_print_timestamp(stderr); + fprintf(stderr, + " InnoDB: Error: comparing a binary BLOB" + " with a character set sensitive\n" + "InnoDB: comparison!\n"); + } + /* fall through */ + case DATA_VARMYSQL: + case DATA_MYSQL: + return(innobase_mysql_cmp( + (int)(prtype & DATA_MYSQL_TYPE_MASK), + (uint)dtype_get_charset_coll(prtype), + a, a_length, b, b_length)); + default: + fprintf(stderr, + "InnoDB: unknown type number %lu\n", + (ulong) mtype); + ut_error; + } + + return(0); +} +#endif /* !UNIV_HOTBACKUP */ + +/***************************************************************** +This function is used to compare two data fields for which we know the +data type. */ +UNIV_INTERN +int +cmp_data_data_slow( +/*===============*/ + /* out: 1, 0, -1, if data1 is greater, equal, + less than data2, respectively */ + ulint mtype, /* in: main type */ + ulint prtype, /* in: precise type */ + const byte* data1, /* in: data field (== a pointer to a memory + buffer) */ + ulint len1, /* in: data field length or UNIV_SQL_NULL */ + const byte* data2, /* in: data field (== a pointer to a memory + buffer) */ + ulint len2) /* in: data field length or UNIV_SQL_NULL */ +{ +#ifndef UNIV_HOTBACKUP + ulint data1_byte; + ulint data2_byte; + ulint cur_bytes; + + if (len1 == UNIV_SQL_NULL || len2 == UNIV_SQL_NULL) { + + if (len1 == len2) { + + return(0); + } + + if (len1 == UNIV_SQL_NULL) { + /* We define the SQL null to be the smallest possible + value of a field in the alphabetical order */ + + return(-1); + } + + return(1); + } + + if (mtype >= DATA_FLOAT + || (mtype == DATA_BLOB + && 0 == (prtype & DATA_BINARY_TYPE) + && dtype_get_charset_coll(prtype) + != DATA_MYSQL_LATIN1_SWEDISH_CHARSET_COLL)) { + + return(cmp_whole_field(mtype, prtype, + data1, (unsigned) len1, + data2, (unsigned) len2)); + } + + /* Compare then the fields */ + + cur_bytes = 0; + + for (;;) { + if (len1 <= cur_bytes) { + if (len2 <= cur_bytes) { + + return(0); + } + + data1_byte = dtype_get_pad_char(mtype, prtype); + + if (data1_byte == ULINT_UNDEFINED) { + + return(-1); + } + } else { + data1_byte = *data1; + } + + if (len2 <= cur_bytes) { + data2_byte = dtype_get_pad_char(mtype, prtype); + + if (data2_byte == ULINT_UNDEFINED) { + + return(1); + } + } else { + data2_byte = *data2; + } + + if (data1_byte == data2_byte) { + /* If the bytes are equal, they will remain such even + after the collation transformation below */ + + goto next_byte; + } + + if (mtype <= DATA_CHAR + || (mtype == DATA_BLOB + && 0 == (prtype & DATA_BINARY_TYPE))) { + + data1_byte = cmp_collate(data1_byte); + data2_byte = cmp_collate(data2_byte); + } + + if (data1_byte > data2_byte) { + + return(1); + } else if (data1_byte < data2_byte) { + + return(-1); + } +next_byte: + /* Next byte */ + cur_bytes++; + data1++; + data2++; + } +#else /* !UNIV_HOTBACKUP */ + /* This function depends on MySQL code that is not included in + InnoDB Hot Backup builds. Besides, this function should never + be called in InnoDB Hot Backup. */ + ut_error; +#endif /* !UNIV_HOTBACKUP */ + + return(0); /* Not reached */ +} + +/***************************************************************** +This function is used to compare a data tuple to a physical record. +Only dtuple->n_fields_cmp first fields are taken into account for +the the data tuple! If we denote by n = n_fields_cmp, then rec must +have either m >= n fields, or it must differ from dtuple in some of +the m fields rec has. If rec has an externally stored field we do not +compare it but return with value 0 if such a comparison should be +made. */ +UNIV_INTERN +int +cmp_dtuple_rec_with_match( +/*======================*/ + /* out: 1, 0, -1, if dtuple is greater, equal, + less than rec, respectively, when only the + common first fields are compared, or + until the first externally stored field in + rec */ + const dtuple_t* dtuple, /* in: data tuple */ + const rec_t* rec, /* in: physical record which differs from + dtuple in some of the common fields, or which + has an equal number or more fields than + dtuple */ + const ulint* offsets,/* in: array returned by rec_get_offsets() */ + ulint* matched_fields, /* in/out: number of already completely + matched fields; when function returns, + contains the value for current comparison */ + ulint* matched_bytes) /* in/out: number of already matched + bytes within the first field not completely + matched; when function returns, contains the + value for current comparison */ +{ +#ifndef UNIV_HOTBACKUP + const dfield_t* dtuple_field; /* current field in logical record */ + ulint dtuple_f_len; /* the length of the current field + in the logical record */ + const byte* dtuple_b_ptr; /* pointer to the current byte in + logical field data */ + ulint dtuple_byte; /* value of current byte to be compared + in dtuple*/ + ulint rec_f_len; /* length of current field in rec */ + const byte* rec_b_ptr; /* pointer to the current byte in + rec field */ + ulint rec_byte; /* value of current byte to be + compared in rec */ + ulint cur_field; /* current field number */ + ulint cur_bytes; /* number of already matched bytes + in current field */ + int ret = 3333; /* return value */ + + ut_ad(dtuple && rec && matched_fields && matched_bytes); + ut_ad(dtuple_check_typed(dtuple)); + ut_ad(rec_offs_validate(rec, NULL, offsets)); + + cur_field = *matched_fields; + cur_bytes = *matched_bytes; + + ut_ad(cur_field <= dtuple_get_n_fields_cmp(dtuple)); + ut_ad(cur_field <= rec_offs_n_fields(offsets)); + + if (cur_bytes == 0 && cur_field == 0) { + ulint rec_info = rec_get_info_bits(rec, + rec_offs_comp(offsets)); + ulint tup_info = dtuple_get_info_bits(dtuple); + + if (UNIV_UNLIKELY(rec_info & REC_INFO_MIN_REC_FLAG)) { + ret = !(tup_info & REC_INFO_MIN_REC_FLAG); + goto order_resolved; + } else if (UNIV_UNLIKELY(tup_info & REC_INFO_MIN_REC_FLAG)) { + ret = -1; + goto order_resolved; + } + } + + /* Match fields in a loop; stop if we run out of fields in dtuple + or find an externally stored field */ + + while (cur_field < dtuple_get_n_fields_cmp(dtuple)) { + + ulint mtype; + ulint prtype; + + dtuple_field = dtuple_get_nth_field(dtuple, cur_field); + { + const dtype_t* type + = dfield_get_type(dtuple_field); + + mtype = type->mtype; + prtype = type->prtype; + } + + dtuple_f_len = dfield_get_len(dtuple_field); + + rec_b_ptr = rec_get_nth_field(rec, offsets, + cur_field, &rec_f_len); + + /* If we have matched yet 0 bytes, it may be that one or + both the fields are SQL null, or the record or dtuple may be + the predefined minimum record, or the field is externally + stored */ + + if (UNIV_LIKELY(cur_bytes == 0)) { + if (rec_offs_nth_extern(offsets, cur_field)) { + /* We do not compare to an externally + stored field */ + + ret = 0; + + goto order_resolved; + } + + if (dtuple_f_len == UNIV_SQL_NULL) { + if (rec_f_len == UNIV_SQL_NULL) { + + goto next_field; + } + + ret = -1; + goto order_resolved; + } else if (rec_f_len == UNIV_SQL_NULL) { + /* We define the SQL null to be the + smallest possible value of a field + in the alphabetical order */ + + ret = 1; + goto order_resolved; + } + } + + if (mtype >= DATA_FLOAT + || (mtype == DATA_BLOB + && 0 == (prtype & DATA_BINARY_TYPE) + && dtype_get_charset_coll(prtype) + != DATA_MYSQL_LATIN1_SWEDISH_CHARSET_COLL)) { + + ret = cmp_whole_field(mtype, prtype, + dfield_get_data(dtuple_field), + (unsigned) dtuple_f_len, + rec_b_ptr, (unsigned) rec_f_len); + + if (ret != 0) { + cur_bytes = 0; + + goto order_resolved; + } else { + goto next_field; + } + } + + /* Set the pointers at the current byte */ + + rec_b_ptr = rec_b_ptr + cur_bytes; + dtuple_b_ptr = (byte*)dfield_get_data(dtuple_field) + + cur_bytes; + /* Compare then the fields */ + + for (;;) { + if (UNIV_UNLIKELY(rec_f_len <= cur_bytes)) { + if (dtuple_f_len <= cur_bytes) { + + goto next_field; + } + + rec_byte = dtype_get_pad_char(mtype, prtype); + + if (rec_byte == ULINT_UNDEFINED) { + ret = 1; + + goto order_resolved; + } + } else { + rec_byte = *rec_b_ptr; + } + + if (UNIV_UNLIKELY(dtuple_f_len <= cur_bytes)) { + dtuple_byte = dtype_get_pad_char(mtype, + prtype); + + if (dtuple_byte == ULINT_UNDEFINED) { + ret = -1; + + goto order_resolved; + } + } else { + dtuple_byte = *dtuple_b_ptr; + } + + if (dtuple_byte == rec_byte) { + /* If the bytes are equal, they will + remain such even after the collation + transformation below */ + + goto next_byte; + } + + if (mtype <= DATA_CHAR + || (mtype == DATA_BLOB + && !(prtype & DATA_BINARY_TYPE))) { + + rec_byte = cmp_collate(rec_byte); + dtuple_byte = cmp_collate(dtuple_byte); + } + + ret = (int) (dtuple_byte - rec_byte); + if (UNIV_LIKELY(ret)) { + if (ret < 0) { + ret = -1; + goto order_resolved; + } else { + ret = 1; + goto order_resolved; + } + } +next_byte: + /* Next byte */ + cur_bytes++; + rec_b_ptr++; + dtuple_b_ptr++; + } + +next_field: + cur_field++; + cur_bytes = 0; + } + + ut_ad(cur_bytes == 0); + + ret = 0; /* If we ran out of fields, dtuple was equal to rec + up to the common fields */ +order_resolved: + ut_ad((ret >= - 1) && (ret <= 1)); + ut_ad(ret == cmp_debug_dtuple_rec_with_match(dtuple, rec, offsets, + matched_fields)); + ut_ad(*matched_fields == cur_field); /* In the debug version, the + above cmp_debug_... sets + *matched_fields to a value */ + *matched_fields = cur_field; + *matched_bytes = cur_bytes; + + return(ret); +#else /* !UNIV_HOTBACKUP */ + /* This function depends on MySQL code that is not included in + InnoDB Hot Backup builds. Besides, this function should never + be called in InnoDB Hot Backup. */ + ut_error; + return(0); +#endif /* !UNIV_HOTBACKUP */ +} + +/****************************************************************** +Compares a data tuple to a physical record. */ +UNIV_INTERN +int +cmp_dtuple_rec( +/*===========*/ + /* out: 1, 0, -1, if dtuple is greater, equal, + less than rec, respectively; see the comments + for cmp_dtuple_rec_with_match */ + const dtuple_t* dtuple, /* in: data tuple */ + const rec_t* rec, /* in: physical record */ + const ulint* offsets)/* in: array returned by rec_get_offsets() */ +{ + ulint matched_fields = 0; + ulint matched_bytes = 0; + + ut_ad(rec_offs_validate(rec, NULL, offsets)); + return(cmp_dtuple_rec_with_match(dtuple, rec, offsets, + &matched_fields, &matched_bytes)); +} + +/****************************************************************** +Checks if a dtuple is a prefix of a record. The last field in dtuple +is allowed to be a prefix of the corresponding field in the record. */ +UNIV_INTERN +ibool +cmp_dtuple_is_prefix_of_rec( +/*========================*/ + /* out: TRUE if prefix */ + const dtuple_t* dtuple, /* in: data tuple */ + const rec_t* rec, /* in: physical record */ + const ulint* offsets)/* in: array returned by rec_get_offsets() */ +{ + ulint n_fields; + ulint matched_fields = 0; + ulint matched_bytes = 0; + + ut_ad(rec_offs_validate(rec, NULL, offsets)); + n_fields = dtuple_get_n_fields(dtuple); + + if (n_fields > rec_offs_n_fields(offsets)) { + + return(FALSE); + } + + cmp_dtuple_rec_with_match(dtuple, rec, offsets, + &matched_fields, &matched_bytes); + if (matched_fields == n_fields) { + + return(TRUE); + } + + if (matched_fields == n_fields - 1 + && matched_bytes == dfield_get_len( + dtuple_get_nth_field(dtuple, n_fields - 1))) { + return(TRUE); + } + + return(FALSE); +} + +#ifndef UNIV_HOTBACKUP +/***************************************************************** +Compare two physical records that contain the same number of columns, +none of which are stored externally. */ +UNIV_INTERN +int +cmp_rec_rec_simple( +/*===============*/ + /* out: 1, 0 , -1 if rec1 is greater, + equal, less, respectively, than rec2 */ + const rec_t* rec1, /* in: physical record */ + const rec_t* rec2, /* in: physical record */ + const ulint* offsets1,/* in: rec_get_offsets(rec1, index) */ + const ulint* offsets2,/* in: rec_get_offsets(rec2, index) */ + const dict_index_t* index) /* in: data dictionary index */ +{ + ulint rec1_f_len; /* length of current field in rec1 */ + const byte* rec1_b_ptr; /* pointer to the current byte + in rec1 field */ + ulint rec1_byte; /* value of current byte to be + compared in rec1 */ + ulint rec2_f_len; /* length of current field in rec2 */ + const byte* rec2_b_ptr; /* pointer to the current byte + in rec2 field */ + ulint rec2_byte; /* value of current byte to be + compared in rec2 */ + ulint cur_field; /* current field number */ + ulint n_uniq; + + n_uniq = dict_index_get_n_unique(index); + ut_ad(rec_offs_n_fields(offsets1) >= n_uniq); + ut_ad(rec_offs_n_fields(offsets2) >= n_uniq); + + ut_ad(rec_offs_comp(offsets1) == rec_offs_comp(offsets2)); + + for (cur_field = 0; cur_field < n_uniq; cur_field++) { + + ulint cur_bytes; + ulint mtype; + ulint prtype; + + { + const dict_col_t* col + = dict_index_get_nth_col(index, cur_field); + + mtype = col->mtype; + prtype = col->prtype; + } + + ut_ad(!rec_offs_nth_extern(offsets1, cur_field)); + ut_ad(!rec_offs_nth_extern(offsets2, cur_field)); + + rec1_b_ptr = rec_get_nth_field(rec1, offsets1, + cur_field, &rec1_f_len); + rec2_b_ptr = rec_get_nth_field(rec2, offsets2, + cur_field, &rec2_f_len); + + if (rec1_f_len == UNIV_SQL_NULL + || rec2_f_len == UNIV_SQL_NULL) { + + if (rec1_f_len == rec2_f_len) { + + goto next_field; + + } else if (rec2_f_len == UNIV_SQL_NULL) { + + /* We define the SQL null to be the + smallest possible value of a field + in the alphabetical order */ + + return(1); + } else { + return(-1); + } + } + + if (mtype >= DATA_FLOAT + || (mtype == DATA_BLOB + && 0 == (prtype & DATA_BINARY_TYPE) + && dtype_get_charset_coll(prtype) + != DATA_MYSQL_LATIN1_SWEDISH_CHARSET_COLL)) { + int ret = cmp_whole_field(mtype, prtype, + rec1_b_ptr, + (unsigned) rec1_f_len, + rec2_b_ptr, + (unsigned) rec2_f_len); + if (ret) { + return(ret); + } + + goto next_field; + } + + /* Compare the fields */ + for (cur_bytes = 0;; cur_bytes++, rec1_b_ptr++, rec2_b_ptr++) { + if (rec2_f_len <= cur_bytes) { + + if (rec1_f_len <= cur_bytes) { + + goto next_field; + } + + rec2_byte = dtype_get_pad_char(mtype, prtype); + + if (rec2_byte == ULINT_UNDEFINED) { + return(1); + } + } else { + rec2_byte = *rec2_b_ptr; + } + + if (rec1_f_len <= cur_bytes) { + rec1_byte = dtype_get_pad_char(mtype, prtype); + + if (rec1_byte == ULINT_UNDEFINED) { + return(-1); + } + } else { + rec1_byte = *rec1_b_ptr; + } + + if (rec1_byte == rec2_byte) { + /* If the bytes are equal, they will remain + such even after the collation transformation + below */ + + continue; + } + + if (mtype <= DATA_CHAR + || (mtype == DATA_BLOB + && !(prtype & DATA_BINARY_TYPE))) { + + rec1_byte = cmp_collate(rec1_byte); + rec2_byte = cmp_collate(rec2_byte); + } + + if (rec1_byte < rec2_byte) { + return(-1); + } else if (rec1_byte > rec2_byte) { + return(1); + } + } +next_field: + continue; + } + + /* If we ran out of fields, rec1 was equal to rec2. */ + return(0); +} +#endif /* !UNIV_HOTBACKUP */ + +/***************************************************************** +This function is used to compare two physical records. Only the common +first fields are compared, and if an externally stored field is +encountered, then 0 is returned. */ +UNIV_INTERN +int +cmp_rec_rec_with_match( +/*===================*/ + /* out: 1, 0 , -1 if rec1 is greater, equal, + less, respectively, than rec2; only the common + first fields are compared */ + const rec_t* rec1, /* in: physical record */ + const rec_t* rec2, /* in: physical record */ + const ulint* offsets1,/* in: rec_get_offsets(rec1, index) */ + const ulint* offsets2,/* in: rec_get_offsets(rec2, index) */ + dict_index_t* index, /* in: data dictionary index */ + ulint* matched_fields, /* in/out: number of already completely + matched fields; when the function returns, + contains the value the for current + comparison */ + ulint* matched_bytes, /* in/out: number of already matched + bytes within the first field not completely + matched; when the function returns, contains + the value for the current comparison */ + ulint stats_method) +{ +#ifndef UNIV_HOTBACKUP + ulint rec1_n_fields; /* the number of fields in rec */ + ulint rec1_f_len; /* length of current field in rec */ + const byte* rec1_b_ptr; /* pointer to the current byte + in rec field */ + ulint rec1_byte; /* value of current byte to be + compared in rec */ + ulint rec2_n_fields; /* the number of fields in rec */ + ulint rec2_f_len; /* length of current field in rec */ + const byte* rec2_b_ptr; /* pointer to the current byte + in rec field */ + ulint rec2_byte; /* value of current byte to be + compared in rec */ + ulint cur_field; /* current field number */ + ulint cur_bytes; /* number of already matched + bytes in current field */ + int ret = 0; /* return value */ + ulint comp; + + ut_ad(rec1 && rec2 && index); + ut_ad(rec_offs_validate(rec1, index, offsets1)); + ut_ad(rec_offs_validate(rec2, index, offsets2)); + ut_ad(rec_offs_comp(offsets1) == rec_offs_comp(offsets2)); + + comp = rec_offs_comp(offsets1); + rec1_n_fields = rec_offs_n_fields(offsets1); + rec2_n_fields = rec_offs_n_fields(offsets2); + + cur_field = *matched_fields; + cur_bytes = *matched_bytes; + + /* Match fields in a loop */ + + while ((cur_field < rec1_n_fields) && (cur_field < rec2_n_fields)) { + + ulint mtype; + ulint prtype; + + if (UNIV_UNLIKELY(index->type & DICT_UNIVERSAL)) { + /* This is for the insert buffer B-tree. */ + mtype = DATA_BINARY; + prtype = 0; + } else { + const dict_col_t* col + = dict_index_get_nth_col(index, cur_field); + + mtype = col->mtype; + prtype = col->prtype; + } + + rec1_b_ptr = rec_get_nth_field(rec1, offsets1, + cur_field, &rec1_f_len); + rec2_b_ptr = rec_get_nth_field(rec2, offsets2, + cur_field, &rec2_f_len); + + if (cur_bytes == 0) { + if (cur_field == 0) { + /* Test if rec is the predefined minimum + record */ + if (UNIV_UNLIKELY(rec_get_info_bits(rec1, comp) + & REC_INFO_MIN_REC_FLAG)) { + + if (!(rec_get_info_bits(rec2, comp) + & REC_INFO_MIN_REC_FLAG)) { + ret = -1; + } + + goto order_resolved; + + } else if (UNIV_UNLIKELY + (rec_get_info_bits(rec2, comp) + & REC_INFO_MIN_REC_FLAG)) { + + ret = 1; + + goto order_resolved; + } + } + + if (rec_offs_nth_extern(offsets1, cur_field) + || rec_offs_nth_extern(offsets2, cur_field)) { + /* We do not compare to an externally + stored field */ + + goto order_resolved; + } + + if (rec1_f_len == UNIV_SQL_NULL + || rec2_f_len == UNIV_SQL_NULL) { + + if (rec1_f_len == rec2_f_len) { + + if (stats_method == SRV_STATS_METHOD_NULLS_EQUAL) { + goto next_field; + } else { + ret = -1; + } + + } else if (rec2_f_len == UNIV_SQL_NULL) { + + /* We define the SQL null to be the + smallest possible value of a field + in the alphabetical order */ + + ret = 1; + } else { + ret = -1; + } + + goto order_resolved; + } + } + + if (mtype >= DATA_FLOAT + || (mtype == DATA_BLOB + && 0 == (prtype & DATA_BINARY_TYPE) + && dtype_get_charset_coll(prtype) + != DATA_MYSQL_LATIN1_SWEDISH_CHARSET_COLL)) { + + ret = cmp_whole_field(mtype, prtype, + rec1_b_ptr, + (unsigned) rec1_f_len, + rec2_b_ptr, + (unsigned) rec2_f_len); + if (ret != 0) { + cur_bytes = 0; + + goto order_resolved; + } else { + goto next_field; + } + } + + /* Set the pointers at the current byte */ + rec1_b_ptr = rec1_b_ptr + cur_bytes; + rec2_b_ptr = rec2_b_ptr + cur_bytes; + + /* Compare then the fields */ + for (;;) { + if (rec2_f_len <= cur_bytes) { + + if (rec1_f_len <= cur_bytes) { + + goto next_field; + } + + rec2_byte = dtype_get_pad_char(mtype, prtype); + + if (rec2_byte == ULINT_UNDEFINED) { + ret = 1; + + goto order_resolved; + } + } else { + rec2_byte = *rec2_b_ptr; + } + + if (rec1_f_len <= cur_bytes) { + rec1_byte = dtype_get_pad_char(mtype, prtype); + + if (rec1_byte == ULINT_UNDEFINED) { + ret = -1; + + goto order_resolved; + } + } else { + rec1_byte = *rec1_b_ptr; + } + + if (rec1_byte == rec2_byte) { + /* If the bytes are equal, they will remain + such even after the collation transformation + below */ + + goto next_byte; + } + + if (mtype <= DATA_CHAR + || (mtype == DATA_BLOB + && !(prtype & DATA_BINARY_TYPE))) { + + rec1_byte = cmp_collate(rec1_byte); + rec2_byte = cmp_collate(rec2_byte); + } + + if (rec1_byte < rec2_byte) { + ret = -1; + goto order_resolved; + } else if (rec1_byte > rec2_byte) { + ret = 1; + goto order_resolved; + } +next_byte: + /* Next byte */ + + cur_bytes++; + rec1_b_ptr++; + rec2_b_ptr++; + } + +next_field: + cur_field++; + cur_bytes = 0; + } + + ut_ad(cur_bytes == 0); + + /* If we ran out of fields, rec1 was equal to rec2 up + to the common fields */ + ut_ad(ret == 0); +order_resolved: + + ut_ad((ret >= - 1) && (ret <= 1)); + + *matched_fields = cur_field; + *matched_bytes = cur_bytes; + + return(ret); +#else /* !UNIV_HOTBACKUP */ + /* This function depends on MySQL code that is not included in + InnoDB Hot Backup builds. Besides, this function should never + be called in InnoDB Hot Backup. */ + ut_error; + return(0); +#endif /* !UNIV_HOTBACKUP */ +} + +#ifdef UNIV_DEBUG +/***************************************************************** +Used in debug checking of cmp_dtuple_... . +This function is used to compare a data tuple to a physical record. If +dtuple has n fields then rec must have either m >= n fields, or it must +differ from dtuple in some of the m fields rec has. If encounters an +externally stored field, returns 0. */ +static +int +cmp_debug_dtuple_rec_with_match( +/*============================*/ + /* out: 1, 0, -1, if dtuple is greater, equal, + less than rec, respectively, when only the + common first fields are compared */ + const dtuple_t* dtuple, /* in: data tuple */ + const rec_t* rec, /* in: physical record which differs from + dtuple in some of the common fields, or which + has an equal number or more fields than + dtuple */ + const ulint* offsets,/* in: array returned by rec_get_offsets() */ + ulint* matched_fields) /* in/out: number of already + completely matched fields; when function + returns, contains the value for current + comparison */ +{ + const dfield_t* dtuple_field; /* current field in logical record */ + ulint dtuple_f_len; /* the length of the current field + in the logical record */ + const byte* dtuple_f_data; /* pointer to the current logical + field data */ + ulint rec_f_len; /* length of current field in rec */ + const byte* rec_f_data; /* pointer to the current rec field */ + int ret = 3333; /* return value */ + ulint cur_field; /* current field number */ + + ut_ad(dtuple && rec && matched_fields); + ut_ad(dtuple_check_typed(dtuple)); + ut_ad(rec_offs_validate(rec, NULL, offsets)); + + ut_ad(*matched_fields <= dtuple_get_n_fields_cmp(dtuple)); + ut_ad(*matched_fields <= rec_offs_n_fields(offsets)); + + cur_field = *matched_fields; + + if (cur_field == 0) { + if (UNIV_UNLIKELY + (rec_get_info_bits(rec, rec_offs_comp(offsets)) + & REC_INFO_MIN_REC_FLAG)) { + + ret = !(dtuple_get_info_bits(dtuple) + & REC_INFO_MIN_REC_FLAG); + + goto order_resolved; + } + + if (UNIV_UNLIKELY + (dtuple_get_info_bits(dtuple) & REC_INFO_MIN_REC_FLAG)) { + ret = -1; + + goto order_resolved; + } + } + + /* Match fields in a loop; stop if we run out of fields in dtuple */ + + while (cur_field < dtuple_get_n_fields_cmp(dtuple)) { + + ulint mtype; + ulint prtype; + + dtuple_field = dtuple_get_nth_field(dtuple, cur_field); + { + const dtype_t* type + = dfield_get_type(dtuple_field); + + mtype = type->mtype; + prtype = type->prtype; + } + + dtuple_f_data = dfield_get_data(dtuple_field); + dtuple_f_len = dfield_get_len(dtuple_field); + + rec_f_data = rec_get_nth_field(rec, offsets, + cur_field, &rec_f_len); + + if (rec_offs_nth_extern(offsets, cur_field)) { + /* We do not compare to an externally stored field */ + + ret = 0; + + goto order_resolved; + } + + ret = cmp_data_data(mtype, prtype, dtuple_f_data, dtuple_f_len, + rec_f_data, rec_f_len); + if (ret != 0) { + goto order_resolved; + } + + cur_field++; + } + + ret = 0; /* If we ran out of fields, dtuple was equal to rec + up to the common fields */ +order_resolved: + ut_ad((ret >= - 1) && (ret <= 1)); + + *matched_fields = cur_field; + + return(ret); +} +#endif /* UNIV_DEBUG */ diff --git a/storage/xtradb/rem/rem0rec.c b/storage/xtradb/rem/rem0rec.c new file mode 100644 index 00000000000..d6899c810e3 --- /dev/null +++ b/storage/xtradb/rem/rem0rec.c @@ -0,0 +1,1720 @@ +/***************************************************************************** + +Copyright (c) 1994, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/************************************************************************ +Record manager + +Created 5/30/1994 Heikki Tuuri +*************************************************************************/ + +#include "rem0rec.h" + +#ifdef UNIV_NONINL +#include "rem0rec.ic" +#endif + +#include "mtr0mtr.h" +#include "mtr0log.h" + +/* PHYSICAL RECORD (OLD STYLE) + =========================== + +The physical record, which is the data type of all the records +found in index pages of the database, has the following format +(lower addresses and more significant bits inside a byte are below +represented on a higher text line): + +| offset of the end of the last field of data, the most significant + bit is set to 1 if and only if the field is SQL-null, + if the offset is 2-byte, then the second most significant + bit is set to 1 if the field is stored on another page: + mostly this will occur in the case of big BLOB fields | +... +| offset of the end of the first field of data + the SQL-null bit | +| 4 bits used to delete mark a record, and mark a predefined + minimum record in alphabetical order | +| 4 bits giving the number of records owned by this record + (this term is explained in page0page.h) | +| 13 bits giving the order number of this record in the + heap of the index page | +| 10 bits giving the number of fields in this record | +| 1 bit which is set to 1 if the offsets above are given in + one byte format, 0 if in two byte format | +| two bytes giving an absolute pointer to the next record in the page | +ORIGIN of the record +| first field of data | +... +| last field of data | + +The origin of the record is the start address of the first field +of data. The offsets are given relative to the origin. +The offsets of the data fields are stored in an inverted +order because then the offset of the first fields are near the +origin, giving maybe a better processor cache hit rate in searches. + +The offsets of the data fields are given as one-byte +(if there are less than 127 bytes of data in the record) +or two-byte unsigned integers. The most significant bit +is not part of the offset, instead it indicates the SQL-null +if the bit is set to 1. */ + +/* PHYSICAL RECORD (NEW STYLE) + =========================== + +The physical record, which is the data type of all the records +found in index pages of the database, has the following format +(lower addresses and more significant bits inside a byte are below +represented on a higher text line): + +| length of the last non-null variable-length field of data: + if the maximum length is 255, one byte; otherwise, + 0xxxxxxx (one byte, length=0..127), or 1exxxxxxxxxxxxxx (two bytes, + length=128..16383, extern storage flag) | +... +| length of first variable-length field of data | +| SQL-null flags (1 bit per nullable field), padded to full bytes | +| 4 bits used to delete mark a record, and mark a predefined + minimum record in alphabetical order | +| 4 bits giving the number of records owned by this record + (this term is explained in page0page.h) | +| 13 bits giving the order number of this record in the + heap of the index page | +| 3 bits record type: 000=conventional, 001=node pointer (inside B-tree), + 010=infimum, 011=supremum, 1xx=reserved | +| two bytes giving a relative pointer to the next record in the page | +ORIGIN of the record +| first field of data | +... +| last field of data | + +The origin of the record is the start address of the first field +of data. The offsets are given relative to the origin. +The offsets of the data fields are stored in an inverted +order because then the offset of the first fields are near the +origin, giving maybe a better processor cache hit rate in searches. + +The offsets of the data fields are given as one-byte +(if there are less than 127 bytes of data in the record) +or two-byte unsigned integers. The most significant bit +is not part of the offset, instead it indicates the SQL-null +if the bit is set to 1. */ + +/* CANONICAL COORDINATES. A record can be seen as a single +string of 'characters' in the following way: catenate the bytes +in each field, in the order of fields. An SQL-null field +is taken to be an empty sequence of bytes. Then after +the position of each field insert in the string +the 'character' <FIELD-END>, except that after an SQL-null field +insert <NULL-FIELD-END>. Now the ordinal position of each +byte in this canonical string is its canonical coordinate. +So, for the record ("AA", SQL-NULL, "BB", ""), the canonical +string is "AA<FIELD_END><NULL-FIELD-END>BB<FIELD-END><FIELD-END>". +We identify prefixes (= initial segments) of a record +with prefixes of the canonical string. The canonical +length of the prefix is the length of the corresponding +prefix of the canonical string. The canonical length of +a record is the length of its canonical string. + +For example, the maximal common prefix of records +("AA", SQL-NULL, "BB", "C") and ("AA", SQL-NULL, "B", "C") +is "AA<FIELD-END><NULL-FIELD-END>B", and its canonical +length is 5. + +A complete-field prefix of a record is a prefix which ends at the +end of some field (containing also <FIELD-END>). +A record is a complete-field prefix of another record, if +the corresponding canonical strings have the same property. */ + +/* this is used to fool compiler in rec_validate */ +UNIV_INTERN ulint rec_dummy; + +/******************************************************************* +Validates the consistency of an old-style physical record. */ +static +ibool +rec_validate_old( +/*=============*/ + /* out: TRUE if ok */ + const rec_t* rec); /* in: physical record */ + +/********************************************************** +Determine how many of the first n columns in a compact +physical record are stored externally. */ +UNIV_INTERN +ulint +rec_get_n_extern_new( +/*=================*/ + /* out: number of externally stored columns */ + const rec_t* rec, /* in: compact physical record */ + dict_index_t* index, /* in: record descriptor */ + ulint n) /* in: number of columns to scan */ +{ + const byte* nulls; + const byte* lens; + dict_field_t* field; + ulint null_mask; + ulint n_extern; + ulint i; + + ut_ad(dict_table_is_comp(index->table)); + ut_ad(rec_get_status(rec) == REC_STATUS_ORDINARY); + ut_ad(n == ULINT_UNDEFINED || n <= dict_index_get_n_fields(index)); + + if (n == ULINT_UNDEFINED) { + n = dict_index_get_n_fields(index); + } + + nulls = rec - (REC_N_NEW_EXTRA_BYTES + 1); + lens = nulls - UT_BITS_IN_BYTES(index->n_nullable); + null_mask = 1; + n_extern = 0; + i = 0; + + /* read the lengths of fields 0..n */ + do { + ulint len; + + field = dict_index_get_nth_field(index, i); + if (!(dict_field_get_col(field)->prtype & DATA_NOT_NULL)) { + /* nullable field => read the null flag */ + + if (UNIV_UNLIKELY(!(byte) null_mask)) { + nulls--; + null_mask = 1; + } + + if (*nulls & null_mask) { + null_mask <<= 1; + /* No length is stored for NULL fields. */ + continue; + } + null_mask <<= 1; + } + + if (UNIV_UNLIKELY(!field->fixed_len)) { + /* Variable-length field: read the length */ + const dict_col_t* col + = dict_field_get_col(field); + len = *lens--; + if (UNIV_UNLIKELY(col->len > 255) + || UNIV_UNLIKELY(col->mtype == DATA_BLOB)) { + if (len & 0x80) { + /* 1exxxxxxx xxxxxxxx */ + if (len & 0x40) { + n_extern++; + } + lens--; + } + } + } + } while (++i < n); + + return(n_extern); +} + +/********************************************************** +Determine the offset to each field in a leaf-page record +in ROW_FORMAT=COMPACT. This is a special case of +rec_init_offsets() and rec_get_offsets_func(). */ +UNIV_INTERN +void +rec_init_offsets_comp_ordinary( +/*===========================*/ + const rec_t* rec, /* in: physical record in + ROW_FORMAT=COMPACT */ + ulint extra, /* in: number of bytes to reserve + between the record header and + the data payload + (usually REC_N_NEW_EXTRA_BYTES) */ + const dict_index_t* index, /* in: record descriptor */ + ulint* offsets)/* in/out: array of offsets; + in: n=rec_offs_n_fields(offsets) */ +{ + ulint i = 0; + ulint offs = 0; + ulint any_ext = 0; + const byte* nulls = rec - (extra + 1); + const byte* lens = nulls + - UT_BITS_IN_BYTES(index->n_nullable); + dict_field_t* field; + ulint null_mask = 1; + +#ifdef UNIV_DEBUG + /* We cannot invoke rec_offs_make_valid() here, because it can hold + that extra != REC_N_NEW_EXTRA_BYTES. Similarly, rec_offs_validate() + will fail in that case, because it invokes rec_get_status(). */ + offsets[2] = (ulint) rec; + offsets[3] = (ulint) index; +#endif /* UNIV_DEBUG */ + + /* read the lengths of fields 0..n */ + do { + ulint len; + + field = dict_index_get_nth_field(index, i); + if (!(dict_field_get_col(field)->prtype + & DATA_NOT_NULL)) { + /* nullable field => read the null flag */ + + if (UNIV_UNLIKELY(!(byte) null_mask)) { + nulls--; + null_mask = 1; + } + + if (*nulls & null_mask) { + null_mask <<= 1; + /* No length is stored for NULL fields. + We do not advance offs, and we set + the length to zero and enable the + SQL NULL flag in offsets[]. */ + len = offs | REC_OFFS_SQL_NULL; + goto resolved; + } + null_mask <<= 1; + } + + if (UNIV_UNLIKELY(!field->fixed_len)) { + /* Variable-length field: read the length */ + const dict_col_t* col + = dict_field_get_col(field); + len = *lens--; + if (UNIV_UNLIKELY(col->len > 255) + || UNIV_UNLIKELY(col->mtype + == DATA_BLOB)) { + if (len & 0x80) { + /* 1exxxxxxx xxxxxxxx */ + len <<= 8; + len |= *lens--; + + offs += len & 0x3fff; + if (UNIV_UNLIKELY(len + & 0x4000)) { + ut_ad(dict_index_is_clust + (index)); + any_ext = REC_OFFS_EXTERNAL; + len = offs + | REC_OFFS_EXTERNAL; + } else { + len = offs; + } + + goto resolved; + } + } + + len = offs += len; + } else { + len = offs += field->fixed_len; + } +resolved: + rec_offs_base(offsets)[i + 1] = len; + } while (++i < rec_offs_n_fields(offsets)); + + *rec_offs_base(offsets) + = (rec - (lens + 1)) | REC_OFFS_COMPACT | any_ext; +} + +/********************************************************** +The following function determines the offsets to each field in the +record. The offsets are written to a previously allocated array of +ulint, where rec_offs_n_fields(offsets) has been initialized to the +number of fields in the record. The rest of the array will be +initialized by this function. rec_offs_base(offsets)[0] will be set +to the extra size (if REC_OFFS_COMPACT is set, the record is in the +new format; if REC_OFFS_EXTERNAL is set, the record contains externally +stored columns), and rec_offs_base(offsets)[1..n_fields] will be set to +offsets past the end of fields 0..n_fields, or to the beginning of +fields 1..n_fields+1. When the high-order bit of the offset at [i+1] +is set (REC_OFFS_SQL_NULL), the field i is NULL. When the second +high-order bit of the offset at [i+1] is set (REC_OFFS_EXTERNAL), the +field i is being stored externally. */ +static +void +rec_init_offsets( +/*=============*/ + const rec_t* rec, /* in: physical record */ + const dict_index_t* index, /* in: record descriptor */ + ulint* offsets)/* in/out: array of offsets; + in: n=rec_offs_n_fields(offsets) */ +{ + ulint i = 0; + ulint offs; + + rec_offs_make_valid(rec, index, offsets); + + if (dict_table_is_comp(index->table)) { + const byte* nulls; + const byte* lens; + dict_field_t* field; + ulint null_mask; + ulint status = rec_get_status(rec); + ulint n_node_ptr_field = ULINT_UNDEFINED; + + switch (UNIV_EXPECT(status, REC_STATUS_ORDINARY)) { + case REC_STATUS_INFIMUM: + case REC_STATUS_SUPREMUM: + /* the field is 8 bytes long */ + rec_offs_base(offsets)[0] + = REC_N_NEW_EXTRA_BYTES | REC_OFFS_COMPACT; + rec_offs_base(offsets)[1] = 8; + return; + case REC_STATUS_NODE_PTR: + n_node_ptr_field + = dict_index_get_n_unique_in_tree(index); + break; + case REC_STATUS_ORDINARY: + rec_init_offsets_comp_ordinary(rec, + REC_N_NEW_EXTRA_BYTES, + index, offsets); + return; + } + + nulls = rec - (REC_N_NEW_EXTRA_BYTES + 1); + lens = nulls - UT_BITS_IN_BYTES(index->n_nullable); + offs = 0; + null_mask = 1; + + /* read the lengths of fields 0..n */ + do { + ulint len; + if (UNIV_UNLIKELY(i == n_node_ptr_field)) { + len = offs += 4; + goto resolved; + } + + field = dict_index_get_nth_field(index, i); + if (!(dict_field_get_col(field)->prtype + & DATA_NOT_NULL)) { + /* nullable field => read the null flag */ + + if (UNIV_UNLIKELY(!(byte) null_mask)) { + nulls--; + null_mask = 1; + } + + if (*nulls & null_mask) { + null_mask <<= 1; + /* No length is stored for NULL fields. + We do not advance offs, and we set + the length to zero and enable the + SQL NULL flag in offsets[]. */ + len = offs | REC_OFFS_SQL_NULL; + goto resolved; + } + null_mask <<= 1; + } + + if (UNIV_UNLIKELY(!field->fixed_len)) { + /* Variable-length field: read the length */ + const dict_col_t* col + = dict_field_get_col(field); + len = *lens--; + if (UNIV_UNLIKELY(col->len > 255) + || UNIV_UNLIKELY(col->mtype + == DATA_BLOB)) { + if (len & 0x80) { + /* 1exxxxxxx xxxxxxxx */ + + len <<= 8; + len |= *lens--; + + /* B-tree node pointers + must not contain externally + stored columns. Thus + the "e" flag must be 0. */ + ut_a(!(len & 0x4000)); + offs += len & 0x3fff; + len = offs; + + goto resolved; + } + } + + len = offs += len; + } else { + len = offs += field->fixed_len; + } +resolved: + rec_offs_base(offsets)[i + 1] = len; + } while (++i < rec_offs_n_fields(offsets)); + + *rec_offs_base(offsets) + = (rec - (lens + 1)) | REC_OFFS_COMPACT; + } else { + /* Old-style record: determine extra size and end offsets */ + offs = REC_N_OLD_EXTRA_BYTES; + if (rec_get_1byte_offs_flag(rec)) { + offs += rec_offs_n_fields(offsets); + *rec_offs_base(offsets) = offs; + /* Determine offsets to fields */ + do { + offs = rec_1_get_field_end_info(rec, i); + if (offs & REC_1BYTE_SQL_NULL_MASK) { + offs &= ~REC_1BYTE_SQL_NULL_MASK; + offs |= REC_OFFS_SQL_NULL; + } + rec_offs_base(offsets)[1 + i] = offs; + } while (++i < rec_offs_n_fields(offsets)); + } else { + offs += 2 * rec_offs_n_fields(offsets); + *rec_offs_base(offsets) = offs; + /* Determine offsets to fields */ + do { + offs = rec_2_get_field_end_info(rec, i); + if (offs & REC_2BYTE_SQL_NULL_MASK) { + offs &= ~REC_2BYTE_SQL_NULL_MASK; + offs |= REC_OFFS_SQL_NULL; + } + if (offs & REC_2BYTE_EXTERN_MASK) { + offs &= ~REC_2BYTE_EXTERN_MASK; + offs |= REC_OFFS_EXTERNAL; + *rec_offs_base(offsets) |= REC_OFFS_EXTERNAL; + } + rec_offs_base(offsets)[1 + i] = offs; + } while (++i < rec_offs_n_fields(offsets)); + } + } +} + +/********************************************************** +The following function determines the offsets to each field +in the record. It can reuse a previously returned array. */ +UNIV_INTERN +ulint* +rec_get_offsets_func( +/*=================*/ + /* out: the new offsets */ + const rec_t* rec, /* in: physical record */ + const dict_index_t* index, /* in: record descriptor */ + ulint* offsets,/* in/out: array consisting of + offsets[0] allocated elements, + or an array from rec_get_offsets(), + or NULL */ + ulint n_fields,/* in: maximum number of + initialized fields + (ULINT_UNDEFINED if all fields) */ + mem_heap_t** heap, /* in/out: memory heap */ + const char* file, /* in: file name where called */ + ulint line) /* in: line number where called */ +{ + ulint n; + ulint size; + + ut_ad(rec); + ut_ad(index); + ut_ad(heap); + + if (dict_table_is_comp(index->table)) { + switch (UNIV_EXPECT(rec_get_status(rec), + REC_STATUS_ORDINARY)) { + case REC_STATUS_ORDINARY: + n = dict_index_get_n_fields(index); + break; + case REC_STATUS_NODE_PTR: + n = dict_index_get_n_unique_in_tree(index) + 1; + break; + case REC_STATUS_INFIMUM: + case REC_STATUS_SUPREMUM: + /* infimum or supremum record */ + n = 1; + break; + default: + ut_error; + return(NULL); + } + } else { + n = rec_get_n_fields_old(rec); + } + + if (UNIV_UNLIKELY(n_fields < n)) { + n = n_fields; + } + + size = n + (1 + REC_OFFS_HEADER_SIZE); + + if (UNIV_UNLIKELY(!offsets) + || UNIV_UNLIKELY(rec_offs_get_n_alloc(offsets) < size)) { + if (UNIV_UNLIKELY(!*heap)) { + *heap = mem_heap_create_func(size * sizeof(ulint), + MEM_HEAP_DYNAMIC, + file, line); + } + offsets = mem_heap_alloc(*heap, size * sizeof(ulint)); + rec_offs_set_n_alloc(offsets, size); + } + + rec_offs_set_n_fields(offsets, n); + rec_init_offsets(rec, index, offsets); + return(offsets); +} + +/********************************************************** +The following function determines the offsets to each field +in the record. It can reuse a previously allocated array. */ +UNIV_INTERN +void +rec_get_offsets_reverse( +/*====================*/ + const byte* extra, /* in: the extra bytes of a + compact record in reverse order, + excluding the fixed-size + REC_N_NEW_EXTRA_BYTES */ + const dict_index_t* index, /* in: record descriptor */ + ulint node_ptr,/* in: nonzero=node pointer, + 0=leaf node */ + ulint* offsets)/* in/out: array consisting of + offsets[0] allocated elements */ +{ + ulint n; + ulint i; + ulint offs; + ulint any_ext; + const byte* nulls; + const byte* lens; + dict_field_t* field; + ulint null_mask; + ulint n_node_ptr_field; + + ut_ad(extra); + ut_ad(index); + ut_ad(offsets); + ut_ad(dict_table_is_comp(index->table)); + + if (UNIV_UNLIKELY(node_ptr)) { + n_node_ptr_field = dict_index_get_n_unique_in_tree(index); + n = n_node_ptr_field + 1; + } else { + n_node_ptr_field = ULINT_UNDEFINED; + n = dict_index_get_n_fields(index); + } + + ut_a(rec_offs_get_n_alloc(offsets) >= n + (1 + REC_OFFS_HEADER_SIZE)); + rec_offs_set_n_fields(offsets, n); + + nulls = extra; + lens = nulls + UT_BITS_IN_BYTES(index->n_nullable); + i = offs = 0; + null_mask = 1; + any_ext = 0; + + /* read the lengths of fields 0..n */ + do { + ulint len; + if (UNIV_UNLIKELY(i == n_node_ptr_field)) { + len = offs += 4; + goto resolved; + } + + field = dict_index_get_nth_field(index, i); + if (!(dict_field_get_col(field)->prtype & DATA_NOT_NULL)) { + /* nullable field => read the null flag */ + + if (UNIV_UNLIKELY(!(byte) null_mask)) { + nulls++; + null_mask = 1; + } + + if (*nulls & null_mask) { + null_mask <<= 1; + /* No length is stored for NULL fields. + We do not advance offs, and we set + the length to zero and enable the + SQL NULL flag in offsets[]. */ + len = offs | REC_OFFS_SQL_NULL; + goto resolved; + } + null_mask <<= 1; + } + + if (UNIV_UNLIKELY(!field->fixed_len)) { + /* Variable-length field: read the length */ + const dict_col_t* col + = dict_field_get_col(field); + len = *lens++; + if (UNIV_UNLIKELY(col->len > 255) + || UNIV_UNLIKELY(col->mtype == DATA_BLOB)) { + if (len & 0x80) { + /* 1exxxxxxx xxxxxxxx */ + len <<= 8; + len |= *lens++; + + offs += len & 0x3fff; + if (UNIV_UNLIKELY(len & 0x4000)) { + any_ext = REC_OFFS_EXTERNAL; + len = offs | REC_OFFS_EXTERNAL; + } else { + len = offs; + } + + goto resolved; + } + } + + len = offs += len; + } else { + len = offs += field->fixed_len; + } +resolved: + rec_offs_base(offsets)[i + 1] = len; + } while (++i < rec_offs_n_fields(offsets)); + + ut_ad(lens >= extra); + *rec_offs_base(offsets) = (lens - extra + REC_N_NEW_EXTRA_BYTES) + | REC_OFFS_COMPACT | any_ext; +} + +/**************************************************************** +The following function is used to get the offset to the nth +data field in an old-style record. */ +UNIV_INTERN +ulint +rec_get_nth_field_offs_old( +/*=======================*/ + /* out: offset to the field */ + const rec_t* rec, /* in: record */ + ulint n, /* in: index of the field */ + ulint* len) /* out: length of the field; + UNIV_SQL_NULL if SQL null */ +{ + ulint os; + ulint next_os; + + ut_ad(rec && len); + ut_ad(n < rec_get_n_fields_old(rec)); + + if (UNIV_UNLIKELY(n > REC_MAX_N_FIELDS)) { + fprintf(stderr, "Error: trying to access field %lu in rec\n", + (ulong) n); + ut_error; + } + + if (UNIV_UNLIKELY(rec == NULL)) { + fputs("Error: rec is NULL pointer\n", stderr); + ut_error; + } + + if (rec_get_1byte_offs_flag(rec)) { + os = rec_1_get_field_start_offs(rec, n); + + next_os = rec_1_get_field_end_info(rec, n); + + if (next_os & REC_1BYTE_SQL_NULL_MASK) { + *len = UNIV_SQL_NULL; + + return(os); + } + + next_os = next_os & ~REC_1BYTE_SQL_NULL_MASK; + } else { + os = rec_2_get_field_start_offs(rec, n); + + next_os = rec_2_get_field_end_info(rec, n); + + if (next_os & REC_2BYTE_SQL_NULL_MASK) { + *len = UNIV_SQL_NULL; + + return(os); + } + + next_os = next_os & ~(REC_2BYTE_SQL_NULL_MASK + | REC_2BYTE_EXTERN_MASK); + } + + *len = next_os - os; + + ut_ad(*len < UNIV_PAGE_SIZE); + + return(os); +} + +/************************************************************** +Determines the size of a data tuple prefix in ROW_FORMAT=COMPACT. */ +UNIV_INTERN +ulint +rec_get_converted_size_comp_prefix( +/*===============================*/ + /* out: total size */ + const dict_index_t* index, /* in: record descriptor; + dict_table_is_comp() is + assumed to hold, even if + it does not */ + const dfield_t* fields, /* in: array of data fields */ + ulint n_fields,/* in: number of data fields */ + ulint* extra) /* out: extra size */ +{ + ulint extra_size; + ulint data_size; + ulint i; + ut_ad(index); + ut_ad(fields); + ut_ad(n_fields > 0); + ut_ad(n_fields <= dict_index_get_n_fields(index)); + + extra_size = REC_N_NEW_EXTRA_BYTES + + UT_BITS_IN_BYTES(index->n_nullable); + data_size = 0; + + /* read the lengths of fields 0..n */ + for (i = 0; i < n_fields; i++) { + const dict_field_t* field; + ulint len; + const dict_col_t* col; + + field = dict_index_get_nth_field(index, i); + len = dfield_get_len(&fields[i]); + col = dict_field_get_col(field); + + ut_ad(dict_col_type_assert_equal(col, + dfield_get_type(&fields[i]))); + + if (dfield_is_null(&fields[i])) { + /* No length is stored for NULL fields. */ + ut_ad(!(col->prtype & DATA_NOT_NULL)); + continue; + } + + ut_ad(len <= col->len || col->mtype == DATA_BLOB); + + if (field->fixed_len) { + ut_ad(len == field->fixed_len); + /* dict_index_add_col() should guarantee this */ + ut_ad(!field->prefix_len + || field->fixed_len == field->prefix_len); + } else if (dfield_is_ext(&fields[i])) { + extra_size += 2; + } else if (len < 128 + || (col->len < 256 && col->mtype != DATA_BLOB)) { + extra_size++; + } else { + /* For variable-length columns, we look up the + maximum length from the column itself. If this + is a prefix index column shorter than 256 bytes, + this will waste one byte. */ + extra_size += 2; + } + data_size += len; + } + + if (UNIV_LIKELY_NULL(extra)) { + *extra = extra_size; + } + + return(extra_size + data_size); +} + +/************************************************************** +Determines the size of a data tuple in ROW_FORMAT=COMPACT. */ +UNIV_INTERN +ulint +rec_get_converted_size_comp( +/*========================*/ + /* out: total size */ + const dict_index_t* index, /* in: record descriptor; + dict_table_is_comp() is + assumed to hold, even if + it does not */ + ulint status, /* in: status bits of the record */ + const dfield_t* fields, /* in: array of data fields */ + ulint n_fields,/* in: number of data fields */ + ulint* extra) /* out: extra size */ +{ + ulint size; + ut_ad(index); + ut_ad(fields); + ut_ad(n_fields > 0); + + switch (UNIV_EXPECT(status, REC_STATUS_ORDINARY)) { + case REC_STATUS_ORDINARY: + ut_ad(n_fields == dict_index_get_n_fields(index)); + size = 0; + break; + case REC_STATUS_NODE_PTR: + n_fields--; + ut_ad(n_fields == dict_index_get_n_unique_in_tree(index)); + ut_ad(dfield_get_len(&fields[n_fields]) == REC_NODE_PTR_SIZE); + size = REC_NODE_PTR_SIZE; /* child page number */ + break; + case REC_STATUS_INFIMUM: + case REC_STATUS_SUPREMUM: + /* infimum or supremum record, 8 data bytes */ + if (UNIV_LIKELY_NULL(extra)) { + *extra = REC_N_NEW_EXTRA_BYTES; + } + return(REC_N_NEW_EXTRA_BYTES + 8); + default: + ut_error; + return(ULINT_UNDEFINED); + } + + return(size + rec_get_converted_size_comp_prefix(index, fields, + n_fields, extra)); +} + +/*************************************************************** +Sets the value of the ith field SQL null bit of an old-style record. */ +UNIV_INTERN +void +rec_set_nth_field_null_bit( +/*=======================*/ + rec_t* rec, /* in: record */ + ulint i, /* in: ith field */ + ibool val) /* in: value to set */ +{ + ulint info; + + if (rec_get_1byte_offs_flag(rec)) { + + info = rec_1_get_field_end_info(rec, i); + + if (val) { + info = info | REC_1BYTE_SQL_NULL_MASK; + } else { + info = info & ~REC_1BYTE_SQL_NULL_MASK; + } + + rec_1_set_field_end_info(rec, i, info); + + return; + } + + info = rec_2_get_field_end_info(rec, i); + + if (val) { + info = info | REC_2BYTE_SQL_NULL_MASK; + } else { + info = info & ~REC_2BYTE_SQL_NULL_MASK; + } + + rec_2_set_field_end_info(rec, i, info); +} + +/*************************************************************** +Sets an old-style record field to SQL null. +The physical size of the field is not changed. */ +UNIV_INTERN +void +rec_set_nth_field_sql_null( +/*=======================*/ + rec_t* rec, /* in: record */ + ulint n) /* in: index of the field */ +{ + ulint offset; + + offset = rec_get_field_start_offs(rec, n); + + data_write_sql_null(rec + offset, rec_get_nth_field_size(rec, n)); + + rec_set_nth_field_null_bit(rec, n, TRUE); +} + +/************************************************************* +Builds an old-style physical record out of a data tuple and +stores it beginning from the start of the given buffer. */ +static +rec_t* +rec_convert_dtuple_to_rec_old( +/*==========================*/ + /* out: pointer to the origin of + physical record */ + byte* buf, /* in: start address of the physical record */ + const dtuple_t* dtuple, /* in: data tuple */ + ulint n_ext) /* in: number of externally stored columns */ +{ + const dfield_t* field; + ulint n_fields; + ulint data_size; + rec_t* rec; + ulint end_offset; + ulint ored_offset; + ulint len; + ulint i; + + ut_ad(buf && dtuple); + ut_ad(dtuple_validate(dtuple)); + ut_ad(dtuple_check_typed(dtuple)); + + n_fields = dtuple_get_n_fields(dtuple); + data_size = dtuple_get_data_size(dtuple); + + ut_ad(n_fields > 0); + + /* Calculate the offset of the origin in the physical record */ + + rec = buf + rec_get_converted_extra_size(data_size, n_fields, n_ext); +#ifdef UNIV_DEBUG + /* Suppress Valgrind warnings of ut_ad() + in mach_write_to_1(), mach_write_to_2() et al. */ + memset(buf, 0xff, rec - buf + data_size); +#endif /* UNIV_DEBUG */ + /* Store the number of fields */ + rec_set_n_fields_old(rec, n_fields); + + /* Set the info bits of the record */ + rec_set_info_bits_old(rec, dtuple_get_info_bits(dtuple) + & REC_INFO_BITS_MASK); + + /* Store the data and the offsets */ + + end_offset = 0; + + if (!n_ext && data_size <= REC_1BYTE_OFFS_LIMIT) { + + rec_set_1byte_offs_flag(rec, TRUE); + + for (i = 0; i < n_fields; i++) { + + field = dtuple_get_nth_field(dtuple, i); + + if (dfield_is_null(field)) { + len = dtype_get_sql_null_size( + dfield_get_type(field)); + data_write_sql_null(rec + end_offset, len); + + end_offset += len; + ored_offset = end_offset + | REC_1BYTE_SQL_NULL_MASK; + } else { + /* If the data is not SQL null, store it */ + len = dfield_get_len(field); + + memcpy(rec + end_offset, + dfield_get_data(field), len); + + end_offset += len; + ored_offset = end_offset; + } + + rec_1_set_field_end_info(rec, i, ored_offset); + } + } else { + rec_set_1byte_offs_flag(rec, FALSE); + + for (i = 0; i < n_fields; i++) { + + field = dtuple_get_nth_field(dtuple, i); + + if (dfield_is_null(field)) { + len = dtype_get_sql_null_size( + dfield_get_type(field)); + data_write_sql_null(rec + end_offset, len); + + end_offset += len; + ored_offset = end_offset + | REC_2BYTE_SQL_NULL_MASK; + } else { + /* If the data is not SQL null, store it */ + len = dfield_get_len(field); + + memcpy(rec + end_offset, + dfield_get_data(field), len); + + end_offset += len; + ored_offset = end_offset; + + if (dfield_is_ext(field)) { + ored_offset |= REC_2BYTE_EXTERN_MASK; + } + } + + rec_2_set_field_end_info(rec, i, ored_offset); + } + } + + return(rec); +} + +/************************************************************* +Builds a ROW_FORMAT=COMPACT record out of a data tuple. */ +UNIV_INTERN +void +rec_convert_dtuple_to_rec_comp( +/*===========================*/ + rec_t* rec, /* in: origin of record */ + ulint extra, /* in: number of bytes to + reserve between the record + header and the data payload + (normally REC_N_NEW_EXTRA_BYTES) */ + const dict_index_t* index, /* in: record descriptor */ + ulint status, /* in: status bits of the record */ + const dfield_t* fields, /* in: array of data fields */ + ulint n_fields)/* in: number of data fields */ +{ + const dfield_t* field; + const dtype_t* type; + byte* end; + byte* nulls; + byte* lens; + ulint len; + ulint i; + ulint n_node_ptr_field; + ulint fixed_len; + ulint null_mask = 1; + ut_ad(extra == 0 || dict_table_is_comp(index->table)); + ut_ad(extra == 0 || extra == REC_N_NEW_EXTRA_BYTES); + ut_ad(n_fields > 0); + + switch (UNIV_EXPECT(status, REC_STATUS_ORDINARY)) { + case REC_STATUS_ORDINARY: + ut_ad(n_fields <= dict_index_get_n_fields(index)); + n_node_ptr_field = ULINT_UNDEFINED; + break; + case REC_STATUS_NODE_PTR: + ut_ad(n_fields == dict_index_get_n_unique_in_tree(index) + 1); + n_node_ptr_field = n_fields - 1; + break; + case REC_STATUS_INFIMUM: + case REC_STATUS_SUPREMUM: + ut_ad(n_fields == 1); + n_node_ptr_field = ULINT_UNDEFINED; + break; + default: + ut_error; + return; + } + + end = rec; + nulls = rec - (extra + 1); + lens = nulls - UT_BITS_IN_BYTES(index->n_nullable); + /* clear the SQL-null flags */ + memset(lens + 1, 0, nulls - lens); + + /* Store the data and the offsets */ + + for (i = 0, field = fields; i < n_fields; i++, field++) { + type = dfield_get_type(field); + len = dfield_get_len(field); + + if (UNIV_UNLIKELY(i == n_node_ptr_field)) { + ut_ad(dtype_get_prtype(type) & DATA_NOT_NULL); + ut_ad(len == 4); + memcpy(end, dfield_get_data(field), len); + end += 4; + break; + } + + if (!(dtype_get_prtype(type) & DATA_NOT_NULL)) { + /* nullable field */ + ut_ad(index->n_nullable > 0); + + if (UNIV_UNLIKELY(!(byte) null_mask)) { + nulls--; + null_mask = 1; + } + + ut_ad(*nulls < null_mask); + + /* set the null flag if necessary */ + if (dfield_is_null(field)) { + *nulls |= null_mask; + null_mask <<= 1; + continue; + } + + null_mask <<= 1; + } + /* only nullable fields can be null */ + ut_ad(!dfield_is_null(field)); + + fixed_len = dict_index_get_nth_field(index, i)->fixed_len; + + if (fixed_len) { + ut_ad(len == fixed_len); + ut_ad(!dfield_is_ext(field)); + } else if (dfield_is_ext(field)) { + ut_ad(len <= REC_MAX_INDEX_COL_LEN + + BTR_EXTERN_FIELD_REF_SIZE); + *lens-- = (byte) (len >> 8) | 0xc0; + *lens-- = (byte) len; + } else { + ut_ad(len <= dtype_get_len(type) + || dtype_get_mtype(type) == DATA_BLOB); + if (len < 128 + || (dtype_get_len(type) < 256 + && dtype_get_mtype(type) != DATA_BLOB)) { + + *lens-- = (byte) len; + } else { + ut_ad(len < 16384); + *lens-- = (byte) (len >> 8) | 0x80; + *lens-- = (byte) len; + } + } + + memcpy(end, dfield_get_data(field), len); + end += len; + } +} + +/************************************************************* +Builds a new-style physical record out of a data tuple and +stores it beginning from the start of the given buffer. */ +static +rec_t* +rec_convert_dtuple_to_rec_new( +/*==========================*/ + /* out: pointer to the origin + of physical record */ + byte* buf, /* in: start address of + the physical record */ + const dict_index_t* index, /* in: record descriptor */ + const dtuple_t* dtuple) /* in: data tuple */ +{ + ulint extra_size; + ulint status; + rec_t* rec; + + status = dtuple_get_info_bits(dtuple) & REC_NEW_STATUS_MASK; + rec_get_converted_size_comp(index, status, + dtuple->fields, dtuple->n_fields, + &extra_size); + rec = buf + extra_size; + + rec_convert_dtuple_to_rec_comp( + rec, REC_N_NEW_EXTRA_BYTES, index, status, + dtuple->fields, dtuple->n_fields); + + /* Set the info bits of the record */ + rec_set_info_and_status_bits(rec, dtuple_get_info_bits(dtuple)); + + return(rec); +} + +/************************************************************* +Builds a physical record out of a data tuple and +stores it beginning from the start of the given buffer. */ +UNIV_INTERN +rec_t* +rec_convert_dtuple_to_rec( +/*======================*/ + /* out: pointer to the origin + of physical record */ + byte* buf, /* in: start address of the + physical record */ + const dict_index_t* index, /* in: record descriptor */ + const dtuple_t* dtuple, /* in: data tuple */ + ulint n_ext) /* in: number of + externally stored columns */ +{ + rec_t* rec; + + ut_ad(buf && index && dtuple); + ut_ad(dtuple_validate(dtuple)); + ut_ad(dtuple_check_typed(dtuple)); + + if (dict_table_is_comp(index->table)) { + rec = rec_convert_dtuple_to_rec_new(buf, index, dtuple); + } else { + rec = rec_convert_dtuple_to_rec_old(buf, dtuple, n_ext); + } + +#ifdef UNIV_DEBUG + { + mem_heap_t* heap = NULL; + ulint offsets_[REC_OFFS_NORMAL_SIZE]; + const ulint* offsets; + rec_offs_init(offsets_); + + offsets = rec_get_offsets(rec, index, + offsets_, ULINT_UNDEFINED, &heap); + ut_ad(rec_validate(rec, offsets)); + if (UNIV_LIKELY_NULL(heap)) { + mem_heap_free(heap); + } + } +#endif /* UNIV_DEBUG */ + return(rec); +} + +/****************************************************************** +Copies the first n fields of a physical record to a data tuple. The fields +are copied to the memory heap. */ +UNIV_INTERN +void +rec_copy_prefix_to_dtuple( +/*======================*/ + dtuple_t* tuple, /* out: data tuple */ + const rec_t* rec, /* in: physical record */ + const dict_index_t* index, /* in: record descriptor */ + ulint n_fields, /* in: number of fields + to copy */ + mem_heap_t* heap) /* in: memory heap */ +{ + ulint i; + ulint offsets_[REC_OFFS_NORMAL_SIZE]; + ulint* offsets = offsets_; + rec_offs_init(offsets_); + + offsets = rec_get_offsets(rec, index, offsets, n_fields, &heap); + + ut_ad(rec_validate(rec, offsets)); + ut_ad(dtuple_check_typed(tuple)); + + dtuple_set_info_bits(tuple, rec_get_info_bits( + rec, dict_table_is_comp(index->table))); + + for (i = 0; i < n_fields; i++) { + dfield_t* field; + const byte* data; + ulint len; + + field = dtuple_get_nth_field(tuple, i); + data = rec_get_nth_field(rec, offsets, i, &len); + + if (len != UNIV_SQL_NULL) { + dfield_set_data(field, + mem_heap_dup(heap, data, len), len); + ut_ad(!rec_offs_nth_extern(offsets, i)); + } else { + dfield_set_null(field); + } + } +} + +/****************************************************************** +Copies the first n fields of an old-style physical record +to a new physical record in a buffer. */ +static +rec_t* +rec_copy_prefix_to_buf_old( +/*=======================*/ + /* out, own: copied record */ + const rec_t* rec, /* in: physical record */ + ulint n_fields, /* in: number of fields to copy */ + ulint area_end, /* in: end of the prefix data */ + byte** buf, /* in/out: memory buffer for + the copied prefix, or NULL */ + ulint* buf_size) /* in/out: buffer size */ +{ + rec_t* copy_rec; + ulint area_start; + ulint prefix_len; + + if (rec_get_1byte_offs_flag(rec)) { + area_start = REC_N_OLD_EXTRA_BYTES + n_fields; + } else { + area_start = REC_N_OLD_EXTRA_BYTES + 2 * n_fields; + } + + prefix_len = area_start + area_end; + + if ((*buf == NULL) || (*buf_size < prefix_len)) { + if (*buf != NULL) { + mem_free(*buf); + } + + *buf = mem_alloc2(prefix_len, buf_size); + } + + ut_memcpy(*buf, rec - area_start, prefix_len); + + copy_rec = *buf + area_start; + + rec_set_n_fields_old(copy_rec, n_fields); + + return(copy_rec); +} + +/****************************************************************** +Copies the first n fields of a physical record to a new physical record in +a buffer. */ +UNIV_INTERN +rec_t* +rec_copy_prefix_to_buf( +/*===================*/ + /* out, own: copied record */ + const rec_t* rec, /* in: physical record */ + const dict_index_t* index, /* in: record descriptor */ + ulint n_fields, /* in: number of fields + to copy */ + byte** buf, /* in/out: memory buffer + for the copied prefix, + or NULL */ + ulint* buf_size) /* in/out: buffer size */ +{ + const byte* nulls; + const byte* lens; + ulint i; + ulint prefix_len; + ulint null_mask; + ulint status; + + UNIV_PREFETCH_RW(*buf); + + if (!dict_table_is_comp(index->table)) { + ut_ad(rec_validate_old(rec)); + return(rec_copy_prefix_to_buf_old( + rec, n_fields, + rec_get_field_start_offs(rec, n_fields), + buf, buf_size)); + } + + status = rec_get_status(rec); + + switch (status) { + case REC_STATUS_ORDINARY: + ut_ad(n_fields <= dict_index_get_n_fields(index)); + break; + case REC_STATUS_NODE_PTR: + /* it doesn't make sense to copy the child page number field */ + ut_ad(n_fields <= dict_index_get_n_unique_in_tree(index)); + break; + case REC_STATUS_INFIMUM: + case REC_STATUS_SUPREMUM: + /* infimum or supremum record: no sense to copy anything */ + default: + ut_error; + return(NULL); + } + + nulls = rec - (REC_N_NEW_EXTRA_BYTES + 1); + lens = nulls - UT_BITS_IN_BYTES(index->n_nullable); + UNIV_PREFETCH_R(lens); + prefix_len = 0; + null_mask = 1; + + /* read the lengths of fields 0..n */ + for (i = 0; i < n_fields; i++) { + const dict_field_t* field; + const dict_col_t* col; + + field = dict_index_get_nth_field(index, i); + col = dict_field_get_col(field); + + if (!(col->prtype & DATA_NOT_NULL)) { + /* nullable field => read the null flag */ + if (UNIV_UNLIKELY(!(byte) null_mask)) { + nulls--; + null_mask = 1; + } + + if (*nulls & null_mask) { + null_mask <<= 1; + continue; + } + + null_mask <<= 1; + } + + if (field->fixed_len) { + prefix_len += field->fixed_len; + } else { + ulint len = *lens--; + if (col->len > 255 || col->mtype == DATA_BLOB) { + if (len & 0x80) { + /* 1exxxxxx */ + len &= 0x3f; + len <<= 8; + len |= *lens--; + UNIV_PREFETCH_R(lens); + } + } + prefix_len += len; + } + } + + UNIV_PREFETCH_R(rec + prefix_len); + + prefix_len += rec - (lens + 1); + + if ((*buf == NULL) || (*buf_size < prefix_len)) { + if (*buf != NULL) { + mem_free(*buf); + } + + *buf = mem_alloc2(prefix_len, buf_size); + } + + memcpy(*buf, lens + 1, prefix_len); + + return(*buf + (rec - (lens + 1))); +} + +/******************************************************************* +Validates the consistency of an old-style physical record. */ +static +ibool +rec_validate_old( +/*=============*/ + /* out: TRUE if ok */ + const rec_t* rec) /* in: physical record */ +{ + const byte* data; + ulint len; + ulint n_fields; + ulint len_sum = 0; + ulint sum = 0; + ulint i; + + ut_a(rec); + n_fields = rec_get_n_fields_old(rec); + + if ((n_fields == 0) || (n_fields > REC_MAX_N_FIELDS)) { + fprintf(stderr, "InnoDB: Error: record has %lu fields\n", + (ulong) n_fields); + return(FALSE); + } + + for (i = 0; i < n_fields; i++) { + data = rec_get_nth_field_old(rec, i, &len); + + if (!((len < UNIV_PAGE_SIZE) || (len == UNIV_SQL_NULL))) { + fprintf(stderr, + "InnoDB: Error: record field %lu len %lu\n", + (ulong) i, + (ulong) len); + return(FALSE); + } + + if (len != UNIV_SQL_NULL) { + len_sum += len; + sum += *(data + len -1); /* dereference the + end of the field to + cause a memory trap + if possible */ + } else { + len_sum += rec_get_nth_field_size(rec, i); + } + } + + if (len_sum != rec_get_data_size_old(rec)) { + fprintf(stderr, + "InnoDB: Error: record len should be %lu, len %lu\n", + (ulong) len_sum, + rec_get_data_size_old(rec)); + return(FALSE); + } + + rec_dummy = sum; /* This is here only to fool the compiler */ + + return(TRUE); +} + +/******************************************************************* +Validates the consistency of a physical record. */ +UNIV_INTERN +ibool +rec_validate( +/*=========*/ + /* out: TRUE if ok */ + const rec_t* rec, /* in: physical record */ + const ulint* offsets)/* in: array returned by rec_get_offsets() */ +{ + const byte* data; + ulint len; + ulint n_fields; + ulint len_sum = 0; + ulint sum = 0; + ulint i; + + ut_a(rec); + n_fields = rec_offs_n_fields(offsets); + + if ((n_fields == 0) || (n_fields > REC_MAX_N_FIELDS)) { + fprintf(stderr, "InnoDB: Error: record has %lu fields\n", + (ulong) n_fields); + return(FALSE); + } + + ut_a(rec_offs_comp(offsets) || n_fields <= rec_get_n_fields_old(rec)); + + for (i = 0; i < n_fields; i++) { + data = rec_get_nth_field(rec, offsets, i, &len); + + if (!((len < UNIV_PAGE_SIZE) || (len == UNIV_SQL_NULL))) { + fprintf(stderr, + "InnoDB: Error: record field %lu len %lu\n", + (ulong) i, + (ulong) len); + return(FALSE); + } + + if (len != UNIV_SQL_NULL) { + len_sum += len; + sum += *(data + len -1); /* dereference the + end of the field to + cause a memory trap + if possible */ + } else if (!rec_offs_comp(offsets)) { + len_sum += rec_get_nth_field_size(rec, i); + } + } + + if (len_sum != rec_offs_data_size(offsets)) { + fprintf(stderr, + "InnoDB: Error: record len should be %lu, len %lu\n", + (ulong) len_sum, + (ulong) rec_offs_data_size(offsets)); + return(FALSE); + } + + rec_dummy = sum; /* This is here only to fool the compiler */ + + if (!rec_offs_comp(offsets)) { + ut_a(rec_validate_old(rec)); + } + + return(TRUE); +} + +/******************************************************************* +Prints an old-style physical record. */ +UNIV_INTERN +void +rec_print_old( +/*==========*/ + FILE* file, /* in: file where to print */ + const rec_t* rec) /* in: physical record */ +{ + const byte* data; + ulint len; + ulint n; + ulint i; + + ut_ad(rec); + + n = rec_get_n_fields_old(rec); + + fprintf(file, "PHYSICAL RECORD: n_fields %lu;" + " %u-byte offsets; info bits %lu\n", + (ulong) n, + rec_get_1byte_offs_flag(rec) ? 1 : 2, + (ulong) rec_get_info_bits(rec, FALSE)); + + for (i = 0; i < n; i++) { + + data = rec_get_nth_field_old(rec, i, &len); + + fprintf(file, " %lu:", (ulong) i); + + if (len != UNIV_SQL_NULL) { + if (len <= 30) { + + ut_print_buf(file, data, len); + } else { + ut_print_buf(file, data, 30); + + fprintf(file, " (total %lu bytes)", + (ulong) len); + } + } else { + fprintf(file, " SQL NULL, size %lu ", + rec_get_nth_field_size(rec, i)); + } + + putc(';', file); + putc('\n', file); + } + + rec_validate_old(rec); +} + +/******************************************************************* +Prints a physical record in ROW_FORMAT=COMPACT. Ignores the +record header. */ +UNIV_INTERN +void +rec_print_comp( +/*===========*/ + FILE* file, /* in: file where to print */ + const rec_t* rec, /* in: physical record */ + const ulint* offsets)/* in: array returned by rec_get_offsets() */ +{ + ulint i; + + for (i = 0; i < rec_offs_n_fields(offsets); i++) { + const byte* data; + ulint len; + + data = rec_get_nth_field(rec, offsets, i, &len); + + fprintf(file, " %lu:", (ulong) i); + + if (len != UNIV_SQL_NULL) { + if (len <= 30) { + + ut_print_buf(file, data, len); + } else { + ut_print_buf(file, data, 30); + + fprintf(file, " (total %lu bytes)", + (ulong) len); + } + } else { + fputs(" SQL NULL", file); + } + putc(';', file); + putc('\n', file); + } +} + +/******************************************************************* +Prints a physical record. */ +UNIV_INTERN +void +rec_print_new( +/*==========*/ + FILE* file, /* in: file where to print */ + const rec_t* rec, /* in: physical record */ + const ulint* offsets)/* in: array returned by rec_get_offsets() */ +{ + ut_ad(rec); + ut_ad(offsets); + ut_ad(rec_offs_validate(rec, NULL, offsets)); + + if (!rec_offs_comp(offsets)) { + rec_print_old(file, rec); + return; + } + + fprintf(file, "PHYSICAL RECORD: n_fields %lu;" + " compact format; info bits %lu\n", + (ulong) rec_offs_n_fields(offsets), + (ulong) rec_get_info_bits(rec, TRUE)); + + rec_print_comp(file, rec, offsets); + rec_validate(rec, offsets); +} + +/******************************************************************* +Prints a physical record. */ +UNIV_INTERN +void +rec_print( +/*======*/ + FILE* file, /* in: file where to print */ + const rec_t* rec, /* in: physical record */ + dict_index_t* index) /* in: record descriptor */ +{ + ut_ad(index); + + if (!dict_table_is_comp(index->table)) { + rec_print_old(file, rec); + return; + } else { + mem_heap_t* heap = NULL; + ulint offsets_[REC_OFFS_NORMAL_SIZE]; + rec_offs_init(offsets_); + + rec_print_new(file, rec, + rec_get_offsets(rec, index, offsets_, + ULINT_UNDEFINED, &heap)); + if (UNIV_LIKELY_NULL(heap)) { + mem_heap_free(heap); + } + } +} diff --git a/storage/xtradb/row/row0ext.c b/storage/xtradb/row/row0ext.c new file mode 100644 index 00000000000..83dfa024ffc --- /dev/null +++ b/storage/xtradb/row/row0ext.c @@ -0,0 +1,114 @@ +/***************************************************************************** + +Copyright (c) 2006, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/****************************************************** +Caching of externally stored column prefixes + +Created September 2006 Marko Makela +*******************************************************/ + +#include "row0ext.h" + +#ifdef UNIV_NONINL +#include "row0ext.ic" +#endif + +#include "btr0cur.h" + +/************************************************************************ +Fills the column prefix cache of an externally stored column. */ +static +void +row_ext_cache_fill( +/*===============*/ + row_ext_t* ext, /* in/out: column prefix cache */ + ulint i, /* in: index of ext->ext[] */ + ulint zip_size,/* compressed page size in bytes, or 0 */ + const dfield_t* dfield) /* in: data field */ +{ + const byte* field = dfield_get_data(dfield); + ulint f_len = dfield_get_len(dfield); + byte* buf = ext->buf + i * REC_MAX_INDEX_COL_LEN; + + ut_ad(i < ext->n_ext); + ut_ad(dfield_is_ext(dfield)); + ut_a(f_len >= BTR_EXTERN_FIELD_REF_SIZE); + + if (UNIV_UNLIKELY(!memcmp(field_ref_zero, + field + f_len - BTR_EXTERN_FIELD_REF_SIZE, + BTR_EXTERN_FIELD_REF_SIZE))) { + /* The BLOB pointer is not set: we cannot fetch it */ + ext->len[i] = 0; + } else { + /* Fetch at most REC_MAX_INDEX_COL_LEN of the column. + The column should be non-empty. However, + trx_rollback_or_clean_all_recovered() may try to + access a half-deleted BLOB if the server previously + crashed during the execution of + btr_free_externally_stored_field(). */ + ext->len[i] = btr_copy_externally_stored_field_prefix( + buf, REC_MAX_INDEX_COL_LEN, zip_size, field, f_len); + } +} + +/************************************************************************ +Creates a cache of column prefixes of externally stored columns. */ +UNIV_INTERN +row_ext_t* +row_ext_create( +/*===========*/ + /* out,own: column prefix cache */ + ulint n_ext, /* in: number of externally stored columns */ + const ulint* ext, /* in: col_no's of externally stored columns + in the InnoDB table object, as reported by + dict_col_get_no(); NOT relative to the records + in the clustered index */ + const dtuple_t* tuple, /* in: data tuple containing the field + references of the externally stored + columns; must be indexed by col_no; + the clustered index record must be + covered by a lock or a page latch + to prevent deletion (rollback or purge). */ + ulint zip_size,/* compressed page size in bytes, or 0 */ + mem_heap_t* heap) /* in: heap where created */ +{ + ulint i; + row_ext_t* ret = mem_heap_alloc(heap, (sizeof *ret) + + (n_ext - 1) * sizeof ret->len); + + ut_ad(ut_is_2pow(zip_size)); + ut_ad(zip_size <= UNIV_PAGE_SIZE); + + ret->n_ext = n_ext; + ret->ext = ext; + ret->buf = mem_heap_alloc(heap, n_ext * REC_MAX_INDEX_COL_LEN); +#ifdef UNIV_DEBUG + memset(ret->buf, 0xaa, n_ext * REC_MAX_INDEX_COL_LEN); + UNIV_MEM_ALLOC(ret->buf, n_ext * REC_MAX_INDEX_COL_LEN); +#endif + + /* Fetch the BLOB prefixes */ + for (i = 0; i < n_ext; i++) { + const dfield_t* dfield; + + dfield = dtuple_get_nth_field(tuple, ext[i]); + row_ext_cache_fill(ret, i, zip_size, dfield); + } + + return(ret); +} diff --git a/storage/xtradb/row/row0ins.c b/storage/xtradb/row/row0ins.c new file mode 100644 index 00000000000..ceb0f7d75a2 --- /dev/null +++ b/storage/xtradb/row/row0ins.c @@ -0,0 +1,2558 @@ +/***************************************************************************** + +Copyright (c) 1996, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/****************************************************** +Insert into a table + +Created 4/20/1996 Heikki Tuuri +*******************************************************/ + +#include "row0ins.h" + +#ifdef UNIV_NONINL +#include "row0ins.ic" +#endif + +#include "dict0dict.h" +#include "dict0boot.h" +#include "trx0undo.h" +#include "btr0btr.h" +#include "btr0cur.h" +#include "mach0data.h" +#include "que0que.h" +#include "row0upd.h" +#include "row0sel.h" +#include "row0row.h" +#include "rem0cmp.h" +#include "lock0lock.h" +#include "log0log.h" +#include "eval0eval.h" +#include "data0data.h" +#include "usr0sess.h" +#include "buf0lru.h" + +#define ROW_INS_PREV 1 +#define ROW_INS_NEXT 2 + + +/********************************************************************* +This prototype is copied from /mysql/sql/ha_innodb.cc. +Invalidates the MySQL query cache for the table. +NOTE that the exact prototype of this function has to be in +/xtradb/row/row0ins.c! */ +extern +void +innobase_invalidate_query_cache( +/*============================*/ + trx_t* trx, /* in: transaction which modifies the table */ + char* full_name, /* in: concatenation of database name, null + char '\0', table name, null char'\0'; + NOTE that in Windows this is always + in LOWER CASE! */ + ulint full_name_len); /* in: full name length where also the null + chars count */ + +/************************************************************************* +Creates an insert node struct. */ +UNIV_INTERN +ins_node_t* +ins_node_create( +/*============*/ + /* out, own: insert node struct */ + ulint ins_type, /* in: INS_VALUES, ... */ + dict_table_t* table, /* in: table where to insert */ + mem_heap_t* heap) /* in: mem heap where created */ +{ + ins_node_t* node; + + node = mem_heap_alloc(heap, sizeof(ins_node_t)); + + node->common.type = QUE_NODE_INSERT; + + node->ins_type = ins_type; + + node->state = INS_NODE_SET_IX_LOCK; + node->table = table; + node->index = NULL; + node->entry = NULL; + + node->select = NULL; + + node->trx_id = ut_dulint_zero; + + node->entry_sys_heap = mem_heap_create(128); + + node->magic_n = INS_NODE_MAGIC_N; + + return(node); +} + +/*************************************************************** +Creates an entry template for each index of a table. */ +UNIV_INTERN +void +ins_node_create_entry_list( +/*=======================*/ + ins_node_t* node) /* in: row insert node */ +{ + dict_index_t* index; + dtuple_t* entry; + + ut_ad(node->entry_sys_heap); + + UT_LIST_INIT(node->entry_list); + + index = dict_table_get_first_index(node->table); + + while (index != NULL) { + entry = row_build_index_entry(node->row, NULL, index, + node->entry_sys_heap); + UT_LIST_ADD_LAST(tuple_list, node->entry_list, entry); + + index = dict_table_get_next_index(index); + } +} + +/********************************************************************* +Adds system field buffers to a row. */ +static +void +row_ins_alloc_sys_fields( +/*=====================*/ + ins_node_t* node) /* in: insert node */ +{ + dtuple_t* row; + dict_table_t* table; + mem_heap_t* heap; + const dict_col_t* col; + dfield_t* dfield; + byte* ptr; + + row = node->row; + table = node->table; + heap = node->entry_sys_heap; + + ut_ad(row && table && heap); + ut_ad(dtuple_get_n_fields(row) == dict_table_get_n_cols(table)); + + /* 1. Allocate buffer for row id */ + + col = dict_table_get_sys_col(table, DATA_ROW_ID); + + dfield = dtuple_get_nth_field(row, dict_col_get_no(col)); + + ptr = mem_heap_alloc(heap, DATA_ROW_ID_LEN); + + dfield_set_data(dfield, ptr, DATA_ROW_ID_LEN); + + node->row_id_buf = ptr; + + /* 3. Allocate buffer for trx id */ + + col = dict_table_get_sys_col(table, DATA_TRX_ID); + + dfield = dtuple_get_nth_field(row, dict_col_get_no(col)); + ptr = mem_heap_alloc(heap, DATA_TRX_ID_LEN); + + dfield_set_data(dfield, ptr, DATA_TRX_ID_LEN); + + node->trx_id_buf = ptr; + + /* 4. Allocate buffer for roll ptr */ + + col = dict_table_get_sys_col(table, DATA_ROLL_PTR); + + dfield = dtuple_get_nth_field(row, dict_col_get_no(col)); + ptr = mem_heap_alloc(heap, DATA_ROLL_PTR_LEN); + + dfield_set_data(dfield, ptr, DATA_ROLL_PTR_LEN); +} + +/************************************************************************* +Sets a new row to insert for an INS_DIRECT node. This function is only used +if we have constructed the row separately, which is a rare case; this +function is quite slow. */ +UNIV_INTERN +void +ins_node_set_new_row( +/*=================*/ + ins_node_t* node, /* in: insert node */ + dtuple_t* row) /* in: new row (or first row) for the node */ +{ + node->state = INS_NODE_SET_IX_LOCK; + node->index = NULL; + node->entry = NULL; + + node->row = row; + + mem_heap_empty(node->entry_sys_heap); + + /* Create templates for index entries */ + + ins_node_create_entry_list(node); + + /* Allocate from entry_sys_heap buffers for sys fields */ + + row_ins_alloc_sys_fields(node); + + /* As we allocated a new trx id buf, the trx id should be written + there again: */ + + node->trx_id = ut_dulint_zero; +} + +/*********************************************************************** +Does an insert operation by updating a delete-marked existing record +in the index. This situation can occur if the delete-marked record is +kept in the index for consistent reads. */ +static +ulint +row_ins_sec_index_entry_by_modify( +/*==============================*/ + /* out: DB_SUCCESS or error code */ + ulint mode, /* in: BTR_MODIFY_LEAF or BTR_MODIFY_TREE, + depending on whether mtr holds just a leaf + latch or also a tree latch */ + btr_cur_t* cursor, /* in: B-tree cursor */ + const dtuple_t* entry, /* in: index entry to insert */ + que_thr_t* thr, /* in: query thread */ + mtr_t* mtr) /* in: mtr; must be committed before + latching any further pages */ +{ + big_rec_t* dummy_big_rec; + mem_heap_t* heap; + upd_t* update; + rec_t* rec; + ulint err; + + rec = btr_cur_get_rec(cursor); + + ut_ad(!dict_index_is_clust(cursor->index)); + ut_ad(rec_get_deleted_flag(rec, + dict_table_is_comp(cursor->index->table))); + + /* We know that in the alphabetical ordering, entry and rec are + identified. But in their binary form there may be differences if + there are char fields in them. Therefore we have to calculate the + difference. */ + + heap = mem_heap_create(1024); + + update = row_upd_build_sec_rec_difference_binary( + cursor->index, entry, rec, thr_get_trx(thr), heap); + if (mode == BTR_MODIFY_LEAF) { + /* Try an optimistic updating of the record, keeping changes + within the page */ + + err = btr_cur_optimistic_update(BTR_KEEP_SYS_FLAG, cursor, + update, 0, thr, mtr); + switch (err) { + case DB_OVERFLOW: + case DB_UNDERFLOW: + case DB_ZIP_OVERFLOW: + err = DB_FAIL; + } + } else { + ut_a(mode == BTR_MODIFY_TREE); + if (buf_LRU_buf_pool_running_out()) { + + err = DB_LOCK_TABLE_FULL; + + goto func_exit; + } + + err = btr_cur_pessimistic_update(BTR_KEEP_SYS_FLAG, cursor, + &heap, &dummy_big_rec, update, + 0, thr, mtr); + ut_ad(!dummy_big_rec); + } +func_exit: + mem_heap_free(heap); + + return(err); +} + +/*********************************************************************** +Does an insert operation by delete unmarking and updating a delete marked +existing record in the index. This situation can occur if the delete marked +record is kept in the index for consistent reads. */ +static +ulint +row_ins_clust_index_entry_by_modify( +/*================================*/ + /* out: DB_SUCCESS, DB_FAIL, or error code */ + ulint mode, /* in: BTR_MODIFY_LEAF or BTR_MODIFY_TREE, + depending on whether mtr holds just a leaf + latch or also a tree latch */ + btr_cur_t* cursor, /* in: B-tree cursor */ + mem_heap_t** heap, /* in/out: pointer to memory heap, or NULL */ + big_rec_t** big_rec,/* out: possible big rec vector of fields + which have to be stored externally by the + caller */ + const dtuple_t* entry, /* in: index entry to insert */ + que_thr_t* thr, /* in: query thread */ + mtr_t* mtr) /* in: mtr; must be committed before + latching any further pages */ +{ + rec_t* rec; + upd_t* update; + ulint err; + + ut_ad(dict_index_is_clust(cursor->index)); + + *big_rec = NULL; + + rec = btr_cur_get_rec(cursor); + + ut_ad(rec_get_deleted_flag(rec, + dict_table_is_comp(cursor->index->table))); + + if (!*heap) { + *heap = mem_heap_create(1024); + } + + /* Build an update vector containing all the fields to be modified; + NOTE that this vector may NOT contain system columns trx_id or + roll_ptr */ + + update = row_upd_build_difference_binary(cursor->index, entry, rec, + thr_get_trx(thr), *heap); + if (mode == BTR_MODIFY_LEAF) { + /* Try optimistic updating of the record, keeping changes + within the page */ + + err = btr_cur_optimistic_update(0, cursor, update, 0, thr, + mtr); + switch (err) { + case DB_OVERFLOW: + case DB_UNDERFLOW: + case DB_ZIP_OVERFLOW: + err = DB_FAIL; + } + } else { + ut_a(mode == BTR_MODIFY_TREE); + if (buf_LRU_buf_pool_running_out()) { + + return(DB_LOCK_TABLE_FULL); + + } + err = btr_cur_pessimistic_update(0, cursor, + heap, big_rec, update, + 0, thr, mtr); + } + + return(err); +} + +/************************************************************************* +Returns TRUE if in a cascaded update/delete an ancestor node of node +updates (not DELETE, but UPDATE) table. */ +static +ibool +row_ins_cascade_ancestor_updates_table( +/*===================================*/ + /* out: TRUE if an ancestor updates table */ + que_node_t* node, /* in: node in a query graph */ + dict_table_t* table) /* in: table */ +{ + que_node_t* parent; + upd_node_t* upd_node; + + parent = que_node_get_parent(node); + + while (que_node_get_type(parent) == QUE_NODE_UPDATE) { + + upd_node = parent; + + if (upd_node->table == table && upd_node->is_delete == FALSE) { + + return(TRUE); + } + + parent = que_node_get_parent(parent); + + ut_a(parent); + } + + return(FALSE); +} + +/************************************************************************* +Returns the number of ancestor UPDATE or DELETE nodes of a +cascaded update/delete node. */ +static +ulint +row_ins_cascade_n_ancestors( +/*========================*/ + /* out: number of ancestors */ + que_node_t* node) /* in: node in a query graph */ +{ + que_node_t* parent; + ulint n_ancestors = 0; + + parent = que_node_get_parent(node); + + while (que_node_get_type(parent) == QUE_NODE_UPDATE) { + n_ancestors++; + + parent = que_node_get_parent(parent); + + ut_a(parent); + } + + return(n_ancestors); +} + +/********************************************************************** +Calculates the update vector node->cascade->update for a child table in +a cascaded update. */ +static +ulint +row_ins_cascade_calc_update_vec( +/*============================*/ + /* out: number of fields in the + calculated update vector; the value + can also be 0 if no foreign key + fields changed; the returned value + is ULINT_UNDEFINED if the column + type in the child table is too short + to fit the new value in the parent + table: that means the update fails */ + upd_node_t* node, /* in: update node of the parent + table */ + dict_foreign_t* foreign, /* in: foreign key constraint whose + type is != 0 */ + mem_heap_t* heap) /* in: memory heap to use as + temporary storage */ +{ + upd_node_t* cascade = node->cascade_node; + dict_table_t* table = foreign->foreign_table; + dict_index_t* index = foreign->foreign_index; + upd_t* update; + upd_field_t* ufield; + dict_table_t* parent_table; + dict_index_t* parent_index; + upd_t* parent_update; + upd_field_t* parent_ufield; + ulint n_fields_updated; + ulint parent_field_no; + ulint i; + ulint j; + + ut_a(node); + ut_a(foreign); + ut_a(cascade); + ut_a(table); + ut_a(index); + + /* Calculate the appropriate update vector which will set the fields + in the child index record to the same value (possibly padded with + spaces if the column is a fixed length CHAR or FIXBINARY column) as + the referenced index record will get in the update. */ + + parent_table = node->table; + ut_a(parent_table == foreign->referenced_table); + parent_index = foreign->referenced_index; + parent_update = node->update; + + update = cascade->update; + + update->info_bits = 0; + update->n_fields = foreign->n_fields; + + n_fields_updated = 0; + + for (i = 0; i < foreign->n_fields; i++) { + + parent_field_no = dict_table_get_nth_col_pos( + parent_table, + dict_index_get_nth_col_no(parent_index, i)); + + for (j = 0; j < parent_update->n_fields; j++) { + parent_ufield = parent_update->fields + j; + + if (parent_ufield->field_no == parent_field_no) { + + ulint min_size; + const dict_col_t* col; + ulint ufield_len; + + col = dict_index_get_nth_col(index, i); + + /* A field in the parent index record is + updated. Let us make the update vector + field for the child table. */ + + ufield = update->fields + n_fields_updated; + + ufield->field_no + = dict_table_get_nth_col_pos( + table, dict_col_get_no(col)); + ufield->exp = NULL; + + ufield->new_val = parent_ufield->new_val; + ufield_len = dfield_get_len(&ufield->new_val); + + /* Clear the "external storage" flag */ + dfield_set_len(&ufield->new_val, ufield_len); + + /* Do not allow a NOT NULL column to be + updated as NULL */ + + if (dfield_is_null(&ufield->new_val) + && (col->prtype & DATA_NOT_NULL)) { + + return(ULINT_UNDEFINED); + } + + /* If the new value would not fit in the + column, do not allow the update */ + + if (!dfield_is_null(&ufield->new_val) + && dtype_get_at_most_n_mbchars( + col->prtype, + col->mbminlen, col->mbmaxlen, + col->len, + ufield_len, + dfield_get_data(&ufield->new_val)) + < ufield_len) { + + return(ULINT_UNDEFINED); + } + + /* If the parent column type has a different + length than the child column type, we may + need to pad with spaces the new value of the + child column */ + + min_size = dict_col_get_min_size(col); + + /* Because UNIV_SQL_NULL (the marker + of SQL NULL values) exceeds all possible + values of min_size, the test below will + not hold for SQL NULL columns. */ + + if (min_size > ufield_len) { + + char* pad_start; + const char* pad_end; + char* padded_data + = mem_heap_alloc( + heap, min_size); + pad_start = padded_data + ufield_len; + pad_end = padded_data + min_size; + + memcpy(padded_data, + dfield_get_data(&ufield + ->new_val), + dfield_get_len(&ufield + ->new_val)); + + switch (UNIV_EXPECT(col->mbminlen,1)) { + default: + ut_error; + return(ULINT_UNDEFINED); + case 1: + if (UNIV_UNLIKELY + (dtype_get_charset_coll( + col->prtype) + == DATA_MYSQL_BINARY_CHARSET_COLL)) { + /* Do not pad BINARY + columns. */ + return(ULINT_UNDEFINED); + } + + /* space=0x20 */ + memset(pad_start, 0x20, + pad_end - pad_start); + break; + case 2: + /* space=0x0020 */ + ut_a(!(ufield_len % 2)); + ut_a(!(min_size % 2)); + do { + *pad_start++ = 0x00; + *pad_start++ = 0x20; + } while (pad_start < pad_end); + break; + } + + dfield_set_data(&ufield->new_val, + padded_data, min_size); + } + + n_fields_updated++; + } + } + } + + update->n_fields = n_fields_updated; + + return(n_fields_updated); +} + +/************************************************************************* +Set detailed error message associated with foreign key errors for +the given transaction. */ +static +void +row_ins_set_detailed( +/*=================*/ + trx_t* trx, /* in: transaction */ + dict_foreign_t* foreign) /* in: foreign key constraint */ +{ + mutex_enter(&srv_misc_tmpfile_mutex); + rewind(srv_misc_tmpfile); + + if (os_file_set_eof(srv_misc_tmpfile)) { + ut_print_name(srv_misc_tmpfile, trx, TRUE, + foreign->foreign_table_name); + dict_print_info_on_foreign_key_in_create_format( + srv_misc_tmpfile, trx, foreign, FALSE); + trx_set_detailed_error_from_file(trx, srv_misc_tmpfile); + } else { + trx_set_detailed_error(trx, "temp file operation failed"); + } + + mutex_exit(&srv_misc_tmpfile_mutex); +} + +/************************************************************************* +Reports a foreign key error associated with an update or a delete of a +parent table index entry. */ +static +void +row_ins_foreign_report_err( +/*=======================*/ + const char* errstr, /* in: error string from the viewpoint + of the parent table */ + que_thr_t* thr, /* in: query thread whose run_node + is an update node */ + dict_foreign_t* foreign, /* in: foreign key constraint */ + const rec_t* rec, /* in: a matching index record in the + child table */ + const dtuple_t* entry) /* in: index entry in the parent + table */ +{ + FILE* ef = dict_foreign_err_file; + trx_t* trx = thr_get_trx(thr); + + row_ins_set_detailed(trx, foreign); + + mutex_enter(&dict_foreign_err_mutex); + rewind(ef); + ut_print_timestamp(ef); + fputs(" Transaction:\n", ef); + trx_print(ef, trx, 600); + + fputs("Foreign key constraint fails for table ", ef); + ut_print_name(ef, trx, TRUE, foreign->foreign_table_name); + fputs(":\n", ef); + dict_print_info_on_foreign_key_in_create_format(ef, trx, foreign, + TRUE); + putc('\n', ef); + fputs(errstr, ef); + fputs(" in parent table, in index ", ef); + ut_print_name(ef, trx, FALSE, foreign->referenced_index->name); + if (entry) { + fputs(" tuple:\n", ef); + dtuple_print(ef, entry); + } + fputs("\nBut in child table ", ef); + ut_print_name(ef, trx, TRUE, foreign->foreign_table_name); + fputs(", in index ", ef); + ut_print_name(ef, trx, FALSE, foreign->foreign_index->name); + if (rec) { + fputs(", there is a record:\n", ef); + rec_print(ef, rec, foreign->foreign_index); + } else { + fputs(", the record is not available\n", ef); + } + putc('\n', ef); + + mutex_exit(&dict_foreign_err_mutex); +} + +/************************************************************************* +Reports a foreign key error to dict_foreign_err_file when we are trying +to add an index entry to a child table. Note that the adding may be the result +of an update, too. */ +static +void +row_ins_foreign_report_add_err( +/*===========================*/ + trx_t* trx, /* in: transaction */ + dict_foreign_t* foreign, /* in: foreign key constraint */ + const rec_t* rec, /* in: a record in the parent table: + it does not match entry because we + have an error! */ + const dtuple_t* entry) /* in: index entry to insert in the + child table */ +{ + FILE* ef = dict_foreign_err_file; + + row_ins_set_detailed(trx, foreign); + + mutex_enter(&dict_foreign_err_mutex); + rewind(ef); + ut_print_timestamp(ef); + fputs(" Transaction:\n", ef); + trx_print(ef, trx, 600); + fputs("Foreign key constraint fails for table ", ef); + ut_print_name(ef, trx, TRUE, foreign->foreign_table_name); + fputs(":\n", ef); + dict_print_info_on_foreign_key_in_create_format(ef, trx, foreign, + TRUE); + fputs("\nTrying to add in child table, in index ", ef); + ut_print_name(ef, trx, FALSE, foreign->foreign_index->name); + if (entry) { + fputs(" tuple:\n", ef); + /* TODO: DB_TRX_ID and DB_ROLL_PTR may be uninitialized. + It would be better to only display the user columns. */ + dtuple_print(ef, entry); + } + fputs("\nBut in parent table ", ef); + ut_print_name(ef, trx, TRUE, foreign->referenced_table_name); + fputs(", in index ", ef); + ut_print_name(ef, trx, FALSE, foreign->referenced_index->name); + fputs(",\nthe closest match we can find is record:\n", ef); + if (rec && page_rec_is_supremum(rec)) { + /* If the cursor ended on a supremum record, it is better + to report the previous record in the error message, so that + the user gets a more descriptive error message. */ + rec = page_rec_get_prev_const(rec); + } + + if (rec) { + rec_print(ef, rec, foreign->referenced_index); + } + putc('\n', ef); + + mutex_exit(&dict_foreign_err_mutex); +} + +/************************************************************************* +Invalidate the query cache for the given table. */ +static +void +row_ins_invalidate_query_cache( +/*===========================*/ + que_thr_t* thr, /* in: query thread whose run_node + is an update node */ + const char* name) /* in: table name prefixed with + database name and a '/' character */ +{ + char* buf; + char* ptr; + ulint len = strlen(name) + 1; + + buf = mem_strdupl(name, len); + + ptr = strchr(buf, '/'); + ut_a(ptr); + *ptr = '\0'; + + /* We call a function in ha_innodb.cc */ +#ifndef UNIV_HOTBACKUP + innobase_invalidate_query_cache(thr_get_trx(thr), buf, len); +#endif + mem_free(buf); +} + +/************************************************************************* +Perform referential actions or checks when a parent row is deleted or updated +and the constraint had an ON DELETE or ON UPDATE condition which was not +RESTRICT. */ +static +ulint +row_ins_foreign_check_on_constraint( +/*================================*/ + /* out: DB_SUCCESS, DB_LOCK_WAIT, + or error code */ + que_thr_t* thr, /* in: query thread whose run_node + is an update node */ + dict_foreign_t* foreign, /* in: foreign key constraint whose + type is != 0 */ + btr_pcur_t* pcur, /* in: cursor placed on a matching + index record in the child table */ + dtuple_t* entry, /* in: index entry in the parent + table */ + mtr_t* mtr) /* in: mtr holding the latch of pcur + page */ +{ + upd_node_t* node; + upd_node_t* cascade; + dict_table_t* table = foreign->foreign_table; + dict_index_t* index; + dict_index_t* clust_index; + dtuple_t* ref; + mem_heap_t* upd_vec_heap = NULL; + const rec_t* rec; + const rec_t* clust_rec; + const buf_block_t* clust_block; + upd_t* update; + ulint n_to_update; + ulint err; + ulint i; + trx_t* trx; + mem_heap_t* tmp_heap = NULL; + + ut_a(thr); + ut_a(foreign); + ut_a(pcur); + ut_a(mtr); + + trx = thr_get_trx(thr); + + /* Since we are going to delete or update a row, we have to invalidate + the MySQL query cache for table. A deadlock of threads is not possible + here because the caller of this function does not hold any latches with + the sync0sync.h rank above the kernel mutex. The query cache mutex has + a rank just above the kernel mutex. */ + + row_ins_invalidate_query_cache(thr, table->name); + + node = thr->run_node; + + if (node->is_delete && 0 == (foreign->type + & (DICT_FOREIGN_ON_DELETE_CASCADE + | DICT_FOREIGN_ON_DELETE_SET_NULL))) { + + row_ins_foreign_report_err("Trying to delete", + thr, foreign, + btr_pcur_get_rec(pcur), entry); + + return(DB_ROW_IS_REFERENCED); + } + + if (!node->is_delete && 0 == (foreign->type + & (DICT_FOREIGN_ON_UPDATE_CASCADE + | DICT_FOREIGN_ON_UPDATE_SET_NULL))) { + + /* This is an UPDATE */ + + row_ins_foreign_report_err("Trying to update", + thr, foreign, + btr_pcur_get_rec(pcur), entry); + + return(DB_ROW_IS_REFERENCED); + } + + if (node->cascade_node == NULL) { + /* Extend our query graph by creating a child to current + update node. The child is used in the cascade or set null + operation. */ + + node->cascade_heap = mem_heap_create(128); + node->cascade_node = row_create_update_node_for_mysql( + table, node->cascade_heap); + que_node_set_parent(node->cascade_node, node); + } + + /* Initialize cascade_node to do the operation we want. Note that we + use the SAME cascade node to do all foreign key operations of the + SQL DELETE: the table of the cascade node may change if there are + several child tables to the table where the delete is done! */ + + cascade = node->cascade_node; + + cascade->table = table; + + cascade->foreign = foreign; + + if (node->is_delete + && (foreign->type & DICT_FOREIGN_ON_DELETE_CASCADE)) { + cascade->is_delete = TRUE; + } else { + cascade->is_delete = FALSE; + + if (foreign->n_fields > cascade->update_n_fields) { + /* We have to make the update vector longer */ + + cascade->update = upd_create(foreign->n_fields, + node->cascade_heap); + cascade->update_n_fields = foreign->n_fields; + } + } + + /* We do not allow cyclic cascaded updating (DELETE is allowed, + but not UPDATE) of the same table, as this can lead to an infinite + cycle. Check that we are not updating the same table which is + already being modified in this cascade chain. We have to check + this also because the modification of the indexes of a 'parent' + table may still be incomplete, and we must avoid seeing the indexes + of the parent table in an inconsistent state! */ + + if (!cascade->is_delete + && row_ins_cascade_ancestor_updates_table(cascade, table)) { + + /* We do not know if this would break foreign key + constraints, but play safe and return an error */ + + err = DB_ROW_IS_REFERENCED; + + row_ins_foreign_report_err( + "Trying an update, possibly causing a cyclic" + " cascaded update\n" + "in the child table,", thr, foreign, + btr_pcur_get_rec(pcur), entry); + + goto nonstandard_exit_func; + } + + if (row_ins_cascade_n_ancestors(cascade) >= 15) { + err = DB_ROW_IS_REFERENCED; + + row_ins_foreign_report_err( + "Trying a too deep cascaded delete or update\n", + thr, foreign, btr_pcur_get_rec(pcur), entry); + + goto nonstandard_exit_func; + } + + index = btr_pcur_get_btr_cur(pcur)->index; + + ut_a(index == foreign->foreign_index); + + rec = btr_pcur_get_rec(pcur); + + if (dict_index_is_clust(index)) { + /* pcur is already positioned in the clustered index of + the child table */ + + clust_index = index; + clust_rec = rec; + clust_block = btr_pcur_get_block(pcur); + } else { + /* We have to look for the record in the clustered index + in the child table */ + + clust_index = dict_table_get_first_index(table); + + tmp_heap = mem_heap_create(256); + + ref = row_build_row_ref(ROW_COPY_POINTERS, index, rec, + tmp_heap); + btr_pcur_open_with_no_init(clust_index, ref, + PAGE_CUR_LE, BTR_SEARCH_LEAF, + cascade->pcur, 0, mtr); + + clust_rec = btr_pcur_get_rec(cascade->pcur); + clust_block = btr_pcur_get_block(cascade->pcur); + + if (!page_rec_is_user_rec(clust_rec) + || btr_pcur_get_low_match(cascade->pcur) + < dict_index_get_n_unique(clust_index)) { + + fputs("InnoDB: error in cascade of a foreign key op\n" + "InnoDB: ", stderr); + dict_index_name_print(stderr, trx, index); + + fputs("\n" + "InnoDB: record ", stderr); + rec_print(stderr, rec, index); + fputs("\n" + "InnoDB: clustered record ", stderr); + rec_print(stderr, clust_rec, clust_index); + fputs("\n" + "InnoDB: Submit a detailed bug report to" + " http://bugs.mysql.com\n", stderr); + + err = DB_SUCCESS; + + goto nonstandard_exit_func; + } + } + + /* Set an X-lock on the row to delete or update in the child table */ + + err = lock_table(0, table, LOCK_IX, thr); + + if (err == DB_SUCCESS) { + /* Here it suffices to use a LOCK_REC_NOT_GAP type lock; + we already have a normal shared lock on the appropriate + gap if the search criterion was not unique */ + + err = lock_clust_rec_read_check_and_lock_alt( + 0, clust_block, clust_rec, clust_index, + LOCK_X, LOCK_REC_NOT_GAP, thr); + } + + if (err != DB_SUCCESS) { + + goto nonstandard_exit_func; + } + + if (rec_get_deleted_flag(clust_rec, dict_table_is_comp(table))) { + /* This can happen if there is a circular reference of + rows such that cascading delete comes to delete a row + already in the process of being delete marked */ + err = DB_SUCCESS; + + goto nonstandard_exit_func; + } + + if ((node->is_delete + && (foreign->type & DICT_FOREIGN_ON_DELETE_SET_NULL)) + || (!node->is_delete + && (foreign->type & DICT_FOREIGN_ON_UPDATE_SET_NULL))) { + + /* Build the appropriate update vector which sets + foreign->n_fields first fields in rec to SQL NULL */ + + update = cascade->update; + + update->info_bits = 0; + update->n_fields = foreign->n_fields; + + for (i = 0; i < foreign->n_fields; i++) { + upd_field_t* ufield = &update->fields[i]; + + ufield->field_no = dict_table_get_nth_col_pos( + table, + dict_index_get_nth_col_no(index, i)); + ufield->orig_len = 0; + ufield->exp = NULL; + dfield_set_null(&ufield->new_val); + } + } + + if (!node->is_delete + && (foreign->type & DICT_FOREIGN_ON_UPDATE_CASCADE)) { + + /* Build the appropriate update vector which sets changing + foreign->n_fields first fields in rec to new values */ + + upd_vec_heap = mem_heap_create(256); + + n_to_update = row_ins_cascade_calc_update_vec(node, foreign, + upd_vec_heap); + if (n_to_update == ULINT_UNDEFINED) { + err = DB_ROW_IS_REFERENCED; + + row_ins_foreign_report_err( + "Trying a cascaded update where the" + " updated value in the child\n" + "table would not fit in the length" + " of the column, or the value would\n" + "be NULL and the column is" + " declared as not NULL in the child table,", + thr, foreign, btr_pcur_get_rec(pcur), entry); + + goto nonstandard_exit_func; + } + + if (cascade->update->n_fields == 0) { + + /* The update does not change any columns referred + to in this foreign key constraint: no need to do + anything */ + + err = DB_SUCCESS; + + goto nonstandard_exit_func; + } + } + + /* Store pcur position and initialize or store the cascade node + pcur stored position */ + + btr_pcur_store_position(pcur, mtr); + + if (index == clust_index) { + btr_pcur_copy_stored_position(cascade->pcur, pcur); + } else { + btr_pcur_store_position(cascade->pcur, mtr); + } + + mtr_commit(mtr); + + ut_a(cascade->pcur->rel_pos == BTR_PCUR_ON); + + cascade->state = UPD_NODE_UPDATE_CLUSTERED; + + err = row_update_cascade_for_mysql(thr, cascade, + foreign->foreign_table); + + if (foreign->foreign_table->n_foreign_key_checks_running == 0) { + fprintf(stderr, + "InnoDB: error: table %s has the counter 0" + " though there is\n" + "InnoDB: a FOREIGN KEY check running on it.\n", + foreign->foreign_table->name); + } + + /* Release the data dictionary latch for a while, so that we do not + starve other threads from doing CREATE TABLE etc. if we have a huge + cascaded operation running. The counter n_foreign_key_checks_running + will prevent other users from dropping or ALTERing the table when we + release the latch. */ + + row_mysql_unfreeze_data_dictionary(thr_get_trx(thr)); + row_mysql_freeze_data_dictionary(thr_get_trx(thr)); + + mtr_start(mtr); + + /* Restore pcur position */ + + btr_pcur_restore_position(BTR_SEARCH_LEAF, pcur, mtr); + + if (tmp_heap) { + mem_heap_free(tmp_heap); + } + + if (upd_vec_heap) { + mem_heap_free(upd_vec_heap); + } + + return(err); + +nonstandard_exit_func: + if (tmp_heap) { + mem_heap_free(tmp_heap); + } + + if (upd_vec_heap) { + mem_heap_free(upd_vec_heap); + } + + btr_pcur_store_position(pcur, mtr); + + mtr_commit(mtr); + mtr_start(mtr); + + btr_pcur_restore_position(BTR_SEARCH_LEAF, pcur, mtr); + + return(err); +} + +/************************************************************************* +Sets a shared lock on a record. Used in locking possible duplicate key +records and also in checking foreign key constraints. */ +static +ulint +row_ins_set_shared_rec_lock( +/*========================*/ + /* out: DB_SUCCESS or error code */ + ulint type, /* in: LOCK_ORDINARY, LOCK_GAP, or + LOCK_REC_NOT_GAP type lock */ + const buf_block_t* block, /* in: buffer block of rec */ + const rec_t* rec, /* in: record */ + dict_index_t* index, /* in: index */ + const ulint* offsets,/* in: rec_get_offsets(rec, index) */ + que_thr_t* thr) /* in: query thread */ +{ + ulint err; + + ut_ad(rec_offs_validate(rec, index, offsets)); + + if (dict_index_is_clust(index)) { + err = lock_clust_rec_read_check_and_lock( + 0, block, rec, index, offsets, LOCK_S, type, thr); + } else { + err = lock_sec_rec_read_check_and_lock( + 0, block, rec, index, offsets, LOCK_S, type, thr); + } + + return(err); +} + +#ifndef UNIV_HOTBACKUP +/************************************************************************* +Sets a exclusive lock on a record. Used in locking possible duplicate key +records */ +static +ulint +row_ins_set_exclusive_rec_lock( +/*===========================*/ + /* out: DB_SUCCESS or error code */ + ulint type, /* in: LOCK_ORDINARY, LOCK_GAP, or + LOCK_REC_NOT_GAP type lock */ + const buf_block_t* block, /* in: buffer block of rec */ + const rec_t* rec, /* in: record */ + dict_index_t* index, /* in: index */ + const ulint* offsets,/* in: rec_get_offsets(rec, index) */ + que_thr_t* thr) /* in: query thread */ +{ + ulint err; + + ut_ad(rec_offs_validate(rec, index, offsets)); + + if (dict_index_is_clust(index)) { + err = lock_clust_rec_read_check_and_lock( + 0, block, rec, index, offsets, LOCK_X, type, thr); + } else { + err = lock_sec_rec_read_check_and_lock( + 0, block, rec, index, offsets, LOCK_X, type, thr); + } + + return(err); +} +#endif /* !UNIV_HOTBACKUP */ + +/******************************************************************* +Checks if foreign key constraint fails for an index entry. Sets shared locks +which lock either the success or the failure of the constraint. NOTE that +the caller must have a shared latch on dict_operation_lock. */ +UNIV_INTERN +ulint +row_ins_check_foreign_constraint( +/*=============================*/ + /* out: DB_SUCCESS, + DB_NO_REFERENCED_ROW, + or DB_ROW_IS_REFERENCED */ + ibool check_ref,/* in: TRUE if we want to check that + the referenced table is ok, FALSE if we + want to to check the foreign key table */ + dict_foreign_t* foreign,/* in: foreign constraint; NOTE that the + tables mentioned in it must be in the + dictionary cache if they exist at all */ + dict_table_t* table, /* in: if check_ref is TRUE, then the foreign + table, else the referenced table */ + dtuple_t* entry, /* in: index entry for index */ + que_thr_t* thr) /* in: query thread */ +{ + upd_node_t* upd_node; + dict_table_t* check_table; + dict_index_t* check_index; + ulint n_fields_cmp; + btr_pcur_t pcur; + ibool moved; + int cmp; + ulint err; + ulint i; + mtr_t mtr; + trx_t* trx = thr_get_trx(thr); + mem_heap_t* heap = NULL; + ulint offsets_[REC_OFFS_NORMAL_SIZE]; + ulint* offsets = offsets_; + rec_offs_init(offsets_); + +run_again: +#ifdef UNIV_SYNC_DEBUG + ut_ad(rw_lock_own(&dict_operation_lock, RW_LOCK_SHARED)); +#endif /* UNIV_SYNC_DEBUG */ + + err = DB_SUCCESS; + + if (trx->check_foreigns == FALSE) { + /* The user has suppressed foreign key checks currently for + this session */ + goto exit_func; + } + + /* If any of the foreign key fields in entry is SQL NULL, we + suppress the foreign key check: this is compatible with Oracle, + for example */ + + for (i = 0; i < foreign->n_fields; i++) { + if (UNIV_SQL_NULL == dfield_get_len( + dtuple_get_nth_field(entry, i))) { + + goto exit_func; + } + } + + if (que_node_get_type(thr->run_node) == QUE_NODE_UPDATE) { + upd_node = thr->run_node; + + if (!(upd_node->is_delete) && upd_node->foreign == foreign) { + /* If a cascaded update is done as defined by a + foreign key constraint, do not check that + constraint for the child row. In ON UPDATE CASCADE + the update of the parent row is only half done when + we come here: if we would check the constraint here + for the child row it would fail. + + A QUESTION remains: if in the child table there are + several constraints which refer to the same parent + table, we should merge all updates to the child as + one update? And the updates can be contradictory! + Currently we just perform the update associated + with each foreign key constraint, one after + another, and the user has problems predicting in + which order they are performed. */ + + goto exit_func; + } + } + + if (check_ref) { + check_table = foreign->referenced_table; + check_index = foreign->referenced_index; + } else { + check_table = foreign->foreign_table; + check_index = foreign->foreign_index; + } + + if (check_table == NULL || check_table->ibd_file_missing) { + if (check_ref) { + FILE* ef = dict_foreign_err_file; + + row_ins_set_detailed(trx, foreign); + + mutex_enter(&dict_foreign_err_mutex); + rewind(ef); + ut_print_timestamp(ef); + fputs(" Transaction:\n", ef); + trx_print(ef, trx, 600); + fputs("Foreign key constraint fails for table ", ef); + ut_print_name(ef, trx, TRUE, + foreign->foreign_table_name); + fputs(":\n", ef); + dict_print_info_on_foreign_key_in_create_format( + ef, trx, foreign, TRUE); + fputs("\nTrying to add to index ", ef); + ut_print_name(ef, trx, FALSE, + foreign->foreign_index->name); + fputs(" tuple:\n", ef); + dtuple_print(ef, entry); + fputs("\nBut the parent table ", ef); + ut_print_name(ef, trx, TRUE, + foreign->referenced_table_name); + fputs("\nor its .ibd file does" + " not currently exist!\n", ef); + mutex_exit(&dict_foreign_err_mutex); + + err = DB_NO_REFERENCED_ROW; + } + + goto exit_func; + } + + ut_a(check_table); + ut_a(check_index); + + if (check_table != table) { + /* We already have a LOCK_IX on table, but not necessarily + on check_table */ + + err = lock_table(0, check_table, LOCK_IS, thr); + + if (err != DB_SUCCESS) { + + goto do_possible_lock_wait; + } + } + + mtr_start(&mtr); + + /* Store old value on n_fields_cmp */ + + n_fields_cmp = dtuple_get_n_fields_cmp(entry); + + dtuple_set_n_fields_cmp(entry, foreign->n_fields); + + btr_pcur_open(check_index, entry, PAGE_CUR_GE, + BTR_SEARCH_LEAF, &pcur, &mtr); + + /* Scan index records and check if there is a matching record */ + + for (;;) { + const rec_t* rec = btr_pcur_get_rec(&pcur); + const buf_block_t* block = btr_pcur_get_block(&pcur); + + if (page_rec_is_infimum(rec)) { + + goto next_rec; + } + + offsets = rec_get_offsets(rec, check_index, + offsets, ULINT_UNDEFINED, &heap); + + if (page_rec_is_supremum(rec)) { + + err = row_ins_set_shared_rec_lock(LOCK_ORDINARY, block, + rec, check_index, + offsets, thr); + if (err != DB_SUCCESS) { + + break; + } + + goto next_rec; + } + + cmp = cmp_dtuple_rec(entry, rec, offsets); + + if (cmp == 0) { + if (rec_get_deleted_flag(rec, + rec_offs_comp(offsets))) { + err = row_ins_set_shared_rec_lock( + LOCK_ORDINARY, block, + rec, check_index, offsets, thr); + if (err != DB_SUCCESS) { + + break; + } + } else { + /* Found a matching record. Lock only + a record because we can allow inserts + into gaps */ + + err = row_ins_set_shared_rec_lock( + LOCK_REC_NOT_GAP, block, + rec, check_index, offsets, thr); + + if (err != DB_SUCCESS) { + + break; + } + + if (check_ref) { + err = DB_SUCCESS; + + break; + } else if (foreign->type != 0) { + /* There is an ON UPDATE or ON DELETE + condition: check them in a separate + function */ + + err = row_ins_foreign_check_on_constraint( + thr, foreign, &pcur, entry, + &mtr); + if (err != DB_SUCCESS) { + /* Since reporting a plain + "duplicate key" error + message to the user in + cases where a long CASCADE + operation would lead to a + duplicate key in some + other table is very + confusing, map duplicate + key errors resulting from + FK constraints to a + separate error code. */ + + if (err == DB_DUPLICATE_KEY) { + err = DB_FOREIGN_DUPLICATE_KEY; + } + + break; + } + + /* row_ins_foreign_check_on_constraint + may have repositioned pcur on a + different block */ + block = btr_pcur_get_block(&pcur); + } else { + row_ins_foreign_report_err( + "Trying to delete or update", + thr, foreign, rec, entry); + + err = DB_ROW_IS_REFERENCED; + break; + } + } + } + + if (cmp < 0) { + err = row_ins_set_shared_rec_lock( + LOCK_GAP, block, + rec, check_index, offsets, thr); + if (err != DB_SUCCESS) { + + break; + } + + if (check_ref) { + err = DB_NO_REFERENCED_ROW; + row_ins_foreign_report_add_err( + trx, foreign, rec, entry); + } else { + err = DB_SUCCESS; + } + + break; + } + + ut_a(cmp == 0); +next_rec: + moved = btr_pcur_move_to_next(&pcur, &mtr); + + if (!moved) { + if (check_ref) { + rec = btr_pcur_get_rec(&pcur); + row_ins_foreign_report_add_err( + trx, foreign, rec, entry); + err = DB_NO_REFERENCED_ROW; + } else { + err = DB_SUCCESS; + } + + break; + } + } + + btr_pcur_close(&pcur); + + mtr_commit(&mtr); + + /* Restore old value */ + dtuple_set_n_fields_cmp(entry, n_fields_cmp); + +do_possible_lock_wait: + if (err == DB_LOCK_WAIT) { + trx->error_state = err; + + que_thr_stop_for_mysql(thr); + + srv_suspend_mysql_thread(thr); + + if (trx->error_state == DB_SUCCESS) { + + goto run_again; + } + + err = trx->error_state; + } + +exit_func: + if (UNIV_LIKELY_NULL(heap)) { + mem_heap_free(heap); + } + return(err); +} + +/******************************************************************* +Checks if foreign key constraints fail for an index entry. If index +is not mentioned in any constraint, this function does nothing, +Otherwise does searches to the indexes of referenced tables and +sets shared locks which lock either the success or the failure of +a constraint. */ +static +ulint +row_ins_check_foreign_constraints( +/*==============================*/ + /* out: DB_SUCCESS or error code */ + dict_table_t* table, /* in: table */ + dict_index_t* index, /* in: index */ + dtuple_t* entry, /* in: index entry for index */ + que_thr_t* thr) /* in: query thread */ +{ + dict_foreign_t* foreign; + ulint err; + trx_t* trx; + ibool got_s_lock = FALSE; + + trx = thr_get_trx(thr); + + foreign = UT_LIST_GET_FIRST(table->foreign_list); + + while (foreign) { + if (foreign->foreign_index == index) { + + if (foreign->referenced_table == NULL) { + dict_table_get(foreign->referenced_table_name, + FALSE); + } + + if (0 == trx->dict_operation_lock_mode) { + got_s_lock = TRUE; + + row_mysql_freeze_data_dictionary(trx); + } + + if (foreign->referenced_table) { + mutex_enter(&(dict_sys->mutex)); + + (foreign->referenced_table + ->n_foreign_key_checks_running)++; + + mutex_exit(&(dict_sys->mutex)); + } + + /* NOTE that if the thread ends up waiting for a lock + we will release dict_operation_lock temporarily! + But the counter on the table protects the referenced + table from being dropped while the check is running. */ + + err = row_ins_check_foreign_constraint( + TRUE, foreign, table, entry, thr); + + if (foreign->referenced_table) { + mutex_enter(&(dict_sys->mutex)); + + ut_a(foreign->referenced_table + ->n_foreign_key_checks_running > 0); + (foreign->referenced_table + ->n_foreign_key_checks_running)--; + + mutex_exit(&(dict_sys->mutex)); + } + + if (got_s_lock) { + row_mysql_unfreeze_data_dictionary(trx); + } + + if (err != DB_SUCCESS) { + return(err); + } + } + + foreign = UT_LIST_GET_NEXT(foreign_list, foreign); + } + + return(DB_SUCCESS); +} + +#ifndef UNIV_HOTBACKUP +/******************************************************************* +Checks if a unique key violation to rec would occur at the index entry +insert. */ +static +ibool +row_ins_dupl_error_with_rec( +/*========================*/ + /* out: TRUE if error */ + const rec_t* rec, /* in: user record; NOTE that we assume + that the caller already has a record lock on + the record! */ + const dtuple_t* entry, /* in: entry to insert */ + dict_index_t* index, /* in: index */ + const ulint* offsets)/* in: rec_get_offsets(rec, index) */ +{ + ulint matched_fields; + ulint matched_bytes; + ulint n_unique; + ulint i; + + ut_ad(rec_offs_validate(rec, index, offsets)); + + n_unique = dict_index_get_n_unique(index); + + matched_fields = 0; + matched_bytes = 0; + + cmp_dtuple_rec_with_match(entry, rec, offsets, + &matched_fields, &matched_bytes); + + if (matched_fields < n_unique) { + + return(FALSE); + } + + /* In a unique secondary index we allow equal key values if they + contain SQL NULLs */ + + if (!dict_index_is_clust(index)) { + + for (i = 0; i < n_unique; i++) { + if (UNIV_SQL_NULL == dfield_get_len( + dtuple_get_nth_field(entry, i))) { + + return(FALSE); + } + } + } + + return(!rec_get_deleted_flag(rec, rec_offs_comp(offsets))); +} +#endif /* !UNIV_HOTBACKUP */ + +/******************************************************************* +Scans a unique non-clustered index at a given index entry to determine +whether a uniqueness violation has occurred for the key value of the entry. +Set shared locks on possible duplicate records. */ +static +ulint +row_ins_scan_sec_index_for_duplicate( +/*=================================*/ + /* out: DB_SUCCESS, DB_DUPLICATE_KEY, or + DB_LOCK_WAIT */ + dict_index_t* index, /* in: non-clustered unique index */ + dtuple_t* entry, /* in: index entry */ + que_thr_t* thr) /* in: query thread */ +{ +#ifndef UNIV_HOTBACKUP + ulint n_unique; + ulint i; + int cmp; + ulint n_fields_cmp; + btr_pcur_t pcur; + ulint err = DB_SUCCESS; + unsigned allow_duplicates; + mtr_t mtr; + mem_heap_t* heap = NULL; + ulint offsets_[REC_OFFS_NORMAL_SIZE]; + ulint* offsets = offsets_; + rec_offs_init(offsets_); + + n_unique = dict_index_get_n_unique(index); + + /* If the secondary index is unique, but one of the fields in the + n_unique first fields is NULL, a unique key violation cannot occur, + since we define NULL != NULL in this case */ + + for (i = 0; i < n_unique; i++) { + if (UNIV_SQL_NULL == dfield_get_len( + dtuple_get_nth_field(entry, i))) { + + return(DB_SUCCESS); + } + } + + mtr_start(&mtr); + + /* Store old value on n_fields_cmp */ + + n_fields_cmp = dtuple_get_n_fields_cmp(entry); + + dtuple_set_n_fields_cmp(entry, dict_index_get_n_unique(index)); + + btr_pcur_open(index, entry, PAGE_CUR_GE, BTR_SEARCH_LEAF, &pcur, &mtr); + + allow_duplicates = thr_get_trx(thr)->duplicates & TRX_DUP_IGNORE; + + /* Scan index records and check if there is a duplicate */ + + do { + const rec_t* rec = btr_pcur_get_rec(&pcur); + const buf_block_t* block = btr_pcur_get_block(&pcur); + + if (page_rec_is_infimum(rec)) { + + continue; + } + + offsets = rec_get_offsets(rec, index, offsets, + ULINT_UNDEFINED, &heap); + + if (allow_duplicates) { + + /* If the SQL-query will update or replace + duplicate key we will take X-lock for + duplicates ( REPLACE, LOAD DATAFILE REPLACE, + INSERT ON DUPLICATE KEY UPDATE). */ + + err = row_ins_set_exclusive_rec_lock( + LOCK_ORDINARY, block, + rec, index, offsets, thr); + } else { + + err = row_ins_set_shared_rec_lock( + LOCK_ORDINARY, block, + rec, index, offsets, thr); + } + + if (err != DB_SUCCESS) { + + break; + } + + if (page_rec_is_supremum(rec)) { + + continue; + } + + cmp = cmp_dtuple_rec(entry, rec, offsets); + + if (cmp == 0) { + if (row_ins_dupl_error_with_rec(rec, entry, + index, offsets)) { + err = DB_DUPLICATE_KEY; + + thr_get_trx(thr)->error_info = index; + + break; + } + } + + if (cmp < 0) { + break; + } + + ut_a(cmp == 0); + } while (btr_pcur_move_to_next(&pcur, &mtr)); + + if (UNIV_LIKELY_NULL(heap)) { + mem_heap_free(heap); + } + mtr_commit(&mtr); + + /* Restore old value */ + dtuple_set_n_fields_cmp(entry, n_fields_cmp); + + return(err); +#else /* UNIV_HOTBACKUP */ + /* This function depends on MySQL code that is not included in + InnoDB Hot Backup builds. Besides, this function should never + be called in InnoDB Hot Backup. */ + ut_error; + return(DB_FAIL); +#endif /* UNIV_HOTBACKUP */ +} + +/******************************************************************* +Checks if a unique key violation error would occur at an index entry +insert. Sets shared locks on possible duplicate records. Works only +for a clustered index! */ +static +ulint +row_ins_duplicate_error_in_clust( +/*=============================*/ + /* out: DB_SUCCESS if no error, + DB_DUPLICATE_KEY if error, DB_LOCK_WAIT if we + have to wait for a lock on a possible + duplicate record */ + btr_cur_t* cursor, /* in: B-tree cursor */ + dtuple_t* entry, /* in: entry to insert */ + que_thr_t* thr, /* in: query thread */ + mtr_t* mtr) /* in: mtr */ +{ +#ifndef UNIV_HOTBACKUP + ulint err; + rec_t* rec; + ulint n_unique; + trx_t* trx = thr_get_trx(thr); + mem_heap_t*heap = NULL; + ulint offsets_[REC_OFFS_NORMAL_SIZE]; + ulint* offsets = offsets_; + rec_offs_init(offsets_); + + UT_NOT_USED(mtr); + + ut_a(dict_index_is_clust(cursor->index)); + ut_ad(dict_index_is_unique(cursor->index)); + + /* NOTE: For unique non-clustered indexes there may be any number + of delete marked records with the same value for the non-clustered + index key (remember multiversioning), and which differ only in + the row refererence part of the index record, containing the + clustered index key fields. For such a secondary index record, + to avoid race condition, we must FIRST do the insertion and after + that check that the uniqueness condition is not breached! */ + + /* NOTE: A problem is that in the B-tree node pointers on an + upper level may match more to the entry than the actual existing + user records on the leaf level. So, even if low_match would suggest + that a duplicate key violation may occur, this may not be the case. */ + + n_unique = dict_index_get_n_unique(cursor->index); + + if (cursor->low_match >= n_unique) { + + rec = btr_cur_get_rec(cursor); + + if (!page_rec_is_infimum(rec)) { + offsets = rec_get_offsets(rec, cursor->index, offsets, + ULINT_UNDEFINED, &heap); + + /* We set a lock on the possible duplicate: this + is needed in logical logging of MySQL to make + sure that in roll-forward we get the same duplicate + errors as in original execution */ + + if (trx->duplicates & TRX_DUP_IGNORE) { + + /* If the SQL-query will update or replace + duplicate key we will take X-lock for + duplicates ( REPLACE, LOAD DATAFILE REPLACE, + INSERT ON DUPLICATE KEY UPDATE). */ + + err = row_ins_set_exclusive_rec_lock( + LOCK_REC_NOT_GAP, + btr_cur_get_block(cursor), + rec, cursor->index, offsets, thr); + } else { + + err = row_ins_set_shared_rec_lock( + LOCK_REC_NOT_GAP, + btr_cur_get_block(cursor), rec, + cursor->index, offsets, thr); + } + + if (err != DB_SUCCESS) { + goto func_exit; + } + + if (row_ins_dupl_error_with_rec( + rec, entry, cursor->index, offsets)) { + trx->error_info = cursor->index; + err = DB_DUPLICATE_KEY; + goto func_exit; + } + } + } + + if (cursor->up_match >= n_unique) { + + rec = page_rec_get_next(btr_cur_get_rec(cursor)); + + if (!page_rec_is_supremum(rec)) { + offsets = rec_get_offsets(rec, cursor->index, offsets, + ULINT_UNDEFINED, &heap); + + if (trx->duplicates & TRX_DUP_IGNORE) { + + /* If the SQL-query will update or replace + duplicate key we will take X-lock for + duplicates ( REPLACE, LOAD DATAFILE REPLACE, + INSERT ON DUPLICATE KEY UPDATE). */ + + err = row_ins_set_exclusive_rec_lock( + LOCK_REC_NOT_GAP, + btr_cur_get_block(cursor), + rec, cursor->index, offsets, thr); + } else { + + err = row_ins_set_shared_rec_lock( + LOCK_REC_NOT_GAP, + btr_cur_get_block(cursor), + rec, cursor->index, offsets, thr); + } + + if (err != DB_SUCCESS) { + goto func_exit; + } + + if (row_ins_dupl_error_with_rec( + rec, entry, cursor->index, offsets)) { + trx->error_info = cursor->index; + err = DB_DUPLICATE_KEY; + goto func_exit; + } + } + + ut_a(!dict_index_is_clust(cursor->index)); + /* This should never happen */ + } + + err = DB_SUCCESS; +func_exit: + if (UNIV_LIKELY_NULL(heap)) { + mem_heap_free(heap); + } + return(err); +#else /* UNIV_HOTBACKUP */ + /* This function depends on MySQL code that is not included in + InnoDB Hot Backup builds. Besides, this function should never + be called in InnoDB Hot Backup. */ + ut_error; + return(DB_FAIL); +#endif /* UNIV_HOTBACKUP */ +} + +/******************************************************************* +Checks if an index entry has long enough common prefix with an existing +record so that the intended insert of the entry must be changed to a modify of +the existing record. In the case of a clustered index, the prefix must be +n_unique fields long, and in the case of a secondary index, all fields must be +equal. */ +UNIV_INLINE +ulint +row_ins_must_modify( +/*================*/ + /* out: 0 if no update, ROW_INS_PREV if + previous should be updated; currently we + do the search so that only the low_match + record can match enough to the search tuple, + not the next record */ + btr_cur_t* cursor) /* in: B-tree cursor */ +{ + ulint enough_match; + rec_t* rec; + + /* NOTE: (compare to the note in row_ins_duplicate_error) Because node + pointers on upper levels of the B-tree may match more to entry than + to actual user records on the leaf level, we have to check if the + candidate record is actually a user record. In a clustered index + node pointers contain index->n_unique first fields, and in the case + of a secondary index, all fields of the index. */ + + enough_match = dict_index_get_n_unique_in_tree(cursor->index); + + if (cursor->low_match >= enough_match) { + + rec = btr_cur_get_rec(cursor); + + if (!page_rec_is_infimum(rec)) { + + return(ROW_INS_PREV); + } + } + + return(0); +} + +/******************************************************************* +Tries to insert an index entry to an index. If the index is clustered +and a record with the same unique key is found, the other record is +necessarily marked deleted by a committed transaction, or a unique key +violation error occurs. The delete marked record is then updated to an +existing record, and we must write an undo log record on the delete +marked record. If the index is secondary, and a record with exactly the +same fields is found, the other record is necessarily marked deleted. +It is then unmarked. Otherwise, the entry is just inserted to the index. */ +static +ulint +row_ins_index_entry_low( +/*====================*/ + /* out: DB_SUCCESS, DB_LOCK_WAIT, DB_FAIL + if pessimistic retry needed, or error code */ + ulint mode, /* in: BTR_MODIFY_LEAF or BTR_MODIFY_TREE, + depending on whether we wish optimistic or + pessimistic descent down the index tree */ + dict_index_t* index, /* in: index */ + dtuple_t* entry, /* in: index entry to insert */ + ulint n_ext, /* in: number of externally stored columns */ + que_thr_t* thr) /* in: query thread */ +{ + btr_cur_t cursor; + ulint ignore_sec_unique = 0; + ulint modify = 0; /* remove warning */ + rec_t* insert_rec; + rec_t* rec; + ulint err; + ulint n_unique; + big_rec_t* big_rec = NULL; + mtr_t mtr; + mem_heap_t* heap = NULL; + + log_free_check(); + + mtr_start(&mtr); + + cursor.thr = thr; + + /* Note that we use PAGE_CUR_LE as the search mode, because then + the function will return in both low_match and up_match of the + cursor sensible values */ + + if (!(thr_get_trx(thr)->check_unique_secondary)) { + ignore_sec_unique = BTR_IGNORE_SEC_UNIQUE; + } + + btr_cur_search_to_nth_level(index, 0, entry, PAGE_CUR_LE, + mode | BTR_INSERT | ignore_sec_unique, + &cursor, 0, &mtr); + + if (cursor.flag == BTR_CUR_INSERT_TO_IBUF) { + /* The insertion was made to the insert buffer already during + the search: we are done */ + + err = DB_SUCCESS; + + goto function_exit; + } + +#ifdef UNIV_DEBUG + { + page_t* page = btr_cur_get_page(&cursor); + rec_t* first_rec = page_rec_get_next( + page_get_infimum_rec(page)); + + ut_ad(page_rec_is_supremum(first_rec) + || rec_get_n_fields(first_rec, index) + == dtuple_get_n_fields(entry)); + } +#endif + + n_unique = dict_index_get_n_unique(index); + + if (dict_index_is_unique(index) && (cursor.up_match >= n_unique + || cursor.low_match >= n_unique)) { + + if (dict_index_is_clust(index)) { + /* Note that the following may return also + DB_LOCK_WAIT */ + + err = row_ins_duplicate_error_in_clust( + &cursor, entry, thr, &mtr); + if (err != DB_SUCCESS) { + + goto function_exit; + } + } else { + mtr_commit(&mtr); + err = row_ins_scan_sec_index_for_duplicate( + index, entry, thr); + mtr_start(&mtr); + + if (err != DB_SUCCESS) { + + goto function_exit; + } + + /* We did not find a duplicate and we have now + locked with s-locks the necessary records to + prevent any insertion of a duplicate by another + transaction. Let us now reposition the cursor and + continue the insertion. */ + + btr_cur_search_to_nth_level(index, 0, entry, + PAGE_CUR_LE, + mode | BTR_INSERT, + &cursor, 0, &mtr); + } + } + + modify = row_ins_must_modify(&cursor); + + if (modify != 0) { + /* There is already an index entry with a long enough common + prefix, we must convert the insert into a modify of an + existing record */ + + if (modify == ROW_INS_NEXT) { + rec = page_rec_get_next(btr_cur_get_rec(&cursor)); + + btr_cur_position(index, rec, + btr_cur_get_block(&cursor),&cursor); + } + + if (dict_index_is_clust(index)) { + err = row_ins_clust_index_entry_by_modify( + mode, &cursor, &heap, &big_rec, entry, + thr, &mtr); + } else { + ut_ad(!n_ext); + err = row_ins_sec_index_entry_by_modify( + mode, &cursor, entry, thr, &mtr); + } + } else { + if (mode == BTR_MODIFY_LEAF) { + err = btr_cur_optimistic_insert( + 0, &cursor, entry, &insert_rec, &big_rec, + n_ext, thr, &mtr); + } else { + ut_a(mode == BTR_MODIFY_TREE); + if (buf_LRU_buf_pool_running_out()) { + + err = DB_LOCK_TABLE_FULL; + + goto function_exit; + } + err = btr_cur_pessimistic_insert( + 0, &cursor, entry, &insert_rec, &big_rec, + n_ext, thr, &mtr); + } + } + +function_exit: + mtr_commit(&mtr); + + if (UNIV_LIKELY_NULL(big_rec)) { + rec_t* rec; + ulint* offsets; + mtr_start(&mtr); + + btr_cur_search_to_nth_level(index, 0, entry, PAGE_CUR_LE, + BTR_MODIFY_TREE, &cursor, 0, &mtr); + rec = btr_cur_get_rec(&cursor); + offsets = rec_get_offsets(rec, index, NULL, + ULINT_UNDEFINED, &heap); + + err = btr_store_big_rec_extern_fields( + index, btr_cur_get_block(&cursor), + rec, offsets, big_rec, &mtr); + + if (modify) { + dtuple_big_rec_free(big_rec); + } else { + dtuple_convert_back_big_rec(index, entry, big_rec); + } + + mtr_commit(&mtr); + } + + if (UNIV_LIKELY_NULL(heap)) { + mem_heap_free(heap); + } + return(err); +} + +/******************************************************************* +Inserts an index entry to index. Tries first optimistic, then pessimistic +descent down the tree. If the entry matches enough to a delete marked record, +performs the insert by updating or delete unmarking the delete marked +record. */ +UNIV_INTERN +ulint +row_ins_index_entry( +/*================*/ + /* out: DB_SUCCESS, DB_LOCK_WAIT, + DB_DUPLICATE_KEY, or some other error code */ + dict_index_t* index, /* in: index */ + dtuple_t* entry, /* in: index entry to insert */ + ulint n_ext, /* in: number of externally stored columns */ + ibool foreign,/* in: TRUE=check foreign key constraints */ + que_thr_t* thr) /* in: query thread */ +{ + ulint err; + + if (foreign && UT_LIST_GET_FIRST(index->table->foreign_list)) { + err = row_ins_check_foreign_constraints(index->table, index, + entry, thr); + if (err != DB_SUCCESS) { + + return(err); + } + } + + /* Try first optimistic descent to the B-tree */ + + err = row_ins_index_entry_low(BTR_MODIFY_LEAF, index, entry, + n_ext, thr); + if (err != DB_FAIL) { + + return(err); + } + + /* Try then pessimistic descent to the B-tree */ + + err = row_ins_index_entry_low(BTR_MODIFY_TREE, index, entry, + n_ext, thr); + return(err); +} + +/*************************************************************** +Sets the values of the dtuple fields in entry from the values of appropriate +columns in row. */ +static +void +row_ins_index_entry_set_vals( +/*=========================*/ + dict_index_t* index, /* in: index */ + dtuple_t* entry, /* in: index entry to make */ + const dtuple_t* row) /* in: row */ +{ + ulint n_fields; + ulint i; + + ut_ad(entry && row); + + n_fields = dtuple_get_n_fields(entry); + + for (i = 0; i < n_fields; i++) { + dict_field_t* ind_field; + dfield_t* field; + const dfield_t* row_field; + ulint len; + + field = dtuple_get_nth_field(entry, i); + ind_field = dict_index_get_nth_field(index, i); + row_field = dtuple_get_nth_field(row, ind_field->col->ind); + len = dfield_get_len(row_field); + + /* Check column prefix indexes */ + if (ind_field->prefix_len > 0 + && dfield_get_len(row_field) != UNIV_SQL_NULL) { + + const dict_col_t* col + = dict_field_get_col(ind_field); + + len = dtype_get_at_most_n_mbchars( + col->prtype, col->mbminlen, col->mbmaxlen, + ind_field->prefix_len, + len, dfield_get_data(row_field)); + + ut_ad(!dfield_is_ext(row_field)); + } + + dfield_set_data(field, dfield_get_data(row_field), len); + if (dfield_is_ext(row_field)) { + ut_ad(dict_index_is_clust(index)); + dfield_set_ext(field); + } + } +} + +/*************************************************************** +Inserts a single index entry to the table. */ +static +ulint +row_ins_index_entry_step( +/*=====================*/ + /* out: DB_SUCCESS if operation successfully + completed, else error code or DB_LOCK_WAIT */ + ins_node_t* node, /* in: row insert node */ + que_thr_t* thr) /* in: query thread */ +{ + ulint err; + + ut_ad(dtuple_check_typed(node->row)); + + row_ins_index_entry_set_vals(node->index, node->entry, node->row); + + ut_ad(dtuple_check_typed(node->entry)); + + err = row_ins_index_entry(node->index, node->entry, 0, TRUE, thr); + + return(err); +} + +/*************************************************************** +Allocates a row id for row and inits the node->index field. */ +UNIV_INLINE +void +row_ins_alloc_row_id_step( +/*======================*/ + ins_node_t* node) /* in: row insert node */ +{ + dulint row_id; + + ut_ad(node->state == INS_NODE_ALLOC_ROW_ID); + + if (dict_index_is_unique(dict_table_get_first_index(node->table))) { + + /* No row id is stored if the clustered index is unique */ + + return; + } + + /* Fill in row id value to row */ + + row_id = dict_sys_get_new_row_id(); + + dict_sys_write_row_id(node->row_id_buf, row_id); +} + +/*************************************************************** +Gets a row to insert from the values list. */ +UNIV_INLINE +void +row_ins_get_row_from_values( +/*========================*/ + ins_node_t* node) /* in: row insert node */ +{ + que_node_t* list_node; + dfield_t* dfield; + dtuple_t* row; + ulint i; + + /* The field values are copied in the buffers of the select node and + it is safe to use them until we fetch from select again: therefore + we can just copy the pointers */ + + row = node->row; + + i = 0; + list_node = node->values_list; + + while (list_node) { + eval_exp(list_node); + + dfield = dtuple_get_nth_field(row, i); + dfield_copy_data(dfield, que_node_get_val(list_node)); + + i++; + list_node = que_node_get_next(list_node); + } +} + +/*************************************************************** +Gets a row to insert from the select list. */ +UNIV_INLINE +void +row_ins_get_row_from_select( +/*========================*/ + ins_node_t* node) /* in: row insert node */ +{ + que_node_t* list_node; + dfield_t* dfield; + dtuple_t* row; + ulint i; + + /* The field values are copied in the buffers of the select node and + it is safe to use them until we fetch from select again: therefore + we can just copy the pointers */ + + row = node->row; + + i = 0; + list_node = node->select->select_list; + + while (list_node) { + dfield = dtuple_get_nth_field(row, i); + dfield_copy_data(dfield, que_node_get_val(list_node)); + + i++; + list_node = que_node_get_next(list_node); + } +} + +/*************************************************************** +Inserts a row to a table. */ +static +ulint +row_ins( +/*====*/ + /* out: DB_SUCCESS if operation successfully + completed, else error code or DB_LOCK_WAIT */ + ins_node_t* node, /* in: row insert node */ + que_thr_t* thr) /* in: query thread */ +{ + ulint err; + + ut_ad(node && thr); + + if (node->state == INS_NODE_ALLOC_ROW_ID) { + + row_ins_alloc_row_id_step(node); + + node->index = dict_table_get_first_index(node->table); + node->entry = UT_LIST_GET_FIRST(node->entry_list); + + if (node->ins_type == INS_SEARCHED) { + + row_ins_get_row_from_select(node); + + } else if (node->ins_type == INS_VALUES) { + + row_ins_get_row_from_values(node); + } + + node->state = INS_NODE_INSERT_ENTRIES; + } + + ut_ad(node->state == INS_NODE_INSERT_ENTRIES); + + while (node->index != NULL) { + err = row_ins_index_entry_step(node, thr); + + if (err != DB_SUCCESS) { + + return(err); + } + + node->index = dict_table_get_next_index(node->index); + node->entry = UT_LIST_GET_NEXT(tuple_list, node->entry); + } + + ut_ad(node->entry == NULL); + + node->state = INS_NODE_ALLOC_ROW_ID; + + return(DB_SUCCESS); +} + +/*************************************************************** +Inserts a row to a table. This is a high-level function used in SQL execution +graphs. */ +UNIV_INTERN +que_thr_t* +row_ins_step( +/*=========*/ + /* out: query thread to run next or NULL */ + que_thr_t* thr) /* in: query thread */ +{ + ins_node_t* node; + que_node_t* parent; + sel_node_t* sel_node; + trx_t* trx; + ulint err; + + ut_ad(thr); + + trx = thr_get_trx(thr); + + trx_start_if_not_started(trx); + + node = thr->run_node; + + ut_ad(que_node_get_type(node) == QUE_NODE_INSERT); + + parent = que_node_get_parent(node); + sel_node = node->select; + + if (thr->prev_node == parent) { + node->state = INS_NODE_SET_IX_LOCK; + } + + /* If this is the first time this node is executed (or when + execution resumes after wait for the table IX lock), set an + IX lock on the table and reset the possible select node. MySQL's + partitioned table code may also call an insert within the same + SQL statement AFTER it has used this table handle to do a search. + This happens, for example, when a row update moves it to another + partition. In that case, we have already set the IX lock on the + table during the search operation, and there is no need to set + it again here. But we must write trx->id to node->trx_id_buf. */ + + trx_write_trx_id(node->trx_id_buf, trx->id); + + if (node->state == INS_NODE_SET_IX_LOCK) { + + /* It may be that the current session has not yet started + its transaction, or it has been committed: */ + + if (UT_DULINT_EQ(trx->id, node->trx_id)) { + /* No need to do IX-locking */ + + goto same_trx; + } + + err = lock_table(0, node->table, LOCK_IX, thr); + + if (err != DB_SUCCESS) { + + goto error_handling; + } + + node->trx_id = trx->id; +same_trx: + node->state = INS_NODE_ALLOC_ROW_ID; + + if (node->ins_type == INS_SEARCHED) { + /* Reset the cursor */ + sel_node->state = SEL_NODE_OPEN; + + /* Fetch a row to insert */ + + thr->run_node = sel_node; + + return(thr); + } + } + + if ((node->ins_type == INS_SEARCHED) + && (sel_node->state != SEL_NODE_FETCH)) { + + ut_ad(sel_node->state == SEL_NODE_NO_MORE_ROWS); + + /* No more rows to insert */ + thr->run_node = parent; + + return(thr); + } + + /* DO THE CHECKS OF THE CONSISTENCY CONSTRAINTS HERE */ + + err = row_ins(node, thr); + +error_handling: + trx->error_state = err; + + if (err != DB_SUCCESS) { + /* err == DB_LOCK_WAIT or SQL error detected */ + return(NULL); + } + + /* DO THE TRIGGER ACTIONS HERE */ + + if (node->ins_type == INS_SEARCHED) { + /* Fetch a row to insert */ + + thr->run_node = sel_node; + } else { + thr->run_node = que_node_get_parent(node); + } + + return(thr); +} diff --git a/storage/xtradb/row/row0merge.c b/storage/xtradb/row/row0merge.c new file mode 100644 index 00000000000..efed3d26e5b --- /dev/null +++ b/storage/xtradb/row/row0merge.c @@ -0,0 +1,2358 @@ +/***************************************************************************** + +Copyright (c) 2005, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/****************************************************** +New index creation routines using a merge sort + +Created 12/4/2005 Jan Lindstrom +Completed by Sunny Bains and Marko Makela +*******************************************************/ + +#include "row0merge.h" +#include "row0ext.h" +#include "row0row.h" +#include "row0upd.h" +#include "row0ins.h" +#include "row0sel.h" +#include "dict0dict.h" +#include "dict0mem.h" +#include "dict0boot.h" +#include "dict0crea.h" +#include "dict0load.h" +#include "btr0btr.h" +#include "mach0data.h" +#include "trx0rseg.h" +#include "trx0trx.h" +#include "trx0roll.h" +#include "trx0undo.h" +#include "trx0purge.h" +#include "trx0rec.h" +#include "que0que.h" +#include "rem0cmp.h" +#include "read0read.h" +#include "os0file.h" +#include "lock0lock.h" +#include "data0data.h" +#include "data0type.h" +#include "que0que.h" +#include "pars0pars.h" +#include "mem0mem.h" +#include "log0log.h" +#include "ut0sort.h" +#include "handler0alter.h" + +#ifdef UNIV_DEBUG +/* Set these in order ot enable debug printout. */ +static ibool row_merge_print_cmp; +static ibool row_merge_print_read; +static ibool row_merge_print_write; +#endif /* UNIV_DEBUG */ + +/* Block size for I/O operations in merge sort. The minimum is +UNIV_PAGE_SIZE, or page_get_free_space_of_empty() rounded to a power of 2. + +When not creating a PRIMARY KEY that contains column prefixes, this +can be set as small as UNIV_PAGE_SIZE / 2. See the comment above +ut_ad(data_size < sizeof(row_merge_block_t)). */ + +typedef byte row_merge_block_t[1048576]; + +/* Secondary buffer for I/O operations of merge records. This buffer +is used for writing or reading a record that spans two row_merge_block_t. +Thus, it must be able to hold one merge record, whose maximum size is +the same as the minimum size of row_merge_block_t. */ + +typedef byte mrec_buf_t[UNIV_PAGE_SIZE]; + +/* Merge record in row_merge_block_t. The format is the same as a +record in ROW_FORMAT=COMPACT with the exception that the +REC_N_NEW_EXTRA_BYTES are omitted. */ +typedef byte mrec_t; + +/* Buffer for sorting in main memory. */ +struct row_merge_buf_struct { + mem_heap_t* heap; /* memory heap where allocated */ + dict_index_t* index; /* the index the tuples belong to */ + ulint total_size; /* total amount of data bytes */ + ulint n_tuples; /* number of data tuples */ + ulint max_tuples; /* maximum number of data tuples */ + const dfield_t**tuples; /* array of pointers to + arrays of fields that form + the data tuples */ + const dfield_t**tmp_tuples; /* temporary copy of tuples, + for sorting */ +}; + +typedef struct row_merge_buf_struct row_merge_buf_t; + +/* Information about temporary files used in merge sort are stored +to this structure */ + +struct merge_file_struct { + int fd; /* File descriptor */ + ulint offset; /* File offset */ +}; + +typedef struct merge_file_struct merge_file_t; + +#ifdef UNIV_DEBUG +/********************************************************** +Display a merge tuple. */ +static +void +row_merge_tuple_print( +/*==================*/ + FILE* f, /* in: output stream */ + const dfield_t* entry, /* in: tuple to print */ + ulint n_fields)/* in: number of fields in the tuple */ +{ + ulint j; + + for (j = 0; j < n_fields; j++) { + const dfield_t* field = &entry[j]; + + if (dfield_is_null(field)) { + fputs("\n NULL;", f); + } else { + ulint field_len = dfield_get_len(field); + ulint len = ut_min(field_len, 20); + if (dfield_is_ext(field)) { + fputs("\nE", f); + } else { + fputs("\n ", f); + } + ut_print_buf(f, dfield_get_data(field), len); + if (len != field_len) { + fprintf(f, " (total %lu bytes)", field_len); + } + } + } + putc('\n', f); +} +#endif /* UNIV_DEBUG */ + +/********************************************************** +Allocate a sort buffer. */ +static +row_merge_buf_t* +row_merge_buf_create_low( +/*=====================*/ + /* out,own: sort buffer */ + mem_heap_t* heap, /* in: heap where allocated */ + dict_index_t* index, /* in: secondary index */ + ulint max_tuples, /* in: maximum number of data tuples */ + ulint buf_size) /* in: size of the buffer, in bytes */ +{ + row_merge_buf_t* buf; + + ut_ad(max_tuples > 0); + ut_ad(max_tuples <= sizeof(row_merge_block_t)); + ut_ad(max_tuples < buf_size); + + buf = mem_heap_zalloc(heap, buf_size); + buf->heap = heap; + buf->index = index; + buf->max_tuples = max_tuples; + buf->tuples = mem_heap_alloc(heap, + 2 * max_tuples * sizeof *buf->tuples); + buf->tmp_tuples = buf->tuples + max_tuples; + + return(buf); +} + +/********************************************************** +Allocate a sort buffer. */ +static +row_merge_buf_t* +row_merge_buf_create( +/*=================*/ + /* out,own: sort buffer */ + dict_index_t* index) /* in: secondary index */ +{ + row_merge_buf_t* buf; + ulint max_tuples; + ulint buf_size; + mem_heap_t* heap; + + max_tuples = sizeof(row_merge_block_t) + / ut_max(1, dict_index_get_min_size(index)); + + buf_size = (sizeof *buf) + (max_tuples - 1) * sizeof *buf->tuples; + + heap = mem_heap_create(buf_size + sizeof(row_merge_block_t)); + + buf = row_merge_buf_create_low(heap, index, max_tuples, buf_size); + + return(buf); +} + +/********************************************************** +Empty a sort buffer. */ +static +row_merge_buf_t* +row_merge_buf_empty( +/*================*/ + /* out: sort buffer */ + row_merge_buf_t* buf) /* in,own: sort buffer */ +{ + ulint buf_size; + ulint max_tuples = buf->max_tuples; + mem_heap_t* heap = buf->heap; + dict_index_t* index = buf->index; + + buf_size = (sizeof *buf) + (max_tuples - 1) * sizeof *buf->tuples; + + mem_heap_empty(heap); + + return(row_merge_buf_create_low(heap, index, max_tuples, buf_size)); +} + +/********************************************************** +Deallocate a sort buffer. */ +static +void +row_merge_buf_free( +/*===============*/ + row_merge_buf_t* buf) /* in,own: sort buffer, to be freed */ +{ + mem_heap_free(buf->heap); +} + +/********************************************************** +Insert a data tuple into a sort buffer. */ +static +ibool +row_merge_buf_add( +/*==============*/ + /* out: TRUE if added, + FALSE if out of space */ + row_merge_buf_t* buf, /* in/out: sort buffer */ + const dtuple_t* row, /* in: row in clustered index */ + const row_ext_t* ext) /* in: cache of externally stored + column prefixes, or NULL */ +{ + ulint i; + ulint n_fields; + ulint data_size; + ulint extra_size; + const dict_index_t* index; + dfield_t* entry; + dfield_t* field; + + if (buf->n_tuples >= buf->max_tuples) { + return(FALSE); + } + + UNIV_PREFETCH_R(row->fields); + + index = buf->index; + + n_fields = dict_index_get_n_fields(index); + + entry = mem_heap_alloc(buf->heap, n_fields * sizeof *entry); + buf->tuples[buf->n_tuples] = entry; + field = entry; + + data_size = 0; + extra_size = UT_BITS_IN_BYTES(index->n_nullable); + + for (i = 0; i < n_fields; i++, field++) { + const dict_field_t* ifield; + const dict_col_t* col; + ulint col_no; + const dfield_t* row_field; + ulint len; + + ifield = dict_index_get_nth_field(index, i); + col = ifield->col; + col_no = dict_col_get_no(col); + row_field = dtuple_get_nth_field(row, col_no); + dfield_copy(field, row_field); + len = dfield_get_len(field); + + if (dfield_is_null(field)) { + ut_ad(!(col->prtype & DATA_NOT_NULL)); + continue; + } else if (UNIV_LIKELY(!ext)) { + } else if (dict_index_is_clust(index)) { + /* Flag externally stored fields. */ + const byte* buf = row_ext_lookup(ext, col_no, + &len); + if (UNIV_LIKELY_NULL(buf)) { + ut_a(buf != field_ref_zero); + if (i < dict_index_get_n_unique(index)) { + dfield_set_data(field, buf, len); + } else { + dfield_set_ext(field); + len = dfield_get_len(field); + } + } + } else { + const byte* buf = row_ext_lookup(ext, col_no, + &len); + if (UNIV_LIKELY_NULL(buf)) { + ut_a(buf != field_ref_zero); + dfield_set_data(field, buf, len); + } + } + + /* If a column prefix index, take only the prefix */ + + if (ifield->prefix_len) { + len = dtype_get_at_most_n_mbchars( + col->prtype, + col->mbminlen, col->mbmaxlen, + ifield->prefix_len, + len, dfield_get_data(field)); + dfield_set_len(field, len); + } + + ut_ad(len <= col->len || col->mtype == DATA_BLOB); + + if (ifield->fixed_len) { + ut_ad(len == ifield->fixed_len); + ut_ad(!dfield_is_ext(field)); + } else if (dfield_is_ext(field)) { + extra_size += 2; + } else if (len < 128 + || (col->len < 256 && col->mtype != DATA_BLOB)) { + extra_size++; + } else { + /* For variable-length columns, we look up the + maximum length from the column itself. If this + is a prefix index column shorter than 256 bytes, + this will waste one byte. */ + extra_size += 2; + } + data_size += len; + } + +#ifdef UNIV_DEBUG + { + ulint size; + ulint extra; + + size = rec_get_converted_size_comp(index, + REC_STATUS_ORDINARY, + entry, n_fields, &extra); + + ut_ad(data_size + extra_size + REC_N_NEW_EXTRA_BYTES == size); + ut_ad(extra_size + REC_N_NEW_EXTRA_BYTES == extra); + } +#endif /* UNIV_DEBUG */ + + /* Add to the total size of the record in row_merge_block_t + the encoded length of extra_size and the extra bytes (extra_size). + See row_merge_buf_write() for the variable-length encoding + of extra_size. */ + data_size += (extra_size + 1) + ((extra_size + 1) >= 0x80); + + /* The following assertion may fail if row_merge_block_t is + declared very small and a PRIMARY KEY is being created with + many prefix columns. In that case, the record may exceed the + page_zip_rec_needs_ext() limit. However, no further columns + will be moved to external storage until the record is inserted + to the clustered index B-tree. */ + ut_ad(data_size < sizeof(row_merge_block_t)); + + /* Reserve one byte for the end marker of row_merge_block_t. */ + if (buf->total_size + data_size >= sizeof(row_merge_block_t) - 1) { + return(FALSE); + } + + buf->total_size += data_size; + buf->n_tuples++; + + field = entry; + + /* Copy the data fields. */ + + do { + dfield_dup(field++, buf->heap); + } while (--n_fields); + + return(TRUE); +} + +/* Structure for reporting duplicate records. */ +struct row_merge_dup_struct { + const dict_index_t* index; /* index being sorted */ + TABLE* table; /* MySQL table object */ + ulint n_dup; /* number of duplicates */ +}; + +typedef struct row_merge_dup_struct row_merge_dup_t; + +/***************************************************************** +Report a duplicate key. */ +static +void +row_merge_dup_report( +/*=================*/ + row_merge_dup_t* dup, /* in/out: for reporting duplicates */ + const dfield_t* entry) /* in: duplicate index entry */ +{ + mrec_buf_t buf; + const dtuple_t* tuple; + dtuple_t tuple_store; + const rec_t* rec; + const dict_index_t* index = dup->index; + ulint n_fields= dict_index_get_n_fields(index); + mem_heap_t* heap = NULL; + ulint offsets_[REC_OFFS_NORMAL_SIZE]; + ulint* offsets; + ulint n_ext; + + if (dup->n_dup++) { + /* Only report the first duplicate record, + but count all duplicate records. */ + return; + } + + rec_offs_init(offsets_); + + /* Convert the tuple to a record and then to MySQL format. */ + + tuple = dtuple_from_fields(&tuple_store, entry, n_fields); + n_ext = dict_index_is_clust(index) ? dtuple_get_n_ext(tuple) : 0; + + rec = rec_convert_dtuple_to_rec(buf, index, tuple, n_ext); + offsets = rec_get_offsets(rec, index, offsets_, ULINT_UNDEFINED, + &heap); + + innobase_rec_to_mysql(dup->table, rec, index, offsets); + + if (UNIV_LIKELY_NULL(heap)) { + mem_heap_free(heap); + } +} + +/***************************************************************** +Compare two tuples. */ +static +int +row_merge_tuple_cmp( +/*================*/ + /* out: 1, 0, -1 if a is greater, + equal, less, respectively, than b */ + ulint n_field,/* in: number of fields */ + const dfield_t* a, /* in: first tuple to be compared */ + const dfield_t* b, /* in: second tuple to be compared */ + row_merge_dup_t* dup) /* in/out: for reporting duplicates */ +{ + int cmp; + const dfield_t* field = a; + + /* Compare the fields of the tuples until a difference is + found or we run out of fields to compare. If !cmp at the + end, the tuples are equal. */ + do { + cmp = cmp_dfield_dfield(a++, b++); + } while (!cmp && --n_field); + + if (UNIV_UNLIKELY(!cmp) && UNIV_LIKELY_NULL(dup)) { + /* Report a duplicate value error if the tuples are + logically equal. NULL columns are logically inequal, + although they are equal in the sorting order. Find + out if any of the fields are NULL. */ + for (b = field; b != a; b++) { + if (dfield_is_null(b)) { + + goto func_exit; + } + } + + row_merge_dup_report(dup, field); + } + +func_exit: + return(cmp); +} + +/************************************************************************** +Merge sort the tuple buffer in main memory. */ +static +void +row_merge_tuple_sort( +/*=================*/ + ulint n_field,/* in: number of fields */ + row_merge_dup_t* dup, /* in/out: for reporting duplicates */ + const dfield_t** tuples, /* in/out: tuples */ + const dfield_t** aux, /* in/out: work area */ + ulint low, /* in: lower bound of the + sorting area, inclusive */ + ulint high) /* in: upper bound of the + sorting area, exclusive */ +{ +#define row_merge_tuple_sort_ctx(a,b,c,d) \ + row_merge_tuple_sort(n_field, dup, a, b, c, d) +#define row_merge_tuple_cmp_ctx(a,b) row_merge_tuple_cmp(n_field, a, b, dup) + + UT_SORT_FUNCTION_BODY(row_merge_tuple_sort_ctx, + tuples, aux, low, high, row_merge_tuple_cmp_ctx); +} + +/********************************************************** +Sort a buffer. */ +static +void +row_merge_buf_sort( +/*===============*/ + row_merge_buf_t* buf, /* in/out: sort buffer */ + row_merge_dup_t* dup) /* in/out: for reporting duplicates */ +{ + row_merge_tuple_sort(dict_index_get_n_unique(buf->index), dup, + buf->tuples, buf->tmp_tuples, 0, buf->n_tuples); +} + +/********************************************************** +Write a buffer to a block. */ +static +void +row_merge_buf_write( +/*================*/ + const row_merge_buf_t* buf, /* in: sorted buffer */ +#ifdef UNIV_DEBUG + const merge_file_t* of, /* in: output file */ +#endif /* UNIV_DEBUG */ + row_merge_block_t* block) /* out: buffer for writing to file */ +#ifndef UNIV_DEBUG +# define row_merge_buf_write(buf, of, block) row_merge_buf_write(buf, block) +#endif /* !UNIV_DEBUG */ +{ + const dict_index_t* index = buf->index; + ulint n_fields= dict_index_get_n_fields(index); + byte* b = &(*block)[0]; + + ulint i; + + for (i = 0; i < buf->n_tuples; i++) { + ulint size; + ulint extra_size; + const dfield_t* entry = buf->tuples[i]; + + size = rec_get_converted_size_comp(index, + REC_STATUS_ORDINARY, + entry, n_fields, + &extra_size); + ut_ad(size > extra_size); + ut_ad(extra_size >= REC_N_NEW_EXTRA_BYTES); + extra_size -= REC_N_NEW_EXTRA_BYTES; + size -= REC_N_NEW_EXTRA_BYTES; + + /* Encode extra_size + 1 */ + if (extra_size + 1 < 0x80) { + *b++ = (byte) (extra_size + 1); + } else { + ut_ad((extra_size + 1) < 0x8000); + *b++ = (byte) (0x80 | ((extra_size + 1) >> 8)); + *b++ = (byte) (extra_size + 1); + } + + ut_ad(b + size < block[1]); + + rec_convert_dtuple_to_rec_comp(b + extra_size, 0, index, + REC_STATUS_ORDINARY, + entry, n_fields); + + b += size; + +#ifdef UNIV_DEBUG + if (row_merge_print_write) { + fprintf(stderr, "row_merge_buf_write %p,%d,%lu %lu", + (void*) b, of->fd, (ulong) of->offset, + (ulong) i); + row_merge_tuple_print(stderr, entry, n_fields); + } +#endif /* UNIV_DEBUG */ + } + + /* Write an "end-of-chunk" marker. */ + ut_a(b < block[1]); + ut_a(b == block[0] + buf->total_size); + *b++ = 0; +#ifdef UNIV_DEBUG_VALGRIND + /* The rest of the block is uninitialized. Initialize it + to avoid bogus warnings. */ + memset(b, 0xff, block[1] - b); +#endif /* UNIV_DEBUG_VALGRIND */ +#ifdef UNIV_DEBUG + if (row_merge_print_write) { + fprintf(stderr, "row_merge_buf_write %p,%d,%lu EOF\n", + (void*) b, of->fd, (ulong) of->offset); + } +#endif /* UNIV_DEBUG */ +} + +/********************************************************** +Create a memory heap and allocate space for row_merge_rec_offsets(). */ +static +mem_heap_t* +row_merge_heap_create( +/*==================*/ + /* out: memory heap */ + const dict_index_t* index, /* in: record descriptor */ + ulint** offsets1, /* out: offsets */ + ulint** offsets2) /* out: offsets */ +{ + ulint i = 1 + REC_OFFS_HEADER_SIZE + + dict_index_get_n_fields(index); + mem_heap_t* heap = mem_heap_create(2 * i * sizeof *offsets1); + + *offsets1 = mem_heap_alloc(heap, i * sizeof *offsets1); + *offsets2 = mem_heap_alloc(heap, i * sizeof *offsets2); + + (*offsets1)[0] = (*offsets2)[0] = i; + (*offsets1)[1] = (*offsets2)[1] = dict_index_get_n_fields(index); + + return(heap); +} + +/************************************************************************** +Search an index object by name and column names. If several indexes match, +return the index with the max id. */ +static +dict_index_t* +row_merge_dict_table_get_index( +/*===========================*/ + /* out: matching index, + NULL if not found */ + dict_table_t* table, /* in: table */ + const merge_index_def_t*index_def) /* in: index definition */ +{ + ulint i; + dict_index_t* index; + const char** column_names; + + column_names = mem_alloc(index_def->n_fields * sizeof *column_names); + + for (i = 0; i < index_def->n_fields; ++i) { + column_names[i] = index_def->fields[i].field_name; + } + + index = dict_table_get_index_by_max_id( + table, index_def->name, column_names, index_def->n_fields); + + mem_free((void*) column_names); + + return(index); +} + +/************************************************************************ +Read a merge block from the file system. */ +static +ibool +row_merge_read( +/*===========*/ + /* out: TRUE if request was + successful, FALSE if fail */ + int fd, /* in: file descriptor */ + ulint offset, /* in: offset where to read */ + row_merge_block_t* buf) /* out: data */ +{ + ib_uint64_t ofs = ((ib_uint64_t) offset) * sizeof *buf; + ibool success; + + success = os_file_read_no_error_handling(OS_FILE_FROM_FD(fd), buf, + (ulint) (ofs & 0xFFFFFFFF), + (ulint) (ofs >> 32), + sizeof *buf); + if (UNIV_UNLIKELY(!success)) { + ut_print_timestamp(stderr); + fprintf(stderr, + " InnoDB: failed to read merge block at %llu\n", ofs); + } + + return(UNIV_LIKELY(success)); +} + +/************************************************************************ +Read a merge block from the file system. */ +static +ibool +row_merge_write( +/*============*/ + /* out: TRUE if request was + successful, FALSE if fail */ + int fd, /* in: file descriptor */ + ulint offset, /* in: offset where to write */ + const void* buf) /* in: data */ +{ + ib_uint64_t ofs = ((ib_uint64_t) offset) + * sizeof(row_merge_block_t); + + return(UNIV_LIKELY(os_file_write("(merge)", OS_FILE_FROM_FD(fd), buf, + (ulint) (ofs & 0xFFFFFFFF), + (ulint) (ofs >> 32), + sizeof(row_merge_block_t)))); +} + +/************************************************************************ +Read a merge record. */ +static +const byte* +row_merge_read_rec( +/*===============*/ + /* out: pointer to next record, + or NULL on I/O error + or end of list */ + row_merge_block_t* block, /* in/out: file buffer */ + mrec_buf_t* buf, /* in/out: secondary buffer */ + const byte* b, /* in: pointer to record */ + const dict_index_t* index, /* in: index of the record */ + int fd, /* in: file descriptor */ + ulint* foffs, /* in/out: file offset */ + const mrec_t** mrec, /* out: pointer to merge record, + or NULL on end of list + (non-NULL on I/O error) */ + ulint* offsets)/* out: offsets of mrec */ +{ + ulint extra_size; + ulint data_size; + ulint avail_size; + + ut_ad(block); + ut_ad(buf); + ut_ad(b >= block[0]); + ut_ad(b < block[1]); + ut_ad(index); + ut_ad(foffs); + ut_ad(mrec); + ut_ad(offsets); + + ut_ad(*offsets == 1 + REC_OFFS_HEADER_SIZE + + dict_index_get_n_fields(index)); + + extra_size = *b++; + + if (UNIV_UNLIKELY(!extra_size)) { + /* End of list */ + *mrec = NULL; +#ifdef UNIV_DEBUG + if (row_merge_print_read) { + fprintf(stderr, "row_merge_read %p,%p,%d,%lu EOF\n", + (const void*) b, (const void*) block, + fd, (ulong) *foffs); + } +#endif /* UNIV_DEBUG */ + return(NULL); + } + + if (extra_size >= 0x80) { + /* Read another byte of extra_size. */ + + if (UNIV_UNLIKELY(b >= block[1])) { + if (!row_merge_read(fd, ++(*foffs), block)) { +err_exit: + /* Signal I/O error. */ + *mrec = b; + return(NULL); + } + + /* Wrap around to the beginning of the buffer. */ + b = block[0]; + } + + extra_size = (extra_size & 0x7f) << 8; + extra_size |= *b++; + } + + /* Normalize extra_size. Above, value 0 signals "end of list". */ + extra_size--; + + /* Read the extra bytes. */ + + if (UNIV_UNLIKELY(b + extra_size >= block[1])) { + /* The record spans two blocks. Copy the entire record + to the auxiliary buffer and handle this as a special + case. */ + + avail_size = block[1] - b; + + memcpy(*buf, b, avail_size); + + if (!row_merge_read(fd, ++(*foffs), block)) { + + goto err_exit; + } + + /* Wrap around to the beginning of the buffer. */ + b = block[0]; + + /* Copy the record. */ + memcpy(*buf + avail_size, b, extra_size - avail_size); + b += extra_size - avail_size; + + *mrec = *buf + extra_size; + + rec_init_offsets_comp_ordinary(*mrec, 0, index, offsets); + + data_size = rec_offs_data_size(offsets); + + /* These overflows should be impossible given that + records are much smaller than either buffer, and + the record starts near the beginning of each buffer. */ + ut_a(extra_size + data_size < sizeof *buf); + ut_a(b + data_size < block[1]); + + /* Copy the data bytes. */ + memcpy(*buf + extra_size, b, data_size); + b += data_size; + + goto func_exit; + } + + *mrec = b + extra_size; + + rec_init_offsets_comp_ordinary(*mrec, 0, index, offsets); + + data_size = rec_offs_data_size(offsets); + ut_ad(extra_size + data_size < sizeof *buf); + + b += extra_size + data_size; + + if (UNIV_LIKELY(b < block[1])) { + /* The record fits entirely in the block. + This is the normal case. */ + goto func_exit; + } + + /* The record spans two blocks. Copy it to buf. */ + + b -= extra_size + data_size; + avail_size = block[1] - b; + memcpy(*buf, b, avail_size); + *mrec = *buf + extra_size; + rec_offs_make_valid(*mrec, index, offsets); + + if (!row_merge_read(fd, ++(*foffs), block)) { + + goto err_exit; + } + + /* Wrap around to the beginning of the buffer. */ + b = block[0]; + + /* Copy the rest of the record. */ + memcpy(*buf + avail_size, b, extra_size + data_size - avail_size); + b += extra_size + data_size - avail_size; + +func_exit: +#ifdef UNIV_DEBUG + if (row_merge_print_read) { + fprintf(stderr, "row_merge_read %p,%p,%d,%lu ", + (const void*) b, (const void*) block, + fd, (ulong) *foffs); + rec_print_comp(stderr, *mrec, offsets); + putc('\n', stderr); + } +#endif /* UNIV_DEBUG */ + + return(b); +} + +/************************************************************************ +Write a merge record. */ +static +void +row_merge_write_rec_low( +/*====================*/ + byte* b, /* out: buffer */ + ulint e, /* in: encoded extra_size */ +#ifdef UNIV_DEBUG + ulint size, /* in: total size to write */ + int fd, /* in: file descriptor */ + ulint foffs, /* in: file offset */ +#endif /* UNIV_DEBUG */ + const mrec_t* mrec, /* in: record to write */ + const ulint* offsets)/* in: offsets of mrec */ +#ifndef UNIV_DEBUG +# define row_merge_write_rec_low(b, e, size, fd, foffs, mrec, offsets) \ + row_merge_write_rec_low(b, e, mrec, offsets) +#endif /* !UNIV_DEBUG */ +{ +#ifdef UNIV_DEBUG + const byte* const end = b + size; + ut_ad(e == rec_offs_extra_size(offsets) + 1); + + if (row_merge_print_write) { + fprintf(stderr, "row_merge_write %p,%d,%lu ", + (void*) b, fd, (ulong) foffs); + rec_print_comp(stderr, mrec, offsets); + putc('\n', stderr); + } +#endif /* UNIV_DEBUG */ + + if (e < 0x80) { + *b++ = (byte) e; + } else { + *b++ = (byte) (0x80 | (e >> 8)); + *b++ = (byte) e; + } + + memcpy(b, mrec - rec_offs_extra_size(offsets), rec_offs_size(offsets)); + ut_ad(b + rec_offs_size(offsets) == end); +} + +/************************************************************************ +Write a merge record. */ +static +byte* +row_merge_write_rec( +/*================*/ + /* out: pointer to end of block, + or NULL on error */ + row_merge_block_t* block, /* in/out: file buffer */ + mrec_buf_t* buf, /* in/out: secondary buffer */ + byte* b, /* in: pointer to end of block */ + int fd, /* in: file descriptor */ + ulint* foffs, /* in/out: file offset */ + const mrec_t* mrec, /* in: record to write */ + const ulint* offsets)/* in: offsets of mrec */ +{ + ulint extra_size; + ulint size; + ulint avail_size; + + ut_ad(block); + ut_ad(buf); + ut_ad(b >= block[0]); + ut_ad(b < block[1]); + ut_ad(mrec); + ut_ad(foffs); + ut_ad(mrec < block[0] || mrec > block[1]); + ut_ad(mrec < buf[0] || mrec > buf[1]); + + /* Normalize extra_size. Value 0 signals "end of list". */ + extra_size = rec_offs_extra_size(offsets) + 1; + + size = extra_size + (extra_size >= 0x80) + + rec_offs_data_size(offsets); + + if (UNIV_UNLIKELY(b + size >= block[1])) { + /* The record spans two blocks. + Copy it to the temporary buffer first. */ + avail_size = block[1] - b; + + row_merge_write_rec_low(buf[0], + extra_size, size, fd, *foffs, + mrec, offsets); + + /* Copy the head of the temporary buffer, write + the completed block, and copy the tail of the + record to the head of the new block. */ + memcpy(b, buf[0], avail_size); + + if (!row_merge_write(fd, (*foffs)++, block)) { + return(NULL); + } + + UNIV_MEM_INVALID(block[0], sizeof block[0]); + + /* Copy the rest. */ + b = block[0]; + memcpy(b, buf[0] + avail_size, size - avail_size); + b += size - avail_size; + } else { + row_merge_write_rec_low(b, extra_size, size, fd, *foffs, + mrec, offsets); + b += size; + } + + return(b); +} + +/************************************************************************ +Write an end-of-list marker. */ +static +byte* +row_merge_write_eof( +/*================*/ + /* out: pointer to end of block, + or NULL on error */ + row_merge_block_t* block, /* in/out: file buffer */ + byte* b, /* in: pointer to end of block */ + int fd, /* in: file descriptor */ + ulint* foffs) /* in/out: file offset */ +{ + ut_ad(block); + ut_ad(b >= block[0]); + ut_ad(b < block[1]); + ut_ad(foffs); +#ifdef UNIV_DEBUG + if (row_merge_print_write) { + fprintf(stderr, "row_merge_write %p,%p,%d,%lu EOF\n", + (void*) b, (void*) block, fd, (ulong) *foffs); + } +#endif /* UNIV_DEBUG */ + + *b++ = 0; + UNIV_MEM_ASSERT_RW(block[0], b - block[0]); + UNIV_MEM_ASSERT_W(block[0], sizeof block[0]); +#ifdef UNIV_DEBUG_VALGRIND + /* The rest of the block is uninitialized. Initialize it + to avoid bogus warnings. */ + memset(b, 0xff, block[1] - b); +#endif /* UNIV_DEBUG_VALGRIND */ + + if (!row_merge_write(fd, (*foffs)++, block)) { + return(NULL); + } + + UNIV_MEM_INVALID(block[0], sizeof block[0]); + return(block[0]); +} + +/***************************************************************** +Compare two merge records. */ +static +int +row_merge_cmp( +/*==========*/ + /* out: 1, 0, -1 if + mrec1 is greater, equal, less, + respectively, than mrec2 */ + const mrec_t* mrec1, /* in: first merge + record to be compared */ + const mrec_t* mrec2, /* in: second merge + record to be compared */ + const ulint* offsets1, /* in: first record offsets */ + const ulint* offsets2, /* in: second record offsets */ + const dict_index_t* index) /* in: index */ +{ + int cmp; + + cmp = cmp_rec_rec_simple(mrec1, mrec2, offsets1, offsets2, index); + +#ifdef UNIV_DEBUG + if (row_merge_print_cmp) { + fputs("row_merge_cmp1 ", stderr); + rec_print_comp(stderr, mrec1, offsets1); + fputs("\nrow_merge_cmp2 ", stderr); + rec_print_comp(stderr, mrec2, offsets2); + fprintf(stderr, "\nrow_merge_cmp=%d\n", cmp); + } +#endif /* UNIV_DEBUG */ + + return(cmp); +} + +/************************************************************************ +Reads clustered index of the table and create temporary files +containing the index entries for the indexes to be built. */ +static +ulint +row_merge_read_clustered_index( +/*===========================*/ + /* out: DB_SUCCESS or error */ + trx_t* trx, /* in: transaction */ + TABLE* table, /* in/out: MySQL table object, + for reporting erroneous records */ + const dict_table_t* old_table,/* in: table where rows are + read from */ + const dict_table_t* new_table,/* in: table where indexes are + created; identical to old_table + unless creating a PRIMARY KEY */ + dict_index_t** index, /* in: indexes to be created */ + merge_file_t* files, /* in: temporary files */ + ulint n_index,/* in: number of indexes to create */ + row_merge_block_t* block) /* in/out: file buffer */ +{ + dict_index_t* clust_index; /* Clustered index */ + mem_heap_t* row_heap; /* Heap memory to create + clustered index records */ + row_merge_buf_t** merge_buf; /* Temporary list for records*/ + btr_pcur_t pcur; /* Persistent cursor on the + clustered index */ + mtr_t mtr; /* Mini transaction */ + ulint err = DB_SUCCESS;/* Return code */ + ulint i; + ulint n_nonnull = 0; /* number of columns + changed to NOT NULL */ + ulint* nonnull = NULL; /* NOT NULL columns */ + + trx->op_info = "reading clustered index"; + + ut_ad(trx); + ut_ad(old_table); + ut_ad(new_table); + ut_ad(index); + ut_ad(files); + + /* Create and initialize memory for record buffers */ + + merge_buf = mem_alloc(n_index * sizeof *merge_buf); + + for (i = 0; i < n_index; i++) { + merge_buf[i] = row_merge_buf_create(index[i]); + } + + mtr_start(&mtr); + + /* Find the clustered index and create a persistent cursor + based on that. */ + + clust_index = dict_table_get_first_index(old_table); + + btr_pcur_open_at_index_side( + TRUE, clust_index, BTR_SEARCH_LEAF, &pcur, TRUE, &mtr); + + if (UNIV_UNLIKELY(old_table != new_table)) { + ulint n_cols = dict_table_get_n_cols(old_table); + + /* A primary key will be created. Identify the + columns that were flagged NOT NULL in the new table, + so that we can quickly check that the records in the + (old) clustered index do not violate the added NOT + NULL constraints. */ + + ut_a(n_cols == dict_table_get_n_cols(new_table)); + + nonnull = mem_alloc(n_cols * sizeof *nonnull); + + for (i = 0; i < n_cols; i++) { + if (dict_table_get_nth_col(old_table, i)->prtype + & DATA_NOT_NULL) { + + continue; + } + + if (dict_table_get_nth_col(new_table, i)->prtype + & DATA_NOT_NULL) { + + nonnull[n_nonnull++] = i; + } + } + + if (!n_nonnull) { + mem_free(nonnull); + nonnull = NULL; + } + } + + row_heap = mem_heap_create(sizeof(mrec_buf_t)); + + /* Scan the clustered index. */ + for (;;) { + const rec_t* rec; + ulint* offsets; + dtuple_t* row = NULL; + row_ext_t* ext; + ibool has_next = TRUE; + + btr_pcur_move_to_next_on_page(&pcur); + + /* When switching pages, commit the mini-transaction + in order to release the latch on the old page. */ + + if (btr_pcur_is_after_last_on_page(&pcur)) { + btr_pcur_store_position(&pcur, &mtr); + mtr_commit(&mtr); + mtr_start(&mtr); + btr_pcur_restore_position(BTR_SEARCH_LEAF, + &pcur, &mtr); + has_next = btr_pcur_move_to_next_user_rec(&pcur, &mtr); + } + + if (UNIV_LIKELY(has_next)) { + rec = btr_pcur_get_rec(&pcur); + offsets = rec_get_offsets(rec, clust_index, NULL, + ULINT_UNDEFINED, &row_heap); + + /* Skip delete marked records. */ + if (rec_get_deleted_flag( + rec, dict_table_is_comp(old_table))) { + continue; + } + + srv_n_rows_inserted++; + + /* Build a row based on the clustered index. */ + + row = row_build(ROW_COPY_POINTERS, clust_index, + rec, offsets, + new_table, &ext, row_heap); + + if (UNIV_LIKELY_NULL(nonnull)) { + for (i = 0; i < n_nonnull; i++) { + dfield_t* field + = &row->fields[nonnull[i]]; + dtype_t* field_type + = dfield_get_type(field); + + ut_a(!(field_type->prtype + & DATA_NOT_NULL)); + + if (dfield_is_null(field)) { + err = DB_PRIMARY_KEY_IS_NULL; + i = 0; + goto err_exit; + } + + field_type->prtype |= DATA_NOT_NULL; + } + } + } + + /* Build all entries for all the indexes to be created + in a single scan of the clustered index. */ + + for (i = 0; i < n_index; i++) { + row_merge_buf_t* buf = merge_buf[i]; + merge_file_t* file = &files[i]; + const dict_index_t* index = buf->index; + + if (UNIV_LIKELY + (row && row_merge_buf_add(buf, row, ext))) { + continue; + } + + /* The buffer must be sufficiently large + to hold at least one record. */ + ut_ad(buf->n_tuples || !has_next); + + /* We have enough data tuples to form a block. + Sort them and write to disk. */ + + if (buf->n_tuples) { + if (dict_index_is_unique(index)) { + row_merge_dup_t dup; + dup.index = buf->index; + dup.table = table; + dup.n_dup = 0; + + row_merge_buf_sort(buf, &dup); + + if (dup.n_dup) { + err = DB_DUPLICATE_KEY; +err_exit: + trx->error_key_num = i; + goto func_exit; + } + } else { + row_merge_buf_sort(buf, NULL); + } + } + + row_merge_buf_write(buf, file, block); + + if (!row_merge_write(file->fd, file->offset++, + block)) { + err = DB_OUT_OF_FILE_SPACE; + goto err_exit; + } + + UNIV_MEM_INVALID(block[0], sizeof block[0]); + merge_buf[i] = row_merge_buf_empty(buf); + + /* Try writing the record again, now that + the buffer has been written out and emptied. */ + + if (UNIV_UNLIKELY + (row && !row_merge_buf_add(buf, row, ext))) { + /* An empty buffer should have enough + room for at least one record. */ + ut_error; + } + } + + mem_heap_empty(row_heap); + + if (UNIV_UNLIKELY(!has_next)) { + goto func_exit; + } + } + +func_exit: + btr_pcur_close(&pcur); + mtr_commit(&mtr); + mem_heap_free(row_heap); + + if (UNIV_LIKELY_NULL(nonnull)) { + mem_free(nonnull); + } + + for (i = 0; i < n_index; i++) { + row_merge_buf_free(merge_buf[i]); + } + + mem_free(merge_buf); + + trx->op_info = ""; + + return(err); +} + +/***************************************************************** +Merge two blocks of linked lists on disk and write a bigger block. */ +static +ulint +row_merge_blocks( +/*=============*/ + /* out: DB_SUCCESS or error code */ + const dict_index_t* index, /* in: index being created */ + merge_file_t* file, /* in/out: file containing + index entries */ + row_merge_block_t* block, /* in/out: 3 buffers */ + ulint* foffs0, /* in/out: offset of first + source list in the file */ + ulint* foffs1, /* in/out: offset of second + source list in the file */ + merge_file_t* of, /* in/out: output file */ + TABLE* table) /* in/out: MySQL table, for + reporting erroneous key value + if applicable */ +{ + mem_heap_t* heap; /* memory heap for offsets0, offsets1 */ + + mrec_buf_t buf[3]; /* buffer for handling split mrec in block[] */ + const byte* b0; /* pointer to block[0] */ + const byte* b1; /* pointer to block[1] */ + byte* b2; /* pointer to block[2] */ + const mrec_t* mrec0; /* merge rec, points to block[0] or buf[0] */ + const mrec_t* mrec1; /* merge rec, points to block[1] or buf[1] */ + ulint* offsets0;/* offsets of mrec0 */ + ulint* offsets1;/* offsets of mrec1 */ + + heap = row_merge_heap_create(index, &offsets0, &offsets1); + + /* Write a record and read the next record. Split the output + file in two halves, which can be merged on the following pass. */ +#define ROW_MERGE_WRITE_GET_NEXT(N, AT_END) \ + do { \ + b2 = row_merge_write_rec(&block[2], &buf[2], b2, \ + of->fd, &of->offset, \ + mrec##N, offsets##N); \ + if (UNIV_UNLIKELY(!b2)) { \ + goto corrupt; \ + } \ + b##N = row_merge_read_rec(&block[N], &buf[N], \ + b##N, index, \ + file->fd, foffs##N, \ + &mrec##N, offsets##N); \ + if (UNIV_UNLIKELY(!b##N)) { \ + if (mrec##N) { \ + goto corrupt; \ + } \ + AT_END; \ + } \ + } while (0) + + if (!row_merge_read(file->fd, *foffs0, &block[0]) + || !row_merge_read(file->fd, *foffs1, &block[1])) { +corrupt: + mem_heap_free(heap); + return(DB_CORRUPTION); + } + + b0 = block[0]; + b1 = block[1]; + b2 = block[2]; + + b0 = row_merge_read_rec(&block[0], &buf[0], b0, index, file->fd, + foffs0, &mrec0, offsets0); + b1 = row_merge_read_rec(&block[1], &buf[1], b1, index, file->fd, + foffs1, &mrec1, offsets1); + if (UNIV_UNLIKELY(!b0 && mrec0) + || UNIV_UNLIKELY(!b1 && mrec1)) { + + goto corrupt; + } + + while (mrec0 && mrec1) { + switch (row_merge_cmp(mrec0, mrec1, + offsets0, offsets1, index)) { + case 0: + if (UNIV_UNLIKELY + (dict_index_is_unique(index))) { + innobase_rec_to_mysql(table, mrec0, + index, offsets0); + mem_heap_free(heap); + return(DB_DUPLICATE_KEY); + } + /* fall through */ + case -1: + ROW_MERGE_WRITE_GET_NEXT(0, goto merged); + break; + case 1: + ROW_MERGE_WRITE_GET_NEXT(1, goto merged); + break; + default: + ut_error; + } + + } + +merged: + if (mrec0) { + /* append all mrec0 to output */ + for (;;) { + ROW_MERGE_WRITE_GET_NEXT(0, goto done0); + } + } +done0: + if (mrec1) { + /* append all mrec1 to output */ + for (;;) { + ROW_MERGE_WRITE_GET_NEXT(1, goto done1); + } + } +done1: + + mem_heap_free(heap); + b2 = row_merge_write_eof(&block[2], b2, of->fd, &of->offset); + return(b2 ? DB_SUCCESS : DB_CORRUPTION); +} + +/***************************************************************** +Merge disk files. */ +static +ulint +row_merge( +/*======*/ + /* out: DB_SUCCESS or error code */ + const dict_index_t* index, /* in: index being created */ + merge_file_t* file, /* in/out: file containing + index entries */ + ulint half, /* in: half the file */ + row_merge_block_t* block, /* in/out: 3 buffers */ + int* tmpfd, /* in/out: temporary file handle */ + TABLE* table) /* in/out: MySQL table, for + reporting erroneous key value + if applicable */ +{ + ulint foffs0; /* first input offset */ + ulint foffs1; /* second input offset */ + ulint error; /* error code */ + merge_file_t of; /* output file */ + + UNIV_MEM_ASSERT_W(block[0], 3 * sizeof block[0]); + ut_ad(half > 0); + + of.fd = *tmpfd; + of.offset = 0; + + /* Merge blocks to the output file. */ + foffs0 = 0; + foffs1 = half; + + for (; foffs0 < half && foffs1 < file->offset; foffs0++, foffs1++) { + error = row_merge_blocks(index, file, block, + &foffs0, &foffs1, &of, table); + + if (error != DB_SUCCESS) { + return(error); + } + } + + /* Copy the last block, if there is one. */ + while (foffs0 < half) { + if (!row_merge_read(file->fd, foffs0++, block) + || !row_merge_write(of.fd, of.offset++, block)) { + return(DB_CORRUPTION); + } + } + while (foffs1 < file->offset) { + if (!row_merge_read(file->fd, foffs1++, block) + || !row_merge_write(of.fd, of.offset++, block)) { + return(DB_CORRUPTION); + } + } + + /* Swap file descriptors for the next pass. */ + *tmpfd = file->fd; + *file = of; + + UNIV_MEM_INVALID(block[0], 3 * sizeof block[0]); + + return(DB_SUCCESS); +} + +/***************************************************************** +Merge disk files. */ +static +ulint +row_merge_sort( +/*===========*/ + /* out: DB_SUCCESS or error code */ + const dict_index_t* index, /* in: index being created */ + merge_file_t* file, /* in/out: file containing + index entries */ + row_merge_block_t* block, /* in/out: 3 buffers */ + int* tmpfd, /* in/out: temporary file handle */ + TABLE* table) /* in/out: MySQL table, for + reporting erroneous key value + if applicable */ +{ + ulint blksz; /* block size */ + + for (blksz = 1; blksz < file->offset; blksz *= 2) { + ulint half; + ulint error; + + ut_ad(ut_is_2pow(blksz)); + half = ut_2pow_round((file->offset + (blksz - 1)) / 2, blksz); + error = row_merge(index, file, half, block, tmpfd, table); + + if (error != DB_SUCCESS) { + return(error); + } + } + + return(DB_SUCCESS); +} + +/***************************************************************** +Copy externally stored columns to the data tuple. */ +static +void +row_merge_copy_blobs( +/*=================*/ + const mrec_t* mrec, /* in: merge record */ + const ulint* offsets,/* in: offsets of mrec */ + ulint zip_size,/* in: compressed page size in bytes, or 0 */ + dtuple_t* tuple, /* in/out: data tuple */ + mem_heap_t* heap) /* in/out: memory heap */ +{ + ulint i; + ulint n_fields = dtuple_get_n_fields(tuple); + + for (i = 0; i < n_fields; i++) { + ulint len; + const void* data; + dfield_t* field = dtuple_get_nth_field(tuple, i); + + if (!dfield_is_ext(field)) { + continue; + } + + ut_ad(!dfield_is_null(field)); + + /* The table is locked during index creation. + Therefore, externally stored columns cannot possibly + be freed between the time the BLOB pointers are read + (row_merge_read_clustered_index()) and dereferenced + (below). */ + data = btr_rec_copy_externally_stored_field( + mrec, offsets, zip_size, i, &len, heap); + + dfield_set_data(field, data, len); + } +} + +/************************************************************************ +Read sorted file containing index data tuples and insert these data +tuples to the index */ +static +ulint +row_merge_insert_index_tuples( +/*==========================*/ + /* out: DB_SUCCESS or error number */ + trx_t* trx, /* in: transaction */ + dict_index_t* index, /* in: index */ + dict_table_t* table, /* in: new table */ + ulint zip_size,/* in: compressed page size of + the old table, or 0 if uncompressed */ + int fd, /* in: file descriptor */ + row_merge_block_t* block) /* in/out: file buffer */ +{ + mrec_buf_t buf; + const byte* b; + que_thr_t* thr; + ins_node_t* node; + mem_heap_t* tuple_heap; + mem_heap_t* graph_heap; + ulint error = DB_SUCCESS; + ulint foffs = 0; + ulint* offsets; + + ut_ad(trx); + ut_ad(index); + ut_ad(table); + + /* We use the insert query graph as the dummy graph + needed in the row module call */ + + trx->op_info = "inserting index entries"; + + graph_heap = mem_heap_create(500); + node = ins_node_create(INS_DIRECT, table, graph_heap); + + thr = pars_complete_graph_for_exec(node, trx, graph_heap); + + que_thr_move_to_run_state_for_mysql(thr, trx); + + tuple_heap = mem_heap_create(1000); + + { + ulint i = 1 + REC_OFFS_HEADER_SIZE + + dict_index_get_n_fields(index); + offsets = mem_heap_alloc(graph_heap, i * sizeof *offsets); + offsets[0] = i; + offsets[1] = dict_index_get_n_fields(index); + } + + b = *block; + + if (!row_merge_read(fd, foffs, block)) { + error = DB_CORRUPTION; + } else { + for (;;) { + const mrec_t* mrec; + dtuple_t* dtuple; + ulint n_ext; + + b = row_merge_read_rec(block, &buf, b, index, + fd, &foffs, &mrec, offsets); + if (UNIV_UNLIKELY(!b)) { + /* End of list, or I/O error */ + if (mrec) { + error = DB_CORRUPTION; + } + break; + } + + dtuple = row_rec_to_index_entry_low( + mrec, index, offsets, &n_ext, tuple_heap); + + if (UNIV_UNLIKELY(n_ext)) { + row_merge_copy_blobs(mrec, offsets, zip_size, + dtuple, tuple_heap); + } + + node->row = dtuple; + node->table = table; + node->trx_id = trx->id; + + ut_ad(dtuple_validate(dtuple)); + + do { + thr->run_node = thr; + thr->prev_node = thr->common.parent; + + error = row_ins_index_entry(index, dtuple, + 0, FALSE, thr); + + if (UNIV_LIKELY(error == DB_SUCCESS)) { + + goto next_rec; + } + + thr->lock_state = QUE_THR_LOCK_ROW; + trx->error_state = error; + que_thr_stop_for_mysql(thr); + thr->lock_state = QUE_THR_LOCK_NOLOCK; + } while (row_mysql_handle_errors(&error, trx, + thr, NULL)); + + goto err_exit; +next_rec: + mem_heap_empty(tuple_heap); + } + } + + que_thr_stop_for_mysql_no_error(thr, trx); +err_exit: + que_graph_free(thr->graph); + + trx->op_info = ""; + + mem_heap_free(tuple_heap); + + return(error); +} + +/************************************************************************* +Sets an exclusive lock on a table, for the duration of creating indexes. */ +UNIV_INTERN +ulint +row_merge_lock_table( +/*=================*/ + /* out: error code or DB_SUCCESS */ + trx_t* trx, /* in/out: transaction */ + dict_table_t* table, /* in: table to lock */ + enum lock_mode mode) /* in: LOCK_X or LOCK_S */ +{ + mem_heap_t* heap; + que_thr_t* thr; + ulint err; + sel_node_t* node; + + ut_ad(trx); + ut_ad(trx->mysql_thread_id == os_thread_get_curr_id()); + ut_ad(mode == LOCK_X || mode == LOCK_S); + + heap = mem_heap_create(512); + + trx->op_info = "setting table lock for creating or dropping index"; + + node = sel_node_create(heap); + thr = pars_complete_graph_for_exec(node, trx, heap); + thr->graph->state = QUE_FORK_ACTIVE; + + /* We use the select query graph as the dummy graph needed + in the lock module call */ + + thr = que_fork_get_first_thr(que_node_get_parent(thr)); + que_thr_move_to_run_state_for_mysql(thr, trx); + +run_again: + thr->run_node = thr; + thr->prev_node = thr->common.parent; + + err = lock_table(0, table, mode, thr); + + trx->error_state = err; + + if (UNIV_LIKELY(err == DB_SUCCESS)) { + que_thr_stop_for_mysql_no_error(thr, trx); + } else { + que_thr_stop_for_mysql(thr); + + if (err != DB_QUE_THR_SUSPENDED) { + ibool was_lock_wait; + + was_lock_wait = row_mysql_handle_errors( + &err, trx, thr, NULL); + + if (was_lock_wait) { + goto run_again; + } + } else { + que_thr_t* run_thr; + que_node_t* parent; + + parent = que_node_get_parent(thr); + run_thr = que_fork_start_command(parent); + + ut_a(run_thr == thr); + + /* There was a lock wait but the thread was not + in a ready to run or running state. */ + trx->error_state = DB_LOCK_WAIT; + + goto run_again; + } + } + + que_graph_free(thr->graph); + trx->op_info = ""; + + return(err); +} + +/************************************************************************* +Drop an index from the InnoDB system tables. The data dictionary must +have been locked exclusively by the caller, because the transaction +will not be committed. */ +UNIV_INTERN +void +row_merge_drop_index( +/*=================*/ + dict_index_t* index, /* in: index to be removed */ + dict_table_t* table, /* in: table */ + trx_t* trx) /* in: transaction handle */ +{ + ulint err; + pars_info_t* info = pars_info_create(); + + /* We use the private SQL parser of Innobase to generate the + query graphs needed in deleting the dictionary data from system + tables in Innobase. Deleting a row from SYS_INDEXES table also + frees the file segments of the B-tree associated with the index. */ + + static const char str1[] = + "PROCEDURE DROP_INDEX_PROC () IS\n" + "BEGIN\n" + "DELETE FROM SYS_FIELDS WHERE INDEX_ID = :indexid;\n" + "DELETE FROM SYS_INDEXES WHERE ID = :indexid\n" + " AND TABLE_ID = :tableid;\n" + "END;\n"; + + ut_ad(index && table && trx); + + pars_info_add_dulint_literal(info, "indexid", index->id); + pars_info_add_dulint_literal(info, "tableid", table->id); + + trx_start_if_not_started(trx); + trx->op_info = "dropping index"; + + ut_a(trx->dict_operation_lock_mode == RW_X_LATCH); + + err = que_eval_sql(info, str1, FALSE, trx); + + ut_a(err == DB_SUCCESS); + + /* Replace this index with another equivalent index for all + foreign key constraints on this table where this index is used */ + + dict_table_replace_index_in_foreign_list(table, index); + dict_index_remove_from_cache(table, index); + + trx->op_info = ""; +} + +/************************************************************************* +Drop those indexes which were created before an error occurred when +building an index. The data dictionary must have been locked +exclusively by the caller, because the transaction will not be +committed. */ +UNIV_INTERN +void +row_merge_drop_indexes( +/*===================*/ + trx_t* trx, /* in: transaction */ + dict_table_t* table, /* in: table containing the indexes */ + dict_index_t** index, /* in: indexes to drop */ + ulint num_created) /* in: number of elements in index[] */ +{ + ulint key_num; + + for (key_num = 0; key_num < num_created; key_num++) { + row_merge_drop_index(index[key_num], table, trx); + } +} + +/************************************************************************* +Drop all partially created indexes during crash recovery. */ +UNIV_INTERN +void +row_merge_drop_temp_indexes(void) +/*=============================*/ +{ + trx_t* trx; + ulint err; + + /* We use the private SQL parser of Innobase to generate the + query graphs needed in deleting the dictionary data from system + tables in Innobase. Deleting a row from SYS_INDEXES table also + frees the file segments of the B-tree associated with the index. */ +#if TEMP_INDEX_PREFIX != '\377' +# error "TEMP_INDEX_PREFIX != '\377'" +#endif + static const char drop_temp_indexes[] = + "PROCEDURE DROP_TEMP_INDEXES_PROC () IS\n" + "indexid CHAR;\n" + "DECLARE CURSOR c IS SELECT ID FROM SYS_INDEXES\n" + "WHERE SUBSTR(NAME,0,1)='\377';\n" + "BEGIN\n" + "\tOPEN c;\n" + "\tWHILE 1=1 LOOP\n" + "\t\tFETCH c INTO indexid;\n" + "\t\tIF (SQL % NOTFOUND) THEN\n" + "\t\t\tEXIT;\n" + "\t\tEND IF;\n" + "\t\tDELETE FROM SYS_FIELDS WHERE INDEX_ID = indexid;\n" + "\t\tDELETE FROM SYS_INDEXES WHERE ID = indexid;\n" + "\tEND LOOP;\n" + "\tCLOSE c;\n" + "\tCOMMIT WORK;\n" + "END;\n"; + + trx = trx_allocate_for_background(); + trx->op_info = "dropping partially created indexes"; + row_mysql_lock_data_dictionary(trx); + + /* Incomplete transactions may be holding some locks on the + data dictionary tables. However, they should never have been + able to lock the records corresponding to the partially + created indexes that we are attempting to delete, because the + table was locked when the indexes were being created. We will + drop the partially created indexes before the rollback of + incomplete transactions is initiated. Thus, this should not + interfere with the incomplete transactions. */ + trx->isolation_level = TRX_ISO_READ_UNCOMMITTED; + err = que_eval_sql(NULL, drop_temp_indexes, FALSE, trx); + ut_a(err == DB_SUCCESS); + + row_mysql_unlock_data_dictionary(trx); + trx_free_for_background(trx); +} + +/************************************************************************* +Create a merge file. */ +static +void +row_merge_file_create( +/*==================*/ + merge_file_t* merge_file) /* out: merge file structure */ +{ + merge_file->fd = innobase_mysql_tmpfile(); + merge_file->offset = 0; +} + +/************************************************************************* +Destroy a merge file. */ +static +void +row_merge_file_destroy( +/*===================*/ + merge_file_t* merge_file) /* out: merge file structure */ +{ + if (merge_file->fd != -1) { + close(merge_file->fd); + merge_file->fd = -1; + } +} + +/************************************************************************* +Determine the precise type of a column that is added to a tem +if a column must be constrained NOT NULL. */ +UNIV_INLINE +ulint +row_merge_col_prtype( +/*=================*/ + /* out: col->prtype, possibly + ORed with DATA_NOT_NULL */ + const dict_col_t* col, /* in: column */ + const char* col_name, /* in: name of the column */ + const merge_index_def_t*index_def) /* in: the index definition + of the primary key */ +{ + ulint prtype = col->prtype; + ulint i; + + ut_ad(index_def->ind_type & DICT_CLUSTERED); + + if (prtype & DATA_NOT_NULL) { + + return(prtype); + } + + /* All columns that are included + in the PRIMARY KEY must be NOT NULL. */ + + for (i = 0; i < index_def->n_fields; i++) { + if (!strcmp(col_name, index_def->fields[i].field_name)) { + return(prtype | DATA_NOT_NULL); + } + } + + return(prtype); +} + +/************************************************************************* +Create a temporary table for creating a primary key, using the definition +of an existing table. */ +UNIV_INTERN +dict_table_t* +row_merge_create_temporary_table( +/*=============================*/ + /* out: table, + or NULL on error */ + const char* table_name, /* in: new table name */ + const merge_index_def_t*index_def, /* in: the index definition + of the primary key */ + const dict_table_t* table, /* in: old table definition */ + trx_t* trx) /* in/out: transaction + (sets error_state) */ +{ + ulint i; + dict_table_t* new_table = NULL; + ulint n_cols = dict_table_get_n_user_cols(table); + ulint error; + mem_heap_t* heap = mem_heap_create(1000); + + ut_ad(table_name); + ut_ad(index_def); + ut_ad(table); + ut_ad(mutex_own(&dict_sys->mutex)); + + new_table = dict_mem_table_create(table_name, 0, n_cols, table->flags); + + for (i = 0; i < n_cols; i++) { + const dict_col_t* col; + const char* col_name; + + col = dict_table_get_nth_col(table, i); + col_name = dict_table_get_col_name(table, i); + + dict_mem_table_add_col(new_table, heap, col_name, col->mtype, + row_merge_col_prtype(col, col_name, + index_def), + col->len); + } + + error = row_create_table_for_mysql(new_table, trx); + mem_heap_free(heap); + + if (error != DB_SUCCESS) { + trx->error_state = error; + new_table = NULL; + } + + return(new_table); +} + +/************************************************************************* +Rename the temporary indexes in the dictionary to permanent ones. The +data dictionary must have been locked exclusively by the caller, +because the transaction will not be committed. */ +UNIV_INTERN +ulint +row_merge_rename_indexes( +/*=====================*/ + /* out: DB_SUCCESS if all OK */ + trx_t* trx, /* in/out: transaction */ + dict_table_t* table) /* in/out: table with new indexes */ +{ + ulint err = DB_SUCCESS; + pars_info_t* info = pars_info_create(); + + /* We use the private SQL parser of Innobase to generate the + query graphs needed in renaming indexes. */ + +#if TEMP_INDEX_PREFIX != '\377' +# error "TEMP_INDEX_PREFIX != '\377'" +#endif + + static const char rename_indexes[] = + "PROCEDURE RENAME_INDEXES_PROC () IS\n" + "BEGIN\n" + "UPDATE SYS_INDEXES SET NAME=SUBSTR(NAME,1,LENGTH(NAME)-1)\n" + "WHERE TABLE_ID = :tableid AND SUBSTR(NAME,0,1)='\377';\n" + "END;\n"; + + ut_ad(table); + ut_ad(trx); + ut_a(trx->dict_operation_lock_mode == RW_X_LATCH); + + trx->op_info = "renaming indexes"; + + pars_info_add_dulint_literal(info, "tableid", table->id); + + err = que_eval_sql(info, rename_indexes, FALSE, trx); + + if (err == DB_SUCCESS) { + dict_index_t* index = dict_table_get_first_index(table); + do { + if (*index->name == TEMP_INDEX_PREFIX) { + index->name++; + } + index = dict_table_get_next_index(index); + } while (index); + } + + trx->op_info = ""; + + return(err); +} + +/************************************************************************* +Rename the tables in the data dictionary. The data dictionary must +have been locked exclusively by the caller, because the transaction +will not be committed. */ +UNIV_INTERN +ulint +row_merge_rename_tables( +/*====================*/ + /* out: error code or DB_SUCCESS */ + dict_table_t* old_table, /* in/out: old table, renamed to + tmp_name */ + dict_table_t* new_table, /* in/out: new table, renamed to + old_table->name */ + const char* tmp_name, /* in: new name for old_table */ + trx_t* trx) /* in: transaction handle */ +{ + ulint err = DB_ERROR; + pars_info_t* info; + const char* old_name= old_table->name; + + ut_ad(trx->mysql_thread_id == os_thread_get_curr_id()); + ut_ad(old_table != new_table); + ut_ad(mutex_own(&dict_sys->mutex)); + + ut_a(trx->dict_operation_lock_mode == RW_X_LATCH); + + trx->op_info = "renaming tables"; + + /* We use the private SQL parser of Innobase to generate the query + graphs needed in updating the dictionary data in system tables. */ + + info = pars_info_create(); + + pars_info_add_str_literal(info, "new_name", new_table->name); + pars_info_add_str_literal(info, "old_name", old_name); + pars_info_add_str_literal(info, "tmp_name", tmp_name); + + err = que_eval_sql(info, + "PROCEDURE RENAME_TABLES () IS\n" + "BEGIN\n" + "UPDATE SYS_TABLES SET NAME = :tmp_name\n" + " WHERE NAME = :old_name;\n" + "UPDATE SYS_TABLES SET NAME = :old_name\n" + " WHERE NAME = :new_name;\n" + "END;\n", FALSE, trx); + + if (err != DB_SUCCESS) { + + goto err_exit; + } + + /* The following calls will also rename the .ibd data files if + the tables are stored in a single-table tablespace */ + + if (!dict_table_rename_in_cache(old_table, tmp_name, FALSE) + || !dict_table_rename_in_cache(new_table, old_name, FALSE)) { + + err = DB_ERROR; + goto err_exit; + } + + err = dict_load_foreigns(old_name, TRUE); + + if (err != DB_SUCCESS) { +err_exit: + trx->error_state = DB_SUCCESS; + trx_general_rollback_for_mysql(trx, FALSE, NULL); + trx->error_state = DB_SUCCESS; + } + + trx->op_info = ""; + + return(err); +} + +/************************************************************************* +Create and execute a query graph for creating an index. */ +static +ulint +row_merge_create_index_graph( +/*=========================*/ + /* out: DB_SUCCESS or error code */ + trx_t* trx, /* in: trx */ + dict_table_t* table, /* in: table */ + dict_index_t* index) /* in: index */ +{ + ind_node_t* node; /* Index creation node */ + mem_heap_t* heap; /* Memory heap */ + que_thr_t* thr; /* Query thread */ + ulint err; + + ut_ad(trx); + ut_ad(table); + ut_ad(index); + + heap = mem_heap_create(512); + + index->table = table; + node = ind_create_graph_create(index, heap); + thr = pars_complete_graph_for_exec(node, trx, heap); + + ut_a(thr == que_fork_start_command(que_node_get_parent(thr))); + + que_run_threads(thr); + + err = trx->error_state; + + que_graph_free((que_t*) que_node_get_parent(thr)); + + return(err); +} + +/************************************************************************* +Create the index and load in to the dictionary. */ +UNIV_INTERN +dict_index_t* +row_merge_create_index( +/*===================*/ + /* out: index, or NULL on error */ + trx_t* trx, /* in/out: trx (sets error_state) */ + dict_table_t* table, /* in: the index is on this table */ + const merge_index_def_t* /* in: the index definition */ + index_def) +{ + dict_index_t* index; + ulint err; + ulint n_fields = index_def->n_fields; + ulint i; + + /* Create the index prototype, using the passed in def, this is not + a persistent operation. We pass 0 as the space id, and determine at + a lower level the space id where to store the table. */ + + index = dict_mem_index_create(table->name, index_def->name, + 0, index_def->ind_type, n_fields); + + ut_a(index); + + for (i = 0; i < n_fields; i++) { + merge_index_field_t* ifield = &index_def->fields[i]; + + dict_mem_index_add_field(index, ifield->field_name, + ifield->prefix_len); + } + + /* Add the index to SYS_INDEXES, using the index prototype. */ + err = row_merge_create_index_graph(trx, table, index); + + if (err == DB_SUCCESS) { + + index = row_merge_dict_table_get_index( + table, index_def); + + ut_a(index); + +#ifdef ROW_MERGE_IS_INDEX_USABLE + /* Note the id of the transaction that created this + index, we use it to restrict readers from accessing + this index, to ensure read consistency. */ + index->trx_id = trx->id; +#endif /* ROW_MERGE_IS_INDEX_USABLE */ + } else { + index = NULL; + } + + return(index); +} + +#ifdef ROW_MERGE_IS_INDEX_USABLE +/************************************************************************* +Check if a transaction can use an index. */ +UNIV_INTERN +ibool +row_merge_is_index_usable( +/*======================*/ + const trx_t* trx, /* in: transaction */ + const dict_index_t* index) /* in: index to check */ +{ + if (!trx->read_view) { + return(TRUE); + } + + return(ut_dulint_cmp(index->trx_id, trx->read_view->low_limit_id) < 0); +} +#endif /* ROW_MERGE_IS_INDEX_USABLE */ + +/************************************************************************* +Drop the old table. */ +UNIV_INTERN +ulint +row_merge_drop_table( +/*=================*/ + /* out: DB_SUCCESS or error code */ + trx_t* trx, /* in: transaction */ + dict_table_t* table) /* in: table to drop */ +{ + /* There must be no open transactions on the table. */ + ut_a(table->n_mysql_handles_opened == 0); + + return(row_drop_table_for_mysql(table->name, trx, FALSE)); +} + +/************************************************************************* +Build indexes on a table by reading a clustered index, +creating a temporary file containing index entries, merge sorting +these index entries and inserting sorted index entries to indexes. */ +UNIV_INTERN +ulint +row_merge_build_indexes( +/*====================*/ + /* out: DB_SUCCESS or error code */ + trx_t* trx, /* in: transaction */ + dict_table_t* old_table, /* in: table where rows are + read from */ + dict_table_t* new_table, /* in: table where indexes are + created; identical to old_table + unless creating a PRIMARY KEY */ + dict_index_t** indexes, /* in: indexes to be created */ + ulint n_indexes, /* in: size of indexes[] */ + TABLE* table) /* in/out: MySQL table, for + reporting erroneous key value + if applicable */ +{ + merge_file_t* merge_files; + row_merge_block_t* block; + ulint block_size; + ulint i; + ulint error; + int tmpfd; + + ut_ad(trx); + ut_ad(old_table); + ut_ad(new_table); + ut_ad(indexes); + ut_ad(n_indexes); + + trx_start_if_not_started(trx); + + /* Allocate memory for merge file data structure and initialize + fields */ + + merge_files = mem_alloc(n_indexes * sizeof *merge_files); + block_size = 3 * sizeof *block; + block = os_mem_alloc_large(&block_size); + + for (i = 0; i < n_indexes; i++) { + + row_merge_file_create(&merge_files[i]); + } + + tmpfd = innobase_mysql_tmpfile(); + + /* Reset the MySQL row buffer that is used when reporting + duplicate keys. */ + innobase_rec_reset(table); + + /* Read clustered index of the table and create files for + secondary index entries for merge sort */ + + error = row_merge_read_clustered_index( + trx, table, old_table, new_table, indexes, + merge_files, n_indexes, block); + + if (error != DB_SUCCESS) { + + goto func_exit; + } + + /* Now we have files containing index entries ready for + sorting and inserting. */ + + for (i = 0; i < n_indexes; i++) { + error = row_merge_sort(indexes[i], &merge_files[i], + block, &tmpfd, table); + + if (error == DB_SUCCESS) { + error = row_merge_insert_index_tuples( + trx, indexes[i], new_table, + dict_table_zip_size(old_table), + merge_files[i].fd, block); + } + + /* Close the temporary file to free up space. */ + row_merge_file_destroy(&merge_files[i]); + + if (error != DB_SUCCESS) { + trx->error_key_num = i; + goto func_exit; + } + } + +func_exit: + close(tmpfd); + + for (i = 0; i < n_indexes; i++) { + row_merge_file_destroy(&merge_files[i]); + } + + mem_free(merge_files); + os_mem_free_large(block, block_size); + + return(error); +} diff --git a/storage/xtradb/row/row0mysql.c b/storage/xtradb/row/row0mysql.c new file mode 100644 index 00000000000..7acc330113b --- /dev/null +++ b/storage/xtradb/row/row0mysql.c @@ -0,0 +1,4212 @@ +/***************************************************************************** + +Copyright (c) 2000, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/****************************************************** +Interface between Innobase row operations and MySQL. +Contains also create table and other data dictionary operations. + +Created 9/17/2000 Heikki Tuuri +*******************************************************/ + +#include "row0mysql.h" + +#ifdef UNIV_NONINL +#include "row0mysql.ic" +#endif + +#include "row0ins.h" +#include "row0sel.h" +#include "row0upd.h" +#include "row0row.h" +#include "que0que.h" +#include "pars0pars.h" +#include "dict0dict.h" +#include "dict0crea.h" +#include "dict0load.h" +#include "dict0boot.h" +#include "trx0roll.h" +#include "trx0purge.h" +#include "trx0rec.h" +#include "trx0undo.h" +#include "lock0lock.h" +#include "rem0cmp.h" +#include "log0log.h" +#include "btr0sea.h" +#include "fil0fil.h" +#include "ibuf0ibuf.h" + +/* Provide optional 4.x backwards compatibility for 5.0 and above */ +UNIV_INTERN ibool row_rollback_on_timeout = FALSE; + +/* List of tables we should drop in background. ALTER TABLE in MySQL requires +that the table handler can drop the table in background when there are no +queries to it any more. Protected by the kernel mutex. */ +typedef struct row_mysql_drop_struct row_mysql_drop_t; +struct row_mysql_drop_struct{ + char* table_name; + UT_LIST_NODE_T(row_mysql_drop_t) row_mysql_drop_list; +}; + +static UT_LIST_BASE_NODE_T(row_mysql_drop_t) row_mysql_drop_list; +static ibool row_mysql_drop_list_inited = FALSE; + +/* Magic table names for invoking various monitor threads */ +static const char S_innodb_monitor[] = "innodb_monitor"; +static const char S_innodb_lock_monitor[] = "innodb_lock_monitor"; +static const char S_innodb_tablespace_monitor[] = "innodb_tablespace_monitor"; +static const char S_innodb_table_monitor[] = "innodb_table_monitor"; +static const char S_innodb_mem_validate[] = "innodb_mem_validate"; + +/* Evaluates to true if str1 equals str2_onstack, used for comparing +the above strings. */ +#define STR_EQ(str1, str1_len, str2_onstack) \ + ((str1_len) == sizeof(str2_onstack) \ + && memcmp(str1, str2_onstack, sizeof(str2_onstack)) == 0) + +#ifndef UNIV_HOTBACKUP +/*********************************************************************** +Determine if the given name is a name reserved for MySQL system tables. */ +static +ibool +row_mysql_is_system_table( +/*======================*/ + /* out: TRUE if name is a MySQL + system table name */ + const char* name) +{ + if (strncmp(name, "mysql/", 6) != 0) { + + return(FALSE); + } + + return(0 == strcmp(name + 6, "host") + || 0 == strcmp(name + 6, "user") + || 0 == strcmp(name + 6, "db")); +} +#endif /* !UNIV_HOTBACKUP */ + +/************************************************************************* +If a table is not yet in the drop list, adds the table to the list of tables +which the master thread drops in background. We need this on Unix because in +ALTER TABLE MySQL may call drop table even if the table has running queries on +it. Also, if there are running foreign key checks on the table, we drop the +table lazily. */ +static +ibool +row_add_table_to_background_drop_list( +/*==================================*/ + /* out: TRUE if the table was not yet in the + drop list, and was added there */ + const char* name); /* in: table name */ + +/*********************************************************************** +Delays an INSERT, DELETE or UPDATE operation if the purge is lagging. */ +static +void +row_mysql_delay_if_needed(void) +/*===========================*/ +{ + if (srv_dml_needed_delay) { + os_thread_sleep(srv_dml_needed_delay); + } +} + +/*********************************************************************** +Frees the blob heap in prebuilt when no longer needed. */ +UNIV_INTERN +void +row_mysql_prebuilt_free_blob_heap( +/*==============================*/ + row_prebuilt_t* prebuilt) /* in: prebuilt struct of a + ha_innobase:: table handle */ +{ + mem_heap_free(prebuilt->blob_heap); + prebuilt->blob_heap = NULL; +} + +/*********************************************************************** +Stores a >= 5.0.3 format true VARCHAR length to dest, in the MySQL row +format. */ +UNIV_INTERN +byte* +row_mysql_store_true_var_len( +/*=========================*/ + /* out: pointer to the data, we skip the 1 or 2 bytes + at the start that are used to store the len */ + byte* dest, /* in: where to store */ + ulint len, /* in: length, must fit in two bytes */ + ulint lenlen) /* in: storage length of len: either 1 or 2 bytes */ +{ + if (lenlen == 2) { + ut_a(len < 256 * 256); + + mach_write_to_2_little_endian(dest, len); + + return(dest + 2); + } + + ut_a(lenlen == 1); + ut_a(len < 256); + + mach_write_to_1(dest, len); + + return(dest + 1); +} + +/*********************************************************************** +Reads a >= 5.0.3 format true VARCHAR length, in the MySQL row format, and +returns a pointer to the data. */ +UNIV_INTERN +const byte* +row_mysql_read_true_varchar( +/*========================*/ + /* out: pointer to the data, we skip + the 1 or 2 bytes at the start that are + used to store the len */ + ulint* len, /* out: variable-length field length */ + const byte* field, /* in: field in the MySQL format */ + ulint lenlen) /* in: storage length of len: either 1 + or 2 bytes */ +{ + if (lenlen == 2) { + *len = mach_read_from_2_little_endian(field); + + return(field + 2); + } + + ut_a(lenlen == 1); + + *len = mach_read_from_1(field); + + return(field + 1); +} + +/*********************************************************************** +Stores a reference to a BLOB in the MySQL format. */ +UNIV_INTERN +void +row_mysql_store_blob_ref( +/*=====================*/ + byte* dest, /* in: where to store */ + ulint col_len,/* in: dest buffer size: determines into + how many bytes the BLOB length is stored, + the space for the length may vary from 1 + to 4 bytes */ + const void* data, /* in: BLOB data; if the value to store + is SQL NULL this should be NULL pointer */ + ulint len) /* in: BLOB length; if the value to store + is SQL NULL this should be 0; remember + also to set the NULL bit in the MySQL record + header! */ +{ + /* MySQL might assume the field is set to zero except the length and + the pointer fields */ + + memset(dest, '\0', col_len); + + /* In dest there are 1 - 4 bytes reserved for the BLOB length, + and after that 8 bytes reserved for the pointer to the data. + In 32-bit architectures we only use the first 4 bytes of the pointer + slot. */ + + ut_a(col_len - 8 > 1 || len < 256); + ut_a(col_len - 8 > 2 || len < 256 * 256); + ut_a(col_len - 8 > 3 || len < 256 * 256 * 256); + + mach_write_to_n_little_endian(dest, col_len - 8, len); + + memcpy(dest + col_len - 8, &data, sizeof data); +} + +/*********************************************************************** +Reads a reference to a BLOB in the MySQL format. */ +UNIV_INTERN +const byte* +row_mysql_read_blob_ref( +/*====================*/ + /* out: pointer to BLOB data */ + ulint* len, /* out: BLOB length */ + const byte* ref, /* in: BLOB reference in the + MySQL format */ + ulint col_len) /* in: BLOB reference length + (not BLOB length) */ +{ + byte* data; + + *len = mach_read_from_n_little_endian(ref, col_len - 8); + + memcpy(&data, ref + col_len - 8, sizeof data); + + return(data); +} + +/****************************************************************** +Stores a non-SQL-NULL field given in the MySQL format in the InnoDB format. +The counterpart of this function is row_sel_field_store_in_mysql_format() in +row0sel.c. */ +UNIV_INTERN +byte* +row_mysql_store_col_in_innobase_format( +/*===================================*/ + /* out: up to which byte we used + buf in the conversion */ + dfield_t* dfield, /* in/out: dfield where dtype + information must be already set when + this function is called! */ + byte* buf, /* in/out: buffer for a converted + integer value; this must be at least + col_len long then! */ + ibool row_format_col, /* TRUE if the mysql_data is from + a MySQL row, FALSE if from a MySQL + key value; + in MySQL, a true VARCHAR storage + format differs in a row and in a + key value: in a key value the length + is always stored in 2 bytes! */ + const byte* mysql_data, /* in: MySQL column value, not + SQL NULL; NOTE that dfield may also + get a pointer to mysql_data, + therefore do not discard this as long + as dfield is used! */ + ulint col_len, /* in: MySQL column length; NOTE that + this is the storage length of the + column in the MySQL format row, not + necessarily the length of the actual + payload data; if the column is a true + VARCHAR then this is irrelevant */ + ulint comp) /* in: nonzero=compact format */ +{ + const byte* ptr = mysql_data; + const dtype_t* dtype; + ulint type; + ulint lenlen; + + dtype = dfield_get_type(dfield); + + type = dtype->mtype; + + if (type == DATA_INT) { + /* Store integer data in Innobase in a big-endian format, + sign bit negated if the data is a signed integer. In MySQL, + integers are stored in a little-endian format. */ + + byte* p = buf + col_len; + + for (;;) { + p--; + *p = *mysql_data; + if (p == buf) { + break; + } + mysql_data++; + } + + if (!(dtype->prtype & DATA_UNSIGNED)) { + + *buf ^= 128; + } + + ptr = buf; + buf += col_len; + } else if ((type == DATA_VARCHAR + || type == DATA_VARMYSQL + || type == DATA_BINARY)) { + + if (dtype_get_mysql_type(dtype) == DATA_MYSQL_TRUE_VARCHAR) { + /* The length of the actual data is stored to 1 or 2 + bytes at the start of the field */ + + if (row_format_col) { + if (dtype->prtype & DATA_LONG_TRUE_VARCHAR) { + lenlen = 2; + } else { + lenlen = 1; + } + } else { + /* In a MySQL key value, lenlen is always 2 */ + lenlen = 2; + } + + ptr = row_mysql_read_true_varchar(&col_len, mysql_data, + lenlen); + } else { + /* Remove trailing spaces from old style VARCHAR + columns. */ + + /* Handle UCS2 strings differently. */ + ulint mbminlen = dtype_get_mbminlen(dtype); + + ptr = mysql_data; + + if (mbminlen == 2) { + /* space=0x0020 */ + /* Trim "half-chars", just in case. */ + col_len &= ~1; + + while (col_len >= 2 && ptr[col_len - 2] == 0x00 + && ptr[col_len - 1] == 0x20) { + col_len -= 2; + } + } else { + ut_a(mbminlen == 1); + /* space=0x20 */ + while (col_len > 0 + && ptr[col_len - 1] == 0x20) { + col_len--; + } + } + } + } else if (comp && type == DATA_MYSQL + && dtype_get_mbminlen(dtype) == 1 + && dtype_get_mbmaxlen(dtype) > 1) { + /* In some cases we strip trailing spaces from UTF-8 and other + multibyte charsets, from FIXED-length CHAR columns, to save + space. UTF-8 would otherwise normally use 3 * the string length + bytes to store an ASCII string! */ + + /* We assume that this CHAR field is encoded in a + variable-length character set where spaces have + 1:1 correspondence to 0x20 bytes, such as UTF-8. + + Consider a CHAR(n) field, a field of n characters. + It will contain between n * mbminlen and n * mbmaxlen bytes. + We will try to truncate it to n bytes by stripping + space padding. If the field contains single-byte + characters only, it will be truncated to n characters. + Consider a CHAR(5) field containing the string ".a " + where "." denotes a 3-byte character represented by + the bytes "$%&". After our stripping, the string will + be stored as "$%&a " (5 bytes). The string ".abc " + will be stored as "$%&abc" (6 bytes). + + The space padding will be restored in row0sel.c, function + row_sel_field_store_in_mysql_format(). */ + + ulint n_chars; + + ut_a(!(dtype_get_len(dtype) % dtype_get_mbmaxlen(dtype))); + + n_chars = dtype_get_len(dtype) / dtype_get_mbmaxlen(dtype); + + /* Strip space padding. */ + while (col_len > n_chars && ptr[col_len - 1] == 0x20) { + col_len--; + } + } else if (type == DATA_BLOB && row_format_col) { + + ptr = row_mysql_read_blob_ref(&col_len, mysql_data, col_len); + } + + dfield_set_data(dfield, ptr, col_len); + + return(buf); +} + +/****************************************************************** +Convert a row in the MySQL format to a row in the Innobase format. Note that +the function to convert a MySQL format key value to an InnoDB dtuple is +row_sel_convert_mysql_key_to_innobase() in row0sel.c. */ +static +void +row_mysql_convert_row_to_innobase( +/*==============================*/ + dtuple_t* row, /* in/out: Innobase row where the + field type information is already + copied there! */ + row_prebuilt_t* prebuilt, /* in: prebuilt struct where template + must be of type ROW_MYSQL_WHOLE_ROW */ + byte* mysql_rec) /* in: row in the MySQL format; + NOTE: do not discard as long as + row is used, as row may contain + pointers to this record! */ +{ + mysql_row_templ_t* templ; + dfield_t* dfield; + ulint i; + + ut_ad(prebuilt->template_type == ROW_MYSQL_WHOLE_ROW); + ut_ad(prebuilt->mysql_template); + + for (i = 0; i < prebuilt->n_template; i++) { + + templ = prebuilt->mysql_template + i; + dfield = dtuple_get_nth_field(row, i); + + if (templ->mysql_null_bit_mask != 0) { + /* Column may be SQL NULL */ + + if (mysql_rec[templ->mysql_null_byte_offset] + & (byte) (templ->mysql_null_bit_mask)) { + + /* It is SQL NULL */ + + dfield_set_null(dfield); + + goto next_column; + } + } + + row_mysql_store_col_in_innobase_format( + dfield, + prebuilt->ins_upd_rec_buff + templ->mysql_col_offset, + TRUE, /* MySQL row format data */ + mysql_rec + templ->mysql_col_offset, + templ->mysql_col_len, + dict_table_is_comp(prebuilt->table)); +next_column: + ; + } +} + +/******************************************************************** +Handles user errors and lock waits detected by the database engine. */ +UNIV_INTERN +ibool +row_mysql_handle_errors( +/*====================*/ + /* out: TRUE if it was a lock wait and + we should continue running the query thread */ + ulint* new_err,/* out: possible new error encountered in + lock wait, or if no new error, the value + of trx->error_state at the entry of this + function */ + trx_t* trx, /* in: transaction */ + que_thr_t* thr, /* in: query thread */ + trx_savept_t* savept) /* in: savepoint or NULL */ +{ +#ifndef UNIV_HOTBACKUP + ulint err; + +handle_new_error: + err = trx->error_state; + + ut_a(err != DB_SUCCESS); + + trx->error_state = DB_SUCCESS; + + switch (err) { + case DB_LOCK_WAIT_TIMEOUT: + if (row_rollback_on_timeout) { + trx_general_rollback_for_mysql(trx, FALSE, NULL); + break; + } + /* fall through */ + case DB_DUPLICATE_KEY: + case DB_FOREIGN_DUPLICATE_KEY: + case DB_TOO_BIG_RECORD: + case DB_ROW_IS_REFERENCED: + case DB_NO_REFERENCED_ROW: + case DB_CANNOT_ADD_CONSTRAINT: + case DB_TOO_MANY_CONCURRENT_TRXS: + case DB_OUT_OF_FILE_SPACE: + if (savept) { + /* Roll back the latest, possibly incomplete + insertion or update */ + + trx_general_rollback_for_mysql(trx, TRUE, savept); + } + /* MySQL will roll back the latest SQL statement */ + break; + case DB_LOCK_WAIT: + srv_suspend_mysql_thread(thr); + + if (trx->error_state != DB_SUCCESS) { + que_thr_stop_for_mysql(thr); + + goto handle_new_error; + } + + *new_err = err; + + return(TRUE); + + case DB_DEADLOCK: + case DB_LOCK_TABLE_FULL: + /* Roll back the whole transaction; this resolution was added + to version 3.23.43 */ + + trx_general_rollback_for_mysql(trx, FALSE, NULL); + break; + + case DB_MUST_GET_MORE_FILE_SPACE: + fputs("InnoDB: The database cannot continue" + " operation because of\n" + "InnoDB: lack of space. You must add" + " a new data file to\n" + "InnoDB: my.cnf and restart the database.\n", stderr); + + exit(1); + + case DB_CORRUPTION: + fputs("InnoDB: We detected index corruption" + " in an InnoDB type table.\n" + "InnoDB: You have to dump + drop + reimport" + " the table or, in\n" + "InnoDB: a case of widespread corruption," + " dump all InnoDB\n" + "InnoDB: tables and recreate the" + " whole InnoDB tablespace.\n" + "InnoDB: If the mysqld server crashes" + " after the startup or when\n" + "InnoDB: you dump the tables, look at\n" + "InnoDB: http://dev.mysql.com/doc/refman/5.1/en/" + "forcing-recovery.html" + " for help.\n", stderr); + break; + default: + fprintf(stderr, "InnoDB: unknown error code %lu\n", + (ulong) err); + ut_error; + } + + if (trx->error_state != DB_SUCCESS) { + *new_err = trx->error_state; + } else { + *new_err = err; + } + + trx->error_state = DB_SUCCESS; + + return(FALSE); +#else /* UNIV_HOTBACKUP */ + /* This function depends on MySQL code that is not included in + InnoDB Hot Backup builds. Besides, this function should never + be called in InnoDB Hot Backup. */ + ut_error; + return(FALSE); +#endif /* UNIV_HOTBACKUP */ +} + +/************************************************************************ +Create a prebuilt struct for a MySQL table handle. */ +UNIV_INTERN +row_prebuilt_t* +row_create_prebuilt( +/*================*/ + /* out, own: a prebuilt struct */ + dict_table_t* table) /* in: Innobase table handle */ +{ + row_prebuilt_t* prebuilt; + mem_heap_t* heap; + dict_index_t* clust_index; + dtuple_t* ref; + ulint ref_len; + + heap = mem_heap_create(sizeof *prebuilt + 128); + + prebuilt = mem_heap_zalloc(heap, sizeof *prebuilt); + + prebuilt->magic_n = ROW_PREBUILT_ALLOCATED; + prebuilt->magic_n2 = ROW_PREBUILT_ALLOCATED; + + prebuilt->table = table; + + prebuilt->sql_stat_start = TRUE; + prebuilt->heap = heap; + + prebuilt->pcur = btr_pcur_create_for_mysql(); + prebuilt->clust_pcur = btr_pcur_create_for_mysql(); + + prebuilt->select_lock_type = LOCK_NONE; + prebuilt->stored_select_lock_type = 99999999; + + prebuilt->search_tuple = dtuple_create( + heap, 2 * dict_table_get_n_cols(table)); + + clust_index = dict_table_get_first_index(table); + + /* Make sure that search_tuple is long enough for clustered index */ + ut_a(2 * dict_table_get_n_cols(table) >= clust_index->n_fields); + + ref_len = dict_index_get_n_unique(clust_index); + + ref = dtuple_create(heap, ref_len); + + dict_index_copy_types(ref, clust_index, ref_len); + + prebuilt->clust_ref = ref; + + prebuilt->autoinc_error = 0; + prebuilt->autoinc_offset = 0; + + /* Default to 1, we will set the actual value later in + ha_innobase::get_auto_increment(). */ + prebuilt->autoinc_increment = 1; + + prebuilt->autoinc_last_value = 0; + + return(prebuilt); +} + +/************************************************************************ +Free a prebuilt struct for a MySQL table handle. */ +UNIV_INTERN +void +row_prebuilt_free( +/*==============*/ + row_prebuilt_t* prebuilt, /* in, own: prebuilt struct */ + ibool dict_locked) /* in: TRUE=data dictionary locked */ +{ + ulint i; + + if (UNIV_UNLIKELY + (prebuilt->magic_n != ROW_PREBUILT_ALLOCATED + || prebuilt->magic_n2 != ROW_PREBUILT_ALLOCATED)) { + + fprintf(stderr, + "InnoDB: Error: trying to free a corrupt\n" + "InnoDB: table handle. Magic n %lu," + " magic n2 %lu, table name ", + (ulong) prebuilt->magic_n, + (ulong) prebuilt->magic_n2); + ut_print_name(stderr, NULL, TRUE, prebuilt->table->name); + putc('\n', stderr); + + mem_analyze_corruption(prebuilt); + + ut_error; + } + + prebuilt->magic_n = ROW_PREBUILT_FREED; + prebuilt->magic_n2 = ROW_PREBUILT_FREED; + + btr_pcur_free_for_mysql(prebuilt->pcur); + btr_pcur_free_for_mysql(prebuilt->clust_pcur); + + if (prebuilt->mysql_template) { + mem_free(prebuilt->mysql_template); + } + + if (prebuilt->ins_graph) { + que_graph_free_recursive(prebuilt->ins_graph); + } + + if (prebuilt->sel_graph) { + que_graph_free_recursive(prebuilt->sel_graph); + } + + if (prebuilt->upd_graph) { + que_graph_free_recursive(prebuilt->upd_graph); + } + + if (prebuilt->blob_heap) { + mem_heap_free(prebuilt->blob_heap); + } + + if (prebuilt->old_vers_heap) { + mem_heap_free(prebuilt->old_vers_heap); + } + + for (i = 0; i < MYSQL_FETCH_CACHE_SIZE; i++) { + if (prebuilt->fetch_cache[i] != NULL) { + + if ((ROW_PREBUILT_FETCH_MAGIC_N != mach_read_from_4( + (prebuilt->fetch_cache[i]) - 4)) + || (ROW_PREBUILT_FETCH_MAGIC_N != mach_read_from_4( + (prebuilt->fetch_cache[i]) + + prebuilt->mysql_row_len))) { + fputs("InnoDB: Error: trying to free" + " a corrupt fetch buffer.\n", stderr); + + mem_analyze_corruption( + prebuilt->fetch_cache[i]); + + ut_error; + } + + mem_free((prebuilt->fetch_cache[i]) - 4); + } + } + + dict_table_decrement_handle_count(prebuilt->table, dict_locked); + + mem_heap_free(prebuilt->heap); +} + +/************************************************************************* +Updates the transaction pointers in query graphs stored in the prebuilt +struct. */ +UNIV_INTERN +void +row_update_prebuilt_trx( +/*====================*/ + /* out: prebuilt dtuple */ + row_prebuilt_t* prebuilt, /* in: prebuilt struct in MySQL + handle */ + trx_t* trx) /* in: transaction handle */ +{ + if (trx->magic_n != TRX_MAGIC_N) { + fprintf(stderr, + "InnoDB: Error: trying to use a corrupt\n" + "InnoDB: trx handle. Magic n %lu\n", + (ulong) trx->magic_n); + + mem_analyze_corruption(trx); + + ut_error; + } + + if (prebuilt->magic_n != ROW_PREBUILT_ALLOCATED) { + fprintf(stderr, + "InnoDB: Error: trying to use a corrupt\n" + "InnoDB: table handle. Magic n %lu, table name ", + (ulong) prebuilt->magic_n); + ut_print_name(stderr, trx, TRUE, prebuilt->table->name); + putc('\n', stderr); + + mem_analyze_corruption(prebuilt); + + ut_error; + } + + prebuilt->trx = trx; + + if (prebuilt->ins_graph) { + prebuilt->ins_graph->trx = trx; + } + + if (prebuilt->upd_graph) { + prebuilt->upd_graph->trx = trx; + } + + if (prebuilt->sel_graph) { + prebuilt->sel_graph->trx = trx; + } +} + +/************************************************************************* +Gets pointer to a prebuilt dtuple used in insertions. If the insert graph +has not yet been built in the prebuilt struct, then this function first +builds it. */ +static +dtuple_t* +row_get_prebuilt_insert_row( +/*========================*/ + /* out: prebuilt dtuple; the column + type information is also set in it */ + row_prebuilt_t* prebuilt) /* in: prebuilt struct in MySQL + handle */ +{ + ins_node_t* node; + dtuple_t* row; + dict_table_t* table = prebuilt->table; + + ut_ad(prebuilt && table && prebuilt->trx); + + if (prebuilt->ins_node == NULL) { + + /* Not called before for this handle: create an insert node + and query graph to the prebuilt struct */ + + node = ins_node_create(INS_DIRECT, table, prebuilt->heap); + + prebuilt->ins_node = node; + + if (prebuilt->ins_upd_rec_buff == NULL) { + prebuilt->ins_upd_rec_buff = mem_heap_alloc( + prebuilt->heap, prebuilt->mysql_row_len); + } + + row = dtuple_create(prebuilt->heap, + dict_table_get_n_cols(table)); + + dict_table_copy_types(row, table); + + ins_node_set_new_row(node, row); + + prebuilt->ins_graph = que_node_get_parent( + pars_complete_graph_for_exec(node, + prebuilt->trx, + prebuilt->heap)); + prebuilt->ins_graph->state = QUE_FORK_ACTIVE; + } + + return(prebuilt->ins_node->row); +} + +/************************************************************************* +Updates the table modification counter and calculates new estimates +for table and index statistics if necessary. */ +UNIV_INLINE +void +row_update_statistics_if_needed( +/*============================*/ + dict_table_t* table) /* in: table */ +{ + ulint counter; + + counter = table->stat_modified_counter; + + table->stat_modified_counter = counter + 1; + + if (!srv_stats_auto_update) + return; + + /* Calculate new statistics if 1 / 16 of table has been modified + since the last time a statistics batch was run, or if + stat_modified_counter > 2 000 000 000 (to avoid wrap-around). + We calculate statistics at most every 16th round, since we may have + a counter table which is very small and updated very often. */ + + if (counter > 2000000000 + || ((ib_int64_t)counter > 16 + table->stat_n_rows / 16)) { + + dict_update_statistics(table); + } +} + +/************************************************************************* +Unlocks AUTO_INC type locks that were possibly reserved by a trx. */ +UNIV_INTERN +void +row_unlock_table_autoinc_for_mysql( +/*===============================*/ + trx_t* trx) /* in/out: transaction */ +{ + mutex_enter(&kernel_mutex); + + lock_release_autoinc_locks(trx); + + mutex_exit(&kernel_mutex); +} + +/************************************************************************* +Sets an AUTO_INC type lock on the table mentioned in prebuilt. The +AUTO_INC lock gives exclusive access to the auto-inc counter of the +table. The lock is reserved only for the duration of an SQL statement. +It is not compatible with another AUTO_INC or exclusive lock on the +table. */ +UNIV_INTERN +int +row_lock_table_autoinc_for_mysql( +/*=============================*/ + /* out: error code or DB_SUCCESS */ + row_prebuilt_t* prebuilt) /* in: prebuilt struct in the MySQL + table handle */ +{ + trx_t* trx = prebuilt->trx; + ins_node_t* node = prebuilt->ins_node; + const dict_table_t* table = prebuilt->table; + que_thr_t* thr; + ulint err; + ibool was_lock_wait; + + ut_ad(trx); + ut_ad(trx->mysql_thread_id == os_thread_get_curr_id()); + + /* If we already hold an AUTOINC lock on the table then do nothing. + Note: We peek at the value of the current owner without acquiring + the kernel mutex. **/ + if (trx == table->autoinc_trx) { + + return(DB_SUCCESS); + } + + trx->op_info = "setting auto-inc lock"; + + if (node == NULL) { + row_get_prebuilt_insert_row(prebuilt); + node = prebuilt->ins_node; + } + + /* We use the insert query graph as the dummy graph needed + in the lock module call */ + + thr = que_fork_get_first_thr(prebuilt->ins_graph); + + que_thr_move_to_run_state_for_mysql(thr, trx); + +run_again: + thr->run_node = node; + thr->prev_node = node; + + /* It may be that the current session has not yet started + its transaction, or it has been committed: */ + + trx_start_if_not_started(trx); + + err = lock_table(0, prebuilt->table, LOCK_AUTO_INC, thr); + + trx->error_state = err; + + if (err != DB_SUCCESS) { + que_thr_stop_for_mysql(thr); + + was_lock_wait = row_mysql_handle_errors(&err, trx, thr, NULL); + + if (was_lock_wait) { + goto run_again; + } + + trx->op_info = ""; + + return((int) err); + } + + que_thr_stop_for_mysql_no_error(thr, trx); + + trx->op_info = ""; + + return((int) err); +} + +/************************************************************************* +Sets a table lock on the table mentioned in prebuilt. */ +UNIV_INTERN +int +row_lock_table_for_mysql( +/*=====================*/ + /* out: error code or DB_SUCCESS */ + row_prebuilt_t* prebuilt, /* in: prebuilt struct in the MySQL + table handle */ + dict_table_t* table, /* in: table to lock, or NULL + if prebuilt->table should be + locked as + prebuilt->select_lock_type */ + ulint mode) /* in: lock mode of table + (ignored if table==NULL) */ +{ + trx_t* trx = prebuilt->trx; + que_thr_t* thr; + ulint err; + ibool was_lock_wait; + + ut_ad(trx); + ut_ad(trx->mysql_thread_id == os_thread_get_curr_id()); + + trx->op_info = "setting table lock"; + + if (prebuilt->sel_graph == NULL) { + /* Build a dummy select query graph */ + row_prebuild_sel_graph(prebuilt); + } + + /* We use the select query graph as the dummy graph needed + in the lock module call */ + + thr = que_fork_get_first_thr(prebuilt->sel_graph); + + que_thr_move_to_run_state_for_mysql(thr, trx); + +run_again: + thr->run_node = thr; + thr->prev_node = thr->common.parent; + + /* It may be that the current session has not yet started + its transaction, or it has been committed: */ + + trx_start_if_not_started(trx); + + if (table) { + err = lock_table(0, table, mode, thr); + } else { + err = lock_table(0, prebuilt->table, + prebuilt->select_lock_type, thr); + } + + trx->error_state = err; + + if (err != DB_SUCCESS) { + que_thr_stop_for_mysql(thr); + + was_lock_wait = row_mysql_handle_errors(&err, trx, thr, NULL); + + if (was_lock_wait) { + goto run_again; + } + + trx->op_info = ""; + + return((int) err); + } + + que_thr_stop_for_mysql_no_error(thr, trx); + + trx->op_info = ""; + + return((int) err); +} + +/************************************************************************* +Does an insert for MySQL. */ +UNIV_INTERN +int +row_insert_for_mysql( +/*=================*/ + /* out: error code or DB_SUCCESS */ + byte* mysql_rec, /* in: row in the MySQL format */ + row_prebuilt_t* prebuilt) /* in: prebuilt struct in MySQL + handle */ +{ + trx_savept_t savept; + que_thr_t* thr; + ulint err; + ibool was_lock_wait; + trx_t* trx = prebuilt->trx; + ins_node_t* node = prebuilt->ins_node; + + ut_ad(trx); + ut_ad(trx->mysql_thread_id == os_thread_get_curr_id()); + + if (prebuilt->table->ibd_file_missing) { + ut_print_timestamp(stderr); + fprintf(stderr, " InnoDB: Error:\n" + "InnoDB: MySQL is trying to use a table handle" + " but the .ibd file for\n" + "InnoDB: table %s does not exist.\n" + "InnoDB: Have you deleted the .ibd file" + " from the database directory under\n" + "InnoDB: the MySQL datadir, or have you" + " used DISCARD TABLESPACE?\n" + "InnoDB: Look from\n" + "InnoDB: http://dev.mysql.com/doc/refman/5.1/en/" + "innodb-troubleshooting.html\n" + "InnoDB: how you can resolve the problem.\n", + prebuilt->table->name); + return(DB_ERROR); + } + + if (UNIV_UNLIKELY(prebuilt->magic_n != ROW_PREBUILT_ALLOCATED)) { + fprintf(stderr, + "InnoDB: Error: trying to free a corrupt\n" + "InnoDB: table handle. Magic n %lu, table name ", + (ulong) prebuilt->magic_n); + ut_print_name(stderr, trx, TRUE, prebuilt->table->name); + putc('\n', stderr); + + mem_analyze_corruption(prebuilt); + + ut_error; + } + + if (UNIV_UNLIKELY(srv_created_new_raw || srv_force_recovery)) { + fputs("InnoDB: A new raw disk partition was initialized or\n" + "InnoDB: innodb_force_recovery is on: we do not allow\n" + "InnoDB: database modifications by the user. Shut down\n" + "InnoDB: mysqld and edit my.cnf so that" + " newraw is replaced\n" + "InnoDB: with raw, and innodb_force_... is removed.\n", + stderr); + + return(DB_ERROR); + } + + trx->op_info = "inserting"; + + row_mysql_delay_if_needed(); + + trx_start_if_not_started(trx); + + if (node == NULL) { + row_get_prebuilt_insert_row(prebuilt); + node = prebuilt->ins_node; + } + + row_mysql_convert_row_to_innobase(node->row, prebuilt, mysql_rec); + + savept = trx_savept_take(trx); + + thr = que_fork_get_first_thr(prebuilt->ins_graph); + + if (prebuilt->sql_stat_start) { + node->state = INS_NODE_SET_IX_LOCK; + prebuilt->sql_stat_start = FALSE; + } else { + node->state = INS_NODE_ALLOC_ROW_ID; + } + + que_thr_move_to_run_state_for_mysql(thr, trx); + +run_again: + thr->run_node = node; + thr->prev_node = node; + + row_ins_step(thr); + + err = trx->error_state; + + if (err != DB_SUCCESS) { + que_thr_stop_for_mysql(thr); + + /* TODO: what is this? */ thr->lock_state= QUE_THR_LOCK_ROW; + + was_lock_wait = row_mysql_handle_errors(&err, trx, thr, + &savept); + thr->lock_state= QUE_THR_LOCK_NOLOCK; + + if (was_lock_wait) { + goto run_again; + } + + trx->op_info = ""; + + return((int) err); + } + + que_thr_stop_for_mysql_no_error(thr, trx); + + prebuilt->table->stat_n_rows++; + + srv_n_rows_inserted++; + + if (prebuilt->table->stat_n_rows == 0) { + /* Avoid wrap-over */ + prebuilt->table->stat_n_rows--; + } + + row_update_statistics_if_needed(prebuilt->table); + trx->op_info = ""; + + return((int) err); +} + +/************************************************************************* +Builds a dummy query graph used in selects. */ +UNIV_INTERN +void +row_prebuild_sel_graph( +/*===================*/ + row_prebuilt_t* prebuilt) /* in: prebuilt struct in MySQL + handle */ +{ + sel_node_t* node; + + ut_ad(prebuilt && prebuilt->trx); + + if (prebuilt->sel_graph == NULL) { + + node = sel_node_create(prebuilt->heap); + + prebuilt->sel_graph = que_node_get_parent( + pars_complete_graph_for_exec(node, + prebuilt->trx, + prebuilt->heap)); + + prebuilt->sel_graph->state = QUE_FORK_ACTIVE; + } +} + +/************************************************************************* +Creates an query graph node of 'update' type to be used in the MySQL +interface. */ +UNIV_INTERN +upd_node_t* +row_create_update_node_for_mysql( +/*=============================*/ + /* out, own: update node */ + dict_table_t* table, /* in: table to update */ + mem_heap_t* heap) /* in: mem heap from which allocated */ +{ + upd_node_t* node; + + node = upd_node_create(heap); + + node->in_mysql_interface = TRUE; + node->is_delete = FALSE; + node->searched_update = FALSE; + node->select = NULL; + node->pcur = btr_pcur_create_for_mysql(); + node->table = table; + + node->update = upd_create(dict_table_get_n_cols(table), heap); + + node->update_n_fields = dict_table_get_n_cols(table); + + UT_LIST_INIT(node->columns); + node->has_clust_rec_x_lock = TRUE; + node->cmpl_info = 0; + + node->table_sym = NULL; + node->col_assign_list = NULL; + + return(node); +} + +/************************************************************************* +Gets pointer to a prebuilt update vector used in updates. If the update +graph has not yet been built in the prebuilt struct, then this function +first builds it. */ +UNIV_INTERN +upd_t* +row_get_prebuilt_update_vector( +/*===========================*/ + /* out: prebuilt update vector */ + row_prebuilt_t* prebuilt) /* in: prebuilt struct in MySQL + handle */ +{ + dict_table_t* table = prebuilt->table; + upd_node_t* node; + + ut_ad(prebuilt && table && prebuilt->trx); + + if (prebuilt->upd_node == NULL) { + + /* Not called before for this handle: create an update node + and query graph to the prebuilt struct */ + + node = row_create_update_node_for_mysql(table, prebuilt->heap); + + prebuilt->upd_node = node; + + prebuilt->upd_graph = que_node_get_parent( + pars_complete_graph_for_exec(node, + prebuilt->trx, + prebuilt->heap)); + prebuilt->upd_graph->state = QUE_FORK_ACTIVE; + } + + return(prebuilt->upd_node->update); +} + +/************************************************************************* +Does an update or delete of a row for MySQL. */ +UNIV_INTERN +int +row_update_for_mysql( +/*=================*/ + /* out: error code or DB_SUCCESS */ + byte* mysql_rec, /* in: the row to be updated, in + the MySQL format */ + row_prebuilt_t* prebuilt) /* in: prebuilt struct in MySQL + handle */ +{ + trx_savept_t savept; + ulint err; + que_thr_t* thr; + ibool was_lock_wait; + dict_index_t* clust_index; + /* ulint ref_len; */ + upd_node_t* node; + dict_table_t* table = prebuilt->table; + trx_t* trx = prebuilt->trx; + + ut_ad(prebuilt && trx); + ut_ad(trx->mysql_thread_id == os_thread_get_curr_id()); + UT_NOT_USED(mysql_rec); + + if (prebuilt->table->ibd_file_missing) { + ut_print_timestamp(stderr); + fprintf(stderr, " InnoDB: Error:\n" + "InnoDB: MySQL is trying to use a table handle" + " but the .ibd file for\n" + "InnoDB: table %s does not exist.\n" + "InnoDB: Have you deleted the .ibd file" + " from the database directory under\n" + "InnoDB: the MySQL datadir, or have you" + " used DISCARD TABLESPACE?\n" + "InnoDB: Look from\n" + "InnoDB: http://dev.mysql.com/doc/refman/5.1/en/" + "innodb-troubleshooting.html\n" + "InnoDB: how you can resolve the problem.\n", + prebuilt->table->name); + return(DB_ERROR); + } + + if (UNIV_UNLIKELY(prebuilt->magic_n != ROW_PREBUILT_ALLOCATED)) { + fprintf(stderr, + "InnoDB: Error: trying to free a corrupt\n" + "InnoDB: table handle. Magic n %lu, table name ", + (ulong) prebuilt->magic_n); + ut_print_name(stderr, trx, TRUE, prebuilt->table->name); + putc('\n', stderr); + + mem_analyze_corruption(prebuilt); + + ut_error; + } + + if (UNIV_UNLIKELY(srv_created_new_raw || srv_force_recovery)) { + fputs("InnoDB: A new raw disk partition was initialized or\n" + "InnoDB: innodb_force_recovery is on: we do not allow\n" + "InnoDB: database modifications by the user. Shut down\n" + "InnoDB: mysqld and edit my.cnf so that newraw" + " is replaced\n" + "InnoDB: with raw, and innodb_force_... is removed.\n", + stderr); + + return(DB_ERROR); + } + + trx->op_info = "updating or deleting"; + + row_mysql_delay_if_needed(); + + trx_start_if_not_started(trx); + + node = prebuilt->upd_node; + + clust_index = dict_table_get_first_index(table); + + if (prebuilt->pcur->btr_cur.index == clust_index) { + btr_pcur_copy_stored_position(node->pcur, prebuilt->pcur); + } else { + btr_pcur_copy_stored_position(node->pcur, + prebuilt->clust_pcur); + } + + ut_a(node->pcur->rel_pos == BTR_PCUR_ON); + + /* MySQL seems to call rnd_pos before updating each row it + has cached: we can get the correct cursor position from + prebuilt->pcur; NOTE that we cannot build the row reference + from mysql_rec if the clustered index was automatically + generated for the table: MySQL does not know anything about + the row id used as the clustered index key */ + + savept = trx_savept_take(trx); + + thr = que_fork_get_first_thr(prebuilt->upd_graph); + + node->state = UPD_NODE_UPDATE_CLUSTERED; + + ut_ad(!prebuilt->sql_stat_start); + + que_thr_move_to_run_state_for_mysql(thr, trx); + +run_again: + thr->run_node = node; + thr->prev_node = node; + + row_upd_step(thr); + + err = trx->error_state; + + if (err != DB_SUCCESS) { + que_thr_stop_for_mysql(thr); + + if (err == DB_RECORD_NOT_FOUND) { + trx->error_state = DB_SUCCESS; + trx->op_info = ""; + + return((int) err); + } + + thr->lock_state= QUE_THR_LOCK_ROW; + was_lock_wait = row_mysql_handle_errors(&err, trx, thr, + &savept); + thr->lock_state= QUE_THR_LOCK_NOLOCK; + + if (was_lock_wait) { + goto run_again; + } + + trx->op_info = ""; + + return((int) err); + } + + que_thr_stop_for_mysql_no_error(thr, trx); + + if (node->is_delete) { + if (prebuilt->table->stat_n_rows > 0) { + prebuilt->table->stat_n_rows--; + } + + srv_n_rows_deleted++; + } else { + srv_n_rows_updated++; + } + + row_update_statistics_if_needed(prebuilt->table); + + trx->op_info = ""; + + return((int) err); +} + +/************************************************************************* +This can only be used when srv_locks_unsafe_for_binlog is TRUE or +this session is using a READ COMMITTED isolation level. Before +calling this function we must use trx_reset_new_rec_lock_info() and +trx_register_new_rec_lock() to store the information which new record locks +really were set. This function removes a newly set lock under prebuilt->pcur, +and also under prebuilt->clust_pcur. Currently, this is only used and tested +in the case of an UPDATE or a DELETE statement, where the row lock is of the +LOCK_X type. +Thus, this implements a 'mini-rollback' that releases the latest record +locks we set. */ +UNIV_INTERN +int +row_unlock_for_mysql( +/*=================*/ + /* out: error code or DB_SUCCESS */ + row_prebuilt_t* prebuilt, /* in: prebuilt struct in MySQL + handle */ + ibool has_latches_on_recs)/* TRUE if called so that we have + the latches on the records under pcur + and clust_pcur, and we do not need to + reposition the cursors. */ +{ + dict_index_t* index; + btr_pcur_t* pcur = prebuilt->pcur; + btr_pcur_t* clust_pcur = prebuilt->clust_pcur; + trx_t* trx = prebuilt->trx; + rec_t* rec; + mtr_t mtr; + + ut_ad(prebuilt && trx); + ut_ad(trx->mysql_thread_id == os_thread_get_curr_id()); + + if (UNIV_UNLIKELY + (!srv_locks_unsafe_for_binlog + && trx->isolation_level != TRX_ISO_READ_COMMITTED)) { + + fprintf(stderr, + "InnoDB: Error: calling row_unlock_for_mysql though\n" + "InnoDB: innodb_locks_unsafe_for_binlog is FALSE and\n" + "InnoDB: this session is not using" + " READ COMMITTED isolation level.\n"); + + return(DB_SUCCESS); + } + + trx->op_info = "unlock_row"; + + index = btr_pcur_get_btr_cur(pcur)->index; + + if (index != NULL && trx_new_rec_locks_contain(trx, index)) { + + mtr_start(&mtr); + + /* Restore the cursor position and find the record */ + + if (!has_latches_on_recs) { + btr_pcur_restore_position(BTR_SEARCH_LEAF, pcur, &mtr); + } + + rec = btr_pcur_get_rec(pcur); + + lock_rec_unlock(trx, btr_pcur_get_block(pcur), + rec, prebuilt->select_lock_type); + + mtr_commit(&mtr); + + /* If the search was done through the clustered index, then + we have not used clust_pcur at all, and we must NOT try to + reset locks on clust_pcur. The values in clust_pcur may be + garbage! */ + + if (dict_index_is_clust(index)) { + + goto func_exit; + } + } + + index = btr_pcur_get_btr_cur(clust_pcur)->index; + + if (index != NULL && trx_new_rec_locks_contain(trx, index)) { + + mtr_start(&mtr); + + /* Restore the cursor position and find the record */ + + if (!has_latches_on_recs) { + btr_pcur_restore_position(BTR_SEARCH_LEAF, clust_pcur, + &mtr); + } + + rec = btr_pcur_get_rec(clust_pcur); + + lock_rec_unlock(trx, btr_pcur_get_block(clust_pcur), + rec, prebuilt->select_lock_type); + + mtr_commit(&mtr); + } + +func_exit: + trx->op_info = ""; + + return(DB_SUCCESS); +} + +/************************************************************************** +Does a cascaded delete or set null in a foreign key operation. */ +UNIV_INTERN +ulint +row_update_cascade_for_mysql( +/*=========================*/ + /* out: error code or DB_SUCCESS */ + que_thr_t* thr, /* in: query thread */ + upd_node_t* node, /* in: update node used in the cascade + or set null operation */ + dict_table_t* table) /* in: table where we do the operation */ +{ + ulint err; + trx_t* trx; + + trx = thr_get_trx(thr); +run_again: + thr->run_node = node; + thr->prev_node = node; + + row_upd_step(thr); + + err = trx->error_state; + + /* Note that the cascade node is a subnode of another InnoDB + query graph node. We do a normal lock wait in this node, but + all errors are handled by the parent node. */ + + if (err == DB_LOCK_WAIT) { + /* Handle lock wait here */ + + que_thr_stop_for_mysql(thr); + + srv_suspend_mysql_thread(thr); + + /* Note that a lock wait may also end in a lock wait timeout, + or this transaction is picked as a victim in selective + deadlock resolution */ + + if (trx->error_state != DB_SUCCESS) { + + return(trx->error_state); + } + + /* Retry operation after a normal lock wait */ + + goto run_again; + } + + if (err != DB_SUCCESS) { + + return(err); + } + + if (node->is_delete) { + if (table->stat_n_rows > 0) { + table->stat_n_rows--; + } + + srv_n_rows_deleted++; + } else { + srv_n_rows_updated++; + } + + row_update_statistics_if_needed(table); + + return(err); +} + +/************************************************************************* +Checks if a table is such that we automatically created a clustered +index on it (on row id). */ +UNIV_INTERN +ibool +row_table_got_default_clust_index( +/*==============================*/ + const dict_table_t* table) +{ + const dict_index_t* clust_index; + + clust_index = dict_table_get_first_index(table); + + return(dict_index_get_nth_col(clust_index, 0)->mtype == DATA_SYS); +} + +/************************************************************************* +Calculates the key number used inside MySQL for an Innobase index. We have +to take into account if we generated a default clustered index for the table */ +UNIV_INTERN +ulint +row_get_mysql_key_number_for_index( +/*===============================*/ + const dict_index_t* index) +{ + const dict_index_t* ind; + ulint i; + + ut_a(index); + + i = 0; + ind = dict_table_get_first_index(index->table); + + while (index != ind) { + ind = dict_table_get_next_index(ind); + i++; + } + + if (row_table_got_default_clust_index(index->table)) { + ut_a(i > 0); + i--; + } + + return(i); +} + +/************************************************************************* +Locks the data dictionary in shared mode from modifications, for performing +foreign key check, rollback, or other operation invisible to MySQL. */ +UNIV_INTERN +void +row_mysql_freeze_data_dictionary_func( +/*==================================*/ + trx_t* trx, /* in/out: transaction */ + const char* file, /* in: file name */ + ulint line) /* in: line number */ +{ + ut_a(trx->dict_operation_lock_mode == 0); + + rw_lock_s_lock_func(&dict_operation_lock, 0, file, line); + + trx->dict_operation_lock_mode = RW_S_LATCH; +} + +/************************************************************************* +Unlocks the data dictionary shared lock. */ +UNIV_INTERN +void +row_mysql_unfreeze_data_dictionary( +/*===============================*/ + trx_t* trx) /* in/out: transaction */ +{ + ut_a(trx->dict_operation_lock_mode == RW_S_LATCH); + + rw_lock_s_unlock(&dict_operation_lock); + + trx->dict_operation_lock_mode = 0; +} + +/************************************************************************* +Locks the data dictionary exclusively for performing a table create or other +data dictionary modification operation. */ +UNIV_INTERN +void +row_mysql_lock_data_dictionary_func( +/*================================*/ + trx_t* trx, /* in/out: transaction */ + const char* file, /* in: file name */ + ulint line) /* in: line number */ +{ + ut_a(trx->dict_operation_lock_mode == 0 + || trx->dict_operation_lock_mode == RW_X_LATCH); + + /* Serialize data dictionary operations with dictionary mutex: + no deadlocks or lock waits can occur then in these operations */ + + rw_lock_x_lock_func(&dict_operation_lock, 0, file, line); + trx->dict_operation_lock_mode = RW_X_LATCH; + + mutex_enter(&(dict_sys->mutex)); +} + +/************************************************************************* +Unlocks the data dictionary exclusive lock. */ +UNIV_INTERN +void +row_mysql_unlock_data_dictionary( +/*=============================*/ + trx_t* trx) /* in/out: transaction */ +{ + ut_a(trx->dict_operation_lock_mode == RW_X_LATCH); + + /* Serialize data dictionary operations with dictionary mutex: + no deadlocks can occur then in these operations */ + + mutex_exit(&(dict_sys->mutex)); + rw_lock_x_unlock(&dict_operation_lock); + + trx->dict_operation_lock_mode = 0; +} + +#ifndef UNIV_HOTBACKUP +/************************************************************************* +Creates a table for MySQL. If the name of the table ends in +one of "innodb_monitor", "innodb_lock_monitor", "innodb_tablespace_monitor", +"innodb_table_monitor", then this will also start the printing of monitor +output by the master thread. If the table name ends in "innodb_mem_validate", +InnoDB will try to invoke mem_validate(). */ +UNIV_INTERN +int +row_create_table_for_mysql( +/*=======================*/ + /* out: error code or DB_SUCCESS */ + dict_table_t* table, /* in, own: table definition + (will be freed) */ + trx_t* trx) /* in: transaction handle */ +{ + tab_node_t* node; + mem_heap_t* heap; + que_thr_t* thr; + const char* table_name; + ulint table_name_len; + ulint err; + ulint i; + + ut_ad(trx->mysql_thread_id == os_thread_get_curr_id()); +#ifdef UNIV_SYNC_DEBUG + ut_ad(rw_lock_own(&dict_operation_lock, RW_LOCK_EX)); +#endif /* UNIV_SYNC_DEBUG */ + ut_ad(mutex_own(&(dict_sys->mutex))); + ut_ad(trx->dict_operation_lock_mode == RW_X_LATCH); + + if (srv_created_new_raw) { + fputs("InnoDB: A new raw disk partition was initialized:\n" + "InnoDB: we do not allow database modifications" + " by the user.\n" + "InnoDB: Shut down mysqld and edit my.cnf so that newraw" + " is replaced with raw.\n", stderr); +err_exit: + dict_mem_table_free(table); + trx_commit_for_mysql(trx); + + return(DB_ERROR); + } + + trx->op_info = "creating table"; + + if (row_mysql_is_system_table(table->name)) { + + fprintf(stderr, + "InnoDB: Error: trying to create a MySQL system" + " table %s of type InnoDB.\n" + "InnoDB: MySQL system tables must be" + " of the MyISAM type!\n", + table->name); + goto err_exit; + } + + /* Check that no reserved column names are used. */ + for (i = 0; i < dict_table_get_n_user_cols(table); i++) { + if (dict_col_name_is_reserved( + dict_table_get_col_name(table, i))) { + + goto err_exit; + } + } + + trx_start_if_not_started(trx); + + /* The table name is prefixed with the database name and a '/'. + Certain table names starting with 'innodb_' have their special + meaning regardless of the database name. Thus, we need to + ignore the database name prefix in the comparisons. */ + table_name = strchr(table->name, '/'); + ut_a(table_name); + table_name++; + table_name_len = strlen(table_name) + 1; + + if (STR_EQ(table_name, table_name_len, S_innodb_monitor)) { + + /* Table equals "innodb_monitor": + start monitor prints */ + + srv_print_innodb_monitor = TRUE; + + /* The lock timeout monitor thread also takes care + of InnoDB monitor prints */ + + os_event_set(srv_lock_timeout_thread_event); + } else if (STR_EQ(table_name, table_name_len, + S_innodb_lock_monitor)) { + + srv_print_innodb_monitor = TRUE; + srv_print_innodb_lock_monitor = TRUE; + os_event_set(srv_lock_timeout_thread_event); + } else if (STR_EQ(table_name, table_name_len, + S_innodb_tablespace_monitor)) { + + srv_print_innodb_tablespace_monitor = TRUE; + os_event_set(srv_lock_timeout_thread_event); + } else if (STR_EQ(table_name, table_name_len, + S_innodb_table_monitor)) { + + srv_print_innodb_table_monitor = TRUE; + os_event_set(srv_lock_timeout_thread_event); + } else if (STR_EQ(table_name, table_name_len, + S_innodb_mem_validate)) { + /* We define here a debugging feature intended for + developers */ + + fputs("Validating InnoDB memory:\n" + "to use this feature you must compile InnoDB with\n" + "UNIV_MEM_DEBUG defined in univ.i and" + " the server must be\n" + "quiet because allocation from a mem heap" + " is not protected\n" + "by any semaphore.\n", stderr); +#ifdef UNIV_MEM_DEBUG + ut_a(mem_validate()); + fputs("Memory validated\n", stderr); +#else /* UNIV_MEM_DEBUG */ + fputs("Memory NOT validated (recompile with UNIV_MEM_DEBUG)\n", + stderr); +#endif /* UNIV_MEM_DEBUG */ + } + + heap = mem_heap_create(512); + + trx_set_dict_operation(trx, TRX_DICT_OP_TABLE); + + node = tab_create_graph_create(table, heap); + + thr = pars_complete_graph_for_exec(node, trx, heap); + + ut_a(thr == que_fork_start_command(que_node_get_parent(thr))); + que_run_threads(thr); + + err = trx->error_state; + + if (UNIV_UNLIKELY(err != DB_SUCCESS)) { + trx->error_state = DB_SUCCESS; + trx_general_rollback_for_mysql(trx, FALSE, NULL); + } + + switch (err) { + case DB_OUT_OF_FILE_SPACE: + ut_print_timestamp(stderr); + fputs(" InnoDB: Warning: cannot create table ", + stderr); + ut_print_name(stderr, trx, TRUE, table->name); + fputs(" because tablespace full\n", stderr); + + if (dict_table_get_low(table->name)) { + + row_drop_table_for_mysql(table->name, trx, FALSE); + trx_commit_for_mysql(trx); + } + break; + + case DB_DUPLICATE_KEY: + ut_print_timestamp(stderr); + fputs(" InnoDB: Error: table ", stderr); + ut_print_name(stderr, trx, TRUE, table->name); + fputs(" already exists in InnoDB internal\n" + "InnoDB: data dictionary. Have you deleted" + " the .frm file\n" + "InnoDB: and not used DROP TABLE?" + " Have you used DROP DATABASE\n" + "InnoDB: for InnoDB tables in" + " MySQL version <= 3.23.43?\n" + "InnoDB: See the Restrictions section" + " of the InnoDB manual.\n" + "InnoDB: You can drop the orphaned table" + " inside InnoDB by\n" + "InnoDB: creating an InnoDB table with" + " the same name in another\n" + "InnoDB: database and copying the .frm file" + " to the current database.\n" + "InnoDB: Then MySQL thinks the table exists," + " and DROP TABLE will\n" + "InnoDB: succeed.\n" + "InnoDB: You can look for further help from\n" + "InnoDB: " + "http://dev.mysql.com/doc/refman/5.1/en/" + "innodb-troubleshooting.html\n", stderr); + + /* We may also get err == DB_ERROR if the .ibd file for the + table already exists */ + + break; + } + + que_graph_free((que_t*) que_node_get_parent(thr)); + + trx->op_info = ""; + + return((int) err); +} + +/************************************************************************* +Does an index creation operation for MySQL. TODO: currently failure +to create an index results in dropping the whole table! This is no problem +currently as all indexes must be created at the same time as the table. */ +UNIV_INTERN +int +row_create_index_for_mysql( +/*=======================*/ + /* out: error number or DB_SUCCESS */ + dict_index_t* index, /* in, own: index definition + (will be freed) */ + trx_t* trx, /* in: transaction handle */ + const ulint* field_lengths) /* in: if not NULL, must contain + dict_index_get_n_fields(index) + actual field lengths for the + index columns, which are + then checked for not being too + large. */ +{ + ind_node_t* node; + mem_heap_t* heap; + que_thr_t* thr; + ulint err; + ulint i; + ulint len; + char* table_name; + +#ifdef UNIV_SYNC_DEBUG + ut_ad(rw_lock_own(&dict_operation_lock, RW_LOCK_EX)); +#endif /* UNIV_SYNC_DEBUG */ + ut_ad(mutex_own(&(dict_sys->mutex))); + ut_ad(trx->mysql_thread_id == os_thread_get_curr_id()); + + trx->op_info = "creating index"; + + /* Copy the table name because we may want to drop the + table later, after the index object is freed (inside + que_run_threads()) and thus index->table_name is not available. */ + table_name = mem_strdup(index->table_name); + + trx_start_if_not_started(trx); + + /* Check that the same column does not appear twice in the index. + Starting from 4.0.14, InnoDB should be able to cope with that, but + safer not to allow them. */ + + for (i = 0; i < dict_index_get_n_fields(index); i++) { + ulint j; + + for (j = 0; j < i; j++) { + if (0 == ut_strcmp( + dict_index_get_nth_field(index, j)->name, + dict_index_get_nth_field(index, i)->name)) { + ut_print_timestamp(stderr); + + fputs(" InnoDB: Error: column ", stderr); + ut_print_name(stderr, trx, FALSE, + dict_index_get_nth_field( + index, i)->name); + fputs(" appears twice in ", stderr); + dict_index_name_print(stderr, trx, index); + fputs("\n" + "InnoDB: This is not allowed" + " in InnoDB.\n", stderr); + + err = DB_COL_APPEARS_TWICE_IN_INDEX; + + goto error_handling; + } + } + + /* Check also that prefix_len and actual length + < DICT_MAX_INDEX_COL_LEN */ + + len = dict_index_get_nth_field(index, i)->prefix_len; + + if (field_lengths) { + len = ut_max(len, field_lengths[i]); + } + + if (len >= DICT_MAX_INDEX_COL_LEN) { + err = DB_TOO_BIG_RECORD; + + goto error_handling; + } + } + + heap = mem_heap_create(512); + + trx_set_dict_operation(trx, TRX_DICT_OP_TABLE); + + /* Note that the space id where we store the index is inherited from + the table in dict_build_index_def_step() in dict0crea.c. */ + + node = ind_create_graph_create(index, heap); + + thr = pars_complete_graph_for_exec(node, trx, heap); + + ut_a(thr == que_fork_start_command(que_node_get_parent(thr))); + que_run_threads(thr); + + err = trx->error_state; + + que_graph_free((que_t*) que_node_get_parent(thr)); + +error_handling: + if (err != DB_SUCCESS) { + /* We have special error handling here */ + + trx->error_state = DB_SUCCESS; + + trx_general_rollback_for_mysql(trx, FALSE, NULL); + + row_drop_table_for_mysql(table_name, trx, FALSE); + + trx_commit_for_mysql(trx); + + trx->error_state = DB_SUCCESS; + } + + trx->op_info = ""; + + mem_free(table_name); + + return((int) err); +} + +/************************************************************************* +Scans a table create SQL string and adds to the data dictionary +the foreign key constraints declared in the string. This function +should be called after the indexes for a table have been created. +Each foreign key constraint must be accompanied with indexes in +bot participating tables. The indexes are allowed to contain more +fields than mentioned in the constraint. Check also that foreign key +constraints which reference this table are ok. */ +UNIV_INTERN +int +row_table_add_foreign_constraints( +/*==============================*/ + /* out: error code or DB_SUCCESS */ + trx_t* trx, /* in: transaction */ + const char* sql_string, /* in: table create statement where + foreign keys are declared like: + FOREIGN KEY (a, b) REFERENCES table2(c, d), + table2 can be written also with the + database name before it: test.table2 */ + const char* name, /* in: table full name in the + normalized form + database_name/table_name */ + ibool reject_fks) /* in: if TRUE, fail with error + code DB_CANNOT_ADD_CONSTRAINT if + any foreign keys are found. */ +{ + ulint err; + + ut_ad(mutex_own(&(dict_sys->mutex))); +#ifdef UNIV_SYNC_DEBUG + ut_ad(rw_lock_own(&dict_operation_lock, RW_LOCK_EX)); +#endif /* UNIV_SYNC_DEBUG */ + ut_a(sql_string); + + trx->op_info = "adding foreign keys"; + + trx_start_if_not_started(trx); + + trx_set_dict_operation(trx, TRX_DICT_OP_TABLE); + + err = dict_create_foreign_constraints(trx, sql_string, name, + reject_fks); +#ifndef UNIV_HOTBACKUP + if (err == DB_SUCCESS) { + /* Check that also referencing constraints are ok */ + err = dict_load_foreigns(name, TRUE); + } +#endif /* !UNIV_HOTBACKUP */ + if (err != DB_SUCCESS) { + /* We have special error handling here */ + + trx->error_state = DB_SUCCESS; + + trx_general_rollback_for_mysql(trx, FALSE, NULL); + + row_drop_table_for_mysql(name, trx, FALSE); + + trx_commit_for_mysql(trx); + + trx->error_state = DB_SUCCESS; + } + + return((int) err); +} + +/************************************************************************* +Drops a table for MySQL as a background operation. MySQL relies on Unix +in ALTER TABLE to the fact that the table handler does not remove the +table before all handles to it has been removed. Furhermore, the MySQL's +call to drop table must be non-blocking. Therefore we do the drop table +as a background operation, which is taken care of by the master thread +in srv0srv.c. */ +static +int +row_drop_table_for_mysql_in_background( +/*===================================*/ + /* out: error code or DB_SUCCESS */ + const char* name) /* in: table name */ +{ + ulint error; + trx_t* trx; + + trx = trx_allocate_for_background(); + + /* If the original transaction was dropping a table referenced by + foreign keys, we must set the following to be able to drop the + table: */ + + trx->check_foreigns = FALSE; + + /* fputs("InnoDB: Error: Dropping table ", stderr); + ut_print_name(stderr, trx, TRUE, name); + fputs(" in background drop list\n", stderr); */ + + /* Try to drop the table in InnoDB */ + + error = row_drop_table_for_mysql(name, trx, FALSE); + + /* Flush the log to reduce probability that the .frm files and + the InnoDB data dictionary get out-of-sync if the user runs + with innodb_flush_log_at_trx_commit = 0 */ + + log_buffer_flush_to_disk(); + + trx_commit_for_mysql(trx); + + trx_free_for_background(trx); + + return((int) error); +} + +/************************************************************************* +The master thread in srv0srv.c calls this regularly to drop tables which +we must drop in background after queries to them have ended. Such lazy +dropping of tables is needed in ALTER TABLE on Unix. */ +UNIV_INTERN +ulint +row_drop_tables_for_mysql_in_background(void) +/*=========================================*/ + /* out: how many tables dropped + + remaining tables in list */ +{ + row_mysql_drop_t* drop; + dict_table_t* table; + ulint n_tables; + ulint n_tables_dropped = 0; +loop: + mutex_enter(&kernel_mutex); + + if (!row_mysql_drop_list_inited) { + + UT_LIST_INIT(row_mysql_drop_list); + row_mysql_drop_list_inited = TRUE; + } + + drop = UT_LIST_GET_FIRST(row_mysql_drop_list); + + n_tables = UT_LIST_GET_LEN(row_mysql_drop_list); + + mutex_exit(&kernel_mutex); + + if (drop == NULL) { + /* All tables dropped */ + + return(n_tables + n_tables_dropped); + } + + mutex_enter(&(dict_sys->mutex)); + table = dict_table_get_low(drop->table_name); + mutex_exit(&(dict_sys->mutex)); + + if (table == NULL) { + /* If for some reason the table has already been dropped + through some other mechanism, do not try to drop it */ + + goto already_dropped; + } + + if (DB_SUCCESS != row_drop_table_for_mysql_in_background( + drop->table_name)) { + /* If the DROP fails for some table, we return, and let the + main thread retry later */ + + return(n_tables + n_tables_dropped); + } + + n_tables_dropped++; + +already_dropped: + mutex_enter(&kernel_mutex); + + UT_LIST_REMOVE(row_mysql_drop_list, row_mysql_drop_list, drop); + + ut_print_timestamp(stderr); + fputs(" InnoDB: Dropped table ", stderr); + ut_print_name(stderr, NULL, TRUE, drop->table_name); + fputs(" in background drop queue.\n", stderr); + + mem_free(drop->table_name); + + mem_free(drop); + + mutex_exit(&kernel_mutex); + + goto loop; +} + +/************************************************************************* +Get the background drop list length. NOTE: the caller must own the kernel +mutex! */ +UNIV_INTERN +ulint +row_get_background_drop_list_len_low(void) +/*======================================*/ + /* out: how many tables in list */ +{ + ut_ad(mutex_own(&kernel_mutex)); + + if (!row_mysql_drop_list_inited) { + + UT_LIST_INIT(row_mysql_drop_list); + row_mysql_drop_list_inited = TRUE; + } + + return(UT_LIST_GET_LEN(row_mysql_drop_list)); +} + +/************************************************************************* +If a table is not yet in the drop list, adds the table to the list of tables +which the master thread drops in background. We need this on Unix because in +ALTER TABLE MySQL may call drop table even if the table has running queries on +it. Also, if there are running foreign key checks on the table, we drop the +table lazily. */ +static +ibool +row_add_table_to_background_drop_list( +/*==================================*/ + /* out: TRUE if the table was not yet in the + drop list, and was added there */ + const char* name) /* in: table name */ +{ + row_mysql_drop_t* drop; + + mutex_enter(&kernel_mutex); + + if (!row_mysql_drop_list_inited) { + + UT_LIST_INIT(row_mysql_drop_list); + row_mysql_drop_list_inited = TRUE; + } + + /* Look if the table already is in the drop list */ + drop = UT_LIST_GET_FIRST(row_mysql_drop_list); + + while (drop != NULL) { + if (strcmp(drop->table_name, name) == 0) { + /* Already in the list */ + + mutex_exit(&kernel_mutex); + + return(FALSE); + } + + drop = UT_LIST_GET_NEXT(row_mysql_drop_list, drop); + } + + drop = mem_alloc(sizeof(row_mysql_drop_t)); + + drop->table_name = mem_strdup(name); + + UT_LIST_ADD_LAST(row_mysql_drop_list, row_mysql_drop_list, drop); + + /* fputs("InnoDB: Adding table ", stderr); + ut_print_name(stderr, trx, TRUE, drop->table_name); + fputs(" to background drop list\n", stderr); */ + + mutex_exit(&kernel_mutex); + + return(TRUE); +} + +/************************************************************************* +Discards the tablespace of a table which stored in an .ibd file. Discarding +means that this function deletes the .ibd file and assigns a new table id for +the table. Also the flag table->ibd_file_missing is set TRUE. */ +UNIV_INTERN +int +row_discard_tablespace_for_mysql( +/*=============================*/ + /* out: error code or DB_SUCCESS */ + const char* name, /* in: table name */ + trx_t* trx) /* in: transaction handle */ +{ + dict_foreign_t* foreign; + dulint new_id; + dict_table_t* table; + ibool success; + ulint err; + pars_info_t* info = NULL; + + /* How do we prevent crashes caused by ongoing operations on + the table? Old operations could try to access non-existent + pages. + + 1) SQL queries, INSERT, SELECT, ...: we must get an exclusive + MySQL table lock on the table before we can do DISCARD + TABLESPACE. Then there are no running queries on the table. + + 2) Purge and rollback: we assign a new table id for the + table. Since purge and rollback look for the table based on + the table id, they see the table as 'dropped' and discard + their operations. + + 3) Insert buffer: we remove all entries for the tablespace in + the insert buffer tree; as long as the tablespace mem object + does not exist, ongoing insert buffer page merges are + discarded in buf0rea.c. If we recreate the tablespace mem + object with IMPORT TABLESPACE later, then the tablespace will + have the same id, but the tablespace_version field in the mem + object is different, and ongoing old insert buffer page merges + get discarded. + + 4) Linear readahead and random readahead: we use the same + method as in 3) to discard ongoing operations. + + 5) FOREIGN KEY operations: if + table->n_foreign_key_checks_running > 0, we do not allow the + discard. We also reserve the data dictionary latch. */ + + ut_ad(trx->mysql_thread_id == os_thread_get_curr_id()); + + trx->op_info = "discarding tablespace"; + trx_start_if_not_started(trx); + + /* Serialize data dictionary operations with dictionary mutex: + no deadlocks can occur then in these operations */ + + row_mysql_lock_data_dictionary(trx); + + table = dict_table_get_low(name); + + if (!table) { + err = DB_TABLE_NOT_FOUND; + + goto funct_exit; + } + + if (table->space == 0) { + ut_print_timestamp(stderr); + fputs(" InnoDB: Error: table ", stderr); + ut_print_name(stderr, trx, TRUE, name); + fputs("\n" + "InnoDB: is in the system tablespace 0" + " which cannot be discarded\n", stderr); + err = DB_ERROR; + + goto funct_exit; + } + + if (table->n_foreign_key_checks_running > 0) { + + ut_print_timestamp(stderr); + fputs(" InnoDB: You are trying to DISCARD table ", stderr); + ut_print_name(stderr, trx, TRUE, table->name); + fputs("\n" + "InnoDB: though there is a foreign key check" + " running on it.\n" + "InnoDB: Cannot discard the table.\n", + stderr); + + err = DB_ERROR; + + goto funct_exit; + } + + /* Check if the table is referenced by foreign key constraints from + some other table (not the table itself) */ + + foreign = UT_LIST_GET_FIRST(table->referenced_list); + + while (foreign && foreign->foreign_table == table) { + foreign = UT_LIST_GET_NEXT(referenced_list, foreign); + } + + if (foreign && trx->check_foreigns) { + + FILE* ef = dict_foreign_err_file; + + /* We only allow discarding a referenced table if + FOREIGN_KEY_CHECKS is set to 0 */ + + err = DB_CANNOT_DROP_CONSTRAINT; + + mutex_enter(&dict_foreign_err_mutex); + rewind(ef); + ut_print_timestamp(ef); + + fputs(" Cannot DISCARD table ", ef); + ut_print_name(stderr, trx, TRUE, name); + fputs("\n" + "because it is referenced by ", ef); + ut_print_name(stderr, trx, TRUE, foreign->foreign_table_name); + putc('\n', ef); + mutex_exit(&dict_foreign_err_mutex); + + goto funct_exit; + } + + new_id = dict_hdr_get_new_id(DICT_HDR_TABLE_ID); + + /* Remove all locks except the table-level S and X locks. */ + lock_remove_all_on_table(table, FALSE); + + info = pars_info_create(); + + pars_info_add_str_literal(info, "table_name", name); + pars_info_add_dulint_literal(info, "new_id", new_id); + + err = que_eval_sql(info, + "PROCEDURE DISCARD_TABLESPACE_PROC () IS\n" + "old_id CHAR;\n" + "BEGIN\n" + "SELECT ID INTO old_id\n" + "FROM SYS_TABLES\n" + "WHERE NAME = :table_name\n" + "LOCK IN SHARE MODE;\n" + "IF (SQL % NOTFOUND) THEN\n" + " COMMIT WORK;\n" + " RETURN;\n" + "END IF;\n" + "UPDATE SYS_TABLES SET ID = :new_id\n" + " WHERE ID = old_id;\n" + "UPDATE SYS_COLUMNS SET TABLE_ID = :new_id\n" + " WHERE TABLE_ID = old_id;\n" + "UPDATE SYS_INDEXES SET TABLE_ID = :new_id\n" + " WHERE TABLE_ID = old_id;\n" + "COMMIT WORK;\n" + "END;\n" + , FALSE, trx); + + if (err != DB_SUCCESS) { + trx->error_state = DB_SUCCESS; + trx_general_rollback_for_mysql(trx, FALSE, NULL); + trx->error_state = DB_SUCCESS; + } else { + dict_table_change_id_in_cache(table, new_id); + + success = fil_discard_tablespace(table->space); + + if (!success) { + trx->error_state = DB_SUCCESS; + trx_general_rollback_for_mysql(trx, FALSE, NULL); + trx->error_state = DB_SUCCESS; + + err = DB_ERROR; + } else { + /* Set the flag which tells that now it is legal to + IMPORT a tablespace for this table */ + table->tablespace_discarded = TRUE; + table->ibd_file_missing = TRUE; + } + } + +funct_exit: + trx_commit_for_mysql(trx); + + row_mysql_unlock_data_dictionary(trx); + + trx->op_info = ""; + + return((int) err); +} + +/********************************************************************* +Imports a tablespace. The space id in the .ibd file must match the space id +of the table in the data dictionary. */ +UNIV_INTERN +int +row_import_tablespace_for_mysql( +/*============================*/ + /* out: error code or DB_SUCCESS */ + const char* name, /* in: table name */ + trx_t* trx) /* in: transaction handle */ +{ + dict_table_t* table; + ibool success; + ib_uint64_t current_lsn; + ulint err = DB_SUCCESS; + + ut_ad(trx->mysql_thread_id == os_thread_get_curr_id()); + + trx_start_if_not_started(trx); + + trx->op_info = "importing tablespace"; + + current_lsn = log_get_lsn(); + + /* It is possible, though very improbable, that the lsn's in the + tablespace to be imported have risen above the current system lsn, if + a lengthy purge, ibuf merge, or rollback was performed on a backup + taken with ibbackup. If that is the case, reset page lsn's in the + file. We assume that mysqld was shut down after it performed these + cleanup operations on the .ibd file, so that it stamped the latest lsn + to the FIL_PAGE_FILE_FLUSH_LSN in the first page of the .ibd file. + + TODO: reset also the trx id's in clustered index records and write + a new space id to each data page. That would allow us to import clean + .ibd files from another MySQL installation. */ + + success = fil_reset_too_high_lsns(name, current_lsn); + + if (!success) { + ut_print_timestamp(stderr); + fputs(" InnoDB: Error: cannot reset lsn's in table ", stderr); + ut_print_name(stderr, trx, TRUE, name); + fputs("\n" + "InnoDB: in ALTER TABLE ... IMPORT TABLESPACE\n", + stderr); + + err = DB_ERROR; + + row_mysql_lock_data_dictionary(trx); + + goto funct_exit; + } + + /* Serialize data dictionary operations with dictionary mutex: + no deadlocks can occur then in these operations */ + + row_mysql_lock_data_dictionary(trx); + + table = dict_table_get_low(name); + + if (!table) { + ut_print_timestamp(stderr); + fputs(" InnoDB: table ", stderr); + ut_print_name(stderr, trx, TRUE, name); + fputs("\n" + "InnoDB: does not exist in the InnoDB data dictionary\n" + "InnoDB: in ALTER TABLE ... IMPORT TABLESPACE\n", + stderr); + + err = DB_TABLE_NOT_FOUND; + + goto funct_exit; + } + + if (table->space == 0) { + ut_print_timestamp(stderr); + fputs(" InnoDB: Error: table ", stderr); + ut_print_name(stderr, trx, TRUE, name); + fputs("\n" + "InnoDB: is in the system tablespace 0" + " which cannot be imported\n", stderr); + err = DB_ERROR; + + goto funct_exit; + } + + if (!table->tablespace_discarded) { + ut_print_timestamp(stderr); + fputs(" InnoDB: Error: you are trying to" + " IMPORT a tablespace\n" + "InnoDB: ", stderr); + ut_print_name(stderr, trx, TRUE, name); + fputs(", though you have not called DISCARD on it yet\n" + "InnoDB: during the lifetime of the mysqld process!\n", + stderr); + + err = DB_ERROR; + + goto funct_exit; + } + + /* Play safe and remove all insert buffer entries, though we should + have removed them already when DISCARD TABLESPACE was called */ + + ibuf_delete_for_discarded_space(table->space); + + success = fil_open_single_table_tablespace( + TRUE, table->space, + table->flags == DICT_TF_COMPACT ? 0 : table->flags, + table->name); + if (success) { + table->ibd_file_missing = FALSE; + table->tablespace_discarded = FALSE; + } else { + if (table->ibd_file_missing) { + ut_print_timestamp(stderr); + fputs(" InnoDB: cannot find or open in the" + " database directory the .ibd file of\n" + "InnoDB: table ", stderr); + ut_print_name(stderr, trx, TRUE, name); + fputs("\n" + "InnoDB: in ALTER TABLE ... IMPORT TABLESPACE\n", + stderr); + } + + err = DB_ERROR; + } + +funct_exit: + trx_commit_for_mysql(trx); + + row_mysql_unlock_data_dictionary(trx); + + trx->op_info = ""; + + return((int) err); +} + +/************************************************************************* +Truncates a table for MySQL. */ +UNIV_INTERN +int +row_truncate_table_for_mysql( +/*=========================*/ + /* out: error code or DB_SUCCESS */ + dict_table_t* table, /* in: table handle */ + trx_t* trx) /* in: transaction handle */ +{ + dict_foreign_t* foreign; + ulint err; + mem_heap_t* heap; + byte* buf; + dtuple_t* tuple; + dfield_t* dfield; + dict_index_t* sys_index; + btr_pcur_t pcur; + mtr_t mtr; + dulint new_id; + ulint recreate_space = 0; + pars_info_t* info = NULL; + + /* How do we prevent crashes caused by ongoing operations on + the table? Old operations could try to access non-existent + pages. + + 1) SQL queries, INSERT, SELECT, ...: we must get an exclusive + MySQL table lock on the table before we can do TRUNCATE + TABLE. Then there are no running queries on the table. This is + guaranteed, because in ha_innobase::store_lock(), we do not + weaken the TL_WRITE lock requested by MySQL when executing + SQLCOM_TRUNCATE. + + 2) Purge and rollback: we assign a new table id for the + table. Since purge and rollback look for the table based on + the table id, they see the table as 'dropped' and discard + their operations. + + 3) Insert buffer: TRUNCATE TABLE is analogous to DROP TABLE, + so we do not have to remove insert buffer records, as the + insert buffer works at a low level. If a freed page is later + reallocated, the allocator will remove the ibuf entries for + it. + + When we truncate *.ibd files by recreating them (analogous to + DISCARD TABLESPACE), we remove all entries for the table in the + insert buffer tree. This is not strictly necessary, because + in 6) we will assign a new tablespace identifier, but we can + free up some space in the system tablespace. + + 4) Linear readahead and random readahead: we use the same + method as in 3) to discard ongoing operations. (This is only + relevant for TRUNCATE TABLE by DISCARD TABLESPACE.) + + 5) FOREIGN KEY operations: if + table->n_foreign_key_checks_running > 0, we do not allow the + TRUNCATE. We also reserve the data dictionary latch. + + 6) Crash recovery: To prevent the application of pre-truncation + redo log records on the truncated tablespace, we will assign + a new tablespace identifier to the truncated tablespace. */ + + ut_ad(trx->mysql_thread_id == os_thread_get_curr_id()); + ut_ad(table); + + if (srv_created_new_raw) { + fputs("InnoDB: A new raw disk partition was initialized:\n" + "InnoDB: we do not allow database modifications" + " by the user.\n" + "InnoDB: Shut down mysqld and edit my.cnf so that newraw" + " is replaced with raw.\n", stderr); + + return(DB_ERROR); + } + + trx->op_info = "truncating table"; + + trx_start_if_not_started(trx); + + /* Serialize data dictionary operations with dictionary mutex: + no deadlocks can occur then in these operations */ + + ut_a(trx->dict_operation_lock_mode == 0); + /* Prevent foreign key checks etc. while we are truncating the + table */ + + row_mysql_lock_data_dictionary(trx); + + ut_ad(mutex_own(&(dict_sys->mutex))); +#ifdef UNIV_SYNC_DEBUG + ut_ad(rw_lock_own(&dict_operation_lock, RW_LOCK_EX)); +#endif /* UNIV_SYNC_DEBUG */ + + /* Check if the table is referenced by foreign key constraints from + some other table (not the table itself) */ + + foreign = UT_LIST_GET_FIRST(table->referenced_list); + + while (foreign && foreign->foreign_table == table) { + foreign = UT_LIST_GET_NEXT(referenced_list, foreign); + } + + if (foreign && trx->check_foreigns) { + FILE* ef = dict_foreign_err_file; + + /* We only allow truncating a referenced table if + FOREIGN_KEY_CHECKS is set to 0 */ + + mutex_enter(&dict_foreign_err_mutex); + rewind(ef); + ut_print_timestamp(ef); + + fputs(" Cannot truncate table ", ef); + ut_print_name(ef, trx, TRUE, table->name); + fputs(" by DROP+CREATE\n" + "InnoDB: because it is referenced by ", ef); + ut_print_name(ef, trx, TRUE, foreign->foreign_table_name); + putc('\n', ef); + mutex_exit(&dict_foreign_err_mutex); + + err = DB_ERROR; + goto funct_exit; + } + + /* TODO: could we replace the counter n_foreign_key_checks_running + with lock checks on the table? Acquire here an exclusive lock on the + table, and rewrite lock0lock.c and the lock wait in srv0srv.c so that + they can cope with the table having been truncated here? Foreign key + checks take an IS or IX lock on the table. */ + + if (table->n_foreign_key_checks_running > 0) { + ut_print_timestamp(stderr); + fputs(" InnoDB: Cannot truncate table ", stderr); + ut_print_name(stderr, trx, TRUE, table->name); + fputs(" by DROP+CREATE\n" + "InnoDB: because there is a foreign key check" + " running on it.\n", + stderr); + err = DB_ERROR; + + goto funct_exit; + } + + /* Remove all locks except the table-level S and X locks. */ + lock_remove_all_on_table(table, FALSE); + + trx->table_id = table->id; + + if (table->space && !table->dir_path_of_temp_table) { + /* Discard and create the single-table tablespace. */ + ulint space = table->space; + ulint flags = fil_space_get_flags(space); + + if (flags != ULINT_UNDEFINED + && fil_discard_tablespace(space)) { + + dict_index_t* index; + + space = 0; + + if (fil_create_new_single_table_tablespace( + &space, table->name, FALSE, flags, + FIL_IBD_FILE_INITIAL_SIZE) != DB_SUCCESS) { + ut_print_timestamp(stderr); + fprintf(stderr, + " InnoDB: TRUNCATE TABLE %s failed to" + " create a new tablespace\n", + table->name); + table->ibd_file_missing = 1; + err = DB_ERROR; + goto funct_exit; + } + + recreate_space = space; + + /* Replace the space_id in the data dictionary cache. + The persisent data dictionary (SYS_TABLES.SPACE + and SYS_INDEXES.SPACE) are updated later in this + function. */ + table->space = space; + index = dict_table_get_first_index(table); + do { + index->space = space; + index = dict_table_get_next_index(index); + } while (index); + + mtr_start(&mtr); + fsp_header_init(space, + FIL_IBD_FILE_INITIAL_SIZE, &mtr); + mtr_commit(&mtr); + } + } + + /* scan SYS_INDEXES for all indexes of the table */ + heap = mem_heap_create(800); + + tuple = dtuple_create(heap, 1); + dfield = dtuple_get_nth_field(tuple, 0); + + buf = mem_heap_alloc(heap, 8); + mach_write_to_8(buf, table->id); + + dfield_set_data(dfield, buf, 8); + sys_index = dict_table_get_first_index(dict_sys->sys_indexes); + dict_index_copy_types(tuple, sys_index, 1); + + mtr_start(&mtr); + btr_pcur_open_on_user_rec(sys_index, tuple, PAGE_CUR_GE, + BTR_MODIFY_LEAF, &pcur, &mtr); + for (;;) { + rec_t* rec; + const byte* field; + ulint len; + ulint root_page_no; + + if (!btr_pcur_is_on_user_rec(&pcur)) { + /* The end of SYS_INDEXES has been reached. */ + break; + } + + rec = btr_pcur_get_rec(&pcur); + + field = rec_get_nth_field_old(rec, 0, &len); + ut_ad(len == 8); + + if (memcmp(buf, field, len) != 0) { + /* End of indexes for the table (TABLE_ID mismatch). */ + break; + } + + if (rec_get_deleted_flag(rec, FALSE)) { + /* The index has been dropped. */ + goto next_rec; + } + + /* This call may commit and restart mtr + and reposition pcur. */ + root_page_no = dict_truncate_index_tree(table, recreate_space, + &pcur, &mtr); + + rec = btr_pcur_get_rec(&pcur); + + if (root_page_no != FIL_NULL) { + page_rec_write_index_page_no( + rec, DICT_SYS_INDEXES_PAGE_NO_FIELD, + root_page_no, &mtr); + /* We will need to commit and restart the + mini-transaction in order to avoid deadlocks. + The dict_truncate_index_tree() call has allocated + a page in this mini-transaction, and the rest of + this loop could latch another index page. */ + mtr_commit(&mtr); + mtr_start(&mtr); + btr_pcur_restore_position(BTR_MODIFY_LEAF, + &pcur, &mtr); + } + +next_rec: + btr_pcur_move_to_next_user_rec(&pcur, &mtr); + } + + btr_pcur_close(&pcur); + mtr_commit(&mtr); + + mem_heap_free(heap); + + new_id = dict_hdr_get_new_id(DICT_HDR_TABLE_ID); + + info = pars_info_create(); + + pars_info_add_int4_literal(info, "space", (lint) table->space); + pars_info_add_dulint_literal(info, "old_id", table->id); + pars_info_add_dulint_literal(info, "new_id", new_id); + + err = que_eval_sql(info, + "PROCEDURE RENUMBER_TABLESPACE_PROC () IS\n" + "BEGIN\n" + "UPDATE SYS_TABLES" + " SET ID = :new_id, SPACE = :space\n" + " WHERE ID = :old_id;\n" + "UPDATE SYS_COLUMNS SET TABLE_ID = :new_id\n" + " WHERE TABLE_ID = :old_id;\n" + "UPDATE SYS_INDEXES" + " SET TABLE_ID = :new_id, SPACE = :space\n" + " WHERE TABLE_ID = :old_id;\n" + "COMMIT WORK;\n" + "END;\n" + , FALSE, trx); + + if (err != DB_SUCCESS) { + trx->error_state = DB_SUCCESS; + trx_general_rollback_for_mysql(trx, FALSE, NULL); + trx->error_state = DB_SUCCESS; + ut_print_timestamp(stderr); + fputs(" InnoDB: Unable to assign a new identifier to table ", + stderr); + ut_print_name(stderr, trx, TRUE, table->name); + fputs("\n" + "InnoDB: after truncating it. Background processes" + " may corrupt the table!\n", stderr); + err = DB_ERROR; + } else { + dict_table_change_id_in_cache(table, new_id); + } + + /* MySQL calls ha_innobase::reset_auto_increment() which does + the same thing. */ + dict_table_autoinc_lock(table); + dict_table_autoinc_initialize(table, 1); + dict_table_autoinc_unlock(table); + dict_update_statistics(table); + + trx_commit_for_mysql(trx); + +funct_exit: + + row_mysql_unlock_data_dictionary(trx); + + trx->op_info = ""; + + srv_wake_master_thread(); + + return((int) err); +} + +/************************************************************************* +Drops a table for MySQL. If the name of the dropped table ends in +one of "innodb_monitor", "innodb_lock_monitor", "innodb_tablespace_monitor", +"innodb_table_monitor", then this will also stop the printing of monitor +output by the master thread. If the data dictionary was not already locked +by the transaction, the transaction will be committed. Otherwise, the +data dictionary will remain locked. */ +UNIV_INTERN +int +row_drop_table_for_mysql( +/*=====================*/ + /* out: error code or DB_SUCCESS */ + const char* name, /* in: table name */ + trx_t* trx, /* in: transaction handle */ + ibool drop_db)/* in: TRUE=dropping whole database */ +{ + dict_foreign_t* foreign; + dict_table_t* table; + ulint space_id; + ulint err; + const char* table_name; + ulint namelen; + ibool locked_dictionary = FALSE; + pars_info_t* info = NULL; + + ut_a(name != NULL); + + if (srv_created_new_raw) { + fputs("InnoDB: A new raw disk partition was initialized:\n" + "InnoDB: we do not allow database modifications" + " by the user.\n" + "InnoDB: Shut down mysqld and edit my.cnf so that newraw" + " is replaced with raw.\n", stderr); + + return(DB_ERROR); + } + + trx->op_info = "dropping table"; + + trx_start_if_not_started(trx); + + /* The table name is prefixed with the database name and a '/'. + Certain table names starting with 'innodb_' have their special + meaning regardless of the database name. Thus, we need to + ignore the database name prefix in the comparisons. */ + table_name = strchr(name, '/'); + ut_a(table_name); + table_name++; + namelen = strlen(table_name) + 1; + + if (namelen == sizeof S_innodb_monitor + && !memcmp(table_name, S_innodb_monitor, + sizeof S_innodb_monitor)) { + + /* Table name equals "innodb_monitor": + stop monitor prints */ + + srv_print_innodb_monitor = FALSE; + srv_print_innodb_lock_monitor = FALSE; + } else if (namelen == sizeof S_innodb_lock_monitor + && !memcmp(table_name, S_innodb_lock_monitor, + sizeof S_innodb_lock_monitor)) { + srv_print_innodb_monitor = FALSE; + srv_print_innodb_lock_monitor = FALSE; + } else if (namelen == sizeof S_innodb_tablespace_monitor + && !memcmp(table_name, S_innodb_tablespace_monitor, + sizeof S_innodb_tablespace_monitor)) { + + srv_print_innodb_tablespace_monitor = FALSE; + } else if (namelen == sizeof S_innodb_table_monitor + && !memcmp(table_name, S_innodb_table_monitor, + sizeof S_innodb_table_monitor)) { + + srv_print_innodb_table_monitor = FALSE; + } + + /* Serialize data dictionary operations with dictionary mutex: + no deadlocks can occur then in these operations */ + + if (trx->dict_operation_lock_mode != RW_X_LATCH) { + /* Prevent foreign key checks etc. while we are dropping the + table */ + + row_mysql_lock_data_dictionary(trx); + + locked_dictionary = TRUE; + } + + ut_ad(mutex_own(&(dict_sys->mutex))); +#ifdef UNIV_SYNC_DEBUG + ut_ad(rw_lock_own(&dict_operation_lock, RW_LOCK_EX)); +#endif /* UNIV_SYNC_DEBUG */ + + table = dict_table_get_low(name); + + if (!table) { + err = DB_TABLE_NOT_FOUND; + ut_print_timestamp(stderr); + + fputs(" InnoDB: Error: table ", stderr); + ut_print_name(stderr, trx, TRUE, name); + fputs(" does not exist in the InnoDB internal\n" + "InnoDB: data dictionary though MySQL is" + " trying to drop it.\n" + "InnoDB: Have you copied the .frm file" + " of the table to the\n" + "InnoDB: MySQL database directory" + " from another database?\n" + "InnoDB: You can look for further help from\n" + "InnoDB: http://dev.mysql.com/doc/refman/5.1/en/" + "innodb-troubleshooting.html\n", + stderr); + goto funct_exit; + } + + /* Check if the table is referenced by foreign key constraints from + some other table (not the table itself) */ + + foreign = UT_LIST_GET_FIRST(table->referenced_list); + + while (foreign && foreign->foreign_table == table) { +check_next_foreign: + foreign = UT_LIST_GET_NEXT(referenced_list, foreign); + } + + if (foreign && trx->check_foreigns + && !(drop_db && dict_tables_have_same_db( + name, foreign->foreign_table_name))) { + FILE* ef = dict_foreign_err_file; + + /* We only allow dropping a referenced table if + FOREIGN_KEY_CHECKS is set to 0 */ + + err = DB_CANNOT_DROP_CONSTRAINT; + + mutex_enter(&dict_foreign_err_mutex); + rewind(ef); + ut_print_timestamp(ef); + + fputs(" Cannot drop table ", ef); + ut_print_name(ef, trx, TRUE, name); + fputs("\n" + "because it is referenced by ", ef); + ut_print_name(ef, trx, TRUE, foreign->foreign_table_name); + putc('\n', ef); + mutex_exit(&dict_foreign_err_mutex); + + goto funct_exit; + } + + if (foreign && trx->check_foreigns) { + goto check_next_foreign; + } + + if (table->n_mysql_handles_opened > 0) { + ibool added; + + added = row_add_table_to_background_drop_list(table->name); + + if (added) { + ut_print_timestamp(stderr); + fputs(" InnoDB: Warning: MySQL is" + " trying to drop table ", stderr); + ut_print_name(stderr, trx, TRUE, table->name); + fputs("\n" + "InnoDB: though there are still" + " open handles to it.\n" + "InnoDB: Adding the table to the" + " background drop queue.\n", + stderr); + + /* We return DB_SUCCESS to MySQL though the drop will + happen lazily later */ + err = DB_SUCCESS; + } else { + /* The table is already in the background drop list */ + err = DB_ERROR; + } + + goto funct_exit; + } + + /* TODO: could we replace the counter n_foreign_key_checks_running + with lock checks on the table? Acquire here an exclusive lock on the + table, and rewrite lock0lock.c and the lock wait in srv0srv.c so that + they can cope with the table having been dropped here? Foreign key + checks take an IS or IX lock on the table. */ + + if (table->n_foreign_key_checks_running > 0) { + + const char* table_name = table->name; + ibool added; + + added = row_add_table_to_background_drop_list(table_name); + + if (added) { + ut_print_timestamp(stderr); + fputs(" InnoDB: You are trying to drop table ", + stderr); + ut_print_name(stderr, trx, TRUE, table_name); + fputs("\n" + "InnoDB: though there is a" + " foreign key check running on it.\n" + "InnoDB: Adding the table to" + " the background drop queue.\n", + stderr); + + /* We return DB_SUCCESS to MySQL though the drop will + happen lazily later */ + + err = DB_SUCCESS; + } else { + /* The table is already in the background drop list */ + err = DB_ERROR; + } + + goto funct_exit; + } + + /* Remove all locks there are on the table or its records */ + lock_remove_all_on_table(table, TRUE); + + trx_set_dict_operation(trx, TRX_DICT_OP_TABLE); + trx->table_id = table->id; + + /* We use the private SQL parser of Innobase to generate the + query graphs needed in deleting the dictionary data from system + tables in Innobase. Deleting a row from SYS_INDEXES table also + frees the file segments of the B-tree associated with the index. */ + + info = pars_info_create(); + + pars_info_add_str_literal(info, "table_name", name); + + err = que_eval_sql(info, + "PROCEDURE DROP_TABLE_PROC () IS\n" + "sys_foreign_id CHAR;\n" + "table_id CHAR;\n" + "index_id CHAR;\n" + "foreign_id CHAR;\n" + "found INT;\n" + "BEGIN\n" + "SELECT ID INTO table_id\n" + "FROM SYS_TABLES\n" + "WHERE NAME = :table_name\n" + "LOCK IN SHARE MODE;\n" + "IF (SQL % NOTFOUND) THEN\n" + " RETURN;\n" + "END IF;\n" + "found := 1;\n" + "SELECT ID INTO sys_foreign_id\n" + "FROM SYS_TABLES\n" + "WHERE NAME = 'SYS_FOREIGN'\n" + "LOCK IN SHARE MODE;\n" + "IF (SQL % NOTFOUND) THEN\n" + " found := 0;\n" + "END IF;\n" + "IF (:table_name = 'SYS_FOREIGN') THEN\n" + " found := 0;\n" + "END IF;\n" + "IF (:table_name = 'SYS_FOREIGN_COLS') THEN\n" + " found := 0;\n" + "END IF;\n" + "WHILE found = 1 LOOP\n" + " SELECT ID INTO foreign_id\n" + " FROM SYS_FOREIGN\n" + " WHERE FOR_NAME = :table_name\n" + " AND TO_BINARY(FOR_NAME)\n" + " = TO_BINARY(:table_name)\n" + " LOCK IN SHARE MODE;\n" + " IF (SQL % NOTFOUND) THEN\n" + " found := 0;\n" + " ELSE\n" + " DELETE FROM SYS_FOREIGN_COLS\n" + " WHERE ID = foreign_id;\n" + " DELETE FROM SYS_FOREIGN\n" + " WHERE ID = foreign_id;\n" + " END IF;\n" + "END LOOP;\n" + "found := 1;\n" + "WHILE found = 1 LOOP\n" + " SELECT ID INTO index_id\n" + " FROM SYS_INDEXES\n" + " WHERE TABLE_ID = table_id\n" + " LOCK IN SHARE MODE;\n" + " IF (SQL % NOTFOUND) THEN\n" + " found := 0;\n" + " ELSE\n" + " DELETE FROM SYS_FIELDS\n" + " WHERE INDEX_ID = index_id;\n" + " DELETE FROM SYS_INDEXES\n" + " WHERE ID = index_id\n" + " AND TABLE_ID = table_id;\n" + " END IF;\n" + "END LOOP;\n" + "DELETE FROM SYS_COLUMNS\n" + "WHERE TABLE_ID = table_id;\n" + "DELETE FROM SYS_TABLES\n" + "WHERE ID = table_id;\n" + "END;\n" + , FALSE, trx); + + if (err != DB_SUCCESS) { + ut_a(err == DB_OUT_OF_FILE_SPACE); + + err = DB_MUST_GET_MORE_FILE_SPACE; + + row_mysql_handle_errors(&err, trx, NULL, NULL); + + ut_error; + } else { + ibool is_path; + const char* name_or_path; + mem_heap_t* heap; + + heap = mem_heap_create(200); + + /* Clone the name, in case it has been allocated + from table->heap, which will be freed by + dict_table_remove_from_cache(table) below. */ + name = mem_heap_strdup(heap, name); + space_id = table->space; + + if (table->dir_path_of_temp_table != NULL) { + is_path = TRUE; + name_or_path = mem_heap_strdup( + heap, table->dir_path_of_temp_table); + } else { + is_path = FALSE; + name_or_path = name; + } + + dict_table_remove_from_cache(table); + + if (dict_load_table(name) != NULL) { + ut_print_timestamp(stderr); + fputs(" InnoDB: Error: not able to remove table ", + stderr); + ut_print_name(stderr, trx, TRUE, name); + fputs(" from the dictionary cache!\n", stderr); + err = DB_ERROR; + } + + /* Do not drop possible .ibd tablespace if something went + wrong: we do not want to delete valuable data of the user */ + + if (err == DB_SUCCESS && space_id > 0) { + if (!fil_space_for_table_exists_in_mem(space_id, + name_or_path, + is_path, + FALSE, TRUE)) { + err = DB_SUCCESS; + + fprintf(stderr, + "InnoDB: We removed now the InnoDB" + " internal data dictionary entry\n" + "InnoDB: of table "); + ut_print_name(stderr, trx, TRUE, name); + fprintf(stderr, ".\n"); + } else if (!fil_delete_tablespace(space_id)) { + fprintf(stderr, + "InnoDB: We removed now the InnoDB" + " internal data dictionary entry\n" + "InnoDB: of table "); + ut_print_name(stderr, trx, TRUE, name); + fprintf(stderr, ".\n"); + + ut_print_timestamp(stderr); + fprintf(stderr, + " InnoDB: Error: not able to" + " delete tablespace %lu of table ", + (ulong) space_id); + ut_print_name(stderr, trx, TRUE, name); + fputs("!\n", stderr); + err = DB_ERROR; + } + } + + mem_heap_free(heap); + } +funct_exit: + + if (locked_dictionary) { + trx_commit_for_mysql(trx); + + row_mysql_unlock_data_dictionary(trx); + } + + trx->op_info = ""; + +#ifndef UNIV_HOTBACKUP + srv_wake_master_thread(); +#endif /* !UNIV_HOTBACKUP */ + + return((int) err); +} + +/*********************************************************************** +Drop all foreign keys in a database, see Bug#18942. +Called at the end of row_drop_database_for_mysql(). */ +static +ulint +drop_all_foreign_keys_in_db( +/*========================*/ + /* out: error code or DB_SUCCESS */ + const char* name, /* in: database name which ends to '/' */ + trx_t* trx) /* in: transaction handle */ +{ + pars_info_t* pinfo; + ulint err; + + ut_a(name[strlen(name) - 1] == '/'); + + pinfo = pars_info_create(); + + pars_info_add_str_literal(pinfo, "dbname", name); + +/* true if for_name is not prefixed with dbname */ +#define TABLE_NOT_IN_THIS_DB \ +"SUBSTR(for_name, 0, LENGTH(:dbname)) <> :dbname" + + err = que_eval_sql(pinfo, + "PROCEDURE DROP_ALL_FOREIGN_KEYS_PROC () IS\n" + "foreign_id CHAR;\n" + "for_name CHAR;\n" + "found INT;\n" + "DECLARE CURSOR cur IS\n" + "SELECT ID, FOR_NAME FROM SYS_FOREIGN\n" + "WHERE FOR_NAME >= :dbname\n" + "LOCK IN SHARE MODE\n" + "ORDER BY FOR_NAME;\n" + "BEGIN\n" + "found := 1;\n" + "OPEN cur;\n" + "WHILE found = 1 LOOP\n" + " FETCH cur INTO foreign_id, for_name;\n" + " IF (SQL % NOTFOUND) THEN\n" + " found := 0;\n" + " ELSIF (" TABLE_NOT_IN_THIS_DB ") THEN\n" + " found := 0;\n" + " ELSIF (1=1) THEN\n" + " DELETE FROM SYS_FOREIGN_COLS\n" + " WHERE ID = foreign_id;\n" + " DELETE FROM SYS_FOREIGN\n" + " WHERE ID = foreign_id;\n" + " END IF;\n" + "END LOOP;\n" + "CLOSE cur;\n" + "COMMIT WORK;\n" + "END;\n", + FALSE, /* do not reserve dict mutex, + we are already holding it */ + trx); + + return(err); +} + +/************************************************************************* +Drops a database for MySQL. */ +UNIV_INTERN +int +row_drop_database_for_mysql( +/*========================*/ + /* out: error code or DB_SUCCESS */ + const char* name, /* in: database name which ends to '/' */ + trx_t* trx) /* in: transaction handle */ +{ + dict_table_t* table; + char* table_name; + int err = DB_SUCCESS; + ulint namelen = strlen(name); + + ut_ad(trx->mysql_thread_id == os_thread_get_curr_id()); + ut_a(name != NULL); + ut_a(name[namelen - 1] == '/'); + + trx->op_info = "dropping database"; + + trx_start_if_not_started(trx); +loop: + row_mysql_lock_data_dictionary(trx); + + while ((table_name = dict_get_first_table_name_in_db(name))) { + ut_a(memcmp(table_name, name, namelen) == 0); + + table = dict_table_get_low(table_name); + + ut_a(table); + + /* Wait until MySQL does not have any queries running on + the table */ + + if (table->n_mysql_handles_opened > 0) { + row_mysql_unlock_data_dictionary(trx); + + ut_print_timestamp(stderr); + fputs(" InnoDB: Warning: MySQL is trying to" + " drop database ", stderr); + ut_print_name(stderr, trx, TRUE, name); + fputs("\n" + "InnoDB: though there are still" + " open handles to table ", stderr); + ut_print_name(stderr, trx, TRUE, table_name); + fputs(".\n", stderr); + + os_thread_sleep(1000000); + + mem_free(table_name); + + goto loop; + } + + err = row_drop_table_for_mysql(table_name, trx, TRUE); + trx_commit_for_mysql(trx); + + if (err != DB_SUCCESS) { + fputs("InnoDB: DROP DATABASE ", stderr); + ut_print_name(stderr, trx, TRUE, name); + fprintf(stderr, " failed with error %lu for table ", + (ulint) err); + ut_print_name(stderr, trx, TRUE, table_name); + putc('\n', stderr); + mem_free(table_name); + break; + } + + mem_free(table_name); + } + + if (err == DB_SUCCESS) { + /* after dropping all tables try to drop all leftover + foreign keys in case orphaned ones exist */ + err = (int) drop_all_foreign_keys_in_db(name, trx); + + if (err != DB_SUCCESS) { + fputs("InnoDB: DROP DATABASE ", stderr); + ut_print_name(stderr, trx, TRUE, name); + fprintf(stderr, " failed with error %d while " + "dropping all foreign keys", err); + } + } + + trx_commit_for_mysql(trx); + + row_mysql_unlock_data_dictionary(trx); + + trx->op_info = ""; + + return(err); +} + +/************************************************************************* +Checks if a table name contains the string "/#sql" which denotes temporary +tables in MySQL. */ +static +ibool +row_is_mysql_tmp_table_name( +/*========================*/ + /* out: TRUE if temporary table */ + const char* name) /* in: table name in the form + 'database/tablename' */ +{ + return(strstr(name, "/#sql") != NULL); + /* return(strstr(name, "/@0023sql") != NULL); */ +} + +/******************************************************************** +Delete a single constraint. */ +static +int +row_delete_constraint_low( +/*======================*/ + /* out: error code or DB_SUCCESS */ + const char* id, /* in: constraint id */ + trx_t* trx) /* in: transaction handle */ +{ + pars_info_t* info = pars_info_create(); + + pars_info_add_str_literal(info, "id", id); + + return((int) que_eval_sql(info, + "PROCEDURE DELETE_CONSTRAINT () IS\n" + "BEGIN\n" + "DELETE FROM SYS_FOREIGN_COLS WHERE ID = :id;\n" + "DELETE FROM SYS_FOREIGN WHERE ID = :id;\n" + "END;\n" + , FALSE, trx)); +} + +/******************************************************************** +Delete a single constraint. */ +static +int +row_delete_constraint( +/*==================*/ + /* out: error code or DB_SUCCESS */ + const char* id, /* in: constraint id */ + const char* database_name, /* in: database name, with the + trailing '/' */ + mem_heap_t* heap, /* in: memory heap */ + trx_t* trx) /* in: transaction handle */ +{ + ulint err; + + /* New format constraints have ids <databasename>/<constraintname>. */ + err = row_delete_constraint_low( + mem_heap_strcat(heap, database_name, id), trx); + + if ((err == DB_SUCCESS) && !strchr(id, '/')) { + /* Old format < 4.0.18 constraints have constraint ids + <number>_<number>. We only try deleting them if the + constraint name does not contain a '/' character, otherwise + deleting a new format constraint named 'foo/bar' from + database 'baz' would remove constraint 'bar' from database + 'foo', if it existed. */ + + err = row_delete_constraint_low(id, trx); + } + + return((int) err); +} + +/************************************************************************* +Renames a table for MySQL. */ +UNIV_INTERN +ulint +row_rename_table_for_mysql( +/*=======================*/ + /* out: error code or DB_SUCCESS */ + const char* old_name, /* in: old table name */ + const char* new_name, /* in: new table name */ + trx_t* trx, /* in: transaction handle */ + ibool commit) /* in: if TRUE then commit trx */ +{ + dict_table_t* table; + ulint err = DB_ERROR; + mem_heap_t* heap = NULL; + const char** constraints_to_drop = NULL; + ulint n_constraints_to_drop = 0; + ibool old_is_tmp, new_is_tmp; + pars_info_t* info = NULL; + + ut_ad(trx->mysql_thread_id == os_thread_get_curr_id()); + ut_a(old_name != NULL); + ut_a(new_name != NULL); + + if (srv_created_new_raw || srv_force_recovery) { + fputs("InnoDB: A new raw disk partition was initialized or\n" + "InnoDB: innodb_force_recovery is on: we do not allow\n" + "InnoDB: database modifications by the user. Shut down\n" + "InnoDB: mysqld and edit my.cnf so that newraw" + " is replaced\n" + "InnoDB: with raw, and innodb_force_... is removed.\n", + stderr); + + goto funct_exit; + } else if (row_mysql_is_system_table(new_name)) { + + fprintf(stderr, + "InnoDB: Error: trying to create a MySQL" + " system table %s of type InnoDB.\n" + "InnoDB: MySQL system tables must be" + " of the MyISAM type!\n", + new_name); + + goto funct_exit; + } + + trx->op_info = "renaming table"; + trx_start_if_not_started(trx); + + old_is_tmp = row_is_mysql_tmp_table_name(old_name); + new_is_tmp = row_is_mysql_tmp_table_name(new_name); + + table = dict_table_get_low(old_name); + + if (!table) { + err = DB_TABLE_NOT_FOUND; + ut_print_timestamp(stderr); + + fputs(" InnoDB: Error: table ", stderr); + ut_print_name(stderr, trx, TRUE, old_name); + fputs(" does not exist in the InnoDB internal\n" + "InnoDB: data dictionary though MySQL is" + " trying to rename the table.\n" + "InnoDB: Have you copied the .frm file" + " of the table to the\n" + "InnoDB: MySQL database directory" + " from another database?\n" + "InnoDB: You can look for further help from\n" + "InnoDB: http://dev.mysql.com/doc/refman/5.1/en/" + "innodb-troubleshooting.html\n", + stderr); + goto funct_exit; + } else if (table->ibd_file_missing) { + err = DB_TABLE_NOT_FOUND; + ut_print_timestamp(stderr); + + fputs(" InnoDB: Error: table ", stderr); + ut_print_name(stderr, trx, TRUE, old_name); + fputs(" does not have an .ibd file" + " in the database directory.\n" + "InnoDB: You can look for further help from\n" + "InnoDB: http://dev.mysql.com/doc/refman/5.1/en/" + "innodb-troubleshooting.html\n", + stderr); + goto funct_exit; + } else if (new_is_tmp) { + /* MySQL is doing an ALTER TABLE command and it renames the + original table to a temporary table name. We want to preserve + the original foreign key constraint definitions despite the + name change. An exception is those constraints for which + the ALTER TABLE contained DROP FOREIGN KEY <foreign key id>.*/ + + heap = mem_heap_create(100); + + err = dict_foreign_parse_drop_constraints( + heap, trx, table, &n_constraints_to_drop, + &constraints_to_drop); + + if (err != DB_SUCCESS) { + + goto funct_exit; + } + } + + /* We use the private SQL parser of Innobase to generate the query + graphs needed in updating the dictionary data from system tables. */ + + info = pars_info_create(); + + pars_info_add_str_literal(info, "new_table_name", new_name); + pars_info_add_str_literal(info, "old_table_name", old_name); + + err = que_eval_sql(info, + "PROCEDURE RENAME_TABLE () IS\n" + "BEGIN\n" + "UPDATE SYS_TABLES SET NAME = :new_table_name\n" + " WHERE NAME = :old_table_name;\n" + "END;\n" + , FALSE, trx); + + if (err != DB_SUCCESS) { + + goto end; + } else if (!new_is_tmp) { + /* Rename all constraints. */ + + info = pars_info_create(); + + pars_info_add_str_literal(info, "new_table_name", new_name); + pars_info_add_str_literal(info, "old_table_name", old_name); + + err = que_eval_sql( + info, + "PROCEDURE RENAME_CONSTRAINT_IDS () IS\n" + "gen_constr_prefix CHAR;\n" + "new_db_name CHAR;\n" + "foreign_id CHAR;\n" + "new_foreign_id CHAR;\n" + "old_db_name_len INT;\n" + "old_t_name_len INT;\n" + "new_db_name_len INT;\n" + "id_len INT;\n" + "found INT;\n" + "BEGIN\n" + "found := 1;\n" + "old_db_name_len := INSTR(:old_table_name, '/')-1;\n" + "new_db_name_len := INSTR(:new_table_name, '/')-1;\n" + "new_db_name := SUBSTR(:new_table_name, 0,\n" + " new_db_name_len);\n" + "old_t_name_len := LENGTH(:old_table_name);\n" + "gen_constr_prefix := CONCAT(:old_table_name,\n" + " '_ibfk_');\n" + "WHILE found = 1 LOOP\n" + " SELECT ID INTO foreign_id\n" + " FROM SYS_FOREIGN\n" + " WHERE FOR_NAME = :old_table_name\n" + " AND TO_BINARY(FOR_NAME)\n" + " = TO_BINARY(:old_table_name)\n" + " LOCK IN SHARE MODE;\n" + " IF (SQL % NOTFOUND) THEN\n" + " found := 0;\n" + " ELSE\n" + " UPDATE SYS_FOREIGN\n" + " SET FOR_NAME = :new_table_name\n" + " WHERE ID = foreign_id;\n" + " id_len := LENGTH(foreign_id);\n" + " IF (INSTR(foreign_id, '/') > 0) THEN\n" + " IF (INSTR(foreign_id,\n" + " gen_constr_prefix) > 0)\n" + " THEN\n" + " new_foreign_id :=\n" + " CONCAT(:new_table_name,\n" + " SUBSTR(foreign_id, old_t_name_len,\n" + " id_len - old_t_name_len));\n" + " ELSE\n" + " new_foreign_id :=\n" + " CONCAT(new_db_name,\n" + " SUBSTR(foreign_id,\n" + " old_db_name_len,\n" + " id_len - old_db_name_len));\n" + " END IF;\n" + " UPDATE SYS_FOREIGN\n" + " SET ID = new_foreign_id\n" + " WHERE ID = foreign_id;\n" + " UPDATE SYS_FOREIGN_COLS\n" + " SET ID = new_foreign_id\n" + " WHERE ID = foreign_id;\n" + " END IF;\n" + " END IF;\n" + "END LOOP;\n" + "UPDATE SYS_FOREIGN SET REF_NAME = :new_table_name\n" + "WHERE REF_NAME = :old_table_name\n" + " AND TO_BINARY(REF_NAME)\n" + " = TO_BINARY(:old_table_name);\n" + "END;\n" + , FALSE, trx); + + } else if (n_constraints_to_drop > 0) { + /* Drop some constraints of tmp tables. */ + + ulint db_name_len = dict_get_db_name_len(old_name) + 1; + char* db_name = mem_heap_strdupl(heap, old_name, + db_name_len); + ulint i; + + for (i = 0; i < n_constraints_to_drop; i++) { + err = row_delete_constraint(constraints_to_drop[i], + db_name, heap, trx); + + if (err != DB_SUCCESS) { + break; + } + } + } + +end: + if (err != DB_SUCCESS) { + if (err == DB_DUPLICATE_KEY) { + ut_print_timestamp(stderr); + fputs(" InnoDB: Error; possible reasons:\n" + "InnoDB: 1) Table rename would cause" + " two FOREIGN KEY constraints\n" + "InnoDB: to have the same internal name" + " in case-insensitive comparison.\n" + "InnoDB: 2) table ", stderr); + ut_print_name(stderr, trx, TRUE, new_name); + fputs(" exists in the InnoDB internal data\n" + "InnoDB: dictionary though MySQL is" + " trying to rename table ", stderr); + ut_print_name(stderr, trx, TRUE, old_name); + fputs(" to it.\n" + "InnoDB: Have you deleted the .frm file" + " and not used DROP TABLE?\n" + "InnoDB: You can look for further help from\n" + "InnoDB: http://dev.mysql.com/doc/refman/5.1/en/" + "innodb-troubleshooting.html\n" + "InnoDB: If table ", stderr); + ut_print_name(stderr, trx, TRUE, new_name); + fputs(" is a temporary table #sql..., then" + " it can be that\n" + "InnoDB: there are still queries running" + " on the table, and it will be\n" + "InnoDB: dropped automatically when" + " the queries end.\n" + "InnoDB: You can drop the orphaned table" + " inside InnoDB by\n" + "InnoDB: creating an InnoDB table with" + " the same name in another\n" + "InnoDB: database and copying the .frm file" + " to the current database.\n" + "InnoDB: Then MySQL thinks the table exists," + " and DROP TABLE will\n" + "InnoDB: succeed.\n", stderr); + } + trx->error_state = DB_SUCCESS; + trx_general_rollback_for_mysql(trx, FALSE, NULL); + trx->error_state = DB_SUCCESS; + } else { + /* The following call will also rename the .ibd data file if + the table is stored in a single-table tablespace */ + + if (!dict_table_rename_in_cache(table, new_name, + !new_is_tmp)) { + trx->error_state = DB_SUCCESS; + trx_general_rollback_for_mysql(trx, FALSE, NULL); + trx->error_state = DB_SUCCESS; + goto funct_exit; + } + + /* We only want to switch off some of the type checking in + an ALTER, not in a RENAME. */ + + err = dict_load_foreigns( + new_name, !old_is_tmp || trx->check_foreigns); + + if (err != DB_SUCCESS) { + ut_print_timestamp(stderr); + + if (old_is_tmp) { + fputs(" InnoDB: Error: in ALTER TABLE ", + stderr); + ut_print_name(stderr, trx, TRUE, new_name); + fputs("\n" + "InnoDB: has or is referenced" + " in foreign key constraints\n" + "InnoDB: which are not compatible" + " with the new table definition.\n", + stderr); + } else { + fputs(" InnoDB: Error: in RENAME TABLE" + " table ", + stderr); + ut_print_name(stderr, trx, TRUE, new_name); + fputs("\n" + "InnoDB: is referenced in" + " foreign key constraints\n" + "InnoDB: which are not compatible" + " with the new table definition.\n", + stderr); + } + + ut_a(dict_table_rename_in_cache(table, + old_name, FALSE)); + trx->error_state = DB_SUCCESS; + trx_general_rollback_for_mysql(trx, FALSE, NULL); + trx->error_state = DB_SUCCESS; + } + } + +funct_exit: + + if (commit) { + trx_commit_for_mysql(trx); + } + + if (UNIV_LIKELY_NULL(heap)) { + mem_heap_free(heap); + } + + trx->op_info = ""; + + return(err); +} + +/************************************************************************* +Checks that the index contains entries in an ascending order, unique +constraint is not broken, and calculates the number of index entries +in the read view of the current transaction. */ +static +ibool +row_scan_and_check_index( +/*=====================*/ + /* out: TRUE if ok */ + row_prebuilt_t* prebuilt, /* in: prebuilt struct in MySQL */ + dict_index_t* index, /* in: index */ + ulint* n_rows) /* out: number of entries seen in the + current consistent read */ +{ + dtuple_t* prev_entry = NULL; + ulint matched_fields; + ulint matched_bytes; + byte* buf; + ulint ret; + rec_t* rec; + ibool is_ok = TRUE; + int cmp; + ibool contains_null; + ulint i; + ulint cnt; + mem_heap_t* heap = NULL; + ulint n_ext; + ulint offsets_[REC_OFFS_NORMAL_SIZE]; + ulint* offsets; + rec_offs_init(offsets_); + + *n_rows = 0; + + buf = mem_alloc(UNIV_PAGE_SIZE); + heap = mem_heap_create(100); + + /* Make a dummy template in prebuilt, which we will use + in scanning the index entries */ + + prebuilt->index = index; + prebuilt->sql_stat_start = TRUE; + prebuilt->template_type = ROW_MYSQL_DUMMY_TEMPLATE; + prebuilt->n_template = 0; + prebuilt->need_to_access_clustered = FALSE; + + dtuple_set_n_fields(prebuilt->search_tuple, 0); + + prebuilt->select_lock_type = LOCK_NONE; + cnt = 1000; + + ret = row_search_for_mysql(buf, PAGE_CUR_G, prebuilt, 0, 0); +loop: + /* Check thd->killed every 1,000 scanned rows */ + if (--cnt == 0) { + if (trx_is_interrupted(prebuilt->trx)) { + goto func_exit; + } + cnt = 1000; + } + if (ret != DB_SUCCESS) { +func_exit: + mem_free(buf); + mem_heap_free(heap); + + return(is_ok); + } + + *n_rows = *n_rows + 1; + + /* row_search... returns the index record in buf, record origin offset + within buf stored in the first 4 bytes, because we have built a dummy + template */ + + rec = buf + mach_read_from_4(buf); + + offsets = rec_get_offsets(rec, index, offsets_, + ULINT_UNDEFINED, &heap); + + if (prev_entry != NULL) { + matched_fields = 0; + matched_bytes = 0; + + cmp = cmp_dtuple_rec_with_match(prev_entry, rec, offsets, + &matched_fields, + &matched_bytes); + contains_null = FALSE; + + /* In a unique secondary index we allow equal key values if + they contain SQL NULLs */ + + for (i = 0; + i < dict_index_get_n_ordering_defined_by_user(index); + i++) { + if (UNIV_SQL_NULL == dfield_get_len( + dtuple_get_nth_field(prev_entry, i))) { + + contains_null = TRUE; + } + } + + if (cmp > 0) { + fputs("InnoDB: index records in a wrong order in ", + stderr); +not_ok: + dict_index_name_print(stderr, + prebuilt->trx, index); + fputs("\n" + "InnoDB: prev record ", stderr); + dtuple_print(stderr, prev_entry); + fputs("\n" + "InnoDB: record ", stderr); + rec_print_new(stderr, rec, offsets); + putc('\n', stderr); + is_ok = FALSE; + } else if (dict_index_is_unique(index) + && !contains_null + && matched_fields + >= dict_index_get_n_ordering_defined_by_user( + index)) { + + fputs("InnoDB: duplicate key in ", stderr); + goto not_ok; + } + } + + { + mem_heap_t* tmp_heap = NULL; + + /* Empty the heap on each round. But preserve offsets[] + for the row_rec_to_index_entry() call, by copying them + into a separate memory heap when needed. */ + if (UNIV_UNLIKELY(offsets != offsets_)) { + ulint size = rec_offs_get_n_alloc(offsets) + * sizeof *offsets; + + tmp_heap = mem_heap_create(size); + offsets = mem_heap_dup(tmp_heap, offsets, size); + } + + mem_heap_empty(heap); + + prev_entry = row_rec_to_index_entry(ROW_COPY_DATA, rec, + index, offsets, + &n_ext, heap); + + if (UNIV_LIKELY_NULL(tmp_heap)) { + mem_heap_free(tmp_heap); + } + } + + ret = row_search_for_mysql(buf, PAGE_CUR_G, prebuilt, 0, ROW_SEL_NEXT); + + goto loop; +} + +/************************************************************************* +Checks a table for corruption. */ +UNIV_INTERN +ulint +row_check_table_for_mysql( +/*======================*/ + /* out: DB_ERROR or DB_SUCCESS */ + row_prebuilt_t* prebuilt) /* in: prebuilt struct in MySQL + handle */ +{ + dict_table_t* table = prebuilt->table; + dict_index_t* index; + ulint n_rows; + ulint n_rows_in_table = ULINT_UNDEFINED; + ulint ret = DB_SUCCESS; + ulint old_isolation_level; + + if (table->ibd_file_missing) { + ut_print_timestamp(stderr); + fprintf(stderr, " InnoDB: Error:\n" + "InnoDB: MySQL is trying to use a table handle" + " but the .ibd file for\n" + "InnoDB: table %s does not exist.\n" + "InnoDB: Have you deleted the .ibd file" + " from the database directory under\n" + "InnoDB: the MySQL datadir, or have you" + " used DISCARD TABLESPACE?\n" + "InnoDB: Look from\n" + "InnoDB: http://dev.mysql.com/doc/refman/5.1/en/" + "innodb-troubleshooting.html\n" + "InnoDB: how you can resolve the problem.\n", + table->name); + return(DB_ERROR); + } + + prebuilt->trx->op_info = "checking table"; + + old_isolation_level = prebuilt->trx->isolation_level; + + /* We must run the index record counts at an isolation level + >= READ COMMITTED, because a dirty read can see a wrong number + of records in some index; to play safe, we use always + REPEATABLE READ here */ + + prebuilt->trx->isolation_level = TRX_ISO_REPEATABLE_READ; + + /* Enlarge the fatal lock wait timeout during CHECK TABLE. */ + mutex_enter(&kernel_mutex); + srv_fatal_semaphore_wait_threshold += 7200; /* 2 hours */ + mutex_exit(&kernel_mutex); + + index = dict_table_get_first_index(table); + + while (index != NULL) { + /* fputs("Validating index ", stderr); + ut_print_name(stderr, trx, FALSE, index->name); + putc('\n', stderr); */ + + if (!btr_validate_index(index, prebuilt->trx)) { + ret = DB_ERROR; + } else { + if (!row_scan_and_check_index(prebuilt,index, &n_rows)){ + ret = DB_ERROR; + } + + if (trx_is_interrupted(prebuilt->trx)) { + break; + } + + /* fprintf(stderr, "%lu entries in index %s\n", n_rows, + index->name); */ + + if (index == dict_table_get_first_index(table)) { + n_rows_in_table = n_rows; + } else if (n_rows != n_rows_in_table) { + + ret = DB_ERROR; + + fputs("Error: ", stderr); + dict_index_name_print(stderr, + prebuilt->trx, index); + fprintf(stderr, + " contains %lu entries," + " should be %lu\n", + (ulong) n_rows, + (ulong) n_rows_in_table); + } + } + + index = dict_table_get_next_index(index); + } + + /* Restore the original isolation level */ + prebuilt->trx->isolation_level = old_isolation_level; + + /* We validate also the whole adaptive hash index for all tables + at every CHECK TABLE */ + + if (!btr_search_validate()) { + + ret = DB_ERROR; + } + + /* Restore the fatal lock wait timeout after CHECK TABLE. */ + mutex_enter(&kernel_mutex); + srv_fatal_semaphore_wait_threshold -= 7200; /* 2 hours */ + mutex_exit(&kernel_mutex); + + prebuilt->trx->op_info = ""; + + return(ret); +} +#endif /* !UNIV_HOTBACKUP */ + +/************************************************************************* +Determines if a table is a magic monitor table. */ +UNIV_INTERN +ibool +row_is_magic_monitor_table( +/*=======================*/ + /* out: TRUE if monitor table */ + const char* table_name) /* in: name of the table, in the + form database/table_name */ +{ + const char* name; /* table_name without database/ */ + ulint len; + + name = strchr(table_name, '/'); + ut_a(name != NULL); + name++; + len = strlen(name) + 1; + + if (STR_EQ(name, len, S_innodb_monitor) + || STR_EQ(name, len, S_innodb_lock_monitor) + || STR_EQ(name, len, S_innodb_tablespace_monitor) + || STR_EQ(name, len, S_innodb_table_monitor) + || STR_EQ(name, len, S_innodb_mem_validate)) { + + return(TRUE); + } + + return(FALSE); +} diff --git a/storage/xtradb/row/row0purge.c b/storage/xtradb/row/row0purge.c new file mode 100644 index 00000000000..8c3f9b993ba --- /dev/null +++ b/storage/xtradb/row/row0purge.c @@ -0,0 +1,690 @@ +/***************************************************************************** + +Copyright (c) 1997, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/****************************************************** +Purge obsolete records + +Created 3/14/1997 Heikki Tuuri +*******************************************************/ + +#include "row0purge.h" + +#ifdef UNIV_NONINL +#include "row0purge.ic" +#endif + +#include "fsp0fsp.h" +#include "mach0data.h" +#include "trx0rseg.h" +#include "trx0trx.h" +#include "trx0roll.h" +#include "trx0undo.h" +#include "trx0purge.h" +#include "trx0rec.h" +#include "que0que.h" +#include "row0row.h" +#include "row0upd.h" +#include "row0vers.h" +#include "row0mysql.h" +#include "log0log.h" + +/************************************************************************ +Creates a purge node to a query graph. */ +UNIV_INTERN +purge_node_t* +row_purge_node_create( +/*==================*/ + /* out, own: purge node */ + que_thr_t* parent, /* in: parent node, i.e., a thr node */ + mem_heap_t* heap) /* in: memory heap where created */ +{ + purge_node_t* node; + + ut_ad(parent && heap); + + node = mem_heap_alloc(heap, sizeof(purge_node_t)); + + node->common.type = QUE_NODE_PURGE; + node->common.parent = parent; + + node->heap = mem_heap_create(256); + + return(node); +} + +/*************************************************************** +Repositions the pcur in the purge node on the clustered index record, +if found. */ +static +ibool +row_purge_reposition_pcur( +/*======================*/ + /* out: TRUE if the record was found */ + ulint mode, /* in: latching mode */ + purge_node_t* node, /* in: row purge node */ + mtr_t* mtr) /* in: mtr */ +{ + ibool found; + + if (node->found_clust) { + found = btr_pcur_restore_position(mode, &(node->pcur), mtr); + + return(found); + } + + found = row_search_on_row_ref(&(node->pcur), mode, node->table, + node->ref, mtr); + node->found_clust = found; + + if (found) { + btr_pcur_store_position(&(node->pcur), mtr); + } + + return(found); +} + +/*************************************************************** +Removes a delete marked clustered index record if possible. */ +static +ibool +row_purge_remove_clust_if_poss_low( +/*===============================*/ + /* out: TRUE if success, or if not found, or + if modified after the delete marking */ + purge_node_t* node, /* in: row purge node */ + ulint mode) /* in: BTR_MODIFY_LEAF or BTR_MODIFY_TREE */ +{ + dict_index_t* index; + btr_pcur_t* pcur; + btr_cur_t* btr_cur; + ibool success; + ulint err; + mtr_t mtr; + rec_t* rec; + mem_heap_t* heap = NULL; + ulint offsets_[REC_OFFS_NORMAL_SIZE]; + rec_offs_init(offsets_); + + index = dict_table_get_first_index(node->table); + + pcur = &(node->pcur); + btr_cur = btr_pcur_get_btr_cur(pcur); + + mtr_start(&mtr); + + success = row_purge_reposition_pcur(mode, node, &mtr); + + if (!success) { + /* The record is already removed */ + + btr_pcur_commit_specify_mtr(pcur, &mtr); + + return(TRUE); + } + + rec = btr_pcur_get_rec(pcur); + + if (0 != ut_dulint_cmp(node->roll_ptr, row_get_rec_roll_ptr( + rec, index, rec_get_offsets( + rec, index, offsets_, + ULINT_UNDEFINED, &heap)))) { + if (UNIV_LIKELY_NULL(heap)) { + mem_heap_free(heap); + } + /* Someone else has modified the record later: do not remove */ + btr_pcur_commit_specify_mtr(pcur, &mtr); + + return(TRUE); + } + + if (UNIV_LIKELY_NULL(heap)) { + mem_heap_free(heap); + } + + if (mode == BTR_MODIFY_LEAF) { + success = btr_cur_optimistic_delete(btr_cur, &mtr); + } else { + ut_ad(mode == BTR_MODIFY_TREE); + btr_cur_pessimistic_delete(&err, FALSE, btr_cur, + RB_NONE, &mtr); + + if (err == DB_SUCCESS) { + success = TRUE; + } else if (err == DB_OUT_OF_FILE_SPACE) { + success = FALSE; + } else { + ut_error; + } + } + + btr_pcur_commit_specify_mtr(pcur, &mtr); + + return(success); +} + +/*************************************************************** +Removes a clustered index record if it has not been modified after the delete +marking. */ +static +void +row_purge_remove_clust_if_poss( +/*===========================*/ + purge_node_t* node) /* in: row purge node */ +{ + ibool success; + ulint n_tries = 0; + + /* fputs("Purge: Removing clustered record\n", stderr); */ + + success = row_purge_remove_clust_if_poss_low(node, BTR_MODIFY_LEAF); + if (success) { + + return; + } +retry: + success = row_purge_remove_clust_if_poss_low(node, BTR_MODIFY_TREE); + /* The delete operation may fail if we have little + file space left: TODO: easiest to crash the database + and restart with more file space */ + + if (!success && n_tries < BTR_CUR_RETRY_DELETE_N_TIMES) { + n_tries++; + + os_thread_sleep(BTR_CUR_RETRY_SLEEP_TIME); + + goto retry; + } + + ut_a(success); +} + +/*************************************************************** +Removes a secondary index entry if possible. */ +static +ibool +row_purge_remove_sec_if_poss_low( +/*=============================*/ + /* out: TRUE if success or if not found */ + purge_node_t* node, /* in: row purge node */ + dict_index_t* index, /* in: index */ + const dtuple_t* entry, /* in: index entry */ + ulint mode) /* in: latch mode BTR_MODIFY_LEAF or + BTR_MODIFY_TREE */ +{ + btr_pcur_t pcur; + btr_cur_t* btr_cur; + ibool success; + ibool old_has = 0; /* remove warning */ + ibool found; + ulint err; + mtr_t mtr; + mtr_t mtr_vers; + + log_free_check(); + mtr_start(&mtr); + + found = row_search_index_entry(index, entry, mode, &pcur, &mtr); + + if (!found) { + /* Not found. This is a legitimate condition. In a + rollback, InnoDB will remove secondary recs that would + be purged anyway. Then the actual purge will not find + the secondary index record. Also, the purge itself is + eager: if it comes to consider a secondary index + record, and notices it does not need to exist in the + index, it will remove it. Then if/when the purge + comes to consider the secondary index record a second + time, it will not exist any more in the index. */ + + /* fputs("PURGE:........sec entry not found\n", stderr); */ + /* dtuple_print(stderr, entry); */ + + btr_pcur_close(&pcur); + mtr_commit(&mtr); + + return(TRUE); + } + + btr_cur = btr_pcur_get_btr_cur(&pcur); + + /* We should remove the index record if no later version of the row, + which cannot be purged yet, requires its existence. If some requires, + we should do nothing. */ + + mtr_start(&mtr_vers); + + success = row_purge_reposition_pcur(BTR_SEARCH_LEAF, node, &mtr_vers); + + if (success) { + old_has = row_vers_old_has_index_entry( + TRUE, btr_pcur_get_rec(&(node->pcur)), + &mtr_vers, index, entry); + } + + btr_pcur_commit_specify_mtr(&(node->pcur), &mtr_vers); + + if (!success || !old_has) { + /* Remove the index record */ + + if (mode == BTR_MODIFY_LEAF) { + success = btr_cur_optimistic_delete(btr_cur, &mtr); + } else { + ut_ad(mode == BTR_MODIFY_TREE); + btr_cur_pessimistic_delete(&err, FALSE, btr_cur, + RB_NONE, &mtr); + success = err == DB_SUCCESS; + ut_a(success || err == DB_OUT_OF_FILE_SPACE); + } + } + + btr_pcur_close(&pcur); + mtr_commit(&mtr); + + return(success); +} + +/*************************************************************** +Removes a secondary index entry if possible. */ +UNIV_INLINE +void +row_purge_remove_sec_if_poss( +/*=========================*/ + purge_node_t* node, /* in: row purge node */ + dict_index_t* index, /* in: index */ + dtuple_t* entry) /* in: index entry */ +{ + ibool success; + ulint n_tries = 0; + + /* fputs("Purge: Removing secondary record\n", stderr); */ + + success = row_purge_remove_sec_if_poss_low(node, index, entry, + BTR_MODIFY_LEAF); + if (success) { + + return; + } +retry: + success = row_purge_remove_sec_if_poss_low(node, index, entry, + BTR_MODIFY_TREE); + /* The delete operation may fail if we have little + file space left: TODO: easiest to crash the database + and restart with more file space */ + + if (!success && n_tries < BTR_CUR_RETRY_DELETE_N_TIMES) { + + n_tries++; + + os_thread_sleep(BTR_CUR_RETRY_SLEEP_TIME); + + goto retry; + } + + ut_a(success); +} + +/*************************************************************** +Purges a delete marking of a record. */ +static +void +row_purge_del_mark( +/*===============*/ + purge_node_t* node) /* in: row purge node */ +{ + mem_heap_t* heap; + dtuple_t* entry; + dict_index_t* index; + + ut_ad(node); + + heap = mem_heap_create(1024); + + while (node->index != NULL) { + index = node->index; + + /* Build the index entry */ + entry = row_build_index_entry(node->row, NULL, index, heap); + ut_a(entry); + row_purge_remove_sec_if_poss(node, index, entry); + + node->index = dict_table_get_next_index(node->index); + } + + mem_heap_free(heap); + + row_purge_remove_clust_if_poss(node); +} + +/*************************************************************** +Purges an update of an existing record. Also purges an update of a delete +marked record if that record contained an externally stored field. */ +static +void +row_purge_upd_exist_or_extern( +/*==========================*/ + purge_node_t* node) /* in: row purge node */ +{ + mem_heap_t* heap; + dtuple_t* entry; + dict_index_t* index; + ibool is_insert; + ulint rseg_id; + ulint page_no; + ulint offset; + ulint i; + mtr_t mtr; + + ut_ad(node); + + if (node->rec_type == TRX_UNDO_UPD_DEL_REC) { + + goto skip_secondaries; + } + + heap = mem_heap_create(1024); + + while (node->index != NULL) { + index = node->index; + + if (row_upd_changes_ord_field_binary(NULL, node->index, + node->update)) { + /* Build the older version of the index entry */ + entry = row_build_index_entry(node->row, NULL, + index, heap); + ut_a(entry); + row_purge_remove_sec_if_poss(node, index, entry); + } + + node->index = dict_table_get_next_index(node->index); + } + + mem_heap_free(heap); + +skip_secondaries: + /* Free possible externally stored fields */ + for (i = 0; i < upd_get_n_fields(node->update); i++) { + + const upd_field_t* ufield + = upd_get_nth_field(node->update, i); + + if (dfield_is_ext(&ufield->new_val)) { + buf_block_t* block; + ulint internal_offset; + byte* data_field; + + /* We use the fact that new_val points to + node->undo_rec and get thus the offset of + dfield data inside the undo record. Then we + can calculate from node->roll_ptr the file + address of the new_val data */ + + internal_offset + = ((const byte*) + dfield_get_data(&ufield->new_val)) + - node->undo_rec; + + ut_a(internal_offset < UNIV_PAGE_SIZE); + + trx_undo_decode_roll_ptr(node->roll_ptr, + &is_insert, &rseg_id, + &page_no, &offset); + mtr_start(&mtr); + + /* We have to acquire an X-latch to the clustered + index tree */ + + index = dict_table_get_first_index(node->table); + + mtr_x_lock(dict_index_get_lock(index), &mtr); + + /* NOTE: we must also acquire an X-latch to the + root page of the tree. We will need it when we + free pages from the tree. If the tree is of height 1, + the tree X-latch does NOT protect the root page, + because it is also a leaf page. Since we will have a + latch on an undo log page, we would break the + latching order if we would only later latch the + root page of such a tree! */ + + btr_root_get(index, &mtr); + + /* We assume in purge of externally stored fields + that the space id of the undo log record is 0! */ + + block = buf_page_get(0, 0, page_no, RW_X_LATCH, &mtr); + buf_block_dbg_add_level(block, SYNC_TRX_UNDO_PAGE); + + data_field = buf_block_get_frame(block) + + offset + internal_offset; + + ut_a(dfield_get_len(&ufield->new_val) + >= BTR_EXTERN_FIELD_REF_SIZE); + btr_free_externally_stored_field( + index, + data_field + dfield_get_len(&ufield->new_val) + - BTR_EXTERN_FIELD_REF_SIZE, + NULL, NULL, NULL, 0, RB_NONE, &mtr); + mtr_commit(&mtr); + } + } +} + +/*************************************************************** +Parses the row reference and other info in a modify undo log record. */ +static +ibool +row_purge_parse_undo_rec( +/*=====================*/ + /* out: TRUE if purge operation required: + NOTE that then the CALLER must unfreeze + data dictionary! */ + purge_node_t* node, /* in: row undo node */ + ibool* updated_extern, + /* out: TRUE if an externally stored field + was updated */ + que_thr_t* thr) /* in: query thread */ +{ + dict_index_t* clust_index; + byte* ptr; + trx_t* trx; + dulint undo_no; + dulint table_id; + dulint trx_id; + dulint roll_ptr; + ulint info_bits; + ulint type; + ulint cmpl_info; + + ut_ad(node && thr); + + trx = thr_get_trx(thr); + + ptr = trx_undo_rec_get_pars(node->undo_rec, &type, &cmpl_info, + updated_extern, &undo_no, &table_id); + node->rec_type = type; + + if (type == TRX_UNDO_UPD_DEL_REC && !(*updated_extern)) { + + return(FALSE); + } + + ptr = trx_undo_update_rec_get_sys_cols(ptr, &trx_id, &roll_ptr, + &info_bits); + node->table = NULL; + + if (type == TRX_UNDO_UPD_EXIST_REC + && cmpl_info & UPD_NODE_NO_ORD_CHANGE && !(*updated_extern)) { + + /* Purge requires no changes to indexes: we may return */ + + return(FALSE); + } + + /* Prevent DROP TABLE etc. from running when we are doing the purge + for this row */ + + row_mysql_freeze_data_dictionary(trx); + + mutex_enter(&(dict_sys->mutex)); + + node->table = dict_table_get_on_id_low(table_id); + + mutex_exit(&(dict_sys->mutex)); + + if (node->table == NULL) { + /* The table has been dropped: no need to do purge */ +err_exit: + row_mysql_unfreeze_data_dictionary(trx); + return(FALSE); + } + + if (node->table->ibd_file_missing) { + /* We skip purge of missing .ibd files */ + + node->table = NULL; + + goto err_exit; + } + + clust_index = dict_table_get_first_index(node->table); + + if (clust_index == NULL) { + /* The table was corrupt in the data dictionary */ + + goto err_exit; + } + + ptr = trx_undo_rec_get_row_ref(ptr, clust_index, &(node->ref), + node->heap); + + ptr = trx_undo_update_rec_get_update(ptr, clust_index, type, trx_id, + roll_ptr, info_bits, trx, + node->heap, &(node->update)); + + /* Read to the partial row the fields that occur in indexes */ + + if (!(cmpl_info & UPD_NODE_NO_ORD_CHANGE)) { + ptr = trx_undo_rec_get_partial_row( + ptr, clust_index, &node->row, + type == TRX_UNDO_UPD_DEL_REC, + node->heap); + } + + return(TRUE); +} + +/*************************************************************** +Fetches an undo log record and does the purge for the recorded operation. +If none left, or the current purge completed, returns the control to the +parent node, which is always a query thread node. */ +static +ulint +row_purge( +/*======*/ + /* out: DB_SUCCESS if operation successfully + completed, else error code */ + purge_node_t* node, /* in: row purge node */ + que_thr_t* thr) /* in: query thread */ +{ + dulint roll_ptr; + ibool purge_needed; + ibool updated_extern; + trx_t* trx; + + ut_ad(node && thr); + + trx = thr_get_trx(thr); + + node->undo_rec = trx_purge_fetch_next_rec(&roll_ptr, + &(node->reservation), + node->heap); + if (!node->undo_rec) { + /* Purge completed for this query thread */ + + thr->run_node = que_node_get_parent(node); + + return(DB_SUCCESS); + } + + node->roll_ptr = roll_ptr; + + if (node->undo_rec == &trx_purge_dummy_rec) { + purge_needed = FALSE; + } else { + purge_needed = row_purge_parse_undo_rec(node, &updated_extern, + thr); + /* If purge_needed == TRUE, we must also remember to unfreeze + data dictionary! */ + } + + if (purge_needed) { + node->found_clust = FALSE; + + node->index = dict_table_get_next_index( + dict_table_get_first_index(node->table)); + + if (node->rec_type == TRX_UNDO_DEL_MARK_REC) { + row_purge_del_mark(node); + + } else if (updated_extern + || node->rec_type == TRX_UNDO_UPD_EXIST_REC) { + + row_purge_upd_exist_or_extern(node); + } + + if (node->found_clust) { + btr_pcur_close(&(node->pcur)); + } + + row_mysql_unfreeze_data_dictionary(trx); + } + + /* Do some cleanup */ + trx_purge_rec_release(node->reservation); + mem_heap_empty(node->heap); + + thr->run_node = node; + + return(DB_SUCCESS); +} + +/*************************************************************** +Does the purge operation for a single undo log record. This is a high-level +function used in an SQL execution graph. */ +UNIV_INTERN +que_thr_t* +row_purge_step( +/*===========*/ + /* out: query thread to run next or NULL */ + que_thr_t* thr) /* in: query thread */ +{ + purge_node_t* node; + ulint err; + + ut_ad(thr); + + node = thr->run_node; + + ut_ad(que_node_get_type(node) == QUE_NODE_PURGE); + + err = row_purge(node, thr); + + ut_ad(err == DB_SUCCESS); + + return(thr); +} diff --git a/storage/xtradb/row/row0row.c b/storage/xtradb/row/row0row.c new file mode 100644 index 00000000000..4343ee2b009 --- /dev/null +++ b/storage/xtradb/row/row0row.c @@ -0,0 +1,1241 @@ +/***************************************************************************** + +Copyright (c) 1996, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/****************************************************** +General row routines + +Created 4/20/1996 Heikki Tuuri +*******************************************************/ + +#include "row0row.h" + +#ifdef UNIV_NONINL +#include "row0row.ic" +#endif + +#include "data0type.h" +#include "dict0dict.h" +#include "btr0btr.h" +#include "ha_prototypes.h" +#include "mach0data.h" +#include "trx0rseg.h" +#include "trx0trx.h" +#include "trx0roll.h" +#include "trx0undo.h" +#include "trx0purge.h" +#include "trx0rec.h" +#include "que0que.h" +#include "row0ext.h" +#include "row0upd.h" +#include "rem0cmp.h" +#include "read0read.h" +#include "ut0mem.h" + +/************************************************************************* +Gets the offset of trx id field, in bytes relative to the origin of +a clustered index record. */ +UNIV_INTERN +ulint +row_get_trx_id_offset( +/*==================*/ + /* out: offset of DATA_TRX_ID */ + const rec_t* rec __attribute__((unused)), + /* in: record */ + dict_index_t* index, /* in: clustered index */ + const ulint* offsets)/* in: rec_get_offsets(rec, index) */ +{ + ulint pos; + ulint offset; + ulint len; + + ut_ad(dict_index_is_clust(index)); + ut_ad(rec_offs_validate(rec, index, offsets)); + + pos = dict_index_get_sys_col_pos(index, DATA_TRX_ID); + + offset = rec_get_nth_field_offs(offsets, pos, &len); + + ut_ad(len == DATA_TRX_ID_LEN); + + return(offset); +} + +/********************************************************************* +When an insert or purge to a table is performed, this function builds +the entry to be inserted into or purged from an index on the table. */ +UNIV_INTERN +dtuple_t* +row_build_index_entry( +/*==================*/ + /* out: index entry which should be + inserted or purged, or NULL if the + externally stored columns in the + clustered index record are unavailable + and ext != NULL */ + const dtuple_t* row, /* in: row which should be + inserted or purged */ + row_ext_t* ext, /* in: externally stored column prefixes, + or NULL */ + dict_index_t* index, /* in: index on the table */ + mem_heap_t* heap) /* in: memory heap from which the memory for + the index entry is allocated */ +{ + dtuple_t* entry; + ulint entry_len; + ulint i; + + ut_ad(row && index && heap); + ut_ad(dtuple_check_typed(row)); + + entry_len = dict_index_get_n_fields(index); + entry = dtuple_create(heap, entry_len); + + if (UNIV_UNLIKELY(index->type & DICT_UNIVERSAL)) { + dtuple_set_n_fields_cmp(entry, entry_len); + /* There may only be externally stored columns + in a clustered index B-tree of a user table. */ + ut_a(!ext); + } else { + dtuple_set_n_fields_cmp( + entry, dict_index_get_n_unique_in_tree(index)); + } + + for (i = 0; i < entry_len; i++) { + const dict_field_t* ind_field + = dict_index_get_nth_field(index, i); + const dict_col_t* col + = ind_field->col; + ulint col_no + = dict_col_get_no(col); + dfield_t* dfield + = dtuple_get_nth_field(entry, i); + const dfield_t* dfield2 + = dtuple_get_nth_field(row, col_no); + ulint len + = dfield_get_len(dfield2); + + dfield_copy(dfield, dfield2); + + if (dfield_is_null(dfield) || ind_field->prefix_len == 0) { + continue; + } + + /* If a column prefix index, take only the prefix. + Prefix-indexed columns may be externally stored. */ + ut_ad(col->ord_part); + + if (UNIV_LIKELY_NULL(ext)) { + /* See if the column is stored externally. */ + const byte* buf = row_ext_lookup(ext, col_no, + &len); + if (UNIV_LIKELY_NULL(buf)) { + if (UNIV_UNLIKELY(buf == field_ref_zero)) { + return(NULL); + } + dfield_set_data(dfield, buf, len); + } + } else if (dfield_is_ext(dfield)) { + ut_a(len >= BTR_EXTERN_FIELD_REF_SIZE); + len -= BTR_EXTERN_FIELD_REF_SIZE; + ut_a(ind_field->prefix_len <= len + || dict_index_is_clust(index)); + } + + len = dtype_get_at_most_n_mbchars( + col->prtype, col->mbminlen, col->mbmaxlen, + ind_field->prefix_len, len, dfield_get_data(dfield)); + dfield_set_len(dfield, len); + } + + ut_ad(dtuple_check_typed(entry)); + + return(entry); +} + +/*********************************************************************** +An inverse function to row_build_index_entry. Builds a row from a +record in a clustered index. */ +UNIV_INTERN +dtuple_t* +row_build( +/*======*/ + /* out, own: row built; + see the NOTE below! */ + ulint type, /* in: ROW_COPY_POINTERS or + ROW_COPY_DATA; the latter + copies also the data fields to + heap while the first only + places pointers to data fields + on the index page, and thus is + more efficient */ + const dict_index_t* index, /* in: clustered index */ + const rec_t* rec, /* in: record in the clustered + index; NOTE: in the case + ROW_COPY_POINTERS the data + fields in the row will point + directly into this record, + therefore, the buffer page of + this record must be at least + s-latched and the latch held + as long as the row dtuple is used! */ + const ulint* offsets,/* in: rec_get_offsets(rec,index) + or NULL, in which case this function + will invoke rec_get_offsets() */ + const dict_table_t* col_table, + /* in: table, to check which + externally stored columns + occur in the ordering columns + of an index, or NULL if + index->table should be + consulted instead */ + row_ext_t** ext, /* out, own: cache of + externally stored column + prefixes, or NULL */ + mem_heap_t* heap) /* in: memory heap from which + the memory needed is allocated */ +{ + dtuple_t* row; + const dict_table_t* table; + ulint n_fields; + ulint n_ext_cols; + ulint* ext_cols = NULL; /* remove warning */ + ulint len; + ulint row_len; + byte* buf; + ulint i; + ulint j; + mem_heap_t* tmp_heap = NULL; + ulint offsets_[REC_OFFS_NORMAL_SIZE]; + rec_offs_init(offsets_); + + ut_ad(index && rec && heap); + ut_ad(dict_index_is_clust(index)); + + if (!offsets) { + offsets = rec_get_offsets(rec, index, offsets_, + ULINT_UNDEFINED, &tmp_heap); + } else { + ut_ad(rec_offs_validate(rec, index, offsets)); + } + + if (type != ROW_COPY_POINTERS) { + /* Take a copy of rec to heap */ + buf = mem_heap_alloc(heap, rec_offs_size(offsets)); + rec = rec_copy(buf, rec, offsets); + /* Avoid a debug assertion in rec_offs_validate(). */ + rec_offs_make_valid(rec, index, (ulint*) offsets); + } + + table = index->table; + row_len = dict_table_get_n_cols(table); + + row = dtuple_create(heap, row_len); + + dict_table_copy_types(row, table); + + dtuple_set_info_bits(row, rec_get_info_bits( + rec, dict_table_is_comp(table))); + + n_fields = rec_offs_n_fields(offsets); + n_ext_cols = rec_offs_n_extern(offsets); + if (n_ext_cols) { + ext_cols = mem_heap_alloc(heap, n_ext_cols * sizeof *ext_cols); + } + + for (i = j = 0; i < n_fields; i++) { + dict_field_t* ind_field + = dict_index_get_nth_field(index, i); + const dict_col_t* col + = dict_field_get_col(ind_field); + ulint col_no + = dict_col_get_no(col); + dfield_t* dfield + = dtuple_get_nth_field(row, col_no); + + if (ind_field->prefix_len == 0) { + + const byte* field = rec_get_nth_field( + rec, offsets, i, &len); + + dfield_set_data(dfield, field, len); + } + + if (rec_offs_nth_extern(offsets, i)) { + dfield_set_ext(dfield); + + if (UNIV_LIKELY_NULL(col_table)) { + ut_a(col_no + < dict_table_get_n_cols(col_table)); + col = dict_table_get_nth_col( + col_table, col_no); + } + + if (col->ord_part) { + /* We will have to fetch prefixes of + externally stored columns that are + referenced by column prefixes. */ + ext_cols[j++] = col_no; + } + } + } + + ut_ad(dtuple_check_typed(row)); + + if (j) { + *ext = row_ext_create(j, ext_cols, row, + dict_table_zip_size(index->table), + heap); + } else { + *ext = NULL; + } + + if (tmp_heap) { + mem_heap_free(tmp_heap); + } + + return(row); +} + +/*********************************************************************** +Converts an index record to a typed data tuple. */ +UNIV_INTERN +dtuple_t* +row_rec_to_index_entry_low( +/*=======================*/ + /* out: index entry built; does not + set info_bits, and the data fields in + the entry will point directly to rec */ + const rec_t* rec, /* in: record in the index */ + const dict_index_t* index, /* in: index */ + const ulint* offsets,/* in: rec_get_offsets(rec, index) */ + ulint* n_ext, /* out: number of externally + stored columns */ + mem_heap_t* heap) /* in: memory heap from which + the memory needed is allocated */ +{ + dtuple_t* entry; + dfield_t* dfield; + ulint i; + const byte* field; + ulint len; + ulint rec_len; + + ut_ad(rec && heap && index); + /* Because this function may be invoked by row0merge.c + on a record whose header is in different format, the check + rec_offs_validate(rec, index, offsets) must be avoided here. */ + ut_ad(n_ext); + *n_ext = 0; + + rec_len = rec_offs_n_fields(offsets); + + entry = dtuple_create(heap, rec_len); + + dtuple_set_n_fields_cmp(entry, + dict_index_get_n_unique_in_tree(index)); + ut_ad(rec_len == dict_index_get_n_fields(index)); + + dict_index_copy_types(entry, index, rec_len); + + for (i = 0; i < rec_len; i++) { + + dfield = dtuple_get_nth_field(entry, i); + field = rec_get_nth_field(rec, offsets, i, &len); + + dfield_set_data(dfield, field, len); + + if (rec_offs_nth_extern(offsets, i)) { + dfield_set_ext(dfield); + (*n_ext)++; + } + } + + ut_ad(dtuple_check_typed(entry)); + + return(entry); +} + +/*********************************************************************** +Converts an index record to a typed data tuple. NOTE that externally +stored (often big) fields are NOT copied to heap. */ +UNIV_INTERN +dtuple_t* +row_rec_to_index_entry( +/*===================*/ + /* out, own: index entry + built; see the NOTE below! */ + ulint type, /* in: ROW_COPY_DATA, or + ROW_COPY_POINTERS: the former + copies also the data fields to + heap as the latter only places + pointers to data fields on the + index page */ + const rec_t* rec, /* in: record in the index; + NOTE: in the case + ROW_COPY_POINTERS the data + fields in the row will point + directly into this record, + therefore, the buffer page of + this record must be at least + s-latched and the latch held + as long as the dtuple is used! */ + const dict_index_t* index, /* in: index */ + ulint* offsets,/* in/out: rec_get_offsets(rec) */ + ulint* n_ext, /* out: number of externally + stored columns */ + mem_heap_t* heap) /* in: memory heap from which + the memory needed is allocated */ +{ + dtuple_t* entry; + byte* buf; + + ut_ad(rec && heap && index); + ut_ad(rec_offs_validate(rec, index, offsets)); + + if (type == ROW_COPY_DATA) { + /* Take a copy of rec to heap */ + buf = mem_heap_alloc(heap, rec_offs_size(offsets)); + rec = rec_copy(buf, rec, offsets); + /* Avoid a debug assertion in rec_offs_validate(). */ + rec_offs_make_valid(rec, index, offsets); + } + + entry = row_rec_to_index_entry_low(rec, index, offsets, n_ext, heap); + + dtuple_set_info_bits(entry, + rec_get_info_bits(rec, rec_offs_comp(offsets))); + + return(entry); +} + +/*********************************************************************** +Builds from a secondary index record a row reference with which we can +search the clustered index record. */ +UNIV_INTERN +dtuple_t* +row_build_row_ref( +/*==============*/ + /* out, own: row reference built; see the + NOTE below! */ + ulint type, /* in: ROW_COPY_DATA, or ROW_COPY_POINTERS: + the former copies also the data fields to + heap, whereas the latter only places pointers + to data fields on the index page */ + dict_index_t* index, /* in: secondary index */ + const rec_t* rec, /* in: record in the index; + NOTE: in the case ROW_COPY_POINTERS + the data fields in the row will point + directly into this record, therefore, + the buffer page of this record must be + at least s-latched and the latch held + as long as the row reference is used! */ + mem_heap_t* heap) /* in: memory heap from which the memory + needed is allocated */ +{ + dict_table_t* table; + dict_index_t* clust_index; + dfield_t* dfield; + dtuple_t* ref; + const byte* field; + ulint len; + ulint ref_len; + ulint pos; + byte* buf; + ulint clust_col_prefix_len; + ulint i; + mem_heap_t* tmp_heap = NULL; + ulint offsets_[REC_OFFS_NORMAL_SIZE]; + ulint* offsets = offsets_; + rec_offs_init(offsets_); + + ut_ad(index && rec && heap); + ut_ad(!dict_index_is_clust(index)); + + offsets = rec_get_offsets(rec, index, offsets, + ULINT_UNDEFINED, &tmp_heap); + /* Secondary indexes must not contain externally stored columns. */ + ut_ad(!rec_offs_any_extern(offsets)); + + if (type == ROW_COPY_DATA) { + /* Take a copy of rec to heap */ + + buf = mem_heap_alloc(heap, rec_offs_size(offsets)); + + rec = rec_copy(buf, rec, offsets); + /* Avoid a debug assertion in rec_offs_validate(). */ + rec_offs_make_valid(rec, index, offsets); + } + + table = index->table; + + clust_index = dict_table_get_first_index(table); + + ref_len = dict_index_get_n_unique(clust_index); + + ref = dtuple_create(heap, ref_len); + + dict_index_copy_types(ref, clust_index, ref_len); + + for (i = 0; i < ref_len; i++) { + dfield = dtuple_get_nth_field(ref, i); + + pos = dict_index_get_nth_field_pos(index, clust_index, i); + + ut_a(pos != ULINT_UNDEFINED); + + field = rec_get_nth_field(rec, offsets, pos, &len); + + dfield_set_data(dfield, field, len); + + /* If the primary key contains a column prefix, then the + secondary index may contain a longer prefix of the same + column, or the full column, and we must adjust the length + accordingly. */ + + clust_col_prefix_len = dict_index_get_nth_field( + clust_index, i)->prefix_len; + + if (clust_col_prefix_len > 0) { + if (len != UNIV_SQL_NULL) { + + const dtype_t* dtype + = dfield_get_type(dfield); + + dfield_set_len(dfield, + dtype_get_at_most_n_mbchars( + dtype->prtype, + dtype->mbminlen, + dtype->mbmaxlen, + clust_col_prefix_len, + len, (char*) field)); + } + } + } + + ut_ad(dtuple_check_typed(ref)); + if (tmp_heap) { + mem_heap_free(tmp_heap); + } + + return(ref); +} + +/*********************************************************************** +Builds from a secondary index record a row reference with which we can +search the clustered index record. */ +UNIV_INTERN +void +row_build_row_ref_in_tuple( +/*=======================*/ + dtuple_t* ref, /* in/out: row reference built; + see the NOTE below! */ + const rec_t* rec, /* in: record in the index; + NOTE: the data fields in ref + will point directly into this + record, therefore, the buffer + page of this record must be at + least s-latched and the latch + held as long as the row + reference is used! */ + const dict_index_t* index, /* in: secondary index */ + ulint* offsets,/* in: rec_get_offsets(rec, index) + or NULL */ + trx_t* trx) /* in: transaction */ +{ + const dict_index_t* clust_index; + dfield_t* dfield; + const byte* field; + ulint len; + ulint ref_len; + ulint pos; + ulint clust_col_prefix_len; + ulint i; + mem_heap_t* heap = NULL; + ulint offsets_[REC_OFFS_NORMAL_SIZE]; + rec_offs_init(offsets_); + + ut_a(ref); + ut_a(index); + ut_a(rec); + ut_ad(!dict_index_is_clust(index)); + + if (UNIV_UNLIKELY(!index->table)) { + fputs("InnoDB: table ", stderr); +notfound: + ut_print_name(stderr, trx, TRUE, index->table_name); + fputs(" for index ", stderr); + ut_print_name(stderr, trx, FALSE, index->name); + fputs(" not found\n", stderr); + ut_error; + } + + clust_index = dict_table_get_first_index(index->table); + + if (UNIV_UNLIKELY(!clust_index)) { + fputs("InnoDB: clust index for table ", stderr); + goto notfound; + } + + if (!offsets) { + offsets = rec_get_offsets(rec, index, offsets_, + ULINT_UNDEFINED, &heap); + } else { + ut_ad(rec_offs_validate(rec, index, offsets)); + } + + /* Secondary indexes must not contain externally stored columns. */ + ut_ad(!rec_offs_any_extern(offsets)); + ref_len = dict_index_get_n_unique(clust_index); + + ut_ad(ref_len == dtuple_get_n_fields(ref)); + + dict_index_copy_types(ref, clust_index, ref_len); + + for (i = 0; i < ref_len; i++) { + dfield = dtuple_get_nth_field(ref, i); + + pos = dict_index_get_nth_field_pos(index, clust_index, i); + + ut_a(pos != ULINT_UNDEFINED); + + field = rec_get_nth_field(rec, offsets, pos, &len); + + dfield_set_data(dfield, field, len); + + /* If the primary key contains a column prefix, then the + secondary index may contain a longer prefix of the same + column, or the full column, and we must adjust the length + accordingly. */ + + clust_col_prefix_len = dict_index_get_nth_field( + clust_index, i)->prefix_len; + + if (clust_col_prefix_len > 0) { + if (len != UNIV_SQL_NULL) { + + const dtype_t* dtype + = dfield_get_type(dfield); + + dfield_set_len(dfield, + dtype_get_at_most_n_mbchars( + dtype->prtype, + dtype->mbminlen, + dtype->mbmaxlen, + clust_col_prefix_len, + len, (char*) field)); + } + } + } + + ut_ad(dtuple_check_typed(ref)); + if (UNIV_LIKELY_NULL(heap)) { + mem_heap_free(heap); + } +} + +/*********************************************************************** +From a row build a row reference with which we can search the clustered +index record. */ +UNIV_INTERN +void +row_build_row_ref_from_row( +/*=======================*/ + dtuple_t* ref, /* in/out: row reference built; + see the NOTE below! + ref must have the right number + of fields! */ + const dict_table_t* table, /* in: table */ + const dtuple_t* row) /* in: row + NOTE: the data fields in ref will point + directly into data of this row */ +{ + const dict_index_t* clust_index; + ulint ref_len; + ulint i; + + ut_ad(ref && table && row); + + clust_index = dict_table_get_first_index(table); + + ref_len = dict_index_get_n_unique(clust_index); + + ut_ad(ref_len == dtuple_get_n_fields(ref)); + + for (i = 0; i < ref_len; i++) { + const dict_col_t* col; + const dict_field_t* field; + dfield_t* dfield; + const dfield_t* dfield2; + + dfield = dtuple_get_nth_field(ref, i); + + field = dict_index_get_nth_field(clust_index, i); + + col = dict_field_get_col(field); + + dfield2 = dtuple_get_nth_field(row, dict_col_get_no(col)); + + dfield_copy(dfield, dfield2); + ut_ad(!dfield_is_ext(dfield)); + + if (field->prefix_len > 0 && !dfield_is_null(dfield)) { + + ulint len = dfield_get_len(dfield); + + len = dtype_get_at_most_n_mbchars( + col->prtype, col->mbminlen, col->mbmaxlen, + field->prefix_len, + len, dfield_get_data(dfield)); + + dfield_set_len(dfield, len); + } + } + + ut_ad(dtuple_check_typed(ref)); +} + +/******************************************************************* +Searches the clustered index record for a row, if we have the row reference. */ +UNIV_INTERN +ibool +row_search_on_row_ref( +/*==================*/ + /* out: TRUE if found */ + btr_pcur_t* pcur, /* out: persistent cursor, which must + be closed by the caller */ + ulint mode, /* in: BTR_MODIFY_LEAF, ... */ + const dict_table_t* table, /* in: table */ + const dtuple_t* ref, /* in: row reference */ + mtr_t* mtr) /* in/out: mtr */ +{ + ulint low_match; + rec_t* rec; + dict_index_t* index; + + ut_ad(dtuple_check_typed(ref)); + + index = dict_table_get_first_index(table); + + ut_a(dtuple_get_n_fields(ref) == dict_index_get_n_unique(index)); + + btr_pcur_open(index, ref, PAGE_CUR_LE, mode, pcur, mtr); + + low_match = btr_pcur_get_low_match(pcur); + + rec = btr_pcur_get_rec(pcur); + + if (page_rec_is_infimum(rec)) { + + return(FALSE); + } + + if (low_match != dtuple_get_n_fields(ref)) { + + return(FALSE); + } + + return(TRUE); +} + +/************************************************************************* +Fetches the clustered index record for a secondary index record. The latches +on the secondary index record are preserved. */ +UNIV_INTERN +rec_t* +row_get_clust_rec( +/*==============*/ + /* out: record or NULL, if no record found */ + ulint mode, /* in: BTR_MODIFY_LEAF, ... */ + const rec_t* rec, /* in: record in a secondary index */ + dict_index_t* index, /* in: secondary index */ + dict_index_t** clust_index,/* out: clustered index */ + mtr_t* mtr) /* in: mtr */ +{ + mem_heap_t* heap; + dtuple_t* ref; + dict_table_t* table; + btr_pcur_t pcur; + ibool found; + rec_t* clust_rec; + + ut_ad(!dict_index_is_clust(index)); + + table = index->table; + + heap = mem_heap_create(256); + + ref = row_build_row_ref(ROW_COPY_POINTERS, index, rec, heap); + + found = row_search_on_row_ref(&pcur, mode, table, ref, mtr); + + clust_rec = found ? btr_pcur_get_rec(&pcur) : NULL; + + mem_heap_free(heap); + + btr_pcur_close(&pcur); + + *clust_index = dict_table_get_first_index(table); + + return(clust_rec); +} + +/******************************************************************* +Searches an index record. */ +UNIV_INTERN +ibool +row_search_index_entry( +/*===================*/ + /* out: TRUE if found */ + dict_index_t* index, /* in: index */ + const dtuple_t* entry, /* in: index entry */ + ulint mode, /* in: BTR_MODIFY_LEAF, ... */ + btr_pcur_t* pcur, /* in/out: persistent cursor, which must + be closed by the caller */ + mtr_t* mtr) /* in: mtr */ +{ + ulint n_fields; + ulint low_match; + rec_t* rec; + + ut_ad(dtuple_check_typed(entry)); + + btr_pcur_open(index, entry, PAGE_CUR_LE, mode, pcur, mtr); + low_match = btr_pcur_get_low_match(pcur); + + rec = btr_pcur_get_rec(pcur); + + n_fields = dtuple_get_n_fields(entry); + + return(!page_rec_is_infimum(rec) && low_match == n_fields); +} + +#ifndef UNIV_HOTBACKUP + +#include <my_sys.h> + +/*********************************************************************** +Formats the raw data in "data" (in InnoDB on-disk format) that is of +type DATA_INT using "prtype" and writes the result to "buf". +If the data is in unknown format, then nothing is written to "buf", +0 is returned and "format_in_hex" is set to TRUE, otherwise +"format_in_hex" is left untouched. +Not more than "buf_size" bytes are written to "buf". +The result is always '\0'-terminated (provided buf_size > 0) and the +number of bytes that were written to "buf" is returned (including the +terminating '\0'). */ +static +ulint +row_raw_format_int( +/*===============*/ + /* out: number of bytes + that were written */ + const char* data, /* in: raw data */ + ulint data_len, /* in: raw data length + in bytes */ + ulint prtype, /* in: precise type */ + char* buf, /* out: output buffer */ + ulint buf_size, /* in: output buffer size + in bytes */ + ibool* format_in_hex) /* out: should the data be + formated in hex */ +{ + ulint ret; + + if (data_len <= sizeof(ullint)) { + + ullint value; + ibool unsigned_type = prtype & DATA_UNSIGNED; + + value = mach_read_int_type((const byte*) data, + data_len, unsigned_type); + + if (unsigned_type) { + + ret = ut_snprintf(buf, buf_size, "%llu", + value) + 1; + } else { + + ret = ut_snprintf(buf, buf_size, "%lld", + (long long) value) + 1; + } + + } else { + + *format_in_hex = TRUE; + ret = 0; + } + + return(ut_min(ret, buf_size)); +} + +/*********************************************************************** +Formats the raw data in "data" (in InnoDB on-disk format) that is of +type DATA_(CHAR|VARCHAR|MYSQL|VARMYSQL) using "prtype" and writes the +result to "buf". +If the data is in binary format, then nothing is written to "buf", +0 is returned and "format_in_hex" is set to TRUE, otherwise +"format_in_hex" is left untouched. +Not more than "buf_size" bytes are written to "buf". +The result is always '\0'-terminated (provided buf_size > 0) and the +number of bytes that were written to "buf" is returned (including the +terminating '\0'). */ +static +ulint +row_raw_format_str( +/*===============*/ + /* out: number of bytes + that were written */ + const char* data, /* in: raw data */ + ulint data_len, /* in: raw data length + in bytes */ + ulint prtype, /* in: precise type */ + char* buf, /* out: output buffer */ + ulint buf_size, /* in: output buffer size + in bytes */ + ibool* format_in_hex) /* out: should the data be + formated in hex */ +{ + ulint charset_coll; + + if (buf_size == 0) { + + return(0); + } + + /* we assume system_charset_info is UTF-8 */ + + charset_coll = dtype_get_charset_coll(prtype); + + if (UNIV_LIKELY(dtype_is_utf8(prtype))) { + + return(ut_str_sql_format(data, data_len, buf, buf_size)); + } + /* else */ + + if (charset_coll == DATA_MYSQL_BINARY_CHARSET_COLL) { + + *format_in_hex = TRUE; + return(0); + } + /* else */ + + return(innobase_raw_format(data, data_len, charset_coll, + buf, buf_size)); +} + +/*********************************************************************** +Formats the raw data in "data" (in InnoDB on-disk format) using +"dict_field" and writes the result to "buf". +Not more than "buf_size" bytes are written to "buf". +The result is always '\0'-terminated (provided buf_size > 0) and the +number of bytes that were written to "buf" is returned (including the +terminating '\0'). */ +UNIV_INTERN +ulint +row_raw_format( +/*===========*/ + /* out: number of bytes + that were written */ + const char* data, /* in: raw data */ + ulint data_len, /* in: raw data length + in bytes */ + const dict_field_t* dict_field, /* in: index field */ + char* buf, /* out: output buffer */ + ulint buf_size) /* in: output buffer size + in bytes */ +{ + ulint mtype; + ulint prtype; + ulint ret; + ibool format_in_hex; + + if (buf_size == 0) { + + return(0); + } + + if (data_len == UNIV_SQL_NULL) { + + ret = ut_snprintf((char*) buf, buf_size, "NULL") + 1; + + return(ut_min(ret, buf_size)); + } + + mtype = dict_field->col->mtype; + prtype = dict_field->col->prtype; + + format_in_hex = FALSE; + + switch (mtype) { + case DATA_INT: + + ret = row_raw_format_int(data, data_len, prtype, + buf, buf_size, &format_in_hex); + break; + case DATA_CHAR: + case DATA_VARCHAR: + case DATA_MYSQL: + case DATA_VARMYSQL: + + ret = row_raw_format_str(data, data_len, prtype, + buf, buf_size, &format_in_hex); + break; + /* XXX support more data types */ + default: + + format_in_hex = TRUE; + } + + if (format_in_hex) { + + if (UNIV_LIKELY(buf_size > 2)) { + + memcpy(buf, "0x", 2); + buf += 2; + buf_size -= 2; + ret = 2 + ut_raw_to_hex(data, data_len, + buf, buf_size); + } else { + + buf[0] = '\0'; + ret = 1; + } + } + + return(ret); +} + +#endif /* !UNIV_HOTBACKUP */ + +#ifdef UNIV_COMPILE_TEST_FUNCS + +#include "ut0dbg.h" + +void +test_row_raw_format_int() +{ + ulint ret; + char buf[128]; + ibool format_in_hex; + +#define CALL_AND_TEST(data, data_len, prtype, buf, buf_size,\ + ret_expected, buf_expected, format_in_hex_expected)\ + do {\ + ibool ok = TRUE;\ + ulint i;\ + memset(buf, 'x', 10);\ + buf[10] = '\0';\ + format_in_hex = FALSE;\ + fprintf(stderr, "TESTING \"\\x");\ + for (i = 0; i < data_len; i++) {\ + fprintf(stderr, "%02hhX", data[i]);\ + }\ + fprintf(stderr, "\", %lu, %lu, %lu\n",\ + (ulint) data_len, (ulint) prtype,\ + (ulint) buf_size);\ + ret = row_raw_format_int(data, data_len, prtype,\ + buf, buf_size, &format_in_hex);\ + if (ret != ret_expected) {\ + fprintf(stderr, "expected ret %lu, got %lu\n",\ + (ulint) ret_expected, ret);\ + ok = FALSE;\ + }\ + if (strcmp((char*) buf, buf_expected) != 0) {\ + fprintf(stderr, "expected buf \"%s\", got \"%s\"\n",\ + buf_expected, buf);\ + ok = FALSE;\ + }\ + if (format_in_hex != format_in_hex_expected) {\ + fprintf(stderr, "expected format_in_hex %d, got %d\n",\ + (int) format_in_hex_expected,\ + (int) format_in_hex);\ + ok = FALSE;\ + }\ + if (ok) {\ + fprintf(stderr, "OK: %lu, \"%s\" %d\n\n",\ + (ulint) ret, buf, (int) format_in_hex);\ + } else {\ + return;\ + }\ + } while (0) + +#if 1 + /* min values for signed 1-8 byte integers */ + + CALL_AND_TEST("\x00", 1, 0, + buf, sizeof(buf), 5, "-128", 0); + + CALL_AND_TEST("\x00\x00", 2, 0, + buf, sizeof(buf), 7, "-32768", 0); + + CALL_AND_TEST("\x00\x00\x00", 3, 0, + buf, sizeof(buf), 9, "-8388608", 0); + + CALL_AND_TEST("\x00\x00\x00\x00", 4, 0, + buf, sizeof(buf), 12, "-2147483648", 0); + + CALL_AND_TEST("\x00\x00\x00\x00\x00", 5, 0, + buf, sizeof(buf), 14, "-549755813888", 0); + + CALL_AND_TEST("\x00\x00\x00\x00\x00\x00", 6, 0, + buf, sizeof(buf), 17, "-140737488355328", 0); + + CALL_AND_TEST("\x00\x00\x00\x00\x00\x00\x00", 7, 0, + buf, sizeof(buf), 19, "-36028797018963968", 0); + + CALL_AND_TEST("\x00\x00\x00\x00\x00\x00\x00\x00", 8, 0, + buf, sizeof(buf), 21, "-9223372036854775808", 0); + + /* min values for unsigned 1-8 byte integers */ + + CALL_AND_TEST("\x00", 1, DATA_UNSIGNED, + buf, sizeof(buf), 2, "0", 0); + + CALL_AND_TEST("\x00\x00", 2, DATA_UNSIGNED, + buf, sizeof(buf), 2, "0", 0); + + CALL_AND_TEST("\x00\x00\x00", 3, DATA_UNSIGNED, + buf, sizeof(buf), 2, "0", 0); + + CALL_AND_TEST("\x00\x00\x00\x00", 4, DATA_UNSIGNED, + buf, sizeof(buf), 2, "0", 0); + + CALL_AND_TEST("\x00\x00\x00\x00\x00", 5, DATA_UNSIGNED, + buf, sizeof(buf), 2, "0", 0); + + CALL_AND_TEST("\x00\x00\x00\x00\x00\x00", 6, DATA_UNSIGNED, + buf, sizeof(buf), 2, "0", 0); + + CALL_AND_TEST("\x00\x00\x00\x00\x00\x00\x00", 7, DATA_UNSIGNED, + buf, sizeof(buf), 2, "0", 0); + + CALL_AND_TEST("\x00\x00\x00\x00\x00\x00\x00\x00", 8, DATA_UNSIGNED, + buf, sizeof(buf), 2, "0", 0); + + /* max values for signed 1-8 byte integers */ + + CALL_AND_TEST("\xFF", 1, 0, + buf, sizeof(buf), 4, "127", 0); + + CALL_AND_TEST("\xFF\xFF", 2, 0, + buf, sizeof(buf), 6, "32767", 0); + + CALL_AND_TEST("\xFF\xFF\xFF", 3, 0, + buf, sizeof(buf), 8, "8388607", 0); + + CALL_AND_TEST("\xFF\xFF\xFF\xFF", 4, 0, + buf, sizeof(buf), 11, "2147483647", 0); + + CALL_AND_TEST("\xFF\xFF\xFF\xFF\xFF", 5, 0, + buf, sizeof(buf), 13, "549755813887", 0); + + CALL_AND_TEST("\xFF\xFF\xFF\xFF\xFF\xFF", 6, 0, + buf, sizeof(buf), 16, "140737488355327", 0); + + CALL_AND_TEST("\xFF\xFF\xFF\xFF\xFF\xFF\xFF", 7, 0, + buf, sizeof(buf), 18, "36028797018963967", 0); + + CALL_AND_TEST("\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF", 8, 0, + buf, sizeof(buf), 20, "9223372036854775807", 0); + + /* max values for unsigned 1-8 byte integers */ + + CALL_AND_TEST("\xFF", 1, DATA_UNSIGNED, + buf, sizeof(buf), 4, "255", 0); + + CALL_AND_TEST("\xFF\xFF", 2, DATA_UNSIGNED, + buf, sizeof(buf), 6, "65535", 0); + + CALL_AND_TEST("\xFF\xFF\xFF", 3, DATA_UNSIGNED, + buf, sizeof(buf), 9, "16777215", 0); + + CALL_AND_TEST("\xFF\xFF\xFF\xFF", 4, DATA_UNSIGNED, + buf, sizeof(buf), 11, "4294967295", 0); + + CALL_AND_TEST("\xFF\xFF\xFF\xFF\xFF", 5, DATA_UNSIGNED, + buf, sizeof(buf), 14, "1099511627775", 0); + + CALL_AND_TEST("\xFF\xFF\xFF\xFF\xFF\xFF", 6, DATA_UNSIGNED, + buf, sizeof(buf), 16, "281474976710655", 0); + + CALL_AND_TEST("\xFF\xFF\xFF\xFF\xFF\xFF\xFF", 7, DATA_UNSIGNED, + buf, sizeof(buf), 18, "72057594037927935", 0); + + CALL_AND_TEST("\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF", 8, DATA_UNSIGNED, + buf, sizeof(buf), 21, "18446744073709551615", 0); + + /* some random values */ + + CALL_AND_TEST("\x52", 1, 0, + buf, sizeof(buf), 4, "-46", 0); + + CALL_AND_TEST("\x0E", 1, DATA_UNSIGNED, + buf, sizeof(buf), 3, "14", 0); + + CALL_AND_TEST("\x62\xCE", 2, 0, + buf, sizeof(buf), 6, "-7474", 0); + + CALL_AND_TEST("\x29\xD6", 2, DATA_UNSIGNED, + buf, sizeof(buf), 6, "10710", 0); + + CALL_AND_TEST("\x7F\xFF\x90", 3, 0, + buf, sizeof(buf), 5, "-112", 0); + + CALL_AND_TEST("\x00\xA1\x16", 3, DATA_UNSIGNED, + buf, sizeof(buf), 6, "41238", 0); + + CALL_AND_TEST("\x7F\xFF\xFF\xF7", 4, 0, + buf, sizeof(buf), 3, "-9", 0); + + CALL_AND_TEST("\x00\x00\x00\x5C", 4, DATA_UNSIGNED, + buf, sizeof(buf), 3, "92", 0); + + CALL_AND_TEST("\x7F\xFF\xFF\xFF\xFF\xFF\xDC\x63", 8, 0, + buf, sizeof(buf), 6, "-9117", 0); + + CALL_AND_TEST("\x00\x00\x00\x00\x00\x01\x64\x62", 8, DATA_UNSIGNED, + buf, sizeof(buf), 6, "91234", 0); +#endif + + /* speed test */ + + speedo_t speedo; + ulint i; + + speedo_reset(&speedo); + + for (i = 0; i < 1000000; i++) { + row_raw_format_int("\x23", 1, + 0, buf, sizeof(buf), + &format_in_hex); + row_raw_format_int("\x23", 1, + DATA_UNSIGNED, buf, sizeof(buf), + &format_in_hex); + + row_raw_format_int("\x00\x00\x00\x00\x00\x01\x64\x62", 8, + 0, buf, sizeof(buf), + &format_in_hex); + row_raw_format_int("\x00\x00\x00\x00\x00\x01\x64\x62", 8, + DATA_UNSIGNED, buf, sizeof(buf), + &format_in_hex); + } + + speedo_show(&speedo); +} + +#endif /* UNIV_COMPILE_TEST_FUNCS */ diff --git a/storage/xtradb/row/row0sel.c b/storage/xtradb/row/row0sel.c new file mode 100644 index 00000000000..ebd7bf4a2ce --- /dev/null +++ b/storage/xtradb/row/row0sel.c @@ -0,0 +1,4736 @@ +/***************************************************************************** + +Copyright (c) 1997, 2009, Innobase Oy. All Rights Reserved. +Copyright (c) 2008, Google Inc. + +Portions of this file contain modifications contributed and copyrighted by +Google, Inc. Those modifications are gratefully acknowledged and are described +briefly in the InnoDB documentation. The contributions by Google are +incorporated with their permission, and subject to the conditions contained in +the file COPYING.Google. + +This program is free software; you can redistribute it and/or modify it under +the terms of the GNU General Public License as published by the Free Software +Foundation; version 2 of the License. + +This program is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + +You 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 + +*****************************************************************************/ + +/******************************************************* +Select + +Created 12/19/1997 Heikki Tuuri +*******************************************************/ + +#include "row0sel.h" + +#ifdef UNIV_NONINL +#include "row0sel.ic" +#endif + +#include "dict0dict.h" +#include "dict0boot.h" +#include "trx0undo.h" +#include "trx0trx.h" +#include "btr0btr.h" +#include "btr0cur.h" +#include "btr0sea.h" +#include "mach0data.h" +#include "que0que.h" +#include "row0upd.h" +#include "row0row.h" +#include "row0vers.h" +#include "rem0cmp.h" +#include "lock0lock.h" +#include "eval0eval.h" +#include "pars0sym.h" +#include "pars0pars.h" +#include "row0mysql.h" +#include "read0read.h" +#include "buf0lru.h" +#include "ha_prototypes.h" + +/* Maximum number of rows to prefetch; MySQL interface has another parameter */ +#define SEL_MAX_N_PREFETCH 16 + +/* Number of rows fetched, after which to start prefetching; MySQL interface +has another parameter */ +#define SEL_PREFETCH_LIMIT 1 + +/* When a select has accessed about this many pages, it returns control back +to que_run_threads: this is to allow canceling runaway queries */ + +#define SEL_COST_LIMIT 100 + +/* Flags for search shortcut */ +#define SEL_FOUND 0 +#define SEL_EXHAUSTED 1 +#define SEL_RETRY 2 + +/************************************************************************ +Returns TRUE if the user-defined column in a secondary index record +is alphabetically the same as the corresponding BLOB column in the clustered +index record. +NOTE: the comparison is NOT done as a binary comparison, but character +fields are compared with collation! */ +static +ibool +row_sel_sec_rec_is_for_blob( +/*========================*/ + /* out: TRUE if the columns + are equal */ + ulint mtype, /* in: main type */ + ulint prtype, /* in: precise type */ + ulint mbminlen, /* in: minimum length of a + multi-byte character */ + ulint mbmaxlen, /* in: maximum length of a + multi-byte character */ + const byte* clust_field, /* in: the locally stored part of + the clustered index column, including + the BLOB pointer; the clustered + index record must be covered by + a lock or a page latch to protect it + against deletion (rollback or purge) */ + ulint clust_len, /* in: length of clust_field */ + const byte* sec_field, /* in: column in secondary index */ + ulint sec_len, /* in: length of sec_field */ + ulint zip_size) /* in: compressed page size, or 0 */ +{ + ulint len; + byte buf[DICT_MAX_INDEX_COL_LEN]; + + len = btr_copy_externally_stored_field_prefix(buf, sizeof buf, + zip_size, + clust_field, clust_len); + + if (UNIV_UNLIKELY(len == 0)) { + /* The BLOB was being deleted as the server crashed. + There should not be any secondary index records + referring to this clustered index record, because + btr_free_externally_stored_field() is called after all + secondary index entries of the row have been purged. */ + return(FALSE); + } + + len = dtype_get_at_most_n_mbchars(prtype, mbminlen, mbmaxlen, + sec_len, len, (const char*) buf); + + return(!cmp_data_data(mtype, prtype, buf, len, sec_field, sec_len)); +} + +/************************************************************************ +Returns TRUE if the user-defined column values in a secondary index record +are alphabetically the same as the corresponding columns in the clustered +index record. +NOTE: the comparison is NOT done as a binary comparison, but character +fields are compared with collation! */ +static +ibool +row_sel_sec_rec_is_for_clust_rec( +/*=============================*/ + /* out: TRUE if the secondary + record is equal to the corresponding + fields in the clustered record, + when compared with collation */ + const rec_t* sec_rec, /* in: secondary index record */ + dict_index_t* sec_index, /* in: secondary index */ + const rec_t* clust_rec, /* in: clustered index record; + must be protected by a lock or + a page latch against deletion + in rollback or purge */ + dict_index_t* clust_index) /* in: clustered index */ +{ + const byte* sec_field; + ulint sec_len; + const byte* clust_field; + ulint n; + ulint i; + mem_heap_t* heap = NULL; + ulint clust_offsets_[REC_OFFS_NORMAL_SIZE]; + ulint sec_offsets_[REC_OFFS_SMALL_SIZE]; + ulint* clust_offs = clust_offsets_; + ulint* sec_offs = sec_offsets_; + ibool is_equal = TRUE; + + rec_offs_init(clust_offsets_); + rec_offs_init(sec_offsets_); + + if (rec_get_deleted_flag(clust_rec, + dict_table_is_comp(clust_index->table))) { + + /* The clustered index record is delete-marked; + it is not visible in the read view. Besides, + if there are any externally stored columns, + some of them may have already been purged. */ + return(FALSE); + } + + clust_offs = rec_get_offsets(clust_rec, clust_index, clust_offs, + ULINT_UNDEFINED, &heap); + sec_offs = rec_get_offsets(sec_rec, sec_index, sec_offs, + ULINT_UNDEFINED, &heap); + + n = dict_index_get_n_ordering_defined_by_user(sec_index); + + for (i = 0; i < n; i++) { + const dict_field_t* ifield; + const dict_col_t* col; + ulint clust_pos; + ulint clust_len; + ulint len; + + ifield = dict_index_get_nth_field(sec_index, i); + col = dict_field_get_col(ifield); + clust_pos = dict_col_get_clust_pos(col, clust_index); + + clust_field = rec_get_nth_field( + clust_rec, clust_offs, clust_pos, &clust_len); + sec_field = rec_get_nth_field(sec_rec, sec_offs, i, &sec_len); + + len = clust_len; + + if (ifield->prefix_len > 0 && len != UNIV_SQL_NULL) { + + if (rec_offs_nth_extern(clust_offs, clust_pos)) { + len -= BTR_EXTERN_FIELD_REF_SIZE; + } + + len = dtype_get_at_most_n_mbchars( + col->prtype, col->mbminlen, col->mbmaxlen, + ifield->prefix_len, len, (char*) clust_field); + + if (rec_offs_nth_extern(clust_offs, clust_pos) + && len < sec_len) { + if (!row_sel_sec_rec_is_for_blob( + col->mtype, col->prtype, + col->mbminlen, col->mbmaxlen, + clust_field, clust_len, + sec_field, sec_len, + dict_table_zip_size( + clust_index->table))) { + goto inequal; + } + + continue; + } + } + + if (0 != cmp_data_data(col->mtype, col->prtype, + clust_field, len, + sec_field, sec_len)) { +inequal: + is_equal = FALSE; + goto func_exit; + } + } + +func_exit: + if (UNIV_LIKELY_NULL(heap)) { + mem_heap_free(heap); + } + return(is_equal); +} + +/************************************************************************* +Creates a select node struct. */ +UNIV_INTERN +sel_node_t* +sel_node_create( +/*============*/ + /* out, own: select node struct */ + mem_heap_t* heap) /* in: memory heap where created */ +{ + sel_node_t* node; + + node = mem_heap_alloc(heap, sizeof(sel_node_t)); + node->common.type = QUE_NODE_SELECT; + node->state = SEL_NODE_OPEN; + + node->plans = NULL; + + return(node); +} + +/************************************************************************* +Frees the memory private to a select node when a query graph is freed, +does not free the heap where the node was originally created. */ +UNIV_INTERN +void +sel_node_free_private( +/*==================*/ + sel_node_t* node) /* in: select node struct */ +{ + ulint i; + plan_t* plan; + + if (node->plans != NULL) { + for (i = 0; i < node->n_tables; i++) { + plan = sel_node_get_nth_plan(node, i); + + btr_pcur_close(&(plan->pcur)); + btr_pcur_close(&(plan->clust_pcur)); + + if (plan->old_vers_heap) { + mem_heap_free(plan->old_vers_heap); + } + } + } +} + +/************************************************************************* +Evaluates the values in a select list. If there are aggregate functions, +their argument value is added to the aggregate total. */ +UNIV_INLINE +void +sel_eval_select_list( +/*=================*/ + sel_node_t* node) /* in: select node */ +{ + que_node_t* exp; + + exp = node->select_list; + + while (exp) { + eval_exp(exp); + + exp = que_node_get_next(exp); + } +} + +/************************************************************************* +Assigns the values in the select list to the possible into-variables in +SELECT ... INTO ... */ +UNIV_INLINE +void +sel_assign_into_var_values( +/*=======================*/ + sym_node_t* var, /* in: first variable in a list of variables */ + sel_node_t* node) /* in: select node */ +{ + que_node_t* exp; + + if (var == NULL) { + + return; + } + + exp = node->select_list; + + while (var) { + ut_ad(exp); + + eval_node_copy_val(var->alias, exp); + + exp = que_node_get_next(exp); + var = que_node_get_next(var); + } +} + +/************************************************************************* +Resets the aggregate value totals in the select list of an aggregate type +query. */ +UNIV_INLINE +void +sel_reset_aggregate_vals( +/*=====================*/ + sel_node_t* node) /* in: select node */ +{ + func_node_t* func_node; + + ut_ad(node->is_aggregate); + + func_node = node->select_list; + + while (func_node) { + eval_node_set_int_val(func_node, 0); + + func_node = que_node_get_next(func_node); + } + + node->aggregate_already_fetched = FALSE; +} + +/************************************************************************* +Copies the input variable values when an explicit cursor is opened. */ +UNIV_INLINE +void +row_sel_copy_input_variable_vals( +/*=============================*/ + sel_node_t* node) /* in: select node */ +{ + sym_node_t* var; + + var = UT_LIST_GET_FIRST(node->copy_variables); + + while (var) { + eval_node_copy_val(var, var->alias); + + var->indirection = NULL; + + var = UT_LIST_GET_NEXT(col_var_list, var); + } +} + +/************************************************************************* +Fetches the column values from a record. */ +static +void +row_sel_fetch_columns( +/*==================*/ + dict_index_t* index, /* in: record index */ + const rec_t* rec, /* in: record in a clustered or non-clustered + index; must be protected by a page latch */ + const ulint* offsets,/* in: rec_get_offsets(rec, index) */ + sym_node_t* column) /* in: first column in a column list, or + NULL */ +{ + dfield_t* val; + ulint index_type; + ulint field_no; + const byte* data; + ulint len; + + ut_ad(rec_offs_validate(rec, index, offsets)); + + if (dict_index_is_clust(index)) { + index_type = SYM_CLUST_FIELD_NO; + } else { + index_type = SYM_SEC_FIELD_NO; + } + + while (column) { + mem_heap_t* heap = NULL; + ibool needs_copy; + + field_no = column->field_nos[index_type]; + + if (field_no != ULINT_UNDEFINED) { + + if (UNIV_UNLIKELY(rec_offs_nth_extern(offsets, + field_no))) { + + /* Copy an externally stored field to the + temporary heap */ + + heap = mem_heap_create(1); + + data = btr_rec_copy_externally_stored_field( + rec, offsets, + dict_table_zip_size(index->table), + field_no, &len, heap); + + ut_a(len != UNIV_SQL_NULL); + + needs_copy = TRUE; + } else { + data = rec_get_nth_field(rec, offsets, + field_no, &len); + + if (len == UNIV_SQL_NULL) { + len = UNIV_SQL_NULL; + } + + needs_copy = column->copy_val; + } + + if (needs_copy) { + eval_node_copy_and_alloc_val(column, data, + len); + } else { + val = que_node_get_val(column); + dfield_set_data(val, data, len); + } + + if (UNIV_LIKELY_NULL(heap)) { + mem_heap_free(heap); + } + } + + column = UT_LIST_GET_NEXT(col_var_list, column); + } +} + +/************************************************************************* +Allocates a prefetch buffer for a column when prefetch is first time done. */ +static +void +sel_col_prefetch_buf_alloc( +/*=======================*/ + sym_node_t* column) /* in: symbol table node for a column */ +{ + sel_buf_t* sel_buf; + ulint i; + + ut_ad(que_node_get_type(column) == QUE_NODE_SYMBOL); + + column->prefetch_buf = mem_alloc(SEL_MAX_N_PREFETCH + * sizeof(sel_buf_t)); + for (i = 0; i < SEL_MAX_N_PREFETCH; i++) { + sel_buf = column->prefetch_buf + i; + + sel_buf->data = NULL; + + sel_buf->val_buf_size = 0; + } +} + +/************************************************************************* +Frees a prefetch buffer for a column, including the dynamically allocated +memory for data stored there. */ +UNIV_INTERN +void +sel_col_prefetch_buf_free( +/*======================*/ + sel_buf_t* prefetch_buf) /* in, own: prefetch buffer */ +{ + sel_buf_t* sel_buf; + ulint i; + + for (i = 0; i < SEL_MAX_N_PREFETCH; i++) { + sel_buf = prefetch_buf + i; + + if (sel_buf->val_buf_size > 0) { + + mem_free(sel_buf->data); + } + } +} + +/************************************************************************* +Pops the column values for a prefetched, cached row from the column prefetch +buffers and places them to the val fields in the column nodes. */ +static +void +sel_pop_prefetched_row( +/*===================*/ + plan_t* plan) /* in: plan node for a table */ +{ + sym_node_t* column; + sel_buf_t* sel_buf; + dfield_t* val; + byte* data; + ulint len; + ulint val_buf_size; + + ut_ad(plan->n_rows_prefetched > 0); + + column = UT_LIST_GET_FIRST(plan->columns); + + while (column) { + val = que_node_get_val(column); + + if (!column->copy_val) { + /* We did not really push any value for the + column */ + + ut_ad(!column->prefetch_buf); + ut_ad(que_node_get_val_buf_size(column) == 0); + ut_d(dfield_set_null(val)); + + goto next_col; + } + + ut_ad(column->prefetch_buf); + ut_ad(!dfield_is_ext(val)); + + sel_buf = column->prefetch_buf + plan->first_prefetched; + + data = sel_buf->data; + len = sel_buf->len; + val_buf_size = sel_buf->val_buf_size; + + /* We must keep track of the allocated memory for + column values to be able to free it later: therefore + we swap the values for sel_buf and val */ + + sel_buf->data = dfield_get_data(val); + sel_buf->len = dfield_get_len(val); + sel_buf->val_buf_size = que_node_get_val_buf_size(column); + + dfield_set_data(val, data, len); + que_node_set_val_buf_size(column, val_buf_size); +next_col: + column = UT_LIST_GET_NEXT(col_var_list, column); + } + + plan->n_rows_prefetched--; + + plan->first_prefetched++; +} + +/************************************************************************* +Pushes the column values for a prefetched, cached row to the column prefetch +buffers from the val fields in the column nodes. */ +UNIV_INLINE +void +sel_push_prefetched_row( +/*====================*/ + plan_t* plan) /* in: plan node for a table */ +{ + sym_node_t* column; + sel_buf_t* sel_buf; + dfield_t* val; + byte* data; + ulint len; + ulint pos; + ulint val_buf_size; + + if (plan->n_rows_prefetched == 0) { + pos = 0; + plan->first_prefetched = 0; + } else { + pos = plan->n_rows_prefetched; + + /* We have the convention that pushing new rows starts only + after the prefetch stack has been emptied: */ + + ut_ad(plan->first_prefetched == 0); + } + + plan->n_rows_prefetched++; + + ut_ad(pos < SEL_MAX_N_PREFETCH); + + column = UT_LIST_GET_FIRST(plan->columns); + + while (column) { + if (!column->copy_val) { + /* There is no sense to push pointers to database + page fields when we do not keep latch on the page! */ + + goto next_col; + } + + if (!column->prefetch_buf) { + /* Allocate a new prefetch buffer */ + + sel_col_prefetch_buf_alloc(column); + } + + sel_buf = column->prefetch_buf + pos; + + val = que_node_get_val(column); + + data = dfield_get_data(val); + len = dfield_get_len(val); + val_buf_size = que_node_get_val_buf_size(column); + + /* We must keep track of the allocated memory for + column values to be able to free it later: therefore + we swap the values for sel_buf and val */ + + dfield_set_data(val, sel_buf->data, sel_buf->len); + que_node_set_val_buf_size(column, sel_buf->val_buf_size); + + sel_buf->data = data; + sel_buf->len = len; + sel_buf->val_buf_size = val_buf_size; +next_col: + column = UT_LIST_GET_NEXT(col_var_list, column); + } +} + +/************************************************************************* +Builds a previous version of a clustered index record for a consistent read */ +static +ulint +row_sel_build_prev_vers( +/*====================*/ + /* out: DB_SUCCESS or error code */ + read_view_t* read_view, /* in: read view */ + dict_index_t* index, /* in: plan node for table */ + rec_t* rec, /* in: record in a clustered index */ + ulint** offsets, /* in/out: offsets returned by + rec_get_offsets(rec, plan->index) */ + mem_heap_t** offset_heap, /* in/out: memory heap from which + the offsets are allocated */ + mem_heap_t** old_vers_heap, /* out: old version heap to use */ + rec_t** old_vers, /* out: old version, or NULL if the + record does not exist in the view: + i.e., it was freshly inserted + afterwards */ + mtr_t* mtr) /* in: mtr */ +{ + ulint err; + + if (*old_vers_heap) { + mem_heap_empty(*old_vers_heap); + } else { + *old_vers_heap = mem_heap_create(512); + } + + err = row_vers_build_for_consistent_read( + rec, mtr, index, offsets, read_view, offset_heap, + *old_vers_heap, old_vers); + return(err); +} + +/************************************************************************* +Builds the last committed version of a clustered index record for a +semi-consistent read. */ +static +ulint +row_sel_build_committed_vers_for_mysql( +/*===================================*/ + /* out: DB_SUCCESS or error code */ + dict_index_t* clust_index, /* in: clustered index */ + row_prebuilt_t* prebuilt, /* in: prebuilt struct */ + const rec_t* rec, /* in: record in a clustered index */ + ulint** offsets, /* in/out: offsets returned by + rec_get_offsets(rec, clust_index) */ + mem_heap_t** offset_heap, /* in/out: memory heap from which + the offsets are allocated */ + const rec_t** old_vers, /* out: old version, or NULL if the + record does not exist in the view: + i.e., it was freshly inserted + afterwards */ + mtr_t* mtr) /* in: mtr */ +{ + ulint err; + + if (prebuilt->old_vers_heap) { + mem_heap_empty(prebuilt->old_vers_heap); + } else { + prebuilt->old_vers_heap = mem_heap_create(200); + } + + err = row_vers_build_for_semi_consistent_read( + rec, mtr, clust_index, offsets, offset_heap, + prebuilt->old_vers_heap, old_vers); + return(err); +} + +/************************************************************************* +Tests the conditions which determine when the index segment we are searching +through has been exhausted. */ +UNIV_INLINE +ibool +row_sel_test_end_conds( +/*===================*/ + /* out: TRUE if row passed the tests */ + plan_t* plan) /* in: plan for the table; the column values must + already have been retrieved and the right sides of + comparisons evaluated */ +{ + func_node_t* cond; + + /* All conditions in end_conds are comparisons of a column to an + expression */ + + cond = UT_LIST_GET_FIRST(plan->end_conds); + + while (cond) { + /* Evaluate the left side of the comparison, i.e., get the + column value if there is an indirection */ + + eval_sym(cond->args); + + /* Do the comparison */ + + if (!eval_cmp(cond)) { + + return(FALSE); + } + + cond = UT_LIST_GET_NEXT(cond_list, cond); + } + + return(TRUE); +} + +/************************************************************************* +Tests the other conditions. */ +UNIV_INLINE +ibool +row_sel_test_other_conds( +/*=====================*/ + /* out: TRUE if row passed the tests */ + plan_t* plan) /* in: plan for the table; the column values must + already have been retrieved */ +{ + func_node_t* cond; + + cond = UT_LIST_GET_FIRST(plan->other_conds); + + while (cond) { + eval_exp(cond); + + if (!eval_node_get_ibool_val(cond)) { + + return(FALSE); + } + + cond = UT_LIST_GET_NEXT(cond_list, cond); + } + + return(TRUE); +} + +/************************************************************************* +Retrieves the clustered index record corresponding to a record in a +non-clustered index. Does the necessary locking. */ +static +ulint +row_sel_get_clust_rec( +/*==================*/ + /* out: DB_SUCCESS or error code */ + sel_node_t* node, /* in: select_node */ + plan_t* plan, /* in: plan node for table */ + rec_t* rec, /* in: record in a non-clustered index */ + que_thr_t* thr, /* in: query thread */ + rec_t** out_rec,/* out: clustered record or an old version of + it, NULL if the old version did not exist + in the read view, i.e., it was a fresh + inserted version */ + mtr_t* mtr) /* in: mtr used to get access to the + non-clustered record; the same mtr is used to + access the clustered index */ +{ + dict_index_t* index; + rec_t* clust_rec; + rec_t* old_vers; + ulint err; + mem_heap_t* heap = NULL; + ulint offsets_[REC_OFFS_NORMAL_SIZE]; + ulint* offsets = offsets_; + rec_offs_init(offsets_); + + *out_rec = NULL; + + offsets = rec_get_offsets(rec, + btr_pcur_get_btr_cur(&plan->pcur)->index, + offsets, ULINT_UNDEFINED, &heap); + + row_build_row_ref_fast(plan->clust_ref, plan->clust_map, rec, offsets); + + index = dict_table_get_first_index(plan->table); + + btr_pcur_open_with_no_init(index, plan->clust_ref, PAGE_CUR_LE, + BTR_SEARCH_LEAF, &plan->clust_pcur, + 0, mtr); + + clust_rec = btr_pcur_get_rec(&(plan->clust_pcur)); + + /* Note: only if the search ends up on a non-infimum record is the + low_match value the real match to the search tuple */ + + if (!page_rec_is_user_rec(clust_rec) + || btr_pcur_get_low_match(&(plan->clust_pcur)) + < dict_index_get_n_unique(index)) { + + ut_a(rec_get_deleted_flag(rec, + dict_table_is_comp(plan->table))); + ut_a(node->read_view); + + /* In a rare case it is possible that no clust rec is found + for a delete-marked secondary index record: if in row0umod.c + in row_undo_mod_remove_clust_low() we have already removed + the clust rec, while purge is still cleaning and removing + secondary index records associated with earlier versions of + the clustered index record. In that case we know that the + clustered index record did not exist in the read view of + trx. */ + + goto func_exit; + } + + offsets = rec_get_offsets(clust_rec, index, offsets, + ULINT_UNDEFINED, &heap); + + if (!node->read_view) { + /* Try to place a lock on the index record */ + + /* If innodb_locks_unsafe_for_binlog option is used + or this session is using READ COMMITTED isolation level + we lock only the record, i.e., next-key locking is + not used. */ + ulint lock_type; + trx_t* trx; + + trx = thr_get_trx(thr); + + if (srv_locks_unsafe_for_binlog + || trx->isolation_level == TRX_ISO_READ_COMMITTED) { + lock_type = LOCK_REC_NOT_GAP; + } else { + lock_type = LOCK_ORDINARY; + } + + err = lock_clust_rec_read_check_and_lock( + 0, btr_pcur_get_block(&plan->clust_pcur), + clust_rec, index, offsets, + node->row_lock_mode, lock_type, thr); + + if (err != DB_SUCCESS) { + + goto err_exit; + } + } else { + /* This is a non-locking consistent read: if necessary, fetch + a previous version of the record */ + + old_vers = NULL; + + if (!lock_clust_rec_cons_read_sees(clust_rec, index, offsets, + node->read_view)) { + + err = row_sel_build_prev_vers( + node->read_view, index, clust_rec, + &offsets, &heap, &plan->old_vers_heap, + &old_vers, mtr); + + if (err != DB_SUCCESS) { + + goto err_exit; + } + + clust_rec = old_vers; + + if (clust_rec == NULL) { + goto func_exit; + } + } + + /* If we had to go to an earlier version of row or the + secondary index record is delete marked, then it may be that + the secondary index record corresponding to clust_rec + (or old_vers) is not rec; in that case we must ignore + such row because in our snapshot rec would not have existed. + Remember that from rec we cannot see directly which transaction + id corresponds to it: we have to go to the clustered index + record. A query where we want to fetch all rows where + the secondary index value is in some interval would return + a wrong result if we would not drop rows which we come to + visit through secondary index records that would not really + exist in our snapshot. */ + + if ((old_vers + || rec_get_deleted_flag(rec, dict_table_is_comp( + plan->table))) + && !row_sel_sec_rec_is_for_clust_rec(rec, plan->index, + clust_rec, index)) { + goto func_exit; + } + } + + /* Fetch the columns needed in test conditions. The clustered + index record is protected by a page latch that was acquired + when plan->clust_pcur was positioned. The latch will not be + released until mtr_commit(mtr). */ + + row_sel_fetch_columns(index, clust_rec, offsets, + UT_LIST_GET_FIRST(plan->columns)); + *out_rec = clust_rec; +func_exit: + err = DB_SUCCESS; +err_exit: + if (UNIV_LIKELY_NULL(heap)) { + mem_heap_free(heap); + } + return(err); +} + +/************************************************************************* +Sets a lock on a record. */ +UNIV_INLINE +ulint +sel_set_rec_lock( +/*=============*/ + /* out: DB_SUCCESS or error code */ + const buf_block_t* block, /* in: buffer block of rec */ + const rec_t* rec, /* in: record */ + dict_index_t* index, /* in: index */ + const ulint* offsets,/* in: rec_get_offsets(rec, index) */ + ulint mode, /* in: lock mode */ + ulint type, /* in: LOCK_ORDINARY, LOCK_GAP, or + LOC_REC_NOT_GAP */ + que_thr_t* thr) /* in: query thread */ +{ + trx_t* trx; + ulint err; + + trx = thr_get_trx(thr); + + if (UT_LIST_GET_LEN(trx->trx_locks) > 10000) { + if (buf_LRU_buf_pool_running_out()) { + + return(DB_LOCK_TABLE_FULL); + } + } + + if (dict_index_is_clust(index)) { + err = lock_clust_rec_read_check_and_lock( + 0, block, rec, index, offsets, mode, type, thr); + } else { + err = lock_sec_rec_read_check_and_lock( + 0, block, rec, index, offsets, mode, type, thr); + } + + return(err); +} + +/************************************************************************* +Opens a pcur to a table index. */ +static +void +row_sel_open_pcur( +/*==============*/ + plan_t* plan, /* in: table plan */ + ibool search_latch_locked, + /* in: TRUE if the thread currently + has the search latch locked in + s-mode */ + mtr_t* mtr) /* in: mtr */ +{ + dict_index_t* index; + func_node_t* cond; + que_node_t* exp; + ulint n_fields; + ulint has_search_latch = 0; /* RW_S_LATCH or 0 */ + ulint i; + + if (search_latch_locked) { + has_search_latch = RW_S_LATCH; + } + + index = plan->index; + + /* Calculate the value of the search tuple: the exact match columns + get their expressions evaluated when we evaluate the right sides of + end_conds */ + + cond = UT_LIST_GET_FIRST(plan->end_conds); + + while (cond) { + eval_exp(que_node_get_next(cond->args)); + + cond = UT_LIST_GET_NEXT(cond_list, cond); + } + + if (plan->tuple) { + n_fields = dtuple_get_n_fields(plan->tuple); + + if (plan->n_exact_match < n_fields) { + /* There is a non-exact match field which must be + evaluated separately */ + + eval_exp(plan->tuple_exps[n_fields - 1]); + } + + for (i = 0; i < n_fields; i++) { + exp = plan->tuple_exps[i]; + + dfield_copy_data(dtuple_get_nth_field(plan->tuple, i), + que_node_get_val(exp)); + } + + /* Open pcur to the index */ + + btr_pcur_open_with_no_init(index, plan->tuple, plan->mode, + BTR_SEARCH_LEAF, &plan->pcur, + has_search_latch, mtr); + } else { + /* Open the cursor to the start or the end of the index + (FALSE: no init) */ + + btr_pcur_open_at_index_side(plan->asc, index, BTR_SEARCH_LEAF, + &(plan->pcur), FALSE, mtr); + } + + ut_ad(plan->n_rows_prefetched == 0); + ut_ad(plan->n_rows_fetched == 0); + ut_ad(plan->cursor_at_end == FALSE); + + plan->pcur_is_open = TRUE; +} + +/************************************************************************* +Restores a stored pcur position to a table index. */ +static +ibool +row_sel_restore_pcur_pos( +/*=====================*/ + /* out: TRUE if the cursor should be moved to + the next record after we return from this + function (moved to the previous, in the case + of a descending cursor) without processing + again the current cursor record */ + plan_t* plan, /* in: table plan */ + mtr_t* mtr) /* in: mtr */ +{ + ibool equal_position; + ulint relative_position; + + ut_ad(!plan->cursor_at_end); + + relative_position = btr_pcur_get_rel_pos(&(plan->pcur)); + + equal_position = btr_pcur_restore_position(BTR_SEARCH_LEAF, + &(plan->pcur), mtr); + + /* If the cursor is traveling upwards, and relative_position is + + (1) BTR_PCUR_BEFORE: this is not allowed, as we did not have a lock + yet on the successor of the page infimum; + (2) BTR_PCUR_AFTER: btr_pcur_restore_position placed the cursor on the + first record GREATER than the predecessor of a page supremum; we have + not yet processed the cursor record: no need to move the cursor to the + next record; + (3) BTR_PCUR_ON: btr_pcur_restore_position placed the cursor on the + last record LESS or EQUAL to the old stored user record; (a) if + equal_position is FALSE, this means that the cursor is now on a record + less than the old user record, and we must move to the next record; + (b) if equal_position is TRUE, then if + plan->stored_cursor_rec_processed is TRUE, we must move to the next + record, else there is no need to move the cursor. */ + + if (plan->asc) { + if (relative_position == BTR_PCUR_ON) { + + if (equal_position) { + + return(plan->stored_cursor_rec_processed); + } + + return(TRUE); + } + + ut_ad(relative_position == BTR_PCUR_AFTER + || relative_position == BTR_PCUR_AFTER_LAST_IN_TREE); + + return(FALSE); + } + + /* If the cursor is traveling downwards, and relative_position is + + (1) BTR_PCUR_BEFORE: btr_pcur_restore_position placed the cursor on + the last record LESS than the successor of a page infimum; we have not + processed the cursor record: no need to move the cursor; + (2) BTR_PCUR_AFTER: btr_pcur_restore_position placed the cursor on the + first record GREATER than the predecessor of a page supremum; we have + processed the cursor record: we should move the cursor to the previous + record; + (3) BTR_PCUR_ON: btr_pcur_restore_position placed the cursor on the + last record LESS or EQUAL to the old stored user record; (a) if + equal_position is FALSE, this means that the cursor is now on a record + less than the old user record, and we need not move to the previous + record; (b) if equal_position is TRUE, then if + plan->stored_cursor_rec_processed is TRUE, we must move to the previous + record, else there is no need to move the cursor. */ + + if (relative_position == BTR_PCUR_BEFORE + || relative_position == BTR_PCUR_BEFORE_FIRST_IN_TREE) { + + return(FALSE); + } + + if (relative_position == BTR_PCUR_ON) { + + if (equal_position) { + + return(plan->stored_cursor_rec_processed); + } + + return(FALSE); + } + + ut_ad(relative_position == BTR_PCUR_AFTER + || relative_position == BTR_PCUR_AFTER_LAST_IN_TREE); + + return(TRUE); +} + +/************************************************************************* +Resets a plan cursor to a closed state. */ +UNIV_INLINE +void +plan_reset_cursor( +/*==============*/ + plan_t* plan) /* in: plan */ +{ + plan->pcur_is_open = FALSE; + plan->cursor_at_end = FALSE; + plan->n_rows_fetched = 0; + plan->n_rows_prefetched = 0; +} + +/************************************************************************* +Tries to do a shortcut to fetch a clustered index record with a unique key, +using the hash index if possible (not always). */ +static +ulint +row_sel_try_search_shortcut( +/*========================*/ + /* out: SEL_FOUND, SEL_EXHAUSTED, SEL_RETRY */ + sel_node_t* node, /* in: select node for a consistent read */ + plan_t* plan, /* in: plan for a unique search in clustered + index */ + mtr_t* mtr) /* in: mtr */ +{ + dict_index_t* index; + rec_t* rec; + mem_heap_t* heap = NULL; + ulint offsets_[REC_OFFS_NORMAL_SIZE]; + ulint* offsets = offsets_; + ulint ret; + rec_offs_init(offsets_); + + index = plan->index; + + ut_ad(node->read_view); + ut_ad(plan->unique_search); + ut_ad(!plan->must_get_clust); +#ifdef UNIV_SYNC_DEBUG + ut_ad(rw_lock_own(&btr_search_latch, RW_LOCK_SHARED)); +#endif /* UNIV_SYNC_DEBUG */ + + row_sel_open_pcur(plan, TRUE, mtr); + + rec = btr_pcur_get_rec(&(plan->pcur)); + + if (!page_rec_is_user_rec(rec)) { + + return(SEL_RETRY); + } + + ut_ad(plan->mode == PAGE_CUR_GE); + + /* As the cursor is now placed on a user record after a search with + the mode PAGE_CUR_GE, the up_match field in the cursor tells how many + fields in the user record matched to the search tuple */ + + if (btr_pcur_get_up_match(&(plan->pcur)) < plan->n_exact_match) { + + return(SEL_EXHAUSTED); + } + + /* This is a non-locking consistent read: if necessary, fetch + a previous version of the record */ + + offsets = rec_get_offsets(rec, index, offsets, ULINT_UNDEFINED, &heap); + + if (dict_index_is_clust(index)) { + if (!lock_clust_rec_cons_read_sees(rec, index, offsets, + node->read_view)) { + ret = SEL_RETRY; + goto func_exit; + } + } else if (!lock_sec_rec_cons_read_sees(rec, node->read_view)) { + + ret = SEL_RETRY; + goto func_exit; + } + + /* Test the deleted flag. */ + + if (rec_get_deleted_flag(rec, dict_table_is_comp(plan->table))) { + + ret = SEL_EXHAUSTED; + goto func_exit; + } + + /* Fetch the columns needed in test conditions. The index + record is protected by a page latch that was acquired when + plan->pcur was positioned. The latch will not be released + until mtr_commit(mtr). */ + + row_sel_fetch_columns(index, rec, offsets, + UT_LIST_GET_FIRST(plan->columns)); + + /* Test the rest of search conditions */ + + if (!row_sel_test_other_conds(plan)) { + + ret = SEL_EXHAUSTED; + goto func_exit; + } + + ut_ad(plan->pcur.latch_mode == BTR_SEARCH_LEAF); + + plan->n_rows_fetched++; + ret = SEL_FOUND; +func_exit: + if (UNIV_LIKELY_NULL(heap)) { + mem_heap_free(heap); + } + return(ret); +} + +/************************************************************************* +Performs a select step. */ +static +ulint +row_sel( +/*====*/ + /* out: DB_SUCCESS or error code */ + sel_node_t* node, /* in: select node */ + que_thr_t* thr) /* in: query thread */ +{ + dict_index_t* index; + plan_t* plan; + mtr_t mtr; + ibool moved; + rec_t* rec; + rec_t* old_vers; + rec_t* clust_rec; + ibool search_latch_locked; + ibool consistent_read; + + /* The following flag becomes TRUE when we are doing a + consistent read from a non-clustered index and we must look + at the clustered index to find out the previous delete mark + state of the non-clustered record: */ + + ibool cons_read_requires_clust_rec = FALSE; + ulint cost_counter = 0; + ibool cursor_just_opened; + ibool must_go_to_next; + ibool mtr_has_extra_clust_latch = FALSE; + /* TRUE if the search was made using + a non-clustered index, and we had to + access the clustered record: now &mtr + contains a clustered index latch, and + &mtr must be committed before we move + to the next non-clustered record */ + ulint found_flag; + ulint err; + mem_heap_t* heap = NULL; + ulint offsets_[REC_OFFS_NORMAL_SIZE]; + ulint* offsets = offsets_; + rec_offs_init(offsets_); + + ut_ad(thr->run_node == node); + + search_latch_locked = FALSE; + + if (node->read_view) { + /* In consistent reads, we try to do with the hash index and + not to use the buffer page get. This is to reduce memory bus + load resulting from semaphore operations. The search latch + will be s-locked when we access an index with a unique search + condition, but not locked when we access an index with a + less selective search condition. */ + + consistent_read = TRUE; + } else { + consistent_read = FALSE; + } + +table_loop: + /* TABLE LOOP + ---------- + This is the outer major loop in calculating a join. We come here when + node->fetch_table changes, and after adding a row to aggregate totals + and, of course, when this function is called. */ + + ut_ad(mtr_has_extra_clust_latch == FALSE); + + plan = sel_node_get_nth_plan(node, node->fetch_table); + index = plan->index; + + if (plan->n_rows_prefetched > 0) { + sel_pop_prefetched_row(plan); + + goto next_table_no_mtr; + } + + if (plan->cursor_at_end) { + /* The cursor has already reached the result set end: no more + rows to process for this table cursor, as also the prefetch + stack was empty */ + + ut_ad(plan->pcur_is_open); + + goto table_exhausted_no_mtr; + } + + /* Open a cursor to index, or restore an open cursor position */ + + mtr_start(&mtr); + + if (consistent_read && plan->unique_search && !plan->pcur_is_open + && !plan->must_get_clust + && !plan->table->big_rows) { + if (!search_latch_locked) { + rw_lock_s_lock(&btr_search_latch); + + search_latch_locked = TRUE; + } else if (rw_lock_get_writer(&btr_search_latch) == RW_LOCK_WAIT_EX) { + + /* There is an x-latch request waiting: release the + s-latch for a moment; as an s-latch here is often + kept for some 10 searches before being released, + a waiting x-latch request would block other threads + from acquiring an s-latch for a long time, lowering + performance significantly in multiprocessors. */ + + rw_lock_s_unlock(&btr_search_latch); + rw_lock_s_lock(&btr_search_latch); + } + + found_flag = row_sel_try_search_shortcut(node, plan, &mtr); + + if (found_flag == SEL_FOUND) { + + goto next_table; + + } else if (found_flag == SEL_EXHAUSTED) { + + goto table_exhausted; + } + + ut_ad(found_flag == SEL_RETRY); + + plan_reset_cursor(plan); + + mtr_commit(&mtr); + mtr_start(&mtr); + } + + if (search_latch_locked) { + rw_lock_s_unlock(&btr_search_latch); + + search_latch_locked = FALSE; + } + + if (!plan->pcur_is_open) { + /* Evaluate the expressions to build the search tuple and + open the cursor */ + + row_sel_open_pcur(plan, search_latch_locked, &mtr); + + cursor_just_opened = TRUE; + + /* A new search was made: increment the cost counter */ + cost_counter++; + } else { + /* Restore pcur position to the index */ + + must_go_to_next = row_sel_restore_pcur_pos(plan, &mtr); + + cursor_just_opened = FALSE; + + if (must_go_to_next) { + /* We have already processed the cursor record: move + to the next */ + + goto next_rec; + } + } + +rec_loop: + /* RECORD LOOP + ----------- + In this loop we use pcur and try to fetch a qualifying row, and + also fill the prefetch buffer for this table if n_rows_fetched has + exceeded a threshold. While we are inside this loop, the following + holds: + (1) &mtr is started, + (2) pcur is positioned and open. + + NOTE that if cursor_just_opened is TRUE here, it means that we came + to this point right after row_sel_open_pcur. */ + + ut_ad(mtr_has_extra_clust_latch == FALSE); + + rec = btr_pcur_get_rec(&(plan->pcur)); + + /* PHASE 1: Set a lock if specified */ + + if (!node->asc && cursor_just_opened + && !page_rec_is_supremum(rec)) { + + /* When we open a cursor for a descending search, we must set + a next-key lock on the successor record: otherwise it would + be possible to insert new records next to the cursor position, + and it might be that these new records should appear in the + search result set, resulting in the phantom problem. */ + + if (!consistent_read) { + + /* If innodb_locks_unsafe_for_binlog option is used + or this session is using READ COMMITTED isolation + level, we lock only the record, i.e., next-key + locking is not used. */ + + rec_t* next_rec = page_rec_get_next(rec); + ulint lock_type; + trx_t* trx; + + trx = thr_get_trx(thr); + + offsets = rec_get_offsets(next_rec, index, offsets, + ULINT_UNDEFINED, &heap); + + if (srv_locks_unsafe_for_binlog + || trx->isolation_level + == TRX_ISO_READ_COMMITTED) { + + if (page_rec_is_supremum(next_rec)) { + + goto skip_lock; + } + + lock_type = LOCK_REC_NOT_GAP; + } else { + lock_type = LOCK_ORDINARY; + } + + err = sel_set_rec_lock(btr_pcur_get_block(&plan->pcur), + next_rec, index, offsets, + node->row_lock_mode, + lock_type, thr); + + if (err != DB_SUCCESS) { + /* Note that in this case we will store in pcur + the PREDECESSOR of the record we are waiting + the lock for */ + + goto lock_wait_or_error; + } + } + } + +skip_lock: + if (page_rec_is_infimum(rec)) { + + /* The infimum record on a page cannot be in the result set, + and neither can a record lock be placed on it: we skip such + a record. We also increment the cost counter as we may have + processed yet another page of index. */ + + cost_counter++; + + goto next_rec; + } + + if (!consistent_read) { + /* Try to place a lock on the index record */ + + /* If innodb_locks_unsafe_for_binlog option is used + or this session is using READ COMMITTED isolation level, + we lock only the record, i.e., next-key locking is + not used. */ + + ulint lock_type; + trx_t* trx; + + offsets = rec_get_offsets(rec, index, offsets, + ULINT_UNDEFINED, &heap); + + trx = thr_get_trx(thr); + + if (srv_locks_unsafe_for_binlog + || trx->isolation_level == TRX_ISO_READ_COMMITTED) { + + if (page_rec_is_supremum(rec)) { + + goto next_rec; + } + + lock_type = LOCK_REC_NOT_GAP; + } else { + lock_type = LOCK_ORDINARY; + } + + err = sel_set_rec_lock(btr_pcur_get_block(&plan->pcur), + rec, index, offsets, + node->row_lock_mode, lock_type, thr); + + if (err != DB_SUCCESS) { + + goto lock_wait_or_error; + } + } + + if (page_rec_is_supremum(rec)) { + + /* A page supremum record cannot be in the result set: skip + it now when we have placed a possible lock on it */ + + goto next_rec; + } + + ut_ad(page_rec_is_user_rec(rec)); + + if (cost_counter > SEL_COST_LIMIT) { + + /* Now that we have placed the necessary locks, we can stop + for a while and store the cursor position; NOTE that if we + would store the cursor position BEFORE placing a record lock, + it might happen that the cursor would jump over some records + that another transaction could meanwhile insert adjacent to + the cursor: this would result in the phantom problem. */ + + goto stop_for_a_while; + } + + /* PHASE 2: Check a mixed index mix id if needed */ + + if (plan->unique_search && cursor_just_opened) { + + ut_ad(plan->mode == PAGE_CUR_GE); + + /* As the cursor is now placed on a user record after a search + with the mode PAGE_CUR_GE, the up_match field in the cursor + tells how many fields in the user record matched to the search + tuple */ + + if (btr_pcur_get_up_match(&(plan->pcur)) + < plan->n_exact_match) { + goto table_exhausted; + } + + /* Ok, no need to test end_conds or mix id */ + + } + + /* We are ready to look at a possible new index entry in the result + set: the cursor is now placed on a user record */ + + /* PHASE 3: Get previous version in a consistent read */ + + cons_read_requires_clust_rec = FALSE; + offsets = rec_get_offsets(rec, index, offsets, ULINT_UNDEFINED, &heap); + + if (consistent_read) { + /* This is a non-locking consistent read: if necessary, fetch + a previous version of the record */ + + if (dict_index_is_clust(index)) { + + if (!lock_clust_rec_cons_read_sees(rec, index, offsets, + node->read_view)) { + + err = row_sel_build_prev_vers( + node->read_view, index, rec, + &offsets, &heap, &plan->old_vers_heap, + &old_vers, &mtr); + + if (err != DB_SUCCESS) { + + goto lock_wait_or_error; + } + + if (old_vers == NULL) { + offsets = rec_get_offsets( + rec, index, offsets, + ULINT_UNDEFINED, &heap); + + /* Fetch the columns needed in + test conditions. The clustered + index record is protected by a + page latch that was acquired + by row_sel_open_pcur() or + row_sel_restore_pcur_pos(). + The latch will not be released + until mtr_commit(mtr). */ + + row_sel_fetch_columns( + index, rec, offsets, + UT_LIST_GET_FIRST( + plan->columns)); + + if (!row_sel_test_end_conds(plan)) { + + goto table_exhausted; + } + + goto next_rec; + } + + rec = old_vers; + } + } else if (!lock_sec_rec_cons_read_sees(rec, + node->read_view)) { + cons_read_requires_clust_rec = TRUE; + } + } + + /* PHASE 4: Test search end conditions and deleted flag */ + + /* Fetch the columns needed in test conditions. The record is + protected by a page latch that was acquired by + row_sel_open_pcur() or row_sel_restore_pcur_pos(). The latch + will not be released until mtr_commit(mtr). */ + + row_sel_fetch_columns(index, rec, offsets, + UT_LIST_GET_FIRST(plan->columns)); + + /* Test the selection end conditions: these can only contain columns + which already are found in the index, even though the index might be + non-clustered */ + + if (plan->unique_search && cursor_just_opened) { + + /* No test necessary: the test was already made above */ + + } else if (!row_sel_test_end_conds(plan)) { + + goto table_exhausted; + } + + if (rec_get_deleted_flag(rec, dict_table_is_comp(plan->table)) + && !cons_read_requires_clust_rec) { + + /* The record is delete marked: we can skip it if this is + not a consistent read which might see an earlier version + of a non-clustered index record */ + + if (plan->unique_search) { + + goto table_exhausted; + } + + goto next_rec; + } + + /* PHASE 5: Get the clustered index record, if needed and if we did + not do the search using the clustered index */ + + if (plan->must_get_clust || cons_read_requires_clust_rec) { + + /* It was a non-clustered index and we must fetch also the + clustered index record */ + + err = row_sel_get_clust_rec(node, plan, rec, thr, &clust_rec, + &mtr); + mtr_has_extra_clust_latch = TRUE; + + if (err != DB_SUCCESS) { + + goto lock_wait_or_error; + } + + /* Retrieving the clustered record required a search: + increment the cost counter */ + + cost_counter++; + + if (clust_rec == NULL) { + /* The record did not exist in the read view */ + ut_ad(consistent_read); + + goto next_rec; + } + + if (rec_get_deleted_flag(clust_rec, + dict_table_is_comp(plan->table))) { + + /* The record is delete marked: we can skip it */ + + goto next_rec; + } + + if (node->can_get_updated) { + + btr_pcur_store_position(&(plan->clust_pcur), &mtr); + } + } + + /* PHASE 6: Test the rest of search conditions */ + + if (!row_sel_test_other_conds(plan)) { + + if (plan->unique_search) { + + goto table_exhausted; + } + + goto next_rec; + } + + /* PHASE 7: We found a new qualifying row for the current table; push + the row if prefetch is on, or move to the next table in the join */ + + plan->n_rows_fetched++; + + ut_ad(plan->pcur.latch_mode == BTR_SEARCH_LEAF); + + if ((plan->n_rows_fetched <= SEL_PREFETCH_LIMIT) + || plan->unique_search || plan->no_prefetch + || plan->table->big_rows) { + + /* No prefetch in operation: go to the next table */ + + goto next_table; + } + + sel_push_prefetched_row(plan); + + if (plan->n_rows_prefetched == SEL_MAX_N_PREFETCH) { + + /* The prefetch buffer is now full */ + + sel_pop_prefetched_row(plan); + + goto next_table; + } + +next_rec: + ut_ad(!search_latch_locked); + + if (mtr_has_extra_clust_latch) { + + /* We must commit &mtr if we are moving to the next + non-clustered index record, because we could break the + latching order if we would access a different clustered + index page right away without releasing the previous. */ + + goto commit_mtr_for_a_while; + } + + if (node->asc) { + moved = btr_pcur_move_to_next(&(plan->pcur), &mtr); + } else { + moved = btr_pcur_move_to_prev(&(plan->pcur), &mtr); + } + + if (!moved) { + + goto table_exhausted; + } + + cursor_just_opened = FALSE; + + /* END OF RECORD LOOP + ------------------ */ + goto rec_loop; + +next_table: + /* We found a record which satisfies the conditions: we can move to + the next table or return a row in the result set */ + + ut_ad(btr_pcur_is_on_user_rec(&plan->pcur)); + + if (plan->unique_search && !node->can_get_updated) { + + plan->cursor_at_end = TRUE; + } else { + ut_ad(!search_latch_locked); + + plan->stored_cursor_rec_processed = TRUE; + + btr_pcur_store_position(&(plan->pcur), &mtr); + } + + mtr_commit(&mtr); + + mtr_has_extra_clust_latch = FALSE; + +next_table_no_mtr: + /* If we use 'goto' to this label, it means that the row was popped + from the prefetched rows stack, and &mtr is already committed */ + + if (node->fetch_table + 1 == node->n_tables) { + + sel_eval_select_list(node); + + if (node->is_aggregate) { + + goto table_loop; + } + + sel_assign_into_var_values(node->into_list, node); + + thr->run_node = que_node_get_parent(node); + + err = DB_SUCCESS; + goto func_exit; + } + + node->fetch_table++; + + /* When we move to the next table, we first reset the plan cursor: + we do not care about resetting it when we backtrack from a table */ + + plan_reset_cursor(sel_node_get_nth_plan(node, node->fetch_table)); + + goto table_loop; + +table_exhausted: + /* The table cursor pcur reached the result set end: backtrack to the + previous table in the join if we do not have cached prefetched rows */ + + plan->cursor_at_end = TRUE; + + mtr_commit(&mtr); + + mtr_has_extra_clust_latch = FALSE; + + if (plan->n_rows_prefetched > 0) { + /* The table became exhausted during a prefetch */ + + sel_pop_prefetched_row(plan); + + goto next_table_no_mtr; + } + +table_exhausted_no_mtr: + if (node->fetch_table == 0) { + err = DB_SUCCESS; + + if (node->is_aggregate && !node->aggregate_already_fetched) { + + node->aggregate_already_fetched = TRUE; + + sel_assign_into_var_values(node->into_list, node); + + thr->run_node = que_node_get_parent(node); + } else { + node->state = SEL_NODE_NO_MORE_ROWS; + + thr->run_node = que_node_get_parent(node); + } + + goto func_exit; + } + + node->fetch_table--; + + goto table_loop; + +stop_for_a_while: + /* Return control for a while to que_run_threads, so that runaway + queries can be canceled. NOTE that when we come here, we must, in a + locking read, have placed the necessary (possibly waiting request) + record lock on the cursor record or its successor: when we reposition + the cursor, this record lock guarantees that nobody can meanwhile have + inserted new records which should have appeared in the result set, + which would result in the phantom problem. */ + + ut_ad(!search_latch_locked); + + plan->stored_cursor_rec_processed = FALSE; + btr_pcur_store_position(&(plan->pcur), &mtr); + + mtr_commit(&mtr); + +#ifdef UNIV_SYNC_DEBUG + ut_ad(sync_thread_levels_empty_gen(TRUE)); +#endif /* UNIV_SYNC_DEBUG */ + err = DB_SUCCESS; + goto func_exit; + +commit_mtr_for_a_while: + /* Stores the cursor position and commits &mtr; this is used if + &mtr may contain latches which would break the latching order if + &mtr would not be committed and the latches released. */ + + plan->stored_cursor_rec_processed = TRUE; + + ut_ad(!search_latch_locked); + btr_pcur_store_position(&(plan->pcur), &mtr); + + mtr_commit(&mtr); + + mtr_has_extra_clust_latch = FALSE; + +#ifdef UNIV_SYNC_DEBUG + ut_ad(sync_thread_levels_empty_gen(TRUE)); +#endif /* UNIV_SYNC_DEBUG */ + + goto table_loop; + +lock_wait_or_error: + /* See the note at stop_for_a_while: the same holds for this case */ + + ut_ad(!btr_pcur_is_before_first_on_page(&plan->pcur) || !node->asc); + ut_ad(!search_latch_locked); + + plan->stored_cursor_rec_processed = FALSE; + btr_pcur_store_position(&(plan->pcur), &mtr); + + mtr_commit(&mtr); + +#ifdef UNIV_SYNC_DEBUG + ut_ad(sync_thread_levels_empty_gen(TRUE)); +#endif /* UNIV_SYNC_DEBUG */ + +func_exit: + if (search_latch_locked) { + rw_lock_s_unlock(&btr_search_latch); + } + if (UNIV_LIKELY_NULL(heap)) { + mem_heap_free(heap); + } + return(err); +} + +/************************************************************************** +Performs a select step. This is a high-level function used in SQL execution +graphs. */ +UNIV_INTERN +que_thr_t* +row_sel_step( +/*=========*/ + /* out: query thread to run next or NULL */ + que_thr_t* thr) /* in: query thread */ +{ + ulint i_lock_mode; + sym_node_t* table_node; + sel_node_t* node; + ulint err; + + ut_ad(thr); + + node = thr->run_node; + + ut_ad(que_node_get_type(node) == QUE_NODE_SELECT); + + /* If this is a new time this node is executed (or when execution + resumes after wait for a table intention lock), set intention locks + on the tables, or assign a read view */ + + if (node->into_list && (thr->prev_node == que_node_get_parent(node))) { + + node->state = SEL_NODE_OPEN; + } + + if (node->state == SEL_NODE_OPEN) { + + /* It may be that the current session has not yet started + its transaction, or it has been committed: */ + + trx_start_if_not_started(thr_get_trx(thr)); + + plan_reset_cursor(sel_node_get_nth_plan(node, 0)); + + if (node->consistent_read) { + /* Assign a read view for the query */ + node->read_view = trx_assign_read_view( + thr_get_trx(thr)); + } else { + if (node->set_x_locks) { + i_lock_mode = LOCK_IX; + } else { + i_lock_mode = LOCK_IS; + } + + table_node = node->table_list; + + while (table_node) { + err = lock_table(0, table_node->table, + i_lock_mode, thr); + if (err != DB_SUCCESS) { + thr_get_trx(thr)->error_state = err; + + return(NULL); + } + + table_node = que_node_get_next(table_node); + } + } + + /* If this is an explicit cursor, copy stored procedure + variable values, so that the values cannot change between + fetches (currently, we copy them also for non-explicit + cursors) */ + + if (node->explicit_cursor + && UT_LIST_GET_FIRST(node->copy_variables)) { + + row_sel_copy_input_variable_vals(node); + } + + node->state = SEL_NODE_FETCH; + node->fetch_table = 0; + + if (node->is_aggregate) { + /* Reset the aggregate total values */ + sel_reset_aggregate_vals(node); + } + } + + err = row_sel(node, thr); + + /* NOTE! if queries are parallelized, the following assignment may + have problems; the assignment should be made only if thr is the + only top-level thr in the graph: */ + + thr->graph->last_sel_node = node; + + if (err != DB_SUCCESS) { + thr_get_trx(thr)->error_state = err; + + return(NULL); + } + + return(thr); +} + +/************************************************************************** +Performs a fetch for a cursor. */ +UNIV_INTERN +que_thr_t* +fetch_step( +/*=======*/ + /* out: query thread to run next or NULL */ + que_thr_t* thr) /* in: query thread */ +{ + sel_node_t* sel_node; + fetch_node_t* node; + + ut_ad(thr); + + node = thr->run_node; + sel_node = node->cursor_def; + + ut_ad(que_node_get_type(node) == QUE_NODE_FETCH); + + if (thr->prev_node != que_node_get_parent(node)) { + + if (sel_node->state != SEL_NODE_NO_MORE_ROWS) { + + if (node->into_list) { + sel_assign_into_var_values(node->into_list, + sel_node); + } else { + void* ret = (*node->func->func)( + sel_node, node->func->arg); + + if (!ret) { + sel_node->state + = SEL_NODE_NO_MORE_ROWS; + } + } + } + + thr->run_node = que_node_get_parent(node); + + return(thr); + } + + /* Make the fetch node the parent of the cursor definition for + the time of the fetch, so that execution knows to return to this + fetch node after a row has been selected or we know that there is + no row left */ + + sel_node->common.parent = node; + + if (sel_node->state == SEL_NODE_CLOSED) { + fprintf(stderr, + "InnoDB: Error: fetch called on a closed cursor\n"); + + thr_get_trx(thr)->error_state = DB_ERROR; + + return(NULL); + } + + thr->run_node = sel_node; + + return(thr); +} + +/******************************************************************** +Sample callback function for fetch that prints each row.*/ +UNIV_INTERN +void* +row_fetch_print( +/*============*/ + /* out: always returns non-NULL */ + void* row, /* in: sel_node_t* */ + void* user_arg) /* in: not used */ +{ + sel_node_t* node = row; + que_node_t* exp; + ulint i = 0; + + UT_NOT_USED(user_arg); + + fprintf(stderr, "row_fetch_print: row %p\n", row); + + exp = node->select_list; + + while (exp) { + dfield_t* dfield = que_node_get_val(exp); + const dtype_t* type = dfield_get_type(dfield); + + fprintf(stderr, " column %lu:\n", (ulong)i); + + dtype_print(type); + putc('\n', stderr); + + if (dfield_get_len(dfield) != UNIV_SQL_NULL) { + ut_print_buf(stderr, dfield_get_data(dfield), + dfield_get_len(dfield)); + putc('\n', stderr); + } else { + fputs(" <NULL>;\n", stderr); + } + + exp = que_node_get_next(exp); + i++; + } + + return((void*)42); +} + +/******************************************************************** +Callback function for fetch that stores an unsigned 4 byte integer to the +location pointed. The column's type must be DATA_INT, DATA_UNSIGNED, length += 4. */ +UNIV_INTERN +void* +row_fetch_store_uint4( +/*==================*/ + /* out: always returns NULL */ + void* row, /* in: sel_node_t* */ + void* user_arg) /* in: data pointer */ +{ + sel_node_t* node = row; + ib_uint32_t* val = user_arg; + ulint tmp; + + dfield_t* dfield = que_node_get_val(node->select_list); + const dtype_t* type = dfield_get_type(dfield); + ulint len = dfield_get_len(dfield); + + ut_a(dtype_get_mtype(type) == DATA_INT); + ut_a(dtype_get_prtype(type) & DATA_UNSIGNED); + ut_a(len == 4); + + tmp = mach_read_from_4(dfield_get_data(dfield)); + *val = (ib_uint32_t) tmp; + + return(NULL); +} + +/*************************************************************** +Prints a row in a select result. */ +UNIV_INTERN +que_thr_t* +row_printf_step( +/*============*/ + /* out: query thread to run next or NULL */ + que_thr_t* thr) /* in: query thread */ +{ + row_printf_node_t* node; + sel_node_t* sel_node; + que_node_t* arg; + + ut_ad(thr); + + node = thr->run_node; + + sel_node = node->sel_node; + + ut_ad(que_node_get_type(node) == QUE_NODE_ROW_PRINTF); + + if (thr->prev_node == que_node_get_parent(node)) { + + /* Reset the cursor */ + sel_node->state = SEL_NODE_OPEN; + + /* Fetch next row to print */ + + thr->run_node = sel_node; + + return(thr); + } + + if (sel_node->state != SEL_NODE_FETCH) { + + ut_ad(sel_node->state == SEL_NODE_NO_MORE_ROWS); + + /* No more rows to print */ + + thr->run_node = que_node_get_parent(node); + + return(thr); + } + + arg = sel_node->select_list; + + while (arg) { + dfield_print_also_hex(que_node_get_val(arg)); + + fputs(" ::: ", stderr); + + arg = que_node_get_next(arg); + } + + putc('\n', stderr); + + /* Fetch next row to print */ + + thr->run_node = sel_node; + + return(thr); +} + +/******************************************************************** +Converts a key value stored in MySQL format to an Innobase dtuple. The last +field of the key value may be just a prefix of a fixed length field: hence +the parameter key_len. But currently we do not allow search keys where the +last field is only a prefix of the full key field len and print a warning if +such appears. A counterpart of this function is +ha_innobase::store_key_val_for_row() in ha_innodb.cc. */ +UNIV_INTERN +void +row_sel_convert_mysql_key_to_innobase( +/*==================================*/ + dtuple_t* tuple, /* in/out: tuple where to build; + NOTE: we assume that the type info + in the tuple is already according + to index! */ + byte* buf, /* in: buffer to use in field + conversions */ + ulint buf_len, /* in: buffer length */ + dict_index_t* index, /* in: index of the key value */ + const byte* key_ptr, /* in: MySQL key value */ + ulint key_len, /* in: MySQL key value length */ + trx_t* trx) /* in: transaction */ +{ + byte* original_buf = buf; + const byte* original_key_ptr = key_ptr; + dict_field_t* field; + dfield_t* dfield; + ulint data_offset; + ulint data_len; + ulint data_field_len; + ibool is_null; + const byte* key_end; + ulint n_fields = 0; + + /* For documentation of the key value storage format in MySQL, see + ha_innobase::store_key_val_for_row() in ha_innodb.cc. */ + + key_end = key_ptr + key_len; + + /* Permit us to access any field in the tuple (ULINT_MAX): */ + + dtuple_set_n_fields(tuple, ULINT_MAX); + + dfield = dtuple_get_nth_field(tuple, 0); + field = dict_index_get_nth_field(index, 0); + + if (UNIV_UNLIKELY(dfield_get_type(dfield)->mtype == DATA_SYS)) { + /* A special case: we are looking for a position in the + generated clustered index which InnoDB automatically added + to a table with no primary key: the first and the only + ordering column is ROW_ID which InnoDB stored to the key_ptr + buffer. */ + + ut_a(key_len == DATA_ROW_ID_LEN); + + dfield_set_data(dfield, key_ptr, DATA_ROW_ID_LEN); + + dtuple_set_n_fields(tuple, 1); + + return; + } + + while (key_ptr < key_end) { + + ulint type = dfield_get_type(dfield)->mtype; + ut_a(field->col->mtype == type); + + data_offset = 0; + is_null = FALSE; + + if (!(dfield_get_type(dfield)->prtype & DATA_NOT_NULL)) { + /* The first byte in the field tells if this is + an SQL NULL value */ + + data_offset = 1; + + if (*key_ptr != 0) { + dfield_set_null(dfield); + + is_null = TRUE; + } + } + + /* Calculate data length and data field total length */ + + if (type == DATA_BLOB) { + /* The key field is a column prefix of a BLOB or + TEXT */ + + ut_a(field->prefix_len > 0); + + /* MySQL stores the actual data length to the first 2 + bytes after the optional SQL NULL marker byte. The + storage format is little-endian, that is, the most + significant byte at a higher address. In UTF-8, MySQL + seems to reserve field->prefix_len bytes for + storing this field in the key value buffer, even + though the actual value only takes data_len bytes + from the start. */ + + data_len = key_ptr[data_offset] + + 256 * key_ptr[data_offset + 1]; + data_field_len = data_offset + 2 + field->prefix_len; + + data_offset += 2; + + /* Now that we know the length, we store the column + value like it would be a fixed char field */ + + } else if (field->prefix_len > 0) { + /* Looks like MySQL pads unused end bytes in the + prefix with space. Therefore, also in UTF-8, it is ok + to compare with a prefix containing full prefix_len + bytes, and no need to take at most prefix_len / 3 + UTF-8 characters from the start. + If the prefix is used as the upper end of a LIKE + 'abc%' query, then MySQL pads the end with chars + 0xff. TODO: in that case does it any harm to compare + with the full prefix_len bytes. How do characters + 0xff in UTF-8 behave? */ + + data_len = field->prefix_len; + data_field_len = data_offset + data_len; + } else { + data_len = dfield_get_type(dfield)->len; + data_field_len = data_offset + data_len; + } + + if (UNIV_UNLIKELY + (dtype_get_mysql_type(dfield_get_type(dfield)) + == DATA_MYSQL_TRUE_VARCHAR) + && UNIV_LIKELY(type != DATA_INT)) { + /* In a MySQL key value format, a true VARCHAR is + always preceded by 2 bytes of a length field. + dfield_get_type(dfield)->len returns the maximum + 'payload' len in bytes. That does not include the + 2 bytes that tell the actual data length. + + We added the check != DATA_INT to make sure we do + not treat MySQL ENUM or SET as a true VARCHAR! */ + + data_len += 2; + data_field_len += 2; + } + + /* Storing may use at most data_len bytes of buf */ + + if (UNIV_LIKELY(!is_null)) { + row_mysql_store_col_in_innobase_format( + dfield, buf, + FALSE, /* MySQL key value format col */ + key_ptr + data_offset, data_len, + dict_table_is_comp(index->table)); + buf += data_len; + } + + key_ptr += data_field_len; + + if (UNIV_UNLIKELY(key_ptr > key_end)) { + /* The last field in key was not a complete key field + but a prefix of it. + + Print a warning about this! HA_READ_PREFIX_LAST does + not currently work in InnoDB with partial-field key + value prefixes. Since MySQL currently uses a padding + trick to calculate LIKE 'abc%' type queries there + should never be partial-field prefixes in searches. */ + + ut_print_timestamp(stderr); + + fputs(" InnoDB: Warning: using a partial-field" + " key prefix in search.\n" + "InnoDB: ", stderr); + dict_index_name_print(stderr, trx, index); + fprintf(stderr, ". Last data field length %lu bytes,\n" + "InnoDB: key ptr now exceeds" + " key end by %lu bytes.\n" + "InnoDB: Key value in the MySQL format:\n", + (ulong) data_field_len, + (ulong) (key_ptr - key_end)); + fflush(stderr); + ut_print_buf(stderr, original_key_ptr, key_len); + putc('\n', stderr); + + if (!is_null) { + ulint len = dfield_get_len(dfield); + dfield_set_len(dfield, len + - (ulint) (key_ptr - key_end)); + } + } + + n_fields++; + field++; + dfield++; + } + + ut_a(buf <= original_buf + buf_len); + + /* We set the length of tuple to n_fields: we assume that the memory + area allocated for it is big enough (usually bigger than n_fields). */ + + dtuple_set_n_fields(tuple, n_fields); +} + +/****************************************************************** +Stores the row id to the prebuilt struct. */ +static +void +row_sel_store_row_id_to_prebuilt( +/*=============================*/ + row_prebuilt_t* prebuilt, /* in/out: prebuilt */ + const rec_t* index_rec, /* in: record */ + const dict_index_t* index, /* in: index of the record */ + const ulint* offsets) /* in: rec_get_offsets + (index_rec, index) */ +{ + const byte* data; + ulint len; + + ut_ad(rec_offs_validate(index_rec, index, offsets)); + + data = rec_get_nth_field( + index_rec, offsets, + dict_index_get_sys_col_pos(index, DATA_ROW_ID), &len); + + if (UNIV_UNLIKELY(len != DATA_ROW_ID_LEN)) { + fprintf(stderr, + "InnoDB: Error: Row id field is" + " wrong length %lu in ", (ulong) len); + dict_index_name_print(stderr, prebuilt->trx, index); + fprintf(stderr, "\n" + "InnoDB: Field number %lu, record:\n", + (ulong) dict_index_get_sys_col_pos(index, + DATA_ROW_ID)); + rec_print_new(stderr, index_rec, offsets); + putc('\n', stderr); + ut_error; + } + + ut_memcpy(prebuilt->row_id, data, len); +} + +/****************************************************************** +Stores a non-SQL-NULL field in the MySQL format. The counterpart of this +function is row_mysql_store_col_in_innobase_format() in row0mysql.c. */ +static +void +row_sel_field_store_in_mysql_format( +/*================================*/ + byte* dest, /* in/out: buffer where to store; NOTE + that BLOBs are not in themselves + stored here: the caller must allocate + and copy the BLOB into buffer before, + and pass the pointer to the BLOB in + 'data' */ + const mysql_row_templ_t* templ, + /* in: MySQL column template. + Its following fields are referenced: + type, is_unsigned, mysql_col_len, + mbminlen, mbmaxlen */ + const byte* data, /* in: data to store */ + ulint len) /* in: length of the data */ +{ + byte* ptr; + byte* field_end; + byte* pad_ptr; + + ut_ad(len != UNIV_SQL_NULL); + + switch (templ->type) { + case DATA_INT: + /* Convert integer data from Innobase to a little-endian + format, sign bit restored to normal */ + + ptr = dest + len; + + for (;;) { + ptr--; + *ptr = *data; + if (ptr == dest) { + break; + } + data++; + } + + if (!templ->is_unsigned) { + dest[len - 1] = (byte) (dest[len - 1] ^ 128); + } + + ut_ad(templ->mysql_col_len == len); + break; + + case DATA_VARCHAR: + case DATA_VARMYSQL: + case DATA_BINARY: + field_end = dest + templ->mysql_col_len; + + if (templ->mysql_type == DATA_MYSQL_TRUE_VARCHAR) { + /* This is a >= 5.0.3 type true VARCHAR. Store the + length of the data to the first byte or the first + two bytes of dest. */ + + dest = row_mysql_store_true_var_len( + dest, len, templ->mysql_length_bytes); + } + + /* Copy the actual data */ + ut_memcpy(dest, data, len); + + /* Pad with trailing spaces. We pad with spaces also the + unused end of a >= 5.0.3 true VARCHAR column, just in case + MySQL expects its contents to be deterministic. */ + + pad_ptr = dest + len; + + ut_ad(templ->mbminlen <= templ->mbmaxlen); + + /* We handle UCS2 charset strings differently. */ + if (templ->mbminlen == 2) { + /* A space char is two bytes, 0x0020 in UCS2 */ + + if (len & 1) { + /* A 0x20 has been stripped from the column. + Pad it back. */ + + if (pad_ptr < field_end) { + *pad_ptr = 0x20; + pad_ptr++; + } + } + + /* Pad the rest of the string with 0x0020 */ + + while (pad_ptr < field_end) { + *pad_ptr = 0x00; + pad_ptr++; + *pad_ptr = 0x20; + pad_ptr++; + } + } else { + ut_ad(templ->mbminlen == 1); + /* space=0x20 */ + + memset(pad_ptr, 0x20, field_end - pad_ptr); + } + break; + + case DATA_BLOB: + /* Store a pointer to the BLOB buffer to dest: the BLOB was + already copied to the buffer in row_sel_store_mysql_rec */ + + row_mysql_store_blob_ref(dest, templ->mysql_col_len, data, + len); + break; + + case DATA_MYSQL: + memcpy(dest, data, len); + + ut_ad(templ->mysql_col_len >= len); + ut_ad(templ->mbmaxlen >= templ->mbminlen); + + ut_ad(templ->mbmaxlen > templ->mbminlen + || templ->mysql_col_len == len); + /* The following assertion would fail for old tables + containing UTF-8 ENUM columns due to Bug #9526. */ + ut_ad(!templ->mbmaxlen + || !(templ->mysql_col_len % templ->mbmaxlen)); + ut_ad(len * templ->mbmaxlen >= templ->mysql_col_len); + + if (templ->mbminlen != templ->mbmaxlen) { + /* Pad with spaces. This undoes the stripping + done in row0mysql.ic, function + row_mysql_store_col_in_innobase_format(). */ + + memset(dest + len, 0x20, templ->mysql_col_len - len); + } + break; + + default: +#ifdef UNIV_DEBUG + case DATA_SYS_CHILD: + case DATA_SYS: + /* These column types should never be shipped to MySQL. */ + ut_ad(0); + + case DATA_CHAR: + case DATA_FIXBINARY: + case DATA_FLOAT: + case DATA_DOUBLE: + case DATA_DECIMAL: + /* Above are the valid column types for MySQL data. */ +#endif /* UNIV_DEBUG */ + ut_ad(templ->mysql_col_len == len); + memcpy(dest, data, len); + } +} + +/****************************************************************** +Convert a row in the Innobase format to a row in the MySQL format. +Note that the template in prebuilt may advise us to copy only a few +columns to mysql_rec, other columns are left blank. All columns may not +be needed in the query. */ +static +ibool +row_sel_store_mysql_rec( +/*====================*/ + /* out: TRUE if success, FALSE if + could not allocate memory for a BLOB + (though we may also assert in that + case) */ + byte* mysql_rec, /* out: row in the MySQL format */ + row_prebuilt_t* prebuilt, /* in: prebuilt struct */ + const rec_t* rec, /* in: Innobase record in the index + which was described in prebuilt's + template; must be protected by + a page latch */ + const ulint* offsets) /* in: array returned by + rec_get_offsets() */ +{ + mysql_row_templ_t* templ; + mem_heap_t* extern_field_heap = NULL; + mem_heap_t* heap; + const byte* data; + ulint len; + ulint i; + + ut_ad(prebuilt->mysql_template); + ut_ad(prebuilt->default_rec); + ut_ad(rec_offs_validate(rec, NULL, offsets)); + + if (UNIV_LIKELY_NULL(prebuilt->blob_heap)) { + mem_heap_free(prebuilt->blob_heap); + prebuilt->blob_heap = NULL; + } + + for (i = 0; i < prebuilt->n_template; i++) { + + templ = prebuilt->mysql_template + i; + + if (UNIV_UNLIKELY(rec_offs_nth_extern(offsets, + templ->rec_field_no))) { + + /* Copy an externally stored field to the temporary + heap */ + + ut_a(!prebuilt->trx->has_search_latch); + + if (UNIV_UNLIKELY(templ->type == DATA_BLOB)) { + if (prebuilt->blob_heap == NULL) { + prebuilt->blob_heap = mem_heap_create( + UNIV_PAGE_SIZE); + } + + heap = prebuilt->blob_heap; + } else { + extern_field_heap + = mem_heap_create(UNIV_PAGE_SIZE); + + heap = extern_field_heap; + } + + /* NOTE: if we are retrieving a big BLOB, we may + already run out of memory in the next call, which + causes an assert */ + + data = btr_rec_copy_externally_stored_field( + rec, offsets, + dict_table_zip_size(prebuilt->table), + templ->rec_field_no, &len, heap); + + ut_a(len != UNIV_SQL_NULL); + } else { + /* Field is stored in the row. */ + + data = rec_get_nth_field(rec, offsets, + templ->rec_field_no, &len); + + if (UNIV_UNLIKELY(templ->type == DATA_BLOB) + && len != UNIV_SQL_NULL) { + + /* It is a BLOB field locally stored in the + InnoDB record: we MUST copy its contents to + prebuilt->blob_heap here because later code + assumes all BLOB values have been copied to a + safe place. */ + + if (prebuilt->blob_heap == NULL) { + prebuilt->blob_heap = mem_heap_create( + UNIV_PAGE_SIZE); + } + + data = memcpy(mem_heap_alloc( + prebuilt->blob_heap, len), + data, len); + } + } + + if (len != UNIV_SQL_NULL) { + row_sel_field_store_in_mysql_format( + mysql_rec + templ->mysql_col_offset, + templ, data, len); + + /* Cleanup */ + if (extern_field_heap) { + mem_heap_free(extern_field_heap); + extern_field_heap = NULL; + } + + if (templ->mysql_null_bit_mask) { + /* It is a nullable column with a non-NULL + value */ + mysql_rec[templ->mysql_null_byte_offset] + &= ~(byte) templ->mysql_null_bit_mask; + } + } else { + /* MySQL assumes that the field for an SQL + NULL value is set to the default value. */ + + mysql_rec[templ->mysql_null_byte_offset] + |= (byte) templ->mysql_null_bit_mask; + memcpy(mysql_rec + templ->mysql_col_offset, + prebuilt->default_rec + templ->mysql_col_offset, + templ->mysql_col_len); + } + } + + return(TRUE); +} + +/************************************************************************* +Builds a previous version of a clustered index record for a consistent read */ +static +ulint +row_sel_build_prev_vers_for_mysql( +/*==============================*/ + /* out: DB_SUCCESS or error code */ + read_view_t* read_view, /* in: read view */ + dict_index_t* clust_index, /* in: clustered index */ + row_prebuilt_t* prebuilt, /* in: prebuilt struct */ + const rec_t* rec, /* in: record in a clustered index */ + ulint** offsets, /* in/out: offsets returned by + rec_get_offsets(rec, clust_index) */ + mem_heap_t** offset_heap, /* in/out: memory heap from which + the offsets are allocated */ + rec_t** old_vers, /* out: old version, or NULL if the + record does not exist in the view: + i.e., it was freshly inserted + afterwards */ + mtr_t* mtr) /* in: mtr */ +{ + ulint err; + + if (prebuilt->old_vers_heap) { + mem_heap_empty(prebuilt->old_vers_heap); + } else { + prebuilt->old_vers_heap = mem_heap_create(200); + } + + err = row_vers_build_for_consistent_read( + rec, mtr, clust_index, offsets, read_view, offset_heap, + prebuilt->old_vers_heap, old_vers); + return(err); +} + +/************************************************************************* +Retrieves the clustered index record corresponding to a record in a +non-clustered index. Does the necessary locking. Used in the MySQL +interface. */ +static +ulint +row_sel_get_clust_rec_for_mysql( +/*============================*/ + /* out: DB_SUCCESS or error code */ + row_prebuilt_t* prebuilt,/* in: prebuilt struct in the handle */ + dict_index_t* sec_index,/* in: secondary index where rec resides */ + const rec_t* rec, /* in: record in a non-clustered index; if + this is a locking read, then rec is not + allowed to be delete-marked, and that would + not make sense either */ + que_thr_t* thr, /* in: query thread */ + const rec_t** out_rec,/* out: clustered record or an old version of + it, NULL if the old version did not exist + in the read view, i.e., it was a fresh + inserted version */ + ulint** offsets,/* in: offsets returned by + rec_get_offsets(rec, sec_index); + out: offsets returned by + rec_get_offsets(out_rec, clust_index) */ + mem_heap_t** offset_heap,/* in/out: memory heap from which + the offsets are allocated */ + mtr_t* mtr) /* in: mtr used to get access to the + non-clustered record; the same mtr is used to + access the clustered index */ +{ + dict_index_t* clust_index; + const rec_t* clust_rec; + rec_t* old_vers; + ulint err; + trx_t* trx; + + *out_rec = NULL; + trx = thr_get_trx(thr); + + row_build_row_ref_in_tuple(prebuilt->clust_ref, rec, + sec_index, *offsets, trx); + + clust_index = dict_table_get_first_index(sec_index->table); + + btr_pcur_open_with_no_init(clust_index, prebuilt->clust_ref, + PAGE_CUR_LE, BTR_SEARCH_LEAF, + prebuilt->clust_pcur, 0, mtr); + + clust_rec = btr_pcur_get_rec(prebuilt->clust_pcur); + + prebuilt->clust_pcur->trx_if_known = trx; + + /* Note: only if the search ends up on a non-infimum record is the + low_match value the real match to the search tuple */ + + if (!page_rec_is_user_rec(clust_rec) + || btr_pcur_get_low_match(prebuilt->clust_pcur) + < dict_index_get_n_unique(clust_index)) { + + /* In a rare case it is possible that no clust rec is found + for a delete-marked secondary index record: if in row0umod.c + in row_undo_mod_remove_clust_low() we have already removed + the clust rec, while purge is still cleaning and removing + secondary index records associated with earlier versions of + the clustered index record. In that case we know that the + clustered index record did not exist in the read view of + trx. */ + + if (!rec_get_deleted_flag(rec, + dict_table_is_comp(sec_index->table)) + || prebuilt->select_lock_type != LOCK_NONE) { + ut_print_timestamp(stderr); + fputs(" InnoDB: error clustered record" + " for sec rec not found\n" + "InnoDB: ", stderr); + dict_index_name_print(stderr, trx, sec_index); + fputs("\n" + "InnoDB: sec index record ", stderr); + rec_print(stderr, rec, sec_index); + fputs("\n" + "InnoDB: clust index record ", stderr); + rec_print(stderr, clust_rec, clust_index); + putc('\n', stderr); + trx_print(stderr, trx, 600); + + fputs("\n" + "InnoDB: Submit a detailed bug report" + " to http://bugs.mysql.com\n", stderr); + } + + clust_rec = NULL; + + goto func_exit; + } + + *offsets = rec_get_offsets(clust_rec, clust_index, *offsets, + ULINT_UNDEFINED, offset_heap); + + if (prebuilt->select_lock_type != LOCK_NONE) { + /* Try to place a lock on the index record; we are searching + the clust rec with a unique condition, hence + we set a LOCK_REC_NOT_GAP type lock */ + + err = lock_clust_rec_read_check_and_lock( + 0, btr_pcur_get_block(prebuilt->clust_pcur), + clust_rec, clust_index, *offsets, + prebuilt->select_lock_type, LOCK_REC_NOT_GAP, thr); + if (err != DB_SUCCESS) { + + goto err_exit; + } + } else { + /* This is a non-locking consistent read: if necessary, fetch + a previous version of the record */ + + old_vers = NULL; + + /* If the isolation level allows reading of uncommitted data, + then we never look for an earlier version */ + + if (trx->isolation_level > TRX_ISO_READ_UNCOMMITTED + && !lock_clust_rec_cons_read_sees( + clust_rec, clust_index, *offsets, + trx->read_view)) { + + /* The following call returns 'offsets' associated with + 'old_vers' */ + err = row_sel_build_prev_vers_for_mysql( + trx->read_view, clust_index, prebuilt, + clust_rec, offsets, offset_heap, &old_vers, + mtr); + + if (err != DB_SUCCESS || old_vers == NULL) { + + goto err_exit; + } + + clust_rec = old_vers; + } + + /* If we had to go to an earlier version of row or the + secondary index record is delete marked, then it may be that + the secondary index record corresponding to clust_rec + (or old_vers) is not rec; in that case we must ignore + such row because in our snapshot rec would not have existed. + Remember that from rec we cannot see directly which transaction + id corresponds to it: we have to go to the clustered index + record. A query where we want to fetch all rows where + the secondary index value is in some interval would return + a wrong result if we would not drop rows which we come to + visit through secondary index records that would not really + exist in our snapshot. */ + + if (clust_rec + && (old_vers + || rec_get_deleted_flag(rec, dict_table_is_comp( + sec_index->table))) + && !row_sel_sec_rec_is_for_clust_rec( + rec, sec_index, clust_rec, clust_index)) { + clust_rec = NULL; +#ifdef UNIV_SEARCH_DEBUG + } else { + ut_a(clust_rec == NULL + || row_sel_sec_rec_is_for_clust_rec( + rec, sec_index, clust_rec, clust_index)); +#endif + } + } + +func_exit: + *out_rec = clust_rec; + + if (prebuilt->select_lock_type == LOCK_X) { + /* We may use the cursor in update: store its position */ + + btr_pcur_store_position(prebuilt->clust_pcur, mtr); + } + + err = DB_SUCCESS; +err_exit: + return(err); +} + +/************************************************************************ +Restores cursor position after it has been stored. We have to take into +account that the record cursor was positioned on may have been deleted. +Then we may have to move the cursor one step up or down. */ +static +ibool +sel_restore_position_for_mysql( +/*===========================*/ + /* out: TRUE if we may need to + process the record the cursor is + now positioned on (i.e. we should + not go to the next record yet) */ + ibool* same_user_rec, /* out: TRUE if we were able to restore + the cursor on a user record with the + same ordering prefix in in the + B-tree index */ + ulint latch_mode, /* in: latch mode wished in + restoration */ + btr_pcur_t* pcur, /* in: cursor whose position + has been stored */ + ibool moves_up, /* in: TRUE if the cursor moves up + in the index */ + mtr_t* mtr) /* in: mtr; CAUTION: may commit + mtr temporarily! */ +{ + ibool success; + ulint relative_position; + + relative_position = pcur->rel_pos; + + success = btr_pcur_restore_position(latch_mode, pcur, mtr); + + *same_user_rec = success; + + if (relative_position == BTR_PCUR_ON) { + if (success) { + return(FALSE); + } + + if (moves_up) { + btr_pcur_move_to_next(pcur, mtr); + } + + return(TRUE); + } + + if (relative_position == BTR_PCUR_AFTER + || relative_position == BTR_PCUR_AFTER_LAST_IN_TREE) { + + if (moves_up) { + return(TRUE); + } + + if (btr_pcur_is_on_user_rec(pcur)) { + btr_pcur_move_to_prev(pcur, mtr); + } + + return(TRUE); + } + + ut_ad(relative_position == BTR_PCUR_BEFORE + || relative_position == BTR_PCUR_BEFORE_FIRST_IN_TREE); + + if (moves_up && btr_pcur_is_on_user_rec(pcur)) { + btr_pcur_move_to_next(pcur, mtr); + } + + return(TRUE); +} + +/************************************************************************ +Pops a cached row for MySQL from the fetch cache. */ +UNIV_INLINE +void +row_sel_pop_cached_row_for_mysql( +/*=============================*/ + byte* buf, /* in/out: buffer where to copy the + row */ + row_prebuilt_t* prebuilt) /* in: prebuilt struct */ +{ + ulint i; + mysql_row_templ_t* templ; + byte* cached_rec; + ut_ad(prebuilt->n_fetch_cached > 0); + ut_ad(prebuilt->mysql_prefix_len <= prebuilt->mysql_row_len); + + if (UNIV_UNLIKELY(prebuilt->keep_other_fields_on_keyread)) { + /* Copy cache record field by field, don't touch fields that + are not covered by current key */ + cached_rec = prebuilt->fetch_cache[ + prebuilt->fetch_cache_first]; + + for (i = 0; i < prebuilt->n_template; i++) { + templ = prebuilt->mysql_template + i; + ut_memcpy(buf + templ->mysql_col_offset, + cached_rec + templ->mysql_col_offset, + templ->mysql_col_len); + /* Copy NULL bit of the current field from cached_rec + to buf */ + if (templ->mysql_null_bit_mask) { + buf[templ->mysql_null_byte_offset] + ^= (buf[templ->mysql_null_byte_offset] + ^ cached_rec[templ->mysql_null_byte_offset]) + & (byte)templ->mysql_null_bit_mask; + } + } + } + else { + ut_memcpy(buf, + prebuilt->fetch_cache[prebuilt->fetch_cache_first], + prebuilt->mysql_prefix_len); + } + prebuilt->n_fetch_cached--; + prebuilt->fetch_cache_first++; + + if (prebuilt->n_fetch_cached == 0) { + prebuilt->fetch_cache_first = 0; + } +} + +/************************************************************************ +Pushes a row for MySQL to the fetch cache. */ +UNIV_INLINE +void +row_sel_push_cache_row_for_mysql( +/*=============================*/ + row_prebuilt_t* prebuilt, /* in: prebuilt struct */ + const rec_t* rec, /* in: record to push; must + be protected by a page latch */ + const ulint* offsets) /* in: rec_get_offsets() */ +{ + byte* buf; + ulint i; + + ut_ad(prebuilt->n_fetch_cached < MYSQL_FETCH_CACHE_SIZE); + ut_ad(rec_offs_validate(rec, NULL, offsets)); + ut_a(!prebuilt->templ_contains_blob); + + if (prebuilt->fetch_cache[0] == NULL) { + /* Allocate memory for the fetch cache */ + + for (i = 0; i < MYSQL_FETCH_CACHE_SIZE; i++) { + + /* A user has reported memory corruption in these + buffers in Linux. Put magic numbers there to help + to track a possible bug. */ + + buf = mem_alloc(prebuilt->mysql_row_len + 8); + + prebuilt->fetch_cache[i] = buf + 4; + + mach_write_to_4(buf, ROW_PREBUILT_FETCH_MAGIC_N); + mach_write_to_4(buf + 4 + prebuilt->mysql_row_len, + ROW_PREBUILT_FETCH_MAGIC_N); + } + } + + ut_ad(prebuilt->fetch_cache_first == 0); + + if (UNIV_UNLIKELY(!row_sel_store_mysql_rec( + prebuilt->fetch_cache[ + prebuilt->n_fetch_cached], + prebuilt, rec, offsets))) { + ut_error; + } + + prebuilt->n_fetch_cached++; +} + +/************************************************************************* +Tries to do a shortcut to fetch a clustered index record with a unique key, +using the hash index if possible (not always). We assume that the search +mode is PAGE_CUR_GE, it is a consistent read, there is a read view in trx, +btr search latch has been locked in S-mode. */ +static +ulint +row_sel_try_search_shortcut_for_mysql( +/*==================================*/ + /* out: SEL_FOUND, SEL_EXHAUSTED, SEL_RETRY */ + const rec_t** out_rec,/* out: record if found */ + row_prebuilt_t* prebuilt,/* in: prebuilt struct */ + ulint** offsets,/* in/out: for rec_get_offsets(*out_rec) */ + mem_heap_t** heap, /* in/out: heap for rec_get_offsets() */ + mtr_t* mtr) /* in: started mtr */ +{ + dict_index_t* index = prebuilt->index; + const dtuple_t* search_tuple = prebuilt->search_tuple; + btr_pcur_t* pcur = prebuilt->pcur; + trx_t* trx = prebuilt->trx; + const rec_t* rec; + + ut_ad(dict_index_is_clust(index)); + ut_ad(!prebuilt->templ_contains_blob); + + btr_pcur_open_with_no_init(index, search_tuple, PAGE_CUR_GE, + BTR_SEARCH_LEAF, pcur, +#ifndef UNIV_SEARCH_DEBUG + RW_S_LATCH, +#else + 0, +#endif + mtr); + rec = btr_pcur_get_rec(pcur); + + if (!page_rec_is_user_rec(rec)) { + + return(SEL_RETRY); + } + + /* As the cursor is now placed on a user record after a search with + the mode PAGE_CUR_GE, the up_match field in the cursor tells how many + fields in the user record matched to the search tuple */ + + if (btr_pcur_get_up_match(pcur) < dtuple_get_n_fields(search_tuple)) { + + return(SEL_EXHAUSTED); + } + + /* This is a non-locking consistent read: if necessary, fetch + a previous version of the record */ + + *offsets = rec_get_offsets(rec, index, *offsets, + ULINT_UNDEFINED, heap); + + if (!lock_clust_rec_cons_read_sees(rec, index, + *offsets, trx->read_view)) { + + return(SEL_RETRY); + } + + if (rec_get_deleted_flag(rec, dict_table_is_comp(index->table))) { + + return(SEL_EXHAUSTED); + } + + *out_rec = rec; + + return(SEL_FOUND); +} + +/************************************************************************ +Searches for rows in the database. This is used in the interface to +MySQL. This function opens a cursor, and also implements fetch next +and fetch prev. NOTE that if we do a search with a full key value +from a unique index (ROW_SEL_EXACT), then we will not store the cursor +position and fetch next or fetch prev must not be tried to the cursor! */ +UNIV_INTERN +ulint +row_search_for_mysql( +/*=================*/ + /* out: DB_SUCCESS, + DB_RECORD_NOT_FOUND, + DB_END_OF_INDEX, DB_DEADLOCK, + DB_LOCK_TABLE_FULL, DB_CORRUPTION, + or DB_TOO_BIG_RECORD */ + byte* buf, /* in/out: buffer for the fetched + row in the MySQL format */ + ulint mode, /* in: search mode PAGE_CUR_L, ... */ + row_prebuilt_t* prebuilt, /* in: prebuilt struct for the + table handle; this contains the info + of search_tuple, index; if search + tuple contains 0 fields then we + position the cursor at the start or + the end of the index, depending on + 'mode' */ + ulint match_mode, /* in: 0 or ROW_SEL_EXACT or + ROW_SEL_EXACT_PREFIX */ + ulint direction) /* in: 0 or ROW_SEL_NEXT or + ROW_SEL_PREV; NOTE: if this is != 0, + then prebuilt must have a pcur + with stored position! In opening of a + cursor 'direction' should be 0. */ +{ + dict_index_t* index = prebuilt->index; + ibool comp = dict_table_is_comp(index->table); + const dtuple_t* search_tuple = prebuilt->search_tuple; + btr_pcur_t* pcur = prebuilt->pcur; + trx_t* trx = prebuilt->trx; + dict_index_t* clust_index; + que_thr_t* thr; + const rec_t* rec; + const rec_t* result_rec; + const rec_t* clust_rec; + ulint err = DB_SUCCESS; + ibool unique_search = FALSE; + ibool unique_search_from_clust_index = FALSE; + ibool mtr_has_extra_clust_latch = FALSE; + ibool moves_up = FALSE; + ibool set_also_gap_locks = TRUE; + /* if the query is a plain locking SELECT, and the isolation level + is <= TRX_ISO_READ_COMMITTED, then this is set to FALSE */ + ibool did_semi_consistent_read = FALSE; + /* if the returned record was locked and we did a semi-consistent + read (fetch the newest committed version), then this is set to + TRUE */ +#ifdef UNIV_SEARCH_DEBUG + ulint cnt = 0; +#endif /* UNIV_SEARCH_DEBUG */ + ulint next_offs; + ibool same_user_rec; + mtr_t mtr; + mem_heap_t* heap = NULL; + ulint offsets_[REC_OFFS_NORMAL_SIZE]; + ulint* offsets = offsets_; + + rec_offs_init(offsets_); + + ut_ad(index && pcur && search_tuple); + ut_ad(trx->mysql_thread_id == os_thread_get_curr_id()); + + if (UNIV_UNLIKELY(prebuilt->table->ibd_file_missing)) { + ut_print_timestamp(stderr); + fprintf(stderr, " InnoDB: Error:\n" + "InnoDB: MySQL is trying to use a table handle" + " but the .ibd file for\n" + "InnoDB: table %s does not exist.\n" + "InnoDB: Have you deleted the .ibd file" + " from the database directory under\n" + "InnoDB: the MySQL datadir, or have you used" + " DISCARD TABLESPACE?\n" + "InnoDB: Look from\n" + "InnoDB: http://dev.mysql.com/doc/refman/5.1/en/" + "innodb-troubleshooting.html\n" + "InnoDB: how you can resolve the problem.\n", + prebuilt->table->name); + + return(DB_ERROR); + } + + if (UNIV_UNLIKELY(prebuilt->magic_n != ROW_PREBUILT_ALLOCATED)) { + fprintf(stderr, + "InnoDB: Error: trying to free a corrupt\n" + "InnoDB: table handle. Magic n %lu, table name ", + (ulong) prebuilt->magic_n); + ut_print_name(stderr, trx, TRUE, prebuilt->table->name); + putc('\n', stderr); + + mem_analyze_corruption(prebuilt); + + ut_error; + } + +#if 0 + /* August 19, 2005 by Heikki: temporarily disable this error + print until the cursor lock count is done correctly. + See bugs #12263 and #12456!*/ + + if (trx->n_mysql_tables_in_use == 0 + && UNIV_UNLIKELY(prebuilt->select_lock_type == LOCK_NONE)) { + /* Note that if MySQL uses an InnoDB temp table that it + created inside LOCK TABLES, then n_mysql_tables_in_use can + be zero; in that case select_lock_type is set to LOCK_X in + ::start_stmt. */ + + fputs("InnoDB: Error: MySQL is trying to perform a SELECT\n" + "InnoDB: but it has not locked" + " any tables in ::external_lock()!\n", + stderr); + trx_print(stderr, trx, 600); + fputc('\n', stderr); + } +#endif + +#if 0 + fprintf(stderr, "Match mode %lu\n search tuple ", + (ulong) match_mode); + dtuple_print(search_tuple); + fprintf(stderr, "N tables locked %lu\n", + (ulong) trx->mysql_n_tables_locked); +#endif + /*-------------------------------------------------------------*/ + /* PHASE 0: Release a possible s-latch we are holding on the + adaptive hash index latch if there is someone waiting behind */ + + if (UNIV_UNLIKELY(rw_lock_get_writer(&btr_search_latch) != RW_LOCK_NOT_LOCKED) + && trx->has_search_latch) { + + /* There is an x-latch request on the adaptive hash index: + release the s-latch to reduce starvation and wait for + BTR_SEA_TIMEOUT rounds before trying to keep it again over + calls from MySQL */ + + rw_lock_s_unlock(&btr_search_latch); + trx->has_search_latch = FALSE; + + trx->search_latch_timeout = BTR_SEA_TIMEOUT; + } + + /* Reset the new record lock info if srv_locks_unsafe_for_binlog + is set or session is using a READ COMMITED isolation level. Then + we are able to remove the record locks set here on an individual + row. */ + + if ((srv_locks_unsafe_for_binlog + || trx->isolation_level == TRX_ISO_READ_COMMITTED) + && prebuilt->select_lock_type != LOCK_NONE) { + + trx_reset_new_rec_lock_info(trx); + } + + /*-------------------------------------------------------------*/ + /* PHASE 1: Try to pop the row from the prefetch cache */ + + if (UNIV_UNLIKELY(direction == 0)) { + trx->op_info = "starting index read"; + + prebuilt->n_rows_fetched = 0; + prebuilt->n_fetch_cached = 0; + prebuilt->fetch_cache_first = 0; + + if (prebuilt->sel_graph == NULL) { + /* Build a dummy select query graph */ + row_prebuild_sel_graph(prebuilt); + } + } else { + trx->op_info = "fetching rows"; + + if (prebuilt->n_rows_fetched == 0) { + prebuilt->fetch_direction = direction; + } + + if (UNIV_UNLIKELY(direction != prebuilt->fetch_direction)) { + if (UNIV_UNLIKELY(prebuilt->n_fetch_cached > 0)) { + ut_error; + /* TODO: scrollable cursor: restore cursor to + the place of the latest returned row, + or better: prevent caching for a scroll + cursor! */ + } + + prebuilt->n_rows_fetched = 0; + prebuilt->n_fetch_cached = 0; + prebuilt->fetch_cache_first = 0; + + } else if (UNIV_LIKELY(prebuilt->n_fetch_cached > 0)) { + row_sel_pop_cached_row_for_mysql(buf, prebuilt); + + prebuilt->n_rows_fetched++; + + srv_n_rows_read++; + err = DB_SUCCESS; + goto func_exit; + } + + if (prebuilt->fetch_cache_first > 0 + && prebuilt->fetch_cache_first < MYSQL_FETCH_CACHE_SIZE) { + + /* The previous returned row was popped from the fetch + cache, but the cache was not full at the time of the + popping: no more rows can exist in the result set */ + + err = DB_RECORD_NOT_FOUND; + goto func_exit; + } + + prebuilt->n_rows_fetched++; + + if (prebuilt->n_rows_fetched > 1000000000) { + /* Prevent wrap-over */ + prebuilt->n_rows_fetched = 500000000; + } + + mode = pcur->search_mode; + } + + /* In a search where at most one record in the index may match, we + can use a LOCK_REC_NOT_GAP type record lock when locking a + non-delete-marked matching record. + + Note that in a unique secondary index there may be different + delete-marked versions of a record where only the primary key + values differ: thus in a secondary index we must use next-key + locks when locking delete-marked records. */ + + if (match_mode == ROW_SEL_EXACT + && dict_index_is_unique(index) + && dtuple_get_n_fields(search_tuple) + == dict_index_get_n_unique(index) + && (dict_index_is_clust(index) + || !dtuple_contains_null(search_tuple))) { + + /* Note above that a UNIQUE secondary index can contain many + rows with the same key value if one of the columns is the SQL + null. A clustered index under MySQL can never contain null + columns because we demand that all the columns in primary key + are non-null. */ + + unique_search = TRUE; + + /* Even if the condition is unique, MySQL seems to try to + retrieve also a second row if a primary key contains more than + 1 column. Return immediately if this is not a HANDLER + command. */ + + if (UNIV_UNLIKELY(direction != 0 + && !prebuilt->used_in_HANDLER)) { + + err = DB_RECORD_NOT_FOUND; + goto func_exit; + } + } + + mtr_start(&mtr); + + /*-------------------------------------------------------------*/ + /* PHASE 2: Try fast adaptive hash index search if possible */ + + /* Next test if this is the special case where we can use the fast + adaptive hash index to try the search. Since we must release the + search system latch when we retrieve an externally stored field, we + cannot use the adaptive hash index in a search in the case the row + may be long and there may be externally stored fields */ + + if (UNIV_UNLIKELY(direction == 0) + && unique_search + && dict_index_is_clust(index) + && !prebuilt->templ_contains_blob + && !prebuilt->used_in_HANDLER + && (prebuilt->mysql_row_len < UNIV_PAGE_SIZE / 8)) { + + mode = PAGE_CUR_GE; + + unique_search_from_clust_index = TRUE; + + if (trx->mysql_n_tables_locked == 0 + && prebuilt->select_lock_type == LOCK_NONE + && trx->isolation_level > TRX_ISO_READ_UNCOMMITTED + && trx->read_view) { + + /* This is a SELECT query done as a consistent read, + and the read view has already been allocated: + let us try a search shortcut through the hash + index. + NOTE that we must also test that + mysql_n_tables_locked == 0, because this might + also be INSERT INTO ... SELECT ... or + CREATE TABLE ... SELECT ... . Our algorithm is + NOT prepared to inserts interleaved with the SELECT, + and if we try that, we can deadlock on the adaptive + hash index semaphore! */ + +#ifndef UNIV_SEARCH_DEBUG + if (!trx->has_search_latch) { + rw_lock_s_lock(&btr_search_latch); + trx->has_search_latch = TRUE; + } +#endif + switch (row_sel_try_search_shortcut_for_mysql( + &rec, prebuilt, &offsets, &heap, + &mtr)) { + case SEL_FOUND: +#ifdef UNIV_SEARCH_DEBUG + ut_a(0 == cmp_dtuple_rec(search_tuple, + rec, offsets)); +#endif + /* At this point, rec is protected by + a page latch that was acquired by + row_sel_try_search_shortcut_for_mysql(). + The latch will not be released until + mtr_commit(&mtr). */ + + if (!row_sel_store_mysql_rec(buf, prebuilt, + rec, offsets)) { + err = DB_TOO_BIG_RECORD; + + /* We let the main loop to do the + error handling */ + goto shortcut_fails_too_big_rec; + } + + mtr_commit(&mtr); + + /* ut_print_name(stderr, index->name); + fputs(" shortcut\n", stderr); */ + + srv_n_rows_read++; + + err = DB_SUCCESS; + goto release_search_latch_if_needed; + + case SEL_EXHAUSTED: + mtr_commit(&mtr); + + /* ut_print_name(stderr, index->name); + fputs(" record not found 2\n", stderr); */ + + err = DB_RECORD_NOT_FOUND; +release_search_latch_if_needed: + if (trx->search_latch_timeout > 0 + && trx->has_search_latch) { + + trx->search_latch_timeout--; + + rw_lock_s_unlock(&btr_search_latch); + trx->has_search_latch = FALSE; + } + + /* NOTE that we do NOT store the cursor + position */ + goto func_exit; + + case SEL_RETRY: + break; + + default: + ut_ad(0); + } +shortcut_fails_too_big_rec: + mtr_commit(&mtr); + mtr_start(&mtr); + } + } + + /*-------------------------------------------------------------*/ + /* PHASE 3: Open or restore index cursor position */ + + if (trx->has_search_latch) { + rw_lock_s_unlock(&btr_search_latch); + trx->has_search_latch = FALSE; + } + + trx_start_if_not_started(trx); + + if (trx->isolation_level <= TRX_ISO_READ_COMMITTED + && prebuilt->select_lock_type != LOCK_NONE + && trx->mysql_thd != NULL + && thd_is_select(trx->mysql_thd)) { + /* It is a plain locking SELECT and the isolation + level is low: do not lock gaps */ + + set_also_gap_locks = FALSE; + } + + /* Note that if the search mode was GE or G, then the cursor + naturally moves upward (in fetch next) in alphabetical order, + otherwise downward */ + + if (UNIV_UNLIKELY(direction == 0)) { + if (mode == PAGE_CUR_GE || mode == PAGE_CUR_G) { + moves_up = TRUE; + } + } else if (direction == ROW_SEL_NEXT) { + moves_up = TRUE; + } + + thr = que_fork_get_first_thr(prebuilt->sel_graph); + + que_thr_move_to_run_state_for_mysql(thr, trx); + + clust_index = dict_table_get_first_index(index->table); + + if (UNIV_LIKELY(direction != 0)) { + ibool need_to_process = sel_restore_position_for_mysql( + &same_user_rec, BTR_SEARCH_LEAF, + pcur, moves_up, &mtr); + + if (UNIV_UNLIKELY(need_to_process)) { + if (UNIV_UNLIKELY(prebuilt->row_read_type + == ROW_READ_DID_SEMI_CONSISTENT)) { + /* We did a semi-consistent read, + but the record was removed in + the meantime. */ + prebuilt->row_read_type + = ROW_READ_TRY_SEMI_CONSISTENT; + } + } else if (UNIV_LIKELY(prebuilt->row_read_type + != ROW_READ_DID_SEMI_CONSISTENT)) { + + /* The cursor was positioned on the record + that we returned previously. If we need + to repeat a semi-consistent read as a + pessimistic locking read, the record + cannot be skipped. */ + + goto next_rec; + } + + } else if (dtuple_get_n_fields(search_tuple) > 0) { + + btr_pcur_open_with_no_init(index, search_tuple, mode, + BTR_SEARCH_LEAF, + pcur, 0, &mtr); + + pcur->trx_if_known = trx; + + rec = btr_pcur_get_rec(pcur); + + if (!moves_up + && !page_rec_is_supremum(rec) + && set_also_gap_locks + && !(srv_locks_unsafe_for_binlog + || trx->isolation_level == TRX_ISO_READ_COMMITTED) + && prebuilt->select_lock_type != LOCK_NONE) { + + /* Try to place a gap lock on the next index record + to prevent phantoms in ORDER BY ... DESC queries */ + const rec_t* next = page_rec_get_next_const(rec); + + offsets = rec_get_offsets(next, index, offsets, + ULINT_UNDEFINED, &heap); + err = sel_set_rec_lock(btr_pcur_get_block(pcur), + next, index, offsets, + prebuilt->select_lock_type, + LOCK_GAP, thr); + + if (err != DB_SUCCESS) { + + goto lock_wait_or_error; + } + } + } else { + if (mode == PAGE_CUR_G) { + btr_pcur_open_at_index_side( + TRUE, index, BTR_SEARCH_LEAF, pcur, FALSE, + &mtr); + } else if (mode == PAGE_CUR_L) { + btr_pcur_open_at_index_side( + FALSE, index, BTR_SEARCH_LEAF, pcur, FALSE, + &mtr); + } + } + + if (!prebuilt->sql_stat_start) { + /* No need to set an intention lock or assign a read view */ + + if (trx->read_view == NULL + && prebuilt->select_lock_type == LOCK_NONE) { + + fputs("InnoDB: Error: MySQL is trying to" + " perform a consistent read\n" + "InnoDB: but the read view is not assigned!\n", + stderr); + trx_print(stderr, trx, 600); + fputc('\n', stderr); + ut_a(0); + } + } else if (prebuilt->select_lock_type == LOCK_NONE) { + /* This is a consistent read */ + /* Assign a read view for the query */ + + trx_assign_read_view(trx); + prebuilt->sql_stat_start = FALSE; + } else { + ulint lock_mode; + if (prebuilt->select_lock_type == LOCK_S) { + lock_mode = LOCK_IS; + } else { + lock_mode = LOCK_IX; + } + err = lock_table(0, index->table, lock_mode, thr); + + if (err != DB_SUCCESS) { + + goto lock_wait_or_error; + } + prebuilt->sql_stat_start = FALSE; + } + +rec_loop: + /*-------------------------------------------------------------*/ + /* PHASE 4: Look for matching records in a loop */ + + rec = btr_pcur_get_rec(pcur); + ut_ad(!!page_rec_is_comp(rec) == comp); +#ifdef UNIV_SEARCH_DEBUG + /* + fputs("Using ", stderr); + dict_index_name_print(stderr, index); + fprintf(stderr, " cnt %lu ; Page no %lu\n", cnt, + page_get_page_no(page_align(rec))); + rec_print(rec); + */ +#endif /* UNIV_SEARCH_DEBUG */ + + if (page_rec_is_infimum(rec)) { + + /* The infimum record on a page cannot be in the result set, + and neither can a record lock be placed on it: we skip such + a record. */ + + goto next_rec; + } + + if (page_rec_is_supremum(rec)) { + + if (set_also_gap_locks + && !(srv_locks_unsafe_for_binlog + || trx->isolation_level == TRX_ISO_READ_COMMITTED) + && prebuilt->select_lock_type != LOCK_NONE) { + + /* Try to place a lock on the index record */ + + /* If innodb_locks_unsafe_for_binlog option is used + or this session is using a READ COMMITTED isolation + level we do not lock gaps. Supremum record is really + a gap and therefore we do not set locks there. */ + + offsets = rec_get_offsets(rec, index, offsets, + ULINT_UNDEFINED, &heap); + err = sel_set_rec_lock(btr_pcur_get_block(pcur), + rec, index, offsets, + prebuilt->select_lock_type, + LOCK_ORDINARY, thr); + + if (err != DB_SUCCESS) { + + goto lock_wait_or_error; + } + } + /* A page supremum record cannot be in the result set: skip + it now that we have placed a possible lock on it */ + + goto next_rec; + } + + /*-------------------------------------------------------------*/ + /* Do sanity checks in case our cursor has bumped into page + corruption */ + + if (comp) { + next_offs = rec_get_next_offs(rec, TRUE); + if (UNIV_UNLIKELY(next_offs < PAGE_NEW_SUPREMUM)) { + + goto wrong_offs; + } + } else { + next_offs = rec_get_next_offs(rec, FALSE); + if (UNIV_UNLIKELY(next_offs < PAGE_OLD_SUPREMUM)) { + + goto wrong_offs; + } + } + + if (UNIV_UNLIKELY(next_offs >= UNIV_PAGE_SIZE - PAGE_DIR)) { + +wrong_offs: + if (srv_force_recovery == 0 || moves_up == FALSE) { + ut_print_timestamp(stderr); + buf_page_print(page_align(rec), 0); + fprintf(stderr, + "\nInnoDB: rec address %p," + " buf block fix count %lu\n", + (void*) rec, (ulong) + btr_cur_get_block(btr_pcur_get_btr_cur(pcur)) + ->page.buf_fix_count); + fprintf(stderr, + "InnoDB: Index corruption: rec offs %lu" + " next offs %lu, page no %lu,\n" + "InnoDB: ", + (ulong) page_offset(rec), + (ulong) next_offs, + (ulong) page_get_page_no(page_align(rec))); + dict_index_name_print(stderr, trx, index); + fputs(". Run CHECK TABLE. You may need to\n" + "InnoDB: restore from a backup, or" + " dump + drop + reimport the table.\n", + stderr); + + err = DB_CORRUPTION; + + goto lock_wait_or_error; + } else { + /* The user may be dumping a corrupt table. Jump + over the corruption to recover as much as possible. */ + + fprintf(stderr, + "InnoDB: Index corruption: rec offs %lu" + " next offs %lu, page no %lu,\n" + "InnoDB: ", + (ulong) page_offset(rec), + (ulong) next_offs, + (ulong) page_get_page_no(page_align(rec))); + dict_index_name_print(stderr, trx, index); + fputs(". We try to skip the rest of the page.\n", + stderr); + + btr_pcur_move_to_last_on_page(pcur, &mtr); + + goto next_rec; + } + } + /*-------------------------------------------------------------*/ + + /* Calculate the 'offsets' associated with 'rec' */ + + offsets = rec_get_offsets(rec, index, offsets, ULINT_UNDEFINED, &heap); + + if (UNIV_UNLIKELY(srv_force_recovery > 0)) { + if (!rec_validate(rec, offsets) + || !btr_index_rec_validate(rec, index, FALSE)) { + fprintf(stderr, + "InnoDB: Index corruption: rec offs %lu" + " next offs %lu, page no %lu,\n" + "InnoDB: ", + (ulong) page_offset(rec), + (ulong) next_offs, + (ulong) page_get_page_no(page_align(rec))); + dict_index_name_print(stderr, trx, index); + fputs(". We try to skip the record.\n", + stderr); + + goto next_rec; + } + } + + /* Note that we cannot trust the up_match value in the cursor at this + place because we can arrive here after moving the cursor! Thus + we have to recompare rec and search_tuple to determine if they + match enough. */ + + if (match_mode == ROW_SEL_EXACT) { + /* Test if the index record matches completely to search_tuple + in prebuilt: if not, then we return with DB_RECORD_NOT_FOUND */ + + /* fputs("Comparing rec and search tuple\n", stderr); */ + + if (0 != cmp_dtuple_rec(search_tuple, rec, offsets)) { + + if (set_also_gap_locks + && !(srv_locks_unsafe_for_binlog + || trx->isolation_level + == TRX_ISO_READ_COMMITTED) + && prebuilt->select_lock_type != LOCK_NONE) { + + /* Try to place a gap lock on the index + record only if innodb_locks_unsafe_for_binlog + option is not set or this session is not + using a READ COMMITTED isolation level. */ + + err = sel_set_rec_lock( + btr_pcur_get_block(pcur), + rec, index, offsets, + prebuilt->select_lock_type, LOCK_GAP, + thr); + + if (err != DB_SUCCESS) { + + goto lock_wait_or_error; + } + } + + btr_pcur_store_position(pcur, &mtr); + + err = DB_RECORD_NOT_FOUND; + /* ut_print_name(stderr, index->name); + fputs(" record not found 3\n", stderr); */ + + goto normal_return; + } + + } else if (match_mode == ROW_SEL_EXACT_PREFIX) { + + if (!cmp_dtuple_is_prefix_of_rec(search_tuple, rec, offsets)) { + + if (set_also_gap_locks + && !(srv_locks_unsafe_for_binlog + || trx->isolation_level + == TRX_ISO_READ_COMMITTED) + && prebuilt->select_lock_type != LOCK_NONE) { + + /* Try to place a gap lock on the index + record only if innodb_locks_unsafe_for_binlog + option is not set or this session is not + using a READ COMMITTED isolation level. */ + + err = sel_set_rec_lock( + btr_pcur_get_block(pcur), + rec, index, offsets, + prebuilt->select_lock_type, LOCK_GAP, + thr); + + if (err != DB_SUCCESS) { + + goto lock_wait_or_error; + } + } + + btr_pcur_store_position(pcur, &mtr); + + err = DB_RECORD_NOT_FOUND; + /* ut_print_name(stderr, index->name); + fputs(" record not found 4\n", stderr); */ + + goto normal_return; + } + } + + /* We are ready to look at a possible new index entry in the result + set: the cursor is now placed on a user record */ + + if (prebuilt->select_lock_type != LOCK_NONE) { + /* Try to place a lock on the index record; note that delete + marked records are a special case in a unique search. If there + is a non-delete marked record, then it is enough to lock its + existence with LOCK_REC_NOT_GAP. */ + + /* If innodb_locks_unsafe_for_binlog option is used + or this session is using a READ COMMITED isolation + level we lock only the record, i.e., next-key locking is + not used. */ + + ulint lock_type; + + if (!set_also_gap_locks + || srv_locks_unsafe_for_binlog + || trx->isolation_level == TRX_ISO_READ_COMMITTED + || (unique_search + && !UNIV_UNLIKELY(rec_get_deleted_flag(rec, comp)))) { + + goto no_gap_lock; + } else { + lock_type = LOCK_ORDINARY; + } + + /* If we are doing a 'greater or equal than a primary key + value' search from a clustered index, and we find a record + that has that exact primary key value, then there is no need + to lock the gap before the record, because no insert in the + gap can be in our search range. That is, no phantom row can + appear that way. + + An example: if col1 is the primary key, the search is WHERE + col1 >= 100, and we find a record where col1 = 100, then no + need to lock the gap before that record. */ + + if (index == clust_index + && mode == PAGE_CUR_GE + && direction == 0 + && dtuple_get_n_fields_cmp(search_tuple) + == dict_index_get_n_unique(index) + && 0 == cmp_dtuple_rec(search_tuple, rec, offsets)) { +no_gap_lock: + lock_type = LOCK_REC_NOT_GAP; + } + + err = sel_set_rec_lock(btr_pcur_get_block(pcur), + rec, index, offsets, + prebuilt->select_lock_type, + lock_type, thr); + + switch (err) { + const rec_t* old_vers; + case DB_SUCCESS: + break; + case DB_LOCK_WAIT: + if (UNIV_LIKELY(prebuilt->row_read_type + != ROW_READ_TRY_SEMI_CONSISTENT) + || index != clust_index) { + + goto lock_wait_or_error; + } + + /* The following call returns 'offsets' + associated with 'old_vers' */ + err = row_sel_build_committed_vers_for_mysql( + clust_index, prebuilt, rec, + &offsets, &heap, &old_vers, &mtr); + + if (err != DB_SUCCESS) { + + goto lock_wait_or_error; + } + + mutex_enter(&kernel_mutex); + if (trx->was_chosen_as_deadlock_victim) { + mutex_exit(&kernel_mutex); + err = DB_DEADLOCK; + + goto lock_wait_or_error; + } + if (UNIV_LIKELY(trx->wait_lock != NULL)) { + lock_cancel_waiting_and_release( + trx->wait_lock); + trx_reset_new_rec_lock_info(trx); + } else { + mutex_exit(&kernel_mutex); + + /* The lock was granted while we were + searching for the last committed version. + Do a normal locking read. */ + + offsets = rec_get_offsets(rec, index, offsets, + ULINT_UNDEFINED, + &heap); + err = DB_SUCCESS; + break; + } + mutex_exit(&kernel_mutex); + + if (old_vers == NULL) { + /* The row was not yet committed */ + + goto next_rec; + } + + did_semi_consistent_read = TRUE; + rec = old_vers; + break; + default: + + goto lock_wait_or_error; + } + } else { + /* This is a non-locking consistent read: if necessary, fetch + a previous version of the record */ + + if (trx->isolation_level == TRX_ISO_READ_UNCOMMITTED) { + + /* Do nothing: we let a non-locking SELECT read the + latest version of the record */ + + } else if (index == clust_index) { + + /* Fetch a previous version of the row if the current + one is not visible in the snapshot; if we have a very + high force recovery level set, we try to avoid crashes + by skipping this lookup */ + + if (UNIV_LIKELY(srv_force_recovery < 5) + && !lock_clust_rec_cons_read_sees( + rec, index, offsets, trx->read_view)) { + + rec_t* old_vers; + /* The following call returns 'offsets' + associated with 'old_vers' */ + err = row_sel_build_prev_vers_for_mysql( + trx->read_view, clust_index, + prebuilt, rec, &offsets, &heap, + &old_vers, &mtr); + + if (err != DB_SUCCESS) { + + goto lock_wait_or_error; + } + + if (old_vers == NULL) { + /* The row did not exist yet in + the read view */ + + goto next_rec; + } + + rec = old_vers; + } + } else if (!lock_sec_rec_cons_read_sees(rec, trx->read_view)) { + /* We are looking into a non-clustered index, + and to get the right version of the record we + have to look also into the clustered index: this + is necessary, because we can only get the undo + information via the clustered index record. */ + + ut_ad(index != clust_index); + + goto requires_clust_rec; + } + } + + /* NOTE that at this point rec can be an old version of a clustered + index record built for a consistent read. We cannot assume after this + point that rec is on a buffer pool page. Functions like + page_rec_is_comp() cannot be used! */ + + if (UNIV_UNLIKELY(rec_get_deleted_flag(rec, comp))) { + + /* The record is delete-marked: we can skip it */ + + if ((srv_locks_unsafe_for_binlog + || trx->isolation_level == TRX_ISO_READ_COMMITTED) + && prebuilt->select_lock_type != LOCK_NONE + && !did_semi_consistent_read) { + + /* No need to keep a lock on a delete-marked record + if we do not want to use next-key locking. */ + + row_unlock_for_mysql(prebuilt, TRUE); + } + + /* This is an optimization to skip setting the next key lock + on the record that follows this delete-marked record. This + optimization works because of the unique search criteria + which precludes the presence of a range lock between this + delete marked record and the record following it. + + For now this is applicable only to clustered indexes while + doing a unique search. There is scope for further optimization + applicable to unique secondary indexes. Current behaviour is + to widen the scope of a lock on an already delete marked record + if the same record is deleted twice by the same transaction */ + if (index == clust_index && unique_search) { + err = DB_RECORD_NOT_FOUND; + + goto normal_return; + } + + goto next_rec; + } + + /* Get the clustered index record if needed, if we did not do the + search using the clustered index. */ + + if (index != clust_index && prebuilt->need_to_access_clustered) { + +requires_clust_rec: + /* We use a 'goto' to the preceding label if a consistent + read of a secondary index record requires us to look up old + versions of the associated clustered index record. */ + + ut_ad(rec_offs_validate(rec, index, offsets)); + + /* It was a non-clustered index and we must fetch also the + clustered index record */ + + mtr_has_extra_clust_latch = TRUE; + + /* The following call returns 'offsets' associated with + 'clust_rec'. Note that 'clust_rec' can be an old version + built for a consistent read. */ + + err = row_sel_get_clust_rec_for_mysql(prebuilt, index, rec, + thr, &clust_rec, + &offsets, &heap, &mtr); + if (err != DB_SUCCESS) { + + goto lock_wait_or_error; + } + + if (clust_rec == NULL) { + /* The record did not exist in the read view */ + ut_ad(prebuilt->select_lock_type == LOCK_NONE); + + goto next_rec; + } + + if (UNIV_UNLIKELY(rec_get_deleted_flag(clust_rec, comp))) { + + /* The record is delete marked: we can skip it */ + + if ((srv_locks_unsafe_for_binlog + || trx->isolation_level == TRX_ISO_READ_COMMITTED) + && prebuilt->select_lock_type != LOCK_NONE) { + + /* No need to keep a lock on a delete-marked + record if we do not want to use next-key + locking. */ + + row_unlock_for_mysql(prebuilt, TRUE); + } + + goto next_rec; + } + + if (prebuilt->need_to_access_clustered) { + + result_rec = clust_rec; + + ut_ad(rec_offs_validate(result_rec, clust_index, + offsets)); + } else { + /* We used 'offsets' for the clust rec, recalculate + them for 'rec' */ + offsets = rec_get_offsets(rec, index, offsets, + ULINT_UNDEFINED, &heap); + result_rec = rec; + } + } else { + result_rec = rec; + } + + /* We found a qualifying record 'result_rec'. At this point, + 'offsets' are associated with 'result_rec'. */ + + ut_ad(rec_offs_validate(result_rec, + result_rec != rec ? clust_index : index, + offsets)); + + /* At this point, the clustered index record is protected + by a page latch that was acquired when pcur was positioned. + The latch will not be released until mtr_commit(&mtr). */ + + if ((match_mode == ROW_SEL_EXACT + || prebuilt->n_rows_fetched >= MYSQL_FETCH_CACHE_THRESHOLD) + && prebuilt->select_lock_type == LOCK_NONE + && !prebuilt->templ_contains_blob + && !prebuilt->clust_index_was_generated + && !prebuilt->used_in_HANDLER + && prebuilt->template_type + != ROW_MYSQL_DUMMY_TEMPLATE) { + + /* Inside an update, for example, we do not cache rows, + since we may use the cursor position to do the actual + update, that is why we require ...lock_type == LOCK_NONE. + Since we keep space in prebuilt only for the BLOBs of + a single row, we cannot cache rows in the case there + are BLOBs in the fields to be fetched. In HANDLER we do + not cache rows because there the cursor is a scrollable + cursor. */ + + row_sel_push_cache_row_for_mysql(prebuilt, result_rec, + offsets); + if (prebuilt->n_fetch_cached == MYSQL_FETCH_CACHE_SIZE) { + + goto got_row; + } + + goto next_rec; + } else { + if (prebuilt->template_type == ROW_MYSQL_DUMMY_TEMPLATE) { + memcpy(buf + 4, result_rec + - rec_offs_extra_size(offsets), + rec_offs_size(offsets)); + mach_write_to_4(buf, + rec_offs_extra_size(offsets) + 4); + } else { + if (!row_sel_store_mysql_rec(buf, prebuilt, + result_rec, offsets)) { + err = DB_TOO_BIG_RECORD; + + goto lock_wait_or_error; + } + } + + if (prebuilt->clust_index_was_generated) { + if (result_rec != rec) { + offsets = rec_get_offsets( + rec, index, offsets, ULINT_UNDEFINED, + &heap); + } + row_sel_store_row_id_to_prebuilt(prebuilt, rec, + index, offsets); + } + } + + /* From this point on, 'offsets' are invalid. */ + +got_row: + /* We have an optimization to save CPU time: if this is a consistent + read on a unique condition on the clustered index, then we do not + store the pcur position, because any fetch next or prev will anyway + return 'end of file'. Exceptions are locking reads and the MySQL + HANDLER command where the user can move the cursor with PREV or NEXT + even after a unique search. */ + + if (!unique_search_from_clust_index + || prebuilt->select_lock_type != LOCK_NONE + || prebuilt->used_in_HANDLER) { + + /* Inside an update always store the cursor position */ + + btr_pcur_store_position(pcur, &mtr); + } + + err = DB_SUCCESS; + + goto normal_return; + +next_rec: + /* Reset the old and new "did semi-consistent read" flags. */ + if (UNIV_UNLIKELY(prebuilt->row_read_type + == ROW_READ_DID_SEMI_CONSISTENT)) { + prebuilt->row_read_type = ROW_READ_TRY_SEMI_CONSISTENT; + } + did_semi_consistent_read = FALSE; + + if (UNIV_UNLIKELY(srv_locks_unsafe_for_binlog + || trx->isolation_level == TRX_ISO_READ_COMMITTED) + && prebuilt->select_lock_type != LOCK_NONE) { + + trx_reset_new_rec_lock_info(trx); + } + + /*-------------------------------------------------------------*/ + /* PHASE 5: Move the cursor to the next index record */ + + if (UNIV_UNLIKELY(mtr_has_extra_clust_latch)) { + /* We must commit mtr if we are moving to the next + non-clustered index record, because we could break the + latching order if we would access a different clustered + index page right away without releasing the previous. */ + + btr_pcur_store_position(pcur, &mtr); + + mtr_commit(&mtr); + mtr_has_extra_clust_latch = FALSE; + + mtr_start(&mtr); + if (sel_restore_position_for_mysql(&same_user_rec, + BTR_SEARCH_LEAF, + pcur, moves_up, &mtr)) { +#ifdef UNIV_SEARCH_DEBUG + cnt++; +#endif /* UNIV_SEARCH_DEBUG */ + + goto rec_loop; + } + } + + if (moves_up) { + if (UNIV_UNLIKELY(!btr_pcur_move_to_next(pcur, &mtr))) { +not_moved: + btr_pcur_store_position(pcur, &mtr); + + if (match_mode != 0) { + err = DB_RECORD_NOT_FOUND; + } else { + err = DB_END_OF_INDEX; + } + + goto normal_return; + } + } else { + if (UNIV_UNLIKELY(!btr_pcur_move_to_prev(pcur, &mtr))) { + goto not_moved; + } + } + +#ifdef UNIV_SEARCH_DEBUG + cnt++; +#endif /* UNIV_SEARCH_DEBUG */ + + goto rec_loop; + +lock_wait_or_error: + /* Reset the old and new "did semi-consistent read" flags. */ + if (UNIV_UNLIKELY(prebuilt->row_read_type + == ROW_READ_DID_SEMI_CONSISTENT)) { + prebuilt->row_read_type = ROW_READ_TRY_SEMI_CONSISTENT; + } + did_semi_consistent_read = FALSE; + + /*-------------------------------------------------------------*/ + + btr_pcur_store_position(pcur, &mtr); + + mtr_commit(&mtr); + mtr_has_extra_clust_latch = FALSE; + + trx->error_state = err; + + /* The following is a patch for MySQL */ + + que_thr_stop_for_mysql(thr); + + thr->lock_state = QUE_THR_LOCK_ROW; + + if (row_mysql_handle_errors(&err, trx, thr, NULL)) { + /* It was a lock wait, and it ended */ + + thr->lock_state = QUE_THR_LOCK_NOLOCK; + mtr_start(&mtr); + + sel_restore_position_for_mysql(&same_user_rec, + BTR_SEARCH_LEAF, pcur, + moves_up, &mtr); + + if ((srv_locks_unsafe_for_binlog + || trx->isolation_level == TRX_ISO_READ_COMMITTED) + && !same_user_rec) { + + /* Since we were not able to restore the cursor + on the same user record, we cannot use + row_unlock_for_mysql() to unlock any records, and + we must thus reset the new rec lock info. Since + in lock0lock.c we have blocked the inheriting of gap + X-locks, we actually do not have any new record locks + set in this case. + + Note that if we were able to restore on the 'same' + user record, it is still possible that we were actually + waiting on a delete-marked record, and meanwhile + it was removed by purge and inserted again by some + other user. But that is no problem, because in + rec_loop we will again try to set a lock, and + new_rec_lock_info in trx will be right at the end. */ + + trx_reset_new_rec_lock_info(trx); + } + + mode = pcur->search_mode; + + goto rec_loop; + } + + thr->lock_state = QUE_THR_LOCK_NOLOCK; + +#ifdef UNIV_SEARCH_DEBUG + /* fputs("Using ", stderr); + dict_index_name_print(stderr, index); + fprintf(stderr, " cnt %lu ret value %lu err\n", cnt, err); */ +#endif /* UNIV_SEARCH_DEBUG */ + goto func_exit; + +normal_return: + /*-------------------------------------------------------------*/ + que_thr_stop_for_mysql_no_error(thr, trx); + + mtr_commit(&mtr); + + if (prebuilt->n_fetch_cached > 0) { + row_sel_pop_cached_row_for_mysql(buf, prebuilt); + + err = DB_SUCCESS; + } + +#ifdef UNIV_SEARCH_DEBUG + /* fputs("Using ", stderr); + dict_index_name_print(stderr, index); + fprintf(stderr, " cnt %lu ret value %lu err\n", cnt, err); */ +#endif /* UNIV_SEARCH_DEBUG */ + if (err == DB_SUCCESS) { + srv_n_rows_read++; + } + +func_exit: + trx->op_info = ""; + if (UNIV_LIKELY_NULL(heap)) { + mem_heap_free(heap); + } + + /* Set or reset the "did semi-consistent read" flag on return. + The flag did_semi_consistent_read is set if and only if + the record being returned was fetched with a semi-consistent read. */ + ut_ad(prebuilt->row_read_type != ROW_READ_WITH_LOCKS + || !did_semi_consistent_read); + + if (UNIV_UNLIKELY(prebuilt->row_read_type != ROW_READ_WITH_LOCKS)) { + if (UNIV_UNLIKELY(did_semi_consistent_read)) { + prebuilt->row_read_type = ROW_READ_DID_SEMI_CONSISTENT; + } else { + prebuilt->row_read_type = ROW_READ_TRY_SEMI_CONSISTENT; + } + } + return(err); +} + +/*********************************************************************** +Checks if MySQL at the moment is allowed for this table to retrieve a +consistent read result, or store it to the query cache. */ +UNIV_INTERN +ibool +row_search_check_if_query_cache_permitted( +/*======================================*/ + /* out: TRUE if storing or retrieving + from the query cache is permitted */ + trx_t* trx, /* in: transaction object */ + const char* norm_name) /* in: concatenation of database name, + '/' char, table name */ +{ + dict_table_t* table; + ibool ret = FALSE; + + table = dict_table_get(norm_name, FALSE); + + if (table == NULL) { + + return(FALSE); + } + + mutex_enter(&kernel_mutex); + + /* Start the transaction if it is not started yet */ + + trx_start_if_not_started_low(trx); + + /* If there are locks on the table or some trx has invalidated the + cache up to our trx id, then ret = FALSE. + We do not check what type locks there are on the table, though only + IX type locks actually would require ret = FALSE. */ + + if (UT_LIST_GET_LEN(table->locks) == 0 + && ut_dulint_cmp(trx->id, + table->query_cache_inv_trx_id) >= 0) { + + ret = TRUE; + + /* If the isolation level is high, assign a read view for the + transaction if it does not yet have one */ + + if (trx->isolation_level >= TRX_ISO_REPEATABLE_READ + && !trx->read_view) { + + trx->read_view = read_view_open_now( + trx->id, trx->global_read_view_heap); + trx->global_read_view = trx->read_view; + } + } + + mutex_exit(&kernel_mutex); + + return(ret); +} + +/*********************************************************************** +Read the AUTOINC column from the current row. If the value is less than +0 and the type is not unsigned then we reset the value to 0. */ +static +ib_uint64_t +row_search_autoinc_read_column( +/*===========================*/ + /* out: value read from the column */ + dict_index_t* index, /* in: index to read from */ + const rec_t* rec, /* in: current rec */ + ulint col_no, /* in: column number */ + ibool unsigned_type) /* in: signed or unsigned flag */ +{ + ulint len; + const byte* data; + ib_uint64_t value; + mem_heap_t* heap = NULL; + ulint offsets_[REC_OFFS_NORMAL_SIZE]; + ulint* offsets = offsets_; + + rec_offs_init(offsets_); + + offsets = rec_get_offsets(rec, index, offsets, ULINT_UNDEFINED, &heap); + + data = rec_get_nth_field(rec, offsets, col_no, &len); + + ut_a(len != UNIV_SQL_NULL); + ut_a(len <= sizeof value); + + /* we assume AUTOINC value cannot be negative */ + value = mach_read_int_type(data, len, unsigned_type); + + if (UNIV_LIKELY_NULL(heap)) { + mem_heap_free(heap); + } + + if (!unsigned_type && (ib_int64_t) value < 0) { + value = 0; + } + + return(value); +} + +/*********************************************************************** +Get the last row. */ +static +const rec_t* +row_search_autoinc_get_rec( +/*=======================*/ + /* out: current rec or NULL */ + btr_pcur_t* pcur, /* in: the current cursor */ + mtr_t* mtr) /* in: mini transaction */ +{ + do { + const rec_t* rec = btr_pcur_get_rec(pcur); + + if (page_rec_is_user_rec(rec)) { + return(rec); + } + } while (btr_pcur_move_to_prev(pcur, mtr)); + + return(NULL); +} + +/*********************************************************************** +Read the max AUTOINC value from an index. */ +UNIV_INTERN +ulint +row_search_max_autoinc( +/*===================*/ + /* out: DB_SUCCESS if all OK else + error code, DB_RECORD_NOT_FOUND if + column name can't be found in index */ + dict_index_t* index, /* in: index to search */ + const char* col_name, /* in: name of autoinc column */ + ib_uint64_t* value) /* out: AUTOINC value read */ +{ + ulint i; + ulint n_cols; + dict_field_t* dfield = NULL; + ulint error = DB_SUCCESS; + + n_cols = dict_index_get_n_ordering_defined_by_user(index); + + /* Search the index for the AUTOINC column name */ + for (i = 0; i < n_cols; ++i) { + dfield = dict_index_get_nth_field(index, i); + + if (strcmp(col_name, dfield->name) == 0) { + break; + } + } + + *value = 0; + + /* Must find the AUTOINC column name */ + if (i < n_cols && dfield) { + mtr_t mtr; + btr_pcur_t pcur; + + mtr_start(&mtr); + + /* Open at the high/right end (FALSE), and INIT + cursor (TRUE) */ + btr_pcur_open_at_index_side( + FALSE, index, BTR_SEARCH_LEAF, &pcur, TRUE, &mtr); + + if (page_get_n_recs(btr_pcur_get_page(&pcur)) > 0) { + const rec_t* rec; + + rec = row_search_autoinc_get_rec(&pcur, &mtr); + + if (rec != NULL) { + ibool unsigned_type = ( + dfield->col->prtype & DATA_UNSIGNED); + + *value = row_search_autoinc_read_column( + index, rec, i, unsigned_type); + } + } + + btr_pcur_close(&pcur); + + mtr_commit(&mtr); + } else { + error = DB_RECORD_NOT_FOUND; + } + + return(error); +} diff --git a/storage/xtradb/row/row0uins.c b/storage/xtradb/row/row0uins.c new file mode 100644 index 00000000000..69d6b2e6c2a --- /dev/null +++ b/storage/xtradb/row/row0uins.c @@ -0,0 +1,350 @@ +/***************************************************************************** + +Copyright (c) 1997, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/****************************************************** +Fresh insert undo + +Created 2/25/1997 Heikki Tuuri +*******************************************************/ + +#include "row0uins.h" + +#ifdef UNIV_NONINL +#include "row0uins.ic" +#endif + +#include "dict0dict.h" +#include "dict0boot.h" +#include "dict0crea.h" +#include "trx0undo.h" +#include "trx0roll.h" +#include "btr0btr.h" +#include "mach0data.h" +#include "row0undo.h" +#include "row0vers.h" +#include "trx0trx.h" +#include "trx0rec.h" +#include "row0row.h" +#include "row0upd.h" +#include "que0que.h" +#include "ibuf0ibuf.h" +#include "log0log.h" + +/******************************************************************* +Removes a clustered index record. The pcur in node was positioned on the +record, now it is detached. */ +static +ulint +row_undo_ins_remove_clust_rec( +/*==========================*/ + /* out: DB_SUCCESS or DB_OUT_OF_FILE_SPACE */ + undo_node_t* node) /* in: undo node */ +{ + btr_cur_t* btr_cur; + ibool success; + ulint err; + ulint n_tries = 0; + mtr_t mtr; + + mtr_start(&mtr); + + success = btr_pcur_restore_position(BTR_MODIFY_LEAF, &(node->pcur), + &mtr); + ut_a(success); + + if (ut_dulint_cmp(node->table->id, DICT_INDEXES_ID) == 0) { + ut_ad(node->trx->dict_operation_lock_mode == RW_X_LATCH); + + /* Drop the index tree associated with the row in + SYS_INDEXES table: */ + + dict_drop_index_tree(btr_pcur_get_rec(&(node->pcur)), &mtr); + + mtr_commit(&mtr); + + mtr_start(&mtr); + + success = btr_pcur_restore_position(BTR_MODIFY_LEAF, + &(node->pcur), &mtr); + ut_a(success); + } + + btr_cur = btr_pcur_get_btr_cur(&(node->pcur)); + + success = btr_cur_optimistic_delete(btr_cur, &mtr); + + btr_pcur_commit_specify_mtr(&(node->pcur), &mtr); + + if (success) { + trx_undo_rec_release(node->trx, node->undo_no); + + return(DB_SUCCESS); + } +retry: + /* If did not succeed, try pessimistic descent to tree */ + mtr_start(&mtr); + + success = btr_pcur_restore_position(BTR_MODIFY_TREE, + &(node->pcur), &mtr); + ut_a(success); + + btr_cur_pessimistic_delete(&err, FALSE, btr_cur, + trx_is_recv(node->trx) + ? RB_RECOVERY + : RB_NORMAL, &mtr); + + /* The delete operation may fail if we have little + file space left: TODO: easiest to crash the database + and restart with more file space */ + + if (err == DB_OUT_OF_FILE_SPACE + && n_tries < BTR_CUR_RETRY_DELETE_N_TIMES) { + + btr_pcur_commit_specify_mtr(&(node->pcur), &mtr); + + n_tries++; + + os_thread_sleep(BTR_CUR_RETRY_SLEEP_TIME); + + goto retry; + } + + btr_pcur_commit_specify_mtr(&(node->pcur), &mtr); + + trx_undo_rec_release(node->trx, node->undo_no); + + return(err); +} + +/******************************************************************* +Removes a secondary index entry if found. */ +static +ulint +row_undo_ins_remove_sec_low( +/*========================*/ + /* out: DB_SUCCESS, DB_FAIL, or + DB_OUT_OF_FILE_SPACE */ + ulint mode, /* in: BTR_MODIFY_LEAF or BTR_MODIFY_TREE, + depending on whether we wish optimistic or + pessimistic descent down the index tree */ + dict_index_t* index, /* in: index */ + dtuple_t* entry) /* in: index entry to remove */ +{ + btr_pcur_t pcur; + btr_cur_t* btr_cur; + ibool found; + ibool success; + ulint err; + mtr_t mtr; + + log_free_check(); + mtr_start(&mtr); + + found = row_search_index_entry(index, entry, mode, &pcur, &mtr); + + btr_cur = btr_pcur_get_btr_cur(&pcur); + + if (!found) { + /* Not found */ + + btr_pcur_close(&pcur); + mtr_commit(&mtr); + + return(DB_SUCCESS); + } + + if (mode == BTR_MODIFY_LEAF) { + success = btr_cur_optimistic_delete(btr_cur, &mtr); + + if (success) { + err = DB_SUCCESS; + } else { + err = DB_FAIL; + } + } else { + ut_ad(mode == BTR_MODIFY_TREE); + + /* No need to distinguish RB_RECOVERY here, because we + are deleting a secondary index record: the distinction + between RB_NORMAL and RB_RECOVERY only matters when + deleting a record that contains externally stored + columns. */ + ut_ad(!dict_index_is_clust(index)); + btr_cur_pessimistic_delete(&err, FALSE, btr_cur, + RB_NORMAL, &mtr); + } + + btr_pcur_close(&pcur); + mtr_commit(&mtr); + + return(err); +} + +/******************************************************************* +Removes a secondary index entry from the index if found. Tries first +optimistic, then pessimistic descent down the tree. */ +static +ulint +row_undo_ins_remove_sec( +/*====================*/ + /* out: DB_SUCCESS or DB_OUT_OF_FILE_SPACE */ + dict_index_t* index, /* in: index */ + dtuple_t* entry) /* in: index entry to insert */ +{ + ulint err; + ulint n_tries = 0; + + /* Try first optimistic descent to the B-tree */ + + err = row_undo_ins_remove_sec_low(BTR_MODIFY_LEAF, index, entry); + + if (err == DB_SUCCESS) { + + return(err); + } + + /* Try then pessimistic descent to the B-tree */ +retry: + err = row_undo_ins_remove_sec_low(BTR_MODIFY_TREE, index, entry); + + /* The delete operation may fail if we have little + file space left: TODO: easiest to crash the database + and restart with more file space */ + + if (err != DB_SUCCESS && n_tries < BTR_CUR_RETRY_DELETE_N_TIMES) { + + n_tries++; + + os_thread_sleep(BTR_CUR_RETRY_SLEEP_TIME); + + goto retry; + } + + return(err); +} + +/*************************************************************** +Parses the row reference and other info in a fresh insert undo record. */ +static +void +row_undo_ins_parse_undo_rec( +/*========================*/ + undo_node_t* node) /* in/out: row undo node */ +{ + dict_index_t* clust_index; + byte* ptr; + dulint undo_no; + dulint table_id; + ulint type; + ulint dummy; + ibool dummy_extern; + + ut_ad(node); + + ptr = trx_undo_rec_get_pars(node->undo_rec, &type, &dummy, + &dummy_extern, &undo_no, &table_id); + ut_ad(type == TRX_UNDO_INSERT_REC); + node->rec_type = type; + + node->update = NULL; + node->table = dict_table_get_on_id(table_id, node->trx); + + /* Skip the UNDO if we can't find the table or the .ibd file. */ + if (UNIV_UNLIKELY(node->table == NULL)) { + } else if (UNIV_UNLIKELY(node->table->ibd_file_missing)) { + node->table = NULL; + } else { + clust_index = dict_table_get_first_index(node->table); + + if (clust_index != NULL) { + ptr = trx_undo_rec_get_row_ref( + ptr, clust_index, &node->ref, node->heap); + } else { + ut_print_timestamp(stderr); + fprintf(stderr, " InnoDB: table "); + ut_print_name(stderr, node->trx, TRUE, + node->table->name); + fprintf(stderr, " has no indexes, " + "ignoring the table\n"); + + node->table = NULL; + } + } +} + +/*************************************************************** +Undoes a fresh insert of a row to a table. A fresh insert means that +the same clustered index unique key did not have any record, even delete +marked, at the time of the insert. InnoDB is eager in a rollback: +if it figures out that an index record will be removed in the purge +anyway, it will remove it in the rollback. */ +UNIV_INTERN +ulint +row_undo_ins( +/*=========*/ + /* out: DB_SUCCESS or DB_OUT_OF_FILE_SPACE */ + undo_node_t* node) /* in: row undo node */ +{ + ut_ad(node); + ut_ad(node->state == UNDO_NODE_INSERT); + + row_undo_ins_parse_undo_rec(node); + + if (!node->table || !row_undo_search_clust_to_pcur(node)) { + trx_undo_rec_release(node->trx, node->undo_no); + + return(DB_SUCCESS); + } + + /* Iterate over all the indexes and undo the insert.*/ + + /* Skip the clustered index (the first index) */ + node->index = dict_table_get_next_index( + dict_table_get_first_index(node->table)); + + while (node->index != NULL) { + dtuple_t* entry; + ulint err; + + entry = row_build_index_entry(node->row, node->ext, + node->index, node->heap); + if (UNIV_UNLIKELY(!entry)) { + /* The database must have crashed after + inserting a clustered index record but before + writing all the externally stored columns of + that record. Because secondary index entries + are inserted after the clustered index record, + we may assume that the secondary index record + does not exist. However, this situation may + only occur during the rollback of incomplete + transactions. */ + ut_a(trx_is_recv(node->trx)); + } else { + err = row_undo_ins_remove_sec(node->index, entry); + + if (err != DB_SUCCESS) { + + return(err); + } + } + + node->index = dict_table_get_next_index(node->index); + } + + return(row_undo_ins_remove_clust_rec(node)); +} diff --git a/storage/xtradb/row/row0umod.c b/storage/xtradb/row/row0umod.c new file mode 100644 index 00000000000..835f357fc8d --- /dev/null +++ b/storage/xtradb/row/row0umod.c @@ -0,0 +1,820 @@ +/***************************************************************************** + +Copyright (c) 1997, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/****************************************************** +Undo modify of a row + +Created 2/27/1997 Heikki Tuuri +*******************************************************/ + +#include "row0umod.h" + +#ifdef UNIV_NONINL +#include "row0umod.ic" +#endif + +#include "dict0dict.h" +#include "dict0boot.h" +#include "trx0undo.h" +#include "trx0roll.h" +#include "btr0btr.h" +#include "mach0data.h" +#include "row0undo.h" +#include "row0vers.h" +#include "trx0trx.h" +#include "trx0rec.h" +#include "row0row.h" +#include "row0upd.h" +#include "que0que.h" +#include "log0log.h" + +/* Considerations on undoing a modify operation. +(1) Undoing a delete marking: all index records should be found. Some of +them may have delete mark already FALSE, if the delete mark operation was +stopped underway, or if the undo operation ended prematurely because of a +system crash. +(2) Undoing an update of a delete unmarked record: the newer version of +an updated secondary index entry should be removed if no prior version +of the clustered index record requires its existence. Otherwise, it should +be delete marked. +(3) Undoing an update of a delete marked record. In this kind of update a +delete marked clustered index record was delete unmarked and possibly also +some of its fields were changed. Now, it is possible that the delete marked +version has become obsolete at the time the undo is started. */ + +/*************************************************************** +Checks if also the previous version of the clustered index record was +modified or inserted by the same transaction, and its undo number is such +that it should be undone in the same rollback. */ +UNIV_INLINE +ibool +row_undo_mod_undo_also_prev_vers( +/*=============================*/ + /* out: TRUE if also previous modify or + insert of this row should be undone */ + undo_node_t* node, /* in: row undo node */ + dulint* undo_no)/* out: the undo number */ +{ + trx_undo_rec_t* undo_rec; + trx_t* trx; + + trx = node->trx; + + if (0 != ut_dulint_cmp(node->new_trx_id, trx->id)) { + + *undo_no = ut_dulint_zero; + return(FALSE); + } + + undo_rec = trx_undo_get_undo_rec_low(node->new_roll_ptr, node->heap); + + *undo_no = trx_undo_rec_get_undo_no(undo_rec); + + return(ut_dulint_cmp(trx->roll_limit, *undo_no) <= 0); +} + +/*************************************************************** +Undoes a modify in a clustered index record. */ +static +ulint +row_undo_mod_clust_low( +/*===================*/ + /* out: DB_SUCCESS, DB_FAIL, or error code: + we may run out of file space */ + undo_node_t* node, /* in: row undo node */ + que_thr_t* thr, /* in: query thread */ + mtr_t* mtr, /* in: mtr; must be committed before + latching any further pages */ + ulint mode) /* in: BTR_MODIFY_LEAF or BTR_MODIFY_TREE */ +{ + btr_pcur_t* pcur; + btr_cur_t* btr_cur; + ulint err; + ibool success; + + pcur = &(node->pcur); + btr_cur = btr_pcur_get_btr_cur(pcur); + + success = btr_pcur_restore_position(mode, pcur, mtr); + + ut_ad(success); + + if (mode == BTR_MODIFY_LEAF) { + + err = btr_cur_optimistic_update(BTR_NO_LOCKING_FLAG + | BTR_NO_UNDO_LOG_FLAG + | BTR_KEEP_SYS_FLAG, + btr_cur, node->update, + node->cmpl_info, thr, mtr); + } else { + mem_heap_t* heap = NULL; + big_rec_t* dummy_big_rec; + + ut_ad(mode == BTR_MODIFY_TREE); + + err = btr_cur_pessimistic_update( + BTR_NO_LOCKING_FLAG + | BTR_NO_UNDO_LOG_FLAG + | BTR_KEEP_SYS_FLAG, + btr_cur, &heap, &dummy_big_rec, node->update, + node->cmpl_info, thr, mtr); + + ut_a(!dummy_big_rec); + if (UNIV_LIKELY_NULL(heap)) { + mem_heap_free(heap); + } + } + + return(err); +} + +/*************************************************************** +Removes a clustered index record after undo if possible. */ +static +ulint +row_undo_mod_remove_clust_low( +/*==========================*/ + /* out: DB_SUCCESS, DB_FAIL, or error code: + we may run out of file space */ + undo_node_t* node, /* in: row undo node */ + que_thr_t* thr __attribute__((unused)), /* in: query thread */ + mtr_t* mtr, /* in: mtr */ + ulint mode) /* in: BTR_MODIFY_LEAF or BTR_MODIFY_TREE */ +{ + btr_pcur_t* pcur; + btr_cur_t* btr_cur; + ulint err; + ibool success; + + pcur = &(node->pcur); + btr_cur = btr_pcur_get_btr_cur(pcur); + + success = btr_pcur_restore_position(mode, pcur, mtr); + + if (!success) { + + return(DB_SUCCESS); + } + + /* Find out if we can remove the whole clustered index record */ + + if (node->rec_type == TRX_UNDO_UPD_DEL_REC + && !row_vers_must_preserve_del_marked(node->new_trx_id, mtr)) { + + /* Ok, we can remove */ + } else { + return(DB_SUCCESS); + } + + if (mode == BTR_MODIFY_LEAF) { + success = btr_cur_optimistic_delete(btr_cur, mtr); + + if (success) { + err = DB_SUCCESS; + } else { + err = DB_FAIL; + } + } else { + ut_ad(mode == BTR_MODIFY_TREE); + + /* Note that since this operation is analogous to purge, + we can free also inherited externally stored fields: + hence the RB_NONE in the call below */ + + btr_cur_pessimistic_delete(&err, FALSE, btr_cur, RB_NONE, mtr); + + /* The delete operation may fail if we have little + file space left: TODO: easiest to crash the database + and restart with more file space */ + } + + return(err); +} + +/*************************************************************** +Undoes a modify in a clustered index record. Sets also the node state for the +next round of undo. */ +static +ulint +row_undo_mod_clust( +/*===============*/ + /* out: DB_SUCCESS or error code: we may run + out of file space */ + undo_node_t* node, /* in: row undo node */ + que_thr_t* thr) /* in: query thread */ +{ + btr_pcur_t* pcur; + mtr_t mtr; + ulint err; + ibool success; + ibool more_vers; + dulint new_undo_no; + + ut_ad(node && thr); + + /* Check if also the previous version of the clustered index record + should be undone in this same rollback operation */ + + more_vers = row_undo_mod_undo_also_prev_vers(node, &new_undo_no); + + pcur = &(node->pcur); + + mtr_start(&mtr); + + /* Try optimistic processing of the record, keeping changes within + the index page */ + + err = row_undo_mod_clust_low(node, thr, &mtr, BTR_MODIFY_LEAF); + + if (err != DB_SUCCESS) { + btr_pcur_commit_specify_mtr(pcur, &mtr); + + /* We may have to modify tree structure: do a pessimistic + descent down the index tree */ + + mtr_start(&mtr); + + err = row_undo_mod_clust_low(node, thr, &mtr, BTR_MODIFY_TREE); + } + + btr_pcur_commit_specify_mtr(pcur, &mtr); + + if (err == DB_SUCCESS && node->rec_type == TRX_UNDO_UPD_DEL_REC) { + + mtr_start(&mtr); + + err = row_undo_mod_remove_clust_low(node, thr, &mtr, + BTR_MODIFY_LEAF); + if (err != DB_SUCCESS) { + btr_pcur_commit_specify_mtr(pcur, &mtr); + + /* We may have to modify tree structure: do a + pessimistic descent down the index tree */ + + mtr_start(&mtr); + + err = row_undo_mod_remove_clust_low(node, thr, &mtr, + BTR_MODIFY_TREE); + } + + btr_pcur_commit_specify_mtr(pcur, &mtr); + } + + node->state = UNDO_NODE_FETCH_NEXT; + + trx_undo_rec_release(node->trx, node->undo_no); + + if (more_vers && err == DB_SUCCESS) { + + /* Reserve the undo log record to the prior version after + committing &mtr: this is necessary to comply with the latching + order, as &mtr may contain the fsp latch which is lower in + the latch hierarchy than trx->undo_mutex. */ + + success = trx_undo_rec_reserve(node->trx, new_undo_no); + + if (success) { + node->state = UNDO_NODE_PREV_VERS; + } + } + + return(err); +} + +/*************************************************************** +Delete marks or removes a secondary index entry if found. */ +static +ulint +row_undo_mod_del_mark_or_remove_sec_low( +/*====================================*/ + /* out: DB_SUCCESS, DB_FAIL, or + DB_OUT_OF_FILE_SPACE */ + undo_node_t* node, /* in: row undo node */ + que_thr_t* thr, /* in: query thread */ + dict_index_t* index, /* in: index */ + dtuple_t* entry, /* in: index entry */ + ulint mode) /* in: latch mode BTR_MODIFY_LEAF or + BTR_MODIFY_TREE */ +{ + ibool found; + btr_pcur_t pcur; + btr_cur_t* btr_cur; + ibool success; + ibool old_has; + ulint err; + mtr_t mtr; + mtr_t mtr_vers; + + log_free_check(); + mtr_start(&mtr); + + found = row_search_index_entry(index, entry, mode, &pcur, &mtr); + + btr_cur = btr_pcur_get_btr_cur(&pcur); + + if (!found) { + /* In crash recovery, the secondary index record may + be missing if the UPDATE did not have time to insert + the secondary index records before the crash. When we + are undoing that UPDATE in crash recovery, the record + may be missing. + + In normal processing, if an update ends in a deadlock + before it has inserted all updated secondary index + records, then the undo will not find those records. */ + + btr_pcur_close(&pcur); + mtr_commit(&mtr); + + return(DB_SUCCESS); + } + + /* We should remove the index record if no prior version of the row, + which cannot be purged yet, requires its existence. If some requires, + we should delete mark the record. */ + + mtr_start(&mtr_vers); + + success = btr_pcur_restore_position(BTR_SEARCH_LEAF, &(node->pcur), + &mtr_vers); + ut_a(success); + + old_has = row_vers_old_has_index_entry(FALSE, + btr_pcur_get_rec(&(node->pcur)), + &mtr_vers, index, entry); + if (old_has) { + err = btr_cur_del_mark_set_sec_rec(BTR_NO_LOCKING_FLAG, + btr_cur, TRUE, thr, &mtr); + ut_ad(err == DB_SUCCESS); + } else { + /* Remove the index record */ + + if (mode == BTR_MODIFY_LEAF) { + success = btr_cur_optimistic_delete(btr_cur, &mtr); + if (success) { + err = DB_SUCCESS; + } else { + err = DB_FAIL; + } + } else { + ut_ad(mode == BTR_MODIFY_TREE); + + /* No need to distinguish RB_RECOVERY here, because we + are deleting a secondary index record: the distinction + between RB_NORMAL and RB_RECOVERY only matters when + deleting a record that contains externally stored + columns. */ + ut_ad(!dict_index_is_clust(index)); + btr_cur_pessimistic_delete(&err, FALSE, btr_cur, + RB_NORMAL, &mtr); + + /* The delete operation may fail if we have little + file space left: TODO: easiest to crash the database + and restart with more file space */ + } + } + + btr_pcur_commit_specify_mtr(&(node->pcur), &mtr_vers); + btr_pcur_close(&pcur); + mtr_commit(&mtr); + + return(err); +} + +/*************************************************************** +Delete marks or removes a secondary index entry if found. +NOTE that if we updated the fields of a delete-marked secondary index record +so that alphabetically they stayed the same, e.g., 'abc' -> 'aBc', we cannot +return to the original values because we do not know them. But this should +not cause problems because in row0sel.c, in queries we always retrieve the +clustered index record or an earlier version of it, if the secondary index +record through which we do the search is delete-marked. */ +static +ulint +row_undo_mod_del_mark_or_remove_sec( +/*================================*/ + /* out: DB_SUCCESS or DB_OUT_OF_FILE_SPACE */ + undo_node_t* node, /* in: row undo node */ + que_thr_t* thr, /* in: query thread */ + dict_index_t* index, /* in: index */ + dtuple_t* entry) /* in: index entry */ +{ + ulint err; + + err = row_undo_mod_del_mark_or_remove_sec_low(node, thr, index, + entry, BTR_MODIFY_LEAF); + if (err == DB_SUCCESS) { + + return(err); + } + + err = row_undo_mod_del_mark_or_remove_sec_low(node, thr, index, + entry, BTR_MODIFY_TREE); + return(err); +} + +/*************************************************************** +Delete unmarks a secondary index entry which must be found. It might not be +delete-marked at the moment, but it does not harm to unmark it anyway. We also +need to update the fields of the secondary index record if we updated its +fields but alphabetically they stayed the same, e.g., 'abc' -> 'aBc'. */ +static +ulint +row_undo_mod_del_unmark_sec_and_undo_update( +/*========================================*/ + /* out: DB_FAIL or DB_SUCCESS or + DB_OUT_OF_FILE_SPACE */ + ulint mode, /* in: search mode: BTR_MODIFY_LEAF or + BTR_MODIFY_TREE */ + que_thr_t* thr, /* in: query thread */ + dict_index_t* index, /* in: index */ + dtuple_t* entry) /* in: index entry */ +{ + mem_heap_t* heap; + btr_pcur_t pcur; + upd_t* update; + ulint err = DB_SUCCESS; + big_rec_t* dummy_big_rec; + mtr_t mtr; + trx_t* trx = thr_get_trx(thr); + + /* Ignore indexes that are being created. */ + if (UNIV_UNLIKELY(*index->name == TEMP_INDEX_PREFIX)) { + + return(DB_SUCCESS); + } + + log_free_check(); + mtr_start(&mtr); + + if (UNIV_UNLIKELY(!row_search_index_entry(index, entry, + mode, &pcur, &mtr))) { + fputs("InnoDB: error in sec index entry del undo in\n" + "InnoDB: ", stderr); + dict_index_name_print(stderr, trx, index); + fputs("\n" + "InnoDB: tuple ", stderr); + dtuple_print(stderr, entry); + fputs("\n" + "InnoDB: record ", stderr); + rec_print(stderr, btr_pcur_get_rec(&pcur), index); + putc('\n', stderr); + trx_print(stderr, trx, 0); + fputs("\n" + "InnoDB: Submit a detailed bug report" + " to http://bugs.mysql.com\n", stderr); + } else { + btr_cur_t* btr_cur = btr_pcur_get_btr_cur(&pcur); + + err = btr_cur_del_mark_set_sec_rec(BTR_NO_LOCKING_FLAG, + btr_cur, FALSE, thr, &mtr); + ut_a(err == DB_SUCCESS); + heap = mem_heap_create(100); + + update = row_upd_build_sec_rec_difference_binary( + index, entry, btr_cur_get_rec(btr_cur), trx, heap); + if (upd_get_n_fields(update) == 0) { + + /* Do nothing */ + + } else if (mode == BTR_MODIFY_LEAF) { + /* Try an optimistic updating of the record, keeping + changes within the page */ + + err = btr_cur_optimistic_update( + BTR_KEEP_SYS_FLAG | BTR_NO_LOCKING_FLAG, + btr_cur, update, 0, thr, &mtr); + switch (err) { + case DB_OVERFLOW: + case DB_UNDERFLOW: + case DB_ZIP_OVERFLOW: + err = DB_FAIL; + } + } else { + ut_a(mode == BTR_MODIFY_TREE); + err = btr_cur_pessimistic_update( + BTR_KEEP_SYS_FLAG | BTR_NO_LOCKING_FLAG, + btr_cur, &heap, &dummy_big_rec, + update, 0, thr, &mtr); + ut_a(!dummy_big_rec); + } + + mem_heap_free(heap); + } + + btr_pcur_close(&pcur); + mtr_commit(&mtr); + + return(err); +} + +/*************************************************************** +Undoes a modify in secondary indexes when undo record type is UPD_DEL. */ +static +ulint +row_undo_mod_upd_del_sec( +/*=====================*/ + /* out: DB_SUCCESS or DB_OUT_OF_FILE_SPACE */ + undo_node_t* node, /* in: row undo node */ + que_thr_t* thr) /* in: query thread */ +{ + mem_heap_t* heap; + dtuple_t* entry; + dict_index_t* index; + ulint err = DB_SUCCESS; + + heap = mem_heap_create(1024); + + while (node->index != NULL) { + index = node->index; + + entry = row_build_index_entry(node->row, node->ext, + index, heap); + if (UNIV_UNLIKELY(!entry)) { + /* The database must have crashed after + inserting a clustered index record but before + writing all the externally stored columns of + that record. Because secondary index entries + are inserted after the clustered index record, + we may assume that the secondary index record + does not exist. However, this situation may + only occur during the rollback of incomplete + transactions. */ + ut_a(trx_is_recv(thr_get_trx(thr))); + } else { + err = row_undo_mod_del_mark_or_remove_sec( + node, thr, index, entry); + + if (err != DB_SUCCESS) { + + break; + } + } + + mem_heap_empty(heap); + + node->index = dict_table_get_next_index(node->index); + } + + mem_heap_free(heap); + + return(err); +} + +/*************************************************************** +Undoes a modify in secondary indexes when undo record type is DEL_MARK. */ +static +ulint +row_undo_mod_del_mark_sec( +/*======================*/ + /* out: DB_SUCCESS or DB_OUT_OF_FILE_SPACE */ + undo_node_t* node, /* in: row undo node */ + que_thr_t* thr) /* in: query thread */ +{ + mem_heap_t* heap; + dtuple_t* entry; + dict_index_t* index; + ulint err; + + heap = mem_heap_create(1024); + + while (node->index != NULL) { + index = node->index; + + entry = row_build_index_entry(node->row, node->ext, + index, heap); + ut_a(entry); + err = row_undo_mod_del_unmark_sec_and_undo_update( + BTR_MODIFY_LEAF, thr, index, entry); + if (err == DB_FAIL) { + err = row_undo_mod_del_unmark_sec_and_undo_update( + BTR_MODIFY_TREE, thr, index, entry); + } + + if (err != DB_SUCCESS) { + + mem_heap_free(heap); + + return(err); + } + + node->index = dict_table_get_next_index(node->index); + } + + mem_heap_free(heap); + + return(DB_SUCCESS); +} + +/*************************************************************** +Undoes a modify in secondary indexes when undo record type is UPD_EXIST. */ +static +ulint +row_undo_mod_upd_exist_sec( +/*=======================*/ + /* out: DB_SUCCESS or DB_OUT_OF_FILE_SPACE */ + undo_node_t* node, /* in: row undo node */ + que_thr_t* thr) /* in: query thread */ +{ + mem_heap_t* heap; + dtuple_t* entry; + dict_index_t* index; + ulint err; + + if (node->cmpl_info & UPD_NODE_NO_ORD_CHANGE) { + /* No change in secondary indexes */ + + return(DB_SUCCESS); + } + + heap = mem_heap_create(1024); + + while (node->index != NULL) { + index = node->index; + + if (row_upd_changes_ord_field_binary(node->row, node->index, + node->update)) { + + /* Build the newest version of the index entry */ + entry = row_build_index_entry(node->row, node->ext, + index, heap); + ut_a(entry); + /* NOTE that if we updated the fields of a + delete-marked secondary index record so that + alphabetically they stayed the same, e.g., + 'abc' -> 'aBc', we cannot return to the original + values because we do not know them. But this should + not cause problems because in row0sel.c, in queries + we always retrieve the clustered index record or an + earlier version of it, if the secondary index record + through which we do the search is delete-marked. */ + + err = row_undo_mod_del_mark_or_remove_sec(node, thr, + index, + entry); + if (err != DB_SUCCESS) { + mem_heap_free(heap); + + return(err); + } + + /* We may have to update the delete mark in the + secondary index record of the previous version of + the row. We also need to update the fields of + the secondary index record if we updated its fields + but alphabetically they stayed the same, e.g., + 'abc' -> 'aBc'. */ + mem_heap_empty(heap); + entry = row_build_index_entry(node->undo_row, + node->undo_ext, + index, heap); + ut_a(entry); + + err = row_undo_mod_del_unmark_sec_and_undo_update( + BTR_MODIFY_LEAF, thr, index, entry); + if (err == DB_FAIL) { + err = row_undo_mod_del_unmark_sec_and_undo_update( + BTR_MODIFY_TREE, thr, index, entry); + } + + if (err != DB_SUCCESS) { + mem_heap_free(heap); + + return(err); + } + } + + node->index = dict_table_get_next_index(node->index); + } + + mem_heap_free(heap); + + return(DB_SUCCESS); +} + +/*************************************************************** +Parses the row reference and other info in a modify undo log record. */ +static +void +row_undo_mod_parse_undo_rec( +/*========================*/ + undo_node_t* node, /* in: row undo node */ + que_thr_t* thr) /* in: query thread */ +{ + dict_index_t* clust_index; + byte* ptr; + dulint undo_no; + dulint table_id; + dulint trx_id; + dulint roll_ptr; + ulint info_bits; + ulint type; + ulint cmpl_info; + ibool dummy_extern; + trx_t* trx; + + ut_ad(node && thr); + trx = thr_get_trx(thr); + ptr = trx_undo_rec_get_pars(node->undo_rec, &type, &cmpl_info, + &dummy_extern, &undo_no, &table_id); + node->rec_type = type; + + node->table = dict_table_get_on_id(table_id, trx); + + /* TODO: other fixes associated with DROP TABLE + rollback in the + same table by another user */ + + if (node->table == NULL) { + /* Table was dropped */ + return; + } + + if (node->table->ibd_file_missing) { + /* We skip undo operations to missing .ibd files */ + node->table = NULL; + + return; + } + + clust_index = dict_table_get_first_index(node->table); + + ptr = trx_undo_update_rec_get_sys_cols(ptr, &trx_id, &roll_ptr, + &info_bits); + + ptr = trx_undo_rec_get_row_ref(ptr, clust_index, &(node->ref), + node->heap); + + trx_undo_update_rec_get_update(ptr, clust_index, type, trx_id, + roll_ptr, info_bits, trx, + node->heap, &(node->update)); + node->new_roll_ptr = roll_ptr; + node->new_trx_id = trx_id; + node->cmpl_info = cmpl_info; +} + +/*************************************************************** +Undoes a modify operation on a row of a table. */ +UNIV_INTERN +ulint +row_undo_mod( +/*=========*/ + /* out: DB_SUCCESS or error code */ + undo_node_t* node, /* in: row undo node */ + que_thr_t* thr) /* in: query thread */ +{ + ulint err; + + ut_ad(node && thr); + ut_ad(node->state == UNDO_NODE_MODIFY); + + row_undo_mod_parse_undo_rec(node, thr); + + if (!node->table || !row_undo_search_clust_to_pcur(node)) { + /* It is already undone, or will be undone by another query + thread, or table was dropped */ + + trx_undo_rec_release(node->trx, node->undo_no); + node->state = UNDO_NODE_FETCH_NEXT; + + return(DB_SUCCESS); + } + + node->index = dict_table_get_next_index( + dict_table_get_first_index(node->table)); + + if (node->rec_type == TRX_UNDO_UPD_EXIST_REC) { + + err = row_undo_mod_upd_exist_sec(node, thr); + + } else if (node->rec_type == TRX_UNDO_DEL_MARK_REC) { + + err = row_undo_mod_del_mark_sec(node, thr); + } else { + ut_ad(node->rec_type == TRX_UNDO_UPD_DEL_REC); + err = row_undo_mod_upd_del_sec(node, thr); + } + + if (err != DB_SUCCESS) { + + return(err); + } + + err = row_undo_mod_clust(node, thr); + + return(err); +} diff --git a/storage/xtradb/row/row0undo.c b/storage/xtradb/row/row0undo.c new file mode 100644 index 00000000000..d372f88e207 --- /dev/null +++ b/storage/xtradb/row/row0undo.c @@ -0,0 +1,378 @@ +/***************************************************************************** + +Copyright (c) 1997, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/****************************************************** +Row undo + +Created 1/8/1997 Heikki Tuuri +*******************************************************/ + +#include "row0undo.h" + +#ifdef UNIV_NONINL +#include "row0undo.ic" +#endif + +#include "fsp0fsp.h" +#include "mach0data.h" +#include "trx0rseg.h" +#include "trx0trx.h" +#include "trx0roll.h" +#include "trx0undo.h" +#include "trx0purge.h" +#include "trx0rec.h" +#include "que0que.h" +#include "row0row.h" +#include "row0uins.h" +#include "row0umod.h" +#include "row0upd.h" +#include "row0mysql.h" +#include "srv0srv.h" + +/* How to undo row operations? +(1) For an insert, we have stored a prefix of the clustered index record +in the undo log. Using it, we look for the clustered record, and using +that we look for the records in the secondary indexes. The insert operation +may have been left incomplete, if the database crashed, for example. +We may have look at the trx id and roll ptr to make sure the record in the +clustered index is really the one for which the undo log record was +written. We can use the framework we get from the original insert op. +(2) Delete marking: We can use the framework we get from the original +delete mark op. We only have to check the trx id. +(3) Update: This may be the most complicated. We have to use the framework +we get from the original update op. + +What if the same trx repeatedly deletes and inserts an identical row. +Then the row id changes and also roll ptr. What if the row id was not +part of the ordering fields in the clustered index? Maybe we have to write +it to undo log. Well, maybe not, because if we order the row id and trx id +in descending order, then the only undeleted copy is the first in the +index. Our searches in row operations always position the cursor before +the first record in the result set. But, if there is no key defined for +a table, then it would be desirable that row id is in ascending order. +So, lets store row id in descending order only if it is not an ordering +field in the clustered index. + +NOTE: Deletes and inserts may lead to situation where there are identical +records in a secondary index. Is that a problem in the B-tree? Yes. +Also updates can lead to this, unless trx id and roll ptr are included in +ord fields. +(1) Fix in clustered indexes: include row id, trx id, and roll ptr +in node pointers of B-tree. +(2) Fix in secondary indexes: include all fields in node pointers, and +if an entry is inserted, check if it is equal to the right neighbor, +in which case update the right neighbor: the neighbor must be delete +marked, set it unmarked and write the trx id of the current transaction. + +What if the same trx repeatedly updates the same row, updating a secondary +index field or not? Updating a clustered index ordering field? + +(1) If it does not update the secondary index and not the clustered index +ord field. Then the secondary index record stays unchanged, but the +trx id in the secondary index record may be smaller than in the clustered +index record. This is no problem? +(2) If it updates secondary index ord field but not clustered: then in +secondary index there are delete marked records, which differ in an +ord field. No problem. +(3) Updates clustered ord field but not secondary, and secondary index +is unique. Then the record in secondary index is just updated at the +clustered ord field. +(4) + +Problem with duplicate records: +Fix 1: Add a trx op no field to all indexes. A problem: if a trx with a +bigger trx id has inserted and delete marked a similar row, our trx inserts +again a similar row, and a trx with an even bigger id delete marks it. Then +the position of the row should change in the index if the trx id affects +the alphabetical ordering. + +Fix 2: If an insert encounters a similar row marked deleted, we turn the +insert into an 'update' of the row marked deleted. Then we must write undo +info on the update. A problem: what if a purge operation tries to remove +the delete marked row? + +We can think of the database row versions as a linked list which starts +from the record in the clustered index, and is linked by roll ptrs +through undo logs. The secondary index records are references which tell +what kinds of records can be found in this linked list for a record +in the clustered index. + +How to do the purge? A record can be removed from the clustered index +if its linked list becomes empty, i.e., the row has been marked deleted +and its roll ptr points to the record in the undo log we are going through, +doing the purge. Similarly, during a rollback, a record can be removed +if the stored roll ptr in the undo log points to a trx already (being) purged, +or if the roll ptr is NULL, i.e., it was a fresh insert. */ + +/************************************************************************ +Creates a row undo node to a query graph. */ +UNIV_INTERN +undo_node_t* +row_undo_node_create( +/*=================*/ + /* out, own: undo node */ + trx_t* trx, /* in: transaction */ + que_thr_t* parent, /* in: parent node, i.e., a thr node */ + mem_heap_t* heap) /* in: memory heap where created */ +{ + undo_node_t* undo; + + ut_ad(trx && parent && heap); + + undo = mem_heap_alloc(heap, sizeof(undo_node_t)); + + undo->common.type = QUE_NODE_UNDO; + undo->common.parent = parent; + + undo->state = UNDO_NODE_FETCH_NEXT; + undo->trx = trx; + + btr_pcur_init(&(undo->pcur)); + + undo->heap = mem_heap_create(256); + + return(undo); +} + +/*************************************************************** +Looks for the clustered index record when node has the row reference. +The pcur in node is used in the search. If found, stores the row to node, +and stores the position of pcur, and detaches it. The pcur must be closed +by the caller in any case. */ +UNIV_INTERN +ibool +row_undo_search_clust_to_pcur( +/*==========================*/ + /* out: TRUE if found; NOTE the node->pcur + must be closed by the caller, regardless of + the return value */ + undo_node_t* node) /* in: row undo node */ +{ + dict_index_t* clust_index; + ibool found; + mtr_t mtr; + ibool ret; + rec_t* rec; + mem_heap_t* heap = NULL; + ulint offsets_[REC_OFFS_NORMAL_SIZE]; + ulint* offsets = offsets_; + rec_offs_init(offsets_); + + mtr_start(&mtr); + + clust_index = dict_table_get_first_index(node->table); + + found = row_search_on_row_ref(&(node->pcur), BTR_MODIFY_LEAF, + node->table, node->ref, &mtr); + + rec = btr_pcur_get_rec(&(node->pcur)); + + offsets = rec_get_offsets(rec, clust_index, offsets, + ULINT_UNDEFINED, &heap); + + if (!found || 0 != ut_dulint_cmp(node->roll_ptr, + row_get_rec_roll_ptr(rec, clust_index, + offsets))) { + + /* We must remove the reservation on the undo log record + BEFORE releasing the latch on the clustered index page: this + is to make sure that some thread will eventually undo the + modification corresponding to node->roll_ptr. */ + + /* fputs("--------------------undoing a previous version\n", + stderr); */ + + ret = FALSE; + } else { + node->row = row_build(ROW_COPY_DATA, clust_index, rec, + offsets, NULL, &node->ext, node->heap); + if (node->update) { + node->undo_row = dtuple_copy(node->row, node->heap); + row_upd_replace(node->undo_row, &node->undo_ext, + clust_index, node->update, node->heap); + } else { + node->undo_row = NULL; + node->undo_ext = NULL; + } + + btr_pcur_store_position(&(node->pcur), &mtr); + + ret = TRUE; + } + + btr_pcur_commit_specify_mtr(&(node->pcur), &mtr); + + if (UNIV_LIKELY_NULL(heap)) { + mem_heap_free(heap); + } + return(ret); +} + +/*************************************************************** +Fetches an undo log record and does the undo for the recorded operation. +If none left, or a partial rollback completed, returns control to the +parent node, which is always a query thread node. */ +static +ulint +row_undo( +/*=====*/ + /* out: DB_SUCCESS if operation successfully + completed, else error code */ + undo_node_t* node, /* in: row undo node */ + que_thr_t* thr) /* in: query thread */ +{ + ulint err; + trx_t* trx; + dulint roll_ptr; + ibool locked_data_dict; + + ut_ad(node && thr); + + trx = node->trx; + + if (node->state == UNDO_NODE_FETCH_NEXT) { + + node->undo_rec = trx_roll_pop_top_rec_of_trx(trx, + trx->roll_limit, + &roll_ptr, + node->heap); + if (!node->undo_rec) { + /* Rollback completed for this query thread */ + + thr->run_node = que_node_get_parent(node); + + return(DB_SUCCESS); + } + + node->roll_ptr = roll_ptr; + node->undo_no = trx_undo_rec_get_undo_no(node->undo_rec); + + if (trx_undo_roll_ptr_is_insert(roll_ptr)) { + + node->state = UNDO_NODE_INSERT; + } else { + node->state = UNDO_NODE_MODIFY; + } + + } else if (node->state == UNDO_NODE_PREV_VERS) { + + /* Undo should be done to the same clustered index record + again in this same rollback, restoring the previous version */ + + roll_ptr = node->new_roll_ptr; + + node->undo_rec = trx_undo_get_undo_rec_low(roll_ptr, + node->heap); + node->roll_ptr = roll_ptr; + node->undo_no = trx_undo_rec_get_undo_no(node->undo_rec); + + if (trx_undo_roll_ptr_is_insert(roll_ptr)) { + + node->state = UNDO_NODE_INSERT; + } else { + node->state = UNDO_NODE_MODIFY; + } + } + + /* Prevent DROP TABLE etc. while we are rolling back this row. + If we are doing a TABLE CREATE or some other dictionary operation, + then we already have dict_operation_lock locked in x-mode. Do not + try to lock again, because that would cause a hang. */ + + locked_data_dict = (trx->dict_operation_lock_mode == 0); + + if (locked_data_dict) { + + row_mysql_lock_data_dictionary(trx); + } + + if (node->state == UNDO_NODE_INSERT) { + + err = row_undo_ins(node); + + node->state = UNDO_NODE_FETCH_NEXT; + } else { + ut_ad(node->state == UNDO_NODE_MODIFY); + err = row_undo_mod(node, thr); + } + + if (locked_data_dict) { + + row_mysql_unlock_data_dictionary(trx); + } + + /* Do some cleanup */ + btr_pcur_close(&(node->pcur)); + + mem_heap_empty(node->heap); + + thr->run_node = node; + + return(err); +} + +/*************************************************************** +Undoes a row operation in a table. This is a high-level function used +in SQL execution graphs. */ +UNIV_INTERN +que_thr_t* +row_undo_step( +/*==========*/ + /* out: query thread to run next or NULL */ + que_thr_t* thr) /* in: query thread */ +{ + ulint err; + undo_node_t* node; + trx_t* trx; + + ut_ad(thr); + + srv_activity_count++; + + trx = thr_get_trx(thr); + + node = thr->run_node; + + ut_ad(que_node_get_type(node) == QUE_NODE_UNDO); + + err = row_undo(node, thr); + + trx->error_state = err; + + if (err != DB_SUCCESS) { + /* SQL error detected */ + + fprintf(stderr, "InnoDB: Fatal error %lu in rollback.\n", + (ulong) err); + + if (err == DB_OUT_OF_FILE_SPACE) { + fprintf(stderr, + "InnoDB: Error 13 means out of tablespace.\n" + "InnoDB: Consider increasing" + " your tablespace.\n"); + + exit(1); + } + + ut_error; + + return(NULL); + } + + return(thr); +} diff --git a/storage/xtradb/row/row0upd.c b/storage/xtradb/row/row0upd.c new file mode 100644 index 00000000000..740f1ee593d --- /dev/null +++ b/storage/xtradb/row/row0upd.c @@ -0,0 +1,2168 @@ +/***************************************************************************** + +Copyright (c) 1996, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/****************************************************** +Update of a row + +Created 12/27/1996 Heikki Tuuri +*******************************************************/ + +#include "row0upd.h" + +#ifdef UNIV_NONINL +#include "row0upd.ic" +#endif + +#include "dict0dict.h" +#include "dict0boot.h" +#include "dict0crea.h" +#include "mach0data.h" +#include "trx0undo.h" +#include "btr0btr.h" +#include "btr0cur.h" +#include "que0que.h" +#include "row0ext.h" +#include "row0ins.h" +#include "row0sel.h" +#include "row0row.h" +#include "rem0cmp.h" +#include "lock0lock.h" +#include "log0log.h" +#include "pars0sym.h" +#include "eval0eval.h" +#include "buf0lru.h" + + +/* What kind of latch and lock can we assume when the control comes to + ------------------------------------------------------------------- +an update node? +-------------- +Efficiency of massive updates would require keeping an x-latch on a +clustered index page through many updates, and not setting an explicit +x-lock on clustered index records, as they anyway will get an implicit +x-lock when they are updated. A problem is that the read nodes in the +graph should know that they must keep the latch when passing the control +up to the update node, and not set any record lock on the record which +will be updated. Another problem occurs if the execution is stopped, +as the kernel switches to another query thread, or the transaction must +wait for a lock. Then we should be able to release the latch and, maybe, +acquire an explicit x-lock on the record. + Because this seems too complicated, we conclude that the less +efficient solution of releasing all the latches when the control is +transferred to another node, and acquiring explicit x-locks, is better. */ + +/* How is a delete performed? If there is a delete without an +explicit cursor, i.e., a searched delete, there are at least +two different situations: +the implicit select cursor may run on (1) the clustered index or +on (2) a secondary index. The delete is performed by setting +the delete bit in the record and substituting the id of the +deleting transaction for the original trx id, and substituting a +new roll ptr for previous roll ptr. The old trx id and roll ptr +are saved in the undo log record. Thus, no physical changes occur +in the index tree structure at the time of the delete. Only +when the undo log is purged, the index records will be physically +deleted from the index trees. + +The query graph executing a searched delete would consist of +a delete node which has as a subtree a select subgraph. +The select subgraph should return a (persistent) cursor +in the clustered index, placed on page which is x-latched. +The delete node should look for all secondary index records for +this clustered index entry and mark them as deleted. When is +the x-latch freed? The most efficient way for performing a +searched delete is obviously to keep the x-latch for several +steps of query graph execution. */ + +/*************************************************************** +Checks if an update vector changes some of the first ordering fields of an +index record. This is only used in foreign key checks and we can assume +that index does not contain column prefixes. */ +static +ibool +row_upd_changes_first_fields_binary( +/*================================*/ + /* out: TRUE if changes */ + dtuple_t* entry, /* in: old value of index entry */ + dict_index_t* index, /* in: index of entry */ + const upd_t* update, /* in: update vector for the row */ + ulint n); /* in: how many first fields to check */ + + +/************************************************************************* +Checks if index currently is mentioned as a referenced index in a foreign +key constraint. */ +static +ibool +row_upd_index_is_referenced( +/*========================*/ + /* out: TRUE if referenced; NOTE that since + we do not hold dict_operation_lock + when leaving the function, it may be that + the referencing table has been dropped when + we leave this function: this function is only + for heuristic use! */ + dict_index_t* index, /* in: index */ + trx_t* trx) /* in: transaction */ +{ + dict_table_t* table = index->table; + dict_foreign_t* foreign; + ibool froze_data_dict = FALSE; + ibool is_referenced = FALSE; + + if (!UT_LIST_GET_FIRST(table->referenced_list)) { + + return(FALSE); + } + + if (trx->dict_operation_lock_mode == 0) { + row_mysql_freeze_data_dictionary(trx); + froze_data_dict = TRUE; + } + + foreign = UT_LIST_GET_FIRST(table->referenced_list); + + while (foreign) { + if (foreign->referenced_index == index) { + + is_referenced = TRUE; + goto func_exit; + } + + foreign = UT_LIST_GET_NEXT(referenced_list, foreign); + } + +func_exit: + if (froze_data_dict) { + row_mysql_unfreeze_data_dictionary(trx); + } + + return(is_referenced); +} + +/************************************************************************* +Checks if possible foreign key constraints hold after a delete of the record +under pcur. NOTE that this function will temporarily commit mtr and lose the +pcur position! */ +static +ulint +row_upd_check_references_constraints( +/*=================================*/ + /* out: DB_SUCCESS or an error code */ + upd_node_t* node, /* in: row update node */ + btr_pcur_t* pcur, /* in: cursor positioned on a record; NOTE: the + cursor position is lost in this function! */ + dict_table_t* table, /* in: table in question */ + dict_index_t* index, /* in: index of the cursor */ + ulint* offsets,/* in/out: rec_get_offsets(pcur.rec, index) */ + que_thr_t* thr, /* in: query thread */ + mtr_t* mtr) /* in: mtr */ +{ + dict_foreign_t* foreign; + mem_heap_t* heap; + dtuple_t* entry; + trx_t* trx; + const rec_t* rec; + ulint n_ext; + ulint err; + ibool got_s_lock = FALSE; + + if (UT_LIST_GET_FIRST(table->referenced_list) == NULL) { + + return(DB_SUCCESS); + } + + trx = thr_get_trx(thr); + + rec = btr_pcur_get_rec(pcur); + ut_ad(rec_offs_validate(rec, index, offsets)); + + heap = mem_heap_create(500); + + entry = row_rec_to_index_entry(ROW_COPY_DATA, rec, index, offsets, + &n_ext, heap); + + mtr_commit(mtr); + + mtr_start(mtr); + + if (trx->dict_operation_lock_mode == 0) { + got_s_lock = TRUE; + + row_mysql_freeze_data_dictionary(trx); + } + + foreign = UT_LIST_GET_FIRST(table->referenced_list); + + while (foreign) { + /* Note that we may have an update which updates the index + record, but does NOT update the first fields which are + referenced in a foreign key constraint. Then the update does + NOT break the constraint. */ + + if (foreign->referenced_index == index + && (node->is_delete + || row_upd_changes_first_fields_binary( + entry, index, node->update, + foreign->n_fields))) { + + if (foreign->foreign_table == NULL) { + dict_table_get(foreign->foreign_table_name, + FALSE); + } + + if (foreign->foreign_table) { + mutex_enter(&(dict_sys->mutex)); + + (foreign->foreign_table + ->n_foreign_key_checks_running)++; + + mutex_exit(&(dict_sys->mutex)); + } + + /* NOTE that if the thread ends up waiting for a lock + we will release dict_operation_lock temporarily! + But the counter on the table protects 'foreign' from + being dropped while the check is running. */ + + err = row_ins_check_foreign_constraint( + FALSE, foreign, table, entry, thr); + + if (foreign->foreign_table) { + mutex_enter(&(dict_sys->mutex)); + + ut_a(foreign->foreign_table + ->n_foreign_key_checks_running > 0); + + (foreign->foreign_table + ->n_foreign_key_checks_running)--; + + mutex_exit(&(dict_sys->mutex)); + } + + if (err != DB_SUCCESS) { + + goto func_exit; + } + } + + foreign = UT_LIST_GET_NEXT(referenced_list, foreign); + } + + err = DB_SUCCESS; + +func_exit: + if (got_s_lock) { + row_mysql_unfreeze_data_dictionary(trx); + } + + mem_heap_free(heap); + + return(err); +} + +/************************************************************************* +Creates an update node for a query graph. */ +UNIV_INTERN +upd_node_t* +upd_node_create( +/*============*/ + /* out, own: update node */ + mem_heap_t* heap) /* in: mem heap where created */ +{ + upd_node_t* node; + + node = mem_heap_alloc(heap, sizeof(upd_node_t)); + node->common.type = QUE_NODE_UPDATE; + + node->state = UPD_NODE_UPDATE_CLUSTERED; + node->in_mysql_interface = FALSE; + + node->row = NULL; + node->ext = NULL; + node->upd_row = NULL; + node->upd_ext = NULL; + node->index = NULL; + node->update = NULL; + + node->foreign = NULL; + node->cascade_heap = NULL; + node->cascade_node = NULL; + + node->select = NULL; + + node->heap = mem_heap_create(128); + node->magic_n = UPD_NODE_MAGIC_N; + + node->cmpl_info = 0; + + return(node); +} + +/************************************************************************* +Updates the trx id and roll ptr field in a clustered index record in database +recovery. */ +UNIV_INTERN +void +row_upd_rec_sys_fields_in_recovery( +/*===============================*/ + rec_t* rec, /* in/out: record */ + page_zip_des_t* page_zip,/* in/out: compressed page, or NULL */ + const ulint* offsets,/* in: array returned by rec_get_offsets() */ + ulint pos, /* in: TRX_ID position in rec */ + dulint trx_id, /* in: transaction id */ + dulint roll_ptr)/* in: roll ptr of the undo log record */ +{ + ut_ad(rec_offs_validate(rec, NULL, offsets)); + + if (UNIV_LIKELY_NULL(page_zip)) { + page_zip_write_trx_id_and_roll_ptr( + page_zip, rec, offsets, pos, trx_id, roll_ptr); + } else { + byte* field; + ulint len; + + field = rec_get_nth_field(rec, offsets, pos, &len); + ut_ad(len == DATA_TRX_ID_LEN); +#if DATA_TRX_ID + 1 != DATA_ROLL_PTR +# error "DATA_TRX_ID + 1 != DATA_ROLL_PTR" +#endif + trx_write_trx_id(field, trx_id); + trx_write_roll_ptr(field + DATA_TRX_ID_LEN, roll_ptr); + } +} + +/************************************************************************* +Sets the trx id or roll ptr field of a clustered index entry. */ +UNIV_INTERN +void +row_upd_index_entry_sys_field( +/*==========================*/ + const dtuple_t* entry, /* in: index entry, where the memory buffers + for sys fields are already allocated: + the function just copies the new values to + them */ + dict_index_t* index, /* in: clustered index */ + ulint type, /* in: DATA_TRX_ID or DATA_ROLL_PTR */ + dulint val) /* in: value to write */ +{ + dfield_t* dfield; + byte* field; + ulint pos; + + ut_ad(dict_index_is_clust(index)); + + pos = dict_index_get_sys_col_pos(index, type); + + dfield = dtuple_get_nth_field(entry, pos); + field = dfield_get_data(dfield); + + if (type == DATA_TRX_ID) { + trx_write_trx_id(field, val); + } else { + ut_ad(type == DATA_ROLL_PTR); + trx_write_roll_ptr(field, val); + } +} + +/*************************************************************** +Returns TRUE if row update changes size of some field in index or if some +field to be updated is stored externally in rec or update. */ +UNIV_INTERN +ibool +row_upd_changes_field_size_or_external( +/*===================================*/ + /* out: TRUE if the update changes the size of + some field in index or the field is external + in rec or update */ + dict_index_t* index, /* in: index */ + const ulint* offsets,/* in: rec_get_offsets(rec, index) */ + const upd_t* update) /* in: update vector */ +{ + const upd_field_t* upd_field; + const dfield_t* new_val; + ulint old_len; + ulint new_len; + ulint n_fields; + ulint i; + + ut_ad(rec_offs_validate(NULL, index, offsets)); + n_fields = upd_get_n_fields(update); + + for (i = 0; i < n_fields; i++) { + upd_field = upd_get_nth_field(update, i); + + new_val = &(upd_field->new_val); + new_len = dfield_get_len(new_val); + + if (dfield_is_null(new_val) && !rec_offs_comp(offsets)) { + /* A bug fixed on Dec 31st, 2004: we looked at the + SQL NULL size from the wrong field! We may backport + this fix also to 4.0. The merge to 5.0 will be made + manually immediately after we commit this to 4.1. */ + + new_len = dict_col_get_sql_null_size( + dict_index_get_nth_col(index, + upd_field->field_no)); + } + + old_len = rec_offs_nth_size(offsets, upd_field->field_no); + + if (rec_offs_comp(offsets) + && rec_offs_nth_sql_null(offsets, + upd_field->field_no)) { + /* Note that in the compact table format, for a + variable length field, an SQL NULL will use zero + bytes in the offset array at the start of the physical + record, but a zero-length value (empty string) will + use one byte! Thus, we cannot use update-in-place + if we update an SQL NULL varchar to an empty string! */ + + old_len = UNIV_SQL_NULL; + } + + if (dfield_is_ext(new_val) || old_len != new_len + || rec_offs_nth_extern(offsets, upd_field->field_no)) { + + return(TRUE); + } + } + + return(FALSE); +} + +/*************************************************************** +Replaces the new column values stored in the update vector to the record +given. No field size changes are allowed. */ +UNIV_INTERN +void +row_upd_rec_in_place( +/*=================*/ + rec_t* rec, /* in/out: record where replaced */ + dict_index_t* index, /* in: the index the record belongs to */ + const ulint* offsets,/* in: array returned by rec_get_offsets() */ + const upd_t* update, /* in: update vector */ + page_zip_des_t* page_zip)/* in: compressed page with enough space + available, or NULL */ +{ + const upd_field_t* upd_field; + const dfield_t* new_val; + ulint n_fields; + ulint i; + + ut_ad(rec_offs_validate(rec, index, offsets)); + + if (rec_offs_comp(offsets)) { + rec_set_info_bits_new(rec, update->info_bits); + } else { + rec_set_info_bits_old(rec, update->info_bits); + } + + n_fields = upd_get_n_fields(update); + + for (i = 0; i < n_fields; i++) { + upd_field = upd_get_nth_field(update, i); + new_val = &(upd_field->new_val); + ut_ad(!dfield_is_ext(new_val) == + !rec_offs_nth_extern(offsets, upd_field->field_no)); + + rec_set_nth_field(rec, offsets, upd_field->field_no, + dfield_get_data(new_val), + dfield_get_len(new_val)); + } + + if (UNIV_LIKELY_NULL(page_zip)) { + page_zip_write_rec(page_zip, rec, index, offsets, 0); + } +} + +/************************************************************************* +Writes into the redo log the values of trx id and roll ptr and enough info +to determine their positions within a clustered index record. */ +UNIV_INTERN +byte* +row_upd_write_sys_vals_to_log( +/*==========================*/ + /* out: new pointer to mlog */ + dict_index_t* index, /* in: clustered index */ + trx_t* trx, /* in: transaction */ + dulint roll_ptr,/* in: roll ptr of the undo log record */ + byte* log_ptr,/* pointer to a buffer of size > 20 opened + in mlog */ + mtr_t* mtr __attribute__((unused))) /* in: mtr */ +{ + ut_ad(dict_index_is_clust(index)); + ut_ad(mtr); + + log_ptr += mach_write_compressed(log_ptr, + dict_index_get_sys_col_pos( + index, DATA_TRX_ID)); + + trx_write_roll_ptr(log_ptr, roll_ptr); + log_ptr += DATA_ROLL_PTR_LEN; + + log_ptr += mach_dulint_write_compressed(log_ptr, trx->id); + + return(log_ptr); +} + +/************************************************************************* +Parses the log data of system field values. */ +UNIV_INTERN +byte* +row_upd_parse_sys_vals( +/*===================*/ + /* out: log data end or NULL */ + byte* ptr, /* in: buffer */ + byte* end_ptr,/* in: buffer end */ + ulint* pos, /* out: TRX_ID position in record */ + dulint* trx_id, /* out: trx id */ + dulint* roll_ptr)/* out: roll ptr */ +{ + ptr = mach_parse_compressed(ptr, end_ptr, pos); + + if (ptr == NULL) { + + return(NULL); + } + + if (end_ptr < ptr + DATA_ROLL_PTR_LEN) { + + return(NULL); + } + + *roll_ptr = trx_read_roll_ptr(ptr); + ptr += DATA_ROLL_PTR_LEN; + + ptr = mach_dulint_parse_compressed(ptr, end_ptr, trx_id); + + return(ptr); +} + +/*************************************************************** +Writes to the redo log the new values of the fields occurring in the index. */ +UNIV_INTERN +void +row_upd_index_write_log( +/*====================*/ + const upd_t* update, /* in: update vector */ + byte* log_ptr,/* in: pointer to mlog buffer: must + contain at least MLOG_BUF_MARGIN bytes + of free space; the buffer is closed + within this function */ + mtr_t* mtr) /* in: mtr into whose log to write */ +{ + const upd_field_t* upd_field; + const dfield_t* new_val; + ulint len; + ulint n_fields; + byte* buf_end; + ulint i; + + n_fields = upd_get_n_fields(update); + + buf_end = log_ptr + MLOG_BUF_MARGIN; + + mach_write_to_1(log_ptr, update->info_bits); + log_ptr++; + log_ptr += mach_write_compressed(log_ptr, n_fields); + + for (i = 0; i < n_fields; i++) { + +#if MLOG_BUF_MARGIN <= 30 +# error "MLOG_BUF_MARGIN <= 30" +#endif + + if (log_ptr + 30 > buf_end) { + mlog_close(mtr, log_ptr); + + log_ptr = mlog_open(mtr, MLOG_BUF_MARGIN); + buf_end = log_ptr + MLOG_BUF_MARGIN; + } + + upd_field = upd_get_nth_field(update, i); + + new_val = &(upd_field->new_val); + + len = dfield_get_len(new_val); + + log_ptr += mach_write_compressed(log_ptr, upd_field->field_no); + log_ptr += mach_write_compressed(log_ptr, len); + + if (len != UNIV_SQL_NULL) { + if (log_ptr + len < buf_end) { + memcpy(log_ptr, dfield_get_data(new_val), len); + + log_ptr += len; + } else { + mlog_close(mtr, log_ptr); + + mlog_catenate_string(mtr, + dfield_get_data(new_val), + len); + + log_ptr = mlog_open(mtr, MLOG_BUF_MARGIN); + buf_end = log_ptr + MLOG_BUF_MARGIN; + } + } + } + + mlog_close(mtr, log_ptr); +} + +/************************************************************************* +Parses the log data written by row_upd_index_write_log. */ +UNIV_INTERN +byte* +row_upd_index_parse( +/*================*/ + /* out: log data end or NULL */ + byte* ptr, /* in: buffer */ + byte* end_ptr,/* in: buffer end */ + mem_heap_t* heap, /* in: memory heap where update vector is + built */ + upd_t** update_out)/* out: update vector */ +{ + upd_t* update; + upd_field_t* upd_field; + dfield_t* new_val; + ulint len; + ulint n_fields; + ulint info_bits; + ulint i; + + if (end_ptr < ptr + 1) { + + return(NULL); + } + + info_bits = mach_read_from_1(ptr); + ptr++; + ptr = mach_parse_compressed(ptr, end_ptr, &n_fields); + + if (ptr == NULL) { + + return(NULL); + } + + update = upd_create(n_fields, heap); + update->info_bits = info_bits; + + for (i = 0; i < n_fields; i++) { + ulint field_no; + upd_field = upd_get_nth_field(update, i); + new_val = &(upd_field->new_val); + + ptr = mach_parse_compressed(ptr, end_ptr, &field_no); + + if (ptr == NULL) { + + return(NULL); + } + + upd_field->field_no = field_no; + + ptr = mach_parse_compressed(ptr, end_ptr, &len); + + if (ptr == NULL) { + + return(NULL); + } + + if (len != UNIV_SQL_NULL) { + + if (end_ptr < ptr + len) { + + return(NULL); + } + + dfield_set_data(new_val, + mem_heap_dup(heap, ptr, len), len); + ptr += len; + } else { + dfield_set_null(new_val); + } + } + + *update_out = update; + + return(ptr); +} + +/******************************************************************* +Builds an update vector from those fields which in a secondary index entry +differ from a record that has the equal ordering fields. NOTE: we compare +the fields as binary strings! */ +UNIV_INTERN +upd_t* +row_upd_build_sec_rec_difference_binary( +/*====================================*/ + /* out, own: update vector of differing + fields */ + dict_index_t* index, /* in: index */ + const dtuple_t* entry, /* in: entry to insert */ + const rec_t* rec, /* in: secondary index record */ + trx_t* trx, /* in: transaction */ + mem_heap_t* heap) /* in: memory heap from which allocated */ +{ + upd_field_t* upd_field; + const dfield_t* dfield; + const byte* data; + ulint len; + upd_t* update; + ulint n_diff; + ulint i; + ulint offsets_[REC_OFFS_SMALL_SIZE]; + const ulint* offsets; + rec_offs_init(offsets_); + + /* This function is used only for a secondary index */ + ut_a(!dict_index_is_clust(index)); + + update = upd_create(dtuple_get_n_fields(entry), heap); + + n_diff = 0; + offsets = rec_get_offsets(rec, index, offsets_, + ULINT_UNDEFINED, &heap); + + for (i = 0; i < dtuple_get_n_fields(entry); i++) { + + data = rec_get_nth_field(rec, offsets, i, &len); + + dfield = dtuple_get_nth_field(entry, i); + + /* NOTE that it may be that len != dfield_get_len(dfield) if we + are updating in a character set and collation where strings of + different length can be equal in an alphabetical comparison, + and also in the case where we have a column prefix index + and the last characters in the index field are spaces; the + latter case probably caused the assertion failures reported at + row0upd.c line 713 in versions 4.0.14 - 4.0.16. */ + + /* NOTE: we compare the fields as binary strings! + (No collation) */ + + if (!dfield_data_is_binary_equal(dfield, len, data)) { + + upd_field = upd_get_nth_field(update, n_diff); + + dfield_copy(&(upd_field->new_val), dfield); + + upd_field_set_field_no(upd_field, i, index, trx); + + n_diff++; + } + } + + update->n_fields = n_diff; + + return(update); +} + +/******************************************************************* +Builds an update vector from those fields, excluding the roll ptr and +trx id fields, which in an index entry differ from a record that has +the equal ordering fields. NOTE: we compare the fields as binary strings! */ +UNIV_INTERN +upd_t* +row_upd_build_difference_binary( +/*============================*/ + /* out, own: update vector of differing + fields, excluding roll ptr and trx id */ + dict_index_t* index, /* in: clustered index */ + const dtuple_t* entry, /* in: entry to insert */ + const rec_t* rec, /* in: clustered index record */ + trx_t* trx, /* in: transaction */ + mem_heap_t* heap) /* in: memory heap from which allocated */ +{ + upd_field_t* upd_field; + const dfield_t* dfield; + const byte* data; + ulint len; + upd_t* update; + ulint n_diff; + ulint roll_ptr_pos; + ulint trx_id_pos; + ulint i; + ulint offsets_[REC_OFFS_NORMAL_SIZE]; + const ulint* offsets; + rec_offs_init(offsets_); + + /* This function is used only for a clustered index */ + ut_a(dict_index_is_clust(index)); + + update = upd_create(dtuple_get_n_fields(entry), heap); + + n_diff = 0; + + roll_ptr_pos = dict_index_get_sys_col_pos(index, DATA_ROLL_PTR); + trx_id_pos = dict_index_get_sys_col_pos(index, DATA_TRX_ID); + + offsets = rec_get_offsets(rec, index, offsets_, + ULINT_UNDEFINED, &heap); + + for (i = 0; i < dtuple_get_n_fields(entry); i++) { + + data = rec_get_nth_field(rec, offsets, i, &len); + + dfield = dtuple_get_nth_field(entry, i); + + /* NOTE: we compare the fields as binary strings! + (No collation) */ + + if (i == trx_id_pos || i == roll_ptr_pos) { + + goto skip_compare; + } + + if (UNIV_UNLIKELY(!dfield_is_ext(dfield) + != !rec_offs_nth_extern(offsets, i)) + || !dfield_data_is_binary_equal(dfield, len, data)) { + + upd_field = upd_get_nth_field(update, n_diff); + + dfield_copy(&(upd_field->new_val), dfield); + + upd_field_set_field_no(upd_field, i, index, trx); + + n_diff++; + } +skip_compare: + ; + } + + update->n_fields = n_diff; + + return(update); +} + +/*************************************************************** +Fetch a prefix of an externally stored column. This is similar +to row_ext_lookup(), but the row_ext_t holds the old values +of the column and must not be poisoned with the new values. */ +static +byte* +row_upd_ext_fetch( +/*==============*/ + /* out: BLOB prefix */ + const byte* data, /* in: 'internally' stored part of the + field containing also the reference to + the external part */ + ulint local_len, /* in: length of data, in bytes */ + ulint zip_size, /* in: nonzero=compressed BLOB + page size, zero for uncompressed + BLOBs */ + ulint* len, /* in: length of prefix to fetch; + out: fetched length of the prefix */ + mem_heap_t* heap) /* in: heap where to allocate */ +{ + byte* buf = mem_heap_alloc(heap, *len); + + *len = btr_copy_externally_stored_field_prefix(buf, *len, + zip_size, + data, local_len); + /* We should never update records containing a half-deleted BLOB. */ + ut_a(*len); + + return(buf); +} + +/*************************************************************** +Replaces the new column value stored in the update vector in +the given index entry field. */ +static +void +row_upd_index_replace_new_col_val( +/*==============================*/ + dfield_t* dfield, /* in/out: data field + of the index entry */ + const dict_field_t* field, /* in: index field */ + const dict_col_t* col, /* in: field->col */ + const upd_field_t* uf, /* in: update field */ + mem_heap_t* heap, /* in: memory heap for allocating + and copying the new value */ + ulint zip_size)/* in: compressed page + size of the table, or 0 */ +{ + ulint len; + const byte* data; + + dfield_copy_data(dfield, &uf->new_val); + + if (dfield_is_null(dfield)) { + return; + } + + len = dfield_get_len(dfield); + data = dfield_get_data(dfield); + + if (field->prefix_len > 0) { + ibool fetch_ext = dfield_is_ext(dfield) + && len < (ulint) field->prefix_len + + BTR_EXTERN_FIELD_REF_SIZE; + + if (fetch_ext) { + ulint l = len; + + len = field->prefix_len; + + data = row_upd_ext_fetch(data, l, zip_size, + &len, heap); + } + + len = dtype_get_at_most_n_mbchars(col->prtype, + col->mbminlen, col->mbmaxlen, + field->prefix_len, len, + (const char*) data); + + dfield_set_data(dfield, data, len); + + if (!fetch_ext) { + dfield_dup(dfield, heap); + } + + return; + } + + switch (uf->orig_len) { + byte* buf; + case BTR_EXTERN_FIELD_REF_SIZE: + /* Restore the original locally stored + part of the column. In the undo log, + InnoDB writes a longer prefix of externally + stored columns, so that column prefixes + in secondary indexes can be reconstructed. */ + dfield_set_data(dfield, + data + len - BTR_EXTERN_FIELD_REF_SIZE, + BTR_EXTERN_FIELD_REF_SIZE); + dfield_set_ext(dfield); + /* fall through */ + case 0: + dfield_dup(dfield, heap); + break; + default: + /* Reconstruct the original locally + stored part of the column. The data + will have to be copied. */ + ut_a(uf->orig_len > BTR_EXTERN_FIELD_REF_SIZE); + buf = mem_heap_alloc(heap, uf->orig_len); + /* Copy the locally stored prefix. */ + memcpy(buf, data, + uf->orig_len - BTR_EXTERN_FIELD_REF_SIZE); + /* Copy the BLOB pointer. */ + memcpy(buf + uf->orig_len - BTR_EXTERN_FIELD_REF_SIZE, + data + len - BTR_EXTERN_FIELD_REF_SIZE, + BTR_EXTERN_FIELD_REF_SIZE); + + dfield_set_data(dfield, buf, uf->orig_len); + dfield_set_ext(dfield); + break; + } +} + +/*************************************************************** +Replaces the new column values stored in the update vector to the index entry +given. */ +UNIV_INTERN +void +row_upd_index_replace_new_col_vals_index_pos( +/*=========================================*/ + dtuple_t* entry, /* in/out: index entry where replaced; + the clustered index record must be + covered by a lock or a page latch to + prevent deletion (rollback or purge) */ + dict_index_t* index, /* in: index; NOTE that this may also be a + non-clustered index */ + const upd_t* update, /* in: an update vector built for the index so + that the field number in an upd_field is the + index position */ + ibool order_only, + /* in: if TRUE, limit the replacement to + ordering fields of index; note that this + does not work for non-clustered indexes. */ + mem_heap_t* heap) /* in: memory heap for allocating and + copying the new values */ +{ + ulint i; + ulint n_fields; + const ulint zip_size = dict_table_zip_size(index->table); + + ut_ad(index); + + dtuple_set_info_bits(entry, update->info_bits); + + if (order_only) { + n_fields = dict_index_get_n_unique(index); + } else { + n_fields = dict_index_get_n_fields(index); + } + + for (i = 0; i < n_fields; i++) { + const dict_field_t* field; + const dict_col_t* col; + const upd_field_t* uf; + + field = dict_index_get_nth_field(index, i); + col = dict_field_get_col(field); + uf = upd_get_field_by_field_no(update, i); + + if (uf) { + row_upd_index_replace_new_col_val( + dtuple_get_nth_field(entry, i), + field, col, uf, heap, zip_size); + } + } +} + +/*************************************************************** +Replaces the new column values stored in the update vector to the index entry +given. */ +UNIV_INTERN +void +row_upd_index_replace_new_col_vals( +/*===============================*/ + dtuple_t* entry, /* in/out: index entry where replaced; + the clustered index record must be + covered by a lock or a page latch to + prevent deletion (rollback or purge) */ + dict_index_t* index, /* in: index; NOTE that this may also be a + non-clustered index */ + const upd_t* update, /* in: an update vector built for the + CLUSTERED index so that the field number in + an upd_field is the clustered index position */ + mem_heap_t* heap) /* in: memory heap for allocating and + copying the new values */ +{ + ulint i; + const dict_index_t* clust_index + = dict_table_get_first_index(index->table); + const ulint zip_size + = dict_table_zip_size(index->table); + + dtuple_set_info_bits(entry, update->info_bits); + + for (i = 0; i < dict_index_get_n_fields(index); i++) { + const dict_field_t* field; + const dict_col_t* col; + const upd_field_t* uf; + + field = dict_index_get_nth_field(index, i); + col = dict_field_get_col(field); + uf = upd_get_field_by_field_no( + update, dict_col_get_clust_pos(col, clust_index)); + + if (uf) { + row_upd_index_replace_new_col_val( + dtuple_get_nth_field(entry, i), + field, col, uf, heap, zip_size); + } + } +} + +/*************************************************************** +Replaces the new column values stored in the update vector. */ +UNIV_INTERN +void +row_upd_replace( +/*============*/ + dtuple_t* row, /* in/out: row where replaced, + indexed by col_no; + the clustered index record must be + covered by a lock or a page latch to + prevent deletion (rollback or purge) */ + row_ext_t** ext, /* out, own: NULL, or externally + stored column prefixes */ + const dict_index_t* index, /* in: clustered index */ + const upd_t* update, /* in: an update vector built for the + clustered index */ + mem_heap_t* heap) /* in: memory heap */ +{ + ulint col_no; + ulint i; + ulint n_cols; + ulint n_ext_cols; + ulint* ext_cols; + const dict_table_t* table; + + ut_ad(row); + ut_ad(ext); + ut_ad(index); + ut_ad(dict_index_is_clust(index)); + ut_ad(update); + ut_ad(heap); + + n_cols = dtuple_get_n_fields(row); + table = index->table; + ut_ad(n_cols == dict_table_get_n_cols(table)); + + ext_cols = mem_heap_alloc(heap, n_cols * sizeof *ext_cols); + n_ext_cols = 0; + + dtuple_set_info_bits(row, update->info_bits); + + for (col_no = 0; col_no < n_cols; col_no++) { + + const dict_col_t* col + = dict_table_get_nth_col(table, col_no); + const ulint clust_pos + = dict_col_get_clust_pos(col, index); + dfield_t* dfield; + + if (UNIV_UNLIKELY(clust_pos == ULINT_UNDEFINED)) { + + continue; + } + + dfield = dtuple_get_nth_field(row, col_no); + + for (i = 0; i < upd_get_n_fields(update); i++) { + + const upd_field_t* upd_field + = upd_get_nth_field(update, i); + + if (upd_field->field_no != clust_pos) { + + continue; + } + + dfield_copy_data(dfield, &upd_field->new_val); + break; + } + + if (dfield_is_ext(dfield) && col->ord_part) { + ext_cols[n_ext_cols++] = col_no; + } + } + + if (n_ext_cols) { + *ext = row_ext_create(n_ext_cols, ext_cols, row, + dict_table_zip_size(table), heap); + } else { + *ext = NULL; + } +} + +/*************************************************************** +Checks if an update vector changes an ordering field of an index record. +This function is fast if the update vector is short or the number of ordering +fields in the index is small. Otherwise, this can be quadratic. +NOTE: we compare the fields as binary strings! */ +UNIV_INTERN +ibool +row_upd_changes_ord_field_binary( +/*=============================*/ + /* out: TRUE if update vector changes + an ordering field in the index record; + NOTE: the fields are compared as binary + strings */ + const dtuple_t* row, /* in: old value of row, or NULL if the + row and the data values in update are not + known when this function is called, e.g., at + compile time */ + dict_index_t* index, /* in: index of the record */ + const upd_t* update) /* in: update vector for the row; NOTE: the + field numbers in this MUST be clustered index + positions! */ +{ + ulint n_unique; + ulint n_upd_fields; + ulint i, j; + dict_index_t* clust_index; + + ut_ad(update && index); + + n_unique = dict_index_get_n_unique(index); + n_upd_fields = upd_get_n_fields(update); + + clust_index = dict_table_get_first_index(index->table); + + for (i = 0; i < n_unique; i++) { + + const dict_field_t* ind_field; + const dict_col_t* col; + ulint col_pos; + ulint col_no; + + ind_field = dict_index_get_nth_field(index, i); + col = dict_field_get_col(ind_field); + col_pos = dict_col_get_clust_pos(col, clust_index); + col_no = dict_col_get_no(col); + + for (j = 0; j < n_upd_fields; j++) { + + const upd_field_t* upd_field + = upd_get_nth_field(update, j); + + /* Note that if the index field is a column prefix + then it may be that row does not contain an externally + stored part of the column value, and we cannot compare + the datas */ + + if (col_pos == upd_field->field_no + && (row == NULL + || ind_field->prefix_len > 0 + || !dfield_datas_are_binary_equal( + dtuple_get_nth_field(row, col_no), + &(upd_field->new_val)))) { + + return(TRUE); + } + } + } + + return(FALSE); +} + +/*************************************************************** +Checks if an update vector changes an ordering field of an index record. +NOTE: we compare the fields as binary strings! */ +UNIV_INTERN +ibool +row_upd_changes_some_index_ord_field_binary( +/*========================================*/ + /* out: TRUE if update vector + may change an ordering field + in an index record */ + const dict_table_t* table, /* in: table */ + const upd_t* update) /* in: update vector for the row */ +{ + upd_field_t* upd_field; + dict_index_t* index; + ulint i; + + index = dict_table_get_first_index(table); + + for (i = 0; i < upd_get_n_fields(update); i++) { + + upd_field = upd_get_nth_field(update, i); + + if (dict_field_get_col(dict_index_get_nth_field( + index, upd_field->field_no)) + ->ord_part) { + + return(TRUE); + } + } + + return(FALSE); +} + +/*************************************************************** +Checks if an update vector changes some of the first ordering fields of an +index record. This is only used in foreign key checks and we can assume +that index does not contain column prefixes. */ +static +ibool +row_upd_changes_first_fields_binary( +/*================================*/ + /* out: TRUE if changes */ + dtuple_t* entry, /* in: index entry */ + dict_index_t* index, /* in: index of entry */ + const upd_t* update, /* in: update vector for the row */ + ulint n) /* in: how many first fields to check */ +{ + ulint n_upd_fields; + ulint i, j; + dict_index_t* clust_index; + + ut_ad(update && index); + ut_ad(n <= dict_index_get_n_fields(index)); + + n_upd_fields = upd_get_n_fields(update); + clust_index = dict_table_get_first_index(index->table); + + for (i = 0; i < n; i++) { + + const dict_field_t* ind_field; + const dict_col_t* col; + ulint col_pos; + + ind_field = dict_index_get_nth_field(index, i); + col = dict_field_get_col(ind_field); + col_pos = dict_col_get_clust_pos(col, clust_index); + + ut_a(ind_field->prefix_len == 0); + + for (j = 0; j < n_upd_fields; j++) { + + upd_field_t* upd_field + = upd_get_nth_field(update, j); + + if (col_pos == upd_field->field_no + && !dfield_datas_are_binary_equal( + dtuple_get_nth_field(entry, i), + &(upd_field->new_val))) { + + return(TRUE); + } + } + } + + return(FALSE); +} + +/************************************************************************* +Copies the column values from a record. */ +UNIV_INLINE +void +row_upd_copy_columns( +/*=================*/ + rec_t* rec, /* in: record in a clustered index */ + const ulint* offsets,/* in: array returned by rec_get_offsets() */ + sym_node_t* column) /* in: first column in a column list, or + NULL */ +{ + byte* data; + ulint len; + + while (column) { + data = rec_get_nth_field(rec, offsets, + column->field_nos[SYM_CLUST_FIELD_NO], + &len); + if (len == UNIV_SQL_NULL) { + len = UNIV_SQL_NULL; + } + eval_node_copy_and_alloc_val(column, data, len); + + column = UT_LIST_GET_NEXT(col_var_list, column); + } +} + +/************************************************************************* +Calculates the new values for fields to update. Note that row_upd_copy_columns +must have been called first. */ +UNIV_INLINE +void +row_upd_eval_new_vals( +/*==================*/ + upd_t* update) /* in/out: update vector */ +{ + que_node_t* exp; + upd_field_t* upd_field; + ulint n_fields; + ulint i; + + n_fields = upd_get_n_fields(update); + + for (i = 0; i < n_fields; i++) { + upd_field = upd_get_nth_field(update, i); + + exp = upd_field->exp; + + eval_exp(exp); + + dfield_copy_data(&(upd_field->new_val), que_node_get_val(exp)); + } +} + +/*************************************************************** +Stores to the heap the row on which the node->pcur is positioned. */ +static +void +row_upd_store_row( +/*==============*/ + upd_node_t* node) /* in: row update node */ +{ + dict_index_t* clust_index; + rec_t* rec; + mem_heap_t* heap = NULL; + ulint offsets_[REC_OFFS_NORMAL_SIZE]; + const ulint* offsets; + rec_offs_init(offsets_); + + ut_ad(node->pcur->latch_mode != BTR_NO_LATCHES); + + if (node->row != NULL) { + mem_heap_empty(node->heap); + } + + clust_index = dict_table_get_first_index(node->table); + + rec = btr_pcur_get_rec(node->pcur); + + offsets = rec_get_offsets(rec, clust_index, offsets_, + ULINT_UNDEFINED, &heap); + node->row = row_build(ROW_COPY_DATA, clust_index, rec, offsets, + NULL, &node->ext, node->heap); + if (node->is_delete) { + node->upd_row = NULL; + node->upd_ext = NULL; + } else { + node->upd_row = dtuple_copy(node->row, node->heap); + row_upd_replace(node->upd_row, &node->upd_ext, + clust_index, node->update, node->heap); + } + + if (UNIV_LIKELY_NULL(heap)) { + mem_heap_free(heap); + } +} + +/*************************************************************** +Updates a secondary index entry of a row. */ +static +ulint +row_upd_sec_index_entry( +/*====================*/ + /* out: DB_SUCCESS if operation successfully + completed, else error code or DB_LOCK_WAIT */ + upd_node_t* node, /* in: row update node */ + que_thr_t* thr) /* in: query thread */ +{ + ibool check_ref; + ibool found; + dict_index_t* index; + dtuple_t* entry; + btr_pcur_t pcur; + btr_cur_t* btr_cur; + mem_heap_t* heap; + rec_t* rec; + ulint err = DB_SUCCESS; + mtr_t mtr; + trx_t* trx = thr_get_trx(thr); + + index = node->index; + + check_ref = row_upd_index_is_referenced(index, trx); + + heap = mem_heap_create(1024); + + /* Build old index entry */ + entry = row_build_index_entry(node->row, node->ext, index, heap); + ut_a(entry); + + log_free_check(); + mtr_start(&mtr); + + found = row_search_index_entry(index, entry, BTR_MODIFY_LEAF, &pcur, + &mtr); + btr_cur = btr_pcur_get_btr_cur(&pcur); + + rec = btr_cur_get_rec(btr_cur); + + if (UNIV_UNLIKELY(!found)) { + fputs("InnoDB: error in sec index entry update in\n" + "InnoDB: ", stderr); + dict_index_name_print(stderr, trx, index); + fputs("\n" + "InnoDB: tuple ", stderr); + dtuple_print(stderr, entry); + fputs("\n" + "InnoDB: record ", stderr); + rec_print(stderr, rec, index); + putc('\n', stderr); + + trx_print(stderr, trx, 0); + + fputs("\n" + "InnoDB: Submit a detailed bug report" + " to http://bugs.mysql.com\n", stderr); + } else { + /* Delete mark the old index record; it can already be + delete marked if we return after a lock wait in + row_ins_index_entry below */ + + if (!rec_get_deleted_flag(rec, + dict_table_is_comp(index->table))) { + err = btr_cur_del_mark_set_sec_rec(0, btr_cur, TRUE, + thr, &mtr); + if (err == DB_SUCCESS && check_ref) { + + ulint* offsets = rec_get_offsets( + rec, index, NULL, + ULINT_UNDEFINED, &heap); + /* NOTE that the following call loses + the position of pcur ! */ + err = row_upd_check_references_constraints( + node, &pcur, index->table, + index, offsets, thr, &mtr); + } + } + } + + btr_pcur_close(&pcur); + mtr_commit(&mtr); + + if (node->is_delete || err != DB_SUCCESS) { + + goto func_exit; + } + + /* Build a new index entry */ + entry = row_build_index_entry(node->upd_row, node->upd_ext, + index, heap); + ut_a(entry); + + /* Insert new index entry */ + err = row_ins_index_entry(index, entry, 0, TRUE, thr); + +func_exit: + mem_heap_free(heap); + + return(err); +} + +/*************************************************************** +Updates the secondary index record if it is changed in the row update or +deletes it if this is a delete. */ +UNIV_INLINE +ulint +row_upd_sec_step( +/*=============*/ + /* out: DB_SUCCESS if operation successfully + completed, else error code or DB_LOCK_WAIT */ + upd_node_t* node, /* in: row update node */ + que_thr_t* thr) /* in: query thread */ +{ + ut_ad((node->state == UPD_NODE_UPDATE_ALL_SEC) + || (node->state == UPD_NODE_UPDATE_SOME_SEC)); + ut_ad(!dict_index_is_clust(node->index)); + + if (node->state == UPD_NODE_UPDATE_ALL_SEC + || row_upd_changes_ord_field_binary(node->row, node->index, + node->update)) { + return(row_upd_sec_index_entry(node, thr)); + } + + return(DB_SUCCESS); +} + +/*************************************************************** +Marks the clustered index record deleted and inserts the updated version +of the record to the index. This function should be used when the ordering +fields of the clustered index record change. This should be quite rare in +database applications. */ +static +ulint +row_upd_clust_rec_by_insert( +/*========================*/ + /* out: DB_SUCCESS if operation successfully + completed, else error code or DB_LOCK_WAIT */ + upd_node_t* node, /* in: row update node */ + dict_index_t* index, /* in: clustered index of the record */ + que_thr_t* thr, /* in: query thread */ + ibool check_ref,/* in: TRUE if index may be referenced in + a foreign key constraint */ + mtr_t* mtr) /* in: mtr; gets committed here */ +{ + mem_heap_t* heap = NULL; + btr_pcur_t* pcur; + btr_cur_t* btr_cur; + trx_t* trx; + dict_table_t* table; + dtuple_t* entry; + ulint err; + + ut_ad(node); + ut_ad(dict_index_is_clust(index)); + + trx = thr_get_trx(thr); + table = node->table; + pcur = node->pcur; + btr_cur = btr_pcur_get_btr_cur(pcur); + + if (node->state != UPD_NODE_INSERT_CLUSTERED) { + rec_t* rec; + dict_index_t* index; + ulint offsets_[REC_OFFS_NORMAL_SIZE]; + ulint* offsets; + rec_offs_init(offsets_); + + err = btr_cur_del_mark_set_clust_rec(BTR_NO_LOCKING_FLAG, + btr_cur, TRUE, thr, mtr); + if (err != DB_SUCCESS) { + mtr_commit(mtr); + return(err); + } + + /* Mark as not-owned the externally stored fields which the new + row inherits from the delete marked record: purge should not + free those externally stored fields even if the delete marked + record is removed from the index tree, or updated. */ + + rec = btr_cur_get_rec(btr_cur); + index = dict_table_get_first_index(table); + offsets = rec_get_offsets(rec, index, offsets_, + ULINT_UNDEFINED, &heap); + btr_cur_mark_extern_inherited_fields( + btr_cur_get_page_zip(btr_cur), + rec, index, offsets, node->update, mtr); + if (check_ref) { + /* NOTE that the following call loses + the position of pcur ! */ + err = row_upd_check_references_constraints( + node, pcur, table, index, offsets, thr, mtr); + if (err != DB_SUCCESS) { + mtr_commit(mtr); + if (UNIV_LIKELY_NULL(heap)) { + mem_heap_free(heap); + } + return(err); + } + } + } + + mtr_commit(mtr); + + if (!heap) { + heap = mem_heap_create(500); + } + node->state = UPD_NODE_INSERT_CLUSTERED; + + entry = row_build_index_entry(node->upd_row, node->upd_ext, + index, heap); + ut_a(entry); + + row_upd_index_entry_sys_field(entry, index, DATA_TRX_ID, trx->id); + + if (node->upd_ext) { + /* If we return from a lock wait, for example, we may have + extern fields marked as not-owned in entry (marked in the + if-branch above). We must unmark them. */ + + btr_cur_unmark_dtuple_extern_fields(entry); + + /* We must mark non-updated extern fields in entry as + inherited, so that a possible rollback will not free them. */ + + btr_cur_mark_dtuple_inherited_extern(entry, node->update); + } + + err = row_ins_index_entry(index, entry, + node->upd_ext ? node->upd_ext->n_ext : 0, + TRUE, thr); + mem_heap_free(heap); + + return(err); +} + +/*************************************************************** +Updates a clustered index record of a row when the ordering fields do +not change. */ +static +ulint +row_upd_clust_rec( +/*==============*/ + /* out: DB_SUCCESS if operation successfully + completed, else error code or DB_LOCK_WAIT */ + upd_node_t* node, /* in: row update node */ + dict_index_t* index, /* in: clustered index */ + que_thr_t* thr, /* in: query thread */ + mtr_t* mtr) /* in: mtr; gets committed here */ +{ + mem_heap_t* heap = NULL; + big_rec_t* big_rec = NULL; + btr_pcur_t* pcur; + btr_cur_t* btr_cur; + ulint err; + + ut_ad(node); + ut_ad(dict_index_is_clust(index)); + + pcur = node->pcur; + btr_cur = btr_pcur_get_btr_cur(pcur); + + ut_ad(!rec_get_deleted_flag(btr_pcur_get_rec(pcur), + dict_table_is_comp(index->table))); + + /* Try optimistic updating of the record, keeping changes within + the page; we do not check locks because we assume the x-lock on the + record to update */ + + if (node->cmpl_info & UPD_NODE_NO_SIZE_CHANGE) { + err = btr_cur_update_in_place(BTR_NO_LOCKING_FLAG, + btr_cur, node->update, + node->cmpl_info, thr, mtr); + } else { + err = btr_cur_optimistic_update(BTR_NO_LOCKING_FLAG, + btr_cur, node->update, + node->cmpl_info, thr, mtr); + } + + mtr_commit(mtr); + + if (UNIV_LIKELY(err == DB_SUCCESS)) { + + return(DB_SUCCESS); + } + + if (buf_LRU_buf_pool_running_out()) { + + return(DB_LOCK_TABLE_FULL); + } + /* We may have to modify the tree structure: do a pessimistic descent + down the index tree */ + + mtr_start(mtr); + + /* NOTE: this transaction has an s-lock or x-lock on the record and + therefore other transactions cannot modify the record when we have no + latch on the page. In addition, we assume that other query threads of + the same transaction do not modify the record in the meantime. + Therefore we can assert that the restoration of the cursor succeeds. */ + + ut_a(btr_pcur_restore_position(BTR_MODIFY_TREE, pcur, mtr)); + + ut_ad(!rec_get_deleted_flag(btr_pcur_get_rec(pcur), + dict_table_is_comp(index->table))); + + err = btr_cur_pessimistic_update(BTR_NO_LOCKING_FLAG, btr_cur, + &heap, &big_rec, node->update, + node->cmpl_info, thr, mtr); + mtr_commit(mtr); + + if (err == DB_SUCCESS && big_rec) { + ulint offsets_[REC_OFFS_NORMAL_SIZE]; + rec_t* rec; + rec_offs_init(offsets_); + + mtr_start(mtr); + + ut_a(btr_pcur_restore_position(BTR_MODIFY_TREE, pcur, mtr)); + rec = btr_cur_get_rec(btr_cur); + err = btr_store_big_rec_extern_fields( + index, btr_cur_get_block(btr_cur), rec, + rec_get_offsets(rec, index, offsets_, + ULINT_UNDEFINED, &heap), + big_rec, mtr); + mtr_commit(mtr); + } + + if (UNIV_LIKELY_NULL(heap)) { + mem_heap_free(heap); + } + + if (big_rec) { + dtuple_big_rec_free(big_rec); + } + + return(err); +} + +/*************************************************************** +Delete marks a clustered index record. */ +static +ulint +row_upd_del_mark_clust_rec( +/*=======================*/ + /* out: DB_SUCCESS if operation successfully + completed, else error code */ + upd_node_t* node, /* in: row update node */ + dict_index_t* index, /* in: clustered index */ + ulint* offsets,/* in/out: rec_get_offsets() for the + record under the cursor */ + que_thr_t* thr, /* in: query thread */ + ibool check_ref,/* in: TRUE if index may be referenced in + a foreign key constraint */ + mtr_t* mtr) /* in: mtr; gets committed here */ +{ + btr_pcur_t* pcur; + btr_cur_t* btr_cur; + ulint err; + + ut_ad(node); + ut_ad(dict_index_is_clust(index)); + ut_ad(node->is_delete); + + pcur = node->pcur; + btr_cur = btr_pcur_get_btr_cur(pcur); + + /* Store row because we have to build also the secondary index + entries */ + + row_upd_store_row(node); + + /* Mark the clustered index record deleted; we do not have to check + locks, because we assume that we have an x-lock on the record */ + + err = btr_cur_del_mark_set_clust_rec(BTR_NO_LOCKING_FLAG, + btr_cur, TRUE, thr, mtr); + if (err == DB_SUCCESS && check_ref) { + /* NOTE that the following call loses the position of pcur ! */ + + err = row_upd_check_references_constraints(node, + pcur, index->table, + index, offsets, + thr, mtr); + } + + mtr_commit(mtr); + + return(err); +} + +/*************************************************************** +Updates the clustered index record. */ +static +ulint +row_upd_clust_step( +/*===============*/ + /* out: DB_SUCCESS if operation successfully + completed, DB_LOCK_WAIT in case of a lock wait, + else error code */ + upd_node_t* node, /* in: row update node */ + que_thr_t* thr) /* in: query thread */ +{ + dict_index_t* index; + btr_pcur_t* pcur; + ibool success; + ibool check_ref; + ulint err; + mtr_t* mtr; + mtr_t mtr_buf; + rec_t* rec; + mem_heap_t* heap = NULL; + ulint offsets_[REC_OFFS_NORMAL_SIZE]; + ulint* offsets; + rec_offs_init(offsets_); + + index = dict_table_get_first_index(node->table); + + check_ref = row_upd_index_is_referenced(index, thr_get_trx(thr)); + + pcur = node->pcur; + + /* We have to restore the cursor to its position */ + mtr = &mtr_buf; + + mtr_start(mtr); + + /* If the restoration does not succeed, then the same + transaction has deleted the record on which the cursor was, + and that is an SQL error. If the restoration succeeds, it may + still be that the same transaction has successively deleted + and inserted a record with the same ordering fields, but in + that case we know that the transaction has at least an + implicit x-lock on the record. */ + + ut_a(pcur->rel_pos == BTR_PCUR_ON); + + success = btr_pcur_restore_position(BTR_MODIFY_LEAF, pcur, mtr); + + if (!success) { + err = DB_RECORD_NOT_FOUND; + + mtr_commit(mtr); + + return(err); + } + + /* If this is a row in SYS_INDEXES table of the data dictionary, + then we have to free the file segments of the index tree associated + with the index */ + + if (node->is_delete + && ut_dulint_cmp(node->table->id, DICT_INDEXES_ID) == 0) { + + dict_drop_index_tree(btr_pcur_get_rec(pcur), mtr); + + mtr_commit(mtr); + + mtr_start(mtr); + + success = btr_pcur_restore_position(BTR_MODIFY_LEAF, pcur, + mtr); + if (!success) { + err = DB_ERROR; + + mtr_commit(mtr); + + return(err); + } + } + + rec = btr_pcur_get_rec(pcur); + offsets = rec_get_offsets(rec, index, offsets_, + ULINT_UNDEFINED, &heap); + + if (!node->has_clust_rec_x_lock) { + err = lock_clust_rec_modify_check_and_lock( + 0, btr_pcur_get_block(pcur), + rec, index, offsets, thr); + if (err != DB_SUCCESS) { + mtr_commit(mtr); + goto exit_func; + } + } + + /* NOTE: the following function calls will also commit mtr */ + + if (node->is_delete) { + err = row_upd_del_mark_clust_rec(node, index, offsets, + thr, check_ref, mtr); + if (err == DB_SUCCESS) { + node->state = UPD_NODE_UPDATE_ALL_SEC; + node->index = dict_table_get_next_index(index); + } +exit_func: + if (UNIV_LIKELY_NULL(heap)) { + mem_heap_free(heap); + } + return(err); + } + + /* If the update is made for MySQL, we already have the update vector + ready, else we have to do some evaluation: */ + + if (UNIV_UNLIKELY(!node->in_mysql_interface)) { + /* Copy the necessary columns from clust_rec and calculate the + new values to set */ + row_upd_copy_columns(rec, offsets, + UT_LIST_GET_FIRST(node->columns)); + row_upd_eval_new_vals(node->update); + } + + if (UNIV_LIKELY_NULL(heap)) { + mem_heap_free(heap); + } + + if (node->cmpl_info & UPD_NODE_NO_ORD_CHANGE) { + + err = row_upd_clust_rec(node, index, thr, mtr); + return(err); + } + + row_upd_store_row(node); + + if (row_upd_changes_ord_field_binary(node->row, index, node->update)) { + + /* Update causes an ordering field (ordering fields within + the B-tree) of the clustered index record to change: perform + the update by delete marking and inserting. + + TODO! What to do to the 'Halloween problem', where an update + moves the record forward in index so that it is again + updated when the cursor arrives there? Solution: the + read operation must check the undo record undo number when + choosing records to update. MySQL solves now the problem + externally! */ + + err = row_upd_clust_rec_by_insert(node, index, thr, check_ref, + mtr); + if (err != DB_SUCCESS) { + + return(err); + } + + node->state = UPD_NODE_UPDATE_ALL_SEC; + } else { + err = row_upd_clust_rec(node, index, thr, mtr); + + if (err != DB_SUCCESS) { + + return(err); + } + + node->state = UPD_NODE_UPDATE_SOME_SEC; + } + + node->index = dict_table_get_next_index(index); + + return(err); +} + +/*************************************************************** +Updates the affected index records of a row. When the control is transferred +to this node, we assume that we have a persistent cursor which was on a +record, and the position of the cursor is stored in the cursor. */ +static +ulint +row_upd( +/*====*/ + /* out: DB_SUCCESS if operation successfully + completed, else error code or DB_LOCK_WAIT */ + upd_node_t* node, /* in: row update node */ + que_thr_t* thr) /* in: query thread */ +{ + ulint err = DB_SUCCESS; + + ut_ad(node && thr); + + if (UNIV_LIKELY(node->in_mysql_interface)) { + + /* We do not get the cmpl_info value from the MySQL + interpreter: we must calculate it on the fly: */ + + if (node->is_delete + || row_upd_changes_some_index_ord_field_binary( + node->table, node->update)) { + node->cmpl_info = 0; + } else { + node->cmpl_info = UPD_NODE_NO_ORD_CHANGE; + } + } + + if (node->state == UPD_NODE_UPDATE_CLUSTERED + || node->state == UPD_NODE_INSERT_CLUSTERED) { + + err = row_upd_clust_step(node, thr); + + if (err != DB_SUCCESS) { + + goto function_exit; + } + } + + if (!node->is_delete && (node->cmpl_info & UPD_NODE_NO_ORD_CHANGE)) { + + goto function_exit; + } + + while (node->index != NULL) { + err = row_upd_sec_step(node, thr); + + if (err != DB_SUCCESS) { + + goto function_exit; + } + + node->index = dict_table_get_next_index(node->index); + } + +function_exit: + if (err == DB_SUCCESS) { + /* Do some cleanup */ + + if (node->row != NULL) { + node->row = NULL; + node->ext = NULL; + node->upd_row = NULL; + node->upd_ext = NULL; + mem_heap_empty(node->heap); + } + + node->state = UPD_NODE_UPDATE_CLUSTERED; + } + + return(err); +} + +/*************************************************************** +Updates a row in a table. This is a high-level function used in SQL execution +graphs. */ +UNIV_INTERN +que_thr_t* +row_upd_step( +/*=========*/ + /* out: query thread to run next or NULL */ + que_thr_t* thr) /* in: query thread */ +{ + upd_node_t* node; + sel_node_t* sel_node; + que_node_t* parent; + ulint err = DB_SUCCESS; + trx_t* trx; + + ut_ad(thr); + + trx = thr_get_trx(thr); + + trx_start_if_not_started(trx); + + node = thr->run_node; + + sel_node = node->select; + + parent = que_node_get_parent(node); + + ut_ad(que_node_get_type(node) == QUE_NODE_UPDATE); + + if (thr->prev_node == parent) { + node->state = UPD_NODE_SET_IX_LOCK; + } + + if (node->state == UPD_NODE_SET_IX_LOCK) { + + if (!node->has_clust_rec_x_lock) { + /* It may be that the current session has not yet + started its transaction, or it has been committed: */ + + err = lock_table(0, node->table, LOCK_IX, thr); + + if (err != DB_SUCCESS) { + + goto error_handling; + } + } + + node->state = UPD_NODE_UPDATE_CLUSTERED; + + if (node->searched_update) { + /* Reset the cursor */ + sel_node->state = SEL_NODE_OPEN; + + /* Fetch a row to update */ + + thr->run_node = sel_node; + + return(thr); + } + } + + /* sel_node is NULL if we are in the MySQL interface */ + + if (sel_node && (sel_node->state != SEL_NODE_FETCH)) { + + if (!node->searched_update) { + /* An explicit cursor should be positioned on a row + to update */ + + ut_error; + + err = DB_ERROR; + + goto error_handling; + } + + ut_ad(sel_node->state == SEL_NODE_NO_MORE_ROWS); + + /* No more rows to update, or the select node performed the + updates directly in-place */ + + thr->run_node = parent; + + return(thr); + } + + /* DO THE CHECKS OF THE CONSISTENCY CONSTRAINTS HERE */ + + err = row_upd(node, thr); + +error_handling: + trx->error_state = err; + + if (err != DB_SUCCESS) { + return(NULL); + } + + /* DO THE TRIGGER ACTIONS HERE */ + + if (node->searched_update) { + /* Fetch next row to update */ + + thr->run_node = sel_node; + } else { + /* It was an explicit cursor update */ + + thr->run_node = parent; + } + + node->state = UPD_NODE_UPDATE_CLUSTERED; + + return(thr); +} diff --git a/storage/xtradb/row/row0vers.c b/storage/xtradb/row/row0vers.c new file mode 100644 index 00000000000..3abba6d6fb8 --- /dev/null +++ b/storage/xtradb/row/row0vers.c @@ -0,0 +1,741 @@ +/***************************************************************************** + +Copyright (c) 1997, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/****************************************************** +Row versions + +Created 2/6/1997 Heikki Tuuri +*******************************************************/ + +#include "row0vers.h" + +#ifdef UNIV_NONINL +#include "row0vers.ic" +#endif + +#include "dict0dict.h" +#include "dict0boot.h" +#include "btr0btr.h" +#include "mach0data.h" +#include "trx0rseg.h" +#include "trx0trx.h" +#include "trx0roll.h" +#include "trx0undo.h" +#include "trx0purge.h" +#include "trx0rec.h" +#include "que0que.h" +#include "row0row.h" +#include "row0upd.h" +#include "rem0cmp.h" +#include "read0read.h" +#include "lock0lock.h" + +/********************************************************************* +Finds out if an active transaction has inserted or modified a secondary +index record. NOTE: the kernel mutex is temporarily released in this +function! */ +UNIV_INTERN +trx_t* +row_vers_impl_x_locked_off_kernel( +/*==============================*/ + /* out: NULL if committed, else the active + transaction; NOTE that the kernel mutex is + temporarily released! */ + const rec_t* rec, /* in: record in a secondary index */ + dict_index_t* index, /* in: the secondary index */ + const ulint* offsets)/* in: rec_get_offsets(rec, index) */ +{ + dict_index_t* clust_index; + rec_t* clust_rec; + ulint* clust_offsets; + rec_t* version; + dulint trx_id; + mem_heap_t* heap; + mem_heap_t* heap2; + dtuple_t* row; + dtuple_t* entry = NULL; /* assignment to eliminate compiler + warning */ + trx_t* trx; + ulint rec_del; + ulint err; + mtr_t mtr; + ulint comp; + + ut_ad(mutex_own(&kernel_mutex)); +#ifdef UNIV_SYNC_DEBUG + ut_ad(!rw_lock_own(&(purge_sys->latch), RW_LOCK_SHARED)); +#endif /* UNIV_SYNC_DEBUG */ + + mutex_exit(&kernel_mutex); + + mtr_start(&mtr); + + /* Search for the clustered index record: this is a time-consuming + operation: therefore we release the kernel mutex; also, the release + is required by the latching order convention. The latch on the + clustered index locks the top of the stack of versions. We also + reserve purge_latch to lock the bottom of the version stack. */ + + clust_rec = row_get_clust_rec(BTR_SEARCH_LEAF, rec, index, + &clust_index, &mtr); + if (!clust_rec) { + /* In a rare case it is possible that no clust rec is found + for a secondary index record: if in row0umod.c + row_undo_mod_remove_clust_low() we have already removed the + clust rec, while purge is still cleaning and removing + secondary index records associated with earlier versions of + the clustered index record. In that case there cannot be + any implicit lock on the secondary index record, because + an active transaction which has modified the secondary index + record has also modified the clustered index record. And in + a rollback we always undo the modifications to secondary index + records before the clustered index record. */ + + mutex_enter(&kernel_mutex); + mtr_commit(&mtr); + + return(NULL); + } + + heap = mem_heap_create(1024); + clust_offsets = rec_get_offsets(clust_rec, clust_index, NULL, + ULINT_UNDEFINED, &heap); + trx_id = row_get_rec_trx_id(clust_rec, clust_index, clust_offsets); + + mtr_s_lock(&(purge_sys->latch), &mtr); + + mutex_enter(&kernel_mutex); + + trx = NULL; + if (!trx_is_active(trx_id)) { + /* The transaction that modified or inserted clust_rec is no + longer active: no implicit lock on rec */ + goto exit_func; + } + + if (!lock_check_trx_id_sanity(trx_id, clust_rec, clust_index, + clust_offsets, TRUE)) { + /* Corruption noticed: try to avoid a crash by returning */ + goto exit_func; + } + + comp = page_rec_is_comp(rec); + ut_ad(index->table == clust_index->table); + ut_ad(!!comp == dict_table_is_comp(index->table)); + ut_ad(!comp == !page_rec_is_comp(clust_rec)); + + /* We look up if some earlier version, which was modified by the trx_id + transaction, of the clustered index record would require rec to be in + a different state (delete marked or unmarked, or have different field + values, or not existing). If there is such a version, then rec was + modified by the trx_id transaction, and it has an implicit x-lock on + rec. Note that if clust_rec itself would require rec to be in a + different state, then the trx_id transaction has not yet had time to + modify rec, and does not necessarily have an implicit x-lock on rec. */ + + rec_del = rec_get_deleted_flag(rec, comp); + trx = NULL; + + version = clust_rec; + + for (;;) { + rec_t* prev_version; + ulint vers_del; + row_ext_t* ext; + dulint prev_trx_id; + + mutex_exit(&kernel_mutex); + + /* While we retrieve an earlier version of clust_rec, we + release the kernel mutex, because it may take time to access + the disk. After the release, we have to check if the trx_id + transaction is still active. We keep the semaphore in mtr on + the clust_rec page, so that no other transaction can update + it and get an implicit x-lock on rec. */ + + heap2 = heap; + heap = mem_heap_create(1024); + err = trx_undo_prev_version_build(clust_rec, &mtr, version, + clust_index, clust_offsets, + heap, &prev_version); + mem_heap_free(heap2); /* free version and clust_offsets */ + + if (prev_version == NULL) { + mutex_enter(&kernel_mutex); + + if (!trx_is_active(trx_id)) { + /* Transaction no longer active: no + implicit x-lock */ + + break; + } + + /* If the transaction is still active, + clust_rec must be a fresh insert, because no + previous version was found. */ + ut_ad(err == DB_SUCCESS); + + /* It was a freshly inserted version: there is an + implicit x-lock on rec */ + + trx = trx_get_on_id(trx_id); + + break; + } + + clust_offsets = rec_get_offsets(prev_version, clust_index, + NULL, ULINT_UNDEFINED, &heap); + + vers_del = rec_get_deleted_flag(prev_version, comp); + prev_trx_id = row_get_rec_trx_id(prev_version, clust_index, + clust_offsets); + + /* If the trx_id and prev_trx_id are different and if + the prev_version is marked deleted then the + prev_trx_id must have already committed for the trx_id + to be able to modify the row. Therefore, prev_trx_id + cannot hold any implicit lock. */ + if (vers_del && 0 != ut_dulint_cmp(trx_id, prev_trx_id)) { + + mutex_enter(&kernel_mutex); + break; + } + + /* The stack of versions is locked by mtr. Thus, it + is safe to fetch the prefixes for externally stored + columns. */ + row = row_build(ROW_COPY_POINTERS, clust_index, prev_version, + clust_offsets, NULL, &ext, heap); + entry = row_build_index_entry(row, ext, index, heap); + /* entry may be NULL if a record was inserted in place + of a deleted record, and the BLOB pointers of the new + record were not initialized yet. But in that case, + prev_version should be NULL. */ + ut_a(entry); + + mutex_enter(&kernel_mutex); + + if (!trx_is_active(trx_id)) { + /* Transaction no longer active: no implicit x-lock */ + + break; + } + + /* If we get here, we know that the trx_id transaction is + still active and it has modified prev_version. Let us check + if prev_version would require rec to be in a different + state. */ + + /* The previous version of clust_rec must be + accessible, because the transaction is still active + and clust_rec was not a fresh insert. */ + ut_ad(err == DB_SUCCESS); + + /* We check if entry and rec are identified in the alphabetical + ordering */ + if (0 == cmp_dtuple_rec(entry, rec, offsets)) { + /* The delete marks of rec and prev_version should be + equal for rec to be in the state required by + prev_version */ + + if (rec_del != vers_del) { + trx = trx_get_on_id(trx_id); + + break; + } + + /* It is possible that the row was updated so that the + secondary index record remained the same in + alphabetical ordering, but the field values changed + still. For example, 'abc' -> 'ABC'. Check also that. */ + + dtuple_set_types_binary(entry, + dtuple_get_n_fields(entry)); + if (0 != cmp_dtuple_rec(entry, rec, offsets)) { + + trx = trx_get_on_id(trx_id); + + break; + } + } else if (!rec_del) { + /* The delete mark should be set in rec for it to be + in the state required by prev_version */ + + trx = trx_get_on_id(trx_id); + + break; + } + + if (0 != ut_dulint_cmp(trx_id, prev_trx_id)) { + /* The versions modified by the trx_id transaction end + to prev_version: no implicit x-lock */ + + break; + } + + version = prev_version; + }/* for (;;) */ + +exit_func: + mtr_commit(&mtr); + mem_heap_free(heap); + + return(trx); +} + +/********************************************************************* +Finds out if we must preserve a delete marked earlier version of a clustered +index record, because it is >= the purge view. */ +UNIV_INTERN +ibool +row_vers_must_preserve_del_marked( +/*==============================*/ + /* out: TRUE if earlier version should be preserved */ + dulint trx_id, /* in: transaction id in the version */ + mtr_t* mtr) /* in: mtr holding the latch on the clustered index + record; it will also hold the latch on purge_view */ +{ +#ifdef UNIV_SYNC_DEBUG + ut_ad(!rw_lock_own(&(purge_sys->latch), RW_LOCK_SHARED)); +#endif /* UNIV_SYNC_DEBUG */ + + mtr_s_lock(&(purge_sys->latch), mtr); + + if (trx_purge_update_undo_must_exist(trx_id)) { + + /* A purge operation is not yet allowed to remove this + delete marked record */ + + return(TRUE); + } + + return(FALSE); +} + +/********************************************************************* +Finds out if a version of the record, where the version >= the current +purge view, should have ientry as its secondary index entry. We check +if there is any not delete marked version of the record where the trx +id >= purge view, and the secondary index entry and ientry are identified in +the alphabetical ordering; exactly in this case we return TRUE. */ +UNIV_INTERN +ibool +row_vers_old_has_index_entry( +/*=========================*/ + /* out: TRUE if earlier version should have */ + ibool also_curr,/* in: TRUE if also rec is included in the + versions to search; otherwise only versions + prior to it are searched */ + const rec_t* rec, /* in: record in the clustered index; the + caller must have a latch on the page */ + mtr_t* mtr, /* in: mtr holding the latch on rec; it will + also hold the latch on purge_view */ + dict_index_t* index, /* in: the secondary index */ + const dtuple_t* ientry) /* in: the secondary index entry */ +{ + const rec_t* version; + rec_t* prev_version; + dict_index_t* clust_index; + ulint* clust_offsets; + mem_heap_t* heap; + mem_heap_t* heap2; + const dtuple_t* row; + const dtuple_t* entry; + ulint err; + ulint comp; + + ut_ad(mtr_memo_contains_page(mtr, rec, MTR_MEMO_PAGE_X_FIX) + || mtr_memo_contains_page(mtr, rec, MTR_MEMO_PAGE_S_FIX)); +#ifdef UNIV_SYNC_DEBUG + ut_ad(!rw_lock_own(&(purge_sys->latch), RW_LOCK_SHARED)); +#endif /* UNIV_SYNC_DEBUG */ + mtr_s_lock(&(purge_sys->latch), mtr); + + clust_index = dict_table_get_first_index(index->table); + + comp = page_rec_is_comp(rec); + ut_ad(!dict_table_is_comp(index->table) == !comp); + heap = mem_heap_create(1024); + clust_offsets = rec_get_offsets(rec, clust_index, NULL, + ULINT_UNDEFINED, &heap); + + if (also_curr && !rec_get_deleted_flag(rec, comp)) { + row_ext_t* ext; + + /* The stack of versions is locked by mtr. + Thus, it is safe to fetch the prefixes for + externally stored columns. */ + row = row_build(ROW_COPY_POINTERS, clust_index, + rec, clust_offsets, NULL, &ext, heap); + entry = row_build_index_entry(row, ext, index, heap); + + /* If entry == NULL, the record contains unset BLOB + pointers. This must be a freshly inserted record. If + this is called from + row_purge_remove_sec_if_poss_low(), the thread will + hold latches on the clustered index and the secondary + index. Because the insert works in three steps: + + (1) insert the record to clustered index + (2) store the BLOBs and update BLOB pointers + (3) insert records to secondary indexes + + the purge thread can safely ignore freshly inserted + records and delete the secondary index record. The + thread that inserted the new record will be inserting + the secondary index records. */ + + /* NOTE that we cannot do the comparison as binary + fields because the row is maybe being modified so that + the clustered index record has already been updated to + a different binary value in a char field, but the + collation identifies the old and new value anyway! */ + if (entry && !dtuple_coll_cmp(ientry, entry)) { + + mem_heap_free(heap); + + return(TRUE); + } + } + + version = rec; + + for (;;) { + heap2 = heap; + heap = mem_heap_create(1024); + err = trx_undo_prev_version_build(rec, mtr, version, + clust_index, clust_offsets, + heap, &prev_version); + mem_heap_free(heap2); /* free version and clust_offsets */ + + if (err != DB_SUCCESS || !prev_version) { + /* Versions end here */ + + mem_heap_free(heap); + + return(FALSE); + } + + clust_offsets = rec_get_offsets(prev_version, clust_index, + NULL, ULINT_UNDEFINED, &heap); + + if (!rec_get_deleted_flag(prev_version, comp)) { + row_ext_t* ext; + + /* The stack of versions is locked by mtr. + Thus, it is safe to fetch the prefixes for + externally stored columns. */ + row = row_build(ROW_COPY_POINTERS, clust_index, + prev_version, clust_offsets, + NULL, &ext, heap); + entry = row_build_index_entry(row, ext, index, heap); + + /* If entry == NULL, the record contains unset + BLOB pointers. This must be a freshly + inserted record that we can safely ignore. + For the justification, see the comments after + the previous row_build_index_entry() call. */ + + /* NOTE that we cannot do the comparison as binary + fields because maybe the secondary index record has + already been updated to a different binary value in + a char field, but the collation identifies the old + and new value anyway! */ + + if (entry && !dtuple_coll_cmp(ientry, entry)) { + + mem_heap_free(heap); + + return(TRUE); + } + } + + version = prev_version; + } +} + +/********************************************************************* +Constructs the version of a clustered index record which a consistent +read should see. We assume that the trx id stored in rec is such that +the consistent read should not see rec in its present version. */ +UNIV_INTERN +ulint +row_vers_build_for_consistent_read( +/*===============================*/ + /* out: DB_SUCCESS or DB_MISSING_HISTORY */ + const rec_t* rec, /* in: record in a clustered index; the + caller must have a latch on the page; this + latch locks the top of the stack of versions + of this records */ + mtr_t* mtr, /* in: mtr holding the latch on rec */ + dict_index_t* index, /* in: the clustered index */ + ulint** offsets,/* in/out: offsets returned by + rec_get_offsets(rec, index) */ + read_view_t* view, /* in: the consistent read view */ + mem_heap_t** offset_heap,/* in/out: memory heap from which + the offsets are allocated */ + mem_heap_t* in_heap,/* in: memory heap from which the memory for + *old_vers is allocated; memory for possible + intermediate versions is allocated and freed + locally within the function */ + rec_t** old_vers)/* out, own: old version, or NULL if the + record does not exist in the view, that is, + it was freshly inserted afterwards */ +{ + const rec_t* version; + rec_t* prev_version; + dulint trx_id; + mem_heap_t* heap = NULL; + byte* buf; + ulint err; + + ut_ad(dict_index_is_clust(index)); + ut_ad(mtr_memo_contains_page(mtr, rec, MTR_MEMO_PAGE_X_FIX) + || mtr_memo_contains_page(mtr, rec, MTR_MEMO_PAGE_S_FIX)); +#ifdef UNIV_SYNC_DEBUG + ut_ad(!rw_lock_own(&(purge_sys->latch), RW_LOCK_SHARED)); +#endif /* UNIV_SYNC_DEBUG */ + + ut_ad(rec_offs_validate(rec, index, *offsets)); + + trx_id = row_get_rec_trx_id(rec, index, *offsets); + + ut_ad(!read_view_sees_trx_id(view, trx_id)); + + rw_lock_s_lock(&(purge_sys->latch)); + version = rec; + + for (;;) { + mem_heap_t* heap2 = heap; + trx_undo_rec_t* undo_rec; + dulint roll_ptr; + dulint undo_no; + heap = mem_heap_create(1024); + + /* If we have high-granularity consistent read view and + creating transaction of the view is the same as trx_id in + the record we see this record only in the case when + undo_no of the record is < undo_no in the view. */ + + if (view->type == VIEW_HIGH_GRANULARITY + && ut_dulint_cmp(view->creator_trx_id, trx_id) == 0) { + + roll_ptr = row_get_rec_roll_ptr(version, index, + *offsets); + undo_rec = trx_undo_get_undo_rec_low(roll_ptr, heap); + undo_no = trx_undo_rec_get_undo_no(undo_rec); + mem_heap_empty(heap); + + if (ut_dulint_cmp(view->undo_no, undo_no) > 0) { + /* The view already sees this version: we can + copy it to in_heap and return */ + + buf = mem_heap_alloc(in_heap, + rec_offs_size(*offsets)); + *old_vers = rec_copy(buf, version, *offsets); + rec_offs_make_valid(*old_vers, index, + *offsets); + err = DB_SUCCESS; + + break; + } + } + + err = trx_undo_prev_version_build(rec, mtr, version, index, + *offsets, heap, + &prev_version); + if (heap2) { + mem_heap_free(heap2); /* free version */ + } + + if (err != DB_SUCCESS) { + break; + } + + if (prev_version == NULL) { + /* It was a freshly inserted version */ + *old_vers = NULL; + err = DB_SUCCESS; + + break; + } + + *offsets = rec_get_offsets(prev_version, index, *offsets, + ULINT_UNDEFINED, offset_heap); + + trx_id = row_get_rec_trx_id(prev_version, index, *offsets); + + if (read_view_sees_trx_id(view, trx_id)) { + + /* The view already sees this version: we can copy + it to in_heap and return */ + + buf = mem_heap_alloc(in_heap, rec_offs_size(*offsets)); + *old_vers = rec_copy(buf, prev_version, *offsets); + rec_offs_make_valid(*old_vers, index, *offsets); + err = DB_SUCCESS; + + break; + } + + version = prev_version; + }/* for (;;) */ + + mem_heap_free(heap); + rw_lock_s_unlock(&(purge_sys->latch)); + + return(err); +} + +/********************************************************************* +Constructs the last committed version of a clustered index record, +which should be seen by a semi-consistent read. */ +UNIV_INTERN +ulint +row_vers_build_for_semi_consistent_read( +/*====================================*/ + /* out: DB_SUCCESS or DB_MISSING_HISTORY */ + const rec_t* rec, /* in: record in a clustered index; the + caller must have a latch on the page; this + latch locks the top of the stack of versions + of this records */ + mtr_t* mtr, /* in: mtr holding the latch on rec */ + dict_index_t* index, /* in: the clustered index */ + ulint** offsets,/* in/out: offsets returned by + rec_get_offsets(rec, index) */ + mem_heap_t** offset_heap,/* in/out: memory heap from which + the offsets are allocated */ + mem_heap_t* in_heap,/* in: memory heap from which the memory for + *old_vers is allocated; memory for possible + intermediate versions is allocated and freed + locally within the function */ + const rec_t** old_vers)/* out: rec, old version, or NULL if the + record does not exist in the view, that is, + it was freshly inserted afterwards */ +{ + const rec_t* version; + mem_heap_t* heap = NULL; + byte* buf; + ulint err; + dulint rec_trx_id = ut_dulint_zero; + + ut_ad(dict_index_is_clust(index)); + ut_ad(mtr_memo_contains_page(mtr, rec, MTR_MEMO_PAGE_X_FIX) + || mtr_memo_contains_page(mtr, rec, MTR_MEMO_PAGE_S_FIX)); +#ifdef UNIV_SYNC_DEBUG + ut_ad(!rw_lock_own(&(purge_sys->latch), RW_LOCK_SHARED)); +#endif /* UNIV_SYNC_DEBUG */ + + ut_ad(rec_offs_validate(rec, index, *offsets)); + + rw_lock_s_lock(&(purge_sys->latch)); + /* The S-latch on purge_sys prevents the purge view from + changing. Thus, if we have an uncommitted transaction at + this point, then purge cannot remove its undo log even if + the transaction could commit now. */ + + version = rec; + + for (;;) { + trx_t* version_trx; + mem_heap_t* heap2; + rec_t* prev_version; + dulint version_trx_id; + + version_trx_id = row_get_rec_trx_id(version, index, *offsets); + if (rec == version) { + rec_trx_id = version_trx_id; + } + + mutex_enter(&kernel_mutex); + version_trx = trx_get_on_id(version_trx_id); + mutex_exit(&kernel_mutex); + + if (!version_trx + || version_trx->conc_state == TRX_NOT_STARTED + || version_trx->conc_state == TRX_COMMITTED_IN_MEMORY) { + + /* We found a version that belongs to a + committed transaction: return it. */ + + if (rec == version) { + *old_vers = rec; + err = DB_SUCCESS; + break; + } + + /* We assume that a rolled-back transaction stays in + TRX_ACTIVE state until all the changes have been + rolled back and the transaction is removed from + the global list of transactions. */ + + if (!ut_dulint_cmp(rec_trx_id, version_trx_id)) { + /* The transaction was committed while + we searched for earlier versions. + Return the current version as a + semi-consistent read. */ + + version = rec; + *offsets = rec_get_offsets(version, + index, *offsets, + ULINT_UNDEFINED, + offset_heap); + } + + buf = mem_heap_alloc(in_heap, rec_offs_size(*offsets)); + *old_vers = rec_copy(buf, version, *offsets); + rec_offs_make_valid(*old_vers, index, *offsets); + err = DB_SUCCESS; + + break; + } + + heap2 = heap; + heap = mem_heap_create(1024); + + err = trx_undo_prev_version_build(rec, mtr, version, index, + *offsets, heap, + &prev_version); + if (heap2) { + mem_heap_free(heap2); /* free version */ + } + + if (UNIV_UNLIKELY(err != DB_SUCCESS)) { + break; + } + + if (prev_version == NULL) { + /* It was a freshly inserted version */ + *old_vers = NULL; + err = DB_SUCCESS; + + break; + } + + version = prev_version; + *offsets = rec_get_offsets(version, index, *offsets, + ULINT_UNDEFINED, offset_heap); + }/* for (;;) */ + + if (heap) { + mem_heap_free(heap); + } + rw_lock_s_unlock(&(purge_sys->latch)); + + return(err); +} diff --git a/storage/xtradb/scripts/install_innodb_plugins.sql b/storage/xtradb/scripts/install_innodb_plugins.sql new file mode 100644 index 00000000000..b2e5e3f6de0 --- /dev/null +++ b/storage/xtradb/scripts/install_innodb_plugins.sql @@ -0,0 +1,16 @@ +-- execute these to install InnoDB if it is built as a dynamic plugin +INSTALL PLUGIN innodb SONAME 'ha_innodb.so'; +INSTALL PLUGIN innodb_trx SONAME 'ha_innodb.so'; +INSTALL PLUGIN innodb_locks SONAME 'ha_innodb.so'; +INSTALL PLUGIN innodb_lock_waits SONAME 'ha_innodb.so'; +INSTALL PLUGIN innodb_cmp SONAME 'ha_innodb.so'; +INSTALL PLUGIN innodb_cmp_reset SONAME 'ha_innodb.so'; +INSTALL PLUGIN innodb_cmpmem SONAME 'ha_innodb.so'; +INSTALL PLUGIN innodb_cmpmem_reset SONAME 'ha_innodb.so'; +INSTALL PLUGIN XTRADB_ENHANCEMENTS SONAME 'ha_innodb.so'; +INSTALL PLUGIN INNODB_BUFFER_POOL_PAGES SONAME 'ha_innodb.so'; +INSTALL PLUGIN INNODB_BUFFER_POOL_PAGES_BLOB SONAME 'ha_innodb.so'; +INSTALL PLUGIN INNODB_BUFFER_POOL_PAGES_INDEX SONAME 'ha_innodb.so'; +INSTALL PLUGIN innodb_rseg SONAME 'ha_innodb.so'; +INSTALL PLUGIN innodb_table_stats SONAME 'ha_innodb.so'; +INSTALL PLUGIN innodb_index_stats SONAME 'ha_innodb.so'; diff --git a/storage/xtradb/scripts/install_innodb_plugins_win.sql b/storage/xtradb/scripts/install_innodb_plugins_win.sql new file mode 100644 index 00000000000..8c94b4e240d --- /dev/null +++ b/storage/xtradb/scripts/install_innodb_plugins_win.sql @@ -0,0 +1,9 @@ +-- execute these to install InnoDB if it is built as a dynamic plugin
+INSTALL PLUGIN innodb SONAME 'ha_innodb.dll';
+INSTALL PLUGIN innodb_trx SONAME 'ha_innodb.dll';
+INSTALL PLUGIN innodb_locks SONAME 'ha_innodb.dll';
+INSTALL PLUGIN innodb_lock_waits SONAME 'ha_innodb.dll';
+INSTALL PLUGIN innodb_cmp SONAME 'ha_innodb.dll';
+INSTALL PLUGIN innodb_cmp_reset SONAME 'ha_innodb.dll';
+INSTALL PLUGIN innodb_cmpmem SONAME 'ha_innodb.dll';
+INSTALL PLUGIN innodb_cmpmem_reset SONAME 'ha_innodb.dll';
diff --git a/storage/xtradb/srv/srv0que.c b/storage/xtradb/srv/srv0que.c new file mode 100644 index 00000000000..344aaed8775 --- /dev/null +++ b/storage/xtradb/srv/srv0que.c @@ -0,0 +1,126 @@ +/***************************************************************************** + +Copyright (c) 1996, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/****************************************************** +Server query execution + +Created 6/5/1996 Heikki Tuuri +*******************************************************/ + +#include "srv0que.h" + +#include "srv0srv.h" +#include "sync0sync.h" +#include "os0thread.h" +#include "usr0sess.h" +#include "que0que.h" + +/************************************************************************** +Checks if there is work to do in the server task queue. If there is, the +thread starts processing a task. Before leaving, it again checks the task +queue and picks a new task if any exists. This is called by a SRV_WORKER +thread. */ +UNIV_INTERN +void +srv_que_task_queue_check(void) +/*==========================*/ +{ + que_thr_t* thr; + + for (;;) { + mutex_enter(&kernel_mutex); + + thr = UT_LIST_GET_FIRST(srv_sys->tasks); + + if (thr == NULL) { + mutex_exit(&kernel_mutex); + + return; + } + + UT_LIST_REMOVE(queue, srv_sys->tasks, thr); + + mutex_exit(&kernel_mutex); + + que_run_threads(thr); + } +} + +/************************************************************************** +Performs round-robin on the server tasks. This is called by a SRV_WORKER +thread every second or so. */ +UNIV_INTERN +que_thr_t* +srv_que_round_robin( +/*================*/ + /* out: the new (may be == thr) query thread + to run */ + que_thr_t* thr) /* in: query thread */ +{ + que_thr_t* new_thr; + + ut_ad(thr); + ut_ad(thr->state == QUE_THR_RUNNING); + + mutex_enter(&kernel_mutex); + + UT_LIST_ADD_LAST(queue, srv_sys->tasks, thr); + + new_thr = UT_LIST_GET_FIRST(srv_sys->tasks); + + mutex_exit(&kernel_mutex); + + return(new_thr); +} + +/************************************************************************** +Enqueues a task to server task queue and releases a worker thread, if there +is a suspended one. */ +UNIV_INTERN +void +srv_que_task_enqueue_low( +/*=====================*/ + que_thr_t* thr) /* in: query thread */ +{ + ut_ad(thr); + ut_ad(mutex_own(&kernel_mutex)); + + UT_LIST_ADD_LAST(queue, srv_sys->tasks, thr); + + srv_release_threads(SRV_WORKER, 1); +} + +/************************************************************************** +Enqueues a task to server task queue and releases a worker thread, if there +is a suspended one. */ +UNIV_INTERN +void +srv_que_task_enqueue( +/*=================*/ + que_thr_t* thr) /* in: query thread */ +{ + ut_ad(thr); + + ut_a(0); /* Under MySQL this is never called */ + + mutex_enter(&kernel_mutex); + + srv_que_task_enqueue_low(thr); + + mutex_exit(&kernel_mutex); +} diff --git a/storage/xtradb/srv/srv0srv.c b/storage/xtradb/srv/srv0srv.c new file mode 100644 index 00000000000..5afa5435b00 --- /dev/null +++ b/storage/xtradb/srv/srv0srv.c @@ -0,0 +1,3073 @@ +/***************************************************************************** + +Copyright (c) 1995, 2009, Innobase Oy. All Rights Reserved. +Copyright (c) 2008, Google Inc. + +Portions of this file contain modifications contributed and copyrighted by +Google, Inc. Those modifications are gratefully acknowledged and are described +briefly in the InnoDB documentation. The contributions by Google are +incorporated with their permission, and subject to the conditions contained in +the file COPYING.Google. + +This program is free software; you can redistribute it and/or modify it under +the terms of the GNU General Public License as published by the Free Software +Foundation; version 2 of the License. + +This program is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + +You 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 + +*****************************************************************************/ + +/****************************************************** +The database server main program + +NOTE: SQL Server 7 uses something which the documentation +calls user mode scheduled threads (UMS threads). One such +thread is usually allocated per processor. Win32 +documentation does not know any UMS threads, which suggests +that the concept is internal to SQL Server 7. It may mean that +SQL Server 7 does all the scheduling of threads itself, even +in i/o waits. We should maybe modify InnoDB to use the same +technique, because thread switches within NT may be too slow. + +SQL Server 7 also mentions fibers, which are cooperatively +scheduled threads. They can boost performance by 5 %, +according to the Delaney and Soukup's book. + +Windows 2000 will have something called thread pooling +(see msdn website), which we could possibly use. + +Another possibility could be to use some very fast user space +thread library. This might confuse NT though. + +Created 10/8/1995 Heikki Tuuri +*******************************************************/ + +/* Dummy comment */ +#include "srv0srv.h" + +#include "ut0mem.h" +#include "ut0ut.h" +#include "os0proc.h" +#include "mem0mem.h" +#include "mem0pool.h" +#include "sync0sync.h" +#include "thr0loc.h" +#include "que0que.h" +#include "srv0que.h" +#include "log0recv.h" +#include "pars0pars.h" +#include "usr0sess.h" +#include "lock0lock.h" +#include "trx0purge.h" +#include "ibuf0ibuf.h" +#include "buf0flu.h" +#include "buf0lru.h" +#include "btr0sea.h" +#include "dict0load.h" +#include "dict0boot.h" +#include "srv0start.h" +#include "row0mysql.h" +#include "ha_prototypes.h" +#include "trx0i_s.h" + +/* This is set to TRUE if the MySQL user has set it in MySQL; currently +affects only FOREIGN KEY definition parsing */ +UNIV_INTERN ibool srv_lower_case_table_names = FALSE; + +/* The following counter is incremented whenever there is some user activity +in the server */ +UNIV_INTERN ulint srv_activity_count = 0; + +/* The following is the maximum allowed duration of a lock wait. */ +UNIV_INTERN ulint srv_fatal_semaphore_wait_threshold = 600; + +/* How much data manipulation language (DML) statements need to be delayed, +in microseconds, in order to reduce the lagging of the purge thread. */ +UNIV_INTERN ulint srv_dml_needed_delay = 0; + +UNIV_INTERN ibool srv_lock_timeout_and_monitor_active = FALSE; +UNIV_INTERN ibool srv_error_monitor_active = FALSE; + +UNIV_INTERN const char* srv_main_thread_op_info = ""; + +/* Prefix used by MySQL to indicate pre-5.1 table name encoding */ +UNIV_INTERN const char srv_mysql50_table_name_prefix[9] = "#mysql50#"; + +/* Server parameters which are read from the initfile */ + +/* The following three are dir paths which are catenated before file +names, where the file name itself may also contain a path */ + +UNIV_INTERN char* srv_data_home = NULL; +#ifdef UNIV_LOG_ARCHIVE +UNIV_INTERN char* srv_arch_dir = NULL; +#endif /* UNIV_LOG_ARCHIVE */ + +/* store to its own file each table created by an user; data +dictionary tables are in the system tablespace 0 */ +UNIV_INTERN my_bool srv_file_per_table; +/* The file format to use on new *.ibd files. */ +UNIV_INTERN ulint srv_file_format = 0; +/* Whether to check file format during startup a value of +DICT_TF_FORMAT_MAX + 1 means no checking ie. FALSE. The default is to +set it to the highest format we support. */ +UNIV_INTERN ulint srv_check_file_format_at_startup = DICT_TF_FORMAT_MAX; + +#if DICT_TF_FORMAT_51 +# error "DICT_TF_FORMAT_51 must be 0!" +#endif +/* Place locks to records only i.e. do not use next-key locking except +on duplicate key checking and foreign key checking */ +UNIV_INTERN ibool srv_locks_unsafe_for_binlog = FALSE; + +UNIV_INTERN ulint srv_n_data_files = 0; +UNIV_INTERN char** srv_data_file_names = NULL; +/* size in database pages */ +UNIV_INTERN ulint* srv_data_file_sizes = NULL; + +UNIV_INTERN ibool srv_extra_undoslots = FALSE; + +/* if TRUE, then we auto-extend the last data file */ +UNIV_INTERN ibool srv_auto_extend_last_data_file = FALSE; +/* if != 0, this tells the max size auto-extending may increase the +last data file size */ +UNIV_INTERN ulint srv_last_file_size_max = 0; +/* If the last data file is auto-extended, we add this +many pages to it at a time */ +UNIV_INTERN ulong srv_auto_extend_increment = 8; +UNIV_INTERN ulint* srv_data_file_is_raw_partition = NULL; + +/* If the following is TRUE we do not allow inserts etc. This protects +the user from forgetting the 'newraw' keyword to my.cnf */ + +UNIV_INTERN ibool srv_created_new_raw = FALSE; + +UNIV_INTERN char** srv_log_group_home_dirs = NULL; + +UNIV_INTERN ulint srv_n_log_groups = ULINT_MAX; +UNIV_INTERN ulint srv_n_log_files = ULINT_MAX; +/* size in database pages */ +UNIV_INTERN ulint srv_log_file_size = ULINT_MAX; +/* size in database pages */ +UNIV_INTERN ulint srv_log_buffer_size = ULINT_MAX; +UNIV_INTERN ulong srv_flush_log_at_trx_commit = 1; + +UNIV_INTERN ulint srv_show_locks_held = 10; +UNIV_INTERN ulint srv_show_verbose_locks = 0; + + +/* The sort order table of the MySQL latin1_swedish_ci character set +collation */ +UNIV_INTERN const byte* srv_latin1_ordering; + +/* use os/external memory allocator */ +UNIV_INTERN my_bool srv_use_sys_malloc = TRUE; +/* requested size in kilobytes */ +UNIV_INTERN ulint srv_buf_pool_size = ULINT_MAX; +/* previously requested size */ +UNIV_INTERN ulint srv_buf_pool_old_size; +/* current size in kilobytes */ +UNIV_INTERN ulint srv_buf_pool_curr_size = 0; +/* size in bytes */ +UNIV_INTERN ulint srv_mem_pool_size = ULINT_MAX; +UNIV_INTERN ulint srv_lock_table_size = ULINT_MAX; + +UNIV_INTERN ulint srv_n_file_io_threads = ULINT_MAX; +UNIV_INTERN ulint srv_n_read_io_threads = 1; +UNIV_INTERN ulint srv_n_write_io_threads = 1; + +#ifdef UNIV_LOG_ARCHIVE +UNIV_INTERN ibool srv_log_archive_on = FALSE; +UNIV_INTERN ibool srv_archive_recovery = 0; +UNIV_INTERN ib_uint64_t srv_archive_recovery_limit_lsn; +#endif /* UNIV_LOG_ARCHIVE */ + +/* This parameter is used to throttle the number of insert buffers that are +merged in a batch. By increasing this parameter on a faster disk you can +possibly reduce the number of I/O operations performed to complete the +merge operation. The value of this parameter is used as is by the +background loop when the system is idle (low load), on a busy system +the parameter is scaled down by a factor of 4, this is to avoid putting +a heavier load on the I/O sub system. */ + +UNIV_INTERN ulong srv_insert_buffer_batch_size = 20; + +UNIV_INTERN char* srv_file_flush_method_str = NULL; +UNIV_INTERN ulint srv_unix_file_flush_method = SRV_UNIX_FSYNC; +UNIV_INTERN ulint srv_win_file_flush_method = SRV_WIN_IO_UNBUFFERED; + +UNIV_INTERN ulint srv_max_n_open_files = 300; + +/* The InnoDB main thread tries to keep the ratio of modified pages +in the buffer pool to all database pages in the buffer pool smaller than +the following number. But it is not guaranteed that the value stays below +that during a time of heavy update/insert activity. */ + +UNIV_INTERN ulong srv_max_buf_pool_modified_pct = 90; + +/* variable counts amount of data read in total (in bytes) */ +UNIV_INTERN ulint srv_data_read = 0; + +/* here we count the amount of data written in total (in bytes) */ +UNIV_INTERN ulint srv_data_written = 0; + +/* the number of the log write requests done */ +UNIV_INTERN ulint srv_log_write_requests = 0; + +/* the number of physical writes to the log performed */ +UNIV_INTERN ulint srv_log_writes = 0; + +/* amount of data written to the log files in bytes */ +UNIV_INTERN ulint srv_os_log_written = 0; + +/* amount of writes being done to the log files */ +UNIV_INTERN ulint srv_os_log_pending_writes = 0; + +/* we increase this counter, when there we don't have enough space in the +log buffer and have to flush it */ +UNIV_INTERN ulint srv_log_waits = 0; + +/* this variable counts the amount of times, when the doublewrite buffer +was flushed */ +UNIV_INTERN ulint srv_dblwr_writes = 0; + +/* here we store the number of pages that have been flushed to the +doublewrite buffer */ +UNIV_INTERN ulint srv_dblwr_pages_written = 0; + +/* in this variable we store the number of write requests issued */ +UNIV_INTERN ulint srv_buf_pool_write_requests = 0; + +/* here we store the number of times when we had to wait for a free page +in the buffer pool. It happens when the buffer pool is full and we need +to make a flush, in order to be able to read or create a page. */ +UNIV_INTERN ulint srv_buf_pool_wait_free = 0; + +/* variable to count the number of pages that were written from buffer +pool to the disk */ +UNIV_INTERN ulint srv_buf_pool_flushed = 0; + +/* variable to count the number of buffer pool reads that led to the +reading of a disk page */ +UNIV_INTERN ulint srv_buf_pool_reads = 0; + +/* variable to count the number of sequential read-aheads */ +UNIV_INTERN ulint srv_read_ahead_seq = 0; + +/* variable to count the number of random read-aheads */ +UNIV_INTERN ulint srv_read_ahead_rnd = 0; + +/* structure to pass status variables to MySQL */ +UNIV_INTERN export_struc export_vars; + +/* If the following is != 0 we do not allow inserts etc. This protects +the user from forgetting the innodb_force_recovery keyword to my.cnf */ + +UNIV_INTERN ulint srv_force_recovery = 0; +/*-----------------------*/ +/* We are prepared for a situation that we have this many threads waiting for +a semaphore inside InnoDB. innobase_start_or_create_for_mysql() sets the +value. */ + +UNIV_INTERN ulint srv_max_n_threads = 0; + +/* The following controls how many threads we let inside InnoDB concurrently: +threads waiting for locks are not counted into the number because otherwise +we could get a deadlock. MySQL creates a thread for each user session, and +semaphore contention and convoy problems can occur withput this restriction. +Value 10 should be good if there are less than 4 processors + 4 disks in the +computer. Bigger computers need bigger values. Value 0 will disable the +concurrency check. */ + +UNIV_INTERN ibool srv_thread_concurrency_timer_based = FALSE; +UNIV_INTERN ulong srv_thread_concurrency = 0; +UNIV_INTERN ulong srv_commit_concurrency = 0; + +/* this mutex protects srv_conc data structures */ +UNIV_INTERN os_fast_mutex_t srv_conc_mutex; +/* number of transactions that have declared_to_be_inside_innodb set. +It used to be a non-error for this value to drop below zero temporarily. +This is no longer true. We'll, however, keep the lint datatype to add +assertions to catch any corner cases that we may have missed. */ +UNIV_INTERN lint srv_conc_n_threads = 0; +/* number of OS threads waiting in the FIFO for a permission to enter +InnoDB */ +UNIV_INTERN ulint srv_conc_n_waiting_threads = 0; + +typedef struct srv_conc_slot_struct srv_conc_slot_t; +struct srv_conc_slot_struct{ + os_event_t event; /* event to wait */ + ibool reserved; /* TRUE if slot + reserved */ + ibool wait_ended; /* TRUE when another + thread has already set + the event and the + thread in this slot is + free to proceed; but + reserved may still be + TRUE at that point */ + UT_LIST_NODE_T(srv_conc_slot_t) srv_conc_queue; /* queue node */ +}; + +/* queue of threads waiting to get in */ +UNIV_INTERN UT_LIST_BASE_NODE_T(srv_conc_slot_t) srv_conc_queue; +/* array of wait slots */ +UNIV_INTERN srv_conc_slot_t* srv_conc_slots; + +/* Number of times a thread is allowed to enter InnoDB within the same +SQL query after it has once got the ticket at srv_conc_enter_innodb */ +#define SRV_FREE_TICKETS_TO_ENTER srv_n_free_tickets_to_enter +#define SRV_THREAD_SLEEP_DELAY srv_thread_sleep_delay +/*-----------------------*/ +/* If the following is set to 1 then we do not run purge and insert buffer +merge to completion before shutdown. If it is set to 2, do not even flush the +buffer pool to data files at the shutdown: we effectively 'crash' +InnoDB (but lose no committed transactions). */ +UNIV_INTERN ulint srv_fast_shutdown = 0; + +/* Generate a innodb_status.<pid> file */ +UNIV_INTERN ibool srv_innodb_status = FALSE; + +/* When estimating number of different key values in an index, sample +this many index pages */ +UNIV_INTERN unsigned long long srv_stats_sample_pages = 8; +UNIV_INTERN ulint srv_stats_method = 0; +UNIV_INTERN ulint srv_stats_auto_update = 1; + +UNIV_INTERN ibool srv_use_doublewrite_buf = TRUE; +UNIV_INTERN ibool srv_use_checksums = TRUE; + +UNIV_INTERN ibool srv_set_thread_priorities = TRUE; +UNIV_INTERN int srv_query_thread_priority = 0; + +UNIV_INTERN ulong srv_replication_delay = 0; + +UNIV_INTERN ulint srv_io_capacity = 100; + +/* Returns the number of IO operations that is X percent of the capacity. +PCT_IO(5) -> returns the number of IO operations that is 5% of the max +where max is srv_io_capacity. */ +#define PCT_IO(pct) ((ulint) (srv_io_capacity * ((double) pct / 100.0))) + +UNIV_INTERN long long srv_ibuf_max_size = 0; +UNIV_INTERN ulint srv_ibuf_active_contract = 0; /* 0:disable 1:enable */ +UNIV_INTERN ulint srv_ibuf_accel_rate = 100; +#define PCT_IBUF_IO(pct) ((ulint) (srv_io_capacity * srv_ibuf_accel_rate * ((double) pct / 10000.0))) + +UNIV_INTERN ulint srv_flush_neighbor_pages = 1; /* 0:disable 1:enable */ + +UNIV_INTERN ulint srv_enable_unsafe_group_commit = 0; /* 0:disable 1:enable */ +UNIV_INTERN ulint srv_read_ahead = 3; /* 1: random 2: linear 3: Both */ +UNIV_INTERN ulint srv_adaptive_checkpoint = 0; /* 0: none 1: reflex 2: estimate */ + +UNIV_INTERN ulint srv_expand_import = 0; /* 0:disable 1:enable */ + +UNIV_INTERN ulint srv_extra_rsegments = 0; /* extra rseg for users */ +UNIV_INTERN ulint srv_dict_size_limit = 0; +/*-------------------------------------------*/ +UNIV_INTERN ulong srv_n_spin_wait_rounds = 20; +UNIV_INTERN ulong srv_n_free_tickets_to_enter = 500; +UNIV_INTERN ulong srv_thread_sleep_delay = 10000; +UNIV_INTERN ulint srv_spin_wait_delay = 5; +UNIV_INTERN ulint srv_spins_microsec = 50; +UNIV_INTERN ibool srv_priority_boost = TRUE; + +#ifdef UNIV_DEBUG +UNIV_INTERN ibool srv_print_thread_releases = FALSE; +UNIV_INTERN ibool srv_print_lock_waits = FALSE; +UNIV_INTERN ibool srv_print_buf_io = FALSE; +UNIV_INTERN ibool srv_print_log_io = FALSE; +UNIV_INTERN ibool srv_print_latch_waits = FALSE; +#endif /* UNIV_DEBUG */ + +UNIV_INTERN ulint srv_n_rows_inserted = 0; +UNIV_INTERN ulint srv_n_rows_updated = 0; +UNIV_INTERN ulint srv_n_rows_deleted = 0; +UNIV_INTERN ulint srv_n_rows_read = 0; +#ifndef UNIV_HOTBACKUP +static ulint srv_n_rows_inserted_old = 0; +static ulint srv_n_rows_updated_old = 0; +static ulint srv_n_rows_deleted_old = 0; +static ulint srv_n_rows_read_old = 0; +#endif /* !UNIV_HOTBACKUP */ + +UNIV_INTERN ulint srv_n_lock_wait_count = 0; +UNIV_INTERN ulint srv_n_lock_wait_current_count = 0; +UNIV_INTERN ib_int64_t srv_n_lock_wait_time = 0; +UNIV_INTERN ulint srv_n_lock_max_wait_time = 0; + + +/* + Set the following to 0 if you want InnoDB to write messages on + stderr on startup/shutdown +*/ +UNIV_INTERN ibool srv_print_verbose_log = TRUE; +UNIV_INTERN ibool srv_print_innodb_monitor = FALSE; +UNIV_INTERN ibool srv_print_innodb_lock_monitor = FALSE; +UNIV_INTERN ibool srv_print_innodb_tablespace_monitor = FALSE; +UNIV_INTERN ibool srv_print_innodb_table_monitor = FALSE; + +/* Array of English strings describing the current state of an +i/o handler thread */ + +UNIV_INTERN const char* srv_io_thread_op_info[SRV_MAX_N_IO_THREADS]; +UNIV_INTERN const char* srv_io_thread_function[SRV_MAX_N_IO_THREADS]; + +UNIV_INTERN time_t srv_last_monitor_time; + +UNIV_INTERN mutex_t srv_innodb_monitor_mutex; + +/* Mutex for locking srv_monitor_file */ +UNIV_INTERN mutex_t srv_monitor_file_mutex; +/* Temporary file for innodb monitor output */ +UNIV_INTERN FILE* srv_monitor_file; +/* Mutex for locking srv_dict_tmpfile. +This mutex has a very high rank; threads reserving it should not +be holding any InnoDB latches. */ +UNIV_INTERN mutex_t srv_dict_tmpfile_mutex; +/* Temporary file for output from the data dictionary */ +UNIV_INTERN FILE* srv_dict_tmpfile; +/* Mutex for locking srv_misc_tmpfile. +This mutex has a very low rank; threads reserving it should not +acquire any further latches or sleep before releasing this one. */ +UNIV_INTERN mutex_t srv_misc_tmpfile_mutex; +/* Temporary file for miscellanous diagnostic output */ +UNIV_INTERN FILE* srv_misc_tmpfile; + +UNIV_INTERN ulint srv_main_thread_process_no = 0; +UNIV_INTERN ulint srv_main_thread_id = 0; + +/* + IMPLEMENTATION OF THE SERVER MAIN PROGRAM + ========================================= + +There is the following analogue between this database +server and an operating system kernel: + +DB concept equivalent OS concept +---------- --------------------- +transaction -- process; + +query thread -- thread; + +lock -- semaphore; + +transaction set to +the rollback state -- kill signal delivered to a process; + +kernel -- kernel; + +query thread execution: +(a) without kernel mutex +reserved -- process executing in user mode; +(b) with kernel mutex reserved + -- process executing in kernel mode; + +The server is controlled by a master thread which runs at +a priority higher than normal, that is, higher than user threads. +It sleeps most of the time, and wakes up, say, every 300 milliseconds, +to check whether there is anything happening in the server which +requires intervention of the master thread. Such situations may be, +for example, when flushing of dirty blocks is needed in the buffer +pool or old version of database rows have to be cleaned away. + +The threads which we call user threads serve the queries of +the clients and input from the console of the server. +They run at normal priority. The server may have several +communications endpoints. A dedicated set of user threads waits +at each of these endpoints ready to receive a client request. +Each request is taken by a single user thread, which then starts +processing and, when the result is ready, sends it to the client +and returns to wait at the same endpoint the thread started from. + +So, we do not have dedicated communication threads listening at +the endpoints and dealing the jobs to dedicated worker threads. +Our architecture saves one thread swithch per request, compared +to the solution with dedicated communication threads +which amounts to 15 microseconds on 100 MHz Pentium +running NT. If the client +is communicating over a network, this saving is negligible, but +if the client resides in the same machine, maybe in an SMP machine +on a different processor from the server thread, the saving +can be important as the threads can communicate over shared +memory with an overhead of a few microseconds. + +We may later implement a dedicated communication thread solution +for those endpoints which communicate over a network. + +Our solution with user threads has two problems: for each endpoint +there has to be a number of listening threads. If there are many +communication endpoints, it may be difficult to set the right number +of concurrent threads in the system, as many of the threads +may always be waiting at less busy endpoints. Another problem +is queuing of the messages, as the server internally does not +offer any queue for jobs. + +Another group of user threads is intended for splitting the +queries and processing them in parallel. Let us call these +parallel communication threads. These threads are waiting for +parallelized tasks, suspended on event semaphores. + +A single user thread waits for input from the console, +like a command to shut the database. + +Utility threads are a different group of threads which takes +care of the buffer pool flushing and other, mainly background +operations, in the server. +Some of these utility threads always run at a lower than normal +priority, so that they are always in background. Some of them +may dynamically boost their priority by the pri_adjust function, +even to higher than normal priority, if their task becomes urgent. +The running of utilities is controlled by high- and low-water marks +of urgency. The urgency may be measured by the number of dirty blocks +in the buffer pool, in the case of the flush thread, for example. +When the high-water mark is exceeded, an utility starts running, until +the urgency drops under the low-water mark. Then the utility thread +suspend itself to wait for an event. The master thread is +responsible of signaling this event when the utility thread is +again needed. + +For each individual type of utility, some threads always remain +at lower than normal priority. This is because pri_adjust is implemented +so that the threads at normal or higher priority control their +share of running time by calling sleep. Thus, if the load of the +system sudenly drops, these threads cannot necessarily utilize +the system fully. The background priority threads make up for this, +starting to run when the load drops. + +When there is no activity in the system, also the master thread +suspends itself to wait for an event making +the server totally silent. The responsibility to signal this +event is on the user thread which again receives a message +from a client. + +There is still one complication in our server design. If a +background utility thread obtains a resource (e.g., mutex) needed by a user +thread, and there is also some other user activity in the system, +the user thread may have to wait indefinitely long for the +resource, as the OS does not schedule a background thread if +there is some other runnable user thread. This problem is called +priority inversion in real-time programming. + +One solution to the priority inversion problem would be to +keep record of which thread owns which resource and +in the above case boost the priority of the background thread +so that it will be scheduled and it can release the resource. +This solution is called priority inheritance in real-time programming. +A drawback of this solution is that the overhead of acquiring a mutex +increases slightly, maybe 0.2 microseconds on a 100 MHz Pentium, because +the thread has to call os_thread_get_curr_id. +This may be compared to 0.5 microsecond overhead for a mutex lock-unlock +pair. Note that the thread +cannot store the information in the resource, say mutex, itself, +because competing threads could wipe out the information if it is +stored before acquiring the mutex, and if it stored afterwards, +the information is outdated for the time of one machine instruction, +at least. (To be precise, the information could be stored to +lock_word in mutex if the machine supports atomic swap.) + +The above solution with priority inheritance may become actual in the +future, but at the moment we plan to implement a more coarse solution, +which could be called a global priority inheritance. If a thread +has to wait for a long time, say 300 milliseconds, for a resource, +we just guess that it may be waiting for a resource owned by a background +thread, and boost the the priority of all runnable background threads +to the normal level. The background threads then themselves adjust +their fixed priority back to background after releasing all resources +they had (or, at some fixed points in their program code). + +What is the performance of the global priority inheritance solution? +We may weigh the length of the wait time 300 milliseconds, during +which the system processes some other thread +to the cost of boosting the priority of each runnable background +thread, rescheduling it, and lowering the priority again. +On 100 MHz Pentium + NT this overhead may be of the order 100 +microseconds per thread. So, if the number of runnable background +threads is not very big, say < 100, the cost is tolerable. +Utility threads probably will access resources used by +user threads not very often, so collisions of user threads +to preempted utility threads should not happen very often. + +The thread table contains +information of the current status of each thread existing in the system, +and also the event semaphores used in suspending the master thread +and utility and parallel communication threads when they have nothing to do. +The thread table can be seen as an analogue to the process table +in a traditional Unix implementation. + +The thread table is also used in the global priority inheritance +scheme. This brings in one additional complication: threads accessing +the thread table must have at least normal fixed priority, +because the priority inheritance solution does not work if a background +thread is preempted while possessing the mutex protecting the thread table. +So, if a thread accesses the thread table, its priority has to be +boosted at least to normal. This priority requirement can be seen similar to +the privileged mode used when processing the kernel calls in traditional +Unix.*/ + +/* Thread slot in the thread table */ +struct srv_slot_struct{ + os_thread_id_t id; /* thread id */ + os_thread_t handle; /* thread handle */ + unsigned type:3; /* thread type: user, utility etc. */ + unsigned in_use:1; /* TRUE if this slot is in use */ + unsigned suspended:1; /* TRUE if the thread is waiting + for the event of this slot */ + ib_time_t suspend_time; /* time when the thread was + suspended */ + os_event_t event; /* event used in suspending the + thread when it has nothing to do */ + que_thr_t* thr; /* suspended query thread (only + used for MySQL threads) */ +}; + +/* Table for MySQL threads where they will be suspended to wait for locks */ +UNIV_INTERN srv_slot_t* srv_mysql_table = NULL; + +UNIV_INTERN os_event_t srv_lock_timeout_thread_event; + +UNIV_INTERN srv_sys_t* srv_sys = NULL; + +/* padding to prevent other memory update hotspots from residing on +the same memory cache line */ +UNIV_INTERN byte srv_pad1[64]; +/* mutex protecting the server, trx structs, query threads, and lock table */ +UNIV_INTERN mutex_t* kernel_mutex_temp; +/* padding to prevent other memory update hotspots from residing on +the same memory cache line */ +UNIV_INTERN byte srv_pad2[64]; + +#if 0 +/* The following three values measure the urgency of the jobs of +buffer, version, and insert threads. They may vary from 0 - 1000. +The server mutex protects all these variables. The low-water values +tell that the server can acquiesce the utility when the value +drops below this low-water mark. */ + +static ulint srv_meter[SRV_MASTER + 1]; +static ulint srv_meter_low_water[SRV_MASTER + 1]; +static ulint srv_meter_high_water[SRV_MASTER + 1]; +static ulint srv_meter_high_water2[SRV_MASTER + 1]; +static ulint srv_meter_foreground[SRV_MASTER + 1]; +#endif + +/* The following values give info about the activity going on in +the database. They are protected by the server mutex. The arrays +are indexed by the type of the thread. */ + +UNIV_INTERN ulint srv_n_threads_active[SRV_MASTER + 1]; +UNIV_INTERN ulint srv_n_threads[SRV_MASTER + 1]; + +static +void +srv_align_spins_microsec(void) +{ + ulint start_sec, end_sec; + ulint start_usec, end_usec; + ib_uint64_t usecs; + + /* change temporary */ + srv_spins_microsec = 1; + + if (ut_usectime(&start_sec, &start_usec)) { + srv_spins_microsec = 50; + goto end; + } + + ut_delay(100000); + + if (ut_usectime(&end_sec, &end_usec)) { + srv_spins_microsec = 50; + goto end; + } + + usecs = (end_sec - start_sec) * 1000000LL + (end_usec - start_usec); + + if (usecs) { + srv_spins_microsec = 100000 / usecs; + if (srv_spins_microsec == 0) + srv_spins_microsec = 1; + if (srv_spins_microsec > 50) + srv_spins_microsec = 50; + } else { + srv_spins_microsec = 50; + } +end: + if (srv_spins_microsec != 50) + fprintf(stderr, + "InnoDB: unit of spin count at ut_delay() is aligned to %lu\n", + srv_spins_microsec); +} + +/************************************************************************* +Sets the info describing an i/o thread current state. */ +UNIV_INTERN +void +srv_set_io_thread_op_info( +/*======================*/ + ulint i, /* in: the 'segment' of the i/o thread */ + const char* str) /* in: constant char string describing the + state */ +{ + ut_a(i < SRV_MAX_N_IO_THREADS); + + srv_io_thread_op_info[i] = str; +} + +/************************************************************************* +Accessor function to get pointer to n'th slot in the server thread +table. */ +static +srv_slot_t* +srv_table_get_nth_slot( +/*===================*/ + /* out: pointer to the slot */ + ulint index) /* in: index of the slot */ +{ + ut_a(index < OS_THREAD_MAX_N); + + return(srv_sys->threads + index); +} + +#ifndef UNIV_HOTBACKUP +/************************************************************************* +Gets the number of threads in the system. */ +UNIV_INTERN +ulint +srv_get_n_threads(void) +/*===================*/ +{ + ulint i; + ulint n_threads = 0; + + mutex_enter(&kernel_mutex); + + for (i = SRV_COM; i < SRV_MASTER + 1; i++) { + + n_threads += srv_n_threads[i]; + } + + mutex_exit(&kernel_mutex); + + return(n_threads); +} + +/************************************************************************* +Reserves a slot in the thread table for the current thread. Also creates the +thread local storage struct for the current thread. NOTE! The server mutex +has to be reserved by the caller! */ +static +ulint +srv_table_reserve_slot( +/*===================*/ + /* out: reserved slot index */ + enum srv_thread_type type) /* in: type of the thread */ +{ + srv_slot_t* slot; + ulint i; + + ut_a(type > 0); + ut_a(type <= SRV_MASTER); + + i = 0; + slot = srv_table_get_nth_slot(i); + + while (slot->in_use) { + i++; + slot = srv_table_get_nth_slot(i); + } + + ut_a(slot->in_use == FALSE); + + slot->in_use = TRUE; + slot->suspended = FALSE; + slot->type = type; + slot->id = os_thread_get_curr_id(); + slot->handle = os_thread_get_curr(); + + thr_local_create(); + + thr_local_set_slot_no(os_thread_get_curr_id(), i); + + return(i); +} + +/************************************************************************* +Suspends the calling thread to wait for the event in its thread slot. +NOTE! The server mutex has to be reserved by the caller! */ +static +os_event_t +srv_suspend_thread(void) +/*====================*/ + /* out: event for the calling thread to wait */ +{ + srv_slot_t* slot; + os_event_t event; + ulint slot_no; + enum srv_thread_type type; + + ut_ad(mutex_own(&kernel_mutex)); + + slot_no = thr_local_get_slot_no(os_thread_get_curr_id()); + + if (srv_print_thread_releases) { + fprintf(stderr, + "Suspending thread %lu to slot %lu\n", + (ulong) os_thread_get_curr_id(), (ulong) slot_no); + } + + slot = srv_table_get_nth_slot(slot_no); + + type = slot->type; + + ut_ad(type >= SRV_WORKER); + ut_ad(type <= SRV_MASTER); + + event = slot->event; + + slot->suspended = TRUE; + + ut_ad(srv_n_threads_active[type] > 0); + + srv_n_threads_active[type]--; + + os_event_reset(event); + + return(event); +} +#endif /* !UNIV_HOTBACKUP */ + +/************************************************************************* +Releases threads of the type given from suspension in the thread table. +NOTE! The server mutex has to be reserved by the caller! */ +UNIV_INTERN +ulint +srv_release_threads( +/*================*/ + /* out: number of threads + released: this may be < n if + not enough threads were + suspended at the moment */ + enum srv_thread_type type, /* in: thread type */ + ulint n) /* in: number of threads to release */ +{ + srv_slot_t* slot; + ulint i; + ulint count = 0; + + ut_ad(type >= SRV_WORKER); + ut_ad(type <= SRV_MASTER); + ut_ad(n > 0); + ut_ad(mutex_own(&kernel_mutex)); + + for (i = 0; i < OS_THREAD_MAX_N; i++) { + + slot = srv_table_get_nth_slot(i); + + if (slot->in_use && slot->type == type && slot->suspended) { + + slot->suspended = FALSE; + + srv_n_threads_active[type]++; + + os_event_set(slot->event); + + if (srv_print_thread_releases) { + fprintf(stderr, + "Releasing thread %lu type %lu" + " from slot %lu\n", + (ulong) slot->id, (ulong) type, + (ulong) i); + } + + count++; + + if (count == n) { + break; + } + } + } + + return(count); +} + +/************************************************************************* +Returns the calling thread type. */ +UNIV_INTERN +enum srv_thread_type +srv_get_thread_type(void) +/*=====================*/ + /* out: SRV_COM, ... */ +{ + ulint slot_no; + srv_slot_t* slot; + enum srv_thread_type type; + + mutex_enter(&kernel_mutex); + + slot_no = thr_local_get_slot_no(os_thread_get_curr_id()); + + slot = srv_table_get_nth_slot(slot_no); + + type = slot->type; + + ut_ad(type >= SRV_WORKER); + ut_ad(type <= SRV_MASTER); + + mutex_exit(&kernel_mutex); + + return(type); +} + +/************************************************************************* +Initializes the server. */ +UNIV_INTERN +void +srv_init(void) +/*==========*/ +{ + srv_conc_slot_t* conc_slot; + srv_slot_t* slot; + dict_table_t* table; + ulint i; + + srv_align_spins_microsec(); + + srv_sys = mem_alloc(sizeof(srv_sys_t)); + + kernel_mutex_temp = mem_alloc(sizeof(mutex_t)); + mutex_create(&kernel_mutex, SYNC_KERNEL); + + mutex_create(&srv_innodb_monitor_mutex, SYNC_NO_ORDER_CHECK); + + srv_sys->threads = mem_alloc(OS_THREAD_MAX_N * sizeof(srv_slot_t)); + + for (i = 0; i < OS_THREAD_MAX_N; i++) { + slot = srv_table_get_nth_slot(i); + slot->in_use = FALSE; + slot->type=0; /* Avoid purify errors */ + slot->event = os_event_create(NULL); + ut_a(slot->event); + } + + srv_mysql_table = mem_alloc(OS_THREAD_MAX_N * sizeof(srv_slot_t)); + + for (i = 0; i < OS_THREAD_MAX_N; i++) { + slot = srv_mysql_table + i; + slot->in_use = FALSE; + slot->type = 0; + slot->event = os_event_create(NULL); + ut_a(slot->event); + } + + srv_lock_timeout_thread_event = os_event_create(NULL); + + for (i = 0; i < SRV_MASTER + 1; i++) { + srv_n_threads_active[i] = 0; + srv_n_threads[i] = 0; +#if 0 + srv_meter[i] = 30; + srv_meter_low_water[i] = 50; + srv_meter_high_water[i] = 100; + srv_meter_high_water2[i] = 200; + srv_meter_foreground[i] = 250; +#endif + } + + UT_LIST_INIT(srv_sys->tasks); + + /* create dummy table and index for old-style infimum and supremum */ + table = dict_mem_table_create("SYS_DUMMY1", + DICT_HDR_SPACE, 1, 0); + dict_mem_table_add_col(table, NULL, NULL, DATA_CHAR, + DATA_ENGLISH | DATA_NOT_NULL, 8); + + srv_sys->dummy_ind1 = dict_mem_index_create( + "SYS_DUMMY1", "SYS_DUMMY1", DICT_HDR_SPACE, 0, 1); + dict_index_add_col(srv_sys->dummy_ind1, table, + dict_table_get_nth_col(table, 0), 0); + srv_sys->dummy_ind1->table = table; + /* create dummy table and index for new-style infimum and supremum */ + table = dict_mem_table_create("SYS_DUMMY2", + DICT_HDR_SPACE, 1, DICT_TF_COMPACT); + dict_mem_table_add_col(table, NULL, NULL, DATA_CHAR, + DATA_ENGLISH | DATA_NOT_NULL, 8); + srv_sys->dummy_ind2 = dict_mem_index_create( + "SYS_DUMMY2", "SYS_DUMMY2", DICT_HDR_SPACE, 0, 1); + dict_index_add_col(srv_sys->dummy_ind2, table, + dict_table_get_nth_col(table, 0), 0); + srv_sys->dummy_ind2->table = table; + + /* avoid ut_ad(index->cached) in dict_index_get_n_unique_in_tree */ + srv_sys->dummy_ind1->cached = srv_sys->dummy_ind2->cached = TRUE; + + /* Init the server concurrency restriction data structures */ + + os_fast_mutex_init(&srv_conc_mutex); + + UT_LIST_INIT(srv_conc_queue); + + srv_conc_slots = mem_alloc(OS_THREAD_MAX_N * sizeof(srv_conc_slot_t)); + + for (i = 0; i < OS_THREAD_MAX_N; i++) { + conc_slot = srv_conc_slots + i; + conc_slot->reserved = FALSE; + conc_slot->event = os_event_create(NULL); + ut_a(conc_slot->event); + } + + /* Initialize some INFORMATION SCHEMA internal structures */ + trx_i_s_cache_init(trx_i_s_cache); +} + +/************************************************************************* +Frees the OS fast mutex created in srv_init(). */ +UNIV_INTERN +void +srv_free(void) +/*==========*/ +{ + os_fast_mutex_free(&srv_conc_mutex); +} + +/************************************************************************* +Initializes the synchronization primitives, memory system, and the thread +local storage. */ +UNIV_INTERN +void +srv_general_init(void) +/*==================*/ +{ + ut_mem_init(); + os_sync_init(); + sync_init(); + mem_init(srv_mem_pool_size); + thr_local_init(); +} + +/*======================= InnoDB Server FIFO queue =======================*/ + +/* Maximum allowable purge history length. <=0 means 'infinite'. */ +UNIV_INTERN ulong srv_max_purge_lag = 0; + +/************************************************************************* +Puts an OS thread to wait if there are too many concurrent threads +(>= srv_thread_concurrency) inside InnoDB. The threads wait in a FIFO queue. */ + +#ifdef INNODB_RW_LOCKS_USE_ATOMICS +static void +enter_innodb_with_tickets(trx_t* trx) +{ + trx->declared_to_be_inside_innodb = TRUE; + trx->n_tickets_to_enter_innodb = SRV_FREE_TICKETS_TO_ENTER; + return; +} + +static void +srv_conc_enter_innodb_timer_based(trx_t* trx) +{ + lint conc_n_threads; + ibool has_yielded = FALSE; + ulint has_slept = 0; + + if (trx->declared_to_be_inside_innodb) { + ut_print_timestamp(stderr); + fputs( +" InnoDB: Error: trying to declare trx to enter InnoDB, but\n" +"InnoDB: it already is declared.\n", stderr); + trx_print(stderr, trx, 0); + putc('\n', stderr); + } +retry: + if (srv_conc_n_threads < (lint) srv_thread_concurrency) { + conc_n_threads = __sync_add_and_fetch(&srv_conc_n_threads, 1); + if (conc_n_threads <= (lint) srv_thread_concurrency) { + enter_innodb_with_tickets(trx); + return; + } + __sync_add_and_fetch(&srv_conc_n_threads, -1); + } + if (!has_yielded) + { + has_yielded = TRUE; + os_thread_yield(); + goto retry; + } + if (trx->has_search_latch + || NULL != UT_LIST_GET_FIRST(trx->trx_locks)) { + + conc_n_threads = __sync_add_and_fetch(&srv_conc_n_threads, 1); + enter_innodb_with_tickets(trx); + return; + } + if (has_slept < 2) + { + trx->op_info = "sleeping before entering InnoDB"; + os_thread_sleep(10000); + trx->op_info = ""; + has_slept++; + } + conc_n_threads = __sync_add_and_fetch(&srv_conc_n_threads, 1); + enter_innodb_with_tickets(trx); + return; +} + +static void +srv_conc_exit_innodb_timer_based(trx_t* trx) +{ + __sync_add_and_fetch(&srv_conc_n_threads, -1); + trx->declared_to_be_inside_innodb = FALSE; + trx->n_tickets_to_enter_innodb = 0; + return; +} +#endif + +UNIV_INTERN +void +srv_conc_enter_innodb( +/*==================*/ + trx_t* trx) /* in: transaction object associated with the + thread */ +{ + ibool has_slept = FALSE; + srv_conc_slot_t* slot = NULL; + ulint i; + + if (trx->mysql_thd != NULL + && thd_is_replication_slave_thread(trx->mysql_thd)) { + + UT_WAIT_FOR(srv_conc_n_threads + < (lint)srv_thread_concurrency, + srv_replication_delay * 1000); + + return; + } + + /* If trx has 'free tickets' to enter the engine left, then use one + such ticket */ + + if (trx->n_tickets_to_enter_innodb > 0) { + trx->n_tickets_to_enter_innodb--; + + return; + } + +#ifdef INNODB_RW_LOCKS_USE_ATOMICS + if (srv_thread_concurrency_timer_based) { + srv_conc_enter_innodb_timer_based(trx); + return; + } +#endif + + os_fast_mutex_lock(&srv_conc_mutex); +retry: + if (trx->declared_to_be_inside_innodb) { + ut_print_timestamp(stderr); + fputs(" InnoDB: Error: trying to declare trx" + " to enter InnoDB, but\n" + "InnoDB: it already is declared.\n", stderr); + trx_print(stderr, trx, 0); + putc('\n', stderr); + os_fast_mutex_unlock(&srv_conc_mutex); + + return; + } + + ut_ad(srv_conc_n_threads >= 0); + + if (srv_conc_n_threads < (lint)srv_thread_concurrency) { + + srv_conc_n_threads++; + trx->declared_to_be_inside_innodb = TRUE; + trx->n_tickets_to_enter_innodb = SRV_FREE_TICKETS_TO_ENTER; + + os_fast_mutex_unlock(&srv_conc_mutex); + + return; + } + + /* If the transaction is not holding resources, let it sleep + for SRV_THREAD_SLEEP_DELAY microseconds, and try again then */ + + if (!has_slept && !trx->has_search_latch + && NULL == UT_LIST_GET_FIRST(trx->trx_locks)) { + + has_slept = TRUE; /* We let it sleep only once to avoid + starvation */ + + srv_conc_n_waiting_threads++; + + os_fast_mutex_unlock(&srv_conc_mutex); + + trx->op_info = "sleeping before joining InnoDB queue"; + + /* Peter Zaitsev suggested that we take the sleep away + altogether. But the sleep may be good in pathological + situations of lots of thread switches. Simply put some + threads aside for a while to reduce the number of thread + switches. */ + if (SRV_THREAD_SLEEP_DELAY > 0) { + os_thread_sleep(SRV_THREAD_SLEEP_DELAY); + } + + trx->op_info = ""; + + os_fast_mutex_lock(&srv_conc_mutex); + + srv_conc_n_waiting_threads--; + + goto retry; + } + + /* Too many threads inside: put the current thread to a queue */ + + for (i = 0; i < OS_THREAD_MAX_N; i++) { + slot = srv_conc_slots + i; + + if (!slot->reserved) { + + break; + } + } + + if (i == OS_THREAD_MAX_N) { + /* Could not find a free wait slot, we must let the + thread enter */ + + srv_conc_n_threads++; + trx->declared_to_be_inside_innodb = TRUE; + trx->n_tickets_to_enter_innodb = 0; + + os_fast_mutex_unlock(&srv_conc_mutex); + + return; + } + + /* Release possible search system latch this thread has */ + if (trx->has_search_latch) { + trx_search_latch_release_if_reserved(trx); + } + + /* Add to the queue */ + slot->reserved = TRUE; + slot->wait_ended = FALSE; + + UT_LIST_ADD_LAST(srv_conc_queue, srv_conc_queue, slot); + + os_event_reset(slot->event); + + srv_conc_n_waiting_threads++; + + os_fast_mutex_unlock(&srv_conc_mutex); + + /* Go to wait for the event; when a thread leaves InnoDB it will + release this thread */ + + trx->op_info = "waiting in InnoDB queue"; + + os_event_wait(slot->event); + + trx->op_info = ""; + + os_fast_mutex_lock(&srv_conc_mutex); + + srv_conc_n_waiting_threads--; + + /* NOTE that the thread which released this thread already + incremented the thread counter on behalf of this thread */ + + slot->reserved = FALSE; + + UT_LIST_REMOVE(srv_conc_queue, srv_conc_queue, slot); + + trx->declared_to_be_inside_innodb = TRUE; + trx->n_tickets_to_enter_innodb = SRV_FREE_TICKETS_TO_ENTER; + + os_fast_mutex_unlock(&srv_conc_mutex); +} + +/************************************************************************* +This lets a thread enter InnoDB regardless of the number of threads inside +InnoDB. This must be called when a thread ends a lock wait. */ +UNIV_INTERN +void +srv_conc_force_enter_innodb( +/*========================*/ + trx_t* trx) /* in: transaction object associated with the + thread */ +{ + if (UNIV_LIKELY(!srv_thread_concurrency)) { + + return; + } + + ut_ad(srv_conc_n_threads >= 0); +#ifdef INNODB_RW_LOCKS_USE_ATOMICS + if (srv_thread_concurrency_timer_based) { + __sync_add_and_fetch(&srv_conc_n_threads, 1); + trx->declared_to_be_inside_innodb = TRUE; + trx->n_tickets_to_enter_innodb = 1; + return; + } +#endif + + os_fast_mutex_lock(&srv_conc_mutex); + + srv_conc_n_threads++; + trx->declared_to_be_inside_innodb = TRUE; + trx->n_tickets_to_enter_innodb = 1; + + os_fast_mutex_unlock(&srv_conc_mutex); +} + +/************************************************************************* +This must be called when a thread exits InnoDB in a lock wait or at the +end of an SQL statement. */ +UNIV_INTERN +void +srv_conc_force_exit_innodb( +/*=======================*/ + trx_t* trx) /* in: transaction object associated with the + thread */ +{ + srv_conc_slot_t* slot = NULL; + + if (trx->mysql_thd != NULL + && thd_is_replication_slave_thread(trx->mysql_thd)) { + + return; + } + + if (trx->declared_to_be_inside_innodb == FALSE) { + + return; + } + +#ifdef INNODB_RW_LOCKS_USE_ATOMICS + if (srv_thread_concurrency_timer_based) { + srv_conc_exit_innodb_timer_based(trx); + return; + } +#endif + + os_fast_mutex_lock(&srv_conc_mutex); + + ut_ad(srv_conc_n_threads > 0); + srv_conc_n_threads--; + trx->declared_to_be_inside_innodb = FALSE; + trx->n_tickets_to_enter_innodb = 0; + + if (srv_conc_n_threads < (lint)srv_thread_concurrency) { + /* Look for a slot where a thread is waiting and no other + thread has yet released the thread */ + + slot = UT_LIST_GET_FIRST(srv_conc_queue); + + while (slot && slot->wait_ended == TRUE) { + slot = UT_LIST_GET_NEXT(srv_conc_queue, slot); + } + + if (slot != NULL) { + slot->wait_ended = TRUE; + + /* We increment the count on behalf of the released + thread */ + + srv_conc_n_threads++; + } + } + + os_fast_mutex_unlock(&srv_conc_mutex); + + if (slot != NULL) { + os_event_set(slot->event); + } +} + +/************************************************************************* +This must be called when a thread exits InnoDB. */ +UNIV_INTERN +void +srv_conc_exit_innodb( +/*=================*/ + trx_t* trx) /* in: transaction object associated with the + thread */ +{ + if (trx->n_tickets_to_enter_innodb > 0) { + /* We will pretend the thread is still inside InnoDB though it + now leaves the InnoDB engine. In this way we save + a lot of semaphore operations. srv_conc_force_exit_innodb is + used to declare the thread definitely outside InnoDB. It + should be called when there is a lock wait or an SQL statement + ends. */ + + return; + } + + srv_conc_force_exit_innodb(trx); +} + +/*========================================================================*/ + +/************************************************************************* +Normalizes init parameter values to use units we use inside InnoDB. */ +static +ulint +srv_normalize_init_values(void) +/*===========================*/ + /* out: DB_SUCCESS or error code */ +{ + ulint n; + ulint i; + + n = srv_n_data_files; + + for (i = 0; i < n; i++) { + srv_data_file_sizes[i] = srv_data_file_sizes[i] + * ((1024 * 1024) / UNIV_PAGE_SIZE); + } + + srv_last_file_size_max = srv_last_file_size_max + * ((1024 * 1024) / UNIV_PAGE_SIZE); + + srv_log_file_size = srv_log_file_size / UNIV_PAGE_SIZE; + + srv_log_buffer_size = srv_log_buffer_size / UNIV_PAGE_SIZE; + + srv_lock_table_size = 5 * (srv_buf_pool_size / UNIV_PAGE_SIZE); + + return(DB_SUCCESS); +} + +/************************************************************************* +Boots the InnoDB server. */ +UNIV_INTERN +ulint +srv_boot(void) +/*==========*/ + /* out: DB_SUCCESS or error code */ +{ + ulint err; + + /* Transform the init parameter values given by MySQL to + use units we use inside InnoDB: */ + + err = srv_normalize_init_values(); + + if (err != DB_SUCCESS) { + return(err); + } + + /* Initialize synchronization primitives, memory management, and thread + local storage */ + + srv_general_init(); + + /* Initialize this module */ + + srv_init(); + + return(DB_SUCCESS); +} + +#ifndef UNIV_HOTBACKUP +/************************************************************************* +Reserves a slot in the thread table for the current MySQL OS thread. +NOTE! The kernel mutex has to be reserved by the caller! */ +static +srv_slot_t* +srv_table_reserve_slot_for_mysql(void) +/*==================================*/ + /* out: reserved slot */ +{ + srv_slot_t* slot; + ulint i; + + ut_ad(mutex_own(&kernel_mutex)); + + i = 0; + slot = srv_mysql_table + i; + + while (slot->in_use) { + i++; + + if (i >= OS_THREAD_MAX_N) { + + ut_print_timestamp(stderr); + + fprintf(stderr, + " InnoDB: There appear to be %lu MySQL" + " threads currently waiting\n" + "InnoDB: inside InnoDB, which is the" + " upper limit. Cannot continue operation.\n" + "InnoDB: We intentionally generate" + " a seg fault to print a stack trace\n" + "InnoDB: on Linux. But first we print" + " a list of waiting threads.\n", (ulong) i); + + for (i = 0; i < OS_THREAD_MAX_N; i++) { + + slot = srv_mysql_table + i; + + fprintf(stderr, + "Slot %lu: thread id %lu, type %lu," + " in use %lu, susp %lu, time %lu\n", + (ulong) i, + (ulong) os_thread_pf(slot->id), + (ulong) slot->type, + (ulong) slot->in_use, + (ulong) slot->suspended, + (ulong) difftime(ut_time(), + slot->suspend_time)); + } + + ut_error; + } + + slot = srv_mysql_table + i; + } + + ut_a(slot->in_use == FALSE); + + slot->in_use = TRUE; + slot->id = os_thread_get_curr_id(); + slot->handle = os_thread_get_curr(); + + return(slot); +} +#endif /* !UNIV_HOTBACKUP */ + +/******************************************************************* +Puts a MySQL OS thread to wait for a lock to be released. If an error +occurs during the wait trx->error_state associated with thr is +!= DB_SUCCESS when we return. DB_LOCK_WAIT_TIMEOUT and DB_DEADLOCK +are possible errors. DB_DEADLOCK is returned if selective deadlock +resolution chose this transaction as a victim. */ +UNIV_INTERN +void +srv_suspend_mysql_thread( +/*=====================*/ + que_thr_t* thr) /* in: query thread associated with the MySQL + OS thread */ +{ +#ifndef UNIV_HOTBACKUP + srv_slot_t* slot; + os_event_t event; + double wait_time; + trx_t* trx; + ulint had_dict_lock; + ibool was_declared_inside_innodb = FALSE; + ib_int64_t start_time = 0; + ib_int64_t finish_time; + ulint diff_time; + ulint sec; + ulint ms; + ulong lock_wait_timeout; + + ut_ad(!mutex_own(&kernel_mutex)); + + trx = thr_get_trx(thr); + + os_event_set(srv_lock_timeout_thread_event); + + mutex_enter(&kernel_mutex); + + trx->error_state = DB_SUCCESS; + + if (thr->state == QUE_THR_RUNNING) { + + ut_ad(thr->is_active == TRUE); + + /* The lock has already been released or this transaction + was chosen as a deadlock victim: no need to suspend */ + + if (trx->was_chosen_as_deadlock_victim) { + + trx->error_state = DB_DEADLOCK; + trx->was_chosen_as_deadlock_victim = FALSE; + } + + mutex_exit(&kernel_mutex); + + return; + } + + ut_ad(thr->is_active == FALSE); + + slot = srv_table_reserve_slot_for_mysql(); + + event = slot->event; + + slot->thr = thr; + + os_event_reset(event); + + slot->suspend_time = ut_time(); + + if (thr->lock_state == QUE_THR_LOCK_ROW) { + srv_n_lock_wait_count++; + srv_n_lock_wait_current_count++; + + if (ut_usectime(&sec, &ms) == -1) { + start_time = -1; + } else { + start_time = (ib_int64_t) sec * 1000000 + ms; + } + } + /* Wake the lock timeout monitor thread, if it is suspended */ + + os_event_set(srv_lock_timeout_thread_event); + + mutex_exit(&kernel_mutex); + + if (trx->declared_to_be_inside_innodb) { + + was_declared_inside_innodb = TRUE; + + /* We must declare this OS thread to exit InnoDB, since a + possible other thread holding a lock which this thread waits + for must be allowed to enter, sooner or later */ + + srv_conc_force_exit_innodb(trx); + } + + had_dict_lock = trx->dict_operation_lock_mode; + + switch (had_dict_lock) { + case RW_S_LATCH: + /* Release foreign key check latch */ + row_mysql_unfreeze_data_dictionary(trx); + break; + case RW_X_LATCH: + /* Release fast index creation latch */ + row_mysql_unlock_data_dictionary(trx); + break; + } + + ut_a(trx->dict_operation_lock_mode == 0); + + /* Suspend this thread and wait for the event. */ + + os_event_wait(event); + + /* After resuming, reacquire the data dictionary latch if + necessary. */ + + switch (had_dict_lock) { + case RW_S_LATCH: + row_mysql_freeze_data_dictionary(trx); + break; + case RW_X_LATCH: + row_mysql_lock_data_dictionary(trx); + break; + } + + if (was_declared_inside_innodb) { + + /* Return back inside InnoDB */ + + srv_conc_force_enter_innodb(trx); + } + + mutex_enter(&kernel_mutex); + + /* Release the slot for others to use */ + + slot->in_use = FALSE; + + wait_time = ut_difftime(ut_time(), slot->suspend_time); + + if (thr->lock_state == QUE_THR_LOCK_ROW) { + if (ut_usectime(&sec, &ms) == -1) { + finish_time = -1; + } else { + finish_time = (ib_int64_t) sec * 1000000 + ms; + } + + diff_time = (ulint) (finish_time - start_time); + + srv_n_lock_wait_current_count--; + srv_n_lock_wait_time = srv_n_lock_wait_time + diff_time; + if (diff_time > srv_n_lock_max_wait_time && + /* only update the variable if we successfully + retrieved the start and finish times. See Bug#36819. */ + start_time != -1 && finish_time != -1) { + srv_n_lock_max_wait_time = diff_time; + } + } + + if (trx->was_chosen_as_deadlock_victim) { + + trx->error_state = DB_DEADLOCK; + trx->was_chosen_as_deadlock_victim = FALSE; + } + + mutex_exit(&kernel_mutex); + + /* InnoDB system transactions (such as the purge, and + incomplete transactions that are being rolled back after crash + recovery) will use the global value of + innodb_lock_wait_timeout, because trx->mysql_thd == NULL. */ + lock_wait_timeout = thd_lock_wait_timeout(trx->mysql_thd); + + if (lock_wait_timeout < 100000000 + && wait_time > (double) lock_wait_timeout) { + + trx->error_state = DB_LOCK_WAIT_TIMEOUT; + } +#else /* UNIV_HOTBACKUP */ + /* This function depends on MySQL code that is not included in + InnoDB Hot Backup builds. Besides, this function should never + be called in InnoDB Hot Backup. */ + ut_error; +#endif /* UNIV_HOTBACKUP */ +} + +/************************************************************************ +Releases a MySQL OS thread waiting for a lock to be released, if the +thread is already suspended. */ +UNIV_INTERN +void +srv_release_mysql_thread_if_suspended( +/*==================================*/ + que_thr_t* thr) /* in: query thread associated with the + MySQL OS thread */ +{ +#ifndef UNIV_HOTBACKUP + srv_slot_t* slot; + ulint i; + + ut_ad(mutex_own(&kernel_mutex)); + + for (i = 0; i < OS_THREAD_MAX_N; i++) { + + slot = srv_mysql_table + i; + + if (slot->in_use && slot->thr == thr) { + /* Found */ + + os_event_set(slot->event); + + return; + } + } + + /* not found */ +#else /* UNIV_HOTBACKUP */ + /* This function depends on MySQL code that is not included in + InnoDB Hot Backup builds. Besides, this function should never + be called in InnoDB Hot Backup. */ + ut_error; +#endif /* UNIV_HOTBACKUP */ +} + +#ifndef UNIV_HOTBACKUP +/********************************************************************** +Refreshes the values used to calculate per-second averages. */ +static +void +srv_refresh_innodb_monitor_stats(void) +/*==================================*/ +{ + mutex_enter(&srv_innodb_monitor_mutex); + + srv_last_monitor_time = time(NULL); + + os_aio_refresh_stats(); + + btr_cur_n_sea_old = btr_cur_n_sea; + btr_cur_n_non_sea_old = btr_cur_n_non_sea; + + log_refresh_stats(); + + buf_refresh_io_stats(); + + srv_n_rows_inserted_old = srv_n_rows_inserted; + srv_n_rows_updated_old = srv_n_rows_updated; + srv_n_rows_deleted_old = srv_n_rows_deleted; + srv_n_rows_read_old = srv_n_rows_read; + + mutex_exit(&srv_innodb_monitor_mutex); +} + +/********************************************************************** +Outputs to a file the output of the InnoDB Monitor. */ +UNIV_INTERN +void +srv_printf_innodb_monitor( +/*======================*/ + FILE* file, /* in: output stream */ + ulint* trx_start, /* out: file position of the start of + the list of active transactions */ + ulint* trx_end) /* out: file position of the end of + the list of active transactions */ +{ + double time_elapsed; + time_t current_time; + ulint n_reserved; + + ulint btr_search_sys_subtotal; + ulint lock_sys_subtotal; + ulint recv_sys_subtotal; + ulint io_counter_subtotal; + + ulint i; + trx_t* trx; + + mutex_enter(&srv_innodb_monitor_mutex); + + current_time = time(NULL); + + /* We add 0.001 seconds to time_elapsed to prevent division + by zero if two users happen to call SHOW INNODB STATUS at the same + time */ + + time_elapsed = difftime(current_time, srv_last_monitor_time) + + 0.001; + + srv_last_monitor_time = time(NULL); + + fputs("\n=====================================\n", file); + + ut_print_timestamp(file); + fprintf(file, + " INNODB MONITOR OUTPUT\n" + "=====================================\n" + "Per second averages calculated from the last %lu seconds\n", + (ulong)time_elapsed); + + fputs("----------\n" + "SEMAPHORES\n" + "----------\n", file); + sync_print(file); + + /* Conceptually, srv_innodb_monitor_mutex has a very high latching + order level in sync0sync.h, while dict_foreign_err_mutex has a very + low level 135. Therefore we can reserve the latter mutex here without + a danger of a deadlock of threads. */ + + mutex_enter(&dict_foreign_err_mutex); + + if (ftell(dict_foreign_err_file) != 0L) { + fputs("------------------------\n" + "LATEST FOREIGN KEY ERROR\n" + "------------------------\n", file); + ut_copy_file(file, dict_foreign_err_file); + } + + mutex_exit(&dict_foreign_err_mutex); + + fputs("--------\n" + "FILE I/O\n" + "--------\n", file); + os_aio_print(file); + + fputs("-------------------------------------\n" + "INSERT BUFFER AND ADAPTIVE HASH INDEX\n" + "-------------------------------------\n", file); + ibuf_print(file); + + ha_print_info(file, btr_search_sys->hash_index); + + fprintf(file, + "%.2f hash searches/s, %.2f non-hash searches/s\n", + (btr_cur_n_sea - btr_cur_n_sea_old) + / time_elapsed, + (btr_cur_n_non_sea - btr_cur_n_non_sea_old) + / time_elapsed); + btr_cur_n_sea_old = btr_cur_n_sea; + btr_cur_n_non_sea_old = btr_cur_n_non_sea; + + fputs("---\n" + "LOG\n" + "---\n", file); + log_print(file); + + fputs("----------------------\n" + "BUFFER POOL AND MEMORY\n" + "----------------------\n", file); + fprintf(file, + "Total memory allocated " ULINTPF + "; in additional pool allocated " ULINTPF "\n", + ut_total_allocated_memory, + mem_pool_get_reserved(mem_comm_pool)); + /* Calcurate reserved memories */ + if (btr_search_sys && btr_search_sys->hash_index->heap) { + btr_search_sys_subtotal = mem_heap_get_size(btr_search_sys->hash_index->heap); + } else { + btr_search_sys_subtotal = 0; + for (i=0; i < btr_search_sys->hash_index->n_mutexes; i++) { + btr_search_sys_subtotal += mem_heap_get_size(btr_search_sys->hash_index->heaps[i]); + } + } + + lock_sys_subtotal = 0; + if (trx_sys) { + mutex_enter(&kernel_mutex); + trx = UT_LIST_GET_FIRST(trx_sys->mysql_trx_list); + while (trx) { + lock_sys_subtotal += ((trx->lock_heap) ? mem_heap_get_size(trx->lock_heap) : 0); + trx = UT_LIST_GET_NEXT(mysql_trx_list, trx); + } + mutex_exit(&kernel_mutex); + } + + recv_sys_subtotal = ((recv_sys && recv_sys->addr_hash) + ? mem_heap_get_size(recv_sys->heap) : 0); + + fprintf(file, + "Internal hash tables (constant factor + variable factor)\n" + " Adaptive hash index %lu \t(%lu + %lu)\n" + " Page hash %lu\n" + " Dictionary cache %lu \t(%lu + %lu)\n" + " File system %lu \t(%lu + %lu)\n" + " Lock system %lu \t(%lu + %lu)\n" + " Recovery system %lu \t(%lu + %lu)\n" + " Threads %lu \t(%lu + %lu)\n", + + (ulong) (btr_search_sys + ? (btr_search_sys->hash_index->n_cells * sizeof(hash_cell_t)) : 0) + + btr_search_sys_subtotal, + (ulong) (btr_search_sys + ? (btr_search_sys->hash_index->n_cells * sizeof(hash_cell_t)) : 0), + (ulong) btr_search_sys_subtotal, + + (ulong) (buf_pool->page_hash->n_cells * sizeof(hash_cell_t)), + + (ulong) (dict_sys ? ((dict_sys->table_hash->n_cells + + dict_sys->table_id_hash->n_cells + ) * sizeof(hash_cell_t) + + dict_sys->size) : 0), + (ulong) (dict_sys ? ((dict_sys->table_hash->n_cells + + dict_sys->table_id_hash->n_cells + ) * sizeof(hash_cell_t)) : 0), + (ulong) (dict_sys ? (dict_sys->size) : 0), + + (ulong) (fil_system_hash_cells() * sizeof(hash_cell_t) + + fil_system_hash_nodes()), + (ulong) (fil_system_hash_cells() * sizeof(hash_cell_t)), + (ulong) fil_system_hash_nodes(), + + (ulong) ((lock_sys ? (lock_sys->rec_hash->n_cells * sizeof(hash_cell_t)) : 0) + + lock_sys_subtotal), + (ulong) (lock_sys ? (lock_sys->rec_hash->n_cells * sizeof(hash_cell_t)) : 0), + (ulong) lock_sys_subtotal, + + (ulong) (((recv_sys && recv_sys->addr_hash) + ? (recv_sys->addr_hash->n_cells * sizeof(hash_cell_t)) : 0) + + recv_sys_subtotal), + (ulong) ((recv_sys && recv_sys->addr_hash) + ? (recv_sys->addr_hash->n_cells * sizeof(hash_cell_t)) : 0), + (ulong) recv_sys_subtotal, + + (ulong) (thr_local_hash_cells() * sizeof(hash_cell_t) + + thr_local_hash_nodes()), + (ulong) (thr_local_hash_cells() * sizeof(hash_cell_t)), + (ulong) thr_local_hash_nodes()); + + fprintf(file, "Dictionary memory allocated " ULINTPF "\n", + dict_sys->size); + + buf_print_io(file); + + fputs("--------------\n" + "ROW OPERATIONS\n" + "--------------\n", file); + fprintf(file, "%ld queries inside InnoDB, %lu queries in queue\n", + (long) srv_conc_n_threads, + (ulong) srv_conc_n_waiting_threads); + + fprintf(file, "%lu read views open inside InnoDB\n", + UT_LIST_GET_LEN(trx_sys->view_list)); + + n_reserved = fil_space_get_n_reserved_extents(0); + if (n_reserved > 0) { + fprintf(file, + "%lu tablespace extents now reserved for" + " B-tree split operations\n", + (ulong) n_reserved); + } + +#ifdef UNIV_LINUX + fprintf(file, "Main thread process no. %lu, id %lu, state: %s\n", + (ulong) srv_main_thread_process_no, + (ulong) srv_main_thread_id, + srv_main_thread_op_info); +#else + fprintf(file, "Main thread id %lu, state: %s\n", + (ulong) srv_main_thread_id, + srv_main_thread_op_info); +#endif + fprintf(file, + "Number of rows inserted " ULINTPF + ", updated " ULINTPF ", deleted " ULINTPF + ", read " ULINTPF "\n", + srv_n_rows_inserted, + srv_n_rows_updated, + srv_n_rows_deleted, + srv_n_rows_read); + fprintf(file, + "%.2f inserts/s, %.2f updates/s," + " %.2f deletes/s, %.2f reads/s\n", + (srv_n_rows_inserted - srv_n_rows_inserted_old) + / time_elapsed, + (srv_n_rows_updated - srv_n_rows_updated_old) + / time_elapsed, + (srv_n_rows_deleted - srv_n_rows_deleted_old) + / time_elapsed, + (srv_n_rows_read - srv_n_rows_read_old) + / time_elapsed); + + srv_n_rows_inserted_old = srv_n_rows_inserted; + srv_n_rows_updated_old = srv_n_rows_updated; + srv_n_rows_deleted_old = srv_n_rows_deleted; + srv_n_rows_read_old = srv_n_rows_read; + + lock_print_info_summary(file); + if (trx_start) { + long t = ftell(file); + if (t < 0) { + *trx_start = ULINT_UNDEFINED; + } else { + *trx_start = (ulint) t; + } + } + lock_print_info_all_transactions(file); + if (trx_end) { + long t = ftell(file); + if (t < 0) { + *trx_end = ULINT_UNDEFINED; + } else { + *trx_end = (ulint) t; + } + } + + fputs("----------------------------\n" + "END OF INNODB MONITOR OUTPUT\n" + "============================\n", file); + mutex_exit(&srv_innodb_monitor_mutex); + fflush(file); +} + +/********************************************************************** +Function to pass InnoDB status variables to MySQL */ +UNIV_INTERN +void +srv_export_innodb_status(void) +{ + mutex_enter(&srv_innodb_monitor_mutex); + + export_vars.innodb_data_pending_reads + = os_n_pending_reads; + export_vars.innodb_data_pending_writes + = os_n_pending_writes; + export_vars.innodb_data_pending_fsyncs + = fil_n_pending_log_flushes + + fil_n_pending_tablespace_flushes; + export_vars.innodb_data_fsyncs = os_n_fsyncs; + export_vars.innodb_data_read = srv_data_read; + export_vars.innodb_data_reads = os_n_file_reads; + export_vars.innodb_data_writes = os_n_file_writes; + export_vars.innodb_data_written = srv_data_written; + export_vars.innodb_dict_tables= (dict_sys ? UT_LIST_GET_LEN(dict_sys->table_LRU) : 0); + export_vars.innodb_buffer_pool_read_requests = buf_pool->n_page_gets; + export_vars.innodb_buffer_pool_write_requests + = srv_buf_pool_write_requests; + export_vars.innodb_buffer_pool_wait_free = srv_buf_pool_wait_free; + export_vars.innodb_buffer_pool_pages_flushed = srv_buf_pool_flushed; + export_vars.innodb_buffer_pool_reads = srv_buf_pool_reads; + export_vars.innodb_buffer_pool_read_ahead_rnd = srv_read_ahead_rnd; + export_vars.innodb_buffer_pool_read_ahead_seq = srv_read_ahead_seq; + export_vars.innodb_buffer_pool_pages_data + = UT_LIST_GET_LEN(buf_pool->LRU); + export_vars.innodb_buffer_pool_pages_dirty + = UT_LIST_GET_LEN(buf_pool->flush_list); + export_vars.innodb_buffer_pool_pages_free + = UT_LIST_GET_LEN(buf_pool->free); +#ifdef UNIV_DEBUG + export_vars.innodb_buffer_pool_pages_latched + = buf_get_latched_pages_number(); +#endif /* UNIV_DEBUG */ + export_vars.innodb_buffer_pool_pages_total = buf_pool->curr_size; + + export_vars.innodb_buffer_pool_pages_misc = buf_pool->curr_size + - UT_LIST_GET_LEN(buf_pool->LRU) + - UT_LIST_GET_LEN(buf_pool->free); +#ifdef HAVE_GCC_ATOMIC_BUILTINS + export_vars.innodb_have_atomic_builtins = 1; +#else + export_vars.innodb_have_atomic_builtins = 0; +#endif + export_vars.innodb_page_size = UNIV_PAGE_SIZE; + export_vars.innodb_log_waits = srv_log_waits; + export_vars.innodb_os_log_written = srv_os_log_written; + export_vars.innodb_os_log_fsyncs = fil_n_log_flushes; + export_vars.innodb_os_log_pending_fsyncs = fil_n_pending_log_flushes; + export_vars.innodb_os_log_pending_writes = srv_os_log_pending_writes; + export_vars.innodb_log_write_requests = srv_log_write_requests; + export_vars.innodb_log_writes = srv_log_writes; + export_vars.innodb_dblwr_pages_written = srv_dblwr_pages_written; + export_vars.innodb_dblwr_writes = srv_dblwr_writes; + export_vars.innodb_pages_created = buf_pool->n_pages_created; + export_vars.innodb_pages_read = buf_pool->n_pages_read; + export_vars.innodb_pages_written = buf_pool->n_pages_written; + export_vars.innodb_row_lock_waits = srv_n_lock_wait_count; + export_vars.innodb_row_lock_current_waits + = srv_n_lock_wait_current_count; + export_vars.innodb_row_lock_time = srv_n_lock_wait_time / 1000; + if (srv_n_lock_wait_count > 0) { + export_vars.innodb_row_lock_time_avg = (ulint) + (srv_n_lock_wait_time / 1000 / srv_n_lock_wait_count); + } else { + export_vars.innodb_row_lock_time_avg = 0; + } + export_vars.innodb_row_lock_time_max + = srv_n_lock_max_wait_time / 1000; + export_vars.innodb_rows_read = srv_n_rows_read; + export_vars.innodb_rows_inserted = srv_n_rows_inserted; + export_vars.innodb_rows_updated = srv_n_rows_updated; + export_vars.innodb_rows_deleted = srv_n_rows_deleted; + + mutex_exit(&srv_innodb_monitor_mutex); +} + +/************************************************************************* +A thread which wakes up threads whose lock wait may have lasted too long. +This also prints the info output by various InnoDB monitors. */ +UNIV_INTERN +os_thread_ret_t +srv_lock_timeout_and_monitor_thread( +/*================================*/ + /* out: a dummy parameter */ + void* arg __attribute__((unused))) + /* in: a dummy parameter required by + os_thread_create */ +{ + srv_slot_t* slot; + double time_elapsed; + time_t current_time; + time_t last_table_monitor_time; + time_t last_tablespace_monitor_time; + time_t last_monitor_time; + ibool some_waits; + double wait_time; + ulint i; + +#ifdef UNIV_DEBUG_THREAD_CREATION + fprintf(stderr, "Lock timeout thread starts, id %lu\n", + os_thread_pf(os_thread_get_curr_id())); +#endif + UT_NOT_USED(arg); + srv_last_monitor_time = time(NULL); + last_table_monitor_time = time(NULL); + last_tablespace_monitor_time = time(NULL); + last_monitor_time = time(NULL); +loop: + srv_lock_timeout_and_monitor_active = TRUE; + + /* When someone is waiting for a lock, we wake up every second + and check if a timeout has passed for a lock wait */ + + os_thread_sleep(1000000); + + current_time = time(NULL); + + time_elapsed = difftime(current_time, last_monitor_time); + + if (time_elapsed > 15) { + last_monitor_time = time(NULL); + + if (srv_print_innodb_monitor) { + srv_printf_innodb_monitor(stderr, NULL, NULL); + } + + if (srv_innodb_status) { + mutex_enter(&srv_monitor_file_mutex); + rewind(srv_monitor_file); + srv_printf_innodb_monitor(srv_monitor_file, NULL, + NULL); + os_file_set_eof(srv_monitor_file); + mutex_exit(&srv_monitor_file_mutex); + } + + if (srv_print_innodb_tablespace_monitor + && difftime(current_time, + last_tablespace_monitor_time) > 60) { + last_tablespace_monitor_time = time(NULL); + + fputs("========================" + "========================\n", + stderr); + + ut_print_timestamp(stderr); + + fputs(" INNODB TABLESPACE MONITOR OUTPUT\n" + "========================" + "========================\n", + stderr); + + fsp_print(0); + fputs("Validating tablespace\n", stderr); + fsp_validate(0); + fputs("Validation ok\n" + "---------------------------------------\n" + "END OF INNODB TABLESPACE MONITOR OUTPUT\n" + "=======================================\n", + stderr); + } + + if (srv_print_innodb_table_monitor + && difftime(current_time, last_table_monitor_time) > 60) { + + last_table_monitor_time = time(NULL); + + fputs("===========================================\n", + stderr); + + ut_print_timestamp(stderr); + + fputs(" INNODB TABLE MONITOR OUTPUT\n" + "===========================================\n", + stderr); + dict_print(); + + fputs("-----------------------------------\n" + "END OF INNODB TABLE MONITOR OUTPUT\n" + "==================================\n", + stderr); + } + } + + mutex_enter(&kernel_mutex); + + some_waits = FALSE; + + /* Check of all slots if a thread is waiting there, and if it + has exceeded the time limit */ + + for (i = 0; i < OS_THREAD_MAX_N; i++) { + + slot = srv_mysql_table + i; + + if (slot->in_use) { + trx_t* trx; + ulong lock_wait_timeout; + + some_waits = TRUE; + + wait_time = ut_difftime(ut_time(), slot->suspend_time); + + trx = thr_get_trx(slot->thr); + lock_wait_timeout = thd_lock_wait_timeout( + trx->mysql_thd); + + if (lock_wait_timeout < 100000000 + && (wait_time > (double) lock_wait_timeout + || wait_time < 0)) { + + /* Timeout exceeded or a wrap-around in system + time counter: cancel the lock request queued + by the transaction and release possible + other transactions waiting behind; it is + possible that the lock has already been + granted: in that case do nothing */ + + if (trx->wait_lock) { + lock_cancel_waiting_and_release( + trx->wait_lock); + } + } + } + } + + os_event_reset(srv_lock_timeout_thread_event); + + mutex_exit(&kernel_mutex); + + if (srv_shutdown_state >= SRV_SHUTDOWN_CLEANUP) { + goto exit_func; + } + + if (some_waits || srv_print_innodb_monitor + || srv_print_innodb_lock_monitor + || srv_print_innodb_tablespace_monitor + || srv_print_innodb_table_monitor) { + goto loop; + } + + /* No one was waiting for a lock and no monitor was active: + suspend this thread */ + + srv_lock_timeout_and_monitor_active = FALSE; + +#if 0 + /* The following synchronisation is disabled, since + the InnoDB monitor output is to be updated every 15 seconds. */ + os_event_wait(srv_lock_timeout_thread_event); +#endif + goto loop; + +exit_func: + srv_lock_timeout_and_monitor_active = FALSE; + + /* We count the number of threads in os_thread_exit(). A created + thread should always use that to exit and not use return() to exit. */ + + os_thread_exit(NULL); + + OS_THREAD_DUMMY_RETURN; +} + +/************************************************************************* +A thread which prints warnings about semaphore waits which have lasted +too long. These can be used to track bugs which cause hangs. */ +UNIV_INTERN +os_thread_ret_t +srv_error_monitor_thread( +/*=====================*/ + /* out: a dummy parameter */ + void* arg __attribute__((unused))) + /* in: a dummy parameter required by + os_thread_create */ +{ + /* number of successive fatal timeouts observed */ + ulint fatal_cnt = 0; + ib_uint64_t old_lsn; + ib_uint64_t new_lsn; + + old_lsn = srv_start_lsn; + +#ifdef UNIV_DEBUG_THREAD_CREATION + fprintf(stderr, "Error monitor thread starts, id %lu\n", + os_thread_pf(os_thread_get_curr_id())); +#endif +loop: + srv_error_monitor_active = TRUE; + + /* Try to track a strange bug reported by Harald Fuchs and others, + where the lsn seems to decrease at times */ + + new_lsn = log_get_lsn(); + + if (new_lsn < old_lsn) { + ut_print_timestamp(stderr); + fprintf(stderr, + " InnoDB: Error: old log sequence number %llu" + " was greater\n" + "InnoDB: than the new log sequence number %llu!\n" + "InnoDB: Please submit a bug report" + " to http://bugs.mysql.com\n", + old_lsn, new_lsn); + } + + old_lsn = new_lsn; + + if (difftime(time(NULL), srv_last_monitor_time) > 60) { + /* We referesh InnoDB Monitor values so that averages are + printed from at most 60 last seconds */ + + srv_refresh_innodb_monitor_stats(); + } + + /* Update the statistics collected for deciding LRU + eviction policy. */ + buf_LRU_stat_update(); + + /* In case mutex_exit is not a memory barrier, it is + theoretically possible some threads are left waiting though + the semaphore is already released. Wake up those threads: */ + + sync_arr_wake_threads_if_sema_free(); + + if (sync_array_print_long_waits()) { + fatal_cnt++; + if (fatal_cnt > 10) { + + fprintf(stderr, + "InnoDB: Error: semaphore wait has lasted" + " > %lu seconds\n" + "InnoDB: We intentionally crash the server," + " because it appears to be hung.\n", + (ulong) srv_fatal_semaphore_wait_threshold); + + ut_error; + } + } else { + fatal_cnt = 0; + } + + /* Flush stderr so that a database user gets the output + to possible MySQL error file */ + + fflush(stderr); + + os_thread_sleep(1000000); + + if (srv_shutdown_state < SRV_SHUTDOWN_CLEANUP) { + + goto loop; + } + + srv_error_monitor_active = FALSE; + + /* We count the number of threads in os_thread_exit(). A created + thread should always use that to exit and not use return() to exit. */ + + os_thread_exit(NULL); + + OS_THREAD_DUMMY_RETURN; +} + +/*********************************************************************** +Tells the InnoDB server that there has been activity in the database +and wakes up the master thread if it is suspended (not sleeping). Used +in the MySQL interface. Note that there is a small chance that the master +thread stays suspended (we do not protect our operation with the kernel +mutex, for performace reasons). */ +UNIV_INTERN +void +srv_active_wake_master_thread(void) +/*===============================*/ +{ + srv_activity_count++; + + if (srv_n_threads_active[SRV_MASTER] == 0) { + + mutex_enter(&kernel_mutex); + + srv_release_threads(SRV_MASTER, 1); + + mutex_exit(&kernel_mutex); + } +} + +/*********************************************************************** +Wakes up the master thread if it is suspended or being suspended. */ +UNIV_INTERN +void +srv_wake_master_thread(void) +/*========================*/ +{ + srv_activity_count++; + + mutex_enter(&kernel_mutex); + + srv_release_threads(SRV_MASTER, 1); + + mutex_exit(&kernel_mutex); +} + +/************************************************************************* +The master thread controlling the server. */ +UNIV_INTERN +os_thread_ret_t +srv_master_thread( +/*==============*/ + /* out: a dummy parameter */ + void* arg __attribute__((unused))) + /* in: a dummy parameter required by + os_thread_create */ +{ + os_event_t event; + time_t last_flush_time; + time_t current_time; + ulint old_activity_count; + ulint n_pages_purged = 0; + ulint n_bytes_merged; + ulint n_pages_flushed; + ulint n_bytes_archived; + ulint n_tables_to_drop; + ulint n_ios; + ulint n_ios_old; + ulint n_ios_very_old; + ulint n_pend_ios; + ibool skip_sleep = FALSE; + ulint i; + + ib_uint64_t lsn_old; + + ib_uint64_t oldest_lsn; + +#ifdef UNIV_DEBUG_THREAD_CREATION + fprintf(stderr, "Master thread starts, id %lu\n", + os_thread_pf(os_thread_get_curr_id())); +#endif + srv_main_thread_process_no = os_proc_get_number(); + srv_main_thread_id = os_thread_pf(os_thread_get_curr_id()); + + srv_table_reserve_slot(SRV_MASTER); + + mutex_enter(&kernel_mutex); + + srv_n_threads_active[SRV_MASTER]++; + + mutex_exit(&kernel_mutex); + + mutex_enter(&(log_sys->mutex)); + lsn_old = log_sys->lsn; + mutex_exit(&(log_sys->mutex)); +loop: + /*****************************************************************/ + /* ---- When there is database activity by users, we cycle in this + loop */ + + srv_main_thread_op_info = "reserving kernel mutex"; + + n_ios_very_old = log_sys->n_log_ios + buf_pool->n_pages_read + + buf_pool->n_pages_written; + mutex_enter(&kernel_mutex); + + /* Store the user activity counter at the start of this loop */ + old_activity_count = srv_activity_count; + + mutex_exit(&kernel_mutex); + + if (srv_force_recovery >= SRV_FORCE_NO_BACKGROUND) { + + goto suspend_thread; + } + + /* ---- We run the following loop approximately once per second + when there is database activity */ + + skip_sleep = FALSE; + + for (i = 0; i < 10; i++) { + n_ios_old = log_sys->n_log_ios + buf_pool->n_pages_read + + buf_pool->n_pages_written; + srv_main_thread_op_info = "sleeping"; + + if (!skip_sleep) { + + os_thread_sleep(1000000); + + /* + mutex_enter(&(log_sys->mutex)); + oldest_lsn = buf_pool_get_oldest_modification(); + ib_uint64_t lsn = log_sys->lsn; + mutex_exit(&(log_sys->mutex)); + + if(oldest_lsn) + fprintf(stderr, + "InnoDB flush: age pct: %lu, lsn progress: %lu\n", + (lsn - oldest_lsn) * 100 / log_sys->max_checkpoint_age, + lsn - lsn_old); + */ + } + + skip_sleep = FALSE; + + /* ALTER TABLE in MySQL requires on Unix that the table handler + can drop tables lazily after there no longer are SELECT + queries to them. */ + + srv_main_thread_op_info = "doing background drop tables"; + + row_drop_tables_for_mysql_in_background(); + + srv_main_thread_op_info = ""; + + if (srv_fast_shutdown && srv_shutdown_state > 0) { + + goto background_loop; + } + + /* We flush the log once in a second even if no commit + is issued or the we have specified in my.cnf no flush + at transaction commit */ + + srv_main_thread_op_info = "flushing log"; + log_buffer_flush_to_disk(); + + srv_main_thread_op_info = "making checkpoint"; + log_free_check(); + + /* If there were less than 5 i/os during the + one second sleep, we assume that there is free + disk i/o capacity available, and it makes sense to + do an insert buffer merge. */ + + n_pend_ios = buf_get_n_pending_ios() + + log_sys->n_pending_writes; + n_ios = log_sys->n_log_ios + buf_pool->n_pages_read + + buf_pool->n_pages_written; + if (n_pend_ios < PCT_IO(3) && (n_ios - n_ios_old < PCT_IO(5))) { + srv_main_thread_op_info = "doing insert buffer merge"; + ibuf_contract_for_n_pages( + TRUE, PCT_IBUF_IO((srv_insert_buffer_batch_size / 4))); + + srv_main_thread_op_info = "flushing log"; + + /* No fsync when srv_flush_log_at_trx_commit != 1 */ + log_buffer_flush_maybe_sync(); + } + + if (UNIV_UNLIKELY(buf_get_modified_ratio_pct() + > srv_max_buf_pool_modified_pct)) { + + /* Try to keep the number of modified pages in the + buffer pool under the limit wished by the user */ + + n_pages_flushed = buf_flush_batch(BUF_FLUSH_LIST, PCT_IO(100), + IB_ULONGLONG_MAX); + + /* If we had to do the flush, it may have taken + even more than 1 second, and also, there may be more + to flush. Do not sleep 1 second during the next + iteration of this loop. */ + + skip_sleep = TRUE; + mutex_enter(&(log_sys->mutex)); + lsn_old = log_sys->lsn; + mutex_exit(&(log_sys->mutex)); + } else if (srv_adaptive_checkpoint == 1) { + + /* Try to keep modified age not to exceed + max_checkpoint_age * 7/8 line */ + + mutex_enter(&(log_sys->mutex)); + lsn_old = log_sys->lsn; + oldest_lsn = buf_pool_get_oldest_modification(); + if (oldest_lsn == 0) { + + mutex_exit(&(log_sys->mutex)); + + } else { + if ((log_sys->lsn - oldest_lsn) + > (log_sys->max_checkpoint_age) - ((log_sys->max_checkpoint_age) / 8)) { + /* LOG_POOL_PREFLUSH_RATIO_ASYNC is exceeded. */ + /* We should not flush from here. */ + mutex_exit(&(log_sys->mutex)); + } else if ((log_sys->lsn - oldest_lsn) + > (log_sys->max_checkpoint_age) - ((log_sys->max_checkpoint_age) / 4)) { + + /* 2nd defence line (max_checkpoint_age * 3/4) */ + + mutex_exit(&(log_sys->mutex)); + + n_pages_flushed = buf_flush_batch(BUF_FLUSH_LIST, PCT_IO(100), + IB_ULONGLONG_MAX); + skip_sleep = TRUE; + } else if ((log_sys->lsn - oldest_lsn) + > (log_sys->max_checkpoint_age)/2 ) { + + /* 1st defence line (max_checkpoint_age * 1/2) */ + + mutex_exit(&(log_sys->mutex)); + + n_pages_flushed = buf_flush_batch(BUF_FLUSH_LIST, PCT_IO(10), + IB_ULONGLONG_MAX); + skip_sleep = TRUE; + } else { + mutex_exit(&(log_sys->mutex)); + } + } + } else if (srv_adaptive_checkpoint == 2) { + + /* Try to keep modified age not to exceed + max_checkpoint_age * 7/8 line */ + + mutex_enter(&(log_sys->mutex)); + + oldest_lsn = buf_pool_get_oldest_modification(); + if (oldest_lsn == 0) { + lsn_old = log_sys->lsn; + mutex_exit(&(log_sys->mutex)); + + } else { + if ((log_sys->lsn - oldest_lsn) + > (log_sys->max_checkpoint_age) - ((log_sys->max_checkpoint_age) / 8)) { + /* LOG_POOL_PREFLUSH_RATIO_ASYNC is exceeded. */ + /* We should not flush from here. */ + lsn_old = log_sys->lsn; + mutex_exit(&(log_sys->mutex)); + } else if ((log_sys->lsn - oldest_lsn) + > (log_sys->max_checkpoint_age)/2 ) { + + /* defence line (max_checkpoint_age * 1/2) */ + ib_uint64_t lsn = log_sys->lsn; + + mutex_exit(&(log_sys->mutex)); + + ib_uint64_t level, bpl; + buf_page_t* bpage; + + mutex_enter(&flush_list_mutex); + + level = 0; + bpage = UT_LIST_GET_FIRST(buf_pool->flush_list); + + while (bpage != NULL) { + ib_uint64_t oldest_modification = bpage->oldest_modification; + if (oldest_modification != 0) { + level += log_sys->max_checkpoint_age + - (lsn - oldest_modification); + } + bpage = UT_LIST_GET_NEXT(flush_list, bpage); + } + + if (level) { + bpl = ((ib_uint64_t) UT_LIST_GET_LEN(buf_pool->flush_list) + * UT_LIST_GET_LEN(buf_pool->flush_list) + * (lsn - lsn_old)) / level; + } else { + bpl = 0; + } + + mutex_exit(&flush_list_mutex); + + if (!srv_use_doublewrite_buf) { + /* flush is faster than when doublewrite */ + bpl = (bpl * 3) / 4; + } + + if (bpl) { +retry_flush_batch: + n_pages_flushed = buf_flush_batch(BUF_FLUSH_LIST, + bpl, + oldest_lsn + (lsn - lsn_old)); + if (n_pages_flushed == ULINT_UNDEFINED) { + os_thread_sleep(5000); + goto retry_flush_batch; + } + } + + lsn_old = lsn; + /* + fprintf(stderr, + "InnoDB flush: age pct: %lu, lsn progress: %lu, blocks to flush:%llu\n", + (lsn - oldest_lsn) * 100 / log_sys->max_checkpoint_age, + lsn - lsn_old, bpl); + */ + } else { + lsn_old = log_sys->lsn; + mutex_exit(&(log_sys->mutex)); + } + } + + } else { + mutex_enter(&(log_sys->mutex)); + lsn_old = log_sys->lsn; + mutex_exit(&(log_sys->mutex)); + } + + if (srv_activity_count == old_activity_count) { + + /* There is no user activity at the moment, go to + the background loop */ + + goto background_loop; + } + } + + /* ---- We perform the following code approximately once per + 10 seconds when there is database activity */ + +#ifdef MEM_PERIODIC_CHECK + /* Check magic numbers of every allocated mem block once in 10 + seconds */ + mem_validate_all_blocks(); +#endif + /* If there were less than 200 i/os during the 10 second period, + we assume that there is free disk i/o capacity available, and it + makes sense to flush 100 pages. */ + + n_pend_ios = buf_get_n_pending_ios() + log_sys->n_pending_writes; + n_ios = log_sys->n_log_ios + buf_pool->n_pages_read + + buf_pool->n_pages_written; + if (n_pend_ios < 3 && (n_ios - n_ios_very_old < PCT_IO(200))) { + + srv_main_thread_op_info = "flushing buffer pool pages"; + buf_flush_batch(BUF_FLUSH_LIST, PCT_IO(100), IB_ULONGLONG_MAX); + + srv_main_thread_op_info = "flushing log"; + /* No fsync when srv_flush_log_at_trx_commit != 1 */ + log_buffer_flush_maybe_sync(); + } + + /* We run a batch of insert buffer merge every 10 seconds, + even if the server were active */ + + srv_main_thread_op_info = "doing insert buffer merge"; + ibuf_contract_for_n_pages(TRUE, PCT_IBUF_IO((srv_insert_buffer_batch_size / 4))); + + srv_main_thread_op_info = "flushing log"; + /* No fsync when srv_flush_log_at_trx_commit != 1 */ + log_buffer_flush_maybe_sync(); + + /* We run a full purge every 10 seconds, even if the server + were active */ + + last_flush_time = time(NULL); + + do { + + if (srv_fast_shutdown && srv_shutdown_state > 0) { + + goto background_loop; + } + + srv_main_thread_op_info = "purging"; + n_pages_purged = trx_purge(); + + current_time = time(NULL); + + if (difftime(current_time, last_flush_time) > 1) { + srv_main_thread_op_info = "flushing log"; + + log_buffer_flush_to_disk(); + last_flush_time = current_time; + } + } while (n_pages_purged); + + srv_main_thread_op_info = "flushing buffer pool pages"; + + /* Flush a few oldest pages to make a new checkpoint younger */ + + if (buf_get_modified_ratio_pct() > 70) { + + /* If there are lots of modified pages in the buffer pool + (> 70 %), we assume we can afford reserving the disk(s) for + the time it requires to flush 100 pages */ + + n_pages_flushed = buf_flush_batch(BUF_FLUSH_LIST, PCT_IO(100), + IB_ULONGLONG_MAX); + } else { + /* Otherwise, we only flush a small number of pages so that + we do not unnecessarily use much disk i/o capacity from + other work */ + + n_pages_flushed = buf_flush_batch(BUF_FLUSH_LIST, PCT_IO(10), + IB_ULONGLONG_MAX); + } + + srv_main_thread_op_info = "making checkpoint"; + + /* Make a new checkpoint about once in 10 seconds */ + + log_checkpoint(TRUE, FALSE); + + srv_main_thread_op_info = "reserving kernel mutex"; + + mutex_enter(&kernel_mutex); + + /* ---- When there is database activity, we jump from here back to + the start of loop */ + + if (srv_activity_count != old_activity_count) { + mutex_exit(&kernel_mutex); + goto loop; + } + + mutex_exit(&kernel_mutex); + + /* If the database is quiet, we enter the background loop */ + + /*****************************************************************/ +background_loop: + /* ---- In this loop we run background operations when the server + is quiet from user activity. Also in the case of a shutdown, we + loop here, flushing the buffer pool to the data files. */ + + /* The server has been quiet for a while: start running background + operations */ + + srv_main_thread_op_info = "doing background drop tables"; + + n_tables_to_drop = row_drop_tables_for_mysql_in_background(); + + if (n_tables_to_drop > 0) { + /* Do not monopolize the CPU even if there are tables waiting + in the background drop queue. (It is essentially a bug if + MySQL tries to drop a table while there are still open handles + to it and we had to put it to the background drop queue.) */ + + os_thread_sleep(100000); + } + + srv_main_thread_op_info = "purging"; + + /* Run a full purge */ + + last_flush_time = time(NULL); + + do { + if (srv_fast_shutdown && srv_shutdown_state > 0) { + + break; + } + + srv_main_thread_op_info = "purging"; + n_pages_purged = trx_purge(); + + current_time = time(NULL); + + if (difftime(current_time, last_flush_time) > 1) { + srv_main_thread_op_info = "flushing log"; + + log_buffer_flush_to_disk(); + last_flush_time = current_time; + } + } while (n_pages_purged); + + srv_main_thread_op_info = "reserving kernel mutex"; + + mutex_enter(&kernel_mutex); + if (srv_activity_count != old_activity_count) { + mutex_exit(&kernel_mutex); + goto loop; + } + mutex_exit(&kernel_mutex); + + srv_main_thread_op_info = "doing insert buffer merge"; + + if (srv_fast_shutdown && srv_shutdown_state > 0) { + n_bytes_merged = 0; + } else { + n_bytes_merged = ibuf_contract_for_n_pages( + TRUE, PCT_IBUF_IO((srv_insert_buffer_batch_size * 5))); + } + + srv_main_thread_op_info = "reserving kernel mutex"; + + mutex_enter(&kernel_mutex); + if (srv_activity_count != old_activity_count) { + mutex_exit(&kernel_mutex); + goto loop; + } + mutex_exit(&kernel_mutex); + +flush_loop: + srv_main_thread_op_info = "flushing buffer pool pages"; + + if (srv_fast_shutdown < 2) { + n_pages_flushed = buf_flush_batch(BUF_FLUSH_LIST, PCT_IO(100), + IB_ULONGLONG_MAX); + } else { + /* In the fastest shutdown we do not flush the buffer pool + to data files: we set n_pages_flushed to 0 artificially. */ + + n_pages_flushed = 0; + } + + srv_main_thread_op_info = "reserving kernel mutex"; + + mutex_enter(&kernel_mutex); + if (srv_activity_count != old_activity_count) { + mutex_exit(&kernel_mutex); + goto loop; + } + mutex_exit(&kernel_mutex); + + srv_main_thread_op_info = "waiting for buffer pool flush to end"; + buf_flush_wait_batch_end(BUF_FLUSH_LIST); + + srv_main_thread_op_info = "flushing log"; + + current_time = time(NULL); + if (difftime(current_time, last_flush_time) > 1) { + log_buffer_flush_to_disk(); + last_flush_time = current_time; + } else { + /* No fsync when srv_flush_log_at_trx_commit != 1 */ + log_buffer_flush_maybe_sync(); + } + + srv_main_thread_op_info = "making checkpoint"; + + log_checkpoint(TRUE, FALSE); + + if (buf_get_modified_ratio_pct() > srv_max_buf_pool_modified_pct) { + + /* Try to keep the number of modified pages in the + buffer pool under the limit wished by the user */ + + goto flush_loop; + } + + srv_main_thread_op_info = "reserving kernel mutex"; + + mutex_enter(&kernel_mutex); + if (srv_activity_count != old_activity_count) { + mutex_exit(&kernel_mutex); + goto loop; + } + mutex_exit(&kernel_mutex); + /* + srv_main_thread_op_info = "archiving log (if log archive is on)"; + + log_archive_do(FALSE, &n_bytes_archived); + */ + n_bytes_archived = 0; + + /* Keep looping in the background loop if still work to do */ + + if (srv_fast_shutdown && srv_shutdown_state > 0) { + if (n_tables_to_drop + n_pages_flushed + + n_bytes_archived != 0) { + + /* If we are doing a fast shutdown (= the default) + we do not do purge or insert buffer merge. But we + flush the buffer pool completely to disk. + In a 'very fast' shutdown we do not flush the buffer + pool to data files: we have set n_pages_flushed to + 0 artificially. */ + + goto background_loop; + } + } else if (n_tables_to_drop + + n_pages_purged + n_bytes_merged + n_pages_flushed + + n_bytes_archived != 0) { + /* In a 'slow' shutdown we run purge and the insert buffer + merge to completion */ + + goto background_loop; + } + + /* There is no work for background operations either: suspend + master thread to wait for more server activity */ + +suspend_thread: + srv_main_thread_op_info = "suspending"; + + mutex_enter(&kernel_mutex); + + if (row_get_background_drop_list_len_low() > 0) { + mutex_exit(&kernel_mutex); + + goto loop; + } + + event = srv_suspend_thread(); + + mutex_exit(&kernel_mutex); + + /* DO NOT CHANGE THIS STRING. innobase_start_or_create_for_mysql() + waits for database activity to die down when converting < 4.1.x + databases, and relies on this string being exactly as it is. InnoDB + manual also mentions this string in several places. */ + srv_main_thread_op_info = "waiting for server activity"; + + os_event_wait(event); + + if (srv_shutdown_state == SRV_SHUTDOWN_EXIT_THREADS) { + /* This is only extra safety, the thread should exit + already when the event wait ends */ + + os_thread_exit(NULL); + } + + /* When there is user activity, InnoDB will set the event and the + main thread goes back to loop. */ + + goto loop; + + OS_THREAD_DUMMY_RETURN; /* Not reached, avoid compiler warning */ +} +#endif /* !UNIV_HOTBACKUP */ diff --git a/storage/xtradb/srv/srv0start.c b/storage/xtradb/srv/srv0start.c new file mode 100644 index 00000000000..9239af0cad5 --- /dev/null +++ b/storage/xtradb/srv/srv0start.c @@ -0,0 +1,2052 @@ +/***************************************************************************** + +Copyright (c) 1996, 2009, Innobase Oy. All Rights Reserved. +Copyright (c) 2008, Google Inc. + +Portions of this file contain modifications contributed and copyrighted by +Google, Inc. Those modifications are gratefully acknowledged and are described +briefly in the InnoDB documentation. The contributions by Google are +incorporated with their permission, and subject to the conditions contained in +the file COPYING.Google. + +This program is free software; you can redistribute it and/or modify it under +the terms of the GNU General Public License as published by the Free Software +Foundation; version 2 of the License. + +This program is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + +You 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 + +*****************************************************************************/ + +/************************************************************************ +Starts the InnoDB database server + +Created 2/16/1996 Heikki Tuuri +*************************************************************************/ + +#include "os0proc.h" +#include "sync0sync.h" +#include "ut0mem.h" +#include "mem0mem.h" +#include "data0data.h" +#include "data0type.h" +#include "dict0dict.h" +#include "buf0buf.h" +#include "buf0flu.h" +#include "buf0rea.h" +#include "os0file.h" +#include "os0thread.h" +#include "fil0fil.h" +#include "fsp0fsp.h" +#include "rem0rec.h" +#include "rem0cmp.h" +#include "mtr0mtr.h" +#include "log0log.h" +#include "log0recv.h" +#include "page0page.h" +#include "page0cur.h" +#include "trx0trx.h" +#include "dict0boot.h" +#include "dict0load.h" +#include "trx0sys.h" +#include "dict0crea.h" +#include "btr0btr.h" +#include "btr0pcur.h" +#include "btr0cur.h" +#include "btr0sea.h" +#include "rem0rec.h" +#include "srv0srv.h" +#include "que0que.h" +#include "usr0sess.h" +#include "lock0lock.h" +#include "trx0roll.h" +#include "trx0purge.h" +#include "row0ins.h" +#include "row0sel.h" +#include "row0upd.h" +#include "row0row.h" +#include "row0mysql.h" +#include "lock0lock.h" +#include "ibuf0ibuf.h" +#include "pars0pars.h" +#include "btr0sea.h" +#include "srv0start.h" +#include "que0que.h" + +/* Log sequence number immediately after startup */ +UNIV_INTERN ib_uint64_t srv_start_lsn; +/* Log sequence number at shutdown */ +UNIV_INTERN ib_uint64_t srv_shutdown_lsn; + +#ifdef HAVE_DARWIN_THREADS +# include <sys/utsname.h> +UNIV_INTERN ibool srv_have_fullfsync = FALSE; +#endif + +UNIV_INTERN ibool srv_start_raw_disk_in_use = FALSE; + +UNIV_INTERN ibool srv_startup_is_before_trx_rollback_phase = FALSE; +UNIV_INTERN ibool srv_is_being_started = FALSE; +UNIV_INTERN ibool srv_was_started = FALSE; +#ifndef UNIV_HOTBACKUP +static ibool srv_start_has_been_called = FALSE; +#endif /* !UNIV_HOTBACKUP */ + +/* At a shutdown the value first climbs to SRV_SHUTDOWN_CLEANUP +and then to SRV_SHUTDOWN_LAST_PHASE */ +UNIV_INTERN ulint srv_shutdown_state = 0; + +#ifndef UNIV_HOTBACKUP +static os_file_t files[1000]; + +static mutex_t ios_mutex; +static ulint ios; + +static ulint n[SRV_MAX_N_IO_THREADS + 5]; +static os_thread_id_t thread_ids[SRV_MAX_N_IO_THREADS + 5]; + +/* We use this mutex to test the return value of pthread_mutex_trylock + on successful locking. HP-UX does NOT return 0, though Linux et al do. */ +static os_fast_mutex_t srv_os_test_mutex; + +/* Name of srv_monitor_file */ +static char* srv_monitor_file_name; +#endif /* !UNIV_HOTBACKUP */ + +#define SRV_N_PENDING_IOS_PER_THREAD OS_AIO_N_PENDING_IOS_PER_THREAD +#define SRV_MAX_N_PENDING_SYNC_IOS 100 + + +/* Avoid warnings when using purify */ + +#ifdef HAVE_valgrind +static int inno_bcmp(register const char *s1, register const char *s2, + register uint len) +{ + while ((len-- != 0) && (*s1++ == *s2++)) + ; + + return(len + 1); +} +#define memcmp(A,B,C) inno_bcmp((A),(B),(C)) +#endif + +static +char* +srv_parse_megabytes( +/*================*/ + /* out: next character in string */ + char* str, /* in: string containing a quantity in bytes */ + ulint* megs) /* out: the number in megabytes */ +{ + char* endp; + ulint size; + + size = strtoul(str, &endp, 10); + + str = endp; + + switch (*str) { + case 'G': case 'g': + size *= 1024; + /* fall through */ + case 'M': case 'm': + str++; + break; + default: + size /= 1024 * 1024; + break; + } + + *megs = size; + return(str); +} + +/************************************************************************* +Reads the data files and their sizes from a character string given in +the .cnf file. */ +UNIV_INTERN +ibool +srv_parse_data_file_paths_and_sizes( +/*================================*/ + /* out: TRUE if ok, FALSE on parse error */ + char* str) /* in/out: the data file path string */ +{ + char* input_str; + char* path; + ulint size; + ulint i = 0; + + srv_auto_extend_last_data_file = FALSE; + srv_last_file_size_max = 0; + srv_data_file_names = NULL; + srv_data_file_sizes = NULL; + srv_data_file_is_raw_partition = NULL; + + input_str = str; + + /* First calculate the number of data files and check syntax: + path:size[M | G];path:size[M | G]... . Note that a Windows path may + contain a drive name and a ':'. */ + + while (*str != '\0') { + path = str; + + while ((*str != ':' && *str != '\0') + || (*str == ':' + && (*(str + 1) == '\\' || *(str + 1) == '/' + || *(str + 1) == ':'))) { + str++; + } + + if (*str == '\0') { + return(FALSE); + } + + str++; + + str = srv_parse_megabytes(str, &size); + + if (0 == strncmp(str, ":autoextend", + (sizeof ":autoextend") - 1)) { + + str += (sizeof ":autoextend") - 1; + + if (0 == strncmp(str, ":max:", + (sizeof ":max:") - 1)) { + + str += (sizeof ":max:") - 1; + + str = srv_parse_megabytes(str, &size); + } + + if (*str != '\0') { + + return(FALSE); + } + } + + if (strlen(str) >= 6 + && *str == 'n' + && *(str + 1) == 'e' + && *(str + 2) == 'w') { + str += 3; + } + + if (*str == 'r' && *(str + 1) == 'a' && *(str + 2) == 'w') { + str += 3; + } + + if (size == 0) { + return(FALSE); + } + + i++; + + if (*str == ';') { + str++; + } else if (*str != '\0') { + + return(FALSE); + } + } + + if (i == 0) { + /* If innodb_data_file_path was defined it must contain + at least one data file definition */ + + return(FALSE); + } + + srv_data_file_names = malloc(i * sizeof *srv_data_file_names); + srv_data_file_sizes = malloc(i * sizeof *srv_data_file_sizes); + srv_data_file_is_raw_partition = malloc( + i * sizeof *srv_data_file_is_raw_partition); + + srv_n_data_files = i; + + /* Then store the actual values to our arrays */ + + str = input_str; + i = 0; + + while (*str != '\0') { + path = str; + + /* Note that we must step over the ':' in a Windows path; + a Windows path normally looks like C:\ibdata\ibdata1:1G, but + a Windows raw partition may have a specification like + \\.\C::1Gnewraw or \\.\PHYSICALDRIVE2:1Gnewraw */ + + while ((*str != ':' && *str != '\0') + || (*str == ':' + && (*(str + 1) == '\\' || *(str + 1) == '/' + || *(str + 1) == ':'))) { + str++; + } + + if (*str == ':') { + /* Make path a null-terminated string */ + *str = '\0'; + str++; + } + + str = srv_parse_megabytes(str, &size); + + srv_data_file_names[i] = path; + srv_data_file_sizes[i] = size; + + if (0 == strncmp(str, ":autoextend", + (sizeof ":autoextend") - 1)) { + + srv_auto_extend_last_data_file = TRUE; + + str += (sizeof ":autoextend") - 1; + + if (0 == strncmp(str, ":max:", + (sizeof ":max:") - 1)) { + + str += (sizeof ":max:") - 1; + + str = srv_parse_megabytes( + str, &srv_last_file_size_max); + } + + if (*str != '\0') { + + return(FALSE); + } + } + + (srv_data_file_is_raw_partition)[i] = 0; + + if (strlen(str) >= 6 + && *str == 'n' + && *(str + 1) == 'e' + && *(str + 2) == 'w') { + str += 3; + (srv_data_file_is_raw_partition)[i] = SRV_NEW_RAW; + } + + if (*str == 'r' && *(str + 1) == 'a' && *(str + 2) == 'w') { + str += 3; + + if ((srv_data_file_is_raw_partition)[i] == 0) { + (srv_data_file_is_raw_partition)[i] = SRV_OLD_RAW; + } + } + + i++; + + if (*str == ';') { + str++; + } + } + + return(TRUE); +} + +/************************************************************************* +Reads log group home directories from a character string given in +the .cnf file. */ +UNIV_INTERN +ibool +srv_parse_log_group_home_dirs( +/*==========================*/ + /* out: TRUE if ok, FALSE on parse error */ + char* str) /* in/out: character string */ +{ + char* input_str; + char* path; + ulint i = 0; + + srv_log_group_home_dirs = NULL; + + input_str = str; + + /* First calculate the number of directories and check syntax: + path;path;... */ + + while (*str != '\0') { + path = str; + + while (*str != ';' && *str != '\0') { + str++; + } + + i++; + + if (*str == ';') { + str++; + } else if (*str != '\0') { + + return(FALSE); + } + } + + if (i != 1) { + /* If innodb_log_group_home_dir was defined it must + contain exactly one path definition under current MySQL */ + + return(FALSE); + } + + srv_log_group_home_dirs = malloc(i * sizeof *srv_log_group_home_dirs); + + /* Then store the actual values to our array */ + + str = input_str; + i = 0; + + while (*str != '\0') { + path = str; + + while (*str != ';' && *str != '\0') { + str++; + } + + if (*str == ';') { + *str = '\0'; + str++; + } + + srv_log_group_home_dirs[i] = path; + + i++; + } + + return(TRUE); +} + +/************************************************************************* +Frees the memory allocated by srv_parse_data_file_paths_and_sizes() +and srv_parse_log_group_home_dirs(). */ +UNIV_INTERN +void +srv_free_paths_and_sizes(void) +/*==========================*/ +{ + free(srv_data_file_names); + srv_data_file_names = NULL; + free(srv_data_file_sizes); + srv_data_file_sizes = NULL; + free(srv_data_file_is_raw_partition); + srv_data_file_is_raw_partition = NULL; + free(srv_log_group_home_dirs); + srv_log_group_home_dirs = NULL; +} + +#ifndef UNIV_HOTBACKUP +/************************************************************************ +I/o-handler thread function. */ +static + +os_thread_ret_t +io_handler_thread( +/*==============*/ + void* arg) +{ + ulint segment; + ulint i; + + segment = *((ulint*)arg); + +#ifdef UNIV_DEBUG_THREAD_CREATION + fprintf(stderr, "Io handler thread %lu starts, id %lu\n", segment, + os_thread_pf(os_thread_get_curr_id())); +#endif + for (i = 0;; i++) { + fil_aio_wait(segment); + + mutex_enter(&ios_mutex); + ios++; + mutex_exit(&ios_mutex); + } + + /* We count the number of threads in os_thread_exit(). A created + thread should always use that to exit and not use return() to exit. + The thread actually never comes here because it is exited in an + os_event_wait(). */ + + os_thread_exit(NULL); + + OS_THREAD_DUMMY_RETURN; +} +#endif /* !UNIV_HOTBACKUP */ + +#ifdef __WIN__ +#define SRV_PATH_SEPARATOR '\\' +#else +#define SRV_PATH_SEPARATOR '/' +#endif + +/************************************************************************* +Normalizes a directory path for Windows: converts slashes to backslashes. */ +UNIV_INTERN +void +srv_normalize_path_for_win( +/*=======================*/ + char* str __attribute__((unused))) /* in/out: null-terminated + character string */ +{ +#ifdef __WIN__ + for (; *str; str++) { + + if (*str == '/') { + *str = '\\'; + } + } +#endif +} + +/************************************************************************* +Adds a slash or a backslash to the end of a string if it is missing +and the string is not empty. */ +UNIV_INTERN +char* +srv_add_path_separator_if_needed( +/*=============================*/ + /* out: string which has the separator if the + string is not empty */ + char* str) /* in: null-terminated character string */ +{ + char* out_str; + ulint len = ut_strlen(str); + + if (len == 0 || str[len - 1] == SRV_PATH_SEPARATOR) { + + return(str); + } + + out_str = ut_malloc(len + 2); + memcpy(out_str, str, len); + out_str[len] = SRV_PATH_SEPARATOR; + out_str[len + 1] = 0; + + return(out_str); +} + +#ifndef UNIV_HOTBACKUP +/************************************************************************* +Calculates the low 32 bits when a file size which is given as a number +database pages is converted to the number of bytes. */ +static +ulint +srv_calc_low32( +/*===========*/ + /* out: low 32 bytes of file size when + expressed in bytes */ + ulint file_size) /* in: file size in database pages */ +{ + return(0xFFFFFFFFUL & (file_size << UNIV_PAGE_SIZE_SHIFT)); +} + +/************************************************************************* +Calculates the high 32 bits when a file size which is given as a number +database pages is converted to the number of bytes. */ +static +ulint +srv_calc_high32( +/*============*/ + /* out: high 32 bytes of file size when + expressed in bytes */ + ulint file_size) /* in: file size in database pages */ +{ + return(file_size >> (32 - UNIV_PAGE_SIZE_SHIFT)); +} + +/************************************************************************* +Creates or opens the log files and closes them. */ +static +ulint +open_or_create_log_file( +/*====================*/ + /* out: DB_SUCCESS or error code */ + ibool create_new_db, /* in: TRUE if we should create a + new database */ + ibool* log_file_created, /* out: TRUE if new log file + created */ + ibool log_file_has_been_opened,/* in: TRUE if a log file has been + opened before: then it is an error + to try to create another log file */ + ulint k, /* in: log group number */ + ulint i) /* in: log file number in group */ +{ + ibool ret; + ulint size; + ulint size_high; + char name[10000]; + + UT_NOT_USED(create_new_db); + + *log_file_created = FALSE; + + srv_normalize_path_for_win(srv_log_group_home_dirs[k]); + srv_log_group_home_dirs[k] = srv_add_path_separator_if_needed( + srv_log_group_home_dirs[k]); + + ut_a(strlen(srv_log_group_home_dirs[k]) + < (sizeof name) - 10 - sizeof "ib_logfile"); + sprintf(name, "%s%s%lu", srv_log_group_home_dirs[k], + "ib_logfile", (ulong) i); + + files[i] = os_file_create(name, OS_FILE_CREATE, OS_FILE_NORMAL, + OS_LOG_FILE, &ret); + if (ret == FALSE) { + if (os_file_get_last_error(FALSE) != OS_FILE_ALREADY_EXISTS +#ifdef UNIV_AIX + /* AIX 5.1 after security patch ML7 may have errno set + to 0 here, which causes our function to return 100; + work around that AIX problem */ + && os_file_get_last_error(FALSE) != 100 +#endif + ) { + fprintf(stderr, + "InnoDB: Error in creating" + " or opening %s\n", name); + + return(DB_ERROR); + } + + files[i] = os_file_create(name, OS_FILE_OPEN, OS_FILE_AIO, + OS_LOG_FILE, &ret); + if (!ret) { + fprintf(stderr, + "InnoDB: Error in opening %s\n", name); + + return(DB_ERROR); + } + + ret = os_file_get_size(files[i], &size, &size_high); + ut_a(ret); + + if (size != srv_calc_low32(srv_log_file_size) + || size_high != srv_calc_high32(srv_log_file_size)) { + + fprintf(stderr, + "InnoDB: Error: log file %s is" + " of different size %lu %lu bytes\n" + "InnoDB: than specified in the .cnf" + " file %lu %lu bytes!\n", + name, (ulong) size_high, (ulong) size, + (ulong) srv_calc_high32(srv_log_file_size), + (ulong) srv_calc_low32(srv_log_file_size)); + + return(DB_ERROR); + } + } else { + *log_file_created = TRUE; + + ut_print_timestamp(stderr); + + fprintf(stderr, + " InnoDB: Log file %s did not exist:" + " new to be created\n", + name); + if (log_file_has_been_opened) { + + return(DB_ERROR); + } + + fprintf(stderr, "InnoDB: Setting log file %s size to %lu MB\n", + name, (ulong) srv_log_file_size + >> (20 - UNIV_PAGE_SIZE_SHIFT)); + + fprintf(stderr, + "InnoDB: Database physically writes the file" + " full: wait...\n"); + + ret = os_file_set_size(name, files[i], + srv_calc_low32(srv_log_file_size), + srv_calc_high32(srv_log_file_size)); + if (!ret) { + fprintf(stderr, + "InnoDB: Error in creating %s:" + " probably out of disk space\n", + name); + + return(DB_ERROR); + } + } + + ret = os_file_close(files[i]); + ut_a(ret); + + if (i == 0) { + /* Create in memory the file space object + which is for this log group */ + + fil_space_create(name, + 2 * k + SRV_LOG_SPACE_FIRST_ID, 0, FIL_LOG); + } + + ut_a(fil_validate()); + + fil_node_create(name, srv_log_file_size, + 2 * k + SRV_LOG_SPACE_FIRST_ID, FALSE); +#ifdef UNIV_LOG_ARCHIVE + /* If this is the first log group, create the file space object + for archived logs. + Under MySQL, no archiving ever done. */ + + if (k == 0 && i == 0) { + arch_space_id = 2 * k + 1 + SRV_LOG_SPACE_FIRST_ID; + + fil_space_create("arch_log_space", arch_space_id, 0, FIL_LOG); + } else { + arch_space_id = ULINT_UNDEFINED; + } +#endif /* UNIV_LOG_ARCHIVE */ + if (i == 0) { + log_group_init(k, srv_n_log_files, + srv_log_file_size * UNIV_PAGE_SIZE, + 2 * k + SRV_LOG_SPACE_FIRST_ID, + SRV_LOG_SPACE_FIRST_ID + 1); /* dummy arch + space id */ + } + + return(DB_SUCCESS); +} + +/************************************************************************* +Creates or opens database data files and closes them. */ +static +ulint +open_or_create_data_files( +/*======================*/ + /* out: DB_SUCCESS or error code */ + ibool* create_new_db, /* out: TRUE if new database should be + created */ +#ifdef UNIV_LOG_ARCHIVE + ulint* min_arch_log_no,/* out: min of archived log + numbers in data files */ + ulint* max_arch_log_no,/* out: max of archived log + numbers in data files */ +#endif /* UNIV_LOG_ARCHIVE */ + ib_uint64_t* min_flushed_lsn,/* out: min of flushed lsn + values in data files */ + ib_uint64_t* max_flushed_lsn,/* out: max of flushed lsn + values in data files */ + ulint* sum_of_new_sizes)/* out: sum of sizes of the + new files added */ +{ + ibool ret; + ulint i; + ibool one_opened = FALSE; + ibool one_created = FALSE; + ulint size; + ulint size_high; + ulint rounded_size_pages; + char name[10000]; + + if (srv_n_data_files >= 1000) { + fprintf(stderr, "InnoDB: can only have < 1000 data files\n" + "InnoDB: you have defined %lu\n", + (ulong) srv_n_data_files); + return(DB_ERROR); + } + + *sum_of_new_sizes = 0; + + *create_new_db = FALSE; + + srv_normalize_path_for_win(srv_data_home); + srv_data_home = srv_add_path_separator_if_needed(srv_data_home); + + for (i = 0; i < srv_n_data_files; i++) { + srv_normalize_path_for_win(srv_data_file_names[i]); + + ut_a(strlen(srv_data_home) + strlen(srv_data_file_names[i]) + < (sizeof name) - 1); + sprintf(name, "%s%s", srv_data_home, srv_data_file_names[i]); + + if (srv_data_file_is_raw_partition[i] == 0) { + + /* First we try to create the file: if it already + exists, ret will get value FALSE */ + + files[i] = os_file_create(name, OS_FILE_CREATE, + OS_FILE_NORMAL, + OS_DATA_FILE, &ret); + + if (ret == FALSE && os_file_get_last_error(FALSE) + != OS_FILE_ALREADY_EXISTS +#ifdef UNIV_AIX + /* AIX 5.1 after security patch ML7 may have + errno set to 0 here, which causes our function + to return 100; work around that AIX problem */ + && os_file_get_last_error(FALSE) != 100 +#endif + ) { + fprintf(stderr, + "InnoDB: Error in creating" + " or opening %s\n", + name); + + return(DB_ERROR); + } + } else if (srv_data_file_is_raw_partition[i] == SRV_NEW_RAW) { + /* The partition is opened, not created; then it is + written over */ + + srv_start_raw_disk_in_use = TRUE; + srv_created_new_raw = TRUE; + + files[i] = os_file_create(name, OS_FILE_OPEN_RAW, + OS_FILE_NORMAL, + OS_DATA_FILE, &ret); + if (!ret) { + fprintf(stderr, + "InnoDB: Error in opening %s\n", name); + + return(DB_ERROR); + } + } else if (srv_data_file_is_raw_partition[i] == SRV_OLD_RAW) { + srv_start_raw_disk_in_use = TRUE; + + ret = FALSE; + } else { + ut_a(0); + } + + if (ret == FALSE) { + /* We open the data file */ + + if (one_created) { + fprintf(stderr, + "InnoDB: Error: data files can only" + " be added at the end\n"); + fprintf(stderr, + "InnoDB: of a tablespace, but" + " data file %s existed beforehand.\n", + name); + return(DB_ERROR); + } + + if (srv_data_file_is_raw_partition[i] == SRV_OLD_RAW) { + files[i] = os_file_create( + name, OS_FILE_OPEN_RAW, + OS_FILE_NORMAL, OS_DATA_FILE, &ret); + } else if (i == 0) { + files[i] = os_file_create( + name, OS_FILE_OPEN_RETRY, + OS_FILE_NORMAL, OS_DATA_FILE, &ret); + } else { + files[i] = os_file_create( + name, OS_FILE_OPEN, OS_FILE_NORMAL, + OS_DATA_FILE, &ret); + } + + if (!ret) { + fprintf(stderr, + "InnoDB: Error in opening %s\n", name); + os_file_get_last_error(TRUE); + + return(DB_ERROR); + } + + if (srv_data_file_is_raw_partition[i] == SRV_OLD_RAW) { + + goto skip_size_check; + } + + ret = os_file_get_size(files[i], &size, &size_high); + ut_a(ret); + /* Round size downward to megabytes */ + + rounded_size_pages + = (size / (1024 * 1024) + 4096 * size_high) + << (20 - UNIV_PAGE_SIZE_SHIFT); + + if (i == srv_n_data_files - 1 + && srv_auto_extend_last_data_file) { + + if (srv_data_file_sizes[i] > rounded_size_pages + || (srv_last_file_size_max > 0 + && srv_last_file_size_max + < rounded_size_pages)) { + + fprintf(stderr, + "InnoDB: Error: auto-extending" + " data file %s is" + " of a different size\n" + "InnoDB: %lu pages (rounded" + " down to MB) than specified" + " in the .cnf file:\n" + "InnoDB: initial %lu pages," + " max %lu (relevant if" + " non-zero) pages!\n", + name, + (ulong) rounded_size_pages, + (ulong) srv_data_file_sizes[i], + (ulong) + srv_last_file_size_max); + + return(DB_ERROR); + } + + srv_data_file_sizes[i] = rounded_size_pages; + } + + if (rounded_size_pages != srv_data_file_sizes[i]) { + + fprintf(stderr, + "InnoDB: Error: data file %s" + " is of a different size\n" + "InnoDB: %lu pages" + " (rounded down to MB)\n" + "InnoDB: than specified" + " in the .cnf file %lu pages!\n", + name, + (ulong) rounded_size_pages, + (ulong) srv_data_file_sizes[i]); + + return(DB_ERROR); + } +skip_size_check: + fil_read_flushed_lsn_and_arch_log_no( + files[i], one_opened, +#ifdef UNIV_LOG_ARCHIVE + min_arch_log_no, max_arch_log_no, +#endif /* UNIV_LOG_ARCHIVE */ + min_flushed_lsn, max_flushed_lsn); + one_opened = TRUE; + } else { + /* We created the data file and now write it full of + zeros */ + + one_created = TRUE; + + if (i > 0) { + ut_print_timestamp(stderr); + fprintf(stderr, + " InnoDB: Data file %s did not" + " exist: new to be created\n", + name); + } else { + fprintf(stderr, + "InnoDB: The first specified" + " data file %s did not exist:\n" + "InnoDB: a new database" + " to be created!\n", name); + *create_new_db = TRUE; + } + + ut_print_timestamp(stderr); + fprintf(stderr, + " InnoDB: Setting file %s size to %lu MB\n", + name, + (ulong) (srv_data_file_sizes[i] + >> (20 - UNIV_PAGE_SIZE_SHIFT))); + + fprintf(stderr, + "InnoDB: Database physically writes the" + " file full: wait...\n"); + + ret = os_file_set_size( + name, files[i], + srv_calc_low32(srv_data_file_sizes[i]), + srv_calc_high32(srv_data_file_sizes[i])); + + if (!ret) { + fprintf(stderr, + "InnoDB: Error in creating %s:" + " probably out of disk space\n", name); + + return(DB_ERROR); + } + + *sum_of_new_sizes = *sum_of_new_sizes + + srv_data_file_sizes[i]; + } + + ret = os_file_close(files[i]); + ut_a(ret); + + if (i == 0) { + fil_space_create(name, 0, 0, FIL_TABLESPACE); + } + + ut_a(fil_validate()); + + fil_node_create(name, srv_data_file_sizes[i], 0, + srv_data_file_is_raw_partition[i] != 0); + } + + ios = 0; + + mutex_create(&ios_mutex, SYNC_NO_ORDER_CHECK); + + return(DB_SUCCESS); +} + +/******************************************************************** +Starts InnoDB and creates a new database if database files +are not found and the user wants. */ +UNIV_INTERN +int +innobase_start_or_create_for_mysql(void) +/*====================================*/ + /* out: DB_SUCCESS or error code */ +{ + buf_pool_t* ret; + ibool create_new_db; + ibool log_file_created; + ibool log_created = FALSE; + ibool log_opened = FALSE; + ib_uint64_t min_flushed_lsn; + ib_uint64_t max_flushed_lsn; +#ifdef UNIV_LOG_ARCHIVE + ulint min_arch_log_no; + ulint max_arch_log_no; +#endif /* UNIV_LOG_ARCHIVE */ + ulint sum_of_new_sizes; + ulint sum_of_data_file_sizes; + ulint tablespace_size_in_header; + ulint err; + ulint i; + my_bool srv_file_per_table_original_value + = srv_file_per_table; + mtr_t mtr; +#ifdef HAVE_DARWIN_THREADS +# ifdef F_FULLFSYNC + /* This executable has been compiled on Mac OS X 10.3 or later. + Assume that F_FULLFSYNC is available at run-time. */ + srv_have_fullfsync = TRUE; +# else /* F_FULLFSYNC */ + /* This executable has been compiled on Mac OS X 10.2 + or earlier. Determine if the executable is running + on Mac OS X 10.3 or later. */ + struct utsname utsname; + if (uname(&utsname)) { + fputs("InnoDB: cannot determine Mac OS X version!\n", stderr); + } else { + srv_have_fullfsync = strcmp(utsname.release, "7.") >= 0; + } + if (!srv_have_fullfsync) { + fputs("InnoDB: On Mac OS X, fsync() may be" + " broken on internal drives,\n" + "InnoDB: making transactions unsafe!\n", stderr); + } +# endif /* F_FULLFSYNC */ +#endif /* HAVE_DARWIN_THREADS */ + + if (sizeof(ulint) != sizeof(void*)) { + fprintf(stderr, + "InnoDB: Error: size of InnoDB's ulint is %lu," + " but size of void* is %lu.\n" + "InnoDB: The sizes should be the same" + " so that on a 64-bit platform you can\n" + "InnoDB: allocate more than 4 GB of memory.", + (ulong)sizeof(ulint), (ulong)sizeof(void*)); + } + + /* System tables are created in tablespace 0. Thus, we must + temporarily clear srv_file_per_table. This is ok, because the + server will not accept connections (which could modify + innodb_file_per_table) until this function has returned. */ + srv_file_per_table = FALSE; +#ifdef UNIV_DEBUG + fprintf(stderr, + "InnoDB: !!!!!!!! UNIV_DEBUG switched on !!!!!!!!!\n"); +#endif + +#ifdef UNIV_IBUF_DEBUG + fprintf(stderr, + "InnoDB: !!!!!!!! UNIV_IBUF_DEBUG switched on !!!!!!!!!\n" + "InnoDB: Crash recovery will fail with UNIV_IBUF_DEBUG\n"); +#endif + +#ifdef UNIV_SYNC_DEBUG + fprintf(stderr, + "InnoDB: !!!!!!!! UNIV_SYNC_DEBUG switched on !!!!!!!!!\n"); +#endif + +#ifdef UNIV_SEARCH_DEBUG + fprintf(stderr, + "InnoDB: !!!!!!!! UNIV_SEARCH_DEBUG switched on !!!!!!!!!\n"); +#endif + +#ifdef UNIV_MEM_DEBUG + fprintf(stderr, + "InnoDB: !!!!!!!! UNIV_MEM_DEBUG switched on !!!!!!!!!\n"); +#endif + + if (UNIV_LIKELY(srv_use_sys_malloc)) { + fprintf(stderr, + "InnoDB: The InnoDB memory heap is disabled\n"); + } + +#ifdef HAVE_GCC_ATOMIC_BUILTINS +#ifdef INNODB_RW_LOCKS_USE_ATOMICS + fprintf(stderr, + "InnoDB: Mutexes and rw_locks use GCC atomic builtins.\n"); +#else /* INNODB_RW_LOCKS_USE_ATOMICS */ + fprintf(stderr, + "InnoDB: Mutexes use GCC atomic builtins, rw_locks do not.\n"); +#endif /* INNODB_RW_LOCKS_USE_ATOMICS */ +#else /* HAVE_GCC_ATOMIC_BUILTINS */ + fprintf(stderr, + "InnoDB: Neither mutexes nor rw_locks use GCC atomic builtins.\n"); +#endif /* HAVE_GCC_ATOMIC_BUILTINS */ + + /* Since InnoDB does not currently clean up all its internal data + structures in MySQL Embedded Server Library server_end(), we + print an error message if someone tries to start up InnoDB a + second time during the process lifetime. */ + + if (srv_start_has_been_called) { + fprintf(stderr, + "InnoDB: Error:startup called second time" + " during the process lifetime.\n" + "InnoDB: In the MySQL Embedded Server Library" + " you cannot call server_init()\n" + "InnoDB: more than once during" + " the process lifetime.\n"); + } + + srv_start_has_been_called = TRUE; + +#ifdef UNIV_DEBUG + log_do_write = TRUE; +#endif /* UNIV_DEBUG */ + /* yydebug = TRUE; */ + + srv_is_being_started = TRUE; + srv_startup_is_before_trx_rollback_phase = TRUE; + os_aio_use_native_aio = FALSE; + +#ifdef __WIN__ + if (os_get_os_version() == OS_WIN95 + || os_get_os_version() == OS_WIN31 + || os_get_os_version() == OS_WINNT) { + + /* On Win 95, 98, ME, Win32 subsystem for Windows 3.1, + and NT use simulated aio. In NT Windows provides async i/o, + but when run in conjunction with InnoDB Hot Backup, it seemed + to corrupt the data files. */ + + os_aio_use_native_aio = FALSE; + } else { + /* On Win 2000 and XP use async i/o */ + os_aio_use_native_aio = TRUE; + } +#endif + if (srv_file_flush_method_str == NULL) { + /* These are the default options */ + + srv_unix_file_flush_method = SRV_UNIX_FSYNC; + + srv_win_file_flush_method = SRV_WIN_IO_UNBUFFERED; +#ifndef __WIN__ + } else if (0 == ut_strcmp(srv_file_flush_method_str, "fsync")) { + srv_unix_file_flush_method = SRV_UNIX_FSYNC; + + } else if (0 == ut_strcmp(srv_file_flush_method_str, "O_DSYNC")) { + srv_unix_file_flush_method = SRV_UNIX_O_DSYNC; + + } else if (0 == ut_strcmp(srv_file_flush_method_str, "O_DIRECT")) { + srv_unix_file_flush_method = SRV_UNIX_O_DIRECT; + + } else if (0 == ut_strcmp(srv_file_flush_method_str, "littlesync")) { + srv_unix_file_flush_method = SRV_UNIX_LITTLESYNC; + + } else if (0 == ut_strcmp(srv_file_flush_method_str, "nosync")) { + srv_unix_file_flush_method = SRV_UNIX_NOSYNC; +#else + } else if (0 == ut_strcmp(srv_file_flush_method_str, "normal")) { + srv_win_file_flush_method = SRV_WIN_IO_NORMAL; + os_aio_use_native_aio = FALSE; + + } else if (0 == ut_strcmp(srv_file_flush_method_str, "unbuffered")) { + srv_win_file_flush_method = SRV_WIN_IO_UNBUFFERED; + os_aio_use_native_aio = FALSE; + + } else if (0 == ut_strcmp(srv_file_flush_method_str, + "async_unbuffered")) { + srv_win_file_flush_method = SRV_WIN_IO_UNBUFFERED; +#endif + } else { + fprintf(stderr, + "InnoDB: Unrecognized value %s for" + " innodb_flush_method\n", + srv_file_flush_method_str); + return(DB_ERROR); + } + + /* Note that the call srv_boot() also changes the values of + some variables to the units used by InnoDB internally */ + + /* Set the maximum number of threads which can wait for a semaphore + inside InnoDB: this is the 'sync wait array' size, as well as the + maximum number of threads that can wait in the 'srv_conc array' for + their time to enter InnoDB. */ + +#if defined(__NETWARE__) + + /* Create less event semaphores because Win 98/ME had + difficulty creating 40000 event semaphores. Comment from + Novell, Inc.: also, these just take a lot of memory on + NetWare. */ + srv_max_n_threads = 1000; +#else + if (srv_buf_pool_size >= 1000 * 1024 * 1024) { + /* If buffer pool is less than 1000 MB, + assume fewer threads. */ + srv_max_n_threads = 50000; + + } else if (srv_buf_pool_size >= 8 * 1024 * 1024) { + + srv_max_n_threads = 10000; + } else { + srv_max_n_threads = 1000; /* saves several MB of memory, + especially in 64-bit + computers */ + } +#endif + err = srv_boot(); + + if (err != DB_SUCCESS) { + + return((int) err); + } + + mutex_create(&srv_monitor_file_mutex, SYNC_NO_ORDER_CHECK); + + if (srv_innodb_status) { + srv_monitor_file_name = mem_alloc( + strlen(fil_path_to_mysql_datadir) + + 20 + sizeof "/innodb_status."); + sprintf(srv_monitor_file_name, "%s/innodb_status.%lu", + fil_path_to_mysql_datadir, os_proc_get_number()); + srv_monitor_file = fopen(srv_monitor_file_name, "w+"); + if (!srv_monitor_file) { + fprintf(stderr, "InnoDB: unable to create %s: %s\n", + srv_monitor_file_name, strerror(errno)); + return(DB_ERROR); + } + } else { + srv_monitor_file_name = NULL; + srv_monitor_file = os_file_create_tmpfile(); + if (!srv_monitor_file) { + return(DB_ERROR); + } + } + + mutex_create(&srv_dict_tmpfile_mutex, SYNC_DICT_OPERATION); + + srv_dict_tmpfile = os_file_create_tmpfile(); + if (!srv_dict_tmpfile) { + return(DB_ERROR); + } + + mutex_create(&srv_misc_tmpfile_mutex, SYNC_ANY_LATCH); + + srv_misc_tmpfile = os_file_create_tmpfile(); + if (!srv_misc_tmpfile) { + return(DB_ERROR); + } + + /* over write innodb_file_io_threads */ + srv_n_file_io_threads = 2 + srv_n_read_io_threads + srv_n_write_io_threads; + + /* Restrict the maximum number of file i/o threads */ + if (srv_n_file_io_threads > SRV_MAX_N_IO_THREADS) { + + srv_n_file_io_threads = SRV_MAX_N_IO_THREADS; + srv_n_read_io_threads = srv_n_write_io_threads = (SRV_MAX_N_IO_THREADS - 2) / 2; + } + + if (!os_aio_use_native_aio) { + /* In simulated aio we currently have use only for 4 threads */ + /*srv_n_file_io_threads = 4;*/ + + os_aio_init(8 * SRV_N_PENDING_IOS_PER_THREAD + * srv_n_file_io_threads, + srv_n_read_io_threads, srv_n_write_io_threads, + SRV_MAX_N_PENDING_SYNC_IOS); + } else { + os_aio_init(SRV_N_PENDING_IOS_PER_THREAD + * srv_n_file_io_threads, + srv_n_read_io_threads, srv_n_write_io_threads, + SRV_MAX_N_PENDING_SYNC_IOS); + } + + fil_init(srv_max_n_open_files); + + ret = buf_pool_init(); + + if (ret == NULL) { + fprintf(stderr, + "InnoDB: Fatal error: cannot allocate the memory" + " for the buffer pool\n"); + + return(DB_ERROR); + } + +#ifdef UNIV_DEBUG + /* We have observed deadlocks with a 5MB buffer pool but + the actual lower limit could very well be a little higher. */ + + if (srv_buf_pool_size <= 5 * 1024 * 1024) { + + fprintf(stderr, "InnoDB: Warning: Small buffer pool size " + "(%luM), the flst_validate() debug function " + "can cause a deadlock if the buffer pool fills up.\n", + srv_buf_pool_size / 1024 / 1024); + } +#endif + + fsp_init(); + log_init(); + + lock_sys_create(srv_lock_table_size); + + /* Create i/o-handler threads: */ + + for (i = 0; i < srv_n_file_io_threads; i++) { + n[i] = i; + + os_thread_create(io_handler_thread, n + i, thread_ids + i); + } + +#ifdef UNIV_LOG_ARCHIVE + if (0 != ut_strcmp(srv_log_group_home_dirs[0], srv_arch_dir)) { + fprintf(stderr, + "InnoDB: Error: you must set the log group" + " home dir in my.cnf the\n" + "InnoDB: same as log arch dir.\n"); + + return(DB_ERROR); + } +#endif /* UNIV_LOG_ARCHIVE */ + + if (srv_n_log_files * srv_log_file_size >= 262144) { + fprintf(stderr, + "InnoDB: Error: combined size of log files" + " must be < 4 GB\n"); + + return(DB_ERROR); + } + + sum_of_new_sizes = 0; + + for (i = 0; i < srv_n_data_files; i++) { +#ifndef __WIN__ + if (sizeof(off_t) < 5 && srv_data_file_sizes[i] >= 262144) { + fprintf(stderr, + "InnoDB: Error: file size must be < 4 GB" + " with this MySQL binary\n" + "InnoDB: and operating system combination," + " in some OS's < 2 GB\n"); + + return(DB_ERROR); + } +#endif + sum_of_new_sizes += srv_data_file_sizes[i]; + } + + if (sum_of_new_sizes < 640) { + fprintf(stderr, + "InnoDB: Error: tablespace size must be" + " at least 10 MB\n"); + + return(DB_ERROR); + } + + err = open_or_create_data_files(&create_new_db, +#ifdef UNIV_LOG_ARCHIVE + &min_arch_log_no, &max_arch_log_no, +#endif /* UNIV_LOG_ARCHIVE */ + &min_flushed_lsn, &max_flushed_lsn, + &sum_of_new_sizes); + if (err != DB_SUCCESS) { + fprintf(stderr, + "InnoDB: Could not open or create data files.\n" + "InnoDB: If you tried to add new data files," + " and it failed here,\n" + "InnoDB: you should now edit innodb_data_file_path" + " in my.cnf back\n" + "InnoDB: to what it was, and remove the" + " new ibdata files InnoDB created\n" + "InnoDB: in this failed attempt. InnoDB only wrote" + " those files full of\n" + "InnoDB: zeros, but did not yet use them in any way." + " But be careful: do not\n" + "InnoDB: remove old data files" + " which contain your precious data!\n"); + + return((int) err); + } + +#ifdef UNIV_LOG_ARCHIVE + srv_normalize_path_for_win(srv_arch_dir); + srv_arch_dir = srv_add_path_separator_if_needed(srv_arch_dir); +#endif /* UNIV_LOG_ARCHIVE */ + + for (i = 0; i < srv_n_log_files; i++) { + err = open_or_create_log_file(create_new_db, &log_file_created, + log_opened, 0, i); + if (err != DB_SUCCESS) { + + return((int) err); + } + + if (log_file_created) { + log_created = TRUE; + } else { + log_opened = TRUE; + } + if ((log_opened && create_new_db) + || (log_opened && log_created)) { + fprintf(stderr, + "InnoDB: Error: all log files must be" + " created at the same time.\n" + "InnoDB: All log files must be" + " created also in database creation.\n" + "InnoDB: If you want bigger or smaller" + " log files, shut down the\n" + "InnoDB: database and make sure there" + " were no errors in shutdown.\n" + "InnoDB: Then delete the existing log files." + " Edit the .cnf file\n" + "InnoDB: and start the database again.\n"); + + return(DB_ERROR); + } + } + + /* Open all log files and data files in the system tablespace: we + keep them open until database shutdown */ + + fil_open_log_and_system_tablespace_files(); + + if (log_created && !create_new_db +#ifdef UNIV_LOG_ARCHIVE + && !srv_archive_recovery +#endif /* UNIV_LOG_ARCHIVE */ + ) { + if (max_flushed_lsn != min_flushed_lsn +#ifdef UNIV_LOG_ARCHIVE + || max_arch_log_no != min_arch_log_no +#endif /* UNIV_LOG_ARCHIVE */ + ) { + fprintf(stderr, + "InnoDB: Cannot initialize created" + " log files because\n" + "InnoDB: data files were not in sync" + " with each other\n" + "InnoDB: or the data files are corrupt.\n"); + + return(DB_ERROR); + } + + if (max_flushed_lsn < (ib_uint64_t) 1000) { + fprintf(stderr, + "InnoDB: Cannot initialize created" + " log files because\n" + "InnoDB: data files are corrupt," + " or new data files were\n" + "InnoDB: created when the database" + " was started previous\n" + "InnoDB: time but the database" + " was not shut down\n" + "InnoDB: normally after that.\n"); + + return(DB_ERROR); + } + + mutex_enter(&(log_sys->mutex)); + +#ifdef UNIV_LOG_ARCHIVE + /* Do not + 1 arch_log_no because we do not use log + archiving */ + recv_reset_logs(max_flushed_lsn, max_arch_log_no, TRUE); +#else + recv_reset_logs(max_flushed_lsn, TRUE); +#endif /* UNIV_LOG_ARCHIVE */ + + mutex_exit(&(log_sys->mutex)); + } + + trx_sys_file_format_init(); + + if (create_new_db) { + mtr_start(&mtr); + fsp_header_init(0, sum_of_new_sizes, &mtr); + + mtr_commit(&mtr); + + trx_sys_create(); + dict_create(); + srv_startup_is_before_trx_rollback_phase = FALSE; + + if (srv_extra_rsegments) + trx_sys_create_extra_rseg(srv_extra_rsegments); +#ifdef UNIV_LOG_ARCHIVE + } else if (srv_archive_recovery) { + fprintf(stderr, + "InnoDB: Starting archive" + " recovery from a backup...\n"); + err = recv_recovery_from_archive_start( + min_flushed_lsn, srv_archive_recovery_limit_lsn, + min_arch_log_no); + if (err != DB_SUCCESS) { + + return(DB_ERROR); + } + /* Since ibuf init is in dict_boot, and ibuf is needed + in any disk i/o, first call dict_boot */ + + dict_boot(); + trx_sys_init_at_db_start(); + srv_startup_is_before_trx_rollback_phase = FALSE; + + /* Initialize the fsp free limit global variable in the log + system */ + fsp_header_get_free_limit(); + + recv_recovery_from_archive_finish(); +#endif /* UNIV_LOG_ARCHIVE */ + } else { + + /* Check if we support the max format that is stamped + on the system tablespace. + Note: We are NOT allowed to make any modifications to + the TRX_SYS_PAGE_NO page before recovery because this + page also contains the max_trx_id etc. important system + variables that are required for recovery. We need to + ensure that we return the system to a state where normal + recovery is guaranteed to work. We do this by + invalidating the buffer cache, this will force the + reread of the page and restoration to its last known + consistent state, this is REQUIRED for the recovery + process to work. */ + err = trx_sys_file_format_max_check( + srv_check_file_format_at_startup); + + if (err != DB_SUCCESS) { + return(err); + } + + /* Invalidate the buffer pool to ensure that we reread + the page that we read above, during recovery. + Note that this is not as heavy weight as it seems. At + this point there will be only ONE page in the buf_LRU + and there must be no page in the buf_flush list. */ + buf_pool_invalidate(); + + /* We always try to do a recovery, even if the database had + been shut down normally: this is the normal startup path */ + + err = recv_recovery_from_checkpoint_start(LOG_CHECKPOINT, + IB_ULONGLONG_MAX, + min_flushed_lsn, + max_flushed_lsn); + if (err != DB_SUCCESS) { + + return(DB_ERROR); + } + + /* Since the insert buffer init is in dict_boot, and the + insert buffer is needed in any disk i/o, first we call + dict_boot(). Note that trx_sys_init_at_db_start() only needs + to access space 0, and the insert buffer at this stage already + works for space 0. */ + + dict_boot(); + trx_sys_init_at_db_start(); + + if (srv_force_recovery < SRV_FORCE_NO_IBUF_MERGE) { + /* The following call is necessary for the insert + buffer to work with multiple tablespaces. We must + know the mapping between space id's and .ibd file + names. + + In a crash recovery, we check that the info in data + dictionary is consistent with what we already know + about space id's from the call of + fil_load_single_table_tablespaces(). + + In a normal startup, we create the space objects for + every table in the InnoDB data dictionary that has + an .ibd file. + + We also determine the maximum tablespace id used. + + TODO: We may have incomplete transactions in the + data dictionary tables. Does that harm the scanning of + the data dictionary below? */ + + dict_check_tablespaces_and_store_max_id( + recv_needed_recovery); + } + + srv_startup_is_before_trx_rollback_phase = FALSE; + + /* Initialize the fsp free limit global variable in the log + system */ + fsp_header_get_free_limit(); + + /* recv_recovery_from_checkpoint_finish needs trx lists which + are initialized in trx_sys_init_at_db_start(). */ + + recv_recovery_from_checkpoint_finish(); + + /* It is possible that file_format tag has never + been set. In this case we initialize it to minimum + value. Important to note that we can do it ONLY after + we have finished the recovery process so that the + image of TRX_SYS_PAGE_NO is not stale. */ + trx_sys_file_format_tag_init(); + } + + if (!create_new_db && sum_of_new_sizes > 0) { + /* New data file(s) were added */ + mtr_start(&mtr); + + fsp_header_inc_size(0, sum_of_new_sizes, &mtr); + + mtr_commit(&mtr); + + /* Immediately write the log record about increased tablespace + size to disk, so that it is durable even if mysqld would crash + quickly */ + + log_buffer_flush_to_disk(); + } + +#ifdef UNIV_LOG_ARCHIVE + /* Archiving is always off under MySQL */ + if (!srv_log_archive_on) { + ut_a(DB_SUCCESS == log_archive_noarchivelog()); + } else { + mutex_enter(&(log_sys->mutex)); + + start_archive = FALSE; + + if (log_sys->archiving_state == LOG_ARCH_OFF) { + start_archive = TRUE; + } + + mutex_exit(&(log_sys->mutex)); + + if (start_archive) { + ut_a(DB_SUCCESS == log_archive_archivelog()); + } + } +#endif /* UNIV_LOG_ARCHIVE */ + + /* fprintf(stderr, "Max allowed record size %lu\n", + page_get_free_space_of_empty() / 2); */ + + /* Create the thread which watches the timeouts for lock waits + and prints InnoDB monitor info */ + + os_thread_create(&srv_lock_timeout_and_monitor_thread, NULL, + thread_ids + 2 + SRV_MAX_N_IO_THREADS); + + /* Create the thread which warns of long semaphore waits */ + os_thread_create(&srv_error_monitor_thread, NULL, + thread_ids + 3 + SRV_MAX_N_IO_THREADS); + srv_is_being_started = FALSE; + + if (trx_doublewrite == NULL) { + /* Create the doublewrite buffer to a new tablespace */ + + trx_sys_create_doublewrite_buf(); + } + + err = dict_create_or_check_foreign_constraint_tables(); + + if (err != DB_SUCCESS) { + return((int)DB_ERROR); + } + + /* Create the master thread which does purge and other utility + operations */ + + os_thread_create(&srv_master_thread, NULL, thread_ids + + (1 + SRV_MAX_N_IO_THREADS)); +#ifdef UNIV_DEBUG + /* buf_debug_prints = TRUE; */ +#endif /* UNIV_DEBUG */ + sum_of_data_file_sizes = 0; + + for (i = 0; i < srv_n_data_files; i++) { + sum_of_data_file_sizes += srv_data_file_sizes[i]; + } + + tablespace_size_in_header = fsp_header_get_tablespace_size(); + + if (!srv_auto_extend_last_data_file + && sum_of_data_file_sizes != tablespace_size_in_header) { + + fprintf(stderr, + "InnoDB: Error: tablespace size" + " stored in header is %lu pages, but\n" + "InnoDB: the sum of data file sizes is %lu pages\n", + (ulong) tablespace_size_in_header, + (ulong) sum_of_data_file_sizes); + + if (srv_force_recovery == 0 + && sum_of_data_file_sizes < tablespace_size_in_header) { + /* This is a fatal error, the tail of a tablespace is + missing */ + + fprintf(stderr, + "InnoDB: Cannot start InnoDB." + " The tail of the system tablespace is\n" + "InnoDB: missing. Have you edited" + " innodb_data_file_path in my.cnf in an\n" + "InnoDB: inappropriate way, removing" + " ibdata files from there?\n" + "InnoDB: You can set innodb_force_recovery=1" + " in my.cnf to force\n" + "InnoDB: a startup if you are trying" + " to recover a badly corrupt database.\n"); + + return(DB_ERROR); + } + } + + if (srv_auto_extend_last_data_file + && sum_of_data_file_sizes < tablespace_size_in_header) { + + fprintf(stderr, + "InnoDB: Error: tablespace size stored in header" + " is %lu pages, but\n" + "InnoDB: the sum of data file sizes" + " is only %lu pages\n", + (ulong) tablespace_size_in_header, + (ulong) sum_of_data_file_sizes); + + if (srv_force_recovery == 0) { + + fprintf(stderr, + "InnoDB: Cannot start InnoDB. The tail of" + " the system tablespace is\n" + "InnoDB: missing. Have you edited" + " innodb_data_file_path in my.cnf in an\n" + "InnoDB: inappropriate way, removing" + " ibdata files from there?\n" + "InnoDB: You can set innodb_force_recovery=1" + " in my.cnf to force\n" + "InnoDB: a startup if you are trying to" + " recover a badly corrupt database.\n"); + + return(DB_ERROR); + } + } + + /* Check that os_fast_mutexes work as expected */ + os_fast_mutex_init(&srv_os_test_mutex); + + if (0 != os_fast_mutex_trylock(&srv_os_test_mutex)) { + fprintf(stderr, + "InnoDB: Error: pthread_mutex_trylock returns" + " an unexpected value on\n" + "InnoDB: success! Cannot continue.\n"); + exit(1); + } + + os_fast_mutex_unlock(&srv_os_test_mutex); + + os_fast_mutex_lock(&srv_os_test_mutex); + + os_fast_mutex_unlock(&srv_os_test_mutex); + + os_fast_mutex_free(&srv_os_test_mutex); + + if (srv_print_verbose_log) { + ut_print_timestamp(stderr); + fprintf(stderr, + " InnoDB Plugin %s started; " + "log sequence number %llu\n", + INNODB_VERSION_STR, srv_start_lsn); + } + + if (srv_force_recovery > 0) { + fprintf(stderr, + "InnoDB: !!! innodb_force_recovery" + " is set to %lu !!!\n", + (ulong) srv_force_recovery); + } + + fflush(stderr); + + if (trx_doublewrite_must_reset_space_ids) { + /* Actually, we did not change the undo log format between + 4.0 and 4.1.1, and we would not need to run purge to + completion. Note also that the purge algorithm in 4.1.1 + can process the the history list again even after a full + purge, because our algorithm does not cut the end of the + history list in all cases so that it would become empty + after a full purge. That mean that we may purge 4.0 type + undo log even after this phase. + + The insert buffer record format changed between 4.0 and + 4.1.1. It is essential that the insert buffer is emptied + here! */ + + fprintf(stderr, + "InnoDB: You are upgrading to an" + " InnoDB version which allows multiple\n" + "InnoDB: tablespaces. Wait that purge" + " and insert buffer merge run to\n" + "InnoDB: completion...\n"); + for (;;) { + os_thread_sleep(1000000); + + if (0 == strcmp(srv_main_thread_op_info, + "waiting for server activity")) { + + ut_a(ibuf_is_empty()); + + break; + } + } + fprintf(stderr, + "InnoDB: Full purge and insert buffer merge" + " completed.\n"); + + trx_sys_mark_upgraded_to_multiple_tablespaces(); + + fprintf(stderr, + "InnoDB: You have now successfully upgraded" + " to the multiple tablespaces\n" + "InnoDB: format. You should NOT DOWNGRADE" + " to an earlier version of\n" + "InnoDB: InnoDB! But if you absolutely need to" + " downgrade, see\n" + "InnoDB: http://dev.mysql.com/doc/refman/5.1/en/" + "multiple-tablespaces.html\n" + "InnoDB: for instructions.\n"); + } + + if (srv_force_recovery == 0) { + /* In the insert buffer we may have even bigger tablespace + id's, because we may have dropped those tablespaces, but + insert buffer merge has not had time to clean the records from + the ibuf tree. */ + + ibuf_update_max_tablespace_id(); + } + + srv_file_per_table = srv_file_per_table_original_value; + + srv_was_started = TRUE; + + return((int) DB_SUCCESS); +} + +/******************************************************************** +Shuts down the InnoDB database. */ +UNIV_INTERN +int +innobase_shutdown_for_mysql(void) +/*=============================*/ + /* out: DB_SUCCESS or error code */ +{ + ulint i; +#ifdef __NETWARE__ + extern ibool panic_shutdown; +#endif + if (!srv_was_started) { + if (srv_is_being_started) { + ut_print_timestamp(stderr); + fprintf(stderr, + " InnoDB: Warning: shutting down" + " a not properly started\n" + "InnoDB: or created database!\n"); + } + + return(DB_SUCCESS); + } + + /* 1. Flush the buffer pool to disk, write the current lsn to + the tablespace header(s), and copy all log data to archive. + The step 1 is the real InnoDB shutdown. The remaining steps 2 - ... + just free data structures after the shutdown. */ + + + if (srv_fast_shutdown == 2) { + ut_print_timestamp(stderr); + fprintf(stderr, + " InnoDB: MySQL has requested a very fast shutdown" + " without flushing " + "the InnoDB buffer pool to data files." + " At the next mysqld startup " + "InnoDB will do a crash recovery!\n"); + } + +#ifdef __NETWARE__ + if(!panic_shutdown) +#endif + logs_empty_and_mark_files_at_shutdown(); + + if (srv_conc_n_threads != 0) { + fprintf(stderr, + "InnoDB: Warning: query counter shows %ld queries" + " still\n" + "InnoDB: inside InnoDB at shutdown\n", + srv_conc_n_threads); + } + + /* 2. Make all threads created by InnoDB to exit */ + + srv_shutdown_state = SRV_SHUTDOWN_EXIT_THREADS; + + /* In a 'very fast' shutdown, we do not need to wait for these threads + to die; all which counts is that we flushed the log; a 'very fast' + shutdown is essentially a crash. */ + + if (srv_fast_shutdown == 2) { + return(DB_SUCCESS); + } + + /* All threads end up waiting for certain events. Put those events + to the signaled state. Then the threads will exit themselves in + os_thread_event_wait(). */ + + for (i = 0; i < 1000; i++) { + /* NOTE: IF YOU CREATE THREADS IN INNODB, YOU MUST EXIT THEM + HERE OR EARLIER */ + + /* a. Let the lock timeout thread exit */ + os_event_set(srv_lock_timeout_thread_event); + + /* b. srv error monitor thread exits automatically, no need + to do anything here */ + + /* c. We wake the master thread so that it exits */ + srv_wake_master_thread(); + + /* d. Exit the i/o threads */ + + os_aio_wake_all_threads_at_shutdown(); + + os_mutex_enter(os_sync_mutex); + + if (os_thread_count == 0) { + /* All the threads have exited or are just exiting; + NOTE that the threads may not have completed their + exit yet. Should we use pthread_join() to make sure + they have exited? Now we just sleep 0.1 seconds and + hope that is enough! */ + + os_mutex_exit(os_sync_mutex); + + os_thread_sleep(100000); + + break; + } + + os_mutex_exit(os_sync_mutex); + + os_thread_sleep(100000); + } + + if (i == 1000) { + fprintf(stderr, + "InnoDB: Warning: %lu threads created by InnoDB" + " had not exited at shutdown!\n", + (ulong) os_thread_count); + } + + if (srv_monitor_file) { + fclose(srv_monitor_file); + srv_monitor_file = 0; + if (srv_monitor_file_name) { + unlink(srv_monitor_file_name); + mem_free(srv_monitor_file_name); + } + } + if (srv_dict_tmpfile) { + fclose(srv_dict_tmpfile); + srv_dict_tmpfile = 0; + } + + if (srv_misc_tmpfile) { + fclose(srv_misc_tmpfile); + srv_misc_tmpfile = 0; + } + + trx_sys_file_format_close(); + + mutex_free(&srv_monitor_file_mutex); + mutex_free(&srv_dict_tmpfile_mutex); + mutex_free(&srv_misc_tmpfile_mutex); + + /* 3. Free all InnoDB's own mutexes and the os_fast_mutexes inside + them */ + sync_close(); + + /* 4. Free the os_conc_mutex and all os_events and os_mutexes */ + + srv_free(); + os_sync_free(); + + /* Check that all read views are closed except read view owned + by a purge. */ + + if (UT_LIST_GET_LEN(trx_sys->view_list) > 1) { + fprintf(stderr, + "InnoDB: Error: all read views were not closed" + " before shutdown:\n" + "InnoDB: %lu read views open \n", + UT_LIST_GET_LEN(trx_sys->view_list) - 1); + } + + /* 5. Free all allocated memory and the os_fast_mutex created in + ut0mem.c */ + + buf_pool_free(); + ut_free_all_mem(); + + if (os_thread_count != 0 + || os_event_count != 0 + || os_mutex_count != 0 + || os_fast_mutex_count != 0) { + fprintf(stderr, + "InnoDB: Warning: some resources were not" + " cleaned up in shutdown:\n" + "InnoDB: threads %lu, events %lu," + " os_mutexes %lu, os_fast_mutexes %lu\n", + (ulong) os_thread_count, (ulong) os_event_count, + (ulong) os_mutex_count, (ulong) os_fast_mutex_count); + } + + if (dict_foreign_err_file) { + fclose(dict_foreign_err_file); + } + if (lock_latest_err_file) { + fclose(lock_latest_err_file); + } + + if (srv_print_verbose_log) { + ut_print_timestamp(stderr); + fprintf(stderr, + " InnoDB: Shutdown completed;" + " log sequence number %llu\n", + srv_shutdown_lsn); + } + + srv_was_started = FALSE; + + return((int) DB_SUCCESS); +} + +#ifdef __NETWARE__ +void set_panic_flag_for_netware() +{ + extern ibool panic_shutdown; + panic_shutdown = TRUE; +} +#endif /* __NETWARE__ */ +#endif /* !UNIV_HOTBACKUP */ diff --git a/storage/xtradb/sync/sync0arr.c b/storage/xtradb/sync/sync0arr.c new file mode 100644 index 00000000000..62165eefd46 --- /dev/null +++ b/storage/xtradb/sync/sync0arr.c @@ -0,0 +1,1029 @@ +/***************************************************************************** + +Copyright (c) 1995, 2009, Innobase Oy. All Rights Reserved. +Copyright (c) 2008, Google Inc. + +Portions of this file contain modifications contributed and copyrighted by +Google, Inc. Those modifications are gratefully acknowledged and are described +briefly in the InnoDB documentation. The contributions by Google are +incorporated with their permission, and subject to the conditions contained in +the file COPYING.Google. + +This program is free software; you can redistribute it and/or modify it under +the terms of the GNU General Public License as published by the Free Software +Foundation; version 2 of the License. + +This program is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + +You 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 + +*****************************************************************************/ + +/****************************************************** +The wait array used in synchronization primitives + +Created 9/5/1995 Heikki Tuuri +*******************************************************/ + +#include "sync0arr.h" +#ifdef UNIV_NONINL +#include "sync0arr.ic" +#endif + +#include "sync0sync.h" +#include "sync0rw.h" +#include "os0sync.h" +#include "os0file.h" +#include "srv0srv.h" + +/* + WAIT ARRAY + ========== + +The wait array consists of cells each of which has an +an operating system event object created for it. The threads +waiting for a mutex, for example, can reserve a cell +in the array and suspend themselves to wait for the event +to become signaled. When using the wait array, remember to make +sure that some thread holding the synchronization object +will eventually know that there is a waiter in the array and +signal the object, to prevent infinite wait. +Why we chose to implement a wait array? First, to make +mutexes fast, we had to code our own implementation of them, +which only in usually uncommon cases resorts to using +slow operating system primitives. Then we had the choice of +assigning a unique OS event for each mutex, which would +be simpler, or using a global wait array. In some operating systems, +the global wait array solution is more efficient and flexible, +because we can do with a very small number of OS events, +say 200. In NT 3.51, allocating events seems to be a quadratic +algorithm, because 10 000 events are created fast, but +100 000 events takes a couple of minutes to create. + +As of 5.0.30 the above mentioned design is changed. Since now +OS can handle millions of wait events efficiently, we no longer +have this concept of each cell of wait array having one event. +Instead, now the event that a thread wants to wait on is embedded +in the wait object (mutex or rw_lock). We still keep the global +wait array for the sake of diagnostics and also to avoid infinite +wait The error_monitor thread scans the global wait array to signal +any waiting threads who have missed the signal. */ + +/* A cell where an individual thread may wait suspended +until a resource is released. The suspending is implemented +using an operating system event semaphore. */ +struct sync_cell_struct { + void* wait_object; /* pointer to the object the + thread is waiting for; if NULL + the cell is free for use */ + mutex_t* old_wait_mutex; /* the latest wait mutex in cell */ + rw_lock_t* old_wait_rw_lock;/* the latest wait rw-lock in cell */ + ulint request_type; /* lock type requested on the + object */ + const char* file; /* in debug version file where + requested */ + ulint line; /* in debug version line where + requested */ + os_thread_id_t thread; /* thread id of this waiting + thread */ + ibool waiting; /* TRUE if the thread has already + called sync_array_event_wait + on this cell */ + ib_int64_t signal_count; /* We capture the signal_count + of the wait_object when we + reset the event. This value is + then passed on to os_event_wait + and we wait only if the event + has not been signalled in the + period between the reset and + wait call. */ + time_t reservation_time;/* time when the thread reserved + the wait cell */ +}; + +/* NOTE: It is allowed for a thread to wait +for an event allocated for the array without owning the +protecting mutex (depending on the case: OS or database mutex), but +all changes (set or reset) to the state of the event must be made +while owning the mutex. */ +struct sync_array_struct { + ulint n_reserved; /* number of currently reserved + cells in the wait array */ + ulint n_cells; /* number of cells in the + wait array */ + sync_cell_t* array; /* pointer to wait array */ + ulint protection; /* this flag tells which + mutex protects the data */ + mutex_t mutex; /* possible database mutex + protecting this data structure */ + os_mutex_t os_mutex; /* Possible operating system mutex + protecting the data structure. + As this data structure is used in + constructing the database mutex, + to prevent infinite recursion + in implementation, we fall back to + an OS mutex. */ + ulint sg_count; /* count of how many times an + object has been signalled */ + ulint res_count; /* count of cell reservations + since creation of the array */ +}; + +#ifdef UNIV_SYNC_DEBUG +/********************************************************************** +This function is called only in the debug version. Detects a deadlock +of one or more threads because of waits of semaphores. */ +static +ibool +sync_array_detect_deadlock( +/*=======================*/ + /* out: TRUE if deadlock detected */ + sync_array_t* arr, /* in: wait array; NOTE! the caller must + own the mutex to array */ + sync_cell_t* start, /* in: cell where recursive search started */ + sync_cell_t* cell, /* in: cell to search */ + ulint depth); /* in: recursion depth */ +#endif /* UNIV_SYNC_DEBUG */ + +/********************************************************************* +Gets the nth cell in array. */ +static +sync_cell_t* +sync_array_get_nth_cell( +/*====================*/ + /* out: cell */ + sync_array_t* arr, /* in: sync array */ + ulint n) /* in: index */ +{ + ut_a(arr); + ut_a(n < arr->n_cells); + + return(arr->array + n); +} + +/********************************************************************** +Reserves the mutex semaphore protecting a sync array. */ +static +void +sync_array_enter( +/*=============*/ + sync_array_t* arr) /* in: sync wait array */ +{ + ulint protection; + + protection = arr->protection; + + if (protection == SYNC_ARRAY_OS_MUTEX) { + os_mutex_enter(arr->os_mutex); + } else if (protection == SYNC_ARRAY_MUTEX) { + mutex_enter(&(arr->mutex)); + } else { + ut_error; + } +} + +/********************************************************************** +Releases the mutex semaphore protecting a sync array. */ +static +void +sync_array_exit( +/*============*/ + sync_array_t* arr) /* in: sync wait array */ +{ + ulint protection; + + protection = arr->protection; + + if (protection == SYNC_ARRAY_OS_MUTEX) { + os_mutex_exit(arr->os_mutex); + } else if (protection == SYNC_ARRAY_MUTEX) { + mutex_exit(&(arr->mutex)); + } else { + ut_error; + } +} + +/*********************************************************************** +Creates a synchronization wait array. It is protected by a mutex +which is automatically reserved when the functions operating on it +are called. */ +UNIV_INTERN +sync_array_t* +sync_array_create( +/*==============*/ + /* out, own: created wait array */ + ulint n_cells, /* in: number of cells in the array + to create */ + ulint protection) /* in: either SYNC_ARRAY_OS_MUTEX or + SYNC_ARRAY_MUTEX: determines the type + of mutex protecting the data structure */ +{ + sync_array_t* arr; + sync_cell_t* cell_array; + sync_cell_t* cell; + ulint i; + + ut_a(n_cells > 0); + + /* Allocate memory for the data structures */ + arr = ut_malloc(sizeof(sync_array_t)); + + cell_array = ut_malloc(sizeof(sync_cell_t) * n_cells); + + arr->n_cells = n_cells; + arr->n_reserved = 0; + arr->array = cell_array; + arr->protection = protection; + arr->sg_count = 0; + arr->res_count = 0; + + /* Then create the mutex to protect the wait array complex */ + if (protection == SYNC_ARRAY_OS_MUTEX) { + arr->os_mutex = os_mutex_create(NULL); + } else if (protection == SYNC_ARRAY_MUTEX) { + mutex_create(&arr->mutex, SYNC_NO_ORDER_CHECK); + } else { + ut_error; + } + + for (i = 0; i < n_cells; i++) { + cell = sync_array_get_nth_cell(arr, i); + cell->wait_object = NULL; + cell->waiting = FALSE; + cell->signal_count = 0; + } + + return(arr); +} + +/********************************************************************** +Frees the resources in a wait array. */ +UNIV_INTERN +void +sync_array_free( +/*============*/ + sync_array_t* arr) /* in, own: sync wait array */ +{ + ulint protection; + + ut_a(arr->n_reserved == 0); + + sync_array_validate(arr); + + protection = arr->protection; + + /* Release the mutex protecting the wait array complex */ + + if (protection == SYNC_ARRAY_OS_MUTEX) { + os_mutex_free(arr->os_mutex); + } else if (protection == SYNC_ARRAY_MUTEX) { + mutex_free(&(arr->mutex)); + } else { + ut_error; + } + + ut_free(arr->array); + ut_free(arr); +} + +/************************************************************************ +Validates the integrity of the wait array. Checks +that the number of reserved cells equals the count variable. */ +UNIV_INTERN +void +sync_array_validate( +/*================*/ + sync_array_t* arr) /* in: sync wait array */ +{ + ulint i; + sync_cell_t* cell; + ulint count = 0; + + sync_array_enter(arr); + + for (i = 0; i < arr->n_cells; i++) { + cell = sync_array_get_nth_cell(arr, i); + if (cell->wait_object != NULL) { + count++; + } + } + + ut_a(count == arr->n_reserved); + + sync_array_exit(arr); +} + +/*********************************************************************** +Returns the event that the thread owning the cell waits for. */ +static +os_event_t +sync_cell_get_event( +/*================*/ + sync_cell_t* cell) /* in: non-empty sync array cell */ +{ + ulint type = cell->request_type; + + if (type == SYNC_MUTEX) { + return(((mutex_t *) cell->wait_object)->event); + } else if (type == RW_LOCK_WAIT_EX) { + return(((rw_lock_t *) cell->wait_object)->wait_ex_event); + } else { /* RW_LOCK_SHARED and RW_LOCK_EX wait on the same event */ + return(((rw_lock_t *) cell->wait_object)->event); + } +} + +/********************************************************************** +Reserves a wait array cell for waiting for an object. +The event of the cell is reset to nonsignalled state. */ +UNIV_INTERN +void +sync_array_reserve_cell( +/*====================*/ + sync_array_t* arr, /* in: wait array */ + void* object, /* in: pointer to the object to wait for */ + ulint type, /* in: lock request type */ + const char* file, /* in: file where requested */ + ulint line, /* in: line where requested */ + ulint* index) /* out: index of the reserved cell */ +{ + sync_cell_t* cell; + os_event_t event; + ulint i; + + ut_a(object); + ut_a(index); + + sync_array_enter(arr); + + arr->res_count++; + + /* Reserve a new cell. */ + for (i = 0; i < arr->n_cells; i++) { + cell = sync_array_get_nth_cell(arr, i); + + if (cell->wait_object == NULL) { + + cell->waiting = FALSE; + cell->wait_object = object; + + if (type == SYNC_MUTEX) { + cell->old_wait_mutex = object; + } else { + cell->old_wait_rw_lock = object; + } + + cell->request_type = type; + + cell->file = file; + cell->line = line; + + arr->n_reserved++; + + *index = i; + + sync_array_exit(arr); + + /* Make sure the event is reset and also store + the value of signal_count at which the event + was reset. */ + event = sync_cell_get_event(cell); + cell->signal_count = os_event_reset(event); + + cell->reservation_time = time(NULL); + + cell->thread = os_thread_get_curr_id(); + + return; + } + } + + ut_error; /* No free cell found */ + + return; +} + +/********************************************************************** +This function should be called when a thread starts to wait on +a wait array cell. In the debug version this function checks +if the wait for a semaphore will result in a deadlock, in which +case prints info and asserts. */ +UNIV_INTERN +void +sync_array_wait_event( +/*==================*/ + sync_array_t* arr, /* in: wait array */ + ulint index) /* in: index of the reserved cell */ +{ + sync_cell_t* cell; + os_event_t event; + + ut_a(arr); + + sync_array_enter(arr); + + cell = sync_array_get_nth_cell(arr, index); + + ut_a(cell->wait_object); + ut_a(!cell->waiting); + ut_ad(os_thread_get_curr_id() == cell->thread); + + event = sync_cell_get_event(cell); + cell->waiting = TRUE; + +#ifdef UNIV_SYNC_DEBUG + + /* We use simple enter to the mutex below, because if + we cannot acquire it at once, mutex_enter would call + recursively sync_array routines, leading to trouble. + rw_lock_debug_mutex freezes the debug lists. */ + + rw_lock_debug_mutex_enter(); + + if (TRUE == sync_array_detect_deadlock(arr, cell, cell, 0)) { + + fputs("########################################\n", stderr); + ut_error; + } + + rw_lock_debug_mutex_exit(); +#endif + sync_array_exit(arr); + + os_event_wait_low(event, cell->signal_count); + + sync_array_free_cell(arr, index); +} + +/********************************************************************** +Reports info of a wait array cell. */ +static +void +sync_array_cell_print( +/*==================*/ + FILE* file, /* in: file where to print */ + sync_cell_t* cell) /* in: sync cell */ +{ + mutex_t* mutex; + rw_lock_t* rwlock; + ulint type; + ulint writer; + + type = cell->request_type; + + fprintf(file, + "--Thread %lu has waited at %s line %lu" + " for %#.5g seconds the semaphore:\n", + (ulong) os_thread_pf(cell->thread), cell->file, + (ulong) cell->line, + difftime(time(NULL), cell->reservation_time)); + + if (type == SYNC_MUTEX) { + /* We use old_wait_mutex in case the cell has already + been freed meanwhile */ + mutex = cell->old_wait_mutex; + + fprintf(file, + "Mutex at %p created file %s line %lu, lock var %lu\n" +#ifdef UNIV_SYNC_DEBUG + "Last time reserved in file %s line %lu, " +#endif /* UNIV_SYNC_DEBUG */ + "waiters flag %lu\n", + (void*) mutex, mutex->cfile_name, (ulong) mutex->cline, + (ulong) mutex->lock_word, +#ifdef UNIV_SYNC_DEBUG + mutex->file_name, (ulong) mutex->line, +#endif /* UNIV_SYNC_DEBUG */ + (ulong) mutex->waiters); + + } else if (type == RW_LOCK_EX + || type == RW_LOCK_WAIT_EX + || type == RW_LOCK_SHARED) { + + fputs(type == RW_LOCK_EX ? "X-lock on" : "S-lock on", file); + + rwlock = cell->old_wait_rw_lock; + + fprintf(file, + " RW-latch at %p created in file %s line %lu\n", + (void*) rwlock, rwlock->cfile_name, + (ulong) rwlock->cline); + writer = rw_lock_get_writer(rwlock); + if (writer != RW_LOCK_NOT_LOCKED) { + fprintf(file, + "a writer (thread id %lu) has" + " reserved it in mode %s", + (ulong) os_thread_pf(rwlock->writer_thread), + writer == RW_LOCK_EX + ? " exclusive\n" + : " wait exclusive\n"); + } + + fprintf(file, + "number of readers %lu, waiters flag %lu, " + "lock_word: %lx\n" + "Last time read locked in file %s line %lu\n" + "Last time write locked in file %s line %lu\n", + (ulong) rw_lock_get_reader_count(rwlock), + (ulong) rwlock->waiters, + rwlock->lock_word, + rwlock->last_s_file_name, + (ulong) rwlock->last_s_line, + rwlock->last_x_file_name, + (ulong) rwlock->last_x_line); + } else { + ut_error; + } + + if (!cell->waiting) { + fputs("wait has ended\n", file); + } +} + +#ifdef UNIV_SYNC_DEBUG +/********************************************************************** +Looks for a cell with the given thread id. */ +static +sync_cell_t* +sync_array_find_thread( +/*===================*/ + /* out: pointer to cell or NULL + if not found */ + sync_array_t* arr, /* in: wait array */ + os_thread_id_t thread) /* in: thread id */ +{ + ulint i; + sync_cell_t* cell; + + for (i = 0; i < arr->n_cells; i++) { + + cell = sync_array_get_nth_cell(arr, i); + + if (cell->wait_object != NULL + && os_thread_eq(cell->thread, thread)) { + + return(cell); /* Found */ + } + } + + return(NULL); /* Not found */ +} + +/********************************************************************** +Recursion step for deadlock detection. */ +static +ibool +sync_array_deadlock_step( +/*=====================*/ + /* out: TRUE if deadlock detected */ + sync_array_t* arr, /* in: wait array; NOTE! the caller must + own the mutex to array */ + sync_cell_t* start, /* in: cell where recursive search + started */ + os_thread_id_t thread, /* in: thread to look at */ + ulint pass, /* in: pass value */ + ulint depth) /* in: recursion depth */ +{ + sync_cell_t* new; + ibool ret; + + depth++; + + if (pass != 0) { + /* If pass != 0, then we do not know which threads are + responsible of releasing the lock, and no deadlock can + be detected. */ + + return(FALSE); + } + + new = sync_array_find_thread(arr, thread); + + if (new == start) { + /* Stop running of other threads */ + + ut_dbg_stop_threads = TRUE; + + /* Deadlock */ + fputs("########################################\n" + "DEADLOCK of threads detected!\n", stderr); + + return(TRUE); + + } else if (new) { + ret = sync_array_detect_deadlock(arr, start, new, depth); + + if (ret) { + return(TRUE); + } + } + return(FALSE); +} + +/********************************************************************** +This function is called only in the debug version. Detects a deadlock +of one or more threads because of waits of semaphores. */ +static +ibool +sync_array_detect_deadlock( +/*=======================*/ + /* out: TRUE if deadlock detected */ + sync_array_t* arr, /* in: wait array; NOTE! the caller must + own the mutex to array */ + sync_cell_t* start, /* in: cell where recursive search started */ + sync_cell_t* cell, /* in: cell to search */ + ulint depth) /* in: recursion depth */ +{ + mutex_t* mutex; + rw_lock_t* lock; + os_thread_id_t thread; + ibool ret; + rw_lock_debug_t*debug; + + ut_a(arr); + ut_a(start); + ut_a(cell); + ut_ad(cell->wait_object); + ut_ad(os_thread_get_curr_id() == start->thread); + ut_ad(depth < 100); + + depth++; + + if (!cell->waiting) { + + return(FALSE); /* No deadlock here */ + } + + if (cell->request_type == SYNC_MUTEX) { + + mutex = cell->wait_object; + + if (mutex_get_lock_word(mutex) != 0) { + + thread = mutex->thread_id; + + /* Note that mutex->thread_id above may be + also OS_THREAD_ID_UNDEFINED, because the + thread which held the mutex maybe has not + yet updated the value, or it has already + released the mutex: in this case no deadlock + can occur, as the wait array cannot contain + a thread with ID_UNDEFINED value. */ + + ret = sync_array_deadlock_step(arr, start, thread, 0, + depth); + if (ret) { + fprintf(stderr, + "Mutex %p owned by thread %lu file %s line %lu\n", + mutex, (ulong) os_thread_pf(mutex->thread_id), + mutex->file_name, (ulong) mutex->line); + sync_array_cell_print(stderr, cell); + + return(TRUE); + } + } + + return(FALSE); /* No deadlock */ + + } else if (cell->request_type == RW_LOCK_EX + || cell->request_type == RW_LOCK_WAIT_EX) { + + lock = cell->wait_object; + + debug = UT_LIST_GET_FIRST(lock->debug_list); + + while (debug != NULL) { + + thread = debug->thread_id; + + if (((debug->lock_type == RW_LOCK_EX) + && !os_thread_eq(thread, cell->thread)) + || ((debug->lock_type == RW_LOCK_WAIT_EX) + && !os_thread_eq(thread, cell->thread)) + || (debug->lock_type == RW_LOCK_SHARED)) { + + /* The (wait) x-lock request can block + infinitely only if someone (can be also cell + thread) is holding s-lock, or someone + (cannot be cell thread) (wait) x-lock, and + he is blocked by start thread */ + + ret = sync_array_deadlock_step( + arr, start, thread, debug->pass, + depth); + if (ret) { +print: + fprintf(stderr, "rw-lock %p ", + (void*) lock); + sync_array_cell_print(stderr, cell); + rw_lock_debug_print(debug); + return(TRUE); + } + } + + debug = UT_LIST_GET_NEXT(list, debug); + } + + return(FALSE); + + } else if (cell->request_type == RW_LOCK_SHARED) { + + lock = cell->wait_object; + debug = UT_LIST_GET_FIRST(lock->debug_list); + + while (debug != NULL) { + + thread = debug->thread_id; + + if ((debug->lock_type == RW_LOCK_EX) + || (debug->lock_type == RW_LOCK_WAIT_EX)) { + + /* The s-lock request can block infinitely + only if someone (can also be cell thread) is + holding (wait) x-lock, and he is blocked by + start thread */ + + ret = sync_array_deadlock_step( + arr, start, thread, debug->pass, + depth); + if (ret) { + goto print; + } + } + + debug = UT_LIST_GET_NEXT(list, debug); + } + + return(FALSE); + + } else { + ut_error; + } + + return(TRUE); /* Execution never reaches this line: for compiler + fooling only */ +} +#endif /* UNIV_SYNC_DEBUG */ + +/********************************************************************** +Determines if we can wake up the thread waiting for a sempahore. */ +static +ibool +sync_arr_cell_can_wake_up( +/*======================*/ + sync_cell_t* cell) /* in: cell to search */ +{ + mutex_t* mutex; + rw_lock_t* lock; + + if (cell->request_type == SYNC_MUTEX) { + + mutex = cell->wait_object; + + if (mutex_get_lock_word(mutex) == 0) { + + return(TRUE); + } + + } else if (cell->request_type == RW_LOCK_EX) { + + lock = cell->wait_object; + + if (lock->lock_word > 0) { + /* Either unlocked or only read locked. */ + + return(TRUE); + } + + } else if (cell->request_type == RW_LOCK_WAIT_EX) { + + lock = cell->wait_object; + + /* lock_word == 0 means all readers have left */ + if (lock->lock_word == 0) { + + return(TRUE); + } + } else if (cell->request_type == RW_LOCK_SHARED) { + lock = cell->wait_object; + + /* lock_word > 0 means no writer or reserved writer */ + if (lock->lock_word > 0) { + + return(TRUE); + } + } + + return(FALSE); +} + +/********************************************************************** +Frees the cell. NOTE! sync_array_wait_event frees the cell +automatically! */ +UNIV_INTERN +void +sync_array_free_cell( +/*=================*/ + sync_array_t* arr, /* in: wait array */ + ulint index) /* in: index of the cell in array */ +{ + sync_cell_t* cell; + + sync_array_enter(arr); + + cell = sync_array_get_nth_cell(arr, index); + + ut_a(cell->wait_object != NULL); + + cell->waiting = FALSE; + cell->wait_object = NULL; + cell->signal_count = 0; + + ut_a(arr->n_reserved > 0); + arr->n_reserved--; + + sync_array_exit(arr); +} + +/************************************************************************** +Increments the signalled count. */ +UNIV_INTERN +void +sync_array_object_signalled( +/*========================*/ + sync_array_t* arr) /* in: wait array */ +{ +#ifdef HAVE_GCC_ATOMIC_BUILTINS + (void) os_atomic_increment(&arr->sg_count, 1); +#else + sync_array_enter(arr); + + arr->sg_count++; + + sync_array_exit(arr); +#endif +} + +/************************************************************************** +If the wakeup algorithm does not work perfectly at semaphore relases, +this function will do the waking (see the comment in mutex_exit). This +function should be called about every 1 second in the server. + +Note that there's a race condition between this thread and mutex_exit +changing the lock_word and calling signal_object, so sometimes this finds +threads to wake up even when nothing has gone wrong. */ +UNIV_INTERN +void +sync_arr_wake_threads_if_sema_free(void) +/*====================================*/ +{ + sync_array_t* arr = sync_primary_wait_array; + sync_cell_t* cell; + ulint count; + ulint i; + os_event_t event; + + sync_array_enter(arr); + + i = 0; + count = 0; + + while (count < arr->n_reserved) { + + cell = sync_array_get_nth_cell(arr, i); + i++; + + if (cell->wait_object == NULL) { + continue; + } + count++; + + if (sync_arr_cell_can_wake_up(cell)) { + + event = sync_cell_get_event(cell); + + os_event_set(event); + } + + } + + sync_array_exit(arr); +} + +/************************************************************************** +Prints warnings of long semaphore waits to stderr. */ +UNIV_INTERN +ibool +sync_array_print_long_waits(void) +/*=============================*/ + /* out: TRUE if fatal semaphore wait threshold + was exceeded */ +{ + sync_cell_t* cell; + ibool old_val; + ibool noticed = FALSE; + ulint i; + ulint fatal_timeout = srv_fatal_semaphore_wait_threshold; + ibool fatal = FALSE; + + for (i = 0; i < sync_primary_wait_array->n_cells; i++) { + + cell = sync_array_get_nth_cell(sync_primary_wait_array, i); + + if (cell->wait_object != NULL && cell->waiting + && difftime(time(NULL), cell->reservation_time) > 240) { + fputs("InnoDB: Warning: a long semaphore wait:\n", + stderr); + sync_array_cell_print(stderr, cell); + noticed = TRUE; + } + + if (cell->wait_object != NULL && cell->waiting + && difftime(time(NULL), cell->reservation_time) + > fatal_timeout) { + fatal = TRUE; + } + } + + if (noticed) { + fprintf(stderr, + "InnoDB: ###### Starts InnoDB Monitor" + " for 30 secs to print diagnostic info:\n"); + old_val = srv_print_innodb_monitor; + + /* If some crucial semaphore is reserved, then also the InnoDB + Monitor can hang, and we do not get diagnostics. Since in + many cases an InnoDB hang is caused by a pwrite() or a pread() + call hanging inside the operating system, let us print right + now the values of pending calls of these. */ + + fprintf(stderr, + "InnoDB: Pending preads %lu, pwrites %lu\n", + (ulong)os_file_n_pending_preads, + (ulong)os_file_n_pending_pwrites); + + srv_print_innodb_monitor = TRUE; + os_event_set(srv_lock_timeout_thread_event); + + os_thread_sleep(30000000); + + srv_print_innodb_monitor = old_val; + fprintf(stderr, + "InnoDB: ###### Diagnostic info printed" + " to the standard error stream\n"); + } + + return(fatal); +} + +/************************************************************************** +Prints info of the wait array. */ +static +void +sync_array_output_info( +/*===================*/ + FILE* file, /* in: file where to print */ + sync_array_t* arr) /* in: wait array; NOTE! caller must own the + mutex */ +{ + sync_cell_t* cell; + ulint count; + ulint i; + + fprintf(file, + "OS WAIT ARRAY INFO: reservation count %ld, signal count %ld\n", + (long) arr->res_count, (long) arr->sg_count); + i = 0; + count = 0; + + while (count < arr->n_reserved) { + + cell = sync_array_get_nth_cell(arr, i); + + if (cell->wait_object != NULL) { + count++; + sync_array_cell_print(file, cell); + } + + i++; + } +} + +/************************************************************************** +Prints info of the wait array. */ +UNIV_INTERN +void +sync_array_print_info( +/*==================*/ + FILE* file, /* in: file where to print */ + sync_array_t* arr) /* in: wait array */ +{ + sync_array_enter(arr); + + sync_array_output_info(file, arr); + + sync_array_exit(arr); +} diff --git a/storage/xtradb/sync/sync0rw.c b/storage/xtradb/sync/sync0rw.c new file mode 100644 index 00000000000..09c732eefc9 --- /dev/null +++ b/storage/xtradb/sync/sync0rw.c @@ -0,0 +1,1035 @@ +/***************************************************************************** + +Copyright (c) 1995, 2009, Innobase Oy. All Rights Reserved. +Copyright (c) 2008, Google Inc. + +Portions of this file contain modifications contributed and copyrighted by +Google, Inc. Those modifications are gratefully acknowledged and are described +briefly in the InnoDB documentation. The contributions by Google are +incorporated with their permission, and subject to the conditions contained in +the file COPYING.Google. + +This program is free software; you can redistribute it and/or modify it under +the terms of the GNU General Public License as published by the Free Software +Foundation; version 2 of the License. + +This program is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + +You 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 + +*****************************************************************************/ + +/****************************************************** +The read-write lock (for thread synchronization) + +Created 9/11/1995 Heikki Tuuri +*******************************************************/ + +#include "sync0rw.h" +#ifdef UNIV_NONINL +#include "sync0rw.ic" +#endif + +#include "os0thread.h" +#include "mem0mem.h" +#include "srv0srv.h" + +/* + IMPLEMENTATION OF THE RW_LOCK + ============================= +The status of a rw_lock is held in lock_word. The initial value of lock_word is +X_LOCK_DECR. lock_word is decremented by 1 for each s-lock and by X_LOCK_DECR +for each x-lock. This describes the lock state for each value of lock_word: + +lock_word == X_LOCK_DECR: Unlocked. +0 < lock_word < X_LOCK_DECR: Read locked, no waiting writers. + (X_LOCK_DECR - lock_word) is the + number of readers that hold the lock. +lock_word == 0: Write locked +-X_LOCK_DECR < lock_word < 0: Read locked, with a waiting writer. + (-lock_word) is the number of readers + that hold the lock. +lock_word <= -X_LOCK_DECR: Recursively write locked. lock_word has been + decremented by X_LOCK_DECR once for each lock, + so the number of locks is: + ((-lock_word) / X_LOCK_DECR) + 1 +When lock_word <= -X_LOCK_DECR, we also know that lock_word % X_LOCK_DECR == 0: +other values of lock_word are invalid. + +The lock_word is always read and updated atomically and consistently, so that +it always represents the state of the lock, and the state of the lock changes +with a single atomic operation. This lock_word holds all of the information +that a thread needs in order to determine if it is eligible to gain the lock +or if it must spin or sleep. The one exception to this is that writer_thread +must be verified before recursive write locks: to solve this scenario, we make +writer_thread readable by all threads, but only writeable by the x-lock holder. + +The other members of the lock obey the following rules to remain consistent: + +recursive: This and the writer_thread field together control the + behaviour of recursive x-locking. + lock->recursive must be FALSE in following states: + 1) The writer_thread contains garbage i.e.: the + lock has just been initialized. + 2) The lock is not x-held and there is no + x-waiter waiting on WAIT_EX event. + 3) The lock is x-held or there is an x-waiter + waiting on WAIT_EX event but the 'pass' value + is non-zero. + lock->recursive is TRUE iff: + 1) The lock is x-held or there is an x-waiter + waiting on WAIT_EX event and the 'pass' value + is zero. + This flag must be set after the writer_thread field + has been updated with a memory ordering barrier. + It is unset before the lock_word has been incremented. +writer_thread: Is used only in recursive x-locking. Can only be safely + read iff lock->recursive flag is TRUE. + This field is uninitialized at lock creation time and + is updated atomically when x-lock is acquired or when + move_ownership is called. A thread is only allowed to + set the value of this field to it's thread_id i.e.: a + thread cannot set writer_thread to some other thread's + id. +waiters: May be set to 1 anytime, but to avoid unnecessary wake-up + signals, it should only be set to 1 when there are threads + waiting on event. Must be 1 when a writer starts waiting to + ensure the current x-locking thread sends a wake-up signal + during unlock. May only be reset to 0 immediately before a + a wake-up signal is sent to event. On most platforms, a + memory barrier is required after waiters is set, and before + verifying lock_word is still held, to ensure some unlocker + really does see the flags new value. +event: Threads wait on event for read or writer lock when another + thread has an x-lock or an x-lock reservation (wait_ex). A + thread may only wait on event after performing the following + actions in order: + (1) Record the counter value of event (with os_event_reset). + (2) Set waiters to 1. + (3) Verify lock_word <= 0. + (1) must come before (2) to ensure signal is not missed. + (2) must come before (3) to ensure a signal is sent. + These restrictions force the above ordering. + Immediately before sending the wake-up signal, we should: + (1) Verify lock_word == X_LOCK_DECR (unlocked) + (2) Reset waiters to 0. +wait_ex_event: A thread may only wait on the wait_ex_event after it has + performed the following actions in order: + (1) Decrement lock_word by X_LOCK_DECR. + (2) Record counter value of wait_ex_event (os_event_reset, + called from sync_array_reserve_cell). + (3) Verify that lock_word < 0. + (1) must come first to ensures no other threads become reader + or next writer, and notifies unlocker that signal must be sent. + (2) must come before (3) to ensure the signal is not missed. + These restrictions force the above ordering. + Immediately before sending the wake-up signal, we should: + Verify lock_word == 0 (waiting thread holds x_lock) +*/ + + +/* number of spin waits on rw-latches, +resulted during shared (read) locks */ +UNIV_INTERN ib_int64_t rw_s_spin_wait_count = 0; +UNIV_INTERN ib_int64_t rw_s_spin_round_count = 0; + +/* number of OS waits on rw-latches, +resulted during shared (read) locks */ +UNIV_INTERN ib_int64_t rw_s_os_wait_count = 0; + +/* number of unlocks (that unlock shared locks), +set only when UNIV_SYNC_PERF_STAT is defined */ +UNIV_INTERN ib_int64_t rw_s_exit_count = 0; + +/* number of spin waits on rw-latches, +resulted during exclusive (write) locks */ +UNIV_INTERN ib_int64_t rw_x_spin_wait_count = 0; +UNIV_INTERN ib_int64_t rw_x_spin_round_count = 0; + +/* number of OS waits on rw-latches, +resulted during exclusive (write) locks */ +UNIV_INTERN ib_int64_t rw_x_os_wait_count = 0; + +/* number of unlocks (that unlock exclusive locks), +set only when UNIV_SYNC_PERF_STAT is defined */ +UNIV_INTERN ib_int64_t rw_x_exit_count = 0; + +/* The global list of rw-locks */ +UNIV_INTERN rw_lock_list_t rw_lock_list; +UNIV_INTERN mutex_t rw_lock_list_mutex; + +#ifdef UNIV_SYNC_DEBUG +/* The global mutex which protects debug info lists of all rw-locks. +To modify the debug info list of an rw-lock, this mutex has to be +acquired in addition to the mutex protecting the lock. */ + +UNIV_INTERN mutex_t rw_lock_debug_mutex; +/* If deadlock detection does not get immediately the mutex, +it may wait for this event */ +UNIV_INTERN os_event_t rw_lock_debug_event; +/* This is set to TRUE, if there may be waiters for the event */ +UNIV_INTERN ibool rw_lock_debug_waiters; + +/********************************************************************** +Creates a debug info struct. */ +static +rw_lock_debug_t* +rw_lock_debug_create(void); +/*======================*/ +/********************************************************************** +Frees a debug info struct. */ +static +void +rw_lock_debug_free( +/*===============*/ + rw_lock_debug_t* info); + +/********************************************************************** +Creates a debug info struct. */ +static +rw_lock_debug_t* +rw_lock_debug_create(void) +/*======================*/ +{ + return((rw_lock_debug_t*) mem_alloc(sizeof(rw_lock_debug_t))); +} + +/********************************************************************** +Frees a debug info struct. */ +static +void +rw_lock_debug_free( +/*===============*/ + rw_lock_debug_t* info) +{ + mem_free(info); +} +#endif /* UNIV_SYNC_DEBUG */ + +/********************************************************************** +Creates, or rather, initializes an rw-lock object in a specified memory +location (which must be appropriately aligned). The rw-lock is initialized +to the non-locked state. Explicit freeing of the rw-lock with rw_lock_free +is necessary only if the memory block containing it is freed. */ +UNIV_INTERN +void +rw_lock_create_func( +/*================*/ + rw_lock_t* lock, /* in: pointer to memory */ +#ifdef UNIV_DEBUG +# ifdef UNIV_SYNC_DEBUG + ulint level, /* in: level */ +# endif /* UNIV_SYNC_DEBUG */ + const char* cmutex_name, /* in: mutex name */ +#endif /* UNIV_DEBUG */ + const char* cfile_name, /* in: file name where created */ + ulint cline) /* in: file line where created */ +{ + /* If this is the very first time a synchronization object is + created, then the following call initializes the sync system. */ + +#ifndef INNODB_RW_LOCKS_USE_ATOMICS + mutex_create(rw_lock_get_mutex(lock), SYNC_NO_ORDER_CHECK); + + lock->mutex.cfile_name = cfile_name; + lock->mutex.cline = cline; + +# if defined UNIV_DEBUG && !defined UNIV_HOTBACKUP + lock->mutex.cmutex_name = cmutex_name; + lock->mutex.mutex_type = 1; +# endif /* UNIV_DEBUG && !UNIV_HOTBACKUP */ + +#else /* INNODB_RW_LOCKS_USE_ATOMICS */ +# ifdef UNIV_DEBUG + UT_NOT_USED(cmutex_name); +# endif +#endif /* INNODB_RW_LOCKS_USE_ATOMICS */ + + lock->lock_word = X_LOCK_DECR; + lock->waiters = 0; + + /* We set this value to signify that lock->writer_thread + contains garbage at initialization and cannot be used for + recursive x-locking. */ + lock->recursive = FALSE; + +#ifdef UNIV_SYNC_DEBUG + UT_LIST_INIT(lock->debug_list); + + lock->level = level; +#endif /* UNIV_SYNC_DEBUG */ + + lock->magic_n = RW_LOCK_MAGIC_N; + + lock->cfile_name = cfile_name; + lock->cline = (unsigned int) cline; + + lock->count_os_wait = 0; + lock->last_s_file_name = "not yet reserved"; + lock->last_x_file_name = "not yet reserved"; + lock->last_s_line = 0; + lock->last_x_line = 0; + lock->event = os_event_create(NULL); + lock->wait_ex_event = os_event_create(NULL); + + mutex_enter(&rw_lock_list_mutex); + + if (UT_LIST_GET_LEN(rw_lock_list) > 0) { + ut_a(UT_LIST_GET_FIRST(rw_lock_list)->magic_n + == RW_LOCK_MAGIC_N); + } + + UT_LIST_ADD_FIRST(list, rw_lock_list, lock); + + mutex_exit(&rw_lock_list_mutex); +} + +/********************************************************************** +Calling this function is obligatory only if the memory buffer containing +the rw-lock is freed. Removes an rw-lock object from the global list. The +rw-lock is checked to be in the non-locked state. */ +UNIV_INTERN +void +rw_lock_free( +/*=========*/ + rw_lock_t* lock) /* in: rw-lock */ +{ + ut_ad(rw_lock_validate(lock)); + ut_a(lock->lock_word == X_LOCK_DECR); + + lock->magic_n = 0; + +#ifndef INNODB_RW_LOCKS_USE_ATOMICS + mutex_free(rw_lock_get_mutex(lock)); +#endif /* INNODB_RW_LOCKS_USE_ATOMICS */ + + mutex_enter(&rw_lock_list_mutex); + os_event_free(lock->event); + + os_event_free(lock->wait_ex_event); + + if (UT_LIST_GET_PREV(list, lock)) { + ut_a(UT_LIST_GET_PREV(list, lock)->magic_n == RW_LOCK_MAGIC_N); + } + if (UT_LIST_GET_NEXT(list, lock)) { + ut_a(UT_LIST_GET_NEXT(list, lock)->magic_n == RW_LOCK_MAGIC_N); + } + + UT_LIST_REMOVE(list, rw_lock_list, lock); + + mutex_exit(&rw_lock_list_mutex); +} + +#ifdef UNIV_DEBUG +/********************************************************************** +Checks that the rw-lock has been initialized and that there are no +simultaneous shared and exclusive locks. */ +UNIV_INTERN +ibool +rw_lock_validate( +/*=============*/ + rw_lock_t* lock) +{ + ut_a(lock); + + ulint waiters = rw_lock_get_waiters(lock); + lint lock_word = lock->lock_word; + + ut_a(lock->magic_n == RW_LOCK_MAGIC_N); + ut_a(waiters == 0 || waiters == 1); + ut_a(lock_word > -X_LOCK_DECR ||(-lock_word) % X_LOCK_DECR == 0); + + return(TRUE); +} +#endif /* UNIV_DEBUG */ + +/********************************************************************** +Lock an rw-lock in shared mode for the current thread. If the rw-lock is +locked in exclusive mode, or there is an exclusive lock request waiting, +the function spins a preset time (controlled by SYNC_SPIN_ROUNDS), waiting +for the lock, before suspending the thread. */ +UNIV_INTERN +void +rw_lock_s_lock_spin( +/*================*/ + rw_lock_t* lock, /* in: pointer to rw-lock */ + ulint pass, /* in: pass value; != 0, if the lock + will be passed to another thread to unlock */ + const char* file_name, /* in: file name where lock requested */ + ulint line) /* in: line where requested */ +{ + ulint index; /* index of the reserved wait cell */ + ulint i = 0; /* spin round count */ + + ut_ad(rw_lock_validate(lock)); + + rw_s_spin_wait_count++; /* Count calls to this function */ +lock_loop: + + /* Spin waiting for the writer field to become free */ + while (i < SYNC_SPIN_ROUNDS && lock->lock_word <= 0) { + if (srv_spin_wait_delay) { + ut_delay(ut_rnd_interval(0, srv_spin_wait_delay)); + } + + i++; + } + + if (i == SYNC_SPIN_ROUNDS) { + os_thread_yield(); + } + + if (srv_print_latch_waits) { + fprintf(stderr, + "Thread %lu spin wait rw-s-lock at %p" + " cfile %s cline %lu rnds %lu\n", + (ulong) os_thread_pf(os_thread_get_curr_id()), + (void*) lock, + lock->cfile_name, (ulong) lock->cline, (ulong) i); + } + + /* We try once again to obtain the lock */ + if (TRUE == rw_lock_s_lock_low(lock, pass, file_name, line)) { + rw_s_spin_round_count += i; + + return; /* Success */ + } else { + + if (i < SYNC_SPIN_ROUNDS) { + goto lock_loop; + } + + rw_s_spin_round_count += i; + + sync_array_reserve_cell(sync_primary_wait_array, + lock, RW_LOCK_SHARED, + file_name, line, + &index); + + /* Set waiters before checking lock_word to ensure wake-up + signal is sent. This may lead to some unnecessary signals. */ + rw_lock_set_waiter_flag(lock); + + if (TRUE == rw_lock_s_lock_low(lock, pass, file_name, line)) { + sync_array_free_cell(sync_primary_wait_array, index); + return; /* Success */ + } + + if (srv_print_latch_waits) { + fprintf(stderr, + "Thread %lu OS wait rw-s-lock at %p" + " cfile %s cline %lu\n", + os_thread_pf(os_thread_get_curr_id()), + (void*) lock, lock->cfile_name, + (ulong) lock->cline); + } + + /* these stats may not be accurate */ + lock->count_os_wait++; + rw_s_os_wait_count++; + + sync_array_wait_event(sync_primary_wait_array, index); + + i = 0; + goto lock_loop; + } +} + +/********************************************************************** +This function is used in the insert buffer to move the ownership of an +x-latch on a buffer frame to the current thread. The x-latch was set by +the buffer read operation and it protected the buffer frame while the +read was done. The ownership is moved because we want that the current +thread is able to acquire a second x-latch which is stored in an mtr. +This, in turn, is needed to pass the debug checks of index page +operations. */ +UNIV_INTERN +void +rw_lock_x_lock_move_ownership( +/*==========================*/ + rw_lock_t* lock) /* in: lock which was x-locked in the + buffer read */ +{ + ut_ad(rw_lock_is_locked(lock, RW_LOCK_EX)); + + rw_lock_set_writer_id_and_recursion_flag(lock, TRUE); +} + +/********************************************************************** +Function for the next writer to call. Waits for readers to exit. +The caller must have already decremented lock_word by X_LOCK_DECR.*/ +UNIV_INLINE +void +rw_lock_x_lock_wait( +/*================*/ + rw_lock_t* lock, /* in: pointer to rw-lock */ +#ifdef UNIV_SYNC_DEBUG + ulint pass, /* in: pass value; != 0, if the lock will + be passed to another thread to unlock */ +#endif + const char* file_name,/* in: file name where lock requested */ + ulint line) /* in: line where requested */ +{ + ulint index; + ulint i = 0; + + ut_ad(lock->lock_word <= 0); + + while (lock->lock_word < 0) { + if (srv_spin_wait_delay) { + ut_delay(ut_rnd_interval(0, srv_spin_wait_delay)); + } + if(i < SYNC_SPIN_ROUNDS) { + i++; + continue; + } + + /* If there is still a reader, then go to sleep.*/ + rw_x_spin_round_count += i; + i = 0; + sync_array_reserve_cell(sync_primary_wait_array, + lock, + RW_LOCK_WAIT_EX, + file_name, line, + &index); + /* Check lock_word to ensure wake-up isn't missed.*/ + if(lock->lock_word < 0) { + + /* these stats may not be accurate */ + lock->count_os_wait++; + rw_x_os_wait_count++; + + /* Add debug info as it is needed to detect possible + deadlock. We must add info for WAIT_EX thread for + deadlock detection to work properly. */ +#ifdef UNIV_SYNC_DEBUG + rw_lock_add_debug_info(lock, pass, RW_LOCK_WAIT_EX, + file_name, line); +#endif + + sync_array_wait_event(sync_primary_wait_array, + index); +#ifdef UNIV_SYNC_DEBUG + rw_lock_remove_debug_info(lock, pass, + RW_LOCK_WAIT_EX); +#endif + /* It is possible to wake when lock_word < 0. + We must pass the while-loop check to proceed.*/ + } else { + sync_array_free_cell(sync_primary_wait_array, + index); + } + } + rw_x_spin_round_count += i; +} + +/********************************************************************** +Low-level function for acquiring an exclusive lock. */ +UNIV_INLINE +ibool +rw_lock_x_lock_low( +/*===============*/ + /* out: RW_LOCK_NOT_LOCKED if did + not succeed, RW_LOCK_EX if success. */ + rw_lock_t* lock, /* in: pointer to rw-lock */ + ulint pass, /* in: pass value; != 0, if the lock will + be passed to another thread to unlock */ + const char* file_name,/* in: file name where lock requested */ + ulint line) /* in: line where requested */ +{ + os_thread_id_t curr_thread = os_thread_get_curr_id(); + + if (rw_lock_lock_word_decr(lock, X_LOCK_DECR)) { + + /* lock->recursive also tells us if the writer_thread + field is stale or active. As we are going to write + our own thread id in that field it must be that the + current writer_thread value is not active. */ + ut_a(!lock->recursive); + + /* Decrement occurred: we are writer or next-writer. */ + rw_lock_set_writer_id_and_recursion_flag(lock, + pass ? FALSE : TRUE); + + rw_lock_x_lock_wait(lock, +#ifdef UNIV_SYNC_DEBUG + pass, +#endif + file_name, line); + + } else { + /* Decrement failed: relock or failed lock */ + if (!pass && lock->recursive + && os_thread_eq(lock->writer_thread, curr_thread)) { + /* Relock */ + lock->lock_word -= X_LOCK_DECR; + } else { + /* Another thread locked before us */ + return(FALSE); + } + } +#ifdef UNIV_SYNC_DEBUG + rw_lock_add_debug_info(lock, pass, RW_LOCK_EX, + file_name, line); +#endif + lock->last_x_file_name = file_name; + lock->last_x_line = (unsigned int) line; + + return(TRUE); +} + +/********************************************************************** +NOTE! Use the corresponding macro, not directly this function! Lock an +rw-lock in exclusive mode for the current thread. If the rw-lock is locked +in shared or exclusive mode, or there is an exclusive lock request waiting, +the function spins a preset time (controlled by SYNC_SPIN_ROUNDS), waiting +for the lock before suspending the thread. If the same thread has an x-lock +on the rw-lock, locking succeed, with the following exception: if pass != 0, +only a single x-lock may be taken on the lock. NOTE: If the same thread has +an s-lock, locking does not succeed! */ +UNIV_INTERN +void +rw_lock_x_lock_func( +/*================*/ + rw_lock_t* lock, /* in: pointer to rw-lock */ + ulint pass, /* in: pass value; != 0, if the lock will + be passed to another thread to unlock */ + const char* file_name,/* in: file name where lock requested */ + ulint line) /* in: line where requested */ +{ + ulint index; /* index of the reserved wait cell */ + ulint i; /* spin round count */ + ibool spinning = FALSE; + + ut_ad(rw_lock_validate(lock)); + + i = 0; + +lock_loop: + + if (rw_lock_x_lock_low(lock, pass, file_name, line)) { + rw_x_spin_round_count += i; + + return; /* Locking succeeded */ + + } else { + + if (!spinning) { + spinning = TRUE; + rw_x_spin_wait_count++; + } + + /* Spin waiting for the lock_word to become free */ + while (i < SYNC_SPIN_ROUNDS + && lock->lock_word <= 0) { + if (srv_spin_wait_delay) { + ut_delay(ut_rnd_interval(0, + srv_spin_wait_delay)); + } + + i++; + } + if (i == SYNC_SPIN_ROUNDS) { + os_thread_yield(); + } else { + goto lock_loop; + } + } + + rw_x_spin_round_count += i; + + if (srv_print_latch_waits) { + fprintf(stderr, + "Thread %lu spin wait rw-x-lock at %p" + " cfile %s cline %lu rnds %lu\n", + os_thread_pf(os_thread_get_curr_id()), (void*) lock, + lock->cfile_name, (ulong) lock->cline, (ulong) i); + } + + sync_array_reserve_cell(sync_primary_wait_array, + lock, + RW_LOCK_EX, + file_name, line, + &index); + + /* Waiters must be set before checking lock_word, to ensure signal + is sent. This could lead to a few unnecessary wake-up signals. */ + rw_lock_set_waiter_flag(lock); + + if (rw_lock_x_lock_low(lock, pass, file_name, line)) { + sync_array_free_cell(sync_primary_wait_array, index); + return; /* Locking succeeded */ + } + + if (srv_print_latch_waits) { + fprintf(stderr, + "Thread %lu OS wait for rw-x-lock at %p" + " cfile %s cline %lu\n", + os_thread_pf(os_thread_get_curr_id()), (void*) lock, + lock->cfile_name, (ulong) lock->cline); + } + + /* these stats may not be accurate */ + lock->count_os_wait++; + rw_x_os_wait_count++; + + sync_array_wait_event(sync_primary_wait_array, index); + + i = 0; + goto lock_loop; +} + +#ifdef UNIV_SYNC_DEBUG +/********************************************************************** +Acquires the debug mutex. We cannot use the mutex defined in sync0sync, +because the debug mutex is also acquired in sync0arr while holding the OS +mutex protecting the sync array, and the ordinary mutex_enter might +recursively call routines in sync0arr, leading to a deadlock on the OS +mutex. */ +UNIV_INTERN +void +rw_lock_debug_mutex_enter(void) +/*==========================*/ +{ +loop: + if (0 == mutex_enter_nowait(&rw_lock_debug_mutex)) { + return; + } + + os_event_reset(rw_lock_debug_event); + + rw_lock_debug_waiters = TRUE; + + if (0 == mutex_enter_nowait(&rw_lock_debug_mutex)) { + return; + } + + os_event_wait(rw_lock_debug_event); + + goto loop; +} + +/********************************************************************** +Releases the debug mutex. */ +UNIV_INTERN +void +rw_lock_debug_mutex_exit(void) +/*==========================*/ +{ + mutex_exit(&rw_lock_debug_mutex); + + if (rw_lock_debug_waiters) { + rw_lock_debug_waiters = FALSE; + os_event_set(rw_lock_debug_event); + } +} + +/********************************************************************** +Inserts the debug information for an rw-lock. */ +UNIV_INTERN +void +rw_lock_add_debug_info( +/*===================*/ + rw_lock_t* lock, /* in: rw-lock */ + ulint pass, /* in: pass value */ + ulint lock_type, /* in: lock type */ + const char* file_name, /* in: file where requested */ + ulint line) /* in: line where requested */ +{ + rw_lock_debug_t* info; + + ut_ad(lock); + ut_ad(file_name); + + info = rw_lock_debug_create(); + + rw_lock_debug_mutex_enter(); + + info->file_name = file_name; + info->line = line; + info->lock_type = lock_type; + info->thread_id = os_thread_get_curr_id(); + info->pass = pass; + + UT_LIST_ADD_FIRST(list, lock->debug_list, info); + + rw_lock_debug_mutex_exit(); + + if ((pass == 0) && (lock_type != RW_LOCK_WAIT_EX)) { + sync_thread_add_level(lock, lock->level); + } +} + +/********************************************************************** +Removes a debug information struct for an rw-lock. */ +UNIV_INTERN +void +rw_lock_remove_debug_info( +/*======================*/ + rw_lock_t* lock, /* in: rw-lock */ + ulint pass, /* in: pass value */ + ulint lock_type) /* in: lock type */ +{ + rw_lock_debug_t* info; + + ut_ad(lock); + + if ((pass == 0) && (lock_type != RW_LOCK_WAIT_EX)) { + sync_thread_reset_level(lock); + } + + rw_lock_debug_mutex_enter(); + + info = UT_LIST_GET_FIRST(lock->debug_list); + + while (info != NULL) { + if ((pass == info->pass) + && ((pass != 0) + || os_thread_eq(info->thread_id, + os_thread_get_curr_id())) + && (info->lock_type == lock_type)) { + + /* Found! */ + UT_LIST_REMOVE(list, lock->debug_list, info); + rw_lock_debug_mutex_exit(); + + rw_lock_debug_free(info); + + return; + } + + info = UT_LIST_GET_NEXT(list, info); + } + + ut_error; +} +#endif /* UNIV_SYNC_DEBUG */ + +#ifdef UNIV_SYNC_DEBUG +/********************************************************************** +Checks if the thread has locked the rw-lock in the specified mode, with +the pass value == 0. */ +UNIV_INTERN +ibool +rw_lock_own( +/*========*/ + /* out: TRUE if locked */ + rw_lock_t* lock, /* in: rw-lock */ + ulint lock_type) /* in: lock type: RW_LOCK_SHARED, + RW_LOCK_EX */ +{ + rw_lock_debug_t* info; + + ut_ad(lock); + ut_ad(rw_lock_validate(lock)); + + rw_lock_debug_mutex_enter(); + + info = UT_LIST_GET_FIRST(lock->debug_list); + + while (info != NULL) { + + if (os_thread_eq(info->thread_id, os_thread_get_curr_id()) + && (info->pass == 0) + && (info->lock_type == lock_type)) { + + rw_lock_debug_mutex_exit(); + /* Found! */ + + return(TRUE); + } + + info = UT_LIST_GET_NEXT(list, info); + } + rw_lock_debug_mutex_exit(); + + return(FALSE); +} +#endif /* UNIV_SYNC_DEBUG */ + +/********************************************************************** +Checks if somebody has locked the rw-lock in the specified mode. */ +UNIV_INTERN +ibool +rw_lock_is_locked( +/*==============*/ + /* out: TRUE if locked */ + rw_lock_t* lock, /* in: rw-lock */ + ulint lock_type) /* in: lock type: RW_LOCK_SHARED, + RW_LOCK_EX */ +{ + ibool ret = FALSE; + + ut_ad(lock); + ut_ad(rw_lock_validate(lock)); + + if (lock_type == RW_LOCK_SHARED) { + if (rw_lock_get_reader_count(lock) > 0) { + ret = TRUE; + } + } else if (lock_type == RW_LOCK_EX) { + if (rw_lock_get_writer(lock) == RW_LOCK_EX) { + ret = TRUE; + } + } else { + ut_error; + } + + return(ret); +} + +#ifdef UNIV_SYNC_DEBUG +/******************************************************************* +Prints debug info of currently locked rw-locks. */ +UNIV_INTERN +void +rw_lock_list_print_info( +/*====================*/ + FILE* file) /* in: file where to print */ +{ + rw_lock_t* lock; + ulint count = 0; + rw_lock_debug_t* info; + + mutex_enter(&rw_lock_list_mutex); + + fputs("-------------\n" + "RW-LATCH INFO\n" + "-------------\n", file); + + lock = UT_LIST_GET_FIRST(rw_lock_list); + + while (lock != NULL) { + + count++; + +#ifndef INNODB_RW_LOCKS_USE_ATOMICS + mutex_enter(&(lock->mutex)); +#endif + if (lock->lock_word != X_LOCK_DECR) { + + fprintf(file, "RW-LOCK: %p ", (void*) lock); + + if (rw_lock_get_waiters(lock)) { + fputs(" Waiters for the lock exist\n", file); + } else { + putc('\n', file); + } + + info = UT_LIST_GET_FIRST(lock->debug_list); + while (info != NULL) { + rw_lock_debug_print(info); + info = UT_LIST_GET_NEXT(list, info); + } + } +#ifndef INNODB_RW_LOCKS_USE_ATOMICS + mutex_exit(&(lock->mutex)); +#endif + + lock = UT_LIST_GET_NEXT(list, lock); + } + + fprintf(file, "Total number of rw-locks %ld\n", count); + mutex_exit(&rw_lock_list_mutex); +} + +/******************************************************************* +Prints debug info of an rw-lock. */ +UNIV_INTERN +void +rw_lock_print( +/*==========*/ + rw_lock_t* lock) /* in: rw-lock */ +{ + rw_lock_debug_t* info; + + fprintf(stderr, + "-------------\n" + "RW-LATCH INFO\n" + "RW-LATCH: %p ", (void*) lock); + +#ifndef INNODB_RW_LOCKS_USE_ATOMICS + mutex_enter(&(lock->mutex)); +#endif + if (lock->lock_word != X_LOCK_DECR) { + + if (rw_lock_get_waiters(lock)) { + fputs(" Waiters for the lock exist\n", stderr); + } else { + putc('\n', stderr); + } + + info = UT_LIST_GET_FIRST(lock->debug_list); + while (info != NULL) { + rw_lock_debug_print(info); + info = UT_LIST_GET_NEXT(list, info); + } + } +#ifndef INNODB_RW_LOCKS_USE_ATOMICS + mutex_exit(&(lock->mutex)); +#endif +} + +/************************************************************************* +Prints info of a debug struct. */ +UNIV_INTERN +void +rw_lock_debug_print( +/*================*/ + rw_lock_debug_t* info) /* in: debug struct */ +{ + ulint rwt; + + rwt = info->lock_type; + + fprintf(stderr, "Locked: thread %ld file %s line %ld ", + (ulong) os_thread_pf(info->thread_id), info->file_name, + (ulong) info->line); + if (rwt == RW_LOCK_SHARED) { + fputs("S-LOCK", stderr); + } else if (rwt == RW_LOCK_EX) { + fputs("X-LOCK", stderr); + } else if (rwt == RW_LOCK_WAIT_EX) { + fputs("WAIT X-LOCK", stderr); + } else { + ut_error; + } + if (info->pass != 0) { + fprintf(stderr, " pass value %lu", (ulong) info->pass); + } + putc('\n', stderr); +} + +/******************************************************************* +Returns the number of currently locked rw-locks. Works only in the debug +version. */ +UNIV_INTERN +ulint +rw_lock_n_locked(void) +/*==================*/ +{ + rw_lock_t* lock; + ulint count = 0; + + mutex_enter(&rw_lock_list_mutex); + + lock = UT_LIST_GET_FIRST(rw_lock_list); + + while (lock != NULL) { + + if (lock->lock_word != X_LOCK_DECR) { + count++; + } + + lock = UT_LIST_GET_NEXT(list, lock); + } + + mutex_exit(&rw_lock_list_mutex); + + return(count); +} +#endif /* UNIV_SYNC_DEBUG */ diff --git a/storage/xtradb/sync/sync0sync.c b/storage/xtradb/sync/sync0sync.c new file mode 100644 index 00000000000..1e7f6f95580 --- /dev/null +++ b/storage/xtradb/sync/sync0sync.c @@ -0,0 +1,1417 @@ +/***************************************************************************** + +Copyright (c) 1995, 2009, Innobase Oy. All Rights Reserved. +Copyright (c) 2008, Google Inc. + +Portions of this file contain modifications contributed and copyrighted by +Google, Inc. Those modifications are gratefully acknowledged and are described +briefly in the InnoDB documentation. The contributions by Google are +incorporated with their permission, and subject to the conditions contained in +the file COPYING.Google. + +This program is free software; you can redistribute it and/or modify it under +the terms of the GNU General Public License as published by the Free Software +Foundation; version 2 of the License. + +This program is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + +You 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 + +*****************************************************************************/ + +/****************************************************** +Mutex, the basic synchronization primitive + +Created 9/5/1995 Heikki Tuuri +*******************************************************/ + +#include "sync0sync.h" +#ifdef UNIV_NONINL +#include "sync0sync.ic" +#endif + +#include "sync0rw.h" +#include "buf0buf.h" +#include "srv0srv.h" +#include "buf0types.h" + +/* + REASONS FOR IMPLEMENTING THE SPIN LOCK MUTEX + ============================================ + +Semaphore operations in operating systems are slow: Solaris on a 1993 Sparc +takes 3 microseconds (us) for a lock-unlock pair and Windows NT on a 1995 +Pentium takes 20 microseconds for a lock-unlock pair. Therefore, we have to +implement our own efficient spin lock mutex. Future operating systems may +provide efficient spin locks, but we cannot count on that. + +Another reason for implementing a spin lock is that on multiprocessor systems +it can be more efficient for a processor to run a loop waiting for the +semaphore to be released than to switch to a different thread. A thread switch +takes 25 us on both platforms mentioned above. See Gray and Reuter's book +Transaction processing for background. + +How long should the spin loop last before suspending the thread? On a +uniprocessor, spinning does not help at all, because if the thread owning the +mutex is not executing, it cannot be released. Spinning actually wastes +resources. + +On a multiprocessor, we do not know if the thread owning the mutex is +executing or not. Thus it would make sense to spin as long as the operation +guarded by the mutex would typically last assuming that the thread is +executing. If the mutex is not released by that time, we may assume that the +thread owning the mutex is not executing and suspend the waiting thread. + +A typical operation (where no i/o involved) guarded by a mutex or a read-write +lock may last 1 - 20 us on the current Pentium platform. The longest +operations are the binary searches on an index node. + +We conclude that the best choice is to set the spin time at 20 us. Then the +system should work well on a multiprocessor. On a uniprocessor we have to +make sure that thread swithches due to mutex collisions are not frequent, +i.e., they do not happen every 100 us or so, because that wastes too much +resources. If the thread switches are not frequent, the 20 us wasted in spin +loop is not too much. + +Empirical studies on the effect of spin time should be done for different +platforms. + + + IMPLEMENTATION OF THE MUTEX + =========================== + +For background, see Curt Schimmel's book on Unix implementation on modern +architectures. The key points in the implementation are atomicity and +serialization of memory accesses. The test-and-set instruction (XCHG in +Pentium) must be atomic. As new processors may have weak memory models, also +serialization of memory references may be necessary. The successor of Pentium, +P6, has at least one mode where the memory model is weak. As far as we know, +in Pentium all memory accesses are serialized in the program order and we do +not have to worry about the memory model. On other processors there are +special machine instructions called a fence, memory barrier, or storage +barrier (STBAR in Sparc), which can be used to serialize the memory accesses +to happen in program order relative to the fence instruction. + +Leslie Lamport has devised a "bakery algorithm" to implement a mutex without +the atomic test-and-set, but his algorithm should be modified for weak memory +models. We do not use Lamport's algorithm, because we guess it is slower than +the atomic test-and-set. + +Our mutex implementation works as follows: After that we perform the atomic +test-and-set instruction on the memory word. If the test returns zero, we +know we got the lock first. If the test returns not zero, some other thread +was quicker and got the lock: then we spin in a loop reading the memory word, +waiting it to become zero. It is wise to just read the word in the loop, not +perform numerous test-and-set instructions, because they generate memory +traffic between the cache and the main memory. The read loop can just access +the cache, saving bus bandwidth. + +If we cannot acquire the mutex lock in the specified time, we reserve a cell +in the wait array, set the waiters byte in the mutex to 1. To avoid a race +condition, after setting the waiters byte and before suspending the waiting +thread, we still have to check that the mutex is reserved, because it may +have happened that the thread which was holding the mutex has just released +it and did not see the waiters byte set to 1, a case which would lead the +other thread to an infinite wait. + +LEMMA 1: After a thread resets the event of a mutex (or rw_lock), some +======= +thread will eventually call os_event_set() on that particular event. +Thus no infinite wait is possible in this case. + +Proof: After making the reservation the thread sets the waiters field in the +mutex to 1. Then it checks that the mutex is still reserved by some thread, +or it reserves the mutex for itself. In any case, some thread (which may be +also some earlier thread, not necessarily the one currently holding the mutex) +will set the waiters field to 0 in mutex_exit, and then call +os_event_set() with the mutex as an argument. +Q.E.D. + +LEMMA 2: If an os_event_set() call is made after some thread has called +======= +the os_event_reset() and before it starts wait on that event, the call +will not be lost to the second thread. This is true even if there is an +intervening call to os_event_reset() by another thread. +Thus no infinite wait is possible in this case. + +Proof (non-windows platforms): os_event_reset() returns a monotonically +increasing value of signal_count. This value is increased at every +call of os_event_set() If thread A has called os_event_reset() followed +by thread B calling os_event_set() and then some other thread C calling +os_event_reset(), the is_set flag of the event will be set to FALSE; +but now if thread A calls os_event_wait_low() with the signal_count +value returned from the earlier call of os_event_reset(), it will +return immediately without waiting. +Q.E.D. + +Proof (windows): If there is a writer thread which is forced to wait for +the lock, it may be able to set the state of rw_lock to RW_LOCK_WAIT_EX +The design of rw_lock ensures that there is one and only one thread +that is able to change the state to RW_LOCK_WAIT_EX and this thread is +guaranteed to acquire the lock after it is released by the current +holders and before any other waiter gets the lock. +On windows this thread waits on a separate event i.e.: wait_ex_event. +Since only one thread can wait on this event there is no chance +of this event getting reset before the writer starts wait on it. +Therefore, this thread is guaranteed to catch the os_set_event() +signalled unconditionally at the release of the lock. +Q.E.D. */ + +/* Number of spin waits on mutexes: for performance monitoring */ + +/* round=one iteration of a spin loop */ +UNIV_INTERN ib_int64_t mutex_spin_round_count = 0; +UNIV_INTERN ib_int64_t mutex_spin_wait_count = 0; +UNIV_INTERN ib_int64_t mutex_os_wait_count = 0; +UNIV_INTERN ib_int64_t mutex_exit_count = 0; + +/* The global array of wait cells for implementation of the database's own +mutexes and read-write locks */ +UNIV_INTERN sync_array_t* sync_primary_wait_array; + +/* This variable is set to TRUE when sync_init is called */ +UNIV_INTERN ibool sync_initialized = FALSE; + + +typedef struct sync_level_struct sync_level_t; +typedef struct sync_thread_struct sync_thread_t; + +#ifdef UNIV_SYNC_DEBUG +/* The latch levels currently owned by threads are stored in this data +structure; the size of this array is OS_THREAD_MAX_N */ + +UNIV_INTERN sync_thread_t* sync_thread_level_arrays; + +/* Mutex protecting sync_thread_level_arrays */ +UNIV_INTERN mutex_t sync_thread_mutex; +#endif /* UNIV_SYNC_DEBUG */ + +/* Global list of database mutexes (not OS mutexes) created. */ +UNIV_INTERN ut_list_base_node_t mutex_list; + +/* Mutex protecting the mutex_list variable */ +UNIV_INTERN mutex_t mutex_list_mutex; + +#ifdef UNIV_SYNC_DEBUG +/* Latching order checks start when this is set TRUE */ +UNIV_INTERN ibool sync_order_checks_on = FALSE; +#endif /* UNIV_SYNC_DEBUG */ + +struct sync_thread_struct{ + os_thread_id_t id; /* OS thread id */ + sync_level_t* levels; /* level array for this thread; if this is NULL + this slot is unused */ +}; + +/* Number of slots reserved for each OS thread in the sync level array */ +#define SYNC_THREAD_N_LEVELS 10000 + +struct sync_level_struct{ + void* latch; /* pointer to a mutex or an rw-lock; NULL means that + the slot is empty */ + ulint level; /* level of the latch in the latching order */ +}; + +/********************************************************************** +Creates, or rather, initializes a mutex object in a specified memory +location (which must be appropriately aligned). The mutex is initialized +in the reset state. Explicit freeing of the mutex with mutex_free is +necessary only if the memory block containing it is freed. */ +UNIV_INTERN +void +mutex_create_func( +/*==============*/ + mutex_t* mutex, /* in: pointer to memory */ +#ifdef UNIV_DEBUG + const char* cmutex_name, /* in: mutex name */ +# ifdef UNIV_SYNC_DEBUG + ulint level, /* in: level */ +# endif /* UNIV_SYNC_DEBUG */ +#endif /* UNIV_DEBUG */ + const char* cfile_name, /* in: file name where created */ + ulint cline) /* in: file line where created */ +{ +#if defined(_WIN32) && defined(UNIV_CAN_USE_X86_ASSEMBLER) + mutex_reset_lock_word(mutex); +#elif defined(HAVE_GCC_ATOMIC_BUILTINS) + mutex_reset_lock_word(mutex); +#else + os_fast_mutex_init(&(mutex->os_fast_mutex)); + mutex->lock_word = 0; +#endif + mutex->event = os_event_create(NULL); + mutex_set_waiters(mutex, 0); +#ifdef UNIV_DEBUG + mutex->magic_n = MUTEX_MAGIC_N; +#endif /* UNIV_DEBUG */ +#ifdef UNIV_SYNC_DEBUG + mutex->line = 0; + mutex->file_name = "not yet reserved"; + mutex->level = level; +#endif /* UNIV_SYNC_DEBUG */ + mutex->cfile_name = cfile_name; + mutex->cline = cline; +#ifndef UNIV_HOTBACKUP + mutex->count_os_wait = 0; +# ifdef UNIV_DEBUG + mutex->cmutex_name= cmutex_name; + mutex->count_using= 0; + mutex->mutex_type= 0; + mutex->lspent_time= 0; + mutex->lmax_spent_time= 0; + mutex->count_spin_loop= 0; + mutex->count_spin_rounds= 0; + mutex->count_os_yield= 0; +# endif /* UNIV_DEBUG */ +#endif /* !UNIV_HOTBACKUP */ + + /* Check that lock_word is aligned; this is important on Intel */ + ut_ad(((ulint)(&(mutex->lock_word))) % 4 == 0); + + /* NOTE! The very first mutexes are not put to the mutex list */ + + if ((mutex == &mutex_list_mutex) +#ifdef UNIV_SYNC_DEBUG + || (mutex == &sync_thread_mutex) +#endif /* UNIV_SYNC_DEBUG */ + ) { + + return; + } + + mutex_enter(&mutex_list_mutex); + + ut_ad(UT_LIST_GET_LEN(mutex_list) == 0 + || UT_LIST_GET_FIRST(mutex_list)->magic_n == MUTEX_MAGIC_N); + + UT_LIST_ADD_FIRST(list, mutex_list, mutex); + + mutex_exit(&mutex_list_mutex); +} + +/********************************************************************** +Calling this function is obligatory only if the memory buffer containing +the mutex is freed. Removes a mutex object from the mutex list. The mutex +is checked to be in the reset state. */ +UNIV_INTERN +void +mutex_free( +/*=======*/ + mutex_t* mutex) /* in: mutex */ +{ + ut_ad(mutex_validate(mutex)); + ut_a(mutex_get_lock_word(mutex) == 0); + ut_a(mutex_get_waiters(mutex) == 0); + + if (mutex != &mutex_list_mutex +#ifdef UNIV_SYNC_DEBUG + && mutex != &sync_thread_mutex +#endif /* UNIV_SYNC_DEBUG */ + ) { + + mutex_enter(&mutex_list_mutex); + + ut_ad(!UT_LIST_GET_PREV(list, mutex) + || UT_LIST_GET_PREV(list, mutex)->magic_n + == MUTEX_MAGIC_N); + ut_ad(!UT_LIST_GET_NEXT(list, mutex) + || UT_LIST_GET_NEXT(list, mutex)->magic_n + == MUTEX_MAGIC_N); + + UT_LIST_REMOVE(list, mutex_list, mutex); + + mutex_exit(&mutex_list_mutex); + } + + os_event_free(mutex->event); + +#if defined(_WIN32) && defined(UNIV_CAN_USE_X86_ASSEMBLER) +#elif defined(HAVE_GCC_ATOMIC_BUILTINS) +#else + os_fast_mutex_free(&(mutex->os_fast_mutex)); +#endif + /* If we free the mutex protecting the mutex list (freeing is + not necessary), we have to reset the magic number AFTER removing + it from the list. */ +#ifdef UNIV_DEBUG + mutex->magic_n = 0; +#endif /* UNIV_DEBUG */ +} + +/************************************************************************ +NOTE! Use the corresponding macro in the header file, not this function +directly. Tries to lock the mutex for the current thread. If the lock is not +acquired immediately, returns with return value 1. */ +UNIV_INTERN +ulint +mutex_enter_nowait_func( +/*====================*/ + /* out: 0 if succeed, 1 if not */ + mutex_t* mutex, /* in: pointer to mutex */ + const char* file_name __attribute__((unused)), + /* in: file name where mutex + requested */ + ulint line __attribute__((unused))) + /* in: line where requested */ +{ + ut_ad(mutex_validate(mutex)); + + if (!mutex_test_and_set(mutex)) { + + ut_d(mutex->thread_id = os_thread_get_curr_id()); +#ifdef UNIV_SYNC_DEBUG + mutex_set_debug_info(mutex, file_name, line); +#endif + + return(0); /* Succeeded! */ + } + + return(1); +} + +#ifdef UNIV_DEBUG +/********************************************************************** +Checks that the mutex has been initialized. */ +UNIV_INTERN +ibool +mutex_validate( +/*===========*/ + const mutex_t* mutex) +{ + ut_a(mutex); + ut_a(mutex->magic_n == MUTEX_MAGIC_N); + + return(TRUE); +} + +/********************************************************************** +Checks that the current thread owns the mutex. Works only in the debug +version. */ +UNIV_INTERN +ibool +mutex_own( +/*======*/ + /* out: TRUE if owns */ + const mutex_t* mutex) /* in: mutex */ +{ + ut_ad(mutex_validate(mutex)); + + return(mutex_get_lock_word(mutex) == 1 + && os_thread_eq(mutex->thread_id, os_thread_get_curr_id())); +} +#endif /* UNIV_DEBUG */ + +/********************************************************************** +Sets the waiters field in a mutex. */ +UNIV_INTERN +void +mutex_set_waiters( +/*==============*/ + mutex_t* mutex, /* in: mutex */ + ulint n) /* in: value to set */ +{ + volatile ulint* ptr; /* declared volatile to ensure that + the value is stored to memory */ + ut_ad(mutex); + + ptr = &(mutex->waiters); + + *ptr = n; /* Here we assume that the write of a single + word in memory is atomic */ +} + +/********************************************************************** +Reserves a mutex for the current thread. If the mutex is reserved, the +function spins a preset time (controlled by SYNC_SPIN_ROUNDS), waiting +for the mutex before suspending the thread. */ +UNIV_INTERN +void +mutex_spin_wait( +/*============*/ + mutex_t* mutex, /* in: pointer to mutex */ + const char* file_name, /* in: file name where mutex + requested */ + ulint line) /* in: line where requested */ +{ + ulint index; /* index of the reserved wait cell */ + ulint i; /* spin round count */ +#if defined UNIV_DEBUG && !defined UNIV_HOTBACKUP + ib_int64_t lstart_time = 0, lfinish_time; /* for timing os_wait */ + ulint ltime_diff; + ulint sec; + ulint ms; + uint timer_started = 0; +#endif /* UNIV_DEBUG && !UNIV_HOTBACKUP */ + ut_ad(mutex); + + /* This update is not thread safe, but we don't mind if the count + isn't exact. Moved out of ifdef that follows because we are willing + to sacrifice the cost of counting this as the data is valuable. + Count the number of calls to mutex_spin_wait. */ + mutex_spin_wait_count++; + +mutex_loop: + + i = 0; + + /* Spin waiting for the lock word to become zero. Note that we do + not have to assume that the read access to the lock word is atomic, + as the actual locking is always committed with atomic test-and-set. + In reality, however, all processors probably have an atomic read of + a memory word. */ + +spin_loop: +#if defined UNIV_DEBUG && !defined UNIV_HOTBACKUP + mutex->count_spin_loop++; +#endif /* UNIV_DEBUG && !UNIV_HOTBACKUP */ + + while (mutex_get_lock_word(mutex) != 0 && i < SYNC_SPIN_ROUNDS) { + if (srv_spin_wait_delay) { + ut_delay(ut_rnd_interval(0, srv_spin_wait_delay)); + } + + i++; + } + + if (i == SYNC_SPIN_ROUNDS) { +#if defined UNIV_DEBUG && !defined UNIV_HOTBACKUP + mutex->count_os_yield++; + if (timed_mutexes == 1 && timer_started==0) { + ut_usectime(&sec, &ms); + lstart_time= (ib_int64_t)sec * 1000000 + ms; + timer_started = 1; + } +#endif /* UNIV_DEBUG && !UNIV_HOTBACKUP */ + os_thread_yield(); + } + +#ifdef UNIV_SRV_PRINT_LATCH_WAITS + fprintf(stderr, + "Thread %lu spin wait mutex at %p" + " cfile %s cline %lu rnds %lu\n", + (ulong) os_thread_pf(os_thread_get_curr_id()), (void*) mutex, + mutex->cfile_name, (ulong) mutex->cline, (ulong) i); +#endif + + mutex_spin_round_count += i; + +#if defined UNIV_DEBUG && !defined UNIV_HOTBACKUP + mutex->count_spin_rounds += i; +#endif /* UNIV_DEBUG && !UNIV_HOTBACKUP */ + + if (mutex_test_and_set(mutex) == 0) { + /* Succeeded! */ + + ut_d(mutex->thread_id = os_thread_get_curr_id()); +#ifdef UNIV_SYNC_DEBUG + mutex_set_debug_info(mutex, file_name, line); +#endif + + goto finish_timing; + } + + /* We may end up with a situation where lock_word is 0 but the OS + fast mutex is still reserved. On FreeBSD the OS does not seem to + schedule a thread which is constantly calling pthread_mutex_trylock + (in mutex_test_and_set implementation). Then we could end up + spinning here indefinitely. The following 'i++' stops this infinite + spin. */ + + i++; + + if (i < SYNC_SPIN_ROUNDS) { + goto spin_loop; + } + + sync_array_reserve_cell(sync_primary_wait_array, mutex, + SYNC_MUTEX, file_name, line, &index); + + /* The memory order of the array reservation and the change in the + waiters field is important: when we suspend a thread, we first + reserve the cell and then set waiters field to 1. When threads are + released in mutex_exit, the waiters field is first set to zero and + then the event is set to the signaled state. */ + + mutex_set_waiters(mutex, 1); + + /* Try to reserve still a few times */ + for (i = 0; i < 4; i++) { + if (mutex_test_and_set(mutex) == 0) { + /* Succeeded! Free the reserved wait cell */ + + sync_array_free_cell(sync_primary_wait_array, index); + + ut_d(mutex->thread_id = os_thread_get_curr_id()); +#ifdef UNIV_SYNC_DEBUG + mutex_set_debug_info(mutex, file_name, line); +#endif + +#ifdef UNIV_SRV_PRINT_LATCH_WAITS + fprintf(stderr, "Thread %lu spin wait succeeds at 2:" + " mutex at %p\n", + (ulong) os_thread_pf(os_thread_get_curr_id()), + (void*) mutex); +#endif + + goto finish_timing; + + /* Note that in this case we leave the waiters field + set to 1. We cannot reset it to zero, as we do not + know if there are other waiters. */ + } + } + + /* Now we know that there has been some thread holding the mutex + after the change in the wait array and the waiters field was made. + Now there is no risk of infinite wait on the event. */ + +#ifdef UNIV_SRV_PRINT_LATCH_WAITS + fprintf(stderr, + "Thread %lu OS wait mutex at %p cfile %s cline %lu rnds %lu\n", + (ulong) os_thread_pf(os_thread_get_curr_id()), (void*) mutex, + mutex->cfile_name, (ulong) mutex->cline, (ulong) i); +#endif + + mutex_os_wait_count++; + +#ifndef UNIV_HOTBACKUP + mutex->count_os_wait++; +# ifdef UNIV_DEBUG + /* !!!!! Sometimes os_wait can be called without os_thread_yield */ + + if (timed_mutexes == 1 && timer_started==0) { + ut_usectime(&sec, &ms); + lstart_time= (ib_int64_t)sec * 1000000 + ms; + timer_started = 1; + } +# endif /* UNIV_DEBUG */ +#endif /* !UNIV_HOTBACKUP */ + + sync_array_wait_event(sync_primary_wait_array, index); + goto mutex_loop; + +finish_timing: +#if defined UNIV_DEBUG && !defined UNIV_HOTBACKUP + if (timed_mutexes == 1 && timer_started==1) { + ut_usectime(&sec, &ms); + lfinish_time= (ib_int64_t)sec * 1000000 + ms; + + ltime_diff= (ulint) (lfinish_time - lstart_time); + mutex->lspent_time += ltime_diff; + + if (mutex->lmax_spent_time < ltime_diff) { + mutex->lmax_spent_time= ltime_diff; + } + } +#endif /* UNIV_DEBUG && !UNIV_HOTBACKUP */ + return; +} + +/********************************************************************** +Releases the threads waiting in the primary wait array for this mutex. */ +UNIV_INTERN +void +mutex_signal_object( +/*================*/ + mutex_t* mutex) /* in: mutex */ +{ + mutex_set_waiters(mutex, 0); + + /* The memory order of resetting the waiters field and + signaling the object is important. See LEMMA 1 above. */ + os_event_set(mutex->event); + sync_array_object_signalled(sync_primary_wait_array); +} + +#ifdef UNIV_SYNC_DEBUG +/********************************************************************** +Sets the debug information for a reserved mutex. */ +UNIV_INTERN +void +mutex_set_debug_info( +/*=================*/ + mutex_t* mutex, /* in: mutex */ + const char* file_name, /* in: file where requested */ + ulint line) /* in: line where requested */ +{ + ut_ad(mutex); + ut_ad(file_name); + + sync_thread_add_level(mutex, mutex->level); + + mutex->file_name = file_name; + mutex->line = line; +} + +/********************************************************************** +Gets the debug information for a reserved mutex. */ +UNIV_INTERN +void +mutex_get_debug_info( +/*=================*/ + mutex_t* mutex, /* in: mutex */ + const char** file_name, /* out: file where requested */ + ulint* line, /* out: line where requested */ + os_thread_id_t* thread_id) /* out: id of the thread which owns + the mutex */ +{ + ut_ad(mutex); + + *file_name = mutex->file_name; + *line = mutex->line; + *thread_id = mutex->thread_id; +} + +/********************************************************************** +Prints debug info of currently reserved mutexes. */ +static +void +mutex_list_print_info( +/*==================*/ + FILE* file) /* in: file where to print */ +{ + mutex_t* mutex; + const char* file_name; + ulint line; + os_thread_id_t thread_id; + ulint count = 0; + + fputs("----------\n" + "MUTEX INFO\n" + "----------\n", file); + + mutex_enter(&mutex_list_mutex); + + mutex = UT_LIST_GET_FIRST(mutex_list); + + while (mutex != NULL) { + count++; + + if (mutex_get_lock_word(mutex) != 0) { + mutex_get_debug_info(mutex, &file_name, &line, + &thread_id); + fprintf(file, + "Locked mutex: addr %p thread %ld" + " file %s line %ld\n", + (void*) mutex, os_thread_pf(thread_id), + file_name, line); + } + + mutex = UT_LIST_GET_NEXT(list, mutex); + } + + fprintf(file, "Total number of mutexes %ld\n", count); + + mutex_exit(&mutex_list_mutex); +} + +/********************************************************************** +Counts currently reserved mutexes. Works only in the debug version. */ +UNIV_INTERN +ulint +mutex_n_reserved(void) +/*==================*/ +{ + mutex_t* mutex; + ulint count = 0; + + mutex_enter(&mutex_list_mutex); + + mutex = UT_LIST_GET_FIRST(mutex_list); + + while (mutex != NULL) { + if (mutex_get_lock_word(mutex) != 0) { + + count++; + } + + mutex = UT_LIST_GET_NEXT(list, mutex); + } + + mutex_exit(&mutex_list_mutex); + + ut_a(count >= 1); + + return(count - 1); /* Subtract one, because this function itself + was holding one mutex (mutex_list_mutex) */ +} + +/********************************************************************** +Returns TRUE if no mutex or rw-lock is currently locked. Works only in +the debug version. */ +UNIV_INTERN +ibool +sync_all_freed(void) +/*================*/ +{ + return(mutex_n_reserved() + rw_lock_n_locked() == 0); +} + +/********************************************************************** +Gets the value in the nth slot in the thread level arrays. */ +static +sync_thread_t* +sync_thread_level_arrays_get_nth( +/*=============================*/ + /* out: pointer to thread slot */ + ulint n) /* in: slot number */ +{ + ut_ad(n < OS_THREAD_MAX_N); + + return(sync_thread_level_arrays + n); +} + +/********************************************************************** +Looks for the thread slot for the calling thread. */ +static +sync_thread_t* +sync_thread_level_arrays_find_slot(void) +/*====================================*/ + /* out: pointer to thread slot, NULL if not found */ + +{ + sync_thread_t* slot; + os_thread_id_t id; + ulint i; + + id = os_thread_get_curr_id(); + + for (i = 0; i < OS_THREAD_MAX_N; i++) { + + slot = sync_thread_level_arrays_get_nth(i); + + if (slot->levels && os_thread_eq(slot->id, id)) { + + return(slot); + } + } + + return(NULL); +} + +/********************************************************************** +Looks for an unused thread slot. */ +static +sync_thread_t* +sync_thread_level_arrays_find_free(void) +/*====================================*/ + /* out: pointer to thread slot */ + +{ + sync_thread_t* slot; + ulint i; + + for (i = 0; i < OS_THREAD_MAX_N; i++) { + + slot = sync_thread_level_arrays_get_nth(i); + + if (slot->levels == NULL) { + + return(slot); + } + } + + return(NULL); +} + +/********************************************************************** +Gets the value in the nth slot in the thread level array. */ +static +sync_level_t* +sync_thread_levels_get_nth( +/*=======================*/ + /* out: pointer to level slot */ + sync_level_t* arr, /* in: pointer to level array for an OS + thread */ + ulint n) /* in: slot number */ +{ + ut_ad(n < SYNC_THREAD_N_LEVELS); + + return(arr + n); +} + +/********************************************************************** +Checks if all the level values stored in the level array are greater than +the given limit. */ +static +ibool +sync_thread_levels_g( +/*=================*/ + /* out: TRUE if all greater */ + sync_level_t* arr, /* in: pointer to level array for an OS + thread */ + ulint limit) /* in: level limit */ +{ + sync_level_t* slot; + rw_lock_t* lock; + mutex_t* mutex; + ulint i; + + for (i = 0; i < SYNC_THREAD_N_LEVELS; i++) { + + slot = sync_thread_levels_get_nth(arr, i); + + if (slot->latch != NULL) { + if (slot->level <= limit) { + + lock = slot->latch; + mutex = slot->latch; + + fprintf(stderr, + "InnoDB: sync levels should be" + " > %lu but a level is %lu\n", + (ulong) limit, (ulong) slot->level); + + if (mutex->magic_n == MUTEX_MAGIC_N) { + fprintf(stderr, + "Mutex created at %s %lu\n", + mutex->cfile_name, + (ulong) mutex->cline); + + if (mutex_get_lock_word(mutex) != 0) { + const char* file_name; + ulint line; + os_thread_id_t thread_id; + + mutex_get_debug_info( + mutex, &file_name, + &line, &thread_id); + + fprintf(stderr, + "InnoDB: Locked mutex:" + " addr %p thread %ld" + " file %s line %ld\n", + (void*) mutex, + os_thread_pf( + thread_id), + file_name, + (ulong) line); + } else { + fputs("Not locked\n", stderr); + } + } else { + rw_lock_print(lock); + } + + return(FALSE); + } + } + } + + return(TRUE); +} + +/********************************************************************** +Checks if the level value is stored in the level array. */ +static +ibool +sync_thread_levels_contain( +/*=======================*/ + /* out: TRUE if stored */ + sync_level_t* arr, /* in: pointer to level array for an OS + thread */ + ulint level) /* in: level */ +{ + sync_level_t* slot; + ulint i; + + for (i = 0; i < SYNC_THREAD_N_LEVELS; i++) { + + slot = sync_thread_levels_get_nth(arr, i); + + if (slot->latch != NULL) { + if (slot->level == level) { + + return(TRUE); + } + } + } + + return(FALSE); +} + +/********************************************************************** +Checks that the level array for the current thread is empty. */ +UNIV_INTERN +ibool +sync_thread_levels_empty_gen( +/*=========================*/ + /* out: TRUE if empty except the + exceptions specified below */ + ibool dict_mutex_allowed) /* in: TRUE if dictionary mutex is + allowed to be owned by the thread, + also purge_is_running mutex is + allowed */ +{ + sync_level_t* arr; + sync_thread_t* thread_slot; + sync_level_t* slot; + ulint i; + + if (!sync_order_checks_on) { + + return(TRUE); + } + + mutex_enter(&sync_thread_mutex); + + thread_slot = sync_thread_level_arrays_find_slot(); + + if (thread_slot == NULL) { + + mutex_exit(&sync_thread_mutex); + + return(TRUE); + } + + arr = thread_slot->levels; + + for (i = 0; i < SYNC_THREAD_N_LEVELS; i++) { + + slot = sync_thread_levels_get_nth(arr, i); + + if (slot->latch != NULL + && (!dict_mutex_allowed + || (slot->level != SYNC_DICT + && slot->level != SYNC_DICT_OPERATION))) { + + mutex_exit(&sync_thread_mutex); + ut_error; + + return(FALSE); + } + } + + mutex_exit(&sync_thread_mutex); + + return(TRUE); +} + +/********************************************************************** +Checks that the level array for the current thread is empty. */ +UNIV_INTERN +ibool +sync_thread_levels_empty(void) +/*==========================*/ + /* out: TRUE if empty */ +{ + return(sync_thread_levels_empty_gen(FALSE)); +} + +/********************************************************************** +Adds a latch and its level in the thread level array. Allocates the memory +for the array if called first time for this OS thread. Makes the checks +against other latch levels stored in the array for this thread. */ +UNIV_INTERN +void +sync_thread_add_level( +/*==================*/ + void* latch, /* in: pointer to a mutex or an rw-lock */ + ulint level) /* in: level in the latching order; if + SYNC_LEVEL_VARYING, nothing is done */ +{ + sync_level_t* array; + sync_level_t* slot; + sync_thread_t* thread_slot; + ulint i; + + if (!sync_order_checks_on) { + + return; + } + + if ((latch == (void*)&sync_thread_mutex) + || (latch == (void*)&mutex_list_mutex) + || (latch == (void*)&rw_lock_debug_mutex) + || (latch == (void*)&rw_lock_list_mutex)) { + + return; + } + + if (level == SYNC_LEVEL_VARYING) { + + return; + } + + mutex_enter(&sync_thread_mutex); + + thread_slot = sync_thread_level_arrays_find_slot(); + + if (thread_slot == NULL) { + /* We have to allocate the level array for a new thread */ + array = ut_malloc(sizeof(sync_level_t) * SYNC_THREAD_N_LEVELS); + + thread_slot = sync_thread_level_arrays_find_free(); + + thread_slot->id = os_thread_get_curr_id(); + thread_slot->levels = array; + + for (i = 0; i < SYNC_THREAD_N_LEVELS; i++) { + + slot = sync_thread_levels_get_nth(array, i); + + slot->latch = NULL; + } + } + + array = thread_slot->levels; + + /* NOTE that there is a problem with _NODE and _LEAF levels: if the + B-tree height changes, then a leaf can change to an internal node + or the other way around. We do not know at present if this can cause + unnecessary assertion failures below. */ + + switch (level) { + case SYNC_NO_ORDER_CHECK: + case SYNC_EXTERN_STORAGE: + case SYNC_TREE_NODE_FROM_HASH: + /* Do no order checking */ + break; + case SYNC_MEM_POOL: + case SYNC_MEM_HASH: + case SYNC_RECV: + case SYNC_WORK_QUEUE: + case SYNC_LOG: + case SYNC_THR_LOCAL: + case SYNC_ANY_LATCH: + case SYNC_TRX_SYS_HEADER: + case SYNC_FILE_FORMAT_TAG: + case SYNC_DOUBLEWRITE: + case SYNC_BUF_LRU_LIST: + case SYNC_BUF_FLUSH_LIST: + case SYNC_BUF_PAGE_HASH: + case SYNC_BUF_FREE_LIST: + case SYNC_BUF_ZIP_FREE: + case SYNC_BUF_ZIP_HASH: + case SYNC_BUF_POOL: + case SYNC_SEARCH_SYS: + case SYNC_SEARCH_SYS_CONF: + case SYNC_TRX_LOCK_HEAP: + case SYNC_KERNEL: + case SYNC_IBUF_BITMAP_MUTEX: + case SYNC_RSEG: + case SYNC_TRX_UNDO: + case SYNC_PURGE_LATCH: + case SYNC_PURGE_SYS: + case SYNC_DICT_AUTOINC_MUTEX: + case SYNC_DICT_OPERATION: + case SYNC_DICT_HEADER: + case SYNC_TRX_I_S_RWLOCK: + case SYNC_TRX_I_S_LAST_READ: + if (!sync_thread_levels_g(array, level)) { + fprintf(stderr, + "InnoDB: sync_thread_levels_g(array, %lu)" + " does not hold!\n", level); + ut_error; + } + break; + case SYNC_BUF_BLOCK: + /* Either the thread must own the buffer pool mutex + (buf_pool_mutex), or it is allowed to latch only ONE + buffer block (block->mutex or buf_pool_zip_mutex). */ + ut_a((sync_thread_levels_contain(array, SYNC_BUF_LRU_LIST) + && sync_thread_levels_g(array, SYNC_BUF_BLOCK - 1)) + || sync_thread_levels_g(array, SYNC_BUF_BLOCK)); + break; + case SYNC_REC_LOCK: + ut_a((sync_thread_levels_contain(array, SYNC_KERNEL) + && sync_thread_levels_g(array, SYNC_REC_LOCK - 1)) + || sync_thread_levels_g(array, SYNC_REC_LOCK)); + break; + case SYNC_IBUF_BITMAP: + /* Either the thread must own the master mutex to all + the bitmap pages, or it is allowed to latch only ONE + bitmap page. */ + ut_a((sync_thread_levels_contain(array, SYNC_IBUF_BITMAP_MUTEX) + && sync_thread_levels_g(array, SYNC_IBUF_BITMAP - 1)) + || sync_thread_levels_g(array, SYNC_IBUF_BITMAP)); + break; + case SYNC_FSP_PAGE: + ut_a(sync_thread_levels_contain(array, SYNC_FSP)); + break; + case SYNC_FSP: + ut_a(sync_thread_levels_contain(array, SYNC_FSP) + || sync_thread_levels_g(array, SYNC_FSP)); + break; + case SYNC_TRX_UNDO_PAGE: + ut_a(sync_thread_levels_contain(array, SYNC_TRX_UNDO) + || sync_thread_levels_contain(array, SYNC_RSEG) + || sync_thread_levels_contain(array, SYNC_PURGE_SYS) + || sync_thread_levels_g(array, SYNC_TRX_UNDO_PAGE)); + break; + case SYNC_RSEG_HEADER: + ut_a(sync_thread_levels_contain(array, SYNC_RSEG)); + break; + case SYNC_RSEG_HEADER_NEW: + ut_a(sync_thread_levels_contain(array, SYNC_KERNEL) + && sync_thread_levels_contain(array, SYNC_FSP_PAGE)); + break; + case SYNC_TREE_NODE: + ut_a(sync_thread_levels_contain(array, SYNC_INDEX_TREE) + || sync_thread_levels_contain(array, SYNC_DICT_OPERATION) + || sync_thread_levels_g(array, SYNC_TREE_NODE - 1)); + break; + case SYNC_TREE_NODE_NEW: + ut_a(sync_thread_levels_contain(array, SYNC_FSP_PAGE) + || sync_thread_levels_contain(array, SYNC_IBUF_MUTEX)); + break; + case SYNC_INDEX_TREE: + ut_a((sync_thread_levels_contain(array, SYNC_IBUF_MUTEX) + && sync_thread_levels_contain(array, SYNC_FSP) + && sync_thread_levels_g(array, SYNC_FSP_PAGE - 1)) + || sync_thread_levels_g(array, SYNC_TREE_NODE - 1)); + break; + case SYNC_IBUF_MUTEX: + ut_a(sync_thread_levels_g(array, SYNC_FSP_PAGE - 1)); + break; + case SYNC_IBUF_PESS_INSERT_MUTEX: + ut_a(sync_thread_levels_g(array, SYNC_FSP - 1) + && !sync_thread_levels_contain(array, SYNC_IBUF_MUTEX)); + break; + case SYNC_IBUF_HEADER: + ut_a(sync_thread_levels_g(array, SYNC_FSP - 1) + && !sync_thread_levels_contain(array, SYNC_IBUF_MUTEX) + && !sync_thread_levels_contain( + array, SYNC_IBUF_PESS_INSERT_MUTEX)); + break; + case SYNC_DICT: +#ifdef UNIV_DEBUG + ut_a(buf_debug_prints + || sync_thread_levels_g(array, SYNC_DICT)); +#else /* UNIV_DEBUG */ + ut_a(sync_thread_levels_g(array, SYNC_DICT)); +#endif /* UNIV_DEBUG */ + break; + default: + ut_error; + } + + for (i = 0; i < SYNC_THREAD_N_LEVELS; i++) { + + slot = sync_thread_levels_get_nth(array, i); + + if (slot->latch == NULL) { + slot->latch = latch; + slot->level = level; + + break; + } + } + + ut_a(i < SYNC_THREAD_N_LEVELS); + + mutex_exit(&sync_thread_mutex); +} + +/********************************************************************** +Removes a latch from the thread level array if it is found there. */ +UNIV_INTERN +ibool +sync_thread_reset_level( +/*====================*/ + /* out: TRUE if found from the array; it is an error + if the latch is not found */ + void* latch) /* in: pointer to a mutex or an rw-lock */ +{ + sync_level_t* array; + sync_level_t* slot; + sync_thread_t* thread_slot; + ulint i; + + if (!sync_order_checks_on) { + + return(FALSE); + } + + if ((latch == (void*)&sync_thread_mutex) + || (latch == (void*)&mutex_list_mutex) + || (latch == (void*)&rw_lock_debug_mutex) + || (latch == (void*)&rw_lock_list_mutex)) { + + return(FALSE); + } + + mutex_enter(&sync_thread_mutex); + + thread_slot = sync_thread_level_arrays_find_slot(); + + if (thread_slot == NULL) { + + ut_error; + + mutex_exit(&sync_thread_mutex); + return(FALSE); + } + + array = thread_slot->levels; + + for (i = 0; i < SYNC_THREAD_N_LEVELS; i++) { + + slot = sync_thread_levels_get_nth(array, i); + + if (slot->latch == latch) { + slot->latch = NULL; + + mutex_exit(&sync_thread_mutex); + + return(TRUE); + } + } + + if (((mutex_t*) latch)->magic_n != MUTEX_MAGIC_N) { + rw_lock_t* rw_lock; + + rw_lock = (rw_lock_t*) latch; + + if (rw_lock->level == SYNC_LEVEL_VARYING) { + mutex_exit(&sync_thread_mutex); + + return(TRUE); + } + } + + ut_error; + + mutex_exit(&sync_thread_mutex); + + return(FALSE); +} +#endif /* UNIV_SYNC_DEBUG */ + +/********************************************************************** +Initializes the synchronization data structures. */ +UNIV_INTERN +void +sync_init(void) +/*===========*/ +{ +#ifdef UNIV_SYNC_DEBUG + sync_thread_t* thread_slot; + ulint i; +#endif /* UNIV_SYNC_DEBUG */ + + ut_a(sync_initialized == FALSE); + + sync_initialized = TRUE; + + /* Create the primary system wait array which is protected by an OS + mutex */ + + sync_primary_wait_array = sync_array_create(OS_THREAD_MAX_N, + SYNC_ARRAY_OS_MUTEX); +#ifdef UNIV_SYNC_DEBUG + /* Create the thread latch level array where the latch levels + are stored for each OS thread */ + + sync_thread_level_arrays = ut_malloc(OS_THREAD_MAX_N + * sizeof(sync_thread_t)); + for (i = 0; i < OS_THREAD_MAX_N; i++) { + + thread_slot = sync_thread_level_arrays_get_nth(i); + thread_slot->levels = NULL; + } +#endif /* UNIV_SYNC_DEBUG */ + /* Init the mutex list and create the mutex to protect it. */ + + UT_LIST_INIT(mutex_list); + mutex_create(&mutex_list_mutex, SYNC_NO_ORDER_CHECK); +#ifdef UNIV_SYNC_DEBUG + mutex_create(&sync_thread_mutex, SYNC_NO_ORDER_CHECK); +#endif /* UNIV_SYNC_DEBUG */ + + /* Init the rw-lock list and create the mutex to protect it. */ + + UT_LIST_INIT(rw_lock_list); + mutex_create(&rw_lock_list_mutex, SYNC_NO_ORDER_CHECK); + +#ifdef UNIV_SYNC_DEBUG + mutex_create(&rw_lock_debug_mutex, SYNC_NO_ORDER_CHECK); + + rw_lock_debug_event = os_event_create(NULL); + rw_lock_debug_waiters = FALSE; +#endif /* UNIV_SYNC_DEBUG */ +} + +/********************************************************************** +Frees the resources in InnoDB's own synchronization data structures. Use +os_sync_free() after calling this. */ +UNIV_INTERN +void +sync_close(void) +/*===========*/ +{ + mutex_t* mutex; + + sync_array_free(sync_primary_wait_array); + + mutex = UT_LIST_GET_FIRST(mutex_list); + + while (mutex) { + mutex_free(mutex); + mutex = UT_LIST_GET_FIRST(mutex_list); + } + + mutex_free(&mutex_list_mutex); +#ifdef UNIV_SYNC_DEBUG + mutex_free(&sync_thread_mutex); +#endif /* UNIV_SYNC_DEBUG */ +} + +/*********************************************************************** +Prints wait info of the sync system. */ +UNIV_INTERN +void +sync_print_wait_info( +/*=================*/ + FILE* file) /* in: file where to print */ +{ +#ifdef UNIV_SYNC_DEBUG + fprintf(file, "Mutex exits %llu, rws exits %llu, rwx exits %llu\n", + mutex_exit_count, rw_s_exit_count, rw_x_exit_count); +#endif + + fprintf(file, + "Mutex spin waits %llu, rounds %llu, OS waits %llu\n" + "RW-shared spins %llu, OS waits %llu;" + " RW-excl spins %llu, OS waits %llu\n", + mutex_spin_wait_count, + mutex_spin_round_count, + mutex_os_wait_count, + rw_s_spin_wait_count, + rw_s_os_wait_count, + rw_x_spin_wait_count, + rw_x_os_wait_count); + + fprintf(file, + "Spin rounds per wait: %.2f mutex, %.2f RW-shared, " + "%.2f RW-excl\n", + (double) mutex_spin_round_count / + (mutex_spin_wait_count ? mutex_spin_wait_count : 1), + (double) rw_s_spin_round_count / + (rw_s_spin_wait_count ? rw_s_spin_wait_count : 1), + (double) rw_x_spin_round_count / + (rw_x_spin_wait_count ? rw_x_spin_wait_count : 1)); +} + +/*********************************************************************** +Prints info of the sync system. */ +UNIV_INTERN +void +sync_print( +/*=======*/ + FILE* file) /* in: file where to print */ +{ +#ifdef UNIV_SYNC_DEBUG + mutex_list_print_info(file); + + rw_lock_list_print_info(file); +#endif /* UNIV_SYNC_DEBUG */ + + sync_array_print_info(file, sync_primary_wait_array); + + sync_print_wait_info(file); +} diff --git a/storage/xtradb/thr/thr0loc.c b/storage/xtradb/thr/thr0loc.c new file mode 100644 index 00000000000..aea1992a921 --- /dev/null +++ b/storage/xtradb/thr/thr0loc.c @@ -0,0 +1,273 @@ +/***************************************************************************** + +Copyright (c) 1995, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/****************************************************** +The thread local storage + +Created 10/5/1995 Heikki Tuuri +*******************************************************/ + +#include "thr0loc.h" +#ifdef UNIV_NONINL +#include "thr0loc.ic" +#endif + +#include "sync0sync.h" +#include "hash0hash.h" +#include "mem0mem.h" +#include "srv0srv.h" + +/* + IMPLEMENTATION OF THREAD LOCAL STORAGE + ====================================== + +The threads sometimes need private data which depends on the thread id. +This is implemented as a hash table, where the hash value is calculated +from the thread id, to prepare for a large number of threads. The hash table +is protected by a mutex. If you need modify the program and put new data to +the thread local storage, just add it to struct thr_local_struct in the +header file. */ + +/* Mutex protecting the local storage hash table */ +static mutex_t thr_local_mutex; + +/* The hash table. The module is not yet initialized when it is NULL. */ +static hash_table_t* thr_local_hash = NULL; +ulint thr_local_hash_n_nodes = 0; + +/* The private data for each thread should be put to +the structure below and the accessor functions written +for the field. */ +typedef struct thr_local_struct thr_local_t; + +struct thr_local_struct{ + os_thread_id_t id; /* id of the thread which owns this struct */ + os_thread_t handle; /* operating system handle to the thread */ + ulint slot_no;/* the index of the slot in the thread table + for this thread */ + ibool in_ibuf;/* TRUE if the the thread is doing an ibuf + operation */ + hash_node_t hash; /* hash chain node */ + ulint magic_n; +}; + +#define THR_LOCAL_MAGIC_N 1231234 + +/*********************************************************************** +Returns the local storage struct for a thread. */ +static +thr_local_t* +thr_local_get( +/*==========*/ + /* out: local storage */ + os_thread_id_t id) /* in: thread id of the thread */ +{ + thr_local_t* local; + +try_again: + ut_ad(thr_local_hash); + ut_ad(mutex_own(&thr_local_mutex)); + + /* Look for the local struct in the hash table */ + + local = NULL; + + HASH_SEARCH(hash, thr_local_hash, os_thread_pf(id), + thr_local_t*, local,, os_thread_eq(local->id, id)); + if (local == NULL) { + mutex_exit(&thr_local_mutex); + + thr_local_create(); + + mutex_enter(&thr_local_mutex); + + goto try_again; + } + + ut_ad(local->magic_n == THR_LOCAL_MAGIC_N); + + return(local); +} + +/*********************************************************************** +Gets the slot number in the thread table of a thread. */ +UNIV_INTERN +ulint +thr_local_get_slot_no( +/*==================*/ + /* out: slot number */ + os_thread_id_t id) /* in: thread id of the thread */ +{ + ulint slot_no; + thr_local_t* local; + + mutex_enter(&thr_local_mutex); + + local = thr_local_get(id); + + slot_no = local->slot_no; + + mutex_exit(&thr_local_mutex); + + return(slot_no); +} + +/*********************************************************************** +Sets the slot number in the thread table of a thread. */ +UNIV_INTERN +void +thr_local_set_slot_no( +/*==================*/ + os_thread_id_t id, /* in: thread id of the thread */ + ulint slot_no)/* in: slot number */ +{ + thr_local_t* local; + + mutex_enter(&thr_local_mutex); + + local = thr_local_get(id); + + local->slot_no = slot_no; + + mutex_exit(&thr_local_mutex); +} + +/*********************************************************************** +Returns pointer to the 'in_ibuf' field within the current thread local +storage. */ +UNIV_INTERN +ibool* +thr_local_get_in_ibuf_field(void) +/*=============================*/ + /* out: pointer to the in_ibuf field */ +{ + thr_local_t* local; + + mutex_enter(&thr_local_mutex); + + local = thr_local_get(os_thread_get_curr_id()); + + mutex_exit(&thr_local_mutex); + + return(&(local->in_ibuf)); +} + +/*********************************************************************** +Creates a local storage struct for the calling new thread. */ +UNIV_INTERN +void +thr_local_create(void) +/*==================*/ +{ + thr_local_t* local; + + if (thr_local_hash == NULL) { + thr_local_init(); + } + + local = mem_alloc(sizeof(thr_local_t)); + + local->id = os_thread_get_curr_id(); + local->handle = os_thread_get_curr(); + local->magic_n = THR_LOCAL_MAGIC_N; + + local->in_ibuf = FALSE; + + mutex_enter(&thr_local_mutex); + + HASH_INSERT(thr_local_t, hash, thr_local_hash, + os_thread_pf(os_thread_get_curr_id()), + local); + + thr_local_hash_n_nodes++; + mutex_exit(&thr_local_mutex); +} + +/*********************************************************************** +Frees the local storage struct for the specified thread. */ +UNIV_INTERN +void +thr_local_free( +/*===========*/ + os_thread_id_t id) /* in: thread id */ +{ + thr_local_t* local; + + mutex_enter(&thr_local_mutex); + + /* Look for the local struct in the hash table */ + + HASH_SEARCH(hash, thr_local_hash, os_thread_pf(id), + thr_local_t*, local,, os_thread_eq(local->id, id)); + if (local == NULL) { + mutex_exit(&thr_local_mutex); + + return; + } + + HASH_DELETE(thr_local_t, hash, thr_local_hash, + os_thread_pf(id), local); + thr_local_hash_n_nodes--; + + mutex_exit(&thr_local_mutex); + + ut_a(local->magic_n == THR_LOCAL_MAGIC_N); + + mem_free(local); +} + +/******************************************************************** +Initializes the thread local storage module. */ +UNIV_INTERN +void +thr_local_init(void) +/*================*/ +{ + + ut_a(thr_local_hash == NULL); + + thr_local_hash = hash_create(OS_THREAD_MAX_N + 100); + + mutex_create(&thr_local_mutex, SYNC_THR_LOCAL); +} + +/************************************************************************* +Return local hash table informations. */ + +ulint +thr_local_hash_cells(void) +/*======================*/ +{ + if (thr_local_hash) { + return (thr_local_hash->n_cells); + } else { + return 0; + } +} + +ulint +thr_local_hash_nodes(void) +/*======================*/ +{ + if (thr_local_hash) { + return (thr_local_hash_n_nodes + * (sizeof(thr_local_t) + MEM_BLOCK_HEADER_SIZE)); + } else { + return 0; + } +} diff --git a/storage/xtradb/trx/trx0i_s.c b/storage/xtradb/trx/trx0i_s.c new file mode 100644 index 00000000000..512e38cc17e --- /dev/null +++ b/storage/xtradb/trx/trx0i_s.c @@ -0,0 +1,1431 @@ +/***************************************************************************** + +Copyright (c) 2007, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/****************************************************** +INFORMATION SCHEMA innodb_trx, innodb_locks and +innodb_lock_waits tables fetch code. + +The code below fetches information needed to fill those +3 dynamic tables and uploads it into a "transactions +table cache" for later retrieval. + +Created July 17, 2007 Vasil Dimov +*******************************************************/ + +#include <mysql/plugin.h> + +#include "mysql_addons.h" + +#include "univ.i" +#include "buf0buf.h" +#include "dict0dict.h" +#include "ha0storage.h" +#include "ha_prototypes.h" +#include "hash0hash.h" +#include "lock0iter.h" +#include "lock0lock.h" +#include "mem0mem.h" +#include "page0page.h" +#include "rem0rec.h" +#include "row0row.h" +#include "srv0srv.h" +#include "sync0rw.h" +#include "sync0sync.h" +#include "sync0types.h" +#include "trx0i_s.h" +#include "trx0sys.h" +#include "trx0trx.h" +#include "ut0mem.h" +#include "ut0ut.h" + +#define TABLE_CACHE_INITIAL_ROWSNUM 1024 + +/* Table cache's rows are stored in a set of chunks. When a new row is +added a new chunk is allocated if necessary. MEM_CHUNKS_IN_TABLE_CACHE +specifies the maximum number of chunks. +Assuming that the first one is 1024 rows (TABLE_CACHE_INITIAL_ROWSNUM) +and each subsequent is N/2 where N is the number of rows we have +allocated till now, then 39th chunk would have 1677416425 number of rows +and all chunks would have 3354832851 number of rows. */ +#define MEM_CHUNKS_IN_TABLE_CACHE 39 + +/* The following are some testing auxiliary macros. Do not enable them +in a production environment. */ + +#if 0 +/* If this is enabled then lock folds will always be different +resulting in equal rows being put in a different cells of the hash +table. Checking for duplicates will be flawed because different +fold will be calculated when a row is searched in the hash table. */ +#define TEST_LOCK_FOLD_ALWAYS_DIFFERENT +#endif + +#if 0 +/* This effectively kills the search-for-duplicate-before-adding-a-row +function, but searching in the hash is still performed. It will always +be assumed that lock is not present and insertion will be performed in +the hash table. */ +#define TEST_NO_LOCKS_ROW_IS_EVER_EQUAL_TO_LOCK_T +#endif + +#if 0 +/* This aggressively repeats adding each row many times. Depending on +the above settings this may be noop or may result in lots of rows being +added. */ +#define TEST_ADD_EACH_LOCKS_ROW_MANY_TIMES +#endif + +#if 0 +/* Very similar to TEST_NO_LOCKS_ROW_IS_EVER_EQUAL_TO_LOCK_T but hash +table search is not performed at all. */ +#define TEST_DO_NOT_CHECK_FOR_DUPLICATE_ROWS +#endif + +#if 0 +/* Do not insert each row into the hash table, duplicates may appear +if this is enabled, also if this is enabled searching into the hash is +noop because it will be empty. */ +#define TEST_DO_NOT_INSERT_INTO_THE_HASH_TABLE +#endif + +#define MAX_ALLOWED_FOR_STORAGE(cache) \ + (TRX_I_S_MEM_LIMIT \ + - (cache)->mem_allocd) + +#define MAX_ALLOWED_FOR_ALLOC(cache) \ + (TRX_I_S_MEM_LIMIT \ + - (cache)->mem_allocd \ + - ha_storage_get_size((cache)->storage)) + +/* Memory for each table in the intermediate buffer is allocated in +separate chunks. These chunks are considered to be concatenated to +represent one flat array of rows. */ +typedef struct i_s_mem_chunk_struct { + ulint offset; /* offset, in number of rows */ + ulint rows_allocd; /* the size of this chunk, in number + of rows */ + void* base; /* start of the chunk */ +} i_s_mem_chunk_t; + +/* This represents one table's cache. */ +typedef struct i_s_table_cache_struct { + ulint rows_used; /* number of used rows */ + ulint rows_allocd; /* number of allocated rows */ + ulint row_size; /* size of a single row */ + i_s_mem_chunk_t chunks[MEM_CHUNKS_IN_TABLE_CACHE]; /* array of + memory chunks that stores the + rows */ +} i_s_table_cache_t; + +/* This structure describes the intermediate buffer */ +struct trx_i_s_cache_struct { + rw_lock_t rw_lock; /* read-write lock protecting + the rest of this structure */ + ullint last_read; /* last time the cache was read; + measured in microseconds since + epoch */ + mutex_t last_read_mutex;/* mutex protecting the + last_read member - it is updated + inside a shared lock of the + rw_lock member */ + i_s_table_cache_t innodb_trx; /* innodb_trx table */ + i_s_table_cache_t innodb_locks; /* innodb_locks table */ + i_s_table_cache_t innodb_lock_waits;/* innodb_lock_waits table */ +/* the hash table size is LOCKS_HASH_CELLS_NUM * sizeof(void*) bytes */ +#define LOCKS_HASH_CELLS_NUM 10000 + hash_table_t* locks_hash; /* hash table used to eliminate + duplicate entries in the + innodb_locks table */ +#define CACHE_STORAGE_INITIAL_SIZE 1024 +#define CACHE_STORAGE_HASH_CELLS 2048 + ha_storage_t* storage; /* storage for external volatile + data that can possibly not be + available later, when we release + the kernel mutex */ + ulint mem_allocd; /* the amount of memory + allocated with mem_alloc*() */ + ibool is_truncated; /* this is TRUE if the memory + limit was hit and thus the data + in the cache is truncated */ +}; + +/* This is the intermediate buffer where data needed to fill the +INFORMATION SCHEMA tables is fetched and later retrieved by the C++ +code in handler/i_s.cc. */ +static trx_i_s_cache_t trx_i_s_cache_static; +UNIV_INTERN trx_i_s_cache_t* trx_i_s_cache = &trx_i_s_cache_static; + +/*********************************************************************** +For a record lock that is in waiting state retrieves the only bit that +is set, for a table lock returns ULINT_UNDEFINED. */ +static +ulint +wait_lock_get_heap_no( +/*==================*/ + /* out: record number within the heap */ + const lock_t* lock) /* in: lock */ +{ + ulint ret; + + switch (lock_get_type(lock)) { + case LOCK_REC: + ret = lock_rec_find_set_bit(lock); + ut_a(ret != ULINT_UNDEFINED); + break; + case LOCK_TABLE: + ret = ULINT_UNDEFINED; + break; + default: + ut_error; + } + + return(ret); +} + +/*********************************************************************** +Initializes the members of a table cache. */ +static +void +table_cache_init( +/*=============*/ + i_s_table_cache_t* table_cache, /* out: table cache */ + size_t row_size) /* in: the size of a + row */ +{ + ulint i; + + table_cache->rows_used = 0; + table_cache->rows_allocd = 0; + table_cache->row_size = row_size; + + for (i = 0; i < MEM_CHUNKS_IN_TABLE_CACHE; i++) { + + /* the memory is actually allocated in + table_cache_create_empty_row() */ + table_cache->chunks[i].base = NULL; + } +} + +/*********************************************************************** +Returns an empty row from a table cache. The row is allocated if no more +empty rows are available. The number of used rows is incremented. +If the memory limit is hit then NULL is returned and nothing is +allocated. */ +static +void* +table_cache_create_empty_row( +/*=========================*/ + /* out: empty row, or + NULL if out of memory */ + i_s_table_cache_t* table_cache, /* in/out: table cache */ + trx_i_s_cache_t* cache) /* in/out: cache to record + how many bytes are + allocated */ +{ + ulint i; + void* row; + + ut_a(table_cache->rows_used <= table_cache->rows_allocd); + + if (table_cache->rows_used == table_cache->rows_allocd) { + + /* rows_used == rows_allocd means that new chunk needs + to be allocated: either no more empty rows in the + last allocated chunk or nothing has been allocated yet + (rows_num == rows_allocd == 0); */ + + i_s_mem_chunk_t* chunk; + ulint req_bytes; + ulint got_bytes; + ulint req_rows; + ulint got_rows; + + /* find the first not allocated chunk */ + for (i = 0; i < MEM_CHUNKS_IN_TABLE_CACHE; i++) { + + if (table_cache->chunks[i].base == NULL) { + + break; + } + } + + /* i == MEM_CHUNKS_IN_TABLE_CACHE means that all chunks + have been allocated :-X */ + ut_a(i < MEM_CHUNKS_IN_TABLE_CACHE); + + /* allocate the chunk we just found */ + + if (i == 0) { + + /* first chunk, nothing is allocated yet */ + req_rows = TABLE_CACHE_INITIAL_ROWSNUM; + } else { + + /* Memory is increased by the formula + new = old + old / 2; We are trying not to be + aggressive here (= using the common new = old * 2) + because the allocated memory will not be freed + until InnoDB exit (it is reused). So it is better + to once allocate the memory in more steps, but + have less unused/wasted memory than to use less + steps in allocation (which is done once in a + lifetime) but end up with lots of unused/wasted + memory. */ + req_rows = table_cache->rows_allocd / 2; + } + req_bytes = req_rows * table_cache->row_size; + + if (req_bytes > MAX_ALLOWED_FOR_ALLOC(cache)) { + + return(NULL); + } + + chunk = &table_cache->chunks[i]; + + chunk->base = mem_alloc2(req_bytes, &got_bytes); + + got_rows = got_bytes / table_cache->row_size; + + cache->mem_allocd += got_bytes; + +#if 0 + printf("allocating chunk %d req bytes=%lu, got bytes=%lu, " + "row size=%lu, " + "req rows=%lu, got rows=%lu\n", + i, req_bytes, got_bytes, + table_cache->row_size, + req_rows, got_rows); +#endif + + chunk->rows_allocd = got_rows; + + table_cache->rows_allocd += got_rows; + + /* adjust the offset of the next chunk */ + if (i < MEM_CHUNKS_IN_TABLE_CACHE - 1) { + + table_cache->chunks[i + 1].offset + = chunk->offset + chunk->rows_allocd; + } + + /* return the first empty row in the newly allocated + chunk */ + row = chunk->base; + } else { + + char* chunk_start; + ulint offset; + + /* there is an empty row, no need to allocate new + chunks */ + + /* find the first chunk that contains allocated but + empty/unused rows */ + for (i = 0; i < MEM_CHUNKS_IN_TABLE_CACHE; i++) { + + if (table_cache->chunks[i].offset + + table_cache->chunks[i].rows_allocd + > table_cache->rows_used) { + + break; + } + } + + /* i == MEM_CHUNKS_IN_TABLE_CACHE means that all chunks + are full, but + table_cache->rows_used != table_cache->rows_allocd means + exactly the opposite - there are allocated but + empty/unused rows :-X */ + ut_a(i < MEM_CHUNKS_IN_TABLE_CACHE); + + chunk_start = (char*) table_cache->chunks[i].base; + offset = table_cache->rows_used + - table_cache->chunks[i].offset; + + row = chunk_start + offset * table_cache->row_size; + } + + table_cache->rows_used++; + + return(row); +} + +/*********************************************************************** +Fills i_s_trx_row_t object. +If memory can not be allocated then FALSE is returned. */ +static +ibool +fill_trx_row( +/*=========*/ + /* out: FALSE if + allocation fails */ + i_s_trx_row_t* row, /* out: result object + that's filled */ + const trx_t* trx, /* in: transaction to + get data from */ + const i_s_locks_row_t* requested_lock_row,/* in: pointer to the + corresponding row in + innodb_locks if trx is + waiting or NULL if trx + is not waiting */ + trx_i_s_cache_t* cache) /* in/out: cache into + which to copy volatile + strings */ +{ + row->trx_id = trx_get_id(trx); + row->trx_started = (ib_time_t) trx->start_time; + row->trx_state = trx_get_que_state_str(trx); + + if (trx->wait_lock != NULL) { + + ut_a(requested_lock_row != NULL); + + row->requested_lock_row = requested_lock_row; + row->trx_wait_started = (ib_time_t) trx->wait_started; + } else { + + ut_a(requested_lock_row == NULL); + + row->requested_lock_row = NULL; + row->trx_wait_started = 0; + } + + row->trx_weight = (ullint) ut_conv_dulint_to_longlong(TRX_WEIGHT(trx)); + + if (trx->mysql_thd != NULL) { + row->trx_mysql_thread_id + = thd_get_thread_id(trx->mysql_thd); + } else { + /* For internal transactions e.g., purge and transactions + being recovered at startup there is no associated MySQL + thread data structure. */ + row->trx_mysql_thread_id = 0; + } + + if (trx->mysql_query_str != NULL && *trx->mysql_query_str != NULL) { + + if (strlen(*trx->mysql_query_str) + > TRX_I_S_TRX_QUERY_MAX_LEN) { + + char query[TRX_I_S_TRX_QUERY_MAX_LEN + 1]; + + memcpy(query, *trx->mysql_query_str, + TRX_I_S_TRX_QUERY_MAX_LEN); + query[TRX_I_S_TRX_QUERY_MAX_LEN] = '\0'; + + row->trx_query = ha_storage_put_memlim( + cache->storage, query, + TRX_I_S_TRX_QUERY_MAX_LEN + 1, + MAX_ALLOWED_FOR_STORAGE(cache)); + } else { + + row->trx_query = ha_storage_put_str_memlim( + cache->storage, *trx->mysql_query_str, + MAX_ALLOWED_FOR_STORAGE(cache)); + } + + if (row->trx_query == NULL) { + + return(FALSE); + } + } else { + + row->trx_query = NULL; + } + + return(TRUE); +} + +/*********************************************************************** +Format the nth field of "rec" and put it in "buf". The result is always +'\0'-terminated. Returns the number of bytes that were written to "buf" +(including the terminating '\0'). */ +static +ulint +put_nth_field( +/*==========*/ + /* out: end of the result */ + char* buf, /* out: buffer */ + ulint buf_size,/* in: buffer size in bytes */ + ulint n, /* in: number of field */ + const dict_index_t* index, /* in: index */ + const rec_t* rec, /* in: record */ + const ulint* offsets)/* in: record offsets, returned + by rec_get_offsets() */ +{ + const byte* data; + ulint data_len; + dict_field_t* dict_field; + ulint ret; + + ut_ad(rec_offs_validate(rec, NULL, offsets)); + + if (buf_size == 0) { + + return(0); + } + + ret = 0; + + if (n > 0) { + /* we must append ", " before the actual data */ + + if (buf_size < 3) { + + buf[0] = '\0'; + return(1); + } + + memcpy(buf, ", ", 3); + + buf += 2; + buf_size -= 2; + ret += 2; + } + + /* now buf_size >= 1 */ + + data = rec_get_nth_field(rec, offsets, n, &data_len); + + dict_field = dict_index_get_nth_field(index, n); + + ret += row_raw_format((const char*) data, data_len, + dict_field, buf, buf_size); + + return(ret); +} + +/*********************************************************************** +Fills the "lock_data" member of i_s_locks_row_t object. +If memory can not be allocated then FALSE is returned. */ +static +ibool +fill_lock_data( +/*===========*/ + /* out: FALSE if allocation fails */ + const char** lock_data,/* out: "lock_data" to fill */ + const lock_t* lock, /* in: lock used to find the data */ + ulint heap_no,/* in: rec num used to find the data */ + trx_i_s_cache_t* cache) /* in/out: cache where to store + volatile data */ +{ + mtr_t mtr; + + const buf_block_t* block; + const page_t* page; + const rec_t* rec; + + ut_a(lock_get_type(lock) == LOCK_REC); + + mtr_start(&mtr); + + block = buf_page_try_get(lock_rec_get_space_id(lock), + lock_rec_get_page_no(lock), + &mtr); + + if (block == NULL) { + + *lock_data = NULL; + + mtr_commit(&mtr); + + return(TRUE); + } + + page = (const page_t*) buf_block_get_frame(block); + + rec = page_find_rec_with_heap_no(page, heap_no); + + if (page_rec_is_infimum(rec)) { + + *lock_data = ha_storage_put_str_memlim( + cache->storage, "infimum pseudo-record", + MAX_ALLOWED_FOR_STORAGE(cache)); + } else if (page_rec_is_supremum(rec)) { + + *lock_data = ha_storage_put_str_memlim( + cache->storage, "supremum pseudo-record", + MAX_ALLOWED_FOR_STORAGE(cache)); + } else { + + const dict_index_t* index; + ulint n_fields; + mem_heap_t* heap; + ulint offsets_onstack[REC_OFFS_NORMAL_SIZE]; + ulint* offsets; + char buf[TRX_I_S_LOCK_DATA_MAX_LEN]; + ulint buf_used; + ulint i; + + rec_offs_init(offsets_onstack); + offsets = offsets_onstack; + + index = lock_rec_get_index(lock); + + n_fields = dict_index_get_n_unique(index); + + ut_a(n_fields > 0); + + heap = NULL; + offsets = rec_get_offsets(rec, index, offsets, n_fields, + &heap); + + /* format and store the data */ + + buf_used = 0; + for (i = 0; i < n_fields; i++) { + + buf_used += put_nth_field( + buf + buf_used, sizeof(buf) - buf_used, + i, index, rec, offsets) - 1; + } + + *lock_data = (const char*) ha_storage_put_memlim( + cache->storage, buf, buf_used + 1, + MAX_ALLOWED_FOR_STORAGE(cache)); + + if (UNIV_UNLIKELY(heap != NULL)) { + + /* this means that rec_get_offsets() has created a new + heap and has stored offsets in it; check that this is + really the case and free the heap */ + ut_a(offsets != offsets_onstack); + mem_heap_free(heap); + } + } + + mtr_commit(&mtr); + + if (*lock_data == NULL) { + + return(FALSE); + } + + return(TRUE); +} + +/*********************************************************************** +Fills i_s_locks_row_t object. Returns its first argument. +If memory can not be allocated then FALSE is returned. */ +static +ibool +fill_locks_row( +/*===========*/ + /* out: FALSE if allocation fails */ + i_s_locks_row_t* row, /* out: result object that's filled */ + const lock_t* lock, /* in: lock to get data from */ + ulint heap_no,/* in: lock's record number + or ULINT_UNDEFINED if the lock + is a table lock */ + trx_i_s_cache_t* cache) /* in/out: cache into which to copy + volatile strings */ +{ + row->lock_trx_id = lock_get_trx_id(lock); + row->lock_mode = lock_get_mode_str(lock); + row->lock_type = lock_get_type_str(lock); + + row->lock_table = ha_storage_put_str_memlim( + cache->storage, lock_get_table_name(lock), + MAX_ALLOWED_FOR_STORAGE(cache)); + + /* memory could not be allocated */ + if (row->lock_table == NULL) { + + return(FALSE); + } + + switch (lock_get_type(lock)) { + case LOCK_REC: + row->lock_index = ha_storage_put_str_memlim( + cache->storage, lock_rec_get_index_name(lock), + MAX_ALLOWED_FOR_STORAGE(cache)); + + /* memory could not be allocated */ + if (row->lock_index == NULL) { + + return(FALSE); + } + + row->lock_space = lock_rec_get_space_id(lock); + row->lock_page = lock_rec_get_page_no(lock); + row->lock_rec = heap_no; + + if (!fill_lock_data(&row->lock_data, lock, heap_no, cache)) { + + /* memory could not be allocated */ + return(FALSE); + } + + break; + case LOCK_TABLE: + row->lock_index = NULL; + + row->lock_space = ULINT_UNDEFINED; + row->lock_page = ULINT_UNDEFINED; + row->lock_rec = ULINT_UNDEFINED; + + row->lock_data = NULL; + + break; + default: + ut_error; + } + + row->lock_table_id = lock_get_table_id(lock); + + row->hash_chain.value = row; + + return(TRUE); +} + +/*********************************************************************** +Fills i_s_lock_waits_row_t object. Returns its first argument. */ +static +i_s_lock_waits_row_t* +fill_lock_waits_row( +/*================*/ + /* out: result object + that's filled */ + i_s_lock_waits_row_t* row, /* out: result object + that's filled */ + const i_s_locks_row_t* requested_lock_row,/* in: pointer to the + relevant requested lock + row in innodb_locks */ + const i_s_locks_row_t* blocking_lock_row)/* in: pointer to the + relevant blocking lock + row in innodb_locks */ +{ + row->requested_lock_row = requested_lock_row; + row->blocking_lock_row = blocking_lock_row; + + return(row); +} + +/*********************************************************************** +Calculates a hash fold for a lock. For a record lock the fold is +calculated from 4 elements, which uniquely identify a lock at a given +point in time: transaction id, space id, page number, record number. +For a table lock the fold is table's id. */ +static +ulint +fold_lock( +/*======*/ + /* out: fold */ + const lock_t* lock, /* in: lock object to fold */ + ulint heap_no)/* in: lock's record number + or ULINT_UNDEFINED if the lock + is a table lock */ +{ +#ifdef TEST_LOCK_FOLD_ALWAYS_DIFFERENT + static ulint fold = 0; + + return(fold++); +#else + ulint ret; + + switch (lock_get_type(lock)) { + case LOCK_REC: + ut_a(heap_no != ULINT_UNDEFINED); + + ret = ut_fold_ulint_pair((ulint) lock_get_trx_id(lock), + lock_rec_get_space_id(lock)); + + ret = ut_fold_ulint_pair(ret, + lock_rec_get_page_no(lock)); + + ret = ut_fold_ulint_pair(ret, heap_no); + + break; + case LOCK_TABLE: + /* this check is actually not necessary for continuing + correct operation, but something must have gone wrong if + it fails. */ + ut_a(heap_no == ULINT_UNDEFINED); + + ret = (ulint) lock_get_table_id(lock); + + break; + default: + ut_error; + } + + return(ret); +#endif +} + +/*********************************************************************** +Checks whether i_s_locks_row_t object represents a lock_t object. */ +static +ibool +locks_row_eq_lock( +/*==============*/ + /* out: TRUE if they match */ + const i_s_locks_row_t* row, /* in: innodb_locks row */ + const lock_t* lock, /* in: lock object */ + ulint heap_no)/* in: lock's record number + or ULINT_UNDEFINED if the lock + is a table lock */ +{ +#ifdef TEST_NO_LOCKS_ROW_IS_EVER_EQUAL_TO_LOCK_T + return(0); +#else + switch (lock_get_type(lock)) { + case LOCK_REC: + ut_a(heap_no != ULINT_UNDEFINED); + + return(row->lock_trx_id == lock_get_trx_id(lock) + && row->lock_space == lock_rec_get_space_id(lock) + && row->lock_page == lock_rec_get_page_no(lock) + && row->lock_rec == heap_no); + + case LOCK_TABLE: + /* this check is actually not necessary for continuing + correct operation, but something must have gone wrong if + it fails. */ + ut_a(heap_no == ULINT_UNDEFINED); + + return(row->lock_trx_id == lock_get_trx_id(lock) + && row->lock_table_id == lock_get_table_id(lock)); + + default: + ut_error; + return(FALSE); + } +#endif +} + +/*********************************************************************** +Searches for a row in the innodb_locks cache that has a specified id. +This happens in O(1) time since a hash table is used. Returns pointer to +the row or NULL if none is found. */ +static +i_s_locks_row_t* +search_innodb_locks( +/*================*/ + /* out: row or NULL */ + trx_i_s_cache_t* cache, /* in: cache */ + const lock_t* lock, /* in: lock to search for */ + ulint heap_no)/* in: lock's record number + or ULINT_UNDEFINED if the lock + is a table lock */ +{ + i_s_hash_chain_t* hash_chain; + + HASH_SEARCH( + /* hash_chain->"next" */ + next, + /* the hash table */ + cache->locks_hash, + /* fold */ + fold_lock(lock, heap_no), + /* the type of the next variable */ + i_s_hash_chain_t*, + /* auxiliary variable */ + hash_chain, + /* assertion on every traversed item */ + , + /* this determines if we have found the lock */ + locks_row_eq_lock(hash_chain->value, lock, heap_no)); + + if (hash_chain == NULL) { + + return(NULL); + } + /* else */ + + return(hash_chain->value); +} + +/*********************************************************************** +Adds new element to the locks cache, enlarging it if necessary. +Returns a pointer to the added row. If the row is already present then +no row is added and a pointer to the existing row is returned. +If row can not be allocated then NULL is returned. */ +static +i_s_locks_row_t* +add_lock_to_cache( +/*==============*/ + /* out: row */ + trx_i_s_cache_t* cache, /* in/out: cache */ + const lock_t* lock, /* in: the element to add */ + ulint heap_no)/* in: lock's record number + or ULINT_UNDEFINED if the lock + is a table lock */ +{ + i_s_locks_row_t* dst_row; + +#ifdef TEST_ADD_EACH_LOCKS_ROW_MANY_TIMES + ulint i; + for (i = 0; i < 10000; i++) { +#endif +#ifndef TEST_DO_NOT_CHECK_FOR_DUPLICATE_ROWS + /* quit if this lock is already present */ + dst_row = search_innodb_locks(cache, lock, heap_no); + if (dst_row != NULL) { + + return(dst_row); + } +#endif + + dst_row = (i_s_locks_row_t*) + table_cache_create_empty_row(&cache->innodb_locks, cache); + + /* memory could not be allocated */ + if (dst_row == NULL) { + + return(NULL); + } + + if (!fill_locks_row(dst_row, lock, heap_no, cache)) { + + /* memory could not be allocated */ + cache->innodb_locks.rows_used--; + return(NULL); + } + +#ifndef TEST_DO_NOT_INSERT_INTO_THE_HASH_TABLE + HASH_INSERT( + /* the type used in the hash chain */ + i_s_hash_chain_t, + /* hash_chain->"next" */ + next, + /* the hash table */ + cache->locks_hash, + /* fold */ + fold_lock(lock, heap_no), + /* add this data to the hash */ + &dst_row->hash_chain); +#endif +#ifdef TEST_ADD_EACH_LOCKS_ROW_MANY_TIMES + } /* for()-loop */ +#endif + + return(dst_row); +} + +/*********************************************************************** +Adds new pair of locks to the lock waits cache. +If memory can not be allocated then FALSE is returned. */ +static +ibool +add_lock_wait_to_cache( +/*===================*/ + /* out: FALSE if + allocation fails */ + trx_i_s_cache_t* cache, /* in/out: cache */ + const i_s_locks_row_t* requested_lock_row,/* in: pointer to the + relevant requested lock + row in innodb_locks */ + const i_s_locks_row_t* blocking_lock_row)/* in: pointer to the + relevant blocking lock + row in innodb_locks */ +{ + i_s_lock_waits_row_t* dst_row; + + dst_row = (i_s_lock_waits_row_t*) + table_cache_create_empty_row(&cache->innodb_lock_waits, + cache); + + /* memory could not be allocated */ + if (dst_row == NULL) { + + return(FALSE); + } + + fill_lock_waits_row(dst_row, requested_lock_row, blocking_lock_row); + + return(TRUE); +} + +/*********************************************************************** +Adds transaction's relevant (important) locks to cache. +If the transaction is waiting, then the wait lock is added to +innodb_locks and a pointer to the added row is returned in +requested_lock_row, otherwise requested_lock_row is set to NULL. +If rows can not be allocated then FALSE is returned and the value of +requested_lock_row is undefined. */ +static +ibool +add_trx_relevant_locks_to_cache( +/*============================*/ + /* out: FALSE if allocation fails */ + trx_i_s_cache_t* cache, /* in/out: cache */ + const trx_t* trx, /* in: transaction */ + i_s_locks_row_t** requested_lock_row)/* out: pointer to the + requested lock row, or NULL or + undefined */ +{ + ut_ad(mutex_own(&kernel_mutex)); + + /* If transaction is waiting we add the wait lock and all locks + from another transactions that are blocking the wait lock. */ + if (trx->que_state == TRX_QUE_LOCK_WAIT) { + + const lock_t* curr_lock; + ulint wait_lock_heap_no; + i_s_locks_row_t* blocking_lock_row; + lock_queue_iterator_t iter; + + ut_a(trx->wait_lock != NULL); + + wait_lock_heap_no + = wait_lock_get_heap_no(trx->wait_lock); + + /* add the requested lock */ + *requested_lock_row + = add_lock_to_cache(cache, trx->wait_lock, + wait_lock_heap_no); + + /* memory could not be allocated */ + if (*requested_lock_row == NULL) { + + return(FALSE); + } + + /* then iterate over the locks before the wait lock and + add the ones that are blocking it */ + + lock_queue_iterator_reset(&iter, trx->wait_lock, + ULINT_UNDEFINED); + + curr_lock = lock_queue_iterator_get_prev(&iter); + while (curr_lock != NULL) { + + if (lock_has_to_wait(trx->wait_lock, + curr_lock)) { + + /* add the lock that is + blocking trx->wait_lock */ + blocking_lock_row + = add_lock_to_cache( + cache, curr_lock, + /* heap_no is the same + for the wait and waited + locks */ + wait_lock_heap_no); + + /* memory could not be allocated */ + if (blocking_lock_row == NULL) { + + return(FALSE); + } + + /* add the relation between both locks + to innodb_lock_waits */ + if (!add_lock_wait_to_cache( + cache, *requested_lock_row, + blocking_lock_row)) { + + /* memory could not be allocated */ + return(FALSE); + } + } + + curr_lock = lock_queue_iterator_get_prev(&iter); + } + } else { + + *requested_lock_row = NULL; + } + + return(TRUE); +} + +/*********************************************************************** +Checks if the cache can safely be updated. */ +static +ibool +can_cache_be_updated( +/*=================*/ + trx_i_s_cache_t* cache) /* in: cache */ +{ + ullint now; + +/* The minimum time that a cache must not be updated after it has been +read for the last time; measured in microseconds. We use this technique +to ensure that SELECTs which join several INFORMATION SCHEMA tables read +the same version of the cache. */ +#define CACHE_MIN_IDLE_TIME_US 100000 /* 0.1 sec */ + + /* Here we read cache->last_read without acquiring its mutex + because last_read is only updated when a shared rw lock on the + whole cache is being held (see trx_i_s_cache_end_read()) and + we are currently holding an exclusive rw lock on the cache. + So it is not possible for last_read to be updated while we are + reading it. */ + +#ifdef UNIV_SYNC_DEBUG + ut_a(rw_lock_own(&cache->rw_lock, RW_LOCK_EX)); +#endif + + now = ut_time_us(NULL); + if (now - cache->last_read > CACHE_MIN_IDLE_TIME_US) { + + return(TRUE); + } + + return(FALSE); +} + +/*********************************************************************** +Declare a cache empty, preparing it to be filled up. Not all resources +are freed because they can be reused. */ +static +void +trx_i_s_cache_clear( +/*================*/ + trx_i_s_cache_t* cache) /* out: cache to clear */ +{ + cache->innodb_trx.rows_used = 0; + cache->innodb_locks.rows_used = 0; + cache->innodb_lock_waits.rows_used = 0; + + hash_table_clear(cache->locks_hash); + + ha_storage_empty(&cache->storage); +} + +/*********************************************************************** +Fetches the data needed to fill the 3 INFORMATION SCHEMA tables into the +table cache buffer. Cache must be locked for write. */ +static +void +fetch_data_into_cache( +/*==================*/ + trx_i_s_cache_t* cache) /* in/out: cache */ +{ + trx_t* trx; + i_s_trx_row_t* trx_row; + i_s_locks_row_t* requested_lock_row; + + ut_ad(mutex_own(&kernel_mutex)); + + trx_i_s_cache_clear(cache); + + /* We iterate over the list of all transactions and add each one + to innodb_trx's cache. We also add all locks that are relevant + to each transaction into innodb_locks' and innodb_lock_waits' + caches. */ + + for (trx = UT_LIST_GET_FIRST(trx_sys->trx_list); + trx != NULL; + trx = UT_LIST_GET_NEXT(trx_list, trx)) { + + if (!add_trx_relevant_locks_to_cache(cache, trx, + &requested_lock_row)) { + + cache->is_truncated = TRUE; + return; + } + + trx_row = (i_s_trx_row_t*) + table_cache_create_empty_row(&cache->innodb_trx, + cache); + + /* memory could not be allocated */ + if (trx_row == NULL) { + + cache->is_truncated = TRUE; + return; + } + + if (!fill_trx_row(trx_row, trx, requested_lock_row, cache)) { + + /* memory could not be allocated */ + cache->innodb_trx.rows_used--; + cache->is_truncated = TRUE; + return; + } + } + + cache->is_truncated = FALSE; +} + +/*********************************************************************** +Update the transactions cache if it has not been read for some time. +Called from handler/i_s.cc. */ +UNIV_INTERN +int +trx_i_s_possibly_fetch_data_into_cache( +/*===================================*/ + /* out: 0 - fetched, 1 - not */ + trx_i_s_cache_t* cache) /* in/out: cache */ +{ + if (!can_cache_be_updated(cache)) { + + return(1); + } + + /* We are going to access trx->query in all transactions */ + innobase_mysql_prepare_print_arbitrary_thd(); + + /* We need to read trx_sys and record/table lock queues */ + mutex_enter(&kernel_mutex); + + fetch_data_into_cache(cache); + + mutex_exit(&kernel_mutex); + + innobase_mysql_end_print_arbitrary_thd(); + + return(0); +} + +/*********************************************************************** +Returns TRUE if the data in the cache is truncated due to the memory +limit posed by TRX_I_S_MEM_LIMIT. */ +UNIV_INTERN +ibool +trx_i_s_cache_is_truncated( +/*=======================*/ + /* out: TRUE if truncated */ + trx_i_s_cache_t* cache) /* in: cache */ +{ + return(cache->is_truncated); +} + +/*********************************************************************** +Initialize INFORMATION SCHEMA trx related cache. */ +UNIV_INTERN +void +trx_i_s_cache_init( +/*===============*/ + trx_i_s_cache_t* cache) /* out: cache to init */ +{ + /* The latching is done in the following order: + acquire trx_i_s_cache_t::rw_lock, X + acquire kernel_mutex + release kernel_mutex + release trx_i_s_cache_t::rw_lock + acquire trx_i_s_cache_t::rw_lock, S + acquire trx_i_s_cache_t::last_read_mutex + release trx_i_s_cache_t::last_read_mutex + release trx_i_s_cache_t::rw_lock */ + + rw_lock_create(&cache->rw_lock, SYNC_TRX_I_S_RWLOCK); + + cache->last_read = 0; + + mutex_create(&cache->last_read_mutex, SYNC_TRX_I_S_LAST_READ); + + table_cache_init(&cache->innodb_trx, sizeof(i_s_trx_row_t)); + table_cache_init(&cache->innodb_locks, sizeof(i_s_locks_row_t)); + table_cache_init(&cache->innodb_lock_waits, + sizeof(i_s_lock_waits_row_t)); + + cache->locks_hash = hash_create(LOCKS_HASH_CELLS_NUM); + + cache->storage = ha_storage_create(CACHE_STORAGE_INITIAL_SIZE, + CACHE_STORAGE_HASH_CELLS); + + cache->mem_allocd = 0; + + cache->is_truncated = FALSE; +} + +/*********************************************************************** +Issue a shared/read lock on the tables cache. */ +UNIV_INTERN +void +trx_i_s_cache_start_read( +/*=====================*/ + trx_i_s_cache_t* cache) /* in: cache */ +{ + rw_lock_s_lock(&cache->rw_lock); +} + +/*********************************************************************** +Release a shared/read lock on the tables cache. */ +UNIV_INTERN +void +trx_i_s_cache_end_read( +/*===================*/ + trx_i_s_cache_t* cache) /* in: cache */ +{ + ullint now; + +#ifdef UNIV_SYNC_DEBUG + ut_a(rw_lock_own(&cache->rw_lock, RW_LOCK_SHARED)); +#endif + + /* update cache last read time */ + now = ut_time_us(NULL); + mutex_enter(&cache->last_read_mutex); + cache->last_read = now; + mutex_exit(&cache->last_read_mutex); + + rw_lock_s_unlock(&cache->rw_lock); +} + +/*********************************************************************** +Issue an exclusive/write lock on the tables cache. */ +UNIV_INTERN +void +trx_i_s_cache_start_write( +/*======================*/ + trx_i_s_cache_t* cache) /* in: cache */ +{ + rw_lock_x_lock(&cache->rw_lock); +} + +/*********************************************************************** +Release an exclusive/write lock on the tables cache. */ +UNIV_INTERN +void +trx_i_s_cache_end_write( +/*====================*/ + trx_i_s_cache_t* cache) /* in: cache */ +{ +#ifdef UNIV_SYNC_DEBUG + ut_a(rw_lock_own(&cache->rw_lock, RW_LOCK_EX)); +#endif + + rw_lock_x_unlock(&cache->rw_lock); +} + +/*********************************************************************** +Selects a INFORMATION SCHEMA table cache from the whole cache. */ +static +i_s_table_cache_t* +cache_select_table( +/*===============*/ + /* out: table cache */ + trx_i_s_cache_t* cache, /* in: whole cache */ + enum i_s_table table) /* in: which table */ +{ + i_s_table_cache_t* table_cache; + +#ifdef UNIV_SYNC_DEBUG + ut_a(rw_lock_own(&cache->rw_lock, RW_LOCK_SHARED) + || rw_lock_own(&cache->rw_lock, RW_LOCK_EX)); +#endif + + switch (table) { + case I_S_INNODB_TRX: + table_cache = &cache->innodb_trx; + break; + case I_S_INNODB_LOCKS: + table_cache = &cache->innodb_locks; + break; + case I_S_INNODB_LOCK_WAITS: + table_cache = &cache->innodb_lock_waits; + break; + default: + ut_error; + } + + return(table_cache); +} + +/*********************************************************************** +Retrieves the number of used rows in the cache for a given +INFORMATION SCHEMA table. */ +UNIV_INTERN +ulint +trx_i_s_cache_get_rows_used( +/*========================*/ + /* out: number of rows */ + trx_i_s_cache_t* cache, /* in: cache */ + enum i_s_table table) /* in: which table */ +{ + i_s_table_cache_t* table_cache; + + table_cache = cache_select_table(cache, table); + + return(table_cache->rows_used); +} + +/*********************************************************************** +Retrieves the nth row (zero-based) in the cache for a given +INFORMATION SCHEMA table. */ +UNIV_INTERN +void* +trx_i_s_cache_get_nth_row( +/*======================*/ + /* out: row */ + trx_i_s_cache_t* cache, /* in: cache */ + enum i_s_table table, /* in: which table */ + ulint n) /* in: row number */ +{ + i_s_table_cache_t* table_cache; + ulint i; + void* row; + + table_cache = cache_select_table(cache, table); + + ut_a(n < table_cache->rows_used); + + row = NULL; + + for (i = 0; i < MEM_CHUNKS_IN_TABLE_CACHE; i++) { + + if (table_cache->chunks[i].offset + + table_cache->chunks[i].rows_allocd > n) { + + row = (char*) table_cache->chunks[i].base + + (n - table_cache->chunks[i].offset) + * table_cache->row_size; + break; + } + } + + ut_a(row != NULL); + + return(row); +} + +/*********************************************************************** +Crafts a lock id string from a i_s_locks_row_t object. Returns its +second argument. This function aborts if there is not enough space in +lock_id. Be sure to provide at least TRX_I_S_LOCK_ID_MAX_LEN + 1 if you +want to be 100% sure that it will not abort. */ +UNIV_INTERN +char* +trx_i_s_create_lock_id( +/*===================*/ + /* out: resulting lock id */ + const i_s_locks_row_t* row, /* in: innodb_locks row */ + char* lock_id,/* out: resulting lock_id */ + ulint lock_id_size)/* in: size of the lock id + buffer */ +{ + int res_len; + + /* please adjust TRX_I_S_LOCK_ID_MAX_LEN if you change this */ + + if (row->lock_space != ULINT_UNDEFINED) { + /* record lock */ + res_len = ut_snprintf(lock_id, lock_id_size, + TRX_ID_FMT ":%lu:%lu:%lu", + row->lock_trx_id, row->lock_space, + row->lock_page, row->lock_rec); + } else { + /* table lock */ + res_len = ut_snprintf(lock_id, lock_id_size, + TRX_ID_FMT ":%llu", + row->lock_trx_id, + row->lock_table_id); + } + + /* the typecast is safe because snprintf(3) never returns + negative result */ + ut_a(res_len >= 0); + ut_a((ulint) res_len < lock_id_size); + + return(lock_id); +} diff --git a/storage/xtradb/trx/trx0purge.c b/storage/xtradb/trx/trx0purge.c new file mode 100644 index 00000000000..7a2a27a94ff --- /dev/null +++ b/storage/xtradb/trx/trx0purge.c @@ -0,0 +1,1175 @@ +/***************************************************************************** + +Copyright (c) 1996, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/****************************************************** +Purge old versions + +Created 3/26/1996 Heikki Tuuri +*******************************************************/ + +#include "trx0purge.h" + +#ifdef UNIV_NONINL +#include "trx0purge.ic" +#endif + +#include "fsp0fsp.h" +#include "mach0data.h" +#include "trx0rseg.h" +#include "trx0trx.h" +#include "trx0roll.h" +#include "read0read.h" +#include "fut0fut.h" +#include "que0que.h" +#include "row0purge.h" +#include "row0upd.h" +#include "trx0rec.h" +#include "srv0que.h" +#include "os0thread.h" + +/* The global data structure coordinating a purge */ +UNIV_INTERN trx_purge_t* purge_sys = NULL; + +/* A dummy undo record used as a return value when we have a whole undo log +which needs no purge */ +UNIV_INTERN trx_undo_rec_t trx_purge_dummy_rec; + +/********************************************************************* +Checks if trx_id is >= purge_view: then it is guaranteed that its update +undo log still exists in the system. */ +UNIV_INTERN +ibool +trx_purge_update_undo_must_exist( +/*=============================*/ + /* out: TRUE if is sure that it is preserved, also + if the function returns FALSE, it is possible that + the undo log still exists in the system */ + dulint trx_id) /* in: transaction id */ +{ +#ifdef UNIV_SYNC_DEBUG + ut_ad(rw_lock_own(&(purge_sys->latch), RW_LOCK_SHARED)); +#endif /* UNIV_SYNC_DEBUG */ + + if (!read_view_sees_trx_id(purge_sys->view, trx_id)) { + + return(TRUE); + } + + return(FALSE); +} + +/*=================== PURGE RECORD ARRAY =============================*/ + +/*********************************************************************** +Stores info of an undo log record during a purge. */ +static +trx_undo_inf_t* +trx_purge_arr_store_info( +/*=====================*/ + /* out: pointer to the storage cell */ + dulint trx_no, /* in: transaction number */ + dulint undo_no)/* in: undo number */ +{ + trx_undo_inf_t* cell; + trx_undo_arr_t* arr; + ulint i; + + arr = purge_sys->arr; + + for (i = 0;; i++) { + cell = trx_undo_arr_get_nth_info(arr, i); + + if (!(cell->in_use)) { + /* Not in use, we may store here */ + cell->undo_no = undo_no; + cell->trx_no = trx_no; + cell->in_use = TRUE; + + arr->n_used++; + + return(cell); + } + } +} + +/*********************************************************************** +Removes info of an undo log record during a purge. */ +UNIV_INLINE +void +trx_purge_arr_remove_info( +/*======================*/ + trx_undo_inf_t* cell) /* in: pointer to the storage cell */ +{ + trx_undo_arr_t* arr; + + arr = purge_sys->arr; + + cell->in_use = FALSE; + + ut_ad(arr->n_used > 0); + + arr->n_used--; +} + +/*********************************************************************** +Gets the biggest pair of a trx number and an undo number in a purge array. */ +static +void +trx_purge_arr_get_biggest( +/*======================*/ + trx_undo_arr_t* arr, /* in: purge array */ + dulint* trx_no, /* out: transaction number: ut_dulint_zero + if array is empty */ + dulint* undo_no)/* out: undo number */ +{ + trx_undo_inf_t* cell; + dulint pair_trx_no; + dulint pair_undo_no; + int trx_cmp; + ulint n_used; + ulint i; + ulint n; + + n = 0; + n_used = arr->n_used; + pair_trx_no = ut_dulint_zero; + pair_undo_no = ut_dulint_zero; + + for (i = 0;; i++) { + cell = trx_undo_arr_get_nth_info(arr, i); + + if (cell->in_use) { + n++; + trx_cmp = ut_dulint_cmp(cell->trx_no, pair_trx_no); + + if ((trx_cmp > 0) + || ((trx_cmp == 0) + && (ut_dulint_cmp(cell->undo_no, + pair_undo_no) >= 0))) { + + pair_trx_no = cell->trx_no; + pair_undo_no = cell->undo_no; + } + } + + if (n == n_used) { + *trx_no = pair_trx_no; + *undo_no = pair_undo_no; + + return; + } + } +} + +/******************************************************************** +Builds a purge 'query' graph. The actual purge is performed by executing +this query graph. */ +static +que_t* +trx_purge_graph_build(void) +/*=======================*/ + /* out, own: the query graph */ +{ + mem_heap_t* heap; + que_fork_t* fork; + que_thr_t* thr; + /* que_thr_t* thr2; */ + + heap = mem_heap_create(512); + fork = que_fork_create(NULL, NULL, QUE_FORK_PURGE, heap); + fork->trx = purge_sys->trx; + + thr = que_thr_create(fork, heap); + + thr->child = row_purge_node_create(thr, heap); + + /* thr2 = que_thr_create(fork, fork, heap); + + thr2->child = row_purge_node_create(fork, thr2, heap); */ + + return(fork); +} + +/************************************************************************ +Creates the global purge system control structure and inits the history +mutex. */ +UNIV_INTERN +void +trx_purge_sys_create(void) +/*======================*/ +{ + ut_ad(mutex_own(&kernel_mutex)); + + purge_sys = mem_alloc(sizeof(trx_purge_t)); + + purge_sys->state = TRX_STOP_PURGE; + + purge_sys->n_pages_handled = 0; + + purge_sys->purge_trx_no = ut_dulint_zero; + purge_sys->purge_undo_no = ut_dulint_zero; + purge_sys->next_stored = FALSE; + + rw_lock_create(&purge_sys->latch, SYNC_PURGE_LATCH); + + mutex_create(&purge_sys->mutex, SYNC_PURGE_SYS); + + purge_sys->heap = mem_heap_create(256); + + purge_sys->arr = trx_undo_arr_create(); + + purge_sys->sess = sess_open(); + + purge_sys->trx = purge_sys->sess->trx; + + purge_sys->trx->is_purge = 1; + + ut_a(trx_start_low(purge_sys->trx, ULINT_UNDEFINED)); + + purge_sys->query = trx_purge_graph_build(); + + purge_sys->view = read_view_oldest_copy_or_open_new(ut_dulint_zero, + purge_sys->heap); +} + +/*================ UNDO LOG HISTORY LIST =============================*/ + +/************************************************************************ +Adds the update undo log as the first log in the history list. Removes the +update undo log segment from the rseg slot if it is too big for reuse. */ +UNIV_INTERN +void +trx_purge_add_update_undo_to_history( +/*=================================*/ + trx_t* trx, /* in: transaction */ + page_t* undo_page, /* in: update undo log header page, + x-latched */ + mtr_t* mtr) /* in: mtr */ +{ + trx_undo_t* undo; + trx_rseg_t* rseg; + trx_rsegf_t* rseg_header; + trx_usegf_t* seg_header; + trx_ulogf_t* undo_header; + trx_upagef_t* page_header; + ulint hist_size; + + undo = trx->update_undo; + + ut_ad(undo); + + rseg = undo->rseg; + + ut_ad(mutex_own(&(rseg->mutex))); + + rseg_header = trx_rsegf_get(rseg->space, rseg->zip_size, + rseg->page_no, mtr); + + undo_header = undo_page + undo->hdr_offset; + seg_header = undo_page + TRX_UNDO_SEG_HDR; + page_header = undo_page + TRX_UNDO_PAGE_HDR; + + if (undo->state != TRX_UNDO_CACHED) { + /* The undo log segment will not be reused */ + + if (undo->id >= TRX_RSEG_N_SLOTS) { + fprintf(stderr, + "InnoDB: Error: undo->id is %lu\n", + (ulong) undo->id); + ut_error; + } + + trx_rsegf_set_nth_undo(rseg_header, undo->id, FIL_NULL, mtr); + + hist_size = mtr_read_ulint(rseg_header + TRX_RSEG_HISTORY_SIZE, + MLOG_4BYTES, mtr); + ut_ad(undo->size == flst_get_len( + seg_header + TRX_UNDO_PAGE_LIST, mtr)); + + mlog_write_ulint(rseg_header + TRX_RSEG_HISTORY_SIZE, + hist_size + undo->size, MLOG_4BYTES, mtr); + } + + /* Add the log as the first in the history list */ + flst_add_first(rseg_header + TRX_RSEG_HISTORY, + undo_header + TRX_UNDO_HISTORY_NODE, mtr); + mutex_enter(&kernel_mutex); + trx_sys->rseg_history_len++; + mutex_exit(&kernel_mutex); + + /* Write the trx number to the undo log header */ + mlog_write_dulint(undo_header + TRX_UNDO_TRX_NO, trx->no, mtr); + /* Write information about delete markings to the undo log header */ + + if (!undo->del_marks) { + mlog_write_ulint(undo_header + TRX_UNDO_DEL_MARKS, FALSE, + MLOG_2BYTES, mtr); + } + + if (rseg->last_page_no == FIL_NULL) { + + rseg->last_page_no = undo->hdr_page_no; + rseg->last_offset = undo->hdr_offset; + rseg->last_trx_no = trx->no; + rseg->last_del_marks = undo->del_marks; + } +} + +/************************************************************************** +Frees an undo log segment which is in the history list. Cuts the end of the +history list at the youngest undo log in this segment. */ +static +void +trx_purge_free_segment( +/*===================*/ + trx_rseg_t* rseg, /* in: rollback segment */ + fil_addr_t hdr_addr, /* in: the file address of log_hdr */ + ulint n_removed_logs) /* in: count of how many undo logs we + will cut off from the end of the + history list */ +{ + page_t* undo_page; + trx_rsegf_t* rseg_hdr; + trx_ulogf_t* log_hdr; + trx_usegf_t* seg_hdr; + ibool freed; + ulint seg_size; + ulint hist_size; + ibool marked = FALSE; + mtr_t mtr; + + /* fputs("Freeing an update undo log segment\n", stderr); */ + + ut_ad(mutex_own(&(purge_sys->mutex))); +loop: + mtr_start(&mtr); + mutex_enter(&(rseg->mutex)); + + rseg_hdr = trx_rsegf_get(rseg->space, rseg->zip_size, + rseg->page_no, &mtr); + + undo_page = trx_undo_page_get(rseg->space, rseg->zip_size, + hdr_addr.page, &mtr); + seg_hdr = undo_page + TRX_UNDO_SEG_HDR; + log_hdr = undo_page + hdr_addr.boffset; + + /* Mark the last undo log totally purged, so that if the system + crashes, the tail of the undo log will not get accessed again. The + list of pages in the undo log tail gets inconsistent during the + freeing of the segment, and therefore purge should not try to access + them again. */ + + if (!marked) { + mlog_write_ulint(log_hdr + TRX_UNDO_DEL_MARKS, FALSE, + MLOG_2BYTES, &mtr); + marked = TRUE; + } + + freed = fseg_free_step_not_header(seg_hdr + TRX_UNDO_FSEG_HEADER, + &mtr); + if (!freed) { + mutex_exit(&(rseg->mutex)); + mtr_commit(&mtr); + + goto loop; + } + + /* The page list may now be inconsistent, but the length field + stored in the list base node tells us how big it was before we + started the freeing. */ + + seg_size = flst_get_len(seg_hdr + TRX_UNDO_PAGE_LIST, &mtr); + + /* We may free the undo log segment header page; it must be freed + within the same mtr as the undo log header is removed from the + history list: otherwise, in case of a database crash, the segment + could become inaccessible garbage in the file space. */ + + flst_cut_end(rseg_hdr + TRX_RSEG_HISTORY, + log_hdr + TRX_UNDO_HISTORY_NODE, n_removed_logs, &mtr); + + mutex_enter(&kernel_mutex); + ut_ad(trx_sys->rseg_history_len >= n_removed_logs); + trx_sys->rseg_history_len -= n_removed_logs; + mutex_exit(&kernel_mutex); + + freed = FALSE; + + while (!freed) { + /* Here we assume that a file segment with just the header + page can be freed in a few steps, so that the buffer pool + is not flooded with bufferfixed pages: see the note in + fsp0fsp.c. */ + + freed = fseg_free_step(seg_hdr + TRX_UNDO_FSEG_HEADER, + &mtr); + } + + hist_size = mtr_read_ulint(rseg_hdr + TRX_RSEG_HISTORY_SIZE, + MLOG_4BYTES, &mtr); + ut_ad(hist_size >= seg_size); + + mlog_write_ulint(rseg_hdr + TRX_RSEG_HISTORY_SIZE, + hist_size - seg_size, MLOG_4BYTES, &mtr); + + ut_ad(rseg->curr_size >= seg_size); + + rseg->curr_size -= seg_size; + + mutex_exit(&(rseg->mutex)); + + mtr_commit(&mtr); +} + +/************************************************************************ +Removes unnecessary history data from a rollback segment. */ +static +void +trx_purge_truncate_rseg_history( +/*============================*/ + trx_rseg_t* rseg, /* in: rollback segment */ + dulint limit_trx_no, /* in: remove update undo logs whose + trx number is < limit_trx_no */ + dulint limit_undo_no) /* in: if transaction number is equal + to limit_trx_no, truncate undo records + with undo number < limit_undo_no */ +{ + fil_addr_t hdr_addr; + fil_addr_t prev_hdr_addr; + trx_rsegf_t* rseg_hdr; + page_t* undo_page; + trx_ulogf_t* log_hdr; + trx_usegf_t* seg_hdr; + int cmp; + ulint n_removed_logs = 0; + mtr_t mtr; + + ut_ad(mutex_own(&(purge_sys->mutex))); + + mtr_start(&mtr); + mutex_enter(&(rseg->mutex)); + + rseg_hdr = trx_rsegf_get(rseg->space, rseg->zip_size, + rseg->page_no, &mtr); + + hdr_addr = trx_purge_get_log_from_hist( + flst_get_last(rseg_hdr + TRX_RSEG_HISTORY, &mtr)); +loop: + if (hdr_addr.page == FIL_NULL) { + + mutex_exit(&(rseg->mutex)); + + mtr_commit(&mtr); + + return; + } + + undo_page = trx_undo_page_get(rseg->space, rseg->zip_size, + hdr_addr.page, &mtr); + + log_hdr = undo_page + hdr_addr.boffset; + + cmp = ut_dulint_cmp(mach_read_from_8(log_hdr + TRX_UNDO_TRX_NO), + limit_trx_no); + if (cmp == 0) { + trx_undo_truncate_start(rseg, rseg->space, hdr_addr.page, + hdr_addr.boffset, limit_undo_no); + } + + if (cmp >= 0) { + mutex_enter(&kernel_mutex); + ut_a(trx_sys->rseg_history_len >= n_removed_logs); + trx_sys->rseg_history_len -= n_removed_logs; + mutex_exit(&kernel_mutex); + + flst_truncate_end(rseg_hdr + TRX_RSEG_HISTORY, + log_hdr + TRX_UNDO_HISTORY_NODE, + n_removed_logs, &mtr); + + mutex_exit(&(rseg->mutex)); + mtr_commit(&mtr); + + return; + } + + prev_hdr_addr = trx_purge_get_log_from_hist( + flst_get_prev_addr(log_hdr + TRX_UNDO_HISTORY_NODE, &mtr)); + n_removed_logs++; + + seg_hdr = undo_page + TRX_UNDO_SEG_HDR; + + if ((mach_read_from_2(seg_hdr + TRX_UNDO_STATE) == TRX_UNDO_TO_PURGE) + && (mach_read_from_2(log_hdr + TRX_UNDO_NEXT_LOG) == 0)) { + + /* We can free the whole log segment */ + + mutex_exit(&(rseg->mutex)); + mtr_commit(&mtr); + + trx_purge_free_segment(rseg, hdr_addr, n_removed_logs); + + n_removed_logs = 0; + } else { + mutex_exit(&(rseg->mutex)); + mtr_commit(&mtr); + } + + mtr_start(&mtr); + mutex_enter(&(rseg->mutex)); + + rseg_hdr = trx_rsegf_get(rseg->space, rseg->zip_size, + rseg->page_no, &mtr); + + hdr_addr = prev_hdr_addr; + + goto loop; +} + +/************************************************************************ +Removes unnecessary history data from rollback segments. NOTE that when this +function is called, the caller must not have any latches on undo log pages! */ +static +void +trx_purge_truncate_history(void) +/*============================*/ +{ + trx_rseg_t* rseg; + dulint limit_trx_no; + dulint limit_undo_no; + + ut_ad(mutex_own(&(purge_sys->mutex))); + + trx_purge_arr_get_biggest(purge_sys->arr, &limit_trx_no, + &limit_undo_no); + + if (ut_dulint_is_zero(limit_trx_no)) { + + limit_trx_no = purge_sys->purge_trx_no; + limit_undo_no = purge_sys->purge_undo_no; + } + + /* We play safe and set the truncate limit at most to the purge view + low_limit number, though this is not necessary */ + + if (ut_dulint_cmp(limit_trx_no, purge_sys->view->low_limit_no) >= 0) { + limit_trx_no = purge_sys->view->low_limit_no; + limit_undo_no = ut_dulint_zero; + } + + ut_ad((ut_dulint_cmp(limit_trx_no, + purge_sys->view->low_limit_no) <= 0)); + + rseg = UT_LIST_GET_FIRST(trx_sys->rseg_list); + + while (rseg) { + trx_purge_truncate_rseg_history(rseg, limit_trx_no, + limit_undo_no); + rseg = UT_LIST_GET_NEXT(rseg_list, rseg); + } +} + +/************************************************************************ +Does a truncate if the purge array is empty. NOTE that when this function is +called, the caller must not have any latches on undo log pages! */ +UNIV_INLINE +ibool +trx_purge_truncate_if_arr_empty(void) +/*=================================*/ + /* out: TRUE if array empty */ +{ + ut_ad(mutex_own(&(purge_sys->mutex))); + + if (purge_sys->arr->n_used == 0) { + + trx_purge_truncate_history(); + + return(TRUE); + } + + return(FALSE); +} + +/*************************************************************************** +Updates the last not yet purged history log info in rseg when we have purged +a whole undo log. Advances also purge_sys->purge_trx_no past the purged log. */ +static +void +trx_purge_rseg_get_next_history_log( +/*================================*/ + trx_rseg_t* rseg) /* in: rollback segment */ +{ + page_t* undo_page; + trx_ulogf_t* log_hdr; + trx_usegf_t* seg_hdr; + fil_addr_t prev_log_addr; + dulint trx_no; + ibool del_marks; + mtr_t mtr; + + ut_ad(mutex_own(&(purge_sys->mutex))); + + mutex_enter(&(rseg->mutex)); + + ut_a(rseg->last_page_no != FIL_NULL); + + purge_sys->purge_trx_no = ut_dulint_add(rseg->last_trx_no, 1); + purge_sys->purge_undo_no = ut_dulint_zero; + purge_sys->next_stored = FALSE; + + mtr_start(&mtr); + + undo_page = trx_undo_page_get_s_latched(rseg->space, rseg->zip_size, + rseg->last_page_no, &mtr); + log_hdr = undo_page + rseg->last_offset; + seg_hdr = undo_page + TRX_UNDO_SEG_HDR; + + /* Increase the purge page count by one for every handled log */ + + purge_sys->n_pages_handled++; + + prev_log_addr = trx_purge_get_log_from_hist( + flst_get_prev_addr(log_hdr + TRX_UNDO_HISTORY_NODE, &mtr)); + if (prev_log_addr.page == FIL_NULL) { + /* No logs left in the history list */ + + rseg->last_page_no = FIL_NULL; + + mutex_exit(&(rseg->mutex)); + mtr_commit(&mtr); + + mutex_enter(&kernel_mutex); + + /* Add debug code to track history list corruption reported + on the MySQL mailing list on Nov 9, 2004. The fut0lst.c + file-based list was corrupt. The prev node pointer was + FIL_NULL, even though the list length was over 8 million nodes! + We assume that purge truncates the history list in moderate + size pieces, and if we here reach the head of the list, the + list cannot be longer than 20 000 undo logs now. */ + + if (trx_sys->rseg_history_len > 20000) { + ut_print_timestamp(stderr); + fprintf(stderr, + " InnoDB: Warning: purge reached the" + " head of the history list,\n" + "InnoDB: but its length is still" + " reported as %lu! Make a detailed bug\n" + "InnoDB: report, and submit it" + " to http://bugs.mysql.com\n", + (ulong) trx_sys->rseg_history_len); + } + + mutex_exit(&kernel_mutex); + + return; + } + + mutex_exit(&(rseg->mutex)); + mtr_commit(&mtr); + + /* Read the trx number and del marks from the previous log header */ + mtr_start(&mtr); + + log_hdr = trx_undo_page_get_s_latched(rseg->space, rseg->zip_size, + prev_log_addr.page, &mtr) + + prev_log_addr.boffset; + + trx_no = mach_read_from_8(log_hdr + TRX_UNDO_TRX_NO); + + del_marks = mach_read_from_2(log_hdr + TRX_UNDO_DEL_MARKS); + + mtr_commit(&mtr); + + mutex_enter(&(rseg->mutex)); + + rseg->last_page_no = prev_log_addr.page; + rseg->last_offset = prev_log_addr.boffset; + rseg->last_trx_no = trx_no; + rseg->last_del_marks = del_marks; + + mutex_exit(&(rseg->mutex)); +} + +/*************************************************************************** +Chooses the next undo log to purge and updates the info in purge_sys. This +function is used to initialize purge_sys when the next record to purge is +not known, and also to update the purge system info on the next record when +purge has handled the whole undo log for a transaction. */ +static +void +trx_purge_choose_next_log(void) +/*===========================*/ +{ + trx_undo_rec_t* rec; + trx_rseg_t* rseg; + trx_rseg_t* min_rseg; + dulint min_trx_no; + ulint space = 0; /* remove warning (??? bug ???) */ + ulint zip_size = 0; + ulint page_no = 0; /* remove warning (??? bug ???) */ + ulint offset = 0; /* remove warning (??? bug ???) */ + mtr_t mtr; + + ut_ad(mutex_own(&(purge_sys->mutex))); + ut_ad(purge_sys->next_stored == FALSE); + + rseg = UT_LIST_GET_FIRST(trx_sys->rseg_list); + + min_trx_no = ut_dulint_max; + + min_rseg = NULL; + + while (rseg) { + mutex_enter(&(rseg->mutex)); + + if (rseg->last_page_no != FIL_NULL) { + + if ((min_rseg == NULL) + || (ut_dulint_cmp(min_trx_no, + rseg->last_trx_no) > 0)) { + + min_rseg = rseg; + min_trx_no = rseg->last_trx_no; + space = rseg->space; + zip_size = rseg->zip_size; + ut_a(space == 0); /* We assume in purge of + externally stored fields + that space id == 0 */ + page_no = rseg->last_page_no; + offset = rseg->last_offset; + } + } + + mutex_exit(&(rseg->mutex)); + + rseg = UT_LIST_GET_NEXT(rseg_list, rseg); + } + + if (min_rseg == NULL) { + + return; + } + + mtr_start(&mtr); + + if (!min_rseg->last_del_marks) { + /* No need to purge this log */ + + rec = &trx_purge_dummy_rec; + } else { + rec = trx_undo_get_first_rec(space, zip_size, page_no, offset, + RW_S_LATCH, &mtr); + if (rec == NULL) { + /* Undo log empty */ + + rec = &trx_purge_dummy_rec; + } + } + + purge_sys->next_stored = TRUE; + purge_sys->rseg = min_rseg; + + purge_sys->hdr_page_no = page_no; + purge_sys->hdr_offset = offset; + + purge_sys->purge_trx_no = min_trx_no; + + if (rec == &trx_purge_dummy_rec) { + + purge_sys->purge_undo_no = ut_dulint_zero; + purge_sys->page_no = page_no; + purge_sys->offset = 0; + } else { + purge_sys->purge_undo_no = trx_undo_rec_get_undo_no(rec); + + purge_sys->page_no = page_get_page_no(page_align(rec)); + purge_sys->offset = page_offset(rec); + } + + mtr_commit(&mtr); +} + +/*************************************************************************** +Gets the next record to purge and updates the info in the purge system. */ +static +trx_undo_rec_t* +trx_purge_get_next_rec( +/*===================*/ + /* out: copy of an undo log record or + pointer to the dummy undo log record */ + mem_heap_t* heap) /* in: memory heap where copied */ +{ + trx_undo_rec_t* rec; + trx_undo_rec_t* rec_copy; + trx_undo_rec_t* rec2; + trx_undo_rec_t* next_rec; + page_t* undo_page; + page_t* page; + ulint offset; + ulint page_no; + ulint space; + ulint zip_size; + ulint type; + ulint cmpl_info; + mtr_t mtr; + + ut_ad(mutex_own(&(purge_sys->mutex))); + ut_ad(purge_sys->next_stored); + + space = purge_sys->rseg->space; + zip_size = purge_sys->rseg->zip_size; + page_no = purge_sys->page_no; + offset = purge_sys->offset; + + if (offset == 0) { + /* It is the dummy undo log record, which means that there is + no need to purge this undo log */ + + trx_purge_rseg_get_next_history_log(purge_sys->rseg); + + /* Look for the next undo log and record to purge */ + + trx_purge_choose_next_log(); + + return(&trx_purge_dummy_rec); + } + + mtr_start(&mtr); + + undo_page = trx_undo_page_get_s_latched(space, zip_size, + page_no, &mtr); + rec = undo_page + offset; + + rec2 = rec; + + for (;;) { + /* Try first to find the next record which requires a purge + operation from the same page of the same undo log */ + + next_rec = trx_undo_page_get_next_rec(rec2, + purge_sys->hdr_page_no, + purge_sys->hdr_offset); + if (next_rec == NULL) { + rec2 = trx_undo_get_next_rec( + rec2, purge_sys->hdr_page_no, + purge_sys->hdr_offset, &mtr); + break; + } + + rec2 = next_rec; + + type = trx_undo_rec_get_type(rec2); + + if (type == TRX_UNDO_DEL_MARK_REC) { + + break; + } + + cmpl_info = trx_undo_rec_get_cmpl_info(rec2); + + if (trx_undo_rec_get_extern_storage(rec2)) { + break; + } + + if ((type == TRX_UNDO_UPD_EXIST_REC) + && !(cmpl_info & UPD_NODE_NO_ORD_CHANGE)) { + break; + } + } + + if (rec2 == NULL) { + mtr_commit(&mtr); + + trx_purge_rseg_get_next_history_log(purge_sys->rseg); + + /* Look for the next undo log and record to purge */ + + trx_purge_choose_next_log(); + + mtr_start(&mtr); + + undo_page = trx_undo_page_get_s_latched(space, zip_size, + page_no, &mtr); + + rec = undo_page + offset; + } else { + page = page_align(rec2); + + purge_sys->purge_undo_no = trx_undo_rec_get_undo_no(rec2); + purge_sys->page_no = page_get_page_no(page); + purge_sys->offset = rec2 - page; + + if (undo_page != page) { + /* We advance to a new page of the undo log: */ + purge_sys->n_pages_handled++; + } + } + + rec_copy = trx_undo_rec_copy(rec, heap); + + mtr_commit(&mtr); + + return(rec_copy); +} + +/************************************************************************ +Fetches the next undo log record from the history list to purge. It must be +released with the corresponding release function. */ +UNIV_INTERN +trx_undo_rec_t* +trx_purge_fetch_next_rec( +/*=====================*/ + /* out: copy of an undo log record or + pointer to the dummy undo log record + &trx_purge_dummy_rec, if the whole undo log + can skipped in purge; NULL if none left */ + dulint* roll_ptr,/* out: roll pointer to undo record */ + trx_undo_inf_t** cell, /* out: storage cell for the record in the + purge array */ + mem_heap_t* heap) /* in: memory heap where copied */ +{ + trx_undo_rec_t* undo_rec; + + mutex_enter(&(purge_sys->mutex)); + + if (purge_sys->state == TRX_STOP_PURGE) { + trx_purge_truncate_if_arr_empty(); + + mutex_exit(&(purge_sys->mutex)); + + return(NULL); + } + + if (!purge_sys->next_stored) { + trx_purge_choose_next_log(); + + if (!purge_sys->next_stored) { + purge_sys->state = TRX_STOP_PURGE; + + trx_purge_truncate_if_arr_empty(); + + if (srv_print_thread_releases) { + fprintf(stderr, + "Purge: No logs left in the" + " history list; pages handled %lu\n", + (ulong) purge_sys->n_pages_handled); + } + + mutex_exit(&(purge_sys->mutex)); + + return(NULL); + } + } + + if (purge_sys->n_pages_handled >= purge_sys->handle_limit) { + + purge_sys->state = TRX_STOP_PURGE; + + trx_purge_truncate_if_arr_empty(); + + mutex_exit(&(purge_sys->mutex)); + + return(NULL); + } + + if (ut_dulint_cmp(purge_sys->purge_trx_no, + purge_sys->view->low_limit_no) >= 0) { + purge_sys->state = TRX_STOP_PURGE; + + trx_purge_truncate_if_arr_empty(); + + mutex_exit(&(purge_sys->mutex)); + + return(NULL); + } + + /* fprintf(stderr, "Thread %lu purging trx %lu undo record %lu\n", + os_thread_get_curr_id(), + ut_dulint_get_low(purge_sys->purge_trx_no), + ut_dulint_get_low(purge_sys->purge_undo_no)); */ + + *roll_ptr = trx_undo_build_roll_ptr(FALSE, (purge_sys->rseg)->id, + purge_sys->page_no, + purge_sys->offset); + + *cell = trx_purge_arr_store_info(purge_sys->purge_trx_no, + purge_sys->purge_undo_no); + + ut_ad(ut_dulint_cmp(purge_sys->purge_trx_no, + (purge_sys->view)->low_limit_no) < 0); + + /* The following call will advance the stored values of purge_trx_no + and purge_undo_no, therefore we had to store them first */ + + undo_rec = trx_purge_get_next_rec(heap); + + mutex_exit(&(purge_sys->mutex)); + + return(undo_rec); +} + +/*********************************************************************** +Releases a reserved purge undo record. */ +UNIV_INTERN +void +trx_purge_rec_release( +/*==================*/ + trx_undo_inf_t* cell) /* in: storage cell */ +{ + trx_undo_arr_t* arr; + + mutex_enter(&(purge_sys->mutex)); + + arr = purge_sys->arr; + + trx_purge_arr_remove_info(cell); + + mutex_exit(&(purge_sys->mutex)); +} + +/*********************************************************************** +This function runs a purge batch. */ +UNIV_INTERN +ulint +trx_purge(void) +/*===========*/ + /* out: number of undo log pages handled in + the batch */ +{ + que_thr_t* thr; + /* que_thr_t* thr2; */ + ulint old_pages_handled; + + mutex_enter(&(purge_sys->mutex)); + + if (purge_sys->trx->n_active_thrs > 0) { + + mutex_exit(&(purge_sys->mutex)); + + /* Should not happen */ + + ut_error; + + return(0); + } + + rw_lock_x_lock(&(purge_sys->latch)); + + mutex_enter(&kernel_mutex); + + /* Close and free the old purge view */ + + read_view_close(purge_sys->view); + purge_sys->view = NULL; + mem_heap_empty(purge_sys->heap); + + /* Determine how much data manipulation language (DML) statements + need to be delayed in order to reduce the lagging of the purge + thread. */ + srv_dml_needed_delay = 0; /* in microseconds; default: no delay */ + + /* If we cannot advance the 'purge view' because of an old + 'consistent read view', then the DML statements cannot be delayed. + Also, srv_max_purge_lag <= 0 means 'infinity'. */ + if (srv_max_purge_lag > 0 + && !UT_LIST_GET_LAST(trx_sys->view_list)) { + float ratio = (float) trx_sys->rseg_history_len + / srv_max_purge_lag; + if (ratio > ULINT_MAX / 10000) { + /* Avoid overflow: maximum delay is 4295 seconds */ + srv_dml_needed_delay = ULINT_MAX; + } else if (ratio > 1) { + /* If the history list length exceeds the + innodb_max_purge_lag, the + data manipulation statements are delayed + by at least 5000 microseconds. */ + srv_dml_needed_delay = (ulint) ((ratio - .5) * 10000); + } + } + + purge_sys->view = read_view_oldest_copy_or_open_new(ut_dulint_zero, + purge_sys->heap); + mutex_exit(&kernel_mutex); + + rw_lock_x_unlock(&(purge_sys->latch)); + + purge_sys->state = TRX_PURGE_ON; + + /* Handle at most 20 undo log pages in one purge batch */ + + purge_sys->handle_limit = purge_sys->n_pages_handled + 20; + + old_pages_handled = purge_sys->n_pages_handled; + + mutex_exit(&(purge_sys->mutex)); + + mutex_enter(&kernel_mutex); + + thr = que_fork_start_command(purge_sys->query); + + ut_ad(thr); + + /* thr2 = que_fork_start_command(purge_sys->query); + + ut_ad(thr2); */ + + + mutex_exit(&kernel_mutex); + + /* srv_que_task_enqueue(thr2); */ + + if (srv_print_thread_releases) { + + fputs("Starting purge\n", stderr); + } + + que_run_threads(thr); + + if (srv_print_thread_releases) { + + fprintf(stderr, + "Purge ends; pages handled %lu\n", + (ulong) purge_sys->n_pages_handled); + } + + return(purge_sys->n_pages_handled - old_pages_handled); +} + +/********************************************************************** +Prints information of the purge system to stderr. */ +UNIV_INTERN +void +trx_purge_sys_print(void) +/*=====================*/ +{ + fprintf(stderr, "InnoDB: Purge system view:\n"); + read_view_print(purge_sys->view); + + fprintf(stderr, "InnoDB: Purge trx n:o " TRX_ID_FMT + ", undo n:o " TRX_ID_FMT "\n", + TRX_ID_PREP_PRINTF(purge_sys->purge_trx_no), + TRX_ID_PREP_PRINTF(purge_sys->purge_undo_no)); + fprintf(stderr, + "InnoDB: Purge next stored %lu, page_no %lu, offset %lu,\n" + "InnoDB: Purge hdr_page_no %lu, hdr_offset %lu\n", + (ulong) purge_sys->next_stored, + (ulong) purge_sys->page_no, + (ulong) purge_sys->offset, + (ulong) purge_sys->hdr_page_no, + (ulong) purge_sys->hdr_offset); +} diff --git a/storage/xtradb/trx/trx0rec.c b/storage/xtradb/trx/trx0rec.c new file mode 100644 index 00000000000..148f93cdbe7 --- /dev/null +++ b/storage/xtradb/trx/trx0rec.c @@ -0,0 +1,1607 @@ +/***************************************************************************** + +Copyright (c) 1996, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/****************************************************** +Transaction undo log record + +Created 3/26/1996 Heikki Tuuri +*******************************************************/ + +#include "trx0rec.h" + +#ifdef UNIV_NONINL +#include "trx0rec.ic" +#endif + +#include "fsp0fsp.h" +#include "mach0data.h" +#include "trx0rseg.h" +#include "trx0trx.h" +#include "trx0undo.h" +#include "dict0dict.h" +#include "ut0mem.h" +#include "row0ext.h" +#include "row0upd.h" +#include "que0que.h" +#include "trx0purge.h" +#include "row0row.h" + +/*=========== UNDO LOG RECORD CREATION AND DECODING ====================*/ + +/************************************************************************** +Writes the mtr log entry of the inserted undo log record on the undo log +page. */ +UNIV_INLINE +void +trx_undof_page_add_undo_rec_log( +/*============================*/ + page_t* undo_page, /* in: undo log page */ + ulint old_free, /* in: start offset of the inserted entry */ + ulint new_free, /* in: end offset of the entry */ + mtr_t* mtr) /* in: mtr */ +{ + byte* log_ptr; + const byte* log_end; + ulint len; + + log_ptr = mlog_open(mtr, 11 + 13 + MLOG_BUF_MARGIN); + + if (log_ptr == NULL) { + + return; + } + + log_end = &log_ptr[11 + 13 + MLOG_BUF_MARGIN]; + log_ptr = mlog_write_initial_log_record_fast( + undo_page, MLOG_UNDO_INSERT, log_ptr, mtr); + len = new_free - old_free - 4; + + mach_write_to_2(log_ptr, len); + log_ptr += 2; + + if (log_ptr + len <= log_end) { + memcpy(log_ptr, undo_page + old_free + 2, len); + mlog_close(mtr, log_ptr + len); + } else { + mlog_close(mtr, log_ptr); + mlog_catenate_string(mtr, undo_page + old_free + 2, len); + } +} + +/*************************************************************** +Parses a redo log record of adding an undo log record. */ +UNIV_INTERN +byte* +trx_undo_parse_add_undo_rec( +/*========================*/ + /* out: end of log record or NULL */ + byte* ptr, /* in: buffer */ + byte* end_ptr,/* in: buffer end */ + page_t* page) /* in: page or NULL */ +{ + ulint len; + byte* rec; + ulint first_free; + + if (end_ptr < ptr + 2) { + + return(NULL); + } + + len = mach_read_from_2(ptr); + ptr += 2; + + if (end_ptr < ptr + len) { + + return(NULL); + } + + if (page == NULL) { + + return(ptr + len); + } + + first_free = mach_read_from_2(page + TRX_UNDO_PAGE_HDR + + TRX_UNDO_PAGE_FREE); + rec = page + first_free; + + mach_write_to_2(rec, first_free + 4 + len); + mach_write_to_2(rec + 2 + len, first_free); + + mach_write_to_2(page + TRX_UNDO_PAGE_HDR + TRX_UNDO_PAGE_FREE, + first_free + 4 + len); + ut_memcpy(rec + 2, ptr, len); + + return(ptr + len); +} + +/************************************************************************** +Calculates the free space left for extending an undo log record. */ +UNIV_INLINE +ulint +trx_undo_left( +/*==========*/ + /* out: bytes left */ + const page_t* page, /* in: undo log page */ + const byte* ptr) /* in: pointer to page */ +{ + /* The '- 10' is a safety margin, in case we have some small + calculation error below */ + + return(UNIV_PAGE_SIZE - (ptr - page) - 10 - FIL_PAGE_DATA_END); +} + +/************************************************************************** +Set the next and previous pointers in the undo page for the undo record +that was written to ptr. Update the first free value by the number of bytes +written for this undo record.*/ +static +ulint +trx_undo_page_set_next_prev_and_add( +/*================================*/ + /* out: offset of the inserted entry + on the page if succeeded, 0 if fail */ + page_t* undo_page, /* in/out: undo log page */ + byte* ptr, /* in: ptr up to where data has been + written on this undo page. */ + mtr_t* mtr) /* in: mtr */ +{ + ulint first_free; /* offset within undo_page */ + ulint end_of_rec; /* offset within undo_page */ + byte* ptr_to_first_free; + /* pointer within undo_page + that points to the next free + offset value within undo_page.*/ + + ut_ad(ptr > undo_page); + ut_ad(ptr < undo_page + UNIV_PAGE_SIZE); + + if (UNIV_UNLIKELY(trx_undo_left(undo_page, ptr) < 2)) { + + return(0); + } + + ptr_to_first_free = undo_page + TRX_UNDO_PAGE_HDR + TRX_UNDO_PAGE_FREE; + + first_free = mach_read_from_2(ptr_to_first_free); + + /* Write offset of the previous undo log record */ + mach_write_to_2(ptr, first_free); + ptr += 2; + + end_of_rec = ptr - undo_page; + + /* Write offset of the next undo log record */ + mach_write_to_2(undo_page + first_free, end_of_rec); + + /* Update the offset to first free undo record */ + mach_write_to_2(ptr_to_first_free, end_of_rec); + + /* Write this log entry to the UNDO log */ + trx_undof_page_add_undo_rec_log(undo_page, first_free, + end_of_rec, mtr); + + return(first_free); +} + +/************************************************************************** +Reports in the undo log of an insert of a clustered index record. */ +static +ulint +trx_undo_page_report_insert( +/*========================*/ + /* out: offset of the inserted entry + on the page if succeed, 0 if fail */ + page_t* undo_page, /* in: undo log page */ + trx_t* trx, /* in: transaction */ + dict_index_t* index, /* in: clustered index */ + const dtuple_t* clust_entry, /* in: index entry which will be + inserted to the clustered index */ + mtr_t* mtr) /* in: mtr */ +{ + ulint first_free; + byte* ptr; + ulint i; + + ut_ad(dict_index_is_clust(index)); + ut_ad(mach_read_from_2(undo_page + TRX_UNDO_PAGE_HDR + + TRX_UNDO_PAGE_TYPE) == TRX_UNDO_INSERT); + + first_free = mach_read_from_2(undo_page + TRX_UNDO_PAGE_HDR + + TRX_UNDO_PAGE_FREE); + ptr = undo_page + first_free; + + ut_ad(first_free <= UNIV_PAGE_SIZE); + + if (trx_undo_left(undo_page, ptr) < 2 + 1 + 11 + 11) { + + /* Not enough space for writing the general parameters */ + + return(0); + } + + /* Reserve 2 bytes for the pointer to the next undo log record */ + ptr += 2; + + /* Store first some general parameters to the undo log */ + *ptr++ = TRX_UNDO_INSERT_REC; + ptr += mach_dulint_write_much_compressed(ptr, trx->undo_no); + ptr += mach_dulint_write_much_compressed(ptr, index->table->id); + /*----------------------------------------*/ + /* Store then the fields required to uniquely determine the record + to be inserted in the clustered index */ + + for (i = 0; i < dict_index_get_n_unique(index); i++) { + + const dfield_t* field = dtuple_get_nth_field(clust_entry, i); + ulint flen = dfield_get_len(field); + + if (trx_undo_left(undo_page, ptr) < 5) { + + return(0); + } + + ptr += mach_write_compressed(ptr, flen); + + if (flen != UNIV_SQL_NULL) { + if (trx_undo_left(undo_page, ptr) < flen) { + + return(0); + } + + ut_memcpy(ptr, dfield_get_data(field), flen); + ptr += flen; + } + } + + return(trx_undo_page_set_next_prev_and_add(undo_page, ptr, mtr)); +} + +/************************************************************************** +Reads from an undo log record the general parameters. */ +UNIV_INTERN +byte* +trx_undo_rec_get_pars( +/*==================*/ + /* out: remaining part of undo log + record after reading these values */ + trx_undo_rec_t* undo_rec, /* in: undo log record */ + ulint* type, /* out: undo record type: + TRX_UNDO_INSERT_REC, ... */ + ulint* cmpl_info, /* out: compiler info, relevant only + for update type records */ + ibool* updated_extern, /* out: TRUE if we updated an + externally stored fild */ + dulint* undo_no, /* out: undo log record number */ + dulint* table_id) /* out: table id */ +{ + byte* ptr; + ulint type_cmpl; + + ptr = undo_rec + 2; + + type_cmpl = mach_read_from_1(ptr); + ptr++; + + if (type_cmpl & TRX_UNDO_UPD_EXTERN) { + *updated_extern = TRUE; + type_cmpl -= TRX_UNDO_UPD_EXTERN; + } else { + *updated_extern = FALSE; + } + + *type = type_cmpl & (TRX_UNDO_CMPL_INFO_MULT - 1); + *cmpl_info = type_cmpl / TRX_UNDO_CMPL_INFO_MULT; + + *undo_no = mach_dulint_read_much_compressed(ptr); + ptr += mach_dulint_get_much_compressed_size(*undo_no); + + *table_id = mach_dulint_read_much_compressed(ptr); + ptr += mach_dulint_get_much_compressed_size(*table_id); + + return(ptr); +} + +/************************************************************************** +Reads from an undo log record a stored column value. */ +static +byte* +trx_undo_rec_get_col_val( +/*=====================*/ + /* out: remaining part of undo log record after + reading these values */ + byte* ptr, /* in: pointer to remaining part of undo log record */ + byte** field, /* out: pointer to stored field */ + ulint* len, /* out: length of the field, or UNIV_SQL_NULL */ + ulint* orig_len)/* out: original length of the locally + stored part of an externally stored column, or 0 */ +{ + *len = mach_read_compressed(ptr); + ptr += mach_get_compressed_size(*len); + + *orig_len = 0; + + switch (*len) { + case UNIV_SQL_NULL: + *field = NULL; + break; + case UNIV_EXTERN_STORAGE_FIELD: + *orig_len = mach_read_compressed(ptr); + ptr += mach_get_compressed_size(*orig_len); + *len = mach_read_compressed(ptr); + ptr += mach_get_compressed_size(*len); + *field = ptr; + ptr += *len; + + ut_ad(*orig_len >= BTR_EXTERN_FIELD_REF_SIZE); + ut_ad(*len > *orig_len); + ut_ad(*len >= REC_MAX_INDEX_COL_LEN + + BTR_EXTERN_FIELD_REF_SIZE); + + *len += UNIV_EXTERN_STORAGE_FIELD; + break; + default: + *field = ptr; + if (*len >= UNIV_EXTERN_STORAGE_FIELD) { + ptr += *len - UNIV_EXTERN_STORAGE_FIELD; + } else { + ptr += *len; + } + } + + return(ptr); +} + +/*********************************************************************** +Builds a row reference from an undo log record. */ +UNIV_INTERN +byte* +trx_undo_rec_get_row_ref( +/*=====================*/ + /* out: pointer to remaining part of undo + record */ + byte* ptr, /* in: remaining part of a copy of an undo log + record, at the start of the row reference; + NOTE that this copy of the undo log record must + be preserved as long as the row reference is + used, as we do NOT copy the data in the + record! */ + dict_index_t* index, /* in: clustered index */ + dtuple_t** ref, /* out, own: row reference */ + mem_heap_t* heap) /* in: memory heap from which the memory + needed is allocated */ +{ + ulint ref_len; + ulint i; + + ut_ad(index && ptr && ref && heap); + ut_a(dict_index_is_clust(index)); + + ref_len = dict_index_get_n_unique(index); + + *ref = dtuple_create(heap, ref_len); + + dict_index_copy_types(*ref, index, ref_len); + + for (i = 0; i < ref_len; i++) { + dfield_t* dfield; + byte* field; + ulint len; + ulint orig_len; + + dfield = dtuple_get_nth_field(*ref, i); + + ptr = trx_undo_rec_get_col_val(ptr, &field, &len, &orig_len); + + dfield_set_data(dfield, field, len); + } + + return(ptr); +} + +/*********************************************************************** +Skips a row reference from an undo log record. */ +UNIV_INTERN +byte* +trx_undo_rec_skip_row_ref( +/*======================*/ + /* out: pointer to remaining part of undo + record */ + byte* ptr, /* in: remaining part in update undo log + record, at the start of the row reference */ + dict_index_t* index) /* in: clustered index */ +{ + ulint ref_len; + ulint i; + + ut_ad(index && ptr); + ut_a(dict_index_is_clust(index)); + + ref_len = dict_index_get_n_unique(index); + + for (i = 0; i < ref_len; i++) { + byte* field; + ulint len; + ulint orig_len; + + ptr = trx_undo_rec_get_col_val(ptr, &field, &len, &orig_len); + } + + return(ptr); +} + +/************************************************************************** +Fetch a prefix of an externally stored column, for writing to the undo log +of an update or delete marking of a clustered index record. */ +static +byte* +trx_undo_page_fetch_ext( +/*====================*/ + /* out: ext_buf */ + byte* ext_buf, /* in: a buffer of + REC_MAX_INDEX_COL_LEN + + BTR_EXTERN_FIELD_REF_SIZE */ + ulint zip_size, /* compressed page size in bytes, + or 0 for uncompressed BLOB */ + const byte* field, /* in: an externally stored column */ + ulint* len) /* in: length of field; + out: used length of ext_buf */ +{ + /* Fetch the BLOB. */ + ulint ext_len = btr_copy_externally_stored_field_prefix( + ext_buf, REC_MAX_INDEX_COL_LEN, zip_size, field, *len); + /* BLOBs should always be nonempty. */ + ut_a(ext_len); + /* Append the BLOB pointer to the prefix. */ + memcpy(ext_buf + ext_len, + field + *len - BTR_EXTERN_FIELD_REF_SIZE, + BTR_EXTERN_FIELD_REF_SIZE); + *len = ext_len + BTR_EXTERN_FIELD_REF_SIZE; + return(ext_buf); +} + +/************************************************************************** +Writes to the undo log a prefix of an externally stored column. */ +static +byte* +trx_undo_page_report_modify_ext( +/*============================*/ + /* out: undo log position */ + byte* ptr, /* in: undo log position, + at least 15 bytes must be available */ + byte* ext_buf, /* in: a buffer of + REC_MAX_INDEX_COL_LEN + + BTR_EXTERN_FIELD_REF_SIZE, + or NULL when should not fetch + a longer prefix */ + ulint zip_size, /* compressed page size in bytes, + or 0 for uncompressed BLOB */ + const byte** field, /* in/out: the locally stored part of + the externally stored column */ + ulint* len) /* in/out: length of field, in bytes */ +{ + if (ext_buf) { + /* If an ordering column is externally stored, we will + have to store a longer prefix of the field. In this + case, write to the log a marker followed by the + original length and the real length of the field. */ + ptr += mach_write_compressed(ptr, UNIV_EXTERN_STORAGE_FIELD); + + ptr += mach_write_compressed(ptr, *len); + + *field = trx_undo_page_fetch_ext(ext_buf, zip_size, + *field, len); + + ptr += mach_write_compressed(ptr, *len); + } else { + ptr += mach_write_compressed(ptr, UNIV_EXTERN_STORAGE_FIELD + + *len); + } + + return(ptr); +} + +/************************************************************************** +Reports in the undo log of an update or delete marking of a clustered index +record. */ +static +ulint +trx_undo_page_report_modify( +/*========================*/ + /* out: byte offset of the inserted + undo log entry on the page if succeed, + 0 if fail */ + page_t* undo_page, /* in: undo log page */ + trx_t* trx, /* in: transaction */ + dict_index_t* index, /* in: clustered index where update or + delete marking is done */ + const rec_t* rec, /* in: clustered index record which + has NOT yet been modified */ + const ulint* offsets, /* in: rec_get_offsets(rec, index) */ + const upd_t* update, /* in: update vector which tells the + columns to be updated; in the case of + a delete, this should be set to NULL */ + ulint cmpl_info, /* in: compiler info on secondary + index updates */ + mtr_t* mtr) /* in: mtr */ +{ + dict_table_t* table; + ulint first_free; + byte* ptr; + const byte* field; + ulint flen; + ulint col_no; + ulint type_cmpl; + byte* type_cmpl_ptr; + ulint i; + dulint trx_id; + ibool ignore_prefix = FALSE; + byte ext_buf[REC_MAX_INDEX_COL_LEN + + BTR_EXTERN_FIELD_REF_SIZE]; + + ut_a(dict_index_is_clust(index)); + ut_ad(rec_offs_validate(rec, index, offsets)); + ut_ad(mach_read_from_2(undo_page + TRX_UNDO_PAGE_HDR + + TRX_UNDO_PAGE_TYPE) == TRX_UNDO_UPDATE); + table = index->table; + + first_free = mach_read_from_2(undo_page + TRX_UNDO_PAGE_HDR + + TRX_UNDO_PAGE_FREE); + ptr = undo_page + first_free; + + ut_ad(first_free <= UNIV_PAGE_SIZE); + + if (trx_undo_left(undo_page, ptr) < 50) { + + /* NOTE: the value 50 must be big enough so that the general + fields written below fit on the undo log page */ + + return(0); + } + + /* Reserve 2 bytes for the pointer to the next undo log record */ + ptr += 2; + + /* Store first some general parameters to the undo log */ + + if (!update) { + type_cmpl = TRX_UNDO_DEL_MARK_REC; + } else if (rec_get_deleted_flag(rec, dict_table_is_comp(table))) { + type_cmpl = TRX_UNDO_UPD_DEL_REC; + /* We are about to update a delete marked record. + We don't typically need the prefix in this case unless + the delete marking is done by the same transaction + (which we check below). */ + ignore_prefix = TRUE; + } else { + type_cmpl = TRX_UNDO_UPD_EXIST_REC; + } + + type_cmpl |= cmpl_info * TRX_UNDO_CMPL_INFO_MULT; + type_cmpl_ptr = ptr; + + *ptr++ = (byte) type_cmpl; + ptr += mach_dulint_write_much_compressed(ptr, trx->undo_no); + + ptr += mach_dulint_write_much_compressed(ptr, table->id); + + /*----------------------------------------*/ + /* Store the state of the info bits */ + + *ptr++ = (byte) rec_get_info_bits(rec, dict_table_is_comp(table)); + + /* Store the values of the system columns */ + field = rec_get_nth_field(rec, offsets, + dict_index_get_sys_col_pos( + index, DATA_TRX_ID), &flen); + ut_ad(flen == DATA_TRX_ID_LEN); + + trx_id = trx_read_trx_id(field); + + /* If it is an update of a delete marked record, then we are + allowed to ignore blob prefixes if the delete marking was done + by some other trx as it must have committed by now for us to + allow an over-write. */ + if (ignore_prefix) { + ignore_prefix = ut_dulint_cmp(trx_id, trx->id) != 0; + } + ptr += mach_dulint_write_compressed(ptr, trx_id); + + field = rec_get_nth_field(rec, offsets, + dict_index_get_sys_col_pos( + index, DATA_ROLL_PTR), &flen); + ut_ad(flen == DATA_ROLL_PTR_LEN); + + ptr += mach_dulint_write_compressed(ptr, trx_read_roll_ptr(field)); + + /*----------------------------------------*/ + /* Store then the fields required to uniquely determine the + record which will be modified in the clustered index */ + + for (i = 0; i < dict_index_get_n_unique(index); i++) { + + field = rec_get_nth_field(rec, offsets, i, &flen); + + /* The ordering columns must not be stored externally. */ + ut_ad(!rec_offs_nth_extern(offsets, i)); + ut_ad(dict_index_get_nth_col(index, i)->ord_part); + + if (trx_undo_left(undo_page, ptr) < 5) { + + return(0); + } + + ptr += mach_write_compressed(ptr, flen); + + if (flen != UNIV_SQL_NULL) { + if (trx_undo_left(undo_page, ptr) < flen) { + + return(0); + } + + ut_memcpy(ptr, field, flen); + ptr += flen; + } + } + + /*----------------------------------------*/ + /* Save to the undo log the old values of the columns to be updated. */ + + if (update) { + if (trx_undo_left(undo_page, ptr) < 5) { + + return(0); + } + + ptr += mach_write_compressed(ptr, upd_get_n_fields(update)); + + for (i = 0; i < upd_get_n_fields(update); i++) { + + ulint pos = upd_get_nth_field(update, i)->field_no; + + /* Write field number to undo log */ + if (trx_undo_left(undo_page, ptr) < 5) { + + return(0); + } + + ptr += mach_write_compressed(ptr, pos); + + /* Save the old value of field */ + field = rec_get_nth_field(rec, offsets, pos, &flen); + + if (trx_undo_left(undo_page, ptr) < 15) { + + return(0); + } + + if (rec_offs_nth_extern(offsets, pos)) { + ptr = trx_undo_page_report_modify_ext( + ptr, + dict_index_get_nth_col(index, pos) + ->ord_part + && !ignore_prefix + && flen < REC_MAX_INDEX_COL_LEN + ? ext_buf : NULL, + dict_table_zip_size(table), + &field, &flen); + + /* Notify purge that it eventually has to + free the old externally stored field */ + + trx->update_undo->del_marks = TRUE; + + *type_cmpl_ptr |= TRX_UNDO_UPD_EXTERN; + } else { + ptr += mach_write_compressed(ptr, flen); + } + + if (flen != UNIV_SQL_NULL) { + if (trx_undo_left(undo_page, ptr) < flen) { + + return(0); + } + + ut_memcpy(ptr, field, flen); + ptr += flen; + } + } + } + + /*----------------------------------------*/ + /* In the case of a delete marking, and also in the case of an update + where any ordering field of any index changes, store the values of all + columns which occur as ordering fields in any index. This info is used + in the purge of old versions where we use it to build and search the + delete marked index records, to look if we can remove them from the + index tree. Note that starting from 4.0.14 also externally stored + fields can be ordering in some index. Starting from 5.2, we no longer + store REC_MAX_INDEX_COL_LEN first bytes to the undo log record, + but we can construct the column prefix fields in the index by + fetching the first page of the BLOB that is pointed to by the + clustered index. This works also in crash recovery, because all pages + (including BLOBs) are recovered before anything is rolled back. */ + + if (!update || !(cmpl_info & UPD_NODE_NO_ORD_CHANGE)) { + byte* old_ptr = ptr; + + trx->update_undo->del_marks = TRUE; + + if (trx_undo_left(undo_page, ptr) < 5) { + + return(0); + } + + /* Reserve 2 bytes to write the number of bytes the stored + fields take in this undo record */ + + ptr += 2; + + for (col_no = 0; col_no < dict_table_get_n_cols(table); + col_no++) { + + const dict_col_t* col + = dict_table_get_nth_col(table, col_no); + + if (col->ord_part) { + ulint pos; + + /* Write field number to undo log */ + if (trx_undo_left(undo_page, ptr) < 5 + 15) { + + return(0); + } + + pos = dict_index_get_nth_col_pos(index, + col_no); + ptr += mach_write_compressed(ptr, pos); + + /* Save the old value of field */ + field = rec_get_nth_field(rec, offsets, pos, + &flen); + + if (rec_offs_nth_extern(offsets, pos)) { + ptr = trx_undo_page_report_modify_ext( + ptr, + flen < REC_MAX_INDEX_COL_LEN + && !ignore_prefix + ? ext_buf : NULL, + dict_table_zip_size(table), + &field, &flen); + } else { + ptr += mach_write_compressed( + ptr, flen); + } + + if (flen != UNIV_SQL_NULL) { + if (trx_undo_left(undo_page, ptr) + < flen) { + + return(0); + } + + ut_memcpy(ptr, field, flen); + ptr += flen; + } + } + } + + mach_write_to_2(old_ptr, ptr - old_ptr); + } + + /*----------------------------------------*/ + /* Write pointers to the previous and the next undo log records */ + if (trx_undo_left(undo_page, ptr) < 2) { + + return(0); + } + + mach_write_to_2(ptr, first_free); + ptr += 2; + mach_write_to_2(undo_page + first_free, ptr - undo_page); + + mach_write_to_2(undo_page + TRX_UNDO_PAGE_HDR + TRX_UNDO_PAGE_FREE, + ptr - undo_page); + + /* Write to the REDO log about this change in the UNDO log */ + + trx_undof_page_add_undo_rec_log(undo_page, first_free, + ptr - undo_page, mtr); + return(first_free); +} + +/************************************************************************** +Reads from an undo log update record the system field values of the old +version. */ +UNIV_INTERN +byte* +trx_undo_update_rec_get_sys_cols( +/*=============================*/ + /* out: remaining part of undo log + record after reading these values */ + byte* ptr, /* in: remaining part of undo log + record after reading general + parameters */ + dulint* trx_id, /* out: trx id */ + dulint* roll_ptr, /* out: roll ptr */ + ulint* info_bits) /* out: info bits state */ +{ + /* Read the state of the info bits */ + *info_bits = mach_read_from_1(ptr); + ptr += 1; + + /* Read the values of the system columns */ + + *trx_id = mach_dulint_read_compressed(ptr); + ptr += mach_dulint_get_compressed_size(*trx_id); + + *roll_ptr = mach_dulint_read_compressed(ptr); + ptr += mach_dulint_get_compressed_size(*roll_ptr); + + return(ptr); +} + +/************************************************************************** +Reads from an update undo log record the number of updated fields. */ +UNIV_INLINE +byte* +trx_undo_update_rec_get_n_upd_fields( +/*=================================*/ + /* out: remaining part of undo log record after + reading this value */ + byte* ptr, /* in: pointer to remaining part of undo log record */ + ulint* n) /* out: number of fields */ +{ + *n = mach_read_compressed(ptr); + ptr += mach_get_compressed_size(*n); + + return(ptr); +} + +/************************************************************************** +Reads from an update undo log record a stored field number. */ +UNIV_INLINE +byte* +trx_undo_update_rec_get_field_no( +/*=============================*/ + /* out: remaining part of undo log record after + reading this value */ + byte* ptr, /* in: pointer to remaining part of undo log record */ + ulint* field_no)/* out: field number */ +{ + *field_no = mach_read_compressed(ptr); + ptr += mach_get_compressed_size(*field_no); + + return(ptr); +} + +/*********************************************************************** +Builds an update vector based on a remaining part of an undo log record. */ +UNIV_INTERN +byte* +trx_undo_update_rec_get_update( +/*===========================*/ + /* out: remaining part of the record, + NULL if an error detected, which means that + the record is corrupted */ + byte* ptr, /* in: remaining part in update undo log + record, after reading the row reference + NOTE that this copy of the undo log record must + be preserved as long as the update vector is + used, as we do NOT copy the data in the + record! */ + dict_index_t* index, /* in: clustered index */ + ulint type, /* in: TRX_UNDO_UPD_EXIST_REC, + TRX_UNDO_UPD_DEL_REC, or + TRX_UNDO_DEL_MARK_REC; in the last case, + only trx id and roll ptr fields are added to + the update vector */ + dulint trx_id, /* in: transaction id from this undo record */ + dulint roll_ptr,/* in: roll pointer from this undo record */ + ulint info_bits,/* in: info bits from this undo record */ + trx_t* trx, /* in: transaction */ + mem_heap_t* heap, /* in: memory heap from which the memory + needed is allocated */ + upd_t** upd) /* out, own: update vector */ +{ + upd_field_t* upd_field; + upd_t* update; + ulint n_fields; + byte* buf; + ulint i; + + ut_a(dict_index_is_clust(index)); + + if (type != TRX_UNDO_DEL_MARK_REC) { + ptr = trx_undo_update_rec_get_n_upd_fields(ptr, &n_fields); + } else { + n_fields = 0; + } + + update = upd_create(n_fields + 2, heap); + + update->info_bits = info_bits; + + /* Store first trx id and roll ptr to update vector */ + + upd_field = upd_get_nth_field(update, n_fields); + buf = mem_heap_alloc(heap, DATA_TRX_ID_LEN); + trx_write_trx_id(buf, trx_id); + + upd_field_set_field_no(upd_field, + dict_index_get_sys_col_pos(index, DATA_TRX_ID), + index, trx); + dfield_set_data(&(upd_field->new_val), buf, DATA_TRX_ID_LEN); + + upd_field = upd_get_nth_field(update, n_fields + 1); + buf = mem_heap_alloc(heap, DATA_ROLL_PTR_LEN); + trx_write_roll_ptr(buf, roll_ptr); + + upd_field_set_field_no( + upd_field, dict_index_get_sys_col_pos(index, DATA_ROLL_PTR), + index, trx); + dfield_set_data(&(upd_field->new_val), buf, DATA_ROLL_PTR_LEN); + + /* Store then the updated ordinary columns to the update vector */ + + for (i = 0; i < n_fields; i++) { + + byte* field; + ulint len; + ulint field_no; + ulint orig_len; + + ptr = trx_undo_update_rec_get_field_no(ptr, &field_no); + + if (field_no >= dict_index_get_n_fields(index)) { + fprintf(stderr, + "InnoDB: Error: trying to access" + " update undo rec field %lu in ", + (ulong) field_no); + dict_index_name_print(stderr, trx, index); + fprintf(stderr, "\n" + "InnoDB: but index has only %lu fields\n" + "InnoDB: Submit a detailed bug report" + " to http://bugs.mysql.com\n" + "InnoDB: Run also CHECK TABLE ", + (ulong) dict_index_get_n_fields(index)); + ut_print_name(stderr, trx, TRUE, index->table_name); + fprintf(stderr, "\n" + "InnoDB: n_fields = %lu, i = %lu, ptr %p\n", + (ulong) n_fields, (ulong) i, ptr); + return(NULL); + } + + upd_field = upd_get_nth_field(update, i); + + upd_field_set_field_no(upd_field, field_no, index, trx); + + ptr = trx_undo_rec_get_col_val(ptr, &field, &len, &orig_len); + + upd_field->orig_len = orig_len; + + if (len == UNIV_SQL_NULL) { + dfield_set_null(&upd_field->new_val); + } else if (len < UNIV_EXTERN_STORAGE_FIELD) { + dfield_set_data(&upd_field->new_val, field, len); + } else { + len -= UNIV_EXTERN_STORAGE_FIELD; + + dfield_set_data(&upd_field->new_val, field, len); + dfield_set_ext(&upd_field->new_val); + } + } + + *upd = update; + + return(ptr); +} + +/*********************************************************************** +Builds a partial row from an update undo log record. It contains the +columns which occur as ordering in any index of the table. */ +UNIV_INTERN +byte* +trx_undo_rec_get_partial_row( +/*=========================*/ + /* out: pointer to remaining part of undo + record */ + byte* ptr, /* in: remaining part in update undo log + record of a suitable type, at the start of + the stored index columns; + NOTE that this copy of the undo log record must + be preserved as long as the partial row is + used, as we do NOT copy the data in the + record! */ + dict_index_t* index, /* in: clustered index */ + dtuple_t** row, /* out, own: partial row */ + ibool ignore_prefix, /* in: flag to indicate if we + expect blob prefixes in undo. Used + only in the assertion. */ + mem_heap_t* heap) /* in: memory heap from which the memory + needed is allocated */ +{ + const byte* end_ptr; + ulint row_len; + + ut_ad(index); + ut_ad(ptr); + ut_ad(row); + ut_ad(heap); + ut_ad(dict_index_is_clust(index)); + + row_len = dict_table_get_n_cols(index->table); + + *row = dtuple_create(heap, row_len); + + dict_table_copy_types(*row, index->table); + + end_ptr = ptr + mach_read_from_2(ptr); + ptr += 2; + + while (ptr != end_ptr) { + dfield_t* dfield; + byte* field; + ulint field_no; + const dict_col_t* col; + ulint col_no; + ulint len; + ulint orig_len; + + ptr = trx_undo_update_rec_get_field_no(ptr, &field_no); + + col = dict_index_get_nth_col(index, field_no); + col_no = dict_col_get_no(col); + + ptr = trx_undo_rec_get_col_val(ptr, &field, &len, &orig_len); + + dfield = dtuple_get_nth_field(*row, col_no); + + dfield_set_data(dfield, field, len); + + if (len != UNIV_SQL_NULL + && len >= UNIV_EXTERN_STORAGE_FIELD) { + dfield_set_len(dfield, + len - UNIV_EXTERN_STORAGE_FIELD); + dfield_set_ext(dfield); + /* If the prefix of this column is indexed, + ensure that enough prefix is stored in the + undo log record. */ + ut_a(ignore_prefix + || !col->ord_part + || dfield_get_len(dfield) + >= REC_MAX_INDEX_COL_LEN + + BTR_EXTERN_FIELD_REF_SIZE); + } + } + + return(ptr); +} + +/*************************************************************************** +Erases the unused undo log page end. */ +static +void +trx_undo_erase_page_end( +/*====================*/ + page_t* undo_page, /* in: undo page whose end to erase */ + mtr_t* mtr) /* in: mtr */ +{ + ulint first_free; + + first_free = mach_read_from_2(undo_page + TRX_UNDO_PAGE_HDR + + TRX_UNDO_PAGE_FREE); + memset(undo_page + first_free, 0xff, + (UNIV_PAGE_SIZE - FIL_PAGE_DATA_END) - first_free); + + mlog_write_initial_log_record(undo_page, MLOG_UNDO_ERASE_END, mtr); +} + +/*************************************************************** +Parses a redo log record of erasing of an undo page end. */ +UNIV_INTERN +byte* +trx_undo_parse_erase_page_end( +/*==========================*/ + /* out: end of log record or NULL */ + byte* ptr, /* in: buffer */ + byte* end_ptr __attribute__((unused)), /* in: buffer end */ + page_t* page, /* in: page or NULL */ + mtr_t* mtr) /* in: mtr or NULL */ +{ + ut_ad(ptr && end_ptr); + + if (page == NULL) { + + return(ptr); + } + + trx_undo_erase_page_end(page, mtr); + + return(ptr); +} + +/*************************************************************************** +Writes information to an undo log about an insert, update, or a delete marking +of a clustered index record. This information is used in a rollback of the +transaction and in consistent reads that must look to the history of this +transaction. */ +UNIV_INTERN +ulint +trx_undo_report_row_operation( +/*==========================*/ + /* out: DB_SUCCESS or error code */ + ulint flags, /* in: if BTR_NO_UNDO_LOG_FLAG bit is + set, does nothing */ + ulint op_type, /* in: TRX_UNDO_INSERT_OP or + TRX_UNDO_MODIFY_OP */ + que_thr_t* thr, /* in: query thread */ + dict_index_t* index, /* in: clustered index */ + const dtuple_t* clust_entry, /* in: in the case of an insert, + index entry to insert into the + clustered index, otherwise NULL */ + const upd_t* update, /* in: in the case of an update, + the update vector, otherwise NULL */ + ulint cmpl_info, /* in: compiler info on secondary + index updates */ + const rec_t* rec, /* in: in case of an update or delete + marking, the record in the clustered + index, otherwise NULL */ + dulint* roll_ptr) /* out: rollback pointer to the + inserted undo log record, + ut_dulint_zero if BTR_NO_UNDO_LOG + flag was specified */ +{ + trx_t* trx; + trx_undo_t* undo; + ulint page_no; + trx_rseg_t* rseg; + mtr_t mtr; + ulint err = DB_SUCCESS; + mem_heap_t* heap = NULL; + ulint offsets_[REC_OFFS_NORMAL_SIZE]; + ulint* offsets = offsets_; + rec_offs_init(offsets_); + + ut_a(dict_index_is_clust(index)); + + if (flags & BTR_NO_UNDO_LOG_FLAG) { + + *roll_ptr = ut_dulint_zero; + + return(DB_SUCCESS); + } + + ut_ad(thr); + ut_ad((op_type != TRX_UNDO_INSERT_OP) + || (clust_entry && !update && !rec)); + + trx = thr_get_trx(thr); + rseg = trx->rseg; + + mutex_enter(&(trx->undo_mutex)); + + /* If the undo log is not assigned yet, assign one */ + + if (op_type == TRX_UNDO_INSERT_OP) { + + if (trx->insert_undo == NULL) { + + err = trx_undo_assign_undo(trx, TRX_UNDO_INSERT); + } + + undo = trx->insert_undo; + + if (UNIV_UNLIKELY(!undo)) { + /* Did not succeed */ + mutex_exit(&(trx->undo_mutex)); + + return(err); + } + } else { + ut_ad(op_type == TRX_UNDO_MODIFY_OP); + + if (trx->update_undo == NULL) { + + err = trx_undo_assign_undo(trx, TRX_UNDO_UPDATE); + + } + + undo = trx->update_undo; + + if (UNIV_UNLIKELY(!undo)) { + /* Did not succeed */ + mutex_exit(&(trx->undo_mutex)); + return(err); + } + + offsets = rec_get_offsets(rec, index, offsets, + ULINT_UNDEFINED, &heap); + } + + page_no = undo->last_page_no; + + mtr_start(&mtr); + + for (;;) { + buf_block_t* undo_block; + page_t* undo_page; + ulint offset; + + undo_block = buf_page_get_gen(undo->space, undo->zip_size, + page_no, RW_X_LATCH, + undo->guess_block, BUF_GET, + __FILE__, __LINE__, &mtr); + buf_block_dbg_add_level(undo_block, SYNC_TRX_UNDO_PAGE); + + undo_page = buf_block_get_frame(undo_block); + + if (op_type == TRX_UNDO_INSERT_OP) { + offset = trx_undo_page_report_insert( + undo_page, trx, index, clust_entry, &mtr); + } else { + offset = trx_undo_page_report_modify( + undo_page, trx, index, rec, offsets, update, + cmpl_info, &mtr); + } + + if (UNIV_UNLIKELY(offset == 0)) { + /* The record did not fit on the page. We erase the + end segment of the undo log page and write a log + record of it: this is to ensure that in the debug + version the replicate page constructed using the log + records stays identical to the original page */ + + trx_undo_erase_page_end(undo_page, &mtr); + mtr_commit(&mtr); + } else { + /* Success */ + + mtr_commit(&mtr); + + undo->empty = FALSE; + undo->top_page_no = page_no; + undo->top_offset = offset; + undo->top_undo_no = trx->undo_no; + undo->guess_block = undo_block; + + UT_DULINT_INC(trx->undo_no); + + mutex_exit(&trx->undo_mutex); + + *roll_ptr = trx_undo_build_roll_ptr( + op_type == TRX_UNDO_INSERT_OP, + rseg->id, page_no, offset); + if (UNIV_LIKELY_NULL(heap)) { + mem_heap_free(heap); + } + return(DB_SUCCESS); + } + + ut_ad(page_no == undo->last_page_no); + + /* We have to extend the undo log by one page */ + + mtr_start(&mtr); + + /* When we add a page to an undo log, this is analogous to + a pessimistic insert in a B-tree, and we must reserve the + counterpart of the tree latch, which is the rseg mutex. */ + + mutex_enter(&(rseg->mutex)); + + page_no = trx_undo_add_page(trx, undo, &mtr); + + mutex_exit(&(rseg->mutex)); + + if (UNIV_UNLIKELY(page_no == FIL_NULL)) { + /* Did not succeed: out of space */ + + mutex_exit(&(trx->undo_mutex)); + mtr_commit(&mtr); + if (UNIV_LIKELY_NULL(heap)) { + mem_heap_free(heap); + } + return(DB_OUT_OF_FILE_SPACE); + } + } +} + +/*============== BUILDING PREVIOUS VERSION OF A RECORD ===============*/ + +/********************************************************************** +Copies an undo record to heap. This function can be called if we know that +the undo log record exists. */ +UNIV_INTERN +trx_undo_rec_t* +trx_undo_get_undo_rec_low( +/*======================*/ + /* out, own: copy of the record */ + dulint roll_ptr, /* in: roll pointer to record */ + mem_heap_t* heap) /* in: memory heap where copied */ +{ + trx_undo_rec_t* undo_rec; + ulint rseg_id; + ulint page_no; + ulint offset; + page_t* undo_page; + trx_rseg_t* rseg; + ibool is_insert; + mtr_t mtr; + + trx_undo_decode_roll_ptr(roll_ptr, &is_insert, &rseg_id, &page_no, + &offset); + rseg = trx_rseg_get_on_id(rseg_id); + + mtr_start(&mtr); + + undo_page = trx_undo_page_get_s_latched(rseg->space, rseg->zip_size, + page_no, &mtr); + + undo_rec = trx_undo_rec_copy(undo_page + offset, heap); + + mtr_commit(&mtr); + + return(undo_rec); +} + +/********************************************************************** +Copies an undo record to heap. */ +UNIV_INTERN +ulint +trx_undo_get_undo_rec( +/*==================*/ + /* out: DB_SUCCESS, or + DB_MISSING_HISTORY if the undo log + has been truncated and we cannot + fetch the old version; NOTE: the + caller must have latches on the + clustered index page and purge_view */ + dulint roll_ptr, /* in: roll pointer to record */ + dulint trx_id, /* in: id of the trx that generated + the roll pointer: it points to an + undo log of this transaction */ + trx_undo_rec_t** undo_rec, /* out, own: copy of the record */ + mem_heap_t* heap) /* in: memory heap where copied */ +{ +#ifdef UNIV_SYNC_DEBUG + ut_ad(rw_lock_own(&(purge_sys->latch), RW_LOCK_SHARED)); +#endif /* UNIV_SYNC_DEBUG */ + + if (!trx_purge_update_undo_must_exist(trx_id)) { + + /* It may be that the necessary undo log has already been + deleted */ + + return(DB_MISSING_HISTORY); + } + + *undo_rec = trx_undo_get_undo_rec_low(roll_ptr, heap); + + return(DB_SUCCESS); +} + +/*********************************************************************** +Build a previous version of a clustered index record. This function checks +that the caller has a latch on the index page of the clustered index record +and an s-latch on the purge_view. This guarantees that the stack of versions +is locked all the way down to the purge_view. */ +UNIV_INTERN +ulint +trx_undo_prev_version_build( +/*========================*/ + /* out: DB_SUCCESS, or DB_MISSING_HISTORY if + the previous version is not >= purge_view, + which means that it may have been removed, + DB_ERROR if corrupted record */ + const rec_t* index_rec,/* in: clustered index record in the + index tree */ + mtr_t* index_mtr __attribute__((unused)), + /* in: mtr which contains the latch to + index_rec page and purge_view */ + const rec_t* rec, /* in: version of a clustered index record */ + dict_index_t* index, /* in: clustered index */ + ulint* offsets,/* in: rec_get_offsets(rec, index) */ + mem_heap_t* heap, /* in: memory heap from which the memory + needed is allocated */ + rec_t** old_vers)/* out, own: previous version, or NULL if + rec is the first inserted version, or if + history data has been deleted (an error), + or if the purge COULD have removed the version + though it has not yet done so */ +{ + trx_undo_rec_t* undo_rec = NULL; + dtuple_t* entry; + dulint rec_trx_id; + ulint type; + dulint undo_no; + dulint table_id; + dulint trx_id; + dulint roll_ptr; + dulint old_roll_ptr; + upd_t* update; + byte* ptr; + ulint info_bits; + ulint cmpl_info; + ibool dummy_extern; + byte* buf; + ulint err; +#ifdef UNIV_SYNC_DEBUG + ut_ad(rw_lock_own(&(purge_sys->latch), RW_LOCK_SHARED)); +#endif /* UNIV_SYNC_DEBUG */ + ut_ad(mtr_memo_contains_page(index_mtr, index_rec, MTR_MEMO_PAGE_S_FIX) + || mtr_memo_contains_page(index_mtr, index_rec, + MTR_MEMO_PAGE_X_FIX)); + ut_ad(rec_offs_validate(rec, index, offsets)); + + if (!dict_index_is_clust(index)) { + fprintf(stderr, "InnoDB: Error: trying to access" + " update undo rec for non-clustered index %s\n" + "InnoDB: Submit a detailed bug report to" + " http://bugs.mysql.com\n" + "InnoDB: index record ", index->name); + rec_print(stderr, index_rec, index); + fputs("\n" + "InnoDB: record version ", stderr); + rec_print_new(stderr, rec, offsets); + putc('\n', stderr); + return(DB_ERROR); + } + + roll_ptr = row_get_rec_roll_ptr(rec, index, offsets); + old_roll_ptr = roll_ptr; + + *old_vers = NULL; + + if (trx_undo_roll_ptr_is_insert(roll_ptr)) { + + /* The record rec is the first inserted version */ + + return(DB_SUCCESS); + } + + rec_trx_id = row_get_rec_trx_id(rec, index, offsets); + + err = trx_undo_get_undo_rec(roll_ptr, rec_trx_id, &undo_rec, heap); + + if (UNIV_UNLIKELY(err != DB_SUCCESS)) { + /* The undo record may already have been purged. + This should never happen in InnoDB. */ + + return(err); + } + + ptr = trx_undo_rec_get_pars(undo_rec, &type, &cmpl_info, + &dummy_extern, &undo_no, &table_id); + + ptr = trx_undo_update_rec_get_sys_cols(ptr, &trx_id, &roll_ptr, + &info_bits); + + /* (a) If a clustered index record version is such that the + trx id stamp in it is bigger than purge_sys->view, then the + BLOBs in that version are known to exist (the purge has not + progressed that far); + + (b) if the version is the first version such that trx id in it + is less than purge_sys->view, and it is not delete-marked, + then the BLOBs in that version are known to exist (the purge + cannot have purged the BLOBs referenced by that version + yet). + + This function does not fetch any BLOBs. The callers might, by + possibly invoking row_ext_create() via row_build(). However, + they should have all needed information in the *old_vers + returned by this function. This is because *old_vers is based + on the transaction undo log records. The function + trx_undo_page_fetch_ext() will write BLOB prefixes to the + transaction undo log that are at least as long as the longest + possible column prefix in a secondary index. Thus, secondary + index entries for *old_vers can be constructed without + dereferencing any BLOB pointers. */ + + ptr = trx_undo_rec_skip_row_ref(ptr, index); + + ptr = trx_undo_update_rec_get_update(ptr, index, type, trx_id, + roll_ptr, info_bits, + NULL, heap, &update); + + if (ut_dulint_cmp(table_id, index->table->id) != 0) { + ptr = NULL; + + fprintf(stderr, + "InnoDB: Error: trying to access update undo rec" + " for table %s\n" + "InnoDB: but the table id in the" + " undo record is wrong\n" + "InnoDB: Submit a detailed bug report" + " to http://bugs.mysql.com\n" + "InnoDB: Run also CHECK TABLE %s\n", + index->table_name, index->table_name); + } + + if (ptr == NULL) { + /* The record was corrupted, return an error; these printfs + should catch an elusive bug in row_vers_old_has_index_entry */ + + fprintf(stderr, + "InnoDB: table %s, index %s, n_uniq %lu\n" + "InnoDB: undo rec address %p, type %lu cmpl_info %lu\n" + "InnoDB: undo rec table id %lu %lu," + " index table id %lu %lu\n" + "InnoDB: dump of 150 bytes in undo rec: ", + index->table_name, index->name, + (ulong) dict_index_get_n_unique(index), + undo_rec, (ulong) type, (ulong) cmpl_info, + (ulong) ut_dulint_get_high(table_id), + (ulong) ut_dulint_get_low(table_id), + (ulong) ut_dulint_get_high(index->table->id), + (ulong) ut_dulint_get_low(index->table->id)); + ut_print_buf(stderr, undo_rec, 150); + fputs("\n" + "InnoDB: index record ", stderr); + rec_print(stderr, index_rec, index); + fputs("\n" + "InnoDB: record version ", stderr); + rec_print_new(stderr, rec, offsets); + fprintf(stderr, "\n" + "InnoDB: Record trx id " TRX_ID_FMT + ", update rec trx id " TRX_ID_FMT "\n" + "InnoDB: Roll ptr in rec %lu %lu, in update rec" + " %lu %lu\n", + TRX_ID_PREP_PRINTF(rec_trx_id), + TRX_ID_PREP_PRINTF(trx_id), + (ulong) ut_dulint_get_high(old_roll_ptr), + (ulong) ut_dulint_get_low(old_roll_ptr), + (ulong) ut_dulint_get_high(roll_ptr), + (ulong) ut_dulint_get_low(roll_ptr)); + + trx_purge_sys_print(); + return(DB_ERROR); + } + + if (row_upd_changes_field_size_or_external(index, offsets, update)) { + ulint n_ext; + + /* We have to set the appropriate extern storage bits in the + old version of the record: the extern bits in rec for those + fields that update does NOT update, as well as the the bits for + those fields that update updates to become externally stored + fields. Store the info: */ + + entry = row_rec_to_index_entry(ROW_COPY_DATA, rec, index, + offsets, &n_ext, heap); + n_ext += btr_push_update_extern_fields(entry, update, heap); + /* The page containing the clustered index record + corresponding to entry is latched in mtr. Thus the + following call is safe. */ + row_upd_index_replace_new_col_vals(entry, index, update, heap); + + buf = mem_heap_alloc(heap, rec_get_converted_size(index, entry, + n_ext)); + + *old_vers = rec_convert_dtuple_to_rec(buf, index, + entry, n_ext); + } else { + buf = mem_heap_alloc(heap, rec_offs_size(offsets)); + *old_vers = rec_copy(buf, rec, offsets); + rec_offs_make_valid(*old_vers, index, offsets); + row_upd_rec_in_place(*old_vers, index, offsets, update, NULL); + } + + return(DB_SUCCESS); +} diff --git a/storage/xtradb/trx/trx0roll.c b/storage/xtradb/trx/trx0roll.c new file mode 100644 index 00000000000..5f3cb15a254 --- /dev/null +++ b/storage/xtradb/trx/trx0roll.c @@ -0,0 +1,1359 @@ +/***************************************************************************** + +Copyright (c) 1996, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/****************************************************** +Transaction rollback + +Created 3/26/1996 Heikki Tuuri +*******************************************************/ + +#include "trx0roll.h" + +#ifdef UNIV_NONINL +#include "trx0roll.ic" +#endif + +#include "fsp0fsp.h" +#include "mach0data.h" +#include "trx0rseg.h" +#include "trx0trx.h" +#include "trx0undo.h" +#include "trx0rec.h" +#include "que0que.h" +#include "usr0sess.h" +#include "srv0que.h" +#include "srv0start.h" +#include "row0undo.h" +#include "row0mysql.h" +#include "lock0lock.h" +#include "pars0pars.h" + +/* This many pages must be undone before a truncate is tried within rollback */ +#define TRX_ROLL_TRUNC_THRESHOLD 1 + +/* In crash recovery, the current trx to be rolled back */ +static trx_t* trx_roll_crash_recv_trx = NULL; + +/* In crash recovery we set this to the undo n:o of the current trx to be +rolled back. Then we can print how many % the rollback has progressed. */ +static ib_int64_t trx_roll_max_undo_no; + +/* Auxiliary variable which tells the previous progress % we printed */ +static ulint trx_roll_progress_printed_pct; + +/*********************************************************************** +Rollback a transaction used in MySQL. */ +UNIV_INTERN +int +trx_general_rollback_for_mysql( +/*===========================*/ + /* out: error code or DB_SUCCESS */ + trx_t* trx, /* in: transaction handle */ + ibool partial,/* in: TRUE if partial rollback requested */ + trx_savept_t* savept) /* in: pointer to savepoint undo number, if + partial rollback requested */ +{ +#ifndef UNIV_HOTBACKUP + mem_heap_t* heap; + que_thr_t* thr; + roll_node_t* roll_node; + + /* Tell Innobase server that there might be work for + utility threads: */ + + srv_active_wake_master_thread(); + + trx_start_if_not_started(trx); + + heap = mem_heap_create(512); + + roll_node = roll_node_create(heap); + + roll_node->partial = partial; + + if (partial) { + roll_node->savept = *savept; + } + + trx->error_state = DB_SUCCESS; + + thr = pars_complete_graph_for_exec(roll_node, trx, heap); + + ut_a(thr == que_fork_start_command(que_node_get_parent(thr))); + que_run_threads(thr); + + mutex_enter(&kernel_mutex); + + while (trx->que_state != TRX_QUE_RUNNING) { + + mutex_exit(&kernel_mutex); + + os_thread_sleep(100000); + + mutex_enter(&kernel_mutex); + } + + mutex_exit(&kernel_mutex); + + mem_heap_free(heap); + + ut_a(trx->error_state == DB_SUCCESS); + + /* Tell Innobase server that there might be work for + utility threads: */ + + srv_active_wake_master_thread(); + + return((int) trx->error_state); +#else /* UNIV_HOTBACKUP */ + /* This function depends on MySQL code that is not included in + InnoDB Hot Backup builds. Besides, this function should never + be called in InnoDB Hot Backup. */ + ut_error; + return(DB_FAIL); +#endif /* UNIV_HOTBACKUP */ +} + +/*********************************************************************** +Rollback a transaction used in MySQL. */ +UNIV_INTERN +int +trx_rollback_for_mysql( +/*===================*/ + /* out: error code or DB_SUCCESS */ + trx_t* trx) /* in: transaction handle */ +{ + int err; + + if (trx->conc_state == TRX_NOT_STARTED) { + + return(DB_SUCCESS); + } + + trx->op_info = "rollback"; + + /* If we are doing the XA recovery of prepared transactions, then + the transaction object does not have an InnoDB session object, and we + set a dummy session that we use for all MySQL transactions. */ + + err = trx_general_rollback_for_mysql(trx, FALSE, NULL); + + trx->op_info = ""; + + return(err); +} + +/*********************************************************************** +Rollback the latest SQL statement for MySQL. */ +UNIV_INTERN +int +trx_rollback_last_sql_stat_for_mysql( +/*=================================*/ + /* out: error code or DB_SUCCESS */ + trx_t* trx) /* in: transaction handle */ +{ + int err; + + if (trx->conc_state == TRX_NOT_STARTED) { + + return(DB_SUCCESS); + } + + trx->op_info = "rollback of SQL statement"; + + err = trx_general_rollback_for_mysql(trx, TRUE, + &(trx->last_sql_stat_start)); + /* The following call should not be needed, but we play safe: */ + trx_mark_sql_stat_end(trx); + + trx->op_info = ""; + + return(err); +} + +/*********************************************************************** +Frees a single savepoint struct. */ +UNIV_INTERN +void +trx_roll_savepoint_free( +/*=====================*/ + trx_t* trx, /* in: transaction handle */ + trx_named_savept_t* savep) /* in: savepoint to free */ +{ + ut_a(savep != NULL); + ut_a(UT_LIST_GET_LEN(trx->trx_savepoints) > 0); + + UT_LIST_REMOVE(trx_savepoints, trx->trx_savepoints, savep); + mem_free(savep->name); + mem_free(savep); +} + +/*********************************************************************** +Frees savepoint structs starting from savep, if savep == NULL then +free all savepoints. */ + +void +trx_roll_savepoints_free( +/*=====================*/ + trx_t* trx, /* in: transaction handle */ + trx_named_savept_t* savep) /* in: free all savepoints > this one; + if this is NULL, free all savepoints + of trx */ +{ + trx_named_savept_t* next_savep; + + if (savep == NULL) { + savep = UT_LIST_GET_FIRST(trx->trx_savepoints); + } else { + savep = UT_LIST_GET_NEXT(trx_savepoints, savep); + } + + while (savep != NULL) { + next_savep = UT_LIST_GET_NEXT(trx_savepoints, savep); + + trx_roll_savepoint_free(trx, savep); + + savep = next_savep; + } +} + +/*********************************************************************** +Rolls back a transaction back to a named savepoint. Modifications after the +savepoint are undone but InnoDB does NOT release the corresponding locks +which are stored in memory. If a lock is 'implicit', that is, a new inserted +row holds a lock where the lock information is carried by the trx id stored in +the row, these locks are naturally released in the rollback. Savepoints which +were set after this savepoint are deleted. */ +UNIV_INTERN +ulint +trx_rollback_to_savepoint_for_mysql( +/*================================*/ + /* out: if no savepoint + of the name found then + DB_NO_SAVEPOINT, + otherwise DB_SUCCESS */ + trx_t* trx, /* in: transaction handle */ + const char* savepoint_name, /* in: savepoint name */ + ib_int64_t* mysql_binlog_cache_pos) /* out: the MySQL binlog cache + position corresponding to this + savepoint; MySQL needs this + information to remove the + binlog entries of the queries + executed after the savepoint */ +{ + trx_named_savept_t* savep; + ulint err; + + savep = UT_LIST_GET_FIRST(trx->trx_savepoints); + + while (savep != NULL) { + if (0 == ut_strcmp(savep->name, savepoint_name)) { + /* Found */ + break; + } + savep = UT_LIST_GET_NEXT(trx_savepoints, savep); + } + + if (savep == NULL) { + + return(DB_NO_SAVEPOINT); + } + + if (trx->conc_state == TRX_NOT_STARTED) { + ut_print_timestamp(stderr); + fputs(" InnoDB: Error: transaction has a savepoint ", stderr); + ut_print_name(stderr, trx, FALSE, savep->name); + fputs(" though it is not started\n", stderr); + return(DB_ERROR); + } + + /* We can now free all savepoints strictly later than this one */ + + trx_roll_savepoints_free(trx, savep); + + *mysql_binlog_cache_pos = savep->mysql_binlog_cache_pos; + + trx->op_info = "rollback to a savepoint"; + + err = trx_general_rollback_for_mysql(trx, TRUE, &(savep->savept)); + + /* Store the current undo_no of the transaction so that we know where + to roll back if we have to roll back the next SQL statement: */ + + trx_mark_sql_stat_end(trx); + + trx->op_info = ""; + + return(err); +} + +/*********************************************************************** +Creates a named savepoint. If the transaction is not yet started, starts it. +If there is already a savepoint of the same name, this call erases that old +savepoint and replaces it with a new. Savepoints are deleted in a transaction +commit or rollback. */ +UNIV_INTERN +ulint +trx_savepoint_for_mysql( +/*====================*/ + /* out: always DB_SUCCESS */ + trx_t* trx, /* in: transaction handle */ + const char* savepoint_name, /* in: savepoint name */ + ib_int64_t binlog_cache_pos) /* in: MySQL binlog cache + position corresponding to this + connection at the time of the + savepoint */ +{ + trx_named_savept_t* savep; + + ut_a(trx); + ut_a(savepoint_name); + + trx_start_if_not_started(trx); + + savep = UT_LIST_GET_FIRST(trx->trx_savepoints); + + while (savep != NULL) { + if (0 == ut_strcmp(savep->name, savepoint_name)) { + /* Found */ + break; + } + savep = UT_LIST_GET_NEXT(trx_savepoints, savep); + } + + if (savep) { + /* There is a savepoint with the same name: free that */ + + UT_LIST_REMOVE(trx_savepoints, trx->trx_savepoints, savep); + + mem_free(savep->name); + mem_free(savep); + } + + /* Create a new savepoint and add it as the last in the list */ + + savep = mem_alloc(sizeof(trx_named_savept_t)); + + savep->name = mem_strdup(savepoint_name); + + savep->savept = trx_savept_take(trx); + + savep->mysql_binlog_cache_pos = binlog_cache_pos; + + UT_LIST_ADD_LAST(trx_savepoints, trx->trx_savepoints, savep); + + return(DB_SUCCESS); +} + +/*********************************************************************** +Releases only the named savepoint. Savepoints which were set after this +savepoint are left as is. */ +UNIV_INTERN +ulint +trx_release_savepoint_for_mysql( +/*============================*/ + /* out: if no savepoint + of the name found then + DB_NO_SAVEPOINT, + otherwise DB_SUCCESS */ + trx_t* trx, /* in: transaction handle */ + const char* savepoint_name) /* in: savepoint name */ +{ + trx_named_savept_t* savep; + + savep = UT_LIST_GET_FIRST(trx->trx_savepoints); + + /* Search for the savepoint by name and free if found. */ + while (savep != NULL) { + if (0 == ut_strcmp(savep->name, savepoint_name)) { + trx_roll_savepoint_free(trx, savep); + return(DB_SUCCESS); + } + savep = UT_LIST_GET_NEXT(trx_savepoints, savep); + } + + return(DB_NO_SAVEPOINT); +} + +/*********************************************************************** +Determines if this transaction is rolling back an incomplete transaction +in crash recovery. */ +UNIV_INTERN +ibool +trx_is_recv( +/*========*/ + /* out: TRUE if trx is an incomplete + transaction that is being rolled back + in crash recovery */ + const trx_t* trx) /* in: transaction */ +{ + return(trx == trx_roll_crash_recv_trx); +} + +/*********************************************************************** +Returns a transaction savepoint taken at this point in time. */ +UNIV_INTERN +trx_savept_t +trx_savept_take( +/*============*/ + /* out: savepoint */ + trx_t* trx) /* in: transaction */ +{ + trx_savept_t savept; + + savept.least_undo_no = trx->undo_no; + + return(savept); +} + +/*********************************************************************** +Roll back an active transaction. */ +static +void +trx_rollback_active( +/*================*/ + trx_t* trx) /* in/out: transaction */ +{ + mem_heap_t* heap; + que_fork_t* fork; + que_thr_t* thr; + roll_node_t* roll_node; + dict_table_t* table; + ib_int64_t rows_to_undo; + const char* unit = ""; + ibool dictionary_locked = FALSE; + + heap = mem_heap_create(512); + + fork = que_fork_create(NULL, NULL, QUE_FORK_RECOVERY, heap); + fork->trx = trx; + + thr = que_thr_create(fork, heap); + + roll_node = roll_node_create(heap); + + thr->child = roll_node; + roll_node->common.parent = thr; + + mutex_enter(&kernel_mutex); + + trx->graph = fork; + + ut_a(thr == que_fork_start_command(fork)); + + trx_roll_crash_recv_trx = trx; + trx_roll_max_undo_no = ut_conv_dulint_to_longlong(trx->undo_no); + trx_roll_progress_printed_pct = 0; + rows_to_undo = trx_roll_max_undo_no; + + if (rows_to_undo > 1000000000) { + rows_to_undo = rows_to_undo / 1000000; + unit = "M"; + } + + ut_print_timestamp(stderr); + fprintf(stderr, + " InnoDB: Rolling back trx with id " TRX_ID_FMT ", %lu%s" + " rows to undo\n", + TRX_ID_PREP_PRINTF(trx->id), + (ulong) rows_to_undo, unit); + mutex_exit(&kernel_mutex); + + trx->mysql_thread_id = os_thread_get_curr_id(); + + trx->mysql_process_no = os_proc_get_number(); + + if (trx_get_dict_operation(trx) != TRX_DICT_OP_NONE) { + row_mysql_lock_data_dictionary(trx); + dictionary_locked = TRUE; + } + + que_run_threads(thr); + + mutex_enter(&kernel_mutex); + + while (trx->que_state != TRX_QUE_RUNNING) { + + mutex_exit(&kernel_mutex); + + fprintf(stderr, + "InnoDB: Waiting for rollback of trx id %lu to end\n", + (ulong) ut_dulint_get_low(trx->id)); + os_thread_sleep(100000); + + mutex_enter(&kernel_mutex); + } + + mutex_exit(&kernel_mutex); + + if (trx_get_dict_operation(trx) != TRX_DICT_OP_NONE + && !ut_dulint_is_zero(trx->table_id)) { + + /* If the transaction was for a dictionary operation, we + drop the relevant table, if it still exists */ + + fprintf(stderr, + "InnoDB: Dropping table with id %lu %lu" + " in recovery if it exists\n", + (ulong) ut_dulint_get_high(trx->table_id), + (ulong) ut_dulint_get_low(trx->table_id)); + + table = dict_table_get_on_id_low(trx->table_id); + + if (table) { + ulint err; + + fputs("InnoDB: Table found: dropping table ", stderr); + ut_print_name(stderr, trx, TRUE, table->name); + fputs(" in recovery\n", stderr); + + err = row_drop_table_for_mysql(table->name, trx, TRUE); + trx_commit_for_mysql(trx); + + ut_a(err == (int) DB_SUCCESS); + } + } + + if (dictionary_locked) { + row_mysql_unlock_data_dictionary(trx); + } + + fprintf(stderr, "\nInnoDB: Rolling back of trx id " TRX_ID_FMT + " completed\n", + TRX_ID_PREP_PRINTF(trx->id)); + mem_heap_free(heap); + + trx_roll_crash_recv_trx = NULL; +} + +/*********************************************************************** +Rollback or clean up any incomplete transactions which were +encountered in crash recovery. If the transaction already was +committed, then we clean up a possible insert undo log. If the +transaction was not yet committed, then we roll it back. +Note: this is done in a background thread. */ +UNIV_INTERN +os_thread_ret_t +trx_rollback_or_clean_all_recovered( +/*================================*/ + /* out: a dummy parameter */ + void* arg __attribute__((unused))) + /* in: a dummy parameter required by + os_thread_create */ +{ + trx_t* trx; + + mutex_enter(&kernel_mutex); + + if (UT_LIST_GET_FIRST(trx_sys->trx_list)) { + + fprintf(stderr, + "InnoDB: Starting in background the rollback" + " of uncommitted transactions\n"); + } else { + goto leave_function; + } + + mutex_exit(&kernel_mutex); + +loop: + mutex_enter(&kernel_mutex); + + for (trx = UT_LIST_GET_FIRST(trx_sys->trx_list); trx; + trx = UT_LIST_GET_NEXT(trx_list, trx)) { + if (!trx->is_recovered) { + continue; + } + + switch (trx->conc_state) { + case TRX_NOT_STARTED: + case TRX_PREPARED: + continue; + + case TRX_COMMITTED_IN_MEMORY: + mutex_exit(&kernel_mutex); + fprintf(stderr, + "InnoDB: Cleaning up trx with id " + TRX_ID_FMT "\n", + TRX_ID_PREP_PRINTF(trx->id)); + trx_cleanup_at_db_startup(trx); + goto loop; + + case TRX_ACTIVE: + mutex_exit(&kernel_mutex); + trx_rollback_active(trx); + goto loop; + } + } + + ut_print_timestamp(stderr); + fprintf(stderr, + " InnoDB: Rollback of non-prepared transactions completed\n"); + +leave_function: + mutex_exit(&kernel_mutex); + + /* We count the number of threads in os_thread_exit(). A created + thread should always use that to exit and not use return() to exit. */ + + os_thread_exit(NULL); + + OS_THREAD_DUMMY_RETURN; +} + +/*********************************************************************** +Creates an undo number array. */ +UNIV_INTERN +trx_undo_arr_t* +trx_undo_arr_create(void) +/*=====================*/ +{ + trx_undo_arr_t* arr; + mem_heap_t* heap; + ulint i; + + heap = mem_heap_create(1024); + + arr = mem_heap_alloc(heap, sizeof(trx_undo_arr_t)); + + arr->infos = mem_heap_alloc(heap, sizeof(trx_undo_inf_t) + * UNIV_MAX_PARALLELISM); + arr->n_cells = UNIV_MAX_PARALLELISM; + arr->n_used = 0; + + arr->heap = heap; + + for (i = 0; i < UNIV_MAX_PARALLELISM; i++) { + + (trx_undo_arr_get_nth_info(arr, i))->in_use = FALSE; + } + + return(arr); +} + +/*********************************************************************** +Frees an undo number array. */ +UNIV_INTERN +void +trx_undo_arr_free( +/*==============*/ + trx_undo_arr_t* arr) /* in: undo number array */ +{ + ut_ad(arr->n_used == 0); + + mem_heap_free(arr->heap); +} + +/*********************************************************************** +Stores info of an undo log record to the array if it is not stored yet. */ +static +ibool +trx_undo_arr_store_info( +/*====================*/ + /* out: FALSE if the record already existed in the + array */ + trx_t* trx, /* in: transaction */ + dulint undo_no)/* in: undo number */ +{ + trx_undo_inf_t* cell; + trx_undo_inf_t* stored_here; + trx_undo_arr_t* arr; + ulint n_used; + ulint n; + ulint i; + + n = 0; + arr = trx->undo_no_arr; + n_used = arr->n_used; + stored_here = NULL; + + for (i = 0;; i++) { + cell = trx_undo_arr_get_nth_info(arr, i); + + if (!cell->in_use) { + if (!stored_here) { + /* Not in use, we may store here */ + cell->undo_no = undo_no; + cell->in_use = TRUE; + + arr->n_used++; + + stored_here = cell; + } + } else { + n++; + + if (0 == ut_dulint_cmp(cell->undo_no, undo_no)) { + + if (stored_here) { + stored_here->in_use = FALSE; + ut_ad(arr->n_used > 0); + arr->n_used--; + } + + ut_ad(arr->n_used == n_used); + + return(FALSE); + } + } + + if (n == n_used && stored_here) { + + ut_ad(arr->n_used == 1 + n_used); + + return(TRUE); + } + } +} + +/*********************************************************************** +Removes an undo number from the array. */ +static +void +trx_undo_arr_remove_info( +/*=====================*/ + trx_undo_arr_t* arr, /* in: undo number array */ + dulint undo_no)/* in: undo number */ +{ + trx_undo_inf_t* cell; + ulint n_used; + ulint n; + ulint i; + + n_used = arr->n_used; + n = 0; + + for (i = 0;; i++) { + cell = trx_undo_arr_get_nth_info(arr, i); + + if (cell->in_use + && 0 == ut_dulint_cmp(cell->undo_no, undo_no)) { + + cell->in_use = FALSE; + + ut_ad(arr->n_used > 0); + + arr->n_used--; + + return; + } + } +} + +/*********************************************************************** +Gets the biggest undo number in an array. */ +static +dulint +trx_undo_arr_get_biggest( +/*=====================*/ + /* out: biggest value, ut_dulint_zero if + the array is empty */ + trx_undo_arr_t* arr) /* in: undo number array */ +{ + trx_undo_inf_t* cell; + ulint n_used; + dulint biggest; + ulint n; + ulint i; + + n = 0; + n_used = arr->n_used; + biggest = ut_dulint_zero; + + for (i = 0;; i++) { + cell = trx_undo_arr_get_nth_info(arr, i); + + if (cell->in_use) { + n++; + if (ut_dulint_cmp(cell->undo_no, biggest) > 0) { + + biggest = cell->undo_no; + } + } + + if (n == n_used) { + return(biggest); + } + } +} + +/*************************************************************************** +Tries truncate the undo logs. */ +UNIV_INTERN +void +trx_roll_try_truncate( +/*==================*/ + trx_t* trx) /* in: transaction */ +{ + trx_undo_arr_t* arr; + dulint limit; + dulint biggest; + + ut_ad(mutex_own(&(trx->undo_mutex))); + ut_ad(mutex_own(&((trx->rseg)->mutex))); + + trx->pages_undone = 0; + + arr = trx->undo_no_arr; + + limit = trx->undo_no; + + if (arr->n_used > 0) { + biggest = trx_undo_arr_get_biggest(arr); + + if (ut_dulint_cmp(biggest, limit) >= 0) { + + limit = ut_dulint_add(biggest, 1); + } + } + + if (trx->insert_undo) { + trx_undo_truncate_end(trx, trx->insert_undo, limit); + } + + if (trx->update_undo) { + trx_undo_truncate_end(trx, trx->update_undo, limit); + } +} + +/*************************************************************************** +Pops the topmost undo log record in a single undo log and updates the info +about the topmost record in the undo log memory struct. */ +static +trx_undo_rec_t* +trx_roll_pop_top_rec( +/*=================*/ + /* out: undo log record, the page s-latched */ + trx_t* trx, /* in: transaction */ + trx_undo_t* undo, /* in: undo log */ + mtr_t* mtr) /* in: mtr */ +{ + page_t* undo_page; + ulint offset; + trx_undo_rec_t* prev_rec; + page_t* prev_rec_page; + + ut_ad(mutex_own(&(trx->undo_mutex))); + + undo_page = trx_undo_page_get_s_latched(undo->space, undo->zip_size, + undo->top_page_no, mtr); + offset = undo->top_offset; + + /* fprintf(stderr, "Thread %lu undoing trx %lu undo record %lu\n", + os_thread_get_curr_id(), ut_dulint_get_low(trx->id), + ut_dulint_get_low(undo->top_undo_no)); */ + + prev_rec = trx_undo_get_prev_rec(undo_page + offset, + undo->hdr_page_no, undo->hdr_offset, + mtr); + if (prev_rec == NULL) { + + undo->empty = TRUE; + } else { + prev_rec_page = page_align(prev_rec); + + if (prev_rec_page != undo_page) { + + trx->pages_undone++; + } + + undo->top_page_no = page_get_page_no(prev_rec_page); + undo->top_offset = prev_rec - prev_rec_page; + undo->top_undo_no = trx_undo_rec_get_undo_no(prev_rec); + } + + return(undo_page + offset); +} + +/************************************************************************ +Pops the topmost record when the two undo logs of a transaction are seen +as a single stack of records ordered by their undo numbers. Inserts the +undo number of the popped undo record to the array of currently processed +undo numbers in the transaction. When the query thread finishes processing +of this undo record, it must be released with trx_undo_rec_release. */ +UNIV_INTERN +trx_undo_rec_t* +trx_roll_pop_top_rec_of_trx( +/*========================*/ + /* out: undo log record copied to heap, NULL + if none left, or if the undo number of the + top record would be less than the limit */ + trx_t* trx, /* in: transaction */ + dulint limit, /* in: least undo number we need */ + dulint* roll_ptr,/* out: roll pointer to undo record */ + mem_heap_t* heap) /* in: memory heap where copied */ +{ + trx_undo_t* undo; + trx_undo_t* ins_undo; + trx_undo_t* upd_undo; + trx_undo_rec_t* undo_rec; + trx_undo_rec_t* undo_rec_copy; + dulint undo_no; + ibool is_insert; + trx_rseg_t* rseg; + ulint progress_pct; + mtr_t mtr; + + rseg = trx->rseg; +try_again: + mutex_enter(&(trx->undo_mutex)); + + if (trx->pages_undone >= TRX_ROLL_TRUNC_THRESHOLD) { + mutex_enter(&(rseg->mutex)); + + trx_roll_try_truncate(trx); + + mutex_exit(&(rseg->mutex)); + } + + ins_undo = trx->insert_undo; + upd_undo = trx->update_undo; + + if (!ins_undo || ins_undo->empty) { + undo = upd_undo; + } else if (!upd_undo || upd_undo->empty) { + undo = ins_undo; + } else if (ut_dulint_cmp(upd_undo->top_undo_no, + ins_undo->top_undo_no) > 0) { + undo = upd_undo; + } else { + undo = ins_undo; + } + + if (!undo || undo->empty + || (ut_dulint_cmp(limit, undo->top_undo_no) > 0)) { + + if ((trx->undo_no_arr)->n_used == 0) { + /* Rollback is ending */ + + mutex_enter(&(rseg->mutex)); + + trx_roll_try_truncate(trx); + + mutex_exit(&(rseg->mutex)); + } + + mutex_exit(&(trx->undo_mutex)); + + return(NULL); + } + + if (undo == ins_undo) { + is_insert = TRUE; + } else { + is_insert = FALSE; + } + + *roll_ptr = trx_undo_build_roll_ptr(is_insert, (undo->rseg)->id, + undo->top_page_no, + undo->top_offset); + mtr_start(&mtr); + + undo_rec = trx_roll_pop_top_rec(trx, undo, &mtr); + + undo_no = trx_undo_rec_get_undo_no(undo_rec); + + ut_ad(ut_dulint_cmp(ut_dulint_add(undo_no, 1), trx->undo_no) == 0); + + /* We print rollback progress info if we are in a crash recovery + and the transaction has at least 1000 row operations to undo. */ + + if (trx == trx_roll_crash_recv_trx && trx_roll_max_undo_no > 1000) { + + progress_pct = 100 - (ulint) + ((ut_conv_dulint_to_longlong(undo_no) * 100) + / trx_roll_max_undo_no); + if (progress_pct != trx_roll_progress_printed_pct) { + if (trx_roll_progress_printed_pct == 0) { + fprintf(stderr, + "\nInnoDB: Progress in percents:" + " %lu", (ulong) progress_pct); + } else { + fprintf(stderr, + " %lu", (ulong) progress_pct); + } + fflush(stderr); + trx_roll_progress_printed_pct = progress_pct; + } + } + + trx->undo_no = undo_no; + + if (!trx_undo_arr_store_info(trx, undo_no)) { + /* A query thread is already processing this undo log record */ + + mutex_exit(&(trx->undo_mutex)); + + mtr_commit(&mtr); + + goto try_again; + } + + undo_rec_copy = trx_undo_rec_copy(undo_rec, heap); + + mutex_exit(&(trx->undo_mutex)); + + mtr_commit(&mtr); + + return(undo_rec_copy); +} + +/************************************************************************ +Reserves an undo log record for a query thread to undo. This should be +called if the query thread gets the undo log record not using the pop +function above. */ +UNIV_INTERN +ibool +trx_undo_rec_reserve( +/*=================*/ + /* out: TRUE if succeeded */ + trx_t* trx, /* in: transaction */ + dulint undo_no)/* in: undo number of the record */ +{ + ibool ret; + + mutex_enter(&(trx->undo_mutex)); + + ret = trx_undo_arr_store_info(trx, undo_no); + + mutex_exit(&(trx->undo_mutex)); + + return(ret); +} + +/*********************************************************************** +Releases a reserved undo record. */ +UNIV_INTERN +void +trx_undo_rec_release( +/*=================*/ + trx_t* trx, /* in: transaction */ + dulint undo_no)/* in: undo number */ +{ + trx_undo_arr_t* arr; + + mutex_enter(&(trx->undo_mutex)); + + arr = trx->undo_no_arr; + + trx_undo_arr_remove_info(arr, undo_no); + + mutex_exit(&(trx->undo_mutex)); +} + +/************************************************************************* +Starts a rollback operation. */ +UNIV_INTERN +void +trx_rollback( +/*=========*/ + trx_t* trx, /* in: transaction */ + trx_sig_t* sig, /* in: signal starting the rollback */ + que_thr_t** next_thr)/* in/out: next query thread to run; + if the value which is passed in is + a pointer to a NULL pointer, then the + calling function can start running + a new query thread; if the passed value is + NULL, the parameter is ignored */ +{ + que_t* roll_graph; + que_thr_t* thr; + /* que_thr_t* thr2; */ + + ut_ad(mutex_own(&kernel_mutex)); + ut_ad((trx->undo_no_arr == NULL) || ((trx->undo_no_arr)->n_used == 0)); + + /* Initialize the rollback field in the transaction */ + + if (sig->type == TRX_SIG_TOTAL_ROLLBACK) { + + trx->roll_limit = ut_dulint_zero; + + } else if (sig->type == TRX_SIG_ROLLBACK_TO_SAVEPT) { + + trx->roll_limit = (sig->savept).least_undo_no; + + } else if (sig->type == TRX_SIG_ERROR_OCCURRED) { + + trx->roll_limit = trx->last_sql_stat_start.least_undo_no; + } else { + ut_error; + } + + ut_a(ut_dulint_cmp(trx->roll_limit, trx->undo_no) <= 0); + + trx->pages_undone = 0; + + if (trx->undo_no_arr == NULL) { + trx->undo_no_arr = trx_undo_arr_create(); + } + + /* Build a 'query' graph which will perform the undo operations */ + + roll_graph = trx_roll_graph_build(trx); + + trx->graph = roll_graph; + trx->que_state = TRX_QUE_ROLLING_BACK; + + thr = que_fork_start_command(roll_graph); + + ut_ad(thr); + + /* thr2 = que_fork_start_command(roll_graph); + + ut_ad(thr2); */ + + if (next_thr && (*next_thr == NULL)) { + *next_thr = thr; + /* srv_que_task_enqueue_low(thr2); */ + } else { + srv_que_task_enqueue_low(thr); + /* srv_que_task_enqueue_low(thr2); */ + } +} + +/******************************************************************** +Builds an undo 'query' graph for a transaction. The actual rollback is +performed by executing this query graph like a query subprocedure call. +The reply about the completion of the rollback will be sent by this +graph. */ +UNIV_INTERN +que_t* +trx_roll_graph_build( +/*=================*/ + /* out, own: the query graph */ + trx_t* trx) /* in: trx handle */ +{ + mem_heap_t* heap; + que_fork_t* fork; + que_thr_t* thr; + /* que_thr_t* thr2; */ + + ut_ad(mutex_own(&kernel_mutex)); + + heap = mem_heap_create(512); + fork = que_fork_create(NULL, NULL, QUE_FORK_ROLLBACK, heap); + fork->trx = trx; + + thr = que_thr_create(fork, heap); + /* thr2 = que_thr_create(fork, heap); */ + + thr->child = row_undo_node_create(trx, thr, heap); + /* thr2->child = row_undo_node_create(trx, thr2, heap); */ + + return(fork); +} + +/************************************************************************* +Finishes error processing after the necessary partial rollback has been +done. */ +static +void +trx_finish_error_processing( +/*========================*/ + trx_t* trx) /* in: transaction */ +{ + trx_sig_t* sig; + trx_sig_t* next_sig; + + ut_ad(mutex_own(&kernel_mutex)); + + sig = UT_LIST_GET_FIRST(trx->signals); + + while (sig != NULL) { + next_sig = UT_LIST_GET_NEXT(signals, sig); + + if (sig->type == TRX_SIG_ERROR_OCCURRED) { + + trx_sig_remove(trx, sig); + } + + sig = next_sig; + } + + trx->que_state = TRX_QUE_RUNNING; +} + +/************************************************************************* +Finishes a partial rollback operation. */ +static +void +trx_finish_partial_rollback_off_kernel( +/*===================================*/ + trx_t* trx, /* in: transaction */ + que_thr_t** next_thr)/* in/out: next query thread to run; + if the value which is passed in is a pointer + to a NULL pointer, then the calling function + can start running a new query thread; if this + parameter is NULL, it is ignored */ +{ + trx_sig_t* sig; + + ut_ad(mutex_own(&kernel_mutex)); + + sig = UT_LIST_GET_FIRST(trx->signals); + + /* Remove the signal from the signal queue and send reply message + to it */ + + trx_sig_reply(sig, next_thr); + trx_sig_remove(trx, sig); + + trx->que_state = TRX_QUE_RUNNING; +} + +/******************************************************************** +Finishes a transaction rollback. */ +UNIV_INTERN +void +trx_finish_rollback_off_kernel( +/*===========================*/ + que_t* graph, /* in: undo graph which can now be freed */ + trx_t* trx, /* in: transaction */ + que_thr_t** next_thr)/* in/out: next query thread to run; + if the value which is passed in is + a pointer to a NULL pointer, then the + calling function can start running + a new query thread; if this parameter is + NULL, it is ignored */ +{ + trx_sig_t* sig; + trx_sig_t* next_sig; + + ut_ad(mutex_own(&kernel_mutex)); + + ut_a(trx->undo_no_arr == NULL || trx->undo_no_arr->n_used == 0); + + /* Free the memory reserved by the undo graph */ + que_graph_free(graph); + + sig = UT_LIST_GET_FIRST(trx->signals); + + if (sig->type == TRX_SIG_ROLLBACK_TO_SAVEPT) { + + trx_finish_partial_rollback_off_kernel(trx, next_thr); + + return; + + } else if (sig->type == TRX_SIG_ERROR_OCCURRED) { + + trx_finish_error_processing(trx); + + return; + } + +#ifdef UNIV_DEBUG + if (lock_print_waits) { + fprintf(stderr, "Trx %lu rollback finished\n", + (ulong) ut_dulint_get_low(trx->id)); + } +#endif /* UNIV_DEBUG */ + + trx_commit_off_kernel(trx); + + /* Remove all TRX_SIG_TOTAL_ROLLBACK signals from the signal queue and + send reply messages to them */ + + trx->que_state = TRX_QUE_RUNNING; + + while (sig != NULL) { + next_sig = UT_LIST_GET_NEXT(signals, sig); + + if (sig->type == TRX_SIG_TOTAL_ROLLBACK) { + + trx_sig_reply(sig, next_thr); + + trx_sig_remove(trx, sig); + } + + sig = next_sig; + } +} + +/************************************************************************* +Creates a rollback command node struct. */ +UNIV_INTERN +roll_node_t* +roll_node_create( +/*=============*/ + /* out, own: rollback node struct */ + mem_heap_t* heap) /* in: mem heap where created */ +{ + roll_node_t* node; + + node = mem_heap_alloc(heap, sizeof(roll_node_t)); + node->common.type = QUE_NODE_ROLLBACK; + node->state = ROLL_NODE_SEND; + + node->partial = FALSE; + + return(node); +} + +/*************************************************************** +Performs an execution step for a rollback command node in a query graph. */ +UNIV_INTERN +que_thr_t* +trx_rollback_step( +/*==============*/ + /* out: query thread to run next, or NULL */ + que_thr_t* thr) /* in: query thread */ +{ + roll_node_t* node; + ulint sig_no; + trx_savept_t* savept; + + node = thr->run_node; + + ut_ad(que_node_get_type(node) == QUE_NODE_ROLLBACK); + + if (thr->prev_node == que_node_get_parent(node)) { + node->state = ROLL_NODE_SEND; + } + + if (node->state == ROLL_NODE_SEND) { + mutex_enter(&kernel_mutex); + + node->state = ROLL_NODE_WAIT; + + if (node->partial) { + sig_no = TRX_SIG_ROLLBACK_TO_SAVEPT; + savept = &(node->savept); + } else { + sig_no = TRX_SIG_TOTAL_ROLLBACK; + savept = NULL; + } + + /* Send a rollback signal to the transaction */ + + trx_sig_send(thr_get_trx(thr), sig_no, TRX_SIG_SELF, thr, + savept, NULL); + + thr->state = QUE_THR_SIG_REPLY_WAIT; + + mutex_exit(&kernel_mutex); + + return(NULL); + } + + ut_ad(node->state == ROLL_NODE_WAIT); + + thr->run_node = que_node_get_parent(node); + + return(thr); +} diff --git a/storage/xtradb/trx/trx0rseg.c b/storage/xtradb/trx/trx0rseg.c new file mode 100644 index 00000000000..db5efd65eb3 --- /dev/null +++ b/storage/xtradb/trx/trx0rseg.c @@ -0,0 +1,282 @@ +/***************************************************************************** + +Copyright (c) 1996, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/****************************************************** +Rollback segment + +Created 3/26/1996 Heikki Tuuri +*******************************************************/ + +#include "trx0rseg.h" + +#ifdef UNIV_NONINL +#include "trx0rseg.ic" +#endif + +#include "trx0undo.h" +#include "fut0lst.h" +#include "srv0srv.h" +#include "trx0purge.h" + +/********************************************************************** +Looks for a rollback segment, based on the rollback segment id. */ +UNIV_INTERN +trx_rseg_t* +trx_rseg_get_on_id( +/*===============*/ + /* out: rollback segment */ + ulint id) /* in: rollback segment id */ +{ + trx_rseg_t* rseg; + + rseg = UT_LIST_GET_FIRST(trx_sys->rseg_list); + ut_ad(rseg); + + while (rseg->id != id) { + rseg = UT_LIST_GET_NEXT(rseg_list, rseg); + ut_ad(rseg); + } + + return(rseg); +} + +/******************************************************************** +Creates a rollback segment header. This function is called only when +a new rollback segment is created in the database. */ +UNIV_INTERN +ulint +trx_rseg_header_create( +/*===================*/ + /* out: page number of the created segment, + FIL_NULL if fail */ + ulint space, /* in: space id */ + ulint zip_size, /* in: compressed page size in bytes + or 0 for uncompressed pages */ + ulint max_size, /* in: max size in pages */ + ulint* slot_no, /* out: rseg id == slot number in trx sys */ + mtr_t* mtr) /* in: mtr */ +{ + ulint page_no; + trx_rsegf_t* rsegf; + trx_sysf_t* sys_header; + ulint i; + buf_block_t* block; + + ut_ad(mtr); + ut_ad(mutex_own(&kernel_mutex)); + ut_ad(mtr_memo_contains(mtr, fil_space_get_latch(space, NULL), + MTR_MEMO_X_LOCK)); + sys_header = trx_sysf_get(mtr); + + *slot_no = trx_sysf_rseg_find_free(mtr); + + if (*slot_no == ULINT_UNDEFINED) { + + return(FIL_NULL); + } + + /* Allocate a new file segment for the rollback segment */ + block = fseg_create(space, 0, + TRX_RSEG + TRX_RSEG_FSEG_HEADER, mtr); + + if (block == NULL) { + /* No space left */ + + return(FIL_NULL); + } + + buf_block_dbg_add_level(block, SYNC_RSEG_HEADER_NEW); + + page_no = buf_block_get_page_no(block); + + /* Get the rollback segment file page */ + rsegf = trx_rsegf_get_new(space, zip_size, page_no, mtr); + + /* Initialize max size field */ + mlog_write_ulint(rsegf + TRX_RSEG_MAX_SIZE, max_size, + MLOG_4BYTES, mtr); + + /* Initialize the history list */ + + mlog_write_ulint(rsegf + TRX_RSEG_HISTORY_SIZE, 0, MLOG_4BYTES, mtr); + flst_init(rsegf + TRX_RSEG_HISTORY, mtr); + + /* Reset the undo log slots */ + for (i = 0; i < TRX_RSEG_N_SLOTS; i++) { + + trx_rsegf_set_nth_undo(rsegf, i, FIL_NULL, mtr); + } + + /* Add the rollback segment info to the free slot in the trx system + header */ + + trx_sysf_rseg_set_space(sys_header, *slot_no, space, mtr); + trx_sysf_rseg_set_page_no(sys_header, *slot_no, page_no, mtr); + + return(page_no); +} + +/*************************************************************************** +Creates and initializes a rollback segment object. The values for the +fields are read from the header. The object is inserted to the rseg +list of the trx system object and a pointer is inserted in the rseg +array in the trx system object. */ +static +trx_rseg_t* +trx_rseg_mem_create( +/*================*/ + /* out, own: rollback segment object */ + ulint id, /* in: rollback segment id */ + ulint space, /* in: space where the segment placed */ + ulint zip_size, /* in: compressed page size in bytes + or 0 for uncompressed pages */ + ulint page_no, /* in: page number of the segment header */ + mtr_t* mtr) /* in: mtr */ +{ + trx_rsegf_t* rseg_header; + trx_rseg_t* rseg; + trx_ulogf_t* undo_log_hdr; + fil_addr_t node_addr; + ulint sum_of_undo_sizes; + ulint len; + + ut_ad(mutex_own(&kernel_mutex)); + + rseg = mem_alloc(sizeof(trx_rseg_t)); + + rseg->id = id; + rseg->space = space; + rseg->zip_size = zip_size; + rseg->page_no = page_no; + + mutex_create(&rseg->mutex, SYNC_RSEG); + + UT_LIST_ADD_LAST(rseg_list, trx_sys->rseg_list, rseg); + + trx_sys_set_nth_rseg(trx_sys, id, rseg); + + rseg_header = trx_rsegf_get_new(space, zip_size, page_no, mtr); + + rseg->max_size = mtr_read_ulint(rseg_header + TRX_RSEG_MAX_SIZE, + MLOG_4BYTES, mtr); + + /* Initialize the undo log lists according to the rseg header */ + + sum_of_undo_sizes = trx_undo_lists_init(rseg); + + rseg->curr_size = mtr_read_ulint(rseg_header + TRX_RSEG_HISTORY_SIZE, + MLOG_4BYTES, mtr) + + 1 + sum_of_undo_sizes; + + len = flst_get_len(rseg_header + TRX_RSEG_HISTORY, mtr); + if (len > 0) { + trx_sys->rseg_history_len += len; + + node_addr = trx_purge_get_log_from_hist( + flst_get_last(rseg_header + TRX_RSEG_HISTORY, mtr)); + rseg->last_page_no = node_addr.page; + rseg->last_offset = node_addr.boffset; + + undo_log_hdr = trx_undo_page_get(rseg->space, rseg->zip_size, + node_addr.page, + mtr) + node_addr.boffset; + + rseg->last_trx_no = mtr_read_dulint( + undo_log_hdr + TRX_UNDO_TRX_NO, mtr); + rseg->last_del_marks = mtr_read_ulint( + undo_log_hdr + TRX_UNDO_DEL_MARKS, MLOG_2BYTES, mtr); + } else { + rseg->last_page_no = FIL_NULL; + } + + return(rseg); +} + +/************************************************************************* +Creates the memory copies for rollback segments and initializes the +rseg list and array in trx_sys at a database startup. */ +UNIV_INTERN +void +trx_rseg_list_and_array_init( +/*=========================*/ + trx_sysf_t* sys_header, /* in: trx system header */ + mtr_t* mtr) /* in: mtr */ +{ + ulint i; + ulint page_no; + ulint space; + + UT_LIST_INIT(trx_sys->rseg_list); + + trx_sys->rseg_history_len = 0; + + for (i = 0; i < TRX_SYS_N_RSEGS; i++) { + + page_no = trx_sysf_rseg_get_page_no(sys_header, i, mtr); + + if (page_no == FIL_NULL) { + + trx_sys_set_nth_rseg(trx_sys, i, NULL); + } else { + ulint zip_size; + + space = trx_sysf_rseg_get_space(sys_header, i, mtr); + + zip_size = space ? fil_space_get_zip_size(space) : 0; + + trx_rseg_mem_create(i, space, zip_size, page_no, mtr); + } + } +} + +/******************************************************************** +Creates a new rollback segment to the database. */ +UNIV_INTERN +trx_rseg_t* +trx_rseg_create( +/*============*/ + /* out: the created segment object, NULL if + fail */ + ulint space, /* in: space id */ + ulint max_size, /* in: max size in pages */ + ulint* id, /* out: rseg id */ + mtr_t* mtr) /* in: mtr */ +{ + ulint flags; + ulint zip_size; + ulint page_no; + trx_rseg_t* rseg; + + mtr_x_lock(fil_space_get_latch(space, &flags), mtr); + zip_size = dict_table_flags_to_zip_size(flags); + mutex_enter(&kernel_mutex); + + page_no = trx_rseg_header_create(space, zip_size, max_size, id, mtr); + + if (page_no == FIL_NULL) { + + mutex_exit(&kernel_mutex); + return(NULL); + } + + rseg = trx_rseg_mem_create(*id, space, zip_size, page_no, mtr); + + mutex_exit(&kernel_mutex); + + return(rseg); +} diff --git a/storage/xtradb/trx/trx0sys.c b/storage/xtradb/trx/trx0sys.c new file mode 100644 index 00000000000..b80a50738c0 --- /dev/null +++ b/storage/xtradb/trx/trx0sys.c @@ -0,0 +1,1391 @@ +/***************************************************************************** + +Copyright (c) 1996, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/****************************************************** +Transaction system + +Created 3/26/1996 Heikki Tuuri +*******************************************************/ + +#include "trx0sys.h" + +#ifdef UNIV_NONINL +#include "trx0sys.ic" +#endif + +#include "fsp0fsp.h" +#include "mtr0mtr.h" +#include "trx0trx.h" +#include "trx0rseg.h" +#include "trx0undo.h" +#include "srv0srv.h" +#include "trx0purge.h" +#include "log0log.h" +#include "os0file.h" + +/* The file format tag structure with id and name. */ +struct file_format_struct { + ulint id; /* id of the file format */ + const char* name; /* text representation of the + file format */ + mutex_t mutex; /* covers changes to the above + fields */ +}; + +typedef struct file_format_struct file_format_t; + +/* The transaction system */ +UNIV_INTERN trx_sys_t* trx_sys = NULL; +UNIV_INTERN trx_doublewrite_t* trx_doublewrite = NULL; + +/* The following is set to TRUE when we are upgrading from the old format data +files to the new >= 4.1.x format multiple tablespaces format data files */ + +UNIV_INTERN ibool trx_doublewrite_must_reset_space_ids = FALSE; + +/* The following is TRUE when we are using the database in the new format, +i.e., we have successfully upgraded, or have created a new database +installation */ + +UNIV_INTERN ibool trx_sys_multiple_tablespace_format = FALSE; + +/* In a MySQL replication slave, in crash recovery we store the master log +file name and position here. We have successfully got the updates to InnoDB +up to this position. If .._pos is -1, it means no crash recovery was needed, +or there was no master log position info inside InnoDB. */ + +UNIV_INTERN char trx_sys_mysql_master_log_name[TRX_SYS_MYSQL_MASTER_LOG_NAME_LEN]; +UNIV_INTERN ib_int64_t trx_sys_mysql_master_log_pos = -1; + +UNIV_INTERN char trx_sys_mysql_relay_log_name[TRX_SYS_MYSQL_MASTER_LOG_NAME_LEN]; +UNIV_INTERN ib_int64_t trx_sys_mysql_relay_log_pos = -1; + +/* If this MySQL server uses binary logging, after InnoDB has been inited +and if it has done a crash recovery, we store the binlog file name and position +here. If .._pos is -1, it means there was no binlog position info inside +InnoDB. */ + +UNIV_INTERN char trx_sys_mysql_bin_log_name[TRX_SYS_MYSQL_LOG_NAME_LEN]; +UNIV_INTERN ib_int64_t trx_sys_mysql_bin_log_pos = -1; + +/* List of animal names representing file format. */ +static const char* file_format_name_map[] = { + "Antelope", + "Barracuda", + "Cheetah", + "Dragon", + "Elk", + "Fox", + "Gazelle", + "Hornet", + "Impala", + "Jaguar", + "Kangaroo", + "Leopard", + "Moose", + "Nautilus", + "Ocelot", + "Porpoise", + "Quail", + "Rabbit", + "Shark", + "Tiger", + "Urchin", + "Viper", + "Whale", + "Xenops", + "Yak", + "Zebra" +}; + +/* The number of elements in the file format name array. */ +static const ulint FILE_FORMAT_NAME_N + = sizeof(file_format_name_map) / sizeof(file_format_name_map[0]); + +/* This is used to track the maximum file format id known to InnoDB. It's +updated via SET GLOBAL innodb_file_format_check = 'x' or when we open +or create a table. */ +static file_format_t file_format_max; + +/******************************************************************** +Determines if a page number is located inside the doublewrite buffer. */ +UNIV_INTERN +ibool +trx_doublewrite_page_inside( +/*========================*/ + /* out: TRUE if the location is inside + the two blocks of the doublewrite buffer */ + ulint page_no) /* in: page number */ +{ + if (trx_doublewrite == NULL) { + + return(FALSE); + } + + if (page_no >= trx_doublewrite->block1 + && page_no < trx_doublewrite->block1 + + TRX_SYS_DOUBLEWRITE_BLOCK_SIZE) { + return(TRUE); + } + + if (page_no >= trx_doublewrite->block2 + && page_no < trx_doublewrite->block2 + + TRX_SYS_DOUBLEWRITE_BLOCK_SIZE) { + return(TRUE); + } + + return(FALSE); +} + +/******************************************************************** +Creates or initialializes the doublewrite buffer at a database start. */ +static +void +trx_doublewrite_init( +/*=================*/ + byte* doublewrite) /* in: pointer to the doublewrite buf + header on trx sys page */ +{ + trx_doublewrite = mem_alloc(sizeof(trx_doublewrite_t)); + + /* Since we now start to use the doublewrite buffer, no need to call + fsync() after every write to a data file */ +#ifdef UNIV_DO_FLUSH + os_do_not_call_flush_at_each_write = TRUE; +#endif /* UNIV_DO_FLUSH */ + + mutex_create(&trx_doublewrite->mutex, SYNC_DOUBLEWRITE); + + trx_doublewrite->first_free = 0; + + trx_doublewrite->block1 = mach_read_from_4( + doublewrite + TRX_SYS_DOUBLEWRITE_BLOCK1); + trx_doublewrite->block2 = mach_read_from_4( + doublewrite + TRX_SYS_DOUBLEWRITE_BLOCK2); + trx_doublewrite->write_buf_unaligned = ut_malloc( + (1 + 2 * TRX_SYS_DOUBLEWRITE_BLOCK_SIZE) * UNIV_PAGE_SIZE); + + trx_doublewrite->write_buf = ut_align( + trx_doublewrite->write_buf_unaligned, UNIV_PAGE_SIZE); + trx_doublewrite->buf_block_arr = mem_alloc( + 2 * TRX_SYS_DOUBLEWRITE_BLOCK_SIZE * sizeof(void*)); +} + +/******************************************************************** +Marks the trx sys header when we have successfully upgraded to the >= 4.1.x +multiple tablespace format. */ +UNIV_INTERN +void +trx_sys_mark_upgraded_to_multiple_tablespaces(void) +/*===============================================*/ +{ + buf_block_t* block; + byte* doublewrite; + mtr_t mtr; + + /* We upgraded to 4.1.x and reset the space id fields in the + doublewrite buffer. Let us mark to the trx_sys header that the upgrade + has been done. */ + + mtr_start(&mtr); + + block = buf_page_get(TRX_SYS_SPACE, 0, TRX_SYS_PAGE_NO, + RW_X_LATCH, &mtr); + buf_block_dbg_add_level(block, SYNC_NO_ORDER_CHECK); + + doublewrite = buf_block_get_frame(block) + TRX_SYS_DOUBLEWRITE; + + mlog_write_ulint(doublewrite + TRX_SYS_DOUBLEWRITE_SPACE_ID_STORED, + TRX_SYS_DOUBLEWRITE_SPACE_ID_STORED_N, + MLOG_4BYTES, &mtr); + mtr_commit(&mtr); + + /* Flush the modified pages to disk and make a checkpoint */ + log_make_checkpoint_at(IB_ULONGLONG_MAX, TRUE); + + trx_sys_multiple_tablespace_format = TRUE; +} + +/******************************************************************** +Creates the doublewrite buffer to a new InnoDB installation. The header of the +doublewrite buffer is placed on the trx system header page. */ +UNIV_INTERN +void +trx_sys_create_doublewrite_buf(void) +/*================================*/ +{ + buf_block_t* block; + buf_block_t* block2; + buf_block_t* new_block; + byte* doublewrite; + byte* fseg_header; + ulint page_no; + ulint prev_page_no; + ulint i; + mtr_t mtr; + + if (trx_doublewrite) { + /* Already inited */ + + return; + } + +start_again: + mtr_start(&mtr); + + block = buf_page_get(TRX_SYS_SPACE, 0, TRX_SYS_PAGE_NO, + RW_X_LATCH, &mtr); + buf_block_dbg_add_level(block, SYNC_NO_ORDER_CHECK); + + doublewrite = buf_block_get_frame(block) + TRX_SYS_DOUBLEWRITE; + + if (mach_read_from_4(doublewrite + TRX_SYS_DOUBLEWRITE_MAGIC) + == TRX_SYS_DOUBLEWRITE_MAGIC_N) { + /* The doublewrite buffer has already been created: + just read in some numbers */ + + trx_doublewrite_init(doublewrite); + + mtr_commit(&mtr); + } else { + fprintf(stderr, + "InnoDB: Doublewrite buffer not found:" + " creating new\n"); + + if (buf_pool_get_curr_size() + < ((2 * TRX_SYS_DOUBLEWRITE_BLOCK_SIZE + + FSP_EXTENT_SIZE / 2 + 100) + * UNIV_PAGE_SIZE)) { + fprintf(stderr, + "InnoDB: Cannot create doublewrite buffer:" + " you must\n" + "InnoDB: increase your buffer pool size.\n" + "InnoDB: Cannot continue operation.\n"); + + exit(1); + } + + block2 = fseg_create(TRX_SYS_SPACE, TRX_SYS_PAGE_NO, + TRX_SYS_DOUBLEWRITE + + TRX_SYS_DOUBLEWRITE_FSEG, &mtr); + + /* fseg_create acquires a second latch on the page, + therefore we must declare it: */ + + buf_block_dbg_add_level(block2, SYNC_NO_ORDER_CHECK); + + if (block2 == NULL) { + fprintf(stderr, + "InnoDB: Cannot create doublewrite buffer:" + " you must\n" + "InnoDB: increase your tablespace size.\n" + "InnoDB: Cannot continue operation.\n"); + + /* We exit without committing the mtr to prevent + its modifications to the database getting to disk */ + + exit(1); + } + + fseg_header = buf_block_get_frame(block) + + TRX_SYS_DOUBLEWRITE + TRX_SYS_DOUBLEWRITE_FSEG; + prev_page_no = 0; + + for (i = 0; i < 2 * TRX_SYS_DOUBLEWRITE_BLOCK_SIZE + + FSP_EXTENT_SIZE / 2; i++) { + page_no = fseg_alloc_free_page(fseg_header, + prev_page_no + 1, + FSP_UP, &mtr); + if (page_no == FIL_NULL) { + fprintf(stderr, + "InnoDB: Cannot create doublewrite" + " buffer: you must\n" + "InnoDB: increase your" + " tablespace size.\n" + "InnoDB: Cannot continue operation.\n" + ); + + exit(1); + } + + /* We read the allocated pages to the buffer pool; + when they are written to disk in a flush, the space + id and page number fields are also written to the + pages. When we at database startup read pages + from the doublewrite buffer, we know that if the + space id and page number in them are the same as + the page position in the tablespace, then the page + has not been written to in doublewrite. */ + + new_block = buf_page_get(TRX_SYS_SPACE, 0, page_no, + RW_X_LATCH, &mtr); + buf_block_dbg_add_level(new_block, + SYNC_NO_ORDER_CHECK); + + /* Make a dummy change to the page to ensure it will + be written to disk in a flush */ + + mlog_write_ulint(buf_block_get_frame(new_block) + + FIL_PAGE_DATA, + TRX_SYS_DOUBLEWRITE_MAGIC_N, + MLOG_4BYTES, &mtr); + + if (i == FSP_EXTENT_SIZE / 2) { + mlog_write_ulint(doublewrite + + TRX_SYS_DOUBLEWRITE_BLOCK1, + page_no, MLOG_4BYTES, &mtr); + mlog_write_ulint(doublewrite + + TRX_SYS_DOUBLEWRITE_REPEAT + + TRX_SYS_DOUBLEWRITE_BLOCK1, + page_no, MLOG_4BYTES, &mtr); + } else if (i == FSP_EXTENT_SIZE / 2 + + TRX_SYS_DOUBLEWRITE_BLOCK_SIZE) { + mlog_write_ulint(doublewrite + + TRX_SYS_DOUBLEWRITE_BLOCK2, + page_no, MLOG_4BYTES, &mtr); + mlog_write_ulint(doublewrite + + TRX_SYS_DOUBLEWRITE_REPEAT + + TRX_SYS_DOUBLEWRITE_BLOCK2, + page_no, MLOG_4BYTES, &mtr); + } else if (i > FSP_EXTENT_SIZE / 2) { + ut_a(page_no == prev_page_no + 1); + } + + prev_page_no = page_no; + } + + mlog_write_ulint(doublewrite + TRX_SYS_DOUBLEWRITE_MAGIC, + TRX_SYS_DOUBLEWRITE_MAGIC_N, + MLOG_4BYTES, &mtr); + mlog_write_ulint(doublewrite + TRX_SYS_DOUBLEWRITE_MAGIC + + TRX_SYS_DOUBLEWRITE_REPEAT, + TRX_SYS_DOUBLEWRITE_MAGIC_N, + MLOG_4BYTES, &mtr); + + mlog_write_ulint(doublewrite + + TRX_SYS_DOUBLEWRITE_SPACE_ID_STORED, + TRX_SYS_DOUBLEWRITE_SPACE_ID_STORED_N, + MLOG_4BYTES, &mtr); + mtr_commit(&mtr); + + /* Flush the modified pages to disk and make a checkpoint */ + log_make_checkpoint_at(IB_ULONGLONG_MAX, TRUE); + + fprintf(stderr, "InnoDB: Doublewrite buffer created\n"); + + trx_sys_multiple_tablespace_format = TRUE; + + goto start_again; + } +} + +/******************************************************************** +At a database startup initializes the doublewrite buffer memory structure if +we already have a doublewrite buffer created in the data files. If we are +upgrading to an InnoDB version which supports multiple tablespaces, then this +function performs the necessary update operations. If we are in a crash +recovery, this function uses a possible doublewrite buffer to restore +half-written pages in the data files. */ +UNIV_INTERN +void +trx_sys_doublewrite_init_or_restore_pages( +/*======================================*/ + ibool restore_corrupt_pages) +{ + byte* buf; + byte* read_buf; + byte* unaligned_read_buf; + ulint block1; + ulint block2; + ulint source_page_no; + byte* page; + byte* doublewrite; + ulint space_id; + ulint page_no; + ulint i; + + /* We do the file i/o past the buffer pool */ + + unaligned_read_buf = ut_malloc(2 * UNIV_PAGE_SIZE); + read_buf = ut_align(unaligned_read_buf, UNIV_PAGE_SIZE); + + /* Read the trx sys header to check if we are using the doublewrite + buffer */ + + fil_io(OS_FILE_READ, TRUE, TRX_SYS_SPACE, 0, TRX_SYS_PAGE_NO, 0, + UNIV_PAGE_SIZE, read_buf, NULL); + doublewrite = read_buf + TRX_SYS_DOUBLEWRITE; + + if (mach_read_from_4(doublewrite + TRX_SYS_DOUBLEWRITE_MAGIC) + == TRX_SYS_DOUBLEWRITE_MAGIC_N) { + /* The doublewrite buffer has been created */ + + trx_doublewrite_init(doublewrite); + + block1 = trx_doublewrite->block1; + block2 = trx_doublewrite->block2; + + buf = trx_doublewrite->write_buf; + } else { + goto leave_func; + } + + if (mach_read_from_4(doublewrite + TRX_SYS_DOUBLEWRITE_SPACE_ID_STORED) + != TRX_SYS_DOUBLEWRITE_SPACE_ID_STORED_N) { + + /* We are upgrading from a version < 4.1.x to a version where + multiple tablespaces are supported. We must reset the space id + field in the pages in the doublewrite buffer because starting + from this version the space id is stored to + FIL_PAGE_ARCH_LOG_NO_OR_SPACE_ID. */ + + trx_doublewrite_must_reset_space_ids = TRUE; + + fprintf(stderr, + "InnoDB: Resetting space id's in the" + " doublewrite buffer\n"); + } else { + trx_sys_multiple_tablespace_format = TRUE; + } + + /* Read the pages from the doublewrite buffer to memory */ + + fil_io(OS_FILE_READ, TRUE, TRX_SYS_SPACE, 0, block1, 0, + TRX_SYS_DOUBLEWRITE_BLOCK_SIZE * UNIV_PAGE_SIZE, + buf, NULL); + fil_io(OS_FILE_READ, TRUE, TRX_SYS_SPACE, 0, block2, 0, + TRX_SYS_DOUBLEWRITE_BLOCK_SIZE * UNIV_PAGE_SIZE, + buf + TRX_SYS_DOUBLEWRITE_BLOCK_SIZE * UNIV_PAGE_SIZE, + NULL); + /* Check if any of these pages is half-written in data files, in the + intended position */ + + page = buf; + + for (i = 0; i < TRX_SYS_DOUBLEWRITE_BLOCK_SIZE * 2; i++) { + + page_no = mach_read_from_4(page + FIL_PAGE_OFFSET); + + if (trx_doublewrite_must_reset_space_ids) { + + space_id = 0; + mach_write_to_4(page + + FIL_PAGE_ARCH_LOG_NO_OR_SPACE_ID, 0); + /* We do not need to calculate new checksums for the + pages because the field .._SPACE_ID does not affect + them. Write the page back to where we read it from. */ + + if (i < TRX_SYS_DOUBLEWRITE_BLOCK_SIZE) { + source_page_no = block1 + i; + } else { + source_page_no = block2 + + i - TRX_SYS_DOUBLEWRITE_BLOCK_SIZE; + } + + fil_io(OS_FILE_WRITE, TRUE, 0, 0, source_page_no, 0, + UNIV_PAGE_SIZE, page, NULL); + /* printf("Resetting space id in page %lu\n", + source_page_no); */ + } else { + space_id = mach_read_from_4( + page + FIL_PAGE_ARCH_LOG_NO_OR_SPACE_ID); + } + + if (!restore_corrupt_pages) { + /* The database was shut down gracefully: no need to + restore pages */ + + } else if (!fil_tablespace_exists_in_mem(space_id)) { + /* Maybe we have dropped the single-table tablespace + and this page once belonged to it: do nothing */ + + } else if (!fil_check_adress_in_tablespace(space_id, + page_no)) { + fprintf(stderr, + "InnoDB: Warning: a page in the" + " doublewrite buffer is not within space\n" + "InnoDB: bounds; space id %lu" + " page number %lu, page %lu in" + " doublewrite buf.\n", + (ulong) space_id, (ulong) page_no, (ulong) i); + + } else if (space_id == TRX_SYS_SPACE + && ((page_no >= block1 + && page_no + < block1 + TRX_SYS_DOUBLEWRITE_BLOCK_SIZE) + || (page_no >= block2 + && page_no + < (block2 + + TRX_SYS_DOUBLEWRITE_BLOCK_SIZE)))) { + + /* It is an unwritten doublewrite buffer page: + do nothing */ + } else { + ulint zip_size = fil_space_get_zip_size(space_id); + + /* Read in the actual page from the file */ + fil_io(OS_FILE_READ, TRUE, space_id, zip_size, + page_no, 0, + zip_size ? zip_size : UNIV_PAGE_SIZE, + read_buf, NULL); + + /* Check if the page is corrupt */ + + if (UNIV_UNLIKELY + (buf_page_is_corrupted(read_buf, zip_size))) { + + fprintf(stderr, + "InnoDB: Warning: database page" + " corruption or a failed\n" + "InnoDB: file read of" + " space %lu page %lu.\n" + "InnoDB: Trying to recover it from" + " the doublewrite buffer.\n", + (ulong) space_id, (ulong) page_no); + + if (buf_page_is_corrupted(page, zip_size)) { + fprintf(stderr, + "InnoDB: Dump of the page:\n"); + buf_page_print(read_buf, zip_size); + fprintf(stderr, + "InnoDB: Dump of" + " corresponding page" + " in doublewrite buffer:\n"); + buf_page_print(page, zip_size); + + fprintf(stderr, + "InnoDB: Also the page in the" + " doublewrite buffer" + " is corrupt.\n" + "InnoDB: Cannot continue" + " operation.\n" + "InnoDB: You can try to" + " recover the database" + " with the my.cnf\n" + "InnoDB: option:\n" + "InnoDB: set-variable=" + "innodb_force_recovery=6\n"); + exit(1); + } + + /* Write the good page from the + doublewrite buffer to the intended + position */ + + fil_io(OS_FILE_WRITE, TRUE, space_id, + zip_size, page_no, 0, + zip_size ? zip_size : UNIV_PAGE_SIZE, + page, NULL); + fprintf(stderr, + "InnoDB: Recovered the page from" + " the doublewrite buffer.\n"); + } + } + + page += UNIV_PAGE_SIZE; + } + + fil_flush_file_spaces(FIL_TABLESPACE); + +leave_func: + ut_free(unaligned_read_buf); +} + +/******************************************************************** +Checks that trx is in the trx list. */ +UNIV_INTERN +ibool +trx_in_trx_list( +/*============*/ + /* out: TRUE if is in */ + trx_t* in_trx) /* in: trx */ +{ + trx_t* trx; + + ut_ad(mutex_own(&(kernel_mutex))); + + trx = UT_LIST_GET_FIRST(trx_sys->trx_list); + + while (trx != NULL) { + + if (trx == in_trx) { + + return(TRUE); + } + + trx = UT_LIST_GET_NEXT(trx_list, trx); + } + + return(FALSE); +} + +/********************************************************************* +Writes the value of max_trx_id to the file based trx system header. */ +UNIV_INTERN +void +trx_sys_flush_max_trx_id(void) +/*==========================*/ +{ + trx_sysf_t* sys_header; + mtr_t mtr; + + ut_ad(mutex_own(&kernel_mutex)); + + mtr_start(&mtr); + + sys_header = trx_sysf_get(&mtr); + + mlog_write_dulint(sys_header + TRX_SYS_TRX_ID_STORE, + trx_sys->max_trx_id, &mtr); + mtr_commit(&mtr); +} + +/********************************************************************* +Updates the offset information about the end of the MySQL binlog entry +which corresponds to the transaction just being committed. In a MySQL +replication slave updates the latest master binlog position up to which +replication has proceeded. */ +UNIV_INTERN +void +trx_sys_update_mysql_binlog_offset( +/*===============================*/ + const char* file_name_in,/* in: MySQL log file name */ + ib_int64_t offset, /* in: position in that log file */ + ulint field, /* in: offset of the MySQL log info field in + the trx sys header */ + mtr_t* mtr) /* in: mtr */ +{ + trx_sysf_t* sys_header; + const char* file_name; + + if (ut_strlen(file_name_in) >= TRX_SYS_MYSQL_MASTER_LOG_NAME_LEN) { + + /* We cannot fit the name to the 512 bytes we have reserved */ + /* -> To store relay log file information, file_name must fit to the 480 bytes */ + + file_name = ""; + } + else { + file_name = file_name_in; + } + + sys_header = trx_sysf_get(mtr); + + if (mach_read_from_4(sys_header + field + + TRX_SYS_MYSQL_LOG_MAGIC_N_FLD) + != TRX_SYS_MYSQL_LOG_MAGIC_N) { + + mlog_write_ulint(sys_header + field + + TRX_SYS_MYSQL_LOG_MAGIC_N_FLD, + TRX_SYS_MYSQL_LOG_MAGIC_N, + MLOG_4BYTES, mtr); + } + + if (0 != strcmp((char*) (sys_header + field + TRX_SYS_MYSQL_LOG_NAME), + file_name)) { + + mlog_write_string(sys_header + field + + TRX_SYS_MYSQL_LOG_NAME, + (byte*) file_name, 1 + ut_strlen(file_name), + mtr); + } + + if (mach_read_from_4(sys_header + field + + TRX_SYS_MYSQL_LOG_OFFSET_HIGH) > 0 + || (offset >> 32) > 0) { + + mlog_write_ulint(sys_header + field + + TRX_SYS_MYSQL_LOG_OFFSET_HIGH, + (ulint)(offset >> 32), + MLOG_4BYTES, mtr); + } + + mlog_write_ulint(sys_header + field + + TRX_SYS_MYSQL_LOG_OFFSET_LOW, + (ulint)(offset & 0xFFFFFFFFUL), + MLOG_4BYTES, mtr); +} + +#ifdef UNIV_HOTBACKUP +/********************************************************************* +Prints to stderr the MySQL binlog info in the system header if the +magic number shows it valid. */ +UNIV_INTERN +void +trx_sys_print_mysql_binlog_offset_from_page( +/*========================================*/ + const byte* page) /* in: buffer containing the trx + system header page, i.e., page number + TRX_SYS_PAGE_NO in the tablespace */ +{ + const trx_sysf_t* sys_header; + + sys_header = page + TRX_SYS; + + if (mach_read_from_4(sys_header + TRX_SYS_MYSQL_LOG_INFO + + TRX_SYS_MYSQL_LOG_MAGIC_N_FLD) + == TRX_SYS_MYSQL_LOG_MAGIC_N) { + + fprintf(stderr, + "ibbackup: Last MySQL binlog file position %lu %lu," + " file name %s\n", + (ulong) mach_read_from_4( + sys_header + TRX_SYS_MYSQL_LOG_INFO + + TRX_SYS_MYSQL_LOG_OFFSET_HIGH), + (ulong) mach_read_from_4( + sys_header + TRX_SYS_MYSQL_LOG_INFO + + TRX_SYS_MYSQL_LOG_OFFSET_LOW), + sys_header + TRX_SYS_MYSQL_LOG_INFO + + TRX_SYS_MYSQL_LOG_NAME); + } +} +#endif /* UNIV_HOTBACKUP */ + +/********************************************************************* +Stores the MySQL binlog offset info in the trx system header if +the magic number shows it valid, and print the info to stderr */ +UNIV_INTERN +void +trx_sys_print_mysql_binlog_offset(void) +/*===================================*/ +{ + trx_sysf_t* sys_header; + mtr_t mtr; + ulint trx_sys_mysql_bin_log_pos_high; + ulint trx_sys_mysql_bin_log_pos_low; + + mtr_start(&mtr); + + sys_header = trx_sysf_get(&mtr); + + if (mach_read_from_4(sys_header + TRX_SYS_MYSQL_LOG_INFO + + TRX_SYS_MYSQL_LOG_MAGIC_N_FLD) + != TRX_SYS_MYSQL_LOG_MAGIC_N) { + + mtr_commit(&mtr); + + return; + } + + trx_sys_mysql_bin_log_pos_high = mach_read_from_4( + sys_header + TRX_SYS_MYSQL_LOG_INFO + + TRX_SYS_MYSQL_LOG_OFFSET_HIGH); + trx_sys_mysql_bin_log_pos_low = mach_read_from_4( + sys_header + TRX_SYS_MYSQL_LOG_INFO + + TRX_SYS_MYSQL_LOG_OFFSET_LOW); + + trx_sys_mysql_bin_log_pos + = (((ib_int64_t)trx_sys_mysql_bin_log_pos_high) << 32) + + (ib_int64_t)trx_sys_mysql_bin_log_pos_low; + + ut_memcpy(trx_sys_mysql_bin_log_name, + sys_header + TRX_SYS_MYSQL_LOG_INFO + + TRX_SYS_MYSQL_LOG_NAME, TRX_SYS_MYSQL_LOG_NAME_LEN); + + fprintf(stderr, + "InnoDB: Last MySQL binlog file position %lu %lu," + " file name %s\n", + trx_sys_mysql_bin_log_pos_high, trx_sys_mysql_bin_log_pos_low, + trx_sys_mysql_bin_log_name); + + mtr_commit(&mtr); +} + +/********************************************************************* +Prints to stderr the MySQL master log offset info in the trx system header if +the magic number shows it valid. */ +UNIV_INTERN +void +trx_sys_print_mysql_master_log_pos(void) +/*====================================*/ +{ + trx_sysf_t* sys_header; + mtr_t mtr; + + mtr_start(&mtr); + + sys_header = trx_sysf_get(&mtr); + + if (mach_read_from_4(sys_header + TRX_SYS_MYSQL_MASTER_LOG_INFO + + TRX_SYS_MYSQL_LOG_MAGIC_N_FLD) + != TRX_SYS_MYSQL_LOG_MAGIC_N) { + + mtr_commit(&mtr); + + return; + } + + fprintf(stderr, + "InnoDB: In a MySQL replication slave the last" + " master binlog file\n" + "InnoDB: position %lu %lu, file name %s\n", + (ulong) mach_read_from_4(sys_header + + TRX_SYS_MYSQL_MASTER_LOG_INFO + + TRX_SYS_MYSQL_LOG_OFFSET_HIGH), + (ulong) mach_read_from_4(sys_header + + TRX_SYS_MYSQL_MASTER_LOG_INFO + + TRX_SYS_MYSQL_LOG_OFFSET_LOW), + sys_header + TRX_SYS_MYSQL_MASTER_LOG_INFO + + TRX_SYS_MYSQL_LOG_NAME); + + fprintf(stderr, + "InnoDB: and relay log file\n" + "InnoDB: position %lu %lu, file name %s\n", + (ulong) mach_read_from_4(sys_header + + TRX_SYS_MYSQL_RELAY_LOG_INFO + + TRX_SYS_MYSQL_LOG_OFFSET_HIGH), + (ulong) mach_read_from_4(sys_header + + TRX_SYS_MYSQL_RELAY_LOG_INFO + + TRX_SYS_MYSQL_LOG_OFFSET_LOW), + sys_header + TRX_SYS_MYSQL_RELAY_LOG_INFO + + TRX_SYS_MYSQL_LOG_NAME); + + /* Copy the master log position info to global variables we can + use in ha_innobase.cc to initialize glob_mi to right values */ + + ut_memcpy(trx_sys_mysql_master_log_name, + sys_header + TRX_SYS_MYSQL_MASTER_LOG_INFO + + TRX_SYS_MYSQL_LOG_NAME, + TRX_SYS_MYSQL_MASTER_LOG_NAME_LEN); + + trx_sys_mysql_master_log_pos + = (((ib_int64_t) mach_read_from_4( + sys_header + TRX_SYS_MYSQL_MASTER_LOG_INFO + + TRX_SYS_MYSQL_LOG_OFFSET_HIGH)) << 32) + + ((ib_int64_t) mach_read_from_4( + sys_header + TRX_SYS_MYSQL_MASTER_LOG_INFO + + TRX_SYS_MYSQL_LOG_OFFSET_LOW)); + + ut_memcpy(trx_sys_mysql_relay_log_name, + sys_header + TRX_SYS_MYSQL_RELAY_LOG_INFO + + TRX_SYS_MYSQL_LOG_NAME, + TRX_SYS_MYSQL_MASTER_LOG_NAME_LEN); + + trx_sys_mysql_relay_log_pos + = (((ib_int64_t) mach_read_from_4( + sys_header + TRX_SYS_MYSQL_RELAY_LOG_INFO + + TRX_SYS_MYSQL_LOG_OFFSET_HIGH)) << 32) + + ((ib_int64_t) mach_read_from_4( + sys_header + TRX_SYS_MYSQL_RELAY_LOG_INFO + + TRX_SYS_MYSQL_LOG_OFFSET_LOW)); + mtr_commit(&mtr); +} + +/******************************************************************** +Looks for a free slot for a rollback segment in the trx system file copy. */ +UNIV_INTERN +ulint +trx_sysf_rseg_find_free( +/*====================*/ + /* out: slot index or ULINT_UNDEFINED if not found */ + mtr_t* mtr) /* in: mtr */ +{ + trx_sysf_t* sys_header; + ulint page_no; + ulint i; + + ut_ad(mutex_own(&(kernel_mutex))); + + sys_header = trx_sysf_get(mtr); + + for (i = 0; i < TRX_SYS_N_RSEGS; i++) { + + page_no = trx_sysf_rseg_get_page_no(sys_header, i, mtr); + + if (page_no == FIL_NULL) { + + return(i); + } + } + + return(ULINT_UNDEFINED); +} + +/********************************************************************* +Creates the file page for the transaction system. This function is called only +at the database creation, before trx_sys_init. */ +static +void +trx_sysf_create( +/*============*/ + mtr_t* mtr) /* in: mtr */ +{ + trx_sysf_t* sys_header; + ulint slot_no; + buf_block_t* block; + page_t* page; + ulint page_no; + ulint i; + + ut_ad(mtr); + + /* Note that below we first reserve the file space x-latch, and + then enter the kernel: we must do it in this order to conform + to the latching order rules. */ + + mtr_x_lock(fil_space_get_latch(TRX_SYS_SPACE, NULL), mtr); + mutex_enter(&kernel_mutex); + + /* Create the trx sys file block in a new allocated file segment */ + block = fseg_create(TRX_SYS_SPACE, 0, TRX_SYS + TRX_SYS_FSEG_HEADER, + mtr); + buf_block_dbg_add_level(block, SYNC_TRX_SYS_HEADER); + + ut_a(buf_block_get_page_no(block) == TRX_SYS_PAGE_NO); + + page = buf_block_get_frame(block); + + mlog_write_ulint(page + FIL_PAGE_TYPE, FIL_PAGE_TYPE_TRX_SYS, + MLOG_2BYTES, mtr); + + /* Reset the doublewrite buffer magic number to zero so that we + know that the doublewrite buffer has not yet been created (this + suppresses a Valgrind warning) */ + + mlog_write_ulint(page + TRX_SYS_DOUBLEWRITE + + TRX_SYS_DOUBLEWRITE_MAGIC, 0, MLOG_4BYTES, mtr); + + sys_header = trx_sysf_get(mtr); + + /* Start counting transaction ids from number 1 up */ + mlog_write_dulint(sys_header + TRX_SYS_TRX_ID_STORE, + ut_dulint_create(0, 1), mtr); + + /* Reset the rollback segment slots */ + for (i = 0; i < TRX_SYS_N_RSEGS; i++) { + + trx_sysf_rseg_set_space(sys_header, i, ULINT_UNDEFINED, mtr); + trx_sysf_rseg_set_page_no(sys_header, i, FIL_NULL, mtr); + } + + /* The remaining area (up to the page trailer) is uninitialized. + Silence Valgrind warnings about it. */ + UNIV_MEM_VALID(sys_header + (TRX_SYS_RSEGS + + TRX_SYS_N_RSEGS * TRX_SYS_RSEG_SLOT_SIZE + + TRX_SYS_RSEG_SPACE), + (UNIV_PAGE_SIZE - FIL_PAGE_DATA_END + - (TRX_SYS_RSEGS + + TRX_SYS_N_RSEGS * TRX_SYS_RSEG_SLOT_SIZE + + TRX_SYS_RSEG_SPACE)) + + page - sys_header); + + /* Create the first rollback segment in the SYSTEM tablespace */ + page_no = trx_rseg_header_create(TRX_SYS_SPACE, 0, ULINT_MAX, &slot_no, + mtr); + ut_a(slot_no == TRX_SYS_SYSTEM_RSEG_ID); + ut_a(page_no != FIL_NULL); + + mutex_exit(&kernel_mutex); +} + +/********************************************************************* +Creates and initializes the central memory structures for the transaction +system. This is called when the database is started. */ +UNIV_INTERN +void +trx_sys_init_at_db_start(void) +/*==========================*/ +{ + trx_sysf_t* sys_header; + ib_int64_t rows_to_undo = 0; + const char* unit = ""; + trx_t* trx; + mtr_t mtr; + + mtr_start(&mtr); + + ut_ad(trx_sys == NULL); + + mutex_enter(&kernel_mutex); + + trx_sys = mem_alloc(sizeof(trx_sys_t)); + + sys_header = trx_sysf_get(&mtr); + + trx_rseg_list_and_array_init(sys_header, &mtr); + + trx_sys->latest_rseg = UT_LIST_GET_FIRST(trx_sys->rseg_list); + + /* VERY important: after the database is started, max_trx_id value is + divisible by TRX_SYS_TRX_ID_WRITE_MARGIN, and the 'if' in + trx_sys_get_new_trx_id will evaluate to TRUE when the function + is first time called, and the value for trx id will be written + to the disk-based header! Thus trx id values will not overlap when + the database is repeatedly started! */ + + trx_sys->max_trx_id = ut_dulint_add( + ut_dulint_align_up(mtr_read_dulint( + sys_header + + TRX_SYS_TRX_ID_STORE, &mtr), + TRX_SYS_TRX_ID_WRITE_MARGIN), + 2 * TRX_SYS_TRX_ID_WRITE_MARGIN); + + UT_LIST_INIT(trx_sys->mysql_trx_list); + trx_dummy_sess = sess_open(); + trx_lists_init_at_db_start(); + + if (UT_LIST_GET_LEN(trx_sys->trx_list) > 0) { + trx = UT_LIST_GET_FIRST(trx_sys->trx_list); + + for (;;) { + + if ( trx->conc_state != TRX_PREPARED) { + rows_to_undo += ut_conv_dulint_to_longlong( + trx->undo_no); + } + + trx = UT_LIST_GET_NEXT(trx_list, trx); + + if (!trx) { + break; + } + } + + if (rows_to_undo > 1000000000) { + unit = "M"; + rows_to_undo = rows_to_undo / 1000000; + } + + fprintf(stderr, + "InnoDB: %lu transaction(s) which must be" + " rolled back or cleaned up\n" + "InnoDB: in total %lu%s row operations to undo\n", + (ulong) UT_LIST_GET_LEN(trx_sys->trx_list), + (ulong) rows_to_undo, unit); + + fprintf(stderr, "InnoDB: Trx id counter is " TRX_ID_FMT "\n", + TRX_ID_PREP_PRINTF(trx_sys->max_trx_id)); + } + + UT_LIST_INIT(trx_sys->view_list); + + trx_purge_sys_create(); + + mutex_exit(&kernel_mutex); + + mtr_commit(&mtr); +} + +/********************************************************************* +Creates and initializes the transaction system at the database creation. */ +UNIV_INTERN +void +trx_sys_create(void) +/*================*/ +{ + mtr_t mtr; + + mtr_start(&mtr); + + trx_sysf_create(&mtr); + + mtr_commit(&mtr); + + trx_sys_init_at_db_start(); +} + +/********************************************************************* +Create extra rollback segments when create_new_db */ +UNIV_INTERN +void +trx_sys_create_extra_rseg( +/*======================*/ + ulint num) /* in: number of extra user rollback segments */ +{ + mtr_t mtr; + ulint slot_no; + ulint i; + + /* Craete extra rollback segments */ + mtr_start(&mtr); + for (i = 1; i < num + 1; i++) { + if(!trx_rseg_create(TRX_SYS_SPACE, ULINT_MAX, &slot_no, &mtr)) { + fprintf(stderr, +"InnoDB: Warning: Failed to create extra rollback segments.\n"); + break; + } + ut_a(slot_no == i); + } + mtr_commit(&mtr); +} + +/********************************************************************* +Update the file format tag. */ +static +ibool +trx_sys_file_format_max_write( +/*==========================*/ + /* out: always TRUE */ + ulint format_id, /* in: file format id */ + const char** name) /* out: max file format name, can + be NULL */ +{ + mtr_t mtr; + byte* ptr; + buf_block_t* block; + ulint tag_value_low; + + mtr_start(&mtr); + + block = buf_page_get( + TRX_SYS_SPACE, 0, TRX_SYS_PAGE_NO, RW_X_LATCH, &mtr); + + file_format_max.id = format_id; + file_format_max.name = trx_sys_file_format_id_to_name(format_id); + + ptr = buf_block_get_frame(block) + TRX_SYS_FILE_FORMAT_TAG; + tag_value_low = format_id + TRX_SYS_FILE_FORMAT_TAG_MAGIC_N_LOW; + + if (name) { + *name = file_format_max.name; + } + + mlog_write_dulint( + ptr, + ut_dulint_create(TRX_SYS_FILE_FORMAT_TAG_MAGIC_N_HIGH, + tag_value_low), + &mtr); + + mtr_commit(&mtr); + + return(TRUE); +} + +/********************************************************************* +Read the file format tag. */ +static +ulint +trx_sys_file_format_max_read(void) +/*==============================*/ + /* out: the file format or + ULINT_UNDEFINED if not set. */ +{ + mtr_t mtr; + const byte* ptr; + const buf_block_t* block; + ulint format_id; + dulint file_format_id; + + /* Since this is called during the startup phase it's safe to + read the value without a covering mutex. */ + mtr_start(&mtr); + + block = buf_page_get( + TRX_SYS_SPACE, 0, TRX_SYS_PAGE_NO, RW_X_LATCH, &mtr); + + ptr = buf_block_get_frame(block) + TRX_SYS_FILE_FORMAT_TAG; + file_format_id = mach_read_from_8(ptr); + + mtr_commit(&mtr); + + format_id = file_format_id.low - TRX_SYS_FILE_FORMAT_TAG_MAGIC_N_LOW; + + if (file_format_id.high != TRX_SYS_FILE_FORMAT_TAG_MAGIC_N_HIGH + || format_id >= FILE_FORMAT_NAME_N) { + + /* Either it has never been tagged, or garbage in it. */ + return(ULINT_UNDEFINED); + } + + return(format_id); +} + +/********************************************************************* +Get the name representation of the file format from its id. */ +UNIV_INTERN +const char* +trx_sys_file_format_id_to_name( +/*===========================*/ + /* out: pointer to the name */ + const ulint id) /* in: id of the file format */ +{ + ut_a(id < FILE_FORMAT_NAME_N); + + return(file_format_name_map[id]); +} + +/********************************************************************* +Check for the max file format tag stored on disk. Note: If max_format_id +is == DICT_TF_FORMAT_MAX + 1 then we only print a warning. */ +UNIV_INTERN +ulint +trx_sys_file_format_max_check( +/*==========================*/ + /* out: DB_SUCCESS or error code */ + ulint max_format_id) /* in: max format id to check */ +{ + ulint format_id; + + /* Check the file format in the tablespace. Do not try to + recover if the file format is not supported by the engine + unless forced by the user. */ + format_id = trx_sys_file_format_max_read(); + if (format_id == ULINT_UNDEFINED) { + /* Format ID was not set. Set it to minimum possible + value. */ + format_id = DICT_TF_FORMAT_51; + } + + ut_print_timestamp(stderr); + fprintf(stderr, + " InnoDB: highest supported file format is %s.\n", + trx_sys_file_format_id_to_name(DICT_TF_FORMAT_MAX)); + + if (format_id > DICT_TF_FORMAT_MAX) { + + ut_a(format_id < FILE_FORMAT_NAME_N); + + ut_print_timestamp(stderr); + fprintf(stderr, + " InnoDB: %s: the system tablespace is in a file " + "format that this version doesn't support - %s\n", + ((max_format_id <= DICT_TF_FORMAT_MAX) + ? "Error" : "Warning"), + trx_sys_file_format_id_to_name(format_id)); + + if (max_format_id <= DICT_TF_FORMAT_MAX) { + return(DB_ERROR); + } + } + + format_id = (format_id > max_format_id) ? format_id : max_format_id; + + /* We don't need a mutex here, as this function should only + be called once at start up. */ + file_format_max.id = format_id; + file_format_max.name = trx_sys_file_format_id_to_name(format_id); + + return(DB_SUCCESS); +} + +/********************************************************************* +Set the file format id unconditionally except if it's already the +same value. */ +UNIV_INTERN +ibool +trx_sys_file_format_max_set( +/*========================*/ + /* out: TRUE if value updated */ + ulint format_id, /* in: file format id */ + const char** name) /* out: max file format name or + NULL if not needed. */ +{ + ibool ret = FALSE; + + ut_a(format_id <= DICT_TF_FORMAT_MAX); + + mutex_enter(&file_format_max.mutex); + + /* Only update if not already same value. */ + if (format_id != file_format_max.id) { + + ret = trx_sys_file_format_max_write(format_id, name); + } + + mutex_exit(&file_format_max.mutex); + + return(ret); +} + +/************************************************************************ +Tags the system table space with minimum format id if it has not been +tagged yet. +WARNING: This function is only called during the startup and AFTER the +redo log application during recovery has finished. */ +UNIV_INTERN +void +trx_sys_file_format_tag_init(void) +/*==============================*/ +{ + ulint format_id; + + format_id = trx_sys_file_format_max_read(); + + /* If format_id is not set then set it to the minimum. */ + if (format_id == ULINT_UNDEFINED) { + trx_sys_file_format_max_set(DICT_TF_FORMAT_51, NULL); + } +} + +/************************************************************************ +Update the file format tag in the system tablespace only if the given +format id is greater than the known max id. */ +UNIV_INTERN +ibool +trx_sys_file_format_max_upgrade( +/*============================*/ + /* out: TRUE if format_id was + bigger than the known max id */ + const char** name, /* out: max file format name */ + ulint format_id) /* in: file format identifier */ +{ + ibool ret = FALSE; + + ut_a(name); + ut_a(file_format_max.name != NULL); + ut_a(format_id <= DICT_TF_FORMAT_MAX); + + mutex_enter(&file_format_max.mutex); + + if (format_id > file_format_max.id) { + + ret = trx_sys_file_format_max_write(format_id, name); + } + + mutex_exit(&file_format_max.mutex); + + return(ret); +} + +/********************************************************************* +Get the name representation of the file format from its id. */ +UNIV_INTERN +const char* +trx_sys_file_format_max_get(void) +/*=============================*/ + /* out: pointer to the max format name */ +{ + return(file_format_max.name); +} + +/********************************************************************* +Initializes the tablespace tag system. */ +UNIV_INTERN +void +trx_sys_file_format_init(void) +/*==========================*/ +{ + mutex_create(&file_format_max.mutex, SYNC_FILE_FORMAT_TAG); + + /* We don't need a mutex here, as this function should only + be called once at start up. */ + file_format_max.id = DICT_TF_FORMAT_51; + + file_format_max.name = trx_sys_file_format_id_to_name( + file_format_max.id); +} + +/********************************************************************* +Closes the tablespace tag system. */ +UNIV_INTERN +void +trx_sys_file_format_close(void) +/*===========================*/ +{ + /* Does nothing at the moment */ +} diff --git a/storage/xtradb/trx/trx0trx.c b/storage/xtradb/trx/trx0trx.c new file mode 100644 index 00000000000..5fb234e3aa7 --- /dev/null +++ b/storage/xtradb/trx/trx0trx.c @@ -0,0 +1,2082 @@ +/***************************************************************************** + +Copyright (c) 1996, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/****************************************************** +The transaction + +Created 3/26/1996 Heikki Tuuri +*******************************************************/ + +#include "trx0trx.h" + +#ifdef UNIV_NONINL +#include "trx0trx.ic" +#endif + +#include "trx0undo.h" +#include "trx0rseg.h" +#include "log0log.h" +#include "que0que.h" +#include "lock0lock.h" +#include "trx0roll.h" +#include "usr0sess.h" +#include "read0read.h" +#include "srv0srv.h" +#include "thr0loc.h" +#include "btr0sea.h" +#include "os0proc.h" +#include "trx0xa.h" +#include "ha_prototypes.h" + +/* Dummy session used currently in MySQL interface */ +UNIV_INTERN sess_t* trx_dummy_sess = NULL; + +/* Number of transactions currently allocated for MySQL: protected by +the kernel mutex */ +UNIV_INTERN ulint trx_n_mysql_transactions = 0; + +/***************************************************************** +Set detailed error message for the transaction. */ +UNIV_INTERN +void +trx_set_detailed_error( +/*===================*/ + trx_t* trx, /* in: transaction struct */ + const char* msg) /* in: detailed error message */ +{ + ut_strlcpy(trx->detailed_error, msg, sizeof(trx->detailed_error)); +} + +/***************************************************************** +Set detailed error message for the transaction from a file. Note that the +file is rewinded before reading from it. */ +UNIV_INTERN +void +trx_set_detailed_error_from_file( +/*=============================*/ + trx_t* trx, /* in: transaction struct */ + FILE* file) /* in: file to read message from */ +{ + os_file_read_string(file, trx->detailed_error, + sizeof(trx->detailed_error)); +} + +/******************************************************************** +Creates and initializes a transaction object. */ +UNIV_INTERN +trx_t* +trx_create( +/*=======*/ + /* out, own: the transaction */ + sess_t* sess) /* in: session */ +{ + trx_t* trx; + + ut_ad(mutex_own(&kernel_mutex)); + ut_ad(sess); + + trx = mem_alloc(sizeof(trx_t)); + + trx->magic_n = TRX_MAGIC_N; + + trx->op_info = ""; + + trx->is_purge = 0; + trx->is_recovered = 0; + trx->conc_state = TRX_NOT_STARTED; + trx->start_time = time(NULL); + + trx->isolation_level = TRX_ISO_REPEATABLE_READ; + + trx->id = ut_dulint_zero; + trx->no = ut_dulint_max; + + trx->support_xa = TRUE; + + trx->check_foreigns = TRUE; + trx->check_unique_secondary = TRUE; + + trx->flush_log_later = FALSE; + trx->must_flush_log_later = FALSE; + + trx->dict_operation = TRX_DICT_OP_NONE; + trx->table_id = ut_dulint_zero; + + trx->mysql_thd = NULL; + trx->mysql_query_str = NULL; + trx->active_trans = 0; + trx->duplicates = 0; + + trx->n_mysql_tables_in_use = 0; + trx->mysql_n_tables_locked = 0; + + trx->mysql_log_file_name = NULL; + trx->mysql_log_offset = 0; + trx->mysql_master_log_file_name = ""; + trx->mysql_master_log_pos = 0; + trx->mysql_relay_log_file_name = ""; + trx->mysql_relay_log_pos = 0; + + mutex_create(&trx->undo_mutex, SYNC_TRX_UNDO); + + trx->rseg = NULL; + + trx->undo_no = ut_dulint_zero; + trx->last_sql_stat_start.least_undo_no = ut_dulint_zero; + trx->insert_undo = NULL; + trx->update_undo = NULL; + trx->undo_no_arr = NULL; + + trx->error_state = DB_SUCCESS; + trx->error_key_num = 0; + trx->detailed_error[0] = '\0'; + + trx->sess = sess; + trx->que_state = TRX_QUE_RUNNING; + trx->n_active_thrs = 0; + + trx->handling_signals = FALSE; + + UT_LIST_INIT(trx->signals); + UT_LIST_INIT(trx->reply_signals); + + trx->graph = NULL; + + trx->wait_lock = NULL; + trx->was_chosen_as_deadlock_victim = FALSE; + UT_LIST_INIT(trx->wait_thrs); + + trx->lock_heap = mem_heap_create_in_buffer(256); + UT_LIST_INIT(trx->trx_locks); + + UT_LIST_INIT(trx->trx_savepoints); + + trx->dict_operation_lock_mode = 0; + trx->has_search_latch = FALSE; + trx->search_latch_timeout = BTR_SEA_TIMEOUT; + + trx->declared_to_be_inside_innodb = FALSE; + trx->n_tickets_to_enter_innodb = 0; + + trx->global_read_view_heap = mem_heap_create(256); + trx->global_read_view = NULL; + trx->read_view = NULL; + + /* Set X/Open XA transaction identification to NULL */ + memset(&trx->xid, 0, sizeof(trx->xid)); + trx->xid.formatID = -1; + + trx->n_autoinc_rows = 0; + + /* Remember to free the vector explicitly. */ + trx->autoinc_locks = ib_vector_create( + mem_heap_create(sizeof(ib_vector_t) + sizeof(void*) * 4), 4); + + trx_reset_new_rec_lock_info(trx); + + return(trx); +} + +/************************************************************************ +Creates a transaction object for MySQL. */ +UNIV_INTERN +trx_t* +trx_allocate_for_mysql(void) +/*========================*/ + /* out, own: transaction object */ +{ + trx_t* trx; + + mutex_enter(&kernel_mutex); + + trx = trx_create(trx_dummy_sess); + + trx_n_mysql_transactions++; + + UT_LIST_ADD_FIRST(mysql_trx_list, trx_sys->mysql_trx_list, trx); + + mutex_exit(&kernel_mutex); + + trx->mysql_thread_id = os_thread_get_curr_id(); + + trx->mysql_process_no = os_proc_get_number(); + + return(trx); +} + +/************************************************************************ +Creates a transaction object for background operations by the master thread. */ +UNIV_INTERN +trx_t* +trx_allocate_for_background(void) +/*=============================*/ + /* out, own: transaction object */ +{ + trx_t* trx; + + mutex_enter(&kernel_mutex); + + trx = trx_create(trx_dummy_sess); + + mutex_exit(&kernel_mutex); + + return(trx); +} + +/************************************************************************ +Releases the search latch if trx has reserved it. */ +UNIV_INTERN +void +trx_search_latch_release_if_reserved( +/*=================================*/ + trx_t* trx) /* in: transaction */ +{ + if (trx->has_search_latch) { + rw_lock_s_unlock(&btr_search_latch); + + trx->has_search_latch = FALSE; + } +} + +/************************************************************************ +Frees a transaction object. */ +UNIV_INTERN +void +trx_free( +/*=====*/ + trx_t* trx) /* in, own: trx object */ +{ + ut_ad(mutex_own(&kernel_mutex)); + + if (trx->declared_to_be_inside_innodb) { + ut_print_timestamp(stderr); + fputs(" InnoDB: Error: Freeing a trx which is declared" + " to be processing\n" + "InnoDB: inside InnoDB.\n", stderr); + trx_print(stderr, trx, 600); + putc('\n', stderr); + + /* This is an error but not a fatal error. We must keep + the counters like srv_conc_n_threads accurate. */ + srv_conc_force_exit_innodb(trx); + } + + if (trx->n_mysql_tables_in_use != 0 + || trx->mysql_n_tables_locked != 0) { + + ut_print_timestamp(stderr); + fprintf(stderr, + " InnoDB: Error: MySQL is freeing a thd\n" + "InnoDB: though trx->n_mysql_tables_in_use is %lu\n" + "InnoDB: and trx->mysql_n_tables_locked is %lu.\n", + (ulong)trx->n_mysql_tables_in_use, + (ulong)trx->mysql_n_tables_locked); + + trx_print(stderr, trx, 600); + + ut_print_buf(stderr, trx, sizeof(trx_t)); + putc('\n', stderr); + } + + ut_a(trx->magic_n == TRX_MAGIC_N); + + trx->magic_n = 11112222; + + ut_a(trx->conc_state == TRX_NOT_STARTED); + + mutex_free(&(trx->undo_mutex)); + + ut_a(trx->insert_undo == NULL); + ut_a(trx->update_undo == NULL); + + if (trx->undo_no_arr) { + trx_undo_arr_free(trx->undo_no_arr); + } + + ut_a(UT_LIST_GET_LEN(trx->signals) == 0); + ut_a(UT_LIST_GET_LEN(trx->reply_signals) == 0); + + ut_a(trx->wait_lock == NULL); + ut_a(UT_LIST_GET_LEN(trx->wait_thrs) == 0); + + ut_a(!trx->has_search_latch); + + ut_a(trx->dict_operation_lock_mode == 0); + + if (trx->lock_heap) { + mem_heap_free(trx->lock_heap); + } + + ut_a(UT_LIST_GET_LEN(trx->trx_locks) == 0); + + if (trx->global_read_view_heap) { + mem_heap_free(trx->global_read_view_heap); + } + + trx->global_read_view = NULL; + + ut_a(trx->read_view == NULL); + + ut_a(ib_vector_is_empty(trx->autoinc_locks)); + /* We allocated a dedicated heap for the vector. */ + ib_vector_free(trx->autoinc_locks); + + mem_free(trx); +} + +/************************************************************************ +Frees a transaction object for MySQL. */ +UNIV_INTERN +void +trx_free_for_mysql( +/*===============*/ + trx_t* trx) /* in, own: trx object */ +{ + mutex_enter(&kernel_mutex); + + UT_LIST_REMOVE(mysql_trx_list, trx_sys->mysql_trx_list, trx); + + trx_free(trx); + + ut_a(trx_n_mysql_transactions > 0); + + trx_n_mysql_transactions--; + + mutex_exit(&kernel_mutex); +} + +/************************************************************************ +Frees a transaction object of a background operation of the master thread. */ +UNIV_INTERN +void +trx_free_for_background( +/*====================*/ + trx_t* trx) /* in, own: trx object */ +{ + mutex_enter(&kernel_mutex); + + trx_free(trx); + + mutex_exit(&kernel_mutex); +} + +/******************************************************************** +Inserts the trx handle in the trx system trx list in the right position. +The list is sorted on the trx id so that the biggest id is at the list +start. This function is used at the database startup to insert incomplete +transactions to the list. */ +static +void +trx_list_insert_ordered( +/*====================*/ + trx_t* trx) /* in: trx handle */ +{ + trx_t* trx2; + + ut_ad(mutex_own(&kernel_mutex)); + + trx2 = UT_LIST_GET_FIRST(trx_sys->trx_list); + + while (trx2 != NULL) { + if (ut_dulint_cmp(trx->id, trx2->id) >= 0) { + + ut_ad(ut_dulint_cmp(trx->id, trx2->id) == 1); + break; + } + trx2 = UT_LIST_GET_NEXT(trx_list, trx2); + } + + if (trx2 != NULL) { + trx2 = UT_LIST_GET_PREV(trx_list, trx2); + + if (trx2 == NULL) { + UT_LIST_ADD_FIRST(trx_list, trx_sys->trx_list, trx); + } else { + UT_LIST_INSERT_AFTER(trx_list, trx_sys->trx_list, + trx2, trx); + } + } else { + UT_LIST_ADD_LAST(trx_list, trx_sys->trx_list, trx); + } +} + +/******************************************************************** +Creates trx objects for transactions and initializes the trx list of +trx_sys at database start. Rollback segment and undo log lists must +already exist when this function is called, because the lists of +transactions to be rolled back or cleaned up are built based on the +undo log lists. */ +UNIV_INTERN +void +trx_lists_init_at_db_start(void) +/*============================*/ +{ + trx_rseg_t* rseg; + trx_undo_t* undo; + trx_t* trx; + + UT_LIST_INIT(trx_sys->trx_list); + + /* Look from the rollback segments if there exist undo logs for + transactions */ + + rseg = UT_LIST_GET_FIRST(trx_sys->rseg_list); + + while (rseg != NULL) { + undo = UT_LIST_GET_FIRST(rseg->insert_undo_list); + + while (undo != NULL) { + + trx = trx_create(trx_dummy_sess); + + trx->is_recovered = TRUE; + trx->id = undo->trx_id; + trx->xid = undo->xid; + trx->insert_undo = undo; + trx->rseg = rseg; + + if (undo->state != TRX_UNDO_ACTIVE) { + + /* Prepared transactions are left in + the prepared state waiting for a + commit or abort decision from MySQL */ + + if (undo->state == TRX_UNDO_PREPARED) { + + fprintf(stderr, + "InnoDB: Transaction " + TRX_ID_FMT + " was in the" + " XA prepared state.\n", + TRX_ID_PREP_PRINTF(trx->id)); + + if (srv_force_recovery == 0) { + + trx->conc_state = TRX_PREPARED; + } else { + fprintf(stderr, + "InnoDB: Since" + " innodb_force_recovery" + " > 0, we will" + " rollback it" + " anyway.\n"); + + trx->conc_state = TRX_ACTIVE; + } + } else { + trx->conc_state + = TRX_COMMITTED_IN_MEMORY; + } + + /* We give a dummy value for the trx no; + this should have no relevance since purge + is not interested in committed transaction + numbers, unless they are in the history + list, in which case it looks the number + from the disk based undo log structure */ + + trx->no = trx->id; + } else { + trx->conc_state = TRX_ACTIVE; + + /* A running transaction always has the number + field inited to ut_dulint_max */ + + trx->no = ut_dulint_max; + } + + if (undo->dict_operation) { + trx_set_dict_operation( + trx, TRX_DICT_OP_TABLE); + trx->table_id = undo->table_id; + } + + if (!undo->empty) { + trx->undo_no = ut_dulint_add(undo->top_undo_no, + 1); + } + + trx_list_insert_ordered(trx); + + undo = UT_LIST_GET_NEXT(undo_list, undo); + } + + undo = UT_LIST_GET_FIRST(rseg->update_undo_list); + + while (undo != NULL) { + trx = trx_get_on_id(undo->trx_id); + + if (NULL == trx) { + trx = trx_create(trx_dummy_sess); + + trx->is_recovered = TRUE; + trx->id = undo->trx_id; + trx->xid = undo->xid; + + if (undo->state != TRX_UNDO_ACTIVE) { + + /* Prepared transactions are left in + the prepared state waiting for a + commit or abort decision from MySQL */ + + if (undo->state == TRX_UNDO_PREPARED) { + fprintf(stderr, + "InnoDB: Transaction " + TRX_ID_FMT " was in the" + " XA prepared state.\n", + TRX_ID_PREP_PRINTF( + trx->id)); + + if (srv_force_recovery == 0) { + + trx->conc_state + = TRX_PREPARED; + } else { + fprintf(stderr, + "InnoDB: Since" + " innodb_force_recovery" + " > 0, we will" + " rollback it" + " anyway.\n"); + + trx->conc_state + = TRX_ACTIVE; + } + } else { + trx->conc_state + = TRX_COMMITTED_IN_MEMORY; + } + + /* We give a dummy value for the trx + number */ + + trx->no = trx->id; + } else { + trx->conc_state = TRX_ACTIVE; + + /* A running transaction always has + the number field inited to + ut_dulint_max */ + + trx->no = ut_dulint_max; + } + + trx->rseg = rseg; + trx_list_insert_ordered(trx); + + if (undo->dict_operation) { + trx_set_dict_operation( + trx, TRX_DICT_OP_TABLE); + trx->table_id = undo->table_id; + } + } + + trx->update_undo = undo; + + if ((!undo->empty) + && (ut_dulint_cmp(undo->top_undo_no, + trx->undo_no) >= 0)) { + + trx->undo_no = ut_dulint_add(undo->top_undo_no, + 1); + } + + undo = UT_LIST_GET_NEXT(undo_list, undo); + } + + rseg = UT_LIST_GET_NEXT(rseg_list, rseg); + } +} + +/********************************************************************** +Assigns a rollback segment to a transaction in a round-robin fashion. +Skips the SYSTEM rollback segment if another is available. */ +UNIV_INLINE +ulint +trx_assign_rseg(void) +/*=================*/ + /* out: assigned rollback segment id */ +{ + trx_rseg_t* rseg = trx_sys->latest_rseg; + + ut_ad(mutex_own(&kernel_mutex)); +loop: + /* Get next rseg in a round-robin fashion */ + + rseg = UT_LIST_GET_NEXT(rseg_list, rseg); + + if (rseg == NULL) { + rseg = UT_LIST_GET_FIRST(trx_sys->rseg_list); + } + + /* If it is the SYSTEM rollback segment, and there exist others, skip + it */ + + if ((rseg->id == TRX_SYS_SYSTEM_RSEG_ID) + && (UT_LIST_GET_LEN(trx_sys->rseg_list) > 1)) { + goto loop; + } + + trx_sys->latest_rseg = rseg; + + return(rseg->id); +} + +/******************************************************************** +Starts a new transaction. */ +UNIV_INTERN +ibool +trx_start_low( +/*==========*/ + /* out: TRUE */ + trx_t* trx, /* in: transaction */ + ulint rseg_id)/* in: rollback segment id; if ULINT_UNDEFINED + is passed, the system chooses the rollback segment + automatically in a round-robin fashion */ +{ + trx_rseg_t* rseg; + + ut_ad(mutex_own(&kernel_mutex)); + ut_ad(trx->rseg == NULL); + + if (trx->is_purge) { + trx->id = ut_dulint_zero; + trx->conc_state = TRX_ACTIVE; + trx->start_time = time(NULL); + + return(TRUE); + } + + ut_ad(trx->conc_state != TRX_ACTIVE); + + if (rseg_id == ULINT_UNDEFINED) { + + rseg_id = trx_assign_rseg(); + } + + rseg = trx_sys_get_nth_rseg(trx_sys, rseg_id); + + trx->id = trx_sys_get_new_trx_id(); + + /* The initial value for trx->no: ut_dulint_max is used in + read_view_open_now: */ + + trx->no = ut_dulint_max; + + trx->rseg = rseg; + + trx->conc_state = TRX_ACTIVE; + trx->start_time = time(NULL); + + UT_LIST_ADD_FIRST(trx_list, trx_sys->trx_list, trx); + + return(TRUE); +} + +/******************************************************************** +Starts a new transaction. */ +UNIV_INTERN +ibool +trx_start( +/*======*/ + /* out: TRUE */ + trx_t* trx, /* in: transaction */ + ulint rseg_id)/* in: rollback segment id; if ULINT_UNDEFINED + is passed, the system chooses the rollback segment + automatically in a round-robin fashion */ +{ + ibool ret; + + /* Update the info whether we should skip XA steps that eat CPU time + For the duration of the transaction trx->support_xa is not reread + from thd so any changes in the value take effect in the next + transaction. This is to avoid a scenario where some undo + generated by a transaction, has XA stuff, and other undo, + generated by the same transaction, doesn't. */ + trx->support_xa = thd_supports_xa(trx->mysql_thd); + + mutex_enter(&kernel_mutex); + + ret = trx_start_low(trx, rseg_id); + + mutex_exit(&kernel_mutex); + + return(ret); +} + +/******************************************************************** +Commits a transaction. */ +UNIV_INTERN +void +trx_commit_off_kernel( +/*==================*/ + trx_t* trx) /* in: transaction */ +{ + page_t* update_hdr_page; + ib_uint64_t lsn = 0; + trx_rseg_t* rseg; + trx_undo_t* undo; + mtr_t mtr; + + ut_ad(mutex_own(&kernel_mutex)); + + trx->must_flush_log_later = FALSE; + + rseg = trx->rseg; + + if (trx->insert_undo != NULL || trx->update_undo != NULL) { + + mutex_exit(&kernel_mutex); + + mtr_start(&mtr); + + /* Change the undo log segment states from TRX_UNDO_ACTIVE + to some other state: these modifications to the file data + structure define the transaction as committed in the file + based world, at the serialization point of the log sequence + number lsn obtained below. */ + + mutex_enter(&(rseg->mutex)); + + if (trx->insert_undo != NULL) { + trx_undo_set_state_at_finish( + rseg, trx, trx->insert_undo, &mtr); + } + + undo = trx->update_undo; + + if (undo) { + mutex_enter(&kernel_mutex); + trx->no = trx_sys_get_new_trx_no(); + + mutex_exit(&kernel_mutex); + + /* It is not necessary to obtain trx->undo_mutex here + because only a single OS thread is allowed to do the + transaction commit for this transaction. */ + + update_hdr_page = trx_undo_set_state_at_finish( + rseg, trx, undo, &mtr); + + /* We have to do the cleanup for the update log while + holding the rseg mutex because update log headers + have to be put to the history list in the order of + the trx number. */ + + trx_undo_update_cleanup(trx, update_hdr_page, &mtr); + } + + mutex_exit(&(rseg->mutex)); + + /* Update the latest MySQL binlog name and offset info + in trx sys header if MySQL binlogging is on or the database + server is a MySQL replication slave */ + + if (trx->mysql_log_file_name + && trx->mysql_log_file_name[0] != '\0') { + trx_sys_update_mysql_binlog_offset( + trx->mysql_log_file_name, + trx->mysql_log_offset, + TRX_SYS_MYSQL_LOG_INFO, &mtr); + trx->mysql_log_file_name = NULL; + } + + if (trx->mysql_master_log_file_name[0] != '\0') { + /* This database server is a MySQL replication slave */ + trx_sys_update_mysql_binlog_offset( + trx->mysql_relay_log_file_name, + trx->mysql_relay_log_pos, + TRX_SYS_MYSQL_RELAY_LOG_INFO, &mtr); + trx_sys_update_mysql_binlog_offset( + trx->mysql_master_log_file_name, + trx->mysql_master_log_pos, + TRX_SYS_MYSQL_MASTER_LOG_INFO, &mtr); + trx->mysql_master_log_file_name = ""; + } + + /* The following call commits the mini-transaction, making the + whole transaction committed in the file-based world, at this + log sequence number. The transaction becomes 'durable' when + we write the log to disk, but in the logical sense the commit + in the file-based data structures (undo logs etc.) happens + here. + + NOTE that transaction numbers, which are assigned only to + transactions with an update undo log, do not necessarily come + in exactly the same order as commit lsn's, if the transactions + have different rollback segments. To get exactly the same + order we should hold the kernel mutex up to this point, + adding to to the contention of the kernel mutex. However, if + a transaction T2 is able to see modifications made by + a transaction T1, T2 will always get a bigger transaction + number and a bigger commit lsn than T1. */ + + /*--------------*/ + mtr_commit(&mtr); + /*--------------*/ + lsn = mtr.end_lsn; + + mutex_enter(&kernel_mutex); + } + + ut_ad(trx->conc_state == TRX_ACTIVE + || trx->conc_state == TRX_PREPARED); + ut_ad(mutex_own(&kernel_mutex)); + + /* The following assignment makes the transaction committed in memory + and makes its changes to data visible to other transactions. + NOTE that there is a small discrepancy from the strict formal + visibility rules here: a human user of the database can see + modifications made by another transaction T even before the necessary + log segment has been flushed to the disk. If the database happens to + crash before the flush, the user has seen modifications from T which + will never be a committed transaction. However, any transaction T2 + which sees the modifications of the committing transaction T, and + which also itself makes modifications to the database, will get an lsn + larger than the committing transaction T. In the case where the log + flush fails, and T never gets committed, also T2 will never get + committed. */ + + /*--------------------------------------*/ + trx->conc_state = TRX_COMMITTED_IN_MEMORY; + /*--------------------------------------*/ + + /* If we release kernel_mutex below and we are still doing + recovery i.e.: back ground rollback thread is still active + then there is a chance that the rollback thread may see + this trx as COMMITTED_IN_MEMORY and goes adhead to clean it + up calling trx_cleanup_at_db_startup(). This can happen + in the case we are committing a trx here that is left in + PREPARED state during the crash. Note that commit of the + rollback of a PREPARED trx happens in the recovery thread + while the rollback of other transactions happen in the + background thread. To avoid this race we unconditionally + unset the is_recovered flag from the trx. */ + + trx->is_recovered = FALSE; + + lock_release_off_kernel(trx); + + if (trx->global_read_view) { + read_view_close(trx->global_read_view); + mem_heap_empty(trx->global_read_view_heap); + trx->global_read_view = NULL; + } + + trx->read_view = NULL; + + if (lsn) { + + mutex_exit(&kernel_mutex); + + if (trx->insert_undo != NULL) { + + trx_undo_insert_cleanup(trx); + } + + /* NOTE that we could possibly make a group commit more + efficient here: call os_thread_yield here to allow also other + trxs to come to commit! */ + + /*-------------------------------------*/ + + /* Depending on the my.cnf options, we may now write the log + buffer to the log files, making the transaction durable if + the OS does not crash. We may also flush the log files to + disk, making the transaction durable also at an OS crash or a + power outage. + + The idea in InnoDB's group commit is that a group of + transactions gather behind a trx doing a physical disk write + to log files, and when that physical write has been completed, + one of those transactions does a write which commits the whole + group. Note that this group commit will only bring benefit if + there are > 2 users in the database. Then at least 2 users can + gather behind one doing the physical log write to disk. + + If we are calling trx_commit() under MySQL's binlog mutex, we + will delay possible log write and flush to a separate function + trx_commit_complete_for_mysql(), which is only called when the + thread has released the binlog mutex. This is to make the + group commit algorithm to work. Otherwise, the MySQL binlog + mutex would serialize all commits and prevent a group of + transactions from gathering. */ + + if (trx->flush_log_later) { + /* Do nothing yet */ + trx->must_flush_log_later = TRUE; + } else if (srv_flush_log_at_trx_commit == 0) { + /* Do nothing */ + } else if (srv_flush_log_at_trx_commit == 1) { + if (srv_unix_file_flush_method == SRV_UNIX_NOSYNC) { + /* Write the log but do not flush it to disk */ + + log_write_up_to(lsn, LOG_WAIT_ONE_GROUP, + FALSE); + } else { + /* Write the log to the log files AND flush + them to disk */ + + log_write_up_to(lsn, LOG_WAIT_ONE_GROUP, TRUE); + } + } else if (srv_flush_log_at_trx_commit == 2) { + + /* Write the log but do not flush it to disk */ + + log_write_up_to(lsn, LOG_WAIT_ONE_GROUP, FALSE); + } else { + ut_error; + } + + trx->commit_lsn = lsn; + + /*-------------------------------------*/ + + mutex_enter(&kernel_mutex); + } + + /* Free all savepoints */ + trx_roll_free_all_savepoints(trx); + + trx->conc_state = TRX_NOT_STARTED; + trx->rseg = NULL; + trx->undo_no = ut_dulint_zero; + trx->last_sql_stat_start.least_undo_no = ut_dulint_zero; + trx->mysql_query_str = NULL; + + ut_ad(UT_LIST_GET_LEN(trx->wait_thrs) == 0); + ut_ad(UT_LIST_GET_LEN(trx->trx_locks) == 0); + + UT_LIST_REMOVE(trx_list, trx_sys->trx_list, trx); +} + +/******************************************************************** +Cleans up a transaction at database startup. The cleanup is needed if +the transaction already got to the middle of a commit when the database +crashed, andf we cannot roll it back. */ +UNIV_INTERN +void +trx_cleanup_at_db_startup( +/*======================*/ + trx_t* trx) /* in: transaction */ +{ + if (trx->insert_undo != NULL) { + + trx_undo_insert_cleanup(trx); + } + + trx->conc_state = TRX_NOT_STARTED; + trx->rseg = NULL; + trx->undo_no = ut_dulint_zero; + trx->last_sql_stat_start.least_undo_no = ut_dulint_zero; + + UT_LIST_REMOVE(trx_list, trx_sys->trx_list, trx); +} + +/************************************************************************ +Assigns a read view for a consistent read query. All the consistent reads +within the same transaction will get the same read view, which is created +when this function is first called for a new started transaction. */ +UNIV_INTERN +read_view_t* +trx_assign_read_view( +/*=================*/ + /* out: consistent read view */ + trx_t* trx) /* in: active transaction */ +{ + ut_ad(trx->conc_state == TRX_ACTIVE); + + if (trx->read_view) { + return(trx->read_view); + } + + mutex_enter(&kernel_mutex); + + if (!trx->read_view) { + trx->read_view = read_view_open_now( + trx->id, trx->global_read_view_heap); + trx->global_read_view = trx->read_view; + } + + mutex_exit(&kernel_mutex); + + return(trx->read_view); +} + +/******************************************************************** +Commits a transaction. NOTE that the kernel mutex is temporarily released. */ +static +void +trx_handle_commit_sig_off_kernel( +/*=============================*/ + trx_t* trx, /* in: transaction */ + que_thr_t** next_thr) /* in/out: next query thread to run; + if the value which is passed in is + a pointer to a NULL pointer, then the + calling function can start running + a new query thread */ +{ + trx_sig_t* sig; + trx_sig_t* next_sig; + + ut_ad(mutex_own(&kernel_mutex)); + + trx->que_state = TRX_QUE_COMMITTING; + + trx_commit_off_kernel(trx); + + ut_ad(UT_LIST_GET_LEN(trx->wait_thrs) == 0); + + /* Remove all TRX_SIG_COMMIT signals from the signal queue and send + reply messages to them */ + + sig = UT_LIST_GET_FIRST(trx->signals); + + while (sig != NULL) { + next_sig = UT_LIST_GET_NEXT(signals, sig); + + if (sig->type == TRX_SIG_COMMIT) { + + trx_sig_reply(sig, next_thr); + trx_sig_remove(trx, sig); + } + + sig = next_sig; + } + + trx->que_state = TRX_QUE_RUNNING; +} + +/*************************************************************** +The transaction must be in the TRX_QUE_LOCK_WAIT state. Puts it to +the TRX_QUE_RUNNING state and releases query threads which were +waiting for a lock in the wait_thrs list. */ +UNIV_INTERN +void +trx_end_lock_wait( +/*==============*/ + trx_t* trx) /* in: transaction */ +{ + que_thr_t* thr; + + ut_ad(mutex_own(&kernel_mutex)); + ut_ad(trx->que_state == TRX_QUE_LOCK_WAIT); + + thr = UT_LIST_GET_FIRST(trx->wait_thrs); + + while (thr != NULL) { + que_thr_end_wait_no_next_thr(thr); + + UT_LIST_REMOVE(trx_thrs, trx->wait_thrs, thr); + + thr = UT_LIST_GET_FIRST(trx->wait_thrs); + } + + trx->que_state = TRX_QUE_RUNNING; +} + +/*************************************************************** +Moves the query threads in the lock wait list to the SUSPENDED state and puts +the transaction to the TRX_QUE_RUNNING state. */ +static +void +trx_lock_wait_to_suspended( +/*=======================*/ + trx_t* trx) /* in: transaction in the TRX_QUE_LOCK_WAIT state */ +{ + que_thr_t* thr; + + ut_ad(mutex_own(&kernel_mutex)); + ut_ad(trx->que_state == TRX_QUE_LOCK_WAIT); + + thr = UT_LIST_GET_FIRST(trx->wait_thrs); + + while (thr != NULL) { + thr->state = QUE_THR_SUSPENDED; + + UT_LIST_REMOVE(trx_thrs, trx->wait_thrs, thr); + + thr = UT_LIST_GET_FIRST(trx->wait_thrs); + } + + trx->que_state = TRX_QUE_RUNNING; +} + +/*************************************************************** +Moves the query threads in the sig reply wait list of trx to the SUSPENDED +state. */ +static +void +trx_sig_reply_wait_to_suspended( +/*============================*/ + trx_t* trx) /* in: transaction */ +{ + trx_sig_t* sig; + que_thr_t* thr; + + ut_ad(mutex_own(&kernel_mutex)); + + sig = UT_LIST_GET_FIRST(trx->reply_signals); + + while (sig != NULL) { + thr = sig->receiver; + + ut_ad(thr->state == QUE_THR_SIG_REPLY_WAIT); + + thr->state = QUE_THR_SUSPENDED; + + sig->receiver = NULL; + + UT_LIST_REMOVE(reply_signals, trx->reply_signals, sig); + + sig = UT_LIST_GET_FIRST(trx->reply_signals); + } +} + +/********************************************************************* +Checks the compatibility of a new signal with the other signals in the +queue. */ +static +ibool +trx_sig_is_compatible( +/*==================*/ + /* out: TRUE if the signal can be queued */ + trx_t* trx, /* in: trx handle */ + ulint type, /* in: signal type */ + ulint sender) /* in: TRX_SIG_SELF or TRX_SIG_OTHER_SESS */ +{ + trx_sig_t* sig; + + ut_ad(mutex_own(&kernel_mutex)); + + if (UT_LIST_GET_LEN(trx->signals) == 0) { + + return(TRUE); + } + + if (sender == TRX_SIG_SELF) { + if (type == TRX_SIG_ERROR_OCCURRED) { + + return(TRUE); + + } else if (type == TRX_SIG_BREAK_EXECUTION) { + + return(TRUE); + } else { + return(FALSE); + } + } + + ut_ad(sender == TRX_SIG_OTHER_SESS); + + sig = UT_LIST_GET_FIRST(trx->signals); + + if (type == TRX_SIG_COMMIT) { + while (sig != NULL) { + + if (sig->type == TRX_SIG_TOTAL_ROLLBACK) { + + return(FALSE); + } + + sig = UT_LIST_GET_NEXT(signals, sig); + } + + return(TRUE); + + } else if (type == TRX_SIG_TOTAL_ROLLBACK) { + while (sig != NULL) { + + if (sig->type == TRX_SIG_COMMIT) { + + return(FALSE); + } + + sig = UT_LIST_GET_NEXT(signals, sig); + } + + return(TRUE); + + } else if (type == TRX_SIG_BREAK_EXECUTION) { + + return(TRUE); + } else { + ut_error; + + return(FALSE); + } +} + +/******************************************************************** +Sends a signal to a trx object. */ +UNIV_INTERN +void +trx_sig_send( +/*=========*/ + trx_t* trx, /* in: trx handle */ + ulint type, /* in: signal type */ + ulint sender, /* in: TRX_SIG_SELF or + TRX_SIG_OTHER_SESS */ + que_thr_t* receiver_thr, /* in: query thread which wants the + reply, or NULL; if type is + TRX_SIG_END_WAIT, this must be NULL */ + trx_savept_t* savept, /* in: possible rollback savepoint, or + NULL */ + que_thr_t** next_thr) /* in/out: next query thread to run; + if the value which is passed in is + a pointer to a NULL pointer, then the + calling function can start running + a new query thread; if the parameter + is NULL, it is ignored */ +{ + trx_sig_t* sig; + trx_t* receiver_trx; + + ut_ad(trx); + ut_ad(mutex_own(&kernel_mutex)); + + if (!trx_sig_is_compatible(trx, type, sender)) { + /* The signal is not compatible with the other signals in + the queue: die */ + + ut_error; + } + + /* Queue the signal object */ + + if (UT_LIST_GET_LEN(trx->signals) == 0) { + + /* The signal list is empty: the 'sig' slot must be unused + (we improve performance a bit by avoiding mem_alloc) */ + sig = &(trx->sig); + } else { + /* It might be that the 'sig' slot is unused also in this + case, but we choose the easy way of using mem_alloc */ + + sig = mem_alloc(sizeof(trx_sig_t)); + } + + UT_LIST_ADD_LAST(signals, trx->signals, sig); + + sig->type = type; + sig->sender = sender; + sig->receiver = receiver_thr; + + if (savept) { + sig->savept = *savept; + } + + if (receiver_thr) { + receiver_trx = thr_get_trx(receiver_thr); + + UT_LIST_ADD_LAST(reply_signals, receiver_trx->reply_signals, + sig); + } + + if (trx->sess->state == SESS_ERROR) { + + trx_sig_reply_wait_to_suspended(trx); + } + + if ((sender != TRX_SIG_SELF) || (type == TRX_SIG_BREAK_EXECUTION)) { + ut_error; + } + + /* If there were no other signals ahead in the queue, try to start + handling of the signal */ + + if (UT_LIST_GET_FIRST(trx->signals) == sig) { + + trx_sig_start_handle(trx, next_thr); + } +} + +/******************************************************************** +Ends signal handling. If the session is in the error state, and +trx->graph_before_signal_handling != NULL, then returns control to the error +handling routine of the graph (currently just returns the control to the +graph root which then will send an error message to the client). */ +UNIV_INTERN +void +trx_end_signal_handling( +/*====================*/ + trx_t* trx) /* in: trx */ +{ + ut_ad(mutex_own(&kernel_mutex)); + ut_ad(trx->handling_signals == TRUE); + + trx->handling_signals = FALSE; + + trx->graph = trx->graph_before_signal_handling; + + if (trx->graph && (trx->sess->state == SESS_ERROR)) { + + que_fork_error_handle(trx, trx->graph); + } +} + +/******************************************************************** +Starts handling of a trx signal. */ +UNIV_INTERN +void +trx_sig_start_handle( +/*=================*/ + trx_t* trx, /* in: trx handle */ + que_thr_t** next_thr) /* in/out: next query thread to run; + if the value which is passed in is + a pointer to a NULL pointer, then the + calling function can start running + a new query thread; if the parameter + is NULL, it is ignored */ +{ + trx_sig_t* sig; + ulint type; +loop: + /* We loop in this function body as long as there are queued signals + we can process immediately */ + + ut_ad(trx); + ut_ad(mutex_own(&kernel_mutex)); + + if (trx->handling_signals && (UT_LIST_GET_LEN(trx->signals) == 0)) { + + trx_end_signal_handling(trx); + + return; + } + + if (trx->conc_state == TRX_NOT_STARTED) { + + trx_start_low(trx, ULINT_UNDEFINED); + } + + /* If the trx is in a lock wait state, moves the waiting query threads + to the suspended state */ + + if (trx->que_state == TRX_QUE_LOCK_WAIT) { + + trx_lock_wait_to_suspended(trx); + } + + /* If the session is in the error state and this trx has threads + waiting for reply from signals, moves these threads to the suspended + state, canceling wait reservations; note that if the transaction has + sent a commit or rollback signal to itself, and its session is not in + the error state, then nothing is done here. */ + + if (trx->sess->state == SESS_ERROR) { + trx_sig_reply_wait_to_suspended(trx); + } + + /* If there are no running query threads, we can start processing of a + signal, otherwise we have to wait until all query threads of this + transaction are aware of the arrival of the signal. */ + + if (trx->n_active_thrs > 0) { + + return; + } + + if (trx->handling_signals == FALSE) { + trx->graph_before_signal_handling = trx->graph; + + trx->handling_signals = TRUE; + } + + sig = UT_LIST_GET_FIRST(trx->signals); + type = sig->type; + + if (type == TRX_SIG_COMMIT) { + + trx_handle_commit_sig_off_kernel(trx, next_thr); + + } else if ((type == TRX_SIG_TOTAL_ROLLBACK) + || (type == TRX_SIG_ROLLBACK_TO_SAVEPT)) { + + trx_rollback(trx, sig, next_thr); + + /* No further signals can be handled until the rollback + completes, therefore we return */ + + return; + + } else if (type == TRX_SIG_ERROR_OCCURRED) { + + trx_rollback(trx, sig, next_thr); + + /* No further signals can be handled until the rollback + completes, therefore we return */ + + return; + + } else if (type == TRX_SIG_BREAK_EXECUTION) { + + trx_sig_reply(sig, next_thr); + trx_sig_remove(trx, sig); + } else { + ut_error; + } + + goto loop; +} + +/******************************************************************** +Send the reply message when a signal in the queue of the trx has been +handled. */ +UNIV_INTERN +void +trx_sig_reply( +/*==========*/ + trx_sig_t* sig, /* in: signal */ + que_thr_t** next_thr) /* in/out: next query thread to run; + if the value which is passed in is + a pointer to a NULL pointer, then the + calling function can start running + a new query thread */ +{ + trx_t* receiver_trx; + + ut_ad(sig); + ut_ad(mutex_own(&kernel_mutex)); + + if (sig->receiver != NULL) { + ut_ad((sig->receiver)->state == QUE_THR_SIG_REPLY_WAIT); + + receiver_trx = thr_get_trx(sig->receiver); + + UT_LIST_REMOVE(reply_signals, receiver_trx->reply_signals, + sig); + ut_ad(receiver_trx->sess->state != SESS_ERROR); + + que_thr_end_wait(sig->receiver, next_thr); + + sig->receiver = NULL; + + } +} + +/******************************************************************** +Removes a signal object from the trx signal queue. */ +UNIV_INTERN +void +trx_sig_remove( +/*===========*/ + trx_t* trx, /* in: trx handle */ + trx_sig_t* sig) /* in, own: signal */ +{ + ut_ad(trx && sig); + ut_ad(mutex_own(&kernel_mutex)); + + ut_ad(sig->receiver == NULL); + + UT_LIST_REMOVE(signals, trx->signals, sig); + sig->type = 0; /* reset the field to catch possible bugs */ + + if (sig != &(trx->sig)) { + mem_free(sig); + } +} + +/************************************************************************* +Creates a commit command node struct. */ +UNIV_INTERN +commit_node_t* +commit_node_create( +/*===============*/ + /* out, own: commit node struct */ + mem_heap_t* heap) /* in: mem heap where created */ +{ + commit_node_t* node; + + node = mem_heap_alloc(heap, sizeof(commit_node_t)); + node->common.type = QUE_NODE_COMMIT; + node->state = COMMIT_NODE_SEND; + + return(node); +} + +/*************************************************************** +Performs an execution step for a commit type node in a query graph. */ +UNIV_INTERN +que_thr_t* +trx_commit_step( +/*============*/ + /* out: query thread to run next, or NULL */ + que_thr_t* thr) /* in: query thread */ +{ + commit_node_t* node; + que_thr_t* next_thr; + + node = thr->run_node; + + ut_ad(que_node_get_type(node) == QUE_NODE_COMMIT); + + if (thr->prev_node == que_node_get_parent(node)) { + node->state = COMMIT_NODE_SEND; + } + + if (node->state == COMMIT_NODE_SEND) { + mutex_enter(&kernel_mutex); + + node->state = COMMIT_NODE_WAIT; + + next_thr = NULL; + + thr->state = QUE_THR_SIG_REPLY_WAIT; + + /* Send the commit signal to the transaction */ + + trx_sig_send(thr_get_trx(thr), TRX_SIG_COMMIT, TRX_SIG_SELF, + thr, NULL, &next_thr); + + mutex_exit(&kernel_mutex); + + return(next_thr); + } + + ut_ad(node->state == COMMIT_NODE_WAIT); + + node->state = COMMIT_NODE_SEND; + + thr->run_node = que_node_get_parent(node); + + return(thr); +} + +/************************************************************************** +Does the transaction commit for MySQL. */ +UNIV_INTERN +ulint +trx_commit_for_mysql( +/*=================*/ + /* out: DB_SUCCESS or error number */ + trx_t* trx) /* in: trx handle */ +{ + /* Because we do not do the commit by sending an Innobase + sig to the transaction, we must here make sure that trx has been + started. */ + + ut_a(trx); + + trx_start_if_not_started(trx); + + trx->op_info = "committing"; + + mutex_enter(&kernel_mutex); + + trx_commit_off_kernel(trx); + + mutex_exit(&kernel_mutex); + + trx->op_info = ""; + + return(DB_SUCCESS); +} + +/************************************************************************** +If required, flushes the log to disk if we called trx_commit_for_mysql() +with trx->flush_log_later == TRUE. */ +UNIV_INTERN +ulint +trx_commit_complete_for_mysql( +/*==========================*/ + /* out: 0 or error number */ + trx_t* trx) /* in: trx handle */ +{ + ib_uint64_t lsn = trx->commit_lsn; + + ut_a(trx); + + trx->op_info = "flushing log"; + + if (!trx->must_flush_log_later) { + /* Do nothing */ + } else if (srv_flush_log_at_trx_commit == 0) { + /* Do nothing */ + } else if (srv_flush_log_at_trx_commit == 1) { + if (srv_unix_file_flush_method == SRV_UNIX_NOSYNC) { + /* Write the log but do not flush it to disk */ + + log_write_up_to(lsn, LOG_WAIT_ONE_GROUP, FALSE); + } else { + /* Write the log to the log files AND flush them to + disk */ + + log_write_up_to(lsn, LOG_WAIT_ONE_GROUP, TRUE); + } + } else if (srv_flush_log_at_trx_commit == 2) { + + /* Write the log but do not flush it to disk */ + + log_write_up_to(lsn, LOG_WAIT_ONE_GROUP, FALSE); + } else { + ut_error; + } + + trx->must_flush_log_later = FALSE; + + trx->op_info = ""; + + return(0); +} + +/************************************************************************** +Marks the latest SQL statement ended. */ +UNIV_INTERN +void +trx_mark_sql_stat_end( +/*==================*/ + trx_t* trx) /* in: trx handle */ +{ + ut_a(trx); + + if (trx->conc_state == TRX_NOT_STARTED) { + trx->undo_no = ut_dulint_zero; + } + + trx->last_sql_stat_start.least_undo_no = trx->undo_no; +} + +/************************************************************************** +Prints info about a transaction to the given file. The caller must own the +kernel mutex and must have called +innobase_mysql_prepare_print_arbitrary_thd(), unless he knows that MySQL +or InnoDB cannot meanwhile change the info printed here. */ +UNIV_INTERN +void +trx_print( +/*======*/ + FILE* f, /* in: output stream */ + trx_t* trx, /* in: transaction */ + ulint max_query_len) /* in: max query length to print, or 0 to + use the default max length */ +{ + ibool newline; + + fprintf(f, "TRANSACTION " TRX_ID_FMT, TRX_ID_PREP_PRINTF(trx->id)); + + switch (trx->conc_state) { + case TRX_NOT_STARTED: + fputs(", not started", f); + break; + case TRX_ACTIVE: + fprintf(f, ", ACTIVE %lu sec", + (ulong)difftime(time(NULL), trx->start_time)); + break; + case TRX_PREPARED: + fprintf(f, ", ACTIVE (PREPARED) %lu sec", + (ulong)difftime(time(NULL), trx->start_time)); + break; + case TRX_COMMITTED_IN_MEMORY: + fputs(", COMMITTED IN MEMORY", f); + break; + default: + fprintf(f, " state %lu", (ulong) trx->conc_state); + } + +#ifdef UNIV_LINUX + fprintf(f, ", process no %lu", trx->mysql_process_no); +#endif + fprintf(f, ", OS thread id %lu", + (ulong) os_thread_pf(trx->mysql_thread_id)); + + if (*trx->op_info) { + putc(' ', f); + fputs(trx->op_info, f); + } + + if (trx->is_recovered) { + fputs(" recovered trx", f); + } + + if (trx->is_purge) { + fputs(" purge trx", f); + } + + if (trx->declared_to_be_inside_innodb) { + fprintf(f, ", thread declared inside InnoDB %lu", + (ulong) trx->n_tickets_to_enter_innodb); + } + + putc('\n', f); + + if (trx->n_mysql_tables_in_use > 0 || trx->mysql_n_tables_locked > 0) { + fprintf(f, "mysql tables in use %lu, locked %lu\n", + (ulong) trx->n_mysql_tables_in_use, + (ulong) trx->mysql_n_tables_locked); + } + + newline = TRUE; + + switch (trx->que_state) { + case TRX_QUE_RUNNING: + newline = FALSE; break; + case TRX_QUE_LOCK_WAIT: + fputs("LOCK WAIT ", f); break; + case TRX_QUE_ROLLING_BACK: + fputs("ROLLING BACK ", f); break; + case TRX_QUE_COMMITTING: + fputs("COMMITTING ", f); break; + default: + fprintf(f, "que state %lu ", (ulong) trx->que_state); + } + + if (0 < UT_LIST_GET_LEN(trx->trx_locks) + || mem_heap_get_size(trx->lock_heap) > 400) { + newline = TRUE; + + fprintf(f, "%lu lock struct(s), heap size %lu," + " %lu row lock(s)", + (ulong) UT_LIST_GET_LEN(trx->trx_locks), + (ulong) mem_heap_get_size(trx->lock_heap), + (ulong) lock_number_of_rows_locked(trx)); + } + + if (trx->has_search_latch) { + newline = TRUE; + fputs(", holds adaptive hash latch", f); + } + + if (!ut_dulint_is_zero(trx->undo_no)) { + newline = TRUE; + fprintf(f, ", undo log entries %lu", + (ulong) ut_dulint_get_low(trx->undo_no)); + } + + if (newline) { + putc('\n', f); + } + + if (trx->mysql_thd != NULL) { + innobase_mysql_print_thd(f, trx->mysql_thd, max_query_len); + } +} + +/*********************************************************************** +Compares the "weight" (or size) of two transactions. Transactions that +have edited non-transactional tables are considered heavier than ones +that have not. */ +UNIV_INTERN +int +trx_weight_cmp( +/*===========*/ + /* out: <0, 0 or >0; similar to strcmp(3) */ + const trx_t* a, /* in: the first transaction to be compared */ + const trx_t* b) /* in: the second transaction to be compared */ +{ + ibool a_notrans_edit; + ibool b_notrans_edit; + + /* If mysql_thd is NULL for a transaction we assume that it has + not edited non-transactional tables. */ + + a_notrans_edit = a->mysql_thd != NULL + && thd_has_edited_nontrans_tables(a->mysql_thd); + + b_notrans_edit = b->mysql_thd != NULL + && thd_has_edited_nontrans_tables(b->mysql_thd); + + if (a_notrans_edit && !b_notrans_edit) { + + return(1); + } + + if (!a_notrans_edit && b_notrans_edit) { + + return(-1); + } + + /* Either both had edited non-transactional tables or both had + not, we fall back to comparing the number of altered/locked + rows. */ + +#if 0 + fprintf(stderr, + "%s TRX_WEIGHT(a): %lld+%lu, TRX_WEIGHT(b): %lld+%lu\n", + __func__, + ut_conv_dulint_to_longlong(a->undo_no), + UT_LIST_GET_LEN(a->trx_locks), + ut_conv_dulint_to_longlong(b->undo_no), + UT_LIST_GET_LEN(b->trx_locks)); +#endif + + return(ut_dulint_cmp(TRX_WEIGHT(a), TRX_WEIGHT(b))); +} + +/******************************************************************** +Prepares a transaction. */ +UNIV_INTERN +void +trx_prepare_off_kernel( +/*===================*/ + trx_t* trx) /* in: transaction */ +{ + page_t* update_hdr_page; + trx_rseg_t* rseg; + ib_uint64_t lsn = 0; + mtr_t mtr; + + ut_ad(mutex_own(&kernel_mutex)); + + rseg = trx->rseg; + + if (trx->insert_undo != NULL || trx->update_undo != NULL) { + + mutex_exit(&kernel_mutex); + + mtr_start(&mtr); + + /* Change the undo log segment states from TRX_UNDO_ACTIVE + to TRX_UNDO_PREPARED: these modifications to the file data + structure define the transaction as prepared in the + file-based world, at the serialization point of lsn. */ + + mutex_enter(&(rseg->mutex)); + + if (trx->insert_undo != NULL) { + + /* It is not necessary to obtain trx->undo_mutex here + because only a single OS thread is allowed to do the + transaction prepare for this transaction. */ + + trx_undo_set_state_at_prepare(trx, trx->insert_undo, + &mtr); + } + + if (trx->update_undo) { + update_hdr_page = trx_undo_set_state_at_prepare( + trx, trx->update_undo, &mtr); + } + + mutex_exit(&(rseg->mutex)); + + /*--------------*/ + mtr_commit(&mtr); /* This mtr commit makes the + transaction prepared in the file-based + world */ + /*--------------*/ + lsn = mtr.end_lsn; + + mutex_enter(&kernel_mutex); + } + + ut_ad(mutex_own(&kernel_mutex)); + + /*--------------------------------------*/ + trx->conc_state = TRX_PREPARED; + /*--------------------------------------*/ + + if (lsn) { + /* Depending on the my.cnf options, we may now write the log + buffer to the log files, making the prepared state of the + transaction durable if the OS does not crash. We may also + flush the log files to disk, making the prepared state of the + transaction durable also at an OS crash or a power outage. + + The idea in InnoDB's group prepare is that a group of + transactions gather behind a trx doing a physical disk write + to log files, and when that physical write has been completed, + one of those transactions does a write which prepares the whole + group. Note that this group prepare will only bring benefit if + there are > 2 users in the database. Then at least 2 users can + gather behind one doing the physical log write to disk. + + TODO: find out if MySQL holds some mutex when calling this. + That would spoil our group prepare algorithm. */ + + mutex_exit(&kernel_mutex); + + if (srv_flush_log_at_trx_commit == 0) { + /* Do nothing */ + } else if (srv_flush_log_at_trx_commit == 1) { + if (srv_unix_file_flush_method == SRV_UNIX_NOSYNC) { + /* Write the log but do not flush it to disk */ + + log_write_up_to(lsn, LOG_WAIT_ONE_GROUP, + FALSE); + } else { + /* Write the log to the log files AND flush + them to disk */ + + log_write_up_to(lsn, LOG_WAIT_ONE_GROUP, TRUE); + } + } else if (srv_flush_log_at_trx_commit == 2) { + + /* Write the log but do not flush it to disk */ + + log_write_up_to(lsn, LOG_WAIT_ONE_GROUP, FALSE); + } else { + ut_error; + } + + mutex_enter(&kernel_mutex); + } +} + +/************************************************************************** +Does the transaction prepare for MySQL. */ +UNIV_INTERN +ulint +trx_prepare_for_mysql( +/*==================*/ + /* out: 0 or error number */ + trx_t* trx) /* in: trx handle */ +{ + /* Because we do not do the prepare by sending an Innobase + sig to the transaction, we must here make sure that trx has been + started. */ + + ut_a(trx); + + trx->op_info = "preparing"; + + trx_start_if_not_started(trx); + + mutex_enter(&kernel_mutex); + + trx_prepare_off_kernel(trx); + + mutex_exit(&kernel_mutex); + + trx->op_info = ""; + + return(0); +} + +/************************************************************************** +This function is used to find number of prepared transactions and +their transaction objects for a recovery. */ +UNIV_INTERN +int +trx_recover_for_mysql( +/*==================*/ + /* out: number of prepared transactions + stored in xid_list */ + XID* xid_list, /* in/out: prepared transactions */ + ulint len) /* in: number of slots in xid_list */ +{ + trx_t* trx; + ulint count = 0; + + ut_ad(xid_list); + ut_ad(len); + + /* We should set those transactions which are in the prepared state + to the xid_list */ + + mutex_enter(&kernel_mutex); + + trx = UT_LIST_GET_FIRST(trx_sys->trx_list); + + while (trx) { + if (trx->conc_state == TRX_PREPARED) { + xid_list[count] = trx->xid; + + if (count == 0) { + ut_print_timestamp(stderr); + fprintf(stderr, + " InnoDB: Starting recovery for" + " XA transactions...\n"); + } + + ut_print_timestamp(stderr); + fprintf(stderr, + " InnoDB: Transaction " TRX_ID_FMT " in" + " prepared state after recovery\n", + TRX_ID_PREP_PRINTF(trx->id)); + + ut_print_timestamp(stderr); + fprintf(stderr, + " InnoDB: Transaction contains changes" + " to %lu rows\n", + (ulong) ut_conv_dulint_to_longlong( + trx->undo_no)); + + count++; + + if (count == len) { + break; + } + } + + trx = UT_LIST_GET_NEXT(trx_list, trx); + } + + mutex_exit(&kernel_mutex); + + if (count > 0){ + ut_print_timestamp(stderr); + fprintf(stderr, + " InnoDB: %lu transactions in prepared state" + " after recovery\n", + (ulong) count); + } + + return ((int) count); +} + +/*********************************************************************** +This function is used to find one X/Open XA distributed transaction +which is in the prepared state */ +UNIV_INTERN +trx_t* +trx_get_trx_by_xid( +/*===============*/ + /* out: trx or NULL */ + XID* xid) /* in: X/Open XA transaction identification */ +{ + trx_t* trx; + + if (xid == NULL) { + + return (NULL); + } + + mutex_enter(&kernel_mutex); + + trx = UT_LIST_GET_FIRST(trx_sys->trx_list); + + while (trx) { + /* Compare two X/Open XA transaction id's: their + length should be the same and binary comparison + of gtrid_lenght+bqual_length bytes should be + the same */ + + if (xid->gtrid_length == trx->xid.gtrid_length + && xid->bqual_length == trx->xid.bqual_length + && memcmp(xid->data, trx->xid.data, + xid->gtrid_length + xid->bqual_length) == 0) { + break; + } + + trx = UT_LIST_GET_NEXT(trx_list, trx); + } + + mutex_exit(&kernel_mutex); + + if (trx) { + if (trx->conc_state != TRX_PREPARED) { + + return(NULL); + } + + return(trx); + } else { + return(NULL); + } +} diff --git a/storage/xtradb/trx/trx0undo.c b/storage/xtradb/trx/trx0undo.c new file mode 100644 index 00000000000..bb5710aeba9 --- /dev/null +++ b/storage/xtradb/trx/trx0undo.c @@ -0,0 +1,2021 @@ +/***************************************************************************** + +Copyright (c) 1996, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/****************************************************** +Transaction undo log + +Created 3/26/1996 Heikki Tuuri +*******************************************************/ + +#include "trx0undo.h" + +#ifdef UNIV_NONINL +#include "trx0undo.ic" +#endif + +#include "fsp0fsp.h" +#include "mach0data.h" +#include "trx0rseg.h" +#include "trx0trx.h" +#include "srv0srv.h" +#include "trx0rec.h" +#include "trx0purge.h" +#include "trx0xa.h" + +/* How should the old versions in the history list be managed? + ---------------------------------------------------------- +If each transaction is given a whole page for its update undo log, file +space consumption can be 10 times higher than necessary. Therefore, +partly filled update undo log pages should be reusable. But then there +is no way individual pages can be ordered so that the ordering agrees +with the serialization numbers of the transactions on the pages. Thus, +the history list must be formed of undo logs, not their header pages as +it was in the old implementation. + However, on a single header page the transactions are placed in +the order of their serialization numbers. As old versions are purged, we +may free the page when the last transaction on the page has been purged. + A problem is that the purge has to go through the transactions +in the serialization order. This means that we have to look through all +rollback segments for the one that has the smallest transaction number +in its history list. + When should we do a purge? A purge is necessary when space is +running out in any of the rollback segments. Then we may have to purge +also old version which might be needed by some consistent read. How do +we trigger the start of a purge? When a transaction writes to an undo log, +it may notice that the space is running out. When a read view is closed, +it may make some history superfluous. The server can have an utility which +periodically checks if it can purge some history. + In a parallellized purge we have the problem that a query thread +can remove a delete marked clustered index record before another query +thread has processed an earlier version of the record, which cannot then +be done because the row cannot be constructed from the clustered index +record. To avoid this problem, we will store in the update and delete mark +undo record also the columns necessary to construct the secondary index +entries which are modified. + We can latch the stack of versions of a single clustered index record +by taking a latch on the clustered index page. As long as the latch is held, +no new versions can be added and no versions removed by undo. But, a purge +can still remove old versions from the bottom of the stack. */ + +/* How to protect rollback segments, undo logs, and history lists with + ------------------------------------------------------------------- +latches? +------- +The contention of the kernel mutex should be minimized. When a transaction +does its first insert or modify in an index, an undo log is assigned for it. +Then we must have an x-latch to the rollback segment header. + When the transaction does more modifys or rolls back, the undo log is +protected with undo_mutex in the transaction. + When the transaction commits, its insert undo log is either reset and +cached for a fast reuse, or freed. In these cases we must have an x-latch on +the rollback segment page. The update undo log is put to the history list. If +it is not suitable for reuse, its slot in the rollback segment is reset. In +both cases, an x-latch must be acquired on the rollback segment. + The purge operation steps through the history list without modifying +it until a truncate operation occurs, which can remove undo logs from the end +of the list and release undo log segments. In stepping through the list, +s-latches on the undo log pages are enough, but in a truncate, x-latches must +be obtained on the rollback segment and individual pages. */ + +/************************************************************************ +Initializes the fields in an undo log segment page. */ +static +void +trx_undo_page_init( +/*===============*/ + page_t* undo_page, /* in: undo log segment page */ + ulint type, /* in: undo log segment type */ + mtr_t* mtr); /* in: mtr */ +/************************************************************************ +Creates and initializes an undo log memory object. */ +static +trx_undo_t* +trx_undo_mem_create( +/*================*/ + /* out, own: the undo log memory object */ + trx_rseg_t* rseg, /* in: rollback segment memory object */ + ulint id, /* in: slot index within rseg */ + ulint type, /* in: type of the log: TRX_UNDO_INSERT or + TRX_UNDO_UPDATE */ + dulint trx_id, /* in: id of the trx for which the undo log + is created */ + const XID* xid, /* in: X/Open XA transaction identification*/ + ulint page_no,/* in: undo log header page number */ + ulint offset);/* in: undo log header byte offset on page */ +/******************************************************************* +Initializes a cached insert undo log header page for new use. NOTE that this +function has its own log record type MLOG_UNDO_HDR_REUSE. You must NOT change +the operation of this function! */ +static +ulint +trx_undo_insert_header_reuse( +/*=========================*/ + /* out: undo log header byte offset on page */ + page_t* undo_page, /* in: insert undo log segment header page, + x-latched */ + dulint trx_id, /* in: transaction id */ + mtr_t* mtr); /* in: mtr */ +/************************************************************************** +If an update undo log can be discarded immediately, this function frees the +space, resetting the page to the proper state for caching. */ +static +void +trx_undo_discard_latest_update_undo( +/*================================*/ + page_t* undo_page, /* in: header page of an undo log of size 1 */ + mtr_t* mtr); /* in: mtr */ + + +/*************************************************************************** +Gets the previous record in an undo log from the previous page. */ +static +trx_undo_rec_t* +trx_undo_get_prev_rec_from_prev_page( +/*=================================*/ + /* out: undo log record, the page s-latched, + NULL if none */ + trx_undo_rec_t* rec, /* in: undo record */ + ulint page_no,/* in: undo log header page number */ + ulint offset, /* in: undo log header offset on page */ + mtr_t* mtr) /* in: mtr */ +{ + ulint space; + ulint zip_size; + ulint prev_page_no; + page_t* prev_page; + page_t* undo_page; + + undo_page = page_align(rec); + + prev_page_no = flst_get_prev_addr(undo_page + TRX_UNDO_PAGE_HDR + + TRX_UNDO_PAGE_NODE, mtr) + .page; + + if (prev_page_no == FIL_NULL) { + + return(NULL); + } + + space = page_get_space_id(undo_page); + zip_size = fil_space_get_zip_size(space); + + prev_page = trx_undo_page_get_s_latched(space, zip_size, + prev_page_no, mtr); + + return(trx_undo_page_get_last_rec(prev_page, page_no, offset)); +} + +/*************************************************************************** +Gets the previous record in an undo log. */ +UNIV_INTERN +trx_undo_rec_t* +trx_undo_get_prev_rec( +/*==================*/ + /* out: undo log record, the page s-latched, + NULL if none */ + trx_undo_rec_t* rec, /* in: undo record */ + ulint page_no,/* in: undo log header page number */ + ulint offset, /* in: undo log header offset on page */ + mtr_t* mtr) /* in: mtr */ +{ + trx_undo_rec_t* prev_rec; + + prev_rec = trx_undo_page_get_prev_rec(rec, page_no, offset); + + if (prev_rec) { + + return(prev_rec); + } + + /* We have to go to the previous undo log page to look for the + previous record */ + + return(trx_undo_get_prev_rec_from_prev_page(rec, page_no, offset, + mtr)); +} + +/*************************************************************************** +Gets the next record in an undo log from the next page. */ +static +trx_undo_rec_t* +trx_undo_get_next_rec_from_next_page( +/*=================================*/ + /* out: undo log record, the page latched, NULL if + none */ + ulint space, /* in: undo log header space */ + ulint zip_size,/* in: compressed page size in bytes + or 0 for uncompressed pages */ + page_t* undo_page, /* in: undo log page */ + ulint page_no,/* in: undo log header page number */ + ulint offset, /* in: undo log header offset on page */ + ulint mode, /* in: latch mode: RW_S_LATCH or RW_X_LATCH */ + mtr_t* mtr) /* in: mtr */ +{ + trx_ulogf_t* log_hdr; + ulint next_page_no; + page_t* next_page; + ulint next; + + if (page_no == page_get_page_no(undo_page)) { + + log_hdr = undo_page + offset; + next = mach_read_from_2(log_hdr + TRX_UNDO_NEXT_LOG); + + if (next != 0) { + + return(NULL); + } + } + + next_page_no = flst_get_next_addr(undo_page + TRX_UNDO_PAGE_HDR + + TRX_UNDO_PAGE_NODE, mtr) + .page; + if (next_page_no == FIL_NULL) { + + return(NULL); + } + + if (mode == RW_S_LATCH) { + next_page = trx_undo_page_get_s_latched(space, zip_size, + next_page_no, mtr); + } else { + ut_ad(mode == RW_X_LATCH); + next_page = trx_undo_page_get(space, zip_size, + next_page_no, mtr); + } + + return(trx_undo_page_get_first_rec(next_page, page_no, offset)); +} + +/*************************************************************************** +Gets the next record in an undo log. */ +UNIV_INTERN +trx_undo_rec_t* +trx_undo_get_next_rec( +/*==================*/ + /* out: undo log record, the page s-latched, + NULL if none */ + trx_undo_rec_t* rec, /* in: undo record */ + ulint page_no,/* in: undo log header page number */ + ulint offset, /* in: undo log header offset on page */ + mtr_t* mtr) /* in: mtr */ +{ + ulint space; + ulint zip_size; + trx_undo_rec_t* next_rec; + + next_rec = trx_undo_page_get_next_rec(rec, page_no, offset); + + if (next_rec) { + return(next_rec); + } + + space = page_get_space_id(page_align(rec)); + zip_size = fil_space_get_zip_size(space); + + return(trx_undo_get_next_rec_from_next_page(space, zip_size, + page_align(rec), + page_no, offset, + RW_S_LATCH, mtr)); +} + +/*************************************************************************** +Gets the first record in an undo log. */ +UNIV_INTERN +trx_undo_rec_t* +trx_undo_get_first_rec( +/*===================*/ + /* out: undo log record, the page latched, NULL if + none */ + ulint space, /* in: undo log header space */ + ulint zip_size,/* in: compressed page size in bytes + or 0 for uncompressed pages */ + ulint page_no,/* in: undo log header page number */ + ulint offset, /* in: undo log header offset on page */ + ulint mode, /* in: latching mode: RW_S_LATCH or RW_X_LATCH */ + mtr_t* mtr) /* in: mtr */ +{ + page_t* undo_page; + trx_undo_rec_t* rec; + + if (mode == RW_S_LATCH) { + undo_page = trx_undo_page_get_s_latched(space, zip_size, + page_no, mtr); + } else { + undo_page = trx_undo_page_get(space, zip_size, page_no, mtr); + } + + rec = trx_undo_page_get_first_rec(undo_page, page_no, offset); + + if (rec) { + return(rec); + } + + return(trx_undo_get_next_rec_from_next_page(space, zip_size, + undo_page, page_no, offset, + mode, mtr)); +} + +/*============== UNDO LOG FILE COPY CREATION AND FREEING ==================*/ + +/************************************************************************** +Writes the mtr log entry of an undo log page initialization. */ +UNIV_INLINE +void +trx_undo_page_init_log( +/*===================*/ + page_t* undo_page, /* in: undo log page */ + ulint type, /* in: undo log type */ + mtr_t* mtr) /* in: mtr */ +{ + mlog_write_initial_log_record(undo_page, MLOG_UNDO_INIT, mtr); + + mlog_catenate_ulint_compressed(mtr, type); +} + +/*************************************************************** +Parses the redo log entry of an undo log page initialization. */ +UNIV_INTERN +byte* +trx_undo_parse_page_init( +/*=====================*/ + /* out: end of log record or NULL */ + byte* ptr, /* in: buffer */ + byte* end_ptr,/* in: buffer end */ + page_t* page, /* in: page or NULL */ + mtr_t* mtr) /* in: mtr or NULL */ +{ + ulint type; + + ptr = mach_parse_compressed(ptr, end_ptr, &type); + + if (ptr == NULL) { + + return(NULL); + } + + if (page) { + trx_undo_page_init(page, type, mtr); + } + + return(ptr); +} + +/************************************************************************ +Initializes the fields in an undo log segment page. */ +static +void +trx_undo_page_init( +/*===============*/ + page_t* undo_page, /* in: undo log segment page */ + ulint type, /* in: undo log segment type */ + mtr_t* mtr) /* in: mtr */ +{ + trx_upagef_t* page_hdr; + + page_hdr = undo_page + TRX_UNDO_PAGE_HDR; + + mach_write_to_2(page_hdr + TRX_UNDO_PAGE_TYPE, type); + + mach_write_to_2(page_hdr + TRX_UNDO_PAGE_START, + TRX_UNDO_PAGE_HDR + TRX_UNDO_PAGE_HDR_SIZE); + mach_write_to_2(page_hdr + TRX_UNDO_PAGE_FREE, + TRX_UNDO_PAGE_HDR + TRX_UNDO_PAGE_HDR_SIZE); + + fil_page_set_type(undo_page, FIL_PAGE_UNDO_LOG); + + trx_undo_page_init_log(undo_page, type, mtr); +} + +/******************************************************************* +Creates a new undo log segment in file. */ +static +ulint +trx_undo_seg_create( +/*================*/ + /* out: DB_SUCCESS if page creation OK + possible error codes are: + DB_TOO_MANY_CONCURRENT_TRXS + DB_OUT_OF_FILE_SPACE */ + trx_rseg_t* rseg __attribute__((unused)),/* in: rollback segment */ + trx_rsegf_t* rseg_hdr,/* in: rollback segment header, page + x-latched */ + ulint type, /* in: type of the segment: TRX_UNDO_INSERT or + TRX_UNDO_UPDATE */ + ulint* id, /* out: slot index within rseg header */ + page_t** undo_page, + /* out: segment header page x-latched, NULL + if there was an error */ + mtr_t* mtr) /* in: mtr */ +{ + ulint slot_no; + ulint space; + buf_block_t* block; + trx_upagef_t* page_hdr; + trx_usegf_t* seg_hdr; + ulint n_reserved; + ibool success; + ulint err = DB_SUCCESS; + + ut_ad(mtr && id && rseg_hdr); + ut_ad(mutex_own(&(rseg->mutex))); + + /* fputs(type == TRX_UNDO_INSERT + ? "Creating insert undo log segment\n" + : "Creating update undo log segment\n", stderr); */ + slot_no = trx_rsegf_undo_find_free(rseg_hdr, mtr); + + if (slot_no == ULINT_UNDEFINED) { + ut_print_timestamp(stderr); + fprintf(stderr, + " InnoDB: Warning: cannot find a free slot for" + " an undo log. Do you have too\n" + "InnoDB: many active transactions" + " running concurrently?\n"); + + return(DB_TOO_MANY_CONCURRENT_TRXS); + } + + space = page_get_space_id(page_align(rseg_hdr)); + + success = fsp_reserve_free_extents(&n_reserved, space, 2, FSP_UNDO, + mtr); + if (!success) { + + return(DB_OUT_OF_FILE_SPACE); + } + + /* Allocate a new file segment for the undo log */ + block = fseg_create_general(space, 0, + TRX_UNDO_SEG_HDR + + TRX_UNDO_FSEG_HEADER, TRUE, mtr); + + fil_space_release_free_extents(space, n_reserved); + + if (block == NULL) { + /* No space left */ + + return(DB_OUT_OF_FILE_SPACE); + } + + buf_block_dbg_add_level(block, SYNC_TRX_UNDO_PAGE); + + *undo_page = buf_block_get_frame(block); + + page_hdr = *undo_page + TRX_UNDO_PAGE_HDR; + seg_hdr = *undo_page + TRX_UNDO_SEG_HDR; + + trx_undo_page_init(*undo_page, type, mtr); + + mlog_write_ulint(page_hdr + TRX_UNDO_PAGE_FREE, + TRX_UNDO_SEG_HDR + TRX_UNDO_SEG_HDR_SIZE, + MLOG_2BYTES, mtr); + + mlog_write_ulint(seg_hdr + TRX_UNDO_LAST_LOG, 0, MLOG_2BYTES, mtr); + + flst_init(seg_hdr + TRX_UNDO_PAGE_LIST, mtr); + + flst_add_last(seg_hdr + TRX_UNDO_PAGE_LIST, + page_hdr + TRX_UNDO_PAGE_NODE, mtr); + + trx_rsegf_set_nth_undo(rseg_hdr, slot_no, + page_get_page_no(*undo_page), mtr); + *id = slot_no; + + return(err); +} + +/************************************************************************** +Writes the mtr log entry of an undo log header initialization. */ +UNIV_INLINE +void +trx_undo_header_create_log( +/*=======================*/ + page_t* undo_page, /* in: undo log header page */ + dulint trx_id, /* in: transaction id */ + mtr_t* mtr) /* in: mtr */ +{ + mlog_write_initial_log_record(undo_page, MLOG_UNDO_HDR_CREATE, mtr); + + mlog_catenate_dulint_compressed(mtr, trx_id); +} + +/******************************************************************* +Creates a new undo log header in file. NOTE that this function has its own +log record type MLOG_UNDO_HDR_CREATE. You must NOT change the operation of +this function! */ +static +ulint +trx_undo_header_create( +/*===================*/ + /* out: header byte offset on page */ + page_t* undo_page, /* in: undo log segment header page, + x-latched; it is assumed that there are + TRX_UNDO_LOG_XA_HDR_SIZE bytes free space + on it */ + dulint trx_id, /* in: transaction id */ + mtr_t* mtr) /* in: mtr */ +{ + trx_upagef_t* page_hdr; + trx_usegf_t* seg_hdr; + trx_ulogf_t* log_hdr; + trx_ulogf_t* prev_log_hdr; + ulint prev_log; + ulint free; + ulint new_free; + + ut_ad(mtr && undo_page); + + page_hdr = undo_page + TRX_UNDO_PAGE_HDR; + seg_hdr = undo_page + TRX_UNDO_SEG_HDR; + + free = mach_read_from_2(page_hdr + TRX_UNDO_PAGE_FREE); + + log_hdr = undo_page + free; + + new_free = free + TRX_UNDO_LOG_OLD_HDR_SIZE; + + ut_a(free + TRX_UNDO_LOG_XA_HDR_SIZE < UNIV_PAGE_SIZE - 100); + + mach_write_to_2(page_hdr + TRX_UNDO_PAGE_START, new_free); + + mach_write_to_2(page_hdr + TRX_UNDO_PAGE_FREE, new_free); + + mach_write_to_2(seg_hdr + TRX_UNDO_STATE, TRX_UNDO_ACTIVE); + + prev_log = mach_read_from_2(seg_hdr + TRX_UNDO_LAST_LOG); + + if (prev_log != 0) { + prev_log_hdr = undo_page + prev_log; + + mach_write_to_2(prev_log_hdr + TRX_UNDO_NEXT_LOG, free); + } + + mach_write_to_2(seg_hdr + TRX_UNDO_LAST_LOG, free); + + log_hdr = undo_page + free; + + mach_write_to_2(log_hdr + TRX_UNDO_DEL_MARKS, TRUE); + + mach_write_to_8(log_hdr + TRX_UNDO_TRX_ID, trx_id); + mach_write_to_2(log_hdr + TRX_UNDO_LOG_START, new_free); + + mach_write_to_1(log_hdr + TRX_UNDO_XID_EXISTS, FALSE); + mach_write_to_1(log_hdr + TRX_UNDO_DICT_TRANS, FALSE); + + mach_write_to_2(log_hdr + TRX_UNDO_NEXT_LOG, 0); + mach_write_to_2(log_hdr + TRX_UNDO_PREV_LOG, prev_log); + + /* Write the log record about the header creation */ + trx_undo_header_create_log(undo_page, trx_id, mtr); + + return(free); +} + +/************************************************************************ +Write X/Open XA Transaction Identification (XID) to undo log header */ +static +void +trx_undo_write_xid( +/*===============*/ + trx_ulogf_t* log_hdr,/* in: undo log header */ + const XID* xid, /* in: X/Open XA Transaction Identification */ + mtr_t* mtr) /* in: mtr */ +{ + mlog_write_ulint(log_hdr + TRX_UNDO_XA_FORMAT, + (ulint)xid->formatID, MLOG_4BYTES, mtr); + + mlog_write_ulint(log_hdr + TRX_UNDO_XA_TRID_LEN, + (ulint)xid->gtrid_length, MLOG_4BYTES, mtr); + + mlog_write_ulint(log_hdr + TRX_UNDO_XA_BQUAL_LEN, + (ulint)xid->bqual_length, MLOG_4BYTES, mtr); + + mlog_write_string(log_hdr + TRX_UNDO_XA_XID, (const byte*) xid->data, + XIDDATASIZE, mtr); +} + +/************************************************************************ +Read X/Open XA Transaction Identification (XID) from undo log header */ +static +void +trx_undo_read_xid( +/*==============*/ + trx_ulogf_t* log_hdr,/* in: undo log header */ + XID* xid) /* out: X/Open XA Transaction Identification */ +{ + xid->formatID = (long)mach_read_from_4(log_hdr + TRX_UNDO_XA_FORMAT); + + xid->gtrid_length + = (long) mach_read_from_4(log_hdr + TRX_UNDO_XA_TRID_LEN); + xid->bqual_length + = (long) mach_read_from_4(log_hdr + TRX_UNDO_XA_BQUAL_LEN); + + memcpy(xid->data, log_hdr + TRX_UNDO_XA_XID, XIDDATASIZE); +} + +/******************************************************************* +Adds space for the XA XID after an undo log old-style header. */ +static +void +trx_undo_header_add_space_for_xid( +/*==============================*/ + page_t* undo_page,/* in: undo log segment header page */ + trx_ulogf_t* log_hdr,/* in: undo log header */ + mtr_t* mtr) /* in: mtr */ +{ + trx_upagef_t* page_hdr; + ulint free; + ulint new_free; + + page_hdr = undo_page + TRX_UNDO_PAGE_HDR; + + free = mach_read_from_2(page_hdr + TRX_UNDO_PAGE_FREE); + + /* free is now the end offset of the old style undo log header */ + + ut_a(free == (ulint)(log_hdr - undo_page) + TRX_UNDO_LOG_OLD_HDR_SIZE); + + new_free = free + (TRX_UNDO_LOG_XA_HDR_SIZE + - TRX_UNDO_LOG_OLD_HDR_SIZE); + + /* Add space for a XID after the header, update the free offset + fields on the undo log page and in the undo log header */ + + mlog_write_ulint(page_hdr + TRX_UNDO_PAGE_START, new_free, + MLOG_2BYTES, mtr); + + mlog_write_ulint(page_hdr + TRX_UNDO_PAGE_FREE, new_free, + MLOG_2BYTES, mtr); + + mlog_write_ulint(log_hdr + TRX_UNDO_LOG_START, new_free, + MLOG_2BYTES, mtr); +} + +/************************************************************************** +Writes the mtr log entry of an undo log header reuse. */ +UNIV_INLINE +void +trx_undo_insert_header_reuse_log( +/*=============================*/ + page_t* undo_page, /* in: undo log header page */ + dulint trx_id, /* in: transaction id */ + mtr_t* mtr) /* in: mtr */ +{ + mlog_write_initial_log_record(undo_page, MLOG_UNDO_HDR_REUSE, mtr); + + mlog_catenate_dulint_compressed(mtr, trx_id); +} + +/*************************************************************** +Parses the redo log entry of an undo log page header create or reuse. */ +UNIV_INTERN +byte* +trx_undo_parse_page_header( +/*=======================*/ + /* out: end of log record or NULL */ + ulint type, /* in: MLOG_UNDO_HDR_CREATE or MLOG_UNDO_HDR_REUSE */ + byte* ptr, /* in: buffer */ + byte* end_ptr,/* in: buffer end */ + page_t* page, /* in: page or NULL */ + mtr_t* mtr) /* in: mtr or NULL */ +{ + dulint trx_id; + + ptr = mach_dulint_parse_compressed(ptr, end_ptr, &trx_id); + + if (ptr == NULL) { + + return(NULL); + } + + if (page) { + if (type == MLOG_UNDO_HDR_CREATE) { + trx_undo_header_create(page, trx_id, mtr); + } else { + ut_ad(type == MLOG_UNDO_HDR_REUSE); + trx_undo_insert_header_reuse(page, trx_id, mtr); + } + } + + return(ptr); +} + +/******************************************************************* +Initializes a cached insert undo log header page for new use. NOTE that this +function has its own log record type MLOG_UNDO_HDR_REUSE. You must NOT change +the operation of this function! */ +static +ulint +trx_undo_insert_header_reuse( +/*=========================*/ + /* out: undo log header byte offset on page */ + page_t* undo_page, /* in: insert undo log segment header page, + x-latched */ + dulint trx_id, /* in: transaction id */ + mtr_t* mtr) /* in: mtr */ +{ + trx_upagef_t* page_hdr; + trx_usegf_t* seg_hdr; + trx_ulogf_t* log_hdr; + ulint free; + ulint new_free; + + ut_ad(mtr && undo_page); + + page_hdr = undo_page + TRX_UNDO_PAGE_HDR; + seg_hdr = undo_page + TRX_UNDO_SEG_HDR; + + free = TRX_UNDO_SEG_HDR + TRX_UNDO_SEG_HDR_SIZE; + + ut_a(free + TRX_UNDO_LOG_XA_HDR_SIZE < UNIV_PAGE_SIZE - 100); + + log_hdr = undo_page + free; + + new_free = free + TRX_UNDO_LOG_OLD_HDR_SIZE; + + /* Insert undo data is not needed after commit: we may free all + the space on the page */ + + ut_a(mach_read_from_2(undo_page + TRX_UNDO_PAGE_HDR + + TRX_UNDO_PAGE_TYPE) + == TRX_UNDO_INSERT); + + mach_write_to_2(page_hdr + TRX_UNDO_PAGE_START, new_free); + + mach_write_to_2(page_hdr + TRX_UNDO_PAGE_FREE, new_free); + + mach_write_to_2(seg_hdr + TRX_UNDO_STATE, TRX_UNDO_ACTIVE); + + log_hdr = undo_page + free; + + mach_write_to_8(log_hdr + TRX_UNDO_TRX_ID, trx_id); + mach_write_to_2(log_hdr + TRX_UNDO_LOG_START, new_free); + + mach_write_to_1(log_hdr + TRX_UNDO_XID_EXISTS, FALSE); + mach_write_to_1(log_hdr + TRX_UNDO_DICT_TRANS, FALSE); + + /* Write the log record MLOG_UNDO_HDR_REUSE */ + trx_undo_insert_header_reuse_log(undo_page, trx_id, mtr); + + return(free); +} + +/************************************************************************** +Writes the redo log entry of an update undo log header discard. */ +UNIV_INLINE +void +trx_undo_discard_latest_log( +/*========================*/ + page_t* undo_page, /* in: undo log header page */ + mtr_t* mtr) /* in: mtr */ +{ + mlog_write_initial_log_record(undo_page, MLOG_UNDO_HDR_DISCARD, mtr); +} + +/*************************************************************** +Parses the redo log entry of an undo log page header discard. */ +UNIV_INTERN +byte* +trx_undo_parse_discard_latest( +/*==========================*/ + /* out: end of log record or NULL */ + byte* ptr, /* in: buffer */ + byte* end_ptr __attribute__((unused)), /* in: buffer end */ + page_t* page, /* in: page or NULL */ + mtr_t* mtr) /* in: mtr or NULL */ +{ + ut_ad(end_ptr); + + if (page) { + trx_undo_discard_latest_update_undo(page, mtr); + } + + return(ptr); +} + +/************************************************************************** +If an update undo log can be discarded immediately, this function frees the +space, resetting the page to the proper state for caching. */ +static +void +trx_undo_discard_latest_update_undo( +/*================================*/ + page_t* undo_page, /* in: header page of an undo log of size 1 */ + mtr_t* mtr) /* in: mtr */ +{ + trx_usegf_t* seg_hdr; + trx_upagef_t* page_hdr; + trx_ulogf_t* log_hdr; + trx_ulogf_t* prev_log_hdr; + ulint free; + ulint prev_hdr_offset; + + seg_hdr = undo_page + TRX_UNDO_SEG_HDR; + page_hdr = undo_page + TRX_UNDO_PAGE_HDR; + + free = mach_read_from_2(seg_hdr + TRX_UNDO_LAST_LOG); + log_hdr = undo_page + free; + + prev_hdr_offset = mach_read_from_2(log_hdr + TRX_UNDO_PREV_LOG); + + if (prev_hdr_offset != 0) { + prev_log_hdr = undo_page + prev_hdr_offset; + + mach_write_to_2(page_hdr + TRX_UNDO_PAGE_START, + mach_read_from_2(prev_log_hdr + + TRX_UNDO_LOG_START)); + mach_write_to_2(prev_log_hdr + TRX_UNDO_NEXT_LOG, 0); + } + + mach_write_to_2(page_hdr + TRX_UNDO_PAGE_FREE, free); + + mach_write_to_2(seg_hdr + TRX_UNDO_STATE, TRX_UNDO_CACHED); + mach_write_to_2(seg_hdr + TRX_UNDO_LAST_LOG, prev_hdr_offset); + + trx_undo_discard_latest_log(undo_page, mtr); +} + +/************************************************************************ +Tries to add a page to the undo log segment where the undo log is placed. */ +UNIV_INTERN +ulint +trx_undo_add_page( +/*==============*/ + /* out: page number if success, else + FIL_NULL */ + trx_t* trx, /* in: transaction */ + trx_undo_t* undo, /* in: undo log memory object */ + mtr_t* mtr) /* in: mtr which does not have a latch to any + undo log page; the caller must have reserved + the rollback segment mutex */ +{ + page_t* header_page; + page_t* new_page; + trx_rseg_t* rseg; + ulint page_no; + ulint n_reserved; + ibool success; + + ut_ad(mutex_own(&(trx->undo_mutex))); + ut_ad(!mutex_own(&kernel_mutex)); + ut_ad(mutex_own(&(trx->rseg->mutex))); + + rseg = trx->rseg; + + if (rseg->curr_size == rseg->max_size) { + + return(FIL_NULL); + } + + header_page = trx_undo_page_get(undo->space, undo->zip_size, + undo->hdr_page_no, mtr); + + success = fsp_reserve_free_extents(&n_reserved, undo->space, 1, + FSP_UNDO, mtr); + if (!success) { + + return(FIL_NULL); + } + + page_no = fseg_alloc_free_page_general(header_page + TRX_UNDO_SEG_HDR + + TRX_UNDO_FSEG_HEADER, + undo->top_page_no + 1, FSP_UP, + TRUE, mtr); + + fil_space_release_free_extents(undo->space, n_reserved); + + if (page_no == FIL_NULL) { + + /* No space left */ + + return(FIL_NULL); + } + + undo->last_page_no = page_no; + + new_page = trx_undo_page_get(undo->space, undo->zip_size, + page_no, mtr); + + trx_undo_page_init(new_page, undo->type, mtr); + + flst_add_last(header_page + TRX_UNDO_SEG_HDR + TRX_UNDO_PAGE_LIST, + new_page + TRX_UNDO_PAGE_HDR + TRX_UNDO_PAGE_NODE, mtr); + undo->size++; + rseg->curr_size++; + + return(page_no); +} + +/************************************************************************ +Frees an undo log page that is not the header page. */ +static +ulint +trx_undo_free_page( +/*===============*/ + /* out: last page number in remaining log */ + trx_rseg_t* rseg, /* in: rollback segment */ + ibool in_history, /* in: TRUE if the undo log is in the history + list */ + ulint space, /* in: space */ + ulint hdr_page_no, /* in: header page number */ + ulint page_no, /* in: page number to free: must not be the + header page */ + mtr_t* mtr) /* in: mtr which does not have a latch to any + undo log page; the caller must have reserved + the rollback segment mutex */ +{ + page_t* header_page; + page_t* undo_page; + fil_addr_t last_addr; + trx_rsegf_t* rseg_header; + ulint hist_size; + ulint zip_size; + + ut_a(hdr_page_no != page_no); + ut_ad(!mutex_own(&kernel_mutex)); + ut_ad(mutex_own(&(rseg->mutex))); + + zip_size = rseg->zip_size; + + undo_page = trx_undo_page_get(space, zip_size, page_no, mtr); + + header_page = trx_undo_page_get(space, zip_size, hdr_page_no, mtr); + + flst_remove(header_page + TRX_UNDO_SEG_HDR + TRX_UNDO_PAGE_LIST, + undo_page + TRX_UNDO_PAGE_HDR + TRX_UNDO_PAGE_NODE, mtr); + + fseg_free_page(header_page + TRX_UNDO_SEG_HDR + TRX_UNDO_FSEG_HEADER, + space, page_no, mtr); + + last_addr = flst_get_last(header_page + TRX_UNDO_SEG_HDR + + TRX_UNDO_PAGE_LIST, mtr); + rseg->curr_size--; + + if (in_history) { + rseg_header = trx_rsegf_get(space, zip_size, + rseg->page_no, mtr); + + hist_size = mtr_read_ulint(rseg_header + TRX_RSEG_HISTORY_SIZE, + MLOG_4BYTES, mtr); + ut_ad(hist_size > 0); + mlog_write_ulint(rseg_header + TRX_RSEG_HISTORY_SIZE, + hist_size - 1, MLOG_4BYTES, mtr); + } + + return(last_addr.page); +} + +/************************************************************************ +Frees an undo log page when there is also the memory object for the undo +log. */ +static +void +trx_undo_free_page_in_rollback( +/*===========================*/ + trx_t* trx __attribute__((unused)), /* in: transaction */ + trx_undo_t* undo, /* in: undo log memory copy */ + ulint page_no,/* in: page number to free: must not be the + header page */ + mtr_t* mtr) /* in: mtr which does not have a latch to any + undo log page; the caller must have reserved + the rollback segment mutex */ +{ + ulint last_page_no; + + ut_ad(undo->hdr_page_no != page_no); + ut_ad(mutex_own(&(trx->undo_mutex))); + + last_page_no = trx_undo_free_page(undo->rseg, FALSE, undo->space, + undo->hdr_page_no, page_no, mtr); + + undo->last_page_no = last_page_no; + undo->size--; +} + +/************************************************************************ +Empties an undo log header page of undo records for that undo log. Other +undo logs may still have records on that page, if it is an update undo log. */ +static +void +trx_undo_empty_header_page( +/*=======================*/ + ulint space, /* in: space */ + ulint zip_size, /* in: compressed page size in bytes + or 0 for uncompressed pages */ + ulint hdr_page_no, /* in: header page number */ + ulint hdr_offset, /* in: header offset */ + mtr_t* mtr) /* in: mtr */ +{ + page_t* header_page; + trx_ulogf_t* log_hdr; + ulint end; + + header_page = trx_undo_page_get(space, zip_size, hdr_page_no, mtr); + + log_hdr = header_page + hdr_offset; + + end = trx_undo_page_get_end(header_page, hdr_page_no, hdr_offset); + + mlog_write_ulint(log_hdr + TRX_UNDO_LOG_START, end, MLOG_2BYTES, mtr); +} + +/*************************************************************************** +Truncates an undo log from the end. This function is used during a rollback +to free space from an undo log. */ +UNIV_INTERN +void +trx_undo_truncate_end( +/*==================*/ + trx_t* trx, /* in: transaction whose undo log it is */ + trx_undo_t* undo, /* in: undo log */ + dulint limit) /* in: all undo records with undo number + >= this value should be truncated */ +{ + page_t* undo_page; + ulint last_page_no; + trx_undo_rec_t* rec; + trx_undo_rec_t* trunc_here; + trx_rseg_t* rseg; + mtr_t mtr; + + ut_ad(mutex_own(&(trx->undo_mutex))); + ut_ad(mutex_own(&(trx->rseg->mutex))); + + rseg = trx->rseg; + + for (;;) { + mtr_start(&mtr); + + trunc_here = NULL; + + last_page_no = undo->last_page_no; + + undo_page = trx_undo_page_get(undo->space, undo->zip_size, + last_page_no, &mtr); + + rec = trx_undo_page_get_last_rec(undo_page, undo->hdr_page_no, + undo->hdr_offset); + for (;;) { + if (rec == NULL) { + if (last_page_no == undo->hdr_page_no) { + + goto function_exit; + } + + trx_undo_free_page_in_rollback( + trx, undo, last_page_no, &mtr); + break; + } + + if (ut_dulint_cmp(trx_undo_rec_get_undo_no(rec), limit) + >= 0) { + /* Truncate at least this record off, maybe + more */ + trunc_here = rec; + } else { + goto function_exit; + } + + rec = trx_undo_page_get_prev_rec(rec, + undo->hdr_page_no, + undo->hdr_offset); + } + + mtr_commit(&mtr); + } + +function_exit: + if (trunc_here) { + mlog_write_ulint(undo_page + TRX_UNDO_PAGE_HDR + + TRX_UNDO_PAGE_FREE, + trunc_here - undo_page, MLOG_2BYTES, &mtr); + } + + mtr_commit(&mtr); +} + +/*************************************************************************** +Truncates an undo log from the start. This function is used during a purge +operation. */ +UNIV_INTERN +void +trx_undo_truncate_start( +/*====================*/ + trx_rseg_t* rseg, /* in: rollback segment */ + ulint space, /* in: space id of the log */ + ulint hdr_page_no, /* in: header page number */ + ulint hdr_offset, /* in: header offset on the page */ + dulint limit) /* in: all undo pages with undo numbers < + this value should be truncated; NOTE that + the function only frees whole pages; the + header page is not freed, but emptied, if + all the records there are < limit */ +{ + page_t* undo_page; + trx_undo_rec_t* rec; + trx_undo_rec_t* last_rec; + ulint page_no; + mtr_t mtr; + + ut_ad(mutex_own(&(rseg->mutex))); + + if (ut_dulint_is_zero(limit)) { + + return; + } +loop: + mtr_start(&mtr); + + rec = trx_undo_get_first_rec(space, rseg->zip_size, + hdr_page_no, hdr_offset, + RW_X_LATCH, &mtr); + if (rec == NULL) { + /* Already empty */ + + mtr_commit(&mtr); + + return; + } + + undo_page = page_align(rec); + + last_rec = trx_undo_page_get_last_rec(undo_page, hdr_page_no, + hdr_offset); + if (ut_dulint_cmp(trx_undo_rec_get_undo_no(last_rec), limit) >= 0) { + + mtr_commit(&mtr); + + return; + } + + page_no = page_get_page_no(undo_page); + + if (page_no == hdr_page_no) { + trx_undo_empty_header_page(space, rseg->zip_size, + hdr_page_no, hdr_offset, + &mtr); + } else { + trx_undo_free_page(rseg, TRUE, space, hdr_page_no, + page_no, &mtr); + } + + mtr_commit(&mtr); + + goto loop; +} + +/************************************************************************** +Frees an undo log segment which is not in the history list. */ +static +void +trx_undo_seg_free( +/*==============*/ + trx_undo_t* undo) /* in: undo log */ +{ + trx_rseg_t* rseg; + fseg_header_t* file_seg; + trx_rsegf_t* rseg_header; + trx_usegf_t* seg_header; + ibool finished; + mtr_t mtr; + + rseg = undo->rseg; + + do { + + mtr_start(&mtr); + + ut_ad(!mutex_own(&kernel_mutex)); + + mutex_enter(&(rseg->mutex)); + + seg_header = trx_undo_page_get(undo->space, undo->zip_size, + undo->hdr_page_no, + &mtr) + TRX_UNDO_SEG_HDR; + + file_seg = seg_header + TRX_UNDO_FSEG_HEADER; + + finished = fseg_free_step(file_seg, &mtr); + + if (finished) { + /* Update the rseg header */ + rseg_header = trx_rsegf_get( + rseg->space, rseg->zip_size, rseg->page_no, + &mtr); + trx_rsegf_set_nth_undo(rseg_header, undo->id, FIL_NULL, + &mtr); + } + + mutex_exit(&(rseg->mutex)); + mtr_commit(&mtr); + } while (!finished); +} + +/*========== UNDO LOG MEMORY COPY INITIALIZATION =====================*/ + +/************************************************************************ +Creates and initializes an undo log memory object according to the values +in the header in file, when the database is started. The memory object is +inserted in the appropriate list of rseg. */ +static +trx_undo_t* +trx_undo_mem_create_at_db_start( +/*============================*/ + /* out, own: the undo log memory object */ + trx_rseg_t* rseg, /* in: rollback segment memory object */ + ulint id, /* in: slot index within rseg */ + ulint page_no,/* in: undo log segment page number */ + mtr_t* mtr) /* in: mtr */ +{ + page_t* undo_page; + trx_upagef_t* page_header; + trx_usegf_t* seg_header; + trx_ulogf_t* undo_header; + trx_undo_t* undo; + ulint type; + ulint state; + dulint trx_id; + ulint offset; + fil_addr_t last_addr; + page_t* last_page; + trx_undo_rec_t* rec; + XID xid; + ibool xid_exists = FALSE; + + if (id >= TRX_RSEG_N_SLOTS) { + fprintf(stderr, + "InnoDB: Error: undo->id is %lu\n", (ulong) id); + ut_error; + } + + undo_page = trx_undo_page_get(rseg->space, rseg->zip_size, + page_no, mtr); + + page_header = undo_page + TRX_UNDO_PAGE_HDR; + + type = mtr_read_ulint(page_header + TRX_UNDO_PAGE_TYPE, MLOG_2BYTES, + mtr); + seg_header = undo_page + TRX_UNDO_SEG_HDR; + + state = mach_read_from_2(seg_header + TRX_UNDO_STATE); + + offset = mach_read_from_2(seg_header + TRX_UNDO_LAST_LOG); + + undo_header = undo_page + offset; + + trx_id = mtr_read_dulint(undo_header + TRX_UNDO_TRX_ID, mtr); + + xid_exists = mtr_read_ulint(undo_header + TRX_UNDO_XID_EXISTS, + MLOG_1BYTE, mtr); + + /* Read X/Open XA transaction identification if it exists, or + set it to NULL. */ + + memset(&xid, 0, sizeof(xid)); + xid.formatID = -1; + + if (xid_exists == TRUE) { + trx_undo_read_xid(undo_header, &xid); + } + + mutex_enter(&(rseg->mutex)); + + undo = trx_undo_mem_create(rseg, id, type, trx_id, &xid, + page_no, offset); + mutex_exit(&(rseg->mutex)); + + undo->dict_operation = mtr_read_ulint( + undo_header + TRX_UNDO_DICT_TRANS, MLOG_1BYTE, mtr); + + undo->table_id = mtr_read_dulint(undo_header + TRX_UNDO_TABLE_ID, mtr); + undo->state = state; + undo->size = flst_get_len(seg_header + TRX_UNDO_PAGE_LIST, mtr); + + /* If the log segment is being freed, the page list is inconsistent! */ + if (state == TRX_UNDO_TO_FREE) { + + goto add_to_list; + } + + last_addr = flst_get_last(seg_header + TRX_UNDO_PAGE_LIST, mtr); + + undo->last_page_no = last_addr.page; + undo->top_page_no = last_addr.page; + + last_page = trx_undo_page_get(rseg->space, rseg->zip_size, + undo->last_page_no, mtr); + + rec = trx_undo_page_get_last_rec(last_page, page_no, offset); + + if (rec == NULL) { + undo->empty = TRUE; + } else { + undo->empty = FALSE; + undo->top_offset = rec - last_page; + undo->top_undo_no = trx_undo_rec_get_undo_no(rec); + } +add_to_list: + if (type == TRX_UNDO_INSERT) { + if (state != TRX_UNDO_CACHED) { + UT_LIST_ADD_LAST(undo_list, rseg->insert_undo_list, + undo); + } else { + UT_LIST_ADD_LAST(undo_list, rseg->insert_undo_cached, + undo); + } + } else { + ut_ad(type == TRX_UNDO_UPDATE); + if (state != TRX_UNDO_CACHED) { + UT_LIST_ADD_LAST(undo_list, rseg->update_undo_list, + undo); + } else { + UT_LIST_ADD_LAST(undo_list, rseg->update_undo_cached, + undo); + } + } + + return(undo); +} + +/************************************************************************ +Initializes the undo log lists for a rollback segment memory copy. This +function is only called when the database is started or a new rollback +segment is created. */ +UNIV_INTERN +ulint +trx_undo_lists_init( +/*================*/ + /* out: the combined size of undo log segments + in pages */ + trx_rseg_t* rseg) /* in: rollback segment memory object */ +{ + ulint page_no; + trx_undo_t* undo; + ulint size = 0; + trx_rsegf_t* rseg_header; + ulint i; + mtr_t mtr; + + UT_LIST_INIT(rseg->update_undo_list); + UT_LIST_INIT(rseg->update_undo_cached); + UT_LIST_INIT(rseg->insert_undo_list); + UT_LIST_INIT(rseg->insert_undo_cached); + + mtr_start(&mtr); + + rseg_header = trx_rsegf_get_new(rseg->space, rseg->zip_size, + rseg->page_no, &mtr); + + if (!srv_extra_undoslots) { + /* uses direct call for avoid "Assertion failure" */ + //page_no = trx_rsegf_get_nth_undo(rseg_header, TRX_RSEG_N_EXTRA_SLOTS - 1, &mtr); + page_no = mtr_read_ulint(rseg_header + TRX_RSEG_UNDO_SLOTS + + (TRX_RSEG_N_EXTRA_SLOTS - 1) * TRX_RSEG_SLOT_SIZE, + MLOG_4BYTES, &mtr); + if (page_no != 0) { + /* check extended slots are not used */ + for (i = TRX_RSEG_N_SLOTS; i < TRX_RSEG_N_EXTRA_SLOTS; i++) { + /* uses direct call for avoid "Assertion failure" */ + page_no = mtr_read_ulint(rseg_header + TRX_RSEG_UNDO_SLOTS + + i * TRX_RSEG_SLOT_SIZE, + MLOG_4BYTES, &mtr); + if (page_no != FIL_NULL) { + srv_extra_undoslots = TRUE; + fprintf(stderr, +"InnoDB: Error: innodb_extra_undoslots option is disabled, but it was enabled before.\n" +"InnoDB: The datafile is not normal for mysqld and disabled innodb_extra_undoslots.\n" +"InnoDB: Enable innodb_extra_undoslots if it was enabled before, and\n" +"InnoDB: ### don't use this datafile with other mysqld or ibbackup! ###\n" +"InnoDB: Cannot continue operation for the safety. Calling exit(1).\n"); + exit(1); + } + } + fprintf(stderr, +"InnoDB: Warning: innodb_extra_undoslots option is disabled, but it was enabled before.\n" +"InnoDB: But extended undo slots seem not used, so continue operation.\n"); + } + } + + for (i = 0; i < TRX_RSEG_N_SLOTS; i++) { + page_no = trx_rsegf_get_nth_undo(rseg_header, i, &mtr); + + /* If it was not initialized when the datafile created, + page_no will be 0 for the extended slots after that */ + + if (page_no == 0) { + page_no = FIL_NULL; + trx_rsegf_set_nth_undo(rseg_header, i, page_no, &mtr); + } + + /* In forced recovery: try to avoid operations which look + at database pages; undo logs are rapidly changing data, and + the probability that they are in an inconsistent state is + high */ + + if (page_no != FIL_NULL + && srv_force_recovery < SRV_FORCE_NO_UNDO_LOG_SCAN) { + + undo = trx_undo_mem_create_at_db_start(rseg, i, + page_no, &mtr); + size += undo->size; + + mtr_commit(&mtr); + + mtr_start(&mtr); + + rseg_header = trx_rsegf_get( + rseg->space, rseg->zip_size, rseg->page_no, + &mtr); + } + } + + mtr_commit(&mtr); + + return(size); +} + +/************************************************************************ +Creates and initializes an undo log memory object. */ +static +trx_undo_t* +trx_undo_mem_create( +/*================*/ + /* out, own: the undo log memory object */ + trx_rseg_t* rseg, /* in: rollback segment memory object */ + ulint id, /* in: slot index within rseg */ + ulint type, /* in: type of the log: TRX_UNDO_INSERT or + TRX_UNDO_UPDATE */ + dulint trx_id, /* in: id of the trx for which the undo log + is created */ + const XID* xid, /* in: X/Open transaction identification */ + ulint page_no,/* in: undo log header page number */ + ulint offset) /* in: undo log header byte offset on page */ +{ + trx_undo_t* undo; + + ut_ad(mutex_own(&(rseg->mutex))); + + if (id >= TRX_RSEG_N_SLOTS) { + fprintf(stderr, + "InnoDB: Error: undo->id is %lu\n", (ulong) id); + ut_error; + } + + undo = mem_alloc(sizeof(trx_undo_t)); + + if (undo == NULL) { + + return NULL; + } + + undo->id = id; + undo->type = type; + undo->state = TRX_UNDO_ACTIVE; + undo->del_marks = FALSE; + undo->trx_id = trx_id; + undo->xid = *xid; + + undo->dict_operation = FALSE; + + undo->rseg = rseg; + + undo->space = rseg->space; + undo->zip_size = rseg->zip_size; + undo->hdr_page_no = page_no; + undo->hdr_offset = offset; + undo->last_page_no = page_no; + undo->size = 1; + + undo->empty = TRUE; + undo->top_page_no = page_no; + undo->guess_block = NULL; + + return(undo); +} + +/************************************************************************ +Initializes a cached undo log object for new use. */ +static +void +trx_undo_mem_init_for_reuse( +/*========================*/ + trx_undo_t* undo, /* in: undo log to init */ + dulint trx_id, /* in: id of the trx for which the undo log + is created */ + const XID* xid, /* in: X/Open XA transaction identification*/ + ulint offset) /* in: undo log header byte offset on page */ +{ + ut_ad(mutex_own(&((undo->rseg)->mutex))); + + if (UNIV_UNLIKELY(undo->id >= TRX_RSEG_N_SLOTS)) { + fprintf(stderr, "InnoDB: Error: undo->id is %lu\n", + (ulong) undo->id); + + mem_analyze_corruption(undo); + ut_error; + } + + undo->state = TRX_UNDO_ACTIVE; + undo->del_marks = FALSE; + undo->trx_id = trx_id; + undo->xid = *xid; + + undo->dict_operation = FALSE; + + undo->hdr_offset = offset; + undo->empty = TRUE; +} + +/************************************************************************ +Frees an undo log memory copy. */ +static +void +trx_undo_mem_free( +/*==============*/ + trx_undo_t* undo) /* in: the undo object to be freed */ +{ + if (undo->id >= TRX_RSEG_N_SLOTS) { + fprintf(stderr, + "InnoDB: Error: undo->id is %lu\n", (ulong) undo->id); + ut_error; + } + + mem_free(undo); +} + +/************************************************************************** +Creates a new undo log. */ +static +ulint +trx_undo_create( +/*============*/ + /* out: DB_SUCCESS if successful in creating + the new undo lob object, possible error + codes are: + DB_TOO_MANY_CONCURRENT_TRXS + DB_OUT_OF_FILE_SPACE + DB_OUT_OF_MEMORY*/ + trx_t* trx, /* in: transaction */ + trx_rseg_t* rseg, /* in: rollback segment memory copy */ + ulint type, /* in: type of the log: TRX_UNDO_INSERT or + TRX_UNDO_UPDATE */ + dulint trx_id, /* in: id of the trx for which the undo log + is created */ + const XID* xid, /* in: X/Open transaction identification*/ + trx_undo_t** undo, /* out: the new undo log object, undefined + * if did not succeed */ + mtr_t* mtr) /* in: mtr */ +{ + trx_rsegf_t* rseg_header; + ulint page_no; + ulint offset; + ulint id; + page_t* undo_page; + ulint err; + + ut_ad(mutex_own(&(rseg->mutex))); + + if (rseg->curr_size == rseg->max_size) { + + return(DB_OUT_OF_FILE_SPACE); + } + + rseg->curr_size++; + + rseg_header = trx_rsegf_get(rseg->space, rseg->zip_size, rseg->page_no, + mtr); + + err = trx_undo_seg_create(rseg, rseg_header, type, &id, + &undo_page, mtr); + + if (err != DB_SUCCESS) { + /* Did not succeed */ + + rseg->curr_size--; + + return(err); + } + + page_no = page_get_page_no(undo_page); + + offset = trx_undo_header_create(undo_page, trx_id, mtr); + + if (trx->support_xa) { + trx_undo_header_add_space_for_xid(undo_page, + undo_page + offset, mtr); + } + + *undo = trx_undo_mem_create(rseg, id, type, trx_id, xid, + page_no, offset); + if (*undo == NULL) { + + err = DB_OUT_OF_MEMORY; + } + + return(err); +} + +/*================ UNDO LOG ASSIGNMENT AND CLEANUP =====================*/ + +/************************************************************************ +Reuses a cached undo log. */ +static +trx_undo_t* +trx_undo_reuse_cached( +/*==================*/ + /* out: the undo log memory object, NULL if + none cached */ + trx_t* trx, /* in: transaction */ + trx_rseg_t* rseg, /* in: rollback segment memory object */ + ulint type, /* in: type of the log: TRX_UNDO_INSERT or + TRX_UNDO_UPDATE */ + dulint trx_id, /* in: id of the trx for which the undo log + is used */ + const XID* xid, /* in: X/Open XA transaction identification */ + mtr_t* mtr) /* in: mtr */ +{ + trx_undo_t* undo; + page_t* undo_page; + ulint offset; + + ut_ad(mutex_own(&(rseg->mutex))); + + if (type == TRX_UNDO_INSERT) { + + undo = UT_LIST_GET_FIRST(rseg->insert_undo_cached); + if (undo == NULL) { + + return(NULL); + } + + UT_LIST_REMOVE(undo_list, rseg->insert_undo_cached, undo); + } else { + ut_ad(type == TRX_UNDO_UPDATE); + + undo = UT_LIST_GET_FIRST(rseg->update_undo_cached); + if (undo == NULL) { + + return(NULL); + } + + UT_LIST_REMOVE(undo_list, rseg->update_undo_cached, undo); + } + + ut_ad(undo->size == 1); + + if (undo->id >= TRX_RSEG_N_SLOTS) { + fprintf(stderr, "InnoDB: Error: undo->id is %lu\n", + (ulong) undo->id); + mem_analyze_corruption(undo); + ut_error; + } + + undo_page = trx_undo_page_get(undo->space, undo->zip_size, + undo->hdr_page_no, mtr); + + if (type == TRX_UNDO_INSERT) { + offset = trx_undo_insert_header_reuse(undo_page, trx_id, mtr); + + if (trx->support_xa) { + trx_undo_header_add_space_for_xid( + undo_page, undo_page + offset, mtr); + } + } else { + ut_a(mach_read_from_2(undo_page + TRX_UNDO_PAGE_HDR + + TRX_UNDO_PAGE_TYPE) + == TRX_UNDO_UPDATE); + + offset = trx_undo_header_create(undo_page, trx_id, mtr); + + if (trx->support_xa) { + trx_undo_header_add_space_for_xid( + undo_page, undo_page + offset, mtr); + } + } + + trx_undo_mem_init_for_reuse(undo, trx_id, xid, offset); + + return(undo); +} + +/************************************************************************** +Marks an undo log header as a header of a data dictionary operation +transaction. */ +static +void +trx_undo_mark_as_dict_operation( +/*============================*/ + trx_t* trx, /* in: dict op transaction */ + trx_undo_t* undo, /* in: assigned undo log */ + mtr_t* mtr) /* in: mtr */ +{ + page_t* hdr_page; + + hdr_page = trx_undo_page_get(undo->space, undo->zip_size, + undo->hdr_page_no, mtr); + + switch (trx_get_dict_operation(trx)) { + case TRX_DICT_OP_NONE: + ut_error; + case TRX_DICT_OP_INDEX: + /* Do not discard the table on recovery. */ + undo->table_id = ut_dulint_zero; + break; + case TRX_DICT_OP_TABLE: + undo->table_id = trx->table_id; + break; + } + + mlog_write_ulint(hdr_page + undo->hdr_offset + + TRX_UNDO_DICT_TRANS, + TRUE, MLOG_1BYTE, mtr); + + mlog_write_dulint(hdr_page + undo->hdr_offset + TRX_UNDO_TABLE_ID, + undo->table_id, mtr); + + undo->dict_operation = TRUE; +} + +/************************************************************************** +Assigns an undo log for a transaction. A new undo log is created or a cached +undo log reused. */ +UNIV_INTERN +ulint +trx_undo_assign_undo( +/*=================*/ + /* out: DB_SUCCESS if undo log assign + successful, possible error codes are: + DB_TOO_MANY_CONCURRENT_TRXS + DB_OUT_OF_FILE_SPACE DB_OUT_OF_MEMORY*/ + trx_t* trx, /* in: transaction */ + ulint type) /* in: TRX_UNDO_INSERT or TRX_UNDO_UPDATE */ +{ + trx_rseg_t* rseg; + trx_undo_t* undo; + mtr_t mtr; + ulint err = DB_SUCCESS; + + ut_ad(trx); + ut_ad(trx->rseg); + + rseg = trx->rseg; + + ut_ad(mutex_own(&(trx->undo_mutex))); + + mtr_start(&mtr); + + ut_ad(!mutex_own(&kernel_mutex)); + + mutex_enter(&(rseg->mutex)); + + undo = trx_undo_reuse_cached(trx, rseg, type, trx->id, &trx->xid, + &mtr); + if (undo == NULL) { + err = trx_undo_create(trx, rseg, type, trx->id, &trx->xid, + &undo, &mtr); + if (err != DB_SUCCESS) { + + goto func_exit; + } + } + + if (type == TRX_UNDO_INSERT) { + UT_LIST_ADD_FIRST(undo_list, rseg->insert_undo_list, undo); + ut_ad(trx->insert_undo == NULL); + trx->insert_undo = undo; + } else { + UT_LIST_ADD_FIRST(undo_list, rseg->update_undo_list, undo); + ut_ad(trx->update_undo == NULL); + trx->update_undo = undo; + } + + if (trx_get_dict_operation(trx) != TRX_DICT_OP_NONE) { + trx_undo_mark_as_dict_operation(trx, undo, &mtr); + } + +func_exit: + mutex_exit(&(rseg->mutex)); + mtr_commit(&mtr); + + return err; +} + +/********************************************************************** +Sets the state of the undo log segment at a transaction finish. */ +UNIV_INTERN +page_t* +trx_undo_set_state_at_finish( +/*=========================*/ + /* out: undo log segment header page, + x-latched */ + trx_rseg_t* rseg, /* in: rollback segment memory object */ + trx_t* trx __attribute__((unused)), /* in: transaction */ + trx_undo_t* undo, /* in: undo log memory copy */ + mtr_t* mtr) /* in: mtr */ +{ + trx_usegf_t* seg_hdr; + trx_upagef_t* page_hdr; + page_t* undo_page; + ulint state; + + ut_ad(trx); + ut_ad(undo); + ut_ad(mtr); + ut_ad(mutex_own(&rseg->mutex)); + + if (undo->id >= TRX_RSEG_N_SLOTS) { + fprintf(stderr, "InnoDB: Error: undo->id is %lu\n", + (ulong) undo->id); + mem_analyze_corruption(undo); + ut_error; + } + + undo_page = trx_undo_page_get(undo->space, undo->zip_size, + undo->hdr_page_no, mtr); + + seg_hdr = undo_page + TRX_UNDO_SEG_HDR; + page_hdr = undo_page + TRX_UNDO_PAGE_HDR; + + if (undo->size == 1 + && mach_read_from_2(page_hdr + TRX_UNDO_PAGE_FREE) + < TRX_UNDO_PAGE_REUSE_LIMIT) { + + /* This is a heuristic to avoid the problem of all UNDO + slots ending up in one of the UNDO lists. Previously if + the server crashed with all the slots in one of the lists, + transactions that required the slots of a different type + would fail for lack of slots. */ + + if (UT_LIST_GET_LEN(rseg->update_undo_list) < 500 + && UT_LIST_GET_LEN(rseg->insert_undo_list) < 500) { + + state = TRX_UNDO_CACHED; + } else { + state = TRX_UNDO_TO_FREE; + } + + } else if (undo->type == TRX_UNDO_INSERT) { + + state = TRX_UNDO_TO_FREE; + } else { + state = TRX_UNDO_TO_PURGE; + } + + undo->state = state; + + mlog_write_ulint(seg_hdr + TRX_UNDO_STATE, state, MLOG_2BYTES, mtr); + + return(undo_page); +} + +/********************************************************************** +Sets the state of the undo log segment at a transaction prepare. */ +UNIV_INTERN +page_t* +trx_undo_set_state_at_prepare( +/*==========================*/ + /* out: undo log segment header page, + x-latched */ + trx_t* trx, /* in: transaction */ + trx_undo_t* undo, /* in: undo log memory copy */ + mtr_t* mtr) /* in: mtr */ +{ + trx_usegf_t* seg_hdr; + trx_upagef_t* page_hdr; + trx_ulogf_t* undo_header; + page_t* undo_page; + ulint offset; + + ut_ad(trx && undo && mtr); + + if (undo->id >= TRX_RSEG_N_SLOTS) { + fprintf(stderr, "InnoDB: Error: undo->id is %lu\n", + (ulong) undo->id); + mem_analyze_corruption(undo); + ut_error; + } + + undo_page = trx_undo_page_get(undo->space, undo->zip_size, + undo->hdr_page_no, mtr); + + seg_hdr = undo_page + TRX_UNDO_SEG_HDR; + page_hdr = undo_page + TRX_UNDO_PAGE_HDR; + + /*------------------------------*/ + undo->state = TRX_UNDO_PREPARED; + undo->xid = trx->xid; + /*------------------------------*/ + + mlog_write_ulint(seg_hdr + TRX_UNDO_STATE, undo->state, + MLOG_2BYTES, mtr); + + offset = mach_read_from_2(seg_hdr + TRX_UNDO_LAST_LOG); + undo_header = undo_page + offset; + + mlog_write_ulint(undo_header + TRX_UNDO_XID_EXISTS, + TRUE, MLOG_1BYTE, mtr); + + trx_undo_write_xid(undo_header, &undo->xid, mtr); + + return(undo_page); +} + +/************************************************************************** +Adds the update undo log header as the first in the history list, and +frees the memory object, or puts it to the list of cached update undo log +segments. */ +UNIV_INTERN +void +trx_undo_update_cleanup( +/*====================*/ + trx_t* trx, /* in: trx owning the update undo log */ + page_t* undo_page, /* in: update undo log header page, + x-latched */ + mtr_t* mtr) /* in: mtr */ +{ + trx_rseg_t* rseg; + trx_undo_t* undo; + + undo = trx->update_undo; + rseg = trx->rseg; + + ut_ad(mutex_own(&(rseg->mutex))); + + trx_purge_add_update_undo_to_history(trx, undo_page, mtr); + + UT_LIST_REMOVE(undo_list, rseg->update_undo_list, undo); + + trx->update_undo = NULL; + + if (undo->state == TRX_UNDO_CACHED) { + + UT_LIST_ADD_FIRST(undo_list, rseg->update_undo_cached, undo); + } else { + ut_ad(undo->state == TRX_UNDO_TO_PURGE); + + trx_undo_mem_free(undo); + } +} + +/********************************************************************** +Frees or caches an insert undo log after a transaction commit or rollback. +Knowledge of inserts is not needed after a commit or rollback, therefore +the data can be discarded. */ +UNIV_INTERN +void +trx_undo_insert_cleanup( +/*====================*/ + trx_t* trx) /* in: transaction handle */ +{ + trx_undo_t* undo; + trx_rseg_t* rseg; + + undo = trx->insert_undo; + ut_ad(undo); + + rseg = trx->rseg; + + mutex_enter(&(rseg->mutex)); + + UT_LIST_REMOVE(undo_list, rseg->insert_undo_list, undo); + trx->insert_undo = NULL; + + if (undo->state == TRX_UNDO_CACHED) { + + UT_LIST_ADD_FIRST(undo_list, rseg->insert_undo_cached, undo); + } else { + ut_ad(undo->state == TRX_UNDO_TO_FREE); + + /* Delete first the undo log segment in the file */ + + mutex_exit(&(rseg->mutex)); + + trx_undo_seg_free(undo); + + mutex_enter(&(rseg->mutex)); + + ut_ad(rseg->curr_size > undo->size); + + rseg->curr_size -= undo->size; + + trx_undo_mem_free(undo); + } + + mutex_exit(&(rseg->mutex)); +} diff --git a/storage/xtradb/usr/usr0sess.c b/storage/xtradb/usr/usr0sess.c new file mode 100644 index 00000000000..f45c43869ea --- /dev/null +++ b/storage/xtradb/usr/usr0sess.c @@ -0,0 +1,97 @@ +/***************************************************************************** + +Copyright (c) 1996, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/****************************************************** +Sessions + +Created 6/25/1996 Heikki Tuuri +*******************************************************/ + +#include "usr0sess.h" + +#ifdef UNIV_NONINL +#include "usr0sess.ic" +#endif + +#include "trx0trx.h" + +/************************************************************************* +Closes a session, freeing the memory occupied by it. */ +static +void +sess_close( +/*=======*/ + sess_t* sess); /* in, own: session object */ + +/************************************************************************* +Opens a session. */ +UNIV_INTERN +sess_t* +sess_open(void) +/*===========*/ + /* out, own: session object */ +{ + sess_t* sess; + + ut_ad(mutex_own(&kernel_mutex)); + + sess = mem_alloc(sizeof(sess_t)); + + sess->state = SESS_ACTIVE; + + sess->trx = trx_create(sess); + + UT_LIST_INIT(sess->graphs); + + return(sess); +} + +/************************************************************************* +Closes a session, freeing the memory occupied by it. */ +static +void +sess_close( +/*=======*/ + sess_t* sess) /* in, own: session object */ +{ + ut_ad(mutex_own(&kernel_mutex)); + ut_ad(sess->trx == NULL); + + mem_free(sess); +} + +/************************************************************************* +Closes a session, freeing the memory occupied by it, if it is in a state +where it should be closed. */ +UNIV_INTERN +ibool +sess_try_close( +/*===========*/ + /* out: TRUE if closed */ + sess_t* sess) /* in, own: session object */ +{ + ut_ad(mutex_own(&kernel_mutex)); + + if (UT_LIST_GET_LEN(sess->graphs) == 0) { + sess_close(sess); + + return(TRUE); + } + + return(FALSE); +} diff --git a/storage/xtradb/ut/ut0auxconf.c b/storage/xtradb/ut/ut0auxconf.c new file mode 100644 index 00000000000..fd9433d16f6 --- /dev/null +++ b/storage/xtradb/ut/ut0auxconf.c @@ -0,0 +1,13 @@ +#include <pthread.h> + +int +main(int argc, char** argv) +{ + pthread_t x1; + pthread_t x2; + pthread_t x3; + + __sync_bool_compare_and_swap(&x1, x2, x3); + + return(0); +} diff --git a/storage/xtradb/ut/ut0byte.c b/storage/xtradb/ut/ut0byte.c new file mode 100644 index 00000000000..5e11e37d0b6 --- /dev/null +++ b/storage/xtradb/ut/ut0byte.c @@ -0,0 +1,50 @@ +/***************************************************************************** + +Copyright (c) 1994, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/******************************************************************* +Byte utilities + +Created 5/11/1994 Heikki Tuuri +********************************************************************/ + +#include "ut0byte.h" + +#ifdef UNIV_NONINL +#include "ut0byte.ic" +#endif + +/* Zero value for a dulint */ +UNIV_INTERN const dulint ut_dulint_zero = {0, 0}; + +/* Maximum value for a dulint */ +UNIV_INTERN const dulint ut_dulint_max = {0xFFFFFFFFUL, 0xFFFFFFFFUL}; + +#ifdef notdefined /* unused code */ +#include "ut0sort.h" + +/**************************************************************** +Sort function for dulint arrays. */ +UNIV_INTERN +void +ut_dulint_sort(dulint* arr, dulint* aux_arr, ulint low, ulint high) +/*===============================================================*/ +{ + UT_SORT_FUNCTION_BODY(ut_dulint_sort, arr, aux_arr, low, high, + ut_dulint_cmp); +} +#endif /* notdefined */ diff --git a/storage/xtradb/ut/ut0dbg.c b/storage/xtradb/ut/ut0dbg.c new file mode 100644 index 00000000000..55dd457a177 --- /dev/null +++ b/storage/xtradb/ut/ut0dbg.c @@ -0,0 +1,180 @@ +/***************************************************************************** + +Copyright (c) 1994, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/********************************************************************* +Debug utilities for Innobase. + +Created 1/30/1994 Heikki Tuuri +**********************************************************************/ + +#include "univ.i" +#include "ut0dbg.h" + +#if defined(__GNUC__) && (__GNUC__ > 2) +#else +/* This is used to eliminate compiler warnings */ +UNIV_INTERN ulint ut_dbg_zero = 0; +#endif + +#if defined(UNIV_SYNC_DEBUG) || !defined(UT_DBG_USE_ABORT) +/* If this is set to TRUE all threads will stop into the next assertion +and assert */ +UNIV_INTERN ibool ut_dbg_stop_threads = FALSE; +#endif +#ifdef __NETWARE__ +/* This is set to TRUE when on NetWare there happens an InnoDB +assertion failure or other fatal error condition that requires an +immediate shutdown. */ +UNIV_INTERN ibool panic_shutdown = FALSE; +#elif !defined(UT_DBG_USE_ABORT) +/* Null pointer used to generate memory trap */ +UNIV_INTERN ulint* ut_dbg_null_ptr = NULL; +#endif + +/***************************************************************** +Report a failed assertion. */ +UNIV_INTERN +void +ut_dbg_assertion_failed( +/*====================*/ + const char* expr, /* in: the failed assertion (optional) */ + const char* file, /* in: source file containing the assertion */ + ulint line) /* in: line number of the assertion */ +{ + ut_print_timestamp(stderr); + fprintf(stderr, + " InnoDB: Assertion failure in thread %lu" + " in file %s line %lu\n", + os_thread_pf(os_thread_get_curr_id()), file, line); + if (expr) { + fprintf(stderr, + "InnoDB: Failing assertion: %s\n", expr); + } + + fputs("InnoDB: We intentionally generate a memory trap.\n" + "InnoDB: Submit a detailed bug report" + " to http://bugs.mysql.com.\n" + "InnoDB: If you get repeated assertion failures" + " or crashes, even\n" + "InnoDB: immediately after the mysqld startup, there may be\n" + "InnoDB: corruption in the InnoDB tablespace. Please refer to\n" + "InnoDB: http://dev.mysql.com/doc/refman/5.1/en/" + "forcing-recovery.html\n" + "InnoDB: about forcing recovery.\n", stderr); +#if defined(UNIV_SYNC_DEBUG) || !defined(UT_DBG_USE_ABORT) + ut_dbg_stop_threads = TRUE; +#endif +} + +#ifdef __NETWARE__ +/***************************************************************** +Shut down MySQL/InnoDB after assertion failure. */ +UNIV_INTERN +void +ut_dbg_panic(void) +/*==============*/ +{ + if (!panic_shutdown) { + panic_shutdown = TRUE; + innobase_shutdown_for_mysql(); + } + exit(1); +} +#else /* __NETWARE__ */ +# if defined(UNIV_SYNC_DEBUG) || !defined(UT_DBG_USE_ABORT) +/***************************************************************** +Stop a thread after assertion failure. */ +UNIV_INTERN +void +ut_dbg_stop_thread( +/*===============*/ + const char* file, + ulint line) +{ + fprintf(stderr, "InnoDB: Thread %lu stopped in file %s line %lu\n", + os_thread_pf(os_thread_get_curr_id()), file, line); + os_thread_sleep(1000000000); +} +# endif +#endif /* __NETWARE__ */ + +#ifdef UNIV_COMPILE_TEST_FUNCS + +#include <sys/types.h> +#include <sys/time.h> +#include <sys/resource.h> + +#include <unistd.h> + +#ifndef timersub +#define timersub(a, b, r) \ + do { \ + (r)->tv_sec = (a)->tv_sec - (b)->tv_sec; \ + (r)->tv_usec = (a)->tv_usec - (b)->tv_usec; \ + if ((r)->tv_usec < 0) { \ + (r)->tv_sec--; \ + (r)->tv_usec += 1000000; \ + } \ + } while (0) +#endif /* timersub */ + +/*********************************************************************** +Resets a speedo (records the current time in it). */ +UNIV_INTERN +void +speedo_reset( +/*=========*/ + speedo_t* speedo) /* out: speedo */ +{ + gettimeofday(&speedo->tv, NULL); + + getrusage(RUSAGE_SELF, &speedo->ru); +} + +/*********************************************************************** +Shows the time elapsed and usage statistics since the last reset of a +speedo. */ +UNIV_INTERN +void +speedo_show( +/*========*/ + const speedo_t* speedo) /* in: speedo */ +{ + struct rusage ru_now; + struct timeval tv_now; + struct timeval tv_diff; + + getrusage(RUSAGE_SELF, &ru_now); + + gettimeofday(&tv_now, NULL); + +#define PRINT_TIMEVAL(prefix, tvp) \ + fprintf(stderr, "%s% 5ld.%06ld sec\n", \ + prefix, (tvp)->tv_sec, (tvp)->tv_usec) + + timersub(&tv_now, &speedo->tv, &tv_diff); + PRINT_TIMEVAL("real", &tv_diff); + + timersub(&ru_now.ru_utime, &speedo->ru.ru_utime, &tv_diff); + PRINT_TIMEVAL("user", &tv_diff); + + timersub(&ru_now.ru_stime, &speedo->ru.ru_stime, &tv_diff); + PRINT_TIMEVAL("sys ", &tv_diff); +} + +#endif /* UNIV_COMPILE_TEST_FUNCS */ diff --git a/storage/xtradb/ut/ut0list.c b/storage/xtradb/ut/ut0list.c new file mode 100644 index 00000000000..c6250edb6cd --- /dev/null +++ b/storage/xtradb/ut/ut0list.c @@ -0,0 +1,187 @@ +/***************************************************************************** + +Copyright (c) 2006, 2009, Innobase Oy. 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 "ut0list.h" +#ifdef UNIV_NONINL +#include "ut0list.ic" +#endif + +/******************************************************************** +Create a new list. */ +UNIV_INTERN +ib_list_t* +ib_list_create(void) +/*=================*/ + /* out: list */ +{ + ib_list_t* list = mem_alloc(sizeof(ib_list_t)); + + list->first = NULL; + list->last = NULL; + list->is_heap_list = FALSE; + + return(list); +} + +/******************************************************************** +Create a new list using the given heap. ib_list_free MUST NOT BE CALLED for +lists created with this function. */ +UNIV_INTERN +ib_list_t* +ib_list_create_heap( +/*================*/ + /* out: list */ + mem_heap_t* heap) /* in: memory heap to use */ +{ + ib_list_t* list = mem_heap_alloc(heap, sizeof(ib_list_t)); + + list->first = NULL; + list->last = NULL; + list->is_heap_list = TRUE; + + return(list); +} + +/******************************************************************** +Free a list. */ +UNIV_INTERN +void +ib_list_free( +/*=========*/ + ib_list_t* list) /* in: list */ +{ + ut_a(!list->is_heap_list); + + /* We don't check that the list is empty because it's entirely valid + to e.g. have all the nodes allocated from a single heap that is then + freed after the list itself is freed. */ + + mem_free(list); +} + +/******************************************************************** +Add the data to the start of the list. */ +UNIV_INTERN +ib_list_node_t* +ib_list_add_first( +/*==============*/ + /* out: new list node*/ + ib_list_t* list, /* in: list */ + void* data, /* in: data */ + mem_heap_t* heap) /* in: memory heap to use */ +{ + return(ib_list_add_after(list, ib_list_get_first(list), data, heap)); +} + +/******************************************************************** +Add the data to the end of the list. */ +UNIV_INTERN +ib_list_node_t* +ib_list_add_last( +/*=============*/ + /* out: new list node*/ + ib_list_t* list, /* in: list */ + void* data, /* in: data */ + mem_heap_t* heap) /* in: memory heap to use */ +{ + return(ib_list_add_after(list, ib_list_get_last(list), data, heap)); +} + +/******************************************************************** +Add the data after the indicated node. */ +UNIV_INTERN +ib_list_node_t* +ib_list_add_after( +/*==============*/ + /* out: new list node*/ + ib_list_t* list, /* in: list */ + ib_list_node_t* prev_node, /* in: node preceding new node (can + be NULL) */ + void* data, /* in: data */ + mem_heap_t* heap) /* in: memory heap to use */ +{ + ib_list_node_t* node = mem_heap_alloc(heap, sizeof(ib_list_node_t)); + + node->data = data; + + if (!list->first) { + /* Empty list. */ + + ut_a(!prev_node); + + node->prev = NULL; + node->next = NULL; + + list->first = node; + list->last = node; + } else if (!prev_node) { + /* Start of list. */ + + node->prev = NULL; + node->next = list->first; + + list->first->prev = node; + + list->first = node; + } else { + /* Middle or end of list. */ + + node->prev = prev_node; + node->next = prev_node->next; + + prev_node->next = node; + + if (node->next) { + node->next->prev = node; + } else { + list->last = node; + } + } + + return(node); +} + +/******************************************************************** +Remove the node from the list. */ +UNIV_INTERN +void +ib_list_remove( +/*===========*/ + ib_list_t* list, /* in: list */ + ib_list_node_t* node) /* in: node to remove */ +{ + if (node->prev) { + node->prev->next = node->next; + } else { + /* First item in list. */ + + ut_ad(list->first == node); + + list->first = node->next; + } + + if (node->next) { + node->next->prev = node->prev; + } else { + /* Last item in list. */ + + ut_ad(list->last == node); + + list->last = node->prev; + } +} diff --git a/storage/xtradb/ut/ut0mem.c b/storage/xtradb/ut/ut0mem.c new file mode 100644 index 00000000000..c87a6a4b57e --- /dev/null +++ b/storage/xtradb/ut/ut0mem.c @@ -0,0 +1,673 @@ +/***************************************************************************** + +Copyright (c) 1994, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/************************************************************************ +Memory primitives + +Created 5/11/1994 Heikki Tuuri +*************************************************************************/ + +#include "ut0mem.h" + +#ifdef UNIV_NONINL +#include "ut0mem.ic" +#endif + +#include "mem0mem.h" +#include "os0thread.h" +#include "srv0srv.h" + +#include <stdlib.h> + +/* This struct is placed first in every allocated memory block */ +typedef struct ut_mem_block_struct ut_mem_block_t; + +/* The total amount of memory currently allocated from the operating +system with os_mem_alloc_large() or malloc(). Does not count malloc() +if srv_use_sys_malloc is set. Protected by ut_list_mutex. */ +UNIV_INTERN ulint ut_total_allocated_memory = 0; + +/* Mutex protecting ut_total_allocated_memory and ut_mem_block_list */ +UNIV_INTERN os_fast_mutex_t ut_list_mutex; + +struct ut_mem_block_struct{ + UT_LIST_NODE_T(ut_mem_block_t) mem_block_list; + /* mem block list node */ + ulint size; /* size of allocated memory */ + ulint magic_n; +}; + +#define UT_MEM_MAGIC_N 1601650166 + +/* List of all memory blocks allocated from the operating system +with malloc. Protected by ut_list_mutex. */ +static UT_LIST_BASE_NODE_T(ut_mem_block_t) ut_mem_block_list; + +static ibool ut_mem_block_list_inited = FALSE; + +static ulint* ut_mem_null_ptr = NULL; + +/************************************************************************** +Initializes the mem block list at database startup. */ +UNIV_INTERN +void +ut_mem_init(void) +/*=============*/ +{ + ut_a(!ut_mem_block_list_inited); + os_fast_mutex_init(&ut_list_mutex); + UT_LIST_INIT(ut_mem_block_list); + ut_mem_block_list_inited = TRUE; +} + +/************************************************************************** +Allocates memory. Sets it also to zero if UNIV_SET_MEM_TO_ZERO is +defined and set_to_zero is TRUE. */ +UNIV_INTERN +void* +ut_malloc_low( +/*==========*/ + /* out, own: allocated memory */ + ulint n, /* in: number of bytes to allocate */ + ibool set_to_zero, /* in: TRUE if allocated memory should be + set to zero if UNIV_SET_MEM_TO_ZERO is + defined */ + ibool assert_on_error)/* in: if TRUE, we crash mysqld if the + memory cannot be allocated */ +{ + ulint retry_count; + void* ret; + + if (UNIV_LIKELY(srv_use_sys_malloc)) { + ret = malloc(n); + ut_a(ret || !assert_on_error); + +#ifdef UNIV_SET_MEM_TO_ZERO + if (set_to_zero) { + memset(ret, '\0', n); + UNIV_MEM_ALLOC(ret, n); + } +#endif + return(ret); + } + + ut_ad((sizeof(ut_mem_block_t) % 8) == 0); /* check alignment ok */ + ut_a(ut_mem_block_list_inited); + + retry_count = 0; +retry: + os_fast_mutex_lock(&ut_list_mutex); + + ret = malloc(n + sizeof(ut_mem_block_t)); + + if (ret == NULL && retry_count < 60) { + if (retry_count == 0) { + ut_print_timestamp(stderr); + + fprintf(stderr, + " InnoDB: Error: cannot allocate" + " %lu bytes of\n" + "InnoDB: memory with malloc!" + " Total allocated memory\n" + "InnoDB: by InnoDB %lu bytes." + " Operating system errno: %lu\n" + "InnoDB: Check if you should" + " increase the swap file or\n" + "InnoDB: ulimits of your operating system.\n" + "InnoDB: On FreeBSD check you" + " have compiled the OS with\n" + "InnoDB: a big enough maximum process size.\n" + "InnoDB: Note that in most 32-bit" + " computers the process\n" + "InnoDB: memory space is limited" + " to 2 GB or 4 GB.\n" + "InnoDB: We keep retrying" + " the allocation for 60 seconds...\n", + (ulong) n, (ulong) ut_total_allocated_memory, +#ifdef __WIN__ + (ulong) GetLastError() +#else + (ulong) errno +#endif + ); + } + + os_fast_mutex_unlock(&ut_list_mutex); + + /* Sleep for a second and retry the allocation; maybe this is + just a temporary shortage of memory */ + + os_thread_sleep(1000000); + + retry_count++; + + goto retry; + } + + if (ret == NULL) { + /* Flush stderr to make more probable that the error + message gets in the error file before we generate a seg + fault */ + + fflush(stderr); + + os_fast_mutex_unlock(&ut_list_mutex); + + /* Make an intentional seg fault so that we get a stack + trace */ + /* Intentional segfault on NetWare causes an abend. Avoid this + by graceful exit handling in ut_a(). */ +#if (!defined __NETWARE__) + if (assert_on_error) { + ut_print_timestamp(stderr); + + fprintf(stderr, + " InnoDB: We now intentionally" + " generate a seg fault so that\n" + "InnoDB: on Linux we get a stack trace.\n"); + + if (*ut_mem_null_ptr) ut_mem_null_ptr = 0; + } else { + return(NULL); + } +#else + ut_a(0); +#endif + } + + if (set_to_zero) { +#ifdef UNIV_SET_MEM_TO_ZERO + memset(ret, '\0', n + sizeof(ut_mem_block_t)); +#endif + } + + UNIV_MEM_ALLOC(ret, n + sizeof(ut_mem_block_t)); + + ((ut_mem_block_t*)ret)->size = n + sizeof(ut_mem_block_t); + ((ut_mem_block_t*)ret)->magic_n = UT_MEM_MAGIC_N; + + ut_total_allocated_memory += n + sizeof(ut_mem_block_t); + + UT_LIST_ADD_FIRST(mem_block_list, ut_mem_block_list, + ((ut_mem_block_t*)ret)); + os_fast_mutex_unlock(&ut_list_mutex); + + return((void*)((byte*)ret + sizeof(ut_mem_block_t))); +} + +/************************************************************************** +Allocates memory. Sets it also to zero if UNIV_SET_MEM_TO_ZERO is +defined. */ +UNIV_INTERN +void* +ut_malloc( +/*======*/ + /* out, own: allocated memory */ + ulint n) /* in: number of bytes to allocate */ +{ + return(ut_malloc_low(n, TRUE, TRUE)); +} + +/************************************************************************** +Tests if malloc of n bytes would succeed. ut_malloc() asserts if memory runs +out. It cannot be used if we want to return an error message. Prints to +stderr a message if fails. */ +UNIV_INTERN +ibool +ut_test_malloc( +/*===========*/ + /* out: TRUE if succeeded */ + ulint n) /* in: try to allocate this many bytes */ +{ + void* ret; + + ret = malloc(n); + + if (ret == NULL) { + ut_print_timestamp(stderr); + fprintf(stderr, + " InnoDB: Error: cannot allocate" + " %lu bytes of memory for\n" + "InnoDB: a BLOB with malloc! Total allocated memory\n" + "InnoDB: by InnoDB %lu bytes." + " Operating system errno: %d\n" + "InnoDB: Check if you should increase" + " the swap file or\n" + "InnoDB: ulimits of your operating system.\n" + "InnoDB: On FreeBSD check you have" + " compiled the OS with\n" + "InnoDB: a big enough maximum process size.\n", + (ulong) n, + (ulong) ut_total_allocated_memory, + (int) errno); + return(FALSE); + } + + free(ret); + + return(TRUE); +} + +/************************************************************************** +Frees a memory block allocated with ut_malloc. */ +UNIV_INTERN +void +ut_free( +/*====*/ + void* ptr) /* in, own: memory block */ +{ + ut_mem_block_t* block; + + if (UNIV_LIKELY(srv_use_sys_malloc)) { + free(ptr); + return; + } + + block = (ut_mem_block_t*)((byte*)ptr - sizeof(ut_mem_block_t)); + + os_fast_mutex_lock(&ut_list_mutex); + + ut_a(block->magic_n == UT_MEM_MAGIC_N); + ut_a(ut_total_allocated_memory >= block->size); + + ut_total_allocated_memory -= block->size; + + UT_LIST_REMOVE(mem_block_list, ut_mem_block_list, block); + free(block); + + os_fast_mutex_unlock(&ut_list_mutex); +} + +/************************************************************************** +Implements realloc. This is needed by /pars/lexyy.c. Otherwise, you should not +use this function because the allocation functions in mem0mem.h are the +recommended ones in InnoDB. + +man realloc in Linux, 2004: + + realloc() changes the size of the memory block pointed to + by ptr to size bytes. The contents will be unchanged to + the minimum of the old and new sizes; newly allocated mem + ory will be uninitialized. If ptr is NULL, the call is + equivalent to malloc(size); if size is equal to zero, the + call is equivalent to free(ptr). Unless ptr is NULL, it + must have been returned by an earlier call to malloc(), + calloc() or realloc(). + +RETURN VALUE + realloc() returns a pointer to the newly allocated memory, + which is suitably aligned for any kind of variable and may + be different from ptr, or NULL if the request fails. If + size was equal to 0, either NULL or a pointer suitable to + be passed to free() is returned. If realloc() fails the + original block is left untouched - it is not freed or + moved. */ +UNIV_INTERN +void* +ut_realloc( +/*=======*/ + /* out, own: pointer to new mem block or NULL */ + void* ptr, /* in: pointer to old block or NULL */ + ulint size) /* in: desired size */ +{ + ut_mem_block_t* block; + ulint old_size; + ulint min_size; + void* new_ptr; + + if (UNIV_LIKELY(srv_use_sys_malloc)) { + return(realloc(ptr, size)); + } + + if (ptr == NULL) { + + return(ut_malloc(size)); + } + + if (size == 0) { + ut_free(ptr); + + return(NULL); + } + + block = (ut_mem_block_t*)((byte*)ptr - sizeof(ut_mem_block_t)); + + ut_a(block->magic_n == UT_MEM_MAGIC_N); + + old_size = block->size - sizeof(ut_mem_block_t); + + if (size < old_size) { + min_size = size; + } else { + min_size = old_size; + } + + new_ptr = ut_malloc(size); + + if (new_ptr == NULL) { + + return(NULL); + } + + /* Copy the old data from ptr */ + ut_memcpy(new_ptr, ptr, min_size); + + ut_free(ptr); + + return(new_ptr); +} + +/************************************************************************** +Frees in shutdown all allocated memory not freed yet. */ +UNIV_INTERN +void +ut_free_all_mem(void) +/*=================*/ +{ + ut_mem_block_t* block; + + ut_a(ut_mem_block_list_inited); + ut_mem_block_list_inited = FALSE; + os_fast_mutex_free(&ut_list_mutex); + + while ((block = UT_LIST_GET_FIRST(ut_mem_block_list))) { + + ut_a(block->magic_n == UT_MEM_MAGIC_N); + ut_a(ut_total_allocated_memory >= block->size); + + ut_total_allocated_memory -= block->size; + + UT_LIST_REMOVE(mem_block_list, ut_mem_block_list, block); + free(block); + } + + if (ut_total_allocated_memory != 0) { + fprintf(stderr, + "InnoDB: Warning: after shutdown" + " total allocated memory is %lu\n", + (ulong) ut_total_allocated_memory); + } +} + +/************************************************************************** +Copies up to size - 1 characters from the NUL-terminated string src to +dst, NUL-terminating the result. Returns strlen(src), so truncation +occurred if the return value >= size. */ +UNIV_INTERN +ulint +ut_strlcpy( +/*=======*/ + /* out: strlen(src) */ + char* dst, /* in: destination buffer */ + const char* src, /* in: source buffer */ + ulint size) /* in: size of destination buffer */ +{ + ulint src_size = strlen(src); + + if (size != 0) { + ulint n = ut_min(src_size, size - 1); + + memcpy(dst, src, n); + dst[n] = '\0'; + } + + return(src_size); +} + +/************************************************************************** +Like ut_strlcpy, but if src doesn't fit in dst completely, copies the last +(size - 1) bytes of src, not the first. */ +UNIV_INTERN +ulint +ut_strlcpy_rev( +/*===========*/ + /* out: strlen(src) */ + char* dst, /* in: destination buffer */ + const char* src, /* in: source buffer */ + ulint size) /* in: size of destination buffer */ +{ + ulint src_size = strlen(src); + + if (size != 0) { + ulint n = ut_min(src_size, size - 1); + + memcpy(dst, src + src_size - n, n + 1); + } + + return(src_size); +} + +/************************************************************************** +Make a quoted copy of a NUL-terminated string. Leading and trailing +quotes will not be included; only embedded quotes will be escaped. +See also ut_strlenq() and ut_memcpyq(). */ +UNIV_INTERN +char* +ut_strcpyq( +/*=======*/ + /* out: pointer to end of dest */ + char* dest, /* in: output buffer */ + char q, /* in: the quote character */ + const char* src) /* in: null-terminated string */ +{ + while (*src) { + if ((*dest++ = *src++) == q) { + *dest++ = q; + } + } + + return(dest); +} + +/************************************************************************** +Make a quoted copy of a fixed-length string. Leading and trailing +quotes will not be included; only embedded quotes will be escaped. +See also ut_strlenq() and ut_strcpyq(). */ +UNIV_INTERN +char* +ut_memcpyq( +/*=======*/ + /* out: pointer to end of dest */ + char* dest, /* in: output buffer */ + char q, /* in: the quote character */ + const char* src, /* in: string to be quoted */ + ulint len) /* in: length of src */ +{ + const char* srcend = src + len; + + while (src < srcend) { + if ((*dest++ = *src++) == q) { + *dest++ = q; + } + } + + return(dest); +} + +/************************************************************************** +Return the number of times s2 occurs in s1. Overlapping instances of s2 +are only counted once. */ +UNIV_INTERN +ulint +ut_strcount( +/*========*/ + /* out: the number of times s2 occurs in s1 */ + const char* s1, /* in: string to search in */ + const char* s2) /* in: string to search for */ +{ + ulint count = 0; + ulint len = strlen(s2); + + if (len == 0) { + + return(0); + } + + for (;;) { + s1 = strstr(s1, s2); + + if (!s1) { + + break; + } + + count++; + s1 += len; + } + + return(count); +} + +/************************************************************************** +Replace every occurrence of s1 in str with s2. Overlapping instances of s1 +are only replaced once. */ +UNIV_INTERN +char* +ut_strreplace( +/*==========*/ + /* out, own: modified string, must be + freed with mem_free() */ + const char* str, /* in: string to operate on */ + const char* s1, /* in: string to replace */ + const char* s2) /* in: string to replace s1 with */ +{ + char* new_str; + char* ptr; + const char* str_end; + ulint str_len = strlen(str); + ulint s1_len = strlen(s1); + ulint s2_len = strlen(s2); + ulint count = 0; + int len_delta = (int)s2_len - (int)s1_len; + + str_end = str + str_len; + + if (len_delta <= 0) { + len_delta = 0; + } else { + count = ut_strcount(str, s1); + } + + new_str = mem_alloc(str_len + count * len_delta + 1); + ptr = new_str; + + while (str) { + const char* next = strstr(str, s1); + + if (!next) { + next = str_end; + } + + memcpy(ptr, str, next - str); + ptr += next - str; + + if (next == str_end) { + + break; + } + + memcpy(ptr, s2, s2_len); + ptr += s2_len; + + str = next + s1_len; + } + + *ptr = '\0'; + + return(new_str); +} + +#ifdef UNIV_COMPILE_TEST_FUNCS + +void +test_ut_str_sql_format() +{ + char buf[128]; + ulint ret; + +#define CALL_AND_TEST(str, str_len, buf, buf_size, ret_expected, buf_expected)\ + do {\ + ibool ok = TRUE;\ + memset(buf, 'x', 10);\ + buf[10] = '\0';\ + fprintf(stderr, "TESTING \"%s\", %lu, %lu\n",\ + str, (ulint) str_len, (ulint) buf_size);\ + ret = ut_str_sql_format(str, str_len, buf, buf_size);\ + if (ret != ret_expected) {\ + fprintf(stderr, "expected ret %lu, got %lu\n",\ + (ulint) ret_expected, ret);\ + ok = FALSE;\ + }\ + if (strcmp((char*) buf, buf_expected) != 0) {\ + fprintf(stderr, "expected buf \"%s\", got \"%s\"\n",\ + buf_expected, buf);\ + ok = FALSE;\ + }\ + if (ok) {\ + fprintf(stderr, "OK: %lu, \"%s\"\n\n",\ + (ulint) ret, buf);\ + } else {\ + return;\ + }\ + } while (0) + + CALL_AND_TEST("abcd", 4, buf, 0, 0, "xxxxxxxxxx"); + + CALL_AND_TEST("abcd", 4, buf, 1, 1, ""); + + CALL_AND_TEST("abcd", 4, buf, 2, 1, ""); + + CALL_AND_TEST("abcd", 0, buf, 3, 3, "''"); + CALL_AND_TEST("abcd", 1, buf, 3, 1, ""); + CALL_AND_TEST("abcd", 2, buf, 3, 1, ""); + CALL_AND_TEST("abcd", 3, buf, 3, 1, ""); + CALL_AND_TEST("abcd", 4, buf, 3, 1, ""); + + CALL_AND_TEST("abcd", 0, buf, 4, 3, "''"); + CALL_AND_TEST("abcd", 1, buf, 4, 4, "'a'"); + CALL_AND_TEST("abcd", 2, buf, 4, 4, "'a'"); + CALL_AND_TEST("abcd", 3, buf, 4, 4, "'a'"); + CALL_AND_TEST("abcd", 4, buf, 4, 4, "'a'"); + CALL_AND_TEST("abcde", 5, buf, 4, 4, "'a'"); + CALL_AND_TEST("'", 1, buf, 4, 3, "''"); + CALL_AND_TEST("''", 2, buf, 4, 3, "''"); + CALL_AND_TEST("a'", 2, buf, 4, 4, "'a'"); + CALL_AND_TEST("'a", 2, buf, 4, 3, "''"); + CALL_AND_TEST("ab", 2, buf, 4, 4, "'a'"); + + CALL_AND_TEST("abcdef", 0, buf, 5, 3, "''"); + CALL_AND_TEST("abcdef", 1, buf, 5, 4, "'a'"); + CALL_AND_TEST("abcdef", 2, buf, 5, 5, "'ab'"); + CALL_AND_TEST("abcdef", 3, buf, 5, 5, "'ab'"); + CALL_AND_TEST("abcdef", 4, buf, 5, 5, "'ab'"); + CALL_AND_TEST("abcdef", 5, buf, 5, 5, "'ab'"); + CALL_AND_TEST("abcdef", 6, buf, 5, 5, "'ab'"); + CALL_AND_TEST("'", 1, buf, 5, 5, "''''"); + CALL_AND_TEST("''", 2, buf, 5, 5, "''''"); + CALL_AND_TEST("a'", 2, buf, 5, 4, "'a'"); + CALL_AND_TEST("'a", 2, buf, 5, 5, "''''"); + CALL_AND_TEST("ab", 2, buf, 5, 5, "'ab'"); + CALL_AND_TEST("abc", 3, buf, 5, 5, "'ab'"); + + CALL_AND_TEST("ab", 2, buf, 6, 5, "'ab'"); + + CALL_AND_TEST("a'b'c", 5, buf, 32, 10, "'a''b''c'"); + CALL_AND_TEST("a'b'c'", 6, buf, 32, 12, "'a''b''c'''"); +} + +#endif /* UNIV_COMPILE_TEST_FUNCS */ diff --git a/storage/xtradb/ut/ut0rnd.c b/storage/xtradb/ut/ut0rnd.c new file mode 100644 index 00000000000..f5d6cb08b0f --- /dev/null +++ b/storage/xtradb/ut/ut0rnd.c @@ -0,0 +1,94 @@ +/***************************************************************************** + +Copyright (c) 1994, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/******************************************************************* +Random numbers and hashing + +Created 5/11/1994 Heikki Tuuri +********************************************************************/ + +#include "ut0rnd.h" + +#ifdef UNIV_NONINL +#include "ut0rnd.ic" +#endif + +/* These random numbers are used in ut_find_prime */ +#define UT_RANDOM_1 1.0412321 +#define UT_RANDOM_2 1.1131347 +#define UT_RANDOM_3 1.0132677 + + +UNIV_INTERN ulint ut_rnd_ulint_counter = 65654363; + +/*************************************************************** +Looks for a prime number slightly greater than the given argument. +The prime is chosen so that it is not near any power of 2. */ +UNIV_INTERN +ulint +ut_find_prime( +/*==========*/ + /* out: prime */ + ulint n) /* in: positive number > 100 */ +{ + ulint pow2; + ulint i; + + n += 100; + + pow2 = 1; + while (pow2 * 2 < n) { + pow2 = 2 * pow2; + } + + if ((double)n < 1.05 * (double)pow2) { + n = (ulint) ((double)n * UT_RANDOM_1); + } + + pow2 = 2 * pow2; + + if ((double)n > 0.95 * (double)pow2) { + n = (ulint) ((double)n * UT_RANDOM_2); + } + + if (n > pow2 - 20) { + n += 30; + } + + /* Now we have n far enough from powers of 2. To make + n more random (especially, if it was not near + a power of 2), we then multiply it by a random number. */ + + n = (ulint) ((double)n * UT_RANDOM_3); + + for (;; n++) { + i = 2; + while (i * i <= n) { + if (n % i == 0) { + goto next_n; + } + i++; + } + + /* Found a prime */ + break; +next_n: ; + } + + return(n); +} diff --git a/storage/xtradb/ut/ut0ut.c b/storage/xtradb/ut/ut0ut.c new file mode 100644 index 00000000000..d94e0771a54 --- /dev/null +++ b/storage/xtradb/ut/ut0ut.c @@ -0,0 +1,600 @@ +/***************************************************************************** + +Copyright (c) 1994, 2009, Innobase Oy. 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 + +*****************************************************************************/ + +/******************************************************************* +Various utilities for Innobase. + +Created 5/11/1994 Heikki Tuuri +********************************************************************/ + +#include "ut0ut.h" + +#ifdef UNIV_NONINL +#include "ut0ut.ic" +#endif + +#include <stdarg.h> +#include <string.h> +#include <ctype.h> + +#include "trx0trx.h" +#include "ha_prototypes.h" +#ifndef UNIV_HOTBACKUP +# include "mysql_com.h" /* NAME_LEN */ +#endif /* UNIV_HOTBACKUP */ + +UNIV_INTERN ibool ut_always_false = FALSE; + +#ifdef __WIN__ +/********************************************************************* +NOTE: The Windows epoch starts from 1601/01/01 whereas the Unix +epoch starts from 1970/1/1. For selection of constant see: +http://support.microsoft.com/kb/167296/ */ +#define WIN_TO_UNIX_DELTA_USEC ((ib_int64_t) 11644473600000000ULL) + + +/********************************************************************* +This is the Windows version of gettimeofday(2).*/ +static +int +ut_gettimeofday( +/*============*/ + /* out: 0 if all OK else -1 */ + struct timeval* tv, /* out: Values are relative to Unix epoch */ + void* tz) /* in: not used */ +{ + FILETIME ft; + ib_int64_t tm; + + if (!tv) { + errno = EINVAL; + return(-1); + } + + GetSystemTimeAsFileTime(&ft); + + tm = (ib_int64_t) ft.dwHighDateTime << 32; + tm |= ft.dwLowDateTime; + + ut_a(tm >= 0); /* If tm wraps over to negative, the quotient / 10 + does not work */ + + tm /= 10; /* Convert from 100 nsec periods to usec */ + + /* If we don't convert to the Unix epoch the value for + struct timeval::tv_sec will overflow.*/ + tm -= WIN_TO_UNIX_DELTA_USEC; + + tv->tv_sec = (long) (tm / 1000000L); + tv->tv_usec = (long) (tm % 1000000L); + + return(0); +} +#else +#define ut_gettimeofday gettimeofday +#endif + +/************************************************************ +Gets the high 32 bits in a ulint. That is makes a shift >> 32, +but since there seem to be compiler bugs in both gcc and Visual C++, +we do this by a special conversion. */ +UNIV_INTERN +ulint +ut_get_high32( +/*==========*/ + /* out: a >> 32 */ + ulint a) /* in: ulint */ +{ + ib_int64_t i; + + i = (ib_int64_t)a; + + i = i >> 32; + + return((ulint)i); +} + +/************************************************************** +Returns system time. We do not specify the format of the time returned: +the only way to manipulate it is to use the function ut_difftime. */ +UNIV_INTERN +ib_time_t +ut_time(void) +/*=========*/ +{ + return(time(NULL)); +} + +/************************************************************** +Returns system time. +Upon successful completion, the value 0 is returned; otherwise the +value -1 is returned and the global variable errno is set to indicate the +error. */ +UNIV_INTERN +int +ut_usectime( +/*========*/ + /* out: 0 on success, -1 otherwise */ + ulint* sec, /* out: seconds since the Epoch */ + ulint* ms) /* out: microseconds since the Epoch+*sec */ +{ + struct timeval tv; + int ret; + int errno_gettimeofday; + int i; + + for (i = 0; i < 10; i++) { + + ret = ut_gettimeofday(&tv, NULL); + + if (ret == -1) { + errno_gettimeofday = errno; + ut_print_timestamp(stderr); + fprintf(stderr, " InnoDB: gettimeofday(): %s\n", + strerror(errno_gettimeofday)); + os_thread_sleep(100000); /* 0.1 sec */ + errno = errno_gettimeofday; + } else { + break; + } + } + + if (ret != -1) { + *sec = (ulint) tv.tv_sec; + *ms = (ulint) tv.tv_usec; + } + + return(ret); +} + +/************************************************************** +Returns the number of microseconds since epoch. Similar to +time(3), the return value is also stored in *tloc, provided +that tloc is non-NULL. */ +UNIV_INTERN +ullint +ut_time_us( +/*=======*/ + /* out: us since epoch */ + ullint* tloc) /* out: us since epoch, if non-NULL */ +{ + struct timeval tv; + ullint us; + + ut_gettimeofday(&tv, NULL); + + us = (ullint) tv.tv_sec * 1000000 + tv.tv_usec; + + if (tloc != NULL) { + *tloc = us; + } + + return(us); +} + +/************************************************************** +Returns the difference of two times in seconds. */ +UNIV_INTERN +double +ut_difftime( +/*========*/ + /* out: time2 - time1 expressed in seconds */ + ib_time_t time2, /* in: time */ + ib_time_t time1) /* in: time */ +{ + return(difftime(time2, time1)); +} + +/************************************************************** +Prints a timestamp to a file. */ +UNIV_INTERN +void +ut_print_timestamp( +/*===============*/ + FILE* file) /* in: file where to print */ +{ +#ifdef __WIN__ + SYSTEMTIME cal_tm; + + GetLocalTime(&cal_tm); + + fprintf(file,"%02d%02d%02d %2d:%02d:%02d", + (int)cal_tm.wYear % 100, + (int)cal_tm.wMonth, + (int)cal_tm.wDay, + (int)cal_tm.wHour, + (int)cal_tm.wMinute, + (int)cal_tm.wSecond); +#else + struct tm cal_tm; + struct tm* cal_tm_ptr; + time_t tm; + + time(&tm); + +#ifdef HAVE_LOCALTIME_R + localtime_r(&tm, &cal_tm); + cal_tm_ptr = &cal_tm; +#else + cal_tm_ptr = localtime(&tm); +#endif + fprintf(file,"%02d%02d%02d %2d:%02d:%02d", + cal_tm_ptr->tm_year % 100, + cal_tm_ptr->tm_mon + 1, + cal_tm_ptr->tm_mday, + cal_tm_ptr->tm_hour, + cal_tm_ptr->tm_min, + cal_tm_ptr->tm_sec); +#endif +} + +/************************************************************** +Sprintfs a timestamp to a buffer, 13..14 chars plus terminating NUL. */ +UNIV_INTERN +void +ut_sprintf_timestamp( +/*=================*/ + char* buf) /* in: buffer where to sprintf */ +{ +#ifdef __WIN__ + SYSTEMTIME cal_tm; + + GetLocalTime(&cal_tm); + + sprintf(buf, "%02d%02d%02d %2d:%02d:%02d", + (int)cal_tm.wYear % 100, + (int)cal_tm.wMonth, + (int)cal_tm.wDay, + (int)cal_tm.wHour, + (int)cal_tm.wMinute, + (int)cal_tm.wSecond); +#else + struct tm cal_tm; + struct tm* cal_tm_ptr; + time_t tm; + + time(&tm); + +#ifdef HAVE_LOCALTIME_R + localtime_r(&tm, &cal_tm); + cal_tm_ptr = &cal_tm; +#else + cal_tm_ptr = localtime(&tm); +#endif + sprintf(buf, "%02d%02d%02d %2d:%02d:%02d", + cal_tm_ptr->tm_year % 100, + cal_tm_ptr->tm_mon + 1, + cal_tm_ptr->tm_mday, + cal_tm_ptr->tm_hour, + cal_tm_ptr->tm_min, + cal_tm_ptr->tm_sec); +#endif +} + +#ifdef UNIV_HOTBACKUP +/************************************************************** +Sprintfs a timestamp to a buffer with no spaces and with ':' characters +replaced by '_'. */ +UNIV_INTERN +void +ut_sprintf_timestamp_without_extra_chars( +/*=====================================*/ + char* buf) /* in: buffer where to sprintf */ +{ +#ifdef __WIN__ + SYSTEMTIME cal_tm; + + GetLocalTime(&cal_tm); + + sprintf(buf, "%02d%02d%02d_%2d_%02d_%02d", + (int)cal_tm.wYear % 100, + (int)cal_tm.wMonth, + (int)cal_tm.wDay, + (int)cal_tm.wHour, + (int)cal_tm.wMinute, + (int)cal_tm.wSecond); +#else + struct tm cal_tm; + struct tm* cal_tm_ptr; + time_t tm; + + time(&tm); + +#ifdef HAVE_LOCALTIME_R + localtime_r(&tm, &cal_tm); + cal_tm_ptr = &cal_tm; +#else + cal_tm_ptr = localtime(&tm); +#endif + sprintf(buf, "%02d%02d%02d_%2d_%02d_%02d", + cal_tm_ptr->tm_year % 100, + cal_tm_ptr->tm_mon + 1, + cal_tm_ptr->tm_mday, + cal_tm_ptr->tm_hour, + cal_tm_ptr->tm_min, + cal_tm_ptr->tm_sec); +#endif +} + +/************************************************************** +Returns current year, month, day. */ +UNIV_INTERN +void +ut_get_year_month_day( +/*==================*/ + ulint* year, /* out: current year */ + ulint* month, /* out: month */ + ulint* day) /* out: day */ +{ +#ifdef __WIN__ + SYSTEMTIME cal_tm; + + GetLocalTime(&cal_tm); + + *year = (ulint)cal_tm.wYear; + *month = (ulint)cal_tm.wMonth; + *day = (ulint)cal_tm.wDay; +#else + struct tm cal_tm; + struct tm* cal_tm_ptr; + time_t tm; + + time(&tm); + +#ifdef HAVE_LOCALTIME_R + localtime_r(&tm, &cal_tm); + cal_tm_ptr = &cal_tm; +#else + cal_tm_ptr = localtime(&tm); +#endif + *year = (ulint)cal_tm_ptr->tm_year + 1900; + *month = (ulint)cal_tm_ptr->tm_mon + 1; + *day = (ulint)cal_tm_ptr->tm_mday; +#endif +} +#endif /* UNIV_HOTBACKUP */ + +/***************************************************************** +Runs an idle loop on CPU. The argument gives the desired delay +in microseconds on 100 MHz Pentium + Visual C++. */ +extern ulint srv_spins_microsec; + +UNIV_INTERN +ulint +ut_delay( +/*=====*/ + /* out: dummy value */ + ulint delay) /* in: delay in microseconds on 100 MHz Pentium */ +{ + ulint i, j; + + j = 0; + + for (i = 0; i < delay * srv_spins_microsec; i++) { +#if (defined (__i386__) || defined (__x86_64__)) && defined (__GNUC__) + /* it is equal to the instruction 'pause' */ + __asm__ __volatile__ ("rep; nop"); +#endif + j += i; + } + + if (ut_always_false) { + ut_always_false = (ibool) j; + } + + return(j); +} + +/***************************************************************** +Prints the contents of a memory buffer in hex and ascii. */ +UNIV_INTERN +void +ut_print_buf( +/*=========*/ + FILE* file, /* in: file where to print */ + const void* buf, /* in: memory buffer */ + ulint len) /* in: length of the buffer */ +{ + const byte* data; + ulint i; + + UNIV_MEM_ASSERT_RW(buf, len); + + fprintf(file, " len %lu; hex ", len); + + for (data = (const byte*)buf, i = 0; i < len; i++) { + fprintf(file, "%02lx", (ulong)*data++); + } + + fputs("; asc ", file); + + data = (const byte*)buf; + + for (i = 0; i < len; i++) { + int c = (int) *data++; + putc(isprint(c) ? c : ' ', file); + } + + putc(';', file); +} + +/***************************************************************** +Calculates fast the number rounded up to the nearest power of 2. */ +UNIV_INTERN +ulint +ut_2_power_up( +/*==========*/ + /* out: first power of 2 which is >= n */ + ulint n) /* in: number != 0 */ +{ + ulint res; + + res = 1; + + ut_ad(n > 0); + + while (res < n) { + res = res * 2; + } + + return(res); +} + +/************************************************************************** +Outputs a NUL-terminated file name, quoted with apostrophes. */ +UNIV_INTERN +void +ut_print_filename( +/*==============*/ + FILE* f, /* in: output stream */ + const char* name) /* in: name to print */ +{ + putc('\'', f); + for (;;) { + int c = *name++; + switch (c) { + case 0: + goto done; + case '\'': + putc(c, f); + /* fall through */ + default: + putc(c, f); + } + } +done: + putc('\'', f); +} + +/************************************************************************** +Outputs a fixed-length string, quoted as an SQL identifier. +If the string contains a slash '/', the string will be +output as two identifiers separated by a period (.), +as in SQL database_name.identifier. */ +UNIV_INTERN +void +ut_print_name( +/*==========*/ + FILE* f, /* in: output stream */ + trx_t* trx, /* in: transaction */ + ibool table_id,/* in: TRUE=print a table name, + FALSE=print other identifier */ + const char* name) /* in: name to print */ +{ + ut_print_namel(f, trx, table_id, name, strlen(name)); +} + +/************************************************************************** +Outputs a fixed-length string, quoted as an SQL identifier. +If the string contains a slash '/', the string will be +output as two identifiers separated by a period (.), +as in SQL database_name.identifier. */ +UNIV_INTERN +void +ut_print_namel( +/*===========*/ + FILE* f, /* in: output stream */ + trx_t* trx, /* in: transaction (NULL=no quotes) */ + ibool table_id,/* in: TRUE=print a table name, + FALSE=print other identifier */ + const char* name, /* in: name to print */ + ulint namelen)/* in: length of name */ +{ +#ifdef UNIV_HOTBACKUP + fwrite(name, 1, namelen, f); +#else + /* 2 * NAME_LEN for database and table name, + and some slack for the #mysql50# prefix and quotes */ + char buf[3 * NAME_LEN]; + const char* bufend; + + bufend = innobase_convert_name(buf, sizeof buf, + name, namelen, + trx ? trx->mysql_thd : NULL, + table_id); + + fwrite(buf, 1, bufend - buf, f); +#endif +} + +/************************************************************************** +Catenate files. */ +UNIV_INTERN +void +ut_copy_file( +/*=========*/ + FILE* dest, /* in: output file */ + FILE* src) /* in: input file to be appended to output */ +{ + long len = ftell(src); + char buf[4096]; + + rewind(src); + do { + size_t maxs = len < (long) sizeof buf + ? (size_t) len + : sizeof buf; + size_t size = fread(buf, 1, maxs, src); + fwrite(buf, 1, size, dest); + len -= (long) size; + if (size < maxs) { + break; + } + } while (len > 0); +} + +/************************************************************************** +snprintf(). */ + +#ifdef __WIN__ +#include <stdarg.h> +int +ut_snprintf( + /* out: number of characters that would + have been printed if the size were + unlimited, not including the terminating + '\0'. */ + char* str, /* out: string */ + size_t size, /* in: str size */ + const char* fmt, /* in: format */ + ...) /* in: format values */ +{ + int res; + va_list ap1; + va_list ap2; + + va_start(ap1, fmt); + va_start(ap2, fmt); + + res = _vscprintf(fmt, ap1); + ut_a(res != -1); + + if (size > 0) { + _vsnprintf(str, size, fmt, ap2); + + if ((size_t) res >= size) { + str[size - 1] = '\0'; + } + } + + va_end(ap1); + va_end(ap2); + + return(res); +} +#endif /* __WIN__ */ diff --git a/storage/xtradb/ut/ut0vec.c b/storage/xtradb/ut/ut0vec.c new file mode 100644 index 00000000000..69b7bec701a --- /dev/null +++ b/storage/xtradb/ut/ut0vec.c @@ -0,0 +1,72 @@ +/***************************************************************************** + +Copyright (c) 2006, 2009, Innobase Oy. 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 "ut0vec.h" +#ifdef UNIV_NONINL +#include "ut0vec.ic" +#endif +#include <string.h> + +/******************************************************************** +Create a new vector with the given initial size. */ +UNIV_INTERN +ib_vector_t* +ib_vector_create( +/*=============*/ + /* out: vector */ + mem_heap_t* heap, /* in: heap */ + ulint size) /* in: initial size */ +{ + ib_vector_t* vec; + + ut_a(size > 0); + + vec = mem_heap_alloc(heap, sizeof(*vec)); + + vec->heap = heap; + vec->data = mem_heap_alloc(heap, sizeof(void*) * size); + vec->used = 0; + vec->total = size; + + return(vec); +} + +/******************************************************************** +Push a new element to the vector, increasing its size if necessary. */ +UNIV_INTERN +void +ib_vector_push( +/*===========*/ + ib_vector_t* vec, /* in: vector */ + void* elem) /* in: data element */ +{ + if (vec->used >= vec->total) { + void** new_data; + ulint new_total = vec->total * 2; + + new_data = mem_heap_alloc(vec->heap, + sizeof(void*) * new_total); + memcpy(new_data, vec->data, sizeof(void*) * vec->total); + + vec->data = new_data; + vec->total = new_total; + } + + vec->data[vec->used] = elem; + vec->used++; +} diff --git a/storage/xtradb/ut/ut0wqueue.c b/storage/xtradb/ut/ut0wqueue.c new file mode 100644 index 00000000000..a5c14ac8130 --- /dev/null +++ b/storage/xtradb/ut/ut0wqueue.c @@ -0,0 +1,110 @@ +/***************************************************************************** + +Copyright (c) 2006, 2009, Innobase Oy. 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 "ut0wqueue.h" + +/******************************************************************** +Create a new work queue. */ +UNIV_INTERN +ib_wqueue_t* +ib_wqueue_create(void) +/*===================*/ + /* out: work queue */ +{ + ib_wqueue_t* wq = mem_alloc(sizeof(ib_wqueue_t)); + + mutex_create(&wq->mutex, SYNC_WORK_QUEUE); + + wq->items = ib_list_create(); + wq->event = os_event_create(NULL); + + return(wq); +} + +/******************************************************************** +Free a work queue. */ +UNIV_INTERN +void +ib_wqueue_free( +/*===========*/ + ib_wqueue_t* wq) /* in: work queue */ +{ + ut_a(!ib_list_get_first(wq->items)); + + mutex_free(&wq->mutex); + ib_list_free(wq->items); + os_event_free(wq->event); + + mem_free(wq); +} + +/******************************************************************** +Add a work item to the queue. */ +UNIV_INTERN +void +ib_wqueue_add( +/*==========*/ + ib_wqueue_t* wq, /* in: work queue */ + void* item, /* in: work item */ + mem_heap_t* heap) /* in: memory heap to use for allocating the + list node */ +{ + mutex_enter(&wq->mutex); + + ib_list_add_last(wq->items, item, heap); + os_event_set(wq->event); + + mutex_exit(&wq->mutex); +} + +/******************************************************************** +Wait for a work item to appear in the queue. */ +UNIV_INTERN +void* +ib_wqueue_wait( + /* out: work item */ + ib_wqueue_t* wq) /* in: work queue */ +{ + ib_list_node_t* node; + + for (;;) { + os_event_wait(wq->event); + + mutex_enter(&wq->mutex); + + node = ib_list_get_first(wq->items); + + if (node) { + ib_list_remove(wq->items, node); + + if (!ib_list_get_first(wq->items)) { + /* We must reset the event when the list + gets emptied. */ + os_event_reset(wq->event); + } + + break; + } + + mutex_exit(&wq->mutex); + } + + mutex_exit(&wq->mutex); + + return(node->data); +} diff --git a/storage/xtradb/win-plugin/README b/storage/xtradb/win-plugin/README new file mode 100644 index 00000000000..9182f2c555c --- /dev/null +++ b/storage/xtradb/win-plugin/README @@ -0,0 +1,25 @@ +This directory contains patches that need to be applied to the MySQL +source tree in order to build the dynamic plugin on Windows -- +HA_INNODB.DLL. Please note the followings when adding the patches: + +* The patch must be applied from the mysql top-level source directory. + patch -p0 < win-plugin.diff +* The patch filenames end in ".diff". +* All patches here are expected to apply cleanly to the latest MySQL 5.1 + tree when storage/innobase is replaced with this InnoDB branch. + +When applying the patch, the following files will be modified: + + * CMakeLists.txt + * sql/CMakeLists.txt + * win/configure.js + * win/build-vs71.bat + * win/build-vs8.bat + * win/build-vs8_x64.bat + +Also, two new files will be added: + + * sql/mysqld.def + * sql/mysqld_x64.def + +You can get "patch" utility for Windows from http://unxutils.sourceforge.net/ diff --git a/storage/xtradb/win-plugin/win-plugin.diff b/storage/xtradb/win-plugin/win-plugin.diff new file mode 100644 index 00000000000..46d2e5b2d2d --- /dev/null +++ b/storage/xtradb/win-plugin/win-plugin.diff @@ -0,0 +1,297 @@ +diff -Nur CMakeLists.txt.orig CMakeLists.txt +--- CMakeLists.txt.orig 2008-10-03 12:25:41 -05:00 ++++ CMakeLists.txt 2008-09-26 17:32:51 -05:00 +@@ -244,9 +244,9 @@ + IF(WITH_FEDERATED_STORAGE_ENGINE) + ADD_SUBDIRECTORY(storage/federated) + ENDIF(WITH_FEDERATED_STORAGE_ENGINE) +-IF(WITH_INNOBASE_STORAGE_ENGINE) ++IF(WITH_INNOBASE_STORAGE_ENGINE OR INNODB_DYNAMIC_PLUGIN) + ADD_SUBDIRECTORY(storage/innobase) +-ENDIF(WITH_INNOBASE_STORAGE_ENGINE) ++ENDIF(WITH_INNOBASE_STORAGE_ENGINE OR INNODB_DYNAMIC_PLUGIN) + ADD_SUBDIRECTORY(sql) + ADD_SUBDIRECTORY(server-tools/instance-manager) + ADD_SUBDIRECTORY(libmysql) + +diff -Nur sql/CMakeLists.txt.orig sql/CMakeLists.txt +--- sql/CMakeLists.txt.orig 2008-10-03 12:25:41 -05:00 ++++ sql/CMakeLists.txt 2008-09-24 03:58:19 -05:00 +@@ -100,6 +100,15 @@ + LINK_FLAGS "/PDB:${CMAKE_CFG_INTDIR}/mysqld${MYSQLD_EXE_SUFFIX}.pdb") + ENDIF(cmake_version EQUAL 20406) + ++# Checks for 64-bit version ++IF(CMAKE_SIZEOF_VOID_P MATCHES 8) ++SET_TARGET_PROPERTIES(mysqld PROPERTIES ++ LINK_FLAGS "/def:\"${PROJECT_SOURCE_DIR}/sql/mysqld_x64.def\"") ++ELSE(CMAKE_SIZEOF_VOID_P MATCHES 8) ++SET_TARGET_PROPERTIES(mysqld PROPERTIES ++ LINK_FLAGS "/def:\"${PROJECT_SOURCE_DIR}/sql/mysqld.def\"") ++ENDIF(CMAKE_SIZEOF_VOID_P MATCHES 8) ++ + IF(EMBED_MANIFESTS) + MYSQL_EMBED_MANIFEST("mysqld" "asInvoker") + ENDIF(EMBED_MANIFESTS) + +diff -Nur sql/mysqld.def.orig sql/mysqld.def +--- sql/mysqld.def.orig 1969-12-31 18:00:00 -06:00 ++++ sql/mysqld.def 2008-10-31 02:20:32 -05:00 +@@ -0,0 +1,98 @@ ++EXPORTS ++ ?use_hidden_primary_key@handler@@UAEXXZ ++ ?get_dynamic_partition_info@handler@@UAEXPAUPARTITION_INFO@@I@Z ++ ?read_first_row@handler@@UAEHPAEI@Z ++ ?read_range_next@handler@@UAEHXZ ++ ?read_range_first@handler@@UAEHPBUst_key_range@@0_N1@Z ++ ?read_multi_range_first@handler@@UAEHPAPAUst_key_multi_range@@PAU2@I_NPAUst_handler_buffer@@@Z ++ ?read_multi_range_next@handler@@UAEHPAPAUst_key_multi_range@@@Z ++ ?index_read_idx_map@handler@@UAEHPAEIPBEKW4ha_rkey_function@@@Z ++ ?print_error@handler@@UAEXHH@Z ++ ?clone@handler@@UAEPAV1@PAUst_mem_root@@@Z ++ ?get_auto_increment@handler@@UAEX_K00PA_K1@Z ++ ?index_next_same@handler@@UAEHPAEPBEI@Z ++ ?get_error_message@handler@@UAE_NHPAVString@@@Z ++ ?ha_thd@handler@@IBEPAVTHD@@XZ ++ ?update_auto_increment@handler@@QAEHXZ ++ ?ha_statistic_increment@handler@@IBEXPQsystem_status_var@@K@Z ++ ?trans_register_ha@@YAXPAVTHD@@_NPAUhandlerton@@@Z ++ ?cmp@Field_blob@@QAEHPBEI0I@Z ++ ?set_time@Field_timestamp@@QAEXXZ ++ ?sql_print_error@@YAXPBDZZ ++ ?sql_print_warning@@YAXPBDZZ ++ ?check_global_access@@YA_NPAVTHD@@K@Z ++ ?schema_table_store_record@@YA_NPAVTHD@@PAUst_table@@@Z ++ ?get_quote_char_for_identifier@@YAHPAVTHD@@PBDI@Z ++ ?copy@String@@QAE_NXZ ++ ?copy@String@@QAE_NABV1@@Z ++ ?copy@String@@QAE_NPBDIPAUcharset_info_st@@@Z ++ ?copy_and_convert@@YAIPADIPAUcharset_info_st@@PBDI1PAI@Z ++ ?filename_to_tablename@@YAIPBDPADI@Z ++ ?strconvert@@YAIPAUcharset_info_st@@PBD0PADIPAI@Z ++ ?calculate_key_len@@YAIPAUst_table@@IPBEK@Z ++ ?sql_alloc@@YAPAXI@Z ++ ?localtime_to_TIME@@YAXPAUst_mysql_time@@PAUtm@@@Z ++ ?push_warning@@YAPAVMYSQL_ERROR@@PAVTHD@@W4enum_warning_level@1@IPBD@Z ++ ?push_warning_printf@@YAXPAVTHD@@W4enum_warning_level@MYSQL_ERROR@@IPBDZZ ++ ?drop_table@handler@@EAEXPBD@Z ++ ?column_bitmaps_signal@handler@@UAEXXZ ++ ?delete_table@handler@@MAEHPBD@Z ++ ?rename_table@handler@@MAEHPBD0@Z ++ ?key_map_empty@@3V?$Bitmap@$0EA@@@B ++ ?THR_THD@@3PAVTHD@@A ++ ?end_of_list@@3Ulist_node@@A ++ ?mysql_tmpdir_list@@3Ust_my_tmpdir@@A ++ mysql_query_cache_invalidate4 ++ thd_query ++ thd_sql_command ++ thd_get_thread_id ++ thd_get_xid ++ thd_slave_thread ++ thd_non_transactional_update ++ thd_mark_transaction_to_rollback ++ thd_security_context ++ thd_charset ++ thd_test_options ++ thd_ha_data ++ thd_killed ++ thd_tx_isolation ++ thd_tablespace_op ++ thd_sql_command ++ thd_memdup ++ thd_make_lex_string ++ thd_in_lock_tables ++ thd_binlog_format ++ _my_hash_init ++ my_hash_free ++ my_tmpdir ++ check_if_legal_filename ++ my_filename ++ my_sync_dir_by_file ++ alloc_root ++ thr_lock_data_init ++ thr_lock_init ++ thr_lock_delete ++ my_multi_malloc ++ get_charset ++ unpack_filename ++ my_hash_insert ++ my_hash_search ++ my_hash_delete ++ mysql_bin_log_file_pos ++ mysql_bin_log_file_name ++ mysqld_embedded ++ my_thread_name ++ my_malloc ++ my_no_flags_free ++ _sanity ++ _mymalloc ++ _myfree ++ _my_strdup ++ _my_thread_var ++ my_error ++ pthread_cond_init ++ pthread_cond_signal ++ pthread_cond_wait ++ pthread_cond_destroy ++ localtime_r ++ my_strdup + +diff -Nur sql/mysqld_x64.def.orig sql/mysqld_x64.def +--- sql/mysqld_x64.def.orig 1969-12-31 18:00:00 -06:00 ++++ sql/mysqld_x64.def 2008-10-31 02:22:04 -05:00 +@@ -0,0 +1,98 @@ ++EXPORTS ++ ?use_hidden_primary_key@handler@@UEAAXXZ ++ ?get_dynamic_partition_info@handler@@UEAAXPEAUPARTITION_INFO@@I@Z ++ ?read_first_row@handler@@UEAAHPEAEI@Z ++ ?read_range_next@handler@@UEAAHXZ ++ ?read_range_first@handler@@UEAAHPEBUst_key_range@@0_N1@Z ++ ?read_multi_range_first@handler@@UEAAHPEAPEAUst_key_multi_range@@PEAU2@I_NPEAUst_handler_buffer@@@Z ++ ?read_multi_range_next@handler@@UEAAHPEAPEAUst_key_multi_range@@@Z ++ ?index_read_idx_map@handler@@UEAAHPEAEIPEBEKW4ha_rkey_function@@@Z ++ ?print_error@handler@@UEAAXHH@Z ++ ?clone@handler@@UEAAPEAV1@PEAUst_mem_root@@@Z ++ ?get_auto_increment@handler@@UEAAX_K00PEA_K1@Z ++ ?index_next_same@handler@@UEAAHPEAEPEBEI@Z ++ ?get_error_message@handler@@UEAA_NHPEAVString@@@Z ++ ?ha_thd@handler@@IEBAPEAVTHD@@XZ ++ ?update_auto_increment@handler@@QEAAHXZ ++ ?ha_statistic_increment@handler@@IEBAXPEQsystem_status_var@@K@Z ++ ?trans_register_ha@@YAXPEAVTHD@@_NPEAUhandlerton@@@Z ++ ?cmp@Field_blob@@QEAAHPEBEI0I@Z ++ ?set_time@Field_timestamp@@QEAAXXZ ++ ?sql_print_error@@YAXPEBDZZ ++ ?sql_print_warning@@YAXPEBDZZ ++ ?check_global_access@@YA_NPEAVTHD@@K@Z ++ ?schema_table_store_record@@YA_NPEAVTHD@@PEAUst_table@@@Z ++ ?get_quote_char_for_identifier@@YAHPEAVTHD@@PEBDI@Z ++ ?copy@String@@QEAA_NXZ ++ ?copy@String@@QEAA_NAEBV1@@Z ++ ?copy@String@@QEAA_NPEBDIPEAUcharset_info_st@@@Z ++ ?copy_and_convert@@YAIPEADIPEAUcharset_info_st@@PEBDI1PEAI@Z ++ ?filename_to_tablename@@YAIPEBDPEADI@Z ++ ?strconvert@@YAIPEAUcharset_info_st@@PEBD0PEADIPEAI@Z ++ ?calculate_key_len@@YAIPEAUst_table@@IPEBEK@Z ++ ?sql_alloc@@YAPEAX_K@Z ++ ?localtime_to_TIME@@YAXPEAUst_mysql_time@@PEAUtm@@@Z ++ ?push_warning@@YAPEAVMYSQL_ERROR@@PEAVTHD@@W4enum_warning_level@1@IPEBD@Z ++ ?push_warning_printf@@YAXPEAVTHD@@W4enum_warning_level@MYSQL_ERROR@@IPEBDZZ ++ ?drop_table@handler@@EEAAXPEBD@Z ++ ?column_bitmaps_signal@handler@@UEAAXXZ ++ ?delete_table@handler@@MEAAHPEBD@Z ++ ?rename_table@handler@@MEAAHPEBD0@Z ++ ?key_map_empty@@3V?$Bitmap@$0EA@@@B ++ ?THR_THD@@3PEAVTHD@@EA ++ ?end_of_list@@3Ulist_node@@A ++ ?mysql_tmpdir_list@@3Ust_my_tmpdir@@A ++ mysql_query_cache_invalidate4 ++ thd_query ++ thd_sql_command ++ thd_get_thread_id ++ thd_get_xid ++ thd_slave_thread ++ thd_non_transactional_update ++ thd_mark_transaction_to_rollback ++ thd_security_context ++ thd_charset ++ thd_test_options ++ thd_ha_data ++ thd_killed ++ thd_tx_isolation ++ thd_tablespace_op ++ thd_sql_command ++ thd_memdup ++ thd_make_lex_string ++ thd_in_lock_tables ++ thd_binlog_format ++ _my_hash_init ++ my_hash_free ++ my_tmpdir ++ check_if_legal_filename ++ my_filename ++ my_sync_dir_by_file ++ alloc_root ++ thr_lock_data_init ++ thr_lock_init ++ thr_lock_delete ++ my_multi_malloc ++ get_charset ++ unpack_filename ++ my_hash_insert ++ my_hash_search ++ my_hash_delete ++ mysql_bin_log_file_pos ++ mysql_bin_log_file_name ++ mysqld_embedded ++ my_thread_name ++ my_malloc ++ my_no_flags_free ++ _sanity ++ _mymalloc ++ _myfree ++ _my_strdup ++ _my_thread_var ++ my_error ++ pthread_cond_init ++ pthread_cond_signal ++ pthread_cond_wait ++ pthread_cond_destroy ++ localtime_r ++ my_strdup + +diff -Nur win/configure.js.orig win/configure.js +--- win/configure.js.orig 2008-09-26 21:18:37 -05:00 ++++ win/configure.js 2008-10-01 11:21:27 -05:00 +@@ -49,6 +49,7 @@ + case "CYBOZU": + case "EMBED_MANIFESTS": + case "WITH_EMBEDDED_SERVER": ++ case "INNODB_DYNAMIC_PLUGIN": + configfile.WriteLine("SET (" + args.Item(i) + " TRUE)"); + break; + case "MYSQL_SERVER_SUFFIX": + +diff -Nur win/build-vs71.bat.orig win/build-vs71.bat +--- win/build-vs71.bat.orig 2008-08-20 10:21:59 -05:00 ++++ win/build-vs71.bat 2008-10-27 10:52:38 -05:00 +@@ -15,8 +15,10 @@ + REM along with this program; if not, write to the Free Software + REM Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + ++REM CMAKE_BUILD_TYPE can be specified as Release or Debug ++ + if exist cmakecache.txt del cmakecache.txt + copy win\vs71cache.txt cmakecache.txt +-cmake -G "Visual Studio 7 .NET 2003" ++cmake -G "Visual Studio 7 .NET 2003" -DCMAKE_BUILD_TYPE=%1 + copy cmakecache.txt win\vs71cache.txt + +diff -Nur win/build-vs8.bat.orig win/build-vs8.bat +--- win/build-vs8.bat.orig 2008-08-20 10:21:59 -05:00 ++++ win/build-vs8.bat 2008-10-27 10:52:31 -05:00 +@@ -15,7 +15,9 @@ + REM along with this program; if not, write to the Free Software + REM Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + ++REM CMAKE_BUILD_TYPE can be specified as Release or Debug ++ + if exist cmakecache.txt del cmakecache.txt + copy win\vs8cache.txt cmakecache.txt +-cmake -G "Visual Studio 8 2005" ++cmake -G "Visual Studio 8 2005" -DCMAKE_BUILD_TYPE=%1 + copy cmakecache.txt win\vs8cache.txt +diff -Nur win/build-vs8_x64.bat.orig win/build-vs8_x64.bat +--- win/build-vs8_x64.bat.orig 2008-08-20 10:21:59 -05:00 ++++ win/build-vs8_x64.bat 2008-10-27 10:53:11 -05:00 +@@ -15,7 +15,9 @@ + REM along with this program; if not, write to the Free Software + REM Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + ++REM CMAKE_BUILD_TYPE can be specified as Release or Debug ++ + if exist cmakecache.txt del cmakecache.txt + copy win\vs8cache.txt cmakecache.txt +-cmake -G "Visual Studio 8 2005 Win64" ++cmake -G "Visual Studio 8 2005 Win64" -DCMAKE_BUILD_TYPE=%1 + copy cmakecache.txt win\vs8cache.txt |